3. 训练后量化(PTQ):三种主流方法深度拆解
训练后量化,说白了就是模型训练完了,我们再动手把权重和激活值从 FP32 转成 INT8。好处很明显——不用重新训练,省时省力。但问题也来了:精度损失怎么控制?
我这些年经手过不少量化项目,从手机端的视觉模型到边缘侧的语音模型,PTQ 几乎是必走的一步。今天咱们就聊聊三种最常用的方法:MinMax、KL 散度、均方误差(MSE)。
嗯,先看一张总览图,心里有个谱。
3.1 MinMax 量化:最直接,但别太天真
MinMax 的思路很简单:找到张量里的最大值和最小值,然后线性映射到 INT8 的范围 [-128, 127] 或 [0, 255]。
公式长这样:
scale = (max - min) / 255
zero_point = round(-min / scale)
quantized_value = round(value / scale) + zero_point
听起来完美,对吧?但我得提醒你——异常值是 MinMax 的噩梦。
所以,MinMax 适合什么场景?
- 数据分布均匀,没有明显异常值
- 激活值范围稳定,比如 ReLU 后的输出
- 快速原型验证,先跑通再优化
我个人习惯在项目初期先用 MinMax 跑一遍,看看精度损失有多大。如果损失在 1% 以内,那就直接用,省事。
3.2 KL 散度量化:让分布更接近
KL 散度量化,核心思想是:找到最优的截断阈值,让量化后的分布和原始分布尽可能相似。
你想想看,MinMax 把整个范围都保留了,但如果两端是长尾噪声,其实没必要。KL 散度会帮你「砍掉」那些不重要的尾巴。
具体做法:
- 收集校准数据,统计激活值的直方图
- 尝试不同的截断阈值 T
- 对每个 T,计算量化前后的 KL 散度
- 选 KL 散度最小的那个 T
关键点: KL 散度量化需要校准数据集。我一般从训练集里随机抽 500~1000 张图片或 1000 条语音片段就够了。太多反而慢,太少统计不准。
伪代码大概这样:
def kl_quantization(tensor, num_bins=2048):
# 1. 统计直方图
hist, bin_edges = np.histogram(tensor, bins=num_bins)
# 2. 遍历可能的截断位置
best_kl = float('inf')
best_threshold = None
for i in range(128, num_bins): # 至少保留128个bin
# 截断
clipped_hist = hist[:i].copy()
# 量化到128个bin
quant_hist = np.zeros(128)
# ... 重采样逻辑
# 计算KL散度
kl = compute_kl(clipped_hist, quant_hist)
if kl < best_kl:
best_kl = kl
best_threshold = bin_edges[i]
return best_threshold
KL 散度量化的优势在哪?它对非均匀分布特别友好。比如某些层的激活值集中在 0 附近,但有个小尾巴,KL 散度会主动把尾巴截掉,保留主要信息。
3.3 MSE 量化:最小化误差,最稳妥
MSE 量化,说白了就是:找一个 scale 和 zero_point,让量化前后的均方误差最小。
公式:
MSE = mean((x - x_quant)²)
其中 x 是原始浮点值,x_quant 是反量化后的值。
为什么说它稳妥?因为它直接优化了「重建误差」。你想想看,量化本质上是有损压缩,MSE 就是让这个损失最小化。
我遇到过的情况:
- 某个卷积层的权重分布是双峰分布,MinMax 和 KL 散度都表现不好
- 换成 MSE 量化,手动搜索最优 scale,精度恢复了 2.3%
MSE 量化的搜索过程:
def mse_quantization(tensor, search_range=1000):
min_val, max_val = tensor.min(), tensor.max()
best_scale = None
best_mse = float('inf')
# 网格搜索 scale
for i in range(search_range):
scale = (max_val - min_val) / 255 * (i / search_range)
# 量化 + 反量化
quant = np.round(tensor / scale).clip(-128, 127)
dequant = quant * scale
mse = np.mean((tensor - dequant) ** 2)
if mse < best_mse:
best_mse = mse
best_scale = scale
return best_scale
3.4 三种方法对比:什么时候用哪个?
直接上表格,一目了然:
| 方法 | 计算量 | 精度保留 | 异常值鲁棒性 | 推荐场景 |
|---|---|---|---|---|
| MinMax | 极低 | 一般 | 差 | 快速验证、均匀分布 |
| KL 散度 | 中等 | 较好 | 中等 | 非均匀分布、激活值量化 |
| MSE | 较高 | 最好 | 好 | 权重量化、精度敏感场景 |
3.5 我的实战建议
做了这么多量化项目,我总结了一套「三步走」策略:
- 先用 MinMax 跑 baseline,看看精度损失。如果损失 < 0.5%,直接收工。
- 如果损失大,换 KL 散度,特别是量化激活值时。校准数据选 500 条左右,跑一遍。
- 还不行?上 MSE,重点量化权重层。注意网格搜索的步长,别太粗也别太细。
嗯,PTQ 的三种方法就聊到这儿。MinMax 简单直接,KL 散度适合激活值,MSE 最稳妥但计算量大。实际项目中,我建议你灵活组合,别死磕一种方法。