Chapter 06

DPO 与变体:IPO / KTO / SimPO / ORPO / CPO

📌 commit 8221a9f"去 RL"的对齐家族

DPO 是 2023 年最重要的对齐论文之一:把 PPO 的 RL 训练替换成离线偏好学习。本章拆解 DPO 数学,然后给出 TRL 里实现的 6 个变体的差异。

6.1DPO 的公式(关键一推)

PPO 的目标可以写成 $\max_\pi R(x,y) - \beta\mathrm{KL}(\pi\|\pi_\text{ref})$,闭式解是:

$$\pi^*(y|x) = \frac{1}{Z(x)}\,\pi_\text{ref}(y|x)\,\exp\!\big(R(x,y)/\beta\big)$$

反过来 $R(x,y) = \beta\log\frac{\pi^*(y|x)}{\pi_\text{ref}(y|x)} + \beta\log Z(x)$。 $Z(x)$ 在 Bradley-Terry pairwise 中被消掉,得到 DPO loss:

$$\mathcal{L}_\text{DPO} = -\mathbb{E}_{(x,y_w,y_l)}\Big[\log\sigma\Big(\beta\log\tfrac{\pi(y_w|x)}{\pi_\text{ref}(y_w|x)} - \beta\log\tfrac{\pi(y_l|x)}{\pi_\text{ref}(y_l|x)}\Big)\Big]$$

关键洞察:RM 被消掉了,直接用偏好对学。

6.2用 DPOTrainer

from trl import DPOConfig, DPOTrainer
from datasets import load_dataset

ds = load_dataset("trl-lib/ultrafeedback_binarized", split="train[:1000]")
# 每条 = {"chosen": [...], "rejected": [...], "prompt": "..."}

cfg = DPOConfig(
    output_dir="./dpo_out",
    per_device_train_batch_size=2,
    gradient_accumulation_steps=4,
    num_train_epochs=1,
    learning_rate=5e-7,         # ★ DPO 用极小 lr
    beta=0.1,
    max_length=2048,
    bf16=True,
)
trainer = DPOTrainer(
    model=sft_model,
    ref_model=None,             # 用 LoRA 时自动构造
    args=cfg,
    train_dataset=ds,
    processing_class=tok,
)
trainer.train()

6.36 个变体一图表

算法loss 核心需要 ref论文
DPO$-\log\sigma(\beta\Delta_\text{logratio})$Rafailov 2023
IPO$(\Delta_\text{logratio} - 1/(2\beta))^2$(square loss 不饱和)Azar 2024
KTO逐样本的 prospect theory loss,不需要 pairEthayarajh 2024
SimPO$-\log\sigma(\beta(\bar{\log\pi}(y_w)-\bar{\log\pi}(y_l))-\gamma)$,平均 log-probMeng 2024
ORPOSFT loss + $\lambda\cdot$odds ratio penalty,单阶段Hong 2024
CPOSFT + $\lambda\cdot$对比 loss,单阶段Xu 2024

关键差异点

6.4DPOConfig 关键字段

字段含义建议
betaKL 强度0.01-0.5;0.1 常用
loss_type"sigmoid" / "hinge" / "ipo" / "kto_pair" / "simpo" / ...试一试
reference_free把 ref 视为 uniform极端用法
label_smoothingconservative DPO (cDPO)偏好数据有噪用 0.1
rpo_alphaRPO regularization(NLL 项权重)0.1-1.0 可试
sync_ref_model训练中同步 ref 到 policyonline DPO 才用
max_length截断2048-4096
max_prompt_lengthprompt 截断1024 常用

6.5什么时候选什么

需求
第一次尝试,要稳DPO(beta=0.1)
偏好数据噪声大cDPO(DPO + label_smoothing=0.1)或 IPO
只有 thumbs up/down 单标签KTO
显存极紧SimPO / ORPO(不要 ref)
想跳过 SFTORPO 或 CPO
需要在线采样(rollout)GRPO(下一章)或 online DPO

6.6实现位置

trl/trainer/
├── dpo_trainer.py    + dpo_config.py         (DPO / IPO / cDPO 都在这)
├── kto_trainer.py    + kto_config.py
├── orpo_trainer.py   + orpo_config.py
├── cpo_trainer.py    + cpo_config.py
└── ...
trl/trainer/utils.py        compute_loss 的实现细节

6.7常见踩坑

现象原因
DPO loss 一直在 ln(2) 附近不降ref / policy 是同一个,没拉开差距;检查 LoRA 配置
训完后乱码 / 重复beta 太小(< 0.01),policy 偏离过多
训完打榜变差偏好数据质量差;或 lr 太大(DPO 应 ≤ 1e-6 全参 / 5e-6 LoRA)
OOM4 份模型 + log-prob 缓存;先开 LoRA + ref_model=None

6.8DPOConfig.loss_type 全枚举(15 种)

TRL 的 DPOTrainer 通过 同一个 trainer + 不同 loss_type 实现整个 "DPO 家族"。这是它替代为每个论文写独立 trainer 的核心抽象。来源 trl/trainer/dpo_config.py:211–219

loss_type核心公式需要 ref论文
"sigmoid"(默认) $-\log\sigma(\beta\Delta)$ 2305.18290 经典 DPO
"hinge" $\max(0, 1 - \beta\Delta)$ SLiC
"ipo" $(\Delta - 1/(2\beta))^2$ 2310.12036 IPO(长度归一化)
"exo_pair" $KL(\sigma(\beta\Delta) \;\|\; [1-\epsilon, \epsilon])$ 2402.00856 EXO
"nca_pair" Noise Contrastive Alignment 形式 NCA 系
"robust" label-flip 鲁棒 DPO 处理偏好噪声
"bco_pair" Binary Classification Objective 的 pair 版 BCO
"sppo_hard" SPPO 硬概率 margin 2405.00675
"aot" Advantage-based Optimal Transport(排序版) AOT
"aot_unpaired" AOT 不需要成对 AOT
"apo_zero" $\Delta_\text{chosen}$ 优于默认时的 APO 分支 2408.06266
"apo_down" $\Delta_\text{chosen}$ 不如默认时的 APO 分支 2408.06266
"discopop" 调制 loss,含 discopop_tau 字段 2406.08414 DiscoPOP
"sft" 只对 chosen 做 SFT loss multi-loss 锚定项
"sigmoid_norm" sigmoid + 长度归一 长度敏感场景

核心 dispatch 在 dpo_trainer.py:1160–1459_compute_loss,按 loss_type 走 switch。

6.9loss_type 可以是 list:multi-loss DPO

v1 让 loss_type 接受 list,配合 loss_weights 同时算多种 loss 加权求和:

cfg = DPOConfig(
    output_dir="./out",
    beta=0.1,
    loss_type=["sigmoid", "sft"],         # ★ 两个 loss 同时优化
    loss_weights=[1.0, 0.1],              # sigmoid 主、sft 辅
    ...
)

典型用法:"sigmoid" + "sft" = MPO(Mixed Preference Optimization),既学偏好又锚定 chosen 的 SFT loss,训练更稳。

6.10f_divergence_type 4 种

DPO 的 KL 正则不一定非要是 reverse-KL。DPOConfig.f_divergence_type(dpo_config.py:237–242)有 4 种:

f_divergence_type含义
"reverse_kl"(默认) $KL(\pi_\theta \;\|\; \pi_\text{ref})$,经典 DPO
"forward_kl" $KL(\pi_\text{ref} \;\|\; \pi_\theta)$,更宽容
"js_divergence" 对称化版本,两边都不偏
"alpha_divergence" 带超参 $\alpha$ 的广义 f-divergence

实战 reverse_kl 已经够用;选 forward_kl 会让 policy 更激进偏离 ref,配合小 beta 用。

6.11v1 新功能:LD-DPO 长度差异化

DPOConfig.ld_alpha(dpo_config.py:228–236)启用 LD-DPO(Length-Differentiated DPO)。原理:

对长度偏置敏感的场景(用户评分通常偏向长回答),ld_alpha 比换 SimPO 更精细可调。

6.12这章你需要带走的