Skip to main content

fandhe_ai/nn/
forward_hook.rs

1//! facade 版 forward hook ラッパー `ForwardHooked`(イシュー #2587・親 #2584)。
2//!
3//! 役割: facade 独自の [`crate::nn::Module`] 実装を包み、`forward` 完了直後に
4//! 観察専用 hook(PyTorch `register_forward_hook` 相当)を 1 回呼ぶ。autodiff 側
5//! `nn::ForwardHooked`(#2586)は autodiff `Module` 専用で、facade 利用者の層は
6//! 生の `fandhe_ai_autodiff::Tape` を名指せず直接は包めないため、本型が橋渡しをする
7//! (`docs/autodiff-forward-backward-hooks-design.md` §14.4 P5/P5′・§18)。
8//!
9//! 単一実装の再利用(P5′): 内部では crate 内専用アダプタ `FacadeModuleAdapter` で
10//! inner を autodiff の `Module` へ包み、autodiff 側 `ForwardHooked` に hook の呼び出し・
11//! `ForwardHookCtx` の構築を任せる。facade 側は hook を直接呼ばず ctx も構築しない
12//! (ドリフトを避ける。`tests/api_surface.rs` が固定する)。`forward` 以外の `Module`
13//! メソッド(`named_parameters`・`state_dict` 等)は inner の facade `Module` メソッドへ
14//! 名前の接頭辞なしで透過委譲する。
15//!
16//! 契約:
17//! - hook は観察専用(戻り値は `Result<(), AutodiffError>`)。出力を書き換えられず、
18//!   `Err` は `forward` の `Err` として伝播する。inner の `forward` が `Err` なら hook は不発。
19//! - hook は構築時に 1 つだけ指定する(登録・解除 API は持たない)。
20//! - facade `Module` はホスト推論経路を持たない(`FacadeModuleAdapter` は
21//!   `supports_forward_host() == false`)ため、`compat::Sequential::predict` 等でも tape 経路で
22//!   1 forward につき 1 回発火する。
23//! - [`ForwardHookCtx::output_value`] は出力ノードを実体化するため、遅延融合の分割が変わりうる
24//!   (observer effect。値を読まない hook では出力は bit 一致。§5.3・§17.3)。
25//! - 包めるのは facade `nn::Module` 実装(利用者定義層・`nn::{ModuleList, Sequential,
26//!   ModuleDict}`)。autodiff 組み込み層の直接ラップは承認形の範囲外。
27//!
28//! 公開は `crate::nn::ForwardHooked` と `crate::nn::ForwardHookCtx`(autodiff からの純
29//! 再エクスポート。`nn/mod.rs`)のみで、`pub mod hooks` は設けない(P4)。
30
31use std::collections::HashMap;
32
33use fandhe_ai_autodiff::nn::ForwardHooked as AutodiffForwardHooked;
34use fandhe_ai_autodiff::nn::Module as AutodiffModule;
35
36use super::ForwardHookCtx;
37use crate::nn::module::{FacadeModuleAdapter, Module};
38use crate::{AutodiffError, TapeRef, Tensor, Var};
39
40/// facade `Module` を包み、`forward` 後に観察専用 hook を呼ぶラッパー。
41///
42/// 構築は [`Self::new`]、取り出しは [`Self::inner`]・[`Self::inner_mut`]・
43/// [`Self::into_inner`]。`Module` としては inner と同じ振る舞いを透過し、`forward` にだけ
44/// hook が加わる。hook の ctx([`ForwardHookCtx`])は入力・出力 shape と、必要時のみ出力値
45/// (`output_value`)を提供する。
46///
47/// ```
48/// use std::sync::Arc;
49/// use std::sync::atomic::{AtomicUsize, Ordering};
50/// use fandhe_ai::nn::{ForwardHooked, Module};
51/// use fandhe_ai::{AutodiffError, Tensor, TapeRef, Var};
52///
53/// struct Double;
54/// impl Module for Double {
55///     fn forward<'t>(
56///         &self,
57///         _tape: TapeRef<'t>,
58///         input: &Var<'t>,
59///     ) -> Result<Var<'t>, AutodiffError> {
60///         input.add(input)
61///     }
62/// }
63///
64/// let calls = Arc::new(AtomicUsize::new(0));
65/// let seen = Arc::clone(&calls);
66/// let hooked = ForwardHooked::new(Double, move |ctx| {
67///     assert_eq!(ctx.input_shape(), ctx.output_shape());
68///     seen.fetch_add(1, Ordering::SeqCst);
69///     Ok(())
70/// });
71/// let tape = fandhe_ai::tape();
72/// let x = tape.var(&Tensor::new(vec![1.0, 2.0], &[2]).unwrap());
73/// let y = hooked.forward(TapeRef::from(&tape), &x).unwrap();
74/// assert_eq!(y.to_tensor().host_slice().as_ref(), &[2.0, 4.0]);
75/// assert_eq!(calls.load(Ordering::SeqCst), 1);
76/// ```
77pub struct ForwardHooked<M: Module> {
78    inner: AutodiffForwardHooked<FacadeModuleAdapter<Box<M>>>,
79}
80
81impl<M: Module> ForwardHooked<M> {
82    /// `inner` を包み、`forward` 完了直後に `hook` を 1 回呼ぶラッパーを作る。
83    pub fn new<F>(inner: M, hook: F) -> Self
84    where
85        F: Fn(&ForwardHookCtx<'_>) -> Result<(), AutodiffError> + Send + Sync + 'static,
86    {
87        Self {
88            inner: AutodiffForwardHooked::new(FacadeModuleAdapter(Box::new(inner)), hook),
89        }
90    }
91
92    /// 内側の層への参照。
93    pub fn inner(&self) -> &M {
94        &self.inner.inner().0
95    }
96
97    /// 内側の層への可変参照。
98    pub fn inner_mut(&mut self) -> &mut M {
99        &mut self.inner.inner_mut().0
100    }
101
102    /// 包みを外して内側の層を取り出す(hook は破棄される)。
103    pub fn into_inner(self) -> M {
104        *self.inner.into_inner().0
105    }
106}
107
108impl<M: Module> Module for ForwardHooked<M> {
109    fn forward<'t>(&self, tape: TapeRef<'t>, input: &Var<'t>) -> Result<Var<'t>, AutodiffError> {
110        AutodiffModule::forward(&self.inner, tape.0, input)
111    }
112
113    fn named_parameters(&self) -> Vec<(String, &Tensor<f32>)> {
114        self.inner().named_parameters()
115    }
116
117    fn set_parameter(&mut self, name: &str, value: Tensor<f32>) -> Result<(), AutodiffError> {
118        self.inner_mut().set_parameter(name, value)
119    }
120
121    fn state_dict(&self) -> HashMap<String, Tensor<f32>> {
122        self.inner().state_dict()
123    }
124
125    fn load_state_dict(
126        &mut self,
127        state: HashMap<String, Tensor<f32>>,
128    ) -> Result<(), AutodiffError> {
129        self.inner_mut().load_state_dict(state)
130    }
131
132    fn set_training(&mut self, training: bool) {
133        self.inner_mut().set_training(training);
134    }
135
136    fn training(&self) -> bool {
137        self.inner().training()
138    }
139
140    fn set_requires_grad(&mut self, requires_grad: bool) -> Result<(), AutodiffError> {
141        self.inner_mut().set_requires_grad(requires_grad)
142    }
143
144    fn freeze(&mut self) -> Result<(), AutodiffError> {
145        self.inner_mut().freeze()
146    }
147
148    fn requires_grad(&self) -> bool {
149        self.inner().requires_grad()
150    }
151
152    fn children(&self) -> Vec<(String, &dyn Module)> {
153        self.inner().children()
154    }
155
156    fn children_mut(&mut self) -> Vec<(String, &mut dyn Module)> {
157        self.inner_mut().children_mut()
158    }
159
160    fn named_modules(&self) -> Vec<(String, &dyn Module)> {
161        self.inner().named_modules()
162    }
163
164    fn parameter_count(&self) -> usize {
165        self.inner().parameter_count()
166    }
167
168    fn type_name(&self) -> &'static str {
169        self.inner().type_name()
170    }
171}