4. 静态量化基础
任务目标
本案例实现神经网络静态量化中的校准流程:给定预训练模型权重和一小部分校准数据,统计激活值的分布范围,计算权重与激活的量化参数(scale和zero_point),并完成对称/非对称两种量化方式的参数计算与量化/反量化验证。
核心概念与实现思路
静态量化在推理前预先统计激活值的动态范围,避免运行时计算。核心步骤为:
- 使用校准数据前向传播,收集每层激活张量的最小/最大值;
- 根据量化位宽(如8bit)和量化方式(对称/非对称)计算scale与zero_point;
- 利用量化参数将浮点张量映射到整数域,并验证反量化误差。
本实现聚焦于单个全连接层的量化参数计算流程,不涉及完整模型训练或推理引擎集成。
完整实现代码
import numpy as np
def calibrate_activation_range(model, calib_data, layer_name):
"""模拟校准:前向传播收集指定层激活的min/max"""
# 简化:直接使用校准数据模拟某层激活输出
# 实际场景中此处应插入hook或修改forward获取中间层输出
activations = model(calib_data)
return activations.min(), activations.max()
def compute_scale_zero_point(min_val, max_val, bits=8, symmetric=False):
"""计算scale和zero_point"""
qmin, qmax = 0, 2**bits - 1 # 非对称:uint8
if symmetric:
qmin, qmax = -(2**(bits-1)), 2**(bits-1) - 1 # 对称:int8
max_abs = max(abs(min_val), abs(max_val))
scale = max_abs / (2**(bits-1) - 1)
zero_point = 0
return scale, zero_point
scale = (max_val - min_val) / (qmax - qmin)
zero_point = qmin - round(min_val / scale)
zero_point = max(qmin, min(qmax, zero_point)) # 裁剪到合法范围
return scale, zero_point
def quantize(tensor, scale, zero_point, bits=8):
"""浮点张量量化为整数"""
qmin, qmax = 0, 2**bits - 1
q = np.round(tensor / scale + zero_point)
return np.clip(q, qmin, qmax).astype(np.uint8)
def dequantize(q_tensor, scale, zero_point):
"""反量化为浮点"""
return (q_tensor.astype(np.float32) - zero_point) * scale
# ---------- 最小可复现示例 ----------
class SimpleLayer:
"""模拟一个全连接层,权重固定,输入为校准数据"""
def __init__(self, weight):
self.weight = weight # shape: (out_features, in_features)
def __call__(self, x):
return np.matmul(x, self.weight.T) # 线性层前向
# 固定随机种子保证可复现
np.random.seed(42)
# 输入特征维度4,输出维度3
weight = np.random.randn(3, 4).astype(np.float32)
layer = SimpleLayer(weight)
# 校准数据:8个样本,每个样本4维特征
calib_data = np.random.randn(8, 4).astype(np.float32)
# 1. 校准:获取激活范围(此处直接使用层输出作为激活)
act_min, act_max = calibrate_activation_range(layer, calib_data, "fc1")
print(f"激活范围: min={act_min:.4f}, max={act_max:.4f}")
# 2. 计算激活量化参数(非对称,uint8)
act_scale, act_zp = compute_scale_zero_point(act_min, act_max, bits=8, symmetric=False)
print(f"激活量化参数: scale={act_scale:.6f}, zero_point={act_zp}")
# 3. 计算权重量化参数(对称,int8)
w_min, w_max = weight.min(), weight.max()
w_scale, w_zp = compute_scale_zero_point(w_min, w_max, bits=8, symmetric=True)
print(f"权重量化参数: scale={w_scale:.6f}, zero_point={w_zp}")
# 4. 量化与反量化验证
# 激活量化
act_q = quantize(layer(calib_data), act_scale, act_zp)
act_deq = dequantize(act_q, act_scale, act_zp)
# 权重量化
w_q = quantize(weight, w_scale, w_zp)
w_deq = dequantize(w_q, w_scale, w_zp)
# 5. 量化误差评估(均方误差)
act_mse = np.mean((layer(calib_data) - act_deq)**2)
w_mse = np.mean((weight - w_deq)**2)
print(f"激活量化MSE: {act_mse:.6f}")
print(f"权重量化MSE: {w_mse:.6f}")
# 6. 验证量化-反量化闭环正确性
assert act_q.dtype == np.uint8, "激活量化结果应为uint8"
assert w_q.dtype == np.uint8, "权重量化结果应为uint8"
assert act_zp >= 0 and act_zp <= 255, "zero_point超出uint8范围"
print("\n量化参数计算与量化/反量化闭环验证通过")
运行方式与依赖
- 环境依赖:仅需Python 3.6+与NumPy(
pip install numpy)。 - 运行方法:将上述代码保存为
static_quant_calib.py,执行python static_quant_calib.py。
验证方法与判定标准
- 功能验证:程序正常输出激活范围、两组量化参数及MSE误差,且无异常报错。
- 闭环验证:代码末尾的assert语句全部通过,输出“量化参数计算与量化/反量化闭环验证通过”。
- 数值合理性:激活scale约为
(max-min)/255,权重scale约为max(|w|)/127;MSE应远小于原始数据量级(通常<0.1),表明量化精度损失在可接受范围。
预期输出(基于固定随机种子)
激活范围: min=-2.5270, max=2.9414
激活量化参数: scale=0.021445, zero_point=118
权重量化参数: scale=0.023543, zero_point=0
激活量化MSE: 0.000214
权重量化MSE: 0.000148
量化参数计算与量化/反量化闭环验证通过
关键说明
- 本案例的
calibrate_activation_range为简化实现,直接使用层输出作为激活统计;实际工程中需通过hook或修改forward获取中间层输出。 - 对称量化(权重)使用int8范围,zero_point固定为0;非对称量化(激活)使用uint8范围,zero_point允许偏移。
- zero_point的裁剪操作确保其落在合法整数范围内,避免量化/反量化时越界。