079在多个 GPU 上高效训练
在多个 GPU 上高效训练¶
如果你发现单个 GPU 训练模型太慢,或者模型的权重无法容纳在单个 GPU 的内存中,那么过渡到多 GPU 配置可能是一个可行的选择。在进行这一转变之前,请务必彻底探索所有在单个 GPU 上提高训练效率的方法(详见单 GPU 高效训练方法和工具),因为这些方法适用于任何数量的 GPU 训练。一旦你应用了这些方法并且发现它们在单个 GPU 上仍然不足,再考虑转向多个 GPU。
从单个 GPU 到多个 GPU 的过渡需要引入某种形式的并行处理,因为工作负载需要分布在资源上。可以采用多种技术来实现并行处理,例如数据并行处理(Data Parallelism)、张量并行处理(Tensor Parallelism)和流水线并行处理(Pipeline Parallelism)。需要注意的是,并没有一种通用的最佳方案,最佳设置取决于你所使用的具体硬件配置。
本指南深入介绍了各种类型的并行处理,并提供了如何组合技术以及选择合适方法的指导。有关分布式训练的逐步教程,请参阅🤗 Accelerate 文档。
虽然本指南讨论的主要概念可能适用于各种框架,但这里我们重点介绍基于 PyTorch 的实现。
在深入探讨每种技术的具体细节之前,让我们先了解一下在大型基础设施上训练大型模型时的决策过程。
可扩展性策略¶
首先估算训练模型所需的显存(vRAM)。对于托管在 🤗 Hub 上的模型,可以使用我们的模型内存计算器,该计算器可以提供准确的结果,误差范围在几个百分点内。
单节点/多 GPU 配置的并行化策略¶
当在一个节点上使用多个 GPU 训练模型时,选择合适的并行化策略对性能有显著影响。以下是你的选项:
情况 1:模型可以容纳在单个 GPU 上
如果模型可以舒适地容纳在一个 GPU 上,你有两个主要选项:
- DDP - 分布式数据并行(Distributed DataParallel)
- ZeRO 冗余优化器(ZeRO) - 根据具体情况和配置,这种方法可能更快或更慢,但值得一试。
情况 2:模型无法容纳在单个 GPU 上
如果模型太大无法容纳在一个 GPU 上,你有几种选择:
- 流水线并行(PipelineParallel,简称 PP)
- ZeRO
- 张量并行(TensorParallel,简称 TP)
如果有非常快速的节点间连接(例如 NVLINK 或 NVSwitch),上述三种策略(PP、ZeRO、TP)的性能应该相似。然而,如果没有这些高速连接,PP 的性能会优于 TP 或 ZeRO。TP 的程度也可能有所不同。最好针对你的具体设置进行实验以确定最合适的策略。
TP 几乎总是用于单个节点内,即 TP 大小 ≤ 每个节点的 GPU 数量。
情况 3:模型的最大层无法容纳在单个 GPU 上
- 如果你不使用 ZeRO,必须使用张量并行(TP),因为仅使用流水线并行(PP)不足以容纳大层。
- 如果你使用 ZeRO,还需采用单 GPU 高效训练方法和工具中的技术。
多节点/多 GPU 配置的并行化策略¶
当你有快速的节点间连接(例如 NVLINK 或 NVSwitch)时,可以考虑以下选项:
- ZeRO - 因为它几乎不需要对模型进行修改。
- 流水线并行(PP)与张量并行(TP)和数据并行(DP)的组合 - 这种方法会减少通信次数,但需要对模型进行大量修改。
当你有较慢的节点间连接且 GPU 内存较低时:
- 使用数据并行(DP)与流水线并行(PP)、张量并行(TP)和 ZeRO 的组合。
在本指南的后续部分,我们将深入探讨这些不同的并行化方法是如何工作的。
数据并行(Data Parallelism)¶
即使只有 2 个 GPU,你也可以利用 PyTorch 内置功能(如 DataParallel (DP) 和 DistributedDataParallel (DDP))提供的加速训练能力。请注意,PyTorch 文档建议在多 GPU 训练时优先使用 DistributedDataParallel (DDP) 而不是 DataParallel (DP),因为它适用于所有模型。让我们看看这两种方法的工作原理及其不同之处。
DataParallel 与 DistributedDataParallel¶
为了理解这两种方法在 GPU 间通信开销上的关键差异,我们来回顾一下每个批次的过程:
DDP:
- 在开始时,主进程从 GPU 0 将模型复制到其他 GPU。
- 然后对于每个批次:
- 每个 GPU 直接消耗其小批量数据。
- 在
backward阶段,一旦本地梯度准备好,它们会在所有进程中平均。
DP:
对于每个批次:
- GPU 0 读取一批数据,然后将小批量数据发送给每个 GPU。
- 最新的模型从 GPU 0 复制到每个 GPU。
- 执行
forward,并将每个 GPU 的输出发送到 GPU 0 以计算损失。 - 损失从 GPU 0 分发到所有 GPU,然后运行
backward。 - 每个 GPU 的梯度被发送到 GPU 0 并平均。
关键差异包括:
- DDP 每个批次只进行一次通信 - 发送梯度,而 DP 每个批次进行五次不同的数据交换。DDP 使用 torch.distributed 复制数据,而 DP 通过 Python 线程(这会引入与 GIL 相关的限制)在进程内复制数据。因此,除非 GPU 之间的连接速度较慢,否则
DistributedDataParallel(DDP) 通常比DataParallel(DP) 更快。 - 在 DP 下,GPU 0 的工作量显著大于其他 GPU,导致 GPU 利用率低下。
- DDP 支持跨多台机器的分布式训练,而 DP 不支持。
这不是 DP 和 DDP 之间差异的详尽列表,但其他细微差别超出了本指南的范围。你可以通过阅读这篇文章来更深入地了解这些方法:使用 PyTorch 在 AWS 上进行分布式数据并行训练。
接下来,我们通过一个实验来说明 DP 和 DDP 之间的差异。我们将基准测试 DP 和 DDP 之间的差异,并添加 NVLink 是否存在的上下文:
- 硬件:2 块 TITAN RTX 24GB 每块 + NVlink,2 条 NVLink(在
nvidia-smi topo -m中显示为NV2)。 - 软件:
pytorch-1.8-to-be+cuda-11.0/transformers==4.3.0.dev0。
为了禁用其中一个基准测试的 NVLink 功能,我们使用 NCCL_P2P_DISABLE=1。
以下是基准测试代码和输出:
DP