burn_optim/optim/
adafactor.rs1use burn_core as burn;
2
3use burn::{
4 config::Config,
5 tensor::{DType, Device, FloatDType, Tensor},
6};
7
8use crate::{LearningRate, RecordState, grad_clipping::GradientClippingConfig};
9
10use super::{ModuleOptimizer, Optimizer};
11
12#[cfg(not(feature = "std"))]
13#[allow(unused_imports)]
14use num_traits::Float as _;
15
16#[derive(Config, Debug)]
24pub struct AdafactorConfig {
25 #[config(default = 1e-30)]
27 epsilon_1: f32,
28 #[config(default = 1e-3)]
30 epsilon_2: f32,
31 #[config(default = 1.0)]
34 clip_threshold: f32,
35 #[config(default = -0.8)]
37 decay_rate: f64,
38 #[config(default = true)]
40 relative_step: bool,
41 #[config(default = true)]
43 scale_parameter: bool,
44 #[config(default = 0.0)]
46 weight_decay: f32,
47 grad_clipping: Option<GradientClippingConfig>,
49}
50
51#[derive(Clone)]
60pub struct Adafactor {
61 epsilon_1: f32,
62 epsilon_2: f32,
63 clip_threshold: f32,
64 decay_rate: f64,
65 relative_step: bool,
66 scale_parameter: bool,
67 weight_decay: f32,
68}
69
70#[derive(RecordState, Clone)]
72pub struct AdafactorState<const D: usize> {
73 pub time: usize,
75 pub second_moment: Tensor<D>,
78 pub column_second_moment: Option<Tensor<D>>,
81}
82
83impl AdafactorConfig {
84 pub fn build(&self) -> Adafactor {
95 assert!(
96 self.epsilon_1.is_finite() && self.epsilon_1 > 0.0,
97 "Adafactor epsilon_1 must be positive and finite"
98 );
99 assert!(
100 self.epsilon_2.is_finite() && self.epsilon_2 > 0.0,
101 "Adafactor epsilon_2 must be positive and finite"
102 );
103 assert!(
104 self.clip_threshold.is_finite() && self.clip_threshold > 0.0,
105 "Adafactor clip_threshold must be positive and finite"
106 );
107 assert!(
108 self.decay_rate.is_finite() && self.decay_rate < 0.0,
109 "Adafactor decay_rate must be negative and finite"
110 );
111 assert!(
112 self.weight_decay.is_finite() && self.weight_decay >= 0.0,
113 "Adafactor weight_decay must be nonnegative and finite"
114 );
115
116 Adafactor {
117 epsilon_1: self.epsilon_1,
118 epsilon_2: self.epsilon_2,
119 clip_threshold: self.clip_threshold,
120 decay_rate: self.decay_rate,
121 relative_step: self.relative_step,
122 scale_parameter: self.scale_parameter,
123 weight_decay: self.weight_decay,
124 }
125 }
126
127 pub fn init(&self) -> ModuleOptimizer {
129 let mut optimizer = ModuleOptimizer::from(self.build());
130 if let Some(config) = &self.grad_clipping {
131 optimizer = optimizer.with_grad_clipping(config.init());
132 }
133 optimizer
134 }
135}
136
137impl Optimizer for Adafactor {
138 type State<const D: usize> = AdafactorState<D>;
139
140 fn step<const D: usize>(
141 &self,
142 lr: LearningRate,
143 tensor: Tensor<D>,
144 grad: Tensor<D>,
145 state: Option<Self::State<D>>,
146 ) -> (Tensor<D>, Option<Self::State<D>>) {
147 let dtype = tensor.dtype();
148 let (tensor, grad) = match dtype {
151 DType::F16 | DType::BF16 => (tensor.cast(FloatDType::F32), grad.cast(FloatDType::F32)),
152 _ => (tensor, grad),
153 };
154
155 let squared_grad = grad.clone().square().add_scalar(self.epsilon_1);
156 let (second_moment, column_second_moment) = if D >= 2 {
157 (
158 squared_grad.clone().mean_dim(D - 1),
159 Some(squared_grad.mean_dim(D - 2)),
160 )
161 } else {
162 (squared_grad, None)
163 };
164
165 let state = match state {
166 Some(mut state) => {
167 state.time += 1;
168 let new_weight = (state.time as f64).powf(self.decay_rate);
169 let old_weight = 1.0 - new_weight;
170 state.second_moment = state
171 .second_moment
172 .mul_scalar(old_weight)
173 .add(second_moment.mul_scalar(new_weight));
174 state.column_second_moment = column_second_moment.map(|column| {
175 state
176 .column_second_moment
177 .take()
178 .expect("Factored Adafactor state must contain a column second moment")
179 .mul_scalar(old_weight)
180 .add(column.mul_scalar(new_weight))
181 });
182 state
183 }
184 None => AdafactorState {
185 time: 1,
186 second_moment,
187 column_second_moment,
188 },
189 };
190
191 let mut update = match &state.column_second_moment {
192 Some(column) => {
193 let row = state.second_moment.clone();
194 let row_scale = row.clone().div(row.mean_dim(D - 2)).sqrt();
197 grad.div(row_scale).div(column.clone().sqrt())
198 }
199 None => grad.div(state.second_moment.clone().sqrt()),
200 };
201
202 let clipping = rms(update.clone())
203 .div_scalar(self.clip_threshold)
204 .clamp_min(1.0);
205 update = update.div(clipping.unsqueeze());
206
207 let step_size = if self.relative_step {
208 lr.min(1.0 / (state.time as f64).sqrt())
209 } else {
210 lr
211 };
212 if self.scale_parameter {
213 let scale = rms(tensor.clone()).clamp_min(self.epsilon_2);
214 update = update.mul(scale.unsqueeze());
215 }
216 update = update.mul_scalar(step_size);
217
218 let tensor = if self.weight_decay == 0.0 {
219 tensor
220 } else {
221 tensor.mul_scalar(1.0 - lr * self.weight_decay as f64)
222 };
223 let tensor = tensor - update;
224 let tensor = match dtype {
225 DType::F16 | DType::BF16 => tensor.cast(FloatDType::from(dtype)),
226 _ => tensor,
227 };
228
229 (tensor, Some(state))
230 }
231
232 fn to_device<const D: usize>(mut state: Self::State<D>, device: &Device) -> Self::State<D> {
233 state.second_moment = state.second_moment.to_device(device);
234 state.column_second_moment = state
235 .column_second_moment
236 .map(|column| column.to_device(device));
237 state
238 }
239}
240
241fn rms<const D: usize>(tensor: Tensor<D>) -> Tensor<1> {
242 tensor.square().mean().sqrt()
243}
244
245#[cfg(test)]
246mod tests;