3. 静态量化与伪量化比较
任务目标
对同一个CNN分类模型(以ResNet-18在CIFAR-10上的预训练权重为例),分别实施静态量化(PTQ,训练后静态量化)与伪量化(FakeQuant,模拟量化),对比两者在推理精度与部署便利性上的差异。
核心差异说明
| 维度 | 静态量化(PTQ) | 伪量化(FakeQuant) |
|---|---|---|
| 权重量化 | 实际转换为int8存储 | 仅模拟量化误差,权重仍为float32 |
| 激活量化 | 需校准集统计范围,推理时整型运算 | 训练/推理时模拟量化噪声,不改变实际计算类型 |
| 部署产物 | 可直接生成int8权重与量化参数,供推理引擎加载 | 仅用于精度评估或QAT训练,不可直接部署 |
| 精度表现 | 通常有少量下降(1%~3%) | 与真实量化精度高度接近,用于预判 |
环境依赖
torch>=2.0, torchvision, numpy
关键完整代码
import torch
import torchvision.models as models
import torchvision.transforms as transforms
from torchvision.datasets import CIFAR10
from torch.utils.data import DataLoader
import torch.nn as nn
# ---------- 1. 加载预训练模型并适配CIFAR-10 ----------
def get_model():
model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)
model.fc = nn.Linear(model.fc.in_features, 10) # 替换分类头
return model
# ---------- 2. 数据加载(仅用校准集与测试集) ----------
transform = transforms.Compose([
transforms.Resize(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
test_set = CIFAR10(root='./data', train=False, download=True, transform=transform)
test_loader = DataLoader(test_set, batch_size=64, shuffle=False)
# 校准集:取测试集前512张
calib_set = torch.utils.data.Subset(test_set, range(512))
calib_loader = DataLoader(calib_set, batch_size=64, shuffle=False)
# ---------- 3. 精度评估函数 ----------
@torch.no_grad()
def evaluate(model, loader):
model.eval()
correct = 0
total = 0
for x, y in loader:
out = model(x)
pred = out.argmax(dim=1)
correct += (pred == y).sum().item()
total += y.size(0)
return correct / total
# ---------- 4. 伪量化(模拟量化) ----------
def apply_fake_quant(model):
# 使用torch.ao.quantization的fake_quant模块模拟8bit量化
from torch.ao.quantization import FakeQuantize, default_fake_quant
for name, module in model.named_modules():
if isinstance(module, (nn.Conv2d, nn.Linear)):
# 为权重添加fake_quant
module.weight_fake_quant = FakeQuantize.with_args(
observer=default_fake_quant.observer,
quant_min=-128, quant_max=127,
dtype=torch.qint8, qscheme=torch.per_tensor_symmetric
)()
module.weight_fake_quant.enable_fake_quant()
return model
# ---------- 5. 静态量化(PTQ) ----------
def apply_static_quant(model):
from torch.ao.quantization import quantize_fx
from torch.ao.quantization.quantize_fx import prepare_fx, convert_fx
from torch.ao.quantization.qconfig import QConfig
from torch.ao.quantization.observer import MinMaxObserver, HistogramObserver
# 使用FX图模式量化
qconfig = QConfig(
activation=HistogramObserver.with_args(dtype=torch.quint8, qscheme=torch.per_tensor_affine),
weight=MinMaxObserver.with_args(dtype=torch.qint8, qscheme=torch.per_tensor_symmetric)
)
model_to_quantize = model.cpu()
model_to_quantize.eval()
q_model = prepare_fx(model_to_quantize, {"": qconfig}, example_inputs=torch.randn(1, 3, 224, 224))
# 校准
with torch.no_grad():
for x, _ in calib_loader:
q_model(x)
q_model = convert_fx(q_model)
return q_model
# ---------- 6. 主流程 ----------
if __name__ == "__main__":
# 原始模型精度
model_fp32 = get_model()
acc_fp32 = evaluate(model_fp32, test_loader)
print(f"FP32 精度: {acc_fp32:.4f}")
# 伪量化模型(仅模拟,不实际转换)
model_fake = get_model()
model_fake = apply_fake_quant(model_fake)
acc_fake = evaluate(model_fake, test_loader)
print(f"伪量化精度: {acc_fake:.4f}")
# 静态量化模型(实际int8)
model_static = get_model()
model_static = apply_static_quant(model_static)
acc_static = evaluate(model_static, test_loader)
print(f"静态量化精度: {acc_static:.4f}")
# 部署便利性对比
print("\n--- 部署便利性 ---")
print(f"静态量化模型大小: {sum(p.numel() for p in model_static.parameters()) * 1 / 1024:.2f} KB (int8)")
print(f"伪量化模型大小: {sum(p.numel() for p in model_fake.parameters()) * 4 / 1024:.2f} KB (float32)")
print("静态量化: 可直接导出为ONNX int8或TorchScript,供移动端/边缘设备加载")
print("伪量化: 仅用于精度模拟,不可直接部署,需配合QAT或PTQ转换")
运行方式
python static_vs_fake_quant.py
首次运行会自动下载CIFAR-10数据集(约170MB)与ResNet-18预训练权重。
验证方法与通过条件
- 精度对比:观察输出的三个精度值。伪量化精度应接近FP32(通常差距<1%),静态量化精度略低(通常差距1%~3%)。若静态量化精度下降超过5%,说明校准集代表性不足或量化配置需调整。
- 部署产物验证:静态量化模型可通过
torch.jit.script或torch.onnx.export导出,导出后文件大小约为FP32模型的1/4;伪量化模型导出后仍是float32,无法获得体积优势。 - 数值一致性:伪量化模型的权重仍为float32类型(可通过
model_fake.conv1.weight.dtype验证),而静态量化模型内部已包含torch.qint8量化权重。
常见问题
- 校准集大小:512张图片足以稳定统计激活范围,若精度异常可增至1024张。
- 量化敏感层:若静态量化精度损失过大,可对特定层使用
float_qconfig保持浮点计算。 - 伪量化不改变推理速度:因为实际仍执行float32运算,仅用于模拟量化误差。