1. 背景与现状:从PPO到GRPO的技术演进
1.1 PPO算法的基础与局限
Proximal Policy Optimization(PPO)作为当前强化学习领域的主流算法,通过重要性采样比率剪裁机制将策略更新限制在先前策略的近端区域内,构建了稳定的策略优化框架。其核心目标函数可表示为:
J
P
P
O
(
θ
)
=
E
[
min
(
r
t
(
θ
)
A
^
t
,
clip
(
r
t
(
θ
)
,
1
−
ε
,
1
+
ε
)
A
^
t
)
]
\mathcal{J}_{PPO}(\theta)=E\left[\min\left(r_t(\theta)\hat{A}_t,\operatorname{clip}(r_t(\theta),1-\varepsilon,1+\varepsilon)\hat{A}_t\right)\right]
JPPO(θ)=E[min(rt(θ)A^t,clip(rt(θ),1−ε,1+ε)A^t)]
其中
r
t
(
θ
)
=
π
θ
(
a
t
∣
s
t
)
/
π
θ
o
l
d
(
a
t
∣
s
t
)
r_t(\theta)=\pi_\theta(a_t|s_t)/\pi_{\theta_{old}}(a_t|s_t)
rt(θ)=πθ(at∣st)/πθold(at∣st)为重要性采样比率。这种对称式剪裁机制虽然保证了训练稳定性,但在处理复杂推理任务时逐渐暴露出三个关键问题:
- 探索能力受限:默认的对称剪裁区间(如 ε = 0.2 \varepsilon=0.2 ε=0.2)对低概率token的采样提升形成压制。例如当初始概率 π θ o l d ( a t ) = 0.01 \pi_{\theta_{old}}(a_t)=0.01 πθold(at)=0.01时,新策略概率最大可提升至 0.012 0.012 0.012,而高概率token(如0.9)却可提升至 1.08 1.08 1.08,这种不对称限制导致模型过早收敛到某种局部最优。
- 长序列优化失效:在需要多步推理的数学证明、代码生成等场景中,传统PPO的样本级损失计算方式(先对每个样本的token损失取平均,再对样本间取平均)导致长序列的有效梯度信号被稀释。
- 奖励噪声敏感:基于规则的奖励机制(如最终答案正确性)在长推理场景中面临严重的延迟奖励问题。特别是在响应长度达到16k+token的复杂数学题场景下,很多正确推理过程因中途截断被错误标记为负奖励。
1.2 GRPO的改进与瓶颈
Group Relative Policy Optimization(GRPO)通过群体相对优势估计和显式KL惩罚项对PPO进行了重要改进:
A
^
i
,
t
=
R
i
−
m
e
a
n
(
{
R
i
}
)
s
t
d
(
{
R
i
}
)
\hat{A}_{i,t}=\frac{R_i-mean(\{R_i\})}{std(\{R_i\})}
A^i,t=std({Ri})Ri−mean({Ri})
J
G
R
P
O
=
E
[
∑
min
(
r
i
,
t
A
^
i
,
t
,
c
l
i
p
(
r
i
,
t
,
1
−
ε
,
1
+
ε
)
A
^
i
,
t
)
−
β
D
K
L
]
\mathcal{J}_{GRPO}=E\left[\sum\min(r_{i,t}\hat{A}_{i,t},clip(r_{i,t},1-\varepsilon,1+\varepsilon)\hat{A}_{i,t})-\beta D_{KL}\right]
JGRPO=E[∑min(ri,tA^i,t,clip(ri,t,1−ε,1+ε)A^i,t)−βDKL]
虽然GRPO在数学推理任务上取得了突破,但在实际工业级应用中仍面临三大挑战:
- 熵崩溃现象:如图2b所示,PPO/GRPO策略熵会有一个骤降的过程,导致采样多样性丧失。在AIME测试集上,模型效果也随着训练进行而逐渐收敛不变。上述现象证明,如PPO或者GRPO等算法严重限制复杂推理路径的探索。
- 梯度稀释效应:当某一批次中存在部分全正确或全错误的样本组时,优势函数计算结果趋近于零。零优势导致策略更新没有梯度,从而降低了样本效率。实验证明,训练过程中准确性等于1的样本数量会持续增加,这意味着每批中的有效提示数量不断减少,这可能导致梯度方差增大,并削弱模型训练中的梯度信号。
- 长度失控风险:在未施加长度约束的情况下,模型响应长度呈现指数级增长趋势。默认情况下,我们对截断样本施加惩罚性奖励,这种方法可能会在训练过程中引入噪声,因为一个合理的推理过程可能仅仅因为过长而受到惩罚。这种惩罚可能会让模型对其推理过程的有效性产生混淆。
2. DAPO的核心创新:突破大规模RL训练瓶颈
DAPO,即Decoupled Clip and Dynamic sAmpling Policy Optimization,解耦裁剪与动态采样的策略优化算法。在这一节中,将结合代码来讲解DAPO的核心创新。
在讨论核心创新之前,原文中先讨论了KL散度损失。KL正则化机制的核心功能在于平衡在线学习策略与固定参考策略之间的策略偏移。在RLHF框架中,训练的核心诉求是在保持预训练模型基准特性的前提下优化模型响应。但当处理长链思维推理任务时,模型输出分布可能发生显著漂移,此时继续施加分布约束反而会阻碍模型性能提升。基于此认知,DAPO算法框架选择移除KL loss。
2.1 解耦剪裁机制(Clip-Higher)
DAPO通过非对称剪裁区间设计重构了策略优化边界:
clip
(
r
i
,
t
,
1
−
ε
l
o
w
,
1
+
ε
h
i
g
h
)
\operatorname{clip}(r_{i,t},1-\varepsilon_{low},1+\varepsilon_{high})
clip(ri,t,1−εlow,1+εhigh)
其中
ε
l
o
w
=
0.2
\varepsilon_{low}=0.2
εlow=0.2,
ε
h
i
g
h
=
0.28
\varepsilon_{high}=0.28
εhigh=0.28。这种设计在保持策略更新稳定性的同时,显著提升了低概率token的探索空间:
- 概率提升上限解禁:对于初始概率为0.01的token,最大可提升至 0.01 ∗ ( 1 + 0.28 ) = 0.0128 0.01*(1+0.28)=0.0128 0.01∗(1+0.28)=0.0128,相比标准PPO提升28%。对应配置:
actor_rollout_ref:
actor:
clip_ratio_low: 0.2
clip_ratio_high: 0.28
无论是PPO还是GRPO,默认的裁剪上下限都是0.2。作者在原文中声称,传统GRPO的对称剪裁范围(如ε=0.2)限制低概率token的探索,导致策略快速收敛(熵崩溃)。作者提出的解决办法是,将剪裁范围解耦为ε_low(抑制高概率token)和ε_high(放宽低概率token限制),允许低概率token有更大提升空间,增加生成多样性。
具体来说,在配置文件中,clip_ratio_high从0.2提高至0.28。
2.2 动态采样策略(Dynamic Sampling)
data:
gen_batch_size: 256
train_batch_size: 64
algorithm:
filter_groups:
enable: True
metric: acc # score / seq_reward / seq_final_reward / ...
max_num_gen_batches: 10 # Non-positive values mean no upper limit
DAPO中第二个关键改进点是动态采样。原文中说,当所有样本奖励相同(如全正确/全错误),梯度信号消失(Zero Advantage)。作者提出的解决方案为,预采样时过滤掉奖励为0或1的样本,仅保留有效梯度样本填充批次。
我一开始理解的是,如果某个大小为64的batch中有5个prompt不满足要求,则对这5个重新采样,或者对64个都重新采样,直到满足要求。
但显然,上面的想法是错误的。核心代码如下:
prompt_bsz = self.config.data.train_batch_size # 原始的batch_size,这里为64
if num_prompt_in_batch < prompt_bsz: # num_prompt_in_batch为统计出来的std不为0的group数量
print(f'{num_prompt_in_batch=} < {prompt_bsz=}')
num_gen_batches += 1 # 继续采样一次
max_num_gen_batches = self.config.algorithm.filter_groups.max_num_gen_batches
if max_num_gen_batches <= 0 or num_gen_batches < max_num_gen_batches:
# 没有达到最大采样上限,这里需要取下一个批次
print(f'{num_gen_batches=} < {max_num_gen_batches=}. Keep generating...')
continue
else:
# 已经达到采样次数上限了,但是还是没有满足要求,此时会报错
raise ValueError(
f'{num_gen_batches=} >= {max_num_gen_batches=}. Generated too many. Please check your data.'
)
else:
# Align the batch
traj_bsz = self.config.data.train_batch_size * self.config.actor_rollout_ref.rollout.n
batch = batch[:traj_bsz]
代码逻辑解析:
# 外层循环从dataloader获取prompt batch
for batch_dict in self.train_dataloader:
# 每次生成全新的gen_batch(包含新的prompt集合)
gen_batch = new_batch.pop(...)
gen_batch_output = self.actor_rollout_wg.generate_sequences(gen_batch)
# 动态过滤逻辑
if self.config.algorithm.filter_groups.enable:
# 计算当前gen_batch的有效prompt
kept_prompt_uids = [...]
num_prompt_in_batch += len(kept_prompt_uids)
# 如果有效prompt不足,继续生成新的gen_batch
if num_prompt_in_batch < self.config.data.train_batch_size:
continue # 跳回外层循环,获取下一个batch_dict
假设存在以下场景:
- 初始batch:包含64个prompt,每个生成8个response
- 过滤结果:其中5个prompt的response标准差为0(被过滤),剩余59个有效
- 保留有效prompt:将59个有效prompt加入累积池
- 检查数量:num_prompt_in_batch = 59 < train_batch_size(假设为64)
- 触发重新生成:通过continue语句跳回外层循环,从self.train_dataloader获取下一个batch_dict
- 处理新batch:对新batch中的prompt(可能是全新的64个)重复生成和过滤流程
- 累积结果:将新batch的有效prompt加入累积池,直到总数≥train_batch_size
- 上述过程最多持续max_num_gen_batches次,如果连续max_num_gen_batches次都没有凑够64个有效样本,那说明数据很有问题了,直接报错即可。
设计特点
- 全量更新:每个gen_batch来自数据加载器的独立prompt集合,而非针对特定prompt重新采样
- 增量累积:有效prompt会跨多个gen_batch累积,直到满足train_batch_size
2.3 灵活的损失聚合模式
示例配置如下:
actor_rollout_ref:
actor:
loss_agg_mode: "token-mean" # / "seq-mean-token-sum" / "seq-mean-token-mean"
# NOTE: "token-mean" is the default behavior
将loss_agg_mode设置为token-mean将意味着小批量中将计算所有序列的所有标记的(策略梯度)损失。
2.4 长度惩罚
DAPO对过长的生成进行了惩罚,相关示例配置如下:
data:
max_response_length: 20480 # 16384 + 4096
reward_model:
overlong_buffer:
enable: True
len: 4096
penalty_factor: 1.0
原文中的公式如下:
R
length
(
y
)
=
{
0
,
∣
y
∣
≤
L
max
−
L
cache
(
L
max
−
L
cache
)
−
∣
y
∣
L
cache
,
L
max
−
L
cache
<
∣
y
∣
≤
L
max
−
1
,
L
max
<
∣
y
∣
R_{\text{length}}(y) = \begin{cases} 0, & |y| \leq L_{\text{max}} - L_{\text{cache}} \\ \frac{(L_{\text{max}} - L_{\text{cache}}) - |y|}{L_{\text{cache}}}, & L_{\text{max}} - L_{\text{cache}} < |y| \leq L_{\text{max}} \\ -1, & L_{\text{max}} < |y| \end{cases}
Rlength(y)=⎩
⎨
⎧0,Lcache(Lmax−Lcache)−∣y∣,−1,∣y∣≤Lmax−LcacheLmax−Lcache<∣y∣≤LmaxLmax<∣y∣
具体来说,当response长度超过预设的最大长度时,定义一个惩罚区间,在这个区间内长度越长收到的惩罚越大。根据上述公式,首先会预设一个最大长度 L m a x L_{max} Lmax,即max_response_length;然后预设一个惩罚区间长度 L c a c h e L_{cache} Lcache,即overlong_buffer.len;接着:
- 如果回复长度小于max_response_length - overlong_buffer.len,那么没有额外奖励也没有额外惩罚
- 如果回复长度大于max_response_length - overlong_buffer.len但又小于预设的最大长度max_response_length,则施加一个软惩罚机制,回复越长,额外添加的reward越小。
- 如果回复长度大于了预设的最大长度,则额外奖励为-1,即惩罚。
相关代码如下:
if self.overlong_buffer_cfg.enable:
overlong_buffer_len = self.overlong_buffer_cfg.len # 惩罚区间长度
expected_len = self.max_resp_len - overlong_buffer_len # L_max - L_cache
exceed_len = valid_response_length - expected_len # 超出的长度
overlong_penalty_factor = self.overlong_buffer_cfg.penalty_factor
overlong_reward = min(-exceed_len / overlong_buffer_len * overlong_penalty_factor, 0)
reward += overlong_reward
3. 实验
在实验中,DAPO成功将 Qwen-32B Base模型训练成一个强大的推理模型。在AIME 2024测试集上,DAPO展现出显著优势:
此外,根据图1
DAPO将Qwen2.5-32B模型在AIME上的准确率从接近 0% 提升至 50%,并且这一提升仅使用了 DeepSeek-R1-Zero-Qwen-32B所需训练步数的 50%。