1use crate::checkpoint::{
2 AsyncCheckpointer, Checkpointer, CheckpointingStrategy, ComposedCheckpointingStrategy,
3 FileCheckpointer, KeepLastNCheckpoints, MetricCheckpointingStrategy,
4};
5use crate::learner::EarlyStoppingStrategy;
6use crate::learner::base::Interrupter;
7use crate::logger::{FileMetricLogger, MetricLogger, TrainingProgressLogger};
8use crate::metric::processor::{
9 AsyncProcessorTraining, FullEventProcessorTraining, MetricsTraining,
10};
11use crate::metric::store::{Aggregate, Direction, EventStoreClient, LogEventStore, Split};
12use crate::metric::{Adaptor, LossMetric, Metric, Numeric};
13use crate::multi::MultiDeviceLearningStrategy;
14use crate::renderer::{MetricsRenderer, default_renderer};
15use crate::single::SingleDeviceTrainingStrategy;
16use crate::{
17 ApplicationLoggerInstaller, EarlyStoppingStrategyRef, ExecutionStrategy,
18 FileApplicationLoggerInstaller, InferenceModelInput, InferenceModelOutput, InferenceStep,
19 LearnerEvent, LearnerModel, LearnerSummaryConfig, LearningCheckpointer, LearningResult,
20 TrainStep, TrainingComponents, TrainingModelInput, TrainingModelOutput, TrainingStrategy,
21};
22use crate::{Learner, SupervisedLearningStrategy};
23use burn_core::data::dataloader::DataLoader;
24use burn_core::store::ModuleRecord;
25use burn_core::tensor::Device;
26use burn_optim::OptimizerRecord;
27use burn_optim::lr_scheduler::LrSchedulerRecord;
28use std::collections::BTreeSet;
29use std::path::{Path, PathBuf};
30use std::sync::Arc;
31
32pub type TrainLoader<M> = Arc<dyn DataLoader<TrainingModelInput<M>>>;
34pub type ValidLoader<M> = Arc<dyn DataLoader<InferenceModelInput<M>>>;
36pub type SupervisedTrainingEventProcessor<M> = AsyncProcessorTraining<
38 LearnerEvent<TrainingModelOutput<M>>,
39 LearnerEvent<InferenceModelOutput<M>>,
40>;
41
42pub struct SupervisedTraining<M: LearnerModel> {
44 #[allow(clippy::type_complexity)]
46 checkpointers: Option<(
47 AsyncCheckpointer<ModuleRecord>,
48 AsyncCheckpointer<OptimizerRecord>,
49 AsyncCheckpointer<LrSchedulerRecord>,
50 )>,
51 num_epochs: usize,
52 checkpoint: Option<usize>,
53 directory: PathBuf,
54 grad_accumulation: Option<usize>,
55 grad_checkpointing: bool,
56 renderer: Option<Box<dyn MetricsRenderer + 'static>>,
57 metrics: MetricsTraining<TrainingModelOutput<M>, InferenceModelOutput<M>>,
58 event_store: LogEventStore,
59 interrupter: Interrupter,
60 tracing_logger: Option<Box<dyn ApplicationLoggerInstaller>>,
61 checkpointer_strategy: Box<dyn CheckpointingStrategy>,
62 early_stopping: Option<EarlyStoppingStrategyRef>,
63 training_strategy: Option<TrainingStrategy<M>>,
64 dataloader_train: TrainLoader<M>,
65 dataloader_valid: ValidLoader<M>,
66 summary_metrics: BTreeSet<String>,
68 summary: bool,
69 progress_logger: Option<Box<dyn TrainingProgressLogger>>,
70 label: Option<String>,
71}
72
73impl<M: LearnerModel> SupervisedTraining<M> {
74 pub fn new(
82 directory: impl AsRef<Path>,
83 dataloader_train: Arc<dyn DataLoader<<M as TrainStep>::Input>>,
84 dataloader_valid: Arc<dyn DataLoader<<M as InferenceStep>::Input>>,
85 ) -> Self {
86 let directory = directory.as_ref().to_path_buf();
87 let experiment_log_file = directory.join("experiment.log");
88 Self {
89 num_epochs: 1,
90 checkpoint: None,
91 checkpointers: None,
92 directory,
93 grad_accumulation: None,
94 grad_checkpointing: false,
95 metrics: MetricsTraining::default(),
96 event_store: LogEventStore::default(),
97 renderer: None,
98 interrupter: Interrupter::new(),
99 tracing_logger: Some(Box::new(FileApplicationLoggerInstaller::new(
100 experiment_log_file,
101 ))),
102 checkpointer_strategy: Box::new(
103 ComposedCheckpointingStrategy::builder()
104 .add(KeepLastNCheckpoints::new(2))
105 .add(MetricCheckpointingStrategy::new(
106 &LossMetric::new(), Aggregate::Mean,
108 Direction::Lowest,
109 Split::Valid,
110 ))
111 .build(),
112 ),
113 early_stopping: None,
114 training_strategy: None,
115 summary_metrics: BTreeSet::new(),
116 summary: false,
117 dataloader_train,
118 dataloader_valid,
119 progress_logger: None,
120 label: None,
121 }
122 }
123}
124
125impl<M: LearnerModel> SupervisedTraining<M> {
126 pub fn with_training_strategy(mut self, training_strategy: TrainingStrategy<M>) -> Self {
132 self.training_strategy = Some(training_strategy);
133 self
134 }
135
136 pub fn with_metric_logger<ML>(mut self, logger: ML) -> Self
142 where
143 ML: MetricLogger + 'static,
144 {
145 self.event_store.register_logger(logger);
146 self
147 }
148
149 pub fn with_progress_logger<PL>(mut self, logger: PL) -> Self
160 where
161 PL: TrainingProgressLogger + 'static,
162 {
163 self.progress_logger = Some(Box::new(logger));
164 self
165 }
166
167 pub fn with_checkpointing_strategy<CS: CheckpointingStrategy + 'static>(
169 mut self,
170 strategy: CS,
171 ) -> Self {
172 self.checkpointer_strategy = Box::new(strategy);
173 self
174 }
175
176 pub fn renderer(mut self, renderer: Box<dyn MetricsRenderer + 'static>) -> Self {
182 self.renderer = Some(renderer);
183 self
184 }
185
186 pub fn metrics<Me: MetricRegistration<M>>(self, metrics: Me) -> Self {
188 metrics.register(self)
189 }
190
191 pub fn metrics_text<Me: TextMetricRegistration<M>>(self, metrics: Me) -> Self {
193 metrics.register(self)
194 }
195
196 pub fn metric_train<Me: Metric + 'static>(mut self, metric: Me) -> Self
198 where
199 TrainingModelOutput<M>: Adaptor<Me::Input>,
200 {
201 self.metrics.register_train_metric(metric);
202 self
203 }
204
205 pub fn metric_valid<Me: Metric + 'static>(mut self, metric: Me) -> Self
207 where
208 InferenceModelOutput<M>: Adaptor<Me::Input>,
209 {
210 self.metrics.register_valid_metric(metric);
211 self
212 }
213
214 pub fn grads_accumulation(mut self, accumulation: usize) -> Self {
229 assert!(
230 accumulation > 0,
231 "Gradient accumulation must be greater than zero."
232 );
233 self.grad_accumulation = Some(accumulation);
234 self
235 }
236
237 pub fn gradient_checkpointing(mut self) -> Self {
245 self.grad_checkpointing = true;
246 self
247 }
248
249 pub fn metric_train_numeric<Me>(mut self, metric: Me) -> Self
251 where
252 Me: Metric + Numeric + 'static,
253 TrainingModelOutput<M>: Adaptor<Me::Input>,
254 {
255 self.summary_metrics.insert(metric.name().to_string());
256 self.metrics.register_train_metric_numeric(metric);
257 self
258 }
259
260 pub fn metric_valid_numeric<Me: Metric + Numeric + 'static>(mut self, metric: Me) -> Self
262 where
263 InferenceModelOutput<M>: Adaptor<Me::Input>,
264 {
265 self.summary_metrics.insert(metric.name().to_string());
266 self.metrics.register_valid_metric_numeric(metric);
267 self
268 }
269
270 pub fn num_epochs(mut self, num_epochs: usize) -> Self {
272 self.num_epochs = num_epochs;
273 self
274 }
275
276 pub fn checkpoint(mut self, checkpoint: usize) -> Self {
278 self.checkpoint = Some(checkpoint);
279 self
280 }
281
282 pub fn interrupter(&self) -> Interrupter {
284 self.interrupter.clone()
285 }
286
287 pub fn with_interrupter(mut self, interrupter: Interrupter) -> Self {
289 self.interrupter = interrupter;
290 self
291 }
292
293 pub fn early_stopping<Strategy>(mut self, strategy: Strategy) -> Self
296 where
297 Strategy: EarlyStoppingStrategy + Clone + Send + Sync + 'static,
298 {
299 self.early_stopping = Some(Box::new(strategy));
300 self
301 }
302
303 pub fn with_application_logger(
307 mut self,
308 logger: Option<Box<dyn ApplicationLoggerInstaller>>,
309 ) -> Self {
310 self.tracing_logger = logger;
311 self
312 }
313
314 pub fn with_default_checkpointers(mut self) -> Self {
317 let checkpoint_dir = self.directory.join("checkpoint");
318 let checkpointer_model = FileCheckpointer::new(&checkpoint_dir, "model");
319 let checkpointer_optimizer = FileCheckpointer::new(&checkpoint_dir, "optim");
320 let checkpointer_scheduler = FileCheckpointer::new(&checkpoint_dir, "scheduler");
321
322 self.checkpointers = Some((
323 AsyncCheckpointer::new(checkpointer_model),
324 AsyncCheckpointer::new(checkpointer_optimizer),
325 AsyncCheckpointer::new(checkpointer_scheduler),
326 ));
327
328 self
329 }
330
331 pub fn with_custom_checkpointers<CM, CO, CL>(
334 mut self,
335 module_checkpointer: CM,
336 optimizer_checkpointer: CO,
337 lr_checkpointer: CL,
338 ) -> Self
339 where
340 CM: Checkpointer<ModuleRecord> + 'static,
341 CO: Checkpointer<OptimizerRecord> + 'static,
342 CL: Checkpointer<LrSchedulerRecord> + 'static,
343 {
344 self.checkpointers = Some((
345 AsyncCheckpointer::new(module_checkpointer),
346 AsyncCheckpointer::new(optimizer_checkpointer),
347 AsyncCheckpointer::new(lr_checkpointer),
348 ));
349
350 self
351 }
352
353 pub fn summary(mut self) -> Self {
357 self.summary = true;
358 self
359 }
360
361 pub fn label(mut self, label: &str) -> Self {
363 self.label = Some(label.to_string());
364 self
365 }
366}
367
368impl<M: LearnerModel> SupervisedTraining<M> {
369 pub fn launch(mut self, learner: Learner<M>) -> LearningResult<M> {
371 if self.tracing_logger.is_some()
372 && let Err(e) = self.tracing_logger.as_ref().unwrap().install()
373 {
374 log::warn!("Failed to install the experiment logger: {e}");
375 }
376 let renderer = self
377 .renderer
378 .unwrap_or_else(|| default_renderer(self.interrupter.clone(), self.checkpoint));
379
380 if !self.event_store.has_loggers() {
381 self.event_store
382 .register_logger(FileMetricLogger::new(self.directory.clone()));
383 }
384
385 let event_store = Arc::new(EventStoreClient::new(self.event_store));
386 let full_processor =
387 FullEventProcessorTraining::new(self.metrics, renderer, event_store.clone());
388 let full_processor = match self.progress_logger {
389 Some(logger) => full_processor.with_progress_logger(logger),
390 None => full_processor,
391 };
392 let event_processor = AsyncProcessorTraining::new(full_processor);
393
394 let checkpointer = self.checkpointers.map(|(model, optim, scheduler)| {
395 LearningCheckpointer::new(
396 model.with_interrupter(self.interrupter.clone()),
397 optim.with_interrupter(self.interrupter.clone()),
398 scheduler.with_interrupter(self.interrupter.clone()),
399 self.checkpointer_strategy,
400 )
401 });
402
403 let summary = if self.summary {
404 Some(LearnerSummaryConfig {
405 directory: self.directory,
406 metrics: self.summary_metrics.into_iter().collect::<Vec<_>>(),
407 })
408 } else {
409 None
410 };
411
412 let components = TrainingComponents {
413 checkpoint: self.checkpoint,
414 checkpointer,
415 interrupter: self.interrupter,
416 early_stopping: self.early_stopping,
417 event_processor,
418 event_store,
419 num_epochs: self.num_epochs,
420 grad_accumulation: self.grad_accumulation,
421 summary,
422 label: self.label,
423 };
424
425 let training_strategy = self.training_strategy.unwrap_or(TrainingStrategy::Default(
427 ExecutionStrategy::SingleDevice(autodiff_device(
428 learner.model.devices()[0].clone(),
429 self.grad_checkpointing,
430 )),
431 ));
432
433 let mut learner = learner;
434 if let Some(checkpoint) = components.checkpoint
435 && let Some(checkpointer) = &components.checkpointer
436 {
437 learner = checkpointer.load_checkpoint(learner, checkpoint);
438 }
439
440 match training_strategy {
441 TrainingStrategy::Custom(learning_paradigm) => learning_paradigm.train(
442 learner,
443 self.dataloader_train,
444 self.dataloader_valid,
445 components,
446 ),
447 TrainingStrategy::Default(strategy) => match strategy {
448 ExecutionStrategy::SingleDevice(device) => {
449 let single_device = SingleDeviceTrainingStrategy::new(autodiff_device(
450 device,
451 self.grad_checkpointing,
452 ));
453 single_device.train(
454 learner,
455 self.dataloader_train,
456 self.dataloader_valid,
457 components,
458 )
459 }
460 ExecutionStrategy::MultiDevice(devices, multi_device_optim) => {
461 let strategy: Box<dyn SupervisedLearningStrategy<M>> = match devices.len() == 1
462 {
463 true => Box::new(SingleDeviceTrainingStrategy::new(autodiff_device(
464 devices[0].clone(),
465 self.grad_checkpointing,
466 ))),
467 false => Box::new(MultiDeviceLearningStrategy::new(
468 devices
469 .into_iter()
470 .map(|d| autodiff_device(d, self.grad_checkpointing))
471 .collect(),
472 multi_device_optim,
473 )),
474 };
475 strategy.train(
476 learner,
477 self.dataloader_train,
478 self.dataloader_valid,
479 components,
480 )
481 }
482 ExecutionStrategy::DistributedDataParallel { devices, context } => {
483 use crate::ddp::DdpTrainingStrategy;
484
485 let ddp = DdpTrainingStrategy::new(
486 devices
487 .into_iter()
488 .map(|d| autodiff_device(d, self.grad_checkpointing))
489 .collect(),
490 context,
491 );
492 ddp.train(
493 learner,
494 self.dataloader_train,
495 self.dataloader_valid,
496 components,
497 )
498 }
499 },
500 }
501 }
502}
503
504fn autodiff_device(mut device: Device, grad_checkpointing: bool) -> Device {
506 if !device.is_autodiff() {
507 device = device.autodiff();
508 }
509
510 if grad_checkpointing {
511 device = device.gradient_checkpointing();
512 }
513
514 device
515}
516
517pub trait MetricRegistration<M: LearnerModel>: Sized {
519 fn register(self, builder: SupervisedTraining<M>) -> SupervisedTraining<M>;
521}
522
523pub trait TextMetricRegistration<M: LearnerModel>: Sized {
525 fn register(self, builder: SupervisedTraining<M>) -> SupervisedTraining<M>;
527}
528
529macro_rules! gen_tuple {
530 ($($M:ident),*) => {
531 impl<$($M,)* M: LearnerModel> TextMetricRegistration<M> for ($($M,)*)
532 where
533 $(TrainingModelOutput<M>: Adaptor<$M::Input>,)*
534 $(InferenceModelOutput<M>: Adaptor<$M::Input>,)*
535 $($M: Metric + 'static,)*
536 {
537 #[allow(non_snake_case)]
538 fn register(
539 self,
540 builder: SupervisedTraining<M>,
541 ) -> SupervisedTraining<M> {
542 let ($($M,)*) = self;
543 $(let builder = builder.metric_train($M.clone());)*
544 $(let builder = builder.metric_valid($M);)*
545 builder
546 }
547 }
548
549 impl<$($M,)* M: LearnerModel> MetricRegistration<M> for ($($M,)*)
550 where
551 $(TrainingModelOutput<M>: Adaptor<$M::Input>,)*
552 $(InferenceModelOutput<M>: Adaptor<$M::Input>,)*
553 $($M: Metric + Numeric + 'static,)*
554 {
555 #[allow(non_snake_case)]
556 fn register(
557 self,
558 builder: SupervisedTraining<M>,
559 ) -> SupervisedTraining<M> {
560 let ($($M,)*) = self;
561 $(let builder = builder.metric_train_numeric($M.clone());)*
562 $(let builder = builder.metric_valid_numeric($M);)*
563 builder
564 }
565 }
566 };
567}
568
569gen_tuple!(M1);
570gen_tuple!(M1, M2);
571gen_tuple!(M1, M2, M3);
572gen_tuple!(M1, M2, M3, M4);
573gen_tuple!(M1, M2, M3, M4, M5);
574gen_tuple!(M1, M2, M3, M4, M5, M6);