跳到正文
博客园· _nibel·· 2 小时前AI 评分22

教你读懂 Nsight Compute 报告:Context、Comments、Raw、Session 四页与全篇收束

第五章:其余页面与全篇收束 ⭐教你读懂 Nsight Compute 报告 系列合集⭐ - _nibel

AI 导读

该系列第五章读完 Nsight Compute 报告剩余的 Context、Comments、Raw、Session 四个页面,并回扣 reduce_ill 的四个性能病态。

正文

第五章:其余页面与全篇收束

Summary给出整体表现,Details把问题展开到硬件资源和具体指标,Source再把这些线索接回源码与指令。沿着报告的标签栏往后,还剩Context、Comments、Raw、Session四个页面。这一章按页面顺序把它们读完,再回到reduce_ill的四个病态,把整套读报告的过程串起来。

Context

Context记录kernel启动时的调用背景。上半部CPU Call Stack展示CPU经过哪些函数发起这次kernel启动,下半部NVTX State展示启动时处在哪些程序标记范围内。序章采集命令里的--call-stack,对应的就是上半部这张调用栈表。

Context页的CPU调用栈与未采集的NVTX状态

先看CPU Call Stack。表格一行是一层函数调用,左边的#从0开始编号,越靠上越接近采集时的调用位置,往下则回溯到是谁调用了它。Module是这层代码所属的可执行文件或库,图里主要是reduce和libc.so.6;PC是这一层对应的CPU代码地址;Function是函数名;Line和File分别是解析出的源码行号与文件。本报告的Line都为0、File为空,说明这次调用栈没有解析出CPU源码位置,仍然可以通过函数名认出调用路径。

从上往下读Function:第0到4层主要是CUDA运行时和编译器生成的kernel启动入口,其中能看到cudaLaunchKernel,以及名字带device_stub的启动辅助函数;第5层是程序的main;再往下是C运行库与程序启动入口。这样就能找到main发起reduce_ill启动、再进入CUDA运行时的路径。表里出现的reduce_ill入口属于CPU侧的启动调用,GPU实际执行的SASS及其对应源码在Source页中查看。本报告只采集了一次reduce_ill启动,调用来源很直接;换成同一kernel被多个上层函数调用的程序,这张表就能帮助区分本次结果来自哪一条调用路径。

下半部NVTX State里写着Data not captured,说明本次采集没有记录这部分数据。NVTX是NVIDIA Tools Extension,用来给一段代码划出范围,例如把归并这一段标成“归约计算”,采集时按截图提示给ncu命令加--nvtx,或在连接对话框的活动选项里把Enable NVTX Support设为Yes,报告就会记录kernel启动时处在哪些标记范围内,再配合区间过滤,就能只关心标记部分的活动。序章的命令启用了CPU调用栈采集,没有启用NVTX,所以本报告的NVTX State部分我们不用关心。

Context给出的信息是“这次kernel从哪里被调用、调用时处在什么程序范围内”。本例已经能从调用栈认出main到CUDA启动接口的路径,性能问题继续沿前面读到的GPU指标分析即可。

Comments把报告里的备注集中放到一起,适合把分析结论直接留在报告中,方便以后重新打开时接着看。

Comments页的报告、kernel与section备注层级

最上面Report Comments是整份报告的备注区。下面的552 - reduce_ill对应当前kernel结果,再往下是Details中的14个section,名称和顺序都与Details页一致。点击section左侧的三角形,可以展开对应的备注区域;报告、单次kernel结果和各section分别有自己的备注位置。截图中已展开的报告与kernel备注区为空,下方各section按折叠状态列出。

例如,第二章已经把共享内存冲突定位到reduce.cu:99的两条LDS.128,就可以在Memory Workload Analysis或Source Counters的备注里写下“冲突集中在这两条共享内存加载,下一步检查访问布局”。这是一条分析记录,之后修改代码、重新测量时,还可以继续补上验证结果。Details里每个section右上角的对话气泡按钮也能编辑该section的备注,Comments负责把这些记录汇总展示,备注随报告文件一起保存。

Raw

Raw把本报告保存的指标名称、数值和单位集中列出来。第二、三章里需要核对某个指标的原值、统计对象或单位时,就可以到这里查。表中既有次数、字节数等计数,也有已经计算好的平均值、比率和吞吐率,范围取决于这次报告实际采集了哪些指标。

Raw页的筛选栏与部分性能指标

截图只保留筛选栏和几行熟悉的指标。当前排列方式下,每行是一个指标,右侧是本次reduce_ill的值,名字后方的方括号标出单位。443,927个周期、318.43微秒、15.98 %和80.44 %,分别对应前面读过的GPU流逝周期、运行时间、计算吞吐和内存吞吐。顶部Filter用于按名字筛选指标,左边的转置按钮可以切换表格的行列方向。需要查数时,先确定要找的指标,再筛选名称、核对单位与数值即可。完整的Raw指标对照另列为附章《Raw各指标说明》,由AI逐项整理,按硬件与用途分组,供大家按名称查阅。

Session

Session记录这份报告怎样采集、采集时使用了什么环境。这些信息保存在报告里,把报告拷到另一台电脑打开,看到的仍然是原来那次采集的记录。下面的截图遮盖了一些个人敏感信息。

Session页的采集设置、环境和进程信息,个人信息已遮盖

最上面Launch Settings汇总采集设置,Profiler Command Line就是序章使用的那条ncu命令。命令中的--set full选择full预设指标组,--launch-count 1限制本次只采集一次符合条件的kernel启动,--call-stack记录CPU调用栈,--target-processes all把目标程序及其子进程纳入采集范围,--import-source yes把采集时能够找到的源码保存进报告。-f允许覆盖同名报告,-o后面指定报告输出位置,最后的reduce程序与ill参数选中本合集分析的reduce_ill版本。这些参数在这里都有记录,Context里的调用栈和Source里的源码也就能接回当时的采集设置。Launch Settings讲的是ncu怎样采集;第三章Launch Statistics讲的是kernel的grid、block和资源用量。

往下是Session Info,记录采集主机与目标程序所在机器的属性。字段名里的Host指采集主机,Target指被剖析程序所在的机器。本报告两边记录的操作系统、架构和处理器信息一致,采集发生在Linux环境中。从上往下看:

项 值 说明
Created 已遮盖 报告创建时间,用来区分不同采集记录
Host Name 已遮盖 采集主机的名称
Host OS Linux 采集主机的操作系统,截图后面还带有内核构建信息
Host Architecture x86_64 采集主机的CPU架构
Host Processor AMD Ryzen 9 9950X 16-Core Processor 采集主机的CPU型号
Target Name、Target IP Addresses 已遮盖 目标机器的名称与网络地址
Target OS、Target Architecture、Target Processor Linux、x86_64、AMD Ryzen 9 9950X 16-Core Processor 目标程序所在机器的操作系统、CPU架构和CPU型号
CUDA Version 12.8 采集环境中记录的CUDA版本
Display Driver Version 570.144 采集环境中的GPU驱动版本
PerfWorks 0.1.0 NVIDIA性能指标采集组件的版本
Nsight Compute Target 2025.1.1.0 目标端采集组件的版本,与序章使用的2025.1.1对应

再往下Processes列出被剖析的目标进程。Filter可以按进程信息筛选,表里的Process Id是进程编号,Process Name给出可执行程序的路径,本例对应reduce程序。它与报告头的Process字段描述同一个目标进程。最后Device Attributes列出所用GPU的设备属性,前面用到的82个SM、每SM最多48个warp等硬件能力,都可以结合这些属性核对。这里保存的是设备能力,本次kernel实际用了多少资源、达到了多少驻留数量,已经分别在Launch Statistics和Occupancy里读过。

Session把前面所有数字放回了采集条件中:本报告来自RTX 3090,记录的CUDA版本为12.8、驱动版本为570.144、ncu目标端版本为2025.1.1。以后比较两份报告时,先核对设备、启动配置与采集设置,再判断运行时间和相关指标怎样变化,结论才有明确的适用范围。

最后的总结

报告的七个页面各有分工。Summary让我们先看到318.43微秒的运行时间,以及15.98 %的计算吞吐和80.44 %的内存吞吐;Details继续展开访存、驻留、调度和等待;Source把指标与源码、SASS指令对应起来。Context交代调用来源,Comments保存分析记录,Raw用于核对具体指标,Session保存采集环境。读到这里,再回头看序章埋下的四个病态,每一个都有对应证据:

病态 报告里的主要证据 对应代码
病态一:非合并全局读 全局读取涉及的sector数量达到理想合并访问的8倍,多余sector集中在八条展开的LDG.E上 各block按列读取input,同一warp相邻线程的地址相隔32,768字节
病态二:共享内存读冲突 两条LDS.128各多出262,144个wavefront,合计524,288个 各线程读取smem中自己负责的八个float,需要检查这些地址怎样落到共享内存bank上
病态三:朴素树形归并 Barrier约占累计warp周期的40.1 %,归并FADD的实际线程参与比例约12.45 % 每轮归并后都执行block同步,满足加法条件的线程数随步长增大而减少
病态四:稀疏写回 共执行8,192条STG,每次写回一个float,只使用一个32字节sector中的4字节 每个block只由thread 0把部分和写入output

分析过程也可以顺着这份报告再走一遍:先从Summary确定运行时间和需要关注的方向,再到Details判断压力落在哪一级硬件、warp主要在等什么。找到异常之后,用定位表进入Source,查看对应指令和源码;遇到数值或单位需要确认,就回Raw核对,同时用Session核对采集条件。这样,整体表现、硬件行为和代码位置能够互相验证,最后得到有证据支撑的修改方向。

接下来若要优化这份kernel,可以围绕全局读的访问顺序、共享内存布局和归并同步分别做改动。每次改完先验证计算结果正确,再在可比较的环境与启动配置下重新采集,观察Duration以及相关sector、wavefront和停顿指标怎样变化。规则给出的预计收益是选择分析方向的线索,实际节省了多少时间,还需由修改后的测量结果回答,但这些都是后话了,也不属于本合集的内容了。

到这里,整个合集也要收尾了。回头再看,我们从Summary里一个看起来不太对劲的数字出发,一节一节展开指标,回到Source找到对应的指令,再到Raw确认单位和采集条件,最后把每一个值得关注的数值解释回硬件正在做的事,也解释回代码为什么让硬件这样工作。一路走下来,作者和大家一起把“数字—硬件—代码”这条线索完整地走通了,这本身就是一件让人高兴的事。写这个合集的过程,也是作者自己重新学习的过程。感谢大家一路读到这里,感谢每一条评论,这些反馈对作者是很大的鼓励,也让这份合集一点点变得更完整。如果这些文字能让大家下次打开Nsight Compute时少一点茫然、多一点把握,那这段时间的整理就没有白费。

合集到这里就结束了,但分析和优化不会结束。谢谢大家一路陪伴!

来源:博客园 · cnblogs.com