use candle_core::{Result, Tensor, DType};
use candle_nn::VarMap;
use std::collections::HashMap;
use super::sync::SyncedGradient;
#[derive(Debug, Clone)]
pub struct AdamWConfig {
pub lr: f64,
pub beta1: f64,
pub beta2: f64,
pub eps: f64,
pub weight_decay: f64,
}
impl Default for AdamWConfig {
fn default() -> Self {
Self {
lr: 3e-4,
beta1: 0.9,
beta2: 0.999,
eps: 1e-8,
weight_decay: 0.01,
}
}
}
impl AdamWConfig {
pub fn with_lr(lr: f64) -> Self {
Self { lr, ..Default::default() }
}
}
pub struct DistributedAdamW {
m: HashMap<String, Tensor>,
v: HashMap<String, Tensor>,
config: AdamWConfig,
step_count: usize,
}
impl DistributedAdamW {
pub fn new(varmap: &VarMap, config: AdamWConfig) -> Result<Self> {
let data = varmap.data().lock().unwrap();
let mut m = HashMap::with_capacity(data.len());
let mut v = HashMap::with_capacity(data.len());
for (name, var) in data.iter() {
let tensor = var.as_tensor();
let zeros = Tensor::zeros(tensor.shape(), tensor.dtype(), tensor.device())?;
m.insert(name.clone(), zeros.clone());
v.insert(name.clone(), zeros);
}
Ok(Self {
m,
v,
config,
step_count: 0,
})
}
#[allow(dead_code)]
pub fn with_lr(varmap: &VarMap, lr: f64) -> Result<Self> {
Self::new(varmap, AdamWConfig::with_lr(lr))
}
pub fn step(&mut self, synced_grads: &[SyncedGradient]) -> Result<()> {
self.step_count += 1;
let t = self.step_count as f64;
let bias_correction1 = 1.0 - self.config.beta1.powf(t);
let bias_correction2 = 1.0 - self.config.beta2.powf(t);
for (name, var, grad) in synced_grads {
let m_t = self.m.get_mut(name).ok_or_else(|| {
candle_core::Error::Msg(format!("Unknown parameter: {}", name))
})?;
let v_t = self.v.get_mut(name).ok_or_else(|| {
candle_core::Error::Msg(format!("Unknown parameter: {}", name))
})?;
let grad = {
let grad_device = format!("{:?}", grad.device());
let param_device = format!("{:?}", var.device());
if grad_device != param_device {
grad.to_device(var.device())?
} else {
grad.clone()
}
};
let m_scaled = (m_t.clone() * self.config.beta1)?;
let grad_scaled = (grad.clone() * (1.0 - self.config.beta1))?;
let new_m = m_scaled.add(&grad_scaled)?;
let grad_sq = grad.sqr()?;
let v_scaled = (v_t.clone() * self.config.beta2)?;
let grad_sq_scaled = (grad_sq * (1.0 - self.config.beta2))?;
let new_v = v_scaled.add(&grad_sq_scaled)?;
*m_t = new_m.clone();
*v_t = new_v.clone();
let m_hat = (&new_m / bias_correction1)?;
let v_hat = (&new_v / bias_correction2)?;
let v_sqrt = v_hat.sqrt()?;
let v_sqrt_eps = (&v_sqrt + self.config.eps)?;
let adam_update = (m_hat.div(&v_sqrt_eps)? * self.config.lr)?;
let param = var.as_tensor();
let weight_decay_update = (param * (self.config.lr * self.config.weight_decay))?;
let param_after_adam = param.sub(&adam_update)?;
let new_param = param_after_adam.sub(&weight_decay_update)?;
var.set(&new_param)?;
}
Ok(())
}
#[allow(dead_code)]
pub fn step_count(&self) -> usize {
self.step_count
}
#[allow(dead_code)]
pub fn learning_rate(&self) -> f64 {
self.config.lr
}
#[allow(dead_code)]
pub fn set_learning_rate(&mut self, lr: f64) {
self.config.lr = lr;
}
}
pub fn compute_grad_norm(synced_grads: &[SyncedGradient]) -> Result<f64> {
let mut total_norm_sq = 0.0f64;
for (_, _, grad) in synced_grads {
let grad_norm_sq = grad.sqr()?
.sum_all()?
.to_dtype(DType::F64)?
.to_vec0::<f64>()?;
total_norm_sq += grad_norm_sq;
}
Ok(total_norm_sq.sqrt())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_adamw_config_default() {
let config = AdamWConfig::default();
assert_eq!(config.lr, 3e-4);
assert_eq!(config.beta1, 0.9);
assert_eq!(config.beta2, 0.999);
}
#[test]
fn test_adamw_config_with_lr() {
let config = AdamWConfig::with_lr(1e-4);
assert_eq!(config.lr, 1e-4);
assert_eq!(config.beta1, 0.9); }
}