3、Profiling工具入门:使用Nsight Systems、PyTorch Profiler、TensorBoard进行性能数据采集

做算子映射优化,最怕什么?

怕瞎猜。

你觉得自己写的算子挺快,结果一跑,GPU利用率不到30%。你怀疑是内存拷贝慢,折腾半天,最后发现是kernel launch太频繁。这种事儿我遇到过太多次了。所以,学会用Profiling工具,是性能优化的第一步。

今天咱们就聊聊三款最常用的工具:Nsight Systems、PyTorch Profiler、TensorBoard。它们各有侧重,配合起来用,基本能覆盖你日常排查的所有场景。

核心思路:先宏观定位瓶颈,再微观分析细节。Nsight Systems看全局,PyTorch Profiler看算子级,TensorBoard做可视化对比。

Profiling工具入门 Nsight Systems PyTorch Profiler TensorBoard GPU时间线 Kernel分析 内存拷贝 算子耗时 调用栈 内存分配 对比实验 火焰图 趋势分析 宏观定位 → 微观分析 → 可视化对比

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 三款工具配合使用的工作流

说了这么多,到底怎么用?我总结了一个工作流,你照着做就行:

  1. 第一步:Nsight Systems扫全局

    跑一次完整的训练或推理,看看整体时间都花在哪。重点关注:GPU利用率、CPU-GPU同步开销、内存拷贝占比。

  2. 第二步:PyTorch Profiler查算子

    根据Nsight Systems的结果,锁定耗时最长的几个阶段。然后用Profiler深入分析,看看是哪个算子拖了后腿。

  3. 第三步:TensorBoard做对比

    优化前后各跑一次Profiling,导出到TensorBoard。对比火焰图、耗时分布,验证优化效果。

一个小建议:别一上来就开三个工具。先只用Nsight Systems跑一次,看看整体情况。如果GPU利用率已经很高了,那问题可能不在算子映射上,而是算法本身需要优化。如果GPU利用率低,再上Profiler和TensorBoard细查。

好了,工具就介绍到这里。记住,工具只是手段,关键是你得知道自己在找什么。下次遇到性能问题,别急着改代码,先跑一次Profiling,让数据告诉你答案。


可可精品资料库