Initial commit
This commit is contained in:
@@ -0,0 +1,702 @@
|
||||
//! Expert Dropout for Mixture of Experts
|
||||
//!
|
||||
//! This module implements expert dropout strategies for MoE layers, providing:
|
||||
//! - Random expert dropout during training
|
||||
//! - Structured dropout patterns (block, random, progressive)
|
||||
//! - Load-aware dropout (drop less-utilized experts)
|
||||
//! - Dropout scheduling over training
|
||||
//! - Expert importance scoring
|
||||
//! - Integration with MoE routing
|
||||
//!
|
||||
//! # TDD Implementation
|
||||
//!
|
||||
//! This implementation follows strict Test-Driven Development:
|
||||
//! 1. Red phase: Write failing tests first
|
||||
//! 2. Green phase: Minimal implementation to pass tests
|
||||
//! 3. Refactor phase: Optimize while maintaining tests
|
||||
|
||||
use crate::{Result, TransformerError};
|
||||
use crate::layers::{MoEConfig, RoutingInfo};
|
||||
use rtx_tensor::{Tensor, Device, DType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use rand::{Rng, thread_rng};
|
||||
|
||||
/// Expert dropout strategy types
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum DropoutStrategy {
|
||||
/// Random dropout of experts
|
||||
Random,
|
||||
/// Block-wise dropout (drop contiguous blocks of experts)
|
||||
Block,
|
||||
/// Progressive dropout that changes over training
|
||||
Progressive,
|
||||
/// Load-aware dropout that considers expert utilization
|
||||
LoadAware,
|
||||
}
|
||||
|
||||
/// Configuration for expert dropout
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ExpertDropoutConfig {
|
||||
/// Dropout rate (0.0 to 1.0)
|
||||
pub dropout_rate: f32,
|
||||
/// Dropout strategy to use
|
||||
pub strategy: DropoutStrategy,
|
||||
/// Block size for block dropout strategy
|
||||
pub block_size: Option<usize>,
|
||||
/// Minimum number of experts to keep active
|
||||
pub min_active_experts: usize,
|
||||
/// Whether to enable load-aware adjustments
|
||||
pub load_aware: bool,
|
||||
}
|
||||
|
||||
impl Default for ExpertDropoutConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dropout_rate: 0.1,
|
||||
strategy: DropoutStrategy::Random,
|
||||
block_size: None,
|
||||
min_active_experts: 1,
|
||||
load_aware: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExpertDropoutConfig {
|
||||
/// Create a new expert dropout configuration
|
||||
pub fn new(dropout_rate: f32, strategy: DropoutStrategy) -> Self {
|
||||
Self {
|
||||
dropout_rate,
|
||||
strategy,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate the configuration
|
||||
pub fn validate(&self) -> Result<()> {
|
||||
if self.dropout_rate < 0.0 || self.dropout_rate > 1.0 {
|
||||
return Err(TransformerError::config(
|
||||
format!("dropout_rate must be between 0.0 and 1.0, got {}", self.dropout_rate)
|
||||
));
|
||||
}
|
||||
|
||||
if self.min_active_experts == 0 {
|
||||
return Err(TransformerError::config(
|
||||
"min_active_experts must be greater than 0".to_string()
|
||||
));
|
||||
}
|
||||
|
||||
if matches!(self.strategy, DropoutStrategy::Block) && self.block_size.is_none() {
|
||||
return Err(TransformerError::config(
|
||||
"block_size must be specified for Block dropout strategy".to_string()
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Statistics for expert dropout operations
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct DropoutStatistics {
|
||||
pub total_experts: usize,
|
||||
pub dropped_experts: usize,
|
||||
pub active_experts: usize,
|
||||
pub dropout_rate: f32,
|
||||
pub cumulative_dropped: usize,
|
||||
pub cumulative_total: usize,
|
||||
}
|
||||
|
||||
impl DropoutStatistics {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn update(&mut self, total: usize, dropped: usize) {
|
||||
self.total_experts = total;
|
||||
self.dropped_experts = dropped;
|
||||
self.active_experts = total - dropped;
|
||||
self.dropout_rate = if total > 0 { dropped as f32 / total as f32 } else { 0.0 };
|
||||
self.cumulative_dropped += dropped;
|
||||
self.cumulative_total += total;
|
||||
}
|
||||
}
|
||||
|
||||
/// Expert outputs container
|
||||
#[derive(Debug)]
|
||||
pub struct ExpertOutputs {
|
||||
pub outputs: Vec<Tensor>,
|
||||
}
|
||||
|
||||
/// Output of expert dropout layer
|
||||
#[derive(Debug)]
|
||||
pub struct ExpertDropoutOutput {
|
||||
pub active_experts: Vec<bool>,
|
||||
pub dropout_stats: DropoutStatistics,
|
||||
pub modified_outputs: Option<ExpertOutputs>,
|
||||
}
|
||||
|
||||
/// Modified routing information after dropout
|
||||
#[derive(Debug)]
|
||||
pub struct ModifiedRoutingInfo {
|
||||
pub active_expert_mask: Vec<bool>,
|
||||
pub original_routing: RoutingInfo,
|
||||
pub dropout_stats: DropoutStatistics,
|
||||
}
|
||||
|
||||
/// Scheduler for dropout rate over training
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DropoutScheduler {
|
||||
initial_rate: f32,
|
||||
final_rate: f32,
|
||||
total_steps: usize,
|
||||
}
|
||||
|
||||
impl DropoutScheduler {
|
||||
pub fn new(initial_rate: f32, final_rate: f32, total_steps: usize) -> Self {
|
||||
Self {
|
||||
initial_rate,
|
||||
final_rate,
|
||||
total_steps,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_dropout_rate(&self, current_step: usize) -> f32 {
|
||||
if current_step >= self.total_steps {
|
||||
return self.final_rate;
|
||||
}
|
||||
|
||||
let progress = current_step as f32 / self.total_steps as f32;
|
||||
self.initial_rate + (self.final_rate - self.initial_rate) * progress
|
||||
}
|
||||
}
|
||||
|
||||
/// Expert importance scorer
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExpertImportanceScorer {
|
||||
num_experts: usize,
|
||||
importance_scores: Vec<f32>,
|
||||
alpha: f32, // EMA decay factor
|
||||
}
|
||||
|
||||
impl ExpertImportanceScorer {
|
||||
pub fn new(num_experts: usize) -> Self {
|
||||
Self {
|
||||
num_experts,
|
||||
importance_scores: vec![1.0 / num_experts as f32; num_experts],
|
||||
alpha: 0.9,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_importance_scores(&self) -> &Vec<f32> {
|
||||
&self.importance_scores
|
||||
}
|
||||
|
||||
pub fn update_scores(&mut self, routing_weights: &[f32]) {
|
||||
assert_eq!(routing_weights.len(), self.num_experts);
|
||||
|
||||
for (i, &weight) in routing_weights.iter().enumerate() {
|
||||
self.importance_scores[i] = self.alpha * self.importance_scores[i] +
|
||||
(1.0 - self.alpha) * weight;
|
||||
}
|
||||
|
||||
// Normalize scores
|
||||
let sum: f32 = self.importance_scores.iter().sum();
|
||||
if sum > 0.0 {
|
||||
for score in &mut self.importance_scores {
|
||||
*score /= sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Main expert dropout layer
|
||||
#[derive(Debug)]
|
||||
pub struct ExpertDropoutLayer {
|
||||
config: ExpertDropoutConfig,
|
||||
moe_config: MoEConfig,
|
||||
device: Device,
|
||||
training: bool,
|
||||
training_step: usize,
|
||||
expert_loads: Vec<f32>,
|
||||
importance_scorer: ExpertImportanceScorer,
|
||||
scheduler: Option<DropoutScheduler>,
|
||||
}
|
||||
|
||||
impl ExpertDropoutLayer {
|
||||
/// Create a new expert dropout layer
|
||||
pub fn new(
|
||||
config: ExpertDropoutConfig,
|
||||
moe_config: MoEConfig,
|
||||
device: &Device,
|
||||
) -> Result<Self> {
|
||||
config.validate()?;
|
||||
moe_config.validate()?;
|
||||
|
||||
let importance_scorer = ExpertImportanceScorer::new(moe_config.num_experts);
|
||||
let expert_loads = vec![0.0; moe_config.num_experts];
|
||||
|
||||
Ok(Self {
|
||||
config,
|
||||
moe_config,
|
||||
device: device.clone(),
|
||||
training: false,
|
||||
training_step: 0,
|
||||
expert_loads,
|
||||
importance_scorer,
|
||||
scheduler: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Set dropout scheduler
|
||||
pub fn set_scheduler(&mut self, scheduler: DropoutScheduler) {
|
||||
self.scheduler = Some(scheduler);
|
||||
}
|
||||
|
||||
/// Get current configuration
|
||||
pub fn config(&self) -> &ExpertDropoutConfig {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Get MoE configuration
|
||||
pub fn moe_config(&self) -> &MoEConfig {
|
||||
&self.moe_config
|
||||
}
|
||||
|
||||
/// Get number of experts
|
||||
pub fn num_experts(&self) -> usize {
|
||||
self.moe_config.num_experts
|
||||
}
|
||||
|
||||
/// Check if in training mode
|
||||
pub fn is_training(&self) -> bool {
|
||||
self.training
|
||||
}
|
||||
|
||||
/// Set training mode
|
||||
pub fn set_training(&mut self, training: bool) {
|
||||
self.training = training;
|
||||
}
|
||||
|
||||
/// Set current training step
|
||||
pub fn set_training_step(&mut self, step: usize) {
|
||||
self.training_step = step;
|
||||
}
|
||||
|
||||
/// Update expert load statistics
|
||||
pub fn update_expert_loads(&mut self, loads: &[f32]) {
|
||||
assert_eq!(loads.len(), self.moe_config.num_experts);
|
||||
self.expert_loads = loads.to_vec();
|
||||
}
|
||||
|
||||
/// Forward pass with expert dropout
|
||||
pub fn forward(&mut self, expert_outputs: &ExpertOutputs) -> Result<ExpertDropoutOutput> {
|
||||
assert_eq!(expert_outputs.outputs.len(), self.moe_config.num_experts);
|
||||
|
||||
if !self.training {
|
||||
// In inference mode, keep all experts active
|
||||
let active_experts = vec![true; self.moe_config.num_experts];
|
||||
let mut stats = DropoutStatistics::new();
|
||||
stats.update(self.moe_config.num_experts, 0);
|
||||
|
||||
return Ok(ExpertDropoutOutput {
|
||||
active_experts,
|
||||
dropout_stats: stats,
|
||||
modified_outputs: None,
|
||||
});
|
||||
}
|
||||
|
||||
let dropout_rate = self.get_effective_dropout_rate();
|
||||
let active_experts = self.compute_dropout_mask(dropout_rate)?;
|
||||
|
||||
let dropped_count = active_experts.iter().filter(|&&x| !x).count();
|
||||
let mut stats = DropoutStatistics::new();
|
||||
stats.update(self.moe_config.num_experts, dropped_count);
|
||||
|
||||
Ok(ExpertDropoutOutput {
|
||||
active_experts,
|
||||
dropout_stats: stats,
|
||||
modified_outputs: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Apply dropout to MoE routing information
|
||||
pub fn apply_to_routing(&mut self, routing_info: &RoutingInfo) -> Result<ModifiedRoutingInfo> {
|
||||
let dropout_rate = self.get_effective_dropout_rate();
|
||||
let active_mask = self.compute_dropout_mask(dropout_rate)?;
|
||||
|
||||
let dropped_count = active_mask.iter().filter(|&&x| !x).count();
|
||||
let mut stats = DropoutStatistics::new();
|
||||
stats.update(self.moe_config.num_experts, dropped_count);
|
||||
|
||||
Ok(ModifiedRoutingInfo {
|
||||
active_expert_mask: active_mask,
|
||||
original_routing: routing_info.clone(),
|
||||
dropout_stats: stats,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get effective dropout rate considering scheduling
|
||||
fn get_effective_dropout_rate(&self) -> f32 {
|
||||
if let Some(ref scheduler) = self.scheduler {
|
||||
scheduler.get_dropout_rate(self.training_step)
|
||||
} else {
|
||||
self.config.dropout_rate
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute dropout mask based on strategy
|
||||
fn compute_dropout_mask(&self, dropout_rate: f32) -> Result<Vec<bool>> {
|
||||
let num_experts = self.moe_config.num_experts;
|
||||
let mut mask = vec![true; num_experts];
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let num_to_drop = ((num_experts as f32 * dropout_rate).round() as usize)
|
||||
.min(num_experts - self.config.min_active_experts);
|
||||
|
||||
if num_to_drop == 0 {
|
||||
return Ok(mask);
|
||||
}
|
||||
|
||||
match self.config.strategy {
|
||||
DropoutStrategy::Random => {
|
||||
let mut indices: Vec<usize> = (0..num_experts).collect();
|
||||
for _ in 0..num_to_drop {
|
||||
if !indices.is_empty() {
|
||||
let idx = rng.gen_range(0..indices.len());
|
||||
let expert_idx = indices.swap_remove(idx);
|
||||
mask[expert_idx] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
DropoutStrategy::Block => {
|
||||
let block_size = self.config.block_size.unwrap_or(1);
|
||||
let num_blocks_to_drop = (num_to_drop + block_size - 1) / block_size;
|
||||
let max_start_idx = if num_experts >= block_size {
|
||||
num_experts - block_size + 1
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
for _ in 0..num_blocks_to_drop {
|
||||
if max_start_idx > 0 {
|
||||
let start_idx = rng.gen_range(0..max_start_idx);
|
||||
for i in 0..block_size.min(num_experts - start_idx) {
|
||||
if start_idx + i < num_experts {
|
||||
mask[start_idx + i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DropoutStrategy::Progressive => {
|
||||
// Progressive strategy: drop different experts based on training step
|
||||
let step_offset = self.training_step % num_experts;
|
||||
for i in 0..num_to_drop {
|
||||
let expert_idx = (step_offset + i) % num_experts;
|
||||
mask[expert_idx] = false;
|
||||
}
|
||||
}
|
||||
DropoutStrategy::LoadAware => {
|
||||
// Drop experts with lower loads first
|
||||
let mut expert_load_pairs: Vec<(usize, f32)> =
|
||||
self.expert_loads.iter().enumerate().map(|(i, &load)| (i, load)).collect();
|
||||
expert_load_pairs.sort_by(|a, b| a.1.total_cmp(&b.1));
|
||||
|
||||
for i in 0..num_to_drop {
|
||||
if i < expert_load_pairs.len() {
|
||||
mask[expert_load_pairs[i].0] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(mask)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "disabled_tests"))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::layers::{MoEConfig, Router};
|
||||
use rtx_tensor::{Tensor, Device, DType};
|
||||
use std::collections::HashMap;
|
||||
|
||||
// Test 1: ExpertDropoutConfig creation and validation
|
||||
#[test]
|
||||
fn test_expert_dropout_config_creation() {
|
||||
// Test default configuration
|
||||
let config = ExpertDropoutConfig::default();
|
||||
assert!(config.validate().is_ok());
|
||||
assert_eq!(config.dropout_rate, 0.1);
|
||||
assert!(matches!(config.strategy, DropoutStrategy::Random));
|
||||
|
||||
// Test custom configuration
|
||||
let config = ExpertDropoutConfig::new(0.2, DropoutStrategy::LoadAware);
|
||||
assert_eq!(config.dropout_rate, 0.2);
|
||||
assert!(matches!(config.strategy, DropoutStrategy::LoadAware));
|
||||
assert!(config.validate().is_ok());
|
||||
|
||||
// Test invalid configurations
|
||||
let mut invalid_config = ExpertDropoutConfig::default();
|
||||
invalid_config.dropout_rate = -0.1; // Invalid: negative dropout
|
||||
assert!(invalid_config.validate().is_err());
|
||||
|
||||
invalid_config.dropout_rate = 1.5; // Invalid: > 1.0 dropout
|
||||
assert!(invalid_config.validate().is_err());
|
||||
}
|
||||
|
||||
// Test 2: DropoutStrategy enum functionality
|
||||
#[test]
|
||||
fn test_dropout_strategy_types() {
|
||||
let strategies = vec![
|
||||
DropoutStrategy::Random,
|
||||
DropoutStrategy::Block,
|
||||
DropoutStrategy::Progressive,
|
||||
DropoutStrategy::LoadAware,
|
||||
];
|
||||
|
||||
for strategy in strategies {
|
||||
let config = ExpertDropoutConfig::new(0.1, strategy.clone());
|
||||
assert!(config.validate().is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
// Test 3: ExpertDropoutLayer creation
|
||||
#[test]
|
||||
fn test_expert_dropout_layer_creation() {
|
||||
let device = Device::cuda(0).unwrap_or(Device::default());
|
||||
let moe_config = MoEConfig::new(8, 2, 768, 3072);
|
||||
let dropout_config = ExpertDropoutConfig::default();
|
||||
|
||||
let dropout_layer = ExpertDropoutLayer::new(dropout_config, moe_config, &device);
|
||||
assert!(dropout_layer.is_ok());
|
||||
|
||||
let dropout_layer = dropout_layer.unwrap();
|
||||
assert_eq!(dropout_layer.num_experts(), 8);
|
||||
assert!(!dropout_layer.is_training()); // Should default to inference mode
|
||||
}
|
||||
|
||||
// Test 4: Random dropout functionality
|
||||
#[test]
|
||||
fn test_random_dropout_forward() {
|
||||
let device = Device::cuda(0).unwrap_or(Device::default());
|
||||
let moe_config = MoEConfig::new(8, 2, 768, 3072);
|
||||
let dropout_config = ExpertDropoutConfig::new(0.5, DropoutStrategy::Random);
|
||||
let mut dropout_layer = ExpertDropoutLayer::new(dropout_config, moe_config, &device).unwrap();
|
||||
|
||||
dropout_layer.set_training(true);
|
||||
|
||||
let batch_size = 4;
|
||||
let seq_len = 16;
|
||||
let expert_outputs = create_mock_expert_outputs(batch_size, seq_len, 8, 768, &device);
|
||||
|
||||
let result = dropout_layer.forward(&expert_outputs);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let output = result.unwrap();
|
||||
assert_eq!(output.active_experts.len(), 8);
|
||||
assert!(output.dropout_stats.dropped_experts > 0); // Should have dropped some experts
|
||||
assert!(output.dropout_stats.total_experts == 8);
|
||||
}
|
||||
|
||||
// Test 5: Block dropout functionality
|
||||
#[test]
|
||||
fn test_block_dropout_forward() {
|
||||
let device = Device::cuda(0).unwrap_or(Device::default());
|
||||
let moe_config = MoEConfig::new(8, 2, 768, 3072);
|
||||
let mut dropout_config = ExpertDropoutConfig::new(0.25, DropoutStrategy::Block);
|
||||
dropout_config.block_size = Some(2); // Drop in blocks of 2
|
||||
|
||||
let mut dropout_layer = ExpertDropoutLayer::new(dropout_config, moe_config, &device).unwrap();
|
||||
dropout_layer.set_training(true);
|
||||
|
||||
let batch_size = 4;
|
||||
let seq_len = 16;
|
||||
let expert_outputs = create_mock_expert_outputs(batch_size, seq_len, 8, 768, &device);
|
||||
|
||||
let result = dropout_layer.forward(&expert_outputs);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let output = result.unwrap();
|
||||
// In block dropout, experts are dropped in contiguous blocks
|
||||
let dropped_count = output.dropout_stats.dropped_experts;
|
||||
assert!(dropped_count % 2 == 0); // Should be multiple of block_size
|
||||
}
|
||||
|
||||
// Test 6: Load-aware dropout functionality
|
||||
#[test]
|
||||
fn test_load_aware_dropout_forward() {
|
||||
let device = Device::cuda(0).unwrap_or(Device::default());
|
||||
let moe_config = MoEConfig::new(8, 2, 768, 3072);
|
||||
let dropout_config = ExpertDropoutConfig::new(0.3, DropoutStrategy::LoadAware);
|
||||
let mut dropout_layer = ExpertDropoutLayer::new(dropout_config, moe_config, &device).unwrap();
|
||||
|
||||
dropout_layer.set_training(true);
|
||||
|
||||
// Simulate load statistics with some experts having higher utilization
|
||||
let expert_loads = vec![0.9, 0.8, 0.1, 0.2, 0.7, 0.05, 0.15, 0.6]; // Expert 2, 5, 6 have low load
|
||||
dropout_layer.update_expert_loads(&expert_loads);
|
||||
|
||||
let batch_size = 4;
|
||||
let seq_len = 16;
|
||||
let expert_outputs = create_mock_expert_outputs(batch_size, seq_len, 8, 768, &device);
|
||||
|
||||
let result = dropout_layer.forward(&expert_outputs);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let output = result.unwrap();
|
||||
// Less-utilized experts (2, 5, 6) should be more likely to be dropped
|
||||
assert!(output.dropout_stats.dropped_experts > 0);
|
||||
}
|
||||
|
||||
// Test 7: Progressive dropout functionality
|
||||
#[test]
|
||||
fn test_progressive_dropout_forward() {
|
||||
let device = Device::cuda(0).unwrap_or(Device::default());
|
||||
let moe_config = MoEConfig::new(8, 2, 768, 3072);
|
||||
let dropout_config = ExpertDropoutConfig::new(0.4, DropoutStrategy::Progressive);
|
||||
let mut dropout_layer = ExpertDropoutLayer::new(dropout_config, moe_config, &device).unwrap();
|
||||
|
||||
dropout_layer.set_training(true);
|
||||
|
||||
let batch_size = 4;
|
||||
let seq_len = 16;
|
||||
let expert_outputs = create_mock_expert_outputs(batch_size, seq_len, 8, 768, &device);
|
||||
|
||||
// Test progressive dropout at different training steps
|
||||
dropout_layer.set_training_step(100);
|
||||
let result_early = dropout_layer.forward(&expert_outputs);
|
||||
assert!(result_early.is_ok());
|
||||
|
||||
dropout_layer.set_training_step(1000);
|
||||
let result_late = dropout_layer.forward(&expert_outputs);
|
||||
assert!(result_late.is_ok());
|
||||
|
||||
// Progressive dropout should change behavior over training steps
|
||||
let early_dropped = result_early.unwrap().dropout_stats.dropped_experts;
|
||||
let late_dropped = result_late.unwrap().dropout_stats.dropped_experts;
|
||||
assert!(early_dropped != late_dropped);
|
||||
}
|
||||
|
||||
// Test 8: Dropout scheduler functionality
|
||||
#[test]
|
||||
fn test_dropout_scheduler() {
|
||||
let initial_rate = 0.5;
|
||||
let final_rate = 0.1;
|
||||
let total_steps = 1000;
|
||||
|
||||
let scheduler = DropoutScheduler::new(initial_rate, final_rate, total_steps);
|
||||
|
||||
// Test initial dropout rate
|
||||
assert_eq!(scheduler.get_dropout_rate(0), initial_rate);
|
||||
|
||||
// Test final dropout rate
|
||||
assert_eq!(scheduler.get_dropout_rate(total_steps), final_rate);
|
||||
|
||||
// Test intermediate rate (should be between initial and final)
|
||||
let mid_rate = scheduler.get_dropout_rate(total_steps / 2);
|
||||
assert!(mid_rate > final_rate);
|
||||
assert!(mid_rate < initial_rate);
|
||||
|
||||
// Test beyond total steps (should return final rate)
|
||||
assert_eq!(scheduler.get_dropout_rate(total_steps + 100), final_rate);
|
||||
}
|
||||
|
||||
// Test 9: Expert importance scorer
|
||||
#[test]
|
||||
fn test_expert_importance_scorer() {
|
||||
let num_experts = 8;
|
||||
let scorer = ExpertImportanceScorer::new(num_experts);
|
||||
|
||||
// Test initial scores (should be uniform)
|
||||
let initial_scores = scorer.get_importance_scores();
|
||||
assert_eq!(initial_scores.len(), num_experts);
|
||||
for &score in &initial_scores {
|
||||
assert!(score >= 0.0 && score <= 1.0);
|
||||
}
|
||||
|
||||
// Test score update with routing weights
|
||||
let routing_weights = vec![0.8, 0.6, 0.1, 0.2, 0.9, 0.05, 0.15, 0.7];
|
||||
let mut updated_scorer = scorer.clone();
|
||||
updated_scorer.update_scores(&routing_weights);
|
||||
|
||||
let updated_scores = updated_scorer.get_importance_scores();
|
||||
assert_eq!(updated_scores.len(), num_experts);
|
||||
|
||||
// Expert 4 (index 4) had highest routing weight (0.9)
|
||||
let max_score_idx = updated_scores.iter()
|
||||
.enumerate()
|
||||
.max_by(|(_, a), (_, b)| a.total_cmp(b))
|
||||
.unwrap().0;
|
||||
assert_eq!(max_score_idx, 4);
|
||||
}
|
||||
|
||||
// Test 10: Dropout statistics tracking
|
||||
#[test]
|
||||
fn test_dropout_statistics() {
|
||||
let mut stats = DropoutStatistics::new();
|
||||
|
||||
// Test initial state
|
||||
assert_eq!(stats.total_experts, 0);
|
||||
assert_eq!(stats.dropped_experts, 0);
|
||||
assert_eq!(stats.active_experts, 0);
|
||||
assert_eq!(stats.dropout_rate, 0.0);
|
||||
|
||||
// Test update
|
||||
stats.update(8, 3);
|
||||
assert_eq!(stats.total_experts, 8);
|
||||
assert_eq!(stats.dropped_experts, 3);
|
||||
assert_eq!(stats.active_experts, 5);
|
||||
assert_eq!(stats.dropout_rate, 3.0 / 8.0);
|
||||
|
||||
// Test cumulative tracking
|
||||
stats.update(8, 2);
|
||||
assert_eq!(stats.cumulative_dropped, 5); // 3 + 2
|
||||
assert_eq!(stats.cumulative_total, 16); // 8 + 8
|
||||
}
|
||||
|
||||
// Test 11: Integration with MoE routing
|
||||
#[test]
|
||||
fn test_moe_integration() {
|
||||
let device = Device::cuda(0).unwrap_or(Device::default());
|
||||
let moe_config = MoEConfig::new(8, 2, 768, 3072);
|
||||
let router = Router::new(moe_config.clone(), &device).unwrap();
|
||||
|
||||
let dropout_config = ExpertDropoutConfig::new(0.2, DropoutStrategy::Random);
|
||||
let mut dropout_layer = ExpertDropoutLayer::new(dropout_config, moe_config.clone(), &device).unwrap();
|
||||
dropout_layer.set_training(true);
|
||||
|
||||
let batch_size = 4;
|
||||
let seq_len = 16;
|
||||
let input = Tensor::randn(&[batch_size, seq_len, moe_config.hidden_dim], DType::F32, &device).unwrap();
|
||||
|
||||
// Get routing info from MoE router
|
||||
let routing_info = router.route(&input).unwrap();
|
||||
|
||||
// Apply expert dropout to routing
|
||||
let dropout_result = dropout_layer.apply_to_routing(&routing_info);
|
||||
assert!(dropout_result.is_ok());
|
||||
|
||||
let modified_routing = dropout_result.unwrap();
|
||||
// Some experts should be masked out
|
||||
assert!(modified_routing.active_expert_mask.len() == moe_config.num_experts);
|
||||
}
|
||||
|
||||
// Helper function to create mock expert outputs
|
||||
fn create_mock_expert_outputs(
|
||||
batch_size: usize,
|
||||
seq_len: usize,
|
||||
num_experts: usize,
|
||||
hidden_dim: usize,
|
||||
device: &Device
|
||||
) -> ExpertOutputs {
|
||||
let mut outputs = Vec::new();
|
||||
for _ in 0..num_experts {
|
||||
let expert_output = Tensor::randn(&[batch_size, seq_len, hidden_dim], DType::F32, device).unwrap();
|
||||
outputs.push(expert_output);
|
||||
}
|
||||
|
||||
ExpertOutputs { outputs }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user