深度学习的并行化策略

19
什么样的并行化策略和形式可用于训练和服务神经网络?:
- 在机器内部跨核心(例如GPU/TPU/CPU) - 在网络或机架上跨多台机器
我还在寻找证据,了解它们如何在TensorFlow、PyTorch或MXNet等框架中使用。
训练:
据我所知,在训练大型数据集上的大型神经网络时,可以采用以下至少一种方式:
- 不同的核心或机器执行不同部分的图(“图拆分”)。例如,通过将不同的层托管在不同的机器上,可以将反向传播通过图本身进行并行化,因为(我认为?)自动微分图始终是DAG。 - 不同的核心或机器处理不同的数据样本(“数据拆分”)。在SGD中,梯度计算可以在不同的批次或样本上独立进行并行化(例如,在不同批次上独立计算梯度后可以将它们组合起来)。我认为这也称为梯度积累(gradient accumulation)(?)。

每种策略更适用于哪种类型的问题或神经网络?现代库支持哪些模式?是否可以将所有四种(2x2)策略结合起来使用?

除此之外,我还了解到:

  • 异步训练
  • 同步训练

但我不知道它具体是指什么,例如是在不同的数据批次上计算梯度还是在不同的子图上计算梯度?或者它完全指的是其他事情?

服务

如果网络很大,则预测/推理可能也很慢,并且在服务时模型可能无法适合单个机器内存。是否有已知的多核和多节点预测解决方案可处理这样的模型?


我认为在StackOverflow上会得到更好的回应。 - Tim Mak
谢谢@TimMak,很高兴将问题转移到StackOverflow。有关如何操作的建议吗? - Josh
1
我并不是这个确切主题的专家,但我过去曾在AWS SageMaker中使用过Horovod,这是在使用TS/Keras进行分布式计算时推荐的方法。以下是链接 https://horovod.readthedocs.io/en/stable/summary_include.html - Matus Dubrava
我对这个话题也不是很了解,但是有一些关于异步训练的指针。https://dev59.com/sV4c5IYBdhLWcg3w3tgu https://realpython.com/async-io-python/ - Tim Mak
加密货币挖矿。查看加密挖掘软件,例如xmr-stak https://github.com/fireice-uk/xmr-stak,您将找到CPU和GPU级别上并行化的完美示例。当我投入这方面时,我在72核Xeon Phi 7295上进行了一些工作,那是帮助我的东西。 - Dan M
@TimMak Python的asyncio与机器学习中的异步训练毫无关系。 - Daniel
2个回答

13

训练

通常有两种并行模型训练策略:数据并行和模型并行。

1. 数据并行

这种策略将训练数据分成N个分区,每个分区都会在不同的“设备”上(不同的CPU核心、GPU甚至机器)进行训练。与没有数据并行的训练相比,它每个minibatch步骤产生N个梯度。下一个问题是我们应该如何组合这些N个梯度。

一种方法是对所有N个梯度进行平均,然后基于平均值更新模型参数一次。这种技术称为同步分布式SGD。通过平均,我们有一个更准确的梯度,但代价是等待所有设备完成计算自己本地梯度。

另一种方式是不组合梯度——每个梯度将独立用于更新模型参数。因此,每个minibatch步骤将有N个参数更新,而不是前一种技术只有一个。这种技术称为异步分布式SGD。因为它不必等待其他设备完成,异步方法完成minibatch步骤所需时间比同步方法短。但是,异步方法会产生更嘈杂的梯度,所以可能需要完成更多的minibatch步骤以赶上同步方法(以损失为例)的性能。

有许多论文提出了一些对任一方法的改进和优化,但主要思想通常与上述相同。

在文献中,对于哪种技术实际上更好存在一些分歧。最终,大多数人现在都采用同步方法。

PyTorch中的数据并行

要进行同步SGD,我们可以使用torch.nn.parallel.DistributedDataParallel包装我们的模型:

from torch.nn.parallel import DistributedDataParallel as DDP

# `model` is the model we previously initialized
model = ...

# `rank` is a device number starting from 0
model = model.to(rank)
ddp_model = DDP(model, device_ids=[rank])

然后我们可以类似地进行训练。有关更多详细信息,请参考官方教程

要在PyTorch中执行异步SGD,由于没有类似于DistributedDataParallel的包装器,因此我们需要更加手动地实现它

TensorFlow/Keras中的数据并行

对于同步的SGD,我们可以使用tf.distribute.MirroredStrategy来包装模型初始化:

import tensorflow as tf

strategy = tf.distribute.MirroredStrategy()
with strategy.scope():
    model = Model(...)
    model.compile(...)

然后我们可以像往常一样进行训练。更多详细信息,请参阅Keras网站TensorFlow网站上的官方指南。

对于异步 SGD,我们同样可以使用tf.distribute.experimental.ParameterServerStrategy

2. 模型并行

这种策略将模型分成N个部分,每个部分都将在不同设备上计算。一种常见的方法是基于层进行划分:将不同的层放置在不同的设备上。但我们也可以根据模型架构进行更复杂的划分。

TensorFlow 和 PyTorch 中的模型并行

要在 TensorFlow 或 PyTorch 中实现模型并行,思路是相同的:将一些模型参数移入到不同的设备中。

在 PyTorch 中,我们可以使用torch.nn.Module.to方法将模块移到不同的设备上。例如,假设我们想要创建两个线性层,每个层都放置在不同的 GPU 上:

import torch.nn as nn

linear1 = nn.Linear(16, 8).to('cuda:0')
linear2 = nn.Linear(8, 4).to('cuda:1')

在TensorFlow中,我们可以使用tf.device将操作放置到特定的设备上。要在TensorFlow中实现上述PyTorch示例:

import tensorflow as tf
from tensorflow.keras import layers

with tf.device('/GPU:0'):
    linear1 = layers.Dense(8, input_dim=16)
with tf.device('/GPU:1'):
    linear2 = layers.Dense(4, input_dim=8)

更多细节请参考官方PyTorch教程; 或者如果你使用TensorFlow,可以使用更高级别的库mesh

3. 混合:数据并行和模型并行

回想一下,数据并行只是将训练数据分割,而模型并行仅分割模型结构。如果我们有一个模型太大,以至于即使使用任何并行策略都无法放入内存中,我们始终可以同时使用两种策略。

实践中,大多数人更喜欢数据并行,因为前者比后者更脱离(事实上是独立于)模型架构。也就是说,通过使用数据并行,他们可以随意更改模型架构,而不必担心该模型的哪个部分应该并行。

模型推理/服务

与并行化模型训练相比,并行化模型服务更容易,因为模型参数已经固定,每个请求都可以独立处理。类似于扩展常规的Python web服务,我们可以通过在单台机器上生成更多进程(以规避Python's GIL)甚至生成更多机器实例来缩放模型服务。

但是,当我们使用GPU提供模型服务时,需要做更多的工作来扩展它。由于GPU与CPU处理并发性的方式不同,为了最大限度地提高性能,我们需要进行推理请求分批处理。其思想是当请求到达时,我们不会立即处理它,而是等待一些超时时间以等待其他请求到达。当超时时间到期时,即使请求数量只有一个,我们也将它们全部批处理到GPU上进行处理。

为了尽量减少平均请求延迟,我们需要找到最佳的超时时间。要找到它,我们需要观察在最小化超时时间和最大化批处理大小之间存在权衡。如果超时时间太短,则批处理大小将很小,因此GPU将被低效利用。但是,如果超时时间太长,则早期到达的请求将等待太久才能得到处理。因此,最佳的超时时间取决于模型复杂度(因此是推理持续时间)和平均每秒收到的请求数。

编写调度程序执行请求分批处理并不是一项微不足道的任务,因此我们最好使用TensorFlow Serving或者PyTorch Serve,它们已经支持这些功能。


要了解更多关于并行和分布式学习的知识,您可以阅读这篇论文综述


7
作为问题比较广泛,我会尽力提供不同的见解并涉及到与@Daniel's深入回答所展示内容有所不同的主题。

训练

数据并行化 vs 模型并行化

@Daniel所提到的,数据并行化更常用且更容易正确实现。模型并行化的主要缺点是需要等待神经网络的一部分以及它们之间的同步。

假设你有一个简单的前馈神经网络,由5个不同的GPU扩展,每个设备对应一个层。在这种情况下,在每次前向传递期间,每个设备都必须等待来自前一层的计算。在这种简单情况下,设备之间的数据复制和同步需要更长时间,并且不会带来好处。

另一方面,有些模型更适合于模型并行化,例如Inception networks,如下图所示:

inception block

这里您可以看到来自前一层的4个独立路径,可以并行执行,只有2个同步点(Filter concatenationPrevious Layer)。

问题

例如,通过图本身的反向传播可以进行并行化,例如将不同的层托管在不同的机器上,因为(我认为?)autodiff图始终是DAG。

这并不容易。梯度是基于损失值(通常)计算的,您需要知道更深层次的梯度才能计算更浅层次的梯度。就像上面所说,如果您有独立路径,则更容易并且可能有所帮助,但在单个设备上更容易。

我相信这也被称为梯度积累(?)

不,实际上是在多个设备上进行缩减。您可以在PyTorch教程中看到一些内容。梯度累积是指您运行前向传递(在单个或多个设备上)N次并反向传播(梯度保留在图中,并且值在每个传递期间添加),优化器仅采取一步来更改神经网络的权重(并清除梯度)。在这种情况下,损失通常被分成没有优化器的步骤数。这用于更可靠的梯度估计,通常在无法使用大批量时使用。
跨设备的缩减如下所示:

reduction

这是数据并行中的全局归约,每个设备计算值并将其发送到所有其他设备,并在那里进行反向传播。
每种策略对于什么类型的问题或神经网络更好的时间是什么时候?
如上所述,如果您有足够的数据且样本很大(最多可以一次处理8k个样本或更多而不会遇到非常大的困难),则数据并行几乎总是可以的。
现代库支持哪些模式?
tensorflow和pytorch都支持两种方式,大多数现代和维护良好的库都以某种方式实现了这些功能。
是否可以同时组合所有四种(2x2)策略?
是的,您可以在机器内外并行化模型和数据。
同步与异步

异步

@Daniel简要描述,但值得一提的是更新不是完全分开的。这没有多大意义,因为我们本质上会基于它们的批次训练N个不同的模型。
相反,存在一个全局参数空间,每个副本都应异步共享计算更新(因此前向传递、反向传递、使用优化器计算更新并将此更新共享到全局参数)。
然而,这种方法有一个问题:没有保证一个工作进程计算前向传递时另一个工作进程已经更新了参数,因此更新是根据旧参数集计算的,这被称为“过时梯度”。由于这个原因,收敛可能会受到影响。
另一种方法是为每个工作进程计算N个步骤和更新,然后进行同步,但这种方法并不经常使用。
这部分内容基于优秀的blogpost,如果感兴趣,你应该一定要阅读它(其中还有更多关于过时性和一些解决方案的内容)。
同步
大多数情况下,如上所述,有不同的方法,但PyTorch从网络中收集输出并在其上进行反向传播(torch.nn.parallel.DistributedDataParallel)。顺便说一下,你应该仅使用这个(不要使用torch.nn.DataParallel),因为它可以克服Python的GIL问题。

总结

  • 当需要加速时,数据并行化几乎总是被使用的,因为您只需要在每个设备上复制神经网络(可以通过网络或单台机器内部),在前向传递期间在每个设备上运行一部分批次,将它们连接成一个单一的批次(同步)在一个设备上进行反向传播。
  • 有多种方法可以进行数据并行化,已由@Daniel介绍过。
  • 当模型太大无法放在单台计算机上(OpenAI's GPT-3是一个极端情况),或者当体系结构适用于此任务时,才会进行模型并行化,但这两种情况很少发生,据我所知。
  • 模型具有更多和更长的并行路径(同步点),则可能更适合进行模型并行化。
  • 重要的是要在相似的时间以及相似的负载下启动工作人员,以便在同步方法中不需要等待同步进程,或在异步方法中不会获得陈旧的梯度(尽管在后一种情况下这还不足够)。

服务

小型模型

由于您需要大型模型,我不会深入探讨小型模型的选项,只简单提及一下。
如果您想通过网络为多个用户提供服务,您需要某种方式来扩展您的架构(通常是像GCP或AWS这样的云)。您可以使用Kubernetes和它的POD,或预先分配一些服务器来处理请求,但这种方法效率低下(少量用户和运行服务器将产生无意义的成本,而大量用户可能会使基础设施停滞并花费太长时间来处理请求)。
另一种方法是使用基于无服务器的自动缩放。资源将根据每个请求提供,因此具有很大的扩展能力+在流量较低时不支付费用。您可以查看Azure Functions,因为它们正在改进ML / DL任务,或torchlambda用于PyTorch(免责声明,我是作者)用于较小的模型。
大型模型
如前所述,您可以使用Kubernetes与自定义代码或可用工具。
在第一种情况下,您可以像训练模型一样传播模型,但仅执行前向传递。通过这种方式,即使是巨大的模型也可以放在网络上(再次提到GPT-3具有175B参数),但需要大量工作。
在第二种情况下,@Daniel 提供了两种可能性。其他值得一提的可能包括(阅读相应文档,因为它们具有许多功能):
  • KubeFlow - 基于Kubernetes的多个框架(因此具有自动扩展、多节点功能),用于训练、服务等,可以与下面的MLFlow等其他内容连接
  • AWS SageMaker - 通过Python API进行训练和服务,由Amazon支持
  • MLFlow - 多个框架,用于实验处理和服务
  • BentoML - 多个框架,用于训练和服务

对于PyTorch,您可以在这里阅读更多信息,而Tensorflow则有很多服务功能可供使用,通过Tensorflow Extended (TFX)即可轻松实现。

来自评论区的问题

在单台机器内和跨多台机器之间,是否存在某些形式的并行性更好?

最佳的并行形式可能是在一个巨型计算机内,以最小化设备之间的传输。此外,在PyTorch中有不同的后端可以选择(mpi,gloo,nccl),并非所有后端都支持直接在设备之间发送、接收、减少等数据(有些可能支持CPU之间,其他GPU之间)。如果设备之间没有直接链接,则必须首先将其复制到另一个设备,然后再复制到目标设备(例如,在其他机器上的GPU->主机上的CPU->主机上的GPU)。请参阅 pytorch info。数据越多、网络越大,计算并行化就越有利可图。如果整个数据集可以适应单个设备,则不需要并行处理。此外,还应考虑诸如互联网传输速度、网络可靠性等因素。这些成本可能超过了收益。
如果你有大量数据(比如ImageNet的1000000张图片)或者大样本(比如2000x2000像素的图片),那么尽可能在单台机器上进行数据并行化以最小化机器之间的传输。只有在没有其他选择的情况下(例如无法放入GPU中),才分布式模型。当整个数据集可以轻松放入内存且从中读取速度最快时(例如MNIST数据集),并行化训练几乎没有意义。

为什么要构建专门用于机器学习的硬件,例如TPUs?

CPU不适合高度并行计算(例如矩阵乘法),而且CPU可能会被许多其他任务占用(例如数据加载),因此使用GPU是有意义的。

由于GPU是为图形处理而创建的(因此代数变换),它可以承担一些CPU工作,并且可以专门化(与CPU相比,它具有更多的核心但更简单,例如V100)。

现在,TPU专门针对张量计算而设计(主要是深度学习),起源于Google,与GPU相比仍处于WIP状态。这些器件适用于某些类型的模型(主要是卷积神经网络),在这种情况下可以带来加速。此外,应该使用最大批次的设备(请参见here),最好能够被128整除。您可以将其与NVidia的Tensor Cores技术(GPU)进行比较,在这种技术中,您只需使用可被168(分别为float16精度和int8)整除的批次(或层大小)即可实现良好的利用率。(尽管更多的批次越好,并且取决于核心数、确切的显卡和许多其他因素,可以参考here一些指南)。
另一方面,尽管两个主要框架(tensorflow正式支持,而PyTorch通过torch_xla包支持),但TPU的支持仍然不是最好的选择。总的来说,在深度学习中,GPU现在是一个很好的默认选择,对于卷积重型结构,TPUs可能会带来一些问题。此外(再次感谢@Daniel),TPUs更加节能,因此在比较单个浮点操作成本时应该更便宜。

谢谢Szymon!那么在GPU(或TPU)内部计算方面呢?是什么使它们适合于哪种类型的并行计算?有没有某些形式的并行计算比跨机器(例如网络中的节点)更适合于机器内部(例如GPU / TPU)?还是你所说的一切(“同步与异步”,“数据与模型并行性”等)在这两种情况下都适用?(如果是这样,为什么要构建定制的ML专用硬件,如TPU?) - Josh
3
@Josh 这些技术适用于机器内部和跨机器的并行处理。然而,正如Szymon所提到的,垂直扩展(在同一台机器上)比水平扩展(跨多台机器)更好,因为跨机器通信成本较高。例如,使用拥有4个GPU的单个实例要优于使用每个实例带有2个GPU的两个实例。至于为什么要构建TPU,是因为它们在性能/功率和性能/价格方面比GPU更出色。 - Daniel
1
@Josh 小批量梯度下降并不是一个不同的概念。您可以使用梯度累积来进行小批量梯度下降。传统上(没有梯度累积),您所做的是:在小批量上运行前向传递,计算损失,反向传播以获得梯度,通过优化器应用更新(例如纯梯度下降)。使用梯度累积:在小批量上运行前向传递,计算损失,反向传播(重复这三个步骤N次),从多个反向传播中平均梯度(除以N),然后应用优化。现在清楚了吗?在回答更新后,TPU / GPU是否清晰? - Szymon Maszke
1
@Josh 参数服务器 - 不仅如此。正如您链接的评论所示,每个工作节点都必须将其数据包发送到服务器(假设为 1GB),服务器会以某种方式合并更新并更新所有工作节点。因此,在这种情况下,每个工作节点只需要进行 21GB 的传输。在没有参数服务器的全约简中,每个工作节点需要进行 8 次传输(每个节点向其他节点发送数据,假设总共有 9 个节点),并且每个节点都应该从其他节点接收数据(再次是 8)。这将导致每个工作节点需要进行 161GB 的传输,因此浪费更多。是的,如果每个工作节点需要本地相同的数据,则可以帮助节省带宽。 - Szymon Maszke
1
@Josh DB - 我认为在数据库中存储参数(假设神经网络)没有意义,但如果我错了,请有人纠正我。这些参数被创建为高度优化的有向图,通常是不规则的,并且不需要太多描述。pytorch使用pickle并将模型加载到RAM中,tensorflow使用protobuf(可用于快速网络传输),keras使用HDF5,这又是一种文件格式。 - Szymon Maszke
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接