如何扩展你的模型(1):Roofline 分析入门
中英对照第 1 篇:roofline 模型是理解一切性能问题的起点——算力、带宽、显存三者的取舍如何决定一个算法能跑多快。含算术强度、带宽受限与计算受限、以及一组可以自己动手算的练习题。左栏原文,右栏译文。
本篇属于系列 如何扩展你的模型(How To Scale Your Model) · 第 1 篇
原文:How To Scale Your Model(Google DeepMind,作者 Jacob Austin、Sholto Douglas、Roy Frostig、Anselm Levskaya、Charlie Chen、Sharad Vikram、Federico Lebron、Peter Choy、Vinay Ramasesh、Albert Webson、Reiner Pope)。
本站是中英对照排版:左栏是英文原文,右栏是对应的中文译文。不好直译的术语(roofline、strong scaling、ICI、MXU、KV cache、FSDP、TP 等)保留英文写法;原文配图全部保留。
本文是系列《如何扩展你的模型(How To Scale Your Model)》的第 1 篇,共 13 篇。系列目录。
原文以 MIT 许可证发布,版权归 Google LLC;本译文仅作学习交流之用,如有错漏以原文为准。
Where Does the Time Go?
时间都去哪了?
Let’s start with an extremely simple question: why does an algorithm take 50ms instead of 50s or 5ms? What is actually happening within the model that takes substantial time and how long should we expect it to take?
我们先从一个极其简单的问题开始:为什么一个算法要花 50ms,而不是 50s 或 5ms?模型内部究竟发生了什么、占掉了大部分时间,而我们应该预期它花多久?
Computation: A deep learning model is effectively a bunch of matrix multiplications, each composed of floating-point multiplication and addition ‘operations’ (FLOPs). Our accelerator speed determines how long these take to compute:
计算(Computation): 一个深度学习模型本质上就是一堆矩阵乘法,每个矩阵乘法由若干浮点乘法和加法「运算」(FLOPs)组成。加速器的速度决定了这些运算要算多久:
$$\begin{equation} T_\text{math} = \frac{\text{Computation FLOPs}}{\text{Accelerator FLOPs/s}} \end{equation}$$
For instance, an NVIDIA H100 can perform about 9.89e14 bfloat16 (bf16 is short for bfloat16, a 16-bit floating point format often used in ML.) FLOPs/s while a TPU v6e can perform 9.1e14 FLOPs/s. (H100s and B200s can usually only achieve around 80-85% of the claimed peak FLOPs, while TPUs can get closer to 95% in normal use.) That means doing 1e12 FLOPs on an H100 will take (roughly) 1e12 / 9.89e14 = 1.01ms and 1e12 / 9.1e14 = 1.1ms on a TPU v6e. (Note that these chips are priced differently, and this comparison does not normalize to cost.)
举例来说,一块 NVIDIA H100 大约能跑 9.89e14 次 bfloat16(bf16 是 bfloat16 的简称,一种 ML 中常用的 16 位浮点格式)FLOPs/s,而一块 TPU v6e 能跑 9.1e14 FLOPs/s。(H100 和 B200 通常只能达到标称峰值 FLOPs 的 80–85%,而 TPU 在正常使用中能接近 95%。)也就是说,在 H100 上做 1e12 次 FLOPs 大约要花 1e12 / 9.89e14 = 1.01ms,在 TPU v6e 上花 1e12 / 9.1e14 = 1.1ms。(注意这些芯片的定价不同,这个比较没有做成本归一化。)
Communication within a chip: Within an accelerator, tensors need to be transferred between accelerator memory (HBM) and the compute cores. You’ll see the bandwidth of this link referred to as “HBM bandwidth”. (NVIDIA also calls this “memory bandwidth.”) On an H100, this is about 3.35TB/s and on TPU v6e this is about 1.6TB/s.
片内通信: 在单颗加速器内部,张量需要在加速器内存(HBM)和计算核心之间搬运。这条链路的带宽通常被称为「HBM 带宽」。(NVIDIA 有时也把它叫「显存带宽」。)在 H100 上大约是 3.35TB/s,在 TPU v6e 上大约是 1.6TB/s。
Communication between chips: When we distribute a model across multiple accelerators, tensors frequently need to be transferred between them. There are often a few options for this on our hardware (ICI, DCN, and PCIe), each with different bandwidths.
片间通信: 当我们把一个模型分布到多颗加速器上时,张量常常需要在它们之间传输。硬件上通常有几种选择(ICI、DCN 和 PCIe),带宽各不相同。
Whether the communication is within a chip or between chips, we measure this in bytes/s and estimate the total communication time with:
无论通信发生在片内还是片间,我们都用 bytes/s 来度量它,并用下式估算总通信时间:
$$\begin{equation} T_\text{comms} = \frac{\text{Communication Bytes}}{\text{Network/Memory Bandwidth Bytes/s}} \end{equation}$$
Typically (but not always), computation within a single chip can be overlapped with communication within a chip and between chips. This means we can lower-bound training and inference time by using the maximum of computation and communication time. We can also upper-bound with their sum. In practice, we optimize against the maximum as the algebra is simpler and we can usually come close to this bound by overlapping our communication and computation. If we optimize with the maximum in mind then the lower and upper bounds differ by at most a factor of 2 since $T_\text{math} + T_\text{comms} \leq 2 * \max(T_\text{math}, T_\text{comms})$. We then increase accuracy beyond this by modeling ‘overlap regions’ and overheads, which can be informed by profiling your specific model and target system.
通常(但不总是)单颗芯片内部的算力可以和片内、片间的通信重叠。这意味着我们可以用「计算时间和通信时间的较大者」作为训练与推理时间的下界,也可以用两者之和作为上界。实践中我们按「较大者」来优化,因为代数上更简单,而且通过重叠通信与计算通常能逼近这个下界。若按较大者来优化,上下界的差距最多是 2 倍,因为 $T_\text{math} + T_\text{comms} \leq 2 * \max(T_\text{math}, T_\text{comms})$。之后我们会通过建模「重叠区间」和各种开销来进一步提高精度,而这些可以借助对你自己模型与目标系统的 profile 来标定。
$$\begin{equation} T_\text{lower}=\max(T_\text{math}, T_\text{comms}) \end{equation}$$
$$\begin{equation} T_\text{upper} = T_\text{math} + T_\text{comms} \end{equation}$$
If we assume we can perfectly overlap communication and computation, when $T_\text{math} > T_\text{comms}$, we see full utilization from our hardware. We call this being “compute-bound”. When $T_\text{comms} > T_\text{math}$, we tend to be “communication-bound” (We’ll use “communication-bound”, “comms-bound”, “memory-bound”, and “bandwidth-bound” interchangeably throughout this book.) and at least some fraction of our accelerator FLOPs/s is wasted waiting for data to be passed around. One way to tell if an operation will be compute or communication-bound is to look at its “arithmetic intensity” or “operational intensity”.
如果我们假设通信和计算可以完美重叠,那么当 $T_\text{math} > T_\text{comms}$ 时,硬件会被充分利用,我们称之为「计算受限(compute-bound)」。当 $T_\text{comms} > T_\text{math}$ 时,我们往往处于「通信受限(communication-bound)」(本书中「communication-bound」「comms-bound」「memory-bound」「bandwidth-bound」这几个词可互换使用。),至少有一部分加速器 FLOPs/s 被浪费在等待数据搬运上。判断一个运算是计算受限还是通信受限,一个办法是看它的「算术强度(arithmetic intensity)」或「运算强度(operational intensity)」。
Definition: the arithmetic intensity of an algorithm is given by the ratio of the total FLOPs it performs to the number of bytes it needs to communicate — either within a chip or between chips.
定义: 一个算法的算术强度,等于它执行的总 FLOPs 与它需要通信的字节数之比——无论是在片内还是片间。
$$\begin{equation} \text{Arithmetic Intensity} = \frac{\text{Computation FLOPs}}{\text{Communication Bytes}} \end{equation}$$
Arithmetic intensity measures the “FLOPs per byte” of a given operation. To a first order, when our arithmetic intensity is high, $T_\text{math}$ is large compared to $T_\text{comms}$ and we typically use most of the available FLOPs. When the opposite is true, we spend more time on comms and waste FLOPs. The point where this crossover happens is the “peak arithmetic intensity” of our hardware, the ratio of peak accelerator FLOPs/s to accelerator bandwidth.
算术强度度量的是一个运算的「每字节 FLOPs」。一阶近似下,当算术强度高时,$T_\text{math}$ 相对 $T_\text{comms}$ 很大,我们通常能用满大部分可用 FLOPs;反之则会花更多时间在通信上、浪费 FLOPs。发生这个转折的点,就是硬件的「峰值算术强度」,即加速器峰值 FLOPs/s 与加速器带宽之比。
$$\begin{align*} T_\text{math} > T_\text{comms} \Leftrightarrow \frac{\text{Computation FLOPs}} {\text{Accelerator FLOPs/s}} > \frac{\text{Communication Bytes}}{\text{Bandwidth Bytes/s}} & \\[0.5em] \Leftrightarrow \frac{\text{Computation FLOPs}}{\text{Communication Bytes}} > \frac{\text{Accelerator FLOPs/s}}{\text{Bandwidth Bytes/s}} & \\[0.5em] \Leftrightarrow \text{Intensity}(\text{Computation}) > \text{Intensity}(\text{Accelerator}) & \\ \end{align*}$$
The quantity $\text{Intensity}(\text{Accelerator})$ is the arithmetic intensity at which our accelerator achieves its peak FLOPs/s. For the TPU v5e MXU, this is about 240 FLOPs/byte, since the TPU can perform 1.97e14 FLOPs/s and load 8.2e11 bytes/s from HBM. (The MXU is the matrix multiply unit on the TPU. We specify this here because the TPU has other accelerators like the VPU that are responsible for elementwise operations that have a different peak FLOPs/s.) That means if an algorithm has a lower arithmetic intensity than 240 FLOPs/byte, it will be bound by byte loading and thus we won’t make good use of our hardware. (This is only true if the algorithm loads its weights from HBM and runs in the MXU. As we’ll discuss in the next section, we can sometimes store parameters in VMEM which has a much higher bandwidth. Many algorithms also run in the VPU, which has different performance characteristics.) Let’s look at one such example:
量 $\text{Intensity}(\text{Accelerator})$ 就是让加速器达到峰值 FLOPs/s 的算术强度。对 TPU v5e 的 MXU 来说大约是 240 FLOPs/byte,因为该 TPU 能跑 1.97e14 FLOPs/s,并可从 HBM 加载 8.2e11 bytes/s。(MXU 是 TPU 上的矩阵乘法单元。这里特别指明它,是因为 TPU 上还有 VPU 等其他加速器负责逐元素运算,它们的峰值 FLOPs/s 不同。)也就是说,如果一个算法的算术强度低于 240 FLOPs/byte,它就会被字节加载所限制,从而无法充分利用硬件。(这只在该算法从 HBM 加载权重且运行在 MXU 上时成立。下一节我们会看到,有时可以把参数放在带宽高得多的 VMEM 里。很多算法还运行在 VPU 上,性能特性也不一样。)来看一个例子:
Example (dot product): to compute the dot product of two vectors in bfloat16 precision, x • y: bf16[N], bf16[N] → bf16[1], we need to load $x$ and $y$ from memory, each of which has $2 * N = 2N$ bytes, perform $N$ multiplications and $N-1$ additions, and write $2$ bytes back into HBM
$$\begin{equation}
\text{Intensity}(\text{dot product}) = \frac{\text{Total FLOPs}}{\text{Total Bytes}} = \frac{N + N - 1}{2N + 2N + 2} = \frac{2N - 1}{4N + 2} \rightarrow \frac{1}{2}
\end{equation}$$
例(点积): 计算两个 bfloat16 向量的点积 x • y: bf16[N], bf16[N] → bf16[1],我们需要从内存加载 $x$ 和 $y$,各占 $2 * N = 2N$ 字节,执行 $N$ 次乘法和 $N-1$ 次加法,再写回 $2$ 字节到 HBM:
$$\begin{equation}
\text{Intensity}(\text{dot product}) = \frac{\text{Total FLOPs}}{\text{Total Bytes}} = \frac{N + N - 1}{2N + 2N + 2} = \frac{2N - 1}{4N + 2} \rightarrow \frac{1}{2}
\end{equation}$$
as $N\rightarrow\infty$. So the dot product has an arithmetic intensity of $\frac{1}{2}$ or, put another way, the dot product does 0.5 floating point operations per byte loaded. This means our arithmetic intensity is lower than that of our hardware and we will be communication-bound. (The 240 number above is not the correct comparison here since, as you will see in the next section, a dot-product is performed on the VPU and not the MXU. The TPU v5p VPU can do roughly 7e12 FLOPs / second per core, so its critical intensity is around 3, which means we are still somewhat comms-bound here. Either way, the fact that our intensity is low and constant means it is difficult to be compute-bound on most hardware.)
当 $N\rightarrow\infty$ 时。所以点积的算术强度是 $\frac{1}{2}$;换句话说,点积每加载一个字节只做 0.5 次浮点运算。这意味着我们的算术强度低于硬件,会处于通信受限。(这里的 240 并不适用:下一节你会看到,点积是在 VPU 而非 MXU 上执行的。TPU v5p 的 VPU 每核大约能做 7e12 FLOPs/秒,因此它的临界强度约为 3,也就是说这里仍有些偏向通信受限。无论如何,强度低且恒定,就说明在多数硬件上都很难做到计算受限。)
Visualizing rooflines
可视化 roofline
We can visualize the tradeoff between memory and compute using a roofline plot, which plots the peak achievable FLOPs/s (throughput) of an algorithm on our hardware (the y-axis) against the arithmetic intensity of that algorithm (the x-axis). Here’s an example log-log plot:
我们可以用 roofline 图 把内存与计算之间的取舍可视化:它把算法在硬件上能达到的峰值 FLOPs/s(吞吐,纵轴)对算法的算术强度(横轴)画出来。下面是一个 log-log 的例子:
Above, as the intensity increases (moving left to right), we initially see a linear increase in the performance of our algorithm (in FLOPs/s) until we hit the critical arithmetic intensity of the hardware, 240 in the case of the TPU v5e. Any algorithm with a lower intensity will be bandwidth (BW) bound and limited by the peak memory bandwidth (shown in red). Any algorithm to the right will fully utilize our FLOPs (shown in green). Here, Algo 1 is comms-bound and uses only a fraction of the total hardware FLOPs/s. Algo 2 is compute-bound. We can generally improve the performance of an algorithm either by increasing its arithmetic intensity or by increasing the memory bandwidth available (moving from BW1 to BW2).
在上图中,随着强度增加(从左向右),我们一开始看到算法性能(FLOPs/s)线性增长,直到触及硬件的临界算术强度——对 TPU v5e 来说是 240。任何强度更低的算法都会受限于带宽(BW),被峰值内存带宽卡住(红色区域)。落在右边的算法则能充分利用 FLOPs(绿色区域)。这里 Algo 1 是通信受限的,只用到硬件总 FLOPs/s 的一小部分;Algo 2 是计算受限的。一般来说,我们可以通过提高算术强度、或提高可用内存带宽(从 BW1 到 BW2)来改善算法性能。
Matrix multiplication
矩阵乘法
Let’s look at our soon-to-be favorite algorithm: matrix multiplication (aka matmul). Given two matrices $X$ and $Y$, we can write $X * Y \rightarrow Z$ where $X$ has shape $\text{bf16}[B, D]$, $Y$ has shape $\text{bf16}[D, F]$, and $Z$ has shape $\text{bf16}[B, F]$. To do the matmul we need to load $2DF + 2BD$ bytes, perform $2BDF$ FLOPs, and write $2BF$ bytes back. (Technically we perform $BF \times (2D - 1)$ FLOPs but this is close enough. This comes from $BDF$ multiplications and $BF * (D-1)$ additions. Section 4 has more details.) (Although the output of a matmul is technically float32 we usually cast down to bfloat16 before copying back to HBM.) Thus:
来看我们即将成为最爱的算法:矩阵乘法(matmul)。给定两个矩阵 $X$ 和 $Y$,可以写成 $X * Y \rightarrow Z$,其中 $X$ 形状为 $\text{bf16}[B, D]$,$Y$ 为 $\text{bf16}[D, F]$,$Z$ 为 $\text{bf16}[B, F]$。做这个 matmul 需要加载 $2DF + 2BD$ 字节,执行 $2BDF$ 次 FLOPs,再写回 $2BF$ 字节。(严格说执行的是 $BF \times (2D - 1)$ 次 FLOPs,但这已经足够接近。它来自 $BDF$ 次乘法和 $BF * (D-1)$ 次加法。第 4 节有更多细节。)(虽然 matmul 的输出严格说是 float32,但通常会在写回 HBM 前降到 bfloat16。)因此:
$$\begin{equation} \text{Intensity}(\text{matmul}) = \frac{2BDF}{2BD + 2DF + 2BF} = \frac{BDF}{BD + DF + BF} \end{equation}$$
We can get a nice simplification if we assume our “batch size” $B$ is small relative to $D$ and $F$. Then we get
如果假设「batch size」$B$ 相对 $D$ 和 $F$ 很小,就能得到一个漂亮的简化。于是:
$$\begin{equation} \frac{BDF}{BD + DF + BF} \approx \frac{BDF}{DF} = B \end{equation}$$
$$\begin{equation} \text{Intensity}(\text{matmul}) > \text{Intensity}(\text{TPU}) \implies B > \frac{1.97e14}{8.20e11} = 240 \end{equation}$$
This is a reasonable assumption for Transformer matmuls since we typically have a local (per-replica) token batch size $B < 1024$ (note, not sequences) but $D$ and $F > 8000$. Thus we generally become compute-bound when our per-replica (We say per-replica because, if we do some kind of model sharding to increase the number of chips used in the matmul, we scale both our available compute and memory bandwidth by the same amount. Thus the critical batch size is true per independent copy of the model weights.) batch size is greater than 240 tokens, a very simple rule!
对 Transformer 的 matmul 来说这是个合理假设,因为本地(每个副本的)token batch size 通常是 $B < 1024$(注意,不是序列数),而 $D$ 和 $F > 8000$。因此,当我们的每副本(这里说「每副本」,是因为如果做某种模型分片来增加 matmul 使用的芯片数,可用算力和内存带宽会按相同比例增加,所以临界 batch size 是针对每份独立的模型权重副本而言的。)batch size 超过 240 个 token 时,通常就变成计算受限——一条非常简单的规则!
Takeaway: for a bfloat16 matmul to be compute-bound on most TPUs, we need our batch size to be greater than 240 (tokens). (Note that this is not the batch size in the usual sense, where it means the batch size in sequences. It turns out most rooflines depend purely on the number of tokens, whether they belong to the same or different sequences. For instance if you have a batch size of 512 sequences of 4096 tokens on 2048 GPUs, you have a total batch size of 512 * 4096 = 2M tokens, and a local batch size of 1k tokens.)
结论: 对一个 bfloat16 matmul 而言,要在多数 TPU 上达到计算受限,batch size 需要大于 240(token)。(注意这_不是_通常意义上的 batch size,后者指序列的批大小。事实上大多数 roofline 只取决于 token 数量,无论它们属于同一个还是不同序列。例如,在 2048 块 GPU 上有 512 条、每条 4096 个 token 的序列,那么总 batch size 是 512 * 4096 = 2M 个 token,本地 batch size 是 1k 个 token。)
This comes with a few notable caveats we’ll explore in the problems below, particularly with respect to quantization (e.g., if we quantize our activations but still do full-precision FLOPs), but it’s a good rule to remember. For GPUs, this number is slightly higher (closer to 300), but the same conclusion generally holds. When we decompose a big matmul into smaller matmuls, the tile sizes also matter. (When we do a large matrix multiplication, we need to break it down into smaller tiles which fit into VMEM/SMEM/TMEM, the higher-bandwidth on-chip memory. This causes us to load chunks multiple times, so it’s no longer quite true that we only load $O(N^2)$ bytes. Consider an $(m, k) \cdot (k, n)$ matmul with tile sizes $bm$, $bk$, $bn$. Let $tm = m / bm$, etc. Then the total FLOPs is $2 \cdot tm \cdot tn \cdot tk \cdot bm \cdot bn \cdot bk$ and the total bytes are $2 \cdot tm \cdot tn \cdot (tk \cdot (bm \cdot bk + bk \cdot bn) + bm \cdot bn)$. Ignoring the last term, we have an intensity of $bm \cdot bn / (bm + bn)$, which is similar to the above.) We’ll discuss the lower-level GPU and TPU details in the next section.
这有一些值得注意的注意事项,我们会在下面的习题中探讨,尤其是量化方面(例如,如果量化了激活,但仍然做全精度 FLOPs)。但这条规则值得记住。对 GPU 来说这个数字略高(接近 300),但结论总体一致。当我们把一个大 matmul 拆成许多小 matmul时,tile 大小也会有影响。(做大型矩阵乘法时,需要把它拆成能放进 VMEM/SMEM/TMEM(更高带宽的片上内存)的小块。这会导致有些块被多次加载,所以「只加载 $O(N^2)$ 字节」不再严格成立。考虑一个 $(m, k) \cdot (k, n)$ 的 matmul,tile 大小为 $bm$、$bk$、$bn$。令 $tm = m / bm$ 等等。那么总 FLOPs 是 $2 \cdot tm \cdot tn \cdot tk \cdot bm \cdot bn \cdot bk$,总字节是 $2 \cdot tm \cdot tn \cdot (tk \cdot (bm \cdot bk + bk \cdot bn) + bm \cdot bn)$。忽略最后一项,强度为 $bm \cdot bn / (bm + bn)$,和上面的结论类似。)我们会在下一节讨论更低层的 GPU 和 TPU 细节。
Network communication rooflines
网络通信的 roofline
All the rooflines we’ve discussed so far have been memory-bandwidth rooflines, all within a single chip. This shouldn’t be taken as a rule. In fact, most of the rooflines we’ll care about in this book involve communication between chips: usually matrix multiplications that involve matrices sharded across multiple TPUs.
到目前为止讨论的所有 roofline 都是内存带宽 roofline,且都在单颗芯片内。这不能当成普遍规律。事实上,本书中我们关心的大多数 roofline 都涉及芯片之间的通信:通常是被分片到多块 TPU 上的矩阵所参与的矩阵乘法。
To pick a somewhat contrived example, say we want to multiply two big matrices $X\sim \text{bf16}[B, D]$ and $Y \sim \text{bf16}[D, F]$ which are split evenly across 2 TPUs/GPUs (along the $D$ dimension). To do this multiplication (as we’ll see in Section 3), we can multiply half of each matrix on each TPU (Z0 = X[:, :D // 2] @ Y[:D // 2, :] on TPU 0 and Z1 = X[:, D // 2:] @ Y[D // 2:, :] on TPU 1) and then copy the resulting “partial sums” to the other TPU and add them together. Say we can copy 4.5e10 bytes/s in each direction and perform 1.97e14 FLOPs/s on each chip. What are $T_\text{math}$ and $T_\text{comms}$?
举一个有点刻意的例子:假设我们要相乘两个大矩阵 $X\sim \text{bf16}[B, D]$ 和 $Y \sim \text{bf16}[D, F]$,它们沿 $D$ 维均匀切分在 2 块 TPU/GPU 上。要做这个乘法(我们会在第 3 节看到),可以在每块 TPU 上各乘一半(TPU 0 上 Z0 = X[:, :D // 2] @ Y[:D // 2, :],TPU 1 上 Z1 = X[:, D // 2:] @ Y[D // 2:, :]),然后把得到的「部分和」复制到另一块 TPU 上相加。假设每个方向可以复制 4.5e10 bytes/s,每块芯片做 1.97e14 FLOPs/s。那么 $T_\text{math}$ 和 $T_\text{comms}$ 各是多少?
$T_\text{math}$ is clearly half of what it was before, since each TPU is doing half the work, i.e. (We’re ignoring the FLOPs required to add the two partial sums together (another BF addition), but this is basically negligible.)
$T_\text{math}$ 显然是之前的一半,因为每块 TPU 只做一半工作,即(这里忽略把两个部分和相加所需的 FLOPs(多一次 BF 加法),但这基本可以忽略不计。)
$$T_\text{math} = \frac{2BDF}{2 \cdot \text{Accelerator FLOPs/s}} = \frac{BDF}{1.97e14}$$
Now what about $T_\text{comms}$? This now refers to the communication time between chips! This is just the total bytes sent divided by the network bandwidth, i.e.
那么 $T_\text{comms}$ 呢?这里指的是芯片之间的通信时间!它等于发送的总字节除以网络带宽,即
$$T_\text{comms} = \frac{2BF}{\text{Network Bandwidth}} = \frac{2BF}{4.5e10}$$
Therefore we become compute-bound (now with respect to the inter-chip network) when $$\text{Intensity}(\text{matmul (2-chips)}) > \text{Intensity}(\text{TPU w.r.t. inter-chip network})$$ or equivalently when $\frac{BDF}{2BF} = \frac{D}{2} > \frac{1.97e14}{4.5e10} = 4377$ or $D > 8755$. Note that, unlike before, the critical threshold now depends on $D$ and not $B$! Try to think why that is. This is just one such example, but we highlight that this kind of roofline is critical to knowing when we can parallelize an operation across multiple TPUs.
因此当 $$\text{Intensity}(\text{matmul (2-chips)}) > \text{Intensity}(\text{TPU w.r.t. inter-chip network})$$,等价地当 $\frac{BDF}{2BF} = \frac{D}{2} > \frac{1.97e14}{4.5e10} = 4377$,也就是 $D > 8755$ 时,我们变成计算受限(此时是相对片间网络而言)。注意,和之前不同,临界阈值现在取决于 $D$ 而不是 $B$!想想为什么。这只是一个例子,但我们想强调:这类 roofline 对于判断「一个运算何时能跨多块 TPU 并行」至关重要。
A Few Problems to Work
几道练习题
Question 1 [int8 matmul]: Say we want to do the matmul $X[B, D] \cdot_D Y[D, F] \rightarrow Z[B, F]$ (Here and throughout we’ll use the notation $A \cdot_D B$ to indicate that the multiplication is performing a contraction over the D dimension. This is an abuse of einsum notation.) in int8 precision (1 byte per parameter) instead of bfloat16 (2 bytes per parameter) since TPUs/GPUs can do matmuls faster in lower precision.
问题 1 [int8 matmul]: 假设我们要以 int8 精度(每个参数 1 字节)而不是 bfloat16(每个参数 2 字节)来做 matmul $X[B, D] \cdot_D Y[D, F] \rightarrow Z[B, F]$(这里以及全书都用记号 $A \cdot_D B$ 表示该乘法在 D 维上做收缩。这是对 einsum 记号的一种滥用。),因为 TPU/GPU 在更低精度下做 matmul 更快。
- How many bytes need to be loaded from memory? How many need to be written back to memory?
- How many total OPs are performed?
- What is the arithmetic intensity?
- What is a roofline estimate for $T_\text{math}$ and $T_\text{comms}$? What are reasonable upper and lower bounds for the runtime of the whole operation?
- 需要从内存加载多少字节?需要写回多少字节?
- 一共执行多少次 OP?
- 算术强度是多少?
- $T_\text{math}$ 和 $T_\text{comms}$ 的 roofline 估计是多少?整个运算运行时间的合理上下界是什么?
Assume our HBM bandwidth is 8.2e11 bytes/s and our int8 peak OPs/s is 3.94e14 (about 2x bfloat16).
假设我们的 HBM 带宽是 8.2e11 bytes/s,int8 峰值 OPs/s 是 3.94e14(约为 bfloat16 的 2 倍)。
Click here for the answer.
点这里看答案。
- Because we’re storing our parameters in int8, we have 1 byte per parameter, so we have $$BD + DF$$ bytes loaded from HBM and $$BF$$ written back.
- This is the same as in bfloat16, but in theory int8 OPs/s should be faster. So this is still $2BDF$ OPs.
- Arithmetic intensity is $$2BDF / (BD + DF + BF)$$. If we make the same assumption as above about $$B \ll D$$ and $$B \ll F$$, we get an arithmetic intensity of $$2B$$, meaning our rule becomes $B > \text{HBM int8 arithmetic intensity} / 2$. Using the numbers given, this int8 intensity is
3.94e14 / 8.2e11 = 480, so the rule is $B > 480 / 2 = 240$. Note that this is basically unchanged! - $$T_\text{math} = 2BDF / 3.94e14$$ and $$T_\text{comms} = (BD + DF + BF) / 8.2e11$$, so a reasonable lower bound is $$\max(T_\text{math}, T_\text{comms})$$ and an upper bound is $$T_\text{math} + T_\text{comms}$$.
- 因为参数以 int8 存储,每个参数 1 字节,所以从 HBM 加载 $$BD + DF$$ 字节,写回 $$BF$$ 字节。
- 这跟 bfloat16 时一样,但理论上 int8 的 OPs/s 更快。所以仍然是 $2BDF$ 次 OP。
- 算术强度是 $$2BDF / (BD + DF + BF)$$。若沿用上面 $$B \ll D$$、$$B \ll F$$ 的假设,得到算术强度 $$2B$$,于是规则变成 $B > \text{HBM int8 arithmetic intensity} / 2$。代入给定数字,int8 强度为
3.94e14 / 8.2e11 = 480,所以规则是 $B > 480 / 2 = 240$。注意这基本没变! - $$T_\text{math} = 2BDF / 3.94e14$$,$$T_\text{comms} = (BD + DF + BF) / 8.2e11$$,所以合理的下界是 $$\max(T_\text{math}, T_\text{comms})$$,上界是 $$T_\text{math} + T_\text{comms}$$。
Question 2 [int8 + bf16 matmul]: In practice we often do different weight vs. activation quantization, so we might store our weights in very low precision but keep activations (and compute) in a higher precision. Say we want to quantize our weights in int8 but keep activations (and compute) in bfloat16. At what batch size do we become compute bound? Assume 1.97e14 bfloat16 FLOPs/s.
问题 2 [int8 + bf16 matmul]: 实践中我们常常对权重和激活采用不同的量化:权重存成很低的精度,但激活(以及计算)保持更高精度。假设我们把权重量化成 int8,但激活(和计算)保持 bfloat16。在多大的 batch size 下我们会变成计算受限?假设 bfloat16 为 1.97e14 FLOPs/s。
Hint: this means specifically bf16[B, D] * int8[D, F] -> bf16[B, F] where $B$ is the “batch size”.
提示:具体来说就是 bf16[B, D] * int8[D, F] -> bf16[B, F],其中 $B$ 是「batch size」。
Click here for the answer.
点这里看答案。
Again assuming B is small, we have 2BDF bfloat16 FLOPs but only DF weights (instead of 2DF in bfloat16). This means we become compute-bound when $$2B > 240$$ or $$B > 120$$. This is a lot lower, meaning if we can do int8 weight quantization (which is fairly easy to do) but still do bfloat16 FLOPs, we get a meaningful win in efficiency (although int8 OPs would be better).
同样假设 B 很小,我们有 2BDF 次 bfloat16 FLOPs,但权重只有 DF(而不是 bfloat16 的 2DF)。这意味着当 $$2B > 240$$、即 $$B > 120$$ 时变成计算受限。这个门槛低得多:如果我们能做 int8 权重量化(比较容易)但仍做 bfloat16 FLOPs,就能在效率上获得可观的收益(当然,能做 int8 OPs 会更好)。
Question 3: Taking the setup from Question 2, make a roofline plot of peak FLOPs/s vs. $B$ for $F = D = 4096$ and $F = D = 1024$. Use the exact number of bytes loaded, not an approximation.
问题 3: 沿用问题 2 的设置,对 $F = D = 4096$ 和 $F = D = 1024$ 画出峰值 FLOPs/s 对 $B$ 的 roofline 图。用精确的加载字节数,不要用近似。
Click here for the answer.
点这里看答案。
Here is the plot in question:
这就是那张图:
Note that both models eventually achieve the peak hardware FLOPs/s, but the larger D/F achieve it sooner. D=F=1024 almost doubles the critical batch size. The code to generate this figure is here:
注意两个模型最终都达到硬件峰值 FLOPs/s,但更大的 D/F 更早达到。D=F=1024 几乎让临界 batch size 翻了一倍。生成这张图的代码如下:
import matplotlib.pyplot as plt
import numpy as np
bs = np.arange(1, 512)
def roofline(B, D, F):
total_flops = 2*B*D*F
flops_time = total_flops / 1.97e14
comms_time = (2*B*D + D*F + 2*B*F) / 8.2e11
total_time = np.maximum(flops_time, comms_time)
return total_flops / total_time
roofline_big = roofline(bs, 4096, 4096)
roofline_small = roofline(bs, 1024, 1024)
plt.figure(figsize=(8, 4))
plt.plot(bs, roofline_big, label='F=D=4096')
plt.plot(bs, roofline_small, label='F=D=1024')
plt.legend()
plt.xlabel('batch size')
plt.ylabel('peak bfloat16 FLOPs/s on TPU v5e')
plt.grid()Question 4: What if we wanted to perform $\text{int8}[B, D] \cdot_D \text{int8}[B, D, F] \rightarrow \text{int8}[B, F]$ where we imagine having a different matrix for each batch element. What is the arithmetic intensity of this operation?
问题 4: 如果我们想执行 $\text{int8}[B, D] \cdot_D \text{int8}[B, D, F] \rightarrow \text{int8}[B, F]$,也就是想象每个 batch 元素都有一个不同的矩阵,那么该运算的算术强度是多少?
Click here for the answer.
点这里看答案。
Let’s start by looking at the total FLOPs and comms.
先看总 FLOPs 和通信。
- Total FLOPs: the FLOPs is basically the same, since we’re doing $$B$$ independent $$[D] \times [D, F]$$ products, the same total work as a single $$[B, D] \times [D, F]$$ matmul (this is discussed more in Section 4). So this is just $$2BDF$$.
- Total comms: we have a lot more comms here: $$BD + BDF + BF$$.
- Therefore, our arithmetic intensity is now actually $$2BDF / (BD + BDF + BF)$$. Since $$BDF$$ dominates the denominator, this is roughly $$2$$. So instead of it depending on the batch size, this is essentially constant. This is bad because it means we’ll basically always be comms bound no matter what.
- 总 FLOPs:基本不变,因为我们是在做 $$B$$ 个独立的 $$[D] \times [D, F]$$ 乘积累加,总的运算量与单个 $$[B, D] \times [D, F]$$ matmul 相同(第 4 节有更多讨论)。所以就是 $$2BDF$$。
- 总通信:这里的通信量大多了:$$BD + BDF + BF$$。
- 因此算术强度实际是 $$2BDF / (BD + BDF + BF)$$。由于 $$BDF$$ 在分母中占主导,这大约是 $$2$$。所以它不再取决于 batch size,而基本是常数。这很糟糕,意味着我们几乎永远会通信受限。
Question 5 [Memory Rooflines for GPUs]: Using the spec sheet provided by NVIDIA for the H100 SXM, calculate the batch size at which a bfloat16 matrix multiplication will become compute-bound. Note that the Tensor Core FLOPs numbers are twice the true value since they’re only achievable with structured sparsity.
问题 5 [GPU 的内存 roofline]: 用 NVIDIA 提供的 H100 SXM 规格表,计算 bfloat16 矩阵乘法在多大的 batch size 下会变成计算受限。注意 Tensor Core 的 FLOPs 数字是真实值的两倍,因为那只有在结构化稀疏下才能达到。
Click here for the answer.
点这里看答案。
From the spec sheet, we see that the reported bfloat16 FLOPs value is 1.979e15 FLOPs/s with an asterisk noting “with sparsity”. The true value is half this without sparsity, i.e. 9.89e14 FLOPs/s. The memory bandwidth is 3.35TB/s, or 3.35e12 bytes / second. Thus $B_\text{crit}$ is 9.89e14 / 3.35e12 = 295, rather similar to the TPU.
从规格表看,报告的 bfloat16 FLOPs 是 1.979e15 FLOPs/s,并带有一个星号注明「with sparsity」。不稀疏时真实值是它的一半,即 9.89e14 FLOPs/s。内存带宽是 3.35TB/s,即 3.35e12 bytes/秒。于是 $B_\text{crit}$ = 9.89e14 / 3.35e12 = 295,和 TPU 相当接近。
讨论
用 GitHub 账号留言;评论保存在公开仓库chengshu-blog-discussions的 Discussions 里。也可通过 RSS 订阅后续文章。