离线偏好优化¶
离线偏好优化只使用已经收集的回答和偏好,不在训练环中向当前策略采样。它容易重放、部署简单,却只能重新分配数据支持集中的概率;若高质量行为从未出现在候选中,离线目标无法凭空探索出来。
DPO 深读把 KL 正则化、Bradley–Terry 偏好似然和 reference policy 的关系放回原论文推导,并给出带 response mask 的紧凑实现;更长的算法脉络见后训练与对齐。
从 KL 正则化到最优策略¶
对固定 prompt \(x\),考虑
并满足 \(\sum_y\pi(y\mid x)=1\)。加入 Lagrange multiplier,令关于 \(\pi(y\mid x)\) 的导数为零:
整理得
其中 \(Z(x)\) 只依赖 prompt。因此隐式 reward 可写为
对同一 prompt 的 chosen/rejected 做差,\(\log Z(x)\) 抵消。这一步把 Bradley–Terry 偏好与 policy/reference log-ratio 连接起来。
DPO¶
定义
Direct Preference Optimization 使用
这个推导依赖:
- Bradley–Terry 风格相对偏好;
- KL 正则化的最优策略关系;
- policy 与 reference 对回答有可比较 support;
- chosen 与 rejected 条件在同一 prompt 和模板上;
- 离线 pair 足以覆盖希望提高的行为。
DPO 避免显式 reward model 和在线 rollout,不等于没有奖励假设,也不自动解决标签噪声、分布外回答或 benchmark 污染。
DPO 的可执行语义¶
下面的 reference 接收已经与目标 token 对齐的 logits、token ID 和 response mask,先得到每条回答的序列 log-probability,再计算逐 pair 的 DPO loss。输出保留 batch 维,便于在外层选择样本权重和全局归约;normalize=True 明确切换到 token mean,不能在实验中静默改变。
import torch
import torch.nn.functional as F
def response_logp(logits, tokens, response_mask, normalize=False):
if logits.ndim < 2 or logits.shape[:-1] != tokens.shape or tokens.shape != response_mask.shape:
raise ValueError("logits, tokens and response mask must share batch and time axes")
valid = response_mask.bool()
if torch.any(valid.sum(-1) == 0): raise ValueError("every response needs a target token")
row_logits = logits.reshape(-1, logits.size(-2), logits.size(-1))
row_tokens = tokens.reshape(-1, tokens.size(-1))
row_valid = valid.reshape(-1, valid.size(-1))
output = []
for current_logits, current_tokens, current_valid in zip(
row_logits, row_tokens, row_valid):
selected_logits = current_logits[current_valid]
selected_tokens = current_tokens[current_valid]
logp = F.log_softmax(selected_logits, -1)
value = logp.gather(-1, selected_tokens[:, None]).sum()
output.append(value / current_valid.sum() if normalize else value)
return torch.stack(output).reshape(tokens.shape[:-1])
def dpo_loss(pi_w, pi_l, ref_w, ref_l, beta):
margin = (pi_w - ref_w) - (pi_l - ref_l)
return F.softplus(-beta * margin)
z = torch.tensor([[[float("nan")] * 2, [0., 2.], [2., 0.]]])
y, m = torch.tensor([[-100, 1, 0]]), torch.tensor([[False, True, True]])
base = response_logp(z, y, m)
assert torch.isfinite(base).all()
neutral = dpo_loss(base, base, base, base, beta=0.1)
assert torch.allclose(neutral, torch.full_like(neutral, torch.log(torch.tensor(2.))))
assert torch.all(dpo_loss(base + 1, base, base, base, 0.1) < neutral)
rejected = False
try:
response_logp(z, y, torch.zeros_like(m), normalize=True)
except ValueError:
rejected = True
assert rejected
核心不变量是 prompt 与 padding 在 log_softmax 之前被排除,masked -100 和 NaN 均不参与目标;每条回答若没有有效 token 则拒绝。policy/reference 还必须采用同一 token space,且 chosen margin 增大时 loss 下降。这里没有实现 chat template、label shift、分布式加权或 reference forward;生产训练必须对四组 log-probability 使用完全相同的序列语义。
序列 log-probability¶
回答概率为
总和、token mean 或其他长度校正不是等价实现:
- sum 对每个 token 累积 log-ratio,长回答的幅度通常更大;
- mean 改变目标,使每个回答近似等权,但也改变原始序列概率语义;
- 外加长度项 把长度偏好显式建模,便于审计。
选择必须作为目标函数的一部分记录。prompt、padding、tool observation 和非策略 token 不得混入回答 log-prob。
相关目标¶
IPO¶
A General Theoretical Paradigm to Understand Learning from Human Preferences 从更一般的偏好优化框架分析 RLHF 与 DPO,并提出 Identity Preference Optimization。常见参数化下可把目标写成让 \(h_\theta\) 接近固定 margin 的平方损失;具体 margin 与温度 convention 绑定,不能只复制公式而混用另一实现的 \(\beta\)。
IPO 的意义不是“DPO 的无 sigmoid 版本”,而是改变了隐式偏好变换和大 margin pair 的梯度行为。
KTO¶
KTO 接受单样本 desirable/undesirable 标签,并相对 reference 构造效用。它适合没有天然 pair 的反馈,但需要处理正负类别不平衡、reference 基准和标签语义;将独立好坏标签随意配对不一定更合理。
SimPO¶
SimPO 去掉显式 reference,并使用平均 log-probability 与目标 margin。其核心形式可写成
reference-free 和 length-normalized 是新的建模选择,不是免费简化;它们改变锚点与长度归纳偏置,必须与 DPO 在相同数据和调参预算下比较。
三者可以共用 chosen/rejected 的序列级输入,但不能共用未声明的 reduction:DPO 与 IPO 的 pi_*、ref_* 通常是 response token log-probability 之和;SimPO 的 pi_* 在进入函数前已经按各自 response 长度取 mean。
import torch
import torch.nn.functional as F
def preference_objective(pi_chosen, pi_rejected, ref_chosen, ref_rejected,
kind, beta=.1, margin=0.):
if beta <= 0:
raise ValueError("beta must be positive")
policy_gap = pi_chosen - pi_rejected
if kind == "simpo":
return F.softplus(-(beta * policy_gap - margin))
if ref_chosen is None or ref_rejected is None:
raise ValueError(f"{kind} requires a reference policy")
relative_gap = policy_gap - (ref_chosen - ref_rejected)
if kind == "dpo":
return F.softplus(-beta * relative_gap)
if kind == "ipo":
return (relative_gap - 1 / (2 * beta)).square()
raise ValueError(kind)
zero = torch.tensor([0.])
neutral = preference_objective(zero, zero, zero, zero, "dpo", beta=.5)
better = preference_objective(torch.tensor([1.]), zero, zero, zero, "dpo", beta=.5)
assert better < neutral
ipo = preference_objective(torch.tensor([1.]), zero, zero, zero, "ipo", beta=.5)
torch.testing.assert_close(ipo, zero)
simpo = preference_objective(torch.tensor([2.]), zero, None, None, "simpo", beta=.5, margin=.3)
torch.testing.assert_close(simpo, F.softplus(torch.tensor([-.7])))
DPO 的 logistic loss 会持续奖励更大 relative gap;IPO 在 convention 对应的有限 target 处最小;SimPO 去掉 reference,并显式引入长度归一与 margin。函数返回逐 pair loss,样本权重、ties 和分布式分母留在外层。三种目标与 reduction 的完整小张量实验见训练目标实现。
数据与版本契约¶
prompt, pair/list IDs and grouped split
chosen / rejected source and generator policy
preference protocol, ties and disagreement
policy and reference exact revisions
tokenizer, chat template and truncation
response mask and sum/mean normalization
beta / margin and implementation convention
data filtering and contamination audit
reference 若使用不同 tokenizer 或模板,log-ratio 不再位于同一 action space。chosen/rejected 被不同截断规则处理,也会制造伪偏好。
正确性与失效¶
- 把 \(\pi_{\text{ref}}\) 当可省略常数:它随回答变化,定义偏离坐标。
- policy/reference 模板不同:相同文本对应不同 token 条件分布。
- sum 与 mean 静默切换:长度偏置和梯度尺度改变。
- pair 随机切分:同一 prompt 或候选泄漏到测试。
- 只保留大 margin pair:可能偏向容易的风格差异,降低信息量。
- 离线数据来自单一旧策略:新 policy 进入未覆盖区域后无监督。
- 标签强度被忽略:tie 和高分歧 pair 被当成确定偏好。
- reference-free 被写成无锚点:数据分布和 margin 仍是隐式锚点。
- 只报 judge win rate:长度、格式和 judge 偏好可能共同抬分。
何时不应使用离线偏好¶
当任务需要发现数据中没有的新解、当前策略已远离 pair 生成策略、可执行 reward 能低成本在线获得,或偏好主要针对多步环境终态时,应考虑在线 RL、搜索或重新收集当前策略数据。若只有可靠示范而没有有意义的相对判断,SFT 更直接。
验证¶
- 用手算 log-prob 检查 DPO logit 的 policy/reference/chosen/rejected 符号。
- policy 等于 reference 时,\(h_\theta=0\)。
- prompt 与 padding token 对 response log-prob 没有贡献。
- 同时报 sum/mean log-ratio、回答长度和每个 slice 的 margin。
- 按 prompt、生成器、长度、语言和分歧程度分层评测。
- 比较 SFT、DPO、IPO/KTO/SimPO 与不训练 baseline,固定数据和调参预算。
- 在当前 policy 新采样回答上重新收集小规模人工偏好,检查离线外推。
- 用事实性、可执行成功、安全、校准和多样性验证 judge 胜率。
奖励数据与偏差见奖励建模,需要在线探索时见在线 RL,目标函数的最小对照见训练目标实现。