新手数据科学家和机器学习工程师常常会问一个关键问题:如何判断他们的深度学习训练过程是否在正常运行?在本文中,我们将学习如何诊断和优化深度学习的性能问题,不论是在单台机器还是多台机器上进行训练。通过这些方法,我们将能够更加高效地使用各种云端 GPU 资源。
首先,我们将从了解 GPU 的利用率开始,最后探讨如何选择合适的批处理大小来最大化 GPU 的利用效率。
请注意:本文假设读者已经具备基本的 Linux 操作系统知识,并熟悉 Python 编程语言。大多数现代的 Linux 发行版(如 Ubuntu)通常已经预装了基本工具,因此我们可以直接安装 pip 和 conda,因为它们将在本文中被广泛使用。
准备工作
为了能够顺利跟进本文的内容,你需要具备一定的 Python 编程经验,并对深度学习有基础的理解。我们假设所有读者都可以使用性能足够强大的机器,便于运行本文中提供的代码。
如果你还没有合适的 GPU,或希望获取更高性能的 GPU ,比如 H100x8,我们建议可以尝试 GPU 云服务来低成本、快速获取这些资源。目前,许多云服务提供商都提供 GPU 资源。DigitalOcean 的 GPU Droplets 云服务现已开放使用,而且正在进行限时优惠,最低仅需 2.5 美元/月即可使用 H100 GPU 服务器。如需商洽,可直接联系 DigitalOcean 中国区独家战略合作伙伴卓普云。
什么是 GPU 利用率?
在机器学习和深度学习的训练过程中,GPU 利用率是需要密切关注的重要指标之一。我们可以通过一些知名的第三方工具以及内置的 GPU 工具来监控它。
GPU 利用率可以定义为单个或多个 GPU 核心在过去一秒中的运行速度,深度学习程序会并行地使用这些 GPU 资源。换句话说,GPU 利用率反映了 GPU 的工作负载情况。
如何判断是否需要更多 GPU 计算资源?
让我们来看一个实际的场景。在典型的一天里,数据科学家可能拥有两块 GPU 供他/她使用,这些资源“应该”足够应对大部分任务。在模型构建的初期阶段,GPU 的短期交互工作良好,工作流程十分顺畅。然而,一旦进入训练阶段,工作流程可能会突然需要额外的 GPU 计算资源,而这些资源并不容易获得。
这表明要完成重要任务时,需要更多的计算资源。特别是在 RAM 已经被完全分配完的情况下,以下任务将无法完成:
- 运行更多实验
- 利用多 GPU 进行训练,以加快训练速度,尝试更大的批处理大小,并获得更高的模型精度
- 专注于新模型的开发,同时让其他模型独立训练
GPU 利用率的优势
通常情况下,增加 GPU 计算资源会显著提高硬件的利用率,从而使模型训练速度提升两倍。
- GPU 利用率的提高能够帮助我们更高效地管理资源分配,减少 GPU 的空闲时间,进而提高整个集群的利用率。
- 从深度学习专家的角度来看,消耗更多的 GPU 计算能力意味着我们可以运行更多的实验,从而提高生产力和模型的质量。
- 此外,IT 管理员可以利用多个 GPU 进行分布式训练,例如使用 DigitalOcean Droplets 提供的 NVLink 多 GPU 机器,这将有效缩短训练时间。
最佳批处理大小与 GPU 利用率
批处理大小的选择常常让人困惑,因为对于特定的数据集和模型架构来说,没有一种单一的“最佳”批处理大小。如果选择较大的批处理大小,训练过程会更快,但会消耗更多的内存,最终可能导致模型的准确率下降。因此,首先让我们了解什么是批处理大小,以及为什么需要它。
什么是批处理大小?
在训练深度学习神经网络等模型时,指定批处理大小至关重要。简而言之,批处理大小是指一次传递给网络进行训练的样本数量。
批处理大小示例
假设你有 1000 张猫的照片,想要训练网络识别不同的猫品种。现在,假设你选择了 10 的批处理大小。这意味着在某一时刻,网络将同时处理 10 张猫的照片,也就是我们称之为的一组或一批。
明白了,批处理大小的概念很简单,但为什么它这么重要呢?理论上,你可以逐个数据元素传递给模型,而不必将数据放入批处理中。接下来,我们将在下文解释为什么需要批处理。
为什么要使用批次?
我们之前提到,较大的批次大小可以帮助模型在训练期间更快完成每个 epoch。这是因为,基于可用的计算资源,机器可能能够同时处理多个样本。
然而,即使你的机器能够处理非常大的批次,随着批次大小的增加,模型的最终输出质量可能会下降,导致模型对新数据的泛化能力减弱。
因此,我们可以得出结论:批次大小是另一个超参数,需要根据特定模型在整个训练过程中的表现进行评估和调整。同时,还需要监控机器在运行不同批次大小时对 GPU 的利用情况。
例如,如果你将批次大小设置得过高,比如 100,机器可能没有足够的处理能力同时处理所有 100 张图像。这时,减小批次大小可能是更好的选择。
现在我们已经了解了批次大小的一般概念,接下来看看如何在 PyTorch 和 Keras 中通过代码来优化合适的批次大小。
使用 PyTorch 找到合适的批处理大小
在本节中,我们将介绍如何在 Resnet18 模型上找到合适的批处理大小。我们将使用 PyTorch 的分析工具来测量 Resnet18 模型的训练性能和 GPU 利用率。
为了演示如何更好地使用 PyTorch 监控模型性能并将结果展示在 TensorBoard 上,我们将在代码中使用 PyTorch 分析器,并开启额外的选项。
跟随此演示
在你的云端 GPU 机器上,使用 wget 下载相应的 Jupyter 笔记本文件。然后,运行 Jupyter Labs 来打开笔记本。你可以通过粘贴以下内容并打开笔记本链接来完成这一操作:
wget https://raw.githubusercontent.com/gradient-ai/batch-optimization-DL/refs/heads/main/notebook.ipynb
jupyter lab
数据和模型的设置和准备
输入以下命令来安装 torch、torchvision 和 Profiler。
pip3 install torch torchvision torch-tb-profiler
以下代码将从 CIFAR10 中获取我们的数据集。接下来,我们将使用预先训练的模型 resnet18 进行迁移学习并训练模型。
#import all the necessary libraries
import torch
import torch.nn
import torch.optim
import torch.profiler
import torch.utils.data
import torchvision.datasets
import torchvision.models
import torchvision.transforms as T
#prepare input data and transform it
transform = T.Compose(
[T.Resize(224),
T.ToTensor(),
T.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
# use dataloader to launch each batch
train_loader = torch.utils.data.DataLoader(train_set, batch_size=1, shuffle=True, num_workers=4)
# Create a Resnet model, loss function, and optimizer objects. To run on GPU, move model and loss to a GPU device
device = torch.device("cuda:0")
model = torchvision.models.resnet18(pretrained=True).cuda(device)
criterion = torch.nn.CrossEntropyLoss().cuda(device)
optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
model.train()
# define the training step for each batch of input data
def train(data):
inputs, labels = data[0].to(device=device), data[1].to(device=device)
outputs = model(inputs)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
我们已经成功设置了基本模型,现在我们将启用分析器中的可选功能,以便在训练过程中记录更多信息。接下来,我们将包括以下参数:
- schedule该参数接受一个步骤(int),并在每个阶段返回要执行的分析器操作。
- profile_memory用于追踪 GPU 内存分配。将其设置为 true 可能会增加一些额外的时间开销。
- with_stack记录所有跟踪操作的源代码信息。
现在我们已经理解了这些术语,可以回到代码继续操作了:
with torch.profiler.profile(
schedule=torch.profiler.schedule(wait=1, warmup=1, active=3, repeat=2),
on_trace_ready=torch.profiler.tensorboard_trace_handler('./log/resnet18_batchsize1'),
record_shapes=True,
profile_memory=True,
with_stack=True
) as prof:
for step, batch_data in enumerate(train_loader):
if step >= (1 + 1 + 3) * 2:
break
train(batch_data)
prof.step() # Need call this at the end of each step to notify profiler of steps' boundary.
使用 Keras 找到合适的批次大小
在这种情况下,我们将使用任意顺序模型;
model = Sequential([
Dense(units=16, input_shape=(1,), activation='relu'),
Dense(units=32, activation='relu', kernel_regularizer=regularizers.l2(0.01)),
Dense(units=2, activation='sigmoid')
])
让我们集中精力于调用 model.fit() 的地方。这是人工神经网络学习并调用来训练模型的函数。
model.fit(
x=scaled_train_samples,
y=train_labels,
validation_data=valid_set,
batch_size=10,
epochs=20,
shuffle=True,
verbose=2
)
上面的 fit() 函数接受一个名为 batch_size 的参数。在这里,我们为 batch_size 变量赋值。在这个模型中,我们将该值设置为 10。因此,在模型训练过程中,我们将一次传入 10 个样本,直到整个循环完成。完成后,训练过程将重新开始下一个循环。
需要注意的重要事项
在执行多 GPU 训练时,批次大小需要特别关注,因为它可能会影响训练速度、内存使用、模型的收敛性。如果不加以注意,模型的权重甚至可能会出错。
- 速度和内存毫无疑问,使用较大的批次可以加快训练和预测的速度。由于与从 GPU 加载和卸载数据相关的开销较大,较小的批次会导致更高的处理开销。然而,一些研究表明,使用小批次进行训练可能会提高模型的最终效果分数。另一方面,较大的批次需要更多的 GPU 内存。大批次可能会导致内存不足问题,因为每层的输入在训练期间都要保留在内存中,尤其是用于反向传播步骤时。
- 收敛性如果你使用随机梯度下降 (SGD) 或其变体来训练模型,批次大小可能会影响网络的收敛速度和泛化能力。在许多计算机视觉问题中,通常使用的批次大小范围在 32 到 512 之间。
- GPU 出错风险这是一个容易被忽视但可能带来灾难性后果的技术细节。在多 GPU 训练中,确保每个 GPU 都能获得数据至关重要。如果你的数据集大小不能被批次大小整除,那么在最后一个 epoch 中可能会有少量样本没有分配给某些 GPU。这种情况会导致某些 Keras 层(特别是批次规范化层)出错,导致权重中出现 NaN 值(比如在批次规范化层中的运行平均值和方差)。
更糟糕的是,在训练期间这个问题可能不会被察觉,因为学习阶段设置为 1,但在预测期间(学习阶段设置为 0)会使用运行平均值和方差,这时 NaN 值可能会导致预测结果出现问题。
因此,在多 GPU 训练时,确保批处理大小保持一致非常重要。你可以通过拒绝不符合预定义大小的批次,或者通过重复批次中的条目直到它符合预定义大小来解决这一问题。最后还要记住,在多 GPU 配置下,批次大小应该大于 GPU 总数。
结论
在本文中,我们讨论了如何通过找到合适的批次大小来最大化 GPU 的利用率。只要你设置了一个合理的批次大小(例如 16 以上),并保持迭代次数和 epochs 不变,批次大小对性能的影响就会较小。不过,训练时间会受到一定影响。对于多 GPU 训练,我们应选择尽可能小的批次大小,以确保每个 GPU 都能充分发挥其能力。每个 GPU 使用 16 个样本是一个不错的选择。