首页 > 解决方案 > 深度学习的并行化策略

问题描述

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

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

训练

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

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

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

最重要的是,我读过:

但我不知道具体指的是什么,例如,是计算不同数据批次的梯度还是计算不同子图的梯度?或者也许它完全指的是别的东西?

服务

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

标签: tensorflowdeep-learningpytorchdistributed-computingmxnet

解决方案


训练

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

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

然后我们可以类似地训练它。更多细节可以参考官方教程

为了在 PyTorch 中执行异步 SGD,我们需要更多地手动实现它,因为没有类似于DistributedDataParallelfor 它的包装器。

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)

更多详情您可以参考the official PyTorch tutorial;或者,如果您使用 TensorFlow,您甚至可以使用更高级的库,例如mesh

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

回想一下,数据并行仅拆分训练数据,而模型并行仅拆分模型结构。如果我们有一个模型如此之大,以至于即使使用了任何一种并行策略,它仍然不适合内存,我们总是可以同时做这两个。

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

模型推理/服务

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

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

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

实现一个调度器来进行请求批处理并不是一项简单的任务,因此我们最好使用已经支持它的TensorFlow ServingPyTorch Serve ,而不是手动执行。


要了解有关并行和分布式学习的更多信息,您可以阅读这篇评论论文


推荐阅读