TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

Service の後ろに vLLM を複数台置いた場合のプレフィックスキャッシュのヒット率

ステータス: ドラフト (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. なぜベースラインから測定するのか

このシリーズでは、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 つです。

負荷ジェネレーターは GPU を持たない別のサーバーで実行し、Service の ClusterIP にリクエストを送信しました。公平な測定を行うために、3 つのルールを遵守しました。

  1. リクエストごとに新しい接続を確立する。 Service は接続単位でポッドを選択します。クライアントが接続を再利用すると、同じポッドにルーティングされ続けるため、ベースラインが実際より良く見えてしまいます。
  2. リクエストの順序をシード値で固定する。 後続の Dynamo の検証環境にも、全く同じリクエストを全く同じ順序で送信します。
  3. ウォームアップを結果の集計から除外する。 測定の前にプレフィックスごとに 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. 結果: プレフィックスが増えるとヒット率が崩壊する

접두가 늘수록 적중이 줄어드는 모양 Service 뒤 vLLM · 동시 8 · 3회 중앙값 · 요청 단위 적중 비율 0 25% 50% 75% 100% 2 7 12 24 서로 다른 접두의 수 워커 3대(네모), 6대(동그라미) 거의 겹칩니다 점선: 3.95 ÷ 접두 수
図: プレフィックスの種類数に応じたリクエスト単位のキャッシュヒット率

リクエストのうちキャッシュにヒットした割合です(同時実行 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, 워커 한 대가 담는 접두 수 ÷ 자주 쓰이는 접두의 종류 수)

ワーカー数はこの数式に含まれていません。したがって、次のように言うことができます。

最後の項目がKVなのか、ルーティングが約束するものなのかです。実際にその上限にどれほど近づけるか、フロントエンドをもう1段挟むコストはどれほどかは、同じリクエストを同じ順序で再度送信して測定します。

この測定の限界についても書き留めておきます。

参考

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます