5. 偏好对齐
这是什么:偏好对齐(preference alignment)= 让模型不只会「答对」,还更会「答得好」。DPO(Direct Preference Optimization,直接偏好优化)是其中最省事的做法。
为什么用:上一章的 SFT 只能教模型「标准答案长这样」,教不了它「两个都对的答案里哪个更好」。语气、礼貌、拒答边界这些没标准答案的东西,全靠偏好数据来教。
如何用:本章先横向对比几种对齐方式,再写出偏好数据的格式,最后用官方 qwen3_lora_dpo.yaml 跑一次 DPO。
5.1 为什么 SFT 之后还要再来一步
SFT 的教材是「一问一答」,答案唯一。现实里很多问题没有唯一答案:
问:帮我写封辞职邮件
A 版:语气生硬,像通知
B 版:礼貌专业,留有余地
两个都「对」,但显然 B 更好。这类偏好用 SFT 学不了,因为 SFT 只会把 A 版当成唯一正确答案学进去。
偏好对齐补的正是这一课:给它两个答案,告诉它哪个更好,让模型朝好的那个方向挪。
5.2 各种对齐方式怎么选
| 方式 | 配置写法 | 需要什么 | 特点 |
|---|---|---|---|
| RM + PPO | stage: rm → stage: ppo |
偏好对 + 一个奖励模型 | 经典 RLHF,效果好但流程复杂、算力贵 |
| DPO | stage: dpo + pref_loss: sigmoid |
偏好对 | 官方推荐的主流选择,隐式奖励模型,不用额外训 RM |
| ORPO | stage: dpo + pref_loss: orpo |
偏好对 | 单模型一次训完,不需要参考模型 |
| SimPO | stage: dpo + pref_loss: simpo |
偏好对 | DPO 简化版,去掉参考模型,长度归一化 |
| KTO | stage: kto |
好/坏标签 | 只要标「好」或「坏」,不需要成对 |
几个关键区别:
- DPO 虽然不用单独训一个奖励模型,但训练时仍然要加载一个冻结的参考模型(原始基座)做对照。代码里
use_ref_model在stage: dpo且pref_loss不是orpo/simpo时才为真。 - ORPO / SimPO 把参考模型去掉了,显存省一半,流程也更简单,代价是效果通常略逊于 DPO。
- KTO 走的是另一条路:只要告诉模型「这条回答是好是坏」,不需要成对的 chosen/rejected,标注成本低得多。
pref_loss在代码里的完整取值是sigmoid/hinge/ipo/kto_pair/orpo/simpo,sigmoid就是标准 DPO。
5.3 偏好数据长什么样
偏好数据要在 dataset_info.json 里加 "ranking": true,框架才知道要按偏好数据来读。
Alpaca 风格(扁平,好坏回答各是一个字符串):
[
{
"instruction": "帮我写封辞职邮件",
"input": "",
"chosen": "您好,感谢您这段时间的付出。我想申请离职,方便的话想约个时间交接工作。",
"rejected": "我不干了,以后别找我。"
}
]
ShareGPT 风格(官方 demo 用的就是这种):
[
{
"conversations": [
{"from": "human", "value": "Hi! I'd like to create a new language game."}
],
"chosen": {"from": "gpt", "value": "That sounds like a fun idea! Here are some tips..."},
"rejected": {"from": "gpt", "value": "I don't understand. Go away."}
}
]
对应的注册说明:
{
"my_pref_demo": {
"file_name": "my_pref_demo.json",
"ranking": true,
"formatting": "sharegpt",
"columns": {
"messages": "conversations",
"chosen": "chosen",
"rejected": "rejected"
}
}
}
chosen 和 rejected 要针对同一个 prompt。质量比数量重要:1000 条干净的偏好对,好过 10 万条随手编的。
5.4 DPO 配置逐项
官方示例 examples/train_lora/qwen3_lora_dpo.yaml 的关键部分:
### method
stage: dpo
do_train: true
finetuning_type: lora
lora_rank: 8
lora_target: all
pref_beta: 0.1
pref_loss: sigmoid # choices: [sigmoid (dpo), orpo, simpo]
### dataset
dataset: dpo_en_demo
template: qwen3_nothink
cutoff_len: 2048
max_samples: 1000
### output
output_dir: saves/qwen3-4b/lora/dpo
### train
per_device_train_batch_size: 1
gradient_accumulation_steps: 8
learning_rate: 5.0e-6
num_train_epochs: 3.0
bf16: true
跟 SFT 配置最大的不同有三点:
| 项 | SFT | DPO | 为什么 |
|---|---|---|---|
learning_rate |
1.0e-4 |
5.0e-6 |
DPO 要在原模型附近小幅挪动,学习率必须小一个数量级 |
stage |
sft |
dpo |
换成偏好阶段 |
pref_beta |
— | 0.1 |
KL 约束强度,越大越保守、越不容易跑偏 |
跑起来:
llamafactory-cli train examples/train_lora/qwen3_lora_dpo.yaml
你会看到(示意):
[INFO] Fine-tuning method: LoRA
[INFO] Number of trainable parameters: 16,515,072
{'rewards/chosen': 0.83, 'rewards/rejected': 0.31, 'rewards/accuracies': 0.91, 'loss': 0.42}
rewards/accuracies 是最关键的指标:它表示「模型在多少比例的样本上确实认为 chosen 比 rejected 好」。健康值是 0.5~0.7 起步并往 0.8 以上走;如果一直卡在 0.5,说明模型压根没学到,可能是偏好数据太弱或者学习率太小。
5.5 换成 ORPO、SimPO、KTO
ORPO / SimPO 只改一个字段:
stage: dpo
pref_loss: orpo # 或 simpo
注意 stage 仍然是 dpo——代码里 stage 没有 orpo 这个取值,ORPO 和 SimPO 是靠 pref_loss 区分的。
KTO 换 stage 和数据集:
### method
stage: kto
do_train: true
finetuning_type: lora
lora_rank: 8
lora_target: all
pref_beta: 0.1
### dataset
dataset: kto_en_demo
KTO 的数据要多一个 kto_tag 字段(true 表示好,false 表示坏):
[
{
"conversations": [
{"from": "human", "value": "帮我写封辞职邮件"},
{"from": "gpt", "value": "您好,感谢您这段时间的付出……"}
],
"kto_tag": true
}
]
注册说明要写成:
{
"my_kto_demo": {
"file_name": "my_kto_demo.json",
"formatting": "sharegpt",
"columns": {
"messages": "conversations",
"kto_tag": "kto_tag"
}
}
}
奖励模型(RM) 则用 stage: rm,喂的也是偏好对,产出的是一个「打分器」:
stage: rm
dataset: dpo_en_demo
output_dir: saves/qwen3-4b/lora/reward
训完的 RM 再喂给 stage: ppo 做强化学习,就是完整的经典 RLHF 流程——但这需要同时在线跑四个模型(策略、参考、奖励、价值),个人机器很难扛,除非你有明确的理由,否则优先选 DPO。
动手改
- 把
pref_beta从0.1改成0.5再训一次,对比rewards/accuracies有什么变化 - 把
pref_loss改成orpo跑一遍,感受一下「不需要参考模型」在显存和速度上的差别 - 找一份公开的 UltraFeedback 数据(
dataset: ultrafeedback),注册好之后训一次,和 demo 数据比一比 accuracies
小结
- SFT 教「标准答案」,偏好对齐教「哪个更好」,两者互补
- DPO 是主流选择:
stage: dpo+pref_loss: sigmoid,不需要单独训奖励模型 - ORPO / SimPO 是
pref_loss的取值,不是独立的 stage;KTO 才用stage: kto - 偏好数据要
"ranking": true,ShareGPT 风格用chosen/rejected两个对象,KTO 多一个kto_tag - DPO 的学习率要比 SFT 小一个数量级(
5.0e-6vs1.0e-4),pref_beta控制保守程度 - 看
rewards/accuracies判断有没有学到;一直停在 0.5 就是没学动
下一章:导出与部署 🐸