2. 性能分析工具链概览:Linux perf、NVIDIA Nsight Systems、Nsight Compute、PyTorch Profiler、TensorBoard
做算子性能优化,说白了就是跟时间赛跑。你写了个算子,跑起来比预期慢了三倍,这时候怎么办?瞎猜?拍脑袋?我刚开始干这行的时候就这么干过,结果折腾了两天,发现瓶颈在一个完全没想到的地方。
后来我学乖了。性能优化,第一步不是动手改代码,而是先搞清楚——时间到底花在哪了。这就需要一套趁手的工具链。今天我就把平时最常用的几把「手术刀」给你捋一遍。
核心观点:没有万能工具,只有合适的场景。每个工具解决一个层面的问题,组合使用才能看清全貌。
2.1 Linux perf:系统级的「听诊器」
perf 是 Linux 内核自带的性能剖析工具。它不依赖任何第三方库,装上 Linux 就能用。我个人习惯把它当作第一道防线——先看看算子在整个系统层面有没有异常。
举个例子,你怀疑算子访存有瓶颈,可以这样采样:
# 采样 CPU 周期和缓存未命中
perf stat -e cycles,instructions,cache-misses ./my_operator
# 或者用火焰图看热点函数
perf record -g ./my_operator
perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg
perf 能告诉你什么?CPU 利用率、缓存命中率、分支预测错误率、上下文切换次数……这些都是硬件级别的指标。我在项目中遇到过一个问题:算子本身计算量不大,但跑起来 CPU 占用率只有 30%。用 perf 一查,发现大量时间花在了 page fault 上——原来是内存分配策略有问题。
小技巧:perf 的 -g 参数会记录调用栈,配合火焰图看,热点一目了然。但注意采样频率别太高,否则会影响程序运行。
2.2 NVIDIA Nsight Systems:GPU 层面的「时间轴」
如果你的算子跑在 GPU 上,perf 就有点力不从心了。它看不到 GPU 内部发生了什么。这时候就该 Nsight Systems 上场了。
Nsight Systems 说白了就是一个时间轴分析器。它能告诉你:CPU 在干什么、GPU 在干什么、数据传输花了多久、Kernel 启动有没有延迟。我建议你把它当作 GPU 性能分析的起点。
# 命令行采集
nsys profile -o my_profile -t cuda,nvtx ./my_operator
# 然后打开 GUI 看时间轴
nsys-ui my_profile.qdrep
你想想看,有时候你觉得是 Kernel 跑得慢,结果一看时间轴,发现大部分时间花在了 cudaMemcpy 上。嗯,这种情况我遇到过不下十次了。
注意:Nsight Systems 的采样开销很小,但如果你开启了全量 trace,数据量会非常大。建议先做粗粒度分析,再针对热点做细粒度采样。
2.3 Nsight Compute:Kernel 内部的「显微镜」
Nsight Systems 告诉你「哪个 Kernel 慢」,Nsight Compute 则告诉你「这个 Kernel 为什么慢」。它是针对单个 CUDA Kernel 的深度分析工具。
它能给出什么?占用率、内存带宽利用率、计算吞吐量、指令混合比、bank conflict……这些都是 GPU 微架构级别的指标。我曾经用它分析过一个矩阵乘算子,发现性能只有理论峰值的 20%。Nsight Compute 告诉我:L1 缓存命中率只有 30%,大量时间花在了等待数据上。
# 分析单个 Kernel
ncu --kernel-name my_kernel -o my_kernel_report ./my_operator
# 或者直接看关键指标
ncu --set full --kernel-name my_kernel ./my_operator
核心指标:关注 Memory Throughput 和 Compute Throughput 的比值。如果 Memory 远低于理论值,说明访存有瓶颈;如果 Compute 远低于理论值,说明计算单元没喂饱。
2.4 PyTorch Profiler:框架层面的「探针」
如果你用 PyTorch 开发算子,PyTorch Profiler 是最直接的入口。它不需要额外安装,PyTorch 自带。它能自动捕获算子的执行时间、显存分配、CUDA 事件等。
import torch.profiler as profiler
with profiler.profile(
activities=[profiler.ProfilerActivity.CPU, profiler.ProfilerActivity.CUDA],
record_shapes=True,
with_stack=True
) as prof:
output = my_operator(input_tensor)
print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=10))
我个人习惯先用 PyTorch Profiler 快速扫一遍,看看哪个算子最耗时。然后针对那个算子,再用 Nsight Compute 做深度分析。这样效率最高。
避坑指南:我曾经因为没加 record_shapes=True,导致看不到张量形状信息,分析半天找不到问题。记得加上这个参数,它能帮你定位到具体的输入尺寸。
2.5 TensorBoard:可视化的「仪表盘」
TensorBoard 不只是一个训练可视化工具。它也能展示性能数据。PyTorch Profiler 可以直接导出 TensorBoard 格式,让你在浏览器里交互式地查看时间轴和算子统计。
# 导出为 TensorBoard 格式
prof.export_chrome_trace("trace.json")
# 或者直接在 TensorBoard 里看
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter("log_dir")
writer.add_profiler(prof)
writer.close()
TensorBoard 的好处是直观。你可以拖拽时间轴,放大缩小,看每个算子的调用关系。我经常用它来跟同事沟通——一张截图比说半天话管用多了。
2.6 工具链的选择策略
说了这么多,你可能会问:到底该用哪个?我的建议是分层使用:
| 层次 | 工具 | 解决什么问题 |
|---|---|---|
| 系统级 | Linux perf | CPU 利用率、缓存、内存、IO 瓶颈 |
| GPU 级 | Nsight Systems | CPU-GPU 交互、数据传输、Kernel 启动 |
| Kernel 级 | Nsight Compute | GPU 微架构瓶颈、占用率、访存模式 |
| 框架级 | PyTorch Profiler | 算子级耗时、显存分配、调用栈 |
| 可视化 | TensorBoard | 交互式分析、团队沟通 |
你想想看,如果一上来就用 Nsight Compute 去分析一个 Kernel,结果发现瓶颈根本不在 GPU 上,那不就白费功夫了?所以我的习惯是:从外到内,逐层深入。先用 perf 看系统,再用 Nsight Systems 看 GPU 整体,最后用 Nsight Compute 看具体 Kernel。
总结一句话:perf 管系统,Nsight Systems 管全局,Nsight Compute 管细节,PyTorch Profiler 管框架,TensorBoard 管展示。五把刀各司其职,组合起来就是一套完整的性能分析流水线。