use std::sync::atomic::{AtomicU64, Ordering};
use crate::{Engine, Error, Matrix, Result};
use crate::ml::Parameter;
pub trait Optimizer {
fn zero_grad(&self);
fn step(&mut self) -> Result<()>;
fn learning_rate(&self) -> f32;
fn set_learning_rate(&mut self, learning_rate: f32) -> Result<()>;
fn step_count(&self) -> u64;
fn belongs_to(&self, engine: &Engine) -> bool;
}
#[allow(private_bounds)]
pub trait CheckpointOptimizer: Optimizer + checkpoint_capability::Persistence {}
impl<T> CheckpointOptimizer for T where T: Optimizer + checkpoint_capability::Persistence {}
pub struct OptimizerCheckpoint {
pub(crate) kind: &'static str,
pub(crate) step: u64,
pub(crate) learning_rate: f32,
pub(crate) beta1: f32,
pub(crate) beta2: f32,
pub(crate) epsilon: f32,
pub(crate) weight_decay: f32,
pub(crate) parameters: Vec<Parameter>,
pub(crate) first_state: Vec<Matrix>,
pub(crate) second_state: Vec<Matrix>,
}
pub struct OptimizerRestore {
pub(crate) kind: String,
pub(crate) step: u64,
pub(crate) learning_rate: f32,
pub(crate) beta1: f32,
pub(crate) beta2: f32,
pub(crate) epsilon: f32,
pub(crate) weight_decay: f32,
pub(crate) first_state: Vec<Matrix>,
pub(crate) second_state: Vec<Matrix>,
}
pub(super) mod checkpoint_capability {
use super::{OptimizerCheckpoint, OptimizerRestore};
use crate::Result;
pub trait Persistence {
fn checkpoint_state(&self) -> Result<OptimizerCheckpoint>;
fn validate_checkpoint_state(&self, state: &OptimizerRestore) -> Result<()>;
fn restore_checkpoint_state(&mut self, state: OptimizerRestore) -> Result<()>;
}
}
pub(super) struct MomentParameterState {
pub(super) parameter: Parameter,
pub(super) first_moment: Matrix,
pub(super) second_moment: Matrix,
}
pub(super) struct SgdParameterState {
pub(super) parameter: Parameter,
pub(super) momentum: Option<Matrix>,
}
pub(super) struct MuonParameterState {
pub(super) parameter: Parameter,
pub(super) momentum: Matrix,
}
pub(super) fn next_optimizer_id() -> Result<u64> {
static NEXT_OPTIMIZER_ID: AtomicU64 = AtomicU64::new(1);
NEXT_OPTIMIZER_ID
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
(value != u64::MAX).then_some(value + 1)
})
.map_err(|_| Error::resource_exhausted("optimizer identity exhausted"))
}
#[derive(Clone, Copy, Debug)]
pub struct NoOpOptimizer {
learning_rate: f32,
}
impl NoOpOptimizer {
pub fn new(learning_rate: f32) -> Result<Self> {
if !learning_rate.is_finite() || learning_rate < 0.0 {
return Err(Error::invalid_argument(
"no-op optimizer learning rate must be finite and non-negative",
));
}
Ok(Self { learning_rate })
}
}
impl Default for NoOpOptimizer {
fn default() -> Self {
Self { learning_rate: 0.0 }
}
}
impl Optimizer for NoOpOptimizer {
fn zero_grad(&self) {}
fn step(&mut self) -> Result<()> {
Ok(())
}
fn learning_rate(&self) -> f32 {
self.learning_rate
}
fn set_learning_rate(&mut self, learning_rate: f32) -> Result<()> {
if !learning_rate.is_finite() || learning_rate < 0.0 {
return Err(Error::invalid_argument(
"no-op optimizer learning rate must be finite and non-negative",
));
}
self.learning_rate = learning_rate;
Ok(())
}
fn step_count(&self) -> u64 {
0
}
fn belongs_to(&self, _engine: &Engine) -> bool {
true
}
}