8月26日、NVIDIAが「Restore LLM Inference Capacity in Seconds with Shadow Engine Recovery in NVIDIA Dynamo」と題した記事を公開した。LLM推論エンジンのプロセス障害からの復旧時間を283秒からわずか7.3秒に短縮する「Shadow Engine Recovery」の仕組みと実測結果を紹介したものだ。復旧時間の約39倍高速化という数字が示す通り、本番運用における可用性の考え方を大きく変えうる技術となっている。
LLM本番運用における可用性の悩みどころは、エンジンプロセスが落ちたときのコールドリスタートのコストだ。大規模モデルでは、HBMへの重みロード・カーネルコンパイル・CUDAグラフキャプチャを経て、復旧まで数分を要する。その間、生き残ったワーカーがすべてのトラフィックを一手に受けることになり、遅延が爆発的に増大する。
NVIDIA Dynamoのプレビュー機能「Shadow Engine Recovery」は、この問題を正面から解決する。
283秒 → 7.3秒:実測値が示す効果
NVIDIAは、2ワーカー構成でGLM-4.5(NVFP4量子化)をNVIDIA B200上で動かし、片方のワーカーをSIGKILLで強制終了する障害試験を実施した。なお本文中のモデル名・バージョン番号は元記事の記載に基づく。設定はTP=8、最大コンテキスト200K、FP8 KVキャッシュ(※1)で、合成負荷として入力32,000トークン・出力1,000トークンのリクエストを0.7 req/sで流した。
※1 NVFP4量子化:モデルの重みを4ビット浮動小数点形式で表現し、精度を保ちながらメモリ使用量と演算コストを削減する量子化手法。FP8 KVキャッシュ:Transformerのアテンション計算で使うKey-Valueキャッシュを8ビット浮動小数点で保持し、HBMの消費量を抑える技術。いずれもNVIDIA Hopperアーキテクチャ以降でハードウェアレベルの対応が進んでいる。
結果は以下のとおりだ。
| 指標 | コールドリスタート(ベースライン) | Shadow Engine Recovery |
|---|---|---|
| 2台目ワーカー復旧までの時間 | 283秒 | 7.3秒 |
| 障害後のTTFT p50 | 23,815 ms | 1,311 ms |
| 障害後のデコードレート p50 | 12 tok/s/user | 46 tok/s/user |
| 初回トークンまで5秒超のリクエスト数 | 399件中201件 | 398件中1件 |
| 20 tok/s/user未満のリクエスト数 | 399件中226件 | 398件中0件 |
復旧時間は約39倍高速化(障害検知1.7秒+シャドウ昇格5.6秒)。コールドリスタートでは半数以上のリクエストがSLA違反相当の状態に陥るのに対し、Shadow Engine Recoveryではほぼゼロに抑えられている。
なぜコールドリスタートは遅いのか
復旧が遅い原因は2つある。
- 重みがエンジンプロセスに紐づいている。 GPUメモリはCUDAコンテキストを介してプロセスに束縛されており、プロセス終了と同時にドライバがすべてのリソースを解放する。後継プロセスは重みを一から再ロードしなければならない。
- 初期化状態の一部は引き継げない。 NCCLや
torch.distributedのコミュニケーター、CUDAグラフはプロセス固有の状態であり、別プロセスに渡せない。新プロセスが毎回再作成する必要がある。
Shadow Engine Recoveryはこの2つの問題にそれぞれ対処する。
仕組み:GPU Memory Service + シャドウエンジン
GPU Memory Service(GMS):プロセスをまたぐ重み共有
GMSは、重みなど特定のメモリ領域をエンジンプロセスから独立して管理するGPUごとのサイドカープロセスだ。
内部的にはCUDA Virtual Memory Management APIを使う。このAPIにより、物理GPUメモリの生存期間と仮想アドレスの生存期間を分離できる。物理アロケーションは参照カウントで管理されるため、いずれかのプロセスがマッピングを保持している限り、プロセスが死んでも物理ページは残存する。
これにより2つのことが実現する。
- 重みがエンジン障害をまたいで保持される。 障害エンジンのCUDAコンテキストが破棄されても、GMSの参照によって物理ページは残り、新しいエンジンが即座にマップできる。
- 複数エンジンが重みを共有できる。 シャドウエンジンが同じGPU上に同居しても、重みの追加コピーは発生しない。
vLLM・SGLang・NVIDIA TensorRT-LLMへの統合は、torch.cuda.CUDAPluggableAllocatorをカスタム実装することで実現している。エンジン内部からは通常のtorch.Tensorとして扱われ、起動時にフラグを一つ切り替えるだけで導入できる。
シャドウエンジン:起動済みのスタンバイ
シャドウエンジンは、アクティブエンジンと同じGPU上に同居する、完全に初期化済みだが待機中のエンジンプロセスだ。重み共有があるから初めて成立する構成で、なければ重みのコピーが2倍になってHBMが枯渇する。
シャドウエンジンは起動時に通常のエンジンと同じ初期化を完了させる。
- CUDAコンテキスト生成、CUDAグラフキャプチャ、NCCLコミュニケーター確立
- GMSへの接続と重みマッピングのインポート
初期化が完了したら「パーク状態」に入る。KVキャッシュのみ物理バッキングを持たない予約状態にしてメモリを節約し、ロック待ちでブロックする。
フェイルオーバー時のシーケンスはシンプルだ。
T₀ 定常: エンジンAがロックを保持し稼働中。エンジンBはロック待ちで休眠。
T₁ 障害: エンジンAのプロセスが終了。カーネルがロックを解放。
T₂ 切替: エンジンBがロック取得→重みをリマップ→KVキャッシュをマテリアライズ→ルーターに再登録。
T₃ 復旧: エンジンAのコンテナが再起動し、今度はシャドウとして待機。
ロック機構にはPOSIXのflockを使う。プロセスがクラッシュしても、カーネルがファイルディスクリプタをreapする際に自動でロックが解放される。デッドロック状態のプロセスに対してはKubernetesのlivenessプローブがSIGKILLを送り、同じパスを踏む。
await engine.initialize() # 重みロード、torch.compile、CUDAグラフキャプチャ
await engine.sleep()
lock = FlockFailoverLock(lock_path)
await lock.acquire(engine_id=engine.id) # ロック取得まで待機
await engine.wake()
現在の制限事項
現時点のプレビューには以下の制約がある。
- 対象障害はエンジンプロセス障害のみ。 ハードウェア障害・ノード障害・マルチノード障害は従来の再スケジューリングに依存する。
- KubernetesのDynamic Resource Allocation(DRA)が必要。 Dynamic Resource Allocation(DRA)が有効化されており、NVIDIA GPU DRAドライバがインストールされていることが前提条件だ。元記事に記載されているKubernetesバージョン要件については、デプロイ時に公式ドキュメントで最新情報を確認されたい。
- KVキャッシュの引き継ぎは未対応。 昇格直後はKVキャッシュが空のため、切替後に一時的なTTFTの増加が生じる。プレフィックスキャッシュのインデックスとキャッシュメモリ自体の引き継ぎは開発中だ。
- 主サポートバックエンドはvLLM。
試験的に使う場合は、Kubernetesクイックスタートでデプロイを立ち上げた後、vLLMフェイルオーバーのサンプルマニフェストを参照するとよい。
背景:推論サービング基盤における位置づけ
※編集部の考察
LLM推論サービングのOSSエコシステムでは、vLLMやSGLangがデファクトとして広く使われており、NVIDIA Dynamoはこれらを束ねるオーケストレーション層として位置づけられている。障害時の高速復旧という課題に対しては、Kubernetes標準のPod再スケジューリングやロードバランサーレベルのフェイルオーバーが一般的な対処策だが、いずれもモデルのコールドロード時間を本質的に解決するものではない。Shadow Engine Recoveryが「プロセス内で解く」アプローチを取るのに対し、マルチノード構成でのホットスタンバイをクラスタ全体で管理するアプローチとはトレードオフが異なり、GPUリソース効率と復旧速度のバランスをどこで取るかが運用設計の焦点になる。
詳細はRestore LLM Inference Capacity in Seconds with Shadow Engine Recovery in NVIDIA Dynamoを参照していただきたい。