第 11 课:GRPO 与组内相对优势

参考答案版:包含完整代码实现与问答题深度参考解析。建议先独立完成练习题后再展开核对。

本课目标与完成标准

学完后你应能:解释 GRPO 如何省掉独立 critic,并实现带零方差保护的组内标准化优势。

通过必须同时满足:

  • 独立补齐本课唯一的代码填空题,并通过给定检查;
  • 三个问答题均说明因果链,而不是只报术语;
  • 能指出至少一个正确性边界和一个性能取舍;
  • 总分不低于 8/10,且没有一票否决级概念错误。

前置关系

  • 课程前置:Python、概率期望、基本深度学习
  • 本课在路线中的作用:GRPO 对同一 prompt 采样一组回答,用组内相对奖励构造 advantage,省去单独价值模型。

核心心智模型

1. 它是什么,解决什么问题

组相对策略优化(Group Relative Policy Optimization, GRPO)由 DeepSeek 团队于 2024 年提出(发表于 DeepSeekMath 并作为 DeepSeek-R1 / R1-Zero 推理大模型的核心强化学习引擎)。 在传统 PPO 架构中,训练大语言模型必须同时维护一个与 Actor 策略网络同等规模的 Critic 价值网络(例如 70B 或 671B 参数)。Critic 网络的引入带来了极其沉重的工程代价:

  1. 显存消耗翻倍:Critic 网络的前向激活值、参数权重与 AdamW 优化器状态(FP32 Master Weights + 动量二阶矩)占据了至少 50% 的集群显存;

  2. 训练复杂性与负迁移:Critic 的价值自举(Bootstrapping)拟合延迟和方差极易破坏 Actor 的策略更新,且在多机多卡训练中带来了高昂的跨节点通信同步开销。

GRPO 解决的核心工程问题是:彻底摒弃单独的 Critic 价值网络(Zero-Critic Architecture),改用对同一 Prompt 采样多个候选回答所构成的组内相对统计量(Group Relative Baseline)来构造优势函数,将大模型强化学习的显存与通信开销腰斩,解锁了在万卡集群上进行超大规模模型全量 RL 后训练的工程可行性。

2. 它如何工作

GRPO 奖励分布标准化与端到端高吞吐流水线如图 11-1 与图 11-2 所示:

  1. 组内采样与相对优势归一化(Group Normalization,图 11-1):

图 11-1:GRPO 无 Critic 组内相对优势打分分布与标准化

对于给定的一个输入 Prompt qq,Actor 策略 πθold\pi_{\theta_{\text{old}}} 独立并行采样生成 GG 个完整候选回答 {o1,o2,…,oG}\{o_1, o_2, \dots, o_G\}(在 DeepSeek 实践中通常取 G=8∼64G = 8 \sim 64)。 外部奖励系统(如编译器沙箱测试 RLVR 或规则判题器)对这 GG 个回答分别评定标量结果奖励 {r1,r2,…,rG}\{r_1, r_2, \dots, r_G\}。 第 ii 个回答的优势值不再依赖 Critic,而是直接通过组内 Z-score 居中与缩放计算: Âi=ri−mean({r1,…,rG})std({r1,…,rG})+ϵ \hat{A}_i = \frac{r_i - \text{mean}(\{r_1, \dots, r_G\})}{\text{std}(\{r_1, \dots, r_G\}) + \epsilon} 得到的标量优势 Âi\hat{A}_i 随后被广播(Broadcast)给该回答包含的所有有效 Completion Token。

  1. GRPO 策略更新目标函数(图 11-2):

flowchart LR
    BatchQ["获取一批查询 Questions"] --> Rollout["每个 Query 采样 G 份完整回答"]
    Rollout --> Eval["答案规则匹配 / 单元测试打分 (0/1 或浮点)"]
    Eval --> Norm["计算各 Prompt 组内部的 Z-score 优势"]
    Norm --> Loss["组装 GRPO 截断损失与参考模型 KL 约束"]
    Loss --> Grad["跨卡通信与模型参数更新"]

图 11-2:GRPO 高吞吐后训练执行与组间优势分配流程

在每个 Token 位置上,沿用 PPO 的悲观比率截断机制(Clipped Surrogate),并将参考模型 KL 散度作为显式正则项: 𝒥GRPO(θ)=𝔼q∼P(Q),{oi}i=1G∼πθold(q)[1G∑i=1G1|oi|∑t=1|oi|{min(πθ(oi,t)πθold(oi,t)Âi,clip(πθ(oi,t)πθold(oi,t),1−ϵ,1+ϵ)Âi)−βDKL(πθ∥πref)}] \mathcal{J}_{\text{GRPO}}(\theta) = \mathbb{E}_{q \sim P(Q), \{o_i\}_{i=1}^G \sim \pi_{\theta_{\text{old}}}(q)} \left[ \frac{1}{G} \sum_{i=1}^G \frac{1}{|o_i|} \sum_{t=1}^{|o_i|} \left\{ \min \left( \frac{\pi_\theta(o_{i,t})}{\pi_{\theta_{\text{old}}}(o_{i,t})} \hat{A}_i, \, \text{clip}\left(\frac{\pi_\theta(o_{i,t})}{\pi_{\theta_{\text{old}}}(o_{i,t})}, 1-\epsilon, 1+\epsilon\right) \hat{A}_i \right) - \beta D_{\text{KL}}(\pi_\theta \parallel \pi_{\text{ref}}) \right\} \right] 其中 DKL(πθ∥πref)=πref(oi,t)πθ(oi,t)−log⁡πref(oi,t)πθ(oi,t)−1D_{\text{KL}}(\pi_\theta \parallel \pi_{\text{ref}}) = \frac{\pi_{\text{ref}}(o_{i,t})}{\pi_\theta(o_{i,t})} - \log \frac{\pi_{\text{ref}}(o_{i,t})}{\pi_\theta(o_{i,t})} - 1(即点态非负的 k3k_3 估计量)。

  1. 主流工业变体对比:
    • 原始 GRPO:组内除以标准差 std(R)\text{std}(R),先在回答内对 Token 损失求均值,再在组内求均值;
    • Dr. GRPO(2025):指出除以组标准差 std\text{std} 会人为放大原本方差极小的非重要 Prompt 的梯度,提倡移除除以 std\text{std},仅做中心化减均值;
    • DAPO(2025):引入非对称裁剪与动态补采样,过滤组内全对/全错的零方差无效样本;
    • GSPO(2025):在序列级别按 Token 几何均值构建重要性采样比率,消解长文本 Token 级比率相乘下溢问题。

3. 正确性条件与常见误区

  • 零方差分支与数值除零保护(Zero-Variance Branch): 若某个 Prompt 难度极高(所有 GG 个候选回答全错,奖励全为 0)或者过于简单(所有回答全对,奖励全为 1),组内样本方差严格为 0。 若直接除以 std\text{std},即使加上 10−810^{-8} 也会导致数值下溢或产生虚假爆炸。系统必须显式判断 std < eps,并强制将该组内所有样本的优势归零(advantages = [0.0] * G)。这意味着在完全同质的组内不产生任何策略梯度,仅由 KL 项负责约束。

  • 回答级均值(Response Mean)vs Token 级均值(Token Mean)的加权偏置: 给定不同长度的回答序列,聚合损失时有两种写法:

    • 先在每个回答内除以长度 |oi||o_i| 得到单回答平均损失,再除以 GG(原始 GRPO);
    • 将整个 Batch 内所有回答的所有有效 Token 损失求和,统一除以有效 Token 总数。 前者赋予短回答和长回答完全同等的样本权重;后者赋予长文本 Token 更多的累计权重。两者的梯度量纲与优化目标不同,混淆会导致模型产生意外的文本冗长或极短偏好。
  • 与 RLOO 的数学本质差异: 初学者常把 GRPO 等同于第 3 课的 RLOO。实际上,RLOO 在计算基线时严格剔除了样本自身(Leave-One-Out),且未除以组标准差,其梯度数学期望是严格无偏的;而 GRPO 的组均值和方差包含了当前样本自身,在数学上是有偏估计,但获得了极强的工程稳定度与方差压缩效果。

4. 性能与工程取舍

  • Group Size GG 的算力与方差权衡: 在总采样预算 N=B×GN = B \times G 固定的前提下(例如每步生成 256 条回答):
    • 增大 GG(如 G=16,32G=16, 32):同题比较更加充分,组内方差估计更准确,更容易在难题上碰巧采样出至少 1 个正确解(避免全 0 废组);但代价值是 Batch 内覆盖的唯一样题数量 BB 骤降,削弱了 Prompt 多样性;
    • 减小 GG(如 G=2,4G=2, 4):Prompt 覆盖广,但组内统计波动巨大,极易频繁遭遇全错/全对组导致大量样本被置零浪费。

具体演示:GRPO 组内归一化数值演算

假设针对某道数学题 Prompt,采样了 3 个回答,规则打分分别为 R=[1.0,2.0,3.0]R = [1.0, 2.0, 3.0]:

  • 组均值:mean=1.0+2.0+3.03=2.0\text{mean} = \frac{1.0 + 2.0 + 3.0}{3} = 2.0;
  • 总体方差:var=(1−2)2+(2−2)2+(3−2)23=1+0+13=23≈0.6667\text{var} = \frac{(1-2)^2 + (2-2)^2 + (3-2)^2}{3} = \frac{1 + 0 + 1}{3} = \frac{2}{3} \approx 0.6667;
  • 总体标准差:std=2/3\text{std} = \sqrt{2/3};
  • 各样本优势值:
    • A0=1.0−2.02/3=−1.5≈−1.2247A_0 = \frac{1.0 - 2.0}{\sqrt{2/3}} = -\sqrt{1.5} \approx -1.2247;
    • A1=2.0−2.02/3=0.0A_1 = \frac{2.0 - 2.0}{\sqrt{2/3}} = 0.0;
    • A2=3.0−2.02/3=+1.5≈+1.2247A_2 = \frac{3.0 - 2.0}{\sqrt{2/3}} = +\sqrt{1.5} \approx +1.2247。

优势总和严格为 0,高分回答获得正优势,低分回答获得对称负优势。 若奖励全为相同分(如 [2.0,2.0][2.0, 2.0]),标准差为 0,触发零方差防御,安全输出 [0.0,0.0][0.0, 0.0]。

实践任务:唯一代码填空题

补齐组内优势的方差与零方差分支。

规则:只能修改 TODO/______ 所在位置;不要删除断言或放宽误差。代码注释说明了每个边界条件。

Code
import math

def group_advantages(rewards, eps=1e-8):
    if not rewards:
        raise ValueError("group cannot be empty")
    mean = sum(rewards) / len(rewards)
    var = ______
    std = math.sqrt(var)
    if std < eps:
        return ______
    return [(r - mean) / std for r in rewards]

assert group_advantages([2., 2.]) == [0.0, 0.0]
got = group_advantages([1., 2., 3.])
assert abs(sum(got)) < 1e-12

assert all(abs(a-b) < 1e-12 for a,b in zip(got, [-math.sqrt(1.5), 0., math.sqrt(1.5)]))
assert group_advantages([1.]) == [0.]
assert group_advantages([0., 1.]) == [-1., 1.]
try:
    group_advantages([])
except ValueError:
    pass
else:
    raise AssertionError("empty group must fail")

# 相同 token loss,不同分母:三个配方不能互换。
losses = [[2.], [0., 0., 0.]]
response_mean = sum(sum(x)/len(x) for x in losses) / len(losses)
token_mean = sum(map(sum, losses)) / sum(map(len, losses))
fixed_length_mean = sum(map(sum, losses)) / (len(losses) * 4)
assert (response_mean, token_mean, fixed_length_mean) == (1., .5, .25)
# GSPO 是几何均值,不是 token ratio 的算术均值。
ratios = [4., .25]
assert abs(math.exp(sum(map(math.log, ratios))/len(ratios)) - 1.) < 1e-12
assert sum(ratios)/len(ratios) == 2.125

检查方法

运行断言,核对总体方差、全同分、单元素和空组边界;再解释回答平均与 token 平均为什么不同。空组不代表零优势,应明确拒绝。

提交补齐后的代码、实际输出及边界解释;环境不可用时注明静态审查。

Q1

不要背定义:请从输入、状态变化和输出三个阶段解释“GRPO 与组内相对优势”的工作机制。

你的答案:

Q2

跨 prompt 减全局均值,与各 prompt 内减均值并除组 std,分别如何改变优势与 prompt 权重?

你的答案:

Q3

固定 rollout 预算下,group size 与 prompt 数量如何权衡?

你的答案:

评分与通过规则

  • 代码 4 分:正常输入 2 分,边界输入 1 分,解释实现 1 分;
  • Q1~Q3 各 2 分;
  • 一票否决:结果碰巧正确但核心因果链错误、删除边界检查、把未运行结果说成实测。

需要提示时按四级机制请求:概念区域 → 具体方向 → 关键局部 → 完整答案。

参考答案(仅 answer 分支)

先完成题目再核对。即使代码一致,也要能解释关键步骤,并尝试更换一个输入规模。

Code
import math

def group_advantages(rewards, eps=1e-8):
    if not rewards:
        raise ValueError("group cannot be empty")
    mean = sum(rewards) / len(rewards)
    var = sum((r - mean) ** 2 for r in rewards) / len(rewards)
    std = math.sqrt(var)
    if std < eps:
        return [0.0] * len(rewards)
    return [(r - mean) / std for r in rewards]

assert group_advantages([2., 2.]) == [0.0, 0.0]
got = group_advantages([1., 2., 3.])
assert abs(sum(got)) < 1e-12

assert all(abs(a-b) < 1e-12 for a,b in zip(got, [-math.sqrt(1.5), 0., math.sqrt(1.5)]))
assert group_advantages([1.]) == [0.]
assert group_advantages([0., 1.]) == [-1., 1.]
try:
    group_advantages([])
except ValueError:
    pass
else:
    raise AssertionError("empty group must fail")

# 相同 token loss,不同分母:三个配方不能互换。
losses = [[2.], [0., 0., 0.]]
response_mean = sum(sum(x)/len(x) for x in losses) / len(losses)
token_mean = sum(map(sum, losses)) / sum(map(len, losses))
fixed_length_mean = sum(map(sum, losses)) / (len(losses) * 4)
assert (response_mean, token_mean, fixed_length_mean) == (1., .5, .25)
# GSPO 是几何均值,不是 token ratio 的算术均值。
ratios = [4., .25]
assert abs(math.exp(sum(map(math.log, ratios))/len(ratios)) - 1.) < 1e-12
assert sum(ratios)/len(ratios) == 2.125

Q1 参考答案

  1. 输入阶段(组采样与离散规则评估):算法从数据集中抽取 Prompt qq,利用行为策略 πθold\pi_{\theta_{\text{old}}} 独立并行采样生成 GG 份候选回答 {o1,…,oG}\{o_1, \dots, o_G\}。规则判定器(如数学代码验证器)对各个回答独立评分给出标量结果奖励序列 [r1,…,rG][r_1, \dots, r_G]。

  2. 状态变化阶段(无 Critic 组内标准化与优势广播):

    • 组内 Z-score 居中与方差防御:计算当前 Prompt 组内的总体均值 μ\mu 与标准差 σ\sigma。若检测到 σ<ϵ\sigma < \epsilon(全对或全错组),触发防御机制将整组优势强制置零;否则计算归一化优势 Âi=(ri−μ)/σ\hat{A}_i = (r_i - \mu) / \sigma;
    • 序列广播与悲观裁剪损失:将标量优势 Âi\hat{A}_i 沿时间轴广播给该回答的所有有效 Token,随后计算重要性采样比率 rt=πθ/πoldr_t = \pi_\theta / \pi_{\text{old}},在 Token 级别施加 PPO 悲观裁剪与参考模型 KL 散度约束,执行反向传播。
  3. 输出阶段(显存极简更新与高通量迭代):输出更新后的策略参数 θ\theta。整个生命周期彻底消除了 Critic 价值网络及其伴生的大规模通信开销与拟合误差,以极高的集群推理和训练吞吐驱动大模型推理能力的涌现。

Q2 参考答案

两种不同的归一化方案对算法的学习动力学与 Prompt 难度权重具有决定性的因果重塑:

  1. 方案 A(跨 Prompt 扣除全局 Batch 均值):
    • 保留了题目固有的绝对难度:若一道极难的奥数竞赛题,模型采样的 GG 个回答中最好的一条得到了 0.5 分、其余全为 0 分。但若整个 Batch 内有大量简单题目获得了 1.0 的满分,Batch 全局均值可能高达 0.8。此时这道难题中好不容易摸索出的突破性回答(0.5 分)依然会因为 0.5−0.8=−0.30.5 - 0.8 = -0.3 而被判定为“负优势”,导致探索出关键解法的宝贵尝试被错误惩罚抑制;
  2. 方案 B(各 Prompt 组内独立减均值并除以组标准差,即 GRPO 标准做法):
    • 完全消除了 Prompt 的加性基准偏移:任何题目(无论难易)均以其自身组内表现为原点。在全错边缘只要有一条稍微更好的回答,立刻能获得显著的正优势;
    • 除以组标准差 std\text{std} 带来的隐式重加权偏置:除以 std\text{std} 意味着题目对总梯度的贡献权重反比于其组内方差(Weight ∝1/σ\propto 1/\sigma)。对于方差极小(例如只有 1 条回答偶然蒙对、其余全错,导致 σ\sigma 极小)的边界题目,其标准化优势会被人为极度放大,使得模型将不成比例的巨大梯度耗费在小方差极端题上。这也是后续 Dr. GRPO 提倡“仅中心化减均值而不除以标准差”的核心动力。

Q3 参考答案

在 GPU 集群固定的生成算力预算(总 Rollout 预算 N=B×GN = B \times G 恒定,例如 N=512N = 512)约束下,Group Size GG 与唯一题目数量 BB 的工程权衡法则如下:

  1. 大 Group Size(例如 G=32,B=16G=32, B=16)的优劣势:
    • 优势:组内统计量极度稳定,均值和方差估计高保真;在复杂长推理任务中,单题采样 32 次能显著增大“至少命中一次正确答案”的概率,大幅减少全 0 废组的出现;
    • 劣势:全局单批次仅能覆盖 16 道不同的 Prompt,数据多样性严重贫瘠,容易导致策略在少数题目类型的局部特征上过拟合。
  2. 小 Group Size(例如 G=4,B=128G=4, B=128)的优劣势:
    • 优势:单批覆盖 128 道多样化题目,泛化视野极其广阔;
    • 劣势:方差估计极不稳定;在困难题目上,仅采样 4 次极大概率全部失败,导致大量 Prompt 沦为零方差组被直接丢弃,造成高达 60%~80% 的生成算力空转浪费。
  3. 工业落地推荐策略:
    • 训练初期(模型推理能力较弱):推荐采用适度偏大的 GG(如 G=8∼16G = 8 \sim 16),确保有足够的解题概率激活强化学习信号;
    • 训练中后期(基础能力已具备):可逐步缩小 GG 并扩大 BB,以提升题库覆盖广度与泛化效率。同时建议引入 DAPO 动态补采样机制,在检测到全 0 组时实时异步触发二次采样,防止预算浪费。

参考资料

这里只比较目标结构,不声称这些配方在任意模型/任务上都有收益。