burn_train/learner/supervised/step/
train.rs1use crate::LearnerModel;
2use crate::{TrainOutput, TrainStep, TrainingModelInput, TrainingModelOutput};
3use burn_core::data::dataloader::DataLoaderIterator;
4use burn_core::data::dataloader::Progress;
5use burn_core::data::dataset::DatasetError;
6use burn_core::tensor::Device;
7use std::sync::mpsc::{Receiver, Sender};
8use std::thread::spawn;
9
10type StepOutput<TO> = (Vec<MultiTrainOutput<TO>>, Progress);
12
13#[derive(Debug)]
15pub enum MultiDeviceStepError {
16 Dataset(DatasetError),
18 Workers(Vec<WorkerFailure>),
20}
21
22#[derive(Debug)]
24pub struct WorkerFailure {
25 pub device_id: usize,
27 pub message: String,
29}
30
31impl core::fmt::Display for WorkerFailure {
32 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
33 write!(
34 f,
35 "training worker on device {} failed: {}",
36 self.device_id, self.message
37 )
38 }
39}
40
41pub(crate) fn fmt_worker_failures(
43 failures: &[WorkerFailure],
44 f: &mut core::fmt::Formatter<'_>,
45) -> core::fmt::Result {
46 match failures {
47 [failure] => write!(f, "{failure}"),
48 failures => {
49 write!(f, "{} training workers failed:", failures.len())?;
50 for failure in failures {
51 write!(f, "\n {failure}")?;
52 }
53 Ok(())
54 }
55 }
56}
57
58impl core::fmt::Display for MultiDeviceStepError {
59 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
60 match self {
61 Self::Dataset(err) => write!(f, "dataset error during training step: {err}"),
62 Self::Workers(failures) => fmt_worker_failures(failures, f),
63 }
64 }
65}
66
67impl core::error::Error for MultiDeviceStepError {}
68
69pub struct MultiDevicesTrainStep<M: LearnerModel> {
71 workers: Vec<Worker<M>>,
72 receiver: Receiver<WorkerMessage<TrainingModelOutput<M>>>,
73}
74
75struct Message<M, TI> {
76 item: TI,
77 model: M,
78}
79
80enum WorkerMessage<TO> {
81 Output(MultiTrainOutput<TO>),
82 Error(usize, String),
83}
84
85fn panic_message(payload: &(dyn std::any::Any + Send)) -> String {
86 if let Some(s) = payload.downcast_ref::<&str>() {
87 s.to_string()
88 } else if let Some(s) = payload.downcast_ref::<String>() {
89 s.clone()
90 } else {
91 "unknown panic in training worker".to_string()
92 }
93}
94
95struct Worker<M: LearnerModel> {
96 sender_input: Sender<Message<M, TrainingModelInput<M>>>,
99 device: Device,
100 device_id: usize,
101}
102
103impl<M: LearnerModel> Worker<M> {
104 fn register(&self, item: TrainingModelInput<M>, model: &M) {
105 let message = Message {
106 item,
107 model: model.clone(),
108 };
109 self.sender_input.send(message).unwrap();
110 }
111
112 fn start(
115 &self,
116 sender_output: Sender<WorkerMessage<TrainingModelOutput<M>>>,
117 receiver_input: Receiver<Message<M, TrainingModelInput<M>>>,
118 ) {
119 let device = self.device.clone();
120 let device_id = self.device_id;
121
122 spawn(move || {
123 loop {
124 match receiver_input.recv() {
125 Ok(item) => {
126 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
127 let model = item.model.fork(&device);
128 TrainStep::step(&model, item.item)
129 }));
130
131 let message = match result {
132 Ok(output) => {
133 WorkerMessage::Output(MultiTrainOutput { output, device_id })
134 }
135 Err(payload) => {
136 WorkerMessage::Error(device_id, panic_message(payload.as_ref()))
137 }
138 };
139
140 if sender_output.send(message).is_err() {
141 break;
142 }
143 }
144 Err(_err) => {
145 log::info!("Closing thread on device {device:?}");
146 break;
147 }
148 }
149 }
150 });
151 }
152}
153
154pub struct MultiTrainOutput<TO> {
156 pub output: TrainOutput<TO>,
158 pub(crate) device_id: usize,
160}
161
162impl<M: LearnerModel> MultiDevicesTrainStep<M> {
163 pub fn new(devices: &[Device]) -> Self {
173 let (sender_output, receiver_output) = std::sync::mpsc::channel();
174 let workers = devices
175 .iter()
176 .enumerate()
177 .map(|(device_id, device)| {
178 let (sender_input, receiver_input) = std::sync::mpsc::channel();
179 let worker = Worker {
180 sender_input,
181 device: device.clone(),
182 device_id,
183 };
184
185 worker.start(sender_output.clone(), receiver_input);
186 worker
187 })
188 .collect();
189
190 Self {
191 workers,
192 receiver: receiver_output,
193 }
194 }
195
196 pub fn step<'a>(
207 &self,
208 dataloaders: &mut [Box<dyn DataLoaderIterator<TrainingModelInput<M>> + 'a>],
209 model: &M,
210 ) -> Result<StepOutput<TrainingModelOutput<M>>, MultiDeviceStepError> {
211 let mut num_send = 0;
212
213 let mut items_total = 0;
214 let mut items_processed = 0;
215 let unit: Option<String> = Some("items".to_string());
216
217 for (i, worker) in self.workers.iter().enumerate() {
218 let dataloader = &mut dataloaders[i];
219 match dataloader.next() {
220 Some(Ok(item)) => {
221 worker.register(item, model);
222 num_send += 1;
223 let progress = dataloader.progress();
224 items_total += progress.items_total;
225 items_processed += progress.items_processed;
226 }
227 Some(Err(err)) => return Err(MultiDeviceStepError::Dataset(err)),
228 None => {}
229 }
230 }
231
232 let mut outputs = Vec::with_capacity(num_send);
233 let mut failures = Vec::new();
234
235 for _ in 0..num_send {
238 match self.receiver.recv().unwrap() {
239 WorkerMessage::Output(output) => outputs.push(output),
240 WorkerMessage::Error(device_id, message) => {
241 failures.push(WorkerFailure { device_id, message })
242 }
243 }
244 }
245
246 if !failures.is_empty() {
247 return Err(MultiDeviceStepError::Workers(failures));
248 }
249 Ok((outputs, Progress::new(items_processed, items_total, unit)))
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 fn failure(device_id: usize, message: &str) -> WorkerFailure {
258 WorkerFailure {
259 device_id,
260 message: message.to_string(),
261 }
262 }
263
264 #[test]
265 fn every_failed_worker_is_reported() {
266 let error = MultiDeviceStepError::Workers(vec![
267 failure(0, "out of memory"),
268 failure(2, "illegal address"),
269 ]);
270
271 assert_eq!(
272 error.to_string(),
273 "2 training workers failed:\n \
274 training worker on device 0 failed: out of memory\n \
275 training worker on device 2 failed: illegal address"
276 );
277 }
278}