vLLM V0 から V1:RL における修正前の正しさの重視
ServiceNow-AI の研究者らが、vLLM V1 への移行において、性能向上よりもまず正しさを保証する「Correctness Before Corrections」の原則を提唱し、RL(強化学習)における検証プロセスの重要性を強調している。
キーポイント
vLLM V0 から V1 への移行と新方針
高性能推論ライブラリ vLLM の次期バージョン V1 への変更において、単なる速度向上や機能追加よりも、まずモデルの出力が正しく動作すること(Correctness)を最優先する方針が示された。
RL における検証の重要性
強化学習(Reinforcement Learning)を用いた RLHF や SFT の文脈では、修正や最適化を行う前に、ベースラインの正しさを厳密に確認することが不可欠であると指摘されている。
失敗モードの分析と対策
V1 移行に伴い発生しうる潜在的な失敗モード(Failure Modes)を特定し、それらが RL パイプラインに与える影響を事前に評価する必要性が説かれている。
移行目標の限定と検証順序
vLLM V1 の移行目標は、トレーナーが期待する形式でのロールアウト logprobs が返されることの確認に絞り、バックエンドの整合性が復元されるまで目的レベルの変化を評価しない方針とした。
初期の実行で検出されたメトリクス不整合
GSPO 学習実験において、clamp_log_ratio_new_old_indicator, kl_new_old, entropy, reward の各指標が V0 リファレンスから早期に乖離し、PPO や GRPO などロールアウト側の logprobs を最適化対象とするオンライン RL システム全体で同様の不整合が発生する可能性がある。
Logprob Semantics の不整合
vLLM V1 はデフォルトで生出力の logprobs を返すため、サンプリング後の分布を想定していた Trainer と意味が異なり、`logprobs-mode=processed_logprobs` 設定が必要でした。
バグ修正によるポリシー比の安定化
上記設定によりロールアウトの平均バイアスが解消され、ポリシー比(policy ratio)が 1.0 に中心に収まるようになりましたが、クリップ率や KL 散逸など他の不整合は残っています。
影響分析・編集コメントを表示
影響分析
この記事は、大規模言語モデルの推論基盤である vLLM の次期バージョン開発において、スピードよりも安定性と信頼性を優先する重要なパラダイムシフトを示しています。特に強化学習を用いたモデル改善プロセスにおいては、誤った前提での最適化を防ぐための厳格な検証基準が業界標準となりつつあることを示唆しており、実務におけるデプロイ戦略に大きな影響を与える可能性があります。
編集コメント
vLLM のような基盤技術のアップデートにおいて、機能追加よりも「正しさ」を優先する姿勢は、実運用におけるリスク管理の観点から極めて重要です。特に RL を活用したモデル改善においては、この検証プロセスが成果物の品質を左右する鍵となります。
- 移行の目的
- 失敗モード
- V1 バックエンドの修正:対数尤度(logprob)の意味論
- ランタイムのデフォルト値
- 実行中の重み更新
- 残されたギャップ:fp32 lm_head
- アブレーション実験
- なぜ最初にバックエンドの正しさを修正したのか
PipelineRL は、ロールアウト生成のための推論エンジンとして vLLM を使用しています。推論エンジンはトークンをサンプリングしてトークンの対数尤度(logprobs)を返しますが、トレーニング側はこれらの対数尤度を使用してポリシー比、KL 発散、クリップ率、エントロピー、および報酬を計算します。これらの対数尤度の計算方法に任何らかの不一致があると、トレーニングのダイナミクスが変化してしまいます。これが、vLLM V0 から V1 への移行中に排除する必要があった「トレーニングと推論の不整合」です。
要約。 vLLM V1 は、以下の4つの点を修正したことで、私たちが参照していた vLLM V0 と一致しました:処理済みのロールアウト対数尤度、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 がトレーナーが期待する形式でロールアウトの対数尤度(logprobs)を返すことを確認する
- 同じワークロードを V0 リファレンスに対して再実行する
- バックエンドの整合性が回復した後にのみ、目的関数レベルの変化を評価する
最初の目に見える兆候は以下の項目で現れました:
- clamp_log_ratio_new_old_indicator
- kl_new_old
- entropy
- reward
これらのメトリクスは、本実験で使用された GSPO 学習ランから得られたものです。ロールアウト側の対数尤度を最適化目標の一部として扱う PPO、GRPO、またはその他のオンライン RL システムでは、同様の不一致が表面化する可能性があります。
初期の V1 実行では問題が明確に示されました。トレーナー側の対数尤度と報酬は、学習の早い段階で V0 リファレンスから乖離し始めました。

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).
同じパターンがトレーナーのメトリクスにも現れています。クリップ率(clip rate)は、初期比較において最も読み取りやすいシグナルです。
image
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
考えられる原因を 3 つの層に分類しました。
- セマンティックの不整合:バックエンドが返す logprobs(対数確率)の意味が、トレーナーが想定しているものと異なる。
- 推論パスの不整合:バックエンドがキャッシュ、スケジューリング、またはリクエスト処理において異なるランタイムデフォルトを使用するため、同じプロンプトでも実行パスが異なる経路をたどる。
- オブジェクトive の不整合:残存する古さ(staleness)やバックエンドの不整合の量に応じて、強化学習(RL)の目的関数に修正が必要となる。
当初は第 3 のカテゴリを早すぎると疑いました。有用な診断結果は、最初の 2 つをバックエンドの動作問題として扱い、まずそれらを除外することから得られました。
V1 Backend Fixes
Logprob Semantics
最初の問題は意味論的なものでした。vLLM V1 はデフォルトで、温度スケーリングやペナルティ、top-k/top-p フィルタリングなどのロジット後処理の前に、生のモデル出力からログ確率(logprobs)を返します。PipelineRL は、サンプラーが使用する処理済みの分布からのログ確率を期待していました。
必要な設定は以下の通りでした:
- logprobs-mode=processed_logprobs
これにより、ロールアウトのログ確率における明白な平均オフセットが解消されました。しかし、トレーニング曲線はまだ既知の良好な参照値とのギャップを示していたため、次の問題は推論パスにあるはずでした。
ポリシー比のプロットはこれを直接的に示しています。V1 で processed_logprobs を有効にした後、すべての 3 つの実行において平均ポリシー比が 1.0 の非常に近くに中心化されます。これが平均バイアスの修正确立了です。残りの不整合は、クリップ率(clip rate)、KL 発散、エントロピー、および下流のトレーニング挙動に現れます。
image
図 4. vLLM V0 参照(青)、初期の vLLM V1 実行(赤)、修正された vLLM V1 実行(緑)における、ロールアウト/トレーナーのポリシー比が 1.0 からずれた値を 10,000 倍にスケーリングしたものをステップごとに表示。
ランタイムデフォルト
初期の V1 実行では、エンジンバージョンと V1 のランタイムデフォルトが混在していました:
- プレフィックスキャッシング(prefix caching)は初期の実行で未設定だったため、vLLM 0.18.1 のデフォルトが適用されました。
- 非同期スケジューリング(async scheduling)も初期の実行で未設定だったため、vLLM 0.18.1 のデフォルトが適用されました。
- 起動時の kwarg パススルーを通じて設定された、一時的な disable-cascade-attn のオーバーライドであり、コミット済み構成の整合性レシピの外側に位置しています
整合性実行においては、これらの選択を明示的に行いました:
vllm_config:
use_v1: true
vllm_kwargs:
logprobs-mode: processed_logprobs
enable-prefix-caching: false
async-scheduling: false
プレフィックスキャッシングには別途注記が必要です。これは通常、固定されたモデル状態に対する正準性を保持する推論最適化です。今回のオンライン RL 設定では、V0 リファレンスパスとの比較において、キャッシュの寿命と再利用が V1 のみで異なる点となりました。また、アクターは繰り返されるプレフィックス、並行リクエスト、非同期スケジューリング、および実行中の重み更新も処理していました。
プレフィックスキャッシュヒットでは、キャッシュポリシーが重み更新の境界を無視する場合、更新前に計算された状態を再利用できます。プレフィックスキャッシングを無効化することで、比較対象から V1 固有の自由度の一つを排除しました。
Inflight Weight Updates (実行中の重み更新)
重みの同期も、オンライン RL の更新モデルに合わせる必要がありました。一つの選択肢は、V0 よりも厳格な V1 を実装し、すべての更新時にリクエストを排水してキャッシュをクリアすることです。これは別の問いに答えることになります。まず、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()
2 つの重要な詳細があります:
- mode="keep" は、wait や abort に比べて、古いインフライト更新モデルにより密接に一致します。
- clear_cache=False は、V0 のラッパー動作に一致しており、これは更新時にキャッシュされた状態をそのまま残すものです。
ラグは有用なランタイム診断でした。初期の V1 パスは、修正された V1 実行よりもトレーニング後半においてより永続的なラグを示します。
image
図 5. ロールアウトサーバー内の重みがトレーナーポリシーより遅れているステップ数。vLLM V0 リファレンス(青)、初期の vLLM V1 実行(赤)、修正された vLLM V1 実行(緑)。
残りのギャップ:fp32 lm_head
上記の V1 バックエンドの修正により明白な移行問題は解消されましたが、最終的な整合性を得るには、ロジットを計算するために使用される数値パスを一致させる必要がありました。トレーナーは最終的な投影に fp32 の lm_head(言語モデルヘッド)を使用していました。ロールアウトバックエンドもこの動作に合わせる必要がありました。
非常に類似した問題が MiniMax-M1 技術報告書 に現れています:同社の RL 実行ではトレーニングと推論のトークン確率の不整合が見られ、これは LM 出力ヘッドに起因するものとして特定され、fp32 でヘッドを計算することで修正されました。
これは、RL(強化学習)更新がトークンの対数確率を直接消費するため重要です。ロジットの小さな変化は、ポリシー比、KL 散逸、クリッピングにおいて顕在化します。したがって、最終的な投影精度は、オンライン RL における正しさの範囲の一部となります。
ScaleRL paper では、後に fp32 ロジットおよびヘッド計算をその RL レシピの一部として含め、大規模 RL における有用な設計選択としてアブレーション(除去実験)を行っています。
fp32 の lm_head パスを含めることで、報酬は最終的なパリティ結果のコンパクトな視点を提供します。図 6 では、最終的な V1 ランが V0 リファレンスを追跡しており、初期の V1 試行では明確に異なる報酬曲線が生じています。
image
図 6. vLLM V0 リファレンス(青)、初期の vLLM V1 試行(赤)、および fp32 lm_head パスを含む最終的な vLLM V1 ラン(緑)に対する報酬。fp32 ヘッドを含めることで、最終的な V1 ランは V0 リファレンスを追跡します。
アブレーション実験
否定的な結果は重要です。なぜなら、それらは一般的な説明を排除するからです。
- processed_logprobs 単独:意味論的な対数確率のバグは修正されましたが、トレーニングの不整合は残りました。
- バッチ不変性:別のテストでも不整合が残っており、ラグが増大し、クリップ率が上昇し、NCCL(NVIDIA Collective Communications Library)に複雑さが生じました。
- 最初の V1 ランを公平なベースラインとして扱うこと:最初の V1 ランでは複数の V1 固有のデフォルトが有効化されていたため、これは交絡した移行比較でした。
なぜ最初にバックエンドの正しさを修正したのか
目的関数側の補正、例えば截断された重要性サンプリングや、重要性比の重み付け、および関連する手法は有用なツールです。ロールアウトが意図的に古く設定されている場合、非同期で生成される場合、またはトレーナー側のポリシーとの等価性が保証されないバックエンドで生成される場合は、何らかの形の補正を追加することが適切な対応となることが多いです。
ここで最初の課題は推論の正確性でした。V1 へ移行した後、ロールアウトバックエンドが返すログ確率と実行時挙動が、トレーナー側の前提を崩壊させました。その時点で目的関数側の補正を追加すると、2 つの異なる問いが混同されてしまいます:
- 推論バックエンドは正しいログ確率を生成しているか?
- 正しいログ確率が得られる場合でも、目的関数にはオフポリシーまたは非同期の補正が必要か?
これらの問いは分離する必要があります。そうでなければ、目的関数側の補正が破損した推論バックエンドの挙動を誤って補償してしまい、学習曲線の解釈が困難になります。
現在の目的関数はまだ改善の余地があります。推論の整合性が回復された後、次の改善は通常の非同期/オフポリシーの整理となります:
- ロールアウト時点での明示的な行動ポリシーのログ確率を保持する
- 最適化時点でトレーナー側の旧ポリシーのログ確率を再計算する
- バックエンドの不整合に対する補正と、ポリシー更新比率を分離する
- 補正項に対して ESS(有効サンプル数)などの診断指標を、集約されたトレーナーメトリクスと共に追跡する
この移行から得られる主な教訓はより限定的です:まずバックエンドの正確性を修正し、その後残る不整合に対する補正を追加すること。
原文を表示
- Migration Objective
- Failure Modes
- V1 Backend Fixes Logprob Semantics
- Runtime Defaults
- Inflight Weight Updates
- The Remaining Gap: fp32 lm_head
- Ablations
- Why We Fixed Backend Correctness First
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.

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_indicator
- kl_new_old
- entropy
- reward
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.

The same pattern appears in the trainer metrics. Clip rate is the easiest signal
to read in the initial comparison.

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.

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.1 default
applied
- async scheduling, left unset in the early run so the vLLM 0.18.1 default
applied
- an ad-hoc disable-cascade-attn override 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 than
wait or abort
- clear_cache=False matches 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.

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.

Ablations
The negative results are important because they rule out common explanations.
- processed_logprobs alone: 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.
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み