1#![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#[derive(Debug, Clone)]
20pub enum FusedOperation {
21 FusedAdam {
23 lr: f64,
24 beta1: f64,
25 beta2: f64,
26 eps: f64,
27 weight_decay: f64,
28 },
29 FusedAdamW {
31 lr: f64,
32 beta1: f64,
33 beta2: f64,
34 eps: f64,
35 weight_decay: f64,
36 },
37 FusedSGDMomentum {
39 lr: f64,
40 momentum: f64,
41 dampening: f64,
42 weight_decay: f64,
43 nesterov: bool,
44 },
45 FusedGradientClipping { max_norm: f64, scale_factor: f64 },
47 FusedBatchNorm { eps: f64, momentum: f64 },
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct FusionConfig {
54 pub enable_memory_coalescing: bool,
56 pub enable_vectorization: bool,
58 pub batch_size: usize,
60 pub enable_kernel_fusion: bool,
62 pub buffer_size: usize,
64 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#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct FusedOptimizerState {
84 pub parameter_states: HashMap<String, OptimizerState>,
86 pub operation_buffers: HashMap<String, Vec<f64>>,
88 pub fusion_stats: FusionStats,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct FusionStats {
95 pub fused_operations: u64,
97 pub memory_bandwidth_saved: u64,
99 pub flops_saved: u64,
101 pub avg_batch_size: f64,
103 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#[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 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 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 if should_execute {
164 self.execute_fused_batch()?;
165 }
166
167 Ok(())
168 }
169
170 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 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 self.execute_single_operation(param_name, op, param, grad)?;
202 },
203 }
204 }
205
206 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 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 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 self.fused_adam_update(
256 ¶m,
257 &grad,
258 opt_state,
259 lr,
260 beta1,
261 beta2,
262 eps,
263 weight_decay,
264 )?;
265 }
266 }
267
268 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 let bandwidth_saved = batch_size * 4 * 8; state.fusion_stats.memory_bandwidth_saved += bandwidth_saved as u64;
278
279 Ok(())
280 }
281
282 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 self.fused_adamw_update(
314 ¶m,
315 &grad,
316 opt_state,
317 lr,
318 beta1,
319 beta2,
320 eps,
321 weight_decay,
322 )?;
323 }
324 }
325
326 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 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 self.fused_sgd_update(
365 ¶m,
366 &grad,
367 opt_state,
368 lr,
369 momentum,
370 dampening,
371 weight_decay,
372 nesterov,
373 )?;
374 }
375 }
376
377 state.fusion_stats.fused_operations += 1;
379 let bandwidth_saved = batch_size * 2 * 8; state.fusion_stats.memory_bandwidth_saved += bandwidth_saved as u64;
381
382 Ok(())
383 }
384
385 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 let mut gradients = Vec::new();
398 for (_, _, _, grad) in &operations {
399 gradients.push(grad.clone());
400 }
401
402 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 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 state.fusion_stats.fused_operations += 1;
425 let bandwidth_saved = batch_size * 8; state.fusion_stats.memory_bandwidth_saved += bandwidth_saved as u64;
427
428 Ok(())
429 }
430
431 fn execute_single_operation(
433 &mut self,
434 _param_name: String,
435 _operation: FusedOperation,
436 _parameter: Tensor,
437 _gradient: Tensor,
438 ) -> Result<()> {
439 Ok(())
441 }
442
443 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 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 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 for i in 0..param_data.len() {
475 let mut grad_val = grad_data[i];
476
477 if weight_decay > 0.0 {
479 grad_val += weight_decay as f32 * param_data[i];
480 }
481
482 momentum[i] = beta1 as f32 * momentum[i] + (1.0 - beta1 as f32) * grad_val;
484
485 variance[i] = beta2 as f32 * variance[i] + (1.0 - beta2 as f32) * grad_val * grad_val;
487
488 let m_hat = momentum[i] / bias_correction1 as f32;
490 let v_hat = variance[i] / bias_correction2 as f32;
491
492 param_data[i] -= lr as f32 * m_hat / (v_hat.sqrt() + eps as f32);
494 }
495
496 Ok(())
497 }
498
499 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 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 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 for i in 0..param_data.len() {
531 let grad_val = grad_data[i];
532
533 momentum[i] = beta1 as f32 * momentum[i] + (1.0 - beta1 as f32) * grad_val;
535
536 variance[i] = beta2 as f32 * variance[i] + (1.0 - beta2 as f32) * grad_val * grad_val;
538
539 let m_hat = momentum[i] / bias_correction1 as f32;
541 let v_hat = variance[i] / bias_correction2 as f32;
542
543 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 param_data[i] -= adaptive_step + weight_decay_step;
549 }
550
551 Ok(())
552 }
553
554 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 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 for i in 0..param_data.len() {
580 let mut grad_val = grad_data[i];
581
582 if weight_decay > 0.0 {
584 grad_val += weight_decay as f32 * param_data[i];
585 }
586
587 if momentum_coef > 0.0 {
589 if state.step == 1 {
590 momentum[i] = grad_val;
592 } else {
593 momentum[i] =
595 momentum_coef as f32 * momentum[i] + (1.0 - dampening as f32) * grad_val;
596 }
597
598 let update_direction = if nesterov {
600 grad_val + momentum_coef as f32 * momentum[i]
601 } else {
602 momentum[i]
603 };
604
605 param_data[i] -= lr as f32 * update_direction;
607 } else {
608 param_data[i] -= lr as f32 * grad_val;
610 }
611 }
612
613 Ok(())
614 }
615
616 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 pub fn flush(&mut self) -> Result<()> {
630 self.execute_fused_batch()
631 }
632
633 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 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 pub fn update_config(&mut self, config: FusionConfig) {
647 self.config = config;
648 }
649}
650
651#[cfg(target_arch = "x86_64")]
653pub mod simd {
654
655 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 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 let m_new = _mm256_fmadd_ps(beta1_vec, m, _mm256_mul_ps(one_minus_beta1, g));
693
694 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 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 _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 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 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 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 assert!((global_norm - 3.606).abs() < 0.01);
891 }
892}