# 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%,精度几乎一样! ```