pytorch模型转为onnx模型格式+PPQ量化工具安装使用与跑通量化demo

前言

最近在做量化相关工作,QQP量化工具是非常全且有文档的repo。目前量化相关的内容太少了,也感谢当前网络上所有量化内容输出作者。这篇文档记录PPQ量化工具安装使用和跑demo,下一篇记录PPQ库里KLD算法的解读。

安装使用

代码库:https://github.com/openppl-public/ppq
配环境:按照PPQ的readme
注意

  • 如果不进行编译,可以不下载ninja;且cuda也不用,因为安装conda安装torch会把cuda全家桶安好 git
  • clone安装,保证最新版本
git clone https://github.com/openppl-public/ppq.git
cd ppq
pip install -r requirements.txt
python setup.py install

至此安装成功

跑量化demo

1. 转换onnx模型

首先需要将torch模型转换为onnx模型。转换主要是两种场景目的:转换torchvision的预训练模型、转换自定义模型。
转换都是两个步骤:加载模型和权重、调onnx接口转换
model = models.vgg16(pretrained=True)就会将模型下载到相应路径

#coding=gbk
# 由.pt导成.onnx
import torch
import torchvision.models as models

# 定义模型和载入模型权重
#model = models.resnet18() # 【改】定义model
#model = models.resnet50()
model = models.vgg16(pretrained=False)
model.load_state_dict(torch.load("/home/xxx/.cache/torch/hub/checkpoints/vgg16-397923af.pth")) # 【改】model权重地址

# #set the model to inference mode
model.eval()

x = torch.randn(1, 3,224,224)	# 生成张量
export_onnx_file = "/home/xxx/model_optimization_tool/jm_log_quant/onnx_format_weight/vgg16.onnx"			# 【改】输出ONNX权重地址
torch.onnx.export(model,
                    x,
                    export_onnx_file,
                    opset_version=10, # ONNX算子的版本,不设置默认为13.0
                    do_constant_folding=True,	# 是否执行常量折叠优化
                    input_names=["input"],	# 输入名
                    output_names=["output"],	# 输出名
                    dynamic_axes={"input":{0:"batch_size"},  # 批处理变量
                                    "output":{0:"batch_size"}})

注意哦(2023.05.11更新)
我这里设置ONNX算子版本为10,用ppq量化不会出错。但是后续我做工作时,量化后模型精度测试报错了。故记录在此,做个内容补充。
A.查看torch转onnx模型的版本(如下是resnet50.onnx)
方式:点击input即可查看
在这里插入图片描述
B.修改转化版本
opset_version=10, # ONNX算子的版本,不设置默认为13.0
C.因ONNX算子版本报错,报错信息
在这里插入图片描述

2.量化demo

有视频教程:PPQ官方教程合集视频教程1的代码是ppq0.6.3版本,但是该版本github网页404,故将其写手打。
文件目录:
——working
————data
——————img1.npy
——————img2.npy
——————…
————model.onnx
——quantized.py

1)准备数据集和原始onnx模型

按照教程,要在当前目录新建working/data文件夹,将生成的数据集(格式为.npy)放在文件夹里。(视频二还有另一种载入数据方式)
原始模型onnx放在working/文件夹
1.只想跑demo不管真实数据,可以生成随机数据集

# 生成随机数据集
import numpy as np
for i in range(32):
	np.save(file=f'working/data/{i+1}',arr=np.random.random(size=[1,3,224,224]))

2.载入imagenet数据集

# 载入ImageNet校准数据集
import os
import torchvision.transforms as transforms
from PIL import Image
import numpy as np
import torch
input_path = "xxxxxxx/xxxxx/x" # 【改】数据集路径
for file in os.listdir(input_path):
    filename = os.fsdecode(file)
    img = Image.open(os.path.join(input_path, filename)).convert('RGB')

    scaler = transforms.Resize((224, 224))
    normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                            std=[0.229, 0.224, 0.225])
    to_tensor = transforms.ToTensor()
    device = torch.device("cpu")
    image = normalize(to_tensor(scaler(img))).unsqueeze(0).to(device)
    
    np.save(file=f'working/data/{filename[:-4]}',arr=image)          # 【改】注意图像格式是.jpg还是.jpeg,.jpeg则filename[:-5]
    print('{} 已完成,进度{} / {}'.format(filename[:-5], os.listdir(input_path).index(file),len(os.listdir(input_path))))

2)执行quantize.py,进行量化

整个量化过程:
设置量化参数 --> 载入数据 --> 执行量化(如果要看结果,则定制一个executor) --> 导出所有层量化结果 --> 分析量化误差(总误差,每层误差) --> 导出量化权重文件
量化文件最后在working/quantized.onnx
注意:该代码输入输出格式都是onnx,但是输出platfor和calibration方法与B站教程不同,该输出模型包含了量化-反量化节点,可以用onnxruntime测试AP

"""
这是一个PPQ量化的入口脚本,将你的模型和数据按要求进行打包:
This file will show you how to quantize your network with PPQ
    You should prepare your model and calibration dataset as follow:
    ~/working/model.onnx                          <--  your model
    ~/working/data/*.npy or ~/working/data/*.bin  <--  your dataset
if you are using caffe model:
    ~/working/model.caffemdoel  <--  your model
    ~/working/model.prototext   <--  your model
### MAKE SURE YOUR INPUT LAYOUT IS [N, C, H, W] or [C, H, W] ###
quantized model will be generated at: ~/working/quantized.onnx
"""
from ppq import *
from ppq.api import *
import os

# modify configuration below:
WORKING_DIRECTORY = 'working'                             # choose your working directory
TARGET_PLATFORM   = TargetPlatform.PPL_CUDA_INT8          # choose your target platform
MODEL_TYPE        = NetworkFramework.ONNX                 # or NetworkFramework.CAFFE
INPUT_LAYOUT          = 'chw'                             # input data layout, chw or hwc
NETWORK_INPUTSHAPE    = [1, 3, 224, 224]                  # input shape of your network
CALIBRATION_BATCHSIZE = 16                                # batchsize of calibration dataset
EXECUTING_DEVICE      = 'cuda'                            # 'cuda' or 'cpu'.
REQUIRE_ANALYSE       = False
DUMP_RESULT = False                              # 是否需要 Finetuning 一下你的网络

# ------------------------------------------------------------------------------
# SETTING对象用于控制PPQ的量化逻辑
# 当你的网络量化误差过高时,你需要修改SETTING对象中的参数进行特定的优化
# ------------------------------------------------------------------------------
SETTING = UnbelievableUserFriendlyQuantizationSetting(
    platform=TARGET_PLATFORM,finetune_steps=2500,
    finetune_lr=1e-3, calibration='kl', # 【改】量化算法可选'kl','pecentile','mse'
    equalization=True, non_quantable_op=None)
SETTING = SETTING.convert_to_daddy_setting()

print('正准备量化你的网络,检查下列设置:')
print(f'WORKING DIRECTORY    : {WORKING_DIRECTORY}')
print(f'TARGET PLATFORM      : {TARGET_PLATFORM.name}')
print(f'NETWORK INPUTSHAPE   : {NETWORK_INPUTSHAPE}')
print(f'CALIBRATION BATCHSIZE: {CALIBRATION_BATCHSIZE}')

# ------------------------------------------------------------------------------
# 此脚本针对单输入模型,输入数据必须是图像数据layout:[n,c,h,w]
# 如果你的模型具有更复杂的输入格式,你可以重写下面的load_calibration_dataset函数
# 请注意,任何可遍历对象都可以作为PPQ的数据集作为输入 
# ------------------------------------------------------------------------------
dataloader = load_calibration_dataset(
    directory=WORKING_DIRECTORY,
    input_shape=NETWORK_INPUTSHAPE,
    batchsize=CALIBRATION_BATCHSIZE,
    input_format=INPUT_LAYOUT)

print('网络正量化中,根据你的量化配置,这将需要一段时间:')
quantized=quantize(
 working_directory=WORKING_DIRECTORY,setting=SETTING,
 model_type=MODEL_TYPE,executing_device=EXECUTING_DEVICE,
 input_shape=NETWORK_INPUTSHAPE,target_platform=TARGET_PLATFORM,
 dataloader=dataloader,calib_steps=32)

# ------------------------------------------------------------------------------
# 如果你需要执行量化后的神经网络并得到结果,则需要创建一个executor
# 这个executor的行为和torch.Module是类似的,你可以利用这个东西来获取执行结果
# 请注意必须在executor之前执行此操作
# ------------------------------------------------------------------------------
executor = TorchExecutor(graph=quantized)
# output=executor.forword(input)

# ------------------------------------------------------------------------------
# 导出PPQ执行网络的所有中间结果,该功能是为了和硬件对比结果
# 中间结果可能十分庞大,因此PPQ将使用线性同余发射器从执行结果中采样
# 对了对比中间结果,硬件执行结果也必须使用同样的随机数种子采样
# 查阅 ppq.util.fetch中的相关代码以进一步了解此内容
# 查阅 ppq.api.fsys中的dump_internal_results函数以确定采样逻辑
# ------------------------------------------------------------------------------
if DUMP_RESULT:
    dump_internal_results(
        graph=quantized,dataloader=dataloader,
        dump_dir=WORKING_DIRECTORY,executing_device=EXECUTING_DEVICE)
    
# -------------------------------------------------------------------
# PPQ 计算量化误差时,使用信噪比的倒数作为指标,即噪声能量 / 信号能量
# 量化误差 0.1 表示在整体信号中,量化噪声的能量约为 10%
# 你应当注意,在 graphwise_error_analyse 分析中,我们衡量的是累计误差
# 网络的最后一层往往都具有较大的累计误差,这些误差是其前面的所有层所共同造成的
# 你需要使用 layerwise_error_analyse 逐层分析误差的来源
# -------------------------------------------------------------------
print('正计算网络量化误差(SNR),最后一层的误差应小于 0.1 以保证量化精度:')
reports = graphwise_error_analyse(
    graph=quantized, running_device=EXECUTING_DEVICE, steps=32,
    dataloader=dataloader, collate_fn=lambda x: x.to(EXECUTING_DEVICE))
for op, snr in reports.items():
    if snr > 0.1: ppq_warning(f'层 {op} 的累计量化误差显著,请考虑进行优化')

if REQUIRE_ANALYSE:
    print('正计算逐层量化误差(SNR),每一层的独立量化误差应小于 0.1 以保证量化精度:')
    layerwise_error_analyse(graph=quantized, running_device=EXECUTING_DEVICE,
                            interested_outputs=None,
                            dataloader=dataloader, collate_fn=lambda x: x.to(EXECUTING_DEVICE))

print('网络量化结束,正在生成目标文件:')
export_ppq_graph(
    graph=quantized, platform=TargetPlatform.ONNXRUNTIME, 
    graph_save_to = os.path.join(WORKING_DIRECTORY, 'quantized.onnx'),
    config_save_to = os.path.join(WORKING_DIRECTORY, 'quant_cfg.json'),
    quantized_param = True)   #【改】 platform:保证输出onnxruntime格式带量化和反量化节点;
                              # quantized_param:确保param储存int8格式,规避onnxruntime无法量化

左图是量化后的quantized.onnx(设置quantized_param = True),
在这里插入图片描述

参考文档:https://www.cnblogs.com/ruidongwu/p/16180991.html

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值