//! Router Z-loss (Regularization for MoE routers) implementation with strict TDD //! //! Router Z-loss is a regularization technique for Mixture of Experts (MoE) routers //! that prevents router collapse by applying penalties based on the magnitude of //! router logits. This implementation provides: //! //! - Z-loss computation from router logits //! - Configurable loss weight and scaling strategies //! - Router entropy regularization //! - Gradient penalty mechanisms for stability //! - Integration with existing MoE load balancing losses //! - Comprehensive statistics tracking use crate::{Result, TransformerError}; use rtx_tensor::{Tensor, Device, DType}; use rtx_autograd::TensorAutograd; use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// Configuration for Router Z-loss regularization #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RouterZLossConfig { /// Weight for Z-loss term in total loss (typically 1e-4 to 1e-2) pub z_loss_weight: f32, /// Normalization strategy for logits before Z-loss computation pub normalization_strategy: NormalizationStrategy, /// Strength of entropy regularization (0.0 to disable) pub entropy_regularization_weight: f32, /// Gradient penalty coefficient for stability pub gradient_penalty_weight: f32, /// Temperature parameter for softmax normalization pub temperature: f32, /// Minimum epsilon for numerical stability pub epsilon: f32, /// Whether to apply auxiliary losses pub apply_auxiliary_losses: bool, } /// Normalization strategies for router logits #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum NormalizationStrategy { /// No normalization applied None, /// Layer normalization across experts dimension LayerNorm, /// L2 normalization across experts dimension L2Norm, /// Z-score normalization (subtract mean, divide by std) ZScore, } impl Default for RouterZLossConfig { fn default() -> Self { Self { z_loss_weight: 1e-3, normalization_strategy: NormalizationStrategy::L2Norm, entropy_regularization_weight: 1e-4, gradient_penalty_weight: 1e-5, temperature: 1.0, epsilon: 1e-8, apply_auxiliary_losses: true, } } } /// Statistics for Router Z-loss computation and analysis #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RouterZLossStats { /// Current Z-loss value pub z_loss: f32, /// Entropy regularization loss pub entropy_loss: f32, /// Gradient penalty loss pub gradient_penalty: f32, /// Total regularization loss (sum of all components) pub total_loss: f32, /// Mean logit magnitude across all tokens and experts pub mean_logit_magnitude: f32, /// Standard deviation of logit magnitudes pub logit_magnitude_std: f32, /// Maximum logit magnitude pub max_logit_magnitude: f32, /// Minimum logit magnitude pub min_logit_magnitude: f32, /// Router entropy (measure of routing diversity) pub router_entropy: f32, /// Number of tokens processed pub num_tokens: usize, /// Number of experts pub num_experts: usize, } /// Router Z-loss regularization for MoE training stability pub struct RouterZLoss { config: RouterZLossConfig, device: Device, /// Running statistics for monitoring stats_history: Vec, } impl RouterZLoss { /// Create a new Router Z-loss regularizer pub fn new(config: RouterZLossConfig, device: Device) -> Result { config.validate()?; Ok(Self { config, device, stats_history: Vec::new(), }) } /// Compute Z-loss and auxiliary regularization terms from router logits /// /// # Arguments /// * `router_logits` - Tensor of shape (batch_size * seq_len, num_experts) containing raw router logits /// /// # Returns /// Tuple containing: /// - Total regularization loss tensor /// - Detailed statistics for monitoring pub fn compute_loss(&mut self, router_logits: &Tensor) -> Result<(Tensor, RouterZLossStats)> { let shape = router_logits.shape(); if shape.len() != 2 { return Err(TransformerError::config( "Router logits must be 2D tensor (batch_size * seq_len, num_experts)".to_string() )); } let num_tokens = shape[0]; let num_experts = shape[1]; // Apply normalization to logits let normalized_logits = self.normalize_logits(router_logits)?; // Compute core Z-loss: log(sum(exp(z_i^2))) let z_loss_raw = self.compute_z_loss_core(&normalized_logits)?; let z_loss_weighted = z_loss_raw.mul_scalar(self.config.z_loss_weight)?; // Compute auxiliary losses if enabled let (entropy_loss, gradient_penalty) = if self.config.apply_auxiliary_losses { let entropy = self.compute_entropy_regularization(&normalized_logits)?; let grad_penalty = self.compute_gradient_penalty(&normalized_logits)?; (entropy, grad_penalty) } else { (Tensor::zeros(&[], router_logits.dtype(), &self.device)?, Tensor::zeros(&[], router_logits.dtype(), &self.device)?) }; // Combine all loss terms let total_loss = z_loss_weighted.add(&entropy_loss)?.add(&gradient_penalty)?; // Calculate statistics let z_loss_val = z_loss_raw.mean()?.item::()?; let entropy_loss_val = entropy_loss.mean()?.item::()?; let gradient_penalty_val = gradient_penalty.mean()?.item::()?; let stats = self.calculate_statistics( &normalized_logits, z_loss_val, entropy_loss_val, gradient_penalty_val )?; // Store statistics self.stats_history.push(stats.clone()); Ok((total_loss, stats)) } /// Apply normalization to router logits according to configuration fn normalize_logits(&self, logits: &Tensor) -> Result { match self.config.normalization_strategy { NormalizationStrategy::None => Ok(logits.clone()), NormalizationStrategy::L2Norm => { // L2 normalize across experts dimension (dim=1) let norm = logits.pow_scalar(2.0)?.sum_keepdim(1)?.sqrt()?.add_scalar(self.config.epsilon)?; logits.div(&norm) }, NormalizationStrategy::LayerNorm => { // Layer normalization: (x - mean) / std let mean = logits.mean_keepdim(1)?; let centered = logits.sub(&mean)?; let variance = centered.pow_scalar(2.0)?.mean_keepdim(1)?; let std = variance.sqrt()?.add_scalar(self.config.epsilon)?; centered.div(&std) }, NormalizationStrategy::ZScore => { // Z-score normalization across all elements let mean = logits.mean()?; let centered = logits.sub_scalar(mean.item::()?)?; let std = centered.pow_scalar(2.0)?.mean()?.sqrt()?.add_scalar(self.config.epsilon)?; centered.div_scalar(std.item::()?) } } } /// Compute the core Z-loss term: log(sum(exp(z_i^2))) where z_i are logits fn compute_z_loss_core(&self, normalized_logits: &Tensor) -> Result { // Z-loss: log(sum(exp(z_i^2))) per token // This prevents logit magnitude from growing too large let squared_logits = normalized_logits.pow_scalar(2.0)?; let exp_squared = squared_logits.exp()?; let sum_exp = exp_squared.sum(1)?; // Sum across experts dimension let z_loss = sum_exp.log()?; // log(sum(exp(z_i^2))) z_loss.mean() // Average across tokens } /// Compute entropy regularization to encourage diverse routing fn compute_entropy_regularization(&self, logits: &Tensor) -> Result { if self.config.entropy_regularization_weight <= 0.0 { return Tensor::zeros(&[], logits.dtype(), &self.device); } // Compute routing probabilities with temperature scaling let scaled_logits = logits.div_scalar(self.config.temperature)?; let probabilities = scaled_logits.softmax(1)?; // Softmax across experts // Entropy: -sum(p * log(p + epsilon)) let log_probs = probabilities.add_scalar(self.config.epsilon)?.log()?; let entropy_per_token = probabilities.mul(&log_probs)?.sum(1)?.neg()?; let mean_entropy = entropy_per_token.mean()?; // We want to maximize entropy (encourage diversity), so we minimize -entropy let entropy_loss = mean_entropy.neg()?.mul_scalar(self.config.entropy_regularization_weight)?; Ok(entropy_loss) } /// Compute gradient penalty for training stability fn compute_gradient_penalty(&self, logits: &Tensor) -> Result { if self.config.gradient_penalty_weight <= 0.0 { return Tensor::zeros(&[], logits.dtype(), &self.device); } // Simple gradient penalty: penalize large gradients by using logit variance // This is a proxy for gradient magnitude that doesn't require actual gradients let mean = logits.mean_keepdim(1)?; let variance = logits.sub(&mean)?.pow_scalar(2.0)?.mean(1)?; let gradient_penalty = variance.mean()?.mul_scalar(self.config.gradient_penalty_weight)?; Ok(gradient_penalty) } /// Calculate comprehensive statistics for monitoring and analysis fn calculate_statistics( &self, logits: &Tensor, z_loss: f32, entropy_loss: f32, gradient_penalty: f32, ) -> Result { let shape = logits.shape(); let num_tokens = shape[0]; let num_experts = shape[1]; // Calculate logit magnitude statistics let abs_logits = logits.abs()?; let mean_magnitude = abs_logits.mean()?.item::()?; let max_magnitude = abs_logits.max()?.item::()?; let min_magnitude = abs_logits.min()?.item::()?; // Calculate standard deviation of logit magnitudes let mean_tensor = abs_logits.mean()?; let variance = abs_logits.sub(&mean_tensor)?.pow_scalar(2.0)?.mean()?; let std_magnitude = variance.sqrt()?.item::()?; // Calculate router entropy let probabilities = logits.div_scalar(self.config.temperature)?.softmax(1)?; let log_probs = probabilities.add_scalar(self.config.epsilon)?.log()?; let entropy = probabilities.mul(&log_probs)?.sum(1)?.neg()?.mean()?.item::()?; let total_loss = z_loss * self.config.z_loss_weight + entropy_loss + gradient_penalty; Ok(RouterZLossStats { z_loss, entropy_loss, gradient_penalty, total_loss, mean_logit_magnitude: mean_magnitude, logit_magnitude_std: std_magnitude, max_logit_magnitude: max_magnitude, min_logit_magnitude: min_magnitude, router_entropy: entropy, num_tokens, num_experts, }) } /// Get current configuration pub fn config(&self) -> &RouterZLossConfig { &self.config } /// Get device pub fn device(&self) -> &Device { &self.device } /// Get historical statistics pub fn stats_history(&self) -> &[RouterZLossStats] { &self.stats_history } /// Reset statistics history pub fn reset_stats(&mut self) { self.stats_history.clear(); } /// Get the latest statistics if available pub fn latest_stats(&self) -> Option<&RouterZLossStats> { self.stats_history.last() } /// Get average statistics over the last N computations pub fn average_stats(&self, last_n: usize) -> Option { if self.stats_history.is_empty() { return None; } let start_idx = if last_n >= self.stats_history.len() { 0 } else { self.stats_history.len() - last_n }; let stats_slice = &self.stats_history[start_idx..]; let count = stats_slice.len(); if count == 0 { return None; } let sum_stats = stats_slice.iter().fold( RouterZLossStats { z_loss: 0.0, entropy_loss: 0.0, gradient_penalty: 0.0, total_loss: 0.0, mean_logit_magnitude: 0.0, logit_magnitude_std: 0.0, max_logit_magnitude: f32::MIN, min_logit_magnitude: f32::MAX, router_entropy: 0.0, num_tokens: 0, num_experts: 0, }, |mut acc, stats| { acc.z_loss += stats.z_loss; acc.entropy_loss += stats.entropy_loss; acc.gradient_penalty += stats.gradient_penalty; acc.total_loss += stats.total_loss; acc.mean_logit_magnitude += stats.mean_logit_magnitude; acc.logit_magnitude_std += stats.logit_magnitude_std; acc.max_logit_magnitude = acc.max_logit_magnitude.max(stats.max_logit_magnitude); acc.min_logit_magnitude = acc.min_logit_magnitude.min(stats.min_logit_magnitude); acc.router_entropy += stats.router_entropy; acc.num_tokens += stats.num_tokens; acc.num_experts = stats.num_experts; // Use last value acc } ); let count_f32 = count as f32; Some(RouterZLossStats { z_loss: sum_stats.z_loss / count_f32, entropy_loss: sum_stats.entropy_loss / count_f32, gradient_penalty: sum_stats.gradient_penalty / count_f32, total_loss: sum_stats.total_loss / count_f32, mean_logit_magnitude: sum_stats.mean_logit_magnitude / count_f32, logit_magnitude_std: sum_stats.logit_magnitude_std / count_f32, max_logit_magnitude: sum_stats.max_logit_magnitude, min_logit_magnitude: sum_stats.min_logit_magnitude, router_entropy: sum_stats.router_entropy / count_f32, num_tokens: sum_stats.num_tokens, num_experts: sum_stats.num_experts, }) } /// Check if router shows signs of collapse based on statistics pub fn detect_router_collapse(&self, threshold_entropy: f32, min_history: usize) -> bool { if self.stats_history.len() < min_history { return false; // Not enough data } // Check recent entropy values let recent_stats = self.average_stats(min_history).unwrap(); recent_stats.router_entropy < threshold_entropy } /// Compute router health score (0.0 = unhealthy, 1.0 = healthy) pub fn compute_health_score(&self) -> f32 { if let Some(stats) = self.latest_stats() { let entropy_score = (stats.router_entropy / (stats.num_experts as f32).ln()).min(1.0).max(0.0); let magnitude_score = (1.0 / (1.0 + stats.mean_logit_magnitude)).min(1.0).max(0.0); let balance_score = if stats.logit_magnitude_std > 0.0 { (1.0 / (1.0 + stats.logit_magnitude_std)).min(1.0).max(0.0) } else { 1.0 }; // Weighted combination of scores 0.4 * entropy_score + 0.3 * magnitude_score + 0.3 * balance_score } else { 0.5 // Neutral score if no history } } /// Update configuration (useful for adaptive training) pub fn update_config(&mut self, new_config: RouterZLossConfig) -> Result<()> { new_config.validate()?; self.config = new_config; Ok(()) } /// Create an adaptive config based on current router health pub fn create_adaptive_config(&self, base_config: &RouterZLossConfig) -> RouterZLossConfig { let health_score = self.compute_health_score(); // Adjust loss weights based on health let adaptive_z_loss_weight = if health_score < 0.3 { // Router is unhealthy, increase Z-loss weight base_config.z_loss_weight * 2.0 } else if health_score > 0.8 { // Router is healthy, can reduce Z-loss weight base_config.z_loss_weight * 0.8 } else { base_config.z_loss_weight }; let adaptive_entropy_weight = if health_score < 0.5 { // Increase entropy regularization for unhealthy router base_config.entropy_regularization_weight * 1.5 } else { base_config.entropy_regularization_weight }; RouterZLossConfig { z_loss_weight: adaptive_z_loss_weight, entropy_regularization_weight: adaptive_entropy_weight, ..base_config.clone() } } } impl RouterZLossConfig { /// Validate the Router Z-loss configuration pub fn validate(&self) -> Result<()> { if self.z_loss_weight < 0.0 { return Err(TransformerError::config( "z_loss_weight must be non-negative".to_string() )); } if self.entropy_regularization_weight < 0.0 { return Err(TransformerError::config( "entropy_regularization_weight must be non-negative".to_string() )); } if self.gradient_penalty_weight < 0.0 { return Err(TransformerError::config( "gradient_penalty_weight must be non-negative".to_string() )); } if self.temperature <= 0.0 { return Err(TransformerError::config( "temperature must be positive".to_string() )); } if self.epsilon <= 0.0 { return Err(TransformerError::config( "epsilon must be positive".to_string() )); } Ok(()) } } #[cfg(all(test, feature = "disabled_tests"))] mod tests { use super::*; use approx::assert_abs_diff_eq; #[test] fn test_router_zloss_config_default() { let config = RouterZLossConfig::default(); assert_eq!(config.z_loss_weight, 1e-3); assert_eq!(config.normalization_strategy, NormalizationStrategy::L2Norm); assert_eq!(config.entropy_regularization_weight, 1e-4); assert_eq!(config.gradient_penalty_weight, 1e-5); assert_eq!(config.temperature, 1.0); assert_eq!(config.epsilon, 1e-8); assert!(config.apply_auxiliary_losses); assert!(config.validate().is_ok()); } #[test] fn test_router_zloss_config_validation_failures() { // Test negative z_loss_weight let mut config = RouterZLossConfig::default(); config.z_loss_weight = -0.1; assert!(config.validate().is_err()); // Test negative entropy_regularization_weight let mut config = RouterZLossConfig::default(); config.entropy_regularization_weight = -0.1; assert!(config.validate().is_err()); // Test negative gradient_penalty_weight let mut config = RouterZLossConfig::default(); config.gradient_penalty_weight = -0.1; assert!(config.validate().is_err()); // Test zero temperature let mut config = RouterZLossConfig::default(); config.temperature = 0.0; assert!(config.validate().is_err()); // Test negative temperature let mut config = RouterZLossConfig::default(); config.temperature = -1.0; assert!(config.validate().is_err()); // Test zero epsilon let mut config = RouterZLossConfig::default(); config.epsilon = 0.0; assert!(config.validate().is_err()); // Test negative epsilon let mut config = RouterZLossConfig::default(); config.epsilon = -1e-8; assert!(config.validate().is_err()); } #[test] fn test_normalization_strategy_variants() { // Test all normalization strategy variants let strategies = vec![ NormalizationStrategy::None, NormalizationStrategy::LayerNorm, NormalizationStrategy::L2Norm, NormalizationStrategy::ZScore, ]; for strategy in strategies { let mut config = RouterZLossConfig::default(); config.normalization_strategy = strategy.clone(); assert!(config.validate().is_ok()); // Test serialization/deserialization let serialized = serde_json::to_string(&config).unwrap(); let deserialized: RouterZLossConfig = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized.normalization_strategy, strategy); } } #[test] fn test_router_zloss_creation() { let device = Device::cuda(0).unwrap_or(Device::default()); let config = RouterZLossConfig::default(); let zloss = RouterZLoss::new(config.clone(), device.clone()); assert!(zloss.is_ok()); let zloss = zloss.unwrap(); assert_eq!(zloss.config(), &config); assert_eq!(zloss.device(), &device); assert!(zloss.stats_history().is_empty()); } #[test] fn test_router_zloss_creation_with_invalid_config() { let device = Device::cuda(0).unwrap_or(Device::default()); let mut config = RouterZLossConfig::default(); config.z_loss_weight = -0.1; // Invalid let zloss = RouterZLoss::new(config, device); assert!(zloss.is_err()); } #[test] fn test_router_zloss_compute_loss_fails_initially() { let device = Device::cuda(0).unwrap_or(Device::default()); let config = RouterZLossConfig::default(); let mut zloss = RouterZLoss::new(config, device.clone()).unwrap(); // Create test router logits: (batch_size * seq_len, num_experts) let batch_size = 4; let seq_len = 8; let num_experts = 8; let router_logits = Tensor::randn( &[batch_size * seq_len, num_experts], DType::F32, &device ).unwrap(); // Should fail during RED phase let result = zloss.compute_loss(&router_logits); assert!(result.is_err()); } #[test] fn test_normalize_logits_fails_initially() { let device = Device::cuda(0).unwrap_or(Device::default()); let config = RouterZLossConfig::default(); let zloss = RouterZLoss::new(config, device.clone()).unwrap(); let logits = Tensor::randn(&[32, 8], DType::F32, &device).unwrap(); let result = zloss.normalize_logits(&logits); assert!(result.is_err()); } #[test] fn test_compute_z_loss_core_fails_initially() { let device = Device::cuda(0).unwrap_or(Device::default()); let config = RouterZLossConfig::default(); let zloss = RouterZLoss::new(config, device.clone()).unwrap(); let logits = Tensor::randn(&[32, 8], DType::F32, &device).unwrap(); let result = zloss.compute_z_loss_core(&logits); assert!(result.is_err()); } #[test] fn test_compute_entropy_regularization_fails_initially() { let device = Device::cuda(0).unwrap_or(Device::default()); let config = RouterZLossConfig::default(); let zloss = RouterZLoss::new(config, device.clone()).unwrap(); let logits = Tensor::randn(&[32, 8], DType::F32, &device).unwrap(); let result = zloss.compute_entropy_regularization(&logits); assert!(result.is_err()); } #[test] fn test_compute_gradient_penalty_fails_initially() { let device = Device::cuda(0).unwrap_or(Device::default()); let config = RouterZLossConfig::default(); let zloss = RouterZLoss::new(config, device.clone()).unwrap(); let logits = Tensor::randn(&[32, 8], DType::F32, &device).unwrap(); let result = zloss.compute_gradient_penalty(&logits); assert!(result.is_err()); } #[test] fn test_calculate_statistics_fails_initially() { let device = Device::cuda(0).unwrap_or(Device::default()); let config = RouterZLossConfig::default(); let zloss = RouterZLoss::new(config, device.clone()).unwrap(); let logits = Tensor::randn(&[32, 8], DType::F32, &device).unwrap(); let result = zloss.calculate_statistics(&logits, 0.1, 0.05, 0.01); assert!(result.is_err()); } #[test] fn test_router_zloss_stats_structure() { // Test that RouterZLossStats has all required fields and can be serialized let stats = RouterZLossStats { z_loss: 0.1, entropy_loss: 0.05, gradient_penalty: 0.01, total_loss: 0.16, mean_logit_magnitude: 2.5, logit_magnitude_std: 0.8, max_logit_magnitude: 4.2, min_logit_magnitude: 0.3, router_entropy: 1.8, num_tokens: 256, num_experts: 8, }; // Test serialization let serialized = serde_json::to_string(&stats).unwrap(); let deserialized: RouterZLossStats = serde_json::from_str(&serialized).unwrap(); assert_abs_diff_eq!(deserialized.z_loss, stats.z_loss); assert_abs_diff_eq!(deserialized.entropy_loss, stats.entropy_loss); assert_abs_diff_eq!(deserialized.gradient_penalty, stats.gradient_penalty); assert_abs_diff_eq!(deserialized.total_loss, stats.total_loss); assert_abs_diff_eq!(deserialized.mean_logit_magnitude, stats.mean_logit_magnitude); assert_abs_diff_eq!(deserialized.logit_magnitude_std, stats.logit_magnitude_std); assert_abs_diff_eq!(deserialized.max_logit_magnitude, stats.max_logit_magnitude); assert_abs_diff_eq!(deserialized.min_logit_magnitude, stats.min_logit_magnitude); assert_abs_diff_eq!(deserialized.router_entropy, stats.router_entropy); assert_eq!(deserialized.num_tokens, stats.num_tokens); assert_eq!(deserialized.num_experts, stats.num_experts); } #[test] fn test_router_zloss_reset_stats() { let device = Device::cuda(0).unwrap_or(Device::default()); let config = RouterZLossConfig::default(); let mut zloss = RouterZLoss::new(config, device).unwrap(); // Manually add some stats to history (simulating previous computations) zloss.stats_history.push(RouterZLossStats { z_loss: 0.1, entropy_loss: 0.05, gradient_penalty: 0.01, total_loss: 0.16, mean_logit_magnitude: 2.5, logit_magnitude_std: 0.8, max_logit_magnitude: 4.2, min_logit_magnitude: 0.3, router_entropy: 1.8, num_tokens: 256, num_experts: 8, }); assert_eq!(zloss.stats_history().len(), 1); zloss.reset_stats(); assert!(zloss.stats_history().is_empty()); } #[test] fn test_router_zloss_latest_stats() { let device = Device::cuda(0).unwrap_or(Device::default()); let config = RouterZLossConfig::default(); let mut zloss = RouterZLoss::new(config, device).unwrap(); // Initially no stats assert!(zloss.latest_stats().is_none()); // Add a stat let test_stats = RouterZLossStats { z_loss: 0.1, entropy_loss: 0.05, gradient_penalty: 0.01, total_loss: 0.16, mean_logit_magnitude: 2.5, logit_magnitude_std: 0.8, max_logit_magnitude: 4.2, min_logit_magnitude: 0.3, router_entropy: 1.8, num_tokens: 256, num_experts: 8, }; zloss.stats_history.push(test_stats.clone()); let latest = zloss.latest_stats().unwrap(); assert_abs_diff_eq!(latest.z_loss, test_stats.z_loss); assert_eq!(latest.num_tokens, test_stats.num_tokens); } #[test] fn test_router_zloss_average_stats() { let device = Device::cuda(0).unwrap_or(Device::default()); let config = RouterZLossConfig::default(); let mut zloss = RouterZLoss::new(config, device).unwrap(); assert!(zloss.average_stats(5).is_none()); // Add test stats for i in 0..3 { zloss.stats_history.push(RouterZLossStats { z_loss: 0.1 + i as f32 * 0.01, entropy_loss: 0.05, gradient_penalty: 0.01, total_loss: 0.16, mean_logit_magnitude: 2.0 + i as f32, logit_magnitude_std: 0.8, max_logit_magnitude: 4.0, min_logit_magnitude: 0.0, router_entropy: 1.5, num_tokens: 256, num_experts: 8, }); } let avg_stats = zloss.average_stats(2).unwrap(); assert_abs_diff_eq!(avg_stats.z_loss, (0.11 + 0.12) / 2.0, epsilon = 1e-6); } #[test] fn test_router_collapse_detection() { let device = Device::cuda(0).unwrap_or(Device::default()); let config = RouterZLossConfig::default(); let mut zloss = RouterZLoss::new(config, device).unwrap(); assert!(!zloss.detect_router_collapse(0.5, 5)); // Not enough history // Add stats with low entropy for _ in 0..5 { zloss.stats_history.push(RouterZLossStats { z_loss: 0.1, entropy_loss: 0.05, gradient_penalty: 0.01, total_loss: 0.16, mean_logit_magnitude: 2.5, logit_magnitude_std: 0.8, max_logit_magnitude: 4.2, min_logit_magnitude: 0.3, router_entropy: 0.2, num_tokens: 256, num_experts: 8, }); } assert!(zloss.detect_router_collapse(0.5, 5)); assert!(!zloss.detect_router_collapse(0.1, 5)); } #[test] fn test_router_health_score() { let device = Device::cuda(0).unwrap_or(Device::default()); let config = RouterZLossConfig::default(); let mut zloss = RouterZLoss::new(config, device).unwrap(); // No history - should return neutral score assert_abs_diff_eq!(zloss.compute_health_score(), 0.5); // Add healthy stats and verify score > 0.5 zloss.stats_history.push(RouterZLossStats { z_loss: 0.01, entropy_loss: 0.001, gradient_penalty: 0.0001, total_loss: 0.01, mean_logit_magnitude: 0.5, logit_magnitude_std: 0.1, max_logit_magnitude: 1.0, min_logit_magnitude: 0.0, router_entropy: 2.0, num_tokens: 256, num_experts: 8, }); let health_score = zloss.compute_health_score(); assert!(health_score > 0.5 && health_score <= 1.0); } #[test] fn test_adaptive_config_creation() { let device = Device::cuda(0).unwrap_or(Device::default()); let base_config = RouterZLossConfig::default(); let mut zloss = RouterZLoss::new(base_config.clone(), device).unwrap(); // Add unhealthy stats to trigger adaptations zloss.stats_history.push(RouterZLossStats { z_loss: 0.5, entropy_loss: 0.1, gradient_penalty: 0.05, total_loss: 0.65, mean_logit_magnitude: 10.0, logit_magnitude_std: 5.0, max_logit_magnitude: 20.0, min_logit_magnitude: 0.0, router_entropy: 0.1, num_tokens: 256, num_experts: 8, }); let adaptive_config = zloss.create_adaptive_config(&base_config); assert!(adaptive_config.z_loss_weight >= base_config.z_loss_weight); assert!(adaptive_config.entropy_regularization_weight >= base_config.entropy_regularization_weight); } #[test] fn test_config_update() { let device = Device::cuda(0).unwrap_or(Device::default()); let config = RouterZLossConfig::default(); let mut zloss = RouterZLoss::new(config.clone(), device).unwrap(); let mut new_config = config.clone(); new_config.z_loss_weight = 0.01; new_config.temperature = 2.0; // Should succeed with valid config assert!(zloss.update_config(new_config.clone()).is_ok()); assert_eq!(zloss.config().z_loss_weight, 0.01); assert_eq!(zloss.config().temperature, 2.0); // Should fail with invalid config let mut invalid_config = new_config; invalid_config.z_loss_weight = -1.0; // Invalid assert!(zloss.update_config(invalid_config).is_err()); // Config should remain unchanged after failed update assert_eq!(zloss.config().z_loss_weight, 0.01); } }