type
Post
status
Published
password
date
Jul 3, 2026
slug
summary
category
人工智能
tags
LLM
icon
概述
量化颗粒度
- 逐层量化:直接把一整层神经网络当成一个整体,一整层共用一套量化参数。
- 逐通道量化:同一网络层里,每一个通道都拥有独立的量化参数。
per-token(针对激活值 x) | 每一行单独配一个量化系数。 |
per-channel(针对权重 w) | 每一列单独配一个量化系数。 |
- 逐组量化:把张量分成一个个小组,每一组共用一套量化参数。例如,激活值按 K 行划为一组,权重按 K 列划为一组。
量化对象
量化时机
量化感知
在模型训练或微调阶段,就主动向计算流程中引入量化误差,让模型在参数更新过程中提前适配低位宽的数值表示,从而抵消最终量化部署带来的精度下降。
原理
首先基于预训练好的模型获取计算图,对计算图插入伪量化算子。准备好训练数据进行训练或者微调,在训练过程中最小化量化误差,最终得到 QAT 之后对神经网络模型。QAT 模型需要转换去掉伪量化算子,为推理部署做准备。
伪量化算子
伪量化算子对数据进行量化并立即反量化,添加了类似于在量化推理过程中可能遇到的量化噪声,以模拟训练期间量化的效果。
- R:原始浮点实数(Real value),比如激活值 X、权重 W
- Q:量化后的整数(Quantized int value)
- S:Scale 缩放因子(浮点正数)
- Z:ZeroPoint 零点(整数,实数 0 被映射到整数 Z)
- :当前张量浮点数值的最大值、最小值
- :量化整数的取值范围。
前向传播
在正向传播中,FakeQuant 节点将输入数据量化为低精度(如 INT8),进行计算后再反量化为浮点数。这样,模型在训练期间就能体验到量化引入的误差,从而进行相应的调整。
每个 epoch 重新统计权重值域,更新一次量化参数;迭代内部 Scale 冻结。
每一个训练 iteration(每一批数据)重新采集当前批次激活的
反向传播
在反向传播过程中,模型需要计算损失函数相对于每个权重和输入的梯度。梯度通过 FakeQuant 节点进行传递,这些节点将量化误差反映到梯度计算中。模型参数的更新因此包含了量化误差的影响,使模型更适应量化后的部署环境。按照正向传播的公式,因为量化后的权重是离散的,反向传播的时候对求导数为 0:
这里可以使用直通估计器(Straight-Through Estimator,简称 STE)简单地将梯度通过量化传递,近似来计算梯度。这使得模型能够在前向传播中进行量化模拟,但在反向传播中仍然更新高精度的浮点数参数。STE 近似假设量化操作的梯度为 1,从而允许梯度直接通过量化节点:
原始权重为 ,伪量化之后得到浮点值
如果被量化的值在 范围内,STE 近似的结果为 1,否则为 0。这种方法使模型能够在训练期间适应量化噪声,从而在实际部署时能够更好地处理量化误差。
训练过程
量化感知训练是在训练过程中模拟量化,利用伪量化算子将量化带来的精度损失计入训练误差,使得优化器能在训练过程中尽量减少量化误差,得到更高的模型精度。量化感知训练的具体流程如下:
- 初始化:设置权重和激活值的范围和的初始值;
- 构建模拟量化网络:在需要量化的权重和激活值后插入伪量化算子;
- 量化训练:重复执行以下步骤直到网络收敛,计算量化网络层的权重和激活值的范围和,并根据该范围将量化损失带入到前向推理和后向参数更新的过程中;
- 导出量化网络:获取和,并计算量化参数s和z;将量化参数代入量化公式中,转换网络中的权重为量化整数值;删除伪量化算子,在量化网络层前后分别插入量化和反量化算子。
LLM-QAT
微调数据
利用预训练模型自生成文本作为微调数据,实验证明该方案效果优于直接使用原始训练集子集。
从词表中随机选取起始标记
<start>输入模型,模型输出首个生成 token<out1>;再将<start>+<out1>拼接后再次输入,迭代生成<out2>,重复该过程直至输出句结束符 EOS 或达到序列最大长度。QAT
- 权值与激活值
在训练的初始阶段,任何基于裁剪的方法都会导致异常高的困惑度(perplexity)分数(即> 10000),从而导致大量信息丢失,并且事实证明很难通过微调来恢复。 因此,我们选择保留这些异常值。我们为权重和激活选择对称 MinMax 量化
表示量化后的权重或激活, 表示实际的权重或激活。
- 键值缓存:对 key 和 value 的整个激活张量进行量化。
训练后量化
训练后量化的方式主要分为动态和静态两种。
动态离线量化
动态离线量化(Post Training Quantization Dynamic, PTQ Dynamic)仅将模型中特定算子的权重从 FP32 类型映射成 INT8/16 类型,主要可以减小模型大小,对特定加载权重费时的模型可以起到一定加速效果。但是对于不同输入值,其缩放因子是动态计算。动态量化的权重是离线转换阶段量化,而激活是在运行阶段才进行量化。
静态离线量化
静态离线量化(Post Training Quantization Static, PTQ Static)同时也称为校正量化或者数据集量化,使用少量无标签校准数据。其核心是计算量化比例因子,使用静态量化后的模型进行预测,在此过程中量化模型的缩放因子会根据输入数据的分布进行调整。
评估标准
- 选取验证数据集中一部分具有代表的数据作为校准数据集(Calibration);
- 对于校准数据进行 FP32 的推理,对于每一层:
- 收集 activation 的分布直方图;
- 使用不同的阈值来生成一定数量的量化好的分布;
- 计算量化好的分布与 FP32 分布的 KL divergence;
- 选取使 KL 最小的阈值作为 saturation 的阈值。
校准
除此之外,由于量化存在固有误差,还需要校正量化误差。以矩阵乘为例,,表示权重,表示激活值,表示偏置。
LLM.int8()
作者发现存在一些离群值,它们的绝对值明显更大;并且这些离群值分布在少量的几个特征中,称为离群特征;这些离群值会严重影响激活值与权重值。LLM.int8() 的思路是把这些特征拿出来单独计算,只对剩余特征做量化。
技术原理
LLM.int8()是一种采用混合精度分解的量化方法。具体方法如下所示:
- 从输入的隐含状态中,按列提取异常值(绝对值大于某个阈值,阈值通常为6) ,进而得到离群特征。
- 对离群特征进行 FP16 矩阵运算,对非离群特征进行量化,做 INT8 矩阵运算;
- 反量化非离群特征的矩阵计算结果,并与离群值矩阵乘结果相加,获得最终的 FP16 结果。
虽然 LLM.in8() 带来的性能下降微乎其微,但是这种分离计算的方式拖慢了推理速度。对于 BLOOM-176B,相比于 FP16,LLM.int8() 慢了大约 15% 到 23%;对于更小的模型(3B 和 11B),速度差距更为明显,LLM.int8() 慢了三倍以上。
GPTQ
GPTQ 的原理来自于另一个量化方法OBQ,而OBQ 实际上是对 OBS 的魔改, 而OBS则来自于OBD。
OBD
OBD 的基本思想是以模型训练损失为目标函数,迭代地选择剪枝参数使目标函数最小化。具体来说,设模型的权重为 ,损失函数为 ,则在当前权重下,模型的训练损失为。
模型剪枝实际上就是将 的某个值调整为 0,对损失函数进行泰勒展开可以估计权重调整造成的影响,对 进行泰勒展开,得到
泰勒展开式
一元函数的泰勒展开为
一个训练好的神经网络模型,其损失一般都处于权重空间中的局部极小值,因此可以认为,因此式中的第一项可以忽略。另外再忽略高阶项,则上式可以简化为
接下来我们需要介绍 OBD 的一个重要假设,为了进一步简化问题,OBD 认为 Hessian 矩阵是一个对角矩阵,其含义是,同时剪枝多个权重参数对模型精度造成的影响,等于单独剪枝每个权重对模型造成影响之和。
这样一来,最终需要求解的问题就成了
其中 就是需要剪枝的权重参数在 中的索引。其中是将 变成0,。
OBS
OBD问题
OBS不同意 OBD 的假设,认为权重剪枝之间是有关联的,所以不能简单地将 Hessian 矩阵假设为对角矩阵。
损失函数
假设对第 个参数进行剪枝,即 ,于是这里就变成了带约束的凸优化问题
其中的约束条件可以写成更一般的形式 ,这里的 是第 个值为 1 的单位向量。
推导
对于上述问题,可以使用拉格朗日乘子法来求解,定义拉格朗日函数
对 求导,并设置为 0
然后做如下变换
计算流程
使用公式 (5) 可以找到当前最优的剪枝参数,然后再使用公式 (4) 计算所有权重的修正量,这就完成了一次迭代,交替应用 (4)(5),即可不断找到需要剪枝的权重参数,直到达到剪枝的目标。
- 第一步:确定要剪掉哪个权重(求 )利用图片中的公式 (5),计算所有权重的显著性得分(Saliency):分数 = 。找出得分最小的那一个,它的下标就是 。
- 第二步:提取对应的数据取出该权重的值 ,以及逆矩阵中对应的第 列 和对角线元素 。
- 第三步:更新权重新权重 。此时,第 个权重会刚好被精准地减成 0,而其他权重则获得了完美的补偿。
假设我们一共有 3 个权重 ,现在决定剪掉第 2 个权重(即 )。根据公式:这里的 是海森逆矩阵的整个第 2 列,它包含 3 个元素:。我们将标量系数乘进去,完整的 向量长这样:当我们将这个修正量向量 加到原来的权重向量 上时:
OBC
OBS问题
OBS 在每次迭代中,都涉及到计算 Hessian 矩阵的逆矩阵,设权重参数总量为 ,则计算 Hessian 矩阵的逆矩阵的时间复杂度为
OBC本质上是对W的每一行生成海森矩阵,然后对每一行进行剪枝
损失函数
针对每一层,定义剪枝损失
其中 表示某层的前向函数,分别表示原始权重和剪枝后的权重矩阵,表示矩阵形式的输入。更具体的说,对于线性层或者卷积层,前向传播函数可以表示为权重和输入矩阵相乘的形式 ,再考虑将 定义为平方损失函数,于是寻找最优压缩方案就变成了如下优化问题
更进一步,若将权重矩阵按行分解,则剪枝损失又可以表示为按行求和的形式
设 ,则 ,并且有
其中 表示 的列数, 表示 的行数或者 的列数。
推导
由于每次迭代我们都只对某一行中的一个权重进行剪枝,也就是说只影响一行的剪枝损失,所以在每次迭代过程中可以认为 ,其中 就是被剪枝权重所在的行索引。基于这样的事实,我们可以推导出 Hessian 矩阵为
为什么对某一行中的一个权重进行剪枝,也就是说只影响一行的剪枝损失。
写成矩阵形式为
设 表示中间过程的 ,其中 表示未量化的权重索引集合, 表示 去掉第 行和第 列后的矩阵,那么有以下公式成立
计算流程
- 根据公式 (6) 计算初始 Hessian 矩阵 ,并为权重矩阵的每一行复制一份;
- 遍历每一行,根据公式 (5) 计算出最佳权重索引 ,其中 为所在行索引,对应的 Hessian 矩阵是 ;
- 应用公式 (4) 计算当前行的权重修正量,然后更新权重矩阵;
- 更新第 行的 Hessian 矩阵 ,并根据公式 (7) 更新 Hessian 逆矩阵;
- 重复步骤 2-4,直到达到剪枝目标。

OBQ
剪枝和量化都涉及到对权重值的修改,只不过剪枝是直接将权重设置为 0,而量化则是降低权重值的数值精度,比如将 fp32 数值变成 fp16,int8,int4 等,因此 OBS 的思想可以很自然的应用到量化上来,这种方法也被称为 OBQ
对于 OBQ 方法,我们仍然从公式 (2) 出发
在 OBS 中,对应的约束条件为 ,也就是说,索引为 的权重修改量为 ,而在量化条件下
其中 表示缩放系数, 表示零点偏移,表示最大整数值。
于是剪枝操作中的约束条件 在这里就变成了 。再次使用拉格朗日乘子法求解公式 (2) 的最小化问题,可以得到
这里的 表示一行权重的修正量,初始大小为 。交替使用公式 (8)(9) 就得到了量化版本的 OBC 算法 OBQ。
GPTQ
GPTQ 将权重分组(如:128列为一组)为多个子矩阵(block)。对某个 block 内的所有参数逐个量化,每个参数量化后,需要适当调整这个 block 内其他未量化的参数,以弥补量化造成的精度损失。
1. 初始化阶段 (Initialization)
- :创建与原权重大小相同的矩阵,用于存放最终的量化结果。
- :创建一个临时矩阵,用于记录当前分块内的量化误差。
- :对逆海森矩阵进行 Cholesky 分解。这一步非常精妙,它将复杂的矩阵求逆简化为三角矩阵计算,不仅提升了数值稳定性,还让后续的逐列补偿可以按顺序高效进行。
Cholesky 分解把一个对称正定矩阵(可以理解为矩阵世界里的“正实数”),唯一地拆解为一个下三角矩阵和它的转置矩阵(上三角矩阵)相乘的形式。
2. 分块与内部循环 (Block-wise Loop)
算法以步长 将权重矩阵的列分成多个块。在每一个块内部,执行以下循环:
- :将当前第 列的权重直接进行普通的低比特量化(如量化为 INT4)。
- :计算由于量化产生的精度损失(误差),并除以海森矩阵对角线元素进行缩放。
- :核心补偿步骤。利用刚才计算出的误差和海森矩阵信息,立即更新当前分块内后面几列还没有被量化的权重。通过调整它们,把第 列产生的误差对冲掉。
3. 跨块全局更新 (Global Update)
- :当一个块(共 列)全部量化并计算完误差后,统一用累积的误差矩阵 ,对该分块后面剩余的所有权重列进行一次大矩阵乘法更新。
其中由拼接起来,但由于此时你没有更新块外的列( 往后),那么对于块外的列来说,第 列量化产生的误差,还没有传导过去,依然处于“欠债”状态。
AWQ
AWQ发现,大语言模型中并所有权重都同等重要。仅保护1%的显著权重就能大幅减少量化误差。为了识别显著的权重通道,我们应该参考激活分布,而非权重分布。为了避免硬件效率低下的混合精度量化,我们从数学上推导得出,放大显著通道可以减少量化误差。
识别显著权重
在线性乘法中,输入 的每一个通道(列)都直接与权重 的对应通道(行)相乘。为了找出哪个通道最重要,AWQ 会计算输入激活值 在每个通道上的平均绝对值(Mean Absolute Value):
如果某个通道的 数值非常大(远超其他通道),说明这个通道就是离群值通道(Outlier Channel)。
权重缩放
- 普通量化
- 权重缩放
原理分析
其中 ,最终误差就是 对比 .