TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

prefill と decode を分離すると、小さなモデルと遅いネットワークで測定した分離サービング

ステータス: ドラフト(2026-11-14)、実測値。 本記事の数値は 2026-11-13 に筆者が直接測定した値です。環境とリクエストは 第 1 回と同様です。Kubernetes v1.34、24GB 級 GPU 1 基のサーバーと 8GB 級ノート PC 用 GPU サーバー、ノード間 1GbE、RDMA なし、Dynamo v1.6.2 の vllm-runtime:1.6.2 イメージ(vLLM 0.31.0)、モデルは Qwen3-0.6B です。分離サービングが不利となる条件である点を最初にお断りしておきます。 作成ルール: 実測データを基本とし、測定できなかったものは「未測定」、確認できなかったものは「未確認」と表示します。

小さなクラスターで分離サービングを再測定する

目次

1. 分立サービングがやろうとしていること

1つのリクエストを処理する作業は 2 つに分かれます。プロンプト全体を読み込んで KV を作成する作業(prefill)と、その KV を参照しながらトークンを 1 つずつ生成する作業(decode)です。prefill は一度に大きな計算を行い、decode は小さな計算を長く繰り返します。両者が同じ GPU で実行されると、長い prefill 1 つが他のリクエストのトークン生成を停止させてしまいます。

分離サービングでは、これら 2 つを異なるワーカーに割り当てます。prefill ワーカーが KV を作成し、それを decode ワーカーに転送した後、decode ワーカーがトークンを生成します。コストは KV を転送する時間です。Dynamo はこの転送に NIXL というライブラリを使用しており、ドキュメントでは RDMA を前提として説明されています。TCP でも動作しますが、はるかに遅いと記載されています。

本編が答えようとするのは次の 3 点です。

  1. Kubernetes で Grove なしに分離サービングのグラフが起動するか。
  2. RDMA のない環境で同じサーバー内の分離にかかるコストはどれほどか。
  3. 1GbE を跨ぐ分離にかかるコストはどれほどか。

2. 構成

DGD の構成要素に type: prefill と type: decode を配置し、ワーカーの引数に役割を記述します。

# prefill 워커
- --disaggregation-mode
- prefill
- --kv-transfer-config
- '{"kv_connector":"NixlConnector","kv_role":"kv_producer","kv_buffer_device":"cpu"}'

# decode 워커
- --disaggregation-mode
- decode
- --kv-transfer-config
- '{"kv_connector":"NixlConnector","kv_role":"kv_consumer","kv_buffer_device":"cpu"}'

# 둘 다
env:
  - {name: UCX_TLS, value: "tcp,self"}
  - {name: UCX_RNDV_THRESH, value: "inf"}

RDMA がないため、転送を TCP に固定しました。 kv_buffer_device を cpu としたのは、Dynamo のサンプルのコメントに従ったものです。GPU バッファで TCP を経由させると UCX 内部でクラッシュすると記載されています。この設定では KV が ホストメモリを経由します。ワーカーのログにも「UCX CUDA support was not found」という警告が残りました。

デプロイは 2 つです。

名前 prefill ワーカー decode ワーカー KV が通過する経路
同じサーバー 24GB 級 GPU サーバーに 2 台 同じサーバーに 2 台 1つのサーバー内の TCP
サーバー間 8GB 級 GPU サーバーに 1 台 24GB 級 GPU サーバーに 2 台 1GbE

フロントエンドは第 3 回と同様に KV ルーティングモードです。エンジンの引数とリクエストも同じです。1 つのリクエストは平均 2,371 トークンであり、1 トークンの KV が 114,688 バイトであるため、 1 つのリクエストの KV は約 272MB です。

Grove なしで起動しました。 DGD を適用すると、オペレーターがフロントエンド、prefill、decode をそれぞれ Deployment として作成し、2 分以内に 5 つの Pod すべてが Ready になりました。フロントエンドはリクエストを prefill ワーカーにまず送信し、続いて decode ワーカーに転送しました。チャートのデフォルトでは Grove はオフになっており、このサイズのグラフには不要でした。

3. 同じサーバー内の分離

プレフィックス 2・7・12・24 種類、同時実行 4・8、各ポイント 3 回の中央値です。24 回の測定すべてでエラーは 0 件でした。比較対象の値は第 3 回の KV ルーティング(ワーカー 6 台)です。

プレフィックスの種類 同時実行 TTFT p50: KV ルーティング / 分離 TTFT p99: KV ルーティング / 分離 リクエスト/秒: KV ルーティング / 分離
2 4 34 / 286ms 73 / 592ms 10.9 / 6.5
7 4 40 / 375ms 129 / 1,007ms 8.9 / 5.6
12 4 43 / 465ms 138 / 887ms 8.7 / 5.0
24 4 64 / 518ms 149 / 958ms 8.1 / 4.8
2 8 49 / 462ms 145 / 981ms 14.7 / 9.0
7 8 60 / 650ms 163 / 1,477ms 12.2 / 7.2
12 8 69 / 794ms 172 / 1,744ms 12.1 / 6.4
24 8 106 / 896ms 184 / 1,776ms 11.5 / 6.0

2つの構成でリソースは同一ではありません。KV ルーティング側は同じ 24GB 級 GPU にワーカー 4 台を配置し、別サーバーのワーカー 2 台を追加で使用しました。分離側はその GPU 1 枚にワーカー 4 台(prefill 2、decode 2)のみを使用しました。そのため、スループットの差にはワーカー 2 台分の要素が混ざっています。一方、 初トークン時間の差はそれだけでは説明がつきません。

最も有利なセルを見るとそれがわかります。プレフィックス 2 種類・同時実行 4 においてキャッシュトークンの比率は 0.857 であり、prefill ワーカーはプレフィックスをほとんど再計算しませんでした。それにもかかわらず、初トークンまでに 286ms かかりました。KV ルーティングにおける同じセルは 34ms でした。 prefill ワーカーのキャッシュにヒットしても、decode ワーカーはその KV を持っていないため、272MB を丸ごと受信する必要があります。 増加した約 250ms がそのコストです。272MB を 0.25 秒で転送したとすると秒速 1GB 強となり、同じサーバー内でホストメモリを経由して TCP でコピーした速度としては妥当なサイズです。転送のみを個別に測定したわけではないため、この内訳は推測です。

同時実行数が上がると転送が重複し、さらに増加します。同時実行 8 において中央値は 0.46〜0.9 秒、p99 は 1〜1.8 秒でした。

ヒット率も低下しました。プレフィックス 12 種類でキャッシュトークン比率が 0.40〜0.42、24 種類で 0.20 前後となり、KV ルーティング(0.57〜0.66、0.36〜0.43)に及びません。プレフィックスキャッシュを保持しているのが prefill ワーカー 2 つだけであり、保持できるプレフィックスの総量がワーカー 6 台時の 3 分の 1 であるためです。

まとめると、この条件において同じサーバー内の分離は、 初トークンが 8〜12 倍遅くなり、スループットが 37〜48% 低下しました。 GPU が 1 枚であるため計算リソースは増加せず、prefill が 100ms 以内に終わる小さなモデルであるため、「長い prefill が decode をブロックする」という問題が最初から小さかったのです。得るものがないところに転送コストだけを上乗せした構成です。

4. サーバー間の分離: 遅かったし、その前にメモリが一杯になった

1GbE は実際には秒速 110〜117MB を転送します。272MB を転送すると 2.3〜2.5 秒かかるという計算になります。

このデプロイは最後まで測定できませんでした。 prefill ワーカーがわずか数件のリクエストで OOMKilled となったためです。

試行 成功したリクエスト それらのリクエストの TTFT その後
同時実行 4 2 件 2,039ms、2,040ms 負荷をかけてから 9 秒後に prefill ワーカーが OOMKilled(終了コード 137)、残りのリクエストはすべて失敗
同時実行 1 4 件 中央値 1,637ms、最大 2,653ms 同じワーカーが再び OOMKilled

成功した 6 件の初トークンは 1.6〜2.7 秒であり、リンク速度から計算した値と同等の大きさです。サンプルが 6 件のみであるため、分布を語ることはできません。KV ルーティングの 34ms と比較すると 50 倍前後です。

メモリ面の方がより有用な発見です。prefill ワーカーのメモリ制限は 3,584Mi でした。同じイメージと同じエンジン引数で第 3 回のすべての測定をエラーなく実行した制限です。 kv_buffer_device: cpu では転送すべき KV がホストメモリに載るため、1 つのリクエストあたり 272MB が追加で必要になります。同時実行 4 なら 1GB を超えます。同時実行 1 でも 4 件だけで制限を超過したことを考えると、転送が完了したバッファが即座に解放されていないようです。ワーカーのメモリ使用量を経時的に測定したわけではないため、原因は未確認です。

同じサーバーのデプロイではこの問題は発生しませんでした。あちらのワーカーの制限は 10Gi でした。サーバー間デプロイの制限を引き上げて再測定はしていません。そのサーバーには他のワークロードが同居しており、制限を十分に大きく与えることができなかったためです。

ここで得られる教訓は 1 つだけです。 分離サービングに移行する場合、ワーカーのメモリ制限を再計算する必要があります。 GPU バッファで RDMA を経由する構成では事情が異なるでしょうが(未測定)、TCP とホストバッファを使用する構成では、「同時リクエスト数 × 1 リクエストあたりの KV サイズ」に余裕分を追加した値が、従来の制限に上乗せして必要になります。

5. では、いつメリットがあるのか

本記事では分離サービングが勝る条件を測定することはできませんでした。測定できたのは負ける条件の 2 つです。ただし、損益を分ける要因が何であるかは測定から明らかになりました。

분리 서빙이 치르는 값 ≈ 요청 하나의 KV 바이트 ÷ 전송 처리량
분리 서빙이 얻는 값 ≈ 긴 prefill 이 같은 GPU 의 decode 를 막던 시간

ドキュメントが RDMA を前提としている理由は、この式にあります。転送スループットが数十 GB/s になって初めて、支払うコストが prefill 時間より小さくなります。RDMA や NVLink が存在する環境での損益は未測定です。

6. まとめ

シリーズ全体を1行ずつ要約すると次のようになります。Serviceの背後にあるvLLMは、プレフィックスが1台の容量を超えるとキャッシュを破棄します(第1部)。Dynamoを導入するには、オペレーターのWebhookのスコープと権限から確認する必要があります(第2部)。KVルーティングはその区間でのヒット率を2〜3倍に高めます(第3部)。分離サービングは、高速なネットワークと大きなモデルがある場合の話です(本稿)。

参考

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

コメント

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

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