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?