万字技术干货!LLM工程师必读量化指南,可视化图解揭秘大模型如何压缩

人工智能 新闻
面对LLM逐渐膨胀的参数规模,没有H100的开发者和研究人员们想出了很多弥补方法,「量化」技术就是其中的一种。这篇可视化指南用各种图解,将「量化」的基本概念和分支方法进行了全方位总结。

大语言模型(LLM)通常过于庞大,无法在消费级硬件上运行。这些模型的参数可能超过数十亿,通常需要显存较大的GPU来加速推理过程。

因此,越来越多的研究开始关注如何缩小模型,比如改进训练方法或使用适配器。该领域的一项主要技术被称为量化(quantization)。

ML工程师Maarten Grootendorst撰写了一篇博客文章,在语言建模背景下专门介绍了量化技术,并通过可视化的方法逐一探索相关概念,以帮助我们建立对该技术的直观理解。

图片

在这篇博文中,Maarten将探讨各种方法、使用案例以及量化背后的原理。

文章目录以及涵盖内容如下图所示,主要介绍了训练后量化(PTQ)以及量化感知训练(QAT)两种方法,建议有AI基础的读者直接跳转至对称量化部分:

图片

第一部分:LLM的「问题」

「大语言模型」就是大在模型参数量上,规模通常达到数十亿的级别(其中主要是权重)。

这些参数不仅存储成本相当高,推理阶段的计算量也很大。

在推理过程中,激活值是输入和权重的乘积,因此权重数量越多,激活值也会越大。

图片

因此,我们希望尽可能高效地表示数十亿个值,从而尽可能减少存储参数所需的空间。

让我们从头开始,探索数值是如何表示的,然后再进行优化。

如何表示数值

数值存储的形式通常是浮点数(floting point number,或简称为floats):一个带有小数点的正数或负数。

这些值由每一位(bit)上的二进制数字表示。

IEEE-754标准描述了每一位上的数字如何表示具体数值,具体来说共有三种映射:符号、指数或小数(尾数)。

图片

这三个部分可以结合起来,根据一组bit值计算出所表示的数值:

图片

使用的位数越多,表示的数值值通常越精确,比如FP32形式就能比FP16精确到小数点后更多位数:

图片

内存限制

可用的位数越多,不仅数值越精确,可表示的数值范围也越广。

图片

给定位数和表示形式,可表示的数值区间称为动态范围(dynamic range),而两个相邻值之间的距离称为精度(precision)。

图片

这种表达形式的一个巧妙特性在于,我们可以计算出设备需要多少内存来存储某个给定值。

由于内存中的每个字节含有8位,我们可以为大多数形式的浮点数创建一个基本公式——

图片

在实际应用中,还有更多因素会影响推理过程中所需的显存/内存大小,例如上下文大小和模型架构

现在假设我们有一个包含700亿参数的模型。大多数模型本身使用32位浮点数(通常称为全精度)表示,这需要280GB的内存来加载模型。

图片

但如果能将所有参数用16位浮点数表示,所需的内存大小就可以直接减少一倍。

因此,将模型参数的表示位数最小化(不仅是推理,还有训练过程)是非常有吸引力的。

然而,这种方法不是没有代价的。随着表示位数减少导致精度降低,模型的准确性通常也会下降。

我们希望在保持准确性的同时减少表示数值的位数……此时,量化技术就派上用场了。

第二部分:量化入门

现在我们知道,量化的目的是将模型参数的精度从较高位宽(如32位浮点数)降低到较低位宽(如8位整数)。

图片

在减少表示原始参数的位数时,通常也会伴随一些精度(粒度,granularity)的损失。

为了让这种效果更直观,我们可以用照片颜色作为类比。比如,选择任意图像(左图),但只用8种颜色表示(右图):

图片

注意看,放大的曲奇饼干看起来比原来更有「颗粒感」。

与之相似,量化的主要目标是减少表示原始参数所需的比特数(颜色),同时尽可能保留原始参数的精度。

常见数据类型

首先,让我们看看常见的数据类型以及使用它们替代32位(称为全精度或FP32)表示的影响。

FP16

首先是一个从32位到16位(称为半精度或FP16)浮点数的例子:

图片

FP16可取的数值范围比FP32小得多。

BF16

为了获得与原始FP32相似的数值范围,引入了bfloat 16作为一种「截断的FP32」类型:

图片

BF16使用的位数与FP16相同,但增加了指数位,因此能取到更广泛的数值范围,常用于深度学习领域。

INT8

进一步减少位数时,就更接近整数而非浮点数的表示方法。比如,从FP32到只具有8位的INT8,只有原始位数的1/4:

图片

每次减少位数时,都会进行映射,将初始的FP32表示「压缩」到较少的位数中。

但在实际操作中,我们不需要将整个FP32范围[-3.4e38, 3.4e38]全部映射到INT8中。我们只需找到一种方法,将实际模型参数的数据范围映射到INT8中。

常见的压缩/映射方法可以有对称量化和非对称量化两种,都属于线性映射。

接下来将要探讨的就是从FP32到INT8的量化方法。

对称量化

在对称量化中,原始浮点值的范围被映射到量化空间中以零为中心的对称范围,量化前后的范围都以零为中点。

这意味着,原来浮点空间中的零,映射到量化空间后也恰好是零。

图片

一种对称量化的典型例子是最大绝对值(absmax)量化。

给定一个数值列表,我们取其中最高的绝对值(α)作为执行线性映射的范围。

图片

[-127, 127]表示受限范围(restricted range),未受限范围是[-128, 127],取决于量化方法

由于这是一个以零为中心的线性映射,公式很简单。

首先用以下公式计算比例因子(s):

- b是我们要量化到的字节数(8)

- α是最高的绝对值

然后,我们使用s来量化输入x:

图片

如上图所示,最大绝对值α为10.8,将FP32映射到INT8时,即有如下公式:

图片

如果要恢复原始的FP32值,也可以使用先前计算的比例因子(s)来进行反量化。

图片

先量化,再反量化以恢复原始值,全过程如下所示:

图片

可以看到某些值,如3.08和3.02,在量化为INT8时都是36。因此进行反量化恢复到FP32时,它们失去了一些精度并且不再可区分。

这种原始值和反量化值之间的差异被称为量化误差。通常,量化结果的位数越少,误差越大。

图片

非对称量化

与对称量化不同,非对称量化不是以零为中心的对称。相反,它将浮点范围内的最小值(β)和最大值(α)分别映射到量化范围的最小值和最大值。

这里我们探讨的方法被称为零点量化(zero-point quantization)。

图片

注意0的位置是如何移动的。这就是为什么它被称为非对称量化。在范围[-7.59, 10.8]中,最大值和最小值到0的距离不同。

由于零点位置的偏移,我们必须计算INT8范围内的零点才能执行线性映射。与之前一样,我们还必须计算比例因子(s),但使用INT8范围的差值[-128, 127]。

图片

由于需要在INT8范围内计算零点(z)以移动权重,这有点复杂。

像之前一样,让我们填入公式:

图片

为了将量化后的值从INT8反量化回FP32,我们需要使用先前计算的比例因子(s)和零点(z)。

除此之外,反量化很简单:

图片

当我们将对称和非对称量化并排放置时,可以快速看出两种方法之间的区别:

图片

在上图中,我们能看到对称量化的零中心特性与非对称量化的偏移。

范围映射和剪裁(Clipping)

在之前的例子中,我们探讨了如何将给定向量中的值范围映射到低位表示。虽然这样可以映射整个向量值的范围,但有一个主要缺点,即异常值(outlier)。

想象一下,你有一个包含以下值的向量:

图片

一个值比其他所有值都大得多,可以被认为是异常值。如果我们映射整个向量的范围,所有小值将被映射到相同的低位表示,并失去它们的区分度:

图片

这是之前使用的absmax方法。如果不进行剪裁,非对称量化也会发生同样的情况

相反,我们可以选择剪裁某些值。剪裁是指设置原始值的不同动态范围,使所有异常值都被设为相同的值。

在下面的例子中,我们手动将动态范围设置为[-5, 5],所有超出该范围的值将被映射到-127或127,无论它们的实际值是多少:

图片

这种方法的主要优点是非异常值的量化误差显著减少。然而会导致异常值的量化误差增加。

校准(Calibration)

上面的例子中,我们随机将动态范围设置为[-5, 5],但其实应该通过「校准」过程做出决定,找到一个合适的范围,包含尽可能多的值,同时最小化量化误差。

校准步骤的具体执行对于不同类型的参数是不一样的。

权重(和偏置)

我们可以将大语言模型(LLM)的权重和偏置(weights & biases)视为静态值,因为它们在运行模型之前是已知的。例如,Llama 3的约20GB文件大部分由其权重和偏置组成。

由于偏置变量的数量(数百万)显著少于权重(数十亿),因此偏置通常保持较高精度(如INT16),而量化的主要工作集中在权重上。

对于已知的静态权重,选择范围的校准技术包括:

- 手动选择输入范围的百分位数

- 优化原始权重和量化权重之间的均方误差(MSE)

- 最小化原始值和量化值之间的熵(KL散度)

图片

例如,选择一个百分位数,会导致类似于我们之前看到的剪裁行为。

激活值

在整个大语言模型中不断更新的输入通常被称为激活值(activations)。

图片

之所以被称为激活值,因为它们通常会经过一些激活函数,如sigmoid或relu

与权重不同,激活值在推理过程中随输入数据而变化,因此难以准确量化。

由于这些值在每个隐藏层之后都会更新,因此在推理阶段,只有输入数据通过模型后才能得知它们的具体数值。

图片

总体来说,有两种方法用于校准权重和激活值,应用于模型的不同阶段:

- 训练后量化(Post-Training Quantization,PTQ)

- 顾名思义,即训练后进行的量化

- 量化感知训练(Quantization Aware Training,QAT)

- 训练/微调期间的量化

第三部分:训练后量化(PTQ)

训练后量化(PTQ)是最流行的量化技术之一。它是在模型训练完成后,对模型的参数(包括权重和激活值)进行量化。

权重的量化可以采用对称量化或非对称量化的方法。

然而,激活值的量化需要经过推理阶段来获取其潜在分布,因为我们事先并不知道它们的范围。

激活值的量化有两种形式:

- 动态量化(dynamic quantization)

- 静态量化(static quantization)

动态量化

数据通过隐藏层后,其激活值会被收集,比较出每一层的最大值(α)和最小值(β):

图片

然后利用这些激活值的分布来计算量化输出所需的零点(zeropoint,z)和比例因子(scale factor,s)值:

图片

每次数据通过新的网络层时,这个过程都会重复。因此,每一层都有其独立的z和s值,从而使用不同的量化方案。

静态量化

与动态量化不同,静态量化并不是在推理过程中计算零点(zeropoint,z)和比例因子(scale factor,s),而是在推理之前计算这些值。

为了找到这些值,我们会使用一个校准数据集,并将其输入模型以收集这些潜在的激活值分布。

图片

收集到这些分布之后,就可以计算出在推理过程中进行量化所需的s和z值。

在实际推理时,不会重新计算s和z值,而是在所有激活中全局使用它们来对其进行量化。

总体来说,动态量化为每个隐藏层计算s和z值,往往更准确。然而,这可能会增加计算时间,因为这些值需要在每次推理时计算。

相反,静态量化虽然不如动态量化准确,但速度更快,因为它已经预先知道用于量化的s和z值。

4-bit量化领域

低于8-bit的量化一直是一个挑战,因为每减少一位,量化误差就会增加。幸运的是,有几种巧妙的方法可以将位数减少到6、4,甚至2-bit(尽管通常不建议将位数降到低于4-bit)。

我们将探讨在HuggingFace上常见的两种方法:

- GPTQ(全模型在GPU上运行)

- GGUF(可能将层卸载到CPU上)

GPTQ

GPTQ可以说是实际应用中最著名的4-bit量化方法之一。

它使用非对称量化,并逐层进行处理,每层独立处理后再继续处理下一层:

图片

在这个逐层量化过程中,它首先将层的权重转换为逆Hessian矩阵。逆Hessian矩阵是模型损失函数的二阶导数,表示模型输出对每个权重变化的敏感性。

简单来说,它本质上展示了每个层中权重的重要性(逆重要性)。

Hessian矩阵中较小值的权重更为重要,因为这些权重的微小变化可能导致模型性能的显著变化。

图片

在逆Hessian矩阵中,较低的值表示更「重要」的权重

接下来,我们量化并反量化权重矩阵的第一行:

图片

这一过程使我们能够计算量化误差(q),我们可以使用之前计算的逆Hessian值(h_1)来加权这个量化误差。

本质上,我们是在基于权重的重要性创建加权量化误差:

图片

接下来,我们将这个加权量化误差重新分配到该行的其他权重上。这有助于保持网络的整体功能和输出。

例如,如果对第二个权重(即x_2=0.3)进行此操作,我们会将量化误差(q)乘以第二个权重的逆Hessian(h_2)加上去:

图片

接下来,继续对给定行中的第三个权重进行相同的操作:

图片

重复这个重新分配加权量化误差q的过程,直到所有值都被量化。

这个方法所以有效,是因为权重通常是相互关联的。因此,当一个权重有量化误差时,相关的权重会通过逆Hessian进行相应的更新。

GGUF

虽然GPTQ是一种很好的在GPU上运行整个大语言模型(LLM)的量化方法,但如果没有相应的硬件条件,也可以通过GGUF将LLM的任意层卸载到CPU上。

相当于同时用CPU和GPU运行模型,以弥补显存(VRAM)不足的情况。

量化方法GGUF经常更新,而且依赖于具体的量化位数,但基本原理如下。

首先,给定层的权重被分成「超级块」,每个「超级块」包含一组「子块」。从这些「子块」中,我们计算出比例因子(s)和α值:

图片

为了量化给定的「子块」,可以使用之前提到的absmax量化,将给定的权重乘以比例因子(s_sub):

图片

比例因子s_sub是使用「子块」中的信息计算的,但用「超级块」中的信息s_super进行量化:

图片

总而言之,这种以块为单位的量化使用「超级块」的比例因子(s_super)来量化「子块」的比例因子(s_sub)。

每个比例因子的量化级别可能不同,「超级块」的比例因子通常比「子块」有更高的精度。

为了说明这一点,让我们探讨几个量化级别(2-bit、4-bit和6-bit):

图片

根据量化类型,还需要一个额外的最小值(m)来调整零点,这些与比例因子(s)一样被量化

第四部分:量化感知训练(QAT)

第三部分讲述了如何在训练后对模型进行量化。这种方法的缺点在于,没有考虑到实际的训练过程。

这就是量化感知训练(QAT)派上用场的地方。与训练后量化(PTQ)不同,QAT的目标是在训练中学习量化过程。

图片

QAT往往比PTQ更准确,因为在训练过程中已经考虑了量化。其工作原理如下:

在训练过程中,引入了所谓的「假」量化。比如先将权重量化为INT4,然后再反量化回FP32:

图片

这一过程让模型在训练阶段进行损失计算和权重更新时,就已经考虑到了量化误差。

如下图所示,QAT尝试探索「宽」极小值情况下的损失值,以减少量化误差,因为「窄」极小值往往会导致更大的量化误差。

图片

假设在反向传播过程中没有考虑量化,梯度下降过程就会选择损失值最小的权重。然而,如果它处于「窄」极小值中,那将引入更大的量化误差。

相反,如果我们考虑量化,将在「宽」极小值中选择一个不同的更新权重,其量化误差要小得多。

图片

因此,尽管PTQ方法在高精度(例如FP32)有较低的损失值,但QAT在低精度(例如INT4)下损失值也很低,这是我们所追求的。

1-bit时代:BitNet

之前我们看到,将量化精度降低到4-bit已经相当小了,但如果我们进一步降低呢?

这就是BitNet的用武之地,它将模型的权重表示为单个比特,即-1或1,通过将量化过程直接注入到Transformer架构中来实现这一点。

Transformer架构是大多数LLM的基础,由涉及线性层的计算组成:

图片

这些线性层通常以更高的精度表示,如FP16,而且是大多数权重所在的位置。

BitNet用BitLinear层替换了这些线性层:

图片

BitLinear层的工作原理与普通线性层相同,用权重乘以激活值来计算输出。

但不同的是,BitLinear层仅用1位表示模型的权重,使用INT8表示激活值:

图片

BitLinear层像量化感知训练(QAT)一样,在训练期间执行一种「假」量化,以分析权重和激活值的量化效果:

图片

让我们一步步地了解BitLinear。

权重量化

在训练过程中,权重以INT8存储,然后使用一种称为符号函数(signum function)的基本策略将其量化为1位。

本质上,它将权重的分布移动至以0为中心,然后将所有小于0的值分配为-1,将所有大于0的值分配为1:

图片

此外,它还跟踪一个值β(平均绝对值),我们将在之后的反量化过程中使用。

激活值量化

为了量化激活值,BitLinear使最大绝对值方法(absmax)将激活值从FP16转换为INT8,因为它们需要以较高的精度进行矩阵乘法(×)。

图片

此外,它还跟踪一个值α(最大绝对值),我们将在之后的反量化过程中使用。

反量化

我们跟踪了α(激活值的最大绝对值)和β(权重的平均绝对值),这些值将帮助我们将激活值反量化回FP16。

输出激活值使用{α, γ}重新缩放,以将其反量化到原始精度:

图片

这个过程相对简单,并且允许模型仅用两个值表示,即-1或1。

通过这种方法,作者观察到,随着模型规模的增长,1位训练和FP16训练之间的性能差距越来越小。

然而,这仅适用于较大的模型(>30B参数),较小模型之间的差距仍然很大。

所有LLM都是1.58位

为了改善之前提到的扩展性问题,BitNet 1.58b被引入。

在这种新方法中,模型的每个权重不仅可以是-1或1,还可以取0,使每个变量成为三元值(ternary)。

有趣的是,仅仅是添加0这个简单的操作,就大大改进了BitNet,加速了计算过程。

0的力量

为什么添加0是一个重大改进呢?

这与矩阵乘法有关!

首先,让我们探讨一下矩阵乘法的基本工作原理。

当计算输出时,我们将权重矩阵与输入向量相乘。下面是权重矩阵的第一层的第一行乘法的可视化:

图片

这种乘法涉及两个动作,将单个权重与输入相乘,然后将它们全部相加。

相比之下,BitNet 1.58b设法避免了乘法的动作,因为三值权重本质上告诉你以下内容:

- 1:我想加上这个值

- 0:我不想要这个值

- -1:我想减去这个值

因此,如果你的权重被量化到1.58 bit,你只需要执行加法:

图片

这不仅可以显著加快计算速度,还允许特征过滤。

将给定的权重设置为0就相当于忽略了这个输入,而不是像1-bit那样,表示加上或减去输入值。

量化

为了执行权重量化,BitNet 1.58b使用平均绝对值量化(absmean),这是我们之前见过的最大绝对值量化(absmax)的变体。

它只是压缩权重的分布并使用绝对均值(α)来量化值。然后将它们四舍五入为-1、0或1:

图片

与BitNet相比,激活值量化是相同的,除了一个方面:不是将激活值缩放到范围[0, 2ᵇ⁻¹],而是使用最大绝对值方法缩放到[-2ᵇ⁻¹, 2ᵇ⁻¹]。

总结一下,1.58-bit量化主要涉及两个技巧:

- 添加0以创建三值表示[-1, 0, 1]

- 权重的绝对均值量化

BitNet论文中有这样的结论:「13B BitNet b1.58在延迟、内存使用和能耗方面比3B FP16 LLM更高效。」

图片

论文地址:https://arxiv.org/abs/2402.17764

由于只有1.58个计算效率高的bit,我们得到了轻量级模型。

责任编辑:张燕妮 来源: 新智元
相关推荐

2024-01-15 08:17:00

模型技术

2024-07-19 08:34:18

2024-09-11 15:59:31

LLM.int8()大模型量化

2023-12-04 13:38:55

模型3D可视化

2023-03-30 08:28:57

explain关键字MySQL

2020-10-26 15:33:13

可视化数据项目

2010-09-13 17:38:47

Google的系统工程

2022-09-28 09:12:16

HBase分布式数据库数据库

2020-09-21 10:50:24

Java多线程代码

2018-06-15 15:50:34

技术

2015-08-13 13:48:50

数据中心

2024-02-01 08:34:30

大模型推理框架NVIDIA

2016-05-16 14:18:00

2018-01-02 09:17:57

2022-06-06 21:46:32

Kubernetes网络

2022-02-15 18:45:35

Linux进程调度器

2018-12-18 15:21:22

海量数据Oracle

2022-07-04 15:56:55

智能方案

2020-11-17 08:08:34

分库分表

2022-07-19 16:03:14

KubernetesLinux
点赞
收藏

51CTO技术栈公众号