v1 单卡上的三招:BF16、activation checkpointing、FlashAttention

v0 的 model state 128 GB,一条 8K 序列的 activation 约 430 GB,fp32 还算得慢。v1 先不加卡,只用三招。BF16 让 matmul 跑上 Tensor Core,FlashAttention 和 activation checkpointing 把 activation 压到 2.4 GB,但 128 GB 的 model state 一点没少。

面试会这样问Why do we train LLMs in BF16 rather than FP16? · You run out of GPU memory during training. What do you do? · How does FlashAttention save memory and time without reducing FLOPs?

考过讯飞("为什么大模型训练用BF16?")· 牛客("是否了解混合精度,FP16 和 BF16 的区别?")· 字节("oom怎么办,activation checkpointing原理")· 牛客("解决显存不够的方法有哪些")· 字节("讲讲 Flash Attention 的原理,矩阵 tiling 的过程说细一点,最好把 shape 也讲清楚。")

第一招 BF16:先看一个浮点数怎么存

浮点数就是二进制的科学计数法,比如 \(1.5 \times 2^{3}\)。它分三段:

格式sign / exponent / mantissa最大值1 附近相邻两数的间距一句话
FP321 / 8 / 233.4e38\(2^{-23}\) ≈ 1.2e-7尺子长、刻度密,但占 4 bytes
FP161 / 5 / 1065504\(2^{-10}\) ≈ 0.001刻度够密,尺子太短
BF161 / 8 / 73.4e38\(2^{-7}\) ≈ 0.008尺子长,刻度粗

为什么大模型都选 BF16:

FP16 为什么要 loss scaling

gradient 常常很小,比如 1e-8,FP16 存不下,直接变成 0(underflow)。所以 FP16 要在 backward 之前把 loss 乘一个大数 S(比如 \(2^{16}\)),让所有 gradient 一起放大,optimizer 更新之前再转成 fp32 除以 S。S 太大又会超过 65504 变成 inf,所以实际用 dynamic 的。BF16 把这一套都省了。

Mixed precision 的配方

“mixed”的意思是,怕误差的地方用 fp32,其余都用 bf16。

  1. fp32 master weight 是“正本”,每一步复制出一份 bf16 副本
  2. forward、backward 的 matmul 输入是 bf16,Tensor Core 内部用 fp32 accumulation(累加)。softmax、LayerNorm 这类要求和的操作也用 fp32
  3. 得到 bf16 的 gradient,转成 fp32,更新 fp32 master weight

两处 fp32 防的是同一件事:大数加小数,刻度太粗,小数就被吃掉。

mixed precision 不减 model state,每个 parameter 还是 2 + 2 + 4 + 4 + 4 = 16 bytes。它省的是 activation(每个数 4 → 2 bytes,约 860 → 430 GB)和时间(matmul 跑上 Tensor Core,memory-bound 的算子要搬的 bytes 减半)。

第二招 FlashAttention:不把 s × s 写回 HBM

430 GB 里 90% 是 attention 的 s × s。标准 attention 每个 head 先算 score \(S = QK^\top\),形状是 s × s:

FlashAttention 的做法:

第三招 activation checkpointing:只存每层的输入

OOM 工具箱:按代价从小到大

办法省什么代价
BF16 mixed precisionactivation 减半,还更快几乎没有
FlashAttentionattention 的 s × s:430 → 41 GB没有,还更快
小 micro-batch + gradient accumulationactivation 随 b 线性下降matmul 变小,GPU 吃不饱
activation checkpointing41 → 2.4 GB多约 1/3 compute
ZeRO / FSDP、TP、PP、CP(第 4–7 课)把 model state 和 activation 切到多张卡communication
CPU offload把 optimizer state 放到 CPU memory走 PCIe,很慢

去查一下你在 TikTok 用的 DeepSpeed + HF 里,这三招各是一个开关:DeepSpeed config 里的 "bf16": {"enabled": true};HF 的 gradient_checkpointing=True(或 model.gradient_checkpointing_enable());from_pretrained(..., attn_implementation="flash_attention_2")。翻一下当时的 config 和启动脚本,看开了哪几个,sequence length 设的多少。如果 attention 写的是 "sdpa",PyTorch 也可能在内部选用 FlashAttention 的 kernel。面试官很可能追问:“Why did you turn on gradient checkpointing, and what did it cost?”

30 秒英文回答

Before adding GPUs, use three tricks. BF16 mixed precision runs matmuls in BF16 on Tensor Cores, with FP32 accumulation and FP32 master weights. BF16 keeps FP32's eight exponent bits, so unlike FP16 it needs no loss scaling. It halves activations and speeds things up, but model state stays at 16 bytes per parameter. FlashAttention computes attention tile by tile in SRAM with an online softmax, so the s-by-s scores never reach HBM: for one 8K sequence on an 8B model, activations drop from about 430 to 41 GB. Activation checkpointing keeps only each layer's input and recomputes the rest in backward: 2.4 GB, for about a third more compute.

自测

为什么大模型训练用 BF16,不用 FP16?

BF16 有 8 bit exponent,range 和 FP32 一样,不用 loss scaling。代价是 mantissa 只有 7 bit,靠 fp32 master weight 和 fp32 accumulation 补回来。

开了 BF16 mixed precision,8B 模型的 model state 是多少?

还是 128 GB,因为 bf16 省下的 4 bytes 被 fp32 master weight 吃回去。省的是 activation 和时间。

FlashAttention 没有减少 FLOPs,为什么还更快?

attention 是 memory-bound,FlashAttention 省掉的是 s × s 在 HBM 的读写。论文里(A100、GPT-2 medium)HBM 读写从 40.3 GB 降到 4.4 GB,forward + backward 从 41.7 ms 降到 7.3 ms。

full recompute 和 selective recompute 有什么区别?开了 FlashAttention 还要 selective 吗?

full 整层重跑 forward,多约 1/3 compute。selective 只重算 attention 里 s × s 的那几步,FLOPs 只多几个百分点。开了 FlashAttention 就不用 selective,要继续压只能用 full(41 → 2.4 GB)。

显存不够有哪些办法?按代价排。

先用不掉速的(BF16、FlashAttention),再花 compute(小 micro-batch 加 gradient accumulation、activation checkpointing),最后花通信(ZeRO/FSDP、TP/PP/CP、CPU offload)。