vLLM V0 到 V1:RL 中纠错前先确保正确性
vLLM V0 to V1: Correctness Before Corrections in RL
ServiceNow AI 将 PipelineRL 的 rollout engine 从 vLLM V0 0.8.5 迁移到 V1 0.18.1,排查 logprobs 语义、runtime defaults、inflight weight updates 和 fp32 lm_head。通过设置 processed_logprobs、禁用 prefix caching 与 async scheduling,并匹配权重更新路径,使 V1 的 clip rate、KL、entropy、reward 接近 V0 参考轨迹。
](https://huggingface.co/rafapi-snow)
PipelineRL 使用 vLLM 作为 rollout 生成的 inference engine。inference engine 对 token 进行采样并返回 token logprobs;trainer 使用这些 logprobs 计算 policy ratios、KL、clip rate、entropy 和 reward。logprobs 计算方式中的任何差异都可能改变训练动态。这就是我们在从 vLLM V0 迁移到 V1 时需要消除的训练-推理不匹配。
TL;DR. 在我们修复四个问题后,vLLM V1 与我们的 vLLM V0 参考结果一致:经过处理的 rollout logprobs、V1 特有的运行时默认值、运行中的权重更新路径,以及用于最终投影的 fp32 lm_head。我们先修复了后端行为,再修改 RL objective。
参考运行使用 vLLM 0.8.5;V1 运行使用 vLLM 0.18.1。图 1 展示了最终结果。红色曲线是最初的 V1 尝试,绿色曲线是完成下文所述修复后的最终 V1 运行。

图 1. vLLM V0 参考运行(蓝色)、最初的 vLLM V1 尝试(红色),以及包含 fp32 lm_head 在内、完成修复后的最终 vLLM V1 运行(绿色)的 trainer 侧指标。最终 V1 运行在 clip rate、KL、entropy 和 reward 上都回到了接近 V0 的轨迹。
迁移目标
vLLM V1 是对 V0 engine 的大幅重写。因此我们的迁移目标刻意保持得很窄:
- 验证 V1 是否以 trainer 期望的形式返回 rollout logprobs
- 针对 V0 参考结果重新运行相同 workload
- 只有在恢复后端一致性之后,才评估 objective 层面的变更
最早可见的症状出现在:
clamp_log_ratio_new_old_indicatorkl_new_oldentropyreward
这些指标来自一次 GSPO 训练运行,也就是本实验使用的 objective。同一类不匹配也可能出现在 PPO、GRPO,或任何将 rollout 侧 logprobs 作为优化目标一部分的在线 RL 系统中。
最初的 V1 运行清楚地暴露了问题。训练早期,trainer 侧 logprobs 和 reward 就偏离了 V0 参考结果。

图 2. 更新期间由 trainer 计算的当前 policy logprobs(左)和 reward(右)。最初的 vLLM V1 运行(红色)与 vLLM V0 参考运行(蓝色)分离。
trainer 指标中也出现了相同模式。在最初的对比中,clip rate 是最容易读出的信号。

图 3. vLLM V0 参考运行(蓝色)与最初的 vLLM V1 尝试(红色)的 trainer 侧指标。clip rate 跟踪 rollout/trainer policy gap;entropy 和 reward 显示该 gap 如何传导到训练中。
失败模式
我们将可能原因分成三层:
- 语义不匹配:后端返回的 logprobs 含义与 trainer 预期不同。
- 推理路径不匹配:后端在 caching、scheduling 或请求处理上使用了不同的运行时默认值,因此相同 prompt 走了不同的执行路径。
- Objective 不匹配:RL objective 需要针对仍然存在的 staleness 或后端不匹配进行修正。
一开始我们过早怀疑了第三类问题。真正有用的诊断方式,是先把前两类当作后端行为问题处理,并优先排除它们。
V1 后端修复
Logprob 语义
第一个问题是语义层面的。vLLM V1 默认从原始 model outputs 返回 logprobs,也就是在 temperature scaling、penalties、top-k/top-p filtering 等 logits 后处理之前。PipelineRL 期望的是 sampler 所使用的处理后分布中的 logprobs。
所需设置是:
logprobs-mode=processed_logprobs
这移除了 rollout logprobs 中明显的均值偏移。训练曲线相对已知正确的参考结果仍然存在差距,因此下一个问题必然在 inference path 中。
policy-ratio 图直接展示了这一点。V1 启用 processed_logprobs 后,三次运行中的平均 policy ratio 在所有步骤上都极其接近 1.0。这说明均值偏差已被修复。剩余不匹配体现在 clip rate、KL、entropy 以及下游训练行为中。

图 4. vLLM V0 参考运行(蓝色)、最初的 vLLM V1 运行(红色)和修正后的 vLLM V1 运行(绿色)中,rollout/trainer policy ratio 相对 1.0 的逐步偏差,放大 10,000 倍。
运行时默认值
早期 V1 运行混用了 engine 版本和 V1 运行时默认值:
- prefix caching:早期运行中未设置,因此应用了 vLLM
0.18.1的默认值 - async scheduling:早期运行中未设置,因此应用了 vLLM
0.18.1的默认值 - 一个临时的
disable-cascade-attnoverride:通过启动时 kwarg 透传设置,且不在已提交 config 的一致性配方中
对于一致性运行,我们显式做出这些选择:
vllm_config:
use_v1: true
vllm_kwargs:
logprobs-mode: processed_logprobs
enable-prefix-caching: false
async-scheduling: false
prefix caching 值得单独说明。对于固定模型状态,它通常是保持正确性的 inference 优化。但在这个在线 RL 设置中,相比 V0 参考路径,它在 cache 生命周期和复用上是一个 V1 独有差异。actor 还在处理重复 prefix、并发请求、async scheduling 和运行中的权重更新。
如果 cache 策略忽略权重更新边界,prefix-cache 命中可能会复用权重更新前计算得到的状态。禁用 prefix caching 从一致性对比中移除了一个 V1 独有的自由度。
运行中的权重更新
权重同步也必须匹配在线 RL 的更新模型。一个选择是让 V1 比 V0 更严格,在每次更新时排空请求并清除 cache。那会回答另一个问题。我们首先需要验证 V1 能否匹配现有 V0 行为。
V0 的实际行为更接近于:
- 在 engine 边界阻塞执行
- 加载新权重
- 在没有显式 cached-state invalidation 的情况下恢复
最接近的 V1 analogue 是:
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 wrapper 行为,即更新时保留 cached state
lag 是一个有用的运行时诊断指标。与修正后的 V1 运行相比,最初的 V1 路径在训练后期携带了更持久的 lag。

图 5. rollout server 中的权重相对于 trainer policy 落后的步数,分别对应 vLLM V0 参考运行(蓝色)、最初的 vLLM V1 运行(红色)和修正后的 vLLM V1 运行(绿色)。
剩余差距:fp32 lm_head
上面的 V1 后端修复消除了明显的迁移问题,但要达到最终一致性,还需要匹配用于计算 logits 的数值路径。trainer 使用 fp32 lm_head 做最终投影。rollout 后端必须匹配这一行为。
MiniMax-M1 technical report 中出现了一个高度相关的问题:他们的 RL 运行出现了训练/推理 token-probability 不匹配,最终追溯到 LM output head,并通过用 fp32 计算 head 解决。
这很重要,因为 RL 更新会直接消费 token logprobs。logits 中的细微变化可能会在 policy ratios、KL 和 clipping 中显现。因此,最终投影精度是在线 RL 正确性表面的一部分。ScaleRL paper 后来也将 fp32 logits/head computation 纳入其 RL recipe,并通过消融实验将其作为大规模 RL 中一个有用的设计选择。
加入 fp32 lm_head 路径后,reward 可以简洁地展示最终一致性结果。在图 6 中,最终 V1 运行跟随 V0 参考结果;最初的 V1 尝试则产生了明显不同的 reward 曲线。

图 6. vLLM V0 参考运行(蓝色)、最初的 vLLM V1 尝试(红色)以及带有 fp32 lm_head 路径的最终 vLLM V1 运行(绿色)的 reward。加入 fp32 head 后,最终 V1 运行跟随 V0 参考结果。
Ablations(消融实验)
负向结果很重要,因为它们排除了常见解释。
- 仅使用
processed_logprobs:修复了 logprob 语义 bug;训练不匹配仍然存在。 - Batch invariance:在另一个测试中,不匹配仍然存在,并伴随更高的 lag、更高的 clip rate,以及 NCCL 相关复杂性。
- 将第一次 V1 运行视为公平 baseline:第一次 V1 运行启用了多个 V1 独有默认值,因此它是一次存在混杂因素的迁移对比。
为什么我们先修复后端正确性
truncated importance sampling、importance-ratio reweighting 及相关方法等 objective 侧修正是有用工具。如果 rollouts 是有意 staleness 的、异步生成的,或者由无法与 trainer 侧 policy 保持等价的后端生成,那么通常应当加入某种形式的修正。
这里的首要问题是 inference correctness。迁移到 V1 后,rollout 后端返回的 logprobs 和运行时行为破坏了 trainer 的假设。此时加入 objective 侧修正会混淆两个问题:
- inference backend 是否生成了正确的 logprobs?
- 在 logprobs 正确的前提下,objective 是否仍需要 off-policy 或 async correction?
这些问题需要分开处理。否则,objective 侧修正可能会补偿有问题的 inference-backend 行为,使训练曲线更难解释。
当前 objective 仍然可以改进。在恢复 inference 一致性之后,下一步改进是常规的 async/off-policy 清理:
- 保留 rollout 时显式的 behavior-policy logprobs
- 在优化时重新计算 trainer 侧 old-policy logprobs
- 将后端不匹配修正与 policy-update ratio 分离
- 跟踪 ESS 等 correction term 诊断指标,并与聚合 trainer 指标并列查看
这次迁移的主要经验更窄:先修复后端正确性,再针对剩余不匹配加入修正。