增大Batch训练神经网络:单GPU、多GPU及分布式配置的实用技巧

简介: 增大Batch训练神经网络:单GPU、多GPU及分布式配置的实用技巧

2018年中的大部分时间,我都在尝试利用训练神经网络克服GPUs的局限。无论是在包含1.5亿个参数的语言模型中,比如OpenAI’s huge Generative Pre-trained Transformer (or the recent and similar BERT model),还是在拥有3000万个输入元素的神经网络中,我都只能利用GPU处理很少的训练样本。

可是若想利用随机梯度下降算法得出不错的结果,大批量的训练样本必不可少。

如果你的GPU只能处理少量样本,该如何训练大批量模型呢?

接下来,我将介绍几类工具和技巧。

本文主要会讨论PyTorch框架,并就以下几个问题进行探讨:

  1. 当训练批量甚至单个训练样本大于GPU内存时,如何训练模型;
  2. 如何高效地利用多GPU机器;
  3. 如何在分布式设备上简单的使用多个机器。

在一个或多个GPU上训练大批量模型

你构建了一个不错的模型,可在尝试处理更多样本时,却得到CUDA RuntimeError:内存不足。

1

根据网友的回答你明白,加倍批量可以对结果进行优化。

此时,梯度累积(accumulating gradients)可以帮助到你。

2

PyTorch代码如下所示:

predictions = model(inputs)               # Forward pass
loss = loss_function(predictions, labels) # Compute loss function
loss.backward()                           # Backward pass
optimizer.step()                          # Optimizer step
predictions = model(inputs)               # Forward pass with new parameters

loss.backward()计算出每个参数的梯度,并存储在parameter.grad中。

梯度累积意味着,在调用potimizer.step()实现梯度下降之前,我们会求取parameter.grad张量中的几个反向操作的梯度和。

如下是使用梯度累积训练模型的示例。

model.zero_grad()                                   # Reset gradients tensors
for i, (inputs, labels) in enumerate(training_set):
    predictions = model(inputs)                     # Forward pass
    loss = loss_function(predictions, labels)       # Compute loss function
    loss = loss / accumulation_steps                # Normalize our loss (if averaged)
    loss.backward()                                 # Backward pass
    if (i+1) % accumulation_steps == 0:             # Wait for several backward steps
        optimizer.step()                            # Now we can do an optimizer step
        model.zero_grad()                           # Reset gradients tensors
        if (i+1) % evaluation_steps == 0:           # Evaluate the model when we...
            evaluate_model()                        # ...have no gradients accumulated

扩展

我们甚至可以在GPU上训练一个连样本都无法加载得模型,并且可以使用梯度检查点(gradient-checkpoingting)节省计算资源。
梯度检查点会将我们连续计算的元前馈和元反向传播切分成片段。但由于需要增加额外的计算以减少内存需求,该方法效率不高。不过,它在某些示例中又有较为明显的优势,比如在长序列上训练RNN模型,点击此处查看详情。

或有兴趣可进入下列文档进行查询:

TensorFlow:https://github.com/openai/gradient-checkpointing
PyTorch doc:https://pytorch.org/docs/stable/checkpoint.html

3
A “Memory-poor” strategy that needs O(1) memory (but requires O(n²) computation steps) — From Yaroslav Bulatov’s nice post: https://medium.com/tensorflow/fitting-larger-networks-into-memory-583e3c758ff9

多GPU机器

在多GPU服务器上训练PyTorch模型首选torch.nn.DataParallel。该策略能够在多个指定设备上按照batch dimension分割输入,实现并行化模块。

DataParallel实现如下所示:

parallel_model = torch.nn.DataParallel(model) # Encapsulate the model
predictions = parallel_model(inputs)          # Forward pass on multi-GPUs
loss = loss_function(predictions, labels)     # Compute loss function
loss.backward()                               # Backward pass
optimizer.step()                              # Optimizer step
predictions = parallel_model(inputs)          # Forward pass with new parameters

但DataParallel存在GPU使用不均衡的问题,下图给出了相应解释:

4
Forward and Backward passes with torch.nn.DataParallel

在前向传播的第四个步骤(见右上)中,GPU-1汇集了所有并行计算的结果。

通过下列所示的方式能够计算出语言模型输出的大小:

5
Number of elements in the output of a language model

现有如下假设:数据集共含4万词汇,序列中包含250 tokens,每个batch 包含32个示例,每个元素4 bytes,模型的输出占用1.2GB。但我们需要2.4GB的内存才能存储相关的梯度张量。

这种存储方式会使得GPU-1被过度使用,从而造成GPU使用不均衡的问题。

多GPU机器上的负载均衡

想要解决GPU使用不均衡的问题需要将每部分输出都保留在原有的GPU上,而不汇集于GPU-1。

张航开源了名为PyTorch-Encoding的包,可用于缓解上述问题。

我对这个开源包做了一些调整,你可以点击此处下载parallel.py。此包中包含两个模块:DataParallelModel以及DataParallelCriterion,如下所示:


from parallel import DataParallelModel, DataParallelCriterion

parallel_model = DataParallelModel(model)             # Encapsulate the model
parallel_loss  = DataParallelCriterion(loss_function) # Encapsulate the loss function

predictions = parallel_model(inputs)      # Parallel forward pass
                                          # "predictions" is a tuple of n_gpu tensors
loss = parallel_loss(predictions, labels) # Compute loss function in parallel
loss.backward()                           # Backward pass
optimizer.step()                          # Optimizer step
predictions = parallel_model(inputs)      # Parallel forward pass with new parameters

DataParallelModel不同于torch.nn.DataParallel的是,前向传播的输出(predictions)没有汇集在GPU-1中,而是作为n_gup张量的元组分布在相应的GPU上。

DataParallelCriterion容器封装了损失函数,并且将n_gpu张量的元组和目标标签张量作为输入。

下图描述了DataParallelModel/DataParallelCriterion的内部情况:

6

下面有两个特殊情况,并给出了解决办法:

  1. 模型输出了一些张量:你可以利用output_1,output_2 = zip(*predictions)分解它们。
  2. 若你不想并行计算损失函数,则可以利用gathered_prdictions = parallel.gather(predictions)收集张量。

分布式训练

PyTorch中的DistributedDataParallel可以帮助我们在遇到大批量训练问题时,拥有控制多个服务器的运算能力。

但值得注意的是:由于对每个节点都要启动一个独立的Python训练脚本,在设定时需要注意改变工作流程。

每个脚本在训练中都会拥有:

  1. 它自己的优化器,在每次迭代中都执行一个完整的优化,不需要参数传输。
  2. 一个独立的Python解释器:能够避免GIL-freeze

在后面我们将通过代码进行讨论:

torch.distributed包能够为同步分布式运算提供低级原语,基于此构建得到DistributedDataParallel。你可以通过阅读文档以及教程对其进行进一步理解。

接下来,我们将使用具有两个4-GPU的服务器。

7
The main server (server 1) has an accessible IP and an open port for communication.

升级Python脚本以适用分布式训练

首先,我们需要对脚本进行升级,使其能够独立的在机器(节点)中运行。我们想要完全实现分布式,并且在每个结点的每个GPU上独立运行进程,这一共需要8个进程。

接下来,初始化分布式后端,封装模型以及准备数据,这些数据用于在独立的数据子集中训练进程。更新后的代码如下:

from torch.utils.data.distributed import DistributedSampler
from torch.utils.data import DataLoader

# Each process runs on 1 GPU device specified by the local_rank argument.
parser = argparse.ArgumentParser()
parser.add_argument("--local_rank", type=int)
args = parser.parse_args()

# Initializes the distributed backend which will take care of sychronizing nodes/GPUs
torch.distributed.init_process_group(backend='nccl')

# Encapsulate the model on the GPU assigned to the current process
device = torch.device('cuda', arg.local_rank)
model = model.to(device)
distrib_model = torch.nn.parallel.DistributedDataParallel(model,
                                                          device_ids=[args.local_rank],
                                                          output_device=args.local_rank)

# Restricts data loading to a subset of the dataset exclusive to the current process
sampler = DistributedSampler(dataset)

dataloader = DataLoader(dataset, sampler=sampler)
for inputs, labels in dataloader:
    predictions = distrib_model(inputs.to(device))         # Forward pass
    loss = loss_function(predictions, labels.to(device))   # Compute loss function
    loss.backward()                                        # Backward pass
    optimizer.step()                                       # Optimizer step

为Python脚本加载多个实例

现在,我们将在每个服务器上启动训练脚本的实例。

我们使用PyTorch中的torch.distributed.launch运行脚本。它能用于环境变量的设置,并使用正确的local_rank参数调用脚本。

最主要的是第一台机器,所有的机器都要求能对它进行访问。因此,它需要拥有一个可以访问的IP地址(示例中为:196.168.1.1)以及一个开放的端口(示例中为:1234)。我们将使用torch.distributed.launch在第一台机器上运行脚本,具体如下:

python -m torch.distributed.launch --nproc_per_node=4 --nnodes=2 --node_rank=0 --master_addr="192.168.1.1" --master_port=1234 OUR_TRAINING_SCRIPT.py (--arg1 --arg2 --arg3 and all other arguments of our training script)

同样在第二台机器中运行脚本:

python -m torch.distributed.launch --nproc_per_node=4 --nnodes=2 --node_rank=1 --master_addr="192.168.1.1" --master_port=1234 OUR_TRAINING_SCRIPT.py (--arg1 --arg2 --arg3 and all other arguments of our training script)

除了—node_rank参数之外,上述两个命令相同。

扩展

如果你觉得在计算机集群上运行一组几乎相同的命令有些枯燥,可点击此处了解GNU并行

image


以上为译文

本文由阿里云云栖社区组织翻译。

文章原标题《Training Neural Nets on Larger Batches: Practical Tips for 1-GPU, Multi-GPU & Distributed setups》,作者:
Thomas Wolf,译者:Elaine,审校:袁虎。

文章为简译,更为详细的内容,请查看原文

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
3月前
|
机器学习/深度学习 PyTorch 算法框架/工具
目标检测实战(一):CIFAR10结合神经网络加载、训练、测试完整步骤
这篇文章介绍了如何使用PyTorch框架,结合CIFAR-10数据集,通过定义神经网络、损失函数和优化器,进行模型的训练和测试。
204 2
目标检测实战(一):CIFAR10结合神经网络加载、训练、测试完整步骤
|
3月前
|
机器学习/深度学习 数据可视化 计算机视觉
目标检测笔记(五):详细介绍并实现可视化深度学习中每层特征层的网络训练情况
这篇文章详细介绍了如何通过可视化深度学习中每层特征层来理解网络的内部运作,并使用ResNet系列网络作为例子,展示了如何在训练过程中加入代码来绘制和保存特征图。
73 1
目标检测笔记(五):详细介绍并实现可视化深度学习中每层特征层的网络训练情况
|
3月前
|
并行计算 Shell TensorFlow
Tensorflow-GPU训练MTCNN出现错误-Could not create cudnn handle: CUDNN_STATUS_NOT_INITIALIZED
在使用TensorFlow-GPU训练MTCNN时,如果遇到“Could not create cudnn handle: CUDNN_STATUS_NOT_INITIALIZED”错误,通常是由于TensorFlow、CUDA和cuDNN版本不兼容或显存分配问题导致的,可以通过安装匹配的版本或在代码中设置动态显存分配来解决。
73 1
Tensorflow-GPU训练MTCNN出现错误-Could not create cudnn handle: CUDNN_STATUS_NOT_INITIALIZED
|
1月前
|
机器学习/深度学习 数据可视化 TensorFlow
使用Python实现深度学习模型的分布式训练
使用Python实现深度学习模型的分布式训练
174 73
|
11天前
|
消息中间件 负载均衡 Java
如何设计一个分布式配置中心?
这篇文章介绍了分布式配置中心的概念、实现原理及其在实际应用中的重要性。首先通过一个面试场景引出配置中心的设计问题,接着详细解释了为什么需要分布式配置中心,尤其是在分布式系统中统一管理配置文件的必要性。文章重点分析了Apollo这一开源配置管理中心的工作原理,包括其基础模型、架构模块以及配置发布后实时生效的设计。此外,还介绍了客户端与服务端之间的交互机制,如长轮询(Http Long Polling)和定时拉取配置的fallback机制。最后,结合实际工作经验,分享了配置中心在解决多台服务器配置同步问题上的优势,帮助读者更好地理解其应用场景和价值。
48 18
|
17天前
|
人工智能 弹性计算 监控
分布式大模型训练的性能建模与调优
阿里云智能集团弹性计算高级技术专家林立翔分享了分布式大模型训练的性能建模与调优。内容涵盖四大方面:1) 大模型对AI基础设施的性能挑战,强调规模增大带来的显存和算力需求;2) 大模型训练的性能分析和建模,介绍TOP-DOWN和bottom-up方法论及工具;3) 基于建模分析的性能优化,通过案例展示显存预估和流水线失衡优化;4) 宣传阿里云AI基础设施,提供高效算力集群、网络及软件支持,助力大模型训练与推理。
|
2月前
|
机器学习/深度学习 自然语言处理 语音技术
Python在深度学习领域的应用,重点讲解了神经网络的基础概念、基本结构、训练过程及优化技巧
本文介绍了Python在深度学习领域的应用,重点讲解了神经网络的基础概念、基本结构、训练过程及优化技巧,并通过TensorFlow和PyTorch等库展示了实现神经网络的具体示例,涵盖图像识别、语音识别等多个应用场景。
89 8
|
2月前
|
数据库
如何在Seata框架中配置分布式事务的隔离级别?
总的来说,配置分布式事务的隔离级别是实现分布式事务管理的重要环节之一,需要认真对待和仔细调整,以满足业务的需求和性能要求。你还可以进一步深入研究和实践 Seata 框架的配置和使用,以更好地应对各种分布式事务场景的挑战。
60 6
|
2月前
|
机器学习/深度学习 自然语言处理 并行计算
DeepSpeed分布式训练框架深度学习指南
【11月更文挑战第6天】随着深度学习模型规模的日益增大,训练这些模型所需的计算资源和时间成本也随之增加。传统的单机训练方式已难以应对大规模模型的训练需求。
244 3
|
2月前
|
分布式计算 Java 开发工具
阿里云MaxCompute-XGBoost on Spark 极限梯度提升算法的分布式训练与模型持久化oss的实现与代码浅析
本文介绍了XGBoost在MaxCompute+OSS架构下模型持久化遇到的问题及其解决方案。首先简要介绍了XGBoost的特点和应用场景,随后详细描述了客户在将XGBoost on Spark任务从HDFS迁移到OSS时遇到的异常情况。通过分析异常堆栈和源代码,发现使用的`nativeBooster.saveModel`方法不支持OSS路径,而使用`write.overwrite().save`方法则能成功保存模型。最后提供了完整的Scala代码示例、Maven配置和提交命令,帮助用户顺利迁移模型存储路径。

热门文章

最新文章