大模型量化技术原理-SpQR

近年来,随着Transformer、MOE架构的提出,使得深度学习模型轻松突破上万亿规模参数,从而导致模型变得越来越大,因此,我们需要一些大模型压缩技术来降低模型部署的成本,并提升模型的推理性能。 模型压缩主要分为如下几类:

  • 剪枝(Pruning)
  • 知识蒸馏(Knowledge Distillation)
  • 量化

本系列将针对大模型的一些常见训练后量化方案(GPTQ、LLM.int8()、SmoothQuant、AWQ等)进行讲述。

还记得之前提到的量化方案 LLM.int8() 吗?本文讲述作者提出的另一种量化方案SpQR。

文章较长,建议先点赞收藏,后续再慢慢观看。另外,我撰写的大模型相关的博客及配套代码 均整理放置在Github:llm-action,有需要的朋友自取。

背景

随着大模型的参数越来越大,通常需要通过量化将此类LLMs压缩为每个参数3-4比特,以适合笔记本电脑和移动电话等内存有限的设备,从而实现个性化使用。但现有的技术方案(如:GPTQ)将参数量化至 3-4 比特通常会导致显著的精度损失,特别是对于 1-10B 参数范围内的较小模型,而这些模型却非常适合边缘部署。

为了解决这个精度的问题,作者引入了稀疏量化表示(SpQR),这是一种新的压缩格式和量化技术,首次实现了跨模型参数规模近乎无损的(near-lossless) LLM 压缩,同时达到与以前的方法类似的压缩水平。

SpQR 简介

SpQR (论文:SpQR: A Sparse-Quantized Representation for Near-Lossless LLM Weight Compression )是一种混合稀疏量化格式,其工作原理是识别和隔离异常值权重 (这些权重会导致特别大的量化误差),并以更高的精度存储它们,同时将所有其他权重压缩到3-4位,并实现小于1%的精度损失。这使得在单张 24 GB 消费级 GPU 上运行 33B 参数的 LLM 成为可能,并且在提升 15% 的推理速度情况下不会出现任何精度下降。

另外,SpQR 提供了高效的算法,可以将权重编码为 SpQR 格式,并在运行时对其进行高效解码。并且为 SpQR 提供了一种高效的 GPU 推理算法,该算法以近似的精度产生比 16 比特基线更快的推理,同时实现超过 4 倍的内存压缩增益。

为了将给定的预训练LLM转换为SpQR格式,作者采用了GPTQ的扩展版本。该方法通过未压缩模型传递校准数据;为了压缩每一层,它针对未压缩模型的输出和量化权重的输出之间的 L2 误差应用逐层(layer-wise)求解器。本方法将这个过程分为两个步骤:

  • 异常值检测步骤,隔离直接量化对层输出行为产生巨大影响的权重,
  • 实际压缩步骤,大多数(≥ 99%)权重被压缩到低位宽。

通过提取离群值,并通过进一步压缩量化元数据使整个表示更加有效。

该分析表明LLM权重量化误差表现出垂直和水平组相关性(group correlations),对应于与输入特征维度和输出隐藏维度相对应的系统性大误差。虽然,之前在 LLM.int8 已经观察到异常值输入特征 ,但本文首次证明,类似的异常值出现在权重(对于特定输出隐藏维度)中。与输入特征异常值不同,输出隐藏维度异常值仅出现在特定输出隐藏维度的小片段中

因此,作者提出的量化算法隔离此类异常值,并以 SpQR 格式有效地对给定模型进行编码。为了利用所得结构,还开发了一种基于压缩稀疏行(CSR)格式的专门稀疏矩阵乘法算法。为了使用 SpQR 进行逐个token生成,我们将这种稀疏算法与 3-4 比特权重的密集量化矩阵乘法结合起来。与LLM 生成 16 比特推理相比,SpQR 将 LLMs 的内存占用减少了约 3.4 倍或更多,而不会降低精度(以语言模型的损失或困惑度来衡量)。

之前的工作

早期的工作[LLM.int8()、Smoothquant]表明激活和权重都可以量化为 8 比特,而对精度的影响相对较低。这些研究对 LLMs 场景下压缩错误的原因产生了一些有趣的见解。具体来说,[LLM.int8(),Smoothquant]观察到在大语言模型的输入/输出中存在显著较高值的"离群特征",这会导致更高的量化误差,并提出不同的缓解策略。

而本文的作者从权重量化的角度来分析这个现象。特别是,作者研究了权重矩阵中输入特征异常值之外的异常值结构。虽然发现当前层的输入特征异常值与前一层的隐藏单元异常值权重相关,但并不存在严格的对应关系。这种部分结构化的异常值模式需要一种细粒度的混合压缩格式,该格式超越了利用先前工作中发现的异常值特征的列结构的算法。

因此,作者提出:

  • 一种高效且准确的训练后压缩算法,该算法将异常值识别为导致高输出误差的权重。
  • 一种将异常值压缩到相对于常规权重更高位宽的格式。
  • 本文的格式将异常值存储在块中,从而可以高效地实现 GPU kernels。

LLM 权重参数量化灵敏度分析

由于神经网络模型中并非所有参数都同等重要。如果权重的舍入误差较大,则权重可以被视为对量化敏感。

权重 <math xmlns="http://www.w3.org/1998/Math/MathML"> w a w_a </math>wa 在与另一个权重 <math xmlns="http://www.w3.org/1998/Math/MathML"> w b w_b </math>wb 强相关时,可能具有较大的舍入误差,这意味着可以通过向下舍入 <math xmlns="http://www.w3.org/1998/Math/MathML"> w b w_b </math>wb 很好地补偿对 <math xmlns="http://www.w3.org/1998/Math/MathML"> w a w_a </math>wa进行舍入的误差。之前的量化算法(如:GPTQ、ZeroQuant)正利用了这一想法,带来了对普通舍入的重大改进,尤其是低位宽。正确捕捉这方面的敏感性需要更健全的定义。

为了计算易处理,作者使用一小组校准集,通过输入 X 来评估 per-layer 级别的灵敏度,这些校准输入 X 是通过将模型运行到特定层来收集的。 我们将层权重矩阵 W 中某个权重 <math xmlns="http://www.w3.org/1998/Math/MathML"> w i j w_{ij} </math>wij 的灵敏度 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i j s_{ij} </math>sij 定义为 X 上的原始预测与该权重被量化的任何权重矩阵 <math xmlns="http://www.w3.org/1998/Math/MathML"> w ′ w' </math>w′ 的预测之间的最小平方差。如: <math xmlns="http://www.w3.org/1998/Math/MathML"> w i j ′ = quant ( w i j ) w'{ij} = \text{quant}(w{ij}) </math>wij′=quant(wij)。
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> s i j = min W ′   ∣ ∣ W X − W ′ X ∣ ∣ 2 2 s.t. w i j ′ = quant ( w i j ) s_{ij} = \text{min}{W'} \, ||WX - W'X||2^2 \quad \text{s.t.} \quad w'{ij} = \text{quant}(w{ij}) </math>sij=minW′∣∣WX−W′X∣∣22s.t.wij′=quant(wij)

更重要的是,除了 <math xmlns="http://www.w3.org/1998/Math/MathML"> w i j ′ w'{ij} </math>wij′ 之外的 <math xmlns="http://www.w3.org/1998/Math/MathML"> W ′ W' </math>W′ 的所有权重都可以采用任意的、不一定是量化的值,以便补偿因舍入 <math xmlns="http://www.w3.org/1998/Math/MathML"> w i j w{ij} </math>wij 而产生的量化误差,从而捕获上面讨论的相关性。 此外,由于我们允许连续值,因此该问题认为是封闭式解决方案。这可以通过遵循广义的OBC(Optimal Brain Compression)来确定,其中, <math xmlns="http://www.w3.org/1998/Math/MathML"> ( X X ⊤ ) − 1 {(XX^\top)^{-1}} </math>(XX⊤)−1是对应于优化问题的逆Hessian矩阵:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> s i j = ( w i j − quant ( w i j ) ) 2 2 ( X X ⊤ ) − 1 . s_{ij} = \frac{(w_{ij} - \text{quant}(w_{ij}))^2}{2(XX^\top)^{-1}}. </math>sij=2(XX⊤)−1(wij−quant(wij))2.

这种显著性度量可以通过量化求解器(例如:GPTQ)有效地近似。 更详细地说,GPTQ 逐列量化权重矩阵,同时在每个步骤中调整尚未量化的部分,以在与上述定义类似的意义上补偿量化误差。 因此,不是预先静态地决定所有敏感度,而是可以在算法处理每一列时,通过使用与所有尚未量化的权重相对应的 Hessian 子选择的逆来动态计算它们。 该矩阵已由 GPTQ 有效计算,因此,不会产生任何额外的开销。 这种方法的主要优点是 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i j s_{ij} </math>sij 总是根据 <math xmlns="http://www.w3.org/1998/Math/MathML"> w i j ′ w'_{ij} </math>wij′ 的最新值确定;因此,也考虑了由于先前量化的权重而进行的调整。

权重矩阵中敏感权重的位置不是随机的,而是具有特定的结构 。为了在量化过程中突出这些结构元素,我们计算了每个权重的灵敏度,并将它们可视化。下图可视化了 LLaMA-65B 最后一个自注意力层的输出投影的权重敏感度 。利用敏感度分析,作者观察到在权重矩阵中存在的几种模式,通常在单行或单列中。由于 LLaMA-65B 中的大权重矩阵有太多的行/列,无法在紧凑的图像(默认值: 8k × 32k 像素)中重新呈现,下图左边采用最大池化来可视化矩阵,选 32×32 行和列的每个正方形中的最大敏感度。

量化误差模式受到层类型和层深度的影响 。通过可视化观察到量化误差模式随层类型(例如:attention与MLP)和层深度的不同而变化。特别是,发现更深的层存在更敏感的异常值

以注意力权重矩阵为例,对异常值结构进行分类:

  • 行异常值 :如图底部中心所示,对应于输出特征的高敏感度区域。其中一些模式横跨整行,而另一些模式则是部分。在注意力层中,一些部分行异常值对应于注意力头的某个子集。
  • 列异常值 :如图右下角所示,显示了所有行的选择输入维度(列)的高灵敏度
  • 敏感注意力头 :如图顶部中心区域所示,出现了宽度为 128 的规则条纹,这对应了一个注意力头的所有权重。对应的"条纹"在 Q & K 投影矩阵中是水平的,在输出投影矩阵中是垂直的,在 V 投影矩阵和任何 MLP 权重中都没有。值得注意的是,即使在敏感的头(sensitive heads)内,单独的权重敏感性也存在显著差异。
  • 旋转嵌入模式 :如图右上角所示,展示了 64 个单位周期的重复垂直敏感度模式,这是旋转嵌入位置编码的特有模式。任何不使用旋转嵌入的层都没有这种模式。
  • 非结构化异常值 :除此之外,每一层都有许多单独的敏感度权重,这些权重不适合任何上述模式。这些非结构化异常值更频繁地出现在具有最大输入索引的列中(即在图像的右侧)。但在热力图上很难看到这种效果。

因此,作者将利用这些发现提出一个压缩表示,可以支持所有这些不同的离群值类型。

SpQR 技术原理

SpQR确定并隔离了异常权重,将其存储在更高的精度中,并将所有其他权重压缩为3-4比特。具体工作原理如下:

  • 首先,我们隔离离群权重,我们发现其量化会导致不成比例的高误差。因此,将这些权重保持高精度,而其他权重存储在低得多的精度中,例如:3比特格式。
  • 其次,我们实现了一种具有非常小的组大小(group size)的分组量化的变体,例如:16 个连续元素,但我们将量化缩放(scales)本身量化为 3 比特表示

之前的 LLM 量化算法同等对待低敏感度权重和高敏感度权重;然而,通过上面的讨论表明这可能会导致次优量化。理想情况下,将表示将更多的大小预算(size budge)分配给敏感权重。然而,这些权重作为单独权重或小组(small groups)分散在权重矩阵中,例如:部分行或注意力头。为了捕获这种结构,我们对量化过程进行了两项更改:一个用于捕获小敏感组,另一个用于捕获单个异常值。

通过双层量化捕获小组(small groups )权重 。在上一节中,我们观察到了几种情况,其中权重在连续的小组中表现相似,但组之间发生突然变化,例如:某些注意力头和部分行异常值。当应用标准方法时,在很多情况下,这些权重将被分组在一起,共享相同的量化统计数据。为了减少此类情况的数量,我们使用极小的组进行分组量化,通常为 <math xmlns="http://www.w3.org/1998/Math/MathML"> β 1 = 8 − 32 β_1 =8 − 32 </math>β1=8−32 个权重。也就是说,对于每 β 个连续权重,都有一个单独的量化 scale 和 zero-point。

这种选择与当前的直觉相悖,之前的工作认为存储量化统计数据的开销将超过精度优势。为了避免这个问题,我们使用与权重相同的量化算法------非对称(最小-最大)量化来量化分组统计数据本身。由于最小-最大量化的工作原理,量化值的范围将适合具有最大(或最小)量化scale的组,从而完美地量化它们。

换句话说,我们对来自 <math xmlns="http://www.w3.org/1998/Math/MathML"> β 2 = 16 β_2 = 16 </math>β2=16 个连续值的分组统计数据进行分组,并以相同的位数将它们一起量化,这样具有非典型量化参数的组最终会使用更多的"量化预算"。最后,第一层和第二层量化都直接在量化过程中进行,允许算法尽可能补偿第二层量化误差。

高灵敏度异常值 。我们的分析表明,存在一小部分敏感权重出现在小群体(在自注意力中)或独立的"异常值"(在 MLP 中)的情况。在某些情况下,1% 的权重占总量化误差的 75% 以上 。由于这些权重似乎会导致较高的、不可减少的误差,因此我们选择以高精度(16 位)保留这些异常值。由于这些离群值通常是非结构化的,因此我们以类似于压缩稀疏行(CSR)表示的按行排列方式对它们进行单独编码。这可以对不符合上述组(group)定义的单个异常值和小结构进行编码。

SpQR 量化算法

SpQR 量化算法如下所示:左侧片段描述了完整的过程,右侧包含用于bilevel量化和查找异常值的子例程。

上面伪代码详细描述了检测异常值的过程。

  • (1) 查找异常值并将其隔离为 16 位权重,
  • (2) 将非异常值"base"权重量化为 3-4 位,并将剩余量化迁移到 16 位异常值权重中。

对于异常值隔离步骤,该算法基于等式( <math xmlns="http://www.w3.org/1998/Math/MathML"> s i j = ( w i j − quant ( w i j ) ) 2 2 ( X X ⊤ ) − 1 s_{ij} = \frac{(w_{ij} - \text{quant}(w_{ij}))^2}{2(XX^\top)^{-1}} </math>sij=2(XX⊤)−1(wij−quant(wij))2)中的灵敏度标准实现了过滤技术。用于将离群值与基本权值隔离分离在全局范围内,对于每个矩阵,该算法的目标是选择一个敏感度阈值 τ 以获得整个模型中所需的异常值数量,通常约为权重的 1% 。具体来说,如果将权重保持为 16 位可以减少等式( <math xmlns="http://www.w3.org/1998/Math/MathML"> s i j = min W ′   ∣ ∣ W X − W ′ X ∣ ∣ 2 2 s.t. w i j ′ = quant ( w i j ) s_{ij} = \text{min}{W'} \, ||WX - W'X||2^2 \quad \text{s.t.} \quad w'{ij} = \text{quant}(w{ij}) </math>sij=minW′∣∣WX−W′X∣∣22s.t.wij′=quant(wij))中的误差,那么,特定权重(至少 τ )被视为异常值。

在第一个异常值检测步骤之后,我们量化base权重,忽略同一量化组中出现的所有异常值。因此,量化统计数据(例如:scale)是通过排除异常值来计算的。这导致错误方面的显著改善,因为例如最小-最大scales将显著减小。然后算法继续应用 GPTQ 来量化剩余的权重。

有趣的是,与LLM.int8()不同,权重不仅可以被选择为离群值,如果它本身导致错误,而且如果GPTQ算法可以使用这个权重来补偿许多其他权重的错误。因此,生成的 16 位值将不包含原始权重,而是包含经过调整以最小化输出误差的权重。因此,SpQR 超越了仅检测异常值,而是实现了隔离和处理量化过程中出现的异常值的更一般概念 。最后,该算法收集并压缩稀疏异常值矩阵 以及双层量化的最终量化统计数据,并返回压缩的权重及其元数据。

稀疏量化表示的实现和利用

我们的算法将同质权重转换为不同大小和精度的多种数据结构

总体而言,该表示由(1)量化权重、(2)第一层量化量化统计数据、第二层量化统计数据、(3)CSR异常值指数和值组成。

下面是单个权重张量的 SpQR 表示的高级概述。右侧描述了所有存储的数据类型及其维度。

上图中总结了 SpQR 的整体结构,下面是每个组件的描述。

存储量化组。所有非异常值权重均被编码为包含以下内容的结构:

  • <math xmlns="http://www.w3.org/1998/Math/MathML"> b w b_w </math>bw 比特独立的权重;
  • 每组大小为 B 的 <math xmlns="http://www.w3.org/1998/Math/MathML"> b q b_q </math>bq 比特 scale 和 zero point;
  • 用于量化 <math xmlns="http://www.w3.org/1998/Math/MathML"> B q B_q </math>Bq 量化组(scale 和 zero point)的 16 比特统计数据。

存储异常值。我们的异常值是非结构化的;为了存储,我们按行(首先)列(其次)对它们进行排序,以便同一行中的异常值在内存中是连续的。对于每个异常值,我们存储两个标量:16 比特权重值和 16 比特列索引。对于每一行,我们还存储一个 32 比特数字(到当前行为止的行中离群值的总数,以便进行高效推理)。这导致每个敏感权重的平均存储成本为 32.03 至 32.1 比特。

使用 SpQR 进行推理。作者为 SpQR 格式设计了一个基于 GPU 的高效解码实现,重点关注流行的逐个 Token 生成的场景。

由于 GPU 上的自回归推理受内存限制,因此高压缩率可以在很大程度上隐藏解码开销。

我们的算法将组统计数据和量化权重加载到共享内存 (SRAM) 中,反量化为 16 位,然后与 16 位输入执行矩阵乘法。为了处理异常值,我们设计了一种稀疏矩阵算法,该算法利用行中出现的异常值。粗略地说,该算法的工作原理如下:

  • 首先,我们将矩阵划分为大小相等的块(block)。
  • 然后,每个 GPU core(线程块block)将一大片异常值加载到共享内存 (SRAM) 中。
  • 并且,每个 GPU core 确定异常值是否是该段的一部分。
  • 之后从主存加载相应的权重;最后进行矩阵乘法。

该算法本质上通过步骤(1-3)执行负载平衡,而步骤(4)由于异常值的row-like模式而倾向于具有连续的内存访问。

SpQR 实验细节

在相似的模型大小下,SpQR 的性能明显优于 GPTQ、RTN,尤其是在较小的模型上。这一改进来自于 SpQR 实现了更多压缩,同时还减少了损失退化。

此外,如果我们测量每个参数所需的位数,使其在困惑度方面达到 16 位性能的 1% 范围内,下图显示使用 SpQR 每个参数仅需 4.6 至 4.71 位即可接近非量化模型。

针对 LLaMa 模型:

针对 Falcon 模型:

最后,还评估了 SpQR 自回归推理的推理速度,重点是测量单个 A100 GPU 上批量大小为 1 的token生成延迟。

我们在两种设置中测量推理速度:

  • i)从头开始生成 100 个token,
  • ii)在 1024 个token前缀(提示)上增加 100 个token。

我们将专门的稀疏矩阵乘法算法与 PyTorch (cuSPARSE) 中实现的算法进行比较。同时还与 16 位基线进行比较。我们将完整 SpQR 算法(即密集乘法部分和稀疏乘法部分)的端到端延迟测量为每秒推理步数。

结果如下表所示。可以看到,我们优化的 SpQR 算法比 16 位基线更快,可实现约 20-30% 的加速;比量化矩阵乘法 + 标准 PyTorch 稀疏矩阵乘法基线快近 2.0 倍。

SpQR 应用

通过以下命令压缩模型,然后使用WikiText2、C4和Penn Treebank数据集测试其perplexity 方面的性能。

css 复制代码
export MODEL_PATH=<PATH_TO_MODEL_DIR>
export DATASET=<INSERT DATASET NAME OR PATH TO CUSTOM DATA>

python main.py $MODEL_PATH $DATASET \
    --wbits 4 \
    --groupsize 16 \
    --perchannel \
    --qq_scale_bits 3 \
    --qq_zero_bits 3 \
    --qq_groupsize 16 \
    --outlier_threshold=0.2 \
    --permutation_order act_order \
    --percdamp 1e0 \
    --nsamples 128 

参数说明:

  • MODEL_PATH -- 模型文件的路径,其中包含 config.json
  • DATASET -- 用于压缩的数据集名称, [c4, ptb, wikitext2, pajama, refinedweb, none]数据集之一,或可供选择的预处理和tokenized数据集的路径。
  • --wbits 3 -- 量化权重表示的比特数。
  • --groupsize 16 -- 用于压缩的 first-order groups 的大小。
  • --qq_groupsize 16 -- 用于压缩的 second-order (量化) groups的大小。
  • --qq_scale_bits 3 --qq_zero_bits 3 -- 用于量化first-order权重的scale和zeros的比特大小。
  • --offload activations -- 不使用时将激活移动到 RAM。减少 VRAM 使用量,同时运行速度将减慢约 10%。
  • --save --load -- 保存/加载量化模型的路径。

总结

本文简要介绍了SpQR,其工作原理是识别和单独处理会导致大量化误差的异常值权重,并以更高的精度存储它们,同时将所有其他权重压缩到 3-4 比特,对于高精度的 LLaMA 和 Falcon LLM,在困惑中实现了小于 1% 的相对精度损失。这使得在单个 24 GB 消费级 GPU 上运行 33B 参数 LLM 成为可能。总之,作者通过小量化group和非结构化异常值处理都独立地改善了困惑度,并且比其他策略表现得更好。

码字不易,如果觉得我的文章能够能够给您带来帮助,期待您的点赞收藏加关注~~

参考文档

相关推荐
凡人的AI工具箱6 小时前
AI教你学Python 第11天 : 局部变量与全局变量
开发语言·人工智能·后端·python
是店小二呀6 小时前
【C++】C++ STL探索:Priority Queue与仿函数的深入解析
开发语言·c++·后端
canonical_entropy6 小时前
金蝶云苍穹的Extension与Nop平台的Delta的区别
后端·低代码·架构
我叫啥都行7 小时前
计算机基础知识复习9.7
运维·服务器·网络·笔记·后端
无名指的等待7128 小时前
SpringBoot中使用ElasticSearch
java·spring boot·后端
.生产的驴8 小时前
SpringBoot 消息队列RabbitMQ 消费者确认机制 失败重试机制
java·spring boot·分布式·后端·rabbitmq·java-rabbitmq
AskHarries9 小时前
Spring Boot利用dag加速Spring beans初始化
java·spring boot·后端
苹果酱05679 小时前
一文读懂SpringCLoud
java·开发语言·spring boot·后端·中间件
掐指一算乀缺钱10 小时前
SpringBoot 数据库表结构文档生成
java·数据库·spring boot·后端·spring
计算机学姐12 小时前
基于python+django+vue的影视推荐系统
开发语言·vue.js·后端·python·mysql·django·intellij-idea