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 {
250 assert!(
251 !config.momentum_coefficients.is_empty(),
252 "Must provide at least one momentum coefficient"
253 );
254 Self {
255 config,
256 momentum_buffers: HashMap::new(),
257 current_step: 0,
258 }
259 }
260
261 pub fn with_defaults(learning_rate: f32, momentum_coefficients: Vec<f32>) -> Self {
263 Self::new(AggMoConfig {
264 learning_rate,
265 momentum_coefficients,
266 weight_decay: 0.0,
267 })
268 }
269
270 pub fn get_config(&self) -> &AggMoConfig {
272 &self.config
273 }
274
275 pub fn num_momentum_buffers(&self) -> usize {
277 self.config.momentum_coefficients.len()
278 }
279}
280
281impl OptimizerState for AggMo {
282 fn zero_grad(&mut self) -> Result<()> {
283 Ok(())
284 }
285
286 fn step(&mut self, parameters: &mut [Tensor]) -> Result<()> {
287 self.current_step += 1;
288
289 for (param_id, parameter) in parameters.iter_mut().enumerate() {
290 let gradient = match parameter.grad() {
292 Ok(grad) => grad,
293 Err(_) => {
294 continue;
296 },
297 };
298
299 let effective_grad = if self.config.weight_decay > 0.0 {
301 gradient.add(¶meter.mul_scalar(self.config.weight_decay)?)?
302 } else {
303 gradient
304 };
305
306 let buffers = match self.momentum_buffers.entry(param_id) {
308 std::collections::hash_map::Entry::Occupied(entry) => entry.into_mut(),
309 std::collections::hash_map::Entry::Vacant(entry) => {
310 let init = (0..self.config.momentum_coefficients.len())
312 .map(|_| Tensor::zeros(&effective_grad.shape()))
313 .collect::<std::result::Result<Vec<_>, _>>()?;
314 entry.insert(init)
315 },
316 };
317
318 let mut aggregated_momentum = Tensor::zeros(&effective_grad.shape())?;
320 for (i, &beta) in self.config.momentum_coefficients.iter().enumerate() {
321 buffers[i] =
323 buffers[i].mul_scalar(beta)?.add(&effective_grad.mul_scalar(1.0 - beta)?)?;
324
325 aggregated_momentum = aggregated_momentum.add(&buffers[i])?;
327 }
328
329 let num_buffers = self.config.momentum_coefficients.len() as f32;
331 let averaged_momentum = aggregated_momentum.div_scalar(num_buffers)?;
332
333 *parameter =
335 parameter.sub(&averaged_momentum.mul_scalar(self.config.learning_rate)?)?;
336 }
337
338 Ok(())
339 }
340
341 fn get_lr(&self) -> f32 {
342 self.config.learning_rate
343 }
344
345 fn set_lr(&mut self, lr: f32) {
346 self.config.learning_rate = lr;
347 }
348
349 fn state_dict(&self) -> Result<HashMap<String, Tensor>> {
350 let mut state = HashMap::new();
351
352 state.insert(
354 "learning_rate".to_string(),
355 Tensor::scalar(self.config.learning_rate)?,
356 );
357 state.insert(
358 "weight_decay".to_string(),
359 Tensor::scalar(self.config.weight_decay)?,
360 );
361 state.insert(
362 "current_step".to_string(),
363 Tensor::scalar(self.current_step as f32)?,
364 );
365 state.insert(
366 "num_momentum_coeffs".to_string(),
367 Tensor::scalar(self.config.momentum_coefficients.len() as f32)?,
368 );
369
370 for (i, &coeff) in self.config.momentum_coefficients.iter().enumerate() {
372 state.insert(format!("momentum_coeff_{}", i), Tensor::scalar(coeff)?);
373 }
374
375 for (¶m_id, buffers) in &self.momentum_buffers {
377 for (buffer_idx, buffer) in buffers.iter().enumerate() {
378 state.insert(
379 format!("momentum_buffer_{}_{}", param_id, buffer_idx),
380 buffer.clone(),
381 );
382 }
383 }
384
385 Ok(state)
386 }
387
388 fn load_state_dict(&mut self, state: HashMap<String, Tensor>) -> Result<()> {
389 if let Some(lr) = state.get("learning_rate") {
391 self.config.learning_rate = lr.to_scalar()?;
392 }
393 if let Some(wd) = state.get("weight_decay") {
394 self.config.weight_decay = wd.to_scalar()?;
395 }
396 if let Some(step) = state.get("current_step") {
397 self.current_step = step.to_scalar()? as usize;
398 }
399
400 if let Some(num_coeffs_tensor) = state.get("num_momentum_coeffs") {
402 let num_coeffs = num_coeffs_tensor.to_scalar()? as usize;
403 let mut coefficients = Vec::with_capacity(num_coeffs);
404 for i in 0..num_coeffs {
405 if let Some(coeff_tensor) = state.get(&format!("momentum_coeff_{}", i)) {
406 coefficients.push(coeff_tensor.to_scalar()?);
407 }
408 }
409 self.config.momentum_coefficients = coefficients;
410 }
411
412 self.momentum_buffers.clear();
414 let mut param_buffers: HashMap<usize, HashMap<usize, Tensor>> = HashMap::new();
415
416 for (key, tensor) in state {
417 if key.starts_with("momentum_buffer_") {
418 let parts: Vec<&str> = key.split('_').collect();
419 if parts.len() >= 4 {
420 if let (Ok(param_id), Ok(buffer_idx)) =
421 (parts[2].parse::<usize>(), parts[3].parse::<usize>())
422 {
423 param_buffers.entry(param_id).or_default().insert(buffer_idx, tensor);
424 }
425 }
426 }
427 }
428
429 for (param_id, buffer_map) in param_buffers {
431 let mut buffers = Vec::new();
432 for i in 0..self.config.momentum_coefficients.len() {
433 if let Some(buffer) = buffer_map.get(&i) {
434 buffers.push(buffer.clone());
435 }
436 }
437 if buffers.len() == self.config.momentum_coefficients.len() {
438 self.momentum_buffers.insert(param_id, buffers);
439 }
440 }
441
442 Ok(())
443 }
444}
445
446#[derive(Debug, Clone, Serialize, Deserialize)]
448pub struct VarianceReductionConfig {
449 pub learning_rate: f32,
451 pub method: VarianceReductionMethod,
453 pub history_size: usize,
455 pub full_grad_frequency: usize,
457 pub weight_decay: f32,
459}
460
461impl Default for VarianceReductionConfig {
462 fn default() -> Self {
463 Self {
464 learning_rate: 1e-3,
465 method: VarianceReductionMethod::SVRG,
466 history_size: 100,
467 full_grad_frequency: 10,
468 weight_decay: 0.0,
469 }
470 }
471}
472
473#[derive(Debug, Clone, Serialize, Deserialize)]
475pub enum VarianceReductionMethod {
476 SVRG,
478 SAG,
480}
481
482#[derive(Debug)]
484pub struct VarianceReduction {
485 config: VarianceReductionConfig,
486 gradient_history: HashMap<usize, Vec<Tensor>>,
487 average_gradients: HashMap<usize, Tensor>,
488 full_gradients: HashMap<usize, Tensor>,
489 current_step: usize,
490 last_full_grad_step: usize,
491}
492
493impl VarianceReduction {
494 pub fn new(config: VarianceReductionConfig) -> Self {
496 Self {
497 config,
498 gradient_history: HashMap::new(),
499 average_gradients: HashMap::new(),
500 full_gradients: HashMap::new(),
501 current_step: 0,
502 last_full_grad_step: 0,
503 }
504 }
505
506 pub fn svrg(learning_rate: f32, history_size: usize, full_grad_frequency: usize) -> Self {
508 Self::new(VarianceReductionConfig {
509 learning_rate,
510 method: VarianceReductionMethod::SVRG,
511 history_size,
512 full_grad_frequency,
513 weight_decay: 0.0,
514 })
515 }
516
517 pub fn sag(learning_rate: f32, history_size: usize) -> Self {
519 Self::new(VarianceReductionConfig {
520 learning_rate,
521 method: VarianceReductionMethod::SAG,
522 history_size,
523 full_grad_frequency: 1, weight_decay: 0.0,
525 })
526 }
527
528 fn update_gradient_history(&mut self, param_id: usize, gradient: &Tensor) -> Result<()> {
529 let history = self.gradient_history.entry(param_id).or_default();
530
531 history.push(gradient.clone());
532 if history.len() > self.config.history_size {
533 history.remove(0);
534 }
535
536 Ok(())
537 }
538
539 fn compute_average_gradient(&mut self, param_id: usize) -> Result<Tensor> {
540 if let Some(history) = self.gradient_history.get(¶m_id) {
541 if history.is_empty() {
542 return Err(anyhow!("No gradient history available"));
543 }
544
545 let mut sum = history[0].clone();
546 for grad in history.iter().skip(1) {
547 sum = sum.add(grad)?;
548 }
549
550 let average = sum.div_scalar(history.len() as f32)?;
551 self.average_gradients.insert(param_id, average.clone());
552 Ok(average)
553 } else {
554 Err(anyhow!("No gradient history for parameter {}", param_id))
555 }
556 }
557
558 fn should_compute_full_gradient(&self) -> bool {
559 self.current_step - self.last_full_grad_step >= self.config.full_grad_frequency
560 }
561}
562
563impl OptimizerState for VarianceReduction {
564 fn zero_grad(&mut self) -> Result<()> {
565 Ok(())
566 }
567
568 fn step(&mut self, parameters: &mut [Tensor]) -> Result<()> {
569 self.current_step += 1;
570
571 let compute_full_grad = match self.config.method {
573 VarianceReductionMethod::SVRG => self.should_compute_full_gradient(),
574 VarianceReductionMethod::SAG => false,
575 };
576
577 if compute_full_grad {
578 self.last_full_grad_step = self.current_step;
579 for (param_id, parameter) in parameters.iter().enumerate() {
582 let gradient = match parameter.grad() {
584 Ok(grad) => grad,
585 Err(_) => {
586 continue;
588 },
589 };
590 self.full_gradients.insert(param_id, gradient);
591 }
592 }
593
594 for (param_id, parameter) in parameters.iter_mut().enumerate() {
595 let current_gradient = match parameter.grad() {
597 Ok(grad) => grad,
598 Err(_) => {
599 continue;
601 },
602 };
603
604 let effective_grad = if self.config.weight_decay > 0.0 {
606 current_gradient.add(¶meter.mul_scalar(self.config.weight_decay)?)?
607 } else {
608 current_gradient
609 };
610
611 self.update_gradient_history(param_id, &effective_grad)?;
613
614 let variance_reduced_grad = match self.config.method {
616 VarianceReductionMethod::SVRG => {
617 let full_grad_opt = self.full_gradients.get(¶m_id).cloned();
619 if let Some(full_grad) = full_grad_opt {
620 let avg_grad = self.compute_average_gradient(param_id)?;
621 effective_grad.sub(&avg_grad)?.add(&full_grad)?
623 } else {
624 effective_grad
625 }
626 },
627 VarianceReductionMethod::SAG => {
628 self.compute_average_gradient(param_id)?
630 },
631 };
632
633 *parameter =
635 parameter.sub(&variance_reduced_grad.mul_scalar(self.config.learning_rate)?)?;
636 }
637
638 Ok(())
639 }
640
641 fn get_lr(&self) -> f32 {
642 self.config.learning_rate
643 }
644
645 fn set_lr(&mut self, lr: f32) {
646 self.config.learning_rate = lr;
647 }
648
649 fn state_dict(&self) -> Result<HashMap<String, Tensor>> {
650 let mut state = HashMap::new();
651
652 state.insert(
653 "learning_rate".to_string(),
654 Tensor::scalar(self.config.learning_rate)?,
655 );
656 state.insert(
657 "current_step".to_string(),
658 Tensor::scalar(self.current_step as f32)?,
659 );
660 state.insert(
661 "last_full_grad_step".to_string(),
662 Tensor::scalar(self.last_full_grad_step as f32)?,
663 );
664
665 Ok(state)
669 }
670
671 fn load_state_dict(&mut self, state: HashMap<String, Tensor>) -> Result<()> {
672 if let Some(lr) = state.get("learning_rate") {
673 self.config.learning_rate = lr.to_scalar()?;
674 }
675 if let Some(step) = state.get("current_step") {
676 self.current_step = step.to_scalar()? as usize;
677 }
678 if let Some(last_step) = state.get("last_full_grad_step") {
679 self.last_full_grad_step = last_step.to_scalar()? as usize;
680 }
681
682 Ok(())
683 }
684}
685
686#[derive(Debug, Clone, Serialize, Deserialize)]
688pub struct NesterovAcceleratedGradientConfig {
689 pub learning_rate: f32,
691 pub momentum: f32,
693 pub weight_decay: f32,
695 pub restart_on_increase: bool,
697}
698
699impl Default for NesterovAcceleratedGradientConfig {
700 fn default() -> Self {
701 Self {
702 learning_rate: 1e-3,
703 momentum: 0.9,
704 weight_decay: 0.0,
705 restart_on_increase: false,
706 }
707 }
708}
709
710#[derive(Debug)]
718pub struct NesterovAcceleratedGradient {
719 config: NesterovAcceleratedGradientConfig,
720 velocity_buffers: HashMap<usize, Tensor>,
721 current_step: usize,
722 previous_loss: Option<f32>,
723}
724
725impl NesterovAcceleratedGradient {
726 pub fn new(config: NesterovAcceleratedGradientConfig) -> Self {
728 Self {
729 config,
730 velocity_buffers: HashMap::new(),
731 current_step: 0,
732 previous_loss: None,
733 }
734 }
735
736 pub fn with_defaults(learning_rate: f32, momentum: f32) -> Self {
738 Self::new(NesterovAcceleratedGradientConfig {
739 learning_rate,
740 momentum,
741 weight_decay: 0.0,
742 restart_on_increase: false,
743 })
744 }
745
746 pub fn get_config(&self) -> &NesterovAcceleratedGradientConfig {
748 &self.config
749 }
750
751 pub fn set_current_loss(&mut self, loss: f32) {
753 if self.config.restart_on_increase {
754 if let Some(prev_loss) = self.previous_loss {
755 if loss > prev_loss {
756 self.velocity_buffers.clear();
758 }
759 }
760 }
761 self.previous_loss = Some(loss);
762 }
763}
764
765impl OptimizerState for NesterovAcceleratedGradient {
766 fn zero_grad(&mut self) -> Result<()> {
767 Ok(())
768 }
769
770 fn step(&mut self, parameters: &mut [Tensor]) -> Result<()> {
771 self.current_step += 1;
772
773 for (param_id, parameter) in parameters.iter_mut().enumerate() {
774 let gradient = match parameter.grad() {
776 Ok(grad) => grad,
777 Err(_) => {
778 continue;
780 },
781 };
782
783 let effective_grad = if self.config.weight_decay > 0.0 {
785 gradient.add(¶meter.mul_scalar(self.config.weight_decay)?)?
786 } else {
787 gradient
788 };
789
790 let velocity = if let Some(v) = self.velocity_buffers.get(¶m_id) {
792 v.clone()
793 } else {
794 Tensor::zeros_like(parameter)?
795 };
796
797 let _lookahead_position = parameter.sub(&velocity.mul_scalar(self.config.momentum)?)?;
799
800 let new_velocity = velocity
806 .mul_scalar(self.config.momentum)?
807 .add(&effective_grad.mul_scalar(self.config.learning_rate)?)?;
808
809 self.velocity_buffers.insert(param_id, new_velocity.clone());
810
811 *parameter = parameter.sub(&new_velocity)?;
813 }
814
815 Ok(())
816 }
817
818 fn get_lr(&self) -> f32 {
819 self.config.learning_rate
820 }
821
822 fn set_lr(&mut self, lr: f32) {
823 self.config.learning_rate = lr;
824 }
825
826 fn state_dict(&self) -> Result<HashMap<String, Tensor>> {
827 let mut state = HashMap::new();
828
829 state.insert(
830 "learning_rate".to_string(),
831 Tensor::scalar(self.config.learning_rate)?,
832 );
833 state.insert(
834 "momentum".to_string(),
835 Tensor::scalar(self.config.momentum)?,
836 );
837 state.insert(
838 "weight_decay".to_string(),
839 Tensor::scalar(self.config.weight_decay)?,
840 );
841 state.insert(
842 "current_step".to_string(),
843 Tensor::scalar(self.current_step as f32)?,
844 );
845
846 if let Some(loss) = self.previous_loss {
847 state.insert("previous_loss".to_string(), Tensor::scalar(loss)?);
848 }
849
850 for (¶m_id, velocity) in &self.velocity_buffers {
851 state.insert(format!("velocity_{}", param_id), velocity.clone());
852 }
853
854 Ok(state)
855 }
856
857 fn load_state_dict(&mut self, state: HashMap<String, Tensor>) -> Result<()> {
858 if let Some(lr) = state.get("learning_rate") {
859 self.config.learning_rate = lr.to_scalar()?;
860 }
861 if let Some(momentum) = state.get("momentum") {
862 self.config.momentum = momentum.to_scalar()?;
863 }
864 if let Some(wd) = state.get("weight_decay") {
865 self.config.weight_decay = wd.to_scalar()?;
866 }
867 if let Some(step) = state.get("current_step") {
868 self.current_step = step.to_scalar()? as usize;
869 }
870 if let Some(loss) = state.get("previous_loss") {
871 self.previous_loss = Some(loss.to_scalar()?);
872 }
873
874 self.velocity_buffers.clear();
875 for (key, tensor) in state {
876 if let Some(param_id_str) = key.strip_prefix("velocity_") {
877 if let Ok(param_id) = param_id_str.parse::<usize>() {
878 self.velocity_buffers.insert(param_id, tensor);
879 }
880 }
881 }
882
883 Ok(())
884 }
885}
886
887#[derive(Debug, Clone, Serialize, Deserialize)]
889pub struct HeavyBallConfig {
890 pub learning_rate: f32,
892 pub beta: f32,
894 pub weight_decay: f32,
896 pub adaptive_momentum: bool,
898}
899
900impl Default for HeavyBallConfig {
901 fn default() -> Self {
902 Self {
903 learning_rate: 1e-3,
904 beta: 0.9,
905 weight_decay: 0.0,
906 adaptive_momentum: false,
907 }
908 }
909}
910
911#[derive(Debug)]
918pub struct HeavyBall {
919 config: HeavyBallConfig,
920 velocity_buffers: HashMap<usize, Tensor>,
921 previous_gradients: HashMap<usize, Tensor>,
922 current_step: usize,
923}
924
925impl HeavyBall {
926 pub fn new(config: HeavyBallConfig) -> Self {
928 Self {
929 config,
930 velocity_buffers: HashMap::new(),
931 previous_gradients: HashMap::new(),
932 current_step: 0,
933 }
934 }
935
936 pub fn with_defaults(learning_rate: f32, beta: f32) -> Self {
938 Self::new(HeavyBallConfig {
939 learning_rate,
940 beta,
941 weight_decay: 0.0,
942 adaptive_momentum: false,
943 })
944 }
945
946 pub fn get_config(&self) -> &HeavyBallConfig {
948 &self.config
949 }
950
951 fn compute_adaptive_momentum(&self, param_id: usize, current_grad: &Tensor) -> Result<f32> {
953 if let Some(prev_grad) = self.previous_gradients.get(¶m_id) {
954 let dot_product = current_grad.mul(prev_grad)?.sum(None, false)?;
956 let norm_current = current_grad.norm_squared()?.sqrt()?;
957 let norm_prev = prev_grad.norm_squared()?.sqrt()?;
958
959 let dot_scalar = dot_product.to_scalar()?;
960 let norm_current_scalar = norm_current.to_scalar()?;
961 let norm_prev_scalar = norm_prev.to_scalar()?;
962
963 let denominator = norm_current_scalar * norm_prev_scalar;
964 if denominator > 1e-8 {
965 let cosine_similarity = dot_scalar / denominator;
966 let adaptive_beta = self.config.beta * cosine_similarity.max(0.0);
968 Ok(adaptive_beta)
969 } else {
970 Ok(self.config.beta)
971 }
972 } else {
973 Ok(self.config.beta)
974 }
975 }
976}
977
978impl OptimizerState for HeavyBall {
979 fn zero_grad(&mut self) -> Result<()> {
980 Ok(())
981 }
982
983 fn step(&mut self, parameters: &mut [Tensor]) -> Result<()> {
984 self.current_step += 1;
985
986 for (param_id, parameter) in parameters.iter_mut().enumerate() {
987 let gradient = match parameter.grad() {
989 Ok(grad) => grad,
990 Err(_) => {
991 continue;
993 },
994 };
995
996 let effective_grad = if self.config.weight_decay > 0.0 {
998 gradient.add(¶meter.mul_scalar(self.config.weight_decay)?)?
999 } else {
1000 gradient
1001 };
1002
1003 let beta = if self.config.adaptive_momentum {
1005 self.compute_adaptive_momentum(param_id, &effective_grad)?
1006 } else {
1007 self.config.beta
1008 };
1009
1010 let velocity = if let Some(v) = self.velocity_buffers.get(¶m_id) {
1012 v.clone()
1013 } else {
1014 Tensor::zeros_like(parameter)?
1015 };
1016
1017 let new_velocity = velocity
1019 .mul_scalar(beta)?
1020 .sub(&effective_grad.mul_scalar(self.config.learning_rate)?)?;
1021
1022 self.velocity_buffers.insert(param_id, new_velocity.clone());
1023
1024 *parameter = parameter.add(&new_velocity)?;
1026
1027 if self.config.adaptive_momentum {
1029 self.previous_gradients.insert(param_id, effective_grad);
1030 }
1031 }
1032
1033 Ok(())
1034 }
1035
1036 fn get_lr(&self) -> f32 {
1037 self.config.learning_rate
1038 }
1039
1040 fn set_lr(&mut self, lr: f32) {
1041 self.config.learning_rate = lr;
1042 }
1043
1044 fn state_dict(&self) -> Result<HashMap<String, Tensor>> {
1045 let mut state = HashMap::new();
1046
1047 state.insert(
1048 "learning_rate".to_string(),
1049 Tensor::scalar(self.config.learning_rate)?,
1050 );
1051 state.insert("beta".to_string(), Tensor::scalar(self.config.beta)?);
1052 state.insert(
1053 "weight_decay".to_string(),
1054 Tensor::scalar(self.config.weight_decay)?,
1055 );
1056 state.insert(
1057 "current_step".to_string(),
1058 Tensor::scalar(self.current_step as f32)?,
1059 );
1060
1061 for (¶m_id, velocity) in &self.velocity_buffers {
1062 state.insert(format!("velocity_{}", param_id), velocity.clone());
1063 }
1064
1065 for (¶m_id, grad) in &self.previous_gradients {
1066 state.insert(format!("prev_grad_{}", param_id), grad.clone());
1067 }
1068
1069 Ok(state)
1070 }
1071
1072 fn load_state_dict(&mut self, state: HashMap<String, Tensor>) -> Result<()> {
1073 if let Some(lr) = state.get("learning_rate") {
1074 self.config.learning_rate = lr.to_scalar()?;
1075 }
1076 if let Some(beta) = state.get("beta") {
1077 self.config.beta = beta.to_scalar()?;
1078 }
1079 if let Some(wd) = state.get("weight_decay") {
1080 self.config.weight_decay = wd.to_scalar()?;
1081 }
1082 if let Some(step) = state.get("current_step") {
1083 self.current_step = step.to_scalar()? as usize;
1084 }
1085
1086 self.velocity_buffers.clear();
1087 self.previous_gradients.clear();
1088
1089 for (key, tensor) in state {
1090 if let Some(param_id_str) = key.strip_prefix("velocity_") {
1091 if let Ok(param_id) = param_id_str.parse::<usize>() {
1092 self.velocity_buffers.insert(param_id, tensor);
1093 }
1094 } else if let Some(param_id_str) = key.strip_prefix("prev_grad_") {
1095 if let Ok(param_id) = param_id_str.parse::<usize>() {
1096 self.previous_gradients.insert(param_id, tensor);
1097 }
1098 }
1099 }
1100
1101 Ok(())
1102 }
1103}
1104
1105#[derive(Debug, Clone, Serialize, Deserialize)]
1107pub struct FISTAConfig {
1108 pub learning_rate: f32,
1110 pub threshold: f32,
1112 pub adaptive_restart: bool,
1114 pub weight_decay: f32,
1116}
1117
1118impl Default for FISTAConfig {
1119 fn default() -> Self {
1120 Self {
1121 learning_rate: 1e-3,
1122 threshold: 1e-4,
1123 adaptive_restart: true,
1124 weight_decay: 0.0,
1125 }
1126 }
1127}
1128
1129#[derive(Debug)]
1134pub struct FISTA {
1135 config: FISTAConfig,
1136 previous_params: HashMap<usize, Tensor>,
1137 current_step: usize,
1138 momentum_coefficient: f32,
1139 previous_momentum: f32,
1140}
1141
1142impl FISTA {
1143 pub fn new(config: FISTAConfig) -> Self {
1145 Self {
1146 config,
1147 previous_params: HashMap::new(),
1148 current_step: 0,
1149 momentum_coefficient: 1.0,
1150 previous_momentum: 1.0,
1151 }
1152 }
1153
1154 pub fn with_defaults(learning_rate: f32, threshold: f32) -> Self {
1156 Self::new(FISTAConfig {
1157 learning_rate,
1158 threshold,
1159 adaptive_restart: true,
1160 weight_decay: 0.0,
1161 })
1162 }
1163
1164 pub fn get_config(&self) -> &FISTAConfig {
1166 &self.config
1167 }
1168
1169 fn soft_threshold(&self, tensor: &Tensor, threshold: f32) -> Result<Tensor> {
1171 let threshold_tensor = Tensor::scalar(threshold)?;
1172 let zero_tensor = Tensor::zeros_like(tensor)?;
1173
1174 let abs_tensor = tensor.abs()?;
1176 let thresholded = abs_tensor.sub(&threshold_tensor)?.max(&zero_tensor)?;
1177 let sign_tensor = tensor.sign()?;
1178
1179 Ok(sign_tensor.mul(&thresholded)?)
1180 }
1181
1182 fn update_momentum_coefficient(&mut self) {
1184 let t = self.current_step as f32;
1185 self.previous_momentum = self.momentum_coefficient;
1186 self.momentum_coefficient = (1.0 + (1.0 + 4.0 * t * t).sqrt()) / 2.0;
1187 }
1188}
1189
1190impl OptimizerState for FISTA {
1191 fn zero_grad(&mut self) -> Result<()> {
1192 Ok(())
1193 }
1194
1195 fn step(&mut self, parameters: &mut [Tensor]) -> Result<()> {
1196 self.current_step += 1;
1197 self.update_momentum_coefficient();
1198
1199 for (param_id, parameter) in parameters.iter_mut().enumerate() {
1200 let gradient = match parameter.grad() {
1202 Ok(grad) => grad,
1203 Err(_) => {
1204 continue;
1206 },
1207 };
1208
1209 let effective_grad = if self.config.weight_decay > 0.0 {
1211 gradient.add(¶meter.mul_scalar(self.config.weight_decay)?)?
1212 } else {
1213 gradient
1214 };
1215
1216 let previous_param = if let Some(prev) = self.previous_params.get(¶m_id) {
1218 prev.clone()
1219 } else {
1220 parameter.clone()
1221 };
1222
1223 let beta = (self.previous_momentum - 1.0) / self.momentum_coefficient;
1225
1226 let extrapolated = parameter.add(&previous_param.sub(parameter)?.mul_scalar(beta)?)?;
1228
1229 let grad_step =
1231 extrapolated.sub(&effective_grad.mul_scalar(self.config.learning_rate)?)?;
1232
1233 let new_parameter = self.soft_threshold(&grad_step, self.config.threshold)?;
1235
1236 self.previous_params.insert(param_id, parameter.clone());
1238
1239 *parameter = new_parameter;
1241 }
1242
1243 Ok(())
1244 }
1245
1246 fn get_lr(&self) -> f32 {
1247 self.config.learning_rate
1248 }
1249
1250 fn set_lr(&mut self, lr: f32) {
1251 self.config.learning_rate = lr;
1252 }
1253
1254 fn state_dict(&self) -> Result<HashMap<String, Tensor>> {
1255 let mut state = HashMap::new();
1256
1257 state.insert(
1258 "learning_rate".to_string(),
1259 Tensor::scalar(self.config.learning_rate)?,
1260 );
1261 state.insert(
1262 "threshold".to_string(),
1263 Tensor::scalar(self.config.threshold)?,
1264 );
1265 state.insert(
1266 "weight_decay".to_string(),
1267 Tensor::scalar(self.config.weight_decay)?,
1268 );
1269 state.insert(
1270 "current_step".to_string(),
1271 Tensor::scalar(self.current_step as f32)?,
1272 );
1273 state.insert(
1274 "momentum_coefficient".to_string(),
1275 Tensor::scalar(self.momentum_coefficient)?,
1276 );
1277 state.insert(
1278 "previous_momentum".to_string(),
1279 Tensor::scalar(self.previous_momentum)?,
1280 );
1281
1282 for (¶m_id, param) in &self.previous_params {
1283 state.insert(format!("prev_param_{}", param_id), param.clone());
1284 }
1285
1286 Ok(state)
1287 }
1288
1289 fn load_state_dict(&mut self, state: HashMap<String, Tensor>) -> Result<()> {
1290 if let Some(lr) = state.get("learning_rate") {
1291 self.config.learning_rate = lr.to_scalar()?;
1292 }
1293 if let Some(threshold) = state.get("threshold") {
1294 self.config.threshold = threshold.to_scalar()?;
1295 }
1296 if let Some(wd) = state.get("weight_decay") {
1297 self.config.weight_decay = wd.to_scalar()?;
1298 }
1299 if let Some(step) = state.get("current_step") {
1300 self.current_step = step.to_scalar()? as usize;
1301 }
1302 if let Some(momentum) = state.get("momentum_coefficient") {
1303 self.momentum_coefficient = momentum.to_scalar()?;
1304 }
1305 if let Some(prev_momentum) = state.get("previous_momentum") {
1306 self.previous_momentum = prev_momentum.to_scalar()?;
1307 }
1308
1309 self.previous_params.clear();
1310 for (key, tensor) in state {
1311 if let Some(param_id_str) = key.strip_prefix("prev_param_") {
1312 if let Ok(param_id) = param_id_str.parse::<usize>() {
1313 self.previous_params.insert(param_id, tensor);
1314 }
1315 }
1316 }
1317
1318 Ok(())
1319 }
1320}
1321
1322#[derive(Debug, Clone, Serialize, Deserialize)]
1324pub struct AdaptiveBatchSizingConfig {
1325 pub initial_batch_size: usize,
1327 pub min_batch_size: usize,
1329 pub max_batch_size: usize,
1331 pub gradient_variance_tolerance: f32,
1333 pub lr_adaptation_factor: f32,
1335 pub variance_window_size: usize,
1337 pub increase_threshold: f32,
1339 pub decrease_threshold: f32,
1341}
1342
1343impl Default for AdaptiveBatchSizingConfig {
1344 fn default() -> Self {
1345 Self {
1346 initial_batch_size: 32,
1347 min_batch_size: 8,
1348 max_batch_size: 512,
1349 gradient_variance_tolerance: 0.1,
1350 lr_adaptation_factor: 0.8,
1351 variance_window_size: 10,
1352 increase_threshold: 0.05,
1353 decrease_threshold: 0.2,
1354 }
1355 }
1356}
1357
1358#[derive(Debug)]
1364pub struct AdaptiveBatchSizing {
1365 config: AdaptiveBatchSizingConfig,
1366 current_batch_size: usize,
1367 gradient_variance_history: Vec<f32>,
1368 loss_history: Vec<f32>,
1369 current_step: usize,
1370 last_adjustment_step: usize,
1371}
1372
1373impl AdaptiveBatchSizing {
1374 pub fn new(config: AdaptiveBatchSizingConfig) -> Self {
1376 let initial_batch_size = config.initial_batch_size;
1377 Self {
1378 config,
1379 current_batch_size: initial_batch_size,
1380 gradient_variance_history: Vec::new(),
1381 loss_history: Vec::new(),
1382 current_step: 0,
1383 last_adjustment_step: 0,
1384 }
1385 }
1386
1387 pub fn with_defaults(
1389 initial_batch_size: usize,
1390 min_batch_size: usize,
1391 max_batch_size: usize,
1392 ) -> Self {
1393 Self::new(AdaptiveBatchSizingConfig {
1394 initial_batch_size,
1395 min_batch_size,
1396 max_batch_size,
1397 ..Default::default()
1398 })
1399 }
1400
1401 pub fn current_batch_size(&self) -> usize {
1403 self.current_batch_size
1404 }
1405
1406 pub fn get_config(&self) -> &AdaptiveBatchSizingConfig {
1408 &self.config
1409 }
1410
1411 pub fn update(&mut self, gradient_variance: f32, current_loss: f32) -> Result<usize> {
1413 self.current_step += 1;
1414
1415 self.gradient_variance_history.push(gradient_variance);
1417 self.loss_history.push(current_loss);
1418
1419 if self.gradient_variance_history.len() > self.config.variance_window_size {
1421 self.gradient_variance_history.remove(0);
1422 }
1423 if self.loss_history.len() > self.config.variance_window_size {
1424 self.loss_history.remove(0);
1425 }
1426
1427 if self.should_adjust_batch_size() {
1429 self.adjust_batch_size()?;
1430 self.last_adjustment_step = self.current_step;
1431 }
1432
1433 Ok(self.current_batch_size)
1434 }
1435
1436 pub fn compute_gradient_variance(&self, gradients: &[Tensor]) -> Result<f32> {
1438 if gradients.is_empty() {
1439 return Ok(0.0);
1440 }
1441
1442 let mut mean_grad = gradients[0].clone();
1444 for grad in gradients.iter().skip(1) {
1445 mean_grad = mean_grad.add(grad)?;
1446 }
1447 mean_grad = mean_grad.div_scalar(gradients.len() as f32)?;
1448
1449 let mut variance_sum = 0.0;
1451 for grad in gradients {
1452 let diff = grad.sub(&mean_grad)?;
1453 let squared_norm = diff.mul(&diff)?.sum(None, false)?;
1454 variance_sum += squared_norm.to_scalar()?;
1455 }
1456
1457 Ok(variance_sum / gradients.len() as f32)
1458 }
1459
1460 fn should_adjust_batch_size(&self) -> bool {
1461 if self.current_step - self.last_adjustment_step < 5 {
1463 return false;
1464 }
1465
1466 self.gradient_variance_history.len() >= 3
1468 }
1469
1470 fn adjust_batch_size(&mut self) -> Result<()> {
1471 let recent_variance = self.recent_average_variance();
1472 let variance_trend = self.variance_trend();
1473 let loss_trend = self.loss_trend();
1474
1475 if recent_variance > self.config.decrease_threshold && variance_trend > 0.0 {
1477 self.increase_batch_size();
1479 } else if recent_variance < self.config.increase_threshold && loss_trend < -0.01 {
1480 self.decrease_batch_size();
1482 }
1483
1484 Ok(())
1485 }
1486
1487 fn recent_average_variance(&self) -> f32 {
1488 if self.gradient_variance_history.is_empty() {
1489 return 0.0;
1490 }
1491
1492 let recent_window = std::cmp::min(5, self.gradient_variance_history.len());
1493 let start_idx = self.gradient_variance_history.len() - recent_window;
1494
1495 self.gradient_variance_history[start_idx..].iter().sum::<f32>() / recent_window as f32
1496 }
1497
1498 fn variance_trend(&self) -> f32 {
1499 if self.gradient_variance_history.len() < 3 {
1500 return 0.0;
1501 }
1502
1503 let len = self.gradient_variance_history.len();
1504 let recent = self.gradient_variance_history[len - 2..].iter().sum::<f32>() / 2.0;
1505 let older = self.gradient_variance_history[len - 4..len - 2].iter().sum::<f32>() / 2.0;
1506
1507 recent - older
1508 }
1509
1510 fn loss_trend(&self) -> f32 {
1511 if self.loss_history.len() < 3 {
1512 return 0.0;
1513 }
1514
1515 let len = self.loss_history.len();
1516 let recent = self.loss_history[len - 2..].iter().sum::<f32>() / 2.0;
1517 let older = self.loss_history[len - 4..len - 2].iter().sum::<f32>() / 2.0;
1518
1519 (recent - older) / older.max(1e-8)
1520 }
1521
1522 fn increase_batch_size(&mut self) {
1523 let new_size = (self.current_batch_size as f32 * 1.5) as usize;
1524 self.current_batch_size = new_size.min(self.config.max_batch_size);
1525 }
1526
1527 fn decrease_batch_size(&mut self) {
1528 let new_size = (self.current_batch_size as f32 * 0.8) as usize;
1529 self.current_batch_size = new_size.max(self.config.min_batch_size);
1530 }
1531
1532 pub fn get_lr_adjustment(&self, original_batch_size: usize) -> f32 {
1534 let ratio = self.current_batch_size as f32 / original_batch_size as f32;
1535 ratio.sqrt() * self.config.lr_adaptation_factor
1536 }
1537
1538 pub fn reset(&mut self) {
1540 self.current_batch_size = self.config.initial_batch_size;
1541 self.gradient_variance_history.clear();
1542 self.loss_history.clear();
1543 self.current_step = 0;
1544 self.last_adjustment_step = 0;
1545 }
1546}
1547
1548#[derive(Debug, Clone, Serialize, Deserialize)]
1550pub struct LossSurfaceSmoothingConfig {
1551 pub smoothing_strength: f32,
1553 pub noise_variance: f32,
1555 pub ema_decay: f32,
1557 pub averaging_window: usize,
1559 pub use_gradient_averaging: bool,
1561 pub use_noise_injection: bool,
1563}
1564
1565impl Default for LossSurfaceSmoothingConfig {
1566 fn default() -> Self {
1567 Self {
1568 smoothing_strength: 0.1,
1569 noise_variance: 1e-4,
1570 ema_decay: 0.9,
1571 averaging_window: 5,
1572 use_gradient_averaging: true,
1573 use_noise_injection: false,
1574 }
1575 }
1576}
1577
1578#[derive(Debug)]
1586pub struct LossSurfaceSmoothing {
1587 config: LossSurfaceSmoothingConfig,
1588 gradient_history: HashMap<usize, Vec<Tensor>>,
1589 ema_gradients: HashMap<usize, Tensor>,
1590 smoothed_parameters: HashMap<usize, Tensor>,
1591 current_step: usize,
1592}
1593
1594impl LossSurfaceSmoothing {
1595 pub fn new(config: LossSurfaceSmoothingConfig) -> Self {
1597 Self {
1598 config,
1599 gradient_history: HashMap::new(),
1600 ema_gradients: HashMap::new(),
1601 smoothed_parameters: HashMap::new(),
1602 current_step: 0,
1603 }
1604 }
1605
1606 pub fn with_defaults(smoothing_strength: f32, use_noise: bool) -> Self {
1608 Self::new(LossSurfaceSmoothingConfig {
1609 smoothing_strength,
1610 use_noise_injection: use_noise,
1611 ..Default::default()
1612 })
1613 }
1614
1615 pub fn get_config(&self) -> &LossSurfaceSmoothingConfig {
1617 &self.config
1618 }
1619
1620 pub fn smooth_gradients(&mut self, parameters: &mut [Tensor]) -> Result<()> {
1622 self.current_step += 1;
1623
1624 for (param_id, parameter) in parameters.iter_mut().enumerate() {
1625 let original_grad = parameter.grad()?;
1626 let mut smoothed_grad = original_grad.clone();
1627
1628 if self.config.use_gradient_averaging {
1630 smoothed_grad = self.apply_gradient_averaging(param_id, &original_grad)?;
1631 }
1632
1633 smoothed_grad = self.apply_ema_smoothing(param_id, &smoothed_grad)?;
1635
1636 if self.config.use_noise_injection {
1638 smoothed_grad = self.apply_noise_injection(&smoothed_grad)?;
1639 }
1640
1641 parameter.set_grad(smoothed_grad)?;
1643 }
1644
1645 Ok(())
1646 }
1647
1648 pub fn smooth_parameters(&mut self, parameters: &mut [Tensor]) -> Result<()> {
1650 for (param_id, parameter) in parameters.iter_mut().enumerate() {
1651 if let Some(smoothed_param) = self.smoothed_parameters.get(¶m_id) {
1652 let new_smoothed = smoothed_param
1654 .mul_scalar(self.config.ema_decay)?
1655 .add(¶meter.mul_scalar(1.0 - self.config.ema_decay)?)?;
1656
1657 *parameter = parameter
1659 .mul_scalar(1.0 - self.config.smoothing_strength)?
1660 .add(&new_smoothed.mul_scalar(self.config.smoothing_strength)?)?;
1661
1662 self.smoothed_parameters.insert(param_id, new_smoothed);
1663 } else {
1664 self.smoothed_parameters.insert(param_id, parameter.clone());
1666 }
1667 }
1668
1669 Ok(())
1670 }
1671
1672 fn apply_gradient_averaging(&mut self, param_id: usize, gradient: &Tensor) -> Result<Tensor> {
1673 let history = self.gradient_history.entry(param_id).or_default();
1674
1675 history.push(gradient.clone());
1676 if history.len() > self.config.averaging_window {
1677 history.remove(0);
1678 }
1679
1680 if history.len() == 1 {
1682 Ok(gradient.clone())
1683 } else {
1684 let mut sum = history[0].clone();
1685 for grad in history.iter().skip(1) {
1686 sum = sum.add(grad)?;
1687 }
1688 Ok(sum.div_scalar(history.len() as f32)?)
1689 }
1690 }
1691
1692 fn apply_ema_smoothing(&mut self, param_id: usize, gradient: &Tensor) -> Result<Tensor> {
1693 if let Some(ema_grad) = self.ema_gradients.get(¶m_id) {
1694 let new_ema = ema_grad
1695 .mul_scalar(self.config.ema_decay)?
1696 .add(&gradient.mul_scalar(1.0 - self.config.ema_decay)?)?;
1697 self.ema_gradients.insert(param_id, new_ema.clone());
1698 Ok(new_ema)
1699 } else {
1700 self.ema_gradients.insert(param_id, gradient.clone());
1701 Ok(gradient.clone())
1702 }
1703 }
1704
1705 fn apply_noise_injection(&self, gradient: &Tensor) -> Result<Tensor> {
1706 let noise = Tensor::randn_like(gradient)
1707 .map_err(|e| anyhow!("Failed to create noise tensor: {}", e))?
1708 .mul_scalar(self.config.noise_variance.sqrt())
1709 .map_err(|e| anyhow!("Failed to scale noise tensor: {}", e))?;
1710 gradient
1711 .add(&noise)
1712 .map_err(|e| anyhow!("Failed to add noise to gradient: {}", e))
1713 }
1714
1715 pub fn reset(&mut self) {
1717 self.gradient_history.clear();
1718 self.ema_gradients.clear();
1719 self.smoothed_parameters.clear();
1720 self.current_step = 0;
1721 }
1722
1723 pub fn get_statistics(&self) -> HashMap<String, f32> {
1725 let mut stats = HashMap::new();
1726 stats.insert("current_step".to_string(), self.current_step as f32);
1727 stats.insert(
1728 "num_tracked_params".to_string(),
1729 self.gradient_history.len() as f32,
1730 );
1731 stats.insert(
1732 "smoothing_strength".to_string(),
1733 self.config.smoothing_strength,
1734 );
1735 stats.insert("ema_decay".to_string(), self.config.ema_decay);
1736 stats
1737 }
1738}
1739
1740#[cfg(test)]
1741mod tests {
1742 use super::*;
1743
1744 #[test]
1745 fn test_qhm_config_default() {
1746 let config = QHMConfig::default();
1747 assert_eq!(config.learning_rate, 1e-3);
1748 assert_eq!(config.momentum, 0.9);
1749 assert_eq!(config.nu, 0.7);
1750 assert_eq!(config.weight_decay, 0.0);
1751 }
1752
1753 #[test]
1754 fn test_aggmo_config_default() {
1755 let config = AggMoConfig::default();
1756 assert_eq!(config.learning_rate, 1e-3);
1757 assert_eq!(config.momentum_coefficients, vec![0.0, 0.9, 0.99]);
1758 assert_eq!(config.weight_decay, 0.0);
1759 }
1760
1761 #[test]
1762 fn test_qhm_creation() {
1763 let optimizer = QHM::with_defaults(1e-3, 0.9, 0.7);
1764 assert_eq!(optimizer.get_lr(), 1e-3);
1765 assert_eq!(optimizer.current_step, 0);
1766 }
1767
1768 #[test]
1769 fn test_aggmo_creation() {
1770 let optimizer = AggMo::with_defaults(1e-3, vec![0.0, 0.9, 0.99]);
1771 assert_eq!(optimizer.get_lr(), 1e-3);
1772 assert_eq!(optimizer.num_momentum_buffers(), 3);
1773 }
1774
1775 #[test]
1776 fn test_variance_reduction_svrg() {
1777 let optimizer = VarianceReduction::svrg(1e-3, 50, 10);
1778 assert_eq!(optimizer.get_lr(), 1e-3);
1779 assert_eq!(optimizer.current_step, 0);
1780 }
1781
1782 #[test]
1783 fn test_variance_reduction_sag() {
1784 let optimizer = VarianceReduction::sag(1e-3, 100);
1785 assert_eq!(optimizer.get_lr(), 1e-3);
1786 assert!(matches!(
1787 optimizer.config.method,
1788 VarianceReductionMethod::SAG
1789 ));
1790 }
1791
1792 #[test]
1793 fn test_nesterov_accelerated_gradient_config() {
1794 let config = NesterovAcceleratedGradientConfig::default();
1795 assert_eq!(config.learning_rate, 1e-3);
1796 assert_eq!(config.momentum, 0.9);
1797 assert_eq!(config.weight_decay, 0.0);
1798 assert!(!config.restart_on_increase);
1799 }
1800
1801 #[test]
1802 fn test_nesterov_accelerated_gradient_creation() {
1803 let optimizer = NesterovAcceleratedGradient::with_defaults(1e-3, 0.9);
1804 assert_eq!(optimizer.get_lr(), 1e-3);
1805 assert_eq!(optimizer.current_step, 0);
1806 assert!(optimizer.previous_loss.is_none());
1807 }
1808
1809 #[test]
1810 fn test_nesterov_restart_on_increase() {
1811 let mut optimizer = NesterovAcceleratedGradient::new(NesterovAcceleratedGradientConfig {
1812 learning_rate: 1e-3,
1813 momentum: 0.9,
1814 weight_decay: 0.0,
1815 restart_on_increase: true,
1816 });
1817
1818 optimizer.set_current_loss(1.0);
1820 assert_eq!(optimizer.previous_loss, Some(1.0));
1821
1822 optimizer.set_current_loss(1.5);
1824 assert_eq!(optimizer.previous_loss, Some(1.5));
1825 }
1826
1827 #[test]
1828 fn test_heavy_ball_config() {
1829 let config = HeavyBallConfig::default();
1830 assert_eq!(config.learning_rate, 1e-3);
1831 assert_eq!(config.beta, 0.9);
1832 assert_eq!(config.weight_decay, 0.0);
1833 assert!(!config.adaptive_momentum);
1834 }
1835
1836 #[test]
1837 fn test_heavy_ball_creation() {
1838 let optimizer = HeavyBall::with_defaults(1e-3, 0.9);
1839 assert_eq!(optimizer.get_lr(), 1e-3);
1840 assert_eq!(optimizer.current_step, 0);
1841 assert_eq!(optimizer.get_config().beta, 0.9);
1842 }
1843
1844 #[test]
1845 fn test_heavy_ball_adaptive_momentum() {
1846 let optimizer = HeavyBall::new(HeavyBallConfig {
1847 learning_rate: 1e-3,
1848 beta: 0.9,
1849 weight_decay: 0.0,
1850 adaptive_momentum: true,
1851 });
1852
1853 assert!(optimizer.config.adaptive_momentum);
1854 }
1855
1856 #[test]
1857 fn test_fista_config() {
1858 let config = FISTAConfig::default();
1859 assert_eq!(config.learning_rate, 1e-3);
1860 assert_eq!(config.threshold, 1e-4);
1861 assert!(config.adaptive_restart);
1862 assert_eq!(config.weight_decay, 0.0);
1863 }
1864
1865 #[test]
1866 fn test_fista_creation() {
1867 let optimizer = FISTA::with_defaults(1e-3, 1e-4);
1868 assert_eq!(optimizer.get_lr(), 1e-3);
1869 assert_eq!(optimizer.current_step, 0);
1870 assert_eq!(optimizer.momentum_coefficient, 1.0);
1871 assert_eq!(optimizer.previous_momentum, 1.0);
1872 }
1873
1874 #[test]
1875 fn test_fista_momentum_update() {
1876 let mut optimizer = FISTA::with_defaults(1e-3, 1e-4);
1877
1878 optimizer.current_step = 1;
1880 optimizer.update_momentum_coefficient();
1881 assert!(optimizer.momentum_coefficient > 1.0);
1882 assert_eq!(optimizer.previous_momentum, 1.0);
1883
1884 let prev_momentum = optimizer.momentum_coefficient;
1885 optimizer.current_step = 2;
1886 optimizer.update_momentum_coefficient();
1887 assert!(optimizer.momentum_coefficient > prev_momentum);
1888 }
1889
1890 #[test]
1891 fn test_adaptive_batch_sizing_config() {
1892 let config = AdaptiveBatchSizingConfig::default();
1893 assert_eq!(config.initial_batch_size, 32);
1894 assert_eq!(config.min_batch_size, 8);
1895 assert_eq!(config.max_batch_size, 512);
1896 assert_eq!(config.gradient_variance_tolerance, 0.1);
1897 assert_eq!(config.lr_adaptation_factor, 0.8);
1898 assert_eq!(config.variance_window_size, 10);
1899 assert_eq!(config.increase_threshold, 0.05);
1900 assert_eq!(config.decrease_threshold, 0.2);
1901 }
1902
1903 #[test]
1904 fn test_adaptive_batch_sizing_creation() {
1905 let abs = AdaptiveBatchSizing::with_defaults(64, 16, 256);
1906 assert_eq!(abs.current_batch_size(), 64);
1907 assert_eq!(abs.get_config().min_batch_size, 16);
1908 assert_eq!(abs.get_config().max_batch_size, 256);
1909 }
1910
1911 #[test]
1912 fn test_adaptive_batch_sizing_lr_adjustment() {
1913 let abs = AdaptiveBatchSizing::with_defaults(64, 16, 256);
1914 let lr_adj = abs.get_lr_adjustment(32);
1915 assert!(lr_adj > 0.0);
1916 assert!(lr_adj < 2.0);
1917 }
1918
1919 #[test]
1920 fn test_adaptive_batch_sizing_reset() {
1921 let mut abs = AdaptiveBatchSizing::with_defaults(64, 16, 256);
1922 abs.current_step = 10;
1923 abs.reset();
1924 assert_eq!(abs.current_step, 0);
1925 assert_eq!(abs.current_batch_size(), 64);
1926 }
1927
1928 #[test]
1929 fn test_loss_surface_smoothing_config() {
1930 let config = LossSurfaceSmoothingConfig::default();
1931 assert_eq!(config.smoothing_strength, 0.1);
1932 assert_eq!(config.noise_variance, 1e-4);
1933 assert_eq!(config.ema_decay, 0.9);
1934 assert_eq!(config.averaging_window, 5);
1935 assert!(config.use_gradient_averaging);
1936 assert!(!config.use_noise_injection);
1937 }
1938
1939 #[test]
1940 fn test_loss_surface_smoothing_creation() {
1941 let lss = LossSurfaceSmoothing::with_defaults(0.2, true);
1942 assert_eq!(lss.get_config().smoothing_strength, 0.2);
1943 assert!(lss.get_config().use_noise_injection);
1944 assert_eq!(lss.current_step, 0);
1945 }
1946
1947 #[test]
1948 fn test_loss_surface_smoothing_statistics() {
1949 let lss = LossSurfaceSmoothing::with_defaults(0.1, false);
1950 let stats = lss.get_statistics();
1951 assert_eq!(stats.get("current_step"), Some(&0.0));
1952 assert_eq!(stats.get("num_tracked_params"), Some(&0.0));
1953 assert_eq!(stats.get("smoothing_strength"), Some(&0.1));
1954 assert_eq!(stats.get("ema_decay"), Some(&0.9));
1955 }
1956
1957 #[test]
1958 fn test_loss_surface_smoothing_reset() {
1959 let mut lss = LossSurfaceSmoothing::with_defaults(0.1, false);
1960 lss.current_step = 5;
1961 lss.reset();
1962 assert_eq!(lss.current_step, 0);
1963 }
1964}