量化菜谱#

参考量化菜谱

量化是一种将模型参数中的 32 位浮点数转换为 8 位整数的技术。通过量化,模型大小和内存占用可以减少到原来大小的 1/4,推理速度可以提高 2-4 倍,而精度保持不变。

量化模型的方法或工作流程大致有三种:后训练动态量化(post training dynamic quantization)、后训练静态量化(post training dynamic quantization)和量化感知训练(quantization aware training)。但是,如果您想要使用的模型已经有量化版本,您可以直接使用它,而不需要经过上面三个工作流中的任何一个。例如,torchvision 库已经包括了模型 MobileNet v2、ResNet 18、ResNet 50、Inception v3、GoogleNet 等的量化版本。因此,我们将最后一种方法作为另一个工作流,尽管很简单。

备注

量化支持可用于有限的一组算子

PyTorch 支持四种量化工作流。

使用预先训练的量化网络#

要得到网络的量化模型,只需做:

from torchvision.models.quantization import resnet18 as qresnet18

model_quantized = qresnet18(pretrained=True,
                            quantize=True)

为了比较非量化的模型与其量化版本的大小差异:

from torchvision.models import resnet18

model = resnet18(pretrained=True)

import os
import torch

def print_model_size(mdl, name=''):
    torch.save(mdl.state_dict(), "tmp.pt")
    size = os.path.getsize("tmp.pt")/1e6
    print(f'{name} {size: .2f} MB')
    os.remove('tmp.pt')

print_model_size(model, '原始模型')
print_model_size(model_quantized, '量化模型')
原始模型  46.83 MB
量化模型  11.84 MB

后训练动态量化#

要应用动态量化,它将模型中的所有权重从 32 位的浮点数转换为 8 位的整数,但在对这些激活执行计算之前不会将激活转换为 int8,只需调用 quantize_dynamic()

from torch.quantization.quantize import quantize_dynamic

model_dynamic_quantized = quantize_dynamic(
    model, qconfig_spec={torch.nn.Linear}, dtype=torch.qint8
)

其中 qconfig_spec 指定要应用量化的 model 中的子模块名称列表。

警告

动态量化的一个重要限制是,虽然它是最简单的工作流程,如果你没有预先训练的量化模型准备使用,它目前在 qconfig_spec 中只支持 nn.Linearnn.LSTM,这意味着你将不得不使用静态量化或量化感知训练,稍后讨论,以量化其他模块,如 nn.Conv2d

使用训练后动态量化的另外三个例子是 Bert 例子LSTM 模型例子 和另一个 LSTM demo 例子

后训练静态量化#

该方法将权值和激活提前转换为 8 位整数,这样就不会像动态量化那样在推理过程中对激活进行实时转换,从而显著提高了性能。

要在模型上应用静态量化,运行以下代码:

from torch.quantization.quantize import prepare, convert
from torch.quantization.qconfig import get_default_qconfig
from torchvision.models.quantization import resnet18 as qresnet18

backend = "fbgemm" # 若为 x86,否则为 'qnnpack' 
model.qconfig = get_default_qconfig(backend)
torch.backends.quantized.engine = backend
model_static_quantized = prepare(model, inplace=False)
model_static_quantized = convert(model_static_quantized, inplace=False)
/media/workspace/anaconda3/envs/torchx/lib/python3.10/site-packages/torch/ao/quantization/observer.py:177: UserWarning: Please use quant_min and quant_max to specify the range for observers.                     reduce_range will be deprecated in a future release of PyTorch.
  warnings.warn(
/media/workspace/anaconda3/envs/torchx/lib/python3.10/site-packages/torch/ao/quantization/observer.py:1124: UserWarning: must run observer before calling calculate_qparams.                                    Returning default scale and zero point 
  warnings.warn(

显示静态量化模型大小:

print_model_size(model_static_quantized)
  11.93 MB

这里有一个完整的模型定义和静态量化的例子。这里有一个专门的静态量化教程。

备注

为了使模型运行在通常有 ARM 架构的移动设备上,你需要使用 'qnnpack' 作为后台;要在 x86 架构的计算机上运行模型,请使用 'fbgemm'

量化感知训练(QAT)#

量化感知训练(Quantization aware training)在模型训练过程中对所有的权值和激活量都插入伪量化,比训练后的量化方法具有更高的推理精度。它通常用于 CNN 的模型中。

要启用量化感知训练的模型,请在模型定义的 __init__ 方法中定义 QuantStubDeQuantStub,以将张量从浮点类型转换为量化类型,反之亦然。

from torch import nn
from torch.quantization.stubs import QuantStub, DeQuantStub

class MyModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.quant = QuantStub()
        self.dequant = DeQuantStub()

    def forward(self, x):
        x = self.quant(x)
        # 其他运算
        x = self.dequant(x)
        return x

然后在模型定义的前向方法的开头和结尾分别调用 x = self.quant(x)x = self.dequant(x)

要进行量化感知训练,请使用下面的代码片段:

from torch.quantization.quantize import prepare_qat, convert
from torch.quantization.qconfig import get_default_qat_qconfig

model = MyModel()
model.qconfig = get_default_qat_qconfig(backend)
model_qat = prepare_qat(model, inplace=False)
# QAT
model_qat = convert(model_qat.eval(), inplace=False)
/media/workspace/anaconda3/envs/torchx/lib/python3.10/site-packages/torch/ao/quantization/utils.py:210: UserWarning: must run observer before calling calculate_qparams. Returning default values.
  warnings.warn(

有关量化感知训练的更多详细示例,请参阅此处此处

在使用上面的步骤之一生成量化模型之后,在模型可以在移动设备上运行之前,它需要进一步转换为 TorchScript 格式,然后针对移动端应用程序进行优化。请参阅脚本和优化移动端食谱的详细信息。