use crate::checkpoint::{
AsyncCheckpointer, Checkpointer, CheckpointingAction, CheckpointingStrategy,
};
use crate::metric::store::EventStoreClient;
use crate::{
CloneEarlyStoppingStrategy, LearnerModel, TrainOutput, TrainStep, TrainingError,
TrainingModelInput, TrainingModelOutput,
};
use burn_core::store::ModuleRecord;
use burn_core::tensor::Device;
use burn_optim::lr_scheduler::LrSchedulerRecord;
use burn_optim::lr_scheduler::module_lr_scheduler::{ModuleLearningRate, ModuleLrScheduler};
use burn_optim::{GradientsParams, ModuleOptimizer, MultiGradientsParams, OptimizerRecord};
use std::marker::PhantomData;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
pub struct Learner<M: LearnerModel> {
pub(crate) model: M,
optim: ModuleOptimizer,
lr_scheduler: ModuleLrScheduler,
lr_module: ModuleLearningRate,
}
impl<M: LearnerModel> Clone for Learner<M> {
fn clone(&self) -> Self {
Self {
model: self.model.clone(),
optim: self.optim.clone(),
lr_scheduler: self.lr_scheduler.clone(),
lr_module: self.lr_module.clone(),
}
}
}
impl<M: LearnerModel> Learner<M> {
pub fn new(
model: M,
optim: ModuleOptimizer,
lr_scheduler: impl Into<ModuleLrScheduler>,
) -> Self {
Self {
model,
optim,
lr_scheduler: lr_scheduler.into(),
lr_module: 0.0.into(),
}
}
}
impl<M: LearnerModel> Learner<M> {
pub fn fork(&mut self, device: &Device) {
self.model = self.model().fork(device);
}
pub fn model(&self) -> M {
self.model.clone()
}
pub fn lr_current(&self) -> ModuleLearningRate {
self.lr_module.clone()
}
pub fn lr_step(&mut self) {
self.lr_module = self.lr_scheduler.step();
}
pub fn train_step(&self, item: TrainingModelInput<M>) -> TrainOutput<TrainingModelOutput<M>> {
TrainStep::step(&self.model, item)
}
pub fn optimizer_step(&mut self, grads: GradientsParams) {
self.model = self
.model()
.optimize(&mut self.optim, self.lr_module.clone(), grads);
}
pub fn optimizer_step_multi(&mut self, grads: MultiGradientsParams) {
self.model = self
.model()
.optimize_multi(&mut self.optim, self.lr_module.clone(), grads);
}
pub fn load_model(&mut self, record: ModuleRecord) {
self.model = self.model.clone().load_record(record);
}
pub fn load_optim(&mut self, record: OptimizerRecord) {
self.optim = self.optim.clone().load_record(record);
}
pub fn load_scheduler(&mut self, record: LrSchedulerRecord) {
self.lr_scheduler = self.lr_scheduler.clone().load_record(record);
}
}
pub struct LearningCheckpointer<M: LearnerModel> {
model: AsyncCheckpointer<ModuleRecord>,
optim: AsyncCheckpointer<OptimizerRecord>,
lr_scheduler: AsyncCheckpointer<LrSchedulerRecord>,
strategy: Box<dyn CheckpointingStrategy>,
_phantom: PhantomData<M>,
}
impl<M: LearnerModel> LearningCheckpointer<M> {
pub fn new(
model: AsyncCheckpointer<ModuleRecord>,
optim: AsyncCheckpointer<OptimizerRecord>,
lr_scheduler: AsyncCheckpointer<LrSchedulerRecord>,
strategy: Box<dyn CheckpointingStrategy>,
) -> Self {
Self {
model,
optim,
lr_scheduler,
strategy,
_phantom: PhantomData,
}
}
pub fn checkpoint(&mut self, learner: &Learner<M>, epoch: usize, store: &EventStoreClient) {
let actions = self.strategy.checkpointing(epoch, store);
for action in actions {
match action {
CheckpointingAction::Delete(epoch) => {
self.model
.delete(epoch)
.expect("Can delete model checkpoint.");
self.optim
.delete(epoch)
.expect("Can delete optimizer checkpoint.");
self.lr_scheduler
.delete(epoch)
.expect("Can delete learning rate scheduler checkpoint.");
}
CheckpointingAction::Save => {
self.model
.save(epoch, learner.model.clone().into_record())
.expect("Can save model checkpoint.");
self.optim
.save(epoch, learner.optim.to_record())
.expect("Can save optimizer checkpoint.");
self.lr_scheduler
.save(epoch, learner.lr_scheduler.to_record())
.expect("Can save learning rate scheduler checkpoint.");
}
}
}
}
pub fn load_checkpoint(&self, mut learner: Learner<M>, epoch: usize) -> Learner<M> {
let record = self
.model
.restore(epoch)
.expect("Can load model checkpoint.");
learner.load_model(record);
let record = self
.optim
.restore(epoch)
.expect("Can load optimizer checkpoint.");
learner.load_optim(record);
let record = self
.lr_scheduler
.restore(epoch)
.expect("Can load learning rate scheduler checkpoint.");
learner.load_scheduler(record);
learner
}
}
pub(crate) type EarlyStoppingStrategyRef = Box<dyn CloneEarlyStoppingStrategy>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Interruption {
pub reason: Option<String>,
}
#[derive(Clone, Default)]
pub struct Interrupter {
state: Arc<AtomicBool>,
message: Arc<Mutex<Option<String>>>,
error: Arc<Mutex<Option<Arc<TrainingError>>>>,
}
impl Interrupter {
pub fn new() -> Self {
Self::default()
}
pub fn stop(&self, reason: Option<&str>) {
self.state.store(true, Ordering::Relaxed);
reason.inspect(|r| {
let mut message = self.message.lock().unwrap();
*message = Some(String::from(*r));
});
}
pub fn fail(&self, error: impl Into<TrainingError>) {
let error = error.into();
log::error!("Stopped by an error: {error}");
let mut slot = self.error.lock().unwrap();
if slot.is_none() {
*slot = Some(Arc::new(error));
}
self.state.store(true, Ordering::Relaxed);
}
pub fn fail_on_error<E: Into<TrainingError>>(&self, result: Result<(), E>) {
if let Err(error) = result {
self.fail(error);
}
}
pub fn reset(&self) {
self.state.store(false, Ordering::Relaxed);
self.error.lock().unwrap().take();
}
pub fn should_stop(&self) -> bool {
self.state.load(Ordering::Relaxed)
}
pub fn interruption(&self) -> Option<Interruption> {
(self.should_stop() && self.error().is_none()).then(|| Interruption {
reason: self.get_message(),
})
}
pub fn error(&self) -> Option<Arc<TrainingError>> {
self.error.lock().unwrap().clone()
}
pub fn get_message(&self) -> Option<String> {
let message = self.message.lock().unwrap();
message.clone()
}
}