量化这个词经常把几个不同层次的概念混在一起。对于已经训练好的模型,最直接的问题其实是:
一串 FP16 权重,怎样变成更小的整数或索引;推理时又怎样恢复成可以参与矩阵乘法的近似权重?
本文只从权重角度解释两种常见做法:均匀量化(uniform quantization)和向量量化(vector quantization)。先不讨论激活量化、GPTQ 的 Hessian 或具体硬件 kernel。
先看一个权重块
把线性层权重矩阵切成小组。为了方便说明,假设其中一组只有 4 个权重:
w = [0.80, 0.70, -0.20, 0.10]
真实模型通常会按 group_size=32/64/128 分组。分组的意义是:每一组可以有自己的量化参数,不必让整个矩阵共用一把尺子。
均匀量化:每个权重找最近的刻度
均匀量化先准备一条等间距的整数刻度。以 4 bit、无符号量化为例,整数码是:
q = 0, 1, 2, ..., 15
浮点数不直接存储,而是通过 scale 和 offset 映射到这些整数格子:
反量化时再做相反的变换:
scale 和 offset 从哪里来
给定这一组权重的浮点范围:
w_min = -0.80
w_max = 0.70
以及目标整数范围:
q_min = 0
q_max = 15
先算刻度间距:
再让 w_min 对应 q_min:
这里因为 q_min=0,所以 offset=w_min=-0.80。于是大致得到:
-0.80 -> 0
-0.42 -> 4
0.10 -> 9
0.70 -> 15
实际实现也经常使用 zero_point 写法:
两种写法等价,关系是:
直觉上,scale 负责“格子间距”,offset 负责“尺子放在哪里”。如果权重分布是非对称的,不减 offset 就无法把真实范围准确对齐到目标整数范围。
qmin 和 qmax 不是算出来的
它们由目标整数格式决定:
| 格式 | q_min | q_max |
|---|---|---|
| 4-bit unsigned | 0 | 15 |
| 8-bit unsigned | 0 | 255 |
| 4-bit signed | -8 | 7 |
| 8-bit signed | -128 | 127 |
所以流程是:先选 bit 数和 signed/unsigned,再得到 qmin/qmax,最后根据权重范围算 scale/offset。
向量量化:一组权重找最近的模板
向量量化换了一个问题。它不再问“每个权重应该落在哪个刻度”,而是问:
这一整组权重,最像码本里的哪个向量?
假设每 4 个权重组成一个向量,码本里有 4 个候选向量:
C0 = [ 0.00, 0.00, 0.00, 0.00]
C1 = [ 0.80, 0.80, -0.10, 0.10]
C2 = [-0.50, 0.50, 0.00, 0.20]
C3 = [ 1.00, -1.00, 0.50, -0.50]
对于:
w = [0.80, 0.70, -0.20, 0.10]
量化器会计算它与 C0/C1/C2/C3 的距离,选择最近的 C1。因此这组权重实际只需要保存:
index = 1
反量化时查表:
codebook[1] -> C1 -> [0.80, 0.80, -0.10, 0.10]
这就是向量量化的核心:保存向量索引,反量化时查回整个向量。
码本是怎么来的
码本不是凭空出现的,常见来源有两种。
后训练聚类
对已经训练好的浮点权重:
- 按固定长度切成很多权重向量;
- 使用 K-Means 等聚类算法寻找中心点;
- 把聚类中心保存为码本;
- 每个权重向量只保存最近中心点的索引。
例如,许多相似的权重向量聚成一类后,类中心就是一个码本向量。这样做不需要重新训练整个模型,但码本质量取决于权重分布和聚类设置。
量化感知训练
也可以把码本向量当成可训练参数:
浮点权重 -> 选择码本向量 -> 前向计算 -> 计算误差 -> 更新码本
实际训练时,离散的索引选择通常需要近似梯度或特殊优化。它比简单的 min-max 映射复杂,但有机会让码本更贴合模型任务。
“码本大小决定 bit 数”到底是什么意思
假设码本有 4 个候选向量:C0/C1/C2/C3。一个权重向量块量化后,只需要保存四者之一的编号:
00 -> C0
01 -> C1
10 -> C2
11 -> C3
因为 2 个 bit 有 2^2=4 种组合,所以 4 个候选向量的索引需要 2 bit:
其中 K 是码本大小。
但这里的 bit 是每个向量块的 bit,不一定是每个权重的 bit。如果一个向量块包含 D 个权重,那么理论平均开销是:
例如:
码本大小 K | 向量块大小 D | 每块索引 | 理论平均值 |
|---|---|---|---|
| 4 | 4 | 2 bit | 0.5 bit/weight |
| 16 | 4 | 4 bit | 1 bit/weight |
| 256 | 4 | 8 bit | 2 bit/weight |
| 256 | 8 | 8 bit | 1 bit/weight |
还要加上码本、scale、对齐和其他元数据,因此实际模型大小会高于这个理论值。
两种方法放在一起
| 对比项 | 均匀量化 | 向量量化 |
|---|---|---|
| 量化对象 | 单个权重 | 一组权重向量 |
| 保存内容 | 整数码、scale、offset | 向量索引、码本,可能还有 scale |
| 量化方式 | 找最近的等间距刻度 | 找最近的码本向量 |
| 反量化 | q * scale + offset | codebook[index] |
| 低 bit 精度 | 可能受离群值影响 | 能利用权重之间的相关性 |
| 部署复杂度 | 低,硬件支持好 | 高,需要查表和专用实现 |
一句话总结:
均匀量化保存的是“每个权重对应哪个刻度”;向量量化保存的是“这一组权重对应哪个模板”。前者更容易部署,后者更善于利用权重之间的共同模式。