NSDT工具推荐Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割

大型语言模型(LLM)可以用于文本生成、翻译、问答任务等。但是,LLM 也非常大(显然,大型语言模型)并且需要大量内存。 这对于手机和平板电脑等小型设备来说可能具有挑战性。

可以将参数乘以所选的精度大小以确定模型大小(以字节为单位)。 假设我们选择的精度是 float16(16 位 = 2 字节)。 假设我们想使用 BLOOM-176B 模型。 我们需要 1760 亿个参数 * 2 字节 = 352GB 来加载模型!

换句话说,要加载所有参数权重,我们需要 12 x 32GB 显存的机器! 如果我们想让LLM具有可移植性,这就太过分了。 减少LLM内存占用的技术就是为了克服这一难题而开发的。 最流行的技术包括:

  • 量化(quantization),涉及将 LLM 的权重转换为较低精度的格式,从而减少存储它们所需的内存。
  • 知识蒸馏(knowledge distillation),涉及训练较小的LLM来模仿较大的LLM的行为。 这可以通过将知识从较大的LLM转移到较小的LLM来完成。

这些技术使得LLM能够适应小内存。 这为在各种设备上使用LLM开辟了新的可能性。 今天我们来聊聊量化(敬请关注知识蒸馏)。

1、量化简介

让我们从一个简单的例子开始。 我们需要将 2023 转换为二进制:

如你所见,该过程相对简单。 为了存储数字 2023,我们需要 12位(1 位用于 + 或 - 符号)。 对于数字,我们可以使用 int16 类型。

将 int 存储为二进制和将 float 存储为二进制之间存在很大差异。 让我们尝试将 20.23 转换为二进制:

可以看到,浮点部分(尾数:mantissa)是按 1/2^n 的组合计算的,即使有 10 位专用于浮点部分,也无法非常精确地计算。 指数部分(指数:exponent)设置为 5 位,涵盖 32 以内的所有数字。总的来说,我们使用 16 位 (FP16) 来存储最接近 20.23 的值,但这是否是保持最接近 20.23 的最有效方法? 漂浮? 如果整个数字更大(例如 202.3)怎么办?

如果我们查看标准浮点类型,我们会注意到要存储 202.3,我们需要使用 FP32,从计算角度来看,这远远不合理。 相反,我们可以使用 bfloat16 将范围(指数)保存为 8 位,将精度(尾数)保存为 7 位。 这使我们能够扩大可能的小数范围,而不会损失太多精度。

需要明确的是,在进行训练时,我们需要尽可能达到的精度。 但是,将速度和大小优先于小数点后第六位对于推理来说是有意义的。

我们可以将内存使用量从 bfloat16 减少到 int8 吗?

2、零点量化和绝对最大量化

事实上,我们可以,并且有几种量化方法:

零点量化(zero-point quantization)通过将定点范围 (-1, 1) 转换为 int8 (-127, 127),然后将 int8 转换回 bfloat16 来节省一半的内存。

绝对最大量化(abs-max quantization)与零点量化类似,但我们没有设置自定义范围 (-1,1),而是将其设置为 (-abs(max), abs(max))。

让我们看一下这些量化如何在矩阵乘法的示例中使用,注意结果矩阵中数值的损失。首先看下未量化的精确矩阵乘法:

零点量化后的矩阵乘法:

绝对最大量化后的矩阵乘法:

3、异常数值分离计算

可以注意到,计算结果中较大的数值如 [-1579, -1780],量化后计算的损失比较大(零点量化后得到 [-1579, -1752],绝对最大量化后得到 [-1565,-1786])。 为了克服这些问题,我们可以单独处理离群的异常值:

正如你所看到的,结果更接近真实值。

4、量化到4个比特

但有没有一种方法可以在不损失太多质量的情况下使用更少的空间呢?

令我惊讶的是,有办法! 如果我们不是独立地将每个数字转换为较低类型,而是考虑错误并将其用于调整,会怎么样? 这种技术称为 GPTQ

与之前的量化一样,我们尽可能找到最接近的小数匹配,使总转换误差尽可能接近于零。

下面是GPTQ近似的第一步:

我们以这种方式逐行填充矩阵:

结果与异常值分离计算相结合,提供了相当不错的结果:

5、量化方法比较

我们现在可以比较各种量化方法:

LLM.int8() 方法表现得非常好! GPTQ 方法会损失质量,但允许使用两倍于 int8 方法的 GPU 内存。

6、BitsAndBytes量化参数

在代码中,你可能会发现类似于以下内容:

from transformers import BitsAndBytesConfig

# Configure BitsAndBytesConfig for 4-bit quantization
bnb_config = BitsAndBytesConfig(
 load_in_4bit=True,
 bnb_4bit_use_double_quant=True,
 bnb_4bit_quant_type="nf4",
 bnb_4bit_compute_dtype=torch.bfloat16,
)
# Loading model in pre-set configuration
pretrained_model = AutoModelForCausalLM.from_pretrained(
 model_id,
 quantization_config=bnb_config,
)

参数说明如下:

  • load_in_4bit 标志指定模型应以 4 位精度加载。  
  • bnb_4bit_use_double_quant 标志指定应使用双量化。
  • bnb_4bit_quant_type 标志指定量化类型。
  • bnb_4bit_compute_dtype 标志指定计算数据类型。

7、结束语

总而言之,我们了解了小数如何存储在内存中、如何通过一些精度损失来减少内存占用,以及如何通过 4 位量化运行选定的模型。


原文链接:How to Fit Large Language Models in Small Memory: Quantization

BimAnt翻译整理,转载请标明出处