vLLM 从 V0 到 V1:在强化学习中正确性优先于修正

TL;DR · AI 摘要
vLLM从V0到V1的升级聚焦推理后端正确性,修复了logprob语义、运行时默认值和飞行中权重更新等关键问题,确保强化学习训练中的结果可靠。
核心要点
- vLLM V1优先解决推理后端的正确性问题,而非直接优化性能。
- 关键修复包括logprob计算、运行时默认参数和飞行中权重更新的一致性。
- fp32精度的lm_head仍是残余差距,影响最终输出精确性。
标题:vLLM V0 到 V1:强化学习中的正确性优先于修正
来源链接:http://huggingface.co/blog/ServiceNow-AI/correctness-before-corrections
发布时间:2026-05-06T19:06:55.743Z
PipelineRL 使用 vLLM 作为 rollout 生成的推理引擎。该推理引擎采样 token 并返回 token 的 logprob;训练器使用这些 logprob 来计算策略比率、KL 散度、裁剪率、熵和奖励。任何在 logprob 计算方式上的差异都可能改变训练动态。这正是我们在从 vLLM V0 迁移到 V1 期间需要消除的训练-推理不匹配问题。
简而言之:在我们修复了四个问题之后,vLLM V1 成功匹配了我们的 vLLM V0 基准:处理后的 rollout logprob、V1 特定的运行时默认设置、飞行中的权重更新路径,以及用于最终投影的 fp32 lm_head。我们在修改 RL 目标之前,首先修复了后端行为。
基准运行使用的是 vLLM 0.8.5;V1 运行使用的是 vLLM 0.18.1。图1展示了最终结果。红色曲线是初始的 V1 尝试,绿色曲线是应用下述修复后的最终 V1 运行结果。

图1. vLLM V0 基准(蓝色)、初始 vLLM V1 尝试(红色)和最终 vLLM V1 运行(绿色,包含 fp32 lm_head)的训练器侧指标。最终的 V1 运行在裁剪率、KL 散度、熵和奖励方面均接近 V0 的轨迹。
迁移目标
vLLM V1 是对 V0 引擎的一次重大重写。因此我们的迁移目标被有意限定得很窄:
- 验证 V1 是否以训练器期望的形式返回 rollout logprob
- 使用相同的负载重新运行与 V0 基准的对比
- 只有在恢复后端一致性之后,才评估目标层面的变化
最早出现的明显症状体现在以下指标中:
clamp_log_ratio_new_old_indicatorkl_new_oldentropyreward
这些指标来自一次 GSPO 训练运行,即本实验所使用的优化目标。类似的不匹配问题也可能出现在 PPO、GRPO 或任何将 rollout 侧 logprob 视为优化目标一部分的在线 RL 系统中。
初始的 V1 运行清楚地暴露了问题。训练器侧的 logprob 和奖励在训练早期就偏离了 V0 基准。

图2. 训练更新过程中由训练器计算的当前策略 logprob(左)和奖励(右)。初始 vLLM V1 运行(红色)与 vLLM V0 基准(蓝色)分离。
同样的模式也出现在训练器指标中。裁剪率是在初步比较中最容易观察到的信号。

图3. vLLM V0 基准(蓝色)和初始 vLLM V1 尝试(红色)的训练器侧指标。裁剪率追踪 rollout 与训练器策略之间的差距;熵和奖励显示了这一差距如何传播到训练过程中。
失败模式
我们将可能的原因分为三个层次:
- 语义不匹配:后端返回的 logprob 与其相对于训练器预期含义不同。
- 推理路径不匹配:后端在缓存、调度或请求处理上使用了不同的运行时默认值,导致相同提示遵循不同的执行路径。
- 目标不匹配:RL 目标需要针对残留的延迟量或后端不匹配进行修正。
我们最初过早地怀疑了第三类原因。有效的诊断方法是先将前两类视为后端行为问题并逐一排除。
V1 后端修复
Logprob 语义
第一个问题是语义性的。vLLM V1 默认返回原始模型输出的 logprob,而未经过如温度缩放、惩罚项或 top-k/top-p 过滤等 logits 后处理步骤。但 PipelineRL 期望的是采样器所用处理后分布的 logprob。
所需的设置为:
logprobs-mode=processed_logprobs
这消除了 rollout logprob 中明显的均值偏移。然而训练曲线仍显示出与已知良好基准之间的差距,因此下一个问题必然出在推理路径上。
策略比率图可直接说明这一点。一旦 V1 开启 processed_logprobs,所有三次运行的平均策略比率都非常接近 1.0。这确认了均值偏差已被修复。剩余的不匹配则体现在裁剪率、KL 散度、熵及下游训练行为中。

图4. 每步 rollout/训练器策略比率偏离 1.0 的程度(乘以 10,000),展示 vLLM V0 基准(蓝色)、初始 vLLM V1 运行(红色)和修正后的 vLLM V1 运行(绿色)。
运行时默认值
早期的 V1 运行混合了引擎版本与 V1 的运行时默认设置:
- 前缀缓存,在早期运行中未设置,因此采用 vLLM
0.18.1的默认值 - 异步调度,在早期运行中未设置,因此采用 vLLM
0.18.1的默认值 - 一个通过启动时 kwargs 传递设置的临时
disable-cascade-attn覆盖项,不在提交的配置一致性配方中
为了实现一致性运行,我们显式指定了这些选项:
vllm_config:
use_v1: true
vllm_kwargs:
logprobs-mode: processed_logprobs
enable-prefix-caching: false
async-scheduling: false前缀缓存值得单独说明。它通常是对固定模型状态保持正确性的推理优化。但在这种在线 RL 设置中,它是相对于 V0 基准路径的 V1 独有差异。actor 还需处理重复前缀、并发请求、异步调度和飞行中权重更新。
当缓存策略忽略权重更新边界时,前缀缓存命中可能会复用权重更新前计算的状态。禁用前缀缓存从一致性比较中移除了一项仅存在于 V1 的自由度。
飞行中权重更新
权重同步也必须匹配在线 RL 的更新模型。一种选择是让 V1 比 V0 更严格,即每次更新时清空请求并清除缓存。但这会回答另一个问题。我们首先需要验证 V1 是否能够匹配现有的 V0 行为。
V0 实际上所做的更接近于:
- 在引擎边界处阻塞执行
- 加载新权重
- 恢复运行而不显式使缓存状态失效
最接近的 V1 类比是:
await engine.pause_generation(mode="keep", clear_cache=False)
await engine_client.collective_rpc_async(
"receive_weight_update",
args=(request.model_dump_json(),),
)
await engine.resume_generation()两个细节很重要:
mode="keep"比wait或abort更贴近旧的飞行中更新模型clear_cache=False匹配 V0 包装器的行为,即更新时不破坏缓存状态
延迟是一个有用的运行时诊断指标。相比修正后的 V1 运行,初始 V1 路径在训练后期表现出更持久的延迟。

图5. rollout 服务器中权重落后于训练器策略的步数,展示 vLLM V0 基准(蓝色)、初始 vLLM V1 运行(红色)和修正后的 vLLM V1 运行(绿色)。
剩余差距:fp32 lm_head
上述 V1 后端修复消除了明显的迁移问题,但最终一致性仍需匹配用于计算 logits 的数值路径。训练器使用 fp32 lm_head 进行最终投影。rollout 后端必须匹配该行为。
一个密切相关的问题出现在 MiniMax-M1 技术报告 中:他们的 RL 运行显示出训练/推断 token 概率不匹配,追溯至语言模型输出头,并通过以 fp32 计算头部解决了该问题。
这一点之所以重要,是因为 RL 更新直接消耗 token logprob。logits 的微小变化可能在策略比率、KL 散度和裁剪中变得显著。因此,最终投影精度是在线 RL 正确性范围的一部分。后来的 ScaleRL 论文 将 fp32 logits/头部计算纳入其 RL 配方,并将其作为大规模 RL 的有效设计选择进行了消融分析。
包含 fp32 lm_head 路径后,奖励提供了最终一致性结果的简洁视图。在图6中,最终 V1 运行紧随 V0 基准;而初始 V1 尝试产生明显不同的奖励曲线。

图6. vLLM V0 基准(蓝色)、初始 vLLM V1 尝试(红色)和包含 fp32 lm_head 路径的最终 vLLM V1 运行(绿色)的奖励。包含 fp32 头部后,最终 V1 运行紧随 V0 基准。
消融实验
负向结果很重要,因为它们排除了常见解释。
- 仅 `processed_logprobs`:修复了语义 logprob 错误;训练不匹配仍然存在。
- 批处理不变性:在另一项测试中不匹配依然存在,且伴有更高延迟、更高裁剪率和 NCCL 复杂性。
- 将首次 V1 运行视为公平基线:首次 V1 运行启用了多个仅 V1 存在的默认设置,因此它是一个混淆变量组合,不适合作为基线。