Roofline 模型:判断算子是 compute-bound 还是 memory-bound
前面几篇反复说”访存密集”“算力密集”,但怎么客观判断一个算子属于哪一类?盲目优化常常白费力气——给一个受带宽限制的 kernel 加算力优化没有任何效果。Roofline 模型用一张图、一个指标,把算子定位到它的性能天花板上,告诉你优化该往哪使劲。这是动手优化前的必备一步。
一、算术强度:一个决定性的比值
算术强度(Arithmetic Intensity, AI) 定义为算子执行的浮点运算数与它从 global memory 搬运的字节数之比,单位 FLOP/byte:
\[ \text{AI} = \frac{\text{FLOPs}}{\text{Bytes moved}} \]
它衡量”每搬一字节数据,能做多少计算”。AI 低意味着搬得多算得少,瓶颈在带宽;AI 高意味着搬一次数据反复计算,瓶颈在算力。
几个例子:
- 向量加法
c=a+b:1 次加法,搬 12 字节(读 2 写 1),\(\text{AI} = 1/12 \approx 0.083\)。极低,必然受带宽限制。 - \(N \times N\) 矩阵乘:约 \(2N^3\) FLOP,朴素实现搬运量巨大、AI 低;tiling 后数据复用,AI 随分块尺寸上升到几十甚至更高。
- 逐元素激活(如 GELU):每元素几次运算、搬 8 字节,AI 仍然很低,受带宽限制。
二、Roofline:两条线和一个脊点
把性能(GFLOP/s)对算术强度(FLOP/byte)作双对数图,硬件的上限是两条线的下包络:
- 斜线(带宽屋顶):\(\text{性能} \le \text{峰值带宽} \times \text{AI}\)。AI 低时,性能被带宽锁死,随 AI 线性上升。
- 水平线(算力屋顶):\(\text{性能} \le \text{峰值算力}\)。AI 高时,性能被算力锁死,不再增长。
两条线相交处叫脊点(ridge point),其横坐标 \(\text{AI}_{\text{ridge}} = \dfrac{\text{峰值算力}}{\text{峰值带宽}}\)。脊点左边是 memory-bound 区,右边是 compute-bound 区。
RTX 3060 Ti 的脊点:\(16197 \text{ GFLOP/s} \div 448 \text{ GB/s} \approx 36 \text{ FLOP/byte}\)。也就是说,算术强度低于约 36 的算子受带宽限制,高于 36 的受算力限制。绝大多数深度学习的访存类算子(element-wise、归一化、注意力的部分阶段)AI 远低于 36,落在带宽区。
三、实测:在本卡上画出经验 Roofline
理论 Roofline 是两条直线,真实硬件能不能贴上去?用一个可调算术强度的 kernel 验证:每个线程读一个 float、做 \(K\) 次相关的 FMA(每次 2 FLOP)、写回一个 float。改变 \(K\) 就改变 AI(\(= 2K/8 = K/4\)),测每个 \(K\) 下的实测 GFLOP/s。
float x = in[i];
for (int k = 0; k < K; ++k) x = x*a + b; // 2 FLOP/次,相关链不被消除
out[i] = x;
RTX 3060 Ti 实测(\(n=2^{24}\),256 线程/block,CUDA event 中位数):
| \(K\) | AI (FLOP/byte) | 实测 GFLOP/s | 实测带宽 |
|---|---|---|---|
| 1 | 0.25 | 97 | 388 GB/s |
| 8 | 2 | 788 | 394 GB/s |
| 32 | 8 | 3093 | 387 GB/s |
| 128 | 32 | 10782 | 337 GB/s |
| 256 | 64 | 12483 | 195 GB/s |
| 1024 | 256 | 13999 | 55 GB/s |
把这些点画到 Roofline 上:
几个观察:
- 低 AI 区(\(K \le 32\)):实测带宽稳定在约 390 GB/s(接近峰值),GFLOP/s 随 AI 线性增长——完美贴着带宽屋顶。这些 kernel 受带宽限制,算力大量空闲。
- 高 AI 区(\(K=1024\),AI=256):达到 13999 GFLOP/s,是理论峰值 16197 的 86%,已经逼近算力屋顶。此时带宽掉到 55 GB/s——根本不缺带宽,缺的是算力。
- 过渡区(\(K=128 \sim 256\),AI=32~64):正好在脊点附近,从带宽限制转向算力限制。
这张实测图证明了 Roofline 不只是理论:硬件确实在两个区各自贴着对应的屋顶。
四、怎么用 Roofline 指导优化
拿到一个算子,三步定位:
- 算 AI:估算 FLOP 和 global 搬运字节,得到算术强度,和脊点(本卡约 36)比较。
- 测实际性能:用 profiler 测实测 GFLOP/s 和带宽利用率,看落在屋顶线哪个位置、离屋顶多远。
- 决定方向:
这套判断尤其能避免一类常见浪费:给一个明明受带宽限制的 element-wise kernel 反复调指令、换算法,性能纹丝不动——因为瓶颈根本不在算力。
五、提高算术强度:优化的本质
对受带宽限制的算子,最有效的优化是提高算术强度,把它从带宽区往脊点推。手段都是让数据复用发生在片上:
- 算子融合:把
matmul → bias → 激活合成一个 kernel,中间结果留在寄存器/shared,不写回 global。少搬一轮数据,AI 直接翻倍以上。kernel fusion 篇 专门讲。 - tiling:GEMM 把子块搬进 shared 反复用,AI 从朴素的 \(O(1)\) 提到分块尺寸量级。GEMM 篇。
- 重算换访存:FlashAttention 宁可重算一部分中间量,也不把大矩阵写回显存,本质是用算力换带宽、提高有效 AI。FlashAttention 篇。
六、模型的边界
Roofline 是一阶模型,几个简化要记住:
- 它假设带宽和算力可以独立打满,实际还受 occupancy、延迟、指令组合影响,所以实测点常在屋顶线下方一些。
- “峰值带宽”用 DRAM 带宽时,没考虑 L2 命中。工作集落进 L2 的算子(第 03 篇 实测约 3.4 TB/s)有效屋顶更高,需要用分层 Roofline 分析。
- Tensor Core 有独立的、远高于 FP32 的算力屋顶,分析它们要用对应精度的峰值,见 第 11 篇。
七、小结与下一步
- 算术强度 = FLOP / 搬运字节,是判断算子瓶颈的核心指标。
- Roofline 由带宽斜线和算力水平线组成,脊点 = 峰值算力/峰值带宽(本卡约 36 FLOP/byte),左边受带宽限制、右边受算力限制。
- 本卡实测经验 Roofline 验证了这一点:低 AI 贴带宽(约 390 GB/s),高 AI 逼近算力峰值 86%。
- 优化受带宽限制的算子,本质是通过融合、tiling、重算提高算术强度。
Roofline 给方向,但要落到具体 kernel 的瓶颈,还得靠 profiler 看真实指标。下一篇讲 Nsight 调优工作流:怎么读 Nsight Compute 和 Nsight Systems,把瓶颈定位到指令和访存级别。
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【GPU 算子工程】GEMM:从朴素实现到 shared memory tiling 与寄存器分块
GEMM 是 GPU 算子优化的标杆。在 RTX 3060 Ti 上实测四个版本:朴素 990、shared tiling 1309、寄存器分块 64 达 4447、128 达 6375 GFLOP/s(峰值 39%)。讲清每一步优化提高的是什么,以及为什么数据复用是关键。
【GPU 算子工程】Kernel Fusion 与 epilogue:减少 HBM 往返
融合通过减少中间结果的 HBM 往返提速 memory-bound 算子。实测逐元素链融合的加速比随链长线性增长(k=16 时 16.8 倍)。讲清逐元素融合、归约融合、GEMM epilogue 融合,以及什么时候不该融合。
GPU 高性能算子工程
从 GPU 执行模型与内存层次出发,系统讲解如何写出并调优高性能 CUDA 算子:访存合并、occupancy、Roofline、Nsight 调优,reduction/GEMM/Tensor Core/FlashAttention 核心算子实现,以及 Triton、CUTLASS、kernel fusion 与算子库工程。
【GPU 算子工程】全景:算子工程在 AI 计算栈的位置
从框架一行 matmul 到 PTX/SASS,拆开 AI 计算栈的分层:框架算子、算子库、手写 kernel、编译器生成。回答工程师什么时候才需要自己写或调 kernel,以及本系列的实验环境与方法。