16.4 GRPO 进阶工程变体
上一节建立了 GRPO 的数学基础:通过组内相对奖励估计优势函数,省去价值网络,使得大模型强化学习在工程上变得可行。然而,当 GRPO 被推向更大规模(数百亿参数的 MoE 模型、数万 token 的长推理链),一系列工程层面的缺陷逐渐暴露——长度偏差(length bias)、熵坍缩(entropy collapse)、token 级重要性采样失效、离线数据利用率低等问题都会拖慢训练甚至导致不可逆的模型崩溃。
本节聚焦 GRPO 的工程改进变体:首先介绍三项直接作用于 GRPO 目标函数的调优技巧(裁剪策略、KL 控制、格式奖励),然后深入两个具有独立论文支撑的算法变体——Dr.GRPO(长度偏差修正)和 GSPO(序列级重要性采样),最后讨论 Replay Buffer 机制和异步训练的系统设计思路。
16.4.1 三项核心调优技巧
在进入独立算法变体之前,我们先看三项在实践中被广泛采纳的改进手段。它们的共同特点是实现简单、效果显著,且已被 TRL 等主流框架直接支持。
一、裁剪策略比率(Clip-Higher)
回顾 GRPO 的裁剪目标函数:
其中
问题的根源在于:当优势
DAPO 提出的解决方案非常直接——解耦上下界:
通过增大上界

图 16-7:Clip-Higher 策略的效果。左图:AIME'24 准确率对比;右图:生成概率的熵变化。未使用 Clip-Higher 时熵迅速降至零(探索能力丧失),使用后熵保持在 0.4-0.5 的健康区间。
二、KL 项控制
GRPO 原始公式中包含一个 KL 散度惩罚项,用 Schulman 近似器计算:
近期的实践表明,KL 项并非在所有场景下都是必需的:
- 数学推理任务:多项研究(DAPO、Dr.GRPO、Open-Reasoner-Zero 等)发现,移除 KL 惩罚(
)反而能让模型在长思维链推理中获得更大的探索自由度,取得更好的效果。TRL 框架已默认将 设为 0。 - 通用对齐任务:当奖励模型可能存在 reward hacking 风险时,保留适度的 KL 约束仍然重要。
- 分域 KL 强度:DeepSeek-V3.2 采用了更精细的策略——对数学领域将 KL 系数设为 0(最大化推理探索),而对其他领域保持非零 KL 约束,兼顾探索与稳定。
三、显式格式奖励
DeepSeek-R1 的训练经验表明,在总奖励中加入一个格式奖励能有效引导模型输出结构化的推理过程。一个典型的格式奖励函数如下:
import re
def format_reward(completion: str) -> float:
"""检查输出是否符合 <think>...</think><answer>...</answer> 格式"""
pattern = r"^<think>.*?</think>\s*<answer>.*?</answer>$"
if re.match(pattern, completion, re.DOTALL):
return 1.0 # 完全符合格式
# 部分格式奖励:每出现一个正确标签给 0.25 分
score = 0.0
for tag in ["<think>", "</think>", "<answer>", "</answer>"]:
if tag in completion:
score += 0.25
return score
# 总奖励 = 正确性奖励 + 格式奖励
# R_total = R_correctness + R_format格式奖励的关键在于不影响正确性信号的主导地位,仅作为辅助信号引导模型养成结构化输出的习惯。在实际训练中,格式奖励权重通常设置得较小(如 0.1-0.3),避免模型"形式正确但内容空洞"。
16.4.2 Dr.GRPO:长度偏差修正
GRPO 在实际训练中暴露出一个理论层面的缺陷——长度偏差(Length Bias)。Dr.GRPO(Doctor GRPO)精确定位了这一问题并给出了简洁的修正。
问题定位。 回忆 GRPO 的目标函数,其损失中包含一个按序列长度归一化的求和:
然而,策略梯度的理论推导告诉我们,正确的梯度应该是直接对所有 token 求和,而不是先求和再除以长度:
- 理论真实梯度:
- GRPO 的梯度:
这个多出来的
| 场景 | 优势符号 | 长度归一化的效果 | 后果 |
|---|---|---|---|
| 模型回答正确( | 正 | 长回答的奖励信号被稀释 | 模型倾向于给出过短的正确回答 |
| 模型回答错误( | 负 | 长回答的惩罚信号被削弱 | 模型不惧产出冗长的错误回答 |
最终效果是:错误回复越写越长(因为惩罚被长度稀释),正确回复越来越短(因为收益被长度稀释),产生大量无意义 token 浪费。
Dr.GRPO 的修正。 方案极为简洁——不再除以序列长度 max_completion_length):
这个常数
from trl import GRPOConfig
config = GRPOConfig(
loss_type="dr_grpo", # 使用 Dr.GRPO 损失
# 其他参数...
)DAPO 的 Token-Level Loss。 同样针对长度偏差问题,DAPO 提出了另一种方案——将归一化因子从"每个序列的长度"改为"所有序列的总长度":
这种 Token-Level Loss 让同一个 mini-batch 内每个 token 对梯度的贡献权重相同,缓解了长序列 token 信号被稀释的问题。Dr.GRPO 的作者指出,DAPO 的方案减轻了长度偏差但没有完全消除——因为归一化因子
下面用一个最小化的例子展示三种损失函数的差异:
import torch
def compare_loss_types(log_probs_list, advantages, max_length=512):
"""对比三种 GRPO 损失计算方式的差异"""
G = len(log_probs_list)
# 1. 原始 GRPO:按各序列长度归一化
grpo_loss = 0.0
for i in range(G):
seq_len = len(log_probs_list[i])
grpo_loss += (log_probs_list[i] * advantages[i]).sum() / seq_len
grpo_loss = -grpo_loss / G
# 2. DAPO Token-Level:按总 token 数归一化
total_tokens = sum(len(lp) for lp in log_probs_list)
dapo_loss = 0.0
for i in range(G):
dapo_loss += (log_probs_list[i] * advantages[i]).sum()
dapo_loss = -dapo_loss / total_tokens
# 3. Dr.GRPO:按固定常数归一化
dr_grpo_loss = 0.0
for i in range(G):
dr_grpo_loss += (log_probs_list[i] * advantages[i]).sum()
dr_grpo_loss = -dr_grpo_loss / (max_length * G)
return {
"GRPO": grpo_loss.item(),
"DAPO": dapo_loss.item(),
"Dr.GRPO": dr_grpo_loss.item(),
}
# 模拟:两个序列,一长一短,优势相同
short_logp = torch.randn(50) # 短序列:50 tokens
long_logp = torch.randn(400) # 长序列:400 tokens
results = compare_loss_types(
[short_logp, long_logp],
advantages=[1.0, 1.0],
max_length=512,
)
for name, val in results.items():
print(f"{name:10s} loss = {val:.4f}")
# 观察:GRPO 中短序列的梯度贡献被放大(除以 50 vs 除以 400)16.4.3 GSPO:序列级重要性采样
如果说 Dr.GRPO 修复的是"损失函数中的长度偏差",那么 GSPO(Group Sequence Policy Optimization,组序列策略优化) 瞄准的是一个更根本的问题——GRPO 中 token 级重要性采样的理论失效。
GRPO 的根本缺陷。 GRPO 继承了 PPO 的 token 级重要性比率
而在 GRPO 中,每个 token 位置
- 高方差噪声注入训练梯度
- 噪声在长序列上逐 token 累积
- 被裁剪机制进一步放大
- 最终可能导致不可逆的模型崩溃
这一问题在 MoE 模型上尤为严重:稀疏激活的专家路由在梯度更新后可能发生变化(实验显示约 10% 的专家激活在更新后改变),使得 token 级重要性比率剧烈波动。
GSPO 的核心设计。 GSPO 的解决思路遵循一个简洁的原则:优化目标的粒度应与奖励的粒度匹配。既然奖励是整个序列的函数,那么重要性采样和裁剪也应该在序列级别进行。
GSPO 定义序列级重要性比率:
其中取
目标函数为:
注意这里的裁剪作用在整个序列而非单个 token 上。
GRPO vs GSPO 梯度对比。 两种算法的梯度差异揭示了核心区别:
| 特征 | GRPO | GSPO |
|---|---|---|
| 重要性权重作用粒度 | 每个 token 独立加权 | 所有 token 等权加权(系数 |
| 裁剪粒度 | Token 级 | 序列级 |
| 裁剪范围 | ||
| 各 token 梯度权重 | 不等,由各自 | 相等,仅由全局 |
GSPO 的裁剪范围之所以小三个数量级,是因为序列级重要性比率经过了指数平均,其数值范围远比 token 级比率紧凑。

图 16-8:GSPO vs GRPO 训练对比(基于 Qwen3-30B-A3B)。上方为训练奖励,下方为 AIME'24、LiveCodeBench、CodeForces 三个基准的表现。GSPO 全程领先且稳定提升。
一个反直觉的发现。 实验数据显示 GSPO 的裁剪比例为 0.15(即 15% 的 token 被裁剪),而 GRPO 仅为 0.0013——两者相差约两个数量级。然而裁剪比例更高的 GSPO 反而训练效率更好。这说明 GRPO 的 token 级梯度估计本质上噪声极大——"更多但更嘈杂的梯度"不如"更少但更干净的梯度"。
GSPO 对 MoE 训练的优势。 GSPO 完全不需要 Routing Replay(路由回放)这一复杂的工程补丁。Routing Replay 的原理是在计算新策略的重要性比率时强制使用旧策略的专家路由模式,以避免路由变化导致的 token 级比率波动。但这增加了额外的内存开销和实现复杂度。GSPO 只关注序列级似然

图 16-9:MoE 模型上 GRPO 的稳定性问题。使用 Routing Replay(紫色)训练正常;不使用(橙色)则训练奖励持续下降、模型崩溃。GSPO 不需要这一机制即可稳定训练。
下面是 GSPO 核心计算的精简实现:
import torch
import torch.nn.functional as F
def compute_gspo_loss(
new_logprobs: torch.Tensor, # [batch, seq_len],当前策略的 token log-prob
old_logprobs: torch.Tensor, # [batch, seq_len],旧策略的 token log-prob
advantages: torch.Tensor, # [batch],组归一化优势
mask: torch.Tensor, # [batch, seq_len],有效 token mask
clip_eps: float = 3e-4, # 序列级裁剪范围(远小于 token 级)
) -> torch.Tensor:
"""GSPO 损失函数:序列级重要性采样 + 序列级裁剪"""
# 序列长度(有效 token 数)
seq_lengths = mask.sum(dim=-1) # [batch]
# 计算序列级重要性比率 s_i = exp(mean(log_ratio))
log_ratios = (new_logprobs - old_logprobs) * mask # [batch, seq_len]
mean_log_ratio = log_ratios.sum(dim=-1) / seq_lengths # [batch]
s = torch.exp(mean_log_ratio) # 序列级重要性比率
# 序列级裁剪
s_clipped = torch.clamp(s, 1.0 - clip_eps, 1.0 + clip_eps)
# 目标函数:取 min(与 PPO 裁剪逻辑相同,但在序列级操作)
obj_unclipped = s * advantages
obj_clipped = s_clipped * advantages
obj = torch.min(obj_unclipped, obj_clipped)
return -obj.mean()16.4.4 DAPO:动态采样与综合工程改进
除了前文提到的 Clip-Higher 和 Token-Level Loss,DAPO 还贡献了两项重要的工程改进。
动态采样(Dynamic Sampling)。 GRPO 在训练过程中会遇到一类"无效样本"——当某个 prompt 的所有
随着训练推进,模型对简单题的正确率接近 100%,全对样本的比例持续增加,有效训练信号不断减少。动态采样的解决方案是:过滤掉全对或全错的 prompt,只保留有区分度的样本。
即一组采样中必须同时存在正确和错误的回答,确保组内优势有正有负。

图 16-10:动态采样的效果。使用 Dynamic Sampling(深色)以约 1/3 的训练步骤达到与不使用时(浅色)相当的准确率。
超长奖励塑造(Overlong Reward Shaping)。 被截断的超长序列如果简单分配惩罚性奖励,会引入 reward noise(一段合理的推理可能仅因长度被惩罚)。DAPO 提出两步方案:
- 超长过滤:对被截断的样本进行 loss 屏蔽,不参与梯度计算
- 软超长惩罚:对接近最大长度的样本施加渐变惩罚
其中
DAPO 在 Qwen2.5-32B 上的消融实验清晰展示了各项改进的增量贡献:
| 配置 | AIME'24 avg@32 |
|---|---|
| 朴素 GRPO | 30 |
| + 超长过滤 | 36 |
| + Clip-Higher | 38 |
| + 软超长惩罚 | 41 |
| + Token-Level Loss | 42 |
| + 动态采样(完整 DAPO) | 50 |
从 30 分提升到 50 分,超过了 DeepSeek-R1-Zero 在相同基础模型上的 47 分。
16.4.5 GRPO with Replay Buffer
标准 GRPO 是一个在线学习算法:每轮训练都用当前策略采样新数据,用完即弃。这意味着如果某个 prompt 在早期阶段产生了高质量的对比信号(组内同时有好答案和坏答案),这些信号在下一轮就被丢弃了。
TRL 框架的 GRPOWithReplayBufferTrainer 实现了一种经验回放机制来缓解这个问题:
- 维护一个固定大小的 Replay Buffer,缓存历史批次中"高质量"的样本组(奖励标准差大于零且奖励均值高的组)
- 当当前批次出现零标准差的组(全对或全错,无学习信号)时,用 Buffer 中的历史样本替换
- 确保每轮训练都有充足的梯度信号
# 教学示例:展示核心逻辑,省略了部分 import 和辅助函数定义
from trl.experimental.grpo_with_replay_buffer import (
GRPOWithReplayBufferConfig,
GRPOWithReplayBufferTrainer,
)
config = GRPOWithReplayBufferConfig(
output_dir="./output",
per_device_train_batch_size=4,
num_generations=8, # 每个 prompt 采样 8 个回答
max_completion_length=1024,
replay_buffer_size=16, # Buffer 容量
)
trainer = GRPOWithReplayBufferTrainer(
model="Qwen/Qwen2.5-7B-Instruct",
reward_funcs=[accuracy_reward],
args=config,
train_dataset=dataset,
)
trainer.train()Replay Buffer 本质上将 GRPO 从纯在线学习推向了部分离线的模式。这引出了一个更深层的设计问题:如何高效利用离线数据?GSPO 的序列级重要性采样恰好为此提供了理论基础——当样本来自较旧的策略时,序列级比率能更稳健地修正分布偏差。
16.4.6 异步训练(AsyncGRPO)设计概要
标准 GRPO 训练是同步的:生成一批数据 → 计算损失 → 更新参数 → 再生成,两个阶段交替进行。在使用 vLLM 等推理引擎加速生成时,训练 GPU 在生成阶段空闲,推理 GPU 在训练阶段空闲,硬件利用率不到 50%。
AsyncGRPO 将生成和训练解耦为两个并发流程:
| 组件 | 运行位置 | 职责 |
|---|---|---|
| Rollout Worker | 后台线程 + 推理 GPU | 持续向 vLLM 发送 prompt、获取补全、计算奖励和优势,推入队列 |
| Training Loop | 主进程 + 训练 GPU | 从队列拉取样本、计算裁剪损失、更新模型参数 |
两个关键参数控制异步程度:
weight_sync_steps:每隔多少训练步将更新后的权重同步到推理引擎(通过 NCCL)max_staleness:允许训练样本滞后多少次权重更新。超过阈值的"过时"样本会被丢弃
由于生成和训练并发进行,训练样本可能由稍旧版本的模型生成。这本质上引入了离线偏差(off-policy bias)——与使用 Replay Buffer 面临的挑战类似。解决思路同样是重要性采样修正:TRL 提供了截断重要性采样(Truncated IS)和掩码重要性采样(Masked IS)两种策略来处理训练-推理不匹配。
AsyncGRPO 的详细系统架构(包括推理-训练分离框架、权重同步协议、多节点部署方案等)将在 §18.4 中专门讨论。
16.4.7 变体选型指南
面对众多 GRPO 变体,如何选择?下面的决策框架可供参考:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 快速上手 / 中小模型 | 原始 GRPO + Dr.GRPO 损失 | 实现简单,消除长度偏差 |
| 长思维链推理(数学/代码) | DAPO 全套(Clip-Higher + Dynamic Sampling + Token-Level Loss + Overlong Shaping) | 解决熵坍缩 + 无效梯度 + 长度控制 |
| 超大规模 MoE 模型 | GSPO | 天然兼容 MoE,无需 Routing Replay |
| 数据利用率要求高 | GRPO + Replay Buffer | 复用高质量历史样本 |
| 硬件利用率优先 | AsyncGRPO | 生成与训练并行 |
小结
本节从三个层次审视了 GRPO 的工程改进:
- 目标函数层面:裁剪策略解耦(Clip-Higher)缓解熵坍缩,KL 项按需调控,格式奖励引导结构化输出。
- 算法层面:Dr.GRPO 通过固定常数归一化消除长度偏差;GSPO 将重要性采样提升到序列级别,从根本上解决 token 级采样失效和 MoE 训练不稳定问题;DAPO 的动态采样和超长奖励塑造提高了有效梯度信号密度。
- 系统层面:Replay Buffer 提高数据复用率,AsyncGRPO 通过生成-训练解耦提升硬件利用率。
这些改进并非互斥——实际训练系统往往组合使用多种技巧。例如,可以在 GSPO 的序列级框架中加入动态采样,或在 AsyncGRPO 系统中使用 Dr.GRPO 的损失函数。理解每项改进解决的核心问题,才能在面对具体场景时做出正确的技术选择。