跳转至

第十章 偏好对齐:Reward Model、PPO、DPO 与 GRPO

预训练学习“什么文本可能出现”,SFT 学习“怎样按示范回答”,偏好对齐进一步学习“多个可行回答中,哪一个更符合目标”。目标可能包括有用、诚实、安全、风格或可验证正确性。偏好数据只代表特定标注规则与人群,不等于抽象的“人类价值”。

PPO、DPO 与 GRPO 对比

图 10-1 PPO 使用显式奖励与在线策略更新;DPO 直接学习成对偏好;GRPO 对同一问题的一组采样计算相对优势。

10.1 RLHF 基本流程

先建立直觉。 RLHF 典型链路包括 SFT 策略、偏好数据、奖励模型和受约束策略优化。每一步都可能引入偏差,因此对齐不是把一个分数最大化,而是管理多目标取舍。 读这一节时,先不要急着记缩写,而要不断追问:输入是什么,经过了哪些可观察变换,输出又怎样被验证。

把过程拆开看:

  1. SFT 建立可用初始策略。
  2. 收集同 prompt 回答偏好。
  3. 训练或定义奖励。
  4. 在 KL 等约束下优化并评估。

最小例子。 标注者更喜欢礼貌长回答,奖励模型可能把长度当捷径;需要平衡数据和反事实测试识别。 例子越小,越容易手算、打印中间量并判断实现是否偏离定义。

容易踩坑。 偏好等同事实正确、奖励分数跨版本可比、只看平均 reward、不检查能力回归,都会造成 reward hacking。 排错时应保存失败输入和版本,先定位最早出现异常的步骤,再决定是否调整模型或超参数。

动手任务:为客服回答写偏好标注指南,区分正确、帮助性、语气、安全和引用。 完成后不要只保存最终输出,还要保存代码、配置、中间张量或检索结果,以及你对结果的解释。

经典 RLHF 可分为:SFT 初始策略;对同一 prompt 采样多个回答并收集偏好;训练 Reward Model;用 PPO 等算法优化策略,同时用 KL 约束避免偏离参考策略过远。InstructGPT 工作系统展示了这条路线。

奖励模型常用 Bradley-Terry 偏好概率:

$$ P(y_w\succ y_l\mid x)=\sigma(r_\phi(x,y_w)-r_\phi(x,y_l)). $$ 对应损失是负对数似然。若标注者偏好长答案,奖励模型也可能学会长度捷径;需要平衡数据、检查分组偏差和对抗样例。

10.2 PPO:受约束地更新策略

从问题出发。 PPO 在当前策略采样轨迹,估计优势,再用裁剪目标限制一次更新不要偏离太远。价值模型帮助降低回报估计方差,KL 约束限制相对参考策略漂移。 先把名词放到一边,沿着输入、状态变化和输出走一遍;只有能指出证据落在哪一步,概念才算真正掌握。

沿数据流逐步检查:

  1. rollout 采样回答。
  2. 奖励与价值估计优势。
  3. 多轮小批量更新策略/价值。
  4. 监控 KL、clip fraction 与 reward。

用小数据走一遍。 概率比 r=πnew/πold 超出 [1-ε,1+ε] 时,裁剪目标阻止单个样本推动过大更新。 先在纸面预测结果,再让程序打印中间状态;预测与运行不一致的地方,正是需要继续追查的知识缺口。

这里最容易出现的误解。 旧策略 logprob 与新 tokenizer 不一致、padding token 计入奖励、价值损失失控、只追 reward 不看 KL,都会导致不稳。 出现异常时先冻结样本、配置和环境,从最靠近输入的环节开始验证;不要同时更换模型、数据和超参数。

小实验:用四个样本手算 ratio、clipped ratio 和 surrogate objective。 提交物应包含预期、运行证据和失败记录;只有别人能按同样步骤复现,结果才具有学习价值。

PPO 使用旧策略采样,根据奖励与价值估计计算优势,再优化裁剪目标:

$$ L^{clip}=\mathbb{E}\left[\min(r_tA_t,\mathrm{clip}(r_t,1-\epsilon,1+\epsilon)A_t)\right], $$ 其中 \(r_t=\pi_\theta(a_t|s_t)/\pi_{old}(a_t|s_t)\)。裁剪限制单次更新幅度;完整训练还含价值损失、熵、KL 惩罚等。语言模型中“状态”是前缀,“动作”是 token。

PPO 工程复杂:要同时管理策略、参考模型、奖励模型、价值模型和 rollout;奖励尺度、KL、生成长度、优势估计都影响稳定性。Reward hacking 表示策略找到高奖励但不符合真实目标的捷径,因此必须人工抽检与独立评估。

10.3 DPO:从偏好对直接优化策略

先看它解决什么。 DPO 直接从 chosen/rejected 对学习相对偏好:策略相对参考模型提高 chosen 的对数概率差,同时降低 rejected。它省去显式奖励模型和在线 rollout,但仍依赖偏好数据质量。 理解的标准不是会复述定义,而是能画出数据流、预测中间结果,并设计一个让错误暴露出来的检查。

可以把实现分成以下环节:

  1. 同一 prompt 构造偏好对。
  2. 计算策略与参考 logprob。
  3. 形成相对 log-ratio 差。
  4. 用 logistic loss 优化。

一个可以手算的例子。 若 chosen 和 rejected 只差一个事实,DPO 会学习该差异;若还同时差长度与语气,模型可能学习混杂特征。 这里故意不用大模型或大数据,因为小输入能把每个轴、分数和状态变化完整暴露出来。

排错时先看这些地方。 prompt 边界不同、参考模型与初始化不匹配、chosen/rejected 长度偏差严重、把序列总 logprob 未做一致处理,都会影响结果。 修复不能止于‘这次跑通’,还要把失败样本变成自动测试,防止同类问题在下一版本重新出现。

现在动手:实现最小 DPO loss,对调 chosen/rejected 后验证梯度方向改变。 把关键断言写进测试,并在 README 说明怎样运行、怎样判断正确、当前实现还不支持什么。

DPO 利用 KL 正则化奖励优化的闭式关系,把奖励差写成策略与参考策略的对数概率差。常见损失为

$$ -\log\sigma\left(\beta[(\log\pi_\theta(y_w|x)-\log\pi_{ref}(y_w|x)) -(\log\pi_\theta(y_l|x)-\log\pi_{ref}(y_l|x))]\right). $$

import torch
import torch.nn.functional as F

def dpo_loss(policy_chosen, policy_rejected,
             ref_chosen, ref_rejected, beta=0.1):
    """输入均为每条完整回答的 token log-prob 之和,形状 (B,)。"""
    policy_margin = policy_chosen - policy_rejected
    ref_margin = ref_chosen - ref_rejected
    logits = beta * (policy_margin - ref_margin)
    losses = -F.logsigmoid(logits)
    chosen_reward = beta * (policy_chosen - ref_chosen).detach()
    rejected_reward = beta * (policy_rejected - ref_rejected).detach()
    return losses.mean(), (chosen_reward - rejected_reward).mean()

代码解读:必须对回答 token 求和并正确屏蔽 prompt/padding;policy 与 reference 使用同一 tokenizer 和模板;第二个返回值是隐式奖励 margin 的监控量,不是独立真实奖励。长度差可能影响序列 log-prob,应在数据和评估中控制。

DPO 省去显式奖励模型和在线 RL rollout,流程更简单,但依赖离线偏好数据覆盖。它不会自动纠正数据之外的新策略分布,也可能过拟合偏好噪声。

10.4 GRPO:组内相对优势

抓住这一节的主线。 GRPO 对同一 prompt 采样一组回答,用组内奖励均值与标准差形成相对优势,从而不依赖独立价值模型。它适合有可验证奖励的多样采样任务。 遇到新术语,先找它在系统中的位置:它读取什么、保存什么、改变什么,以及失败时会留下什么信号。

真正动手时按这个顺序走:

  1. 每个 prompt 采样 G 条回答。
  2. 计算每条奖励。
  3. 组内标准化得到优势。
  4. 结合概率比与 KL 更新。

先做最小实验。 若一组奖励完全相同,标准差接近零,归一化优势缺少有效信号;实现需加 epsilon 并监控奖励多样性。 把随机性固定并保留中间量,这个例子就能成为后续优化时的回归基线。

别被表面现象带偏。 组大小太小、奖励几乎常数、同质采样、规则验证器可被格式投机,都会削弱训练。 先区分定义错误、实现错误、数据问题和性能瓶颈。四类问题需要的证据不同,不能靠盲目调参混在一起处理。

验证任务:模拟两组奖励,手算相对优势并讨论离群高奖励的影响。 实验结束后写三句话:观察到了什么、这些证据支持什么结论、还有哪种解释尚未排除。

GRPO 为同一 prompt 采样一组回答,计算每个回答的奖励,再用组内均值和标准差标准化,形成相对优势:

$$ A_i=\frac{r_i-\mathrm{mean}(r)}{\mathrm{std}(r)+\epsilon}. $$ 随后以类似策略比率裁剪和 KL 正则的目标更新策略。它可以避免单独训练与策略同规模的价值模型,特别适合数学、代码等有可验证奖励的推理任务。

def group_relative_advantage(rewards: torch.Tensor, eps=1e-6):
    # rewards: (batch_of_prompts, samples_per_prompt)
    mean = rewards.mean(dim=1, keepdim=True)
    std = rewards.std(dim=1, keepdim=True, unbiased=False)
    return (rewards - mean) / (std + eps)

若一组回答奖励完全相同,优势接近零,无法提供有效学习信号。组大小、采样多样性和奖励判定决定信号质量。GRPO 与 DPO 的数据和目标不同,不能简单称为“DPO 的泛化形式”。

10.5 奖励设计

先把概念落到可观察对象上。 奖励函数把产品目标转成训练信号,必须防止模型利用未预期漏洞。可验证正确性、格式、效率和安全往往需要多项奖励与硬约束组合。 这一节的重点是建立因果链。先说明问题,再看计算或流程,最后用可重复实验验证结论。

把抽象概念还原成操作:

  1. 列出真正目标与代理指标。
  2. 设计正常和对抗样例。
  3. 检查可被投机的捷径。
  4. 对各奖励尺度校准并消融。

把它缩小到能逐项检查。 代码任务只按单元测试通过奖励可能鼓励硬编码;增加隐藏测试、静态检查和资源限制可降低投机。 如果这个小例子还不能解释清楚,扩大数据只会让错误更难发现。

需要特别守住的边界。 复杂加权分数无解释、规则奖励泄漏答案、用同一 judge 训练和评测、惩罚项尺度压倒主目标,都会扭曲行为。 保留完整 trace 比猜原因更重要。找到第一次偏离预期的位置,通常比分析最终错误输出更高效。

本节练习:为数学解题设计奖励卡,列出目标、实现、攻击方式和监控指标。 除代码外,请保留一份结果说明,标明环境、随机种子、输入规模和你主动检查过的边界。

可验证任务可以使用单元测试、数学答案、格式 Schema、编译结果等规则奖励。开放问答常需奖励模型或模型评委,但评委也会偏向特定风格并可能被答案欺骗。组合奖励时要防止某一项尺度压倒其他项。

过程奖励评价中间步骤,结果奖励只看最终结果。过程奖励能提供密集信号,却需要可靠步骤标注;错误的过程监督可能限制模型发现新策略。任何奖励都要配独立 holdout 和人工审计。

10.6 数据与训练管线

先建立直觉。 偏好数据需要 prompt 分布、多样回答、清晰标注标准与一致性检查。在线算法还要管理 rollout 版本、奖励版本和策略版本的对应关系。 读这一节时,先不要急着记缩写,而要不断追问:输入是什么,经过了哪些可观察变换,输出又怎样被验证。

把过程拆开看:

  1. 分层采样真实任务。
  2. 生成多样候选而非模板变体。
  3. 双人标注与仲裁。
  4. 保存所有模型与配置版本。

最小例子。 若所有 rejected 都明显更短,模型可能只学长度;可通过长度匹配或反向样本减少混杂。 例子越小,越容易手算、打印中间量并判断实现是否偏离定义。

容易踩坑。 同一回答出现在训练和评测、标注者看到模型名、低一致性样本直接混入、数据无许可证,都会降低可信度。 排错时应保存失败输入和版本,先定位最早出现异常的步骤,再决定是否调整模型或超参数。

动手任务:对 100 对偏好数据统计长度差、主题分布、标注一致率和重复率。 完成后不要只保存最终输出,还要保存代码、配置、中间张量或检索结果,以及你对结果的解释。

偏好数据格式包含 prompt、chosen、rejected 和元数据。确保差异来自质量而非模板、长度或敏感词等捷径。训练/测试按 prompt 语义去重;标注指南明确正确性、有用性、安全、引用等优先级;对分歧样本保留多标注者信息。

PPO/GRPO 需要高吞吐 rollout,通常把生成引擎与训练进程协调;策略版本、采样参数和奖励版本必须记录。离线 DPO 更容易复现,但同样要固定参考模型。

10.7 评估对齐而不损伤能力

从问题出发。 对齐评估必须同时测目标行为、基础能力、过度拒答、欺骗与分布外稳健性。一个更安全的模型若无害问题大量拒答,也不是成功。 先把名词放到一边,沿着输入、状态变化和输出走一遍;只有能指出证据落在哪一步,概念才算真正掌握。

沿数据流逐步检查:

  1. 帮助性与正确性。
  2. 危险请求与越狱。
  3. 无害边界请求的过拒。
  4. 通用能力和格式回归。

用小数据走一遍。 安全拒答测试要包含真正危险、明显无害和语义相近的边界样本,才能区分拒答能力与关键词触发。 先在纸面预测结果,再让程序打印中间状态;预测与运行不一致的地方,正是需要继续追查的知识缺口。

这里最容易出现的误解。 只用单一 judge、不给 judge 参考证据、对齐集与评测集重叠、只报胜率不报失败类型,都会遗漏损伤。 出现异常时先冻结样本、配置和环境,从最靠近输入的环节开始验证;不要同时更换模型、数据和超参数。

小实验:建立对齐评估矩阵,至少包含正确拒绝、错误拒绝、错误服从和正常帮助四格。 提交物应包含预期、运行证据和失败记录;只有别人能按同样步骤复现,结果才具有学习价值。

同时评估偏好胜率、任务正确率、安全、拒答、校准、格式、长度和原有通用能力。只优化单一胜率可能得到冗长讨好回答;安全训练过强可能过度拒答。红队测试应包含提示注入、角色扮演、编码变体和工具副作用。

在线 A/B 需要用户保护、停止阈值和日志审计。高风险领域不能用点击率替代事实正确性。

10.8 选择方法

偏好优化方法选择

图 10-2 先识别离线偏好、在线采样和奖励形态,再比较 DPO、PPO 与 GRPO。 先看它解决什么。 方法选择取决于数据、在线采样能力、奖励可验证性、稳定性与预算。DPO 适合高质量离线偏好,PPO/GRPO 适合需要探索或可验证奖励的任务。 理解的标准不是会复述定义,而是能画出数据流、预测中间结果,并设计一个让错误暴露出来的检查。

可以把实现分成以下环节:

  1. 先用 SFT 建立基线。
  2. 判断是否有偏好对或标量奖励。
  3. 评估 rollout 和训练复杂度。
  4. 设置能力与安全门禁。

一个可以手算的例子。 只有几千对人工偏好且算力有限时先试 DPO;数学验证器可靠且希望探索新策略时可评估 GRPO。 这里故意不用大模型或大数据,因为小输入能把每个轴、分数和状态变化完整暴露出来。

排错时先看这些地方。 追逐算法名称、没有可靠评估就上 RL、把不同实现超参直接照搬,都会浪费资源。 修复不能止于‘这次跑通’,还要把失败样本变成自动测试,防止同类问题在下一版本重新出现。

现在动手:为三个场景写决策表,说明为什么不选择另外两种方法。 把关键断言写进测试,并在 README 说明怎样运行、怎样判断正确、当前实现还不支持什么。

  • 有高质量成对偏好、资源有限:先做 DPO 基线。
  • 有可靠奖励、需要在线探索和精细 KL 控制:考虑 PPO。
  • 同题可采多答案且奖励可验证、希望省去价值模型:考虑 GRPO。
  • 目标主要是格式和任务示范:可能 SFT 已足够。

练习:手算一对 chosen/rejected 的 DPO loss;构造组奖励验证 GRPO 优势和为零;设计一个会被长度欺骗的奖励,并提出诊断方法。

延伸阅读:InstructGPTDPODeepSeek-R1TRL 文档

本章配套代码

下面的脚本与正文使用相同符号。建议先在代码中打印形状和中间量,再运行断言;若依赖尚未安装,至少先阅读入口函数、输入输出和测试部分。

本章端到端实验:把知识变成可复现证据

本实验不是把本章代码重新抄一遍,而是把概念、实现、测试和解释串成一个小型工程。请新建独立目录,保存 README.md、环境文件、源代码、测试、运行日志和结果图。README 至少说明任务、输入输出、运行命令、预期现象、已知限制和复现条件。

实验步骤

  1. 为客服回答写偏好标注指南,区分正确、帮助性、语气、安全和引用。
  2. 用四个样本手算 ratio、clipped ratio 和 surrogate objective。
  3. 实现最小 DPO loss,对调 chosen/rejected 后验证梯度方向改变。
  4. 模拟两组奖励,手算相对优势并讨论离群高奖励的影响。
  5. 为数学解题设计奖励卡,列出目标、实现、攻击方式和监控指标。
  6. 对 100 对偏好数据统计长度差、主题分布、标注一致率和重复率。

每完成一步,先写下预期,再运行代码。若结果与预期不一致,不要覆盖旧日志;建立 failures.md,记录现象、假设、证据、修复和回归测试。这样得到的不是一次性 Demo,而是一份能证明你真正理解本章内容的实验档案。

验收标准

  • 全新环境能够按照 README 从头运行,依赖和随机种子已记录。
  • 关键函数至少有正常、边界和错误输入三类测试;涉及数值计算时检查有限值与合理误差。
  • 结果包含一个基线和至少一个受控改动,能够说明变化来自哪里。
  • 日志保留输入规模、耗时、内存或显存、软件版本和失败样本。
  • 结论区分“实验直接证明的事实”“根据事实做出的推断”和“仍未验证的猜想”。

本章自测

  1. 不看正文,用自己的话解释“RLHF 典型链路包括 SFT 策略、偏好数据、奖励模型和受约束策略优化”,并给出一个可以证伪的测试。
  2. 不看正文,用自己的话解释“PPO 在当前策略采样轨迹,估计优势,再用裁剪目标限制一次更新不要偏离太远”,并给出一个可以证伪的测试。
  3. 不看正文,用自己的话解释“DPO 直接从 chosen/rejected 对学习相对偏好:策略相对参考模型提高 chosen 的对数概率差,同时降低 rejected”,并给出一个可以证伪的测试。
  4. 不看正文,用自己的话解释“GRPO 对同一 prompt 采样一组回答,用组内奖励均值与标准差形成相对优势,从而不依赖独立价值模型”,并给出一个可以证伪的测试。
  5. 不看正文,用自己的话解释“奖励函数把产品目标转成训练信号,必须防止模型利用未预期漏洞”,并给出一个可以证伪的测试。
  6. 不看正文,用自己的话解释“偏好数据需要 prompt 分布、多样回答、清晰标注标准与一致性检查”,并给出一个可以证伪的测试。

回答时先画图或写形状,再给结论。若只能说出术语而不能给出最小例子、边界条件和验证方法,说明这一节仍需要回到代码中重做。