大模型强化学习:GRPO超级无敌深度剖析,看完即高手
GRPO 是一种通过组内归一化移除 Critic 网络的高效强化学习算法;它利用相对优势和在线探索,解决了 PPO 的显存瓶颈和 DPO 的探索不足问题,是目前训练大模型强推理能力(System 2)的最佳工程实践。
前言:GRPO 宏观视角
1. 为什么我们需要 GRPO?(Motivation)
在DeepSeek-Math和DeepSeek-R1等前沿工作中,GRPO被证明是一种极其高效的强化学习算法。要理解它,我们必须先看一眼它的前辈——PPO (Proximal Policy Optimization)。
在标准的RLHF(Reinforcement Learning from Human Feedback)流程中,PPO是绝对的王者,但它有一个巨大的痛点:显存占用极大,计算资源昂贵。
请看下面这个内存占用对比图:
+-------------------------------------------------------+
| 传统 PPO (Standard RLHF) |
| ----------------------------------------------------- |
| 1. Actor Model (策略模型,即我们要训练的LLM) | <--- 梯度更新
| 2. Critic Model (价值模型,估算状态价值 V(s)) | <--- 梯度更新
| 3. Reference Model (参考模型,用于计算 KL 散度) | <--- 冻结参数
| 4. Reward Model (奖励模型,用于给 Answer 打分) | <--- 冻结参数
+-------------------------------------------------------+
↓↓↓ (演变为) ↓↓↓
+-------------------------------------------------------+
| GRPO |
| ----------------------------------------------------- |
| 1. Actor Model (策略模型) | <--- 梯度更新
| 2. Reference Model (参考模型) | <--- 冻结参数
| [Critic Model 被移除了!] |
| [Reward Model 依然存在,但通常是轻量级或规则基] |
+-------------------------------------------------------+
核心痛点: PPO 需要维护一个和 Actor 一样大的 Critic 模型(通常是同等规模的 Transformer)。如果你在训练一个 70B 的模型,PPO 意味着你需要加载 70B (Actor) + 70B (Critic) + 70B (Ref) + RM,这对显存是灾难性的。
GRPO 的核心洞察: 我们真的需要一个单独的神经网络(Critic)来告诉 Actor “你做得好不好”吗?能不能直接通过“这一组生成结果的内部比较”来判断优劣?
2. GRPO 的核心机制:自顶向下的直觉
GRPO 全称 Group Relative Policy Optimization。顾名思义,它由两个关键词组成:
-
Group(组)
-
Relative(相对)
想象一下考试评分:
-
PPO (Value-based): 学生 A 回答了问题。老师(Reward Model)打分 80 分。但是,这个 80 分好不好?我们需要一个“预言家”(Critic Model)来预测:“这道题通常大家能拿 75 分”。
-
优势(Advantage)= 80 - 75 = +5。
-
结论: Critic 必不可少,用来提供 Baseline(基线)。
-
-
GRPO (Group-based): 我就不请“预言家”了。我让学生 A 针对同一个问题,生成 8 个不同的答案(Group)。 这 8 个答案的得分分别是:[80, 70, 90, 60, 85, 75, 95, 65]。 算出这组的平均分:77.5 分。
-
答案1 (80分) 的优势 = 80 - 77.5 = +2.5
-
答案3 (90分) 的优势 = 90 - 77.5 = +12.5
-
答案4 (60分) 的优势 = 60 - 77.5 = -17.5
-
结论: GRPO 通过采样一组输出,计算组内的平均值作为 Baseline。不需要额外的 Critic 模型参数。
3. GRPO 的系统脉络图 (Architecture Flow)
为了让你建立最直观的印象,我绘制了 GRPO 的数据流转图。请仔细观察“Group Sampling”这一步。
User Prompt (Question)
│
▼
+---------------------+
| Actor Model | <-- 也就是我们要训练的 LLM
+---------------------+
│
│ (生成 N 个不同的回答)
▼
+---------------------------------------------------------------+
| Group Sampling (N=4 示例) |
| |
| Output 1: "The answer is A..." ----> Reward Model --> r1 |
| Output 2: "Calculated as B..." ----> Reward Model --> r2 |
| Output 3: "Let x = y, so A..." ----> Reward Model --> r3 |
| Output 4: "Based on logic C..." ----> Reward Model --> r4 |
+---------------------------------------------------------------+
│
▼
+---------------------------------------------------------------+
| Advantage Calculation (核心) |
| |
| 1. 计算均值 (Mean): μ = (r1+r2+r3+r4) / 4 |
| 2. 计算标准差 (Std): σ |
| 3. 计算相对优势 (Advantage): A_i = (r_i - μ) / σ |
+---------------------------------------------------------------+
│
▼
+---------------------+
| Policy Optimization| <-- 使用 PPO 的 Clip Loss 公式
| (Update) | 但在 Advantage 项上使用了上述 A_i
+---------------------+
本讲核心 takeaway:
-
省资源: GRPO 去掉了 Critic 模型,极大地节省了显存和计算量,使得训练超大模型变得更加可行。
-
基线(Baseline)的转变: 从“神经网络预测的基线”(Value Function)转变为“当前Batch内的统计基线”(Group Mean)。
-
原理: 通过对同一个问题采样多条路径,用组内归一化(Group Normalization)的方式来确定哪个答案是好的,哪个是差的。
GRPO 深度剖析:PPO 昂贵且脆弱的 Critic
在经典的强化学习(RL)理论中,Actor-Critic 架构几乎被奉为圭臬。但在大语言模型(LLM)的场景下,这个架构显得愈发笨重且低效。我们要解决的问题是:为什么训练一个好的 Value Function (Critic) 如此困难且昂贵?
1. 回顾 PPO 的核心数学依赖
要理解痛点,先看公式。PPO 的核心在于最大化以下目标函数(简化版):
在标准 PPO 中,我们通常使用 GAE (Generalized Advantage Estimation) 来计算它,而 GAE 高度依赖于 TD Error ():
请注意这里出现的 。这就是 Critic 网络,它的任务是预测状态
的预期回报。
致命逻辑链条:
-
Actor 的更新方向取决于
。
-
的准确性取决于
的预测精度。
-
如果 Critic
也是一个刚开始训练的神经网络,它的预测是充满了噪声和偏差的。
2. 痛点一:计算与显存的“双倍惩罚” (The Computational Tax)
在传统的 RL(如玩 Atari 游戏或机器人控制)中,Actor 和 Critic 通常只是几层 MLP 或小型 CNN,参数量很少。
但在 LLM 中,情况发生了质变:
-
状态空间(State Space)极度复杂: 输入是 token 序列,语义极其丰富。
-
理解“价值”需要“智能”: 要判断一句话“好不好”,Critic 网络必须具备和 Actor 相当的理解能力。
这意味着,如果你的 Actor 是一个 7B 的 Llama-3,你的 Critic 通常也必须是一个 7B 的 Transformer(或者至少共享大部分主干参数)。
显存账单(Training a 7B model):
-
Actor (7B): 权重 + 梯度 + 优化器状态 很大。
-
Critic (7B): 权重 + 梯度 + 优化器状态 同样大。
这就是为什么在 RLHF 阶段,显存占用往往是 SFT 阶段的 2-4 倍。Critic 不仅占据了显存,还占据了大量的 FLOPs(前向传播和反向传播),使得训练吞吐量(Tokens/sec)直接减半。
3. 痛点二:价值函数的“训练困境” (The Optimization Difficulty)
这才是更深层的学术痛点。在 LLM 生成任务中,训练 Critic 往往比训练 Actor 更难。
3.1 奖励的稀疏性与主观性
在围棋中,输赢是客观的。但在对话中,Reward Model 给出的分数(比如 0.85 或 0.92)往往带有很强的主观噪声。Critic 试图去拟合这个充满噪声的 Reward Model,极易过拟合或欠拟合。
3.2 价值评估的难度
比如以下场景:
Prompt: "请证明黎曼猜想。"
Token 1: "黎"
Token 2: "曼"
Token 3: "猜"
...
Critic 需要在看到 "黎曼猜" 这几个字的时候,就预测出这句话最终能得多少分(Value)。这几乎是不可能的任务,因为后续生成的质量完全未定。
结果: 在 LLM 训练初期,Critic 的 Loss 通常非常高,且收敛极慢。
后果: 如果 Critic 预测不准(V(s) 瞎猜),那么计算出的优势 就是错误的。Actor 会根据错误的信号进行更新,导致模型性能震荡甚至崩塌(Collapse)。
4. GRPO 的降维打击:用“统计”替代“预测”
既然训练一个神经网络(Critic)去预测 Baseline(V(s))既贵又难,GRPO 选择了一个统计学的方法。
我们不再问 Critic:“这一单大概能得多少分?”
而是直接做实验:“既然我不知道基线,那我就多跑几次,取平均值当基线。”
让我们对比一下 Baseline 的来源:
| 特性 | PPO (Standard) | GRPO |
| Baseline 来源 | 参数化模型 |
蒙特卡洛采样的组均值 (Group Mean) |
| 计算成本 | 高 (需前向/反向传播整个 Critic 网络) | 低 (仅需对 Reward 标量求平均) |
| 准确性依赖 | 依赖 Critic 训练得好不好 | 依赖 Group Size (N) 是否足够大 |
| 无偏性 | Critic 可能有 Bias | 组内均值是当前策略的无偏估计 |
【PPO 的视角:预言家模式】
学生(Actor): "我写完了,看我这篇作文!"
│
▼
预言家(Critic): (甚至还没看别人的)
"基于我对你过往表现和题目难度的深奥计算,
我觉得这种题目的平均分应该是 75 分。"
│
▼
老师(Reward): "这篇实际得分 80 分。"
│
▼
结果: 优势 = 80 - 75 = +5 (你比预言的要好)
----------------------------------------------------
【GRPO 的视角:赛马模式】
学生(Actor): 分身成 4 个人,写了 4 篇作文。
│
▼
作文A (80分) | 作文B (60分) | 作文C (85分) | 作文D (75分)
│
▼
统计员(Math): "大家停一下,算个平均分。"
平均分 = (80+60+85+75)/4 = 75 分。
│
▼
结果(A): 优势 = 80 - 75 = +5 (你比这一组的平均水平好)
本质总结: GRPO 利用大数定律(虽然 N 通常只有 8-64,但在统计上已足够有效)来实时的、动态的构建 Baseline,从而彻底删除了那个笨拙的 Critic 网络。
本讲核心 Takeaway:
-
PPO 的 Critic 在 LLM 时代不仅仅是增加了显存,更重要的是它引入了优化难题。
-
Value Function 在长文本生成任务中极难收敛,不准确的 Value 会误导 Actor。
-
GRPO 通过Group Relative(组相对)的方式,用“采样均值”替代了“参数预测”,在数学上依然构成了有效的 Advantage 估计,同时将计算成本砍半。
GRPO 深度剖析:数学形式化—目标函数的重构
在强化学习中,所有都蕴含在那个我们需要最大化的 之中。GRPO 的美妙之处在于,它在 PPO 的基础上做了一次手术,切除了
,植入了一个基于组(Group)的归一化算子。
1. 符号定义 (Notation Setup)
为了保证严谨性,我们先定义这一讲的数学符合:
-
: 我们要训练的策略模型(Actor),即当前的 LLM。
-
: 参考模型(Reference Model),通常是 SFT 后的初始模型,用于防止模型跑偏。
-
: 用户的问题(Prompt),从数据集 P(Q) 中采样。
-
一组输出(Outputs)。这里 G是组的大小(Group Size),比如 8 或 16。
-
: 第
个输出对应的奖励值(Reward),由 Reward Model 给出。
2. 核心变革:组相对优势
在 PPO 中,优势 依赖于 Critic。而在 GRPO 中,优势
则依赖于同行衬托。
对于同一个问题 ,模型生成了一组输出
,对应的奖励为
。
第 个输出的优势函数定义为:
深度解读:
-
Mean (
): 这一组输出的平均水平。如果
,说明这个回答比你自己生成的平均水平好,应当鼓励(
)。
-
Std (
): 标准差用于缩放。这非常关键!
-
如果某次生成大家的差别很大(比如 [10, 90, 50]),标准差大,优势会被缩小,梯度的更新幅度会变稳。
-
如果差别很小(比如 [80, 81, 79]),说明模型对这个问题的表现很一致,标准差小,微小的分数差异会被放大,模型能学到细微的区别。
-
-
: 一个极小的数(如
),防止分母为 0。
[原始奖励空间 Reward Space]
Group A (简单题): [0.9, 0.92, 0.88, 0.95] <-- 分数都很高
Group B (困难题): [0.1, 0.05, 0.20, 0.15] <-- 分数都很低
如果不做归一化,模型会拼命学习 Group A (Reward大,梯度大),
而忽略 Group B (Reward小,梯度小)。这显然不合理。
-------------------------------------------
[GRPO 归一化空间 Normalized Advantage]
Group A: [-0.4, +0.3, -1.1, +1.2]
Group B: [-0.4, -1.2, +1.2, +0.4]
↑↑↑
你看!无论是简单题还是困难题,
都被拉到了同一个“相对起跑线” (均值为0,方差为1)。
模型现在只关注:在这个场景下,哪个答案相对更好?
3. 终极公式:GRPO 目标函数
有了上面的 ,我们可以写出 GRPO 需要最大化的完整目标函数:
3.1 期望与采样 (Expectation & Sampling)
这告诉我们训练的循环逻辑:
-
采样一个 Prompt
。
-
用旧策略
采样
个输出
。
-
接下来的计算都是基于这
个样本的平均值。
3.2 代理损失 (Surrogate Loss with Clipping)
这是直接继承自 PPO 的部分,但在 $A_i$ 上用了 GRPO 的定义:
-
比率 (Ratio)
:衡量当前策略和采样时的策略差了多少。
-
Clip: 限制更新幅度,防止一次更新步子迈太大把模型扯坏了。这是 PPO 家族稳定性的基石。
3.3 KL 散度惩罚 (KL Divergence Penalty)
为什么要减去它?
-
这是一个正则化项 (Regularization)。
-
是我们的 SFT 模型(它是懂人话的)。
-
是我们在训练的模型。
-
如果
为了拿高分(Reward Hacking),开始输出乱码或者极端的欺骗性文本,它和
的分布差异会变大,KL 散度飙升。
-
因为公式前面是负号,最大化目标函数
就意味着要最小化 KL 散度。
4. Token 级 vs 样本级 KL
在实际实现中(如 DeepSeekMath 的论文),KL 散度通常使用如下近似计算方式:
(注:具体实现有多种近似公式,Schulman 的近似最为常见)
关键点在于:GRPO 通常将 KL 散度作为一个 Loss 项直接加在目标函数里,而不是像某些 PPO 实现那样作为 Reward 的一部分(PPO-reward-shaping)。 这使得训练更加直观,我们明确地知道模型在优化什么:“相对优势”减去“偏离代价”。
本讲核心 Takeaway:
-
公式的核心:
是 PPO Clip Loss 的变体,唯一的区别在于 Advantage 的来源。
-
归一化的魔力:
使得模型在面对不同难度的 Prompt 时,梯度尺度保持稳定,这是 GRPO 训练稳定的关键。
-
正则化: KL 惩罚项是防止模型“走火入魔”的必选组件。
现在,公式已经摆在桌上了。但在实际把代码跑起来之前,我们面临一个极其具体且棘手的超参数选择问题:(Group Size) 到底取多少?
取 4?取 16?还是 64?
如果 太小,统计均值不准确怎么办?如果
太大,显存爆了怎么办?
在下一讲,我们将深入探讨 Group 的奥秘,从统计学角度分析 的大小如何决定了 GRPO 的成败,以及如何根据你的显存大小来权衡这个参数。
GRPO 深度剖析:组(Group)——统计方差与采样策略
1. 为什么
不能太小?——基线估计的方差问题
我们知道,GRPO 用组内均值 来近似真实的 State Value
。从蒙特卡洛(Monte Carlo)的角度看,这是一个无偏估计,但方差(Variance)可能极大。
1.1 统计学直觉
假设真实的价值 。
如果 (极端的例子),我们只采样两个样本:
-
Case 1: 运气好,采到 [74, 76]。均值 75,估计完美。
-
Case 2: 运气差,采到 [50, 60](模型发挥失常)。均值 55。
-
在这个 Case 2 中,60 分的样本会被认为具有“巨大优势”(+5分 relative to 55),而在真实基准(75分)下,它其实是很差的。
-
结论: 当 太小时,Baseline (
) 的波动极其剧烈。这会导致梯度的方差变大,训练过程像是在“走醉鬼步”(Random Walk),难以收敛。
1.2 文献中的经验值
DeepSeek-Math 和 DeepSeek-R1 的论文中,通常倾向于较大的 。
-
DeepSeek-Math: 推荐
。
-
经验法则: 对于复杂的逻辑推理任务(如数学题),解空间的方差本身就大(对就是1,错就是0),需要更大的
来稳定均值。
2. 为什么 $G$ 不能无限大?——边际递减与显存墙
你可能会问:“既然 G 越大估计越准,为什么不设成 1024?”
这里有两个制约因素:
-
显存墙(The VRAM Wall):
GRPO 需要在一次 Forward Pass 中生成 G 个完整的长序列。
-
如果你训练 70B 模型,Context Length 4096。
-
G=64 意味着你需要同时显存驻留 64 条 4096 长度的 KV Cache 和中间激活值。这对显存是毁灭性的打击。
-
-
边际效益递减(Diminishing Returns):
根据中心极限定理,标准误(Standard Error)随
下降。
-
从 4 增加到 16,误差减半,收益巨大。
-
从 64 增加到 256,误差只减半,但计算成本翻了 4 倍。
-
3. 致命陷阱:模式崩塌(Mode Collapse)与标准差
在 GRPO 公式中,分母是 (标准差)。这引入了一个在 PPO 中不存在的风险。
如果 会发生什么?
这意味着模型生成的 个回答完全一样,或者得分完全一样。
-
此时,分子
也是 0。
-
结果:
。
-
梯度消失: 模型学不到任何东西。对于这个 Batch,训练是无效的。
3.1 采样策略的重要性 (Temperature is Key)
在 SFT 中,我们有时会用 Greedy Search (Temp=0) 来追求最稳妥的回答。
但在 GRPO 中,Temperature 绝对不能为 0。
你需要强制引入多样性(Diversity)。
-
Temperature: 通常设为 0.6 - 1.0。
-
Top-P / Top-K: 适当放宽。
我们必须迫使模型在一个Prompt下探索不同的路径。只有产生了差异(Contrast),GRPO 才能通过比较(Comparison)来学习。
【无效的组 (Low Variance)】 -> 训练停滞
Temperature = 0.1
Prompt: 1+1=?
Output 1: 2 (Reward: 1.0)
Output 2: 2 (Reward: 1.0)
Output 3: 2 (Reward: 1.0)
Output 4: 2 (Reward: 1.0)
Stats: μ=1.0, σ=0.0
Advantage: [0, 0, 0, 0] <-- 没有任何信号告诉模型“保持这样”或“改变”
---------------------------------------------
【有效的组 (High Variance)】 -> 高效学习
Temperature = 1.0
Prompt: 1+1=?
Output 1: 2 (Reward: 1.0) -> Adv: +0.8
Output 2: 3 (Reward: 0.0) -> Adv: -1.2
Output 3: 11 (Reward: 0.0) -> Adv: -1.2
Output 4: Two (Reward: 1.0) -> Adv: +0.8
Stats: μ=0.5, σ=0.5
Advantage: 产生了强烈的正负反馈信号!
4. 进阶技巧:Iterative GRPO (在线生成 vs 离线生成)
处理 带来的显存压力,学术界和工业界有两种做法:
4.1 Online Generation (标准 GRPO)
在训练步中实时生成 个样本。
-
优点: 数据是完全 On-Policy 的,完全符合数学推导。
-
缺点: 慢,显存占用大。
4.2 Offline / Hybrid Generation (变体)
先用当前模型跑 inference,把数据存到 Buffer 里(比如存 64 个),然后训练时从 Buffer 里拿数据计算 Loss。
-
风险: 变成了 Off-Policy。如果模型更新了,Buffer 里的数据就“过时”了(Stale)。
-
修正: 需要引入 Importance Sampling (IS) 或者限制 Buffer 的刷新频率。DeepSeek 的实现通常倾向于高效的 Online 生成,或者极短周期的 Buffer。
本讲核心 Takeaway:
-
Group Size (
) 是权衡“估计方差”与“显存成本”的杠杆。一般推荐
。
-
多样性是生命线: GRPO 依赖组内差异来提取信号。必须使用非零的 Temperature 采样。如果模型坍缩到单一输出,GRPO 将失效。
-
标准差的意义:
不仅仅是分母,它是一个“自适应的各种系数”。当大家都很烂时,微小的差异会被放大;当大家都很好时,差异会被缩小。
明白了 的设定,我们其实只解决了一半的问题。公式里的
算出来了,但不要忘了,我们并没有 Critic。这就引出了一个深刻的问题:没有 Value Function 的情况下,我们如何确保 Advantage 的计算真的是“优势”,而不是“噪声”? 尤其是当 Reward 本身很稀疏的时候(比如做数学题,全错就是0,很难拿到1)。
下一讲,我们将深入 优势函数(Advantage Function)的重构,探讨在 Reward 稀疏或密集场景下,GRPO 具体的数值表现和潜在的改进技巧(如 Outcome Reward vs Process Reward)。
在传统的 PPO 中,优势函数 是极其复杂的,它通常利用 GAE(Generalized Advantage Estimation)进行时间步(Token)级别的细粒度指派。而在 GRPO 中,由于移除了 Critic,我们的评价体系发生了根本性的范式转移:从“基于价值预测的绝对评价”转向了“基于群体竞争的相对评价”。
GRPO 深度剖析:优势函数(Advantage Function)的重构——零和博弈与稀疏奖励
1. 零和博弈:归一化的深层含义
让我们再次审视那个看起来人畜无害的公式:
如果你对这一组 求和,你会发现一个惊人的性质:
(注:在 极小的情况下,加权后的均值严格为 0,标准差严格为 1)
1.1 物理意义:组内“内卷”
这意味着 GRPO 构建了一个局部的零和博弈(Zero-Sum Game)环境。
-
在一个 Batch 中,无论大家的回答质量整体有多高(比如都是 99 分),或者整体有多烂(比如都是 1 分),总有一半的回答会被“惩罚”(Adv < 0),另一半会被“奖励”(Adv > 0)。
-
绝对分数的“消逝”: 模型不再关心“我是否考了 60 分及格”,模型只关心“我是否考得比隔壁的同学高”。
1.2 这种设计的利弊
-
利(鲁棒性): Reward Model 有时候会发生 Drift(整体分数虚高或虚低),组内归一化直接消除了这种整体性的 Bias。
-
弊(错误信号): 如果一组回答全是垃圾(Reward 都是 0.0),但有一个稍微沾点边(Reward 0.01),那个 0.01 的回答会被赋予巨大的正优势,导致模型过度强化这个“矮子里拔将军”的行为。(这也是为什么我们需要上一讲提到的 KL 散度约束,防止模型跑偏)
2. 信号粒度的问题:轨迹级 vs Token 级
这是一个学术界非常关注的细节。
-
PPO (Critc-based): 每一句话的每一个 Token,Critic 都会给出一个价值评估
。因此 PPO 可以精确地说:“虽然你最后答错了,但你中间推理的第三步是很棒的。”(Temporal Credit Assignment)。
-
GRPO (Outcome-based): 通常情况下,我们只有一个最终的 Reward
(比如答案对不对)。这意味着,我们将把这个
产生的优势
,广播(Broadcast) 给这条回答中的每一个 Token。
这听起来很粗糙,对吧? 如果我写了 1000 字的推理,只有最后一步错了,导致 ,难道这 1000 字都要被惩罚吗?
DeepSeek 的解决方案:过程奖励与格式奖励
为了缓解这个“一刀切”的问题,GRPO 极其依赖 Reward Engineering。在 DeepSeek-R1 中,Reward 不再是一个单一的标量,而是多个部分的组合:
-
结果奖励 (
): 最终答案对不对?(LeedCode 通过率,数学答案匹配)。
-
格式奖励 (
): 这就是“过程”的一种体现。比如强制模型必须输出
<think>...</think>标签。如果模型写了标签,即便答案错,它也能拿到一部分格式分,避免全盘否定。
通过这种方式,GRPO 在没有 Critic 进行逐词评估的情况下,依然引导模型学习到了结构化的推理能力,还是挺逆天的。
【场景:做一道数学题,过程完美,但最后算错了】
Prompt: 2 * 3 + 4 = ?
Model: "2*3得6, 6+4得... 9!" (错误答案)
---------------------------------------------------
[PPO 的视角 - 精细化打击]
Token序列: [2*3得6] -> [6+4得] -> [9!]
Critic V(s): ↑高 ↑高 ↓骤降
Advantage: (+0.5) (+0.6) (-2.0)
更新结果:
"2*3得6" 被鼓励 (保留正确逻辑)
"9!" 被狠狠惩罚 (修正错误结果)
---------------------------------------------------
[GRPO 的视角 - 连坐制度]
Token序列: [2*3得6] -> [6+4得] -> [9!]
Group Mean: 0.5 (假设别人做对了)
此样本 Reward: 0.0 (因为答案错了)
Advantage: -1.0 (全序列统一惩罚)
更新结果:
"2*3得6" 被惩罚 (无辜躺枪)
"6+4得" 被惩罚 (无辜躺枪)
"9!" 被惩罚 (罪有应得)
[思考]: 为什么 GRPO 依然能工作?
因为在海量的采样中 (Big Data + Large Steps),
"2*3得6" 这一步出现在"正确样本"中的概率远高于"错误样本"。
虽然单次更新有噪声,但期望值 (Expectation) 最终会收敛到正确方向。
这就是大数定律的暴力美学。
4 处理稀疏奖励 (The Sparse Reward Problem)
在数学或代码任务中,Reward 往往是二值的(0 或 1)。这在 GRPO 中会制造麻烦。
极端情况:个样本全错(全是 0)或全对(全是 1)。
此时 ,公式退化。虽然有
保护不报错,但
会变成 0。梯度消失,训练空转。
工程上的应对策略:
-
冷启动(Cold Start): 在使用 GRPO 纯 RL 之前,必须有一个足够强的 SFT 模型,保证它至少有 10%-20% 的概率能答对题。如果模型一开始什么都不会,GRPO 是推不动的。
-
混合奖励: 如前所述,加入 dense reward(如格式检查、长度惩罚、语言流畅度分),确保存活下来的样本之间即使答案都错了,分数也有高低之分(比如写了步骤的 0 分比啥都没写的 0 分要稍微好一点点)。
本讲核心 Takeaway:
-
相对评价: GRPO 构建了一个组内竞争机制,Advantage 均值为 0。这消除了绝对 Reward 的波动影响。
-
粗粒度信号: 相比 PPO 的 Token 级评价,GRPO 默认是 Trajectory 级评价。它依赖海量数据的统计规律来区分哪些 Token 是真正导致错误的。
-
对初始能力的依赖: 因为没有 Critic 指导“这一步走得好”,GRPO 要求模型自身必须具备一定的成功率,否则无法从全 0 的反馈中学习。
现在,我们有了目标函数,有了采样策略,也有了奖励计算方法。 但是,强化学习最怕的就是模型为了拿高分而“走火入魔”(Reward Hacking)。比如,为了让回答变长(如果Reward和长度正相关),模型开始疯狂重复废话。
虽然我们在公式里写了 KL 散度项,但在实际代码和训练中,如何精确计算和控制 KL 散度是一门极深的学问。
在 RLHF 的世界里,有一个著名的诅咒叫 Goodhart's Law(古德哈特定律):“当一个指标变成目标,它就不再是一个好的指标。”
如果我们完全放任 GRPO 去最大化我们定义的 Reward(比如数学题做对得 1 分),而没有任何约束,模型很快就会发现一些人类意想不到的“捷径”(Reward Hacking)。为了防止模型变成一个“为了得分不择手段的疯子”,我们需要一个锚点。这个锚点就是 KL 散度(Kullback-Leibler Divergence)。
GRPO 深度剖析:KL 散度的处理
1. 为什么要加 KL?
想象我们在训练模型写诗。 Reward Model 倾向于给押韵的句子高分。
如果没有 KL 散度约束,GRPO 训练出来的模型可能会变成这样:
Prompt: 写一首关于春天的诗。 Model: "猫 猫 猫 猫 / 喵 喵 喵 喵" (押韵满分,语义崩坏)
这就是 Reward Hacking。模型利用了 Reward Model 的漏洞(比如对某些特定 Token 的偏好),牺牲了语言的可读性和逻辑性来换取高分。
(Reference Model) 的作用就是那个拿着教鞭的老师。它通常是 SFT 阶段得到的模型。它的潜台词是: “你可以去尝试拿高分,但你的说话方式(概率分布)不能离我太远。你首先得像个正常人说话,其次才是答对题。”
2. KL 散度的数学计算:Token 级别的微操
在 LLM 中,KL 散度不是算整个句子的,而是算每一个 Token 的。
对于第 个 Token,模型
预测的概率分布是
,参考模型
预测的概率分布是
。 理论上的 KL 公式是
。
但在 GRPO 的实际工程实现中(参考 DeepSeekMath 和常见的 PPO 实现),我们通常使用 Schulman 近似 或者简单的 Log-Ratio。
最常用的近似公式:
对于生成的某个特定 token :
注意:
-
如果
比
高很多(模型过度自信地输出了这个词),差值为正,KL Loss 变大,惩罚模型。
-
如果
变小了(模型不敢说 SFT 原本会说的词),差值为负,但这通常由期望项平衡。
汇总到 Loss 中
在 GRPO 的目标函数中,我们将这个 KL 值作为一个惩罚项(Penalty)减去:
这里 T 是生成的序列长度。这意味着:每一个偏离 Reference 的 Token 都要付出代价。
3. 两种流派:KL in Reward vs KL in Loss
在代码实现层面,你会看到两种处理 KL 的方式,这点非常容易混淆,你需要特别注意:
流派 A:PPO 经典做法 (Reward Shaping)
直接把 KL 惩罚扣在 Reward 里。
-
逻辑: 这里的 Reward 变成了“综合得分”。
-
在 GRPO 中的问题: 如果我们把 KL 加到
里,再进行 Group Normalization (
),KL 的物理意义会被方差缩放扭曲。这通常不是 GRPO 的最佳实践。
流派 B:DeepSeek-Math 做法 (Direct Loss Term)
保持 Reward 纯净,把 KL 放在目标函数里作为正则项。
-
逻辑:
负责指引方向(哪个答案好),
负责拉住缰绳(别走太远)。
-
优势: 这种分离让优化更可控。
可以独立调节,不受 Reward 数值范围的影响。
我们可以把 想象成地球,
是风筝。
[ 崩坏区 / Collapse Zone ]
(模型输出乱码,但可能偶然骗过 Reward Model)
WARNING: KL >>> 1.0
/
/ <-- 巨大的 KL 惩罚 (强引力)
/
+-----------------------+
| 训练中的模型 | ---> 试图往高 Reward 方向飞
| Policy (Actor) |
+-----------------------+
\
\ <-- Beta (β) 决定了引力绳的粗细
\
[ 安全区 / Trust Region ]
(语言通顺,符合人类语法,由 SFT 模型定义)
Reference Model
-
如果
太大 (Beta=1.0): 引力太强,风筝飞不起来。模型会死死守着 SFT 的参数,不敢做任何探索。训练 Loss 下降,但 Reward 不升。
-
如果
太小 (Beta=0.0): 绳子断了。风筝飞入太空(崩坏区)。模型为了优化 Reward 开始胡言乱语。
-
黄金甜点 (Beta
0.01 - 0.05): 既允许模型探索新的解题路径,又强迫它用“人话”把答案写出来。
动态 Beta (Adaptive KL) —— 进阶技巧
在长时间的训练(如几万步)中,固定的 往往不好用。
-
训练初期,模型很容易偏离,需要强 KL 约束。
-
训练后期,模型已经学乖了,可以适当放松
让它冲击更高分。
或者反过来(PPO 中常用):设定一个目标 KL 值(比如 )。
-
如果当前 KL >
:说明跑太远了,调大
。
-
如果当前 KL <
:说明太保守了,调小
。
虽然 GRPO 论文中较少强调这一点,但在工程实践中,监控 KL 曲线是判断训练是否健康的唯一标准。
-
健康的曲线: KL 缓慢上升,然后稳定在一个小数值(如 0.01-0.05)。
-
暴雷的曲线: KL 指数级爆炸,或者突然变成 0。
本讲核心 Takeaway:
-
安全锚点: KL 散度约束是 RLHF 区别于普通 Fine-tuning 的关键,它保证了生成的语言质量。
-
公式选择: 在 GRPO 中,建议将 KL 作为 Loss 的独立正则项,而不是混入 Reward,以避免被 Normalization 干扰。
-
计算粒度: KL 是 Token-level 的。每一个“标新立异”的词都会累积罚分。
理论闭环已经完成。现在,我们需要把这些积木拼成一个完整的、会动的机器。 下一讲,我们将进入系统级的整合,我们将像拆解引擎一样,一步步拆解 GRPO 的完整训练循环 (Training Loop)。我们将看到数据是如何在 GPU 之间流转,以及梯度是如何反向传播的。
GRPO 深度剖析·:训练循环详解
GRPO 的训练过程可以被切割为两个截然不同的阶段:
-
Rollout Phase (采样阶段):只做推理,不传梯度,极度消耗显存带宽。
-
Training Phase (学习阶段):反向传播,更新权重,极度消耗算力。
1. Step 1: 采样 (Rollout / Generation) —— 最昂贵的开销
一切始于一批 Prompts。假设我们的 Batch Size (of Prompts) = ,Group Size =
。
我们需要生成 条完整的回答。
-
输入:
个问题(例如:["1+1=?", "解释量子力学", ...])。
-
动作: 模型
(当前的 Actor)进入
eval()模式。 -
操作:
-
复制每个 Prompt
次。
-
并行生成
个回答。
-
关键数据留存: 在生成的过程中,我们要顺手把每个 Token 的Log Probability (LogProbs) 存下来!
-
为什么要存?因为计算 Loss 里的比率
时,分母就是这个时候算出来的。如果不存,训练时就要多做一次前向传播,太亏了。
-
-
注意: 这是 GRPO 训练中最慢的一步。对于 70B 模型,生成长文本(COT)可能占据整个训练时间的 70%-80%。所以这里通常会用到 vLLM 等加速推理框架。
2. Step 2: 评分与归一化 (Evaluation & Advantage)
生成结束后,GPU 显存里躺着 条轨迹(Trajectories)。现在的任务是给它们打标签。
-
动作:
-
打分: 调用 Reward Function(可能是代码解释器,也可能是另一个 Reward Model),得到
个标量奖励
。
-
分组归一化(核心):
-
将数据切分为
组,每组
个轨迹。
-
对每组计算均值
和标准差
。
-
计算优势
。
-
-
释放显存: 此时,原始的 KV Cache 可以扔掉了(除非你用 PPO 的特殊实现),我们只需要 Input IDs, Masks, Old LogProbs 和 Advantages。
-
3. Step 3: 学习 (Optimization) —— 内层循环
数据准备好了,现在进入真正的“训练”环节。这通常是一个内层循环(Inner Epochs)。
也就是说,我们辛辛苦苦生成的这批数据,不会只用一次,通常会反复用它更新模型 1-4 次(Update Epochs)。
对于每一次更新迭代:
-
Actor Forward (新策略前向传播):
-
把刚才那
条数据喂给当前正在更新的 Actor 模型。
-
计算 New LogProbs。
-
注意: 这一步需要梯度
requires_grad=True。
-
-
Reference Forward (参考模型前向传播):
-
把同样的数据喂给 Reference Model(冻结参数)。
-
计算 Ref LogProbs。
-
用途: 用于计算 KL 散度。
-
优化技巧: 如果显存不够,这一步可以预先算好存起来,牺牲磁盘/内存换显存。
-
-
Loss Calculation (损失计算):
-
Ratio:
。
-
Surrogate Loss: 用 Clip 公式结合 Advantage 计算策略梯度。
-
KL Loss:
。
-
Total Loss: Surrogate Loss + KL Loss。
-
-
Backward & Step (反向传播与更新):
-
loss.backward() -
optimizer.step()
-
实例
Prompt (输入): “2 + 2 = ?”
Group Size (): 4 (我们让模型生成 4 个回答)
模型生成了以下 4 条数据(为了方便理解,我把 Token 拆开了):
-
回答 A:
["是", "4", "<EOS>"](正确,简洁) -
回答 B:
["等", "于", "5", "<EOS>"](错误) -
回答 C:
["答", "案", "是", "4", "<EOS>"](正确,啰嗦) -
回答 D:
["不", "知", "道", "<EOS>"](错误,拒绝回答)
第一步:打分 (Scoring) —— 此时与 Token 长短无关
GRPO 并不是看一个字打一次分,而是看完整个句子打一次分。
假设 Reward Model 只看结果对不对(对=1.0,错=0.0):
-
回答 A 得分 (
): 1.0
-
回答 B 得分 (
): 0.0
-
回答 C 得分 (
): 1.0
-
回答 D 得分 (
): 0.0
注意:此时我们手里的 Reward 只是 4 个简单的数字:[1.0, 0.0, 1.0, 0.0]。完全不涉及 Token 的切分。
第二步:计算均值与标准差 (Group Statistics)
现在我们在组内(Group)计算统计量:
-
均值 (
):
-
标准差 (
): 计算可得
-
计算优势 (
): 公式
-
回答 A (Adv):
-
回答 B (Adv):
-
回答 C (Adv):
-
回答 D (Adv):
-
关键点: 到目前为止,我们只得到了 4 个标量值。
第三步:广播 (Broadcasting)
现在的难题是:回答 A 有 3 个 Token,回答 C 有 5 个 Token,长度不一样,怎么把这一个标量 用到计算里?
这就叫广播。我们将这个优势值,像“淋雨”一样,均匀地淋在每一个 Token 头上。
看看【回答 C】的内部视角:
Token 序列:["答", "案", "是", "4", "<EOS>"]
优势值 :+1.0
在计算 Loss 时,我们会展开成这样:
| 时间步 (t) | Token (xt) | 模型预测概率 (π) | 这一步的优势 (A^t) | 解释 |
| t=1 | 答 | 0.2 | +1.0 | 既然结果对了,鼓励你说“答” |
| t=2 | 案 | 0.9 | +1.0 | 既然结果对了,鼓励你说“案” |
| t=3 | 是 | 0.8 | +1.0 | 既然结果对了,鼓励你说“是” |
| t=4 | 4 | 0.9 | +1.0 | 既然结果对了,鼓励你说“4” |
| t=5 | EOS | 0.99 | +1.0 | 既然结果对了,鼓励你结束 |
哪怕 "答" 和 "案" 这两个字本身和数学没关系,它们也会被同样地奖励。
再看看【回答 B】的内部视角:
Token 序列:["等", "于", "5", "<EOS>"]
优势值 :-1.0
| 时间步 (t) | Token (xt) | 模型预测概率 (π) | 这一步的优势 (A^t) | 解释 |
| t=1 | 等 | 0.6 | -1.0 | 既然结果错了,惩罚你说“等” |
| t=2 | 于 | 0.8 | -1.0 | 既然结果错了,惩罚你说“于” |
| t=3 | 5 | 0.3 | -1.0 | 既然结果错了,惩罚你说“5” |
| t=4 | EOS | 0.9 | -1.0 | 既然结果错了,惩罚你结束 |
第四步:Masking (对齐与遮盖) —— 工程实现的最后一步
在 GPU 里,因为 tensor 必须是方方正正的,所以短的句子后面会补 0 (Padding)。同时,我们不能计算 Prompt 的 Loss。
假设 Prompt 长度为 5。 回答 A 长度为 3。 回答 C 长度为 5。 最大长度为 5。
优势矩阵 (Advantage Tensor) 的形状是 [Batch, Group, Max_Seq_Len]:
Prompt 部分 (Mask掉,不算分) | Response 部分 (参与计算)
------------------------------------------------------------
[ 0, 0, 0, 0, 0, +1.0, +1.0, +1.0, 0, 0 ] <- 回答 A (补了2个0)
[ 0, 0, 0, 0, 0, -1.0, -1.0, -1.0, -1.0, 0 ] <- 回答 B (补了1个0)
[ 0, 0, 0, 0, 0, +1.0, +1.0, +1.0, +1.0, +1.0 ] <- 回答 C (填满)
[ 0, 0, 0, 0, 0, -1.0, -1.0, -1.0, -1.0, 0 ] <- 回答 D (补了1个0)
[ Prompts (Batch=B) ]
│
▼
+-----------------------------+
| Generation Phase | <--- 🕰️ 耗时最长
| (No Grad, Temp > 0) |
| Outputs: B * G trajectories |
| Save: Old LogProbs |
+-----------------------------+
│
▼
+-----------------------------+
| Scoring Phase |
| 1. Calculate Rewards (r) |
| 2. Group Normalization | <--- GRPO 魔法发生地
| 3. Compute Advantage (A) | A = (r - μ) / σ
+-----------------------------+
│
▼
[ Training Buffer ]
(包含: InputIDs, Old_LogProbs, Advantages)
│
▼
+-----------------------------+ <--循环 N 次 (Inner Epochs)
| Optimization Phase |
| |
| Current Policy (Actor) --> New LogProbs (with Grad)
| Reference Model --> Ref LogProbs (No Grad)
| |
| Loss = PPO_Clip(A) + β*KL |
| Backprop |
+-----------------------------+
│
▼
[ Updated Actor Model ] --> 准备下一轮采样
工程细节:显存优化的关键点
GRPO 虽然去掉了 Critic,但 倍的数据量依然庞大。
-
Gradient Checkpointing (梯度检查点): 必须开。用 30% 的额外计算时间换取 4-5 倍的显存空间。否则
的长序列直接 OOM。
-
Micro-Batching (微批次):
-
虽然我们采样了
个样本(比如
),在 Optimization 阶段,我们不需要一次性把 64 个样本全塞进 GPU。
-
我们可以把这 64 个样本拆成 Chunk(例如每次 2 个),累积梯度(Gradient Accumulation),最后更新一次。
-
注意: Group Normalization 必须在切分 Micro-Batch 之前 完成!优势的计算是基于全组的,不能切碎了算。
-
本讲核心 Takeaway:
-
两段式结构: GRPO 严格区分“造数据(Rollout)”和“吃数据(Train)”。
-
数据复用: 为了摊薄昂贵的采样成本,通常会对同一批数据进行多次梯度更新(Inner Epochs)。
-
计算瓶颈: 与 SFT 不同,SFT 的瓶颈在反向传播,GRPO 的瓶颈通常在生成阶段(因为是自回归的逐词生成)。
现在,我们已经彻底搞懂了 GRPO 内部是如何运转的。 但是,没有对比就没有伤害。学术界现在百家争鸣,DPO (Direct Preference Optimization) 也是红极一时,号称“不需要强化学习的强化学习”。
GRPO 深度剖析:横向对比——GRPO vs DPO vs PPO,谁才是终局?
1. 三国鼎立:流派概览
首先,让我们用最简练的语言定义这三位选手:
1.PPO (Proximal Policy Optimization):
-
哲学: “我要请个老师(Critic)时刻盯着你,你每走一步(Token),我就告诉你这步走得好不好。”
-
状态: 极其强大,但重得像坦克,显存杀手,调参火葬场。
2.DPO (Direct Preference Optimization):
-
哲学: “我不需要老师。我只要看两份作业,一份是满分(Winner),一份是零分(Loser)。我只学满分的,远离零分的。”
-
状态: 轻量级,本质上是对比学习(Contrastive Learning),不是强化学习。训练极快,但容易过拟合。
3.GRPO (Group Relative Policy Optimization):
-
哲学: “我不需要老师,但我让你们全班(Group)考试。谁考得比平均分高,谁就是好学生。”
-
状态: 结合了 PPO 的在线探索能力和 DPO 的轻量化优势。
2. DPO 的死穴:为什么推理模型(O1/R1)不爱用 DPO?
DPO 在聊天、写小说、排比句等主观偏好(Alignment)任务上是非常牛。但在数学、代码等客观推理(Reasoning)任务上,它有致命缺陷。
2.1 缺陷一:缺乏探索 (No Exploration)
-
DPO (Off-policy): 数据集是静态的,通常由 GPT-4 蒸馏生成好和坏回答。
-
训练时: 模型看着 GPT-4 的答案说:“哦,我要模仿这个”。
-
问题: 如果 GPT-4 的解法不是最优的呢?或者模型自己能想出一种更“刁钻”的解法呢?DPO 不允许模型尝试新路径。它只是在做Behavior Cloning (行为克隆) 的变体。
2.2 缺陷二:分布偏移 (Distribution Shift)
DPO 的数据往往是离线的。
-
如果你的模型(Student)很弱,而数据(Teacher)太强,模型根本理解不了 Teacher 为什么这么做,只能死记硬背。
-
GRPO (On-policy): 数据是模型自己刚刚生成的。
-
“这是我自己写出来的答案,虽然烂,但我知道我是怎么想的。”
-
基于自己的能力边界进行改进,这种**自举(Bootstrapping)对于逻辑能力的提升至关重要。
-
【DPO 的路径:照猫画虎】
标准答案(y_w): A -> B -> C -> D (完美路径)
模型能力: 只能走到 B,后面看不懂。
结果: 强行背诵 C -> D。遇到新题直接懵逼。
-------------------------------------------
【GRPO 的路径:自我进化】
尝试1: A -> X -> Y (错) -> Reward: 0
尝试2: A -> B -> E (错) -> Reward: 0
尝试3: A -> B -> C (对!) -> Reward: 1 <-- "Aha! 我找到路了!"
结果: 模型通过无数次试错,真的"理解"了从 B 到 C 的逻辑。
3. PPO vs GRPO:同门师兄弟的权衡
既然确定了要用 RL(强化学习),为什么不坚持用 PPO?
3.1 显存与计算效率
-
PPO: 需要 Critic 模型。对于 70B 模型,这基本上意味着显存翻倍。
-
GRPO: 只要 Actor。通过多次采样(Group Sampling)来代替 Critic。
-
代价: GRPO 的采样阶段(Inference)很慢。
-
收益: 显存占用低,能训练更大的模型。
-
结论: 在推理算力(vLLM)越来越便宜,而训练显存(H100)依然昂贵的今天,GRPO 是更经济的选择。
-
3.2 信号的细腻程度
-
PPO: Token-level 信号。Critic 能告诉你:“第 5 步推导错了”。
-
GRPO: Trajectory-level 信号。只能告诉你:“这一整题做错了”。
-
这看起来是 GRPO 的劣势。但在 DeepSeek-R1 等工作中,通过思维链(CoT)和过程奖励(Process Reward)的结合,或者仅仅依靠大规模数据的统计规律,GRPO 证明了即使信号粗糙,只要样本量够大,依然能收敛出 SOTA 的效果。
-
| 维度 | DPO | PPO | GRPO |
| 本质 | 监督学习 (Contrastive) | 强化学习 (Actor-Critic) | 强化学习 (Policy Gradient) |
| 核心机制 | 拟合偏好数据 (Offline) | 价值函数指引 (Online) | 组内相对优势 (Online) |
| 显存占用 | ⭐ (极低) | ⭐⭐⭐ (极高) | ⭐⭐ (中等,含采样) |
| 训练稳定性 | ⭐⭐⭐ (需调参少) | ⭐ (极难调) | ⭐⭐ (较稳,看Group大小) |
| 探索能力 | 无 (模仿) | 强 (探索) | 强 (探索) |
| 适用场景 | 聊天、风格对齐、安全 | 机器人控制、复杂博弈 | 逻辑推理、数学、代码 |
| Critic网络 | 不需要 | 需要 | 不需要 |
| Baseline | 无 | Value Model | Group Mean |
为什么 GRPO 是“推理”的未来?
DeepSeek-Math 和 DeepSeek-R1 的成功揭示了一个趋势:
对于推理(Reasoning)任务,验证(Verification)比生成(Generation)容易。
-
判断一道数学题做没做对(验证),只需要一个简单的 Python 脚本或 Rule。
-
让 DPO 去模仿解题过程,很容易学到皮毛(格式)学不到神髓(逻辑)。
-
让 GRPO 去猜答案,然后通过客观的验证器(Verifier/Compiler)给反馈,模型就能在不断的“猜-验证-修正”中涌现出真正的智能。
结论: 如果你的目标是让模型像人一样说话,用 DPO。如果你的目标是让模型解出人类未解的数学题,必须用 RL (GRPO/PPO)。而 GRPO 是目前 RL 路线中性价比最高的工程解法。
本讲核心 Takeaway:
-
DPO 缺乏探索: 它是离线的,适合对齐人类偏好,但不适合探索未知解法(推理)。
-
GRPO 是 PPO 的瘦身版: 它用计算(采样)换显存(去 Critic),在算力成本结构变化的今天,这笔交易极其划算。
-
推理需要 On-policy: 想要复现 O1/R1 的效果,必须让模型在环境中实时试错,GRPO 提供了这种环境。
现在,理论、公式、对比都讲完了。
你已经具备了手搓 GRPO 的所有知识储备。
在下一讲,我们将进入代码实战环节。我将为你构建一个PyTorch 风格的 GRPO 最小实现(Minimal Implementation)。我们将不再谈论抽象的 ,而是谈论具体的
torch.gather, logits, 和 loss.backward()。
GRPO 深度剖析·:用 PyTorch 伪代码还原 GRPO
假设我们已经完成采样(Rollout),数据已经躺在显存里了。 现在的任务是:计算 Loss 并反向传播。
1. 核心输入数据结构 (The Inputs)
在进入计算之前,你必须极其清楚你的 Tensor 形状(Shape)。 假设:
-
Batch Size (B): 2 (有多少个 Prompt)
-
Group Size (G): 4 (每个 Prompt 生成了 4 个回答)
-
Sequence Length (L): 1024 (Token 长度)
import torch
import torch.nn.functional as F
# 1. input_ids: 包含了 Prompt + Response 的完整 token 序列
# Shape: [B, G, L] -> [2, 4, 1024]
# 在实际通过模型前,通常会 flatten 成 [B*G, L] -> [8, 1024]
input_ids = ...
# 2. attention_mask: 标记哪些是 Padding (0), 哪些是真实数据 (1)
# Shape: [B*G, L]
attention_mask = ...
# 3. old_log_probs: 在采样阶段(Rollout)记录下来的 log 概率
# 这是为了计算 PPO 的 Ratio。
# Shape: [B*G, L]
old_log_probs = ...
# 4. rewards: 外部 Reward Model 给出的分数,每个回答一个分
# Shape: [B, G] -> [2, 4]
# 注意:这是标量,还没广播到 Token 级别
rewards = torch.tensor([
[1.0, 0.0, 1.0, 0.5], # Group 1
[0.2, 0.2, 0.9, 0.1] # Group 2
])
2. 核心函数一:计算优势 (Compute Advantages)
这是 GRPO 区别于 PPO 的灵魂代码。我们需要在 Group 维度上做归一化。
def compute_grpo_advantages(rewards, epsilon=1e-8):
"""
输入 rewards: [B, G]
输出 advantages: [B, G]
"""
# 1. 计算组内均值 (Mean)
# dim=1 表示沿着 Group 维度求平均
mean = rewards.mean(dim=1, keepdim=True) # Shape: [B, 1]
# 2. 计算组内标准差 (Std)
std = rewards.std(dim=1, keepdim=True) # Shape: [B, 1]
# 3. 归一化 (Normalization)
# 广播机制会自动把 [B, 1] 扩展成 [B, G]
advantages = (rewards - mean) / (std + epsilon)
# 返回 Shape: [B, G]
return advantages
3. 核心函数二:获取 Token 概率 (Get LogProbs)
我们需要让模型再跑一次前向传播(Forward),拿到当前的 LogProbs。 注意:这里有一个经典的 gather 操作,一定要看懂。
def get_per_token_logps(model, input_ids, attention_mask):
"""
计算给定序列中每个 Token 的 Log Probability
"""
# 1. 模型前向传播
# outputs.logits Shape: [B*G, L, Vocab_Size]
outputs = model(input_ids, attention_mask=attention_mask)
logits = outputs.logits
# 2. 对 Logits 取 LogSoftmax (转成 Log 概率)
# Shape: [B*G, L, Vocab_Size]
all_logprobs = F.log_softmax(logits, dim=-1)
# 3. 提取特定 Token 的概率 (Gather)
# input_ids 是我们需要模型预测的目标词。
# 我们通常把 input_ids 向左移一位作为 label (next token prediction),这里简化处理。
# 这一步是为了只把“模型实际生成的那个词”的概率拿出来。
# Shape: [B*G, L]
token_logprobs = torch.gather(
all_logprobs,
dim=2,
index=input_ids.unsqueeze(-1)
).squeeze(-1)
return token_logprobs
4. 核心函数三:GRPO Loss 计算 (The Grand Assembly)
现在把所有东西拼起来。
def grpo_loss_step(model, ref_model, input_ids, mask, old_log_probs, rewards, beta=0.04):
"""
单步训练逻辑
"""
# --- Step 1: 准备优势 (Advantage) ---
# Shape: [B, G]
advantages = compute_grpo_advantages(rewards)
# [关键] 广播机制 (Broadcasting)
# 我们要把 [B, G] 的优势扩展到每个 Token 上 [B*G, L]
# 先 flatten 成 [B*G]
advantages = advantages.view(-1)
# 再扩展到序列长度 (这里只是逻辑示意,实际代码通常在计算 loss 时利用 broadcasting)
# 假设我们让 advantage 变成 [B*G, 1],后面乘以 [B*G, L] 的 loss 矩阵
advantages = advantages.unsqueeze(1)
# --- Step 2: 当前模型前向传播 ---
# Shape: [B*G, L]
new_log_probs = get_per_token_logps(model, input_ids, mask)
# --- Step 3: 参考模型前向传播 (用于 KL) ---
with torch.no_grad():
ref_log_probs = get_per_token_logps(ref_model, input_ids, mask)
# --- Step 4: 计算比率 (Ratio) ---
# ratio = P_new / P_old = exp(log_P_new - log_P_old)
# Shape: [B*G, L]
ratio = torch.exp(new_log_probs - old_log_probs)
# --- Step 5: PPO Clip Loss (Surrogate) ---
# loss1 = ratio * A
surr1 = ratio * advantages
# loss2 = clip(ratio) * A
surr2 = torch.clamp(ratio, 1 - 0.2, 1 + 0.2) * advantages
# 取最小值 (PPO 核心),注意是负号因为要 maximize advantage
policy_loss = -torch.min(surr1, surr2)
# --- Step 6: KL Penalty ---
# approx_kl = log_P_new - log_P_ref
kl_div = new_log_probs - ref_log_probs
# 这种写法是把 KL 当作 Loss 的一项
kl_loss = beta * kl_div
# --- Step 7: 综合 Loss ---
# Shape: [B*G, L]
total_loss = policy_loss + kl_loss
# --- Step 8: Masking (只计算 Response 部分) ---
# 我们不能计算 Prompt 部分的 Loss,也不能计算 Padding 的 Loss。
# 假设我们要 mask 掉 Prompt 和 Padding,只保留 Response。
# completion_mask 是一个 0/1 矩阵,标记哪些是回答的 token。
completion_mask = ... (根据 prompt 长度生成)
# 应用 Mask 并求平均
masked_loss = total_loss * completion_mask
final_loss = masked_loss.sum() / completion_mask.sum()
return final_loss
本讲核心 Takeaway:
-
代码即公式: GRPO 的代码实现就是对 PPO Loss 的一次简单修改,改动不超过 50 行。
-
Group 处理: 核心在于把
[B, G]的 Reward 变成[B, G]的 Advantage,然后 Flatten 成[B*G]喂给 Loss 计算。 -
计算图:
ref_model不需要梯度,old_log_probs是常数(detach),只有new_log_probs所在的计算图需要反向传播。
进阶与展望——R1 的变体与推理的未来
1. DeepSeek-R1 的实战变体:规则即奖励 (Rule-based Rewards)
DeepSeek-R1 最让人震撼的一点是:它在 R1-Zero 阶段,完全没有使用神经网络作为 Reward Model。
这是一个极其反直觉的“退步”。我们花了几年时间研究怎么训练 Reward Model,结果 R1 告诉我们:对于推理任务,只要写个 Python 脚本就够了。
GRPO + Compiler/Verifier
在 R1 的 GRPO 训练中,Reward 的构成极其朴素:
-
准确性奖励 (Accuracy Reward): 把模型生成的答案(Answer)塞进编译器运行,或者与标准答案字符串匹配。通过 = 1.0,失败 = 0.0。
-
格式奖励 (Format Reward): 强制模型把思考过程写在
<think>和</think>之间。这其实是一种“强约束引导”,防止模型在探索过程中把格式跑丢了。
洞察: 这意味着 GRPO 将强化学习从“拟合人类偏好”(这是玄学)拉回了“通过客观检验”(这是科学)。只要你能写出一个验证脚本(Checker),你就能用 GRPO 训练出一个超越人类的模型。
2. 解决冷启动问题 (The Cold Start Problem)
在第五讲我们提到,如果模型一道题都做不对,GRPO 是无效的(全 0 奖励)。DeepSeek-R1 是如何解决这个问题的?
这里有两个阶段的 GRPO:
-
R1-Zero (纯 RL): 让模型盲搜。这需要极大的耐心和算力。初期模型会输出无数的乱码和错误,直到偶然间(Serendipity)它撞对了一次。一旦撞对,GRPO 的优势函数就会瞬间捕捉到这个信号,指数级地放大这条路径的概率。这是“顿悟时刻”(Aha Moment)的数学本质。
-
R1 (SFT + RL): 先用少量高质量的“思维链数据”做 SFT,把模型教会“怎么像人一样思考”。这相当于给 GRPO 一个很高的初始起点(Initial Policy)。这种做法收敛更快,效果更稳。
未来趋势: 如何更高效地获取“第一口奶”(SFT Data)?使用小模型(Student)去蒸馏大模型(Teacher)的 GRPO 轨迹,可能是一条捷径。
3. 迈向多模态与 Agent (Multimodal & Agents)
GRPO 的逻辑可以完美迁移到 Text 以外的领域。
3.1 Visual GRPO
想象一个视觉推理任务:“图中这只猫在干什么?”
-
Prompt: 图片 + 问题。
-
Group: 生成 16 个不同的解释。
-
Verifier: 这里很难用规则判断。但如果是“数图中有几个苹果”,这就是完美的 GRPO 场景。模型多数几次,如果答案是 5,且检测框对准了,Reward = 1。
3.2 Agent Tool Use
Agent 调用工具(如搜索、计算器)本质上也是一种推理路径。
-
Action: 调用 API。
-
Outcome: API 返回结果是否解决了用户问题?
-
GRPO 的机会: 我们可以采样一组 API 调用序列。有的序列调用了 10 次才解决,有的调用了 2 次就解决了。GRPO 会自然地奖励那些既快又准的工具使用策略。
4. 终极猜想:Process GRPO (Dense Reward)
目前的 GRPO 还是基于结果(Outcome)的。就像老师只看卷子最后的答案给分。
我个人猜想未来的 GRPO 可能会进化为 Dense GRPO。
我们不需要训练一个巨大的 Critic,但我们可以引入轻量级的 Step-level Verifier。
-
每生成一行代码,就跑一次语法检查。
-
每推导一步数学公式,就用符号计算引擎(SymPy)验算一步。
这样,Reward 不再是一个标量,而是一个向量
。
优势函数 将利用蒙特卡洛树搜索(MCTS)的思想,更精准地评估每一步的价值。这将是 System 2 思维的完全体。
【GRPO 知识图谱】
1. 核心动机 (Motivation)
└── 抛弃 Critic -> 省显存 -> 赋能大模型训练 (Lecture 1-2)
2. 数学原理 (Math Foundation)
├── 目标函数: Clip Loss + KL Penalty (Lecture 3, 6)
├── 优势函数: Group Normalization (r - μ) / σ (Lecture 5)
└── 采样策略: Group Size & Temperature (Lecture 4)
3. 工程实现 (Implementation)
├── 训练循环: Rollout -> Score -> Train (Lecture 7)
├── 代码实战: Gather, Broadcasting, Masking (Lecture 9)
└── 算法对比: PPO vs DPO vs GRPO (Lecture 8)
4. 未来展望 (Future)
└── Rule-based Reward, Self-Verification, Agents (Lecture 10)
你现在已经掌握了目前 LLM Post-training 领域最前沿的武器。
-
当你看到显存不够时,你会想到 GRPO。
-
当你看到模型推理能力弱时,你会想到 GRPO。
-
当你看到 DPO 遇到瓶颈时,你会想到 GRPO。
能看到这里,说明你有及其惊人的毅力和学习渴望,希望本人小小的一点分享,能够帮助大家完全搞懂GRPO,在AGI的道路上出一份力,加油!
更多推荐
所有评论(0)