1use std::collections::{HashMap, HashSet};
26
27use fandhe_ai_autodiff::nn::Module as AutodiffModule;
28use fandhe_ai_autodiff::nn::RequiresGradSnapshot;
29
30use crate::{AutodiffError, TapeRef, Tensor, Var};
31
32pub trait Module {
46 fn forward<'t>(&self, tape: TapeRef<'t>, input: &Var<'t>) -> Result<Var<'t>, AutodiffError>;
51
52 fn named_parameters(&self) -> Vec<(String, &Tensor<f32>)> {
55 Vec::new()
56 }
57
58 fn set_parameter(&mut self, name: &str, _value: Tensor<f32>) -> Result<(), AutodiffError> {
64 Err(AutodiffError::InvalidArgument(format!(
65 "Module::set_parameter: no parameter named `{name}` (default fail-safe; this \
66 Module does not override set_parameter)"
67 )))
68 }
69
70 fn state_dict(&self) -> HashMap<String, Tensor<f32>> {
72 self.named_parameters()
73 .into_iter()
74 .map(|(name, tensor)| (name, tensor.clone()))
75 .collect()
76 }
77
78 fn load_state_dict(
85 &mut self,
86 state: HashMap<String, Tensor<f32>>,
87 ) -> Result<(), AutodiffError> {
88 AutodiffModule::load_state_dict(&mut FacadeModuleAdapter(self), state)
89 }
90
91 fn set_training(&mut self, _training: bool) {}
94
95 fn training(&self) -> bool {
97 true
98 }
99
100 fn set_requires_grad(&mut self, _requires_grad: bool) -> Result<(), AutodiffError> {
128 let param_count = self.named_parameters().len();
129 if param_count == 0 {
130 Ok(())
131 } else {
132 Err(AutodiffError::InvalidArgument(format!(
133 "Module::set_requires_grad: this Module has {param_count} named parameter(s) \
134 but does not override set_requires_grad (fail-closed default; freezing would \
135 silently be a no-op)"
136 )))
137 }
138 }
139
140 fn freeze(&mut self) -> Result<(), AutodiffError> {
142 self.set_requires_grad(false)
143 }
144
145 fn requires_grad(&self) -> bool {
150 true
151 }
152
153 fn children(&self) -> Vec<(String, &dyn Module)> {
159 Vec::new()
160 }
161
162 fn named_modules(&self) -> Vec<(String, &dyn Module)> {
174 let mut out = Vec::new();
175 let root_key: NodeKey = (self as *const Self as *const (), self.type_name());
176 let mut ancestors: Vec<NodeKey> = vec![root_key];
177 let mut visited: HashSet<NodeKey> = HashSet::new();
178 for (name, child) in self.children() {
179 collect_named_modules(name, child, &mut ancestors, &mut visited, &mut out);
180 }
181 out
182 }
183
184 fn parameter_count(&self) -> usize {
188 self.named_parameters()
189 .into_iter()
190 .fold(0usize, |acc, (_, tensor)| {
191 acc.saturating_add(tensor.numel())
192 })
193 }
194
195 fn type_name(&self) -> &'static str {
200 std::any::type_name::<Self>()
201 }
202
203 fn children_mut(&mut self) -> Vec<(String, &mut dyn Module)> {
232 Vec::new()
233 }
234}
235
236pub(super) type NodeKey = (*const (), &'static str);
238
239fn collect_named_modules<'a>(
242 name: String,
243 child: &'a dyn Module,
244 ancestors: &mut Vec<NodeKey>,
245 visited: &mut HashSet<NodeKey>,
246 out: &mut Vec<(String, &'a dyn Module)>,
247) {
248 let key: NodeKey = (child as *const dyn Module as *const (), child.type_name());
249 if ancestors.contains(&key) {
250 return;
251 }
252 let is_zst = std::mem::size_of_val(child) == 0;
253 if !is_zst && !visited.insert(key) {
254 return;
255 }
256 out.push((name.clone(), child));
257 ancestors.push(key);
258 for (descendant_name, descendant) in child.children() {
259 collect_named_modules(
260 format!("{name}.{descendant_name}"),
261 descendant,
262 ancestors,
263 visited,
264 out,
265 );
266 }
267 ancestors.pop();
268}
269
270pub(crate) struct FacadeModuleAdapter<P>(pub(crate) P);
306
307impl<P> AutodiffModule for FacadeModuleAdapter<P>
308where
309 P: std::ops::DerefMut,
310 P::Target: Module,
311{
312 fn forward<'t>(
313 &self,
314 tape: &'t fandhe_ai_autodiff::Tape,
315 input: &Var<'t>,
316 ) -> Result<Var<'t>, AutodiffError> {
317 Module::forward(&*self.0, TapeRef::from_autodiff(tape), input)
318 }
319
320 fn named_parameters(&self) -> Vec<(String, &Tensor<f32>)> {
321 Module::named_parameters(&*self.0)
322 }
323
324 fn set_parameter(&mut self, name: &str, value: Tensor<f32>) -> Result<(), AutodiffError> {
325 Module::set_parameter(&mut *self.0, name, value)
326 }
327
328 fn set_training(&mut self, training: bool) {
329 Module::set_training(&mut *self.0, training);
330 }
331
332 fn training(&self) -> bool {
333 Module::training(&*self.0)
334 }
335
336 fn set_requires_grad(&mut self, requires_grad: bool) -> Result<(), AutodiffError> {
337 Module::set_requires_grad(&mut *self.0, requires_grad)
338 }
339
340 fn requires_grad(&self) -> bool {
341 Module::requires_grad(&*self.0)
342 }
343
344 fn requires_grad_snapshot(&mut self) -> Result<RequiresGradSnapshot, AutodiffError> {
351 crate::nn::container::snapshot_requires_grad(&mut *self.0)
352 .map(|snap| RequiresGradSnapshot::Opaque(Box::new(snap)))
353 }
354
355 fn restore_requires_grad_snapshot(
358 &mut self,
359 snapshot: &RequiresGradSnapshot,
360 ) -> Result<(), AutodiffError> {
361 let inner = match snapshot {
362 RequiresGradSnapshot::Opaque(any) => {
363 any.downcast_ref::<crate::nn::container::RequiresGradSnapshot>()
364 }
365 _ => None,
366 };
367 match inner {
368 Some(snap) => crate::nn::container::restore_requires_grad(&mut *self.0, snap),
369 None => Err(AutodiffError::InvalidArgument(
370 "FacadeModuleAdapter::restore_requires_grad_snapshot: snapshot was not \
371 produced by a FacadeModuleAdapter (structure changed during rollback)"
372 .to_string(),
373 )),
374 }
375 }
376
377 fn type_name(&self) -> &'static str {
378 Module::type_name(&*self.0)
379 }
380
381 fn supports_forward_host(&self) -> bool {
382 false
383 }
384
385 fn forward_host(
386 &self,
387 _ops: &dyn fandhe_ai_tensor_core::BackendOps,
388 _input: &Tensor<f32>,
389 ) -> Result<Tensor<f32>, AutodiffError> {
390 Err(AutodiffError::InvalidArgument(
391 "FacadeModuleAdapter::forward_host: facade nn::Module does not support the host \
392 (tape-free) inference path; supports_forward_host() is false and must be honored"
393 .to_string(),
394 ))
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401
402 struct TwoParam {
404 a: Tensor<f32>,
405 b: Tensor<f32>,
406 fail_b: bool,
407 }
408
409 impl Module for TwoParam {
410 fn forward<'t>(
411 &self,
412 tape: TapeRef<'t>,
413 input: &Var<'t>,
414 ) -> Result<Var<'t>, AutodiffError> {
415 let a = tape.var(&self.a);
416 input.add(&a)
417 }
418 fn named_parameters(&self) -> Vec<(String, &Tensor<f32>)> {
419 vec![("a".into(), &self.a), ("b".into(), &self.b)]
420 }
421 fn set_parameter(&mut self, name: &str, value: Tensor<f32>) -> Result<(), AutodiffError> {
422 match name {
423 "a" => self.a = value,
424 "b" if self.fail_b => {
425 return Err(AutodiffError::InvalidArgument("b failed".into()));
426 }
427 "b" => self.b = value,
428 _ => return Err(AutodiffError::InvalidArgument(format!("unknown {name}"))),
429 }
430 Ok(())
431 }
432 }
433
434 struct Stateless;
435 impl Module for Stateless {
436 fn forward<'t>(
437 &self,
438 _tape: TapeRef<'t>,
439 input: &Var<'t>,
440 ) -> Result<Var<'t>, AutodiffError> {
441 Ok(*input)
442 }
443 }
444
445 fn t(v: f32) -> Tensor<f32> {
446 Tensor::from_slice(&[v, v], &[2]).expect("tensor")
447 }
448
449 fn bits(x: &Tensor<f32>) -> Vec<u32> {
450 x.as_slice()
451 .expect("host tensor")
452 .iter()
453 .map(|v| v.to_bits())
454 .collect()
455 }
456
457 #[test]
458 fn bridge_forward_matches_direct_forward() {
459 let mut layer = TwoParam {
460 a: t(1.0),
461 b: t(2.0),
462 fail_b: false,
463 };
464 let ta = crate::tape();
465 let xa = ta.var(&t(3.0));
466 let direct = layer.forward(TapeRef::from(&ta), &xa).expect("direct");
467
468 let tb = crate::tape();
469 let xb = tb.var(&t(3.0));
470 let bridge = FacadeModuleAdapter(&mut layer);
471 let via = AutodiffModule::forward(&bridge, &tb.0, &xb).expect("bridge");
472 assert_eq!(bits(&direct.value()), bits(&via.value()));
473 }
474
475 #[test]
476 fn default_set_parameter_message_matches_autodiff() {
477 let mine = Stateless
478 .set_parameter("w", t(0.0))
479 .expect_err("default is Err")
480 .to_string();
481 let mut relu = fandhe_ai_autodiff::nn::activation::Relu;
482 let theirs = AutodiffModule::set_parameter(&mut relu, "w", t(0.0))
483 .expect_err("default is Err")
484 .to_string();
485 assert_eq!(mine, theirs);
486 }
487
488 #[test]
489 fn load_state_dict_errors_and_rollback_via_bridge() {
490 let mut layer = TwoParam {
491 a: t(1.0),
492 b: t(2.0),
493 fail_b: true,
494 };
495 let mut good = layer.state_dict();
496 good.insert("a".into(), t(9.0));
497 let err = layer.load_state_dict(good).expect_err("b fails");
498 assert!(err.to_string().contains("b failed"), "{err}");
499 assert_eq!(bits(&layer.a), bits(&t(1.0)), "a is rolled back");
500
501 let mut missing = layer.state_dict();
502 missing.remove("b");
503 let e = layer.load_state_dict(missing).expect_err("missing");
504 assert!(e.to_string().contains("missing keys"), "{e}");
505 }
506
507 use fandhe_ai_autodiff::nn::{Linear, Sequential};
510
511 struct Scale {
513 a: Tensor<f32>,
514 training: bool,
515 }
516
517 impl Scale {
518 fn new() -> Self {
519 Self {
520 a: Tensor::from_slice(&[0.5, -1.5, 2.0], &[3]).expect("tensor"),
521 training: true,
522 }
523 }
524 }
525
526 impl Module for Scale {
527 fn forward<'t>(
528 &self,
529 tape: TapeRef<'t>,
530 input: &Var<'t>,
531 ) -> Result<Var<'t>, AutodiffError> {
532 let a = tape.var(&self.a);
533 input.mul(&a)
534 }
535 fn named_parameters(&self) -> Vec<(String, &Tensor<f32>)> {
536 vec![("a".into(), &self.a)]
537 }
538 fn set_parameter(&mut self, name: &str, value: Tensor<f32>) -> Result<(), AutodiffError> {
539 if name != "a" {
540 return Err(AutodiffError::InvalidArgument(format!("unknown {name}")));
541 }
542 self.a = value;
543 Ok(())
544 }
545 fn set_training(&mut self, training: bool) {
546 self.training = training;
547 }
548 fn training(&self) -> bool {
549 self.training
550 }
551 }
552
553 fn input() -> Tensor<f32> {
554 Tensor::from_slice(&[0.1, 0.2, -0.3, 0.4, -0.5, 0.6], &[2, 3]).expect("tensor")
555 }
556
557 fn adapter() -> FacadeModuleAdapter<Box<dyn Module>> {
558 FacadeModuleAdapter(Box::new(Scale::new()) as Box<dyn Module>)
559 }
560
561 type Snapshot = (Vec<u32>, Option<Vec<u32>>, Vec<Option<Vec<u32>>>, usize);
563
564 fn snapshot<'t>(tape: &'t crate::Tape, x: &Var<'t>, out: &Var<'t>) -> Snapshot {
565 let loss = out.sum(None).expect("sum");
566 let grads = tape.backward(&loss).expect("backward");
567 let leaves = (0..tape.leaf_count())
568 .map(|i| {
569 let v = tape.leaf(i).expect("leaf");
570 grads.get(&v).expect("get").map(bits)
571 })
572 .collect();
573 (
574 bits(&out.value()),
575 grads.get(x).expect("get").map(bits),
576 leaves,
577 tape.0.len(),
578 )
579 }
580
581 #[test]
582 fn adapter_in_autodiff_sequential_matches_manual_chain() {
583 for linear_first in [true, false] {
584 let linear = Linear::new(3, 3, true, 7).expect("linear");
585 let reference = Scale::new();
586
587 let tape = crate::tape();
588 let x = tape.var(&input());
589 let mut h = x;
590 if linear_first {
591 h = linear.bind(&tape.0).forward(&h).expect("linear");
592 h = Module::forward(&reference, TapeRef::from(&tape), &h).expect("scale");
593 } else {
594 h = Module::forward(&reference, TapeRef::from(&tape), &h).expect("scale");
595 h = linear.bind(&tape.0).forward(&h).expect("linear");
596 }
597 let expected = snapshot(&tape, &x, &h);
598
599 let mut seq = Sequential::new();
600 let linear2 = Linear::new(3, 3, true, 7).expect("linear");
601 if linear_first {
602 seq.push(Box::new(linear2));
603 seq.push(Box::new(adapter()));
604 } else {
605 seq.push(Box::new(adapter()));
606 seq.push(Box::new(linear2));
607 }
608 let tape2 = crate::tape();
609 let x2 = tape2.var(&input());
610 let out = AutodiffModule::forward(&seq, &tape2.0, &x2).expect("seq forward");
611 let actual = snapshot(&tape2, &x2, &out);
612 assert_eq!(expected, actual, "linear_first={linear_first}");
613 }
614 }
615
616 #[test]
617 fn adapter_state_dict_semantics_match_facade_layer() {
618 let mut ad = adapter();
619 let direct = Scale::new();
620 let via = AutodiffModule::state_dict(&ad);
621 let want = Module::state_dict(&direct);
622 assert_eq!(via.len(), want.len());
623 assert_eq!(bits(&via["a"]), bits(&want["a"]));
624
625 let mut seq = Sequential::new();
626 seq.push(Box::new(Linear::new(3, 3, false, 1).expect("linear")));
627 seq.push(Box::new(adapter()));
628 let mut sd = AutodiffModule::state_dict(&seq);
629 let new_a = Tensor::from_slice(&[9.0, 8.0, 7.0], &[3]).expect("tensor");
630 sd.insert("1.a".into(), new_a.clone());
631 AutodiffModule::load_state_dict(&mut seq, sd).expect("load");
632 assert_eq!(bits(&AutodiffModule::state_dict(&seq)["1.a"]), bits(&new_a));
633
634 let mut direct_mut = Scale::new();
636 let e_direct = Module::load_state_dict(&mut direct_mut, HashMap::new())
637 .expect_err("missing")
638 .to_string();
639 let e_adapter = AutodiffModule::load_state_dict(&mut ad, HashMap::new())
640 .expect_err("missing")
641 .to_string();
642 assert_eq!(e_direct, e_adapter);
643 }
644
645 #[test]
646 fn adapter_propagates_training_flag() {
647 let mut seq = Sequential::new();
648 seq.push(Box::new(adapter()));
649 assert!(AutodiffModule::training(&seq));
650 AutodiffModule::set_training(&mut seq, false);
651 assert!(!AutodiffModule::training(&seq));
652 assert!(!seq.layers()[0].training());
653 AutodiffModule::set_training(&mut seq, true);
654 assert!(seq.layers()[0].training());
655 }
656
657 #[test]
658 fn default_set_requires_grad_message_matches_autodiff() {
659 struct AdParams(Tensor<f32>);
661 impl AutodiffModule for AdParams {
662 fn forward<'t>(
663 &self,
664 _tape: &'t fandhe_ai_autodiff::Tape,
665 input: &Var<'t>,
666 ) -> Result<Var<'t>, AutodiffError> {
667 Ok(*input)
668 }
669 fn named_parameters(&self) -> Vec<(String, &Tensor<f32>)> {
670 vec![("p".into(), &self.0)]
671 }
672 }
673 struct FacParams(Tensor<f32>);
674 impl Module for FacParams {
675 fn forward<'t>(
676 &self,
677 _tape: TapeRef<'t>,
678 input: &Var<'t>,
679 ) -> Result<Var<'t>, AutodiffError> {
680 Ok(*input)
681 }
682 fn named_parameters(&self) -> Vec<(String, &Tensor<f32>)> {
683 vec![("p".into(), &self.0)]
684 }
685 }
686 let mine = FacParams(t(1.0))
687 .set_requires_grad(false)
688 .expect_err("fail-closed")
689 .to_string();
690 let theirs = AutodiffModule::set_requires_grad(&mut AdParams(t(1.0)), false)
691 .expect_err("fail-closed")
692 .to_string();
693 assert_eq!(mine, theirs);
694 }
695
696 #[test]
697 fn default_requires_grad_for_stateless_layer() {
698 let mut s = Stateless;
699 assert!(s.set_requires_grad(false).is_ok());
700 assert!(s.freeze().is_ok());
701 assert!(s.requires_grad());
702 let mut p = TwoParam {
703 a: t(1.0),
704 b: t(2.0),
705 fail_b: false,
706 };
707 assert!(matches!(p.freeze(), Err(AutodiffError::InvalidArgument(_))));
708 }
709
710 struct Freezable {
712 a: Tensor<f32>,
713 rg: bool,
714 }
715 impl Module for Freezable {
716 fn forward<'t>(
717 &self,
718 tape: TapeRef<'t>,
719 input: &Var<'t>,
720 ) -> Result<Var<'t>, AutodiffError> {
721 let a = if self.rg {
722 tape.var(&self.a)
723 } else {
724 tape.var_no_grad(&self.a)
725 };
726 input.mul(&a)
727 }
728 fn named_parameters(&self) -> Vec<(String, &Tensor<f32>)> {
729 vec![("a".into(), &self.a)]
730 }
731 fn set_requires_grad(&mut self, v: bool) -> Result<(), AutodiffError> {
732 self.rg = v;
733 Ok(())
734 }
735 fn requires_grad(&self) -> bool {
736 self.rg
737 }
738 }
739
740 #[test]
741 fn adapter_delegates_set_requires_grad_and_requires_grad() {
742 let mut seq = Sequential::new();
743 seq.push(Box::new(FacadeModuleAdapter(Box::new(Freezable {
744 a: Tensor::from_slice(&[1.0, 2.0, 3.0], &[3]).expect("tensor"),
745 rg: true,
746 }) as Box<dyn Module>)));
747 assert!(AutodiffModule::requires_grad(&seq));
748 AutodiffModule::freeze(&mut seq).expect("freeze");
749 assert!(!AutodiffModule::requires_grad(&seq));
750 assert!(!seq.layers()[0].requires_grad());
751 AutodiffModule::set_requires_grad(&mut seq, true).expect("unfreeze");
752 assert!(AutodiffModule::requires_grad(&seq));
753
754 let mut bad = Sequential::new();
756 bad.push(Box::new(adapter()));
757 assert!(AutodiffModule::freeze(&mut bad).is_err());
758 }
759 #[test]
760 fn adapter_delegates_type_name_but_not_children() {
761 let ad = adapter();
762 assert_eq!(
763 AutodiffModule::type_name(&ad),
764 Module::type_name(&Scale::new())
765 );
766 assert!(AutodiffModule::children(&ad).is_empty());
767 assert_eq!(
768 AutodiffModule::parameter_count(&ad),
769 Module::parameter_count(&Scale::new())
770 );
771
772 let mut seq = Sequential::new();
773 seq.push(Box::new(adapter()));
774 seq.push(Box::new(adapter()));
775 let names: Vec<String> = AutodiffModule::named_modules(&seq)
776 .into_iter()
777 .map(|(n, _)| n)
778 .collect();
779 assert_eq!(names, vec!["0".to_string(), "1".to_string()]);
780 }
781
782 #[test]
783 fn adapter_host_inference_is_fail_closed() {
784 let ad = adapter();
785 assert!(!AutodiffModule::supports_forward_host(&ad));
786 let ops = fandhe_ai_backend_cpu::CpuBackendOps::new();
787 let e = AutodiffModule::forward_host(&ad, &ops, &input()).expect_err("must be Err");
788 assert!(matches!(e, AutodiffError::InvalidArgument(_)), "{e}");
789
790 let mut seq = Sequential::new();
791 seq.push(Box::new(adapter()));
792 let e = AutodiffModule::forward_host(&seq, &ops, &input()).expect_err("must be Err");
793 assert!(matches!(e, AutodiffError::InvalidArgument(_)), "{e}");
794
795 assert!(AutodiffModule::as_linear(&ad).is_none());
796 assert!(!AutodiffModule::is_pooling(&ad));
797 }
798}