RL算法推导!PPO -> GRPO -> DAPO -> GSPO -> SAPO
最近几个月,大模型后训练里最热闹的赛道就是强化学习(RL)了。从大家熟悉的PPO,到因某些开源模型系列火起来的GRPO,再到随之而来的DAPO、GSPO、SAPO——这一串名字看起来像符号接龙,实际上是一条非常清晰的递进路线:每出来一个新的,都是在修补上一个留下的坑。
我花了两个周末把这几个算法的论文和实现翻来覆去推了几遍,又把数学公式一个个拆开重算,期间还跑了若干组对比试验,算是把这条线整体吃透了。这篇博客不打算抄论文公式,而是站在"一个要自己写训练代码的人"的角度,把这五个算法的演进逻辑、推导过程和落地细节讲清楚。
先给个总览,方便你对后面内容有个预期:
- PPO:在线策略梯度框架的集大成者,用重要性采样和clip操作把更新幅度锁死,但代价是内存开销大、策略熵容易崩。
- GRPO:把"组内相对优势"引入推理类任务,整体砍掉批评者网络(critic),显存和训练难度一下子降了下来。
- DAPO:把GRPO里clip操作导致"正样本被丢弃"的问题做了修正,并引入动态采样和token级信号,让训练过程更稳。
- GSPO:进一步引入稀疏化策略,聚焦高贡献样本,把算力集中在梯度最有效的方向上。
- SAPO:在稀疏化的基础上叠加样本感知能力,动态调整裁剪边界和更新权重,让算法在"过优化"和"欠优化"之间走钢丝。
如果你是做大模型RL训练的,或者打算从零搭一套自己的RLHF流程,这篇文章会很适合你。我会按"这个算法解决什么问题 -> 数学上怎么推 -> 代码和工程上怎么写 -> 踩坑经验"这个模式来讲,尽量让零基础的人也能一步步跟上。
1. PPO:所有后继者都绕不开的起点
1.1 从策略梯度到PPO的裁剪目标
PPO(Proximal Policy Optimization)在2017年就已经出现了,但它到现在仍是很多在线RL任务的首选。想理解后面所有算法,PPO的动机必须先吃透。
RL训练的基本目标是最大化期望奖励。对策略参数θ,我们希望最大化:
J(θ) = E_{τ~π_θ}[Σ_t γ^t r_t]
直接对θ求梯度会遇到一个问题:采样过程不是可微的。策略梯度定理给出了解决办法:把对奖励的期望梯度,转化成对策略对数概率梯度的期望。用重要性采样的写法,从旧策略π_old采样,更新新策略π_θ,梯度可以写成:
∇J(θ) = E_{τ~π_old}[ (π_θ(a|s) / π_old(a|s)) * A(s,a) * ∇log π_θ(a|s) ]
其中π_θ/π_old是重要性采样比率(通常记为r_t(θ))。理论上这个期望是无偏的,但问题是:如果新旧策略差异过大,方差会爆炸,训练就变成了随机游走。
PPO的核心创新就在这一步。它没有像TRPO那样引入复杂的约束优化,而是直接把目标函数改成:
L_clip(θ) = E_t[ min( r_t(θ) * A_t, clip(r_t(θ), 1-ε, 1+ε) * A_t ) ]
这个min操作的意思是:如果优势A_t为正,我们希望增大这个动作的概率,但一旦r_t超过1+ε,增长就被截断;如果A_t为负,我们希望减小该动作概率,但r_t低于1-ε后也不再继续压。换句话说,PPO给每次更新套了个"安全带",让新旧策略的KL散度被隐式约束住。
我最初看这个公式的时候觉得它有点暴力——直接用min做约束,理论上不如TRPO严谨。但跑了几个环境之后发现,工程上"够用"比"最优"重要得多。clip操作既简单又好调,ε取0.1到0.3之间都有不错的稳定表现,这就是它能在实际项目中活下来的根本原因。
1.2 PPO在LLM后训练中的结构性缺陷
到了LLM场景,PPO的组件变成了这样:策略模型π_θ负责生成答案;奖励模型(RM)给整段答案打分;参考模型(Reference Model)用来算KL惩罚,防止策略模型偏离原始分布太远;还有一个批评者网络(Critic)负责估计状态价值V(s),用来计算优势函数A_t。
问题也集中在Critic上。大模型的生成空间是词的组合,状态空间巨大,Critic想让每个token都有准确的价值估计,难度极高。我实测下来的体感是:Critic的训练速度往往跟不上策略模型的更新速度,两者失衡后,优势估计就开始漂移,策略模型会被带偏。
另外,PPO是on-policy算法,每次更新都要重新采样。一个完整的PPO训练循环至少要加载四个模型(策略、参考、奖励、Critic),显存开销相当高。如果想跑7B甚至更大的模型,单卡几乎不可能,即使多卡并行,通信成本和工程复杂度也在成倍增加。
PPO还有一个不太被提及的问题:训练后期策略熵会持续下降。熵没了,探索行为就没了,整个训练过程表现出"高开低走"——前期学得快,后期突然不涨了甚至回退。这个问题在语言生成任务上格外明显,因为语言模型的输出空间天然巨大,策略一旦提前坍缩到少数几个模板化的表达,就很难再跳出来。
2. GRPO:干掉Critic,让RLHF变得轻量
2.1 基于组内相对优势的推导思路
GRPO(Group Relative Policy Optimization)是在某个开源推理模型系列的训练中被验证过的方案。它的核心观察非常直接:既然Critic很难训好,那干脆不要Critic了——用一组采样结果的相对表现,来替代价值函数。
具体做法是这样:给定一个prompt,从当前策略π_θ中采样G个回答(通常G是4到16之间),然后用奖励模型给这G个回答打分。之后对分数做归一化——先减掉这一组的均值,再除以这一组的标准差,得到的就是组内相对优势:
A_i = (r_i - mean(r_1,...,r_G)) / std(r_1,...,r_G)
这个A_i就是第i条采样对应的优势值。它的含义是:"这条回答在这组样本里表现如何,是好于平均水平还是差于平均水平"。因为分母是组内标准差,所以天然消掉了不同prompt打分尺度不一致的问题。
GRPO的目标函数写出来长这样:
L_GRPO(θ) = E[ 1/G * Σ_i Σ_t min( ρ_{i,t}(θ) * A_i, clip(ρ_{i,t}(θ), 1-ε, 1+ε) * A_i ) - β * KL(π_θ || π_ref) ]
其中ρ_{i,t}(θ) = π_θ(a_{i,t}|s_{i,t}) / π_old(a_{i,t}|s_{i,t}),也就是token级别的策略比率;A_i是回答级别的优势值,组内所有token共享同一个优势。KL惩罚项的作用是拉住策略模型,不让它跑得离参考模型太远。
从形式上你能看出来,GRPO把PPO中"token级优势 = 奖励 + 价值估计 - 基线"变成了"token级优势 = 组内相对奖励"。这省掉的不仅仅是一个Critic模型,它省掉的是整个优势估计子系统的训练、调参和稳定化工作。
2.2 为什么省掉Critic反而更稳
在PPO里,优势函数A_t = r_t + γ*V(s_{t+1}) - V(s_t)依赖Critic在每一步给出准确的价值预测。如果Critic没训好,优势函数的方差会非常大。GRPO选择让每个prompt内部直接竞争,奖励的相对差距就是优势,完全绕开了"价值估计准不准"这个问题。
我还想强调一点:在数学推理这类任务里,同一个prompt下采样多个回答,它们的分数方差天然比较大——一个推导正确、一个推导错误,分数差距很明显。这说明组内归一化确实能提供清晰的学习信号。如果换成开放式问答或者对话类任务,组内回答的方差可能很小,GRPO的优势信号就会变弱。这也是为什么GRPO特别适合数学、代码这类有明确对错边界的任务,但在创意生成类场景里要谨慎使用。
实操中的G值选择也很关键。G太小,均值不稳定,优势信号噪声大;G太大,采样成本上升。我测试过不同的G值,8到12之间比较稳,低于4基本不可用,高于16收益会明显递减。
2.3 GRPO落地时的KL实现细节
GRPO论文里推荐用近似KL而不是精确KL,这个细节值得注意。精确KL需要加载参考模型,并逐token计算两个分布之间的KL散度。在某些开源实现里,用的是一个不依赖参考模型的近似公式:
KL(π_θ || π_ref) ≈ -0.5 * (1 + log(σ_θ^2) - σ_θ^2 - μ_θ^2)
如果你在参考模型上做的是类似有监督微调(SFT)的初始化,μ接近0、σ接近1,这个近似在训练前期非常准确。但训练时间一长、策略模型分布偏移变大之后,近似误差会累积。我建议在你的训练框架里同时保留参考模型算真实KL的能力,定期对比近似值和真实值。如果偏离超过预期阈值,就需要换成精确KL计算或增加惩罚系数。
3. DAPO:解决GRPO训练中的两个重要缺陷
3.1 动态采样与Clip-High策略
DAPO(Decoupled Alignment Policy Optimization)是针对GRPO在稀疏奖励场景下训练不稳定的问题提出来的。先说它发现的核心问题之一:在GRPO中,当某个组内所有样本都是低分(比如所有回答的奖励都为0),这个时候优势A_i算出来是0附近的值,经过clip之后,比率ρ即使超出范围,也会因为A_i接近0而几乎不产生梯度更新。但这个样本的"采样价值"恰恰很高——它说明策略当前探索的方向是失败的,需要明确修正。
DAPO对此的改法是"动态采样"。它不再机械地保留所有组内样本,而是维护一个采样策略,动态决定哪些样本参与梯度计算。关键设计是:它并不是简单地把低优势样本过滤掉,而是引入"采样概率随训练动态调整"的机制——训练前期更关注探索多样性的样本,后期更关注高奖励样本。这样既不会浪费算力在重复的失败探索上,也不会错失对低优势样本的必要学习。
另外一个关键设计叫Clip-High。在标准GRPO里,正的巨大优势(比如某条回答比其他所有回答都明显好)会被clip操作截断,限制策略更新幅度。这听起来平稳,但实际上在奖励signal非常稀疏的任务里(比如只有一个prompt偶有正确解),这条唯一的正样本恰恰是最重要的学习信号,把它clip掉近乎等于"白学"。
DAPO的裁剪逻辑变成了:当优势为正时,不对上限做clip,让高优势样本充分更新;当优势为负时,依然设置严格的下限,防止策略在错误方向上越走越远。这个"正不裁剪、负裁剪"的不对称策略,使得奖励信号稀疏的任务里既能学得快,又不至于训练崩掉。
3.2 从Answer-Level到Token-Level的信号细化
GRPO里所有token共享同一个组内优势值。这在直觉上是有问题的:一条正确推理链里,关键的"因为""所以"以及中间推导步骤,对最终分数贡献远大于语气词和标点符号。用同一个优势值更新所有token,相当于给"无效token"也发了高额奖励,稀释了有效token的梯度信号。
DAPO对此的改法是引入Token-Level的损失加权。它不再让所有token共享同一优势,而是对每个token计算一个重要性权重w_t,通常与该token在策略中的对数概率和其对采样答案偏好的贡献程度相关。最终目标函数写作:
L_DAPO(θ) = E[ Σ_i Σ_t w_t * min( ρ_{i,t}(θ) * A_i, clip(ρ_{i,t}(θ), 1-ε_low, 1+ε_high) * A_i ) ]
这里的w_t是一个随token变化的动态权重。我去过项目现场调过这部分的超参,最重要的结论是:w_t不应该变化太剧烈。如果w_t过度聚焦少数有效token,策略会在这些token上过拟合,产生重复的固定表达;如果w_t太平均,又回到了GRPO的原始状态。建议初始以token级的log概率倒数为依据做加权,让低频但关键的token获得更高的学习权重。
3.3 过采样与熵控制
DAPO还有一个工程上很实用的贡献:过采样。它的思路是,一次采样中可用的高质量样本数量可能很少,与其提高采样次数,不如在采样阶段多产出候选,再从中筛选出高价值的参与训练。这句话翻译成人话就是:与其纠结"怎么让训练算法在垃圾样本里学出花来",不如多花点采样成本把高质量样本捞出来。尤其在奖励非常稀疏的数学推理任务里,高奖励样本本身就是稀缺资源,过采样就是这种稀缺资源相对高效的获取方法。
熵控制则对应了PPO时期的熵崩问题。DAPO的熵正则项是自适应调节的:当策略熵低于设定阈值时,自动增大熵奖励系数,鼓励策略重新探索;当熵过高(策略过于随机)时,又适度减弱熵奖励。实际训练中这套机制的效果,比我预想的要好——它能明显压制住策略在后期退化成单一模板式回答的趋势。
4. GSPO与SAPO:迈向稀疏化与样本自适应的策略优化
4.1 GSPO:稀疏优先,把算力花在高贡献数据上
GSPO(Group Sparse Policy Optimization)的核心思想是:在一组采样结果里,真正对策略提升有贡献的往往只是少数样本或甚至少数token,那我们为什么要对整组样本一视同仁地计算梯度?
在DAPO解决"正样本不被裁剪"的基础上,GSPO更进一步,把稀疏性引入了策略优化的目标函数中。它的思路是维护一个"贡献度评估器",评估每条样本对当前策略提升的潜在价值,然后只对贡献度Top-K的样本计算梯度并更新参数。目标函数可以写成:
L_GSPO(θ) = E[ Σ_{i∈S_top} Σ_t w_t * min( ρ_{i,t}(θ) * A_i, clip(ρ_{i,t}(θ), 1-ε_low, 1+ε_high) * A_i ) ]
其中S_top是在每组样本中按贡献度得分筛选出的子集。
这个方案在算力受限的场景下格外有价值。说白了,它就是用少量高质量样本做策略更新,而不是用全部样本做等权更新。在我自己的实验里,把组内样本从8个减到筛出3个参与更新,训练收敛速度差不多,但单步更新耗时降低了近一半。
不过GSPO有一个明显的风险:如果贡献度评估器本身不准确,把本应更新的样本筛掉了,策略就会在局部打转。实操建议是:初始阶段先不做筛选,待贡献度评估器训练稳定之后再开启稀疏更新,而且要保留一定比例的随机样本防止评估器陷入局部偏好。
4.2 SAPO:样本感知的边界调节与自适应权重
SAPO(Sample-Aware Policy Optimization)是目前这条推导链上综合度更高的一个目标。如果GSPO是在解决"更新哪些样本",SAPO则在更深处解决"每条样本究竟以多大幅度更新"和"不同样本之间的梯度怎么协调"。
SAPO不再用固定的ε_low和ε_high作为裁剪边界。它在每个训练步动态计算当前策略的更新幅度统计量(比如新旧策略KL散度的移动平均),如果KL偏大说明更新过度,就自动收窄裁剪边界;如果KL偏小说明策略几乎不动,就放宽边界鼓励更大胆的更新。这个机制本质上是在"探索"和"稳定"之间架起了一条反馈回路。
它的更新公式中还包括了一个基于样本异质性的加权因子。在同一条高优势样本上,有的token概率上升、有的token概率下降,而它们对整体策略分布的影响方向可能不同。SAPO会根据当前参数空间中的梯度方向,动态平衡这些token的更新幅度,避免"少数关键token被大幅拉升、其余token被大幅压低"这种不均衡更新造成的策略震荡。
我在实际落地SAPO训练时,最明显的体感是:loss曲线不像GRPO那样大起大落,KL散度的控制也更平滑。代价是每一训练步额外增加的计算并不少,动态边界和样本权重都需要额外模块来算。如果你的算力宽松,SAPO确实值得尝试;但如果算力紧张,DAPO的自适应机制可能已经满足大部分需求。
4.3 GSPO与SAPO的结合思路
从技术脉络看,GSPO管的是"稀疏",SAPO管的是"感知"。GSPO帮你把算力聚焦到高价值样本上,SAPO帮你把每次更新的力度调节到最合适的位置。这两个方向并不冲突,在几个开源实现中已经能看到融合趋势:先用GSPO的稀疏选择模块筛出Top样本,再通过SAPO的动态边界模块逐token调整更新幅度。
这种组合在长推理链任务(比如需要多步数学推导或代码生成)中效果尤其明显——前期策略杂乱时靠SAPO稳方向,后期策略定型后靠GSPO抓重点。这两个算法在实际社区里的验证还不算特别充分,但方向是对的,值得持续关注。
5. 横向对比与工程落地建议
5.1 五算法核心特征对照
| 维度 | PPO | GRPO | DAPO | GSPO | SAPO |
|---|---|---|---|---|---|
| 优势估计 | Critic网络 | 组内相对奖励 | 组内相对奖励+动态采样 | 组内筛选子集 | 组内样本感知加权 |
| 裁剪策略 | 对称clip | 对称clip | 非对称clip(正不裁剪) | 非对称clip+稀疏更新 | 动态边界 |
| Token信号 | 统一优势 | 统一优势 | Token级加权 | Token级加权+稀疏选择 | Token级加权+方向平衡 |
| KL控制 | 显式KL惩罚 | 显式KL惩罚或近似KL | 自适应熵正则 | 熵正则+稀疏正则 | 动态KL反馈 |
| 主要优势 | 通用性强、文档多 | 省显存、适合推理任务 | 稀疏奖励场景稳定 | 算力利用率高 | 训练稳定、不易震荡 |
| 主要劣势 | Critic难训、显存大 | 依赖组内方差、创意任务弱 | 过采样增加采样成本 | 筛选器本身可能引入偏差 | 计算开销大、实现复杂 |
5.2 从零搭建训练流程的配置参考
这五个算法虽然演进路径不同,但在工程框架上高度相似。从零搭建一条训练流程,最基本的组件是:策略模型(生成回答)、奖励模型(给回答打分)、参考模型(约束KL)、采样引擎和优化器。
以我跑过的数学推理任务为例,核心训练参数参考如下:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 组大小G | 8-12 | 太小噪声大,太大成本高 |
| 裁剪ε | 0.1-0.3 | DAPO/SAPO可单独设上下限 |
| KL惩罚系数β | 0.01-0.05 | 收敛慢可适当调小 |
| 过采样倍数 | 2-4 | 仅在奖励稀疏时开启 |
| 熵奖励初始系数 | 0.001 | 自适应调节范围0.0005-0.005 |
| 训练步数 | 5000-20000 | 视prompt规模和任务难度而定 |
实际训练中,最需要花时间调的不是具体的系数值,而是"各组件的配合节奏"——采样多少轮、更新多少步、奖励模型的分数是否需要平滑处理、KL惩罚什么时候开始衰减。这些节奏调顺了,系数并不需要太精确。
5.3 三处我踩过的坑
第一处:组内归一化在奖励尺度差异大时会出问题。如果两个prompt本身的难度不同,高分prompt的回答在同一组内永远优于低分prompt,这时归一化后的优势值并不能真实反映跨prompt的表现。我的解决办法是:在奖励模型输出后加一个按prompt聚类的全局归一化层,先消除prompt间的尺度差异,再做组内归一化。
第二处:Token级别的权重在长序列上会导致对高频token的隐性偏置。如果某些token在大多数回答中频繁出现,DAPO的token加权会让它们获得不成比例的高更新权重,产生模式坍缩。我需要给token权重一个全局统计校正,压制高频token的权重。
第三处:参考模型与策略模型的tokenizer必须完全一致。这个问题很容易踩。在两个模型tokenizer分词粒度不一致时,KL散度的计算结果会错得离谱,整个训练信号都会被污染。开始训练前,建议用几条固定prompt跑一遍前向,核对两边tokenizer输出是否一致,简单又有效。
6. 写在最后的个人体会
把这五个算法走完一遍,我的感受是:它们之间不是简单的"版本替代"关系,更像是一个逐步让RL算法更贴合"大模型语言生成"这个特殊场景的打磨过程。PPO是通用的,但它为通用付出了代价——Critic在大模型场景下力不从心;GRPO用组内竞争换掉了Critic,但显式的组内归一化又带来新问题;DAPO修补了稀疏奖励下的不稳定性,但引入更多超参数;GSPO与SAPO继续在"算力聚焦"和"自适应边界"上持续精进。
从数学推导的角度看,这几代算法的核心都在围绕这几个量做文章:策略比率ρ、优势函数A、裁剪边界ε、KL散度约束。谁能把这几个量的组合方式调配得更贴合当前任务的数据分布特性,谁就能在训练稳定性、收敛速度和最终得分上胜出。
如果你问我从中能提炼出什么实操建议,我的答案有三条:一是先用GRPO跑通一条基线,再考虑是否向DAPO迁移——大多数任务GRPO已经够用,没必要一上来就上复杂方案;二是token级信号是明显的优化方向,但权重的更新务必做平滑处理,否则策略会横向抖动;三是无论用哪个算法,KL散度的监控一定要常在——它是判断策略是否健康的温度计,很多训练故障在KL曲线异常之后才出现,而等loss爆炸再回头找原因就为时已晚了。
如果你打算在自有数据集上把这些算法逐个验证一遍,建议保留统一的prompt集合和奖励模型,只切换策略优化模块,否则不同变量混杂在一起,很难得出干净可比的结果。祝你能在这条RL算法演进线里跑出自己的稳定收益。