共计 12900 个字符,预计需要花费 33 分钟才能阅读完成。
引言
语言模型始终在变大。截至撰写本文时,PaLM 有 5400 亿参数,OPT、GPT-3 和 BLOOM 有大概 1760 亿参数,而且咱们仍在持续朝着更大的模型倒退。下图总结了最近的一些语言模型的尺寸。
因为这些模型很大,因而它们很难在个别的设施上运行。举个例子,仅推理 BLOOM-176B 模型,你就须要 8 个 80GB A100 GPU (每个约 15,000 美元)。而如果要微调 BLOOM-176B 的话,你须要 72 个这样的 GPU!更大的模型,如 PaLM,还须要更多资源。
因为这些宏大的模型须要大量 GPU 能力运行,因而咱们须要找到升高资源需要而同时放弃模型性能的办法。目前已有一些试图放大模型尺寸的技术,比方你可能据说过的量化和蒸馏等技术。
实现 BLOOM-176B 的训练后,Hugging Face 和 BigScience 始终在寻找能让这个大模型更容易在更少的 GPU 上运行的办法。通过咱们的 BigScience 社区,咱们理解到一些无关 Int8 推理的钻研,它不会升高大模型的预测性能,而且能够将大模型的内存占用量减少 2 倍。很快咱们就开始单干进行这项钻研,最终将其齐全整合到 Hugging Face transformers
中。本文咱们将详述咱们集成在 Hugging Face 中的 LLM.int8() 计划,它实用于所有 Hugging Face 模型。如果你想理解更多钻研细节,能够浏览咱们的论文 LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale。
本文将次要介绍 LLM.int8() 量化技术,探讨将其纳入 transformers
库的过程中经验的艰难,并对后续工作进行了打算。
在这里,你将理解到到底是什么让一个大模型占用这么多内存?是什么让 BLOOM 占用了 350GB 内存?咱们先从一些基础知识开始,缓缓开展。
机器学习中罕用的数据类型
咱们从了解不同浮点数据类型开始,这些数据类型在机器学习中也被称为“精度”。
模型的大小由其参数量及其精度决定,精度通常为 float32、float16 或 bfloat16 之一 (下图起源)。
Float32 (FP32) 是规范的 IEEE 32 位浮点示意。应用该数据类型,能够示意大范畴的浮点数。在 FP32 中,为“指数”保留了 8 位,为“尾数”保留了 23 位,为符号保留了 1 位。因为是规范数据类型,所以大部分硬件都反对 FP32 运算指令。
而在 Float16 (FP16) 数据类型中,指数保留 5 位,尾数保留 10 位。这使得 FP16 数字的数值范畴远低于 FP32。因而 FP16 存在上溢 (当用于示意十分大的数时) 和下溢 (当用于示意十分小的数时) 的危险。
例如,当你执行 10k * 10k
时,最终后果应为 100M
,FP16 无奈示意该数,因为 FP16 能示意的最大数是 64k
。因而你最终会失去 NaN
(Not a Number,不是数字),在神经网络的计算中,因为计算是按层和 batch 程序进行的,因而一旦呈现 NaN
,之前的所有计算就全毁了。个别状况下,咱们能够通过缩放损失 (loss scaling) 来缓解这个问题,但该办法并非总能见效。
于是咱们创造了一种新格局 Bfloat16 (BF16) 来躲避这些限度。BF16 为指数保留了 8 位 (与 FP32 雷同),为小数保留了 7 位。这意味着应用 BF16 咱们能够保留与 FP32 雷同的动静范畴。然而绝对于 FP16,咱们损失了 3 位精度。因而,在应用 BF16 精度时,大数值相对没有问题,然而精度会比 FP16 差。
在 Ampere 架构中,NVIDIA 还引入了 TensorFloat-32(TF32) 精度格局,它应用 19 位示意,联合了 BF16 的范畴和 FP16 的精度。目前,它仅在某些操作的外部应用 [译者注: 即 TF32 是一个计算数据类型而不是存储数据类型]。
在机器学习术语中,FP32 称为全精度 (4 字节),而 BF16 和 FP16 称为半精度 (2 字节)。除此以外,还有 Int8 (INT8) 数据类型,它是一个 8 位的整型数据表示,能够存储 $2^8$ 个不同的值 (对于有符号整数,区间为 [-128, 127],而对于无符号整数,区间为 [0, 255])。
尽管现实状况下训练和推理都应该在 FP32 中实现,但 FP32 比 FP16/BF16 慢两倍,因而实际中经常应用混合精度办法,其中,应用 FP32 权重作为准确的“主权重 (master weight)”,而应用 FP16/BF16 权重进行前向和后向流传计算以进步训练速度,最初在梯度更新阶段再应用 FP16/BF16 梯度更新 FP32 主权重。
在训练期间,主权重始终为 FP32。而在实践中,在推理时,半精度权重通常能提供与 FP32 类似的精度 —— 因为只有在模型梯度更新时才须要准确的 FP32 权重。这意味着在推理时咱们能够应用半精度权重,这样咱们仅需一半 GPU 显存就能取得雷同的后果。
以字节为单位计算模型大小时,须要将参数量乘以所选精度的大小 (以字节为单位)。例如,如果咱们应用 BLOOM-176B 模型的 Bfloat16 版本,其大小就应为 $176 \times 10^{9} \times 2 字节 = 352GB$!如前所述,这个大小须要多个 GPU 能力装得下,这是一个相当大的挑战。
然而,如果咱们能够应用另外的数据类型来用更少的内存存储这些权重呢?深度学习社区已宽泛应用的办法是量化。
模型量化简介
通过试验,咱们发现不应用 4 字节 FP32 精度转而应用 2 字节 BF16/FP16 半精度能够取得简直雷同的推理后果,同时模型大小会减半。这促使咱们想进一步削减内存,但随着咱们应用更低的精度,推理后果的品质也开始急剧下降。
为了解决这个问题,咱们引入了 8 位量化。仅用四分之一精度,因而模型大小也仅需 1/4!但这次,咱们不能简略地抛弃另一半位宽了。
基本上讲,量化过程是从一种数据类型“舍入”到另一种数据类型。举个例子,如果一种数据类型的范畴为 0..9
,而另一种数据类型的范畴为 0..4
,则第一种数据类型中的值 4
将舍入为第二种数据类型中的 2
。然而,如果在第一种数据类型中有值 3
,它介于第二种数据类型的 1
和 2
之间,那么咱们通常会四舍五入为 2
。也就是说,第一种数据类型的值 4
和 3
在第二种数据类型中具备雷同的值 2
。这充沛表明量化是一个有噪过程,会导致信息失落,是一种有损压缩。
两种最常见的 8 位量化技术是零点量化 (zero-point quantization) 和最大绝对值 (absolute maximum quantization,absmax) 量化。它们都将浮点值映射为更紧凑的 Int8 (1 字节) 值。这些办法的第一步都是用量化常数对输出进行归一化缩放。
在零点量化中,如果我的数值范畴是 -1.0…1.0
,我想量化到 -127…127
,我须要先缩放 127
倍,而后四舍五入到 8
位精度。要复原原始值,我须要将 Int8 值除以雷同的量化因子 127
。在这个例子中,值 0.3
将缩放为 0.3*127 = 38.1
。四舍五入后失去值 38
。复原时,咱们会失去 38/127=0.2992
—— 因而最终会有 0.008
的量化误差。这些看似渺小的误差在沿着模型各层流传时往往会累积和增长,从而导致最终的精度降落。
译者注: 这个例子举得不好,因为浮点范畴和整型范畴都是对称的,所以不存在零点调整了,而零点调整是零点量化中最能体现其命名起因的局部。简而言之,零点量化分为两步,第一步值域映射,即通过缩放将原始的数值范畴映射为量化后的数值范畴; 第二步零点调整,即通过平移将映射后的数据的最小值对齐为指标值域的最小值
(图源)
当初咱们再看下 absmax 量化的细节。要计算 absmax 量化中 fp16 数与其对应的 int8 数之间的映射,你必须先除以张量的最大绝对值,而后再乘以数据类型的最大可示意值。
例如,假如你要用 absmax 对向量 [1.2, -0.5, -4.3, 1.2, -3.1, 0.8, 2.4, 5.4]
进行量化。首先须要计算该向量元素的最大绝对值,在本例中为 5.4
。Int8 的范畴为 [-127, 127]
,因而咱们将 127
除以 5.4
,失去缩放因子 23.5
。最初,将原始向量乘以缩放因子失去最终的量化向量 [28, -12, -101, 28, -73, 19, 56, 127]
。
要恢复原向量,能够将 int8 量化值除以缩放因子,但因为下面的过程是“四舍五入”的,咱们将失落一些精度。
对于无符号 Int8,咱们能够先减去最小值而后再用最大绝对值来缩放,这与零点量化的做法类似。其做法也与最小 – 最大缩放 (min-max scaling) 相似,但后者在缩放时会额定保障输出中的 0
始终映射到一个整数,从而保障 0
的量化是无误差的。
当进行矩阵乘法时,咱们能够通过组合各种技巧,例如逐行或逐向量量化,来获取更准确的后果。举个例子,对矩阵乘法 $A \times B=C$,咱们不会间接应用惯例量化形式,即用整个张量的最大绝对值对张量进行归一化,而会转而应用向量量化办法,找到 A 的每一行和 B 的每一列的最大绝对值,而后逐行或逐列归一化 A 和 B。最初将 A 与 B 相乘失去 C。最初,咱们再计算与 A 和 B 的最大绝对值向量的外积,并将此与 C 求哈达玛积来反量化回 FP16。无关此技术的更多详细信息能够参考 LLM.int8() 论文 或 Tim 的博客上的 对于量化和涌现特色的博文。
尽管这些根本技术可能帮忙咱们量化深度学习模型,但它们通常会导致大模型准确性的降落。咱们集成到 Hugging Face Transformers 和 Accelerate 库中的 LLM.int8() 是第一个实用于大模型 ( 如 BLOOM-176B) 且不会升高准确性的量化技术。
简要总结 LLM.int8(): 大语言模型的零进化矩阵乘法
在 LLM.int8() 中,咱们曾经证实了解 transformer 模型体现出的与模型规模相干的涌现个性对于了解为什么传统量化对大模型生效至关重要。咱们证实性能降落是由离群特色 (outlier feature) 引起的,下一节咱们会具体解释。LLM.int8() 算法自身如下。
实质上,LLM.int8() 通过三个步骤实现矩阵乘法计算:
- 从输出的隐含状态中,按列提取异样值 (即大于某个阈值的值)。
- 对 FP16 离群值矩阵和 Int8 非离群值矩阵别离作矩阵乘法。
- 反量化非离群值的矩阵乘后果并其与离群值矩阵乘后果相加,取得最终的 FP16 后果。
该过程能够总结为如下动画:
离群特色的重要性
超出某个散布范畴的值通常称为离群值。离群值检测已失去广泛应用,在很多文献中也有波及,且获取特色的先验散布对离群值检测工作很有助益。更具体地说,咱们察看到对于参数量大于 6B 的 transformer 模型,经典的量化办法会生效。尽管离群值特色也存在于较小的模型中,但在大于 6B 的 transformer 模型中,咱们察看到简直每层都会呈现超出特定阈值的离群点,而且这些离群点呈现出肯定的系统性模式。无关该景象的更多详细信息,请参阅 LLM.int8() 论文 和 涌现特色的博文。
如前所述,8 位精度的动静范畴极其无限,因而量化具备多个大值的向量会产生重大误差。此外,因为 transformer 架构的固有个性,它会将所有元素相互关联起来,这样的话,这些误差在流传几层后往往会混淆在一起。因而,咱们创造了混合精度合成的办法,以对此类极其离群值进行无效量化。接下来咱们对此办法进行探讨。
MatMul 外部
计算隐含状态后,咱们应用自定义阈值提取离群值,并将矩阵合成为两局部,如上所述。咱们发现,以这种形式提取所有幅度大于等于 6 的离群值能够完全恢复推理精度。离群值局部应用 FP16 示意,因而它是一个经典的矩阵乘法,而 8 位矩阵乘法是通过应用向量量化将权重和隐含状态别离量化为 8 位精度 – 即按行量化权重矩阵,并按列量化隐含状态,而后再进行相应向量乘加操作。最初,将后果反量化至半精度,以便与第一个矩阵乘法的后果相加。
0 进化是什么意思?
咱们如何正确评估该办法是否会对性能造成降落?应用 8 位模型时,咱们的生成品质损失了多少?
咱们应用 lm-eval-harness
在 8 位和原始模型上运行了几个常见的基准测试,后果如下。
对 OPT-175B 模型:
测试基准 | – | – | – | – | 差值 |
---|---|---|---|---|---|
测试基准名 | 指标 | 指标值 – int8 | 指标值 – fp16 | 标准差 – fp16 | – |
hellaswag | acc_norm | 0.7849 | 0.7849 | 0.0041 | 0 |
hellaswag | acc | 0.5921 | 0.5931 | 0.0049 | 0.001 |
piqa | acc | 0.7965 | 0.7959 | 0.0094 | 0.0006 |
piqa | acc_norm | 0.8101 | 0.8107 | 0.0091 | 0.0006 |
lambada | ppl | 3.0142 | 3.0152 | 0.0552 | 0.001 |
lambada | acc | 0.7464 | 0.7466 | 0.0061 | 0.0002 |
winogrande | acc | 0.7174 | 0.7245 | 0.0125 | 0.0071 |
对 BLOOM-176 模型:
测试基准 | – | – | – | – | 差值 |
---|---|---|---|---|---|
测试基准名 | 指标 | 指标值 – int8 | 指标值 – fp16 | 标准差 – fp16 | – |
hellaswag | acc_norm | 0.7274 | 0.7303 | 0.0044 | 0.0029 |
hellaswag | acc | 0.5563 | 0.5584 | 0.005 | 0.0021 |
piqa | acc | 0.7835 | 0.7884 | 0.0095 | 0.0049 |
piqa | acc_norm | 0.7922 | 0.7911 | 0.0095 | 0.0011 |
lambada | ppl | 3.9191 | 3.931 | 0.0846 | 0.0119 |
lambada | acc | 0.6808 | 0.6718 | 0.0065 | 0.009 |
winogrande | acc | 0.7048 | 0.7048 | 0.0128 | 0 |
咱们切实地看到上述这些模型的性能降落为 0,因为指标的相对差别均低于原始模型的标准误差 (BLOOM-int8 除外,它在 lambada 上略好于原始模型)。如果想要晓得 LLM.int8() 与以后其余先进办法的更具体的性能比拟,请查看 论文!
比原始模型更快吗?
LLM.int8() 办法的次要目标是在不升高性能的状况下升高大模型的利用门槛。但如果速度十分慢,该办法用途也不会很大。所以咱们对多个模型的生成速度进行了基准测试。
咱们发现应用了 LLM.int8() 的 BLOOM-176B 比 FP16 版本慢了大概 15% 到 23% —— 这应该是齐全能够承受的。咱们发现较小模型 ( 如 T5-3B 和 T5-11B) 的降速幅度更大。咱们还在致力优化这些小模型的推理速度。在一天之内,咱们能够将 T5-3B 的每词元推理提早从 312 毫秒升高到 173 毫秒,将 T5-11B 从 45 毫秒升高到 25 毫秒。此外,咱们 曾经找到起因,在行将公布的版本中,LLM.int8() 在小模型上的推理速度可能会更快。下表列出了以后版本的一些性能数据。
精度 | 参数量 | 硬件 | 每词元提早 (单位: 毫秒,batch size: 1) | 每词元提早 (单位: 毫秒,batch size: 8) | 每词元提早 (单位: 毫秒,batch size: 32) |
---|---|---|---|---|---|
bf16 | 176B | 8xA100 80GB | 239 | 32 | 9.9 |
int8 | 176B | 4xA100 80GB | 282 | 37.5 | 10.2 |
bf16 | 176B | 14xA100 40GB | 285 | 36.5 | 10.4 |
int8 | 176B | 5xA100 40GB | 367 | 46.4 | oom |
fp16 | 11B | 2xT4 15GB | 11.7 | 1.7 | 0.5 |
int8 | 11B | 1xT4 15GB | 43.5 | 5.3 | 1.3 |
fp32 | 3B | 2xT4 15GB | 45 | 7.2 | 3.1 |
int8 | 3B | 1xT4 15GB | 312 | 39.1 | 10.2 |
上表中的 3 个模型别离为 BLOOM-176B、T5-11B 和 T5-3B。
Hugging Face transformers
集成细节
接下来让咱们探讨在 Hugging Face transformers
集成该办法的细节,向你展现常见的用法及在应用过程中可能遇到的常见问题。
用法
所有的操作都集成在 Linear8bitLt
模块中,你能够轻松地从 bitsandbytes
库中导入它。它是 torch.nn.modules
的子类,你能够仿照下述代码轻松地将其利用到本人的模型中。
上面以应用 bitsandbytes
将一个小模型转换为 int8 为例,并给出相应的步骤。
-
首先导入模块,如下。
import torch import torch.nn as nn import bitsandbytes as bnb from bnb.nn import Linear8bitLt
-
而后就能够定义本人的模型了。请留神,咱们反对将任何精度的 checkpoint 或模型转换为 8 位 (FP16、BF16 或 FP32),但目前,仅当模型的输出张量数据类型为 FP16 时,咱们的 Int8 模块能力工作。因而,这里咱们称模型为 fp16 模型。
fp16_model = nn.Sequential(nn.Linear(64, 64), nn.Linear(64, 64) )
-
假如你曾经在你的数据集和工作上训完了你的模型!当初须要保留模型:
[... train the model ...] torch.save(fp16_model.state_dict(), "model.pt")
-
至此,
state_dict
已保留,咱们须要定义一个 int8 模型:int8_model = nn.Sequential(Linear8bitLt(64, 64, has_fp16_weights=False), Linear8bitLt(64, 64, has_fp16_weights=False) )
此处标记变量
has_fp16_weights
十分重要。默认状况下,它设置为True
,用于在训练时使能 Int8/FP16 混合精度。然而,因为在推理中咱们对内存节俭更感兴趣,因而咱们须要设置has_fp16_weights=False
。 -
当初加载 8 位模型!
int8_model.load_state_dict(torch.load("model.pt")) int8_model = int8_model.to(0) # 量化产生在此处
请留神,一旦将模型的设施设置为 GPU,量化过程就会在第二行代码中实现。如果在调用 .to
函数之前打印 int8_model[0].weight
,你会看到:
int8_model[0].weight
Parameter containing:
tensor([[0.0031, -0.0438, 0.0494, ..., -0.0046, -0.0410, 0.0436],
[-0.1013, 0.0394, 0.0787, ..., 0.0986, 0.0595, 0.0162],
[-0.0859, -0.1227, -0.1209, ..., 0.1158, 0.0186, -0.0530],
...,
[0.0804, 0.0725, 0.0638, ..., -0.0487, -0.0524, -0.1076],
[-0.0200, -0.0406, 0.0663, ..., 0.0123, 0.0551, -0.0121],
[-0.0041, 0.0865, -0.0013, ..., -0.0427, -0.0764, 0.1189]],
dtype=torch.float16)
而如果你在第二行之后打印它,你会看到:
int8_model[0].weight
Parameter containing:
tensor([[3, -47, 54, ..., -5, -44, 47],
[-104, 40, 81, ..., 101, 61, 17],
[-89, -127, -125, ..., 120, 19, -55],
...,
[82, 74, 65, ..., -49, -53, -109],
[-21, -42, 68, ..., 13, 57, -12],
[-4, 88, -1, ..., -43, -78, 121]],
device='cuda:0', dtype=torch.int8, requires_grad=True)
正如咱们在后面局部解释量化办法时所讲,权重值被“截断”了。此外,这些值的散布看上去在 [-127, 127] 之间。
你可能还想晓得如何获取 FP16 权重以便在 FP16 中执行离群值的矩阵乘?很简略:
(int8_model[0].weight.CB * int8_model[0].weight.SCB) / 127
你会看到:
tensor([[0.0028, -0.0459, 0.0522, ..., -0.0049, -0.0428, 0.0462],
[-0.0960, 0.0391, 0.0782, ..., 0.0994, 0.0593, 0.0167],
[-0.0822, -0.1240, -0.1207, ..., 0.1181, 0.0185, -0.0541],
...,
[0.0757, 0.0723, 0.0628, ..., -0.0482, -0.0516, -0.1072],
[-0.0194, -0.0410, 0.0657, ..., 0.0128, 0.0554, -0.0118],
[-0.0037, 0.0859, -0.0010, ..., -0.0423, -0.0759, 0.1190]],
device='cuda:0')
这跟第一次打印的原始 FP16 值很靠近!
- 当初你只需将输出推给正确的 GPU 并确保输出数据类型是 FP16 的,你就能够应用该模型进行推理了:
input_ = torch.randn(64, dtype=torch.float16)
hidden_states = int8_model(input_.to(torch.device('cuda', 0)))
你能够查看 示例脚本,获取残缺的示例代码!
多说一句,Linear8bitLt
与 nn.Linear
模块略有不同,次要在 Linear8bitLt
的参数属于 bnb.nn.Int8Params
类而不是 nn.Parameter
类。稍后你会看到这给咱们带来了一些小麻烦!
当初咱们开始理解如何将其集成到 transformers
库中!
accelerate
足矣
在解决大模型时,accelerate
库蕴含许多有用的工具。init_empty_weights
办法特地有用,因为任何模型,无论大小,都能够在此办法的上下文 (context) 内进行初始化,而无需为模型权重调配任何内存。
import torch.nn as nn
from accelerate import init_empty_weights
with init_empty_weights():
model = nn.Sequential([nn.Linear(100000, 100000) for _ in range(1000)]) # This will take ~0 RAM!
初始化过的模型将放在 PyTorch 的 meta
设施上,这是一种用于表征向量的形态和数据类型而无需理论的内存调配的超酷的底层机制。
最后,咱们在 .from_pretrained
函数外部调用 init_empty_weights
,并将所有参数重载为 torch.nn.Parameter
。这不是咱们想要的,因为在咱们的状况中,咱们心愿为 Linear8bitLt
模块保留 Int8Params
类,如上所述。咱们最初胜利应用 此 PR 修复了该问题,它将下述代码:
module._parameters[name] = nn.Parameter(module._parameters[name].to(torch.device("meta")))
批改成:
param_cls = type(module._parameters[name])
kwargs = module._parameters[name].__dict__
module._parameters[name] = param_cls(module._parameters[name].to(torch.device("meta")), **kwargs)
当初这个问题曾经解决了,咱们能够轻松地在一个自定义函数中利用这个上下文管理器将所有 nn.Linear
模块替换为 bnb.nn.Linear8bitLt
而无需占用内存!
def replace_8bit_linear(model, threshold=6.0, module_to_not_convert="lm_head"):
for name, module in model.named_children():
if len(list(module.children())) > 0:
replace_8bit_linear(module, threshold, module_to_not_convert)
if isinstance(module, nn.Linear) and name != module_to_not_convert:
with init_empty_weights():
model._modules[name] = bnb.nn.Linear8bitLt(
module.in_features,
module.out_features,
module.bias is not None,
has_fp16_weights=False,
threshold=threshold,
)
return model
此函数递归地将 meta
设施上初始化的给定模型的所有 nn.Linear
层替换为 Linear8bitLt
模块。这里,必须将 has_fp16_weights
属性设置为 False
,以便间接将权重加载为 Int8
,并同时加载其量化统计信息。
咱们放弃了对某些模块 (这里时 lm_head
) 进行替换,因为咱们心愿放弃输入层的原始精度以取得更准确、更稳固的后果。
但还没完!下面的函数在 init_empty_weights
上下文管理器中执行,这意味着新模型将仍在 meta
设施中。
对于在此上下文管理器中初始化的模型,accelerate
将手动加载每个模块的参数并将它们拷贝到正确的设施上。因而在 bitsandbytes
中,设置 Linear8bitLt
模块的设施是至关重要的一步 (感兴趣的读者能够查看 此代码),正如你在咱们下面提供的脚本中所见。
而且,第二次调用量化过程时会失败!咱们必须想出一个与 accelerate
的 set_module_tensor_to_device
函数相应的实现 (称为 set_module_8bit_tensor_to_device
),以确保咱们不会调用两次量化。咱们将在上面的局部中具体探讨这个问题!
在 accelerate
设置设施要当心
这方面,咱们对 accelerate
库进行了精美的批改,以获得均衡!
在模型被加载且设置到正确的设施上后,有时你仍需调用 set_module_tensor_to_device
以便向所有设施分派加了 hook 的模型。该操作在用户调用 accelerate
的 dispatch_model
函数时会被触发,这意味着咱们有可能屡次调用 .to
,咱们须要防止该行为。
咱们通过两个 PR 实现了目标,这里 的第一个 PR 毁坏了一些测试,但 这个 PR 胜利修复了所有问题!
总结
因而,最终咱们实现了:
- 应用正确的模块在
meta
设施上初始化模型。 - 不重不漏地对指标 GPU 逐个设置参数,确保不要对同一个 GPU 反复设置!
- 将新加的参数变量更新到所有须要的中央,并增加好文档。
- 增加高覆盖度的测试!你能够从 此处 查看更多对于测试的详细信息。
知易行难,在此过程中,咱们经验了许多艰巨的调试局,其中很多跟 CUDA 核函数无关!
总而言之,这次集成的过程充斥了冒险和趣味; 从深入研究并对不同的库做一些“手术”,到整合所有并最终使其发挥作用,每一步都充斥挑战!
当初,咱们看看如何在 transformers
中胜利应用它并从中获益!
如何在 transformers
中应用它
硬件要求
CPU 不反对 8 位张量外围 [*]。bitsandbytes 能够在反对 8 位张量外围的硬件上运行,这些硬件有 Turing 和 Ampere GPU (RTX 20s、RTX 30s、A40-A100、T4+)。例如,Google Colab GPU 通常是 NVIDIA T4 GPU,而最新的 T4 是反对 8 位张量外围的。咱们前面的演示将会基于 Google Colab!
*: 译者注: Intel 最新的 Sapphire Rapids CPU 已反对 8 位张量指令集: AMX
装置
应用以下命令装置最新版本的库 (确保你的 python>=3.8)。
pip install accelerate
pip install bitsandbytes
pip install git+https://github.com/huggingface/transformers.git
演示示例 – 在 Google Colab 上运行 T5 11B
以下是运行 T5-11B 的演示。T5-11B 模型的 checkpoint 精度为 FP32,须要 42GB 内存,Google Colab 里跑不动。应用咱们的 8 位模块,它仅需 11GB 内存,因而能轻易跑通:
T5-11B 的 Colab 演示: <url>https://colab.research.google.com/drive/1YORPWx4okIHXnjW7MSAi…</url>
或者,你还能够看看上面这个应用 8 位 BLOOM-3B 模型进行推理的演示!
BLOOM-3B 的 Colab 演示: <url>https://colab.research.google.com/github/huggingface/blog/blo…</url>
影响范畴
咱们认为,该办法让超大模型不再是下里巴人,而是人人皆可涉及。在不升高性能的状况下,它使领有较少算力的用户可能应用以前无奈应用的模型。
咱们曾经发现了几个能够在持续改良的畛域,以使该办法对大模型更敌对!
较小模型的推理减速
正如咱们在 基准测试局部 中看到的那样,咱们能够将小模型 (<=6B 参数) 的运行速度进步近 2 倍。然而,尽管推理速度对于像 BLOOM-176B 这样的大模型来说比较稳定,但对小模型而言仍有改良的余地。咱们曾经定位到了问题并有心愿复原与 FP16 雷同的性能,甚至还可能会有小幅减速。咱们将在接下来的几周内合入这些改良。
反对 Kepler GPU (GTX 1080 等)
尽管咱们只反对过来四年的所有 GPU,但事实是某些旧的 GPU (如 GTX 1080) 当初依然被大量应用。尽管这些 GPU 没有 Int8 张量外围,但它们有 Int8 向量单元 (一种“弱”张量外围)。因而,这些 GPU 也能够体验 Int8 减速。然而,它须要一个齐全不同的软件栈来优化推理速度。尽管咱们的确打算集成对 Kepler GPU 的反对以使 LLM.int8() 的利用更宽泛,但因为其复杂性,实现这一指标须要一些工夫。
在 Hub 上保留 8 位 checkpoint
目前 8 位模型无奈间接加载被推送到 Hub 上的 8 位 checkpoint。这是因为模型计算所需的统计数据 (还记得上文提到的 weight.CB
和 weight.SCB
吗?) 目前没有存储在 state_dict 中,而且 state_dict 的设计也未思考这一信息的存储,同时 Linear8bitLt
模块也还尚未反对该个性。
但咱们认为保留它并将其推送到 Hub 可能有助于进步模型的可拜访性。
CPU 的反对
正如本文结尾所述,CPU 设施不反对 8 位张量核。然而,咱们能克服它吗?在 CPU 上运行此模块能够显著进步可用性和可拜访性。[译者注: 如上文,最新的 Intel CPU 已反对 8 位张量核]
扩大至其余模态
目前,大模型以语言模型为主。在超大视觉、音频和多模态模型上利用这种办法可能会很有意思,因为随着这些模型在将来几年变得越来越多,它们的易用性也会越来越重要。
致谢
非常感谢以下为进步文章的可读性以及在 transformers
中的集成过程做出奉献的人 (按字母程序列出):
JustHeuristic (Yozh),
Michael Benayoun,
Stas Bekman,
Steven Liu,
Sylvain Gugger,
Tim Dettmers
英文原文: https://hf.co/blog/hf-bitsandbytes-integration
原文作者: Younes Belkada,Tim Dettmers
译者: Matrix Yao (姚伟峰),英特尔深度学习工程师,工作方向为 transformer-family 模型在各模态数据上的利用及大规模模型的训练推理。
排版: zhongdongy (阿东)