PipelineRL 使用 vLLM 作为推理引擎来生成 rollout。推理引擎对模型 token 进行采样并返回 token 的 logprobs;训练器利用这些 logprobs 计算策略比率、KL 散度、裁剪率、熵和奖励。这些 logprobs 计算方式的任何差异都会改变训练动态。这正是我们在从 vLLM V0 迁移到 V1 过程中需要消除的训练-推理不匹配问题。简而言之,在修复了以下四个问题后,vLLM V1 与我们的 vLLM V0 参考版本匹配:已处理的 rollout logprobs、V1 特定的运行时默认值、动态权重更新路径以及用于最终投影的 fp32 lm_head。我们在改变 RL 目标之前修复了后端行为。
参考运行使用了 vLLM 0.8.5;V1 运行使用了 vLLM 0.18.1。图 1 展示了最终结果。红色曲线是初始的 V1 尝试,绿色曲线是经过下述修复后的最终 V1 运行。
图 1. vLLM V0 参考(蓝色)、初始 vLLM V1 尝试(红色)以及我们修复后(包括 fp32 lm_head)的最终 vLLM V1 运行(绿色)的训练器端指标。最终 V1 运行在裁剪率、KL 散度、熵和奖励方面与 V0 的轨迹接近。
迁移目标
vLLM V1 是对 V0 引擎的一次重大重写。因此,我们的迁移目标被刻意设定得非常狭窄:
- 验证 V1 是否以训练器期望的形式返回了 rollout logprobs
- 针对 V0 参考版本重新运行相同的工作负载
- 仅在后端一致性恢复后评估目标层面的变化
最初出现的可见症状体现在:
- clamp_log_ratio_new_old_indicator
- kl_new_old
- 熵
- 奖励
这些指标来自一次 GSPO 训练运行,即本次实验所使用的目标函数。同一类不匹配问题也可能出现在 PPO、GRPO 或任何将 rollout 侧 logprobs 视为优化目标一部分的在线 RL 系统中。
初始的 V1 运行清晰地展示了问题。训练器端的 logprobs 和奖励在训练早期就偏离了 V0 参考版本。
图 2. 训练器在更新期间计算的当前策略 logprobs(左)和奖励(右)。初始的 vLLM V1 运行(红色)与 vLLM V0 参考版本(蓝色)发生了偏离。
同样的模式也出现在训练器指标中。在初步对比中,裁剪率是最容易解读的信号。
图 3. vLLM V0 参考版本(蓝色)与初始 vLLM V1 尝试版本(红色)的训练器端指标。裁剪率反映了 rollout 与训练器策略之间的差距;熵和奖励则展示了这种差距如何传导至训练过程。
失败模式
我们将可能的原因分为三个层面:
- 语义不匹配:后端返回的 logprobs 含义与训练器预期的含义不同。
- 推理路径不匹配:后端在缓存、调度或请求处理方面使用了不同的运行时默认设置,导致相同的提示词遵循了不同的执行路径。
- 目标不匹配:强化学习目标需要针对残留的陈旧度或后端不匹配程度进行修正。
我们最初过早地怀疑了第三类原因。而有效的诊断方法是将前两类视为后端行为问题,并首先将其排除。
V1 后端修复
Logprob 语义
第一个问题是语义问题。vLLM V1 默认从原始模型输出返回 logprobs,即在经过温度缩放、惩罚以及 top-k/top-p 过滤等 logits 后处理之前。PipelineRL 期望的是采样器所使用的已处理分布中的 logprobs。
所需的设置是:
- logprobs-mode=processed_logprobs
这消除了 rollout logprobs 中明显的均值偏移。但训练曲线与已知良好的参考版本相比仍存在差距,因此下一个问题必定出在推理路径上。
策略比率图直接显示了这一点。一旦 V1 启用了 processed_logprobs,所有三次运行的平均策略比率都极其接近 1.0。这确立了均值偏差的修复。剩余的差异则体现在裁剪率、KL 散度、熵以及下游训练行为中。
图 4. vLLM V0 参考版本(蓝色)、初始 vLLM V1 运行版本(红色)以及修正后的 vLLM V1 运行版本(绿色)中,rollout/训练器策略比率与 1.0 的每步偏差(已缩放 10,000 倍)。
运行时默认设置
早期的 V1 运行将引擎版本与 V1 运行时默认设置混合在了一起:
- 前缀缓存,在早期运行中未设置,因此应用了 vLLM 0.18.1 的默认设置。
- 异步调度,在早期运行中未设置,因此应用了 vLLM 0.18.1 的默认值。
- 一个临时性的禁用级联注意力覆盖,通过启动时的 kwargs 参数传递设置,位于已提交配置的对等性方案之外。
对于对等性运行,我们明确了这些选择:
vllm_config:
use_v1: true
vllm_kwargs:
logprobs-mode: processed_logprobs
enable-prefix-caching: false
async-scheduling: false
前缀缓存值得单独说明。对于固定的模型状态,它通常是一种保持正确性的推理优化。在此在线强化学习设置中,相对于 V0 参考路径,它在缓存生命周期和重用方面是 V1 独有的差异。行动者还需要处理重复前缀、并发请求、异步调度以及运行中的权重更新。
当缓存策略忽略权重更新边界时,前缀缓存命中可以重用权重更新之前计算的状态。禁用前缀缓存从对等性比较中移除了一个 V1 独有的自由度。
运行中权重更新
权重同步也必须匹配在线强化学习的更新模型。一种选择是让 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. 在 vLLM V0 参考(蓝色)、初始 vLLM V1 运行(红色)和修正后的 vLLM V1 运行(绿色)中,推出服务器权重落后于训练器策略的步数。
剩余差距:fp32 lm_head
上述 V1 后端修复消除了明显的迁移问题,但最终的对等性仍需匹配用于计算 logits 的数值路径。训练器使用 fp32 lm_head 进行最终投影。推出后端必须匹配该行为。
MiniMax-M1 技术报告中出现了一个密切相关的问题:他们的强化学习运行显示训练/推理的 token 概率存在不匹配,他们将其追溯到大语言模型输出头,并通过在 fp32 精度下计算该输出头解决了问题。
这一点很重要,因为强化学习更新直接消耗 token 的 logprobs。logits 的微小变化会在策略比率、KL 散度和裁剪中变得可见。因此,最终的投影精度是在线强化学习正确性的一部分。ScaleRL 论文后来将 fp32 logits/输出头计算纳入其强化学习方案,并将其作为大规模强化学习的一个有用设计选择进行了消融实验。
在包含 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 专属默认设置,因此这是一次混杂了多种因素的迁移对比。
为何我们优先修复后端正确性
目标端的修正,例如截断重要性采样、重要性比率重新加权及相关方法,都是有用的工具。如果 rollout 数据是故意过时的、异步生成的,或者由某个无法与训练端策略保持等价的后端产生的,那么添加某种形式的修正通常是正确的做法。
这里首要的问题是推理正确性。迁移到 V1 后,rollout 后端返回的 logprobs 和运行时行为破坏了训练端的假设。此时在目标端添加修正会混淆两个问题:
- 推理后端是否产生了正确的 logprobs?
- 在给定正确对数概率的情况下,目标函数是否仍需要离策略或异步修正?
这些问题需要分开处理。否则,目标函数侧的修正可能会掩盖推理后端的错误行为,导致训练曲线更难解读。
当前的目标函数仍有改进空间。在推理一致性恢复之后,下一步改进就是常规的异步/离策略清理:
- 保留 rollout 阶段显式的行为策略对数概率
- 在优化阶段重新计算训练器侧的旧策略对数概率
- 将后端不匹配修正与策略更新比率分开
- 在聚合训练指标之外,跟踪修正项的有效样本量(ESS)等诊断指标
这次迁移的主要教训更为具体:先修复后端的正确性,再针对剩余的不匹配问题添加修正。
PipelineRL uses vLLM as the inference engine for rollout generation. The inference engine samples tokens and returns token logprobs; the trainer uses those logprobs to compute policy ratios, KL, clip rate, entropy, and reward. Any discrepancy in how those logprobs are computed can change the training dynamics. This is the train-inference mismatch we needed to eliminate during the vLLM V0 to V1 migration. TL;DR. vLLM V1 matched our vLLM V0 reference after we fixed four things: processed rollout logprobs, V1-specific runtime defaults, the inflight weight-update path, and the fp32 lm_head used for the final projection. We fixed the backend behavior before changing the RL objective.
The reference run used vLLM 0.8.5; the V1 runs used vLLM 0.18.1. Figure 1 shows the final result. The red run is the initial V1 attempt, and the green run is the final V1 run after the fixes described below.
Figure 1. Trainer-side metrics for the vLLM V0 reference (blue), the initial vLLM V1 attempt (red), and the final vLLM V1 run after our fixes (green), including the fp32 lm_head. The final V1 run returns close to the V0 trajectory across clip rate, KL, entropy, and reward.
Migration Objective
vLLM V1 is a substantial rewrite of the V0 engine. Our migration target was therefore deliberately narrow:
- verify that V1 returned rollout logprobs in the form the trainer expected
- rerun the same workload against the V0 reference
- evaluate objective-level changes only after backend parity was restored
The first visible symptoms appeared in:
clamp_log_ratio_new_old_indicatorkl_new_oldentropyreward
Those metrics came from a GSPO training run, the objective used for this experiment. The same class of mismatch can surface in PPO, GRPO, or any online RL system that treats rollout-side logprobs as part of the optimization target.
The initial V1 run showed the problem clearly. The trainer-side logprobs and reward moved away from the V0 reference early in training.
Figure 2. Current-policy logprobs computed by the trainer during updates (left) and reward (right). The initial vLLM V1 run (red) separates from the vLLM V0 reference (blue).
The same pattern appears in the trainer metrics. Clip rate is the easiest signal to read in the initial comparison.
Figure 3. Trainer-side metrics for the vLLM V0 reference (blue) and the initial vLLM V1 attempt (red). Clip rate tracks the rollout/trainer policy gap; entropy and reward show how that gap propagates into training.
Failure Modes
We separated the possible causes into three layers:
- Semantic mismatch: the backend returns logprobs with different meaning relative to what the trainer expects.
- Inference-path mismatch: the backend uses different runtime defaults for caching, scheduling, or request handling, so the same prompts follow a different execution path.
- Objective mismatch: the RL objective needs correction for the amount of staleness or backend mismatch that remains.
We initially suspected the third category too early. The useful diagnosis came from treating the first two as backend behavior problems and ruling them out first.
V1 Backend Fixes
Logprob Semantics
The first issue was semantic. vLLM V1 returns logprobs from the raw model outputs by default, before logits post-processing such as temperature scaling, penalties, and top-k/top-p filtering. PipelineRL expected logprobs from the processed distribution used by the sampler.
The required setting was:
logprobs-mode=processed_logprobs
This removed the obvious mean offset in rollout logprobs. The training curves still showed a gap relative to the known-good reference, so the next issue had to be in the inference path.
The policy-ratio plot shows this directly. Once processed_logprobs is on for V1, the mean policy ratio stays centered extremely close to 1.0 across all three runs. That establishes the mean-bias fix. The remaining mismatch shows up in clip rate, KL, entropy, and downstream training behavior.
Figure 4. Per-step deviation of the rollout/trainer policy ratio from 1.0, scaled by 10,000, for the vLLM V0 reference (blue), the initial vLLM V1 run (red), and the corrected vLLM V1 run (green).
Runtime Defaults
The early V1 run mixed the engine version with V1 runtime defaults:
- prefix caching, left unset in the early run so the vLLM
0.18.1default applied - async scheduling, left unset in the early run so the vLLM
0.18.1default applied - an ad-hoc
disable-cascade-attnoverride that was set through launch-time kwarg passthrough and sits outside the parity recipe in committed config
For the parity run, we made these choices explicit:
vllm_config:
use_v1: true
vllm_kwargs:
logprobs-mode: processed_logprobs
enable-prefix-caching: false
async-scheduling: false
Prefix caching deserves a separate note. It is normally a correctness-preserving inference optimization for a fixed model state. In this online RL setup, it was a V1-only difference in cache lifetime and reuse relative to the V0 reference path. The actor was also handling repeated prefixes, concurrent requests, async scheduling, and inflight weight updates.
A prefix-cache hit can reuse state computed before a weight update when the cache policy ignores the weight-update boundary. Disabling prefix caching removed one V1-only degree of freedom from the parity comparison.
Inflight Weight Updates
Weight synchronization also had to match the online-RL update model. One option was to make V1 stricter than V0 by draining requests and clearing caches at every update. That would answer a separate question. We first needed to verify that V1 could match the existing V0 behavior.
What V0 effectively did was closer to:
- block execution at an engine boundary
- load the new weights
- resume without an explicit cached-state invalidation
The nearest V1 analogue was:
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()
Two details matter:
mode="keep"matches the old inflight update model more closely thanwaitorabortclear_cache=Falsematches the V0 wrapper behavior, which left cached state intact on update
Lag was a useful runtime diagnostic. The initial V1 path carries more persistent lag later in training than the corrected V1 run.
Figure 5. Number of steps the weights in the rollout server are behind the trainer policy, for the vLLM V0 reference (blue), the initial vLLM V1 run (red), and the corrected vLLM V1 run (green).
The Remaining Gap: fp32 lm_head
The V1 backend fixes above removed the obvious migration issues, but final parity still required matching the numerical path used to compute logits. The trainer used an fp32 lm_head for the final projection. The rollout backend had to match that behavior.
A closely related issue appears in the MiniMax-M1 technical report: their RL run showed a training/inference token-probability mismatch that they traced to the LM output head and fixed by computing the head in fp32.
This matters because the RL update consumes token logprobs directly. Small changes in logits can become visible in policy ratios, KL, and clipping. The final projection precision is therefore part of the correctness surface for online RL. The ScaleRL paper later includes fp32 logits/head computation as part of its RL recipe and ablates it as a useful design choice for large-scale RL.
With the fp32 lm_head path included, reward gives a compact view of the final parity result. In Figure 6, the final V1 run tracks the V0 reference; the initial V1 attempt produces a clearly different reward curve.
Figure 6. Reward for the vLLM V0 reference (blue), the initial vLLM V1 attempt (red), and the final vLLM V1 run with the fp32 lm_head path (green). With the fp32 head included, the final V1 run tracks the V0 reference.
Ablations
The negative results are important because they rule out common explanations.
processed_logprobsalone: fixed the semantic logprob bug; the training mismatch remained.- Batch invariance: the mismatch remained in a separate test, with higher lag, higher clip rate, and NCCL complications.
- Treating the first V1 run as a fair baseline: the first V1 run had multiple V1-only defaults enabled, so it was a confounded migration comparison.
Why We Fixed Backend Correctness First
Objective-side corrections such as truncated importance sampling, importance-ratio reweighting, and related methods are useful tools. If rollouts are intentionally stale, generated asynchronously, or produced by a backend where equivalence to the trainer-side policy is unavailable, then some form of correction is often the right thing to add.
The first problem here was inference correctness. After moving to V1, the rollout backend returned logprobs and runtime behavior that broke the trainer assumption. Adding an objective-side correction at that point would have mixed two questions:
- is the inference backend producing the right logprobs?
- given correct logprobs, does the objective still need an off-policy or async correction?
Those questions need to be separated. Otherwise an objective-side correction can compensate for broken inference-backend behavior, which makes the training curve harder to interpret.
The current objective can still improve. After inference parity is restored, the next improvement is the usual async/off-policy cleanup:
- keep explicit behavior-policy logprobs from rollout time
- recompute trainer-side old-policy logprobs at optimization time
- separate backend mismatch correction from the policy-update ratio
- track diagnostics like ESS for the correction term alongside aggregate trainer metrics
The main lesson from this migration is narrower: fix backend correctness first, then add corrections for the mismatch that remains.