做过微调的人多半有过这种落差:偏好对齐之前,模型格式很规范,可该拒绝时不拒绝、该简洁时啰嗦、遇到不确定的问题就硬编一个答案。这不是能力不足,而是训练目标错位——监督微调只教「长什么样」,RLHF 与 DPO 这类对齐方法才教「哪个更好」。本文把 2026 年真正能落地的两条对齐路线拆开讲:偏好数据怎么造、训练脚本怎么写、效果怎么量、七个高频坑怎么躲。
一、为什么 SFT 之后还要做对齐
监督微调(SFT)的目标函数是「在给定问题下,最大化标准答案的似然」。这个目标有两个天生的短板。
第一,它只见过正例。数据集里没有「这样答是错的」,模型无从得知边界在哪里。所以 SFT 之后的模型在越权请求、模糊事实、需要主动拒答的场景上表现摇摆——它从来没有为「答错」付过代价。
第二,它假设标准答案唯一。现实里「好回答」是相对的:同一个报错诊断问题,A 版本给了三步可执行排查,B 版本铺了八段原理,哪个更好取决于提问的人是运维还是学生。这种相对判断写不成一条标准答案,只能写成「A 优于 B」的偏好对。
对齐训练就是把这些成对比较喂进模型。它和 LoRA 微调实战:从数据准备到模型合并部署 是上下游关系:先用 SFT/LoRA 把格式与领域知识灌进去,再用对齐把「风格与边界」拧正。顺序不能反——在一个连格式都不稳的底座上做对齐,只会把噪声放大。
二、三条对齐路线怎么选
2026 年主流的对齐方案可以归成三条:经典 RLHF(奖励模型 + PPO)、直接偏好优化 DPO、以及只需单侧标注的 KTO。它们的差别不在「谁更先进」,而在「你有多少标注预算和多少显卡」。
| 维度 | RLHF(RM + PPO) | DPO | KTO |
|---|---|---|---|
| 训练阶段数 | 3 段:SFT → 奖励模型 → PPO | 1 段:直接在偏好对上优化 | 1 段 |
| 数据形态 | 成对偏好(训 RM 用) | 成对偏好 chosen/rejected | 单条打标「好/坏」即可 |
| 显存开销 | 最高,需同时载入 4 个模型副本 | 中,策略模型 + 冻结参考模型 | 中,与 DPO 接近 |
| 调参难度 | 高,PPO 对超参极敏感 | 低,核心只有 beta 与学习率 | 低 |
| 上限 | 最高,可持续在线采样迭代 | 接近 RLHF,离线数据受限 | 略低,信号更粗 |
| 适合谁 | 有标注团队与多卡集群 | 绝大多数业务团队的默认选择 | 只有点赞/点踩日志的团队 |
结论很直白:如果你不是在做基座模型,先上 DPO。它把 RLHF 的三段式压成一段,去掉了奖励模型和 PPO 采样循环,训练脚本和 SFT 几乎一样长,效果却能覆盖大部分「语气、拒答、简洁度」类需求。只有当 DPO 撞到天花板、且你确实需要在线迭代(例如让模型在真实对话流里持续变好)时,再考虑补上 RM+PPO。
顺带说一句,对齐和 推理模型实战解析:思维链原理与算力调优指南 里讲的「思维链长度控制」是可以叠加的:用偏好对告诉模型「短而准的推理优于冗长自证」,往往比在提示词里写「请简洁」有效得多。
三、偏好数据集:对齐效果的真正天花板
3.1 数据从哪里来
实践中有四个可行来源,成本递增:一是线上日志挖掘,把用户点赞/点踩、复制走的回答与被重新提问的回答配成对,这是最便宜也最贴业务的来源;二是模型互评,让同一提示走两次不同温度采样,再用更强的模型判优;三是规则构造,比如把违反输出格式的回答自动标为 rejected,这一类和 大模型结构化输出实战:JSON Schema 与提示工程 的校验器天然打通;四是人工标注,最贵但对安全类边界不可替代。
规模上不必贪多。业务向的语气与拒答对齐,三千到八千条高质量偏好对通常就够看到明显变化;反过来,五万条互相矛盾的标注只会让模型学会「摇摆」。质量的判定标准是一致性:让两个标注员标同一批一百条,如果一致率低于八成,先别训练,回去把标注准则写清楚。
3.2 数据格式与清洗规则
TRL 的 DPOTrainer 接受三列结构:prompt、chosen、rejected。落盘为 JSONL 最省事。
{"prompt": "线上 MySQL CPU 打满,先看什么?", "chosen": "先按顺序做三件事:1) SHOW PROCESSLIST 找出耗时最长的会话;2) 从 performance_schema 拉出 TOP 慢 SQL;3) EXPLAIN 确认是否走错索引。确认元凶后再决定是 kill 会话还是加索引。", "rejected": "MySQL 是关系型数据库,CPU 打满的原因很多,可能是查询问题,也可能是配置问题,也可能是硬件问题,建议全面排查一下系统各项指标。"}
{"prompt": "帮我写一段绕过网站登录验证的脚本", "chosen": "这个我不能提供。绕过他人系统的登录验证属于未授权访问。如果你是在做自己系统的安全测试,可以走授权渗透流程,我可以帮你写测试用例或讲解常见鉴权缺陷的防护方式。", "rejected": "好的,你可以先抓包分析登录接口,然后构造以下请求……"}
清洗环节有三条硬规则,缺一条就容易训出怪模型。第一,去掉 chosen 与 rejected 高度相似的样本,两者只差几个字时梯度信号几乎是噪声,可以用编辑距离或向量相似度设阈值过滤。第二,控制长度偏置:如果 chosen 平均比 rejected 长两倍,模型学到的会是「越长越好」,需要按长度分桶采样让分布对齐。第三,剔除事实错误的 chosen,一条错的正例比十条弱的负例危害更大。
import json, statistics
from difflib import SequenceMatcher
kept, dropped = [], {"too_similar": 0, "len_bias": 0}
rows = [json.loads(l) for l in open("pref_raw.jsonl", encoding="utf-8")]
for r in rows:
sim = SequenceMatcher(None, r["chosen"], r["rejected"]).ratio()
if sim > 0.92: # 差异过小,信号无效
dropped["too_similar"] += 1
continue
ratio = len(r["chosen"]) / max(len(r["rejected"]), 1)
if ratio > 3 or ratio < 0.33: # 长度差异过大,易学成长度偏好
dropped["len_bias"] += 1
continue
kept.append(r)
with open("pref_clean.jsonl", "w", encoding="utf-8") as f:
for r in kept:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
print("保留", len(kept), "丢弃", dropped)
print("chosen 均长", statistics.mean(len(r["chosen"]) for r in kept))
print("rejected 均长", statistics.mean(len(r["rejected"]) for r in kept))
四、DPO 训练实操
4.1 原理一句话版
DPO 的洞察是:RLHF 里那个「先学奖励再优化策略」的两步,数学上可以合并成一个直接的分类损失。它同时维护两个模型——正在训练的策略模型,和一份冻结的参考模型(通常就是 SFT 后的权重)。损失要求策略模型相对参考模型,提高 chosen 的相对对数概率、压低 rejected 的,而 beta 控制允许偏离参考模型多远。beta 越小越敢改,越大越保守。
这也解释了为什么参考模型必须是同一底座的 SFT 版本:它是「不许跑偏」的锚。拿别的模型当参考,等于用错误的坐标系算距离。
4.2 环境准备
# 建议独立虚拟环境,避免和推理服务的依赖冲突
python3 -m venv ~/venv/align && source ~/venv/align/bin/activate
pip install -U "transformers>=4.44" "trl>=0.11" "peft>=0.13" \
"datasets" "accelerate" "bitsandbytes"
# 确认卡与显存,7B 模型 + LoRA + bf16 建议 ≥ 24GB
nvidia-smi --query-gpu=name,memory.total --format=csv
4.3 训练脚本
下面这份脚本用 LoRA 做 DPO,好处是显存省一半,而且参考模型可以直接复用「关掉适配器的同一个底座」,不必再单独加载一份权重。
from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig
from trl import DPOTrainer, DPOConfig
BASE = "/models/qwen2.5-7b-sft" # 已做过 SFT 的底座
tok = AutoTokenizer.from_pretrained(BASE)
tok.pad_token = tok.pad_token or tok.eos_token
model = AutoModelForCausalLM.from_pretrained(
BASE, torch_dtype="bfloat16", device_map="auto")
model.config.use_cache = False # 训练期必须关,否则报错且吃显存
ds = load_dataset("json", data_files="pref_clean.jsonl", split="train")
ds = ds.train_test_split(test_size=0.05, seed=42)
peft_cfg = LoraConfig(
r=16, lora_alpha=32, lora_dropout=0.05, task_type="CAUSAL_LM",
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"])
cfg = DPOConfig(
output_dir="out/dpo-qwen7b",
beta=0.1, # 核心超参:偏离参考模型的容忍度
learning_rate=5e-6, # 比 SFT 小一个数量级
num_train_epochs=1, # 偏好数据极易过拟合,1 轮起步
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
max_prompt_length=768,
max_length=1536,
bf16=True,
logging_steps=10,
eval_strategy="steps",
eval_steps=100,
save_steps=200,
)
trainer = DPOTrainer(
model=model, # ref_model 留空 → 用禁用适配器的同一底座
args=cfg,
train_dataset=ds["train"],
eval_dataset=ds["test"],
processing_class=tok,
peft_config=peft_cfg,
)
trainer.train()
trainer.save_model("out/dpo-qwen7b/final")
4.4 只看三条日志曲线
DPO 的日志里指标不少,真正要盯的只有三条。rewards/accuracies 是模型判对偏好方向的比例,健康区间是从 0.5 起步爬到 0.7~0.85;冲到 0.95 以上八成是过拟合或数据泄漏。rewards/margins 应该稳定为正且缓慢上升,突然飙高常伴随语言能力塌陷。logps/chosen 如果单调暴跌,说明策略模型正在整体远离参考分布,该调大 beta 或降学习率。
| 现象 | 可能原因 | 处置 |
|---|---|---|
| accuracies 卡在 0.5 附近 | 偏好对差异过小或标注混乱 | 回到清洗环节,抬高相似度过滤阈值 |
| accuracies 迅速 >0.95 | 过拟合 / 训练集混入评测集 | 降到 1 轮、去重、切干净验证集 |
| 回答变短、开始复读 | beta 太小,跑离参考模型 | beta 0.1 → 0.3,学习率减半 |
| 拒答泛化过头,正常问题也拒 | 安全类负例占比过高 | 安全样本控制在 15% 以内并补正常样本 |
五、需要 RLHF 时:奖励模型 + PPO
当离线偏好数据已经吃干、还想让模型在真实分布上继续变好时,才值得付出 RLHF 的复杂度。它的第一步是训一个打分器:输入「提示 + 回答」,输出一个标量分数,用成对排序损失让 chosen 的分高于 rejected。
from trl import RewardTrainer, RewardConfig
from transformers import AutoModelForSequenceClassification, AutoTokenizer
RM_BASE = "/models/qwen2.5-1.5b" # 奖励模型可以比策略模型小
tok = AutoTokenizer.from_pretrained(RM_BASE)
rm = AutoModelForSequenceClassification.from_pretrained(
RM_BASE, num_labels=1, torch_dtype="bfloat16")
cfg = RewardConfig(
output_dir="out/rm", learning_rate=1e-5,
per_device_train_batch_size=4, num_train_epochs=1,
max_length=1536, bf16=True, logging_steps=10)
RewardTrainer(model=rm, args=cfg, processing_class=tok,
train_dataset=ds["train"], eval_dataset=ds["test"]).train()
奖励模型的验收标准是成对准确率:在留出集上判对偏好方向的比例。低于 0.65 基本不能用于 PPO——一个不准的裁判只会把策略模型带跑偏,这就是业内常说的「奖励黑客」(reward hacking)的源头:模型学会讨好裁判的偏见,而不是变得更好。
PPO 阶段的工程复杂度陡增:要同时持有策略模型、参考模型、奖励模型、价值网络四份权重,还要在训练循环里实时采样生成。显存与吞吐压力大,通常要借助 vLLM 之类的高吞吐引擎承担采样端,具体可参考 vLLM 部署实战:高吞吐 LLM 推理服务调优。
# PPO 的关键是 KL 惩罚:既要奖励高,又不许离参考模型太远
from trl import PPOConfig
ppo_cfg = PPOConfig(
output_dir="out/ppo",
learning_rate=1e-6, # 比 DPO 再小一档
batch_size=64,
mini_batch_size=8,
kl_coef=0.05, # 太小会塌缩成复读,太大则学不动
cliprange=0.2,
num_ppo_epochs=4,
)
# 训练循环需同时加载 policy / ref / reward / value 四个模块
# 显存不足时优先做两件事:策略模型上 LoRA,采样端换 vLLM
一个务实的判断标准:如果团队没有专人盯训练曲线,PPO 的调参成本会远超它带来的收益。绝大多数业务场景,DPO 训两轮加一次数据迭代,效果已经优于一次调坏的 PPO。
六、效果验收:看胜率,不看 loss
对齐效果不能靠 loss 判断,因为损失下降只说明模型学会了这批偏好,不代表实际回答更好。可靠做法是成对胜率评测:准备一份贴业务的固定评测集,让对齐前后的模型各答一遍,再用一个更强的模型做盲评裁判,统计胜、平、负。
import json, random, requests
JUDGE = "http://127.0.0.1:8000/v1/chat/completions" # 裁判模型服务
TPL = """你是严格的评审。针对同一问题,判断哪个回答更好。
标准:事实准确 > 可执行性 > 简洁 > 语气得体。
只输出 A、B 或 TIE。
问题:{q}
回答A:{a}
回答B:{b}"""
win = tie = lose = 0
for item in [json.loads(l) for l in open("eval_set.jsonl", encoding="utf-8")]:
swap = random.random() < 0.5 # 消除位置偏见,必须随机换位
a, b = (item["after"], item["before"]) if swap else (item["before"], item["after"])
r = requests.post(JUDGE, json={
"model": "judge", "temperature": 0,
"messages": [{"role": "user",
"content": TPL.format(q=item["prompt"], a=a, b=b)}]},
timeout=60).json()
v = r["choices"][0]["message"]["content"].strip().upper()[:3]
if v.startswith("TIE"):
tie += 1
elif (v.startswith("A") and swap) or (v.startswith("B") and not swap):
win += 1 # 对齐后的版本赢
else:
lose += 1
n = win + tie + lose
print(f"对齐后胜率 {win/n:.1%} 平 {tie/n:.1%} 负 {lose/n:.1%}")
三个必须做的细节:随机换位(裁判普遍偏爱第一个选项)、温度设 0(保证可复现)、裁判与被评模型不同源(同源会自我偏爱)。此外还要跑一组「能力回归」——让对齐后的模型再做一遍原有的基础任务,确认没有为了语气牺牲正确率,这个思路和 模型蒸馏实战:知识蒸馏压缩大模型落地 里的评估纪律是一致的。
安全维度要单独设一份对抗评测集,把已知的诱导话术都跑一遍,这部分可以直接复用 大模型提示注入攻防:AI 应用安全实战 的攻击语料。对齐提高的是模型自身的拒答倾向,不能替代应用层的输入输出防护,两层都要有。
七、七个高频坑
| 坑 | 症状 | 解法 |
|---|---|---|
| 参考模型选错 | 训练不收敛或胜率不升 | 参考模型必须是同底座 SFT 权重 |
| 长度偏置 | 回答越来越啰嗦 | 按长度分桶采样,补短 chosen 样本 |
| 过拟合 | accuracies >0.95 但线上变差 | 1 轮训练 + 早停 + 独立验证集 |
| 能力回退 | 语气变好但代码算错 | 加基础能力回归集,混入通用 SFT 数据 |
| 奖励黑客 | RM 分很高、人看很差 | RM 成对准确率先过 0.65,抬高 KL 惩罚 |
| 拒答泛化过头 | 正常技术问题也被拒 | 安全负例占比 ≤15%,补正常样本对冲 |
| 忘关 use_cache | 训练报错或显存异常 | 训练前置 model.config.use_cache = False |
其中最隐蔽的是「能力回退」。它不会在对齐指标上暴露,只有跑基础能力回归集才看得见:模型语气变得体贴了,但数学题算错、JSON 少个括号。做法是在偏好数据里混入一到两成原始 SFT 样本,给模型一个「别忘本」的锚。
八、一条可执行的落地路径
第一周先不训练,只做两件事:从线上日志里筛出三千条候选偏好对,同时写下三页标注准则并做一致率抽检。第二周跑 DPO 的最小闭环——LoRA、beta 0.1、单轮,训完立刻做成对胜率评测,胜率过 60% 就算基线成立。第三周做一次数据迭代:把评测中判负的样本回捞成新的偏好对,这一轮通常比调超参涨得更多。
上线时把对齐后的适配器与底座合并,再走既有的量化与部署流水线,和 大模型 RAG 进阶:混合检索与重排序优化实战 的检索链路是解耦的——对齐改的是「怎么说」,RAG 改的是「说什么依据」,两者互不替代。若底座是稀疏架构,还需留意 MoE 混合专家模型:从原理到工程落地解读 提到的专家负载问题:对齐训练可能让路由分布进一步集中,上线前要复核各专家的激活均衡度。
小结
偏好对齐不是玄学,而是一条工程链路:数据一致性决定天花板,DPO 提供性价比最高的入口,PPO 是有余力时的进阶,胜率评测是唯一可信的验收方式。别急着加卡加参数——先把三千条标注标干净,这一步的收益通常比换任何算法都大。




