AI技術教材 / LLM推論最適化

vLLMは何を速くしているのか
— 渋滞・ページング・量子化、そして公開したときの穴

この記事は、AI が生成したとみられる vLLM 解説文書 3 本を一次資料と突き合わせて検証した結果を下敷きに、誤りを取り除いて組み直したものです。検証で最も繰り返し出てきた発見は技術そのものより読み方に関わるもので、印象的な数字ほど出典がない、というものでした。87.5%削減、24倍、320万ドル、性能低下20%——どれも文書中で最も目を引き、どれも裏が取れませんでした。
前提として読むとよい記事KVキャッシュと回帰性(なぜ生成がメモリ律速になるのか、KVキャッシュ電卓つき)。本記事はその上に「では推論サーバは何をしているのか」を積みます。
検証時点:2026年前半。vLLM は変化が速いため、バージョン依存の記述には版数を添えています。実測は特記なき限り 実測 で示した自環境(DGX Spark, GB10)のものです。
00 / 読み方の話

技術記事が壊れる4つの型

本題に入る前に、検証で見つかった誤りの分類を置きます。これは vLLM に固有ではなく、あらゆる技術記事に効く読み方のチェックリストになります。

型1 / 出典なき定量値

24倍・87.5%・320万ドル・欠陥検出99.9%。目を引く数字ほど、根拠に当たると消える。数字が出てきたら「誰が・いつ・何と比べて・どのワークロードで」の4点が書いてあるかを見る。4点そろわない数字は、記事の主張ではなく装飾です。

型2 / ベースラインの取り違え

「量子化で87.5%削減」は FP32 を基準にすれば正しい。しかし現代の LLM は BF16/FP16 で配布されている。実際に置き換わるのは BF16 → INT4 で、重みは 75%。しかも VRAM 全体では KV キャッシュと活性値が残るので 50〜60%。基準をずらすだけで、効果は 2 倍良く見えます。

型3 / レイヤーの混同

通信・メモリ・アプリ状態・安全性は別の層にある。「vLLM が通信ボトルネックを解消し、LangGraph のチェックポイントが同時障害に備える」という文は、4つの層を1本の因果連鎖に押し込んでいる。層をまたぐ因果は、たいてい書き手が層を区別していないだけです。

型4 / 手法の同一視

AWQ・GPTQ・FP8 を「量子化」でひとくくりにすると、bit 幅も原理も適用条件も違う三者が同じ箱に入る。「同じカテゴリ名で呼べる」ことは「同じ性質を持つ」ことを意味しない。§3 はこの解体に一章を使います。

以降、各章の末尾に「よくある誤解」ボックスを置きます。そこに並ぶのは、実際に検証対象の文書に書かれていた誤りです。

01 / 渋滞

なぜ本番サーバーだと急に遅くなるのか

手元で1本だけ流すと十分速いモデルが、同時に人が使い始めた途端に遅くなる。原因は演算能力ではなく、スケジューリングとメモリの持ち方にあります。

推論には性格の違う2つの相があります。プリフィル(入力プロンプト全体を一度に通し、KV キャッシュを作る)は行列積が大きく演算律速。デコード(1トークンずつ生成する)は1ステップあたりの計算が小さいのに、モデル重みと KV キャッシュを全部読み直すためメモリ律速。生成が遅いと感じるのは後者です。

ここで素朴な実装、静的バッチングを考えます。到着したリクエストを B 本まとめて1バッチにし、まとめて回す。問題はバッチ内の全員が終わるまでスロットが解放されないことです。「はい」と3トークンで終わる人が、2000トークン書く人の隣に座ってしまうと、その席は2000ステップ空きません。空いた席で計算はされ続けますが、その計算はパディングを噛んでいるだけで、誰の役にも立っていない。これが Head-of-Line blocking(先頭がつかえると後続が全部待つ)です。

さらにメモリ側にも同じ構造の無駄があります。KV キャッシュを「このリクエストは最大 max_model_len トークンまで伸びるかもしれない」と見込んで連続した領域で事前確保すると、実際には数百トークンで終わったリクエストの予約分がまるごと遊びます。vLLM の論文が測ったところ、従来手法では確保した KV 領域の 60〜80% が実際には使われていなかった(内部断片化・外部断片化・過剰予約の合計)。

静的バッチング vs 継続的バッチング — スロット占有の可視化

生成中 席を占有したまま空転 未着席/終了
静的バッチ
完了ステップ数
継続的バッチ
完了ステップ数
静的:スロット
有効利用率
継続的:スロット
有効利用率
静的バッチング(波ごとに全員の完了を待つ)
継続的バッチング(席が空いた瞬間に次を入れる)
概念モデルです。実測ではなく、席の空き方だけを取り出した図。プリフィルのコスト、スケジューラのプリエンプション、トークン単位のバッチ上限(--max-num-batched-tokens)は省いています。ばらつきを 0 にすると両者の差が消える——継続的バッチングの利得は出力長の分散から来ていることが見えます。
よくある誤解 / 01
巨大なプロンプトを投げると OOM してプロセスが落ちる
max_model_len を超える入力は、そもそも推論に入る前に HTTP 400 で拒否されます(vLLM 0.x 系の OpenAI 互換サーバ)。これは事故ではなく設計どおりの動作。実際に OOM を起こす要因は別で、活性値のピーク(プリフィル時の中間テンソルは文脈長の2乗に比例する項を含む)、起動時プロファイリングの見積り誤差(vLLM は起動時にダミー実行で空きを測り KV ブロック数を決めるので、後から前提が変わると足りなくなる)、マルチモーダル前処理(画像・音声のデコードは KV の外側でメモリを食う)の3つが主です。
遅いのは GPU の演算能力が足りないから
デコード段はメモリ帯域律速です。自環境の GGUF 実測では、同一機で e2b=80.8 / e4b=44.9 / 27B=11.0 tok/s と、デコード速度がモデルサイズにほぼ反比例しました 実測。演算器が律速なら、こうはきれいに反比例しません。
02 / ページング

PagedAttention と継続的バッチングは、片方だけでは効かない

vLLM の中核は2つの機構で、しかも両者は独立した改善ではなく、互いの前提になっています。

PagedAttention は OS の仮想メモリからの借用です。KV キャッシュを連続領域で確保するのをやめ、固定サイズのブロック(vLLM のデフォルトは block_size=16 トークン)に切り、論理ブロック → 物理ブロックの対応表(ブロックテーブル)で間接参照する。連続性の要求が消えるので外部断片化がなくなり、無駄は「各シーケンスの最後のブロックの端数」だけになります。ここから、冒頭の 60〜80% が 4% 未満に落ちる。

この設計はもう一つ副産物を持ちます。ブロックテーブルの参照先を共有すれば、同じプレフィックスを持つ複数のリクエストが物理ブロックを共有できる(プレフィックスキャッシュ、並列サンプリング、ビームサーチ)。同じシステムプロンプトを全リクエストが持つ実運用では、これが地味に大きい。

継続的バッチング(イテレーションレベルスケジューリング)は、バッチの単位を「リクエストの束」から「1ステップ」に変えます。各デコードステップの後にスケジューラが走り、終わったリクエストを外し、待っているリクエストを入れる。§1 の図で見た空転が消えます。

そして、この2つは片方だけでは成立しません。 継続的バッチングが「席が空いた瞬間に次を入れる」ためには、退出したリクエストの KV 領域が即座に、断片を残さず再利用可能でなければならない。連続領域の事前確保のままでは、空いた穴の形が合わず次が入らない。逆に、ページングだけ入れてバッチを静的なままにすると、断片化は減っても席は空かない。スケジューリングの粒度とメモリ管理の粒度を同時に細かくしたことが、vLLM の設計上の要点です。

公称値をどう読むか

vLLM 公式(2023年、初出論文と発表時ブログ)が示した数字は、HuggingFace Transformers 比 14〜24倍、TGI(Text Generation Inference)比 2.2〜2.5倍です。この数字自体は出典があり、実在します。

ただし読み方に注意が要ります。①「24倍」の比較相手は素の HF Transformers(そもそも本番サービング用に書かれていない)であって、最適化された推論サーバ一般ではない。②2023年の測定で、以後 TGI も TensorRT-LLM も SGLang も継続的バッチングを実装しており、現在の相対差はこの数字とは別物。③スループット(全体で1秒あたり何トークン出るか)の比であって、1本あたりのレイテンシではない。継続的バッチングは同時本数を増やすことで全体スループットを上げる技術なので、同時1本しか流さないなら利得はほぼゼロです。

よくある誤解 / 02
PagedAttention によって KV キャッシュのメモリが減る
減るのは断片化と過剰予約による無駄であって、KV キャッシュそのものの量ではありません。1トークンあたりの KV サイズは 2 × 層数 × KVヘッド数 × ヘッド次元 × バイト数 で決まり、PagedAttention はこの式のどの項にも触れない。KV の量を減らすのは GQA/MQA(ヘッド数)、KV量子化(バイト数)、sliding window(範囲)です。
vLLM を入れれば 24 倍速くなる
24倍は2023年の、HF Transformers 比の、スループットの数字です。自環境で継続的バッチングの有無を比べた実測では、Ollama(当時のバージョンは連続バッチ非対応)で並列数を 1→8 に上げても集計スループットが 3.6→4.4 tok/s とほぼ動かなかったのに対し、vLLM 側は同時実行の余力が公称 12倍規模で確保されました 実測。効くのは「同時に人が使うとき」であって、倍率はワークロードで決まります。
P50 レイテンシ=平均レイテンシ
P50 は中央値です。推論のレイテンシ分布は長い尾を引くので、平均と中央値は一致しません。ベンチマークを取るときは P50 と P99 を両方見る。継続的バッチングは P50 を改善しつつ P99 を悪化させることがある(後から入った短いリクエストが先に出る一方、プリエンプトされたリクエストは待たされる)ので、片方だけ見ると判断を誤ります。
03 / 圧縮

量子化を三層に分けて数える

「量子化」という一語が指しているものは、少なくとも三つあります。減る場所が違い、効く条件が違い、必要なハードウェアが違う。

代表手法何が減るかbit 幅効き方必要なハード
① 重みのみ 4bit AWQ / GPTQ / NVFP4 等 モデル重み。KV も活性値も減らない 16 → 4(1/4) デコードの読み出し量が減る=メモリ律速の緩和。演算は多くの実装で高精度に戻して行う(dequantize して matmul) 特別な演算器は不要。カーネルさえあれば動く
② FP8 / INT8 FP8 (E4M3) / INT8 W8A8 重み+活性値。演算そのものも 8bit で行える 16 → 8(1/2) 読み出しが半分になるうえ、演算器のスループットが上がる。大バッチほど効く ネイティブ対応が必須。FP8 は Ada/Hopper 以降、NVFP4 は Blackwell 世代
③ KVキャッシュ量子化 --kv-cache-dtype fp8 KV キャッシュのみ。重みは無関係 16 → 8(1/2) スループットではなく同時本数と最大文脈長が伸びる方向に効く スケーリング方式により対応世代が異なる

① の中でも AWQ と GPTQ は原理が違います。 AWQ(Activation-aware Weight Quantization)は活性値の大きさを手がかりに重要なチャネルを見つけ、そこだけ精度を守る——「重要な部分を残す賢い圧縮」という説明が当てはまるのはこちらです。GPTQ は全チャネルを一律 4bit に落としたうえで、二次情報(Hessian の近似)を使って層ごとに誤差を補償する。どちらも良い手法ですが、「重要部分を残す」で両方を説明するのは GPTQ の説明として誤りです。この違いは、キャリブレーションデータの性質が結果に与える影響の大きさとして実務に出ます。

メモリ帳簿 — 「87.5%削減」がどこで 50〜60% になるのか

重みだけの削減率
(ベースライン比)
実効メモリ全体の
削減率
重み GiB
KVキャッシュ GiB
(同時本数ぶん)
重み KVキャッシュ その他・空き(活性値/断片/ワークスペース)
KV は 2 × 層数 × KVヘッド数 × ヘッド次元 × bytes × 文脈長 の公開構成ベース概算。活性値・ワークスペース・CUDA コンテキストは含まないため、実機の必要量はこれより増えますベースラインを FP32 に切り替えると削減率が跳ね上がるのを確かめてください——これが「87.5%削減」の作り方です。実際に配布されているのは BF16 なので、置き換えの効果は BF16 基準で数えるのが正しい。
「実効50〜60%」も定数ではありません——初期値(32B・文脈4,096・同時16本)でおよそ59%ですが、文脈長と同時本数を上げれば KV が支配的になって30%台まで落ちます。削減率は構成の関数であって、量子化手法の属性ではない。

品質の話。 AWQ/GPTQ の 4bit で、下流タスクの劣化は概ね 1〜3%。perplexity で見れば 1% 未満に収まるケースもあります。ただしこれは平均の話で、タスクによって偏る。INT4 はコード生成など、1トークンの誤りが全体を壊す種類のタスクで劣化が目立つ傾向が報告されています。知財実務でいえば、要約や分類は 4bit で足りても、クレーム文言を逐語で扱う工程は精度を落とす場所ではない、という判断になります。

ハードウェア世代が意味を変える例:Jetson Orin

Jetson Orin は Ampere 世代で、INT4 を演算レベルで持っていません。INT4 の重みを載せると INT8 にフォールバックして計算されます。結果として、メモリは減るが演算の加速は限定的という、①の層だけが効いて②の層が効かない状態になる。同じ「4bit 量子化しました」でも、H100 と Orin では起きていることが違います。

逆に自環境の GB10(Blackwell 世代)では NVFP4 がネイティブで、ここでは①と②が同時に効きます。実測では NVFP4 + vLLM の Gemma-4-26B-A4B が 27.2 tok/s(他コンテナ併走下)だったのに対し、同規模の GGUF 27B が 11.0 tok/s 実測。ただしこれは量子化方式だけでなく、MoE か dense か、vLLM か Ollama かも同時に変わっているので、この差を「NVFP4 の効果」と呼ぶことはできません。要因が3つ動いている比較は、比較ではなく観察です。

よくある誤解 / 03
量子化でメモリが 87.5% 削減される
FP32 → INT4 なら 87.5%。しかし実際の出発点は BF16/FP16 なので重みは 75% 減。さらに VRAM には KV キャッシュと活性値が残るため、実効では 50〜60% というのが現実的な数字です(構成による)。上の帳簿でベースラインを切り替えて確かめてください。
量子化すると性能が 10〜20% 落ちる
4bit の AWQ/GPTQ で概ね 1〜3%。10〜20% 落ちるとしたら、それは量子化の相場ではなくその実装か設定が壊れているサインです。
AWQ も GPTQ も「重要な部分を残す賢い圧縮」
AWQ のみ該当。GPTQ は一律 4bit+Hessian による誤差補償で、重要度で bit を変えているわけではありません。
AWQ / GPTQ / FP8 はどれも 16bit → 8bit で半分になる
4bit 系は 1/4、FP8 / INT8 が 1/2。三者は bit 幅も、演算器のネイティブ対応の要否も、活性値を量子化するかどうかも違います。ひとくくりにした時点で、どれを選ぶかの判断材料が失われます。
量子化のベースラインは 32bit
学習は FP32/TF32 混在でも、推論向けに配布される重みは BF16/FP16 が標準。圧縮率は実際の置き換え元で数える。
04 / 分散

分散推論と量子化は、同じ方向を向いていない

モデルが1枚に載らないとき、複数 GPU に割る。ここで量子化の利得が目減りする理由と、「そもそも分散すべきか」の判断。

テンソル並列(TP)は、各層の重み行列を GPU 間で列方向・行方向に割り、それぞれが部分積を計算して層ごとに all-reduce で足し合わせる方式です。1トークン生成するたびに、層の数だけ集団通信が走る。ノード内なら NVLink、ノード間なら InfiniBand/RoCE を使いますが、vLLM もこの通信を行います。vLLM は NCCL を呼んで all-reduce しており、通信をなくす仕組みは持っていません。

ここから、TP 度が上がるほど量子化の高速化効果が減衰するという現象が出てきます。量子化が縮めるのは「重みの読み出し量」で、これはメモリ帯域律速の部分にしか効かない。TP を増やすと1 GPU あたりの重みは小さくなり、律速がメモリ帯域から集団通信へ移る。報告されている目安では、32B クラスで TP=2 なら約 1.3倍、TP=4 では約 1.1倍まで縮む。分散すればするほど、量子化にお金を払う理由が減るということです。

自環境での判断:2ノードあるが、TP は張っていない

手元の構成は DGX Spark(GB10)2ノードで、ConnectX-7 の QSFP で直結できるハードウェアはあります。にもかかわらず、現在の本番構成はノードをまたぐテンソル並列を使っていません 実測。spark-01 が推論(vLLM, nvidia/Qwen3-32B-NVFP4, max_model_len 32768, --gpu-memory-utilization 0.6)、spark-02 がデータ側、という役割分担です。

理由は単純で、1ノードに載るモデルは1ノードで回すのが最も速く、最も壊れにくいから。ノードをまたいだ瞬間に、all-reduce のレイテンシ、NCCL の環境変数、片肺運転時の挙動、という3種類の新しい壊れ方が増えます。分散は性能向上の手段ではなく、載らないときの最後の手段です。「2ノードあるから分散推論」は、TP 度が上がるほど量子化が効かなくなる話と合わせて考えると、しばしば逆効果になります。

関連する実測として、72B と 32B(ともに NVFP4)を盲検リプレイで比較したところ、事前登録した「明確な勝ち」の条件を 72B が満たさず、実用速度も 4.8 vs 10.7 tok/s だったため 72B を退役させました 実測。大きいほうが良いとは限らない、というより、大きさは測るべき仮説であって前提ではないという話です。

統合メモリ環境では「GPUメモリ」の常識が通じない

GB10 は 128GB の LPDDR5X を CPU と GPU で共有する単一プールです。結果として、①OOM は「GPU メモリ不足」ではなくシステム全体のメモリ不足として起きる、②nvidia-smi のメモリ欄は [N/A] を返すので /proc/meminfo を見るしかない、③MIG が使えず、MPS や time-slicing のような共有機構はむしろ OOM を悪化させる。正解は一貫して直列化(admission control)でした 実測。「GPU メモリを見て空きを判断する」という一般的な手順が、そのままでは通用しない環境があります。

よくある誤解 / 04
vLLM が分散推論の通信ボトルネックを解消する
しません。vLLM も NCCL で all-reduce します。vLLM が解決したのはメモリ側(断片化とスケジューリング)であって、通信側ではない。通信を減らすのは別系統の工夫(通信と計算のオーバーラップ、量子化した all-reduce、そもそも TP を張らない)です。
Megatron-LM が大規模モデルの推論を可能にした
Megatron-LM は学習用フレームワークです。ただし無関係ではなく、Megatron が定式化したテンソル並列のシャーディング方式が推論側にも受け継がれています。「Megatron のアイデアが推論に流用されている」は正しく、「Megatron が推論を可能にした」は誤り。
量子化と分散を両方やれば効果は掛け算になる
むしろ打ち消し合う方向です。量子化はメモリ帯域律速に効き、TP はその律速を通信律速に置き換えるため。まず量子化して1ノードに載せきれないかを試し、それでも載らないときに TP を検討する、という順番になります。
05 / 露出

本番公開時のセキュリティ設計

ここは、この記事で唯一「今すぐ確認してほしい」章です。自分のサーバが該当するかどうかは、数分で確かめられます。

vLLM の OpenAI 互換サーバは、デフォルトで認証がありません--api-key を付ければ Bearer トークンを要求するようになりますが、その認証ミドルウェアの適用範囲が限定的であるという問題が 2026年前半時点で報告されています。

報告されている挙動

認証ミドルウェアが、①HTTP メソッドが OPTIONS のリクエストと、②パスが /v1 で始まらないリクエストをスキップする、という実装になっている。結果として、/invocations(SageMaker 互換の推論エンドポイント)、/pooling/score/rerank といった /v1 配下でないパスから、API キーを持たずに推論を実行できる。さらにビルドによっては /update_weights による重みの差し替えや、/pause によるサービス停止(DoS)も同じ経路で届きうる、とされています。

要点は「特定のパスが危ない」ことではなく、認証を「パスの接頭辞」で判定する設計そのものが脆いことです。エンドポイントは版が上がるたびに増えるので、判定を接頭辞に依存させると、新しいエンドポイントは自動的に穴になります。

そして、エンドポイント面はバージョンとモデル種別で変わります。 手元のサーバ(vLLM 0.21.0+2325b6f0.dev / NVIDIA コンテナ nvcr.io/nvidia/vllm:26.05.post1-py3)で /openapi.json を叩いた実測がこれです 実測

/v1 配下(認証が効く想定) /v1/chat/completions /v1/chat/completions/batch /v1/chat/completions/render /v1/completions /v1/completions/render /v1/models /v1/messages /v1/messages/count_tokens /v1/responses /v1/responses/{id} /v1/responses/{id}/cancel /v1 配下ではない(=接頭辞判定なら素通りする側) /invocations /inference/v1/generate /generative_scoring /tokenize /detokenize /load /scale_elastic_ep /is_scaling_elastic_ep /metrics /health /ping /version

注目してほしいのは、報告に出てくる /pooling/score がこの環境には存在しない一方で、/invocations/inference/v1/generate は存在することです。前者は埋め込み・スコアリング系モデルを載せたときに生えるもので、生成モデル単体では出ない。つまり記事に書かれたエンドポイント一覧を自分の環境の一覧だと思ってはいけない/openapi.json は誰でも1コマンドで引けるので、自分で列挙するのが唯一正しい確認方法です。

設計としての結論

--api-key を唯一の防壁にしない。 認証はネットワーク層で二重に掛ける。具体的には 0.0.0.0 にバインドせず 127.0.0.1 に閉じ、外から使うときは SSH トンネルか WireGuard/Tailscale のようなオーバーレイ網、あるいは認証付きリバースプロキシを前段に置く。自環境も 127.0.0.1 バインド+SSH トンネルで運用しています 実測。この構成なら、ミドルウェアがどのパスをスキップしようと到達経路がありません。

② 開発用エンドポイントを本番で開けない。 重み更新やスケーリング系は VLLM_SERVER_DEV_MODE のような開発フラグ配下にあることが多い。本番の起動コマンドにそれが混ざっていないか確認する。

③ 露出の判定はコードではなく実測で行う。 「認証を付けたから安全」は仮説です。/openapi.json でパスを列挙し、キーなしで各パスを叩いて 401 が返るかを実際に確かめる。§6 のハンズオン3がこれです。

よくある誤解 / 05
LangGraph のチェックポイントを入れておけば、推論サーバの障害にも対処できる
層が違います。LangGraph のチェックポイントが保護するのはエージェントのグラフ状態——どのノードまで進んだか、途中の変数は何か——であって、インフラの可用性ではない。vLLM が落ちればそのノードの実行は失敗し、チェックポイントは「失敗した地点から再開できる」だけで、失敗そのものは防ぎません。可用性の話(多重化・ヘルスチェック・フェイルオーバー)と、安全性の話(状態の再開性・冪等性)を混ぜない。これは §0 の「型3 / レイヤーの混同」の典型例です。
RAG を入れると推論負荷が減ってコストが下がる
RAG は検索結果をプロンプトに詰めるので、プリフィルのトークン数を増やします。推論負荷は減るどころか、1リクエストあたりでは確実に増える。RAG でコストが下がるとすれば、その実体は「小さいモデルで足りるようになった」ことと「ファインチューニングを回避できた」ことであって、推論そのものが軽くなったわけではありません。節約の因果を正しい場所に置かないと、プリフィル爆発(長い検索結果を毎回詰めて TTFT が伸びる)を予測できなくなります。
06 / もくもく会

手を動かすネタ 3本

いずれもローカルに vLLM が1つあれば完結します。所要 30〜60分。数字は自分の環境で出したものだけが信用できる、というのがこの記事の趣旨なので、ここが本体です。

LAB 01 / 難易度 ★★☆

継続的バッチングの飽和点を実測する

同時本数を上げていくと、スループットはどこかで頭打ちになり、そこから先はレイテンシだけが伸びます。その折れ点が自分の環境の同時接続上限です。カタログ値ではなく実測で持っておくと、運用の判断が全部軽くなる。

# 同時本数を振ってスループットとレイテンシを同時に取る
for C in 1 2 4 8 16 32; do
  vllm bench serve \
    --base-url http://127.0.0.1:8000 \
    --model $MODEL \
    --dataset-name random --random-input-len 1024 --random-output-len 256 \
    --max-concurrency $C --num-prompts $((C*10))
done

# サーバ側のパラメータも振る(要再起動)
#   --max-num-seqs             : 同時に走らせるシーケンス数の上限
#   --max-num-batched-tokens   : 1ステップで処理するトークン数の上限
#   --block-size               : KVブロック粒度(既定16)

見るべきもの:出力スループット(tok/s)、TTFT、そして P50 と P99 を両方。P50 が横ばいなのに P99 だけ跳ねる点があれば、そこがプリエンプションの発生点です。予想を先に書いてから測るのがおすすめ——「128 まで伸びるはず」と書いてから 16 で折れるのを見ると、体で覚えます。

ひねり:--random-output-len を固定値ではなくばらつかせると、§1 の図が示したとおり継続的バッチングの利得が変わるはずです。出力長の分散を 0 に近づけたとき、静的バッチとの差がどれだけ縮むかを測ると、この技術が何を解いているのかが数字で出ます。

LAB 02 / 難易度 ★☆☆

KVキャッシュの帳簿を自分で合わせる

vLLM は起動時に「GPU KV cache size: N tokens」「# GPU blocks: M」といったログを出します。この2つの数と、モデルの config.json から手計算した値が合うかを確かめる作業です。合わないときは、たいてい自分の理解のほうが間違っている。

# 1) 起動ログからブロック数とKVトークン数を拾う
docker logs vllm-32b 2>&1 | grep -iE "kv cache|gpu blocks|block_size"

# 2) config.json から手計算
#    KV bytes/token = 2 × num_hidden_layers × num_key_value_heads
#                       × (hidden_size / num_attention_heads) × dtype_bytes
#    総KVトークン数 = # GPU blocks × block_size

# 3) KVをfp8にして、ブロック数が何倍になるか
vllm serve $MODEL --kv-cache-dtype fp8 --gpu-memory-utilization 0.6

確かめたいこと:①GQA のモデルで num_key_value_headsnum_attention_heads と取り違えると、答えが 4〜8倍ずれる。②--kv-cache-dtype fp8 でブロック数がちょうど2倍にはならないはず(重みとワークスペースの分は変わらないので、増えるのは KV に割り当てられた領域の中だけ)。この「ちょうど2倍にならない」が、§3 の「重みは75%減でも全体は50〜60%」と同じ構造です。③--block-size を 8 や 32 に変えて、端数の無駄と管理オーバーヘッドがどちらに振れるかを見る。

LAB 03 / 難易度 ★★☆ / 自分の管理下のサーバでのみ実施すること

自分のサーバのエンドポイント面を列挙し、認証を試す

§5 の話が自分の環境に当てはまるかを確かめます。他人のサーバに対して行えば不正アクセスです。 対象は自分がオーナーであるローカルのインスタンスに限ってください。

# 0) 検証用に、認証つきで起動する
vllm serve $MODEL --host 127.0.0.1 --api-key test-key-123

# 1) まず自分の環境のエンドポイントを全部出す(記事の一覧を信じない)
curl -s http://127.0.0.1:8000/openapi.json \
  | python3 -c "import sys,json;print(*sorted(json.load(sys.stdin)['paths']),sep='\n')"

# 2) /v1 配下:キー無しなら 401 が返るはず
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/v1/models

# 3) /v1 配下でないパス:同じくキー無しで叩く
curl -s -o /dev/null -w "%{http_code}\n" -X POST http://127.0.0.1:8000/invocations \
  -H "Content-Type: application/json" \
  -d '{"model":"'"$MODEL"'","prompt":"hello","max_tokens":8}'

# 4) OPTIONS も試す
curl -s -o /dev/null -w "%{http_code}\n" -X OPTIONS http://127.0.0.1:8000/v1/models

見るべきもの:2 が 401 を返すのに 3 が 200 を返したら、§5 の挙動が自分の版で再現しています。返らなかった場合も収穫です——使っている版で修正済みか、そのエンドポイント自体が生えていないか、のどちらか。どちらなのかは 1 の一覧で分かります。「自分の環境では再現しなかった」を確認するのも検証であって、確認せずに安全だと思っているのとは別物です。

そのあとやること:結果に関わらず、本番の起動コマンドが 0.0.0.0 にバインドしていないかを確認する。ss -tlnp | grep 8000 で、127.0.0.1:8000 なのか 0.0.0.0:8000 なのかは一目で分かります。後者だったら、それが今日の一番の収穫です。

07 / 総覧

訂正した主張の一覧

検証対象の3文書に実際に書かれていて、一次資料と合わなかったもの。持ち帰り用。

書かれていたこと正しい理解
量子化でメモリ 87.5% 削減FP16→INT4 は 75%。VRAM 全体では 50〜60%型2
量子化の性能低下は 10〜20%1〜3%。それ以上落ちるなら設定が壊れている型1
AWQ も GPTQ も重要部分を残す賢い圧縮AWQ のみ。GPTQ は一律4bit+Hessian 誤差補償型4
AWQ / GPTQ / FP8 は 16→8bit で半分4bit系は 1/4、FP8・INT8 が 1/2。一括りにできない型4
量子化のベースラインは 32bit配布されている重みは BF16/FP16 が基準型2
P50 = 平均レイテンシP50 は中央値。P99 と併せて見る型1
巨大プロンプトで OOM しプロセスがダウンmax_model_len 超過は 400 で拒否。実 OOM 要因は活性値ピーク・起動時プロファイリング誤差・マルチモーダル前処理型3
Megatron-LM が大規模推論を可能にしたMegatron-LM は学習用フレームワーク(TPの定式化は推論に継承)型3
vLLM が通信ボトルネックを解消vLLM も NCCL で all-reduce する。解決したのはメモリ側型3
LangGraph のチェックポイントで同時障害に対処保護対象はエージェントのグラフ状態。可用性と安全性の混同型3
RAG で推論負荷が減りコスト削減RAG はプリフィルを増やす。節約の実体はモデル小型化とFT回避型3
通信会社 320万ドル削減/欠陥検出 99.9% 等の事例出典を確認できず。本記事では使用しない型1
08 / Q&A

読んだあとに出る問い

結局、vLLM を入れると何倍速くなるんですか
この問いには答えがありません、というのが正直な答えです。同時1本しか流さないなら、ほぼ変わりません(継続的バッチングは同時本数から利得を取る技術なので)。同時に多数を捌くなら、比較相手が素の HF Transformers なら二桁倍、すでに継続的バッチングを持つ推論サーバなら数割から数倍。「何倍か」はワークロードの側にある変数であって、ソフトウェアの属性ではありません。LAB 01 を回して自分の折れ点を持つのが、この問いへの唯一の答え方です。
量子化はどこまで攻めていいですか
工程で分けるのが実務的です。要約・分類・ルーティング・初期スクリーニングは 4bit で十分なことが多い(劣化 1〜3%)。一方で逐語の正確さが結果を決める工程——クレーム文言の対比、条文の引用、コード生成——は、平均劣化が小さくても失敗の形が致命的なので落とす場所ではありません。「モデル全体で何bitにするか」ではなく「どの工程にどのモデルを当てるか」の問題に組み替えると、判断が楽になります。
FP8 が使えるハードがないのですが、4bit だけでも意味ありますか
あります。①の層(重みのみ4bit)はネイティブ演算器を要求しないので、dequantize して高精度で計算する実装でも、読み出し量が減るぶんデコードは速くなります。デコードがメモリ律速である以上、読み出しを減らすのは常に効く。ただし大バッチでの演算スループット向上(②の層の利得)は得られません。Jetson Orin の例(INT4 → INT8 フォールバック)が、この「メモリは減るが演算は加速しない」状態の典型です。
KV キャッシュ量子化は品質にどう響きますか
重み量子化とは影響の出方が違います。重みは一度落とせば全リクエストに同じ影響が出ますが、KV の量子化誤差は文脈が長くなるほど蓄積する方向に働きます。短い対話では気づかず、長文の後半で効いてくる、という出方をしうる。評価するなら短文ベンチではなく長文タスクで。効果の方向も違って、スループットではなく同時本数と最大文脈長が伸びます。
この記事自体は検証されているんですか
部分的に、です。実測 が付いた数字は自環境で実行して得たもの、公称 は一次資料に当たれたもの、それ以外は一般に報告されている目安として書いています。TP 度による量子化利得の減衰(TP=2 で 1.3倍、TP=4 で 1.1倍)は自環境で再現していません——手元は TP を張っていないので測れない。再現していないものを「実測」と書かないことが、この記事が §0 で立てた基準を自分に適用する唯一のやり方です。誤りを見つけたら教えてください。