AI Infra 自学教材

1.5 从 CPU 到 GPU

同样一套思想,换到 GPU 上变成了什么样子,以及 Roofline 模型

学习目标

读完这一节,你应该能够:

  1. 对照说出 CPU 与 GPU 的存储层次及其管理方式的差异。
  2. 解释 GPU 为什么放弃大容量 Cache,改用程序员手工管理的片上存储。
  3. 写出 Roofline 模型,算出拐点,并判断一个算子受限在哪一侧。
  4. 把 FlashAttention 放进这个坐标系里,说清它到底省了什么。

一、两边的存储层次对照

先看一张对照表。左边你刚学完,右边是它的 GPU 版本。

层级CPUGPU
最快、最小寄存器(编译器分配)寄存器(编译器分配,但每个线程的份额很小,且是稀缺资源)
片上快速存储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 的做法完全不同:它同时挂着成千上万个线程。当某个线程组因为等数据而停住时,调度器立刻切换到另一个准备好的线程组。

用线程级并行(TLP)隐藏延迟,而不是靠提高命中率\text{用线程级并行(TLP)隐藏延迟,而不是靠提高命中率}

既然延迟可以靠"换个人干活"来隐藏,那么 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。 分块的大小 TT 直接取决于 shared memory 有多大(典型的几十 KB 到一百多 KB),而不是硬件的 Cache 容量——因为这一次是你说了算

⚠️ 代价:手动管理意味着你必须自己保证正确性(同步、边界处理)。这也是 GPU kernel 比 CPU 代码难写的主要原因——不是数学更难,是并发与数据搬运的正确性更难


四、合并访存:CPU Cache line 的 GPU 版本

上次说过,GPU 上一个 warp(通常是 32 个线程)如果访问连续的地址,硬件会把它们合并成一次内存事务;如果地址散开,就会拆成很多次事务。

这和 1.2 节的"每个 64 字节块被用到多少字节"是完全同构的:

CPUGPU
Cache line 64 字节内存事务按 warp 的访问模式聚合
步长大 → 块内利用率低地址分散 → 事务数成倍增加
循环顺序决定成败线程与数据的映射方式决定成败

👉 "让同一时刻访问的数据在地址上挨着"——这条准则在两边都成立。


五、从 AMAT 到 Roofline

前面用的是 AMAT(平均访问时间),它是延迟视角:一次访问平均要等多久。

但 GPU 上有海量线程,延迟基本被隐藏掉了。真正决定性能的变成了吞吐视角:单位时间能搬多少数据、能做多少运算。

这就是 Roofline(屋脊线)模型。它只需要两个硬件参数:

  • 峰值算力 π\pi(FLOP/s)
  • 峰值带宽 β\beta(Byte/s)

然后用 计算强度 II(1.1 节讲过,单位是 FLOP/Byte)预测实际能达到的性能:

P=min(π, β×I)P = \min(\pi,\ \beta \times I)

这画出来是一个"屋顶"

性能 P

π ├──────────────●━━━━━━━━━━━━━━━━━  ← 算力天花板
  │            ╱
  │          ╱   ← 带宽斜线:P = β × I
  │        ╱
  │      ╱
  └────┴────────────────────────────→ 计算强度 I

      拐点 I* = π / β

拐点(ridge point)是两条线的交点:

I=πβI^{*} = \frac{\pi}{\beta}

怎么用它做判断

情况含义该优化什么
I<II < I^{*}落在斜线上,带宽受限减少要搬的字节(量化、复用、增大 batch)
I>II > I^{*}落在平线上,算力受限减少运算、提高运算效率
III \approx I^{*}在拐点附近,两边都要看优先改善其中较容易的一项

算一个例子(数字取典型量级):某数据中心 GPU 的 FP16 算力约 300 TFLOPS,显存带宽约 2 TB/s,则

I=300×10122×1012150 FLOP/ByteI^{*} = \frac{300 \times 10^{12}}{2 \times 10^{12}} \approx 150 \ \text{FLOP/Byte}

也就是每搬 1 个字节,至少要做 150 次浮点运算,才配得上这块卡。

现在回头看 1.1 节的结论:LLM 推理的 decode 阶段,每读一个参数字节只做大约 1 到 2 次运算——I1I \approx 1离拐点差两个数量级。所以它牢牢地卡在斜线上,是纯粹的带宽受限。

💡 Roofline 是整套教材最重要的一个模型。 后面每一篇讨论优化时,你都可以问一句:"它把 II 提上去了,还是把 β×I\beta \times I 的斜率变高了?"——几乎所有 AI 系统优化都能落进这个框里。


六、把 FlashAttention 放进这个坐标系

现在你手上有全部工具了。来看这个 AI Infra 最著名的优化。

朴素注意力的做法:计算 N×NN \times N 的注意力分数矩阵,中间要把它写回 HBM、再读回来若干次。在长序列下,这个中间矩阵比输入还大。

  • 它做了什么?搬了大量本来可以不搬的数据。
  • 换算力还是换带宽?纯纯的带宽浪费。

FlashAttention 的做法(用本篇的语言重述):

动作对应到本篇的概念
把矩阵切成能放进片上存储的块1.3 节的分块(blocking)
每一块算完就直接参与下一步,不写回 HBM1.3 节的"在快存储里用够本"
用 online softmax 避免二次遍历消除对中间矩阵的依赖,从而不需要保存它
反向传播时重算而不是存下来用计算换存储——算力是富余的,带宽是紧缺的

👉 一句话FlashAttention 就是把 1.3 节的 Cache 分块思想搬到了 GPU 上,用在注意力这个算子上。

它没有减少浮点运算次数(甚至因为重算而增加了),但它把 HBM 的读写量从 O(N2)O(N^2) 降到了接近 O(N)O(N) 级别,从而把算子从"带宽受限"推向了更接近"算力受限"的一侧。

这就是为什么理解本篇,比记住 FlashAttention 的三个步骤更重要。 步骤会忘,判断依据不会。


关键结论

  1. CPU 与 GPU 的核心差异不是容量,而是谁管理片上快速存储:CPU 硬件自动,GPU 程序员显式。
  2. GPU 用海量线程切换隐藏延迟,而不是靠大 Cache 提高命中率,因此把 SRAM 交给程序员做成 scratchpad。
  3. 分块在 GPU 上从"调循环顺序"变成"手写数据搬运",正确性也要自己保证。
  4. 合并访存是 CPU Cache line 准则的 GPU 版本:让同一时刻访问的地址挨着。
  5. RooflineP=min(π, β×I)P = \min(\pi,\ \beta \times I),拐点 I=π/βI^{*} = \pi / \beta。这是判断题的答案来源。
  6. FlashAttention = 把分块用到注意力上,用算力换带宽,把算子推向算力受限一侧。

自测问题

  1. 为什么 GPU 不像 CPU 那样堆一个大容量 Cache?给出两条理由。
  2. "GPU 上你是在替代 Cache 做决策"——这句话是什么意思?
  3. 某 GPU 算力 400 TFLOPS(FP16),带宽 3 TB/s。拐点是多少?一个计算强度为 30 FLOP/Byte 的算子受限在哪一侧?理论性能上限是多少?
  4. 一个算子同时被优化了两种方式:(甲) 减少 20% 的浮点运算;(乙) 减少 60% 的显存读写。如果它原本严重带宽受限,哪个更有效?如果它原本算力受限呢?
  5. FlashAttention 增加了浮点运算次数(因为重算),为什么反而更快?用 Roofline 解释。
  6. 为什么说"分块大小 TT 在 CPU 上由 Cache 容量决定,在 GPU 上由 shared memory 大小决定"?这个区别的实际后果是什么?

延伸阅读

  • Roofline 原始论文:Williams、Waterman、Patterson 的 Roofline: An Insightful Visual Performance Model for Multicore Architectures(CACM 2009)。本篇给的是简化版,原文有完整的推导和实测。
  • FlashAttentionFlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness(NeurIPS 2022)。第三篇会精读它——你现在已经有了读它需要的全部背景。
  • 配套论文清单第一模块的论文清单 里的 Roofline 与 TPU 论文。

← 上一节:1.4 程序优化的方法论 | 下一节 → 1.6 动手实验

On this page