想象你是一家“全自动餐厅”的老板。后厨里有一百位普通厨师(CUDA 核心)守着各自的炉灶,大厅中央摆着一座魔法蒸锅(Tensor Core)——只要食材按 4×4 的方阵摆好,它一次能同时蒸熟几十道菜。最近餐厅出菜越来越慢,客人在催单。你凭直觉想“再买一口更大的锅”,但真正的问题可能根本不在锅:也许是传菜员(内存带宽)从仓库搬食材太慢,也许是厨师们在蒸锅前干等(停滞 stall),也许是洗、切、炒三个工位之间反复装箱入库(访存开销)浪费了时间。 一位效率顾问(Nsight Compute 等性能分析工具)在后厨装了一圈监控探头,记录每位厨师每一秒在干什么:锅开了多久、人等了几秒、食材在仓库与灶台之间走了几趟;再画一张“餐厅瓶颈速查表”(Roofline 模型)区分“锅的产能”与“上菜速度”谁在卡脖子;最后给出改造方案:把三个工位合并成一条流水线(算子融合),让食材不再落地。本文就带你走完这套“厨房效率大作战”的全流程。
掌握 Tensor Core 性能分析,你就能用数据说话:一眼定位 AI 算子的真实瓶颈,让每一次优化都精准命中。这是 GPU 性能工程师、训练优化工程师最硬核的看家本领。
可视化演示
动态展示利用率指标、Roofline模型、Stall原因诊断与优化迭代流程四个视角,直观理解Nsight Compute性能分析工作流。
本节配有交互动效(进入视口后自动加载,约 3KB)。
性能分析概览
老板安装监控后的第一件事,不是忙着下令改造,而是先把营业时间里的每一分钟都录下来,按“备菜、蒸制、上桌”切成片段回放。性能分析的起点同样如此:先用工具把 GPU 上发生的每一类活动记录下来,再回答一个朴素的问题——时间到底花在了哪里。
这个“厨房监控”就是 Nsight Compute。它工作在指令级别:对于一个 CUDA 内核,它报告哪些指令流水线在忙碌、哪些 warp 在等待、各级缓存与显存搬运了多少数据。和 nvidia-smi 只能看到整卡负载不同,Nsight Compute 把内核执行的每一拍都摊开在桌面上,是 Tensor Core 性能分析的主角工具。
完整的工作流通常有五步:采集(用 Nsight Compute 抓取热点内核)、归类(把时间按计算、访存、等待拆分)、定位(沿着指标树下钻到具体指令与停滞理由)、优化(实施针对性改造)、验证(复测并对比基线)。而这一切的前提,是先建立“总时间等于各内核时间之和”的分解意识。
就像老板先看哪一段工序耗时最长,分析者先把总时长拆到每个内核,才能锁定要深挖的目标——接下来的每一节,都在回答“拆开之后怎么看”。
- 总执行时间 T_total整个算子或应用在 GPU 上消耗的总时长,后续所有利用率计算都以它为分母。
- 求和符号 Σ表示把从第 1 个到第 n 个内核的执行时间逐项累加起来。
- 第 i 个内核时间一个深度学习算子往往被拆成多个 kernel,每个内核的耗时各占一份。
核心利用率
监控回放里,老板最关心那口魔法蒸锅:一整天下来,锅盖真正被蒸汽顶起的时间有多少?这个比例,就是 Tensor Core 利用率。锅虽然先进,但如果一天 8 小时只有 1.6 小时在冒热气,那再大的锅也等于摆设。
在 Nsight Compute 中,通过 Tensor 流水线的活跃周期计数,可以算出 Tensor Core 利用率:Tensor Core 忙碌时间除以内核总执行时间。它回答的是“计算单元在总时长中的忙碌占比”,而不是“计算能力有多强”。利用率低不代表硬件弱,只代表它没被喂饱。
利用率低通常有三类病根:一是算子形状与 tile 不匹配,矩阵数据喂不进 Tensor Core 的乘加阵列;二是访存供给不足,Tensor Core 在空转等待数据;三是占用率过低,活跃 warp 太少,无法用并行掩盖延迟。三种病因对应的药方完全不同——改布局、提带宽、调占用。
老板发现蒸锅睡着后,先要分辨是“没菜可蒸”还是“菜在路上堵着”。前者去看算子形态,后者把镜头对准传菜员——也就是下一节的内存带宽。
- 利用率 U_TCTensor Core 流水线保持忙碌的时间占比,是判断计算单元是否被有效使用的第一眼指标。
- TC 活跃时间 T_TC采样周期内 Tensor Core 矩阵运算单元处于工作状态的累计周期数。
- 总执行时间 T_total内核在 SM 上的全部耗时,包含计算、访存与各种等待周期。
- 百分比换算把比值放大 100 倍并加上百分号,得到我们熟悉的 0% 到 100% 的读数。
内存带宽分析
传菜员从仓库到厨房的通道,决定了每秒能搬多少食材。这条通道就是显存与 SM 之间的 HBM 总线,它的“宽度”就是内存带宽。如果通道已经塞满,而蒸锅还在饿肚子,问题就出在供给端而不是烹饪端。
带宽利用率的定义是实测带宽除以峰值带宽,其中实测带宽等于内核读写总字节数除以内核耗时。当这个比值逼近 1,说明数据通道已经满负荷运转,此时再多的算力也插不上手,必须从减少搬运量或提高局部性入手。
除了总量,还要看访存质量:合并访问能把相邻线程的请求拼成一整趟运输,随机跨步访问则让每次搬运都装载不足;L2 缓存命中相当于在厨房门口建了一个小冷库,许多食材不必每次都回大仓库取。Nsight Compute 的 Memory Workload Analysis 面板会同时给出 DRAM 吞吐、L1/L2 命中率等分层指标,正好用来做这套“物流审计”。
传菜通道一旦饱和,蒸锅就只能干等。要系统性回答“瓶颈到底在算力还是访存”,需要一张全局速查表——Roofline 模型。
- 带宽利用率 U_BW衡量内存子系统繁忙程度的指标,反映“传菜通道”的拥堵情况。
- 实际带宽 B_actual由内核读写数据的总字节数除以实测耗时计算得到,单位常用 TB/s 或 GB/s。
- 峰值带宽 B_peak硬件手册上的理论最大带宽,例如 A100 的 HBM 约为 1.6 TB/s,是一个恒定上限。
Roofline模型
顾问给老板画了一张图:横轴是“口味密度”,即一斤食材能做出几道菜;纵轴是出菜速度。图上有两条天花板:一条水平的“锅产能天花板”,一条斜的“上菜速度斜坡”。任何菜品都只能落在两条线形成的折线以下。
这就是 Roofline(屋顶线)模型。横轴是操作强度,即每搬运 1 字节数据能执行多少次浮点运算;纵轴是可达性能。水平线对应算力峰值,斜坡对应带宽峰值乘以操作强度,两条线的交点叫拐点。落在斜坡段的算子是访存受限,落在平台段的算子是算力受限,拐点附近则是理论上最均衡的甜蜜点。
以 A100 为例:FP16 Tensor Core 算力峰值约 312 TFLOPS,HBM 带宽约 1.6 TB/s,拐点约 195 FLOP/Byte。绝大多数 Transformer 里的逐元素算子操作强度都远低于这个值,所以天然落在访存受限区——这也是为什么“算子融合”会成为大模型优化的第一选择。
有了这张速查表,老板不再凭感觉改造:先查菜品落在哪个区,再决定是“修锅”还是“修路”。下一节继续用监控回答:即使知道了区域,具体停滞的每一秒又是怎么浪费的?
- 操作强度 I平均每搬运 1 字节数据能执行的浮点运算次数,单位 FLOP/Byte。
- 计算量 W算子总的浮点运算次数,单位 FLOP,是“菜谱”里总共有多少道工序。
- 访存量 Q算子读写显存的总字节数,单位 Byte,是“食材”总共被搬运了多少吨。
- 带宽限制下的性能每秒钟最多搬入 B_peak 字节,若每字节能做 I 次运算,上限就是两者相乘。
- 取最小值 min实际可达性能永远等于两条限制线中较低的那一条,像屋顶的檐口一样封顶。
- 算力峰值 P_peak硬件理论上每秒最多能完成的浮点运算数,例如 A100 的 FP16 峰值。
停滞原因诊断
回放监控时,老板发现了更多细节:不少厨师手举到一半停在原地发呆。有人眼巴巴等食材从传送带尽头送过来,有人等上一道工序的调料(数据依赖),有人被并排的同事挡住,还有人因为和搭档拿着两套菜谱而互相掣肘。
GPU 中 warp 的“发呆”在 Nsight Compute 里被归类为 Stall Reasons。常见的包括:long scoreboard(等待显存或 L2 返回数据)、short scoreboard(等待寄存器或算术单元结果)、barrier(等待同步)、branch resolving(等待分支解析)、math pipe throttle(算术流水线拥塞)。在 Warp State 面板里,这些原因会按占比列出,一眼就能看出“发呆的大头”。
停滞占比表示 warp 周期中花在等待上的比例。注意:SM 利用率高并不等于没有停滞——如果所有 warp 都在等内存,SM 依然显示忙碌,但真正干活的周期很少。诊断的要点是把停滞原因与上一节的瓶颈区域关联:访存受限通常伴随 long scoreboard 居高不下;算力受限则常见 math pipe throttle;分支发散往往对应数据排布不规整。
老板把发呆分为“等食材、等佐料、等位子、等口令”四类,每类都有不同解药:提高访存并行度、提升指令级并行、调节占用率、消除分支发散。对症下药之后,最后一步是减少“同一批食材被反复搬进搬出”——这就是算子融合。
- 停滞占比 R_stallwarp 状态为等待的周期占总周期的比例,是并行效率的“照妖镜”。
- 停滞周期 C_stall所有 warp 因各类原因停滞的周期计数之和,由 Nsight Compute 直接统计。
- 总周期 C_total内核执行期间的全部时钟周期,是停滞占比的分母基准。
算子融合优化
老板发现:原来洗菜、切菜、炒菜是三个独立工位,洗完的菜要装箱送回仓库,切菜时再从仓库取回,炒菜时再取一回。三个工位之间,食材在仓库进进出出,白白耗尽了传送带的运力,出菜慢的根源恰恰在这里。
算子融合正是把多个内核合并成一个:中间结果不再写回显存,而是留在寄存器、共享内存或 L2 缓存中,由下一个阶段直接消费。比如把 scale、relu、bias 三个逐元素算子合为一个内核,访存从六次(每个算子读一次写一次)降到两次(只读最开始的输入、只写最终的输出)。
在访存受限场景,融合带来的理论加速比约等于访存量下降的倍数。这正好把前三节的分析串成一条完整的优化建议流水线:先用 Roofline 判断方向,再用 stall 诊断补充证据;访存受限优先融合与提升局部性,算力受限则去 padding、降精度、减少冗余计算;每一步改造后都用 Nsight Compute 复测验证,形成闭环。
至此,老板拥有了完整的“厨房效率手册”:装监控(采集)→ 拆时间 → 查蒸锅 → 查通道 → 画速查表 → 分类发呆 → 流水线融合 → 复测验证。Tensor Core 性能分析,本质上就是这套“用数据定位、按瓶颈施策”的工程方法。
- 融合前访存量 Q_before每个内核各自读写中间结果的总字节数,搬运次数越多浪费越大。
- 融合后访存量 Q_after只保留第一个内核的输入读取与最后一个内核的输出写回,中间结果留在片上。
- 加速比 S_fusion当瓶颈是访存时,时间近似正比于访存量,因此下降倍数约等于加速倍数。
AI 应用场景
大模型训练中的 Tensor Core 调优
阶段:模型训练与性能优化
解决的问题:训练团队报告 GPU 利用率只有 30%,却说不清瓶颈在哪。传统做法是凭经验反复调整 batch size 和并行策略,盲目且低效。用 Nsight Compute 采集后,发现 MoE 层的 gate 网络分支发散严重,专家权重访存不连续导致 long scoreboard 高企;再结合 Roofline 判定为访存受限,通过专家权重重排与算子融合(把 gate 前的归一化与矩阵乘合并),有效 Tensor Core 利用率从 22% 提升到 41%,模型 FLOPs 利用率(MFU)提升约 40%。
具体案例:某 70B MoE 模型训练任务,原先 40 分钟一个 step;优化后缩短到 28 分钟,单轮训练节省数天 GPU 机时。
LLM 推理服务的访存瓶颈治理
阶段:推理部署与延迟优化
解决的问题:LLM 推理 decode 阶段一次只生成一个 token,计算强度很低,属于典型的访存受限:KV Cache 读取与权重搬运占主导。Roofline 分析显示算子大多落在斜坡段,一味增大 batch 反而让带宽更快耗尽。通过把 QKV projection 与 RoPE 算子融合,并用 4-bit 量化压缩 KV Cache,中间张量不再反复写回显存,单 token 延迟下降 35%,吞吐提升近一倍。
具体案例:某在线对话服务的 FP16 引擎在 batch=64 时 TTFT 抖动明显;改造后 P99 延迟从 210ms 降到 135ms,GPU 数量减半仍满足 SLA。
算子库开发中的 Roofline 回归测试
阶段:算子开发与基准测试
解决的问题:算子库维护者每次提交新 kernel,都需要判断性能是否逼近硬件极限。传统做法是与旧版本比速度,无法说明绝对优化空间。团队把 Roofline 理论峰值接入 CI 流水线:每次提交自动输出“当前算子距离屋顶线还有多少差距”,并标注是访存受限还是算力受限;超过阈值自动告警,引导开发者选择算子融合或改进访存布局。
具体案例:某图像预处理算子库在 CI 中暴露 0.6 倍屋顶线差距,经融合三个 elementwise kernel 后提升到 0.88 倍。
Python 演示
"""Roofline 瓶颈判定与算子融合收益估算演示"""import math COMPUTE_PEAK = 312.0 # 算力峰值 TFLOPS (FP16 Tensor Core)MEM_BW = 1.6 # 内存带宽峰值 TB/s (HBM) def arithmetic_intensity(flops, bytes_moved): """计算操作强度: 每搬运 1 字节数据能执行多少 FLOP""" return flops / bytes_moved def roofline_perf(flops, bytes_moved): """Roofline 模型: 可达性能取算力线与带宽线的较小值""" ridge = COMPUTE_PEAK / MEM_BW intensity = flops / bytes_moved perf = min(COMPUTE_PEAK, MEM_BW * intensity) kind = "算力受限" if intensity >= ridge else "访存受限" return perf, intensity, ridge, kind def fusion_benefit(kernels): """融合前后访存量对比: 中间结果不再写回显存""" before = sum(k["read"] + k["write"] for k in kernels) after = kernels[0]["read"] + kernels[-1]["write"] return before, after, before / after def main(): kernels = [ {"name": "scale", "read": 2.0, "write": 2.0}, {"name": "relu", "read": 2.0, "write": 2.0}, {"name": "bias", "read": 2.0, "write": 2.0}, ] before, after, speedup = fusion_benefit(kernels) print("== 算子融合收益 ==") print("融合前访存 {:.1f} GB, 融合后 {:.1f} GB".format(before, after)) print("访存下降 {:.2f} 倍, 访存受限时预估加速 {:.2f} 倍".format(speedup, speedup)) flops = 1.0e13 bytes_moved = after * 1.0e9 perf, intensity, ridge, kind = roofline_perf(flops, bytes_moved) print("\n== Roofline 判定 ==") print("操作强度 {:.1f} FLOP/Byte, 拐点 {:.1f} FLOP/Byte".format(intensity, ridge)) print("可达性能 {:.1f} TFLOPS, 瓶颈类型: {}".format(perf, kind)) if __name__ == "__main__": main()
COMPUTE_PEAK = 312.0声明屋顶线里的水平天花板:显卡每秒最多能算多少 TFLOP,这里对应 A100 FP16 峰值。MEM_BW = 1.6声明斜坡的斜率:内存每秒最多能搬多少 TB 数据,对应 A100 HBM 峰值带宽。ridge = COMPUTE_PEAK / MEM_BW求拐点:操作强度高于它就算力受限,低于它则访存受限。perf = min(COMPUTE_PEAK, MEM_BW * intensity)屋顶线核心公式:可达性能永远取两条限制线的较小值。kind = "算力受限" if intensity >= ridge else "访存受限"用拐点把算子分进两个瓶颈区域,决定优化方向。before = sum(k["read"] + k["write"] for k in kernels)融合前每个内核都要读写一遍显存,把三次搬运全部相加。after = kernels[0]["read"] + kernels[-1]["write"]融合后只在最前读一次、最后写一次,中间结果留在芯片内。roofline_perf(flops, bytes_moved)一次调用同时返回可达性能、操作强度、拐点与瓶颈类型。if __name__ == "__main__":让脚本既能被 import 也能直接运行,输出两类分析结果。
常见误区
误区我看了 Nsight Compute,Tensor Core 利用率才 20%,肯定是显卡太弱,换 A100、H100 就能解决。
正解换卡只能抬高“算力天花板”,但如果瓶颈在数据供给,性能几乎不会变。用 Roofline 一看,若算子落在访存受限区,换卡后水平线升高而斜坡不变,可达性能仍被带宽摁住。先做算子融合、调整访存布局,再考虑升级硬件,才是正确的顺序。
为何常见:人习惯把“利用率低”直接等同于“硬件不行”,忽略了利用率低往往是因为数据喂不饱。
误区我调优的目标就是摆脱“访存受限”,只要进入“算力受限”就代表优化成功了。
正解两种受限只是方向标,不是优劣标签。算力受限意味着已贴近算力天花板,进一步优化的空间很小;访存受限反而说明算力还有大量余量,算子融合、缓存优化、数据压缩等手段能带来倍数级收益。真正该追求的不是“换一种受限”,而是让性能点尽量贴近屋顶线的拐角。
为何常见:对“限制”二字的直觉判断,让人误以为“受限”就是坏结果。
误区我通过加 padding 把矩阵补得整整齐齐,利用率冲到 98%,任务应该更快了吧?可实际速度反而变慢了。
正解利用率是“时间占比”,不是“有效计算占比”。大量 padding 让 Tensor Core 在算大量的“零”,流水线确实忙碌,但真正有用的 FLOPs 占比很低。还应对比实际 FLOPs 与理论最小 FLOPs,评估有效计算的比例,而不是只盯一个占用率数字。
为何常见:单一 KPI 驱动的思维惯性,指标好看但真实收益不达预期,忽略了“有效”与“忙碌”的区别。
误区我已经把三个算子的 CUDA 代码写进同一个函数了,为什么 Nsight Compute 还是显示三个内核?
正解融合的本质是让中间结果留在片上存储(寄存器、共享内存、L2),消除“写回显存再读回”的往返。如果只是源码位置相邻,每个算子仍各自分配显存并单独 launch,那依然是多个内核。判断标准很简单:Nsight Compute 的 kernel 列表里是否只剩一个。
为何常见:望文生义,把源码组织方式与内核调度机制混为一谈。
误区我每次跑完只看 SM 利用率,90%,说明优化到位了,报告里其他东西没必要看。
正解SM 利用率高不代表没有浪费:如果所有 warp 都在 long scoreboard 等内存,SM 依然显示“忙”,但大部分周期都在空转。必须结合 Warp State、Stall Reasons、Tensor 流水线利用率、缓存命中率等多维指标交叉判断,才能定位真正的浪费。
为何常见:人眼偏好用单一数字做结论,而 GPU 性能问题恰恰是多因素耦合的。
面试题
Q1. 下列关于 Nsight Compute 的说法,正确的是?
- A.主要用于监测 GPU 温度功耗
- B.能够深入指令级剖析内核← 正确答案
- C.只能统计进程级整体负载
- D.是 NVIDIA 的自动调参库
答案:B。Nsight Compute 是 NVIDIA 的指令级性能剖析工具,能深入内核内部查看各流水线吞吐、warp 停滞、缓存命中等信息。功耗温控属于 nvidia-smi 或 DCGM 的范畴;进程级负载与自动调参也都不是它的定位。就像后厨的监控探头看的是每一位厨师的细动作,而不是门口测温枪读个体温。
Q2. Tensor Core 利用率的计算中,分母通常取?
- A.张量核心活跃时长
- B.核函数总执行时间← 正确答案
- C.显存数据搬运时间
- D.应用进程启动耗时
答案:B。利用率的分子是 Tensor Core 活跃时间,分母是整个内核在 SM 上的总执行时间(含等待与访存周期)。若把分母换成 TC 活跃时间会恒等于 100%;显存时间或启动耗时都不是该指标的分母。就像计算蒸锅开火率要用营业时间做分母,而不是用开火时间。
Q3. Roofline 模型的横轴通常表示?
- A.内存访问带宽峰值情况
- B.计算操作强度的大小← 正确答案
- C.芯片核心频率的高低
- D.线程块数量的多少
答案:B。Roofline 横轴是操作强度(FLOP/Byte),纵轴是可达性能。操作强度是算子的固有属性,由计算量与访存量决定,与硬件频率、带宽无关;带宽和频率只影响天花板线的位置。就像“口味密度”取决于菜谱本身,而不是锅的大小。
Q4. Roofline 中拐点的正确计算方式是?
- A.算力峰值除以带宽峰值← 正确答案
- B.带宽峰值除以算力峰值
- C.算力峰值乘以带宽峰值
- D.算力峰值加上带宽峰值
答案:A。拐点由两条限制线的交点解出:令带宽限制 B×I 等于算力峰值 P,可得 I = P / B。反过来、相乘或相加都会破坏量纲。拐点左侧访存受限,右侧算力受限,是决定优化方向的分水岭。
Q5. 某算子计算量 200 GFLOP,访存量 2 GB,拐点为 150 FLOP/Byte,其瓶颈属于?
- A.访存受限类型的瓶颈← 正确答案
- B.算力受限类型的瓶颈
- C.两者同时受限的瓶颈
- D.数据不足难以判断
答案:A。操作强度 I = 200×10^9 / (2×10^9) = 100 FLOP/Byte,小于拐点 150,落在斜坡段,属于访存受限。此时提升算力没有意义,应通过算子融合、减少访存量来拉高操作强度,让性能点沿斜坡向上爬。
Q6. 在 Warp 停滞原因中,long scoreboard 通常对应?
- A.等待显存或 L2 数据返回← 正确答案
- B.等待算术单元计算结果
- C.等待同步栅栏信号释放
- D.等待分支指令解析完成
答案:A。long scoreboard 指等待显存或 L2 这类“远距离”数据返回,是访存受限算子里最常见的停滞原因;等待算术单元结果属于 short scoreboard;barrier 与分支解析则是另外两类停滞。就像厨师等传送带尽头的食材,与等手边的调料是两种不同的发呆。
Q7. 算子融合带来加速的直接原因是?
- A.显著减少了计算量规模
- B.显著减少了中间结果访存← 正确答案
- C.提高了算术流水线精度
- D.增大了线程块的数量
答案:B。融合的本质是让中间结果留在片上存储(寄存器、共享内存、L2),不再写回显存又读回,因此直接减少访存量。计算量、精度、线程块数量都不是融合的直接目标。就像洗切炒三段合一条流水线,食材不再装箱入库。
Q8. Nsight Compute 能够报告的信息包括?
- A.各指令流水线的吞吐量← 正确答案
- B.Warp 停滞原因的分布← 正确答案
- C.L1 与 L2 缓存命中率← 正确答案
- D.主板上电容老化程度
答案:ABC。Nsight Compute 聚焦指令级性能:指令吞吐、warp 停滞原因、缓存命中率都在其报告范围内;主板电容老化属于硬件健康监测,不在性能剖析工具的职责内。选 D 的同学是把“硬件监控”和“指令剖析”混为一谈了。
Q9. 某算子在 A100 上实测:算力峰值 312 TFLOPS,带宽峰值 1.6 TB/s,操作强度 100 FLOP/Byte,预测可达性能约为?
- A.160TFLOPS← 正确答案
- B.200TFLOPS
- C.312TFLOPS
- D.100TFLOPS
答案:A。操作强度 100 小于拐点 195,属于访存受限。可达性能 = min(312, 1.6×100) = 160 TFLOPS,而不是算力峰值 312。这正好说明:当数据供给跟不上时,再强的算力也只能被带宽“摁”在斜坡上。
Q10. 下列哪些情况会导致 Tensor Core 利用率偏低?
- A.矩阵块排布未对齐尺寸← 正确答案
- B.内存带宽长期处于饱和← 正确答案
- C.活跃线程块数量过少← 正确答案
- D.算力已逼近理论峰值
答案:ABC。矩阵排布不对齐使 Tensor Core 难以形成有效的矩阵乘;带宽饱和使 TC 空等数据;活跃线程过少则无法用并行掩盖延迟——三者都会拉低利用率。而“算力已逼近峰值”说明 TC 正在满负荷工作,利用率不会低,因此 D 不选。