3. 动态量化与静态量化对比
任务目标
以MobileNetV2图像分类模型为例,分别应用PyTorch 2.x的动态量化(Dynamic Quantization)与静态量化(Static Quantization),在端侧CPU上对比两者的模型体积、推理延迟与Top-1精度差异,明确两种量化策略的适用场景。
环境与依赖
- Python 3.9+,PyTorch ≥ 2.0(含torchvision),CPU版本即可
- 依赖安装:
pip install torch torchvision - 数据集:使用torchvision内置的
CIFAR10测试集前200张图片作为校准与验证子集(自动下载)
关键实现
核心逻辑分为四步:加载预训练MobileNetV2并构造校准数据集;分别执行动态量化(仅量化Linear层)与静态量化(量化Conv+BN+ReLU融合层,使用校准数据统计激活范围);在相同输入上测量推理延迟与精度;输出对比结果。静态量化需要torch.ao.quantization的fuse与prepare/convert流程,动态量化则一行API完成。
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
import time, copy, os
from torch.ao.quantization import quantize_dynamic, prepare_qat, convert
from torch.ao.quantization.quantize_fx import prepare_fx, convert_fx
# ---------- 1. 固定随机种子,加载模型与数据 ----------
torch.manual_seed(0)
device = "cpu"
model_fp32 = torchvision.models.mobilenet_v2(weights=torchvision.models.MobileNet_V2_Weights.IMAGENET1K_V1).eval().to(device)
transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
testset = torchvision.datasets.CIFAR10(root="./data", train=False, download=True, transform=transform)
# 取前200张作为校准/验证子集(CIFAR10类别与ImageNet不完全一致,仅用于相对比较)
calib_loader = torch.utils.data.DataLoader(testset, batch_size=20, shuffle=False)
calib_data = [x for x, _ in calib_loader][:10] # 200张
# ---------- 2. 动态量化 ----------
model_dynamic = quantize_dynamic(
model_fp32, {nn.Linear}, dtype=torch.qint8
)
# ---------- 3. 静态量化(fx图模式,自动融合) ----------
from torch.ao.quantization.quantize_fx import prepare_fx, convert_fx
qconfig = torch.ao.quantization.QConfig(
activation=torch.ao.quantization.MinMaxObserver.with_args(dtype=torch.quint8),
weight=torch.ao.quantization.MinMaxObserver.with_args(dtype=torch.qint8)
)
model_static = copy.deepcopy(model_fp32)
model_static.eval()
# 准备静态量化(fx模式自动处理融合)
qconfig_dict = {"": qconfig}
model_prepared = prepare_fx(model_static, qconfig_dict, example_inputs=torch.randn(1,3,224,224))
# 校准:用前100张统计激活范围
with torch.no_grad():
for i, x in enumerate(calib_data[:5]):
model_prepared(x)
model_static = convert_fx(model_prepared)
# ---------- 4. 评估函数:延迟与Top-1精度 ----------
def evaluate(model, data_list, num_runs=5):
model.eval()
# 预热
with torch.no_grad():
for x in data_list[:2]:
model(x)
# 延迟测量(取多次平均)
times = []
with torch.no_grad():
for _ in range(num_runs):
for x in data_list:
t0 = time.time()
model(x)
times.append(time.time() - t0)
avg_latency = sum(times) / len(times) * 1000 # ms
# 精度(CIFAR10标签0-9,与ImageNet索引不同,此处仅比较相对变化)
correct = 0
total = 0
with torch.no_grad():
for x, y in zip(calib_data, [torch.tensor([i%10 for i in range(20)]) for _ in range(10)]):
# 简化:仅用前200张的原始标签(CIFAR10真实标签)
pass
# 重新用真实标签评估
correct = 0
total = 0
loader_iter = iter(torch.utils.data.DataLoader(testset, batch_size=20, shuffle=False))
with torch.no_grad():
for i, (x, y) in enumerate(loader_iter):
if i >= 10: break
out = model(x)
pred = out.argmax(dim=1)
# CIFAR10类别索引与ImageNet不同,此处仅演示流程,精度值不具意义
correct += (pred % 10 == y).sum().item() # 粗略映射,仅用于相对比较
total += y.size(0)
acc = correct / total
return avg_latency, acc
# ---------- 5. 模型体积对比 ----------
def get_model_size(model):
torch.save(model.state_dict(), "/tmp/tmp_model.pth")
size = os.path.getsize("/tmp/tmp_model.pth") / 1024
os.remove("/tmp/tmp_model.pth")
return size
size_fp32 = get_model_size(model_fp32)
size_dyn = get_model_size(model_dynamic)
size_static = get_model_size(model_static)
lat_fp32, acc_fp32 = evaluate(model_fp32, calib_data)
lat_dyn, acc_dyn = evaluate(model_dynamic, calib_data)
lat_static, acc_static = evaluate(model_static, calib_data)
print(f"FP32: size={size_fp32:.1f}KB latency={lat_fp32:.2f}ms acc={acc_fp32:.3f}")
print(f"Dynamic: size={size_dyn:.1f}KB latency={lat_dyn:.2f}ms acc={acc_dyn:.3f}")
print(f"Static: size={size_static:.1f}KB latency={lat_static:.2f}ms acc={acc_static:.3f}")
print(f"\n体积压缩比: Dynamic={size_fp32/size_dyn:.2f}x, Static={size_fp32/size_static:.2f}x")
print(f"延迟加速比: Dynamic={lat_fp32/lat_dyn:.2f}x, Static={lat_fp32/lat_static:.2f}x")
运行与验证
- 直接运行脚本,首次会自动下载CIFAR10数据集(约170MB)与MobileNetV2权重(约14MB)
- 观察输出中的体积、延迟与精度数值,验证以下判定条件:
| 指标 | 预期关系 | 说明 |
|---|---|---|
| 模型体积 | FP32 > Dynamic ≈ Static | 两者均将权重压缩至int8,体积应接近且显著小于FP32 |
| 推理延迟 | Static < Dynamic < FP32(通常) | 静态量化可提前计算int8卷积,动态量化仅Linear层受益 |
| 精度 | FP32 ≥ Static ≥ Dynamic(通常) | 静态量化有校准过程,精度损失通常小于动态量化 |
通过标准:程序无报错完成推理,输出三个模型的体积、延迟与精度数值,且体积关系满足FP32最大、延迟满足静态量化最快(或与动态量化差距在10%以内),即视为案例验证通过。由于CIFAR10类别与ImageNet不匹配,精度值仅用于观察相对变化趋势。
常见问题
- 静态量化校准数据不足:若精度异常低,可增加校准图片数至500张以上,但会延长运行时间
- fx模式兼容性:若PyTorch版本低于2.0,需改用
torch.quantization的经典API并手动融合Conv+BN+ReLU - 延迟波动:CPU频率调度会导致单次测量偏差,建议增加
num_runs至10以上取平均