GPU 只关心两件事:算得多快、搬得多快

所有训练加速的技术,最后都落在一个判断上:这一步慢,是因为算不过来,还是因为数据搬不过来?后面每一课都要用这个判断。

面试会这样问How do you tell whether a kernel is compute-bound or memory-bound? · Explain the roofline model. · Why is LLM inference memory-bound?

考过NVIDIA DevTech NG 2026(GPU memory 架构、matmul tiling,1p3a 1161658)· NVIDIA Senior System Software Engineer 2024("proving that LLMs are memory-intensive rather than purely compute-intensive")

先把 GPU 想成一个厨房

厨房GPUH100 的数
厨师炒菜的速度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 下才不会闲着。

一个概念、一条分界线

三个能手算的例子(BF16,每个数 2 bytes)

操作FLOPs读写 bytesintensity结论
4096 × 4096 的 matmul2 · 4096³ ≈ 1.37e11三个矩阵 × 4096² × 2 ≈ 1.0e8≈ 1365compute-bound,约 0.14 ms
elementwise add(y = a + b)每个元素 1 次读 2 个、写 1 个,共 6 bytes≈ 0.17memory-bound
decode,batch = 1(matrix × vector)每个 parameter 乘一次、加一次:2Ψ每个 parameter 2 bytes,全部读一遍:2Ψ≈ 1memory-bound

Ψ(psi)是模型的 parameter 个数,8B 模型 Ψ = 8e9。一般地,[M, K] × [K, N] 的 matmul:

你的经验第三行就是你用过的 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)的原因。

所以提速只有两条路

  1. compute-bound 的(大 matmul):用 BF16 或 FP8 跑满 Tensor Core,矩阵维度取 8 或 64 的倍数
  2. 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 现读两个数。