9.3 性能分析工具:torch.profiler、Nsight Systems 与 Nsight Compute
推理场景性能分析方法论:torch.profiler 算子级分析、Nsight Systems 全链路分析、Nsight Compute Kernel 级下钻、vLLM 内置 profiling
压测告诉你”慢”,分析工具告诉你”为什么慢”。9.3 讲三层分析工具:torch.profiler(算子级,快上手)、Nsight Systems(全链路时间线,看调度与空闲)、Nsight Compute(kernel 内部,看算力/带宽利用)。方法论核心:从粗到细逐层下钻,先定位到阶段,再定位到算子,最后定位到 kernel 内部。
📑 目录
- 1. 三层分析模型:从阶段到 Kernel
- 2. torch.profiler:算子级快速定位
- 3. Nsight Systems:全链路时间线
- 4. Nsight Compute:Kernel 内部下钻
- 5. vLLM 内置 Profiling 与日志
- 6. 推理场景的典型瓶颈图谱
- 7. 分析实战流程:一次完整的性能排障
- 📝 总结
- 🎯 自我检验清单
- 📚 参考资料
1. 三层分析模型:从阶段到 Kernel
第 1 层:阶段级(Nsight Systems / vLLM 日志)
请求在 Prefill / Decode / 调度 / 网络 / 排队 各花多少时间?
→ 定位"慢在哪个阶段"
第 2 层:算子级(torch.profiler / Nsight Systems kernel view)
阶段内的哪些算子(QKV GEMM、Attention、FFN、采样)耗时最多?
→ 定位"哪个算子"
第 3 层:Kernel 内部(Nsight Compute)
算子的 GPU kernel 是否吃满?瓶颈是算力、带宽、延迟还是 launch 开销?
→ 定位"为什么慢"
📌 关键点:永远从第 1 层开始。推理优化的常见错误是跳过阶段分析直接下钻 kernel——“Attention kernel 慢”的结论如果放到整体看只是 2% 的时间占比,就是白忙。先找大头,再钻细节。
2. torch.profiler:算子级快速定位
2.1 基本用法
import torch
with torch.profiler.profile(
activities=[
torch.profiler.ProfilerActivity.CPU,
torch.profiler.ProfilerActivity.CUDA,
],
schedule=torch.profiler.schedule(wait=1, warmup=2, active=5),
on_trace_ready=torch.profiler.tensorboard_trace_handler("./trace"),
) as prof:
for step in range(8):
# 推理一 batch(decode 步)
output = model.generate(...)
prof.step()
# 命令行/Notebook 查看:chrome://tracing 或 tensorboard
prof.key_averages().table(sort_by="cuda_time_total", row_limit=30)
2.2 解读关键字段
| 字段 | 含义 |
|---|---|
Self CUDA Time | 该算子自身的 GPU 时间(排除子操作)——先看这个 |
CUDA Time Total | 含子操作的总时间 |
CPU Total | CPU 侧耗时(launch 开销、数据搬运) |
CUDA Mem | 显存分配量 |
Call Stack | 调用栈(定位到代码行) |
2.3 推理场景的看表顺序
- 按 Self CUDA Time 排序,看前 5 的算子(GEMM/Attention/采样/量化反量化?)
- 算占比:top1 占 prefill/decode 总时间多少?(>50% 才值得深挖)
- 看 CPU 时间 vs CUDA 时间:CPU > CUDA 说明 launch-bound(kernel 启动开销掩盖计算)——小 batch 场景常见
- 看显存分配热点:
torch.empty/copy频繁 → 碎片与搬运问题
💡 提示:torch.profiler 的输出可以直接在 TensorBoard(
tensorboard --logdir ./trace)里交互看时间线,支持按算子过滤和 GPU 利用视图——比 chrome://tracing 更适合深挖。
3. Nsight Systems:全链路时间线
3.1 是什么
NVIDIA 系统级 profiler:抓 CPU/GPU 时间线 + CUDA API + 内核活动 + 内存拷贝 + 通信(NCCL)。不深入 kernel 内部,但能看到”谁在等谁”。
nsys profile -o trace_out --trace=cuda,nvtx,osrt \
vllm serve meta-llama/Llama-3.1-8B-Instruct
# 或对已运行进程 attach
nsys profile --attach-pid <pid> -o trace_out
3.2 推理场景最值钱的三个视图
| 视图 | 看什么 | 典型结论 |
|---|---|---|
| Timeline(默认) | GPU 空闲段与 kernel 间隙 | “GPU 在等 CPU 喂数据”(launch-bound) |
| CUDA API 视图 | API 调用时长与重叠 | cudaMemcpy 串行搬运 |
| NCCL 视图 | 通信与计算是否重叠 | TP 场景通信间隙(6.2 的通信账) |
3.3 推理场景的典型发现
GPU 时间线长段空白 + CPU 忙
→ launch-bound:kernel 太小/太多,CPU 调度不过来
(对策:算子融合、加大 batch、CUDA Graph)
kernel 之间规则性小空隙
→ 通信或同步点:TP 的 allreduce、PD 分离的传输
(对策:通信计算重叠、减少同步)
prefill 与 decode 的 GPU 片段交织
→ 两者互抢算力(未分离架构)
(对策:第 7 章的 PD 分离)
📌 关键点:Nsight Systems 回答的是”时间去哪了”(调度、等待、搬运、通信),Nsight Compute 回答”kernel 本身为什么慢”。推理排障先 Nsight Systems——大多数推理性能问题出在”没在算”而不是”算得慢”。
4. Nsight Compute:Kernel 内部下钻
4.1 是什么
Kernel 级 profiler:分析单个 CUDA kernel 的 SM 占用、内存带宽、指令吞吐、warp 停滞原因。
# 只 profile 指定 kernel(避免海量输出)
ncu --kernel-name regex:.*flash_attn.* --launch-count 3 -o kern_out \
<推理命令>
# 或对运行中进程
ncu --target-processes all -o kern_out --kernel-name-base demangled <cmd>
4.2 关键指标
| 指标 | 含义 | 判断 |
|---|---|---|
| Compute (SM) Throughput | SM 算力利用率 | >80% = 算力饱和 |
| Memory Throughput | 显存带宽利用率 | >80% = 带宽饱和 |
| Achieved Occupancy | 实际 SM 占用率 | 低 → 并发度不足 |
| Warp Stall Reasons | warp 等待原因 | Long Scoreboard(等显存)/ Barrier(同步) |
| Launch Statistics | 启动开销 | 小 kernel 的启动占比 |
4.3 推理场景的四个典型诊断
带宽饱和 + 算力低 → 访存密集型(decode 的 KV 读取、量化模型的权重读取)
算力饱和 + 带宽低 → 计算密集型(大 batch GEMM)
两者都低 + occupancy 低 → 并发不足(batch 小、序列短、wave 不满)
两者都低 + stall 高 → 延迟受限(依赖链、小 GEMM 的 tail effect)
📌 关键点:decode 阶段几乎总是带宽瓶颈(每 token 读全部权重,计算量小);prefill 大 batch 时是算力瓶颈。Ncu 数据要配合这个先验判断——“decode 的 GEMM 带宽 90%“是正常的,重点看是不是异常地算力受限(如量化精度导致的非对齐访问)。
5. vLLM 内置 Profiling 与日志
5.1 服务端 profiler 配置
vllm serve <model> \
--profiler-config '{"start_profiler_on_next_step": true, "num_steps_to_profile": 5, "output_dir": "./profiles"}'
配合压测端 vllm bench serve --profile(9.2)在真实负载下抓 trace。
5.2 关键日志指标(无需工具先看这些)
# 启动日志:显存分布(权重 / KV 池 / 激活)
Memory profiling results: total_gpu_memory=80.0GiB ...
weights=16.1GiB, kv_cache=60.0GiB, ...
# 请求日志:调度信息
INFO: Received request ... prompt=120 tokens, sampling_params=...
INFO: Finished request ... generated=200 tokens, TTFT=..., TPOT=...
- KV 池大小:决定并发上限(4.x 的 gpu_memory_utilization 调这里)
- 每请求 TTFT/TPOT:不用起任何工具就能看真实负载的分布——先看日志,再上 profiler
💡 提示:把 vLLM 的
/metrics(Prometheus 格式)接入 Grafana:vllm:num_requests_running、vllm:gpu_cache_usage_perc、vllm:time_to_first_token_seconds等——日常监控用 metrics,深度排障用 profiler,两者分工。
6. 推理场景的典型瓶颈图谱
┌─────────────────────────────────────────────────────────┐
│ 现象 │ 优先检查 │
├─────────────────────────────────────────────────────────┤
│ TTFT 高(prefill 慢) │ batch 太大?chunked prefill?│
│ │ 长 prompt?APC 命中率? │
│ TPOT 高(decode 慢) │ 带宽饱和?batch 过大? │
│ │ 量化精度? │
│ 吞吐低但延迟正常 │ 调度器排队?max-num-seqs 小? │
│ GPU 有空闲但利用率高 │ launch-bound(小 kernel) │
│ 多卡扩展性差 │ NCCL 通信未重叠?拓扑不对? │
│ 显存峰值突增 │ 激活值峰值?KV 碎片? │
└─────────────────────────────────────────────────────────┘
📌 关键点:瓶颈图谱的每一行都对应前面章节的内容——第 2 章(APC/调度)、4.x(量化/显存)、5.x(投机解码)、6.x(并行/通信)、7.x(PD 分离)。性能分析不是孤立技巧,是把全书知识串起来的诊断过程。
7. 分析实战流程:一次完整的性能排障
假设现象:并发 64 时 TPOT P99 从 30ms 涨到 90ms。
第 1 步(阶段级,Nsight Systems / metrics)
看 GPU 时间线:prefill 与 decode 交织?
→ 发现 decode 步频繁被 prefill 插入打断(未分离架构)
第 2 步(算子级,torch.profiler)
decode 步算子排序:Self CUDA Time top3
→ QKV GEMM 38%、FlashAttention 22%、FFN 30%
第 3 步(Kernel 级,Nsight Compute)
对 QKV GEMM 下钻:Memory Throughput 85%(带宽饱和)
→ 确认:decode 是带宽瓶颈,batch 变大后每 token 权重读取翻倍
结论与对策
带宽受限 + 并发上升 → decode 算力被 prefill 抢占 + 带宽饱和
→ 对策:PD 分离(第 7 章)或限制 prefill 频率(调度策略);
或对权重做更低精度量化(4.x)降低带宽需求
💡 提示:每次排障都写”现象 → 证据 → 结论 → 对策 → 验证”五段式记录。证据(trace 截图、指标数值)要存档——这是 9.5 回归门禁的退化定位素材库。
📝 总结
- 三层下钻:阶段(Nsight Systems)→ 算子(torch.profiler)→ kernel(Nsight Compute);先找大头
- torch.profiler:Self CUDA Time 排序;CPU>CUDA = launch-bound
- Nsight Systems:回答”时间去哪了”——调度、等待、搬运、通信
- Nsight Compute:回答”kernel 为什么慢”——算力/带宽/占用/停滞
- decode 几乎总是带宽瓶颈,prefill 大 batch 是算力瓶颈
- vLLM 自带:profiler 配置 + 日志 TTFT/TPOT + /metrics Prometheus
- 瓶颈图谱:每行现象对应全书某章知识——分析是知识的诊断应用
🎯 自我检验清单
- 三层分析模型各自回答什么问题?为什么先做阶段级?
- torch.profiler 的看表顺序?(排序→占比→CPU/CUDA→显存)
- launch-bound 的特征与对策?
- Nsight Systems 三个视图各看什么?典型发现?
- Nsight Compute 四个典型诊断组合(算力/带宽/占用/停滞)?
- 为什么 decode 是带宽瓶颈?(每 token 读全部权重)
- 用五段式(现象→证据→结论→对策→验证)复述 7 的案例?
📚 参考资料
- PyTorch Profiler 文档(https://pytorch.org/docs/stable/profiler.html)
- NVIDIA Nsight Systems 用户指南(https://docs.nvidia.com/nsight-systems/)
- NVIDIA Nsight Compute 用户指南(https://docs.nvidia.com/nsight-compute/)
- vLLM 官方文档:Profiling(
--profiler-config)(https://docs.vllm.ai/en/latest/features/profiling.html) - 现代 GPU 性能分析(kernel 瓶颈分类方法论)