经验采样与小批量生成是连接环境交互与策略更新的核心数据桥梁。在 RSL-RL 中,这一环节由 RolloutStorage 和 CircularBuffer 两大存储组件协同完成:前者负责按时间步收集并编排 on-policy 的 rollout 数据,后者为 AMP 判别器等场景提供带历史深度的循环缓冲采样。理解它们的数据布局、生成器语义以及与 PPO.update() 的交互方式,是掌握训练循环数据流的关键前提。
RolloutStorage 的数据布局与生命周期
RolloutStorage 采用时间×环境的二维张量布局,核心张量的形状为 [num_transitions_per_env, num_envs, ...]。这种布局天然适配向量化环境:每个时间步一次性写入所有并行环境的观测、动作、奖励与终止信号。
存储对象在初始化时根据 training_type 动态分配张量。当类型为 "rl" 时,除了基础字段外,还会预分配 values、actions_log_prob、mu、sigma、returns 和 advantages 等 PPO 所需的计算中间量;当类型为 "distillation" 时,则额外分配 privileged_actions 用于师生蒸馏。
Transition 作为内部嵌套类,充当单步数据的临时容器。PPO.act() 在环境步进前填充动作、价值与策略分布参数,PPO.process_env_step() 在步进后补充奖励与终止信号,最终通过 add_transition() 将整包数据深拷贝到存储的当前时间片。当步数达到 num_transitions_per_env 后,存储进入只读状态,直到 clear() 重置计数器。这一设计严格保证了 on-policy 数据不被后续交互污染。
Sources: rollout_storage.py
从 Rollout 到 Mini-batch 的三类生成器
RolloutStorage 暴露了三类生成器,分别服务于蒸馏训练、前馈策略强化学习与循环策略强化学习。它们的核心差异在于是否展平时间维度、是否保持轨迹边界,以及是否引入随机重排。
蒸馏生成器:顺序遍历
蒸馏模式下,generator() 按时间顺序逐行 yield 数据,不做任何随机打乱。其语义是让学生策略依次回放教师策略在相同观测下产生的 privileged actions,无需考虑时序断裂或优势函数计算。
Sources: rollout_storage.py
前馈 Mini-batch 生成器:全局打乱与分片
对于前馈网络,标准的 PPO 训练要求将多环境、多时间步的数据彻底展平后随机打乱,以破坏样本相关性并满足小批量梯度下降的独立性假设。
mini_batch_generator() 的执行流程如下:首先将 [T, N, ...] 的张量通过 flatten(0, 1) 转化为 [T×N, ...];随后构造一个覆盖全部样本的随机索引序列 indices;接着按 num_mini_batches 将总批量等分为若干 mini_batch_size;最后在 num_epochs 个 epoch 中反复遍历这些分片。由于每个 epoch 都复用同一套打乱索引,同一 mini-batch 在不同 epoch 中对应的数据内容保持不变,仅遍历顺序重复。
这一生成器同时展平了 observations、actions、values、returns、advantages、actions_log_prob、mu 和 sigma,确保策略和价值网络在每个 mini-batch 内接收到完整且一致的 transition 切片。返回的 hidden_states_batch 和 masks_batch 恒为 None,显式标识当前不涉及循环状态。
Sources: rollout_storage.py
循环 Mini-batch 生成器:轨迹保持与填充
循环策略(LSTM/GRU)不能随意打乱时间顺序,否则会导致隐藏状态错位。recurrent_mini_batch_generator() 的核心任务是在环境维度分片的同时保持每个子序列的时序完整性。
它首先调用 split_and_pad_trajectories() 处理 observations 和 dones。该工具函数以 dones 为边界,将展平后的数据切分为若干条独立轨迹,对短轨迹进行零填充,并返回一个布尔掩码 trajectory_masks 标记有效时间步。此后,生成器不再按样本数均分,而是按 num_envs // num_mini_batches 的环境数均分,并在每个 mini-batch 内包含这些环境所产生的全部轨迹。
隐藏状态的处理是循环生成器的关键。saved_hidden_state_a 与 saved_hidden_state_c 在 rollout 阶段以 [time, num_layers, num_envs, hidden_dim] 的格式保存。生成时,先通过 dones 的布尔掩码提取每个轨迹起始时刻的隐藏状态,再将其 reshape 为 [num_layers, batch, hidden_dim] 供 RNN 模块消费。masks_batch 则用于在策略前向传播中屏蔽填充位置,避免无效时间步影响隐藏状态的传播。
Sources: rollout_storage.py, utils.py
CircularBuffer:带历史深度的循环采样
CircularBuffer 是为 AMP 判别器观察缓冲等场景设计的按环境独立循环存储。与 RolloutStorage 的固定时间窗口不同,它在每个环境中维护一个长度上限为 max_len 的环形队列,支持通过 append() 持续写入,并通过 __getitem__ 以 LIFO 方式按历史深度检索。
其内部状态包括:_pointer 指向最新写入位置,_num_pushes 记录每个环境的实际写入次数(用于处理未满缓冲区),_buffer 的形状为 [max_len, batch_size, ...]。reset() 可按批量索引局部清零,适配环境重启场景。
CircularBuffer.mini_batch_generator() 的采样逻辑与 RolloutStorage 有本质区别:它从当前有效长度与批量大小的所有组合中随机抽取 epoch_batch_size 个 (history_index, env_index) 二元组,再将其映射到环形缓冲的物理索引上。这种设计使得判别器可以随机访问历史观察的任意时间切片,而不受 rollout 时间窗口的严格限制。AMP 算法通过 zip() 将 RolloutStorage 的生成器与两个 CircularBuffer 生成器同步对齐,确保每个 PPO mini-batch 都能拿到对应的策略观察与演示观察样本。
Sources: circular_buffer.py, ppo_amp.py
与 PPO 更新循环的集成
PPO.update() 是消费上述生成器的最终环节。它首先根据 policy.is_recurrent 选择对应的生成器,随后进入一个嵌套循环:外层为 num_learning_epochs,内层为 num_mini_batches。每次迭代从生成器取出一份 mini-batch,执行以下步骤:
- 可选的每 mini-batch 优势归一化:当
normalize_advantage_per_mini_batch=True时,在当前 batch 内重新计算均值与标准差,而非使用全局归一化。 - 对称数据增强:若启用对称性,对观测与动作进行镜像增强,并相应重复旧策略的对数概率与价值目标。
- 策略重评估:用当前网络参数重新计算
actions_log_prob和value,为 PPO 的 clipped surrogate 提供新旧策略对比。 - 自适应 KL 调度:根据新旧策略分布的 KL 散度动态调整学习率。
整个更新过程严格遵循生成器提供的批次边界,前馈与循环路径在 update() 中共享同一套损失计算逻辑,区别仅在于 masks 和 hidden_states 是否非空。
Sources: ppo.py
三类采样策略对比
| 维度 | Distillation Generator | Feedforward Mini-batch | Recurrent Mini-batch |
|---|---|---|---|
| 适用训练类型 | distillation |
rl + 前馈策略 |
rl + 循环策略 |
| 时间维度处理 | 按时间步顺序 yield | flatten(0,1) 后全局打乱 |
按 dones 切分轨迹并填充 |
| 随机性 | 无 | 每 epoch 固定一套打乱索引 | 环境维度固定分片,时序内保持 |
| 隐藏状态 | 不涉及 | None |
提取轨迹起始状态并 reshape |
| 掩码 | 不涉及 | None |
trajectory_masks 屏蔽填充 |
| 典型消费者 | DistillationRunner |
OnPolicyRunner + PPO |
OnPolicyRunner + PPO-Recurrent |
数据流架构概览
以下 Mermaid 图展示了从环境交互到 mini-batch 消费的完整数据流:
flowchart TD
A[向量化环境<br/>VecEnv] -->|step| B[PPO.act]
B -->|actions| A
A -->|obs, rewards, dones| C[PPO.process_env_step]
C -->|Transition| D[RolloutStorage<br/>add_transition]
subgraph 存储层
D --> E[observations<br/>[T,N,...]]
D --> F[rewards/dones<br/>[T,N,1]]
D --> G[values/log_prob<br/>[T,N,1]]
D --> H[saved_hidden_states<br/>[T,Layers,N,H]]
end
I[PPO.compute_returns] -->|GAE| J[returns / advantages<br/>写入 Storage]
subgraph 生成器层
K[RolloutStorage<br/>mini_batch_generator] -->|flatten & shuffle| L[Mini-batch Tensor<br/>[B,...]]
M[RolloutStorage<br/>recurrent_mini_batch_generator] -->|split_and_pad| N[Trajectories<br/>+ Masks + Hidden States]
O[CircularBuffer<br/>mini_batch_generator] -->|random history index| P[Disc Obs Batch]
end
G --> K
E --> K
J --> K
E --> M
G --> M
H --> M
J --> M
subgraph 更新层
L --> Q[PPO.update<br/>Surrogate / Value Loss]
N --> Q
P --> R[PPOAMP.update<br/>Discriminator Loss]
end
关键设计原则总结
RSL-RL 的经验采样体系体现了三条紧密关联的设计原则。第一,张量形状的一致性:所有存储字段严格保持 [time, env, ...] 或展平后的 [batch, ...],使得生成器可以通过统一的索引切片提取相关数据,无需复杂的打包与解包。第二,生成器即接口:RolloutStorage 不暴露原始张量,而是通过 Python Generator 向算法层提供 mini-batch,强制实现延迟计算与内存友好。第三,策略架构决定采样方式:前馈与循环策略在更新阶段共享同一套 PPO.update() 逻辑,差异完全由生成器封装,这一解耦使得新增策略架构时只需实现对应的生成器即可。
如需深入理解 GAE 回报计算与策略损失的数学细节,可继续阅读 PPO 算法实现与训练流程;若关注循环策略与注意力策略的网络结构,请参考 循环与注意力策略变体。关于 RolloutStorage 中 Transition 的数据结构与存储初始化,亦可交叉查阅 Rollout 数据存储与 Transition。