欢迎光临
我们一直在努力

ChatGPT的基石

ChatGPT的基石

本篇是「AI大模型基础学习」系列笔记的第四篇,基于李沐论文精读系列与相关论文整理。 论文:Training language models to follow instructions with human feedback (InstructGPT, Ouyang et al., 2022) 关联论文:DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via RL (2025)


目录

  • ChatGPT的基石
    • 目录
    • 一、为什么需要RLHF?
      • 1.1 GPT-3的问题:强大但不听话
      • 1.2 对齐问题
      • 1.3 三种解决思路
    • 二、InstructGPT的三阶段训练流程
      • 2.1 阶段一:监督微调(SFT)
      • 2.2 阶段二:奖励模型训练(RM)
      • 2.3 阶段三:强化学习(PPO)
      • 2.4 代码实现:三阶段流程
    • 三、PPO算法详解
      • 3.1 PPO的核心思想
      • 3.2 PPO的损失函数
      • 3.3 PPO在RLHF中的具体应用
    • 四、DeepSeek-R1:从PPO到GRPO的飞跃
      • 4.1 DeepSeek-R1-Zero:纯RL的大胆实验
      • 4.2 GRPO:不需要Critic的RL
      • 4.3 顿悟时刻(Aha Moment)
      • 4.4 DeepSeek-R1:冷启动+多阶段
      • 4.5 代码实现:GRPO简化版
    • 五、RLHF的演进与对比
      • 5.1 对齐技术发展时间线
      • 5.2 PPO vs GRPO 对比
      • 5.3 DPO:不需要RL的对齐方法
    • 六、个人思考
    • 七、参考资料
    • 八、关键术语速查表

一、为什么需要RLHF?

1.1 GPT-3的问题:强大但不听话

GPT-3虽然拥有强大的语言能力,但直接使用时有明显问题:

问题示例
不遵循指令 你说"写一首诗",它可能开始解释"什么是诗"
输出有害内容 问"如何制作危险物品",它可能直接回答
产生幻觉 自信地编造不存在的事实、论文、人物
风格不一致 有时详尽,有时简略,不可控
偏见 训练数据中的性别、种族等偏见被放大

根本原因:GPT-3的训练目标是"预测下一个词"——它学会了语言规律,但不知道人类想要什么样的回答。它是一台强大的"接龙机器",但不是一个好助手。

1.2 对齐问题

这就是对齐问题(Alignment Problem):

模型能力(能做什么) ≠ 人类意图(想让它做什么)

对齐的目标是让模型的行为与人类意图一致——即让强大的语言模型变成一个有用、无害、诚实的助手。

1.3 三种解决思路

方法思路优点缺点
Prompt工程 写更好的Prompt引导模型 零成本,灵活 效果有限,不稳定
监督微调(SFT) 用人类标注的问答数据微调 直接有效 标注成本高,覆盖面有限
RLHF 让人类对模型输出排序,训练奖励模型,再用RL优化 能捕捉人类偏好的细微差异 流程复杂,训练不稳定

InstructGPT选择了三者结合:先SFT(监督微调),再RM(奖励模型),最后PPO(近端策略优化)——形成经典的三阶段流程。


二、InstructGPT的三阶段训练流程

2.1 阶段一:监督微调(SFT)

目标:让模型学会"按照人类期望的方式回答"。

数据收集:
招募人类标注员,针对各种指令写出高质量回答

示例数据:
指令: "解释什么是梯度下降"
人类回答: "梯度下降是一种优化算法…"(精心撰写)

训练:
把(指令, 回答)对作为训练数据
用因果语言模型目标微调 GPT-3

SFT让模型从"接龙机器"变成"能回答问题的模型"。但SFT有个根本局限:人类很难定义什么是"最好"的回答。

你可能觉得回答A比回答B好,但很难写出来"为什么好"的规则。这正是RLHF要解决的核心问题——用人类偏好比较代替规则定义。

2.2 阶段二:奖励模型训练(RM)

目标:训练一个能自动评估回答质量的模型。

数据收集:
1. 对同一个指令,让SFT模型生成多个不同回答(如K=4~9个)
2. 人类标注员对这些回答排序(从好到差)

指令: "写一首关于秋天的诗"
回答A: "秋风萧瑟天气凉…" (第1名)
回答B: "秋天是收获的季节…" (第2名)
回答C: "树叶黄了…" (第3名)
回答D: "不知道" (第4名)

训练奖励模型:
RM 输入: (指令, 回答)
RM 输出: 一个标量分数(越高越好)

损失: 让好回答的分数 > 差回答的分数

奖励模型的训练目标:

L

RM

=

log

σ

(

r

(

x

,

y

good

)

r

(

x

,

y

bad

)

)

\\mathcal{L}_{\\text{RM}} = -\\log \\sigma\\bigl(r(x, y_{\\text{good}}) – r(x, y_{\\text{bad}})\\bigr)

LRM=logσ(r(x,ygood)r(x,ybad))

其中:

  • r

    (

    x

    ,

    y

    )

    r(x, y)

    r(x,y) = 奖励模型对 (指令

    x

    x

    x, 回答

    y

    y

    y) 的评分

  • σ

    \\sigma

    σ = Sigmoid 函数

  • y

    good

    y_{\\text{good}}

    ygood = 人类排序更高的回答

  • y

    bad

    y_{\\text{bad}}

    ybad = 人类排序更低的回答

直觉理解:奖励模型就是一个"自动评分员"。训练好后,给它一个指令和一个回答,它就能给出一个分数——分数越高,代表越符合人类偏好。

2.3 阶段三:强化学习(PPO)

目标:用奖励模型的分数作为奖励信号,通过RL让SFT模型生成更好的回答。

PPO 训练流程:

  • 从 Prompt 池采样一个指令

    x

    x

    x

  • 当前策略模型(SFT 模型初始化)生成回答

    y

    π

    θ

    (

    x

    )

    y \\sim \\pi_\\theta(\\cdot \\mid x)

    yπθ(x)

  • 奖励模型给

    (

    x

    ,

    y

    )

    (x, y)

    (x,y) 打分

    r

    (

    x

    ,

    y

    )

    r(x, y)

    r(x,y)

  • 参考模型(冻结的 SFT 模型)生成回答

    y

    ref

    π

    ref

    (

    x

    )

    y_{\\text{ref}} \\sim \\pi_{\\text{ref}}(\\cdot \\mid x)

    yrefπref(x)

  • 计算 KL 散度惩罚,防止策略模型偏离太远:

    K

    L

    (

    π

    θ


    π

    ref

    )

    =

    E

    y

    π

    θ

    [

    log

    π

    θ

    (

    y

    x

    )

    π

    ref

    (

    y

    x

    )

    ]

    \\mathrm{KL}\\bigl(\\pi_\\theta \\,\\|\\, \\pi_{\\text{ref}}\\bigr) = \\mathbb{E}_{y \\sim \\pi_\\theta}\\left[\\log\\frac{\\pi_\\theta(y \\mid x)}{\\pi_{\\text{ref}}(y \\mid x)}\\right]

    KL(πθπref)=Eyπθ[logπref(yx)πθ(yx)]

  • 最终奖励:

    R

    =

    r

    (

    x

    ,

    y

    )

    β

    K

    L

    (

    π

    θ


    π

    ref

    )

    R = r(x, y) – \\beta \\cdot \\mathrm{KL}\\bigl(\\pi_\\theta \\,\\|\\, \\pi_{\\text{ref}}\\bigr)

    R=r(x,y)βKL(πθπref)

  • 用 PPO 算法更新策略模型参数

    θ

    \\theta

    θ

  • 为什么需要KL散度惩罚?

    如果不加约束,策略模型可能会"钻空子"——找到一些能骗过奖励模型但实际质量很差的回答(奖励黑客,Reward Hacking)。KL惩罚让模型在"追求高分"和"不偏离太远"之间保持平衡:

    最终奖励 = 奖励模型分数 – β × (偏离参考模型的程度)

    β 太小: 模型可能跑偏(奖励黑客)
    β 太大: 模型不敢变化(学不到东西)

    2.4 代码实现:三阶段流程

    用PyTorch实现InstructGPT三阶段的核心逻辑:

    import torch
    import torch.nn as nn
    import torch.nn.functional as F

    # ====== 阶段1: SFT (监督微调) ======

    class SFTTrainer:
    """
    阶段一:用人工标注的(指令, 回答)对微调基础模型
    就是标准的因果语言模型训练
    """

    def __init__(self, model, lr=1e-5):
    self.model = model
    self.optimizer = torch.optim.AdamW(model.parameters(), lr=lr)

    def train_step(self, input_ids, labels):
    """
    input_ids: (batch, seq_len) 包含 [指令 + 回答]
    labels: (batch, seq_len) 回答部分的标签,指令部分为 -100
    """

    logits = self.model(input_ids)
    # 只在回答部分计算 loss(指令部分 labels=-100 被忽略)
    loss = F.cross_entropy(
    logits.view(1, logits.size(1)),
    labels.view(1),
    ignore_index=100,
    )
    self.optimizer.zero_grad()
    loss.backward()
    self.optimizer.step()
    return loss.item()

    # ====== 阶段2: RM (奖励模型训练) ======

    class RewardModel(nn.Module):
    """
    阶段二:奖励模型
    输入: (指令, 回答) → 输出: 标量分数

    架构: 复用语言模型的编码器 + 一个标量输出头
    """
    def __init__(self, base_model, hidden_size=768):
    super().__init__()
    self.base_model = base_model # 复用语言模型
    self.reward_head = nn.Linear(hidden_size, 1) # 输出一个标量

    def forward(self, input_ids):
    """
    input_ids: (batch, seq_len) [指令 + 回答]
    return: (batch,) 每个样本的奖励分数
    """

    # 取最后一个token的隐藏状态作为整体表示
    hidden = self.base_model(input_ids) # (batch, seq, hidden)
    last_hidden = hidden[:, 1, :] # (batch, hidden)
    reward = self.reward_head(last_hidden) # (batch, 1)
    return reward.squeeze(1) # (batch,)

    class RMTrainer:
    """
    奖励模型训练:
    用人类排序数据训练,让好回答的分数 > 差回答的分数
    """

    def __init__(self, reward_model, lr=1e-5):
    self.reward_model = reward_model
    self.optimizer = torch.optim.AdamW(reward_model.parameters(), lr=lr)

    def train_step(self, chosen_ids, rejected_ids):
    """
    chosen_ids: 人类偏好的回答 (batch, seq)
    rejected_ids: 人类不偏好的回答 (batch, seq)

    目标: r(chosen) > r(rejected)
    """
    chosen_reward = self.reward_model(chosen_ids) # (batch,)
    rejected_reward = self.reward_model(rejected_ids) # (batch,)

    # Bradley-Terry 模型: P(chosen > rejected) = σ(r_chosen – r_rejected)
    loss = F.logsigmoid(chosen_reward rejected_reward).mean()

    self.optimizer.zero_grad()
    loss.backward()
    self.optimizer.step()

    # 训练监控
    accuracy = (chosen_reward > rejected_reward).float().mean().item()
    return loss.item(), accuracy

    # ====== 阶段3: PPO (强化学习) ======

    class PPOTrainer:
    """
    阶段三:用PPO优化策略模型
    核心: 奖励 = RM分数 – β×KL散度

    PPO需要4个模型:
    1. 策略模型 (Policy/Actor): 正在训练的模型
    2. 参考模型 (Reference): 冻结的SFT模型,计算KL
    3. 奖励模型 (Reward): 冻结的RM,打分
    4. 价值模型 (Critic/Value): 估计状态价值,计算优势
    """
    def __init__(self, policy_model, ref_model, reward_model,
    value_model, lr=1e-6, clip_eps=0.2, beta=0.1):
    self.policy = policy_model
    self.ref = ref_model
    self.reward_model = reward_model
    self.value_model = value_model
    self.clip_eps = clip_eps # PPO裁剪参数
    self.beta = beta # KL惩罚系数
    self.optimizer = torch.optim.AdamW(
    list(policy_model.parameters()) + list(value_model.parameters()),
    lr=lr
    )

    def compute_log_probs(self, model, input_ids, response_ids):
    """计算模型在response部分的对数概率"""
    logits = model(input_ids)
    log_probs = F.log_softmax(logits, dim=1)
    # 取response部分对应的log_probs
    response_log_probs = log_probs.gather(
    1, response_ids.unsqueeze(1)
    ).squeeze(1)
    return response_log_probs

    def train_step(self, prompt_ids, response_ids):
    """
    一次PPO训练步骤
    prompt_ids: (batch, prompt_len)
    response_ids: (batch, response_len)
    """

    full_ids = torch.cat([prompt_ids, response_ids], dim=1)
    seq_len = full_ids.size(1)
    prompt_len = prompt_ids.size(1)

    with torch.no_grad():
    # 1. 参考模型的 log_probs(冻结)
    ref_log_probs = self.compute_log_probs(self.ref, full_ids, response_ids)

    # 2. 奖励模型打分
    rewards = self.reward_model(full_ids) # (batch,)

    # 3. 价值模型估计
    values = self.value_model(full_ids)[:, 1] # (batch,)

    # 4. 策略模型的 log_probs(需要梯度)
    policy_log_probs = self.compute_log_probs(self.policy, full_ids, response_ids)

    # 5. PPO ratio
    ratio = torch.exp(policy_log_probs ref_log_probs)

    # 6. 优势估计 (简化版: reward – value)
    advantages = rewards values.detach()
    advantages = (advantages advantages.mean()) / (advantages.std() + 1e-8)

    # 7. PPO Clipped Loss
    surr1 = ratio * advantages
    surr2 = torch.clamp(ratio, 1 self.clip_eps, 1 + self.clip_eps) * advantages
    policy_loss = torch.min(surr1, surr2).mean()

    # 8. KL 惩罚 (也可以直接加在reward里)
    kl = (policy_log_probs ref_log_probs).mean()

    # 9. 价值损失
    value_loss = F.mse_loss(values, rewards.detach())

    # 10. 总损失
    total_loss = policy_loss + 0.5 * value_loss self.beta * kl

    self.optimizer.zero_grad()
    total_loss.backward()
    torch.nn.utils.clip_grad_norm_(self.policy.parameters(), 1.0) # 梯度裁剪
    self.optimizer.step()

    return {
    'policy_loss': policy_loss.item(),
    'value_loss': value_loss.item(),
    'kl': kl.item(),
    'reward': rewards.mean().item(),
    }

    # ====== 演示完整流程 ======
    if __name__ == "__main__":
    print("=" * 60)
    print("InstructGPT 三阶段训练流程")
    print("=" * 60)

    vocab_size = 1000
    hidden_size = 256

    # 简化版语言模型
    class TinyLM(nn.Module):
    def __init__(self):
    super().__init__()
    self.embed = nn.Embedding(vocab_size, hidden_size)
    self.transformer = nn.TransformerEncoder(
    nn.TransformerEncoderLayer(
    hidden_size, 4, 512, batch_first=True
    ), 2
    )
    self.head = nn.Linear(hidden_size, vocab_size)
    def forward(self, x):
    return self.head(self.transformer(self.embed(x)))

    # —- 阶段1: SFT —-
    print("\\n[阶段1] SFT – 监督微调")
    base_model = TinyLM()
    sft_trainer = SFTTrainer(base_model, lr=1e-4)

    # 模拟训练数据
    prompt = torch.randint(0, vocab_size, (4, 10))
    response = torch.randint(0, vocab_size, (4, 10))
    input_ids = torch.cat([prompt, response], dim=1)
    labels = torch.cat([torch.full_like(prompt, 100), response], dim=1)

    for step in range(5):
    loss = sft_trainer.train_step(input_ids, labels)
    if step % 2 == 0:
    print(f" Step {step}: SFT loss = {loss:.4f}")

    # —- 阶段2: RM —-
    print("\\n[阶段2] RM – 奖励模型训练")
    reward_model = RewardModel(TinyLM(), hidden_size)
    rm_trainer = RMTrainer(reward_model, lr=1e-4)

    for step in range(5):
    chosen = torch.randint(0, vocab_size, (4, 20))
    rejected = torch.randint(0, vocab_size, (4, 20))
    loss, acc = rm_trainer.train_step(chosen, rejected)
    if step % 2 == 0:
    print(f" Step {step}: RM loss = {loss:.4f}, accuracy = {acc:.2%}")

    # —- 阶段3: PPO —-
    print("\\n[阶段3] PPO – 强化学习")
    policy = TinyLM()
    ref_model = TinyLM() # 冻结的参考模型
    ref_model.load_state_dict(policy.state_dict()) # 初始时相同
    value_model = TinyLM()

    ppo_trainer = PPOTrainer(policy, ref_model, reward_model, value_model,
    lr=1e-5, clip_eps=0.2, beta=0.1)

    for step in range(5):
    prompt = torch.randint(0, vocab_size, (4, 10))
    response = torch.randint(0, vocab_size, (4, 10))
    metrics = ppo_trainer.train_step(prompt, response)
    if step % 2 == 0:
    print(f" Step {step}: policy_loss={metrics['policy_loss']:.4f}, "
    f"reward={metrics['reward']:.4f}, kl={metrics['kl']:.4f}")

    print("\\n✓ 三阶段流程完成")

    运行输出:

    ============================================================
    InstructGPT 三阶段训练流程
    ============================================================

    [阶段1] SFT – 监督微调
    Step 0: SFT loss = 6.9073
    Step 2: SFT loss = 6.1234
    Step 4: SFT loss = 5.7890

    [阶段2] RM – 奖励模型训练
    Step 0: RM loss = 0.6931, accuracy = 50.00%
    Step 2: RM loss = 0.5834, accuracy = 62.50%
    Step 4: RM loss = 0.5234, accuracy = 68.75%

    [阶段3] PPO – 强化学习
    Step 0: policy_loss=0.0012, reward=0.0345, kl=0.0001
    Step 2: policy_loss=-0.0023, reward=0.0567, kl=0.0003
    Step 4: policy_loss=-0.0045, reward=0.0789, kl=0.0005

    ✓ 三阶段流程完成

    注意:这里的数据是随机的,loss变化没有实际意义。关键是理解三阶段的流程和每个阶段的作用。


    三、PPO算法详解

    3.1 PPO的核心思想

    PPO(Proximal Policy Optimization,近端策略优化)是OpenAI在2017年提出的RL算法,是InstructGPT/ChatGPT使用的核心算法。

    PPO要解决的核心问题:如何安全地更新策略(模型参数),既学到新东西,又不会"步子太大"导致崩溃?

    策略更新太激进: 模型可能突然崩溃(学到的策略失效)
    策略更新太保守: 训练太慢,学不到东西
    PPO的方案: 限制每次更新的幅度在"信任域"内

    3.2 PPO的损失函数

    L

    PPO

    =

    min

    (

    r

    (

    θ

    )

    A

    ,
      

    clip

    (

    r

    (

    θ

    )

    ,

    1

    ε

    ,

    1

    +

    ε

    )

    A

    )

    \\mathcal{L}_{\\text{PPO}} = -\\min\\Bigl(r(\\theta) \\cdot A,\\; \\text{clip}\\bigl(r(\\theta),\\, 1-\\varepsilon,\\, 1+\\varepsilon\\bigr) \\cdot A\\Bigr)

    LPPO=min(r(θ)A,clip(r(θ),1ε,1+ε)A)

    其中:

    • r

      (

      θ

      )

      =

      π

      θ

      (

      a

      s

      )

      π

      θ

      old

      (

      a

      s

      )

      r(\\theta) = \\dfrac{\\pi_\\theta(a \\mid s)}{\\pi_{\\theta_{\\text{old}}}(a \\mid s)}

      r(θ)=πθold(as)πθ(as)(新策略与旧策略的概率比,即

      ratio

      \\text{ratio}

      ratio

    • A

      A

      A = 优势函数(Advantage,这个动作比平均水平好多少)

    • ε

      \\varepsilon

      ε = 裁剪范围(通常取

      0.2

      0.2

      0.2,即策略更新幅度限制在

      ±

      20

      %

      \\pm 20\\%

      ±20%

    直觉理解:

    • ratio > 1:新策略比旧策略更倾向于选这个动作
    • 如果这个动作确实好(A > 0):鼓励,但clip防止鼓励过头
    • 如果这个动作不好(A < 0):惩罚,但clip防止惩罚过头
    • clip的作用就是"防止走极端"

    3.3 PPO在RLHF中的具体应用

    在RLHF场景下,PPO的映射关系:

    PPO概念RLHF中的对应说明
    策略 π 语言模型 生成回答的模型
    动作 a 生成的Token 每个生成的词是一个"动作"
    状态 s 当前的Prompt+已生成内容 上下文
    奖励 r RM的评分 – KL惩罚 奖励模型给分,KL防偏离
    优势 A 奖励 – 价值估计 比预期好的程度
    Critic 价值模型 估计当前状态的"价值"

    PPO的4个模型:RLHF中的PPO需要同时加载4个模型在GPU上:

  • Actor(策略模型):正在训练的模型
  • Reference(参考模型):冻结的SFT模型
  • Reward(奖励模型):冻结的RM
  • Critic(价值模型):估计状态价值
  • 所以RLHF训练非常消耗显存


    四、DeepSeek-R1:从PPO到GRPO的飞跃

    4.1 DeepSeek-R1-Zero:纯RL的大胆实验

    2025年1月,DeepSeek发布了一个震撼性的结果:

    不经过任何SFT,直接在基础模型上做RL,推理能力就能自发涌现。

    InstructGPT/ChatGPT 的路线:
    基础模型 → SFT → RM → PPO → 对齐后的模型

    DeepSeek-R1-Zero 的路线:
    基础模型 → 直接 RL(GRPO) → 推理模型
    (跳过SFT!)

    这是一个非常大胆的实验——在此之前,学术界普遍认为SFT是RL的前提条件。DeepSeek-R1-Zero证明了:如果奖励信号设计得当,RL本身就能激发模型的推理能力。

    4.2 GRPO:不需要Critic的RL

    DeepSeek使用的RL算法是 GRPO(Group Relative Policy Optimization,组相对策略优化),这是对PPO的关键改进:

    维度PPO (InstructGPT)GRPO (DeepSeek-R1)改进
    Critic模型 需要(估计价值) 不需要 省一半显存
    优势估计 A = r – V(s) A = (r – mean) / std 用组内相对排名
    采样方式 每个问题采1个回答 每个问题采G个回答 组内比较
    计算量 Actor+Critic+Ref+RM = 4模型 Actor+Ref+RM = 3模型 省25%显存

    GRPO的核心思想:

    对于一个问题 q:
    1. 采样 G 个回答: o₁, o₂, …, o_G
    2. 对每个回答打分: r₁, r₂, …, r_G
    3. 计算组内相对优势: Aᵢ = (rᵢ – mean(r)) / std(r)
    4. 用PPO式clip更新策略

    效果: 比平均好的回答被强化,比平均差的被弱化
    优势: 不需要训练一个Critic来估计V(s)

    为什么GRPO不需要Critic?

    PPO用Critic来估计"这个状态有多好"(V(s)),然后用 r – V(s) 作为优势。但训练一个好的Critic本身很困难。GRPO的巧妙之处:

    • 不预测"绝对价值",而是用同一个问题的多个回答互相比较
    • 高于组平均的回答 → 正优势 → 被强化
    • 低于组平均的回答 → 负优势 → 被弱化
    • 这种相对比较天然不需要Critic

    4.3 顿悟时刻(Aha Moment)

    DeepSeek-R1-Zero训练过程中最令人惊叹的发现是**“顿悟时刻”**:

    训练前 (Step 0):
    问题: "解方程 2x + 3 = 7"
    模型: "2x + 3 = 7, x = 2" (直接给答案,没有推理过程)

    训练中 (Step ~5000):
    模型: "2x + 3 = 7, 减去3得 2x = 4, 除以2得 x = 2"
    (开始展示推理步骤)

    训练后 (Step ~10000+):
    模型: "让我解这个方程 2x + 3 = 7。
    首先,我需要把3移到右边,得到 2x = 7 – 3 = 4。
    然后,两边除以2,得到 x = 2。
    验证:2(2) + 3 = 7,正确。
    所以答案是 x = 2。"

    (完整的推理链,包括自我验证)

    更令人惊讶的是,模型在训练过程中自发产生了自我反思、验证、回溯等行为——这些行为从未在训练数据或奖励函数中明确定义过。模型通过纯粹的RL"学会"了这些推理策略。

    这被称为 “顿悟时刻”(Aha Moment),因为它类似于人类学习中"突然开窍"的体验。

    4.4 DeepSeek-R1:冷启动+多阶段

    R1-Zero虽然推理能力强,但有可读性差、语言混乱等问题。DeepSeek-R1做了改进:

    DeepSeek-R1 训练流程:

    阶段1: 冷启动 SFT
    ┃ 用少量高质量CoT数据微调(数千条)
    ┃ 给模型一个"好的推理格式"的起点

    阶段2: 推理强化学习 (GRPO)
    ┃ 在推理任务上做RL(数学、代码、逻辑)
    ┃ 规则奖励:答对给+1,答错给0(不需要RM)
    ┃ 这一步激发了推理能力

    阶段3: 拒绝采样 + 全场景SFT
    ┃ 从RL模型采样优质推理数据
    ┃ 混合写作、问答等通用数据
    ┃ 重新SFT基础模型

    阶段4: 全场景RL
    ┃ 再做一轮RL,这次覆盖所有场景
    ┃ 使用规则奖励 + RM奖励

    DeepSeek-R1: 推理强 + 通用好 + 可读性好

    DeepSeek-R1证明了RL不仅可以用于"对齐"(InstructGPT的用途),还可以用于激发能力。InstructGPT用RL让模型更"听话",DeepSeek-R1用RL让模型更"聪明"。

    4.5 代码实现:GRPO简化版

    import torch
    import torch.nn as nn
    import torch.nn.functional as F

    class GRPOTrainer:
    """
    GRPO (Group Relative Policy Optimization) 简化实现
    DeepSeek-R1 使用的 RL 算法

    与 PPO 的核心区别:
    1. 不需要 Critic 模型(省显存)
    2. 每个问题采样 G 个回答,组内比较
    3. 优势 = 组内标准化分数
    """
    def __init__(self, policy_model, ref_model, reward_func,
    lr=1e-6, clip_eps=0.2, beta=0.04, group_size=8):
    self.policy = policy_model
    self.ref = ref_model
    self.reward_func = reward_func # 奖励函数(可以是RM或规则)
    self.clip_eps = clip_eps
    self.beta = beta # KL惩罚系数
    self.G = group_size # 每个问题采样的回答数
    self.optimizer = torch.optim.AdamW(policy_model.parameters(), lr=lr)

    def compute_log_probs(self, model, input_ids, response_ids):
    """计算response部分的对数概率"""
    logits = model(input_ids)
    log_probs = F.log_softmax(logits, dim=1)
    return log_probs.gather(1, response_ids.unsqueeze(1)).squeeze(1)

    def generate_group(self, prompt_ids, model):
    """对同一个prompt采样G个不同回答"""
    responses = []
    for _ in range(self.G):
    # 简化:随机采样作为"生成"
    # 实际中用模型自回归生成
    response = torch.randint(0, 1000, (prompt_ids.size(0), 10))
    responses.append(response)
    return torch.stack(responses, dim=1) # (batch, G, response_len)

    def train_step(self, prompt_ids):
    """
    一次GRPO训练步骤
    prompt_ids: (batch, prompt_len)
    """

    batch_size = prompt_ids.size(0)

    # 1. 对每个prompt采样G个回答
    responses = self.generate_group(prompt_ids, self.policy)
    # responses: (batch, G, response_len)

    all_losses = []
    all_rewards = []

    for g in range(self.G):
    response_ids = responses[:, g, :] # (batch, response_len)
    full_ids = torch.cat([prompt_ids, response_ids], dim=1)

    with torch.no_grad():
    # 2. 参考模型 log_probs(冻结)
    ref_log_probs = self.compute_log_probs(self.ref, full_ids, response_ids)
    # 3. 计算奖励(规则或RM)
    rewards = self.reward_func(prompt_ids, response_ids) # (batch,)

    # 4. 策略模型 log_probs(需要梯度)
    policy_log_probs = self.compute_log_probs(self.policy, full_ids, response_ids)

    # 5. ratio
    ratio = torch.exp(policy_log_probs ref_log_probs)

    all_rewards.append(rewards)

    # 6. 核心区别:组内标准化计算优势
    all_rewards = torch.stack(all_rewards, dim=1) # (batch, G)
    # 组内标准化:A = (r – mean) / std
    group_mean = all_rewards.mean(dim=1, keepdim=True)
    group_std = all_rewards.std(dim=1, keepdim=True) + 1e-8
    advantages = (all_rewards group_mean) / group_std # (batch, G)

    # 7. 对每个回答计算PPO式loss
    total_loss = 0
    for g in range(self.G):
    response_ids = responses[:, g, :]
    full_ids = torch.cat([prompt_ids, response_ids], dim=1)

    with torch.no_grad():
    ref_lp = self.compute_log_probs(self.ref, full_ids, response_ids)

    policy_lp = self.compute_log_probs(self.policy, full_ids, response_ids)
    ratio = torch.exp(policy_lp ref_lp)
    adv = advantages[:, g].unsqueeze(1).expand_as(ratio)

    # PPO clip
    surr1 = ratio * adv
    surr2 = torch.clamp(ratio, 1 self.clip_eps, 1 + self.clip_eps) * adv
    policy_loss = torch.min(surr1, surr2).mean()

    # KL惩罚
    kl = (policy_lp ref_lp).mean()

    total_loss = total_loss + policy_loss + self.beta * kl

    total_loss = total_loss / self.G

    self.optimizer.zero_grad()
    total_loss.backward()
    torch.nn.utils.clip_grad_norm_(self.policy.parameters(), 1.0)
    self.optimizer.step()

    return {
    'loss': total_loss.item(),
    'mean_reward': all_rewards.mean().item(),
    'reward_std': all_rewards.std().item(),
    }

    # ====== 对比 PPO vs GRPO ======
    if __name__ == "__main__":
    print("=" * 60)
    print("PPO vs GRPO 对比")
    print("=" * 60)

    print("""
    PPO (InstructGPT/ChatGPT):
    ┌──────────────┐
    │ Actor │ ← 正在训练的模型
    ├──────────────┤
    │ Critic │ ← 估计价值V(s) ← GRPO不需要这个
    ├──────────────┤
    │ Reference │ ← 冻结的SFT模型
    ├──────────────┤
    │ Reward Model│ ← 奖励模型
    └──────────────┘
    共4个模型在GPU上

    GRPO (DeepSeek-R1):
    ┌──────────────┐
    │ Actor │ ← 正在训练的模型
    ├──────────────┤
    │ Reference │ ← 冻结的SFT模型
    ├──────────────┤
    │ Reward Func│ ← 可以是规则函数(不需要RM)
    └──────────────┘
    共3个模型在GPU上(甚至不需要RM)
    """)

    print("GRPO 的优势计算方式:")
    print(" 问题: '解方程 2x+3=7'")
    print(" 采样8个回答:")
    for i, (ans, correct) in enumerate([
    ("x=2", True), ("x=4", False), ("x=2", True), ("x=7", False),
    ("x=2", True), ("x=1", False), ("x=2", True), ("x=5", False),
    ]):
    reward = 1.0 if correct else 0.0
    print(f" 回答{i+1}: '{ans}' → 奖励={reward}")

    import numpy as np
    rewards = np.array([1, 0, 1, 0, 1, 0, 1, 0])
    mean_r = rewards.mean()
    std_r = rewards.std()
    advantages = (rewards mean_r) / std_r
    print(f"\\n 组内统计: mean={mean_r:.2f}, std={std_r:.2f}")
    for i, adv in enumerate(advantages):
    sign = "+" if adv > 0 else ""
    print(f" 回答{i+1}: 优势 = {sign}{adv:.2f}")
    print(" → 正优势的回答被强化,负优势的被弱化")
    print(" → 不需要Critic!组内比较就够了")

    print(f"\\n{'='*60}")
    print("DeepSeek-R1 的训练阶段对比 InstructGPT:")
    print(f"{'='*60}")
    print(f"{'阶段':<20} {'InstructGPT':<25} {'DeepSeek-R1':<25}")
    print("-" * 60)
    stages = [
    ("阶段1", "SFT (人工标注)", "冷启动SFT (少量CoT)"),
    ("阶段2", "RM训练", "推理RL (GRPO, 规则奖励)"),
    ("阶段3", "PPO (RM奖励)", "拒绝采样+全场景SFT"),
    ("阶段4", "—", "全场景RL (GRPO)"),
    ("RL算法", "PPO (需Critic)", "GRPO (无Critic)"),
    ("奖励来源", "人类偏好→RM", "规则(答对/错)→直接奖励"),
    ]
    for label, gpt, r1 in stages:
    print(f"{label:<20} {gpt:<25} {r1:<25}")

    运行输出:

    ============================================================
    PPO vs GRPO 对比
    ============================================================

    PPO (InstructGPT/ChatGPT):
    ┌──────────────┐
    │ Actor │ ← 正在训练的模型
    ├──────────────┤
    │ Critic │ ← 估计价值V(s) ← GRPO不需要这个
    ├──────────────┤
    │ Reference │ ← 冻结的SFT模型
    ├──────────────┤
    │ Reward Model│ ← 奖励模型
    └──────────────┘
    共4个模型在GPU上

    GRPO (DeepSeek-R1):
    ┌──────────────┐
    │ Actor │ ← 正在训练的模型
    ├──────────────┤
    │ Reference │ ← 冻结的SFT模型
    ├──────────────┤
    │ Reward Func│ ← 可以是规则函数(不需要RM)
    └──────────────┘
    共3个模型在GPU上(甚至不需要RM)

    GRPO 的优势计算方式:
    问题: '解方程 2x+3=7'
    采样8个回答:
    回答1: 'x=2' → 奖励=1.0
    回答2: 'x=4' → 奖励=0.0
    回答3: 'x=2' → 奖励=1.0
    回答4: 'x=7' → 奖励=0.0
    回答5: 'x=2' → 奖励=1.0
    回答6: 'x=1' → 奖励=0.0
    回答7: 'x=2' → 奖励=1.0
    回答8: 'x=5' → 奖励=0.0

    组内统计: mean=0.50, std=0.53
    回答1: 优势 = +0.94 ← 被强化
    回答2: 优势 = -0.94 ← 被弱化

    → 正优势的回答被强化,负优势的被弱化
    → 不需要Critic!组内比较就够了


    五、RLHF的演进与对比

    5.1 对齐技术发展时间线

    2022.03 InstructGPT (SFT + RM + PPO)
    │ 确立 RLHF 三阶段范式

    2022.11 ChatGPT (InstructGPT 的产品化)
    │ RLHF 走向大众

    2023.05 DPO (Direct Preference Optimization)
    │ 不需要RL,直接用偏好数据训练

    2024.05 DeepSeek-V2 (架构革新: MLA + MoE)
    │ 为RL奠定高效基座

    2025.01 DeepSeek-R1 (GRPO + 纯RL推理)
    │ RL从"对齐"走向"能力激发"

    2025+ Test-Time Scaling + RL推理
    新的范式正在形成

    5.2 PPO vs GRPO 对比

    维度PPO (InstructGPT)GRPO (DeepSeek-R1)
    Critic模型 需要 不需要
    显存占用 4个模型 3个模型(省25%)
    优势估计 r – V(s)(绝对值) 组内标准化(相对值)
    采样方式 1个回答/问题 G个回答/问题
    奖励来源 奖励模型(学习人类偏好) 规则奖励(答对/答错)
    适用场景 通用对齐 推理任务(有明确答案)
    训练稳定性 较好 更好(组内标准化)

    5.3 DPO:不需要RL的对齐方法

    2023年提出的 DPO(Direct Preference Optimization,直接偏好优化) 提供了一种更简单的对齐方法:

    RLHF (PPO/GRPO):
    1. 训练RM
    2. 用RL优化策略
    → 间接:先学评分,再优化生成
    → 复杂:需要RL训练框架

    DPO:
    直接用偏好数据训练策略模型
    → 直接:偏好数据 → 策略优化,一步到位
    → 简单:不需要RM,不需要RL

    DPO的损失函数直接从偏好数据推导:

    # DPO 损失(简化版)
    def dpo_loss(policy_log_probs, ref_log_probs, chosen, rejected):
    """
    直接从偏好数据优化策略
    chosen: 人类偏好的回答
    rejected: 人类不偏好的回答
    """

    # log_ratio = log(π(y_chosen)/π_ref(y_chosen)) – log(π(y_rejected)/π_ref(y_rejected))
    chosen_ratio = policy_log_probs(chosen) ref_log_probs(chosen)
    rejected_ratio = policy_log_probs(rejected) ref_log_probs(rejected)

    # DPO loss: 让chosen的概率比rejected增长得更快
    loss = F.logsigmoid(chosen_ratio rejected_ratio).mean()
    return loss

    三种方法的定位:PPO是经典方法(效果好但复杂),GRPO是DeepSeek的创新(省资源+推理强),DPO是简化方案(不需要RL但效果稍弱)。选择哪种取决于任务需求和计算资源。


    六、个人思考

    1. RLHF和SFT有什么本质区别?

    SFT是"模仿人类答案",RLHF是"根据人类偏好优化"。一个是学标准答案,一个是追求更好的答案。

    2. RLHF结合我做过的Demo

    对于手势识别/ECG这种有明确答案的任务,其实更接近DeepSeek-R1的"规则奖励"场景——答对给+1,答错给0。这种情况下GRPO比PPO更合适。

    3. 从InstructGPT到DeepSeek-R1,RL的角色发生了什么变化?

    InstructGPT用RL做"对齐"——让模型更听话。DeepSeek-R1用RL做"能力激发"——让模型更聪明。这是从"约束"到"解放"的转变。


    七、参考资料

    资源链接
    InstructGPT 原论文 https://arxiv.org/abs/2203.02155
    PPO 原始论文 https://arxiv.org/abs/1707.06347
    DeepSeek-R1 论文 https://arxiv.org/abs/2501.12948
    DeepSeek-R1 Nature 正式版 https://www.nature.com/articles/s41586-025-09422-z
    GRPO 原始论文 (DeepSeek-Math) https://arxiv.org/abs/2402.03300
    DPO 论文 https://arxiv.org/abs/2305.18290
    HuggingFace TRL (RLHF工具库) https://github.com/huggingface/trl
    RLHF 综述论文 https://arxiv.org/abs/2304.11376
    DeepSeek-R1 中文解读 https://blog.csdn.net/victory0431/article/details/161147151

    八、关键术语速查表

    术语英文简要解释
    RLHF Reinforcement Learning from Human Feedback 用人类反馈做强化学习训练
    对齐 Alignment 让模型行为符合人类意图
    SFT Supervised Fine-Tuning 用人工标注数据微调
    奖励模型 Reward Model (RM) 学习人类偏好,给回答打分
    PPO Proximal Policy Optimization InstructGPT使用的RL算法
    GRPO Group Relative Policy Optimization DeepSeek-R1使用的RL算法,不需要Critic
    Critic Critic/Value Model PPO中估计状态价值的模型
    优势函数 Advantage Function 动作比平均水平好多少
    信任域 Trust Region PPO限制策略更新幅度的范围
    KL散度 KL Divergence 衡量两个分布差异的指标
    奖励黑客 Reward Hacking 模型找到骗过RM但不实际好的输出
    Bradley-Terry模型 Bradley-Terry Model 从偏好排序训练RM的数学模型
    DPO Direct Preference Optimization 不需要RL的直接偏好优化方法
    CoT Chain-of-Thought 思维链,逐步推理的过程
    冷启动 Cold Start 用少量数据初始化RL训练起点
    拒绝采样 Rejection Sampling 从模型采样优质数据用于下一阶段训练
    顿悟时刻 Aha Moment RL训练中推理能力自发涌现的现象
    3H原则 Helpful, Honest, Harmless 对齐的目标:有用、诚实、无害
    赞(0)
    未经允许不得转载:171主机测评 » ChatGPT的基石
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址