Skip to content

26.5 策略优化改进

上一节我们从零实现了完整的 GRPO 训练管线,并在数学推理任务上观察到了模型能力的提升。然而,"能跑起来"只是起点——基础版 GRPO 在训练稳定性、收敛效率和输出质量方面仍有大量改进空间。本节围绕三个核心改进方向展开:裁剪策略比率(Clipped Policy Ratio) 稳定训练更新、KL 散度惩罚(KL Divergence Penalty) 控制策略偏移、格式奖励(Format Reward) 引导结构化输出。最后,我们还将系统梳理社区涌现的一系列高级技巧,为读者构建一幅完整的 GRPO 改进全景图。

本节涵盖的策略优化改进内容总览


26.5.1 训练诊断:优势统计量与熵

在实施具体改进之前,我们需要先建立一套更精细的训练诊断工具。上一节我们只追踪了 loss、平均奖励和响应长度等基本指标,而要真正理解训练动态,还需要关注两个关键信号:优势统计量(Advantage Statistics)策略熵(Entropy)

高级训练指标追踪概览

优势的均值与标准差

回顾 GRPO 算法:对同一道题生成多个 rollout,计算各自的奖励,然后通过组内归一化得到优势值。优势的两个汇总统计量能告诉我们训练信号的质量。

优势统计量示意:来自四个 rollout 的奖励及其归一化后的优势

python
import torch

rewards = torch.tensor([0.0, 0.0, 1.0, 0.0])
advantages = (rewards - rewards.mean()) / (rewards.std() + 1e-4)

print(f"优势均值: {advantages.mean():.4f}")  # ≈ 0.0(归一化的数学必然)
print(f"优势标准差: {advantages.std():.4f}")  # ≈ 1.0(信号尺度正常)
  • 均值恒为 0:GRPO 的组内归一化(减均值除标准差)保证了优势均值始终为零。如果观察到非零偏移,大概率是代码 bug。
  • 标准差是关键信号std1 说明梯度信号尺度合理,训练稳定;std1 说明所有 rollout 获得几乎相同的奖励(要么全对要么全错),梯度信号消失;std1 则意味着更新幅度过大,可能导致训练不稳定。

一个特殊但值得关注的情况是 std=0——当一组 rollout 的奖励完全相同时。如果平均奖励为 1.0(全部正确),这实际上是好事,说明模型已经掌握了当前难度的题目,应该增大题目难度或停止训练;但如果平均奖励为 0.0(全部错误),则说明模型对当前题目束手无策,训练信号完全丢失。

策略熵

熵(Entropy)衡量 LLM 在生成下一个 token 时的不确定性。具体计算方式如下:对模型输出的 logits 做 softmax 得到概率分布 p,再做 log-softmax 得到对数概率 logp,然后按下式计算单步熵并对所有生成步取平均:

(1)H=vVp(v)logp(v)

熵的计算过程:概率乘以对数概率再取负和

python
def sequence_logprob_and_entropy(model, token_ids, prompt_len):
    """计算序列对数概率和平均熵"""
    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)

    # 生成部分的序列对数概率
    answer_logprobs = selected[prompt_len - 1:]
    logp = torch.sum(answer_logprobs)

    # 生成部分的平均熵
    all_logprobs = logprobs[:-1][prompt_len - 1:]
    if all_logprobs.numel() == 0:
        entropy = logp.new_tensor(0.0)
    else:
        probs = torch.exp(all_logprobs)
        step_entropy = -torch.sum(probs * all_logprobs, dim=-1)
        entropy = torch.mean(step_entropy)

    return logp, entropy

如何解读熵值?作为经验法则:

熵范围含义
极低(接近 0)模型高度确定,几乎确定性地输出某个 token——可能出现模式坍缩
适中(1~2)概率质量分布在少数几个 token 上,训练通常稳定
极高(接近 $\log\mathcal

在实际训练中,若观察到熵持续下降至接近零,往往意味着模型正在过拟合到某种固定的输出模式,需要引起警惕。


26.5.2 裁剪策略比率

基础版 GRPO 直接用优势值乘以对数概率来计算策略梯度损失。这种做法有一个风险:单次更新可能过于激进,导致当前策略与生成 rollout 时的旧策略偏差过大,从而破坏训练稳定性。裁剪策略比率(Clipped Policy Ratio) 借鉴了 PPO(Proximal Policy Optimization)的思想,通过限制新旧策略之间的偏离幅度来解决这一问题。

裁剪策略比率在 GRPO 流程中的位置

策略比率的定义

策略比率衡量的是:对于同一组 token,当前策略分配的概率与旧策略分配的概率之间的比值。由于概率值通常很小,我们在对数空间中计算差值后取指数:

(2)r(θ)=exp(logπθ(a|s)logπθold(a|s))=πθ(a|s)πθold(a|s)

当新旧策略完全一致时,r(θ)=1;当新策略给某个序列分配了更高的概率时,r(θ)>1

在 GRPO 管线中,策略比率比较的是步骤 4(旧策略的对数概率)和步骤 6(更新后策略的对数概率)

裁剪目标函数

PPO 风格的裁剪将策略比率限制在 [1ϵ,1+ϵ] 区间内,然后根据优势的正负取保守估计:

(3)LCLIP=E[{min(rA,clip(r,1ϵ,1+ϵ)A)if A0max(rA,clip(r,1ϵ,1+ϵ)A)if A<0]

直觉上:当优势为正(好的 rollout)且策略比率已经很大时,裁剪阻止模型继续"放大"这个方向的更新;当优势为负(差的 rollout)且策略比率已经很小时,裁剪阻止模型过度"远离"这个方向。

策略比率与裁剪比率的计算示例

python
def compute_clipped_grpo_loss(new_logps, old_logps, advantages, clip_eps=10.0):
    """带裁剪的 GRPO 策略梯度损失"""
    log_ratio = new_logps - old_logps
    ratio = torch.exp(log_ratio)
    clipped_ratio = torch.clamp(ratio, 1.0 - clip_eps, 1.0 + clip_eps)

    unclipped = ratio * advantages
    clipped = clipped_ratio * advantages
    objective = torch.where(
        advantages >= 0,
        torch.minimum(unclipped, clipped),
        torch.maximum(unclipped, clipped),
    )
    return -objective.mean()

clip_eps 的选择:DeepSeek-R1 使用了 ϵ=10,这是一个非常宽松的裁剪范围,几乎不会触发。但在许多其他 RL 训练管线中,ϵ 通常设为 0.1~0.3 之间,限制更加严格。后文介绍的 DAPO 进一步将上下界解耦:下界使用较小的 ϵlow=0.2,上界使用较大的 ϵhigh=10.0,允许模型更自由地增加好序列的概率,但严格限制减少坏序列概率的幅度。

训练效果:加入裁剪后,训练曲线通常更加平滑,之前在 400 步左右出现的性能骤降现象得到有效缓解。


26.5.3 KL 散度惩罚

裁剪限制的是单次更新的幅度,而 KL 散度惩罚(KL Divergence Penalty) 关注的是全局偏移——当前策略 πθ 相对于训练起点的参考策略 πref 偏离了多远。这是完整 GRPO 算法中不可或缺的一个组件,其目的是防止模型在追求奖励的过程中遗忘预训练所学的通用知识。

KL 散度在 GRPO 中的位置

KL 惩罚的计算

对每个 rollout,在相同 token 序列上分别用当前策略和参考策略计算对数概率,两者之差的期望即为 KL 散度的近似:

(4)LKL=βE[logπθ(a|s)logπref(a|s)]

其中 β 是 KL 惩罚系数(kl_coeff)。最终损失为策略梯度损失与 KL 损失之和:

(5)L=LCLIP+LKL

KL 惩罚的计算过程:比较当前策略和参考策略在相同 token 上的对数概率

python
def compute_grpo_loss_with_kl(
    model, old_model, ref_model, token_ids_list, prompt_lens,
    advantages, clip_eps=10.0, kl_coeff=0.001
):
    """带 KL 惩罚的 GRPO 损失"""
    new_logps, ref_logps_list = [], []
    for token_ids, prompt_len in zip(token_ids_list, prompt_lens):
        new_logp, _ = sequence_logprob_and_entropy(model, token_ids, prompt_len)
        new_logps.append(new_logp)
        with torch.no_grad():
            ref_logp, _ = sequence_logprob_and_entropy(
                ref_model, token_ids, prompt_len
            )
            ref_logps_list.append(ref_logp)

    new_logps = torch.stack(new_logps)
    ref_logps = torch.stack(ref_logps_list)

    # 策略梯度损失(带裁剪)
    old_logps = torch.stack([
        sequence_logprob_and_entropy(old_model, t, p)[0]
        for t, p in zip(token_ids_list, prompt_lens)
    ]).detach()
    pg_loss = compute_clipped_grpo_loss(new_logps, old_logps, advantages, clip_eps)

    # KL 惩罚
    kl_loss = kl_coeff * torch.mean(new_logps - ref_logps)

    return pg_loss + kl_loss, pg_loss, kl_loss

实际注意事项

加入 KL 惩罚需要在内存中额外维护一份参考模型的副本,对 GPU 显存有显著影响。更关键的是,kl_coeff 的取值需要谨慎调节:

  • 过大(如 0.02):KL 约束过强,模型几乎无法偏离参考策略,难以学到新能力。实验表明,在小规模模型上使用较大的 KL 系数可能导致训练在几十步后完全崩溃,模型输出退化为乱码。
  • 过小(如 0.0001):KL 约束形同虚设,无法防止灾难性遗忘。
  • 设为 0:完全去掉 KL 项。多项研究(Dr. GRPO (Liu et al., 2025)、OLMo 3、DeepSeek-V3.2)发现,在数学推理任务上去掉 KL 项反而效果更好,因为数学任务有明确的正确性奖励作为锚定,不太需要 KL 来防止漂移。

[选读] KL 惩罚的三种改进方案

当 KL 惩罚表现不佳时,可以从以下三个方向修复:

  1. 长度归一化:将序列级对数概率除以生成长度后再计算 KL,消除长序列天然获得更大 KL 值的偏差。
  2. 降低 kl_coeff:从 0.02 降到 0.001 甚至更低,但需要仔细实验。
  3. 重加权 KL(DeepSeek-V3.2 方案):用旧策略的重要性采样权重对 KL 进行加权,公式为 LKLrw=βE[πθπθold(logπθlogπref)],使 KL 惩罚聚焦于策略变化最大的样本。

26.5.4 格式奖励

到目前为止,我们只使用了单一的正确性奖励(答案对得 1 分,错得 0 分)。然而,仅凭正确性奖励无法引导模型学会使用结构化的推理格式——比如 <think>...</think> 标签来分隔中间推理过程和最终答案。格式奖励(Format Reward) 正是为此而设计的辅助奖励信号。

格式奖励概览

格式奖励函数

格式奖励检查生成的 token 序列中是否包含正确排列的 <think></think> 标记。如果 <think> 出现在 </think> 之前,返回 1.0;否则返回 0.0:

格式奖励的判定逻辑:检查 think 标记的顺序

python
THINK_TOKEN_ID = 151667      # <think> 的 token ID
END_THINK_TOKEN_ID = 151668  # </think> 的 token ID

def reward_format(token_ids, prompt_len):
    """检查生成的 token 中是否包含正确排列的 <think>...</think>"""
    try:
        gen = token_ids[prompt_len:].tolist()
        return float(gen.index(THINK_TOKEN_ID) < gen.index(END_THINK_TOKEN_ID))
    except ValueError:
        return 0.0

组合奖励

将格式奖励与正确性奖励加权组合,得到最终奖励信号:

(6)r=rcorrect+λrformat

其中 λ 是格式奖励权重(format_reward_weight),默认为 1.0。一个可选策略是条件格式奖励——仅在答案正确时才给予格式分:

python
def combined_reward(text, ground_truth, token_ids, prompt_len,
                    format_weight=1.0, conditional=False):
    """组合正确性奖励和格式奖励"""
    correctness = reward_rlvr(text, ground_truth)
    fmt = reward_format(token_ids, prompt_len)

    if conditional:
        fmt *= correctness  # 仅正确答案获得格式奖励

    return correctness + format_weight * fmt

实践经验

使用格式奖励训练时需要注意以下要点:

  1. 基座模型 vs 推理模型:基座模型从未见过 <think> 标记,直接加格式奖励效果很差。应使用已经通过指令微调熟悉这些标记的推理变体模型(如 Qwen3 的 reasoning 版本)。
  2. 权重平衡:格式奖励权重过高(λ=1.0)可能导致模型过度追求格式而忽视正确性,表现为模型很快学会输出 <think>...</think> 标签但推理质量下降。将 λ 降至 0.1 或使用条件格式奖励可以缓解此问题。
  3. 监控分离:训练时应分别追踪正确性奖励和格式奖励的变化趋势,以便及时发现奖励信号的失衡。

26.5.5 社区改进技巧全景

GRPO 改进技巧总览

DeepSeek-R1 于 2025 年 1 月引爆 GRPO 热潮后,短短数月内社区提出了大量改进方案。这些改进可以按来源分为三个流派:DAPO 流派(主打探索效率)、Dr. GRPO 流派(主打理论简洁)、和 DeepSeek-V3.2 流派(主打工程精细化)。下面逐一介绍关键技巧。

DAPO 流派(Yu et al., 2025)

DAPO(Decoupled Alignment via Policy Optimization)提出了一揽子改进,其核心洞察是:标准 GRPO 过于保守,限制了模型的探索能力。

  1. 零梯度信号过滤:当一组 rollout 的奖励完全相同时(全对或全错),优势全为零,此时的梯度更新毫无意义。DAPO 在检测到这种情况时直接跳过更新步,节省计算。

  2. 主动采样(Active Sampling):与零梯度过滤配合——当某道题的 rollout 全部零梯度时,自动换一道题重新采样,确保每个训练步都有有效的学习信号。

  3. Token 级损失:标准 GRPO 将整个序列的对数概率求和后作为一个标量参与损失计算。DAPO 改为在每个 token 位置独立计算策略比率和裁剪,使梯度信号更精细:

python
def token_level_clipped_loss(new_token_logps, old_token_logps, advantage,
                              clip_low=0.2, clip_high=10.0):
    """Token 级裁剪损失(DAPO 风格)"""
    log_ratio = new_token_logps - old_token_logps
    ratio = torch.exp(log_ratio)
    # 截断重要性采样(防止极端比率)
    ratio = torch.clamp(ratio, max=1.0 + clip_high)
    clipped_ratio = torch.clamp(ratio, 1.0 - clip_low, 1.0 + clip_high)

    unclipped = ratio * advantage
    clipped = clipped_ratio * advantage
    token_obj = torch.where(
        advantage >= 0,
        torch.minimum(unclipped, clipped),
        torch.maximum(unclipped, clipped),
    )
    return -token_obj.mean()
  1. 去掉 KL 惩罚:DAPO 发现在数学推理任务上,KL 项反而阻碍了学习,直接设 kl_coeff = 0

  2. 非对称裁剪(Clip Higher):下界 ϵlow=0.2 严格限制减小概率的幅度,上界 ϵhigh 放得很宽(如 10.0),鼓励模型大胆增加好序列的概率。

Dr. GRPO 流派(Liu et al., 2025)

Dr. GRPO(Decoupled Regularization for GRPO)的核心改进更为精炼:

  1. 去掉标准差归一化:标准 GRPO 将优势除以标准差进行归一化,Dr. GRPO 发现仅减均值而不除标准差反而更稳定,因为标准差归一化在奖励方差极小时会放大噪声:
python
# 标准 GRPO
advantages = (rewards - rewards.mean()) / (rewards.std() + 1e-4)

# Dr. GRPO
advantages = rewards - rewards.mean()

DeepSeek-V3.2 流派

DeepSeek-V3.2 针对大规模分布式训练场景做了一系列精细化调整:

  1. 分领域 KL 系数:对数学领域设 KL 系数为 0(数学有明确的对错信号,不需要 KL 锚定),对代码和通用领域保留 KL 惩罚。

  2. 重加权 KL:如前所述,用重要性采样权重对 KL 进行加权,使惩罚聚焦于策略变化最大的样本。

  3. 离策略序列掩码(Off-Policy Sequence Masking):当某个负优势样本的 KL 散度超过阈值 δ 时,将其优势置零,相当于忽略这个"过时"的样本:

python
# 计算每个序列的平均 KL
seq_kl = (old_logps - new_logps) / gen_lens
# 掩码:负优势 + 高 KL = 离策略太远,忽略
off_policy_mask = torch.ones_like(advantages)
off_policy_mask[(advantages < 0) & (seq_kl > delta)] = 0
advantages = advantages * off_policy_mask
  1. 保留采样掩码(Keep Sampling Mask):在 top-p/top-k 采样时记录每步的有效 token 集合,训练时用该掩码遮盖未被采样器选中的 token 的 logits,确保训练和采样看到的概率空间一致。

更多方案

除上述三个主要流派外,社区还提出了其他有价值的改进:

  • GDPO(Liu et al., 2026):当使用多个奖励信号(如正确性 + 格式)时,在聚合之前对每个奖励维度分别做组内归一化,避免某个奖励的尺度压制另一个。
  • GSPO(Zheng et al., 2025):在序列级引入重要性采样和裁剪,介于标准 GRPO(无 IS)和 DAPO(token 级 IS)之间。
  • CISPO(MiniMax et al., 2025):直接裁剪重要性采样权重而非 token 级更新量,理论上更接近原始 PPO 的设计意图。

下表总结了各改进技巧及其来源:

编号改进技巧来源
1零梯度信号过滤DAPO
2主动采样DAPO
3Token 级损失DAPO
4去掉 KL 惩罚DAPO, Dr. GRPO
5非对称裁剪DAPO
6截断重要性采样Yao et al., 2025
7去掉标准差归一化Dr. GRPO
8分领域 KL 系数DeepSeek-V3.2
9重加权 KLDeepSeek-V3.2
10离策略序列掩码DeepSeek-V3.2
11保留采样掩码DeepSeek-V3.2
12保留标准 GRPO 优势归一化DeepSeek-V3.2
13多奖励分组归一化GDPO
14序列级重要性采样GSPO
15裁剪重要性采样权重CISPO

26.5.6 改进方案的组合与选择

面对如此多的改进技巧,实践中该如何选择?根据社区经验和源码分析,以下是两种经过验证的组合配方

配方一:OLMo 3 风格(适合快速实验)

整合 DAPO 和 Dr. GRPO 的核心改进:零梯度过滤 + 主动采样 + Token 级损失 + 去掉 KL + 非对称裁剪 + 截断重要性采样 + 去掉标准差归一化。该配方实现简单,不需要额外维护参考模型,内存开销最小:

python
# OLMo 3 风格的核心超参数
config = {
    "clip_eps_low": 0.2,       # 非对称裁剪下界
    "clip_eps_high": 10.0,     # 非对称裁剪上界
    "kl_coeff": 0.0,           # 去掉 KL
    "active_sampling": True,   # 主动采样
    "token_level_loss": True,  # Token 级损失
    "normalize_std": False,    # 去掉标准差归一化
}

配方二:DeepSeek-V3.2 风格(适合大规模训练)

保留 KL 惩罚但做精细化处理:分领域 KL 系数 + 重加权 KL + 离策略序列掩码 + 保留采样掩码 + 标准优势归一化。该配方适用于多领域混合训练,且对超参数不那么敏感:

python
# DeepSeek-V3.2 风格的核心超参数
config = {
    "clip_eps": 10.0,
    "kl_coeff_math": 0.0,       # 数学领域关闭 KL
    "kl_coeff_code": 0.02,      # 代码领域保留 KL
    "off_policy_delta": 0.1,    # 离策略掩码阈值
    "reweighted_kl": True,      # 重加权 KL
    "keep_sampling_mask": True, # 保留采样掩码
}

无论选择哪种配方,建议始终保留裁剪策略比率(几乎零成本但显著提升稳定性)和完整的训练指标追踪(优势统计量 + 熵 + 策略比率),以便及时发现训练异常。


本节从诊断工具出发,逐步引入裁剪、KL 惩罚、格式奖励三大改进,并系统梳理了社区涌现的 15 项高级技巧。这些改进并非孤立存在——它们共同构成了一个工具箱,读者可以根据自己的任务特点和资源条件灵活组合。核心原则是:先确保训练稳定(裁剪 + 诊断),再根据需要添加约束(KL)或信号(格式奖励),最后按需引入社区最新方案提升效率