TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

NVIDIA Dynamo を実際に使ってみる:KV認識ルーティングはほぼ正確だったが、分離サービングは1枚のGPUで5倍、2台のサーバー間で20倍以上遅かった

ステータス:ドラフト(2026-10-07)。 この記事の数値はすべて筆者が実際に測定した値です。環境は8GB級のGPU 1枚、0.6Bモデル、Dynamo 1.6.0.dev20261006(開発ビルド)です。パート6が読んだドキュメントはv1.5.0であるため、フラグやデフォルト値が異なる可能性があります。異なるGPUを持つ2台のサーバー間(1GbE級ネットワーク)は4.5節で測定しており、NVLinkおよびRDMA接続は 未実測 です。 作成ルール:実測データを基本とし、測定できなかったものは「未実測」と表記します。

目次

パート6はDynamoをドキュメントベースで読んでまとめた記事でした。ドキュメントに記載されている内容が実際にその通りであるかを確認するため、単一GPU環境に実際にインストールしてみました。この記事では4つの疑問に答えます。

  1. Dynamoフロントエンドを前に置くと、リクエストごとにどれくらい遅延が増加するのか。
  2. KV認識ルーティングは、同じプレフィックスを再度送信したリクエストをどれくらい同じワーカーにルーティングするのか。ラウンドロビンとどれくらい違うのか。
  3. ルーターがKVイベントを受信できなかった場合どうなるのか。
  4. prefillとdecodeを2つのプロセスに分けると、1枚のGPU上で最初のトークンはどう変わるのか。
  5. 異なるGPUを持つ2台のサーバーに分けると(1GbE級ネットワーク)、どう変わるのか。

短い答え。 フロントエンドのコストはサーバーが空いているときは約13 msであり、忙しいときは目立たなくなりました。KV認識ルーティングは同じプレフィックスを再送した60回のうち53〜60回を同じワーカーに送り、ラウンドロビンでは35〜43回でした。ルーターをイベントモードで起動したにもかかわらずワーカーがイベントを発行しない場合、ヒット率 que ランダムと同等の水準まで低下するにもかかわらずエラーは発生せず、ログに警告が1行残るだけでした。1枚のGPU上での分離サービングは、すべての条件において最初のトークンが4.7〜7.2倍遅くなりました。2つのワーカーが1枚のGPUを共有しており、KVの転送コストが加算されたためであり、これは分離サービングの利点を測定したものではありません。異なるGPUを持つ2台のサーバーに分けると、1,500単語の入力における最初のトークンが1.7秒、3,000単語では3.4〜3.5秒となり、単独サービング(44〜172 ms)の20倍以上に増加しました。増加した時間は、KVのバイト数をリンクのスループット(104 MB/s)で割った値と一致しました。

1. 環境と方法

2. フロントエンドを経由するコスト

同じモデルをvLLMのみで起動した場合(solo)と、Dynamoフロントエンドの背後に配置した場合(dyn)を、入力512トークン、出力128トークンで3回ずつ比較しました。値は最初のエントリートークン生成時間(TTFT)の中央値です。

同時リクエスト vLLM単独 Dynamo配下
1 29〜39 ms 42〜43 ms
8 129〜130 ms 129〜141 ms
32 272〜274 ms 272〜274 ms

同時実行数1の中央値は30 msから43 msへ約13 ms増加しました。同時実行数8および32では区別がつかず、スループットも同じでした(同時実行数32で秒間1,507対1,509トークン)。フロントエンド(HTTP、トークン化、ルーティング判断)のコストはサーバーが空いているときのみ現れる固定コストであり、忙しいときは埋もれました。このモデルはトークンの処理が非常に高速であるため13 msが目立っていますが、大きなモデルであれば相対的な割合はさらに小さくなると予想されますが、これは 未実測 です。

3. KV認識ルーティング

3.1 設定

2つのワーカーを同じモデルで起動し、フロントエンドの --router-mode のみを変更しました。

名前 フロントエンドオプション ワーカー側
ラウンドロビン --router-mode round-robin KVイベントオフ
ランダム --router-mode random KVイベントオフ
KV認識、近似 --router-mode kv --no-router-kv-events KVイベントオフ
KV認識、イベントモードだがイベントなし --router-mode kv KVイベントオフ
KV認識、イベントモード --router-mode kv KVイベントオン(ZMQでパブリッシュ)

負荷はプレフィックス(約1,500単語、合成)を12種類作成し、それぞれ6回ずつシャッフルして送信し、毎回末尾の24単語を変更しました。リクエストは72個であり、プレフィックスを2番目以降に送信した再リクエストは60個です。再リクエストの最初のトークンが最初に送信したリクエスト(約80 ms)の60%未満である場合、キャッシュにヒットしたとカウントしました。ヒットした場合は約15 ms、ミスした場合は約80 msでした。

3.2 結果

ルーティング 再リクエスト60回中のヒット数(3回) 再リクエストの最初トークン平均
ラウンドロビン 35, 40, 43 34〜42 ms
ランダム 44, 31, 45 32〜46 ms
KV認識、近似 60, 53, 60 15〜26 ms
KV認識、イベントモードだがイベントなし 39, 44, 41 32〜38 ms
KV認識、イベントモード 60, 60, 60 15 ms

同じ構成を事前にもう一度実行した結果(ラウンドロビン 35, 40, 43、ランダム 37, 37, 41、近似 60, 60, 60、イベントなし 41, 44, 37、イベントモード 60, 60, 60)も傾向は同じでした。近似方式は初回実行で3回とも60回でしたが、再実行で1回53回となったため、自ら記憶する方式はイベント方式よりも少し不安定である可能性があると考えられますが、断定はできません。イベント方式は6回すべてが60回でした。

3.3 サイレント失敗

表の4行目は、この実験において最も重要な結果です。 --router-mode kv でイベントモードを有効にしたにもかかわらず、ワーカーがKVイベントを発行しない場合、ルーターのインデックスが空のままとなり、すべてのリクエストの重複度が0になり、負荷のみに基づいてワーカーが選択されるようになります。ヒット率はランダムの水準(39, 44, 41)まで低下したにもかかわらず、サーバーは正常に応答し、エラーも発生しませんでした。フロントエンドのログに以下の警告が1行出力され、イベントを発行するワーカーに変更して実行した際には出力されませんでした。

WARN ... KV-event publishing is disabled for a role that requires KV events
diagnostic_code="kv_event_publisher_disabled" ... requirement="cache_aware_routing"

ヒット率がランダムと同等であることを気付くには、ログの警告を確認するか、同じプレフィックスを再度送信したときに最初のトークンが短縮されるかを個別に測定する必要があります。ルーターを有効にしただけでヒット率を測定しない場合、この状態のまま運用されてしまう可能性があります。

3.4 シミュレータと実測の違い

このルーターのルールを純粋なPythonで実装して同じ負荷を実行してみると(最初から連続して重複するブロックが最も多いワーカー、タイの場合は負荷が少ないワーカー)、KV認識は60回中60回ヒットし、イベントを通知しない場合はラウンドロビンと同じ値になります。傾向は実測値と同じです。ただし、シミュレータのラウンドロビンは60回中49回であり、実測値(35〜43)よりも高くなっています。シミュレータのワーカーは一度処理したプレフィックスを永遠に記憶しますが、実際のワーカーはKV領域が14,816トークンであるため、12種類のプレフィックス(約18,000トークン)をすべて保持しきれず、古いものから追い出されます。KV認識ルーティングはプレフィックスをワーカーに分散させ、各ワーカーが6種類(約9,000トークン)のみを保持するようにするため、追い出されません。プレフィックスルーティングの利点には、「同じ場所に送る」ことに加えて「各ワーカーの作業セットが小さくなる」ことが含まれていることを意味します。

3.5 2台のサーバー前のルーター

異なるGPUを持つ2台のサーバーにワーカーを1つずつ配置し(KV領域は14,816トークンと27,088トークン)、同じ負荷をサーバー間のネットワーク経由で送信しました。再リクエスト60回中のヒット数は、ラウンドロビンが40, 47, 50、ランダムが47, 40, 52、KV認識の近似が60, 60, 60、KV認識のイベントモードが43, 60, 60でした。KV認識ルーティングは、2つのGPUが異なる環境においても同じ傾向でヒットしました。イベントモードの最初のイテレーションが43回だった理由は分析していません(イベント購読が確立される前のリクエストであった可能性がありますが、確認できていません)。サーバーごとに最初のリクエストの速度が異なり(44msと80ms)、「最初のリクエストの60%未満」というヒット判定がこのテストではあまり明確ではないという制限もあります。

4. 1枚のGPU上のprefill/decode分離

4.1 設定

プリフィル専用ワーカーとデコード専用ワーカーを2つのプロセスとして同じGPU上に立ち上げ(それぞれGPUメモリの38%、KV転送はNIXL)、1つのプロセスが両方の処理を行う構成と同じ負荷で比較しました。出力は64トークンであり、値は3回繰り返したTTFTの中央値です。

4.2 結果

入力(単語) 同時リクエスト 1プロセス(ms) 分離(ms) 倍率
1,500 1 78 406 5.2
1,500 4 190 1,359 7.2
3,000 1 171 812 4.7
3,000 4 398 2,442 6.1

すべてのセルにおいて分離の方が遅くなりました。ワーカーのKV転送メトリクスで見ると、同時実行数1、入力1,500単語の場合、1リクエストあたり平均196 ms(転送あたり119.3 MB、スループット608 MB/s)がかかり、同時実行数4で入力3,000単語の場合は平均711〜1,723 msでした。最初のトークンが1プロセスの場合よりも約330 ms遅くなった同時実行数1の場合、転送にかかった196 msはその大部分を占めています。残りの約130 msが何であるかは分解して測定していません(ズレ:未実測).

4.3 この結果の読み方

これは「分離サービングが遅い」のではありません。2つのワーカーが同じGPU1枚を共有しているため計算資源が増えておらず、分離が解決しようとする問題(長いprefillが他のリクエストのdecodeを妨げること)を解く仕組みが1つしかない構成です。そこにKVを移動するコストが追加されただけです。異なるGPUの2つのサーバーに分けた場合は4.5節で測定しました。NVLinkやRDMAで接続された環境でメリットがあるのかはこの記事では測定していません(未測定).

トークンあたりのKVが114,688バイトであるため、1,500トークンは約164 MiBとなります。第6編が引用したドキュメントの警告(遅い経路でKVを移動させるとTTFTが約98秒まで延びる可能性がある)と同じ傾向の結果です。ただし、その98秒は別のモデル、別の入力長、別の接続の値であり、この実験と規模を比較することはできません。608 MB/sで1,500トークンを移動させると約280 msであり、サーバー間のTCPのような0.1 GB/s台であれば1.5秒を超えます。

4.4 KV転送はなぜ遅かったのか

原因は特定できませんでした。転送方式を変更した結果は以下の通りです。

UCX設定 結果
デフォルト(制限なし) 転送スループット 608 MB/s、初回のトークン 379〜384 ms
デバッグログを有効にしたデフォルト 615 MB/s、初回のトークン 387〜389 ms
TCP系に限定(UCX_TLS=tcp,cuda_copy,sm,self) 621 MB/s、初回のトークン 381〜384 ms
TCPとデバイス内IPCを同時に許可 初回のトークン 383〜384 ms(スループット低下はログに残らず)
デバイス内IPCのみを許可(UCX_TLS=cuda_ipc,cuda_copy,sm,self) エンジンが起動せず

IPCのみを許可した際のエラーは no active messages transport ... cuda_ipc/cuda - no am bcopy と Destination is unreachable でした。NIXLのUCXバックエンドの初期化において、アクティブメッセージを送信できる転送が要求されますが、IPC転送はそれを提供していないと読み取れます。したがって、デバイス内転送を使用したいという理由で UCX_TLS をIPCのみに絞る設定はこの組み合わせでは使用できません。デフォルト設定でUCXが作成したインターフェースは tcp/eth0, tcp/lo, cuda_ipc/cuda でした。

転送方式を変更しても速度が変わらなかったため、ボトルネックはUCXの転送選択ではない可能性があります。どこがボトルネックなのか(リクエスト単位の直列化、ブロック単位の転送ディスクリプタ28個、コピー経路など)は分解して測定していません(未測定).

4.5 2台のサーバーに分けた分離サービング(異なるGPU、1GbE級ネットワーク)

一方のサーバーは前述の8GB級GPU、もう一方は24GB級のコンシューマー向けGPU(他のサービスと時分割)であり、ポッド間のTCPスループットは113 MB/sでした。ワーカーの検出は一方のポッド内のetcdで行い、KV転送はNIXLデフォルト(UCX)です。同一の負荷(出力64、各セルの繰り返し3回の初回のトークンの中央値)です。

構成 1,500単語 同時1 1,500 同時4 3,000 同時1 3,000 同時4
8GB級サーバー単体 78ms 190ms 172ms 421ms
24GB級サーバー単体 44ms 92ms 83ms 181ms
8GB級がprefill、24GB級がdecode 1,752ms 5,818ms 3,533ms 12,476ms
24GB級がprefill、8GB級がdecode 1,721ms 5,637ms 3,407ms 12,222ms

5. 経験したトラブル

6. 制限事項と未測定項目

7. 運用時のチェックリスト

  1. KV認識ルーティングを有効にした場合、 ワーカーがKVイベントを出力しているか を最初に対象のログで確認します。フロントエンドのログに kv_event_publisher_disabled という警告が出ていないか確認します。
  2. ルーティングを変更した後は、「同じプレフィックスを再度送信したときに最初のトークンが減少するか」を直接測定します。エラーなしでランダムレベルまで低下する可能性があります。
  3. プレフィックスが1つのワーカーのKV領域よりも多様性に富んでいる場合、ルーティングがワーカー間でプレフィックスを分散させる効果が加わります。ワーカーのKV領域(サーバーログの GPU KV cache size)とプレフィックスの合計を比較します。
  4. 分離サービングは、KVの移動経路(転送スループット)を個別に測定した上で検討します。この実験では、リンクが1GbE級であれば移動時間は計算値とほぼ一致しました(4.5節)。トークンあたりのKVサイズ(レイヤー数 × KVヘッド数 × ヘッドサイズ × 2 × バイト数)に入力長を掛けると1つのリクエストが移動させるバイト数が算出しでき、それを実際の転送スループットで割ると、最初のトークンに加算される時間が求まります。
  5. 実行前に、古いプロセス(エンジンコア)が残っていないか、モデルのダウンロードロックが残っていないかを確認します。
  6. サーバーごとにNVIDIAドライバーのバージョンが異なる場合、インストールしたvLLMとtorchのCUDAビルドをそのドライバーがサポートしているか最初に確認します。サポートしていない場合、サーバーのログではなくエンジンの起動段階でCUDAエラーにより異常終了します。

参考資料

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

コメント

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

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