DAPO-style 训练:GRPO 主线上的 recipe 组合
DAPO 容易被初学者误解成“一个全新的 trainer”。在当前 verl 主仓库里,更准确的读法是:DAPO-style 训练主要沿用 GRPO/RLVR 主线,然后组合一组 recipe 来解决长 CoT 数学训练里的稳定性问题。
它的名字来自 Decoupled Clip and Dynamic Sampling Policy Optimization。放到源码里看,关键不是某个 compute_dapo_loss(),而是这些组件一起工作:
GRPO advantage + clipped actor loss + 不对称 clip + token-level loss aggregation
+ dynamic sampling / group filtering + overlong reward shapingDAPO 想解决什么
RLVR 训练常见的坏味道有四个:
- 同一题采样全对或全错,GRPO 组内优势没有信息。
- response 越写越长,模型靠拖长推理碰运气。
- PPO clip 太保守或方向不合适,正向学习被限制。
- 长短 response 的 token 数不同,loss 聚合方式带来长度偏置。
DAPO-style recipe 的作用,就是把这些问题拆开处理。
组件一:Clip-Higher / 不对称 clip
PPO 的 ratio 是:
DAPO 文档里的 separated clip epsilons 对应:
verl 源码落点仍是 core_algos.compute_policy_loss_vanilla():
clip_ratio_low = config.clip_ratio_low or config.clip_ratio
clip_ratio_high = config.clip_ratio_high or config.clip_ratio
pg_losses2 = -advantages * clamp(ratio, 1 - clip_ratio_low, 1 + clip_ratio_high)常见 DAPO-style 配置:
actor_rollout_ref.actor.clip_ratio_low=0.2
actor_rollout_ref.actor.clip_ratio_high=0.28直觉是:对提升好样本概率的一侧给更宽空间,对另一侧保持更强约束。源码没有 DAPO 专属 loss;它复用普通 PPO clip 的不对称参数。
组件二:Dynamic Sampling / Group Filtering
GRPO 需要同 prompt 的多条 response 有差异。如果同一组全对或全错:
这一组就几乎没有学习信号。DAPO 的 dynamic sampling 会反复生成,过滤掉“组内指标全一样”的 prompt,直到凑够训练 batch 或达到 max_num_gen_batches。
官方文档中的配置是:
data.gen_batch_size=1536
data.train_batch_size=512
algorithm.filter_groups.enable=True
algorithm.filter_groups.metric=acc
algorithm.filter_groups.max_num_gen_batches=10本地源码里 AlgoConfig 有 filter_groups 字段,并标注用于 DAPO/Entropy。主仓库同步 RayPPOTrainer.fit() 当前可读到的主线包含 GRPO advantage、KL、loss、rollout correction;dynamic sampling 的完整复现实验更多出现在 recipe/fully-async 配置与脚本里,例如 verl/experimental/fully_async_policy/shell/dapo_30b_a3b_base_math_fsdp.sh。
组件三:Token-level loss aggregation
DAPO 文档强调 token-level loss。verl 的具体实现是 core_algos.agg_loss():
token-mean:
sum(loss over all valid tokens) / number_of_valid_tokens
seq-mean-token-sum:
mean over sequences of sum(token losses)
seq-mean-token-mean:
mean over sequences of mean(token losses)
seq-mean-token-sum-norm:
mean sequence token-sum, then divide by fixed horizon/loss_scale_factor这不是纯工程细节。LLM response 长短差很多时,聚合方式会改变“长回答”和“短回答”在梯度中的权重。DAPO/Dr.GRPO-style 经常要和 loss_agg_mode 一起读,不能只看 adv_estimator=grpo。
组件四:Overlong reward shaping
主仓库里 DAPO reward manager 的同步实现是 verl/workers/reward_manager/dapo.py,reward loop 版本在 verl/experimental/reward_loop/reward_manager/dapo.py。它的核心逻辑是:
expected_len = max_resp_len - overlong_buffer.len
exceed_len = valid_response_length - expected_len
overlong_reward = min(-exceed_len / overlong_buffer.len * penalty_factor, 0)
reward = score + overlong_reward
reward_tensor[i, valid_response_length - 1] = reward变量含义:
| 源码变量 | 含义 |
|---|---|
valid_response_length | 这条 response 的有效 token 数 |
max_resp_len | 允许的最大 response 长度 |
overlong_buffer.len | 从多长开始线性惩罚的缓冲区 |
penalty_factor | 走完整个 buffer 时最多扣多少分 |
reward_tensor[..., valid_response_length - 1] | outcome reward 写在最后一个有效 token 上 |
直觉:如果 max_response_length=20480、buffer 为 4096,那么超过 16384 后开始线性扣分,越接近硬上限扣得越多。
源码实现怎么读
读 DAPO-style 不要找单点入口,按下面顺序更稳:
- 先读
core_algos.compute_grpo_outcome_advantage():DAPO-style 大多仍是 GRPO 分组优势。 - 再读
core_algos.compute_policy_loss_vanilla():不对称 clip 在这里生效。 - 再读
core_algos.agg_loss():理解 token-level / sequence-level 聚合。 - 再读
verl/workers/reward_manager/dapo.py:overlong penalty 如何变成最后一个 token 的 reward。 - 最后读
ray_trainer.py的 reward、KL、advantage 顺序:先 reward shaping,再use_kl_in_reward,再 advantage。
在 RayPPOTrainer.fit() 中,DAPO-style 主线仍然是:
reward_tensor -> token_level_scores
if use_kl_in_reward:
token_level_rewards = token_level_scores - beta * KL
else:
token_level_rewards = token_level_scores
compute_advantage(... adv_estimator=grpo ...)
actor.update_actor(...)所以 overlong penalty 会先进入 token_level_scores,再参与后续 KL reward penalty 和 GRPO advantage。
本仓库与 recipe 的边界
当前主仓库能直接读到并复用的通用能力:
- GRPO/Dr.GRPO advantage。
- 不对称 PPO clip。
loss_agg_mode的多种聚合。- DAPO reward manager 的 overlong penalty。
AlgoConfig.filter_groups配置结构。- fully-async / experimental shell 里的 DAPO-style 组合示例。
需要注意的边界:
- DAPO 复现脚本和部分 dynamic sampling 逻辑在 verl-recipe 或
experimental/fully_async_policy路径里更完整。 - MTP、fully-async、one-step-off-policy 是系统/执行路径能力,不等于 DAPO 算法本体。
reward.reward_manager.name=dapo只是 reward shaping 组件,不代表整个训练就自动拥有 DAPO paper 的全部 recipe。
哪些地方不适合初学者硬啃
- 不要把 DAPO 当成和 PPO/GRPO 平级的单个 loss。它更像 GRPO/RLVR 上的一套 recipe。
- 不要只抄
clip_ratio_high=0.28。如果 reward、采样、长度、KL 设置不同,效果可能完全不同。 - 不要混淆 overlong filtering 和 overlong reward shaping。官方 DAPO docs 明确说主实验多用 shaping,本仓库实现也主要是 reward penalty。
- 不要忽略 dynamic sampling 的算力代价。过滤组会让实际生成次数超过一次 train batch。
本节参考与延伸阅读
- verl 源码:
verl/trainer/ppo/core_algos.py的compute_grpo_outcome_advantage()、compute_policy_loss_vanilla()、agg_loss()。 - verl 源码:
verl/trainer/ppo/ray_trainer.py的 reward、KL、advantage、actor update 主线。 - verl 源码:
verl/workers/reward_manager/dapo.py、verl/experimental/reward_loop/reward_manager/dapo.py。 - verl 配置:
verl/trainer/config/algorithm.py的filter_groups、rollout_correction、AlgoConfig。 - verl 官方文档:
docs/algo/dapo.md、docs/algo/grpo.md。 - 示例脚本:
examples/grpo_trainer/run_qwen3_30b_a3b_megatron.sh、examples/grpo_trainer/run_deepseek_v3_671b_megatron.sh、examples/mtp_trainer/run_mimo_7b_mtp_megatron.sh、verl/experimental/fully_async_policy/shell/dapo_30b_a3b_base_math_fsdp.sh。 - 论文:DAPO: Decoupled Clip and Dynamic Sampling Policy Optimization。
- 数据与模型:BytedTsinghua-SIA DAPO collection。