SimPO:一种简洁高效的大模型对齐新范式
在大语言模型能力飞速提升的今天,一个核心问题愈发凸显——我们如何让这些'聪明'的模型输出真正符合人类价值观和实际需求的回答?这不仅是技术挑战,更是构建可信 AI 系统的基石。
传统方法如监督微调(SFT)虽然能教会模型完成任务,却难以捕捉复杂的人类偏好。而基于人类反馈的强化学习(RLHF)虽效果显著,但其三阶段流程——奖励建模、PPO 优化、策略更新——不仅实现复杂,训练过程也极不稳定,动辄数天的实验周期让许多团队望而却步。
于是,像 DPO 这样无需显式奖励模型的方法应运而生,大幅降低了对齐门槛。然而,DPO 仍存在生成内容偏短、信息密度不足的问题。正是在这一背景下,SimPO(Simple Preference Optimization) 横空出世,以极简的设计实现了更优的对齐效果。
它由浙江大学与阿里巴巴通义实验室联合提出,如今已深度集成进 ms-swift 这一全链路大模型开发框架中,开发者可快速上手实验。
SimPO 为什么有效?
SimPO 的核心思想非常直观:在对比学习的基础上,引入长度归一化机制,鼓励模型生成更完整、更有价值的回答。
它的输入是经典的三元组结构:{prompt, chosen, rejected},即同一个问题下,人类更偏好的回答与较差的回答。目标很明确——让模型更倾向于输出被选中的那个答案。
但与 DPO 不同的是,SimPO 没有简单地比较两个回答的整体似然差,而是将每个回答的对数概率除以其 token 长度,再进行对比。这种'单位长度下的平均得分'设计,巧妙地解决了长期困扰对齐训练的一个顽疾:短应回答因计算路径短、置信度高而被错误偏好。
试想这样一个场景:用户问'请解释相对论的基本原理',模型若回复'这是一个物理理论'显然不如一段数百字的详细阐述来得有用。但在传统 DPO 中,由于短句更容易获得高似然分数,模型反而可能学会'偷懒'。而 SimPO 通过长度归一化,使得长而详尽的回答在对比中更具优势,从而自然引导模型走向高质量输出。
其损失函数形式简洁优美:
$$ \mathcal{\text{SimPO}} = -\log \sigma\left( \beta \left[ \frac{\log p(y_c|x)}{|y_c|} - \frac{\log p(y_r|x)}{|y_r|} \right] + \gamma \right) $$
其中 $\beta$ 控制偏好强度,$\gamma$ 是一个可调节的边界项,用于设定最小偏好差距。整个训练过程无需额外的奖励模型,也不依赖复杂的 PPO 策略梯度更新,仅需标准反向传播即可完成。
这意味着什么?意味着你可以在现有的微调框架中,只需替换一行损失函数,就能接入这一前沿算法。没有额外模块,没有多阶段训练,也没有令人头疼的超参调试。
import torch
import torch.nn.functional as F
def simpo_loss(policy_logits_chosen, policy_logits_rejected, labels_chosen, labels_rejected, beta=0.1, gamma=0.5):
def get_logps(logits, labels):
log_probs = F.log_softmax(logits, dim=-1)
per_token_logps = torch.gather(log_probs, dim=-1, index=labels.unsqueeze(-1)).squeeze(-1)
return per_token_logps.sum(-1)
logps_chosen = get_logps(policy_logits_chosen, labels_chosen)
logps_rejected = get_logps(policy_logits_rejected, labels_rejected)
len_chosen = labels_chosen.ne(-).(-).clamp(=)
len_rejected = labels_rejected.ne(-).(-).clamp(=)
normalized_logps_diff = (logps_chosen / len_chosen) - (logps_rejected / len_rejected)
losses = -F.logsigmoid(beta * normalized_logps_diff + gamma)
losses.mean()

