Skip to main content

optirs_learned/transformer/
mod.rs

1use std::fmt::Debug;
2// Transformer-based learned optimizer
3//
4// This module provides a modular implementation of a transformer-based neural optimizer
5// that uses self-attention mechanisms to adaptively update optimization parameters.
6// The implementation is organized into architectural components, optimization strategies,
7// and training infrastructure for improved maintainability and extensibility.
8
9pub mod architecture;
10pub mod strategies;
11pub mod training;
12
13// Re-export key types for convenience
14pub 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/// Configuration specific to Transformer optimizer
39#[derive(Debug, Clone)]
40pub struct TransformerOptimizerConfig {
41    /// Base learned optimizer config
42    pub base_config: LearnedOptimizerConfig,
43
44    /// Model dimension (d_model)
45    pub modeldim: usize,
46
47    /// Number of attention heads
48    pub numheads: usize,
49
50    /// Feed-forward network dimension
51    pub ff_dim: usize,
52
53    /// Number of transformer layers
54    pub num_layers: usize,
55
56    /// Maximum sequence length, which is also the optimization-history window
57    /// fed to the network on every step
58    pub max_sequence_length: usize,
59
60    /// Attention dropout rate
61    pub attention_dropout: f64,
62
63    /// Feed-forward dropout rate
64    pub ff_dropout: f64,
65
66    /// Layer normalization epsilon
67    pub layer_norm_eps: f64,
68
69    /// Use pre-layer normalization
70    pub pre_layer_norm: bool,
71
72    /// Positional encoding type
73    pub pos_encoding_type: PositionalEncodingType,
74
75    /// Enable relative position bias
76    pub relative_position_bias: bool,
77
78    /// Use rotary position embedding
79    pub use_rope: bool,
80
81    /// Mask out future positions in self-attention
82    pub causal_attention: bool,
83
84    /// Feed-forward activation function
85    pub activation: ActivationFunction,
86
87    /// Seed used by the dropout layers so training runs are reproducible
88    pub dropout_seed: u64,
89
90    /// Amplitude of the transformer's modulation of the gradient step.
91    ///
92    /// The applied per-coordinate scale is `1 + modulation * tanh(network
93    /// output)`, so values in `(0, 1)` keep the scale strictly positive and the
94    /// update in the descent half-space.
95    pub update_modulation: f64,
96
97    /// Enable gradient checkpointing
98    pub gradient_checkpointing: bool,
99
100    /// Attention pattern optimization
101    pub attention_optimization: AttentionOptimization,
102
103    /// Multi-scale attention
104    pub multi_scale_attention: bool,
105
106    /// Cross-attention for multi-task learning
107    pub cross_attention: bool,
108
109    /// Memory efficiency mode
110    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    /// Validate the configuration, returning a descriptive error for any
144    /// combination that would otherwise fail (or silently misbehave) later.
145    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/// Transformer network architecture
221#[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 layer
234    input_embedding: InputEmbedding<T>,
235
236    /// Transformer layers
237    layers: Vec<TransformerLayer<T>>,
238
239    /// Output projection
240    output_projection: OutputProjectionLayer<T>,
241
242    /// Layer normalization for output
243    output_layer_norm: LayerNorm<T>,
244
245    /// Position encoder
246    position_encoder: PositionalEncoder<T>,
247
248    /// Configuration
249    config: TransformerOptimizerConfig,
250}
251
252/// Transformer-based neural optimizer with self-attention mechanisms
253#[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    /// Configuration for the Transformer optimizer
266    config: TransformerOptimizerConfig,
267
268    /// Transformer network architecture
269    transformer_network: TransformerNetwork<T>,
270
271    /// Gradient processing strategies
272    gradient_processor: GradientProcessor<T>,
273
274    /// Learning rate adaptation
275    lr_adapter: LearningRateAdapter<T>,
276
277    /// Momentum integration
278    momentum_integrator: MomentumIntegrator<T>,
279
280    /// Regularization strategies
281    regularizer: TransformerRegularizer<T>,
282
283    /// Meta-learning components
284    meta_learner: TransformerMetaLearner<T>,
285
286    /// Curriculum learning
287    curriculum_learner: CurriculumLearner<T>,
288
289    /// Evaluation framework
290    evaluator: TransformerEvaluator<T>,
291
292    /// Sequence buffer for maintaining optimization history
293    sequence_buffer: SequenceBuffer<T>,
294
295    /// Performance metrics
296    metrics: TransformerOptimizerMetrics,
297
298    /// Current optimization step
299    step_count: usize,
300}
301
302/// Per-parameter sequence buffer for optimization history
303#[derive(Debug, Clone)]
304pub struct SequenceBuffer<
305    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
306> {
307    /// Gradient sequences, keyed by parameter name
308    gradient_sequences: HashMap<String, VecDeque<Array1<T>>>,
309
310    /// Parameter sequences, keyed by parameter name
311    parameter_sequences: HashMap<String, VecDeque<Array1<T>>>,
312
313    /// Loss sequences
314    loss_sequences: VecDeque<T>,
315
316    /// Learning rate sequences
317    lr_sequences: VecDeque<T>,
318
319    /// Buffer capacity per key
320    capacity: usize,
321}
322
323/// Performance metrics for transformer optimizer
324#[derive(Debug, Clone)]
325pub struct TransformerOptimizerMetrics {
326    /// Total optimization steps
327    total_steps: usize,
328
329    /// Convergence history
330    convergence_history: Vec<f64>,
331
332    /// Attention pattern statistics
333    attention_stats: HashMap<String, f64>,
334
335    /// Strategy usage statistics
336    strategy_stats: HashMap<String, f64>,
337
338    /// Performance comparisons
339    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    /// Create new transformer network
355    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    /// Forward pass through transformer network
381    pub fn forward(&mut self, input: &Array2<T>) -> Result<Array2<T>> {
382        // Input embedding
383        let mut x = self.input_embedding.forward(input)?;
384
385        // Add positional encoding
386        x = self.position_encoder.encode(&x)?;
387
388        // Pass through transformer layers
389        for layer in &mut self.layers {
390            x = layer.forward(&x)?;
391        }
392
393        // Output layer normalization
394        x = self.output_layer_norm.forward(&x)?;
395
396        // Output projection
397        let output = self.output_projection.forward(&x)?;
398
399        Ok(output)
400    }
401
402    /// Get attention patterns from all layers
403    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    /// Switch every dropout in the network between training and inference mode.
411    pub fn set_training(&mut self, training: bool) {
412        for layer in &mut self.layers {
413            layer.set_training(training);
414        }
415    }
416
417    /// Number of transformer layers
418    pub fn num_layers(&self) -> usize {
419        self.layers.len()
420    }
421
422    /// Configuration this network was built from
423    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    /// Create new transformer optimizer
441    pub fn new(config: TransformerOptimizerConfig) -> Result<Self> {
442        // Reject invalid configurations before any allocation happens.
443        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    /// Perform one optimization step, writing the updated values back into
478    /// `parameters` in place.
479    ///
480    /// The pipeline is: regularize -> process -> adapt learning rate ->
481    /// integrate momentum -> run the recent optimization history through the
482    /// transformer -> apply `param -= lr * modulation * momentum_gradient`.
483    ///
484    /// The transformer output is used as a bounded, strictly positive
485    /// per-coordinate *scale* on the momentum-integrated gradient rather than
486    /// as the update itself. This is the standard learned-preconditioner
487    /// formulation: the network can speed up or slow down individual
488    /// coordinates but cannot, by itself, reverse the descent direction, so an
489    /// untrained network degrades gracefully to plain gradient descent instead
490    /// of diverging.
491    ///
492    /// Returns the loss it was given, so callers can chain steps.
493    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        // Deterministic ordering so repeated runs are reproducible.
502        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            // 1. Regularization operates on the real parameter/gradient pair and
528            //    its effect is kept.
529            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            // Spectral normalization may have rescaled the weights.
544            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            // 2. Gradient processing.
550            let processed_gradient = self
551                .gradient_processor
552                .process_gradients(name, &flat_gradient)?;
553
554            // 3. Learning rate adaptation.
555            let current_lr = self
556                .lr_adapter
557                .update_learning_rate(Some(loss), Some(&processed_gradient))?;
558
559            // 4. Momentum integration.
560            let momentum_gradient =
561                self.momentum_integrator
562                    .integrate_momentum(name, &processed_gradient, None)?;
563
564            // 5. Record the history the transformer consumes.
565            self.sequence_buffer
566                .add_gradient(name, momentum_gradient.clone());
567            self.sequence_buffer
568                .add_parameters(name, flat_param.clone());
569
570            // 6. Transformer forward pass over the recent history.
571            let modulation = self.transformer_modulation(name, flat_param.len())?;
572
573            // 7. Apply the update in place.
574            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        // Update sequence buffer with loss and learning rate
589        self.sequence_buffer.add_loss(loss);
590        self.sequence_buffer
591            .add_learning_rate(self.lr_adapter.current_learning_rate());
592
593        // Update curriculum learning
594        self.curriculum_learner
595            .update_curriculum("current_task", loss, self.step_count)?;
596
597        // Update metrics
598        self.metrics
599            .update_step(loss.to_f64().unwrap_or(0.0), self.step_count);
600
601        Ok(loss)
602    }
603
604    /// Build the `(seq_len, modeldim)` feature matrix for `param_name` from the
605    /// recorded optimization history.
606    ///
607    /// Each row summarises one historical step: the flattened gradient is
608    /// average-pooled into `modeldim` buckets by index modulo `modeldim`, which
609    /// is well defined for parameter tensors both smaller and larger than the
610    /// model dimension.
611    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    /// Average-pool an arbitrary-length vector into exactly `width` buckets.
634    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    /// Run the history through the transformer and expand its output into a
659    /// per-coordinate, strictly positive modulation factor.
660    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        // The last row summarises the most recent optimization state.
670        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    /// Get current optimization statistics
684    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        // Add gradient processor statistics
697        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        // Add momentum statistics
708        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        // Add curriculum statistics
722        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    /// Switch the network between training mode (dropout active) and inference
731    /// mode. Optimizers are created in inference mode.
732    pub fn set_training(&mut self, training: bool) {
733        self.transformer_network.set_training(training);
734    }
735
736    /// Override the base learning rate used by the adapter.
737    pub fn set_learning_rate(&mut self, lr: T) {
738        self.lr_adapter.set_base_learning_rate(lr);
739    }
740
741    /// Current (post-adaptation) learning rate.
742    pub fn current_learning_rate(&self) -> T {
743        self.lr_adapter.current_learning_rate()
744    }
745
746    /// Number of steps taken so far.
747    pub fn step_count(&self) -> usize {
748        self.step_count
749    }
750
751    /// Configuration in use.
752    pub fn config(&self) -> &TransformerOptimizerConfig {
753        &self.config
754    }
755
756    /// Mutable access to the learning rate adapter.
757    pub fn lr_adapter_mut(&mut self) -> &mut LearningRateAdapter<T> {
758        &mut self.lr_adapter
759    }
760
761    /// Mutable access to the gradient processor.
762    pub fn gradient_processor_mut(&mut self) -> &mut GradientProcessor<T> {
763        &mut self.gradient_processor
764    }
765
766    /// Mutable access to the momentum integrator.
767    pub fn momentum_integrator_mut(&mut self) -> &mut MomentumIntegrator<T> {
768        &mut self.momentum_integrator
769    }
770
771    /// Mutable access to the regularizer.
772    pub fn regularizer_mut(&mut self) -> &mut TransformerRegularizer<T> {
773        &mut self.regularizer
774    }
775
776    /// Mutable access to the meta-learner.
777    pub fn meta_learner_mut(&mut self) -> &mut TransformerMetaLearner<T> {
778        &mut self.meta_learner
779    }
780
781    /// Mutable access to the evaluator.
782    pub fn evaluator_mut(&mut self) -> &mut TransformerEvaluator<T> {
783        &mut self.evaluator
784    }
785
786    /// Reset optimizer state
787    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    /// Create new sequence buffer
815    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    /// Record a gradient for a named parameter
833    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    /// Record parameter values for a named parameter
843    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    /// Add loss to buffer
853    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    /// Add learning rate to buffer
861    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    /// Clear buffer
869    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    /// Recorded gradients for a parameter, oldest first
877    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    /// Recorded parameter values for a parameter, oldest first
885    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    /// Recorded losses, oldest first
893    pub fn losses(&self) -> &VecDeque<T> {
894        &self.loss_sequences
895    }
896
897    /// Recorded learning rates, oldest first
898    pub fn learning_rates(&self) -> &VecDeque<T> {
899        &self.lr_sequences
900    }
901
902    /// Per-key capacity
903    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    /// Create new metrics tracker
916    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    /// Update metrics after optimization step
927    pub fn update_step(&mut self, loss: f64, step: usize) {
928        self.total_steps = step;
929        self.convergence_history.push(loss);
930
931        // Keep only recent history
932        if self.convergence_history.len() > 10000 {
933            self.convergence_history.remove(0);
934        }
935    }
936
937    /// Total steps recorded
938    pub fn total_steps(&self) -> usize {
939        self.total_steps
940    }
941
942    /// Recorded loss history
943    pub fn convergence_history(&self) -> &[f64] {
944        &self.convergence_history
945    }
946
947    /// Attention statistics
948    pub fn attention_stats(&self) -> &HashMap<String, f64> {
949        &self.attention_stats
950    }
951
952    /// Strategy usage statistics
953    pub fn strategy_stats(&self) -> &HashMap<String, f64> {
954        &self.strategy_stats
955    }
956
957    /// Baseline comparison statistics
958    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        // 32 is not divisible by 5
985        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        // Descent on a convex quadratic moves positive weights downward.
1052        assert!(updated[[0, 0]] < initial[[0, 0]]);
1053    }
1054
1055    #[test]
1056    fn optimizing_a_quadratic_reduces_the_loss() {
1057        // Several seeds' worth of starting points: the update is gradient
1058        // anchored, so convergence must not depend on a lucky initialization.
1059        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(&current);
1075                let mut gradients = HashMap::new();
1076                gradients.insert("w".to_string(), quadratic_gradient(&current));
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}