vLLMサーバーが、nvidia-smi上で8分間も97%の利用率を叫んでいる。それと同時に、トークンスループットは奈落の底へ。驚くべきことに、両方の記述は真実である。そこにこそ、デジタルの怪しい儲け話があるのだ。NVIDIAのどこにでもあるGPU利用率メトリクスは、生産的な作業量を測るものではない。いや。それは単なるデューティサイクルのカウンターに過ぎない。GPU上で『何か』が動いていたかどうかを教えてくれるが、それが『実行する価値のあるもの』だったかどうかは教えてくれない。
我々はこの不条理に、vLLMのレイテンシスパイクの内部再現テスト中に遭遇した。ハードウェアはTensorDock RTX 4090。ソフトウェアはvLLM 0.18.0、Qwen2.5-0.5B-Instructが黙々と動作している。8分間、ダッシュボードは健全そのものだった。Nvidia-smiは92-99%の間を揺れ動き、平均で安定した97%を示していた。ファンは唸り、メモリは安定し、電力消費は320Wを維持していた。すべて順調、だろ?
間違っている。
原因は、地味なリクエストにあった。n_completions=8とlogprobs=20の組み合わせだ。この「化け物」は、各デコードステップを8つの別々のシーケンスに拡張し、それぞれがフルボキャブラリのソフトマックスを要求した。150,000トークン/拡張という規模だ。これらの巨大な処理のそれぞれが、事実上、同時にスケジュールされた他のすべてのリクエストを9〜11秒間人質に取っていた。GPUは確かに忙しかったが、それはユーザーの目に見えないゴミを処理するのに忙しかったのだ。スループット?崩壊した。
これは、一部の限られたシナリオの話ではない。これは、唯一の診断ツールが、ただの立派なストップウォッチである場合に予測される結果だ。
NVIDIA自身のドキュメントは、GPU-Utilを「過去のサンプル期間中に、1つ以上のカーネルがGPU上で実行されていた時間の割合」と親切に定義している。デューティサイクル。それだけだ。カーネルが効率的か、ボトルネックになっているか、あるいは他の操作を積極的に妨げているかについては、全く洞察を与えてくれない。ジムにいた時間をどれだけ自慢しても、ウェイトリフティングをしていたのか、それともただ天井を眺めていただけなのかを言わないのと同じようなものだ。
NVIDIAのより高度なツールキットであるDCGMは、SM_ACTIVEやMEM_COPY_UTILといったカウンターで、より細かい粒度を提供してくれる。これらは多少役立つ。しかし、カーネルが100ミリ秒の間、そのピークポテンシャルのわずか5%で実行されていたとしても、その間は100%のSM_ACTIVEとして記録される。ダッシュボードは何も知らないままなのだ。
我々は、様々なワークロードでこのパターンを分析してきた。高利用率、急落するスループット、そしてマジック8ボールでもおかしくないようなダッシュボード。共通点は?根本原因はもっと深いところにある。
いつもの容疑者たち:なぜGPUは自分が忙しいと思っているのか
-
Prefill/Decode タンゴ: vLLM、SGLang、TGIのようなフレームワークは、Prefill(入力処理)とDecode(出力生成)を同じハードウェアでバッチ処理しようとする。PrefillがDecodeよりも指数関数的に多くの計算を必要とする場合(長いコンテキストではよくあるシナリオ)、単一の長コンテキストリクエストが、他のすべての短いリクエストの交通渋滞となる。Prefillカーネルがシェーダーコアを占有するため、GPUは100%の
SM_ACTIVEを維持する。その間、待機中のリクエストのDecodeレイテンシは無限大に伸びる。 -
分散学習のグリッドロック: 4GPUのall-reduce操作を想像してみよう。もし1つのGPUが遅延すると、他は待つことになる。待機中のGPUは、待機をオーケストレーションしているカーネル自体がカーネルであるため、100%の利用率を示す。全体のスループットは、効率的なGPUではなく、最も遅いランクによって決定される。
-
Dataloader デッドロック: PyTorchの
DataLoaderは、メインプロセスでインデックス順列を実行する際、シングルスレッドのボトルネックになる可能性がある。GPUは律儀に同じフォワードカーネルを繰り返し実行するが、次のバッチの起動はcudaStreamSyncによってブロックされる。カーネルは叫ぶが、次のジョブは車庫の入り口で立ち往生している。 -
CPUコアの混沌: vLLMのエンジンループはシングルスレッドだ。OSのコンテキストスイッチ(隣接コアのカーネル処理、厄介な割り込み、適切に管理されていないcgroup)が、
cudaLaunchKernelの呼び出しを停止させることがある。p99のcudaLaunchKernel時間が13.1ms(典型的な16.7usのp50から飛躍的に増加)に達したのを見たことがあるが、これはすべてスケジューラの不具合によるものだ。GPUは、停止する前にアクティブだったカーネルを実行し続けているため、利用率は正常に見える。 -
メモリ帯域幅のメルトダウン: SMが処理できるよりも速くシステムにデータを送り込むカーネルは、100%の
SM_ACTIVEを報告するだろう。しかし、本当の制約は?DRAM帯域幅だ。利用率は陽動作戦; ボトルネックはメモリのスループットだ。
これらのシナリオのそれぞれにおいて、症状は憂鬱なほどお馴染みだ:高利用率、低スループット。しかし、原因は、その下にあるレイヤーに隠れている。
真のボトルネックを見つける
では、どうすればレイヤーを剥がすことができるか?集計された利用率を忘れてほしい。厳しい質問をしよう。「GPUは何に実際に待っていたのか、秒ごとに?」
これに答えるには、同じホスト上の複数のソースからのデータを、タイムスタンプで同期させて相関させる必要がある。
- CUDA Runtime API呼び出し:
libcudart.so上のuprobeを使用して、cudaLaunchKernel、cudaMemcpyAsync、cudaStreamSynchronize、cudaDeviceSynchronizeなどのイベントを監視する。 - CUDA Driver API呼び出し:
libcuda.so上のuprobeを使用して、cuLaunchKernelおよび関連するドライバーレベルの操作を追跡する。 - カーネル実行トレース: 実際に実行されているカーネルに深く潜る。CUPTIやNVIDIA Nsightのようなツールは、カーネル実行時間、占有率、カーネル自体のリソース利用率の詳細なプロファイルを提供できる。
- ホストサイドアクティビティ: CPUを無視するな。GPUドライバーとのやり取りに関連するCPUスレッドアクティビティ、コンテキストスイッチ、システムコールを監視する。
- メモリ帯域幅: DRAM帯域幅の使用量を直接測定する。これはしばしばDCGMまたは特定のプロファイリングツールを通じて公開される。
これらのスレッドを織り交ぜることで、生産的な計算を churning( churning は「懸命にこなす」といったニュアンス。ここでは「処理する」と訳す)しているGPUと、単に車輪を空転させているだけのGPUとの違いを、ついに見ることができるようになる。97%の利用率が都合よく隠蔽してくれるその区別だ。
これは単なる理論上の問題ではない。ハイパフォーマンスコンピューティングにおける、しぶとい、イライラする現実なのだ。AIワークロードがますます複雑になるにつれて、単純な利用率カウンターを超えて見抜く能力は、単に有益なだけでなく、絶対的に不可欠になるだろう。