2. 定点数量化与数据格式转换
任务目标
设计一个INT8定点数量化模块,将浮点权重和激活值转换为定点数,并验证量化前后的精度损失。本案例聚焦于对称均匀量化方案,覆盖量化参数计算、浮点转定点、定点转浮点(反量化)的完整链路。
量化方案与数据格式
采用对称INT8量化:scale = max_abs / 127,浮点值 f 映射为 q = round(f / scale),反量化为 f_hat = q * scale。该方案无需零点偏移,适合权重和ReLU后的激活值。
核心代码(Python实现)
import numpy as np
def compute_scale(tensor):
"""计算对称量化scale"""
max_abs = np.max(np.abs(tensor))
if max_abs == 0:
return 1.0
return max_abs / 127.0
def quantize_to_int8(tensor, scale):
"""浮点张量 → INT8定点数(返回int8数组)"""
q = np.round(tensor / scale)
q = np.clip(q, -128, 127)
return q.astype(np.int8)
def dequantize_to_float(q_tensor, scale):
"""INT8定点数 → 浮点张量(反量化)"""
return q_tensor.astype(np.float32) * scale
def quantization_error(orig, dequantized):
"""计算量化前后均方误差(MSE)与相对误差"""
mse = np.mean((orig - dequantized) ** 2)
norm_orig = np.linalg.norm(orig)
rel_err = np.linalg.norm(orig - dequantized) / norm_orig if norm_orig > 0 else 0
return mse, rel_err
# ===== 验证流程 =====
if __name__ == "__main__":
# 模拟一组浮点权重(正态分布,含正负值)
np.random.seed(42)
weights = np.random.randn(64, 64).astype(np.float32) * 0.5
# 模拟ReLU后的激活值(非负)
activations = np.abs(np.random.randn(128, 32).astype(np.float32)) * 0.3
for name, tensor in [("weights", weights), ("activations", activations)]:
scale = compute_scale(tensor)
q = quantize_to_int8(tensor, scale)
deq = dequantize_to_float(q, scale)
mse, rel_err = quantization_error(tensor, deq)
print(f"{name}: scale={scale:.6f}, "
f"INT8 range=[{q.min()}, {q.max()}], "
f"MSE={mse:.6e}, relative_err={rel_err:.4f}")
运行方式
python int8_quantization.py
验证方法与判定标准
运行脚本后观察输出:
- INT8范围检查:量化后数值必须在[-128, 127]内,输出中
q.min()与q.max()应落在此区间。 - 精度损失度量:记录MSE与相对误差。相对误差通常应小于5%(本例随机数据预期在1%~3%量级),若误差过大说明scale计算或舍入方式有误。
- 反量化正确性:反量化结果应与原始浮点值趋势一致,可通过打印若干对应值人工抽查。
若输出中INT8范围越界或相对误差异常(如>10%),检查np.clip边界与np.round是否按预期工作。
关键说明
- 本实现为对称量化,适用于权重和正激活值;若激活值含负值(如Leaky ReLU输出),需改用非对称量化或调整方案。
- 实际FPGA部署时,
scale通常以浮点常量传入硬件,INT8乘累加结果用INT32累加后再反量化,本案例覆盖了数据格式转换的核心数学链路。 - 随机种子固定为42,保证可复现;替换为真实模型权重时,只需将
weights/activations替换为实际张量即可。