3. 模型量化基础

任务目标

本案例使用PyTorch实现后训练量化(Post-Training Quantization, PTQ),将一个预训练好的全精度CNN模型(ResNet-18)量化为INT8动态量化模型,并在CIFAR-10验证集上对比量化前后的Top-1准确率,评估精度损失是否在可接受范围内。

环境与依赖

  • Python 3.8+
  • PyTorch ≥ 1.12(需包含torch.ao.quantization模块)
  • torchvision ≥ 0.13
  • numpy

实现原理

后训练量化无需重新训练模型,直接对预训练权重进行量化。本案例采用动态量化(Dynamic Quantization),即权重在推理前量化为INT8,激活值在运行时动态量化。该方法无需校准数据集,实现简单,适合CPU端部署。核心步骤为:加载预训练模型 → 融合特定层 → 配置量化配置 → 转换模型 → 评估对比。

关键完整代码

import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
from torch.ao.quantization import quantize_dynamic, QConfig, default_dynamic_qconfig

# 1. 数据加载(CIFAR-10验证集)
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
val_dataset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
val_loader = torch.utils.data.DataLoader(val_dataset, batch_size=128, shuffle=False, num_workers=2)

# 2. 加载预训练模型(ResNet-18,CIFAR-10适配版)
model = torchvision.models.resnet18(num_classes=10)
model.load_state_dict(torch.load('resnet18_cifar10.pth', map_location='cpu'))
model.eval()

# 3. 定义评估函数
def evaluate(model, loader):
    correct, total = 0, 0
    with torch.no_grad():
        for images, labels in loader:
            outputs = model(images)
            _, predicted = torch.max(outputs, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
    return 100.0 * correct / total

# 4. 量化前精度
fp32_acc = evaluate(model, val_loader)
print(f"FP32 Top-1 Accuracy: {fp32_acc:.2f}%")

# 5. 动态量化(仅量化Linear层)
quantized_model = quantize_dynamic(
    model,
    {nn.Linear},  # 只量化全连接层
    dtype=torch.qint8
)

# 6. 量化后精度
int8_acc = evaluate(quantized_model, val_loader)
print(f"INT8 Dynamic Top-1 Accuracy: {int8_acc:.2f}%")

# 7. 精度损失评估
acc_loss = fp32_acc - int8_acc
print(f"Accuracy Drop: {acc_loss:.2f}%")
print(f"Relative Drop: {acc_loss / fp32_acc * 100:.2f}%")

运行方式

  1. 下载预训练权重文件resnet18_cifar10.pth(训练脚本见案例2,或使用任意CIFAR-10预训练ResNet-18权重替换加载路径);
  2. 将上述代码保存为ptq_demo.py;
  3. 执行命令:python ptq_demo.py。

验证方法与通过条件

  • 程序正常输出FP32与INT8的Top-1准确率,无报错;
  • INT8准确率与FP32相比,下降幅度通常不超过2%(相对损失不超过5%);
  • 量化后模型大小约为原来的1/4(可通过torch.save后对比文件大小验证);
  • 若精度下降超过5%,检查预训练权重是否收敛良好,或改用qnnpack后端进行静态量化。

常见问题

  • 量化后推理变慢?动态量化在CPU小批量上可能不如FP32快,这是正常现象;若需加速,应使用静态量化并配合INT8算子优化。
  • 模型包含BatchNorm层?动态量化不需要融合BN层,但静态量化前需调用torch.ao.quantization.fuse_modules进行Conv-BN-ReLU融合。
  • 如何验证量化模型确实使用INT8?打印quantized_model.fc的权重dtype,应为torch.qint8。
预训练模型 ResNet-18 (FP32) 动态量化 quantize_dynamic() INT8模型 仅Linear层量化 FP32评估 Top-1 准确率 INT8评估 Top-1 准确率 精度损失对比 损失 < 2% 则通过 后训练量化流程:无需训练数据,直接转换,对比精度