ステータス: ドラフト (2026-11-14)、実測値ベース。 この記事の数値は 2026-11-13 に筆者が直接測定したものです。環境は Kubernetes v1.34、GPU サーバー 3 台(24GB 級 GPU 1枚、8GB 級ノートPC用 GPU 2枚)、ノード間は 1GbE です。エンジンは Dynamo ランタイムイメージの
vllm-runtime:1.6.2に含まれる vLLM 0.31.0、モデルは Qwen3-0.6B です。小規模なモデルと小さなキャッシュで測定した値であるため、絶対的な時間をそのまま大規模モデルに適用することはできません。適用できるのはヒット率が決定される仕組みのほうです。 作成ルール: 実測データを基本とし、測定できなかったものは「未測定」、確認できなかったものは「未確認」と表記します。
Service 配下の vLLM におけるプレフィックスキャッシュ
目次
- 1. なぜベースラインから測定するのか
- 2. 何をどのように測定したか
- 3. 結果: プレフィックスが増えるとヒット率が崩壊する
- 4. ヒットとミスの最初のトークン生成時間の違い
- 5. このベースラインが意味するもの
- 参考
1. なぜベースラインから測定するのか
このシリーズでは、vLLM のみを使用していた Kubernetes 環境に NVIDIA Dynamo を導入するプロセスを実測ベースで記録します。Dynamo が掲げる機能の 1 つが KV アウェアルーティングです。これは、リクエストのプレフィックス部分の KV キャッシュをすでに保持しているワーカーにリクエストを送信する機能です。
その価値を語るには、「現在の方式がキャッシュをどれだけ無駄にしているか」をまず把握する必要があります。現在の方式とは、複数台の vLLM ポッドの手前に Service を 1 つ配置する構成です。この記事ではその構成のみを測定対象とします。Dynamo については次回の記事以降で取り上げます。
前提知識を 1 つだけ確認します。vLLM のプレフィックスキャッシュ(prefix cache)は、リクエストのプレフィックスが以前のリクエストと一致する場合、その部分の KV 再計算を行わずに再利用します。システムプロンプトや長いドキュメントを先頭に付与するサービスでは、最初のトークンまでの時間(TTFT)を大幅に短縮してくれます。しかし、このキャッシュは プロセスごとに独立 しています。ポッド A が計算した KV をポッド B は認識しません。
2. 何をどのように測定したか
| 項目 | 値 |
|---|---|
| モデル | Qwen3-0.6B、1 トークンあたりの KV は 114,688 バイト |
| エンジン引数 | --max-model-len 4096 --kv-cache-memory-bytes 1073741824 --gpu-memory-utilization 0.01 |
| ワーカー 1 台あたりの KV 容量 | 1GiB ÷ 114,688 バイト = 約 9,362 トークン |
| リクエスト | 共有プレフィックス約 2,000 トークン + リクエストごとに異なるサフィックス約 300 トークン、出力 64 トークン |
| 実際の入力長 | 平均 2,371 トークン、キャッシュヒット時はそのうち 2,032 トークンが再利用される |
| プレフィックスの種類数 | 2, 7, 12, 24 |
| 同時リクエスト数 | 4, 8, 16 |
| 試行回数 | 各データポイントにつき 3 回、中央値を使用 |
| リクエスト数 | 各データポイントにつき プレフィックス数 × 20(最小 200) |
KV 容量をあえて 1GiB に制限しました。1 つのリクエストが 2,371 トークンであるため、ワーカー 1 台には約 3.95 リクエスト分の KV しか収まりません。プレフィックスが 4 種類を超えると、1 台にすべてが収まらない条件を作り出すためです。大規模モデルと長いコンテキストを使用する実際のサービスで発生するキャッシュ圧迫を、小規模な環境で模倣する方法です。
構成パターンは 2 つです。
- ワーカー 6台:24GB 級 GPU 1枚を時分割で 4 ワーカーに分割し、8GB 級 GPU 2枚にそれぞれ 1 ワーカーずつ配置。
- ワーカー 3台:GPU 1枚につきワーカー 1台ずつ配置。
負荷ジェネレーターは GPU を持たない別のサーバーで実行し、Service の ClusterIP にリクエストを送信しました。公平な測定を行うために、3 つのルールを遵守しました。
- リクエストごとに新しい接続を確立する。 Service は接続単位でポッドを選択します。クライアントが接続を再利用すると、同じポッドにルーティングされ続けるため、ベースラインが実際より良く見えてしまいます。
- リクエストの順序をシード値で固定する。 後続の Dynamo の検証環境にも、全く同じリクエストを全く同じ順序で送信します。
- ウォームアップを結果の集計から除外する。 測定の前にプレフィックスごとに 1 回ずつリクエストを送信し、そのリクエストは集計に含めません。
キャッシュヒットの確認は 2 つの方法で行いました。1 つはレスポンスの usage.prompt_tokens_details.cached_tokens であり、もう 1 つは各ワーカーから取得した vllm:prefix_cache_hits_total ÷ vllm:prefix_cache_queries_total の測定区間の差分です。両者の値は 24 個のデータポイント中 23 個で小数点以下 3 桁まで一致し、残りの 1 つも 0.001 の差にとどまりました。例えば、プレフィックス 2 種類・同時実行 4 の場合、どちらも 0.814 でした。
1 つ落とし穴がありました。vLLM 単体サーバーは --enable-prompt-tokens-details を指定しないと、 cached_tokens をレスポンスに含めません。このスイッチを有効にせずに測定すると、すべてのリクエストがミスしているように見えてしまいます。
3. 結果: プレフィックスが増えるとヒット率が崩壊する
リクエストのうちキャッシュにヒットした割合です(同時実行 8、3 回の中央値)。
| プレフィックスの種類 | ワーカー 6台 | ワーカー 3台 | 3.95 ÷ プレフィックス数 |
|---|---|---|---|
| 2 | 95.0% | 98.0% | 100%(上限) |
| 7 | 55.5% | 59.5% | 56.4% |
| 12 | 32.1% | 33.8% | 32.9% |
| 24 | 17.5% | 16.9% | 16.5% |
同時実行 4 および 16 でも同様の傾向が見られます。ワーカー 6台の構成を基準とすると、プレフィックス 7 種類では 52.5%・52.0%、12 種類では 28.7%・32.1%、24 種類では 16.0%・16.7% でした。
注目すべき点は 2 つあります。
第一に、ヒット率は単純な数式と一致します。 Serviceがリクエストを均等に分散させると、各ワーカーはすべてのプレフィックスが混ざったリクエストを受け取ります。ワーカーは最も長く使われていないブロックから削除するため、1台のキャッシュには「最近見たプレフィックス約3.95個」が残ります。次のリクエストのプレフィックスがその中にある確率は、3.95 ÷ プレフィックス数となります。表の最後の列がその数値であり、測定値と1〜3%pの範囲内で一致しています。
第二に、ワーカーを増やしてもヒット率は上がりません。 6台と3台でほぼ同じです。ランダム分散ではワーカーごとに同じことが個別に行われるためです。全体のKV容量は2倍ですが、ヒット率はそのままです。ワーカー6台の容量を合わせると約23種類のプレフィックスを収められるため、24種類の負荷においてキャッシュの大半を生かすことができた容量の16%しか使われなかったことになります。
プレフィックス2種類で100%にならない理由も計算でわかります。ワーカー6台とプレフィックス2種類であれば(ワーカー、プレフィックス)の組み合わせは12個です。ウォームアップ2回がそのうち2個を埋め、残りの10個の組み合わせは最初に遭遇したときに1回ずつミスします。測定値はいずれも3回とも200回中190回ヒットでした。ワーカー3台は組み合わせ6個から2個を引いた4回ミスして196回ヒットとなり、これも3回すべて同じでした。
4. ヒットとミスの初回トークンにおける違い
同時実行数4でリクエストをヒットとミスに分けてTTFTを確認しました(ワーカー6台、3回分の合算値)。
| プレフィックスの種類 | ヒットリクエスト数 | ヒット p50 / p90 | ミスリクエスト数 | ミス p50 / p90 |
|---|---|---|---|---|
| 2 | 570 | 37 / 49ms | 30 | 113 / 164ms |
| 7 | 311 | 41 / 63ms | 289 | 107 / 140ms |
| 12 | 200 | 42 / 61ms | 520 | 105 / 141ms |
| 24 | 218 | 36 / 55ms | 1,222 | 104 / 139ms |
ヒットすれば約40ms、ミスすれば約105〜113msです。リクエストごとの処理時間はプレフィックスの種類が増えても変わりませんが、 2つのグループの比率のみが変化します。 そのため、全体のTTFTの中央値はプレフィックスが増えるにつれてミス側にシフトしていきます。
| プレフィックスの種類 | 同時4 p50 / p99 | 同時8 p50 / p99 | 同時16 p50 / p99 |
|---|---|---|---|
| 2 | 37 / 122ms | 47 / 190ms | 64 / 325ms |
| 7 | 63 / 184ms | 93 / 526ms | 142 / 1,353ms |
| 12 | 94 / 193ms | 126 / 493ms | 194 / 1,441ms |
| 24 | 101 / 254ms | 136 / 635ms | 205 / 1,508ms |
(ワーカー6台、3回の中央値。)同時16でテールレイテンシが長くなります。プレフィックス2種類の場合にp99は325msでしたが、7種類以上からは1.3〜1.5秒になります。ミスしたリクエストは2,371トークンをすべて計算する必要があり、その計算が同じGPU上で実行されていた他のリクエストの処理を圧迫するためです。
スループットも同時に低下しました。ワーカー6台・同時16において、プレフィックス2種類は秒間18.9件、24種類は13.3件でした。ワーカー3台では25.1件から11.7件に減少しました。
ワーカー3台の構成はGPUを分割使用していないにもかかわらず、同時16でテールレイテンシがさらに長くなりました(プレフィックス24種類のp99は3,060ms)。同じ負荷を半分しかいないワーカーが受けるためです。逆にプレフィックス2種類では3台側のほうが高スループットでした。24GB級GPU1枚を4台のワーカーで時分割する場合、計算リソースは増えないためです。両構成の絶対時間を比較することは本記事の目的ではなく、 ヒット率が構成に関わらず同じ数式に従うという点のみを持ち帰ります。
5. この基準線が意味するもの
要約すると、Service背後のvLLMにおけるプレフィックスキャッシュのヒット率は次の値によって決まります。
적중률 ≈ min(1, 워커 한 대가 담는 접두 수 ÷ 자주 쓰이는 접두의 종류 수)
ワーカー数はこの数式に含まれていません。したがって、次のように言うことができます。
- プレフィックスが1台のワーカーにすべて収まる場合、ルーティングを変更しても得られるものはほとんどありません。プレフィックス2種類で失われたのは、組み合わせごとに1回ずつの最初のミスのみです。
- プレフィックスが1台の容量を超え始めると、ランダム分散は容量をワーカー数分だけ重複して使用します。同じプレフィックスを複数のワーカーがそれぞれ計算して保持し、そのせいで互いに異なるプレフィックスを追い出してしまうのです。
- リクエストをプレフィックス単位でワーカーに振り分けると、ワーカーごとに担当するプレフィックスの種類が減ります。理論上の上限は「ワーカー数 × 1台が保持するプレフィックス数 ÷ プレフィックスの種類数」に向上します。ワーカー6台であれば、プレフィックス24種類でも大部分でヒットさせることができるという計算になります。
最後の項目がKVなのか、ルーティングが約束するものなのかです。実際にその上限にどれほど近づけるか、フロントエンドをもう1段挟むコストはどれほどかは、同じリクエストを同じ順序で再度送信して測定します。
この測定の限界についても書き留めておきます。
- プレフィックスを均等に抽出しました。実際のサービスでは特定のプレフィックスがはるかに高頻度で使用されるため、ランダム分散のヒット率はこれよりも高くなります。人気度分布を考慮した測定は未実施です。
- リクエストの長さがすべて同じです。長さが混在している場合、「1台が保持するプレフィックス数」は固定値ではありません。
- KV容量を1GiBに固定しました。容量に余裕を持たせれば、同じプレフィックス数でもプレッシャーは生じません。見るべきなのはプレフィックス数ではなく、 プレフィックスの総量と1台あたりの容量の比率です。
- GPUの種類が混在しています。ワーカーごとに速度が異なり、TTFTの分布が広がっている可能性があります。
参考
- vLLMドキュメント: Automatic Prefix Caching
- 測定ツールと実データの要約はリポジトリの
docs/experiments/2026-11-13-dynamo-adoption/にあります。 - KVキャッシュの原理とトークンあたりのバイト数計算は LLM KVキャッシュ解剖 第1回、Dynamoの構成と過去の実測値は 第4回にあります。