[LLM 5/10] GRPO:删掉价值网络,让一组回答互为 baseline
上一章我们以 DPO 的空白收尾:它只能给文件里别人准备好的回答排序。 而第 3 章我们付过 PPO 的全价:4 个模型挤在显存里,还要额外训练一整个价值网络。 这一章把两边的优点合起来——让模型采样自己的回答来学习,是货真价实的 RL, 却把价值网络整个删掉。靠的是一个简单到让人恼火"为什么没人早点想到"的统计学观察: 对同一道题采样多个回答,这组回答的平均 reward,本来就是价值网络想要估计的那个 baseline。 而且只要题目能用代码批改,我们连人类偏好数据都不需要——零对,零成本。
Open in Colab05_grpo.ipynb
1. 问题(Problem statement)
设一道这样的题:教 Qwen3-0.6B 解泰语数学题。
最终答案是一个数字,一行 == 就能判对错。
把前三章的工具拿出来一件件比对,会发现没有一件正好合身:
| 方法 | 模型能采样自己的回答来学习吗 | 需要的人类 label | 显存中的模型 |
|---|---|---|---|
| SFT(第 2 章) | 不能——只会模仿标准答案 | 人写的标准答案 | 1 |
| PPO(第 3 章) | 能 | 训练 reward model 用的偏好对 | 4 |
| DPO(第 4 章) | 不能——纯 offline | 偏好对 | 2(LoRA 减到 1) |
- SFT 教模型模仿标准答案的解法,但从不让模型自己试错。 模型从未见过"自己的哪种思路"能通向正确答案
- PPO 让模型自己试,但代价是用偏好对训练一个 reward model, 外加一整个价值网络——尽管这个任务的 reward 直接就能写成一个 Python 函数
- DPO 漂亮地砍掉了 RL,但它只能给数据集中已经存在的回答排序。 数学题需要的是让模型多试几条路,然后强化通向正确答案的那条
所以本章的问题又窄又锋利:当 reward 可以用代码批改时, PPO 里哪些零件是真必要的,哪些可以删掉?
2. 我们要做什么(Solution)
回到第 3 章价值网络的职责:它只回答一个问题—— "平均而言,这个 prompt 应该拿到多少 reward"——用作 baseline, 从真实 reward 里减掉:比平均"好"的回答拿正梯度,比平均"差"的拿负梯度。 没有这个 baseline,policy gradient 的噪声会大到几乎没法训练。
PPO 的回答方式是再训练一整个模型来预测这个平均值。 GRPO 的回答方式是直接采样出来看:
对同一个 prompt 采样 个回答,取组内 reward 的平均—— 这个平均值按定义就是"该 prompt 期望 reward"的 unbiased estimate。 它和价值网络想估计的是同一个东西,却不用训练、不用加载、永远不会估歪。 于是整个价值网络可以删掉。 而当 reward 用代码批改(数字答案对或错)时, reward model 和人类偏好数据也跟着消失——剩下零 label。
这就是 GRPO(Group Relative Policy Optimization),由 Shao 等人(2024)在 DeepSeekMath 中提出, 也是训练 DeepSeek-R1 的那台发动机。这一路线有个统称: RLVR(RL with Verifiable Rewards)——reward 来自验证器、而非人类口味的 RL。
另外从本章开头就把预期摆正:现有证据表明,这类 RL 大部分时候做的是 把基座模型在 pass@8 已经具备的能力"打磨"到 pass@1 上显现出来, 而不是从零创造新能力。第 9 节我们会带着测量工具回到这件事。
3. 公式(Equation)
3.1 组相对 advantage(优势)——全部心脏就这一行
- = 从同一个 prompt 采样的回答数量(本章为 8)
- = 第 个回答的 reward
- 第 个回答的每一个 token 都用同一个 ,整句共享—— 这与 PPO 不同:PPO 靠价值网络和 GAE 追求逐 token 的精细 advantage
用人话读一遍:"对同一道题,这个回答比我自己其他几次尝试更好还是更差。" 不跨题比较,不预测未来,只有组内的自我竞争。
这个短公式有一个极其重要的推论:如果全组拿到相同的 reward (全对或全错),所有 都是零,那个 batch 什么也教不了。 把这句话记牢——它会同时成为本章的头号陷阱和最重要的指标。
3.2 完整版 GRPO objective
其中 是 token 相对采样时 policy 的概率比。
逐块读,因为每一块都在本系列里出现过:
- = 第 3 章的 PPO clip 原封不动,没有任何新东西—— 防止从采样 rollout 的位置迈出太远
- = 按 token 取平均,防止长回答影响力超标(想想第 4 章的 length bias)
- = 还是那根牵引绳,拴在与第 3、4 章同一个 上
真正该读的是不在公式里的东西:没有 ,没有 GAE,没有 critic loss。 整行只用两个模型( 和 )加上验证器给出的 reward 数字。
3.3 KL 项不是直接算的——认识 k3 estimator
真正的 KL divergence 要对每个位置的整个 vocabulary 求和,既昂贵又没必要。 GRPO 从已采样出的 token 上估计它,用的 estimator 外号叫 k3:
学生永远会问(也应该问)的问题:为什么不直接用 , 它的期望值不就是 KL 吗?
回答:那个朴素版(叫 k1)确实 unbiased,但单个 sample 可以是负数—— 约 40% 的 sample 给出负值,尽管 KL 按定义不可能为负——而且 variance 非常高。 在真实 batch 的规模下,估计值会晃到 penalty 一会儿推一会儿拉。
k3 同时修好两个毛病。令 ,然后注意两个事实:
- 不等式 恒成立,所以 k3 ,每个 sample 都非负
- ,所以 这一项期望为零—— 它是一个 control variate,抵消 的噪声而不动期望值
结果是一个同样 unbiased、variance 却低一个档次、且永不为负的 estimator。 图 5.3 会让你亲眼看到这个差别。
3.4 进阶注记:除以 std 并不像看上去那么纯洁(Dr.GRPO)
公式 3.1 里除以 悄悄带进了一种 bias: reward 几乎全相同的组(std 小,比如 8 个里对 7 个)的 advantage 会被巨大的系数放大, 而真正意见分裂的组(std 大——恰恰信息量最大)反而被相对压低。 净效果是梯度偏向那些模型几乎已经自我一致的题目。 Dr.GRPO(Liu 等人,2025)提出删掉除以 std,只保留减 mean—— 后者依然是完全正确的 baseline。第 4 节的小工具里有开关,两种都可以试。
3.5 unbiased 的 pass@k——第 9 节要用的工具
对每道题采样 次、答对 次,想知道"如果给 次配额,至少对一次的概率":
后面那个分式是从 个里抽 个、抽到的全是错误回答的概率。 人们常用错的公式是 ,它在 小时系统性地往自己脸上贴金 (这正是 Chen 等人 2021 年 HumanEval 论文要单开一个 appendix 讲它的原因)。 记住这个公式——它就是裁决 GRPO 到底在"创造"新能力还是在"打磨"旧能力的量尺。
4. 把公式画出来(Visualize)
一个组教了什么——以及什么样的组什么都教不了
Figure 5.1左:一组真实的 8 个回答在本章 reward shaping(答对 +1.0、格式 +0.3、泰语 +0.2)下的 advantage——零线正好是组的 mean。右:reward 全相同的组,所有 advantage 为零,梯度为零
左面板是公式 3.1 的实际工作现场:两个样样做全的回答(r = 1.5)拿到强劲的正向推力, 只拿到格式分的回答(r = 0.3)被往下压——尽管它的 reward 是正的—— 因为标准不是"好不好",而是"比组里的同伴好不好"。 右面板是 GRPO 全章的静默死亡模式:reward 全相同 = std 为零 = 没有学习发生。
自己输入几个 reward,实时看 advantage 怎么变——别忘了把 Divide by std 的勾去掉, 亲眼看看 3.4 节 Dr.GRPO 的区别:
Rewards r_i (G = 8)
Watch the std term.Dividing by std(r) = 0.500 rescales this whole group. A group that happened to be near-unanimous gets a large multiplier and dominates the update, even though it carries less information than a group that genuinely disagreed. Untick the box to see the same rewards without the rescaling.
GRPO 从 PPO 身上删掉了什么
Figure 5.2显存中的模型数量,按 Qwen3-0.6B 的 fp16 权重(每份 1.11 GB)计算:PPO 要加载 4 份,GRPO 只剩 2 份——价值网络被组内平均取代,reward model 被一个 Python 函数取代
注意消失的那两块,正是需要训练(价值网络)或需要提前训练(reward model)的两块。 剩下的是 policy 和 reference——而第 4 章已经教过我们,LoRA 能让这两个共用一份基座权重。 所以本章 notebook 里 GRPO 的净模型开销,和普通的 SFT 一样。
k3 与 k1:同样 unbiased,好不好用天差地别
Figure 5.3一个已知答案的合成例子:π_θ = N(0,1)、π_ref = N(0.5,1),真实 KL 恰好 = 0.125——k1 分布很宽且约 40% 的 sample 为负,而 k3 完全不为负,std 低了近三倍(0.18 对 0.50)
右面板才是实践中的要点:两条线收敛到同一个答案(都 unbiased), 但在真实 batch 的 sample 量级(几十到几百)上,k1 那条还在剧烈摇摆, 而 k3 从最初几个 step 起就稳到可以当一个可信的 penalty 来用。
5. 准备环境(Environment)
打开 Colab,选择 Runtime → Change runtime type → T4 GPU(免费额度够用)。
Colab 的 T4 是 Turing 架构(SM 7.5),它不支持 bfloat16,也不支持 FlashAttention-2。
但 Qwen3-0.6B 的 config.json 里写着 torch_dtype: bfloat16。
所以 torch_dtype="auto" 是个陷阱:代码会崩掉或者慢得离谱,而且不会告诉你原因。
torch_dtype=torch.float16 # 不是 bfloat16
attn_implementation="sdpa" # 不是 flash_attention_2
fp16=True # 在 GRPOConfig 里(不是 bf16=True)
cap = torch.cuda.get_device_capability(0)
print("compute capability:", cap) # T4 = (7, 5)
print("native bf16:", cap[0] >= 8) # T4 -> False
print("torch says :", torch.cuda.is_bf16_supported()) # T4 -> True(把 emulation 也算上了!)
is_bf16_supported() 在 T4 上会骗你较新的 torch 在 T4 上返回 True,因为它把**模拟(emulation)**也算作支持——而模拟比 fp16 慢得多。
请改为判断 compute capability ≥ 8.0(Ampere 及以上)。这是真正在 Colab 上跑才发现的 bug。
一份钱两个模型——第 4 章的老配方
policy 是第 2 章的 LoRA adapter, 是同一个 base 关掉 adapter。
TRL 自己认识这套机制:只要 model 是 PeftModel,它就不会另外加载 reference。
from peft import PeftModel
base = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen3-0.6B",
torch_dtype=torch.float16,
attn_implementation="sdpa",
).cuda()
policy = PeftModel.from_pretrained(base, "kobkrit/qwen3-0.6b-th-sft-lora", is_trainable=True)
GRPO 是 online RL:每次更新权重之前,都要先从模型现场采样回答。 本章的完整 config 最多要 generate 128 道题 × 8 个回答 × 256 token = 262,144 token。 对比第 4 章只是对文件里现成的文本做 forward——完全是两个世界。
所以 T4 上的时间都耗在 generate,不是 backprop。这也是 notebook 默认
FAST_MODE = True 的原因(64 道题 × 4 个回答 × 192 token ≈ 49k token,约 10 分钟);
报告结果用的完整 config(约 18 分钟)切一个 flag 就能打开——
我们把这件事直说,因为不告诉你"漂亮数字来自哪套 config"的文章,正在对你说半句谎话。
6. 准备数据(Data)
我们使用 VISAI-AI/gsm8k-thai——GSM8K 数学应用题的泰语翻译版。
从 train split 抽 128 道,另切一份 held-out 专用于评测,训练期间不碰。
from datasets import load_dataset
ds = load_dataset("VISAI-AI/gsm8k-thai", split="train")
ds = ds.shuffle(seed=42).select(range(128))
SYSTEM = "จงคิดทีละขั้นใน <think>...</think> แล้วจบด้วยคำตอบเป็นตัวเลขบรรทัดสุดท้าย"
# (泰语指令:"在 <think>...</think> 中逐步思考,最后一行以数字作答")
def to_prompt(ex):
return {
"prompt": [{"role": "system", "content": SYSTEM},
{"role": "user", "content": ex["translated_question"]}],
"answer": extract_final_int(ex["translated_answer"]), # GSM8K 的答案在 "####" 之后
}
train_ds = ds.map(to_prompt, remove_columns=ds.column_names)
注意没有 chosen/rejected 列,也没有任何人类 label——只有题目和数字答案。 顶替 label 的是三个纯代码批改的 reward 函数:
import re
THAI_DIGITS = str.maketrans("๐๑๒๓๔๕๖๗๘๙", "0123456789")
def extract_final_int(text):
text = text.translate(THAI_DIGITS) # 以防模型用泰文数字作答,如 ๔๒(= 42)
tail = text.split("</think>")[-1] # 只检查思考过程之后的部分
nums = re.findall(r"-?\d[\d,]*", tail)
return int(nums[-1].replace(",", "")) if nums else None
def reward_correct(completions, answer, **kwargs): # +1.0 最终答案正确
return [1.0 if extract_final_int(c) == a else 0.0
for c, a in zip(completions, answer)]
def reward_format(completions, **kwargs): # +0.3 有非空的 <think>
pat = re.compile(r"<think>.+?</think>", re.DOTALL)
return [0.3 if pat.search(c) else 0.0 for c in completions]
def reward_thai(completions, **kwargs): # +0.2 真的用泰语思考
def th_ratio(s):
letters = [ch for ch in s if ch.isalpha()]
return sum("ก" <= ch <= "๛" for ch in letters) / max(len(letters), 1)
return [0.2 if th_ratio(c) > 0.5 else 0.0 for c in completions]
一个回答的总 reward 是三者之和:最高 1.5,最低 0.0。
回头看图 5.1 的右面板:学习的源头是组内的差异。 训练初期,0.6B 模型答对数学题的次数极少——如果 reward 只有对/错, 大多数组都会是 [0,0,0,0,0,0,0,0],即 std 为零、梯度为零,白训一场。 格式和泰语这两个子 reward 让组里在模型开始答对之前就有差异可学。 这就是最字面意义上的 reward shaping。同时用加大加粗的字注明:它也打开了作弊的口子 ——第 9 节陷阱 1 会告诉你模型从哪一条里找到了漏洞(真找到了,notebook 里有证据)。
7. 核心代码(Main code)
7.1 GRPOTrainer——这次 trainer 是一等公民
第 3 章我们只能眯着眼用 TRL 那个还在半实验状态、API 几乎每个 minor version 都变的 PPOTrainer。
GRPOTrainer 完全是另一回事:它是 R1 热潮之后 TRL 当头牌维护的 trainer,
reward 直接收普通的 Python 函数,什么模型都不用包。
from trl import GRPOConfig, GRPOTrainer
FAST_MODE = True # 默认值:在免费 T4 上约 10 分钟跑完
# False = 报告结果所用的完整 config(约 18 分钟)
cfg = GRPOConfig(
output_dir="grpo-out",
num_generations=4 if FAST_MODE else 8, # G —— 组大小
max_completion_length=192 if FAST_MODE else 256,
max_prompt_length=256,
temperature=1.0, # 不许调低 —— 组内多样性就是燃料
beta=0.04, # KL 权重(用 3.3 节的 k3 计算)
epsilon=0.2, # 与 PPO 相同的 clip 区间
learning_rate=1e-6, # 比 DPO 还低 —— 见下方警告
per_device_train_batch_size=16, # 必须被 num_generations 整除
gradient_accumulation_steps=2 if FAST_MODE else 4,
num_train_epochs=1,
fp16=True, # T4 没有 bf16
logging_steps=1,
)
trainer = GRPOTrainer(
model=policy, # PeftModel → ref 免费
reward_funcs=[reward_correct, reward_format, reward_thai],
args=cfg,
train_dataset=train_ds,
)
trainer.train()
把预算的账直接算给你看:完整 config 是 128 道题 × 8 个回答 = 1,024 条 completion, 除以每 step 64 条 completion 的 effective batch = 16 个 optimizer step——真的就这么多。 其余几乎全部时间,都花在每个 step 之前 generate 那(最多)约 262k 个 token 上。
per_device_train_batch_size 数的是 completion,不是 prompt,并且必须被
num_generations 整除——因为同组成员必须落在同一个 batch 里,才能算出组的
mean/std。设得除不尽,TRL 会在创建 trainer 时就报错——这是好事,
响亮地坏,好过安静地坏。
全系列的排序是 SFT 2e-4 → DPO 5e-6 → GRPO 1e-6。
原因:GRPO 的训练数据是当前这一版模型自己采样出的回答。
权重一旦动猛了、语言开始跑偏,下一批回答就会跟着跑偏,然后 reward 全盘崩掉——
online RL 的错误会复利,不像 supervised,训练数据待在原地哪也不去。
7.2 亲手算一遍 advantage——所有的数都在这里
为了不让 GRPOTrainer 变成黑盒,notebook 里有一个 cell 把公式 3.1 的算术直接演给你看:
import torch
def group_advantages(rewards, G, eps=1e-4):
"""rewards: [B],按同一 prompt 每 G 个一组排列"""
r = rewards.view(-1, G) # [B/G, G]
mean = r.mean(dim=1, keepdim=True)
std = r.std(dim=1, keepdim=True)
return ((r - mean) / (std + eps)).view(-1) # Dr.GRPO:删掉 "/ (std + eps)"
r = torch.tensor([1.5, 0.3, 0.5, 1.5, 0.3, 0.0, 0.3, 0.5]) # 图 5.1 的那一组
print(group_advantages(r, G=8))
# → [+1.56, -0.55, -0.20, +1.56, -0.55, -1.08, -0.55, -0.20]
这八行就是 GRPO 在原有 PPO clip 之上添加的全部内容。 对比第 3 章那个要全程陪练的价值网络 + GAE——这是全系列最划算的一笔交换。
8. 结果(Results)
notebook 会测四样东西并写入 results.json:
- 每 step 的 mean reward——应当爬升(这是 optimizer 看到的东西)
- 每 step 中 std 不为零的组的占比——几乎没人画的那条线
- held-out 上的 pass@1 和 pass@8,用 3.5 节的 unbiased 公式,附 Wilson 95% CI
- 每 step 的平均 completion 长度——和 accuracy 成对着看
| 指标(完整 config) | 训练前 | 训练后 |
|---|---|---|
| pass@1,held-out(95% CI) | ? | ? |
| pass@8,held-out(unbiased) | ? | ? |
| 每组 mean reward | ? | ? |
| 平均回答长度(token) | ? | ? |
| std > 0 的组占比(首个 step → 最后一个 step) | ? | ? |
纹丝不动的 mean reward 有两种读法:"模型已经饱和"或者"学习早就停了"。 区分这两种情况的就是 std 不为零的组占比——它一旦触零, 此后的每个 batch 都是无声的 no-op:loss 照样打印、step 照样走、GPU 照样发热, 但每一步的梯度都精确为零(图 5.1 右面板乘以整个 batch)。 再训一个小时结果也一样。notebook 永远把这条线和 mean reward 画在一起, 而这也应该成为你在所有 RLVR 项目里的习惯。
答应过的 mini-R1 时刻
DeepSeek-R1 的论文里有一张著名的图:回答长度随着准确率一起自己长了出来, 没有人命令它想得更长——模型自己发现,把思考步骤写详细能带来 reward。 我们的 notebook 在微缩尺度上画同一对曲线(每 step 的 completion 长度与 accuracy)。 如果看到两条线哪怕轻微地一起上移,那就是 R1 的同一套机制在你自己的试管里发生了。 而如果长度在涨、accuracy 不动——永远先怀疑 reward hacking(奖励欺骗,第 9 节陷阱 1)。
Promptอธิบายว่าทำไมท้องฟ้าถึงเป็นสีฟ้า แบบสั้น ๆbase
sft
Showing the built-in sample.
9. 对比(Comparison)
四章、四种方法,在同一个任务上测量(同一套 held-out 泰语数学题)—— 这张表是整个系列兑现回报的地方,因为最右一列在之前任何一章都没有出现过:
| 方法 | pass@1(95% CI) | 回答长度 | 训练耗时 | 显存中的模型 | 人类 label 成本 |
|---|---|---|---|---|---|
| SFT(第 2 章) | ? | ? | ? | 1 | 每条样本一个人写答案 |
| PPO(第 3 章) | ? | ? | ? | 4 | 训 reward model 的偏好对 |
| DPO(第 4 章) | ? | ? | 约 9 分钟 | 2(LoRA 减到 1) | 约 500 对偏好 |
| GRPO(本章) | ? | ? | 约 18 分钟 | 2(LoRA 减到 1) | 零 |
这张表要从右往左读:整个系列的路线是逐步降低对人类 label 的依赖—— 从每条样本的标准答案 → 偏好对 → 零,而底下的机器同时越来越简单。 让最后一列归零的唯一条件是任务必须能用代码批改——请把这一条刻在脑子里。
RL 是在创造新能力,还是在打磨旧能力
Figure 5.4一个机制完全指明的玩具模型(RL 把曾经答对过的题的 odds 放大 ×8,但对 p = 0 的题完全无能为力):pass@1 冲向原本 pass@8 的天花板——仅为机制示意插图,实测数字在 notebook 里
这张图背后的逻辑比看上去更硬:GRPO 从 advantage 学习,而 advantage 非零的前提是 组里至少有一个回答比同伴做得好——也就是说,基座模型无论怎么采样都从未答对过的题(p = 0), 永远不会向系统里送出任何学习信号。 RLVR 擅长的是把散落在 pass@8 里的能力搬到 pass@1 上集中呈现。 2025 年的研究(Yue 等人)甚至测到:在非常大的 下,基座模型可能赢过 RL 之后的模型。 这不代表 GRPO 没用——真实用户只拿到一个回答,pass@1 是真金白银—— 但它意味着:别盯着爬升的 reward 曲线就下结论说模型"变聪明了",它主要是"变稳了"。
需要提防的坑
1. Reward hacking:用空的 <think> 白刷 +0.3
notebook 里 format reward 的第一版用的 regex 是 <think>.*?</think>(关键:.*? 接受空字符串)。
结果模型在几个 step 之内就发现,打一个空的 <think></think> 再瞎猜个数字,
每次都能白拿 +0.3,比真思考便宜得多——mean reward 爬得很漂亮,accuracy 一动不动。
notebook 保存了当场抓获的真实样本,然后把 regex 修成 .+? 强制要求有内容。
教训:模型不会 optimize 你想要的东西,它 optimize 你写下的东西。
2. 全组 reward 相同 → 图 5.1 右面板的教训 组越小,全组 reward 相同的概率越高—— 时两枚硬币同面朝上太常见了。 小于 4 的组会把相当大比例的算力白白烧掉,而且从两个样本估出来的 mean 本身噪声也大。 是被广泛使用的平衡点(我们的 FAST 模式降到 4 换时间——并且直说了)。
3. temperature 太低 = 从源头掐死多样性
调低 temperature,8 个回答就几乎一模一样 → reward 全相同 → 退回陷阱 2。
千万别把 inference 的习惯(低 temperature 求稳)带到收集 rollout 的时候。
我们设 temperature=1.0,因为组内多样性是整个系统的学习燃料。
4. 瓶颈是 generation 不是 backprop——预算要砍对地方
跑得慢的时候,先别去降 batch size 或折腾 optimizer——先看第 5 节那笔 262k token 的账。
有效的选项按力度排:降 max_completion_length、降 num_generations、减题目数量。
(生产系统靠 vLLM 这类 inference engine 解决这个问题,TRL 能接,但超出免费 Colab 的范围。)
10. 小结(Summary)
- 组内平均是不用训练的 baseline——对同一个 prompt 采样 个回答, PPO 的整个价值网络就不再必要
- 代码可批改的 reward = 零人类 label——系列从第 2 章一路爬到这里的汇合点
- advantage 是组内的相对量:reward 为正的回答照样会被往下压,只要组里同伴做得更好
- 全组 reward 相同的组什么都教不了——永远把 std > 0 的组占比画出来, 它是"饱和"与"学习早就停了却没人知道"之间的分界线
- k3 让 KL penalty 在小 batch 上真正可用——和 log-ratio 一样 unbiased,却不为负且 variance 低
- 除以 std 藏着 bias——Dr.GRPO 砍掉它、只留减 mean,第 4 节的小工具里可以自己试
- reward shaping 必要但危险——子 reward 防住初期的全零组,也开了刷分的口子
- RLVR 大部分是在"打磨",不是"创造"——pass@1 爬向原本 pass@8 的天花板,两个都要测
GRPO 需要能用代码批改的 reward。 数学题可以批改,代码可以批改(跑测试)。 但"写一封礼貌自然的泰语邮件"没有批改函数——那类开放式任务 属于 preference data 和第 4 章 DPO 的领地。这两章因此是互补,不是替代。 选工具要看 reward 的形状,不是看算法的新旧。
不要把结果解读成模型"变聪明了"——我们自己的证据和真实规模的研究都指向: RLVR 主要是把 pass@8 处已有能力的概率重新整理,让它稳定地在 pass@1 显现。 真想要新知识,得回到第 1 章(CPT)。
还是那句话:128 道题、16 个 optimizer step 是机制演示,不是真正的训练。 DeepSeek-R1 用的是十万量级的题目和相差好几个 order of magnitude 的算力。 能迁移到真实规模的是理解:组就是 baseline,多样性就是燃料, 而 reward 验证器就是那个模型永远会替你找出漏洞的东西。
下一章: Context Distillation——那段每次调用模型都要付一遍钱的长长的 system prompt, 要怎样蒸馏进权重,让模型再也不用看到它、却依然照它行事。
参考文献(References)
- Shao et al. (2024). DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models — DeepSeekMath:GRPO 的起源
- DeepSeek-AI et al. (2025). DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning — R1:真实规模下基于可验证奖励的 RL
- Liu et al. (2025). Understanding R1-Zero-Like Training: A Critical Perspective — Dr.GRPO:第 3 节讨论的除以 std 所引入的偏置
- Ahmadian et al. (2024). Back to Basics: Revisiting REINFORCE Style Optimization for Learning from Human Feedback in LLMs — 朴素 REINFORCE 也许就够——与删除价值网络对照阅读
- Schulman et al. (2017). Proximal Policy Optimization Algorithms — PPO 原始论文:第 3 节的裁剪代理目标
- Chen et al. (2021). Evaluating Large Language Models Trained on Code — 第 9 节所用的 pass@k 无偏估计
本系列的文章、代码与 notebook 均以 CC BY-NC-SA 4.0 授权 —— 可自由使用与改编,须署名、限非商业用途,并以相同方式共享。文中引用的第三方模型与数据集仍适用各自的许可证。
