面试知识库
高 困难

后训练算法对比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 上算的交叉熵#

LSFT=−1∑tmt∑tmtlog⁡πθ(yt∣x,y<t)\mathcal{L}_{SFT} = -\frac{1}{\sum_t m_t}\sum_{t} m_t \log \pi_\theta(y_t \mid x, y_{<t})

  • 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, labels
python

2. PPO:RM 打分 + critic 估基线 + clip + KL#

RLHF 里 PPO 的优化目标是「奖励高,同时别离参考模型太远」:

max⁡θ  Ex,y∼πθ[rϕ(x,y)]−β KL(πθ(⋅∣x) ∥ πref(⋅∣x))\max_\theta \; \mathbb{E}_{x, y\sim\pi_\theta}\big[r_\phi(x,y)\big] - \beta\,\mathrm{KL}\big(\pi_\theta(\cdot\mid x)\,\|\,\pi_{ref}(\cdot\mid x)\big)

落到实现上分四步:

  1. 采样:当前策略(记为 π_old)对一批 prompt 生成回答
  2. 算每个 token 的奖励:RM 只对整条回答打一个分,放在最后一个 token 上;KL 惩罚按 token 摊开,常见写法是 r_t = −β·(log π_old(y_t) − log π_ref(y_t)),最后一个 token 再加上 r_φ(x,y)
  3. 算优势:critic V(s_t) 预测「从这个位置往后能拿多少奖励」,用 GAE 得到每个 token 的优势

δt=rt+γV(st+1)−V(st),At=∑l≥0(γλ)l δt+l\delta_t = r_t + \gamma V(s_{t+1}) - V(s_t),\qquad A_t = \sum_{l\ge 0}(\gamma\lambda)^l\,\delta_{t+l}

  1. clip 更新:同一批样本上跑几个 epoch,用重要性比 ρ_t 修正「数据是旧策略采的」,并把 ρ_t 截在 [1−ε, 1+ε] 内,防止一步走太远

Lclip=−Et[min⁡(ρtAt,  clip(ρt,1−ϵ,1+ϵ) At)],ρt=πθ(yt∣st)πold(yt∣st)\mathcal{L}_{clip} = -\mathbb{E}_t\Big[\min\big(\rho_t A_t,\; \mathrm{clip}(\rho_t, 1-\epsilon, 1+\epsilon)\,A_t\big)\Big],\qquad \rho_t = \frac{\pi_\theta(y_t\mid s_t)}{\pi_{old}(y_t\mid s_t)}

每一项在防什么:

组件作用去掉会怎样
RM r_φ把人类偏好变成标量信号没有学习信号
critic V给每个 token 一个基线,降低梯度方差、做 token 级功劳分配方差大,训练抖;只能用整条回答的奖励
clip ε限制单次更新里策略的变化量同一批数据多轮更新时容易走飞
KL β拴住 π_ref,防止钻 RM 的漏洞、防止语言退化reward hacking、输出变啰嗦或乱码

代价:actor、critic、reference、RM 四个模型同时在场(critic 常用 RM 初始化),actor 和 critic 都要存梯度和优化器状态,还要做在线生成。显存、调参难度和工程复杂度都是四个算法里最高的。

3. DPO:把奖励藏进对数概率比#

推导只有三步:

  1. 上面那个带 KL 的目标有闭式最优解:π*(y|x) = π_ref(y|x)·exp(r(x,y)/β) / Z(x)
  2. 反过来解出奖励:r(x,y) = β·log(π*(y|x)/π_ref(y|x)) + β·log Z(x)
  3. 代入 Bradley-Terry 偏好模型 P(y_w ≻ y_l) = σ(r_w − r_l),同一个 x 的 β·log Z(x) 正好相减抵消,于是:

LDPO=−E(x,yw,yl)[log⁡σ(βlog⁡πθ(yw∣x)πref(yw∣x)−βlog⁡πθ(yl∣x)πref(yl∣x))]\mathcal{L}_{DPO} = -\mathbb{E}_{(x,y_w,y_l)}\Big[\log\sigma\Big(\beta\log\frac{\pi_\theta(y_w\mid x)}{\pi_{ref}(y_w\mid x)} - \beta\log\frac{\pi_\theta(y_l\mid x)}{\pi_{ref}(y_l\mid x)}\Big)\Big]

  • 括号里的 β·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,用来监控
python

DPO 的已知问题:

  • 离线 + 分布偏移:偏好对如果是别的模型生成的,和当前策略分布差得远,学到的东西迁移差。常见改法是用当前模型采样、再打分造对(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:

A^i,t=ri−mean(r1,…,rG)std(r1,…,rG)\hat{A}_{i,t} = \frac{r_i - \mathrm{mean}(r_1,\dots,r_G)}{\mathrm{std}(r_1,\dots,r_G)}

JGRPO=E[1G∑i=1G1∣oi∣∑t=1∣oi∣(min⁡(ρi,tA^i,t,  clip(ρi,t,1−ϵ,1+ϵ)A^i,t)−β DKL[πθ∥πref])]\mathcal{J}_{GRPO} = \mathbb{E}\Big[\frac{1}{G}\sum_{i=1}^{G}\frac{1}{|o_i|}\sum_{t=1}^{|o_i|}\Big(\min\big(\rho_{i,t}\hat{A}_{i,t},\;\mathrm{clip}(\rho_{i,t},1-\epsilon,1+\epsilon)\hat{A}_{i,t}\big) - \beta\,\mathbb{D}_{KL}\big[\pi_\theta\|\pi_{ref}\big]\Big)\Big]

  • 基线从哪来: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,这组没有梯度
python

GRPO 的几个坑和对应改法:

  • 组内奖励全一样 → 优势全 0:题太简单(全对)或太难(全错)的 prompt 白白消耗 rollout。DAPO 的动态采样会把这类组过滤掉再补样本
  • 长度偏差:每条回答先除以 |o_i| 再平均,错误的长回答每个 token 受到的惩罚被稀释,可能助长回答越写越长。DAPO 改成所有 token 统一平均,Dr. GRPO 去掉了长度归一化和 std 归一化
  • 熵塌缩:对称 clip 让低概率 token 很难被抬上来,探索不足。DAPO 的 clip-higher 把上界放宽(ε_high > ε_low)

5. 四者横向对比#

SFTPPODPOGRPO
数据形态(prompt, 标准回答)prompt + 偏好对训出的 RM(prompt, chosen, rejected)prompt + 可自动判分的规则/环境/RM
是否在线采样否是否(iterative 版本是)是,每题 G 条
训练中的模型14(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
verlPPO、GRPO、DAPO、Dr.GRPO、GSPO、RLOO 等基于 Ray,训练后端 FSDP/Megatron,rollout 用 vLLM/SGLang,面向大规模 RL;DAPO 的复现代码在其 recipe 里
OpenRLHFPPO、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,否则模型会去学「生成用户的话」