use crate::learner::base::Interrupter;
use crate::metric::processor::{EventProcessorTraining, LearnerEvent, TrainingItem};
use crate::train::MultiDevicesTrainStep;
use crate::{
Learner, LearnerModel, MultiDeviceOptim, SupervisedTrainingEventProcessor, TrainLoader,
};
use burn_core::data::dataloader::Progress;
use burn_core::tensor::Device;
use burn_optim::GradientsAccumulator;
use burn_optim::MultiGradientsParams;
#[derive(new)]
pub struct MultiDeviceTrainEpoch<M: LearnerModel> {
dataloaders: Vec<TrainLoader<M>>,
grad_accumulation: Option<usize>,
}
impl<M: LearnerModel> MultiDeviceTrainEpoch<M> {
#[allow(clippy::too_many_arguments)]
pub fn run(
&self,
learner: &mut Learner<M>,
global_progress: &Progress,
event_processor: &mut SupervisedTrainingEventProcessor<M>,
interrupter: &Interrupter,
devices: Vec<Device>,
strategy: MultiDeviceOptim,
) {
match strategy {
MultiDeviceOptim::OptimMainDevice => self.run_optim_main(
learner,
global_progress,
event_processor,
interrupter,
devices,
),
MultiDeviceOptim::OptimSharded => self.run_optim_distr(
learner,
global_progress,
event_processor,
interrupter,
devices,
),
}
}
fn run_optim_main(
&self,
learner: &mut Learner<M>,
global_progress: &Progress,
event_processor: &mut SupervisedTrainingEventProcessor<M>,
interrupter: &Interrupter,
devices: Vec<Device>,
) {
let epoch = global_progress.items_processed;
log::info!(
"Executing training step for epoch {} on devices {:?}",
epoch,
devices
);
let mut iterators = self
.dataloaders
.iter()
.map(|d| d.iter())
.collect::<Vec<_>>();
interrupter.fail_on_error(event_processor.process_train(LearnerEvent::StartSplit {
epoch_number: epoch,
progress: Progress::aggregate(iterators.iter().map(|it| it.progress())),
}));
let mut iteration = 0;
let mut accumulator = GradientsAccumulator::new();
let mut accumulation_current = 0;
let accumulation = self.grad_accumulation.unwrap_or(1);
let step = MultiDevicesTrainStep::<M>::new(&devices);
let device_main = devices.first().expect("A minimum of one device.").clone();
while !interrupter.should_stop() {
let (items, progress) = match step.step(iterators.as_mut_slice(), &learner.model()) {
Ok(result) => result,
Err(err) => {
interrupter.fail(err);
break;
}
};
if items.is_empty() {
break;
}
let mut progress_items = Vec::with_capacity(items.len());
for item in items.into_iter() {
let grads = item.output.grads.to_device(&device_main, &learner.model());
accumulator.accumulate(&learner.model(), grads);
progress_items.push(item.output.item);
}
accumulation_current += 1;
if accumulation <= accumulation_current {
let grads = accumulator.grads();
learner.lr_step();
learner.optimizer_step(grads);
accumulation_current = 0;
}
for item in progress_items {
iteration += 1;
let item = TrainingItem::new(
item,
progress.clone(),
Some(iteration),
Some(learner.lr_current()),
);
interrupter.fail_on_error(
event_processor.process_train(LearnerEvent::ProcessedItem(item)),
);
}
if interrupter.should_stop() {
break;
}
}
interrupter.fail_on_error(event_processor.flush());
if accumulation_current > 0 && !interrupter.should_stop() {
let grads = accumulator.grads();
learner.lr_step();
learner.optimizer_step(grads);
}
let mut progress = Progress::aggregate(iterators.iter().map(|it| it.progress()));
if !interrupter.should_stop() {
progress.resolve_total();
}
interrupter.fail_on_error(
event_processor.process_train(LearnerEvent::EndSplit { epoch, progress }),
);
interrupter.fail_on_error(event_processor.flush());
}
fn run_optim_distr(
&self,
learner: &mut Learner<M>,
global_progress: &Progress,
event_processor: &mut SupervisedTrainingEventProcessor<M>,
interrupter: &Interrupter,
devices: Vec<Device>,
) {
let epoch = global_progress.items_processed;
log::info!(
"Executing training step for epoch {} on devices {:?}",
epoch,
devices
);
let mut iterators = self
.dataloaders
.iter()
.map(|d| d.iter())
.collect::<Vec<_>>();
interrupter.fail_on_error(event_processor.process_train(LearnerEvent::StartSplit {
epoch_number: epoch,
progress: Progress::aggregate(iterators.iter().map(|it| it.progress())),
}));
let mut iteration = 0;
let mut accumulators: Vec<GradientsAccumulator<_>> = (0..devices.len())
.map(|_| GradientsAccumulator::new())
.collect();
let mut accumulation_current = 0;
let accumulation = self.grad_accumulation.unwrap_or(1);
let step = MultiDevicesTrainStep::<M>::new(&devices);
while !interrupter.should_stop() {
let (items, progress) = match step.step(iterators.as_mut_slice(), &learner.model()) {
Ok(result) => result,
Err(err) => {
interrupter.fail(err);
break;
}
};
if items.is_empty() {
break;
}
let mut progress_items = Vec::with_capacity(items.len());
for item in items.into_iter() {
let accumulator = &mut accumulators[item.device_id];
accumulator.accumulate(&learner.model(), item.output.grads);
progress_items.push(item.output.item);
}
accumulation_current += 1;
if accumulation <= accumulation_current {
Self::step_sharded(learner, &mut accumulators, &devices);
accumulation_current = 0;
}
for item in progress_items {
iteration += 1;
let item = TrainingItem::new(
item,
progress.clone(),
Some(iteration),
Some(learner.lr_current()),
);
interrupter.fail_on_error(
event_processor.process_train(LearnerEvent::ProcessedItem(item)),
);
}
if interrupter.should_stop() {
break;
}
}
interrupter.fail_on_error(event_processor.flush());
if accumulation_current > 0 && !interrupter.should_stop() {
Self::step_sharded(learner, &mut accumulators, &devices);
}
let mut progress = Progress::aggregate(iterators.iter().map(|it| it.progress()));
if !interrupter.should_stop() {
progress.resolve_total();
}
interrupter.fail_on_error(
event_processor.process_train(LearnerEvent::EndSplit { epoch, progress }),
);
interrupter.fail_on_error(event_processor.flush());
}
fn step_sharded(
learner: &mut Learner<M>,
accumulators: &mut [GradientsAccumulator<M>],
devices: &[Device],
) {
let mut grads = MultiGradientsParams::default();
for (device_id, accumulator) in accumulators.iter_mut().enumerate() {
grads
.grads
.push((accumulator.grads(), devices[device_id].clone()));
}
learner.lr_step();
learner.optimizer_step_multi(grads);
}
}