性能分析工具——找到真正的瓶颈 / Performance Analysis Tools for Finding Real Bottlenecks
📅 创建时间:2026-06-02 🏷️ 标签:#HPC #性能分析 #nsys #ncu #vtune #PyTorchProfiler 📚 前置知识:[[06-cuda-optimization]](CUDA 优化) [[10-dl-training-optimization]](训练优化) 📚 相关知识:[[05-cuda-kernel-and-memory]](内存管理)
先抓住直觉
Profiler 像医院检查:先判断问题发生在整条训练流水线、某个 Kernel,还是内存分配,再选择对应工具。指标不是分数;“越高越好”通常不成立,关键是它能否解释实际耗时。
- 必须理解:先建立基线;从总时间逐层下钻;一次只验证一个假设。
- 用到再查:
nsys、ncu和 PyTorch Profiler 的命令参数。 - 最小流程:复现 → 测量 → 定位最大耗时 → 提出假设 → 修改 → 用同一基线复测。
场景:优化了半天,为什么还是没快?
┌─────────────────────────────────────────────────────────────┐
│ │
│ 你做了一堆优化: │
│ ✅ 合并内存访问 │
│ ✅ 使用共享内存 │
│ ✅ 优化了 block 大小 │
│ │
│ 自测结果: │
│ 优化前:15 GFLOPS │
│ 优化后:18 GFLOPS │
│ │
│ 提升 20%,但离 A100 的 156 TFLOPS 还差很远! │
│ │
│ 瓶颈到底在哪里? │
│ → 盲目优化 = 浪费时间 │
│ → 必须用工具定位瓶颈! │
│ │
└─────────────────────────────────────────────────────────────┘第1节:性能分析工具全景
┌─────────────────────────────────────────────────────────────┐
│ NVIDIA 性能分析工具 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Nsight Systems(nsys): │
│ 定位:系统级 timeline 分析 │
│ 看什么:CPU-GPU 传输时间、kernel 执行顺序、并行度 │
│ 类比:航空调度中心,看整个机场的运行 │
│ │
│ Nsight Compute(ncu): │
│ 定位:kernel 级别性能分析 │
│ 看什么:SM 利用率、内存带宽、warp 效率、瓶颈类型 │
│ 类比:单架飞机黑匣子,看具体故障原因 │
│ │
│ PyTorch Profiler: │
│ 定位:深度学习训练性能分析 │
│ 看什么:每层的耗时、CPU-GPU 传输、内存使用 │
│ │
│ VTune(Intel): │
│ 定位:CPU 性能分析 │
│ 看什么:CPU 利用率、OpenMP 效率、SIMD 利用率 │
│ │
└─────────────────────────────────────────────────────────────┘第2节:Nsight Systems——系统级 Timeline
基本用法
# 抓取性能数据
nsys profile --trace=cuda,nvtx,osrt \
--output=my_profile \
./my_cuda_app
# 生成 .qdrep 文件
# 用 Nsight Systems GUI 打开查看
# 或者命令行快速查看
nsys stats my_profile.qdrep输出解读
┌─────────────────────────────────────────────────────────────┐
│ nsys timeline 输出示例 │
├─────────────────────────────────────────────────────────────┤
│ │
│ GPU Core: │
│ ┌────────┬────────┬────────┬────────┬────────┐ │
│ │Kernel A│Kernel B│Kernel A│Kernel C│ Idle │ │
│ └────────┴────────┴────────┴────────┴────────┘ │
│ 0ms 5ms 10ms 15ms 20ms 25ms │
│ │
│ 观察:Kernel A 和 B 顺序执行,没有并行! │
│ → 可以用 CUDA Stream 并行化 │
│ │
│ GPU Utilization: 45% │
│ CPU-GPU Memcpy: 30% │
│ GPU Kernel: 45% │
│ Idle: 25% │
│ │
└─────────────────────────────────────────────────────────────┘常见问题的 timeline 特征
┌─────────────────────────────────────────────────────────────┐
│ 典型问题识别 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题1:CPU-GPU 传输瓶颈 │
│ Timeline: [H2D][GPU][D2H][H2D][GPU][D2H] │
│ 解决:用 CUDA Stream 重叠传输和计算 │
│ │
│ 问题2:GPU 空闲等待 │
│ Timeline: [GPU-Kernel][CPU-Preprocess][GPU-Kernel] │
│ 解决:Double Buffering,CPU 和 GPU 并行 │
│ │
│ 问题3:kernel 顺序执行 │
│ Timeline: [Kernel A][ Kernel B ] │
│ 解决:用多个 CUDA Stream 并行 │
│ │
│ 问题4:频繁的小 kernel 调用 │
│ Timeline: [K1][K2][K3][K4][K5][K6][K7][K8] │
│ 解决:合并多个小 kernel 为一个 │
│ │
└─────────────────────────────────────────────────────────────┘第3节:Nsight Compute——Kernel 级分析
基本用法
# 抓取 kernel 级别数据
ncu --set full \
--output my_kernel_profile \
./my_cuda_app
# 或者针对特定 kernel
ncu --set full \
--kernel-name "matrix_mul_kernel" \
--output my_kernel_profile \
./my_cuda_app输出指标解读
┌─────────────────────────────────────────────────────────────┐
│ ncu 输出指标解读 │
├─────────────────────────────────────────────────────────────┤
│ │
│ SM Utilization: │
│ 值:75% │
│ 含义:75% 的时间 SM 在执行有效指令 │
│ 低原因:分支分化、Warp 等待内存、Occupancy 低 │
│ │
│ Memory [GB/s]: │
│ Achieved:1800 GB/s │
│ 说明:实际内存带宽使用 │
│ 对比 HBM2e 理论:2000 GB/s → 利用率 90% │
│ │
│ SM Active Cycles: │
│ 值:high │
│ 含义:计算单元利用率高 │
│ │
│ L1/TEX Cache Hit Rate: │
│ 值:60% │
│ 含义:40% 的内存访问走了全局内存(慢) │
│ 解决:增加共享内存使用 │
│ │
│ Warp Occupancy: │
│ 值:48 warps/SM │
│ 含义:每 SM 平均 48 个 warp 在调度 │
│ 对比最大 64 warps/SM → Occupancy 75% │
│ │
└─────────────────────────────────────────────────────────────┘瓶颈定位流程
┌─────────────────────────────────────────────────────────────┐
│ ncu 瓶颈定位流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Step 1: 查看 SM Utilization │
│ ├─ 低(< 50%):→ Step 2 │
│ └─ 高(> 80%):→ Step 3 │
│ │
│ Step 2: 查看 Warp Execution Efficiency │
│ ├─ 低(< 95%):分支分化问题 │
│ └─ 高(> 95%):→ Occupancy 问题 │
│ │
│ Step 3: 查看 MemorySOL / ComputeSOL │
│ ├─ MemorySOL < 50%:内存带宽瓶颈 │
│ └─ ComputeSOL < 50%:计算单元瓶颈 │
│ │
└─────────────────────────────────────────────────────────────┘第4节:PyTorch Profiler——训练性能分析
基本用法
import torch
from torch.profiler import profile, ProfilerActivity
with profile(
activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
record_shapes=True,
profile_memory=True,
with_stack=True
) as prof:
# 训练循环
for batch in dataloader:
output = model(batch.input)
loss = criterion(output, batch.target)
loss.backward()
optimizer.step()
# 打印耗时最多的 10 个操作
print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=10))输出解读
┌─────────────────────────────────────────────────────────────┐
│ PyTorch Profiler 输出示例 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Name CUDA Time % │
│ ──────────────────────────────────────────────────────────│
│ cudaLaunchKernel 1000 ms 10%│
│ amp::fastMixedPrecisionKernel 2500 ms 25%│
│ layernorm_forward 1200 ms 12%│
│ embedding_forward 800 ms 8% │
│ tensor_expr_gpu_kernel 600 ms 6% │
│ aten::matmul 400 ms 4% │
│ ... │
│ │
│ 观察:amp::fastMixedPrecisionKernel 耗时最长(25%) │
│ → 可能是 Tensor Core 利用率低 │
│ │
└─────────────────────────────────────────────────────────────┘导出和可视化
# 导出 Chrome Trace 格式
prof.export_chrome_trace("trace.json")
# 在 Chrome 浏览器打开:chrome://tracing
# 可以看到每个操作的时间线和调用栈
# 导出内存快照
prof.export_memory_timeline("memory.html", device_index=0)
# 查看显存使用随时间的变化第5节:实用调试流程
┌─────────────────────────────────────────────────────────────┐
│ 性能调试完整流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 系统级:nsys timeline │
│ ├─ CPU-GPU 传输是否重叠? │
│ ├─ GPU 是否空闲等待? │
│ └─ kernel 是否并行执行? │
│ │
│ 2. Kernel 级:ncu profile │
│ ├─ SM Utilization 高还是低? │
│ ├─ 内存带宽利用率? │
│ └─ Warp 效率? │
│ │
│ 3. 热点分析: │
│ ├─ 哪个 kernel 占时间最多? │
│ ├─ 哪个操作显存占用最大? │
│ └─ 哪层耗时最长? │
│ │
│ 4. 针对性优化: │
│ ├─ 内存带宽瓶颈 → 合并访问、共享内存复用 │
│ ├─ Warp 效率低 → 减少分支分化 │
│ ├─ CPU-GPU 不同步 → Double Buffering │
│ └─ Occupancy 低 → 调整 block 大小 │
│ │
└─────────────────────────────────────────────────────────────┘"AI 可查 vs 必须理解"清单
AI 可查:
✅ nsys/ncu 的具体命令行参数
✅ PyTorch Profiler 的具体配置选项
✅ Chrome Trace 格式的解读方法
必须理解:
🔴 nsys = 系统级 timeline,定位 CPU-GPU 协同问题
🔴 ncu = kernel 级分析,定位单 kernel 性能瓶颈
🔴 SM Utilization 低 = warp 调度不足或等待内存
🔴 Memory SOL 低 = 内存带宽瓶颈
🔴 PyTorch Profiler = 训练性能分析,定位哪层最慢学习状态:🟡 开始学习
历史版本补充说明
本页保留课程合并前的工具介绍,命令、指标名称和界面可能随工具版本变化。
当前扩展版工具指南位于:
查阅历史内容时请注意:
- WPR 负责基于 ETW 采集 Windows 系统和应用事件;
- WPA 负责打开 ETL,分析 CPU、线程状态、I/O、内存和关键路径;
- VTune 从 Hotspots 进入 Threading、Memory Access 与 Microarchitecture;
- Nsight Systems 观察 CPU/GPU 时间线;
- Nsight Compute 下钻 NVIDIA 单个 Kernel;
- PyTorch Profiler 连接框架 Operator 与 CPU/CUDA 活动。
工具参数以当前安装版本的帮助和官方文档为准。 历史阈值不能机械套到新硬件,指标必须和耗时、工作负载及正确性一起解释。
推荐使用顺序:先用低开销系统级采集找到慢阶段,再对具体 CPU 函数、线程等待或 GPU Kernel 使用深度分析,修改后回到同一端到端基线复测。
WPR 与 WPA 的职责分离
WPR 是采集器,它根据 Profile 启用 ETW Provider、Kernel Event 和 Stack,并把结果写入内存循环缓冲区或 ETL 文件。WPA 是分析器,它打开 ETL 后提供时间线与可透视表格。CPU Usage (Sampled) 用采样栈定位执行热点,CPU Usage (Precise) 使用上下文切换事件区分 Running、Ready 和 Wait,并能沿唤醒关系追踪关键路径。两者配合适合 Windows 应用卡顿、启动、磁盘、内存和后台功耗问题。
WPR 的使用逻辑
采集前先明确现象和时间窗口,再通过 wpr -profiles 查看本机内置 Profile。可控短场景使用文件模式,偶发问题使用内存循环模式并在症状出现后立即停止。只启用回答问题所需的 Provider 和 Stack,防止采集本身产生大量 I/O。ETL 必须与应用构建、PDB 和复现步骤一起保存,否则 WPA 中只能看到地址或无法判断操作区间。
WPA 的使用逻辑
打开 ETL 后先缩放到用户感知的慢区间并过滤目标进程。CPU 很高时看 Sampled Stack,CPU 很低但操作很慢时看 Precise 的线程状态、Readying Thread 与 Wait Reason。文件读取问题同时查看 File I/O 和 Disk Usage,区分页缓存与物理设备。任何表格都应保留时间范围、分组列和聚合口径,避免截图脱离上下文。
VTune 的使用逻辑
VTune 通常从 Hotspots 开始,确认最耗时函数和调用路径。多线程扩展差时进入 Threading,查看并行度、同步等待和负载不均;怀疑 Cache、带宽或 NUMA 时进入 Memory Access;需要解释前端、推测和后端槽位时使用 Microarchitecture Exploration。分析名称和 Knob 随版本变化,命令应通过当前安装的 vtune -help collect 核对。
工具组合
WPA 擅长 Windows 全系统事件与线程因果,VTune 擅长 Intel CPU 应用源码和微架构,Nsight Systems 擅长 CPU/NVIDIA GPU 协同时间线,Nsight Compute 擅长单个 NVIDIA Kernel,PyTorch Profiler 负责框架 Operator 与 CPU/CUDA 活动的对应。没有一个工具可以一次回答全部问题。正确流程是先定位层次,再选择更深的采集。
历史版本边界
本页原始内容以 Nsight 和 PyTorch 为主,当前扩展版已经加入 WPR、WPA 与 VTune 的详细流程。历史截图中的百分比和阈值不能当作所有硬件通用标准,尤其是 SOL、Occupancy、Cache Hit 和 Warp 指标。复现时保存工具版本、采集命令、原始报告、输入和正确性结果,并用同一端到端基线验证优化。