Lesson 01 · CUDA 性能调优 · 建立核心心智模型
调优一个慢 kernel 时,新手最常犯的错误是:凭直觉乱优化。 加 shared memory、调 block 大小、展开循环……结果性能纹丝不动。 原因是:每个 kernel 都被某一种资源卡住,而你优化了另一种资源。 本节你将掌握调优的第一性原理——先判断瓶颈类型,再动手。
一个 GPU kernel 要干两件事:搬数据(从显存读写)和 算数据(在 SM 上做浮点/整数运算)。 GPU 的这两种能力是分开的、各有上限:
关键洞察:这两件事同时进行、互相重叠。kernel 的实际耗时,取决于哪一件先做不完—— 那就是它的瓶颈。优化非瓶颈的那一边,总耗时不会变。
就像水管接水:水龙头流量(带宽)和水桶大小(算力)决定接满一桶水的时间。 如果水龙头细,你换个更大的桶毫无意义——你是「带宽瓶颈」。
怎么知道一个 kernel 撞哪堵墙?看它的 算术强度(Arithmetic Intensity, AI):
这个比值是 kernel 自身的算法属性,和 GPU 无关。举两个 AI 里的典型例子:
| 算子 | 特征 | 算术强度 |
|---|---|---|
逐元素加法 c = a + b | 读 8 字节,算 1 次 | 极低(≈0.125) |
| 大矩阵乘 GEMM | 每个元素被复用 N 次 | 高(可达几十~上百) |
这解释了一个 AI 工程师天天遇到的现象:逐元素算子、归一化、softmax 几乎总是访存瓶颈, 而大矩阵乘才有机会成为计算瓶颈。[1]
把算术强度放横轴、可达性能放纵轴,就得到 Roofline 图——一张能一眼看出瓶颈的图:
性能 (FLOP/s)
▲
峰值算力 ┤ ___________________ ← 计算屋顶(水平线)
│ /
│ / ← 访存屋顶(斜线,斜率=带宽)
│ /
│ / memory-bound │ compute-bound
│ / │
│ / │
└─┴──────────────────────────────────▶ 算术强度 (FLOP/Byte)
↑ ridge point(脊点)
峰值算力 ÷ 带宽。现代数据中心卡的脊点高得吓人。例如 A100:峰值 FP32 约 19.5 TFLOP/s,带宽约 1.5 TB/s, 脊点 ≈ 13 FLOP/Byte。[1] 这意味着绝大多数 AI 算子都落在斜线左侧——访存瓶颈。 这就是为什么「访存优化」是 CUDA 调优的重头戏。
下面 4 道题不要看上文,凭记忆判断。点选项立刻看反馈。 假设 GPU 脊点 = 13 FLOP/Byte。
你有数据中心卡,强烈建议做这一步,把抽象变具体。用 Nsight Compute 跑任意一个 kernel:
# 编译你的程序后,用 ncu 抓 Speed of Light 段
ncu --section SpeedOfLight ./your_program
# 报告里看这两行:
# Compute (SM) Throughput → 计算利用率
# Memory Throughput → 访存利用率
# 哪个接近 100%,就是哪个瓶颈。
Nsight Compute 的 Speed of Light 段其实就是 roofline 的自动化版本:它直接告诉你 kernel 贴着哪堵墙。[2] 下一节我们会专门拆解怎么读这份报告。
📘 CUDA C++ Best Practices Guide ——调优的权威圣经。本节相关的是开头的「Assess(评估)」与「Memory Optimizations」章节, 核心思想就是:先测量定位瓶颈,再优化。这是你接下来反复回看的第一资源。