# verl 中推演修正方法的数学公式 **作者:** [Yingru Li](https://richardli.xyz) **更新日期:** 2025-11-04 --- > **📖 文档结构** > - **本文档** - 理论:公式、推导和算法基础 > - **[推演修正使用指南](rollout_corr.md)** - 实践实现:配置、预设和故障排除 > > 从这里开始学习理论和设计理念,然后参考使用指南了解实现细节。 --- ## 摘要 本文档提供了 `verl` 中推演修正方法的确切数学公式,其演进遵循**REINFORCE** 到 **PPO** 再到**解耦 PPO** 的自然逻辑。 推演修正提供了一个统一的框架,用于处理 RL 训练中的**一般离策略问题**,即任何数据收集分布与训练分布不同的场景。 **适用场景包括:** - **策略不匹配**:不同精度(FP8 与 FP16 与 BF16 与 FP32)、不同后端(vLLM 与 SGLang 与 FSDP 与 Megatron) - **时间延迟**:模型过时、异步推演工作者 - **回放缓冲区**:基于早期策略版本的历史轨迹进行训练 - **离策略算法**:行为克隆、DAPO、专家演示 - **数据过滤**:重新加权、偏好学习、课程学习 --- ## 目录 1. [理论基础:从 REINFORCE 到解耦 PPO](#1-理论基础从-reinforce-到解耦-ppo) 2. [verl 中的实现:三策略框架](#2-verl-中的实现三策略框架) 3. [算法组件和组合](#3-算法组件和组合) 4. [离策略诊断指标](#4-离策略诊断指标) 5. [总结和决策指南](#5-总结和决策指南) 6. [实现参考](#6-实现参考) --- ## 1. 理论基础:从 REINFORCE 到解耦 PPO 本节奠定了 `verl` 实现的理论演进基础。 ### 1.1 REINFORCE:策略梯度基线 REINFORCE 算法([Williams, 1992](https://doi.org/10.1007/BF00992696))是策略梯度方法的基石。 **传统 REINFORCE(联策略)** 对于从当前策略 $\pi_\theta$ 采样得到的轨迹 $\tau = (s_0, a_0, s_1, a_1, \ldots, s_T, a_T)$,策略梯度为: $$ \nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta} \left[ \sum_{t=0}^T \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot A_t \right] $$ 其中 $A_t$ 是时间步 $t$ 的优势函数。 **离策略 REINFORCE** 当轨迹从不同的行为策略 $\mu$ 采样时,我们对**联合轨迹分布**应用重要性采样: $$ \nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim \mu} \left[ \frac{P_{\pi_\theta}(\tau)}{P_\mu(\tau)} \sum_{t=0}^T \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot A_t \right] $$ 其中轨迹级重要性权重为: $$ \frac{P_{\pi_\theta}(\tau)}{P_\mu(\tau)} = \frac{p(s_0) \prod_{t=0}^T \pi_\theta(a_t|s_t) p(s_{t+1}|s_t, a_t)}{p(s_0) \prod_{t=0}^T \mu(a_t|s_t) p(s_{t+1}|s_t, a_t)} = \prod_{t=0}^T \frac{\pi_\theta(a_t|s_t)}{\mu(a_t|s_t)} $$ 转移动态 $p(s_{t+1}|s_t, a_t)$ 和初始状态 $p(s_0)$ 被抵消,只剩下每个步骤动作概率比的乘积。 **关键特性:** - **离策略能力**:通过重要性采样,可以从任何行为策略学习 - **无信任区域**:策略更新不受约束 **在 verl 中的实现:**`pg_is` 方法实现了具有截断重要性采样的离策略 REINFORCE。 ### 1.2 PPO:添加信任区域控制 近端策略优化([Schulman et al., 2017](https://arxiv.org/abs/1707.06347))添加了剪切的代理目标: $$ L_{\text{PPO}}(\theta) = -\mathbb{E}_{(s,a) \sim \mu} \left[ \min\left( r_t(\theta) A_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) A_t \right) \right] $$ 其中 $r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\mu(a_t|s_t)}$,且 $\epsilon$ 是剪切范围(通常为 0.2)。 **关键特性:** - **两种策略**:$\mu$(用于剪切的参考)和 $\pi_\theta$(正在更新) - **通过剪切的信任区域**:通过比值 $r_t(\theta) = \frac{\pi_\theta}{\mu}$ 限制策略更新大小 ### 1.3 解耦 PPO:实现批次大小不变性 解耦 PPO([Hilton et al., 2021](https://arxiv.org/abs/2110.00641))通过**解耦两个角色**解决了 PPO 的批次大小敏感性: 1. **近端策略** $\pi_{\text{prox}}$:PPO 剪切的锚定策略(控制策略更新大小) 2. **行为策略** $\mu$:收集数据的策略(用于通过重要性采样进行离策略修正) **问题在于**:标准 PPO 控制策略更新大小通过比值 $\frac{\pi_\theta}{\pi_{\text{old}}}$,其中 $\pi_{\text{old}}$ 被假设为近端策略*和*行为策略。这两个角色的耦合使得算法对批次大小敏感,因为聚合来自多个工作者的数据或使用回放缓冲区会改变有效的行为策略。 **解决方案**:解耦这两个角色,导致**三策略公式**: $$ L_{\text{DecoupledPPO}}(\theta) = -\mathbb{E}_{(s,a) \sim \mu} \left[ w_t \cdot \min\left( r_t(\theta) A_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) A_t \right) \right] $$ 其中: - $w_t = \frac{\pi_{\text{prox}}(a_t|s_t)}{\mu(a_t|s_t)}$:重要性采样权重(修正行为策略 $\mu$)。此处 $\pi_{\text{prox}}$ 在训练期间冻结,所以 $w_t$ 是常量(无需 stopgrad 操作符)。 - $r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\text{prox}}(a_t|s_t)}$:PPO 比值(针对近端策略 $\pi_{\text{prox}}$ 控制策略更新大小) **关键特性**:通过解耦: - **批次大小不变性**:策略更新控制(通过 $\pi_{\text{prox}}$)独立于数据聚合 - **灵活的行为策略**:任何 $\mu$ 都可以使用(不同工作者、回放缓冲区或过时检查点) - **过时数据利用**:通过重要性采样可以修正较旧的轨迹 - **保留剪切**:针对 $\pi_{\text{prox}}$ 的剪切限制更新大小 **这是 `verl` 通过其三策略框架实现的算法。** --- ## 2. verl 中的实现:三策略框架 `verl` 库使用三种不同的策略实现了解耦 PPO,每种策略服务于特定角色。 ### 2.1 策略角色和符号 **$\pi_{\text{rollout}}$(行为策略 $\mu$)** 用于数据收集的策略。这是理论中的行为分布 $\mu$。 - **创建时机**:数据收集/推演阶段 - **目的**:生成训练轨迹 - **常见来源**: - 策略不匹配:相同权重、不同实现(精度、后端) - 时间延迟:异步工作者中的过时检查点 - 回放缓冲区:迭代早期历史数据 - 离策略算法:专家演示、辅助策略(DAPO) - 数据过滤:重新加权或过滤数据 - **固定**:在批次上的训练期间冻结 **$\pi_{\text{old}}$(近端策略 $\pi_{\text{prox}}$)** PPO 剪切的参考策略。这是解耦 PPO 理论中的“近端策略”。 - **创建时机**: - **解耦模式**:在每个训练周期开始时通过 `actor.compute_log_prob()` 计算 - **旁路模式**:设为等于 $\pi_{\text{rollout}}$(跳过单独计算) - **目的**: - PPO 剪切的锚定点(控制策略更新大小) - 当与 $\pi_{\text{rollout}}$ 分开时:实现批次大小不变性和高效过时数据利用 - **固定**:在相同批次的所有 PPO 更新周期冻结 **$\pi_{\theta}$(当前策略)** 正在训练中积极优化的策略。 - **更新**:每个梯度步骤 - **目的**:我们正在改进的策略 ### 2.2 操作模式 三策略框架可以以两种模式操作: **解耦模式(三种策略)** - 在每个训练周期开始时单独计算 $\pi_{\text{old}}$ - **算法**:具有三种策略的完整解耦 PPO(数学上正确) - **特性**:实现批次大小不变性;单独修正漂移 1(rollout→old)和漂移 2(old→current) **旁路模式(两种策略)** - 设 $\pi_{\text{old}} = \pi_{\text{rollout}}$(跳过单独计算) - **算法**:使用 $\pi_{\text{rollout}}$ 作为行为策略和近端策略(数学上正确) - **关键差异**:近端策略等于行为策略,所以不需要它们之间的 IS 修正 - **特性**:更快(跳过 `actor.compute_log_prob()` 调用);不实现批次大小不变性 ### 2.3 两种分布漂移 三策略框架处理两种类型的分布漂移: **漂移 1:$\pi_{\text{rollout}} \to \pi_{\text{old}}$(离策略差距)** 这是数据收集策略和训练参考策略之间的分布漂移。 - **性质**:从微不足道(相同检查点、轻微差异)到严重(回放缓冲区、专家数据) - **修正**:重要性采样权重 $w_t = \frac{\pi_{\text{old}}(a_t|s_t)}{\pi_{\text{rollout}}(a_t|s_t)}$ - **可选**:当微不足道时,可以忽略(旁路模式) **漂移 2:$\pi_{\text{old}} \to \pi_{\theta}$(策略更新漂移)** 这是策略参数更新期间从策略漂移。 - **性质**:梯度下降更新 $\pi_\theta$ 时发生 - **修正**:PPO 剪切对比值 $r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\text{old}}(a_t|s_t)}$ - **通用**:应用于联策略和离策略训练 ### 2.4 符号总结 - $\pi_{\text{rollout}}$:行为策略(数据收集) - $\pi_{\text{old}}$:近端策略(PPO 锚点) - $\pi_{\theta}$:当前策略(正在更新) - $\rho_t = \frac{\pi_{\text{old}}(a_t|s_t)}{\pi_{\text{rollout}}(a_t|s_t)}$:每个 token 的 IS 比值(修正漂移 1) - $r_t(\theta) = \frac{\pi_{\theta}(a_t|s_t)}{\pi_{\text{old}}(a_t|s_t)}$:PPO 比值(修正漂移 2) - $A_t$:token $t$ 的优势 - $T$:序列中有效 token 的集合 - $C_{\text{IS}}$:IS 权重的上阈值(例如,2.0) - $C_{\text{RS-upper}}$:RS mask 的上阈值(例如,2.0) - $C_{\text{RS-lower}}$:RS mask 的下阈值(通常 $1/C_{\text{RS-upper}}$) - $\epsilon$:PPO 剪切范围(通常 0.2) --- ## 3. 算法组件和组合 `verl` 中的推演修正框架由**正交组件**构建,这些组件可以灵活组合: 1. **操作模式**:$\pi_{\text{old}}$ 的计算方式(解耦 vs 旁路) 2. **损失函数**:PPO(带剪切)vs 纯 IS(仅策略梯度) 3. **IS/RS 聚合级别**:Token、序列或几何 4. **安全机制**:拒绝极端异常值 本节解释每个组件及其有效组合。 ### 3.1 操作模式:解耦 vs 旁路 操作模式决定了近端策略 $\pi_{\text{old}}$ 的计算方式。 #### 3.1.1 解耦模式(三种策略) **配置:** `bypass_mode = false` **策略设置:** - $\pi_{\text{rollout}}$:行为策略(数据收集) - $\pi_{\text{old}}$:近端策略(通过 `actor.compute_log_prob()` 计算) - $\pi_{\theta}$:当前策略(正在更新) **IS 比值:** $\rho_t = \frac{\pi_{\text{old}}(a_t|s_t)}{\pi_{\text{rollout}}(a_t|s_t)}$(修正漂移 1:rollout→old) **PPO 比值:** $r_t(\theta) = \frac{\pi_{\theta}(a_t|s_t)}{\pi_{\text{old}}(a_t|s_t)}$(修正漂移 2:old→current) **特性:** - ✅ 实现批次大小不变性 - ✅ 分别修正两种分布漂移 - ✅ 高效过时数据利用 - ❌ 需要额外的前向传递(`actor.compute_log_prob()`) #### 3.1.2 旁路模式(两种策略) **配置:** `bypass_mode = true` **策略设置:** - $\pi_{\text{rollout}}$:行为策略(数据收集) - $\pi_{\text{old}} = \pi_{\text{rollout}}$:近端策略等于行为策略 - $\pi_{\theta}$:当前策略(正在更新) **比值:** - **使用 PPO 损失时**(`use_policy_gradient = false`):无单独 IS 计算;PPO 比值 $r_t(\theta) = \frac{\pi_{\theta}(a_t|s_t)}{\pi_{\text{rollout}}(a_t|s_t)}$ 针对 rollout 策略剪切 - **使用策略梯度损失时**(`use_policy_gradient = true`):IS 比值 $\rho_t = \frac{\pi_{\theta}(a_t|s_t)}{\pi_{\text{rollout}}(a_t|s_t)}$ 在损失函数中即时计算 **特性:** - ✅ 跳过 `actor.compute_log_prob()` 调用(更快) - ✅ 处理基于 IS/RS 的离策略修正(当使用策略梯度时) - ✅ 使用两种策略而不是三种($\pi_{\text{rollout}} = \pi_{\text{old}}$) - ⚠️ 与解耦模式不同,不分离近端策略和行为策略 ### 3.2 损失函数:PPO vs 策略梯度 #### 3.2.1 PPO 损失(带剪切) **配置:** `use_policy_gradient = false` **损失函数:** $$ L_{\text{PPO}}(\theta) = -\mathbb{E}_t \left[ w_t \cdot \min\left( r_t(\theta) A_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) A_t \right) \right] $$ 其中: - $w_t$:IS 权重(取决于聚合级别,见第 3.3 节)。在解耦模式中,$w_t = \frac{\pi_{\text{old}}}{\pi_{\text{rollout}}}$($\pi_{\text{old}}$ 冻结,所以 $w_t$ 是常量,无需 stopgrad)。在旁路模式下,使用 PPO 损失时,通常不计算单独 IS 权重。 - $r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\text{old}}(a_t|s_t)}$:PPO 比值 - $\epsilon$:剪切范围(通常 0.2) **特性:** - 通过剪切提供信任区域控制 - 限制策略更新大小 - RL 训练的标准做法 #### 3.2.2 策略梯度损失(带 IS/RS 修正) **配置:** `use_policy_gradient = true`(需要 `bypass_mode = true`) **损失函数**(以序列级 IS 为例): $$ L_{\text{PG}}(\theta) = -\mathbb{E}_{(s,a) \sim \pi_{\text{rollout}}} \left[ \text{stopgrad}(w_{\text{seq}}(\theta)) \cdot \sum_{t \in T} \log \pi_{\theta}(a_t|s_t) \cdot A_t \right] $$ 其中: - $w_{\text{seq}}(\theta)$:样本权重(IS 或 RS,见 §3.3-3.4) - 对于 IS:$w_{\text{seq}}(\theta) = \min\left( \prod_{t \in T} \frac{\pi_{\theta}(a_t|s_t)}{\pi_{\text{rollout}}(a_t|s_t)}, C_{\text{IS}} \right)$ - 对于 RS:$w_{\text{seq}}(\theta) \in \{0, 1\}$(二进制拒绝 mask) - **stopgrad 操作符**:权重 $w_{\text{seq}}(\theta)$ 使用 $\pi_\theta$ 计算,但被视为**常系数**(在计算 $\nabla_\theta L$ 时)。这对于重要性采样的正确性至关重要(见下面的理论理由)。 **有效梯度:** $$ \nabla_\theta L_{\text{PG}} = -\mathbb{E}_{(s,a) \sim \pi_{\text{rollout}}} \left[ \text{stopgrad}(w_{\text{seq}}(\theta)) \cdot \sum_{t \in T} \nabla_\theta \log \pi_{\theta}(a_t|s_t) \cdot A_t \right] $$ **理论理由 for stopgrad:** stopgrad 操作符是重要性采样理论**必需的**,不是实现细节。以下是原因: **基本原理**:重要性采样是一种**改变度量**的技术,用于从一个分布重新加权样本以估计另一个分布下的期望,而不是优化重新加权函数本身。 **正式推导**: 1. **原始目标**:我们想要优化 $J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta}[\sum_t A_t]$。 2. **离策略设置**:我们只有从 $\pi_{\text{rollout}}$ 采样,所以使用重要性采样: $$ J(\theta) = \mathbb{E}_{\tau \sim \pi_{\text{rollout}}} \left[ \underbrace{\frac{P_{\pi_\theta}(\tau)}{P_{\pi_{\text{rollout}}}(\tau)}}_{w(\tau;\theta)} \sum_t A_t \right] $$ 3. **计算策略梯度**:正确的梯度使用**在重要性采样之前的策略梯度定理**: $$ \begin{aligned} \nabla_\theta J(\theta) &= \nabla_\theta \mathbb{E}_{\tau \sim \pi_\theta}\left[\sum_t A_t\right] \\ &= \mathbb{E}_{\tau \sim \pi_\theta} \left[\sum_t A_t \nabla_\theta \log \pi_\theta(a_t|s_t) \right] \quad \text{(策略梯度定理)} \\ &= \mathbb{E}_{\tau \sim \pi_{\text{rollout}}} \left[ w(\tau;\theta) \sum_t A_t \nabla_\theta \log \pi_\theta(a_t|s_t) \right] \quad \text{(度量变化)} \end{aligned} $$ 在最终行中,$w(\tau;\theta)$ 作为来自度量变化的**乘积系数**出现,而不是我们想要微分的东西。 4. **没有 stopgrad 出错的地方**:如果我们在损失中天真地计算 $\nabla_\theta \left[w(\theta) \log \pi_\theta \right]$,我们得到: $$ \nabla_\theta \left[w(\theta) \log \pi_\theta \right] = \underbrace{\log \pi_\theta \cdot \nabla_\theta w(\theta)}_{\text{错误:偏差项}} + \underbrace{w(\theta) \cdot \nabla_\theta \log \pi_\theta}_{\text{正确:IS 加权梯度}} $$ 第一个项 $\log \pi_\theta \cdot \nabla_\theta w(\theta)$ 是计算技巧的副产物,不是真实策略梯度的一部分。它会偏差梯度估计器并优化不同于 $J(\theta)$ 的目标。 5. **实现要求**:在 PyTorch 中,为了只计算第二个项,我们必须使用: ```python loss = -advantages * log_prob * rollout_is_weights.detach() # stopgrad on weights ``` 没有 `.detach()`,autograd 会计算两项,给出不正确的梯度。 **直觉**:IS 权重 $w(\theta)$ 告诉我们“对估计 $\pi_\theta$ 下的梯度多信任这个样本”。我们使用重新加权的目标更新 $\theta$,但我们不会更新 $\theta$ 来最大化权重本身——那会是循环推理(优化修正因子而不是实际目标)。 **特性:** - **算法**:离策略 REINFORCE + IS/RS 修正 - **无 PPO 剪切**:纯策略梯度 - **始终使用旁路模式**:直接 $\pi_\theta$ 到 $\pi_{\text{rollout}}$ 比较 - **快速**:单前向传递 **实现:**`compute_policy_loss_with_rollout_correction()` 在 [core_algos.py](../../verl/trainer/ppo/core_algos.py#L1537-L1681) 中 --- ### 3.3 IS/RS 聚合级别 聚合级别决定了每个 token 概率比如何组合到 IS 权重和/或拒绝 mask 中。这个选择**正交于操作模式**——你可以在解耦或旁路模式中使用任何聚合级别。 #### 3.3.1 Token 级聚合 **IS 权重:** $w_t = \min(\rho_t, C_{\text{IS}})$ 其中 $\rho_t = \frac{\pi_{\text{old}}(a_t|s_t)}{\pi_{\text{rollout}}(a_t|s_t)}$(解耦)或 $\rho_t = \frac{\pi_{\theta}(a_t|s_t)}{\pi_{\text{rollout}}(a_t|s_t)}$(旁路/纯 IS) **配置:** ```python rollout_is = "token" # IS 权重 rollout_rs = "token" # 可选:拒绝采样 ``` **特性:** - 每个 token 独立截断 - 比序列级方差更低(比值乘积单独限制) - 典型阈值:1.5 - 5.0 - 可选批次归一化(第 3.6 节):在所有 token 权重上归一化以确保 $\mathbb{E}[\tilde{w}_t] = 1$(减少方差) **损失函数(REINFORCE + Token IS):** $$ L_{\text{REINFORCE+TIS}}(\theta) = -\mathbb{E}_t \left[ \text{stopgrad}(w_t) \cdot \log \pi_\theta(a_t|s_t) \cdot A_t \right] $$ 其中 $w_t = \min(\rho_t, C_{\text{IS}})$ 是截断的 token 级 IS 权重。stopgrad 操作符确保当计算 $\nabla_\theta L$ 时,权重被视为常量(见 §3.2.2 的理论理由)。这个公式也可以与 PPO 剪切结合,通过替换 REINFORCE 梯度为剪切的代理目标。 **实现:** - IS 权重:`compute_rollout_correction_weights()` 在 [rollout_corr_helper.py](../../verl/trainer/ppo/rollout_corr_helper.py#L325-L402) - 损失:`compute_policy_loss()` 在 [core_algos.py](../../verl/trainer/ppo/core_algos.py#L812-L884) #### 3.3.2 序列级聚合 **IS 权重:** $w_{\text{seq}} = \min\left( \prod_{t \in T} \rho_t, C_{\text{IS}} \right) = \min\left( \exp\left(\sum_{t \in T} \log \rho_t\right), C_{\text{IS}} \right)$(广播到所有 token) **配置:** ```python rollout_is = "sequence" # IS 权重 rollout_rs = "sequence" # 可选:拒绝采样 ``` **特性:** - 跨序列乘积聚合 - 比 token 级更敏感异常值 - 典型阈值:2.0 - 10.0 - 可选批次归一化(第 3.6 节):在序列均值上归一化(每序列一个权重) **损失函数(REINFORCE + 序列 IS):** $$ L_{\text{REINFORCE+SeqIS}}(\theta) = -\mathbb{E}_t \left[ \text{stopgrad}(w_{\text{seq}}) \cdot \log \pi_\theta(a_t|s_t) \cdot A_t \right] $$ 其中 $w_{\text{seq}}$ 广播到序列中的所有 token。stopgrad 操作符确保正确的 IS 梯度计算(见 §3.2.2)。这个公式也可以与 PPO 剪切结合。 #### 3.3.3 几何聚合 **IS 权重(仅用于拒绝):** $\rho_{\text{geo}} = \exp\left( \frac{1}{|T|} \sum_{t \in T} \log \rho_t \right) = \left(\prod_{t \in T} \rho_t\right)^{1/|T|}$(广播到所有 token) **配置:** ```python rollout_is = null # 无 IS 权重,纯拒绝 rollout_rs = "geometric" # 仅拒绝采样 ``` **特性:** - 每个 token 比值的几何平均值 - 比算术乘积(序列级)更敏感 - 典型阈值:1.0001 - 1.001(比序列/token 级更严格) - **仅用于拒绝采样,不用于 IS 加权** **为什么严格阈值?** 对于每个 $\rho_t = 1.01$ 的 100 个 token: - 算术乘积:$\prod_{t=1}^{100} \rho_t = 1.01^{100} \approx 2.7$ - 几何平均值:$(1.01)^{1} = 1.01$ 1.001 的阈值意味着拒绝平均每个 token 偏差 > 0.1%的序列。 **损失函数(REINFORCE + 几何 RS):** $$ L_{\text{GeoRS}}(\theta) = -\mathbb{E}_{(s,a) \mid \text{seq} \in \mathcal{A}_{\text{geo}}} \left[ \sum_{t \in T} \log \pi_\theta(a_t|s_t) \cdot A_t \right] $$ 其中 $\mathcal{A}_{\text{geo}} = \{ \text{seq} : C_{\text{RS-lower}} \leq \rho_{\text{geo}} \leq C_{\text{RS-upper}} \}$ 是接受集合(拒绝 mask)。无 IS 权重,所以无需 stopgrad。这个公式也可以与 PPO 剪切结合。 --- ### 3.4 拒绝采样 (RS) 拒绝采样可以添加到**任何操作模式和聚合级别的组合**中。它修改 `response_mask` 以排除异常值 token/序列。 **配置:** ```python rollout_rs = "token" # 或 "sequence" 或 "geometric" rollout_rs_threshold = 2.0 # 上阈值 rollout_rs_threshold_lower = 0.5 # 下阈值(如果为 null 则自动取倒数) ``` **接受集合:** - **Token 级**:$\mathcal{A}_{\text{token}} = \{ t : C_{\text{RS-lower}} \leq \rho_t \leq C_{\text{RS-upper}} \}$ - **序列级**:$\mathcal{A}_{\text{seq}} = \{ \text{seq} : C_{\text{RS-lower}} \leq \prod_{t \in T} \rho_t \leq C_{\text{RS-upper}} \}$ - **几何**:$\mathcal{A}_{\text{geo}} = \{ \text{seq} : C_{\text{RS-lower}} \leq \rho_{\text{geo}} \leq C_{\text{RS-upper}} \}$ **特性:** - 与 IS 加权分离(可以无 IS 使用 RS) - 减少有效样本大小 - 过滤极端异常值 **实现:**`compute_rollout_rejection_mask()` 在 [rollout_corr_helper.py](../../verl/trainer/ppo/rollout_corr_helper.py#L80-L188) --- ### 3.5 否决机制 一个**独立**的安全层,用于拒绝在 token 概率极低的情况下整个序列。 **配置:** ```python rollout_token_veto_threshold = 1e-4 # null = 禁用 ``` **否决条件:** $$ \text{如果存在 } t \in T \text{ 使得 } \rho_t < C_{\text{veto}} \text{ 则拒绝整个序列} $$ **特性:** - 防止来自概率近零 token 的灾难性更新 - **独立**于 IS/RS 设置(如果启用则始终应用) - 在安全边界之前检查**未钳位的每个 token 比值** - 典型值:$10^{-4}$ 到 $10^{-6}$ **实现:** [rollout_corr_helper.py](../../verl/trainer/ppo/rollout_corr_helper.py#L620-L640) --- ### 3.6 批次归一化 一个可选的方差减少技术,用于将 IS 权重归一化到批次中均值为 1.0。 **配置:** ```python rollout_is_batch_normalize = True # 默认:False ``` **归一化公式(聚合感知):** 对于**token 级 IS**(第 3.3.1 节): $$ \tilde{w}_t = \frac{w_t}{\frac{1}{\sum_{i,t} m_{i,t}} \sum_{i,t} w_{i,t} \cdot m_{i,t}} $$ 其中 $w_{i,t}$ 是截断的 token IS 权重,$m_{i,t}$ 是响应 mask,且归一化在**所有 token**上进行。 对于**序列级 IS**(第 3.3.2 节): $$ \tilde{w}_i = \frac{w_i}{\frac{1}{B}\sum_{j=1}^B \bar{w}_j} $$ 其中 $\bar{w}_j = \frac{1}{T_j}\sum_{t=1}^{T_j} w_{j,t} \cdot m_{j,t}$ 是每个序列的均值(一个序列的所有 token 具有相同权重),且归一化在**序列**上进行。 **特性:** - 在截断**后**应用以保留截断语义 - 确保 $\mathbb{E}[\tilde{w}] = 1$ 在每个批次内 - **聚合感知**:Token 级在 token 上归一化;序列级在序列上归一化 - 使用 `masked_mean` 来尊重填充 token - 通过移除批次级随机规模波动减少梯度幅度方差 **指标:** - `rollout_is_batch_norm_factor`:应用的前归一化批次均值 **实现:** [rollout_corr_helper.py](../../verl/trainer/ppo/rollout_corr_helper.py#L401-L421) --- ### 3.7 组合矩阵 #### 可用的预设方法 | 预设方法 | 模式 | IS 级别 | RS 级别 | 特性 | |---------------|------|----------|----------|------------| | `decoupled_token_is()` | 解耦 | token | - | 每个 token IS 权重 | | `decoupled_seq_is()` | 解耦 | 序列 | - | 序列级 IS 权重 | | `decoupled_seq_is_rs()` | 解耦 | 序列 | 序列 | 序列 IS + 序列 RS | | `decoupled_geo_rs()` | 解耦 | - | 几何 + 否决 | 几何 RS + 否决,无 IS 权重 | | `ppo_is_bypass()` | 旁路 | - | - | 旁路模式,跳过 old_log_prob | | `pg_rs()` | 旁路 | - | 几何 + 否决 | 策略梯度与 RS(无 IS 权重) | | `pg_is()` | 旁路 | 序列 | - | 策略梯度与 IS | | `disabled()` | - | - | - | 仅指标,无修正 | **注意:** 所有预设使用 PPO 损失,除了 `pg_is()` 和 `pg_rs()` 使用策略梯度(两者需要 `use_policy_gradient=True`)。 #### 附加支持的组合(手动配置) 这些组合**完全支持**但需要手动配置: **1. Token IS + Token RS** ```python config = RolloutCorrectionConfig( rollout_is="token", rollout_is_threshold=2.0, rollout_rs="token", rollout_rs_threshold=2.0, ) ``` **特性:** Token 级 IS 权重 + token 级 RS mask。 **2. 纯 Token RS** ```python config = RolloutCorrectionConfig( rollout_is=None, rollout_rs="token", rollout_rs_threshold=2.0, ) ``` **特性:** Token 级 RS mask,仅此而已,无 IS 权重。 **3. 纯序列 RS** ```python config = RolloutCorrectionConfig( rollout_is=None, rollout_rs="sequence", rollout_rs_threshold=2.0, ) ``` **特性:** 序列级 RS mask,仅此而已,无 IS 权重。 **关键特性:** - 任何 IS 聚合级别(token/序列)可以在解耦或旁路模式中使用 - 拒绝采样可以添加到任何组合中 - 否决独立,可以添加到任何组合 - 几何聚合通常仅用于 RS(不用于 IS 加权) - 纯 RS(`pg_rs`)使用旁路 + 几何 RS 与 `use_policy_gradient=True` 进行纯策略梯度(无 IS 权重) - 上表中的所有组合都有效,由实现支持 --- ### 3.8 常见实现错误 #### 错误的 LLM-RL 实现(无推演修正的 PPO) **理论:** 天真的 LLM-RL 实现错误地应用 PPO,通过**忽略实际 rollout 策略**并假设 $\pi_{\text{old}} = \pi_{\text{rollout}}$。 **注意:** 这个错误的实现模式在 [Liu, Li, et al. (2025)](https://yingru.notion.site/When-Speed-Kills-Stability-Demystifying-RL-Collapse-from-the-Training-Inference-Mismatch-271211a558b7808d8b12d403fd15edda) 中被识别为 LLM-RL 系统训练不稳定的关键原因,激发了这个推演修正框架的发展。 **损失函数:** $$ L_{\text{PPO}}(\theta) = -\mathbb{E}_t \left[ \min\left( r_t(\theta) A_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) A_t \right) \right] $$ 其中 $r_t(\theta) = \frac{\pi_{\theta}(a_t|s_t)}{\pi_{\text{old}}(a_t|s_t)}$(忽略 $\pi_{\text{rollout}}$)。 **为什么错误:** - **忽略 $\pi_{\text{rollout}}$**:使用 $\pi_{\text{old}}$ 作为行为策略而不是实际 $\pi_{\text{rollout}}$ - **策略不匹配**:在 LLM-RL 中,rollout 通常使用训练不同的精度/后端/检查点,导致 $\pi_{\text{rollout}} \neq \pi_{\text{old}}$ 即使具有相同模型权重 - **PPO 本身没错**:问题是错误的假设 **正确的替代方案:** 1. **解耦模式**:三种策略,具有从 $\pi_{\text{rollout}}$ 到 $\pi_{\text{old}}$ 的 IS 修正 2. **旁路模式**:两种策略,使用 $\pi_{\text{rollout}}$ 作为行为策略和近端策略 3. **旁路 + 策略梯度模式**:两种策略,具有 IS/RS 修正和无 PPO 剪切 **实现:**`compute_policy_loss()` 在 [core_algos.py](../../verl/trainer/ppo/core_algos.py#L812-L884) --- ## 4. 离策略诊断指标 这些指标量化了离策略漂移的严重性。 **注意符号:** 指标使用 $\rho_t = \frac{\pi_{\text{old}}(a_t|s_t)}{\pi_{\text{rollout}}(a_t|s_t)}$。在旁路模式中,$\pi_{\text{old}} = \pi_{\text{rollout}}$,所以指标测量 rollout→current 漂移使用 $\rho_t = \frac{\pi_{\theta}}{\pi_{\text{rollout}}}$ 代替。 ### 4.1 KL 散度 **直接 KL 估计器:** $$ \text{KL}(\pi_{\text{rollout}} \| \pi_{\text{old}}) = \mathbb{E}_{t \sim \pi_{\text{rollout}}} \left[ \log \pi_{\text{rollout}}(a_t|s_t) - \log \pi_{\text{old}}(a_t|s_t) \right] $$ **K3 KL 估计器**(替代公式): $$ \text{KL}_{\text{K3}} = \mathbb{E}_{t \sim \pi_{\text{rollout}}} \left[ \rho_t - \log \rho_t - 1 \right] $$ 其中 $\rho_t = \frac{\pi_{\text{old}}(a_t|s_t)}{\pi_{\text{rollout}}(a_t|s_t)}$。 ### 4.2 困惑度 **旧策略困惑度:** $$ \text{PPL}_{\text{old}} = \exp\left( -\frac{1}{|T|} \sum_{t \in T} \log \pi_{\text{old}}(a_t|s_t) \right) $$ **Rollout 策略困惑度:** $$ \text{PPL}_{\text{rollout}} = \exp\left( -\frac{1}{|T|} \sum_{t \in T} \log \pi_{\text{rollout}}(a_t|s_t) \right) $$ **PPL 比值**(几何平均 IS 权重的倒数): $$ \text{PPL}_{\text{ratio}} = \frac{\text{PPL}_{\text{old}}}{\text{PPL}_{\text{rollout}}} = \exp\left( -\frac{1}{|T|} \sum_{t \in T} \log \rho_t \right) = \left(\prod_{t \in T} \rho_t\right)^{-1/|T|} $$ **解释:** > 1 的值表示 $\pi_{\text{old}}$ 比 $\pi_{\text{rollout}}$ 为观察到的动作分配更低概率(分布漂移)。 ### 4.3 χ² 散度 测量 IS 权重分布的第二时刻。 **Token 级:** $$ \chi^2_{\text{token}} = \mathbb{E}_{t \sim \pi_{\text{rollout}}} \left[ \rho_t^2 \right] - 1 $$ **序列级:** $$ \chi^2_{\text{seq}} = \mathbb{E}_{\text{seq} \sim \pi_{\text{rollout}}} \left[ \left(\prod_{t \in T} \rho_t\right)^2 \right] - 1 $$ **解释:** - $\chi^2 = 0$:策略相同 - $\chi^2 > 0$:更高值表示更严重的离策略分布漂移 **实现:**`compute_offpolicy_metrics()` 在 [rollout_corr_helper.py](../../verl/trainer/ppo/rollout_corr_helper.py#L670-L776) --- ## 5. 总结和决策指南 ### 5.1 方法总结表 | 方法 | 理论 | 策略 | PPO 剪切 | IS 修正 | 正确性 | 速度 | |--------|--------|----------|----------|---------------|-------------|-------| | `pg_is` | 离策略 REINFORCE | 2 (rollout, θ) | ❌ | ✅ 序列级 | ✅ 正确 | **快速** | | `pg_rs` | 纯 PG + 几何 RS | 2 (rollout, θ) | ❌ | 仅拒绝 | ✅ 正确 | **快速** | | 天真 LLM-RL | 错误 PPO 使用 | 2 (old, θ) | ✅ | ❌ | ⚠️ 错误 | 标准 | | `ppo_is_bypass` | PPO (rollout 作为 prox) | 2 (rollout, θ) | ✅ | ❌ | ✅ 正确 | **快速** | | `decoupled_token_is` | 解耦 PPO | 3 (rollout, old, θ) | ✅ | ✅ Token 级 | ✅ 正确 | 标准 | | `decoupled_seq_is` | 解耦 PPO | 3 (rollout, old, θ) | ✅ | ✅ 序列级 | ✅ 正确 | 标准 | | `decoupled_seq_is_rs` | 解耦 PPO + RS | 3 (rollout, old, θ) | ✅ | ✅ + 拒绝 | ✅ 正确 | 标准 | | `decoupled_geo_rs` | 解耦 PPO + 几何 RS | 3 (rollout, old, θ) | ✅ | 仅拒绝 | ✅ 正确 | 标准 | ### 5.2 按场景的方法特性 **离策略严重性:** - **可忽略**(相同检查点,轻微差异):`ppo_is_bypass` 使用 $\pi_{\text{rollout}}$ 作为近端策略(数学上正确);天真 LLM-RL 实现使用 $\pi_{\text{old}}$ 而不是 $\pi_{\text{rollout}}$(当 $\pi_{\text{rollout}} \neq \pi_{\text{old}}$ 时数学上错误) - **中等**(异步工作者,轻微过时):`decoupled_token_is` 提供具有单独近端策略的每个 token IS 修正 - **严重**(回放缓冲区,老数据):`decoupled_seq_is` 和 `decoupled_seq_is_rs` 提供具有可选拒绝采样的序列级 IS 修正 **算法特性:** - **批次大小不变性**:解耦模式具有三种策略(`decoupled_token_is`、`decoupled_seq_is`)实现批次大小不变性 - **计算效率**:旁路模式(`ppo_is_bypass`)跳过 `old_log_prob` 计算 - **纯策略梯度**:`pg_is` 实现无 PPO 剪切的离策略 REINFORCE ### 5.3 解耦模式 vs 旁路模式 **解耦模式**(单独计算 `old_log_prob`): - 实现具有三种策略的完整解耦 PPO(数学上正确) - 分开测量和修正漂移 1(rollout→old)和漂移 2(old→current) - 实现批次大小不变性并高效利用过时数据 - 启用准确的离策略指标监控 **旁路模式**(设 $\pi_{\text{old}} = \pi_{\text{rollout}}$): - 使用 $\pi_{\text{rollout}}$ 作为行为策略和近端策略(数学上正确) - 计算效率:跳过单独 `old_log_prob` 计算 - 不实现批次大小不变性(近端策略依赖数据收集) --- ## 6. 实现参考 - **[推演修正使用指南](rollout_corr.md)** - 实践配置和故障排除 - **配置:** [verl/trainer/config/algorithm.py](../../verl/trainer/config/algorithm.py) - **IS/RS 帮助器:** [verl/trainer/ppo/rollout_corr_helper.py](../../verl/trainer/ppo/rollout_corr_helper.py) - **PPO 损失:** [verl/trainer/ppo/core_algos