在这里插入图片描述

😊你好,我是小航,一个正在变秃、变强的文艺倾年。
🔔本文讲解【强化学习】用 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_modelmodel 一开始完全一样(都来自 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
❌ [ 勘误 ] /* 暂无 */
📜 [ 声明 ] 由于作者水平有限,本文有错误和不准确之处在所难免,
本人也很想知道这些错误,恳望读者批评指正!

在这里插入图片描述

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐