powered by TechFeed
表示モード
Deep Dive

AIエージェントの「待機中もGPUを占領する問題」をアーキテクチャで解決する — vLLMとllm-dによるKVキャッシュのCPUオフロード戦略

7月22日、Gabriel Becharaが「A Deep Dive into High-Efficiency Agentic Serving on GKE with vLLM and llm-d」と題した記事を公開した。この記事では、GKE上でvLLMとllm-dを組み合わせ、LLMエージェントが引き起こす「メモリの無駄遣い問題」をアーキテクチャレベルで解決する方法について詳しく紹介されている。エージェントの「待機時間」がそのままGPUコストに直結するという構造的問題に対し、具体的なKubernetes構成まで踏み込んだ実践的な内容だ。

7月22日、Gabriel Becharaが「A Deep Dive into High-Efficiency Agentic Serving on GKE with vLLM and llm-d」と題した記事を公開した。この記事では、GKE上でvLLMとllm-dを組み合わせ、LLMエージェントが引き起こす「メモリの無駄遣い問題」をアーキテクチャレベルで解決する方法について詳しく紹介されている。エージェントの「待機時間」がそのままGPUコストに直結するという構造的問題に対し、具体的なKubernetes構成まで踏み込んだ実践的な内容だ。


「Memory Tax(メモリ税)」とは何か

LLMエージェントの本番運用で最初に直面する課題が、記事が「Memory Tax」と呼ぶ問題だ。

通常の単発リクエストであれば、ユーザーがプロンプトを送り、モデルが応答を返した時点でKV Cache(Key-Valueキャッシュ)は即座に破棄される。KV Cacheとは、過去のトークン列の数学的表現を保持しておくことで、毎回全コンテキストを再計算せずに済む最適化機構だ。

しかしエージェントは違う。ツール呼び出し、外部APIの待機、データベースクエリ、ヒューマン・イン・ザ・ループの承認待ち——これらが発生する間、そのエージェントの巨大なKV CacheはGPUのHBM(High Bandwidth Memory)に居座り続ける。30秒間APIの応答を待つだけで、高価なアクセラレータメモリが他のリクエストを処理できないまま完全に塞がれる。これがMemory Taxだ。

この問題を解決するアーキテクチャとして、記事では以下の2つの技術を組み合わせた構成を解説している。

  • **vLLM**:ローカルのメモリ管理を担うLLM推論エンジン
  • **llm-d**:分散推論のオーケストレーションを担うOSSプロジェクト(記事公開時点でCNCFサンドボックスプロジェクトとして登録済み)

アーキテクチャの核心:Prefill/Decode分離とKVキャッシュルーティング

Prefill/Decode Disaggregation(推論フェーズの分離)

従来の推論エンジンは、Prefill(プロンプトを読み込み最初のKV Cacheを生成するコンピュート集約フェーズ)とDecode(トークンを逐次生成するメモリ集約フェーズ)を同一GPU上で実行する。これにより「Prefillストール」が発生し、大きなプロンプトが入ってくるたびに他ユーザーのトークン生成が止まる。

これを解消するため、クラスターを2種類のノードプールに物理分離する。

ノード種別 役割 典型ハードウェア
Prefillノード プロンプト処理・KV Cache生成 NVIDIA H100、Google TPU
Decodeノード トークン逐次生成 メモリ最適化ノード

Prefillノードがプロンプト処理を終えると、KV Cacheのテンソルはネットワーク経由でDecodeノードへストリーミングされる。このKV転送にはPyNcclConnectorを使い、GPUDirect TCPX/RoCEの高帯域ネットワークを活用する構成が示されている。このフェーズ分離により、各ノードプールをその特性(計算集約/メモリ集約)に合ったハードウェアで独立してスケールできる点が設計上の重要な利点だ。

Stateful KV Cache Routing(セッション維持ルーティング)

リクエストのルーティングを担うのがGKE Inference Gatewayllm-d Endpoint Picker(EPP)の組み合わせだ。

EPPはリクエストのセッションコンテキストを検査し、そのセッションのWarmなKV Cacheを保持しているPodを特定してルーティングする。ext-proc(Envoy External Processing)フィルターを介したgRPC呼び出しで実現しており、データプレーンに重い処理を載せない設計になっている。セッション単位のルーティングが保証されなければ、CPU RAMへオフロードしたKV Cacheが別のPodへリクエストが流れた際に無駄になる——EPPはその前提を支える要となっている。


Memory Taxの回避:CPU RAMへのオフロード

ここが記事の最も重要な部分だ。

Decodeノードがトークン生成中に外部ツール呼び出しを検知し、APIの応答待ちに入ったとき——vLLMはKV Cacheをただちに安価なホストマシンのCPU RAMへ退避させる。GPUのVRAMは即座に解放され、別のリクエストの処理に充てられる。

APIから結果が返ってきたとき、GatewayはEPPを通じて該当のDecodeノードを特定し、CPU RAMのKV CacheをGPU VRAMへスワップバックする。エージェントは中断したところから正確に再開できる。

記事はこの価値を以下の観点から説明している:

50ターンに渡る会話であっても、KV Cacheは線形に成長するが、Prefill計算はユーザーの最新メッセージ分だけでよい。ツール呼び出し中にキャッシュをCPU RAMへ移動するコストは、キャッシュを破棄して50ターン分を再Prefillするコストと比べれば極めて安い。


GKEデプロイメントマニフェストの要点

記事ではPrefillノードとDecodeノードそれぞれのKubernetesマニフェストを掲載している。インフラエンジニアが実際に使えるベースラインとして設計されており、以下の点が特徴的だ。

Prefillノードマニフェスト抜粋:

args:
  - "--model=meta-llama/Llama-3.1-70B-Instruct"
  - "--gpu-memory-utilization=0.95"
  - "--kv-transfer-config={'kv_role':'kv_producer','kv_connector':'PyNcclConnector'}"
annotations:
  networking.gke.io/interfaces: |
    [
      {"interfaceName":"eth0","network":"default"},
      {"interfaceName":"eth1","network":"gke-accelerator-net-0"},
      {"interfaceName":"eth2","network":"gke-accelerator-net-1"}
    ]
  • マルチNICアノテーション:KV Cacheのストリーミングにはgke-accelerator-net用の追加NICが必要。デフォルトのeth0だけでは帯域が不足し、KV転送がボトルネックになる
  • Tolerations:GKEはGPU/TPUノードを自動Taintするため、nvidia.com/gpucloud.google.com/gke-acceleratorの両Tolerationが必須。どちらか片方だけでは対象ノードプールへのスケジューリングが通らない
  • Shared Memory(/dev/shm:vLLMのPagedAttentionがIPCに使うため、emptyDirMemory媒体の共有メモリボリュームが必要。デフォルトのtmpfsサイズ制限に引っかかるケースを防ぐ設定だ
  • NodeAffinitygpu-prefill-poolノードプールへの明示的なバインドが必要。PrefillとDecodeが混在スケジューリングされると、フェーズ分離の効果が失われる

記事は「これらはベースラインであり、モデルサイズ・コンテキストウィンドウ要件・エージェントユースケースに応じて負荷試験とチューニングが必須」と明示している。


まとめ

このアーキテクチャが解決する問題を一言でまとめると、「エージェントの待機時間をGPUリソースの無駄にしない」ことだ。Prefill/Decode分離によりGPU特性に合わせたハードウェア選定が可能になり、vLLMのKV Cache CPU RAMオフロードにより待機中のエージェントがVRAMを占有しなくなる。llm-dのEPPがセッション単位のルーティングを保証することで、これらが有機的に機能する。

詳細はA Deep Dive into High-Efficiency Agentic Serving on GKE with vLLM and llm-dを参照していただきたい。