Skip to main content

burn_tensor/tensor/api/
autodiff.rs

1use crate::{Tensor, kind::Autodiff};
2
3#[cfg(feature = "autodiff")]
4use crate::ops::{BridgeKind, BridgeTensor};
5#[cfg(feature = "autodiff")]
6use burn_backend::AutodiffBackend;
7#[cfg(feature = "autodiff")]
8use burn_dispatch::Dispatch;
9#[cfg(feature = "autodiff")]
10use burn_dispatch::GradientCheckpointingStrategy;
11
12#[cfg(feature = "autodiff")]
13type AutodiffGradients = <Dispatch as AutodiffBackend>::Gradients;
14
15// Aligned, type-erased storage for `AutodiffGradients`. See `crate::macros`
16// for why this indirection exists.
17#[cfg(feature = "autodiff")]
18burn_std::obfuscate!(
19    type: AutodiffGradients,
20    module: gradients_opaque,
21    derives: [Send]
22);
23
24/// Gradients container used during the backward pass.
25#[cfg(feature = "autodiff")]
26pub struct Gradients {
27    blob: gradients_opaque::Opaque,
28}
29
30#[cfg(feature = "autodiff")]
31impl Gradients {
32    /// Crate-internal constructor wrapping the dispatch-level gradients.
33    pub(crate) fn from_inner(inner: AutodiffGradients) -> Self {
34        Self {
35            blob: gradients_opaque::Opaque::new(inner),
36        }
37    }
38
39    /// Crate-internal borrow of the underlying gradients container.
40    pub(crate) fn as_inner(&self) -> &AutodiffGradients {
41        self.blob.as_ref()
42    }
43
44    /// Crate-internal mutable borrow of the underlying gradients container.
45    pub(crate) fn as_inner_mut(&mut self) -> &mut AutodiffGradients {
46        self.blob.as_mut()
47    }
48}
49
50#[cfg(feature = "autodiff")]
51impl<const D: usize> Tensor<D> {
52    /// Backward pass of the tensor.
53    pub fn backward(&self) -> Gradients {
54        backward_impl(&self.primitive)
55    }
56
57    /// Get the gradients of a tensor if it exist.
58    ///
59    /// Returns a new reference to the same tensor. Therefore the same grad tensor can
60    /// be accessed multiple times. If you only need to get the gradients one time,
61    /// consider using [grad_remove](Tensor::grad_remove) for better performance.
62    pub fn grad(&self, grads: &Gradients) -> Option<Tensor<D>> {
63        grad_impl(&self.primitive, grads).map(Tensor::new)
64    }
65
66    /// Remove the grad tensor from the [grads](AutodiffBackend::Gradients) struct returning the result.
67    pub fn grad_remove(&self, grads: &mut Gradients) -> Option<Tensor<D>> {
68        grad_remove_impl(&self.primitive, grads).map(Tensor::new)
69    }
70
71    /// Replace the grad tensor from the [grads](AutodiffBackend::Gradients) struct with the provided
72    /// gradient.
73    pub fn grad_replace(&self, grads: &mut Gradients, grad: Tensor<D>) {
74        grad_replace_impl(&self.primitive, grads, grad.primitive)
75    }
76}
77
78#[cfg(feature = "autodiff")]
79fn backward_impl(p: &BridgeTensor) -> Gradients {
80    Gradients::from_inner(Dispatch::backward(p.clone().into_float()))
81}
82
83#[cfg(feature = "autodiff")]
84fn grad_impl(p: &BridgeTensor, grads: &Gradients) -> Option<BridgeTensor> {
85    // A non-float tensor — a packed base included — records no tape, so there
86    // is no gradient to look up.
87    Dispatch::grad(p.try_as_float()?, grads.as_inner()).map(BridgeTensor::float)
88}
89
90#[cfg(feature = "autodiff")]
91fn grad_remove_impl(p: &BridgeTensor, grads: &mut Gradients) -> Option<BridgeTensor> {
92    Dispatch::grad_remove(p.try_as_float()?, grads.as_inner_mut()).map(BridgeTensor::float)
93}
94
95#[cfg(feature = "autodiff")]
96fn grad_replace_impl(p: &BridgeTensor, grads: &mut Gradients, grad: BridgeTensor) {
97    Dispatch::grad_replace(p.as_float(), grads.as_inner_mut(), grad.into_float())
98}
99
100impl<const D: usize, K: Autodiff> Tensor<D, K> {
101    /// Returns the inner tensor without the autodiff information.
102    pub fn inner(self) -> Tensor<D, K> {
103        Tensor::new(K::inner(self.primitive))
104    }
105
106    /// Take the tensor off the autodiff backend, dropping any graph reference
107    /// it carries. A tensor that is not on an autodiff backend is returned as
108    /// is.
109    ///
110    /// Unlike [detach](Tensor::detach), which severs the tensor from the graph
111    /// but leaves it on the autodiff backend, the result lives on the inner
112    /// backend: later operations pay no autodiff dispatch and can never be
113    /// recorded. And unlike [inner](Self::inner), which panics on a tensor
114    /// with no autodiff wrapper, this is safe to call anywhere — batchers,
115    /// metric pipelines, anything that must guarantee a tensor is off the
116    /// tape without knowing where it came from.
117    pub fn no_grad(self) -> Self {
118        if self.device().is_autodiff() {
119            self.inner()
120        } else {
121            self
122        }
123    }
124
125    /// Convert a tensor to the autodiff backend.
126    ///
127    /// # Arguments
128    ///
129    /// * `inner` - The tensor to convert.
130    ///
131    /// # Returns
132    ///
133    /// The tensor converted to the autodiff backend.
134    pub fn from_inner(inner: Tensor<D, K>) -> Self {
135        Self::new(K::from_inner(inner.primitive))
136    }
137
138    /// Sets the autodiff checkpointing strategy carried by this tensor.
139    ///
140    /// The strategy is normally derived from the device the tensor was created on (see
141    /// [`Device::gradient_checkpointing`](crate::Device::gradient_checkpointing)); this
142    /// method overrides it for a single tensor. A tensor carrying a strategy is treated
143    /// as tracked by autodiff, so this also marks an inner-backend tensor for tracking.
144    ///
145    /// # Panics
146    ///
147    /// Operations combining tensors that carry different strategies panic; make sure all
148    /// operands share the same one.
149    #[cfg(feature = "autodiff")]
150    pub fn with_gradient_checkpointing_strategy(
151        self,
152        strategy: GradientCheckpointingStrategy,
153    ) -> Self {
154        let (kind, mut tensor) = self.primitive.into_parts();
155        tensor.checkpointing = Some(strategy);
156        Self::new(match kind {
157            BridgeKind::Bool => BridgeTensor::bool(tensor),
158            BridgeKind::Int => BridgeTensor::int(tensor),
159            BridgeKind::Float => BridgeTensor::float(tensor),
160            BridgeKind::QFloat => BridgeTensor::qfloat(tensor),
161        })
162    }
163}
164
165// TODO: a lot of the `tensor.inner` / `Tensor::from_inner(...)` are actually scoped to perform some operations
166// so it might be cleaner and easier to manage the device etc. if we provide a method to scope the autodiff?