Skip to main content

burn_train/learner/rl/env_runner/
async_runner.rs

1use rand::prelude::SliceRandom;
2use std::{
3    sync::mpsc::{Receiver, Sender},
4    thread::spawn,
5};
6
7use burn_core::{Tensor, data::dataloader::Progress, tensor::Device};
8use burn_rl::Policy;
9use burn_rl::Transition;
10use burn_rl::{AsyncPolicy, Environment};
11use burn_rl::{EnvironmentInit, ToObservation};
12
13use crate::{
14    AgentEnvLoop, AgentEvaluationEvent, EpisodeSummary, EvaluationItem, EventProcessorTraining,
15    Interrupter, RLComponentsTypes, RLEvent, RLEventProcessorType, RLTimeStep, RLTrajectory,
16    RlPolicy, TimeStep, Trajectory,
17};
18
19enum RequestMessage {
20    Step(),
21    Episode(),
22}
23
24/// Configuration for an async agent/environment loop.
25pub struct AsyncAgentEnvLoopConfig {
26    /// If the loop is used for evaluation (as opposed to training).
27    pub eval: bool,
28    /// If the agent should take action deterministically.
29    pub deterministic: bool,
30    /// An arbitrary ID for the loop.
31    pub id: usize,
32}
33
34/// An asynchronous agent/environement interface.
35pub struct AgentEnvAsyncLoop<RLC: RLComponentsTypes> {
36    eval: bool,
37    agent: AsyncPolicy<RlPolicy<RLC>>,
38    transition_receiver: Receiver<RLTimeStep<RLC>>,
39    trajectory_receiver: Receiver<RLTrajectory<RLC>>,
40    request_sender: Sender<RequestMessage>,
41    device: Device,
42}
43
44impl<RLC: RLComponentsTypes> AgentEnvAsyncLoop<RLC> {
45    /// Create a new asynchronous runner.
46    ///
47    /// # Arguments
48    ///
49    /// * `env_init` - A function returning an environment instance.
50    /// * `agent` - An [AsyncPolicy](AsyncPolicy) taking actions in the loop.
51    /// * `config` - An [AsyncAgentEnvLoopConfig](AsyncAgentEnvLoopConfig).
52    /// * `transition_sender` - Optional sender for transitions if you want to drive the requests from outside of the loop instance.
53    /// * `trajectory_sender` - Optional sender for trajectories if you want to drive the requests from outside of the loop instance.
54    ///
55    /// # Returns
56    ///
57    /// An async Agent/Environement loop.
58    pub fn new(
59        env_init: RLC::EnvInit,
60        agent: AsyncPolicy<RlPolicy<RLC>>,
61        config: AsyncAgentEnvLoopConfig,
62        transition_device: &Device,
63        transition_sender: Option<Sender<RLTimeStep<RLC>>>,
64        trajectory_sender: Option<Sender<RLTrajectory<RLC>>>,
65    ) -> Self {
66        let (loop_transition_sender, transition_receiver) = std::sync::mpsc::channel();
67        let (loop_trajectory_sender, trajectory_receiver) = std::sync::mpsc::channel();
68        let (request_sender, request_receiver) = std::sync::mpsc::channel();
69        let loop_transition_sender = transition_sender.unwrap_or(loop_transition_sender);
70        let loop_trajectory_sender = trajectory_sender.unwrap_or(loop_trajectory_sender);
71
72        let device = transition_device.clone();
73        let mut loop_agent = agent.clone().to_device(transition_device);
74        let eval = config.eval;
75
76        let mut current_steps = vec![];
77        let mut current_reward = 0.0;
78        let mut step_num = 0;
79        spawn(move || {
80            let mut env = env_init.init();
81            env.reset();
82
83            let mut request_episode = false;
84            loop {
85                let state = env.state();
86                let (action, context) =
87                    loop_agent.action(state.clone().to_observation(&device), config.deterministic);
88
89                let env_action = RLC::Action::from(action);
90                let step_result = env.step(env_action.clone());
91
92                current_reward += step_result.reward;
93                step_num += 1;
94
95                let transition = Transition::new(
96                    state.clone(),
97                    step_result.next_state,
98                    env_action,
99                    Tensor::from_data([step_result.reward], &device),
100                    Tensor::from_data(
101                        [(step_result.done || step_result.truncated) as i32 as f64],
102                        &device,
103                    ),
104                );
105
106                if !request_episode {
107                    loop_agent.decrement_agents(1);
108                    let request = match request_receiver.recv() {
109                        Ok(req) => req,
110                        Err(err) => {
111                            log::error!("Error in env runner : {}", err);
112                            break;
113                        }
114                    };
115                    loop_agent.increment_agents(1);
116
117                    match request {
118                        RequestMessage::Step() => (),
119                        RequestMessage::Episode() => request_episode = true,
120                    }
121                }
122
123                let time_step = TimeStep {
124                    env_id: config.id,
125                    transition,
126                    done: step_result.done,
127                    ep_len: step_num,
128                    cum_reward: current_reward,
129                    action_context: context[0].clone(),
130                };
131                current_steps.push(time_step.clone());
132
133                if !request_episode && let Err(err) = loop_transition_sender.send(time_step) {
134                    log::error!("Error in env runner : {}", err);
135                    break;
136                }
137
138                if step_result.done || step_result.truncated {
139                    if request_episode {
140                        request_episode = false;
141                        loop_trajectory_sender
142                            .send(Trajectory {
143                                timesteps: current_steps.clone(),
144                            })
145                            .expect("Can send trajectory to main thread.");
146                    }
147                    current_steps.clear();
148
149                    env.reset();
150                    current_reward = 0.;
151                    step_num = 0;
152                }
153            }
154        });
155
156        Self {
157            eval,
158            agent,
159            transition_receiver,
160            trajectory_receiver,
161            request_sender,
162            device: transition_device.clone(),
163        }
164    }
165}
166
167impl<RLC> AgentEnvLoop<RLC> for AgentEnvAsyncLoop<RLC>
168where
169    RLC: RLComponentsTypes,
170{
171    fn run_steps(
172        &mut self,
173        num_steps: usize,
174        processor: &mut RLEventProcessorType<RLC>,
175        interrupter: &Interrupter,
176        progress: &mut Progress,
177    ) -> Vec<RLTimeStep<RLC>> {
178        let mut items = vec![];
179        for _ in 0..num_steps {
180            self.request_sender
181                .send(RequestMessage::Step())
182                .expect("Can request transitions.");
183            let transition = self
184                .transition_receiver
185                .recv()
186                .expect("Can receive transitions.");
187            items.push(transition.clone());
188
189            if !self.eval {
190                progress.items_processed += 1;
191                interrupter.fail_on_error(processor.process_train(RLEvent::EnvStep(
192                    EvaluationItem::new(transition.action_context, progress.clone(), None),
193                )));
194
195                if transition.done {
196                    interrupter.fail_on_error(processor.process_train(RLEvent::EpisodeEnd(
197                        EvaluationItem::new(
198                            EpisodeSummary {
199                                episode_length: transition.ep_len,
200                                cum_reward: transition.cum_reward,
201                            },
202                            progress.clone(),
203                            None,
204                        ),
205                    )));
206                }
207            }
208
209            if interrupter.should_stop() {
210                break;
211            }
212        }
213        items
214    }
215
216    fn run_episodes(
217        &mut self,
218        num_episodes: usize,
219        processor: &mut RLEventProcessorType<RLC>,
220        interrupter: &Interrupter,
221        _progress: &mut Progress,
222    ) -> Vec<RLTrajectory<RLC>> {
223        let mut items = vec![];
224        self.agent.increment_agents(1);
225        for episode_num in 0..num_episodes {
226            self.request_sender
227                .send(RequestMessage::Episode())
228                .expect("Can request episodes.");
229            let trajectory = self
230                .trajectory_receiver
231                .recv()
232                .expect("Main thread can receive trajectory.");
233
234            for (i, step) in trajectory.timesteps.iter().enumerate() {
235                // TODO : clean this up.
236                if self.eval {
237                    interrupter.fail_on_error(processor.process_valid(
238                        AgentEvaluationEvent::EnvStep(EvaluationItem::new(
239                            step.action_context.clone(),
240                            Progress::new(i, i, Some("steps".to_string())),
241                            None,
242                        )),
243                    ));
244
245                    if step.done {
246                        interrupter.fail_on_error(processor.process_valid(
247                            AgentEvaluationEvent::EpisodeEnd(EvaluationItem::new(
248                                EpisodeSummary {
249                                    episode_length: step.ep_len,
250                                    cum_reward: step.cum_reward,
251                                },
252                                Progress::new(
253                                    episode_num + 1,
254                                    num_episodes,
255                                    Some("episodes".to_string()),
256                                ),
257                                None,
258                            )),
259                        ));
260                    }
261                } else {
262                    interrupter.fail_on_error(processor.process_train(RLEvent::EnvStep(
263                        EvaluationItem::new(
264                            step.action_context.clone(),
265                            Progress::new(i, i, Some("steps".to_string())),
266                            None,
267                        ),
268                    )));
269
270                    if step.done {
271                        interrupter.fail_on_error(processor.process_train(RLEvent::EpisodeEnd(
272                            EvaluationItem::new(
273                                EpisodeSummary {
274                                    episode_length: step.ep_len,
275                                    cum_reward: step.cum_reward,
276                                },
277                                Progress::new(
278                                    episode_num + 1,
279                                    num_episodes,
280                                    Some("episodes".to_string()),
281                                ),
282                                None,
283                            ),
284                        )));
285                    }
286                }
287            }
288
289            items.push(trajectory);
290            if interrupter.should_stop() {
291                break;
292            }
293        }
294        self.agent.decrement_agents(1);
295        items
296    }
297
298    fn update_policy(&mut self, update: RLC::PolicyState) {
299        self.agent.update(update);
300        self.agent.clone().to_device(&self.device);
301    }
302
303    fn policy(&self) -> RLC::PolicyState {
304        self.agent.state()
305    }
306
307    fn device(&self) -> Device {
308        self.device.clone()
309    }
310}
311
312/// An asynchronous runner for multiple agent/environement interfaces.
313pub struct MultiAgentEnvLoop<RLC: RLComponentsTypes> {
314    num_envs: usize,
315    eval: bool,
316    agent: AsyncPolicy<RLC::Policy>,
317    transition_receiver: Receiver<RLTimeStep<RLC>>,
318    trajectory_receiver: Receiver<RLTrajectory<RLC>>,
319    request_senders: Vec<Sender<RequestMessage>>,
320    device: Device,
321}
322
323impl<RLC: RLComponentsTypes> MultiAgentEnvLoop<RLC> {
324    /// Create a new asynchronous runner for multiple agent/environement interfaces.
325    pub fn new(
326        num_envs: usize,
327        env_init: RLC::EnvInit,
328        agent: AsyncPolicy<RLC::Policy>,
329        eval: bool,
330        deterministic: bool,
331        device: &Device,
332    ) -> Self {
333        let (transition_sender, transition_receiver) = std::sync::mpsc::channel();
334        let (trajectory_sender, trajectory_receiver) = std::sync::mpsc::channel();
335        let mut request_senders = vec![];
336
337        // Double batching : The environments are always one step ahead of requests. This allows inference for the first batch of steps.
338        agent.increment_agents(num_envs);
339
340        for i in 0..num_envs {
341            let config = AsyncAgentEnvLoopConfig {
342                eval,
343                deterministic,
344                id: i,
345            };
346            let runner = AgentEnvAsyncLoop::<RLC>::new(
347                env_init.clone(),
348                agent.clone(),
349                config,
350                &device.clone(),
351                Some(transition_sender.clone()),
352                Some(trajectory_sender.clone()),
353            );
354            request_senders.push(runner.request_sender.clone());
355        }
356
357        // Double batching : The environments are always one step ahead.
358        request_senders.iter().for_each(|s| {
359            s.send(RequestMessage::Step())
360                .expect("Main thread can send step requests.")
361        });
362
363        Self {
364            num_envs,
365            eval,
366            agent: agent.clone(),
367            transition_receiver,
368            trajectory_receiver,
369            request_senders,
370            device: device.clone(),
371        }
372    }
373}
374
375impl<RLC> AgentEnvLoop<RLC> for MultiAgentEnvLoop<RLC>
376where
377    RLC: RLComponentsTypes,
378{
379    fn run_steps(
380        &mut self,
381        num_steps: usize,
382        processor: &mut RLEventProcessorType<RLC>,
383        interrupter: &Interrupter,
384        progress: &mut Progress,
385    ) -> Vec<RLTimeStep<RLC>> {
386        let mut items = vec![];
387        for _ in 0..num_steps {
388            let transition = self
389                .transition_receiver
390                .recv()
391                .expect("Can receive transitions.");
392            items.push(transition.clone());
393
394            self.request_senders[transition.env_id]
395                .send(RequestMessage::Step())
396                .expect("Main thread can request steps.");
397
398            if !self.eval {
399                progress.items_processed += 1;
400                interrupter.fail_on_error(processor.process_train(RLEvent::EnvStep(
401                    EvaluationItem::new(transition.action_context, progress.clone(), None),
402                )));
403
404                if transition.done {
405                    interrupter.fail_on_error(processor.process_train(RLEvent::EpisodeEnd(
406                        EvaluationItem::new(
407                            EpisodeSummary {
408                                episode_length: transition.ep_len,
409                                cum_reward: transition.cum_reward,
410                            },
411                            progress.clone(),
412                            None,
413                        ),
414                    )));
415                }
416            }
417
418            if interrupter.should_stop() {
419                break;
420            }
421        }
422        items
423    }
424
425    fn update_policy(&mut self, update: RLC::PolicyState) {
426        self.agent.update(update);
427        self.agent.clone().to_device(&self.device);
428    }
429
430    fn run_episodes(
431        &mut self,
432        num_episodes: usize,
433        processor: &mut RLEventProcessorType<RLC>,
434        interrupter: &Interrupter,
435        _progress: &mut Progress,
436    ) -> Vec<RLTrajectory<RLC>> {
437        // Send `num_episodes` initial requests.
438        let mut idx = vec![];
439        if num_episodes < self.num_envs {
440            let mut rng = rand::rng();
441            let mut vec: Vec<usize> = (0..self.num_envs).collect();
442            vec.shuffle(&mut rng);
443            idx = vec.into_iter().take(num_episodes).collect();
444        } else {
445            idx = (0..self.num_envs).collect();
446        }
447        let num_requests = self.num_envs.min(num_episodes);
448        idx.into_iter().for_each(|i| {
449            self.request_senders[i]
450                .send(RequestMessage::Episode())
451                .expect("Main thread can request steps.");
452        });
453
454        let mut items = vec![];
455        for episode_num in 0..num_episodes {
456            let trajectory = self
457                .trajectory_receiver
458                .recv()
459                .expect("Can receive trajectory.");
460            items.push(trajectory.clone());
461            if items.len() + num_requests <= num_episodes {
462                self.request_senders[trajectory.timesteps[0].env_id]
463                    .send(RequestMessage::Episode())
464                    .expect("Main thread can request steps.");
465            }
466            for (i, step) in trajectory.timesteps.iter().enumerate() {
467                if self.eval {
468                    interrupter.fail_on_error(processor.process_valid(
469                        AgentEvaluationEvent::EnvStep(EvaluationItem::new(
470                            step.action_context.clone(),
471                            Progress::new(i, i, Some("steps".to_string())),
472                            None,
473                        )),
474                    ));
475
476                    if step.done {
477                        interrupter.fail_on_error(processor.process_valid(
478                            AgentEvaluationEvent::EpisodeEnd(EvaluationItem::new(
479                                EpisodeSummary {
480                                    episode_length: step.ep_len,
481                                    cum_reward: step.cum_reward,
482                                },
483                                Progress::new(
484                                    episode_num + 1,
485                                    num_episodes,
486                                    Some("episodes".to_string()),
487                                ),
488                                None,
489                            )),
490                        ));
491                    }
492                } else {
493                    interrupter.fail_on_error(processor.process_train(RLEvent::EnvStep(
494                        EvaluationItem::new(
495                            step.action_context.clone(),
496                            Progress::new(i, i, Some("steps".to_string())),
497                            None,
498                        ),
499                    )));
500
501                    if step.done {
502                        interrupter.fail_on_error(processor.process_train(RLEvent::EpisodeEnd(
503                            EvaluationItem::new(
504                                EpisodeSummary {
505                                    episode_length: step.ep_len,
506                                    cum_reward: step.cum_reward,
507                                },
508                                Progress::new(
509                                    episode_num + 1,
510                                    num_episodes,
511                                    Some("episodes".to_string()),
512                                ),
513                                None,
514                            ),
515                        )));
516                    }
517                }
518            }
519
520            if interrupter.should_stop() {
521                break;
522            }
523        }
524
525        items
526    }
527
528    fn policy(&self) -> RLC::PolicyState {
529        self.agent.state()
530    }
531
532    fn device(&self) -> Device {
533        self.device.clone()
534    }
535}
536
537#[cfg(test)]
538#[allow(clippy::needless_range_loop)]
539mod tests {
540    use burn_core::data::dataloader::Progress;
541    use burn_rl::AsyncPolicy;
542
543    use crate::learner::rl::env_runner::async_runner::AsyncAgentEnvLoopConfig;
544    use crate::learner::rl::env_runner::base::AgentEnvLoop;
545    use crate::learner::tests::{MockPolicyState, MockProcessor};
546    use crate::{
547        AgentEnvAsyncLoop,
548        learner::tests::{MockEnvInit, MockPolicy, MockRLComponents},
549    };
550    use crate::{AsyncProcessorTraining, Interrupter, MultiAgentEnvLoop};
551
552    fn setup_async_loop(
553        state: usize,
554        eval: bool,
555        deterministic: bool,
556    ) -> AgentEnvAsyncLoop<MockRLComponents> {
557        let env_init = MockEnvInit;
558        let agent = MockPolicy(state);
559        let config = AsyncAgentEnvLoopConfig {
560            eval,
561            deterministic,
562            id: 0,
563        };
564        AgentEnvAsyncLoop::<MockRLComponents>::new(
565            env_init,
566            AsyncPolicy::new(1, agent),
567            config,
568            &Default::default(),
569            None,
570            None,
571        )
572    }
573
574    fn setup_multi_loop(
575        num_envs: usize,
576        autobatch_size: usize,
577        state: usize,
578        eval: bool,
579        deterministic: bool,
580    ) -> MultiAgentEnvLoop<MockRLComponents> {
581        let env_init = MockEnvInit;
582        let agent = MockPolicy(state);
583        MultiAgentEnvLoop::<MockRLComponents>::new(
584            num_envs,
585            env_init,
586            AsyncPolicy::new(autobatch_size, agent),
587            eval,
588            deterministic,
589            &Default::default(),
590        )
591    }
592
593    #[test]
594    fn test_policy_async_loop() {
595        let runner = setup_async_loop(1000, false, false);
596        let policy_state = runner.policy();
597        assert_eq!(policy_state.0, 1000);
598    }
599
600    #[test]
601    fn test_update_policy_async_loop() {
602        let mut runner = setup_async_loop(0, false, false);
603
604        runner.update_policy(MockPolicyState(1));
605        assert_eq!(runner.policy().0, 1);
606    }
607
608    #[test]
609    fn run_steps_returns_requested_number_async_loop() {
610        let mut runner = setup_async_loop(0, false, false);
611        let mut processor = AsyncProcessorTraining::new(MockProcessor);
612        let interrupter = Interrupter::new();
613        let mut progress = Progress {
614            items_processed: 0,
615            items_total: 1,
616            unit: Some("steps".to_string()),
617        };
618
619        let steps = runner.run_steps(1, &mut processor, &interrupter, &mut progress);
620        assert_eq!(steps.len(), 1);
621        let steps = runner.run_steps(8, &mut processor, &interrupter, &mut progress);
622        assert_eq!(steps.len(), 8);
623    }
624
625    #[test]
626    fn run_episodes_returns_requested_number_async_loop() {
627        let mut runner = setup_async_loop(0, false, false);
628        let mut processor = AsyncProcessorTraining::new(MockProcessor);
629        let interrupter = Interrupter::new();
630        let mut progress = Progress {
631            items_processed: 0,
632            items_total: 1,
633            unit: Some("steps".to_string()),
634        };
635
636        let trajectories = runner.run_episodes(1, &mut processor, &interrupter, &mut progress);
637        assert_eq!(trajectories.len(), 1);
638        assert_ne!(trajectories[0].timesteps.len(), 0);
639        let trajectories = runner.run_episodes(8, &mut processor, &interrupter, &mut progress);
640        assert_eq!(trajectories.len(), 8);
641        for i in 0..8 {
642            assert_ne!(trajectories[i].timesteps.len(), 0);
643        }
644    }
645
646    #[test]
647    fn test_policy_multi_loop() {
648        let runner = setup_multi_loop(4, 4, 1000, false, false);
649        let policy_state = runner.policy();
650        assert_eq!(policy_state.0, 1000);
651    }
652
653    #[test]
654    fn test_update_policy_multi_loop() {
655        let mut runner = setup_multi_loop(4, 4, 0, false, false);
656
657        runner.update_policy(MockPolicyState(1));
658        assert_eq!(runner.policy().0, 1);
659    }
660
661    #[test]
662    fn run_steps_returns_requested_number_multi_loop() {
663        fn run_test(num_envs: usize, autobatch_size: usize) {
664            let mut runner = setup_multi_loop(num_envs, autobatch_size, 0, false, false);
665            let mut processor = AsyncProcessorTraining::new(MockProcessor);
666            let interrupter = Interrupter::new();
667            let mut progress = Progress {
668                items_processed: 0,
669                items_total: 1,
670                unit: Some("steps".to_string()),
671            };
672
673            // Kickstart tests by running some steps to make sure it's not a double batching edge case success.
674            let steps = runner.run_steps(8, &mut processor, &interrupter, &mut progress);
675            assert_eq!(steps.len(), 8);
676
677            for i in 0..16 {
678                let steps = runner.run_steps(i, &mut processor, &interrupter, &mut progress);
679                assert_eq!(steps.len(), i);
680            }
681        }
682
683        // num_envs == autobatch_size
684        run_test(1, 1);
685        run_test(4, 4);
686        // num_envs < autobatch_size
687        run_test(1, 2);
688        run_test(1, 3);
689        run_test(2, 3);
690        run_test(2, 4);
691        run_test(5, 19);
692        // num_envs > autobatch_size
693        run_test(2, 1);
694        run_test(8, 1);
695        run_test(3, 2);
696        run_test(8, 2);
697        run_test(8, 3);
698        run_test(8, 7);
699    }
700
701    #[test]
702    fn run_episodes_returns_requested_number_multi_loop() {
703        fn run_test(num_envs: usize, autobatch_size: usize) {
704            let mut runner = setup_multi_loop(num_envs, autobatch_size, 0, false, false);
705            let mut processor = AsyncProcessorTraining::new(MockProcessor);
706            let interrupter = Interrupter::new();
707            let mut progress = Progress {
708                items_processed: 0,
709                items_total: 1,
710                unit: Some("steps".to_string()),
711            };
712
713            // Kickstart tests by running some episodes to make sure it's not a double batching edge case success.
714            let trajectories = runner.run_episodes(8, &mut processor, &interrupter, &mut progress);
715            assert_eq!(trajectories.len(), 8);
716            for j in 0..8 {
717                assert_ne!(trajectories[j].timesteps.len(), 0);
718            }
719
720            for i in 0..16 {
721                let trajectories =
722                    runner.run_episodes(i, &mut processor, &interrupter, &mut progress);
723                assert_eq!(trajectories.len(), i);
724                for j in 0..i {
725                    assert_ne!(trajectories[j].timesteps.len(), 0);
726                }
727            }
728        }
729
730        // num_envs == autobatch_size
731        run_test(1, 1);
732        run_test(4, 4);
733        // num_envs < autobatch_size
734        run_test(1, 2);
735        run_test(1, 3);
736        run_test(2, 3);
737        run_test(2, 4);
738        run_test(5, 19);
739        // num_envs > autobatch_size
740        run_test(2, 1);
741        run_test(8, 1);
742        run_test(3, 2);
743        run_test(8, 2);
744        run_test(8, 3);
745        run_test(8, 7);
746    }
747}