量化基础与INT8

量化概述

量化(Quantization)是将模型参数和计算从高精度(FP32/FP16)转换为低精度(INT8/INT4/INT2)表示的技术,目的是降低显存占用、加速推理、减少计算量。

量化优势

精度内存占用计算量(相对FP32)精度损失
FP324 bytes/param1x
FP16/BF162 bytes/param~1x极小
INT81 byte/param~0.25x较小
INT40.5 byte/param~0.125x中等
INT20.25 byte/param~0.0625x较大

量化基本原理

量化映射

将连续值映射到离散值域:

x_float = scale * x_quant + zero_point
x_quant = round((x_float - zero_point) / scale)

其中:

  • scale:量化缩放因子
  • zero_point:零点(用于非对称量化)

量化精度类型

1. 对称量化(Symmetric Quantization)

使用零对称的映射,zero_point = 0:

x_quant = round(x_float / scale)
x_float = x_quant * scale

scale = max(|x_float|) / 127  (INT8范围: -127~127)

特点

  • 数值范围关于0对称
  • 适合权重分布接近正态分布的情况
  • 实现简单

2. 非对称量化(Asymmetric Quantization)

允许zero_point非0:

scale = (max(x_float) - min(x_float)) / 255
zero_point = -min(x_float) / scale

x_quant = round((x_float - zero_point) / scale)
x_float = scale * x_quant + zero_point

特点

  • 可处理偏置分布
  • 适合激活值(通常不是零对称)

量化粒度

Per-Tensor量化

整个张量使用同一个scale和zero_point:

scale = max(|W|) / 127
W_quant = round(W / scale)

优点:实现简单,通信量小 缺点:精度损失较大(受最值限制)

Per-Channel量化

按通道(layer)分别设置scale:

对于Linear层 weight shape: (out_features, in_features)
每个out_channel有独立的scale

优点:精度更高,每个通道自适应范围 缺点:通信量增加(需传输多个scale)

Per-Token量化

按token维度量化(主要用于激活值):

每行(sequence length维度)有独立scale
适合Attention score等动态范围大的张量

INT8推理流程

推理框架

Input FP32 → Quantize → INT8 GEMM → Dequantize → Output FP32
           ↓
        Lookup Table (可选)

核心计算:INT8 GEMM

硬件支持INT8矩阵乘法加速(如NVIDIA Tensor Core):

  • INT8 输入: (M, K) ⊗ (K, N) → INT32 累加
  • INT32 → FP16/FP32 反量化输出

量化流程示例

# 权重量化
W_fp32 = weight.data
W_max = W_fp32.abs().max()
W_scale = W_max / 127.0
W_int8 = (W_fp32 / W_scale).round().clamp(-128, 127)
 
# 激活量化(动态)
X_fp32 = input.data
X_scale = X_fp32.abs().max(dim=-1, keepdim=True)[0] / 127.0
X_int8 = (X_fp32 / X_scale).round().clamp(-128, 127)
 
# INT8矩阵乘法
Y_int32 = F.linear(X_int8, W_int8, bias=None)
 
# 反量化
Y_fp32 = Y_int32 * X_scale * W_scale

量化误差分析

量化误差来源

  1. 截断误差:超出量化范围的值被截断
  2. 舍入误差:round操作的近似误差
  3. 尺度误差:scale选取不最优

误差度量

# 量化误差计算
error = W_fp32 - W_dequantized  # 反量化后误差
mse = (error ** 2).mean()
snr = 10 * torch.log10(var(W_fp32) / var(error))

误差传播

量化误差在网络中的传播:

  • 卷积层:误差累积,但可被激活函数平滑
  • 残差连接:误差直接累积,影响显著
  • Softmax:指数放大误差,需特别注意

训练后量化(Post-Training Quantization, PTQ)

静态量化(Static Quantization)

预计算激活值范围,需要校准数据集:

model.eval()
model.qconfig = QConfig(activation=None, weight=None)  # 指定量化配置
torch.quantization.prepare(model, inplace=True)
# 校准
with torch.no_grad():
    for data in calibration_loader:
        model(data)
torch.quantization.convert(model, inplace=True)

动态量化(Dynamic Quantization)

运行时动态决定激活范围:

# 权重静态量化,激活动态量化
model_dynamic = torch.quantization.quantize_dynamic(
    model, {nn.Linear}, dtype=torch.qint8
)

量化感知训练(Quantization-Aware Training, QAT)

在前向传播中模拟量化效果:

# Fake quantization
x_quant = (x / scale).round() * scale
 
# 使用STE (Straight-Through Estimator) 反向传播
# gradient = gradient.round() * scale ≈ gradient

QAT vs PTQ

特性PTQQAT
训练需求不需要需要微调
精度较低较高
成本中等
适用场景快速部署追求精度