第4章:权重量化(Quantization)——让模型瘦身还能跑得快
量化,说白了就是把模型里那些高精度的浮点数(FP32),换成低精度的整数(INT8、INT4)。为什么要这么干?因为浮点数在嵌入式设备上跑起来太慢了,而且占内存。我最早接触量化是在一个智能门锁项目上,芯片只有2MB的Flash,模型压缩前根本塞不进去。嗯,量化就是那个救场的技术。
4.1 量化原理:对称 vs 非对称
量化的核心就一句话:用更少的比特数表示原来的数值范围。但怎么映射,就有讲究了。
4.1.1 对称量化
对称量化,就是让0映射到0。权重和激活值的正负范围是对称的。公式很简单:
Q = round( x / scale )
scale = max(|x|) / (2^(b-1) - 1)
举个例子,FP32的权重范围是[-6.0, 6.0],量化到INT8(-127~127)。那么scale = 6.0 / 127 ≈ 0.0472。0.0映射到0,-6.0映射到-127,6.0映射到127。
我个人习惯:对称量化对权重效果不错,因为权重分布通常接近0对称。但激活值就不一定了,比如ReLU后的输出全是非负的,这时候用对称量化会浪费一半的表示范围。
4.1.2 非对称量化
非对称量化引入了零点(zero-point)的概念,可以偏移量化范围:
Q = round( x / scale ) + zero_point
scale = (max - min) / (2^b - 1)
zero_point = round( -min / scale )
这样,不管数据分布是正偏还是负偏,都能充分利用量化范围。我在一个语音唤醒模型上试过,激活值用非对称量化比对称量化精度高了0.3%,别小看这0.3%,在唤醒率上可能就是“听得见”和“听不见”的区别。
| 类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 对称量化 | 实现简单,计算快 | 非对称分布时浪费范围 | 权重、接近0对称的激活值 |
| 非对称量化 | 充分利用量化范围 | 需要额外存储零点 | ReLU后的激活值、偏态分布 |
4.2 训练后量化(PTQ)—— 最快上手的方式
PTQ,就是模型训练完了,我再给它做量化。不需要重新训练,只需要一小部分校准数据。流程如下:
- 加载训练好的FP32模型
- 准备几百张校准图片(或几百条数据)
- 跑一遍前向推理,统计每层的min/max或直方图
- 根据统计结果计算scale和zero_point
- 把权重和激活值量化到INT8
代码示例(PyTorch + torch.quantization):
import torch
from torch.quantization import quantize_dynamic, QuantStub, DeQuantStub
# 定义模型时插入量化/反量化节点
class QuantModel(torch.nn.Module):
def __init__(self):
super().__init__()
self.quant = QuantStub()
self.conv = torch.nn.Conv2d(3, 64, 3)
self.relu = torch.nn.ReLU()
self.dequant = DeQuantStub()
def forward(self, x):
x = self.quant(x)
x = self.conv(x)
x = self.relu(x)
x = self.dequant(x)
return x
model = QuantModel()
model.eval()
# 准备校准数据(示例)
calibration_data = [torch.randn(1, 3, 224, 224) for _ in range(100)]
# 校准
model.qconfig = torch.quantization.get_default_qconfig('fbgemm')
torch.quantization.prepare(model, inplace=True)
for data in calibration_data:
model(data)
torch.quantization.convert(model, inplace=True)
# 现在model已经是INT8量化模型了
print(model)
避坑指南:我曾经在一个项目里只用10张图片做校准,结果量化后精度掉了5%。后来改成200张,精度只掉了0.5%。校准数据量太少,统计出来的min/max不准确。建议至少100-500张,覆盖各种典型场景。
4.3 量化感知训练(QAT)—— 精度不够?那就练一练
PTQ虽然快,但有时候精度掉得厉害。尤其是小模型,比如MobileNetV2,量化后精度可能掉2-3%。这时候就得用QAT了。
QAT的原理:在训练过程中模拟量化误差,让模型自己学会适应。具体做法是在前向传播时插入fake_quant节点,把权重和激活值先量化再反量化,这样反向传播时梯度就能感知到量化误差。
import torch
from torch.quantization import FakeQuantize, MovingAverageMinMaxObserver
# 在训练中插入fake_quant
class QATModel(torch.nn.Module):
def __init__(self):
super().__init__()
self.quant = torch.quantization.QuantStub()
self.conv = torch.nn.Conv2d(3, 64, 3)
self.relu = torch.nn.ReLU()
self.dequant = torch.quantization.DeQuantStub()
def forward(self, x):
x = self.quant(x)
x = self.conv(x)
x = self.relu(x)
x = self.dequant(x)
return x
model = QATModel()
model.train()
# 配置QAT
model.qconfig = torch.quantization.get_default_qat_qconfig('fbgemm')
torch.quantization.prepare_qat(model, inplace=True)
# 正常训练循环
optimizer = torch.optim.SGD(model.parameters(), lr=0.001)
for epoch in range(10):
for data, target in train_loader:
optimizer.zero_grad()
output = model(data)
loss = torch.nn.functional.cross_entropy(output, target)
loss.backward()
optimizer.step()
# 训练完成后转为量化模型
model.eval()
torch.quantization.convert(model, inplace=True)
我建议:QAT的训练轮次不用太多,一般5-10个epoch就够了。因为模型已经收敛了,我们只是让它适应量化噪声。训练太多反而可能过拟合校准数据。
4.4 INT8/INT4量化实战
INT8是目前最主流的量化位宽,几乎所有的推理框架都支持。但INT4呢?它能把模型再压缩一半,但精度损失更大。
我在一个智能音箱项目上做过对比:
| 量化位宽 | 模型大小 | 推理速度(ms) | 精度(Top-1) |
|---|---|---|---|
| FP32 | 45 MB | 120 | 72.5% |
| INT8 | 11.5 MB | 35 | 72.1% |
| INT4 | 6.2 MB | 22 | 70.8% |
INT8只掉了0.4%的精度,但INT4掉了1.7%。如果你的应用对精度要求极高,INT4要慎用。不过,如果模型本身很大(比如ResNet-152),INT4的精度损失反而会小一些,因为冗余度高。
注意:不是所有硬件都支持INT4。我在一个Cortex-M4芯片上试过,它只支持INT8的SIMD指令,INT4需要软件模拟,速度反而更慢。选型前一定要查清楚目标芯片的指令集支持情况。
4.5 精度损失如何补救
量化后精度掉了,怎么办?别慌,我有几个实战经验:
- 混合精度量化:不是所有层都适合量化。比如第一层和最后一层,对精度影响最大,可以保留FP32。中间层用INT8。我在一个目标检测模型上试过,只把第一层和最后一层保留FP32,精度就恢复了1.2%。
- 逐通道量化:默认是逐层量化(整个卷积层用一个scale),但不同通道的数值范围可能差异很大。逐通道量化(per-channel)给每个通道单独算scale,精度能提升0.3-0.5%。代价是计算量稍微增加。
- 知识蒸馏+量化:先用大模型(Teacher)指导小模型(Student)训练,然后再量化。我在一个语音识别模型上试过,蒸馏后的模型量化精度比直接量化高了0.8%。
- 量化后微调:量化完模型后,用少量数据再做几个epoch的微调。注意要冻结量化参数,只更新权重。这相当于让权重“适应”量化后的数值范围。
我曾经遇到一个极端情况:一个语义分割模型量化后,边界区域出现了大量噪点。后来发现是激活值的分布太宽,量化后细节丢失了。解决办法是把激活值做clip,限制到[-6, 6]范围内再量化,效果立竿见影。
4.6 知识体系总览
下面这张图总结了本章的核心逻辑,从量化原理到实战补救,一目了然:
量化这条路,说白了就是用精度换速度。但怎么换得值,换得稳,就得靠经验了。我建议你从PTQ开始,如果精度达标就直接用。不达标再上QAT或混合精度。别一上来就搞复杂的,先跑通再说。
公众号:蓝海资料掘金营,微信deep3321