Nsight Compute 分析
NVIDIA Nsight Compute (ncu) 对单个 CUDA 内核进行性能分析,以确定它们为何运行缓慢以及如何优化。它将 GPU 吞吐量测量为理论峰值(Speed of Light / SOL%)的百分比,从而实现系统化的瓶颈分类和有针对性的优化。
使用时机
当你遇到以下情况时,可调用此技能:
- 触发条件:用户想要对 CUDA 内核进行性能分析、分析
ncu输出、解读.ncu-rep报告,或优化 GPU 内核性能 - 症状:内核运行速度低于预期、GPU 利用率低、需要分类计算受限 vs 内存受限、占用率问题
- 关键词:"ncu"、"nsight compute"、"SOL%"、"speed of light"、"内核性能分析"、"计算受限"、"内存受限"、"延迟受限"、"占用率"、"屋顶线"、"warp stalls"、"缓存命中率"、"ncu-rep"
不得将此技能用于:
- 系统级分析(请使用 Nsight Systems /
nsys) - CUDA API 跟踪或 CPU-GPU 时间线分析(请使用
nsys) - 无需性能分析的 GPU 监控(请使用
nvidia-smi)
要求
| 依赖项 | 版本 | 说明 |
|---|---|---|
| CUDA Toolkit | >=11.0 | 包含 ncu |
ncu 二进制文件 | 驱动程序支持的版本 | Nsight Compute 2026.3 需要与 CUDA 13 兼容的驱动程序。或设置 $NCU 环境变量 |
| NVIDIA GPU | Turing+ | Volta 需要 Nsight Compute 2025.2 或更早版本 |
权限:ncu 在容器中可能需要 sudo、CAP_SYS_ADMIN 或 --privileged。ncu -v 仅打印版本;缺少权限会在第一个内核被分析时显示为 ERR_NVGPUCTRPERM。
原则
数据完整性
这是一个数据驱动的分析系统。你呈现的每个数字都必须有权威来源。 请毫无例外地遵循以下规则:
- 先引用再解读。 在呈现 ncu 输出的指标时,始终先展示你实际运行的 ncu 命令以及相关的原始输出(CSV 行、指标值),然后再陈述任何数字结论。
- 绝不虚构指标。 如果 ncu 失败、返回意外输出,或者你无法运行它,请明确说明。不要编造看起来合理的数字。诚实的"性能分析失败"优于虚假数据。
- 归因每个值。 对于你引用的每个指标(SOL%、持续时间、占用率、吞吐量),读者必须能够将其追溯回你展示的原始 ncu 输出中的特定行。
SOL% 心智模型
Speed of Light (SOL%) 衡量内核运行接近 GPU 理论峰值的程度:
- 计算 SOL% (
Compute (SM) Throughput) = 最繁忙 SM 单元的吞吐量占其峰值的百分比 - 内存 SOL% (
Memory Throughput) = 最繁忙内存单元(DRAM、L2、L1、共享内存)的吞吐量占其峰值的百分比
较高的指标通常揭示瓶颈类型。将其作为主要的分类信号。两者可能同时较高:此时内核已达到其算法的硬件极限。
分类阈值
| 计算 % | 内存 % | 瓶颈 | 下一步 |
|---|---|---|---|
| >60 | <40 | 计算受限 | ComputeWorkloadAnalysis 部分 |
| <40 | >60 | 内存受限 | MemoryWorkloadAnalysis 部分 |
| <40 | <40 | 延迟受限 | LaunchStats + Occupancy 部分 |
| 40-60 | 40-60 | 均衡 | 使用详细部分进行更深入分析 |
| >60 | >60 | 接近硬件极限 | 算法变更;检查两个工作负载部分 |
附加信号:
- 持续时间 <10us 且启动次数多 -> 启动开销受限(先使用 nsys)
- 两者均 <40% 但占用率 >50% -> 指令受限(检查 InstructionStats)
SOL% 性能水平
| SOL% | 水平 | 行动 |
|---|---|---|
| >80% | 优秀 | 仅需微调 |
| 60-80% | 良好 | 针对性优化 |
| 40-60% | 一般 | 需要重大优化 |
| <40% | 差 | 需要重大重构 |
按部分优先分析
始终使用有针对性的 --section 标志,而不是使用 --set 进行批量收集。单独的部分更快、更精准。仅当需要广泛探索时,才升级到 --set basic 或 --set detailed。
ncu vs nsys
| 工具 | 范围 | 开销 | 目的 |
|---|---|---|---|
| nsys | 系统级 | 5-10% | 找出要优化的内核 |
| ncu | 内核级 | 慢 10-100 倍 | 理解内核为何慢 |
先使用 nsys 识别按 GPU 时间排名靠前的内核,然后使用 ncu 对这些特定内核进行深入分析。
工作流程
根据请求选择你的路径:
- 知识查询(使用哪些指标、
--sectionvs--set、如何过滤内核):直接从下面的原则、命令参考和参考资料中回答。不要运行 ncu。 - 快速诊断(分类瓶颈、检查 SOL%):仅执行步骤 1。如果用户想要更多信息,则升级。
- 特定诊断(bank 冲突、寄存器压力、占用率):快速 SOL% 检查(步骤 1),然后直接进入步骤 2 中的相关部分。
- 深度分析(详细报告、优化建议):完整步骤 1-5。在你的最终回复中呈现包含所有关键指标(SOL%、持续时间、占用率)的完整结构化报告——不要将报告拆分到多条消息中,也不要将其替换为简短摘要。
步骤 0:验证 ncu
ncu -v
# 或:$NCU -v
如果未找到,请确保已安装 CUDA toolkit 或设置 NCU 环境变量指向二进制文件路径。
步骤 1:SOL% 诊断
始终以 SpeedOfLight 开始分类瓶颈:
ncu --section SpeedOfLight --csv \
--kernel-name regex:"KERNEL" \
--launch-skip 5 --launch-count 3 \
-- COMMAND
从输出中读取 Compute (SM) Throughput 和 Memory Throughput。使用上述阈值进行分类。
步骤 2:使用针对性部分升级
基于步骤 1 的分类,添加部分:
| 分类 | 添加的部分 |
|---|---|
| 计算受限 | ComputeWorkloadAnalysis |
| 内存受限 | MemoryWorkloadAnalysis |
| 延迟受限 | LaunchStats、Occupancy |
| Warp 停顿 | WarpStateStats、SchedulerStats |
| 需要指令分解 | InstructionStats |
在诊断延迟受限内核时,始终包含 LaunchStats 和 Occupancy。这些能揭示寄存器压力、共享内存限制和块大小问题。
示例 -- 内存受限深度分析:
ncu --section SpeedOfLight --section MemoryWorkloadAnalysis --csv \
--kernel-name regex:"embedding_lookup" \
--launch-count 3 \
-- python script.py
示例 -- 计算受限深度分析:
ncu --section SpeedOfLight --section ComputeWorkloadAnalysis --csv \
--kernel-name regex:"gemm|nvjet" \
--launch-count 3 -- python script.py
示例 -- 占用率调查:
ncu --section SpeedOfLight --section LaunchStats --section Occupancy --csv \
--kernel-name regex:"small_kernel" \
-- python script.py
步骤 3:屋顶线分析(可选)
用于直观理解计算与内存的平衡。屋顶线部分是图表:在 CLI 上,它们仅在 --print-details all 时打印,这会列出图表背后的数字(达到和峰值的工作量与通信量)。要查看图表,请使用 -o 保存报告并在 Nsight Compute UI 中打开。
ncu --section SpeedOfLight_RooflineChart --print-details all \
--kernel-name regex:"KERNEL" -- COMMAND
对于特定精度层次结构屋顶线:
# FP16 内核
ncu --section SpeedOfLight_HierarchicalHalfRooflineChart --print-details all \
--kernel-name regex:"KERNEL" -- COMMAND
# Tensor core 内核
ncu --section SpeedOfLight_HierarchicalTensorRooflineChart --print-details all \
--kernel-name regex:"KERNEL" -- COMMAND
解读:内核位于脊点左侧 = 内存受限;右侧 = 计算受限;远低于两个屋顶 = 延迟/占用率问题。请参阅 references/roofline-analysis.md。
步骤 4:解读与优化
- 从 SOL% 分类中识别主导瓶颈
- 在
references/bottleneck-guide.md中查找详细的分析和优化策略 - 首先应用影响最大的优化
- 重新分析以验证改进并检测瓶颈转移
步骤 5:验证
优化后重新分析相同的内核:
ncu --section SpeedOfLight --csv \
--kernel-name regex:"optimized_kernel" \
--launch-count 3 \
-- python optimized_script.py
比较:吞吐量百分比是否增加?持续时间是否减少?瓶颈类型是否发生变化?
分析 JIT 编译的内核(Triton/cuTile/CuTeDSL)
JIT 编译的内核在首次调用时会触发自动调优。隔离实际执行:
- 先预热:运行内核 3-5 次以完成 JIT 编译和自动调优,然后
torch.cuda.synchronize()。 - 使用分析器标记:使用
cudaProfilerStart()/cudaProfilerStop()包围测量区域。 - 使用
--profile-from-start off以便 ncu 仅捕获标记区域:
# 预热(JIT + 自动调优)
for _ in range(5):
result = kernel(inputs)
torch.cuda.synchronize()
# 仅分析稳态
torch.cuda.cudart().cudaProfilerStart()
for _ in range(3):
result = kernel(inputs)
torch.cuda.synchronize()
torch.cuda.cudart().cudaProfilerStop()
ncu --profile-from-start off --section SpeedOfLight --csv \
--kernel-name regex:"target_kernel" \
--launch-count 3 -- python script.py
替代方案:使用 --launch-skip N 跳过自动调优启动。有关 NVTX 范围和重放模式的替代方案,请参阅 references/advanced-profiling.md。
程序化报告分析
使用 ncu_report Python 模块(位于 Nsight Compute 安装目录的 extras/python/ 中)从 .ncu-rep 或 .ncu-repz 文件中提取指标:
import ncu_report
ctx = ncu_report.load_report("report.ncu-rep")
for rng in ctx:
for action in rng:
name = action.name()
# SpeedOfLight 的 "Compute (SM) Throughput" 和 "Memory Throughput"
compute = action["sm__throughput.avg.pct_of_peak_sustained_elapsed"].as_double()
memory = action["gpu__compute_memory_throughput.avg.pct_of_peak_sustained_elapsed"].as_double()
duration = action["gpu__time_duration.sum"].as_uint64()
if compute > 60:
classification = "compute-bound"
elif memory > 60:
classification = "memory-bound"
else:
classification = "latency-bound"
print(f"{name}: {classification} (compute={compute:.1f}%, mem={memory:.1f}%, {duration}ns)")
有关完整 API(IContext、IRange、IAction、IMetric 类),请参阅 references/python-report-api.md。
输出格式
CSV 输出(用于脚本编写和自动化分析):
ncu --csv --section SpeedOfLight --kernel-name regex:"KERNEL" -- COMMAND
ncu --csv --page raw --section SpeedOfLight -- COMMAND # 所有指标平铺
默认 CSV 每行包含一个指标。其列包括 ID(分析的启动)、Kernel Name、Section Name、Metric Name、Metric Unit 和 Metric Value;规则结果是填入 Rule Name、Rule Description 和 Estimated Speedup 的行。Metric Name 可能跨部分重复(Memory Throughput 在 SpeedOfLight 中是 %,在 MemoryWorkloadAnalysis 中是 字节/秒),因此也需要按 Section Name 过滤。--page raw 每行对应一次启动,每列对应一个指标,并有第二行的单位。值使用基本单位(ns、hz、%)。
在分析期间,ncu 的 ==PROF== 行和应用程序自身的输出会与 CSV 共享标准输出。为获得干净的 CSV,请将报告写入文件然后读取:
报告文件(用于后续分析):
ncu -o report.ncu-rep --section SpeedOfLight -- COMMAND
ncu --import report.ncu-rep --csv # 每行一个指标
ncu --import report.ncu-rep --csv --page raw # 每行一次启动
为 -o 指定扩展名:无扩展名时,Nsight Compute 2026.3 及更新版本写入 zstd 压缩的 report.ncu-repz,旧版本写入 report.ncu-rep。--import 和 ncu_report 均可读取两者。
关键指标行(Metric Name 值):
| Metric Name | Section | 含义 |
|---|---|---|
Duration | SpeedOfLight | 执行时间 |
Compute (SM) Throughput | SpeedOfLight | 计算峰值的 % |
Memory Throughput | SpeedOfLight | 最繁忙内存单元峰值的 % |
SM Frequency | SpeedOfLight | 分析运行的时钟频率 |
Achieved Occupancy | Occupancy | 活跃 warp / 最大 warp (%) |
成功指标:
- 输出中存在 SOL% 值 -> 分析成功
- Duration 值合理(不为 0 或极大)
- 当
--launch-count > 1时捕获了多次启动
示例
示例:分类一个 GEMM 内核
Blackwell 上的 cuBLAS GEMM 内核名为 nvjet_*,因此匹配这两个名称:
ncu --section SpeedOfLight --csv \
--kernel-name regex:"gemm|nvjet" \
--launch-skip 5 --launch-count 3 \
-- python train.py
B200 上 FP16 4096x4096 矩阵乘法的输出(列和行已精简):
"ID",...,"Kernel Name",...,"Section Name","Metric Name","Metric Unit","Metric Value",...
"0"...,"nvjet_sm100_hsh_128x256_64x6_2x2f_2cta_h_bz_NNT",...,"GPU Speed Of Light Throughput","Memory Throughput","%","38.38",
"0"...,"nvjet_sm100_hsh_128x256_64x6_2x2f_2cta_h_bz_NNT",...,"GPU Speed Of Light Throughput","Duration","ns","97600",
"0"...,"nvjet_sm100_hsh_128x256_64x6_2x2f_2cta_h_bz_NNT",...,"GPU Speed Of Light Throughput","Compute (SM) Throughput","%","77.11",
解读:计算受限(计算 77.1%,内存 38.4%)。下一步:使用 --section ComputeWorkloadAnalysis 检查 Tensor Core 使用情况。
示例:诊断一个内存受限的 Embedding 内核
PyTorch 的 embedding lookup 执行 vectorized_gather_kernel,因此也要匹配它:
ncu --section SpeedOfLight --section MemoryWorkloadAnalysis --csv \
--kernel-name regex:"gather|index|embedding" \
--launch-count 3 -- python train.py
在输出中检查 L1/L2 缓存命中率:命中率低表明...