4.7 KiB
4.7 KiB
量化基础与INT8
量化概述
量化(Quantization)是将模型参数和计算从高精度(FP32/FP16)转换为低精度(INT8/INT4/INT2)表示的技术,目的是降低显存占用、加速推理、减少计算量。
量化优势
| 精度 | 内存占用 | 计算量(相对FP32) | 精度损失 |
|---|---|---|---|
| FP32 | 4 bytes/param | 1x | 无 |
| FP16/BF16 | 2 bytes/param | ~1x | 极小 |
| INT8 | 1 byte/param | ~0.25x | 较小 |
| INT4 | 0.5 byte/param | ~0.125x | 中等 |
| INT2 | 0.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
量化误差分析
量化误差来源
- 截断误差:超出量化范围的值被截断
- 舍入误差:round操作的近似误差
- 尺度误差: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
| 特性 | PTQ | QAT |
|---|---|---|
| 训练需求 | 不需要 | 需要微调 |
| 精度 | 较低 | 较高 |
| 成本 | 低 | 中等 |
| 适用场景 | 快速部署 | 追求精度 |