1use std::{borrow::Cow, collections::HashMap, rc::Rc};
2
3use mlx_internal_macros::{generate_builder, Buildable};
4
5use crate::{
6 array,
7 error::AdafactorBuildError,
8 ops::{
9 matmul, maximum, mean, mean_axes, minimum, rsqrt, sqrt, square, zeros_dtype, zeros_like,
10 },
11 utils::Updatable,
12 Array,
13};
14
15use super::*;
16
17fn rms(inputs: &Array) -> crate::error::Result<Array> {
18 sqrt(&mean(&square(inputs)?, None)?)
19}
20
21fn approvate_exp_moving_avg(
22 exp_avg_sq_row: &Array,
23 exp_avg_sq_col: &Array,
24) -> crate::error::Result<Array> {
25 let rfactor = rsqrt(&exp_avg_sq_row.divide(&mean_axes(exp_avg_sq_row, &[-1], true)?)?)?;
26 let cfactor = rsqrt(exp_avg_sq_col)?;
27 matmul(&rfactor.expand_dims(-1)?, &cfactor.expand_dims(0)?)
28}
29
30pub type AdafactorEps = (f32, f32);
32
33#[derive(Debug, Clone)]
35pub struct AdafactorState {
36 pub(crate) step: Array,
37 pub(crate) exp_avg_sq_row: Option<Array>,
38 pub(crate) exp_avg_sq_col: Option<Array>,
39 pub(crate) exp_avg_sq: Option<Array>,
40 pub(crate) exp_avg: Option<Array>,
41}
42
43impl OptimizerState for State<AdafactorState> {
44 type UnflattenError = UnflattenError;
45
46 fn state_projection(&mut self) -> Result<StateProjection<'_>, StateProjectionError> {
47 let mut projection = StateProjection::new();
48 for (key, state) in self {
49 projection.required(format!("{key}.step"), &mut state.step)?;
50 projection.optional(format!("{key}.exp_avg_sq_row"), &mut state.exp_avg_sq_row)?;
51 projection.optional(format!("{key}.exp_avg_sq_col"), &mut state.exp_avg_sq_col)?;
52 projection.optional(format!("{key}.exp_avg_sq"), &mut state.exp_avg_sq)?;
53 projection.optional(format!("{key}.exp_avg"), &mut state.exp_avg)?;
54 }
55 Ok(projection)
56 }
57
58 fn unflatten<I, K>(input: I) -> Result<Self, Self::UnflattenError>
59 where
60 Self: Sized,
61 I: IntoIterator<Item = (K, Array)>,
62 K: Ord + AsRef<str> + Into<Rc<str>>,
63 {
64 let mut state = State::new();
65 let iter = input
66 .into_iter()
67 .sorted_by(|a, b| a.0.as_ref().cmp(b.0.as_ref()));
68
69 for (k, v) in iter {
70 let key = k.into();
71 let mut parts = key.rsplit('.');
72 let suffix = parts.next().ok_or(UnflattenError::InvalidKey)?;
73 let prefix = parts.next().ok_or(UnflattenError::InvalidKey)?;
74
75 let prefix = Rc::from(prefix);
76 let state = state.entry(prefix).or_insert_with(|| AdafactorState {
77 step: array!(AdafactorState::DEFAULT_STEP),
78 exp_avg_sq_row: None,
79 exp_avg_sq_col: None,
80 exp_avg_sq: None,
81 exp_avg: None,
82 });
83
84 match suffix {
85 "step" => state.step = v,
86 "exp_avg_sq_row" => state.exp_avg_sq_row = Some(v),
87 "exp_avg_sq_col" => state.exp_avg_sq_col = Some(v),
88 "exp_avg_sq" => state.exp_avg_sq = Some(v),
89 "exp_avg" => state.exp_avg = Some(v),
90 _ => return Err(UnflattenError::InvalidKey),
91 }
92 }
93
94 Ok(state)
95 }
96}
97
98impl AdafactorState {
99 pub const DEFAULT_STEP: i32 = 0;
101
102 fn new(parameter: &Array, beta1_is_some: bool) -> crate::error::Result<Self> {
103 let step = array!(Self::DEFAULT_STEP);
104 let mut exp_avg_sq_row = None;
105 let mut exp_avg_sq_col = None;
106 let mut exp_avg_sq = None;
107 let mut exp_avg = None;
108
109 if parameter.ndim() >= 2 {
110 let shape = parameter.shape();
111 let dtype = parameter.dtype();
112
113 let row_shape = &shape[..shape.len() - 1];
114 exp_avg_sq_row = Some(zeros_dtype(row_shape, dtype)?);
115
116 let mut col_shape = shape[..shape.len() - 2].to_vec();
117 col_shape.push(*shape.last().unwrap());
118 exp_avg_sq_col = Some(zeros_dtype(&col_shape, dtype)?);
119 } else {
120 exp_avg_sq = Some(zeros_like(parameter)?);
121 };
122
123 if beta1_is_some {
124 exp_avg = Some(zeros_like(parameter)?);
125 }
126
127 Ok(Self {
128 step,
129 exp_avg_sq_row,
130 exp_avg_sq_col,
131 exp_avg_sq,
132 exp_avg,
133 })
134 }
135}
136
137pub type AdafactorBuilderLr = Option<f32>;
140
141pub type AdafactorLr = Option<Array>;
143
144pub type AdafactorBuilderBeta1 = Option<f32>;
147
148pub type AdafactorBeta1 = Option<Array>;
150
151generate_builder! {
152 #[derive(Debug, Clone, Buildable)]
158 #[buildable(root = crate)]
159 #[builder(
160 build_with = build_adafactor,
161 err = AdafactorBuildError,
162 root = crate
163 )]
164 pub struct Adafactor {
165 #[builder(optional, default = Adafactor::DEFAULT_LR)]
167 pub lr: Option<f32>,
168
169 #[builder(optional, ty_override = AdafactorEps, default = Adafactor::DEFAULT_EPS)]
172 pub eps: (Array, Array),
173
174 #[builder(optional, ty_override = f32, default = Adafactor::DEFAULT_CLIP_THRESHOLD)]
176 pub clip_threshold: Array,
177
178 #[builder(optional, ty_override = f32, default = Adafactor::DEFAULT_DECAY_RATE)]
181 pub decay_rate: Array,
182
183 #[builder(optional, ty_override = AdafactorBuilderBeta1, default = Adafactor::DEFAULT_BETA1)]
185 pub beta1: AdafactorBeta1,
186
187 #[builder(optional, default = Adafactor::DEFAULT_WEIGHT_DECAY)]
189 pub weight_decay: f32,
190
191 #[builder(optional, default = Adafactor::DEFAULT_SCALE_PARAMETER)]
194 pub scale_parameter: bool,
195
196 #[builder(optional, ty_override = bool, default = Adafactor::DEFAULT_RELATIVE_STEP)]
199 pub relative_step: bool,
200
201 #[builder(optional, default = Adafactor::DEFAULT_WARMUP_INIT)]
204 pub warmup_init: bool,
205
206 #[builder(ignore)]
208 pub state: State<AdafactorState>,
209 }
210}
211
212fn build_adafactor(builder: AdafactorBuilder) -> Result<Adafactor, AdafactorBuildError> {
214 let eps = builder.eps;
215 let clip_threshold = builder.clip_threshold;
216 let decay_rate = builder.decay_rate;
217 let weight_decay = builder.weight_decay;
218 let scale_parameter = builder.scale_parameter;
219 let relative_step = builder.relative_step;
220 let warmup_init = builder.warmup_init;
221
222 if builder.lr.is_none() && !relative_step {
223 return Err(AdafactorBuildError::LrIsNoneAndRelativeStepIsFalse);
224 }
225
226 Ok(Adafactor {
227 lr: builder.lr,
228 eps: (array!(eps.0), array!(eps.1)),
229 clip_threshold: array!(clip_threshold),
230 decay_rate: array!(decay_rate),
231 beta1: builder.beta1.map(Array::from),
232 weight_decay,
233 scale_parameter,
234 relative_step,
235 warmup_init,
236 state: State::new(),
237 })
238}
239
240impl Adafactor {
241 pub const DEFAULT_LR: Option<f32> = None;
243
244 pub const DEFAULT_EPS: (f32, f32) = (1e-30, 1e-3);
246
247 pub const DEFAULT_CLIP_THRESHOLD: f32 = 1.0;
249
250 pub const DEFAULT_DECAY_RATE: f32 = -0.8;
252
253 pub const DEFAULT_WEIGHT_DECAY: f32 = 0.0;
255
256 pub const DEFAULT_SCALE_PARAMETER: bool = true;
258
259 pub const DEFAULT_RELATIVE_STEP: bool = true;
261
262 pub const DEFAULT_WARMUP_INIT: bool = false;
264
265 pub const DEFAULT_BETA1: Option<f32> = None;
267}
268
269fn get_mut_or_insert_with<'a, T, E>(
270 map: &'a mut HashMap<Rc<str>, T>,
271 key: &Rc<str>,
272 f: impl FnOnce() -> Result<T, E>,
273) -> Result<&'a mut T, E> {
274 if !map.contains_key(key) {
275 map.insert(key.clone(), f()?);
276 }
277
278 Ok(map.get_mut(key).unwrap())
279}
280
281fn compute_lr(
282 relative_step: bool,
283 warmup_init: bool,
284 lr: Option<f32>,
285 scale_parameter: bool,
286 eps: &(Array, Array),
287 step: &Array,
288 parameter_rms: &Array,
289) -> crate::error::Result<Array> {
290 let relative_step_size = if relative_step {
291 let min_step = if warmup_init {
292 array!(1e-6) * step
294 } else {
295 array!(1e-2)
296 };
297 minimum(min_step, array!(1.0) / sqrt(step)?)?
299 } else {
300 array!(lr.expect("The learning rate should be set if the relative step is not enabled"))
302 };
303
304 let mut parameter_scale = array!(1.0);
305 if scale_parameter {
306 parameter_scale = maximum(&eps.1, parameter_rms)?;
307 }
308
309 parameter_scale.multiply(relative_step_size)
310}
311
312impl Optimizer for Adafactor {
313 type State = State<AdafactorState>;
314
315 fn state(&self) -> &Self::State {
316 &self.state
317 }
318
319 fn state_mut(&mut self) -> &mut Self::State {
320 &mut self.state
321 }
322
323 fn update_single(
324 &mut self,
325 key: &std::rc::Rc<str>,
326 gradient: &Array,
327 parameter: &mut Array,
328 ) -> crate::error::Result<()> {
329 let beta1_is_some = self.beta1.is_some();
330 let state = get_mut_or_insert_with(&mut self.state, key, || {
331 AdafactorState::new(parameter, beta1_is_some)
332 })?;
333
334 state.step = state.step.add(array!(1))?;
335
336 let gradient_shape = gradient.shape();
337 let factored = gradient_shape.len() >= 2;
338 let step = &state.step;
339
340 let parameter_rms = rms(parameter)?;
341 let lr = compute_lr(
342 self.relative_step,
343 self.warmup_init,
344 self.lr,
345 self.scale_parameter,
346 &self.eps,
347 step,
348 ¶meter_rms,
349 )?;
350 let beta2 = array!(1.0).subtract(&step.power(&self.decay_rate)?)?;
351
352 let mut update: Cow<Array> = Cow::Owned(gradient.square()?.add(&self.eps.0)?);
353
354 let one_minus_beta2 = array!(1.0).subtract(&beta2)?;
355 if factored {
356 let exp_avg_sq_row = state.exp_avg_sq_row.as_mut().unwrap();
358 let exp_avg_sq_col = state.exp_avg_sq_col.as_mut().unwrap();
359
360 *exp_avg_sq_row = beta2
361 .multiply(&*exp_avg_sq_row)?
362 .add(&one_minus_beta2.multiply(&update.mean_axes(&[-1], None)?)?)?;
363 *exp_avg_sq_col = beta2
364 .multiply(&*exp_avg_sq_col)?
365 .add(&one_minus_beta2.multiply(&update.mean_axes(&[-2], None)?)?)?;
366
367 update = Cow::Owned(approvate_exp_moving_avg(
368 &*exp_avg_sq_row,
369 &*exp_avg_sq_col,
370 )?);
371 update = Cow::Owned(update.multiply(gradient)?);
372 } else {
373 let exp_avg_sq = state.exp_avg_sq.as_mut().unwrap();
375
376 *exp_avg_sq = beta2
377 .multiply(&*exp_avg_sq)?
378 .add(&one_minus_beta2.multiply(&update)?)?;
379 update = Cow::Owned(rsqrt(&*exp_avg_sq)?.multiply(gradient)?);
380 }
381
382 let update_rms = rms(&update)?;
383 let max = maximum(array!(1.0), update_rms.divide(&self.clip_threshold)?)?;
384 update = Cow::Owned(update.divide(max)?);
385 update = Cow::Owned(lr.multiply(update)?);
386
387 if let Some(beta1) = &self.beta1 {
388 let exp_avg = state.exp_avg.as_mut().unwrap();
390 let one_minus_beta1 = array!(1.0).subtract(beta1)?;
391 *exp_avg = beta1
392 .multiply(&*exp_avg)?
393 .add(&one_minus_beta1.multiply(&update)?)?;
394 update = Cow::Borrowed(&*exp_avg);
395 }
396
397 if self.weight_decay != 0.0 {
398 let rhs = parameter.multiply(array!(-self.weight_decay).multiply(lr)?)?;
399 *parameter = parameter.add(rhs)?;
400 }
401
402 *parameter = parameter.subtract(&update)?;
403
404 Ok(())
405 }
406}
407
408impl Updatable for Adafactor {
409 optimizer_updatable_state_methods!();
410}
411
412impl_updatable_for_mut_optimizer!(Adafactor);