大语言模型量化技术(一):基本原理
什么是量化
大语言模型的参数量持续增长,推理所需的显存也越来越高。一个 70B 参数的模型,若用 FP16 保存权重,仅模型参数就要占 140 GB 显存,通常需要两张 80 GB 显卡才能装下。对大量显存需求限制了模型部署,也让推理成本居高不下。降低显存占用的一条路径是用更少的 bit 表示每个参数,把原本占两个字节的 FP16 权重使用一个字节(8 bit)甚至 4 bit 来表示。这样模型参数的显存占用能大幅下降,同时也降低了内存带宽压力,甚至可能加速推理。使用更少的字节数来表示一个参数,必然会引入误差,使用精心设计的方法可以实现在可接受的精度损失下显著降低显存占用。而这正是模型量化的基本思想。
量化的原理是先把浮点数的取值范围切成若干等宽区间,每个区间对应一个整数值。存储时只保存整数和区间宽度信息,计算时再把整数还原成浮点数。整个过程会引入少量误差,但能显著降低存储和传输成本。比如将浮点数范围 映射到 8 位整数范围 ,量化步长约为 ,下图为浮点数和整数的映射关系:
量化使用整数来近似浮点数,这会引入误差,比如在 区间内的浮点数都会被映射到整数 ,从整数恢复浮点数时,所有这些浮点数都会被还原为 。因此,量化是不可逆的:解码时只能得到整数对应的代表值,而无法还原出原始浮点数。
量化的可行性
大模型的参数通常都比较小且分布在零周围,对这样一个小的浮点数区间进行量化引入的误差相对较小。且虽然会引入一些误差,但量化的核心思想建立在一个观察之上:神经网络不靠单个参数的精确值工作,而是靠大量参数共同协作。一个权重的最后几位小数对最终输出的影响微乎其微。因此,用更少的 bit 来近似每个参数,对模型精度的影响通常是可接受的。
下面是我用 Qwen3-0.6B 统计的各层权重分布。表中 P50、P90、P95、P99 分别表示权重绝对值的不同百分位数——例如 P99 为 0.08,意味着该层 99% 的权重绝对值不超过 0.08。
Layer #Params Min Max Mean Std |Abs|P50 |Abs|P90 |Abs|P95 |Abs|P99
------------------------------------------------------------------------------------------------------------
embed 311,165,952 -0.3184 15.3125 -0.00019 0.03005 0.01904 0.04834 0.05786 0.07715
0 15,730,944 -1.4922 96.5000 0.00009 0.04135 0.01807 0.04761 0.05859 0.08496
1 15,730,944 -3.8594 44.5000 0.00012 0.03436 0.01758 0.04883 0.06177 0.09375
2 15,730,944 -0.8203 44.0000 0.00013 0.03314 0.01709 0.04688 0.05884 0.08838
3 15,730,944 -1.1953 23.0000 0.00015 0.03270 0.01770 0.04834 0.06030 0.08887
4 15,730,944 -1.1172 41.7500 0.00014 0.03427 0.01758 0.04761 0.05884 0.08545
5 15,730,944 -1.5469 34.0000 0.00016 0.03373 0.01770 0.04663 0.05737 0.08203
6 15,730,944 -1.1953 24.2500 0.00015 0.03202 0.01697 0.04565 0.05640 0.08105
7 15,730,944 -1.2031 22.2500 0.00018 0.03177 0.01709 0.04541 0.05566 0.07910
8 15,730,944 -0.6719 9.5000 0.00017 0.03114 0.01685 0.04517 0.05591 0.08057
9 15,730,944 -0.4707 13.5000 0.00018 0.03267 0.01660 0.04517 0.05640 0.08252
10 15,730,944 -0.5391 22.7500 0.00019 0.03245 0.01636 0.04468 0.05542 0.08105
11 15,730,944 -1.1172 6.8750 0.00020 0.03283 0.01587 0.04419 0.05518 0.08203
12 15,730,944 -3.6875 21.8750 0.00021 0.03344 0.01611 0.04346 0.05396 0.07861
13 15,730,944 -3.8906 6.7500 0.00022 0.03296 0.01544 0.04175 0.05200 0.07617
14 15,730,944 -4.7188 13.0000 0.00026 0.03393 0.01489 0.04053 0.05054 0.07373
15 15,730,944 -6.5625 25.2500 0.00029 0.03813 0.01501 0.04102 0.05103 0.07471
16 15,730,944 -0.8281 11.0625 0.00028 0.03642 0.01483 0.04126 0.05176 0.07861
17 15,730,944 -10.5000 21.6250 0.00041 0.04645 0.01532 0.04199 0.05225 0.07715
18 15,730,944 -9.8125 19.6250 0.00040 0.04477 0.01526 0.04175 0.05225 0.07715
19 15,730,944 -1.1328 22.1250 0.00047 0.05169 0.01550 0.04297 0.05371 0.08057
20 15,730,944 -1.8203 25.1250 0.00047 0.05443 0.01636 0.04541 0.05664 0.08398
21 15,730,944 -2.0156 26.8750 0.00057 0.06634 0.01733 0.04785 0.05957 0.08740
22 15,730,944 -0.5977 33.7500 0.00063 0.07224 0.01807 0.04956 0.06128 0.08838
23 15,730,944 -2.4688 46.5000 0.00069 0.08098 0.01843 0.04980 0.06128 0.08643
24 15,730,944 -2.8438 69.0000 0.00087 0.10738 0.01855 0.05054 0.06226 0.08838
25 15,730,944 -4.7500 89.5000 0.00106 0.13881 0.01868 0.05078 0.06250 0.08887
26 15,730,944 -3.9062 106.5000 0.00122 0.16400 0.01831 0.05005 0.06201 0.09033
27 15,730,944 -1.4062 192.0000 0.00118 0.13839 0.01868 0.04858 0.05933 0.08301
从这张表可以读出两层信息。第一,各层中 99% 的权重绝对值都小于 0.1,均值接近零,标准差也很小,绝大多数权重密集分布在零附近的狭窄区间内。第二,从 Max 列可以看出,虽然主体分布很集中,少数异常值可以达到几十甚至上百。这些离群点是量化的主要麻烦,它们拉大了数值范围,迫使量化步长变大,反而伤害了大多数正常权重的表示精度。
如果将某组权重的量化范围控制在 ,使用对称 INT8 的 255 个量化级别表示该区间,量化步长约为 0.000787,最大误差约为 0.000393。相对于 0.1 量级的权重,这一误差较小,但对于 0.001 量级的权重,误差就相对较大了,最大误差可达 39.4%。
量化后的每个参数都和原值存在差异。量化之所以可行,不是因为误差消失了,而是神经网络对一定范围内的数值扰动本来就有容忍度。这种容忍度来自两个方面。第一,量化时有些权重被向上舍入,另一些被向下舍入,大量乘加之后,误差可能部分抵消,而不是始终沿同一方向累积。第二,语言模型的输出由 logits 的相对大小决定。假设最高 logit 明显高于其他候选项,小幅数值扰动不会改变排序,模型仍会生成同一个 token,只有当几个候选项本来就很接近时,量化噪声才更容易改变结果。
这种对误差的容忍度是神经网络的一个重要特性,它使得量化成为可行的优化手段,能够在不显著损失模型性能的前提下,降低存储和计算成本。
量化的收益
在理解量化的更多细节之前,先直观感受一下它能省下多少资源。下表列出了常见数值格式下模型权重的理论存储体积。为方便对比,采用十进制单位(),7B 和 70B 分别按 70 亿和 700 亿个参数计算。
| 数值格式 | 位宽 | 每参数字节数 | 7B 权重 | 70B 权重 | 相对 FP16 |
|---|---|---|---|---|---|
| FP16 / BF16 | 16 bit | 2 B | 14 GB | 140 GB | 1 倍 |
| FP8 / INT8 | 8 bit | 1 B | 7 GB | 70 GB | 1/2 |
| INT6 | 6 bit | 0.75 B | 5.25 GB | 52.5 GB | 3/8 |
| INT4 | 4 bit | 0.5 B | 3.5 GB | 35 GB | 1/4 |
| INT3 | 3 bit | 0.375 B | 2.625 GB | 26.25 GB | 3/16 |
| INT2 | 2 bit | 0.25 B | 1.75 GB | 17.5 GB | 1/8 |
从 FP16 切到 INT4,权重的理论体积直接降到四分之一。换句话说,原来需要两张 80 GB 显存显卡才能装下的 70B 模型,量化后用一张 48 GB 显卡就有余量。
除了节省显存,量化还降低了读取权重时的内存带宽压力。大语言模型逐 token 解码时,每一步都要把权重从显存搬到计算单元。位宽减半后,单位时间内搬运的数据量就减半。即使计算本身没有变快,整体推理也可能因为等待数据的时间减少而加速。
有些硬件还支持低位宽的矩阵乘法运算。比如 NVIDIA 的 Hopper 架构 GPU,INT8 计算的吞吐量是 FP16 的两倍,INT4 计算的吞吐量是 FP16 的四倍。量化后,推理速度可能不止因为内存带宽减少而加快,还可能因为计算本身更快而加速。
量化与反量化
接下来看量化具体是怎么把浮点数变成整数的,以及如何在计算时把整数还原成浮点数,前者称为量化(quantization),后者称为反量化(dequantization)。量化把浮点数的取值范围切成若干等宽区间,每个区间对应一个整数值。反量化则是在计算时把整数还原成浮点数。
假设浮点数的范围是 ,我们希望用 8 位整数来表示它,其中 -127 对应 -0.1,127 对应 0.1,为了保持对称性,-128 被舍弃不用。相邻两个整数值之间的浮点数间隔为:
此时给定一个浮点数 ,可以通过下式映射为整数 :
其中 表示就近取整, 表示把结果限制在 范围内。
比如 :
解码时,用同样的 把 还原为 :
与原值 相比,误差约为 ,这就是单次量化引入的误差。
通过这个例子可以看出,量化把许多不同的浮点数压到同一个整数值上,解码时只能得到这个离散值对应的代表值,因此天生不可逆。整体流程可以概括为:用整数存储和传输以节省空间,计算时再还原成浮点数,全程只引入少量误差——而神经网络对这种误差通常有容忍度。
上面的例子假设数值范围关于 0 对称,但真实数据不一定都这么规整。接下来我们看,当分布不对称时,应该如何选择量化映射。
对称量化与非对称量化
量化的核心是用整数近似浮点数。第一个需要决定的问题是:整数范围是否关于 0 对称?这对应两种量化方式。
对称量化(symmetric quantization)
对称量化把浮点数的取值范围设为关于 对称。对于 位有符号整数,整数范围为 。以 INT8 为例,量化值通常取 。虽然有符号 INT8 的完整范围是 ,但对称量化常舍弃 ,让正负两侧拥有相同数量的量化级别。
设浮点数的取值范围为 。对称量化要求量化区间关于 0 对称,因此实际的量化区间为 ,其中 。
则量化步长 的计算公式为:
量化和反量化的公式为:
对称量化的优势是计算简单,只需要维护一个 scale(量化步长)即可,很适合均值接近 的权重。但如果原始数值明显偏向正数一侧(比如范围是 ),为了覆盖最大值 ,量化的范围必须扩展到 ,这时负数一侧会保留大量用不到的量化级别,浪费了本就有限的表示能力。
非对称量化(asymmetric quantization)
非对称量化的量化范围不要求关于 对称,而是根据实际数据分布来决定。它通过引入零点(zero point)来平移整数范围,使得整数的最小值对应浮点数的最小值,整数的最大值对应浮点数的最大值。
非对称量化需要使用整数 来表示浮点数的范围 。量化步长 的计算公式为:
在非对称浮点范围 做量化时,如果还简单地采用 的方式映射到整数范围,则会得到如下的映射关系:
但实际上我们希望将浮点数的范围映射到整数范围 ,也就是希望浮点数的最小值 对应整数 ,最大值 对应整数 ,如下图所示:
为了实现这个映射,我们需要对整数范围进行平移,使得整数 对应浮点数的最小值。这只需要对 的结果加上一个偏移量 ,这个偏移量就是浮点数 0 对应的整数值。
这里零点的计算公式为:
于是量化公式变为:
反量化的公式为:
代入前面的例子,,零点 。因此浮点数 对应整数 ,浮点数 对应整数 ,浮点数 对应整数 。
对于 5.5 这个浮点数,量化后得到整数:
反量化后得到浮点数:
以上就是非对称量化的基本原理。对称量化的原理相对容易理解:如果浮点数的分布关于 0 对称,使用对称量化是最简单的选择。非对称量化则更灵活,能够更好地适应偏移的分布,但需要额外计算零点,算法实现也更复杂。
小结
对称量化和非对称量化在实战中都会被用到,选择哪种方式取决于数据的分布特性。模型参数通常关于 0 对称,因此权重量化常用对称量化。而激活值经过 ReLU 之后,通常是非负的,分布明显偏向一侧,因此经过 ReLU 的激活量化常用非对称量化。
| 方法 | 零点 | 适合的分布 | 特点 |
|---|---|---|---|
| 对称量化 | 固定为 | 以 为中心的权重或激活 | 计算简单,可能浪费一侧范围 |
| 非对称量化 | 由数据范围决定 | 明显偏向一侧的激活 | 范围利用率高,零点处理更复杂 |
均匀量化与非均匀量化
前面的讨论都假设量化级别之间的间隔处处相同,浮点数的取值范围被切成若干等宽区间,每个区间对应一个整数值。这种方式称为均匀量化(uniform quantization)。但数据的分布往往具有非均匀性,某些区间的数值密集,其他区间则稀疏。以模型参数为例,权重大多集中在 0 附近,离 0 越远权重越少。为了更好地利用有限的量化级别,可以在密集区域放置更多的量化点,在稀疏区域放置较少的量化点——这就是非均匀量化(non-uniform quantization)的思路。
均匀量化(uniform quantization)
均匀量化把整个数值范围等距切分,相邻量化值之间的间隔处处相等。前面介绍的仿射量化就是均匀量化——所有区间由同一个 scale 决定宽度。
均匀量化是目前最主流的选择,原因很务实。第一,它只需要保存一个 scale(和可选的 zero point),元数据极少。第二,通用矩阵乘法硬件(如 GPU 的 Tensor Core)天然适合等距的整数运算,均匀量化可以直接利用这些硬件单元,而非均匀量化往往需要额外的查表或解码步骤。第三,在大多数场景下,均匀量化的精度损失已经足够小,引入非均匀带来的额外复杂度未必值得。
当然,均匀量化也有局限:它假设数值在所有区间内的分布是均匀的。如果数据集中在某个子区间,均匀分配量化点就意味着密集区域和稀疏区域获得相同的表示精度——密集区域可能精度不足,稀疏区域则在浪费量化级别。
非均匀量化(non-uniform quantization)
非均匀量化不再要求量化值等距排列,而是根据数据分布灵活分配。直观上,数值密集的区域放置更多量化点(间隔更小),稀疏区域放置较少量化点(间隔更大)。
实现非均匀量化的一种常见方式是对权重做聚类——比如用 k-means 将所有权重聚成 个簇,每个簇的中心作为一个量化值,存储时只保存簇编号( 位),计算时使用整数查表得到对应的浮点数还原。
非均匀量化有机会在相同位宽下获得更低的量化误差。代价是通常需要码本或查表操作,不一定能直接利用通用矩阵乘法硬件,推理速度可能不如均匀量化。
量化粒度
量化参数(scale 和 zero point)不一定由整个模型共享,量化粒度决定了每个 scale 覆盖多少数据范围。粒度越粗,元数据越少、实现越简单,误差越大。粒度越细,scale 越贴合局部分布,误差通常也越小。常见的粒度有三种:逐张量量化(per-tensor)、逐通道量化(per-channel)和分组量化(per-group)。
逐张量量化(Per-Tensor Quantization)
这是最粗的粒度,整个张量共享一组量化参数。对于线性层的权重矩阵 ,会将整个矩阵的最小值和最大值作为量化范围,计算出一个 scale 和一个 zero point。
它的元数据最少、计算最简单,但一个异常值就可能拉大整个张量的量化范围,拖累所有数值的精度。如果一层只有一个权重张量,比如全连接层,逐张量量化也常被称为逐层量化 (per-layer quantization)。
逐通道量化(Per-Channel Quantization)
线性层的权重矩阵形状为 ,其中 是输出通道数, 是输入维度。逐通道量化以权重矩阵的每个输出通道为单位进行量化。每个通道独立计算最小值和最大值,得到各自的 scale 和 zero point。
相比逐张量量化,逐通道量化可以适应不同通道之间数值范围的差异,例如某些通道的权重幅度偏大,另一些通道的权重幅度偏小。各自独立量化后,每个通道都能用适合自身的步长和范围,精度会明显优于逐张量量化。代价是实现更复杂,算子需要按通道应用不同的 scale。需要保存 组量化元信息。
分组量化(Per-Group Quantization)
把每个通道沿输入维度继续切成若干组,每组独立量化。常见的 group size 有 32、64 和 128。组越小,scale 越贴合局部分布,量化误差越低,但需要存储的 scale 也越多。
量化粒度对比
| 粒度 | scale 数量 | 精度 | 元数据与实现成本 |
|---|---|---|---|
| Per-tensor | 每个张量一组 | 较低 | 最低 |
| Per-channel | 每个通道一组 | 较高 | 中等 |
| Per-group | 每个小组一组 | 通常最高 | 最高 |
更细的粒度通常能降低误差,但真实速度还取决于 scale 的读取方式、反量化逻辑和量化算子的内存布局。选择量化粒度,本质上是在精度和速度之间做权衡。
粒度决定了一个 scale 覆盖多大数据范围。另一方面,我们还可以选择对谁做量化——权重、激活,还是 KV Cache。
量化对象
大语言模型推理时,显存中不只有权重,还有激活值和 KV Cache,这些对象的数据分布各不相同,使用的量化方法也不一样。下面将详细介绍不同对象的量化特点和难点。
权重量化(Weight Quantization,WQ)
权重是静态的,在模型训练完成后就固定了。它们的分布可以离线统计,量化参数也可以提前计算好。权重量化通常是最容易落地的量化方式。它能缩小模型文件,减少权重显存占用和读取权重所需的带宽。
模型参数在初始化时通常采用均值为 0 的正态分布,训练过程中 AdamW 优化器的权重衰减(weight decay)也会约束权重的幅度,因此权重分布始终保持在零附近的狭窄区间内。对于这种分布,权重量化通常使用对称量化。
下面是 Qwen3-0.6B 模型的权重分布统计表。可以看到,绝大多数权重都集中在零附近,均值接近零,标准差也很小,但少数异常值可以达到几十甚至上百。
Layer #Params Min Max Mean Std |Abs|P50 |Abs|P90 |Abs|P95 |Abs|P99
------------------------------------------------------------------------------------------------------------
embed 311,165,952 -0.3184 15.3125 -0.00019 0.03005 0.01904 0.04834 0.05786 0.07715
0 15,730,944 -1.4922 96.5000 0.00009 0.04135 0.01807 0.04761 0.05859 0.08496
1 15,730,944 -3.8594 44.5000 0.00012 0.03436 0.01758 0.04883 0.06177 0.09375
2 15,730,944 -0.8203 44.0000 0.00013 0.03314 0.01709 0.04688 0.05884 0.08838
3 15,730,944 -1.1953 23.0000 0.00015 0.03270 0.01770 0.04834 0.06030 0.08887
4 15,730,944 -1.1172 41.7500 0.00014 0.03427 0.01758 0.04761 0.05884 0.08545
5 15,730,944 -1.5469 34.0000 0.00016 0.03373 0.01770 0.04663 0.05737 0.08203
6 15,730,944 -1.1953 24.2500 0.00015 0.03202 0.01697 0.04565 0.05640 0.08105
7 15,730,944 -1.2031 22.2500 0.00018 0.03177 0.01709 0.04541 0.05566 0.07910
8 15,730,944 -0.6719 9.5000 0.00017 0.03114 0.01685 0.04517 0.05591 0.08057
9 15,730,944 -0.4707 13.5000 0.00018 0.03267 0.01660 0.04517 0.05640 0.08252
10 15,730,944 -0.5391 22.7500 0.00019 0.03245 0.01636 0.04468 0.05542 0.08105
11 15,730,944 -1.1172 6.8750 0.00020 0.03283 0.01587 0.04419 0.05518 0.08203
12 15,730,944 -3.6875 21.8750 0.00021 0.03344 0.01611 0.04346 0.05396 0.07861
13 15,730,944 -3.8906 6.7500 0.00022 0.03296 0.01544 0.04175 0.05200 0.07617
14 15,730,944 -4.7188 13.0000 0.00026 0.03393 0.01489 0.04053 0.05054 0.07373
15 15,730,944 -6.5625 25.2500 0.00029 0.03813 0.01501 0.04102 0.05103 0.07471
16 15,730,944 -0.8281 11.0625 0.00028 0.03642 0.01483 0.04126 0.05176 0.07861
17 15,730,944 -10.5000 21.6250 0.00041 0.04645 0.01532 0.04199 0.05225 0.07715
18 15,730,944 -9.8125 19.6250 0.00040 0.04477 0.01526 0.04175 0.05225 0.07715
19 15,730,944 -1.1328 22.1250 0.00047 0.05169 0.01550 0.04297 0.05371 0.08057
20 15,730,944 -1.8203 25.1250 0.00047 0.05443 0.01636 0.04541 0.05664 0.08398
21 15,730,944 -2.0156 26.8750 0.00057 0.06634 0.01733 0.04785 0.05957 0.08740
22 15,730,944 -0.5977 33.7500 0.00063 0.07224 0.01807 0.04956 0.06128 0.08838
23 15,730,944 -2.4688 46.5000 0.00069 0.08098 0.01843 0.04980 0.06128 0.08643
24 15,730,944 -2.8438 69.0000 0.00087 0.10738 0.01855 0.05054 0.06226 0.08838
25 15,730,944 -4.7500 89.5000 0.00106 0.13881 0.01868 0.05078 0.06250 0.08887
26 15,730,944 -3.9062 106.5000 0.00122 0.16400 0.01831 0.05005 0.06201 0.09033
27 15,730,944 -1.4062 192.0000 0.00118 0.13839 0.01868 0.04858 0.05933 0.08301
激活量化(Activation Quantization,AQ)
激活值是动态的,随着输入数据的不同而变化,它们的分布相比权重更难预测,量化参数无法提前计算好。而且对于不同的输入,激活值的分布可能差异很大。为了保证量化精度,通常需要在推理时动态计算量化参数。
下面是 Qwen3-0.6B 中部分层的激活值分布情况:
Hook #Elements Min Max Mean Std |Abs|P50 |Abs|P90 |Abs|P95 |Abs|P99
----------------------------------------------------------------------------------------------------------------------------------
L0.input_layernorm 83,968 -3.2344 2.2500 -0.00393 0.21117 0.12061 0.27734 0.34180 0.68750
L0.mlp 83,968 -1.3906 3.8438 0.00036 0.13545 0.08008 0.20801 0.25781 0.37500
L0.mlp.act_fn 251,904 -0.2793 3.9062 -0.10261 0.18652 0.18066 0.27539 0.27930 0.55469
L0.mlp.down_proj 83,968 -1.3906 3.8438 0.00036 0.13545 0.08008 0.20801 0.25781 0.37500
L0.mlp.gate_proj 251,904 -6.7500 3.9844 -0.50819 0.71414 0.45508 1.39062 1.92969 2.92188
L0.mlp.up_proj 251,904 -3.2188 3.3281 -0.00643 0.33383 0.19727 0.52734 0.64844 0.94141
L0.post_attention_layernorm 83,968 -11.5000 2.5781 -0.01011 0.46711 0.22461 0.60938 0.75781 1.15625
L0.self_attn 83,968 -1.4688 2.9844 0.00110 0.18536 0.08154 0.25586 0.34570 0.64844
L0.self_attn.k_norm 83,968 -456.0000 476.0000 0.05652 21.09000 0.89844 3.40625 4.75000 60.25000
L0.self_attn.k_proj 83,968 -3.9844 5.1875 0.00296 0.41711 0.14648 0.61719 0.89062 1.60938
L0.self_attn.o_proj 83,968 -1.4688 2.9844 0.00110 0.18536 0.08154 0.25586 0.34570 0.64844
L0.self_attn.q_norm 167,936 -32.7500 37.0000 -0.01111 2.29004 0.76953 2.59375 3.50000 7.71875
L0.self_attn.q_proj 167,936 -5.7188 5.0625 -0.00239 0.36771 0.13574 0.40625 0.55078 1.47656
L0.self_attn.v_proj 83,968 -1.3984 1.3438 -0.00229 0.14925 0.07422 0.24023 0.30273 0.46484
L5.input_layernorm 83,968 -15.0625 18.2500 0.00390 0.72366 0.27734 0.77344 1.00000 2.07812
L5.mlp 83,968 -1.8125 4.3750 0.00144 0.26729 0.15723 0.42578 0.53516 0.79688
L5.mlp.act_fn 251,904 -0.2793 7.0312 -0.11501 0.27846 0.23730 0.27734 0.39453 1.08594
L5.mlp.down_proj 83,968 -1.8125 4.3750 0.00144 0.26729 0.15723 0.42578 0.53516 0.79688
L5.mlp.gate_proj 251,904 -6.4688 7.0312 -0.88920 0.99305 0.89844 2.17188 2.62500 3.50000
L5.mlp.up_proj 251,904 -4.2500 4.5000 0.00760 0.59836 0.36523 0.95703 1.18750 1.75000
L5.post_attention_layernorm 83,968 -16.5000 20.7500 0.02429 0.86202 0.40234 1.06250 1.32812 2.17188
L5.self_attn 83,968 -1.7422 1.4141 0.00491 0.17603 0.10254 0.27734 0.34961 0.54297
L5.self_attn.k_norm 83,968 -119.0000 105.0000 0.05862 8.71167 0.89062 3.54688 5.56250 57.50000
L5.self_attn.k_proj 83,968 -11.3750 5.8750 -0.00446 0.75661 0.38281 1.21875 1.58594 2.43750
L5.self_attn.o_proj 83,968 -1.7422 1.4141 0.00491 0.17603 0.10254 0.27734 0.34961 0.54297
L5.self_attn.q_norm 167,936 -26.2500 23.6250 0.00557 2.21857 0.55469 2.40625 4.09375 10.43750
L5.self_attn.q_proj 167,936 -7.6562 9.2500 -0.00818 0.79544 0.34375 1.09375 1.59375 3.12500
L5.self_attn.v_proj 83,968 -2.7969 3.6250 0.00860 0.45993 0.27539 0.73828 0.91797 1.37500
可以看到,激活值的分布范围比权重大得多。权重的 P99 分位值也都小于 0.1,而激活的 P99 分位值最小的也有 0.375,最大的甚至达到 60.25。激活函数的输出会明显偏向于正数,尤其是经过 ReLU 之后,负数会被截断为 0。
激活值的范围更大,这会增大量化步长,从而增加舍入误差。激活值经过非线性函数后,分布可能会偏向一侧,使用对称量化可能会浪费一侧的表示范围。激活值的以上特点使得激活量化比权重量化更难。
KV Cache 量化
自回归生成会为每一层保存历史 token 的 KV Cache,以便在下一步生成时直接使用,而不必重新计算注意力。KV Cache 的大小随上下文长度线性增长,它们本质上是激活值的缓存,因此分布特性和量化难点与激活类似。
长上下文或高并发推理中,KV Cache 可能比模型权重消耗更多显存。因此对 KV Cache 进行量化可以显著降低显存占用和带宽需求,并支持更长的上下文和更高的并发。将 KV Cache 从 FP16 量化为 INT8,理论上可以降低近一半的显存占用,量化为 INT4 则可以降到四分之一。
小结
在 LLM 相关论文或开源项目中,常常使用 W8A16、W4A16、W8A8、W4A8、KV8、KV4 等缩写来表示不同对象的量化位宽。其中 W 表示权重,A 表示激活,KV 表示 KV Cache,数字表示量化位宽。比如 W8A16 表示权重量化为 8 位,激活量化为 16 位。
下面是不同对象量化的特点总结:
| 对象 | 常见写法 | 主要收益 | 主要难点 |
|---|---|---|---|
| 权重 | W8A16、W4A16 | 模型文件、权重显存和读取带宽 | 敏感通道与异常权重 |
| 激活 | W8A8、W4A8 | 激活显存和低精度计算 | 分布随输入变化,异常值明显 |
| KV Cache | KV8、KV4 | 长上下文与高并发下的缓存显存 | 持续读写,误差影响注意力 |
量化误差的三大来源
无论选择哪种量化路线,精度损失最终都来自三个基本来源:截断、舍入和误差传播。这一节将从数学上分析它们的误差来源,这有助于理解量化算法的设计思路。
截断误差
量化范围是有限的,当原始数值超出 时,只能被截断到边界:
绝对值明显偏大的数值被称为异常值(outlier),少数异常值让范围选择变得棘手。如果把范围扩得很大来容纳异常值,量化步长 就会增大,大多数正常数值的舍入误差也跟着上升;如果缩小范围来控制步长,异常值又会被直接截断。如何处理异常值是量化算法设计的关键点之一,后文提到的很多量化算法都会对异常值做特殊处理。
舍入误差
落在量化范围内的数值,仍然要对齐到最近的离散点。对于均匀量化,单次舍入误差满足:
在理想化的均匀分布假设下,如果数值没有被截断,采用最近邻舍入时的均方误差约为:
这里的均方误差用来衡量量化后数值与原始数值的偏差,这很符合直觉:量化步长越小,舍入误差越小。但步长越小,相同位宽下能覆盖的数值范围就越窄,截断误差可能会增大。量化算法需要在截断误差和舍入误差之间做权衡。
误差传播
一层中的小误差会成为下一层的输入。对线性层 ,若权重和输入分别产生扰动 和 ,量化后的输出为:
展开后输出误差是:
第一项来自权重量化,第二项来自激活量化,最后一项是两者的交叉项。误差传到后续层后,还会被权重矩阵、非线性函数、残差路径和注意力分布继续变换。
这解释了两个常见现象:只量化权重通常比同时量化权重和激活容易;每一层局部误差都很小,不代表最终输出误差一定小。因此量化算法很少只看权重本身的均方误差,而是关注在测试数据集上测量得到的困惑度和模型在下游任务上的表现。
量化的时机
对模型进行量化的时机不同,策略也不同。按量化介入时机可以分成两条路线:训练后量化(Post-Training Quantization, PTQ)和量化感知训练(Quantization-Aware Training, QAT)。
训练后量化(Post-Training Quantization, PTQ)
PTQ 是在模型训练完成后进行量化,最简单的做法是统计权重或校准样本的数值范围,直接算好 scale 和 zero point,然后完成量化。PTQ 成本低,拿到现成模型后可以快速部署,是当前大模型权重量化的主流路线。但位宽越低、激活量化越激进,单纯依靠校准就越难维持精度。
PTQ 不等于"完全不需要数据"。统计激活范围通常需要一小批有代表性的校准样本。更复杂的量化方法会逐层修复量化误差,比如先量化第一层,再调整第二层的参数来补偿第一层的误差,依次类推,只是不进行常规的全量训练。
量化感知训练(Quantization-Aware Training, QAT)
QAT 在训练或微调时模拟部署后的量化过程。训练中始终保留一份高精度的主权重,用于接收梯度和更新参数;但在前向计算时,先将主权重量化,再立即反量化,用得到的近似浮点值参与计算。这样,前向计算看到的是带有舍入和截断误差的权重,模型会在训练中逐步适应这种扰动。
问题在于,量化中的舍入操作不可导,无法直接用反向传播计算梯度。QAT 通常使用直通估计器(Straight-Through Estimator,STE):前向传播照常执行舍入,反向传播则近似把它当作恒等映射,将伪量化权重的梯度直接传回高精度主权重:
优化器据此更新主权重,下一次前向传播再重新伪量化。因为权重分布会在训练中主动避开不利的量化边界,QAT 在低位宽、权重与激活同时量化、精度要求严格的场景下往往表现更好。代价也很明显:需要训练数据、额外算力和更复杂的训练流程。
PTQ 与 QAT 对比
| 对比项 | PTQ | QAT |
|---|---|---|
| 起点 | 已训练好的浮点模型 | 浮点模型或待训练模型 |
| 是否训练 | 不做完整训练,可做校准或局部优化 | 需要训练或微调 |
| 数据需求 | 无数据或少量校准数据 | 训练数据,通常更多 |
| 成本 | 低,部署周期短 | 高,需要训练资源 |
| 低位宽精度 | 更容易下降 | 通常更稳 |
| 常见场景 | W8、W4,快速部署现有 LLM | 激进低比特、W/A 同时量化、端侧模型 |
现在也有不少方法只微调少量参数、学习最优的 scale,或在 PTQ 之后做短时间的恢复训练,它们落在纯 PTQ 和完整 QAT 之间。PTQ 和 QAT 的选择取决于部署场景:追求快速落地选 PTQ,追求极致精度选 QAT。
总结
本文从直觉出发,以压缩模型参数量,降低内存消耗为目标,逐步解释了量化的基本概念。这些基本概念为后续了解量化算法提供了基础。后续文章会介绍常见的量化方法,读者将会看到这些方法识别并处理异常值、如何选择量化范围,以及如何在精度、显存和推理速度之间做取舍。