use alloc::sync::Arc;
use core::any::Any;
use crate::{RecordState, StateSink, StateSource};
use burn_core as burn;
use burn_core::tensor::kind::BridgeTensor;
use crate::LearningRate;
use burn::tensor::{Device, Tensor};
pub trait Optimizer: Send + Sync + Clone + 'static {
type State<const D: usize>: Clone + RecordState;
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>>);
fn to_device<const D: usize>(state: Self::State<D>, device: &Device) -> Self::State<D>;
}
#[derive(Clone)]
pub struct DynState {
state: Arc<dyn Any + Send + Sync>,
rank: usize,
}
impl DynState {
pub fn create<T: Send + Sync + 'static>(state: T, rank: usize) -> Self {
Self {
state: Arc::new(state),
rank,
}
}
pub fn downcast<T: Clone + Send + Sync + 'static>(self) -> T {
let state = self
.state
.downcast::<T>()
.expect("The dynamic optimizer state should match the optimizer state type.");
Arc::try_unwrap(state).unwrap_or_else(|state| (*state).clone())
}
pub fn downcast_ref<T: 'static>(&self) -> &T {
self.state
.downcast_ref::<T>()
.expect("The dynamic optimizer state should match the optimizer state type.")
}
pub fn rank(&self) -> usize {
self.rank
}
}
macro_rules! dispatch_rank {
($rank:expr, $d:ident => $body:block) => {
match $rank {
0 => {
const $d: usize = 0;
$body
}
1 => {
const $d: usize = 1;
$body
}
2 => {
const $d: usize = 2;
$body
}
3 => {
const $d: usize = 3;
$body
}
4 => {
const $d: usize = 4;
$body
}
5 => {
const $d: usize = 5;
$body
}
6 => {
const $d: usize = 6;
$body
}
7 => {
const $d: usize = 7;
$body
}
8 => {
const $d: usize = 8;
$body
}
other => panic!("Unsupported tensor rank for optimizer state: {other}"),
}
};
}
pub trait DynOptimizer: Send + Sync {
fn step_dyn(
&self,
rank: usize,
lr: LearningRate,
tensor: BridgeTensor,
grad: BridgeTensor,
state: Option<DynState>,
) -> (BridgeTensor, Option<DynState>);
fn to_device_dyn(&self, state: DynState, device: &Device) -> DynState;
fn state_flatten(&self, prefix: &str, state: &DynState, out: &mut StateSink);
fn state_unflatten(
&self,
rank: usize,
prefix: &str,
src: &mut StateSource,
device: &Device,
) -> Option<DynState>;
}
impl<O: Optimizer> DynOptimizer for O {
fn step_dyn(
&self,
rank: usize,
lr: LearningRate,
tensor: BridgeTensor,
grad: BridgeTensor,
state: Option<DynState>,
) -> (BridgeTensor, Option<DynState>) {
dispatch_rank!(rank, D => {
let (tensor, state) = self.step(
lr,
Tensor::<D>::from_bridge(tensor),
Tensor::<D>::from_bridge(grad),
state.map(|state| state.downcast::<O::State<D>>()),
);
(tensor.into_bridge(), state.map(|state| DynState::create(state, D)))
})
}
fn to_device_dyn(&self, state: DynState, device: &Device) -> DynState {
dispatch_rank!(state.rank(), D => {
let state = O::to_device::<D>(state.downcast::<O::State<D>>(), device);
DynState::create(state, D)
})
}
fn state_flatten(&self, prefix: &str, state: &DynState, out: &mut StateSink) {
dispatch_rank!(state.rank(), D => {
RecordState::state_flatten(state.downcast_ref::<O::State<D>>(), prefix, out);
})
}
fn state_unflatten(
&self,
rank: usize,
prefix: &str,
src: &mut StateSource,
device: &Device,
) -> Option<DynState> {
dispatch_rank!(rank, D => {
let state = <O::State<D> as RecordState>::state_unflatten(prefix, src, device)?;
Some(DynState::create(state, D))
})
}
}