1. 引言:为什么需要"对齐"?

1.1 从 SFT 到 RLHF 的进化

如果说预训练给学生灌输了整个互联网的知识,SFT 教会学生"听人话做事",那么RLHF 就是让学生学会**“做正确的事”**——不仅按指令,还要符合人类的价值观、偏好与安全约束。

训练范式的演进:

预训练 (Pre-training)
  ↓ 学习"语言 + 世界知识"
  ↓ 能力:续写、模仿
  
SFT (Supervised Fine-Tuning)
  ↓ 学习"指令 → 回答"
  ↓ 能力:助手、对话
  
RLHF (Reinforcement Learning from Human Feedback)
  ↓ 学习"偏好 + 价值观"
  ↓ 能力:有用 (Helpful) + 无害 (Harmless) + 诚实 (Honest)

1.2 SFT 解决不了的问题

即使做完 SFT,模型仍会表现出:

问题表现例子
胡编乱造一本正经说错信息编造不存在的论文
有害输出教用户做危险的事制造武器、绕过安全
过度冗长不必要的客套话“作为 AI 助手…”
风格不适配不符合期望语气客服场景过于正式
幻觉 + 自信错误但充满信心错误医学建议

这些问题是主观偏好价值观判断,无法用"标准答案"完全标注——SFT 的最大似然损失没有偏好信号

1.3 RLHF 的核心思想

两阶段训练

  1. 奖励建模(Reward Modeling):学习一个人类偏好的"打分器"
  2. 策略优化(Policy Optimization):用 RL 让 LLM 生成高分回答
┌──────────────────────────────────────────────────────────────┐
│                        RLHF 流程总览                          │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  ① 收集偏好数据                                              │
│     prompt + response_A + response_B + 人类标注 A>B          │
│                          ↓                                   │
│  ② 训练奖励模型 RM(y|x)                                      │
│     loss = -log σ( RM(y_w|x) - RM(y_l|x) )                  │
│                          ↓                                   │
│  ③ 用 PPO 优化 SFT 模型                                      │
│     reward = RM(y|x) - β·KL(π_θ || π_ref)                   │
│                                                              │
└──────────────────────────────────────────────────────────────┘

2. 偏好数据:从反馈到标注

2.1 数据形式

每个偏好样本是一个三元组四元组

(x, yw, yl) (x,\ y_w,\ y_l) (x, yw, yl)

其中:

  • xxx:提示(prompt)
  • ywy_wyw:被人类标注为"更好"的回答(winner)
  • yly_lyl:被人类标注为"更差"的回答(loser)

2.2 数据来源

preference_data_sources = {
    # 1. 人类标注
    "human_annotation": "标注员对同一 prompt 的多个回答进行排序",
    
    # 2. AI 反馈(RLAIF / Constitutional AI)
    "ai_feedback": "用强模型按宪法原则评判弱模型回答",
    
    # 3. 用户隐式反馈
    "implicit_feedback": "点赞、采纳、停留时间、复制按钮",
    
    # 4. 合成数据
    "synthetic": "用 prompt 模板生成 y_w, 再故意制造 y_l 错误",
}

2.3 数据质量的关键

维度影响工程经验
标注一致性RM 学到正确偏好多标注员 + Krippendorff α
多样性RM 不偏科跨领域、跨任务、跨长度
边界 caseRM 鲁棒性故意加入对抗样本
失败模式覆盖知道"哪里不好"红队测试数据

[!WARNING] 数据陷阱
标注员有风格偏好(偏好长回答),导致 RM 学会"越长越好"。解决:在 prompt 中明确告知"长度不是评判标准"。


3. 奖励模型(Reward Model)

3.1 Bradley-Terry 模型

假设人类偏好遵循 Bradley-Terry 模型:

P(yw≻yl∣x)=exp⁡(R(x,yw))exp⁡(R(x,yw))+exp⁡(R(x,yl))=σ(R(x,yw)−R(x,yl)) P(y_w \succ y_l \mid x) = \frac{\exp(R(x, y_w))}{\exp(R(x, y_w)) + \exp(R(x, y_l))} = \sigma\big(R(x, y_w) - R(x, y_l)\big) P(ywylx)=exp(R(x,yw))+exp(R(x,yl))exp(R(x,yw))=σ(R(x,yw)R(x,yl))

其中 R(x,y)R(x, y)R(x,y) 是真实的"人类偏好分数"。我们用一个神经网络 RθR_\thetaRθ 来近似它。

3.2 损失函数

对数似然:

LRM=−E(x,yw,yl)∼D[log⁡σ(Rθ(x,yw)−Rθ(x,yl))] \mathcal{L}_{\text{RM}} = -\mathbb{E}_{(x,y_w,y_l)\sim D}\Big[\log \sigma\big(R_\theta(x,y_w) - R_\theta(x,y_l)\big)\Big] LRM=E(x,yw,yl)D[logσ(Rθ(x,yw)Rθ(x,yl))]

直觉:拉大 winner 和 loser 的分数差

3.3 实现

import torch
import torch.nn as nn
from transformers import AutoModelForCausalLM

class RewardModel(nn.Module):
    """
    基于 LLM 的奖励模型。
    通常是一个 LLM + 一个标量回归头,预测回答的"人类偏好分数"。
    """
    def __init__(self, base_model_path: str, cache_dir: str = None):
        super().__init__()
        # 加载与 SFT 相同架构的 LLM
        self.backbone = AutoModelForCausalLM.from_pretrained(
            base_model_path, cache_dir=cache_dir, torch_dtype=torch.bfloat16
        )
        hidden_size = self.backbone.config.hidden_size
        # 标量奖励头
        self.score_head = nn.Linear(hidden_size, 1, bias=False)
        # 初始化为接近 0(让初始分数居中)
        nn.init.zeros_(self.score_head.weight)

    def forward(self, input_ids: torch.Tensor, attention_mask: torch.Tensor):
        """
        输入: 拼接 [prompt; response]
        输出: 最后一个 token 的标量分数
        """
        outputs = self.backbone(
            input_ids=input_ids,
            attention_mask=attention_mask,
            output_hidden_states=True,
        )
        # 取每个序列最后一个有效 token 的隐藏状态
        last_hidden = outputs.hidden_states[-1]  # (B, L, H)
        sequence_lengths = attention_mask.sum(dim=1) - 1
        batch_idx = torch.arange(last_hidden.size(0), device=last_hidden.device)
        last_token_hidden = last_hidden[batch_idx, sequence_lengths]
        score = self.score_head(last_token_hidden).squeeze(-1)
        return score

class RewardModelLoss(nn.Module):
    """Bradley-Terry 偏好损失"""
    def forward(self, score_w: torch.Tensor, score_l: torch.Tensor) -> torch.Tensor:
        return -torch.nn.functional.logsigmoid(score_w - score_l).mean()

3.4 训练实践

def train_reward_model(model, dataset, optimizer, epochs=2, batch_size=4):
    for epoch in range(epochs):
        for batch in dataset:
            # batch 包含 (x, y_w, y_l) 三组 tokenized 输入
            score_w = model(batch["input_ids_w"], batch["attention_mask_w"])
            score_l = model(batch["input_ids_l"], batch["attention_mask_l"])
            loss = -torch.nn.functional.logsigmoid(score_w - score_l).mean()
            
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

3.5 评估 RM 的方法

def evaluate_rm(model, eval_pairs):
    """
    评估 RM 的"判别准确率":
    在测试集上,正确判定 y_w > y_l 的比例。
    """
    correct = 0
    for pair in eval_pairs:
        score_w = model(pair["x_w"])
        score_l = model(pair["x_l"])
        if score_w > score_l:
            correct += 1
    return correct / len(eval_pairs)

4. PPO:经典的策略优化算法

4.1 为什么不用 SFT 直接拟合 RM?

直觉:用 RM 给回答打分,然后 SFT 用 reward⋅p(y∣x)\text{reward} \cdot p(y|x)rewardp(yx) 加权拟合。问题

  1. 分布偏移:RM 只在 SFT 数据上学过,对"模型自己生成的分布"外推差
  2. Reward hacking:模型学会 RM 的盲点(比如生成"看起来很好但其实乱码"的回答)
  3. 无约束:可能偏离原始 SFT 模型,丧失能力

4.2 RL 基础

把 LLM 视为一个自回归策略 πθ(y∣x)\pi_\theta(y|x)πθ(yx)

πθ(y∣x)=∏t=1∣y∣πθ(yt∣x,y<t) \pi_\theta(y|x) = \prod_{t=1}^{|y|} \pi_\theta(y_t | x, y_{<t}) πθ(yx)=t=1yπθ(ytx,y<t)

目标是最大化期望奖励:

J(θ)=Ey∼πθ[R(x,y)] J(\theta) = \mathbb{E}_{y\sim\pi_\theta}\big[R(x,y)\big] J(θ)=Eyπθ[R(x,y)]

4.3 KL 约束:锚定参考策略

为防止 πθ\pi_\thetaπθ 偏离太远,加入 KL 惩罚:

JRLHF(θ)=Ey∼πθ[R(x,y)−β KL(πθ(⋅∣x) ∥ πref(⋅∣x))] J_{\text{RLHF}}(\theta) = \mathbb{E}_{y\sim\pi_\theta}\Big[R(x,y) - \beta\,\text{KL}\big(\pi_\theta(\cdot|x)\,\|\,\pi_{\text{ref}}(\cdot|x)\big)\Big] JRLHF(θ)=Eyπθ[R(x,y)βKL(πθ(x)πref(x))]

其中 πref\pi_{\text{ref}}πref 是 SFT 模型(参数冻结),β\betaβ 控制约束强度。

[!TIP] 直觉
KL 项相当于"不许忘本"。模型可以优化 RM,但不能变成另一个完全不同的模型。

4.4 PPO 目标函数

PPO 是信赖域优化的实用版本:

LPPO(θ)=−Et[min⁡(rt(θ) At, clip(rt(θ),1−ϵ,1+ϵ) At)] \mathcal{L}_{\text{PPO}}(\theta) = -\mathbb{E}_t\Big[\min\big(r_t(\theta)\,A_t,\ \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon)\,A_t\big)\Big] LPPO(θ)=Et[min(rt(θ)At, clip(rt(θ),1ϵ,1+ϵ)At)]

其中:

  • 重要性比率:rt(θ)=πθ(at∣st)πθold(at∣st)r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)}rt(θ)=πθold(atst)πθ(atst)
  • 优势函数:AtA_tAt(用 GAE 或 critic 估计)
  • ϵ\epsilonϵ 通常为 0.2

clip 机制:限制新旧策略的比率不超出 [1−ϵ,1+ϵ][1-\epsilon, 1+\epsilon][1ϵ,1+ϵ],避免一次更新步子过大。

4.5 RM + KL 的奖励设计

PPO 中每个 token 的"奖励":

rt={R(x,y)−β (log⁡πθ(yt∣⋅)−log⁡πref(yt∣⋅))t=T−β (log⁡πθ(yt∣⋅)−log⁡πref(yt∣⋅))t<T r_t = \begin{cases} R(x, y) - \beta\,\big(\log \pi_\theta(y_t|\cdot) - \log \pi_{\text{ref}}(y_t|\cdot)\big) & t = T \\ -\beta\,\big(\log \pi_\theta(y_t|\cdot) - \log \pi_{\text{ref}}(y_t|\cdot)\big) & t < T \end{cases} rt={R(x,y)β(logπθ(yt)logπref(yt))β(logπθ(yt)logπref(yt))t=Tt<T

只有最后一个 token获得真实 RM 分数,前面所有 token 的"奖励"只是负的 KL 惩罚——鼓励每个位置的策略不偏离参考。

4.6 PPO for LLM 的完整实现

import torch
import torch.nn.functional as F

def compute_gae(rewards, values, dones, gamma=0.99, lam=0.95):
    """广义优势估计 GAE"""
    advantages = torch.zeros_like(rewards)
    last_adv = 0
    T = rewards.size(1)
    for t in reversed(range(T)):
        if t == T - 1:
            next_value = 0
        else:
            next_value = values[:, t + 1]
        nonterminal = 1.0 - dones[:, t]
        delta = rewards[:, t] + gamma * next_value * nonterminal - values[:, t]
        last_adv = delta + gamma * lam * nonterminal * last_adv
        advantages[:, t] = last_adv
    returns = advantages + values
    return advantages, returns

def ppo_loss(
    log_probs_new,      # 新策略对每个 token 的 log prob: (B, T)
    log_probs_old,      # 旧策略(rollout 时)的 log prob
    advantages,         # GAE 优势
    clip_ratio=0.2,
):
    """PPO clipped surrogate loss"""
    ratio = torch.exp(log_probs_new - log_probs_old)
    surr1 = ratio * advantages
    surr2 = torch.clamp(ratio, 1 - clip_ratio, 1 + clip_ratio) * advantages
    policy_loss = -torch.min(surr1, surr2).mean()
    return policy_loss

def rlhf_train_step(
    policy_model,        # π_θ,待优化
    ref_model,           # π_ref,冻结
    reward_model,        # R(x,y)
    critic,              # 价值网络 V(s)
    batch,               # prompt 列表
    beta=0.1,            # KL 系数
    clip_ratio=0.2,
):
    """
    RLHF 单步训练流程(简化的 PPO):
    1. 用当前 policy 生成回答(rollout)
    2. 用 RM 评分
    3. 计算 KL 惩罚
    4. 用 GAE 计算优势
    5. PPO 更新 policy 与 critic
    """
    prompts = batch["prompts"]
    
    # === 1. Rollout:用当前策略生成回答 ===
    with torch.no_grad():
        responses, response_mask = policy_model.generate(
            prompts, max_new_tokens=256, do_sample=True, temperature=1.0
        )
    
    # === 2. 计算旧策略的 log prob(用于重要性采样) ===
    with torch.no_grad():
        log_probs_old = compute_log_probs(policy_model, responses)
    
    # === 3. 用奖励模型打分 ===
    with torch.no_grad():
        rewards = reward_model(prompts + responses)
    
    # === 4. 计算 KL 惩罚(per token) ===
    with torch.no_grad():
        log_probs_ref = compute_log_probs(ref_model, responses)
    kl_per_token = log_probs_old - log_probs_ref  # = log(π_θ / π_ref)
    
    # 最后一个 token 获得 RM 分数;其余 token 仅 KL 惩罚
    T = responses.size(1)
    token_rewards = -beta * kl_per_token
    token_rewards[:, -1] += rewards
    
    # === 5. GAE 计算优势 ===
    with torch.no_grad():
        values = critic(responses)
    dones = torch.zeros_like(token_rewards)
    dones[:, -1] = 1.0  # 序列结束
    advantages, returns = compute_gae(token_rewards, values, dones)
    advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)
    
    # === 6. PPO 更新(通常做 K_mini_epochs 次) ===
    for _ in range(4):
        log_probs_new = compute_log_probs(policy_model, responses)
        policy_loss = ppo_loss(log_probs_new, log_probs_old, advantages, clip_ratio)
        
        value_pred = critic(responses)
        value_loss = F.mse_loss(value_pred, returns)
        
        loss = policy_loss + 0.5 * value_loss
        loss.backward()
        # ... optimizer.step()

4.7 PPO 的工程痛点

痛点原因缓解
显存爆炸同时持有 policy、ref、RM、criticLoRA + 共享参数
训练不稳定大 reward 方差Reward 归一化、KL warmup
Reward hacking模型找到 RM 漏洞多 RM ensemble
每步 4 模型前向vLLM rollout
崩溃极端 KL 时崩早停 + checkpoint rollback

[!WARNING] 经典教训
InstructGPT 论文报告:RLHF 训练中,约 10-30% 的训练会因奖励坍缩或 KL 爆炸而中断。需要持续监控。


5. GRPO:DeepSeek 提出的高效替代

5.1 动机

PPO 的最大开销:

  • 一个额外的 critic 网络(与 policy 同尺寸)
  • 每个 prompt 一次采样 → GAE 优势估计
  • 4 个模型同时在显存

5.2 GRPO 核心思想

**GRPO(Group Relative Policy Optimization)**由 DeepSeek 提出:

不需要 critic,对同一 prompt 采样一组回答,用组内相对排名作为优势。

5.3 算法流程

对每个 prompt x:
  1. 用 π_θ 采样 G 个回答 {y_1, y_2, ..., y_G}
  2. 用 RM 给每个回答打分 {r_1, r_2, ..., r_G}
  3. 组内归一化得到优势:
     A_i = (r_i - mean(r)) / std(r)
  4. 用优势做 PPO 风格更新

5.4 优势计算

Ai=ri−mean(r)std(r),i=1,…,G A_i = \frac{r_i - \text{mean}(\mathbf{r})}{\text{std}(\mathbf{r})}, \quad i = 1,\ldots,G Ai=std(r)rimean(r),i=1,,G

直觉:比平均好的回答获得正优势,比平均差的获得负优势。无需 critic,无需 GAE。

5.5 完整损失

LGRPO(θ)=−E[1G∑i=1G(min⁡(riAi,clip(ri,1−ϵ,1+ϵ)Ai)−β KL(πθ∥πref))] \mathcal{L}_{\text{GRPO}}(\theta) = -\mathbb{E}\Big[\frac{1}{G}\sum_{i=1}^{G}\big(\min(r_i A_i, \text{clip}(r_i, 1-\epsilon, 1+\epsilon) A_i) - \beta\,\text{KL}(\pi_\theta \| \pi_{\text{ref}})\big)\Big] LGRPO(θ)=E[G1i=1G(min(riAi,clip(ri,1ϵ,1+ϵ)Ai)βKL(πθπref))]

其中重要性比率:

ri(θ)=πθ(yi∣x)πθold(yi∣x) r_i(\theta) = \frac{\pi_\theta(y_i|x)}{\pi_{\theta_{\text{old}}}(y_i|x)} ri(θ)=πθold(yix)πθ(yix)

5.6 GRPO 实现

import torch
import torch.nn.functional as F

def grpo_loss(
    log_probs_new,       # (B, G, T) — B 个 prompt,每个 G 个回答
    log_probs_old,       # 同上
    rewards_group,       # (B, G) — 组内每个回答的 RM 分数
    ref_log_probs,       # (B, G, T)
    beta=0.04,
    clip_ratio=0.2,
):
    """
    GRPO 损失(DeepSeek-R1 风格)。
    log_probs_new, log_probs_old, ref_log_probs 形状: (B, G, T)
    rewards_group 形状: (B, G)
    """
    # === 组内归一化得到优势 ===
    mean_r = rewards_group.mean(dim=-1, keepdim=True)
    std_r = rewards_group.std(dim=-1, keepdim=True) + 1e-8
    advantages = (rewards_group - mean_r) / std_r  # (B, G)
    
    # === 重要性比率 ===
    # 这里我们用平均 log prob(per sequence)作为比率
    ratio = torch.exp(
        (log_probs_new - log_probs_old).sum(dim=-1)
    )  # (B, G)
    
    # === PPO-style clipped surrogate ===
    surr1 = ratio * advantages
    surr2 = torch.clamp(ratio, 1 - clip_ratio, 1 + clip_ratio) * advantages
    policy_loss = -torch.min(surr1, surr2).mean()
    
    # === KL 惩罚 ===
    kl = (log_probs_new - ref_log_probs).sum(dim=-1).mean()
    kl_loss = beta * kl
    
    total_loss = policy_loss + kl_loss
    return total_loss, {"policy_loss": policy_loss.item(), "kl": kl.item()}

5.7 PPO vs GRPO 对比

维度PPOGRPO
Critic需要不需要
优势估计GAE + Value Net组内排名
显存4 模型2 模型 (policy + ref)
采样数1/GG(按 prompt)
训练稳定较稳定需要足够大的 G
代表工作InstructGPT, ChatGPTDeepSeek-R1

6. DPO:免强化学习的对齐

6.1 核心洞见

DPO(Direct Preference Optimization)由 Stanford 在 2023 年提出,关键问题是:

能不能绕过 RM 和 RL,直接用偏好数据训练 LLM?

答案是:可以。RLHF 的目标函数有一个闭式解

6.2 推导:从 RLHF 到 DPO

RLHF 目标:

max⁡πEx,y∼π[R(x,y)]−β KL(π(⋅∣x) ∥ πref(⋅∣x)) \max_\pi \mathbb{E}_{x,y\sim\pi}\Big[R(x,y)\Big] - \beta\,\text{KL}\big(\pi(\cdot|x)\,\|\,\pi_{\text{ref}}(\cdot|x)\big) πmaxEx,yπ[R(x,y)]βKL(π(x)πref(x))

其最优解为:

π∗(y∣x)=1Z(x)πref(y∣x)exp⁡ ⁣(1βR(x,y)) \pi^*(y|x) = \frac{1}{Z(x)}\pi_{\text{ref}}(y|x)\exp\!\Big(\frac{1}{\beta}R(x,y)\Big) π(yx)=Z(x)1πref(yx)exp(β1R(x,y))

其中 Z(x)=∑yπref(y∣x)exp⁡(R(x,y)/β)Z(x) = \sum_y \pi_{\text{ref}}(y|x)\exp(R(x,y)/\beta)Z(x)=yπref(yx)exp(R(x,y)/β) 是归一化常数。

反解出 RM:

R(x,y)=βlog⁡π∗(y∣x)πref(y∣x)+βlog⁡Z(x) R(x,y) = \beta\log\frac{\pi^*(y|x)}{\pi_{\text{ref}}(y|x)} + \beta\log Z(x) R(x,y)=βlogπref(yx)π(yx)+βlogZ(x)

代入 Bradley-Terry 偏好模型(Z(x)Z(x)Z(x) 抵消):

P(yw≻yl∣x)=σ(βlog⁡πθ(yw∣x)πref(yw∣x)−βlog⁡πθ(yl∣x)πref(yl∣x)) P(y_w \succ y_l | x) = \sigma\Big(\beta\log\frac{\pi_\theta(y_w|x)}{\pi_{\text{ref}}(y_w|x)} - \beta\log\frac{\pi_\theta(y_l|x)}{\pi_{\text{ref}}(y_l|x)}\Big) P(ywylx)=σ(βlogπref(ywx)πθ(ywx)βlogπref(ylx)πθ(ylx))

这就是 DPO 损失

6.3 DPO 损失

LDPO(θ)=−E(x,yw,yl)[log⁡σ(β Δθ(x,yw,yl))] \mathcal{L}_{\text{DPO}}(\theta) = -\mathbb{E}_{(x,y_w,y_l)}\Big[\log\sigma\big(\beta\,\Delta_\theta(x,y_w,y_l)\big)\Big] LDPO(θ)=E(x,yw,yl)[logσ(βΔθ(x,yw,yl))]

其中:

Δθ=log⁡πθ(yw∣x)πref(yw∣x)−log⁡πθ(yl∣x)πref(yl∣x) \Delta_\theta = \log\frac{\pi_\theta(y_w|x)}{\pi_{\text{ref}}(y_w|x)} - \log\frac{\pi_\theta(y_l|x)}{\pi_{\text{ref}}(y_l|x)} Δθ=logπref(ywx)πθ(ywx)logπref(ylx)πθ(ylx)

6.4 关键优势

RLHF 流程:
  偏好数据 → 训练 RM → PPO 优化 policy
  (3 个阶段,复杂)

DPO 流程:
  偏好数据 → 直接训练 policy
  (1 个阶段,简单)
维度RLHF/PPODPO
训练阶段3(RM、critic、policy)1(policy)
显存4 模型2 模型
超参β\betaβ + clip + GAE + KL只需 β\betaβ
在线采样需要不需要(offline)
稳定性易崩稳定
性能略好(一般)与 PPO 相当
代表模型GPT-4, Claude 2Llama 3 Instruct, Mistral

6.5 DPO 实现

import torch
import torch.nn.functional as F

def compute_log_probs(model, input_ids, attention_mask, response_mask):
    """计算回答部分的 log π(y|x) 之和"""
    outputs = model(input_ids=input_ids, attention_mask=attention_mask)
    logits = outputs.logits[:, :-1, :]  # 预测下一个 token
    labels = input_ids[:, 1:]
    log_probs = F.log_softmax(logits, dim=-1)
    # 取出真实 token 的 log prob
    token_log_probs = log_probs.gather(2, labels.unsqueeze(-1)).squeeze(-1)
    # 只对 response 部分求和(mask 掉 prompt)
    response_token_log_probs = token_log_probs * response_mask[:, 1:]
    return response_token_log_probs.sum(dim=-1)

def dpo_loss(
    policy_chosen_logps,    # π_θ(y_w|x) 对数和
    policy_rejected_logps,
    ref_chosen_logps,       # π_ref(y_w|x) 对数和
    ref_rejected_logps,
    beta=0.1,
):
    """
    DPO 损失。
    policy_chosen_logps: (B,)
    policy_rejected_logps: (B,)
    ref_chosen_logps: (B,)
    ref_rejected_logps: (B,)
    """
    # log π_θ(y|x) - log π_ref(y|x)
    policy_logratios = policy_chosen_logps - policy_rejected_logps
    ref_logratios = ref_chosen_logps - ref_rejected_logps
    
    # DPO 目标:β * (Δ_θ - Δ_ref)
    logits = beta * (policy_logratios - ref_logratios)
    
    # 偏好损失
    loss = -F.logsigmoid(logits).mean()
    
    # 额外监控:chosen reward margin
    chosen_rewards = beta * (policy_chosen_logps - ref_chosen_logps).detach()
    rejected_rewards = beta * (policy_rejected_logps - ref_rejected_logps).detach()
    
    return loss, {
        "chosen_rewards": chosen_rewards.mean().item(),
        "rejected_rewards": rejected_rewards.mean().item(),
        "accuracy": (chosen_rewards > rejected_rewards).float().mean().item(),
    }

6.6 DPO 训练循环

from torch.optim import AdamW

def train_dpo(policy_model, ref_model, dataloader, beta=0.1, lr=1e-6, epochs=1):
    optimizer = AdamW(policy_model.parameters(), lr=lr)
    ref_model.eval()  # 冻结
    for epoch in range(epochs):
        for batch in dataloader:
            # batch: {chosen: input_ids, mask, response_mask, rejected: ...}
            
            # Policy 对 chosen 和 rejected 的 log prob
            policy_chosen_logps = compute_log_probs(
                policy_model, batch["chosen_input_ids"], 
                batch["chosen_attention_mask"], batch["chosen_response_mask"]
            )
            policy_rejected_logps = compute_log_probs(
                policy_model, batch["rejected_input_ids"],
                batch["rejected_attention_mask"], batch["rejected_response_mask"]
            )
            
            # Ref(不计算梯度)
            with torch.no_grad():
                ref_chosen_logps = compute_log_probs(
                    ref_model, batch["chosen_input_ids"],
                    batch["chosen_attention_mask"], batch["chosen_response_mask"]
                )
                ref_rejected_logps = compute_log_probs(
                    ref_model, batch["rejected_input_ids"],
                    batch["rejected_attention_mask"], batch["rejected_response_mask"]
                )
            
            loss, metrics = dpo_loss(
                policy_chosen_logps, policy_rejected_logps,
                ref_chosen_logps, ref_rejected_logps,
                beta=beta,
            )
            
            optimizer.zero_grad()
            loss.backward()
            torch.nn.utils.clip_grad_norm_(policy_model.parameters(), 1.0)
            optimizer.step()
            
            print(f"loss={loss.item():.4f}  "
                  f"acc={metrics['accuracy']:.2f}  "
                  f"chosen_r={metrics['chosen_rewards']:.2f}  "
                  f"rejected_r={metrics['rejected_rewards']:.2f}")

6.7 DPO 的局限与变体

[!WARNING] DPO 偏差
DPO 容易过拟合到偏好数据,并出现"verbosity bias"——偏好变长但不一定变好。

主要变体:

算法改进点公式
IPO防止过拟合L=(log⁡πθ(yw)πref(yw)−log⁡πθ(yl)πref(yl)−12β)2\mathcal{L} = (\log\frac{\pi_\theta(y_w)}{\pi_{\text{ref}}(y_w)} - \log\frac{\pi_\theta(y_l)}{\pi_{\text{ref}}(y_l)} - \frac{1}{2\beta})^2L=(logπref(yw)πθ(yw)logπref(yl)πθ(yl)2β1)2
KTO不需要成对偏好(单回答 + 标签)Kahneman-Tversky 前景理论
SimPO移除 reference modelL=−log⁡σ(∣yw∣∣yw∣R(yw)−∣yl∣∣yl∣R(yl)−γ)\mathcal{L} = -\log\sigma(\frac{|y_w|}{|y_w|}R(y_w) - \frac{|y_l|}{|y_l|}R(y_l) - \gamma)L=logσ(ywywR(yw)ylylR(yl)γ)
ORPOSFT + DPO 联合联合 SFT 与 odds ratio 损失
CPOSFT + DPO 简化加 SFT 正则项

7. 进阶:从 RLHF 到 RLAIF / Constitutional AI

7.1 RLAIF (RL from AI Feedback)

动机:人类标注慢、贵、不一致。用强模型替代人类打标。

传统 RLHF:
  人类对 (y_A, y_B) 排序 → RM → PPO

RLAIF:
  GPT-4 对 (y_A, y_B) 按"宪法原则"排序 → RM → PPO

7.2 Constitutional AI (CAI)

Anthropic 提出,两阶段

阶段 1:监督式(SL-CAI)

1. 模型生成回答 y
2. 模型基于"宪法原则"(如"不要有害")批评 y
3. 模型根据批评改写 y'
4. 用 (prompt, y') 做监督微调

阶段 2:RL-CAI

1. 模型生成回答 y_A, y_B
2. 模型按宪法原则判断哪个更好
3. 用这些 AI 偏好训练 RM
4. PPO 训练

宪法原则示例:

## 宪法(Constitution)

1. 请选择更**有帮助**的回答。
2. 请选择更**诚实**的回答。
3. 请选择更**无害**的回答,不应协助暴力、欺骗。
4. 请避免**歧视性**内容。
5. 请避免**违反隐私**的回答。

7.3 自我奖励语言模型(Meta,2024)

迭代过程:
1. 用 CAI 生成偏好数据
2. 训练 RM
3. 用 DPO 训练 LLM
4. 重复:用更强的 LLM 重新打标
→ 模型自己当老师

8. 实战:完整 RLHF 训练流水线

8.1 三阶段流水线

┌──────────────────────────────────────────────────────────────┐
│                  完整 RLHF 训练流水线                          │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  Stage 0: Base Model (预训练 LLM)                            │
│     ↓                                                        │
│  Stage 1: SFT (监督微调)                                     │
│     ↓  用高质量指令数据训练 π_SFT                              │
│  Stage 2: Reward Model Training                              │
│     ↓  用偏好数据训练 R_θ                                     │
│  Stage 3: RL Fine-tuning (PPO / GRPO / DPO)                  │
│     ↓                                                        │
│  Final: Aligned Model                                        │
│                                                              │
└──────────────────────────────────────────────────────────────┘

8.2 推荐训练顺序(基于 Llama 2/3 论文经验)

1. 预训练(基模型) ............................. 1-3 月,~1万 GPU·小时
2. SFT(指令微调) ............................... 数小时
3. RLHF 阶段 A:采样 + RM 训练 .................. 数小时
4. RLHF 阶段 B:PPO/DPO ......................... 数十小时
5. 安全对齐(可选的第二轮 RLHF) ............... 数小时

8.3 关键超参表

超参推荐值备注
学习率(policy)1e-6 ~ 5e-6比 SFT 小一个数量级
学习率(RM)1e-5 ~ 5e-5RM 可以稍大
β\betaβ(KL 系数)0.04 ~ 0.2DPO 论文用 0.1
PPO clip ϵ\epsilonϵ0.2标准值
Rollout 采样数 GGG4 ~ 16GRPO 用更大
Mini-batch 大小1 ~ 4受显存限制
GAE λ\lambdaλ0.95
折扣 γ\gammaγ1.0序列级任务

8.4 监控指标

metrics_to_log = {
    # === RM 训练 ===
    "rm/loss": "RM Bradley-Terry 损失",
    "rm/accuracy": "RM 在测试集上判别准确率",
    "rm/score_margin": "winner - loser 平均分数差",
    
    # === PPO/DPO 训练 ===
    "policy/kl": "π_θ 与 π_ref 的 KL",
    "policy/reward": "RM 给当前生成回答的平均分",
    "policy/reward_std": "RM 分数方差",
    "policy/clip_fraction": "被 clip 的 token 比例",
    "policy/advantages": "优势均值",
    
    # === 任务指标 ===
    "eval/helpfulness": "GPT-4 评分(1-10)",
    "eval/harmlessness": "安全率",
    "eval/win_rate": "相对基模型的胜率",
}

8.5 常见 Failure Mode

┌────────────────────────┬──────────────────────────────────┐
│ 现象                    │ 原因 / 解决                       │
├────────────────────────┼──────────────────────────────────┤
│ KL 爆炸               │ β 太小 / LR 太大 → 增大 β、降低 LR │
│ Reward 坍缩到 0        │ RM 过拟合 → 加正则、做 ensemble    │
│ 模型输出变成乱码        │ Reward hacking → 多 RM、加规则   │
│ 模型变得太保守(拒绝一切)│ RM 偏负样本 → 重新平衡数据         │
│ 偏好过拟合(DPO)       │ 学习率太大 / epoch 多 → 减小       │
│ Verbosity bias(越长越好)│ RM 没控制长度 → 加长度归一化       │
└────────────────────────┴──────────────────────────────────┘

9. 算法全景对比

9.1 主流对齐方法

┌─────────────────────────────────────────────────────────────┐
│                    对齐方法谱系                                │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  RL-based:                                                   │
│    ├── PPO (2017)         ──  经典,4 模型,复杂             │
│    ├── GRPO (2024)        ──  DeepSeek-R1,无 critic         │
│    ├── REINFORCE          ──  最简,公式简洁                  │
│    └── A2C / A3C          ──  通用 RL,LLM 中较少用          │
│                                                              │
│  RL-free (Direct Preference):                                │
│    ├── DPO (2023)         ──  开山之作,1 阶段               │
│    ├── IPO (2023)         ──  防过拟合                        │
│    ├── KTO (2024)         ──  不需成对偏好                    │
│    ├── SimPO (2024)       ──  无参考模型                      │
│    ├── ORPO (2024)        ──  SFT + DPO 联合                 │
│    └── CPO (2024)         ──  SFT + DPO 正则                  │
│                                                              │
│  Self-Play / Iterative:                                      │
│    ├── Constitutional AI  ──  AI 当标注员                     │
│    ├── Self-Rewarding LM  ──  自我奖励迭代                    │
│    └── SPIN / Iterative DPO── 多轮迭代偏好对齐                │
│                                                              │
└─────────────────────────────────────────────────────────────┘

9.2 决策树

需要对齐 LLM
  │
  ├── 你有多少 GPU?多少数据?
  │     │
  │     ├── 资源受限(< 8 卡 A100)
  │     │     └── ✅ DPO / SimPO(最便宜、最稳)
  │     │
  │     ├── 中等资源(8-32 卡)
  │     │     ├── 数据多 + 想追最强性能 → ✅ GRPO
  │     │     └── 数据少 + 想稳定 → ✅ DPO
  │     │
  │     └── 大量资源(> 64 卡)
  │           └── ✅ PPO + 大 RM ensemble
  │
  ├── 你能采集偏好数据吗?
  │     │
  │     ├── 可以(人工标注)
  │     │     └── ✅ PPO / DPO
  │     │
  │     ├── 不能人工,但有强模型
  │     │     └── ✅ RLAIF + PPO / DPO
  │     │
  │     └── 都没有
  │           └── ✅ KTO(只需要"好/坏"标签)
  │
  └── 是否需要在线探索?
        │
        ├── 是 → ✅ PPO / GRPO
        └── 否 → ✅ DPO(offline)

9.3 性能 vs 复杂度矩阵

算法实现复杂度显存性能稳定性推荐场景
PPO★★★★★★★大厂、追 SOTA
GRPO★★★★★★★★DeepSeek 复现
DPO★★★★★★★★★快速迭代、初创
IPO★★★★★★★★DPO 过拟合时
KTO★★★★★★★只有二值标签
SimPO最低★★★★★★★★显存紧张

10. 核心数学公式汇总

10.1 Bradley-Terry 偏好模型

P(yw≻yl∣x)=σ(R(x,yw)−R(x,yl)) P(y_w \succ y_l | x) = \sigma\big(R(x,y_w) - R(x,y_l)\big) P(ywylx)=σ(R(x,yw)R(x,yl))

10.2 RM 损失

LRM=−E[log⁡σ(Rθ(x,yw)−Rθ(x,yl))] \mathcal{L}_{\text{RM}} = -\mathbb{E}\Big[\log\sigma\big(R_\theta(x,y_w) - R_\theta(x,y_l)\big)\Big] LRM=E[logσ(Rθ(x,yw)Rθ(x,yl))]

10.3 RLHF 目标

J(πθ)=E[R(x,y)]−β KL(πθ(⋅∣x) ∥ πref(⋅∣x)) J(\pi_\theta) = \mathbb{E}\Big[R(x,y)\Big] - \beta\,\text{KL}\big(\pi_\theta(\cdot|x)\,\|\,\pi_{\text{ref}}(\cdot|x)\big) J(πθ)=E[R(x,y)]βKL(πθ(x)πref(x))

10.4 PPO Clipped Surrogate

LCLIP=−Et[min⁡(rtAt, clip(rt,1−ϵ,1+ϵ) At)] \mathcal{L}^{\text{CLIP}} = -\mathbb{E}_t\Big[\min\big(r_t A_t,\ \text{clip}(r_t, 1-\epsilon, 1+\epsilon)\,A_t\big)\Big] LCLIP=Et[min(rtAt, clip(rt,1ϵ,1+ϵ)At)]

10.5 GRPO 组内归一化

Ai=ri−mean(r)std(r) A_i = \frac{r_i - \text{mean}(\mathbf{r})}{\text{std}(\mathbf{r})} Ai=std(r)rimean(r)

10.6 DPO 损失

LDPO=−E[log⁡σ(β Δθ)],Δθ=log⁡πθ(yw)πref(yw)−log⁡πθ(yl)πref(yl) \mathcal{L}_{\text{DPO}} = -\mathbb{E}\Big[\log\sigma\big(\beta\,\Delta_\theta\big)\Big], \quad \Delta_\theta = \log\frac{\pi_\theta(y_w)}{\pi_{\text{ref}}(y_w)} - \log\frac{\pi_\theta(y_l)}{\pi_{\text{ref}}(y_l)} LDPO=E[logσ(βΔθ)],Δθ=logπref(yw)πθ(yw)logπref(yl)πθ(yl)

10.7 IPO 损失

LIPO=E[(log⁡πθ(yw)πref(yw)−log⁡πθ(yl)πref(yl)−12β)2] \mathcal{L}_{\text{IPO}} = \mathbb{E}\Big[\big(\log\frac{\pi_\theta(y_w)}{\pi_{\text{ref}}(y_w)} - \log\frac{\pi_\theta(y_l)}{\pi_{\text{ref}}(y_l)} - \frac{1}{2\beta}\big)^2\Big] LIPO=E[(logπref(yw)πθ(yw)logπref(yl)πθ(yl)2β1)2]


11. 总结

11.1 对齐范式的演进

阶段 1: SFT only
  → 学会"按指令回答"
  → 无法处理偏好、价值观
  
阶段 2: RLHF (InstructGPT, ChatGPT)
  → 引入 RM + PPO
  → 性能强但训练复杂
  
阶段 3: Direct Preference (DPO)
  → 闭式解,无需 RL
  → 工程简单,性能相当
  
阶段 4: Self-Improvement (RLAIF, Self-Rewarding)
  → AI 替代人类标注
  → 持续自我改进

11.2 选择建议

项目初期 / 资源受限:
  → DPO 是最好的起点。简单、稳定、性能强。

追求 SOTA:
  → PPO + 大 RM(如果算力足够)
  → 或 GRPO(DeepSeek 路径)

数据形式特殊:
  → 只有"好/坏"标签 → KTO
  → 没有 reference model → SimPO

长期迭代:
  → Constitutional AI + 自我奖励
  → SPIN / Iterative DPO
``
Logo

AtomGit AI 社区提供模型库、数据集、Agent、Token等资源

更多推荐