跳到主要内容

数据并行和模型并行

数据并行

数据并行的方式是比较多的,有简单的数据并行、分布式数据并行还有全切片数据并行。

而数据并行本身的操作也比较多,不仅要对数据进行并行,还要对网络模型的参数进行并行,接着对梯度进行并行,还有优化器的状态。

数据并行的基本方式

假设我们现在有一套数据,如何高效地处理这批数据呢?一份活给一个人干要干一天,给两个人干就只需要半天。我们把数据切分成多份,然后分别给不同的人干,这样,我们只需要一个机器,就可以完成所有的工作。

这种切分数据的方式就叫做数据并行。

我们把这一套数据切分成两份,分别进行前向计算和反向计算,这样就能缩短一半的时间。

但是,在进行神经网络等工作的时候,我们会得到梯度,而这两台处理器往往不会是一模一样的,每台机器有独立的网络模型,得到的梯度也不一样。这个时候,我们就需要进行梯度汇合,并做梯度累积。

我们把数据分成Mini-Batch,然后把Mini-Batch切分成多份,每份给不同的处理器,这样,梯度累积这一步进行求和汇总,更新参数模型,分发给每一台机器,再去处理下一批数据。

梯度累积的基本方式

梯度累积目前基本有两种方式,一种是同步梯度累积,一种是异步梯度累积。

同步梯度累积:

每一台机器都算完自己的梯度,聚集到PS服务器中,统一进行更新。梯度累积分为计算-通讯两个阶段。 同步梯度累积严格地按照时间序列进行更新。

这样做会有一个弊端,就是资源浪费。假如说某一台设备要处理的数据量特别大,别的服务器就要等这台服务器处理完了再统一通讯更新,很明显就会存在时间和资源的浪费。

异步梯度累积:

每一个网络模型都单独计算自己的参数,不需要等别人计算完再更新。但是这样会造成网络模型很难收敛的结果。

实际上我们为了最终结果,会去等,所以更多采用同步梯度累积的方式。

通讯方式:

通讯服务器也有并行一说。假设我们以单张GPU(单卡)作为参数服务器,这个服务器就是分发参数给每一个GPU,然后每一个GPU计算自己的梯度,最后将梯度发送给参数服务器,参数服务器再把梯度分发给每一个GPU。

假设我们以多张GPU(多卡)作为参数服务器,这个服务器的工作方式稍微复杂一些,简单的说就是形成环,在遍历多次环之后,完成所有数据的同步。

多卡和跨机器通讯需要用到MPI/NCCL/HCCL/GLOO。

基于Pytorch的数据并行的具体实现

DP是对训练的数据进行并行,DDP是对梯度的数据进行并行。

DDP: distributed data parallel,多进程的实现方式,没有了Python的GIL,所以效率更高。每个进程不是同步所有的参数,而是同步误差这个指标,所以能够减少通讯这一步的数据处理量。采用Ring Allreduce方式。

需要注意梯度的同步不是设计成算完整个网络模型,再更新所有的服务器。这一步是每次进行分桶(bucket)。每一台服务器都有自己的bucket。分桶过后进行梯度的逆向排序,确定哪些梯度需要更新。然后跳过时间太慢或者很久没有更新的梯度。bucket的数据俱全,该层完成计算,进行集合通讯。通讯的时候其它层也会开始计算,有效利用空载时间。

All-Reduce的操作可以拆解为Reduce-Scatter和All-Gather。

FSDP全数据切片并行

FSDP更新了网络模型的参数、梯度、优化器的状态,并且卸载静态内存到CPU上。

还是一批数据,切分之后送进服务器进行前向计算、反向计算,然后对网络模型的权重参数进行Reduce-Scatter的同步方式,并卸载不需要的权重到CPU中(需要的时候再加载,并做All-Gather。这里是半闭环的)。更新网络模型权重。接着执行All-Gather。

FSDP的实现方式一般是调用API。常见的API包括RPC call、RPC fc、Dis Autograd、Dis Optimizer。