2.5 直接偏好优化(DPO)¶
本章你将学到
- 推导 KL 正则化 RLHF 目标的闭式最优策略
- 这个闭式解如何把奖励模型变成"隐式奖励",并得到 DPO 损失
- 如何用几行 PyTorch 实现 DPO 损失,以及 \(\beta\) 控制什么
- 主要变体(IPO、KTO、ORPO、SimPO)及其区别
- 离线与在线/迭代式 DPO,以及何时选 DPO 而不是 RL
为什么重要¶
RLHF-PPO(第 2.4 章)能用,但很重:要单独训练奖励模型和价值模型,训练循环里还要在线生成,并且有许多敏感的超参数。Rafailov 等人(2023)提出一个问题:既然 RLHF 的目标是一个已知的数学表达式,能不能先用解析方法解出其中一部分,从而绕开强化学习?
答案就是直接偏好优化(Direct Preference Optimization,DPO):它直接在偏好对 \((x, y_w, y_l)\) 上训练策略,损失形式很像逻辑回归。它只需要策略模型和一个冻结的参考模型,训练过程中不需要采样,跑起来几乎和 SFT 一样简单。2023–2024 年间它成为开源模型偏好调优的默认方法,至今仍被广泛使用。
直觉¶
RLHF 的目标可以这样说:"最大化奖励,但不要偏离参考模型太远。"对于任意奖励函数,这个目标下的最优策略形式很简单:取参考模型的概率,乘以 \(\exp(\text{reward}/\beta)\),再重新归一化。好的回答被放大,差的回答被压低。
反过来想:如果策略是最优的,那么它相对参考模型的对数概率比 \(\log\frac{\pi(y\mid x)}{\pi_{\text{ref}}(y\mid x)}\) 就是奖励(差一个缩放系数和一个只依赖 prompt 的常数)。所以我们不必先训练奖励模型再训练策略,而是直接用策略自身来参数化奖励,并让它去拟合偏好数据。这就是 DPO 论文副标题所说的"你的语言模型暗地里就是一个奖励模型"。
工作原理¶
第 1 步:KL 正则化目标¶
记号:\(x\) 是 prompt,\(y\) 是回答,\(\pi\) 是策略(回答上的分布),\(\pi_{\text{ref}}\) 是冻结的参考(SFT)模型,\(r(x,y)\) 是奖励,\(\beta > 0\) 是 KL 强度。对单个 prompt,RLHF 目标(第 2.3 章)为
第 2 步:闭式最优策略¶
把 KL 写成期望,并提出 \(-\beta\):
分母 \(\pi_{\text{ref}}(y\mid x)\exp(r(x,y)/\beta)\) 不是一个概率分布,所以需要归一化。定义配分函数
以及分布
把 \(\pi_{\text{ref}}\exp(r/\beta) = Z(x)\,\pi^*\) 代入目标:
\(Z(x)\) 与 \(\pi\) 无关,而 KL 散度 \(\ge 0\),仅当两个分布相等时取等号。所以目标恰好在 \(\pi = \pi^*\) 处取得最大值。
用大白话说:最优策略就是用指数化的奖励对参考模型重新加权,\(\beta\) 起着温度的作用。\(\beta\) 小,奖励占主导,策略集中到最好的回答上;\(\beta\) 大,策略停留在 \(\pi_{\text{ref}}\) 附近。
我们没法直接用这个公式来采样(计算 \(Z(x)\) 需要对所有可能的回答求和),但可以在代数上利用它。
第 3 步:隐式奖励¶
对最优策略公式取对数,并解出 \(r\):
用大白话说:任何奖励函数都可以用它自己的最优策略来表示,再加上一个只依赖 prompt 的项 \(\beta\log Z(x)\)。因此,如果我们在策略 \(\pi_\theta\) 的空间里搜索,每个策略都定义了一个隐式奖励
而该策略正是这个隐式奖励对应的最优策略(至多差一个与 prompt 相关的常数)。
第 4 步:代入 Bradley–Terry,\(Z\) 被消掉¶
Bradley–Terry 模型(第 2.4 章)只用到同一 prompt 下两个回答的奖励差。\(\beta\log Z(x)\) 这一项对 \(y_w\) 和 \(y_l\) 完全相同,所以被消掉:
在偏好数据集 \(\mathcal{D}\) 上做极大似然,就得到 DPO 损失:
用大白话说:相对参考模型,提高被选中(chosen)回答的对数概率,降低被拒绝(rejected)回答的对数概率,中间经过一个 sigmoid,一旦隐式奖励的间隔足够大它就会饱和。这其实就是 RLHF 里奖励模型的损失,只不过把奖励模型换成了策略自己的对数比。训练它,等于同时拟合了一个奖励模型并得到了它的最优策略。
这里 \(\log\pi_\theta(y\mid x) = \sum_t \log\pi_\theta(y_t\mid x, y_{<t})\) 是回答所有 token 的对数概率之和,只需一次前向传播即可算出,掩码方式与 SFT 相同(第 2.2 章)。
梯度:一种加权的 SFT/不似然(unlikelihood)更新¶
记 \(\hat r_w = \hat r_\theta(x,y_w)\),\(\hat r_l = \hat r_\theta(x,y_l)\)。求导得到:
用大白话说:提高 \(y_w\) 的似然,降低 \(y_l\) 的似然,权重取决于当前隐式奖励错得有多厉害。已经以较大间隔排对的偏好对,权重接近 0。这个加权很重要:DPO 论文报告说,如果没有它,朴素的"提高 chosen、降低 rejected"目标可能会退化。
\(\beta\) 的作用¶
\(\beta\) 同时扮演两个相互关联的角色:
- 原始目标中的 KL 强度:\(\beta\) 越大,隐含的最优策略越贴近 \(\pi_{\text{ref}}\)。
- 隐式奖励的尺度:sigmoid 看到的是 \(\beta\times\)(对数比间隔)。\(\beta\) 大时,对数比的一点小变化就足以让 sigmoid 饱和,训练很早就不再推动策略。\(\beta\) 小时,对数比要增长很多损失才会饱和,策略因此会离参考模型更远。
常见取值在 0.01–0.5 之间,默认值常用 0.1。值得做一次扫描。
变体¶
DPO 衍生出许多变体,每个变体改动一个假设。
| 方法 | 数据 | 是否需要参考模型 | 关键改动 | 直觉 |
|---|---|---|---|---|
| DPO | 偏好对 | 是 | — | 对对数比间隔使用逻辑损失 |
| IPO(Azar et al., 2023) | 偏好对 | 是 | 用平方损失把对数比间隔拉向固定目标 \(\tfrac{1}{2\tau}\),而不是用 sigmoid | 当偏好接近确定性时,DPO 可能把间隔推向无穷大而无视 KL;IPO 的有限目标让它保持被正则化 |
| KTO(Ethayarajh et al., 2024) | 非成对的点赞/点踩标签 | 是 | 受前景理论启发的逐样本损失,把每个回答的隐式奖励与一个参考点(KL 的估计)比较 | 可以使用比偏好对容易收集得多的二元反馈;对收益和损失的处理是不对称的 |
| ORPO(Hong et al., 2024) | 偏好对 | 否 | 在 \(y_w\) 上的 SFT 损失,加上 \(y_w\) 与 \(y_l\) 之间基于长度归一化似然的对数几率比惩罚 | 一个阶段代替"先 SFT 再 DPO";无需在显存中保留参考模型 |
| SimPO(Meng et al., 2024) | 偏好对 | 否 | 隐式奖励 = 长度归一化的对数似然 \(\frac{\beta}{\lvert y\rvert}\log\pi_\theta(y\mid x)\),再加一个目标间隔 \(\gamma\) | 让奖励与驱动生成的平均对数似然一致;消除长度偏置,也去掉了参考模型 |
SimPO 的损失,具体写出来是:
其中 \(\lvert y\rvert\) 是 \(y\) 的 token 数,\(\gamma > 0\) 是间隔。
离线与在线/迭代式 DPO¶
标准 DPO 是离线的:偏好对在训练前就固定了,而且常常是由其他模型生成的。这会造成分布不匹配:策略学的是给它自己永远不会生成的回答排序,而一旦策略发生变化,它又得不到关于自己实际生成内容的反馈。
在线或迭代式 DPO 把这个环闭合起来:
- 用当前策略为每个 prompt 采样若干回答。
- 用奖励模型或 LLM 裁判给回答打标签,组成偏好对(例如最好 vs 最差)。
- 在这些新鲜的偏好对上跑 DPO;把新的参考模型设为当前策略(或保留原来的)。
- 重复。
这让 DPO 的行为更接近 RL。2024–2025 年的证据表明,当采样成本可以接受时,在线、同策略方法(PPO、GRPO、在线/迭代式 DPO)通常优于纯离线 DPO。
何时用 DPO,何时用 RL¶
| 适合用 DPO(或其变体)的情况 | 适合用 RL(PPO/GRPO)的情况 |
|---|---|
| 有偏好数据,但没有程序化验证器 | 有验证器(数学、代码、测试)——见第 2.7 章 |
| 算力有限,想要类似 SFT 的简单与稳定 | 能承担在线采样,并想要最好的最终质量 |
| 目标是风格、语气、有用性、安全校准 | 目标是需要探索才能获得的能力(长推理、智能体任务) |
| 需要在 SFT 之后快速做一轮偏好调优 | 需要多轮交互或与环境交互 |
代码¶
import torch, torch.nn.functional as F
def sequence_logprob(model, input_ids, attention_mask, labels):
"""回答 token 上 log pi(token) 之和。labels 在 prompt/pad 位置为 -100(与 SFT 相同)。
input_ids, attention_mask, labels: [B, L] -> 返回 ([B] 对数概率之和, [B] token 数)。"""
logits = model(input_ids=input_ids, attention_mask=attention_mask).logits[:, :-1].float()
targets = labels[:, 1:]
mask = (targets != -100)
logp = torch.log_softmax(logits, -1).gather(-1, targets.clamp(min=0).unsqueeze(-1)).squeeze(-1)
return (logp * mask).sum(-1), mask.sum(-1)
def dpo_loss(pi_w, pi_l, ref_w, ref_l, beta=0.1):
"""所有输入: [B] 的对数概率之和。返回损失以及用于记录日志的隐式奖励。"""
r_w = beta * (pi_w - ref_w) # chosen 的隐式奖励
r_l = beta * (pi_l - ref_l) # rejected 的隐式奖励
loss = -F.logsigmoid(r_w - r_l).mean()
return loss, r_w.detach(), r_l.detach()
def ipo_loss(pi_w, pi_l, ref_w, ref_l, tau=0.1):
h = (pi_w - ref_w) - (pi_l - ref_l) # 对数比间隔
return ((h - 1.0 / (2 * tau)) ** 2).mean()
def simpo_loss(pi_w, pi_l, len_w, len_l, beta=2.0, gamma=1.0):
r_w = beta * pi_w / len_w # 长度归一化,无参考模型
r_l = beta * pi_l / len_l
return -F.logsigmoid(r_w - r_l - gamma).mean()
def dpo_step(policy, ref, batch, opt, beta=0.1):
"""batch: 包含 chosen_/rejected_ 的 input_ids、attention_mask、labels 的字典(每项 [B, L])。"""
pi_w, _ = sequence_logprob(policy, batch["chosen_input_ids"], batch["chosen_attention_mask"], batch["chosen_labels"])
pi_l, _ = sequence_logprob(policy, batch["rejected_input_ids"], batch["rejected_attention_mask"], batch["rejected_labels"])
with torch.no_grad(): # 参考模型是冻结的
ref_w, _ = sequence_logprob(ref, batch["chosen_input_ids"], batch["chosen_attention_mask"], batch["chosen_labels"])
ref_l, _ = sequence_logprob(ref, batch["rejected_input_ids"], batch["rejected_attention_mask"], batch["rejected_labels"])
loss, r_w, r_l = dpo_loss(pi_w, pi_l, ref_w, ref_l, beta)
opt.zero_grad(); loss.backward(); opt.step()
return {"loss": loss.item(), "reward_acc": (r_w > r_l).float().mean().item(),
"margin": (r_w - r_l).mean().item(), "chosen_logp": pi_w.mean().item()}
用第 2.2 章的 build_example 构造批次:chosen 和 rejected 共享同一个 prompt,只在助手回复部分不同。由于参考模型是冻结的,一个常见技巧是对整个数据集一次性预先算好 ref_w 和 ref_l,这样训练时 GPU 显存里只需要放策略模型。
实例演算¶
最优策略与 \(\beta\)。 某个 prompt 有两个可能的回答,\(\pi_{\text{ref}} = (0.5, 0.5)\),奖励 \(r = (1, 0)\)。
- \(\beta = 0.5\):\(\pi^* \propto (0.5e^{2}, 0.5e^{0}) = (3.69, 0.5)\),所以 \(\pi^*(y_1) = 3.69/4.19 \approx 0.88\)。
- \(\beta = 2\):\(\pi^* \propto (0.5e^{0.5}, 0.5) = (0.824, 0.5)\),所以 \(\pi^*(y_1) \approx 0.824/1.324 \approx 0.62\)。
\(\beta\) 越大,最优策略越接近参考模型的 50/50。
一次 DPO 损失的计算。 对某个偏好对,对数概率之和为 \(\log\pi_\theta(y_w) = -12\),\(\log\pi_{\text{ref}}(y_w) = -13\),\(\log\pi_\theta(y_l) = -15\),\(\log\pi_{\text{ref}}(y_l) = -14\),\(\beta = 0.1\)。
- 隐式奖励:\(\hat r_w = 0.1\times(-12+13) = 0.1\);\(\hat r_l = 0.1\times(-15+14) = -0.1\)。
- 间隔 \(0.2\);\(\sigma(0.2) \approx 0.550\);损失 \(= -\log 0.550 \approx 0.598\)。
- 初始化时(\(\pi_\theta = \pi_{\text{ref}}\)),所有间隔都是 0,损失为 \(\log 2 \approx 0.693\):这是检查你实现是否正确的一个快速办法。
- 梯度权重 \(\sigma(\hat r_l - \hat r_w) = \sigma(-0.2) \approx 0.450\)。
- 若 \(\beta = 1\),同样的对数概率给出间隔 2,损失 \(-\log\sigma(2) \approx 0.127\):损失几乎饱和,训练对这个偏好对的推动会小得多。
实践中的做法(2025–2026)¶
- DPO 系列方法仍然是偏好调优的主力,在开源配方中尤其如此(Zephyr 在 UltraFeedback 上用 AI 裁判打出的偏好做 DPO;Tülu 3 在 SFT 与 RLVR 之间加入一个 DPO 阶段)。之后在可验证领域再用 RL。
- 数据比变体重要。 许多对比发现,在 DPO/IPO/SimPO/KTO 之间如何选择,其影响不如偏好对的质量和同策略程度。从正在训练的模型本身采样、再由强裁判打标的偏好对效果最好。
- 留意 chosen 的对数概率。 DPO 一个众所周知的现象是:训练中 chosen 和 rejected 的对数概率都会下降,而间隔却在增大;如果 chosen 的对数概率下降过多,输出质量可能退化。混入 chosen 上的 SFT 损失(ORPO 在设计上就是这样)或使用更大的 \(\beta\) 都有帮助。
- 长度利用。 DPO 的对数概率之和可能偏向更长的回答;长度归一化的变体(SimPO)或控制长度的数据可以解决这个问题。
常见误区¶
- "DPO 没有奖励模型。" 它有一个隐式奖励模型 \(\beta\log(\pi_\theta/\pi_{\text{ref}})\);你可以像对待奖励模型那样记录它的准确率和间隔。
- "DPO 在实践中等价于 RLHF。" 它只在数据的支撑集上优化同一个目标;离线时,它从未见过也无法纠正策略自己新产生的输出。
- 用错参考模型。 参考模型必须是策略初始化所用的那个模型(通常是 SFT 模型);换成别的模型会改变隐含的优化目标。
- 忘记掩码。 对 prompt token 的对数概率求和(chosen 和 rejected 完全相同)既增加噪声又浪费算力;请把它们掩掉。
- 训练轮数过多。 DPO 很快就会过拟合偏好数据(训练集上的奖励准确率 → 100%);通常 1–3 个 epoch 即可。
本章要点¶
- KL 正则化的 RLHF 目标有闭式最优解 \(\pi^* \propto \pi_{\text{ref}}\exp(r/\beta)\)。
- 把它反过来写,得到隐式奖励 \(\beta\log(\pi_\theta/\pi_{\text{ref}})\);在 Bradley–Terry 中,难以计算的 \(Z(x)\) 被消掉。
- DPO 损失:\(-\log\sigma\big(\beta[\log\frac{\pi_\theta(y_w)}{\pi_{\text{ref}}(y_w)} - \log\frac{\pi_\theta(y_l)}{\pi_{\text{ref}}(y_l)}]\big)\)——每个偏好对每个模型做两次前向传播,无需采样。
- \(\beta\) 既控制 KL 强度,也控制损失饱和的快慢。
- 变体分别改变数据格式(KTO)、损失形状(IPO),或去掉参考模型(ORPO、SimPO);在线/迭代式 DPO 能找回 RL 的大部分优势。
自测¶
为什么配分函数 \(Z(x)\) 会从 DPO 损失中消失?
隐式奖励包含 \(\beta\log Z(x)\),它只依赖 prompt。Bradley–Terry 用的是同一 prompt 下两个回答的奖励之差,所以这一项被消掉了。
训练刚开始时 DPO 损失是多少,为什么?
\(\log 2 \approx 0.693\)。策略初始化为参考模型,因此所有对数比都是 0,间隔为 0,\(-\log\sigma(0) = \log 2\)。
如果你只有针对单个回答的点赞/点踩反馈,应该选哪个 DPO 变体?
KTO,它就是为非成对的二元标签设计的。
为什么在线/迭代式 DPO 可能优于离线 DPO?
离线偏好对来自一个固定的(通常是别的模型的)分布,所以策略得不到关于自己当前输出的反馈。从当前策略采样并给这些样本打标签,能让训练保持同策略,使方法像 RL 一样纠正自己的错误。
两次训练分别用 \(\beta=0.05\) 和 \(\beta=0.5\),哪个策略会偏离参考模型更远?
\(\beta = 0.05\):KL 惩罚更弱,而且 sigmoid 要到对数比间隔大得多时才会饱和。
延伸阅读¶
| 论文 | 为什么读 |
|---|---|
| Direct Preference Optimization (DPO) — Rafailov et al., 2023 | 本章的推导出处,另有实验和梯度分析。 |
| A General Theoretical Paradigm to Understand Learning from Human Preferences (IPO) — Azar et al., 2023 | 统一了 RLHF 与 DPO,并提出 IPO 以避免 DPO 在确定性偏好上的过拟合。 |
| KTO: Model Alignment as Prospect Theoretic Optimization — Ethayarajh et al., 2024 | 用非成对的点赞/点踩标签做偏好调优。 |
| ORPO: Monolithic Preference Optimization without Reference Model — Hong et al., 2024 | 把 SFT 与偏好损失合并为一个阶段,无需参考模型。 |
| SimPO: Simple Preference Optimization with a Reference-Free Reward — Meng et al., 2024 | 长度归一化、无参考模型、带间隔的奖励;效果强且成本低。 |
| Zephyr: Direct Distillation of LM Alignment — Tunstall et al., 2023 | 一份实用配方:先 SFT,再在 AI 裁判打分的 UltraFeedback 偏好上做 DPO。 |