Skip to main content

burn_optim/optim/
grads.rs

1use burn_core as burn;
2
3use burn::{
4    Tensor,
5    tensor::{Device, Gradients, container::TensorContainer},
6};
7
8use burn::module::{AutodiffModule, ParamId};
9
10use super::visitor::{GradientsParamsChangeDevice, GradientsParamsConverter};
11
12/// Data type that contains gradients for parameters.
13#[derive(Default, Debug)]
14pub struct GradientsParams {
15    container: TensorContainer<ParamId>,
16}
17
18impl GradientsParams {
19    /// Creates a new [GradientsParams](GradientsParams).
20    pub fn new() -> Self {
21        Self::default()
22    }
23
24    /// Extract each tensor gradients for the given [module](AutodiffModule).
25    ///
26    /// Note: This consumes the gradients. See ['from_module'] to extract gradients only for
27    ///  a specific module.
28    pub fn from_grads<M: AutodiffModule>(grads: Gradients, module: &M) -> Self {
29        let mut grads = grads;
30        Self::from_module(&mut grads, module)
31    }
32
33    /// Extract each tensor gradients for the given [module](AutodiffModule).
34    pub fn from_module<M: AutodiffModule>(grads: &mut Gradients, module: &M) -> Self {
35        let mut grads_params = GradientsParams::new();
36        let mut visitor = GradientsParamsConverter::<M>::new(grads, &mut grads_params, None);
37        module.visit(&mut visitor);
38        grads_params
39    }
40
41    /// Extract tensor gradients for the given [module](AutodiffModule) and given parameters.
42    pub fn from_params<M: AutodiffModule>(
43        grads: &mut Gradients,
44        module: &M,
45        params: &[ParamId],
46    ) -> Self {
47        let mut grads_params = GradientsParams::new();
48        let mut visitor =
49            GradientsParamsConverter::<M>::new(grads, &mut grads_params, Some(params.to_vec()));
50        module.visit(&mut visitor);
51        grads_params
52    }
53
54    /// Get the gradients for the given [parameter id](ParamId).
55    ///
56    /// # Notes
57    ///
58    /// You should use [remove](GradientsParams::remove) if you want to get the gradients
59    /// only one time.
60    pub fn get<const D: usize>(&self, id: ParamId) -> Option<Tensor<D>> {
61        self.container.get(&id)
62    }
63
64    /// Remove the gradients for the given [parameter id](ParamId).
65    pub fn remove<const D: usize>(&mut self, id: ParamId) -> Option<Tensor<D>> {
66        self.container.remove(&id)
67    }
68
69    /// Register a gradients tensor for the given [parameter id](ParamId).
70    ///
71    /// # Notes
72    ///
73    /// If a tensor is already registered for the given [parameter id](ParamId), it will be replaced.
74    pub fn register<const D: usize>(&mut self, id: ParamId, value: Tensor<D>) {
75        // TODO: always call value.inner() to make sure?
76        self.container.register(id, value)
77    }
78
79    /// The number of gradients tensors registered.
80    pub fn len(&self) -> usize {
81        self.container.len()
82    }
83
84    /// If any tensor is contained.
85    pub fn is_empty(&self) -> bool {
86        self.len() == 0
87    }
88
89    /// Change the device of each tensor gradients registered for the given [module](AutodiffModule).
90    pub fn to_device<M: AutodiffModule>(mut self, device: &Device, module: &M) -> Self {
91        let mut visitor = GradientsParamsChangeDevice::<M>::new(device, &mut self);
92        module.visit(&mut visitor);
93        self
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use burn::module::{Module, list_param_ids};
101    use burn::tensor::Distribution;
102    use burn_nn::{Linear, LinearConfig};
103
104    #[test]
105    fn test_convert_grads() {
106        let device = Device::default().autodiff();
107        let layer_1 = layer(&device);
108        let mut layer_2 = layer_1.clone();
109        layer_2 = layer_2.fork(&device);
110        let loss_1 = layer_1.forward(random_tensor(&device));
111        let loss_2 = layer_2.forward(random_tensor(&device));
112        let grads_1 = GradientsParams::from_grads(loss_1.backward(), &layer_1);
113        let grads_2 = GradientsParams::from_grads(loss_2.backward(), &layer_2);
114
115        let param_ids_1 = list_param_ids(&layer_1);
116        let param_ids_2 = list_param_ids(&layer_2);
117
118        assert_eq!(param_ids_1, param_ids_2);
119        assert_eq!(grads_1.len(), param_ids_1.len());
120        assert_eq!(grads_2.len(), param_ids_2.len());
121    }
122
123    fn layer(device: &Device) -> Linear {
124        LinearConfig::new(20, 20).init(device)
125    }
126
127    fn random_tensor(device: &Device) -> Tensor<2> {
128        Tensor::<2>::random([2, 20], Distribution::Default, device)
129    }
130}