本題に入る前に、検証で見つかった誤りの分類を置きます。これは vLLM に固有ではなく、あらゆる技術記事に効く読み方のチェックリストになります。
24倍・87.5%・320万ドル・欠陥検出99.9%。目を引く数字ほど、根拠に当たると消える。数字が出てきたら「誰が・いつ・何と比べて・どのワークロードで」の4点が書いてあるかを見る。4点そろわない数字は、記事の主張ではなく装飾です。
「量子化で87.5%削減」は FP32 を基準にすれば正しい。しかし現代の LLM は BF16/FP16 で配布されている。実際に置き換わるのは BF16 → INT4 で、重みは 75%。しかも VRAM 全体では KV キャッシュと活性値が残るので 50〜60%。基準をずらすだけで、効果は 2 倍良く見えます。
通信・メモリ・アプリ状態・安全性は別の層にある。「vLLM が通信ボトルネックを解消し、LangGraph のチェックポイントが同時障害に備える」という文は、4つの層を1本の因果連鎖に押し込んでいる。層をまたぐ因果は、たいてい書き手が層を区別していないだけです。
AWQ・GPTQ・FP8 を「量子化」でひとくくりにすると、bit 幅も原理も適用条件も違う三者が同じ箱に入る。「同じカテゴリ名で呼べる」ことは「同じ性質を持つ」ことを意味しない。§3 はこの解体に一章を使います。
以降、各章の末尾に「よくある誤解」ボックスを置きます。そこに並ぶのは、実際に検証対象の文書に書かれていた誤りです。
手元で1本だけ流すと十分速いモデルが、同時に人が使い始めた途端に遅くなる。原因は演算能力ではなく、スケジューリングとメモリの持ち方にあります。
推論には性格の違う2つの相があります。プリフィル(入力プロンプト全体を一度に通し、KV キャッシュを作る)は行列積が大きく演算律速。デコード(1トークンずつ生成する)は1ステップあたりの計算が小さいのに、モデル重みと KV キャッシュを全部読み直すためメモリ律速。生成が遅いと感じるのは後者です。
ここで素朴な実装、静的バッチングを考えます。到着したリクエストを B 本まとめて1バッチにし、まとめて回す。問題はバッチ内の全員が終わるまでスロットが解放されないことです。「はい」と3トークンで終わる人が、2000トークン書く人の隣に座ってしまうと、その席は2000ステップ空きません。空いた席で計算はされ続けますが、その計算はパディングを噛んでいるだけで、誰の役にも立っていない。これが Head-of-Line blocking(先頭がつかえると後続が全部待つ)です。
さらにメモリ側にも同じ構造の無駄があります。KV キャッシュを「このリクエストは最大 max_model_len トークンまで伸びるかもしれない」と見込んで連続した領域で事前確保すると、実際には数百トークンで終わったリクエストの予約分がまるごと遊びます。vLLM の論文が測ったところ、従来手法では確保した KV 領域の 60〜80% が実際には使われていなかった(内部断片化・外部断片化・過剰予約の合計)。
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本しか流さないなら利得はほぼゼロです。
「量子化」という一語が指しているものは、少なくとも三つあります。減る場所が違い、効く条件が違い、必要なハードウェアが違う。
| 層 | 代表手法 | 何が減るか | 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 の説明として誤りです。この違いは、キャリブレーションデータの性質が結果に与える影響の大きさとして実務に出ます。
品質の話。 AWQ/GPTQ の 4bit で、下流タスクの劣化は概ね 1〜3%。perplexity で見れば 1% 未満に収まるケースもあります。ただしこれは平均の話で、タスクによって偏る。INT4 はコード生成など、1トークンの誤りが全体を壊す種類のタスクで劣化が目立つ傾向が報告されています。知財実務でいえば、要約や分類は 4bit で足りても、クレーム文言を逐語で扱う工程は精度を落とす場所ではない、という判断になります。
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つ動いている比較は、比較ではなく観察です。
モデルが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倍まで縮む。分散すればするほど、量子化にお金を払う理由が減るということです。
手元の構成は 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 を退役させました 実測。大きいほうが良いとは限らない、というより、大きさは測るべき仮説であって前提ではないという話です。
GB10 は 128GB の LPDDR5X を CPU と GPU で共有する単一プールです。結果として、①OOM は「GPU メモリ不足」ではなくシステム全体のメモリ不足として起きる、②nvidia-smi のメモリ欄は [N/A] を返すので /proc/meminfo を見るしかない、③MIG が使えず、MPS や time-slicing のような共有機構はむしろ OOM を悪化させる。正解は一貫して直列化(admission control)でした 実測。「GPU メモリを見て空きを判断する」という一般的な手順が、そのままでは通用しない環境があります。
ここは、この記事で唯一「今すぐ確認してほしい」章です。自分のサーバが該当するかどうかは、数分で確かめられます。
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 を叩いた実測がこれです 実測:
注目してほしいのは、報告に出てくる /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がこれです。
いずれもローカルに vLLM が1つあれば完結します。所要 30〜60分。数字は自分の環境で出したものだけが信用できる、というのがこの記事の趣旨なので、ここが本体です。
同時本数を上げていくと、スループットはどこかで頭打ちになり、そこから先はレイテンシだけが伸びます。その折れ点が自分の環境の同時接続上限です。カタログ値ではなく実測で持っておくと、運用の判断が全部軽くなる。
# 同時本数を振ってスループットとレイテンシを同時に取る 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 に近づけたとき、静的バッチとの差がどれだけ縮むかを測ると、この技術が何を解いているのかが数字で出ます。
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_heads を num_attention_heads と取り違えると、答えが 4〜8倍ずれる。②--kv-cache-dtype fp8 でブロック数がちょうど2倍にはならないはず(重みとワークスペースの分は変わらないので、増えるのは KV に割り当てられた領域の中だけ)。この「ちょうど2倍にならない」が、§3 の「重みは75%減でも全体は50〜60%」と同じ構造です。③--block-size を 8 や 32 に変えて、端数の無駄と管理オーバーヘッドがどちらに振れるかを見る。
§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 なのかは一目で分かります。後者だったら、それが今日の一番の収穫です。
検証対象の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 |