动机

PPO 需要在显存中维持 4 个模型(policy、reference、reward 模型、value head)、复杂的 RL 基础设施,并且以不稳定著称。DPO [302] 提出的问题是:我们能否跳过 RL,直接从偏好中学习?

关键洞察:在 RLHF 目标(reward 最大化 + KL 惩罚)下的最优 policy 拥有解析解。我们可以推导出一个有监督 loss,隐式优化同一个目标。

数学推导

第 1 步:RLHF 目标:maxπ𝔼x,y∼π[r(x,y)]−βDKL[π∥πref]

第 2 步:最优解为:π∗​(y|x)=1Z⁡(x)​πref​(y|x)​exp⁡(r⁡(x,y)β)

第 3 步:重排式子,用 policy 表达 reward:r⁡(x,y)=β​log⁡π∗​(y|x)πref​(y|x)+β​log⁡Z⁡(x)

第 4 步:代入 Bradley-Terry 偏好模型 P⁡(yw≻yl)=σ⁡(r⁡(yw)−r⁡(yl))。Z⁡(x) 项相互抵消!

ℒDPO​(θ)=−𝔼(x,yw,yl)​[log⁡σ⁡(β​log⁡πθ​(yw|x)πref​(yw|x)−β​log⁡πθ​(yl|x)πref​(yl|x))](6.1)

Gradient 分析

DPO 的 gradient 可分解为:

∇θℒ=−β⋅σ⁡(−r^w+r^l)⏟weight: higher when model is wrong⋅[∇θlogπθ(yw|x)−∇θlogπθ(yl|x)](6.2)

解读:gradient 会提升「被选」项的概率,降低「被拒」项的概率。当模型当前更偏好错误答案时,权重最大——它把学习重点放在「易混淆」的成对样本上。

TRL 实现

下面给出一个使用 HuggingFace TRL 的最小可运行示例。

from trl import DPOConfig, DPOTrainer
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig
from datasets import load_dataset
 
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.1-8B-Instruct",
    torch_dtype=torch.bfloat16, attn_implementation="flash_attention_2")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B-Instruct")
 
# Dataset format: {"prompt": str, "chosen": str, "rejected": str}
dataset = load_dataset("argilla/ultrafeedback-binarized-preferences")
 
lora_config = LoraConfig(r=64, lora_alpha=16, lora_dropout=0.05,
    target_modules=["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"])
 
dpo_config = DPOConfig(
    output_dir="./dpo_output",
    beta=0.1,                    # KL regularization strength
    learning_rate=5e-7,          # Very low LR for stability
    loss_type="sigmoid",         # Standard DPO loss
    max_length=2048,             # Max sequence length
    max_prompt_length=1024,      # Truncation for prompts
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,  # Effective batch = 16
    gradient_checkpointing=True,
    bf16=True,
    num_train_epochs=1,          # DPO overfits fast - 1 epoch!
    warmup_ratio=0.1,
    logging_steps=10,
    eval_strategy="steps",
    eval_steps=200,
    save_strategy="steps",
    save_steps=500,
)
 
trainer = DPOTrainer(
    model=model,
    ref_model=None,             # With LoRA, ref = base model (no copy needed!)
    args=dpo_config,
    train_dataset=dataset["train"],
    eval_dataset=dataset["test"],
    tokenizer=tokenizer,
    peft_config=lora_config,
)
trainer.train()
# Key metrics to monitor: train/rewards/chosen, train/rewards/rejected, train/rewards/margins

DPO 的完整机制

本节给出 DPO 完整的计算细节——训练过程中在 token 层面究竟发生了什么。

序列级对数概率

DPO 中的关键量是给定 prompt x 时整条序列y=(y1,y2,…,yT) 的对数概率。它按各 token 对数概率之和计算:

log⁡πθ​(y|x)=∑t=1Tlog⁡πθ​(yt∣x,y<t)(6.3)

每一项 log⁡πθ​(yt|x,y<t) 是位置 t 处针对序列中实际 token yt 的 log-softmax 输出。这与标准语言建模所用的交叉熵 loss 完全相同——但这里我们做的是求和,而不是取平均。

关键细节:gradient 会流经 yw 和 yl 中的每一个 token 位置。中间 token 没有被 mask——每个 token 都对序列级对数概率有贡献。

DPO Loss 的分解

从 loss 出发:

ℒDPO​(θ)=−𝔼(x,yw,yl)∼𝒟​[log⁡σ⁡(β⋅hθ​(x,yw,yl))](6.4)

其中「隐式 reward margin」hθ 为:

hθ​(x,yw,yl)=log⁡πθ​(yw|x)πref​(yw|x)⏟chosen reward proxy−log⁡πθ​(yl|x)πref​(yl|x)⏟rejected reward proxy(6.5)

展开到 token 级别:

hθ=∑t=1|yw|[log⁡πθ​(ywt|x,yw<t)−log⁡πref​(ywt|x,yw<t)]−∑t=1|yl|[log⁡πθ​(ylt|x,yl<t)−log⁡πref​(ylt|x,yl<t)](6.6)

Forward Pass:逐步详解

对于一条训练样本 (x,yw,yl):

  1. 拼接:构造两条序列:[x;yw] 和 [x;yl]。在 batch 内补齐到相同长度。
  2. 前向传播(policy πθ):让两条序列都通过模型。收集每个回答位置上的 logits。
  3. 提取对数概率:在回答的每个位置 t,取 log⁡softmax​(logitst)​[yt]——即实际 token 的对数概率。
  4. 对 token 求和:logp_chosen=∑t∈response positionslog⁡πθ​(ywt|x,yw<t)(6.7)logp_rejected=∑t∈response positionslog⁡πθ​(ylt|x,yl<t)(6.8)
  5. 减去 reference(预先计算,或来自第二次前向传播):ratio_w=logp_chosen−ref_logp_chosen(6.9)ratio_l=logp_rejected−ref_logp_rejected(6.10)
  6. 计算 loss:ℒ=−log⁡σ⁡(β⋅(ratio_w−ratio_l))
  7. 反向传播:gradient 经由第 5 步 → 第 4 步 → 第 3 步 → 第 2 步回传,以更新 θ。

Token 级 Gradient 分析

每个 token 都能得到 gradient 吗?能。被选序列中位置 t 处 logits 的 gradient 为:

∂ℒ∂logitst(w)=−σ(−β⋅hθ)⏟scaling factor⋅β⋅∂logπθ(ywt|⋅)∂logitst(w)(6.11)

关键洞察:缩放因子 σ(−β⋅hθ) 在两条序列中为所有 token 共享。它起到自适应学习率的作用:

  • 当 hθ 很小(模型无法区分被选与被拒)时:缩放为 ≈0.5——gradient 强,学习激进。
  • 当 hθ 很大(模型已经偏好被选)时:缩放为 ≈0——gradient 可忽略,不要过拟合。

对被选 token 的影响:概率被提升(对数概率被推高)。

对被拒 token 的影响:概率被降低(对数概率被压低)。

相对于 reference:只有与 πref 的差值才重要。如果模型已经给被选回答分配了高概率(与 reference 一致),gradient 就很小。

逐 Token vs. 序列级:长度归一化

一个微妙的问题:更长的序列天然具有更低的对数概率(求和的项更多,每项 ≤0)。如果 |yw|≫|yl|,loss 可能偏向于偏好更短的回答。

解决方案:

  • 长度归一化 DPO:用 1|y|∑tlogπθ(yt|⋅) 替换 log⁡πθ​(y|x)。部分实现采用了这一做法(SimPO 即采用它)。
  • 标准 DPO:使用原始求和(不做归一化)。这会隐式地惩罚冗长——模型必须为被选回答中的每个 token 赋予高概率。
  • 实际影响:在基准测试上,长度归一化 DPO 减少了长度博弈,但可能损害指令遵循质量。标准(未归一化)版本在生产中更常见。

标签掩码:哪些 token 会收到 Gradient

伪代码:DPO 训练步

常见陷阱

DPO 变体及其各自的失败场景

β\beta 选择指南

β档位适用场景0.01非常激进仅当数据极其干净且你需要较大的分布偏移时0.05激进数据质量好,希望在 SFT 基础上有明显提升0.1标准默认起点。质量与稳定性之间平衡良好0.2保守数据有噪声,或模型已接近期望行为0.5非常保守安全性微调,且不能破坏模型既有能力

DPO 的 Batch Size 配置与扩展

与在单序列 token 预测上运行的标准 SFT 不同,DPO 利用成对 loss,将被偏好序列与不被偏好序列进行比较。这从根本上改变了显存占用与优化稳定性。

全局 Batch Size 目标

跨多种 DPO 实现的经验证据确立了一个最优的全局 batch size 区间:

Bglobal∈[32,128](6.12)
  • Bglobal<32:隐式 reward 估计中的 gradient 噪声严重 → policy 在对齐目标之间(有用性 vs. 安全性)破坏性地来回振荡。
  • Bglobal>128:收敛速度收益递减;分布式计算中的通信开销很高。

数学分解

因为 DPO 会同时加载两份模型副本(活动 policy πθ + 冻结的 reference πref),每条序列的显存占用翻倍。全局 batch size 可分解为:

Bglobal=Bmicro×NGPUs×Kaccum(6.13)
  • Bmicro:单设备微 batch 大小(每次前向传播处理的偏好成对样本数)。
  • NGPUs:并行数据处理设备数量。
  • Kaccum:权重更新前的 gradient 累积步数。

成对倍增因子:单个 DPO 数据实例包含一个 prompt(x)、一个被选回答(yw)和一个被拒回答(yl)。每个微 batch 实际产生的张量负载为:

Tsequences=2×Bmicro(6.14)

对于 >7B 参数规模的模型,在 80GB GPU 上以 4096–8192 token 的上下文长度运行时,物理上限被严格约束在 Bmicro∈[1,2]。

分布式扩展配置

配置单 GPU8-GPU 节点
Bglobal6464
Bmicro2(4 条序列)2(4 条序列)
NGPUs18
Kaccum32 步4 步
吞吐量串行/慢高并行吞吐

表 6.1:DPO 训练的分布式扩展配置(Bglobal=64 目标)。

显存优化:预计算 Reference 对数概率

DPO loss 为:

ℒDPO​(θ)=−𝔼(x,yw,yl)​[log⁡σ⁡(β​log⁡πθ​(yw|x)πref​(yw|x)−β​log⁡πθ​(yl|x)πref​(yl|x))](6.15)

因为 πref 在整个训练过程中完全静态,其输出可以预先计算:

DPO 扩展与变体

直接偏好优化(Direct Preference Optimization,DPO)通过推导 reward 函数与最优 policy 之间的解析映射,将 RLHF 重新表述为一个监督学习问题。标准 DPO loss 为:

ℒDPO​(θ)=−𝔼(q,yw,yl)​[log⁡σ⁡(β​log⁡πθ​(yw|q)πref​(yw|q)−β​log⁡πθ​(yl|q)πref​(yl|q))],

其中 yw 是被偏好(胜出)的回答,yl 是不被偏好(落败)的回答,β 控制 KL 惩罚的强度。以下小节介绍最重要的扩展与变体。

f-DPO——广义 f-Divergence DPO

f-DPO loss 用 f-散度生成函数的导数替换对数比:

ℒf​-DPO=−𝔼⁡[f′​(πθ​(yw|q)πref​(yw|q))−f′​(πθ​(yl|q)πref​(yl|q))],

其中 f′ 是 f-散度生成函数的导数。

Robust DPO

假设每个标签以概率 ϵ(噪声率)被翻转。去偏后的 loss 为:

ℒrobust=(1−ϵ)​ℒDPO​(yw,yl)−ϵ​ℒDPO​(yl,yw)1−2​ϵ,

其中 ℒDPO​(yw,yl) 是把 yw 视为被偏好回答的标准 DPO loss,ℒDPO​(yl,yw) 是标签翻转后的 loss。这一修正去除了标签噪声引入的偏差。

TR-DPO——信任域 DPO

TR-DPO 使用指数移动平均(EMA)更新 reference 模型:

πref(t+1)←α⋅πθ(t)+(1−α)⋅πref(t),

其中 α∈(0,1) 是混合系数。每 Tsync 个 gradient 步执行一次。

EXO——精确优化

EXO 最小化模型分布与目标(reward 最优)分布之间的反向 KL:

ℒEXO=𝔼y∼πθ​[log⁡πθ​(y|q)p∗​(y|q)],

其中 p∗​(y|q)∝πref​(y|q)​exp⁡(r⁡(y,q)/β) 是最优 policy。实践中,EXO 利用可用的偏好成对样本来近似它:

ℒEXO≈−𝔼⁡[log⁡σ⁡(β​log⁡πref​(yw|q)πθ​(yw|q)−β​log⁡πref​(yl|q)πθ​(yl|q))].

注意与 DPO 相比,πθ 与 πref 的角色是对调的。

NCA——噪声对比对齐

NCA 把对齐重新表述为噪声对比估计。loss 包含三项:

ℒNCA=−log⁡σ⁡(rw)−12​log⁡σ⁡(−rw)−12​log⁡σ⁡(−rl),

其中 ry=β​log⁡(πθ​(y|q)/πref​(y|q)) 是隐式 reward。第一项鼓励 yw 获得高 reward;第二、三项则惩罚 yw 和 yl 获得高 reward(防止崩塌)。

SLiC-HF——序列似然校准

SLiC-HF loss 为:

ℒSLiC=max⁡(0,δ−β​log⁡πθ​(yw|q)πref​(yw|q)+β​log⁡πθ​(yl|q)πref​(yl|q)),

其中 δ 是 margin 阈值。当模型在胜出与落败回答之间已经给出 δ 的 margin 时,loss 为零。

Iterative RPO——推理偏好优化

RPO loss 结合了 DPO 与 SFT:

ℒRPO=λ1​ℒDPO​(yw,yl)+λ2​ℒNLL​(yw),

其中 ℒNLL​(yw)=−log⁡πθ​(yw|q) 是作用于胜出回答的标准语言建模 loss。

SimPO——简化版偏好优化

SimPO 把隐式 reward 定义为:

rSimPO​(y|q)=β|y|​log⁡πθ​(y|q),

loss 定义为:

ℒSimPO=−𝔼⁡[log⁡σ⁡(β|yw|​log⁡πθ​(yw|q)−β|yl|​log⁡πθ​(yl|q)−γ)],

其中 γ>0 是目标 reward margin,它确保胜出回答的 reward 至少比落败回答高出 γ。