1use crate::error::{OptimError, Result};
7use crate::utils::{scalar_opt, scalar_or, try_scalar};
8use scirs2_core::ndarray::{Array, Dimension, ScalarOperand};
9use scirs2_core::numeric::Float;
10use std::collections::HashMap;
11use std::fmt::Debug;
12pub type LayerId = String;
16
17pub type ParamId = String;
19
20#[derive(Debug, Clone)]
22pub struct ParameterMetadata {
23 pub layername: LayerId,
25 pub param_name: ParamId,
27 pub shape: Vec<usize>,
29 pub requires_grad: bool,
31 pub paramtype: ParameterType,
33 pub sharing_group: Option<String>,
35 pub tags: Vec<String>,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum ParameterType {
42 Weight,
44 Bias,
46 Normalization,
48 Embedding,
50 Attention,
52 Custom,
54}
55
56#[derive(Debug, Clone)]
58pub struct LayerArchitecture {
59 pub layer_type: String,
61 pub input_dims: Vec<usize>,
63 pub output_dims: Vec<usize>,
65 pub config: HashMap<String, LayerConfig>,
67 pub trainable: bool,
69}
70
71#[derive(Debug, Clone)]
73pub enum LayerConfig {
74 Int(i64),
76 Float(f64),
78 String(String),
80 Bool(bool),
82 List(Vec<LayerConfig>),
84}
85
86pub trait ParameterOptimizer<A: Float, D: Dimension> {
88 fn register_parameter(
90 &mut self,
91 paramid: ParamId,
92 parameter: &Array<A, D>,
93 metadata: ParameterMetadata,
94 ) -> Result<()>;
95
96 fn step(
98 &mut self,
99 gradients: HashMap<ParamId, Array<A, D>>,
100 parameters: &mut HashMap<ParamId, Array<A, D>>,
101 ) -> Result<()>;
102
103 fn get_learning_rate(&self, paramid: &ParamId) -> Option<A>;
105
106 fn set_learning_rate(&mut self, paramid: &ParamId, lr: A) -> Result<()>;
108
109 fn get_parameter_state(&self, paramid: &ParamId) -> Option<&HashMap<String, Array<A, D>>>;
111
112 fn reset_state(&mut self);
114
115 fn registered_parameters(&self) -> Vec<ParamId>;
117}
118
119#[derive(Debug)]
121pub struct ParameterManager<A: Float, D: Dimension> {
122 parameters: HashMap<ParamId, ParameterMetadata>,
124 optimizer_states: HashMap<ParamId, HashMap<String, Array<A, D>>>,
126 layer_architectures: HashMap<LayerId, LayerArchitecture>,
128 sharing_groups: HashMap<String, Vec<ParamId>>,
130 layer_rules: HashMap<LayerId, LayerOptimizationRule<A>>,
132 global_config: OptimizationConfig<A>,
134 lazy_mode: bool,
136 pending_registrations: Vec<(ParamId, ParameterMetadata)>,
138}
139
140#[derive(Debug, Clone)]
142pub struct LayerOptimizationRule<A: Float> {
143 pub lr_multiplier: A,
145 pub weight_decay_multiplier: A,
147 pub frozen: bool,
149 pub custom_settings: HashMap<String, LayerConfig>,
151}
152
153#[derive(Debug, Clone)]
155pub struct OptimizationConfig<A: Float> {
156 pub base_learning_rate: A,
158 pub weight_decay: A,
160 pub gradient_clip: Option<A>,
162 pub mixed_precision: bool,
164 pub architecture_optimizations: HashMap<String, bool>,
166}
167
168impl<A: Float + ScalarOperand + Debug + Send + Sync, D: Dimension + Send + Sync>
169 ParameterManager<A, D>
170{
171 pub fn new(config: OptimizationConfig<A>) -> Self {
173 Self {
174 parameters: HashMap::new(),
175 optimizer_states: HashMap::new(),
176 layer_architectures: HashMap::new(),
177 sharing_groups: HashMap::new(),
178 layer_rules: HashMap::new(),
179 global_config: config,
180 lazy_mode: false,
181 pending_registrations: Vec::new(),
182 }
183 }
184
185 pub fn enable_lazy_mode(&mut self) {
187 self.lazy_mode = true;
188 }
189
190 pub fn disable_lazy_mode(&mut self) -> Result<()> {
192 self.lazy_mode = false;
193
194 let pending = std::mem::take(&mut self.pending_registrations);
196 for (paramid, metadata) in pending {
197 self.register_parameter_impl(paramid, metadata)?;
198 }
199
200 Ok(())
201 }
202
203 pub fn register_layer(&mut self, layerid: LayerId, architecture: LayerArchitecture) {
205 self.layer_architectures.insert(layerid, architecture);
206 }
207
208 pub fn set_layer_rule(&mut self, layerid: LayerId, rule: LayerOptimizationRule<A>) {
210 self.layer_rules.insert(layerid, rule);
211 }
212
213 pub fn register_parameter(
215 &mut self,
216 paramid: ParamId,
217 metadata: ParameterMetadata,
218 ) -> Result<()> {
219 if self.lazy_mode {
220 self.pending_registrations.push((paramid, metadata));
221 Ok(())
222 } else {
223 self.register_parameter_impl(paramid, metadata)
224 }
225 }
226
227 fn register_parameter_impl(
229 &mut self,
230 paramid: ParamId,
231 metadata: ParameterMetadata,
232 ) -> Result<()> {
233 if let Some(sharing_group) = &metadata.sharing_group {
235 self.sharing_groups
236 .entry(sharing_group.clone())
237 .or_default()
238 .push(paramid.clone());
239 }
240
241 self.optimizer_states
243 .insert(paramid.clone(), HashMap::new());
244
245 self.parameters.insert(paramid, metadata);
247
248 Ok(())
249 }
250
251 pub fn get_effective_learning_rate(&self, paramid: &ParamId) -> A {
253 let base_lr = self.global_config.base_learning_rate;
254
255 if let Some(metadata) = self.parameters.get(paramid) {
256 if let Some(rule) = self.layer_rules.get(&metadata.layername) {
257 return base_lr * rule.lr_multiplier;
258 }
259 }
260
261 base_lr
262 }
263
264 pub fn get_effective_weight_decay(&self, paramid: &ParamId) -> A {
266 let base_decay = self.global_config.weight_decay;
267
268 if let Some(metadata) = self.parameters.get(paramid) {
269 if let Some(rule) = self.layer_rules.get(&metadata.layername) {
270 return base_decay * rule.weight_decay_multiplier;
271 }
272 }
273
274 base_decay
275 }
276
277 pub fn is_parameter_frozen(&self, paramid: &ParamId) -> bool {
279 if let Some(metadata) = self.parameters.get(paramid) {
280 if let Some(rule) = self.layer_rules.get(&metadata.layername) {
281 return rule.frozen;
282 }
283 }
284 false
285 }
286
287 pub fn get_sharing_group(&self, groupname: &str) -> Option<&[ParamId]> {
289 self.sharing_groups.get(groupname).map(|v| v.as_slice())
290 }
291
292 pub fn get_all_parameters(&self) -> &HashMap<ParamId, ParameterMetadata> {
294 &self.parameters
295 }
296
297 pub fn get_layer_architecture(&self, layerid: &LayerId) -> Option<&LayerArchitecture> {
299 self.layer_architectures.get(layerid)
300 }
301
302 pub fn get_parameter_metadata(&self, paramid: &ParamId) -> Option<&ParameterMetadata> {
304 self.parameters.get(paramid)
305 }
306
307 pub fn update_config(&mut self, config: OptimizationConfig<A>) {
309 self.global_config = config;
310 }
311
312 pub fn get_optimizer_state(&self, paramid: &ParamId) -> Option<&HashMap<String, Array<A, D>>> {
314 self.optimizer_states.get(paramid)
315 }
316
317 pub fn get_optimizer_state_mut(
319 &mut self,
320 paramid: &ParamId,
321 ) -> Option<&mut HashMap<String, Array<A, D>>> {
322 self.optimizer_states.get_mut(paramid)
323 }
324
325 pub fn init_optimizer_state(
327 &mut self,
328 paramid: &ParamId,
329 state_name: &str,
330 state: Array<A, D>,
331 ) -> Result<()> {
332 if let Some(states) = self.optimizer_states.get_mut(paramid) {
333 states.insert(state_name.to_string(), state);
334 Ok(())
335 } else {
336 Err(OptimError::InvalidConfig(format!(
337 "Parameter {} not registered",
338 paramid
339 )))
340 }
341 }
342
343 pub fn reset_optimizer_states(&mut self) {
345 for states in self.optimizer_states.values_mut() {
346 states.clear();
347 }
348 }
349
350 pub fn get_parameters_by_layer(&self, layerid: &LayerId) -> Vec<&ParamId> {
352 self.parameters
353 .iter()
354 .filter(|(_, metadata)| &metadata.layername == layerid)
355 .map(|(paramid, _)| paramid)
356 .collect()
357 }
358
359 pub fn get_parameters_by_type(&self, paramtype: ParameterType) -> Vec<&ParamId> {
361 self.parameters
362 .iter()
363 .filter(|(_, metadata)| metadata.paramtype == paramtype)
364 .map(|(paramid, _)| paramid)
365 .collect()
366 }
367
368 pub fn get_trainable_parameters(&self) -> Vec<&ParamId> {
370 self.parameters
371 .iter()
372 .filter(|(paramid, metadata)| {
373 metadata.requires_grad && !self.is_parameter_frozen(paramid)
374 })
375 .map(|(paramid, _)| paramid)
376 .collect()
377 }
378}
379
380impl<A: Float + Send + Sync> Default for OptimizationConfig<A> {
381 fn default() -> Self {
382 Self {
383 base_learning_rate: scalar_or(0.001, A::zero()),
384 weight_decay: A::zero(),
385 gradient_clip: None,
386 mixed_precision: false,
387 architecture_optimizations: HashMap::new(),
388 }
389 }
390}
391
392impl<A: Float + Send + Sync> Default for LayerOptimizationRule<A> {
393 fn default() -> Self {
394 Self {
395 lr_multiplier: A::one(),
396 weight_decay_multiplier: A::one(),
397 frozen: false,
398 custom_settings: HashMap::new(),
399 }
400 }
401}
402
403pub mod forward_backward {
405 use super::*;
406
407 pub trait ForwardHook<A: Float, D: Dimension> {
409 fn pre_forward(&mut self, layerid: &LayerId, inputs: &[Array<A, D>]) -> Result<()>;
411
412 fn post_forward(&mut self, layerid: &LayerId, outputs: &[Array<A, D>]) -> Result<()>;
414 }
415
416 pub trait BackwardHook<A: Float, D: Dimension> {
418 fn pre_backward(&mut self, layerid: &LayerId, gradoutputs: &[Array<A, D>]) -> Result<()>;
420
421 fn post_backward(&mut self, layerid: &LayerId, gradinputs: &[Array<A, D>]) -> Result<()>;
423 }
424
425 pub struct NeuralIntegration<A: Float, D: Dimension> {
427 param_manager: ParameterManager<A, D>,
429 forward_hooks: HashMap<LayerId, Box<dyn ForwardHook<A, D>>>,
431 backward_hooks: HashMap<LayerId, Box<dyn BackwardHook<A, D>>>,
433 gradient_accumulation: bool,
435 accumulated_gradients: HashMap<ParamId, Array<A, D>>,
437 accumulation_count: usize,
439 }
440
441 impl<
442 A: Float
443 + ScalarOperand
444 + Debug
445 + 'static
446 + scirs2_core::numeric::FromPrimitive
447 + std::iter::Sum
448 + Send
449 + Sync,
450 D: Dimension + 'static,
451 > NeuralIntegration<A, D>
452 {
453 pub fn new(config: OptimizationConfig<A>) -> Self {
455 Self {
456 param_manager: ParameterManager::new(config),
457 forward_hooks: HashMap::new(),
458 backward_hooks: HashMap::new(),
459 gradient_accumulation: false,
460 accumulated_gradients: HashMap::new(),
461 accumulation_count: 0,
462 }
463 }
464
465 pub fn register_forward_hook<H>(&mut self, layerid: LayerId, hook: H)
467 where
468 H: ForwardHook<A, D> + 'static,
469 {
470 self.forward_hooks.insert(layerid, Box::new(hook));
471 }
472
473 pub fn register_backward_hook<H>(&mut self, layerid: LayerId, hook: H)
475 where
476 H: BackwardHook<A, D> + 'static,
477 {
478 self.backward_hooks.insert(layerid, Box::new(hook));
479 }
480
481 pub fn enable_gradient_accumulation(&mut self) {
483 self.gradient_accumulation = true;
484 }
485
486 pub fn disable_gradient_accumulation(&mut self) -> HashMap<ParamId, Array<A, D>> {
488 self.gradient_accumulation = false;
489 let result = std::mem::take(&mut self.accumulated_gradients);
490 self.accumulation_count = 0;
491 result
492 }
493
494 pub fn forward_pass(
496 &mut self,
497 layerid: &LayerId,
498 inputs: &[Array<A, D>],
499 ) -> Result<Vec<Array<A, D>>> {
500 if let Some(hook) = self.forward_hooks.get_mut(layerid) {
502 hook.pre_forward(layerid, inputs)?;
503 }
504
505 let layer_arch = self
507 .param_manager
508 .get_layer_architecture(layerid)
509 .ok_or_else(|| {
510 OptimError::InvalidConfig(format!("Layer {} not registered", layerid))
511 })?
512 .clone();
513
514 let outputs = match layer_arch.layer_type.as_str() {
516 "linear" | "dense" | "fc" => {
517 self.compute_linear_forward(layerid, inputs)?
519 }
520 "conv" | "conv2d" => {
521 self.compute_conv_forward(layerid, inputs)?
523 }
524 "activation" => {
525 self.compute_activation_forward(layerid, inputs, &layer_arch)?
527 }
528 "normalization" | "batchnorm" | "layernorm" => {
529 self.compute_normalization_forward(layerid, inputs)?
531 }
532 "dropout" => {
533 self.compute_dropout_forward(layerid, inputs, &layer_arch)?
535 }
536 "pooling" | "maxpool" | "avgpool" => {
537 self.compute_pooling_forward(layerid, inputs, &layer_arch)?
539 }
540 _ => {
541 inputs.to_vec()
543 }
544 };
545
546 if let Some(hook) = self.forward_hooks.get_mut(layerid) {
548 hook.post_forward(layerid, &outputs)?;
549 }
550
551 Ok(outputs)
552 }
553
554 fn compute_linear_forward(
564 &self,
565 _layer_id: &LayerId,
566 _inputs: &[Array<A, D>],
567 ) -> Result<Vec<Array<A, D>>> {
568 Err(OptimError::UnsupportedOperation(
569 "linear layer forward pass is not implemented: this module tracks \
570 optimizer state, not weight values, so it cannot compute input @ Wáµ€ + b"
571 .to_string(),
572 ))
573 }
574
575 fn compute_conv_forward(
581 &self,
582 _layer_id: &LayerId,
583 _inputs: &[Array<A, D>],
584 ) -> Result<Vec<Array<A, D>>> {
585 Err(OptimError::UnsupportedOperation(
586 "convolution forward pass is not implemented: no convolution kernels \
587 are stored in this module"
588 .to_string(),
589 ))
590 }
591
592 fn compute_activation_forward(
594 &self,
595 _layer_id: &LayerId,
596 inputs: &[Array<A, D>],
597 layer_arch: &LayerArchitecture,
598 ) -> Result<Vec<Array<A, D>>> {
599 let activation_type = layer_arch
600 .config
601 .get("activation")
602 .and_then(|v| match v {
603 LayerConfig::String(s) => Some(s.as_str()),
604 _ => None,
605 })
606 .unwrap_or("relu");
607
608 let outputs: Vec<Array<A, D>> = inputs
609 .iter()
610 .map(|input| {
611 match activation_type {
612 "relu" => input.mapv(|x| if x > A::zero() { x } else { A::zero() }),
613 "sigmoid" => input.mapv(|x| A::one() / (A::one() + (-x).exp())),
614 "tanh" => input.mapv(|x| x.tanh()),
615 "leaky_relu" => {
616 let alpha = scalar_or(0.01, A::zero());
617 input.mapv(|x| if x > A::zero() { x } else { alpha * x })
618 }
619 _ => input.clone(), }
621 })
622 .collect();
623
624 Ok(outputs)
625 }
626
627 fn compute_normalization_forward(
629 &self,
630 _layer_id: &LayerId,
631 inputs: &[Array<A, D>],
632 ) -> Result<Vec<Array<A, D>>> {
633 let outputs: Vec<Array<A, D>> = inputs
635 .iter()
636 .map(|input| {
637 let mean = input.iter().copied().sum::<A>()
638 / A::from(input.len()).unwrap_or(A::zero());
639 let variance = input
640 .mapv(|x| (x - mean).powi(2))
641 .mean()
642 .unwrap_or(A::one());
643 let std_dev = variance.sqrt();
644 let epsilon = scalar_or(1e-5, A::one());
645
646 input.mapv(|x| (x - mean) / (std_dev + epsilon))
647 })
648 .collect();
649
650 Ok(outputs)
651 }
652
653 fn compute_dropout_forward(
655 &self,
656 _layer_id: &LayerId,
657 inputs: &[Array<A, D>],
658 layer_arch: &LayerArchitecture,
659 ) -> Result<Vec<Array<A, D>>> {
660 let dropout_rate = layer_arch
661 .config
662 .get("dropout_rate")
663 .and_then(|v| match v {
664 LayerConfig::Float(f) => scalar_opt(*f),
665 _ => None,
666 })
667 .unwrap_or(try_scalar::<A, _>(0.5)?);
668
669 let scale = A::one() - dropout_rate;
672 let outputs: Vec<Array<A, D>> = inputs
673 .iter()
674 .map(|input| input.mapv(|x| x * scale))
675 .collect();
676
677 Ok(outputs)
678 }
679
680 fn compute_pooling_forward(
686 &self,
687 _layer_id: &LayerId,
688 _inputs: &[Array<A, D>],
689 _layer_arch: &LayerArchitecture,
690 ) -> Result<Vec<Array<A, D>>> {
691 Err(OptimError::UnsupportedOperation(
692 "pooling forward pass is not implemented: downsampling window/stride \
693 are not modeled in this module"
694 .to_string(),
695 ))
696 }
697
698 pub fn backward_pass(
700 &mut self,
701 layerid: &LayerId,
702 grad_outputs: &[Array<A, D>],
703 ) -> Result<Vec<Array<A, D>>> {
704 if let Some(hook) = self.backward_hooks.get_mut(layerid) {
706 hook.pre_backward(layerid, grad_outputs)?;
707 }
708
709 let layer_arch = self
711 .param_manager
712 .get_layer_architecture(layerid)
713 .ok_or_else(|| {
714 OptimError::InvalidConfig(format!("Layer {} not registered", layerid))
715 })?
716 .clone();
717
718 let grad_inputs = match layer_arch.layer_type.as_str() {
720 "linear" | "dense" | "fc" => {
721 self.compute_linear_backward(layerid, grad_outputs)?
723 }
724 "conv" | "conv2d" => {
725 self.compute_conv_backward(layerid, grad_outputs)?
727 }
728 "activation" => {
729 self.compute_activation_backward(layerid, grad_outputs, &layer_arch)?
731 }
732 "normalization" | "batchnorm" | "layernorm" => {
733 self.compute_normalization_backward(layerid, grad_outputs)?
735 }
736 "dropout" => {
737 self.compute_dropout_backward(layerid, grad_outputs, &layer_arch)?
739 }
740 "pooling" | "maxpool" | "avgpool" => {
741 self.compute_pooling_backward(layerid, grad_outputs, &layer_arch)?
743 }
744 _ => {
745 grad_outputs.to_vec()
747 }
748 };
749
750 let clipped_grads =
752 if let Some(clipvalue) = self.param_manager.global_config.gradient_clip {
753 self.apply_gradient_clipping(grad_inputs, clipvalue)?
754 } else {
755 grad_inputs
756 };
757
758 if let Some(hook) = self.backward_hooks.get_mut(layerid) {
760 hook.post_backward(layerid, &clipped_grads)?;
761 }
762
763 Ok(clipped_grads)
764 }
765
766 fn compute_linear_backward(
775 &mut self,
776 _layer_id: &LayerId,
777 _grad_outputs: &[Array<A, D>],
778 ) -> Result<Vec<Array<A, D>>> {
779 Err(OptimError::UnsupportedOperation(
780 "linear layer backward pass is not implemented: computing grad_output @ W \
781 requires stored weight values this module does not hold"
782 .to_string(),
783 ))
784 }
785
786 fn compute_conv_backward(
791 &self,
792 _layer_id: &LayerId,
793 _grad_outputs: &[Array<A, D>],
794 ) -> Result<Vec<Array<A, D>>> {
795 Err(OptimError::UnsupportedOperation(
796 "convolution backward pass is not implemented: no convolution kernels \
797 are stored in this module"
798 .to_string(),
799 ))
800 }
801
802 fn compute_activation_backward(
804 &self,
805 _layer_id: &LayerId,
806 grad_outputs: &[Array<A, D>],
807 layer_arch: &LayerArchitecture,
808 ) -> Result<Vec<Array<A, D>>> {
809 let activation_type = layer_arch
810 .config
811 .get("activation")
812 .and_then(|v| match v {
813 LayerConfig::String(s) => Some(s.as_str()),
814 _ => None,
815 })
816 .unwrap_or("relu");
817
818 let grad_inputs: Vec<Array<A, D>> = grad_outputs
820 .iter()
821 .map(|grad| {
822 match activation_type {
823 "relu" => {
824 grad.mapv(|g| if g > A::zero() { g } else { A::zero() })
827 }
828 "sigmoid" => {
829 let factor = scalar_or(0.25, A::one()); grad.mapv(|g| g * factor)
833 }
834 "tanh" => {
835 let factor = scalar_or(0.5, A::one());
838 grad.mapv(|g| g * factor)
839 }
840 "leaky_relu" => {
841 let alpha = scalar_or(0.01, A::zero());
842 grad.mapv(|g| if g > A::zero() { g } else { alpha * g })
843 }
844 _ => grad.clone(), }
846 })
847 .collect();
848
849 Ok(grad_inputs)
850 }
851
852 fn compute_normalization_backward(
854 &self,
855 _layer_id: &LayerId,
856 grad_outputs: &[Array<A, D>],
857 ) -> Result<Vec<Array<A, D>>> {
858 let scale_factor = try_scalar::<A, _>(0.9)?;
861 let grad_inputs: Vec<Array<A, D>> = grad_outputs
862 .iter()
863 .map(|grad| grad.mapv(|g| g * scale_factor))
864 .collect();
865
866 Ok(grad_inputs)
867 }
868
869 fn compute_dropout_backward(
871 &self,
872 _layer_id: &LayerId,
873 grad_outputs: &[Array<A, D>],
874 layer_arch: &LayerArchitecture,
875 ) -> Result<Vec<Array<A, D>>> {
876 let dropout_rate = layer_arch
877 .config
878 .get("dropout_rate")
879 .and_then(|v| match v {
880 LayerConfig::Float(f) => scalar_opt(*f),
881 _ => None,
882 })
883 .unwrap_or(try_scalar::<A, _>(0.5)?);
884
885 let scale = A::one() - dropout_rate;
887 let grad_inputs: Vec<Array<A, D>> = grad_outputs
888 .iter()
889 .map(|grad| grad.mapv(|g| g * scale))
890 .collect();
891
892 Ok(grad_inputs)
893 }
894
895 fn compute_pooling_backward(
901 &self,
902 _layer_id: &LayerId,
903 _grad_outputs: &[Array<A, D>],
904 _layer_arch: &LayerArchitecture,
905 ) -> Result<Vec<Array<A, D>>> {
906 Err(OptimError::UnsupportedOperation(
907 "pooling backward pass is not implemented: pooling indices are not \
908 modeled in this module"
909 .to_string(),
910 ))
911 }
912
913 fn apply_gradient_clipping(
915 &self,
916 gradients: Vec<Array<A, D>>,
917 clipvalue: A,
918 ) -> Result<Vec<Array<A, D>>> {
919 let clipped: Vec<Array<A, D>> = gradients
920 .into_iter()
921 .map(|grad| {
922 let norm = grad.mapv(|x| x * x).sum().sqrt();
924
925 if norm > clipvalue {
926 let scale = clipvalue / norm;
928 grad.mapv(|x| x * scale)
929 } else {
930 grad
931 }
932 })
933 .collect();
934
935 Ok(clipped)
936 }
937
938 pub fn accumulate_gradients(
940 &mut self,
941 gradients: HashMap<ParamId, Array<A, D>>,
942 ) -> Result<()> {
943 if !self.gradient_accumulation {
944 return Err(OptimError::InvalidConfig(
945 "Gradient accumulation not enabled".to_string(),
946 ));
947 }
948
949 self.accumulation_count += 1;
950
951 for (paramid, grad) in gradients {
952 if let Some(acc_grad) = self.accumulated_gradients.get_mut(¶mid) {
953 *acc_grad = acc_grad.clone() + grad;
955 } else {
956 self.accumulated_gradients.insert(paramid, grad);
958 }
959 }
960
961 Ok(())
962 }
963
964 pub fn parameter_manager(&self) -> &ParameterManager<A, D> {
966 &self.param_manager
967 }
968
969 pub fn parameter_manager_mut(&mut self) -> &mut ParameterManager<A, D> {
971 &mut self.param_manager
972 }
973
974 pub fn accumulation_count(&self) -> usize {
976 self.accumulation_count
977 }
978 }
979}
980
981pub mod architecture_aware {
983 use super::*;
984
985 #[derive(Debug, Clone)]
987 pub enum ArchitectureStrategy {
988 Transformer {
990 component_specific_lr: bool,
992 layer_wise_decay: bool,
994 attention_warmup: usize,
996 },
997 ConvolutionalNet {
999 layer_type_lr: bool,
1001 depth_scaling: bool,
1003 bn_special_handling: bool,
1005 },
1006 RecurrentNet {
1008 rnn_gradient_clip: Option<f64>,
1010 weight_type_lr: bool,
1012 },
1013 Custom {
1015 rules: HashMap<String, LayerConfig>,
1017 },
1018 }
1019
1020 #[derive(Debug)]
1022 pub struct ArchitectureAwareOptimizer<A: Float, D: Dimension> {
1023 param_manager: ParameterManager<A, D>,
1025 strategy: ArchitectureStrategy,
1027 step_count: usize,
1029 }
1030
1031 impl<A: Float + ScalarOperand + Debug + Send + Sync, D: Dimension + Send + Sync>
1032 ArchitectureAwareOptimizer<A, D>
1033 {
1034 pub fn new(config: OptimizationConfig<A>, strategy: ArchitectureStrategy) -> Self {
1036 Self {
1037 param_manager: ParameterManager::new(config),
1038 strategy,
1039 step_count: 0,
1040 }
1041 }
1042
1043 pub fn apply_architecture_optimizations(&mut self) -> Result<()> {
1045 let strategy = self.strategy.clone();
1047 match strategy {
1048 ArchitectureStrategy::Transformer {
1049 component_specific_lr,
1050 layer_wise_decay,
1051 attention_warmup,
1052 } => {
1053 self.apply_transformer_optimizations(
1054 component_specific_lr,
1055 layer_wise_decay,
1056 attention_warmup,
1057 )?;
1058 }
1059 ArchitectureStrategy::ConvolutionalNet {
1060 layer_type_lr,
1061 depth_scaling,
1062 bn_special_handling,
1063 } => {
1064 self.apply_cnn_optimizations(
1065 layer_type_lr,
1066 depth_scaling,
1067 bn_special_handling,
1068 )?;
1069 }
1070 ArchitectureStrategy::RecurrentNet {
1071 rnn_gradient_clip,
1072 weight_type_lr,
1073 } => {
1074 self.apply_rnn_optimizations(rnn_gradient_clip, weight_type_lr)?;
1075 }
1076 ArchitectureStrategy::Custom { rules } => {
1077 self.apply_custom_optimizations(&rules)?;
1078 }
1079 }
1080 Ok(())
1081 }
1082
1083 fn apply_transformer_optimizations(
1085 &mut self,
1086 component_specific_lr: bool,
1087 layer_wise_decay: bool,
1088 attention_warmup: usize,
1089 ) -> Result<()> {
1090 if component_specific_lr {
1091 self.set_component_learning_rates()?;
1093 }
1094
1095 if layer_wise_decay {
1096 self.apply_layer_wise_decay()?;
1098 }
1099
1100 if attention_warmup > 0 && self.step_count < attention_warmup {
1101 self.apply_attention_warmup(attention_warmup)?;
1103 }
1104
1105 Ok(())
1106 }
1107
1108 fn apply_cnn_optimizations(
1110 &mut self,
1111 layer_type_lr: bool,
1112 depth_scaling: bool,
1113 bn_special_handling: bool,
1114 ) -> Result<()> {
1115 if layer_type_lr {
1116 self.set_layer_type_learning_rates()?;
1118 }
1119
1120 if depth_scaling {
1121 self.apply_depth_scaling()?;
1123 }
1124
1125 if bn_special_handling {
1126 self.apply_bn_optimizations()?;
1128 }
1129
1130 Ok(())
1131 }
1132
1133 fn apply_rnn_optimizations(
1135 &mut self,
1136 rnn_gradient_clip: Option<f64>,
1137 weight_type_lr: bool,
1138 ) -> Result<()> {
1139 if let Some(clipvalue) = rnn_gradient_clip {
1140 let clip = A::from(clipvalue).ok_or_else(|| {
1144 OptimError::InvalidConfig(format!(
1145 "RNN gradient clip value {clipvalue} is not representable \
1146 in the optimizer's float type"
1147 ))
1148 })?;
1149 self.apply_rnn_gradient_clipping(clip)?;
1150 }
1151
1152 if weight_type_lr {
1153 self.set_weight_type_learning_rates()?;
1155 }
1156
1157 Ok(())
1158 }
1159
1160 fn apply_custom_optimizations(
1162 &mut self,
1163 rules: &HashMap<String, LayerConfig>,
1164 ) -> Result<()> {
1165 let rule_entries: Vec<(String, LayerConfig)> = rules
1167 .iter()
1168 .map(|(name, config)| (name.clone(), config.clone()))
1169 .collect();
1170
1171 for (rule_name, config) in rule_entries {
1172 self.apply_custom_rule(&rule_name, &config)?;
1173 }
1174 Ok(())
1175 }
1176
1177 fn set_component_learning_rates(&mut self) -> Result<()> {
1179 let layer_rules: Vec<(LayerId, LayerOptimizationRule<A>)> = self
1181 .param_manager
1182 .get_all_parameters()
1183 .values()
1184 .map(|metadata| {
1185 let mut rule = LayerOptimizationRule::default();
1186
1187 if metadata.tags.contains(&"attention".to_string()) {
1189 rule.lr_multiplier = scalar_or(1.2, A::one());
1190 } else if metadata.tags.contains(&"ffn".to_string()) {
1192 rule.lr_multiplier = scalar_or(1.0, A::one());
1193 } else if metadata.tags.contains(&"normalization".to_string()) {
1195 rule.lr_multiplier = scalar_or(0.8, A::one());
1196 }
1198
1199 (metadata.layername.clone(), rule)
1200 })
1201 .collect();
1202
1203 for (layername, rule) in layer_rules {
1205 self.param_manager.set_layer_rule(layername, rule);
1206 }
1207 Ok(())
1208 }
1209
1210 fn apply_layer_wise_decay(&mut self) -> Result<()> {
1212 for (layerid, _) in self.param_manager.layer_architectures.clone() {
1214 if let Some(layer_num) = self.extract_layer_number(&layerid) {
1215 let decay_factor = try_scalar::<A, _>(0.95_f64.powi(layer_num as i32))?;
1216 let mut rule = self
1217 .param_manager
1218 .layer_rules
1219 .get(&layerid)
1220 .cloned()
1221 .unwrap_or_default();
1222 rule.lr_multiplier = rule.lr_multiplier * decay_factor;
1223 self.param_manager.set_layer_rule(layerid, rule);
1224 }
1225 }
1226 Ok(())
1227 }
1228
1229 fn apply_attention_warmup(&mut self, warmupsteps: usize) -> Result<()> {
1231 let warmup_factor = try_scalar::<A, _>(self.step_count as f64 / warmupsteps as f64)?;
1232
1233 let attention_layers: Vec<LayerId> = self
1235 .param_manager
1236 .get_all_parameters()
1237 .values()
1238 .filter_map(|metadata| {
1239 if metadata.tags.contains(&"attention".to_string()) {
1240 Some(metadata.layername.clone())
1241 } else {
1242 None
1243 }
1244 })
1245 .collect();
1246
1247 for layername in attention_layers {
1249 let mut rule = self
1250 .param_manager
1251 .layer_rules
1252 .get(&layername)
1253 .cloned()
1254 .unwrap_or_default();
1255 rule.lr_multiplier = rule.lr_multiplier * warmup_factor;
1256 self.param_manager.set_layer_rule(layername, rule);
1257 }
1258 Ok(())
1259 }
1260
1261 fn set_layer_type_learning_rates(&mut self) -> Result<()> {
1263 for (layerid, architecture) in self.param_manager.layer_architectures.clone() {
1264 let mut rule = LayerOptimizationRule::default();
1265
1266 match architecture.layer_type.as_str() {
1267 "conv" | "conv2d" | "conv3d" => {
1268 rule.lr_multiplier = scalar_or(1.0, A::one());
1269 }
1271 "linear" | "dense" | "fc" => {
1272 rule.lr_multiplier = scalar_or(0.8, A::one());
1273 }
1275 _ => {
1276 rule.lr_multiplier = scalar_or(1.0, A::one());
1277 }
1279 }
1280
1281 self.param_manager.set_layer_rule(layerid, rule);
1282 }
1283 Ok(())
1284 }
1285
1286 fn apply_depth_scaling(&mut self) -> Result<()> {
1288 let total_layers = self.param_manager.layer_architectures.len();
1290
1291 for (i, (layerid, _)) in self
1292 .param_manager
1293 .layer_architectures
1294 .clone()
1295 .iter()
1296 .enumerate()
1297 {
1298 let depth_factor =
1299 try_scalar::<A, _>(1.0 - 0.1 * (i as f64 / total_layers as f64))?;
1300 let mut rule = self
1301 .param_manager
1302 .layer_rules
1303 .get(layerid)
1304 .cloned()
1305 .unwrap_or_default();
1306 rule.lr_multiplier = rule.lr_multiplier * depth_factor;
1307 self.param_manager.set_layer_rule(layerid.clone(), rule);
1308 }
1309 Ok(())
1310 }
1311
1312 fn apply_bn_optimizations(&mut self) -> Result<()> {
1314 let norm_layers: Vec<LayerId> = self
1316 .param_manager
1317 .get_all_parameters()
1318 .values()
1319 .filter_map(|metadata| {
1320 if metadata.paramtype == ParameterType::Normalization {
1321 Some(metadata.layername.clone())
1322 } else {
1323 None
1324 }
1325 })
1326 .collect();
1327
1328 for layername in norm_layers {
1330 let mut rule = self
1331 .param_manager
1332 .layer_rules
1333 .get(&layername)
1334 .cloned()
1335 .unwrap_or_default();
1336 rule.lr_multiplier = try_scalar::<A, _>(2.0)?;
1338 rule.weight_decay_multiplier = A::zero();
1339 self.param_manager.set_layer_rule(layername, rule);
1340 }
1341 Ok(())
1342 }
1343
1344 fn apply_rnn_gradient_clipping(&mut self, clipvalue: A) -> Result<()> {
1346 self.param_manager.global_config.gradient_clip = Some(clipvalue);
1349 Ok(())
1350 }
1351
1352 fn set_weight_type_learning_rates(&mut self) -> Result<()> {
1354 let layer_rules: Vec<(LayerId, LayerOptimizationRule<A>)> = self
1356 .param_manager
1357 .get_all_parameters()
1358 .values()
1359 .map(|metadata| {
1360 let mut rule = LayerOptimizationRule::default();
1361
1362 if metadata.tags.contains(&"recurrent".to_string()) {
1363 rule.lr_multiplier = scalar_or(0.5, A::one());
1364 } else if metadata.tags.contains(&"linear".to_string()) {
1366 rule.lr_multiplier = scalar_or(1.0, A::one());
1367 }
1369
1370 (metadata.layername.clone(), rule)
1371 })
1372 .collect();
1373
1374 for (layername, rule) in layer_rules {
1376 self.param_manager.set_layer_rule(layername, rule);
1377 }
1378 Ok(())
1379 }
1380
1381 fn apply_custom_rule(&mut self, rule_name: &str, config: &LayerConfig) -> Result<()> {
1399 let (target_layer, setting) = match rule_name.rsplit_once('.') {
1400 Some((layer, setting)) => (Some(layer.to_string()), setting),
1401 None => (None, rule_name),
1402 };
1403
1404 let layers: Vec<LayerId> = {
1407 let all: Vec<LayerId> = self
1408 .param_manager
1409 .get_all_parameters()
1410 .values()
1411 .map(|metadata| metadata.layername.clone())
1412 .collect();
1413 match &target_layer {
1414 Some(name) => {
1415 if !all.iter().any(|layer| layer == name) {
1416 return Err(OptimError::InvalidConfig(format!(
1417 "custom rule `{rule_name}` targets layer `{name}`, \
1418 which has no registered parameters"
1419 )));
1420 }
1421 vec![name.clone()]
1422 }
1423 None => {
1424 let mut unique = all;
1425 unique.sort();
1426 unique.dedup();
1427 unique
1428 }
1429 }
1430 };
1431
1432 let as_float = |config: &LayerConfig| -> Result<A> {
1433 let raw = match config {
1434 LayerConfig::Float(value) => *value,
1435 LayerConfig::Int(value) => *value as f64,
1436 other => {
1437 return Err(OptimError::InvalidConfig(format!(
1438 "custom rule `{rule_name}` expects a numeric value, got {other:?}"
1439 )))
1440 }
1441 };
1442 A::from(raw).ok_or_else(|| {
1443 OptimError::InvalidConfig(format!(
1444 "custom rule `{rule_name}` value {raw} is not representable \
1445 in the optimizer's float type"
1446 ))
1447 })
1448 };
1449
1450 for layer in layers {
1451 let mut rule = self
1452 .param_manager
1453 .layer_rules
1454 .get(&layer)
1455 .cloned()
1456 .unwrap_or_default();
1457 match setting {
1458 "lr_multiplier" => rule.lr_multiplier = as_float(config)?,
1459 "weight_decay_multiplier" => rule.weight_decay_multiplier = as_float(config)?,
1460 "frozen" => match config {
1461 LayerConfig::Bool(value) => rule.frozen = *value,
1462 other => {
1463 return Err(OptimError::InvalidConfig(format!(
1464 "custom rule `{rule_name}` expects a boolean value, got {other:?}"
1465 )))
1466 }
1467 },
1468 unknown => {
1469 return Err(OptimError::InvalidConfig(format!(
1470 "unknown custom optimization rule `{unknown}`; supported \
1471 settings are `lr_multiplier`, `weight_decay_multiplier` \
1472 and `frozen`, optionally prefixed with `<layer>.`"
1473 )))
1474 }
1475 }
1476 rule.custom_settings
1477 .insert(rule_name.to_string(), config.clone());
1478 self.param_manager.set_layer_rule(layer, rule);
1479 }
1480
1481 Ok(())
1482 }
1483
1484 fn extract_layer_number(&self, layername: &str) -> Option<usize> {
1486 layername.split('_').next_back()?.parse().ok()
1487 }
1488
1489 pub fn step(&mut self) -> Result<()> {
1491 self.step_count += 1;
1492 self.apply_architecture_optimizations()
1493 }
1494
1495 pub fn parameter_manager(&self) -> &ParameterManager<A, D> {
1497 &self.param_manager
1498 }
1499
1500 pub fn parameter_manager_mut(&mut self) -> &mut ParameterManager<A, D> {
1502 &mut self.param_manager
1503 }
1504 }
1505}
1506
1507#[cfg(test)]
1508mod tests {
1509 use super::*;
1510 use approx::assert_relative_eq;
1511
1512 #[test]
1513 fn test_parameter_manager_basic() {
1514 let config = OptimizationConfig::default();
1515 let mut manager = ParameterManager::<f64, scirs2_core::ndarray::Ix1>::new(config);
1516
1517 let metadata = ParameterMetadata {
1518 layername: "layer1".to_string(),
1519 param_name: "weight".to_string(),
1520 shape: vec![10, 5],
1521 requires_grad: true,
1522 paramtype: ParameterType::Weight,
1523 sharing_group: None,
1524 tags: vec!["dense".to_string()],
1525 };
1526
1527 manager
1528 .register_parameter("param1".to_string(), metadata)
1529 .expect("unwrap failed");
1530
1531 assert!(manager
1532 .get_parameter_metadata(&"param1".to_string())
1533 .is_some());
1534 assert_eq!(manager.get_all_parameters().len(), 1);
1535 assert!(!manager.is_parameter_frozen(&"param1".to_string()));
1536 }
1537
1538 #[test]
1539 fn test_lazy_registration() {
1540 let config = OptimizationConfig::default();
1541 let mut manager = ParameterManager::<f64, scirs2_core::ndarray::Ix1>::new(config);
1542
1543 manager.enable_lazy_mode();
1544
1545 let metadata = ParameterMetadata {
1546 layername: "layer1".to_string(),
1547 param_name: "weight".to_string(),
1548 shape: vec![10, 5],
1549 requires_grad: true,
1550 paramtype: ParameterType::Weight,
1551 sharing_group: None,
1552 tags: vec![],
1553 };
1554
1555 manager
1556 .register_parameter("param1".to_string(), metadata)
1557 .expect("unwrap failed");
1558
1559 assert_eq!(manager.get_all_parameters().len(), 0);
1561
1562 manager.disable_lazy_mode().expect("unwrap failed");
1564
1565 assert_eq!(manager.get_all_parameters().len(), 1);
1567 }
1568
1569 #[test]
1570 fn test_layer_specific_rules() {
1571 let config = OptimizationConfig {
1572 base_learning_rate: 0.01,
1573 weight_decay: 0.001,
1574 gradient_clip: None,
1575 mixed_precision: false,
1576 architecture_optimizations: HashMap::new(),
1577 };
1578 let mut manager = ParameterManager::<f64, scirs2_core::ndarray::Ix1>::new(config);
1579
1580 let rule = LayerOptimizationRule {
1581 lr_multiplier: 2.0,
1582 weight_decay_multiplier: 0.5,
1583 frozen: false,
1584 custom_settings: HashMap::new(),
1585 };
1586
1587 manager.set_layer_rule("layer1".to_string(), rule);
1588
1589 let metadata = ParameterMetadata {
1590 layername: "layer1".to_string(),
1591 param_name: "weight".to_string(),
1592 shape: vec![10, 5],
1593 requires_grad: true,
1594 paramtype: ParameterType::Weight,
1595 sharing_group: None,
1596 tags: vec![],
1597 };
1598
1599 manager
1600 .register_parameter("param1".to_string(), metadata)
1601 .expect("unwrap failed");
1602
1603 let effective_lr = manager.get_effective_learning_rate(&"param1".to_string());
1605 assert_relative_eq!(effective_lr, 0.02, epsilon = 1e-6); let effective_decay = manager.get_effective_weight_decay(&"param1".to_string());
1609 assert_relative_eq!(effective_decay, 0.0005, epsilon = 1e-6); }
1611
1612 #[test]
1613 fn test_parameter_sharing() {
1614 let config = OptimizationConfig::default();
1615 let mut manager = ParameterManager::<f64, scirs2_core::ndarray::Ix1>::new(config);
1616
1617 let metadata1 = ParameterMetadata {
1618 layername: "layer1".to_string(),
1619 param_name: "weight".to_string(),
1620 shape: vec![10, 5],
1621 requires_grad: true,
1622 paramtype: ParameterType::Weight,
1623 sharing_group: Some("shared_weights".to_string()),
1624 tags: vec![],
1625 };
1626
1627 let metadata2 = ParameterMetadata {
1628 layername: "layer2".to_string(),
1629 param_name: "weight".to_string(),
1630 shape: vec![10, 5],
1631 requires_grad: true,
1632 paramtype: ParameterType::Weight,
1633 sharing_group: Some("shared_weights".to_string()),
1634 tags: vec![],
1635 };
1636
1637 manager
1638 .register_parameter("param1".to_string(), metadata1)
1639 .expect("unwrap failed");
1640 manager
1641 .register_parameter("param2".to_string(), metadata2)
1642 .expect("unwrap failed");
1643
1644 let sharing_group = manager
1645 .get_sharing_group("shared_weights")
1646 .expect("unwrap failed");
1647 assert_eq!(sharing_group.len(), 2);
1648 assert!(sharing_group.contains(&"param1".to_string()));
1649 assert!(sharing_group.contains(&"param2".to_string()));
1650 }
1651
1652 #[test]
1653 fn test_parameter_filtering() {
1654 let config = OptimizationConfig::default();
1655 let mut manager = ParameterManager::<f64, scirs2_core::ndarray::Ix1>::new(config);
1656
1657 let weight_metadata = ParameterMetadata {
1658 layername: "layer1".to_string(),
1659 param_name: "weight".to_string(),
1660 shape: vec![10, 5],
1661 requires_grad: true,
1662 paramtype: ParameterType::Weight,
1663 sharing_group: None,
1664 tags: vec![],
1665 };
1666
1667 let bias_metadata = ParameterMetadata {
1668 layername: "layer1".to_string(),
1669 param_name: "bias".to_string(),
1670 shape: vec![5],
1671 requires_grad: true,
1672 paramtype: ParameterType::Bias,
1673 sharing_group: None,
1674 tags: vec![],
1675 };
1676
1677 manager
1678 .register_parameter("weight".to_string(), weight_metadata)
1679 .expect("unwrap failed");
1680 manager
1681 .register_parameter("bias".to_string(), bias_metadata)
1682 .expect("unwrap failed");
1683
1684 let weights = manager.get_parameters_by_type(ParameterType::Weight);
1686 assert_eq!(weights.len(), 1);
1687 assert_eq!(weights[0], &"weight".to_string());
1688
1689 let biases = manager.get_parameters_by_type(ParameterType::Bias);
1690 assert_eq!(biases.len(), 1);
1691 assert_eq!(biases[0], &"bias".to_string());
1692
1693 let layer_params = manager.get_parameters_by_layer(&"layer1".to_string());
1695 assert_eq!(layer_params.len(), 2);
1696
1697 let trainable = manager.get_trainable_parameters();
1699 assert_eq!(trainable.len(), 2);
1700 }
1701
1702 #[test]
1703 fn test_architecture_aware_transformer() {
1704 use crate::neural_integration::architecture_aware::*;
1705
1706 let config = OptimizationConfig::default();
1707 let strategy = ArchitectureStrategy::Transformer {
1708 component_specific_lr: true,
1709 layer_wise_decay: true,
1710 attention_warmup: 1000,
1711 };
1712
1713 let mut optimizer =
1714 ArchitectureAwareOptimizer::<f64, scirs2_core::ndarray::Ix1>::new(config, strategy);
1715
1716 let layer_arch = LayerArchitecture {
1718 layer_type: "transformer_block".to_string(),
1719 input_dims: vec![512],
1720 output_dims: vec![512],
1721 config: HashMap::new(),
1722 trainable: true,
1723 };
1724
1725 optimizer
1726 .parameter_manager_mut()
1727 .register_layer("layer_0".to_string(), layer_arch);
1728
1729 let attention_metadata = ParameterMetadata {
1731 layername: "layer_0".to_string(),
1732 param_name: "attention_weight".to_string(),
1733 shape: vec![512, 512],
1734 requires_grad: true,
1735 paramtype: ParameterType::Attention,
1736 sharing_group: None,
1737 tags: vec!["attention".to_string()],
1738 };
1739
1740 optimizer
1741 .parameter_manager_mut()
1742 .register_parameter("attn_param".to_string(), attention_metadata)
1743 .expect("unwrap failed");
1744
1745 optimizer
1747 .apply_architecture_optimizations()
1748 .expect("unwrap failed");
1749
1750 assert!(optimizer
1752 .parameter_manager()
1753 .get_parameter_metadata(&"attn_param".to_string())
1754 .is_some());
1755 }
1756}