读完这篇,你能做什么
核对组均值、总体标准差和奖励全相同的边界;在 CPU 实验中更新正确候选的概率,并拦住利用格式或类型错误骗过评分的输入。
学习理由、准备要求与知识点
为什么学它
不用训练价值模型可以省下一部分成本,但仍要生成候选、检查奖励是否可靠,也要处理组内奖励完全相同、无法区分好坏的情况。
先读这些内容
知识点清单
- 同提示分组采样
- 组均值基线
- 总体与样本标准差
- 全同奖励零优势
- 结果监督与过程监督
- token与序列归约
- 可验证奖励边界
- 奖励漏洞和保留验证器
运行前先确认论文机制按文中版本说明;Java 17 完整算法,Python 3.14 与 PyTorch 2.9.1+cpu 的小型 CPU 实验。未执行 GPU、多机或大规模模型训练。
01先弄懂它是什么
同一个问题生成多份答案后,可以先在这一组里比较谁做得更好,GRPO 就使用这种相对信号。它针对同一提示采样 G 份回答;若奖励为 [1,0,0,0],第一份高于组内平均水平,若全是零,就没有哪份更好的组内证据。GRPO 不需要另训配套的价值函数,但仍要生成多个候选、计算奖励、保存所需概率,所以不能仅凭这一点说训练一定便宜或稳定。
具体计算时,r_i 是第 i 份回答的奖励,mu 是这个问题的组内平均奖励,sigma 是标准差。常见的结果监督优势为 A_i=(r_i-mu)/(sigma+epsilon),形状为 [G]。若一批问题排成 [B,G],应在每题自己的 G 个候选里归一化,不要把不同难度的题混成一组。奖励可以来自人工偏好模型,也可以来自答案检查或执行规则;可验证奖励只是其中一种来源。
RLVR强调通过可核验规则获得奖励,例如精确答案、程序测试或形式验证。验证器只能证明其检查的属性:几个测试通过不等于程序对所有输入正确,最终答案相同也不证明中间推导可信。研究中需要把可验证目标、格式约束和任务真实目标分开写清,防止模型利用解析器漏洞获得高分。
文中这几个词,先认一认
- 组相对优势
- 先在同一道题的候选答案里算平均奖励,再看每份答案比平均值高低多少。本篇还用这一组的标准差加上小常数 epsilon 作分母来缩放差值,不把不同题目的候选混在一起算。
- 可验证奖励
- 按明确的程序或证明规则算出的任务反馈,例如运行检查程序后给出的通过结果。
- 零方差组
- 一组候选得到完全相同的奖励,因此奖励本身无法告诉我们该更偏好哪一个。
02看看它怎么工作
DeepSeekMath 的结果监督先给整份回答一个组内相对奖励,再把这个值作为该回答各 token 的优势。随后还要使用概率比裁剪目标和参考策略的 KL 正则项。先在回答内部平均、再在组内平均,也是目标的一部分。
长短回答怎样加权、标准差是否修正、epsilon 放在哪里,都会影响数值。本篇明确采用总体标准差 correction=0,并在分母加 epsilon,这是可复现的教学约定,不是所有实现必须采用的唯一规则。
在本篇只给 0 或 1 的奖励规则下,一组候选全对或全错时,组内相对优势都是零,因为它们的奖励完全相同。但原因可能不同:全对也许是题太简单,全错也许是没探索到有效解,或验证器出了错。因此要报告两类组各占多少,不能悄悄丢掉它们。零优势只表示这组奖励没有提供策略更新信号;如果目标里还有 KL 等其他项,整体梯度仍可能不为零。
CPU实验用四个固定JSON候选,严格解析唯一整数answer字段得到奖励,随后做一次真实PyTorch梯度更新。它检验组归一化、梯度方向和解析边界,未声称候选来自语言模型采样,也未复现论文训练规模。Java算法使用Welford统计均值与方差,避免平方和减均值平方造成不必要的消减误差,再对每份奖励输出优势。
先跑通一个最小例子
在PyTorch 2.9.1+cpu运行experiment.py。四个固定JSON候选用于可重复机制检查,进行一次真实梯度更新;不下载权重,不训练推理语言模型。
展开收起代码 · experiment.py
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
import json
import torch
torch.manual_seed(17)
torch.set_num_threads(1)
torch.set_default_dtype(torch.float64)
def verify(text, target):
try:
obj = json.loads(text)
except (ValueError,TypeError):
return 0.
# 只接受唯一answer字段和真正整数;bool在Python里也是int的子类,需精确类型检查。
if type(obj) is not dict or set(obj) != {"answer"} or type(obj["answer"]) is not int:
return 0.
return float(obj["answer"] == target)
assert verify('{"answer":4}',4)==1.
assert verify('{"answer":true}',1)==0.
assert verify('{"answer":4,"hint":"extra"}',4)==0.
assert verify('4 or 5',4)==0.
texts=['{"answer":4}','{"answer":5}','4','{"answer":false}']
rewards=torch.tensor([verify(t,4) for t in texts])
def normalize(r):
return (r-r.mean())/(r.std(correction=0)+1e-8) # 固定总体标准差口径。
advantages=normalize(rewards).detach()
assert abs(advantages.sum().item())<1e-12
assert torch.equal(normalize(torch.ones(4)),torch.zeros(4))
logits=torch.nn.Parameter(torch.zeros(4))
old=torch.log_softmax(logits,dim=0).detach()
optimizer=torch.optim.SGD([logits],lr=.2)
ratio=(torch.log_softmax(logits,dim=0)-old).exp()
surrogate=torch.minimum(ratio*advantages,ratio.clamp(.8,1.2)*advantages)
loss=-surrogate.mean() # 固定候选组的一次调试更新,未做真实语言采样。
optimizer.zero_grad();loss.backward();optimizer.step()
assert torch.softmax(logits,dim=0)[0].item()>.25
assert torch.isfinite(logits).all()
print(f"rewards={rewards.tolist()} correct_candidate_probability={torch.softmax(logits,dim=0)[0].item():.6f}")
print("PASS 8 checks; group-relative CPU mechanism only")
核对运行结果奖励为[1,0,0,0],正确候选概率从0.25上升;输出PASS 8 checks。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart TD
N0["按同一提示收集候选组"]
N1["验证器分别输出奖励与原因"]
N2["检查组内方差及任务难度"]
N3["计算优势和裁剪策略目标"]
N4["用独立验证集审计奖励漏洞"]
N0 --> N1
N1 --> N2
N2 --> N3
N3 --> N4按顺序看,每一步做了什么
- 按同一提示收集候选组
- 验证器分别输出奖励与原因
- 检查组内方差及任务难度
- 计算优势和裁剪策略目标
- 用独立验证集审计奖励漏洞
03对照实现代码
DeepSeekMath arXiv:2402.03300v2;PyTorch 2.9.1 CPU示例 · 第4.1.1节式3至4;第4.1.2节结果监督优势
依据固定论文公式原创的组归一化教学函数;标准差与epsilon约定明确,不冒称作者未公开的完整训练实现。
def relative_advantage(rewards, eps=1e-8):
mean = rewards.mean(dim=-1, keepdim=True) # [B,G]沿同提示的G维求均值。
std = rewards.std(dim=-1, correction=0, keepdim=True) # 明确总体标准差约定。
return ((rewards-mean)/(std+eps)).detach() # 奖励优势作为固定训练目标。
论文先给出PPO基于价值函数的优势,再引入同提示候选组以替代额外价值估计。式3包含裁剪代理目标、每回答token平均和参考KL项;本篇短函数只实现组优势,缺少其余部分,不能称为完整GRPO训练器或声称与所有后续实现逐行等价。
论文结果监督段落用组均值与标准差归一化回答奖励,过程监督则涉及多个推理步骤奖励及其后续累计。两者学习信号的粒度不同,不能把每步局部得分随意平均后仍称原来的过程优势。实验选择结果监督,使每个候选只有一个可检查标量。
算标准差时,PyTorch 的默认修正与总体标准差不是同一口径;本片段明确设置 correction=0,组很小时尤其要看清这个差别。keepdim 让组均值或标准差保留 [B,1] 形状,才能与 [B,G] 的候选逐组对应。若把这个维度去掉,某些 B、G 组合会沿错误方向广播。应直接检查分组形状和提示 ID,确认同组确实是同一道题的候选。
DeepSeek-R1论文讨论规则奖励的应用与局限,但公开论文不是对本机训练结果的背书。这里的JSON验证器由本文原创,只验证受控算术答案,不实现论文中的数学评分或代码沙箱。扩展成代码执行验证时,需要独立隔离、资源限制和可信测试,不能让候选程序读取或修改评分规则。
代码先调用谁,再调用谁
- 旧策略对同提示生成G份输出
- 奖励器逐份评分
- 按组形成结果或过程优势
- 结合旧概率和参考KL形成目标
- 更新策略并重新采样
04动手写一个例子
稳定计算单组相对优势
输入至少两项奖励、绝对值不超过一百万及正epsilon,按总体标准差输出优势;这是有界奖励的CPU统计契约。
- 输入 1
奖励[0,1],epsilon很小- 输出 1
约[-1,1]
- 输入 2
奖励[1,1]- 输出 2
[0,0]
计算均值与方差时,直接把所有平方相加再减均值平方,遇到数值很近的奖励可能损失有效位。Welford逐项维护样本数、均值和离均差平方和,每次只利用新旧均值间的关系,通常更适合稳定统计。本文还限制奖励范围,避免任意巨大实数造成溢出。
第一遍得到组均值及M2,使用M2/G定义总体方差。第二遍计算每个中心化奖励除以标准差加epsilon。这样全相同奖励中心化后就是零,不会产生NaN。epsilon不是人为奖励差:它只稳定分母,不能给完全没有差异的组创造学习方向。
处理完前k个输入后,mean为这些奖励的均值,M2为相对该均值的平方偏差和;更新公式保持这两个关系。最终优势之和理论为零,浮点下按容差检查。加入epsilon后方差通常略小于一,不能错误地把严格单位方差写进不变量或测试。
为什么这样写是对的第一遍mean与M2描述已读前缀;第二遍每个输出使用同一完整组统计量,理论中心化和为零。
两次线性扫描,不排序、不构建成对比较矩阵。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
展开收起代码 · Main.java · 完整可运行程序
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
import java.util.*;
public class Main {
static void near(double expected,double actual) {
if (!Double.isFinite(actual) || Math.abs(expected-actual)>1e-9*Math.max(1,Math.abs(expected)))
throw new AssertionError(expected+" != "+actual);
}
static void check(boolean ok) { if(!ok) throw new AssertionError(); }
static void bad(Runnable r) {
try { r.run(); } catch(IllegalArgumentException e) { return; }
throw new AssertionError("invalid input accepted");
}
static double[] relative(double[] rewards,double epsilon) {
if(rewards==null||rewards.length<2||!Double.isFinite(epsilon)||epsilon<=0)
throw new IllegalArgumentException("group or epsilon");
double mean=0,m2=0; int n=0;
for(double x:rewards) {
if(!Double.isFinite(x)||Math.abs(x)>1e6) throw new IllegalArgumentException("reward range");
n++; double delta=x-mean; mean+=delta/n;
m2+=delta*(x-mean); // Welford更新,避免直接用平方和相减。
}
double sd=Math.sqrt(Math.max(0,m2/n)); // 本实验明确使用总体方差除以G。
double[] result=new double[n];
for(int i=0;i<n;i++) result[i]=(rewards[i]-mean)/(sd+epsilon);
// 相同奖励的组自然得到零优势,不编造组内偏好。
return result;
}
public static void main(String[] args) {
double eps=1e-8;
double[] a=relative(new double[]{0,1},eps);
near(-.5/(.5+eps),a[0]);near(.5/(.5+eps),a[1]);
check(Arrays.equals(new double[]{0,0},relative(new double[]{1,1},eps)));
check(Arrays.equals(new double[]{0,0},relative(new double[]{0,0},eps)));
double[] b=relative(new double[]{2,3},eps);near(a[0],b[0]);near(a[1],b[1]);
double[] c=relative(new double[]{1,0,0,0},eps);near(0,Arrays.stream(c).sum());check(c[0]>0&&c[1]<0);
bad(()->relative(new double[]{1},eps));
bad(()->relative(new double[]{0,Double.NaN},eps));
bad(()->relative(new double[]{0,1},0));
System.out.println("PASS 8 cases");
}
}
这些特殊情况也要试一试
- 全对与全错组都输出零优势
- 少于两候选拒绝
- 总体标准差与样本标准差必须区分
- epsilon加入后不要求严格单位方差
进阶选读 · 验证器的对抗审计与奖励归因
场景模型越来越擅长输出评分器偏好的格式,却没有提高真实解题率。
做法保留隐藏测试和独立解析器对照;逐类审计错误接受、错误拒绝、答案泄漏和重复候选,记录每个奖励分量与改版影响。
复杂度G份候选的统计O(G),验证器执行成本按测试数和程序预算独立计量。
好处与代价严格格式降低歧义却可能错误拒绝等价答案;宽松解析提高覆盖也扩大漏洞面。
05项目里怎么用
每次训练日志同时记录通过率、全零组比例、全一组比例、输出长度和验证器异常率。平均奖励不变可能来自不同的错误分布,标准化优势看起来正常也不证明标签可靠。将解析失败与答案错误分开计数,有助于判断应该改模型、数据还是验证契约。
每题多生成几个候选,比较机会更多,生成和验证费用也会增加。在总 token 预算相同的条件下,一道题用得更多,就可能少覆盖一些题。因此比较 GRPO 变体时,应固定提示集合、生成预算、求和或平均方式,以及验证器版本,并列明无法固定的条件;否则成功率提升可能只是因为多花了采样成本。
可执行奖励环境必须保存不可被候选修改的测试与评判逻辑,并限制CPU、内存、时间和文件权限。人工抽查仍用于发现规则没有覆盖的任务含义。本文没有运行候选代码、GPU训练或多机采样;JSON检查和小张量更新只为机制提供局部证据。
容易踩的坑
- 把不同提示混成一组归一化
- 忘记std默认修正影响小组
- 把零优势说成所有梯度为零
- 只检查答案子串导致伪通过
- 比较方法时不对齐采样预算
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 结果奖励 | 验收清楚、标注成本较低 | 长链路信用分配稀疏 | 答案或执行结果可核验 |
| 过程奖励 | 提供更细粒度反馈 | 过程标签和评分器难维护 | 步骤语义可验证的任务 |
| 更大候选组 | 增加相对比较机会 | 生成与验证成本上升 | 奖励有差异且预算允许 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01GRPO一定使用规则奖励吗?
展开参考答案与推导
不一定,组内奖励可来自奖励模型或规则;RLVR描述奖励来源。
02全对与全错为何都零优势?
展开参考答案与推导
组内没有相对差异,但任务难度和业务含义不同,需要分别监控。
03epsilon能解决全错探索吗?
展开参考答案与推导
不能,它只稳定除法,无法创造正确候选或奖励信息。
换个情况,还会处理吗
01通过率升高但答案错误增多怎么办?
展开参考答案与推导
审计验证器假阳性、解析捷径和测试泄漏,用独立标准复评。
02G从2改8后效果提高如何报告?
展开参考答案与推导
同时报告生成token、验证成本、题目覆盖及全同奖励比例。
03全部奖励相同但参数仍变化是不是bug?
展开参考答案与推导
检查KL、熵或其他正则项,它们仍可能产生梯度。
写代码试一试
01计算组相对优势。
- 输入 / 条件
[0,1]与小epsilon- 输出 / 预期
约[-1,1]
提示 总体方差除G
展开参考答案与推导
使用两遍统计或Welford后再中心化。
02拒绝布尔值冒充整数。
- 输入 / 条件
JSON {"answer":true},目标1- 输出 / 预期
奖励0
提示 Python bool属于int子类
展开参考答案与推导
精确检查type(value) is int。
03构造零方差测试。
- 输入 / 条件
[0,0,0,0]- 输出 / 预期
四个零且无NaN
提示 分母加epsilon
展开参考答案与推导
中心化后全零,再验证有限性。
读完之后,独立完成
课后作业:自己做一遍
本篇共 10 道作业,先完成 8 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
核对组均值、总体标准差和奖励全相同的边界;在 CPU 实验中更新正确候选的概率,并拦住利用格式或类型错误骗过评分的输入。
本篇重点加练
零方差组与变长归约决定有效学习信号,重点区分没有梯度和没有任务进步。
- 全同奖励零优势
- token与序列归约
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01代码推演
同一提示下奖励为 [0,0,1,2],另一组全为 1。分别采用总体和样本标准差约定写优势计算表,明确退化组政策。
- 提交内容
- 两组计算及契约
- 怎样算完成
- 标准差约定显式
- 退化组不被忽略
练到的知识点同提示分组采样 · 组均值基线 · 总体与样本标准差 · 全同奖励零优势
- 作业 02原理问答
比较最终答案判定与逐步过程判定的监督信息;为变长回答写清 token 归约和序列归约各对应的样本权重。
- 提交内容
- 监督与归约对照表
- 怎样算完成
- 奖励来源准确
- 不同长度权重可追踪
练到的知识点结果监督与过程监督 · token与序列归约 · 可验证奖励边界
- 作业 03原理问答
两组分别全成功与全失败,但长度分布不同。分析只由组相对任务优势产生的训练信号,并列出仍可能改变参数的其他目标项。
- 提交内容
- 组统计与目标项清单
- 怎样算完成
- 任务优势与完整损失分开
- 不能从零优势直接推断全部更新
练到的知识点全同奖励零优势 · token与序列归约
综合应用
- 作业 04编程实践
为整数算术题编写受约束验证器与批量分组记录器,测试缺失答案、格式伪装、额外文本和同组全失败输入。
- 提交内容
- CPU 验证器与攻击样例
- 怎样算完成
- 解析规则明确
- 失败类型不混成一类
练到的知识点同提示分组采样 · 全同奖励零优势 · 可验证奖励边界
- 作业 05排错实验
设计至少五个验证器漏洞样例,并保留一组不参与开发的攻击样例;记录修补前后的误接受与误拒绝。
- 提交内容
- 漏洞集与保留审计报告
- 怎样算完成
- 保留集真实隔离
- 修补不只优化已知样例
练到的知识点可验证奖励边界 · 奖励漏洞和保留验证器
- 作业 06面试场景
组大小由 4 增到 8 后成功率提高。制定等总候选预算的对照,分别考察探索覆盖与优化更新次数。
- 提交内容
- 公平比较方案
- 怎样算完成
- 采样及训练成本同时记录
- 归因范围明确
练到的知识点同提示分组采样 · 组均值基线 · 全同奖励零优势
- 作业 07编程实践
CPU 实现变长候选的组相对损失核对器,测试全同奖励及一个非退化组,比较 token 等权与序列等权两种明确目标。
- 提交内容
- 实现与逐候选贡献表
- 怎样算完成
- 组内统计和全批归约分开
- 空序列有处理契约
练到的知识点全同奖励零优势 · token与序列归约
- 作业 08设计评审
一次采样因预算只保留短回答,之后仍将结果当原始候选组训练。审阅筛选如何改变组构成、目标权重与可报告的成功率。
- 提交内容
- 选择流程及偏差审查
- 怎样算完成
- 被丢候选有记录
- 条件分布变化不被隐藏
练到的知识点token与序列归约
进阶挑战 · 掌握前置后选做
- 作业 09设计评审
在 CPU 可枚举的候选空间中比较稀疏结果奖励和人工构造的过程奖励,检查奖励提升是否对应真实任务成功。
- 提交内容
- 合成实验与奖励审计
- 怎样算完成
- 真实性判定独立
- 过程奖励偏差被纳入检查
练到的知识点结果监督与过程监督 · token与序列归约 · 奖励漏洞和保留验证器
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 10思维拓展 · 条件变化
若把候选组中每个回答复制两次,哪些组统计与损失应保持不变,哪些取决于归约约定?给出一般条件并用小输入核验。
- 提交内容
- 复制变换论证与检查程序
- 怎样算完成
- 复制不是独立新样本
- 每种归约的条件显式
练到的知识点全同奖励零优势 · token与序列归约
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读参考文档英文
DeepSeekMath v2 (新标签页)↗
阅读4.1节式3、式4及结果/过程监督的区别。
- 深入参考文档英文
DeepSeek-R1 v1 (新标签页)↗
阅读规则奖励设置及失败尝试,避免把论文结果当通用保证。
- 实践参考文档英文
PyTorch 2.9 std (新标签页)↗
核对correction、dim、keepdim对组归一化的影响。
- 实践参考文档英文
DeepSeekMath作者仓库 (新标签页)↗
核对公开范围与论文、模型说明;不将未公开训练管线虚构成已运行。