Skip to main content

thrust_rl/buffer/
rollout.rs

1//! Rollout buffer for storing and processing trajectories
2//!
3//! This module implements experience storage for PPO training, including:
4//! - Trajectory storage (observations, actions, rewards, etc.)
5//! - Generalized Advantage Estimation (GAE) computation
6//! - Efficient sampling for minibatch training
7//!
8//! # Buffer Layout
9//!
10//! The buffer uses a `[num_steps, num_envs]` layout where:
11//! - `num_steps`: Number of timesteps per rollout (typically 128-2048)
12//! - `num_envs`: Number of parallel environments
13//!
14//! This layout provides good cache locality for forward passes and
15//! efficient computation of advantages.
16
17// Re-export main components
18pub use gae::{
19    compute_advantages, compute_advantages_multi_agent, compute_advantages_partial,
20    compute_mc_returns, compute_nstep_returns, normalize_advantages,
21};
22pub use recurrent::{RecurrentMinibatchIterator, RecurrentRolloutBatch, RecurrentRolloutBuffer};
23pub use sampling::{
24    Minibatch, MinibatchIterator, generate_minibatch_indices, sample_minibatch, shuffle_indices,
25    train_val_split,
26};
27pub use storage::{RolloutBatch, RolloutBuffer, RolloutBurnTensors};
28pub use vtrace::compute_vtrace_advantages;
29
30// Submodules
31mod gae;
32mod recurrent;
33mod sampling;
34mod storage;
35mod vtrace;
36
37#[cfg(test)]
38mod tests;
39
40// Legacy interface - re-export compute_advantages as a method on RolloutBuffer
41impl RolloutBuffer {
42    /// Compute advantages using Generalized Advantage Estimation
43    ///
44    /// This is a convenience method that calls the module-level function.
45    /// Iterates the full `[num_steps, num_envs]` capacity; use
46    /// [`Self::compute_advantages_partial`] when the buffer is only
47    /// partially filled.
48    ///
49    /// # Arguments
50    /// * `last_values` - Value estimates for the final states `[num_envs]`
51    /// * `gamma` - Discount factor
52    /// * `gae_lambda` - GAE lambda parameter
53    pub fn compute_advantages(&mut self, last_values: &[f32], gamma: f32, gae_lambda: f32) {
54        gae::compute_advantages(self, last_values, gamma, gae_lambda);
55    }
56
57    /// Compute advantages over the first `valid_steps` rows only.
58    ///
59    /// Convenience wrapper around the module-level
60    /// [`compute_advantages_partial`]. Use this when the rollout buffer
61    /// has been partially filled (`valid_steps < num_steps`) to prevent
62    /// zero-padded tail rows from contaminating GAE on the real prefix.
63    ///
64    /// # Arguments
65    /// * `valid_steps` - Number of filled rows at the start of the buffer
66    /// * `last_values` - Bootstrap `V(s_{T+1})` for the state after row
67    ///   `valid_steps - 1`, per environment `[num_envs]`
68    /// * `gamma` - Discount factor
69    /// * `gae_lambda` - GAE lambda parameter
70    pub fn compute_advantages_partial(
71        &mut self,
72        valid_steps: usize,
73        last_values: &[f32],
74        gamma: f32,
75        gae_lambda: f32,
76    ) {
77        gae::compute_advantages_partial(self, valid_steps, last_values, gamma, gae_lambda);
78    }
79
80    /// Compute V-trace targets and advantages (Espeholt et al. 2018) for
81    /// off-policy correction.
82    ///
83    /// Convenience wrapper around the module-level
84    /// [`compute_vtrace_advantages`]. The buffer's stored `log_probs` are
85    /// treated as the behavior-policy log-probs; `target_log_probs`
86    /// (`[num_steps][num_envs]`) are the current target policy's log-probs
87    /// reevaluated over the stored observations/actions. Results are
88    /// written into `advantages`/`returns` exactly like
89    /// [`Self::compute_advantages`], so `get_batch()` works as usual.
90    ///
91    /// # Arguments
92    /// * `target_log_probs` - Target-policy log-probs `[num_steps][num_envs]`
93    /// * `last_values` - Bootstrap `V(s_{T+1})` per environment `[num_envs]`
94    /// * `gamma` - Discount factor
95    /// * `rho_bar` - IS ratio clip for the TD target (typically 1.0)
96    /// * `c_bar` - IS ratio clip for the trace coefficient (typically 1.0)
97    pub fn compute_vtrace_advantages(
98        &mut self,
99        target_log_probs: &[Vec<f32>],
100        last_values: &[f32],
101        gamma: f32,
102        rho_bar: f32,
103        c_bar: f32,
104    ) {
105        vtrace::compute_vtrace_advantages(
106            self,
107            target_log_probs,
108            last_values,
109            gamma,
110            rho_bar,
111            c_bar,
112        );
113    }
114
115    /// Get a batch of all data from the buffer
116    ///
117    /// This is a convenience method that calls the module-level function.
118    /// Returns the full `[num_steps, num_envs]` capacity, including any
119    /// zero-padded unwritten tail; use [`Self::get_filled_batch`] when
120    /// the buffer is only partially filled.
121    pub fn get_batch(&self) -> RolloutBatch {
122        RolloutBatch::from_buffer(self)
123    }
124
125    /// Get a batch covering only the first `valid_steps` rows.
126    ///
127    /// Use this when the buffer was filled with fewer than `num_steps`
128    /// transitions (e.g. an early-terminating rollout). It prevents the
129    /// zero-initialized tail of the buffer from being handed to PPO as
130    /// fake training data.
131    ///
132    /// # Panics
133    /// Panics if `valid_steps > self.shape().0`.
134    pub fn get_filled_batch(&self, valid_steps: usize) -> RolloutBatch {
135        RolloutBatch::from_buffer_partial(self, valid_steps)
136    }
137}