模型量化:PTQ + onnx

8 位线性量化的数学表达

  • 将 32 位浮点(实数)模型转换为 8 位整数模型
    F 32 = S c a l e ∗ ( I i n t 8 − Z ) 量化公式: I i n t 8 = F 32 S c a l e + Z F_{32} = Scale * (I_{int8}-Z)\\ 量化公式: I_{int8} = \frac{F_{32}}{Scale} + Z F32=Scale(Iint8Z)量化公式:Iint8=ScaleF32+Z
对称量化仿射量化
Z = 0 Z = 0 Z=0 Z ≠ 0 Z\neq 0 Z=0
S = 2 ∗ m a x ( a b s ( F m a x ) , a b s ( F m i n ) ) I m a x − I m i n S=\frac{2*max(abs(F_{max}), abs(F_{min}))}{ I_{max} - I_{min}} S=ImaxImin2max(abs(Fmax),abs(Fmin)) S = F m a x − F m i n I m a x − I m i n S=\frac{ F_{max}-F_{min}}{ I_{max} - I_{min}} S=ImaxIminFmaxFmin
s =max(abs(A1), abs(A2))/(2^7) ,z =0s = (A1-A2)/(2^8 + 1) ,z = -(ROUND(A2 * s)) - 2^(8-1)
量化到[-127,127]一般可量化到[-128,127] ([0,255])

卷积的量化(对称量化)

在这里插入图片描述

y 1 , 1                                                            = x 1 , 1 ∗ w 1 , 1 + x 1 , 2 ∗ w 2 , 1 + x 1 , 3 ∗ w 3 , 1 = s a ∗ X 1 , 1 ∗ s b ∗ W 1 , 1 +     s c ∗ X 1 , 2 ∗ s d ∗ W 2 , 1 +      s e ∗ X 1 , 3 ∗ s f ∗ W 3 , 1 ( 量化 )      = s A ∗ s B ( X 1 , 1 ∗ W 1 , 1 + X 1 , 2 ∗ W 2 , 1      + X 1 , 3 ∗ W 3 , 1 ) ( 需满足 s A = s a , s c , s e 才行 ) y_{1,1} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \\ =x_{1,1}*w_{1,1}+x_{1,2}*w_{2,1}+x_{1,3}*w_{3,1} \\ = s_a*X_{1,1}* s_b*W_{1,1}+ \qquad \qquad \qquad \ \\\ s_c*X_{1,2}*s_d*W_{2,1}+\qquad \qquad \quad \ \ \\\ s_e*X_{1,3}*s_f*W_{3,1}(量化)\qquad \ \ \ \ \\ = s_A*s_B(X_{1,1}*W_{1,1}+X_{1,2}*W_{2,1} \ \ \ \ \\ +X_{1,3}*W_{3,1})(需满足s_A=s_a,s_c,s_e才行) y1,1                                                          =x1,1w1,1+x1,2w2,1+x1,3w3,1=saX1,1sbW1,1+  scX1,2sdW2,1+   seX1,3sfW3,1(量化)    =sAsB(X1,1W1,1+X1,2W2,1    +X1,3W3,1)(需满足sA=sa,sc,se才行)

手动实现的一些细节(代码来自github,A simple network quantization demo using pytorch from scratch)

  • 基础公式
def quantize_tensor(x, scale, zero_point, num_bits=8, signed=False):
    if signed:
        qmin = - 2. ** (num_bits - 1)
        qmax = 2. ** (num_bits - 1) - 1
    else:
        qmin = 0.
        qmax = 2. ** num_bits - 1.
 
    q_x = zero_point + x / scale
    q_x.clamp_(qmin, qmax).round_()
    
    return q_x
 
def dequantize_tensor(q_x, scale, zero_point):
    return scale * (q_x - zero_point)
class QConv2d(QModule):

    def __init__(self, conv_module, qi=True, qo=True, num_bits=8):
        super(QConv2d, self).__init__(qi=qi, qo=qo, num_bits=num_bits)
        self.num_bits = num_bits
        self.conv_module = conv_module
        self.qw = QParam(num_bits=num_bits)
        self.register_buffer('M', torch.tensor([], requires_grad=False))  # 将M注册为buffer

    def freeze(self, qi=None, qo=None):
        
        if hasattr(self, 'qi') and qi is not None:
            raise ValueError('qi has been provided in init function.')
        if not hasattr(self, 'qi') and qi is None:
            raise ValueError('qi is not existed, should be provided.')

        if hasattr(self, 'qo') and qo is not None:
            raise ValueError('qo has been provided in init function.')
        if not hasattr(self, 'qo') and qo is None:
            raise ValueError('qo is not existed, should be provided.')

        if qi is not None:
            self.qi = qi
        if qo is not None:
            self.qo = qo
        self.M.data = (self.qw.scale * self.qi.scale / self.qo.scale).data

        self.conv_module.weight.data = self.qw.quantize_tensor(self.conv_module.weight.data)
        self.conv_module.weight.data = self.conv_module.weight.data - self.qw.zero_point

        self.conv_module.bias.data = quantize_tensor(self.conv_module.bias.data, scale=self.qi.scale * self.qw.scale,
                                                     zero_point=0, num_bits=32, signed=True)

    def forward(self, x):
        if hasattr(self, 'qi'):
            self.qi.update(x)
            x = FakeQuantize.apply(x, self.qi)

        self.qw.update(self.conv_module.weight.data)

        x = F.conv2d(x, FakeQuantize.apply(self.conv_module.weight, self.qw), self.conv_module.bias, 
                     stride=self.conv_module.stride,
                     padding=self.conv_module.padding, dilation=self.conv_module.dilation, 
                     groups=self.conv_module.groups)

        if hasattr(self, 'qo'):
            self.qo.update(x)
            x = FakeQuantize.apply(x, self.qo)

        return x

PTQ(Post Training Quantization)

    def quantize(self, num_bits=8):
        self.qconv1 = QConvBNReLU(self.conv1, self.bn1, qi=True, qo=True, num_bits=num_bits)
        self.qmaxpool2d_1 = QMaxPooling2d(kernel_size=2, stride=2, padding=0)
        self.qconv2 = QConvBNReLU(self.conv2, self.bn2, qi=False, qo=True, num_bits=num_bits)
        self.qmaxpool2d_2 = QMaxPooling2d(kernel_size=2, stride=2, padding=0)
        self.qfc = QLinear(self.fc, qi=False, qo=True, num_bits=num_bits)

在这里插入图片描述

预处理

  • ONNX 运行时提供 python API,预处理使用命令python -m onnxruntime.quantization.preprocess
  • 官方提供的实例为: python -m onnxruntime.quantization.preprocess --input mobilenetv2-7.onnx --output mobilenetv2-7-infer.onnx

量化( 动态量化& 静态量化)

静态量化

  • 官方提供的实例为:python run.py --input_model mobilenetv2-7-infer.onnx --output_model mobilenetv2-7.quant.onnx --calibrate_dataset ./test_images/
    静态量化方法首先使用一组称为校准数据的输入来运行模型。在这些运行期间,我们计算每个激活的量化参数。这些量化参数作为常量写入量化模型并用于所有输入。
import numpy as np
import onnxruntime
from onnxruntime.quantization import QuantFormat, QuantType, quantize_static

import resnet50_data_reader
dr = resnet50_data_reader.ResNet50DataReader(
    calibration_dataset_path, input_model_path
)

# Calibrate and quantize model
# Turn off model optimization during quantization
quantize_static(
    input_model_path,
    output_model_path,
    dr,
    quant_format=args.quant_format,# default=QuantFormat.QDQ, 有两种表示量化 ONNX 模型的方法:面向操作符(QOperator)的所有量化运算符都有自己的 ONNX 定义,如 QLinearConv、MatMulInteger 等。面向张量(QDQ;量化和反量化)。此格式在原始运算符之间插入 DeQuantizeLinear(QuantizeLinear(tensor)) 以模拟量化和反量化过程
    per_channel=args.per_channel,
    weight_type=QuantType.QInt8,
    optimize_model=False,
)

动态量化

  • 动态量化动态计算激活的量化参数(比例和零点)。这些计算增加了推理成本,但与静态计算相比通常可以获得更高的准确性。
import onnx
from onnxruntime.quantization import quantize_dynamic, QuantType

model_fp32 = 'path/to/the/model.onnx'
model_quant = 'path/to/the/model.quant.onnx'
quantized_model = quantize_dynamic(model_fp32, model_quant)# 
quantized_model = quantize_dynamic(model_fp32, model_quant, weight_type=QuantType.QUInt8)

调试与优化

  • 调试与优化见官方的例子
  • console python run_qdq_debug.py --float_model mobilenetv2-7-infer.onnx --qdq_model mobilenetv2-7.quant.onnx --calibrate_dataset ./test_images/

参考与更多

FX

- PyTorch量化感知训练(QAT)步骤

  • https://github.com/leimao/PyTorch-Quantization-Aware-Training/blob/main/cifar.py

  • https://leimao.github.io/blog/PyTorch-Quantization-Aware-Training/

  • Codes for paper: Central Similarity Quantization for Efficient Image and Video Retrieval,

  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PyTorch支持通过量化技术来压缩模型,减小模型大小和内存占用,并提高模型的推理性能。其中,PTQ(Post Training Quantization)是一种常见的量化方法,它可以在训练后对模型进行量化PTQ的基本思路是将原始模型中的浮点数参数转化为固定位宽的整数,从而减小模型的大小和内存占用,提高模型在嵌入式设备上的推理速度。在PTQ中,可以对权重、激活值、梯度等进行量化。 下面是使用PyTorch进行PTQ的基本流程: 1. 定义模型 首先需要定义一个PyTorch模型。 2. 定义量化方法 接下来需要定义量化方法。PyTorch提供了一些量化方法,可以根据实际需求进行选择。例如,可以使用torch.quantization.quantize_dynamic()方法进行动态量化,或者使用torch.quantization.quantize_static()方法进行静态量化。 3. 对模型进行量化 使用定义的量化方法对模型进行量化,将浮点数参数转化为整数参数。可以使用torch.quantization.prepare()方法对模型进行准备,使用torch.quantization.convert()方法进行转换。 4. 测试量化后的模型 量化完成后,需要测试量化后的模型,确保准确性没有明显下降。 下面是一个简单的示例代码,演示了如何使用PyTorch进行PTQ: ```python import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms from torchvision.models import resnet18 from torch.utils.data import DataLoader # 定义模型 model = resnet18() # 定义数据预处理 transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))]) # 加载数据集 trainset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform) trainloader = DataLoader(trainset, batch_size=128, shuffle=True) # 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9) # 训练模型 for epoch in range(5): running_loss = 0.0 for i, data in enumerate(trainloader, 0): inputs, labels = data optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() print('[Epoch %d] loss: %.3f' % (epoch + 1, running_loss / len(trainloader))) # 定义量化方法 quantization_method = torch.quantization.quantize_dynamic # 对模型进行量化 model.qconfig = torch.quantization.get_default_qconfig('fbgemm') quantized_model = quantization_method(model, qconfig_spec={nn.Linear}, dtype=torch.qint8) # 测试量化后的模型 quantized_model.eval() testset = torchvision.datasets.MNIST(root='./data', train=False, download=True, transform=transform) testloader = DataLoader(testset, batch_size=128, shuffle=False) correct = 0 total = 0 with torch.no_grad(): for data in testloader: images, labels = data outputs = quantized_model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print('Accuracy of the network on the test images: %d %%' % (100 * correct / total)) ``` 注意:PTQ可能会对模型的准确性产生一定的影响,因此需要根据实际情况进行调整。同时,PTQ的效果也受到数据集的影响,因此需要在实际应用中进行测试和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值