1use crate::metric::processor::{EvaluatorEvent, EventProcessorError, EventProcessorEvaluation};
2
3use super::EventProcessorTraining;
4use async_channel::{Receiver, Sender};
5use std::thread::JoinHandle;
6
7pub struct AsyncProcessorTraining<ET, EV> {
16 sender: Sender<Message<ET, EV>>,
17 worker: Worker,
18}
19
20pub struct AsyncProcessorEvaluation<P: EventProcessorEvaluation> {
24 sender: Sender<EvalMessage<P>>,
25 worker: Worker,
26}
27
28struct Worker {
33 handle: Option<JoinHandle<()>>,
34 error_rec: Receiver<EventProcessorError>,
35}
36
37impl Worker {
38 fn new(handle: JoinHandle<()>, error_rec: Receiver<EventProcessorError>) -> Self {
39 Self {
40 handle: Some(handle),
41 error_rec,
42 }
43 }
44
45 fn check_errors(&self) -> Result<(), EventProcessorError> {
47 let Ok(mut reported) = self.error_rec.try_recv() else {
48 return Ok(());
49 };
50 while let Ok(later) = self.error_rec.try_recv() {
51 reported.merge(later);
52 }
53 Err(reported)
54 }
55
56 fn died(&mut self) -> ! {
58 match self.handle.take().map(JoinHandle::join) {
59 Some(Err(payload)) => std::panic::resume_unwind(payload),
60 _ => panic!("the event processor worker stopped without reporting an error"),
61 }
62 }
63}
64
65struct WorkerTraining<ET, EV, P: EventProcessorTraining<ET, EV>> {
66 processor: P,
67 rec: Receiver<Message<ET, EV>>,
68 error_sender: Sender<EventProcessorError>,
69}
70
71struct WorkerEvaluation<P: EventProcessorEvaluation> {
72 processor: P,
73 rec: Receiver<EvalMessage<P>>,
74 error_sender: Sender<EventProcessorError>,
75}
76
77fn report(error_sender: &Sender<EventProcessorError>, result: Result<(), EventProcessorError>) {
78 if let Err(err) = result {
79 let _ = error_sender.try_send(err);
80 }
81}
82
83impl<ET: Send + 'static, EV: Send + 'static, P: EventProcessorTraining<ET, EV> + 'static>
84 WorkerTraining<ET, EV, P>
85{
86 pub fn start(
87 processor: P,
88 rec: Receiver<Message<ET, EV>>,
89 error_sender: Sender<EventProcessorError>,
90 ) -> JoinHandle<()> {
91 let mut worker = Self {
92 processor,
93 rec,
94 error_sender,
95 };
96 std::thread::Builder::new()
97 .name("train-worker".into())
98 .spawn(move || {
99 while let Ok(msg) = worker.rec.recv_blocking() {
100 match msg {
101 Message::Train(event) => {
102 report(&worker.error_sender, worker.processor.process_train(event))
103 }
104 Message::Valid(event) => {
105 report(&worker.error_sender, worker.processor.process_valid(event))
106 }
107 Message::Flush(callback) => {
108 report(&worker.error_sender, worker.processor.flush());
109 callback.send_blocking(()).unwrap();
110 }
111 Message::Renderer(callback) => {
112 callback.send_blocking(worker.processor.renderer()).unwrap();
113 return;
114 }
115 }
116 }
117 })
118 .unwrap()
119 }
120}
121impl<P: EventProcessorEvaluation + 'static> WorkerEvaluation<P> {
122 pub fn start(
123 processor: P,
124 rec: Receiver<EvalMessage<P>>,
125 error_sender: Sender<EventProcessorError>,
126 ) -> JoinHandle<()> {
127 let mut worker = Self {
128 processor,
129 rec,
130 error_sender,
131 };
132
133 std::thread::Builder::new()
134 .name("evel-worker".into())
135 .spawn(move || {
136 while let Ok(event) = worker.rec.recv_blocking() {
137 match event {
138 EvalMessage::Test(event) => {
139 report(&worker.error_sender, worker.processor.process_test(event))
140 }
141 EvalMessage::Flush(callback) => {
142 report(&worker.error_sender, worker.processor.flush());
143 callback.send_blocking(()).unwrap();
144 }
145 EvalMessage::Renderer(sender) => {
146 sender.send_blocking(worker.processor.renderer()).unwrap();
147 return;
148 }
149 }
150 }
151 })
152 .unwrap()
153 }
154}
155
156impl<ET: Send + 'static, EV: Send + 'static> AsyncProcessorTraining<ET, EV> {
157 pub fn new<P: EventProcessorTraining<ET, EV> + 'static>(processor: P) -> Self {
159 let (sender, rec) = async_channel::bounded(1);
160 let (error_sender, error_rec) = async_channel::unbounded();
161
162 let worker = Worker::new(
163 WorkerTraining::start(processor, rec, error_sender),
164 error_rec,
165 );
166
167 Self { sender, worker }
168 }
169}
170
171impl<P: EventProcessorEvaluation + 'static> AsyncProcessorEvaluation<P> {
172 pub fn new(processor: P) -> Self {
174 let (sender, rec) = async_channel::bounded(1);
175 let (error_sender, error_rec) = async_channel::unbounded();
176
177 let worker = Worker::new(
178 WorkerEvaluation::start(processor, rec, error_sender),
179 error_rec,
180 );
181
182 Self { sender, worker }
183 }
184}
185
186enum Message<EventTrain, EventValid> {
187 Train(EventTrain),
188 Valid(EventValid),
189 Flush(Sender<()>),
190 Renderer(Sender<Box<dyn crate::renderer::MetricsRenderer>>),
191}
192
193enum EvalMessage<P: EventProcessorEvaluation> {
194 Test(EvaluatorEvent<P::ItemTest>),
195 Flush(Sender<()>),
196 Renderer(Sender<Box<dyn crate::renderer::MetricsRenderer>>),
197}
198
199impl<ET: Send, EV: Send> EventProcessorTraining<ET, EV> for AsyncProcessorTraining<ET, EV> {
200 fn process_train(&mut self, event: ET) -> Result<(), EventProcessorError> {
201 if self.sender.send_blocking(Message::Train(event)).is_err() {
202 self.worker.died();
203 }
204 self.worker.check_errors()
205 }
206
207 fn process_valid(&mut self, event: EV) -> Result<(), EventProcessorError> {
208 if self.sender.send_blocking(Message::Valid(event)).is_err() {
209 self.worker.died();
210 }
211 self.worker.check_errors()
212 }
213
214 fn flush(&mut self) -> Result<(), EventProcessorError> {
215 let (sender, receiver) = async_channel::bounded(1);
216 if self.sender.send_blocking(Message::Flush(sender)).is_err()
217 || receiver.recv_blocking().is_err()
218 {
219 self.worker.died();
220 }
221 self.worker.check_errors()
222 }
223
224 fn renderer(mut self) -> Box<dyn crate::renderer::MetricsRenderer> {
225 let (sender, rec) = async_channel::bounded(1);
226 if self
227 .sender
228 .send_blocking(Message::Renderer(sender))
229 .is_err()
230 {
231 self.worker.died();
232 }
233
234 match rec.recv_blocking() {
235 Ok(value) => value,
236 Err(_) => self.worker.died(),
237 }
238 }
239}
240
241impl<P: EventProcessorEvaluation> EventProcessorEvaluation for AsyncProcessorEvaluation<P> {
242 type ItemTest = P::ItemTest;
243
244 fn process_test(
245 &mut self,
246 event: EvaluatorEvent<Self::ItemTest>,
247 ) -> Result<(), EventProcessorError> {
248 if self.sender.send_blocking(EvalMessage::Test(event)).is_err() {
249 self.worker.died();
250 }
251 self.worker.check_errors()
252 }
253
254 fn flush(&mut self) -> Result<(), EventProcessorError> {
255 let (sender, receiver) = async_channel::bounded(1);
256 if self
257 .sender
258 .send_blocking(EvalMessage::Flush(sender))
259 .is_err()
260 || receiver.recv_blocking().is_err()
261 {
262 self.worker.died();
263 }
264 self.worker.check_errors()
265 }
266
267 fn renderer(mut self) -> Box<dyn crate::renderer::MetricsRenderer> {
268 let (sender, rec) = async_channel::bounded(1);
269 if self
270 .sender
271 .send_blocking(EvalMessage::Renderer(sender))
272 .is_err()
273 {
274 self.worker.died();
275 }
276
277 match rec.recv_blocking() {
278 Ok(value) => value,
279 Err(_) => self.worker.died(),
280 }
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287 use crate::metric::{
288 processor::{EventProcessorFailure, ItemLazy, MetricError},
289 store::Split,
290 };
291 use crate::renderer::{MetricsRenderer, cli::CliMetricsRenderer};
292 use burn_core::tensor::TensorReadError;
293 use burn_std::ExecutionError;
294 use std::sync::{
295 Arc,
296 atomic::{AtomicUsize, Ordering},
297 };
298
299 struct TestProcessor {
300 processed: Arc<AtomicUsize>,
301 processed_on_flush: Arc<AtomicUsize>,
302 }
303
304 impl EventProcessorTraining<usize, usize> for TestProcessor {
305 fn process_train(&mut self, event: usize) -> Result<(), EventProcessorError> {
306 self.processed.fetch_add(event, Ordering::SeqCst);
307 Ok(())
308 }
309
310 fn process_valid(&mut self, event: usize) -> Result<(), EventProcessorError> {
311 self.processed.fetch_add(event, Ordering::SeqCst);
312 Ok(())
313 }
314
315 fn flush(&mut self) -> Result<(), EventProcessorError> {
316 self.processed_on_flush
317 .store(self.processed.load(Ordering::SeqCst), Ordering::SeqCst);
318 Ok(())
319 }
320
321 fn renderer(self) -> Box<dyn MetricsRenderer> {
322 Box::new(CliMetricsRenderer::new())
323 }
324 }
325
326 #[test]
327 fn flush_waits_for_queued_training_events() {
328 let processed = Arc::new(AtomicUsize::new(0));
329 let processed_on_flush = Arc::new(AtomicUsize::new(0));
330 let mut processor = AsyncProcessorTraining::new(TestProcessor {
331 processed: processed.clone(),
332 processed_on_flush: processed_on_flush.clone(),
333 });
334
335 processor.process_train(2).unwrap();
336 processor.process_valid(3).unwrap();
337 processor.flush().unwrap();
338
339 assert_eq!(processed.load(Ordering::SeqCst), 5);
340 assert_eq!(processed_on_flush.load(Ordering::SeqCst), 5);
342 }
343
344 struct FailingProcessor {
347 processed: Arc<AtomicUsize>,
348 }
349
350 impl EventProcessorTraining<usize, usize> for FailingProcessor {
351 fn process_train(&mut self, event: usize) -> Result<(), EventProcessorError> {
352 if event < 2 {
353 return EventProcessorError::from_errors(vec![MetricError {
354 metric: Arc::new(format!("event-{event}")),
355 split: Split::Train,
356 source: ExecutionError::device_poisoned("the loss could not be read").into(),
357 }]);
358 }
359 self.processed.fetch_add(event, Ordering::SeqCst);
360 Ok(())
361 }
362
363 fn process_valid(&mut self, _event: usize) -> Result<(), EventProcessorError> {
364 Ok(())
365 }
366
367 fn renderer(self) -> Box<dyn MetricsRenderer> {
368 Box::new(CliMetricsRenderer::new())
369 }
370 }
371
372 #[test]
373 fn every_event_error_reaches_the_caller_and_the_worker_keeps_going() {
374 let processed = Arc::new(AtomicUsize::new(0));
375 let mut processor = AsyncProcessorTraining::new(FailingProcessor {
376 processed: processed.clone(),
377 });
378
379 let reports = [
382 processor.process_train(0),
383 processor.process_train(1),
384 processor.flush(),
385 ];
386 let failures: Vec<MetricError> = reports
387 .into_iter()
388 .filter_map(Result::err)
389 .flat_map(EventProcessorError::into_failures)
390 .map(|failure| match failure {
391 EventProcessorFailure::Metric(error) => error,
392 other => panic!("expected a metric failure, got {other}"),
393 })
394 .collect();
395
396 let failed: Vec<&str> = failures.iter().map(|err| err.metric.as_str()).collect();
397 assert_eq!(failed, ["event-0", "event-1"], "every failure, in order");
398 assert!(failures.iter().all(|err| matches!(
399 &err.source,
400 TensorReadError::Execution(err) if err.is_device_poisoned()
401 )));
402
403 processor.process_train(2).unwrap();
405 processor.flush().unwrap();
406 assert_eq!(processed.load(Ordering::SeqCst), 2);
407 }
408
409 struct FailingEvalProcessor;
411
412 struct NoItem;
413
414 impl ItemLazy for NoItem {
415 fn sync(self) -> Result<Self, ExecutionError> {
416 Ok(self)
417 }
418 }
419
420 impl EventProcessorEvaluation for FailingEvalProcessor {
421 type ItemTest = NoItem;
422
423 fn process_test(
424 &mut self,
425 event: EvaluatorEvent<NoItem>,
426 ) -> Result<(), EventProcessorError> {
427 match event {
428 EvaluatorEvent::EndTest => EventProcessorError::from_errors(vec![MetricError {
429 metric: Arc::new("Accuracy".to_string()),
430 split: Split::Test(None),
431 source: ExecutionError::with_context("refused").into(),
432 }]),
433 _ => Ok(()),
434 }
435 }
436
437 fn renderer(self) -> Box<dyn MetricsRenderer> {
438 Box::new(CliMetricsRenderer::new())
439 }
440 }
441
442 #[test]
443 fn flush_reports_last_events_errors() {
444 let mut processor = AsyncProcessorEvaluation::new(FailingEvalProcessor);
445
446 let reported = processor
449 .process_test(EvaluatorEvent::EndTest)
450 .err()
451 .or_else(|| processor.flush().err())
452 .expect("the last event's error must reach the caller");
453 match &reported.failures()[0] {
454 EventProcessorFailure::Metric(error) => assert_eq!(error.metric.as_str(), "Accuracy"),
455 other => panic!("expected a metric failure, got {other}"),
456 }
457
458 processor.flush().unwrap();
459 }
460
461 struct PanickingProcessor;
463
464 impl EventProcessorTraining<usize, usize> for PanickingProcessor {
465 fn process_train(&mut self, _event: usize) -> Result<(), EventProcessorError> {
466 panic!("the processor panicked");
467 }
468
469 fn process_valid(&mut self, _event: usize) -> Result<(), EventProcessorError> {
470 Ok(())
471 }
472
473 fn renderer(self) -> Box<dyn MetricsRenderer> {
474 Box::new(CliMetricsRenderer::new())
475 }
476 }
477
478 #[test]
479 fn a_worker_panic_reaches_the_caller_with_its_cause() {
480 let mut processor = AsyncProcessorTraining::new(PanickingProcessor);
481
482 let payload = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
483 let _ = processor.process_train(1);
487 let _ = processor.process_train(2);
488 let _ = processor.flush();
489 }))
490 .expect_err("the worker's panic must reach the caller");
491
492 let message = payload
493 .downcast_ref::<&str>()
494 .map(|message| message.to_string())
495 .or_else(|| payload.downcast_ref::<String>().cloned())
496 .unwrap_or_default();
497 assert!(
498 message.contains("the processor panicked"),
499 "the caller must see the worker's own panic, got: {message:?}"
500 );
501 }
502}