GPU Profilingとは?LLM推論の遅さをtimelineとkernelから切り分ける
GPU utilizationが90%でも、LLM推論が効率よく動いているとは限らない。短いkernelが細切れに並び、その間をCPU待ちが埋めている場合もあれば、一つのkernelがHBM bandwidthへ張り付いている場合もある。
GPU Profilingは、遅いという結果をCPU、GPU、memory、通信、schedulerのどこで時間を使ったかへ分解する作業である。LLM servingでは、まずrequest全体のtimelineを見てから、支配的なkernelを詳しく調べる順序が重要になる。
metric監視とprofilingは役割が違う
常時監視ではGPU utilization、memory使用量、power、temperature、TTFT、ITLなどを集める。異常の発見には向くが、なぜ遅いかを一つの平均値だけで説明できない。
profilingでは短い時間窓のeventとhardware counterを詳しく取得する。kernel名、CUDA API、memory copy、stream、CPU thread、collectiveの順序まで追える一方、収集overheadとdata量が増える。
常時すべてをprofileせず、監視で異常を検知し、再現条件を絞って短いtraceを取る。
最初にNsight Systemsで全体を見る
Nsight Systemsはsystem-wideなtimeline分析に向く。NVIDIAの公式資料では、CUDA Runtime・Driver API、GPU kernel、Host-to-DeviceやDevice-to-Hostのmemory copy、CUDA contextとstreamなどをtimelineへ表示できる。
LLM推論では次を確認する。
- CPU threadがtokenizationやschedulerで止まっていないか
- CUDA API callからkernel開始まで長いgapがないか
- GPU上に空白時間があるか
- PrefillとDecodeのkernel列がどう並ぶか
- memory copyとcomputeがoverlapしているか
- 複数streamが同時に進んでいるか
- NCCL collectiveでrankが待っていないか
- CUDA Graph replayが意図した範囲を覆っているか
「GPUが遅い」と思っていた問題が、CPU queue、lock、network、request batchingにあることも多い。kernel counterを集める前に、時間の流れを確認する。
次にNsight Computeでkernelを見る
Nsight Computeは個々のCUDA kernelの詳細分析に向く。compute pipeline、memory workload、cache、occupancy、warp state、instructionなどのmetricを収集できる。
timelineで総時間の大きいkernel、頻繁に呼ばれるkernel、version更新後に遅くなったkernelを選んで調べる。全kernelへ最初からfull metric setを取ると、実行時間とreportが大きくなり、調査対象もぼやける。
確認の入口は次の通りである。
- Speed of Lightでcomputeとmemory resourceの利用を見る
- Rooflineでarithmetic intensityと達成性能を見る
- Memory Workload AnalysisでL1、L2、DRAM trafficを見る
- Occupancyでregister、shared memory、block sizeの制約を見る
- Warp Stateでissueできない理由を見る
- Source・SASS対応で高costなinstructionを特定する
一つのmetricが高いことを、すぐに悪い状態と解釈しない。高いutilizationはresourceを有効利用している場合も、bottleneckへ張り付いている場合もある。
NVTXでrequestの意味をtimelineへ載せる
kernel名だけでは、それがどのrequest、model phase、scheduler iterationに属するか分かりにくい。NVTXのmarkerとrangeを入れると、applicationの意味をNsight Systemsのtimelineへ重ねられる。
LLM servingでは次の範囲をannotateすると読みやすい。
- request受付、queue待ち、admission
- tokenization
- Prefill
- Decode iteration
- samplingとstop判定
- KV Cache allocate、swap、recompute
- model・adapter切替
- NCCL collective
- response stream送信
request IDそのものをprofileへ残す場合は、個人情報やprompt内容を含めない。匿名化したtrace ID、length bucket、tenant classなど必要最小限にする。
TTFTを段階へ分ける
TTFTはrequest受付から最初のtokenまでの時間である。長くてもGPU Prefillが遅いとは限らない。
NVTX rangeとrequest traceを合わせ、各段階の開始と終了を残す。GPU timelineではPrefill kernelが短いのにTTFTが長ければ、queueやCPU処理を疑う。
一方、Prefill kernelが長い場合はprompt長、batch、attention backend、GEMM shape、Tensor Parallel communicationへ分解する。
ITLは一tokenの平均だけで見ない
Inter-Token Latencyは生成token間の時間である。average ITLが同じでも、定期的なstallがあれば利用者には引っかかりとして見える。
Decode iterationごとのNVTX rangeを置き、batch構成、active sequence、KV Cache使用量、kernel列、collective時間を対応させる。
stallの候補には次がある。
- 長いPrefillがDecodeへ割り込む
- CPU schedulerが次iterationをsubmitできない
- KV Cache allocationやoffloadが同期する
- 一つのrankがcollectiveへ遅れて到着する
- graph bucket missでeager executionやcompileが走る
- request終了処理やsamplingがCPUで詰まる
percentileだけでなく、遅いtokenのtimelineを直接見る。
Rooflineを実測kernelへ適用する
Nsight Computeはkernelのachieved performanceとarithmetic intensityをRoofline chartへ表示できる。前稿で説明した理論modelを、実行したkernelのcounterで確認する位置付けである。
achieved pointがmemory bandwidthの斜めの境界に近ければ、data reuse、fusion、layout、quantizationを検討する。compute roofに近ければ、Tensor Core、precision、tiling、並列化を検討する。
どちらのroofからも遠い場合は、occupancy、dependency、branch、small grid、launch gapなど別の制約を調べる。
NVIDIAの資料は、Tensor Core kernelでは対応するTensor rooflineを選び、clockやoperation counter、SASSも確認するよう案内している。異なるprecisionのroofを混ぜない。
occupancyは高ければよいわけではない
occupancyは、SMが同時に保持できるactive warp数がhardware上限に対してどれくらいかを表す。registerやshared memoryを多く使うkernelはoccupancyが下がる。
低occupancyでも、一つのwarpが十分なinstruction-level parallelismを持ち、Tensor Coreを効率よく使えば速い場合がある。逆に高occupancyでもmemory待ちやdependencyで進まなければ遅い。
occupancyは原因候補を絞るmetricとして使う。block sizeを変えた結果、register spillやmemory trafficが増えていないか合わせて見る。
warp stall reasonを単独で追わない
Nsight ComputeのWarp Stateは、warpが次のinstructionをissueできなかった理由を示す。memory dependency、execution dependency、barrierなどのstallを調べられる。
ただしNVIDIAの資料は、stall reasonが必ずしも性能へ影響しているとは限らず、schedulerが毎cycle issueできていない場合に注目するよう説明している。
割合が最大のstallを機械的に消すのではなく、kernelのcritical path、achieved throughput、変更前後の実時間と結び付ける。
memory分析では階層を見る
GPU memoryはHBMだけではない。register、shared memory、L1、L2、device memoryがあり、同じbyteでもどこから供給されたかでcostが違う。
Memory Workload Analysisではdata transfer、cache hit、memory requestなどを確認できる。Rooflineのhierarchical viewではL1、L2、DRAMの異なるceilingを見ることもできる。
LLM kernelでは次を調べる。
- weightとKV Cacheがどのaccess patternで読まれるか
- coalesced accessになっているか
- L2 hit率がbatch・contextでどう変わるか
- shared memory bank conflictがあるか
- register spillでlocal memoryが増えていないか
- quantizationのunpackが余分なtemporary trafficを作っていないか
HBM bandwidthが低いからmemory-boundではない、と即断しない。latency、cache、small transaction、serializationで帯域を使い切れない場合もある。
distributed inferenceは全rankを対応付ける
Tensor、Context、Expert Parallelismでは一つのrankだけをprofileしても、全体の待ちを説明できない。rankごとのcompute終了時刻とcollective開始時刻を並べる。
一つのrankが遅い場合、原因はそのGPUのkernel、temperature、Topology、NUMA、NIC、load imbalanceのいずれかである。collective時間が長く見えても、実際には早いrankが遅いrankを待っているだけの場合がある。
traceにはrank、GPU UUID、PCI bus ID、node、communicator、sequence numberを残す。別hostのclock差も考慮し、可能なら同期したtimestampを使う。
profiling overheadを測る
Nsight Computeはmetric収集のためkernelをreplayする場合があり、application実行時間へoverheadを加える。NVIDIA公式資料も、performance data収集がruntime overheadを伴うと明記している。
profile中のlatencyをそのまま本番性能として報告しない。手順は次のように分ける。
- profilerなしでbaselineを測る
- Nsight Systemsで短いtimelineを取る
- 調査対象kernelを絞る
- Nsight Computeで必要なsectionだけ集める
- 修正後にprofilerなしで効果を再測定する
full metric collectionをproduction trafficへ無制限に実行しない。isolated nodeや再現環境で行う。
再現可能なprofile条件を残す
traceだけ保存しても、条件が違えば比較できない。少なくとも次を記録する。
- model、revision、quantization、adapter
- engine、framework、kernel backendのversion
- GPU型番、MIG profile、clock、power limit
- driver、CUDA、NCCL
- batch、prompt長、output長、context長
- request arrival patternとconcurrency
- Prefix Cache、KV Cache、CUDA Graphのwarm状態
- Tensor・Pipeline・Context・Expert Parallel設定
- profiler option、収集時間、対象range
prompt本文を保存できない場合は、token lengthと処理経路を再現するsynthetic inputを用意する。
profile dataも機密情報である
traceにはprocess名、path、hostname、thread名、environment、annotationなどが含まれうる。promptやtenant名をNVTX labelへ直接入れない。
profile fileの保存期間、access権限、共有方法を決める。外部supportへ渡す場合は、path、user名、request metadata、model名などの機密性を確認する。
調査の順序
- TTFT、ITL、throughputのどれが悪化したか決める
- input length、batch、concurrencyを固定して再現する
- profilerなしのbaselineを取る
- NVTXでrequest phaseを区切る
- Nsight SystemsでCPU・GPU・通信timelineを見る
- 支配的または異常なkernelを選ぶ
- Nsight ComputeでRoofline、memory、occupancy、warp stateを見る
- 仮説を一つだけ変更して再測定する
- output品質とerror率を確認する
- profilerなしの本番相当負荷で効果を確認する
監視するmetric
- TTFTのqueue、tokenize、Prefill、sampling別時間
- iteration別ITLとstall位置
- CPUからGPUへのlaunch gap
- kernel数、kernel duration、CUDA API時間
- HBM・L2・L1 trafficとcache hit率
- achieved FLOPS、Tensor Core、SM utilization
- occupancy、register、shared memory、spill
- memory copyとcomputeのoverlap
- rank別compute・collective時間
- profile overheadとtrace drop
- version別のbaseline差分
timelineからkernelへ降りる
GPU Profilingで最初に見るべきものは、最も細かなcounterではない。Nsight Systemsでrequest、CPU、CUDA API、kernel、memory copy、collectiveの順序を見て、時間を失っている範囲を決める。
次にNsight Computeで対象kernelのcompute、memory、occupancy、warp state、Rooflineを調べる。NVTXを使えばPrefillやDecode iterationの意味をtimelineへ載せられる。
profiling自体にはoverheadがある。短いrangeへ絞り、変更後の効果はprofilerなしで確認する。GPU utilizationという一つの数字から、実際に直せるbottleneckへ降りていくことが重要である。