1use crate::optimizer::OptimizerState;
20use anyhow::{anyhow, Result};
21use serde::{Deserialize, Serialize};
22use std::collections::HashMap;
23use trustformers_core::tensor::Tensor;
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct QHMConfig {
28 pub learning_rate: f32,
30 pub momentum: f32,
32 pub nu: f32,
34 pub weight_decay: f32,
36}
37
38impl Default for QHMConfig {
39 fn default() -> Self {
40 Self {
41 learning_rate: 1e-3,
42 momentum: 0.9,
43 nu: 0.7,
44 weight_decay: 0.0,
45 }
46 }
47}
48
49#[derive(Debug)]
55pub struct QHM {
56 config: QHMConfig,
57 momentum_buffers: HashMap<usize, Tensor>,
58 current_step: usize,
59}
60
61impl QHM {
62 pub fn new(config: QHMConfig) -> Self {
64 Self {
65 config,
66 momentum_buffers: HashMap::new(),
67 current_step: 0,
68 }
69 }
70
71 pub fn with_defaults(learning_rate: f32, momentum: f32, nu: f32) -> Self {
73 Self::new(QHMConfig {
74 learning_rate,
75 momentum,
76 nu,
77 weight_decay: 0.0,
78 })
79 }
80
81 pub fn get_config(&self) -> &QHMConfig {
83 &self.config
84 }
85
86 pub fn set_config(&mut self, config: QHMConfig) {
88 self.config = config;
89 }
90}
91
92impl OptimizerState for QHM {
93 fn zero_grad(&mut self) -> Result<()> {
94 Ok(())
96 }
97
98 fn step(&mut self, parameters: &mut [Tensor]) -> Result<()> {
99 self.current_step += 1;
100
101 for (param_id, parameter) in parameters.iter_mut().enumerate() {
102 let gradient = match parameter.grad() {
104 Ok(grad) => grad,
105 Err(_) => {
106 continue;
108 },
109 };
110
111 let effective_grad = if self.config.weight_decay > 0.0 {
113 gradient.add(¶meter.mul_scalar(self.config.weight_decay)?)?
114 } else {
115 gradient
116 };
117
118 let momentum_buffer = if let Some(buffer) = self.momentum_buffers.get(¶m_id) {
120 let updated = buffer
122 .mul_scalar(self.config.momentum)?
123 .add(&effective_grad.mul_scalar(1.0 - self.config.momentum)?)?;
124 self.momentum_buffers.insert(param_id, updated.clone());
125 updated
126 } else {
127 let initial_momentum = effective_grad.clone();
129 self.momentum_buffers.insert(param_id, initial_momentum.clone());
130 initial_momentum
131 };
132
133 let update_direction = effective_grad
135 .mul_scalar(self.config.nu)?
136 .add(&momentum_buffer.mul_scalar(1.0 - self.config.nu)?)?;
137
138 *parameter = parameter.sub(&update_direction.mul_scalar(self.config.learning_rate)?)?;
140 }
141
142 Ok(())
143 }
144
145 fn get_lr(&self) -> f32 {
146 self.config.learning_rate
147 }
148
149 fn set_lr(&mut self, lr: f32) {
150 self.config.learning_rate = lr;
151 }
152
153 fn state_dict(&self) -> Result<HashMap<String, Tensor>> {
154 let mut state = HashMap::new();
155
156 state.insert(
158 "learning_rate".to_string(),
159 Tensor::scalar(self.config.learning_rate)?,
160 );
161 state.insert(
162 "momentum".to_string(),
163 Tensor::scalar(self.config.momentum)?,
164 );
165 state.insert("nu".to_string(), Tensor::scalar(self.config.nu)?);
166 state.insert(
167 "weight_decay".to_string(),
168 Tensor::scalar(self.config.weight_decay)?,
169 );
170 state.insert(
171 "current_step".to_string(),
172 Tensor::scalar(self.current_step as f32)?,
173 );
174
175 for (¶m_id, buffer) in &self.momentum_buffers {
177 state.insert(format!("momentum_buffer_{}", param_id), buffer.clone());
178 }
179
180 Ok(state)
181 }
182
183 fn load_state_dict(&mut self, state: HashMap<String, Tensor>) -> Result<()> {
184 if let Some(lr) = state.get("learning_rate") {
186 self.config.learning_rate = lr.to_scalar()?;
187 }
188 if let Some(momentum) = state.get("momentum") {
189 self.config.momentum = momentum.to_scalar()?;
190 }
191 if let Some(nu) = state.get("nu") {
192 self.config.nu = nu.to_scalar()?;
193 }
194 if let Some(wd) = state.get("weight_decay") {
195 self.config.weight_decay = wd.to_scalar()?;
196 }
197 if let Some(step) = state.get("current_step") {
198 self.current_step = step.to_scalar()? as usize;
199 }
200
201 self.momentum_buffers.clear();
203 for (key, tensor) in state {
204 if let Some(param_id_str) = key.strip_prefix("momentum_buffer_") {
205 if let Ok(param_id) = param_id_str.parse::<usize>() {
206 self.momentum_buffers.insert(param_id, tensor);
207 }
208 }
209 }
210
211 Ok(())
212 }
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize)]
217pub struct AggMoConfig {
218 pub learning_rate: f32,
220 pub momentum_coefficients: Vec<f32>,
222 pub weight_decay: f32,
224}
225
226impl Default for AggMoConfig {
227 fn default() -> Self {
228 Self {
229 learning_rate: 1e-3,
230 momentum_coefficients: vec![0.0, 0.9, 0.99],
231 weight_decay: 0.0,
232 }
233 }
234}
235
236#[derive(Debug)]
241pub struct AggMo {
242 config: AggMoConfig,
243 momentum_buffers: HashMap<usize, Vec<Tensor>>, current_step: usize,
245}
246
247impl AggMo {
248 pub fn new(config: AggMoConfig) -> Self {
255 match Self::try_new(config) {
256 Ok(optimizer) => optimizer,
257 Err(error) => panic!("invalid AggMo configuration: {error}"),
258 }
259 }
260
261 pub fn try_new(config: AggMoConfig) -> Result<Self> {
268 if config.momentum_coefficients.is_empty() {
269 return Err(anyhow!("AggMo needs at least one momentum coefficient"));
270 }
271 Ok(Self {
272 config,
273 momentum_buffers: HashMap::new(),
274 current_step: 0,
275 })
276 }
277
278 pub fn with_defaults(learning_rate: f32, momentum_coefficients: Vec<f32>) -> Self {
280 Self::new(AggMoConfig {
281 learning_rate,
282 momentum_coefficients,
283 weight_decay: 0.0,
284 })
285 }
286
287 pub fn get_config(&self) -> &AggMoConfig {
289 &self.config
290 }
291
292 pub fn num_momentum_buffers(&self) -> usize {
294 self.config.momentum_coefficients.len()
295 }
296}
297
298impl OptimizerState for AggMo {
299 fn zero_grad(&mut self) -> Result<()> {
300 Ok(())
301 }
302
303 fn step(&mut self, parameters: &mut [Tensor]) -> Result<()> {
304 self.current_step += 1;
305
306 for (param_id, parameter) in parameters.iter_mut().enumerate() {
307 let gradient = match parameter.grad() {
309 Ok(grad) => grad,
310 Err(_) => {
311 continue;
313 },
314 };
315
316 let effective_grad = if self.config.weight_decay > 0.0 {
318 gradient.add(¶meter.mul_scalar(self.config.weight_decay)?)?
319 } else {
320 gradient
321 };
322
323 let buffers = match self.momentum_buffers.entry(param_id) {
325 std::collections::hash_map::Entry::Occupied(entry) => entry.into_mut(),
326 std::collections::hash_map::Entry::Vacant(entry) => {
327 let init = (0..self.config.momentum_coefficients.len())
329 .map(|_| Tensor::zeros(&effective_grad.shape()))
330 .collect::<std::result::Result<Vec<_>, _>>()?;
331 entry.insert(init)
332 },
333 };
334
335 let mut aggregated_momentum = Tensor::zeros(&effective_grad.shape())?;
337 for (i, &beta) in self.config.momentum_coefficients.iter().enumerate() {
338 buffers[i] =
340 buffers[i].mul_scalar(beta)?.add(&effective_grad.mul_scalar(1.0 - beta)?)?;
341
342 aggregated_momentum = aggregated_momentum.add(&buffers[i])?;
344 }
345
346 let num_buffers = self.config.momentum_coefficients.len() as f32;
348 let averaged_momentum = aggregated_momentum.div_scalar(num_buffers)?;
349
350 *parameter =
352 parameter.sub(&averaged_momentum.mul_scalar(self.config.learning_rate)?)?;
353 }
354
355 Ok(())
356 }
357
358 fn get_lr(&self) -> f32 {
359 self.config.learning_rate
360 }
361
362 fn set_lr(&mut self, lr: f32) {
363 self.config.learning_rate = lr;
364 }
365
366 fn state_dict(&self) -> Result<HashMap<String, Tensor>> {
367 let mut state = HashMap::new();
368
369 state.insert(
371 "learning_rate".to_string(),
372 Tensor::scalar(self.config.learning_rate)?,
373 );
374 state.insert(
375 "weight_decay".to_string(),
376 Tensor::scalar(self.config.weight_decay)?,
377 );
378 state.insert(
379 "current_step".to_string(),
380 Tensor::scalar(self.current_step as f32)?,
381 );
382 state.insert(
383 "num_momentum_coeffs".to_string(),
384 Tensor::scalar(self.config.momentum_coefficients.len() as f32)?,
385 );
386
387 for (i, &coeff) in self.config.momentum_coefficients.iter().enumerate() {
389 state.insert(format!("momentum_coeff_{}", i), Tensor::scalar(coeff)?);
390 }
391
392 for (¶m_id, buffers) in &self.momentum_buffers {
394 for (buffer_idx, buffer) in buffers.iter().enumerate() {
395 state.insert(
396 format!("momentum_buffer_{}_{}", param_id, buffer_idx),
397 buffer.clone(),
398 );
399 }
400 }
401
402 Ok(state)
403 }
404
405 fn load_state_dict(&mut self, state: HashMap<String, Tensor>) -> Result<()> {
406 if let Some(lr) = state.get("learning_rate") {
408 self.config.learning_rate = lr.to_scalar()?;
409 }
410 if let Some(wd) = state.get("weight_decay") {
411 self.config.weight_decay = wd.to_scalar()?;
412 }
413 if let Some(step) = state.get("current_step") {
414 self.current_step = step.to_scalar()? as usize;
415 }
416
417 if let Some(num_coeffs_tensor) = state.get("num_momentum_coeffs") {
419 let num_coeffs = num_coeffs_tensor.to_scalar()? as usize;
420 let mut coefficients = Vec::with_capacity(num_coeffs);
421 for i in 0..num_coeffs {
422 if let Some(coeff_tensor) = state.get(&format!("momentum_coeff_{}", i)) {
423 coefficients.push(coeff_tensor.to_scalar()?);
424 }
425 }
426 self.config.momentum_coefficients = coefficients;
427 }
428
429 self.momentum_buffers.clear();
431 let mut param_buffers: HashMap<usize, HashMap<usize, Tensor>> = HashMap::new();
432
433 for (key, tensor) in state {
434 if key.starts_with("momentum_buffer_") {
435 let parts: Vec<&str> = key.split('_').collect();
436 if parts.len() >= 4 {
437 if let (Ok(param_id), Ok(buffer_idx)) =
438 (parts[2].parse::<usize>(), parts[3].parse::<usize>())
439 {
440 param_buffers.entry(param_id).or_default().insert(buffer_idx, tensor);
441 }
442 }
443 }
444 }
445
446 for (param_id, buffer_map) in param_buffers {
448 let mut buffers = Vec::new();
449 for i in 0..self.config.momentum_coefficients.len() {
450 if let Some(buffer) = buffer_map.get(&i) {
451 buffers.push(buffer.clone());
452 }
453 }
454 if buffers.len() == self.config.momentum_coefficients.len() {
455 self.momentum_buffers.insert(param_id, buffers);
456 }
457 }
458
459 Ok(())
460 }
461}
462
463#[derive(Debug, Clone, Serialize, Deserialize)]
465pub struct VarianceReductionConfig {
466 pub learning_rate: f32,
468 pub method: VarianceReductionMethod,
470 pub history_size: usize,
472 pub full_grad_frequency: usize,
474 pub weight_decay: f32,
476}
477
478impl Default for VarianceReductionConfig {
479 fn default() -> Self {
480 Self {
481 learning_rate: 1e-3,
482 method: VarianceReductionMethod::SVRG,
483 history_size: 100,
484 full_grad_frequency: 10,
485 weight_decay: 0.0,
486 }
487 }
488}
489
490#[derive(Debug, Clone, Serialize, Deserialize)]
492pub enum VarianceReductionMethod {
493 SVRG,
495 SAG,
497}
498
499#[derive(Debug)]
501pub struct VarianceReduction {
502 config: VarianceReductionConfig,
503 gradient_history: HashMap<usize, Vec<Tensor>>,
504 average_gradients: HashMap<usize, Tensor>,
505 full_gradients: HashMap<usize, Tensor>,
506 current_step: usize,
507 last_full_grad_step: usize,
508}
509
510impl VarianceReduction {
511 pub fn new(config: VarianceReductionConfig) -> Self {
513 Self {
514 config,
515 gradient_history: HashMap::new(),
516 average_gradients: HashMap::new(),
517 full_gradients: HashMap::new(),
518 current_step: 0,
519 last_full_grad_step: 0,
520 }
521 }
522
523 pub fn svrg(learning_rate: f32, history_size: usize, full_grad_frequency: usize) -> Self {
525 Self::new(VarianceReductionConfig {
526 learning_rate,
527 method: VarianceReductionMethod::SVRG,
528 history_size,
529 full_grad_frequency,
530 weight_decay: 0.0,
531 })
532 }
533
534 pub fn sag(learning_rate: f32, history_size: usize) -> Self {
536 Self::new(VarianceReductionConfig {
537 learning_rate,
538 method: VarianceReductionMethod::SAG,
539 history_size,
540 full_grad_frequency: 1, weight_decay: 0.0,
542 })
543 }
544
545 fn update_gradient_history(&mut self, param_id: usize, gradient: &Tensor) -> Result<()> {
546 let history = self.gradient_history.entry(param_id).or_default();
547
548 history.push(gradient.clone());
549 if history.len() > self.config.history_size {
550 history.remove(0);
551 }
552
553 Ok(())
554 }
555
556 fn compute_average_gradient(&mut self, param_id: usize) -> Result<Tensor> {
557 if let Some(history) = self.gradient_history.get(¶m_id) {
558 if history.is_empty() {
559 return Err(anyhow!("No gradient history available"));
560 }
561
562 let mut sum = history[0].clone();
563 for grad in history.iter().skip(1) {
564 sum = sum.add(grad)?;
565 }
566
567 let average = sum.div_scalar(history.len() as f32)?;
568 self.average_gradients.insert(param_id, average.clone());
569 Ok(average)
570 } else {
571 Err(anyhow!("No gradient history for parameter {}", param_id))
572 }
573 }
574
575 fn should_compute_full_gradient(&self) -> bool {
576 self.current_step - self.last_full_grad_step >= self.config.full_grad_frequency
577 }
578}
579
580impl OptimizerState for VarianceReduction {
581 fn zero_grad(&mut self) -> Result<()> {
582 Ok(())
583 }
584
585 fn step(&mut self, parameters: &mut [Tensor]) -> Result<()> {
586 self.current_step += 1;
587
588 let compute_full_grad = match self.config.method {
590 VarianceReductionMethod::SVRG => self.should_compute_full_gradient(),
591 VarianceReductionMethod::SAG => false,
592 };
593
594 if compute_full_grad {
595 self.last_full_grad_step = self.current_step;
596 for (param_id, parameter) in parameters.iter().enumerate() {
599 let gradient = match parameter.grad() {
601 Ok(grad) => grad,
602 Err(_) => {
603 continue;
605 },
606 };
607 self.full_gradients.insert(param_id, gradient);
608 }
609 }
610
611 for (param_id, parameter) in parameters.iter_mut().enumerate() {
612 let current_gradient = match parameter.grad() {
614 Ok(grad) => grad,
615 Err(_) => {
616 continue;
618 },
619 };
620
621 let effective_grad = if self.config.weight_decay > 0.0 {
623 current_gradient.add(¶meter.mul_scalar(self.config.weight_decay)?)?
624 } else {
625 current_gradient
626 };
627
628 self.update_gradient_history(param_id, &effective_grad)?;
630
631 let variance_reduced_grad = match self.config.method {
633 VarianceReductionMethod::SVRG => {
634 let full_grad_opt = self.full_gradients.get(¶m_id).cloned();
636 if let Some(full_grad) = full_grad_opt {
637 let avg_grad = self.compute_average_gradient(param_id)?;
638 effective_grad.sub(&avg_grad)?.add(&full_grad)?
640 } else {
641 effective_grad
642 }
643 },
644 VarianceReductionMethod::SAG => {
645 self.compute_average_gradient(param_id)?
647 },
648 };
649
650 *parameter =
652 parameter.sub(&variance_reduced_grad.mul_scalar(self.config.learning_rate)?)?;
653 }
654
655 Ok(())
656 }
657
658 fn get_lr(&self) -> f32 {
659 self.config.learning_rate
660 }
661
662 fn set_lr(&mut self, lr: f32) {
663 self.config.learning_rate = lr;
664 }
665
666 fn state_dict(&self) -> Result<HashMap<String, Tensor>> {
667 let mut state = HashMap::new();
668
669 state.insert(
670 "learning_rate".to_string(),
671 Tensor::scalar(self.config.learning_rate)?,
672 );
673 state.insert(
674 "current_step".to_string(),
675 Tensor::scalar(self.current_step as f32)?,
676 );
677 state.insert(
678 "last_full_grad_step".to_string(),
679 Tensor::scalar(self.last_full_grad_step as f32)?,
680 );
681
682 Ok(state)
686 }
687
688 fn load_state_dict(&mut self, state: HashMap<String, Tensor>) -> Result<()> {
689 if let Some(lr) = state.get("learning_rate") {
690 self.config.learning_rate = lr.to_scalar()?;
691 }
692 if let Some(step) = state.get("current_step") {
693 self.current_step = step.to_scalar()? as usize;
694 }
695 if let Some(last_step) = state.get("last_full_grad_step") {
696 self.last_full_grad_step = last_step.to_scalar()? as usize;
697 }
698
699 Ok(())
700 }
701}
702
703#[derive(Debug, Clone, Serialize, Deserialize)]
705pub struct NesterovAcceleratedGradientConfig {
706 pub learning_rate: f32,
708 pub momentum: f32,
710 pub weight_decay: f32,
712 pub restart_on_increase: bool,
714}
715
716impl Default for NesterovAcceleratedGradientConfig {
717 fn default() -> Self {
718 Self {
719 learning_rate: 1e-3,
720 momentum: 0.9,
721 weight_decay: 0.0,
722 restart_on_increase: false,
723 }
724 }
725}
726
727#[derive(Debug)]
735pub struct NesterovAcceleratedGradient {
736 config: NesterovAcceleratedGradientConfig,
737 velocity_buffers: HashMap<usize, Tensor>,
738 current_step: usize,
739 previous_loss: Option<f32>,
740}
741
742impl NesterovAcceleratedGradient {
743 pub fn new(config: NesterovAcceleratedGradientConfig) -> Self {
745 Self {
746 config,
747 velocity_buffers: HashMap::new(),
748 current_step: 0,
749 previous_loss: None,
750 }
751 }
752
753 pub fn with_defaults(learning_rate: f32, momentum: f32) -> Self {
755 Self::new(NesterovAcceleratedGradientConfig {
756 learning_rate,
757 momentum,
758 weight_decay: 0.0,
759 restart_on_increase: false,
760 })
761 }
762
763 pub fn get_config(&self) -> &NesterovAcceleratedGradientConfig {
765 &self.config
766 }
767
768 pub fn set_current_loss(&mut self, loss: f32) {
770 if self.config.restart_on_increase {
771 if let Some(prev_loss) = self.previous_loss {
772 if loss > prev_loss {
773 self.velocity_buffers.clear();
775 }
776 }
777 }
778 self.previous_loss = Some(loss);
779 }
780}
781
782impl OptimizerState for NesterovAcceleratedGradient {
783 fn zero_grad(&mut self) -> Result<()> {
784 Ok(())
785 }
786
787 fn step(&mut self, parameters: &mut [Tensor]) -> Result<()> {
788 self.current_step += 1;
789
790 for (param_id, parameter) in parameters.iter_mut().enumerate() {
791 let gradient = match parameter.grad() {
793 Ok(grad) => grad,
794 Err(_) => {
795 continue;
797 },
798 };
799
800 let effective_grad = if self.config.weight_decay > 0.0 {
802 gradient.add(¶meter.mul_scalar(self.config.weight_decay)?)?
803 } else {
804 gradient
805 };
806
807 let velocity = if let Some(v) = self.velocity_buffers.get(¶m_id) {
809 v.clone()
810 } else {
811 Tensor::zeros_like(parameter)?
812 };
813
814 let _lookahead_position = parameter.sub(&velocity.mul_scalar(self.config.momentum)?)?;
816
817 let new_velocity = velocity
823 .mul_scalar(self.config.momentum)?
824 .add(&effective_grad.mul_scalar(self.config.learning_rate)?)?;
825
826 self.velocity_buffers.insert(param_id, new_velocity.clone());
827
828 *parameter = parameter.sub(&new_velocity)?;
830 }
831
832 Ok(())
833 }
834
835 fn get_lr(&self) -> f32 {
836 self.config.learning_rate
837 }
838
839 fn set_lr(&mut self, lr: f32) {
840 self.config.learning_rate = lr;
841 }
842
843 fn state_dict(&self) -> Result<HashMap<String, Tensor>> {
844 let mut state = HashMap::new();
845
846 state.insert(
847 "learning_rate".to_string(),
848 Tensor::scalar(self.config.learning_rate)?,
849 );
850 state.insert(
851 "momentum".to_string(),
852 Tensor::scalar(self.config.momentum)?,
853 );
854 state.insert(
855 "weight_decay".to_string(),
856 Tensor::scalar(self.config.weight_decay)?,
857 );
858 state.insert(
859 "current_step".to_string(),
860 Tensor::scalar(self.current_step as f32)?,
861 );
862
863 if let Some(loss) = self.previous_loss {
864 state.insert("previous_loss".to_string(), Tensor::scalar(loss)?);
865 }
866
867 for (¶m_id, velocity) in &self.velocity_buffers {
868 state.insert(format!("velocity_{}", param_id), velocity.clone());
869 }
870
871 Ok(state)
872 }
873
874 fn load_state_dict(&mut self, state: HashMap<String, Tensor>) -> Result<()> {
875 if let Some(lr) = state.get("learning_rate") {
876 self.config.learning_rate = lr.to_scalar()?;
877 }
878 if let Some(momentum) = state.get("momentum") {
879 self.config.momentum = momentum.to_scalar()?;
880 }
881 if let Some(wd) = state.get("weight_decay") {
882 self.config.weight_decay = wd.to_scalar()?;
883 }
884 if let Some(step) = state.get("current_step") {
885 self.current_step = step.to_scalar()? as usize;
886 }
887 if let Some(loss) = state.get("previous_loss") {
888 self.previous_loss = Some(loss.to_scalar()?);
889 }
890
891 self.velocity_buffers.clear();
892 for (key, tensor) in state {
893 if let Some(param_id_str) = key.strip_prefix("velocity_") {
894 if let Ok(param_id) = param_id_str.parse::<usize>() {
895 self.velocity_buffers.insert(param_id, tensor);
896 }
897 }
898 }
899
900 Ok(())
901 }
902}
903
904#[derive(Debug, Clone, Serialize, Deserialize)]
906pub struct HeavyBallConfig {
907 pub learning_rate: f32,
909 pub beta: f32,
911 pub weight_decay: f32,
913 pub adaptive_momentum: bool,
915}
916
917impl Default for HeavyBallConfig {
918 fn default() -> Self {
919 Self {
920 learning_rate: 1e-3,
921 beta: 0.9,
922 weight_decay: 0.0,
923 adaptive_momentum: false,
924 }
925 }
926}
927
928#[derive(Debug)]
935pub struct HeavyBall {
936 config: HeavyBallConfig,
937 velocity_buffers: HashMap<usize, Tensor>,
938 previous_gradients: HashMap<usize, Tensor>,
939 current_step: usize,
940}
941
942impl HeavyBall {
943 pub fn new(config: HeavyBallConfig) -> Self {
945 Self {
946 config,
947 velocity_buffers: HashMap::new(),
948 previous_gradients: HashMap::new(),
949 current_step: 0,
950 }
951 }
952
953 pub fn with_defaults(learning_rate: f32, beta: f32) -> Self {
955 Self::new(HeavyBallConfig {
956 learning_rate,
957 beta,
958 weight_decay: 0.0,
959 adaptive_momentum: false,
960 })
961 }
962
963 pub fn get_config(&self) -> &HeavyBallConfig {
965 &self.config
966 }
967
968 fn compute_adaptive_momentum(&self, param_id: usize, current_grad: &Tensor) -> Result<f32> {
970 if let Some(prev_grad) = self.previous_gradients.get(¶m_id) {
971 let dot_product = current_grad.mul(prev_grad)?.sum(None, false)?;
973 let norm_current = current_grad.norm_squared()?.sqrt()?;
974 let norm_prev = prev_grad.norm_squared()?.sqrt()?;
975
976 let dot_scalar = dot_product.to_scalar()?;
977 let norm_current_scalar = norm_current.to_scalar()?;
978 let norm_prev_scalar = norm_prev.to_scalar()?;
979
980 let denominator = norm_current_scalar * norm_prev_scalar;
981 if denominator > 1e-8 {
982 let cosine_similarity = dot_scalar / denominator;
983 let adaptive_beta = self.config.beta * cosine_similarity.max(0.0);
985 Ok(adaptive_beta)
986 } else {
987 Ok(self.config.beta)
988 }
989 } else {
990 Ok(self.config.beta)
991 }
992 }
993}
994
995impl OptimizerState for HeavyBall {
996 fn zero_grad(&mut self) -> Result<()> {
997 Ok(())
998 }
999
1000 fn step(&mut self, parameters: &mut [Tensor]) -> Result<()> {
1001 self.current_step += 1;
1002
1003 for (param_id, parameter) in parameters.iter_mut().enumerate() {
1004 let gradient = match parameter.grad() {
1006 Ok(grad) => grad,
1007 Err(_) => {
1008 continue;
1010 },
1011 };
1012
1013 let effective_grad = if self.config.weight_decay > 0.0 {
1015 gradient.add(¶meter.mul_scalar(self.config.weight_decay)?)?
1016 } else {
1017 gradient
1018 };
1019
1020 let beta = if self.config.adaptive_momentum {
1022 self.compute_adaptive_momentum(param_id, &effective_grad)?
1023 } else {
1024 self.config.beta
1025 };
1026
1027 let velocity = if let Some(v) = self.velocity_buffers.get(¶m_id) {
1029 v.clone()
1030 } else {
1031 Tensor::zeros_like(parameter)?
1032 };
1033
1034 let new_velocity = velocity
1036 .mul_scalar(beta)?
1037 .sub(&effective_grad.mul_scalar(self.config.learning_rate)?)?;
1038
1039 self.velocity_buffers.insert(param_id, new_velocity.clone());
1040
1041 *parameter = parameter.add(&new_velocity)?;
1043
1044 if self.config.adaptive_momentum {
1046 self.previous_gradients.insert(param_id, effective_grad);
1047 }
1048 }
1049
1050 Ok(())
1051 }
1052
1053 fn get_lr(&self) -> f32 {
1054 self.config.learning_rate
1055 }
1056
1057 fn set_lr(&mut self, lr: f32) {
1058 self.config.learning_rate = lr;
1059 }
1060
1061 fn state_dict(&self) -> Result<HashMap<String, Tensor>> {
1062 let mut state = HashMap::new();
1063
1064 state.insert(
1065 "learning_rate".to_string(),
1066 Tensor::scalar(self.config.learning_rate)?,
1067 );
1068 state.insert("beta".to_string(), Tensor::scalar(self.config.beta)?);
1069 state.insert(
1070 "weight_decay".to_string(),
1071 Tensor::scalar(self.config.weight_decay)?,
1072 );
1073 state.insert(
1074 "current_step".to_string(),
1075 Tensor::scalar(self.current_step as f32)?,
1076 );
1077
1078 for (¶m_id, velocity) in &self.velocity_buffers {
1079 state.insert(format!("velocity_{}", param_id), velocity.clone());
1080 }
1081
1082 for (¶m_id, grad) in &self.previous_gradients {
1083 state.insert(format!("prev_grad_{}", param_id), grad.clone());
1084 }
1085
1086 Ok(state)
1087 }
1088
1089 fn load_state_dict(&mut self, state: HashMap<String, Tensor>) -> Result<()> {
1090 if let Some(lr) = state.get("learning_rate") {
1091 self.config.learning_rate = lr.to_scalar()?;
1092 }
1093 if let Some(beta) = state.get("beta") {
1094 self.config.beta = beta.to_scalar()?;
1095 }
1096 if let Some(wd) = state.get("weight_decay") {
1097 self.config.weight_decay = wd.to_scalar()?;
1098 }
1099 if let Some(step) = state.get("current_step") {
1100 self.current_step = step.to_scalar()? as usize;
1101 }
1102
1103 self.velocity_buffers.clear();
1104 self.previous_gradients.clear();
1105
1106 for (key, tensor) in state {
1107 if let Some(param_id_str) = key.strip_prefix("velocity_") {
1108 if let Ok(param_id) = param_id_str.parse::<usize>() {
1109 self.velocity_buffers.insert(param_id, tensor);
1110 }
1111 } else if let Some(param_id_str) = key.strip_prefix("prev_grad_") {
1112 if let Ok(param_id) = param_id_str.parse::<usize>() {
1113 self.previous_gradients.insert(param_id, tensor);
1114 }
1115 }
1116 }
1117
1118 Ok(())
1119 }
1120}
1121
1122#[derive(Debug, Clone, Serialize, Deserialize)]
1124pub struct FISTAConfig {
1125 pub learning_rate: f32,
1127 pub threshold: f32,
1129 pub adaptive_restart: bool,
1131 pub weight_decay: f32,
1133}
1134
1135impl Default for FISTAConfig {
1136 fn default() -> Self {
1137 Self {
1138 learning_rate: 1e-3,
1139 threshold: 1e-4,
1140 adaptive_restart: true,
1141 weight_decay: 0.0,
1142 }
1143 }
1144}
1145
1146#[derive(Debug)]
1151pub struct FISTA {
1152 config: FISTAConfig,
1153 previous_params: HashMap<usize, Tensor>,
1154 current_step: usize,
1155 momentum_coefficient: f32,
1156 previous_momentum: f32,
1157}
1158
1159impl FISTA {
1160 pub fn new(config: FISTAConfig) -> Self {
1162 Self {
1163 config,
1164 previous_params: HashMap::new(),
1165 current_step: 0,
1166 momentum_coefficient: 1.0,
1167 previous_momentum: 1.0,
1168 }
1169 }
1170
1171 pub fn with_defaults(learning_rate: f32, threshold: f32) -> Self {
1173 Self::new(FISTAConfig {
1174 learning_rate,
1175 threshold,
1176 adaptive_restart: true,
1177 weight_decay: 0.0,
1178 })
1179 }
1180
1181 pub fn get_config(&self) -> &FISTAConfig {
1183 &self.config
1184 }
1185
1186 fn soft_threshold(&self, tensor: &Tensor, threshold: f32) -> Result<Tensor> {
1188 let threshold_tensor = Tensor::scalar(threshold)?;
1189 let zero_tensor = Tensor::zeros_like(tensor)?;
1190
1191 let abs_tensor = tensor.abs()?;
1193 let thresholded = abs_tensor.sub(&threshold_tensor)?.max(&zero_tensor)?;
1194 let sign_tensor = tensor.sign()?;
1195
1196 Ok(sign_tensor.mul(&thresholded)?)
1197 }
1198
1199 fn update_momentum_coefficient(&mut self) {
1201 let t = self.current_step as f32;
1202 self.previous_momentum = self.momentum_coefficient;
1203 self.momentum_coefficient = (1.0 + (1.0 + 4.0 * t * t).sqrt()) / 2.0;
1204 }
1205}
1206
1207impl OptimizerState for FISTA {
1208 fn zero_grad(&mut self) -> Result<()> {
1209 Ok(())
1210 }
1211
1212 fn step(&mut self, parameters: &mut [Tensor]) -> Result<()> {
1213 self.current_step += 1;
1214 self.update_momentum_coefficient();
1215
1216 for (param_id, parameter) in parameters.iter_mut().enumerate() {
1217 let gradient = match parameter.grad() {
1219 Ok(grad) => grad,
1220 Err(_) => {
1221 continue;
1223 },
1224 };
1225
1226 let effective_grad = if self.config.weight_decay > 0.0 {
1228 gradient.add(¶meter.mul_scalar(self.config.weight_decay)?)?
1229 } else {
1230 gradient
1231 };
1232
1233 let previous_param = if let Some(prev) = self.previous_params.get(¶m_id) {
1235 prev.clone()
1236 } else {
1237 parameter.clone()
1238 };
1239
1240 let beta = (self.previous_momentum - 1.0) / self.momentum_coefficient;
1242
1243 let extrapolated = parameter.add(&previous_param.sub(parameter)?.mul_scalar(beta)?)?;
1245
1246 let grad_step =
1248 extrapolated.sub(&effective_grad.mul_scalar(self.config.learning_rate)?)?;
1249
1250 let new_parameter = self.soft_threshold(&grad_step, self.config.threshold)?;
1252
1253 self.previous_params.insert(param_id, parameter.clone());
1255
1256 *parameter = new_parameter;
1258 }
1259
1260 Ok(())
1261 }
1262
1263 fn get_lr(&self) -> f32 {
1264 self.config.learning_rate
1265 }
1266
1267 fn set_lr(&mut self, lr: f32) {
1268 self.config.learning_rate = lr;
1269 }
1270
1271 fn state_dict(&self) -> Result<HashMap<String, Tensor>> {
1272 let mut state = HashMap::new();
1273
1274 state.insert(
1275 "learning_rate".to_string(),
1276 Tensor::scalar(self.config.learning_rate)?,
1277 );
1278 state.insert(
1279 "threshold".to_string(),
1280 Tensor::scalar(self.config.threshold)?,
1281 );
1282 state.insert(
1283 "weight_decay".to_string(),
1284 Tensor::scalar(self.config.weight_decay)?,
1285 );
1286 state.insert(
1287 "current_step".to_string(),
1288 Tensor::scalar(self.current_step as f32)?,
1289 );
1290 state.insert(
1291 "momentum_coefficient".to_string(),
1292 Tensor::scalar(self.momentum_coefficient)?,
1293 );
1294 state.insert(
1295 "previous_momentum".to_string(),
1296 Tensor::scalar(self.previous_momentum)?,
1297 );
1298
1299 for (¶m_id, param) in &self.previous_params {
1300 state.insert(format!("prev_param_{}", param_id), param.clone());
1301 }
1302
1303 Ok(state)
1304 }
1305
1306 fn load_state_dict(&mut self, state: HashMap<String, Tensor>) -> Result<()> {
1307 if let Some(lr) = state.get("learning_rate") {
1308 self.config.learning_rate = lr.to_scalar()?;
1309 }
1310 if let Some(threshold) = state.get("threshold") {
1311 self.config.threshold = threshold.to_scalar()?;
1312 }
1313 if let Some(wd) = state.get("weight_decay") {
1314 self.config.weight_decay = wd.to_scalar()?;
1315 }
1316 if let Some(step) = state.get("current_step") {
1317 self.current_step = step.to_scalar()? as usize;
1318 }
1319 if let Some(momentum) = state.get("momentum_coefficient") {
1320 self.momentum_coefficient = momentum.to_scalar()?;
1321 }
1322 if let Some(prev_momentum) = state.get("previous_momentum") {
1323 self.previous_momentum = prev_momentum.to_scalar()?;
1324 }
1325
1326 self.previous_params.clear();
1327 for (key, tensor) in state {
1328 if let Some(param_id_str) = key.strip_prefix("prev_param_") {
1329 if let Ok(param_id) = param_id_str.parse::<usize>() {
1330 self.previous_params.insert(param_id, tensor);
1331 }
1332 }
1333 }
1334
1335 Ok(())
1336 }
1337}
1338
1339#[derive(Debug, Clone, Serialize, Deserialize)]
1341pub struct AdaptiveBatchSizingConfig {
1342 pub initial_batch_size: usize,
1344 pub min_batch_size: usize,
1346 pub max_batch_size: usize,
1348 pub gradient_variance_tolerance: f32,
1350 pub lr_adaptation_factor: f32,
1352 pub variance_window_size: usize,
1354 pub increase_threshold: f32,
1356 pub decrease_threshold: f32,
1358}
1359
1360impl Default for AdaptiveBatchSizingConfig {
1361 fn default() -> Self {
1362 Self {
1363 initial_batch_size: 32,
1364 min_batch_size: 8,
1365 max_batch_size: 512,
1366 gradient_variance_tolerance: 0.1,
1367 lr_adaptation_factor: 0.8,
1368 variance_window_size: 10,
1369 increase_threshold: 0.05,
1370 decrease_threshold: 0.2,
1371 }
1372 }
1373}
1374
1375#[derive(Debug)]
1381pub struct AdaptiveBatchSizing {
1382 config: AdaptiveBatchSizingConfig,
1383 current_batch_size: usize,
1384 gradient_variance_history: Vec<f32>,
1385 loss_history: Vec<f32>,
1386 current_step: usize,
1387 last_adjustment_step: usize,
1388}
1389
1390impl AdaptiveBatchSizing {
1391 pub fn new(config: AdaptiveBatchSizingConfig) -> Self {
1393 let initial_batch_size = config.initial_batch_size;
1394 Self {
1395 config,
1396 current_batch_size: initial_batch_size,
1397 gradient_variance_history: Vec::new(),
1398 loss_history: Vec::new(),
1399 current_step: 0,
1400 last_adjustment_step: 0,
1401 }
1402 }
1403
1404 pub fn with_defaults(
1406 initial_batch_size: usize,
1407 min_batch_size: usize,
1408 max_batch_size: usize,
1409 ) -> Self {
1410 Self::new(AdaptiveBatchSizingConfig {
1411 initial_batch_size,
1412 min_batch_size,
1413 max_batch_size,
1414 ..Default::default()
1415 })
1416 }
1417
1418 pub fn current_batch_size(&self) -> usize {
1420 self.current_batch_size
1421 }
1422
1423 pub fn get_config(&self) -> &AdaptiveBatchSizingConfig {
1425 &self.config
1426 }
1427
1428 pub fn update(&mut self, gradient_variance: f32, current_loss: f32) -> Result<usize> {
1430 self.current_step += 1;
1431
1432 self.gradient_variance_history.push(gradient_variance);
1434 self.loss_history.push(current_loss);
1435
1436 if self.gradient_variance_history.len() > self.config.variance_window_size {
1438 self.gradient_variance_history.remove(0);
1439 }
1440 if self.loss_history.len() > self.config.variance_window_size {
1441 self.loss_history.remove(0);
1442 }
1443
1444 if self.should_adjust_batch_size() {
1446 self.adjust_batch_size()?;
1447 self.last_adjustment_step = self.current_step;
1448 }
1449
1450 Ok(self.current_batch_size)
1451 }
1452
1453 pub fn compute_gradient_variance(&self, gradients: &[Tensor]) -> Result<f32> {
1455 if gradients.is_empty() {
1456 return Ok(0.0);
1457 }
1458
1459 let mut mean_grad = gradients[0].clone();
1461 for grad in gradients.iter().skip(1) {
1462 mean_grad = mean_grad.add(grad)?;
1463 }
1464 mean_grad = mean_grad.div_scalar(gradients.len() as f32)?;
1465
1466 let mut variance_sum = 0.0;
1468 for grad in gradients {
1469 let diff = grad.sub(&mean_grad)?;
1470 let squared_norm = diff.mul(&diff)?.sum(None, false)?;
1471 variance_sum += squared_norm.to_scalar()?;
1472 }
1473
1474 Ok(variance_sum / gradients.len() as f32)
1475 }
1476
1477 fn should_adjust_batch_size(&self) -> bool {
1478 if self.current_step - self.last_adjustment_step < 5 {
1480 return false;
1481 }
1482
1483 self.gradient_variance_history.len() >= 3
1485 }
1486
1487 fn adjust_batch_size(&mut self) -> Result<()> {
1488 let recent_variance = self.recent_average_variance();
1489 let variance_trend = self.variance_trend();
1490 let loss_trend = self.loss_trend();
1491
1492 if recent_variance > self.config.decrease_threshold && variance_trend > 0.0 {
1494 self.increase_batch_size();
1496 } else if recent_variance < self.config.increase_threshold && loss_trend < -0.01 {
1497 self.decrease_batch_size();
1499 }
1500
1501 Ok(())
1502 }
1503
1504 fn recent_average_variance(&self) -> f32 {
1505 if self.gradient_variance_history.is_empty() {
1506 return 0.0;
1507 }
1508
1509 let recent_window = std::cmp::min(5, self.gradient_variance_history.len());
1510 let start_idx = self.gradient_variance_history.len() - recent_window;
1511
1512 self.gradient_variance_history[start_idx..].iter().sum::<f32>() / recent_window as f32
1513 }
1514
1515 fn variance_trend(&self) -> f32 {
1516 if self.gradient_variance_history.len() < 3 {
1517 return 0.0;
1518 }
1519
1520 let len = self.gradient_variance_history.len();
1521 let recent = self.gradient_variance_history[len - 2..].iter().sum::<f32>() / 2.0;
1522 let older = self.gradient_variance_history[len - 4..len - 2].iter().sum::<f32>() / 2.0;
1523
1524 recent - older
1525 }
1526
1527 fn loss_trend(&self) -> f32 {
1528 if self.loss_history.len() < 3 {
1529 return 0.0;
1530 }
1531
1532 let len = self.loss_history.len();
1533 let recent = self.loss_history[len - 2..].iter().sum::<f32>() / 2.0;
1534 let older = self.loss_history[len - 4..len - 2].iter().sum::<f32>() / 2.0;
1535
1536 (recent - older) / older.max(1e-8)
1537 }
1538
1539 fn increase_batch_size(&mut self) {
1540 let new_size = (self.current_batch_size as f32 * 1.5) as usize;
1541 self.current_batch_size = new_size.min(self.config.max_batch_size);
1542 }
1543
1544 fn decrease_batch_size(&mut self) {
1545 let new_size = (self.current_batch_size as f32 * 0.8) as usize;
1546 self.current_batch_size = new_size.max(self.config.min_batch_size);
1547 }
1548
1549 pub fn get_lr_adjustment(&self, original_batch_size: usize) -> f32 {
1551 let ratio = self.current_batch_size as f32 / original_batch_size as f32;
1552 ratio.sqrt() * self.config.lr_adaptation_factor
1553 }
1554
1555 pub fn reset(&mut self) {
1557 self.current_batch_size = self.config.initial_batch_size;
1558 self.gradient_variance_history.clear();
1559 self.loss_history.clear();
1560 self.current_step = 0;
1561 self.last_adjustment_step = 0;
1562 }
1563}
1564
1565#[derive(Debug, Clone, Serialize, Deserialize)]
1567pub struct LossSurfaceSmoothingConfig {
1568 pub smoothing_strength: f32,
1570 pub noise_variance: f32,
1572 pub ema_decay: f32,
1574 pub averaging_window: usize,
1576 pub use_gradient_averaging: bool,
1578 pub use_noise_injection: bool,
1580}
1581
1582impl Default for LossSurfaceSmoothingConfig {
1583 fn default() -> Self {
1584 Self {
1585 smoothing_strength: 0.1,
1586 noise_variance: 1e-4,
1587 ema_decay: 0.9,
1588 averaging_window: 5,
1589 use_gradient_averaging: true,
1590 use_noise_injection: false,
1591 }
1592 }
1593}
1594
1595#[derive(Debug)]
1603pub struct LossSurfaceSmoothing {
1604 config: LossSurfaceSmoothingConfig,
1605 gradient_history: HashMap<usize, Vec<Tensor>>,
1606 ema_gradients: HashMap<usize, Tensor>,
1607 smoothed_parameters: HashMap<usize, Tensor>,
1608 current_step: usize,
1609}
1610
1611impl LossSurfaceSmoothing {
1612 pub fn new(config: LossSurfaceSmoothingConfig) -> Self {
1614 Self {
1615 config,
1616 gradient_history: HashMap::new(),
1617 ema_gradients: HashMap::new(),
1618 smoothed_parameters: HashMap::new(),
1619 current_step: 0,
1620 }
1621 }
1622
1623 pub fn with_defaults(smoothing_strength: f32, use_noise: bool) -> Self {
1625 Self::new(LossSurfaceSmoothingConfig {
1626 smoothing_strength,
1627 use_noise_injection: use_noise,
1628 ..Default::default()
1629 })
1630 }
1631
1632 pub fn get_config(&self) -> &LossSurfaceSmoothingConfig {
1634 &self.config
1635 }
1636
1637 pub fn smooth_gradients(&mut self, parameters: &mut [Tensor]) -> Result<()> {
1639 self.current_step += 1;
1640
1641 for (param_id, parameter) in parameters.iter_mut().enumerate() {
1642 let original_grad = parameter.grad()?;
1643 let mut smoothed_grad = original_grad.clone();
1644
1645 if self.config.use_gradient_averaging {
1647 smoothed_grad = self.apply_gradient_averaging(param_id, &original_grad)?;
1648 }
1649
1650 smoothed_grad = self.apply_ema_smoothing(param_id, &smoothed_grad)?;
1652
1653 if self.config.use_noise_injection {
1655 smoothed_grad = self.apply_noise_injection(&smoothed_grad)?;
1656 }
1657
1658 parameter.set_grad(smoothed_grad)?;
1660 }
1661
1662 Ok(())
1663 }
1664
1665 pub fn smooth_parameters(&mut self, parameters: &mut [Tensor]) -> Result<()> {
1667 for (param_id, parameter) in parameters.iter_mut().enumerate() {
1668 if let Some(smoothed_param) = self.smoothed_parameters.get(¶m_id) {
1669 let new_smoothed = smoothed_param
1671 .mul_scalar(self.config.ema_decay)?
1672 .add(¶meter.mul_scalar(1.0 - self.config.ema_decay)?)?;
1673
1674 *parameter = parameter
1676 .mul_scalar(1.0 - self.config.smoothing_strength)?
1677 .add(&new_smoothed.mul_scalar(self.config.smoothing_strength)?)?;
1678
1679 self.smoothed_parameters.insert(param_id, new_smoothed);
1680 } else {
1681 self.smoothed_parameters.insert(param_id, parameter.clone());
1683 }
1684 }
1685
1686 Ok(())
1687 }
1688
1689 fn apply_gradient_averaging(&mut self, param_id: usize, gradient: &Tensor) -> Result<Tensor> {
1690 let history = self.gradient_history.entry(param_id).or_default();
1691
1692 history.push(gradient.clone());
1693 if history.len() > self.config.averaging_window {
1694 history.remove(0);
1695 }
1696
1697 if history.len() == 1 {
1699 Ok(gradient.clone())
1700 } else {
1701 let mut sum = history[0].clone();
1702 for grad in history.iter().skip(1) {
1703 sum = sum.add(grad)?;
1704 }
1705 Ok(sum.div_scalar(history.len() as f32)?)
1706 }
1707 }
1708
1709 fn apply_ema_smoothing(&mut self, param_id: usize, gradient: &Tensor) -> Result<Tensor> {
1710 if let Some(ema_grad) = self.ema_gradients.get(¶m_id) {
1711 let new_ema = ema_grad
1712 .mul_scalar(self.config.ema_decay)?
1713 .add(&gradient.mul_scalar(1.0 - self.config.ema_decay)?)?;
1714 self.ema_gradients.insert(param_id, new_ema.clone());
1715 Ok(new_ema)
1716 } else {
1717 self.ema_gradients.insert(param_id, gradient.clone());
1718 Ok(gradient.clone())
1719 }
1720 }
1721
1722 fn apply_noise_injection(&self, gradient: &Tensor) -> Result<Tensor> {
1723 let noise = Tensor::randn_like(gradient)
1724 .map_err(|e| anyhow!("Failed to create noise tensor: {}", e))?
1725 .mul_scalar(self.config.noise_variance.sqrt())
1726 .map_err(|e| anyhow!("Failed to scale noise tensor: {}", e))?;
1727 gradient
1728 .add(&noise)
1729 .map_err(|e| anyhow!("Failed to add noise to gradient: {}", e))
1730 }
1731
1732 pub fn reset(&mut self) {
1734 self.gradient_history.clear();
1735 self.ema_gradients.clear();
1736 self.smoothed_parameters.clear();
1737 self.current_step = 0;
1738 }
1739
1740 pub fn get_statistics(&self) -> HashMap<String, f32> {
1742 let mut stats = HashMap::new();
1743 stats.insert("current_step".to_string(), self.current_step as f32);
1744 stats.insert(
1745 "num_tracked_params".to_string(),
1746 self.gradient_history.len() as f32,
1747 );
1748 stats.insert(
1749 "smoothing_strength".to_string(),
1750 self.config.smoothing_strength,
1751 );
1752 stats.insert("ema_decay".to_string(), self.config.ema_decay);
1753 stats
1754 }
1755}
1756
1757#[cfg(test)]
1758mod tests {
1759 use super::*;
1760
1761 #[test]
1762 fn test_qhm_config_default() {
1763 let config = QHMConfig::default();
1764 assert_eq!(config.learning_rate, 1e-3);
1765 assert_eq!(config.momentum, 0.9);
1766 assert_eq!(config.nu, 0.7);
1767 assert_eq!(config.weight_decay, 0.0);
1768 }
1769
1770 #[test]
1771 fn test_aggmo_config_default() {
1772 let config = AggMoConfig::default();
1773 assert_eq!(config.learning_rate, 1e-3);
1774 assert_eq!(config.momentum_coefficients, vec![0.0, 0.9, 0.99]);
1775 assert_eq!(config.weight_decay, 0.0);
1776 }
1777
1778 #[test]
1779 fn test_qhm_creation() {
1780 let optimizer = QHM::with_defaults(1e-3, 0.9, 0.7);
1781 assert_eq!(optimizer.get_lr(), 1e-3);
1782 assert_eq!(optimizer.current_step, 0);
1783 }
1784
1785 #[test]
1786 fn test_aggmo_creation() {
1787 let optimizer = AggMo::with_defaults(1e-3, vec![0.0, 0.9, 0.99]);
1788 assert_eq!(optimizer.get_lr(), 1e-3);
1789 assert_eq!(optimizer.num_momentum_buffers(), 3);
1790 }
1791
1792 #[test]
1793 fn test_variance_reduction_svrg() {
1794 let optimizer = VarianceReduction::svrg(1e-3, 50, 10);
1795 assert_eq!(optimizer.get_lr(), 1e-3);
1796 assert_eq!(optimizer.current_step, 0);
1797 }
1798
1799 #[test]
1800 fn test_variance_reduction_sag() {
1801 let optimizer = VarianceReduction::sag(1e-3, 100);
1802 assert_eq!(optimizer.get_lr(), 1e-3);
1803 assert!(matches!(
1804 optimizer.config.method,
1805 VarianceReductionMethod::SAG
1806 ));
1807 }
1808
1809 #[test]
1810 fn test_nesterov_accelerated_gradient_config() {
1811 let config = NesterovAcceleratedGradientConfig::default();
1812 assert_eq!(config.learning_rate, 1e-3);
1813 assert_eq!(config.momentum, 0.9);
1814 assert_eq!(config.weight_decay, 0.0);
1815 assert!(!config.restart_on_increase);
1816 }
1817
1818 #[test]
1819 fn test_nesterov_accelerated_gradient_creation() {
1820 let optimizer = NesterovAcceleratedGradient::with_defaults(1e-3, 0.9);
1821 assert_eq!(optimizer.get_lr(), 1e-3);
1822 assert_eq!(optimizer.current_step, 0);
1823 assert!(optimizer.previous_loss.is_none());
1824 }
1825
1826 #[test]
1827 fn test_nesterov_restart_on_increase() {
1828 let mut optimizer = NesterovAcceleratedGradient::new(NesterovAcceleratedGradientConfig {
1829 learning_rate: 1e-3,
1830 momentum: 0.9,
1831 weight_decay: 0.0,
1832 restart_on_increase: true,
1833 });
1834
1835 optimizer.set_current_loss(1.0);
1837 assert_eq!(optimizer.previous_loss, Some(1.0));
1838
1839 optimizer.set_current_loss(1.5);
1841 assert_eq!(optimizer.previous_loss, Some(1.5));
1842 }
1843
1844 #[test]
1845 fn test_heavy_ball_config() {
1846 let config = HeavyBallConfig::default();
1847 assert_eq!(config.learning_rate, 1e-3);
1848 assert_eq!(config.beta, 0.9);
1849 assert_eq!(config.weight_decay, 0.0);
1850 assert!(!config.adaptive_momentum);
1851 }
1852
1853 #[test]
1854 fn test_heavy_ball_creation() {
1855 let optimizer = HeavyBall::with_defaults(1e-3, 0.9);
1856 assert_eq!(optimizer.get_lr(), 1e-3);
1857 assert_eq!(optimizer.current_step, 0);
1858 assert_eq!(optimizer.get_config().beta, 0.9);
1859 }
1860
1861 #[test]
1862 fn test_heavy_ball_adaptive_momentum() {
1863 let optimizer = HeavyBall::new(HeavyBallConfig {
1864 learning_rate: 1e-3,
1865 beta: 0.9,
1866 weight_decay: 0.0,
1867 adaptive_momentum: true,
1868 });
1869
1870 assert!(optimizer.config.adaptive_momentum);
1871 }
1872
1873 #[test]
1874 fn test_fista_config() {
1875 let config = FISTAConfig::default();
1876 assert_eq!(config.learning_rate, 1e-3);
1877 assert_eq!(config.threshold, 1e-4);
1878 assert!(config.adaptive_restart);
1879 assert_eq!(config.weight_decay, 0.0);
1880 }
1881
1882 #[test]
1883 fn test_fista_creation() {
1884 let optimizer = FISTA::with_defaults(1e-3, 1e-4);
1885 assert_eq!(optimizer.get_lr(), 1e-3);
1886 assert_eq!(optimizer.current_step, 0);
1887 assert_eq!(optimizer.momentum_coefficient, 1.0);
1888 assert_eq!(optimizer.previous_momentum, 1.0);
1889 }
1890
1891 #[test]
1892 fn test_fista_momentum_update() {
1893 let mut optimizer = FISTA::with_defaults(1e-3, 1e-4);
1894
1895 optimizer.current_step = 1;
1897 optimizer.update_momentum_coefficient();
1898 assert!(optimizer.momentum_coefficient > 1.0);
1899 assert_eq!(optimizer.previous_momentum, 1.0);
1900
1901 let prev_momentum = optimizer.momentum_coefficient;
1902 optimizer.current_step = 2;
1903 optimizer.update_momentum_coefficient();
1904 assert!(optimizer.momentum_coefficient > prev_momentum);
1905 }
1906
1907 #[test]
1908 fn test_adaptive_batch_sizing_config() {
1909 let config = AdaptiveBatchSizingConfig::default();
1910 assert_eq!(config.initial_batch_size, 32);
1911 assert_eq!(config.min_batch_size, 8);
1912 assert_eq!(config.max_batch_size, 512);
1913 assert_eq!(config.gradient_variance_tolerance, 0.1);
1914 assert_eq!(config.lr_adaptation_factor, 0.8);
1915 assert_eq!(config.variance_window_size, 10);
1916 assert_eq!(config.increase_threshold, 0.05);
1917 assert_eq!(config.decrease_threshold, 0.2);
1918 }
1919
1920 #[test]
1921 fn test_adaptive_batch_sizing_creation() {
1922 let abs = AdaptiveBatchSizing::with_defaults(64, 16, 256);
1923 assert_eq!(abs.current_batch_size(), 64);
1924 assert_eq!(abs.get_config().min_batch_size, 16);
1925 assert_eq!(abs.get_config().max_batch_size, 256);
1926 }
1927
1928 #[test]
1929 fn test_adaptive_batch_sizing_lr_adjustment() {
1930 let abs = AdaptiveBatchSizing::with_defaults(64, 16, 256);
1931 let lr_adj = abs.get_lr_adjustment(32);
1932 assert!(lr_adj > 0.0);
1933 assert!(lr_adj < 2.0);
1934 }
1935
1936 #[test]
1937 fn test_adaptive_batch_sizing_reset() {
1938 let mut abs = AdaptiveBatchSizing::with_defaults(64, 16, 256);
1939 abs.current_step = 10;
1940 abs.reset();
1941 assert_eq!(abs.current_step, 0);
1942 assert_eq!(abs.current_batch_size(), 64);
1943 }
1944
1945 #[test]
1946 fn test_loss_surface_smoothing_config() {
1947 let config = LossSurfaceSmoothingConfig::default();
1948 assert_eq!(config.smoothing_strength, 0.1);
1949 assert_eq!(config.noise_variance, 1e-4);
1950 assert_eq!(config.ema_decay, 0.9);
1951 assert_eq!(config.averaging_window, 5);
1952 assert!(config.use_gradient_averaging);
1953 assert!(!config.use_noise_injection);
1954 }
1955
1956 #[test]
1957 fn test_loss_surface_smoothing_creation() {
1958 let lss = LossSurfaceSmoothing::with_defaults(0.2, true);
1959 assert_eq!(lss.get_config().smoothing_strength, 0.2);
1960 assert!(lss.get_config().use_noise_injection);
1961 assert_eq!(lss.current_step, 0);
1962 }
1963
1964 #[test]
1965 fn test_loss_surface_smoothing_statistics() {
1966 let lss = LossSurfaceSmoothing::with_defaults(0.1, false);
1967 let stats = lss.get_statistics();
1968 assert_eq!(stats.get("current_step"), Some(&0.0));
1969 assert_eq!(stats.get("num_tracked_params"), Some(&0.0));
1970 assert_eq!(stats.get("smoothing_strength"), Some(&0.1));
1971 assert_eq!(stats.get("ema_decay"), Some(&0.9));
1972 }
1973
1974 #[test]
1975 fn test_loss_surface_smoothing_reset() {
1976 let mut lss = LossSurfaceSmoothing::with_defaults(0.1, false);
1977 lss.current_step = 5;
1978 lss.reset();
1979 assert_eq!(lss.current_step, 0);
1980 }
1981}