1use crate::LRScheduler;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::sync::{Arc, Mutex};
10use trustformers_core::errors::{Result, TrustformersError};
11use trustformers_core::traits::Optimizer;
12use trustformers_core::Tensor;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct FusionConfig {
17 pub fuse_parameters: bool,
19 pub fuse_gradients: bool,
21 pub fuse_state: bool,
23 pub window_size: usize,
25 pub memory_threshold: usize,
27}
28
29impl Default for FusionConfig {
30 fn default() -> Self {
31 Self {
32 fuse_parameters: true,
33 fuse_gradients: true,
34 fuse_state: true,
35 window_size: 32,
36 memory_threshold: 1024 * 1024 * 100, }
38 }
39}
40
41pub struct FusedOptimizer {
43 optimizers: Vec<Box<dyn Optimizer>>,
44 config: FusionConfig,
45 fused_parameters: Arc<Mutex<HashMap<String, Tensor>>>,
46 fused_gradients: Arc<Mutex<HashMap<String, Tensor>>>,
47 fusion_groups: Vec<Vec<usize>>, }
49
50impl FusedOptimizer {
51 pub fn new(optimizers: Vec<Box<dyn Optimizer>>, config: FusionConfig) -> Result<Self> {
53 let fusion_groups = Self::compute_fusion_groups(&optimizers, &config);
54
55 Ok(Self {
56 optimizers,
57 config,
58 fused_parameters: Arc::new(Mutex::new(HashMap::new())),
59 fused_gradients: Arc::new(Mutex::new(HashMap::new())),
60 fusion_groups,
61 })
62 }
63
64 fn compute_fusion_groups(
66 optimizers: &[Box<dyn Optimizer>],
67 config: &FusionConfig,
68 ) -> Vec<Vec<usize>> {
69 let mut groups = Vec::new();
70 let mut used = vec![false; optimizers.len()];
71
72 for i in 0..optimizers.len() {
73 if used[i] {
74 continue;
75 }
76
77 let mut group = vec![i];
78 used[i] = true;
79
80 for j in (i + 1)..optimizers.len() {
82 if used[j] {
83 continue;
84 }
85
86 if Self::can_fuse(&optimizers[i], &optimizers[j], config) {
87 group.push(j);
88 used[j] = true;
89 }
90 }
91
92 groups.push(group);
93 }
94
95 groups
96 }
97
98 fn can_fuse(
100 _opt1: &Box<dyn Optimizer>,
101 _opt2: &Box<dyn Optimizer>,
102 _config: &FusionConfig,
103 ) -> bool {
104 true
107 }
108
109 fn fuse_parameters(&self, parameters: &mut HashMap<String, Tensor>) -> Result<()> {
111 if !self.config.fuse_parameters {
112 return Ok(());
113 }
114
115 let mut fused_params = self.fused_parameters.lock().map_err(|_| {
116 TrustformersError::tensor_op_error("Failed to lock fused parameters", "fuse_parameters")
117 })?;
118 fused_params.clear();
119
120 for group in &self.fusion_groups {
122 if group.len() > 1 {
123 let group_params: Vec<_> = parameters
125 .iter()
126 .filter(|(name, _)| {
127 group.iter().any(|&i| name.contains(&format!("opt_{}", i)))
129 })
130 .collect();
131
132 if !group_params.is_empty() {
133 let fused_name = format!("fused_group_{}", group[0]);
135 let fused_tensor = self.concatenate_tensors(
136 &group_params.iter().map(|(_, t)| *t).collect::<Vec<_>>(),
137 )?;
138 fused_params.insert(fused_name, fused_tensor);
139 }
140 }
141 }
142
143 Ok(())
144 }
145
146 fn concatenate_tensors(&self, tensors: &[&Tensor]) -> Result<Tensor> {
148 if tensors.is_empty() {
149 return Err(TrustformersError::invalid_argument(
150 "Empty tensor list".to_string(),
151 ));
152 }
153
154 let mut total_size = 0;
156 for tensor in tensors {
157 total_size += tensor.len();
158 }
159
160 Tensor::zeros(&[total_size])
162 }
163
164 pub fn fused_step(&mut self, parameters: &mut HashMap<String, Tensor>) -> Result<()> {
166 self.fuse_parameters(parameters)?;
168
169 let fusion_groups = self.fusion_groups.clone();
171 for group in &fusion_groups {
172 if group.len() > 1 {
173 self.apply_fused_group_optimization(group)?;
175 } else {
176 let optimizer_idx = group[0];
178 for (name, param) in parameters.iter_mut() {
180 if let Some(grad) = self.get_gradient_for_param(name) {
181 self.optimizers[optimizer_idx].update(param, &grad)?;
182 }
183 }
184 }
185 }
186
187 Ok(())
188 }
189
190 fn apply_fused_group_optimization(&mut self, group: &[usize]) -> Result<()> {
192 let primary_optimizer_idx = group[0];
194
195 let mut fused_params = self.fused_parameters.lock().map_err(|_| {
196 TrustformersError::tensor_op_error(
197 "Failed to lock fused parameters",
198 "apply_fused_group_optimization",
199 )
200 })?;
201 let fused_gradients = self.fused_gradients.lock().map_err(|_| {
202 TrustformersError::tensor_op_error(
203 "Failed to lock fused gradients",
204 "apply_fused_group_optimization",
205 )
206 })?;
207
208 let group_name = format!("fused_group_{}", primary_optimizer_idx);
209
210 if let (Some(param), Some(grad)) = (
211 fused_params.get_mut(&group_name),
212 fused_gradients.get(&group_name),
213 ) {
214 self.optimizers[primary_optimizer_idx].update(param, grad)?;
215 }
216
217 Ok(())
218 }
219
220 fn get_gradient_for_param(&self, param_name: &str) -> Option<Tensor> {
225 {
227 let fused_gradients = self.fused_gradients.lock().ok()?;
228 if let Some(gradient) = fused_gradients.get(param_name) {
229 return Some(gradient.clone());
230 }
231 }
232
233 for (idx, _optimizer) in self.optimizers.iter().enumerate() {
236 let full_param_name = format!("optimizer_{}_{}", idx, param_name);
237
238 let fused_gradients = self.fused_gradients.lock().ok()?;
240 if let Some(gradient) = fused_gradients.get(&full_param_name) {
241 return Some(gradient.clone());
242 }
243 drop(fused_gradients);
244
245 }
249
250 None
252 }
253
254 pub fn register_gradient(&self, param_name: &str, gradient: Tensor) -> Result<()> {
259 let mut fused_gradients = self.fused_gradients.lock().map_err(|_| {
260 TrustformersError::tensor_op_error(
261 "Failed to lock fused gradients",
262 "register_gradient",
263 )
264 })?;
265
266 fused_gradients.insert(param_name.to_string(), gradient);
267 Ok(())
268 }
269
270 pub fn clear_gradients(&self) -> Result<()> {
274 let mut fused_gradients = self.fused_gradients.lock().map_err(|_| {
275 TrustformersError::tensor_op_error("Failed to lock fused gradients", "clear_gradients")
276 })?;
277
278 fused_gradients.clear();
279 Ok(())
280 }
281
282 pub fn get_available_gradient_names(&self) -> Result<Vec<String>> {
286 let fused_gradients = self.fused_gradients.lock().map_err(|_| {
287 TrustformersError::tensor_op_error(
288 "Failed to lock fused gradients",
289 "get_available_gradient_names",
290 )
291 })?;
292
293 Ok(fused_gradients.keys().cloned().collect())
294 }
295
296 pub fn get_fusion_stats(&self) -> FusionStats {
298 let total_optimizers = self.optimizers.len();
299 let fused_groups = self.fusion_groups.iter().filter(|group| group.len() > 1).count();
300 let unfused_optimizers = self.fusion_groups.iter().filter(|group| group.len() == 1).count();
301
302 FusionStats {
303 total_optimizers,
304 fused_groups,
305 unfused_optimizers,
306 fusion_ratio: fused_groups as f64 / total_optimizers as f64,
307 memory_saved: self.estimate_memory_savings(),
308 }
309 }
310
311 fn estimate_memory_savings(&self) -> usize {
313 let fused_params =
314 self.fused_parameters.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
315 let total_fused_size: usize = fused_params.values()
316 .map(|t| t.len() * 4) .sum();
318
319 let estimated_original_size = total_fused_size * 2; estimated_original_size.saturating_sub(total_fused_size)
323 }
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct FusionStats {
329 pub total_optimizers: usize,
330 pub fused_groups: usize,
331 pub unfused_optimizers: usize,
332 pub fusion_ratio: f64,
333 pub memory_saved: usize,
334}
335
336pub struct MultiOptimizerTrainer {
338 optimizers: HashMap<String, Box<dyn Optimizer>>,
339 parameter_assignments: HashMap<String, String>, schedulers: HashMap<String, Box<dyn LRScheduler>>,
341 weights: HashMap<String, f64>, }
343
344impl Default for MultiOptimizerTrainer {
345 fn default() -> Self {
346 Self::new()
347 }
348}
349
350impl MultiOptimizerTrainer {
351 pub fn new() -> Self {
353 Self {
354 optimizers: HashMap::new(),
355 parameter_assignments: HashMap::new(),
356 schedulers: HashMap::new(),
357 weights: HashMap::new(),
358 }
359 }
360
361 pub fn add_optimizer(
363 &mut self,
364 name: String,
365 optimizer: Box<dyn Optimizer>,
366 weight: f64,
367 ) -> Result<()> {
368 self.optimizers.insert(name.clone(), optimizer);
369 self.weights.insert(name, weight);
370 Ok(())
371 }
372
373 pub fn add_scheduler(
375 &mut self,
376 optimizer_name: String,
377 scheduler: Box<dyn LRScheduler>,
378 ) -> Result<()> {
379 if !self.optimizers.contains_key(&optimizer_name) {
380 return Err(TrustformersError::invalid_argument(format!(
381 "Optimizer {} not found",
382 optimizer_name
383 )));
384 }
385
386 self.schedulers.insert(optimizer_name, scheduler);
387 Ok(())
388 }
389
390 pub fn assign_parameters(&mut self, assignments: HashMap<String, String>) -> Result<()> {
392 for optimizer_name in assignments.values() {
394 if !self.optimizers.contains_key(optimizer_name) {
395 return Err(TrustformersError::invalid_argument(format!(
396 "Optimizer {} not found",
397 optimizer_name
398 )));
399 }
400 }
401
402 self.parameter_assignments = assignments;
403 Ok(())
404 }
405
406 pub fn step(
408 &mut self,
409 parameters: &HashMap<String, Tensor>,
410 gradients: &HashMap<String, Tensor>,
411 ) -> Result<()> {
412 let mut optimizer_params: HashMap<String, Vec<(String, Tensor, Tensor)>> = HashMap::new();
414
415 for (param_name, param) in parameters {
416 if let Some(grad) = gradients.get(param_name) {
417 let optimizer_name = self
418 .parameter_assignments
419 .get(param_name)
420 .cloned()
421 .unwrap_or_else(|| "default".to_string());
422
423 optimizer_params.entry(optimizer_name).or_default().push((
424 param_name.clone(),
425 param.clone(),
426 grad.clone(),
427 ));
428 }
429 }
430
431 for (optimizer_name, param_grad_pairs) in optimizer_params {
433 if let Some(optimizer) = self.optimizers.get_mut(&optimizer_name) {
434 let weight = self.weights.get(&optimizer_name).copied().unwrap_or(1.0);
435
436 for (_, param, grad) in param_grad_pairs {
437 let scaled_grad = grad.mul_scalar(weight as f32)?;
439 optimizer.update(&mut param.clone(), &scaled_grad)?;
440 }
441 }
442 }
443
444 Ok(())
445 }
446
447 pub fn step_schedulers(&mut self, epoch: usize) -> Result<()> {
449 for (optimizer_name, scheduler) in &mut self.schedulers {
450 let new_lr = scheduler.get_lr(epoch);
451
452 if let Some(optimizer) = self.optimizers.get_mut(optimizer_name) {
453 optimizer.set_lr(new_lr);
454 }
455 }
456
457 Ok(())
458 }
459
460 pub fn get_stats(&self) -> MultiOptimizerStats {
462 MultiOptimizerStats {
463 num_optimizers: self.optimizers.len(),
464 num_schedulers: self.schedulers.len(),
465 num_assigned_params: self.parameter_assignments.len(),
466 optimizer_weights: self.weights.clone(),
467 }
468 }
469}
470
471#[derive(Debug, Clone, Serialize, Deserialize)]
473pub struct MultiOptimizerStats {
474 pub num_optimizers: usize,
475 pub num_schedulers: usize,
476 pub num_assigned_params: usize,
477 pub optimizer_weights: HashMap<String, f64>,
478}
479
480#[derive(Debug, Clone, Serialize, Deserialize)]
482pub enum WarmupStrategy {
483 Linear { steps: usize },
485 Exponential { steps: usize, base: f64 },
487 Cosine { steps: usize },
489 Custom { steps: usize },
491}
492
493pub struct WarmupOptimizer {
495 inner: Box<dyn Optimizer>,
496 strategy: WarmupStrategy,
497 current_step: usize,
498 base_lr: f64,
499 target_lr: f64,
500}
501
502impl WarmupOptimizer {
503 pub fn new(
505 optimizer: Box<dyn Optimizer>,
506 strategy: WarmupStrategy,
507 base_lr: f64,
508 target_lr: f64,
509 ) -> Self {
510 Self {
511 inner: optimizer,
512 strategy,
513 current_step: 0,
514 base_lr,
515 target_lr,
516 }
517 }
518
519 fn get_warmup_lr(&self) -> f64 {
521 let warmup_steps = match &self.strategy {
522 WarmupStrategy::Linear { steps } => *steps,
523 WarmupStrategy::Exponential { steps, .. } => *steps,
524 WarmupStrategy::Cosine { steps } => *steps,
525 WarmupStrategy::Custom { steps } => *steps,
526 };
527
528 if self.current_step >= warmup_steps {
529 return self.target_lr;
530 }
531
532 let progress = self.current_step as f64 / warmup_steps as f64;
533
534 match &self.strategy {
535 WarmupStrategy::Linear { .. } => {
536 self.base_lr + (self.target_lr - self.base_lr) * progress
537 },
538 WarmupStrategy::Exponential { base, .. } => {
539 self.base_lr + (self.target_lr - self.base_lr) * base.powf(1.0 - progress)
540 },
541 WarmupStrategy::Cosine { .. } => {
542 let cosine_progress = 0.5 * (1.0 - (std::f64::consts::PI * progress).cos());
543 self.base_lr + (self.target_lr - self.base_lr) * cosine_progress
544 },
545 WarmupStrategy::Custom { .. } => {
546 self.base_lr + (self.target_lr - self.base_lr) * progress
548 },
549 }
550 }
551
552 pub fn is_warmup_complete(&self) -> bool {
554 let warmup_steps = match &self.strategy {
555 WarmupStrategy::Linear { steps } => *steps,
556 WarmupStrategy::Exponential { steps, .. } => *steps,
557 WarmupStrategy::Cosine { steps } => *steps,
558 WarmupStrategy::Custom { steps } => *steps,
559 };
560
561 self.current_step >= warmup_steps
562 }
563}
564
565impl Optimizer for WarmupOptimizer {
566 fn update(&mut self, parameter: &mut Tensor, grad: &Tensor) -> Result<()> {
567 let current_lr = self.get_warmup_lr();
569 self.inner.set_lr(current_lr as f32);
570
571 self.inner.update(parameter, grad)
573 }
574
575 fn zero_grad(&mut self) {
576 self.inner.zero_grad()
577 }
578
579 fn step(&mut self) {
580 self.inner.step();
581 self.current_step += 1;
582 }
583
584 fn get_lr(&self) -> f32 {
585 self.get_warmup_lr() as f32
586 }
587
588 fn set_lr(&mut self, lr: f32) {
589 self.target_lr = lr as f64;
590 self.inner.set_lr(lr);
591 }
592}
593
594#[derive(Debug, Clone, Serialize, Deserialize)]
596pub struct CheckpointConfig {
597 pub save_interval: usize,
599 pub compress: bool,
601 pub max_checkpoints: usize,
603 pub incremental: bool,
605}
606
607impl Default for CheckpointConfig {
608 fn default() -> Self {
609 Self {
610 save_interval: 1000,
611 compress: true,
612 max_checkpoints: 5,
613 incremental: false,
614 }
615 }
616}
617
618pub struct MemoryBandwidthOptimizer {
620 inner: Box<dyn Optimizer>,
621 memory_threshold: usize,
622 bandwidth_threshold: f64,
623 adaptive_batch_size: bool,
624 current_batch_size: usize,
625 base_batch_size: usize,
626}
627
628impl MemoryBandwidthOptimizer {
629 pub fn new(
631 optimizer: Box<dyn Optimizer>,
632 memory_threshold: usize,
633 bandwidth_threshold: f64,
634 base_batch_size: usize,
635 ) -> Self {
636 Self {
637 inner: optimizer,
638 memory_threshold,
639 bandwidth_threshold,
640 adaptive_batch_size: true,
641 current_batch_size: base_batch_size,
642 base_batch_size,
643 }
644 }
645
646 pub fn adjust_batch_size(&mut self, memory_usage: usize, bandwidth_usage: f64) -> usize {
648 if !self.adaptive_batch_size {
649 return self.current_batch_size;
650 }
651
652 let memory_pressure = memory_usage as f64 / self.memory_threshold as f64;
653 let bandwidth_pressure = bandwidth_usage / self.bandwidth_threshold;
654
655 let pressure = memory_pressure.max(bandwidth_pressure);
656
657 if pressure > 1.1 {
658 self.current_batch_size = (self.current_batch_size as f64 * 0.9) as usize;
660 self.current_batch_size = self.current_batch_size.max(1);
661 } else if pressure < 0.8 {
662 self.current_batch_size = (self.current_batch_size as f64 * 1.1) as usize;
664 self.current_batch_size = self.current_batch_size.min(self.base_batch_size * 4);
665 }
666
667 self.current_batch_size
668 }
669
670 pub fn get_utilization(&self) -> ResourceUtilization {
672 ResourceUtilization {
673 current_batch_size: self.current_batch_size,
674 base_batch_size: self.base_batch_size,
675 memory_threshold: self.memory_threshold,
676 bandwidth_threshold: self.bandwidth_threshold,
677 adaptive_enabled: self.adaptive_batch_size,
678 }
679 }
680}
681
682impl Optimizer for MemoryBandwidthOptimizer {
683 fn update(&mut self, parameter: &mut Tensor, grad: &Tensor) -> Result<()> {
684 self.inner.update(parameter, grad)
685 }
686
687 fn zero_grad(&mut self) {
688 self.inner.zero_grad()
689 }
690
691 fn step(&mut self) {
692 self.inner.step()
693 }
694
695 fn get_lr(&self) -> f32 {
696 self.inner.get_lr()
697 }
698
699 fn set_lr(&mut self, lr: f32) {
700 self.inner.set_lr(lr)
701 }
702}
703
704#[derive(Debug, Clone, Serialize, Deserialize)]
706pub struct ResourceUtilization {
707 pub current_batch_size: usize,
708 pub base_batch_size: usize,
709 pub memory_threshold: usize,
710 pub bandwidth_threshold: f64,
711 pub adaptive_enabled: bool,
712}
713
714#[cfg(test)]
715mod tests {
716 use super::*;
717 use crate::Adam;
718
719 #[test]
720 fn test_fusion_config_default() {
721 let config = FusionConfig::default();
722 assert!(config.fuse_parameters);
723 assert!(config.fuse_gradients);
724 assert!(config.fuse_state);
725 assert_eq!(config.window_size, 32);
726 }
727
728 #[test]
729 fn test_warmup_strategy_linear() {
730 let strategy = WarmupStrategy::Linear { steps: 100 };
731
732 let adam = Adam::new(0.001, (0.9, 0.999), 1e-8, 0.0);
733
734 let warmup_optimizer = WarmupOptimizer::new(Box::new(adam), strategy, 0.0, 0.001);
735
736 assert!(!warmup_optimizer.is_warmup_complete());
737 assert_eq!(warmup_optimizer.get_warmup_lr(), 0.0);
738 }
739
740 #[test]
741 fn test_multi_optimizer_trainer_creation() {
742 let mut trainer = MultiOptimizerTrainer::new();
743
744 let adam = Adam::new(0.001, (0.9, 0.999), 1e-8, 0.0);
745 trainer
746 .add_optimizer("adam".to_string(), Box::new(adam), 1.0)
747 .expect("Construction failed");
748
749 let stats = trainer.get_stats();
750 assert_eq!(stats.num_optimizers, 1);
751 assert_eq!(stats.optimizer_weights.get("adam"), Some(&1.0));
752 }
753
754 #[test]
755 fn test_memory_bandwidth_optimizer() {
756 let adam = Adam::new(0.001, (0.9, 0.999), 1e-8, 0.0);
757 let mut mb_optimizer = MemoryBandwidthOptimizer::new(
758 Box::new(adam),
759 1024 * 1024 * 100, 100.0, 32,
762 );
763
764 let utilization = mb_optimizer.get_utilization();
765 assert_eq!(utilization.current_batch_size, 32);
766 assert_eq!(utilization.base_batch_size, 32);
767
768 let new_batch_size = mb_optimizer.adjust_batch_size(
770 1024 * 1024 * 120, 50.0,
772 );
773 assert!(new_batch_size < 32);
774 }
775
776 #[test]
777 fn test_checkpoint_config_default() {
778 let config = CheckpointConfig::default();
779 assert_eq!(config.save_interval, 1000);
780 assert!(config.compress);
781 assert_eq!(config.max_checkpoints, 5);
782 assert!(!config.incremental);
783 }
784}