1use crate::checkpoint::{
2 AsyncCheckpointer, Checkpoint, CheckpointingStrategy, ComposedCheckpointingStrategy,
3 FileCheckpointer, KeepLastNCheckpoints, MetricCheckpointingStrategy,
4};
5use crate::learner::base::Interrupter;
6use crate::logger::{FileMetricLogger, MetricLogger};
7use crate::metric::store::{Aggregate, Direction, EventStoreClient, LogEventStore, Split};
8use crate::metric::{Adaptor, EpisodeLengthMetric, Metric, Numeric};
9use crate::renderer::{MetricsRenderer, default_renderer};
10use crate::{
11 ApplicationLoggerInstaller, AsyncProcessorTraining, FileApplicationLoggerInstaller, ItemLazy,
12 LearnerSummaryConfig, OffPolicyConfig, OffPolicyStrategy, RLAgentRecord, RLCheckpointer,
13 RLComponents, RLComponentsMarker, RLComponentsTypes, RLEventProcessor, RLMetrics,
14 RLPolicyRecord, RLStrategy,
15};
16use crate::{EpisodeSummary, RLStrategies};
17use burn_core::tensor::Device;
18use burn_rl::{
19 Batchable, Environment, EnvironmentInit, Policy, PolicyLearner, PolicyState, SliceAccess,
20 ToAction, ToObservation,
21};
22use std::collections::BTreeSet;
23use std::path::{Path, PathBuf};
24use std::sync::Arc;
25
26pub struct RLTraining<RLC: RLComponentsTypes> {
28 #[allow(clippy::type_complexity)]
30 checkpointers: Option<(
31 AsyncCheckpointer<RLPolicyRecord<RLC>>,
32 AsyncCheckpointer<RLAgentRecord<RLC>>,
33 )>,
34 num_steps: usize,
35 checkpoint: Option<usize>,
36 directory: PathBuf,
37 grad_accumulation: Option<usize>,
38 renderer: Option<Box<dyn MetricsRenderer + 'static>>,
39 metrics: RLMetrics<RLC::TrainingOutput, RLC::ActionContext>,
40 event_store: LogEventStore,
41 interrupter: Interrupter,
42 tracing_logger: Option<Box<dyn ApplicationLoggerInstaller>>,
43 checkpointer_strategy: Box<dyn CheckpointingStrategy>,
44 learning_strategy: RLStrategies<RLC>,
45 summary_metrics: BTreeSet<String>,
47 summary: bool,
48 env_initializer: RLC::EnvInit,
49 inference_device: Device,
50 label: Option<String>,
51}
52
53impl<E, EI, A> RLTraining<RLComponentsMarker<E, EI, A>>
54where
55 E: Environment + 'static,
56 EI: EnvironmentInit<E> + Send + 'static,
57 A: PolicyLearner + Send + 'static,
58 <A as PolicyLearner>::Record: Checkpoint,
59 A::TrainContext: ItemLazy + Clone + Send,
60 A::InnerPolicy: Policy + Send,
61 <A::InnerPolicy as Policy>::Observation: Batchable + Clone + Send,
62 <A::InnerPolicy as Policy>::ActionDistribution: Batchable + Clone + Send,
63 <A::InnerPolicy as Policy>::Action: Batchable + Clone + Send,
64 <A::InnerPolicy as Policy>::ActionContext: ItemLazy + Clone + Send + 'static,
65 <A::InnerPolicy as Policy>::PolicyState: Clone + Send,
66 <<A::InnerPolicy as Policy>::PolicyState as PolicyState>::Record: Checkpoint,
67 E::State: ToObservation<<A::InnerPolicy as Policy>::Observation> + Clone + Send + 'static,
68 E::Action: From<<A::InnerPolicy as Policy>::Action>
69 + ToAction<<A::InnerPolicy as Policy>::Action>
70 + Clone
71 + Send
72 + 'static,
73{
74 pub fn new(directory: impl AsRef<Path>, env_initializer: EI) -> Self {
81 let directory = directory.as_ref().to_path_buf();
82 let experiment_log_file = directory.join("experiment.log");
83 Self {
84 num_steps: 1,
85 checkpoint: None,
86 checkpointers: None,
87 directory,
88 grad_accumulation: None,
89 metrics: RLMetrics::default(),
90 event_store: LogEventStore::default(),
91 renderer: None,
92 interrupter: Interrupter::new(),
93 tracing_logger: Some(Box::new(FileApplicationLoggerInstaller::new(
94 experiment_log_file,
95 ))),
96 checkpointer_strategy: Box::new(
97 ComposedCheckpointingStrategy::builder()
98 .add(KeepLastNCheckpoints::new(2))
99 .add(MetricCheckpointingStrategy::new(
100 &EpisodeLengthMetric::new(), Aggregate::Mean,
102 Direction::Lowest,
103 Split::Valid,
104 ))
105 .build(),
106 ),
107 learning_strategy: RLStrategies::OffPolicyStrategy(OffPolicyConfig::new()),
108 summary_metrics: BTreeSet::new(),
109 summary: false,
110 env_initializer,
111 inference_device: Default::default(),
112 label: None,
113 }
114 }
115}
116
117impl<RLC: RLComponentsTypes + 'static> RLTraining<RLC> {
118 pub fn with_learning_strategy(mut self, learning_strategy: RLStrategies<RLC>) -> Self {
124 self.learning_strategy = learning_strategy;
125 self
126 }
127
128 pub fn with_metric_logger<ML>(mut self, logger: ML) -> Self
134 where
135 ML: MetricLogger + 'static,
136 {
137 self.event_store.register_logger(logger);
138 self
139 }
140
141 pub fn with_checkpointing_strategy<CS: CheckpointingStrategy + 'static>(
143 mut self,
144 strategy: CS,
145 ) -> Self {
146 self.checkpointer_strategy = Box::new(strategy);
147 self
148 }
149
150 pub fn renderer(mut self, renderer: Box<dyn MetricsRenderer + 'static>) -> Self {
156 self.renderer = Some(renderer);
157 self
158 }
159
160 pub fn metrics_train<Me: TrainMetricRegistration<RLC>>(self, metrics: Me) -> Self {
162 metrics.register(self)
163 }
164
165 pub fn text_metrics_train<Me: TrainTextMetricRegistration<RLC>>(self, metrics: Me) -> Self {
167 metrics.register(self)
168 }
169
170 pub fn metrics_agent<Me: AgentMetricRegistration<RLC>>(self, metrics: Me) -> Self {
172 metrics.register(self)
173 }
174
175 pub fn text_metrics_agent<Me: AgentTextMetricRegistration<RLC>>(self, metrics: Me) -> Self {
177 metrics.register(self)
178 }
179
180 pub fn metrics_episode<Me: EpisodeMetricRegistration<RLC>>(self, metrics: Me) -> Self {
182 metrics.register(self)
183 }
184
185 pub fn text_metrics_episode<Me: EpisodeTextMetricRegistration<RLC>>(self, metrics: Me) -> Self {
187 metrics.register(self)
188 }
189
190 pub fn text_metric_train<Me: Metric + 'static>(mut self, metric: Me) -> Self
192 where
193 RLC::TrainingOutput: Adaptor<Me::Input>,
194 {
195 self.metrics.register_text_metric_train(metric);
196 self
197 }
198
199 pub fn metric_train<Me>(mut self, metric: Me) -> Self
201 where
202 Me: Metric + Numeric + 'static,
203 RLC::TrainingOutput: Adaptor<Me::Input>,
204 {
205 self.summary_metrics.insert(metric.name().to_string());
206 self.metrics.register_metric_train(metric);
207 self
208 }
209
210 pub fn text_metric_agent<Me: Metric + 'static>(mut self, metric: Me) -> Self
212 where
213 RLC::ActionContext: Adaptor<Me::Input>,
214 {
215 self.metrics.register_text_metric_agent(metric.clone());
216 self.metrics.register_text_metric_agent_valid(metric);
217 self
218 }
219
220 pub fn metric_agent<Me>(mut self, metric: Me) -> Self
222 where
223 Me: Metric + Numeric + 'static,
224 RLC::ActionContext: Adaptor<Me::Input>,
225 {
226 self.summary_metrics.insert(metric.name().to_string());
227 self.metrics.register_agent_metric(metric.clone());
228 self.metrics.register_agent_metric_valid(metric);
229 self
230 }
231
232 pub fn text_metric_episode<Me: Metric + 'static>(mut self, metric: Me) -> Self
234 where
235 EpisodeSummary: Adaptor<Me::Input> + 'static,
236 {
237 self.metrics.register_text_metric_episode(metric.clone());
238 self.metrics.register_text_metric_episode_valid(metric);
239 self
240 }
241
242 pub fn metric_episode<Me>(mut self, metric: Me) -> Self
244 where
245 Me: Metric + Numeric + 'static,
246 EpisodeSummary: Adaptor<Me::Input> + 'static,
247 {
248 self.summary_metrics.insert(metric.name().to_string());
249 self.metrics.register_episode_metric(metric.clone());
250 self.metrics.register_episode_metric_valid(metric);
251 self
252 }
253
254 pub fn num_steps(mut self, num_steps: usize) -> Self {
256 self.num_steps = num_steps;
257 self
258 }
259
260 pub fn checkpoint(mut self, checkpoint: usize) -> Self {
262 self.checkpoint = Some(checkpoint);
263 self
264 }
265
266 pub fn interrupter(&self) -> Interrupter {
268 self.interrupter.clone()
269 }
270
271 pub fn with_interrupter(mut self, interrupter: Interrupter) -> Self {
273 self.interrupter = interrupter;
274 self
275 }
276
277 pub fn with_application_logger(
281 mut self,
282 logger: Option<Box<dyn ApplicationLoggerInstaller>>,
283 ) -> Self {
284 self.tracing_logger = logger;
285 self
286 }
287
288 pub fn with_checkpointer(mut self) -> Self
291 where
292 RLPolicyRecord<RLC>: Checkpoint,
293 RLAgentRecord<RLC>: Checkpoint,
294 {
295 let checkpoint_dir = self.directory.join("checkpoint");
296 let checkpointer_policy = FileCheckpointer::new(&checkpoint_dir, "policy");
297 let checkpointer_learning = FileCheckpointer::new(&checkpoint_dir, "learning-agent");
298
299 self.checkpointers = Some((
300 AsyncCheckpointer::new(checkpointer_policy),
301 AsyncCheckpointer::new(checkpointer_learning),
302 ));
303
304 self
305 }
306
307 pub fn with_inference_device(mut self, device: Device) -> Self {
309 self.inference_device = device;
310 self
311 }
312
313 pub fn summary(mut self) -> Self {
317 self.summary = true;
318 self
319 }
320
321 pub fn label(mut self, label: &str) -> Self {
323 self.label = Some(label.to_string());
324 self
325 }
326
327 pub fn launch(mut self, learner_agent: RLC::LearningAgent) -> RLResult<RLC::Policy>
329 where
330 RLC::PolicyObs: SliceAccess,
331 RLC::PolicyAction: SliceAccess,
332 {
333 if self.tracing_logger.is_some()
334 && let Err(e) = self.tracing_logger.as_ref().unwrap().install()
335 {
336 log::warn!("Failed to install the experiment logger: {e}");
337 }
338 let renderer = self
339 .renderer
340 .unwrap_or_else(|| default_renderer(self.interrupter.clone(), self.checkpoint));
341
342 if !self.event_store.has_loggers() {
343 self.event_store
344 .register_logger(FileMetricLogger::new(self.directory.clone()));
345 }
346
347 let event_store = Arc::new(EventStoreClient::new(self.event_store));
348 let event_processor = AsyncProcessorTraining::new(RLEventProcessor::new(
349 self.metrics,
350 renderer,
351 event_store.clone(),
352 ));
353
354 let checkpointer = self.checkpointers.map(|(policy, learning_agent)| {
355 RLCheckpointer::new(policy, learning_agent, self.checkpointer_strategy)
356 });
357
358 let summary = if self.summary {
359 Some(LearnerSummaryConfig {
360 directory: self.directory,
361 metrics: self.summary_metrics.into_iter().collect::<Vec<_>>(),
362 })
363 } else {
364 None
365 };
366
367 let components = RLComponents::<RLC> {
368 checkpoint: self.checkpoint,
369 checkpointer,
370 interrupter: self.interrupter,
371 event_processor,
372 event_store,
373 num_steps: self.num_steps,
374 grad_accumulation: self.grad_accumulation,
375 summary,
376 inference_device: self.inference_device,
377 label: self.label,
378 };
379
380 let mut learner_agent = learner_agent;
381 if let Some(checkpoint) = components.checkpoint
382 && let Some(checkpointer) = &components.checkpointer
383 {
384 learner_agent = checkpointer.load_checkpoint(learner_agent, checkpoint);
385 }
386
387 match self.learning_strategy {
388 RLStrategies::OffPolicyStrategy(config) => {
389 let strategy = OffPolicyStrategy::new(config);
390 strategy.train(learner_agent, components, self.env_initializer)
391 }
392 RLStrategies::Custom(strategy) => {
393 strategy.train(learner_agent, components, self.env_initializer)
394 }
395 }
396 }
397}
398
399pub struct RLResult<P> {
401 pub policy: P,
403 pub renderer: Box<dyn MetricsRenderer>,
405 pub interrupted: Option<crate::Interruption>,
408 pub error: Option<std::sync::Arc<crate::TrainingError>>,
410}
411
412pub trait AgentMetricRegistration<RLC: RLComponentsTypes>: Sized {
414 fn register(self, builder: RLTraining<RLC>) -> RLTraining<RLC>;
416}
417
418pub trait AgentTextMetricRegistration<RLC: RLComponentsTypes>: Sized {
420 fn register(self, builder: RLTraining<RLC>) -> RLTraining<RLC>;
422}
423
424pub trait TrainMetricRegistration<RLC: RLComponentsTypes>: Sized {
426 fn register(self, builder: RLTraining<RLC>) -> RLTraining<RLC>;
428}
429
430pub trait TrainTextMetricRegistration<RLC: RLComponentsTypes>: Sized {
432 fn register(self, builder: RLTraining<RLC>) -> RLTraining<RLC>;
434}
435
436pub trait EpisodeMetricRegistration<RLC: RLComponentsTypes>: Sized {
438 fn register(self, builder: RLTraining<RLC>) -> RLTraining<RLC>;
440}
441
442pub trait EpisodeTextMetricRegistration<RLC: RLComponentsTypes>: Sized {
444 fn register(self, builder: RLTraining<RLC>) -> RLTraining<RLC>;
446}
447
448macro_rules! gen_tuple {
449 ($($M:ident),*) => {
450 impl<$($M,)* RLC: RLComponentsTypes + 'static> TrainTextMetricRegistration<RLC> for ($($M,)*)
451 where
452 $(RLC::TrainingOutput: Adaptor<$M::Input>,)*
453 $($M: Metric + 'static,)*
454 {
455 #[allow(non_snake_case)]
456 fn register(
457 self,
458 builder: RLTraining<RLC>,
459 ) -> RLTraining<RLC> {
460 let ($($M,)*) = self;
461 $(let builder = builder.text_metric_train($M.clone());)*
462 builder
463 }
464 }
465
466 impl<$($M,)* RLC: RLComponentsTypes + 'static> TrainMetricRegistration<RLC> for ($($M,)*)
467 where
468 $(RLC::TrainingOutput: Adaptor<$M::Input>,)*
469 $($M: Metric + Numeric + 'static,)*
470 {
471 #[allow(non_snake_case)]
472 fn register(
473 self,
474 builder: RLTraining<RLC>,
475 ) -> RLTraining<RLC> {
476 let ($($M,)*) = self;
477 $(let builder = builder.metric_train($M.clone());)*
478 builder
479 }
480 }
481
482 impl<$($M,)* RLC: RLComponentsTypes + 'static> AgentTextMetricRegistration<RLC> for ($($M,)*)
483 where
484 $(RLC::ActionContext: Adaptor<$M::Input>,)*
485 $($M: Metric + 'static,)*
486 {
487 #[allow(non_snake_case)]
488 fn register(
489 self,
490 builder: RLTraining<RLC>,
491 ) -> RLTraining<RLC> {
492 let ($($M,)*) = self;
493 $(let builder = builder.text_metric_agent($M.clone());)*
494 builder
495 }
496 }
497
498 impl<$($M,)* RLC: RLComponentsTypes + 'static> AgentMetricRegistration<RLC> for ($($M,)*)
499 where
500 $(RLC::ActionContext: Adaptor<$M::Input>,)*
501 $($M: Metric + Numeric + 'static,)*
502 {
503 #[allow(non_snake_case)]
504 fn register(
505 self,
506 builder: RLTraining<RLC>,
507 ) -> RLTraining<RLC> {
508 let ($($M,)*) = self;
509 $(let builder = builder.metric_agent($M.clone());)*
510 builder
511 }
512 }
513
514 impl<$($M,)* RLC: RLComponentsTypes + 'static> EpisodeTextMetricRegistration<RLC> for ($($M,)*)
515 where
516 $(EpisodeSummary: Adaptor<$M::Input> + 'static,)*
517 $($M: Metric + 'static,)*
518 {
519 #[allow(non_snake_case)]
520 fn register(
521 self,
522 builder: RLTraining<RLC>,
523 ) -> RLTraining<RLC> {
524 let ($($M,)*) = self;
525 $(let builder = builder.text_metric_episode($M.clone());)*
526 builder
527 }
528 }
529
530 impl<$($M,)* RLC: RLComponentsTypes + 'static> EpisodeMetricRegistration<RLC> for ($($M,)*)
531 where
532 $(EpisodeSummary: Adaptor<$M::Input> + 'static,)*
533 $($M: Metric + Numeric + 'static,)*
534 {
535 #[allow(non_snake_case)]
536 fn register(
537 self,
538 builder: RLTraining<RLC>,
539 ) -> RLTraining<RLC> {
540 let ($($M,)*) = self;
541 $(let builder = builder.metric_episode($M.clone());)*
542 builder
543 }
544 }
545 };
546}
547
548gen_tuple!(M1);
549gen_tuple!(M1, M2);
550gen_tuple!(M1, M2, M3);
551gen_tuple!(M1, M2, M3, M4);
552gen_tuple!(M1, M2, M3, M4, M5);
553gen_tuple!(M1, M2, M3, M4, M5, M6);