核心概念

torch.profiler 是 PyTorch 內建的效能分析工具,輸出兩種互補的分析產物:

  • Profiler Table:統計摘要,回答「哪個操作最耗時」
  • Profiler Trace:時間軸視圖,回答「操作在什麼時間點發生、為什麼發生」

兩者缺一不可。Table 找熱點,Trace 找根因。

Overhead-Bound vs Compute-Bound

這是理解 GPU 效能的根本對立:

狀態 現象 問題
Overhead-bound CPU dispatch 時間 >> GPU 計算時間 GPU 大部分時間閒置
Compute-bound GPU 計算是瓶頸 理想狀態——GPU 持續工作

實測:64×64 矩陣 → CPU time 2.3ms,GPU time 23μs(GPU 閒置 98%)。改成 4096×4096 → CPU 4.9ms / GPU 4.5ms,進入 compute-bound。

基礎設定

with torch.profiler.profile(
    activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
    schedule=torch.profiler.schedule(wait=1, warmup=1, active=3, repeat=1),
) as prof:
    for _ in range(5):
        step(); prof.step()

prof.export_chrome_trace("trace.json")
  • wait:跳過不穩定的初始步驟
  • warmup:不記錄的預熱(讓 GPU clock 進入穩態)
  • active:實際錄製的步驟數

關鍵要點

冷啟動延遲不可忽略:第一個 profiling step 比後續慢 ~228μs,含 workspace 分配、cuBLAS 啟發式選擇、延遲模組載入。正式 profile 前必須加暖機迭代。

CPU-GPU Lane 時間差是正常的:Trace 中 CPU 和 GPU 道通常有 2-5ms 偏移,代表 kernel 提交到實際執行的延遲,不是 bug。

cudaOccupancyMaxActiveBlocksPerMultiprocessor 的語意:此查詢只出現在 GEMM、卷積等重量級 kernel 前,elementwise 操作不會觸發。cuBLAS 有數百種 kernel 變體,需要查詢硬體容量才能選出最優配置。

Kernel 時間的自然波動:相同操作在不同迭代時間差異大,原因是 GPU clock 受熱量與電源管理影響。Table 平均值會遮蓋這種現象,Trace 才看得出來。

torch.compile 的融合發生在 dispatcher 層,不在 kernel 層:Eager mode aten::add + aten::mm → Compiled mode aten::addmm 單一 dispatch。GPU 仍然跑同一個 cuBLAS GEMM kernel,差別是 bias addition 被折入 epilogue,同時觸發一次 Device-to-Device memcpy 複製 bias。

實務應用

快速診斷清單

現象 含義 行動
CPU time (ms) >> GPU time (μs) Overhead-bound 增加 batch size 或融合操作
第一個 ProfileStep 明顯更寬 冷啟動開銷 加 warmup 迭代
GEMM 前有 cudaOccupancyMaxActiveBlocksPerMultiprocessor 正在選擇 cuBLAS kernel 變體 正常,表示重量級 kernel
cudaDeviceSynchronize Profiler 刷新 buffer;GPU 還在工作 勿誤判為 idle
意外的 cudaMemcpyAsync bias 的 DtoD copy(addmm epilogue) torch.compile 預期行為

視覺化:將 .json 上傳至 Perfetto UI,鍵盤 WASD 移動,點擊 GPU kernel 可看暫存器用量與 block size。

與 Hugging Face Accelerate 整合時,可改用 ProfileKwargs 傳入,讓多 GPU 訓練迴圈自動管理 profiler 生命週期。

延伸觀點

Profiler 本身有效能代價:PerfTracker(arXiv 2025)指出 torch.profiler 每秒可產生 100+ MB trace 資料,大規模訓練下會拖慢 CUDA kernel 執行速度,且停止 profiling 後殘留影響仍存在。在生產環境做線上 profiling 時,需考慮取樣策略而非全程錄製。

記憶體 profiling 是獨立維度:Hugging Face Accelerate 文件指出,profile_memory=True 才會追蹤每個算子的記憶體分配量。純時間分析無法看到記憶體瓶頸(如大量 aten::empty 分配);複雜模型需同時看時間與記憶體兩份 Table。

Chrome Trace ≠ 所有分析的終點:GPU MODE Lecture 1 強調,torch.profiler 的 trace 只能看到 kernel launch 與 memory transfer,無法直接告訴你如何優化 kernel 本身。要從「看到瓶頸」到「知道怎麼修」,需要進一步用 Nsight Compute 分析 memory coalescing、occupancy 等硬體層指標。torch.profiler 是宏觀定位工具,Nsight Compute 是微觀調優工具,兩者互補。