AReaL 中的 PPO/GRPO 算法家族:从 GRPO 到 DAPO、SAPO、GSPO 的配置切换与源码原理
【免费下载链接】AReaLThe RL Bridge for LLM-based Agent Applications. Made Simple & Flexible.项目地址: https://gitcode.com/GitHub_Trending/are/AReaL
本文以 docs/en/algorithms/grpo_series.md 为骨架,系统讲解 AReaL(The RL Bridge for LLM-based Agent Applications)中对 PPO 一族强化学习算法的支持:Vanilla PPO、GRPO、Dr.GRPO、LitePPO、RLOO、DAPO、SAPO、GSPO,以及可任意组合的 IcePop / KPop token 掩码策略。读者将掌握这些算法在 AReaL 中「同一份执行代码、仅靠 YAML 参数切换」的使用方法、
NormConfig/PPOActorConfig等核心配置项的语义,以及 GAE 时间步、动态 GAE lambda 与turn_ids协议等源码级实现细节,从而能针对自己的 RL 任务快速实验不同算法组合。
算法家族全景
AReaL 将下列 PPO-like 算法统一收敛到同一个基目标函数上,它们之间的差异主要体现为:优势/奖励的归一化策略(mean_level/std_level/mean_leave1out)、裁剪机制(对称 / 非对称 / 软门控)、重要性采样层级(token 级 / sequence 级)、以及是否使用解耦损失与 token 掩码等。正因为差异被收敛到少数几个配置参数上,在 AReaL 中只需修改 YAML 配置即可在不同算法之间切换:
- Vanilla PPO:使用学习到的价值函数(critic)通过 GAE 估计优势;
- GRPO(DeepSeekMath):去掉 critic,用组内奖励均值/标准差做基线;
- Dr.GRPO:组内做均值归一化、关闭 std 缩放;
- LitePPO:组内均值归一化、batch 级 std 缩放;
- RLOO:leave-one-out 基线;
- DAPO:非对称裁剪 + 动态采样 + 超长惩罚;
- SAPO:用软 sigmoid 门控替代硬裁剪;
- GSPO(Qwen3):sequence 级重要性采样比率;
- IcePop:基于重要性比率的 token 掩码(可与上述任一算法组合);
- KPop:基于双向二值 KL 散度的 token 掩码(可与上述任一算法组合)。
所有算法共享同一套 rollout / trainer 执行管线,对应入口为 examples/math/gsm8k_rl.py。
快速上手:统一的执行模式与配置切换
所有算法使用完全相同的执行方式,官方推荐直接修改配置文件(YAML)中的参数,而不是修改代码:
| 后端 | 命令 |
|---|---|
| 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/下对应的现成配置文件。scheduler.type通过命令行覆盖选择本地多进程、Ray 集群或 Slurm 调度。
通过 CLI 覆盖切换算法
不修改 YAML,直接用命令行参数覆盖也可以实现算法切换:
# 从 GRPO 配置切到 Dr.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 # 从 GRPO 配置切到 GSPO python3 examples/math/gsm8k_rl.py \ --config examples/math/gsm8k_grpo.yaml \ scheduler.type=local \ +actor.importance_sampling_level=sequence # 从 GRPO 配置切到 SAPO 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.use_sapo_loss=true);对已存在的键直接赋值即可。
核心配置参数
所有配置均在 areal/api/cli_args.py 中定义,主要位于PPOActorConfig(约 L1715 起)与NormConfig(约 L46 起)两个 dataclass 中。完整的参数说明可参考 CLI 配置参考。
奖励与优势归一化(actor.reward_norm与actor.adv_norm)
NormConfig控制奖励(reward)与优势(advantage)的归一化方式,核心字段如下:
| 参数 | 类型 | 取值 | 说明 |
|---|---|---|---|
mean_level | str | None | "batch"、"group"、None | 计算均值(居中)的层级 |
std_level | str | None | "batch"、"group"、None | 计算标准差(缩放)的层级 |
mean_leave1out | bool | true、false | 是否使用 leave-one-out 均值(剔除当前样本) |
std_unbiased | bool | true、false | 是否使用无偏标准差计算(默认true) |
eps | float | - | 除标准差时避免除零的小常数(默认1e-5) |
group_size | int | - | 组级归一化时的组大小 |
从 cli_args.py 的源码可见,mean_level与std_level在__post_init__中会校验取值必须为"batch"、"group"或None;同时NormConfig.uses_group_statistics属性判断是否任一字段为group(组内统计),这一属性在后续批量组筛选逻辑中会被复用。
语义上,"batch" 层级在整个全局 batch 上计算均值/标准差,而 "group" 层级在组内计算(例如共享同一 prompt 的多条轨迹)。组边界来源于 rollout 批次的元数据TrajBatchMeta.traj_group_sizes,而非group_size,因此当组内样本被过滤导致各组大小不等时,仍然按 prompt 逐组归一化;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}AReaL 默认实践:默认配置使用std_level: batch做优势归一化。这是 AReaL 团队在多种 RL 应用(从 StarCraft 等游戏 AI 到 LLM 的 RLHF、推理、Agent 场景)中一贯的标准做法。虽然 Dr.GRPO 建议std_level: null以获得潜在的性能提升,AReaL 为保持向后兼容仍保留std_level: batch;想要 Dr.GRPO 风格行为的用户应设置actor.adv_norm.std_level=null。
裁剪策略(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}})$。
在 cli_args.py 中,eps_clip_higher的注释还说明:当设置eps_clip_higher时,eps_clip作为下界值使用。此外源码还暴露了c_clip(dual clipping factor,必须大于 1.0,None表示禁用)这一相关参数。
重要性采样层级(actor.importance_sampling_level)
| 参数 | 类型 | 取值 | 说明 |
|---|---|---|---|
importance_sampling_level | str | "token"、"sequence" | 计算重要性比率的层级 |
"token"(默认):标准的逐 token 重要性比率(GRPO、PPO 等);"sequence"(GSPO):逐 token 比率的 sequence 级几何平均。
算法配置矩阵
下表给出各算法在 AReaL 中对应的推荐参数设置(同一列即为该算法的配置签名):
| 算法 | 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 实现要点」),例如 examples/math/gsm8k_grpo.yaml 实际使用reward_norm: {mean_level: group, std_level: group}与adv_norm: {mean_level: batch, std_level: batch}的组合。
这些配置在examples/math/下均有现成文件可对照,例如 gsm8k_liteppo.yaml 中reward_norm.std_level: batch、gsm8k_rloo.yaml 中adv_norm.mean_leave1out: true、gsm8k_gspo.yaml 中actor.importance_sampling_level: sequence。
算法详解与专属选项
Vanilla PPO
Vanilla PPO 使用学习到的价值函数(critic)通过 GAE 估计优势,其关键配置差异在于需要独立的critic:配置段(包含自己的模型与优化器)。完整配置示例见 examples/math/gsm8k_ppo.yaml。
GRPO(DeepSeekMath)
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)}. $$
与 PPO 相比,GRPO 不需要 critic:同一 prompt 采样出 G 条响应,组内奖励均值/标准差即作为优势基线。
RLOO(REINFORCE Leave-One-Out)
RLOO 通过求取其他采样响应的奖励均值(剔除当前响应)来估计基线:
$$ 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. $$
在 AReaL 中,仅需设置actor.adv_norm.mean_leave1out=true即可实现该 leave-one-out 基线(见 gsm8k_rloo.yaml)。
GSPO(Group Sequence Policy Optimization)
GSPO 在 sequence 级而非 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(sequence 级):
$$ 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) $$
即对逐 token 比率取对数平均后再指数化(几何平均)。配置上只需设置actor.importance_sampling_level: sequence,见 gsm8k_gspo.yaml。
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]$,其中 $g_t = g_t^+$(若 $A_t > 0$),否则 $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(cli_args.py 中use_sapo_loss的注释也明确它与 PPO 裁剪互斥)。
actor: use_sapo_loss: true sapo_tau_pos: 1.0 sapo_tau_neg: 1.05 use_decoupled_loss: false完整示例见 gsm8k_sapo.yaml,其中同时通过recompute_logprob: false、use_decoupled_loss: false显式关闭了解耦损失路径。
DAPO
DAPO 引入非对称裁剪与动态采样(dynamic sampling):当某 prompt 下所有响应全对或全错时,该组样本被排除在训练之外。
$$ 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 | - | 上界裁剪值(设置后启用非对称) |
gsm8k_dapo_dynamic_bs.yaml 中使用eps_clip: 0.2与eps_clip_higher: 0.28构成非对称裁剪区间。
超长惩罚参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
actor.overlong_reward_penalty | bool | false | 对超长响应启用惩罚 |
actor.overlong_tokens | int | - | 视为超长的尾部 token 数量 |
actor.overlong_penalty_factor | float | - | 应用于超长响应的惩罚因子 |
动态采样:
AReaL 通过传入PPOTrainer.train()的dynamic_filter_fn支持动态采样。该函数接收从同一 prompt 采样的分组轨迹,返回布尔值表示是否接受该组进入训练:
trainer.train( workflow=..., dynamic_filter_fn=lambda x: 0 < x["rewards"].mean() < 1 )默认情况下,AReaL 使用固定 batch size + 动态过滤:持续等待直到收集满batch_size个被接受的样本才进行训练。这与部分 DAPO 实现(先收集一整批样本再统一过滤)的动态 batch size 方案不同。控制 batch 大小的参数如下:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
dynamic_bs | bool | false | 启用动态 batch size |
gsm8k_dapo_dynamic_bs.yaml 顶层即设置了dynamic_bs: true。
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})}$($\pi_\theta$ 为当前训练策略,$\pi_{\theta_\text{old}}$ 为 rollout 使用的行为策略)落在可配置区间 $[\alpha, \beta]$ 之外的 token 从损失中掩码掉——比率过低或过高的 token 都被排除。
IcePop 通过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不生效。完整示例见 examples/math/gsm8k_icepop.yaml(其中lower: 0.5、upper: 5.0、level: token、action: mask均与上表一致)。
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 的概率被看作一个 Bernoulli 参数:$\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)。
KPop 通过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),且binary_kl不使用lower;KPop 同样要求actor.use_decoupled_loss=true,否则rejection_sampling不生效。完整示例见 examples/math/gsm8k_kpop.yaml(metric: binary_kl、upper: 2.0)。
核心概念:Rewards、Advantages 与 GAE
Rewards:AReaL 假设基于结果的奖励(outcome-based rewards)。每条轨迹(可能由拼接的 LLM 输入-输出对组成)在sequence 级获得单个标量奖励,而非 token 级。
Advantages:AReaL 为轨迹中每个输出 token 计算逐 token 优势。PPO 将结果奖励视为最后一个 token 的奖励,前面所有 token 的奖励为 0;随后通过广义优势估计(GAE)做标准折扣与 TD-error 反向传播得到每个 token 的优势。在默认的 token 级时间步递归下,当discount=1、gae_lambda=1、critic 值为零且禁用 KL 正则时,最终结果奖励实际上被广播给每个生成 token。
GAE 时间步单位(actor.gae_timestep_unit)
actor.gae_timestep_unit决定 GAE 是在生成 token上推进还是在**生成轮次(turn)**上推进。prompt、工具、padding 及其他被掩码的位置不消耗 GAE 时间步。该参数在 cli_args.py 中定义,取值"token"或"turn"。
Token 级 GAE
token是默认值,保留 AReaL 的原始行为。对连续活跃的生成 token:
$$ \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 结束的轨迹使用零终端 bootstrap,而无 EOS 的被截断轨迹则从最终价值估计处 bootstrap。
Turn 级 GAE
turn将每个非空生成轮次视为一个宏时间步。对轮次 $u$,AReaL 将其任务奖励增量求和为 $r_u^{\mathrm{task}}$,并取该轮次第一个活跃动作 token 位置的 $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}}. $$
任务优势 $A_u^{\mathrm{task}}$ 与 critic 目标 $G_u=A_u^{\mathrm{task}}+V_u$ 被广播给该轮次内的每个活跃 token。token 级 KL 被有意排除在 turn 递归与 critic 目标之外:在可选的actor.adv_norm之前,轮次 $u$ 中 token $j$ 的 actor 优势为 $A_{u,j}=A_u^{\mathrm{task}}+r_{u,j}^{\mathrm{KL}}$。这避免了把一轮的 KL 惩罚求和后再广播回每个 token 的做法。
动态 GAE lambda
actor.gae_lambda既接受静态 float,也接受指向可调用对象的点分路径(dotted path)。actor.gae_lambda_kwargs向该可调用对象传递关键字参数,静态 float 时被忽略。可调用对象接收一个 context,内含三个形状为[B]的 tensor:
effective_token_lengths:活跃生成 token 数(含活跃的 EOS);turn_counts:非空生成轮次数,token 模式下无turn_ids时为 0;timestep_lengths:由gae_timestep_unit选定的长度 $L$。
可调用对象必须为每条本地轨迹返回一个有限的浮点 lambda,即同设备上形状为[B]的 tensor,且该返回值用于该轨迹的所有选定时间步。
AReaL 内置两个长度感知函数(实现在 areal/trainer/ppo/lambda_fn.py):
| 函数路径 | Kwargs | 定义 |
|---|---|---|
areal.trainer.ppo.lambda_fn.vapo_length_adaptive_gae | alpha > 0 | $\lambda=\max(0, 1 - 1/(\alpha L))$($L>0$);$L=0$ 时取 0 |
areal.trainer.ppo.lambda_fn.relative_position_gae_lambda | 0 < q <= 1 | $\lambda=q^{1/(L-1)}$($L\ge2$);$L=1$ 取 1,$L=0$ 取 0。相对保持(relative-retention)解释假设 $\gamma=1$ |
从源码看,vapo_length_adaptive_gae会校验alpha必须为正数,对长度做clamp_min(1.0)后计算并clamp_min(0.0);relative_position_gae_lambda会校验q必须是 $(0,1]$ 内的有限数。二者都由resolve_gae_lambda_fn统一解析:静态 float 走constant_gae_lambda快速路径,字符串则通过import_from_string动态导入。
示例配置:
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 预测掩码对齐。该 tensor 必须满足:
- 与
input_ids和loss_mask形状相同(batch 为[B, S]); - 使用整数 dtype(建议使用有符号整数,因为需要
-1哨兵值); - 给每个活跃生成 token 分配
[0, S)内的 ID; - 保持活跃 ID 在时间上非递减,同一 assistant 轮次内的所有 token 使用同一个 ID;
- 对 prompt、user、tool、padding 及其他非损失位置使用
-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 原始论文相比有两处关键差异:
长度归一化(Length Normalization):AReaL 从原始 GRPO 目标中移除了逐 token 长度归一化项。这与 Dr.GRPO 的建议一致,消除了优势估计中的偏差。
KL 正则化(KL Regularization):AReaL 不直接将 KL 散度项加进目标函数,而是采用 PPO 风格、将 KL 正则并入 actor 优势,由actor.kl_ctl控制(cli_args.py 中默认kl_ctl=0.1,可选k1/k2/k3估计器)。在 token 模式下,KLEstimator惩罚在 GAE 之前被加入逐 token 奖励;在 turn 模式下,它保持为 token 局部的 actor 惩罚,不进入 turn 递归与 critic 目标。
实践建议与延伸阅读
- 以 GRPO 为起点:多数实验可以从 examples/math/gsm8k_grpo.yaml 出发,通过命令行覆盖逐步叠加 Dr.GRPO、GSPO、SAPO 等变体,避免复制粘贴大量重复配置。
- IcePop / KPop 是可组合的:二者本质是 token 掩码策略,可与列表中任一 RL 算法组合使用,但都要求
use_decoupled_loss=true(SAPO 除外,它要求use_decoupled_loss=false)。 - 多轮/Agent 场景:当处理多轮 agent 轨迹时,优先考虑
gae_timestep_unit: turn配合动态 GAE lambda(如relative_position_gae_lambda),并确保自定义 workflow 按上文契约输出turn_ids。 - 源码入口:算法相关配置定义在 areal/api/cli_args.py,GAE lambda 内置函数在 areal/trainer/ppo/lambda_fn.py,GAE 计算与 actor 损失实现可继续阅读 areal/trainer/ppo/gae.py 与 areal/trainer/ppo/actor.py;配置参数全量说明见 CLI 配置参考。
【免费下载链接】AReaLThe RL Bridge for LLM-based Agent Applications. Made Simple & Flexible.项目地址: https://gitcode.com/GitHub_Trending/are/AReaL
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考