后训练算法对比SFT-PPO-DPO-GRPO#
一句话答案#
SFT 是只在 response 上算的 token 级交叉熵,教模型「照着答」;PPO 用奖励模型打分、critic 估基线、clip 限制步长、KL 拴住参考模型,教模型「往高分走」;DPO 把 KL 约束下的最优奖励写成策略与参考模型的对数概率比,直接在偏好对上做分类,不训 RM 也不采样;GRPO 保留 PPO 的 clip 和 KL,但用同题 G 个回答的组内标准化奖励当优势,去掉 critic。选型看数据形态:有标准答案用 SFT,有偏好对用 DPO,有可自动判分的环境用 GRPO。
核心要点
三段式训练流程、RLHF 的全景和 base/instruct 区别见 LLM训练流程与对齐;推理模型怎么用 RLVR 训出长思维链见 推理模型与思维链。本篇只讲四个算法的目标函数怎么写、每一项在干什么、工程上要准备什么。
1. SFT:只在 response 上算的交叉熵#
- m_t 是 loss mask:prompt、system、工具返回这些「不是模型该生成的」token 记 0,assistant 段记 1。HF 生态里通常把这些位置的 label 设成
-100,CrossEntropyLoss默认ignore_index=-100就会跳过 - 归一化口径会影响结果:按 batch 内所有有效 token 求平均,长回答权重大;按每条样本先平均再求平均,短回答权重相对更大。多轮对话、长短差异大的数据要留意这一点
- SFT 是模仿学习:只见过正确示范,没见过「错在哪」,所以学到的是示范数据的分布。它擅长教格式、风格、工具调用 schema,不擅长把模型推到超过示范质量的水平
def build_labels(prompt_ids: list[int], response_ids: list[int]) -> tuple[list[int], list[int]]:
input_ids = prompt_ids + response_ids
labels = [-100] * len(prompt_ids) + response_ids # prompt 段不算 loss
return input_ids, labelspython2. PPO:RM 打分 + critic 估基线 + clip + KL#
RLHF 里 PPO 的优化目标是「奖励高,同时别离参考模型太远」:
落到实现上分四步:
- 采样:当前策略(记为 π_old)对一批 prompt 生成回答
- 算每个 token 的奖励:RM 只对整条回答打一个分,放在最后一个 token 上;KL 惩罚按 token 摊开,常见写法是 r_t = −β·(log π_old(y_t) − log π_ref(y_t)),最后一个 token 再加上 r_φ(x,y)
- 算优势:critic V(s_t) 预测「从这个位置往后能拿多少奖励」,用 GAE 得到每个 token 的优势
- clip 更新:同一批样本上跑几个 epoch,用重要性比 ρ_t 修正「数据是旧策略采的」,并把 ρ_t 截在 [1−ε, 1+ε] 内,防止一步走太远
每一项在防什么:
| 组件 | 作用 | 去掉会怎样 |
|---|---|---|
| RM r_φ | 把人类偏好变成标量信号 | 没有学习信号 |
| critic V | 给每个 token 一个基线,降低梯度方差、做 token 级功劳分配 | 方差大,训练抖;只能用整条回答的奖励 |
| clip ε | 限制单次更新里策略的变化量 | 同一批数据多轮更新时容易走飞 |
| KL β | 拴住 π_ref,防止钻 RM 的漏洞、防止语言退化 | reward hacking、输出变啰嗦或乱码 |
代价:actor、critic、reference、RM 四个模型同时在场(critic 常用 RM 初始化),actor 和 critic 都要存梯度和优化器状态,还要做在线生成。显存、调参难度和工程复杂度都是四个算法里最高的。
3. DPO:把奖励藏进对数概率比#
推导只有三步:
- 上面那个带 KL 的目标有闭式最优解:π*(y|x) = π_ref(y|x)·exp(r(x,y)/β) / Z(x)
- 反过来解出奖励:r(x,y) = β·log(π*(y|x)/π_ref(y|x)) + β·log Z(x)
- 代入 Bradley-Terry 偏好模型 P(y_w ≻ y_l) = σ(r_w − r_l),同一个 x 的 β·log Z(x) 正好相减抵消,于是:
- 括号里的 β·log(π_θ/π_ref) 就是隐式奖励。训练时监控 chosen 与 rejected 的隐式奖励之差(reward margin)和偏好准确率
- 梯度直觉:给 y_w 升概率、给 y_l 降概率,权重是 σ(r̂_l − r̂_w)——模型已经分对的样本权重小,分错的权重大
- log π(y|x) 是整条回答所有 token 的 log-prob 求和,天然偏向「长度」相关的模式;β 越小越允许偏离 π_ref
- π_ref 的 log-prob 在训练中不变,可以离线预先算好,训练时显存里只需要 policy 一份
import torch.nn.functional as F
def dpo_loss(pi_w, pi_l, ref_w, ref_l, beta: float = 0.1):
"""pi_*/ref_*: [B] 每条回答所有 response token 的 log-prob 之和"""
chosen = beta * (pi_w - ref_w) # 隐式奖励
rejected = beta * (pi_l - ref_l)
loss = -F.logsigmoid(chosen - rejected).mean()
return loss, (chosen - rejected).detach() # 第二个返回值是 reward margin,用来监控pythonDPO 的已知问题:
- 离线 + 分布偏移:偏好对如果是别的模型生成的,和当前策略分布差得远,学到的东西迁移差。常见改法是用当前模型采样、再打分造对(on-policy / iterative DPO)
- chosen 的概率也可能下降:损失只要求两者的差变大,训练中常见 chosen 和 rejected 的 log-prob 一起往下掉,生成质量可能变差。可以加一项 chosen 上的 SFT loss 缓解
- 衍生方法按「去掉了什么」记:KTO 不要成对数据,只要「好/坏」单条标签;IPO 改了损失形状缓解过拟合;SimPO 和 ORPO 不要 π_ref,SimPO 用长度归一化的 log-prob 当奖励并加 margin,ORPO 把 SFT loss 和 odds ratio 项合在一起一步训。具体形式以各自论文为准
4. GRPO:去掉 critic,用组内相对优势#
来自 DeepSeekMath,后来用在 DeepSeek-R1。对每个问题 q,用 π_old 采 G 个回答 o_1…o_G,拿到奖励 r_1…r_G:
- 基线从哪来:PPO 靠 critic 预测基线,GRPO 让同题的 G 个回答互为基线。结果奖励(outcome reward)下,同一条回答的每个 token 优势相同
- KL 放在哪:PPO 把 KL 塞进每个 token 的奖励里;GRPO 把 KL 直接作为损失项,用无偏估计 π_ref/π_θ − log(π_ref/π_θ) − 1 逐 token 计算
- 模型份数:policy + reference(β=0 时连 reference 都可以不加载)+ 奖励函数。奖励可以是规则、单测、检索环境,也可以是 RM。省掉的是和 policy 同规模、要训练的 critic
- 成本转移到了采样:每个 prompt 要采 G 条(常见 8–16),rollout 通常交给 vLLM / SGLang 这类推理引擎,训练和生成分开部署或在同一批卡上切换
import torch
def group_advantages(rewards: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
"""rewards: [num_prompts, G],返回同形状的优势"""
mean = rewards.mean(dim=1, keepdim=True)
std = rewards.std(dim=1, keepdim=True)
return (rewards - mean) / (std + eps) # 组内全对或全错时 std=0,优势全为 0,这组没有梯度pythonGRPO 的几个坑和对应改法:
- 组内奖励全一样 → 优势全 0:题太简单(全对)或太难(全错)的 prompt 白白消耗 rollout。DAPO 的动态采样会把这类组过滤掉再补样本
- 长度偏差:每条回答先除以 |o_i| 再平均,错误的长回答每个 token 受到的惩罚被稀释,可能助长回答越写越长。DAPO 改成所有 token 统一平均,Dr. GRPO 去掉了长度归一化和 std 归一化
- 熵塌缩:对称 clip 让低概率 token 很难被抬上来,探索不足。DAPO 的 clip-higher 把上界放宽(ε_high > ε_low)
5. 四者横向对比#
| SFT | PPO | DPO | GRPO | |
|---|---|---|---|---|
| 数据形态 | (prompt, 标准回答) | prompt + 偏好对训出的 RM | (prompt, chosen, rejected) | prompt + 可自动判分的规则/环境/RM |
| 是否在线采样 | 否 | 是 | 否(iterative 版本是) | 是,每题 G 条 |
| 训练中的模型 | 1 | 4(actor/critic/ref/RM) | 2(policy/ref,ref 可预计算) | 2(policy/ref)+ 奖励函数 |
| 负样本信号 | 无 | 有(低奖励) | 有(rejected) | 有(组内低于均值的回答) |
| 稳定性 | 最稳 | 超参多、最难调 | 稳,但会过拟合偏好数据 | 较稳,受奖励设计和组内方差影响大 |
| 典型用途 | 格式、风格、冷启动 | 通用对齐 | 主观偏好(语气、安全、有用性) | 数学、代码、工具调用、结构化规划 |
选型顺序:先 SFT 把格式和基本能力教会(RL 阶段格式错误会拿到持续的低分,拉低组内基线);有偏好对、任务偏主观就上 DPO;能写出确定性打分函数的任务上 GRPO;PPO 适合有成熟 RLHF 基础设施、需要 token 级功劳分配的团队。实际流水线经常是 SFT → DPO → 在线 RL 多段组合。
6. 常用训练框架(2026-09 状态,以官方文档为准)#
| 框架 | 覆盖范围 | 备注 |
|---|---|---|
| TRL(Hugging Face) | SFTTrainer、DPOTrainer、GRPOTrainer、RLOOTrainer、KTOTrainer 等 | 可配 PEFT 做 LoRA,GRPO 可接 vLLM 生成 rollout;v1.13 起已移除 PPOTrainer |
| verl | PPO、GRPO、DAPO、Dr.GRPO、GSPO、RLOO 等 | 基于 Ray,训练后端 FSDP/Megatron,rollout 用 vLLM/SGLang,面向大规模 RL;DAPO 的复现代码在其 recipe 里 |
| OpenRLHF | PPO、GRPO、REINFORCE++ 等 | 基于 Ray + vLLM + DeepSpeed |
| LLaMA-Factory / Unsloth | 以 SFT、LoRA/QLoRA、DPO 为主,也支持部分 RL | 上手快、单机友好 |
7. reward hacking:奖励的漏洞一定会被找到#
RL 优化的是奖励函数,不是你心里的目标。只要两者有缝,策略就会往缝里钻:
| 形式 | 例子 | 对策 |
|---|---|---|
| 长度/格式偏差 | RM 给长回答、列表格式打高分 | 长度惩罚、长度归一化评测、RM 训练数据控长度 |
| 迎合(sycophancy) | 顺着用户立场说 | 偏好数据里加入「正确但不讨好」的样本 |
| 规则漏洞 | 奖励只校验最终答案格式,模型输出多个答案碰运气 | 严格解析,格式不合法直接给负分 |
| 弃权漏洞 | 多维奖励里某维度「无输出即不计分、权重重新归一」,模型学会不输出这一项来甩掉它的权重 | 用标注答案判断该维度是否应当参与,而不是看模型有没有输出 |
发现手段:把奖励拆成各分项看,不只看总分;定期人工抽样 rollout;保留一份不参与训练的评测集和另一把尺子(比如端到端评测,见 LLM评测方法)。KL 约束、clip 和早停能减慢 hacking,但只能减慢,不能代替修奖励函数。
面试回答(2分钟版)
这四个都是后训练,区别在信号从哪来、要几个模型。SFT 就是 next-token 交叉熵,但 prompt 部分 mask 掉只算 response,本质是模仿示范,适合教格式和冷启动,学不到「错在哪」。PPO 是经典 RLHF:先用偏好对训一个奖励模型,策略采样后 RM 给整条回答打分,每个 token 再扣一个相对参考模型的 KL;critic 估计每个位置的价值,用 GAE 算优势;更新时用重要性比并 clip 在 1±ε,防止一步走太远。它要 actor、critic、ref、RM 四个模型,最贵也最难调。DPO 的关键是 KL 约束下的最优策略有闭式解,能把奖励反解成 β 乘以策略和参考模型的对数概率比,代进 Bradley-Terry 以后配分函数抵消,就变成偏好对上的一个 logistic 损失,不训 RM、不采样,只要 policy 和 ref,很稳;缺点是离线,偏好数据和当前策略分布不一致时效果打折,chosen 的概率也可能一起降。GRPO 是 DeepSeek 提的,每题采 G 个回答,用组内奖励减均值除标准差当优势,替掉 critic,保留 clip 和 KL,适合奖励能用规则或环境算出来的任务。坑是组内全对全错时优势为 0,还有长度偏差,DAPO 这类改进就是修这些。选型上:先 SFT 教格式,主观偏好用 DPO,能自动判分用 GRPO。不管哪种 RL,都要防 reward hacking,要把奖励拆开看。结合项目时可以讲:为什么选这个算法、奖励函数由哪几项组成、怎么发现并修掉 hacking、用哪把独立的尺子确认效果。
追问与易错
追问方向:
- “PPO 里 KL 放在奖励里和 GRPO 把 KL 放在损失里,有什么区别?” → PPO 把 −β·log(π/π_ref) 加到每个 token 的奖励上,会经过 GAE 影响优势,critic 也要学着预测它;GRPO 把 KL 作为独立的损失项直接加在目标上,不进优势计算,实现更简单。
- “DPO 为什么不需要奖励模型却仍需要参考模型?” → 隐式奖励就是 β·log(π_θ/π_ref),推导里配分函数 Z(x) 能抵消,但 π_ref 不能省;它的 log-prob 可以离线预计算,所以训练时不一定要加载。SimPO、ORPO 这类方法通过改损失去掉了 π_ref。
- “GRPO 的组大小 G 怎么选?” → G 太小基线估计噪声大、容易全对或全错;G 大了 rollout 成本线性上涨。常见 8–16,按 prompt 难度分布调整,并统计「优势全 0 的组」占比,占比高就要换题或做动态采样。
- “β 调大调小分别什么现象?” → β 大,策略贴着参考模型,reward 涨不动;β 小,reward 涨得快,但更容易 hacking、输出风格漂移。GRPO 做可验证奖励时也有人设 β=0,此时要靠 clip、学习率和早停控制步长。
- “DPO 训练时 chosen 和 rejected 的 log-prob 都在下降,正常吗?” → 常见现象,损失只约束两者的差。如果 chosen 降得太多,生成质量会变差,可以加 chosen 上的 SFT loss、调大 β,或改用 on-policy 采样造偏好对。
- “为什么 RL 前一定要先 SFT?” → RL 靠策略自己采样探索,格式不对的输出拿不到有效奖励,基座连 schema 都写不对时大部分 rollout 浪费;先 SFT 让输出落在奖励函数能解析的范围内,RL 再去优化「对不对」。
- “PPO 的 clip 和 KL 都在限制策略变化,为什么两个都要?” → clip 限制的是相邻两次更新之间(π_θ 相对 π_old)的变化,保证重要性采样近似成立;KL 限制的是相对最初参考模型的累计偏离,防止长期漂移和钻 RM 的空子。
- “怎么判断 GRPO 训练真的在变好?” → 看分段均值而不是逐步曲线(每步 prompt 少,抖动大),同时看各奖励分项、KL、格式失败率、平均回答长度;最后在不参与训练的评测集上用另一把尺子复核。
易错点:
- ❌ “GRPO 不需要奖励模型” → GRPO 去掉的是 critic;奖励可以来自规则,也可以来自 RM,两件事别混
- ❌ “DPO 等价于 RLHF,效果一样” → 两者最优解相同只在理想条件下成立;DPO 离线、不探索,数据分布不匹配时效果差距明显
- ❌ “SFT 的 loss 算整条序列” → prompt/system/工具返回部分要 mask,否则模型会去学「生成用户的话」