NVIDIA NIMを「ブラックボックスの推論API」として扱うのは楽だが、本番運用に載せる前に内部アーキテクチャを把握しておくと、障害切り分けと性能チューニングが段違いに速くなる。本稿ではNGCで配布される公開仕様に基づいて、NIMコンテナの構造層を順に見ていく。
コンテナ層構成
NIMのOCIイメージは大まかに4層で構成される。最下層はCUDAランタイムとドライバ互換shim、その上にTriton Inference Server 24.xx系のバイナリ、さらにTensorRT-LLMバックエンド(`libtriton_tensorrtllm.so`)、最上位にモデル固有のengine pllansとトークナイザ、および`api_server.py`相当のFastAPIラッパーが乗る。エントリポイントは`/opt/nim/start-server`系のスクリプトで、`NIM_MODEL_PROFILE`環境変数を読んでGPU SMアーキテクチャ(Hopper、Ada、Ampere)に合致するprebuilt engineを選択する。
リクエストパスの実際
`POST /v1/chat/completions`が着弾すると、OpenAI互換シムがリクエストをTritonの`ensemble`モデルにルーティングする。ensembleは preprocessing(トークナイズ)→ TensorRT-LLM推論 → postprocessing(detokenize)の3段構成だ。TensorRT-LLMはin-flight batching(continuous batching)を実装しており、到着済みリクエストのKVキャッシュを保持したまま新規リクエストを同一forward passに合流させる。これがvLLMのPagedAttentionに相当する工夫で、VRAM断片化を抑える役割を果たす。
CUDA Graphsとカーネル最適化
デコードステップ(1トークン生成あたり)は呼び出しオーバーヘッドが支配的になりやすい。NIMはCUDA Graphsを使って、同一形状のforward passをキャプチャし再生する。`NIM_ENABLE_CUDA_GRAPHS=1`(デフォルト有効)で、バッチサイズやシーケンス長の主要コンビネーションが事前キャプチャされ、kernel launch latencyを削減する。さらにTensorRT-LLMはFP8(Hopper/H100)、INT4 AWQ、SmoothQuantなどの量子化kernelをengine plan内にコンパイル済みで持つ。
NGCカタログとモデルプロファイル
NGCから`nvcr.io/nim/meta/llama-3.1-70b-instruct:1.x.x`のようにpullすると、内部にはH100×2、H100×4、H100×8、L40S×4などGPU構成ごとのengine planが同梱されている。`list-model-profiles`サブコマンドで利用可能なプロファイル一覧が取れ、起動時に検出されたGPUに最適なものが自動選択される。オフラインでengine再ビルドを避けられる点が、vLLMに対する運用上の大きな差分だ。
監視ポイント
Tritonは`:8002/metrics`でPrometheus形式のメトリクスを出す。`nv_inference_queue_duration_us`と`nv_inference_compute_infer_duration_us`の比率を見れば、キュー滞留か計算律速かを即判定できる。DCGMと組み合わせてSMアクティビティとHBM帯域を併読するのが定石だ。