18.2 GRPO 训练推理模型(RLVR 管线)
上一节介绍了 RLVR(Reinforcement Learning with Verifiable Rewards,可验证奖励强化学习)的整体范式——用规则验证器代替人类偏好打分,让模型在数学、代码等可自动判定对错的任务上"自我进化"。本节将动手实现一条完整的 RLVR 训练管线:从采样推出(rollout)、可验证奖励计算、组优势准备,到策略更新的完整循环。GRPO 的数学定义(目标函数、分组优势估计器)已在 §16.3 详细推导,本节不重复公式推导,而是聚焦于将这些公式落地为可运行的训练代码。
18.2.1 RLVR 管线的四个阶段
一个 RLVR + GRPO 的训练步骤可以分解为四个阶段:
| 阶段 | 输入 | 输出 | 关键操作 |
|---|---|---|---|
| 1. Rollout | prompt | 模型 eval 模式,自回归采样 | |
| 2. Reward | 回复文本 + 标准答案 | 标量奖励 | 数学验证器比对答案 |
| 3. Advantage | 奖励向量 | 优势向量 | 组内归一化 |
| 4. Update | 优势 + log 概率 | 更新后的 | 策略梯度 + 梯度裁剪 |
表 18-1:RLVR + GRPO 单步训练的四阶段流水线。
18.2.2 阶段一:Rollout 采样
对每道数学题,模型在 eval 模式下用 temperature + top-p 采样
import torch
@torch.no_grad()
def sample_one_response(model, input_ids, max_new_tokens=512,
temperature=0.8, top_p=0.9):
"""自回归采样一条回复,返回 (full_ids, prompt_len)。"""
device = input_ids.device
generated = []
logits = model(input_ids.unsqueeze(0))[:, -1]
for _ in range(max_new_tokens):
if temperature != 1.0:
logits = logits / temperature
probs = torch.softmax(logits, dim=-1)
# Top-p 过滤
sorted_probs, sorted_idx = torch.sort(probs, descending=True)
cumsum = sorted_probs.cumsum(dim=-1)
mask = cumsum - sorted_probs > top_p
sorted_probs[mask] = 0.0
sorted_probs /= sorted_probs.sum(dim=-1, keepdim=True)
probs.zero_().scatter_(1, sorted_idx, sorted_probs)
next_token = torch.multinomial(probs, num_samples=1)
token_id = next_token.item()
generated.append(token_id)
if token_id == model.config.eos_token_id:
break
logits = model(next_token)[:, -1]
prompt_len = input_ids.numel()
gen_ids = torch.tensor(generated, device=device, dtype=input_ids.dtype)
return torch.cat([input_ids, gen_ids]), prompt_len实现要点:采样期间必须关闭 dropout(eval 模式),否则同一 prompt 的多条回复会因 dropout 掩码引入额外噪声。生产级实现会维护 KV Cache 加速自回归推理,此处为了清晰省略了缓存细节。当
18.2.3 阶段二:可验证奖励计算
RLVR 与 RLHF 的核心区别在于奖励来源。在数学任务中,验证器从模型输出中提取 \boxed{} 内的答案,然后与标准答案比对。

图 18-2:GRPO 在 RLVR 管线中的工作流程。策略模型对同一 prompt 生成 G 条回复,每条回复经奖励函数评分后,通过 Group Computation 计算组相对优势。
import re
# --- 答案提取:从模型输出中定位最后一个 \boxed{} 并处理嵌套 ---
def extract_boxed_answer(text):
"""从模型输出中提取 \\boxed{} 内的答案,支持嵌套括号。"""
idx = text.rfind("\\boxed{")
if idx == -1:
return None
depth, start = 0, idx + len("\\boxed{")
for i in range(start, len(text)):
if text[i] == "{":
depth += 1
elif text[i] == "}":
if depth == 0:
return text[start:i].strip()
depth -= 1
return None
# --- 等价判断:先比字符串,不匹配再尝试数值比较 ---
def grade_answer(predicted, ground_truth):
"""判断预测答案与标准答案是否等价(字符串 + 数值比较)。"""
if predicted is None:
return False
pred = predicted.strip().replace(" ", "")
gt = ground_truth.strip().replace(" ", "")
if pred == gt:
return True
try:
return abs(float(pred) - float(gt)) < 1e-6
except (ValueError, TypeError):
return False
# --- 组合:提取答案 → 判断等价 → 返回二值奖励 ---
def reward_rlvr(response_text, ground_truth):
"""RLVR 二值奖励:正确 1.0,错误 0.0。"""
extracted = extract_boxed_answer(response_text)
if extracted is None:
return 0.0
return 1.0 if grade_answer(extracted, ground_truth) else 0.0奖励设计考量:最简单的"对 = 1,错 = 0"二值奖励在实践中非常有效——组优势机制已在组内提供了足够的梯度信号。如果模型输出中没有 \boxed{},直接给 0 奖励,这种"缺省惩罚"会自然驱动模型学会输出格式化答案。DeepSeek-R1 还额外使用了格式奖励(检查推理标签是否齐全),但在纯 RLVR 管线中仅用正确性奖励已足够。
18.2.4 阶段三:组优势计算
拿到
# 教学示例:展示核心逻辑,省略了部分 import 和辅助函数定义
def compute_group_advantages(rewards, epsilon=1e-4):
"""计算 GRPO 组相对优势,返回与 rewards 同形状的张量。"""
rewards_t = torch.tensor(rewards, dtype=torch.float32)
return (rewards_t - rewards_t.mean()) / (rewards_t.std() + epsilon)
def is_degenerate_group(advantages):
"""检查优势是否全为零(退化组:全对或全错)。"""
return torch.allclose(advantages, torch.zeros_like(advantages),
atol=1e-8, rtol=0.0)退化组处理:当
18.2.5 阶段四:策略更新
根据优势和 log 概率计算策略梯度损失。首先需要计算仅生成部分的 log 概率之和:
# 教学示例:展示核心逻辑,省略了部分 import 和辅助函数定义
def sequence_logprob(model, token_ids, prompt_len):
"""计算生成部分的总 log 概率(可反向传播)。"""
logits = model(token_ids.unsqueeze(0)).squeeze(0).float()
logprobs = torch.log_softmax(logits, dim=-1)
targets = token_ids[1:]
selected = logprobs[:-1].gather(1, targets.unsqueeze(-1)).squeeze(-1)
return selected[prompt_len - 1:].sum() # 只对生成部分求和在无 KL 惩罚的简化版本中(DAPO、Dr.GRPO 等推荐移除 KL 项),损失化简为加权策略梯度:
优势为正的回复增大概率,为负的回复减小概率。注意优势用 .detach() 停止梯度——它只作为权重,梯度仅通过 log 概率流回模型参数。
18.2.6 完整训练循环
将四个阶段串联起来,加上优化器、梯度裁剪和日志,就构成了完整的训练循环:
# 教学示例:展示核心逻辑,省略了部分 import 和辅助函数定义
def compute_grpo_loss(model, tokenizer, example, device,
num_rollouts=8, max_new_tokens=512,
temperature=0.8, top_p=0.9):
"""一个样本的完整 GRPO 流水线:rollout → reward → advantage → loss。"""
prompt = (f"Solve the following math problem. "
f"Put your final answer in \\boxed{{}}.\n\n{example['problem']}")
input_ids = torch.tensor(tokenizer.encode(prompt), device=device)
# 阶段 1-2:采样 + 奖励
model.eval()
rollout_data, all_rewards, samples = [], [], []
for _ in range(num_rollouts):
full_ids, plen = sample_one_response(
model, input_ids, max_new_tokens, temperature, top_p)
gen_text = tokenizer.decode(full_ids[plen:].tolist())
reward = reward_rlvr(gen_text, example["answer"])
rollout_data.append((full_ids, plen))
all_rewards.append(reward)
samples.append({"text": gen_text, "reward": reward,
"gen_len": full_ids.numel() - plen})
model.train()
# 阶段 3:优势
advantages = compute_group_advantages(all_rewards)
if is_degenerate_group(advantages):
return {"loss": 0.0, "loss_tensor": None,
"rewards": all_rewards, "samples": samples}
# 阶段 4:损失
logps = torch.stack([
sequence_logprob(model, ids, plen)
for ids, plen in rollout_data
])
pg_loss = -(advantages.to(device).detach() * logps).mean()
return {"loss": pg_loss.item(), "loss_tensor": pg_loss,
"rewards": all_rewards, "samples": samples}
def train_rlvr_grpo(model, tokenizer, train_data, device,
steps=100, num_rollouts=8, max_new_tokens=512,
lr=1e-5, checkpoint_every=50):
"""RLVR + GRPO 完整训练循环。"""
optimizer = torch.optim.AdamW(model.parameters(), lr=lr)
model.train()
for step in range(1, steps + 1):
example = train_data[(step - 1) % len(train_data)]
stats = compute_grpo_loss(model, tokenizer, example, device,
num_rollouts, max_new_tokens)
if stats["loss_tensor"] is not None:
optimizer.zero_grad()
stats["loss_tensor"].backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()
reward_avg = sum(stats["rewards"]) / len(stats["rewards"])
print(f"[Step {step}/{steps}] loss={stats['loss']:.4f} "
f"reward_avg={reward_avg:.3f}")
if checkpoint_every and step % checkpoint_every == 0:
torch.save(model.state_dict(), f"rlvr_grpo_step{step:05d}.pt")关键设计决策:
- 单步更新(
):TRL 默认设置,每份数据仅用一轮梯度更新,裁剪目标自动退化为普通策略梯度。 - 梯度裁剪 1.0:防止长序列 log 概率累加导致梯度过大。
- 学习率
1e-5:比 SFT 的2e-5略低,保证训练稳定。 - 无 KL 惩罚:DAPO、Dr.GRPO、Open-Reasoner-Zero 等多项研究表明,RLVR 场景中移除 KL 惩罚反而有利于性能。TRL 框架默认
。
18.2.7 使用 TRL GRPOTrainer 快速启动
生产级训练通常使用 TRL 库的 GRPOTrainer,它封装了分布式训练、vLLM 加速推理、多种 loss 变体等工程细节:
from datasets import load_dataset
from trl import GRPOTrainer, GRPOConfig
import re
dataset = load_dataset("trl-lib/DeepMath-103K", split="train")
def accuracy_reward(completions, ground_truth, **kwargs):
"""可验证正确性奖励:提取 \\boxed{} 并与标准答案比对。"""
rewards = []
for comp, gt in zip(completions, ground_truth):
match = re.search(r"\\boxed\{(.*?)\}", comp)
extracted = match.group(1) if match else ""
rewards.append(1.0 if extracted.strip() == gt.strip() else 0.0)
return rewards
config = GRPOConfig(
output_dir="grpo-math-output",
per_device_train_batch_size=4,
num_generations=8, # 组大小 G
max_completion_length=512,
learning_rate=1e-5,
beta=0.0, # 无 KL 惩罚
loss_type="grpo", # 可选 "dapo" / "dr_grpo"
)
trainer = GRPOTrainer(
model="Qwen/Qwen2-0.5B-Instruct",
args=config,
reward_funcs=accuracy_reward,
train_dataset=dataset,
)
trainer.train()| 参数 | 含义 | 推荐值 |
|---|---|---|
num_generations | 组大小 | 8-16 |
beta | KL 惩罚系数 | 0.0(RLVR 场景) |
loss_type | 损失归一化方式 | "grpo" / "dapo" / "dr_grpo" |
scale_rewards | 是否除以标准差 | True(原始)/ False(Dr.GRPO) |
use_vllm | vLLM 加速 rollout | 生产训练建议开启 |
表 18-2:GRPOTrainer 关键配置项。
18.2.8 训练过程观察与调参
以 Qwen3-0.6B + MATH 数据集 + 8 rollouts 的实验为例,典型的训练曲线表现为:
| 阶段 | 典型表现 |
|---|---|
| 前 20 步 | reward 波动剧烈,模型还在探索,输出格式不稳定 |
| 20-50 步 | reward 稳步上升,模型学会使用 \boxed{} 格式 |
| 50-100 步 | reward 增速放缓,简单题基本掌握,难题仍失败 |
| 100+ 步 | 需监控"奖励坍塌"(reward 骤降),这是常见不稳定现象 |
表 18-3:RLVR + GRPO 训练各阶段的典型表现。
关键超参数影响:增大组大小 num_rollouts 较小时可增加梯度累积步数来提升稳定性。
18.2.9 小结
本节从工程实现角度走通了 RLVR + GRPO 训练管线的完整流程:
- 四阶段流水线:Rollout → Reward → Advantage → Update,每步都有清晰的输入输出接口。
- 数学验证器:通过正则提取
\boxed{}答案并比对,实现零成本可验证奖励。 - 退化组处理:检测并跳过全对/全错的无效组,避免浪费计算。
- 无 KL 简化:RLVR 场景中移除 KL 惩罚有利于性能,代码实现更简洁。
- GRPOTrainer:TRL 框架提供开箱即用的生产级实现,支持 vLLM 加速和多种 loss 变体。
下一节将介绍在 GRPO 基础上的策略优化进阶技术,包括 DAPO、Dr.GRPO 等工程变体如何进一步提升训练稳定性。