📢 转载信息
原文链接:https://machinelearningmastery.com/3-ways-to-speed-up-model-training-without-more-gpus/
原文作者:Shittu Olumide
在本文中,您将学习三种经过验证的方法,通过优化精度、内存和数据流来加速模型训练——而无需增加任何新的GPU。
我们将涵盖的主题包括:
- 混合精度和内存技术如何安全地提高吞吐量
- 使用梯度累积以使用更大的“虚拟”批次进行训练
- 使用ZeRO进行分片和卸载,以便在现有硬件上容纳更大的模型
让我们不要再浪费时间了。

3 Ways to Speed Up Model Training Without More GPUs
Image by Editor
引言
训练大型模型可能会非常缓慢,人们的第一反应通常是要求更多的GPU。但是额外的硬件并不总是一个可行的选项。预算和云限制等问题会阻碍这一进程。好消息是,有一些方法可以在不增加任何GPU的情况下显著加快训练速度。
加速训练不仅仅是关于原始计算能力;它还关乎更有效地利用您已有的资源。大量的训练时间浪费在了内存交换、空闲的GPU和未优化的数据管道上。通过改善代码与硬件的通信方式,您可以从训练运行中节省数小时甚至数天的时间。
方法一:混合精度和内存优化
在不增加新GPU的情况下加速训练的最简单方法之一是使用混合精度。现代GPU设计用于比标准的32位浮点数(FP32)更快地处理半精度(FP16)或bfloat16运算。通过使用更小的数据类型进行存储和计算,您可以减少内存使用和带宽,从而一次性将更多数据装入GPU,这意味着运算可以更快完成。
核心思想很简单:
- 对大多数运算使用较低精度(FP16或BF16)
- 将关键部分(如损失缩放和少数累加操作)保留为全精度(FP32)以保持稳定性
如果操作得当,混合精度通常可以带来1.5 – 2 倍的训练速度提升,而准确率几乎没有下降。它在PyTorch、TensorFlow和JAX中原生支持,并且大多数NVIDIA、AMD和Apple GPU现在都为此提供了硬件加速。
这是一个启用自动混合精度的PyTorch示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# Mixed Precision Example (PyTorch)
import torch
from torch import nn, optim
from torch.cuda.amp import GradScaler, autocast
model = nn.Linear(512, 10).cuda()
optimizer = optim.Adam(model.parameters(), lr=1e-3)
scaler = GradScaler()
for inputs, targets in dataloader:
optimizer.zero_grad()
with autocast(): # operations run in lower precision
outputs = model(inputs.cuda())
loss = nn.functional.cross_entropy(outputs, targets.cuda())
scaler.scale(loss).backward() # scaled to prevent underflow
scaler.step(optimizer)
scaler.update()
|
工作原理:
autocast()
会自动为每次运算选择 FP16 或 FP32GradScaler()
通过动态调整损失缩放因子来防止下溢- GPU执行速度更快,因为它在每次运算中移动和计算的字节更少
您也可以通过PyTorch的自动混合精度(AMP)或用于传统设置的Apex 库来全局激活它。对于较新的设备(A100、H100、RTX 40 系列),bfloat16 (BF16) 通常比 FP16 更稳定。
内存优化与混合精度相辅相成。两种常见的技巧是:
- 梯度检查点(Gradient checkpointing):仅保存关键激活值,并在反向传播期间重新计算其他激活值,以计算换取内存
- 激活值卸载(Activation offloading):将不常用的张量暂时移动到CPU内存中
在PyTorch中,可以通过以下方式启用它们:
1
|
from torch.utils.checkpoint import checkpoint
|
何时使用:
- 如果您的模型紧凑地装入GPU内存,或者您的批次大小较小
- 您正在使用较新的GPU(RTX 20 系列或更新版本)
- 您可以容忍训练期间轻微的数值差异
根据模型大小和硬件的不同,通常可以预期获得30%–100% 更快的训练速度和高达50%的内存节省。
方法二:梯度累积和有效批次大小技巧
有时,训练速度的最大障碍不是计算能力,而是GPU内存。您可能希望使用大批次来提高梯度稳定性,但GPU在达到该批次大小之前就耗尽了内存。
梯度累积完美地解决了这个问题。您不必一次性处理一个巨大的批次,而是将其分成更小的微批次。您对每个微批次运行前向和后向传播,累积梯度,并且只在经过多次迭代后才更新模型权重。这使得您可以使用相同的硬件来模拟大批次训练。
以下是PyTorch中的实现方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# Gradient Accumulation Example (PyTorch)
import torch
from torch import nn
from torch.cuda.amp import GradScaler, autocast
# Assumes `model`, `optimizer`, and `dataloader` are defined elsewhere
criterion = nn.CrossEntropyLoss()
scaler = GradScaler()
accum_steps = 4 # accumulate gradients over 4 mini-batches
for i, (inputs, targets) in enumerate(dataloader):
with autocast(): # works nicely with mixed precision
outputs = model(inputs.cuda())
loss = criterion(outputs, targets.cuda()) / accum_steps # normalize
scaler.scale(loss).backward()
if (i + 1) % accum_steps == 0:
scaler.step(optimizer)
scaler.update()
optimizer.zero_grad(set_to_none=True)
|
工作原理:
- 损失除以累积步数,以保持梯度平衡
- 梯度在步与步之间存储在内存中,而不是被清除
- 经过
accum_steps
个微批次后,优化器执行一次更新
这个简单的更改允许您使用大上四到八倍的虚拟批次大小,在不超出GPU内存的情况下提高稳定性和潜在的收敛速度。
重要性:
- 更大的有效批次可以减少梯度更新中的噪声,从而改善复杂模型的收敛性
- 您可以将其与混合精度结合使用以获得额外的好处
- 当内存而不是计算是您的限制因素时,它尤其有效
何时使用:
- 您遇到大批次的“内存不足”错误
- 您希望在不更换硬件的情况下获得大批次的好处
- 您的数据加载器或增强管道可以跟上每次更新所需的多步微小处理
方法三:智能卸载和分片训练(ZeRO)
随着模型规模的增长,GPU内存成为主要的瓶颈,甚至先于计算。您可能有能力训练模型,但没有足够的内存来同时容纳其所有参数、梯度和优化器状态。这时就需要智能卸载和分片训练了。
其思想是智能地分割和分配内存使用,而不是在每张GPU上复制所有内容。像DeepSpeed和Hugging Face Accelerate这样的框架通过诸如ZeRO(Zero Redundancy Optimizer,零冗余优化器)等技术实现了这一点。
ZeRO如何工作
通常,多GPU设置中的每张GPU都保存了以下内容的完整副本:模型参数、梯度和优化器状态。这对于大型模型来说非常浪费。ZeRO通过在设备间分片这些状态来打破这种重复:
- ZeRO 阶段 1: 分片优化器状态
- ZeRO 阶段 2: 分片优化器状态和梯度
- ZeRO 阶段 3: 分片所有内容,包括模型参数
现在,每张GPU只保存总内存占用的一个片段,但它们仍然协同工作以完成完整的更新。这使得那些比单个GPU内存容量大得多的模型也能高效地进行训练。
简单的示例(DeepSpeed)
下面是一个启用ZeRO优化的基本DeepSpeed配置片段:
1
2
3
4
5
6
7
8
9
|
{
"train_batch_size": 64,
"fp16": { "enabled": true },
"zero_optimization": {
"stage": 2,
"offload_optimizer": { "device": "cpu", "pin_memory": true },
"offload_param": { "device": "cpu" }
}
}
|
1
2
|
import deepspeed
model, optimizer, _, _ = deepspeed.initialize(model=model, optimizer=optimizer, config='ds_config.json')
|
它做了什么:
- 为更快的计算启用混合精度(fp16)
- 激活ZeRO阶段2,在设备间分片优化器状态和梯度
- 当GPU内存紧张时,将未使用的张量卸载到CPU内存
何时使用
- 您正在训练一个大型模型(数亿或数十亿参数)
- 即使使用混合精度,您仍然会耗尽GPU内存
- 您正在使用多个GPU或分布式节点
附加提示
上述三种主要方法——混合精度、梯度累积和ZeRO卸载——在不增加硬件的情况下,可以带来您能实现的大部分性能提升。但还有一些较小的、常常被忽视的优化措施,尤其是在与其他主要方法结合使用时,也能带来明显的不同。
让我们看一些在几乎所有训练设置中都适用的优化措施。
1. 优化您的数据管道
GPU利用率通常会下降,因为在下一个批次准备好处理之前,模型就已经完成了计算。解决办法是并行化和预取数据。
在PyTorch中,您可以通过调整DataLoader来提高数据吞吐量:
1
|
train_loader = DataLoader(dataset, batch_size=64, num_workers=8, pin_memory=True, prefetch_factor=4)
|
num_workers
使用多个CPU线程进行加载pin_memory=True
加速主机到GPU的传输prefetch_factor
确保在GPU请求之前批次已准备就绪
如果您处理大型数据集,请将它们存储在优化用于顺序读取的格式中,例如WebDataset、TFRecord或Parquet,而不是纯图像或文本文件。
2. 优化前分析
在应用高级技术之前,请找出您的训练循环实际花费时间的地方。框架提供了内置的分析器:
您通常会发现最大的瓶颈不是GPU,而是数据增强、日志记录或缓慢的损失计算等问题。修复这些问题可以立即加速,而无需进行任何算法更改。
3. 使用早停和课程学习
并非所有样本在整个训练过程中都做出同等贡献。早停(Early stopping)可以防止性能趋平时进行不必要的训练周期。课程学习(Curriculum learning)从更简单的示例开始训练,然后引入更难的示例,有助于模型更快地收敛。
1
2
3
4
|
if validation_loss > best_loss:
patience_counter += 1
if patience_counter >= patience_limit:
break # early stop
|
4. 定期监控内存和利用率
了解模型实际使用了多少内存有助于您平衡批次大小、累积和卸载。在PyTorch中,您可以使用以下命令记录GPU内存统计信息:
1
|
print(f"Max memory used: {torch.cuda.max_memory_allocated() / 1e9:.2f} GB")
|
5. 智能组合技术
最大的收获来自于堆叠这些策略:
- 混合精度 + 梯度累积 = 更快、更稳定的训练
- ZeRO卸载 + 数据管道优化 = 在没有内存错误的情况下训练更大的模型
- 早停 + 分析 = 减少浪费的训练周期
何时使用每种方法
为了让您更容易决定哪种方法适合您的设置,以下是所涵盖的三种主要技术的比较摘要表,包括其预期益处、最适用场景和权衡。
方法 | 最适用场景 | 如何提供帮助 | 典型速度提升 | 内存影响 | 复杂度 | 关键工具/文档 |
---|---|---|---|---|---|---|
混合精度与内存优化 | 任何紧凑装入GPU内存的模型 | 使用较低精度(FP16/BF16)和较轻的张量来减少计算和传输开销 | 1.5 – 2× 更快训练 | 30–50% 内存减少 | 低 | PyTorch AMP, NVIDIA Apex |
梯度累积与有效批次大小 | 受限于GPU内存,但需要大批次大小的模型 | 通过在较小批次中累积梯度来模拟大批次训练 | 提高收敛稳定性;通过更少重启带来的间接速度提升 | 中等额外内存(临时梯度) | 低 – 中 | DeepSpeed 文档, PyTorch 论坛 |
智能卸载与分片训练 (ZeRO) | 无法装入GPU内存的超大型模型 | 跨设备或CPU分片优化器状态、梯度和参数 | 10–30% 吞吐量提升;训练2–4×更大的模型 | 释放大部分GPU内存 | 中 – 高 | DeepSpeed ZeRO, Hugging Face Accelerate |
🚀 想要体验更好更全面的AI调用?
欢迎使用青云聚合API,约为官网价格的十分之一,支持300+全球最新模型,以及全球各种生图生视频模型,无需翻墙高速稳定,文档丰富,小白也可以简单操作。
评论区