DPO(Direct Preference Optimization,直接偏好优化)是2023年由斯坦福大学提出的大模型对齐算法。它最大的亮点是:不需要训练奖励模型,也不需要运行复杂的强化学习流程,仅凭一批“回答A比回答B好”的偏好数据,就能直接对语言模型进行对齐微调。这个看似“偷懒”的做法,实际上有严格的数学推导支撑。本文将从RLHF的困境讲起,推导DPO的核心公式,并给出可直接运行的PyTorch实现。

一、为什么需要DPO:RLHF的痛点
RLHF(基于人类反馈的强化学习)是ChatGPT时代的经典对齐方案,完整流程分为三步:首先是监督微调(SFT),让模型学会基本的对话能力;其次是训练一个奖励模型,让它学会给回答打分;最后用PPO等强化学习算法,让语言模型在最大化奖励的同时不偏离原始模型太远。
这套流程的问题在于第三步。PPO本身是深度强化学习中最难调的算法之一,需要同时维护四个模型:待训练的策略模型、用于约束的参考模型、奖励模型和价值模型。四个模型都要放进显存,对硬件压力极大。而且强化学习训练极易崩溃,奖励值一旦异常上升,模型就可能学会“钻空子”——输出一堆讨好奖励模型但毫无意义的文字,也就是所谓的reward hacking问题。学习率、KL系数这些超参数稍有不当,训练就会发散。
DPO的作者发现,RLHF的整个目标函数其实存在闭式解。既然可以显式写出最优策略与奖励之间的关系,那就没有必要显式训练奖励模型再跑强化学习了——可以直接用这个关系把偏好数据转成对策略模型的直接监督信号,一步到位完成对齐。这就是DPO的核心思想来源。
二、DPO的核心公式推导
DPO的推导从RLHF的目标函数出发。RLHF希望最大化期望奖励,同时用KL散度约束策略模型不要偏离参考模型太远,写成公式就是:
max_policy E[r(x, y)] - beta * KL(pi_policy || pi_ref)
这个带KL约束的优化问题在数学上有解析解,最优策略可以写成 pi*(y|x) = (1/Z(x)) * pi_ref(y|x) * exp(r(x,y)/beta) 的形式,其中Z(x)是配分函数,用来保证概率归一化。问题在于Z(x)无法显式计算,这正是RLHF必须用采样方式求解的原因。
DPO的巧妙之处在于对上式做变形:先取对数,再把r(x,y)移到等式左边,用两个模型的概率重新表示奖励,得到 r(x,y) = beta * log(pi(y|x)/pi_ref(y|x)) + beta * log(Z(x))。关键来了——当我们把这个表达式代入Bradley-Terry偏好模型(人类判断y优于y'的概率等于sigmoid(r(y)-r(y')))时,含有Z(x)的项在相减中被消掉了!因为同一个prompt x下,配分函数是相同的。
消去Z(x)后,就得到了DPO的最终损失函数:给定偏好对(y_w, y_l),其中y_w是被偏好的回答,y_l是被拒绝的回答,损失为负对数似然形式。直觉上理解,DPO在同时做两件事:提高被偏好回答相对于参考模型的对数概率,降低被拒绝回答的对数概率,而且调整幅度由隐式奖励差距经过sigmoid加权决定。如果模型已经把两个回答区分得很好,梯度会自动变小,这带来了天然的训练稳定性。
三、PyTorch完整实现
下面给出DPO损失的核心实现。可以看到它本质上就是一个带自定义权重的二元交叉熵,代码远比PPO简洁:
import torch
import torch.nn.functional as F
def dpo_loss(policy_logps_win, policy_logps_lose,
ref_logps_win, ref_logps_lose,
beta=0.1):
"""
policy_logps_win: 策略模型对偏好回答的序列对数概率
policy_logps_lose: 策略模型对拒绝回答的序列对数概率
ref_logps_*: 参考模型(通常是SFT模型)的对应值
"""
# 计算隐式奖励:策略与参考模型的log概率差
policy_rewards_win = policy_logps_win - ref_logps_win
policy_rewards_lose = policy_logps_lose - ref_logps_lose
logits = beta * (policy_rewards_win - policy_rewards_lose)
# 偏好损失:希望偏好样本的奖励差尽可能大
loss = -F.logsigmoid(logits).mean()
# 记录隐式奖励均值,用于监控训练
reward_acc = (logits > 0).float().mean()
reward_margins = logits.mean()
return loss, reward_acc, reward_margins序列对数概率的计算需要注意细节:要对整个回答序列的所有token对数概率求和,并且只在回答部分计算,prompt部分要被mask掉。实际工程中通常用HuggingFace的TRL库,它的DPOTrainer已经封装好了这些逻辑,数据格式只需包含prompt、chosen、rejected三个字段即可。
超参数方面最重要的是beta,它对应RLHF中的KL约束强度。beta越大,模型越保守,偏离SFT模型越少但学习能力越弱;beta越小,对齐效果可能更强但容易过拟合甚至语言能力退化。经验上0.1是常用起点,如果偏好数据质量高、量大,可以尝试0.05;数据较少时建议用0.3。学习率通常设为SFT阶段的十分之一左右,例如1e-6到5e-6。
四、DPO与RLHF及其他方法的对比
DPO的优势非常明确:只需两个模型(策略模型和冻结的参考模型),显存开销约为PPO的一半;没有reward hacking风险,训练曲线平稳,几乎不会崩溃;实现和调参难度大幅降低。在Anthropic、Llama等模型的后续对齐实践中,DPO及 variants 已成为主流选择之一。
但它也有局限。DPO本质上是离线算法,训练分布固定,无法像在线RLHF那样根据模型当前状态采样新数据并迭代,因此理论性能上限略低于调得很好的在线RLHF。此外DPO对偏好数据质量极其敏感,标注噪声会被直接放大。学术界后续提出的IPO(用平方损失替代logsigmoid缓解过拟合)、KTO(只需要好/坏二元标签而不需要成对比较)、SimPO(彻底去掉参考模型)等,都是在DPO基础上的改进。
选择建议很直接:中小团队、数据量在几万到几十万条偏好对、追求快速落地的场景,DPO是首选;拥有充足算力和在线采样基础设施、追求极致性能的团队,可以继续投入在线RLHF。实践中一个常见的有效组合是先SFT、再DPO、必要时迭代多轮DPO(即online DPO或iterative DPO),用较小的成本逼近在线强化学习的效果。
总的来说,DPO把对齐问题从复杂的强化学习转化成了一个简单的监督学习任务,这种化繁为简的思路本身就是一个优秀的研究范例。理解了它,也就更深刻地理解了RLHF的本质。