状態:ドラフト(2026-10-27)、実測値。 8GB級ノートPC用GPU(Ada世代)1枚、vLLM 0.31.0、Qwen3-Reranker-0.6B を使用して2026-10-27に測定しました。スリープモードのテスト中、コンテナの1つがメモリ制限により強制終了し、その経緯も含めて結果に記載しています。指標名がバージョンごとに変更された経緯については「オープンソースコントリビューション記録」の第3回、OpenTelemetryのトレーシングについては「トス ML/AI Infra 準備」の第25回にあります。 2026-10-27 追加。 同日、vLLM 0.32.0にて認証パス、起動時間、スリープモードの再測定結果を各セクションの「0.32.0 再測定」に追加しました。0.31.0の数値はそのまま残しています。 執筆ルール:実測データを基本とし、測定できなかったものは「未測定」、確認できなかったものは「未確認」と表示します。
目次
- 1. スリープモード:サーバーを落とさずにGPUを解放する
- 2. 起動時間:コンパイルキャッシュで半分に短縮
- 3. /metrics:何が出力されるか
- 4. --api-key は /v1 のみを保護する
- 5. 運用への示唆
- 6. 確認できなかったこと
- 参考
1. スリープモード:サーバーを落とさずにGPUを解放する
1枚のGPUを複数のモデルやトレーニングと共用する場合、使用していない間はGPUメモリを解放する必要があります。サーバーを終了させると、再起動に数十秒かかります(第2節)。スリープモードでは、プロセスやCUDAコンテキスト、コンパイル済みグラフを維持したまま、大容量のメモリのみを解放します。
| 段階 | 重み | KVキャッシュ | 復帰時 | 利用シーン |
|---|---|---|---|---|
| 1段階目 | CPUメモリに退避 | 破棄 | CPUから復元 | 同じモデルに再度切り替えるとき |
| 2段階目 | 破棄 | 破棄 | 重みを再ロードする必要がある(reload_weights) |
別の重みに切り替えるとき(RLHFの重み更新)、CPUメモリが不足しているとき |
有効化にはサーバーオプションと開発モード用環境変数の両方を使用します。スリープ状態への移行と復帰はHTTPパス経由で開放されるため、開発モードでのみ使用可能です。
VLLM_SERVER_DEV_MODE=1 vllm serve <모델> --enable-sleep-mode
curl -X POST 'localhost:8000/sleep?level=1'
curl -X POST 'localhost:8000/wake_up'
# 2단계: 가중치 자리만 깨우고, 가중치를 다시 읽고, KV 를 깨운다
curl -X POST 'localhost:8000/sleep?level=2'
curl -X POST 'localhost:8000/wake_up?tags=weights'
curl -X POST 'localhost:8000/collective_rpc' -H 'Content-Type: application/json' -d '{"method":"reload_weights"}'
curl -X POST 'localhost:8000/wake_up?tags=kv_cache'
--enable-sleep-mode はvLLMのCuMemアロケータを有効にし、重みとKVをタグ付き仮想メモリとして確保した後、実メモリのみを解放および再アタッチします。vLLMの公式ブログ(2025-10-26、vLLM 0.11.0)によると、A100環境でQwen3-0.6Bを1段階目で復帰させるのに0.26秒、2段階目で0.85秒かかり、新規起動と比較して18〜20倍高速であると報告されています。同記事では、A4000での復帰に0.1秒から0.8秒かかるとされています(ベンダー発表)。
実際に測定してみる
- 環境:8GB級ノートPC用GPU(Ada世代)1枚、vLLM 0.31.0、Qwen3-Reranker-0.6B(重み 1.12GiB)、
VLLM_SERVER_DEV_MODE=1 --enable-sleep-mode. - 方法:貪欲デコーディングで100トークンを生成するリクエストを最初に1回送信して出力ハッシュを記録し、スリープ → 復帰 → 同様のリクエストを4回繰り返しました。各段階のHTTP呼び出し時間、
nvidia-smiのGPUメモリ、コンテナcgroupの匿名メモリを記録しました。2段階目を先に、1段階目を後に行いました。
| 項目 | 2段階目(4回) |
|---|---|
| スリープ呼び出し | 42ms(42、42、42、50) |
| スリープ中のGPUメモリ | 6,168MiB → 606MiB |
復帰(重みの位置 → reload_weights → KVの位置) |
中央値 0.39秒(初回 0.85秒、以降 0.40・0.38・0.38秒) |
| 復帰後の初リクエスト(100トークン) | 569ms(スリープ前 579ms) |
| 出力はスリープ前と同じか | 4回すべて同じ |
| コンテナ匿名メモリ | 2,750MiBのまま変化なし |
| 項目 | 1段階目 |
|---|---|
| 1回目の試行(コンテナ制限 4GiB、スリープ前匿名メモリ 2,704MiB) | スリープ呼び出し中に コンテナが OOMKilled |
| 再試行(新規コンテナ、制限 4.5GiB、匿名メモリ 2,750MiB) | 再度 OOMKilled |
結果の読み方は以下の通りです。
- 2段階目はこのモデルにおいてほぼコストゼロでした。 重み1.12GiBをディスク(ページキャッシュ)から再読み込みしても0.4秒以内に復帰し、復帰後の初リクエストはスリープ前と同じ時間で同じ出力を生成しました。同設定での再起動(第2節、32秒)と比較して約80倍高速です。CUDAコンテキストとコンパイル済みグラフが維持されているためです。
- スリープ中も606MiBは残りました。 CUDAコンテキスト、CUDAグラフ、スリープアロケータが管理していないバッファです。他の用途に割り当て可能なGPUメモリは、全体からこの値を引いたものになります。
- 1段階目はコンテナのメモリ制限を超過しました。 1段階目は重みをCPU側の固定メモリに退避させるため、その分コンテナのメモリ消費が増加します。計算上は 2.75GiB + 1.12GiB で4.5GiBの範囲内に収まるはずですが、どちらの試行でも上限を超過しました。重み以外にも退避されるデータが存在するか、コピー中に一時的に2重の領域が生成されるものと推測され、正確な容量は未確認です。このOOMは単一のPod内で完結しノードへの影響はありませんでしたが、本番環境で同様の事象が発生した場合、サービス中のエンジン全体がクラッシュします。 1段階目を利用する場合は、コンテナの制限値に対して重みサイズの2倍以上の余裕を持たせてから事前にテストする必要があります。 0.32.0 再測定(2026-10-27 0.31.0での測定 → 0.32.0で再測定)。 0.32.0のリリースノートには、スリープ機能がワークスペースとKVの初期化状態までも解放するように修正されたと記載されています。同じモデル、同じ4.5GiBの制限で再測定しました。
| 項目 | 0.31.0 | 0.32.0 |
|---|---|---|
| 2段階目スリープ呼び出し(4回の中央値) | 42ms | 43ms |
| スリープ中のGPUメモリ | 6,168 → 606MiB | 6,170 → 606MiB |
| 2段階目復帰(4回の中央値) | 0.39秒 | 0.41秒 |
| 復帰後の初リクエスト、出力一致 | 569ms、4/4 | 569ms、4/4 |
| 1段階目 | コンテナ OOMKilled(2回とも) | 再度 OOMKilled(スリープ前匿名メモリ 2,693MiB) |
2段階目はバージョンが変わってもほぼ同等であり、スリープ中に残存する606MiBも減少しませんでした。1段階目のメモリ問題もそのまま残っていました。発生した3回のOOMはすべて単一のPod内で収まり、ノードへの影響はありませんでした。
- ドキュメントでも同様の注意喚起がなされています。1段階目は重みをCPUにバックアップするため十分なCPUメモリが必要であり、CPUメモリが不足している場合は2段階目を使用し、
reload_weightsで再度読み込むよう記載されています。今回の実測により、その「十分な容量」が重みサイズよりも大きいことが示されました。
2. 起動時間:コンパイルキャッシュで半分に短縮
同一Pod内でサーバーを複数回起動し、 /health が200を返すまでの時間を記録しました。
| ケース | 準備完了まで | うち torch.compile の時間 |
|---|---|---|
| 新規Podでの初回起動(デフォルト設定) | 65秒、72秒(2つのPod) | 19.6秒 |
| 同設定での再起動 | 30〜34秒 | 0.15秒(ディスク上の結果を読み込み) |
| 初めて使用する設定(オプション変更時) | 46〜62秒 | 約19秒 |
| 推論先読みを有効化 | 78秒、96秒 | 19〜36秒 |
vLLMは torch.compile の結果を ~/.cache/vllm 配下に保存し、モデル、設定、関連環境変数、torchのバージョン、GPUが一致している場合に再利用します。ログには Directly load AOT compilation from path … が出力されます。コンテナが新規作成されるとこのキャッシュが失われるため、頻繁に再起動が発生するサーバーの場合、キャッシュフォルダをボリュームにマウントするかイメージに組み込んでおくことがドキュメントで推奨されています。0.31.0のドキュメントでは、このほかにメモリ測定をスキップする --kv-cache-memory、コンパイルを一切行わない --enforce-eager(代わりにデコードが低速化)、最適化段階の -O0 から -O3 まで(デフォルトは -O2)を起動時間を調整するパラメータとして挙げています。
起動時間とは別に、サーバーが「準備完了」を出力した後であっても、初見の入力パターンに対してはコンパイルが追加で発生します。第4回で確認した通り、最初のプロンプトサンプリングのバッチ処理でTritonカーネルをコンパイルするために数秒かかり、ログに jit_monitor の警告が残りました。準備完了の確認は /health ではなく、実際のクエリパターンで一度ウォームアップを行ってから判断する方が安全です。
0.32.0 再測定。 新規コンテナでの初回家起動時は60秒、同設定での再起動時は30秒となり、0.31.0と同様の傾向を示しました。0.32.0のログには「JIT kernel warmup」というステージが新たに見られたものの、最初のサンプリングバッチにおけるカーネルコンパイルの警告および遅延(チャットモデルで6.2秒、リランクモデルで同時実行数16の場合に5.3秒)はそのまま残っていました。
3. /metrics:何が出力されるか
/metrics はPrometheus形式です。デフォルトのサーバーでは vllm: で始まる指標が38種類あり、推論先読みサーバーでは41種類、KVオフロードサーバーでは53種類でした。本番ダッシュボードに優先的に配置すべきものを、実測サーバーの実値とともにまとめました。
| 確認したい項目 | 指標 | 今回の実測値 |
|---|---|---|
| 現在いくつ実行中でいくつ待機しているか | vllm:num_requests_running, vllm:num_requests_waiting, vllm:num_requests_waiting_by_reason |
待機理由を capacity(空きスロットなし)と deferred(一時的制約)に分類 |
| KVキャッシュの使用率 | vllm:kv_cache_usage_perc |
0〜1の割合 |
| プレフィックスキャッシュが機能しているか | vllm:prefix_cache_hits_total / vllm:prefix_cache_queries_total |
ベースサーバーのテスト全体で 94,592 / 330,084 トークン |
| プロンプトトークンはどこから来たか | vllm:prompt_tokens_by_source_total{source=...} |
local_compute, local_cache_hit, external_kv_transfer(第7回のCPUオフロード) |
| レイテンシ | vllm:time_to_first_token_seconds, vllm:inter_token_latency_seconds, vllm:e2e_request_latency_seconds, vllm:request_queue_time_seconds |
ヒストグラム。0.32.0以降は --custom-histogram-buckets でバケットを変更可能 |
| プリエンプション(割り込み)が発生しているか | vllm:num_preemptions_total |
今回のテストでは 0 |
| 終了理由 | vllm:request_success_total{finished_reason=...} |
stop 372, length 807 |
| 推論先読みが的中しているか | vllm:spec_decode_num_accepted_tokens_total / ..._draft_tokens_total、位置ごとの ..._per_pos_total |
第3回 |
| ツール呼び出しパーサーの出力結果 | vllm:tool_call_parser_invocations_total{outcome=...} |
第5回 |
| スリープ状態か | vllm:engine_sleep_state{sleep_state=...} |
awake, weights_offloaded(1段階目)、 discard_all(2段階目) |
| モデルFLOPs活用率 | vllm:estimated_flops_per_gpu_total など |
デフォルトは0、 --enable-mfu-metrics で有効化 |
指標名はバージョンごとに変更されてきました。例えばV0エンジンの vllm:gpu_prefix_cache_hit_rate は0.10で削除され、その代わりに上記のhits・queriesの2つのカウンターが使われます。ダッシュボードやアラート規則は、バージョンを上げるたびに現在のサーバーが出力する名前と突き合わせる必要があります。今回使用した方法はシンプルで、 curl -s localhost:8000/metrics | grep '^# HELP vllm:' で名前の一覧を取得し、ルールファイルと比較しました。
4. --api-key は /v1 のみを保護する
vLLMのセキュリティドキュメントには、「--api-key は /v1, /v2, /inference, /cohere で始まるパスのみを保護する」と明記されており、認証なしでオープンになるパスが列挙されています。実際にその通りか確認しました。サーバーを VLLM_API_KEY(このテストでのみ使用して破棄したランダムな値)で起動し、キーなしで各パスにリクエストを送信しました。
| パス | キーなしの場合 | 意味 |
|---|---|---|
GET /v1/models |
401 | 保護されている(キーの指定で200を返す) |
POST /v1/completions |
401 | 保護されている(キーの指定で200を返す) |
POST /invocations |
200 | SageMaker互換パス。 /v1/completions と同様の生成機能が キーなしで実行できた |
POST /tokenize |
200 | トークナイゼーション。プロンプト処理コストをキーなしで消費可能 |
GET /metrics |
200 | モデル名、スループット、キュー、キャッシュ状態 |
GET /version, GET /load |
200 | バージョン情報、負荷 |
POST /pause, POST /sleep |
404 | このバージョン、この設定ではパスが存在しない(スリープは開発モードのみ) |
0.32.0再測定(2026-10-27 0.31.0測定 → 0.32.0再測定)。 同じテストを0.32.0の公式コンテナで再度実行したところ、14個のパスのステータスコードが1つも変わりませんでした。 /invocations は0.32.0でもキーなしで200を返しました。
/invocations 1つの結論に至ります。 このサーバーをインターネットや他のチームのネットワークにそのまま公開すると、キーを渡していても誰でもモデルを実行できます。 ドキュメントも --api-key だけに頼らず、許可されたパスのみを通過させるリバースプロキシやゲートウェイを手前に置くよう推奨しています。同じドキュメントによると、プーリングモデルの /score, /rerank, /pooling, /classify も /v1 がないバージョンは認証を受けません。
開発モード(VLLM_SERVER_DEV_MODE=1)はさらに危険です。スリープ(サービス拒否)、キャッシュのクリア、そしてエンジン内部の任意のメソッドを呼び出す /collective_rpc が有効になります。ドキュメントには本番環境では絶対に有効化しないよう記載されています。第1節のスリープモードを本番環境で使用するには、そのパスが外部からアクセスできないようにネットワークポリシーでブロックし、オーケストレータのみが呼び出せるようにする必要があります。
0.32.0ではデフォルトのCORSが「すべてのオリジンを許可」から「許可なし」に変更されました。ブラウザから直接呼び出していたサービスであれば、バージョンを上げる際に --allowed-origins を明示する必要があります(リリースノート基準、未実測)。
5. 本番環境での運用事項
- スリープはステップ2からテストします。ステップ1を使用する場合は、コンテナのメモリ制限に重みサイズの2倍以上を加えてからテストします。今回の実測でその余裕がなかったコンテナは、両方のケースで丸ごとOOMにより強制終了しました。
- コンパイルキャッシュをボリュームに配置すると、同じ設定での再起動時間が半分になります。バージョンを上げたりオプションを変更すると、キャッシュが一致しないため再コンパイルされます。
- 準備確認は、実際のトラフィックの形でウォームアップした後に送信します。
/metrics名前のバージョンごとに照合します。- vLLMの前段には必ずルーティングを行うゲートウェイを配置します。
--api-keyは/v1のみを遵守します。
6. 未確認事項
- スリープモードにおけるテンソル平行(複数GPU)と大規模モデルの復帰時間は未実測です。
- 0.32.0のスリープ改善(ワークスペース・KV初期化状態の解除、CUDAグラフのオフロード)およびCORSデフォルト値の変更は未実測です。
/pause·/resumeがこのバージョンで404になった条件(必要なオプションが他にあるか)は未確認です。
参考
- vLLM Documentation, Sleep Mode, Optimization and Tuning (Faster Startup), Production Metrics, Security (0.32.0): https://docs.vllm.ai/en/latest/
- vLLMブログ, 「Zero-Reload Model Switching with vLLM Sleep Mode」, 2025-10-26: https://vllm.ai/blog/2025-10-26-sleep-mode
- vLLM 0.32.0リリースノート (CORS, スリープ, ヒストグラムバケット): https://github.com/vllm-project/vllm/releases/tag/v0.32.0
- 関連記事: 「オープンソースコントリビューション記録」第3回 vLLMが削除したメトリクス名、「Toss ML/AI Infraの準備」第25回 OpenTelemetryによるLLMサービングの観測