Skip to main content

trustformers_optim/
fusion.rs

1//! Optimizer Fusion Techniques
2//!
3//! This module provides advanced optimizer fusion techniques for performance optimization.
4//! It combines multiple optimizer operations into fused kernels to reduce memory bandwidth
5//! and improve overall training performance.
6
7// reason: research-stage module — reserved API/scaffolding fields and methods
8// retained intentionally for in-progress features; not yet on active call paths.
9#![allow(dead_code)]
10
11use crate::OptimizerState;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::sync::{Arc, Mutex};
15use trustformers_core::errors::{Result, TrustformersError};
16use trustformers_core::Tensor;
17
18/// Fused optimizer operations for performance optimization
19#[derive(Debug, Clone)]
20pub enum FusedOperation {
21    /// Fused Adam update (parameter, gradient, momentum, velocity)
22    FusedAdam {
23        lr: f64,
24        beta1: f64,
25        beta2: f64,
26        eps: f64,
27        weight_decay: f64,
28    },
29    /// Fused AdamW update with decoupled weight decay
30    FusedAdamW {
31        lr: f64,
32        beta1: f64,
33        beta2: f64,
34        eps: f64,
35        weight_decay: f64,
36    },
37    /// Fused SGD with momentum
38    FusedSGDMomentum {
39        lr: f64,
40        momentum: f64,
41        dampening: f64,
42        weight_decay: f64,
43        nesterov: bool,
44    },
45    /// Fused gradient clipping and scaling
46    FusedGradientClipping { max_norm: f64, scale_factor: f64 },
47    /// Fused batch normalization update
48    FusedBatchNorm { eps: f64, momentum: f64 },
49}
50
51/// Configuration for fused optimizer operations
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct FusionConfig {
54    /// Enable memory bandwidth optimization
55    pub enable_memory_coalescing: bool,
56    /// Use vectorized operations when possible
57    pub enable_vectorization: bool,
58    /// Batch size for parameter updates
59    pub batch_size: usize,
60    /// Enable kernel fusion for compatible operations
61    pub enable_kernel_fusion: bool,
62    /// Buffer size for batched operations
63    pub buffer_size: usize,
64    /// Enable asynchronous updates
65    pub enable_async_updates: bool,
66}
67
68impl Default for FusionConfig {
69    fn default() -> Self {
70        Self {
71            enable_memory_coalescing: true,
72            enable_vectorization: true,
73            batch_size: 64,
74            enable_kernel_fusion: true,
75            buffer_size: 1024,
76            enable_async_updates: false,
77        }
78    }
79}
80
81/// Fused optimizer state for multiple parameters
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct FusedOptimizerState {
84    /// Parameter states indexed by parameter name
85    pub parameter_states: HashMap<String, OptimizerState>,
86    /// Fused operation buffers
87    pub operation_buffers: HashMap<String, Vec<f64>>,
88    /// Fusion statistics
89    pub fusion_stats: FusionStats,
90}
91
92/// Statistics for fusion operations
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct FusionStats {
95    /// Number of fused operations executed
96    pub fused_operations: u64,
97    /// Memory bandwidth saved (bytes)
98    pub memory_bandwidth_saved: u64,
99    /// FLOPS saved through fusion
100    pub flops_saved: u64,
101    /// Average batch size
102    pub avg_batch_size: f64,
103    /// Fusion efficiency ratio
104    pub fusion_efficiency: f64,
105}
106
107impl Default for FusionStats {
108    fn default() -> Self {
109        Self {
110            fused_operations: 0,
111            memory_bandwidth_saved: 0,
112            flops_saved: 0,
113            avg_batch_size: 0.0,
114            fusion_efficiency: 0.0,
115        }
116    }
117}
118
119/// Fused optimizer that combines multiple optimization operations
120#[derive(Debug)]
121pub struct FusedOptimizer {
122    config: FusionConfig,
123    state: Arc<Mutex<FusedOptimizerState>>,
124    pending_operations: Arc<Mutex<Vec<(String, FusedOperation, Tensor, Tensor)>>>,
125    operation_queue: Arc<Mutex<HashMap<String, Vec<FusedOperation>>>>,
126}
127
128impl FusedOptimizer {
129    /// Create new fused optimizer
130    pub fn new(config: FusionConfig) -> Result<Self> {
131        let state = FusedOptimizerState {
132            parameter_states: HashMap::new(),
133            operation_buffers: HashMap::new(),
134            fusion_stats: FusionStats::default(),
135        };
136
137        Ok(Self {
138            config,
139            state: Arc::new(Mutex::new(state)),
140            pending_operations: Arc::new(Mutex::new(Vec::new())),
141            operation_queue: Arc::new(Mutex::new(HashMap::new())),
142        })
143    }
144
145    /// Add operation to fusion queue
146    pub fn queue_operation(
147        &mut self,
148        param_name: String,
149        operation: FusedOperation,
150        parameter: Tensor,
151        gradient: Tensor,
152    ) -> Result<()> {
153        let should_execute = {
154            let mut pending = self
155                .pending_operations
156                .lock()
157                .map_err(|_| TrustformersError::lock_error("fusion mutex poisoned".to_string()))?;
158            pending.push((param_name, operation, parameter, gradient));
159            pending.len() >= self.config.batch_size
160        };
161
162        // Execute batch if buffer is full
163        if should_execute {
164            self.execute_fused_batch()?;
165        }
166
167        Ok(())
168    }
169
170    /// Execute all pending operations in a fused manner
171    pub fn execute_fused_batch(&mut self) -> Result<()> {
172        let mut pending = self
173            .pending_operations
174            .lock()
175            .map_err(|_| TrustformersError::lock_error("fusion mutex poisoned".to_string()))?;
176        if pending.is_empty() {
177            return Ok(());
178        }
179
180        let operations = std::mem::take(&mut *pending);
181        drop(pending);
182
183        // Group operations by type for maximum fusion efficiency
184        let mut adam_ops = Vec::new();
185        let mut adamw_ops = Vec::new();
186        let mut sgd_ops = Vec::new();
187        let mut clip_ops = Vec::new();
188
189        for (param_name, op, param, grad) in operations {
190            match op {
191                FusedOperation::FusedAdam { .. } => adam_ops.push((param_name, op, param, grad)),
192                FusedOperation::FusedAdamW { .. } => adamw_ops.push((param_name, op, param, grad)),
193                FusedOperation::FusedSGDMomentum { .. } => {
194                    sgd_ops.push((param_name, op, param, grad))
195                },
196                FusedOperation::FusedGradientClipping { .. } => {
197                    clip_ops.push((param_name, op, param, grad))
198                },
199                _ => {
200                    // Handle other operations individually
201                    self.execute_single_operation(param_name, op, param, grad)?;
202                },
203            }
204        }
205
206        // Execute fused batches
207        if !adam_ops.is_empty() {
208            self.execute_fused_adam_batch(adam_ops)?;
209        }
210        if !adamw_ops.is_empty() {
211            self.execute_fused_adamw_batch(adamw_ops)?;
212        }
213        if !sgd_ops.is_empty() {
214            self.execute_fused_sgd_batch(sgd_ops)?;
215        }
216        if !clip_ops.is_empty() {
217            self.execute_fused_clipping_batch(clip_ops)?;
218        }
219
220        Ok(())
221    }
222
223    /// Execute fused Adam operations
224    fn execute_fused_adam_batch(
225        &mut self,
226        operations: Vec<(String, FusedOperation, Tensor, Tensor)>,
227    ) -> Result<()> {
228        let mut state = self
229            .state
230            .lock()
231            .map_err(|_| TrustformersError::lock_error("fusion mutex poisoned".to_string()))?;
232        let batch_size = operations.len();
233
234        for (param_name, op, param, grad) in operations {
235            if let FusedOperation::FusedAdam {
236                lr,
237                beta1,
238                beta2,
239                eps,
240                weight_decay,
241            } = op
242            {
243                // Get or create optimizer state
244                let opt_state =
245                    state.parameter_states.entry(param_name.clone()).or_insert_with(|| {
246                        OptimizerState {
247                            step: 0,
248                            momentum: HashMap::new(),
249                            variance: HashMap::new(),
250                            ..Default::default()
251                        }
252                    });
253
254                // Fused Adam update with optimized memory access
255                self.fused_adam_update(
256                    &param,
257                    &grad,
258                    opt_state,
259                    lr,
260                    beta1,
261                    beta2,
262                    eps,
263                    weight_decay,
264                )?;
265            }
266        }
267
268        // Update fusion statistics
269        state.fusion_stats.fused_operations += 1;
270        state.fusion_stats.avg_batch_size = (state.fusion_stats.avg_batch_size
271            * (state.fusion_stats.fused_operations - 1) as f64
272            + batch_size as f64)
273            / state.fusion_stats.fused_operations as f64;
274
275        // Estimate memory bandwidth savings (simplified)
276        let bandwidth_saved = batch_size * 4 * 8; // 4 tensors * 8 bytes per element (approximate)
277        state.fusion_stats.memory_bandwidth_saved += bandwidth_saved as u64;
278
279        Ok(())
280    }
281
282    /// Execute fused AdamW operations
283    fn execute_fused_adamw_batch(
284        &mut self,
285        operations: Vec<(String, FusedOperation, Tensor, Tensor)>,
286    ) -> Result<()> {
287        let mut state = self
288            .state
289            .lock()
290            .map_err(|_| TrustformersError::lock_error("fusion mutex poisoned".to_string()))?;
291        let batch_size = operations.len();
292
293        for (param_name, op, param, grad) in operations {
294            if let FusedOperation::FusedAdamW {
295                lr,
296                beta1,
297                beta2,
298                eps,
299                weight_decay,
300            } = op
301            {
302                let opt_state =
303                    state.parameter_states.entry(param_name.clone()).or_insert_with(|| {
304                        OptimizerState {
305                            step: 0,
306                            momentum: HashMap::new(),
307                            variance: HashMap::new(),
308                            ..Default::default()
309                        }
310                    });
311
312                // Fused AdamW update with decoupled weight decay
313                self.fused_adamw_update(
314                    &param,
315                    &grad,
316                    opt_state,
317                    lr,
318                    beta1,
319                    beta2,
320                    eps,
321                    weight_decay,
322                )?;
323            }
324        }
325
326        // Update statistics
327        state.fusion_stats.fused_operations += 1;
328        let bandwidth_saved = batch_size * 4 * 8;
329        state.fusion_stats.memory_bandwidth_saved += bandwidth_saved as u64;
330
331        Ok(())
332    }
333
334    /// Execute fused SGD operations
335    fn execute_fused_sgd_batch(
336        &mut self,
337        operations: Vec<(String, FusedOperation, Tensor, Tensor)>,
338    ) -> Result<()> {
339        let mut state = self
340            .state
341            .lock()
342            .map_err(|_| TrustformersError::lock_error("fusion mutex poisoned".to_string()))?;
343        let batch_size = operations.len();
344
345        for (param_name, op, param, grad) in operations {
346            if let FusedOperation::FusedSGDMomentum {
347                lr,
348                momentum,
349                dampening,
350                weight_decay,
351                nesterov,
352            } = op
353            {
354                let opt_state =
355                    state.parameter_states.entry(param_name.clone()).or_insert_with(|| {
356                        OptimizerState {
357                            step: 0,
358                            momentum: HashMap::new(),
359                            ..Default::default()
360                        }
361                    });
362
363                // Fused SGD with momentum update
364                self.fused_sgd_update(
365                    &param,
366                    &grad,
367                    opt_state,
368                    lr,
369                    momentum,
370                    dampening,
371                    weight_decay,
372                    nesterov,
373                )?;
374            }
375        }
376
377        // Update statistics
378        state.fusion_stats.fused_operations += 1;
379        let bandwidth_saved = batch_size * 2 * 8; // SGD uses fewer tensors
380        state.fusion_stats.memory_bandwidth_saved += bandwidth_saved as u64;
381
382        Ok(())
383    }
384
385    /// Execute fused gradient clipping operations
386    fn execute_fused_clipping_batch(
387        &mut self,
388        operations: Vec<(String, FusedOperation, Tensor, Tensor)>,
389    ) -> Result<()> {
390        let mut state = self
391            .state
392            .lock()
393            .map_err(|_| TrustformersError::lock_error("fusion mutex poisoned".to_string()))?;
394        let batch_size = operations.len();
395
396        // Collect all gradients for global norm computation
397        let mut gradients = Vec::new();
398        for (_, _, _, grad) in &operations {
399            gradients.push(grad.clone());
400        }
401
402        // Compute global gradient norm for batch
403        let global_norm = self.compute_global_norm(&gradients)?;
404
405        for (_, op, _, grad) in operations {
406            if let FusedOperation::FusedGradientClipping {
407                max_norm,
408                scale_factor,
409            } = op
410            {
411                // Apply clipping with pre-computed global norm
412                if global_norm > max_norm {
413                    let clip_coef = max_norm / global_norm;
414                    let grad_mut = grad;
415                    grad_mut.mul_scalar((clip_coef * scale_factor) as f32)?;
416                } else {
417                    let grad_mut = grad;
418                    grad_mut.mul_scalar(scale_factor as f32)?;
419                }
420            }
421        }
422
423        // Update statistics
424        state.fusion_stats.fused_operations += 1;
425        let bandwidth_saved = batch_size * 8; // Single pass through gradients
426        state.fusion_stats.memory_bandwidth_saved += bandwidth_saved as u64;
427
428        Ok(())
429    }
430
431    /// Execute single operation (fallback for non-batchable operations)
432    fn execute_single_operation(
433        &mut self,
434        _param_name: String,
435        _operation: FusedOperation,
436        _parameter: Tensor,
437        _gradient: Tensor,
438    ) -> Result<()> {
439        // Implementation for individual operations
440        Ok(())
441    }
442
443    /// Optimized Adam update with fused operations
444    fn fused_adam_update(
445        &self,
446        param: &Tensor,
447        grad: &Tensor,
448        state: &mut OptimizerState,
449        lr: f64,
450        beta1: f64,
451        beta2: f64,
452        eps: f64,
453        weight_decay: f64,
454    ) -> Result<()> {
455        use crate::common::ParameterIds;
456
457        state.step += 1;
458        let param_id = ParameterIds::from_tensor(param)?;
459        let param_len = param.data()?.len();
460
461        // Get or initialize momentum and variance buffers
462        let momentum =
463            state.momentum.entry(param_id.clone()).or_insert_with(|| vec![0.0; param_len]);
464        let variance = state.variance.entry(param_id).or_insert_with(|| vec![0.0; param_len]);
465
466        let grad_data = grad.data()?;
467        let mut param_data = param.data()?;
468
469        // Bias correction factors
470        let bias_correction1 = 1.0 - beta1.powi(state.step as i32);
471        let bias_correction2 = 1.0 - beta2.powi(state.step as i32);
472
473        // Fused update loop - combines all operations in single pass
474        for i in 0..param_data.len() {
475            let mut grad_val = grad_data[i];
476
477            // Apply weight decay if specified (L2 regularization)
478            if weight_decay > 0.0 {
479                grad_val += weight_decay as f32 * param_data[i];
480            }
481
482            // Update biased first moment estimate (momentum)
483            momentum[i] = beta1 as f32 * momentum[i] + (1.0 - beta1 as f32) * grad_val;
484
485            // Update biased second raw moment estimate (variance)
486            variance[i] = beta2 as f32 * variance[i] + (1.0 - beta2 as f32) * grad_val * grad_val;
487
488            // Compute bias-corrected first and second moment estimates
489            let m_hat = momentum[i] / bias_correction1 as f32;
490            let v_hat = variance[i] / bias_correction2 as f32;
491
492            // Update parameter with fused Adam step
493            param_data[i] -= lr as f32 * m_hat / (v_hat.sqrt() + eps as f32);
494        }
495
496        Ok(())
497    }
498
499    /// Optimized AdamW update with fused operations and decoupled weight decay
500    fn fused_adamw_update(
501        &self,
502        param: &Tensor,
503        grad: &Tensor,
504        state: &mut OptimizerState,
505        lr: f64,
506        beta1: f64,
507        beta2: f64,
508        eps: f64,
509        weight_decay: f64,
510    ) -> Result<()> {
511        use crate::common::ParameterIds;
512
513        state.step += 1;
514        let param_id = ParameterIds::from_tensor(param)?;
515        let param_len = param.data()?.len();
516
517        // Get or initialize momentum and variance buffers
518        let momentum =
519            state.momentum.entry(param_id.clone()).or_insert_with(|| vec![0.0; param_len]);
520        let variance = state.variance.entry(param_id).or_insert_with(|| vec![0.0; param_len]);
521
522        let grad_data = grad.data()?;
523        let mut param_data = param.data()?;
524
525        // Bias correction factors
526        let bias_correction1 = 1.0 - beta1.powi(state.step as i32);
527        let bias_correction2 = 1.0 - beta2.powi(state.step as i32);
528
529        // Fused AdamW update loop - decoupled weight decay
530        for i in 0..param_data.len() {
531            let grad_val = grad_data[i];
532
533            // Update biased first moment estimate (momentum)
534            momentum[i] = beta1 as f32 * momentum[i] + (1.0 - beta1 as f32) * grad_val;
535
536            // Update biased second raw moment estimate (variance)
537            variance[i] = beta2 as f32 * variance[i] + (1.0 - beta2 as f32) * grad_val * grad_val;
538
539            // Compute bias-corrected first and second moment estimates
540            let m_hat = momentum[i] / bias_correction1 as f32;
541            let v_hat = variance[i] / bias_correction2 as f32;
542
543            // AdamW update: apply weight decay directly to parameters (decoupled)
544            let adaptive_step = lr as f32 * m_hat / (v_hat.sqrt() + eps as f32);
545            let weight_decay_step = lr as f32 * weight_decay as f32 * param_data[i];
546
547            // Combined update with decoupled weight decay
548            param_data[i] -= adaptive_step + weight_decay_step;
549        }
550
551        Ok(())
552    }
553
554    /// Optimized SGD update with fused momentum
555    fn fused_sgd_update(
556        &self,
557        param: &Tensor,
558        grad: &Tensor,
559        state: &mut OptimizerState,
560        lr: f64,
561        momentum_coef: f64,
562        dampening: f64,
563        weight_decay: f64,
564        nesterov: bool,
565    ) -> Result<()> {
566        use crate::common::ParameterIds;
567
568        state.step += 1;
569        let param_id = ParameterIds::from_tensor(param)?;
570        let param_len = param.data()?.len();
571
572        // Get or initialize momentum buffer
573        let momentum = state.momentum.entry(param_id).or_insert_with(|| vec![0.0; param_len]);
574
575        let grad_data = grad.data()?;
576        let mut param_data = param.data()?;
577
578        // Fused SGD update loop with momentum
579        for i in 0..param_data.len() {
580            let mut grad_val = grad_data[i];
581
582            // Apply weight decay if specified
583            if weight_decay > 0.0 {
584                grad_val += weight_decay as f32 * param_data[i];
585            }
586
587            // Update momentum buffer
588            if momentum_coef > 0.0 {
589                if state.step == 1 {
590                    // First step: initialize momentum with gradient
591                    momentum[i] = grad_val;
592                } else {
593                    // Update momentum with dampening
594                    momentum[i] =
595                        momentum_coef as f32 * momentum[i] + (1.0 - dampening as f32) * grad_val;
596                }
597
598                // Apply Nesterov momentum if enabled
599                let update_direction = if nesterov {
600                    grad_val + momentum_coef as f32 * momentum[i]
601                } else {
602                    momentum[i]
603                };
604
605                // Update parameter
606                param_data[i] -= lr as f32 * update_direction;
607            } else {
608                // Simple SGD without momentum
609                param_data[i] -= lr as f32 * grad_val;
610            }
611        }
612
613        Ok(())
614    }
615
616    /// Compute global gradient norm for clipping
617    fn compute_global_norm(&self, gradients: &[Tensor]) -> Result<f64> {
618        let mut total_norm_sq = 0.0;
619
620        for grad in gradients {
621            let norm = grad.norm()?;
622            total_norm_sq += norm * norm;
623        }
624
625        Ok(total_norm_sq.sqrt() as f64)
626    }
627
628    /// Flush all pending operations
629    pub fn flush(&mut self) -> Result<()> {
630        self.execute_fused_batch()
631    }
632
633    /// Get fusion statistics
634    pub fn get_fusion_stats(&self) -> FusionStats {
635        let state = self.state.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
636        state.fusion_stats.clone()
637    }
638
639    /// Reset fusion statistics
640    pub fn reset_stats(&mut self) {
641        let mut state = self.state.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
642        state.fusion_stats = FusionStats::default();
643    }
644
645    /// Update fusion configuration
646    pub fn update_config(&mut self, config: FusionConfig) {
647        self.config = config;
648    }
649}
650
651/// SIMD-optimized vectorized operations
652#[cfg(target_arch = "x86_64")]
653pub mod simd {
654
655    /// SIMD-optimized Adam update
656    pub fn simd_adam_update(
657        param: &mut [f32],
658        grad: &[f32],
659        momentum: &mut [f32],
660        velocity: &mut [f32],
661        lr: f32,
662        beta1: f32,
663        beta2: f32,
664        eps: f32,
665        step: i32,
666    ) {
667        use std::arch::x86_64::*;
668
669        let bias_correction1 = 1.0 - beta1.powi(step);
670        let bias_correction2 = 1.0 - beta2.powi(step);
671        let corrected_lr = lr * (bias_correction2.sqrt() / bias_correction1);
672
673        unsafe {
674            let beta1_vec = _mm256_set1_ps(beta1);
675            let beta2_vec = _mm256_set1_ps(beta2);
676            let one_minus_beta1 = _mm256_set1_ps(1.0 - beta1);
677            let one_minus_beta2 = _mm256_set1_ps(1.0 - beta2);
678            let eps_vec = _mm256_set1_ps(eps);
679            let lr_vec = _mm256_set1_ps(corrected_lr);
680
681            let chunks = param.len() / 8;
682            for i in 0..chunks {
683                let idx = i * 8;
684
685                // Load values
686                let p = _mm256_loadu_ps(param.as_ptr().add(idx));
687                let g = _mm256_loadu_ps(grad.as_ptr().add(idx));
688                let m = _mm256_loadu_ps(momentum.as_ptr().add(idx));
689                let v = _mm256_loadu_ps(velocity.as_ptr().add(idx));
690
691                // Update momentum: momentum = beta1 * momentum + (1 - beta1) * grad
692                let m_new = _mm256_fmadd_ps(beta1_vec, m, _mm256_mul_ps(one_minus_beta1, g));
693
694                // Update velocity: velocity = beta2 * velocity + (1 - beta2) * grad^2
695                let g_sq = _mm256_mul_ps(g, g);
696                let v_new = _mm256_fmadd_ps(beta2_vec, v, _mm256_mul_ps(one_minus_beta2, g_sq));
697
698                // Update parameter: param = param - lr * momentum / (sqrt(velocity) + eps)
699                let v_sqrt = _mm256_sqrt_ps(v_new);
700                let v_sqrt_eps = _mm256_add_ps(v_sqrt, eps_vec);
701                let update = _mm256_div_ps(m_new, v_sqrt_eps);
702                let p_new = _mm256_fnmadd_ps(lr_vec, update, p);
703
704                // Store results
705                _mm256_storeu_ps(param.as_mut_ptr().add(idx), p_new);
706                _mm256_storeu_ps(momentum.as_mut_ptr().add(idx), m_new);
707                _mm256_storeu_ps(velocity.as_mut_ptr().add(idx), v_new);
708            }
709
710            // Handle remaining elements
711            for i in (chunks * 8)..param.len() {
712                let g = grad[i];
713                momentum[i] = beta1 * momentum[i] + (1.0 - beta1) * g;
714                velocity[i] = beta2 * velocity[i] + (1.0 - beta2) * g * g;
715                param[i] -= corrected_lr * momentum[i] / (velocity[i].sqrt() + eps);
716            }
717        }
718    }
719}
720
721#[cfg(test)]
722mod tests {
723    use super::*;
724    use trustformers_core::Tensor;
725
726    #[test]
727    fn test_fused_optimizer_creation() {
728        let config = FusionConfig::default();
729        let optimizer = FusedOptimizer::new(config).expect("Failed to create fused optimizer");
730
731        let stats = optimizer.get_fusion_stats();
732        assert_eq!(stats.fused_operations, 0);
733    }
734
735    #[test]
736    fn test_fused_adam_operation() {
737        let config = FusionConfig::default();
738        let mut optimizer = FusedOptimizer::new(config).expect("Failed to create fused optimizer");
739
740        let param = Tensor::ones(&[10, 10]).expect("Failed to create tensor");
741        let grad = Tensor::ones(&[10, 10]).expect("Failed to create tensor");
742
743        let operation = FusedOperation::FusedAdam {
744            lr: 0.001,
745            beta1: 0.9,
746            beta2: 0.999,
747            eps: 1e-8,
748            weight_decay: 0.0,
749        };
750
751        optimizer
752            .queue_operation("param1".to_string(), operation, param, grad)
753            .expect("Failed to queue operation");
754
755        optimizer.flush().expect("Flush failed");
756
757        let stats = optimizer.get_fusion_stats();
758        assert_eq!(stats.fused_operations, 1);
759    }
760
761    #[test]
762    fn test_fused_adamw_operation() {
763        let config = FusionConfig::default();
764        let mut optimizer = FusedOptimizer::new(config).expect("Failed to create fused optimizer");
765
766        let param = Tensor::ones(&[5, 5]).expect("Failed to create tensor");
767        let grad = Tensor::ones(&[5, 5]).expect("Failed to create tensor");
768
769        let operation = FusedOperation::FusedAdamW {
770            lr: 0.001,
771            beta1: 0.9,
772            beta2: 0.999,
773            eps: 1e-8,
774            weight_decay: 0.01,
775        };
776
777        optimizer
778            .queue_operation("param2".to_string(), operation, param, grad)
779            .expect("Failed to queue operation");
780
781        optimizer.flush().expect("Flush failed");
782
783        let stats = optimizer.get_fusion_stats();
784        assert_eq!(stats.fused_operations, 1);
785    }
786
787    #[test]
788    fn test_fused_sgd_operation() {
789        let config = FusionConfig::default();
790        let mut optimizer = FusedOptimizer::new(config).expect("Failed to create fused optimizer");
791
792        let param = Tensor::ones(&[3, 3]).expect("Failed to create tensor");
793        let grad = Tensor::ones(&[3, 3]).expect("Failed to create tensor");
794
795        let operation = FusedOperation::FusedSGDMomentum {
796            lr: 0.01,
797            momentum: 0.9,
798            dampening: 0.0,
799            weight_decay: 0.0,
800            nesterov: false,
801        };
802
803        optimizer
804            .queue_operation("param3".to_string(), operation, param, grad)
805            .expect("Failed to queue operation");
806
807        optimizer.flush().expect("Flush failed");
808
809        let stats = optimizer.get_fusion_stats();
810        assert_eq!(stats.fused_operations, 1);
811    }
812
813    #[test]
814    fn test_batch_fusion() {
815        let config = FusionConfig {
816            batch_size: 2,
817            ..FusionConfig::default()
818        };
819        let mut optimizer = FusedOptimizer::new(config).expect("Failed to create fused optimizer");
820
821        // Queue multiple operations
822        for i in 0..3 {
823            let param = Tensor::ones(&[2, 2]).expect("Failed to create tensor");
824            let grad = Tensor::ones(&[2, 2]).expect("Failed to create tensor");
825
826            let operation = FusedOperation::FusedAdam {
827                lr: 0.001,
828                beta1: 0.9,
829                beta2: 0.999,
830                eps: 1e-8,
831                weight_decay: 0.0,
832            };
833
834            optimizer
835                .queue_operation(format!("param_{}", i), operation, param, grad)
836                .expect("Operation failed in test");
837        }
838
839        // Should have executed batch automatically
840        let stats = optimizer.get_fusion_stats();
841        assert!(stats.fused_operations > 0);
842    }
843
844    #[test]
845    fn test_fusion_stats() {
846        let config = FusionConfig::default();
847        let mut optimizer = FusedOptimizer::new(config).expect("Failed to create fused optimizer");
848
849        let param = Tensor::ones(&[10, 10]).expect("Failed to create tensor");
850        let grad = Tensor::ones(&[10, 10]).expect("Failed to create tensor");
851
852        let operation = FusedOperation::FusedAdam {
853            lr: 0.001,
854            beta1: 0.9,
855            beta2: 0.999,
856            eps: 1e-8,
857            weight_decay: 0.0,
858        };
859
860        optimizer
861            .queue_operation("param1".to_string(), operation, param, grad)
862            .expect("Failed to queue operation");
863
864        optimizer.flush().expect("Flush failed");
865
866        let stats = optimizer.get_fusion_stats();
867        assert_eq!(stats.fused_operations, 1);
868        assert!(stats.memory_bandwidth_saved > 0);
869
870        optimizer.reset_stats();
871        let reset_stats = optimizer.get_fusion_stats();
872        assert_eq!(reset_stats.fused_operations, 0);
873        assert_eq!(reset_stats.memory_bandwidth_saved, 0);
874    }
875
876    #[test]
877    fn test_global_norm_computation() {
878        let config = FusionConfig::default();
879        let optimizer = FusedOptimizer::new(config).expect("Failed to create fused optimizer");
880
881        let grad1 = Tensor::ones(&[3, 3]).expect("Failed to create tensor");
882        let grad2 = Tensor::ones(&[2, 2]).expect("Failed to create tensor");
883
884        let gradients = vec![grad1, grad2];
885        let global_norm = optimizer
886            .compute_global_norm(&gradients)
887            .expect("Failed to compute global norm");
888
889        // Expected: sqrt(9 + 4) = sqrt(13) ≈ 3.606
890        assert!((global_norm - 3.606).abs() < 0.01);
891    }
892}