Initial commit
This commit is contained in:
@@ -0,0 +1,573 @@
|
||||
//! Comprehensive tests for NovoGrad optimizer
|
||||
//!
|
||||
//! NovoGrad: Layer-wise gradient normalization with adaptive second moment averaging
|
||||
//! Key features tested:
|
||||
//! - Layer-wise gradient normalization: g_norm = g / ||g||
|
||||
//! - Adaptive second moment: v_t = β2 * v_{t-1} + (1-β2) * ||g||²
|
||||
//! - Momentum with normalized gradients: m_t = β1 * m_{t-1} + (lr / √(v_t + ε)) * g_norm
|
||||
//! - Weight update: θ_t+1 = θ_t - m_t - λ * θ_t (with weight decay)
|
||||
//! - Bias correction for early iterations
|
||||
//! - Gradient clipping support
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::{NovoGradOptimizer, NovoGradConfig, Optimizer};
|
||||
use crate::{Result, TransformerError};
|
||||
use rtx_tensor::{Tensor, Device, DType};
|
||||
use std::collections::HashMap;
|
||||
|
||||
// Test configuration defaults
|
||||
fn default_novograd_config() -> NovoGradConfig {
|
||||
NovoGradConfig {
|
||||
learning_rate: 1e-3,
|
||||
beta1: 0.95,
|
||||
beta2: 0.98,
|
||||
eps: 1e-8,
|
||||
weight_decay: 0.0,
|
||||
grad_averaging: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_config_validation() {
|
||||
// Valid config should pass
|
||||
let config = default_novograd_config();
|
||||
assert!(NovoGradOptimizer::new(config).is_ok());
|
||||
|
||||
// Invalid learning rate
|
||||
let mut invalid_config = default_novograd_config();
|
||||
invalid_config.learning_rate = -0.1;
|
||||
assert!(NovoGradOptimizer::new(invalid_config).is_err());
|
||||
|
||||
// Invalid beta1
|
||||
let mut invalid_config = default_novograd_config();
|
||||
invalid_config.beta1 = 1.1;
|
||||
assert!(NovoGradOptimizer::new(invalid_config).is_err());
|
||||
|
||||
// Invalid beta2
|
||||
let mut invalid_config = default_novograd_config();
|
||||
invalid_config.beta2 = -0.1;
|
||||
assert!(NovoGradOptimizer::new(invalid_config).is_err());
|
||||
|
||||
// Invalid eps
|
||||
let mut invalid_config = default_novograd_config();
|
||||
invalid_config.eps = -1e-8;
|
||||
assert!(NovoGradOptimizer::new(invalid_config).is_err());
|
||||
|
||||
// Invalid weight decay
|
||||
let mut invalid_config = default_novograd_config();
|
||||
invalid_config.weight_decay = -0.5;
|
||||
assert!(NovoGradOptimizer::new(invalid_config).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_parameter_creation() -> Result<()> {
|
||||
let config = default_novograd_config();
|
||||
let optimizer = NovoGradOptimizer::new(config)?;
|
||||
|
||||
assert_eq!(optimizer.learning_rate(), 1e-3);
|
||||
assert_eq!(optimizer.beta1(), 0.95);
|
||||
assert_eq!(optimizer.beta2(), 0.98);
|
||||
assert_eq!(optimizer.eps(), 1e-8);
|
||||
assert_eq!(optimizer.weight_decay(), 0.0);
|
||||
assert_eq!(optimizer.grad_averaging(), false);
|
||||
assert_eq!(optimizer.optimizer_type(), "NovoGrad");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_gradient_normalization() -> Result<()> {
|
||||
let config = NovoGradConfig {
|
||||
learning_rate: 0.01,
|
||||
beta1: 0.95,
|
||||
beta2: 0.98,
|
||||
eps: 1e-8,
|
||||
weight_decay: 0.0,
|
||||
grad_averaging: false,
|
||||
};
|
||||
|
||||
let mut optimizer = NovoGradOptimizer::new(config)?;
|
||||
let device = Device::cpu();
|
||||
|
||||
// Create test parameter and gradient with known norm
|
||||
let param = Tensor::from_data(vec![1.0, 2.0, 3.0, 4.0], &[4], &device)?;
|
||||
let grad = Tensor::from_data(vec![3.0, 4.0, 0.0, 0.0], &[4], &device)?; // ||grad|| = 5.0
|
||||
|
||||
// First step - should normalize gradient by its norm
|
||||
let updated_param1 = optimizer.step_param("test_param", ¶m, &grad)?;
|
||||
|
||||
// Verify state was created
|
||||
assert!(optimizer.has_state("test_param"));
|
||||
assert_eq!(optimizer.get_step_count("test_param")?, 1);
|
||||
|
||||
// For first step with normalized gradient: g_norm = [0.6, 0.8, 0.0, 0.0]
|
||||
// Second moment: v_1 = (1-0.98) * 25 = 0.5
|
||||
// Momentum: m_1 = (0.01 / √(0.5 + 1e-8)) * [0.6, 0.8, 0.0, 0.0]
|
||||
let param_data = param.to_cpu()?;
|
||||
let updated_data = updated_param1.to_cpu()?;
|
||||
|
||||
// Parameters should be updated in proportion to normalized gradient
|
||||
let update_ratio = (param_data[1] - updated_data[1]) / (param_data[0] - updated_data[0]);
|
||||
assert!((update_ratio - 4.0/3.0).abs() < 1e-6,
|
||||
"Update should be proportional to normalized gradient: expected 4/3, got {}", update_ratio);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_momentum_accumulation() -> Result<()> {
|
||||
let config = NovoGradConfig {
|
||||
learning_rate: 0.01,
|
||||
beta1: 0.95,
|
||||
beta2: 0.98,
|
||||
eps: 1e-8,
|
||||
weight_decay: 0.0,
|
||||
grad_averaging: false,
|
||||
};
|
||||
|
||||
let mut optimizer = NovoGradOptimizer::new(config)?;
|
||||
let device = Device::cpu();
|
||||
|
||||
let param = Tensor::from_data(vec![1.0], &[1], &device)?;
|
||||
let grad = Tensor::from_data(vec![2.0], &[1], &device)?; // ||grad|| = 2.0, normalized = 1.0
|
||||
|
||||
// Take multiple steps with same gradient
|
||||
let mut current_param = param.clone();
|
||||
let mut momentum_effects = Vec::new();
|
||||
|
||||
for step in 0..5 {
|
||||
let prev_data = current_param.to_cpu()?;
|
||||
current_param = optimizer.step_param("param", ¤t_param, &grad)?;
|
||||
let new_data = current_param.to_cpu()?;
|
||||
|
||||
let update_magnitude = (prev_data[0] - new_data[0]).abs();
|
||||
momentum_effects.push(update_magnitude);
|
||||
|
||||
assert_eq!(optimizer.get_step_count("param")?, step + 1);
|
||||
}
|
||||
|
||||
// Momentum should build up over iterations for consistent gradient direction
|
||||
// Later updates should be influenced by accumulated momentum
|
||||
assert!(momentum_effects.len() == 5, "Should have 5 momentum measurements");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_second_moment_averaging() -> Result<()> {
|
||||
let config = NovoGradConfig {
|
||||
learning_rate: 0.01,
|
||||
beta1: 0.95,
|
||||
beta2: 0.98,
|
||||
eps: 1e-8,
|
||||
weight_decay: 0.0,
|
||||
grad_averaging: false,
|
||||
};
|
||||
|
||||
let mut optimizer = NovoGradOptimizer::new(config)?;
|
||||
let device = Device::cpu();
|
||||
|
||||
let param = Tensor::from_data(vec![1.0], &[1], &device)?;
|
||||
|
||||
// Use different gradient magnitudes to test second moment averaging
|
||||
let gradients = vec![
|
||||
Tensor::from_data(vec![1.0], &[1], &device)?, // ||g|| = 1.0
|
||||
Tensor::from_data(vec![2.0], &[1], &device)?, // ||g|| = 2.0
|
||||
Tensor::from_data(vec![0.5], &[1], &device)?, // ||g|| = 0.5
|
||||
];
|
||||
|
||||
let mut current_param = param;
|
||||
for (step, grad) in gradients.iter().enumerate() {
|
||||
current_param = optimizer.step_param("param", ¤t_param, grad)?;
|
||||
assert_eq!(optimizer.get_step_count("param")?, step as i64 + 1);
|
||||
}
|
||||
|
||||
// The optimizer should have adapted to different gradient norms
|
||||
assert!(optimizer.has_state("param"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_bias_correction() -> Result<()> {
|
||||
let config = NovoGradConfig {
|
||||
learning_rate: 0.1,
|
||||
beta1: 0.95,
|
||||
beta2: 0.98,
|
||||
eps: 1e-8,
|
||||
weight_decay: 0.0,
|
||||
grad_averaging: false,
|
||||
};
|
||||
|
||||
let mut optimizer = NovoGradOptimizer::new(config)?;
|
||||
let device = Device::cpu();
|
||||
|
||||
let param = Tensor::from_data(vec![1.0], &[1], &device)?;
|
||||
let grad = Tensor::from_data(vec![1.0], &[1], &device)?;
|
||||
|
||||
// First few steps should show bias correction effects
|
||||
let mut update_magnitudes = Vec::new();
|
||||
let mut current_param = param;
|
||||
|
||||
for _step in 0..10 {
|
||||
let prev_data = current_param.to_cpu()?;
|
||||
current_param = optimizer.step_param("param", ¤t_param, &grad)?;
|
||||
let new_data = current_param.to_cpu()?;
|
||||
|
||||
let update_magnitude = (prev_data[0] - new_data[0]).abs();
|
||||
update_magnitudes.push(update_magnitude);
|
||||
}
|
||||
|
||||
// Early steps should have larger updates due to bias correction
|
||||
// Later steps should stabilize
|
||||
assert!(update_magnitudes[0] > update_magnitudes[9] * 0.5,
|
||||
"Early steps should benefit from bias correction");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_multi_parameter() -> Result<()> {
|
||||
let config = default_novograd_config();
|
||||
let mut optimizer = NovoGradOptimizer::new(config)?;
|
||||
let device = Device::cpu();
|
||||
|
||||
// Create multiple parameters with different layer characteristics
|
||||
let weight1 = Tensor::from_data(vec![1.0, 2.0, 3.0, 4.0], &[2, 2], &device)?;
|
||||
let weight2 = Tensor::from_data(vec![0.1, 0.2], &[2], &device)?;
|
||||
let bias = Tensor::from_data(vec![0.01], &[1], &device)?;
|
||||
|
||||
// Create gradients with different norms (simulating different layers)
|
||||
let weight1_grad = Tensor::from_data(vec![0.1, -0.1, 0.2, -0.2], &[2, 2], &device)?; // norm ≈ 0.316
|
||||
let weight2_grad = Tensor::from_data(vec![1.0, -1.0], &[2], &device)?; // norm ≈ 1.414
|
||||
let bias_grad = Tensor::from_data(vec![0.01], &[1], &device)?; // norm = 0.01
|
||||
|
||||
// Store gradients and process
|
||||
let mut gradients = HashMap::new();
|
||||
gradients.insert("weight1".to_string(), weight1_grad);
|
||||
gradients.insert("weight2".to_string(), weight2_grad);
|
||||
gradients.insert("bias".to_string(), bias_grad);
|
||||
|
||||
optimizer.set_gradients(gradients)?;
|
||||
let parameter_updates = optimizer.step(0.01)?;
|
||||
|
||||
// Verify updates exist for all parameters
|
||||
assert!(parameter_updates.contains_key("weight1"));
|
||||
assert!(parameter_updates.contains_key("weight2"));
|
||||
assert!(parameter_updates.contains_key("bias"));
|
||||
|
||||
// Verify shapes match
|
||||
assert_eq!(parameter_updates["weight1"].shape().dims(), &[2, 2]);
|
||||
assert_eq!(parameter_updates["weight2"].shape().dims(), &[2]);
|
||||
assert_eq!(parameter_updates["bias"].shape().dims(), &[1]);
|
||||
|
||||
// Verify state was created for all parameters
|
||||
assert!(optimizer.has_state("weight1"));
|
||||
assert!(optimizer.has_state("weight2"));
|
||||
assert!(optimizer.has_state("bias"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_weight_decay() -> Result<()> {
|
||||
let config = NovoGradConfig {
|
||||
learning_rate: 0.1,
|
||||
beta1: 0.95,
|
||||
beta2: 0.98,
|
||||
eps: 1e-8,
|
||||
weight_decay: 0.01,
|
||||
grad_averaging: false,
|
||||
};
|
||||
|
||||
let mut optimizer = NovoGradOptimizer::new(config)?;
|
||||
let device = Device::cpu();
|
||||
|
||||
let param = Tensor::from_data(vec![2.0], &[1], &device)?; // Non-zero parameter
|
||||
let grad = Tensor::from_data(vec![0.0], &[1], &device)?; // Zero gradient
|
||||
|
||||
// With zero gradient, only weight decay should affect parameter
|
||||
let updated_param = optimizer.step_param("param", ¶m, &grad)?;
|
||||
let param_data = param.to_cpu()?;
|
||||
let updated_data = updated_param.to_cpu()?;
|
||||
|
||||
// Weight decay should reduce parameter magnitude: θ_new = θ_old - λ * θ_old
|
||||
let expected = param_data[0] * (1.0 - 0.01);
|
||||
assert!((updated_data[0] - expected).abs() < 1e-6,
|
||||
"Weight decay should reduce parameter: expected {}, got {}", expected, updated_data[0]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_gradient_averaging() -> Result<()> {
|
||||
let config = NovoGradConfig {
|
||||
learning_rate: 0.01,
|
||||
beta1: 0.95,
|
||||
beta2: 0.98,
|
||||
eps: 1e-8,
|
||||
weight_decay: 0.0,
|
||||
grad_averaging: true,
|
||||
};
|
||||
|
||||
let mut optimizer = NovoGradOptimizer::new(config)?;
|
||||
let device = Device::cpu();
|
||||
|
||||
let param = Tensor::from_data(vec![1.0, 2.0], &[2], &device)?;
|
||||
let grad = Tensor::from_data(vec![3.0, 4.0], &[2], &device)?; // ||grad|| = 5.0
|
||||
|
||||
let updated_param = optimizer.step_param("param", ¶m, &grad)?;
|
||||
|
||||
// With gradient averaging enabled, behavior should differ from standard NovoGrad
|
||||
assert!(optimizer.has_state("param"));
|
||||
assert_eq!(optimizer.get_step_count("param")?, 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_learning_rate_updates() -> Result<()> {
|
||||
let config = default_novograd_config();
|
||||
let mut optimizer = NovoGradOptimizer::new(config)?;
|
||||
|
||||
assert_eq!(optimizer.learning_rate(), 1e-3);
|
||||
|
||||
// Update learning rate
|
||||
optimizer.set_learning_rate(0.01)?;
|
||||
assert_eq!(optimizer.learning_rate(), 0.01);
|
||||
|
||||
// Invalid learning rate should error
|
||||
assert!(optimizer.set_learning_rate(-0.1).is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_gradient_clipping_compatibility() -> Result<()> {
|
||||
let config = default_novograd_config();
|
||||
let mut optimizer = NovoGradOptimizer::new(config)?;
|
||||
let device = Device::cpu();
|
||||
|
||||
// Create large gradients that would need clipping
|
||||
let large_grad1 = Tensor::from_data(vec![10.0, 20.0], &[2], &device)?;
|
||||
let large_grad2 = Tensor::from_data(vec![30.0], &[1], &device)?;
|
||||
|
||||
let mut gradients = HashMap::new();
|
||||
gradients.insert("param1".to_string(), large_grad1);
|
||||
gradients.insert("param2".to_string(), large_grad2);
|
||||
|
||||
optimizer.set_gradients(gradients)?;
|
||||
|
||||
// Should be able to compute gradient norm before clipping
|
||||
let total_norm_squared = 10.0*10.0 + 20.0*20.0 + 30.0*30.0; // 100 + 400 + 900 = 1400
|
||||
let expected_norm = total_norm_squared.sqrt(); // ≈ 37.42
|
||||
|
||||
// The optimizer should handle large gradients without issues
|
||||
let parameter_updates = optimizer.step(0.01)?;
|
||||
assert_eq!(parameter_updates.len(), 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_zero_gradient_handling() -> Result<()> {
|
||||
let config = default_novograd_config();
|
||||
let mut optimizer = NovoGradOptimizer::new(config)?;
|
||||
let device = Device::cpu();
|
||||
|
||||
let param = Tensor::from_data(vec![1.0], &[1], &device)?;
|
||||
let zero_grad = Tensor::from_data(vec![0.0], &[1], &device)?;
|
||||
|
||||
// Zero gradient should not cause errors (gradient norm will be 0)
|
||||
let updated_param = optimizer.step_param("param", ¶m, &zero_grad)?;
|
||||
|
||||
let param_data = param.to_cpu()?;
|
||||
let updated_data = updated_param.to_cpu()?;
|
||||
|
||||
// With zero gradient and no weight decay, parameter should be unchanged
|
||||
// (normalized gradient is 0/0 which should be handled gracefully)
|
||||
assert!((updated_data[0] - param_data[0]).abs() < 1e-6,
|
||||
"Zero gradient should not change parameter significantly");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_convergence_quadratic() -> Result<()> {
|
||||
let config = NovoGradConfig {
|
||||
learning_rate: 0.1,
|
||||
beta1: 0.95,
|
||||
beta2: 0.98,
|
||||
eps: 1e-8,
|
||||
weight_decay: 0.0,
|
||||
grad_averaging: false,
|
||||
};
|
||||
|
||||
let mut optimizer = NovoGradOptimizer::new(config)?;
|
||||
let device = Device::cpu();
|
||||
|
||||
// Optimize f(x) = (x - 3)^2, minimum at x = 3
|
||||
let target = 3.0;
|
||||
let mut param = Tensor::from_data(vec![0.0], &[1], &device)?;
|
||||
let mut loss_history = Vec::new();
|
||||
|
||||
for _step in 0..50 {
|
||||
let param_data = param.to_cpu()?;
|
||||
let current_value = param_data[0];
|
||||
let loss = (current_value - target).powi(2);
|
||||
loss_history.push(loss);
|
||||
|
||||
// Gradient of (x - 3)^2 is 2(x - 3)
|
||||
let gradient_value = 2.0 * (current_value - target);
|
||||
let grad = Tensor::from_data(vec![gradient_value], &[1], &device)?;
|
||||
|
||||
param = optimizer.step_param("param", ¶m, &grad)?;
|
||||
}
|
||||
|
||||
let final_data = param.to_cpu()?;
|
||||
let final_value = final_data[0];
|
||||
let final_loss = (final_value - target).powi(2);
|
||||
|
||||
// Should converge reasonably close to target
|
||||
assert!(
|
||||
(final_value - target).abs() < 0.5,
|
||||
"Should converge close to target, got {} vs {}",
|
||||
final_value, target
|
||||
);
|
||||
|
||||
// Loss should decrease significantly
|
||||
assert!(
|
||||
final_loss < loss_history[0] * 0.1,
|
||||
"Loss should decrease significantly from {} to {}",
|
||||
loss_history[0], final_loss
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_state_management() -> Result<()> {
|
||||
let config = default_novograd_config();
|
||||
let mut optimizer = NovoGradOptimizer::new(config)?;
|
||||
let device = Device::cpu();
|
||||
|
||||
let param = Tensor::from_data(vec![1.0, 2.0, 3.0], &[3], &device)?;
|
||||
let grad = Tensor::from_data(vec![0.1, 0.2, 0.3], &[3], &device)?;
|
||||
|
||||
// Initialize state
|
||||
optimizer.step_param("param", ¶m, &grad)?;
|
||||
|
||||
// NovoGrad should store momentum and second moment (per-layer)
|
||||
assert!(optimizer.has_state("param"));
|
||||
|
||||
// Reset state and verify cleanup
|
||||
optimizer.reset_state("param")?;
|
||||
assert!(!optimizer.has_state("param"));
|
||||
|
||||
// Reset all state
|
||||
optimizer.step_param("param1", ¶m, &grad)?;
|
||||
optimizer.step_param("param2", ¶m, &grad)?;
|
||||
optimizer.reset_all_state();
|
||||
assert!(!optimizer.has_state("param1"));
|
||||
assert!(!optimizer.has_state("param2"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_gradient_shapes() -> Result<()> {
|
||||
let config = default_novograd_config();
|
||||
let mut optimizer = NovoGradOptimizer::new(config)?;
|
||||
let device = Device::cpu();
|
||||
|
||||
let param = Tensor::from_data(vec![1.0, 2.0], &[2], &device)?;
|
||||
let wrong_grad = Tensor::from_data(vec![0.1], &[1], &device)?;
|
||||
|
||||
// Shape mismatch should error
|
||||
let result = optimizer.step_param("param", ¶m, &wrong_grad);
|
||||
assert!(result.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_deterministic_behavior() -> Result<()> {
|
||||
let config = default_novograd_config();
|
||||
let device = Device::cpu();
|
||||
|
||||
// Run same optimization twice
|
||||
let mut results1 = Vec::new();
|
||||
let mut results2 = Vec::new();
|
||||
|
||||
for trial in 0..2 {
|
||||
let mut optimizer = NovoGradOptimizer::new(config.clone())?;
|
||||
let param = Tensor::from_data(vec![1.0], &[1], &device)?;
|
||||
let grad = Tensor::from_data(vec![0.5], &[1], &device)?;
|
||||
|
||||
let mut current_param = param;
|
||||
for _step in 0..5 {
|
||||
current_param = optimizer.step_param("param", ¤t_param, &grad)?;
|
||||
let value = current_param.to_cpu()?[0];
|
||||
|
||||
if trial == 0 {
|
||||
results1.push(value);
|
||||
} else {
|
||||
results2.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Results should be identical (deterministic)
|
||||
assert_eq!(results1.len(), results2.len());
|
||||
for (r1, r2) in results1.iter().zip(results2.iter()) {
|
||||
assert!(
|
||||
(r1 - r2).abs() < 1e-6,
|
||||
"Results should be deterministic: {} vs {}",
|
||||
r1, r2
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_novograd_large_batch_stability() -> Result<()> {
|
||||
// NovoGrad is particularly designed for large batch training
|
||||
let config = NovoGradConfig {
|
||||
learning_rate: 0.01,
|
||||
beta1: 0.95,
|
||||
beta2: 0.98,
|
||||
eps: 1e-8,
|
||||
weight_decay: 0.0,
|
||||
grad_averaging: false,
|
||||
};
|
||||
|
||||
let mut optimizer = NovoGradOptimizer::new(config)?;
|
||||
let device = Device::cpu();
|
||||
|
||||
// Simulate large batch gradients (high variance)
|
||||
let param = Tensor::from_data(vec![1.0, 2.0, 3.0], &[3], &device)?;
|
||||
|
||||
let batch_gradients = vec![
|
||||
Tensor::from_data(vec![1.0, 0.1, 0.01], &[3], &device)?, // Different scales
|
||||
Tensor::from_data(vec![0.01, 1.0, 0.1], &[3], &device)?,
|
||||
Tensor::from_data(vec![0.1, 0.01, 1.0], &[3], &device)?,
|
||||
];
|
||||
|
||||
let mut current_param = param;
|
||||
for (step, grad) in batch_gradients.iter().enumerate() {
|
||||
let prev_param = current_param.clone();
|
||||
current_param = optimizer.step_param("param", ¤t_param, grad)?;
|
||||
|
||||
// Should remain stable and not explode
|
||||
let param_data = current_param.to_cpu()?;
|
||||
for &value in param_data.iter() {
|
||||
assert!(value.is_finite(), "Parameter should remain finite at step {}", step);
|
||||
assert!(value.abs() < 100.0, "Parameter should not explode at step {}", step);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user