3、Profiling工具入门:使用Nsight Systems、PyTorch Profiler、TensorBoard进行性能数据采集
做算子映射优化,最怕什么?
怕瞎猜。
你觉得自己写的算子挺快,结果一跑,GPU利用率不到30%。你怀疑是内存拷贝慢,折腾半天,最后发现是kernel launch太频繁。这种事儿我遇到过太多次了。所以,学会用Profiling工具,是性能优化的第一步。
今天咱们就聊聊三款最常用的工具:Nsight Systems、PyTorch Profiler、TensorBoard。它们各有侧重,配合起来用,基本能覆盖你日常排查的所有场景。
核心思路:先宏观定位瓶颈,再微观分析细节。Nsight Systems看全局,PyTorch Profiler看算子级,TensorBoard做可视化对比。
3.1 Nsight Systems:宏观视角的利器
Nsight Systems,说白了就是给GPU做CT扫描的。它能让你一眼看出整个程序的执行时间线,CPU在干嘛,GPU在干嘛,数据传输花了多久。
我个人的习惯是,拿到一个性能问题,第一件事就是跑一次Nsight Systems。为什么?因为它能快速告诉你瓶颈在哪——是计算不够快,还是数据搬来搬去太慢。
基本用法
# 命令行采集
nsys profile -o my_profile -t cuda,nvtx,osrt python train.py
# 关键参数说明
# -o: 输出文件名
# -t: 跟踪哪些事件(cuda: GPU事件, nvtx: 自定义标记, osrt: 系统调用)
# --gpu-metrics-device: 采集GPU硬件指标
跑完之后会生成一个 .nsys-rep 文件。用Nsight Systems GUI打开,你会看到类似这样的时间线:
- CPU活动:数据预处理、模型加载、Python解释器开销
- GPU活动:Kernel执行、内存拷贝、同步操作
- NVTX标记:你自己插入的代码段标记,方便定位
小技巧:在代码里加NVTX标记,能让你在时间线上快速定位到关键代码段。比如:
torch.cuda.nvtx.range_push("forward_pass")
output = model(input)
torch.cuda.nvtx.range_pop()
嗯,这里要注意一个坑。Nsight Systems默认只采集一小段时间,如果你的训练跑好几个小时,记得用 --duration 参数限制采集时长,不然文件会大到离谱。我曾经有一次没设限制,结果生成了20GB的文件,打开都费劲。
3.2 PyTorch Profiler:算子级别的显微镜
Nsight Systems告诉你「哪里慢」,PyTorch Profiler告诉你「哪个算子慢」。它直接集成在PyTorch里,用起来非常方便。
我个人觉得,PyTorch Profiler最牛的地方在于,它能给出每个算子的详细统计——调用次数、平均耗时、总耗时、输入输出形状。这些信息对于算子映射优化来说,简直是宝藏。
基本用法
import torch.profiler as profiler
with profiler.profile(
activities=[
profiler.ProfilerActivity.CPU,
profiler.ProfilerActivity.CUDA,
],
record_shapes=True,
with_stack=True,
profile_memory=True,
) as prof:
for step in range(10):
output = model(input)
loss = loss_fn(output, target)
loss.backward()
optimizer.step()
prof.step() # 标记一个step
# 打印结果
print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=20))
输出结果长这样:
| Name | Self CPU % | Self CPU | CUDA Total | # of Calls | Input Shapes |
|---|---|---|---|---|---|
| aten::mm | 12.5% | 23.4ms | 45.2ms | 128 | [256, 512], [512, 1024] |
| aten::conv2d | 8.3% | 15.6ms | 32.1ms | 64 | [32, 64, 56, 56], [64, 64, 3, 3] |
| cudaMemcpy | 5.1% | 9.8ms | 9.8ms | 256 | — |
避坑指南:我曾经遇到过一个问题——Profiler跑出来的结果和实际推理速度对不上。后来发现是Profiler本身的开销影响了结果。解决办法是:用 with_stack=False 减少开销,或者只采集几个step,不要全程开着。
3.3 TensorBoard:让数据说话
光有数据还不够,你得能看懂。TensorBoard就是那个帮你把数据变成图表的工具。
PyTorch Profiler可以直接导出TensorBoard格式的数据,然后你用TensorBoard打开,就能看到各种可视化图表:
- 算子耗时分布图:哪个算子最耗时,一目了然
- 调用栈火焰图:追踪耗时算子的调用路径
- GPU Kernel视图:每个Kernel的执行时间线
- 内存使用趋势:看看有没有内存泄漏
导出到TensorBoard
# 保存Profiler结果
prof.export_chrome_trace("trace.json")
# 或者直接启动TensorBoard插件
from torch.profiler import tensorboard_trace_handler
with profiler.profile(
activities=[...],
on_trace_ready=tensorboard_trace_handler("./log_dir")
) as prof:
# 你的训练代码
pass
# 然后在命令行启动TensorBoard
# tensorboard --logdir ./log_dir
打开TensorBoard后,我一般先看「Overview」页面。它会自动给出性能建议,比如「你的GPU利用率只有40%,建议增大batch size」或者「内存拷贝耗时占比过高,建议使用pin_memory」。这些建议虽然不一定100%准确,但至少给你指明了方向。
我的经验:TensorBoard最适合做对比实验。比如你改了算子映射策略,跑两次Profiling,然后在TensorBoard里对比两次的trace。你能直观地看到,改了之后哪个算子变快了,哪个反而变慢了。这种对比分析,比看一堆数字有效得多。
3.4 三款工具配合使用的工作流
说了这么多,到底怎么用?我总结了一个工作流,你照着做就行:
- 第一步:Nsight Systems扫全局
跑一次完整的训练或推理,看看整体时间都花在哪。重点关注:GPU利用率、CPU-GPU同步开销、内存拷贝占比。
- 第二步:PyTorch Profiler查算子
根据Nsight Systems的结果,锁定耗时最长的几个阶段。然后用Profiler深入分析,看看是哪个算子拖了后腿。
- 第三步:TensorBoard做对比
优化前后各跑一次Profiling,导出到TensorBoard。对比火焰图、耗时分布,验证优化效果。
一个小建议:别一上来就开三个工具。先只用Nsight Systems跑一次,看看整体情况。如果GPU利用率已经很高了,那问题可能不在算子映射上,而是算法本身需要优化。如果GPU利用率低,再上Profiler和TensorBoard细查。
好了,工具就介绍到这里。记住,工具只是手段,关键是你得知道自己在找什么。下次遇到性能问题,别急着改代码,先跑一次Profiling,让数据告诉你答案。