4、量化技术基础:线性量化与非线性量化、定点数与浮点数转换、量化误差分析
量化,说白了就是用更少的比特去表示一个数值。在模型压缩里,我们经常要把 32 位浮点数(FP32)的权重,变成 8 位甚至 4 位的整数。你想想看,存储一下子少了 4 倍到 8 倍,推理速度也能快不少。
但代价是什么?精度损失。我刚开始做量化的时候,总觉得不就是四舍五入嘛,能差多少?结果模型直接崩了。嗯,这里面的门道,咱们今天一条条捋清楚。
4.1 线性量化 vs 非线性量化
量化分两大类:线性量化和非线性量化。区别在于,数值映射到整数时,步长是不是均匀的。
4.1.1 线性量化
线性量化是最常用的方法。它假设原始浮点数的分布是均匀的,或者至少可以用均匀的步长去近似。公式很简单:
Q = round((r - zero_point) / scale)
r = Q * scale + zero_point
其中 r 是原始浮点数,Q 是量化后的整数,scale 是步长,zero_point 是零点偏移。
我在项目中遇到过一个问题:当权重分布特别不均匀时,比如大部分值集中在 0 附近,但有几个离群点特别大。这时候线性量化的步长会被那几个离群点拉大,导致中间区域的精度严重损失。模型精度直接掉了 5 个点。
核心要点:线性量化适合数据分布比较均匀的场景。如果数据有长尾或离群点,需要先做裁剪(clipping)或者改用非线性量化。
4.1.2 非线性量化
非线性量化,说白了就是步长不固定。常见的有对数量化、浮点数量化(如 FP8、BF16)。
对数量化的公式长这样:
Q = round(log2(|r|))
r = sign(r) * 2^Q
为什么用对数?因为很多神经网络权重的分布其实是对数正态的——小值特别多,大值特别少。用对数步长,小值区域量化得更精细,大值区域粗糙一点,整体误差反而更小。
我记得有一次做语音模型的量化,权重分布跨度特别大,从 1e-6 到 10 都有。线性量化直接崩了,换成对数量化后,精度只掉了 0.3%。
| 特性 | 线性量化 | 非线性量化 |
|---|---|---|
| 步长 | 均匀 | 不均匀(如对数、指数) |
| 硬件支持 | 广泛(CPU/GPU/NPU 都支持) | 有限(需要特殊指令集) |
| 适用分布 | 均匀分布 | 长尾分布、对数正态分布 |
| 实现复杂度 | 低 | 中高 |
| 精度损失 | 分布均匀时小 | 分布不均匀时更优 |
我的建议:优先尝试线性量化。如果精度损失超过 1%,再考虑非线性量化。不要一上来就搞复杂的,工程上简单就是美。
4.2 定点数与浮点数转换
量化本质上就是浮点数到定点数的转换。但这里有个坑:定点数不是整数,它有一个隐含的小数点位置。
4.2.1 定点数的表示
定点数用 Qm.n 格式表示,其中 m 是整数位宽,n 是小数位宽。比如 Q4.3 表示总共 7 位,其中 4 位整数,3 位小数。
// Q4.3 格式示例
// 二进制: 0101 100
// 整数部分: 0101 = 5
// 小数部分: 100 = 0.5
// 实际值: 5.5
浮点数转定点数的步骤:
- 确定位宽和小数点位置(
Qm.n) - 计算缩放因子:
scale = 2^n - 量化:
Q = round(r * scale) - 反量化:
r_hat = Q / scale
注意:定点数有溢出风险。如果浮点数太大,乘以 2^n 后可能超出整数位宽。我曾经在 DSP 上吃过这个亏,一个乘法结果直接溢出变成负数,排查了半天。
4.2.2 浮点数转定点数的工程实践
在实际项目中,我习惯用以下流程:
1. 统计权重/激活值的范围 [min, max]
2. 选择位宽(通常 8 位或 16 位)
3. 计算小数位宽 n = floor(log2((max - min) / (2^bitwidth)))
4. 如果 n < 0,说明需要先做缩放或裁剪
5. 生成量化表或直接做 round 操作
你想想看,如果权重范围是 [-1.0, 1.0],用 8 位定点数,小数位宽可以设到 7 位,精度相当高。但如果范围是 [-128, 128],小数位宽只能设 0 位,精度就差了。
4.3 量化误差分析
量化误差是绕不开的话题。我见过太多人量化完模型,精度掉了也不分析原因,直接放弃。其实误差来源就那么几种,对症下药就好。
4.3.1 误差来源
- 舍入误差:round 操作带来的,不可避免,但可以控制范围
- 截断误差:超出量化范围的值被截断,这是最大的误差来源
- 零点偏移误差:zero_point 选择不当导致的系统性偏移
- 累积误差:多层量化后误差不断放大
4.3.2 误差度量指标
| 指标 | 公式 | 说明 |
|---|---|---|
| MSE | mean((r - r_hat)^2) | 均方误差,最常用 |
| MAE | mean(|r - r_hat|) | 平均误差,对离群点不敏感 |
| SNR | 10*log10(var(r)/var(e)) | 信噪比,越高越好 |
| MaxErr | max(|r - r_hat|) | 最大误差,关注最坏情况 |
经验之谈:我一般先看 MaxErr,如果某个权重的误差特别大,说明裁剪策略有问题。然后看 MSE,如果整体偏高,说明步长太大,需要增加位宽或改用非线性量化。
4.3.3 误差传播分析
量化误差会随着网络层数加深而放大。我做过一个实验:
层数 1: 量化误差 MSE = 0.001
层数 5: 量化误差 MSE = 0.015
层数 10: 量化误差 MSE = 0.12
层数 20: 量化误差 MSE = 0.89 // 模型已经崩了
为什么会这样?因为每一层的输出误差会作为下一层的输入误差,加上该层自身的量化误差,像滚雪球一样越来越大。
解决办法有两个:
- 逐层校准:每一层单独做量化校准,而不是全局统一 scale
- 误差补偿:在量化后做一次 fine-tuning,让模型适应量化后的权重分布
避坑指南:我曾经在一个 50 层的 ResNet 上做量化,直接全局量化后精度掉了 8%。后来改成逐层校准,精度只掉了 1.2%。记住,层数越深,校准越要精细。
4.4 知识体系总览
下面这张图把本章的核心逻辑串起来了。你可以看到量化技术从分类到实现再到误差分析的完整链路。
从这张图可以看得很清楚:量化技术从线性/非线性量化出发,经过定点/浮点转换,最终都要落到误差分析上。而误差分析的结果,又会指导我们做逐层校准和误差补偿。这是一个闭环。
好了,这一章的内容就到这里。量化看起来简单,但真正做好需要大量的实验和调优。记住我前面说的那些坑,能帮你少走不少弯路。