AReaL 中 PPO/GRPO 系列 RL 算法实战指南:从 Vanilla PPO 到 IcePop/KPop 的配置切换与源码原理
【免费下载链接】AReaLThe RL Bridge for LLM-based Agent Applications. Made Simple & Flexible.项目地址: https://gitcode.com/GitHub_Trending/are/AReaL
本指南系统讲解 AReaL(The RL Bridge for LLM-based Agent Applications)对类 PPO 强化学习算法族的统一实现,涵盖 Vanilla PPO、GRPO、Dr.GRPO、LitePPO、RLOO、DAPO、SAPO、GSPO,以及 IcePop/KPop 两种 token 掩码策略。这些算法共享同一套基础目标,仅在归一化策略、裁剪机制、重要性采样级别上存在差异,通过修改少量配置即可在它们之间自由切换。读完本文,你将掌握 AReaL 中这些算法的配置矩阵、CLI 运行方式、GAE 优势估计细节,以及每个算法在源码中的实现位置与验证路径。
算法总览:一个配置框架,十种算法策略
AReaL 将一系列 PPO 变体整合进统一的训练框架中,全部配置定义于areal/api/cli_args.py的PPOActorConfig与NormConfig数据类。当前支持的算法包括:
- Vanilla PPO:使用学习到的 critic 价值函数,通过 GAE 估计优势
- GRPO(DeepSeekMath):去掉 critic,使用组内奖励归一化(论文 arXiv:2402.03300)
- Dr.GRPO:GRPO 的改进版,移除长度归一化与标准差归一化(论文 arXiv:2503.20783)
- LitePPO:轻量级 PPO(论文 arXiv:2508.08221)
- RLOO:REINFORCE Leave-One-Out,用留一均值做基线(论文 arXiv:2402.14740)
- DAPO:非对称裁剪 + 动态采样(论文 arXiv:2503.14476)
- SAPO:软自适应策略优化,用 sigmoid 软门替换硬裁剪(论文 arXiv:2511.20347)
- GSPO(Qwen3):序列级重要性采样(论文 arXiv:2507.18071)
- IcePop:基于重要性比率的 token 掩码(可与其它 RL 算法组合)
- KPop:双向二元 KL 散度 token 掩码(可与其它 RL 算法组合)
IcePop 和 KPop 是 token 掩码策略,可以叠加在上面任意 RL 算法之上。这些算法共享相同的基础目标,但在归一化策略、裁剪机制、重要性采样级别等方面有所不同——这正是 AReaL 将它们统一到一套配置框架的原因:通过调整少量配置参数即可在不同算法之间切换。
运行方式:统一的执行入口
所有算法使用相同的执行模式,AReaL 官方建议直接修改配置 YAML 文件中的参数。训练入口脚本是examples/math/gsm8k_rl.py,它通过PPOTrainer驱动完整 RL 循环:加载 config、tokenizer、数据集后,以字符串路径areal.workflow.openai.math_agent.MathAgent指定 rollout workflow(详见 examples/math/gsm8k_rl.py)。
| 后端 | 命令 |
|---|---|
| local | python3 examples/math/gsm8k_rl.py --config examples/math/gsm8k_<algo>.yaml scheduler.type=local |
| ray | python3 examples/math/gsm8k_rl.py --config examples/math/gsm8k_<algo>.yaml scheduler.type=ray |
| slurm | python3 examples/math/gsm8k_rl.py --config examples/math/gsm8k_<algo>.yaml scheduler.type=slurm |
将<algo>替换为ppo、grpo、drgrpo、liteppo、rloo、gspo、dapo_dynamic_bs、sapo、icepop或kpop。这些配置文件都真实存在于仓库中,例如 examples/math/gsm8k_grpo.yaml、examples/math/gsm8k_ppo.yaml、examples/math/gsm8k_icepop.yaml、examples/math/gsm8k_kpop.yaml 等。
通过 CLI 覆盖切换算法
除了直接编辑 YAML,你还可以通过覆盖配置参数来切换算法,不必为每个算法单独准备一份配置:
# Dr.GRPO (从GRPO配置) python3 examples/math/gsm8k_rl.py \ --config examples/math/gsm8k_grpo.yaml \ scheduler.type=local \ actor.adv_norm.mean_level=group \ actor.adv_norm.std_level=null # GSPO (从GRPO配置) python3 examples/math/gsm8k_rl.py \ --config examples/math/gsm8k_grpo.yaml \ scheduler.type=local \ +actor.importance_sampling_level=sequence # SAPO (从GRPO配置) python3 examples/math/gsm8k_rl.py \ --config examples/math/gsm8k_grpo.yaml \ scheduler.type=local \ +actor.use_sapo_loss=true \ +actor.sapo_tau_pos=1.0 \ +actor.sapo_tau_neg=1.05 \ actor.use_decoupled_loss=false注意:添加原始 YAML 中不存在的键时,必须使用+前缀(如+actor.importance_sampling_level=sequence),否则会因键不存在而报错。
核心配置参数
所有配置都定义在areal/api/cli_args.py中的PPOActorConfig和NormConfig下,完整字段清单可参考 CLI 配置参考。
奖励和优势归一化(actor.reward_norm和actor.adv_norm)
NormConfig数据类(源码位置)控制奖励和优势的归一化方式:
| 参数 | 类型 | 选项 | 描述 |
|---|---|---|---|
mean_level | str | None | "batch"、"group"、None | 计算均值的级别 |
std_level | str | None | "batch"、"group"、None | 计算标准差的级别 |
mean_leave1out | bool | true、false | 使用留一法平均值(排除当前样本) |
std_unbiased | bool | true、false | 使用无偏标准差计算(默认:true,该默认值自 v0.3.4 起由false变更而来) |
eps | float | - | 避免除零的小常数(默认:1e-5) |
group_size | int | - | 分组级归一化的组大小 |
"Batch" 级在整个全局批次上计算均值/标准差,而 "group" 级在组内计算(例如,共享相同提示的轨迹)。分组边界来自 rollout 批次元数据(TrajBatchMeta.traj_group_sizes)而非group_size,因此即使各组大小不一(例如部分样本被过滤),仍会按提示逐组归一化;仅当该元数据不可用时,group_size才作为固定步长的回退方案生效。将mean_level或std_level设为None分别跳过均值减法或标准差缩放。如果整个字段被省略(例如 YAML 中的adv_norm: null),则不执行归一化。
示例:
actor: adv_norm: null reward_norm: mean_level: group std_level: group group_size: ${gconfig.n_samples}从源码看,NormConfig的__post_init__会校验mean_level/std_level必须是"batch"、"group"或None,且使用分组统计时group_size必须为正整数(源码位置)。PPOActorConfig还提供了uses_group_statistics属性与resolve_min_usable_group_size()方法,用于在部分 rollout 样本失败或过滤时推导"最少可用组大小":使用分组统计时至少需要 2 个组内成员,否则归一化无从谈起(源码位置)。
AReaL 默认实践:默认配置使用std_level: batch进行优势归一化。这已成为 AReaL 团队在各种 RL 应用中的标准实践,从游戏 AI(StarCraft)到 LLM 训练(RLHF、推理、agent 设置)。虽然 Dr.GRPO 建议使用std_level: null以获得潜在更好的性能,但 AReaL 保留std_level: batch以保持向后兼容性。寻求 Dr.GRPO 风格行为的用户应设置actor.adv_norm.std_level=null。
作为参考,examples/math/gsm8k_grpo.yaml 中实际的默认 GRPO 配置为:
reward_norm: mean_level: group std_level: group group_size: ${gconfig.n_samples} adv_norm: mean_level: batch std_level: batch裁剪策略(actor.eps_clip*)
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
eps_clip | float | 0.2 | 下裁剪边界:比率裁剪到[1-eps_clip, ...] |
eps_clip_higher | float | None | None | 上裁剪边界:设置时,比率裁剪到[1-eps_clip, 1+eps_clip_higher] |
当eps_clip_higher为None时,使用对称裁剪:$\text{clip}(r, 1-\epsilon, 1+\epsilon)$。当设置eps_clip_higher时(DAPO 风格),使用非对称裁剪:$\text{clip}(r, 1-\epsilon_{\text{low}}, 1+\epsilon_{\text{high}})$。注意实际配置文件中eps_clip的取值随数据集与场景变化,例如 GRPO 示例中使用了eps_clip: 0.4(examples/math/gsm8k_grpo.yaml)。
重要性采样级别(actor.importance_sampling_level)
| 参数 | 类型 | 选项 | 描述 |
|---|---|---|---|
importance_sampling_level | str | "token"、"sequence" | 计算重要性比率的级别 |
"token"(默认):标准逐 token 重要性比率(GRPO、PPO 等)"sequence"(GSPO):逐 token 比率的序列级几何平均值
在实现层面,重要性权重统一通过_compute_importance_weight()计算为exp(logp_num - logp_denom)(源码位置);sequence 级别则在此基础上对逐 token 对数概率取序列级几何平均。PPOActorConfig.__post_init__还会校验importance_sampling_level只能是token或sequence(源码位置)。
算法配置矩阵
下表展示了如何通过设置适当的参数来配置每个算法:
| 算法 | adv_norm.mean_level | adv_norm.std_level | adv_norm.mean_leave1out | importance_sampling_level | 特殊配置 |
|---|---|---|---|---|---|
| PPO | batch | batch | false | token | 需要 critic 模型 |
| GRPO | batch | batch | false | token | - |
| Dr.GRPO | group | null | false | token | - |
| LitePPO | group | batch | false | token | - |
| RLOO | group | null | true | token | - |
| GSPO | batch | batch | false | sequence | - |
| DAPO | batch | batch | false | token | 非对称裁剪,动态采样 |
| SAPO | batch | batch | false | token | use_sapo_loss=true |
| IcePop | batch | batch | false | token | rejection_sampling.metric=ratio |
| KPop | batch | batch | false | token | rejection_sampling.metric=binary_kl |
注意:"GRPO" 行反映原始 DeepSeekMath 公式。AReaL 的默认 GRPO 配置使用这些设置,但已移除长度归一化(见下文"AReaL 实现说明")。此外,源码中的PPOActorConfig还提供了算法矩阵之外的更多开关,例如use_cispo_loss(CISPO,来自 MiniMax-M1,arXiv:2506.13585),它要求eps_clip_higher > 0、importance_sampling_level='token',且与 SAPO 互斥(源码位置),进一步扩展了这套"配置即算法"框架的表达能力。
算法特定选项
Vanilla PPO
Vanilla PPO 使用学习到的价值函数(critic)通过 GAE 估计优势。关键配置差异是它需要一个critic:配置部分,包含自己的模型和优化器——这在源码中对应PPOCriticConfig数据类(源码位置),其中eps_clip默认值为0.5(用于价值损失裁剪)。完整的配置示例见examples/math/gsm8k_ppo.yaml。
GRPO
$$ J_{\text{GRPO}}(\theta) = \mathbb{E}{\substack{q \sim P(Q), \ {o_i}{i=1}^G \sim \pi_{\theta_{\text{old}}}(O \mid q)}} \left[ \frac{1}{G} \sum_{i=1}^G \sum_{t=1}^{|o_i|} \min\left( r_{i,t}(\theta) \hat{A}{i,t}, \text{clip}\left( r{i,t}(\theta), 1-\epsilon, 1+\epsilon \right) \hat{A}{i,t} \right) - \beta D{\mathrm{KL}}\left[ \pi_\theta \middle| \pi_{\text{ref}} \right] \right] $$
其中:
$$ r_{i,t}(\theta) = \frac{\pi_\theta(o_{i,t} \mid q, o_{i,<t})}{\pi_{\theta_{\text{old}}}(o_{i,t} \mid q, o_{i,<t})}, \quad \hat{A}{i,t} = \frac{r_i - \text{mean}({r_i}{i=1}^G)}{\text{std}({r_i}_{i=1}^G)}. $$
RLOO (REINFORCE Leave-One-Out)
RLOO 通过平均其他采样响应的奖励(排除当前响应)来估计基线。这通过设置actor.adv_norm.mean_leave1out=true实现。
$$ J_{\text{RLOO}}(\theta) = \mathbb{E}{\substack{q \sim P(Q), \ {o_i}{i=1}^G \sim \pi_{\theta_{\text{old}}}(O \mid q)}} \left[ \frac{1}{G} \sum_{i=1}^G \frac{1}{|o_i|} \sum_{t=1}^{|o_i|} \min\left( r_{i,t}(\theta) \hat{A}{i,t}, \text{clip}\left( r{i,t}(\theta), 1-\epsilon, 1+\epsilon \right) \hat{A}_{i,t} \right) \right] $$
其中:
$$ \hat{A}{i,t} = r_i - \frac{1}{G-1} \sum{j \neq i} r_j. $$
GSPO (Group Sequence Policy Optimization)
GSPO 在序列级别而非 token 级别计算重要性采样比率。
标准 PPO(token 级):
$$ r_{i,t}(\theta) = \frac{\pi_\theta(o_{i,t} \mid q, o_{i,<t})}{\pi_{\theta_{\text{old}}}(o_{i,t} \mid q, o_{i,<t})} $$
GSPO(序列级):
$$ r_i(\theta) = \exp\left(\frac{1}{|o_i|}\sum_{t=1}^{|o_i|} \log\frac{\pi_\theta(o_{i,t} \mid q, o_{i,<t})}{\pi_{\theta_{\text{old}}}(o_{i,t} \mid q, o_{i,<t})}\right) $$
这一"序列级几何平均"语义不仅用于 GSPO 的目标函数,也复用在RejectionSamplingConfig的 sequence 级过滤中:agg='mean'时比率在 log 空间聚合为几何平均,长度不变式(length-invariant),与 GSPO 语义保持一致(源码位置)。
SAPO (Soft Adaptive Policy Optimization)
SAPO 用软 sigmoid 门替换 PPO 的硬裁剪,提供平滑梯度和非对称控制。
标准 PPO:
$$ L^{\text{PPO}} = -\mathbb{E}_t[\min(r_t A_t, r_t^{\text{clip}} A_t)] $$
SAPO(带软门):
- 对于正向优势:$g_t^+ = \frac{4}{\tau_{\text{pos}}} \sigma(\tau_{\text{pos}} (r_t - 1))$
- 对于负向优势:$g_t^- = \frac{4}{\tau_{\text{neg}}} \sigma(\tau_{\text{neg}} (r_t - 1))$
- 损失:$L^{\text{SAPO}} = -\mathbb{E}_t[g_t A_t]$,其中如果 $A_t > 0$ 则 $g_t = g_t^+$,否则 $g_t = g_t^-$
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
actor.use_sapo_loss | bool | false | 启用 SAPO 损失代替 PPO 裁剪 |
actor.sapo_tau_pos | float | 1.0 | 正向优势的温度参数 |
actor.sapo_tau_neg | float | 1.05 | 负向优势的温度参数 |
注意:SAPO 需要actor.use_decoupled_loss=false。这一约束在源码中有强制校验:PPOActorConfig.__post_init__会在use_sapo_loss=true且use_decoupled_loss=true时直接抛出ValueError,同时校验两个温度参数必须为正数(源码位置)。
actor: use_sapo_loss: true sapo_tau_pos: 1.0 sapo_tau_neg: 1.05 use_decoupled_loss: falseDAPO
DAPO 引入非对称裁剪和动态采样,后者排除所有响应都完全正确或完全错误的样本。
$$ J_{\text{DAPO}}(\theta) = \mathbb{E}{\substack{(q,a) \sim \mathcal{D}, \ {o_i}{i=1}^G \sim \pi_{\theta_{\text{old}}}(o \mid q)}} \left[ \frac{1}{\sum_{i=1}^G |o_i|} \sum_{i=1}^G \sum_{t=1}^{|o_i|} \min\left( r_{i,t}(\theta) \hat{A}{i,t}, \text{clip}\left( r{i,t}(\theta), 1-\epsilon_{\text{low}}, 1+\epsilon_{\text{high}} \right) \hat{A}_{i,t} \right) \right] $$
其中 $\hat{A}{i,t}$ 是分组归一化优势,$r{i,t}(\theta)$ 是 token 级策略比率。
非对称裁剪参数:
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
actor.eps_clip | float | 0.2 | 下裁剪边界 |
actor.eps_clip_higher | float | - | 上裁剪边界(设置以启用非对称) |
过长惩罚参数:
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
actor.overlong_reward_penalty | bool | false | 启用过长响应惩罚 |
actor.overlong_tokens | int | - | 被视为过长的尾部 token 数量 |
actor.overlong_penalty_factor | float | - | 应用于过长响应的惩罚因子 |
在实现层面,过长惩罚在PPOActor._compute_advantages()的最前端通过reward_overlong_penalty()施加,然后再进入 reward scaling、clip 与归一化流程(源码位置)。
动态采样:
AReaL 通过传递给PPOTrainer.train()的dynamic_filter_fn支持动态采样。该函数接收从相同提示采样的分组轨迹,并返回布尔值指示是否接受它们进行训练:
trainer.train( workflow=..., dynamic_filter_fn=lambda x: 0 < x["rewards"].mean() < 1 )在RLTrainer的训练循环中,dynamic_filter_fn作为should_accept_fn传入prepare_batch,由_collect_trainable_rollout_batch()依据dynamic_bs决定收集行为(源码位置)。
默认情况下,AReaL 使用固定批量大小的动态过滤——它等待收集到batch_size个接受样本后再进行训练。这与某些使用动态批量大小的 DAPO 实现不同,后者收集整个批次的样本然后过滤它们。以下选项控制批量大小行为:
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
dynamic_bs | bool | false | 启用动态批量大小 |
IcePop
IcePop 对重要性比率 $r_{i,t} = \frac{\pi_\theta(o_{i,t} \mid q, o_{i,<t})}{\pi_{\theta_\text{old}}(o_{i,t} \mid q, o_{i,<t})}$ 超出可配置范围 $[\alpha, \beta]$ 的 token 进行掩码(其中 $\pi_\theta$ 为当前训练策略,$\pi_{\theta_\text{old}}$ 为采样时的行为策略)。重要性比率过低或过高的 token 不参与损失计算。
通过rejection_sampling配置的metric=ratio实现:
actor: use_decoupled_loss: true rejection_sampling: level: token action: mask metric: ratio lower: 0.5 upper: 5.0| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
actor.rejection_sampling.metric | str | - | 设置为ratio以启用 IcePop |
actor.rejection_sampling.lower | float | 0.5 | 重要性比率下界 |
actor.rejection_sampling.upper | float | 5.0 | 重要性比率上界 |
注意:IcePop 需要actor.use_decoupled_loss=true,否则rejection_sampling不生效。源码中PPOActorConfig.__post_init__会对该组合发出明确警告("rejection_sampling is configured but use_decoupled_loss=False. Filtering will be ignored.",源码位置)。完整的配置示例见examples/math/gsm8k_icepop.yaml。
RejectionSamplingConfig(源码位置)是 IcePop/KPop 及更一般过滤策略的统一实现,它取代了旧版的behave_imp_weight_cap/behave_imp_weight_mode两个参数。除ratio外还支持kl_k1、kl_k2、kl_k3三种 KL 估计量,action可选mask(将 loss_mask 置零、完全排除梯度)或clamp(将重要性权重截断到边界、保留有界梯度)。校验规则包括:ratio度量下upper必须大于 1.0、lower必须为正;clamp动作只支持ratio度量。
KPop
KPop 对双向二元 KL 散度超过阈值的 token 进行掩码。对于每个 token,计算:
$$ \text{KL}{\text{fwd}} = \text{KL}(P\theta | P_{\theta_\text{old}}), \quad \text{KL}{\text{rev}} = \text{KL}(P{\theta_\text{old}} | P_\theta) $$
其中每个 token 概率被视为伯努利参数:$\text{KL}(P | Q) = p \log \frac{p}{q} + (1-p) \log \frac{1-p}{1-q}$。$\max(\text{KL}{\text{fwd}}, \text{KL}{\text{rev}}) > \phi$ 的 token 被掩码(此处 $\phi$ 对应actor.rejection_sampling.upper)。
通过rejection_sampling配置的metric=binary_kl实现:
actor: use_decoupled_loss: true rejection_sampling: level: token action: mask metric: binary_kl upper: 2.0| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
actor.rejection_sampling.metric | str | - | 设置为binary_kl以启用 KPop |
actor.rejection_sampling.upper | float | 2.0 | KL 散度阈值($\phi$) |
注意:KPop 仅支持action=mask(不支持clamp),且lower在binary_kl中不使用。KPop 需要actor.use_decoupled_loss=true,否则rejection_sampling不生效。完整的配置示例见examples/math/gsm8k_kpop.yaml。
核心概念
奖励:AReaL 假设基于结果的奖励。每个可能由连接的 LLM 输入-输出对组成的轨迹,在序列级而非 token 级被分配一个标量奖励。
优势:AReaL 为轨迹中的每个输出 token 计算逐 token 优势。PPO 算法将结果奖励视为最后一个 token 的奖励,所有前面的 token 奖励为 0。然后 AReaL 通过广义优势估计(GAE)应用标准折扣和 TD 误差反向传播来计算每个 token 的优势。使用默认的 token 级递推时,如果discount=1、gae_lambda=1、critic value 为 0 且禁用 KL 正则化,终止结果奖励会等效广播到每个生成 token。
GAE 时间步单位
actor.gae_timestep_unit用于选择 GAE 按生成 token 还是生成 turn 推进。Prompt、tool、padding 及其他被 mask 的位置都不会消耗 GAE 时间步。该参数在源码中被限制为token或turn两个取值(源码位置)。
Token 级 GAE
token是默认值,并保留 AReaL 原有行为。对于相邻的有效生成 token,AReaL 计算:
$$ \delta_t = r_t + \gamma V_{t+1} - V_t, \qquad A_t = \delta_t + \gamma \lambda A_{t+1}, \qquad G_t = A_t + V_t, $$
其中 $\gamma$ 为actor.discount,$\lambda$ 为actor.gae_lambda配置解析出的逐轨迹取值。奖励 $r_t$ 包含结果奖励增量和 token 级 KL 惩罚。以 EOS 结束的轨迹使用 0 作为终止 bootstrap;没有 EOS 的截断轨迹则使用最后一个 value 估计进行 bootstrap。这一递推逻辑对应_compute_token_level_gae()的实现(源码位置),它从序列末尾向前迭代,并用loss_mask跳过不贡献损失的 token。
Turn 级 GAE
turn将每个非空生成 turn 视为一个宏观时间步。对于 turn $u$,AReaL 将其中的 task reward 增量求和为 $r_u^{\mathrm{task}}$,并使用该 turn 第一个有效 action token 位置的 value 作为 $V_u$,然后计算:
$$ \delta_u^{\mathrm{task}} = r_u^{\mathrm{task}} + \gamma V_{u+1} - V_u, \qquad A_u^{\mathrm{task}} = \delta_u^{\mathrm{task}} + \gamma \lambda A_{u+1}^{\mathrm{task}}. $$
Task advantage $A_u^{\mathrm{task}}$ 和 critic target $G_u=A_u^{\mathrm{task}}+V_u$ 会广播到该 turn 中的每个有效 token。Token 级 KL 不会进入 turn 递推和 critic target;在可选的actor.adv_norm之前,turn $u$ 中 token $j$ 的 actor advantage 为 $A_{u,j}=A_u^{\mathrm{task}}+r_{u,j}^{\mathrm{KL}}$。这样可以避免先对一个 turn 的 KL 惩罚求和,再将总和广播回每个 token。
Turn 级 GAE 依赖turn_ids元数据的校验:_validate_turn_ids()要求turn_ids与loss_mask形状相同、位于同一 device、使用整数 dtype,且所有有效 loss token 的 ID 非负并小于序列长度(源码位置)。
动态 GAE lambda
actor.gae_lambda既可以是静态 float,也可以是 callable 的点分路径。actor.gae_lambda_kwargs用于向 callable 传递关键字参数,配置静态 float 时会被忽略。该函数接收包含三个[B]形状 tensor 的 context(GAELambdaContext,源码位置):
effective_token_lengths:有效生成 token 数量,包括有效的 EOS token;turn_counts:非空生成 turn 数量;token 模式缺少turn_ids时为 0;timestep_lengths:由gae_timestep_unit选择的长度 $L$。
Callable 必须在相同 device 上返回[B]形状的有限浮点 tensor,即每条本地轨迹一个 lambda。返回的 lambda 会用于该轨迹的所有选定时间步。静态 float 在运行时被resolve_gae_lambda_fn()包装为constant_gae_lambda()(逐轨迹返回同一个常量),而字符串路径则通过import_from_string()动态导入(源码位置)。
内置了两个长度自适应函数:
| 函数路径 | 参数 | 定义 |
|---|---|---|
areal.trainer.ppo.lambda_fn.vapo_length_adaptive_gae | alpha > 0 | $L>0$ 时 $\lambda=\max(0, 1 - 1/(\alpha L))$;$L=0$ 时取 0 |
areal.trainer.ppo.lambda_fn.relative_position_gae_lambda | 0 < q <= 1 | $L\ge2$ 时 $\lambda=q^{1/(L-1)}$;$L=1$ 时取 1,$L=0$ 时取 0。相对保留率解释假设 $\gamma=1$ |
其中vapo_length_adaptive_gae实现了 VAPO 论文的长度自适应 lambda,其timestep_lengths由gae_timestep_unit决定:token 模式下为有效生成 token 数,turn 模式下为有效 turn 数;空轨迹与过短轨迹取 lambda 0(源码位置)。relative_position_gae_lambda则保证在 $\gamma=1$ 时,终端结果奖励在等相对位置上保留相同的比例——第 1 个时间步恰好保留 $q$,最后一个时间步保留 1(源码位置)。
配置示例:
actor: gae_timestep_unit: turn gae_lambda: areal.trainer.ppo.lambda_fn.relative_position_gae_lambda gae_lambda_kwargs: q: 0.5自定义 workflow 的turn_ids约定
Turn 级 GAE 要求 workflow 返回原始、与 token 对齐的turn_ids;actor 会在内部将其与 next-token prediction mask 对齐。该 tensor 必须满足:
- 与
input_ids和loss_mask形状相同(batch 后为[B, S]); - 使用整数 dtype(建议使用有符号整数来表示
-1哨兵值); - 为每个有效生成 token 分配
[0, S)范围内的 ID; - 有效 ID 随时间单调不减,同一个 assistant turn 中的所有 token 使用相同 ID;
- prompt、user、tool、padding 及其他非 loss 位置使用
-1。
编号允许跳跃且不会额外消耗 GAE 时间步,但建议从 0 开始连续编号。自定义 workflow 可以按如下方式构造该字段;不要在 workflow 中进行 roll:
turn_ids += [-1] * input_len + [turn_idx] * resp.output_len result["turn_ids"] = torch.tensor(turn_ids, dtype=torch.int32).unsqueeze(0)AReaL 实现说明
AReaL 的 GRPO 实现在两个关键方面与原始 DeepSeekMath 论文不同:
长度归一化:AReaL 从原始 GRPO 目标中移除了逐 token 长度归一化项。这与 Dr.GRPO 的建议一致,并消除了优势估计中的偏差。
KL 正则化:AReaL 不是将 KL 散度项直接添加到目标函数中,而是通过actor.kl_ctl将 KL 正则化纳入 actor advantage(PPO 风格)。在 token 模式下,KLEstimator计算的惩罚会在 GAE 之前加入逐 token 奖励;在 turn 模式下,它仅作为 token 局部的 actor 惩罚,不进入 turn 递推和 critic target。源码中kl_ctl默认值为0.1,kl_estimator可选k1/k2/k3(源码位置),而 examples/math/gsm8k_grpo.yaml 中的默认 GRPO 配置将kl_ctl设为0.0。
总结与延伸阅读
本文完整覆盖了 AReaL 中 PPO/GRPO 算法族的配置与原理:统一的gsm8k_rl.py执行入口、PPOActorConfig/NormConfig核心参数、算法配置矩阵、各算法的公式与 YAML 配置,以及 GAE 时间步单位、动态 GAE lambda 和turn_ids约定等进阶话题。相关测试用例(如tests/test_ppo_gae.py、tests/test_cispo_loss.py、tests/test_ppo_actor_truncation.py)可以作为验证这些行为的参考;更多 CLI 参数细节可查阅 CLI 配置参考。若想了解 RL 训练流程的整体架构,可继续阅读 RL 训练器文档 下的相关章节。
【免费下载链接】AReaLThe RL Bridge for LLM-based Agent Applications. Made Simple & Flexible.项目地址: https://gitcode.com/GitHub_Trending/are/AReaL
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考