Files
2026-05-16 17:16:51 +08:00

209 lines
4.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 量化基础与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 |
|------|-----|-----|
| 训练需求 | 不需要 | 需要微调 |
| 精度 | 较低 | 较高 |
| 成本 | 低 | 中等 |
| 适用场景 | 快速部署 | 追求精度 |