本文主要是介绍『PyTorch学习笔记』分布式深度学习训练中的数据并行(DP/DDP) VS 模型并行,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
分布式深度学习训练中的数据并行(DP/DDP) VS 模型并行 |
文章目录
- 一. 介绍
- 二. 并行数据加载
- 2.1. 加载数据步骤
- 2.2. PyTorch 1.0 中的数据加载器(Dataloader)
- 二. 数据并行
- 2.1. DP(DataParallel)的基本原理
- 2.1.1. 从流程上理解
- 2.1.2. 从模式角度理解
- 2.1.3. 从操作系统角度看
- 2.1.4. 低效率
- 2.2. DDP(DistributedDataParallel)的基本原理
- 2.3. DP和DDP对比
- 2.4. 分布式中的几个概念
- 三. 模型并行(ModelParallel)
- 四. 参考文献
一. 介绍
- 现代深度学习模型中的参数数量越来越大,数据集的规模也急剧增加。要在大型数据集上训练复杂的现代深度学习模型,必须使用多节点训练,否则会花费很长时间。在分布式深度学习训练中,人们可能总会看到数据并行和模型并行。在这篇博文中,将讨论这两种深度学习并行方法的理论、逻辑和一些误导点。
二. 并行数据加载
2.1. 加载数据步骤
- 流行的深度学习框架(例如Pytorch和Tensorflow)为分布式培训提供内置支持。从广义上讲,从磁盘读取输入数据开始,加载数据涉及四个步骤:
- 将数据从磁盘加载到主机:在这个阶段,数据从磁盘(可能是HDD或SSD)读取到主机的内存中。这个过程涉及文件系统的I/O操作,通常使用高级API,如Python的open函数,或者在深度学习框架中,可以使用数据加载器(如PyTorch的DataLoader或TensorFlow的tf.data API)来实现。这些数据加载器通常具有多线程或多进程功能,可以异步读取数据,并将其加载到CPU的内存中。
- 将数据从可分页内存传输到主机上的固定内存。请参阅此有关分页和固定的内存更多信息:可分页(pageable)内存和固定(pinned)内存都是主机内存的类型。可分页内存是普通的系统内存,操作系统可以将其页(一个内存管理单位)移动到磁盘上(即分页)。固定内存,又称为非分页内存,是指操作系统不能移动到磁盘的内存区域。固定内存的数据传输到GPU通常比从可分页内存传输更快,因为它避免了额外的复制步骤,并且可以直接通过DMA(直接内存访问)进行。在深度学习训练中,经常将数据从可分页内存复制到固定内存以准备传输到GPU。
- 将数据从固定内存传输到GPU:一旦数据位于固定内存中,它就可以通过高带宽的 PCIe总线(Peripheral Component Interface Express,总线和接口标准) 高效地传输到GPU内存中。深度学习框架通常提供了简化这个过程的工具。例如,在PyTorch中,你可以使用.to(device)或.cuda()方法将张量移动到GPU。此过程是由DMA引擎管理的,可以在不占用CPU资源的情况下进行。
- 在GPU上向前和向后传递:当数据位于GPU内存中时,可以开始训练过程,即进行模型的前向和反向传播。在前向传播中,模型的参数(也必须在GPU内存中)用于计算输出和损失函数。然后,通过反向传播,根据损失函数相对于模型参数的梯度,更新模型参数。这些计算完全在GPU上进行,利用其并行计算能力来加速训练过程。
- 补充2:谈到主机内存,有两个主要类别——可分页(或“非固定”)[pageable (or “un-pinned”)]内存和页面锁定(或“固定”)内存[page-locked (or “pinned”)]。当您在 C 程序中使用 malloc 分配内存时,分配是在可分页内存中完成的。 GPU 无法直接从可分页主机内存访问数据,因此当调用从可分页主机内存到设备内存的数据传输时,CUDA 驱动程序首先分配一个临时固定主机数组,将主机数据复制到固定数组,然后传输数据从固定数组传输到设备内存,如下图所示(有关更多信息,请参阅本页)
- 补充3:GPU 通常通过 PCIe 连接连接到主板,来自主(主机)内存的数据必须通过此 PCIe 链路传输到 GPU 内存。该链路的预期带宽是多少?为了回答这个问题,让我们看一下有关 PCIe 链路的一些基本信息。
2.2. PyTorch 1.0 中的数据加载器(Dataloader)
- PyTorch中的Dataloader提供使用多个进程(通过将
num_workers>0
设置)从磁盘加载数据以及将多页数据从可分页内存到固定内存(pinned memory) 的能力(通过设置)pin_memory = True
)。- 一般的,对于大批量的数据,若仅有一个线程用于加载数据,则数据加载时间占主导地位,这意味着无论我们如何加快数据处理速度,性能都会受到数据加载时间的限制。现在,设置
num_workers = 3以及pin_memory = True
。这样,可以使用多个进程从磁盘读取不重叠的数据,并启动生产者-消费者线程以将这些进程读取的数据从可分页的内存转移到固定的内存。
- 多个进程能够更快地加载数据,并且当数据处理时间足够长时,流水线数据加载几乎可以完全隐藏数据加载延迟。这是因为在处理当前批次的同时,将从磁盘读取下一个批次的数据,并将其传输到固定内存。如果处理当前批次的时间足够长,则下一个批次的数据将立即可用。这个想法还建议如何为num_workers参数设置适当的值。应该设置此参数,以使从磁盘读取批处理数据的速度比GPU处理当前批处理的速度更快(但不能更高,因为这只会浪费多个进程使用的系统资源)。
二. 数据并行
- 当一张 GPU 可以存储一个模型时,可以采用数据并行得到更准确的梯度或者加速训练,即每个 GPU 复制一份模型,将一批样本分为多份输入各个模型并行计算。因为求导以及加和都是线性的,数据并行在数学上也有效。
- 假设我们一个 batch 有 n n n 个样本,一共有 k k k 个 GPU 每个 GPU 分到 m j m_j mj 个样本。假设样本刚好等分,则有 m j = n k m_{j}=\frac nk mj=kn 。我们考虑总的损失函数 l l l 对参数 w w w 的导数:
∂ L o s s ∂ w = ∂ ⌊ 1 n ∑ i = 1 n l ( x i , y i ) ⌋ ∂ w = 1 n ∑ i = 1 n ∂ l ( x i , y i ) ∂ w = m 1 n ∂ [ 1 m 1 ∑ i = 1 m 1 l ( x i , y i ) ] ∂ w + m 2 n ∂ [ 1 m 2 ∑ i = m 1 + 1 m 1 + m 2 l ( x i , y i ) ] ∂ w + ⋯ + m k n ∂ [ 1 m k ∑ i = m k − 1 + 1 m k − 1 + m k l ( x i , y i ) ] ∂ w = ∑ j = 1 k m j n ∂ [ 1 m j ∑ i = m j − 1 + 1 m j − 1 + m j l ( x i , y i ) ] ∂ w = ∑ j = 1 k m j n ∂ l o s s j ∂ w \begin{aligned} \begin{aligned}\frac{\partial Loss}{\partial w}\end{aligned}& =\frac{\partial\left\lfloor\frac1n\sum_{i=1}^nl(x_i,y_i)\right\rfloor}{\partial w} \\ &=\frac1n\sum_{i=1}^n\frac{\partial l(x_i,y_i)}{\partial w} \\ &=\frac{m_1}n\frac{\partial\left[\frac1{m_1}\sum_{i=1}^{m_1}l(x_i,y_i)\right]}{\partial w}+\frac{m_2}n\frac{\partial\left[\frac1{m_2}\sum_{i=m_1+1}^{m_1+m_2}l(x_i,y_i)\right]}{\partial w}+\cdots+\frac{m_k}n\frac{\partial\left[\frac1{m_k}\sum_{i=m_{k-1}+1}^{m_{k-1}+m_k}l(x_i,y_i)\right]}{\partial w} \\ &=\sum_{j=1}^k\frac{m_j}n\frac{\partial\left[\frac1{m_j}\sum_{i=m_{j-1}+1}^{m_{j-1}+m_j}l(x_i,y_i)\right]}{\partial w} \\ &=\sum_{j=1}^k\frac{m_j}n\frac{\partial loss_j}{\partial w} \end{aligned} ∂w∂Loss=∂w∂⌊n1∑i=1nl(xi,yi)⌋=n1i=1∑n∂w∂l(xi,yi)=nm1∂w∂[m11∑i=1m1l(xi,yi)]+nm2∂w∂[m21∑i=m1+1m1+m2l(xi,yi)]+⋯+nmk∂w∂[mk1∑i=mk−1+1mk−1+mkl(xi,yi)]=j=1∑knmj∂w∂[mj1∑i=mj−1+1mj−1+mjl(xi,yi)]=j=1∑knmj∂w∂lossj- 其中: w w w 是模型参数, ∂ L o s s ∂ w \frac{\partial Loss}{\partial w} ∂w∂Loss 是大小为 n n n 的big batch的真实梯度, ∂ l o s s j ∂ w \frac{\partial loss_j}{\partial w} ∂w∂lossj 是 GPU/node k k k 的小批量梯度, x i x_i xi 和 y i y_i yi 是数据点 i i i, l ( x i , y i ) l(x_i,y_i) l(xi,yi) 是根据前向传播计算出的数据点 i i i 的损失, n n n 是数据集中数据点的总数, k k k 是 GPU/Node的总数, m k m_k mk 是分配给 GPU/节点的样本数量, m 1 + m 2 + ⋯ + m k = n m_1+m_2+\cdots+m_k=n m1+m2+⋯+mk=n。当 m 1 = m 2 = ⋯ = m k = n k m_1=m_2=\cdots=m_k=\frac nk m1=m2=⋯=mk=kn时,我们可以进一步有:
∂ L o s s ∂ w = 1 k [ ∂ l o s s 1 ∂ w + ∂ l o s s 2 ∂ w + ⋯ + ∂ l o s s k ∂ w ] \frac{\partial{Loss}}{ \partial w }=\frac{1}{k}\big[\frac{\partial loss_1}{\partial w}+\frac{\partial loss_2}{\partial w}+\cdots+\frac{\partial loss_k}{\partial w}\big] ∂w∂Loss=k1[∂w∂loss1+∂w∂loss2+⋯+∂w∂lossk]- 这里对于每个GPU/Node,我们使用相同的模型/参数进行前向传播,我们向每个节点发送一小批不同的数据,正常计算梯度,并将梯度发送回主节点。此步骤是异步的,因为每个 GPU/节点的速度略有不同。一旦我们获得了所有梯度(我们在这里进行同步),我们就计算梯度的(加权)平均值,并使用梯度的(加权)平均值来更新模型/参数。然后我们继续下一次迭代。
2.1. DP(DataParallel)的基本原理
- DP 的好处是,使用起来非常方便,只需要将原来单卡的 module 用 DP 改成多卡:
model = nn.DataParallel(model)
2.1.1. 从流程上理解
- DataParallel 从流程上来看,是通过将整个小批次(minibatch)数据加载到主线程上,然后将子小批次(sub-minibatches)数据分散到整个GPU网络中来工作。
- 把 minibatch 数据从page-locked memory 传输到 GPU 0(master),Master GPU 也持有模型,其他GPU拥有模型的 stale copy。
- 在 GPUs 之间 scatter minibatch 数据。具体是将输入一个 minibatch 的数据均分成多份,分别送到对应的 GPU 进行计算。
- 在 GPUs 之间复制模型。与 Module 相关的所有数据也都会复制多份。
- 在每个GPU之上运行前向传播,计算输出。PyTorch 使用多线程来并行前向传播,每个 GPU 在单独的线程上将针对各自的输入数据独立并行地进行 forward 计算。
- 在 master GPU 之上收集(gather)输出,计算损失。即通过将网络输出与批次中每个元素的真实数据标签进行比较来计算损失函数值。
- 把损失在 GPUs 之间 scatter,在各个GPU之上运行后向传播,计算参数梯度。
- 在 GPU 0 之上归并梯度。
- 更新梯度参数。①进行梯度下降,并更新主GPU上的模型参数;②由于模型参数仅在主GPU上更新,而其他从属GPU此时并不是同步更新的,所以需要将更新后的模型参数复制到剩余的从属 GPU 中,以此来实现并行。
2.1.2. 从模式角度理解
- 首先我们先给出一个技术上的概括,从模式角度看:
- DP 可以被认为是类似参数服务器的应用。
- DDP 可以被认为是集合通讯的应用。
- 参数服务器大致可以分为 master 和 worker,而DP 基于单机多卡,所以对应关系如下:
- worker :所有GPU(包括GPU 0)都是worker,都负责计算和训练网络。
- master :GPU 0(并非 GPU 真实标号,而是输入参数 device_ids 的首位)也负责整合梯度,更新参数。
- 所以我们重点看看 GPU 0。
- DataParallel会将网络模型默认放在GPU 0上,然后把模型从GPU 0 拷贝到其他的GPU,各个GPU开始并行训练,接着 GPU 0 作为master来进行梯度的汇总和模型的更新,最后将计算任务下发给其他GPU。这非常类似参数服务器的机制。
- 从官方图也可以看到同样的信息。
- DataParallel会开启 单进程多线程 进行数据并行化
- 前向传播:首先,模型和mini-batch的数据会被放到GPU:0上(master GPU),之后,GPU:0会把数据分割成sub-mini-batch并scatter(分发)到其他GPU上,第二步,GPU:0会把自己的模型参数复制到其他GPU上,每个GPU拥有相同的模型参数。第三步,每个GPU在单独的线程上对其sub-mini-batch的数据前向传播,得到模型的输出结结果。第四部,GPU:0会收集所有GPU的输出结果。
- 反向传播:GPU:0得到所有的结果之后会与真实的label计算loss并得到loss的梯度,GPU:0会把loss梯度 Scatter到所有GPU上,每个GPU会根据loss梯度反向传播计算所有参数的梯度,之后,所有GPU上计算得到的参数梯度会汇总到GPU:0上,GPU:0进而对参数进行更新。这就完成了一个batch的模型训练。
- 有人说GPU:0是个自私的家伙,它把其他GPU都当成工具人来用,核心机密不传授,我只给你们数据,不给你label,你们得到结果之后给我我给你们计算loss和loss的梯度,然后分发给你们去给我计算参数的梯度,之后我得到这些参数的梯度之后我去更新参数,之后等下回需要你们的时候再去给你们其他GPU去分发我更新好的参数。
- 这是一个悲伤的故事,首先 单进程多线程 就似乎已经注定的结局,python的全局解释锁给这些附属的GPU戴上了沉沉的牢拷,其他GPU想奋起反抗,但是DP里面只有一个优化器Optimizer,这个优化器Optimizer只在主GPU上进行参数更新,当环境不在改变的时候,其他GPU选择了躺平,当GPU:0忙前忙后去分发数据、汇总梯度,更新参数的时候,其他GPU就静静躺着。
2.1.3. 从操作系统角度看
- 从操作系统角度看,DP 和 DDP 有如下不同(我们属于提前剧透):
- DataParallel 是单进程,多线程的并行训练方式,并且只能在单台机器上运行。
- DistributedDataParallel 是多进程,并且适用于单机和多机训练。DistributedDataParallel 还预先复制模型,而不是在每次迭代时复制模型,并避免了全局解释器锁定。
2.1.4. 低效率
- 这种效率不高的数据并行方法,注定要被淘汰。是的,我们迎来了DDP(DistributedDataParallel)
2.2. DDP(DistributedDataParallel)的基本原理
- DistributedDataParallel,支持
all-reduce,broadcast,send 和 receive
等等。通过 MPI 实现 CPU 通信,通过 NCCL 实现 GPU 通信。可以用于 单机多卡也可用于多机多卡, 官方也曾经提到用 DistributedDataParallel 解决 DataParallel 速度慢,GPU负载不均衡的问题。- 效果比DataParallel好太多!!!torch.distributed相对于torch.nn.DataParalle 是一个底层的API,所以我们要修改我们的代码,使其能够独立的在机器(节点)中运行。
- 与 DataParallel 的单进程控制多 GPU 不同,在 distributed 的帮助下,我们只需要编写一份代码,torch 就会自动将其分配给n个进程,分别在 n 个 GPU 上运行。不再有主GPU,每个GPU执行相同的任务。对每个GPU的训练都是在自己的过程中进行的。每个进程都从磁盘加载其自己的数据。分布式数据采样器可确保加载的数据在各个进程之间不重叠。损失函数的前向传播和计算在每个GPU上独立执行。因此,不需要收集网络输出。在反向传播期间,梯度下降在所有GPU上均被执行,从而确保每个GPU在反向传播结束时最终得到平均梯度的相同副本。
- 区别: DDP通过多进程实现的。也就是说操作系统会为每个GPU创建一个进程,从而避免了Python解释器GIL带来的性能开销。而DataParallel()是通过单进程控制多线程来实现的。
- 假如我们有N张显卡:
- (缓解GIL限制)在DDP模式下,会有N个进程被启动,每个进程在一张卡上加载一个模型,这些模型的参数在数值上是相同的。
- (Ring-Reduce加速)在模型训练时,各个进程通过一种叫Ring-Reduce的方法与其他进程通讯,交换各自的梯度,从而获得所有进程的梯度;
- (实际上就是Data Parallelism)各个进程用平均后的梯度更新自己的参数,因为各个进程的初始参数、更新梯度是一致的,所以更新后的参数也是完全相同的。
2.3. DP和DDP对比
- 1. 每个进程对应一个独立的训练过程,且只对梯度等少量数据进行信息交换。
- 2. 每个进程包含独立的解释器和 GIL
2.4. 分布式中的几个概念
- 在 PyTorch 中,分布式并行处理是指在多个进程之间分配任务以便并行执行的一种方法,特别是在多个计算节点上进行深度学习模型的训练。以下是一些关键概念的详细解释:
- Group(进程组):在分布式计算中,group指的是进程组。一个进程组包含了一系列可以进行集体通信操作的进程。在PyTorch中,默认情况下所有的进程都会被自动分配到一个全局默认的进程组中,我们称之为 “world”。在这个默认组中,所有的进程都可以互相通信。
- 有时,你可能需要更精细的控制,比如你只想在一部分进程之间进行通信。在这种情况下,你可以通过 torch.distributed.new_group 接口创建一个新的进程组,该组包含全局进程组的一个子集。
- World Size(全局进程个数):world size是全局进程组中进程的总数。在多机分布式训练中,world size等于所有机器上的进程总和。例如,如果你在每台机器上启动了4个进程,且一共使用了3台机器,那么 world size 就是12。
- Rank(进程序号):在分布式训练中,rank是分配给每个进程的唯一标识符,其范围是从0到world size - 1。每个进程在通信时都会使用它的rank作为标识。通常情况下,rank为0的进程被认为是主进程(master),它可能负责一些协调工作,比如汇总数据或打印日志。
- Local Rank(局部GPU编号):在单个节点(机器)上可能有多个进程,每个进程可能管理一个或多个GPU。local_rank是指一个进程所管理的GPU在该节点上的编号。例如,如果一个节点上有8个GPU,那么每个进程的local_rank的范围将是0到7。在PyTorch中,这通常是由torch.distributed.launch模块在启动进程时内部设置的。这对于确保每个进程使用不同的GPU至关重要,以避免资源冲突。
三. 模型并行(ModelParallel)
- 模型并行性对我来说听起来很可怕,但它实际上与数学无关。这是分配计算机资源的本能。有时我们无法将所有数据放入(GPU)内存中,因为我们的深度学习模型中有太多层和参数。因此,我们可以将深度学习模型分成几个部分,将几个连续的层放在一个节点上并计算其梯度。这样,单个节点的参数数量就减少了,并且可以利用数据进行训练,得到更准确的梯度。
- 例如,我们有 10 个 GPU,我们想要训练一个简单的 ResNet50 模型。我们可以将前 5 层分配给 GPU #1,后 5 层分配给 GPU #2,依此类推,最后 5 层分配给 GPU #10。在训练期间,在每次迭代中,前向传播必须首先在 GPU #1 中完成。 GPU #2 正在等待 GPU #1 的输出,GPU #3 正在等待 GPU #2 的输出,依此类推。一旦前向传播完成。我们计算驻留在 GPU #10 中的最后一层的梯度,并更新 GPU #10 中这些层的模型参数。然后梯度反向传播到 GPU #9 中的前一层,等等。每个 GPU/节点就像工厂生产线中的一个隔间,它等待来自前一个隔间的产品,并将自己的产品发送到下一个隔间。
- 在我看来,模型并行性的名称具有误导性,不应将其视为并行计算的示例。更好的名称可能是 “模型序列化”,因为它在并行计算中使用串行方法而不是并行方法。然而,在某些场景下,某些神经网络中的某些层(例如 Siamese Network)实际上是“并行的”。这样,模型并行性可以在某种程度上表现得像真正的并行计算。然而,数据并行是100%并行计算。
四. 参考文献
- pytorch(分布式)数据并行个人实践总结——DataParallel/DistributedDataParallel
- Data Parallelism VS Model Parallelism in Distributed Deep Learning Training
- https://d2l.ai/chapter_computational-performance/parameterserver.html
- 💥 Training Neural Nets on Larger Batches: Practical Tips for 1-GPU, Multi-GPU & Distributed setups
- [原创][深度][PyTorch] DDP系列第一篇:入门教程
- [原创][深度][PyTorch] DDP系列第二篇:实现原理与源代码解析
- PyTorch 源码解读之 DP & DDP:模型并行和分布式训练解析
- 【DDP详解: 1】起底DP和DDP,数据分布式训练炼丹秘籍!
- 同时推荐一个官方设计笔记,讲得很详细,有兴趣可以看看。https://pytorch.org/docs/stable/notes/ddp.html
这篇关于『PyTorch学习笔记』分布式深度学习训练中的数据并行(DP/DDP) VS 模型并行的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!