深度学习的并行化策略
Posted
技术标签:
【中文标题】深度学习的并行化策略【英文标题】:Parallelization strategies for deep learning 【发布时间】:2020-10-26 19:21:42 【问题描述】:哪些并行化策略和形式对于训练和服务神经网络是可行的?:
内部一台机器跨个内核(例如 GPU / TPU / CPU) 跨网络或机架上的机器我也在寻找证据证明它们也可以用于例如TensorFlow、PyTorch 或 MXNet。
培训
据我所知,在大型数据集上训练大型神经网络时,至少可以:
-
不同的核心 或 机器在图表的不同部分(“图分裂”)。例如。通过图本身的反向传播可以并行化,例如通过在不同的机器上托管不同的层,因为(我认为?)autodiff 图始终是一个DAG。
不同的核心 或 机器对不同的样本数据(“数据 分裂”)。在 SGD 中,跨批次或样本的梯度计算也可以并行化(例如,梯度可以在不同批次上独立计算后组合起来)。我相信这也称为梯度累积(?)。
每种策略何时更适合哪种类型的问题或神经网络?现代图书馆支持哪些模式?是否可以结合所有四种 (2x2) 策略?
除此之外,我还阅读了以下内容:
异步训练 同步训练但我不知道具体指的是什么,例如是在不同数据批次上计算梯度,还是在不同子图上计算梯度?或者它可能指的是完全不同的东西?
服务
如果网络很大,预测/推理也可能很慢,并且模型在服务时可能不适合内存中的单台机器。是否有任何已知的多核和多节点预测解决方案可以处理此类模型?
【问题讨论】:
我认为这会在 *** 上得到更好的响应。 感谢@TimMak 很高兴将问题移至 ***。关于如何做到这一点的任何建议? 我不是这个确切主题的专家,但我过去曾将Horovod
与 AWS SageMaker 一起使用,这是使用 TS/Keras 进行分布式计算时推荐的方法。这是链接horovod.readthedocs.io/en/stable/summary_include.html
我对这个话题也不太了解,但是对异步训练有一些指导。 ***.com/questions/27435284/…realpython.com/async-io-python
加密货币。查看加密软件,例如 xmr-stak github.com/fireice-uk/xmr-stak,您会在 CPU 和 GPU 级别找到完美的并行化示例。当我进入这个领域时,我在 72 核 Xeon Phi 7295 上做了一些工作,这对我有帮助。
【参考方案1】:
培训
一般来说,模型训练的并行化策略有两种:数据并行化和模型并行化。
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和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
;或者,如果您使用 TensorFlow,您甚至可以使用更高级的库,例如 mesh。
3。混合:数据和模型并行
回想一下,数据并行仅拆分训练数据,而模型并行仅拆分模型结构。如果我们有一个模型如此之大,以至于即使在使用了任何一种并行策略之后它仍然无法放入内存中,我们总是可以同时使用这两种策略。
在实践中,与模型并行相比,大多数人更喜欢数据并行,因为前者与模型架构的解耦(事实上,独立)比后者多。也就是说,通过使用数据并行,他们可以随心所欲地改变模型架构,而不用担心模型的哪个部分应该并行化。
模型推理/服务
并行化模型服务比并行化模型训练更容易,因为模型参数已经固定,每个请求都可以独立处理。与扩展常规 Python Web 服务类似,我们可以通过在单台机器中生成更多进程(解决方法Python's GIL)甚至生成更多机器实例来扩展模型服务。
但是,当我们使用 GPU 为模型提供服务时,我们需要做更多的工作来扩展它。由于 GPU 与 CPU 处理并发的方式不同,为了最大限度地提高性能,我们需要进行推理请求批处理。这个想法是当一个请求到来时,我们不是立即处理它,而是等待一些超时时间来等待其他请求的到来。当超时时间到时,即使请求数只有一个,我们也会将它们全部批量处理以在 GPU 上处理。
为了最小化平均请求延迟,我们需要找到最佳的超时时间。为了找到它,我们需要观察在最小化超时持续时间和最大化批量大小之间存在权衡。如果超时时间太短,batch size 会很小,因此 GPU 会被充分利用。但是如果超时时间太长,那么早来的请求会等待太久才能得到处理。因此,最佳超时持续时间取决于模型复杂度(因此,推理持续时间)和每秒接收的平均请求数。
实现一个调度器来做请求批处理不是一件小事,所以与其手动做,我们最好使用已经支持它的TensorFlow Serving或PyTorch Serve。
要了解有关并行和分布式学习的更多信息,您可以阅读this review paper。
【讨论】:
【参考方案2】:由于这个问题相当广泛,我将尝试阐明一些不同的观点,并触及与 @Daniel's深度解答。
培训
数据并行化与模型并行化
正如@Daniel 所提到的,数据并行性被更频繁地使用并且更容易正确执行。模型并行的主要警告是需要等待部分神经网络和它们之间的同步。
假设您有一个简单的前馈 5
层神经网络,分布在 5
不同的 GPU 上,每一层用于一个设备。在这种情况下,在每次前向传递期间,每个设备都必须等待来自前一层的计算。在这种简单的情况下,在设备之间复制数据和同步将花费更长的时间,而且不会带来任何好处。
另一方面,也有更适合模型并行化的模型,例如Inception networks,见下图:
在这里,您可以看到 4
与前一层的独立路径,这些路径可以并行运行,并且只有 2
同步点(Filter concatenation
和 Previous Layer
)。
问题
例如通过图本身的反向传播可以并行化,例如 通过在不同的机器上托管不同的层,因为(我 认为?)autodiff 图始终是一个 DAG。
这并不容易。梯度是根据损失值计算的(通常),你需要知道更深层的梯度来计算更浅层的梯度。如上所述,如果您有独立的路径,它会更容易并且可能会有所帮助,但在单个设备上会更容易。
我相信这也叫梯度累积(?)
不,它实际上是跨多个设备的减少。您可以在PyTorch tutorial 中看到其中的一些内容。梯度累积是当您运行前向传递(在单个或多个设备上)N
次和反向传播(梯度保留在图中并在每次传递期间添加值)时,优化器只需一步即可更改神经网络网络的权重(并清除梯度)。在这种情况下,损失通常除以没有优化器的步数。这用于更可靠的梯度估计,通常在您无法使用大批量时。
跨设备减少如下所示:
这是数据并行化的全部减少,每个设备计算发送到所有其他设备并在那里反向传播的值。
每种策略何时更适合哪种类型的问题或神经网络 网络?
如上所述,如果您有足够的数据并且样本很大,数据并行几乎总是可以的(最多 8k
个样本或更多样本可以一次完成,而无需 非常 大的努力)。
现代库支持哪些模式?
tensorflow
和 pytorch
都支持,大多数现代和维护的库都以一种或另一种方式实现了这些功能
可以结合所有四种 (2x2) 策略
是的,您可以在机器之间和机器内并行化模型和数据。
同步与异步
异步
由@Daniel 简要描述,但值得一提的是更新并非完全独立。这没有什么意义,因为我们基本上会根据批次训练 N
不同的模型。
相反,有一个全局参数空间,其中每个副本都应该异步共享计算的更新(因此前向传递、后向、使用优化器计算更新并将此更新共享给全局参数)。
这种方法有一个问题:不能保证当一个工人向前计算时,另一个工人更新了参数,所以更新是相对于旧的参数集计算的,这被称为陈旧的渐变。因此,收敛可能会受到影响。
其他方法是计算每个工作人员的N
步骤和更新,然后同步它们,尽管它不经常使用。
这部分基于伟大的blogpost,如果您有兴趣一定要阅读它(还有更多关于陈旧和一些解决方案的信息)。
同步
前面主要描述了,有不同的方法,但 PyTorch 从网络收集输出并在它们上反向传播 (torch.nn.parallel.DistributedDataParallel
)[https://pytorch.org/docs/stable/nn.html#torch.nn.parallel.DistributedDataParallel ]。顺便提一句。你应该只这样做(不是torch.nn.DataParallel
),因为它克服了 Python 的 GIL 问题。
要点
几乎总是在加速时使用数据并行化,因为您“仅”必须在每个设备上复制神经网络(通过网络或在单台机器内),在前向传递期间在每个设备上运行部分批处理,连接在一台设备上将它们分成单个批次(同步)并在所述设备上进行反向传播。 有多种方法可以进行数据并行化,@Daniel 已经介绍过 当模型太大而无法在单台机器上容纳(OpenAI's GPT-3 是一种极端情况)或架构适合此任务时,会进行模型并行化,但 AFAIK 很少会出现这两种情况。 模型具有的并行路径(同步点)越多越长,它可能越适合模型并行化 重要的是要在相似的时间以相似的负载启动工作器,以免在同步方法中进行同步过程或在异步中避免过时的梯度(尽管在后一种情况下这还不够)。服务
小型号
由于您追求的是大型模型,因此我不会深入研究小型模型的选择,只是简单提一下。
如果您想通过网络为多个用户提供服务,您需要某种方式来扩展您的架构(通常是像 GCP 或 AWS 这样的云)。您可以使用Kubernetes 和它的 POD 来做到这一点,或者预先分配一些服务器来处理请求,但这种方法效率低下(少量用户和运行服务器会产生毫无意义的成本,而大量用户可能会停止基础设施和处理请求的时间太长)。
其他方法是使用基于无服务器方法的自动缩放。将根据每个请求提供资源,因此它具有很大的扩展能力 + 流量低时您无需付费。你可以看到Azure Functions,因为他们正在改进它以用于 ML/DL 任务,或者torchlambda
用于 PyTorch(免责声明,我是作者)用于较小的模型。
大型模型
如前所述,您可以将 Kubernetes 与您的自定义代码或准备使用的工具一起使用。
在第一种情况下,您可以像训练一样传播模型,但只通过forward
。通过这种方式,即使是巨大的模型也可以放在网络上(再次,GPT-3 具有 175B 参数),但需要大量工作。
在第二种情况下,@Daniel 提供了两种可能性。其他值得一提的可能是(阅读各自的文档,因为它们有很多功能):
KubeFlow - 多个框架,基于 Kubernetes(所以自动扩展,多节点),训练,服务等等,与下面的 MLFlow 等其他东西连接 AWS SageMaker - 使用 Python API 进行训练和提供服务,由 Amazon 提供支持 MLFlow - 多个框架,用于实验处理和服务 BentoML - 多个框架、训练和服务对于 PyTorch,您可以阅读更多 here,而 tensorflow 通过 Tensorflow EXtended (TFX) 提供许多开箱即用的服务功能。
OP 评论中的问题
是否有任何形式的并行性比机器内部更好? 跨机器
最好的并行性可能是在一台巨型计算机内,以最大程度地减少设备之间的传输。
此外,有不同的后端(至少在 PyTorch 中)可供选择(mpi
、gloo
、nccl
),并非所有后端都支持设备之间直接发送、接收、减少等数据(有些可能支持 CPU 到 CPU,有些可能支持 GPU 到 GPU)。如果设备之间没有直接链接,则必须首先将它们复制到另一个设备并再次复制到目标设备(例如,其他机器上的 GPU -> 主机上的 CPU -> 主机上的 GPU)。见pytorch info。
数据越多,网络越大,并行计算应该越有利可图。如果整个数据集可以放在单个设备上,则不需要并行化。此外,还应考虑互联网传输速度、网络可靠性等因素。这些成本可能超过收益。
一般来说,如果您有大量数据(例如带有 1.000.000
图像的 ImageNet)或大样本(例如图像 2000x2000
),请选择数据并行化。如果可能,在一台机器内尽量减少机器间的传输。仅在无法绕过模型的情况下分发模型(例如,它不适合 GPU)。否则不要(在训练 MNIST 时并行化几乎没有意义,因为整个数据集很容易放入 RAM 并且读取速度最快)。
为什么要构建自定义的 ML 专用硬件,例如 TPU?
CPU 不是最适合高度并行计算(例如矩阵乘法)+ CPU 可能会被许多其他任务(例如数据加载)占用,因此使用 GPU 是有意义的。
由于创建 GPU 时考虑了图形(因此是代数转换),它可以承担一些 CPU 职责并且可以专门化(与 CPU 相比,内核更多,但更简单,例如,请参阅 V100)。
现在,TPU 是专门为张量计算(主要是深度学习)量身定制的,起源于 Google,与 GPU 相比仍然在制品。这些适用于某些类型的模型(主要是卷积神经网络),并且在这种情况下可以带来加速。此外,应该使用此设备的最大批次(请参阅here),最好能被128
整除。您可以将其与 NVidia 的 Tensor Cores 技术 (GPU) 进行比较,在该技术中,批次(或层大小)可被 16
或 8
(分别为 float16
精度和 int8
)整除,以获得良好的利用率(尽管更多更好,取决于内核数量、确切的显卡和许多其他东西,请参阅一些指南here)。
另一方面,TPU 支持仍然不是最好的,尽管有两个主要框架支持它(tensorflow
官方支持,而 PyTorch 支持 torch_xla
包)。
一般来说,GPU 是目前深度学习中一个不错的默认选择,TPU 用于卷积密集型架构,尽管可能会让人有些头疼。此外(再次感谢@Daniel),TPU 更节能,因此在比较单浮点运算成本时应该更便宜。
【讨论】:
谢谢希蒙! GPU 内(或 TPU 内)计算呢?是什么让它们非常适合什么类型的并行性?是否有任何形式的并行性比跨 台机器(例如 GPU/TPU)和 跨 台机器更好? (网络中的节点)。或者您在这里所说的一切(“同步与异步”、“数据与模型并行性”等)在这两种情况下都适合吗? (如果是这样,为什么还要构建自定义的 ML 专用硬件,例如 TPU?) @Josh 这些技术适用于机器内部和机器之间的并行性。然而,正如 Szymon 提到的,最好先进行垂直扩展(在机器内)而不是水平扩展(跨机器),因为跨机器通信的成本很高。例如,最好有一个实例有 4 个 GPU,而不是两个实例每个有 2 个 GPU。至于为什么要构建 TPU,在性能/功率和性能/价格方面,它们都优于 GPU。 @Josh minibatch 梯度下降不是一个不同的概念。您可以使用梯度累积进行小批量梯度下降。传统上(没有梯度累积),你要做的是:在小批量上运行前向传递,计算损失,反向传播以获得梯度,通过优化器应用更新(例如普通梯度下降)。使用梯度累积:在 minibatch 上运行前向传递,计算损失,反向传播(重复这三个步骤 N 次),来自多个反向传播的平均梯度(除以 N),然后应用优化。现在清楚了吗?答案更新后 TPU/GPU 是否清晰? @Josh 参数服务器——不仅如此。正如您链接到的评论所示,每个工作人员都必须将其数据包发送到服务器(例如1GB
),服务器以某种方式合并更新并更新所有工作人员。所以在这种情况下,只有2
1GB
转移每个工人。在没有参数服务器的 all-reduce 中,每个工作人员需要 8
传输(每个工作人员向其他工作人员发送数据,假设总共有 9
个工作人员),并且每个工作人员都应该从其他工作人员那里接收数据(再次8
)。这将是16
1GB
每个工人的转移,因此更加浪费。是的,如果每个工作人员在本地需要相同的数据,这有助于提高带宽。
@Josh DB - 我认为在数据库中存储参数没有意义(假设是神经网络),但如果我错了,有人纠正我。这些被创建为高度优化的有向图,通常是不规则的,不需要太多描述。 pytorch
使用 pickle
并将模型加载到 RAM,tensorflow
使用 protobuf
(可用于通过网络进行快速传输),keras
使用 HDF5
,这又是一种文件格式。跨度>
以上是关于深度学习的并行化策略的主要内容,如果未能解决你的问题,请参考以下文章