vLLM の品質保証プロセスを解説
vLLM ブログは、CI、ベンチマーク、リリースプロセスの詳細を公開し、大規模言語モデル推論フレームワークの生産環境品質維持戦略を明らかにした。
キーポイント
包括的な CI/CD の可視化
vLLM は開発プロセスにおける継続的インテグレーション(CI)の詳細な仕組みと、コード品質を保証する自動化されたワークフローを公開した。
厳格なベンチマーク戦略
新機能やパフォーマンス改善が実際の生産環境で期待通りの効果をもたらすことを確認するため、多様なシナリオにおける定量的なベンチマークプロセスを標準化している。
信頼性の高いリリースプロセス
安定したバージョン提供のために、段階的なテストと検証を経てリリースが行われる厳格なプロトコルが確立されていることが示された。
重要な引用
Keeping vLLM Production Quality: A Look Inside CI, Benchmarking, and the Release Process
vLLM blog reveals internal practices for maintaining production quality.
影響分析・編集コメントを表示
影響分析
この記事は、vLLM が単なる実験的なコードではなく、堅牢な生産環境向けソフトウェアとして成熟していることを示す重要な証拠です。開発プロセスの透明性を高めることで、企業ユーザーが vLLM を採用する際のリスクを低減し、エコシステムの信頼性を強化する効果があります。
編集コメント
vLLM が開発プロセスの内部を公開したことは、同プロジェクトが成熟期に入り、コミュニティとユーザーからの信頼獲得に本腰を入れている証左です。特に大規模モデル推論の現場では、安定性と再現性が最優先されるため、この透明性は業界全体にとって有益な動きと言えます。
imagevLLM のプルリクエストが、CI やパフォーマンス・精度評価、リリースゲートを通過する様子
イントロダクション
vLLM は現在、最も広く利用されているオープンソースの LLM 推論エンジンです。GitHub ではスター数が 8.6 万を超え、月間の pip インストール数は 560 万件、イメージプル数は 250 万件に達しています。さらに、1,000 種類以上のモデルアーキテクチャと 600 種以上のアクセラレータに対応しています。
このように多様なモデルやハードウェアをサポートできる点は vLLM の最大の強みですが、同時に安定性を維持するのが極めて困難な要因でもあります。
2026 年 6 月、vLLM はメインブランチに 1,918 コミットをマージしました。これは平均して 1 日あたり 64 コミットに相当し、PyTorch や Kubernetes といった大規模な OSS プロジェクトと同等のペースです。この期間中、CI(継続的インテグレーション)はピーク時に 1,400 の並列ランナーを稼働させ、合計で 1,300 万分のジョブ時間を消費しました。
このようなペースで vLLM をテストするのは、日々難しくなっています。H100 では問題なく動作する変更でも、AMD ではコンパイルに失敗したり、B200 でスループットが低下したり、特定のバックエンドではモデルの出力をわずかに変えてしまう可能性があります。vLLM が使われる理由となっている広範な対応範囲こそが、コミットごとに守らなければならない領域そのものです。
この記事では、このペースで vLLM のリリースを安定させるために私たちが行っていること、成功した施策や得た教訓、そしてまだ課題が残る部分について共有します。ここでは技術的な詳細よりも、高レベルなプロセスに焦点を当てます。技術の詳細については別の記事で取り上げます。
vLLM におけるプルリクエストから新バージョンへのリリースに至るまでの道のりは、以下の 3 つの層を経由する必要があります:
必ず JSON 形式で返してください。translation フィールドのみ。他のフィールドは一切追加しないこと — 余計なフィールドを書こうとして本文がトークン上限で打ち切られる事故を防ぐため:
{"translation": "リライト全文"}
- CI — PR ごとに何が壊れているかを即座に検出する仕組み
- パフォーマンスベンチマークと精度評価 — CI では捉えきれない、静かに壊れる現象を検出する方法
- リリースプロセス — シグナルを評価し、判断を下した上で、ユーザーへ安全にアーティファクトを届ける手順
レイヤー 1: CI
コードベースの全コンポーネントに対する広範なユニットテスト
すべての PR(プルリクエスト)には、まず軽量な GitHub Actions のチェックが走ります。コードの静的解析(linting)やフォーマット確認など、基本的なガードレールです。
コミッターが「マージ可能」と判断すると、CI プラットフォームである Buildkite で本格的なユニットテストの実行が始まります。
imageGitHub Actions のチェックから、動的に選択された Buildkite ジョブへと至る vLLM の CI フロー
Buildkite は各 PR に対してテストパイプラインを動的に構築します。まずブートストラップステップでジョブ定義を読み込み、差分(diff)を検証して関連するグループのみをスケジュールします。
ドキュメントのみの変更であれば、実行されるのは数個のジョブだけです。しかし、重要なカーネルに触れた場合は、100 以上のジョブが並列で起動することになります。
全体として、vLLM の CI スイートは 37 のテストグループと 266 のジョブから構成され、異なるカーネルやスペキュレティブ・ディコーディング(speculative decoding)、LoRA など主要なコンポーネントと機能すべてを網羅しています。各グループに含まれるジョブ数は数個から数十個まで様々で、多くのテストでは複数のコンポーネントが同時に検証されます。以下はその一部です:
imagevLLM の CI テストグループとジョブの例
テスト環境の一貫性を担保する
テストが意味を持つのは、毎回同じ条件で実行される場合に限られます。 障害となるのは主に 2 つの「ドリフト(ズレ)」です。一つは CI ランナー間で環境が異なること、もう一つは時間が経つにつれて依存関係が変わってしまうことです。前者は共有コンテナイメージで、後者は固定された依存関係グラフによって解決します。
どのマシンでも同じコンテナイメージを使用。 266 のジョブが数十種類のマシンに分散して実行される中で、不安定で信頼性の低い結果を招く最速の方法は、各ジョブが独自に微妙に異なる環境を設定させてしまうことです。これを防ぐため、vLLM では主要なジョブのほとんどが共通のコンテナイメージ内で実行されます。このイメージは各ランの実行開始時に一度だけビルドされ、すべての場所で再利用されます。
imagevLLM の CI およびリリースに共通するコンテナのビルドステージ
Dockerfile は多段階で構築されます。各ステージは下位のステージをベースに、必要な要素を追加していく形になっています。
具体的には、ベースステージで CUDA ツールチェーンを用意し、ビルドステージでその上にホイール(wheel)をコンパイルします。そしてランタイムステージで、これらのホイールと実行時の依存関係をインストールして完了です。
そこからビルドは分岐します。一方のイメージにはサービングのエントリーポイントを追加してリリース用とし、別のイメージにはテスト依存関係を追加して CI ジョブが引き出す用にします。この共通の祖先を持つことで、テスト環境と本番環境を常に近い状態に保つことができます。
同じイメージを利用するジョブでは、B200 上のカーネルテストと L4 上のエントリーポイントテストが、全く同じコンテナイメージをバイト単位で引き出しながら、異なるハードウェア上で実行されます。一度ビルドして共有することで、大きな変動要因を排除できます。つまり、各ジョブごとの設定のズレに起因する失敗は大幅に減ります。
毎回同じバージョン。依存関係は時間とともに変化します——これは私たちが痛い目を見て学んだ教訓です。
バージョンが固定されていない依存関係があると、失敗の原因を追跡するのが非常に難しくなります。あるテストが月曜日は成功しても、水曜日にはクラッシュすることがあります。その間のコード変更を一つずつ確認しますが、どれも関連していそうには見えません。数時間後になってようやく気づくのです。「あ、FlashInfer が水曜日に新しいバージョンをリリースして、ビルドが静かにそれを取り込んでいたんだ」と。FlashInfer だけが問題だったわけではありません。nixl や transformers、そしてその転送依存関係も同様に私たちを苦しめました。それぞれの無通告のアップグレードは CI を壊す新たな機会となり、原因は依存関係の層の奥深くに埋もれてしまうのです。
そこで vLLM の CI は依存関係を固定します。トップレベルの依存関係すべてを pip-compile に通してロックファイルを生成し、転送依存関係を含むすべてのパッケージのバージョンを固定しています。
image 上位依存関係をすべて固定した完全なパッケージグラフ
ロックファイルは定期的に更新され、その都度 CI システム全体を再実行しています。このように依存関係のバージョンを完全に固定して以来、依存関係に起因する不具合が頻発するという頭痛の種は解消されました。
多様なハードウェアと複数プロバイダによる CI 計算リソースのスケーリング
各ジョブは Buildkite のランナーキューに送信されます。Buildkite は特定のハードウェア構成を持つマシン群からなるプールです。例えば、gpu_1 キューは L4 GPU を搭載した個別の VM で支えられており、b200 キューは B200 GPU を内蔵した Kubernetes クラスターによって運用されています。ランナーが利用可能になると、キュー内の次のジョブを引き受けて実行し、その結果を Buildkite に報告します。
執筆時点での vLLM の CI システムでは、58 種類のランナーキューが存在し、多様なアクセラレータに対応しています。これらのハードウェアは複数のパートナー組織から提供されています。
image アクセラレータベンダーとハードウェア種別ごとの vLLM CI ランナーキュー
計算リソースへの支出には限度があります。仮に予算が許すとしても、これほど多様なマシンを自社で管理するのは非常に困難な作業です。このように多様性に富んだ CI カバレッジを実現できているのは、多くの素晴らしいパートナーからの寛大な支援と協力のおかげです。
しかし、統合には大きな課題があります。パートナーごとに要件は異なります。すべてにアクセス権限を任せるケースもあれば、自社のハードウェアを管理したいという要望を持つところもあります。また、セキュリティの制約が非常に厳しい環境もあるのです。
では、これら多様な環境を一つの CI パイプラインにどう組み込むのでしょうか?
ここで活躍するのがBuildkite agentです。このエージェントはプロバイダーの環境内で動作し、HTTPS を介して Buildkite へアウトバウンド接続を行います。これにより、Buildkite からエージェントへの接続要求が必要なくなるため、プロバイダー側ではインバウンドポートの開放や VPN の設定、ネットワークへのアクセス権限付与といった手間が不要になります。
エージェントがジョブを受け付けると、指定されたコマンドを実行し、ログをストリーミングで返送した上で最終的な終了ステータスを報告します。永続型(persistent)のエージェントは待機状態に戻り次のジョブを待ちますが、一時的(ephemeral)なエージェントはジョブ完了後に即座に終了します。
このエージェントの運用方法には複数の選択肢があり、プロバイダーは自社の環境に合わせて最適な方式を選んでいます。
最もシンプルな形態はスタンドアロンのマシンです。8 基の A100 を搭載したサーバーや Arm サーバーがこれに該当します。プロバイダー側でエージェントをインストールし、ランナーキューを指定するだけで、このループが継続して実行されます。
imageスタンドアロンの Buildkite エージェントがランナーキューをポーリングし、結果を報告する様子
Kubernetes クラスター内のマシンでは、同じモデルが Buildkite Agent Stack for Kubernetes を通じて機能します。コントローラーは一致するジョブごとに 1 つの Pod を持つ Kubernetes Job を作成し、そこでテストを実行して結果を報告します。私たちは常にこの方法を推奨しています。なぜなら非常にスケーラブルだからです。すべてのノードに Buildkite エージェントをインストールする必要はなく、クラスターに追加するだけで済みます。
imageBuildkite Agent Stack for Kubernetes が CI ジョブごとに 1 つの Pod を作成する様子
どちらの方法でも、導入は私たちが行う側で簡単です。キューを作成しトークンを提供すれば、プロバイダー側がエージェントを起動します。マシンへのアクセス権限は不要です。これこそが、vLLM が所有できる以上のハードウェア、つまり年間数百万ドル相当の寄付されたファームウェア上でテストを行える理由なのです。
ハードウェアの有効活用は容易ではない
需要が高く計算リソースには明確な限界があるため、無駄をなくす必要があります。
大規模 GPU を MIG スライスで分割する
imageH200 GPU 8 枚を分割し、56 のマルチインスタンス GPU(MIG)スライスを作成
CI ジョブの多くは小規模なモデルで実行されるため、GPU 1 台全体を使う必要はありません。NVIDIA の MIG 機能を使えば、1 枚の GPU を複数の独立したスライスに分割できます。例えば H200 は 7 つの 18GB パーティションに分けられるため、同時に 7 つのジョブを 1 台で処理可能です。計算してみると、大規模な GPU を分割して使う方が、同様の負荷をこなすために小規模な GPU を個別に借りるよりもはるかにコストが安いことがわかりました。
ゼロから自動スケール:マシン 1 台あたり 1 ジョブ
時間課金のマシンを利用する場合、稼働させておくだけで無駄遣いになります。そのため各キューは自律的にスケールします。ジョブが待機していればマシンの数を増やし、実行すべきものがなくなればゼロまで減らします。各マシンは 1 つのジョブを引き受け、コンテナ内で実行して終了後にシャットダウンします。この仕組みにはもう一つのメリットがあります。すべてのジョブが新鮮なマシンで実行されるため、過去の処理名残りがテスト結果に影響を与えることがないのです。
再利用可能なものは再構築しない
CI において最も時間がかかり、反復的でコストの高い作業は以下の通りです:
- CI パイプライン全体で使用される標準 Docker イメージのビルド
- CUDA カーネルのコンパイル
- すべての依存関係のインストール
- Hugging Face からのモデル重みデータのダウンロード
そのため私たちは以下を避けるように努めています:
- Docker レイヤー:レジストリのキャッシュを活用し、再構築せずにキャッシュされたレイヤーを再利用します。これには依存関係も含まれます。
- ビルダー用 AMI のウォームキャッシュ:ビルダーマシンに最新のレイヤーを事前に読み込んでおくための AMI を、毎晩ビルドするジョブを用意しています。これにより、ビルダーマシンは本番環境に近い状態から起動できます。
- コンパイラーキャッシュ:sccache を活用し、コンパイルされた C++/CUDA の出力結果を S3 バケットにキャッシュして再利用します。すべてのビルダーマシンはこのバケットから読み込み可能ですが、メインブランチ用のビルダーマシンのみが書き込み権限を持ちます。
- モデル重み:テスト対象のモデルは非常に巨大なため、各クラスターごとに一度だけ共有ストレージへダウンロードし、ジョブ側からはその場所から読み込むようにしています。毎回ギガバイト単位のデータをプルする必要はありません。
CI の健全性を可視化する
1 日に数百回の CI ランが実行され、それぞれが異なるハードウェア上で数百のジョブを走らせています。そのため、システム自体が健全に動作しているかどうかを確認する必要があります。
キューに数時間分の待ち時間が溜まっていたり、テストが 20 回に 1 回の割合で不安定になったり、先月よりジョブの実行時間が 10 分遅延したりする事態が発生しても、それを追跡するのは容易ではありません。
私たちは PyTorch チーム(PyTorch の素晴らしい友人たち)が構築した「PyTorch CI HUD」(hud.pytorch.org) に着想を得て、独自のダッシュボード ci.vllm.ai を作成しました。
15 分ごとに Buildkite パイプラインのデータが Databricks と ClickHouse に取り込まれます。
利用可能なすべてのデータとダッシュボードへの完全な制御権を持つことで、観測スタックを構築する際に非常に高い柔軟性が得られます。これにより、以下のような典型的な質問に迅速かつ容易に回答できるようになりました:
CI の健全性シグナル
01 of 04
メインブランチは現在健全か?

ダッシュボードが示すもの
過去 3 日間、答えは「いいえ」でした。なぜジョブの実行に 10 時間もかかったのでしょうか?
これらは、私たちのダッシュボードが果たせる役割の一部に過ぎません。現代のコーディングエージェントのおかげで、深いフロントエンドの専門知識がなくても、こうしたツールを誰でも使いこなせるようになりました。
失敗の検出と対応の自動化
ダッシュボードは問題を可視化してくれます。次のステップは、検知から診断までの時間を短縮することです。もちろん、そのためには強力な AI エージェントを活用する必要があります。
毎晩、CI アナライザーボットがテストスイート全体を実行し、前日の結果と比較します。新たに失敗した箇所があれば、エラーログを読み込んで失敗の種類を分類し、直近のコミット履歴を追跡して原因となる変更を見つけ出します。そして、修正済みの自動リバージョン PR を作成して Slack に報告し、メンテナーがレビューとマージを行えるようにします。
このボットは、1 日あたり平均 1.5 件の自動リバージョン PR を生成しています。失敗の種類と原因となったコミットを正しく特定できる確率は約 70% です。つまり、オンコール担当者が対応を開始する際、真っ白な状態からではなく、すでに正しい診断結果に基づいて作業を始められるのです。
このボットは、問題発生時の迅速な検出に不可欠な存在となっています。また、コミュニティ全体で問題が発生したその場で修正に取り組む取り組みとも相まって、vLLM の品質を支えています。特に、Red Hat のオンコールローテーションに参加してくださる皆様に感謝いたします!
imageCI アナライザー・ボットが回帰を検知し、リバージョンを提案している様子
グリーンチェックでは見えないもの
これらすべてを組み合わせることで、PR に対する「グリーンチェック(合格)」を信頼できるようになります。広範な単体テストのカバレッジ、多様なアクセラレータでの実行、一貫した環境、そして綿密なモニタリングです。CI をパスすれば、マージに伴うリスクは大幅に低減できると確信できます。
しかし、CI だけでは全体像は見えません。すべてのテストを通過しても、モデルの処理速度が低下したり、出力結果が誤ったりする可能性があります。CI の高速化とコスト削減のため、私たちは多くの E2E テストを省略しがちです。それ以上に重要なのは、vLLM ユーザーが日常直面する状況を十分にシミュレートできていない点です。次の層は、まさにその課題を解決するために存在します。
レイヤー 2:パフォーマンスベンチマークと精度評価
5 月に v0.20.0 をリリースした直後、わずか数日で緊急パッチである v0.20.1 と v0.20.2 を公開せざるを得ない事態になりました。2 つの重大な問題がすり抜けていたのです。1 つは Blackwell アーキテクチャ上で gpt-oss が複数 GPU にまたがる(テンソル並列度が 1 より大きい)場合に動作しなくなったというもので、もう 1 つは GB200 上での DeepSeek V4 のスループットが急激に低下したというものです。
当時、私たちはベンチマークパイプラインを持っていませんでした。つまり、リリース前にこれらのモデルがそのハードウェア上で正しく動作し、高速で実行されることを確認する E2E テストが何一つ走っていなかったのです。そのため、両方の問題が CI を潜り抜け、ユーザーに届いてしまいました。
パフォーマンスの低下は、サーバーが起動しリクエストも成功する限り、すぐにクラッシュすることはありません。ユーザーが体感するのは、1 秒あたりに生成されるトークン数の減少や、最初のトークンを待つ時間の延長です。同様に、精度の低下も目立たないものです。モデルは有効な回答を返しますが、その内容は誤っています。
私たちは、パフォーマンスベンチマークと精度チェックを両方実施してモデルをエンドツーエンドで検証することの重要性に気づきました。そこで、この検証層を構築するために多くの時間を費やしました。
現在、このシステムはリリースプロセスにおいて重要な指標を提供しており、すでにいくつかの重大な回帰問題を検出しています。v0.20.0 のリリース前に発生していた問題も、今回構築したシステムであれば確実に捕捉できたはずです。
毎夜実行するモデルとアクセラレーターの組み合わせテスト
私たちは、https://github.com/vllm-project/perf-eval でパイプラインを維持しています。各設定ファイルは、vLLM サーバーの起動方法や引数、提供するモデル、使用するアクセラレータ、実行するタスクなど、ワークロードの詳細を記述します。
各ワークロードでは、通常以下の 3 つのタスクが実行されます:
- パフォーマンスベンチマーク — vllm-bench を使用して、最初のトークンまでの時間(TTFT)や出力トークンあたりの時間(TPOT)、その他多くの指標を測定します。
- 数学的推論モデルの精度評価 — lm-eval を用いて、GSM8K、GPQA、AIME などのベンチマークでモデルの精度を検証します。
- 関数呼び出しの精度検証 — Berkeley Function-Calling Leaderboard(BFCL)を通じて確認します。
image 毎夜、vLLM ではワークロードの性能評価に加え、精度や関数呼び出しのテストを自動実行しています。
毎晩、そしてリリース候補版が出るたびに、DeepSeek V4 Pro/Flash、gpt-oss、Kimi K2.5、MiniMax M2.5 と M3、Qwen3.5、GLM 5.1、Gemma 4、Nemotron 3 Super など主要モデルを対象に、H200、B200、MI300X、MI355X の各ハードウェア上でフルスイートの実行を行います。現在では合計 17 種類のモデルとハードウェアの組み合わせ(レシピ)があり、さらに増えつつあります。今後は GB200/GB300 や PD 非同期化(disaggregation)への対応も追加予定で、対象モデルも拡大していく方針です。
常に高速なのか?
各実行が完了すると、結果はデータベースに格納されます。以前ご紹介した CI ダッシュボードにも、この性能データが表示されています。
毎晩の測定値をグラフ化し、経時的なパフォーマンスの低下(リグレッション)を容易に検出できるようにしています。
例えば、Performance dashboard の画面は以下の通りです:
image H200 上での gpt-oss 120B の性能履歴
*テンソル並列度を 8 に設定し、同時実行数ごとに分割した gpt-oss 120B の性能履歴*
Compare view を使えば、2 つの vLLM イメージを直接比較できます。例えば、リリース候補版と直前のリリース版を並べて検証するといった使い方が可能です。
imageパフォーマンスダッシュボードで 2 つの vLLM イメージを比較
常に正しいのか?
vLLM インスタンスが驚異的な速度を発揮していても、出力内容がゴミであればその速さは無意味です。パフォーマンスだけでなく、モデルからの回答が信頼できるかどうかも厳しくチェックしています。
Evaluation ダッシュボード には集計スコアと誤差範囲が表示され、そこから特定のランを実行して詳細を確認できます。ここでは、質問文、正解となる参照回答、生のモデル応答、抽出された回答、そして正誤判定結果を一つずつ inspect 可能です。単なる集計値からデバッグするよりも、こうしたサンプルレベルのエビデンスの方がはるかに有用です。
image誤った評価サンプルの調査
*GSM8K の不正解サンプルでは、正確な質問文、期待される回答、モデルの応答、そして抽出結果が明確に示されます。
レイヤー 3:リリースプロセス
高速なペースでの提供
2025 年 11 月以降、vLLM のリリースは 2 週間ごとのサイクルを維持しています。同規模のプロジェクトでは通常、これよりも遥かに長い期間をかけてリリースを行うのが一般的です。なぜ私たちはこのペースを堅持しているのか。
- ユーザーへの変更が迅速に届きます。新しいリリースは常にメインブランチからすぐ後に公開されます。
- 予測可能性が高いです。ユーザーや下流のプロジェクトは、次のリリース時期を推測するのではなく、安定したスケジュールに基づいて計画を立てられます。
- 機能管理と回帰バグの特定が容易になります。500 コミット程度であればバイセクション(二分探索)による調査も可能ですが、数千コミットに及ぶ場合はその負担が大きくなります。
デッドラインのプレッシャーも軽減されます。コントリビューターは、列車(リリース)が出発する前に慌てて変更を提出する必要がなくなり、次回の 2 週間後の列車に乗せるだけで済みます。
チェリーピックもクリーンに保たれます。数日前の修正であれば、通常は単純なピックアップで済み、マージ競合による混乱にはなりません。
隔週の月曜日にリリース週が始まります。その様子を見てみましょう。
image vLLM のリリース候補のテストと公開ループ
最も安全なコミットから始める
月曜日、リリースマネージャーはメインブランチにおける最新の完全 CI 実行結果を確認し、最も健全な状態にあるコミットを選択します。これにより、リリース固有の変更を加える前に、リリースブランチに利用可能な中で最も健康な出発点を与えます。
その特定のコミットで releases/vX.Y.Z ブランチを切り出し、ブランチ名とリリース期間を発表します。
リリース候補ごとの厳重なテスト
ブランチ作成から水曜日にかけて、チェリーピックの依頼を確認し、それらをバッチ処理でリリースブランチに取り込み、結果を次のリリース候補としてタグ付けします。
すべての候補は、以下の 3 つのゲートを通過する必要があります。
- 完全 CI スイート
- パフォーマンスベンチマークスイート
- モデル精度評価スイート
各結果は特定のリリース候補に紐付きます。後続の候補で CI の健全性やパフォーマンス、評価品質に変化が生じた場合、どの候補がその違いをもたらしたのかを特定できます。対象となる候補は、わずか数十コミット先のものです。
水曜日に cherry-pick の期間を閉じます。その後、リリース候補版に対しては既存の課題に対する修正のみが cherry-pick 対象となり、新しい候補版タグが付与され、3 つのゲートを通るまで再テストされます。最終的に基準を満たす候補版が出るまでこのプロセスを繰り返します。
バージョン品質への妥協はない
候補版が合格するのは、3 つのゲートをすべて通過した場合のみです。
週末になっても合格する候補版が出ない場合もありますが、それは問題ありません。私たちは期限通りにリリースできるよう最善を尽くしますが、そのために基準を下げることは決してありません。新しいバージョンについては、Rockstar が GTA 6 を扱う姿勢と同じように、「完成した時が完成」と考えます。ただし、10 年かかるわけではありませんが…
すべてのプラットフォームに対応してリリース
候補版が合格すると、そのコミットを基にさまざまなハードウェアプラットフォームや CUDA バージョン向けのビルド成果物を作成し始めます。これにより、誰もが vLLM をネイティブで利用できるようになります。そして、何よりも先に、ビルドされた成果物自体に対して smoke test(簡易動作確認)を実施します。
執筆時点では、すべてのリリースで以下の成果物を提供しています:
- Python wheel 7 種類:
- CUDA 12.9 (x86_64/arm64)
- CUDA 13.0 (x86_64/arm64)
- CPU (x86_64/arm64)
- ROCm
- Docker イメージ 11 種類:
- CUDA 12.9, x86_64/arm64, Ubuntu 22.04/24.04
- CUDA 13.0, x86_64/arm64, Ubuntu 22.04/24.04
- ROCm
- CPU (x86_64/arm64)
次のステップ
これまでの取り組みについて自慢話が多くなりましたが、正直に言えば、ロードマップ上にはまだ多くの課題が残っています。主なものは以下の通りです:
- テストの自動選定。現在は各 PR でどのテストを実行するかを手動で管理したマッピングに基づいて選択していますが、この情報はすぐに陳腐化してしまいます。これを自動化したいと考えており、LLM を活用した選定、静的解析、動的解析、ソースパスにラベルを付けて対応するテストと紐付ける手法など、いくつかのアプローチを試しています。
- 結果までの時間短縮。現在の CI は平均して 1〜2 時間かかりますが、これを 30 分以内に抑えたいと考えています。
- ユニットテストの効率化。多くの「ユニットテスト」は実際には vLLM サーバーを起動し、実際のリクエストを送信しているため、CI の速度が大きく低下しています。
- 終了コードの扱い改善。一部のジョブでは失敗時に誤った終了コードを返すケースがあり、例えばインフラの問題をテスト失敗として報告してしまい、障害の切り分けやアラート・再試行処理が困難になっています。
- フラキー(不安定)なテストの検出と隔離の高速化。インフラや上位パッケージに起因するもの、あるいは安全に記述されていないテストなど、多くのフラキーテストが存在します。これらを自動的に検出して隔離できるようにしたいと考えています。
- インフラ問題の自動検出。不良マシンの兆候を早期に察知し、多数のジョブが失敗する前に CI フリートから自動的に除外できるようにしたいです。
- アラート機能の強化。ランナーキューの混雑や回帰現象に対する基本的なアラートはありますが、さらに充実させたいと考えています。具体的には、CI ランナーでのディスク使用圧力の検知、通常より急激にジョブが失敗する状況の検出、依存パッケージのインストール失敗などが挙げられます。
- コードカバレッジレポートの改善。カバレッジ範囲は広くなっていますが、コードベースの隅々まで実際にテストされていることを確信できる状態にはまだ至っていません。
CI の開発は、多くの人が思っている以上に面白いものです。この記事では vLLM のリリースを安定させるための高レベルなプロセスについてのみ取り上げましたが、もっと楽しい技術的な詳細もたくさんあります。これらは別の投稿で扱えるかもしれません。
これらの問題があなたの興味に合致している場合や、私たちが何か間違っていると感じる場合は、vLLM の Slack で #sig-ci チャンネルまでお気軽にご連絡ください。また、この分野を専門としてフルタイムで働きたいとお考えの方は、Inferact 社で採用を行っていますので、ぜひご応募ください。
謝辞
これらは決して一人の努力ではありません。vLLM の CI はコミュニティ全体によって構築され、維持されています。
CI の開発に貢献してくださったすべての皆様に心から感謝いたします(アルファベット順):
- Amazon: Junpu Fan, Liangfu Chen, Omri Shiv,
- AMD: Alexei Ivanov, Andreas Karatzas, Kenny Roche, Micah Williamson
- Arm: Fadi Arafeh, Ioana Ghiban
- EmbeddedLLM: Tun Jian Tan
- Google: Brittany Rockwell, Jincheng Chen, Ming Huang, Qiliang Cui, Yarong Mu, Yiwei Wang
- HuggingFace: Harry Mellor
- Inferact: Harry Chen, Jiangyun Zhu, Kaichao You, Nick Hill, Roger Wang, Simon Mo, Zhewen Li
- Intel: Chendi Xue, Jiang Li, Kunshang Ji, Wenjun Liu
- Meta: Andrey Talman, Charlotte Qi, Eli Uriegas, Huamin Li, Huy Do, Orion Reblitz-Richardson, Reza Barazesh
- NVIDIA: Alec Flowers, Benjamin Chislett, Mathew Wicks, Pen Chung Li, Stefano Castagnetta, Vadim Gimpelson, Xin Li
- Red Hat: アンディ・リンフート、アビナシュ・シン、ドグ・スミス、エドワード・クワーム、フローラ・フォン、ルーカス・ウィルキンソン、ルカ・ゴベディッチ、マイケル・ゴイン、ニコロ・ルッチェージ、ロバート・ショー、ラッセル・ブライアント、タルン・クマール、タイラー・マイケル・スミス、ウェントァオ・イェ
- Reflection AI: アムル・マフディ(メタ在籍時の貢献)
- 個人貢献者:サイラス・レオン(DarkLight1337)、ユーチー・ワン(noooop)、haosdent、モハメド・アンガード
素晴らしいパートナーの方々:
- AWS、Crusoe、LambdaLabs、Nebius、NVIDIA、Roblox、RunPod:計算リソースのクレジットを提供してくださりありがとうございます。
- Buildkite:プラットフォーム上で CI を無償で実行できる環境を提供してくださり、本当に感謝しています <3
そして最後に、Anyscale(Ray)在籍中に CI について多くのことを教えてくれた二人のメンター:Lonnie Liu(現 OpenAI)、Cuong Nguyen(現 NVIDIA)。
原文を表示

Intro
vLLM is the most widely used open-source LLM inference engine. 86K+ GitHub stars. 5.6M+ monthly pip installs. 2.5M+ monthly image pulls, with support for 1000+ model architectures and 600+ accelerator types.
Supporting this many models and accelerators has always been one of vLLM's biggest strengths. It's also what makes it so hard to keep stable.
In June 2026, vLLM merged 1,918 commits into main — 64 a day on average, on par with other big OSS projects like PyTorch or Kubernetes. During this time, our CI ran 13 million job minutes with 1400 concurrent runners at peak.
Testing vLLM, especially at this pace, gets harder every day. A change that's clean on an H100 might fail to compile on AMD, lose throughput on B200, or nudge a model's outputs just enough on one backend to matter. The surface area that makes vLLM worth using is the exact surface area we have to defend on every commit.
In this post, I want to share how we keep vLLM releases stable at this pace: what works, what we've learned, and where we still fall short. It will mostly cover high-level processes rather than technical details; I'll save those for another post.
A journey from pull requests to a new version release on vLLM has to go through three layers:
- CI — how we catch what breaks loudly, on every PR
- Performance benchmarking & accuracy evaluation — how we catch what breaks silently, beyond what CI can cover
- Release process — how we evaluate the signals, make the call, then build and ship artifacts to users safely.
Layer 1: CI
Extensive unit testing on every component of the codebase
Every PR starts with lightweight GitHub Actions checks—linting, formatting, and similar guardrails. Once a committer thinks the PR is ready to merge, the heavier unit testing then starts running on Buildkite, our CI platform.

Buildkite assembles each PR’s testing pipeline dynamically: a bootstrap step reads the job definitions, inspects the diff, and schedules only the relevant groups. Change only documentation and you may get a handful of jobs. Touch a few important kernels? Buckle up for 100+ jobs launching in parallel.
In total, the vLLM CI suite runs 37 test groups and 266 jobs, covering every major component and feature—from different kernels to speculative decoding to LoRA. Groups range from a couple of jobs to a few dozen, and many tests exercise several components at once. Here is a subset:

Ensuring test environment is consistent
A test only means something if it runs the same way every time. We usually see two kinds of drift get in the way: the environment can differ across our CI runners, and dependencies can change under us over time. A shared container image removes the first; a pinned dependency graph removes the second.
Same container image, every machine. With 266 jobs fanning out across dozens of machine types, the fastest way to a flaky, untrustworthy result is to let each job set up its own slightly different environment. To avoid this, the majority of our jobs run inside the same container image, built once at the start of a run and reused everywhere. Our Dockerfile builds in stages, each adding to the one below it.

A base stage provides the CUDA toolchain; a build stage compiles the wheels on top of it; and a runtime stage installs those wheels with their runtime dependencies.
From there, the build forks: one image adds the serving entrypoint and becomes the release image, while a separate test image adds the test dependencies and becomes the image CI jobs pull. That shared ancestry keeps what we test close to what we ship.
For jobs using that shared image, a kernel test on a B200 and an entrypoints test on an L4 pull the same container image, byte for byte, while running on different hardware. Building it once removes a major source of variation: failures are much less likely to come from per-job setup drift.
Same versions, every run. Dependencies drift over time—and that's the half we learned the hard way.
An unpinned dependency makes failures tricky to chase down: the same test passes on Monday and crashes on Wednesday. You read every code change in between; none of it looks related. Hours later, it clicks—FlashInfer shipped a new version on Wednesday, and the build quietly picked it up. And FlashInfer was never alone: nixl, transformers, and their transitive dependencies bit us the same way—each unannounced upgrade a fresh chance to break CI, with the cause buried a dependency layer down.
So vLLM CI locks its dependencies. We run the top-level dependencies through pip-compile to generate lock files that pin every package, including transitive dependencies.

We update the locks periodically and run the full CI suite each time. Since we started pinning the full graph, dependency-caused breakages are no longer a recurring headache.
Scaling CI compute across a heterogeneous, multi-provider fleet
Each job gets pushed to a runner queue on Buildkite — a pool of machines with a particular hardware profile. For example, the gpu_1 queue is backed by individual VMs with L4 GPU; the b200 queue is backed by a Kubernetes cluster with B200s inside. When a runner becomes available, it claims the next job in the queue, runs it, and reports the result back to Buildkite.
vLLM CI, at the time of writing, has 58 runner queues spanning a wide range of accelerators, and that hardware is provided by multiple partner organizations.

There’s a limit on how much we can spend on compute. Even if we can afford it, managing all of these machines ourselves is a pretty tough job. CI coverage with this much diversity is only possible because of the generous support and collaboration from many of our amazing partners.
However, integration is a big challenge. Every partner has different requirements: some simply hand us access to everything, some prefer to manage their own hardware, some have very tight security guardrails.
So how do we manage to plug them all into one CI pipeline?
This is where Buildkite agent comes in. It runs inside the provider’s environment and connects outbound to Buildkite over HTTPS to receive work. Because Buildkite does not need to initiate connections to the agent, providers do not have to expose inbound ports, configure a VPN, or give us access to their network.
When the agent accepts a job, it runs the command, streams the logs back, and reports the final exit status. A persistent agent then waits for more work, while an ephemeral agent exits after completing its job.
There's more than one way to run that agent, and providers pick whatever fits their setup.
The simplest is a standalone machine — our 8xA100 machine or Arm server. The provider installs the agent, points it at a runner queue, and it runs that loop forever.

For machines in a Kubernetes cluster, the same model works through the Buildkite Agent Stack for Kubernetes. The controller turns each matching job into a Kubernetes Job with a single Pod, which runs the test and reports back. We always recommend this way because it’s very scalable: you don’t need to install Buildkite agents on every single node, just add them into the cluster.

Either way, onboarding is simple on our end: we create a queue, provide a token, and the provider starts the agent themselves. We don’t need access to the machine. That's what lets vLLM test on more hardware than we could ever afford to own—a donated fleet worth millions of dollars a year.
Utilizing hardware is challenging
There’s a lot of demand and a hard limit on compute, so we need to make sure none of it goes to waste.
MIG-slice the big GPUs

Most CI jobs run with smaller models and need far less than a whole GPU. NVIDIA's Multi-Instance GPU (MIG) lets us carve one card into several isolated slices — an H200 becomes seven 18 GB partitions — meaning 7 jobs can share one GPU at a time. We did some math here and realized that in many cases, slicing a big GPU is a lot cheaper than renting smaller GPUs for the same amount of workload!
Autoscale from zero, one job per machine
For the machines we rent by the hour, leaving them on and idle just wastes money. So each of those queues scales itself: when jobs are waiting, it starts more machines; when there's nothing to run, it goes down to zero. Each machine picks up one job, runs it in a container, and shuts down. As a bonus, it also keeps tests clean: every job gets a fresh machine, so nothing left over from a past run can mess with it.
Don't rebuild what you can reuse
The slowest, most repetitive, and most expensive parts of CI are:
- Building the standard Docker image used by whole CI pipeline
Compiling CUDA kernels
- Installing all dependencies
- Downloading model weights from Hugging Face.
so we try not to:
- Docker layers: we apply registry caching and reuse cached layers instead of rebuilding. This would include the dependencies.
- Warm-cache AMI for builder: we have a nightly job to build the AMI used for our builder machines with the latest layers already pulled, so our builder machine starts as close to main as possible.
- Compiler cache: we leverage sccache so that compiled C++/CUDA outputs are cached in an S3 bucket and reused across builds. Every builder machine can read from this bucket, but only builder machines used for the main branch can write to it.
- Model weights: the models we test are huge, so for each of the clusters, we download them once to shared storage and every job reads from there, instead of pulling gigabytes each time.
Making CI health visible
With hundreds of CI runs a day, each running hundreds of jobs across different hardware, we also need to know whether the system itself is healthy.
A queue quietly backs up to hours of wait time. A test starts to flake one run in twenty. The job runs 10 minutes slower than last month. It’s not easy to track that.
We took inspiration from the incredible PyTorch CI HUD (hud.pytorch.org), built by our good friends at PyTorch, and created one of our own at ci.vllm.ai.
Every 15 minutes, data from our Buildkite pipelines is ingested into Databricks and ClickHouse.
With all the available data and full control of the dashboard, we have so much flexibility on building out our observability stack. It gives us an easier time answering these typical questions:
CI health signals
01 of 04
Question
Is main branch healthy right now?

What the dashboard tells us
For the past 3 days, no. And why did jobs take 10 hours!?
Those are just a few examples of what our dashboard can do. Modern coding agents have made this kind of tooling surprisingly approachable, even without deep front-end expertise.
Automating failure detection and response
The dashboard helps us see problems. The next step is shortening the time from detection to diagnosis, and of course we have to leverage the powerful AI agents here.
Every night, a CI-analyzer bot runs the full suite and compares the results with the previous night's run. If something newly failed, it reads the error logs, classifies the failure, and walks the intervening commits to find the culprit. It then posts a report to Slack with an auto-revert PR ready for maintainers to review and merge. That's about 1.5 auto-revert PRs a day, with the right failure and culprit commit identified around 70% of the time—so the on-call reviewer usually starts from a correct diagnosis instead of a blank page.
The bot has become essential to catching breakages fast, alongside the community effort to fix issues as they land—shout-out to everyone that helps, especially the on-call rotation at Red Hat!

What a green check cannot tell us
Put together, all of this is what lets us trust a green check on a PR: broad unit test coverage, run across a huge fleet of different accelerators, in consistent environments, and well-monitored. When CI passes, we're confident merge risk is significantly reduced.
But CI doesn’t tell the whole story. A change can pass every test and still make a model slower or its output incorrect. To keep CI fast and affordable, we tend to skip a lot of e2e tests and, more importantly, not closely simulate what vLLM users go through every day. That's what the next layer is for.
Layer 2: Performance benchmarking & accuracy evaluation
In May, we shipped v0.20.0 and within days had to cut two emergency patches, v0.20.1 and v0.20.2. Two problems had slipped through: one broke gpt-oss on Blackwell when split across multiple GPUs (tensor parallelism > 1), the other tanked DeepSeek V4 throughput on GB200.
At the time we had no benchmarking pipeline; nothing ran these models end to end on that hardware to confirm they still worked and ran fast before we shipped. So both problems sailed past CI and reached users.
Performance regressions rarely crash. The server starts and requests succeed; users simply get fewer tokens per second or wait longer for the first token. Accuracy regressions are quiet: the model returns a valid response, but the answer is wrong.
We realized how important it is to run models end to end, with performance benchmarks and accuracy checks, so we invested a lot of time building this layer.
It now provides a lot of signals for our release process and has already caught several major regressions. We built the system that would’ve caught the problems on v0.20.0 before it was shipped.
Running a matrix of models and accelerators every night
We maintain our pipeline at https://github.com/vllm-project/perf-eval. Each config file describes a workload: how to start vLLM server, which arguments to use, which model to serve, which accelerator, and which tasks to run.
Each workload generally runs three tasks:
- Performance benchmark — measuring time-to-first-token (TTFT), time-per-output-token (TPOT), and many other metrics — using vllm-bench
- Model accuracy on math and reasoning benchmarks (GSM8K, GPQA, AIME) using lm-eval
- Function-calling accuracy via the Berkeley Function-Calling Leaderboard (BFCL)

Every night, and for every release candidate, we run the full suite across selected models — DeepSeek V4 Pro/Flash, gpt-oss, Kimi K2.5, MiniMax M2.5 and M3, Qwen3.5, GLM 5.1, Gemma 4, and Nemotron 3 Super — on H200, B200, MI300X, and MI355X. That's 17 model-hardware recipes in total right now, and the list keeps growing. We plan to add support for GB200/GB300, PD disaggregation, and more models very soon.
Is it always fast?
After every run, the results are ingested into our database. Remember the CI dashboard from earlier? It has perf results too!
We turn the nightly numbers into charts that make regressions easy to spot over time.
For example, this is a view of our Performance dashboard:
*Performance history for gpt-oss 120B on H200 with tensor parallelism 8, split by concurrency.*
The Compare view lets us compare two vLLM images head-to-head — say, a release candidate against the last release.

Is it always correct?
If your vLLM instance is blazing fast but its output is garbage, that speed is worthless. Beyond performance, we make sure the model's answers still hold up.
The Evaluation dashboard stores aggregate scores and error bars, then lets us open a run and inspect the underlying question, reference answer, raw response, extracted answer, and correctness result. That sample-level evidence is far more useful than debugging from a single aggregate number.
*An incorrect GSM8K sample exposes the exact question, expected answer, model response, and extraction result.*
Layer 3: Release process
Shipping on a fast cadence
Since November 2025, we have been maintaining a two-week cadence on vLLM releases. Many projects of our size take a lot longer to ship. Why we keep this cadence:
- Changes reach users fast. A new release is never far behind main.
- It’s predictable. Users and downstream projects can plan around a steady schedule instead of guessing when the next release lands.
- Managing features and tracing regressions are easier when there are 500 commits to bisect rather than a few thousand.
- Less deadline pressure. Contributors no longer feel the need to rush their changes in before the train departs. They just catch the next one in two weeks.
- Cherry-picks stay clean. A fix from just days ago is usually a simple pick, not a merge-conflict mess.
Every other Monday, we kick off release week. Here's what that looks like:

Start from the safest commit
On Monday, the release manager reviews the most recent full-CI runs on the main branch and chooses the greenest commit. That gives the release branch the healthiest available starting point before any release-specific changes are added.
We cut releases/vX.Y.Z at that exact commit and announce the branch and release window.
Heavy testing on every release candidate
From the branch cut through Wednesday, we review the cherry-pick requests, cherry-pick them into the release branch in batches, and tag the result as the next release candidate.
Every candidate goes through the same three gates:
- Full CI suite
- Performance benchmark suite
- Model accuracy evaluation suite
Each result is tied to a release candidate. When a later candidate changes CI health, performance, or evaluation quality, we can track down which candidate introduced the difference: they are just tens of commits away.
We end the cherry-pick window on Wednesday. Then, only fixes for existing issues on release candidates can be cherry-picked, followed by a new release-candidate tag and another run through the three gates, until one candidate meets the bar.
No compromise for the bar
A candidate qualifies only when all three gates pass.
Sometimes there’s no qualifying candidate at the end of the week, and that’s okay. We try our best to release on time, but never compromise our bar just to make it. We treat our new version the way Rockstar treats GTA 6: it’s done when it’s done. We don’t take a decade though…
Ship for every platform
Once a candidate qualifies, we take that commit and start building all the artifacts for different hardware platforms and CUDA versions, ensuring that everyone out there can use vLLM natively. And before anything ships, we smoke test the built artifacts themselves.
At the time of writing, we are shipping these for every release:
- 7 Python wheels:
CUDA 12.9 x86_64/arm64
- CUDA 13.0 x86_64/arm64
- CPU x86_64/arm64
- ROCm
- 11 Docker images:
CUDA 12.9, x86_64/arm64, Ubuntu 22.04/24.04
- CUDA 13.0, x86_64/arm64, Ubuntu 22.04/24.04
- ROCm
- CPU x86_64/arm64
What's next
I've been bragging a lot about what we've built — but honestly, we still have a lot to do on our roadmap. Some of the big ones:
- Automatic test selection. Today we pick which tests run for each PR from a hand-maintained mapping, and it goes stale fast. We want this to be automatic, and we're trying a few angles: LLM-based selection, static analysis, dynamic analysis, and labeling source paths to match them to tests.
- Faster time-to-signal. CI takes 1–2 hours on average to return a verdict; we'd love to get that under 30 minutes.
- Leaner unit tests. A lot of our "unit" tests actually spin up a full vLLM server and fire real requests at it, which slows down CI a lot.
- Better exit-code handling. Some jobs still return the wrong exit code when they fail, like reporting an infra problem as a failed test, making it hard to triage failures and alert/retry jobs.
- Faster flaky-test detection and quarantine. We have plenty of flaky tests — from infra, upstream packages, or tests that just aren't written safely — and we'd like to catch and quarantine them automatically.
- Automatic detection for infra issues. Spot a bad machine quickly and pull it out of the CI fleet on its own, before it fails a pile of jobs.
- Better alerting. We have some basic alerts for congested runner queues and regressions. It’s always nice to have more: high disk pressure on CI runners, jobs suddenly failing far faster than usual, broken dependency installs, etc.
- Code-coverage reporting. Our coverage is broad, but we can't yet say for sure that every corner of the codebase is actually exercised.
Working on CI is actually a lot more interesting than most people think. This post only covers the high-level process of how we keep vLLM releases stable; there are plenty of fun technical details I didn't get to cover — maybe in another post :)
If any of these problems sound like your kind of fun, or you think we're doing something wrong, come say hi in #sig-ci on the vLLM Slack. And if you'd like to work on this full-time, we're hiring at Inferact~!
Acknowledgements
None of this is a solo effort. vLLM CI is built and kept alive by the whole community.
I'm deeply grateful to everyone who helped with CI along the way (listed alphabetically):
- Amazon: Junpu Fan, Liangfu Chen, Omri Shiv,
- AMD: Alexei Ivanov, Andreas Karatzas, Kenny Roche, Micah Williamson
- Arm: Fadi Arafeh, Ioana Ghiban
- EmbeddedLLM: Tun Jian Tan
- Google: Brittany Rockwell, Jincheng Chen, Ming Huang, Qiliang Cui, Yarong Mu, Yiwei Wang
- HuggingFace: Harry Mellor
- Inferact: Harry Chen, Jiangyun Zhu, Kaichao You, Nick Hill, Roger Wang, Simon Mo, Zhewen Li
- Intel: Chendi Xue, Jiang Li, Kunshang Ji, Wenjun Liu
- Meta: Andrey Talman, Charlotte Qi, Eli Uriegas, Huamin Li, Huy Do, Orion Reblitz-Richardson, Reza Barazesh
- NVIDIA: Alec Flowers, Benjamin Chislett, Mathew Wicks, Pen Chung Li, Stefano Castagnetta, Vadim Gimpelson, Xin Li
- Red Hat: Andy Linfoot, Avinash Singh, Doug Smith, Edward Quarm, Flora Feng, Lucas Wilkinson, Luka Govedic, Michael Goin, Nicolo Lucchesi, Robert Shaw, Russell Bryant, Tarun Kumar, Tyler Michael Smith, Wentao Ye
- Reflection AI: Amr Mahdi (contribution made during his time at Meta)
- Independent contributors: Cyrus Leung (DarkLight1337), Yuqi Wang (noooop), haosdent, Mohammad Angkad
the amazing partners:
- AWS, Crusoe, LambdaLabs, Nebius, NVIDIA, Roblox, RunPod for sponsoring us with compute credits
- Buildkite for letting us run CI free of charge on their platform <3
and finally, two mentors who taught me a lot about CI during my time at Anyscale (Ray): Lonnie Liu (now at OpenAI) and Cuong Nguyen (now at NVIDIA).
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み