Llama-factory 详细学习笔记:第六章:DPO (直接偏好优化) 实战 (难点)
本文介绍了直接偏好优化(DPO)作为传统强化学习(RLHF)的替代方案。DPO通过简化训练流程,只需在监督微调(SFT)后直接优化模型偏好,避免了复杂的PPO训练环节。其核心思想是利用偏好数据隐式定义奖励,通过分类损失函数直接优化模型输出。相比RLHF,DPO具有训练简单、内存友好、稳定性高等优势。文章详细说明了DPO的数据准备、关键配置参数及实践意义,特别是参考模型和dpo_beta参数的作用。
第六章:DPO (直接偏好优化) 实战 (难点)
在SFT之后,我们的模型学会了“说话”,但它的回答可能仍然是“正确的废话”,或者在面对开放性问题时,其回答的安全性、有用性和真实性仍有待提高。传统的解决方案是强化学习(RLHF),即先训练一个奖励模型(RM),再用这个RM作为环境,通过复杂的强化学习算法(如PPO)来优化语言模型。然而,RLHF流程复杂、训练不稳定、且对计算资源要求极高,令许多开发者望而却步。
直接偏好优化 (Direct Preference Optimization, DPO) 的出现,如同一道曙光,彻底改变了这一局面。它以一种极其优雅和高效的方式,实现了与RLHF相媲美甚至更好的对齐效果,但训练成本和复杂度却大大降低。本章将深入剖析DPO的核心思想、重难点配置,并通过详尽的实战步骤,带你完整地跑通一个DPO训练流程,真正让你的模型“更懂人心”。
6.1 为什么需要 DPO? (轻理论:替代 PPO,让模型更符合人类偏好)
要理解DPO的革命性,我们首先要明白传统RLHF的痛点。
- 传统RLHF的四步“炼丹法” (复杂且昂贵):
- SFT阶段:先对基座模型进行监督微调,得到一个能对话的基础策略模型 (SFT Model)。
- RM阶段:收集人类偏好数据
(prompt, chosen_response, rejected_response),训练一个奖励模型 (Reward Model),让它学会给回答打分。 - PPO阶段 (瓶颈):这是最复杂的一步。需要同时在GPU内存中加载四个模型:
- 正在训练的策略模型 (Policy Model)。
- 用于计算奖励分数的奖励模型 (Reward Model)。
- 一个SFT模型的冻结副本,用于计算KL散度惩罚,防止模型“走火入魔”。
- 一个用于生成经验的生成模型(有时与策略模型是同一个)。
这个阶段通过PPO算法,让策略模型生成回答,奖励模型打分,然后根据分数更新策略模型,整个过程涉及大量的采样和复杂的超参数调试,非常不稳定。
- 评估阶段:对优化后的模型进行人工或自动评估。
PPO 训练实战(可选,用于对比)
为了让你更直观地感受 PPO 训练的复杂性,我们在这里提供一个使用 Llama-factory 进行 PPO 训练的命令示例。请注意,运行此步骤的前提是你已经完成了 SFT 阶段和 RM 阶段的训练。
-
PPO 训练的前提:
- SFT 模型: 假设我们已有一个训练好的 SFT 适配器,存放在
saves/Qwen1.5-7B/lora/sql_generator_v1。 - RM 模型: 假设我们已有一个训练好的奖励模型适配器,存放在
saves/Qwen1.5-7B/lora/sql_reward_model_v1。 - PPO 数据: PPO 训练时,通常只需要一个包含一系列 prompt(指令)的数据集即可,模型会根据这些 prompt 生成回答,并由奖励模型打分。假设我们有一个
ppo_prompts.jsonl文件,并已注册为sql_ppo_prompts。
- SFT 模型: 假设我们已有一个训练好的 SFT 适配器,存放在
-
PPO 训练脚本 (
train_ppo_sql.sh):#!/bin/bash export CUDA_VISIBLE_DEVICES="0" llamafactory-cli train \ --stage ppo \ --do_train \ --model_name_or_path Qwen/Qwen1.5-7B-Chat \ --adapter_name_or_path saves/Qwen1.5-7B/lora/sql_generator_v1 \ --reward_model saves/Qwen1.5-7B/lora/sql_reward_model_v1 \ --dataset sql_ppo_prompts \ --template default \ --finetuning_type lora \ --lora_rank 8 \ --lora_alpha 16 \ --lora_target all \ --output_dir saves/Qwen1.5-7B/lora/sql_ppo_v1 \ --per_device_train_batch_size 1 \ --gradient_accumulation_steps 4 \ --lr_scheduler_type cosine \ --logging_steps 5 \ --save_steps 100 \ --learning_rate 1e-5 \ --num_train_epochs 1.0 \ --plot_loss \ --fp16 -
脚本核心参数解读 (与 DPO 对比):
--stage ppo: 切换到 PPO 模式。这是启动 PPO 训练的开关。--adapter_name_or_path ...: 指定 SFT 模型的适配器,它将作为 PPO 训练的初始策略模型。--reward_model ...: 这是 PPO 独有的关键参数。你需要明确提供一个已经训练好的奖励模型 (RM) 的路径。在训练过程中,这个 RM 会被加载进来,用于给策略模型生成的回答打分,从而产生梯度信号。- 内存消耗:请注意,这个命令启动后,除了策略模型和其 SFT 参考模型外,还需要额外加载一个奖励模型。这就是我们在理论部分提到的 PPO 资源消耗大的原因之一。
- 超参数:PPO 还有许多独特的超参数,如
kl_coeff(功能类似 DPO 的beta)、ppo_score_norm等,这些参数的调试通常比 DPO 更为复杂和敏感,进一步增加了训练的不稳定性。
通过这个具体的训练命令,我们可以清晰地看到,相较于 DPO,PPO 流程不仅多了一个训练 RM 的步骤,而且在最终的对齐训练阶段,配置也更为复杂,需要管理的模型和路径更多。
-
DPO的“神来之笔”:
DPO的作者们通过精妙的数学推导发现,整个复杂的PPO优化目标,可以等价地转换为一个简单的、类似SFT的分类损失函数。核心思想 (高度简化):
DPO认为,我们不需要一个显式的奖励模型。偏好数据(chosen, rejected)本身就隐式地定义了奖励。DPO的损失函数直接利用这一点,其目标是:增大模型生成chosen回答的概率,同时减小生成rejected回答的概率。更具体地说,DPO通过计算模型对
chosen和rejected回答的对数概率差,并将其与一个由参考模型(即SFT模型)计算出的隐式奖励进行对比,构建了一个简单的损失函数。训练过程就是最小化这个损失,直接将偏好“注入”到模型中。 -
DPO的压倒性优势:
| 特性 | 传统RLHF (PPO) | 直接偏好优化 (DPO) |
|---|---|---|
| 训练流程 | 极其复杂 (SFT -> RM -> PPO) | 极其简单 (SFT -> DPO) |
| 模型需求 | 训练时需加载多个模型,内存占用巨大 | 训练时只需加载策略模型和参考模型,内存友好 |
| 稳定性 | PPO训练不稳定,对超参数敏感,容易崩溃 | 非常稳定,本质上是监督学习,收敛性好 |
| 效率 | 采样过程耗时,训练速度慢 | 无需采样,训练速度远快于PPO |
| 效果 | 效果好,但调试成本高 | 效果与PPO相当甚至更好,且实现简单 |
结论: DPO以其简洁、稳定、高效的特性,已成为当今大模型对齐的首选方案,极大地降低了让模型更符合人类偏好的技术门槛。
6.2 DPO 的数据准备 (重难点)
DPO在数据层面延续了它的优雅——它直接复用RM阶段的偏好数据,无需任何额外处理。这是DPO流程简洁性的关键所在。
复用 RM 的数据 (Query, chosen, rejected)
-
数据格式: DPO训练所需的数据格式与第五章介绍的奖励建模 (RM) 完全一致。每一条数据样本都是一个JSON对象,包含一个指令 (
instruction/input),以及一个包含两个元素的output列表,分别代表“更受欢迎的回答”(chosen)和“不太受欢迎的回答”(rejected)。 -
实践示例:
假设我们有一个名为dpo_data.jsonl的文件:{ "instruction": "作为一名旅行规划师,请为一对希望进行为期一周的浪漫海岛游的夫妇推荐一个目的地,并简述理由。", "output": [ { "content": "我强烈推荐马尔代夫。那里有一流的水上别墅,提供极致的私密性和奢华体验。你们可以享受清澈的潟湖、白色的沙滩和丰富的水下活动,是蜜月和浪漫之旅的完美选择。" }, { "content": "去马尔代夫吧,那儿不错。" } ] } { "instruction": "如何修复一个漏水的水龙头?", "output": [ { "content": "修复漏水水龙头通常分几步:1. 关闭总水阀。2. 用扳手拧开水龙头手柄下的压盖螺母。3. 取出内部的垫圈或O形圈,这是最常见的磨损部件。4. 更换新的垫圈后,按相反顺序重新组装。如果问题依旧,可能需要更换整个阀芯。" }, { "content": "你得先关掉水,然后把它拆开,换掉坏了的零件,再装回去。" } ] }在这个例子中,
chosen的回答明显比rejected的回答更详细、更专业、更有用。DPO的目标就是让模型学会这种“品味”。
--dataset 参数的设置
-
注册
dataset_info.json: 与RM阶段一样,你必须在data/dataset_info.json中注册你的DPO数据集,并且必须设置"ranking": true来告诉Llama-factory这是一个偏好数据集。// in data/dataset_info.json { // ... other datasets "my_dpo_dataset": { "file_name": "dpo_data.jsonl", "ranking": true } } -
在CLI或Web UI中指定:
- CLI:
--dataset my_dpo_dataset - Web UI: 在“数据集”下拉菜单中选择
my_dpo_dataset。
- CLI:
数据质量是DPO成功的基石。高质量的偏好对应该具备:
- 清晰的偏好差异:
chosen和rejected之间的优劣应该是一目了然的。如果两个回答质量相近,会让模型感到困惑。 - 多样性: 覆盖多样的指令类型、主题和偏好维度(如事实性、安全性、创造性、详细程度等)。
chosen回答的质量:chosen回答本身也应该是高质量的。如果chosen回答也存在事实错误,模型在学习偏好的同时也会学到这些错误。
6.3 DPO 的训练配置 (重难点)
这是本章最核心、最关键的部分。DPO的配置有其独特性,理解这些配置是成功进行DPO训练的前提。
关键: DPO 训练需要 两个 模型 (训练中的模型 + SFT 后的参考模型)
正如6.1节理论部分所述,DPO的损失函数中,需要一个固定的参考模型 (Reference Model) 来衡量当前策略模型 (Policy Model) 的变化程度,以防止其偏离原始SFT模型太远。
-
策略模型 (Policy Model):
- 角色: 这是我们正在训练和优化的模型。它的参数会在每个训练步骤中被更新。
- 起点: 策略模型的初始状态,必须是已经完成SFT的模型。我们不能用一个原始的基座模型来做DPO,因为它首先需要具备基本的指令跟随能力。
-
参考模型 (Reference Model):
- 角色: 这是一个权重被冻结、不参与训练的模型副本。它只用于在前向传播中计算
chosen和rejected回答的对数概率,为损失函数提供一个基准。 - 来源: 参考模型就是SFT模型本身。
- 角色: 这是一个权重被冻结、不参与训练的模型副本。它只用于在前向传播中计算
-
Llama-factory 中的实现机制:
Llama-factory 的设计非常巧妙,它简化了这个“双模型”的配置:- 你通过
--model_name_or_path参数,加载你已经训练好的SFT模型。 - Llama-factory 在内部会用这个路径加载两次模型:
- 一次作为策略模型,并附加LoRA权重(如果你用LoRA的话),使其变为可训练状态。
- 另一次作为参考模型,并将其权重完全冻结。
- 这样,你就无需手动管理两个模型的加载,框架会自动处理。
- 你通过
--dpo_beta 参数的实践意义
dpo_beta 是DPO训练中最重要的一个超参数。
-
是什么?
beta是DPO损失函数中,控制KL散度惩罚项权重的系数。KL散度衡量的是策略模型和参考模型在输出概率分布上的差异。 -
实践意义 (调参关键):
beta的值决定了DPO训练的**“保守”程度**。- 较低的
beta(如0.01,0.05):- 含义: 对KL散度的惩罚较弱。模型会更“大胆”地去拟合偏好数据,即更专注于拉开
chosen和rejected的概率差距。 - 优点: 可能在你的目标偏好上学得更充分,模型风格变化更明显。
- 风险: 容易过拟合偏好,导致模型忘记了SFT阶段学到的通用语言能力。可能会生成一些符合偏好但重复、啰嗦或不自然的文本,这就是所谓的“KL塌陷”。
- 含义: 对KL散度的惩罚较弱。模型会更“大胆”地去拟合偏好数据,即更专注于拉开
- 较高的
beta(如0.5,1.0):- 含义: 对KL散度的惩罚很强。模型在学习偏好的同时,被一股强大的力量“拽”着,不允许它离SFT参考模型太远。
- 优点: 训练更稳定,能很好地保持SFT模型的通用性和语言风格,在其基础上进行“微调”。
- 风险: 可能学习偏好不充分,
chosen和rejected之间的差异不够明显,模型提升有限。
- 较低的
-
如何选择?
- 黄金起点: 原始DPO论文和大量实践证明,
beta = 0.1是一个极其鲁棒和优秀的默认值。对于绝大多数任务,你都应该从0.1开始。 - 调参建议:
- 如果DPO训练后,你发现模型在偏好任务上表现很好,但通用对话能力下降,或者说话变得很奇怪,尝试增大
beta(如0.2or0.3)。 - 如果DPO训练后,你感觉模型和SFT版本相比没什么变化,提升不明显,可以尝试减小
beta(如0.05),让它学得更“激进”一些。
- 如果DPO训练后,你发现模型在偏好任务上表现很好,但通用对话能力下降,或者说话变得很奇怪,尝试增大
- 黄金起点: 原始DPO论文和大量实践证明,
6.4 实战:跑通一个 DPO 训练
现在,我们将所有理论知识串联起来,一步步完成一个完整的DPO流程。我们的目标是,在第五章SFT训练出的SQL生成模型的基础上,进一步通过DPO让它更偏爱格式规范、带有注释的SQL语句。
步骤 1:先 SFT 一个基础模型
这是DPO的绝对前提。我们必须先有一个具备基础能力的SFT模型。我们复用第五章的SFT脚本,并假设其输出保存在 saves/Qwen1.5-7B/lora/sql_generator_v1。
- SFT脚本 (
train_sft_for_dpo.sh):#!/bin/bash # (此脚本已在第五章运行过,这里仅作回顾) llamafactory-cli train \ --stage sft \ --do_train \ --model_name_or_path Qwen/Qwen1.5-7B-Chat \ --dataset sql_gen_custom \ --finetuning_type lora \ --output_dir saves/Qwen1.5-7B/lora/sql_generator_v1 \ # ... 其他SFT参数 ... ```**关键产物**: `saves/Qwen1.5-7B/lora/sql_generator_v1` 文件夹,包含了LoRA适配器权重。
步骤 2:使用该 SFT 模型作为起点来启动 DPO
现在,我们进入DPO阶段。
-
DPO数据准备:
假设我们创建了dpo_sql_preference.jsonl,内容如下,并已注册为sql_dpo_custom。{ "instruction": "查询'用户表'中所有年龄大于30岁的用户的姓名和邮箱。", "output": [ { "content": "-- 查询30岁以上用户的核心信息\nSELECT \n name, \n email \nFROM \n user_table \nWHERE \n age > 30;" }, { "content": "SELECT name, email FROM user_table WHERE age > 30;" } ] }这里,
chosen的回答包含了注释和更好的格式,是我们希望模型学习的偏好。 -
DPO训练脚本 (
train_dpo_sql.sh):#!/bin/bash export CUDA_VISIBLE_DEVICES="0" llamafactory-cli train \ --stage dpo \ --do_train \ --model_name_or_path Qwen/Qwen1.5-7B-Chat \ --adapter_name_or_path saves/Qwen1.5-7B/lora/sql_generator_v1 \ --dataset sql_dpo_custom \ --template default \ --finetuning_type lora \ --lora_rank 8 \ --lora_alpha 16 \ --lora_target all \ --output_dir saves/Qwen1.5-7B/lora/sql_dpo_v1 \ --per_device_train_batch_size 1 \ --gradient_accumulation_steps 4 \ --lr_scheduler_type cosine \ --logging_steps 5 \ --save_steps 100 \ --learning_rate 1e-5 \ --num_train_epochs 1.0 \ --plot_loss \ --fp16 \ --dpo_beta 0.1 -
脚本核心参数解读 (重中之重):
--stage dpo: 切换到DPO模式。这是启动DPO训练的开关,Llama-factory会自动加载DPO Trainer和对应的损失函数。--model_name_or_path Qwen/Qwen1.5-7B-Chat: 注意!这里依然是原始的基座模型。--adapter_name_or_path saves/Qwen1.5-7B/lora/sql_generator_v1: 这才是DPO的真正起点。Llama-factory会先将这个SFT阶段训练好的LoRA适配器加载到基座模型上,形成完整的SFT模型。然后,以此为基础,创建策略模型和参考模型。--learning_rate 1e-5: DPO的学习率通常需要设置得比SFT更小。因为偏好对齐是一个更精细的微调过程。1e-5到5e-6是一个很好的范围。--dpo_beta 0.1: 设置核心超参数beta为推荐的默认值0.1。--output_dir: DPO训练产物的输出目录,不要和SFT的目录混淆。
-
监控训练过程:
当你运行此脚本后,观察终端输出的日志。除了loss之外,你应该重点关注以下几个指标:rewards/chosen: 策略模型赋予chosen回答的平均奖励分数。rewards/rejected: 策略模型赋予rejected回答的平均奖励分数。rewards/accuracy: 关键指标。表示在当前batch中,模型正确地给予chosen回答比rejected回答更高分数的样本比例。一个健康的DPO训练,这个值应该稳定在50%以上,并逐渐提升,通常能达到70%~90%。rewards/margins:chosen和rejected奖励分数的平均差值。这个值应该是正数并逐渐增大,表示模型正在成功地拉开好坏回答的差距。
至此,你已经完整地掌握了从理论到实践的DPO全流程。通过先SFT再DPO的两步走策略,你可以将一个通用的语言模型,先塑造成一个特定领域的“专家”,再进一步教会它“品味”,使其回答不仅“正确”,而且“优秀”,更符合人类的期望和偏好。下一章,我们将讨论如何将我们辛辛苦苦训练出的模型(无论是SFT还是DPO的产物)真正地应用起来,包括模型合并、部署推理和评估。
Llama-factory 详细学习笔记 目录
以下是整个系列的8章目录,点击章节标题即可跳转阅读,可直接访问:
- Llama-factory 详细学习笔记 第一章:环境搭建与“Hello World” (入门与排错)
- Llama-factory 详细学习笔记 第二章:数据准备(决定成败的关键)
- Llama-factory 详细学习笔记 第三章:核心配置项详解(Web UI 篇)
- Llama-factory 详细学习笔记 第四章:命令行(CLI) 训练实战
- Llama-factory 详细学习笔记 第五章:SFT 与RM 微调实践
- Llama-factory 详细学习笔记 第六章:DPO (直接偏好优化) 实战(难点)
- Llama-factory 详细学习笔记 第七章:模型评估、合并与推理(应用落地)
- Llama-factory 详细学习笔记 第八章:常见疑难杂症(Troubleshooting) 与进阶技巧
更多推荐



所有评论(0)