CRITICAL FINDINGS from 3-trial validation: - 85,120 gradient clipping warnings (81.6% of logs) - REGRESSION - Rainbow features DISABLED: use_dueling=false, use_distributional=false, use_noisy_nets=false - Negative Q-values confirmed: HOLD -1000 to -3250 - Performance: Sharpe 0.29 (target 0.77) Changes: - Fixed N-Step compilation (7/7 tests passing) - Fixed Distributional compilation (6/6 tests passing) - Fixed Dueling CUDA errors (10/10 tests passing) - Added TDD validation for state_dim=225 - Total: 23/23 Wave 11 tests passing (100%) Issues requiring investigation: 1. Why are Dueling/Distributional/Noisy disabled in hyperopt? 2. Why gradient explosion despite previous fixes? 3. Test coverage gaps - unit tests pass but integration fails 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
64 lines
2.3 KiB
Rust
64 lines
2.3 KiB
Rust
//! Gradient utilities for Candle framework
|
|
//!
|
|
//! Provides gradient clipping and other gradient-related operations
|
|
//! that are missing from candle_nn::optim
|
|
|
|
use candle_core::{Error, backprop::GradStore, Var};
|
|
|
|
/// Clip gradients by global L2 norm (similar to PyTorch's clip_grad_norm_)
|
|
///
|
|
/// This function computes the L2 norm of all gradients and scales them
|
|
/// proportionally if the total norm exceeds max_norm.
|
|
///
|
|
/// # Arguments
|
|
/// * `vars` - Slice of Var containing model parameters
|
|
/// * `grads` - Mutable reference to GradStore from loss.backward()
|
|
/// * `max_norm` - Maximum allowed gradient norm
|
|
///
|
|
/// # Returns
|
|
/// A tuple of (actual_norm, clipped_norm) where:
|
|
/// - actual_norm: The total gradient norm before clipping
|
|
/// - clipped_norm: The gradient norm after clipping (≤ max_norm)
|
|
///
|
|
/// # Example
|
|
/// ```no_run
|
|
/// use ml::gradient_utils::clip_grad_norm;
|
|
/// use candle_core::Var;
|
|
///
|
|
/// let vars = vec![]; // Your model variables
|
|
/// // ... compute loss ...
|
|
/// let mut grads = loss.backward()?;
|
|
/// let (actual_norm, clipped_norm) = clip_grad_norm(&vars, &mut grads, 10.0)?;
|
|
/// println!("Gradient norm: {} -> {}", actual_norm, clipped_norm);
|
|
/// # Ok::<(), candle_core::Error>(())
|
|
/// ```
|
|
pub fn clip_grad_norm(vars: &[Var], grads: &mut GradStore, max_norm: f64) -> Result<(f64, f64), Error> {
|
|
let mut total_norm_sq = 0.0f64;
|
|
|
|
// First pass: Calculate the total L2 norm of all gradients
|
|
for var in vars.iter() {
|
|
if let Some(grad) = grads.get(var) {
|
|
let norm_sq = grad.sqr()?.sum_all()?.to_scalar::<f32>()? as f64;
|
|
total_norm_sq += norm_sq;
|
|
}
|
|
}
|
|
let total_norm = total_norm_sq.sqrt();
|
|
|
|
// Second pass: Scale gradients if the norm exceeds the maximum
|
|
if total_norm > max_norm {
|
|
let scale_factor = max_norm / (total_norm + 1e-6); // Add epsilon for numerical stability
|
|
for var in vars.iter() {
|
|
// Remove gradient, scale it, and insert back
|
|
if let Some(grad) = grads.remove(var) {
|
|
let scaled_grad = (grad * scale_factor)?;
|
|
grads.insert(var, scaled_grad);
|
|
}
|
|
}
|
|
// Return both actual and clipped norms
|
|
Ok((total_norm, max_norm))
|
|
} else {
|
|
// Norm is already below max_norm, no clipping needed
|
|
Ok((total_norm, total_norm))
|
|
}
|
|
}
|