分布式训练

部署运行你感兴趣的模型镜像


分布式训练

数据并行(DP & DDP)

DataParallel

image-20240327152041750

DP 是较简单的一种数据并行方式,直接将模型复制到多个 GPU 上并行计算,每个 GPU 计算 batch 中的一部分数据,各自完成前向和反向后,将梯度汇总到主 GPU 上。其基本流程:

  1. 加载模型、数据至内存;
  2. 创建 DP 模型;
  3. DP 模型的 forward 过程:
    1. 一个 batch 的数据均分到不同 device 上;
    2. 为每个 device 复制一份模型;
    3. 至此,每个 device 上有模型和一份数据,并行进行前向传播;
    4. 收集各个 device 上的输出;
  4. 每个 device 上的模型反向传播后,收集梯度到主 device 上,更新主 device 上的模型,将模型广播到其他 device 上;
  5. 3-4 循环。

在 DP 中,只有一个主进程,主进程下有多个线程,每个线程管理一个 device 的训练。因此,DP 中内存中只存在一份数据,各个线程间是共享这份数据的。DP 和 Parameter Server 的方式很像。

DistributedDataParallel

  1. 准备阶段

    • 环境初始化:在各张卡上初始化进程并建立进程间通信,对应代码:init_process_group

    • 模型广播:将模型 parameter、buffer 广播到各节点,对应代码:model = DDP(model).to(local_rank)

    • 创建管理器 reducer,给每个参数注册梯度平均 hook。

  2. 准备数据

    • 加载数据集,创建适用于分布式场景的数据采样器,以防不同节点使用的数据重叠。
  3. 训练阶段

    • 前向传播

      • 同步各进程状态(parameter 和 buffer);
      • 当 DDP 参数 find_unused_parametertrue 时,其会在 forward 结束时,启动一个回溯,标记未用到的参数,提前将这些设置为 ready
    • 计算梯度

      • reducer 外面:各进程各自开始反向计算梯度;
      • reducer 外面:当某个参数的梯度计算好了,其之前注册的 grad hook 就会触发,在 reducer 里把这个参数的状态标记为 ready
      • reducer 里面:当某个 bucket 的所有参数都是 ready 时,reducer 开始对这个 bucket 的所有参数开始一个异步的 all-reduce 梯度平均操作;
      • reducer 里面:当所有 bucket 的梯度平均都结束后,reducer 把得到的平均梯度正式写入到 parameter.grad 里。
    • 优化器应用梯度更新参数。

DDP 与 DP 的区别

DPDDP
多线程
1. 受到 GIL 的限制
2. 单机工作
多进程
1. 多机多卡
迭代更新传输数据包括 梯度和参数
1. 全程维护 一个 optimizer
2 梯度 汇总到主 GPU, 主 GPU 进行参数更新
3. 主 GPU Broadcast 参数 给其他的 GPU
传输数据包括 梯度
1. 每个进程具有 自己的 optimizer
2. 各进程自己计算梯度
3. Ring All-Reduce 将 梯度 汇总平均
4. 各进程用梯度来独立的更新参数
通信效率通信成本随着 GPU 数量线性增长Ring All-Reduce 通信成本恒定,与 GPU 数量无关

DDP 中由于各进程中的模型,初始参数一致 (初始时刻进行一次 broadcast),而每次用于更新参数的梯度也一致,因此,各进程的模型参数始终保持一致。

TP (Tensor Parallelism)

每个张量都被 水平 分成多个块,因此张量的每个分片都位于其指定的 GPU 上,而不是让整个张量驻留在单个 GPU 上。在处理过程中,每个分片在不同的 GPU 上分别并行处理,结果在步骤结束时同步。

image-20240325202756320

MLP 的并行化:权重 A 矩阵竖着切 B 矩阵横着切 最后 MERGE

  1. 对于输入 X X X,它的行数是批量大小 B B B 乘以序列长度 L L L,列数是隐藏层的宽度即 D D D
    其隐藏层的模块里面其实就是两个全连接层
  2. 假定第一个隐藏层的权重是 A A A ( [ D , D ′ ] [D, D'] [D,D] D ′ D' D 一般是 D D D 的 4 倍),则先做矩阵乘法,然后再接一个激活函数比如 GELU (GELU 类似把 ReLU 的拐点顺滑下)
  3. 假定第二个隐藏层的权重是 B B B ( [ D ′ , D ] [D', D] [D,D]),最终 σ ( X ⋅ A ) B = Y \sigma(X \cdot A) B= Y σ(XA)B=Y
  4. 如果是输入数据比较大,则优先选择做数据并行,即对输入做拆分
    如果是模型本身比较大,则优先选择做模型并行,即对矩阵做拆分:
    1. A A A 按行拆 ( A = [ A 1 A 2 ] A =\left[\begin{matrix} A_1 \\ A_2\end{matrix}\right] A=[A1A2]) , 则相应的 X X X 按列拆,导致的结果是两个 GPU 之间需要通讯。
    2. A A A 按列拆 ( A = [ A 1 , A 2 ] A =\left[\begin{matrix} A_1 , A_2\end{matrix}\right] A=[A1,A2]) ,则相应的 X X X 按行拆,或者在两个 GPU 上都得有一份才行,此时则无需任何额外的通信。
  5. 使用第 2 种拆分方式,通过执行矩阵乘法得到 X A 1 XA_1 XA1 X A n XA_n XAn, 它们可以独立输入 GeLU: [ Y 1 , Y 2 ] = [ GeLU ⁡ ( X A 1 ) , GeLU ⁡ ( X A 2 ) ] \left[Y_{1}, Y_{2}\right]=\left[\operatorname{GeLU}\left(X A_{1}\right), \operatorname{GeLU}\left(X A_{2}\right)\right] [Y1,Y2]=[GeLU(XA1),GeLU(XA2)], 然后再得到 n n n 个输出向量 Y 1 , Y 2 , ⋯ , Y n Y_1, Y_2, ⋯, Y_n Y1,Y2,,Yn
  6. 同理, B B B 按行拆分 ( B = [ B 1 B 2 ] B =\left[\begin{matrix} B_1 \\ B_2\end{matrix}\right] B=[B1B2]) ,通过执行矩阵乘法得到 Y 1 B 1 Y_1B_1 Y1B1 Y n B n Y_nB_n YnBn, 即 n n n 个输出向量 Z 1 , Z 2 , ⋯ , Z n Z_1, Z_2, ⋯, Z_n Z1,Z2,,Zn, 最终 merge 得到完整的 Z Z Z
  7. 通过上述操作,我们可以更新任意深度的 MLP,只需在每个 拆列-拆行 序列之后同步 GPU

Self-Attention 的并行化:各个头各自计算

  1. 对于输入 X X X,它的行数是批量大小 B B B 乘以序列长度 L L L,列数是隐藏层的宽度即 D D D,在自注意力机制中,输入 X X X 会被复制成三份,分别对应为 X X X Q Q Q K K K V V V 向量矩阵。
  2. 对于多头注意力,头的维度为 D / h D/h D/h, 假定 h = 2 h=2 h=2,之后针对每个头中 X X X 输入矩阵中各个单词的 Q Q Q 向量,会与各自上下文的 K K K 向量做缩放点积然后做 softmax 得到一个注意力分数或权重,之后再与 V V V 相乘,得到一个 [ L , D / h ] [L,D/h] [L,D/h] 的输出
  3. 每个头的计算是各自独立并行的,那意味着一个头可以放在 GPU 0 上,另一个头可以放在 GPU 1 上,最后 all reduce 每个头的结果

由于前向和后向传播中每层都有两个 all reduce,因此 TP 需要设备间有非常快速的互联。因此,除非你有一个非常快的网络,否则不建议跨多个节点进行 TP。实际上,如果节点有 4 个 GPU,则最高 TP 度设为 4 比较好。如果需要 TP 度为 8,则需要使用至少有 8 个 GPU 的节点

PP (Pipeline Parallelism)

模型在多个 GPU 上 垂直 (即按层) 拆分,因此只有一个或多个模型层放置在单个 GPU 上。每个 GPU 并行处理流水线的不同阶段,并处理 batch 的一部分数据

image-20240326092738269
  1. 把网络分成 4 块,每一块放在一个 GPU 上,不同的颜色表示不同的 GPU),于是就有了 F0、F1、F2、F3 这 4 个管级的前向路径,然后是 B3、B2、B1、B0 的逆序后向路径。

  2. b 部分表示朴素 PP 方案: 在每个时间点只有一台设备在处理计算逻辑,完成计算后将结果发送给下一台设备。

  3. c 部分是 PP 方法:

    1. PP 引入了一个新的超参数来调整,称为 块 (chunks)。它定义了通过同一管级按顺序发送多少数据块。图中 chunks = 4 \text{chunks} = 4 chunks=4.

    2. GPU 0 在 chunk 0、1、2 和 3 (F0,0、F0,1、F0,2、F0,3) 上执行相同的前向路径,然后等待,等其他 GPU 完成工作后,GPU 0 会再次开始工作,为块 3、2、1 和 0 (B0,3、B0,2、B0,1、B0,0) 执行后向路径。

      请注意,从概念上讲,这与梯度累积 (gradient accumulation steps,GAS) 的意思相同。PyTorch 叫它 ,而 DeepSpeed 叫它 GAS

    3. 因为 块 (chunks),PP 引入了 micro-batches (MBS) 的概念。

      DP 将全局 batch size 拆分为小 batch size,因此如果 DP 度为 4,则全局 batch size 1024 将拆分为 4 个小 batch size,每个小 batch size 为 256 (1024/4)。而如果 块 (或 GAS) 的数量为 32,我们最终得到的 micro batch size 为 8 (256/32)。每个管级一次处理一个 micro batch。
      计算 DP + PP 设置的全局批量大小的公式为: mbs ∗ chunks ∗ dp_degree  ( 8 ∗ 32 ∗ 4 = 1024 ) \text{mbs}*\text{chunks}*\text{dp\_degree }(8*32*4=1024) mbschunksdp_degree (8324=1024)

    4. 将 mini-batch 进一步划分成更小的 micro-batch,同时利用 pipipline 方案,每次处理一个 micro-batch 的数据,得到结果后,将该 micro-batch 的结果发送给下游设备,同时开始处理后一个 micro-batch 的数据,通过这套方案减小设备中的 Bubble(设备空闲的时间称为 Bubble)

您可能感兴趣的与本文相关的镜像

TensorFlow-v2.9

TensorFlow-v2.9

TensorFlow

TensorFlow 是由Google Brain 团队开发的开源机器学习框架,广泛应用于深度学习研究和生产环境。 它提供了一个灵活的平台,用于构建和训练各种机器学习模型

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值