1.5 从 CPU 到 GPU
同样一套思想,换到 GPU 上变成了什么样子,以及 Roofline 模型
学习目标
读完这一节,你应该能够:
- 对照说出 CPU 与 GPU 的存储层次及其管理方式的差异。
- 解释 GPU 为什么放弃大容量 Cache,改用程序员手工管理的片上存储。
- 写出 Roofline 模型,算出拐点,并判断一个算子受限在哪一侧。
- 把 FlashAttention 放进这个坐标系里,说清它到底省了什么。
一、两边的存储层次对照
先看一张对照表。左边你刚学完,右边是它的 GPU 版本。
| 层级 | CPU | GPU |
|---|---|---|
| 最快、最小 | 寄存器(编译器分配) | 寄存器(编译器分配,但每个线程的份额很小,且是稀缺资源) |
| 片上快速存储 | L1 / L2 Cache(硬件自动管理) | Shared memory / L1(程序员显式管理) |
| 大容量缓存 | L3(多核共享) | L2(全芯片共享) |
| 主存 | DRAM(几十 GB) | HBM(几十 GB,但带宽高得多) |
| 谁在搬运 | 硬件自动,程序不可见 | 常常需要你在代码里写搬运指令 |
最关键的差异不在容量,而在"谁来决定把什么放进快速存储里"。
CPU 上,Cache 对程序是完全透明的:你写 a[i],硬件自己决定要不要缓存它。 GPU 上,Shared memory 是一块你能用地址直接访问的暂存区:你必须自己写代码把数据搬进去、自己管理什么时候搬出。
👉 一句话概括:CPU 把 Cache 管理做成了自动挡,GPU 把它做成了手动挡。
二、GPU 为什么放弃大 Cache
这是一个很自然的问题:既然 Cache 有效,为什么 GPU 不堆一个大 Cache?
原因是面积和收益的权衡:
1. Cache 很占芯片面积。 SRAM 的每个存储单元要用多个晶体管,把 Cache 从小容量做大,占用的面积可能挤掉大量运算单元。GPU 的设计哲学是把面积优先给运算单元和带宽。
2. GPU 有别的办法隐藏延迟——海量线程。
CPU 遇到访存延迟时,主要靠乱序执行和预取在单条指令流内部找活干。但一个线程能提供的独立指令数量有限。
GPU 的做法完全不同:它同时挂着成千上万个线程。当某个线程组因为等数据而停住时,调度器立刻切换到另一个准备好的线程组。
既然延迟可以靠"换个人干活"来隐藏,那么 Cache 的收益就没那么大了;反过来,把这块 SRAM 交给程序员做成 scratchpad(暂存区),让他精确控制数据的搬运,收益更高。
💡 这就是为什么 GPU kernel 优化的核心动作是"手工把数据搬进 shared memory"。 你不是在"帮助 Cache",你是在替代 Cache 做决策。
三、于是,分块从"自动"变成"手写"
回顾 1.3 节的分块:CPU 上,你只是调整了循环顺序和分块大小,剩下的(把块搬进 Cache)是硬件自动做的。
GPU 上,同一件事变成:
1. 把 A 的一个 T×T 块从 HBM 载入 shared memory
2. 把 B 的一个 T×T 块也从 HBM 载入 shared memory
3. 线程之间同步一次(确保块已就位)
4. 在 shared memory 上完成这块的乘加
5. 同步,载入下一块这不是"让 Cache 帮忙",这是你自己在写 Cache。 分块的大小 直接取决于 shared memory 有多大(典型的几十 KB 到一百多 KB),而不是硬件的 Cache 容量——因为这一次是你说了算。
⚠️ 代价:手动管理意味着你必须自己保证正确性(同步、边界处理)。这也是 GPU kernel 比 CPU 代码难写的主要原因——不是数学更难,是并发与数据搬运的正确性更难。
四、合并访存:CPU Cache line 的 GPU 版本
上次说过,GPU 上一个 warp(通常是 32 个线程)如果访问连续的地址,硬件会把它们合并成一次内存事务;如果地址散开,就会拆成很多次事务。
这和 1.2 节的"每个 64 字节块被用到多少字节"是完全同构的:
| CPU | GPU |
|---|---|
| Cache line 64 字节 | 内存事务按 warp 的访问模式聚合 |
| 步长大 → 块内利用率低 | 地址分散 → 事务数成倍增加 |
| 循环顺序决定成败 | 线程与数据的映射方式决定成败 |
👉 "让同一时刻访问的数据在地址上挨着"——这条准则在两边都成立。
五、从 AMAT 到 Roofline
前面用的是 AMAT(平均访问时间),它是延迟视角:一次访问平均要等多久。
但 GPU 上有海量线程,延迟基本被隐藏掉了。真正决定性能的变成了吞吐视角:单位时间能搬多少数据、能做多少运算。
这就是 Roofline(屋脊线)模型。它只需要两个硬件参数:
- 峰值算力 (FLOP/s)
- 峰值带宽 (Byte/s)
然后用 计算强度 (1.1 节讲过,单位是 FLOP/Byte)预测实际能达到的性能:
这画出来是一个"屋顶":
性能 P
│
π ├──────────────●━━━━━━━━━━━━━━━━━ ← 算力天花板
│ ╱
│ ╱ ← 带宽斜线:P = β × I
│ ╱
│ ╱
└────┴────────────────────────────→ 计算强度 I
↑
拐点 I* = π / β拐点(ridge point)是两条线的交点:
怎么用它做判断:
| 情况 | 含义 | 该优化什么 |
|---|---|---|
| 落在斜线上,带宽受限 | 减少要搬的字节(量化、复用、增大 batch) | |
| 落在平线上,算力受限 | 减少运算、提高运算效率 | |
| 在拐点附近,两边都要看 | 优先改善其中较容易的一项 |
算一个例子(数字取典型量级):某数据中心 GPU 的 FP16 算力约 300 TFLOPS,显存带宽约 2 TB/s,则
也就是每搬 1 个字节,至少要做 150 次浮点运算,才配得上这块卡。
现在回头看 1.1 节的结论:LLM 推理的 decode 阶段,每读一个参数字节只做大约 1 到 2 次运算——,离拐点差两个数量级。所以它牢牢地卡在斜线上,是纯粹的带宽受限。
💡 Roofline 是整套教材最重要的一个模型。 后面每一篇讨论优化时,你都可以问一句:"它把 提上去了,还是把 的斜率变高了?"——几乎所有 AI 系统优化都能落进这个框里。
六、把 FlashAttention 放进这个坐标系
现在你手上有全部工具了。来看这个 AI Infra 最著名的优化。
朴素注意力的做法:计算 的注意力分数矩阵,中间要把它写回 HBM、再读回来若干次。在长序列下,这个中间矩阵比输入还大。
- 它做了什么?搬了大量本来可以不搬的数据。
- 换算力还是换带宽?纯纯的带宽浪费。
FlashAttention 的做法(用本篇的语言重述):
| 动作 | 对应到本篇的概念 |
|---|---|
| 把矩阵切成能放进片上存储的块 | 1.3 节的分块(blocking) |
| 每一块算完就直接参与下一步,不写回 HBM | 1.3 节的"在快存储里用够本" |
| 用 online softmax 避免二次遍历 | 消除对中间矩阵的依赖,从而不需要保存它 |
| 反向传播时重算而不是存下来 | 用计算换存储——算力是富余的,带宽是紧缺的 |
👉 一句话:FlashAttention 就是把 1.3 节的 Cache 分块思想搬到了 GPU 上,用在注意力这个算子上。
它没有减少浮点运算次数(甚至因为重算而增加了),但它把 HBM 的读写量从 降到了接近 级别,从而把算子从"带宽受限"推向了更接近"算力受限"的一侧。
这就是为什么理解本篇,比记住 FlashAttention 的三个步骤更重要。 步骤会忘,判断依据不会。
关键结论
- CPU 与 GPU 的核心差异不是容量,而是谁管理片上快速存储:CPU 硬件自动,GPU 程序员显式。
- GPU 用海量线程切换隐藏延迟,而不是靠大 Cache 提高命中率,因此把 SRAM 交给程序员做成 scratchpad。
- 分块在 GPU 上从"调循环顺序"变成"手写数据搬运",正确性也要自己保证。
- 合并访存是 CPU Cache line 准则的 GPU 版本:让同一时刻访问的地址挨着。
- Roofline:,拐点 。这是判断题的答案来源。
- FlashAttention = 把分块用到注意力上,用算力换带宽,把算子推向算力受限一侧。
自测问题
- 为什么 GPU 不像 CPU 那样堆一个大容量 Cache?给出两条理由。
- "GPU 上你是在替代 Cache 做决策"——这句话是什么意思?
- 某 GPU 算力 400 TFLOPS(FP16),带宽 3 TB/s。拐点是多少?一个计算强度为 30 FLOP/Byte 的算子受限在哪一侧?理论性能上限是多少?
- 一个算子同时被优化了两种方式:(甲) 减少 20% 的浮点运算;(乙) 减少 60% 的显存读写。如果它原本严重带宽受限,哪个更有效?如果它原本算力受限呢?
- FlashAttention 增加了浮点运算次数(因为重算),为什么反而更快?用 Roofline 解释。
- 为什么说"分块大小 在 CPU 上由 Cache 容量决定,在 GPU 上由 shared memory 大小决定"?这个区别的实际后果是什么?
延伸阅读
- Roofline 原始论文:Williams、Waterman、Patterson 的 Roofline: An Insightful Visual Performance Model for Multicore Architectures(CACM 2009)。本篇给的是简化版,原文有完整的推导和实测。
- FlashAttention:FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness(NeurIPS 2022)。第三篇会精读它——你现在已经有了读它需要的全部背景。
- 配套论文清单:第一模块的论文清单 里的 Roofline 与 TPU 论文。
← 上一节:1.4 程序优化的方法论 | 下一节 → 1.6 动手实验