use anyhow::Result;
pub trait BackendOptimizer {
type Module;
fn clip_grad_norm(&mut self, max: f64);
fn step_module(&mut self, module: Self::Module) -> Self::Module;
fn learning_rate(&self) -> f64;
}
pub struct BurnOptimizer<B, M, O>
where
B: burn::tensor::backend::AutodiffBackend,
M: burn::module::AutodiffModule<B>,
O: burn::optim::Optimizer<M, B>,
{
inner: O,
learning_rate: f64,
grad_clip_norm: Option<f64>,
_marker: core::marker::PhantomData<(B, M)>,
}
impl<B, M, O> std::fmt::Debug for BurnOptimizer<B, M, O>
where
B: burn::tensor::backend::AutodiffBackend,
M: burn::module::AutodiffModule<B>,
O: burn::optim::Optimizer<M, B>,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BurnOptimizer")
.field("learning_rate", &self.learning_rate)
.field("grad_clip_norm", &self.grad_clip_norm)
.field("inner", &"burn::optim::Optimizer<...>")
.finish()
}
}
impl<B, M, O> BurnOptimizer<B, M, O>
where
B: burn::tensor::backend::AutodiffBackend,
M: burn::module::AutodiffModule<B>,
O: burn::optim::Optimizer<M, B>,
{
pub fn new(inner: O, learning_rate: f64) -> Self {
Self { inner, learning_rate, grad_clip_norm: None, _marker: core::marker::PhantomData }
}
pub fn inner(&self) -> &O {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut O {
&mut self.inner
}
pub fn grad_clip_norm(&self) -> Option<f64> {
self.grad_clip_norm
}
}
impl<B, M, O> BackendOptimizer for BurnOptimizer<B, M, O>
where
B: burn::tensor::backend::AutodiffBackend,
M: burn::module::AutodiffModule<B>,
O: burn::optim::Optimizer<M, B>,
{
type Module = M;
fn clip_grad_norm(&mut self, max: f64) {
self.grad_clip_norm = Some(max);
}
fn step_module(&mut self, module: Self::Module) -> Self::Module {
module
}
fn learning_rate(&self) -> f64 {
self.learning_rate
}
}
pub fn wrap_burn<B, M, O>(inner: O, learning_rate: f64) -> BurnOptimizer<B, M, O>
where
B: burn::tensor::backend::AutodiffBackend,
M: burn::module::AutodiffModule<B>,
O: burn::optim::Optimizer<M, B>,
{
BurnOptimizer::new(inner, learning_rate)
}
pub type OptimResult<T> = Result<T>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn burn_optimizer_satisfies_trait() {
use burn::{
backend::{Autodiff, NdArray},
optim::AdamConfig,
};
type B = Autodiff<NdArray<f32>>;
let device = Default::default();
let module = crate::policy::mlp::MlpBurnPolicy::<B>::new(2, 2, 4, &device);
let inner_opt = AdamConfig::new().init();
let mut opt: BurnOptimizer<B, crate::policy::mlp::MlpBurnPolicy<B>, _> =
BurnOptimizer::new(inner_opt, 1e-3);
opt.clip_grad_norm(0.5);
assert_eq!(opt.grad_clip_norm(), Some(0.5));
let module = opt.step_module(module);
let _ = module;
assert!((opt.learning_rate() - 1e-3).abs() < 1e-12);
}
}