首页 > 其他分享 >大模型--训练 加速之 流水线并行Pipeline Parallelism-10

大模型--训练 加速之 流水线并行Pipeline Parallelism-10

时间:2024-11-08 11:33:09浏览次数:1  
标签:10 Pipeline 训练 Gpipe -- 模型 并行 batch GPU

目录

1. 参考

https://zhuanlan.zhihu.com/p/613196255

2. 概述

回顾ChatGPT的发展历程,我们可以总结出大语言模型(LLM)取得惊艳效果的要点(重要性从高到低排序):

  • 愿意烧钱,且接受“烧钱 != 好模型”的现实
  • 高质量的训练语料
  • 高效的分布式训练框架和充沛优质的硬件资源
  • 算法的迭代创新

几种经典的分布式并行范式,包括流水线并行(Pipeline Parallelism),数据并行(Data Parallelism)和张量并行(Tensor Parallesim)。
微软开源的分布式训练框DeepSpeed,融合了这三种并行范式,开发出3D并行的框架,实现了千亿级别模型参数的训练。

经典的流水线并行范式有Google推出的Gpipe,
微软推出的PipeDream。

两者的推出时间都在2019年左右,大体设计框架一致。主要差别为:在梯度更新上,Gpipe是同步的,PipeDream是异步的。
异步方法更进一步降低了GPU的空转时间比。
虽然PipeDream设计更精妙些,但是Gpipe因为其“够用”和浅显易懂,更受大众欢迎(torch的pp接口就基于Gpipe)。

3. 目标

当你从单卡穷人变成多卡富翁时,你做分布式训练的总体目标是什么呢?(虽然手握一张A100怎么能是穷呢)

  • 能训练更大的模型。理想状况下,模型的大小和GPU的数量成线性关系。即GPU量提升x倍,模型大小也能提升x倍。
  • 能更快地训练模型。理想状况下,训练的速度和GPU的数量成线性关系。即GPU量提升x倍,训练速度也能提升x倍。

难点:
训练更大的模型时,每块GPU里不仅要存模型参数,还要存中间结果(用来做Backward)。而更大的模型意味着需要更多的训练数据,进一步提高了中间结果的大小。加重了每块GPU的内存压力。--(内存限制)
网络通讯开销。数据在卡之间进行传输,是需要通讯时间的。不做设计的话,这个通讯时间可能会抹平多卡本身带来的训练速度提升。(带宽限制)
明确这两个训练目标后,我们来看并行范式的设计者,是如何在现有硬件限制的条件下,完成这两个目标的。

4. 模型并行

当你有一个单卡装不下的大模型时,一个直接的解决办法是,把模型隔成不同的层,每一层都放到一块GPU上,如下图:

此时,模型做一轮forward和backward的过程如下:

其中下标表示batch编号,这里只有一个batch,因此下标都是0。
每一行表示一个GPU。
每一列表示timestep。

解读:在GPU0上做完一次forward,然后将GPU0上最后一层的输入传给GPU1,继续做forward,直到四块GPU都做完forward后,再依次做backward。
等把四块GPU上的backward全部做完后,最后一个时刻我统一更新每一层的梯度。

这样做确实能训更大的模型了,但也带来了两个问题:
GPU利用度不够:

阴影部分所表示的时间段里,总有GPU在空转。

在Gpipe中,将阴影部分定义为bubble(空泡)。

假设有K块GPU,而单块GPU上做一次forward和backward的时间为: 。则


当K越大,即GPU的数量越多时,空置的比例接近1,即GPU的资源都被浪费掉了。因此这个问题肯定需要解决。

中间结果占用大量的内存:

W_t+1 = W_t - etagrad
grad = activate
error # activate激活值则为中间结果 反向传播的时候需要用, error 为实际值与真实值的差值。
在做backward计算梯度的过程中,我们需要用到每一层的中间结果z。
假设我们的模型有L层,每一层的宽度为d,则对于每块GPU,不考虑其参数本身的存储,额外的空间复杂度为:

从这个复杂度可以看出,随着模型的增大,N,L,d三者的增加可能会平滑掉K增加带来的GPU内存收益。因此,这也是需要优化的地方。

4.流水线并行

总结一下上面的:朴素的模型并行存在GPU利用度不足,中间结果消耗内存大的问题。
Gpipe提出的流水线并行,就是用来解决这两个主要问题的。

4.1 切分micro-batch

流水线并行的核心思想是:在模型并行的基础上,进一步引入数据并行的办法,即把原先的数据再划分成若干个batch,送入GPU进行训练
未划分前的数据,叫mini-batch。在mini-batch上再划分的数据,叫micro-batch

第一个下标表示GPU编号,
第二个下标表示micro-batch编号。
假设我们将mini-batch划分为M个,则流水线并行下:

bubble的时间复杂度为:

Gpipe通过实验证明,当时,
bubble产生的空转时间占比对最终训练时长影响是微小的,可以忽略不计。
将batch切好,并逐一送入GPU的过程,就像一个流水生产线一样(类似于CPU里的流水线),因此也被称为Pipeline Parallelism。

4.2 re-materialization(active checkpoint)

解决了GPU的空置问题,提升了GPU计算的整体效率。接下来,就要解决GPU的内存问题了。
前文说过,随着模型的增加,每块GPU中存储的中间结果也会越大。
Gpipe采用了一种非常简单粗暴但有效的办法:
用时间换空间,在论文里,这种方法被命名为re-materalization,后人也称其为active checkpoint。
re-materalization:也就是物料再计算的意思。
具体来说,就是几乎不存中间结果,等到backward的时候,再重新算一遍forward,图例如下:
每块GPU上,我们只保存来自上一块的最后一层输入z,其余的中间结果我们算完就废。 等到backward的时候再由保存下来的z重新进行forward来算出。

每块GPU峰值时刻的内存:
每块GPU上的输入数据大小 + 每块GPU在forward过程中的中间结果大小

每块GPU上固定需要保存它的起始输入,我们记起始输入为 (即mini-batch的大小)。

每个micro-batch是流水线形式进来的,算完一个micro-batch才算下一个。在计算一个micro-batch的过程中,我们会产生中间变量,
其大小为: (其中M为micro-batch个数)
每块GPU峰值时刻的空间复杂度为 :
与朴素模型并行中的GPU空间复杂度:比较
可以发现,由于采用了micro-batch的方法,当L变大时,流水线并行相比于朴素模型并行,对GPU内存的压力显著减小。

使用Pytorch提供的pipeline接口,其中有一个参数叫checkpoint,就是用来做这一项的:

在micro-batch的划分下,我们在计算Batch Normalization时会有影响。
Gpipe的方法是,在训练时计算和运用的是micro-batch里的均值和方差,但同时持续追踪全部mini-batch的移动平均和方差,以便在测试阶段进行使用。Layer Normalization则不受影响。

5. 实验效果

总结一下Gpipe本质上就做了两点:

  1. mini-batch 切割成micro-batch
  2. 中间激活值 用完即销毁 反向的时候再计算 只保留需要的卡间传递值

第二部分的两个目标,Gpipe真的实现了吗?如果实现不了,又是因为什么原因呢?来看下实验效果。

5.1 GPU数量 VS 模型大小

Naive表示单卡
Pipeline-N表示re-materalization + N卡
AmeobaNet-D和Trasformer-L 表示超参数的量

of Model Parameter表示模型的参数量

Total Model Parameter Memory表示模型参数所占内存大小
Peak Activation Memory表示峰值时中间结果大小

结论:
在Transformer上,Gpipe基本实现了模型大小(参数量)和GPU个数之间的线性关系。例如从32卡增到128卡时,模型的大小也从21.08B增加到82.9B,约扩4倍
对AmoebaNet而言,却没有完全实现线性增长。例如从4卡到8卡,模型大小从1.05B到1.8B,不满足2倍的关系。本质原因是AmoebaNet模型在切割时,没有办法像Transformer一样切得匀称,保证每一块GPU上的内存使用率是差不多的。因此对于AmoebaNet,当GPU个数上升时,某一块GPU可能成为木桶的短板。

5.2 GPU数量 VS 训练速度

为了验证Gpipe框架带来的收益,实验中关掉了NVlinks(GPU间快速通信的桥梁。估计是通过强迫GPU先连CPU然后再连别的GPU做到的)
关掉的意义在于说明,不靠硬件本身的高效通讯带来的收益,Gpipe一样能做的很好。

M=32表示micro-batch的数量为32
K表示GPU数量

从实验结果可知,在关掉NVlinks的情况下,Gpipe一样也能实现随着GPU数量的增加,训练速度也增加的效果。虽然这两者间不是线性的。同样,因为模型切割不均的原因,AmoebaNet的表现不如Transformer。

开启NVlinks,并寻找最佳M:

当重新开启NVlinks后,我们来看M的大小(即流水线的核心)对训练速度的影响。
当M=1的时候,如前文所说,GPU的空置率太高,因此两个模型都没有实现训练速度和GPU个数间的线性关系
当M=4时,表现明显好转。
当M=32时,表现最佳,且Transformer基本实现了训练速度和GPU个数的线性关系。

5.3 Gpipe下时间消耗分布


对每块GPU来说,约2/3的时间,是真正花在计算上的
其余1/3的时间,大部分花在re-materalization策略下的重计算上。因为采用流水线的方法,bubble的时间也被压缩到很短,可以忽略不计。

标签:10,Pipeline,训练,Gpipe,--,模型,并行,batch,GPU
From: https://www.cnblogs.com/cavalier-chen/p/18534760

相关文章

  • 20241107数据封装
    20241107数据封装数据包利用网络在不同设备之间传输时,为了可靠和准确地发送到目的地,并且高效地利用传输资源(传输设备和传输线路),事先要对数据包进行拆分和打包,在所发送的数据包上附加上目标地址,本地地址,以及一些用于纠错的字节,安全性和可靠性较高时,还要进行加密处理等等。这些操......
  • Matlab矩阵运算的硬件资源分析
    在用Matlab创建矩阵并计算矩阵乘法运算时,要注意计算机的可用内存大小(空间资源)和CPU性能(影响所用计算时间)。例如我们做以下测试:n为qubit的数目,那么一个矩阵Matrix_A=rand(2^n,2^n)可以表示一个n-qubit的密度矩阵(全实数量子态)或者一个幺正操作(实数矩阵例子)。注意:这样的矩阵Matrix_......
  • 海康私有化视频平台EasyCVR视频分析设备平台流媒体协议RTMP、HTTP-FLV、HLS的简单对比
    在当今的数字化世界中,视频流协议的选择对于确保流畅、高效的视频传输至关重要。随着互联网技术的快速发展,直播和视频点播服务已经成为人们日常生活中不可或缺的一部分。无论是安防监控、在线教育、远程会议还是娱乐直播,用户对于视频流的实时性、稳定性和兼容性都有着极高的要求。......
  • 11.8 javaweb学习 day1 入门
    网页响应流程浏览器前端服务器后端服务器数据库1.浏览器请求前端2.前端响应浏览器3.浏览器请求后端4.后端请求数据库5.数据库响应后端6.后端响应浏览器网页的组成1.网页的文字,图片,音频,视频,超链接什么的,本质是前端代码2.前端代码通过浏览器的转化......
  • linux新增物理卷,扩容逻辑分区,出现WARNING: xfs signature detected on /dev/vdb at of
    linux新增物理卷出现WARNING:xfssignaturedetectedon/dev/vdbatoffset0.Wipeit?[y/n]:标识这个/dev/vdb磁盘已经从0位置被标记为xfs类型的文件系统报错解释:这条信息表示在设备/dev/vdb上检测到了XFS文件系统的签名。通常情况下,这可能意味着分区/dev/vdb已被......
  • helm upgrade
    要更新Helm中的单个依赖Chart的版本,你可以按照以下步骤操作:1.**修改`Chart.yaml`或`requirements.yaml`文件**:在你的主Chart中,找到`Chart.yaml`或`requirements.yaml`文件(Helm3使用`Chart.yaml`,Helm2使用`requirements.yaml`),并修改其中依赖的版本号。......
  • Kubernetes 中实现 MySQL 的读写分离
    Kubernetes中实现MySQL的读写分离在Kubernetes中实现MySQL的读写分离,可以通过主从复制架构来实现。在这种架构中,MySQL主节点(Master)负责处理所有写操作,而MySQL从节点(Slave)负责处理所有读操作。下面是一个详细的步骤指南:步骤1:创建Kubernetes集群确保你有一个运行良......
  • 利用FreeSql.Generator自动根据数据库表动态生成实体类
    安装dotnettoolinstall-gFreeSql.Generator示例FreeSql.Generator-Razor1-NameOptions0,0,0,1-NameSpaceLinCms.Core.Entities-DB"MySql,DataSource=127.0.0.1;Port=3306;UserID=root;Password=123456;InitialCatalog=lincms;Charset=utf8;SslMode=none;M......
  • 使用 ref 引用值
    当你希望组件“记住”某些信息,但又不想让这些信息触发新的渲染时,你可以使用ref。constref=useRef(0);useRef返回一个这样的对象:{current:0//你向useRef传入的值}与state一样,ref在重新渲染之间由React保留。但是,设置state会重新渲染组件,而更改ref......
  • 【前端】浅谈SOLID原则在前端的使用
    原创宁奇舞精选本文作者系360奇舞团前端开发工程师简介SOLID原则是由RobertC.Martin在2000年提出的一套软件开发准则,最初用于面向对象编程(OOP),旨在解决软件开发中的复杂性和维护问题。随着时间推移,它不仅在传统OOP语言中广泛应用,也被引入到JavaScript和TypeS......