3、NVIDIA Nsight Systems入门:安装与配置、Timeline视图、关键指标解读

好,咱们开始聊Nsight Systems。说实话,这工具是我日常工作中用得最多的一个。你想想看,写CUDA代码,最怕什么?不是写不出来,是写出来跑不快。跑不快又不知道卡在哪。Nsight Systems就是帮你找到那个「卡点」的利器。

我个人习惯,拿到一个新项目,第一件事不是看代码,而是先跑一遍Nsight Systems。为什么?因为它能给你一个全局视角。就像你进到一个工厂,先站在二楼往下看,哪条流水线堵了,哪台机器在空转,一目了然。

3.1 安装与配置

安装其实没什么难度。去NVIDIA官网下载对应你操作系统的版本就行。Windows、Linux、macOS都支持。我建议你直接装最新的,因为老版本有时候对新显卡的支持不太好。

这里有个坑,我踩过——环境变量。装完之后,记得把Nsight的bin目录加到PATH里。不然你在命令行敲nsys会提示找不到命令。嗯,我当时折腾了十分钟才反应过来。

注意:如果你用的是WSL2(Windows Subsystem for Linux),记得在Windows端也装一份Nsight Systems。因为WSL2里直接跑,有时候抓不到GPU事件。

配置方面,默认设置其实就够用。但如果你要抓取长时间运行的程序,建议调整一下--duration参数。默认是60秒,超过会自动停止。

# 基本用法
nsys profile -o my_profile ./my_cuda_app

# 指定抓取时长(单位:秒)
nsys profile --duration=120 -o my_profile ./my_cuda_app

# 只抓取GPU相关事件
nsys profile -t cuda,osrt -o my_profile ./my_cuda_app

我个人习惯用-t cuda,osrt,nvtx这个组合。cuda抓GPU事件,osrt抓系统调用,nvtx抓你自己打的标记。这样信息最全,又不会太冗余。

3.2 Timeline视图

打开生成的.qdrep文件,你首先看到的就是Timeline视图。说白了,就是一条时间轴,上面画满了各种颜色的条条块块。

别被吓到。咱们拆开来看。

Timeline视图分上下两部分:

  • 上半部分:CPU活动。显示每个线程在干什么。比如主线程在提交kernel,其他线程可能在等同步。
  • 下半部分:GPU活动。显示每个stream上的kernel执行、内存拷贝、同步操作。

你想想看,如果CPU和GPU之间有大段的空白,说明什么?说明有一方在等另一方。这就是典型的「负载不均衡」。

我记得有一次,帮一个团队优化一个深度学习推理程序。他们觉得GPU利用率已经很高了,结果我一打开Timeline视图,发现GPU在大部分时间都在等CPU把数据准备好。说白了,数据搬运成了瓶颈。

核心观察:Timeline视图里,最怕看到的是「锯齿状」的CPU-GPU交互。理想情况是,CPU提交完任务后,GPU连续执行,中间没有明显空隙。

3.3 关键指标解读

光看颜色条还不够,你得会读数字。Nsight Systems里几个关键指标,我一个个说。

3.3.1 GPU Utilization

这个指标告诉你GPU有多忙。但注意,不是越高越好。如果GPU Utilization一直100%,但你的程序跑得并不快,那说明你的kernel可能太复杂了,或者有别的隐藏问题。

我见过一个案例,GPU Utilization 98%,但吞吐量只有理论峰值的30%。后来发现是kernel里用了太多atomicAdd,导致内存访问冲突严重。嗯,这就是典型的「看似忙碌,实则低效」。

3.3.2 Memory Throughput

内存吞吐量,分读和写。这个指标能帮你判断是不是「内存带宽受限」。

指标 正常范围 瓶颈信号
Memory Throughput (Read) 60%-90% 峰值 <30% 说明kernel计算密集或访存模式差
Memory Throughput (Write) 40%-70% 峰值 >90% 说明写操作过多,可能需优化
Compute Utilization 40%-80% <20% 说明计算单元没充分利用

为什么会这样?说白了,如果你的kernel是计算密集型的,那内存吞吐量低是正常的。但如果你觉得它应该是访存密集型的,结果内存吞吐量却很低,那就要检查你的数据访问模式了——是不是没做合并访问?

3.3.3 API Trace

这个视图显示的是CUDA API的调用情况。比如cudaMemcpycudaLaunchKernel这些。

我建议你重点关注两个东西:

  • cudaMemcpy的耗时。如果它占了总时间的很大比例,说明数据搬运是瓶颈。考虑用异步拷贝或者统一内存。
  • cudaLaunchKernel的延迟。如果每次launch kernel都有几十微秒的延迟,那说明你的kernel太小了。可以考虑合并多个小kernel。
小技巧:在API Trace里,你可以右键点击某个API调用,选择「Zoom to Selection」。这样能快速定位到具体的时间点,结合Timeline视图一起看,效果更好。

3.3.4 NVTX标记

这个是我个人最推荐的。你可以在代码里插入NVTX标记,把程序的不同阶段标出来。比如「数据加载」、「预处理」、「推理」、「后处理」。

#include <nvtx3/nvToolsExt.h>

// 标记一个范围
nvtxRangePushA("Data Loading");
// ... 数据加载代码 ...
nvtxRangePop();

// 标记一个瞬间事件
nvtxMarkA("Kernel Launched");

有了这些标记,你在Timeline视图里就能一眼看出,哪个阶段耗时最长。我曾经帮一个客户优化,他们觉得是kernel慢,结果我一看NVTX标记,发现「数据预处理」阶段占了60%的时间。嗯,方向完全错了。

3.4 实战建议

最后,给你几个实战中的小建议:

  1. 先跑一次全量profile。不要一开始就想着只抓某个部分。全量profile能给你全局视角。
  2. 关注「空隙」。Timeline视图里,任何一段空白都值得你点进去看看。是CPU在等?还是GPU在等?
  3. 对比优化前后。每次改完代码,都跑一次profile。对比两次的Timeline视图,看看你改的地方有没有效果。
  4. 别只看平均值。有时候平均值好看,但个别kernel拖了后腿。用Nsight Systems的统计功能,看看每个kernel的耗时分布。

我曾经有一个项目,优化了一个月,性能提升不到10%。后来用Nsight Systems一看,发现有个很小的kernel,每次只跑几微秒,但被调用了上百万次。累计耗时占了总时间的40%。我把这个小kernel合并到前一个kernel里,性能直接翻倍。你想想看,这就是工具的价值。

好,这一章就到这里。下一章咱们聊Nsight Compute,那个是专门分析单个kernel的。到时候你会看到更细粒度的指标。