【强化学习】用 DPO 微调 LLM,20W字总结(十一)

😊你好,我是小航,一个正在变秃、变强的文艺倾年。
🔔本文讲解【强化学习】用 DPO 微调 LLM,20W字总结(十一),期待与你一同探索、学习、进步,一起卷起来叭!
🎯 把我的博客装进你的 Claude Code,它就是你的 AI 学习搭子
想随时搜我的文章、让 AI 帮你深度讲解甚至出面试题?复制下面这段提示词丢进你的 Claude Code——它会自动生成一个本地 SKILL,之后你直接说「搜一下强化学习的文章」就行。RSS 自动同步最新内容,不用手动存任何文件。
请为这个 CSDN 博客创建一个本地 SKILL(存到 .claude/skills/csdn-blog/SKILL.md): RSS 源:https://rss.csdn.net/m0_51517236/rss/map 支持三件事:① 列出最新文章(标题+链接+摘要);② 按关键词搜索; ③ 抓取指定文章全文,作为 AI 学习助手 / 面试官深度讲解并出题考核我。 SKILL.md 里写清楚 RSS URL、调用方式和示例。生成完就能用自然语言搜文章了。一键订阅,长期可用。🚀
上一篇我们从数学上推导了 DPO——用"重新参数化"把奖励模型和强化学习都甩掉了。这篇就上手实战:用 DPO 微调 LLM,看看代码到底能比 PPO 简洁多少。

准备:SFT 模型 + 一个"参考"双胞胎
DPO 需要两个模型:
- 训练模型 π θ \pi_\theta πθ:要优化的对象,初始就是 SFT 后的模型
- 参考模型 π r e f \pi_{ref} πref:SFT 模型的冻结副本,用来算 KL 约束里的"分母",全程不更新
所以开头先加载 SFT 模型,再复制一份冻结当参考:
from transformers import AutoModelForCausalLM, AutoTokenizer
model_path = './gpt2-sft'
model = AutoModelForCausalLM.from_pretrained(model_path).to(device) # 训练模型 π_θ
ref_model = AutoModelForCausalLM.from_pretrained(model_path).to(device) # 参考模型 π_ref
ref_model.eval()
for p in ref_model.parameters():
p.requires_grad = False # 参考模型冻结,只读不算梯度
tokenizer = AutoTokenizer.from_pretrained(model_path)
💡 代码解析:ref_model 和 model 一开始完全一样(都来自 SFT)。区别是参考模型 requires_grad=False,它只负责给出"基准概率",不参与训练。
偏好数据:chosen vs rejected
DPO 不需要在线采样(这点和 PPO 大不同),偏好数据是预先准备好的成对样本——同一个 prompt,一个喜欢的回答(chosen / y w y_w yw),一个不喜欢的(rejected / y l y_l yl)。
把 prompt + 回答拼成序列,转成 token id,再做两件事:填充对齐(凑成等长 batch)和掩码(只算"回答部分"的概率,prompt 部分不算):
# 偏好对:chosen(喜欢)和 rejected(不喜欢)
chosen_input_ids_list = [...] # prompt + y_w 的 token id
rejected_input_ids_list = [...] # prompt + y_l 的 token id
# answer_mask:只把"回答部分"标 1,prompt 和 padding 标 0
chosen_mask = create_answer_mask(chosen_inputs, tokenizer) & padding_mask
rejected_mask = create_answer_mask(rejected_inputs, tokenizer) & padding_mask
💡 代码解析:掩码很关键——DPO 算的是"模型生成回答 y y y 的概率",所以只该统计回答部分的 token,prompt 部分(大家都一样)和 padding 不能算进去。
DPO 微调核心:两个前向 + 一个损失
DPO 的训练循环,骨架比 PPO 干净得多。核心就三步:
import torch.nn.functional as F
beta = 0.1 # KL 约束强度
for batch in dataloader:
# ① 训练模型 算 logπ(y_w) 和 logπ(y_l)
chosen_logp = compute_log_prob(model, chosen_inputs, chosen_labels, chosen_mask)
rejected_logp = compute_log_prob(model, rejected_inputs, rejected_labels, rejected_mask)
# ② 参考模型 算 logπ_ref(y_w) 和 logπ_ref(y_l)(不算梯度)
with torch.no_grad():
ref_chosen_logp = compute_log_prob(ref_model, chosen_inputs, chosen_labels, chosen_mask)
ref_rejected_logp = compute_log_prob(ref_model, rejected_inputs, rejected_labels, rejected_mask)
# ③ DPO 损失:让"喜欢的相对概率"比"不喜欢的"更高
chosen_ratios = chosen_logp - ref_chosen_logp # log(π(y_w)/π_ref(y_w))
rejected_ratios = rejected_logp - ref_rejected_logp # log(π(y_l)/π_ref(y_l))
logits = chosen_ratios - rejected_ratios
loss = -F.logsigmoid(beta * logits).mean()
optimizer.zero_grad()
loss.backward()
optimizer.step()
💡 代码解析:对照第 10 篇的公式看就一目了然——chosen_ratios 是 log π ( y w ) π r e f ( y w ) \log\frac{\pi(y_w)}{\pi_{ref}(y_w)} logπref(yw)π(yw),rejected_ratios 是 log π ( y l ) π r e f ( y l ) \log\frac{\pi(y_l)}{\pi_{ref}(y_l)} logπref(yl)π(yl),两者相减后套个 logsigmoid(就是 − log σ ( ⋅ ) -\log\sigma(\cdot) −logσ(⋅))。让模型给喜欢的回答越来越高的相对概率、给不喜欢的越来越低。compute_log_prob 就是把 logits 过 log_softmax,再按掩码取回答部分 token 的对数概率求和。
对比 PPO:DPO 简洁在哪
把第 9 篇(PPO 微调)和这篇(DPO 微调)放一起,差别一目了然:
| 对比项 | PPO 微调(第 9 篇) | DPO 微调(本篇) |
|---|---|---|
| 奖励模型 | 要单独训练 | 不需要 |
| 数据 | 训练时在线采样生成回答 | 预先准备的偏好对 |
| 训练时互动 | 模型生成 → 奖励打分 → PPO 更新 | 两个模型前向 → 算损失 → 反传 |
| 强化学习 | 是(PPO clip) | 不是(纯监督学习) |
| 模型数量 | 策略 + 价值 + 奖励(3 个) | 训练 + 参考(2 个) |
一句话:PPO 是"边生成边学"(在线、要奖励裁判),DPO 是"拿着现成偏好对直接学"(离线、无裁判)。代码量、调参难度、显存占用,DPO 都更友好——这也是 DPO 能流行开来的原因。
推理测试
微调完,用常规方式生成看看效果:
from transformers import pipeline
g = pipeline('text-generation', model=model, tokenizer=tokenizer)
print(g("Translate to English: 你好", max_new_tokens=20))

小结
这篇把 DPO 实战跑通了:
| 步骤 | 做什么 |
|---|---|
| 准备 | SFT 模型 + 冻结一份当参考 |
| 数据 | 偏好对(chosen / rejected)+ 掩码只算回答部分 |
| 损失 | DPO: − log σ ( β ( log π ( y w ) π r e f ( y w ) − log π ( y l ) π r e f ( y l ) ) ) -\log\sigma(\beta(\log\frac{\pi(y_w)}{\pi_{ref}(y_w)} - \log\frac{\pi(y_l)}{\pi_{ref}(y_l)})) −logσ(β(logπref(yw)π(yw)−logπref(yl)π(yl))) |
| 训练 | 标准监督学习,无奖励模型、无 RL |
到这里,PPO 和 DPO 这两大对齐算法的实战都讲完了。下一篇进入 GRPO——DeepSeek 的选择,它在 PPO 基础上又做了巧妙的简化(去掉价值网络、按"组"评估),专攻推理能力。
📌 [ 笔者 ] 文艺倾年
📃 [ 更新 ] 2026.06.14
❌ [ 勘误 ] /* 暂无 */
📜 [ 声明 ] 由于作者水平有限,本文有错误和不准确之处在所难免,
本人也很想知道这些错误,恳望读者批评指正!

更多推荐


所有评论(0)