# 量化基础与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 反量化输出 ### 量化流程示例 ```python # 权重量化 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选取不最优 ### 误差度量 ```python # 量化误差计算 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) 预计算激活值范围,需要校准数据集: ```python 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) 运行时动态决定激活范围: ```python # 权重静态量化,激活动态量化 model_dynamic = torch.quantization.quantize_dynamic( model, {nn.Linear}, dtype=torch.qint8 ) ``` ## 量化感知训练(Quantization-Aware Training, QAT) 在前向传播中模拟量化效果: ```python # Fake quantization x_quant = (x / scale).round() * scale # 使用STE (Straight-Through Estimator) 反向传播 # gradient = gradient.round() * scale ≈ gradient ``` ### QAT vs PTQ | 特性 | PTQ | QAT | |------|-----|-----| | 训练需求 | 不需要 | 需要微调 | | 精度 | 较低 | 较高 | | 成本 | 低 | 中等 | | 适用场景 | 快速部署 | 追求精度 |