模型部署:量化中的Post-Training-Quantization(PTQ)和Quantization-Aware-Training(QAT)

模型部署:量化中的Post-Training-Quantization(PTQ)和Quantization-Aware-Training(QAT)

前言

随着人工智能的不断发展,深度学习网络被广泛应用于图像处理、自然语言处理等实际场景,将其部署至多种不同设备的需求也日益增加。然而,常见的深度学习网络模型通常包含大量参数和数百万的浮点数运算(例如ResNet50具有95MB的参数以及38亿浮点数运算),实时地运行这些模型需要消耗大量内存和算力,这使得它们难以部署到资源受限且需要满足实时性、低功耗等要求的边缘设备。为了进一步推动深度学习网络模型在移动端或边缘设备中的快速部署,深度学习领域提出了一系列的模型压缩与加速方法:

  • 知识蒸馏(Knowledge distillation):使用教师-学生网络结构,让小型的学生网络模仿大型教师网络的行为,以使得准确率尽可能高的同时,能够获得一个轻量化的网络。
  • 剪枝(Parameter pruning):删除不必要的网络参数,以减少模型的规模和计算复杂度。
  • 低秩分解(Low-rank factorization):将模型的参数矩阵分解为较低秩的小矩阵,以减少模型的复杂度和计算成本。
  • 参数共享(Parameter sharing):将多个层共用一组参数,以减少模型的参数数量。
  • 量化(Quantization):将模型的参数和运算转化为更小的数据类型,以减少内存占用和计算时间。

量化

模型量化(Quantization)是一种将浮点计算转化为定点计算的技术,例如从FP32降低至INT8,主要用于减少模型的计算强度、参数大小以及内存消耗,以提高模型在设备上的推理计算效率,但是也有可能会带来一定的精度损失。相对于 FP32 模型,INT8模型的优点如下:

  • 体积更小,8-bit 模型可降低 75% 文件大小。
  • 由于模型变小,Cache 命中率提升,速度更快。
  • 芯片往往有对应的定点加速指令,这些指令更快、能耗更低(常见 CPU 上 INT8 大约只需要 10% 能量)。

模型量化精度损失的主要原因为量化-反量化(Quantization-Dequantization)过程中取整引起的误差。这里简单介绍一下量化的计算方法,以FP32到INT8的量化为例,量化的核心思想就是将浮点数区间的参数映射到INT8的离散区间中。
量化公式:
q = r s + Z q = \frac{r}{s} + Z q=sr+Z反量化公式:
r = S ( q − Z ) r = S(q-Z) r=S(qZ)其中, r r r 为FP32的浮点数(real value), q q q 为INT8的量化值(quantization value),
S S S Z Z Z 分别为缩放因子(Scale-factor)和零点(Zero-Point)。

量化最重要的便是确定 S S S Z Z Z 的值, S S S Z Z Z 的计算公式如下:
S = r m a x − r m i n q m a x − q m i n S = \frac{r_{max}-r_{min}}{q_{max}-q_{min}} S=qmaxqminrmaxrmin Z = − r m i n S + q m i n Z = -\frac{r_{min}}{S} + q_{min} Z=Srmin+qmin其中, r m a x r_{max} rmax r m i n r_{min} rmin 分别为FP32网络参数最大、最小值, q m a x q_{max} qmax q m i n q_{min} qmin 分别为INT8网络参数最大、最小值。

为了减少量化所带来的精度损失,学者提出了Quantization-Aware-Training(QAT)方法,再介绍此之前,由于Post-Training-Quantization(PTQ)方法也经常在文献中出现,此篇博客将着重介绍这两个方法的含义与区别。
在这里插入图片描述

Post-Training-Quantization(PTQ)

Post-Training-Quantization(PTQ)是目前常用的模型量化方法之一。以INT8量化为例,PTQ方法的处理流程为:

  1. 首先在数据集上以FP32精度进行模型训练,得到训练好的模型;
  2. 使用小部分数据对FP32模型进行采样(Calibration),主要是为了得到网络各层参数的数据分布特性(比如统计最大最小值);
  3. 根据步骤2中的数据分布特性,计算出网络各层 S 和 Z 量化参数;
  4. 使用步骤3中的量化参数对FP32模型进行量化得到INT8模型,并将其部署至推理框架进行推理。

PTQ方法会使用小部分数据集来估计网络各层参数的数据分布,找到合适的S和Z的取值,从而一定程度上降低模型精度损失。然而,论文中指出PTQ方式虽然在大模型上效果较好(例如ResNet101),但是在小模型上经常会有较大的精度损失(例如MobileNet) 不同通道的输出范围相差可能会非常大(大于100x), 对异常值较为敏感。

Quantization-Aware-Training(QAT)

由上文可知PTQ方法中模型的训练和量化是分开的,而Quantization-Aware-Training(QAT)方法则是在模型训练时加入了伪量化节点,用于模拟模型量化时引起的误差,并通过微调使得模型在量化后尽可能减少精度损失。以INT8量化为例,QAT方法的处理流程为:

  1. 首先在数据集上以FP32精度进行模型训练,得到训练好的FP32模型;
  2. 在FP32模型中插入伪量化节点,得到QAT模型,并且在数据集上对QAT模型进行微调(Fine-tuning);
  3. 同PTQ方法中的采样(Calibration),并计算量化参数 S 和 Z ;
  4. 使用步骤3中得到的量化参数对QAT模型进行量化得到INT8模型,并部署至推理框架中进行推理。

在PyTorch中,可以使用 torch.quantization.quantize_dynamic() 方法来执行 QAT。这是一个基本的 QAT 代码示例:

import torch
import torch.nn as nn
import torch.optim as optim
from torch.quantization import quantize_dynamic, QuantStub, DeQuantStub

# 定义简单的模型
class SimpleModel(nn.Module):
    def __init__(self):
        super(SimpleModel, self).__init__()
        self.quant = QuantStub()
        self.dequant = DeQuantStub()
        self.fc1 = nn.Linear(784, 256)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(256, 10)

    def forward(self, x):
        x = self.quant(x)
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)
        x = self.dequant(x)
        return x

# 数据加载
# 这里使用 MNIST 数据集作为示例
from torchvision import datasets, transforms

transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))])

train_loader = torch.utils.data.DataLoader(datasets.MNIST('./data', train=True, download=True, transform=transform),
                                           batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(datasets.MNIST('./data', train=False, download=True, transform=transform),
                                          batch_size=64, shuffle=False)

# 定义损失函数和优化器
model = SimpleModel()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 定义 QAT 训练函数
def train(model, train_loader, criterion, optimizer, num_epochs=5):
    model.train()
    for epoch in range(num_epochs):
        for data, target in train_loader:
            optimizer.zero_grad()
            output = model(data.view(data.shape[0], -1))
            loss = criterion(output, target)
            loss.backward()
            optimizer.step()

# 训练模型
train(model, train_loader, criterion, optimizer, num_epochs=5)

# 在训练完成后执行动态量化
quantized_model = quantize_dynamic(model, {nn.Linear}, dtype=torch.qint8)

# 评估量化模型
def test(model, test_loader, criterion):
    model.eval()
    correct = 0
    total = 0
    with torch.no_grad():
        for data, target in test_loader:
            output = model(data.view(data.shape[0], -1))
            _, predicted = torch.max(output.data, 1)
            total += target.size(0)
            correct += (predicted == target).sum().item()
    accuracy = correct / total
    print(f'Accuracy of the network on the test images: {accuracy * 100:.2f}%')

# 测试量化模型
test(quantized_model, test_loader, criterion)

上述代码示例中,我使用了一个简单的全连接神经网络,并在训练完成后使用torch.quantization.quantize_dynamic()对模型进行动态量化。在量化之前,我们通过QuantStub()DeQuantStub()添加了量化和反量化的辅助模块。这个示例使用了MNIST数据集,你可以根据你的实际需求替换成其他数据集和模型。

参考文献

量化感知训练(Quantization-aware-training)探索-从原理到实践

猜你喜欢

转载自blog.csdn.net/weixin_43603658/article/details/134355611