# Summary
混合精度训练(Mixed Precision)混合精度训练的精髓在于“在内存中用FP16做储存和乘法从而加速计算,用 FP32做累加避免舍入误差”。混合精度训练的策略有效地缓解了舍入误差的问题。
就像 Java 中你不会用 `double` 存储年龄(用 `int` 就够了),深度学习中:
- **FP32**:默认选择,像 Java 的 `int`
- **FP16**:省内存提速,像用 `short` 代替 `int`
- **混合精度**:聪明的折中方案,计算用 FP16,更新用 FP32
记住:神经网络本身就是近似计算,不需要超高精度。就像炒菜不需要精确到克,差不多就行!
# Cues
# Notes
用FP16(半精度)代替FP32(全精度)
好!我用你熟悉的 Java 数据类型类比,一次性讲清楚深度学习中的精度问题。
## 1. **精度类型对比**
```java
// Java 中的数据类型
byte b = 127; // 8位,范围:-128 到 127
short s = 32767; // 16位,范围:-32,768 到 32,767
int i = 2147483647; // 32位,范围:约 ±21亿
long l = 9223372036854775807L; // 64位,范围:约 ±9百万兆
float f = 3.14159f; // 32位浮点数,精度约7位小数
double d = 3.14159265358979; // 64位浮点数,精度约15位小数
```
深度学习中的对应关系:
```python
# 深度学习中的浮点类型
FP64 = double # 64位,几乎不用(太慢太占内存)
FP32 = float # 32位,标准精度
FP16 = half # 16位,半精度
BF16 = bfloat16 # 16位,Brain Float(Google设计的)
INT8 = byte # 8位整数,用于量化
```
## 2. **为什么要用 FP16?**
### 用买菜的例子说明
```python
# FP32 就像用高精度秤
weight_fp32 = 1.23456789 # 能精确到小数点后7-8位
price_fp32 = 12.99999999 #
total_fp32 = weight_fp32 * price_fp32 # = 16.04938266
# FP16 就像用普通秤
weight_fp16 = 1.234 # 只能精确到小数点后3-4位
price_fp16 = 13.0 #
total_fp16 = weight_fp16 * price_fp16 # = 16.04
# 对于买菜来说,差0.009元根本不重要!
```
## 3. **实际的深度学习例子**
```python
import torch
# 创建一个简单的神经网络权重矩阵
weight_fp32 = torch.tensor([[1.23456789, 2.87654321],
[0.11111111, 0.99999999]], dtype=torch.float32)
weight_fp16 = weight_fp32.half() # 转换为 FP16
print(f"FP32: {weight_fp32}")
print(f"FP16: {weight_fp16}")
# 输出:
# FP32: tensor([[1.2346, 2.8765],
# [0.1111, 1.0000]])
# FP16: tensor([[1.2344, 2.8770],
# [0.1111, 1.0000]], dtype=torch.float16)
# 看到了吗?精度损失很小!
```
## 4. **FP16 的优势**
```python
# 内存使用对比
batch_size = 32
seq_length = 1024
hidden_size = 768
# FP32 占用内存
tensor_fp32 = torch.randn(batch_size, seq_length, hidden_size)
memory_fp32 = tensor_fp32.element_size() * tensor_fp32.numel() / (1024**2) # MB
print(f"FP32 内存: {memory_fp32:.1f} MB") # 96.0 MB
# FP16 占用内存
tensor_fp16 = tensor_fp32.half()
memory_fp16 = tensor_fp16.element_size() * tensor_fp16.numel() / (1024**2)
print(f"FP16 内存: {memory_fp16:.1f} MB") # 48.0 MB
# 内存减半!速度提升约2倍!
```
## 5. **FP16 的问题和解决方案**
### 问题1:梯度下溢(Gradient Underflow)
```python
# 梯度太小时,FP16 会变成 0
tiny_gradient_fp32 = torch.tensor(0.0000001) # 1e-7
tiny_gradient_fp16 = tiny_gradient_fp32.half()
print(f"FP32: {tiny_gradient_fp32}") # 1e-07
print(f"FP16: {tiny_gradient_fp16}") # 0.0 😱 梯度消失了!
```
### 解决方案:混合精度训练(Mixed Precision)
```python
# 实际训练中的混合精度
from torch.cuda.amp import autocast, GradScaler
model = MyTransformer()
optimizer = torch.optim.Adam(model.parameters())
scaler = GradScaler() # 梯度缩放器
for data, target in dataloader:
optimizer.zero_grad()
# 前向传播用 FP16(快)
with autocast():
output = model(data) # 自动转为 FP16
loss = loss_fn(output, target)
# 反向传播:
# 1. 先放大损失(防止梯度下溢)
scaler.scale(loss).backward()
# 2. 缩放回梯度并更新权重(用 FP32)
scaler.step(optimizer)
scaler.update()
```
## 6. **不同精度类型对比**
| 类型 | 位数 | 范围 | 精度 | 用途 |
|------|------|------|------|------|
| FP32 | 32 | ±3.4×10³⁸ | ~7位小数 | 标准训练 |
| FP16 | 16 | ±65,504 | ~3位小数 | 推理、混合精度训练 |
| BF16 | 16 | ±3.4×10³⁸ | ~2位小数 | TPU训练(范围大但精度低)|
| INT8 | 8 | -128到127 | 整数 | 模型量化部署 |
## 7. **实际效果**
```python
# 用 Transformer 做例子
def train_comparison():
model = TransformerLM(vocab_size=50000, d_model=768)
# FP32 训练
time_fp32 = train_with_fp32(model) # 假设: 100分钟
memory_fp32 = get_memory_usage() # 假设: 16GB
# FP16 混合精度训练
time_fp16 = train_with_mixed_precision(model) # 约 50分钟
memory_fp16 = get_memory_usage() # 约 10GB
# 结果:训练速度快2倍,内存省40%,精度几乎一样!
```