01
引言
在训练大型深度学习模型(是的,包括 LLM 和视觉Transformer)时,最常见的瓶颈之一就是显存消耗达到峰值。由于大多数人无法使用大规模的 GPU 集群,因此在本文中,我将概述一些技术和策略,在不牺牲模型性能和预测准确性的情况下,将显存消耗降低近 20 倍。请记住,这些技术中的大多数应用中并不相互排斥,可以很容易地结合使用,以提高显存效率!
闲话少说,我们直接开始吧!
02
混合精度训练结合了16位(FP16)和32位(FP32)浮点格式。其核心思想是在低精度下执行大部分数学运算,从而降低显存带宽和存储需求,同时在计算的关键环节保留必要的精度保障。通过使用FP16存储激活值和梯度,这些张量的显存占用量可减少约一半。但需注意,某些网络层或运算仍需保持FP32精度以避免数值不稳定问题。值得庆幸的是,PyTorch对自动混合精度(AMP)的原生支持极大地简化了这一过程。
注意这里是混合精度训练而不是低精度训练。
-
什么是混合精度训练?
混合精度训练结合使用16位(FP16)和32位(FP32)浮点格式以保持模型精度。通过使用16位精度计算梯度,相比全32位精度计算,这一过程可大幅加快运算速度并显著减少显存占用。这种方法在显存或计算资源受限的场景下尤为实用。
之所以采用混合精度而非低精度这一表述,是因为并非所有参数或运算都被转换为16位格式。实际上,训练过程会在32位与16位运算之间动态切换,这种精度层级的有序交替正是该技术被称为混合精度的根本原因。
如上述示意图所示,混合精度训练流程首先将权重转换为低精度格式(FP16)以加速计算。随后梯度计算在低精度环境下完成,但为确保数值稳定性,这些梯度会被重新转换为高精度格式(FP32),最终经过缩放处理的梯度将用于更新原始权重。因此,通过这种机制既能提升训练效率,又不会牺牲网络的整体精度与稳定性。
如前所述,使用 torch.cuda.amp.autocast() 可以轻松启用该功能,一个简单的代码示例片段如下:
import torchfrom torch.cuda.amp import autocast, GradScaler# Assume your model and optimizer have been defined elsewhere.model = MyModel().cuda()optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)scaler = GradScaler()for data, target in data_loader:optimizer.zero_grad()# Enable mixed precisionwith autocast():output = model(data)loss = loss_fn(output, target)# Scale the loss and backpropagatescaler.scale(loss).backward()scaler.step(optimizer)scaler.update()
03
import torchprint(torch.cuda.is_bf16_supported()) # should print True
04
import torchfrom torch.utils.checkpoint import checkpointdef checkpointed_segment(input_tensor):# This function represents a portion of your model# which will be recomputed during the backward pass.# You can create a custom forward pass for this segment.return model_segment(input_tensor)# Instead of a conventional forward pass, wrap the segment with checkpoint.output = checkpoint(checkpointed_segment, input_tensor)
为什么不干脆减少batchsize大小?
那么如何达到平衡呢?
import torchfrom torch.distributed.fsdp import FullyShardedDataParallel as FSDP# Initialize your model and ensure it is on the correct device.model = MyLargeModel().cuda()# Wrap the model in FSDP for sharded training across GPUs.fsdp_model = FSDP(model)
from torch.utils.data import DataLoader# Create your dataset instance and then the DataLoader with pinned memory enabled.train_loader = DataLoader(dataset,batch_size=64,shuffle=True,num_workers=4, # Adjust based on your CPU capabilitiespin_memory=True # Enables faster host-to-device transfers)
import torchx = torch.randn(100, 100, device='cuda')y = torch.randn(100, 100, device='cuda')# Using in-place additionx.add_(y) # Here x is modified directly instead of creating a new tensor
09
def offload_activation(tensor):# Move tensor to CPU to save GPU memoryreturn tensor.cpu()def process_batch(data):# Offload some activations explicitlyintermediate = model.layer1(data)intermediate = offload_activation(intermediate)intermediate = intermediate.cuda() # Move back when neededoutput = model.layer2(intermediate)return output
# instead of thisoptimizer = torch.optim.Adam(model.parameters(), lr=5e-5)# use thisoptimizer = torch.optim.SGD(model.parameters(), lr=0.01)num_steps = NUM_EPOCHS * len(train_loader)scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=num_steps)
-
内存剖析和高速缓存管理
import torch# print a detailed report of current GPU memory usage and fragmentationprint(torch.cuda.memory_summary(device=None, abbreviated=False))# free up cached memory that’s no longer needed by PyTorchtorch.cuda.empty_cache()
使用TorchScript进行JIT编译
import torch# Suppose `model` is an instance of your PyTorch network.scripted_model = torch.jit.script(model)# Now, you can run the scripted model just like before.output = scripted_model(input_tensor)
自定义内核融合
使用 torch.compile() 进行动态内存分配
12
点击上方小卡片关注我
添加个人微信,进专属粉丝群!

