1use std::fmt::Debug;
2pub mod architecture;
10pub mod strategies;
11pub mod training;
12
13pub use architecture::{
15 ActivationFunction, AttentionOptimization, FeedForwardNetwork, InputEmbedding, LayerNorm,
16 MultiHeadAttention, OutputProjectionLayer, PositionalEncoder, PositionalEncodingType,
17 TransformerLayer,
18};
19
20pub use strategies::{
21 GradientProcessingStrategy, GradientProcessor, LearningRateAdaptationStrategy,
22 LearningRateAdapter, MomentumIntegrator, MomentumStrategy, RegularizationStrategy,
23 TransformerRegularizer,
24};
25
26pub use training::{
27 CurriculumLearner, CurriculumStrategy, EvaluationStrategy, MetaLearningStrategy,
28 TransformerEvaluator, TransformerMetaLearner,
29};
30
31use scirs2_core::ndarray::{Array1, Array2};
32use scirs2_core::numeric::Float;
33use std::collections::{HashMap, VecDeque};
34
35use super::LearnedOptimizerConfig;
36use crate::error::{OptimError, Result};
37
38#[derive(Debug, Clone)]
40pub struct TransformerOptimizerConfig {
41 pub base_config: LearnedOptimizerConfig,
43
44 pub modeldim: usize,
46
47 pub numheads: usize,
49
50 pub ff_dim: usize,
52
53 pub num_layers: usize,
55
56 pub max_sequence_length: usize,
59
60 pub attention_dropout: f64,
62
63 pub ff_dropout: f64,
65
66 pub layer_norm_eps: f64,
68
69 pub pre_layer_norm: bool,
71
72 pub pos_encoding_type: PositionalEncodingType,
74
75 pub relative_position_bias: bool,
77
78 pub use_rope: bool,
80
81 pub causal_attention: bool,
83
84 pub activation: ActivationFunction,
86
87 pub dropout_seed: u64,
89
90 pub update_modulation: f64,
96
97 pub gradient_checkpointing: bool,
99
100 pub attention_optimization: AttentionOptimization,
102
103 pub multi_scale_attention: bool,
105
106 pub cross_attention: bool,
108
109 pub memory_efficient: bool,
111}
112
113impl Default for TransformerOptimizerConfig {
114 fn default() -> Self {
115 Self {
116 base_config: LearnedOptimizerConfig::default(),
117 modeldim: 32,
118 numheads: 4,
119 ff_dim: 64,
120 num_layers: 2,
121 max_sequence_length: 32,
122 attention_dropout: 0.1,
123 ff_dropout: 0.1,
124 layer_norm_eps: 1e-6,
125 pre_layer_norm: true,
126 pos_encoding_type: PositionalEncodingType::Sinusoidal,
127 relative_position_bias: false,
128 use_rope: false,
129 causal_attention: true,
130 activation: ActivationFunction::GELU,
131 dropout_seed: 0x0D15_EA5E,
132 update_modulation: 0.5,
133 gradient_checkpointing: false,
134 attention_optimization: AttentionOptimization::Full,
135 multi_scale_attention: false,
136 cross_attention: false,
137 memory_efficient: false,
138 }
139 }
140}
141
142impl TransformerOptimizerConfig {
143 pub fn validate(&self) -> Result<()> {
146 if self.modeldim == 0 {
147 return Err(OptimError::InvalidConfig(
148 "modeldim must be positive".to_string(),
149 ));
150 }
151 if self.numheads == 0 {
152 return Err(OptimError::InvalidConfig(
153 "numheads must be positive".to_string(),
154 ));
155 }
156 if !self.modeldim.is_multiple_of(self.numheads) {
157 return Err(OptimError::InvalidConfig(format!(
158 "modeldim ({}) must be divisible by numheads ({})",
159 self.modeldim, self.numheads
160 )));
161 }
162 if self.ff_dim == 0 {
163 return Err(OptimError::InvalidConfig(
164 "ff_dim must be positive".to_string(),
165 ));
166 }
167 if self.num_layers == 0 {
168 return Err(OptimError::InvalidConfig(
169 "num_layers must be positive".to_string(),
170 ));
171 }
172 if self.max_sequence_length == 0 {
173 return Err(OptimError::InvalidConfig(
174 "max_sequence_length must be positive".to_string(),
175 ));
176 }
177 if !(0.0..1.0).contains(&self.attention_dropout) {
178 return Err(OptimError::InvalidConfig(format!(
179 "attention_dropout must lie in [0, 1), got {}",
180 self.attention_dropout
181 )));
182 }
183 if !(0.0..1.0).contains(&self.ff_dropout) {
184 return Err(OptimError::InvalidConfig(format!(
185 "ff_dropout must lie in [0, 1), got {}",
186 self.ff_dropout
187 )));
188 }
189 if self.layer_norm_eps <= 0.0 {
190 return Err(OptimError::InvalidConfig(format!(
191 "layer_norm_eps must be positive, got {}",
192 self.layer_norm_eps
193 )));
194 }
195 if !(0.0..1.0).contains(&self.update_modulation) {
196 return Err(OptimError::InvalidConfig(format!(
197 "update_modulation must lie in [0, 1), got {}",
198 self.update_modulation
199 )));
200 }
201 if self.activation.is_gated() && !self.ff_dim.is_multiple_of(2) {
202 return Err(OptimError::InvalidConfig(format!(
203 "gated activation {:?} requires an even ff_dim, got {}",
204 self.activation, self.ff_dim
205 )));
206 }
207 let head_dim = self.modeldim / self.numheads;
208 if (self.use_rope || self.pos_encoding_type == PositionalEncodingType::Rotary)
209 && !head_dim.is_multiple_of(2)
210 {
211 return Err(OptimError::InvalidConfig(format!(
212 "rotary embeddings require an even head dimension, got {head_dim}"
213 )));
214 }
215
216 Ok(())
217 }
218}
219
220#[derive(Debug, Clone)]
222pub struct TransformerNetwork<
223 T: Float
224 + Debug
225 + Default
226 + Clone
227 + std::iter::Sum
228 + scirs2_core::ndarray::ScalarOperand
229 + Send
230 + Sync
231 + 'static,
232> {
233 input_embedding: InputEmbedding<T>,
235
236 layers: Vec<TransformerLayer<T>>,
238
239 output_projection: OutputProjectionLayer<T>,
241
242 output_layer_norm: LayerNorm<T>,
244
245 position_encoder: PositionalEncoder<T>,
247
248 config: TransformerOptimizerConfig,
250}
251
252#[derive(Debug)]
254pub struct TransformerOptimizer<
255 T: Float
256 + Debug
257 + Default
258 + Clone
259 + std::iter::Sum
260 + scirs2_core::ndarray::ScalarOperand
261 + Send
262 + Sync
263 + 'static,
264> {
265 config: TransformerOptimizerConfig,
267
268 transformer_network: TransformerNetwork<T>,
270
271 gradient_processor: GradientProcessor<T>,
273
274 lr_adapter: LearningRateAdapter<T>,
276
277 momentum_integrator: MomentumIntegrator<T>,
279
280 regularizer: TransformerRegularizer<T>,
282
283 meta_learner: TransformerMetaLearner<T>,
285
286 curriculum_learner: CurriculumLearner<T>,
288
289 evaluator: TransformerEvaluator<T>,
291
292 sequence_buffer: SequenceBuffer<T>,
294
295 metrics: TransformerOptimizerMetrics,
297
298 step_count: usize,
300}
301
302#[derive(Debug, Clone)]
304pub struct SequenceBuffer<
305 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
306> {
307 gradient_sequences: HashMap<String, VecDeque<Array1<T>>>,
309
310 parameter_sequences: HashMap<String, VecDeque<Array1<T>>>,
312
313 loss_sequences: VecDeque<T>,
315
316 lr_sequences: VecDeque<T>,
318
319 capacity: usize,
321}
322
323#[derive(Debug, Clone)]
325pub struct TransformerOptimizerMetrics {
326 total_steps: usize,
328
329 convergence_history: Vec<f64>,
331
332 attention_stats: HashMap<String, f64>,
334
335 strategy_stats: HashMap<String, f64>,
337
338 performance_comparisons: HashMap<String, f64>,
340}
341
342impl<
343 T: Float
344 + Debug
345 + Default
346 + Clone
347 + std::iter::Sum
348 + scirs2_core::ndarray::ScalarOperand
349 + Send
350 + Sync
351 + 'static,
352 > TransformerNetwork<T>
353{
354 pub fn new(config: &TransformerOptimizerConfig) -> Result<Self> {
356 config.validate()?;
357
358 let input_embedding = InputEmbedding::new(config.modeldim, config.modeldim)?;
359
360 let mut layers = Vec::with_capacity(config.num_layers);
361 let mut rng = scirs2_core::random::thread_rng();
362 for _ in 0..config.num_layers {
363 layers.push(TransformerLayer::new(config, &mut rng)?);
364 }
365
366 let output_projection = OutputProjectionLayer::new(config.modeldim, config.modeldim)?;
367 let output_layer_norm = LayerNorm::new(config.modeldim, config.layer_norm_eps);
368 let position_encoder = PositionalEncoder::new(config)?;
369
370 Ok(Self {
371 input_embedding,
372 layers,
373 output_projection,
374 output_layer_norm,
375 position_encoder,
376 config: config.clone(),
377 })
378 }
379
380 pub fn forward(&mut self, input: &Array2<T>) -> Result<Array2<T>> {
382 let mut x = self.input_embedding.forward(input)?;
384
385 x = self.position_encoder.encode(&x)?;
387
388 for layer in &mut self.layers {
390 x = layer.forward(&x)?;
391 }
392
393 x = self.output_layer_norm.forward(&x)?;
395
396 let output = self.output_projection.forward(&x)?;
398
399 Ok(output)
400 }
401
402 pub fn get_attention_patterns(&self) -> Vec<Option<&scirs2_core::ndarray::Array3<T>>> {
404 self.layers
405 .iter()
406 .map(|layer| layer.get_attention_patterns())
407 .collect()
408 }
409
410 pub fn set_training(&mut self, training: bool) {
412 for layer in &mut self.layers {
413 layer.set_training(training);
414 }
415 }
416
417 pub fn num_layers(&self) -> usize {
419 self.layers.len()
420 }
421
422 pub fn config(&self) -> &TransformerOptimizerConfig {
424 &self.config
425 }
426}
427
428impl<
429 T: Float
430 + Debug
431 + Default
432 + Clone
433 + std::iter::Sum
434 + scirs2_core::ndarray::ScalarOperand
435 + Send
436 + Sync
437 + 'static,
438 > TransformerOptimizer<T>
439{
440 pub fn new(config: TransformerOptimizerConfig) -> Result<Self> {
442 config.validate()?;
444
445 let transformer_network = TransformerNetwork::new(&config)?;
446 let gradient_processor = GradientProcessor::new(GradientProcessingStrategy::Adaptive);
447 let base_lr: T = scirs2_core::numeric::NumCast::from(config.base_config.learning_rate)
448 .unwrap_or_else(|| T::zero());
449 let lr_adapter = LearningRateAdapter::new(
450 LearningRateAdaptationStrategy::TransformerPredicted,
451 base_lr,
452 );
453 let momentum_integrator = MomentumIntegrator::new(MomentumStrategy::Adam);
454 let regularizer = TransformerRegularizer::new(RegularizationStrategy::Adaptive);
455 let meta_learner = TransformerMetaLearner::new(MetaLearningStrategy::GradientBased)?;
456 let curriculum_learner = CurriculumLearner::new(CurriculumStrategy::Adaptive)?;
457 let evaluator = TransformerEvaluator::new(EvaluationStrategy::Comprehensive)?;
458 let sequence_buffer = SequenceBuffer::new(config.max_sequence_length.max(1));
459 let metrics = TransformerOptimizerMetrics::new();
460
461 Ok(Self {
462 config,
463 transformer_network,
464 gradient_processor,
465 lr_adapter,
466 momentum_integrator,
467 regularizer,
468 meta_learner,
469 curriculum_learner,
470 evaluator,
471 sequence_buffer,
472 metrics,
473 step_count: 0,
474 })
475 }
476
477 pub fn step(
494 &mut self,
495 parameters: &mut HashMap<String, Array2<T>>,
496 gradients: &mut HashMap<String, Array2<T>>,
497 loss: T,
498 ) -> Result<T> {
499 self.step_count += 1;
500
501 let mut names: Vec<String> = gradients.keys().cloned().collect();
503 names.sort();
504
505 for name in &names {
506 let Some(gradient) = gradients.get(name) else {
507 continue;
508 };
509 let Some(param_matrix) = parameters.get(name) else {
510 return Err(OptimError::InvalidConfig(format!(
511 "Gradient '{name}' has no matching parameter"
512 )));
513 };
514 if param_matrix.dim() != gradient.dim() {
515 return Err(OptimError::InvalidConfig(format!(
516 "Parameter '{}' has shape {:?} but its gradient has shape {:?}",
517 name,
518 param_matrix.dim(),
519 gradient.dim()
520 )));
521 }
522
523 let shape = param_matrix.dim();
524 let param_matrix = param_matrix.clone();
525 let gradient = gradient.clone();
526
527 let mut param_map = HashMap::new();
530 param_map.insert(name.clone(), param_matrix.clone());
531 let mut grad_map = HashMap::new();
532 grad_map.insert(name.clone(), gradient);
533
534 let _reg_loss =
535 self.regularizer
536 .apply_regularization(&mut param_map, &mut grad_map, None)?;
537
538 let regularized = grad_map.remove(name).ok_or_else(|| {
539 OptimError::ComputationError(format!(
540 "Regularizer dropped the gradient for '{name}'"
541 ))
542 })?;
543 let param_matrix = param_map.remove(name).unwrap_or(param_matrix);
545
546 let flat_gradient = Array1::from_iter(regularized.iter().cloned());
547 let flat_param = Array1::from_iter(param_matrix.iter().cloned());
548
549 let processed_gradient = self
551 .gradient_processor
552 .process_gradients(name, &flat_gradient)?;
553
554 let current_lr = self
556 .lr_adapter
557 .update_learning_rate(Some(loss), Some(&processed_gradient))?;
558
559 let momentum_gradient =
561 self.momentum_integrator
562 .integrate_momentum(name, &processed_gradient, None)?;
563
564 self.sequence_buffer
566 .add_gradient(name, momentum_gradient.clone());
567 self.sequence_buffer
568 .add_parameters(name, flat_param.clone());
569
570 let modulation = self.transformer_modulation(name, flat_param.len())?;
572
573 let mut updated = Vec::with_capacity(flat_param.len());
575 for i in 0..flat_param.len() {
576 updated.push(flat_param[i] - current_lr * momentum_gradient[i] * modulation[i]);
577 }
578 let updated = Array2::from_shape_vec(shape, updated).map_err(|e| {
579 OptimError::ComputationError(format!(
580 "Failed to reshape the update for '{name}': {e}"
581 ))
582 })?;
583
584 parameters.insert(name.clone(), updated);
585 gradients.insert(name.clone(), regularized);
586 }
587
588 self.sequence_buffer.add_loss(loss);
590 self.sequence_buffer
591 .add_learning_rate(self.lr_adapter.current_learning_rate());
592
593 self.curriculum_learner
595 .update_curriculum("current_task", loss, self.step_count)?;
596
597 self.metrics
599 .update_step(loss.to_f64().unwrap_or(0.0), self.step_count);
600
601 Ok(loss)
602 }
603
604 fn build_feature_sequence(&self, param_name: &str) -> Array2<T> {
612 let modeldim = self.config.modeldim;
613 let history = self.sequence_buffer.recent_gradients(param_name);
614
615 if history.is_empty() {
616 return Array2::zeros((1, modeldim));
617 }
618
619 let seq_len = history.len().min(self.config.max_sequence_length).max(1);
620 let start = history.len() - seq_len;
621 let mut features = Array2::zeros((seq_len, modeldim));
622
623 for (row, gradient) in history[start..].iter().enumerate() {
624 let pooled = Self::pool_to_width(gradient, modeldim);
625 for j in 0..modeldim {
626 features[[row, j]] = pooled[j];
627 }
628 }
629
630 features
631 }
632
633 fn pool_to_width(values: &Array1<T>, width: usize) -> Array1<T> {
635 let mut sums = Array1::zeros(width);
636 if width == 0 {
637 return sums;
638 }
639 let mut counts = vec![0usize; width];
640
641 for (i, &value) in values.iter().enumerate() {
642 let bucket = i % width;
643 sums[bucket] = sums[bucket] + value;
644 counts[bucket] += 1;
645 }
646
647 for (j, &count) in counts.iter().enumerate() {
648 if count > 0 {
649 let denominator: T =
650 scirs2_core::numeric::NumCast::from(count as f64).unwrap_or_else(|| T::one());
651 sums[j] = sums[j] / denominator;
652 }
653 }
654
655 sums
656 }
657
658 fn transformer_modulation(&mut self, param_name: &str, target_len: usize) -> Result<Array1<T>> {
661 let features = self.build_feature_sequence(param_name);
662 let output = self.transformer_network.forward(&features)?;
663
664 let rows = output.nrows();
665 if rows == 0 || output.ncols() == 0 {
666 return Ok(Array1::from_elem(target_len, T::one()));
667 }
668
669 let code = output.row(rows - 1);
671 let width = code.len();
672 let amplitude: T = scirs2_core::numeric::NumCast::from(self.config.update_modulation)
673 .unwrap_or_else(|| T::zero());
674
675 let mut modulation = Array1::from_elem(target_len, T::one());
676 for i in 0..target_len {
677 modulation[i] = T::one() + amplitude * code[i % width].tanh();
678 }
679
680 Ok(modulation)
681 }
682
683 pub fn get_statistics(&self) -> HashMap<String, f64> {
685 let mut stats = HashMap::new();
686
687 stats.insert("step_count".to_string(), self.step_count as f64);
688 stats.insert(
689 "current_lr".to_string(),
690 self.lr_adapter
691 .current_learning_rate()
692 .to_f64()
693 .unwrap_or(0.0),
694 );
695
696 let grad_stats = self.gradient_processor.statistics();
698 stats.insert(
699 "mean_gradient_magnitude".to_string(),
700 grad_stats.mean_magnitude().to_f64().unwrap_or(0.0),
701 );
702 stats.insert(
703 "gradient_sparsity".to_string(),
704 grad_stats.sparsity().to_f64().unwrap_or(0.0),
705 );
706
707 let momentum_stats = self.momentum_integrator.statistics();
709 stats.insert(
710 "momentum_magnitude".to_string(),
711 momentum_stats
712 .avg_momentum_magnitude
713 .to_f64()
714 .unwrap_or(0.0),
715 );
716 stats.insert(
717 "momentum_direction_consistency".to_string(),
718 momentum_stats.direction_consistency.to_f64().unwrap_or(0.0),
719 );
720
721 let curriculum_stats = self.curriculum_learner.get_curriculum_statistics();
723 for (key, value) in curriculum_stats {
724 stats.insert(format!("curriculum_{}", key), value.to_f64().unwrap_or(0.0));
725 }
726
727 stats
728 }
729
730 pub fn set_training(&mut self, training: bool) {
733 self.transformer_network.set_training(training);
734 }
735
736 pub fn set_learning_rate(&mut self, lr: T) {
738 self.lr_adapter.set_base_learning_rate(lr);
739 }
740
741 pub fn current_learning_rate(&self) -> T {
743 self.lr_adapter.current_learning_rate()
744 }
745
746 pub fn step_count(&self) -> usize {
748 self.step_count
749 }
750
751 pub fn config(&self) -> &TransformerOptimizerConfig {
753 &self.config
754 }
755
756 pub fn lr_adapter_mut(&mut self) -> &mut LearningRateAdapter<T> {
758 &mut self.lr_adapter
759 }
760
761 pub fn gradient_processor_mut(&mut self) -> &mut GradientProcessor<T> {
763 &mut self.gradient_processor
764 }
765
766 pub fn momentum_integrator_mut(&mut self) -> &mut MomentumIntegrator<T> {
768 &mut self.momentum_integrator
769 }
770
771 pub fn regularizer_mut(&mut self) -> &mut TransformerRegularizer<T> {
773 &mut self.regularizer
774 }
775
776 pub fn meta_learner_mut(&mut self) -> &mut TransformerMetaLearner<T> {
778 &mut self.meta_learner
779 }
780
781 pub fn evaluator_mut(&mut self) -> &mut TransformerEvaluator<T> {
783 &mut self.evaluator
784 }
785
786 pub fn reset(&mut self) -> Result<()> {
788 self.step_count = 0;
789 self.gradient_processor.reset();
790 self.lr_adapter.reset();
791 self.momentum_integrator.reset();
792 self.regularizer.reset();
793 self.meta_learner.reset();
794 self.curriculum_learner.reset();
795 self.evaluator.reset();
796 self.sequence_buffer.clear();
797 self.metrics = TransformerOptimizerMetrics::new();
798
799 Ok(())
800 }
801}
802
803impl<
804 T: Float
805 + Debug
806 + Default
807 + Clone
808 + scirs2_core::ndarray::ScalarOperand
809 + Send
810 + Sync
811 + 'static,
812 > SequenceBuffer<T>
813{
814 pub fn new(capacity: usize) -> Self {
816 Self {
817 gradient_sequences: HashMap::new(),
818 parameter_sequences: HashMap::new(),
819 loss_sequences: VecDeque::new(),
820 lr_sequences: VecDeque::new(),
821 capacity: capacity.max(1),
822 }
823 }
824
825 fn push_bounded(queue: &mut VecDeque<Array1<T>>, value: Array1<T>, capacity: usize) {
826 queue.push_back(value);
827 while queue.len() > capacity {
828 queue.pop_front();
829 }
830 }
831
832 pub fn add_gradient(&mut self, param_name: &str, gradient: Array1<T>) {
834 let capacity = self.capacity;
835 let queue = self
836 .gradient_sequences
837 .entry(param_name.to_string())
838 .or_default();
839 Self::push_bounded(queue, gradient, capacity);
840 }
841
842 pub fn add_parameters(&mut self, param_name: &str, parameters: Array1<T>) {
844 let capacity = self.capacity;
845 let queue = self
846 .parameter_sequences
847 .entry(param_name.to_string())
848 .or_default();
849 Self::push_bounded(queue, parameters, capacity);
850 }
851
852 pub fn add_loss(&mut self, loss: T) {
854 self.loss_sequences.push_back(loss);
855 while self.loss_sequences.len() > self.capacity {
856 self.loss_sequences.pop_front();
857 }
858 }
859
860 pub fn add_learning_rate(&mut self, lr: T) {
862 self.lr_sequences.push_back(lr);
863 while self.lr_sequences.len() > self.capacity {
864 self.lr_sequences.pop_front();
865 }
866 }
867
868 pub fn clear(&mut self) {
870 self.gradient_sequences.clear();
871 self.parameter_sequences.clear();
872 self.loss_sequences.clear();
873 self.lr_sequences.clear();
874 }
875
876 pub fn recent_gradients(&self, param_name: &str) -> Vec<&Array1<T>> {
878 match self.gradient_sequences.get(param_name) {
879 Some(queue) => queue.iter().collect(),
880 None => Vec::new(),
881 }
882 }
883
884 pub fn recent_parameters(&self, param_name: &str) -> Vec<&Array1<T>> {
886 match self.parameter_sequences.get(param_name) {
887 Some(queue) => queue.iter().collect(),
888 None => Vec::new(),
889 }
890 }
891
892 pub fn losses(&self) -> &VecDeque<T> {
894 &self.loss_sequences
895 }
896
897 pub fn learning_rates(&self) -> &VecDeque<T> {
899 &self.lr_sequences
900 }
901
902 pub fn capacity(&self) -> usize {
904 self.capacity
905 }
906}
907
908impl Default for TransformerOptimizerMetrics {
909 fn default() -> Self {
910 Self::new()
911 }
912}
913
914impl TransformerOptimizerMetrics {
915 pub fn new() -> Self {
917 Self {
918 total_steps: 0,
919 convergence_history: Vec::new(),
920 attention_stats: HashMap::new(),
921 strategy_stats: HashMap::new(),
922 performance_comparisons: HashMap::new(),
923 }
924 }
925
926 pub fn update_step(&mut self, loss: f64, step: usize) {
928 self.total_steps = step;
929 self.convergence_history.push(loss);
930
931 if self.convergence_history.len() > 10000 {
933 self.convergence_history.remove(0);
934 }
935 }
936
937 pub fn total_steps(&self) -> usize {
939 self.total_steps
940 }
941
942 pub fn convergence_history(&self) -> &[f64] {
944 &self.convergence_history
945 }
946
947 pub fn attention_stats(&self) -> &HashMap<String, f64> {
949 &self.attention_stats
950 }
951
952 pub fn strategy_stats(&self) -> &HashMap<String, f64> {
954 &self.strategy_stats
955 }
956
957 pub fn performance_comparisons(&self) -> &HashMap<String, f64> {
959 &self.performance_comparisons
960 }
961}
962
963#[cfg(test)]
964mod tests {
965 use super::*;
966
967 fn quadratic_loss(params: &Array2<f64>) -> f64 {
968 params.iter().map(|&x| x * x).sum()
969 }
970
971 fn quadratic_gradient(params: &Array2<f64>) -> Array2<f64> {
972 params.mapv(|x| 2.0 * x)
973 }
974
975 #[test]
976 fn default_config_is_valid() {
977 TransformerOptimizerConfig::default()
978 .validate()
979 .expect("the default configuration must be valid");
980 }
981
982 #[test]
983 fn invalid_configurations_are_rejected() {
984 let config = TransformerOptimizerConfig {
986 numheads: 5,
987 ..TransformerOptimizerConfig::default()
988 };
989 assert!(config.validate().is_err());
990 assert!(TransformerOptimizer::<f64>::new(config).is_err());
991
992 let config = TransformerOptimizerConfig {
993 modeldim: 0,
994 ..TransformerOptimizerConfig::default()
995 };
996 assert!(TransformerOptimizer::<f64>::new(config).is_err());
997
998 let config = TransformerOptimizerConfig {
999 ff_dropout: 1.5,
1000 ..TransformerOptimizerConfig::default()
1001 };
1002 assert!(TransformerOptimizer::<f64>::new(config).is_err());
1003
1004 let config = TransformerOptimizerConfig {
1005 num_layers: 0,
1006 ..TransformerOptimizerConfig::default()
1007 };
1008 assert!(TransformerOptimizer::<f64>::new(config).is_err());
1009 }
1010
1011 #[test]
1012 fn every_layer_count_is_supported() {
1013 for num_layers in 1..=4 {
1014 let config = TransformerOptimizerConfig {
1015 num_layers,
1016 ..Default::default()
1017 };
1018 let mut optimizer =
1019 TransformerOptimizer::<f64>::new(config).expect("optimizer creation");
1020
1021 let mut parameters = HashMap::new();
1022 parameters.insert("w".to_string(), Array2::from_elem((2, 5), 0.5));
1023 let mut gradients = HashMap::new();
1024 gradients.insert("w".to_string(), Array2::from_elem((2, 5), 0.1));
1025
1026 optimizer
1027 .step(&mut parameters, &mut gradients, 1.0)
1028 .unwrap_or_else(|e| panic!("{num_layers} layers failed: {e}"));
1029 }
1030 }
1031
1032 #[test]
1033 fn step_writes_parameters_in_place() {
1034 let mut optimizer = TransformerOptimizer::<f64>::new(TransformerOptimizerConfig::default())
1035 .expect("optimizer creation");
1036 optimizer.set_learning_rate(1e-2);
1037
1038 let initial = Array2::from_elem((3, 4), 1.0);
1039 let mut parameters = HashMap::new();
1040 parameters.insert("w".to_string(), initial.clone());
1041 let mut gradients = HashMap::new();
1042 gradients.insert("w".to_string(), quadratic_gradient(&initial));
1043
1044 optimizer
1045 .step(&mut parameters, &mut gradients, quadratic_loss(&initial))
1046 .expect("step must succeed");
1047
1048 let updated = parameters.get("w").expect("parameter present");
1049 assert_ne!(*updated, initial, "step left the parameters untouched");
1050 assert!(updated.iter().all(|v| v.is_finite()));
1051 assert!(updated[[0, 0]] < initial[[0, 0]]);
1053 }
1054
1055 #[test]
1056 fn optimizing_a_quadratic_reduces_the_loss() {
1057 for scale in [0.25_f64, 0.5, 1.0, 2.0] {
1060 let mut optimizer =
1061 TransformerOptimizer::<f64>::new(TransformerOptimizerConfig::default())
1062 .expect("optimizer creation");
1063 optimizer.set_learning_rate(1e-2);
1064
1065 let initial =
1066 Array2::from_shape_fn((2, 4), |(i, j)| scale * (1.0 + (i + j) as f64 * 0.25));
1067 let initial_loss = quadratic_loss(&initial);
1068
1069 let mut parameters = HashMap::new();
1070 parameters.insert("w".to_string(), initial.clone());
1071
1072 for _ in 0..100 {
1073 let current = parameters.get("w").expect("parameter present").clone();
1074 let loss = quadratic_loss(¤t);
1075 let mut gradients = HashMap::new();
1076 gradients.insert("w".to_string(), quadratic_gradient(¤t));
1077 optimizer
1078 .step(&mut parameters, &mut gradients, loss)
1079 .expect("step must succeed");
1080 }
1081
1082 let updated = parameters.get("w").expect("parameter present");
1083 let end_loss = quadratic_loss(updated);
1084
1085 assert!(
1086 end_loss < initial_loss,
1087 "scale {scale}: loss {end_loss} did not improve on {initial_loss}"
1088 );
1089 assert!(
1090 updated.iter().zip(initial.iter()).any(|(a, b)| a != b),
1091 "scale {scale}: parameters never changed"
1092 );
1093 assert!(end_loss.is_finite());
1094 }
1095 }
1096
1097 #[test]
1098 fn parameters_of_different_shapes_are_handled() {
1099 let mut optimizer = TransformerOptimizer::<f64>::new(TransformerOptimizerConfig::default())
1100 .expect("optimizer creation");
1101
1102 let mut parameters = HashMap::new();
1103 parameters.insert("small".to_string(), Array2::from_elem((2, 3), 0.5));
1104 parameters.insert("large".to_string(), Array2::from_elem((8, 16), 0.25));
1105 let mut gradients = HashMap::new();
1106 gradients.insert("small".to_string(), Array2::from_elem((2, 3), 0.1));
1107 gradients.insert("large".to_string(), Array2::from_elem((8, 16), 0.05));
1108
1109 optimizer
1110 .step(&mut parameters, &mut gradients, 1.0)
1111 .expect("heterogeneous shapes must work");
1112
1113 assert_eq!(parameters["small"].dim(), (2, 3));
1114 assert_eq!(parameters["large"].dim(), (8, 16));
1115 }
1116
1117 #[test]
1118 fn shape_mismatch_between_parameter_and_gradient_is_an_error() {
1119 let mut optimizer = TransformerOptimizer::<f64>::new(TransformerOptimizerConfig::default())
1120 .expect("optimizer creation");
1121
1122 let mut parameters = HashMap::new();
1123 parameters.insert("w".to_string(), Array2::from_elem((2, 3), 0.5));
1124 let mut gradients = HashMap::new();
1125 gradients.insert("w".to_string(), Array2::from_elem((3, 2), 0.1));
1126
1127 assert!(optimizer
1128 .step(&mut parameters, &mut gradients, 1.0)
1129 .is_err());
1130 }
1131
1132 #[test]
1133 fn zero_gradients_leave_parameters_finite() {
1134 let mut optimizer = TransformerOptimizer::<f64>::new(TransformerOptimizerConfig::default())
1135 .expect("optimizer creation");
1136
1137 let mut parameters = HashMap::new();
1138 parameters.insert("w".to_string(), Array2::from_elem((2, 3), 0.5));
1139 let mut gradients = HashMap::new();
1140 gradients.insert("w".to_string(), Array2::zeros((2, 3)));
1141
1142 optimizer
1143 .step(&mut parameters, &mut gradients, 0.0)
1144 .expect("step must succeed");
1145 assert!(parameters["w"].iter().all(|v| v.is_finite()));
1146 }
1147
1148 #[test]
1149 fn network_forward_is_reachable_and_nonzero() {
1150 let config = TransformerOptimizerConfig::default();
1151 let mut network = TransformerNetwork::<f64>::new(&config).expect("network creation");
1152 let input = Array2::from_shape_fn((4, config.modeldim), |(i, j)| {
1153 ((i * config.modeldim + j) as f64).sin()
1154 });
1155 let output = network.forward(&input).expect("forward");
1156 assert_eq!(output.dim(), (4, config.modeldim));
1157 assert!(output.iter().any(|&v| v != 0.0));
1158 assert!(output.iter().all(|v| v.is_finite()));
1159 }
1160
1161 #[test]
1162 fn training_mode_toggles_dropout() {
1163 let config = TransformerOptimizerConfig::default();
1164 let mut network = TransformerNetwork::<f64>::new(&config).expect("network creation");
1165 let input = Array2::from_elem((4, config.modeldim), 0.1);
1166
1167 let inference_a = network.forward(&input).expect("forward");
1168 let inference_b = network.forward(&input).expect("forward");
1169 assert_eq!(inference_a, inference_b, "inference must be deterministic");
1170
1171 network.set_training(true);
1172 let training_out = network.forward(&input).expect("forward");
1173 assert_ne!(
1174 training_out, inference_a,
1175 "training mode must actually apply dropout"
1176 );
1177 }
1178
1179 #[test]
1180 fn reset_clears_state() {
1181 let mut optimizer = TransformerOptimizer::<f64>::new(TransformerOptimizerConfig::default())
1182 .expect("optimizer creation");
1183 let mut parameters = HashMap::new();
1184 parameters.insert("w".to_string(), Array2::from_elem((2, 3), 0.5));
1185 let mut gradients = HashMap::new();
1186 gradients.insert("w".to_string(), Array2::from_elem((2, 3), 0.1));
1187
1188 optimizer
1189 .step(&mut parameters, &mut gradients, 1.0)
1190 .expect("step");
1191 assert_eq!(optimizer.step_count(), 1);
1192 optimizer.reset().expect("reset");
1193 assert_eq!(optimizer.step_count(), 0);
1194 }
1195
1196 #[test]
1197 fn sequence_buffer_is_bounded_per_parameter() {
1198 let mut buffer = SequenceBuffer::<f64>::new(3);
1199 for i in 0..10 {
1200 buffer.add_gradient("a", Array1::from_elem(2, i as f64));
1201 buffer.add_gradient("b", Array1::from_elem(5, i as f64));
1202 }
1203 assert_eq!(buffer.recent_gradients("a").len(), 3);
1204 assert_eq!(buffer.recent_gradients("b").len(), 3);
1205 assert_eq!(buffer.recent_gradients("a")[2].len(), 2);
1206 assert_eq!(buffer.recent_gradients("b")[2].len(), 5);
1207 assert!(buffer.recent_gradients("missing").is_empty());
1208 }
1209}