状態:ドラフト(2026-10-27)、オフロード実測、分離サービング未測定。 8GB 級ノート PC 用 GPU(Ada 世代)1 基、vLLM 0.31.0、Qwen3-Reranker-0.6B を使用して 2026-10-27 に計測しました。LMCache と NVIDIA Dynamo を利用したオフロード・分離サービングの原理と実測は「LLM KV キャッシュ解剖」の第 3・4 篇にあるため、この記事では vLLM に標準搭載されている CPU オフロードに焦点を当てます。 作成ルール:実測データを基本とし、計測できなかったものは「未測定」、確認できなかったものは「未確認」と表示します。
目次
- 1. GPU 内のプレフィックスキャッシュはすぐに押し出される
- 2. vLLM のデフォルトのオフロード
- 3. 実際に測定してみる
- 4. いつメリットがないのか
- 5. 分離サービング:同じコネクタで KV を別のエンジンに送信する
- 6. 運用から得られる知見
- 7. 確認できなかったこと
- 参考
1. GPU 内のプレフィックスキャッシュはすぐに押し出される
第 2 篇で、3,200 トークンのプレフィックスを再利用すると最初のトークンが 174.7ms から 18.9ms になると計測しました。しかし、そのブロックは GPU KV の「空き領域」に残っている場合にのみ再利用できます。新しいリクエストが領域を要求すると、vLLM は誰も使用していないブロックのうち最も古いものから削除します。複数の長い文書について交互に問い合わせるサービスでは、GPU の数 GB なんてすぐに足りなくなります。
KV オフロードとは、削除する前(正確にはブロックが一杯になったとき)にそのブロックをより大容量で低速な場所、すなわち CPU メモリにコピーしておく機能です。同じプレフィックスが再び来ると、GPU で見つからなかったブロックを CPU から探して復元します。再計算するよりも転送する方が速い場合にのみメリットがあるという損益分岐点は、LMCache 篇で扱いました。
2. vLLM のデフォルトのオフロード
vLLM は KV とエンジン外部とのやり取りを KV コネクタという 1 つのインターフェースにまとめます。同じインターフェースに複数の実装がアタッチされます。
| コネクタ | 役割 |
|---|---|
OffloadingConnector |
vLLM のデフォルト CPU オフロード。0.11.0 で LRU 管理とともに導入され、0.19.0 で汎用 CPU オフロードとして整理された |
LMCacheConnectorV1, LMCacheMPConnector |
LMCache による CPU・ディスク・リモートストレージまでの拡張 |
NixlConnector, MooncakeConnector |
別のエンジン(別の GPU、別のサーバー)へ KV を送信する。分離サービングで使用 |
MultiConnector |
複数のコネクタを順番に適用する |
デフォルトのオフロードは 2 行の設定で有効になります。 --kv-offloading-size は GiB 単位の CPU バッファサイズであり、この値を指定すると native バックエンド(すなわち OffloadingConnector)が有効になります。
vllm serve <모델> --kv-offloading-size 0.5 --kv-offloading-backend native
詳細な設定は --kv-transfer-config の kv_connector_extra_config で指定します。0.32.0 のドキュメント基準では、CPU のみを使用する CPUOffloadingSpec(デフォルト)と、CPU の配下にファイルシステムのような二次階層を設ける TieringOffloadingSpec があり、削除順序は lru または arc から選択します。デフォルトではプロンプト(プリフィル)ブロックのみをダウングレードし、デコードブロックはダウングレードしません(offload_prompt_only)。GPU と CPU 間のコピーは cudaMemcpyAsync により計算と並行して実行されるとドキュメントに記載されています。
3. 実際に測定してみる
方法
キャッシュが追い出される状況を意図的に作り出しました。
- GPU KV を
--kv-cache-memory-bytes 268435456(256MiB)に制限し、 2,336 トークン分のみが入るようにしました。--max-model-len 2048. - 1 バッチは 5 つのリクエストです。プレフィックス A(約 1,600 トークン)を最初に送信し(コールド)、すぐに再度送信し(GPU ヒット)、別のプレフィックス B と C を送信して A を追い出した後、もう一度 A を送信します。
- オフロードなしのサーバーと
--kv-offloading-size 0.5(約 4,600 トークン分)のサーバーでそれぞれ 5 バッチずつ実行しました。最初のバッチの最初のリクエストにはサーバーのウォームアップが混ざるため、分けて確認します。 - その他の環境は第 2 篇と同じです(8GB 級ノート PC 用 GPU、vLLM 0.31.0、Qwen3-Reranker-0.6B、コンテナメモリ制限 4GiB)。
結果
| 段階 | オフロードなし(TTFT 中央値、2〜5 番目のバッチ) | オフロード 0.5GiB(同条件) |
|---|---|---|
| A 初回(コールド) | 76.8ms | 77.7ms |
| A 直後の再送(GPU ヒット) | 14.0ms | 14.3ms |
| B・C による追い出し | 77.2ms | 78.1ms |
| A 再送(追い出された後) | 77.3ms(再計算) | 35.1ms(CPU からロード) |
5 バッチすべてを確認すると、オフロードサーバーの「A 再送」は 35.3、35.4、34.8、33.3、35.6ms とすべて 34ms 前後であり、オフロードなしのサーバーは 76.3〜78.0ms とコールドリクエストと同等でした。一番最初のリクエストは両方のサーバーで 104ms となり、ウォームアップ分が 30ms 近く加算されていました。
サーバーのメトリクスから、何が転送されたのかも確認しました。
メトリクス(/metrics) |
値 | 意味 |
|---|---|---|
vllm:kv_offload_load_bytes_total |
849.6MB(5回) | CPU→GPU に戻した量。1回あたり約 170MB = 約 1,480 トークン × 114,688 バイト |
vllm:kv_offload_load_time_total |
0.0673秒 | 5 回復元するのにかかった時間。秒速約 12.6GB、1回あたり 13.5ms |
vllm:kv_offload_store_bytes_total |
2.82GB(25回) | GPU→CPU に落とした量。プロンプトブロックはすべてダウングレードして保存される |
vllm:kv_offload_store_time_total |
0.221秒 | 秒速約 12.8GB |
vllm:prompt_tokens_by_source_total |
計算 24,771 / GPU ヒット 7,936 / 外部 7,408 | 「外部」が CPU からロードしたトークン数 |
読み方は以下の通りです。
- 追い出されたプレフィックスの最初のトークンが 2.2 倍高速になりました(77.3ms → 35.1ms)。 それでも GPU に残っている場合(14.0ms)よりは 21ms 遅くなります。その差の大部分は 170MB を転送する 13.5ms です。
- この GPU で KV を転送する速度は秒速約 12.6GB でした。 同じ 1,480 トークンを再計算するには約 63ms かかります(77.3ms から GPU ヒットの 14.0ms を引いた値)。計算速度が秒速約 23,000 トークンで、転送速度が秒速約 110,000 トークン分であるため、この組み合わせでは転送する方が 5 倍近く高速です。
- ダウングレード処理はタダではありません。 再利用されなかったブロックも含めてすべて合計 2.82GB をダウングレードしました。コピーは非同期であるため今回の測定におけるコールドリクエストの TTFT にはほとんど差が見られませんでしたが(76.8 対 77.7ms)、PCIe の帯域と CPU メモリを継続的に消費します。
- CPU バッファは固定メモリです。 0.5GiB を割り当てると、その分ページのピン留めが行われ、コンテナのメモリ制限にそのまま計上されます。今回のテストは 4GiB の制限内で行うため、バッファを小さく設定しました。
4. いつメリットがないのか
- モデルが小さく GPU の計算が高速であるほど、メリットは減少します。 再計算にかかる時間が転送時間に近づくためです。逆に大きなモデルほど、トークンあたりの計算量が増加する一方で、トークンあたりの KV バイト数は GQA・MLA のおかげでそこまで増えません。「LLM KV キャッシュ解剖」の第 3 篇で同じ GPU により測定した LMCache の実測(576ms → 48.6ms)の方が今回よりもメリットが大きかったのも、入力が長かったためです。
- 再利用がない場合、コストしか残りません。 すべてのプロンプトブロックをダウングレードするため、プレフィックスが再利用されない負荷においては PCIe の帯域と固定メモリを消費するだけになります。
store_threshold(何回参照されたブロックのみをダウングレードするか)により削減できます(未測定)。 - GPU に十分な空き容量がある場合、使用する機会はありません。 第 2 篇のデフォルトサーバーでは KV が 40,208 トークンであるため、同じテストで A が追い出されることはありません。
5. 分離サービング:同じコネクタで KV を別のエンジンに送信する
プリフィルとデコードを異なる GPU に配置する分離サービング(disaggregated prefill)は、上記のコネクタ構造上で動作します。プリフィルエンジンが KV を作成して NixlConnector を通じてデコードエンジンに送信し、デコードエンジンは受け取った KV を使用して生成を継続します。vLLM のドキュメントが挙げている理由は 2 つあります。
- TTFT と ITL を個別に調整するため(プリフィルとデコードに異なる並列処理を適用可能)
- テール ITL を制御するため。第 2 篇で見た「長い入力がデコードを停止させる時間」はチャンクサイズでも軽減できますが、適切なチャンクサイズを選ぶのが難しいとドキュメントには記載されています。
同じドキュメントには「分離サービングはスループットを向上させない」と明確に記載されており、機能全体が実験的であると示されています。元の論文である DistServe(OSDI 2024)は、TTFT と TPOT の目標を同時に満たす GPU あたりの最大リクエストレートの観点から評価を行い、従来のシステムと比較して 7.4 倍、または 12.6 倍厳しい目標を達成したと報告しています(アブストラクト基準)。0.32.0 には、分離サービングにおいてデコード側がプリフィル側で生成されたトークン ID を再利用する機能(kv_transfer_params の prompt_token_ids)も含まれています。
GPU が 1 基しかないため、今回も vLLM 単体での分離サービングは未測定です。同一 GPU ファミリーの 1 基と 1GbE で接続した 2 台の環境で NVIDIA Dynamo を用いて分離サービングを試した結果(1 基の場合は 5 倍、2 台間では 20 倍以上低速)は、Dynamo 篇と「トス ML/AI Infra 準備」の第 24 篇に記載されています。KV の転送経路が NVLink や RDMA のように高速である場合にのみ意味があるという結論は、今回のオフロード実測(PCIe 経由で秒速 12.6GB)とも方向性が一致しています。
6. 運用から得られる知見
- 複数の長い文書について交互に問い合わせるサービス(RAG、コードリポジトリのクエリ、長い会話)の場合、CPU オフロードは設定 1 行で追い出されたプレフィックスの TTFT を半分以下に短縮します。GPU KV の使用率(
vllm:kv_cache_usage_perc)が常に高く、プレフィックスのヒット率が期待値よりも低い場合に真っ先に試す価値があります。 - 有効にした後は、
vllm:prompt_tokens_by_source_total{source="external_kv_transfer"}とvllm:kv_offload_load_time_totalを用いて実際にロードされているか、どれくらいの時間がかかっているかを確認します。 - CPU バッファのサイズはコンテナのメモリ制限に上乗せして確保します。固定メモリはスワップアウトされず、制限を超えるとエンジンは OOM で強制終了します。
7. 確認できなかったこと
TieringOffloadingSpecのファイルシステム階層、arcポリシー、store_thresholdは未測定です。- 高負荷時におけるオフロードのコピー処理がデコードのスループットに与える影響は未測定です。今回のテストではリクエストを 1 つずつ送信しました。
- vLLM 単体での分離サービング(
NixlConnector)は GPU が 1 基しかないため未測定です。
参考
- vLLM ドキュメント、KV Offloading Usage Guide、Disaggregated Prefilling(0.32.0):https://docs.vllm.ai/en/latest/features/kv_offloading_usage.html、https://docs.vllm.ai/en/latest/features/disagg_prefill.html
- vLLM 0.11.0、0.19.0 リリースノート(CPU オフロード):https://github.com/vllm-project/vllm/releases
- Zhong 他, 「DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving」, OSDI 2024. https://arxiv.org/abs/2401.09670
- 前の記事: LMCache: KV キャッシュを GPU の外へ、NVIDIA Dynamo