use crate::{Optimizer, OptimizerError, OptimizerResult, OptimizerState};
use parking_lot::RwLock;
use std::collections::HashMap;
use std::sync::Arc;
use torsh_core::error::TorshError;
use torsh_tensor::Tensor;
pub struct AdvancedAdam {
pub lr: f64,
pub beta1: f64,
pub beta2: f64,
pub eps: f64,
pub weight_decay: f64,
pub amsgrad: bool,
pub state: HashMap<String, AdamState>,
pub step_count: u64,
pub adaptive_lr: bool,
pub gradient_clipping: Option<f64>,
pub warmup_steps: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct AdamState {
pub exp_avg: Tensor,
pub exp_avg_sq: Tensor,
pub max_exp_avg_sq: Option<Tensor>,
}
impl AdvancedAdam {
pub fn new(lr: f64) -> Self {
Self {
lr,
beta1: 0.9,
beta2: 0.999,
eps: 1e-8,
weight_decay: 0.0,
amsgrad: false,
state: HashMap::new(),
step_count: 0,
adaptive_lr: false,
gradient_clipping: None,
warmup_steps: None,
}
}
pub fn with_amsgrad(mut self) -> Self {
self.amsgrad = true;
self
}
pub fn with_weight_decay(mut self, weight_decay: f64) -> Self {
self.weight_decay = weight_decay;
self
}
pub fn with_adaptive_lr(mut self) -> Self {
self.adaptive_lr = true;
self
}
pub fn with_gradient_clipping(mut self, max_norm: f64) -> Self {
self.gradient_clipping = Some(max_norm);
self
}
pub fn with_warmup(mut self, warmup_steps: u64) -> Self {
self.warmup_steps = Some(warmup_steps);
self
}
}
impl Optimizer for AdvancedAdam {
fn step(&mut self) -> OptimizerResult<()> {
self.step_count += 1;
Ok(())
}
fn zero_grad(&mut self) {
}
fn get_lr(&self) -> Vec<f32> {
vec![self.lr as f32]
}
fn set_lr(&mut self, lr: f32) {
self.lr = lr as f64;
}
fn add_param_group(
&mut self,
_params: Vec<Arc<RwLock<Tensor>>>,
_options: HashMap<String, f32>,
) {
}
fn state_dict(&self) -> OptimizerResult<OptimizerState> {
let mut global_state = HashMap::new();
global_state.insert("lr".to_string(), self.lr as f32);
global_state.insert("beta1".to_string(), self.beta1 as f32);
global_state.insert("beta2".to_string(), self.beta2 as f32);
global_state.insert("eps".to_string(), self.eps as f32);
global_state.insert("weight_decay".to_string(), self.weight_decay as f32);
global_state.insert("step_count".to_string(), self.step_count as f32);
Ok(OptimizerState {
optimizer_type: "AdvancedAdam".to_string(),
version: "1.0".to_string(),
param_groups: vec![], state: HashMap::new(), global_state,
})
}
fn load_state_dict(&mut self, state: OptimizerState) -> OptimizerResult<()> {
if state.optimizer_type != "AdvancedAdam" {
return Err(OptimizerError::InvalidParameter(format!(
"Expected AdvancedAdam, got {}",
state.optimizer_type
)));
}
if let Some(lr) = state.global_state.get("lr") {
self.lr = *lr as f64;
}
if let Some(beta1) = state.global_state.get("beta1") {
self.beta1 = *beta1 as f64;
}
if let Some(beta2) = state.global_state.get("beta2") {
self.beta2 = *beta2 as f64;
}
if let Some(eps) = state.global_state.get("eps") {
self.eps = *eps as f64;
}
if let Some(weight_decay) = state.global_state.get("weight_decay") {
self.weight_decay = *weight_decay as f64;
}
if let Some(step_count) = state.global_state.get("step_count") {
self.step_count = *step_count as u64;
}
Ok(())
}
}
pub struct LAMB {
pub lr: f64,
pub beta1: f64,
pub beta2: f64,
pub eps: f64,
pub weight_decay: f64,
pub bias_correction: bool,
pub state: HashMap<String, LambState>,
pub step_count: u64,
}
#[derive(Debug, Clone)]
pub struct LambState {
pub exp_avg: Tensor,
pub exp_avg_sq: Tensor,
}
impl LAMB {
pub fn new(lr: f64) -> Self {
Self {
lr,
beta1: 0.9,
beta2: 0.999,
eps: 1e-6,
weight_decay: 0.01,
bias_correction: true,
state: HashMap::new(),
step_count: 0,
}
}
}
impl Optimizer for LAMB {
fn step(&mut self) -> OptimizerResult<()> {
self.step_count += 1;
Ok(())
}
fn zero_grad(&mut self) {
}
fn get_lr(&self) -> Vec<f32> {
vec![self.lr as f32]
}
fn set_lr(&mut self, lr: f32) {
self.lr = lr as f64;
}
fn add_param_group(
&mut self,
_params: Vec<Arc<RwLock<Tensor>>>,
_options: HashMap<String, f32>,
) {
}
fn state_dict(&self) -> OptimizerResult<OptimizerState> {
let mut global_state = HashMap::new();
global_state.insert("lr".to_string(), self.lr as f32);
global_state.insert("beta1".to_string(), self.beta1 as f32);
global_state.insert("beta2".to_string(), self.beta2 as f32);
global_state.insert("eps".to_string(), self.eps as f32);
global_state.insert("weight_decay".to_string(), self.weight_decay as f32);
global_state.insert("step_count".to_string(), self.step_count as f32);
Ok(OptimizerState {
optimizer_type: "LAMB".to_string(),
version: "1.0".to_string(),
param_groups: vec![], state: HashMap::new(), global_state,
})
}
fn load_state_dict(&mut self, state: OptimizerState) -> OptimizerResult<()> {
if state.optimizer_type != "LAMB" {
return Err(OptimizerError::InvalidParameter(format!(
"Expected LAMB, got {}",
state.optimizer_type
)));
}
if let Some(lr) = state.global_state.get("lr") {
self.lr = *lr as f64;
}
if let Some(beta1) = state.global_state.get("beta1") {
self.beta1 = *beta1 as f64;
}
if let Some(beta2) = state.global_state.get("beta2") {
self.beta2 = *beta2 as f64;
}
if let Some(eps) = state.global_state.get("eps") {
self.eps = *eps as f64;
}
if let Some(weight_decay) = state.global_state.get("weight_decay") {
self.weight_decay = *weight_decay as f64;
}
if let Some(step_count) = state.global_state.get("step_count") {
self.step_count = *step_count as u64;
}
Ok(())
}
}
pub struct Lookahead<T: Optimizer> {
pub base_optimizer: T,
pub alpha: f64,
pub k: u64,
pub slow_weights: HashMap<String, Tensor>,
pub step_count: u64,
}
impl<T: Optimizer> Lookahead<T> {
pub fn new(base_optimizer: T, alpha: f64, k: u64) -> Self {
Self {
base_optimizer,
alpha,
k,
slow_weights: HashMap::new(),
step_count: 0,
}
}
}
impl<T: Optimizer> Optimizer for Lookahead<T> {
fn step(&mut self) -> OptimizerResult<()> {
self.base_optimizer.step()?;
self.step_count += 1;
Ok(())
}
fn zero_grad(&mut self) {
self.base_optimizer.zero_grad();
}
fn get_lr(&self) -> Vec<f32> {
self.base_optimizer.get_lr()
}
fn set_lr(&mut self, lr: f32) {
self.base_optimizer.set_lr(lr);
}
fn add_param_group(&mut self, params: Vec<Arc<RwLock<Tensor>>>, options: HashMap<String, f32>) {
self.base_optimizer.add_param_group(params, options);
}
fn parameters(&self) -> Vec<Arc<RwLock<Tensor>>> {
self.base_optimizer.parameters()
}
fn state_dict(&self) -> OptimizerResult<OptimizerState> {
let mut base_state = self.base_optimizer.state_dict()?;
base_state
.global_state
.insert("alpha".to_string(), self.alpha as f32);
base_state
.global_state
.insert("k".to_string(), self.k as f32);
base_state
.global_state
.insert("step_count".to_string(), self.step_count as f32);
base_state.optimizer_type = format!("Lookahead<{}>", base_state.optimizer_type);
Ok(base_state)
}
fn load_state_dict(&mut self, mut state: OptimizerState) -> OptimizerResult<()> {
if let Some(alpha) = state.global_state.remove("alpha") {
self.alpha = alpha as f64;
}
if let Some(k) = state.global_state.remove("k") {
self.k = k as u64;
}
if let Some(step_count) = state.global_state.remove("step_count") {
self.step_count = step_count as u64;
}
if state.optimizer_type.starts_with("Lookahead<") && state.optimizer_type.ends_with(">") {
let base_type = &state.optimizer_type[10..state.optimizer_type.len() - 1];
state.optimizer_type = base_type.to_string();
}
self.base_optimizer.load_state_dict(state)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_advanced_adam() {
let mut optimizer = AdvancedAdam::new(0.001)
.with_amsgrad()
.with_weight_decay(0.01)
.with_gradient_clipping(1.0);
assert_eq!(optimizer.get_lr(), vec![0.001]);
assert!(optimizer.step().is_ok());
}
#[test]
fn test_lamb_optimizer() {
let mut optimizer = LAMB::new(0.001);
assert_eq!(optimizer.get_lr(), vec![0.001]);
assert!(optimizer.step().is_ok());
}
#[test]
fn test_lookahead_wrapper() {
let base_optimizer = AdvancedAdam::new(0.001);
let mut lookahead = Lookahead::new(base_optimizer, 0.5, 5);
assert_eq!(lookahead.get_lr(), vec![0.001]);
assert!(lookahead.step().is_ok());
}
}