- 最新高二英语教学反思【优秀5篇】2024-12-13
- 英文推荐信_122024-12-13
- 留学瑞典2024-12-13
- 澳洲出国留学详解!附真实经历2024-12-13
- 移民加拿大最容易的三个省,门槛低,名额充足!2024-12-13
- 出国留学好处多还是坏处多?2024-12-13
- 大学排名_22024-11-26
- 985/211院校均分85能申请英国哪些大学研究生?这八大名校妥妥滴2024-11-26
- 美术生留学哪里好?2024-11-26
- 专科留学去哪个国家比较好?2024-11-26
奇亿娱乐-奇亿注册登录站
邮箱:youweb@admin.com
手机:13899999999
电话:020-88888888
地址:广东省广州市番禺经济开发区
数据并行就是把train set分布给不同的worker,每个worker通过缓存在本地的,分配到的数据集来更新一个共有的parameter,比如mini-batch中每个worker随机从本地数据集中选出一个batch,更新parameter。 关键点在于如何管理共有的paramter,比如有经典的ps和petuum 的ssp改进。petuum的bosen就是这种数据并行方式。
模型并行我只有大体的了解。比如一个model有1000维的parameter,那么通过某种算法让10个worker中的每个只负责10维的parameter的更新。 petuum的strads是模型并行的。
最后安利下我自己的基于c++分布式平台Dogee,使用DSM模型,管理本地内存数据和管理分布式内存一样简单
http://github.com/Menooker/Dogee
很巧这个问题出现在我的推荐上,一打开知乎就看到,遂答一发。
首先说下为什么要并行,众所周知目前的深度学习领域就是海量的数据加上大量的数学运算,所以计算量相当的大,训练一个模型跑上十天半个月啥的是常事。那此时分布式的意义就出现了,既然一张GPU卡跑得太慢就来两张,一台机器跑得太慢就用多台机器,于是我们先来说说数据并行,放张网上copy的图
在上面的这张图里,每一个节点(或者叫进程)都有一份模型,然后各个节点取不同的数据,通常是一个batch_size,然后各自完成前向和后向的计算得到梯度,这些进行训练的进程我们成为worker,除了worker,还有参数服务器,简称ps server,这些worker会把各自计算得到的梯度送到ps server,然后由ps server来进行update操作,然后把update后的模型再传回各个节点。因为在这种并行模式中,被划分的是数据,所以这种并行方式叫数据并行。
然后呢咱们来说说模型并行,深度学习的计算其实主要是矩阵运算,而在计算时这些矩阵都是保存在内存里的,如果是用GPU卡计算的话就是放在显存里,可是有的时候矩阵会非常大,比如在CNN中如果num_classes达到千万级别,那一个FC层用到的矩阵就可能会大到显存塞不下。这个时候就不得不把这样的超大矩阵给拆了分别放到不同的卡上去做计算,从网络的角度来说就是把网络结构拆了,其实从计算的过程来说就是把矩阵做了分块处理。这里再放一张网上盗的图表示下模型并行:
最后说说两者之间的联系,有的时候呢数据并行和模型并行会被同时用上。比如深度的卷积神经网络中卷积层计算量大,但所需参数系数 W 少,而FC层计算量小,所需参数系数 W 多。因此对于卷积层适合使用数据并行,对于全连接层适合使用模型并行。 就像这样:
关于这个更多地可以参考这篇博客,说的挺详细的卷积神经网络的并行化模型--One weird trick for parallelizing convolutional neural networks
作者|Lilian Weng、Greg Brockman
翻译|董文文
AI领域的许多最新进展都围绕大规模神经网络展开,但训练大规模神经网络是一项艰巨的工程和研究挑战,需要协调GPU集群来执行单个同步计算。
随着集群数和模型规模的增长,机器学习从业者开发了多项技术,在多个GPU上进行并行模型训练。
乍一看,这些并行技术令人生畏,但只需对计算结构进行一些假设,这些技术就会变得清晰——在这一点上,就像数据包在网络交换机之间传递一样,那也只是从A到B传递并不透明的位(bits)。
训练神经网络是一个迭代的过程。在一次迭代过程中,一批数据中的训练样本通过模型的layer(层)进行前向传递,计算得到输出。然后再通过layer进行反向传递,其中,通过计算参数的梯度,可以得到各个参数对最终输出的影响程度。
批量平均梯度、参数和每个参数的优化状态会传递给优化算法,如Adam,优化算法会计算下一次迭代的参数 ( 性能更佳)并更新每个参数的优化状态。随着对数据进行多次迭代训练,训练模型会不断优化,得到更加精确的输出。
不同的并行技术将训练过程划分为不同的维度,包括:
本文以GPU训练神经网络为例,并行技术同样也适用于使用其他神经网络加速器进行训练。作者为OpenAI华裔工程师Lilian Weng和联合创始人&总裁Greg Brockman。
数据并行是指将相同的参数复制到多个GPU上,通常称为“工作节点(workers)”,并为每个GPU分配不同的数据子集同时进行处理。
数据并行需要把模型参数加载到单GPU显存里,而让多个GPU计算的代价就是需要存储参数的多个副本。话虽如此,还有一些方法可以增加GPU的RAM,例如在使用的间隙临时将参数卸载(offload)到CPU的内存上。
更新数据并行的节点对应的参数副本时,需要协调节点以确保每个节点具有相同的参数。
最简单的方法是在节点之间引入阻塞通信:(1)单独计算每个节点上的梯度;(2) 计算节点之间的平均梯度;(3) 单独计算每个节点相同的新参数。其中,步骤 (2) 是一个阻塞平均值,需要传输大量数据(与节点数乘以参数大小成正比),可能会损害训练吞吐量。
有一些异步更新方案可以消除这种开销,但是会损害学习效率;在实践中,通常会使用同步更新方法。
流水并行是指按顺序将模型切分为不同的部分至不同的GPU上运行。每个GPU上只有部分参数,因此每个部分的模型消耗GPU的显存成比例减少。
将大型模型分为若干份连续的layer很简单。但是,layer的输入和输出之间存在顺序依赖关系,因此在一个GPU等待其前一个GPU的输出作为其输入时,朴素的实现会导致出现大量空闲时间。这些空闲时间被称作“气泡”,而在这些等待的过程中,空闲的机器本可以继续进行计算。
为了减少气泡的开销,在这里可以复用数据并行的打法,核心思想是将大批次数据分为若干个微批次数据(microbatches),每个节点每次只处理一个微批次数据,这样在原先等待的时间里可以进行新的计算。
每个微批次数据的处理速度会成比例地加快,每个节点在下一个小批次数据释放后就可以开始工作,从而加快流水执行。有了足够的微批次,节点大部分时间都在工作,而气泡在进程的开头和结束的时候最少。梯度是微批次数据梯度的平均值,并且只有在所有小批次完成后才会更新参数。
模型拆分的节点数通常被称为流水线深度(pipeline depth)。
在前向传递过程中,节点只需将其layer块的输出(激活)发送给下一个节点;在反向传递过程中,节点将这些激活的梯度发送给前一个节点。如何安排这些进程以及如何聚合微批次的梯度有很大的设计空间。GPipe 让每个节点连续前向和后向传递,在最后同步聚合多个微批次的梯度。PipeDream则是让每个节点交替进行前向和后向传递。
在流水并行中,模型沿layer被“垂直”拆分,如果在一个layer内“水平”拆分单个操作,这就是模型并行。许多现代模型(如 Transformer)的计算瓶颈是将激活值与权重相乘。
矩阵乘法可以看作是若干对行和列的点积:可以在不同的 GPU 上计算独立的点积,也可以在不同的 GPU 上计算每个点积的一部分,然后相加得到结果。
无论采用哪种策略,都可以将权重矩阵切分为大小均匀的“shards”,不同的GPU负责不同的部分。要得到完整矩阵的结果,需要进行通信将不同部分的结果进行整合。
Megatron-LM在Transformer的self-attention和MLP layer进行并行矩阵乘法;PTD-P同时使用模型、数据和流水并行,其中流水并行将多个不连续的layer分配到单设备上运行,以更多网络通信为代价来减少气泡开销。
在某些场景下,网络的输入可以跨维度并行,相对于交叉通信,这种方式的并行计算程度较高。如序列并行,输入序列在时间上被划分为多个子集,通过在更细粒度的子集上进行计算,峰值内存消耗可以成比例地减少。
混合专家(MoE)模型是指,对于任意输入只用一小部分网络用于计算其输出。在拥有多组权重的情况下,网络可以在推理时通过门控机制选择要使用的一组权重,这可以在不增加计算成本的情况下获得更多参数。
每组权重都被称为“专家(experts)”,理想情况是,网络能够学会为每个专家分配专门的计算任务。不同的专家可以托管在不同的GPU上,这也为扩大模型使用的GPU数量提供了一种明确的方法。
GShard将MoE Transformer扩展到6000亿个参数,其中MoE layers被拆分到多个TPU上,其他layers是完全重复的。 Switch Transformer将输入只路由给一个专家,将模型大小扩展到数万亿个参数,具有更高的稀疏性。
除了以上的并行策略,还有很多其他的计算策略可以用于训练大规模神经网络:
(原文:https://openai.com/blog/techniques-for-training-large-neural-networks/)
其他人都在看
欢迎下载体验OneFlow v0.7.0:
https://github.com/Oneflow-Inc/oneflow/【本文是图解大模型训练2篇,持续更新中,欢迎关注】:
猛猿:图解大模型训练之:流水线并行(Pipeline Parallelism),以Gpipe为例
猛猿:图解大模型训练之:数据并行上篇(DP, DDP与ZeRO)
猛猿:图解大模型训练之:数据并行下篇(ZeRO,零冗余优化)
猛猿:图解大模型系列之:张量模型并行,Megatron-LM
猛猿:图解大模型系列之:Megatron源码解读1,分布式环境初始化
【ChatGPT算法解析系列,可见】
猛猿:ChatGPT技术解析系列之:训练框架InstructGPT(因平台bug暂时显示不出来,可以看这一篇回答
猛猿:ChatGPT技术解析系列之:GPT1、GPT2与GPT3
猛猿:ChatGPT技术解析系列之:赋予GPT写代码能力的Codex
【20231205更新】
针对评论区问得比较多的两个问题,这边做一下回复:
(1)stage1的通讯量为什么是 而不是 ?
先说结论:实操中是 ,按论文概念定义是 。
在实操中,我们可以只对梯度做一次scatter-reduce,并用各自维护的optimizer去更新对应的W,然后再对W做all-gather使得每块卡上都有更新后的完整W,这样通讯量就是 。
那么 是怎么来的呢?因为论文定义stage1只有optimizer是切开的,意味着G和W都是完整的。所以对G做all-reduce(虽然拿回完整的G并没有意义),对W做all-gather,这样通讯量就是 。
本文写作时,最终选择按照论文对相关概念的定义,选择了 ,但是实操来看是完全可以用 实现的。评论区有朋友提到deepspeed的某次代码更新是将stage1的通讯量从 降至 ,可能也是基于此做了改进。
(2)stage2和stage3的流程是不是不太对?
本文在写作时,对于stage2和stage3是做了抽象的。即把整个FWD过程抽象为一个整体,把整个BWD过程抽象为一个整体,抽象过后对于一个模型就没有“layer“的概念了。这里做抽象的意义,是为了用更简明的图,来对FWD/BWD过程的通讯量做分析(可见这篇文章评论区对zlkkk这位朋友的回复)。Layer层面上Zero运作的细节,大家可以参考微软的官方博客。如果近期有时间,我也会把Layer层面的运作细节再整理下,绘制些新图po上来。
在上一篇的介绍中,我们介绍了以Google GPipe为代表的流水线并行范式。当模型太大,一块GPU放不下时,流水线并行将模型的不同层放到不同的GPU上,通过切割mini-batch实现对训练数据的流水线处理,提升GPU计算通讯比。同时通过re-materialization机制降低显存消耗。
但在实际应用中,流水线并行并不特别流行,主要原因是模型能否均匀切割,影响了整体计算效率,这就需要算法工程师做手调。因此,今天我们来介绍一种应用最广泛,最易于理解的并行范式:数据并行。
数据并行的核心思想是:在各个GPU上都拷贝一份完整模型,各自吃一份数据,算一份梯度,最后对梯度进行累加来更新整体模型。理念不复杂,但到了大模型场景,巨大的存储和GPU间的通讯量,就是系统设计要考虑的重点了。在本文中,我们将递进介绍三种主流数据并行的实现方式:
本文将首先介绍DP和DDP,在下一篇文章里,介绍ZeRO。全文内容如下:
1、数据并行(DP)
2、分布式数据并行(DDP)
本文是大模型训练系列的第二篇,持续更新中,推荐阅读:
猛猿:图解大模型训练之:流水线并行(Pipeline Parallelism),以Gpipe为例
一个经典数据并行的过程如下:
前文说过,实现DP的一种经典编程框架叫“参数服务器”,在这个框架里,计算GPU称为Worker,梯度聚合GPU称为Server。在实际应用中,为了尽量减少通讯量,一般可选择一个Worker同时作为Server。比如可把梯度全发到GPU0上做聚合。需要再额外说明几点:
在参数服务器的语言体系下,DP的过程又可以被描述下图:
DP的框架理解起来不难,但实战中确有两个主要问题:
我们对通讯开销再做详细说明。如果将传输比作一条马路,带宽就是马路的宽度,它决定每次并排行驶的数据量。例如带宽是100G/s,但每秒却推给Server 1000G的数据,消化肯定需要时间。那么当Server在搬运数据,计算梯度的时候,Worker们在干嘛呢?当然是在:
人类老板不愿意了:“打工系统里不允许有串行存在的任务!”,于是梯度异步更新这一管理层略诞生了。
上图刻画了在梯度异步更新的场景下,某个Worker的计算顺序为:
参数服务器的框架下,延迟的步数也可以由用户自己决定,下图分别刻划了几种延迟情况:
总结一下,异步很香,但对一个Worker来说,只是等于W不变,batch的数量增加了而已,在SGD下,会减慢模型的整体收敛速度。异步的整体思想是,比起让Worker闲着,倒不如让它多吃点数据,虽然反馈延迟了,但只要它在干活在学习就行。
batch就像活,异步就像画出去的饼,且往往不指定延迟步数,每个Worker干越来越多的活,但模型却没收敛取效,这又是刺伤了哪些打工仔们的心(狗头
受通讯负载不均的影响,DP一般用于单机多卡场景。因此,DDP作为一种更通用的解决方案出现了,既能多机,也能单机。DDP首先要解决的就是通讯问题:将Server上的通讯压力均衡转到各个Worker上。实现这一点后,可以进一步去Server,留Worker。
前文我们说过,聚合梯度 + 下发梯度这一轮操作,称为AllReduce。接下来我们介绍目前最通用的AllReduce方法:Ring-AllReduce。它由百度最先提出,非常有效地解决了数据并行中通讯负载不均的问题,使得DDP得以实现。
如下图,假设有4块GPU,每块GPU上的数据也对应被切成4份。AllReduce的最终目标,就是让每块GPU上的数据都变成箭头右边汇总的样子。
Ring-ALLReduce则分两大步骤实现该目标:Reduce-Scatter和All-Gather。
Reduce-Scatter
定义网络拓扑关系,使得每个GPU只和其相邻的两块GPU通讯。每次发送对应位置的数据进行累加。每一次累加更新都形成一个拓扑环,因此被称为Ring。看到这觉得困惑不要紧,我们用图例把详细步骤画出来。
一次累加完毕后,蓝色位置的数据块被更新,被更新的数据块将成为下一次更新的起点,继续做累加操作。
3次更新之后,每块GPU上都有一块数据拥有了对应位置完整的聚合(图中红色)。此时,Reduce-Scatter阶段结束。进入All-Gather阶段。目标是把红色块的数据广播到其余GPU对应的位置上。
All-Gather
如名字里Gather所述的一样,这操作里依然按照“相邻GPU对应位置进行通讯”的原则,但对应位置数据不再做相加,而是直接替换。All-Gather以红色块作为起点。
以此类推,同样经过3轮迭代后,使得每块GPU上都汇总到了完整的数据,变成如下形式:
建议读者们手动推一次,加深理解。
假设模型参数W的大小为 ,GPU个数为 。则梯度大小也为 ,每个梯度块的大小为
对单卡GPU来说(只算其send通讯量):
单卡总通讯量为 ,随着N的增大,可以近似为 。全卡总通讯量为
而对前文的DP来说,它的Server承载的通讯量是 ,Workers为 ,全卡总通讯量依然为 。虽然通讯量相同,但搬运相同数据量的时间却不一定相同。DDP把通讯量均衡负载到了每一时刻的每个Worker上,而DP仅让Server做勤劳的搬运工。当越来越多的GPU分布在距离较远的机器上时,DP的通讯时间是会增加的。
但这并不说明参数服务器不能打(有很多文章将参数服务器当作old dinosaur来看)。事实上,参数服务器也提供了多Server方法,如下图:
在多Server的模式下,进一步,每个Server可以只负责维护和更新某一块梯度(也可以某块梯度+参数一起维护),此时虽然每个Server仍然需要和所有Worker通讯,但它的带宽压力会小非常多。经过调整设计后,依然可以用来做DDP。虽然这篇文章是用递进式的方式来介绍两者,但不代表两者间一定要决出优劣。我想表达的观点是,方法是多样性的。对参数服务器有兴趣的朋友,可以阅读参考的第1个链接。
最后,请大家记住Ring-AllReduce的方法,因为在之后的ZeRO,Megatron-LM中,它将频繁地出现,是分布式训练系统中重要的算子。
1、在DP中,每个GPU上都拷贝一份完整的模型,每个GPU上处理batch的一部分数据,所有GPU算出来的梯度进行累加后,再传回各GPU用于更新参数
2、DP多采用参数服务器这一编程框架,一般由若个计算Worker和1个梯度聚合Server组成。Server与每个Worker通讯,Worker间并不通讯。因此Server承担了系统所有的通讯压力。基于此DP常用于单机多卡场景。
3、异步梯度更新是提升计算通讯比的一种方法,延迟更新的步数大小决定了模型的收敛速度。
4、Ring-AllReduce通过定义网络环拓扑的方式,将通讯压力均衡地分到每个GPU上,使得跨机器的数据并行(DDP)得以高效实现。
5、DP和DDP的总通讯量相同,但因负载不均的原因,DP需要耗费更多的时间搬运数据
1、https://web.eecs.umich.edu/~mosharaf/Readings/Parameter-Server.pdf
2、https://zh.d2l.ai/chapter_computational-performance/parameterserver.html
3、https://blog.csdn.net/dpppBR/article/details/80445569
4、https://arxiv.org/abs/1910.02054
5、https://blog.51cto.com/u_14691718/5631471
数据可以连续地址空间复制
A: double [0:4]
B: double [0:4] = [0.0, 1.0, 2.2, 3.3, 4.4]
A = B
也可以跳步(stride)赋值(内存上不连续)
A = B [0:4:2] // 以间隔2的方式访问B中元素
C: double [0:4, 0:4]
A = C [*,3] // 复制C的列
收集(gather) :以索引的方式获取某数组中的元素
X: int [0:4] = [3, 0, 4, 2, 1] // 打乱索引顺序
A = B[X] // A 为[3.3, 0.0, 4.4, 2.2, 1.1]
散射(satter):以索引的方式获取某数组中的元素,现在索引放在等号左侧
A[X] = B // A 为[1.1, 4.4, 3.3, 0.0, 2.2]
// 如果X=[0,0,0,0,0]则可能会有写入冲突
scan分为inclusive和exclusive两种。inclusive在计算时,不包含当前位置;exclusive在计算时包含当前位置。例如累计加法add_scan_inclusive([1, 0, 3, 0, 2]) à[1, 1, 4, 4, 6]
,add_scan_exclusive([1, 0, 3, 0, 2]) à[0, 1, 1, 4, 4]
。两者的结果可以通过加减原数据互相转化。
y(0) = 0;
for i = 1:n
y(i) = y(i-1) + x(i);
计算第n个值,就需要n-1次操作,且依赖于第n-1个值。看上去无法并行计算。
? 基于上述思想,有这样一种计算scan累加的方式,步骤为:1. 成对数据求和,2.递归scan累加,3. 成对数据求和。每一步对应如下计算结果的一行。
? 总计求和计算量的递推公式为,易知通项公式为。如果使用无穷多个处理器,则时间复杂度为
int find (int x, int y) (y % x==0) ? 1 : 0;
? 取余数,后使用上面压缩数组的方式即可实现。
d(n)=
0 if n.next is null
1+d(n.next) otherwise
? 为了计算通项值,要累计相乘以下矩阵(可并行完成)。再与初始两个值相乘即得到结果。
c[-1] = 0 //个位数之前没有进位,设置为0
for i = 0 to n-1 //从低位计算到高位
s[i] = ( a[i] xor b[i] ) xor c[i-1] //两数+进位 使用三个数的异或获得结果
c[i] = ( (a[i] xor b[i]) and c[i-1] ) or ( a[i] and b[i] ) //获得下一个进位。两数中有一个为1,且来自上一位的进位为1;或者两数都为1,才会有新的进位为1
p[i]=a[i]xor b[i]
,生成位(generate bit)g[i]=a[i]and b[i]
? 将进位写为递归形式
? 则M矩阵可以使用并行前缀方法计算。这种加法被称为carry look-ahead addition
? 状态转换可以查表完成
//定义函数
Tri_Inv(T)//简便起见,假设n为2的m次方
if T is 1-by-1 //如果维度为[1,1],则直接返回倒数
return 1/T
else
T=[[A,0],[C,B]] //矩阵分块,后递归调用
in parallel do {
invA = Tri_Inv( )
invB = Tri_Inv( ) // 隐含树结构
}
newC = -invB * C * invA // 矩阵乘法 log(n) 复杂度
return [[invA,0],[newC invB]]
time(Tri_Inv(n))=time(Tri_Inv(n/2)) + O(log(n))
time(Tri_Inv(n))
复杂度为推荐阅读