增大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,审校:袁虎。

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

相关实践学习
在云上部署ChatGLM2-6B大模型(GPU版)
ChatGLM2-6B是由智谱AI及清华KEG实验室于2023年6月发布的中英双语对话开源大模型。通过本实验,可以学习如何配置AIGC开发环境,如何部署ChatGLM2-6B大模型。
相关文章
|
3月前
|
机器学习/深度学习 监控 算法
分布式光伏储能系统的优化配置方法(Matlab代码实现)
分布式光伏储能系统的优化配置方法(Matlab代码实现)
148 1
|
3月前
|
机器学习/深度学习 算法 物联网
【SCI】利用信念传播在超密集无线网络中进行分布式信道分配(Matlab代码实现)
【SCI】利用信念传播在超密集无线网络中进行分布式信道分配(Matlab代码实现)
129 0
|
2月前
|
编解码 运维 算法
【分布式能源选址与定容】光伏、储能双层优化配置接入配电网研究(Matlab代码实现)
【分布式能源选址与定容】光伏、储能双层优化配置接入配电网研究(Matlab代码实现)
162 12
|
8月前
|
安全 网络安全 数据库
YashanDB分布式节点间SSL连接配置
本文介绍YashanDB分布式节点间SSL连接配置方法,确保通信安全。需统一为整个集群配置SSL,使用相同根证书签名的服务器证书,否则可能导致连接失败或数据库无法启动。文章详细说明了使用OpenSSL生成根证书、服务器私钥、证书及DH文件的步骤,并指导如何将证书分发至各节点。最后,通过配置数据库参数(如`din_ssl_enable`)并重启集群完成设置。注意,证书过期需重新生成以保障安全性。
|
3月前
|
算法 Python
【EI复现】考虑网络动态重构的分布式电源选址定容优化方法(Matlab代码实现)
【EI复现】考虑网络动态重构的分布式电源选址定容优化方法(Matlab代码实现)
|
9月前
|
并行计算 PyTorch 算法框架/工具
融合AMD与NVIDIA GPU集群的MLOps:异构计算环境中的分布式训练架构实践
本文探讨了如何通过技术手段混合使用AMD与NVIDIA GPU集群以支持PyTorch分布式训练。面对CUDA与ROCm框架互操作性不足的问题,文章提出利用UCC和UCX等统一通信框架实现高效数据传输,并在异构Kubernetes集群中部署任务。通过解决轻度与强度异构环境下的挑战,如计算能力不平衡、内存容量差异及通信性能优化,文章展示了如何无需重构代码即可充分利用异构硬件资源。尽管存在RDMA验证不足、通信性能次优等局限性,但该方案为最大化GPU资源利用率、降低供应商锁定提供了可行路径。源代码已公开,供读者参考实践。
756 3
融合AMD与NVIDIA GPU集群的MLOps:异构计算环境中的分布式训练架构实践
|
9月前
|
机器学习/深度学习 人工智能 物联网
MiniMind:2小时训练出你的专属AI!开源轻量级语言模型,个人GPU轻松搞定
MiniMind 是一个开源的超小型语言模型项目,帮助开发者以极低成本从零开始训练自己的语言模型,最小版本仅需25.8M参数,适合在普通个人GPU上快速训练。
1745 10
MiniMind:2小时训练出你的专属AI!开源轻量级语言模型,个人GPU轻松搞定
|
9月前
|
人工智能 负载均衡 调度
COMET:字节跳动开源MoE训练加速神器,单层1.96倍性能提升,节省百万GPU小时
COMET是字节跳动推出的针对Mixture-of-Experts(MoE)模型的优化系统,通过细粒度的计算-通信重叠技术,显著提升分布式训练效率,支持多种并行策略和大规模集群部署。
474 9
|
10月前
|
SQL 数据建模 BI
【YashanDB 知识库】用 yasldr 配置 Bulkload 模式作单线程迁移 300G 的业务数据到分布式数据库,迁移任务频繁出错
问题描述 详细版本:YashanDB Server Enterprise Edition Release 23.2.4.100 x86_64 6db1237 影响范围: 离线数据迁移场景,影响业务数据入库。 外场将部分 NewCIS 的报表业务放到分布式数据库,验证 SQL 性能水平。 操作系统环境配置: 125G 内存 32C CPU 2T 的 HDD 磁盘 问题出现的步骤/操作: 1、部署崖山分布式数据库 1mm 1cn 3dn 单线启动 yasldr 数据迁移任务,设置 32 线程的 bulk load 模式 2、观察 yasldr.log 是否出现如下错

热门文章

最新文章