AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


王文广万字长文揭秘大模型量化的GPTQ方法:从OBS经OBQ到GPTQ,海森矩阵的魔力
发布日期:2024-05-31 06:06:31 浏览次数: 3117


什么是大模型的量化呢?如果你关心大模型,你可能会看到许多大模型量化有关的概念,比如某个机构发布了4位量化的模型,而这4位的模型命名中,很可能会出现 GPTQ,那么什么是 GPTQ 呢?它又为什么这么有用呢?我是王文广,在公众号“走向未来”力图以通俗易懂的语言讲清楚GPTQ 的原理及实现。如果有任何不解之处,随时在评论中提问或讨论。最后,抱怨一下,微信公众号对公式特别不友好,排版不足之处,找微信!

本文是《秒懂人工智能》《秒懂大模型》系列文章一篇,此前已发布的系列文章包括:

  1. 王文广万字长文揭秘大模型量化技术:探究原理,理解大模型高效推理最重要的技术

  2. 王文广万字长文揭秘SORA:多模态大模型技术、架构、应用与模拟世界的探索

  3. 王文广揭秘LoRA:低秩适配,大模型高效训练的降维方法【秒懂人工智能】

  4. 王文广揭秘人工智能成长路径:从零开始构建你的AI技能,成为AI领域专家【秒懂人工智能系列】

  5. 王文广揭秘大语言模型:AI新范式的无限可能与企业价值的驱动引擎【秒懂人工智能系列】

  6. 王文广揭秘RAG:如何让大语言模型更准确、更可靠、更有时效性【秒懂人工智能系列】

什么是大模型的量化呢?如果你关心大模型,你可能会看到许多大模型量化有关的概念,比如某个机构发布了4位量化的模型。有关量化的概念,详情阅读上一篇文章王文广万字长文揭秘大模型量化技术:探究原理,理解大模型高效推理最重要的技术。在量化中,也许会看到 GPTQ的字眼,那么什么是 GPTQ 呢?它又为什么这么有用呢?我是王文广,在公众号“走向未来”力图以通俗易懂的语言讲清楚GPTQ 的原理及实现。如果有任何不解之处,随时在评论中提问或讨论。

GPTQ是在一篇题为“GPTQ: Accurate Post-Training Quantization for Generative Pre-Trained Transformers”的论文(arxiv: 2210.17323)提出的量化方法,GPTQ是一种基于近似二阶信息的一次性权重量化方法,既能保持高准确率又实现高效率推理。最开始主要是对 GPT系列模型进行训练后量化 (PTQ) ,标题就明确提到“准确的生成式预训练转换器的后训练量化”。GPTQ的核心优势是能够在精度下降非常小的情况下将大模型量化为4bit 或3bit,这使得单个 GPU 能够推理千亿参数(100B) 规模以上的大模型。同时,推理速度上,相比 FP16的浮点数大模型,用 gptq 量化为int4能够实现3~5倍的推理效率提升。具体有:与 FP16 模型相比,GPTQ 模型在 NVIDIA A100 等高端 GPU 上提供 3.25 倍的速度提升,在 NVIDIA A6000 等经济高效的 GPU 上提供 4.5 倍的速度提升。论文提到,GPTQ,在最多几个小时内就能有效地执行数千亿参数量级的模型,并且足够精确,可以将这种规模的模型压缩到每个参数3或4位,而不会显著降低精度。例如,GPTQ可以在大约四个GPU小时内量化OPT-175B和BLOOM-176B,同时仅略微提高混淆度(一个非常严格的精度度量)。通过 GPTQ 量化,能够在单个NVIDIA A100 GPU上运行压缩后的OPT-175B模型,或者仅使用两个更具成本效益的NVIDIA A6000 GPU。通过定制了GPU内核来利用压缩加快内存加载,从而在使用A100 GPU时实现约3.25倍的加速,在使用A6000 GPU时实现约4.5倍的加速。

GPTQ 只能将模型量化为基于 INT 的数据类型,最常用于转换为 4INT。使用 GPTQ 4 位量化的模型与 ExLLama 兼容,可实现 GPU 加速。Hugging Face的AutoGTPQ默认自动使用ExLLama进行加速。论文中提到,GPTQ可以在大约四个GPU小时内量化1750亿参数的GPT模型,将权重位宽降低到3或4位,相对于未压缩的基线几乎没有准确性损失。

GPTQ 本质上是将OBS(Optimal Brain Surgeon,最优脑外科医生)方法用于量化。OBS是用于网络剪枝的一种方法【Hassibi, B., & Stork, D. (1992). Second order derivatives for network pruning: Optimal brain surgeon. Advances in neural information processing systems, 5.】,是1992年的一篇论文,核心思想是利用误差函数所有二阶导数的信息进行网络剪枝(network pruning)。与量化类似,网络剪枝也是一种压缩网络的方法,核心思想是对训练好的神经网络中无关紧要的权重予以删除,从而改善神经网络模型的泛化能力、简化网络、降低硬件或存储需求、加快进一步训练速度,在某些情况下还能实现规则提取。OBS的关键是一个关于训练数据和网络结构信息的递推公式,用于计算海森(Hessian)矩阵的逆矩阵海森矩阵的定义如下:

在最优脑外科医OBS中,一个已训练达到误差局部最小值的网络中,误差相对于权重(或参数)的泰勒级数展开式为:

5. 不再删除任何权重,否则会导致误差大幅增加。(此时可能需要重新训练网络。)

上图阐释了OBS 的基本思想。图中是误差作为神经网络模型中两个权重的函数。(局部)最小值发生在通过梯度下降或其他学习方法找到的权重W*处。在这个例子中,基于量级的剪枝技术(mag)然后删除最小的权重,即图中的weight 2; 在任何重新训练之前,最优神经损伤(OBD)方法删除weight 1。相比之下, OBS不仅删除weight 1,而且还自动调整weight 2的值以最小化误差,无需重新训练。剪枝后误差的相对大小(在任何重新训练之前)取决于特定的问题,但在二阶导数范围内服从E(mag) ≥E(OBD) ≥E(OBS),这是OBS优越性的关键。在上图这个例子中,OBS和OBD引导删除了相同的权重(权重1)。然而,在许多情况下,OBS会删除不同于OBD删除的权重。我们称我们的方法为"最优脑外科医生"是因为除了删除权重外,它还计算并调整其他权重的强度,而无需进行梯度下降或其他增量重新训练。这里的误差曲面是一般性的,沿不同方向具有不同的曲率(二阶导数),非特殊权重值处的最小值,以及非对角海森矩阵(即主轴不平行于权重轴)。我们惊讶地发现,我们研究过的每个问题都有强非对角海森矩阵——从而解释了OBS 比 OBD有所改进。

如何计算海森矩阵的逆矩阵?无论是通过反向传播、竞争学习(competitive learning)、Boltzmann算法还是任何其他方法对其进行训练是无关紧要的,只要能够求导即可。在这种请开,海森矩阵可以化简为某些梯度向量的样本协方差矩阵。而计算OBS所需的梯度向量通常是以很小的计算代价即可获得;海森矩阵的协方差形式产生了一个计算逆矩阵的递推公式。

考虑一个将维数为n_i的输入向量in映射到维数为n_o的输出向量o的一般非线性神经网络,按照如下方式:

o=F(w,in)

其中w是表示神经网络权重或其他参数的n维向量。为简单起见,我们将称w为权重向量,但必须强调,w可以表示任何连续参数,例如描述神经传递函数、权重共享等的参数。对应于训练集的均方误差定义为:

接下来我们考虑一个在w*处训练到误差局部最小值的网络。在这种情况下,网络响应o[k]将接近期望响应t[k],因此我们忽略涉及(t[k]-o[k])的项。即使在剪枝的后期,当这种误差对于单个模式而言并不小时,这种近似也是可以被证明的。从计算的角度来看,我们首先注意到在进行大量剪枝之前,H通常是退化的,其逆矩阵是不好定义的。该近似保证了在计算H^(-1)时不会出现奇异性。它还使得计算H^(-1)的计算复杂度与计算H相同——O(pn^2)。在统计学中,该近似是Fisher评分法的基础,其目标是用期望值代替真实海森矩阵,并确保H是正定的(从而避免困扰高斯-牛顿法的稳定性问题)【Seber和Wild,1989,Nonlinear Regression 35-36 Wiley.】。另一种解释是功能理由。考虑一个经过训练达到小训练误差的高容量网络。我们可以将网络结构视为包含信号和噪声两个部分。在剪枝时,我们希望消除那些导致"过度拟合"的权重,即学习了噪声。如果我们的剪枝方法不采用(t-o)≈0近似,每一步剪枝都会将噪声重新注入系统,因为它会惩罚噪声项。以下是另一种思考这一近似的方式。在通过OBS进行一些剪枝之后,我们已经到达一个新的权重向量,该向量是误差的一个新的局部最小值(参见上图)。即使这种误差不可忽略,我们也希望尽可能接近这个误差最小值。因此,我们想象有一个新的、有效的教师信号t*,该信号将使网络保持在这个新的误差最小值附近。那么,在使用下式(**)而不是上式时,我们实际上将(t*-o)设置为零。

但是,OBS要求的是对 H 求逆。这个逆矩阵计算可以使用标准的矩阵求逆方程:

受到了K-FAC近似的启发,OBS首先被扩展为逐层OBS(L-OBS)。L-OBS不是为全局目标近似OBS框架,而是为每层的二次损失进行近似,同时基于单次Hessian计算对所有权重进行剪枝。事实上,迭代应用OBS来一次移除一个权重,将产生层内剪枝问题的精确贪婪解,因为它在每个步骤中都做出(局部)最优决策。虽然这种贪婪方法不能保证收敛到全局最优,但对于处理规模太大而无法通过精确方法解决的问题实例,这种方法可以非常有效。当然,一次剪枝一个权重,在计算上是非常昂贵的。这是由于海森矩阵H是一个d×d矩阵,其中,即模型权重矩阵的参数数量,仅存储和计算它就非常地昂贵。对于大模型来讲,往往权重的维度或更高,那么将大于,是非常庞大的矩阵。

Optimal Brain Quantizer (OBQ)将经典的Optimal Brain Surgeon(OBS)二阶权重剪枝框架推广应用于量化。OBQ按量化误差的顺序逐一量化权重,并始终调整剩余权重。用数学语言正式地描述,就是,在给定由权重定义的层 的网络,层输入为,层网络结构则定义为。那么,OBQ 的目标是找到量化权重,使得网络的输出与未量化的层输出尽可能接近。层的输入的期望值通常由一小组N输入样本的均值来近似。层的量化可以使用平方损失来衡量近似误差,其定义可以通过一系列近似从二阶信息推导得出【推导过程参考:Markus Nagel, Rana Ali Amjad, Mart Van Baalen, Christos Louizos, and Tijmen Blankevoort. Up or down? Adaptive rounding for post-training quantization. In International Conference on Machine Learning (ICML), 2020.】并被许多应用证实了良好的表现。针对特定层的量化,假设权重是一个矩阵W,输入 X则有:

OBQ的方法是将前述的OBS应用于上述的平方误差公式。在这种情况下,该框架在理论上可以产生一个精确的贪婪解,但直接实现会有令人难以承受的的计算成本(d是层维度,即 hidden size)。OBQ 将计算成本降低到了,其中是权重矩阵的列维数。实践中,这些改进足以使我们能够在现代 DNN(参数数量达数千万)的规模上,在合理的时间内、仅使用一个 GPU 就实现精确的 OBS 贪婪解,即逐一剪枝权重,并在每个步骤之后更新所有剩余权重。将其推广到量化,即是最优大脑量化器 OBQ,其原理是迭代地量化权重,取决于它们对损失增加的影响,之后对剩余未量化的权重应用了一个封闭形式的更新,进一步降低了损失(loss)。显然,OBQ是一种后训练量化方法,并且是在每层的独立压缩,从而可以简单地从逐层结果"拼装"出最终模型。尽管对每一层都独立进行操作,但在校正诸如批归一化等基本统计信息之后,我们发现我们的方法在统一量化方面的表现与顺序方法相当。

通常的量化是对一层中的所有权重进行量化,从而没有更多的权重可以更新。但在 OBQ 则是贪婪地量化当前"最容易"的权重(按上述指标),然后调整所有剩余未量化的权重以补偿这种精度损失,从而改变它们的值。接着,选择下一个要量化的权重,依此类推。这可能会产生与最初简单取整时不同的量化赋值,从而获得更好的整体量化结果。具体来说,为了实现这一点,将(7)插入到下面算法中,以便对给定层进行迭代量化权重。

OBQ 用于存在一些异常值权重的情况下进行量化时,可能存在一些问题。由于这些异常值可能具有较高的量化误差,它们通常会最后被量化,当时只有很少的其他未量化权重可用来补偿量化异常值所造成的较大误差。当一些权重在中间更新时被推得更远离量化网格时,这种效果可能会变得更糟。采用一种简单而有效的启发式方法即可防止这种情况:一旦出现异常值(通常每层只有几次)就立即量化该异常值(例如,量化误差>Δ/2的权重,其中Δ是量化值之间的距离)。有了这种启发式方法,OBQ就成为了一种高度有效的逐层量化方案。

该方法有一个矢量化实现,可并行处理W的多行。最终,该算法可以在中等规模模型上达到合理的运行时间:例如,它可以在单个GPU上大约1小时内完全量化ResNet-50模型(2500万参数),这与其他达到最先进精度的后训练方法大致相当。然而,OBQ对于矩阵W的运行时间具有4次方的复杂性,这意味着将其应用于数十亿参数的模型将极其昂贵。

GPTQ的方法在OBQ方法之上,进行了一系列重大修改,使其能够扩展到大型语言模型,提供了三个数量级的计算加速。

OBQ按贪婪顺序量化权重,即它总是选择当前产生最小额外量化误差的权重。有趣的是,我们发现尽管这种相当自然的策略在整体上确实表现非常出色,但其改进相对于任意顺序量化通常都很小,特别是在大型高度参数化的层上。这可能是因为,虽然较少量化的权重具有较大的单个误差,但这些权重量化的时间是在后期,当时剩余的未量化权重用于补偿的数量较少,因此两者相互抵消。如我们接下来将讨论的,关于任何固定顺序可能都表现良好(尤其是在大型模型上)的这一洞见,带来了一些有趣的影响。

与原始OBQ方法独立按特定顺序量化W的行不同,我们将旨在以相同顺序量化所有行的权重,并将展示这通常会产生与原始解最终平方误差相似的结果。因此,未量化权重集F和类似的对于所有行都是相同的。下图示例了GPTQ量化的过程。在给定的步骤中,使用存储在乔里斯基Cholesky分解中的逆海森矩阵信息对连续的列组成的块(Blocks of consecutive columns,即下图中粗黑框部分)进行量化,并在该步骤结束时更新剩余的权重(蓝色部分)量化过程在每个块内部递归应用:白色的中间列目前正在被量化。

考虑到对于第i列的最终舍入决策,只受到对该列执行的更新的影响,因此对更晚的列进行的更新在此时是无关紧要的。这使得可以"懒惰(延迟)批量(lazily batch"汇总更新,从而达到更好的GPU利用率。具体而言,我们一次对B=128列( 一个batch)应用该算法,将更新限制在这些列和的对应BxB块中(见上图)只有在某个块完全处理之后,我们才使用下面这个多权重版本的等式对整个W矩阵执行全局更新,其中Q表示一组索引(即多个 q,表示已移除对应行和列(Q )的逆矩阵:

显然,这一策略并不减少理论上的计算量,但它有效解决了内存吞吐量瓶颈,能够充分利用当今 GPU 的强大批计算能力。对于超大型模型,这在实践中可提供一个数量级的加速,是 GPTQ算法的一个关键组成部分。

在现有模型的规模下,尤其是与上一步中的块更新相结合时, 数值的不精确性成为了一个重大问题。具体来说,可能会发生矩阵变为不定(indefinite)的情况【不定矩阵(indefinitematrix)是指其特征值(eigenvalues)中既包括正值又包括负值的矩阵。不定矩阵的特征值可以是实数或复数,且可以是正数、负数或零。】,我们注意到这可能会导致算法对剩余权重进行不当的强力更新(更新的方向不正确),从而导致相应层的量化质量极差。在实践中,我们观察到这种情况发生的概率随着模型大小的增加而增加:具体而言,对于超过几十亿参数的模型,几乎肯定会至少有一些层出现这种情况。

对于较小的模型,在H的对角线元素中加入一个小常数(我们总是选择对角线平均值的1%)似乎足以避免数值问题。但是,更大的模型需要一种更健壮、更通用的方法。

为解决这个问题,我们首先注意到,从 (其中表示量化第q个权重时的未量化权重集)中所需的唯一信息就是第q行,或更精确地说,是从对角线开始的该行中的元素。即下图中的橙色部分。结果是,我们可以使用更加数值稳定的方法预先计算所有这些行,而不会显著增加内存消耗。

实际上,通过(***3)对对称矩阵进行的行移,本质上就是采取了Cholesky分解,只有一个小差异,即后者将第q行除以。因此,我们可以利用优秀的Cholesky内核来计算我们将需要的中的所有信息。结合适度的阻尼(mild dampening,轻微的抑制)所得到的方法就足够健壮,可以在巨大的模型上执行而不会出现问题。额外的好处是,使用经过良好优化的Cholesky内核还可以进一步提速。

根据上述的内容,对 OBQ 算法的改动,就得到GPTQ 算:

根据上述算法,易得gptq 的代码实现(未完整版)

1. def quantize(x, scale, zero, maxq):2.     #    参考前面章节(仿射量化)的内容3.     q = torch.clamp(torch.round(x / scale) + zero, 0, maxq)4.     return scale * (q - zero)5.  6. def hessian(x, nsamples):7.     # x 包含有nsamples个样本的层输入8.     return x.matmul(x.t()) * 2 / nsamples9.     10. def gptq(H, W, blocksize=128, percdamp=0.01):11.     # H Hassian, dcol X dcol12.     # W layer.weight, drow X dcol13.     drow, dcol = W.shape14.     15.     dead = torch.diag(H) == 016.     H[dead, dead] = 117.     W[:, dead] = 018.     damp = percdamp * torch.mean(torch.diag(H))19.     Losses = torch.zeros_like(W)20.     Q = torch.zeros_like(W)21.     diag = torch.arange(dcol)22.     H[diag, diag] += damp23.     H = torch.linalg.cholesky(H)24.     H = torch.cholesky_inverse(H)25.     H = torch.linalg.cholesky(H, upper=True)26.     Hinv = H27.     28.     for i1 in range(0, dcol, blocksize):29.         i2 = min(i1 + blocksize, dcol)30.         count = i2 - i131.  32.         W1 = W[:, i1:i2].clone()33.         34.         Q1 = torch.zeros_like(W1)35.         Err1 = torch.zeros_like(W1)36.         Losses1 = torch.zeros_like(W1)37.         38.         Hinv1 = Hinv[i1:i2, i1:i2]39.  40.         for i in range(count):41.             w = W1[:, i]42.             d = Hinv1[i, i]43.             q = quantize(w.unsqueeze(1), qscale, zeropoint, maxq).flatten()44.             Q1[:, i] = q45.             Losses1[:, i] = (w - q) ** 2 / d**246.             err1 = (w - q) / d47.             W1[:, i:] -= err1.unsqueeze(1).matmul(Hinv1[i, i:].unsqueeze(0))48.             Err1[:, i] = err149.  50.         Q[:, i1:i2] = Q151.         Losses[:, i1:i2] = Losses1 / 252.         W[:, i2:] -= Err1.matmul(Hinv[i1:i2, i2:])53.

实际使用GPTQ 算法的话,可使用AutoGPTQ、ExLlama、Transformers或 GPTQ-for-LLaMA 等库来量化模型,使用 Transformers进行量化的代码如下。运行时需要注意,量化过程会比较耗时。

1. from transformers import AutoModelForCausalLM, AutoTokenizer, GPTQConfig2. model_id =“meta/llama-3-70B”3. tokenizer = AutoTokenizer.from_pretrained(model_id)4. quant_cfg = GPTQConfig(bits=4, dataset="c4", tokenizer=tokenizer)5. model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=quant_cfg)

ExLLama是 Llama 的独立实现,与 4 位 GPTQ 权重一起使用,旨在在现代 GPU 上实现快速且内存高效。由于不依赖非优化的Transformers库,它的VRAM 使用率要低得多,速度要高得多。该库的新版本为ExLlamaV2【https://github.com/turboderp/exllamav2】。

进一步阅读:

美利坚列模志(2)变革者:齐之锋刃与Gemini的蜕变创新——脑洞大模型系列

王文广:多模态大语言模型是向着通用人工智能AGI迈出的一步

大模型产业落地难,知识图谱来帮忙!

188个大模型和20个开源基础模型,《2023大模型产业发展白皮书》全面解析国内外大模型发展情况


53AI,企业落地应用大模型首选服务商

产品:大模型应用平台+智能体定制开发+落地咨询服务

承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

与创始人交个朋友

回到顶部

 
扫码咨询