一声棒喝,本不立文字
偏要著録,已是二义

huggingface-blog

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)

图片 2:Ehsan Kamalloo 的头像

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 运行。

图片 3

图 1. vLLM V0 参考运行(蓝色)、最初的 vLLM V1 尝试(红色),以及包含 fp32 lm_head 在内、完成修复后的最终 vLLM V1 运行(绿色)的 trainer 侧指标。最终 V1 运行在 clip rate、KL、entropy 和 reward 上都回到了接近 V0 的轨迹。

迁移目标

vLLM V1 是对 V0 engine 的大幅重写。因此我们的迁移目标刻意保持得很窄:

  1. 验证 V1 是否以 trainer 期望的形式返回 rollout logprobs
  2. 针对 V0 参考结果重新运行相同 workload
  3. 只有在恢复后端一致性之后,才评估 objective 层面的变更

最早可见的症状出现在:

这些指标来自一次 GSPO 训练运行,也就是本实验使用的 objective。同一类不匹配也可能出现在 PPO、GRPO,或任何将 rollout 侧 logprobs 作为优化目标一部分的在线 RL 系统中。

最初的 V1 运行清楚地暴露了问题。训练早期,trainer 侧 logprobs 和 reward 就偏离了 V0 参考结果。

图片 4

图 2. 更新期间由 trainer 计算的当前 policy logprobs(左)和 reward(右)。最初的 vLLM V1 运行(红色)与 vLLM V0 参考运行(蓝色)分离。

trainer 指标中也出现了相同模式。在最初的对比中,clip rate 是最容易读出的信号。

图片 5

图 3. vLLM V0 参考运行(蓝色)与最初的 vLLM V1 尝试(红色)的 trainer 侧指标。clip rate 跟踪 rollout/trainer policy gap;entropy 和 reward 显示该 gap 如何传导到训练中。

失败模式

我们将可能原因分成三层:

  1. 语义不匹配:后端返回的 logprobs 含义与 trainer 预期不同。
  2. 推理路径不匹配:后端在 caching、scheduling 或请求处理上使用了不同的运行时默认值,因此相同 prompt 走了不同的执行路径。
  3. Objective 不匹配:RL objective 需要针对仍然存在的 staleness 或后端不匹配进行修正。

一开始我们过早怀疑了第三类问题。真正有用的诊断方式,是先把前两类当作后端行为问题处理,并优先排除它们。

V1 后端修复

Logprob 语义

第一个问题是语义层面的。vLLM V1 默认从原始 model outputs 返回 logprobs,也就是在 temperature scaling、penalties、top-k/top-p filtering 等 logits 后处理之前。PipelineRL 期望的是 sampler 所使用的处理后分布中的 logprobs。

所需设置是:

这移除了 rollout logprobs 中明显的均值偏移。训练曲线相对已知正确的参考结果仍然存在差距,因此下一个问题必然在 inference path 中。

policy-ratio 图直接展示了这一点。V1 启用 processed_logprobs 后,三次运行中的平均 policy ratio 在所有步骤上都极其接近 1.0。这说明均值偏差已被修复。剩余不匹配体现在 clip rate、KL、entropy 以及下游训练行为中。

图片 6

图 4. vLLM V0 参考运行(蓝色)、最初的 vLLM V1 运行(红色)和修正后的 vLLM V1 运行(绿色)中,rollout/trainer policy ratio 相对 1.0 的逐步偏差,放大 10,000 倍。

运行时默认值

早期 V1 运行混用了 engine 版本和 V1 运行时默认值:

对于一致性运行,我们显式做出这些选择:

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 的实际行为更接近于:

最接近的 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()

有两个细节很重要:

lag 是一个有用的运行时诊断指标。与修正后的 V1 运行相比,最初的 V1 路径在训练后期携带了更持久的 lag。

图片 7

图 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 曲线。

图片 8

图 6. vLLM V0 参考运行(蓝色)、最初的 vLLM V1 尝试(红色)以及带有 fp32 lm_head 路径的最终 vLLM V1 运行(绿色)的 reward。加入 fp32 head 后,最终 V1 运行跟随 V0 参考结果。

Ablations(消融实验)

负向结果很重要,因为它们排除了常见解释。

为什么我们先修复后端正确性

truncated importance sampling、importance-ratio reweighting 及相关方法等 objective 侧修正是有用工具。如果 rollouts 是有意 staleness 的、异步生成的,或者由无法与 trainer 侧 policy 保持等价的后端生成,那么通常应当加入某种形式的修正。

这里的首要问题是 inference correctness。迁移到 V1 后,rollout 后端返回的 logprobs 和运行时行为破坏了 trainer 的假设。此时加入 objective 侧修正会混淆两个问题:

这些问题需要分开处理。否则,objective 侧修正可能会补偿有问题的 inference-backend 行为,使训练曲线更难解释。

当前 objective 仍然可以改进。在恢复 inference 一致性之后,下一步改进是常规的 async/off-policy 清理:

这次迁移的主要经验更窄:先修复后端正确性,再针对剩余不匹配加入修正。

译自 huggingface-blog · 录于 二〇二六年五月六日