深度学习的并行化策略

Parallelization strategies for deep learning

哪些并行化策略和形式可行并可用于训练服务神经网络?:

我也在寻找证据证明它们还可以如何用于例如TensorFlow、PyTorch 或 MXNet。

培训

据我所知,在大型数据集上训练大型神经网络时,至少可以:

  1. 不同的核心机器不同的部分运行图 (" 分裂").例如。通过图本身的反向传播可以并行化,例如通过将不同的层托管在不同的机器上,因为(我认为?)autodiff graph 始终是 DAG.
  2. 不同的内核机器不同的样本 的数据(数据 拆分”)。在 SGD 中,跨批次或样本的梯度计算也可以并行化(例如,可以在不同批次上独立计算梯度后组合梯度)。我相信这也叫梯度积累(?)。

每种策略何时更适合哪种类型的问题或神经网络?现代图书馆支持哪些模式?可以将所有四种 (2x2) 策略结合起来吗?

除此之外,我还读到了:

但我不知道具体指的是什么,例如是梯度不同数据批次上的计算还是梯度在不同[=32]上的计算=]子图?或者它可能指的是其他东西?

服务

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

培训

一般来说,并行化模型训练有两种策略:数据并行和模型并行。

1。数据并行度

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

一种方法是对所有 N 个梯度取平均值,然后根据平均值更新模型参数一次。这种技术称为 同步分布式 SGD。通过取平均值,我们得到了更准确的梯度,但是需要等待所有设备完成计算自己的局部梯度。

另一种方法是不组合梯度——每个梯度将用于独立更新模型参数。因此,每个小批量步骤将有 N 个参数更新,而之前的技术只有一个。这种技术被称为异步分布式SGD。因为它不必等待其他设备完成,所以异步方法完成一个小批量步骤所花费的时间比同步方法要少。然而,异步方法会产生更多的噪声梯度,因此它可能需要完成更多的小批量步骤才能赶上同步方法的性能(在损失方面)。

有很多论文对这两种方法提出了一些改进和优化,但主要思想与上述大致相同。

在文献中,对于哪种技术在实践中更好存在一些分歧。最后,大多数人现在都选择了同步方法。

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])

然后我们可以类似地训练它。更详细的可以参考the official tutorial.

为了在 PyTorch 中进行异步 SGD,我们需要 implement it more manually 因为没有类似于 DistributedDataParallel 的包装器。

TensorFlow/Keras

中的数据并行

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

import tensorflow as tf

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

然后我们就可以照常训练了。更详细的可以参考Keras website and TensorFlow website.

上的官方攻略

对于异步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)

详情请参考the official PyTorch tutorial; or if you use TensorFlow you can even use a more high-level library like mesh

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

回想一下,数据并行仅拆分训练数据,而模型并行仅拆分模型结构。如果我们的模型如此之大,以至于即使在使用任何一种并行策略后它仍然无法容纳在内存中,我们总是可以同时执行这两种策略。

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

模型推理/服务

并行模型服务比并行模型训练更容易,因为模型参数已经固定并且每个请求都可以独立处理。与扩展常规 Python Web 服务类似,我们可以通过在单台机器中生成更多进程(解决方法 Python's GIL)或什至生成更多机器实例来扩展模型服务。

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

为了最小化平均请求延迟,我们需要找到最佳的超时时间。为了找到它,我们需要观察最小化超时持续时间和最大化批量大小之间的权衡。如果超时时间太短,批量大小就会很小,因此 GPU 将得不到充分利用。但是如果超时太高,早到的请求将等待太久才能得到处理。因此,最佳超时持续时间取决于模型复杂性(因此,推理持续时间)和每秒接收的平均请求数。

实现一个调度器来做请求批处理不是一件小事,所以我们最好使用已经支持它的 TensorFlow Serving or PyTorch Serve 而不是手动完成。


要了解有关并行和分布式学习的更多信息,您可以阅读 this review paper

由于问题范围很广,我将尝试阐明一些不同的观点,并探讨与中所示内容不同的主题 深入解答。

训练

数据并行化与模型并行化

所述,数据并行性使用得更频繁,也更容易正确执行。模型并行性的主要警告是需要等待部分神经网络和它们之间的同步。

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

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

在这里你可以看到 4 个来自上一层的独立路径可以并行并且只有 2 个同步点(Filter concatenationPrevious Layer)。

问题

E.g. backpropagation through the graph itself can be parallelized e.g. by having different layers hosted on different machines since (I think?) the autodiff graph is always a DAG.

没那么容易。梯度是根据损失值(通常)计算的,你需要知道更深层的梯度才能计算更浅层的梯度。如上所述,如果你有独立的路径,它会更容易并且可能会有所帮助,但在单个设备上更容易。

I believe this is also called gradient accumulation (?)

不,它实际上是跨多个设备减少。您可以在 PyTorch tutorial 中看到其中的一些内容。梯度累积是当你 运行 你的正向传递(在单个或多个设备上)N 次并反向传播(梯度保留在图中并且在每次传递期间添加值)并且优化器仅使一步改变神经网络的权重(并清除梯度)。在这种情况下,损失通常除以没有优化器的步骤数。这用于更可靠的梯度估计,通常在您无法使用大批量时。

跨设备减少如下所示:

这是数据并行化的全归约,每个设备计算发送到所有其他设备并在那里反向传播的值。

When is each strategy better for what type of problem or neural network?

如上所述,如果您有足够的数据并且样本很大(最多 8k 个样本或更多样本可以一次完成而没有 非常大奋斗)。

Which modes are supported by modern libraries?

tensorflowpytorch 都支持,大多数现代和维护的库都以某种方式实现了这些功能

can one combine all four (2x2) strategies

是的,您可以跨机器和机器内部并行化模型和数据。

synchronous vs asynchronous

异步

简要描述,但值得一提的是更新并不是完全独立的。这没什么意义,因为我们基本上会根据批次训练 N 不同的模型。

相反,有一个全局参数 space,其中每个副本都应该异步共享计算的更新(因此向前传递,向后,使用优化器计算更新并将此更新共享到全局参数)。

虽然这种方法有一个问题:不能保证当一个工作人员计算前向传递时另一个工作人员更新了参数,因此更新是根据旧参数集计算的 这称为 陈旧梯度。因此,收敛可能会受到伤害。

其他方法是为每个工作人员计算 N 个步骤和更新并在之后同步它们,尽管它不经常使用。

这部分基于很棒的 blogpost,如果有兴趣,您绝对应该阅读它(有更多关于陈旧性和一些解决方案)。

同步

之前主要描述过,有不同的方法,但 PyTorch 从网络收集输出并在它们上反向传播 (torch.nn.parallel.DistributedDataParallel)[https://pytorch.org/docs/stable/nn.html#torch.nn.parallel.DistributedDataParallel]。顺便提一句。你应该只有这个(没有 torch.nn.DataParallel)因为它克服了 Python 的 GIL 问题。

外卖

  • 在加速时几乎总是使用数据并行化,因为你“只”需要在每个设备上复制神经网络(通过网络或在单台机器内),运行 每个批处理的一部分在前向传播过程中,将它们在一台设备上连接成一个批次(同步)并在 said 上反向传播。
  • 有多种方法可以进行数据并行化,
  • 已经介绍过
  • 当模型太大而无法在单台机器上运行时(OpenAI's GPT-3 将是一种极端情况)或当体系结构适合此任务时,模型并行化就会完成,但据我所知,这两种情况很少见。
  • 模型的并行路径(同步点)越多越长,可能越适合模型并行化
  • 重要的是在相似的时间以相似的负载启动 worker,以免在同步方法中阻碍同步过程或在异步中不获得陈旧的梯度(尽管在后一种情况下这还不够)。

服务

小型号

由于您追求的是大型模型,因此我不会深入研究小型模型的选项,只是简单提及一下。

如果您想通过网络为多个用户提供服务,您需要某种方式来扩展您的架构(通常是像 GCP 或 AWS 这样的云)。您可以使用 Kubernetes 和它的 PODs 或预先分配一些服务器来处理请求,但这种方法效率低下(少量用户和 运行ning 服务器会产生毫无意义的成本,而大量用户可能会停止基础架构并花费太长时间来处理请求)。

其他方法是使用基于无服务器方法的自动缩放。资源将根据每个请求提供,因此它具有较大的扩展能力+流量低时您无需付费。对于较小的模型,您可以看到 Azure Functions as they are on the path to improve it for ML/DL tasks, or torchlambda 用于 PyTorch(免责声明,我是作者)。

大型号

如前所述,您可以将 Kubernetes 与您的自定义代码或现成的工具一起使用。

在第一种情况下,您可以像训练一样传播模型,但只做 forward 通过。通过这种方式,即使是巨型模型也可以在网络上发布(再次,GPT-3 具有 175B 参数),但需要大量工作。

第二种,提供了两种可能性。其他值得一提的可能是(阅读各自的文档,因为它们有很多功能):

  • KubeFlow - 多个框架,基于 Kubernetes(因此自动扩展、多节点)、训练、服务等等,与下面的 MLFlow 等其他东西连接
  • AWS SageMaker - 使用 Python API 进行培训和服务,由 Amazon
  • 提供支持
  • MLFlow - 多个框架,用于实验处理和服务
  • BentoML - 多个框架,训练和服务

对于 PyTorch,您可以阅读更多内容 here, while tensorflow has a lot of serving functionality out of the box via Tensorflow EXtended (TFX)

OP评论中的问题

Are there any forms of parallelism that are better within a machine vs across machines

最好的并行性可能是在一台巨型计算机中,以尽量减少设备之间的传输。

此外,有不同的后端(至少在 PyTorch 中)可以选择(mpigloonccl),但并非所有后端都支持直接发送、接收,减少设备之间的数据等(有些可能支持 CPU 到 CPU,其他的 GPU 到 GPU)。如果设备之间没有直接 link,则必须先将它们复制到另一个设备,然后再复制到目标设备(例如,其他机器上的 GPU -> 主机上的 CPU -> 主机上的 GPU)。参见 pytorch info

数据越多,网络越大,并行计算的收益就越大。如果整个数据集可以适合单个设备,则不需要并行化。此外,还应考虑互联网传输速度、网络可靠性等因素。这些成本可能超过收益。

一般来说,如果您有大量数据(比如带有 1.000.000 图像的 ImageNet)或大样本(比如图像 2000x2000),请进行数据并行化。如果可能,在一台机器内尽量减少机器间的传输。仅在没有办法解决时才分发模型(例如,它不适合 GPU)。否则不要(在训练 MNIST 时几乎没有并行化的意义,因为整个数据集很容易放入 RAM 中,并且从中读取速度最快)。

why bother build custom ML-specific hardware such as TPUs?

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

由于 GPU 是在考虑图形的情况下创建的(因此代数转换),它可以承担一些 CPU 的职责并且可以专门化(与 CPU 相比更多的核心但更简单的核心,例如,参见 V100

现在,TPU 专为张量计算(主要是深度学习)量身定制,起源于 Google,与 GPU 相比仍处于开发阶段。这些适用于某些类型的模型(主要是卷积神经网络)并且可以在这种情况下带来加速。此外,应该使用此设备的最大批次(参见 here), best to be divisible by 128. You can compare that to NVidia's Tensor Cores technology (GPU) where you are fine with batches (or layer sizes) divisible by 16 or 8 (float16 precision and int8 respectively) for good utilization (although the more the better and depends on number of cores, exact graphic card and many other stuff, see some guidelines here)。

另一方面,TPU 支持仍然不是最好的,尽管有两个主要框架支持它(tensorflow 正式支持,而 PyTorch 带有 torch_xla 包)。

总的来说,GPU 是目前深度学习的一个很好的默认选择,TPU 用于卷积繁重的架构,虽然可能会让人头疼。此外(再次感谢@Daniel),TPU 更节能,因此在比较单浮点运算成本时应该更便宜。