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 Gatewayとllm-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/gpuとcloud.google.com/gke-acceleratorの両Tolerationが必須。どちらか片方だけでは対象ノードプールへのスケジューリングが通らない - Shared Memory(
/dev/shm):vLLMのPagedAttentionがIPCに使うため、emptyDirでMemory媒体の共有メモリボリュームが必要。デフォルトのtmpfsサイズ制限に引っかかるケースを防ぐ設定だ - NodeAffinity:
gpu-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を参照していただきたい。