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运算,仅用于模拟量化误差。
静态量化 vs 伪量化 流程对比 FP32 预训练模型 ResNet-18 (CIFAR-10) 伪量化 (FakeQuant) 权重添加 FakeQuantize 模块 推理时模拟 int8 量化误差 权重仍为 float32 存储 精度评估 不可直接部署,仅用于预判量化损失 静态量化 (PTQ) FX图模式 + 校准集统计范围 权重实际转换为 int8 激活量化 + 整型推理 精度评估 + 部署 可导出 int8 ONNX/TorchScript,体积减少75%