TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

同じリクエストをDynamoに送信した場合の、ラウンドロビンとKVアウェアルーティングの実測

ステータス:下書き(2026-11-14)、実測。 この記事の数値は、2026-11-13に筆者が直接測定した値です。環境とリクエストは 第1部と同じです。Kubernetes v1.34、GPUサーバー3台(24GBクラスGPU 1枚、8GBクラスノートPC用GPU 2枚)、ノード間1GbE、Dynamo v1.6.2の vllm-runtime:1.6.2 イメージ(vLLM 0.31.0)、モデルはQwen3-0.6Bです。ルーターの引数はすべてデフォルト値です。作成ルール:実測データを基本とし、測定できなかったものは「未測定」、確認できなかったものは「未確認」と表記します。

ラウンドロビンとKVアウェアルーティング

目次

1. この記事で答える質問

第1部の結論は次のとおりでした。Serviceの背後にvLLMを複数台配置した場合、プレフィックスキャッシュのヒット率は「1台のワーカーが保持するプレフィックス数÷プレフィックスの種類数」で決まり、ワーカーを増やしても向上しません。この記事では、同じリクエストをDynamoに送信して3つの事柄を確認します。

  1. Dynamoを経由するだけで変化するものはあるか(ラウンドロビン)。
  2. KVアウェアルーティングを有効にすると、ヒット率と初回トークン時間がどれほど変化するか。
  3. ドキュメントが注意を促している2つの要素、すなわちハッシュシードとKVイベントを除外するとどうなるか。

2. マニフェストにおける変更点

基準線はDeploymentとServiceでした。Dynamoではそれら2つが DynamoGraphDeployment(DGD) 1つに置き換わります。オペレーターがこれを読み取り、ポッドとServiceを作成します。エンジン引数、イメージ、モデル、ノード配置はそのままにし、次の4点のみを変更しました。

変更点 基準線 Dynamo
リソース Deployment + Service DGD 1つ(構成要素:フロントエンド1、ワーカー複数)
ワーカーの実行コマンド python3 -m vllm.entrypoints.openai.api_server python3 -m dynamo.vllm
OpenAI APIを開く場所 ワーカーごとに8000 フロントエンドのみ8000、ワーカーはHTTPを開かない
ルーティング Serviceが接続単位でランダム フロントエンドの DYN_ROUTER_MODE

KVアウェアルーティングに必要な設定は2つです。

# 프런트엔드
env:
  - {name: DYN_ROUTER_MODE, value: kv}

# 워커 인자
- --kv-events-config
- '{"publisher":"zmq","topic":"kv-events","endpoint":"tcp://*:20080","enable_kv_cache_events":true}'

ワーカーはKVブロックが作成および削除されるたびにこのアドレスにイベントを送信し、フロントエンド内のルーターがそれを受け取って「どのワーカーがどのブロックを保持しているか」のマップを作成します。リクエストが到着すると、ルーターはリクエストの先頭部分と最も多く重複するワーカーを選択しますが、その際にワーカーの負荷も考慮します。

移行時に確認した事実を記録しておきます。

3. 5つのバリエーション

名前 構成 検証内容
基準線 Serviceの背後のvLLM(第1部) 比較値
ラウンドロビン DGD、 DYN_ROUTER_MODE=round-robin Dynamoを経由するだけの効果
KVルーティング DGD、 DYN_ROUTER_MODE=kv、KVイベント、 PYTHONHASHSEED=0 主要機能
シードなし KVルーティングから PYTHONHASHSEED のみを除外 ハッシュシードは本当に必要か
近似モード DYN_ROUTER_USE_KV_EVENTS=false、ワーカーのイベントも無効化 イベントなしの推測のみでどこまで可能か

リクエストは第1部と同じです。共有プレフィックス約2,000トークンに、リクエストごとに異なるテール約300トークンを付与し、1台のワーカーのKV容量を1GiB(リクエスト約3.95回分)に制限しました。プレフィックスの種類は2・7・12・24、同時リクエストは4・8・16とし、各点で3回実行してその中央値を使用しました。シードが同一であるため、すべてのバリエーションが全く同じリクエストを同じ順序で受け取りました。5つのバリエーションのすべての点においてエラーは0件でした。

ラウンドロビンのバリエーションの1点(プレフィックス2種・同時実行4)は、最初の試行を除外しました。直前に同じシードでテストリクエストを送信したため、プレフィックスがすでにキャッシュに存在していたためです。その点は残りの2回の中央値です。

4. 結果:ラウンドロビンは基準線と同等で、KVルーティングは異なる

같은 요청, 세 가지 길 워커 6대 · 동시 8 · 3회 중앙값 · 요청 단위 적중 비율 0 25% 50% 75% 100% 2 7 12 24 서로 다른 접두의 수 KV 인지 라우팅 Service(동그라미), 라운드로빈(네모)
図:3つの経路におけるリクエスト単位のヒット率

リクエストのうちキャッシュにヒットした割合です(ワーカー6台、同時実行8)。

プレフィックス種類 Serviceの背後のvLLM ラウンドロビン KVルーティング
2 95.0% 95.0% 97.5%
7 55.5% 53.5% 83.0%
12 32.1% 29.2% 67.9%
24 17.5% 17.5% 43.3%

入力トークンのうちキャッシュから取得された割合で見ても同様です。このリクエストでは2,371トークン中2,032トークンがプレフィックスであるため、0.857が達成可能な最大値となります。

プレフィックス種類 同時実行 Serviceの背後のvLLM ラウンドロビン KVルーティング
2 4 0.814 0.814 0.857
7 4 0.431 0.385 0.788
12 4 0.241 0.262 0.664
24 4 0.135 0.126 0.429
2 16 0.814 0.814 0.835
7 16 0.425 0.442 0.779
12 16 0.268 0.237 0.695
24 16 0.141 0.148 0.431

ラウンドロビンは基準線と区別できません。 Dynamoのフロントエンドを手前に置くだけではキャッシュは有効になりません。リクエストをワーカーに均等に振り分けることと、ランダムに割り振ることは、プレフィックスキャッシュの観点からは同じことです。

KVルーティングは、プレフィックスが1台の容量を超える区間でヒット率を2倍から3倍に向上させました。 プレフィックスが2種の場合は差が小さくなります。第1部に記載したとおり、プレフィックスが1台に収まる場合、ルーティングによるメリットはほとんどありません。

ルーターが実際にリクエストを集約したかどうかは、「1つのプレフィックスが何台のワーカーにルーティングされたか」で確認しました。リクエストの nvext.extra_fields に worker_id を入れると、レスポンスに処理したワーカーの識別子が含まれて返されます。それを使用して集計しました。

プレフィックス種類 ラウンドロビン KVルーティング
2 6.00台 1.00台
7 6.00台 2.86台
12 6.00台 4.00台
24 5.92台 5.17台

(ワーカー6台、同時実行4、3回の中央値。)ラウンドロビンはすべてのプレフィックスをすべてのワーカーに送信しました。KVルーティングは、プレフィックスが2種の場合はプレフィックスごとに1台のみに送信し、プレフィックスが増えるにつれて分散しました。

5. 初回トークン時間とスループット

첫 토큰까지의 시간, 꼬리(p99) 워커 6대 · 동시 16 · 3회 중앙값 0 0.4초 0.8초 1.2초 1.6초 2 7 12 24 서로 다른 접두의 수 Service 뒤 vLLM Dynamo 라운드로빈 Dynamo KV 인지 라우팅
図:初回トークンまでの時間 p99

初回トークンまでの時間(TTFT)です。セルごとに p50 / p99 です(ワーカー6台)。

プレフィックス種類 同時実行 Serviceの背後のvLLM ラウンドロビン KVルーティング
2 4 37 / 122ms 41 / 125ms 34 / 73ms
7 4 63 / 184ms 89 / 142ms 40 / 129ms
12 4 94 / 193ms 106 / 140ms 43 / 138ms
24 4 101 / 254ms 113 / 143ms 64 / 149ms
2 16 64 / 325ms 74 / 332ms 67 / 206ms
7 16 142 / 1,353ms 111 / 576ms 72 / 240ms
12 16 193 / 1,441ms 190 / 646ms 77 / 193ms
24 16 205 / 1,508ms 199 / 774ms 109 / 242ms

読み取るべきポイントは3つあります。

スループット(秒間リクエスト数、ワーカー6台・同時実行16)も向上しました。

プレフィックス種類 Serviceの背後のvLLM ラウンドロビン KVルーティング
2 18.9 19.1 21.1
7 14.1 16.0 20.1
12 13.9 14.7 19.2
24 13.3 14.3 17.6

GPUは同じままで、プレフィックス24種において32%多く処理しました。プレフィックスの再計算に費やされていた時間が減少した分です。

6. ワーカー3台でも同様か

GPU 1枚につきワーカー1台を配置した構成です(同時実行8)。

プレフィックス種類 キャッシュトークン比率:基準線 / ラウンドロビン / KV TTFT p99:基準線 / ラウンドロビン / KV 秒間リクエスト:基準線 / ラウンドロビン / KV
2 0.840 / 0.840 / 0.848 165 / 171 / 83ms 17.0 / 17.3 / 20.6
7 0.487 / 0.475 / 0.690 853 / 413 / 145ms 12.4 / 13.6 / 18.5
12 0.283 / 0.291 / 0.474 1,125 / 679 / 152ms 11.2 / 12.5 / 17.2
24 0.143 / 0.141 / 0.252 1,216 / 830 / 202ms 10.7 / 11.8 / 16.6

傾向は同じです。プレフィックス24種でスループットが55%増加しました。ただし、ワーカーが3台しかないため保持できるプレフィックスの総量が半分になり、それに伴いヒット率も6台構成よりも低くなります。

負荷が容量を超える点も1つありました。ワーカー3台・同時実行16・プレフィックス24種において、KVルーティングのTTFT p50は279ms、p99は962msでした。基準線(563ms、3,060ms)よりは優れていますが、他の点のように0.2秒以内に収まることはありませんでした。ルーティングは無駄を削減するだけであり、不足している計算リソースを生み出すわけではありません。

7. 上限にどれほど近づけたか

第1部で計算した理論上の上限は「ワーカー数×1台が保持するプレフィックス数÷プレフィックス種類数」でした。プレフィックスをワーカー間で重複しないように分割して保持すると仮定した値です。

構成 プレフィックス種類 上限 実際の درخواست 単位のヒット率(同時実行 4 / 8 / 16)
ワーカー6台 7 100% 93 / 83 / 91%
ワーカー6台 12 100% 80 / 68 / 82%
ワーカー6台 24 99% 52 / 43 / 52%
ワーカー3台 7 100% 77 / 82 / 90%
ワーカー3台 12 99% 53 / 58 / 64%
ワーカー3台 24 49% 29 / 30 / 28%

デフォルト値のKVルーティングは、キャッシュが逼迫している区間で 上限の半分から3分の2の間を記録しました。ランダム分散よりは2〜3倍優れていますが、容量を余すことなく活用する構成には及びません。

その理由は第4部の表に表れています。プレフィックス24種において、1つのプレフィックスが平均5台のワーカーに送信されていました。ルーターは重複だけでなくワーカーの負荷も考慮するため、重複しているワーカーが忙しい場合は別のワーカーに送信します。そのワーカーはプレフィックスを新しく計算して保持するため、その過程で別のプレフィックスを追い出します。キャッシュが逼迫するほど、この追い出しが連鎖的に発生します。

Dynamoのドキュメントには、重複と負荷の比率を変更するルーターの引数があります。この記事ではデフォルト値でのみ測定しており、その引数を変更することで上限にさらに近づけることができるかどうかは未測定です。

8. ハッシュシードと近似モード

ドキュメントとサンプルでは、ワーカーに PYTHONHASHSEED=0 を与えるよう記載されています。vLLMのブロックハッシュがPythonのハッシュを経由するため、シードがプロセスの異なる場合に同じプレフィックスがワーカーごとに異なるハッシュになってしまうというのがその理由です。シードを抜いて再計測してみました(ワーカー6台、同時実行8、キャッシュトークン比率)。

プレフィックスの種類 ラウンドロビン KVルーティング シードなし 近似モード
2 0.814 0.835 0.848 0.848
7 0.439 0.702 0.729 0.638
12 0.247 0.573 0.547 0.480
24 0.149 0.361 0.343 0.245

シードを抜いても結果は同じでした。 4点の差は0.03以内であり、方向も交錯しています。反復間の揺らぎと区別がつかない大きさです。少なくともこの実験とこの構成では、シードがなくてもルーティングが崩れませんでした。理由は未確認です。ランタイムがシードを自ら決定するのか、ルーターがワーカーのハッシュに依存しない方法があるのかは、コードから確認できませんでした。ドキュメントが要求する設定であり、追加のコストもかからないため、結果が同じだったという理由だけで外すことは推奨しません。

近似モードは中間です。 このモードではワーカーはイベントを送信せず、ルーターは自分が送信したリクエストの記録から「そのワーカーにまだこのプレフィックスが存在するはずだ」と推測します。プレフィックス7種ではKVイベントを使用した場合と大きな差はありませんでしたが(0.638対0.702)、24種では0.245となり、ラウンドロビン(0.149)との差が半分に縮まりました。キャッシュが枯渇してブロックが頻繁に追い出されると、ルーターの推測と実際が食い違うためだと思われます。イベントポートを開きにくい環境で使用できる次善の策程度として読み取ればよいでしょう。

9. まとめ

この測定の限界は第1部と同様です。プレフィックスを均等に抽出し、リクエストの長さがすべて同じで、モデルが小さい点です。大きなモデルでは、外れたリクエスト1件あたりの計算がはるかに長いため、同じヒット率の差がより大きな時間差として現れるはずですが、それは本記事で測定した値ではありません。フロントエンドを複数台配置した際、ルーターが同じマップを参照しているかも未計測です。

参考

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

コメント

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

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