注:本文包含 AI 辅助创作
- 参考链接:
整体说明
- “Rollout Correction” 包含两种可同时存在的修正组件:
- 重要性采样(IS,Importance Sampling)权重,修正 off-policy 偏差
- 拒绝采样(RS,Rejection Sampling),过滤异常样本
- 内部变量
rollout_is_weights特指 IS 权重组件 - Rollout Correction(Rollout 校正)提供了一个统一的框架,用于处理 RL 训练中的一般 off-policy 问题
- 任何数据收集分布与训练分布不同的场景都可以从这些方法中受益
常见的 off-policy 场景:
- 1)策略不匹配(实现差异)
- 不同精度:FP8 vs FP16 vs BF16 vs FP32
- 不同后端:vLLM vs SGLang vs FSDP vs Megatron
- 即使权重相同,不同实现也会有差异
- 2)时间滞后(模型陈旧性)
- Rollout 使用较旧的 checkpoint,而训练已经推进
- 具有陈旧参数的异步 Rollout 工作器
- 常见于分布式/异步 RL 系统
- 3)回放缓冲区(Replay Buffers)
- 在来自更早迭代的历史轨迹上训练
- 来自不同策略版本的经验回放
- 数据增强或重采样策略
- 4)Off-Policy 算法
- 来自专家演示的行为克隆
- DAPO(来自辅助策略的数据)
- 任何使用来自不同策略轨迹的算法
- 5)数据质量过滤
- 收集数据的重新加权或过滤
- 具有修改分布的偏好学习
- 具有分布偏移的课程学习
- 这些 off-policy 差距可能导致训练不稳定和策略崩溃
- Rollout Correction(Rollout 校正)使用重要性采样(IS)权重和拒绝采样(RS)来校正数据收集与训练之间的任何分布偏移
关于常见实现错误的重要说明:
- 许多 LLM-RL 实现错误地应用 PPO,忽略实际的 Rollout 策略 \(\pi_{\text{rollout}}\),并假设训练参考策略 \(\pi_{\text{old}}\) 是行为策略
- 当 \(\pi_{\text{rollout}}\) ≠ \(\pi_{\text{old}}\) 时(由于 LLM-RL 中 Rollout 与训练之间的精度/后端差异,这很典型),这在数学上是不正确的
- 这不是 PPO 的错
- PPO 本身在数学上是正确的,问题在于 naive 实现中错误地假设 \(\pi_{\text{old}}\) = \(\pi_{\text{rollout}}\)
- 这个导致 RL 训练崩溃的关键实现错误在博客文章 “When Speed Kills Stability: Demystifying RL Collapse from the Training-Inference Mismatch” 中被指出,并推动了本 Rollout 校正框架的开发
数学上正确的方法
- Decoupled 模式(Decoupled mode) :三种策略(\(\pi_{\text{rollout}}\)、\(\pi_{\text{old}}\)、\(\pi_\theta\)),使用从 \(\pi_{\text{rollout}}\) 到 \(\pi_{\text{old}}\) 的 IS 校正
- ** Bypass Mode (Bypass mode)** :两种策略(\(\pi_{\text{rollout}}\) = \(\pi_{\text{old}}\)、\(\pi_\theta\)),使用实际 Rollout 策略作为 PPO 锚点
- Bypass + 策略梯度模式(Bypass + Policy Gradient mode) :两种策略(\(\pi_{\text{rollout}}\)、\(\pi_\theta\)),使用 IS/RS 校正且不进行 PPO 裁剪
- 详见
rollout_corr_math.md文件
关键设计原则:IS 权重与拒绝采样的分离
- 实现清晰地将两种正交机制分开
- 机制1 IS 权重(
rollout_is_weights):用于梯度校正的连续重加权- 策略比率:\(\pi_{\text{old}}\)/\(\pi_{\text{rollout}}\)(Decoupled 模式)或 \(\pi_\theta\)/\(\pi_{\text{rollout}}\)( Bypass Mode )
- 安全有界 :被限制在 [exp(-20), exp(20)] ≈ [2e-9, 5e8] 以防止溢出
- Token 级:对每个 token 的比率施加界限
- 序列级:对比率乘积施加界限(广播到所有 token)
- 截断 :通过
.clamp(max=rollout_is_threshold)进行上限截断(TIS:截断重要性采样,Truncated Importance Sampling) - 在填充位置置零 :乘以
response_mask以将填充位置置零 - 用于加权策略梯度(方差降低)
- 机制2 拒绝采样(
modified_response_mask):用于排除异常值的二值过滤- 创建二值掩码:1 = 保留,0 = 拒绝
- 拒绝 IS 比率落在 [lower_threshold, upper_threshold] 之外的 token/序列
- 修改
response_mask以从训练中排除被拒绝的样本
- 这种分离确保:
- ✅ IS 权重提供连续重加权(降低方差)
- ✅ 拒绝采样提供硬过滤(移除极端异常值)
- ✅ 两种机制可以独立或同时启用
- ✅ 安全界限在所有情况下防止数值溢出
Quark Start:使用经过验证的预设(Verified Presets)
- 现在提供类型化配置,以及针对常见场景经过验证的预设
- 这些预设已在各种模型和训练场景下经过数万 GPU 小时的验证
Python API
- Python API 调用可以快速获取这些 config:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28from verl.trainer.config.algorithm import RolloutCorrectionConfig
# === Decoupled PPO 模式(3 种策略:\\(\pi_{\text{rollout}}\\)、\\(\pi_{\text{old}}\\)、\\(\pi_\theta\\))===
# IS 权重校正 \\(\pi_{\text{old}}\\) 与 \\(\pi_{\text{rollout}}\\) 之间的差距
config = RolloutCorrectionConfig.decoupled_token_is() # Token-TIS
config = RolloutCorrectionConfig.decoupled_seq_is() # Seq-TIS
config = RolloutCorrectionConfig.decoupled_seq_is_rs() # Seq-MIS
config = RolloutCorrectionConfig.decoupled_geo_rs() # Geo-RS(比率模式)
config = RolloutCorrectionConfig.decoupled_geo_rs_token_tis() # Geo-RS + Token-TIS
# === K3 KL 估计器预设(对于小 KL 更稳定)===
config = RolloutCorrectionConfig.decoupled_k3_rs() # K3-RS 仅
config = RolloutCorrectionConfig.decoupled_k3_rs_token_tis() # K3-RS + Token-TIS
# === Bypass PPO 模式(2 种策略:\\(\pi_{\text{rollout}}\\) = \\(\pi_{\text{old}}\\)、\\(\pi_\theta\\))- 快速 ===
# PPO 比率处理 IS,因此不需要显式 IS 权重
config = RolloutCorrectionConfig.bypass_ppo_clip() # PPO-clip 仅
config = RolloutCorrectionConfig.bypass_ppo_clip_geo_rs() # PPO-clip + Geo-RS
config = RolloutCorrectionConfig.bypass_ppo_clip_k3_rs() # PPO-clip + K3-RS
# === Bypass PG 模式(2 种策略,无 PPO 裁剪)- 快速 ===
# IS 权重在损失函数中即时计算为 \\(\pi_\theta\\) / \\(\pi_{\text{rollout}}\\)
config = RolloutCorrectionConfig.bypass_pg_is() # Seq-TIS + PG
config = RolloutCorrectionConfig.bypass_pg_geo_rs() # Geo-RS + PG
config = RolloutCorrectionConfig.bypass_pg_geo_rs_token_tis() # Geo-RS + Token-TIS + PG
# === 其他 ===
config = RolloutCorrectionConfig.disabled() # 仅指标(不进行校正)
YAML 配置(高级)
用于高级定制或基于 YAML 的配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14algorithm:
rollout_correction:
rollout_is: token # IS 权重:"token"、"sequence" 或 null
rollout_is_threshold: 2.0 # TIS 上限,或用于 IcePop 的 "0.5_5.0"
rollout_is_batch_normalize: false # 批归一化 IS 权重使均值为 1.0
rollout_rs: null # 拒绝采样:逗号分隔的规范选项(例如 "token_k1,seq_max_k2")
rollout_rs_threshold: null # 阈值规格:float 或 "lower_upper" 字符串
bypass_mode: false # 跳过 old_log_prob 计算(设置 \\(\pi_{\text{old}}\\) = \\(\pi_{\text{rollout}}\\))
loss_type: ppo_clip # Bypass Mode 中的损失类型:"ppo_clip"(默认)或 "reinforce"
# 必需:启用 log prob 计算
actor_rollout_ref:
rollout:
calculate_log_probs: true注:使用 YAML 配置的时候可以参考 Python API 的内容来配置,确保自己的配置和官方一致
Configuration Parameters
- 所有参数都在
algorithm.rollout_correction下:
rollout_is(str 或 null)
- 重要性采样权重聚合级别:
null= 不计算 IS 权重(仅指标模式)"token":每个 Token 的 IS 权重- Decoupled 模式 :\(\rho_t\) = \(\pi_{\text{old}}(t)\)/\(\pi_{\text{rollout}}(t)\)
- Bypass/纯 IS 模式 :\(\rho_t\) = \(\pi_\theta(t)\)/\(\pi_{\text{rollout}}(t)\)
- 每个 Token 独立截断
- 典型阈值:1.5 - 5.0
"sequence":每个序列的权重 \(\rho_\text{seq} = \prod_t \rho_t\)- 在序列上进行乘法聚合
- 典型阈值:2.0 - 10.0
- 所有 IS 权重都被安全限制在
[exp(-20), exp(20)] ≈ [2e-9, 5e8]范围内
rollout_is_threshold(str 或 float)
- IS 加权的阈值,默认值:
2.0 - 单个 float 或类 float 字符串:通过
.clamp(max=rollout_is_threshold)进行 TIS - 诸如
"0.5_5.0"的"lower_upper"字符串:IcePop,将[lower, upper]外的权重置零 - 应用于 IS 权重以降低方差
- 与拒绝采样分开(由
rollout_rs参数控制) - 与
rollout_rs不同,IcePop 不修改response_mask;它只改变 IS 系数
rollout_is_batch_normalize(bool)
- 对 IS 权重应用批归一化,默认值:
FalseTrue:在每个批次内将 IS 权重归一化为均值 = 1.0- Token 级 IS :在所有 token 权重上归一化,即除以 Token 粒度的均值(保证优化后的均值为 1)
- 序列级 IS :在序列均值上归一化(每个序列一个权重),即除以 Sequence 粒度的均值(保证归一化后的均值为 1)
- 注:不论哪种情况,被除以的那个数字都是
weights_mean,并被上报为rollout_is_batch_norm_factor指标
False:使用原始(截断的)IS 权重
- 通过确保每批平均权重为 1.0 来降低方差
- 在截断之后应用,以保留截断语义
- 仅影响最终生效的 IS 权重值,不影响拒绝采样
- 特别说明:也不会影响
rollout_is_mean和rollout_is_seq_mean等上报指标 - 打开
rollout_is_batch_normalize,可以看到rollout_is_batch_norm_factor指标(这个指标是归一化时除以的那个均值weights_mean) - 补充:
rollout_is_batch_norm_factor与rollout_is_seq_mean的关系- 在
sequence粒度的 TIS 校准中,当前上报方式下两者值相等,但 202603 之前(202511 版本)中,两者有差异,当时上报的rollout_is_seq_mean等指标是在截断rollout_is_weights = rollout_is_weights.clamp(max=rollout_is_threshold)之前上报的 token粒度的 TIS 校准rollout_is_batch_norm_factor与rollout_is_mean的关系 也类似
- 在
- 特别说明:也不会影响
rollout_rs(str 或 null)
- 拒绝采样聚合模式,提供逗号分隔的字符串(空格可选),使用
rollout_corr_helper中实现的规范选项:token_k1:Token 级拒绝,使用-log r界限(比率阈值以lower_upper提供),示例:"0.6_1.4"token_k2:Token 级拒绝,使用0.5 * (log r)^2(仅上限)token_k3:Token 级拒绝,使用exp(log r) - 1 - log r(仅上限)seq_sum_k1:序列级拒绝,使用-log r之和(比率界限)seq_sum_k2:序列级拒绝,使用0.5 * (log r)^2之和(仅上限)seq_sum_k3:序列级拒绝,使用exp(log r) - 1 - log r之和(仅上限)seq_mean_k1:序列级拒绝,使用-log r的均值(比率界限)seq_mean_k2:序列级拒绝,使用0.5 * (log r)^2的均值(仅上限)seq_mean_k3:序列级拒绝,使用exp(log r) - 1 - log r的均值(仅上限)seq_max_k2:序列级拒绝,使用0.5 * (log r)^2的最大值(仅上限)seq_max_k3:序列级拒绝,使用exp(log r) - 1 - log r的最大值(仅上限)
rollout_rs_threshold(str、float 或 null)
rollout_rs_threshold是拒绝采样的阈值- 为每个选项提供一个 entry ,用逗号分隔,单个 entry 会广播到所有 option
- 注:这里这么说是因为
rollout_rs是可以同时配置多个的rollout_rs="token_k1,seq_max_k2"这样,此时rollout_rs_threshold可以使用单个值来自动广播,也可以按照逗号分别配置阈值
- 注:这里这么说是因为
- 不同模式的阈值说明:
- k1 KL 模式(
*k1) :使用"lower_upper"字符串(例如"0.7_1.3")- 理解:这里的 k1 KL 模式包括
token_k1、seq_sum_k1、seq_mean_k1等,这些是双侧比率限制(因为 k1 是可能为负值的),阈值要写成 包含 “lower_upper” 的字符串 - 注:如果只写一个
float则意味着只是上界(此时的下限默认为其倒数)
- 理解:这里的 k1 KL 模式包括
- k2/k3 KL 模式(
*k2/*k3) :提供正的上限(float 或数字字符串)- 理解:包括
token_k2、seq_max_k2、seq_mean_k3等,这些是单侧散度限制(因为 k2 和 k3 是非负的),只需要提供一个正的上界
- 理解:包括
- k1 KL 模式(
- 阈值设置为
null时,表示完全禁用阈值- 注意:仅当
rollout_rs为null(即不启用任何拒绝采样)时才合法
- 注意:仅当
- 使用注意:在训练时,使用 RS 有可能导致
assert response_mask.any(), "Expected at least one valid token in response mask"- 这里 Rollout Correction 的 off-policy 指标统计是在 micro_batch 维度,所以 micro_batch 上的所有样本都被 Mask 时,则会直接报错
- 注:类似 Megatron 中将 mini-batch 拆为多个 micro-batch,verl 也会将 mini-batch 拆为多个 micro-batch
- verl 中策略:
- 打开
use_dynamic_bsz=True时使用 rearrange_micro_batches 函数来拆分 micro-batches- 这里的目标是让每个 GPU 单次的 Token 数量小于等于
ppo_max_token_len_per_gpu,所以 micro-batch 数量也是不确定的(平均队列越短,得到的 micro-batch 数量越小) - 极端情况下一个 micro-batch 中的真实样本数可能非常小(如果序列长度跟
ppo_max_token_len_per_gpu时,甚至只包含一个样本,此时这个样本被 mask 就意味着出问题) - 注:rearrange_micro_batches 函数拆分 micro-batches 时只按照索引拆,仍然是多上,packing 发生在 forward_fn 内部,对我们是透明的
- 这里的目标是让每个 GPU 单次的 Token 数量小于等于
- 否则
use_dynamic_bsz=False时使用固定条数作为一个 micro-batches- 此时的配置为
ppo_micro_batch_size_per_gpu
- 此时的配置为
- 打开
- 不论怎样,micro-batches 都会小于 mini-batch 数
- verl 中策略:
- 注:类似 Megatron 中将 mini-batch 拆为多个 micro-batch,verl 也会将 mini-batch 拆为多个 micro-batch
- 补充指标计算位置:
指标组 产生函数 调用位置 输入规模 rollout_is_*(含rollout_is_seq_min/max)compute_rollout_correction_weights→compute_is_metrics仅 ray_trainer.py(driver 侧)完整 512 条 kl/log_ppl_*/chi2compute_rollout_corr_metrics_from_logprobsmegatron_actor.py、dp_actor.py单个 micro-batch - 上诉问题一般发生在
compute_rollout_corr_metrics_from_logprobs中 - 注:其实这种方式下
compute_rollout_corr_metrics_from_logprobs中上报的 sequence 粒度的指标似乎都不是真实 Sequence 粒度的了(待确认)
- 上诉问题一般发生在
- 这里 Rollout Correction 的 off-policy 指标统计是在 micro_batch 维度,所以 micro_batch 上的所有样本都被 Mask 时,则会直接报错
Understanding the Framework: Components and Combinations
- Rollout 校正框架构建自正交组件 ,可以灵活组合
Key Components
- 1) Operating Mode
- Decoupled :三种策略(\(\pi_{\text{rollout}}\)、\(\pi_{\text{old}}\)、\(\pi_\theta\)),单独计算 \(\pi_{\text{old}}\)
- Bypass :两种策略(\(\pi_{\text{rollout}}\) = \(\pi_{\text{old}}\)、\(\pi_\theta\)),跳过 \(\pi_{\text{old}}\) 计算
- 2) Loss Function( Bypass Mode 下,由
loss_type控制)- PPO-clip(
loss_type="ppo_clip",默认):PPO 裁剪目标(IS 由比率处理) - REINFORCE(
loss_type="reinforce"):带显式 IS 权重的策略梯度(无裁剪)
- PPO-clip(
- 3) IS/RS Aggregation Level
- Token :每个 Token 的 IS 权重/拒绝
- Sequence :序列级 IS 权重/拒绝
Preset Configuration Guide
- 本节给的每个 Preset 都是针对常见场景优化的特定组件组合
Understanding the Presets
Available Preset Methods
- 所有提前配置好的方法示例:
预设方法 估计器 模式 IS 级别 RS 级别 属性 Decoupled PPO Mode(3 种策略:\(\pi_{\text{rollout}}\)、\(\pi_{\text{old}}\)、\(\pi_\theta\)) decoupled_token_is()Token-TIS Decoupled token - Token 级 IS 权重 decoupled_seq_is()Seq-TIS Decoupled sequence - 序列级 IS 权重 decoupled_seq_is_rs()Seq-MIS Decoupled sequence sequence 序列 IS + seq_sum_k1 RS decoupled_geo_rs()Geo-RS Decoupled - sequence 几何 RS(seq_mean_k1) decoupled_geo_rs_token_tis()Geo-RS-Token-TIS Decoupled token sequence 几何 RS + token IS K3 KL Estimator(对小 KL 值更稳定) decoupled_k3_rs()K3-RS Decoupled - sequence seq_mean_k3 RS decoupled_k3_rs_token_tis()K3-RS-Token-TIS Decoupled token sequence seq_mean_k3 RS + token IS Bypass PPO-clip Mode(2 种策略;比率处理 IS,RS 掩码排除异常值) bypass_ppo_clip()- Bypass(PPO-clip) - - 仅 PPO-clip bypass_ppo_clip_geo_rs()Geo-RS Bypass(PPO-clip) - sequence PPO-clip + Geo-RS bypass_ppo_clip_k3_rs()K3-RS Bypass(PPO-clip) - sequence PPO-clip + K3-RS Bypass REINFORCE Mode(2 种策略;显式 IS 权重,无 PPO 裁剪) bypass_pg_is()Seq-TIS Bypass(REINFORCE) sequence - 带显式 IS 的 REINFORCE bypass_pg_geo_rs()Geo-RS Bypass(REINFORCE) - sequence REINFORCE + Geo-RS bypass_pg_geo_rs_token_tis()Geo-RS-Token-TIS Bypass(REINFORCE) token sequence REINFORCE + Geo-RS + token IS Other disabled()- - - - 仅指标,不校正 - 使用注意:
- Bypass Mode设置 \(\pi_{\text{old}} = \pi_{\text{rollout}}\),并使用
loss_type选择损失函数:"ppo_clip"(默认):PPO 裁剪目标,其中比率 = \(\pi_\theta / \pi_{\text{rollout}}\) 已经处理了 IS"reinforce":带显式 IS 权重的 REINFORCE,IS 权重为 \(\pi_\theta / \pi_{\text{rollout}}\)
- 两种损失类型都受益于拒绝采样(RS),它掩码掉分布外的样本
- 所有估计器(Token-TIS、Seq-TIS、Seq-MIS、Geo-RS 等)都与Decoupled 模式和 Bypass Mode 兼容
- Bypass Mode设置 \(\pi_{\text{old}} = \pi_{\text{rollout}}\),并使用
其他可行组合(需手动配置)
- 没有预设方法但受可行的其他组合:
- Token IS + Token RS:Token 级 IS 权重 + Token 级 RS 掩码
- 纯 Token RS:仅 Token 级 RS,无 IS 权重
- 纯序列 RS:仅序列级 RS,无 IS 权重
- 说明:
- 任何聚合级别(token/sequence)都可在 Decoupled 或 Bypass Mode 下工作
- 实现完全支持所有组合
- 拒绝采样独立于 IS 加权
- 纯 RS(
bypass_pg_rs)使用 Bypass + 几何 RS,loss_type="reinforce"(无 IS 权重)
Decoupled 模式 + Token 级重要性采样(decoupled_token_is)
参数配置:
1
config = RolloutCorrectionConfig.decoupled_token_is(threshold=2.0)
基本组件
- 运行模式 :Decoupled(3 种策略)
- 损失 :带裁剪的 PPO(仅用于第二次漂移校正)
- IS 聚合 :Token 级
- RS :无(可单独添加)
等效 YAML:
1
2
3
4
5
6algorithm:
rollout_correction:
rollout_is: token
rollout_is_threshold: 2.0
rollout_rs: null
bypass_mode: false # Decoupled 模式特点:
- 每个 Token 独立截断
- 方差低于序列级(比率乘积被逐项限制)
- 典型阈值:1.5 - 5.0
Decoupled 模式 + 序列级重要性采样(decoupled_seq_is)
也称为:Seq-TIS(序列级截断重要性采样,Sequence-Level Truncated IS)
参数配置:
1
config = RolloutCorrectionConfig.decoupled_seq_is(threshold=2.0)
基本组件:
- 运行模式 :Decoupled(3 种策略)
- 损失 :带裁剪的 PPO(仅用于第二次漂移校正)
- IS 聚合 :序列级(Seq-TIS)
- RS :无(可单独添加)
等效 YAML:
1
2
3
4
5
6algorithm:
rollout_correction:
rollout_is: sequence
rollout_is_threshold: 2.0
rollout_rs: null
bypass_mode: false # Decoupled 模式特点:
- 在序列上进行乘法聚合
- 对异常值比 Token 级更敏感
- 典型阈值:2.0 - 10.0(高于 Token 级)
Decoupled 模式 + 序列级 IS + 拒绝采样(decoupled_seq_is_rs)
也称为:Seq-MIS(序列级掩码 IS,Sequence-Level Masked IS)
参数配置:
1
config = RolloutCorrectionConfig.decoupled_seq_is_rs(is_threshold=2.0, rs_threshold="0.5_2.0")
基本组件:
- 运行模式 :Decoupled(3 种策略)
- 损失 :带裁剪的 PPO(仅用于第二次漂移校正)
- IS 聚合 :序列级(Seq-TIS)
- RS :序列级拒绝(Seq-MIS)
等效 YAML:
1
2
3
4
5
6
7algorithm:
rollout_correction:
rollout_is: sequence
rollout_is_threshold: 2.0
rollout_rs: seq_sum_k1
rollout_rs_threshold: 0.5_2.0
bypass_mode: false # Decoupled 模式特点:
- 双重机制:IS 重加权(Seq-TIS)+ 拒绝过滤(Seq-MIS)
- 有效样本量较低(拒绝异常值)
- 适用于严重的 off-policy 差距或当分布尾部是“有毒的”(垃圾/对抗样本)时
何时使用 Seq-MIS 而非 Seq-TIS:
- Seq-TIS(仅裁剪) :最大化信息效率;从所有样本中提取信号
- 当数据干净且不匹配适中时使用
- Seq-MIS(拒绝) :最大化安全性;充当硬信任区域过滤器
- 当不匹配严重或高权重样本很可能是垃圾而非信号时使用
- Seq-TIS(仅裁剪) :最大化信息效率;从所有样本中提取信号
Bypass Mode + PPO-clip(bypass_ppo_clip)
参数配置:
1
config = RolloutCorrectionConfig.bypass_ppo_clip()
基本组件:
- 运行模式 :Bypass(2 种策略:\(\pi_{\text{rollout}}\) = \(\pi_{\text{old}}\)、\(\pi_\theta\))
- 损失 :PPO-clip(IS 由比率处理,无显式 IS 权重)
- IS 聚合 :无(PPO 比率处理)
- RS :无
等效 YAML:
1
2
3
4
5rollout_correction:
rollout_is: null
rollout_rs: null
bypass_mode: true
loss_type: ppo_clip特点:
- Bypass Mode 下的 PPO 裁剪目标
- PPO 比率 = \(\pi_\theta / \pi_{\text{rollout}}\) 已经处理了 IS(不需要显式 IS 权重)
- 跳过
actor.compute_log_prob()前向传播(2 种策略而非 3 种) - 无拒绝采样——使用
bypass_ppo_clip_geo_rs()添加 RS
配置要求:
- 需要设置
actor_rollout_ref.rollout.calculate_log_probs: true
- 需要设置
Bypass Mode 的额外要求:
- 设置
actor_rollout_ref.actor.use_rollout_log_probs: true - 设置
actor_rollout_ref.actor.policy_loss.loss_mode: bypass_mode - 通过
actor_rollout_ref.actor.policy_loss.rollout_correction设置 Rollout 校正配置为上诉 Yaml 配置
- 设置
带 IS 的 REINFORCE(bypass_pg_is)
参数配置:
1
config = RolloutCorrectionConfig.bypass_pg_is(threshold=2.0)
基本组件:
- 运行模式 :Bypass(2 种策略:\(\pi_{\text{rollout}}\)、\(\pi_\theta\))
- 损失 :REINFORCE(带显式 IS 权重的策略梯度,无 PPO 裁剪)
- IS 聚合 :序列级
- RS :无
等效 YAML:
1
2
3
4
5
6rollout_correction:
rollout_is: sequence
rollout_is_threshold: 2.0
rollout_rs: null
bypass_mode: true
loss_type: reinforce # 带显式 IS 权重的 REINFORCE特点:
- 带显式 IS 权重的 REINFORCE 损失(无 PPO 裁剪)
- 单次前向传播(跳过 old_log_prob 计算)
- IS 权重在损失函数中即时计算
问题:为什么 这里要将 Bypass Mode + PPO-clip 模式 和 带 IS 的 REINFORCE 模式 分开写?
- 因为两者在 VeRL 中需要的配置不同
- Bypass Mode + PPO-clip 模式配置
rollout_is=null - 带 IS 的 REINFORCE 模式配置
rollout_is=sequence/token
- Bypass Mode + PPO-clip 模式配置
- PPO-clip 中天然就有 IS 和 Clip 了,可以通过 PPO 的 IS(\(r_t\theta\)) 直接校准两种不同
$$
r_t(\theta) = \frac{\pi_{\theta}(a_t \mid s_t)}{\pi_{\text{rollout} }(a_t \mid s_t)}
$$ - 带 IS 的 REINFORCE 中将 IS 视为了 Rollout 修正 的这个角色的比值(\(\rho_t\theta\)),所以需要设置
rollout_is=sequence/token(打开 rollout_correction)- 若为 Token 粒度
$$
\rho_t(\theta) = \frac{\pi_{\theta}(a_t \mid s_t)}{\pi_{\text{rollout} }(a_t \mid s_t)}
$$ - 若为 Sequence 粒度
$$
w_{\text{seq} }(\theta) = \rho(\theta) = \frac{\pi_{\theta}(y \mid x)}{\pi_{\text{rollout} }(y \mid x)} = \prod_{t=1}^{T} \frac{\pi_{\theta}(a_t \mid s_t)}{\pi_{\text{rollout} }(a_t \mid s_t)}
$$
- 若为 Token 粒度
- 因为两者在 VeRL 中需要的配置不同
补充:额外有用的配置(未作为预设公开)
- 这些配置可用 ,但还没有便捷的预设方法
Token IS + Token RS(token_is_rs)
Token 级 IS 权重加上 Token 级 RS 掩码
1
2
3
4
5
6config = RolloutCorrectionConfig(
rollout_is="token",
rollout_is_threshold=2.0,
rollout_rs="token_k1",
rollout_rs_threshold=2.0,
)特点: 每个 Token 的 IS 权重 + 每个 Token 的 RS 掩码
纯 Token RS(token_rs)
仅 Token 级 RS,无 IS 权重
1
2
3
4
5config = RolloutCorrectionConfig(
rollout_is=None,
rollout_rs="token_k1",
rollout_rs_threshold=2.0,
)特点:Token 级 RS 掩码,无 IS 重加权
纯序列 RS(seq_rs)
仅序列级 RS,无 IS 权重
1
2
3
4
5config = RolloutCorrectionConfig(
rollout_is=None,
rollout_rs="seq_sum_k1",
rollout_rs_threshold="0.5_2.0",
)特点:序列级 RS 掩码,无 IS 重加权
总结:IS 权重的处理方式
- IS 权重(
rollout_is_weights)经过固定的处理流水线: - 阶段 1:安全界限(防止溢出)
- Token 级:每个 Token 的
exp(clamp(log_ratio, -20, 20))→ 将每个 token 限制在 [2e-9, 5e8] - 序列级:
exp(clamp(sum(log_ratio), -20, 20))→ 将乘积限制在 [2e-9, 5e8],广播到所有 token
- Token 级:每个 Token 的
- 阶段 2:截断(降低方差)
.clamp(max=rollout_is_threshold)→ 将权重上限截断(TIS:截断重要性采样)- 无下限截断(对小权重保持无偏性)
- 阶段 3:填充位置置零(正确聚合)
weights * response_mask→ 将填充位置置零
- 阶段 4:可选的批归一化
- 如果
rollout_is_batch_normalize=True:将权重归一化到批次内均值为 1.0 - 在截断之后应用,以保留截断语义
- 如果
- 拒绝采样(独立机制)
- 拒绝采样通过
compute_rollout_rejection_mask()修改response_mask(而非权重):- 独立计算安全有界的比率
- 创建二值掩码:落在 [lower_threshold, upper_threshold] 之外的 token/序列 → 0(拒绝)
- 修改后的掩码用于损失聚合
- 拒绝采样通过
Operation Modes
- 该框架提供两种运行模式 来计算 \(\pi_{\text{old}}\),可与不同的损失函数结合
Operating Modes and Configuration
- Operating Modes 配置情况:
配置 bypass_modeloss_type运行模式 损失函数 描述 Decoupled falseN/A Decoupled PPO 通过 actor.compute_log_prob()单独计算old_log_probBypass + PPO-clip true"ppo_clip"(默认)Bypass PPO-clip PPO 裁剪目标(IS 由比率处理) Bypass + REINFORCE true"reinforce"Bypass REINFORCE 带显式 IS 权重的策略梯度(无 PPO 裁剪)
Operating Mode Details
Decoupled Mode,三种策略
- 策略设置:
- \(\pi_{\text{rollout}}\):行为策略(数据收集)
- \(\pi_{\text{old}}\):近端策略(在训练 epoch 开始时通过
actor.compute_log_prob()计算) - \(\pi_\theta\):当前策略(正在更新)
- 参数配置:
bypass_mode = false - 特点:
- ✅ 实现批次大小不变性
- ✅ 分别校正漂移 1(rollout→old)和漂移 2(old→current)
- ✅ 高效利用陈旧数据
- ❌ 需要额外的前向传播(
actor.compute_log_prob())
Bypass Mode,两种策略
- 策略设置:
- \(\pi_{\text{rollout}}\):行为策略(数据收集)
- \(\pi_{\text{old}}\) = \(\pi_{\text{rollout}}\):近端策略等于行为策略
- \(\pi_\theta\):当前策略(正在更新)
- 参数配置:
bypass_mode = true - 属性:
- ✅ 跳过
actor.compute_log_prob()调用(更快) - ✅ 通过 IS/RS 处理 off-policy 校正(当使用带 IS/RS 的策略梯度时)
- ✅ 使用两种策略而非三种(\(\pi_{\text{rollout}}\) = \(\pi_{\text{old}}\))
- ⚠️ 不将近端策略与行为策略分离(不同于 Decoupled 模式)
- ✅ 跳过
IS/RS 聚合级别(正交于运行模式)
- 聚合级别可以独立于 运行模式选择,任何聚合级别都可以在 Decoupled 或 Bypass Mode 下工作
rollout_isrollout_rs行为 nullnull禁用 :不计算、无指标、无拒绝 null"token_k1"、"seq_sum_k1"、"seq_mean_k1"、"seq_max_k2"等仅拒绝 :计算指标、不进行权重校正、进行拒绝采样 "token"或"sequence"null仅 IS 权重 :启用权重校正、不进行拒绝采样 "token"或"sequence""token_k1"、"seq_sum_k1"、"seq_mean_k1"、"seq_max_k2"等完整校正 :同时启用权重校正和拒绝采样
Key Insights
- ✅ 任何 IS/RS 聚合级别(token/sequence/geometric)都可以在Decoupled或Bypass模式下使用
- ✅ 您可以单独使用拒绝采样而不进行 IS 权重校正(
rollout_is=null, rollout_rs="token_k1") - ✅ 您可以单独使用 IS 权重而不进行异常值拒绝(
rollout_is="token", rollout_rs=null) - ✅ 您可以同时使用两者(
rollout_is="token", rollout_rs="token_k1") - ✅ 您可以仅监控指标而不进行任何校正,方法是同时将两者设置为
null,但仍提供rollout_log_probs
Example Workflow
推荐: Bypass Mode
该工作流为高效而使用 Bypass Mode
1) 从仅指标开始 ,以了解 off-policy 差距:
1
2
3
4
5rollout_correction:
rollout_is: null
rollout_rs: null
bypass_mode: true # Bypass Mode (推荐)
loss_type: ppo_clip # 默认:PPO 裁剪目标- 监控
rollout_corr/kl、rollout_corr/log_ppl_abs_diff、rollout_corr/chi2_token以评估 off-policy 差距 - 注意:off-policy 指标是无条件计算的,不管是否打开 rollout_correction 都会被计算
- 关闭 Bypass Mode 模式下,ritio = old_log_prob/rollout_log_prob :
- old_log_prob = log_prob
- rollout_log_prob = rollout_log_prob
- 关闭 Bypass Mode 模式下,ritio = old_log_prob/rollout_log_prob :
- 监控
2) 如果看到高异常值比例,则启用拒绝采样 :
1
2
3
4
5
6rollout_correction:
rollout_is: null
rollout_rs: sequence # 或 "geometric" 以获得更高敏感性
rollout_rs_threshold: 2.0
bypass_mode: true # Bypass Mode
loss_type: ppo_clip # 或 "reinforce" 以使用显式 IS 权重- 这将从训练中排除异常值,而不修改梯度
3) 一旦对指标满意,启用完整的 IS 校正(配合 REINFORCE 损失):
1
2
3
4
5
6
7rollout_correction:
rollout_is: sequence # 推荐:无偏,适用于大多数情况(
rollout_is_threshold: 2.0
rollout_rs: sequence # 或 "geometric" 以进行更激进的过滤
rollout_rs_threshold: 2.0
bypass_mode: true # Bypass Mode
loss_type: reinforce # 带显式 IS 权重的 REINFORCE- 理解(个人经验):虽然 sequence-level MIS 是无偏的,但亲测当 micro-batch 过小时,容易报错
- rollout_corr_helper.py 本身会 assert 一个 micro-batch 内部的 Token 不能全被 Mask
- 解法1:
- Mask 时将 Advantage 置为 0 而不是使用 response_mask
- 解法2:
- 使用 Token 粒度的 Mask,然后保留 Sequence 粒度的 IS
- 理解(个人经验):虽然 sequence-level MIS 是无偏的,但亲测当 micro-batch 过小时,容易报错
Bypass Mode 的优点:
- ✅ 跳过昂贵的
actor.compute_log_prob()前向传播(更快) - ✅
loss_type控制损失函数:”ppo_clip”(默认)或 “reinforce” - ✅ PPO-clip:IS 由比率处理(无显式权重),应用 RS 掩码
- ✅ REINFORCE:显式 IS 权重即时计算(\(\pi_\theta\) / \(\pi_{\text{rollout}}\))
- ✅ 两种损失类型都与所有 IS/RS 组合兼容
- ✅ 跳过昂贵的
Usage
Basic Setup
- 配置:
1
2
3
4
5
6
7
8
9algorithm:
rollout_correction:
rollout_is: token # 在 Token 级启用 IS 权重
rollout_is_threshold: 2.0 # IS 权重的阈值
rollout_rs: null # 不进行拒绝采样
actor_rollout_ref:
rollout:
calculate_log_probs: true # 必需!
Bypass Mode 的额外配置
- 设置
actor_rollout_ref.actor.use_rollout_log_probs: true - 设置
actor_rollout_ref.actor.policy_loss.loss_mode: bypass_mode - 通过
actor_rollout_ref.actor.policy_loss.rollout_correction设置 Rollout 校正配置
Metrics
- 所有指标在日志中都以
rollout_corr/为前缀- 例如,
rollout_is_mean在日志中显示为rollout_corr/rollout_is_mean
- 例如,
- 这些指标涵盖:
- 诊断指标 :KL 散度、困惑度差异(衡量 off-policy 差距)
- 校正统计量 :IS 权重、拒绝率(衡量所施加的校正)
Core IS Weight Metrics
rollout_is_mean:所有有效 token 上的平均重要性采样权重- Token 粒度的 IS 时,这个指标一般为 1 左右(看着上报甚至四舍五入后一直是 1)
- 注意:等于 1 而不是小于 1 的原因是跟之前我们所熟悉的 KL 散度不一样
- KL 的结论针对 log ratio,不是 ratio
- \(q\):rollout 策略
- \(p\):训练引擎重算
old_log_probs对应的策略 - \(r=p/q\)
- 对于固定上下文,在支持集覆盖、概率计算正确的条件下:
$$
\mathbb E_{a\sim q}[r]=\sum_a q(a)\frac{p(a)}{q(a)}=\sum_a p(a)=1.
$$ - 而 KL 对应的是:
$$
\mathbb E_q[\log r]=-D_{\text{KL} }(q\Vert p)\le 0.
$$ - 所以:
$$
\begin{align}
\underbrace{\mathbb E_q[r]&=1}_{\text{算术平均} }\\
\underbrace{\exp(\mathbb E_q[\log r])&\le 1}_{\text{几何平均} }
\end{align}
$$
- KL 的结论针对 log ratio,不是 ratio
- 注意:等于 1 而不是小于 1 的原因是跟之前我们所熟悉的 KL 散度不一样
- Sequence 粒度 IS 时,每个 Token 的权重相等,计算平均后一般不是 1(长序列的 IS 小于1)
- Token 粒度的 IS 时,这个指标一般为 1 左右(看着上报甚至四舍五入后一直是 1)
rollout_is_std:IS 权重的标准差- 值越高表示 IS 权重方差越大
rollout_is_min:观察到的最小 IS 权重- 显示最被低估的 token/序列
- 对于 sequence/geometric:从未裁剪的对数空间比率计算(真实最小值)
- 对于 token:从安全有界的权重计算
rollout_is_max:观察到的最大 IS 权重- 显示最被高估的 token/序列
- 对于 sequence/geometric:从未裁剪的对数空间比率计算(安全界限之前的真实最大值)
- 对于 token:从安全有界的权重计算(阈值裁剪之前)
- 与
rollout_is_threshold比较以查看截断影响
Effective Sample Size
rollout_is_eff_sample_size:IS 加权后的有效样本量- 公式 :
1 / mean(weights^2),其中权重已归一化- 注:这个是可以推导出来的,详情见:NLP——LLM-RL训练指标记录
- 范围 :0.0 到 1.0(占原始批次的分数)
- 值越低表示权重集中在更少的样本上
- 公式 :
Threshold Exceedance Metrics
rollout_is_ratio_fraction_high:超过上限阈值的权重比例- 显示截断/掩码在高端的发生频率
- 对于 sequence/geometric:从未裁剪的对数空间比率计算(真实超出)
- 对于 token:从安全有界的权重计算(阈值裁剪之前)
rollout_is_ratio_fraction_low:低于下限阈值(1/upper_threshold)的权重比例- 显示有多少权重低于倒数阈值的诊断指标
- 对于 sequence/geometric:从未裁剪的对数空间比率计算(真实超出)
- 对于 token:从安全有界的权重计算(截断之前)
序列级指标(用于序列聚合)
rollout_is_seq_mean:序列级的平均 IS 权重- 现在 seq 内部对所有 Token 的 IS(这个 IS 的粒度可能是 sequence 或者 Token) 平均,然后再对 batch 内部对 seq 平均
- Token 粒度的 IS 时,这个指标和
rollout_is_mean一般都是 1 - Sequence 粒度的 IS 时,这个指标可能较小,且和
rollout_is_mean可能不一致
rollout_is_seq_std:序列级 IS 权重的标准差rollout_is_seq_min:最小序列级 IS 权重rollout_is_seq_max:最大序列级 IS 权重rollout_is_seq_max_deviation:序列级与 1.0 的最大绝对偏差- 显示最坏情况序列的 off-policy 差距
rollout_is_seq_fraction_high:超过上限阈值的序列比例rollout_is_seq_fraction_low:低于下限阈值的序列比例
补充:rollout_is_seq_mean 和 rollout_is_mean 指标计算细节
rollout_is=token下,rollout_is_seq_mean和rollout_is_mean都接近 \(1\)- 名字中的
seq不代表“把整条序列的 token ratio 相乘 - 设第 \(i\) 条序列有 \(L_i\) 个有效 token,token ratio 为 \(r_{it}\),代码先求每条序列的算术均值:
$$
a_i=\frac{1}{L_i}\sum_t r_{it}.
$$ - 然后两个指标分别是:
$$
\begin{align}
\texttt{rollout_is_mean}&=\frac{\sum_i L_i a_i}{\sum_i L_i} \\
\texttt{rollout_is_seq_mean}&=\frac1N\sum_i a_i
\end{align}
$$ - 即:
mean:所有有效 token 等权,长序列贡献更多;seq_mean:先在序列内平均,再让各序列等权
- 当各序列内部的 token ratio 均值都接近 \(1\) 时,两个指标自然都接近 \(1\)
- 名字中的
- 换成
rollout_is=sequence后,seq_mean一般比mean大,甚至大一个数量级- sequence 模式改变了权重本身,此时每条序列计算一个权重:
$$
w_i=\exp\left(\sum_t\log r_{it}\right),
$$ - 实现上会先将上述 log-ratio 总和 clamp 到 \([-20,20]\),再取指数,并将 \(w_i\) 广播到该序列的所有有效 token
- 因此,两个指标变为:
$$
\begin{align}
\texttt{rollout_is_mean}&=\frac{\sum_i L_iw_i}{\sum_i L_i} \\
\texttt{rollout_is_seq_mean}&=\frac1N\sum_i w_i
\end{align}
$$- 它们仍是同一批权重的不同加权平均,不是两种不同的修正比例
- 当长序列权重更小时,token 加权均值就更低
- 补充:为什么长轨迹容易出现小权重?
- 因为 sequence IS 累积的是 log ratio 的总和,没有除以长度 :
$$
\log w_i=L_i\cdot\overline{\log r}_i.
$$ - 例如,一条轨迹的平均 log ratio 仅为 \(-0.001\)(对应 ratio 微小于 1):
- \(1,000\) 个 token:\(w=e^{-1}\approx0.368\);
- \(10,000\) 个 token:\(w=e^{-10}\approx0.0000454\)
- 因为 sequence IS 累积的是 log ratio 的总和,没有除以长度 :
- 补充:为什么长轨迹容易出现小权重?
- sequence 模式改变了权重本身,此时每条序列计算一个权重:
拒绝采样指标(当启用 rollout_rs 时)
rollout_rs_masked_fraction:通过拒绝采样被拒绝的 token 比例- 重要 :拒绝采样修改
response_mask(将被拒绝的 token 设为 0) - 与 IS 权重分开 :IS 权重仍然被截断;拒绝是独立的过滤步骤
- 仅在启用
rollout_rs时存在(token/sequence/geometric)
- 重要 :拒绝采样修改
rollout_rs_seq_masked_fraction:至少有一个被拒绝 token 的序列比例- 显示拒绝采样对序列级的影响
- Token 级 RS:如果 ANY token 在 [lower, upper] 之外,则序列被拒绝
- 序列级 RS:整个序列根据序列级比率被拒绝或接受
- 几何 RS:整个序列根据几何均值被拒绝或接受
Off-Policy 诊断指标(训练策略 vs Rollout 策略)
- 关于术语的说明: 这些指标使用 “training” 指代训练参考策略,“rollout” 指代 \(\pi_{\text{rollout}}\)(用于数据收集的行为策略)
- Decoupled 模式 :“training” = \(\pi_{\text{old}}\)(在训练 epoch 开始时计算)
- Bypass/纯 IS 模式 :“training” = \(\pi_\theta\)(当前正在训练的策略)
- 在 Bypass/纯 IS 模式下,指标直接衡量 \(\pi_\theta\) 与 \(\pi_{\text{rollout}}\) 之间的漂移
training_ppl:训练参考策略的困惑度(Decoupled 模式下为 \(\pi_{\text{old}}\),Bypass/纯 IS 模式下为 \(\pi_\theta\))- 公式 :
exp(-mean(log_probs)) - 值越低表示模型置信度越高
- 公式 :
rollout_ppl:Rollout 策略 \(\pi_{\text{rollout}}\) 的困惑度(例如 vLLM BF16)ppl_ratio:训练 PPL 与 Rollout PPL 的比率- 公式 :
exp(mean(log(training_ppl / rollout_ppl))) - 含义 :> 1.0 表示训练不如 Rollout 自信
- 公式 :
training_log_ppl:训练策略的对数困惑度- 有助于识别趋势(线性尺度)
rollout_log_ppl:Rollout 策略的对数困惑度log_ppl_diff:对数困惑度差异的均值- 公式 :
mean(log_ppl_rollout - log_ppl_training) - 符号指示哪个策略更自信
- 公式 :
log_ppl_abs_diff:对数困惑度绝对差异的均值- off-policy 差距的大小,与方向无关
log_ppl_diff_max:序列间最大的对数困惑度差异- 识别最坏情况序列
log_ppl_diff_min:序列间最小的对数困惑度差异kl:KL 散度 KL(\(\pi_{\text{rollout}}\) || \(\pi_\text{training}\))- 公式 :
mean(log_prob_rollout - log_prob_training) - 注意 :可以为负(Rollout 不太自信)
- 公式 :
k3_kl:K3 散度(在期望上等于 KL(\(\pi_{\text{rollout}}\) || \(\pi_\text{training}\)))- 公式 :
mean(exp(log_ratio) - log_ratio - 1) - 比直接 KL 更稳定(每个 token 非负)
- 总是 >= 0
- 公式 :
chi2_token:Token 级的卡方散度- 公式 :
mean(ratio²) - 1,其中 ratio = \(\pi_\text{training}\)/\(\pi_{\text{rollout}}\) - 衡量 IS 权重分布的二阶矩
- 总是非负
- 理解;这个指标相对 kl 等的好处是,对训推不一致的感受更加敏感,往往在 kl 等变化比较平缓时,
chi2_token已经波动较大了
- 公式 :
chi2_seq:序列级的卡方散度- 公式 :
mean((∏_t ratio_t)²) - 1 - IS 权重的序列级二阶矩
- 比 Token 级卡方更敏感
- 理解:这个指标比
chi2_token更敏感,特别在模型的输出长度方差较大时,常样本带来的波动会在这里很敏感的体现出来
- 公式 :
示例:在代码中访问指标
- 代码示例(详情见原文,这里只保留核心阈值思路):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19from verl.trainer.ppo.rollout_corr_helper import compute_rollout_correction_and_rejection_mask
weights_proto, modified_response_mask, metrics = compute_rollout_correction_and_rejection_mask(
old_log_prob=training_log_probs,
rollout_log_prob=rollout_log_probs,
response_mask=response_mask,
rollout_is="token",
rollout_is_threshold=2.0,
rollout_rs="token_k1",
rollout_rs_threshold="0.5_2.0",
)
is_weights = weights_proto.batch["rollout_is_weights"]
if metrics['rollout_corr/rollout_is_mean'] < 0.5 or metrics['rollout_corr/rollout_is_mean'] > 2.0:
# IS 在 [0.5, 2.0] 之外,异常告警
if metrics['rollout_corr/rollout_is_eff_sample_size'] < 0.3:
# 有效样本低于 在 0.3,异常告警
示例:基于指标的条件告警
- 一些告警条件示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47def check_rollout_correction_health(metrics, config):
"""检查 Rollout Correction 指标是否表明训练健康"""
warnings = []
# 检查平均 IS 权重
mean_weight = metrics['rollout_corr/rollout_is_mean']
if mean_weight < 0.5 or mean_weight > 2.0:
warnings.append(f"平均 IS 权重 {mean_weight:.3f} 远离 1.0")
# 检查有效样本量
ess = metrics['rollout_corr/rollout_is_eff_sample_size']
if ess < 0.3:
warnings.append(f"有效样本量 {ess:.3f} 过低")
# 检查标准差
std = metrics['rollout_corr/rollout_is_std']
if std > 1.0:
warnings.append(f"IS 权重标准差 {std:.3f} 过高")
# 检查 KL 散度
kl = metrics['rollout_corr/kl']
if abs(kl) > 0.1:
warnings.append(f"KL 散度 {kl:.3f} 表明存在显著 off-policy 差距")
# 检查卡方散度
if 'rollout_corr/chi2_token' in metrics:
chi2_token = metrics['rollout_corr/chi2_token']
if chi2_token > 1.0:
warnings.append(f"卡方散度(token){chi2_token:.3f} 表明存在严重分布偏移")
if warnings:
print("Rollout Correction 健康警告:")
for warning in warnings:
print(f" - {warning}")
return False
else:
print("Rollout Correction 指标看起来健康")
return True
_,_, metrics = compute_rollout_correction_and_rejection_mask(...)
is_healthy = check_rollout_correction_health(metrics, config)
if not is_healthy:
print("考虑:")
print(" - 收紧 rollout_is_threshold")
print(" - 切换到几何聚合级别")
print(" - 检查 Rollout 和训练策略是否差异过大")
Running Examples
从基本的 Token 级截断配置开始:
1
bash examples/rollout_correction/run_qwen2_5_7b_fsdp.sh
在调整参数之前监控指标 1-2 个 epoch
Configuration Examples
示例 1:仅 IS 权重(Token 级)
1
2
3
4
5algorithm:
rollout_correction:
rollout_is: token
rollout_is_threshold: 2.0
rollout_rs: null # 不进行拒绝采样示例 2:仅拒绝采样(无 IS 权重)
1
2
3
4
5algorithm:
rollout_correction:
rollout_is: null # 无 IS 权重
rollout_rs: token_k1
rollout_rs_threshold: "0.5_2.0"示例 3:同时使用 IS 和 RS(Token RS)
1
2
3
4
5
6algorithm:
rollout_correction:
rollout_is: token
rollout_is_threshold: 2.0
rollout_rs: token_k1
rollout_rs_threshold: "0.5_2.0"示例 5: Bypass Mode + PPO-clip(默认)
1
2
3
4
5
6
7
8algorithm:
rollout_correction:
rollout_is: token
rollout_is_threshold: 2.0
rollout_rs: token_k1
rollout_rs_threshold: "0.5_2.0"
bypass_mode: true # 跳过 old_log_prob 计算
loss_type: ppo_clip # PPO 裁剪目标(默认)示例 6: Bypass Mode + REINFORCE,无 PPO 裁剪,纯策略梯度 + IS 校正
1
2
3
4
5
6rollout_correction:
rollout_is: sequence # 损失中的显式 IS 校正
rollout_is_threshold: 2.0
rollout_rs: null # 可选:可以添加拒绝采样
bypass_mode: true
loss_type: reinforce # 带显式 IS 权重的 REINFORCE示例 7: Bypass Mode + PPO-clip + 拒绝采样,PPO 裁剪 + 拒绝采样,IS 由 PPO 比率处理(无显式 IS 权重)
1
2
3
4
5
6
7rollout_correction:
rollout_is: sequence # 为指标计算
rollout_is_threshold: 2.0
rollout_rs: seq_max_k2 # 序列最大 χ²/2 守卫
rollout_rs_threshold: 2.5
bypass_mode: true
loss_type: ppo_clip # PPO 裁剪目标(IS 由比率处理)- 问题:这里不担心 PPO Clip 和 rollout_is 两次校准重复了吗?
- 回答:不用担心,源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28# Dispatch to appropriate loss function based on loss_type
if loss_type == "reinforce":
# REINFORCE: Apply IS weights explicitly
pg_loss, pg_metrics = compute_policy_loss_reinforce(
rollout_log_prob=rollout_log_prob,
log_prob=log_prob,
advantages=advantages,
response_mask=effective_mask,
loss_agg_mode=loss_agg_mode,
config=config,
# 下面这一行是核心,REINFORCE 没有 ppo_clip,需要加入 computed_is_weights 作为训推不一致的校正
rollout_is_weights=computed_is_weights,
)
elif loss_type == "ppo_clip":
# PPO-clip: The ratio π_current/π_old = π_current/π_rollout already handles IS
# DO NOT apply IS weights - would be double-counting!
# The clipping mechanism constrains the effective IS ratio
pg_loss, pg_metrics = compute_policy_loss_vanilla( # type: ignore[call-arg]
old_log_prob=rollout_log_prob, # = old_log_prob in bypass mode
log_prob=log_prob,
advantages=advantages,
response_mask=effective_mask,
loss_agg_mode=loss_agg_mode,
config=config,
# 下面这一行是核心,保证即使 computed_is_weights 在之前有值(有时会为了上报指标而打开 rollout_is),也不影响 Loss 计算
rollout_is_weights=None, # Explicitly None - no IS weights for PPO-clip
)
Troubleshooting
问题:IS 权重分布范围过大
- 问题表现:
rollout_is_std> 1.0,rollout_is_eff_sample_size< 0.3 - 解决方案:
- 1)从
sequence切换到geometric级别 - 2)收紧阈值
- 3)验证 Rollout 和训练差异是否过大
- 1)从
问题:平均 IS 权重远离 1.0
- 问题表现:
rollout_is_mean< 0.5 或 > 2.0 - 解决方案:
- 1)确认已设置
calculate_log_probs=True - 2)检查
rollout_log_probs是否正确传递 - 3)检查是否存在系统性分布偏移
- 1)确认已设置
调试:可视化指标
- 示例:绘制 IS 权重分布
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52import matplotlib.pyplot as plt
import numpy as np
def plot_is_metrics(metrics_history):
"""绘制随训练步数变化的 Rollout IS 指标"""
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
# 图 1:平均 IS 权重随时间变化
axes[0, 0].plot(metrics_history['rollout_corr/rollout_is_mean'])
axes[0, 0].axhline(y=1.0, color='r', linestyle='--', label='理想值')
axes[0, 0].set_title('平均 IS 权重')
axes[0, 0].set_xlabel('步数')
axes[0, 0].legend()
# 图 2:有效样本量
axes[0, 1].plot(metrics_history['rollout_corr/rollout_is_eff_sample_size'])
axes[0, 1].axhline(y=0.5, color='g', linestyle='--', label='良好')
axes[0, 1].axhline(y=0.3, color='r', linestyle='--', label='警告')
axes[0, 1].set_title('有效样本量')
axes[0, 1].set_xlabel('步数')
axes[0, 1].legend()
# 图 3:KL 散度随时间变化
axes[1, 0].plot(metrics_history['rollout_corr/kl'], label='KL')
axes[1, 0].plot(metrics_history['rollout_corr/k3_kl'], label='K3 KL')
axes[1, 0].axhline(y=0, color='g', linestyle='--', alpha=0.3)
axes[1, 0].set_title('KL 散度')
axes[1, 0].set_xlabel('步数')
axes[1, 0].legend()
# 图 4:PPL 比率随时间变化
axes[1, 1].plot(metrics_history['rollout_corr/ppl_ratio'])
axes[1, 1].axhline(y=1.0, color='r', linestyle='--', label='理想值')
axes[1, 1].set_title('PPL 比率(训练/Rollout)')
axes[1, 1].set_xlabel('步数')
axes[1, 1].legend()
# 图 5:卡方散度
if 'rollout_corr/chi2_token' in metrics_history:
axes[1, 2].plot(metrics_history['rollout_corr/chi2_token'], label='Token 级')
if 'rollout_corr/chi2_seq' in metrics_history:
axes[1, 2].plot(metrics_history['rollout_corr/chi2_seq'], label='序列级')
axes[1, 2].axhline(y=1.0, color='r', linestyle='--', label='警告')
axes[1, 2].set_title('卡方散度')
axes[1, 2].set_xlabel('步数')
axes[1, 2].legend()
else:
axes[1, 2].axis('off')
plt.tight_layout()
plt.savefig('rollout_is_metrics.png', dpi=150)
print("已将图表保存为 rollout_is_metrics.png")
补充:其他事项
Performance Impact
- 内存开销 :约为模型内存的 1%
- 计算开销 :取决于级别,约为 1-3%
- 训练稳定性 :当存在 off-policy 差距时显著提高
Testing
运行测试套件以验证一切正常:
1
2
3
4
5# 基本单元测试
python tests/trainer/ppo/test_rollout_corr.py
# 集成测试(如果 pytest 可用)
pytest tests/trainer/ppo/test_rollout_corr_integration.py -v预期输出:所有测试通过