GRPO 与 DAPO:从组内相对优势到长 CoT 强化学习
从原始 GRPO 的组内优势、PPO-Clip 与 KL 出发,分析长 CoT 强化学习中的四类问题,以及 DAPO 如何逐项处理这些问题
GRPO 与 DAPO:从组内相对优势到长 CoT 强化学习
简介
PPO 训练大语言模型时,通常需要一个 value model,也就是常说的 critic,用它估计每个状态的价值并进一步计算 advantage。模型规模变大以后,critic 会带来额外的参数、显存和计算成本。DeepSeekMath 提出的 GRPO(Group Relative Policy Optimization)保留了 PPO 风格的 clipped policy update,但不再训练 critic,而是对同一个问题采样一组回答,用组内 reward 的相对高低构造 advantage。
这解决了 critic 成本,却没有自动解决长 CoT 强化学习中的所有问题。DAPO 论文报告,在长推理训练中还会遇到探索受限、无效采样、长序列 token 信号稀释和截断 reward noise。DAPO 没有推翻 GRPO 的组内比较思路,而是分别修改 clipping、sampling、loss reduction 和 overlong reward。
本文沿着下面这条主线展开:
GRPO 用组内相对 reward 替代 critic
↓
长 CoT 训练暴露探索、采样、长度加权和截断问题
↓
DAPO 用四项改动逐一处理这些 failure modes
为了避免把论文和代码混为一谈,本文会明确区分三个层次:
- 原始 GRPO:以 DeepSeekMath 论文为定义来源;
- DAPO paper:以 DAPO 论文的公式、实验和结论为准;
- verl 实现:先固定 DAPO 论文对应的
4f80e465...commit,再单独说明当前main的后续工程演化。
文中的 PyTorch 代码是为了说明公式与数据流的教学实现,当前环境没有安装 PyTorch,因此这些代码只经过静态核查、没有直接执行。我另外使用等价的 NumPy 小张量计算检查了 shape、mask、loss reduction 和边界行为,但没有进行完整模型训练;这些检查不代表对论文训练结果的复现。
理解 GRPO 所需的最小 PPO 背景
Policy、old policy 与 reference policy
GRPO 公式中会同时出现三个策略,先把它们分清楚:
pi_theta:当前正在训练的 policy;pi_old:生成这批 rollout 时使用的旧 policy,用来计算 importance ratio;pi_ref:reference policy,只在需要约束 KL 时使用。
对于第 i 条 response 的第 t 个 token,DeepSeekMath 和 DAPO 使用逐 token importance ratio:
r_{i,t}(theta)
= pi_theta(o_{i,t} | q, o_{i,<t})
/ pi_old(o_{i,t} | q, o_{i,<t})
它衡量同一个已采样 token 在新旧 policy 下的概率变化。分母是 pi_old,不是 pi_ref。在代码中一般用 log probability 计算:
ratio = torch.exp(log_prob - old_log_prob)
Clipping 在限制什么
PPO-Clip 使用下面的 surrogate:
min(
r_{i,t} * A_{i,t},
clip(r_{i,t}, 1 - epsilon_low, 1 + epsilon_high) * A_{i,t}
)
当 advantage 为正时,训练希望提高这个 token 的概率,主要由上界 1 + epsilon_high 限制;当 advantage 为负时,训练希望降低它的概率,主要由下界 1 - epsilon_low 限制。这里不是简单地永远把 ratio 替换成 clipped ratio,而是在 unclipped 和 clipped surrogate 中选择更保守的一项。
PPO 通常借助 critic 估计 advantage。GRPO 真正替换的是这一部分:policy update 仍然保留 importance ratio 和 clipping,但 advantage 改由同一问题下的一组回答相互比较得到。
原始 GRPO:用组内比较替代 critic
统一数据布局:[B, G, T]
后面的公式和教学代码统一使用下面的逻辑布局:
B:一个 batch 中不同 prompt 的数量;G:每个 prompt 采样的 response 数量;T:padding 后的最大 response token 数。
主要 tensor 为:
rewards: [B, G] # 每条 response 的 outcome reward
log_prob: [B, G, T] # 当前 policy 对已采样 token 的 log probability
old_log_prob: [B, G, T] # old policy 的 log probability
ref_log_prob: [B, G, T] # reference policy 的 log probability
response_mask: [B, G, T] # 有效 response token 为 1,padding 为 0
advantages: [B, G, T]
生产框架可能把前两维展平成 [B * G, T] 以便分发计算,但这只是 storage layout 的变化。组内统计仍然必须知道哪些 G 条 response 来自同一个 prompt。
response_mask 很重要。不同 response 长度不同,短 response 会被 padding 到 T;padding token 不应进入 KL、policy loss 或 token 数分母。后面所有 reduction 都显式乘 mask,而不是假设每条 response 都有相同长度。
Group sampling 与相对 advantage
对每个 prompt q_b,从 old policy 采样 G 个回答,并得到 outcome reward R_{b,g}。组均值和标准差记为:
mu_b = (1 / G) * sum_g R_{b,g}
A_hat_{b,g}
= (R_{b,g} - mu_b) / sigma_b
这是 response-level advantage:同一条 response 内的所有有效 token 共享同一个标量 A_hat_{b,g}。广播到 token 维并应用 mask 后:
A_hat_{b,g,t} = A_hat_{b,g} * response_mask_{b,g,t}
需要特别注意:DeepSeekMath、DeepSeek-R1 和 DAPO 论文都写了“除以组内 reward 标准差”,但没有规定使用 G 还是 G - 1 作为分母,也没有给出数值稳定 epsilon。所以下面的代码显式暴露 correction,不能把这个实现选择反推成论文定义。
import torch
def group_outcome_advantage(
rewards: torch.Tensor,
response_mask: torch.Tensor,
eps: float = 1e-6,
correction: int = 1,
) -> torch.Tensor:
"""教学实现:把 [B, G] outcome reward 广播为 [B, G, T] advantage。"""
if rewards.ndim != 2 or response_mask.ndim != 3:
raise ValueError("rewards 应为 [B, G],response_mask 应为 [B, G, T]")
if rewards.shape != response_mask.shape[:2]:
raise ValueError("rewards 与 response_mask 的 [B, G] 必须一致")
group_size = rewards.shape[1]
if group_size <= correction:
raise ValueError("group size 必须大于 std correction")
group_mean = rewards.mean(dim=1, keepdim=True) # [B, 1]
group_std = rewards.std(
dim=1,
keepdim=True,
correction=correction,
) # [B, 1]
scalar_adv = (rewards - group_mean) / (group_std + eps) # [B, G]
return scalar_adv.unsqueeze(-1) * response_mask # [B, G, T]
DAPO 论文对应的 verl commit 在 GRPO outcome advantage 中使用 torch.std 和 1e-6;按对应 PyTorch API 的默认行为,这相当于 correction=1。这是固定版本的官方实现细节,不是 DeepSeekMath 论文对标准差的精确定义。
如果一个二值 reward 组全对或全错,那么组内所有 reward 相同,分子 R - mu 本身就是零。即使分母加了 epsilon,得到的 advantage 仍然全为零。这会成为 DAPO Dynamic Sampling 要处理的问题。
图:GRPO / DAPO 在 [B,G,T] 张量布局下的数据流。response-level reward 在组内归一化得到 advantage,并广播到 token 维度;在本图关注的 loss aggregation 路径中,GRPO-style reduction 先按 response 聚合,而 DAPO 使用 token-level reduction。
原始 GRPO policy objective
DeepSeekMath 的原始 GRPO objective 可以写成:
J_GRPO(theta) = E[
(1 / G) * sum_i (
(1 / |o_i|) * sum_t (
min(
r_{i,t} * A_hat_{i,t},
clip(r_{i,t}, 1-epsilon, 1+epsilon) * A_hat_{i,t}
)
- beta * D_KL(i,t)
)
)
]
它的 reduction 顺序是:
- 在每条 response 内,对有效 token 求 mean;
- 再对组内
G条 response 求 mean。
因此每条 response 的总权重相同。长 response 包含更多 token,所以其中单个 token 分到的权重更小。本文把这种 reduction 称为 sequence-balanced 或 sample-level reduction,不要和“sequence-level importance ratio”混为一谈。
KL 在原始 GRPO 中的位置
DeepSeekMath 先在 PPO 背景中介绍过把 KL penalty 加入 reward 的做法,但其 GRPO 公式把 KL 直接放进 policy objective。论文使用的 sampled KL estimator 为:
x_{i,t} = log pi_ref(o_{i,t}) - log pi_theta(o_{i,t})
D_KL(i,t) = exp(x_{i,t}) - x_{i,t} - 1
所以原始 GRPO 仍然需要 reference policy。pi_ref 用于 KL,而 pi_old 用于 importance ratio,两者职责不同。
下面把 clipped surrogate、可选的 KL 和 response mask 放在同一个教学函数中。由于训练通常最小化 loss,启用 KL 时,代码使用 -surrogate + beta * KL,与最大化 surrogate - beta * KL 对应;beta = 0 时则不需要 reference policy。
def clipped_policy_loss(
log_prob: torch.Tensor,
old_log_prob: torch.Tensor,
advantages: torch.Tensor,
response_mask: torch.Tensor,
ref_log_prob: torch.Tensor | None = None,
clip_low: float = 0.2,
clip_high: float = 0.2,
beta: float = 0.0,
reduction: str = "sequence-balanced",
) -> torch.Tensor:
"""教学实现:输入均为 [B, G, T],不包含生产级分布式优化。"""
tensors = [log_prob, old_log_prob, advantages, response_mask]
if any(x.ndim != 3 for x in tensors):
raise ValueError("所有输入都应使用 [B, G, T]")
if any(x.shape != log_prob.shape for x in tensors[1:]):
raise ValueError("所有输入 shape 必须一致")
if ref_log_prob is not None and ref_log_prob.shape != log_prob.shape:
raise ValueError("ref_log_prob 必须使用与 log_prob 相同的 [B, G, T] shape")
valid = response_mask.bool()
mask = response_mask.to(log_prob.dtype)
# padding 位置先置零,避免无效 token 的极端 log-prob 进入 exp。
log_ratio = torch.where(
valid,
log_prob - old_log_prob,
torch.zeros_like(log_prob),
)
ratio = torch.exp(log_ratio)
clipped_ratio = torch.clamp(ratio, 1.0 - clip_low, 1.0 + clip_high)
surrogate_1 = ratio * advantages
surrogate_2 = clipped_ratio * advantages
clipped_surrogate = torch.minimum(surrogate_1, surrogate_2)
token_loss = -clipped_surrogate # [B, G, T]
if beta != 0.0:
if ref_log_prob is None:
raise ValueError("beta != 0 时必须提供 ref_log_prob")
log_ref_over_policy = torch.where(
valid,
ref_log_prob - log_prob,
torch.zeros_like(log_prob),
)
sampled_kl = (
torch.exp(log_ref_over_policy) - log_ref_over_policy - 1.0
)
token_loss = token_loss + beta * sampled_kl
token_loss = torch.where(valid, token_loss, torch.zeros_like(token_loss))
if reduction == "token-mean":
return (token_loss * mask).sum() / mask.sum().clamp_min(1.0)
if reduction == "sequence-balanced":
token_count = mask.sum(dim=-1) # [B, G]
valid_sequence = token_count > 0
per_sequence = (token_loss * mask).sum(dim=-1) / token_count.clamp_min(1.0)
return (
per_sequence * valid_sequence
).sum() / valid_sequence.sum().clamp_min(1)
raise ValueError(f"未知 reduction: {reduction}")
原始 DeepSeekMath GRPO 的调用需要 reference log-prob。下面的 beta = 0.04 是论文实验配置,不是算法固定常数:
grpo_loss = clipped_policy_loss(
log_prob=log_prob,
old_log_prob=old_log_prob,
advantages=advantages,
response_mask=response_mask,
ref_log_prob=ref_log_prob,
beta=0.04,
)
这段代码刻意没有加入当前 verl 的 log-ratio clamp、distributed global denominator、rollout correction 和 Dual-Clip,因为它的目标是让数学公式与数据流一一对应,而不是模拟当前框架的全部工程路径。
GRPO 到底解决了什么
到这里,GRPO 的核心可以压缩为两点:
- 用组内 reward 的相对高低构造 advantage,不再训练 critic;
- 保留 old policy ratio、clipping,以及原始定义中的 objective KL。
但“去掉 critic”不等于“长 CoT RL 已经稳定”。当回答从较短的数学解答扩展到上万 token 的 reasoning trajectory,采样分布、有效 batch、长度加权和截断处理都会变成更显著的问题。
长 CoT 训练暴露的四类问题
下面四类 failure mode 来自 DAPO 论文对长 CoT 数学 RL 的分析与实验观察。它们不是 GRPO 在所有模型、任务和 reward 下都必然出现的定理。
问题一:对称 clipping 限制探索
普通 PPO-Clip 常使用对称区间,例如 [1 - epsilon, 1 + epsilon]。对于正 advantage token,ratio 达到上界后,继续提高 token 概率不再增加 surrogate。DAPO 论文认为,这会限制低概率 token 的提升空间,使模型更难探索新的 reasoning path,并可能伴随 entropy collapse。
问题二:全对或全错组的 advantage 为零
考虑二值 correctness reward。当同一个 prompt 的 G 条 response 全对时,reward 全为 +1;全错时,reward 全为 -1。两种情况的组内方差都是零,减去组均值后 advantage 全为零。
这些样本的组内 advantage 为零,因此不会贡献 clipped policy-gradient surrogate。在 DAPO 所采用的无 KL 训练配置中,这意味着对应 prompt 不再提供 actor 更新信号;而在原始 DeepSeekMath GRPO 中,objective 还包含 KL 正则项,因此不能笼统地说整个 actor gradient 必然为零。
于是对于 DAPO 这类无 KL 的训练配置,nominal batch size 看起来没变,真正提供有效 policy-gradient 信号的 prompt 数量却可能下降。
问题三:sample-level mean 稀释长序列 token 信号
原始 GRPO 的 sequence-balanced reduction 为:
(1 / G) * sum_g [
(1 / length_g) * sum_t loss_{g,t}
]
每条 response 总权重相同。response 越长,单个 token 的权重越小。DAPO 论文认为,这会让长 CoT 中具体 reasoning pattern 的奖励和惩罚被稀释。
问题四:硬截断制造 reward noise
长 CoT 可能在最大生成长度处被截断。一个被截断的回答可能已经包含大量有效推理,只是还没有产生最终答案。如果直接把它和普通错误答案一样施加强负 reward,reward 会同时混入“推理质量”和“是否在长度上限前完成”两种因素。
四个问题与 DAPO 改进一一对应:
| Long-CoT failure mode | DAPO 改进 |
|---|---|
| 正 advantage token 的概率提升过早受限 | Clip-Higher |
| 全对或全错组产生零 advantage | Dynamic Sampling |
| 长 response 的单 token 信号被稀释 | Token-Level Policy Gradient Loss |
| 硬截断造成 reward noise | Overlong Reward Shaping |
DAPO:逐项处理长 CoT failure modes
Clip-Higher:放宽正向更新的上界
DAPO 使用非对称 clip:
epsilon_low = 0.20
epsilon_high = 0.28
clip range = [1 - epsilon_low, 1 + epsilon_high]
对正 advantage,真正限制概率提升的是 1 + epsilon_high。将上界从 1.20 放宽到 1.28,允许有价值但原本概率较低的 token 获得更大的单次提升空间。论文没有同步放宽 lower clip;论文给出的解释是,过度放宽下界可能进一步压低负 advantage token 的概率并收缩 sampling space。
这里的 0.20/0.28 是 DAPO 论文的具体实验配置,不应写成其他模型和任务上的普适最优参数。
Dynamic Sampling:只训练有组内差异的 prompt
对论文中的二值 correctness reward,Dynamic Sampling 的精确保留条件是:
0 < number_of_correct_responses < G
也就是同时过滤全对组和全错组。当过滤后有效 prompt 不足时,trainer 会继续生成新的 prompt group;固定论文复现 recipe 最多允许生成 10 个 generation batch。如果达到该上限仍未补足目标 batch,trainer 会直接抛出 ValueError,而不是使用不足的 effective batch 继续训练。
下面是使用 [B, G] correctness matrix 的教学实现:
def dynamic_sampling_mask(correct: torch.Tensor) -> torch.Tensor:
"""correct: [B, G] bool tensor;返回 [B],True 表示保留该 prompt。"""
if correct.ndim != 2 or correct.dtype != torch.bool:
raise ValueError("correct 应为 [B, G] bool tensor")
correct_count = correct.sum(dim=1) # [B]
group_size = correct.shape[1]
return (correct_count > 0) & (correct_count < group_size)
DAPO 论文对应的 verl trainer 实际检查所选 metric 的组内 np.std > 0。当 metric 是二值 accuracy 时,这与上面的条件等价;如果换成连续 reward,实现语义就变成“保留 reward 非零方差组”,不能继续表述为“既有正确回答也有错误回答”。
Dynamic Sampling 提高的是 rollout 的有效性,不是让全对或全错样本突然产生新的 advantage。代价是可能需要额外生成候选组;固定 recipe 把补采样失败显式暴露为报错,而不是静默缩小 batch。
Token-Level Policy Gradient Loss:让每个有效 token 权重相同
DAPO 将原始 GRPO 的 per-sequence token mean 改为所有有效 response token 的全局 mean:
J_DAPO(theta) = E[
1 / sum_{i,t} mask_{i,t}
* sum_{i,t} mask_{i,t}
* min(
r_{i,t} * A_hat_{i,t},
clip(r_{i,t}, 1-epsilon_low, 1+epsilon_high) * A_hat_{i,t}
)
]
在前面的 clipped_policy_loss 教学实现里,这就是:
loss = clipped_policy_loss(
log_prob=log_prob,
old_log_prob=old_log_prob,
advantages=advantages,
response_mask=response_mask,
ref_log_prob=None,
clip_low=0.20,
clip_high=0.28,
beta=0.0,
reduction="token-mean",
)
注意,DAPO 修改的是 loss reduction,不是把 importance ratio 从 sequence-level 改成 token-level。DeepSeekMath 原始 GRPO 本来就使用 token-level ratio。
两种 reduction 的差异可以直接从 mask 看出来:
sequence-balanced:
每条有效 response 先除以自己的 token_count,再平均 response
token-mean:
所有 token_loss 求和,再除以整个 batch 的有效 token 总数
Token-Level Loss 让每个有效 token 权重相同,因此长 response 会因为包含更多 token 而获得更大的总权重。这是它改善长 CoT 信号的机制,也是需要意识到的 trade-off。
Overlong Reward Shaping:在硬截断前加入软惩罚
DAPO 论文使用一个长度 buffer。在进入 buffer 前不惩罚;进入 buffer 后,长度 reward 从 0 线性下降;超过最大长度时为 -1:
R_length(y) =
0,
if |y| <= L_max - L_cache
((L_max - L_cache) - |y|) / L_cache,
if L_max - L_cache < |y| <= L_max
-1,
if |y| > L_max
论文实验设置为 L_max = 20480、L_cache = 4096,因此前 16384 个 token 没有长度惩罚,随后在 4096 个 token 的 buffer 内线性下降,到 20480 时为 -1。这个 length reward 会加到 correctness reward 上。
def overlong_reward(
response_length: torch.Tensor,
max_length: int,
cache_length: int,
) -> torch.Tensor:
"""教学实现:response_length 为 [B, G],返回同 shape 的长度 reward。"""
if cache_length <= 0 or cache_length > max_length:
raise ValueError("cache_length 必须位于 (0, max_length] 内")
length = response_length.to(torch.float32)
expected_length = max_length - cache_length
soft_penalty = (expected_length - length) / cache_length
reward = torch.where(
length <= expected_length,
torch.zeros_like(length),
soft_penalty,
)
return torch.where(length > max_length, -torch.ones_like(length), reward)
论文对应的 verl reward manager 还提供可配置 penalty factor,DAPO recipe 使用 1.0。正常生成通常在 L_max 截断,因此 |y| > L_max 分支在该 recipe 的常规执行路径中通常不可达,但它仍是论文分段公式的一部分。
DAPO 为什么移除 KL
DAPO 论文的 Eq. 8 不包含 KL,论文对应 recipe 也同时配置:
use_kl_in_reward = False
kl_coef = 0
use_kl_loss = False
kl_loss_coef = 0
因此可以准确地说:DAPO 论文目标和论文对应实验配置移除了 KL。但不能进一步推广成“任何称为 DAPO 的训练都必须关闭 KL”。verl 是可配置训练框架,用户可以组合其他 KL 设置;那会成为新的训练变体,而不是 DAPO 论文实验的原样复现。
从 DAPO 论文映射到官方 verl
先固定论文对应版本
DAPO 官方 recipe 将论文复现指向 verl commit:
4f80e465c2ec79ab9c3c30ec74b9745de61d0490
论文概念与该版本代码的主要映射为:
| 论文概念 | 固定 commit 中的实现位置 |
|---|---|
| GRPO outcome advantage | verl/trainer/ppo/core_algos.py |
| asymmetric clipping 与 policy loss | verl/trainer/ppo/core_algos.py |
| Dynamic Sampling | recipe/dapo/src/dapo_ray_trainer.py |
| Overlong Reward Shaping | verl/workers/reward_manager/dapo.py |
| 论文实验配置 | recipe/dapo/run_dapo_qwen2.5_32b.sh |
生产代码通常把逻辑上的 [B, G, T] 展平成 batch 维,再通过 prompt uid 恢复 group。本文教学代码保留 [B, G, T],是为了让 group normalization、response mask 和两种 loss reduction 更直观,不代表官方代码使用相同的物理 layout。
论文公式和 recipe 并不完全相同
即使固定到论文对应 commit,也不能认为 recipe 只是把 DAPO Eq. 8 原封不动翻译成代码:
- Dual-Clip:recipe 设置了
clip_ratio_c=10.0,代码会对负 advantage 额外应用 Dual-Clip;DAPO Eq. 8 没有写出这项,它也不是论文列出的四项核心改进之一。 - Warmup:论文写的是
20个 rollout steps,固定 commit 的 actor optimizer script 参数为10steps。两者不能悄悄合并成同一个事实。 - Mini-batch 口径:论文使用 trajectory 口径描述 mini-batch,script 的
ppo_mini_batch_size=32使用 prompt 口径。没有额外运行证据时,不应直接判定两者相等或冲突。
因此,公式章节解释的是 DAPO paper objective,代码章节解释的是 固定 commit 的官方 recipe 行为。
当前 verl main 是后续工程实现
截至本文核查时,当前 verl main 固定在:
a35908ca3c9632859c58d6a2855d858918ae21dc
相对于论文复现 commit,当前实现已经继续演化:
grpo是一个可配置的 outcome advantage estimator,不是原始 GRPO 完整训练配置的别名;- reward-KL 和 actor loss-KL 是两条独立配置路径;
- loss aggregation 支持
token-mean、seq-mean-token-mean等多种模式; - GRPO advantage 可以选择不除组内 std;
- policy ratio 对 log-ratio 增加了数值稳定 clamp;
- 当前
token-mean结合 global token count 与 data-parallel scaling,减少不同 micro-batch 和并行切分造成的 loss scale 差异; - 框架还加入了 rollout correction 等论文之后的功能。
所以“当前 verl 中设置 adv_estimator=grpo”只能说明 advantage estimator 的选择,不能单独推出 KL、clip、loss reduction 或 Dynamic Sampling 的完整行为。讨论代码时必须同时给出 commit 和配置。
DAPO 论文实验能说明什么
下面只记录 DAPO 论文及其官方工件报告的结果,不是我的本地实验。
论文报告的实验设置
论文实验以 Qwen2.5-32B Base 为 base model,使用论文所称的 DAPO-Math-17K 数据。每个 rollout step 使用 512 个 prompt,每个 prompt 生成 16 条 response,共 8192 条 trajectory;optimizer 为 AdamW,constant learning rate 为 1e-6,最大 response 长度为 20480。
论文在 AIME 2024 上评估,generation temperature 为 1.0、top-p 为 0.7、最大长度为 20480。官方 evaluation artifact 将题目重复 32 次,每个重复项采样一次,然后对 correctness 求平均,因此 avg@32 可以理解为每题 32 次采样的平均正确率。它不是 pass@32、best-of-32,也不是 majority voting 的 cons@32。
论文报告的累计结果
DAPO 论文 Table 1 报告了下面的 AIME 2024 avg@32 累计结果:
| 累计配置 | 论文报告结果 |
|---|---|
| R1-Zero-Qwen-32B | 47 |
| Naive GRPO | 30 |
| + Overlong Filtering | 36 |
| + Clip-Higher | 38 |
| + Soft Overlong Punishment | 41 |
| + Token-Level Policy Gradient Loss | 42 |
| + Dynamic Sampling | 50 |
这些行是逐步叠加组件后的结果,不能把相邻两行的差值直接当作某个组件在严格独立控制变量实验中的固定收益。50 是论文报告结果,不是本文复现实验结果。
论文还报告,DAPO 达到该结果使用的 training steps 比 R1-Zero-Qwen-32B 少 50%。论文没有给出足以无歧义引用的 DAPO 精确总 step,所以不能根据对照训练的 step 数自行推算并写成确定数字。
此外,当前 Hugging Face 上名为 DAPO-Math-17K 的 artifact 行数与论文中的 17K 描述不一致,官方 issue 尚未给出明确解释。本文因此只把 17K 作为论文实验描述,不把当前数据页面的行数反推为论文实际训练集规模。
常见误解
| 误解 | 更准确的说法 |
|---|---|
| GRPO 的 ratio 是 sequence-level | DeepSeekMath 和 DAPO 公式使用 token-level ratio;DeepSeek-R1 报告使用了完整 response probability 的记号,但训练代码未公开,不能据此反推实际实现 |
| DAPO 的创新是去掉 critic | 去掉 critic 是 GRPO 的核心;DAPO 处理的是长 CoT 下的四类训练问题 |
| DAPO 必须没有 KL | DAPO 论文和对应 recipe 关闭 KL,不代表所有后续变体必须如此 |
| Token-Level Loss 只是换一种写法 | 它改变不同长度 response 的相对总权重 |
当前 verl 的 grpo 就是 DeepSeekMath 原始定义 | 当前 verl 是可配置框架,需要同时检查 KL、clip、reduction 和 sampling 配置 |
| DAPO Eq. 8 等于 recipe 的完整 loss | 固定 commit 的 recipe 还使用了论文 Eq. 8 未写出的 Dual-Clip |
总结
GRPO 解决的问题很明确:对同一个 prompt 采样一组回答,用组内相对 reward 估计 advantage,从而不再训练 critic;policy update 仍然保留逐 token importance ratio 和 PPO-style clipping,原始 DeepSeekMath 定义还在 objective 中加入 reference-policy KL。
当 response 扩展成长 CoT 后,DAPO 论文观察到四类新的训练问题:对称 upper clip 限制探索、全对或全错组的 advantage 为零、sequence-balanced reduction 稀释长序列中的 token 信号、硬截断产生 reward noise。DAPO 分别用 Clip-Higher、Dynamic Sampling、Token-Level Policy Gradient Loss 和 Overlong Reward Shaping 处理它们,并在论文目标及对应实验 recipe 中移除了 KL。
最后,论文定义与训练框架必须保持边界:DeepSeekMath 定义原始 GRPO,DAPO paper 定义四项改进,固定 commit 说明官方 recipe 的实际行为,而当前 verl main 只是继续演化后的可配置实现。只有把公式、版本、配置和 tensor reduction 同时写清楚,代码里的“GRPO”或“DAPO”才有确定含义。
参考资料
- DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models
- DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning
- DAPO: An Open-Source LLM Reinforcement Learning System at Scale
- DeepSeekMath 官方仓库
- DeepSeek-R1 官方仓库
- DAPO 官方仓库
- DAPO 官方 verl recipe
- DAPO 论文对应 verl commit
4f80e465... - 本文核查的 verl main commit
a35908ca... - DAPO-Qwen-32B 官方模型卡
- DAPO-Math-17K 官方数据集页面
- DAPO 数据集规模疑问 issue #36