前言
随着模型规模越来越大,FP32 全精度训练越来越吃力:显存不够、速度慢、成本高。混合精度通过"低精度计算 + 高精度存储"的方式,在几乎不损失模型精度的前提下,大幅降低显存占用、提升训练速度。今天就来详细聊聊这项技术。
一、什么是混合精度?
一句话概括:
用低精度(FP16/BF16)做计算,用高精度(FP32)存主权重和更新,兼顾速度和稳定性。
它不是"全用低精度",而是"关键地方低精度,关键地方高精度"。
二、为什么需要混合精度?
-
省显存:FP16/BF16 只占 FP32 一半显存。
-
算得快:现代 GPU 对 FP16/BF16 有专门加速,吞吐更高。
-
保精度:纯低精度训练容易下溢、不稳定,混合精度用 FP32 主权重兜底。
三、核心流程
混合精度训练的核心流程如下:
-
主权重:模型始终保留一份 FP32 权重。
-
前向:把权重转成 FP16/BF16 计算,得到低精度激活。
-
损失:用 FP32 计算 loss。
-
反向:得到低精度梯度。
-
更新:梯度转回 FP32,更新 FP32 主权重。
-
下一轮:再把更新后的 FP32 权重转成低精度去前向。
这样既享受低精度速度,又不会让参数更新丢失精度。
四、FP16 和 BF16 的区别
| 格式 | 符号 | 指数 | 尾数 | 特点 |
|---|---|---|---|---|
| FP32 | 1 | 8 | 23 | 标准精度 |
| FP16 | 1 | 5 | 10 | 精度高,范围小,容易下溢 |
| BF16 | 1 | 8 | 7 | 范围大,不易下溢,精度低 |
-
FP16 :需要 Loss Scaling,防止梯度下溢成 0。
-
BF16:范围和 FP32 一样,通常不需要 Loss Scaling,训练更稳。
现在大模型训练更常用 BF16,因为稳定。
五、Loss Scaling 是什么?
FP16 的梯度可能非常小,小到低于 FP16 能表示的最小值,直接变成 0。解决办法:
-
前向算完 loss 后,先乘以一个很大的数(比如 1024)。
-
反向传播得到放大后的梯度。
-
更新权重前,再把梯度除以 1024。
-
如果发现梯度溢出,就动态调小缩放系数。
PyTorch 的 GradScaler 会自动做这件事。
六、PyTorch 实战
python
scaler = torch.cuda.amp.GradScaler()
for input_ids, attention_mask, labels in dataloader:
optimizer.zero_grad()
with torch.cuda.amp.autocast(dtype=torch.bfloat16):
logits = model(input_ids, attention_mask)
loss = criterion(logits, labels)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
-
autocast:自动决定哪些算子用低精度,哪些保持 FP32。 -
GradScaler:只对 FP16 需要,BF16 通常不用。
在 QLoRA / BNB 里:
python
bnb_4bit_compute_dtype=torch.bfloat16
意思就是:权重用 4bit 存,计算时用 BF16 算,这也是混合精度的一种。
七、面试口头版
混合精度就是训练时用 FP16 或 BF16 做前向和反向计算,同时保留一份 FP32 主权重来更新参数。
好处是省显存、算得快,又不会因为低精度导致训练不稳定。
FP16 范围小,容易梯度下溢,需要 Loss Scaling;BF16 范围大,通常不用。
PyTorch 里用
autocast和GradScaler就能实现。一句话:低精度算,高精度存,速度和稳定兼得。
总结
混合精度是大模型训练中非常实用的技术,核心就是"低精度计算,高精度存储"。FP16 需要 Loss Scaling,BF16 更省心。实际使用中,PyTorch 的 autocast 和 GradScaler 可以轻松实现。如果你还在为显存和速度发愁,不妨试试混合精度。