Re0-03: HuggingFace TRL DPOTrainer
配套代码:
dpotrainer.py
承上启下
前两章已经完成:
- 第一章:使用原生 Trainer 进行 SFT,梳理 Loss Masking 的基本概念
- 第二章:使用 SFTTrainer 简化流程,实现了自动化的 SFT 训练
通过 SFT,模型学会了:
- 按照指令格式回答问题
- 生成符合语法的响应
- 理解用户的意图
但是,SFT 的局限在于:它让模型学习”如何回答”,却不直接区分”什么样的回答更好”。
本章介绍 DPO(Direct Preference Optimization)——一种让模型学习人类偏好的训练方法。若要系统理解 DPO 的数学理论,仍应阅读原文;下面只保留必要推导。
本章学习目标
本章覆盖:
- 偏好对齐 的概念和价值
- DPO 与传统 RLHF 的区别
- 偏好数据集 的格式和构建
- DPOTrainer 的使用方法
- 参考模型 的作用和配置
1. 为什么需要偏好对齐?
1.1 SFT 的局限性
SFT 无法区分回答质量
用户提问: “如何提高编程效率?”
SFT 模型可能的回答 A(更好):
提高编程效率可以通过以下方式:
- 学习快捷键
- 使用代码片段
- 定期休息
SFT 模型可能的回答 B(较差):
多写代码就行了。
问题: 两个回答语法都正确,但 A 明显更详细、更实用!SFT 无法区分回答的”质量”差异。
1.2 偏好对齐的目标
让模型学习:给定相同的问题,哪个回答更符合人类偏好?
1 | 偏好数据格式: |
2. RLHF vs DPO
2.1 传统 RLHF (PPO) 流程
传统 RLHF (PPO) 流程
- 收集偏好数据 → 人类标注者标记 chosen vs rejected
- 训练奖励模型 → 用偏好数据训练rewarder模型,为了实现baseline保证梯度稳定,还需要构建一个value模型,从而导致大量的显存开销
- PPO 优化策略 → 从策略模型采样,用奖励模型评分,更新策略
需要 4 个模型: 策略、参考、奖励、价值模型
问题:
- 流程复杂,需要多个模型
- 训练不稳定,需要大量调参
- 计算成本高(需要采样生成)
2.2 DPO:简化的偏好对齐
DPO 流程
- 收集偏好数据 → 与 RLHF 相同
- 直接优化策略 ← 主要改动
只需 2 个模型: 策略 + 参考模型
优势:
- 流程简单,易于实现
- 训练稳定,无需复杂调参
- 计算效率高
- 代码逻辑接近 SFT,仍旧是去优化损失
2.3 数学原理(从直觉到公式)
2.3.1 RLHF 的核心目标
在传统 RLHF 中,我们的目标很简单:让模型更倾向于生成人类偏好的回答。
用数学语言说,对于一组偏好数据 (其中 是更好的回答, 是较差的回答),我们希望:
即:模型应该认为 chosen 回答比 rejected 回答更好。
2.3.2 传统 RLHF 的做法:训练奖励模型
为了实现这个目标,传统 RLHF 分两步:
第一步:训练奖励模型
奖励模型的作用是给每个回答打分。我们用偏好数据训练它,使得:
第二步:用强化学习优化策略
使用 PPO 等算法,调整策略模型 的参数,让它生成的回答能获得更高奖励:
问题:这需要训练一个独立的奖励模型,还要用复杂的强化学习算法,流程繁琐且不稳定。
2.3.3 DPO 的洞察:奖励函数可以解析表达
DPO 的主要思路是:如果我们知道了最优策略的奖励函数形式,就可以绕过奖励模型,直接优化策略。
关键定理:在 RLHF 设置下,最优策略的奖励函数可以表示为:
其中:
- = 最优策略(我们想学到的策略)
- = 参考策略(SFT 后的模型,作为起点)
- = 温度参数,控制偏离程度
- = 归一化常数(与 无关,优化时可忽略)
直觉理解:
这个公式告诉我们:奖励的大小取决于策略模型相对于参考模型的”偏好变化”。
- 如果 相比 更倾向于生成某个回答 → 该回答的奖励高
- 如果 相比 更不倾向于生成某个回答 → 该回答的奖励低
2.3.4 DPO 的优化目标
有了奖励函数的表达式,我们可以直接写出优化目标。
RLHF 的目标(用奖励模型):
其中 是 sigmoid 函数。这个损失鼓励 。
DPO 的目标(用策略模型):
将奖励函数的表达式代入,消去 ,得到:
简化后:
这个损失的含义:
- 分子(我们想要的): 要大, 是基准
- 分母(我们要避免的): 要小, 是基准
训练时,我们通过梯度上升最大化这个损失,使得:
- 相比参考模型上升 → 模型更倾向于生成 chosen 回答
- 相比参考模型下降 → 模型更不倾向于生成 rejected 回答
2.3.5 为什么需要参考模型?
你可能会问:为什么不能直接最大化 ,最小化 ?
问题:这样会导致模型崩溃(Mode Collapse)
如果没有参考模型约束,模型可能学会:
- 对 chosen 回答输出极高概率(接近 1)
- 对 rejected 回答输出极低概率(接近 0)
但这会让模型:
- 忘记 SFT 阶段学到的知识
- 生成奇怪、不自然的回答
- 失去泛化能力
参考模型的作用:
就像一个"锚点",确保:- 不能随意偏离原始模型
- 只在偏好对齐的方向上调整
- 保持模型的语言能力和稳定性
数学上,这通过 KL 散度正则化实现:
DPO 的损失函数中已经隐式包含了 KL 约束(通过 参数控制)。
2.3.6 总结:DPO vs RLHF
| 方面 | 传统 RLHF (PPO) | DPO |
|---|---|---|
| 第一步 | 训练奖励模型 | [否] 不需要 |
| 第二步 | 用强化学习优化策略 | [是] 直接优化策略 |
| 优化目标 | 最大化 | 最大化 |
| 参考模型 | 用于 KL 正则化 | 直接嵌入损失函数 |
| 计算复杂度 | 需要采样生成,计算量大 | 类似 SFT,计算高效 |
主要优势:DPO 将强化学习问题转化为了监督学习问题,不需要训练奖励模型,不需要复杂的 RL 算法,代码实现接近 SFT。
3. 偏好数据集格式
3.1 标准格式
1 | # 格式1: 简单格式 |
3.2 常用偏好数据集
| 数据集 | 描述 | 大小 |
|---|---|---|
trl-lib/ultrafeedback_binarized |
TRL 官方提供 | ~60k |
Anthropic/hh-rlhf |
Anthropic 的有用+无害数据 | ~170k |
argilla/ultrafeedback-binarized-preferences-cleaned |
清洗后的 UltraFeedback | ~60k |
3.3 自建偏好数据集
1 | # 偏好数据的构建方式: |
4. DPOTrainer 使用指南
4.1 基本配置
1 | from trl import DPOConfig, DPOTrainer |
4.2 关键参数:beta
beta 是 DPO 最重要的超参数:
1 | beta 的作用:控制模型偏离参考模型的程度 |
4.3 损失函数类型
DPO 的重点在于如何将强化学习对齐转换为损失函数优化,因此有很多衍生形式,可以通过这里进行选择。
1 | loss_type = "sigmoid" # 默认,标准 DPO |
4.4 创建 DPOTrainer
1 | from trl import DPOTrainer |
5. 参考模型的处理
5.1 参考模型的作用
DPO 需要两个模型:
策略模型
- 我们要训练的模型
- 参数会被更新
参考模型
- 用于计算 KL 散度正则化
- 通常是 SFT 后的模型副本
- 参数冻结,不更新
5.2 三种参考模型策略
flowchart TB
subgraph Strategies["参考模型配置策略"]
direction TB
subgraph S1["策略1: 自动创建 推荐"]
S1_Code["ref_model = None"]
S1_F1["→ DPOTrainer 自动创建参考模型副本"]
S1_F2["→ 使用 PEFT 时,会卸载适配器进行参考推理"]
S1_F3["→ 最省显存"]
S1_Code --> S1_F1
S1_F1 --> S1_F2
S1_F2 --> S1_F3
end
subgraph S2["策略2: 手动创建"]
S2_Code["ref_model = AutoModelForCausalLM.from_pretrained(...)"]
S2_F1["→ 完全独立的模型副本"]
S2_F2["→ 显存占用翻倍"]
S2_Code --> S2_F1
S2_F1 --> S2_F2
end
subgraph S3["策略3: 适配器切换 (使用 PEFT 时)"]
S3_F1["→ 同一个基础模型加载两个适配器"]
S3_F2["→ 训练时切换适配器"]
S3_F3["→ 显存占用较低"]
S3_F1 --> S3_F2
S3_F2 --> S3_F3
end
S1 -.-> S2
S2 -.-> S3
end
5.3 代码示例
1 | # 策略1: 自动创建(推荐) |
6. 训练监控指标
6.1 关键指标
| 指标 | 含义 | 期望趋势 |
|---|---|---|
rewards/chosen |
chosen 回答的平均奖励 | 上升 |
rewards/rejected |
rejected 回答的平均奖励 | 下降 |
rewards/margins |
chosen - rejected 的差值 | 上升 |
rewards/accuracies |
chosen 奖励 > rejected 奖励的比例 | 接近 1.0 |
6.2 监控示例
1 | 训练日志: |
7. DPO 与 SFT 的训练流程对比
完整训练流程对比
| 阶段 | SFT (第一、二章) | DPO (本章) |
|---|---|---|
| 目标 | 让模型学会指令遵循 | 让模型学会偏好 |
| 数据 | (prompt, response) 二元对 | (prompt, chosen, rejected) 三元组 |
| 结果 | 能回答问题,但质量不一定最优 | 回答质量显著提升 |
| 时间 | 1-3 epochs | 1-3 epochs |
| 顺序 | 先进行 | 在 SFT 之后进行 |
重要: DPO 应该在 SFT 之后进行!
8. 运行代码
8.1 配置参数
在 dpotrainer.py 中可以修改:
1 | MODEL_NAME = "Qwen/Qwen3-8B" # 模型 |
8.2 运行训练
1 | python dpotrainer.py |
8.3 评估训练效果
训练后,观察:
rewards/margins是否在上升rewards/accuracies是否接近 1.0- 生成测试:模型回答是否更符合预期
9. 本章小结
| 概念 | 说明 |
|---|---|
| 偏好对齐 | 让模型学习”什么回答更好” |
| DPO | 直接偏好优化,简化的 RLHF 替代方案 |
| 偏好数据 | (prompt, chosen, rejected) 三元组 |
| 参考模型 | 用于 KL 正则化,防止模型偏离太远 |
| beta | 控制保守/激进程度的关键超参数 |
10. DPO 的局限性与展望
10.1 DPO 的局限性
依赖离线数据
- 只能使用预先收集的偏好数据
- 无法在线探索和学习
奖励函数隐式化
- DPO 将奖励函数通过数学变换隐藏了
- 无法直接控制奖励信号
- 难以实现复杂的奖励设计
只适用于偏好对齐
- 无法处理需要明确奖励信号的任务
- 如:数学推理、代码生成等需要可验证正确性的任务
10.2 下一步:GRPO 和可验证奖励
问题:如果我们的任务有明确的正确/错误判断(如数学题),能否设计更好的奖励?
答案:可以!这就是 GRPO 等高级 RL 方法的用武之地。
附录:常见问题
Q: DPO 训练后模型变差了?
A: 1) 检查 beta 值,可能太小导致过度偏离 2) 检查偏好数据质量 3) 减少训练 epochsQ: rewards/accuracies 一直不提升?
A: 1) 检查数据格式是否正确 2) 尝试增大学习率 3) 确保 chosen 确实比 rejected 好Q: 显存不够怎么办?
A: 1) 使用ref_model=None让 DPOTrainer 自动处理 2) 减小 batch_size 3) 启用 gradient_checkpointing
参考资料
- Title: Re0-03: HuggingFace TRL DPOTrainer
- Author: Hyacehila
- Created at : 2025-12-29 14:00:00
- Link: https://hyacehila.github.io//blog/2025/12/29/Re0HF-03/
- License: This work is licensed under CC BY-NC-SA 4.0.