算法:PPO / GRPO / DAPO 及变体
9.1 verl 算法谱系
verl 把所有 PPO 家族算法通过 不同的 algorithm.adv_estimator + 不同的 loss 实现。examples/ 下每个 trainer 都对应一种算法:
| examples 目录 | 算法 | 关键 override |
|---|---|---|
ppo_trainer/ | 经典 PPO | adv_estimator=gae |
grpo_trainer/ | GRPO(DeepSeek-R1 同款) | adv_estimator=grpo |
dppo_trainer/ | Decoupled PPO | adv_estimator=gae + decouple |
gdpo_trainer/ | GDPO | — |
gmpo_trainer/ | GMPO | — |
gspo_trainer/ | GSPO (group sequence PO) | — |
gpg_trainer/ | Group Policy Gradient | — |
reinforce_plus_plus_trainer/ | REINFORCE++ | adv_estimator=reinforce_plus_plus |
rloo_trainer/ | RLOO(leave-one-out) | adv_estimator=rloo |
remax_trainer/ | ReMax | — |
cispo_trainer/ | CISPO | — |
sapo_trainer/ | SAPO | — |
otb_trainer/ | OTB | — |
mtp_trainer/ | Multi-Token Prediction | — |
看着多,本质都是 PPO 的变体,区别在 advantage 估计、KL 项、ratio 计算这三件事上。
9.2 PPO(最经典)
$$ \mathcal{L}_\text{PPO}(\theta) = -\mathbb{E}_t\!\left[\min\!\big(\rho_t \hat A_t,\;\mathrm{clip}(\rho_t,\,1{-}\epsilon,\,1{+}\epsilon)\hat A_t\big)\right] - c_v \mathcal{L}_v + c_e \mathcal{H}[\pi_\theta] $$
- $\rho_t = \pi_\theta(a_t|s_t) / \pi_{\theta_\text{old}}(a_t|s_t)$ —— importance ratio;
- $\hat A_t$ —— 由 critic 给的 GAE advantage;
- $\epsilon$ —— clip 阈值,对应
actor.clip_ratio,默认 0.2。
verl 中 PPO 启动:
algorithm.adv_estimator=gae \
algorithm.gamma=1.0 \
algorithm.lam=0.95 \
critic.optim.lr=1e-5
9.3 GRPO(DeepSeek-R1 同款)
GRPO 的招牌操作:用同一 prompt 采样 $G$ 个 response,组内归一化做 advantage。不再需要 critic。
$$ \hat A_i = \frac{r_i - \mathrm{mean}(\{r_j\}_{j=1}^G)}{\mathrm{std}(\{r_j\}_{j=1}^G)} $$
$$ \mathcal{L}_\text{GRPO}(\theta) = -\mathbb{E}\!\left[\tfrac{1}{G}\sum_{i=1}^G \min\!\big(\rho_i \hat A_i,\;\mathrm{clip}(\rho_i, 1{-}\epsilon, 1{+}\epsilon)\hat A_i\big)\right] - \beta\,\mathrm{KL}(\pi_\theta \| \pi_\text{ref}) $$
关键配置:
algorithm.adv_estimator=grpo
algorithm.norm_adv_by_std_in_grpo=true # 启用 std 归一
algorithm.use_kl_in_reward=false # KL 走 loss,不走 reward
actor_rollout_ref.actor.use_kl_loss=true
actor_rollout_ref.actor.kl_loss_coef=0.001
actor_rollout_ref.rollout.n=5 # 每 prompt 采样 5 个
9.4 DAPO(高效 R1 风格)
DAPO 是 ByteDance 提出的 GRPO 升级版,主要改动:
- 动态采样:动态判断每个 prompt 需要多少 response;
- clip-higher:clip 上下界非对称,缓解 entropy collapse;
- token-level loss:对长 sequence 的每 token 单独算 loss,避免长 response 主导梯度。
verl 通过 GRPO + 特殊参数实现 DAPO 风格:
algorithm.adv_estimator=grpo
algorithm.use_dapo_clip_higher=true
algorithm.clip_ratio_low=0.2
algorithm.clip_ratio_high=0.28
algorithm.use_token_level_loss=true
具体 recipe 在子仓 verl-recipe 的 dapo/ 目录。
9.5 REINFORCE++
简化版无 critic 算法:
$$ \hat A_t = r - \tau \cdot \mathrm{normalize}(r) $$
等同于"reward 归一化 + REINFORCE 梯度"。优点:实现极简、显存最省。缺点:方差大,需要更多样本。
algorithm.adv_estimator=reinforce_plus_plus
9.6 RLOO(Leave-One-Out)
RLOO 给同一 prompt 采 $G$ 个 response,每个 response 的 advantage = 自己的 reward 减其他 $G-1$ 个的平均:
$$ \hat A_i = r_i - \frac{1}{G-1}\sum_{j\neq i} r_j $$
$$\Leftrightarrow\; \hat A_i = \frac{G}{G-1}\big(r_i - \bar r\big)$$
与 GRPO 区别:不做 std 归一。更稳定但 sample efficiency 略低。
algorithm.adv_estimator=rloo
actor_rollout_ref.rollout.n=4
9.7 KL 项的三种放法
| 配置 | 含义 |
|---|---|
algorithm.use_kl_in_reward=true | 把 $-\beta\,\mathrm{KL}$ 加到 reward 上(OpenAI 经典) |
actor.use_kl_loss=true | 把 KL 作为 loss 的额外项 |
| 两个都 false | 不做 KL 约束(仅 clip 限制策略漂移) |
GRPO / DAPO 默认走第二种(KL 进 loss),更稳。
9.8 KL 估计的几种方式
实际计算 $\mathrm{KL}(\pi_\theta \| \pi_\text{ref})$ 在 token level,verl 提供:
algorithm.kl_penalty | 公式 | 特点 |
|---|---|---|
kl | $\log\pi_\theta - \log\pi_\text{ref}$ | 无偏但方差大 |
abs | $|\log\pi_\theta - \log\pi_\text{ref}|$ | 稳定但有偏 |
mse | $\tfrac{1}{2}(\log\pi_\theta - \log\pi_\text{ref})^2$ | 常用 |
low_var_kl | $r - \log r - 1,\; r = \pi_\text{ref}/\pi_\theta$ | 无偏 + 低方差(推荐) |
full | 所有词表上的 KL(不需要采样) | 精确但贵 |
推荐 low_var_kl,这是 John Schulman 博客提出的无偏低方差估计。
9.9 选哪个算法
| 业务诉求 | 推荐 |
|---|---|
| "我有可信打分(编译通过 / 单测 / GSM8K 答案)" | GRPO |
| "我训长思考链 R1 风格" | GRPO + DAPO 改进 |
| "我已有 reward model + 偏好数据" | 经典 PPO |
| "显存极紧 / 不想训 critic" | REINFORCE++ 或 RLOO |
| "想要最 SOTA 的算法" | 看 verl-recipe 的最新 recipe |
| "想做学术对比" | 跑 GRPO / RLOO / REINFORCE++ 各一遍 |
9.10 多种算法在同一 verl 入口下的切换
四种算法的切换:
# PPO
algorithm.adv_estimator=gae
# GRPO
algorithm.adv_estimator=grpo
actor_rollout_ref.rollout.n=8
# RLOO
algorithm.adv_estimator=rloo
actor_rollout_ref.rollout.n=4
# REINFORCE++
algorithm.adv_estimator=reinforce_plus_plus
verl 在 core_algos.py 里把 advantage 估计抽成一个 dispatch 函数,新增算法只要加一个 case。
9.11 完整的 AdvantageEstimator 枚举
verl/trainer/ppo/core_algos.py 的 AdvantageEstimator(line 88–111)现在已经 13 项。逐个简介,配公式:
| estimator | 函数(core_algos.py 行号) | 核心公式 / 特征 | 是否需要 critic |
|---|---|---|---|
gae | compute_gae_advantage_return (216) | $A_t = \sum_l (\gamma\lambda)^l \delta_{t+l}$,标准 GAE | 是 |
grpo | compute_grpo_outcome_advantage (268) | $A_i = (r_i - \mu_\text{group}) / (\sigma_\text{group}+\varepsilon)$,组内归一 | 否 |
grpo_vectorized | 335 | GPU 友好版 grpo(无 Python 循环) | 否 |
gdpo | compute_gdpo_outcome_advantage (362) | 每维 reward 各自归一后加权聚合,再 batch-level whitening | 否 |
grpo_passk | 472 | 只有 group 内最好的一条拿非零 advantage:$r_\max - r_\text{2nd}$ | 否 |
rloo | compute_rloo_outcome_advantage (588) | $A_i = \tfrac{n}{n-1}(r_i - \mu_{-i})$,leave-one-out 无偏估计 | 否 |
rloo_vectorized | — | 同 rloo,向量化版 | 否 |
opo | compute_opo_outcome_advantage (640) | length-normalized advantage(arXiv:2505.23585) | 否 |
reinforce_plus_plus | 694 | $G_t = \sum_{t'\geq t}\gamma^{t'-t} r_{t'}$ + batch-level whitening | 否 |
reinforce_plus_plus_baseline | — | R++ 但减去 group baseline | 否 |
remax | compute_remax_outcome_advantage (733) | cumulative reward - 贪心 baseline 的 cumulative reward | 否(但要并行 greedy rollout) |
gpg | compute_gpg_outcome_advantage (769) | group-relative + f-norm 加权 + α 缩放 | 否 |
optimal_token_baseline | 870 | token-level 最优 baseline(长 horizon) | 否 |
tir_optimal_token_baseline | 989 | 同上但 multi-turn / tool-in-reasoning 版本 | 否 |
注册机制:ADV_ESTIMATOR_REGISTRY(line 113)+ @register_adv_est(name_or_enum) 装饰器(line 116)。新增自定义算法只要写一个函数 + 一行装饰器,不用动主循环。
9.12 完整的 POLICY_LOSS_REGISTRY
同文件的 POLICY_LOSS_REGISTRY(line 50)注册了 11 种 policy loss。这是 ch08.12 那张 YAML 表的算法侧对应:
| 名字 | 函数 (行号) | 核心思路 |
|---|---|---|
vanilla | 1278 | 标准 PPO clip:$\min(rA, \text{clip}(r)A)$,带 dual-clip 下界 |
dppo_tv | 1372 | DPPO + Total Variation 正则 |
dppo_kl | 1453 | DPPO + KL 正则 |
gspo | 1539 | 序列级 IS × token 级 ratio;seq-mean-token-mean 聚合(2025-11) |
sapo | 1614 | $\text{sigmoid}(\tau(r-1))\cdot\tfrac{4}{\tau}$ soft-clip(2025-11,arXiv:2511.20347) |
gpg | 1699 | group PG + 选择性 clip |
clip_cov | 1735 | PPO + 协方差正则 |
kl_cov | 1840 | KL + 协方差跟踪 |
geo_mean | 1920 | $\exp(\text{mean}(\log r))$ 几何均值,数值更稳 |
cispo | 2007 | Clipped IS PPO,rollout IS 直接进 ratio |
bypass_mode | 2351 | off-policy 矫正路径:REINFORCE-style 或 PPO-clip with IS 重加权 |
注册装饰器:@register_policy_loss(name)(line 53);检索:get_policy_loss_fn(name)(line 70)。ch10 会讲怎么自己写一个。
9.13 KL 函数细节
9.4 列了几种 KL 估计名字,对应函数都在 core_algos.py:2126 的 kl_penalty() 里 dispatch:
| 名字 | 公式 | 用途 |
|---|---|---|
"kl" | $\log p - \log q$ | 定义式,有正负 |
"abs" | $|\log p - \log q|$ | 绝对值,单调正 |
"mse" | $(\log p - \log q)^2$ | 平方 |
"low_var_kl" | $\exp(\log q - \log p) - (\log q - \log p) - 1$ | 低方差近似(Schulman),实战默认 |
"full" | full distribution KL(需要完整 logits) | 计算贵但精确 |
9.14 GDPO:多维 reward 的归一
2026-Q1 引入的 GDPO 解决的是多 reward 来源信号互相淹没的问题。比如同时要 reward = 0.5·正确性 + 0.3·简洁性 + 0.2·安全性,三个维度的量纲完全不同。GRPO 直接聚合后归一会让小维度信号被大维度淹掉。
GDPO 的做法(compute_gdpo_outcome_advantage(), line 362):
- 对每一维 reward单独做 group 归一化;
- 按
gdpo_reward_weights加权聚合归一化后的 advantage; - 最后做一次 batch-level whitening 控制整体方差。
YAML 写法:
algorithm:
adv_estimator: gdpo
gdpo_reward_keys: [accuracy, brevity, safety] # 三维 reward 字段名
gdpo_reward_weights: [0.5, 0.3, 0.2]
对应 reward 函数要返回 non_tensor_batch 里有这三个字段而不是只返回 scalar。
9.15 DAPO 的两个具体改进
9.7 简单提了 DAPO 的两个 idea,这里给真实落地字段:
| 字段(algorithm 段) | 含义 |
|---|---|
filter_groups.enabled | 开 group 过滤 |
filter_groups.metric | 过滤准则:"std"(reward 方差太小=无信号)/ "all_correct"(全对/全错 group 不学) |
filter_groups.max_num_gen_batches | 每个 prompt 最多重采样多少次直到能产生有信号 group |
"Clip-Higher" 在 vanilla policy_loss 里通过 clip_ratio_high / clip_ratio_low 两个字段控制(非对称 PPO clip)。GRPO 的 Dr.GRPO 变体只要把 algorithm.norm_adv_by_std_in_grpo: false 即可。
9.16 rollout correction 的三个预设
训练和 rollout 一旦不在同一 forward pass(off-policy),就要校正。看 verl/trainer/config/algorithm.py 的 RolloutCorrectionConfig:
| 预设 | 构成 | 适用 |
|---|---|---|
decoupled_token_is() | 3 策略(actor / old / rollout)+ token-level IS | 常规 off-policy |
decoupled_seq_is() | 3 策略 + sequence-level IS | 长序列,token IS 方差太大 |
bypass_ppo_clip_geo_rs() | 2 策略 + PPO clip + 几何拒绝采样 | rollout / training 偏离巨大时 |
没开 correction 时(默认)算法本质是 on-policy(同步 rollout-training);开了 correction 才能放心做 fully_async / one_step_off_policy。
9.17 这章你需要带走什么
- 知道 verl 算法谱系 ≈ "PPO 加上不同的 advantage / KL / clip 变体";
- GRPO / DAPO 是当前主流,
algorithm.adv_estimator=grpo一键切换; - KL 项有 reward-side 与 loss-side 两种放法,主流走 loss-side;
- KL 估计推荐
low_var_kl; - 选算法的核心是"reward 信号怎么来"——可验证 reward 走 GRPO 系,神经 reward 走 PPO 系;
- 13 个 AdvantageEstimator + 11 个 PolicyLoss + 5 个 KL 估计可以排列组合,但 90% 的项目用
grpo + vanilla + low_var_kl。