4. 模型量化基础:量化原理、定点与浮点、量化误差来源
4.1 为什么需要量化:从“稠密计算”到“低比特表示”
大语言模型在推理时的主要瓶颈往往不是显存容量,而是显存带宽与计算吞吐。以 FP16 格式存储的 7B 模型,权重占用约 14 GB 显存;每次生成一个 token 都需要将全部权重从 HBM 读取到计算单元。若将权重压缩为 INT8,则读取量减半;压缩为 INT4,则读取量降至原来的 1/4。量化正是通过降低每个参数的比特数,换取更低的访存压力与更高的计算密度,从而直接提升吞吐。
量化的本质是建立一种映射:将高精度数值(如 FP32/FP16)映射到有限集合的低精度整数(如 INT8/INT4),并在推理时用整数运算近似原始浮点运算。这种映射必须尽量保留原始张量的数值分布特征,否则会引入过大误差。
4.2 定点数与浮点数:两种数值世界的差异
要理解量化误差,必须先厘清定点数与浮点数的本质区别。
- 浮点数(FP32/FP16/BF16):由符号位、指数位、尾数位组成。其数值范围极大(FP32 约 ±3.4×10³⁸),但精度随数值大小变化——大数附近精度低,小数附近精度高。这种“动态范围”特性适合训练,但对硬件访存不友好。
- 定点数(INT8/INT4):只有符号位和整数位,数值均匀分布在固定区间。INT8 范围为 [-128, 127],INT4 范围为 [-8, 7]。定点数没有指数位,因此动态范围极小,但硬件计算单元简单、吞吐高。
量化本质上就是将浮点张量“压缩”到定点整数域。常用的映射方式为仿射量化(Affine Quantization):
r = S × (q - Z)
其中 r 为原始浮点值,q 为量化后的整数,S 为缩放因子(浮点标量),Z 为零点(整数偏移)。反量化时通过该公式恢复近似浮点值。对称量化则令 Z = 0,仅需存储 S,计算更简单,但无法很好处理分布偏移(如 ReLU 后全为正值的张量)。
下图展示了 FP16 与 INT8 在表示同一权重矩阵时的数值分布差异,以及量化映射关系:
4.3 量化误差的三个主要来源
量化过程并非无损,误差主要来自以下三个方面。理解这些来源,是后续选择量化算法(如 RTN、GPTQ、AWQ)的基础。
| 误差来源 | 产生原因 | 典型影响 | 缓解手段 |
|---|---|---|---|
| 舍入误差 | 浮点值映射到最近整数时产生的精度损失 | 权重微小扰动,激活值敏感时被放大 | 使用更细粒度(per-channel/per-group)量化 |
| 截断误差 | 超出量化范围(如 >127 或 <-128)的值被强制裁剪 | 分布尾部被“压平”,极端值丢失 | 选择合适的量化范围(如百分位裁剪) |
| 分布失配 | 原始张量分布与量化假设的均匀分布不一致 | 激活值分布偏斜时误差显著增大 | 使用非对称量化或动态量化 |
4.4 量化粒度:从 Per-Tensor 到 Per-Group
量化粒度决定了缩放因子 S 和零点 Z 的共享范围。粒度越细,量化精度越高,但存储开销也越大。
- Per-Tensor(整张量共享一组参数):实现最简单,但若张量内数值分布差异大,误差会很大。适合分布均匀的权重。
- Per-Channel(每个输出通道一组参数):对权重矩阵按输出维度分别计算
S和Z,显著降低舍入误差。是 INT8 权重量化的常用方案。 - Per-Group(每组若干元素共享参数):将通道进一步划分为小组(如每组 128 个元素),每组独立量化。INT4 量化(如 GPTQ)通常采用此方案,以在极低比特下保持精度。
以 weight.shape = [4096, 4096] 为例,不同粒度的参数数量对比:
Per-Tensor: S 和 Z 各 1 个
Per-Channel: S 和 Z 各 4096 个(沿输出维度)
Per-Group: S 和 Z 各 4096 × (4096 / 128) = 131072 个
可以看到,Per-Group 虽然增加了少量存储开销(通常用 FP16 存储 S 和 Z),但能大幅提升量化精度,尤其适合对精度敏感的激活值量化。
4.5 实际案例:观察量化误差对推理的影响
假设我们有一个简单的线性层 y = Wx,其中 W 的分布为均值为 0、标准差为 0.02 的正态分布,x 为输入激活。若直接使用 Per-Tensor 对称量化到 INT8:
# 伪代码示意
W_fp32 = normal(0, 0.02, shape=[4096, 4096])
scale = max(abs(W_fp32)) / 127
W_int8 = round(W_fp32 / scale)
W_dequant = W_int8 * scale
# 计算量化误差
mse = mean((W_fp32 - W_dequant) ** 2)
print(f"Per-Tensor MSE: {mse:.6f}")
若改用 Per-Channel 量化,每个输出通道单独计算 scale,MSE 通常会下降 10~100 倍。这就是为什么实际部署中几乎不使用 Per-Tensor 对权重做 INT8 量化。
--quantization awq 启动服务,可观察日志中的量化参数确认粒度设置。
4.6 误区与复盘:量化误差的“放大效应”
量化误差并非均匀分布在所有层。Transformer 结构中,注意力层的 Q/K/V 投影和最后的 LM Head 对量化误差更敏感。原因在于:
- 注意力分数经过 Softmax 后分布集中在 0~1 之间,量化误差可能改变注意力权重的相对大小,导致“注意力漂移”。
- LM Head 的输出直接决定 token 概率分布,微小误差可能改变 top-k 排序,造成生成质量下降。
- 残差连接会将量化误差逐层累积,深层网络误差更大。
因此,在实际量化部署中,常采用混合精度策略:对敏感层保留 FP16,对非敏感层使用 INT8/INT4。vLLM 的 --quantization 参数配合 --quantization-param 可实现对特定模块的精度控制。
lm_head 和 qkv_proj 保持高精度,往往能解决大部分质量问题。