封面图

系列:开源 AI 论文复现实验与代码解读
日期:2026-08-27
适合读者:研究生、科研新人、希望检查偏好对齐实验的工程读者
检索日期:2026-08-27

摘要

DPO(Direct Preference Optimization,直接偏好优化)常被说成“不需要 reward model 的 RLHF”。这句话只对了一半:DPO 确实省去了单独拟合显式奖励模型和用 PPO 在线采样更新策略的阶段,但它仍然依赖人类或其他标注者给出的偏好对,并通过策略相对参考模型的对数概率比定义“隐式奖励”。

本文围绕一个可验收目标:用三元组 prompt/chosen/rejected 构造最小偏好数据,从公式推到每样本 loss,再用小脚本验证损失下降、隐式奖励 margin 上升。这不是论文规模复现,而是一个能暴露符号、mask、模板和参考模型错误的最小检查点。

目录

  1. DPO 究竟省掉了什么
  2. 偏好数据的最小单元
  3. 从 KL 约束目标到 DPO loss
  4. 官方代码阅读路线
  5. 最小实验与评测协议
  6. 失败分析与证据边界
  7. 后续科研问题
  8. 总结与参考资料

DPO 究竟省掉了什么

经典 RLHF 管线通常有三步:先做监督微调(SFT),再用回答排序训练 reward model,最后用 PPO 等强化学习方法在奖励和偏离参考策略之间取舍。InstructGPT 论文是这条管线的代表之一。DPO 论文的关键做法,是将 KL 约束奖励最大化问题中的最优策略写成闭式,再把 reward 重参数化为当前策略与参考策略的 log-ratio。这样可以直接对偏好对做二元分类式训练。

因此,“reward-free”最稳妥的解释是:不再训练一个独立、可查询的显式 reward model,也不需要训练期间从策略反复采样再由该模型打分。它不等于“没有奖励假设”,更不等于“没有人类偏好成本”。偏好标签是 DPO 的监督信号,参考模型则给出相对尺度。

偏好数据的最小单元

一条 DPO 样本是 (x, y_w, y_l)x 是 prompt,y_w 是被偏好的 chosen completion,y_l 是 rejected completion。两个回答必须对应同一个 prompt;否则模型可能学到 prompt 差异,而不是回答质量差异。TRL 当前官方文档支持普通字符串格式和对话消息格式,并推荐显式保留 prompt

{
  "prompt": "用一句话解释梯度下降。",
  "chosen": "沿损失增长最快方向的反方向更新参数。",
  "rejected": "随机改变参数。"
}

数据验收不能只看三个字段存在。至少要检查:两个回答不完全相同;对话模板后 prompt token 前缀一致;不会因截断把 chosen 的关键结论删掉、却保留 rejected;训练 loss 只累加 completion token,不把 prompt 重复计入偏好对比;数据切分不按单条随机后泄漏近重复 prompt。

还要做一份“偏好对体检表”。对每条样本记录两个回答的 token 数、长度差、来源模型、采样参数、标注规则和标注者一致性。若 chosen 几乎总是更长,或 rejected 总来自一个更弱模型,训练集就存在可被轻易利用的捷径。对对话数据,应以完整会话或 prompt 族为单位划分训练与测试,而不是将同一问题的改写版随机打散。

平局和弱偏好也不该被强制写成硬标签。若标注者无法区分两个回答,可删除该对、保留软偏好概率,或至少在分析中单独分层。否则一个随意决定的 chosen 会被 loss 当成确定性证据,这是标签噪声,不是更多监督。

从 KL 约束目标到 DPO loss

论文从一个 KL 约束的奖励最大化目标出发。给定 prompt x,参考策略为 pi_ref,待训练策略为 pi_theta,可将目标写成:

$$ \max_{\pi};\mathbb{E}{y\sim\pi(y|x)}[r(x,y)]-\beta D{KL}[\pi(y|x)|\pi_{ref}(y|x)] $$

r(x,y) 是回答奖励,beta>0 是奖励与偏离参考策略之间的尺度参数。其最优解满足 pi_r(y|x) ∝ pi_ref(y|x) exp(r(x,y)/beta)。整理后,奖励可表示为 beta * log(pi_r/pi_ref) 加上一个只与 x 有关的归一化项。在 Bradley–Terry 偏好模型中比较同一 prompt 的两个回答时,这个归一化项相消,得到:

$$ L_{DPO}=-\mathbb{E}\log\sigma\left(\beta\left[\log\frac{\pi_\theta(y_w|x)}{\pi_{ref}(y_w|x)}-\log\frac{\pi_\theta(y_l|x)}{\pi_{ref}(y_l|x)}\right]\right) $$

DPO 最小复现流程

对 batch size 为 B、序列长度为 T、词表大小为 V 的自回归模型,原始 logits 形状是 [B,T,V]。用 label 取出目标 token 的 log-prob,再乘 completion mask 并沿 T 求和,得到 chosen/rejected 各一个 [B] 序列 log-prob。当前策略和参考策略各做一次,最终产生 [B] reward margin 和标量 batch loss。这个形状链是检查 mask 和符号的最快方法。

重要的是,DPO 优化的是“相对参考模型的 chosen/rejected 优势”,不是单独最大化 chosen 的绝对概率。因而 loss 下降并不保证 chosen log-prob 一定上升;它也可能主要通过压低 rejected 来扩大 margin。

可以用一个数值例子检查理解。当 policy 与 reference 完全相同时,两个 log-ratio 都为零,每对 loss 是 -log sigmoid(0)=log 2,约为 0.6931。若后续 chosen 的相对 log-prob 增加 0.8,rejected 的相对 log-prob 减少 0.2,当 beta=0.1 时,DPO logit 为 0.1*(0.8-(-0.2))=0.1,loss 降至约 0.6444。这也说明 beta 与优化步长会耦合:不能只看 beta 的名义就猜训练强度,应联合观察 margin、KL 代理量和生成质量。

官方代码阅读路线

作者参考仓库给出了一条清晰路线:先用 preference_datasets.py 看偏好对如何组织,再看 trainers.py 中 policy/reference log-prob、DPO loss 和训练循环,最后回到 train.py 核对配置传递。仓库 README 将管线分为 SFT 和偏好学习两阶段,并建议偏好数据与 SFT 分布尽量一致。

若需要现代工程实现,再读 TRL 的 DPOTrainer。顺序是:数据格式与 chat template;prompt/chosen/rejected 的分词和 EOS;padding 与 truncation;policy 和 reference 的前向;默认 sigmoid loss;最后是 rewards/chosenrewards/rejectedrewards/marginsrewards/accuracies 日志。当前 TRL 还支持多种 DPO 变体,但最小复现应先固定默认 sigmoid loss,避免把框架开关当成原始论文结论。文档和默认值会变,真实实验须记录 TRL、Transformers、tokenizer 与模型 revision,本文的当前 API 细节标记为待人工复核。

代码层还有一笔容易被忽略的资源账:每个 batch 需要 chosen 和 rejected 两支,policy 与 reference 又各需要 log-prob。天真写法可能做四次前向,成熟实现则会尽量拼接两支减少调度开销。参考 log-prob 若不随训练改变,还可预计算并随数据保存,以空间换训练时间;但必须一起锁定 tokenizer、模板、截断和 reference revision,否则缓存值会静默过期。

当 policy 使用 LoRA 时,可以通过禁用 adapter 来得到基座 reference,避免再驻留一份完整模型。但这种优化不能只看显存是否下降,必须用固定 batch 对比“禁用 adapter 的 policy”与单独 reference 的 log-prob;两者不一致时,优先排查已合并权重、dropout 状态、精度和 adapter 切换逻辑。

最小实验与评测协议

配套脚本位于 code/minimal_dpo.py。不安装任何依赖也能运行标准库检查:

python3 code/minimal_dpo.py --check-only

脚本先验证三元组,再用三对手工 sequence log-prob 计算 DPO loss,最后对每对的策略 margin 做解析梯度更新。本次实际 smoke test 中,loss 从 0.693147 下降到 0.415586,平均隐式 reward margin 从 0 上升到 0.663086,三对的 reward accuracy 由 0 变为 1。这些数字只证明脚本的 loss 方向和检查量一致,不是语言模型对齐结果。

安装 PyTorch 后可运行一个 [3,2] logits 的 toy 优化:

python3 -m pip install -r code/requirements.txt
python3 code/minimal_dpo.py --steps 80 --seed 7

真实小模型实验应至少有四组记录。数据层记录标注规则、平局处理、长度分布、近重复去重和切分;模型层记录 SFT checkpoint、reference 构造、chat template 和是否使用 LoRA;优化层记录 beta、学习率、有效 batch、序列长度和 seed;评测层同时报告 held-out DPO loss、reward accuracy/margin、生成长度、与 reference 的 KL 代理量和独立人评或任务指标。

不要只报 reward accuracy。它只问隐式 reward 是否把 chosen 排在 rejected 之上,不能证明模型对新 prompt 的生成更真实、更有用或更安全。最好预先冻结评测 prompt、解码参数和 rubric,对 SFT/reference 与 DPO checkpoint 做盲测对比,并保留逐样本输出。

一个可操作的三层验收顺序是:先做数学单元测试,用四个已知 log-prob 确认符号和 logsigmoid;再做过拟合测试,确认几十个偏好对能被小模型记住,并抽样查看 chosen/rejected 的概率变化;最后才做泛化评测,在未见 prompt 上比较生成和任务指标。第一层失败是实现错,第二层失败多半是优化或数据管线错,只有前两层通过后,第三层失败才值得讨论方法与数据假设。

为了评估方差,至少对小实验运行多个 seed,报告均值、标准差和逐 seed 日志。若资源只够跑一次,应将结果明确标成探索性运行,并优先保留逐样本输出、长度、margin 和失败类型,而不是只保留一个总分。

最终产物应让第三方能回放:保留原始偏好对的不可变版本或校验和、预处理脚本、模板、完整启动命令、环境锁定文件、每次评测的逐样本 JSONL 和失败样例标注。只有 checkpoint 而没有数据与协议,无法证明两次 DPO 实验真的在比较同一个问题。

失败分析与证据边界

第一类是数据假对齐。chosen 和 rejected 使用了不同 system prompt、不同对话历史或不同截断规则,模型会读取这些捷径。修复方法是在分词后直接比较两支 prompt token 前缀,并统计被截断比例。

第二类是 loss 符号或 mask 错。将 chosen/rejected 对调、把参考 log-ratio 减反,或把 padding/prompt token 算进 sequence log-prob,都可能让 loss 看似正常下降。一个必做单元测试是:手工提高 chosen 相对 policy log-prob 后,loss 必须下降,reward margin 必须上升。

第三类是 reference 错位。参考模型应冻结,其 tokenizer、模板和 completion mask 应与 policy 可比。若误把参考模型也更新,log-ratio 标尺会漂移;若 LoRA 训练中没有正确切换 adapter,policy 和 reference 甚至可能实际指向同一状态。

第四类是偏好过优化。偏好数据可能把“更长、更有格式、更自信”误当质量,训练可以成功扩大 margin,却放大这些标注偏差。应分长度区间报告准确率,检查选中答案的长度优势,并对可验证任务加入真实性或执行结果指标。

证据上需严格分层。DPO 论文报告了在情感控制、摘要和单轮对话任务上与 PPO 管线的对比;本文没有重跑这些设置。本次实际验证只包括一手资料打开核对、Python 语法检查和标准库 toy loss smoke test。PyTorch toy 与真实语言模型微调未运行,不应被写成论文复现结果。

后续科研问题

  1. 在相同 SFT checkpoint 上,beta 和学习率是否可分辨,还是只通过有效梯度尺度耦合?
  2. 将偏好对按长度差、难度和标注者一致性分层后,哪一类对最能改善独立人评?
  3. 与完全冻结参考模型相比,周期性更新 reference 会如何改变稳定性和过拟合?
  4. 当 chosen 和 rejected 都很差时,成对相对偏好是否需要质量下限或 SFT loss 作为护栏?
  5. 在代码、数学等可执行任务上,把测试结果与人类偏好分开记录,能否识别“更讨喜但更不正确”的训练方向?

总结

DPO 的最小实质是:对同一 prompt 的 chosen/rejected 回答,比较当前策略相对参考策略的两个 log-ratio,再用 -log sigmoid 扩大偏好 margin。它绕过了显式 reward model 和 PPO 训练环,却没有绕过偏好数据、参考策略、长度偏差和评测设计。

对科研复现而言,最有价值的第一步不是立即微调数十亿参数模型,而是用手工 log-prob 确认符号和 mask,再用小模型固定数据、reference、beta 和解码协议。只有当“loss 按预期变化”和“独立评测真正改善”同时成立,才能把训练成功解释为偏好对齐成功。

若只有前者而没有后者,应停止扩大训练,回到数据偏差、参考模型和评测协议重新排查,而不是继续追求更小的训练 loss。

参考资料

检索日期:2026-08-27。以下优先选用论文页、会议页、作者仓库和官方框架文档。TRL 代码与默认配置会变,实际运行前需再核对锁定版本。

  1. Rafael Rafailov, Archit Sharma, Eric Mitchell, Stefano Ermon, Christopher D. Manning, Chelsea Finn, Direct Preference Optimization: Your Language Model is Secretly a Reward Model, arXiv 2023,v3 2024。
  2. NeurIPS 2023, DPO conference page
  3. Eric Mitchell et al., DPO official reference implementation,包含数据处理、trainer 与 SFT/DPO 配置。
  4. Hugging Face TRL, DPOTrainer documentation,数据格式、loss、日志指标与 PEFT 集成。
  5. Hugging Face TRL, dpo_trainer.py,当前训练器源码。
  6. Long Ouyang et al., Training language models to follow instructions with human feedback, arXiv 2022 / NeurIPS 2022。
  7. Yuntao Bai et al., Training a Helpful and Harmless Assistant with Reinforcement Learning from Human Feedback, arXiv 2022。