use burn_core as burn;
use burn::{
config::Config,
tensor::{DType, Device, FloatDType, Tensor},
};
use crate::{LearningRate, RecordState, grad_clipping::GradientClippingConfig};
use super::{ModuleOptimizer, Optimizer};
#[cfg(not(feature = "std"))]
#[allow(unused_imports)]
use num_traits::Float as _;
#[derive(Config, Debug)]
pub struct AdafactorConfig {
#[config(default = 1e-30)]
epsilon_1: f32,
#[config(default = 1e-3)]
epsilon_2: f32,
#[config(default = 1.0)]
clip_threshold: f32,
#[config(default = -0.8)]
decay_rate: f64,
#[config(default = true)]
relative_step: bool,
#[config(default = true)]
scale_parameter: bool,
#[config(default = 0.0)]
weight_decay: f32,
grad_clipping: Option<GradientClippingConfig>,
}
#[derive(Clone)]
pub struct Adafactor {
epsilon_1: f32,
epsilon_2: f32,
clip_threshold: f32,
decay_rate: f64,
relative_step: bool,
scale_parameter: bool,
weight_decay: f32,
}
#[derive(RecordState, Clone)]
pub struct AdafactorState<const D: usize> {
pub time: usize,
pub second_moment: Tensor<D>,
pub column_second_moment: Option<Tensor<D>>,
}
impl AdafactorConfig {
pub fn build(&self) -> Adafactor {
assert!(
self.epsilon_1.is_finite() && self.epsilon_1 > 0.0,
"Adafactor epsilon_1 must be positive and finite"
);
assert!(
self.epsilon_2.is_finite() && self.epsilon_2 > 0.0,
"Adafactor epsilon_2 must be positive and finite"
);
assert!(
self.clip_threshold.is_finite() && self.clip_threshold > 0.0,
"Adafactor clip_threshold must be positive and finite"
);
assert!(
self.decay_rate.is_finite() && self.decay_rate < 0.0,
"Adafactor decay_rate must be negative and finite"
);
assert!(
self.weight_decay.is_finite() && self.weight_decay >= 0.0,
"Adafactor weight_decay must be nonnegative and finite"
);
Adafactor {
epsilon_1: self.epsilon_1,
epsilon_2: self.epsilon_2,
clip_threshold: self.clip_threshold,
decay_rate: self.decay_rate,
relative_step: self.relative_step,
scale_parameter: self.scale_parameter,
weight_decay: self.weight_decay,
}
}
pub fn init(&self) -> ModuleOptimizer {
let mut optimizer = ModuleOptimizer::from(self.build());
if let Some(config) = &self.grad_clipping {
optimizer = optimizer.with_grad_clipping(config.init());
}
optimizer
}
}
impl Optimizer for Adafactor {
type State<const D: usize> = AdafactorState<D>;
fn step<const D: usize>(
&self,
lr: LearningRate,
tensor: Tensor<D>,
grad: Tensor<D>,
state: Option<Self::State<D>>,
) -> (Tensor<D>, Option<Self::State<D>>) {
let dtype = tensor.dtype();
let (tensor, grad) = match dtype {
DType::F16 | DType::BF16 => (tensor.cast(FloatDType::F32), grad.cast(FloatDType::F32)),
_ => (tensor, grad),
};
let squared_grad = grad.clone().square().add_scalar(self.epsilon_1);
let (second_moment, column_second_moment) = if D >= 2 {
(
squared_grad.clone().mean_dim(D - 1),
Some(squared_grad.mean_dim(D - 2)),
)
} else {
(squared_grad, None)
};
let state = match state {
Some(mut state) => {
state.time += 1;
let new_weight = (state.time as f64).powf(self.decay_rate);
let old_weight = 1.0 - new_weight;
state.second_moment = state
.second_moment
.mul_scalar(old_weight)
.add(second_moment.mul_scalar(new_weight));
state.column_second_moment = column_second_moment.map(|column| {
state
.column_second_moment
.take()
.expect("Factored Adafactor state must contain a column second moment")
.mul_scalar(old_weight)
.add(column.mul_scalar(new_weight))
});
state
}
None => AdafactorState {
time: 1,
second_moment,
column_second_moment,
},
};
let mut update = match &state.column_second_moment {
Some(column) => {
let row = state.second_moment.clone();
let row_scale = row.clone().div(row.mean_dim(D - 2)).sqrt();
grad.div(row_scale).div(column.clone().sqrt())
}
None => grad.div(state.second_moment.clone().sqrt()),
};
let clipping = rms(update.clone())
.div_scalar(self.clip_threshold)
.clamp_min(1.0);
update = update.div(clipping.unsqueeze());
let step_size = if self.relative_step {
lr.min(1.0 / (state.time as f64).sqrt())
} else {
lr
};
if self.scale_parameter {
let scale = rms(tensor.clone()).clamp_min(self.epsilon_2);
update = update.mul(scale.unsqueeze());
}
update = update.mul_scalar(step_size);
let tensor = if self.weight_decay == 0.0 {
tensor
} else {
tensor.mul_scalar(1.0 - lr * self.weight_decay as f64)
};
let tensor = tensor - update;
let tensor = match dtype {
DType::F16 | DType::BF16 => tensor.cast(FloatDType::from(dtype)),
_ => tensor,
};
(tensor, Some(state))
}
fn to_device<const D: usize>(mut state: Self::State<D>, device: &Device) -> Self::State<D> {
state.second_moment = state.second_moment.to_device(device);
state.column_second_moment = state
.column_second_moment
.map(|column| column.to_device(device));
state
}
}
fn rms<const D: usize>(tensor: Tensor<D>) -> Tensor<1> {
tensor.square().mean().sqrt()
}
#[cfg(test)]
mod tests;