GPU 只关心两件事:算得多快、搬得多快
所有训练加速的技术,最后都落在一个判断上:这一步慢,是因为算不过来,还是因为数据搬不过来?后面每一课都要用这个判断。
先把 GPU 想成一个厨房
| 厨房 | GPU | H100 的数 |
|---|---|---|
| 厨师炒菜的速度 | Tensor Core 的算力 | 989 TFLOPS(BF16),每秒 989 万亿次运算 |
| 城外的大仓库 | HBM(global memory) | 80 GB,每秒能运 3.35 TB |
| 手边的案板 | SRAM(shared memory / L1) | 每个 SM 约 256 KB,很快但很小 |
989 ÷ 3.35 ≈ 295,也就是每从城外运来 1 byte 的菜,厨师要炒约 295 下才不会闲着。
一个概念、一条分界线
- Arithmetic intensity = FLOPs ÷ 从 HBM 读写的 bytes,也就是“每运 1 byte 菜炒几下”
- Ridge point:H100 BF16 约为 295。低于它是 memory-bound,厨师在等菜;高于它是 compute-bound,运输在等厨师
- Roofline:能达到的速度 = min(峰值算力, intensity × bandwidth)。画出来先是斜线(受搬运限制),再是平线(受算力限制),拐点就是 ridge point
三个能手算的例子(BF16,每个数 2 bytes)
| 操作 | FLOPs | 读写 bytes | intensity | 结论 |
|---|---|---|---|---|
| 4096 × 4096 的 matmul | 2 · 4096³ ≈ 1.37e11 | 三个矩阵 × 4096² × 2 ≈ 1.0e8 | ≈ 1365 | compute-bound,约 0.14 ms |
elementwise add(y = a + b) | 每个元素 1 次 | 读 2 个、写 1 个,共 6 bytes | ≈ 0.17 | memory-bound |
| decode,batch = 1(matrix × vector) | 每个 parameter 乘一次、加一次:2Ψ | 每个 parameter 2 bytes,全部读一遍:2Ψ | ≈ 1 | memory-bound |
Ψ(psi)是模型的 parameter 个数,8B 模型 Ψ = 8e9。一般地,[M, K] × [K, N] 的 matmul:
- FLOPs = 2MKN,bytes = 2(MK + KN + MN)(读 A、读 B、写 C,每个数 2 bytes)
- 捷径:intensity 大致等于“每个数被重复用了几次”。方阵每个数用 4096 次,intensity 上千;elementwise 每个数只用 1 次,intensity 不到 1
你的经验第三行就是你用过的 vLLM。8B 模型 16 GB 的 weight 放不进 SRAM,每生成一个 token 都要从 HBM 读一遍:16 GB ÷ 3.35 TB/s ≈ 4.8 ms,所以单个请求最多每秒约 200 个 token。batch 开到 B,读一次 weight 给 B 个请求共用,intensity 也变成约 B。这就是 vLLM 要拼 batch(continuous batching)的原因。
所以提速只有两条路
- compute-bound 的(大 matmul):用 BF16 或 FP8 跑满 Tensor Core,矩阵维度取 8 或 64 的倍数
- memory-bound 的(GeLU、LayerNorm、softmax、dropout、decode):少搬
- Kernel fusion:bias → GeLU → dropout 分成 3 个 kernel,要读写 HBM 各 3 次,融成 1 个 kernel 只要各 1 次,快约 3 倍
- Tiling:先把一块数据搬到案板(SRAM)上反复用完再放回去,不要切一刀就跑一趟仓库。matmul 的 tiling 和 FlashAttention 都是这个思路
- 加大 batch:让同一份 weight 多用几次
后面还有第三种“搬”:卡与卡之间
第 3 课开始用多张卡,多出卡与卡之间的 communication。机内走 NVLink(每卡每个方向 450 GB/s),跨机走 InfiniBand(约 50 GB/s),慢约 9 倍。判断方法不变,算和搬谁的时间长谁就是瓶颈。办法是少搬,或者边算边搬(overlap)。
30 秒英文回答
Whether a kernel is compute-bound or memory-bound depends on its arithmetic intensity: FLOPs per byte moved from HBM. The roofline says attainable throughput is min(peak FLOPs, intensity × bandwidth). On an H100, that's 989 TFLOPS over 3.35 TB/s, a ridge point of about 300 FLOPs per byte. Large matmuls sit far above it, so they're compute-bound and we feed them to Tensor Cores in BF16 or FP8. Elementwise ops, normalization and batch-1 decode sit near 1, so they're memory-bound; we speed them up by moving fewer bytes: kernel fusion, tiling in shared memory like FlashAttention, and larger batches.
自测
为什么说 LLM inference 是 memory-intensive?(NVIDIA 原题)
decode 每生成一个 token 要读 2Ψ bytes 的 weight(8B 模型 16 GB),只算约 2Ψ FLOPs,intensity ≈ 1,远低于约 300。所以速度由 HBM bandwidth 决定。
[4096, 4096] × [4096, 16] 的 matmul 是哪种 bound?
FLOPs = 2 · 4096 · 4096 · 16 ≈ 5.4e8;bytes ≈ 2 · (4096² + 2 · 4096 · 16) ≈ 3.4e7;intensity ≈ 16,所以是 memory-bound。这正是 decode 时 batch = 16 的情形,batch 要到约 300 才会变成 compute-bound。
LayerNorm 是哪种 bound?怎么加速?
memory-bound。每个元素读 2 bytes、写 2 bytes,中间只做约 8 次运算,intensity ≈ 8 ÷ 4 = 2,远低于 295。它也不是 matmul,用不上 Tensor Core。办法是和前后的 elementwise 操作融成一个 kernel。
tiling 为什么能加速 matmul?
一个 T × T 的 tile 搬进 shared memory 之后,每个数被用 T 次,intensity 随 T 变大。naive 版每做一次乘加都去 HBM 现读两个数。
想看细一点 6.24 matmul 与 roofline(tiling 的推导和 CUDA 代码)· 6.23 GPU memory hierarchy(register、SRAM、L2、HBM 的大小和 latency)· 2.8 GPU 为什么比 CPU 快