4215 字
21 分钟
次浏览
大模型量化入门 — 从"最小化误差"出发,一步步推导量化公式

0. 前言#

你有没有好奇过, 为什么一个 7B 参数的模型要占 14GB 显存?

但在回答之前, 我想先确认一个最基本的问题: “7B” 和 “14GB” 背后, 到底是怎么算出来的?

0.1 先聊点最基础的: bit 和 byte#

计算机里最小单位是一个 bit (比特), 它只能表示 0 或 1 两种状态.

但 1 个 bit 能存的数字太少了, 所以通常 8 个 bit 组成一个 byte (字节):

1 byte=8 bits1 \ \text{byte} = 8 \ \text{bits}

有了 byte 以后, 我们习惯用 byte 来计量内存/显存:

  • 1 KB = 1024 bytes
  • 1 MB = 1024 KB
  • 1 GB = 1024 MB

那一个数字要占多少个 byte 呢? 取决于我们用多少 bit 来表示它.

0.2 n 个 bit 能表示多少种状态?#

对于整数来说: n 个 bit 可以表示 2n2^n 种不同的状态.

比如:

bit 数能表示的状态数取值范围 (无符号)取值范围 (有符号)
1 bit20 ~ 1-1 ~ 0
4 bit24=162^4 = 160 ~ 15-8 ~ 7
8 bit28=2562^8 = 2560 ~ 255-128 ~ 127
16 bit216=655362^{16} = 655360 ~ 65535-32768 ~ 32767

所以当我说”一个 INT4 数”时, 意思是它用 4 个 bit 来存, 只能取 16 种不同的值. 有符号时范围是 8-877.

0.3 二进制怎么转成十进制?#

比如 4-bit 二进制数 1101:

1×23+1×22+0×21+1×20=8+4+0+1=131 \times 2^3 + 1 \times 2^2 + 0 \times 2^1 + 1 \times 2^0 = 8 + 4 + 0 + 1 = 13

如果是有符号数 (用补码表示), 最高位是符号位:

  • 最高位 = 0: 正数, 直接算
  • 最高位 = 1: 负数, 先取反再加 1, 再算

这就是为什么 INT4 的范围是 8-8 (二进制 1000) 到 77 (二进制 0111).

0.4 那浮点数呢?#

深度学习里用的不是整数, 而是浮点数. 浮点数用科学计数法来存:

数值=(1)符号×尾数×2指数\text{数值} = (-1)^{\text{符号}} \times \text{尾数} \times 2^{\text{指数}}

以 FP32 (32 位浮点数) 为例, 它由 3 部分组成:

部分位数含义
符号位 (sign)1 bit0 为正, 1 为负
指数位 (exponent)8 bits决定数值范围, 偏置 127
尾数位 (mantissa)23 bits决定数值精度

所以 FP32 能表示大约 ±3.4×1038\pm 3.4 \times 10^{38}, 精度约 7 位有效数字. BF16 保留了 FP32 的 8 位指数 (因此范围一样大), 但尾数砍到 7 位 (精度更低但范围不变).

格式总 bit指数 bit尾数 bit字节数数值范围
FP32328234 bytes±3.4×1038\pm 3.4 \times 10^{38}
FP16165102 bytes±6.5×104\pm 6.5 \times 10^4
BF1616872 bytes±3.4×1038\pm 3.4 \times 10^{38}
INT881 byte-128 ~ 127
INT440.5 byte-8 ~ 7

注意: 这里 1 byte = 8 bits, 所以一个 INT4 数是 0.5 个 byte.

0.5 回到最初的问题#

7×1097 \times 10^9 个参数, 每个参数用 BF16 存 (2 bytes/参数):

7×109×2 bytes=14×109 bytes14 GB7 \times 10^9 \times 2 \ \text{bytes} = 14 \times 10^9 \ \text{bytes} \approx 14 \ \text{GB}

如果量化到 INT4 (0.5 bytes/参数):

7×109×0.5 bytes=3.5×109 bytes3.5 GB7 \times 10^9 \times 0.5 \ \text{bytes} = 3.5 \times 10^9 \ \text{bytes} \approx 3.5 \ \text{GB}

一下子省了 4 倍的空间!

于是大家想: 能不能用更少的 bit 来存这些数字, 同时尽量不损失模型的质量?

这听起来像是一个数据压缩问题——用较少的信息表示同样的内容, 尽量保留原始信息. 但和一般的压缩(比如 zip)不同, 量化有一个重要特点: 我们不需要精确还原每个参数, 我们只需要最终模型输出的质量尽量不变.

这就给了我们操作空间.


1. 设定目标#

先想清楚我们在做什么.

假设有一个权重矩阵 WRm×nW \in \mathbb{R}^{m \times n}, 原始精度是 BF16. 我们想把它转成 INT4 来存, 即每个数只占 4 个 bit.

我们的操作流程是:

W量化Wq (INT4)加载时反量化W^W \xrightarrow{\text{量化}} W_q \ (\text{INT4}) \xrightarrow{\text{加载时反量化}} \hat{W}

推理时计算的是 W^x\hat{W}x, 而不是 WxWx. 所以误差就是:

error=WxW^x\text{error} = \|Wx - \hat{W}x\|

我们希望这个误差尽量小.

这就是量化问题的核心: 找到一个映射函数 Q:RZ4Q: \mathbb{R} \to \mathbb{Z}_4 (实数到 4-bit 整数), 使得反量化后的 W^\hat{W}WW 的误差最小.


2. 最简单的尝试: 四舍五入#

最直观的想法: 把每个浮点数四舍五入到最近的整数.

比如 0.300.3 \to 0, 1.721.7 \to 2, 0.81-0.8 \to -1.

但马上发现问题了: 模型参数的取值范围一般是 [2,2][-2, 2] 左右, 而 INT4 只能表示 8-877. 这就像用一把 16 米长的尺子去量 2 米的东西——大部分刻度都浪费了. 四舍五入后, 几乎所有数都变成 0 或 ±1, 信息全丢了.

所以我们需要缩放. 先除以一个 scale, 让数据的范围匹配整数的范围, 再四舍五入. 这就是量化的核心思想.


3. 对称量化: 引入 scale#

先考虑最简单的情况: 数据分布在 0 两侧, 大致对称.

3.1 推导 scale#

我们希望把 [a,a][-a, a] 范围内的浮点数映射到 [Qn,Qp][-Q_n, Q_p] 范围内的整数. 对于对称 INT8, Qn=127Q_n = -127, Qp=127Q_p = 127.

目标: 最小化量化误差. 假设有一组数值 x1,x2,...,xNx_1, x_2, ..., x_N, 我们选择的 scale 是 ss, 量化函数是:

xq=round(xs)x_q = \text{round}\left(\frac{x}{s}\right)

反量化:

x^=xq×s\hat{x} = x_q \times s

量化误差:

MSE(s)=1Ni=1N(xix^i)2=1Ni=1N(xisround(xis))2\text{MSE}(s) = \frac{1}{N}\sum_{i=1}^N (x_i - \hat{x}_i)^2 = \frac{1}{N}\sum_{i=1}^N \left(x_i - s \cdot \text{round}\left(\frac{x_i}{s}\right)\right)^2

这个 MSE 的最小化没有完美的闭式解, 但有一个在实际中效果很好的启发式: 让 scale 刚好覆盖数据的最大范围.

s=max(x)Qps = \frac{\max(|x|)}{Q_p}

为什么? 如果 ss 太大, 那么 xs\frac{x}{s} 就很小, 四舍五入后很多不同的 xx 会映射到同一个整数, 量化颗粒度太粗. 如果 ss 太小, 会有一部分数据超出 [Qns,Qps][-Q_n s, Q_p s] 的范围, 发生截断 (clipping), 引入截断误差.

最佳 scale 就是在颗粒度误差截断误差之间取平衡. 对于接近均匀分布的数据, 覆盖最大范围的 scale 是近似最优的. 但对于长尾分布 (模型权重的分布通常中间密两边疏), 选择稍微小一点的 scale (允许少量截断) 反而能降低整体 MSE——因为截断的是极少数离群点, 而保留的精度给大部分值带来了好处.

我们把这个重要的权衡点记下来, 后面 AWQ 会用到.

3.2 直观例子#

假设权重值: [0.1,0.3,0.7,0.9,1.2][0.1, -0.3, 0.7, -0.9, 1.2], 量化到 INT8 (Qp=127Q_p = 127).

s=1.21270.00945s = \frac{1.2}{127} \approx 0.00945
原始值x/sx/sround反量化误差
0.110.58110.104+0.004
-0.3-31.75-32-0.302-0.002
0.774.07740.699-0.001
-0.9-95.24-95-0.898+0.002
1.2126.981271.2000.000

可以看到误差都在 s/20.005s/2 \approx 0.005 以内, 相对误差不到 1%. 在小数值上的相对误差会大一些 (0.1 → 0.104, 误差 4%), 但绝对值很小.

3.3 对称量化的局限性#

如果数据分布不对称怎么办? 比如 ReLU 之后的激活值全是正数 [0,5.0][0, 5.0].

用对称量化, 正半轴用 [0,127][0, 127], 负半轴 [127,0)[-127, 0) 完全浪费了, 相当于只用了一半的精度预算. 可以用非对称量化来补救.


4. 非对称量化: 引入 zero_point#

4.1 推导#

非对称量化不再要求映射关于 0 对称, 而是允许平移. 引入两个参数: scale ss 和 zero_point zz.

量化:

xq=round(xs)+zx_q = \text{round}\left(\frac{x}{s}\right) + z

反量化:

x^=(xqz)×s\hat{x} = (x_q - z) \times s

其中 zz 是一个整数, 使得 x=0x = 0 映射到 xq=zx_q = z. 参数怎么确定?

假设数据范围是 [xmin,xmax][x_{\min}, x_{\max}], 量化范围是 [0,2n1][0, 2^n - 1] (对 INT8 就是 [0,255][0, 255]).

s=xmaxxmin2n1s = \frac{x_{\max} - x_{\min}}{2^n - 1}z=round(xmins)=round(xmin(2n1)xmaxxmin)z = \text{round}\left(-\frac{x_{\min}}{s}\right) = \text{round}\left(-\frac{x_{\min} \cdot (2^n - 1)}{x_{\max} - x_{\min}}\right)

推导: 把数据范围映射到整数范围:

xmin0,xmax2n1x_{\min} \to 0, \quad x_{\max} \to 2^n - 1

线性映射就是:

xq=xxminxmaxxmin(2n1)x_q = \frac{x - x_{\min}}{x_{\max} - x_{\min}} \cdot (2^n - 1)

等价于上面 sszz 的形式 (验证一下: 代入 x=0x = 0, 得到 xq=xminxmaxxmin(2n1)zx_q = \frac{-x_{\min}}{x_{\max} - x_{\min}} \cdot (2^n - 1) \approx z).

把公式整理成更常用的形式:

xq=round(xs)+zx_q = \text{round}\left(\frac{x}{s}\right) + z

其中 s=xmaxxmin2n1s = \frac{x_{\max} - x_{\min}}{2^n - 1}, z=round(xmins)z = \text{round}\left(-\frac{x_{\min}}{s}\right).

4.2 对称 vs 非对称, 选哪个?#

对称非对称
参数数量1 个 scale2 个 (scale + zero_point)
计算效率高 (无额外减法)低 (多一步减法)
适合数据对称分布 (如权重)不对称分布 (如激活值)
硬件支持几乎所有硬件部分硬件优化不足

实践中: 权重用对称量化, 激活值用非对称量化.


5. 量化的误差到底从哪里来?#

搞清楚了公式, 现在深入看看误差来源.

把反量化后的值写成:

x^=sround(xs)=x+s(round(xs)xs)\hat{x} = s \cdot \text{round}\left(\frac{x}{s}\right) = x + s \cdot \left(\text{round}\left(\frac{x}{s}\right) - \frac{x}{s}\right)

括号里的项是四舍五入的误差 ee, 范围是 [0.5,0.5][-0.5, 0.5]. 所以:

x^=x+se,e[0.5,0.5]\hat{x} = x + s \cdot e, \quad e \in [-0.5, 0.5]

量化误差就是 ses \cdot e, 最大值 0.5s0.5s. 这告诉我们两件事:

  1. scale 越大, 误差越大. 数据范围大的张量, 量化损失也更严重.
  2. 量化误差的上限是固定的 (0.5s0.5s), 不管原始值大小.

第二条特别重要: 如果一个权重值本来就很小 (比如 0.0010.001), 量化误差 0.5s0.5s 可能跟它本身一样大! 而一个大的权重值 (比如 1.51.5), 同样 0.5s0.5s 的误差占比不到 1%.

这就引出了一个关键洞察: 不是所有权重对最终结果的影响都一样大.

计算 WxWx 时, 某个权重 WijW_{ij} 对输出的贡献是 WijxjW_{ij} \cdot x_j. 如果 Wij|W_{ij}| 很大, 或者它对应的激活值 xj|x_j| 很大, 那这个权重的量化误差就会被放大. 反过来, 如果一个权重很小, 即使它的相对误差很大, 对最终结果的绝对影响也很小.

这个洞察是后面 AWQ 的核心.


6. 从数学到工程: 量化粒度#

上面讲的都是针对一个张量的整体量化. 但一个模型有几十上百层, 每层的参数分布可能差别很大.

量化粒度就是每多少个参数共用一个 scale:

  • Per-tensor: 整个权重矩阵 1 个 scale, 最简单但精度最差
  • Per-channel: 每个输出通道 1 个 scale (对线性层来说, 就是权重矩阵的每一行), 实际中最常用
  • Per-group: 每 gg 个参数 1 个 scale (比如 g=32g=32), 精度最高但存储开销也大

为什么? 从信息论角度理解: 每组独立算 scale, 相当于给每组分配了独立的”精度预算”, 可以针对该组的实际分布来优化. 缺点是需要额外存储 scale 值 (一般用 FP16 或 FP32), group size 越小, 额外存储占比越大. 例如 group size = 32, 每 32 个值存 1 个 16-bit scale, 额外开销占 16/32=0.516/32 = 0.5 bit/参数——对于 4-bit 量化来说, 相当于增加了 12.5% 的存储.


7. GPTQ: 有脑子的量化#

上面讲的都是”独立量化每个参数”. GPTQ 做了一个更聪明的选择:量化参数时考虑参数之间的相互作用.

7.1 从 OBS 说起#

GPTQ 的前身是 Optimal Brain Quantizer (OBQ), 它的核心思路来自最优脑损伤 (Optimal Brain Damage) 的思想: 衡量移除某个参数对损失函数的影响, 优先量化影响小的参数.

对于一个训练好的网络, 损失函数 L\mathcal{L} 在最优参数 w\mathbf{w} 附近的二阶泰勒展开是:

L(w+δ)L(w)+L(w)Tδ=0 在最优解处+12δTHδ\mathcal{L}(\mathbf{w} + \delta) \approx \mathcal{L}(\mathbf{w}) + \underbrace{\nabla \mathcal{L}(\mathbf{w})^{\mathsf{T}} \delta}_{=0 \ \text{在最优解处}} + \frac{1}{2} \delta^{\mathsf{T}} H \delta

其中 HHHessian 矩阵 (二阶偏导数矩阵, 衡量损失函数在各方向上的曲率). 对角线元素 HqqH_{qq} 越大, 说明损失函数在参数 wqw_q 方向上越”陡峭”, 这个参数越重要.

ΔL12wq2[H1]qq\Delta \mathcal{L} \approx \frac{1}{2} \frac{w_q^2}{[H^{-1}]_{qq}}

推导: 如果我们量化 (移除) 参数 wqw_q, 最优的补偿是调整其他参数来最小化损失. OBQ 证明, 最小损失的调整量是 δ=wq[H1]qqH:,q1\delta = -\frac{w_q}{[H^{-1}]_{qq}} H_{:,q}^{-1}, 对应的损失增量就是 12wq2[H1]qq\frac{1}{2} \frac{w_q^2}{[H^{-1}]_{qq}}.

注意这里 [H1]qq[H^{-1}]_{qq}分母. [H1]qq[H^{-1}]_{qq} 的含义是”移除参数 wqw_q 后, 其他参数能补偿的程度”:

  • 如果 [H1]qq[H^{-1}]_{qq} 很大: 说明损失函数在这个方向上很”平缓”, 移除 wqw_q 后其他参数很容易补偿 → 损失增量小 → 这个参数不重要
  • 如果 [H1]qq[H^{-1}]_{qq} 很小: 说明损失函数在这个方向上很”陡峭”, wqw_q 的位置很关键 → 损失增量大 → 这个参数很重要

7.2 GPTQ 的工程优化#

OBQ 每次量化一个参数后要更新 Hessian 逆矩阵, 复杂度 O(dcol3)O(d_{\text{col}}^3), 对大规模模型不可行.

GPTQ 做了三个关键工程优化:

  1. 固定顺序: 不再贪心选择”影响最小的参数”, 而是直接按列从左到右量化
  2. 懒惰更新: 批量量化多列后再更新 Hessian, 利用矩阵乘法的 GPU 加速
  3. Cholesky 预分解: 预先对 Hessian 做 Cholesky 分解 (H=LLTH = LL^{\mathsf{T}}), 避免逐次求逆. Cholesky 分解是一种将对称正定矩阵分解为下三角矩阵乘以其转置的方法, 比直接求逆更高效稳定.

最终 GPTQ 可以在几个小时内量化 175B 的模型, 且 INT4 精度损失极小.

算法流程 (简化的伪代码):

输入: 权重矩阵 W, 校准数据 X, 量化精度 b
1. 计算 Hessian: H = 2 X^T X
2. 对 H 做 Cholesky 分解
3. for 每一列 j in W:
a. 量化第 j 列: W_q[:, j]
b. 计算量化误差: err = W[:, j] - W_q[:, j]
c. 把误差按 Hessian 信息"补偿"到未量化的列上

步骤 3c 是关键: 量化第 1 列造成的误差, 会被分摊到后面的列上, 后面的参数会”吸收”前面的量化误差. 这就好比: 你前面做错了事, 后面的人帮你兜着.

7.3 GPTQ 的精度#

实际经验: GPTQ INT4 的精度损失一般在 1% 以内 (以 MMLU 为基准). 对于大模型 (70B+), 损失甚至更小, 因为参数越多, 量化误差越容易被”稀释”.


8. AWQ: 重要通道保护#

AWQ 的出发点很简单: 不是所有权重都值得平等对待.

8.1 关键观察#

AWQ 的作者发现了一个有趣的现象: 权重中约 1% 的通道 (channel) 对模型质量影响巨大. 这些通道的特点是——它们对应的激活值 (activation) 幅度特别大.

回忆前面说的: 计算 WxWx 时, 某个权重 WijW_{ij} 对输出的贡献是 WijxjW_{ij} \cdot x_j. 如果激活值 xjx_j 很大, 那么这个通道的微小量化误差都会被放大.

8.2 AWQ 的做法#

AWQ 的做法和直觉相反——它不直接保留这些重要通道的高精度, 而是用一个巧妙的技巧来”保护”它们:

  1. 用少量校准数据跑一遍, 找到激活值幅度大的通道
  2. 对这些通道的权重乘以一个大于 1 的缩放因子 ss
  3. 量化缩放后的权重
  4. 在推理时, 对相应的激活值除以 ss, 保证计算结果不变

数学上:

量化前: Wx量化后: W^(xs)s\text{量化前: } Wx \quad \longrightarrow \quad \text{量化后: } \hat{W}\left(\frac{x}{s}\right) \cdot s

其中 W^\hat{W}WsW \cdot s 的量化版本. 由于 Ws|W \cdot s| 变大了, 在同样的量化范围下, scale 相应增大, 但更重要的是: 重要通道的权重被放大后, 量化相对误差变小了 (因为四舍五入的绝对误差 0.5squant0.5 \cdot s_{\text{quant}} 相对于放大后的值变小).

这就像在教育预算中, 给更需要支持的学校多分配资源——总量不变, 但分配更合理.

8.3 AWQ vs GPTQ#

GPTQAWQ
核心思路量化误差补偿保护重要通道
需要校准数据
计算开销高 (需要 Hessian)低 (只需要激活值统计)
精度 (INT4)略好相当
主要贡献大规模高效量化简单有效的重要性感知

两种方法可以结合使用, 不少实际部署方案会先用 AWQ 的思路找重要通道, 再用 GPTQ 的方法做量化.


9. 总结#

回头看看量化到底在做什么:

核心思想: 用更少的 bit 存数字, 最小化对输出的影响.

数学本质: 找到缩放 ss 和偏移 zz, 使得 x^=s(round(xs)+z)\hat{x} = s \cdot \left(\text{round}\left(\frac{x}{s}\right) + z\right) 的误差最小.

关键权衡:

权衡选择效果
scale 大小大 → 覆盖范围大但精度粗小 → 精度细但可能截断
量化粒度粗 → 速度飞快精度低细 → 精度高但开销大
量化方法独立量化 → 简单关联量化(GPTQ) → 精度好但慢
通道保护一视同仁 → 简单区别对待(AWQ) → 精度更好

一句话: 量化就是用精度换效率, 关键是怎么在尽可能少丢精度的情况下, 做到极致的效率.


参考资料#

  1. Gray & Neuhoff, Quantization. IEEE Transactions on Information Theory, 1998. — 量化信息论的经典综述
  2. GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers. Frantar et al., ICLR 2023. arXiv:2210.17323
  3. AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration. Lin et al., MLSys 2024. arXiv:2306.00978
  4. Nagel et al., A White Paper on Neural Network Quantization. arXiv:2106.08295
  5. llama.cpp GGUF format. GitHub
大模型量化入门 — 从"最小化误差"出发,一步步推导量化公式
https://xuchenhui.cc/posts/2026-05-16-llm-quantization-guide/
作者
CHENHUI
发布于
2026-05-16
许可协议
CC BY-NC-SA 4.0
📖 目录