2. Nsight Systems快速上手

Nsight Systems 是 NVIDIA 提供的系统级性能分析工具,它回答的第一个问题不是“这个内核为什么慢”,而是“这段时间里 GPU 到底在忙什么、CPU 在等什么、数据搬了多少”。它通过采样与追踪相结合的方式,把 CPU 线程、CUDA API、内核执行、内存拷贝、同步事件统一放到一条时间轴上,因此特别适合做宏观瓶颈定位。本节的目标是让你能独立完成一次完整采集:装好工具、用命令行或 GUI 采集一个 CUDA 程序、打开时间线、并快速读出内核与内存拷贝的分布特征。

安装与配置

Nsight Systems 的安装有三条常见路径。第一,随 CUDA Toolkit 一起安装,Linux 下通常位于 /usr/local/cuda/bin/nsys,Windows 下位于 CUDA 安装目录的 nsight-systems 子目录。第二,从 NVIDIA 官网单独下载 Nsight Systems 安装包,这种方式可以拿到比 CUDA 自带版本更新的功能。第三,在容器或远程服务器上,只安装命令行版本 nsys,采集后用本地 GUI 打开报告。

安装完成后先做两件事:确认版本、确认权限。执行 nsys --version 应输出类似 NVIDIA Nsight Systems version 2024.x.x 的信息。权限方面,Linux 下普通用户采集 GPU 指标需要访问性能计数器,通常要设置 perf_event_paranoid 或使用 sudo;如果只采集 CUDA API 与内核时间线,一般不需要额外提权。远程采集时,建议在目标机上只跑 nsys profile,把生成的 .nsys-rep 文件拷回本地用 GUI 分析,避免远程图形界面的开销干扰数据。

采集前先关闭无关的后台 GPU 任务,否则时间线上会混入其他进程的内核,干扰判断。可以用 nvidia-smi 确认当前 GPU 上没有别的计算进程。

命令行采集:一条命令拿到时间线

最常用的采集命令是 nsys profile。假设我们有一个编译好的 CUDA 程序 vec_add,它执行一次向量加法,包含一次 H2D 拷贝、一次内核、一次 D2H 拷贝。采集命令如下:

nsys profile \
  --trace=cuda,nvtx,osrt \
  --sample=cpu \
  --output=vec_add_report \
  --force-overwrite=true \
  ./vec_add 1048576

这里几个参数值得解释。--trace=cuda,nvtx,osrt 指定追踪 CUDA API、NVTX 标记和操作系统运行时调用;--sample=cpu 打开 CPU 采样,用于观察 CPU 侧热点;--output 指定输出文件名,实际会生成 vec_add_report.nsys-rep;--force-overwrite=true 允许覆盖同名文件。程序参数 1048576 表示向量长度,属于示例参数,实际运行时按你的程序接口调整。

采集结束后,命令行会打印一段摘要,包括内核数量、内存拷贝次数、总耗时等。如果只想快速看统计而不打开 GUI,可以用 nsys stats:

nsys stats --report cuda_gpu_kern_sum,cuda_gpu_mem_time_sum vec_add_report.nsys-rep

其中 cuda_gpu_kern_sum 汇总每个内核的调用次数与总耗时,cuda_gpu_mem_time_sum 汇总内存拷贝的方向与耗时。这两个报告是识别宏观分布最快的手段。

GUI 基本操作

在本地打开 Nsight Systems GUI,选择 File → Open 载入 .nsys-rep 文件。界面主要分三块:上方是时间线视图,左侧是进程/线程树,右侧是事件详情面板。时间线视图里,每一行代表一个 CPU 线程或一条 GPU 流,横向是时间,色块代表事件。CUDA API 调用显示在 CPU 线程行上,内核和内存拷贝显示在 GPU 流行上。

常用操作包括:鼠标滚轮缩放时间轴,按住中键拖动平移,双击某个色块可以跳转到对应事件并在右侧查看参数。顶部工具栏的 Events View 可以切换到表格模式,按耗时排序内核和拷贝。对于初学者,建议先看 GPU 行上的色块分布,再看 CPU 行上是否有长时间等待。

时间线阅读:内核与内存拷贝的宏观分布

打开报告后,先看 GPU 行。典型的一次向量加法采集会呈现这样的结构:最左侧一个较短的 H2D 拷贝块,中间一个内核块,右侧一个 D2H 拷贝块。如果内核块很长而拷贝块很短,说明计算是主要开销;如果拷贝块与内核块长度相当,甚至拷贝更长,说明数据传输是瓶颈,需要考虑使用 pinned memory 或减少拷贝次数。

再看 CPU 行。CUDA API 调用(如 cudaMemcpy、cudaLaunchKernel)会以细长色块出现。如果 CPU 行上出现大段空白,说明 CPU 在等待 GPU 完成,此时 GPU 可能已经空闲,属于同步等待造成的利用率下降。如果 CPU 行上 API 调用密集且 GPU 行有空隙,说明启动开销或同步策略有问题。

下面这张图展示了一次示例采集的宏观时间线结构,用于对照理解内核与内存拷贝在时间轴上的相对位置和长度关系。

Nsight Systems 时间线宏观结构(示例) 横轴为时间,色块长度表示事件持续时间;GPU 行展示内核与拷贝,CPU 行展示 API 调用与等待 0 ms 2 ms 4 ms CPU 线程 cudaMalloc cudaMemcpy H2D cudaLaunch 等待 GPU(同步) cudaMemcpy D2H cudaFree GPU 流 0 H2D 拷贝 vec_add 内核 D2H 拷贝 GPU 空闲 CUDA API 调用 内存拷贝 内核执行 空闲/等待 宏观判读要点 • 内核块远长于拷贝块 → 计算主导,进一步分析内核内部效率 • 拷贝块与内核块相当或更长 → 传输主导,考虑 pinned memory、合并拷贝、减少同步

从图中可以看到,示例程序在 H2D 拷贝之后、内核启动之前,CPU 行上有一段“等待 GPU”的空白,GPU 行上也出现了“GPU 空闲”区间。这说明程序在拷贝与内核之间存在同步点,导致 GPU 短暂闲置。实际分析时,这类空隙就是优化切入点:能否用异步拷贝 cudaMemcpyAsync 配合流来重叠传输与计算。

用统计报告快速定位

时间线适合看结构,统计报告适合看占比。执行 nsys stats 后,cuda_gpu_kern_sum 会给出类似下面的示例输出(数值为示例,非实测):

Time (%)  Total Time (ns)  Instances  Avg (ns)  Name
--------  ---------------  ---------  --------  ----
   62.3        1,245,000          1  1245000  vec_add_kernel
   21.1          422,000          1   422000  [CUDA memcpy H2D]
   16.6          332,000          1   332000  [CUDA memcpy D2H]

这张表直接告诉你:内核占总 GPU 时间的 62.3%,两次拷贝合计约 37.7%。如果目标是提升整体性能,优先优化内核;如果拷贝占比超过一半,则应先处理数据传输。注意 Instances 列,如果某个内核被调用上千次而每次耗时很短,说明启动开销可能成为问题,需要合并内核或使用 CUDA Graph。

一次完整采集的检查清单

  • 确认 nsys --version 可用,远程采集时确认目标机权限。
  • 用 nsys profile --trace=cuda,nvtx,osrt --sample=cpu -o 报告名 ./程序 参数 采集。
  • 用 nsys stats --report cuda_gpu_kern_sum,cuda_gpu_mem_time_sum 先看统计占比。
  • GUI 打开报告,先看 GPU 行色块分布,再看 CPU 行等待与空隙。
  • 记录内核总时间、拷贝总时间、GPU 空闲区间,作为后续优化的基线。

完成以上步骤,你就已经具备用 Nsight Systems 做宏观瓶颈定位的能力。下一阶段的分析会深入到单个内核内部,而本节建立的“时间线 + 统计占比”视角,是后续所有深入分析的基础参照。