#[cfg(feature = "autodiff")]
use crate::ad::context;
#[cfg(feature = "autodiff")]
use crate::ext_op::ExtensionOp;
#[cfg(feature = "autodiff")]
use crate::std_tensor_op::StdTensorOp;
#[cfg(feature = "autodiff")]
use computegraph::graph::GraphBuilder;
#[cfg(feature = "autodiff")]
use computegraph::types::{LocalValueId, OperationRole, ValueKey, ValueRef};
#[cfg(feature = "autodiff")]
mod residual_spec;
#[cfg(feature = "autodiff")]
pub use residual_spec::primitive_residual_spec;
#[cfg(feature = "autodiff")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ADRuleKind {
Jvp,
Transpose,
}
#[cfg(feature = "autodiff")]
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum ADRuleError {
#[error("unsupported AD rule {rule:?} for {op}")]
Unsupported { op: String, rule: ADRuleKind },
#[error("invalid AD rule input for {op} ({rule:?}): {message}")]
InvalidInput {
op: String,
rule: ADRuleKind,
message: String,
},
}
#[cfg(feature = "autodiff")]
impl ADRuleError {
pub fn unsupported(op: impl Into<String>, rule: ADRuleKind) -> Self {
Self::Unsupported {
op: op.into(),
rule,
}
}
pub fn invalid_input(
op: impl Into<String>,
rule: ADRuleKind,
message: impl Into<String>,
) -> Self {
Self::InvalidInput {
op: op.into(),
rule,
message: message.into(),
}
}
pub const fn rule(&self) -> ADRuleKind {
match self {
Self::Unsupported { rule, .. } | Self::InvalidInput { rule, .. } => *rule,
}
}
}
#[cfg(feature = "autodiff")]
pub type ADRuleResult<T> = std::result::Result<T, ADRuleError>;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ResidualSpec {
input_mask: u64,
output_mask: u64,
}
impl ResidualSpec {
pub const fn none() -> Self {
Self {
input_mask: 0,
output_mask: 0,
}
}
pub const fn input(index: usize) -> Self {
Self {
input_mask: 1 << index,
output_mask: 0,
}
}
pub const fn output(index: usize) -> Self {
Self {
input_mask: 0,
output_mask: 1 << index,
}
}
pub const fn with_input(mut self, index: usize) -> Self {
self.input_mask |= 1 << index;
self
}
pub const fn with_output(mut self, index: usize) -> Self {
self.output_mask |= 1 << index;
self
}
pub const fn with_all_inputs(mut self) -> Self {
self.input_mask = u64::MAX;
self
}
pub const fn with_all_outputs(mut self) -> Self {
self.output_mask = u64::MAX;
self
}
pub const fn all_inputs() -> Self {
Self {
input_mask: u64::MAX,
output_mask: 0,
}
}
pub const fn all_outputs() -> Self {
Self {
input_mask: 0,
output_mask: u64::MAX,
}
}
pub const fn declares_input(&self, index: usize) -> bool {
index < 64 && (self.input_mask >> index) & 1 == 1
}
pub const fn declares_output(&self, index: usize) -> bool {
index < 64 && (self.output_mask >> index) & 1 == 1
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.input_mask == 0 && self.output_mask == 0
}
}
#[cfg(feature = "autodiff")]
#[derive(Clone, Debug)]
pub enum PrimitiveTransposeInput<Op: computegraph::GraphOperation> {
Residual(ValueKey<Op>),
Linear {
key: ValueKey<Op>,
primal: Option<ValueKey<Op>>,
},
}
#[cfg(feature = "autodiff")]
impl<Op: computegraph::GraphOperation> PrimitiveTransposeInput<Op> {
pub fn key(&self) -> &ValueKey<Op> {
match self {
Self::Residual(key) | Self::Linear { key, .. } => key,
}
}
}
#[cfg(feature = "autodiff")]
pub trait PrimitiveRuleBuilder {
fn add_operation(
&mut self,
operation: StdTensorOp,
inputs: Vec<ValueRef<StdTensorOp>>,
role: OperationRole,
) -> Vec<LocalValueId>;
}
#[cfg(feature = "autodiff")]
impl PrimitiveRuleBuilder for GraphBuilder<StdTensorOp> {
fn add_operation(
&mut self,
operation: StdTensorOp,
inputs: Vec<ValueRef<StdTensorOp>>,
role: OperationRole,
) -> Vec<LocalValueId> {
GraphBuilder::add_operation(self, operation, inputs, role)
}
}
#[doc(hidden)]
#[cfg(feature = "autodiff")]
pub trait ExtensionAdDispatcher: std::fmt::Debug + Send + Sync + 'static {
fn has_primal_vjp(&self, family_id: &str) -> bool;
fn linearize(
&self,
op: &dyn ExtensionOp,
builder: &mut dyn PrimitiveRuleBuilder,
primal_in: &[ValueKey<StdTensorOp>],
primal_out: &[ValueKey<StdTensorOp>],
tangent_in: &[Option<LocalValueId>],
ctx: &mut context::ShapeGuardContext,
) -> ADRuleResult<Vec<Option<LocalValueId>>>;
fn transpose(
&self,
op: &dyn ExtensionOp,
builder: &mut dyn PrimitiveRuleBuilder,
cotangent_out: &[Option<LocalValueId>],
inputs: &[PrimitiveTransposeInput<StdTensorOp>],
mode: &OperationRole,
ctx: &mut context::ShapeGuardContext,
) -> ADRuleResult<Vec<Option<LocalValueId>>>;
}
#[cfg(test)]
mod tests;