4、激活量化:动态量化与静态量化的实现与权衡
聊完权重量化,咱们得聊聊另一个大头——激活量化。
权重是模型学到的参数,训练完就固定了。但激活值不一样,它是每层计算出来的中间结果。你想想看,输入一张猫的图片和输入一张狗的图片,中间层的激活值分布能一样吗?
这就带来了一个核心问题:激活值的范围是动态变化的。
所以,激活量化比权重量化要棘手得多。我刚开始做量化的时候,在权重量化上顺风顺水,一到激活量化就翻车。模型直接输出乱码,跟中了邪似的。
目前主流的做法分两种:动态量化 和 静态量化。咱们一个一个拆开看。
4.1 动态量化:实时计算,灵活但慢
动态量化,说白了就是每次推理时,实时统计当前 batch 的激活值范围,然后当场做量化。
它的流程是这样的:
- 拿到当前层的激活值(比如一个 4D 张量)
- 统计它的 min 和 max(或者用其他校准方法)
- 根据这个范围计算 scale 和 zero_point
- 用这个 scale 做量化计算
代码实现其实不复杂:
import torch
def dynamic_quantize_activation(x, num_bits=8):
# x: 输入激活值张量
min_val = x.min()
max_val = x.max()
# 计算 scale 和 zero_point
qmin = 0
qmax = 2 ** num_bits - 1
scale = (max_val - min_val) / (qmax - qmin)
zero_point = qmin - min_val / scale
zero_point = torch.round(zero_point).clamp(qmin, qmax)
# 量化
x_q = torch.round(x / scale + zero_point).clamp(qmin, qmax)
# 反量化(模拟量化效果)
x_deq = (x_q - zero_point) * scale
return x_deq, scale, zero_point
嗯,这里要注意:动态量化每次都要做 min/max 统计。这个操作本身是有开销的。我在项目中遇到过,对于小模型(比如 MobileNet),动态量化的额外开销甚至能抵消掉量化带来的加速收益。
适用场景:
- 模型结构不稳定,经常改
- 输入分布变化剧烈(比如 NLP 任务中不同长度的句子)
- 对精度要求极高,不能接受校准误差
- 推理框架支持动态量化(PyTorch 原生支持较好)
4.2 静态量化:离线校准,快但需要调
静态量化就不一样了。它提前用一批校准数据跑一遍模型,统计出每层激活值的典型范围。然后把这个范围固定下来,推理时直接用。
我个人习惯用 500-1000 张图片做校准。太少统计不准,太多浪费时间。
校准过程的核心是收集激活值的直方图:
import torch
import numpy as np
class CalibrationCollector:
def __init__(self, num_bins=2048):
self.histograms = {} # 每层一个直方图
self.num_bins = num_bins
def collect(self, layer_name, activation):
# 将激活值展平并统计直方图
act_flat = activation.detach().cpu().numpy().flatten()
hist, edges = np.histogram(act_flat, bins=self.num_bins)
if layer_name not in self.histograms:
self.histograms[layer_name] = {'hist': hist, 'edges': edges}
else:
# 合并多个 batch 的直方图
self.histograms[layer_name]['hist'] += hist
def get_scale(self, layer_name, quantile=0.999):
# 根据分位数确定量化范围
hist = self.histograms[layer_name]['hist']
edges = self.histograms[layer_name]['edges']
total = hist.sum()
cumsum = np.cumsum(hist)
threshold_idx = np.searchsorted(cumsum, total * quantile)
return edges[threshold_idx] # 返回 max_val
这里有个坑,我曾经踩过:校准数据必须能代表真实推理时的数据分布。有一次我用 ImageNet 的验证集做校准,结果部署时用户上传的都是自拍照片,激活值分布完全不一样,模型精度直接掉了 5 个点。
避坑指南:
我曾经犯过一个低级错误——校准数据里全是白天拍的图片,结果模型在夜间场景下直接崩了。后来我学乖了,校准数据集至少要覆盖 80% 以上的真实场景。
4.3 动态 vs 静态:一张表说清楚
| 对比维度 | 动态量化 | 静态量化 |
|---|---|---|
| 校准方式 | 实时统计 | 离线校准 |
| 推理速度 | 较慢(有统计开销) | 快(无额外开销) |
| 精度损失 | 小(自适应) | 略大(固定范围) |
| 部署复杂度 | 低(无需校准数据) | 高(需要校准流程) |
| 硬件适配 | CPU 友好 | GPU/NPU 友好 |
| 典型框架 | PyTorch 动态量化 | TensorRT、ONNX Runtime |
4.4 知识体系:激活量化的核心逻辑
为了让你更直观地理解,我画了一张图:
4.5 实际项目中的选择建议
说了这么多,到底该选哪个?我根据经验给几个建议:
- CPU 部署 + 追求精度:选动态量化。PyTorch 的
torch.quantization.quantize_dynamic一行代码搞定,省心。 - GPU/NPU 部署 + 追求速度:选静态量化。TensorRT 的 INT8 校准流程虽然麻烦,但推理速度能翻倍。
- 模型经常更新:选动态量化。每次更新都要重新校准,太折腾了。
- 输入分布极其稳定:选静态量化。比如人脸识别,输入都是对齐后的人脸,分布很固定。
我的小技巧:
如果你不确定选哪个,可以先用动态量化跑一遍,记录下每层激活值的 min/max 范围。然后把这些范围作为静态量化的初始值。这样既保证了精度,又享受了静态量化的速度。我在一个 OCR 项目里就是这么干的,效果出奇的好。
最后说一句,没有银弹。动态量化和静态量化各有各的适用场景。关键是要理解你的模型、你的数据、你的部署环境。多试几次,你就能找到最适合的方案。