模型量化基础:量化原理、定点与浮点、量化参数
3.1 为什么要量化:从连续世界到离散表示
深度学习模型在训练阶段通常使用 FP32(32位浮点数) 存储权重和激活值,以获得足够的数值精度和梯度稳定性。但在推理部署阶段,我们面对的是另一组约束:内存带宽有限、算力芯片(尤其是边缘设备)对浮点运算支持较弱、功耗预算紧张。模型量化正是为了解决这一矛盾而生的——它把连续分布的浮点数值映射到离散的整数集合上,用更少的比特位表达相近的信息。
量化的本质是有损压缩,但优秀的量化方案能让精度损失控制在可接受范围内。以 INT8 量化为例,模型体积直接缩小到原来的 1/4,推理速度在支持整数指令集的硬件上可提升 2~4 倍。对于 7B 甚至更大规模的模型,这种压缩带来的部署收益是决定性的。
3.2 定点数与浮点数:两种数字世界观
要理解量化,必须先厘清定点数(Fixed-Point)与浮点数(Floating-Point)的差异。浮点数用科学计数法表示数值,由符号位、指数位和尾数位组成,能动态适应从 1e-38 到 3.4e38 的巨大范围,但代价是每一位的“分辨率”不均匀——数值越大,相邻两个可表示数之间的间隔越大。
定点数则固定小数点的位置,用整数部分和小数部分的固定位宽来表示数值。例如 Q7.8 格式表示 7 位整数、8 位小数,总共 16 位。定点数的优势在于:均匀分辨率、硬件实现简单(整数加减乘除即可)、可预测的精度。缺点也很明显:动态范围有限,超出范围就会饱和溢出。
| 特性 | FP32 浮点 | INT8 定点(量化后) |
|---|---|---|
| 位宽 | 32 位(1符号 + 8指数 + 23尾数) | 8 位(1符号 + 7数值) |
| 数值范围 | 约 ±3.4e38 | -128 ~ 127 |
| 分辨率 | 不均匀,大数间隔大 | 均匀,固定步长 |
| 硬件成本 | 需要浮点运算单元(FPU) | 整数ALU即可,功耗低 |
| 典型用途 | 训练、高精度科学计算 | 推理部署、边缘计算 |
ONNX 量化主要面向定点整数格式(INT8、UINT8、INT16 等),因为整数运算在 CPU、GPU、NPU 上都有极高的吞吐优化。
3.3 量化参数:scale 与 zero-point 的数学含义
将浮点张量 r 映射到整数张量 q,需要两个关键参数:缩放因子 scale(s) 和 零点 zero-point(z)。映射公式如下:
// 浮点 → 整数(量化)
q = round(r / s) + z
// 整数 → 浮点(反量化)
r = (q - z) * s
其中:
- scale(s):一个正浮点数,表示整数中“1”代表的实际浮点步长。它由浮点数据的动态范围决定:
s = (rmax - rmin) / (qmax - qmin)。 - zero-point(z):整数域中对应浮点 0 的值。它保证了浮点 0 能被精确表示,这对卷积神经网络中大量存在的 padding 操作至关重要——padding 值通常是 0,如果零点不准确,padding 区域会引入噪声。
scale = (6.0 - (-2.0)) / (127 - (-128)) = 8.0 / 255 ≈ 0.03137
zero-point = round(-(-2.0) / 0.03137) - 128 = round(63.75) - 128 = 64 - 128 = -64
于是浮点值 0 对应整数 q = round(0 / 0.03137) + (-64) = -64,反量化 (-64 - (-64)) * 0.03137 = 0,精确无误。
根据 zero-point 是否为 0,量化分为两种对称形式:
- 对称量化(Symmetric):z = 0,浮点范围对称于原点(如 [-a, a])。实现简单,适合权重分布近似对称的情况。
- 非对称量化(Asymmetric):z ≠ 0,能更充分利用整数范围,适合激活值(通常经过 ReLU 后非负)这类分布偏斜的数据。
3.4 量化粒度:张量级、通道级与逐元素
scale 和 zero-point 并非只能对整个张量统一设置。根据共享参数的范围,量化粒度分为三个层级:
- 逐张量量化(Per-Tensor):整个权重张量共用一个 scale 和 zero-point。实现最简,但若张量内数值分布差异大(如深度可分离卷积的 depthwise 层),精度损失明显。
- 逐通道量化(Per-Channel):对卷积核的每个输出通道分别计算 scale 和 zero-point。这是目前 CNN 量化的主流选择,能显著提升精度,ONNX Runtime 和 TensorRT 均支持。
- 逐组量化(Per-Group):将通道进一步细分为小组(如每组 128 个元素),常用于大模型权重量化(如 GPTQ、AWQ),在精度与存储之间取得平衡。
3.5 量化误差的来源与缓解策略
量化误差主要来自三个方面:
- 舍入误差(Rounding Error):
round()操作不可避免,均匀分布在 ±0.5 个 scale 步长内。 - 截断误差(Clipping Error):超出 [qmin, qmax] 范围的浮点值被强制截断,若数据分布有长尾,误差会很大。
- 异常值(Outliers):个别极大或极小的值会拉宽动态范围,导致 scale 变大、整体分辨率下降。
缓解策略包括:MinMax 校准(直接取 min/max)、百分位裁剪(忽略 0.1% 极端值)、KL 散度校准(TensorRT 采用,寻找信息损失最小的阈值)。在 ONNX 生态中,可使用 onnxruntime.quantization 工具库配合校准数据集完成这些操作。
# ONNX Runtime 静态量化示例(伪代码)
from onnxruntime.quantization import quantize_static, QuantType
from onnxruntime.quantization.calibrate import CalibrationDataReader
# 准备校准数据读取器(通常使用验证集子集)
calib_reader = YourCalibrationDataReader(calibration_data)
# 执行静态量化
quantize_static(
model_input="model_fp32.onnx",
model_output="model_int8.onnx",
calibration_data_reader=calib_reader,
quant_format=QuantType.QInt8, # 使用 INT8
per_channel=True, # 逐通道量化
weight_type=QuantType.QInt8,
activation_type=QuantType.QInt8
)
3.6 案例复盘:一个典型的 INT8 量化流程
假设我们有一个训练好的 ResNet-18 分类模型(FP32,约 45MB),目标部署到 ARM 边缘设备。完整流程如下:
- 数据准备:从验证集抽取 500 张图片作为校准集,覆盖各类别。
- 校准:运行模型前向,统计每层激活值的 min/max 分布,计算 scale 和 zero-point。
- 量化:将权重和激活值转换为 INT8,生成量化后的 ONNX 模型。
- 验证:在测试集上对比 FP32 与 INT8 的 Top-1 精度。通常损失应 < 1%。
- 部署:使用 ONNX Runtime 的
EP(Execution Provider)加载 INT8 模型,在 ARM 上测试延迟。
若采用逐张量量化,精度可能降至 67.5%;改用逐通道后恢复至 69.2%——可见量化粒度的重要性。
量化不是银弹。对于极深或注意力机制密集的模型(如 Transformer),直接 INT8 量化可能损失较大,此时需考虑混合精度(部分层保持 FP16)或更高级的量化感知训练(QAT)。理解 scale 与 zero-point 的数学本质,是后续学习 QAT、GPTQ 等进阶技术的地基。