【CS336】lecture2 FLOPs|MFU|计算强度|Roofline model|通算隐藏
1. FLOPs 与硬件利用率
分析LLM的性能,计算量,都需要一个定量的指标。如果上过计算机组成原理或者体系结构,大概还记得FLOPs这个东西。
FLOP 是一次基本浮点运算,例如加法或乘法。注意两个容易混淆的术语:
- FLOPs:floating-point operations,表示完成了多少计算。
- FLOP/s:每秒完成多少浮点运算,表示硬件速度,也常写作 FLOPS。
简单来说,第一个是用来衡量模型计算量的,第二个是用来衡量硬件上限的。作为数量级参考,GPT-3 的训练约用了 3.14×10233.14\times10^{23}3.14×1023 FLOPs,GPT-4 据估计约为 2×10252\times10^{25}2×1025 FLOPs。
1.1 矩阵乘法的 FLOPs
设
X∈RB×D,W∈RD×K, X\in\mathbb{R}^{B\times D},\qquad W\in\mathbb{R}^{D\times K}, X∈RB×D,W∈RD×K,
计算 Y=XWY=XWY=XW 时,每个 (i,j,k)(i,j,k)(i,j,k) 组合包含一次乘法和一次加法,因此
FLOPs≈2BDK. \text{FLOPs}\approx2BDK. FLOPs≈2BDK.
具体计算可以使用如下代码,注意这里我们算出来的是一个线性层的实际吞吐率,这既不是FLOPs,也不是硬件FLOPS,可以叫Achieved FLOPS,实际达到的计算吞吐率。
B, D, K = 16384, 32768, 8192
x = torch.ones(B, D, device="cuda")
w = torch.randn(D, K, device="cuda")
actual_num_flops = 2 * B * D * K
actual_time = benchmark(lambda: x @ w)
actual_flop_per_sec = actual_num_flops / actual_time
硬件规格中的峰值 FLOP/s 强烈依赖 dtype。例如 H100 的 sparse bf16 峰值约为 1979 TFLOP/s,dense 计算约取其一半。
1.2 Model FLOPs Utilization(MFU)
衡量一个模型的吞吐表现时更常用的指标是MFU,这表示一个模型在某个特定硬件上的表现。
MFU=实际 FLOP/s硬件标称 FLOP/s. \text{MFU}=\frac{\text{实际 FLOP/s}}{\text{硬件标称 FLOP/s}}. MFU=硬件标称 FLOP/s实际 FLOP/s.
MFU 达到 0.5 通常已经不错。MFU 很少接近 1,是因为计算单元不一定总有数据可算;需要进一步分析 compute 与 memory bandwidth 的关系。
2. Arithmetic Intensity 与 Roofline

GPU上一次计算通常包含:
- 从 memory 把输入传到计算单元。
- 执行计算。
- 把输出写回 memory。
总时间同时受 计算单元性能 accelerator speed(FLOP/s)和 数据传输带宽 memory bandwidth(bytes/s)影响。若通信与计算能完美重叠(这一般是可以达到的,GPU上一个任务等待取数据时,计算单元会转而执行其他准备好的任务),则
T=max(bytesbandwidth,FLOPspeak FLOP/s). T=\max\left(\frac{\text{bytes}}{\text{bandwidth}}, \frac{\text{FLOPs}}{\text{peak FLOP/s}}\right). T=max(bandwidthbytes,peak FLOP/sFLOPs).
定义:
arithmetic intensity=workload FLOPstransferred bytes, \text{arithmetic intensity}=\frac{\text{workload FLOPs}}{\text{transferred bytes}}, arithmetic intensity=transferred bytesworkload FLOPs,
accelerator intensity=peak FLOP/smemory bandwidth. \text{accelerator intensity}=\frac{\text{peak FLOP/s}}{\text{memory bandwidth}}. accelerator intensity=memory bandwidthpeak FLOP/s.
- arithmetic intensity 小于 accelerator intensity:memory-bound。
- arithmetic intensity 大于 accelerator intensity:compute-bound。
接下来我们来根据这个模型,具体计算几个算子的bound类型,以 bf16 为例,每个值占 2 bytes。
2.1 ReLU
对 nnn 个元素执行 ReLU,需要读 xxx、写 yyy,并进行约 nnn 次比较:
bytes=4n,FLOPs=n,intensity≈14. \text{bytes}=4n,\qquad \text{FLOPs}=n, \qquad \text{intensity}\approx\frac14. bytes=4n,FLOPs=n,intensity≈41.
它远低于 H100 的 accelerator intensity(一般在20-500,根据数据类型不同而变化),因此是 memory-bound。
2.2 GELU
GELU 每个元素大约需要 20 FLOPs,总的FLOPs为20n20n20n,但数据移动仍约为 4n4n4n bytes:
intensity≈5. \text{intensity}\approx5. intensity≈5.
它的 arithmetic intensity 高于 ReLU,但仍是 memory-bound。因此若两个操作分别以孤立 kernel 运行,GELU 未必明显比 ReLU 慢,瓶颈都是数据搬运。
2.3 Dot product
两个长度为 nnn 的 bf16 vector 做 dot product:
bytes≈4n+2,FLOPs=2n−1, \text{bytes}\approx4n+2,\qquad \text{FLOPs}=2n-1, bytes≈4n+2,FLOPs=2n−1,
所以 intensity 约为 1/21/21/2,仍是 memory-bound。
2.4 Matrix-vector product
长度为 nnn 的 vector 乘 n×nn\times nn×n matrix:
bytes=2n+2n2+2n,FLOPs=n(2n−1). \text{bytes}=2n+2n^2+2n,\qquad \text{FLOPs}=n(2n-1). bytes=2n+2n2+2n,FLOPs=n(2n−1).
当 nnn 很大时 intensity 约为 1,仍然 memory-bound。LLM推理的 decode 阶段大量执行类似操作,这也是 inference 经常受 memory bandwidth 限制的原因。
5.5 Matrix-matrix multiplication
两个 n×nn\times nn×n matrix 相乘:
bytes=6n2,FLOPs=n2(2n−1), \text{bytes}=6n^2,\qquad \text{FLOPs}=n^2(2n-1), bytes=6n2,FLOPs=n2(2n−1),
所以
intensity≈n3. \text{intensity}\approx\frac n3. intensity≈3n.
matrix 足够大时,它会成为 compute-bound,并充分占用 accelerator。Transformer 训练的大型 matmul 正属于这种情况。
2.6 Roofline plot
把前面的分析画成图如下,roofline这个名字是因为画出来的线是一段线性,然后到达一个水平阶段,像一个屋顶的形状。

roofline plot 的横轴是计算强度arithmetic intensity,纵轴是实际 FLOP/s。每条分段直线代表一种硬件,转折点就是 accelerator intensity,即从 memory-bound 过渡到 compute-bound 的位置。
理想化情况下:
MFU=min(1,arithmetic intensityaccelerator intensity). \text{MFU}=\min\left(1, \frac{\text{arithmetic intensity}}{\text{accelerator intensity}} \right). MFU=min(1,accelerator intensityarithmetic intensity).
这个模型给我们的指导意义是,优化算子性能时,先分析他在哪一段,如果在内存bound时,我们应该增大计算强度,先不用考虑优化计算操作;等到了计算bound阶段,则不用考虑增大计算强度了,可以考虑减少计算操作。
并且,这个线标志的是在特定计算强度下的上界,如果计算强度不变时,没达到这条线的上界,往往是因为前提没达到,也就是没有实现计算和通信的完美重叠,需要我们设计流水线来掩盖通信延迟。
2.7 通算隐藏
通算隐藏又是另一个深奥的问题,这里简单讲讲,我们前面的公式
T=max(bytesbandwidth,FLOPspeak FLOP/s).
T=\max\left(\frac{\text{bytes}}{\text{bandwidth}},
\frac{\text{FLOPs}}{\text{peak FLOP/s}}\right).
T=max(bandwidthbytes,peak FLOP/sFLOPs).
总时间把数据搬运时间和计算时间取最大值了,这需要有专用通信硬件,并且需要良好的异步设计,让通信和计算可以并行。如果没有专用硬件,只能实现通信计算并发,最好结果是总时间=通信时间+计算时间,并发只能省去通信另一端还没准备好时的阻塞时间。
例如下图,启动了两个cuda stream,可以理解两个异步线程,这是科学计算中的经典问题:网格计算

在网格计算(如偏微分方程求解、物理模拟)或大模型分布式计算(如 3D 并行中的 Halo Exchange 边缘交换)中,数据通常被划分为两部分:
-
Boundary(边界数据):这部分数据在计算完后,必须立刻发送给邻近的 GPU,否则隔壁的 GPU 就无法开始下一步计算。
-
Inner(内部数据):这部分计算完全在 GPU 本地内部完成,不需要与外界通信,计算完了也无需发送。
因为边界数据计算完毕后需要进行通信,而通信有延迟,因此让一号stream先计算边界数据,然后发起一个异步的通信请求,交换边界数据。这个通信不仅要发,还要接收相邻网格的数据,相邻网格和当前网格不在同一个线程块,或者不在一个GPU上,是异步计算的,中间有一段时间,通信处于阻塞。此时可以让异步的二号stream进行网格内部的计算。
如果有专用的通信硬件,两个流可以并行进行,最后一起结束,计算单元开始计算边界,然后无缝转到计算inner,通信收发操作交给通信硬件执行;如果没有专用硬件,这个异步设计也有收益,可以在一号stream通信阻塞时,让二号stream的计算抢占计算单元,把中间阻塞的那段时间充分利用上。
更多推荐
所有评论(0)