DAPO: An Open-Source LLM Reinforcement Learning System at Scale
Paper: arXiv:2503.14476 Code: BytedTsinghua-SIA/DAPO Code reference:
main@33fe3176(2025-05-11) Authors: Qiying Yu, Zheng Zhang, Ruofei Zhu, Yufeng Yuan, Xiaochen Zuo, Yu Yue (Algorithm), Weinan Dai, Tiantian Fan, et al. (Infrastructure), Jiaze Chen, et al. (Dataset) Affiliations: ByteDance Seed, Institute for AI Industry Research (AIR), Tsinghua University, The University of Hong Kong, SIA-Lab of Tsinghua AIR and ByteDance Seed Project Page: dapo-sia.github.io Training implementation refs: verl-recipemain@99fc8b1d+ verlmain@2eab9364(2026-05-18)
1. Motivation (研究动机)
这篇论文研究的是 大规模 LLM 强化学习训练系统。作者指出,尽管 test-time scaling(如 OpenAI o1、DeepSeek R1)通过 RL 训练出长 Chain-of-Thought 推理能力,但实际的训练细节一直被隐藏: 作者在 Qwen2.5-32B 上用 naive GRPO 训练,AIME 2024 只达到 30 分,远低于 DeepSeek-R1-Zero 的 47 分; 深入分析后发现 naive GRPO 存在 entropy collapse(策略熵快速下降)、reward noise(截断样本的 reward 不准确)、和 training instability(训练不稳定)等问题; 社区也普遍反映难以复现 R1 的结果,说明关键训练细节可能被遗漏。 因此,这篇论文的目标是:公开一个完整的、可复现的、达到 industry-level 性能的大规模 LLM RL 训练系统,包括算法、代码和数据。
2. Idea (核心思想)
DAPO(Decoupled Clip and Dynamic sAmpling Policy Optimization)的核心思想是:在 GRPO 框架上引入四个关键技术改进,解决 long-CoT RL 训练中的 entropy collapse、zero-gradient、loss imbalance 和 reward noise 问题。
四个技术分别是:
- Clip-Higher:解耦上下 clip 阈值,放宽上限让低概率 token 有更大提升空间,防止 entropy collapse;
- Dynamic Sampling:过滤掉 accuracy=0 和 accuracy=1 的 prompt,保证每个 batch 都有有效梯度;
- Token-Level Policy Gradient Loss:按 token 而非 sample 聚合 loss,避免长序列被低估;
- Overlong Reward Shaping:对超长截断样本用软惩罚替代硬惩罚,减少 reward noise。
与 DeepSeek-R1-Zero 的根本区别是:DAPO 不依赖 KL 惩罚项(因为 long-CoT 场景下模型需要大幅偏离初始分布),而是通过上述四个技术共同维持训练稳定性。
3. Method (方法)
3.1 DAPO 目标函数
DAPO 的完整目标函数如下: 其中: 约束条件 表示一个 prompt 的 个回复中,必须既有正确又有错误的才参与训练(Dynamic Sampling 的核心约束)。
3.2 Clip-Higher
Figure 2 解读:左图是 AIME 准确率,右图是 actor model 的生成熵。没有 Clip-Higher 时(紫线),entropy 在约 500 步后快速降到接近 0,说明策略崩溃为几乎确定性输出;有 Clip-Higher 后(蓝线),entropy 保持在合理范围内持续上升,准确率也更高。
问题:标准 PPO/GRPO 使用对称 clip 。当 时,一个概率为 0.01 的 token 最多只能被提升到 ,而概率为 0.9 的 token 可以被提升到 (截断到 1)。上限 clip 严重限制了低概率 “exploration” token 的概率提升空间。
解决方案:解耦上下 clip 阈值:,。放宽上限给低概率 token 更多空间,同时保持下限不变以避免不确定 token 概率被压到 0。
3.3 Dynamic Sampling
Figure 3 解读:左图是被 up-clip 的 token 的平均概率(都低于 0.2,验证了 Clip-Higher 的直觉);右图是 accuracy=1 的样本比例随训练增长到 60%+,意味着越来越多 prompt 的 G 个回复全部正确,advantage 为零,产生零梯度。
问题:GRPO 用组内 reward 归一化算 advantage。如果一个 prompt 的 个回复全部正确(reward 相同),advantage 全为零,这个 prompt 不产生梯度。训练中这种 prompt 比例不断增长,有效梯度信号越来越稀疏。
解决方案:过采样 + 过滤。在采样时持续生成直到 batch 被 accuracy 既非 0 也非 1 的 prompt 填满。这保证了 batch 中每个 prompt 都有有效梯度。
3.4 Token-Level Policy Gradient Loss
Figure 4 解读:左图是生成熵,右图是平均回复长度。没有 token-level loss 时(紫线),entropy 不稳定,回复长度在 2000-4000 之间剧烈波动;有 token-level loss 后(蓝线),entropy 稳定上升,长度增长更健康。
问题:原始 GRPO 先按 token 对每个 sample 内部取平均,再按 sample 取平均。这意味着不管序列长短,每个 sample 权重相同。长序列中的 token 贡献被稀释,短序列中的 token 贡献被放大。
解决方案:改为按 token 总数做全局平均(分母从 变成 )。这样长序列的 reasoning pattern 有更大影响力,同时长序列中的 gibberish/repetitive pattern 也会被更有效地惩罚。
3.5 Overlong Reward Shaping
Figure 5 解读:左图是 AIME 准确率,右图是 entropy。没有 overlong filtering 时(蓝线),准确率震荡且 entropy 爆炸到 4+;有了之后(紫线),训练稳定得多。
问题:RL 训练通常设置最大生成长度 ,超过的样本被截断。截断样本默认被判为错误并给 reward,但实际上其推理过程可能是正确的,只是太长。这引入了 reward noise。
解决方案:先做 Overlong Filtering(mask 掉截断样本的 loss),再加 Soft Overlong Punishment: 其中 是软惩罚缓冲区长度。当回复接近最大长度时,施加渐进惩罚,避免突然切断。
3.6 Algorithm 1: DAPO 完整算法
Algorithm: DAPO
Input: initial policy π_θ, reward model R, task prompts D, hyperparams ε_low, ε_high
1: for step = 1, ..., M do
2: Sample batch D_b from D
3: Update old policy π_θ_old ← π_θ
4: Sample G outputs {o_i}_{i=1}^G ~ π_θ_old(·|q) for each q ∈ D_b
5: Compute rewards {r_i}_{i=1}^G for each o_i via R
6: Filter out o_i and add remaining to dynamic sampling buffer (Eq.11)
7: if buffer size n_b < N:
8: continue # keep sampling until batch is full
9: For each o_i in buffer, compute Â_{i,t} for each token (Eq.9)
10: for iteration = 1, ..., μ do
11: Update π_θ by maximizing DAPO objective (Eq.8)
Output: π_θ3.7 Pseudocode(基于 verl 公开实现)
组件 A:Clip-Higher (decoupled clip ratios)
# Source: verl/trainer/ppo/core_algos.py
# Corresponds to: actor.clip_ratio_low, actor.clip_ratio_high in config
def compute_policy_loss_with_clip_higher(log_prob, old_log_prob, advantages,
clip_ratio_low, clip_ratio_high):
ratio = torch.exp(log_prob - old_log_prob)
# Decoupled clipping: different lower and upper bounds
clipped_ratio = torch.clamp(ratio, 1 - clip_ratio_low, 1 + clip_ratio_high)
pg_loss1 = ratio * advantages
pg_loss2 = clipped_ratio * advantages
loss = -torch.min(pg_loss1, pg_loss2)
return loss组件 B:Dynamic Sampling (filter accuracy=0 and accuracy=1 groups)
# Source: verl/trainer/ppo/ray_trainer.py (training loop)
# The dynamic sampling buffer continues sampling until batch is full
def dynamic_sampling_step(policy, prompts, G, batch_size):
buffer = []
while len(buffer) < batch_size:
batch = sample_batch(prompts)
for q in batch:
outputs = [policy.generate(q) for _ in range(G)]
rewards = [reward_fn(q, o) for o in outputs]
n_correct = sum(1 for r in rewards if r > 0)
# Filter: keep only prompts with 0 < n_correct < G
if 0 < n_correct < G:
buffer.append((q, outputs, rewards))
return buffer[:batch_size]组件 C:Token-Level Policy Gradient Loss
# Source: verl/trainer/ppo/core_algos.py
# loss_agg_mode="token-mean" in config
def token_level_loss_aggregation(per_token_loss, response_mask):
# Instead of: mean over tokens per sample, then mean over samples
# Do: sum all token losses, divide by total token count
total_loss = (per_token_loss * response_mask).sum()
total_tokens = response_mask.sum()
return total_loss / total_tokens组件 D:Overlong Reward Shaping (Soft Overlong Punishment)
# Source: verl reward manager (reward.reward_manager.name=dapo)
def compute_overlong_penalty(response_length, max_resp_len, buffer_len, penalty_factor):
cache_start = max_resp_len - buffer_len
if response_length <= cache_start:
return 0.0
elif response_length <= max_resp_len:
return penalty_factor * (cache_start - response_length) / buffer_len
else:
return -1.0 * penalty_factor3.8 Code-to-paper mapping table
Code reference:
BytedTsinghua-SIA/DAPOmain@33fe3176(2025-05-11); training recipe cross-checked againstverl-project/verl-recipemain@99fc8b1dandverl-project/verlmain@2eab9364.
| Paper Concept | Source File | Key Class/Function |
|---|---|---|
| DAPO 官方论文/模型/评估仓库 | BytedTsinghua-SIA/DAPO | README.md, dapo_paper.pdf, eval/*, img/* |
| AIME 2024 官方评估入口 | BytedTsinghua-SIA/DAPO/eval/eval_aime24.py | vLLM/Ray Serve client evaluation script |
| 模型服务封装 | BytedTsinghua-SIA/DAPO/eval/llm.py | Ray Serve + vLLM deployment wrapper |
| DAPO 训练主入口 | verl-recipe/dapo/main_dapo.py | python -m recipe.dapo.main_dapo Hydra/Ray entry |
| 32B 复现实验脚本 | verl-recipe/dapo/run_dapo_qwen2.5_32b.sh | Qwen2.5-32B DAPO full hyperparameters |
| 训练配置模板 | verl-recipe/dapo/config/dapo_trainer.yaml | reward_manager.name=dapo, data.gen_batch_size |
| Dynamic Sampling | verl-recipe/dapo/dapo_ray_trainer.py | filter_groups, metric=acc, max_num_gen_batches loop |
| Clip-Higher (decoupled clip) | verl/trainer/ppo/core_algos.py | clip_ratio_low, clip_ratio_high, clipped policy loss |
| Token-Level Policy Gradient Loss | verl/trainer/ppo/core_algos.py | agg_loss(..., loss_agg_mode="token-mean") |
| Overlong Reward Shaping | verl/workers/reward_manager/dapo.py | DAPORewardManager, overlong_buffer_cfg |
| 数学答案验证 | verl/utils/reward_score/math_dapo.py | boxed-answer / AIME integer rule-based scorer |
| 分布式 RL 编排 | verl/trainer/ppo/ray_trainer.py | Ray trainer, rollout / actor / ref worker orchestration |
4. Experimental Setup (实验设置)
4.1 模型与数据
Base model: Qwen2.5-32B(预训练模型,非 instruct 版本) 训练数据: DAPO-Math-17K,从 web 和竞赛主页爬取并经 LLM 转换为整数答案格式的数学题 Reward: Rule-based, if correct, otherwise(无 reward model,直接判对错)
4.2 训练配置
Paper-reported optimizer: AdamW, lr = , 20 rollout-step linear warmup(论文 §4.1) Current reproduction script: actor.optim.lr=1e-6, actor.optim.lr_warmup_steps=10, weight_decay=0.1, grad_clip=1.0(verl-recipe/dapo/run_dapo_qwen2.5_32b.sh) Rollout: train_prompt_bsz=512, gen_prompt_bsz=1536, 每个 prompt 采样 16 个回复 (n_resp_per_prompt=16) Mini-batch: train_prompt_mini_bsz=32;
论文文本描述为 mini-batch 512 / 16 gradient updates,这里需要区分 paper 叙述与当前 recipe 脚本 Max length: paper uses tokens;当前 script 写为 max_prompt_length=2048, max_response_length=20480, overlong_buffer_len=4096 Clip-Higher: , ;
script 对应 clip_ratio_low=0.2, clip_ratio_high=0.28 Dynamic Sampling: enable_filter_groups=True, filter_groups_metric=acc, max_num_gen_batches=10 Token-level loss: loss_agg_mode="token-mean",避免长 CoT token 在 sample-level mean 下被过度稀释 评估: 论文 AIME 2024 使用 temperature=1.0、top_p=0.7、重复 32 次取 avg@32;
官方 DAPO 仓库 eval 命令使用 --temperature 1.0 --top_p 0.7 --max_tokens 20480 框架: verl / HybridFlow + Ray;当前 full script 默认 NNODES=16, trainer.n_gpus_per_node=8, tensor_model_parallel_size=4, ulysses_sequence_parallel_size=8, use_dynamic_bsz=True, offload=True
4.3 Baselines
DeepSeek-R1-Zero-Qwen-32B: 47 on AIME 2024(之前 SOTA) Naive GRPO: 30 on AIME 2024(作者的 baseline)
5. Experimental Results (实验结果)
5.1 主结果:逐步添加技术的 ablation
Figure 1 解读:DAPO 在 Qwen2.5-32B 上的 AIME 2024 训练曲线。avg@32(蓝线实线)在约 6000 步达到 50,超过 DeepSeek-R1-Zero-Qwen-32B 的 47(虚线),且只用了约 50% 的训练步数。pass@32 和 cons@32 也在持续提升。
| Model | AIME24 avg@32 |
|---|---|
| DeepSeek-R1-Zero-Qwen-32B | 47 |
| Naive GRPO | 30 |
| + Overlong Filtering | 36 |
| + Clip-Higher | 38 |
| + Soft Overlong Punishment | 41 |
| + Token-level Loss | 42 |
| + Dynamic Sampling (DAPO) | 50 |
每个技术都有正向贡献,从 naive GRPO 的 30 逐步提升到 DAPO 的 50。
5.2 Training Dynamics 分析
Figure 7 解读:左图是平均回复长度,右图是 reward score。回复长度在 1000-4000 之间波动但整体上升,说明模型在学习更长的推理链。Reward 则相对稳定上升,没有明显的 reward hacking。
Figure 7c-d 解读:左图是生成熵,右图是平均生成概率。Entropy 保持在 0.3-0.7 的健康范围内缓慢上升;生成概率从 0.84 下降到 0.74,说明模型在探索更多样的 token 选择。
5.3 Dynamic Sampling 的效率
Figure 6 解读:有无 Dynamic Sampling 的对比。虽然 Dynamic Sampling 需要更多采样次数(因为要过滤掉 accuracy=0/1 的 prompt),但它到达相同性能所需的训练步数更少。总训练时间基本不受影响,因为采样时间在 RL 系统中通常不是瓶颈。
5.4 涌现行为
论文发现在训练过程中,模型逐渐出现了反思和回溯行为——这在训练初期几乎不存在。例如,模型会输出 “However, wait a moment, let’s rethink…” 这样的自我修正模式,说明 RL 训练不仅在强化已有的推理模式,还在涌现全新的推理能力。
5.5 Limitations
论文只在数学任务上验证,未展示代码、科学推理等其他领域的结果; DAPO 的四个技术虽然各自有效,但它们之间的交互效应没有被系统分析; 训练成本仍然很高(512 prompt × 16 responses = 8192 次生成/step),对小团队不够友好。
总体来说,DAPO 的最大贡献是:把一个 industry-level 的 LLM RL 系统从算法到代码到数据全部开源,填补了 DeepSeek-R1 和 OpenAI o1 之间的可复现性空白。
5.6 复现视角下的关键检查点
如果把 DAPO 当作可复现实验而不是只读论文,最容易踩坑的是“论文算法名”和“实际 recipe 参数”之间的错位。论文的四个技术点都落在配置或 trainer 逻辑里;只开 adv_estimator=grpo 并不等于运行 DAPO full recipe。
必须确认 code ref:官方 DAPO 仓库当前 main@33fe3176 主要提供 paper、README、评估脚本、AIME parquet 和图像资源;训练复现入口在 verl-recipe/dapo,核心 loss / reward manager 在 verl。必须确认 Dynamic Sampling 真开了:full script 中 enable_filter_groups=True,filter_groups_metric=acc,gen_prompt_bsz=3×train_prompt_bsz,否则 batch 中会混入 accuracy 全 0 或全 1 的 prompt group。
必须确认 token-level reduction:loss_agg_mode="token-mean" 是 DAPO 相对 GRPO sample-level loss 的核心区别之一;如果回到 sample-level mean,长回答 token 的梯度贡献会被稀释。必须确认 overlong buffer:full script 使用 max_response_length=20480 与 overlong_buffer_len=4096;reward manager 中的惩罚是线性负项,只有在 overlong_buffer.enable=True 时才会生效。
5.6.1 github_ref 与实现 ref
| Ref | Verified value | Why it matters |
|---|---|---|
| Official paper/release repo | BytedTsinghua-SIA/DAPO main@33fe3176 | 笔记 frontmatter 的 github_ref;README 声明 AIME 50、AIME 44、数据/权重/评估入口 |
| Training recipe repo | verl-project/verl-recipe main@99fc8b1d | dapo/run_dapo_qwen2.5_32b.sh, dapo/main_dapo.py, dapo/dapo_ray_trainer.py |
| Framework implementation repo | verl-project/verl main@2eab9364 | core_algos.py, workers/reward_manager/dapo.py, utils/reward_score/math_dapo.py |
| arXiv version | 2503.14476v2, revised 2025-05-20 | v2 abstract / HTML 与 note 中的 50 AIME、四个技术点、DAPO-Math-17K 一致 |
5.6.2 Full DAPO script 中的核心超参
| Category | Script value | Interpretation |
|---|---|---|
adv_estimator | grpo | DAPO 仍使用 group-relative advantage,不引入 value model |
| KL in reward / actor KL loss | False, False | long-CoT RL 中不再强行贴近初始 reference policy |
clip_ratio_low, clip_ratio_high | 0.2, 0.28 | decoupled clip;上界更高以释放低概率探索 token |
max_prompt_length | 2048 | prompt 截断长度;当前 recipe 明确写为 2K |
max_response_length | 20480 | rollout 最大生成长度;和论文 16K + 4K soft buffer 对齐 |
overlong_buffer_len | 4096 | soft overlong punishment 的线性缓冲区 |
train_prompt_bsz | 512 | 每轮保留的有效 prompt 数 |
gen_prompt_bsz | 1536 | Dynamic Sampling 先过采样,再过滤无梯度 group |
n_resp_per_prompt | 16 | 每个 prompt 的 group size |
train_prompt_mini_bsz | 32 | 当前 recipe 的 actor mini batch;和论文叙述的 mini-batch 512 要区分 |
filter_groups_metric | acc | 按同一 prompt 下 16 个回复的准确率分布过滤 |
max_num_gen_batches | 10 | 防止无限重采样;太难/太易数据会触发上限 |
temperature, top_p, val_top_p | 1.0, 1.0, 0.7 | 训练 rollout 更开放,验证按论文使用 top_p 0.7 |
5.6.3 代码路径到论文概念的解释
dapo_ray_trainer.py 里的 filtering 逻辑是 Dynamic Sampling 的实现核心:它先把新生成 batch 的每条 trajectory 按 uid 汇总到 prompt group,再看该 group 的 acc 是否全相同。如果 16 条回复全错或全对,该 prompt 的 advantage 近似没有有效排序信号,因此被过滤;只有 mixed-outcome group 才进入训练 batch。
core_algos.py 里的 policy loss 是 Clip-Higher 和 token-level loss 的交汇点:clip_ratio_low / clip_ratio_high 分别控制 ratio 下界和上界,agg_loss(..., loss_agg_mode="token-mean") 则把有效 token 当作全局平均对象,而不是先在每条 sample 内平均再对 sample 平均。
workers/reward_manager/dapo.py 负责 Soft Overlong Punishment:当 response length 进入 max_resp_len - overlong_buffer_len 之后的 buffer 区间,reward 会额外加上 min(-exceed_len / overlong_buffer_len * penalty_factor, 0)。这不是简单“超过上限给 -1”,而是在截断前逐渐施压,减少把正确但长的推理链误判为坏样本的噪声。
utils/reward_score/math_dapo.py 与官方 DAPO eval 脚本共同说明 reward 的边界:DAPO 的 reward 是 verifiable math outcome reward,重点是最后答案能否被规则解析并匹配 ground truth;这也是论文把 DAPO-Math-17K 转成整数答案格式的原因。
5.6.4 图表核验与读图要点
| Figure asset | Note location | What to check |
|---|---|---|
score.svg | §5.1 | AIME 2024 从 Naive GRPO 30 逐步到 DAPO 50;DeepSeek-R1-Zero-Qwen-32B 47 是外部对照 |
3.1.1.svg, 3.1.2.svg | §3.2 | Clip-Higher 后 accuracy 与 entropy 同时改善,支撑“upper clip 限制探索”的解释 |
3.2.1.svg, 3.2.2.svg | §3.3 | 随训练进行,accuracy=1 的 prompt group 增多,Dynamic Sampling 针对的是有效梯度比例下降 |
3.3.1.svg, 3.3.2.svg | §3.4 | sample-level loss 容易让长度和 entropy 失控,token-level loss 让长 CoT 学习更健康 |
3.4.1.svg, 3.4.2.svg | §3.5 | overlong shaping 的价值是稳定 reward 与 entropy,而不是单纯压短回答 |
4.1.1.svg | §5.3 | Dynamic Sampling 虽增加生成尝试,但减少达到同等性能所需训练步数 |
length.svg, reward.svg, entropy.svg, prob.svg | §5.2 | 这些不是最终指标,而是排查 RL 是否跑偏的训练动态监控面板 |
5.6.5 这篇论文最值得借鉴的实验习惯
第一,DAPO 把“最终分数”拆成了可监控的中间现象:length、reward、entropy、probability。对于 long-CoT RL,最终 AIME score 很稀疏且有滞后;如果只看 score,很难定位是探索塌缩、奖励噪声、过长回答还是 batch 有效梯度不足。 第二,DAPO 的 ablation 顺序是工程诊断式的:先处理 overlong/truncation 噪声,再处理 entropy collapse,再处理 soft overlong,再处理 token-level credit assignment,最后用 Dynamic Sampling 保证每个 batch 的有效 prompt 数。这个顺序体现了系统 RL 的特点:稳定性和可观测性优先于单点算法美感。 第三,DAPO 的开源方式强调“recipe”而不是只开权重:同一论文同时给 paper、dataset、model weights、eval script、wandb record、verl recipe 和 framework code path。对后续工作来说,可复现的最小单元不是一个公式,而是固定 ref 下的 paper + data + launch script + reward/parser + eval。
5.7 Verified References
| Item | URL |
|---|---|
| Paper | https://arxiv.org/abs/2503.14476 |
| arXiv HTML v2 | https://ar5iv.labs.arxiv.org/html/2503.14476v2 |
| Project page | https://dapo-sia.github.io/ |
| Official repo | https://github.com/BytedTsinghua-SIA/DAPO |
| Official model | https://huggingface.co/BytedTsinghua-SIA/DAPO-Qwen-32B |
| Dataset | https://huggingface.co/datasets/BytedTsinghua-SIA/DAPO-Math-17k |
| verl framework | https://github.com/verl-project/verl |
| verl recipe | https://github.com/verl-project/verl-recipe/tree/main/dapo |