1pub mod context;
9
10#[cfg(feature = "autodiff")]
11mod analytic;
12#[cfg(feature = "autodiff")]
13mod contraction;
14#[cfg(feature = "autodiff")]
15mod diagonal;
16#[cfg(feature = "autodiff")]
17mod dynamic;
18#[cfg(feature = "autodiff")]
19mod elementwise;
20#[cfg(feature = "autodiff")]
21mod indexing;
22#[cfg(feature = "autodiff")]
23pub(crate) mod registry;
24#[cfg(feature = "autodiff")]
25mod semiring;
26#[cfg(feature = "autodiff")]
27mod structural;
28#[cfg(feature = "autodiff")]
29#[doc(hidden)]
30pub mod support;
31#[cfg(feature = "autodiff")]
32#[doc(hidden)]
33pub mod transpose_input;
34#[cfg(feature = "autodiff")]
35mod zeros;
36
37#[cfg(feature = "autodiff")]
38use crate::ext_op::ExtensionOp;
39#[cfg(feature = "autodiff")]
40use crate::std_tensor_op::StdTensorOp;
41#[cfg(feature = "autodiff")]
42use computegraph::graph::GraphBuilder;
43#[cfg(feature = "autodiff")]
44use computegraph::types::{LocalValueId, OperationRole, ValueKey, ValueRef};
45
46#[cfg(feature = "autodiff")]
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub enum ADRuleKind {
49 Jvp,
50 Transpose,
51}
52
53#[cfg(feature = "autodiff")]
54#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
55pub enum ADRuleError {
56 #[error("unsupported AD rule {rule:?} for {op}")]
57 Unsupported { op: String, rule: ADRuleKind },
58 #[error("invalid AD rule input for {op} ({rule:?}): {message}")]
59 InvalidInput {
60 op: String,
61 rule: ADRuleKind,
62 message: String,
63 },
64}
65
66#[cfg(feature = "autodiff")]
67impl ADRuleError {
68 pub fn unsupported(op: impl Into<String>, rule: ADRuleKind) -> Self {
69 Self::Unsupported {
70 op: op.into(),
71 rule,
72 }
73 }
74
75 pub fn invalid_input(
76 op: impl Into<String>,
77 rule: ADRuleKind,
78 message: impl Into<String>,
79 ) -> Self {
80 Self::InvalidInput {
81 op: op.into(),
82 rule,
83 message: message.into(),
84 }
85 }
86
87 pub const fn rule(&self) -> ADRuleKind {
88 match self {
89 Self::Unsupported { rule, .. } | Self::InvalidInput { rule, .. } => *rule,
90 }
91 }
92}
93
94#[cfg(feature = "autodiff")]
95pub type ADRuleResult<T> = std::result::Result<T, ADRuleError>;
96
97#[cfg(feature = "autodiff")]
98#[derive(Clone, Debug)]
99pub enum PrimitiveTransposeInput<Op: computegraph::GraphOperation> {
100 Residual(ValueKey<Op>),
101 Linear {
102 key: ValueKey<Op>,
103 primal: Option<ValueKey<Op>>,
104 },
105}
106
107#[cfg(feature = "autodiff")]
108impl<Op: computegraph::GraphOperation> PrimitiveTransposeInput<Op> {
109 pub fn key(&self) -> &ValueKey<Op> {
110 match self {
111 Self::Residual(key) | Self::Linear { key, .. } => key,
112 }
113 }
114}
115
116#[cfg(feature = "autodiff")]
117fn missing_primitive_kind(op: &StdTensorOp, rule: ADRuleKind) -> ADRuleError {
118 ADRuleError::invalid_input(
119 "tenferro-internal-ops primitive AD dispatch",
120 rule,
121 format!("non-extension operation has no primitive kind: {op:?}"),
122 )
123}
124
125#[cfg(feature = "autodiff")]
147pub trait PrimitiveRuleBuilder {
148 fn add_operation(
150 &mut self,
151 operation: StdTensorOp,
152 inputs: Vec<ValueRef<StdTensorOp>>,
153 role: OperationRole,
154 ) -> Vec<LocalValueId>;
155}
156
157#[cfg(feature = "autodiff")]
158impl PrimitiveRuleBuilder for GraphBuilder<StdTensorOp> {
159 fn add_operation(
160 &mut self,
161 operation: StdTensorOp,
162 inputs: Vec<ValueRef<StdTensorOp>>,
163 role: OperationRole,
164 ) -> Vec<LocalValueId> {
165 GraphBuilder::add_operation(self, operation, inputs, role)
166 }
167}
168
169#[doc(hidden)]
175#[cfg(feature = "autodiff")]
176pub trait ExtensionAdDispatcher: std::fmt::Debug + Send + Sync + 'static {
177 fn has_primal_vjp(&self, family_id: &str) -> bool;
178
179 fn linearize(
180 &self,
181 op: &dyn ExtensionOp,
182 builder: &mut dyn PrimitiveRuleBuilder,
183 primal_in: &[ValueKey<StdTensorOp>],
184 primal_out: &[ValueKey<StdTensorOp>],
185 tangent_in: &[Option<LocalValueId>],
186 ctx: &mut context::ShapeGuardContext,
187 ) -> ADRuleResult<Vec<Option<LocalValueId>>>;
188
189 fn transpose(
190 &self,
191 op: &dyn ExtensionOp,
192 builder: &mut dyn PrimitiveRuleBuilder,
193 cotangent_out: &[Option<LocalValueId>],
194 inputs: &[PrimitiveTransposeInput<StdTensorOp>],
195 mode: &OperationRole,
196 ctx: &mut context::ShapeGuardContext,
197 ) -> ADRuleResult<Vec<Option<LocalValueId>>>;
198}
199
200#[cfg(feature = "autodiff")]
201fn dispatch_extension_linearize(
202 op: &dyn ExtensionOp,
203 builder: &mut dyn PrimitiveRuleBuilder,
204 primal_in: &[ValueKey<StdTensorOp>],
205 primal_out: &[ValueKey<StdTensorOp>],
206 tangent_in: &[Option<LocalValueId>],
207 ctx: &mut context::ShapeGuardContext,
208) -> ADRuleResult<Vec<Option<LocalValueId>>> {
209 if let Some(dispatcher) = ctx.extension_ad_dispatcher() {
210 return dispatcher.linearize(op, builder, primal_in, primal_out, tangent_in, ctx);
211 }
212 Err(ADRuleError::unsupported(op.family_id(), ADRuleKind::Jvp))
213}
214
215#[cfg(feature = "autodiff")]
216fn dispatch_extension_transpose(
217 op: &dyn ExtensionOp,
218 builder: &mut dyn PrimitiveRuleBuilder,
219 cotangent_out: &[Option<LocalValueId>],
220 inputs: &[PrimitiveTransposeInput<StdTensorOp>],
221 mode: &OperationRole,
222 ctx: &mut context::ShapeGuardContext,
223) -> ADRuleResult<Vec<Option<LocalValueId>>> {
224 if let Some(dispatcher) = ctx.extension_ad_dispatcher() {
225 return dispatcher.transpose(op, builder, cotangent_out, inputs, mode, ctx);
226 }
227 Err(ADRuleError::unsupported(
228 op.family_id(),
229 ADRuleKind::Transpose,
230 ))
231}
232
233#[cfg(feature = "autodiff")]
248pub fn linearize(
249 op: &StdTensorOp,
250 builder: &mut dyn PrimitiveRuleBuilder,
251 primal_in: &[ValueKey<StdTensorOp>],
252 primal_out: &[ValueKey<StdTensorOp>],
253 tangent_in: &[Option<LocalValueId>],
254 ctx: &mut context::ShapeGuardContext,
255) -> ADRuleResult<Vec<Option<LocalValueId>>> {
256 if let StdTensorOp::Extension(ext) = op {
257 return dispatch_extension_linearize(
258 ext.as_ref(),
259 builder,
260 primal_in,
261 primal_out,
262 tangent_in,
263 ctx,
264 );
265 }
266
267 let kind = op
268 .primitive_kind()
269 .ok_or_else(|| missing_primitive_kind(op, ADRuleKind::Jvp))?;
270 let rule = registry::primitive_ad_rule(kind)
271 .ok_or_else(|| registry::missing_rule(kind, ADRuleKind::Jvp))?;
272 rule.linearize(op, builder, primal_in, primal_out, tangent_in, ctx)
273}
274
275#[cfg(feature = "autodiff")]
289pub fn transpose_rule(
290 op: &StdTensorOp,
291 builder: &mut impl PrimitiveRuleBuilder,
292 cotangent_out: &[Option<LocalValueId>],
293 inputs: &[PrimitiveTransposeInput<StdTensorOp>],
294 mode: &OperationRole,
295 ctx: &mut context::ShapeGuardContext,
296) -> ADRuleResult<Vec<Option<LocalValueId>>> {
297 if let StdTensorOp::Extension(ext) = op {
298 let builder_dyn: &mut dyn PrimitiveRuleBuilder = builder;
299 return dispatch_extension_transpose(
300 ext.as_ref(),
301 builder_dyn,
302 cotangent_out,
303 inputs,
304 mode,
305 ctx,
306 );
307 }
308
309 let transpose_inputs = inputs
310 .iter()
311 .map(transpose_input::TransposeInputRef::new)
312 .collect::<Vec<_>>();
313 let kind = op
314 .primitive_kind()
315 .ok_or_else(|| missing_primitive_kind(op, ADRuleKind::Transpose))?;
316 let rule = registry::primitive_ad_rule(kind)
317 .ok_or_else(|| registry::missing_rule(kind, ADRuleKind::Transpose))?;
318 let builder_dyn: &mut dyn PrimitiveRuleBuilder = builder;
319 rule.transpose_rule(op, builder_dyn, cotangent_out, &transpose_inputs, mode, ctx)
320}
321
322#[cfg(test)]
323mod tests;