| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 人工智能 -> 大模型系统和应用——高效训练&模型压缩 -> 正文阅读 |
|
[人工智能]大模型系统和应用——高效训练&模型压缩 |
引言最近在公众号中了解到了刘知远团队退出的视频课程《大模型交叉研讨课》,看了目录觉得不错,因此拜读一下。 目录:
背景介绍预训练语言模型以每年十倍的速度增大,越大的模型往往表现出更好的性能; 我们希望让训练过程变得更加简单,训练变得更高效,并且训练更加廉价。 首先我们要分析GPU内存;其次理解在多张显卡之间的合作模式是怎样的。
如果我们想把模型的向量加法或矩阵乘法放到GPU中计算的话,我们需要把这些数据从我们的CPU上拷贝到GPU上( 我们来看下显卡中有哪些显存的组成。 为了加速模型的前向传播,我们需要把模型所有的参数都放到显卡中。
第四部分,在显存中占大头的一部分,就是我们的优化器,比如Adam,我们需要保存模型的梯度,和相关的历史信息( m t , v t m_t,v_t mt?,vt?)。它们的参数量是和梯度等数量级的。 这四部分是我们预训练模型在显卡中主要的四个组成部分。 我们现在知道显存都去了哪里,下面来看一下多GPU之间的合作模式是怎样的。 我们下面看几种模型训练的优化方式。 数据并行
我们的参数服务器可以在0号显卡上,我们从0号显卡把模型的参数复制到1,2,3号显卡。这就像一个广播过程;而从1,2,3号显卡上对模型的梯度进行聚合(或规约),我们把规约的结果放到服务器0号显卡上。
第二个多张显卡的通信算子,规约(Reduce)。 第三个操作算法是All Reduce。比规约多了一个All。什么意思,就是在规约的基础上,把规约得到的结果告诉所有的显卡(All)。 第四个合作方式是Reduce Scatter。它和All Reduce的相同之处在于,都会把规约得到的结果发送给所有的显卡。不同之处在于,Reduce Scatter最后每张显卡上只得到了一部分的规约结果。比如0号显卡就会得到我们
可以看到数据并行有两个核心点。一,我们通过把数据分成很多份,让每张显卡计算得到各自梯度之后,为了得到所有数据的知识,我们需要把这些梯度进行一个规约操作。二,通过使用参数服务器,让规约后的梯度去更新参数服务器上的参数。然后通过广播的操作,让每张显卡上同步得到更新之后的参数。 而分布式参数并行对此进行了优化,舍弃了专门的参数服务器,让每张显卡各自去完成参数的更新,保证它们参数更新之后的结果一致。 具体来说,初始时,每张显卡上都有一个相同的模型参数,得到了一部分数据。通过前向传播&反向传播得到各自的梯度信息,然后对梯度信息进行一个规约。为了让每张显卡都得到相同的梯度信息,使用All Reduce,它会把规约结果告诉所有的显卡。这样,我们每一张显卡上都能得到完整的规约之后的梯度,每张显卡都有一样的参数,就可以分别通过模型的优化器进行更新。每轮更新之后,既然参数一样,梯度一样,优化器之前的历史信息一样,那么更新之后,各张显卡上的参数也会保持一致。
模型并行既然一张显卡上无法存放模型的所有参数,那么我们就想办法把一个模型分成很多个小的部分。 这里的 3 × 2 3 \times 2 3×2的矩阵就是线性层中的参数 W W W,向量就是线性层的输入。我们可以通过矩阵乘法的性质,把模型的参数横向切成很多份(n),最后得到线性层的结果就是很多个这样小的矩阵乘上线性层的输入,最后把结果进行拼接。 通过这样的方式,我们线性层的参数就可以划分到多张显卡上。同时我们需要保证多张显卡上模型的输入是一样的。那么我们就不能使用数据并行的方式对数据进行划分。
这样,每张显卡上只需要保存原来的N分之一的模型参数,N是显卡数量。由于只保留了这么一小部分参数,梯度也只需要保留这么多,同时优化器也只需要保持同样级别的参数量。但模型计算的中间结果没有减少,这也是该方法的一个弊端。当batch size很大的时候,仍然会出现显存溢出的问题。 下面我们来介绍另一种方法。 ZERO
我们可以发现每张显卡用的是同样的一批数据,和同样的一批梯度去进行参数更新。那么它们各自去进行参数优化,是不是就带来了计算上的重复和冗余。 为了消除这样的冗余,那么本小节介绍的方法是每张显卡只获得一部分的梯度,然后只更新一部分参数。这样多张显卡通过合作的方式来更新模型的完整参数。 ZeRO-Stage 1 具体来说,由于它也是基于数据并行的架构,因此它每张显卡上保存了完整的模型参数。有一部分数据,通过前向传播&反向传播得到各自的梯度。之后在规约的时候,不是使用All Reduce的方式,而是使用Reduce Scatter让每张显卡得到一部分reduce的结果。这样让每张显卡上得到的部分梯度去更新对应的部分模型参数,最后通过收集的操作All Gather将每张显卡分工合作之后的结果告诉所有的显卡。这样,每张显卡上得到了完全一样的参数和一致的结果。 ZeRO-Stage 2 在第二个阶段中,进行了一个优化。在第一阶段中,需要在反向传播得到所有梯度之后,对梯度进行Reduce Scatter,然后让每张显卡上各得到一部分规约后的梯度 ZeRO-Stage 3
然后我们比较一下这三个阶段的显存占比:
通过这三部分的优化,显卡上的四大组成部分:参数、梯度、优化器和中间结果都得到了划分,每张显卡只需要保持自己的那部分参数。 Pipeline并行最后我们来介绍流水线的并行方法。 下面我们介绍一些技术的优化细节。 技术细节混合精度比如C语言中有float类型、double类型和long double类型。数值表示范围依次增大。 比如double类型比float类型有更大的表示范围和更高的有效位精度,但是double类型的计算会更慢。 那我们能否从FP32转到FP16得到运行速度上的提升呢?其实会面临一个问题,在参数更新的时候,一般学习率是比较小的:1e-5、1e-3等。而FP16能表示的最小值,是1e-5数量级的数,假如我们的梯度乘上学习率低于FP16的表示范围,那么参数更新量就会产生丢失(下溢)。 那么既然FP32能达到出更高的表示范围,我们可以把FP16的梯度乘上学习率得到的参数更新量表示为FP32,但模型的参数是更低精度的FP16。那我们无法直接把参数更新量加到模型参数上,此时需要在优化器上额外保留单精度(FP32)的一个参数。
而在混合精度训练中,为了加速模型的前向传播&反向传播,模型中会使用半精度(FP16)的参数,和半精度的梯度,把梯度传到优化器里进行优化器的更新。同时把优化器的更新量保存为FP32类型,把这个FP32类型通过优化器里临时创建的FP32参数进行累积,之后转回到FP16的参数来与模型进行计算。 Offloading以Adam为例,优化器的参数量会是模型参数量两倍的关系,显然它是一个显存占用的大头。我们能否把它从显卡中移除呢?
Overlapping
在模型前向传播过程中,我们需要把Layer1的参数通过Gather操作,然后对Layer2的参数进行优化。在获得完Layer1参数之后,在Layer1前向传播计算过程中,异步地把Layer2参数的获得进行提前。在Layer1前向传播计算完之后,Layer2的参数也已经获得,那么就可以马上进行Layer2前向传播计算。 CheckpointingCheckpointing就是检查点,就像单机游戏中的存档。 为了支持模型的反向传播,我们需要把模型计算的所有中间结果保持在显卡中,我们是否可以通过存档的方式进行优化。 即我们不把所有结果都保持到显卡中,而只保持一定的存档点。
BMTrain——使用介绍本小节介绍BMTrain性能上的提升。
下面介绍大规模预训练模型压缩的一些技术,主要介绍他们的工具包BMCook。 模型压缩背景就是大模型的规模增长非常快。 接下来介绍模型压缩的一些技术,目的是希望把大规模的模型压缩成更小规模。 知识蒸馏什么是知识
第一篇关于预训练模型的知识蒸馏工作称为PKD,它是面向BERT做的知识蒸馏。 它针对传统的知识蒸馏进行改进,让student模型可以从teacher模型中间层进行学习。 还有一个非常有代表性的工作是,TinyBERT。它进一步地推广了能学习的信号。从Teacher模型中找到了更多的可用于知识蒸馏的中间表示。比如输入的嵌入向量以及Attention矩阵。 模型剪枝
剪枝分为结构化剪枝和非结构化剪枝。
模型量化标准的神经网络数值计算是浮点计算,那么表示的位数相对多一些。观察发现,神经网络其实不需要这么高的精度,所以可以把浮点的表示转换成定精度的表示。 BMInfBMInf是OpenBMB发布的第一个工具包。 主要的目的是能让你在便宜的GPU,比如GTX 1060上,也能运行起来大模型。 深入理解Transformer我们来深入分析模型,看如何优化模型。 Transformer模型中主要的就是线性层,比如对于CMP-2中90%的参数都是在线性层中。 所以我们先来针对线性层。我们在允许一些精度损失的前提下,来优化线性层的运算效率。
量化
首先找到矩阵里面最大的那个数,然后缩放到-127,得到缩放系数。然后把浮点矩阵中所有元素除以该缩放系数,每个元素值经过四舍五入就能得到新的整数。这样可以把浮点数矩阵拆成缩放系数和一个整数矩阵。 就让能让矩阵中值从FP16变成了INT8。
针对线性层来说,分别对它的输入和权重进行量化,就可以得到两个INT8的矩阵和对应的缩放系数。接着在这两个INT8的矩阵中进行矩阵乘法。这会得到一个整数结果,但该结果INT8是存不下来的,此时会用INT32来存储。同时针对缩放系数进行一个标量惩罚,得到一个新的缩放系数,然后把整数结果乘上这个新缩放系数还原成浮点数。 但是该方法直接应用在Transformer上效果不理想。
内存分配借鉴操作系统中虚拟内存机制。 这种方法在CUD6中被实现了。
但实际操作上遇到了一些问题,
假设一块GPU上能放n层参数,那么我们可以固定n-2层在GPU上,多余的2层空间用于调度。 那现在的问题是,哪些层固定? 假如我们两层需要从CPU加载,左边的方案是固定7,8,9,调度6和10。 那么左边的方案肯定不会差于右边的,因为我们在加载完第6层之后,中间留下第7、8、9层计算的时间来加载第10层。即留给加载第10层的时间更长。 所以我们要尽量扩大需要加载的两层之间的间隔。 使用介绍在实现了上面的技术(BMInf包)之后,我们终于可以把百亿参数模型放到GTX1060上运行起来。 那么这么好的工具包怎么使用呢? |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/25 20:52:47- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |