1pub mod architecture;
9pub mod attention;
10pub mod config;
11pub mod feedforward;
12pub mod layers;
13pub mod memory_manager;
14pub mod meta_learning;
15pub mod performance_tracker;
16pub mod positional_encoding;
17pub mod sequence_processor;
18pub mod state;
19
20pub use architecture::{ArchitectureCache, TransformerArchitecture, TransformerLayer};
22pub use attention::{AttentionMechanism, MultiHeadAttention};
23pub use config::{TransformerArchConfig, TransformerBasedOptimizerConfig};
24pub use feedforward::{ActivationFunction, FeedForwardNetwork};
25pub use layers::{
26 DropoutLayer, EmbeddingLayer, LayerNormalization, OutputProjection, ResidualConnections,
27};
28pub use memory_manager::{MemoryManagementStrategy, TransformerMemoryManager};
29pub use meta_learning::{MetaLearningStrategy, TransformerMetaLearning};
30pub use performance_tracker::{PerformanceMetrics, TransformerPerformanceTracker};
31pub use positional_encoding::{PositionalEncoding, PositionalEncodingType};
32pub use sequence_processor::{OptimizationSequenceProcessor, SequenceProcessingStrategy};
33pub use state::{OptimizerStateSnapshot, TransformerOptimizerState};
34
35pub use TransformerBasedOptimizerConfig as TransformerOptimizerConfig;
37
38use scirs2_core::ndarray::{Array1, Array2};
39use scirs2_core::numeric::Float;
40use std::fmt::Debug;
41use std::time::{Duration, Instant};
42
43use crate::error::{OptimError, Result};
44
45pub struct TransformerOptimizer<
47 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
48> {
49 transformer: TransformerArchitecture<T>,
51
52 positional_encoding: PositionalEncoding<T>,
54
55 meta_learning: TransformerMetaLearning<T>,
65
66 sequence_processor: OptimizationSequenceProcessor<T>,
68
69 memory_manager: TransformerMemoryManager<T>,
71
72 config: TransformerBasedOptimizerConfig<T>,
74
75 performance_tracker: TransformerPerformanceTracker<T>,
77
78 state: TransformerOptimizerState<T>,
80}
81
82impl<
83 T: Float
84 + Debug
85 + Send
86 + Sync
87 + 'static
88 + scirs2_core::ndarray::ScalarOperand
89 + scirs2_core::numeric::FromPrimitive,
90 > TransformerOptimizer<T>
91{
92 pub fn new(config: TransformerBasedOptimizerConfig<T>) -> Result<Self> {
107 config.validate().map_err(OptimError::InvalidConfig)?;
108
109 let transformer_config = TransformerArchConfig::from_optimizer_config(&config);
110 let transformer = TransformerArchitecture::new(transformer_config)?;
111
112 let positional_encoding = PositionalEncoding::new(
113 config.sequence_length,
114 config.model_dimension,
115 config.positional_encoding_type,
116 )?;
117
118 let meta_learning = TransformerMetaLearning::new(&config)?;
119 let sequence_processor = OptimizationSequenceProcessor::new(&config)?;
120 let memory_manager = TransformerMemoryManager::new(&config)?;
121 let performance_tracker = TransformerPerformanceTracker::new();
122 let state = TransformerOptimizerState::new(&config)?;
123
124 Ok(Self {
125 transformer,
126 positional_encoding,
127 meta_learning,
128 sequence_processor,
129 memory_manager,
130 config,
131 performance_tracker,
132 state,
133 })
134 }
135
136 pub fn generate_optimization_step(
138 &mut self,
139 gradient_history: &Array2<T>,
140 parameter_history: &Array2<T>,
141 loss_history: &Array1<T>,
142 ) -> Result<Array1<T>> {
143 let start_time = Instant::now();
144
145 let processed_sequence = self.sequence_processor.process_optimization_sequence(
147 gradient_history,
148 parameter_history,
149 loss_history,
150 )?;
151
152 let encoded_sequence = self.positional_encoding.encode(&processed_sequence)?;
154
155 let transformer_output = self.transformer.forward(&encoded_sequence)?;
157
158 let optimization_step = self
160 .meta_learning
161 .generate_update(&transformer_output, &self.state.current_parameters)?;
162
163 self.state
165 .update_with_step(&optimization_step, loss_history.last().copied())?;
166
167 let elapsed = start_time.elapsed();
169 self.performance_tracker
170 .record_optimization_step(elapsed, &optimization_step);
171
172 Ok(optimization_step)
173 }
174
175 pub fn train_on_trajectories(
177 &mut self,
178 trajectories: &[OptimizationTrajectory<T>],
179 ) -> Result<TrainingMetrics> {
180 let start_time = Instant::now();
181 let mut total_loss = T::zero();
182 let mut batch_count = 0;
183 let mut applied_gradient_energy = T::zero();
184
185 for trajectory in trajectories {
186 let sequences = self
188 .sequence_processor
189 .trajectory_to_sequences(trajectory)?;
190
191 for sequence in sequences {
192 let encoded = self.positional_encoding.encode(&sequence.input)?;
194 let (prediction, cache) = self.transformer.forward_with_cache(&encoded)?;
195
196 let loss = self.calculate_sequence_loss(&prediction, &sequence.target)?;
198 total_loss = total_loss + loss;
199
200 applied_gradient_energy = applied_gradient_energy
202 + self.backward_pass(&prediction, &sequence.target, &cache, loss)?;
203
204 batch_count += 1;
205 }
206 }
207
208 let avg_loss = if batch_count > 0 {
209 total_loss
210 / scirs2_core::numeric::NumCast::from(batch_count).unwrap_or_else(|| T::zero())
211 } else {
212 T::zero()
213 };
214
215 let training_time = start_time.elapsed();
216
217 let metrics = TrainingMetrics {
218 loss: avg_loss.to_f64().unwrap_or(0.0),
219 training_time,
220 num_sequences: batch_count,
221 convergence_rate: self.calculate_convergence_rate()?,
222 applied_gradient_energy: applied_gradient_energy.to_f64().unwrap_or(0.0),
223 };
224
225 self.performance_tracker
226 .record_training_epoch(metrics.clone());
227
228 Ok(metrics)
229 }
230
231 pub fn forward_sequence(&mut self, sequence: &Array2<T>) -> Result<Array2<T>> {
233 let encoded_sequence = self.positional_encoding.encode(sequence)?;
235
236 self.transformer.forward(&encoded_sequence)
238 }
239
240 fn calculate_sequence_loss(&self, prediction: &Array2<T>, target: &Array2<T>) -> Result<T> {
242 if prediction.shape() != target.shape() {
243 return Err(OptimError::Other(
244 "Shape mismatch in loss calculation".to_string(),
245 ));
246 }
247
248 if prediction.is_empty() {
249 return Err(OptimError::InvalidConfig(
250 "cannot take the mean squared error of an empty prediction".to_string(),
251 ));
252 }
253
254 let diff = prediction - target;
256 let squared_diff = &diff * &diff;
257 let sum = squared_diff.sum();
258 let count = T::from(prediction.len()).ok_or_else(|| {
259 OptimError::ComputationError(format!(
260 "cannot represent the element count {} in the value type",
261 prediction.len()
262 ))
263 })?;
264
265 Ok(sum / count)
266 }
267
268 fn backward_pass(
283 &mut self,
284 prediction: &Array2<T>,
285 target: &Array2<T>,
286 cache: &ArchitectureCache<T>,
287 loss: T,
288 ) -> Result<T> {
289 if prediction.shape() != target.shape() {
290 return Err(OptimError::Other(format!(
291 "Shape mismatch in backward pass: prediction {:?} vs target {:?}",
292 prediction.shape(),
293 target.shape()
294 )));
295 }
296
297 let count = T::from(prediction.len()).unwrap_or_else(|| T::one());
298 let two = T::one() + T::one();
299 let mut grad_output = (prediction - target).mapv(|d| two * d / count);
300
301 if self.config.enable_gradient_clipping {
302 let norm = grad_output
303 .iter()
304 .map(|&g| g * g)
305 .fold(T::zero(), |a, b| a + b)
306 .sqrt();
307 let threshold = self.config.gradient_clip_value;
308 if threshold > T::zero() && norm > threshold {
309 let scale = threshold / norm;
310 grad_output.mapv_inplace(|g| g * scale);
311 }
312 }
313
314 let applied = self
315 .transformer
316 .backward(cache, &grad_output, self.config.learning_rate)?;
317
318 self.meta_learning.update_from_loss(loss)?;
319 Ok(applied)
320 }
321
322 fn calculate_convergence_rate(&self) -> Result<f64> {
324 let loss_history = self.performance_tracker.get_loss_history();
325 if loss_history.len() < 2 {
326 return Ok(0.0);
327 }
328
329 let recent_losses: Vec<_> = loss_history.iter().rev().take(10).collect();
331 let (Some(final_loss), Some(initial_loss)) = (recent_losses.first(), recent_losses.last())
334 else {
335 return Ok(0.0);
336 };
337 let (initial_loss, final_loss) = (**initial_loss, **final_loss);
338 if initial_loss == 0.0 {
339 return Ok(0.0);
341 }
342
343 let improvement = (initial_loss - final_loss) / initial_loss;
344 Ok(improvement.clamp(0.0, 1.0))
345 }
346
347 pub fn apply_architecture_config(
380 &mut self,
381 config: &TransformerBasedOptimizerConfig<T>,
382 ) -> Result<ArchitectureUpdate> {
383 let structural_change = config.model_dimension != self.config.model_dimension
384 || config.num_transformer_layers != self.config.num_transformer_layers
385 || config.num_attention_heads != self.config.num_attention_heads
386 || config.attention_head_dimension != self.config.attention_head_dimension
387 || config.feedforward_dimension != self.config.feedforward_dimension
388 || config.sequence_length != self.config.sequence_length
389 || config.activation_function != self.config.activation_function
390 || config.positional_encoding_type != self.config.positional_encoding_type;
391
392 if structural_change {
393 let transformer_config = TransformerArchConfig::from_optimizer_config(config);
395 let transformer = TransformerArchitecture::new(transformer_config)?;
396 let positional_encoding = PositionalEncoding::new(
397 config.sequence_length,
398 config.model_dimension,
399 config.positional_encoding_type,
400 )?;
401 let meta_learning = TransformerMetaLearning::new(config)?;
402 let sequence_processor = OptimizationSequenceProcessor::new(config)?;
403 let memory_manager = TransformerMemoryManager::new(config)?;
404 let state = TransformerOptimizerState::new(config)?;
405
406 self.transformer = transformer;
407 self.positional_encoding = positional_encoding;
408 self.meta_learning = meta_learning;
409 self.sequence_processor = sequence_processor;
410 self.memory_manager = memory_manager;
411 self.state = state;
412 self.config = config.clone();
413 return Ok(ArchitectureUpdate::Rebuilt);
414 }
415
416 let non_structural_change = config.learning_rate != self.config.learning_rate
417 || config.dropout_rate != self.config.dropout_rate
418 || config.enable_gradient_clipping != self.config.enable_gradient_clipping
419 || config.gradient_clip_value != self.config.gradient_clip_value
420 || config.weight_decay != self.config.weight_decay;
421 if non_structural_change {
422 self.config = config.clone();
423 return Ok(ArchitectureUpdate::InPlace);
424 }
425
426 Ok(ArchitectureUpdate::Unchanged)
427 }
428
429 pub fn config(&self) -> &TransformerBasedOptimizerConfig<T> {
431 &self.config
432 }
433
434 pub fn parameter_count(&self) -> usize {
439 self.transformer.parameter_count() + self.meta_learning.parameter_count()
440 }
441
442 pub fn get_state(&self) -> &TransformerOptimizerState<T> {
444 &self.state
445 }
446
447 pub fn get_performance_metrics(&self) -> &TransformerPerformanceTracker<T> {
449 &self.performance_tracker
450 }
451
452 pub fn reset_state(&mut self) -> Result<()> {
454 self.state = TransformerOptimizerState::new(&self.config)?;
455 self.performance_tracker.reset();
456 Ok(())
457 }
458}
459
460#[derive(Debug, Clone)]
462pub struct OptimizationTrajectory<
463 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
464> {
465 pub gradient_sequence: Array2<T>,
466 pub parameter_sequence: Array2<T>,
467 pub loss_sequence: Array1<T>,
468 pub metadata: TrajectoryMetadata,
469}
470
471#[derive(Debug, Clone)]
473pub struct TrajectoryMetadata {
474 pub task_id: String,
475 pub optimizer_type: String,
476 pub convergence_achieved: bool,
477 pub total_steps: usize,
478}
479
480#[derive(Debug, Clone)]
482pub struct TrainingSequence<
483 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
484> {
485 pub input: Array2<T>,
486 pub target: Array2<T>,
487 pub sequence_length: usize,
488}
489
490#[derive(Debug, Clone, Copy, PartialEq, Eq)]
495pub enum ArchitectureUpdate {
496 Unchanged,
498 InPlace,
500 Rebuilt,
503}
504
505impl ArchitectureUpdate {
506 pub fn changed(self) -> bool {
508 !matches!(self, ArchitectureUpdate::Unchanged)
509 }
510
511 pub fn discarded_weights(self) -> bool {
513 matches!(self, ArchitectureUpdate::Rebuilt)
514 }
515}
516
517#[derive(Debug, Clone)]
519pub struct TrainingMetrics {
520 pub loss: f64,
521 pub training_time: Duration,
522 pub num_sequences: usize,
523 pub convergence_rate: f64,
524 pub applied_gradient_energy: f64,
532}
533
534#[cfg(test)]
535mod tests {
536 use super::*;
537
538 fn small_config() -> TransformerBasedOptimizerConfig<f64> {
540 TransformerBasedOptimizerConfig::<f64> {
541 model_dimension: 8,
542 num_transformer_layers: 2,
543 num_attention_heads: 2,
544 attention_head_dimension: 4,
545 feedforward_dimension: 16,
546 sequence_length: 8,
547 learning_rate: 1e-2,
548 dropout_rate: 0.0,
549 ..Default::default()
550 }
551 }
552
553 fn trajectory(steps: usize, width: usize, scale: f64) -> OptimizationTrajectory<f64> {
554 let gradient_sequence = Array2::from_shape_fn((steps, width), |(i, j)| {
555 scale * ((i * width + j) as f64 * 0.017).sin()
556 });
557 let parameter_sequence = Array2::from_shape_fn((steps, width), |(i, j)| {
558 scale * ((i * width + j) as f64 * 0.031).cos()
559 });
560 let loss_sequence = Array1::from_shape_fn(steps, |i| scale / (1.0 + i as f64));
561 OptimizationTrajectory {
562 gradient_sequence,
563 parameter_sequence,
564 loss_sequence,
565 metadata: TrajectoryMetadata {
566 task_id: "regression".to_string(),
567 optimizer_type: "adam".to_string(),
568 convergence_achieved: false,
569 total_steps: steps,
570 },
571 }
572 }
573
574 #[test]
575 fn test_transformer_optimizer_creation() {
576 let config = TransformerBasedOptimizerConfig::default();
577 let optimizer = TransformerOptimizer::<f32>::new(config);
578 assert!(optimizer.is_ok());
579 }
580
581 #[test]
591 fn construction_rejects_every_invalid_configuration() {
592 let base = || TransformerBasedOptimizerConfig::<f32> {
595 model_dimension: 8,
596 num_transformer_layers: 1,
597 num_attention_heads: 2,
598 attention_head_dimension: 4,
599 feedforward_dimension: 16,
600 sequence_length: 4,
601 batch_size: 2,
602 ..Default::default()
603 };
604 assert!(
605 TransformerOptimizer::<f32>::new(base()).is_ok(),
606 "the baseline configuration must itself be valid"
607 );
608
609 let mut cases: Vec<(&str, TransformerBasedOptimizerConfig<f32>)> = Vec::new();
610
611 cases.push(("model_dimension = 0", {
613 let mut c = base();
614 c.model_dimension = 0;
615 c
616 }));
617 cases.push(("num_transformer_layers = 0", {
618 let mut c = base();
619 c.num_transformer_layers = 0;
620 c
621 }));
622 cases.push(("num_attention_heads = 0", {
623 let mut c = base();
624 c.num_attention_heads = 0;
625 c
626 }));
627 cases.push(("heads do not divide model_dimension", {
628 let mut c = base();
629 c.num_attention_heads = 3;
630 c
631 }));
632 cases.push(("head_dim * heads != model_dimension", {
633 let mut c = base();
634 c.attention_head_dimension = 3;
635 c
636 }));
637 cases.push(("sequence_length = 0", {
638 let mut c = base();
639 c.sequence_length = 0;
640 c
641 }));
642 cases.push(("dropout_rate above 1", {
643 let mut c = base();
644 c.dropout_rate = 2.0;
645 c
646 }));
647 cases.push(("negative learning_rate", {
648 let mut c = base();
649 c.learning_rate = -1.0;
650 c
651 }));
652 cases.push(("batch_size = 0", {
653 let mut c = base();
654 c.batch_size = 0;
655 c
656 }));
657
658 cases.push(("max_cache_size = 0", {
660 let mut c = base();
661 c.memory_config.max_cache_size = 0;
662 c
663 }));
664 cases.push(("allocation_block_size = 0", {
665 let mut c = base();
666 c.memory_config.allocation_block_size = 0;
667 c
668 }));
669
670 cases.push(("meta_learning_rate = 0", {
672 let mut c = base();
673 c.meta_learning_config.meta_learning_rate = 0.0;
674 c
675 }));
676 cases.push(("inner_steps = 0", {
677 let mut c = base();
678 c.meta_learning_config.inner_steps = 0;
679 c
680 }));
681 cases.push(("num_support = 0", {
682 let mut c = base();
683 c.meta_learning_config.num_support = 0;
684 c
685 }));
686
687 cases.push(("metrics_interval = 0", {
689 let mut c = base();
690 c.performance_config.metrics_interval = 0;
691 c
692 }));
693 cases.push(("max_history_size = 0", {
694 let mut c = base();
695 c.performance_config.max_history_size = 0;
696 c
697 }));
698
699 for (label, config) in cases {
700 let outcome = TransformerOptimizer::<f32>::new(config);
701 assert!(
702 outcome.is_err(),
703 "`{label}` was accepted by TransformerOptimizer::new"
704 );
705 }
706 }
707
708 #[test]
714 fn no_dead_duplicate_parameter_blocks() {
715 let config = TransformerBasedOptimizerConfig::<f32>::default();
716 let model_dim = config.model_dimension;
717 let ff_dim = config.feedforward_dimension;
718 let layers = config.num_transformer_layers;
719 let optimizer = TransformerOptimizer::<f32>::new(config).expect("default construction");
720
721 let live = optimizer.parameter_count();
722 let dead_attention = 4 * model_dim * model_dim;
723 let dead_feedforward = layers * 2 * model_dim * ff_dim;
724
725 assert!(
726 live > 0,
727 "the optimizer must own some parameters ({live} found)"
728 );
729 let expected =
732 optimizer.transformer.parameter_count() + optimizer.meta_learning.parameter_count();
733 assert_eq!(
734 live,
735 expected,
736 "live parameter count {live} does not match \
737 transformer+meta {expected}; something allocated {} extra \
738 (dead attention {dead_attention}, dead feed-forward {dead_feedforward})",
739 live.saturating_sub(expected)
740 );
741 }
742
743 #[test]
747 fn train_on_trajectories_propagates_real_gradients() {
748 let config = small_config();
749 let width = config.model_dimension;
750 let mut optimizer =
751 TransformerOptimizer::<f64>::new(config).expect("optimizer construction");
752
753 let probe = Array2::from_shape_fn((4, width), |(i, j)| ((i + j) as f64 * 0.05).tanh());
754 let before = optimizer
755 .forward_sequence(&probe)
756 .expect("forward before training");
757
758 let trajectories = vec![trajectory(12, width, 1.0)];
759 let metrics = optimizer
760 .train_on_trajectories(&trajectories)
761 .expect("training");
762
763 assert!(
764 metrics.num_sequences > 0,
765 "the trajectory must produce at least one training sequence"
766 );
767 assert!(
768 metrics.applied_gradient_energy > 0.0,
769 "backward pass propagated no gradient (energy {})",
770 metrics.applied_gradient_energy
771 );
772
773 let after = optimizer
774 .forward_sequence(&probe)
775 .expect("forward after training");
776 let moved = (&after - &before)
777 .iter()
778 .map(|d| d.abs())
779 .fold(0.0, f64::max);
780 assert!(
781 moved > 1e-12,
782 "training left the network unchanged (max output delta {moved})"
783 );
784 }
785
786 #[test]
789 fn repeated_training_epochs_reduce_the_loss() {
790 let config = small_config();
791 let width = config.model_dimension;
792 let mut optimizer =
793 TransformerOptimizer::<f64>::new(config).expect("optimizer construction");
794
795 let trajectories = vec![trajectory(12, width, 1.0)];
796 let first = optimizer
797 .train_on_trajectories(&trajectories)
798 .expect("epoch 1")
799 .loss;
800 let mut last = first;
801 for _ in 0..12 {
802 last = optimizer
803 .train_on_trajectories(&trajectories)
804 .expect("later epoch")
805 .loss;
806 }
807
808 assert!(
809 last < first,
810 "loss did not decrease across epochs: {first} -> {last}"
811 );
812 }
813
814 #[test]
816 fn sequence_loss_rejects_shape_mismatch() {
817 let config = small_config();
818 let optimizer = TransformerOptimizer::<f64>::new(config).expect("construction");
819 let prediction = Array2::<f64>::zeros((3, 8));
820 let target = Array2::<f64>::zeros((4, 8));
821 assert!(optimizer
822 .calculate_sequence_loss(&prediction, &target)
823 .is_err());
824 }
825
826 #[test]
827 fn test_trajectory_creation() {
828 let trajectory = OptimizationTrajectory::<f32> {
829 gradient_sequence: Array2::zeros((10, 5)),
830 parameter_sequence: Array2::zeros((10, 5)),
831 loss_sequence: Array1::zeros(10),
832 metadata: TrajectoryMetadata {
833 task_id: "test".to_string(),
834 optimizer_type: "adam".to_string(),
835 convergence_achieved: true,
836 total_steps: 10,
837 },
838 };
839
840 assert_eq!(trajectory.gradient_sequence.shape(), &[10, 5]);
841 assert_eq!(trajectory.loss_sequence.len(), 10);
842 }
843}