ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

DPO:不用奖励模型,语言模型自己就能对齐

DPO:不用奖励模型,语言模型自己就能对齐 NeurIPS 2023的这篇《Direct Preference Optimization: Your Language Model is Secretly a Reward Model》我前前后后读了三遍也基于它把团队内部的对齐方案整个重做了一遍。如果你被RLHF那套“四步走”流程折磨过一定会理解为什么这篇论文能引起这么大反响——它直接告诉你不需要显式训练奖励模型不需要在线强化学习采样你手里那个SFT之后的语言模型本身就藏着一个奖励函数。这篇总结不打算复述论文里的每一个数学符号而是想把DPO从“为什么提出”到“怎么落地”再到“它到底有哪些坑”讲清楚顺便穿插一些我自己踩过的和看别人踩过的教训。适合正在做LLM对齐、或者准备入局但被RLHF工程复杂度劝退的读者参考。1. RLHF工程里那块最难啃的骨头1.1 标准对齐流程到底有多重在DPO出现之前让大语言模型“听人类的话”这件事基本离不开RLHFReinforcement Learning from Human Feedback基于人类反馈的强化学习。RLHF的经典工程链路是这样的训练一个SFT模型作为指令微调基线。收集人类偏好数据通常构造“同一个prompt两个不同回答”的pair让标注员选出更好的一个。用这些偏好对训练一个单独的Reward Model奖励模型学习预测“哪个回答更好”以及“好多少”。把SFT模型作为策略模型用PPO等强化学习算法去最大化奖励模型的打分同时约束策略不要偏离SFT模型太远。每一步都有独立的模型权重、独立的数据管道、独立的训练代码。一个完整的RLHF工程等于同时维护三个大模型SFT模型、Reward Model、策略模型和一个强化学习采样集群。以7B模型为例PPO阶段每轮更新需要大规模采样rollout显存开销是普通SFT的几倍工程团队还得处理采样卡死、KL散度爆炸、奖励模型过拟合等一堆幺蛾子。我见过很多中小团队SFT阶段做得挺好的一进RLHF就失控。不是他们不够仔细而是这套流程的不稳定是内在的随机种子稍微换一个训练曲线就像换了一个项目。1.2 真正让人抓狂的三个深坑细拆RLHF痛点集中在三处。第一个是奖励黑客reward hacking。奖励模型只是人类偏好的一个近似代理它很容易学到一些“表面规律”比如回答更长、包含更多列表、语气更肯定就能拿高分。策略模型在PPO优化中会疯狂钻这些空子生成看着华丽但实际空洞的内容而奖励模型还会给这种行为加分形成恶性循环。实践中你往往要花大量时间给奖励模型加正则、洗数据才能勉强抑制这种现象。第二个是强化学习训练的不稳定性。PPO对hyperparameter非常敏感critic学习率、clip范围、KL惩罚系数、GAE参数任何一个不匹配都会让训练曲线出现悬崖式下跌。策略模型在优化过程中会突然崩溃输出变成乱码这在SFT里几乎不会发生但在RLHF里是家常便饭。第三个是工程复杂度。PPO需要同时管理策略模型、参考模型、奖励模型、价值模型四个权重副本还要维护经验缓冲区和rollout分布式采样。很多团队在配置分布式环境上花的时间比实际调模型的时间还多。这也是为什么RLHF一直是大厂和顶尖实验室的“专属玩具”。DPO不是对RLHF的修补它换了一个更根本的思路既然奖励模型是从偏好数据里训出来的而策略模型又是被奖励模型推着走的那么能不能跳过奖励模型直接用偏好数据优化策略模型本身这篇论文的核心答案是可以而且你的语言模型本身就是那个奖励模型。2. 一句话看懂DPO语言模型其实自带奖励函数2.1 偏好从哪来Bradley-Terry假设要理解DPO先得明白RLHF里奖励模型是怎么被定义出来的。人类标注员在看到两个回答时给出的偏好通常被建模为Bradley-TerryBT模型。这个模型的假设非常简单假设每个回答y在模型内部有一个真实奖励值 r(x, y)那么人类选择回答 y_w 而不是 y_l 的概率等于这两个奖励值差值的sigmoid函数。公式表达就是P(y_w y_l) sigmoid(r(x, y_w) - r(x, y_l))。这个公式本身很朴素但它是整个偏好建模的地基。RLHF的奖励模型训练本质上就是用神经网络去拟合这个BT模型中的奖励函数 r。而DPO的关键观察是这个奖励函数不一定非要显式地训练出来因为在一个已经训练好的RLHF策略里奖励函数的信息已经内嵌在策略的参数之中了。2.2 把奖励从策略里反解出来论文里有一段非常漂亮的推导。RLHF的优化目标是在最大化奖励的期望同时加一个KL散度约束让策略不要偏离参考策略太远。这个带约束的最优化问题数学上存在一个闭式解最优策略 π* 与奖励函数 r 之间存在一一对应的关系。具体来说给定一个奖励函数 r满足KL约束的最优策略形式是π*(y|x) 正比于 π_ref(y|x) 乘以 exp(r(x, y) / β)。其中 π_ref 是SFT参考模型β 是控制KL强度的系数。这个式子往管理解就是“奖励越高的回答最优策略给它的概率越高同时概率不能偏离参考模型太远”。常规RLHF到这里就停止使用了人们把 π* 当作待学的策略用PPO去逼近它。DPO做了一件看起来很小但非常关键的事把这个公式反过来解把奖励函数 r 表示成策略比值的形式。也就是说给定策略 π你可以反推出一个奖励模型 r 使得 π 是那个KL约束问题的最优解。这个反推出来的奖励就是 log(π(y|x) / π_ref(y|x)) 乘以β再加一个只和状态x有关的归一化常数。这就是标题“Your Language Model is Secretly a Reward Model”的含义一旦你把策略参数化了这个策略就隐式地定义了一个奖励模型。既然如此何必再单独训练一个奖励模型绕一个大圈子再回来优化策略呢直接把这个隐式奖励代入偏好概率公式然后最大化偏好概率就行了。这就是DPO的直觉根源。3. DPO的损失函数看这一节就够了3.1 损失的最终形态与直觉既然语言模型本身就定义了奖励下面要做的事就很自然了把反解出的奖励表达式代入BT偏好模型得到“策略π和参考模型π_ref之间的比值”决定偏好的概率然后对这个概率做最大似然估计。最终的DPO损失长这样L_DPO -E_{(x, y_w, y_l)} [ log σ( β * ( log(π(y_w|x)/π_ref(y_w|x)) - log(π(y_l|x)/π_ref(y_l|x)) ) ) ]一眼看上去有点吓人但拆开看其实很好懂。σ是sigmoid函数括号里比较的是两个回答相对于参考模型被强化或削弱的差距。如果“胜出回答被推高的程度”大于“落选回答被推高的程度”括号里就是正的损失就会变小。换句话说DPO训练的目标是让模型对“被人类偏好的回答”的生成概率相比参考模型有显著提升同时让模型对“人类不喜欢的回答”的生成概率相比参考模型显著下降。训练标准不再是拟合一个奖励模型的打分而是直接拉大偏好对之间的概率差距。这里有一个我在看懂论文前一直误解的点DPO不是简单地对“好的回答加概率、对坏的回答减概率”。因为共享同一个语言模型两个回答的概率是耦合的更新任何一个回答的概率都会改变整个分布的形状。最终的优化动力学比单纯的加减要微妙这也是为什么PPT里那个梯度公式值得细看。3.2 为什么梯度不需要强化学习采样DPO在工程上最大的优势在于它的梯度可以直接用标准的反向传播算出来完全不需要策略采样、不需要价值网络、不需要重要性采样。RLHF的PPO阶段为什么要做强化学习因为它的优化目标里策略π生成的样本需要用来估计期望梯度采样和网络更新之间存在循环依赖。而DPO通过数学推导把这个期望中的策略采样部分给消掉了最后剩下的目标函数只依赖固定数据集里的偏好对。这带来的工程简化是巨大的。训练DPO就像训练一个普通的分类器或对比学习模型数据准备好之后一次前向、一次反向、一次参数更新日常训练损失不再发疯也不需要额外的采样集群。我自己的实测感受是DPO训练的内存占用和SFT在一个量级只是需要额外加载参考模型做概率比值计算整体显存开销比PPO小得不是一星半点。3.3 一个让直觉落地的简化示例为了让抽象公式落地我用一个虚拟但贴近真实的例子说明DPO到底“更新了什么”。假设你有一个SFT模型它生成“你好”的概率是0.5生成“你是谁”的概率是0.1。偏好数据告诉你“你好”是好回答“你是谁”是坏回答。用一个β0.5的DPO损失计算梯度模型会增大“你好”的生成概率同时降低“你是谁”的生成概率但变化的幅度不是由两者原始概率差距直接决定的而是由“相对于参考模型的变化幅度”决定的。如果SFT参考模型原本生成“你是谁”的概率已经很低比如0.05那么DPO不会对它施加过大的惩罚因为它本来就很少被采样到。反之如果坏回答是模型高频生成的答案比如0.4那么即使它只比好回答低一点DPO也会对它施加强得多的惩罚因为训练的目标是要把“模型喜欢但人类不喜欢”的生成模式彻底压下去。这个细微差别在实践中很重要。它意味着DPO不只是把人类偏好当作排序信号还隐含地利用了参考模型的先验分布让模型在“能力边界内”做重排。如果你后来发现模型输出变得过分集中、多样性下降这个机制往往是根源之一。4. 动手跑DPO时我最想提醒你的五件事4.1 数据是第一生产力DPO虽然是离线训练不会像PPO那样在训练中动态探索但这也意味着它的上限完全由你的偏好数据集质量决定。数据不行再好的算法也白搭。偏好数据的形式非常简单每一行包含一个prompt、一个chosen响应、一个rejected响应。用Hugging Facedatasets库加载的话大意是这样{ prompt: 用一句话解释什么是量子纠缠。, chosen: 量子纠缠是多个粒子之间的一种关联状态即使相隔很远测量其中一个粒子会即时影响另一个粒子的状态。, rejected: 量子纠缠是一种神秘的物理现象。 }听起来简单但现实里数据质量问题非常多。一个最常见的坑是长度偏置chosen响应往往比rejected更长模型很快学会一个捷径——“更长的回答就是更好的回答”。训练完成后模型开始输出冗长但内容空洞的文本你以为是质量提升了其实是过度拟合了数据里的长度信号。我的建议是在数据清洗阶段做长度匹配。要么在构造pair时尽量让chosen和rejected长度相近要么在数据集中显式控制长度分布避免chosen总是长于rejected。这一步不处理干净后面调β、调学习率都是在错误的地基上盖楼。4.2 β参数、参考模型与训练节奏β是DPO最核心的超参数它控制模型可以偏离参考模型多远。直觉上β较大KL约束更重模型更“粘着”参考模型训练更保守输出变化小。β较小约束放松模型更激进地去学偏好数据但容易过拟合和模式崩溃。论文中使用的β通常在0.1到1.0之间。我自己的经验是如果偏好数据量不大比如只有几千对β可以适当调高一些避免模型在有效信号不足时乱跑。如果数据量大且质量高β可以往低调一些让模型学得更充分。训练节奏上DPO一般不需要像SFT那样训练很多个epoch。我在7B模型上的经验是1到3个epoch就能看到明显效果训练超过3个epoch会出现典型的过拟合信号训练损失一直降但评测集上通用能力开始下降回答变得模板化。建议训练过程中保留一个和偏好数据分布不同的通用评测集每0.25个epoch做一次观察而不是只看DPO损失。参考模型的设置也有讲究。参考模型一般直接用SFT模型训练时完全冻结。它的作用是为“策略偏移”提供锚点如果参考模型和策略模型结构不一致DPO的比值计算就没有意义。低资源场景下你可以用同一个SFT模型同时作为策略初始化和参考模型但要牢记训练时策略模型在更新参考模型永远不能更新。4.3 低成本环境下的跑法如果你不想从头实现DPO社区里已经有不少成熟工具。Triton的DPOTrainer、LLaMA-Factory里的DPO脚本还有各种论文复现仓库都开箱可用。我自己最常用的是基于transformers和peft的方式在LoRA微调的基础上去做DPO。实际跑一个7B模型的DPO训练对硬件的要求没有传说中那么高。全参数训练大约需要4张A10080G用LoRA可以把显存需求压到单张消费级显卡也可运行。关键是在训练前预先缓存参考模型的logprob否则每个batch都要让参考模型重新前向一次显存和算力都受不了。缓存之后训练主循环只需要加载策略模型和参考模型各一份前向反向各一次提速非常明显。我在一次实际训练中遇到过一个隐蔽问题用LoRA做DPO时如果只在注意力层加了低秩适配器策略模型对偏好对的表达力有限训练损失很难降到和全参数训练相同的水平。后来在FFN层也加了LoRA效果立刻改善。这提醒我DPO要优化的是一个“相对概率差距”需要模型有足够的自由度去重塑分布适配器数量太少会限制这个重塑过程的表达能力。5. DPO的边界在哪里5.1 在线探索能力缺失的问题DPO本质上是离线对比学习它只利用数据中已有的偏好对没有任何在线探索机制。这在很多场景下够用但也意味着它不会发现“数据里不存在的好行为”。举个例子如果偏好数据里没有涉及“代码注释规范性”的相关对DPO不会主动摸索出更规范地写注释的方式。相比之下PPO在采样过程中会不断生成新回复虽然稳定性差但理论上能探索到一些训练集中从未出现过的行为。这一点在对话类任务里尤其明显。DPO对数据分布的覆盖度要求很高你需要人工判断偏好数据集是否覆盖了期望的全部能力维度。覆盖不到的能力DPO完全不会去碰。很多团队做完DPO后感觉模型“变乖了”但“变笨了”一部分原因是数据只覆盖了他们评测的那些能力维度其他能力没有受到保护被偏好优化过程中隐藏的分布偏移损害了。5.2 过度优化与通用能力退化这种“变笨”背后有一个更本质的问题DPO的损失只关注偏好对的相对概率没有任何显式机制保护通用能力。训练多轮之后模型可能把所有概率质量都压到训练集中出现过的模板上导致生成多样性下降、对没见过的提示反应变差。我见过一个非常典型的案例有人用DPO微调一个写作模型偏好数据全部来自“结构化清单式输出”训练后模型在写作评测中得分大涨但用户立刻反馈“模型只会列要点、不会写长段落了”。这正是因为DPO损失只优化了偏好数据的分布没有提供任何“不要忘记长段落写作”的约束。缓解手段有几个一是严格控制epoch数不要贪心二是构造一些“通用能力保持对”比如让chosen和rejected在通用能力上是相近的、只在偏好维度上有差异用这种pair来对冲模型向偏好模板一侧崩溃三是使用带正则的DPO变体下面会提到。5.3 模型能力圈之外的偏好翻转还有一个我在阅读论文和复现时思考很久的点如果偏好数据中的chosen响应超出了参考模型原本的能力分布范围DPO会强行把参考模型原本给出极低概率的低概率token拔高。这种情况在数学推理和代码生成这类需要精确能力的任务中尤其危险因为一个token的突然提升可能完全破坏句子的逻辑连贯性。在论文的推导背景下这其实是“策略分布必须被KL约束限制在参考模型附近”这个前提的边界条件。当chosen在参考模型下概率极低比值log(π/π_ref)就会非常大模型为了降低损失需要激进地重塑概率分布。这种激进重塑一旦过头就产生了分布外行为。实操中的表现是你看到训练损失下降得很快但生成质量反而下降甚至出现重复片段或语法混乱。这也解释了为什么DPO在大模型上效果好于小模型大模型的参考模型能力圈更广偏好数据里的chosen响应落在参考模型支持范围内的概率更高DPO的“重排”工作更接近在可信区域内做微调小模型能力圈窄chosen响应常常落在参考模型的低概率区域DPO很容易被逼到“为了讨好偏好数据而牺牲基本能力”的境地。如果你做的是6B以下的模型且发现DPO效果不佳优先怀疑这个原因而不是算法本身。6. 选型建议与我的个人体会6.1 什么时候该用DPO如果你问我现在做对齐优先选什么我的回答是除非你有明确的在线探索需求否则先用DPO把基线做出来再用失败分析决定是否需要更复杂的RLHF。DPO最适合的场景是预算有限、偏好数据量不大或不完美、团队缺少强化学习工程经验。在这些条件下DPO的简单稳定是巨大优势。它不需要分布式采样集群训练流程像SFT一样可控出问题了也容易定位是数据问题还是超参数问题。但如果你面对的任务特别依赖探索性奖励比如复杂的代码竞赛题、需要长程规划的Agent任务DPO就不够了。这类任务里奖励不是简单的偏好排序可以表达的PPO这类在线方法可以从环境反馈中持续学习而DPO只能“看着数据集猜”。这种场景下你需要的是先设计好的可学习奖励再考虑PPO路线。一句话总结我的取舍原则追求稳定收敛、快速迭代选DPO追求极限能力、有成熟RL基础设施选PPO。两者不是替代关系而是不同发展阶段的不同答案。6.2 值得关注的DPO变体论文发表不到一年社区已经涌现了一批DPO的改进工作我觉得有三个值得额外了解。第一个是cDPO它修正了DPO损失中的长度偏置在计算概率时除以回答长度防止模型通过“变长”来作弊。这个修正简单但实用我建议直接作为默认配置考虑。第二个是IPOIdentity Preference Optimization它对“过度优化”做了一项针对性修改不再强迫偏好对之间的概率差无限扩大而是把它优化到一个合理区间。它在噪音较大的偏好数据集上表现得比原版DPO更稳。第三个是KTOKahneman-Tversky Optimization它不需要成对的chosen和rejected数据只需要单条结果被标注为“好”或“坏”。在生产环境里成对标注比单条标注贵很多KTO这种数据形式对小团队吸引力极大。这些变体本质上都是在调整DPO那个权重项的形态理解原版DPO的核心逻辑后再看这些改进会非常快。6.3 我复现和使用DPO积累的几条实用建议最后分享几个不容易在论文里读到的小经验都是我用真金白银换来的。第一DPO的评估不能只看偏好对上的准确率。一个模型可以在训练集偏好对上达到接近100%的准确率但人类的真实偏好倾向可能完全没学到。我建议在模型训练前先选定一套独立的、包含通用能力和安全维度的评测集每次训练完都跑一遍完整评测不要只看损失曲线。第二β参数和SFT程度是耦合的。如果SFT阶段已经训得很熟练、模型已经很听话DPO的β可以调小一点给模型留下调整空间如果SFT比较弱β需要大一点防止模型被偏好数据带节奏跑偏。第三数据清洗远比你想象得重要。DPO对bad pair非常敏感一条chosen和rejected标反的数据可能抵消几十条正确数据的效果。清洗阶段最好做交叉验证找两个人分别对同一批pair判断是否清晰可分凡是两个人意见不一致的pair直接丢弃这是性价比最高的数据处理策略。第四如果追求极致效果可以尝试两阶段先做一轮低β的DPO把模型从SFT基线拉向偏好方向然后以第一轮DPO的输出作为新的参考模型再做一轮高β调整。这种方式比一次性训练更长更稳定也方便观察每一轮优化的实际影响。DPO这篇论文我推荐每个做大模型的人精读不是因为它完美而是因为它把“对齐”从强化学习独有的贵重玩具变成了一个普通人也能上手的标准化工具。它不简单但它成功地把顶级复杂系统中的一件大事拆成了所有人都看得懂的一步。就冲这一点这篇论文值得反复读。
返回列表