量化——低精度推理的工程实践 / Engineering Low-Precision Inference with Quantization
📅 创建时间:2026-06-03 🏷️ 标签:#量化 #Quantization #PTQ #QAT #INT8 #FP8 #BF16 #混合精度 #SmoothQuant #GPTQ #AWQ 📚 前置知识:[[/04-ai/01-llm-engineering/07-llm-evolution]](LLM 发展脉络) 📚 相关知识:[[18-cutlass]](CUTLASS) [[08-operator-fusion]](算子融合)
┌──────────────────────────────────────────────────────────────────────────────┐
│ 📌 场景:量化精度崩塌 │
├──────────────────────────────────────────────────────────────────────────────┤
│ 你做了一次 INT8 量化: │
│ • 模型:Llama-3-8B,FP16 推理速度 100ms/token │
│ • 量化后速度:20ms/token(快 5 倍) │
│ • 但 accuracy 测试:MMLU 从 68% 掉到 52%(掉了 16 个点!) │
│ │
│ 你完全没想到精度下降这么多。你不知道这是量化方法的问题, │
│ 还是有办法解决。 │
└──────────────────────────────────────────────────────────────────────────────┘第1节:量化基础理论
1.1 什么是量化?为什么需要量化?
量化(Quantization)是将模型参数和计算从高精度浮点数(如 FP32、FP16)转换为低精度整数(如 INT8)或更小的浮点数(如 FP8、BF16)的过程。
为什么需要量化?
| 精度格式 | 内存占用 | 计算速度 | 精度损失风险 | 硬件支持 |
|---|---|---|---|---|
| FP32 | 4 字节 | 基准 | 无 | 所有 GPU |
| FP16 | 2 字节 | 2x | 极低 | 所有 Ampere+ |
| BF16 | 2 字节 | 2x | 极低 | 所有 Ampere+ |
| INT8 | 1 字节 | 4x | 中等 | Tensor Core GPU |
| FP8 (E4M3) | 1 字节 | 4x+ | 中等 | Hopper+ |
| FP8 (E5M2) | 1 字节 | 4x+ | 较高 | Hopper+ |
| INT4 | 0.5 字节 | 8x | 较高 | 部分 GPU |
量化的核心价值:
- 内存节省:8B 模型 FP16 需要 16GB,INT8 只需 8GB,INT4 只需 4GB
- 带宽提升:INT8 的内存带宽需求是 FP16 的一半
- 计算加速:Tensor Core 对 INT8 有专门的矩阵乘法加速
1.2 对称量化 vs 非对称量化
对称量化(Symmetric Quantization):
- 使用单个 scale 参数
- 零点固定为 0
- 公式:
round(x / scale),其中scale = max(|x|) / 127 - 适用于 weights(分布相对对称)
# 对称量化实现
def symmetric_quantize(tensor, num_bits=8):
"""
对称量化:将浮点 tensor 量化为 int8
"""
# 计算 scale:确保原值范围 [-max_val, max_val] 映射到 [-127, 127]
max_val = torch.max(torch.abs(tensor))
scale = max_val / 127.0 # 量化范围是 -127 到 127(留出 -128 给特殊情况)
# 量化:除以 scale 然后 round
quantized = torch.round(tensor / scale)
# 限制在 [-127, 127] 范围内
quantized = torch.clamp(quantized, -127, 127)
# 反量化(用于验证)
dequantized = quantized * scale
return quantized.to(torch.int8), scale, dequantized
# 示例:对一个权重矩阵进行对称量化
weight = torch.randn(1024, 1024) * 2.5 # 模拟实际权重
q_weight, scale, dq_weight = symmetric_quantize(weight)
# 计算量化误差
error = torch.mean((weight - dq_weight) ** 2)
print(f"量化误差 (MSE): {error:.6f}")非对称量化(Asymmetric Quantization):
- 使用 scale 和 zero_point 两个参数
- 可以处理不对称分布
- 公式:
round((x - zero_point) / scale) - 适用于 activations(分布通常不对称)
# 非对称量化实现
def asymmetric_quantize(tensor, num_bits=8):
"""
非对称量化:用于处理不对称分布(如 ReLU 后的 activations)
"""
# 计算 min 和 max
min_val = torch.min(tensor)
max_val = torch.max(tensor)
# 计算 scale 和 zero_point
# 量化范围: [0, 255] for uint8
quant_min = 0
quant_max = 255
scale = (max_val - min_val) / (quant_max - quant_min)
zero_point = quant_min - min_val / scale
# 量化
quantized = torch.round(tensor / scale + zero_point)
quantized = torch.clamp(quantized, quant_min, quant_max)
# 反量化
dequantized = (quantized - zero_point) * scale
return quantized.to(torch.uint8), scale, zero_point, dequantized1.3 Per-tensor vs Per-channel 量化
Per-tensor 量化:整个 tensor 共享一个 scale
- 优点:实现简单,开销小
- 缺点:可能精度损失大(不同 channel 分布差异大时)
# Per-tensor 量化示意
# scale 对整个 tensor 是唯一的
# tensor: [C, H, W] -> 只有一个 global_scale
#
# 问题示例:某个 channel 有 outlier,会影响其他所有 channel 的精度
# Channel 0: 值范围 [-0.5, 0.5] -> 量化精度高
# Channel 1: 值范围 [-50, 50] -> 需要更大的 scale,Channel 0 的精度被牺牲Per-channel 量化:每个 channel 有独立的 scale
- 优点:更好地捕捉每个 channel 的分布,精度更高
- 缺点:需要存储更多 scale 参数(但相对权重本身很小)
# Per-channel 量化实现
def per_channel_quantize(tensor, num_bits=8, dim=0):
"""
Per-channel 量化:每个 channel 独立计算 scale
dim: 沿着哪个维度计算 channel(通常 dim=0 for linear weights)
"""
# tensor shape: [out_channels, in_channels]
# 每个 out_channel 有一个独立的 scale
# 计算每个 channel 的最大绝对值
max_vals = torch.max(torch.abs(tensor), dim=dim, keepdim=True)[0]
# 每个 channel 的 scale
scales = max_vals / 127.0
# 量化
quantized = torch.round(tensor / scales)
quantized = torch.clamp(quantized, -127, 127)
# 反量化
dequantized = quantized * scales
return quantized.to(torch.int8), scales, dequantized
# 示例:对 Linear 层的权重进行 per-channel 量化
# weight shape: [out_features, in_features]
# 每个 out_feature 是一个 channel,有独立的 scale
linear_weight = torch.randn(4096, 4096)
q_weight, scales, dq_weight = per_channel_quantize(linear_weight, dim=0)
# scales shape: [4096, 1] - 每个输出通道一个 scale
print(f"原始权重 shape: {linear_weight.shape}")
print(f"Scale 数量: {scales.numel()} (等于 out_features)")第2节:量化方法——PTQ vs QAT
2.1 PTQ(Post-Training Quantization)
PTQ 是在模型训练完成后进行量化的方法,不需要重新训练。
优势:
- 实现简单,不需要训练资源
- 快速部署,适合已训练好的模型
- 支持任意预训练模型
劣势:
- 精度损失可能较大(尤其是 INT8)
- 无法处理复杂的量化误差分布
# PyTorch 动态量化(最简单的方式)
import torch.quantization
# 动态量化:权重 INT8, activations FP32(推理时动态量化)
model_dynamic = torch.quantization.quantize_dynamic(
model, # 原始模型
{torch.nn.Linear, torch.nn.LSTM}, # 要量化的层类型
dtype=torch.qint8 # 量化精度
)
# 动态量化特点:
# - 只有权重被静态量化存储为 INT8
# - Activations 在计算时动态量化和反量化
# - 精度损失较小,但推理速度提升有限# PyTorch 静态量化(需要校准)
import torch.quantization
# 1. 模型准备:插入 QuantStub 和 DeQuantStub
model = Model()
model.eval()
model.qconfig = torch.quantization.get_default_qconfig('fbgemm') # x86 优化
# 对于 ARM: torch.quantization.get_default_qconfig('qnnpack')
# 2. 融合操作(conv + bn + relu -> fused)
torch.quantization.fuse_model(model)
# 3. 准备量化模型
model_prepared = torch.quantization.prepare(model)
# 4. 校准(使用代表性数据集)
calibration_data = load_calibration_data()
with torch.no_grad():
for batch in calibration_data:
model_prepared(batch)
# 5. 转换
model_int8 = torch.quantization.convert(model_prepared)
# 6. 推理
result = model_int8(input_data)校准(Calibration)的重要性:
# 校准过程详解
class CalibrationRunner:
"""
校准器:收集 activations 的统计信息,确定最佳量化参数
"""
def __init__(self, model):
self.model = model
self histograms = {} # 存储每个量化节点的直方图
self.collector = HistogramObserver()
def calibrate(self, calibration_loader, num_batches=100):
"""
运行校准:收集 activations 的分布信息
"""
self.model.eval()
with torch.no_grad():
for i, (data, _) in enumerate(calibration_loader):
if i >= num_batches:
break
self.model(data)
# 计算最佳 scale 和 zero_point
for name, observer in self.observer_store.items():
# MinMaxObserver: 使用 min/max
# HistogramObserver: 使用直方图 + KL 散度
# MovingAverageObserver: 使用移动平均
observer.calculate_qparams()2.2 QAT(Quantization-Aware Training)
QAT 在训练过程中模拟量化效果,让模型学会在低精度下保持精度。
优势:
- 精度损失更小(通常可以接近 FP16 水平)
- 模型可以适应量化带来的误差
劣势:
- 需要重新训练,计算成本高
- 需要训练数据和训练基础设施
# PyTorch QAT 实现
import torch.quantization
# 1. 模型准备(插入 fake quantization 节点)
model = Model()
model.qconfig = torch.quantization.get_default_qat_qconfig('fbgemm')
model_prepared = torch.quantization.prepare_qat(model)
# 2. 使用小学习率微调
optimizer = torch.optim.Adam(model_prepared.parameters(), lr=1e-5)
# 3. 训练几个 epoch
model_prepared.train()
for epoch in range(3): # 通常 1-3 个 epoch 就够了
for batch in train_loader:
optimizer.zero_grad()
output = model_prepared(batch)
loss = loss_fn(output, target)
loss.backward()
optimizer.step()
# 4. 转换为真正的 INT8
model_prepared.eval()
model_int8 = torch.quantization.convert(model_prepared)Fake Quantization 的工作原理:
# Fake quantization 等效实现
class FakeQuantize:
"""
模拟量化:在 forward 时执行量化-反量化过程
让梯度能够绕过 quantize 节点(Straight-Through Estimator)
"""
def __init__(self, scale, zero_point, quant_min, quant_max):
self.scale = scale
self.zero_point = zero_point
self.quant_min = quant_min
self.quant_max = quant_max
def forward(self, x):
# 量化
quantized = torch.round(x / self.scale + self.zero_point)
quantized = torch.clamp(quantized, self.quant_min, self.quant_max)
# 反量化
dequantized = (quantized - self.zero_point) * self.scale
# STE: 直通估计 - 梯度直接传回去,不经过量化
if self.training:
return dequantized + (x - x.detach())
return dequantized2.3 PTQ vs QAT 对比
| 维度 | PTQ | QAT |
|---|---|---|
| 实现复杂度 | 低 | 高 |
| 训练时间 | 无 | 1-3 epoch 微调 |
| 精度损失 | 中等(INT8 可能掉点严重) | 低(接近 FP16) |
| 适用场景 | 快速部署、演示 | 生产环境、严格精度要求 |
| 模型适配 | 无 | 有 |
| 资源需求 | 校准数据集 | 完整训练数据 + 计算资源 |
第3节:数值安全——Outlier 与解决方案
3.1 Outlier 问题
量化精度损失的主要原因是 outlier——某些 channel 的值远大于其他 channel。
# 演示 outlier 问题
import torch
# 模拟一个有 outlier 的 activation tensor
# 假设有 100 个 channel,其中一个 channel 有极端值
activations = torch.randn(100, 512) * 0.5 # 正常 channel
activations[0] = activations[0] * 20 # outlier channel:值大 20 倍
# Per-tensor 量化
scale = torch.max(torch.abs(activations)) / 127
q_act = torch.round(activations / scale)
q_act = torch.clamp(q_act, -127, 127)
dq_act = q_act * scale
# 问题:为了容纳 outlier channel,其他 99 个 channel 的精度被牺牲了
# 正常 channel 的信号范围被压缩到 ±6 左右
# 计算量化误差分布
errors = (activations - dq_act).abs()
print(f"Outlier channel 误差: {errors[0].mean():.4f}")
print(f"正常 channel 误差: {errors[1:].mean():.4f}")
print(f"误差比值: {errors[0].mean() / errors[1:].mean():.1f}x")3.2 SmoothQuant
SmoothQuant 的核心思想:将 activation 的 outlier 平滑到 weights 上。
# SmoothQuant 原理
"""
原始公式: Y = X @ W
量化: Y_q = round(X / s_x) @ round(W / s_w) * (s_x * s_w)
问题: 如果 X 有 outlier,会导致 s_x 很大,其他 channel 精度差
SmoothQuant 解法: 引入平滑因子 S
Y = X @ W = (X @ diag(S)) @ (diag(S)^-1 @ W)
= X_smooth @ W_smooth
通过选择合适的 S,使得 X_smooth 和 W_smooth 都有合理的值域
"""
def smooth_quant(x, w, alpha=0.5):
"""
SmoothQuant 实现
alpha: 平滑强度,0 = 不平滑,1 = 完全平滑
"""
# 计算每个 channel 的最大绝对值
# x shape: [batch, seq_len, hidden_dim]
# w shape: [hidden_dim, hidden_dim]
# 沿着最后一个维度计算每个 token 的最大绝对值
x_max = torch.max(torch.abs(x), dim=-1, keepdim=True)[0]
# 沿着 weight 的行计算最大绝对值
w_max = torch.max(torch.abs(w), dim=1, keepdim=True)[0]
# 计算平滑因子
# 让 x 和 w 的"难度"按 alpha 比例分配
s = (x_max ** alpha) / (w_max ** (1 - alpha))
# 确保 S 的值不会太大或太小
s = torch.clamp(s, min=1e-5, max=1e5)
# 平滑 X
x_smooth = x / s
# 平滑 W
w_smooth = w * s
return x_smooth, w_smooth3.3 GPTQ 与 AWQ
GPTQ (Generative Post-Training Quantization):
- Weight-only 量化:只量化 weights,activations 保持 FP16
- 使用二阶信息(hessian)来选择量化参数
- 4-bit 量化效果好,但需要校准数据
# GPTQ 伪代码
def gptq_quantize(w, bits=4, per_channel=True):
"""
GPTQ 量化流程
"""
n, m = w.shape # 权重矩阵 shape
q_w = np.zeros((n, m), dtype=np.int32)
q_scale = np.zeros(n)
# 对每一行(channel)独立量化
for i in range(n):
w_row = w[i]
# 计算 hessian 的逆(用于加权误差)
hess_inv = compute_hessian_inverse(w_row, bits)
# 量化这一行
q_w[i], q_scale[i] = quantize_row_gptq(w_row, hess_inv, bits)
return q_w, q_scale
# 实际使用:使用 autoawq 或 gptq 库
# pip install autoawq
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
model = AutoAWQForCausalLM.from_pretrained("meta-llama/Llama-3-8B")
quant_config = {
"zero_point": True, # 非对称量化
"q_group_size": 128, # 量化组大小
"w_bit": 4, # 4-bit 量化
"version": "GEMM" # GEMM 风格量化
}
awq_model = AutoAWQForCausalLM.quantize(model, quant_config)AWQ (Activation-Aware Weight Quantization):
- 考虑 activations 的分布来决定 weight 的量化参数
- 比 GPTQ 更快,不需要 hessian 计算
- 精度通常更好
# AWQ 核心思想
"""
AWQ 发现:weight 的重要性和其对应 activation 的幅度成正比
因此,对于幅度大的 activation,对应的 weight 应该用更高的精度
公式:
- 找到对最终输出影响大的 weight 通道
- 对这些通道使用更小的量化误差
- 通过搜索找到最优的 per-channel scale
"""
def awq_calibrate(model, calibration_data):
"""
AWQ 校准过程
"""
# 收集激活值的统计信息
act_scales = {}
for batch in calibration_data:
with torch.no_grad():
hooks = register_hooks(model, act_scales)
model(batch)
remove_hooks(hooks)
# 计算 AWQ scale
# 对于每个 weight channel,计算对应的 activation scale
for name, act_scale in act_scales.items():
weight_name = name.replace(".input", "").replace(".output", "")
weight = get_weight(model, weight_name)
# AWQ scale = activation scale 的某个函数
awq_scale = find_awq_scale(act_scale, weight)
# 应用平滑
smooth_weight(weight, awq_scale)第4节:量化策略与精度恢复
4.1 混合精度策略
核心原则:不是所有层对量化都同样敏感。
# 混合精度量化策略
def mixed_precision_quantization(model, calibration_data):
"""
根据各层对量化的敏感性,使用不同的精度
"""
# 第一步:评估每个层对量化的敏感性
sensitivity_scores = {}
for name, module in model.named_modules():
if isinstance(module, torch.nn.Linear):
# 保存原始权重
original_weight = module.weight.data.clone()
# 量化这一层
module.weight.data = quantize_to_int8(original_weight)
# 评估精度损失
loss_before = evaluate_on_calibration(model, calibration_data)
# 恢复并评估
module.weight.data = original_weight
loss_after = evaluate_on_calibration(model, calibration_data)
sensitivity = loss_after - loss_before
sensitivity_scores[name] = sensitivity
# 第二步:根据敏感性分配精度
# 敏感层保持 FP16,不敏感层使用 INT8
for name, score in sensitivity_scores.items():
if score < threshold: # 不敏感
quantize_layer(model, name, dtype=torch.qint8)
else: # 敏感
quantize_layer(model, name, dtype=torch.float16)
return model敏感性分析结果(典型 LLM):
| 层类型 | 量化敏感性 | 推荐精度 | 原因 |
|---|---|---|---|
| Input Embedding | 高 | FP16 | 错误会累积传播 |
| First Attention Layer | 高 | FP16 | 错误会累积传播 |
| Last Output Layer | 高 | FP16 | 直接影响最终结果 |
| Middle Layers | 中 | INT8 | 有一定容错空间 |
| FFN Layers | 低 | INT8 | 相对独立 |
| LayerNorm | 中 | FP16 或 BF16 | 归一化敏感 |
4.2 量化精度损失排查流程
# 量化问题排查清单
class QuantizationDebugger:
"""
量化精度问题的系统化排查
"""
def check_outliers(self, model, calibration_data):
"""1. 检查是否存在 outlier"""
print("=" * 50)
print("1. 检查 Outlier")
print("=" * 50)
act_stats = self.collect_activation_stats(model, calibration_data)
for name, stats in act_stats.items():
# 计算变异系数(CV = std / mean)
cv = stats['std'] / (stats['mean'].abs() + 1e-8)
if cv > 10: # 变异系数太大,说明有 outlier
print(f"⚠️ {name}: CV={cv:.1f}, 存在严重 outlier")
else:
print(f"✅ {name}: CV={cv:.1f}, 分布正常")
def compare_quantization_modes(self, model, calibration_data):
"""2. 对比 per-tensor vs per-channel"""
print("\n" + "=" * 50)
print("2. Per-Tensor vs Per-Channel")
print("=" * 50)
# Per-tensor 精度
model_pt = copy.deepcopy(model)
quantize_per_tensor(model_pt)
acc_pt = evaluate(model_pt, calibration_data)
# Per-channel 精度
model_pc = copy.deepcopy(model)
quantize_per_channel(model_pc)
acc_pc = evaluate(model_pc, calibration_data)
print(f"Per-Tensor INT8: {acc_pt:.2f}%")
print(f"Per-Channel INT8: {acc_pc:.2f}%")
print(f"Per-Channel 提升: +{acc_pc - acc_pt:.2f}%")
def analyze_error_distribution(self, model, calibration_data):
"""3. 分析量化误差分布"""
print("\n" + "=" * 50)
print("3. 误差分布分析")
print("=" * 50)
for name, module in model.named_modules():
if isinstance(module, torch.nn.Linear):
# 计算量化误差
original = module.weight.data
quantized = quantize_int8(original)
dequantized = dequantize(quantized)
error = (original - dequantized).abs()
# 找到误差最大的位置
max_error_idx = error.argmax()
max_error = error.max()
max_error_ratio = max_error / original.abs().max()
if max_error_ratio > 0.1: # 误差超过 10%
print(f"⚠️ {name}: 最大误差比例 {max_error_ratio:.1%}")
def try_smoothing(self, model, calibration_data):
"""4. 尝试 SmoothQuant"""
print("\n" + "=" * 50)
print("4. SmoothQuant 效果")
print("=" * 50)
# 原始 INT8 精度
acc_before = evaluate(model, calibration_data)
# 应用 SmoothQuant
model_smooth = smoothquant_apply(model, alpha=0.5)
acc_after = evaluate(model_smooth, calibration_data)
print(f"SmoothQuant 前: {acc_before:.2f}%")
print(f"SmoothQuant 后: {acc_after:.2f}%")
print(f"精度恢复: +{acc_after - acc_before:.2f}%")第5节:硬件支持与编译器集成
5.1 各精度格式的硬件支持
| 精度 | NVIDIA GPU | Intel GPU | AMD GPU | CPU |
|---|---|---|---|---|
| FP32 | ✅ 所有 | ✅ 所有 | ✅ 所有 | ✅ 所有 |
| FP16 | ✅ 所有 Ampere+ | ✅ Gen9+ | ✅ CDNA+ | ⚠️ AVX512 |
| BF16 | ✅ Ampere+ (A100) | ✅ Gen9+ | ✅ CDNA2+ | ✅ AVX512-BF16 |
| INT8 | ✅ Tensor Core | ✅ IMKA | ✅ Matrix Core | ✅ VNNI |
| INT4 | ⚠️ Turing+ (Tensor Core) | ⚠️ 部分 | ⚠️ 部分 | ⚠️ AMX |
| FP8 E4M3 | ✅ Hopper (H100) | ❌ | ❌ | ❌ |
| FP8 E5M2 | ✅ Hopper (H100) | ❌ | ❌ | ❌ |
BF16 vs FP16 选择:
- BF16 的动态范围更大(8-bit exponent vs 5-bit exponent)
- LLM 训练推荐 BF16(更稳定)
- 推理两者差别不大,但 BF16 更通用
5.2 PyTorch 量化 API
import torch
import torch.quantization
# ============================================================
# 方法 1:动态量化(最简单)
# ============================================================
# 特点:权重 INT8,activations FP32,计算时动态量化
model_dynamic = torch.quantization.quantize_dynamic(
model=original_model,
qconfig_spec={
torch.nn.Linear: torch.qint8, # Linear 层量化
torch.nn.LSTM: torch.qint8, # LSTM 层量化
# torch.nn.Embedding: torch.quint8 # Embedding 量化
},
dtype=torch.qint8
)
# ============================================================
# 方法 2:静态量化(需要校准)
# ============================================================
# 准备量化
model_static = original_model.clone()
model_static.eval()
model_static.qconfig = torch.quantization.get_default_qconfig('fbgemm')
# fbgemm: x86 CPU 优化
# qnnpack: ARM CPU 优化
# onednn: Intel CPU 优化
torch.quantization.fuse_model(model_static) # 融合 conv+bn
model_prepared = torch.quantization.prepare(model_static)
# 校准
with torch.no_grad():
for batch, _ in calibration_loader:
model_prepared(batch)
# 转换
model_int8 = torch.quantization.convert(model_prepared)
# ============================================================
# 方法 3:torch.compile + 量化
# ============================================================
# PyTorch 2.0+ 支持
model_compiled = torch.compile(model_int8, mode="reduce-overhead")
# 在编译时指定量化配置
from torch._export import capture_pre_autograd_graph
# 对于 XLA 后端
import torch_xla.core.xla_model as xm
xla_quant_config = {
"quantization_method": "symmetric",
"quantization_bytesize": 1,
"round_mode": "ROUND_NEAREST_EVEN"
}5.3 Inductor 量化
import torch
# ============================================================
# torch.compile 量化配置
# ============================================================
# 方式 1:使用默认量化后端
model_quantized = torch.compile(
model,
backend="inductor",
options={
"mode": "reduce-overhead",
# inductor 会自动尝试量化
}
)
# 方式 2:显式指定量化
torch._inductor.config.force_quantize = True
# 方式 3:自定义量化配置
inductor_config = {
# 算子融合配置
"max_autotune": True,
"autotune_fallback_to_aten": False,
# 量化相关
"force_quantize": True,
"quantization_config": {
"weight_quantization": "int8",
"activation_quantization": "int8",
"per_channel_quant": True, # 推荐 per-channel
}
}
model_compiled = torch.compile(
model,
backend="inductor",
options=inductor_config
)
# ============================================================
# 验证量化效果
# ============================================================
# 检查编译后的模型是否使用了量化
print(model_compiled)
# 应该在输出中看到 QuantizeLinear / DequantizeLinear 节点第6节:量化策略决策流程
┌─────────────────────────────────────────────────────────────────────────────┐
│ 量化策略决策流程 │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────┐
│ 确定目标场景 │
└────────┬────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 推理部署 │ │ 模型训练 │ │ 研究实验 │
└───────┬──────┘ └───────┬──────┘ └───────┬──────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ PTQ 快速部署 │ │ BF16 训练 │ │ FP32 基线 │
└───────┬──────┘ └──────────────┘ └──────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ 精度要求测试 │
└─────────────────────┬───────────────────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ 精度 OK │ │ 精度下降 │ │ 精度严重 │
└─────┬──────┘ └─────┬──────┘ └─────┬──────┘
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ 部署 │ │ Per-channel│ │ 混合精度 │
└────────────┘ └─────┬──────┘ └─────┬──────┘
│ │
▼ ▼
┌────────────┐ ┌────────────┐
│ 重新测试 │ │ SmoothQuant│
└─────┬──────┘ └─────┬──────┘
│ │
▼ ▼
┌─────────────────────────┐
│ 精度满足要求? │
└────────────┬────────────┘
│
┌────────┴────────┐
▼ ▼
┌─────────┐ ┌─────────┐
│ 部署 │ │ QAT 微调 │
└─────────┘ └─────────┘升华
┌──────────────────────────────────────────────────────────────────────────────┐
│ 量化工程实践原则 │
├──────────────────────────────────────────────────────────────────────────────┤
│ 1. 先测后用:量化前先评估敏感性,不是所有层都需要同精度 │
│ 2. Per-channel 优先:相比 per-tensor 通常能获得更好的精度 │
│ 3. 校准要代表性:校准数据应该覆盖真实推理场景 │
│ 4. 混合精度是常态:关键层保持高精度,次要层 INT8 │
│ 5. Outlier 处理:先检查 outliers,用 SmoothQuant 处理 │
└──────────────────────────────────────────────────────────────────────────────┘"AI 可查 vs 必须理解"清单
必须理解(不理解就等于不会):
- 🔴 对称量化 vs 非对称量化的公式和适用场景
- 🔴 Per-tensor vs per-channel 量化的精度差异原因
- 🔴 Outlier 问题是量化精度损失的核心原因
- 🔴 SmoothQuant 的核心思想:把 outlier 平滑到 weights
- 🔴 PTQ 和 QAT 的trade-off:速度 vs 精度
AI 可查(知道去哪查就行):
- ✅ PyTorch 量化 API 的具体参数和语法
- ✅ GPTQ/AWQ 的具体实现细节
- ✅ 各硬件平台对不同精度的支持情况
- ✅ torch.compile 量化配置的具体选项
- ✅ 特定模型的量化敏感性数据
学习状态:🟡 开始学习