chore: import upstream snapshot with attribution
Lint test / lint (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:34:58 +08:00
commit a203934033
1368 changed files with 175001 additions and 0 deletions
@@ -0,0 +1,63 @@
# On-Policy RL Meets Off-Policy Experts: Harmonizing SFT and RL via Dynamic Weighting (CHORD)
本文档介绍论文 [On-Policy RL Meets Off-Policy Experts: Harmonizing SFT and RL via Dynamic Weighting](https://arxiv.org/abs/2508.11408) 中提出的 CHORD 算法。CHORD 的核心思想是在强化学习过程中,动态融合专家数据(SFT),通过 全局权重 μ + token 级别权重 φ 的双重控制机制,在模仿与探索之间实现平衡。
## 算法概述
CHORD 算法通过在 GRPO loss 中引入 **SFT loss**,实现动态混合训练。总体目标函数为:
$$
\mathcal{L}_{\text{CHORD}} = (1 - \mu) \cdot \mathcal{L}_{\text{GRPO}} + \mu \cdot \mathcal{L}_{\text{SFT}}
$$
其中:
- $\mathcal{L}_{\text{GRPO}}$:基于 on-policy 采样的强化学习损失(类似 PPO)。
- $\mathcal{L}_{\text{SFT}}$:监督微调损失。
- $\mu \in [0, 1]$:全局平衡系数,控制 SFT 信号在总梯度中的贡献。
### 参数配置(数据与批量大小)
我们可以基于 GRPO 训练实现 CHORD 训练。
CHORD 需要在训练时指定额外的 SFT 数据集和批量大小:
- `chord_sft_dataset`: 用于提供专家数据的 SFT 数据集。
- `chord_sft_per_device_train_batch_size`: 每个设备的 SFT mini-batch 大小。
---
## 两种 CHORD 变体
论文提出了两种算法变体:**CHORD-µ** 和 **CHORD-ϕ**
### CHORD-µ
通过在训练过程中逐步 **衰减 μ**,实现从模仿专家到自主探索的过渡。
**参数:**
- `chord_mu_peak`:μ 的峰值。
- `chord_mu_valley` μ 的衰减终值。
- `chord_mu_warmup_steps` μ 值上升至峰值的训练步数。
- `chord_mu_decay_steps` μ 从峰值衰减到谷值的训练步数。
### CHORD-ϕ(Token 级加权)
**CHORD-ϕ** 通过 **token-wise 权重函数 φ** 动态控制每个专家 token 的梯度贡献。
**φ 定义:**
$$
\phi(y_t^\star, \pi_\theta) = p_t \cdot (1 - p_t)
$$
其中:
- $p_t = \pi_\theta(y_t^\star \mid x, y_{<t}^\star)$:模型当前预测专家 token 的概率。
- 当 $p_t ≈ 0.5$(模型不确定时),φ 取最大值 → 强化学习不确定的 token。
- 当 $p_t ≈ 0$ 或 $p_t ≈ 1$,φ → 0 → 避免对过于确定或完全不会的 token 过度学习。
**开启 φ 加权的参数**
- `chord_enable_phi_function: bool = False`
- 设置为 `True` 即启用 token-wise 权重 φ。
注:如果使用常数 μ 值 ,设置 chord_mu_peak 与 chord_mu_valley 相同
<details>
<summary>mu值衰减与loss计算代码实现</summary>
请参考`GRPOTrainer``_compute_chord_loss`方法:
</details>
训练参考该[脚本](https://github.com/modelscope/ms-swift/tree/main/examples/train/grpo/internal/chord.sh)
@@ -0,0 +1,61 @@
# Clipped Importance Sampling Policy Optimization (CISPO)
Clipped Importance Sampling Policy Optimization (CISPO) 是 [MiniMax-M1](https://arxiv.org/abs/2506.13585) 论文中提出的一种强化学习算法。相比GRPOGroup Relative Policy Optimization)算法,CISPO 对重要性采样权重(importance sampling weights)本身进行裁剪。
## 算法原理
为便于理解,我们基于 GRPO 算法进行对比说明。
GRPO通过裁剪策略比率来限制策略更新幅度,其损失函数为:
$$
\mathcal{L}_{\text{GRPO}}(\theta) = -\mathbb{E}\left[\min\left(r_t(\theta) \cdot \hat{A}_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) \cdot \hat{A}_t\right)\right]
$$
其中 $r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)}$ 是重要性采样比。
在处理长推理链条时,这种裁剪方式可能导致以下问题:
**关键 Token 的梯度被抑制**:在复杂推理任务中,某些关键的低概率 token(如 *However, Recheck, Wait, Aha*)对于触发深度思考和推理纠错至关重要。这些 token 在旧策略 $\pi_{\theta_{\text{old}}}$ 中概率较低,当新策略试图提高其概率时,会导致较大的策略比率 $r_t(\theta)$GRPO 的裁剪机制会将这些 token 丢弃。
### CISPO 的解决方案
CISPO 的核心思想是:裁剪重要性采样权重,保留梯度更新。具体来说,CISPO 的损失函数为:
$$
\mathcal{L}_{\text{CISPO}}(\theta) = -\mathbb{E}\left[\text{detach}\left(\min(r_t(\theta), \epsilon_{\text{high}})\right) \cdot \hat{A}_t \cdot \log \pi_\theta(a_t|s_t)\right]
$$
其中 $r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)}$ 是重要性采样比。
**关键机制**
- 对重要性采样权重进行裁剪:$\min(r_t(\theta), \epsilon_{\text{high}})$
- **detach 操作**:裁剪后的权重不参与梯度计算,作为常数系数
- 梯度来自 $\log \pi_\theta(a_t|s_t)$ 项,保证所有 token 都有梯度贡献
## 实现细节
CISPO 的伪代码实现如下:
```python
log_ratio = per_token_logps - old_per_token_logps
importance_weights = torch.exp(log_ratio) # r_t(θ) = π_θ / π_θ_old
clamped_ratios = torch.clamp(importance_weights, max=epsilon_high).detach()
per_token_loss = -clamped_ratios * advantages.unsqueeze(1) * per_token_logps
```
## 参数设置
我们可以基于 `GRPOTrainer`,通过设置以下参数实现 CISPO 训练:
```bash
--loss_type cispo
--epsilon_high 5.0
```
> 相比其他算法, cispo 的 epsilon_high 一般取值较大,minimax论文中未给出具体的参数设置,这里的值参考论文[ScaleRL](https://arxiv.org/pdf/2510.13786)的实验设置
其他训练参数参考 [GRPO参数文档](../../Command-line-parameters.md#grpo参数)
@@ -0,0 +1,102 @@
# DAPO: An Open-Source LLM Reinforcement Learning System at Scale
[Decoupled Clip and Dynamic sAmpling Policy Optimization (DAPO)](https://arxiv.org/abs/2503.14476)在GRPO的基础上设置了几种trick,分别是
- [Clip Higher](#clip-higher)
- [Dynamic Sampling](#dynamic-sampling)
- [Token level Loss](#token-level-loss)
- [Overlong Filtering](#overlong-filtering)
- [Soft Overlong Punishment](#soft-overlong-punishment)
## Clip Higher
PPO和GRPO使用对称裁剪范围(如±0.2)限制策略更新幅度,虽然保证了稳定性,但也制约了模型的探索能力。特别是当某些token在旧策略中概率极低时,即使当前梯度显示其应被强化(A>0),最大增幅也被严格限制。
DAPO使用非对称裁剪范围, 提高上裁剪范围来鼓励模型进行探索:
- 上界(鼓励侧)放宽至0.28
- 下界(抑制侧)保持0.2不变
GRPO中,默认使用`epsilon`设置用对称裁剪范围
使用参数
- `epsilon_high` 设置上裁剪范围,此时参数`epsilon` 为下裁剪范围
## Dynamic Sampling
GRPO对每个问题采样多个回答计算组间优势,
$$
\hat{A}_{i,t} = \frac{R_i - \text{mean}(\{R_j\}_{j=1}^G)}{\text{std}(\{R_j\}_{j=1}^G)}
$$
而当生成的所有输出{oi}获得相同奖励时,组间优势等于0,会出现梯度消失导致训练效率下降
DAPO引入动态采样策略解决这一问题:
- 采样阶段跳过组间奖励标准差为0的数据
- 持续生成样本直到填满批次
使用参数
- `dynamic_sample true` 来开启动态采样
- `max_resample_times` 设置最多重采样次数
## Token level Loss
GRPO 在归一化损失时采用句子级归一化,这会导致损失计算具有长度偏差。
DAPO 使用token级归一化,避免了回答长度在损失计算上的偏差。
使用参数
- loss_type bnpo/dapo 来使用token级归一化
> loss_type 计算公式可参考[文档](../DeveloperGuide/loss_types.md)
## Overlong Filtering
DAPO 认为被强制截断的回复的奖励噪声较大,可能会导致模型难以区分质量问题和长度问题。为此,DAPO 筛除了训练中被截断的数据,使其不参与损失计算。
使用参数
- overlong_filter 开启对超长样本的过滤
## Soft Overlong Punishment
语言模型常面临生成长度控制难题:
- 过长输出可能被截断,导致正确内容被误判
- 无约束生成长度影响实用性和计算效率
DAPO 设计了三段式长度惩罚函数:
$$
R_{\text{length}}(L) =
\begin{cases}
0, & L \leq L_{\text{max}} - L_{\text{cache}} \\[10pt]
\dfrac{(L_{\text{max}} - L_{\text{cache}}) - L}{L_{\text{cache}}}, & L_{\text{max}} - L_{\text{cache}} < L \leq L_{\text{max}} \\[10pt]
-1, & L > L_{\text{max}}
\end{cases}
$$
在长度位于 $(L_{\text{max}} - L_{\text{cache}} < L \leq L_{\text{max}})$ 区间时设置线性递增惩罚,在 $(L > L_{\text{max}})$ 时设置最大惩罚(-1)
使用参数
- `reward_funcs soft_overlong` 启用该奖励函数
- `soft_max_length` 设置L_max,默认等于为模型的最长输出长度(max_completion_length)。
- `soft_cache_length`: 设置L_cache
## 参数设置
综上所述,我们可以基于GRPOTrainer,设置以下参数实现 DAPO 训练。
| 参数 | 类型 | 值 |
|----------------------|-----------|-------------|
| `--loss_type` | `str` | `bnpo`/`dapo`|
| `--epsilon_high` | `float` | `0.28` |
| `--dynamic_sample` | `bool` | `true` |
| `--max_resample_times` | `int` | `3` |
| `--overlong_filter` | `bool` | `true` |
| `--reward_funcs` | `str` | `soft_overlong`|
| `--soft_cache_length` | `int` | `4096`|
@@ -0,0 +1,54 @@
# FIPO: Future-KL Influenced Policy Optimization
作者: [li2zhi](https://github.com/li2zhi)
[FIPO](https://arxiv.org/abs/2603.19835) 是一种面向长链推理的 value-free RL 方法。它保留 GRPO/DAPO 的整体训练框架,但改变 token 级策略更新的加权方式:不再让一个序列级 advantage 均匀作用到所有 token,而是用折扣累积的 Future-KL 信号判断“从当前 token 开始的后续轨迹”整体是在被增强还是被削弱。
## 核心思想
GRPO/DAPO 中,每个 response 的 token 通常共享同一个序列级 advantage:
$$
\hat{A}_{i,t} = \hat{A}_{i}
$$
这种做法稳定且简单,但 credit assignment 粒度较粗。FIPO 引入当前策略与旧策略在每个 token 上的 log-prob shift
$$
\Delta \log p_t = \log \pi_\theta(y_t \mid x, y_{<t}) -
\log \pi_{\mathrm{old}}(y_t \mid x, y_{<t})
$$
如果 $\Delta \log p_t > 0$,说明当前训练正在提高该 token 的概率;如果小于 0,则说明该 token 正在被压低。FIPO进一步从当前位置向后折扣累积该信号:
$$
\mathrm{FutureKL}_t =
\sum_{k=t}^{T}\gamma^{k-t} M_k \Delta \log p_k
$$
其中 $M_k$ 是 completion mask$\gamma = 2^{-1 / \text{decay\_rate}}$。`decay_rate` 越大,越远的 future token 对当前位置的影响越强;`decay_rate` 越小,Future-KL 越偏局部。然后将 Future-KL 映射为 influence weight
$$
f_t = \mathrm{clip}(\exp(\mathrm{FutureKL}_t), 1-\epsilon_f, 1+\epsilon_f)
$$
最终把原本的 advantage 改成 future-aware advantage
$$
\tilde{A}_{i,t} = \hat{A}_{i} \cdot f_{i,t}
$$
## 参数
| 参数 | 类型 | 默认值 | 说明 |
|---------------------------|---------|--------|----------------------------------------------------------------------------------------------------------------|
| `--loss_type` | `str` | `grpo` | 设置为`fipo` 启用 FIPO loss |
| `--delta` | `float` | `None` | 启用后会同时用于 Future-KL 高 IS ratio token 过滤和主 loss 的 dual-clip 上限,应大于 `1 + epsilon_high`,对齐FIPO 32B训练脚本建议设置为 `10.0` |
| `--fipo_decay_rate` | `float` | `32.0` | Future-KL 折扣半衰参数,实际折扣为`2 ** (-1 / fipo_decay_rate)` |
| `--fipo_clip_range` | `float` | `0.2` | influence weight 裁剪范围;`0.2` 表示默认裁剪到 `[0.8, 1.2]` |
| `--fipo_clip_high_only` | `bool` | `true` | 若为`true`,权重只裁剪到 `[1.0, 1.0 + fipo_clip_range]`,更偏向放大正 Future-KL |
| `--fipo_safety_threshold` | `float` | `4.0` | 负 advantage 且 IS ratio 超过该阈值时,将 FIPO 权重限制到 `[0.8, 1.0]` 以避免过度惩罚 |
## 训练示例
[swift](https://github.com/modelscope/ms-swift/tree/main/examples/train/grpo/internal/fipo.sh)
@@ -0,0 +1,64 @@
# Group Sequence Policy Optimization
[Group Sequence Policy Optimization](https://arxiv.org/abs/2507.18071)中指出GRPO在计算重要性采样权重时,是在token级别进行操作的。然而,这种做法由于每个token仅采样一次,无法实现有效的分布校正,反而会在模型训练过程中引入高方差噪声,极易导致模型的梯度估计不稳定,最终造成模型训练的崩塌。因此,论文认为,优化目标的单位应该与奖励的单位保持一致。由于奖励通常是在序列级别(即完整生成的回复)给出的,因此更合理的做法是将 off-policy 校正和优化也提升到序列级别,而非 token 级别。以下是三种计算策略对比:
1. GRPO
对每个 token 独立计算重要性采样比,具体公式为
$$
w^{\mathrm{GRPO}}_{i,t} = \frac{\pi_\theta (y_{i, t} \mid x, y_{i, <t})}{\pi_{\theta_{\mathrm{old}}} (y_{i, t} \mid x, y_{i, <t})}
$$
2. GSPO (Sequence-Level)
在序列级别上计算重要性采样比,具体公式为
$$
w^{\mathrm{GSPO}}_{i} = \left[ \frac{\pi_\theta (y_i \mid x)}{\pi_{\theta_{\mathrm{old}}} (y_i \mid x)} \right]^{\frac{1}{|y_i|}}
= \exp\left( \frac{1}{|y_i|} \sum_{t=1}^{|y_i|} \log \frac{\pi_\theta (y_{i, t} \mid x, y_{i, <t})}{\pi_{\theta_{\mathrm{old}}} (y_{i, t} \mid x, y_{i, <t})} \right)
$$
3. GSPO-token
GSPO-token 结合了序列级与 token 级的重要性采样思想
$$
w_{i, t}^{\mathrm{GSPO-token}} = \mathrm{sg}\left[w_i^{\mathrm{GSPO}}\right] \cdot \frac{\pi_{\theta}(y_{i, t} \mid x, y_{i, < t})}{\mathrm{sg}\left[\pi_{\theta}(y_{i, t} \mid x, y_{i, < t})\right]}
$$
其中,$(\mathrm{sg}[\cdot])$ 表示梯度截断(detach())。
> 注意:根据梯度推导(即论文中的公式(11)和(18)),当各 token 的 advantage 相同时,GSPO-token 与 GSPO 等价。当前的 GRPO 实现中,所有 token 的 advantage 实际上都是基于句子级 reward 并在 group 内进行归一化,因此在这种设置下,GSPO-token 和 GSPO 在理论上是等价的。不过,GSPO-token 为未来更细粒度(token 级别)的 advantage 提供了支持。
伪代码实现
```python
log_ratio = per_token_logps - old_per_token_logps
# GRPO
log_importance_weights = log_ratio
# GSPO (Sequence-Level)
seq_weight = (log_ratio * mask).sum(-1) / mask.sum(-1)
log_importance_weights = seq_weight.unsqueeze(-1) # (B,1)
# GSPO-token
seq_weight = (log_ratio * mask).sum(-1) / mask.sum(-1)
log_importance_weights = seq_weight.detach().unsqueeze(-1) + (per_token_logps - per_token_logps.detach())
importance_weights = torch.exp(log_importance_weights)
```
我们可以在 GRPO 训练的基础上,通过参数 `--importance_sampling_level` 选择不同的算法:
- `importance_sampling_level token` (默认,GRPO 实现)
- `importance_sampling_level sequence` GSPO
- `importance_sampling_level sequence_token` GSPO-token
论文其他超参
```bash
--epsilon 3e-4 # from paper section 5.1
--epsilon_high 4e-4 # from paper section 5.1
--gradient_accumulation_steps 8
--steps_per_generation 32 # from paper section 5.1 (each batch of rollout data is partitioned into four minibatches for gradient updates)
--beta 0 # zero kl regularization https://github.com/volcengine/verl/pull/2775#issuecomment-3131807306
```
训练可以参考该[脚本](https://github.com/modelscope/ms-swift/blob/main/examples/train/grpo/internal/gspo.sh)
@@ -0,0 +1,77 @@
# Rewards as Labels: Revisiting RLVR from a Classification Perspective
作者: [li2zhi](https://github.com/li2zhi)
[Rewards as Labels: Revisiting RLVR from a Classification Perspective](https://arxiv.org/abs/2602.05630) 针对GRPO提出把奖励视为标签,在group内分类而不是计算advantage,从而将策略优化问题转化为分类问题,以此解决GRPO Loss中存在的正样本**梯度错配**与负样本**梯度主导**问题。
## 背景与动机
GRPO目标函数
$$
J_{\mathrm{GRPO}}(\theta)=\mathbb{E}_{q,o\sim\pi_{\mathrm{od}}(\cdot|q)}\left[\frac{1}{|o|}\sum_{t=1}^{|o|}\left(\min\left(\rho_tA_t,\mathrm{clip}(\rho_t,1-\epsilon,1+\epsilon)A_t\right)\right)\right]
$$
其中$\rho_t=\frac{\pi_\theta(o_t|q)}{\pi_{\mathrm{old}}(o_t|q)}$为相对概率,$A_{t}$为优势函数,故梯度为:
$$
\nabla_{\theta} J_{\mathrm{GRPO}} = \mathbb { E } \left[ \frac { 1 } { | o | } \sum _ { t = 1 } ^ { | o | } \mathbb { I } _ { \mathrm { clip } } \cdot A _ { t } e ^ { s _ { t } } \nabla _ { \theta } \log \pi _ { \theta } \left( o _ { t } | q \right) \right]
$$
其中$s_t=\log\frac{\pi_\theta(o_t|q)}{\pi_{\mathrm{old}}(o_t|q)}$作为token的相对对数概率,$\mathbb { I } _ { \mathrm { clip } }$为指示函数
故 GRPO 对单 token 的梯度权重为:
$$
|\mathcal{W}_{\mathrm{GRPO}}|=\left\{ \begin{array} {ll}\left|A\cdot e^s\right|, & \mathrm{if~}\mathbb{I}_{\mathrm{clip}}=1, \\ 0, & \text{otherwise.} \end{array}\right.
$$
![Gradient magnitude visualizations in GRPO](../../../../resources/real.png)
- 正样本的梯度错配(Gradient Misassignment):对正样本来说,随着相对概率$s$变小,梯度更新幅度反而越弱。这违背直觉,因为模型对“不太自信”的正确 token 本来就需要更大的更新幅度来强化,但更多的梯度权重却放到更“自信”的 token,没学好的 token 得不到足够的重视。
- 负样本的梯度主导(Gradient Domination):对负样本来说,随着相对概率$s$变小,梯度更新幅度呈指数级增加。这意味着,只要出现几个模型“盲目自信”的错误 token,它们产生的巨大梯度就会把同组内其他负样本的信号淹没。由于缺乏上限保护,模型在处理这些错误样本时可能会产生过大的参数更新,让训练过程变得不太可控。
为解决上述问题,Real提出将奖励直接视为标签然后进行组内的样本分类训练
![Real Framework](../../../../resources/real_framework.png)
分类的logits分值设计:
$$
\bar{s}^k=\frac{1}{|o^k|}\sum_{t=1}^{|o^k|}\left(\log\frac{\pi_\theta(o_t^k\mid q)}{\pi_{\mathrm{old}}(o_t^k\mid q)}\right)
$$
- $\bar{s}^k > 0$: 表示该样本在当前策略下生成的概率比旧策略整体更高,模型倾向于**增强**该样本。
- $\bar{s}^k < 0$: 表示该样本在当前策略下生成的概率比旧策略整体更低,模型倾向于**抑制**该样本。
损失函数设计:
$$
\mathcal{L}_{REAL}=\log\left(1+\sum_{\mathcal{O}_+}e^{-\bar{s}^i/\tau}\right)+\log\left(1+\sum_{\mathcal{O}_-}e^{\bar{s}^j/\tau}\right)
$$
梯度特性:
$$
|\mathcal{W}_{\mathrm{REAL}}|=
\begin{cases}
\frac{1}{\tau}\frac{1}{1+C_{+}e^{\bar{s}^{k}/\tau}}, & r=1 \\
\\
\frac{1}{\tau}\frac{1}{1+C_{-}e^{-\bar{s}^{k}/\tau}}, & r=0 & & &
\end{cases}
$$
## 参数设置
| 参数 | 类型 | 默认值 | 说明 |
|-------------------|---------|-------|-------------------|
| `--loss_type` | `str` | - | 设置为 `real` |
| `--real_tau` | `float` | `0.5` | 温度参数,控制决策边界锐度 |
训练脚本参考
[swift](https://github.com/modelscope/ms-swift/tree/main/examples/train/grpo/internal/real.sh)
## 注意事项
设置参数时,确保 per_device_train_batch_size 能够被 num_generations 整除,以此保证单个训练batch中能拿到完整的 group 进行分类。
@@ -0,0 +1,87 @@
# REINFORCE++: An Efficient RLHF Algorithm with Robustness to Both Prompt and Reward Models
[REINFORCE++ Baseline](https://arxiv.org/abs/2501.03262) 是 REINFORCE++ 算法的简化版本,适用于 outcome rewardsresponse-level 标量奖励)。它与 GRPO 类似,对每个prompt输入采样多条模型输出,并使用组内 baseline 来估计优势函数,主要区别在于标准化时使用的统计量不同。
## 算法原理
为便于理解,我们基于 GRPOGroup Relative Policy Optimization)算法进行对比说明。
GRPO 和 REINFORCE++ Baseline 都采用组内对比的方式来估计优势函数,主要区别在于:
### 区别1:标准化时使用的统计量不同
**GRPO (Group Relative Policy Optimization)**
对每个 prompt 生成 $G$ 个响应样本,使用**组内所有样本的均值和标准差**进行标准化:
$$
\hat{A}_{i} = \frac{R_i - \text{mean}(\{R_j\}_{j=1}^G)}{\text{std}(\{R_j\}_{j=1}^G)}
$$
当设置 `scale_rewards='batch'` 时,使用**原始奖励的批次 std**:
$$
\hat{A}_{i} = \frac{R_i - \text{mean}(\{R_j\}_{j=1}^G)}{\text{std}(\{R_j\}_{j=1}^{N})}
$$
其中 $N$ 是批次中所有样本数。
**REINFORCE++ Baseline**
对每个 prompt 生成 $G$ 个响应样本,先减去组内均值,再使用**减去组内均值后的奖励**的标准差进行标准化:
$$
\begin{align}
\tilde{A}_{i} &= R_i - \text{mean}(\{R_j\}_{j=1}^G) \\
\hat{A}_{i} &= \frac{\tilde{A}_{i}}{\text{std}(\{\tilde{A}_k\}_{k=1}^{N})}
\end{align}
$$
其中 $N$ 是批次中所有样本数。
**关键区别**
- **GRPO**:标准化时使用**原始奖励 $R$** 的标准差
- **REINFORCE++**:标准化时使用**减去组内均值后的奖励 $\tilde{A}$** 的标准差
### 区别2: KL 散度正则化
与 RLOO 类似,REINFORCE++ Baseline 将 KL 散度整合到奖励项中:
$$
R'_i = R_i - \beta \cdot \text{KL}(\pi_\theta || \pi_{\text{ref}})
$$
其中 $\beta$ 是 KL 散度的权重系数(对应参数 `beta`),$\pi_{\text{ref}}$ 是参考策略。
## 参数设置
我们可以基于 `GRPOTrainer`,通过设置以下参数实现 REINFORCE++ Baseline 训练:
```bash
--advantage_estimator reinforce_plus_plus
--scale_rewards batch
--kl_in_reward true
```
训练可以参考该[脚本](https://github.com/modelscope/ms-swift/tree/main/examples/train/grpo/internal/reinforce_plus_plus.sh)
### 重要参数说明
- **`--advantage_estimator`**:选择优势函数估计方法
- `grpo`(默认):标准化时使用原始奖励的标准差
- `reinforce_plus_plus`:标准化时使用减去组内均值后的奖励的标准差
- **`--kl_in_reward`**:控制 KL 散度正则化项的处理位置
- `false`:KL 散度作为损失函数的独立正则化项(GRPO 默认)
- `true`:KL 散度直接从奖励中扣除(REINFORCE++ 原始实现)
- **`--scale_rewards`**:控制标准化方式
- `group`(默认):组内标准化
- `batch`:全局批次标准化(REINFORCE++原始实现)
- `none`:不进行标准化
- **`--num_generations`**:每个 prompt 生成的样本数量 $G$
- **`--beta`**KL 散度正则化系数 $\beta$
其他参数参考 [GRPO参数](../../Command-line-parameters.md#grpo参数)
@@ -0,0 +1,92 @@
# REINFORCE Leave-One-Out (RLOO)
[REINFORCE Leave-One-Out (RLOO)](https://arxiv.org/abs/2402.14740) 基于经典的 REINFORCE 策略梯度方法,通过留一法(Leave-One-Out)构造无偏的优势函数基线。
## 算法原理
为便于理解,我们基于 GRPOGroup Relative Policy Optimization)算法进行对比说明。
GRPO 和 RLOO 都采用组内对比的方式来估计优势函数,避免了全局基线估计带来的高方差问题。两者的核心区别主要体现在以下两个方面:
### 区别1:优势函数基线的构造方法
**1. GRPO (Group Relative Policy Optimization)**
GRPO 对每个 prompt 生成 $G$ 个响应样本,使用**组内所有样本的均值和标准差**进行标准化:
$$
\hat{A}_{i} = \frac{R_i - \text{mean}(\{R_j\}_{j=1}^G)}{\text{std}(\{R_j\}_{j=1}^G)}
$$
其中:
- $R_i$ 是第 $i$ 个样本的奖励值
- $\text{mean}(\{R_j\}_{j=1}^G) = \frac{1}{G}\sum_{j=1}^G R_j$ 是组内均值
- $\text{std}(\{R_j\}_{j=1}^G)$ 是组内标准差
**2. RLOO (REINFORCE Leave-One-Out)**
RLOO 对每个 prompt 生成 $K$ 个响应样本,使用 **留一法(Leave-One-Out** 构造基线,即第 $i$ 个样本的基线为除自己外的其他 $K-1$ 个样本的均值:
$$
\hat{A}_{i} = R_i - \frac{1}{K-1}\sum_{j \neq i} R_j
$$
这个公式可以等价地改写为:
$$
\hat{A}_{i} = \frac{K}{K-1} \left(R_i - \bar{R}\right)
$$
其中 $\bar{R} = \frac{1}{K}\sum_{j=1}^K R_j$ 是组内所有样本的均值。
> **说明**:这里使用 $K$ 对齐论文符号,与 GRPO 中的 $G$ 含义一致,均对应配置参数 `num_generations`
**为什么使用留一法?**
留一法的关键优势在于**无偏性**。对于第 $i$ 个样本,其奖励 $R_i$ 和基线 $\frac{1}{K-1}\sum_{j \neq i} R_j$ 是独立的,因此优势估计是无偏的。相比之下,如果使用包含自身的均值作为基线,会引入偏差。
### 区别2:KL 散度正则化项的处理方式
为防止策略偏离参考策略过远,两种算法都引入了 KL 散度正则化,但处理方式不同:
**GRPO**:将 KL 散度作为独立的正则化项添加到[损失函数](../GetStarted/GRPO.md#算法原理)中:
$$
\mathcal{L}(\theta) = -\mathbb{E}\left[\hat{A}_i \log \pi_\theta(a_i|s_i)\right] + \beta \cdot \text{KL}(\pi_\theta || \pi_{\text{ref}})
$$
**RLOO**:将 KL 散度直接整合到奖励项中,构造修正后的奖励:
$$
R'_i = R_i - \beta \cdot \text{KL}(\pi_\theta || \pi_{\text{ref}})
$$
其中 $\beta$ 是 KL 散度的权重系数(对应参数 `beta`),$\pi_{\text{ref}}$ 是参考策略(通常是 SFT 模型或初始策略)。
## 参数设置
我们可以基于 `GRPOTrainer`,通过设置以下参数实现 RLOO 训练:
```bash
# 基本 RLOO 配置
--advantage_estimator rloo # 使用 RLOO 的留一法优势函数计算
--kl_in_reward true # 将 KL 散度项整合到奖励中(RLOO 默认方式)
```
训练可以参考该[脚本](https://github.com/modelscope/ms-swift/tree/main/examples/train/grpo/internal/rloo.sh)
### 重要参数说明
- **`--advantage_estimator`**:选择优势函数估计方法
- `grpo`(默认):使用组内均值和标准差进行标准化
- `rloo`:使用留一法(Leave-One-Out)构造基线
- **`--kl_in_reward`**:控制 KL 散度正则化项的处理位置
- `false`:KL 散度作为损失函数的独立正则化项(GRPO 方式)
- `true`:KL 散度直接从奖励中扣除,构造修正后的奖励(RLOO 方式)
- **`--num_generations`**:每个 prompt 生成的样本数量 $K$
- **`--beta`**KL 散度正则化系数 $\beta$
- 控制策略更新的保守程度
其他参数与 [GRPO参数](../../Command-line-parameters.md#grpo参数)一致
@@ -0,0 +1,91 @@
# Soft Adaptive Policy Optimization (SAPO)
[Soft Adaptive Policy Optimization (SAPO)](https://arxiv.org/abs/2511.20347) 针对 GRPO 中硬裁剪(hard clipping)带来的问题,提出了一种基于温度控制的软门控(soft gate)机制,用于平滑地衰减离策略更新,同时保留有用的学习信号。
## 背景与动机
在强化学习训练 LLM 时,GRPO 通过计算 token 级别的重要性采样比(Importance Sampling Ratio)来处理 off-policy 训练:
$$
r_t = \frac{\pi_\theta(y_t|x, y_{<t})}{\pi_{\theta_{\mathrm{old}}}(y_t|x, y_{<t})}
$$
然而,token 级别的重要性采样比往往表现出高方差,这一现象在以下情况下可能更严重:
- **长文本生成**
- **MoE 模型的路由异质性**:采样时的 old-policy 模型与训练模型可能使用不同的专家路由,导致 logps 差异显著放大
为此,GRPO 通过硬裁剪来限制策略更新的幅度:
$$
L^{\mathrm{GRPO}} = -\min\left( r_t \cdot A, \mathrm{clip}(r_t, 1-\epsilon, 1+\epsilon) \cdot A \right)
$$
**硬裁剪的困境**:硬裁剪难以在稳定性和学习效率之间取得平衡——裁剪范围过严格会限制有效样本的数量,而过宽松则会引入离策略样本的噪声梯度,导致训练不稳定。
## SAPO 方法
SAPO 使用温度控制的 sigmoid 软门控函数替代硬裁剪,实现平滑的梯度衰减。
### 软门控函数
SAPO 的核心是使用 sigmoid 函数对重要性采样比进行软门控:
对于正向优势($A > 0$),使用正向门控:
$$
g^{+}_t = \sigma\left( \tau_{\mathrm{pos}} \cdot (r_t - 1) \right) \cdot \frac{4}{\tau_{\mathrm{pos}}}
$$
对于负向优势($A < 0$),使用负向门控:
$$
g^{-}_t = \sigma\left( \tau_{\mathrm{neg}} \cdot (r_t - 1) \right) \cdot \frac{4}{\tau_{\mathrm{neg}}}
$$
其中:
- $\sigma(\cdot)$ 是 sigmoid 函数
- $\tau_{\mathrm{pos}}$ 和 $\tau_{\mathrm{neg}}$ 是温度参数,控制门控函数的斜率
- $r_t$ 是重要性采样比
### SAPO 损失函数
$$
L^{\mathrm{SAPO}} = -g_t \cdot A
$$
其中 $g_t = g^{+}_t$ 当 $A > 0$$g_t = g^{-}_t$ 当 $A < 0$。
### 温度参数
温度参数 $\tau$ 控制软门控函数的衰减速率,数值越大,衰减越快。
![tau curve](../../../../resources/sapo_tau.png)
论文指出正向优势会提升采样token的logit,并降低所有未采样token的logit;负向优势相反,提高许多未采样token的logit,可能会扩散到大量无关token上,带来一定的不稳定性。所以论文推荐设置温度 $\tau_\text{neg} > \tau_\text{pos}$,来使负向奖励的token梯度衰减更快,提升训练的稳定性和性能。
论文默认推荐 $\tau_{\mathrm{pos}} = 1.0$$\tau_{\mathrm{neg}} = 1.05$。
## 参数设置
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `--loss_type` | `str` | - | 设置为 `sapo` |
| `--tau_pos` | `float` | `1.0` | 正向优势的温度参数,控制门控斜率 |
| `--tau_neg` | `float` | `1.05` | 负向优势的温度参数,控制门控斜率 |
```bash
swift rlhf \
--rlhf_type grpo \
--loss_type sapo \
--tau_pos 1.0 \
--tau_neg 1.05 \
# ... 其他参数
```
训练脚本参考
- [swift](https://github.com/modelscope/ms-swift/blob/main/examples/train/grpo/internal/sapo.sh)
- [megatron swift](https://github.com/modelscope/ms-swift/blob/main/examples/megatron/grpo/sapo.sh)
> SAPO 的软门控机制仅在 off-policy 训练下生效。
> SAPO 中的重要性采样粒度为 token 级别(即 importance_sampling_level 默认设置为 token),与 GSPO 冲突。
@@ -0,0 +1,95 @@
# DeepEyes: Incentivizing "Thinking with Images" via Reinforcement Learning
## 原理介绍
[DeepEyes论文](https://arxiv.org/abs/2505.14362) 提出了一种利用强化学习使模型具备“think with images”(以图辅助思考)能力的方法。该方法通过端到端的强化学习,模型能力自发涌现,无需额外的 SFT(监督微调)过程。模型内置图像定位能力,能够主动调用“图像放大工具”:在推理过程中,模型会自动选取图片中的具体区域进行放大和裁剪,将处理后的区域信息进行进一步推理,实现视觉与文本的链式推理。
![DeepEyes Overview](../../../../resources/deepeyes.png)
## 最佳实践
**数据集下载与注册**
下载 DeepEyes 官方训练数据集到本地
```bash
# modelscope
modelscope download --dataset Lixiang/ChenShawn-DeepEyes-Datasets-47k
# huggingface
huggingface-cli download ChenShawn/DeepEyes-Datasets-47k --repo-type=dataset
```
数据集内有三个parquet文件,`swift/dataset/data/dataset_info.json` 文件中分别进行注册,将数据集中的 `prompt` 列重命名为 `messages`
```json
{
"ms_dataset_id": "path/to/data_0.1.2_visual_toolbox_v2.parquet",
"columns": {
"prompt": "messages"
}
},
{
"ms_dataset_id": "path/to/data/data_thinklite_reasoning_acc.parquet",
"columns": {
"prompt": "messages"
}
},
{
"ms_dataset_id": "path/to/data/data_v0.8_visual_toolbox_v2.parquet",
"columns": {
"prompt": "messages"
}
}
```
在本地注册论文中所用到的奖励函数和工具调用逻辑,实现可以参考[DeepEyes实现示例](https://github.com/modelscope/ms-swift/tree/main/examples/train/grpo/plugin/deepeyes/deepeyes_plugin.py)
**部署验证模型**
Deepeyes 的奖励函数依赖生成式奖励模型对模型生成结果与标准答案进行对比评估,为了加速这一环节,推荐对模型进行部署。
假设使用 Qwen2.5-VL-72B-Instruct 模型进行评估,参考以下部署命令
```bash
# 4*80G
CUDA_VISIBLE_DEVICES=0,1,2,3 \
swift deploy \
--model Qwen/Qwen2.5-VL-72B-Instruct \
--infer_backend vllm \
--vllm_tensor_parallel_size 4 \
```
在 plugin 文件中,使用OpenAI接口进行调用,参考[奖励模型文档](../DeveloperGuide/reward_model.md#外部部署)
训练参考该[脚本](https://github.com/modelscope/ms-swift/tree/main/examples/train/grpo/plugin/deepeyes/deepeyes.sh)
## 实现细节
[DeepEyes实现示例](https://github.com/modelscope/ms-swift/tree/main/examples/train/grpo/plugin/deepeyes/deepeyes_plugin.py)参考[官方实现](https://github.com/Visual-Agent/DeepEyes/blob/main/verl/utils/reward_score/vl_agent.py) 给出了 DeepEyes 训练插件的样例代码,涵盖了奖励函数与多轮交互调用的相关逻辑。
**数据集数据**如下
| 数据集文件名 | data_source | 对应评分函数 | 工具调用 |
|------------------------------------------|-----------------------|----------------------------------|------------------|
| data_v0.8_visual_toolbox_v2.parquet | chart | vl_agent.compute_score | True (image_zoom_in_tool) |
| data_0.1.2_visual_toolbox_v2.parquet | vstar | vl_agent.compute_score | True (image_zoom_in_tool) |
| data_thinklite_reasoning_acc.parquet | thinklite_eureka | vl_agent.compute_score_math | False |
**注意**:多模态大模型在处理图像输入时,可能会对图像进行预处理(例如受 max_pixels 参数限制的裁剪或缩放等操作)。当调用图像放大工具 image_zoom_in_tool 时,模型会根据输入图像输出裁剪后的 bbox。因此,在调用图像放大工具时,需要确保输入的是经过预处理后的图像。示例代码展示了 Qwen2.5-VL 系列模型的实现方式:
```python
from qwen_vl_utils import fetch_image
# 这里的images尚未经过图像处理
infer_request.images
# 通过加载为PIL.Image格式,进行裁剪(使用环境变量MAX_PIXELS时的处理)
img = fetch_image({'image': load_pil_image(infer_request.images[0])})
```
**工具奖励**
论文中指出当最终答案正确,且轨迹至少使用一个工具时给予工具奖励。为了避免模型生成的工具调用是无效的,我们通过图像数量而不是`<tool_call>` 等token进行判断。
```python
tool_reward = 1.0 if num_image > 1 and acc_reward > 0.5 else 0.0
```
@@ -0,0 +1,30 @@
# Beyond the 80/20 Rule: High-Entropy Minority Tokens Drive Effective Reinforcement Learning for LLM Reasoning
[论文](https://arxiv.org/abs/2506.01939)发现在以 RLVR等方法训练大型语言模型推理能力时,驱动学习进步的关键在于一小部分高熵“少数 token”,而并非大多数信息熵低的 token。
论文指出,在模型推理的 token 分布中,只有极少数信息熵较高的 token 起到了主导作用。这些 token 往往出现在推理和决策路径分歧最大的关键节点(如 "wait"、"since" 等),决定了模型能否习得复杂推理任务。而大多数熵低的 token 对模型推理能力的提升作用有限。论文提出只对高熵 token 计算策略梯度、舍弃低熵 token 的梯度。
token 熵公式如下
$
H_t := -∑_{j=1}^{V} p_{t,j} \log p_{t,j}, \qquad where (p_{t,1}, ···, p_{t,V}) = \mathbf{p}_t = π_θ(\cdot | \mathbf{q}, \mathbf{o}_{<t}) = \text{Softmax}(\frac{\mathbf{z}_t}{T})
$
其中
- $\pi_\theta$:参数为 $\theta$ 的模型
- $\mathbf{q}$:输入查询(input query)。
- $\mathbf{o}_{<t} = (o_1, o_2, \cdots, o_{t-1})$:时间步 $t$ 之前已生成的 token 序列。
- $V$:词表大小(vocabulary size)。
- $\mathbf{z}_t \in \mathbb{R}^V$:时间步 $t$ 的 pre-softmax 逻辑值(logits)。
- $\mathbf{p}_t \in \mathbb{R}^V$:模型对词表的概率分布。
- $T \in \mathbb{R}$:解码温度(decoding temperature),控制分布的平滑程度。
熵的计算对象:$H_t$ 是 token 生成分布 $\mathbf{p}_t$ 的熵,用于衡量训练策略 $\pi_\theta$ 在给定上下文 $(\mathbf{q}, \mathbf{o}_{<t})$ 下的不确定性。
> "Token entropy" $H_t$ 始终指向位置 $t$ 的生成分布 $\mathbf{p}_t$ 的不确定性,而非 token $o_t$ 本身的属性。即$H_t$ 是位置 $t$ 对应分布 $\mathbf{p}_t$ 的熵,与采样得到的 token $o_t$ 无关。
在实践中,我们可以在 GRPO 训练中通过参数 `top_entropy_quantile` 控制训练范围。论文实验设置该参数为 0.2,即每次仅对处于熵分布前 20% 的 token 进行训练优化。
同时使用参数`log_entropy`,可以记录训练过程中的熵值变化,参考[文档](../GetStarted/GRPO.md#logged-metrics)
@@ -0,0 +1,19 @@
Advanced Research
===============
.. toctree::
:maxdepth: 1
entropy_mask.md
CISPO.md
DAPO.md
deepeyes.md
FIPO.md
GSPO.md
CHORD.md
RLOO.md
REINFORCEPP.md
REAL.md
router_replay.md
SAPO.md
training_inference_mismatch.md
treepo.md
@@ -0,0 +1,104 @@
# Router Replay (R2/R3)
**TL;DR**: 在 MoE 模型的 RL 训练中,训练引擎与推理引擎之间的路由(expert routing)不一致会显著放大训推偏差,甚至导致训练崩溃。Router Replay 通过在训练前向传播中回放固定的路由掩码来消除这一不一致。根据回放来源的不同,分为 R2Vanilla Routing Replay)和 R3Rollout Routing Replay)两种策略。
## Background
### MoE RL 中的三个策略
在 MoE 模型的 GRPO 训练中,存在三个不同阶段的策略,它们共享相同的模型权重,但路由行为可能不同:
| 策略 | 符号 | 路由结果 | 说明 |
|------|------|---------|------|
| **训练策略** | $\pi_\theta$ | $e^{\pi}_t$ | 梯度更新中的模型 |
| **Old Policy** | $\pi_{\theta_{\text{old}}}$ | $e^{\pi}_{\text{old},t}$ | 批次更新前的模型状态 |
| **Rollout Policy** | $\mu_{\theta_{\text{old}}}$ | $e^{\mu}_{\text{old},t}$ | 推理引擎(如vLLM)中的采样策略,权重与 old policy 相同,但 kernel 实现、精度等差异导致路由不同 |
其中,$\pi_{\theta_{\text{old}}}$ 和 $\mu_{\theta_{\text{old}}}$ 的权重在采样时完全一致,但由于推理引擎与训练引擎的实现差异(如算子实现),即使输入相同,路由结果也可能不同。
### 训推不一致的分解
根据 [论文](https://arxiv.org/abs/2507.18071) 的分析,token 级重要性采样比可以分解为两个因子:
$$
\frac{\pi_\theta(y_t|x, y_{<t})}{\mu_{\theta_{\text{old}}}(y_t|x, y_{<t})} = \underbrace{\frac{\pi_{\theta_{\text{old}}}(y_t|x, y_{<t})}{\mu_{\theta_{\text{old}}}(y_t|x, y_{<t})}}_{\text{training-inference discrepancy}} \times \underbrace{\frac{\pi_\theta(y_t|x, y_{<t})}{\pi_{\theta_{\text{old}}}(y_t|x, y_{<t})}}_{\text{policy staleness}}
$$
对于 MoE 模型,专家路由与这两个因子深度耦合:
- **Training-inference discrepancy**:训练引擎和推理引擎的路由不一致($e^{\pi}_{\text{old},t} \neq e^{\mu}_{\text{old},t}$),放大输出差异
- **Policy staleness**:随着 mini-batch 更新,路由也随之漂移($e^{\pi}_t \neq e^{\pi}_{\text{old},t}$),进一步偏离采样时的策略
## R2: Vanilla Routing Replay
R2 的核心思想是在梯度更新时,**回放 old policy 在训练引擎中确定的路由**$e^{\pi}_{\text{old},t}$)。
### 原理
在训练前向传播中,先用 old policy 的权重做一次前向,记录每一层 MoE Router 选择的 expert indices,然后在训练模型 $\pi_\theta$ 的前向传播中强制使用这些 indices:
$$
g_{\text{replay},i} = \frac{I^{\pi}_{\text{old},i} \cdot \exp(s_{\text{train},i})}{\sum_j I^{\pi}_{\text{old},j} \cdot \exp(s_{\text{train},j})}
$$
其中 $I^{\pi}_{\text{old}}$ 是 old policy 的路由掩码,$s_{\text{train}}$ 是训练模型计算的 router logits。softmax 仍作用于训练 logits,因此梯度可以正常回传到 router 权重。
### 特性
| 场景 | 行为 |
|------|------|
| **首个 mini-batch**on-policy | $\theta = \theta_{\text{old}}$,故 $e^{\pi}_t = e^{\pi}_{\text{old},t}$**target policy 不变**(无 bias |
| **后续 mini-batch**off-policy | $\theta \neq \theta_{\text{old}}$,故 $e^{\pi}_t \neq e^{\pi}_{\text{old},t}$**target policy 被改变**(有 bias),但 policy staleness 被控制 |
## R3: Rollout Routing Replay
R3 的核心思想是在训练前向传播中,**回放 rollout policy 在推理引擎中确定的路由**$e^{\mu}_{\text{old},t}$)。
### 原理
在推理引擎(如 vLLM)采样时,额外记录每个 token 在每一层 MoE Router 的 expert indices(路由掩码),然后将这些掩码传递到训练引擎,在 $\pi_\theta$ 的前向传播中强制使用:
$$
g_{\text{replay},i} = \frac{I^{\mu}_{\text{old},i} \cdot \exp(s_{\text{train},i})}{\sum_j I^{\mu}_{\text{old},j} \cdot \exp(s_{\text{train},j})}
$$
### 与其他方法的兼容性
- R3 与 **GSPO** 正交,组合使用可进一步提升
- R3 与 **TIS** 组合不一定有增益(R3 已从根源消除不一致,TIS 的额外修正可能多余)
- Router Replay 与 **Clipping** 在 off-policy 训练中缺一不可
## Router Mask Caching
R3 论文还提出路由掩码可以与 KV Cache 一起缓存:对于相同的前缀 token,MoE Router 的输出是确定性的,因此路由掩码可以随 prefix KVCache 一起存储和复用。这在多轮 Agent 场景(tool calling)中尤为重要,避免了重新 prefill 前缀来获取路由掩码,整体 rollout 延迟开销 < 3%。
## Swift 实现
### 参数设置
通过 `--router_replay_mode` 参数选择路由回放策略:
| 参数值 | 说明 |
|--------|------|
| `disabled`(默认) | 不启用路由回放 |
| `R2` | Vanilla Routing Replay:在训练引擎中记录 old policy 路由并回放 |
| `R3` | Rollout Routing Replay:从推理引擎导出路由掩码并在训练中回放 |
环境依赖:
- R3 需要 vLLM ≥ 0.14.0 以支持返回 `routed_experts` 信息。
- 当前 Router Replay 仅在 Megatron backend 下可用,需要 megatron-core ≥ 0.16.0。
### 与训推校正的关系
Router Replay 与 [Training-Inference Mismatch](training_inference_mismatch.md) 中介绍的重要性采样校正(IS correction)是互补的:
- **IS correction**:在 loss 层面通过权重修正训推概率偏差
- **Router Replay**:在模型结构层面通过固定路由消除偏差来源
## 参考资料
1. [Stabilizing MoE Reinforcement Learning by Aligning Training and Inference Routers](https://arxiv.org/abs/2510.11370)
2. [Group Sequence Policy Optimization](https://arxiv.org/abs/2507.18071)
3. [Stabilizing Reinforcement Learning with LLMs: Formulation and Practices](https://arxiv.org/abs/2512.01374)
4. [Megatron Core Router Replay Design Document](https://docs.nvidia.com/megatron-core/developer-guide/nightly/api-guide/router_replay.html)
@@ -0,0 +1,239 @@
# Training-Inference-Mismatch
**TL;DR**: GRPO 引入 vLLM 加速采样过程的同时,也引入了训练-推理不一致(Training-Inference Mismatch)的问题,从而可能影响训练稳定性。本文将解释这个问题的背景、原因以及相应的解决方案。
## Background
### GRPO 的基本假设
GRPO (Group Relative Policy Optimization) 的训练目标可以表示为:
$$
\mathcal{L}_{\text{GRPO}} = - \mathbb{E}_{y \sim \pi_\theta} \left[ \min \left( r_t(\theta) \hat{A}_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) \hat{A}_t \right) \right]
$$
其中:
- $r_t(\theta) = \frac{\pi_\theta(y_t|x, y_{<t})}{\pi_{\theta_{\text{old}}}(y_t|x, y_{<t})}$ 是重要性采样比
- $\hat{A}_t$ 是优势函数(advantage),基于 reward 和 group baseline 计算
- $\epsilon$ 是 clipping 参数
**核心假设**:样本 $y$ 是从策略 $\pi_\theta$ 中采样得到的。在实际训练中,这意味着:
1. 采样模型(rollout model)与训练模型(policy model)应当是**同一个模型** $\pi_\theta$
2. 两个模型的概率分布应当**完全一致**,即 $\pi_{\text{rollout}} = \pi_\theta$
### 引入 vLLM 后的假设偏离
GRPO 的训练速度很大程度上受到采样过程(rollout)的速度制约。为了加速,训练框架引入高效推理引擎(如 vLLM)来执行采样。**理想假设**是:通过权重同步,vLLM 与训练模型保持一致,即 $\pi_{\text{vLLM}} \equiv \pi_\theta$。
然而,在实践中,即使权重完全同步,由于算子实现等差异,两者的概率分布仍然存在偏差:
$$
\pi_{\text{vLLM}}(y|x) \neq \pi_\theta(y|x)
$$
此时,实际的训练目标变为:
$$
\mathcal{L} = - \mathbb{E}_{y \sim \pi_{\text{vLLM}}} \left[ \min \left( r_t(\theta) \hat{A}_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) \hat{A}_t \right) \right]
$$
其中样本来自 $\pi_{\text{vLLM}}$,但梯度是基于 $\pi_\theta$ 计算的,这**破坏了算法的 on-policy 假设**,引入了训推不一致的问题。
## Solution
针对训推不一致问题,可以引入**重要性采样(Importance Sampling, IS**的校正机制。
### 重要性采样校正
重要性采样的基本思想是:当样本来自分布 $q$ 而非目标分布 $p$ 时,可以通过引入权重来修正期望的计算:
$$
\mathbb{E}_{x \sim p} [f(x)] = \mathbb{E}_{x \sim q} \left[ \frac{p(x)}{q(x)} \cdot f(x) \right]
$$
应用到 GRPO 的场景,修正后的损失函数为:
$$
\mathcal{L}_{\text{corrected}} = - \mathbb{E}_{y \sim \pi_{\text{vLLM}}} \left[ w(x, y) \cdot \min \left( r_t(\theta) \hat{A}_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) \hat{A}_t \right) \right]
$$
其中 $w(x, y)$ 是重要性采样权重,用于校正 vLLM 与训练模型之间的分布偏差
重要性采样权重可以在不同粒度上计算和应用:
1. **Token-Level**
每个 token 上计算重要性采样比:
$$
w_{i,t}^{\text{token}} = \frac{\pi_\theta(y_{i,t}|x, y_{i,<t})}{\pi_{\text{vLLM}}(y_{i,t}|x, y_{i,<t})}
$$
2. **Sequence-Level**
计算序列级别的重要性采样比,然后广播到每个 token:
$$
w_i^{\text{seq}} = \left[ \frac{\pi_\theta(y_i|x)}{\pi_{\text{vLLM}}(y_i|x)} \right]^{\frac{1}{|y_i|}} = \exp\left( \frac{1}{|y_i|} \sum_{t=1}^{|y_i|} \log \frac{\pi_\theta(y_{i,t}|x, y_{i,<t})}{\pi_{\text{vLLM}}(y_{i,t}|x, y_{i,<t})} \right)
$$
### 稳定性控制:Truncate vs. Mask
过大的重要性采样权重会导致梯度爆炸,破坏训练稳定性。因此需要对权重进行控制:
#### 1. Truncate(截断)
将重要性采样权重截断到 $[0, \tau]$ 区间:
$$
w_{\text{truncate}} = \min(w, \tau)
$$
该方法保留所有样本,但限制其影响范围。
#### 2. Mask(屏蔽)
舍弃权重超过阈值的 token/sequence 数据
$$
w_{\text{mask}} = \begin{cases}
w & \text{if } w \leq \tau \\
0 & \text{otherwise}
\end{cases}
$$
### 四种校正模式
结合粒度和控制策略,共设置四种校正模式(通过 `--rollout_importance_sampling_mode` 参数选择):
| 模式 | 说明 |
|------|------|
| `token_truncate` | Token 级截断 |
| `token_mask` | Token 级屏蔽 |
| `sequence_truncate` | Sequence 级截断 |
| `sequence_mask` | Sequence 级屏蔽 |
其中阈值通过 `--rollout_importance_sampling_threshold` 参数设置。
## Metrics
为了监控训练中训推不一致的程度,我们在log中加入以下指标(前缀为 `rollout_correction/`):
### 1. KL 散度(KL Divergence
KL 散度衡量 rollout 策略与训练策略之间的偏离程度。两个指标都估计 $\text{KL}(\pi_{\text{vLLM}} \| \pi_\theta)$
**直接估计器 `kl`**
$$
\text{KL}(\pi_{\text{vLLM}} \| \pi_\theta) = \mathbb{E}_{\pi_{\text{vLLM}}}\left[ \log \frac{\pi_{\text{vLLM}}}{\pi_\theta} \right]
$$
**K3 估计器 `k3_kl`**
$$
\text{KL}(\pi_{\text{vLLM}} \| \pi_\theta) \approx \mathbb{E}_{\pi_{\text{vLLM}}}\left[ \rho - \log \rho - 1 \right], \quad \rho = \frac{\pi_\theta}{\pi_{\text{vLLM}}}
$$
K3 估计器在 KL 值较小时数值更稳定,且始终非负。
### 2. Perplexity (PPL)
困惑度衡量模型对序列的预测不确定性:
$$
\text{PPL} = \exp\left( -\frac{1}{|y|} \sum_{t=1}^{|y|} \log p(y_t) \right)
$$
相关指标:
- `training_ppl` / `training_log_ppl`:训练策略的 PPL 及其对数
- `rollout_ppl` / `rollout_log_ppl`rollout 策略的 PPL 及其对数
- `log_ppl_diff`:log PPL 差异,正值表示训练策略分配的概率更低
- `log_ppl_abs_diff`log PPL 绝对差异
- `log_ppl_diff_max` / `log_ppl_diff_min`log PPL 差异的最大/最小值
- `ppl_ratio`PPL 比率 $\frac{\text{PPL}_{\text{training}}}{\text{PPL}_{\text{rollout}}}$
### 3. χ² 散度(Chi-squared Divergence
χ² 散度衡量重要性采样权重的方差:
$$
\chi^2(\pi_\theta \| \pi_{\text{vLLM}}) = \mathbb{E}_{\pi_{\text{vLLM}}}\left[ \rho^2 \right] - 1, \quad \rho = \frac{\pi_\theta}{\pi_{\text{vLLM}}}
$$
- `chi2_token`Token 级别 χ² 散度,$\mathbb{E}[\rho_t^2] - 1$
- `chi2_seq`:Sequence 级别 χ² 散度(基于几何平均),$\mathbb{E}[\rho_{\text{geo}}^2] - 1$,其中 $\rho_{\text{geo}} = \exp(\frac{1}{T}\sum_t \log \rho_t)$
χ² 散度越大,表示 IS 权重方差越大,训练越不稳定。`chi2_seq` 使用几何平均而非乘积,使其与 `chi2_token` 在量级上可比较。
### 4. Effective Sample Size (ESS)
有效样本大小衡量重要性采样后实际起作用的样本数量:
$$
\text{ESS} = \frac{1}{\mathbb{E}\left[\left(\frac{w}{\mathbb{E}[w]}\right)^2\right]}
$$
ESS 值越大(接近1),表示重要性采样权重分布越均匀,样本的有效利用率越高。当所有权重相等时(on-policy),ESS = 1;当权重差异很大时(严重 off-policy),ESS 会很小。
### 5. IS 权重统计
- `is_weight_mean`:平均重要性采样权重,理想值为 1.0
- `clipped_frac`:被截断或屏蔽的样本比例
## 使用方式
### 仅记录诊断指标
如果只想监控训推不一致的程度,而不启用重要性采样校正,可以设置:
```
--log_rollout_offpolicy_metrics true
```
这将记录上述所有诊断指标(KL、PPL、χ² 等),但不会对损失函数进行任何修正。
### 启用重要性采样校正
在GRPO训练中,设置以下参数启用校正机制:
```
--rollout_importance_sampling_mode (默认为None
--rollout_importance_sampling_threshold (默认为2
```
当设置了 `rollout_importance_sampling_mode` 时,诊断指标会自动记录,无需额外设置 `log_rollout_offpolicy_metrics`
## Off-Policy Sequence Masking
除了重要性采样校正外,还可以使用 **Off-Policy Sequence Masking** 技术来处理训推不一致问题。该技术来自 [DeepSeek-V3.2 论文](https://arxiv.org/abs/2512.02556)。
### 原理
Off-Policy Sequence Masking 的核心思想是:当当前策略相对于旧策略(rollout 或 old policy)发生较大偏移时,直接丢弃(mask)该序列,不参与损失计算。这种方法特别针对**优势为负的序列**,因为这些序列在策略偏移较大时更容易导致训练不稳定。
具体来说,对于每个序列,计算:
$$
\delta_i = \frac{1}{|y_i|} \sum_{t=1}^{|y_i|} \bigl( \log \pi_{\text{old}}(y_{i,t}|x, y_{i,<t}) - \log \pi_\theta(y_{i,t}|x, y_{i,<t}) \bigr)
$$
当满足以下条件时,序列 $i$ 将被 mask 掉(平均只在 completion token 上计算,即 `completion_mask=1` 的位置):
1. $\delta_i > \tau$
2. **且** $\hat{A}_i < 0$
其中:
- $\pi_{\text{old}}$ 优先使用 `rollout_per_token_logps`rollout/行为策略的 logprobs),若不存在则使用 `old_per_token_logps`
- $\tau$ 是用户设置的阈值(`--off_policy_sequence_mask_delta`,默认 None 表示关闭)
参考资料
1. https://yingru.notion.site/When-Speed-Kills-Stability-Demystifying-RL-Collapse-from-the-Training-Inference-Mismatch-271211a558b7808d8b12d403fd15edda
2. https://fengyao.notion.site/off-policy-rl
3. https://github.com/volcengine/verl/blob/main/verl/trainer/ppo/rollout_corr_helper.py
4. [DeepSeek-V3.2: Pushing the Frontier of Open Large Language Models](https://arxiv.org/abs/2512.02556)
@@ -0,0 +1,35 @@
# TreePO: Bridging the Gap of Policy Optimization and Efficacy and Inference Efficiency with Heuristic Tree-based Modeling
作者: [li2zhi](https://github.com/li2zhi)
## 原理介绍
[TreePO论文](https://arxiv.org/abs/2508.17445) 提出了一种树状结构建模方法。该方法将序列生成组织为分段式的树结构搜索,通过动态分支、回退与提前终止机制,显著提高KV缓存复用率,从而降低计算开销,同时保持甚至增强了探索的多样性。
![TreePO Overview](../../../../resources/treepo.png)
## 实现细节
[TreePO实现示例](https://github.com/modelscope/ms-swift/tree/main/examples/train/grpo/plugin/treepo/tree_rollout_plugin.py)参考[官方实现](https://github.com/multimodal-art-projection/TreePO/blob/main/recipe/treepo/vllm_rollout_tree.py) 给出了 TreePO 训练插件的样例代码,涵盖了多轮交互、终止判断,与分支回退等相关逻辑。
**注意**:在实际使用中,你需要根据自己的场景需求,重写step、check_finished等方法的逻辑,以确保其能够在自定义场景下按照预期执行。而关于自定义奖励的设计与使用,你可以参考[DeepEyes](https://github.com/modelscope/ms-swift/tree/main/examples/train/grpo/plugin/deepeyes/deepeyes_plugin.py)的实现。
训练参考该[脚本](https://github.com/modelscope/ms-swift/tree/main/examples/train/grpo/plugin/treepo/tree_rollout.sh)
## 测试数据
> model: Qwen/Qwen2.5-0.5B
> dataset: AI-MO/NuminaMath-TIR
> subset size: 1,000 samples
> 1 GPU for training, 1 GPU for inference
| \ | batch_size | num_generation | max_tree_depth | global_step | total inference calls | saving ratio | train_speed(iter/s) | improvement rate |
| ----------------------- | ---------- | -------------- | -------------- | ----------- | --------------------- | ------------ | ------------------- | ---------------- |
| original implementation | 8 | 8 | 4 | 200 | 5965 | 0.00% | 0.292436 | 0.00% |
| tree(max_divergence=3) | 8 | 8 | 4 | 200 | 3678 | 38.34% | 0.31819 | 8.81% |
| | | | | | | | | |
| original implementation | 8 | 8 | 5 | 105 | 4312 | 0.00% | 0.261324 | 0.00% |
| tree(max_divergence=2) | 8 | 8 | 5 | 105 | 2513 | 52.69% | 0.336639 | 28.82% |
| tree(max_divergence=3) | 8 | 8 | 5 | 105 | 2990 | 30.66% | 0.308791 | 18.16% |
| | | | | | | | | |
| original implementation | 8 | 8 | 6 | 105 | 5202 | 0.00% | 0.24832 | 0.00% |
| tree(max_divergence=2) | 8 | 8 | 6 | 105 | 3348 | 35.64% | 0.27755 | 11.77% |
| tree(max_divergence=3) | 8 | 8 | 6 | 105 | 3888 | 25.26% | 0.272339 | 9.67% |