fandhe_ai_autodiff/error.rs
1//! `autodiff` クレートの公開エラー型。
2//!
3//! 順伝播(`Var` の演算メソッド。TASK-1.5a・#16)と逆伝播
4//! (`Tape::backward`/`Gradients::get`。`backward.rs`・TASK-1.5c・
5//! 本イシュー・#18)双方の失敗経路をここに集約する(spec 根拠:
6//! `docs/public-api-design.md` §3.1)。`tensor-core::ShapeError`
7//! を包む形でラップし、shape 検査ロジック・エラー variant を二重定義しない。
8
9use std::fmt;
10
11use fandhe_ai_tensor_core::{Device, ShapeError};
12
13/// `autodiff` の公開 API が返すエラー型。
14///
15/// `#[non_exhaustive]` を付す理由: 公開 API 非破壊はガードレール条件
16/// (`.claude/rules/security.md`)であり、TASK-1.5 で演算セットが拡張
17/// されるたびに新しい失敗要因(Conv 系の padding 不整合等)の variant
18/// 追加を非破壊にするため(`docs/public-api-design.md` §3.1 準拠)。
19#[non_exhaustive]
20#[derive(Debug)]
21pub enum AutodiffError {
22 /// 順伝播時の shape 不整合(`matmul`/`add`/`mul` の不正なブロード
23 /// キャスト、`sum`/`max`/`mse_loss` の shape 不一致等)。
24 /// `tensor-core::ShapeError` をラップする。
25 Shape(ShapeError),
26 /// `Tape::backward`(`backward.rs`・TASK-1.5c・#18)時のグラフ不整合
27 /// 用に予約された variant。当初はクロステープ検査
28 /// (`TapeMismatch`。下記)で表現できる範囲のみを想定し構築箇所を
29 /// 持たなかったが、TASK-12.1d(#164)の遅延評価統合で
30 /// `materialize_fallible`(`tape.rs`)の `OnceCell` 不変条件違反
31 /// (`set`/`get` の理論上到達しないはずの `None` 分岐)検出用途に
32 /// 転用する(`docs/fusion-graph-design.md` §3.5.2「`OnceCell::set`
33 /// の `Err` は通常分岐として扱う」節。`materialize_fallible` は
34 /// `&'a Tensor<f32>` を返す関数であり `None` 分岐で捏造した値への
35 /// 参照を作れないため、安全側のフォール先を型付きエラーとする)。
36 Backward(String),
37 /// 二項演算(`matmul`/`add`/`mul`/`mse_loss`)に、異なる `Tape` に
38 /// 属する `Var` が渡された。ライフタイム `'t` の一致は同一 `Tape`
39 /// を指す証明にはならない(同一スコープに複数の `Tape` が存在する
40 /// 場合、それぞれの `Var<'t>` は同一の `'t` を持ちうる)ため、
41 /// `Var::matmul`/`add`/`mul`/`mse_loss` は shape 検査より前に
42 /// `self.tape.id` と相手側の `TapeId` を実行時照合し、不一致なら
43 /// 本 variant を返す(`docs/public-api-design.md` §3.1
44 /// 「クロステープ安全性」)。
45 TapeMismatch,
46 /// 呼び出し元が渡した引数がテンソル未生成の段階で構築不可能な
47 /// 組み合わせだった(例: `nn::Linear::new` の `in_features == 0`。
48 /// `1/√in_features` が非有限になるため shape 検査より前に弾く)。
49 ///
50 /// `tensor-core::ShapeError`(`#[non_exhaustive]`)へ variant を
51 /// 追加する案もあったが、`ShapeError` は「テンソル生成・view 操作・
52 /// reshape の shape 不整合」(`tensor-core/src/error.rs` 冒頭)専用の
53 /// 型であり、`nn` 層のコンストラクタ引数検証はその責務外
54 /// (`tensor-core` は `nn` を知らない下位クレート)。そのため
55 /// `autodiff` 側で `ShapeError` をラップしない専用 variant として
56 /// 追加する(review 指摘 #91: 既存 `AxisOutOfRange` への転用は
57 /// 意味不一致だったため撤回)。
58 InvalidArgument(String),
59 /// 融合実行・実体化(TASK-12.1d・#164。`materialize_fallible`。
60 /// `tape.rs`)で発生した型付きバックエンドエラー。
61 /// `fandhe_ai_tensor_core::BackendError` をラップする(`docs/
62 /// fusion-graph-design.md` §3.5.2「層 1 は `Unsupported` 以外の
63 /// `run_fused` の失敗をフォールバックせずそのまま伝播する」)。
64 Backend(fandhe_ai_tensor_core::BackendError),
65 /// `Gradients::get` の対象ノードが `requires_grad == false`
66 /// (`Tape::var_no_grad` で登録した葉、または `Var::detach` で
67 /// 切り離した葉、およびそれらのみを祖先に持つ非葉ノード)だった
68 /// (イシュー #1748・`docs/autodiff-nograd-leaf-dinput-skip-decision.md`
69 /// §5「案 B」)。「loss から未到達(`Ok(None)`)」とは型で区別する
70 /// ——未到達は「たまたま今回の loss には寄与しなかった」ことを、
71 /// 本 variant は「この `Var` は構造的に勾配を持ちえない」ことを表す。
72 GradientTrackingDisabled,
73 /// [`crate::Var::to`](イシュー #1614)が要求先デバイスと実際の
74 /// デバイスの不一致を検出した。`Var` は所属する 1 つの `Tape`
75 /// (=1 デバイス)に束縛されており(`docs/public-api-design.md`
76 /// §3.1「クロステープ安全性」)、同一テープ内でデバイスを差し
77 /// 替える演算は表現できない。単位 variant ではなく要求先・実際の
78 /// 両方の [`Device`] を保持するのは、呼び出し元が「なぜ失敗した
79 /// か」をエラー値だけから判断できるようにするため(`BackendError::
80 /// DeviceUnavailable` と同じ設計方針)。別デバイスへ実際に値を
81 /// 転送したい場合は [`crate::Var::to_tape`](もしくは facade
82 /// `Tape::transfer`)を使う。
83 DeviceMismatch {
84 /// 呼び出し元が [`crate::Var::to`] へ渡した要求先デバイス。
85 requested: Device,
86 /// この `Var` が実際に属する `Tape` のデバイス。
87 actual: Device,
88 },
89}
90
91impl From<ShapeError> for AutodiffError {
92 fn from(err: ShapeError) -> Self {
93 AutodiffError::Shape(err)
94 }
95}
96
97impl From<fandhe_ai_tensor_core::BackendError> for AutodiffError {
98 fn from(err: fandhe_ai_tensor_core::BackendError) -> Self {
99 AutodiffError::Backend(err)
100 }
101}
102
103/// `tensor-core::fft` の形状・引数検査失敗を写像する(イシュー #2631)。
104/// 形状違反は `Shape`、引数不正は `InvalidArgument`(バックエンド経由の
105/// `unify_backend_error` と同じ見え方に揃える)。
106impl From<fandhe_ai_tensor_core::FftError> for AutodiffError {
107 fn from(err: fandhe_ai_tensor_core::FftError) -> Self {
108 match err {
109 fandhe_ai_tensor_core::FftError::Shape(e) => AutodiffError::Shape(e),
110 fandhe_ai_tensor_core::FftError::InvalidArgument(msg) => {
111 AutodiffError::InvalidArgument(msg)
112 }
113 }
114 }
115}
116
117/// `tensor-core::stat_reduce` の形状・引数検査失敗を写像する(イシュー #2637)。
118/// 写像規則は [`fandhe_ai_tensor_core::FftError`] 版と同じ。
119impl From<fandhe_ai_tensor_core::StatReduceError> for AutodiffError {
120 fn from(err: fandhe_ai_tensor_core::StatReduceError) -> Self {
121 match err {
122 fandhe_ai_tensor_core::StatReduceError::Shape(e) => AutodiffError::Shape(e),
123 fandhe_ai_tensor_core::StatReduceError::InvalidArgument(msg) => {
124 AutodiffError::InvalidArgument(msg)
125 }
126 }
127 }
128}
129
130/// `tensor-core::pad_modes` の形状・引数検査失敗を写像する(イシュー #2642)。
131/// 写像規則は [`fandhe_ai_tensor_core::StatReduceError`] 版と同じ。
132impl From<fandhe_ai_tensor_core::PadModeError> for AutodiffError {
133 fn from(err: fandhe_ai_tensor_core::PadModeError) -> Self {
134 match err {
135 fandhe_ai_tensor_core::PadModeError::Shape(e) => AutodiffError::Shape(e),
136 fandhe_ai_tensor_core::PadModeError::InvalidArgument(msg) => {
137 AutodiffError::InvalidArgument(msg)
138 }
139 }
140 }
141}
142
143/// `tensor-core::binning` の形状・引数検査失敗を写像する(イシュー #2638)。
144/// 写像規則は [`fandhe_ai_tensor_core::FftError`] 版と同じ。
145impl From<fandhe_ai_tensor_core::BinningError> for AutodiffError {
146 fn from(err: fandhe_ai_tensor_core::BinningError) -> Self {
147 match err {
148 fandhe_ai_tensor_core::BinningError::Shape(e) => AutodiffError::Shape(e),
149 fandhe_ai_tensor_core::BinningError::InvalidArgument(msg) => {
150 AutodiffError::InvalidArgument(msg)
151 }
152 }
153 }
154}
155
156impl fmt::Display for AutodiffError {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 match self {
159 AutodiffError::Shape(err) => write!(f, "shape error: {err}"),
160 AutodiffError::Backward(msg) => write!(f, "backward error: {msg}"),
161 AutodiffError::TapeMismatch => {
162 write!(f, "operands belong to different Tape instances")
163 }
164 AutodiffError::InvalidArgument(message) => {
165 write!(f, "invalid argument: {message}")
166 }
167 AutodiffError::Backend(err) => write!(f, "backend error: {err}"),
168 AutodiffError::GradientTrackingDisabled => write!(
169 f,
170 "gradient tracking disabled: この Var は requires_grad=false の葉(var_no_grad/detach)から派生しており勾配を持たない"
171 ),
172 AutodiffError::DeviceMismatch { requested, actual } => write!(
173 f,
174 "device mismatch: requested {requested:?} but this Var belongs to a Tape on {actual:?}(別デバイスへ値を転送するには Var::to_tape/Tape::transfer を使うこと)"
175 ),
176 }
177 }
178}
179
180impl std::error::Error for AutodiffError {}