GRPO 与可验证奖励强化学习(RLVR)

Build~70 minutesPython先修要求: Phase 9 · 08 (PPO), Phase 9 · 09 (Reward Modeling / RLHF)
PPO 需要一个 critic 网络和一个学习出来的奖励模型。GRPO 把 critic 扔掉了:同一个提示采样一组回答,用这组回答自身的统计量当 baseline。再把奖励模型换成一个程序化验证器,你就得到了 DeepSeek-R1 和 Kimi k1.5 背后的配方——用机器能检查的奖励来训练推理模型。
学习目标
  • 解释为什么对于答案可检查的领域,可验证奖励消除了奖励模型的攻击面。
  • 推导 GRPO 目标:组相对优势、裁剪比率,以及相对参考策略的 k3 KL 惩罚。
  • 在 NumPy 里对一个玩具可验证任务实现完整可跑的 GRPO 训练循环,看成功率爬升。
  • 描述 DeepSeek-R1 pipeline 各阶段(冷启动 SFT、推理 RL、拒绝采样、全场景 RL)以及每个奖励项的来源。
  • 实现 Kimi k1.5 长度控制奖励,并预测它对回答长度和准确率的影响。
  • 识别两个经典失败模式:把验证器 reward hacking 掉,以及朴素格式奖励导致的长度塌缩。

问题

第 09 课搭出了 RLHF pipeline:先预训练,再用 PPO 对照一个学习出来的奖励模型优化策略。这条 pipeline 有两个昂贵的部件。

奖励模型是个负债。 它是一个模仿人类偏好的神经网络,而策略恰恰被优化去最大化它的打分。"打分高"和"真的对"之间的任何缝隙都是攻击面。训练规模一大,策略就会找到这些缝隙:冗长的回答、自信的含糊其辞、谄媚。对奖励模型的 red-teaming 永远做不完,因为策略永远在探测它。

critic 是死重。 PPO 学习价值函数 V_\psi(s) 只是为了给策略梯度算优势。对 LLM 负载来说,这是第二个全尺寸模型躺在显存里、接收梯度,而且更糟的是——对稀疏的二值奖励("答案对或不对"),它的价值估计几乎没用。当大多数 rollout 得分为 0 时,V_\psi(s) 学到的主要就是预测 0。

然而对一大类提示,这些机制都不需要。数学题有标准答案。编程题有单元测试。格式要求就是正则表达式。如果奖励可以被计算出来而不是被预测出来,奖励模型就消失了;如果奖励是单个终止标量,价值网络本来也是错误的工具。

这就是本课的设定:可验证奖励强化学习(RLVR)——奖励由程序发出——用组相对策略优化(GRPO)来优化——一个 PPO 变体,用同一提示采样出的一组样本的经验统计量取代 critic。

概念

RLVR:奖励是一个程序

验证器是从回答到奖励的函数:

r(x, y) = \begin{cases} 1 & \text{if } \text{check}(x, y) = \text{true} \\ 0 & \text{otherwise} \end{cases}

其中 x 是提示,y 是回答。真实系统会叠加多个检查器:准确性奖励(对照标准答案精确匹配,或单元测试通过)、格式奖励(答案出现在指定标签内,CoT 与最终答案分离),以及可选的软性项(代码能否编译、kernel 的运行时间)。

验证器是精确的,在百万级 rollout 上跑起来很便宜,而且——因为它是一段代码——它无法像神经奖励模型那样被逐步漂移优化。它仍然可能在设计空间里被 hack:如果 check 只比对最终的数字,策略可能学会跳过推理直接猜。R1 的对策是要求推理过程以固定格式呈现,并把训练拉得足够长,让"猜"的胜率不如"推理"。验证器设计是一个工程决策,每个领域都要重新审视。

GRPO:组就是 baseline

PPO 的优势估计 A_t = Q(s_t, a_t) - V_\psi(s_t) 需要 critic。GRPO(Shao 等人 2024,随 DeepSeekMath 提出)改为从同一个提示用当前策略采样一组 G 个回答 \{y_1, \dots, y_G\},用验证器逐个打分,然后在组内归一化:

\hat A_i = \frac{r_i - \operatorname{mean}(r_1, \dots, r_G)}{\operatorname{std}(r_1, \dots, r_G) + \varepsilon}

组均值就是"这个提示"这个状态的价值的蒙特卡洛估计——恰好是 critic 在近似的东西——而且是从你反正需要的 rollout 里免费算出来的。当全部 G 个回答拿到相同奖励(全过或全挂)时,每个优势都是 0,这个提示不贡献梯度。这是正确行为:一个被轻松解决或毫无希望解决的提示什么都教不了,GRPO 会自动把训练聚焦在有用的带宽内。

优化步是 PPO 的裁剪替代目标加上参考策略 KL,KL 用 k_3 估计量(无偏、恒非负):

\mathcal{L}(\theta) = -\frac{1}{\sum_i |y_i|} \sum_i \sum_t \Big[ \min\big( \rho_{i,t} \hat A_i,\; \operatorname{clip}(\rho_{i,t}, 1-\epsilon, 1+\epsilon) \hat A_i \big) \Big] + \beta\, \mathbb{D}_{KL}\!\big[\pi_\theta \| \pi_{\text{ref}}\big]
\rho_{i,t} = \frac{\pi_\theta(y_{i,t} \mid x, y_{i,<t})}{\pi_{\theta_{\text{old}}}(y_{i,t} \mid x, y_{i,<t})}, \qquad \mathbb{D}_{KL}[\pi_\theta\|\pi_{\text{ref}}] = \frac{\pi_{\text{ref}}}{\pi_\theta} - \log\frac{\pi_{\text{ref}}}{\pi_\theta} - 1

用工程师的方式读这些部件:

  • \rho_{i,t} 是采样策略与被更新策略之间的逐 token 重要性比率。有了 clip,对同一组数据做多个 epoch 仍近似保持同策略。
  • 裁剪 \epsilon \approx 0.2 限制了任何单个 token 每次更新能把策略推多远。
  • \beta \approx 0.001–0.04 把策略锚在参考策略(SFT checkpoint)上。太大,模型拒绝离开初始分布;太小,策略漂移成仍然满足验证器的退化文本。

R1 配方

DeepSeek-R1 报告(2025)是规模化 RLVR 的参考 pipeline:

  1. 冷启动 SFT — 几千条长思维链示例,让基座模型在 RL 开始前就能产出可解析的推理。从基座模型直接做纯 RL 会产出语言混杂的乱汤;格式必须先存在,才能被奖励。
  2. 面向推理的 RL — 在数学/代码提示上跑 GRPO,奖励 = 准确性 + 格式。"顿悟"行为(自我验证、反思、难题上更长的 CoT)就在这个阶段涌现。
  3. 拒绝采样 + SFT — 大量采样,保留验证器接受的那部分,在赢家上微调;然后再混入非推理数据(写作、问答),防止通用能力烂掉。
  4. 全场景 RL — 第二轮 GRPO,在可验证奖励之上叠加有用性/无害性奖励。

Kimi k1.5(2025)贡献了长度控制奖励。推理 RL 有一个已知病理:模型发现思考得更久会提高"蒙对"的几率,于是回答长度膨胀,推理成本爆炸。k1.5 重新塑形了奖励:

r_{\text{len}} = \begin{cases} \lambda \cdot \dfrac{L_{\max} - L_i}{L_{\max} - L_{\min}} & \text{if correct} \\[6pt] -\lambda \cdot \max\!\Big(0, \dfrac{L_i - L_{\text{target}}}{L_{\max} - L_{\text{target}}}\Big) & \text{if wrong} \end{cases}

答对了,越短奖励越高;答错了,越长罚得越狠。同一套 RLVR 机器,更聪明的奖励形状。

构建它

本课交付一个完整的 NumPy GRPO 训练器:表格式 token 级策略、真实的验证器、组采样、带裁剪的多 epoch 更新、k_3 KL 惩罚,以及 k1.5 长度项。

玩具领域。 提示给出三个数字(2、3、5 的一个排列)和一个目标值。策略输出一个 5 token 的表达式——两个 +/* 运算符与三个数字交错,顺序自由。验证器按真实的运算符优先级求值并支付奖励:命中目标的准确性奖励、表达式合语法的格式奖励,以及 k1.5 长度项(这里长度固定,所以它表现为一个小常数——但这个函数被完整实现并测试,因为之后接入变长解码时不能改动训练器)。

有些目标值在 24 个可能表达式中只有少数几个能命中(17 = 2+3*5),所以组 baseline 有真正的活要干。

运行:

consolepython3 main.py

你会看到每次迭代的统计(组奖励均值、格式违例、相对参考策略的 KL),以及最终评估——贪心策略能解出留出的提示。完整数学推导在 docs/en.md;训练器约 200 行,文档里的每个 GRPO 常数都对应代码里的一个具名常数。

验证它

code/tests/test_main.py 锁住了让 GRPO 成为 GRPO 的那些部件:

  • 验证器尊重运算符优先级并拒绝不合语法的表达式;
  • 组优势零均值,且全部奖励持平时严格为零;
  • 对极端重要性比率,裁剪比率保持在 [1-\epsilon, 1+\epsilon] 的逻辑之内;
  • k_3 估计量非负,且对相同策略为零;
  • 长度奖励对"短而对"支付多于"长而对",对"长而错"罚得最狠;
  • 固定种子的端到端训练把贪心成功率从冷启动拉升。

工程师为什么需要它

  • 只要存在检查器,就选 RLVR。 数学答案、代码、结构化输出(JSON schema 校验!)、对照标注语料库的检索忠实性。把学习出来的奖励模型留给开放式质量,并做好永远 red-team 它的准备。
  • 验证器是你攻击面的一部分。 在扩规模之前设计它:要求推理格式,在需要之前不要放部分得分进来,并审计"猜比推理更划算"的均衡。
  • 组大小就是你的价值函数分辨率。 G=8 常见;难的提示分布需要更大的组或课程过滤(丢掉全零优势的提示——GRPO 隐式就在做这件事)。
  • KL 锚是一个成本旋钮。 它是"RL 改进了模型"和"RL 产出了一个奖励最大化的退化方言"之间的区别。
  • 这套循环正是 R1 级 pipeline 里跑的东西;LLM 更大,rollout 来自 vLLM,但目标函数就是你刚手工训练的这个。

提示

  • 如果组内奖励全部相同,优势按定义为零——检查你的训练混合里是否有贴近模型能力边界的提示。
  • 奖励均值为负且格式违例为零,通常说明策略找到了一个永远打不中目标的有效表达式族:调大 \beta 或换一个提示混合的种子。
  • 长度塌缩的表现是成功率保持而每条回答的 token 数膨胀;k1.5 项正是为此而生。

关键术语

术语人们说实际含义
RLVR"带奖励的 RL"奖励由程序(验证器)计算,而不是由学习出来的奖励模型预测。
GRPO"无 critic 的 PPO"每个提示采一组 G 个样本;组统计量取代价值网络当 baseline。
Verifier"检查器"把(提示,回答)映射到奖励的代码:准确性、格式、测试、schema。
k_3 估计量"KL 惩罚"非负、无偏的 KL 样本估计:\pi_{\text{ref}}/\pi_\theta - \log(\pi_{\text{ref}}/\pi_\theta) - 1。
冷启动 SFT"阶段 0"一轮小规模 SFT,让推理在 RL 能奖励它之前就以可解析的格式存在。
长度控制"k1.5 技巧"奖励塑形:支付短而对的、惩罚长而错的,止住 CoT 膨胀。

“Stay hungry, stay foolish.”

— Steve Jobs

esc
↑↓切换↵打开esc关闭