前言

概述

本文档详细介绍了如何使用AMCT,对pytorch框架的网络模型进行量化。

读者对象

本文档主要适用于以下工程师:

  • 技术支持工程师

  • 软件开发工程师

掌握以下经验和技能可以更好地理解本文档:

  • 熟悉Linux基本命令。

  • 对图像分析方法有一定的了解。

符号约定

在本文中可能出现下列标志,它们所代表的含义如下。

符号

说明

表示如不避免则将会导致死亡或严重伤害的具有高等级风险的危害。

表示如不避免则可能导致死亡或严重伤害的具有中等级风险的危害。

表示如不避免则可能导致轻微或中度伤害的具有低等级风险的危害。

用于传递设备或环境安全警示信息。如不避免则可能会导致设备损坏、数据丢失、设备性能降低或其它不可预知的结果。

“须知”不涉及人身伤害。

对正文中重点信息的补充说明。

“说明”不是安全警示信息,不涉及人身、设备及环境伤害信息。

修改记录

文档版本

发布日期

修改说明

00B01

2025-09-15

第1次临时版本发布。

概述

简介

本文介绍如何通过高级模型压缩工具(Advanced Model Compression Toolkit,简称AMCT)对原始pytorch框架的网络模型进行量化,量化是指对模型的权重(weight)和数据(activation)进行低比特处理,让最终生成的网络模型更加轻量化,从而达到节省网络模型存储空间、降低传输时延、提高计算效率,达到性能提升与优化的目标。

AMCT是基于pytorch框架的Python工具包,实现了模型中算子融合,和对模型中可量化层的独立量化,并将量化后的模型保存为onnx文件。其中量化后的仿真模型可以在CPU或者GPU上运行,完成精度仿真;量化后的部署模型可以部署在SoC上运行,达到提升推理性能的目的。该工具优点如下。

  • 使用方便,接口简单,pip install安装后,在用户原有的pytorch推理脚本上调用API即可完成量化。

  • 与硬件配套,生成的量化模型(deploy模型)经过ATC工具转换后可用于对硬件的推理。

  • 量化可配置,用户可自行修改量化配置文件,调整量化策略,获取较优的量化结果。

  • 自定义量化算法。

  • 引入了Torch.FX静态图特性,支持在pytorch上直接进行中间结果导出和模型可视化。

AMCT使用场景如图1所示,AMCT当前仅支持在Ubuntu 18.04架构操作系统进行部署,配套信息请参见环境准备。使用该工具量化完的模型,需要借助ATC工具转换成SoC的离线模型,然后完成推理操作。

图 1 部署架构

概念介绍

根据量化方法不同,分为训练后量化(Post-Training Quantization)和量化感知训练(Quantization-Aware Training);上述两种量化方法,根据量化对象不同,分为权重(weight)量化和数据(activation)量化。

下面分别介绍相关概念:

训练后量化

训练后量化,是指将训练后模型中的权重由float32量化到int8或者int4,并通过少量校准数据集在模型推理时对数据(activation)进行校准量化。量化过程请参见训练后量化。

  • 校准数据集

    数据的量化因子的确定过程(calibration过程),网络模型将校准集中的每一份数据作为输入进行前向推理,量化算法积攒下每个待量化层/算子的对应输入数据,据此来确定量化因子。由于量化因子的确定和校准数据集的选择相关,量化后模型的精度也和校准数据集的选择相关,推荐使用验证集的子集作为校准数据集。

  • 数据(activation)量化

    数据量化是对每个待量化的层/算子的输入数据进行统计,每个层/算子计算出最优的一组scale和offset(参数解释请参见量化因子记录文件说明)。

    数据是模型推理计算的中间结果,其范围与模型输入相关,因此需要使用一组参考输入(校准数据集)作为激励,从而记录下来待量化层/算子的输入数据,搜索得到量化因子(scale和offset)。由于在做数据calibration的过程中,需要占用额外的存储空间(显存/内存)来存储用于确定量化因子的输入数据,所以对于显存/内存的占用,比仅推理的过程要高,额外占用空间的大小和calibration过程中的batch_size* batch_num正相关。支持4bit、8bit和16bit量化。

  • 权重(weight)量化

    训练后模型的权值已经确定,数值的范围也已经确定,因此直接根据权值的数据范围进行量化。支持4bit和8bit量化。

  • 反量化

    反量化指权重(weight)/数据(校准数据集)/偏差(bias)从已量化的int转化成float的过程,如图1所示。

    图 1 反量化示意图

  • 伪量化

    伪量化主要针对训练和推理过程中对量化误差的模拟场景,其能够保证大多数浮点训练框架不变的情况下,通过量化-反量化的机制来模拟误差。

    例如,数据和权重从float32类型转化为int8类型,然后再经过一轮反量化转换回float32类型,如图2所示,量化(Quant)和反量化(DeQuant)的过程组合为伪量化。

    图 2 伪量化示意图

  • 真量化

    真量化能够模拟板端硬件的误差。例如图3所示,权重(weight)/数据(activation)/偏差(bias)经过量化转化int类型,然后执行后续的乘法运算和加法运算,再次从浮点重量化(ReQuant)为定点的计算过程称为真量化。

    图 3 真量化示意图

量化感知训练

量化感知训练,是指借助用户完整训练数据集,在训练过程中引入量化操作,通过在训练前向计算中对数据和权重进行量化反量化,引入量化误差损失,从而在训练过程中提高模型对量化效应的适应能力,提高最终的量化模型精度。

量化感知训练缺点是较为耗时,同时需要大量数据。量化过程请参见量化感知训练。

  • 训练数据集

    基于用户训练网络中的数据集。

  • 数据(activation)量化

    数据量化是迭代训练截断最大值和截断最小值,并通过这两个值来计算当前的scale和offset。数据是模型推理计算的中间结果,通过数据量化算法,在量化感知训练的过程中,不断优化这两个参数,得到最终的最优参数。

  • 权重(weight)量化

    权重量化指的是在量化感知训练的过程中不断优化权重的量化参数,通过权重量化算法,得到最终的权重量化参数。

量化训练,支持量化的层以及约束参见表1。

运行流程

具体运行流程如图1所示。因为最终部署模型为.onnx模型,原始被转换模型需要是可转为onnx的模型。

图 1 运行流程

表 1 运行流程关键操作步骤说明

关键步骤

说明

获取软件包

安装前请先获取对应软件包,详情请参见获取软件包。

安装前准备

安装AMCT之前,需要创建AMCT的安装用户,检查系统环境是否满足要求,安装依赖以及上传软件包等一系列动作。详细操作请参见安装前准备。

安装

参见安装安装pytorch框架的AMCT。

(可选)编写脚本,调用AMCT的API

如果用户需要量化自己的网络模型,不使用本手册提供的sample进行量化,则需要修改量化脚本,进行适配,然后才能进行量化。

执行量化

根据量化方法不同,分为训练后量化和量化感知训练。详细量化步骤请参见训练后量化和量化感知训练。

使用Mindstudio或MindCmd工具进行模型转换

用户使用上述量化后的部署模型,通过Mindstudio或MindCmd工具转换成SoC的离线模型,详细可参考对应的用户指南,然后可以使用该模型进行推理。

工具安装

安装AMCT

获取软件包

AMCT只支持在Ubuntu 18.04 x86_64架构服务器安装;安装前,请先获取AMCT软件包:amct_pytorch

安装前准备

Ubuntu x86系统

AMCT用户准备(可选)

支持任意用户(root或者非root)安装AMCT,本章节以非root用户为例进行操作。

  • 若使用root用户安装,则不需要操作该章节,不需要对root用户做任何设置。

  • 若使用已存在的非root用户安装,须保证该用户对$HOME目录具有读写以及可执行权限。

  • 若使用新的非root用户安装,请参考如下步骤进行创建,如下操作请在root用户下执行。本手册以该种场景为例执行AMCT的安装。

    1. 执行以下命令创建AMCT安装用户并设置该用户的$HOME目录。

      useradd -d /home/username -m username
      
    2. 执行以下命令设置密码。

      passwd username
      

说明: username为安装AMCT的用户名,该用户的umask值不能小于0027:

  • 若要查看umask的值,则执行命令:umask

  • 若要修改umask的值,则执行命令:umask 新的取值

配置AMCT安装用户权限(可选)

当用户使用非root用户安装时,需要操作该章节,否则请忽略。

AMCT安装前需要下载相关依赖软件,下载依赖软件需要使用sudo apt-get权限,请以root用户执行如下操作。

  1. 打开“/etc/sudoers”文件:

    chmod u+w /etc/sudoers
    vi /etc/sudoers
    
  2. 在该文件“# User privilege specification”下面增加如下内容。

    username ALL=(ALL:ALL)   NOPASSWD:SETENV:/usr/bin/apt-get,/usr/bin/pip, /bin/tar, /bin/mkdir, /bin/sh, /bin/bash, /usr/bin/make, /usr/bin/pip3, /usr/bin/pip3.7, /usr/bin/pip3.7.5, /bin/ln
    

    “username”为执行安装脚本的非root用户名。

    说明: 请确保“/etc/sudoers”文件的最后一行为“#includedir /etc/sudoers.d”,如果没有该信息,请手动添加。

  3. 添加完成后,执行:wq!保存文件。

  4. 执行以下命令取消“/etc/sudoers”文件的写权限:

    chmod u-w /etc/sudoers
    
环境准备

AMCT目前仅支持在Ubuntu 18.04 x86_64架构操作系统安装,配套信息如表1

表 1 Ubuntu X86_64架构配套版本信息

类别

版本限制

获取方式

注意事项

操作系统

18.04 64位Ubuntu操作系统

请从http://old-releases.ubuntu.com/releases/18.04.1/网站下载对应版本软件进行安装,可以下载Server版“ubuntu-18.04-server-amd64.iso”

-

Python

3.7.5

请参见安装Python3.7.5(Ubuntu)。

使用apt-get命令安装依赖时,请确保服务器能够连接网络。

pip

-

pip install --upgrade pip

pip 需要更新到最新版本

CUDA toolkit/CUDA driver

10.2/11.3

请用户自行获取相关软件包进行安装,例如可以参见如下链接获取相关toolkit包,该包中包括driver软件包。

https://developer.nvidia.com/cuda-toolkit-archive

如果使用GPU模式执行量化功能,则CUDA软件必须安装。

graphviz

-

sudo apt-get insall graphviz

生成svg依赖的系统工具包

检查源

安装依赖时,请确保AMCT所在服务器能够连接网络,请在root用户下执行如下命令检查源是否可用。

apt-get update

如果命令执行报错,则检查网络是否连接或者把“/etc/apt/sources.list”文件中的源更换为可用的源。

安装依赖

请使用AMCT的安装用户安装依赖的软件,参考表1

表 1 依赖列表

依赖名称

版本号

安装命令

pytorch版本的CPU或GPU

1.10.2

1.10.2版本pytorch,CPU或GPU安装命令:
  • CPU版本安装命令:
    python3.7.5 -m pip --trusted-host=download.pytorch.org install torch==1.10.2+cpu torchvision==0.11.3+cpu torchaudio==0.10.2 -f https://download.pytorch.org/whl/torch_stable.html
  • GPU版本安装命令:
    # CUDA 10.2:
    python3.7.5 -m pip --trusted-host=download.pytorch.org install torch==1.10.2+cu102 torchvision==0.11.3+cu102 torchaudio==0.10.2 -f https://download.pytorch.org/whl/torch_stable.html
    # CUDA 11.3:
    python3.7.5 -m pip --trusted-host=download.pytorch.org install torch==1.10.2+cu113 torchvision==0.11.3+cu113 torchaudio==0.10.2 -f https://download.pytorch.org/whl/torch_stable.html

numpy

>=1.16.0

pip3.7.5 install numpy==1.16.0

protobuf

>=3.13.0

<=3.19.0

pip3.7.5 install protobuf==3.13.0

ONNX

>=1.6.0

pip3.7.5 install onnx==1.6.0

interval

1.0.0

pip3.7.5 install interval==1.0.0

pyyaml

>=5.4

pip3.7.5 install pyyaml==5.4

pydot

-

pip3.7.5 install pydot

scipy

-

pip3.7.5 install scipy

wheel

  

pip3.7.5 install wheel

上传软件包

以AMCT的安装用户将amct_pytorch软件包上传到Linux服务器任意目录下,本示例为上传到$HOME/amct/目录,获得如下内容。

表 1 AMCT软件包解压后内容

一级目录

二级目录

说明

使用场景及注意事项

amct_pytorch/

pytorch框架AMCT目录。

  • 只支持部署在Ubuntu 18.04 x86_64架构服务器。
  • 使用方法请参见《AMCT使用指南(PyTorch)》。
  • 量化完的模型,如果要执行推理,则需要在板端推理环境运行。

hotwheels_amct_pytorch-{version}-py3-none-linux_{arch}.tar.gz

pytorch框架AMCT源码安装包。

amct_pytorch_sample.tar.gz

pytorch框架量化sample包。

其中:_{version}表示AMCT具体版本号。{arch}_表示linux操作系统的架构。

安装

  1. 在AMCT软件包所在目录下,执行如下命令进行源码安装。

    pip3.7.5 install hotwheels_amct_pytorch-{version}-py3-none-linux_{arch}.tar.gz
    

    其中:_{version}表示AMCT具体版本号和CUDA的版本,{arch}_表示软件包支持的安装服务器具体架构形态。如果用户使用root用户安装AMCT,并且使用了--target参数,请确保--target参数指定的路径为当前用户的路径,避免指定到其他非root用户。

  2. 若出现如下信息则说明工具安装成功。

    Successfully build hotwheels-amct-pytorch 
    ... 
    Successfully installed hotwheels-amct-pytorch-{version}
    

    用户可以在python3.7.5软件包所在路径下(例如:$HOME/.local/lib/python3.7.5/site-packages)查看已经安装的AMCT,例如:

    drwxr-xr-x  5 amct amct   4096 Mar 17 11:50 hotwheels/ 
    drwxr-xr-x  2 amct amct   4096 Mar 17 11:50 hotwheels_amct_pytorch-{version}.dist-info/
    

    其中amct_pytorch即为AMCT所在安装目录。

日志级别控制

AMCT量化过程中的日志信息以及日志级别,可以通过环境变量设置,本章节给出详细设置方法。其中日志包括打印在屏幕上的日志以及保存到amct_log/amct_pytorch.log文件中的日志。该部分环境变量为可选配置,如果不设置,则按照默认日志级别,默认级别为INFO。

  • 变量取值

    日志打印级别通过如下两个变量设置:

    • AMCT_LOG_FILE_LEVEL: 控制amct_pytorch.log日志文件的信息级别以及生成精度仿真模型时,对应量化层生成的日志文件信息级别。

    • AMCT_LOG_LEVEL:控制屏幕输出的信息级别。

    有效取值以及含义如表1所示。

表 1 变量取值范围

信息级别

含义

信息描述

DEBUG

输出DEBUG/INFO/WARNING/ERROR级别的运行信息。

详细的流程信息,包括量化层及对应的处理阶段(融合,参数量化或者数据量化等)。

INFO

输出INFO/WARNING/ERROR级别的运行信息。默认为INFO。

概要的量化处理信息,包含量化的阶段等信息。

WARNING

输出WARNING/ERROR级别的运行信息。

量化处理过程中的警告信息。

ERROR

输出ERROR级别的运行信息。

量化处理过程中的错误信息。

信息级别不区分大小写,即Info、info、INFO均为有效取值。

  • 使用示例

    如下命令只是样例,用户根据实际情况进行设置。

    • 将量化日志amct_pytorch.log信息级别设置为INFO级别。

      export AMCT_LOG_FILE_LEVEL=INFO

    • 将屏幕打印输出信息级别设置为INFO级别。

      export AMCT_LOG_LEVEL=INFO

更新AMCT

建议使用最新版本的AMCT,以便获得最新功能。使用新版本之前请先参见卸载AMCT卸载之前版本的AMCT,然后参见安装AMCT安装最新版本。

卸载AMCT

若用户不再使用AMCT时,可以参见该章节将其卸载。

  1. 以AMCT的安装用户在Linux服务器任意目录执行如下命令进行卸载:

    pip3.7.5 uninstall hotwheels-amct-pytorch
    
  2. 出现如下信息时,输入“y”:

    Uninstalling hotwheels-amct-pytorch-{version}:  
       Would remove: 
        ...
        ...  
    Proceed (y/n)? y
    

    若出现如下信息则说明卸载成功。

    Successfully uninstalled hotwheels-amct-pytorch-{version} 
    

量化

训练后量化

实现原理

图 1 训练后量化实现原理

量化示例

量化前提

模型准备

以AMCT的安装用户将需要量化的pytorch模型上传到Linux服务器任意目录下。本手册以下载的resnet50网络模型为例进行说明(sample中提供的mobilenet_v2模型量化脚本和resnet50网络模型量化脚本一样,仅模型不同,不再介绍)。

用户准备的模型,建议先在pytorch环境中执行推理,并确保能成功运行,精度没有异常。

模型下载

  1. 在sample中模型使用torchvision中的resnet50(pretrain=True),如果模型的预训练权重下载较慢,请手动下载(https://download.pytorch.org/models/resnet50-0676ba61.pth)

  2. 把下载的权重文件复制到 /user/.cache/torch/hub/checkpoints/文件夹下

注意:如果模型权重文件已存在,以上两个步骤无需执行。

数据集准备

使用AMCT对模型完成量化后,需要对模型进行推理,以测试量化数据的精度。推理过程中需要使用和模型相匹配的数据集。

以AMCT的安装用户将和模型相匹配的数据集上传到Linux服务器任意目录下。

当前calibration的sample提供160张的images数据集,方便用户快速上手使用。

校准集准备

校准集用来产生量化参数,保证精度。

计算量化参数的过程被称为“校准(calibration)”。校准过程需要使用一部分测试图片来针对性计算量化参数。batch_num*batch_size为量化使用的校准集图片数量,其中batch_size为每个batch所用的图片数量该值为sample中传入的batch_size参数,batch_num为config.json中的量化参数默认为1。当前sample使用images dataloader的一个batch的数据作为校准集。

量化步骤

下面以sample包中分类网络量化脚本resnet50_calibration.py,演示如何执行量化脚本,执行前需要额外安装一个第三方python工具包pillow,安装命令:pip install pillow。

  1. 获取量化脚本。

    在量化脚本sample包amct_pytorch_sample.tar.gz所在路径下执行如下解压命令,获取量化脚本。

    tar -zxvf amct_pytorch_sample.tar.gz 
    cd sample
    

    其中:

    resnet50/ 
    ├── dump_layer_outputs.py     //保存量化模型中每一层网络的输出
    ├── calibration
    │    └─resnet50_calibration.py                      //calibration 量化脚本
    │    └─images
    │       └─xx.jpg               //验证集图片
    │       └─image_label.txt      //验证集和标签的文本列表
    ├── retrain
    │    └─resnet50_retrain.py                      //重训量化脚本
    │    └─resnet50_retrain_from_checkpoint.py      //断点重训脚本
    ├── quantize_conf
    │    └─ custom_quantize.yml    // 简易量化配置文件
    
  2. 执行量化。

    切换到量化脚本所在目录sample/resnet50/calibration,执行如下命令量化resnet50网络模型。

    python3.7.5 resnet50_calibration.py --benchmark
    # dump layer outputs
    python3.7.5 ../dump_layer_outputs.py --config_file ./tmp/calibration/config.json --pt ./checkpoints/calibration/resnet50_calibration.pt
    

    须知:

    • 默认使用sample中自带的images验证集。

    • 如果无GPU环境,直接执行上述量化命令,则使用cpu进行训练,训练速度较慢。

    • 如果存在多个GPU,默认使用0号GPU,也可以在上述命令前面加上CUDA_VISIBLE_DEVICES=gpu_index,修改gpu_index来切换其他GPU。

    • 暂不支持多卡量化。

    表 1 训练后量化所用参数说明

    参数

    说明

    --eval_set

    • 是否必填:否。
    • 数据类型:string。
    • 默认值:./images/image_label.txt。
    • 参数解释:验证数据集路径,默认使用sample自带的images。

    --eval_iter

    • 是否必填:否。
    • 数据类型:int。
    • 默认值:20。
    • 参数解释:验证数据集的迭代次数。

    --batch_size

    • 是否必填:否。
    • 数据类型:int。
    • 默认值:16。
    • 参数解释:pytorch执行一次前向推理所使用的样本数量,根据内存或显存大小酌情调整。

    --benchmark

    • 是否必填:否。
    • 数据类型:bool。
    • 默认值:False。
    • 参数解释:迭代全量数据集进行模型推理,传入即开启。

    --config_def

    • 是否必填:否。
    • 数据类型:string。
    • 参数解释:简易量化配置文件。

    若出现如下信息,则说明量化成功。

    ==> AMCT run resnet50_calibration.pyin 30s
    
  3. 量化结果说明。

    量化成功后,在脚本的同级目录下还会生成量化配置文件等中间文件、量化日志文件以及量化结果文件。

    • amct_log/hotwheels.amct_pytorch.log:量化日志文件,记录了量化过程的日志信息。

    • checkpoints/calibration:训练后量化结果文件:

      • resnet50_calibration_deploy_model.onnx:量化后的可在SoC部署的模型文件。

      • resnet50_quant_param_record.txt:量化参数文件。

      • resnet50_calibration.pt :校准后保存下来的pytorch模型权重文件

    • tmp/calibration:临时文件

      • config.json:描述了如何对模型中的每一层进行量化。如果量化脚本所在目录下已经存在量化配置文件,则再次调用create_quant_config_fx接口时,如果新生成的量化配置文件与已有的文件同名,则会覆盖已有的量化配置文件,否则生成新的量化配置文件。

        如果量化后的模型推理精度不满足要求,用户可以修改config.json文件,量化配置文件内容、修改原则以及参数解释请参见量化调优。

      • float.svg:用户需要量化的pytorch模型原始浮点静态图。

      • quant.svg:用户需要量化的pytorch模型插入量化层以及融合后的静态图。

  4. 如果用户需要将量化后的deploy模型,转换为适配SoC的离线模型,则请参见MindCmd 对应的用户指南。

量化调优

本章节以分类网络量化配置文件为例进行说明。

参数配置说明

如果通过create_quant_config_fx接口生成的calibration_config.json量化配置文件,推理精度不满足要求,则需要参见该章节不断调整calibration_config.json文件中的内容,直至精度满足要求,该文件部分内容样例如下:

{
    "add":{
        "quant_enable":true,
        "activation_quant_params":[
            {
                "quantizer":"UlqFakeQuantize",
                "quantizer_args":{
                    "num_bits":8,
                    "fixed_min":false,
                    "observer":"IFMRObserver",
                    "batch_num":1,
                    "object_layer":"add"
                }
            },
            {
                "quantizer":"UlqFakeQuantize",
                "quantizer_args":{
                    "num_bits":8,
                    "fixed_min":false,
                    "observer":"IFMRObserver",
                    "batch_num":1,
                    "object_layer":"add"
                }
            }
        ]
    },
"conv1":{
        "quant_enable":true,
        "activation_quant_params":[
            {
                "quantizer":"UlqFakeQuantize",
                "quantizer_args":{
                    "num_bits":8,
                    "fixed_min":false,
                    "observer":"IFMRObserver",
                    "batch_num":1,
                    "object_layer":"conv1"
                }
            }
        ],
        "weight_quant_params":{
            "quantizer":"ArqWeightFakeQuantize",
            "quantizer_args":{
                "observer":"ArqObserver",
                "num_bits":8,
                "channel_wise":true
            }
        }
    },
    "fc":{
        "quant_enable":true,
        "activation_quant_params":[
            {
                "quantizer":"UlqFakeQuantize",
                "quantizer_args":{
                    "num_bits":8,
                    "fixed_min":false,
                    "observer":"IFMRObserver",
                    "batch_num":1,
                    "object_layer":"fc"
                }
            }
        ],
        "weight_quant_params":{
            "quantizer":"ArqWeightFakeQuantize",
            "quantizer_args":{
                "observer":"ArqObserver",
                "num_bits":8,
                "channel_wise":false
            }
        }
    },
}

配置文件中参数说明如下。

表 1 quant_enable参数说明

作用

该层是否可量化。

类型

bool

取值范围

true或false

参数说明

取值为true时量化该层,取值为false时不量化该层。

推荐配置

true

必选或可选

必选

表 2 activation_quant_params参数说明

作用

该层数据量化的参数。

类型

object

取值范围

参数说明

activation_quant_params内部包含如下参数:

  • quantizer
  • quantizer_args

推荐配置

必选或可选

可选

表 3 weight_quant_params参数说明

作用

该层权重量化的参数。

类型

object

取值范围

参数说明

  • quantizer
  • quantizer_args

推荐配置

必选或可选

可选

表 4 quantizer参数说明

作用

fakequant算法

类型

object

取值范围

参数说明

用来量化数据和权重的量化算法

推荐配置

必选或可选

必选

表 5 quantizer_args参数说明

作用

数据量化和权重量化中的quantizer的量化参数

类型

object

取值范围

参数说明

  • num_bits
  • fixed_min
  • observer
  • batch_num
  • object_layer
  • channel_wise

推荐配置

必选或可选

必选

参数调优说明

按照calibration_config.json文件中的默认配置进行量化,若量化后的推理精度不满足要求,则按照如下步骤调整量化配置文件中的参数。

  1. 执行amct_pytorch_sample.tar.gz包中的量化脚本,根据create_quant_config_fx接口生成的默认配置进行量化。

  2. 若根据步骤1中的默认配置进行量化后,精度满足要求,则调参结束,否则进行步骤3

  3. 手动调整量化策略

    • 调整IFMRObserver算法中的batch_num:

      batch_num控制量化使用数据的batch数目,可根据batch的大小以及量化需要使用的图片数量调整。通常情况下,量化过程中使用的数据样本越多,量化后精度损失越小,但过多的数据并不会带来精度的提升,反而会占用较多的内存,降低量化的速度,并可能引起内存、显存、线程资源不足等情况。因此,建议batch_num*batch_size为16或32。

    • 其他的observer算法,则调整forward轮次,减少或增加。

  4. 若按照步骤3中的量化配置进行量化后,精度满足要求,则调参结束,否则进行步骤5

  5. 手动调整量化配置文件中的quant_enable:

    quant_enable可以指定该层是否量化,取值为true时量化该层,取值为false时不量化该层,将该层及配置删除也可不量化该层。通常情况下,量化层数目越少,量化后精度越高。某些层对量化比较敏感,比如说首层、尾层、detphwise卷积层以及参数量偏少的层,量化后精度会有较大的下降,可按照精度情况调整量化层。

  6. 若按照步骤5中的量化配置进行量化后,精度满足要求,则调参结束,否则进行步骤7

  7. 手动调整量化配置文件中的activation_quant_params和weight_quant_params:

    • activation_quant_params中的num_bits(4 or 8~12(整数类型) or 16)控制数据量化的位宽,数据量化高精推荐使用12bit。需要注意的是,下游ATC(Advanced Tensor Compiler)中--compile_mode参数选项也可对量化后的数据bit位宽进行指定(8/16 bit),相比较而言,AMCT activation_quant_params中对于位宽的指定具有更高的优先级。

    • weight_quant_params中的channel_wise控制权重量化时每个channel是否采用不同的量化因子,取值为true时,每个channel独立量化,量化因子不同;取值为false时所有channel同时量化,共享同一个量化因子。通常情况下,每个channel独立量化,量化后的精度会比较高,推荐使用。但全连接层没有channel,修改该参数不起作用。

  8. 若按照步骤7中的量化配置进行量化后,精度满足要求,则调参结束,否则表明量化对精度影响很大,不能进行量化,去除量化配置。

    图 1 调参流程

量化感知训练

实现原理

图 1 量化感知训练实现原理

量化示例

量化前提

  • 模型准备

    请参见模型准备。

  • 数据集准备

    由于进行量化感知训练需要使用大量数据对量化参数进行进一步优化,因此进行量化感知训练数据需要与模型训练数据一致。ResNet50的数据集是在ImageNet的子集ILSVRC-2012-CLS上训练而来,因此需要用户自己准备ImagenetPytorch格式的数据集(获取方式请参见https://github.com/pytorch/examples/tree/master/imagenet)。如果更换其他数据集,则需要自己进行数据预处理。

量化步骤

  1. 执行resnet50网络模型的量化感知训练。

    切换到sample目录,执行如下命令。

    • 单卡量化感知训练

      CUDA_VISIBLE_DEVICES=0 python3.7.5 resnet50_retrain.py --train_set /user/train_set --eval_set /user/eval_set --benchmark
      
    • 多卡量化感知训练

      CUDA_VISIBLE_DEVICES=0,1,2,3 python3.7.5 resnet50_retrain.py --train_set /user/train_set --eval_set /user/eval_set --distributed --benchmark
      

      须知:

      • 仅支持伪量化多卡训练,仅支持DistributedDataParallel模式的多卡训练,不支持DataParallel模式的多卡训练。

      • 仅量化模型可以转换成DDP模型,进行训练和推理,生成量化配置文件和量化模型时,用到的浮点模型不能转换成DDP。

    • 断点重训

      python3.7.5 resnet50_retrain_from_checkpoint.py --config_file ./tmp/retrain/config.json --pt ./checkpoints/retrain/resnet50_retrain_last.pt --train_set /user/train_set --eval_set /user/eval_set --distributed --benchmark
      
    • dump layer outputs

      python3.7.5 ../dump_layer_outputs.py --config_file ./tmp/retrain/config.json --pt ./checkpoints/retrain/resnet50_retrain_best.pt --state_dict_name quant_model_weights
      

    注意:train_set和eval_set需指定对应服务器上的数据集路径。

    上述命令只给出了常用的参数,不常用参数以及各个参数解释请参见如表1

    表 1 量化感知训练所用参数说明

    参数

    说明

    --train_set

    • 是否必填:是。
    • 数据类型:string。
    • 参数解释:训练数据集路径。

    --eval_set

    • 是否必填:是。
    • 数据类型:string。
    • 参数解释:验证数据集路径。

    --num_parallel_reads

    • 是否必填:否。
    • 数据类型:int。
    • 默认值:4。
    • 参数解释:用于读取数据集的线程数,根据硬件运算能力酌情调整。

    --epochs

    • 是否必填:否。
    • 数据类型:int。
    • 默认值:10。
    • 参数解释:量化训练过程中的样本训练次数。

    --batch_size

    • 是否必填:否。
    • 数据类型:int。
    • 默认值:16。
    • 参数解释:pytorch执行一次前向推理所使用的样本数量,根据内存或显存大小酌情调整。

    --learning_rate

    • 是否必填:否。
    • 数据类型:float。
    • 默认值:1e-5。
    • 参数解释:学习率。

    --train_iter

    • 是否必填:否。
    • 数据类型:int。
    • 默认值:50。
    • 参数解释:在每个epoch中,要运行的总迭代次数,为了展示量化过程,缩短训练时间,将train_iter设置为较小的值,train_iter的值不能超过train_set的长度。

    --eval_iter

    • 是否必填:否。
    • 数据类型:int。
    • 默认值:50。
    • 参数解释:验证数据集的迭代次数。

    --print_freq

    • 是否必填:否。
    • 数据类型:int。
    • 默认值:10。
    • 参数解释:训练及测试信息的打印频率。

    --dist_url

    • 是否必填:否。
    • 数据类型:string。
    • 默认值:tcp://127.0.0.1:50011。
    • 参数解释:初始化多卡训练通信进程的方法。

    --distributed

    • 是否必填:否。
    • 数据类型:无。
    • 参数解释:使用该参数表示进行多卡训练,否则不进行多卡训练。

    --benchmark

    • 是否必填:否。
    • 数据类型:bool。
    • 默认值:False。
    • 参数解释:迭代全量数据集进行模型训练和推理,传入即开启。

    --config_def

    • 是否必填:否。
    • 数据类型:string。
    • 参数解释:简易量化配置文件的路径。

    --state_dict_name

    • 是否必填:否。
    • 数据类型:string。
    • 参数解释:权重文件中权重对应的键值。
  2. 若出现如下信息,则说明执行量化感知训练成功:

    ==> AMCT run resnet50_retrain.py in 387.5s
    
  3. 量化结果说明。

    量化成功后,在脚本的同级目录下还会生成量化配置文件等中间文件、量化日志文件以及量化结果文件:

    • amct_log/hotwheels.amct_pytorch.log:量化日志文件,记录了量化过程的日志信息。

    • checkpoints/retrain:

      • resnet50_retrain_best.pt:real quant top1 最高的量化模型权重。

      • resnet50_retrain_last.pt:最后一个epoch的量化模型权重。

      • resnet50_retrain_last_deploy_model.onnx:可在SoC部署的量化模型。

      • resnet50_epoch_0_quant_param_record.txt:量化参数文件。

    • tmp/retrain:临时文件

      • config.json:描述了如何对模型中的每一层进行量化。如果量化脚本所在目录下已经存在量化配置文件,则再次调用create_quant_config_fx接口时,如果新生成的量化配置文件与已有的文件同名,则会覆盖已有的量化配置文件,否则生成新的量化配置文件。

        如果量化后的模型推理精度不满足要求,用户可以修改config.json文件,量化配置文件内容、修改原则以及参数解释请参见量化调优。

      • float.svg:用户需要量化的pytorch模型原始浮点静态图。

      • quant.svg:用户需要量化的pytorch模型插入量化层以及融合后的静态图。

      • results.csv:保存每一个epoch的量化模型准确率。

  4. 如果用户需要将量化后的deploy模型,转换为适配SoC的离线模型,则请参见MindCmd 对应的用户指南。

量化调优

参数配置说明

如果通过create_quant_config_fx接口生成的retrain_config.json量化感知训练配置文件,推理精度不满足要求,则需要参见该章节不断调整retrain_config.json文件中的内容,直至精度满足要求,该文件部分内容样例如下(用户修改json文件时,请确保层名唯一,对于需要量化的不带权重的层,因为不用保存权重信息,在原始模型定义文件中,其层名会重复使用,用户需要手动修改模型定义文件)。

配置文件中参数说明参照参数配置说明。

参数调优说明

按照retrain_config.json文件中的默认配置进行量化,若量化后的推理精度不满足要求,则按照如下步骤调整量化配置文件中的参数。

  1. 执行amct_pytorch_sample.tar.gz包中的量化脚本,根据create_quant_config_fx接口生成的默认配置进行量化。若精度满足要求,则调参结束,否则进行步骤2

  2. 将部分量化层取消量化,即将其"retrain_enable"参数修改为"false"。通常模型首尾层对推理结果影响较大,故建议优先取消首尾层的量化。

  3. 完成配置后,精度满足要求则调参结束;否则表明量化感知训练对精度影响很大,不能进行量化感知训练,去除量化感知训练配置。

量化算法

本章节介绍的observer算法和fakequant算法,继承了torch官方量化算法。observer对应量化过程中的calibration场景,fakequant算法对应量化过程中模型训练和推理场景。

observer算法

IFMRObserver

作用:统计权重和数据的max、min、scale和zero_point。

表 1 IFMRObserver

参数名

参数说明

类型

默认值

参数范围

是否必填

object_layer

节点层名

string

num_bits

量化位宽

int

8

4、[8~16]整数

batch_num

如果不配置,则使用默认值1

int

2

大于0

with_offset

是否使用偏移

bool

True

True、Fasle

max_percentile

在从大到小排序的一组数中,决定取第多少大的数,比如有100个数,1.0表示取第100-100*1.0=0,对应的就是第一个大的数。

对待量化的数据做截断处理时,该值越大,说明截断的上边界越接近待量化数据的最大值。

float

0.999999

(0.5,1.0]

min_percentile

在从小到大排序的一组数中,决定取第多少小的数,比如有100个数,1.0表示取第100-100*1.0=0,对应的就是第一个小的数。

对待量化的数据做截断处理时,该值越大,说明截断的下边界越接近待量化数据的最小值。

float

0.999999

(0.5,1.0]

search_start

控制量化因子的搜索开始的点

float

0.7

[0.7,1.3]

search_end

控制量化因子的搜索结束的点

float

1.3

[0.7,1.3]

search_step

控制截断的上边界的浮动范围步长,值越小,浮动步长越小。

float

0.01

(0, (search_end-search_start)]

ArqObserver

作用:为ARQ算法定制的算法。

表 1 ArqObserver

参数名

参数说明

类型

默认值

参数范围

是否必填

num_bits

量化位宽

int

8

4、8

channel_wise

  • 取值为true时,每个channel独立量化,量化因子不同。
  • 取值为false时,所有channel同时量化,共享同一个量化因子。

bool

True

True、Fasle

LuqMseObserver

作用:为LUQ量化算法算法定制的MSE算法。

表 1 LuqMseObserver

参数名

参数说明

类型

默认值

参数范围

是否必填

qscheme

量化方案

string

symmetric

dynamic、affine和symmetric

quant_min

量化范围最小值

int

-128

-(2**(num_bits - 1))

quant_max

量化范围最大值

int

127

(2**(num_bits - 1)) - 1

search_start

搜索开始位置

float

0.5

[0.5,1.5]

search_end

搜索结束位置

float

1.5

[0.5,1.5]

steps

搜索步长

int

100

num_bits

量化位宽

int

8

[8~16]

SnqObserver

作用:为LNQ算法定制的4bit非均匀算法。

表 1 SnqObserver

参数名

参数说明

类型

默认值

参数范围

是否必填

max_iteration

寻找聚类的最大迭代次数

int

1000

[500, 2000]整数

cali_min_distance

寻找聚类的最小距离

float

1e-10

[1e-10, 1e-3]

channel_wise

是否对每个channel采用不同的量化因子

float

True

True、Fasle

init_algo

聚类中心初始化方式

str

"uniform"

"uniform"、"gaussian"、"d2sampling"

fakequant算法

注意: 数据量化位宽为[9~16]bit时,权重量化位宽只支持8bit。 数据量化位宽为4bit时,权重量化位宽只支持4bit。

ARQ权重量化算法

ARQ (Adaptive Range Quantization)算法是对权重直接量化的算法。该算法提供了 两种方式,channel_wise(是否对每个channel采用不同的量化因子)量化和非channel_wise量化,量化原理图如下所示。

channel_wise=False 量化

channel_wise=True 量化

量化配置中ArqWeightFakeQuantize提供的参数channel_wise用来选择量化方式:

  • channel_wise为False时,所有filter一起分析数据分布,进行量化,某一层的所有不同channel共用一个量化因子。

  • channel_wise为True时,每个filter独立做数据分析,进行量化,同一层的每个channel有独立的量化因子。一般来说,每一个filter独立进行量化,channel_wise设为True,量化精度较高;如果每个filter的数据量比较少,量化效果会比较差,此时推荐channel_wise设为False。

注意:全连接层、平均下采样层Pooling(下采样方式为AVE,且非global pooling)和LSTM层没有channel,设置channel_wise为True时,会提示错误信息。

作用:该层权重量化配置。

表 1 ArqWeightFakeQuantize

参数名

参数说明

类型

默认值

参数范围

是否必填

observer

ArqObserver

torch.quantization.ObserverBase

ArqObserver

num_bits

量化位宽

int

8

4、8

channel_wise

是否对每个channel采用不同的量化因子

bool

True

True、Fasle

ULQ数据量化算法

ULQ(Universal Linear Quantization)算法在训练过程中不断训练量化因子,以减少量化损失。该算法初始化时会对数值做量化(包含截断),对初始化敏感。该算法支持ULQ数据量化算法。

作用:该层数据量化配置。

表 1 UlqFakeQuantize

参数名

参数说明

类型

默认值

参数范围

是否必填

observer

IFMRObserver

torch.quantization.ObserverBase

IFMRObserver

num_bits

量化位宽

int

8

[8~16]整数

fixed_min

截断量化算法最小值固定为0

bool

False

True、Fasle

LUQ量化算法

默认支持luq mse算法,luq算法支持数据和权重量化算法。

作用:该层数据量化配置。

表 1 LuqActivationFakeQuantize

参数名

参数说明

类型

默认值

参数范围

是否必填

observer

LuqMseObserver

torch.quantization.ObserverBase

LuqMseObserver

num_bits

量化位宽

int

8

[8~16]整数

max_value_eps

max_value的最小值

float

0.00038928920371275036

表 2 LuqWeightFakeQuantize

参数名

参数说明

类型

默认值

参数范围

是否必填

observer

LuqMseObserver

torch.quantization.ObserverBase

LuqMseObserver

num_bits

量化位宽

int

8

8

max_value_eps

max_value的最小值

float

0.00038928920371275036

LNQ权重量化算法

4bit非均匀权重量化算法。

作用:该层权重量化配置。

表 1 LnqFakeQuantize

参数名

参数说明

类型

默认值

参数范围

是否必填

observer

SnqObserver

torch.quantization.ObserverBase

SnqObserver

SnqObserver

cluster_freq

更新聚类中心的频率

int

400

正整数

max_iteration

寻找聚类的最大迭代次数

int

1000

[500, 2000]整数

min_distance

寻找聚类的最小距离

float

1e-4

[1e-8, 1e-2]

clip_max

学习的边界初始值

float

3.0

大于0

symmetric

是否对称

bool

False

True、Fasle

Fixed量化算法

FixedFakeQuantize分为数据和权重量化算法

表 1 FixedFakeQuantize 数据量化算法参数

参数名

参数说明

类型

默认值

参数范围

是否必填

scale

量化系数

float

zero_point

量化零点值

float 或 int

num_bits

量化位宽

int

8

[4~16]整数

表 2 FixedFakeQuantize 权重量化算法参数

参数名

参数说明

类型

默认值

参数范围

是否必填

scale

量化系数

List:[float, float] 或 float

num_bits

量化位宽

int

8

[4, 8]整数

示例:

{
    "maxpool":{
        "quant_enable":true,
        "activation_quant_params":[
            {
                "quantizer":"FixedFakeQuantize",
                "quantizer_args":{
                    "scale":0.0118393,
                    "zero_point":-128.0,
                    "num_bits":8,
                }
            }
        ]
    },
    "conv1":{
        "quant_enable":true,
        "activation_quant_params":[
            {
                "quantizer": "FixedFakeQuantize",
                "quantizer_args": {
                    "scale": 0.0061145,
                    "zero_point": -128.0,
                    "num_bits": 8,
                }
            }
        ],
        "weight_quant_params":{
                "quantizer":"FixedFakeQuantize",
                "quantizer_args":{
                    "scale":[0.010429775, 0.009767545],
                    "num_bits":8,
                }
            }
    },
}

自定义量化算法

本版本对用户自定义量化算法完全开放,算法满足如下条件即可使用。

  1. 线性量化算法, 量化到torch.qint (quint不支持)

  2. 带有scale和zero_point属性

  3. 带有quant_min和quant_max属性(可选,不带时按照(-128, 127)处理)

自定义算法以包名发现,即时注册的方式引入,将算法类的完整包名配置到quantizer字段,将类需要的初始化属性配置到quantizer_args字段(属性中有复杂类型也用包名),就能实时注册到AMCT中使用。

以torch.quantization.FakeQuantize为例,修改量化配置文件config.json或者简易量化配置文件(参考sample中的custom_quantize.yml)即可实现:

"conv1": {
    "quant_enable": true,
    "activation_quant_params": [
        {
            "quantizer": "torch.quantization.FakeQuantize",
            "quantizer_args": {
                "observer": "torch.quantization.PerChannelMinMaxObserver",
                "quant_min": -128,
                "quant_max": 127,
                "dtype": "torch.qint8",
                "qscheme": "torch.per_channel_symmetric",
                "reduce_range": false
                "ch_axis": 0
            }
        },
        {
            "quantizer": "torch.quantization.FakeQuantize",
            "quantizer_args": {
                "observer": "torch.quantization.MovingAverageMinMaxObserver",
                "quant_min": -128,
                "quant_max": 127,
                "dtype": "torch.qint8",
                "qscheme": "torch.per_tensor_affine",
                "reduce_range": false
            }
        }
    ]
}

须知: 此特性具有过高的开放和灵活性,AMCT无法知道自定义算法内部的功能正确与否,推荐使用基于torch.quantization.FakeQuantizeBase拓展的算法子类。

工具实现的融合功能

当前该工具主要实现的融合功能包括:

  • Conv2d+Batchnorm2d/SyncBatchnorm2d融合:在模型保存阶段进行融合成Conv2d算子。

  • 量化层之间的融合比如:Dequant反量化和Quant量化算子会融合成ReQuant重量化算子。

Transformer加速

简介

为了加速Transformer的模型,AMCT内置了亲和当前硬件平台的算子和Backbone实现,还提供了VIT量化部署示例。

amct.nn.modules

表 1 amct.nn提供亲和硬件的算子

算子

对应开源算子

区别

amct.nn.GELU

torch.nn.GELU

torch.nn.GELU的近似实现

amct.nn.LayerNorm

torch.nn.LayerNorm

沿着给定的axis运算。对单轴进行归一化,一般是C轴,而不是沿着后面几个维度运算。

amct.nn.MultiheadConvAttention

torch.nn.MultiheadAttention

将attention block用1x1卷积核的conv算子进行重新包装。

amct.nn.SpaceToDepth

onnx 算子中的SpaceToDepth

不同于torch.nn.PixelUnshuffle , 和amct.nn.DepthToSpace成对使用。

amct.nn.DepthToSpace

onnx 算子中的DepthToSpace的DCR模式

不同于torch.nn.PixelShuffle, PixelShuffle, 对应的是DepthToSpace的CRD模式。

amct.nn.Mlp

Swin Transformer V1中的Mlp

使用1x1卷积核的conv算子替换Linear算子

表 2 amct.nn使用到的自定义算子

算子

描述

torch.ops.pico_ops.LayerNorm

层归一化,对网络中每一层的输出进行归一化,使其具有相似的分布。

torch.ops.pico_ops.Gelu

高斯误差线性单元激活函数。

torch.ops.pico_ops.DepthToSpaceDCR

对应onnx 算子中DepthToSpace的DCR 模式。

torch.ops.pico_ops.SpaceToDepth

对应onnx 算子SpaceToDepth。

须知: torch.ops.pico_ops提供的算子必须在import hotwheels.amct_pytorch as amct 后才能被识别。

接口说明

create_quant_config_fx

功能说明:生成量化配置文件接口,根据图的结构找到所有可量化的层,自动生成量化配置文件,并将可量化层的量化配置信息写入文件。

函数原型:

create_quant_config_fx(config_file: str, model: torch.nn.Module, config_definition: str)

参数说明:

参数名

输入/返回值

含义

使用限制

config_file

输入

待生成的量化配置文件存放路径及名称。

如果存放路径下已经存在该文件,则调用该接口时会覆盖已有文件。

数据类型:string

model

输入

待量化的模型,已加载权重。

数据类型:torch.nn.module

config_definition

输入

简易量化配置文件路径,限定为yaml格式文件(.yml或.yaml)

数据类型:string

返回值说明:无。

函数输出:输出一个calibration_config.json格式的量化配置文件(重新执行量化时,该接口输出的量化配置文件将会被覆盖)。

create_quant_model_fx

功能说明:接收create_quant_config_fx生成的量化配置文件,对模型进行量化,并生成原始模型和量化后模型的图结构,返回修改后的torch.fx.GraphModule静态量化模型。

函数原型:

create_quant_model_fx(config_file: str, model: torch.nn.Module)

参数说明:

参数名

输入/返回值

含义

使用限制

config_file

输入

用户生成的量化配置文件,用于指定模型network中量化层的配置情况。

数据类型:string

model

输入

待量化的模型,已加载权重。

数据类型:torch.nn.module

量化后的GraphModule

返回值

修改后的torch.fx.GraphModule量化模型。

数据类型:torch.nn.module

返回值说明:返回修改后的torch.fx.GraphModule量化模型

save_quant_model_fx

功能说明:保存量化模型,将用户量化的模型保存为可以在SoC做推理的部署模型,并生成模型量化过程中的参数quant_param_record.txt。

函数原型:

save_quant_model_fx(model: torch.fx.GraphModule, save_path: str, input_data, onnx_export_setting = None)

参数说明:

参数名

输入/返回值

含义

使用限制

model

输入

量化训练后的模型

数据类型:torch.fx.GraphModule

save_path

输入

模型保存的路径和模型的名字前缀的拼接字符串

数据类型:string

input_data

输入

模型的输入

数据类型:不固定,参考实际模型。

onnx_export_setting

输入

详情请参考torch.onnx.export中的参数

默认值:None

数据类型:字典

返回值说明:无。

函数输出:

  • 部署模型文件:ONNX格式的模型文件,模型名中包含deploy,经过ATC转换工具转换后可部署到在SoC。

  • 浮点模型文件:ONNX格式的原始torch模型文件。

  • 量化参数文件:记录每一层算子的量化参数的txt文件。

重新执行量化时,该接口输出的上述文件将会被覆盖。

enable_quantization

功能说明:量化模型状态开关,支持四种状态calibration、fake_quant、real_quant和原始float模型推理。

函数原型:enable_quantization(model: torch.fx.GraphModule, calibration=False, fake_quant=False, real_quant=False)

参数说明:

  • 浮点模式: 三个参数都为False,可用于训练/推理。

  • 校准模式:

    • 模式一:calibration为True, 其他为False,推荐用于量化校准。

    • 模式二:calibration和fake_quant为True,其他为False,可用于量化重训前的预校准。

  • 伪量化模式:fake_quant为True, 其他为False,可用于训练/推理。

  • 真量化模式: real_quant为True, 其他为False,可用于训练/推理。

参数名

输入/返回值

含义

使用限制

model

输入

量化后的模型

数据类型:torch.fx.GraphModule

calibration

输入

校准

默认值:False

数据类型:bool

fake_quant

输入

伪量化

默认值False

数据类型:bool

real_quant

输入

真量化

默认值False

数据类型:bool

返回值说明:无

函数输出:无

enable_dump

功能说明:调用此函数,该函数会把下一次量化模型推理过程每一层的输出保存为一个文件,有效期仅一次forword。

函数原型:

enable_dump(model: torch.fx.GraphModule, dump_dir=None, dump_format=None)

参数说明:

参数名

输入/返回值

含义

使用限制

model

输入

量化训练后的模型

数据类型:torch.fx.GraphModule

dump_dir

输入

数据保存的目录

默认值:None

数据类型:string

dump_format

输入

保存数据的文件格式

默认值:None

数据类型:string 支持.npy .float .txt 文件类型

返回值说明:无。

函数输出:模型每一层输出的值保存为一个文件。

restore_quant_model_fx

功能说明:使用量化模型的config.json和量化模型保存的权重文件pt,将用户未量化的模型重新加载为量化模型

函数原型:

restore_quant_model_fx(config_file: str, model: torch.nn.Module, pth_file: str, state_dict_name: str = None)

参数名

输入/返回值

含义

使用限制

config_file

输入

量化训练后保存的模型量化配置文件

数据类型:string

model

输入

原始未量化pytorch模型

数据类型:torch.nn.Module

pth_file

输入

量化模型的权重文件路径

数据类型:string

state_dict_name

输入

权重文件中的权重对应的键值

默认值:None

数据类型:string

返回值说明:量化模型。

静态图量化限制

不支持torch的自动混精模式

AMCT基于模型中float32类型的数据进行量化,如果使用torch的自动混精模式(amp),模型的中的数据类型转变为float16,会导致精度损失。

不支持动态控制流

由于torch的静态图机制不支持动态控制流,因此AMCT工具也不支持动态控制流,关于动态控制流的约束和规避方法请参考fx.symbolic_trace异常场景汇总

动态图版本说明

amct动态图版本的功能和API在静态图版本中仍能够正常使用。动态图版本的模型压缩量化工具在该场景下需借助通过pytorch导出onnx的方式构建图结构,并添加拓展标记层记录层名,以此建立pytorch和onnx层名的映射。量化工具通过该方式,实现模型的量化配置的生成、量化层的插入和量化参数的保存。但是,由于pytorch与onnx的算子映射不能一一对应,例如pytorch中的GlobalAvgPooling算子,在onnx中会被拆分为Pad+Pool的算子组合,因此,动态图版本中的量化策略需要对上述转换的场景进行特殊适配。此外,动态图版本中用户的量化model如果存在function类型算子,如torch.nn.functional.sigmoid,需要手动替换成module实现,以保证量化层名的标记。上述特点均易导致工具代码的灵活性和扩展性差,用户使用不方便,不易维护等问题。

接口说明

训练后量化

create_quant_config

create_quant_config

功能说明:训练后量化接口,根据图的结构找到所有可量化的层,自动生成量化配置文件,并将可量化层的量化配置信息写入文件。

函数原型:

create_quant_config(config_file, model, input_data, skip_layers=None, batch_num=1, activation_offset=True, config_defination=None)

参数说明:

参数名

输入/返回值

含义

使用限制

config_file

输入

待生成的量化配置文件存放路径及名称。

如果存放路径下已经存在该文件,则调用该接口时会覆盖已有文件。

数据类型:string

model

输入

待量化的模型,已加载权重。

数据类型:torch.nn.module

input_data

输入

模型的输入数据。一个torch.tensor会被等价为tuple(torch.tensor)。

数据类型:tuple

skip_layers

输入

可量化但不需要量化的层的层名。

默认值:None

数据类型:list,列表中元素类型为string

使用约束:如果使用简易配置文件作为入参,则该参数需要在简易配置文件中设置,此时输入参数中该参数配置不生效。

batch_num

输入

量化使用的batch数量,即使用多少个batch的数据生成量化因子。

数据类型:int

取值范围:大于0的整数

默认值:1

使用约束:

  • batch_num不宜过大,batch_num与batch_size的乘积为量化过程中使用的图片数量,过多的图片会占用较大的内存。
  • 如果使用简易配置文件作为入参,则该参数需要在简易配置文件中设置,此时输入参数中该参数配置不生效。

activation_offset

输入

数据量化是否带offset。

默认值:true

数据类型:bool

使用约束:如果使用简易配置文件作为入参,则该参数需要在简易配置文件中设置,此时输入参数中该参数配置不生效。

config_defination

输入

基于calibration_config_pytorch.proto文件生成的简易量化配置文件quant.cfg,calibration_config_pytorch.proto文件所在路径为:AMCT安装目录/amct_pytorch/proto/calibration_config_pytorch.proto。

calibration_config_pytorch.proto文件参数解释以及生成的quant.cfg简易量化配置文件样例请参见训练后量化简易配置文件说明。

默认值:None

数据类型:string

使用约束:当取值为None时,使用输入参数生成配置文件;否则,忽略输入的其他量化参数(skip_layers,batch_num,activation_offset),根据简易量化配置文件参数config_defination生成json格式的配置文件。

返回值说明:无。

函数输出:输出一个json格式的量化配置文件(重新执行量化时,该接口输出的量化配置文件将会被覆盖)。

{ 
    "version":1, 
    "batch_num":2, 
    "activation_offset":true, 
    "do_fusion":true, 
    "skip_fusion_layers":[], 
    "conv1":{ 
        "quant_enable":true, 
        "activation_quant_params":[
{ 
                "max_percentile":0.999999, 
                "min_percentile":0.999999, 
                "search_range":[ 
                    0.7, 
                    1.3 
                ], 
                "search_step":0.01 
            }
], 
        "weight_quant_params":{ 
            "channel_wise":true 
        } 
    }, 
    "layer1.0.conv1":{ 
        "quant_enable":true, 
        "activation_quant_params":[
{ 
                "max_percentile":0.999999, 
                "min_percentile":0.999999, 
                "search_range":[ 
                    0.7, 
                    1.3 
                ], 
                "search_step":0.01 
            }
        ], 
         "weight_quant_params":{ 
             "channel_wise":false 
         } 
     } 
 }

调用示例:

import hotwheels.amct_pytorch as amct 
# 建立待量化的网络图结构 
model = build_model() 
model.load_state_dict(torch.load(state_dict_path)) 
input_data = tuple([torch.randn(input_shape)]) 
model.eval() 
 
# 生成量化配置文件 
amct.create_quant_config(config_file="./configs/config.json", 
                         model=model, 
                         input_data=input_data, 
                         skip_layers=None, 
                         batch_num=1, 
                         activation_offset=True)

quantize_model

功能说明:训练后量化接口,将输入的待量化的图结构按照给定的量化配置文件进行量化处理,在传入的图结构中插入量化相关的算子,生成量化因子记录文件record_file,返回修改后的torch.nn.module校准模型。

函数原型:

calibration_torch_model = quantize_model(config_file, modfied_onnx_file, record_file, model, input_data, input_names=None, output_names=None, dynamic_axes=None)

参数说明:

参数名

输入/返回值

含义

使用限制

config_file

输入

用户生成的量化配置文件,用于指定模型network中量化层的配置情况。

数据类型:string

modfied_onnx_file

输入

文件名,用于存储融合后模型的onnx格式。

数据类型:string

record_file

输入

量化因子记录文件路径及名称。

数据类型:string

model

输入

待量化的模型,已加载权重。

数据类型:torch.nn.module

input_data

输入

模型的输入数据。一个torch.tensor会被等价为tuple(torch.tensor)。

数据类型:tuple

input_names

输入

模型的输入的名称,用于modfied_onnx_file中显示。

默认值:None

数据类型:list(string)

output_names

输入

模型的输出的名称,用于modfied_onnx_file中显示。

默认值:None

数据类型:list(string)

dynamic_axes

输入

对模型输入输出动态轴的指定,例如对于输入inputs(NCHW),N、H、W为不确定大小,输出outputs(NL),N为不确定大小,则{"inputs": [0,2,3], "outputs": [0]}

默认值:None

数据类型:dict<string, dict<python:int, string>> or dict<string, list(int)>

calibration_torch_model

返回值

修改后的torch.nn.module校准模型。

默认值:None

数据类型:torch.nn.module

返回值说明:

返回修改后的torch.nn.module校准模型和原始图结构。

调用示例:

import hotwheels.amct_pytorch as amct 
# 建立待量化的网络图结构 
model = build_model() 
model.load_state_dict(torch.load(state_dict_path)) 
input_data = tuple([torch.randn(input_shape)]) 
 
scale_offset_record_file = os.path.join(TMP, 'scale_offset_record.txt') 
modified_model = os.path.join(TMP, 'modified_model.onnx') 
# 插入量化API 
calibration_torch_model = amct.quantize_model(config_json_file, 
                                        modified_model, 
                                        scale_offset_record_file, 
                                        model, 
                                        input_data, 
                                        input_names=['input'], 
                                        output_names=['output'], 
                                        dynamic_axes={'input':{0: 'batch_size'}, 
                                                      'output':{0: 'batch_size'}})

save_model

功能说明:训练后量化接口,根据量化因子记录文件record_file将用户待量化的模型保存为可以在ONNX Runtime环境进行量化精度仿真的精度仿真模型,和可以在SoC做推理的部署模型。

约束说明:

  • 在网络推理的batch数目达到batch_num后,再调用该接口,否则量化因子不正确,量化结果不正确。

  • 该接口只接收quantize_model接口产生的onnx类型模型文件。

  • 该接口需要输入量化因子记录文件,量化因子记录文件在quantize_model阶段生成,在模型推理阶段填充有效值。

函数原型:

save_model(modfied_onnx_file, record_file, save_path, calibration_torch_model)

参数说明:

参数名

输入/返回值

含义

使用限制

modfied_onnx_file

输入

文件名,存储融合后模型的onnx格式。

数据类型:string

record_file

输入

文件名,存储量化因子

数据类型:string

save_path

输入

模型存放路径。

该路径需要包含模型名前缀,例如./quantized_model/*model

数据类型:string

calibration_torch_model

输入

有quantize_model接口返回的经过量化的模型文件

数据类型:torch.nn.Module

返回值说明:无。

函数输出:

  • 精度仿真模型文件:ONNX格式的模型文件,模型名中包含fake_quant,可以在ONNX Runtime环境进行精度仿真。

  • 部署模型文件:ONNX格式的模型文件,模型名中包含deploy,经过ATC转换工具转换后可部署到在SoC。

重新执行量化时,该接口输出的上述文件将会被覆盖。

调用示例:

import hotwheels.amct_pytorch as amct 
# 进行网络推理,期间完成量化 
for i in batch_num: 
    output = calibration_model(input_batch) 
# 插入API,将量化的模型存为onnx文件 
amct.save_model(modfied_onnx_file="./tmp/modified_model.onnx", 
record_file=”./tmp/scale_offset_record.txt”,
                save_path="./results/model",
                calibration_torch_model)

update_bn_status

功能说明:该接口只在4bit calibration算法时调用,用于更新BN层的权重为可学习状态或不可学习状态。

函数原型:

update_bn_status(calibration_model, training=True)

参数说明:

参数名

输入/返回值

含义

使用限制

calibration_model

输入

待修改BN状态的模型文件

数据类型:torch.nn.module

training

输入

True: 更新BN的权重为可学习状态

False: 更新BN的权重为不可学习状态

数据类型:bool

返回值说明:无。

调用示例:

import hotwheels.amct_pytorch as amct 
# 建立待量化的网络图结构 

modified_model = os.path.join(TMP, 'modified_model.onnx') 
# 插入API,修改BN为可学习状态 
amct.update_bn_status(calibration_model, False)

量化感知训练

create_quant_retrain_config

功能说明:量化感知训练接口,根据图的结构找到所有可量化的层,自动生成量化配置文件,并将可量化层的量化配置信息写入配置文件。

函数原型:

create_quant_retrain_config(config_file, model, input_data, config_defination=None)

参数说明:

参数名

输入/返回值

含义

使用限制

config_file

输入

待生成的量化感知训练配置文件存放路径及名称。

如果存放路径下已经存在该文件,则调用该接口时会覆盖已有文件。

数据类型:string

model

输入

待进行量化感知训练的模型,已加载权重。

数据类型:torch.nn.module

input_data

输入

模型的输入数据。一个torch.tensor会被等价为tuple(torch.tensor)。

数据类型:tuple

config_defination

输入

简易配置文件。

基于retrain_config_pytorch.proto文件生成的简易配置文件quant.cfg,

retrain_config_pytorch.proto文件所在路径为:AMCT安装目录/amct_pytorch/proto/retrain_config_pytorch.proto。

retrain_config_pytorch.proto文件参数解释以及生成的quant.cfg简易量化配置文件样例请参见量化感知训练简易配置文件说明。

默认值:None

数据类型:string

使用约束:当取值为None时,使用输入参数生成配置文件;否则,根据量化感知训练简易配置文件参数config_defination生成json格式的配置文件。

返回值说明:无。

函数输出:输出一个json格式的量化感知训练配置文件(重新执行量化感知训练时,该接口输出的配置文件将会被覆盖),样例如下。

{ 
    "version":1, 
    "batch_num":1, 
    "conv1":{
    "retrain_enable":true,
          "retrain_data_config":[
             {
                "algo":"luq_quantize",
                "num_bits":8
             }
    ],
        "retrain_weight_config":{
            "algo":"arq_retrain",
            "num_bits":8,
            "channel_wise":true
        }
},
"layer1.0.conv1":{
        "retrain_enable":true,
        "retrain_data_config":[
            {
                "algo":"luq_quantize",
                "num_bits":8
            }
    ],
        "retrain_weight_config":{
            "algo":"lnq_retrain",
            "num_bits":4,
            "clip_max":3.0,
            "cluster_freq":1200,
            "max_iteration":1000,
            "min_distance":0.0001,
"channel_wise":false
    }
},
"layer1.0.eltwise_add":{
        "retrain_enable":true,
        "retrain_data_config":[
            {
                "algo":"luq_quantize",
                "num_bits":8
            },
            {
                "algo":"luq_quantize",
                "num_bits":8
            }
    ]
},
    "fc":{
        "retrain_enable":true,
        "retrain_data_config":[
            {
                 "algo":"luq_quantize",
                 "num_bits":8
            }
    ],
        "retrain_weight_config":{
            "algo":"arq_retrain",
            "num_bits":8,
            "channel_wise":false
        }
    }
}

调用示例:

import hotwheels.amct_pytorch as amct 
# 建立待量化的网络图结构 
model = build_model() 
model.load_state_dict(torch.load(state_dict_path)) 
input_data = tuple([torch.randn(input_shape)]) 
  
# 生成量化配置文件 
amct.create_quant_retrain_config(config_file="./configs/config.json", 
                            model=model, 
                            input_data=input_data)

create_quant_retrain_model

功能说明:量化感知训练接口,将输入的待量化的图结构按照给定的量化配置文件进行量化处理,在传入的图结构中插入量化相关的算子(数据和权重的量化感知训练层以及找N的层),生成量化因子记录文件record_file,返回修改后可用于量化感知训练的torch.nn.module模型。

函数原型:

quant_retrain_model = create_quant_retrain_model (config_file, model, record_file, input_data)

参数说明:

参数名

输入/返回值

含义

使用限制

config_file

输入

用户生成的量化感知训练配置文件,用于指定模型network中量化层的配置。

数据类型:string

model

输入

待进行量化感知训练的模型,已加载权重。

数据类型:torch.nn.module

record_file

输入

量化因子记录文件路径及名称。

数据类型:string

input_data

输入

模型的输入数据。一个torch.tensor会被等价为tuple(torch.tensor)。

数据类型:tuple

quant_retrain_model

返回值

修改后可用于量化感知训练的torch.nn.module模型。

默认值:None

数据类型:torch.nn.module

返回值说明:量化感知训练的模型。

函数输出:无。

调用示例:

import hotwheels.amct_pytorch as amct 
# 建立待进行量化感知训练的网络图结构 
model = build_model() 
model.load_state_dict(torch.load(state_dict_path)) 
input_data = tuple([torch.randn(input_shape)]) 
  
scale_offset_record_file = os.path.join(TMP, 'scale_offset_record.txt') 
# 插入量化API 
quant_retrain_model = amct.create_quant_retrain_model( 
               config_json_file, 
               model, 
               scale_offset_record_file, 
               input_data)

restore_quant_retrain_model

功能说明:量化感知训练接口,将输入的待量化的图结构按照给定的量化感知训练配置文件进行量化处理,在传入的图结构中插入量化感知训练相关的算子(数据和权重的量化感知训练层以及找N的层),生成量化因子记录文件record_file,加载训练过程中保存的checkpoint权重参数,返回修改后的torch.nn.module量化感知训练模型。

函数原型:

quant_retrain_model = restore_quant_retrain_model (config_file, model, record_file, input_data, pth_file, state_dict_name=None)

参数说明:

参数名

输入/返回值

含义

使用限制

config_file

输入

用户生成的量化感知训练配置文件,用于指定模型network中量化层的配置情况。

数据类型:string

使用约束:该接口输入的config.json必须和create_quant_retrain_model接口输入的config.json一致。

model

输入

待进行量化感知训练的原始模型,未加载权重。

数据类型:torch.nn.module

record_file

输入

量化因子记录文件路径及名称。

数据类型:string

input_data

输入

模型的输入数据。一个torch.tensor会被等价为tuple(torch.tensor)。

数据类型:tuple

pth_file

输入

训练过程中保存的权重文件。

数据类型:string

state_dict_name

输入

权重文件中的权重对应的键值。

默认值:None

数据类型:string

quant_retrain_model

返回值

修改后的torch.nn.module量化感知训练模型。

默认值:None

数据类型:torch.nn.module

返回值说明:量化感知训练的模型。

函数输出:无。

调用示例:

import hotwheels.amct_pytorch as amct 
# 建立待量化的网络图结构 
model = build_model() 
input_data = tuple([torch.randn(input_shape)]) 
  
scale_offset_record_file = os.path.join(TMP, 'scale_offset_record.txt') 
# 插入量化API 
quant_retrain_model = amct.restore_quant_retrain_model( 
               config_json_file, 
               model, 
               scale_offset_record_file, 
               input_data, 
               pth_file)

save_quant_retrain_model

功能说明:量化感知训练接口,根据用户最终的重训练好的模型,生成最终量化精度仿真模型以及量化部署模型。

函数原型:

save_quant_retrain_model (config_file, model, record_file, save_path, input_data, input_names=None, output_names=None, dynamic_axes=None)

参数说明:

参数名

输入/返回值

含义

使用限制

config_file

输入

用户生成的量化感知训练配置文件,用于指定模型network中量化层的配置情况。

数据类型:string

model

输入

已进行量化感知训练后的量化模型。

数据类型:torch.nn.module

record_file

输入

量化因子记录文件路径及名称。

数据类型:string

save_path

输入

量化模型存放路径。

该路径需要包含模型名前缀,例如./quantized_model/*model。

数据类型:string

input_data

输入

模型的输入数据。一个torch.tensor会被等价为tuple(torch.tensor)。

数据类型:tuple

input_names

输入

模型的输入的名称,用于保存的量化onnx模型中显示。

默认值:None

数据类型:list(string)

output_names

输入

模型的输出的名称,用于保存的量化onnx模型中显示。

默认值:None

数据类型:list(string)

dynamic_axes

输入

对模型输入输出动态轴的指定,例如对于输入inputs(NCHW),N、H、W为不确定大小,输出outputs(NL),N为不确定大小,则 { "inputs": [0,2,3], "outputs": [0]}

默认值:None

数据类型:dict<string, dict<python:int, string>> or dict<string, list(int)>

返回值说明:无。

函数输出:

  • 精度仿真模型文件:模型名中包含fake_quant,该模型可以在ONNX Runtime环境进行精度仿真。

  • 部署模型文件:模型名中包含deploy,经过ATC转换工具转换后可部署到在SoC。

重新执行量化感知训练时,该接口输出的上述文件将会被覆盖。

调用示例:

import hotwheels.amct_pytorch as amct 
# 建立待量化的网络图结构 
model = build_model() 
model.load_state_dict(torch.load(state_dict_path)) 
input_data = tuple([torch.randn(input_shape)]) 
# 训练量化retrain模型,训练量化因子 
train_model(quant_retrain_model, input_batch) 
# 推理量化retrain模型,导出量化因子 
infer_model(quant_retrain_model, input_batch) 
  
# 插入量化API,将量化感知训练的模型存为onnx文件 
amct.save_quant_retrain_model( 
               config_json_file, 
               model,  
               scale_offset_record_file, 
               input_data, 
               input_names=['input'], 
               output_names=['output'], 
               dynamic_axes={'input':{0: 'batch_size'}, 
                             'output':{0: 'batch_size'}})

注意:

  1. 多GPU训练时,由于保存模型的操作涉及到写文件等操作,请务必在主进程调用save_quant_retrain_model接口,防止出现写文件冲突。 调用示例:

def is_master_proc(num_gpus=8):
    """
    Determines if the current process is the master process.
    """
    if torch.distributed.is_available() and torch.distributed.is_initialized():
        return dist.get_rank() % num_gpus == 0
    else:
        return True
if is_master_proc():
    amct.save_quant_retrain_model(...)
  1. optimizer的构建需使用amct添加量化模块之后的model,否则量化模块中的可学习参数不会更新,例如:

# Define optimizer.
    optimizer = torch.optim.SGD(model.parameters(), args.learning_rate,
                                momentum=0.9,
                                weight_decay=1e-4)
# optimizer 需在添加量化模块后构建

动态图版本FAQ

AMCT Pytorch量化约束说明

  • 数据类型约束

    NN层数据支持INT8、INT16,权重支持INT8、INT4;

    非NN层数据支持INT8、INT16、FP16

  • 网络结构约束

    AMCT针对不同结构的模型量化过程中,会结合模型和生成的量化配置文件对量化进行前置校验,以判断用户模型的当前量化状态是否满足后续量化流程。

    对于校验的详情,包括不限于以下若干场景:

    1. 场景1:量化层数量校验

      对于生成的量化配置文件,如果其中不包含任何需要量化的层,校验会抛出异常:” No quant enable layer in quant config file, please check the quant config file.”。例如,在config.json中,包含的层 quant_enable均为false

      {
          "version":1,
          "batch_num":2,
          "activation_offset":true,
          "do_fusion":true,
          "skip_fusion_layers":[],
          "update_bn":false,
          "add_1":{
              "quant_enable":false,
              "activation_quant_params":[
                  {
                      "num_bits":8,
                      "max_percentile":0.999999,
                      "min_percentile":0.999999,
                      "search_range":[
                          0.7,
                          1.3
                      ],
                      "search_step":0.01
                  },
                  {
                      "num_bits":8,
                      "max_percentile":0.999999,
                      "min_percentile":0.999999,
                      "search_range":[
                          0.7,
                          1.3
                      ],
                      "search_step":0.01
                  }
              ]
          }
      }
      
    2. 场景2:一出多算子节点输出量化一致性校验

      对于一出多算子,AMCT要求其多路输出的量化参数必须一致,否则校验拦截,提示形如:”Outputs {}(onnx name: {}) and {}(onnx name: {}) of layer {}(onnx name: {}) should have consistent”。例如,模型onnx拓扑如下。

      其中,OP1存在两路输出,分别对应OP2和OP3的其中一路输入。在这种情况下,OP2和OP3的对应路的输入量化参数需要保持一致。

      如下量化配置config.json文件内容,红框中的两个参数需相等。

    3. 场景3:无指令层前后量化参数一致性校验

      针对无指令层(没有计算,只有shape相关操作的数据传输层)算子('Concat', 'Split', 'Reshape', 'Flatten', 'Slice', 'Transpose', 'Tile', 'Squeeze'等),其输入的量化参数应与下一层节点的输入量化参数保持一致,否则校验报错:” Layer {} should have consistent {} with no command layer {}”。例如,如下图所示模型结构。

      其中,OP为无指令层关联的下一层算子节点。

      两个层的输入量化参数需保持一致,无指令层处于尾层的场景除外(该场景跳过校验)

    4. 场景4:CUBE算子量化校验

      如'Conv', 'Gemm', 'ConvTranspose', 'MaxPool', 'AveragePool', 'GlobalMaxPool', 'GlobalAveragePool' 的于CUBE单元上计算的算子必须量化,否则校验报错:” Layer {} must be quantized as it is an operation calculated on CUBE”。

      对于生成的config.json,其量化参数内容会被解析以确定待量化的层(quant_layers),quant_layers会统计config.json中存在参数且quant_enable字段为True的层。如果模型中CUBE类型算子不在 quant_layers 中,会触发校验异常。

如何适配拓展层

当前小型化工具仅支持pytorch官方的算子的量化,也就是支持量化的算子列表中罗列的算子,如果用户基于这些算子做了定制化适配,小型化工具就无法识别,如图1所示。

图 1 自定义Conv层

这样的模型,小型化无法识别到模型中带有Conv层,需要将卷积和padding实现放到模型的forward方法中,如图2所示。

图 2 修改后的模型定义

retrain场景,重复使用的层需多次定义

retrain场景,对于不带权重重复使用的层,需要根据使用次数在模型init方法中定义相应次数。原始模型如图1

图 1 原始模型定义

将加法和cat运算全部替换为amct_pytorch拓展算子,且加法按使用次数定义,如图2所示。

图 2 替换加法和cat运算为amct_pytorch的拓展算子

如果网络中存在多轮循环调用相同层的场景,如图3所示。

图 3 在循环中调用‘+’

需要在__init__方法中按照使用次数定义Eltwise module,由于出现的两轮循环,其命名可以带上每轮循环对应的编号,如图4所示。

图 4 修改后的循环调用eltwise的module

chunk算子替换成split算子

当前1.10.2版本的pytorch,指定onnx opset version为13进行pytorch到onnx的算子转换时,torch.chunk算子会变成小算子组合,这使得量化配置及量化参数映射流程极其复杂。此时需要用户手动将chunk算子替换为split算子。torch.chunk算子的含义是将tensor拆分成n块,torch.split算子的含义是将tensor拆分成大小为n的多块。经过替换后的模型,先进行一轮数据forward,确保所有split算子的chunk属性被初始化,再进行模型修改和forward。

图 1 Shufflenet中原始InvertedResidual定义

图 2 chunk修改为split定义后的InvertedResidual定义

拓展单输入单输出module量化

  1. 对于单输入单输出且有module表示的算子,不在支持量化的算子列表中,可以手动进行拓展,打开site-packages/hotwheels/amct_pytorch/capacity/capacity_config.csv,在NO_WEIGHT_QUANT_TYPES增加要拓展的pytorch层类型,NO_WEIGHT_QUANT_ONNX_TYPES中增加该层转换到onnx对应的层类型。例如,客户想要增加torch.nn.XLayer的量化,可以将‘Xlayer’添加到NO_WEIGHT_QUANT_TYPES列表末尾,并用逗号分隔,再将其在opset_version为13下转为onnx对应的算子类型ONNX_XLayer添加到NO_WEIGHT_QUANT_ONNX_TYPES列表末尾,并用逗号分隔。

  2. 对于单输入单输出且无module表示的算子,可参考site-packages/hotwheels/amct_pytorch/custom_op/max/max.py的拓展方式,基于CustomModuleBase拓展对应算子,并将其按照第1条的描述加入到支持量化的算子列表中。

动态图附录

训练后量化简易配置文件说明

calibration_config_pytorch.proto文件参数说明如表1所示。

表 1 calibration_config_pytorch.proto参数说明

消息

是否必填

类型

字段

说明

AMCTConfig

-

-

-

AMCT训练后量化的简易量化配置。

optional

uint32

batch_num

量化使用的batch数量。

optional

bool

activation_offset

数据量化是否带offset。

repeated

string

skip_layers

不需要量化层的层名。

repeated

string

skip_layer_types

不需要量化的ONNX层类型。

optional

CalibrationConfig

common_config

通用的量化配置,若某层未被override_layer_types或者override_layer_configs重写,则使用该配置。

repeated

OverrideLayerType

override_layer_types

重写某一类型层的量化配置。

repeated

OverrideLayer

override_layer_configs

重写某一层的量化配置。

optional

bool

do_fusion

是否开启BN融合功能,默认为true,表示开启该功能。

repeated

string

skip_fusion_layers

跳过BN融合的层,配置之后这些层不会进行BN融合。

optional

bool

update_bn

是否开启BN更新功能。

optional

BnUpdateConfig

bn_update_config

BN层进行更新的参数配置。

optional

CalibrationOmConig

om_config

debug场景,用Mindstudio工具基于已生成的模型进行模型转换时会生成一个以.om.json为后缀的文件,其中会包含各层输入的量化位宽信息,可以用这个文件进行配置重新生成config.json进行校准,当前仅支持eltwise_add层的处理。

CalibrationOmConig

-

-

-

om_json量化配置。

required

string

mapping_file

om_json所在路径。

OverrideLayerType

-

-

-

override layer量化配置。

required

string

layer_type

支持量化的ONNX层类型的名字。

required

CalibrationConfig

calibration_config

重置的量化配置。

OverrideLayer

-

-

-

重置某层量化配置。

required

string

layer_name

被重置层的层名。

required

CalibrationConfig

calibration_config

重置的量化配置。

BnUpdateConfig

-

-

-

控制BN更新的参数。

optinal

uint32

bn_update_iterations

控制BN更新的迭代次数。

optinal

double

bn_momentum

控制BN更新的滑动学习率。

CalibrationConfig

-

-

-

Calibration量化的配置。

-

ARQuantize

arq_quantize

权重量化算法配置。

arq_quantize:ARQ量化算法配置。

-

SNQuantize

snq_quantize

权重量化算法配置。

snq_quantize:SNQ量化算法配置。

-

FMRQuantize

ifmr_quantize

数据量化算法配置。

ifmr_quantize:IFMR量化算法配置。

ARQuantize

-

-

-

ARQ量化算法配置。

optional

bool

channel_wise

是否对每个channel采用不同的量化因子。

SNQuantize

-

-

-

SNQ量化算法配置。

optional

bool

channel_wise

是否对每个channel采用不同的量化因子。

optional

uint32

max_iteration

最大迭代次数。

optional

float

min_distance

迭代更新聚类中心的最小距离。

optinal

string

init_algo

聚类中心初始化方式。

optional

int32

num_bits

量化位宽。

FMRQuantize

-

-

-

FMR量化算法配置。

optional

float

search_range_start

量化因子搜索范围左边界。

optional

float

search_range_end

量化因子搜索范围右边界。

optional

float

search_step

量化因子搜索步长。

optional

float

max_percentile

最大值搜索位置。

optional

float

min_percentile

最小值搜索位置。

基于该文件构造的均匀量化简易配置文件quant.cfg样例如下所示。

# global quantize parameter 
batch_num : 2 
activation_offset : true 
do_fusion: true 
skip_fusion_layers : "layer1.1.conv2" 
update_bn: true
bn_update_config: {
    bn_momentum : 0.1
    bn_update_iterations : 30
}
om_config : {
    mapping_file : "./retrain_conf/inst.om.json"
}
common_config : { 
     snq_quantize : { 
        channel_wise : true
        max_iteration : 1000
        min_distance : 1e-10
        init_algo: 'uniform'
        num_bits: 4
    }
    ifmr_quantize : { 
        search_range_start : 0.7 
        search_range_end : 1.3 
        search_step : 0.01 
        max_percentile : 0.999999 
        min_percentile : 0.999999 
        num_bits: 12
    } 
} 
  
override_layer_types : { 
    layer_type : "Gemm" 
    calibration_config : { 
        arq_quantize : { 
            channel_wise : false 
        } 
        ifmr_quantize : { 
            search_range_start : 0.8 
            search_range_end : 1.2 
            search_step : 0.02 
            max_percentile : 0.999999 
            min_percentile : 0.999999 
            num_bits: 12
        } 
    } 
} 
  
override_layer_configs : { 
    layer_name :"layer1.2.conv2" 
    calibration_config : { 
        arq_quantize : { 
            channel_wise : true 
            num_bits: 8
        } 
        ifmr_quantize : { 
            search_range_start : 0.8 
            search_range_end : 1.2 
            search_step : 0.02 
            max_percentile : 0.999999 
            min_percentile : 0.999999 
            num_bits: 12
         } 
     } 
 }

量化感知训练简易配置文件说明

retrain_config_pytorch.proto文件参数说明如表1所示。

表 1 retrain_config_pytorch.proto参数说明

消息

是否必填

类型

字段

说明

AMCTRetrainConfig

-

-

-

AMCT量化感知训练的简易配置。

optional

uint32

batch_num

量化使用的batch数量。

required

RetrainDataQuantConfig

retrain_data_quant_config

retrain data quant的参数。

required

RetrainWeightQuantConfig

retrain_weight_quant_config

retrain weights quant的参数。

repeated

string

skip_layers

不需要量化层的层名。

repeated

RetrainOverrideLayer

override_layer_configs

按层名重写哪些层。

repeated

string

skip_layer_types

不需要量化的层类型。

repeated

RetrainOverrideLayerType

override_layer_types

按层类型重写哪些层。

optional

RetrainOmConig

om_config

debug场景,用Mindstudio工具基于已生成的模型进行模型转换时会生成一个以.om.json为后缀的文件,其中会包含各层输入的量化位宽信息,可以用这个文件进行配置重新生成config.json进行校准,当前仅支持eltwise_add层的处理。

RetrainOmConig

-

-

-

om_json量化配置

required

string

mapping_file

om_json量化配置文件所在路径

RetrainDataQuantConfig

-

-

-

retrain data参数配置。

-

ULQuantize

ulq_retrain

数据重训ulq的算法。

-

LUQuantize

luq_retrain

数据重训luq算法,目前默认支持luq算法。

-

FixedQuantize

fixed_quant_param_retrain

固定数据量化配置

ULQuantize

-

-

-

ULQ算法参数。

optional

ClipMaxMin

clip_max_min

初始化的上下限值,如果不配置,默认用ifmr进行初始化。

optional

bool

fixed_min

是否下限不学习且固定为0。默认Relu后为true,其他为false。

optional

int32

num_bits

量化位宽

LUQuantize

-

-

-

LUQ算法参数。

optional

int32

num_bits

量化位宽

ClipMaxMin

-

-

-

初始上下限。

required

float

clip_max

初始上限值。

required

float

clip_min

初始下限值。

RetrainWeightQuantConfig

-

-

-

retrain weights参数配置。

-

ARQRetrain

arq_retrain

weights线性量化arq

-

LNQRetrain

lnq_retrain

weights非线性量化lnq

ARQRetrain

-

-

-

ARQ算法参数。

optional

bool

channel_wise

是否做channel wise的arq。

optional

int32

num_bits

权重量化位宽

LNQRetrain

-

-

-

LNQ算法参数。

optional

int32

cluster_freq

更新聚类中心的频率

optional

bool

channel_wise

默认为false,当前lnq_retrain算法只支持channel_wise为false,配置成true时不会生效

optional

int32

num_bits

权重量化位宽

optional

float

clip_max

学习的边界初始值

optional

uint32

max_iteration

寻找聚类的最大迭代次数

optional

float

min_distance

寻找聚类的最小距离

RetrainOverrideLayer

-

-

-

重写的层配置。

required

string

layer_name

层名。

required

RetrainDataQuantConfig

retrain_data_quant_config

重写的数据层量化参数。

required

RetrainWeightQuantConfig

retrain_weight_quant_config

重写的weights层量化参数。

RetrainOverrideLayerType

-

-

-

重写的层类型配置。

required

string

layer_type

层type。(转为ONNX的层类型)

required

RetrainDataQuantConfig

retrain_data_quant_config

重写的数据层量化参数。

required

RetrainWeightQuantConfig

retrain_weight_quant_config

重写的weights层量化参数。

基于该文件构造的量化感知训练简易配置文件quant.cfg样例如下所示。

数据ulq_retrain,权重arq_retrain

# global quantize parameter 
retrain_data_quant_config: { 
    luq_retrain: { 
        num_bits: 8 
    } 
} 
 
om_config : {
    mapping_file : "./retrain_conf/inst.om.json"
}
override_layer_types : { 
    layer_type: "Gemm" 
    retrain_weight_quant_config: { 
        lnq_retrain: {
            channel_wise: false
            num_bits:4
            clip_max:3.0
            cluster_freq:1200
        }
} 
 } 
  
 override_layer_configs : { 
    layer_name: "Conv" 
    retrain_data_quant_config: { 
        ulq_quantize: { 
            clip_max_min: { 
                clip_max: 3.0 
                clip_min: -3.0 
            } 
        } 
    } 
    retrain_weight_quant_config: { 
       arq_retrain: { 
          channel_wise: true 
         } 
       } 
 }

FAQ

fx.symbolic_trace异常场景汇总

torch.fx 符号跟踪 API 的限制

torch.fx 符号跟踪的局限性:https://pytorch.org/docs/stable/fx.html#limitations-of-symbolic-tracing

注意:

  1. 被wrap的自定义函数必须要接收一个在线的输入,这个在线输入不管有没有被用到,都要被传入自定义函数。

  2. 被wrap的自定义函数要模型前面前进行初始化,以便被torch.fx检测到。

  3. 如果@torch.fx.wrap没有生效,可以采用torch.fx.wrap('my_custom_function')的方式.

  4. 如果两种方式都不生效,请排查一下wrap的函数是否在模型初始化前被初始化,尽量写在模型定义代码上面。

torch.fx不支持动态控制流

  • 动态控制流程

    条件可能取决于某些输入值的循环或 if-else 语句。它只能跟踪一个执行路径,所有其他未跟踪的分支将被忽略。例如,跟踪简单函数时,将失败并显示 TraceError :“符号跟踪变量不能用作控制流的输入”:

    def func_to_trace(x):
        if x.sum() > 0:
            return torch.relu(x)
        else:
            return torch.neg(x)
     traced = torch.fx.symbolic_trace(func_to_trace)
    """  <...>  File "dyn.py", line 6, in func_to_trace    if x.sum() > 0:  File "pytorch/torch/fx/proxy.py", line 155, in __bool__    return self.tracer.to_bool(self)  File "pytorch/torch/fx/proxy.py", line 85, in to_bool    raise TraceError('symbolically traced variables cannot be used as inputs to control flow')torch.fx.proxy.TraceError: symbolically traced variables cannot be used as inputs to control flow"""
    

    if语句的条件依赖于x.sum()的值,而 又依赖于x函数输入的值。由于x可以更改(即,如果您将新的输入张量传递给跟踪函数),这就是_动态控制流_。回溯遍历您的代码以向您展示这种情况发生的位置。

  • 静态控制流程

    另一方面,支持所谓的_静态控制流。_静态控制流是循环或if语句,其值不能在调用之间更改。通常,在 pytorch 程序中,此控制流的出现是为了根据超参数对模型架构做出代码决策。作为一个具体的例子:

    import torch
    import torch.fx
    class MyModule(torch.nn.Module):
        def __init__(self, do_activation : bool = False):
            super().__init__()
            self.do_activation = do_activation
            self.linear = torch.nn.Linear(512, 512)
    
         def forward(self, x):
            x = self.linear(x)
            # This if-statement is so-called static control flow.
            # Its condition does not depend on any input values
            if self.do_activation:
                x = torch.relu(x)
            return x
    
    without_activation = MyModule(do_activation=False)
    with_activation = MyModule(do_activation=True)
    
    traced_without_activation = torch.fx.symbolic_trace(without_activation)
    print(traced_without_activation.code)
    """def forward(self, x):
        linear_1 = self.linear(x);  x = None    
    return linear_1"""
    
    traced_with_activation = torch.fx.symbolic_trace(with_activation)
    print(traced_with_activation.code)
    """import torch
    def forward(self, x):
        linear_1 = self.linear(x); x = None
        relu_1 = torch.relu(linear_1);  linear_1 = None
        return relu_1"""
    

    if self.do_activation不依赖于任何函数输入,因此它是静态的。do_activation可以被认为是一个超参数。

Tracer使用类自定义跟踪

该类Tracer是实现symbolic_trace. 跟踪的行为可以通过子类化 Tracer 来定制,

叶模块是在符号跟踪中显示为调用而不是被跟踪的模块。默认的叶模块集是标准torch.nn模块实例集。例如:

叶模块集可以通过重写来定制 Tracer.is_leaf_module()。

如下所示:

amct内部实现了自定义Tracer,把amct标准torch.nn模块实例集和自定义的nn作为叶子节点。

class CustomTracer(torch.fx.Tracer):
    def is_leaf_module(self, m: torch.nn.Module, module_qualified_name: str) -> bool:
        if m.__module__.startswith('torch.nn') and not isinstance(m, torch.nn.Sequential):
            return True
        elif m.__module__.startswith('hotwheels.amct_pytorch.nn'):
            return True
        else:
            return False

# Let's use this custom tracer to trace through this module
class MyModule(torch.nn.Module):
    def forward(self, x):
        return torch.relu(x) + torch.ones(3, 4)
mod = MyModule()
traced_graph = CustomTracer().trace(mod)
# trace() returns a Graph. Let's wrap it up in a
# GraphModule to make it runnable
traced = torch.fx.GraphModule(mod, traced_graph)

非torch函数不可追踪

FX__torch_function__用作拦截调用的机制(有关此的更多信息,请参阅技术概述)。一些函数,例如内置的 Python 函数或math模块中的函数,未包含在中 __torch_function__,但我们仍然希望在符号跟踪中捕获它们。例如:

import torch.fx
from math import sqrt
def normalize(x):
    """
    Normalize `x` by the size of the batch dimension
    """
    return x / sqrt(len(x))
# It's valid Python code
normalize(torch.rand(3, 4))
traced = torch.fx.symbolic_trace(normalize)
"""
  <...>
  File "sqrt.py", line 9, in normalize
    return x / sqrt(len(x))
  File "pytorch/torch/fx/proxy.py", line 161, in __len__
    raise RuntimeError("'len' is not supported in symbolic tracing by default. If you want "
RuntimeError: 'len' is not supported in symbolic tracing by default. If you want this call to be recorded, please call torch.fx.wrap('len') at module scope
"""

该错误告诉我们不支持内置函数。我们可以将这样的函数作为使用wrap() API 的直接调用记录在跟踪中 :

torch.fx.wrap('len')
torch.fx.wrap('sqrt')
traced = torch.fx.symbolic_trace(normalize)
print(traced.code)
"""
import math
def forward(self, x):
    len_1 = len(x)
    sqrt_1 = math.sqrt(len_1);  len_1 = None
    truediv = x / sqrt_1;  x = sqrt_1 = None
    return truediv
"""

张量构造函数不可追踪

def f(x):
    return torch.arange(x.shape[0], device=x.device)
torch.fx.symbolic_trace(f)
Error traceback:
    return torch.arange(x.shape[0], device=x.device)
    TypeError: arange() received an invalid combination of arguments - got (Proxy, device=Attribute), but expected one of:
    * (Number end, *, Tensor out, torch.dtype dtype, torch.layout layout, torch.device device, bool pin_memory, bool requires_grad)
    * (Number start, Number end, Number step, *, Tensor out, torch.dtype dtype, torch.layout layout, torch.device device, bool pin_memory, bool requires_grad)

上面的代码片段是有问题的,因为 torch.arange() 的参数依赖于输入。此问题的解决方法:

  • 使用确定性构造函数(硬编码),以便将它们产生的值作为常量嵌入到graph:

    def f(x):
         return torch.arange(8, device=device)
    
  • 或者使用 torch.fx.wrap API 包装 torch.arange() 并调用它:

    @torch.fx.wrap
    def do_not_trace_me(x):
        return torch.arange(x.shape[0], device=x.device)
    def f(x):
        return do_not_trace_me(x)
    torch.fx.symbolic_trace(f)
    

tensor.dtype类型转换函数不可追踪

报错提示:TypeError: dtype must be a type, str, or dtype object

修改方法:将 dtype类型转换算子写入到单独的函数中,用@torch.fx.wrap装饰该函数。

@torch.fx.wrap
def do_not_trace_me(x):
    return torch.randn(2).type(x.data.type())

assert函数不可追踪

报错提示:torch.fx.proxy.TraceError: symbolically traced variables cannot be used as inputs to control flow

修改方法:删除 assert代码

数据按下标或切片赋值操作不可追踪

报错提示:TypeError: 'Proxy' object does not support item assignment

修改方法:把相关操作封装为一个函数,对该函数加装饰器@torch.fx.wrap

proxy对象不可迭代

报错提示:Proxy object cannot be iterated. This can be attempted when the Proxy is used in a loop or as a *args or **kwargs function argument.

  1. 列表、元组等可迭代对象不能被遍历。

    # y = list(x.chunk(2))
    y0, y1 = x.chunk(2)
    y = [y0, y1]
    

    修改方法:chunk算子返回的是元组类型,在trace的过程返回的proxy对象。需要把proxy对象进行解包再重组,才能进行遍历。

  2. 函数位置参数和关键字参数不能用*args和**kwargs接收。

fx模型中的变量类型无法判断

  • 静态图模型输入的类型是<class 'torch.fx.proxy.Proxy'>

  • 静态图模型forward过程中函数的返回值类型是<class 'torch.fx.proxy.Proxy'>

以上两种情况在torch.fx.symbolic_trace 追踪过程中都无法判断浮点模型中变量的真实类型,从而导致静态图模型和浮点模型的计算流不一致。

须知: 浮点模型类的属性变量和静态图模型的在forward保持一致,可以进行类型判断。

静态控制流变量

torch._C._get_tracing_state()的返回值在torch的trace过程中恒为True,所以trace之后的静态图结构和该条件下的模型结构一致。

proxy对象无法被deepcopy

报错提示:

 raise NotImplementedError(f"argument of type: {type(a)}")
NotImplementedError: argument of type: <class 'torch.fx.graph_module.GraphModule.__new__.<locals>.GraphModuleImpl'>

原因:在对静态图模型进行trace的过程中,如果将proxy对象赋值为模型的类属性,deepcopy操作将无法对该proxy对象进行深拷贝,从而导致出错。

生成proxy的场景有:

  • 被torch.fx.warp的函数返回值

  • 无法进一步trace的算子比如:Conv2d、Linear、DeConv、算数类算子、形变类算子等小算子。

解决方法:从模型设计上避免将proxy对象赋值为模型的类属性。

如何跳过整个模块量化(如后处理)

针对于模型中存在后处理且后处理存在很多小算子的情况。

import torch

class Model(torch.nn.Module):

    def __init__(self):
        super().__init__()
        self.conv = torch.nn.Conv2d(3, 3, 3)

    def forward(self, x):
        x = self.conv(x)

        # Some operations in the model we don't want it to be quantized.
        # For example, for the following four lines of code,
        # we encapsulate these four lines of code into a function, and then decorate the function with torch.fx.wrap or use torch.fx.wrap('func_name')
        m = torch.meshgrid(torch.arange(x.size()[-1]), torch.arange(x.size()[-1]))[0].unsqueeze(0).unsqueeze(0)
        x = torch.cat((x, m), dim=1)
        x = x.view(-1)
        x = x.sigmoid()

        return x

针对上面的模型,假如我们要跳过其中的四行代码量化,可以把这四行代码封装为一个函数,然后用torch.fx.wrap装饰该函数或使用torch.fx.wrap('函数名')。

修改如下。

import torch
import torch.fx


@torch.fx.wrap
def my_custom_function(x):
    m = torch.meshgrid(torch.arange(x.size()[-1]), torch.arange(x.size()[-1]))[0].unsqueeze(0).unsqueeze(0)
    x = torch.cat((x, m), dim=1)
    x = x.view(-1)
    x = x.sigmoid()
    return x


# torch.fx.wrap('my_custom_function')


class Model(torch.nn.Module):

    def __init__(self):
        super().__init__()
        self.conv = torch.nn.Conv2d(3, 3, 3)

    def forward(self, x):
        x = self.conv(x)
        x = my_custom_function(x)
        return x

注意: 不能跳过pooling/conv/matmul/convtranspose等矩阵运算单元的量化 关于torch.fx.wrap更多信息请参考fx.symbolic_trace异常场景汇总。

静态图模型如何定位bug

如果静态图模型内部逻辑有错误,那么在量化过程中报错打印信息无法准确的给出模型内部的报错点,因此需要在报错的函数前将静态模型转为动态模型作为后续量化的模型,该动态模型保存在下面函数的指定目录下,重新运行量化脚本查看报错信息,该动态模型仅用于debug,当bug解决之后,删除该静态图转动态图的操作。以下为静态图转动态图示例代码,仅供参考。

def graph_to_dynamic_module(graph_module: torch.fx.GraphModule, save_module_path: str,
                            module_name: str = 'FxModule') -> torch.nn.Module:
    """
    Convert static graph to dynamic module
    @param graph_module: fx static graph
    @param save_module_path: path where the dynamic model is to be saved
    @param module_name: Top-level name to use for the ``Module`` while writing out the code
    @return: dynamic module
    """
    sys.path.append(save_module_path)
    device = torch.quantization.fx.utils.assert_and_get_unique_device(graph_module)
    with warnings.catch_warnings():
        warnings.simplefilter('ignore')
        graph_module.to_folder(save_module_path, module_name)
    if 'module' in sys.modules:
        del sys.modules['module']
    module = getattr(importlib.import_module('module'), module_name)
    return module().to(device) if device else module()

forward方法中包含动态获取的参数

当前部署方案是将pytorch模型文件转为onnx模型,编译生成om上板部署,pytorch中如果包含了过多的运算,往往会导致转出的onnx图结构非常复杂,因此,请尽量简化图结构,保证转出的onnx图简洁。

图 1 forward中存在运算过程

对于这种情况,x的大小往往是固定的,或者在固定的循环时是固定的,此时建议在init方法中将x的大小通过数组或固定值的方式提前写好,直接进行调用。如图2所示。

图 2 优化后模型定义

pip install 安装torch包失败

按照如下指令安装torch及torch相关依赖,出现代理错误。

python3.7.5 -m pip --trusted-host=download.pytorch.org install torch==1.10.2+cu102 torchvision==0.11.3+cu102 torchaudio==0.10.2 -f https://download.pytorch.org/whl/torch_stable.html

可以去https://download.pytorch.org/whl/torch_stable.html网页,下载torchvision-0.11.3+cu102-cp37-cp37m-linux_x86_64.whl,torchaudio-0.10.2-cp37-cp37m-linux_x86_64.whl和torch-1.10.2+cu102-cp37-cp37m-linux_x86_64.whl,并通过pip install package_name手动安装。

onnx算子拆分场景说明

表 1 onnx算子拆分场景说明

原始算子

拆分后算子

(包含该算子拆分场景的)典型网络

torch.onnx.symbolic_opset版本号

chunk

gather、Shape、Add、Div、Mul和Slice

ViT

opset_version13

AMCT量化过程中会对原始模型进行onnx导出,过程中部分算子会拆分成小算子的组合,如表1所示。当前AMCT不会对拆分后的算子进行量化,建议将原始模型中的对应算子进行手动替换以实现量化。

安装python3-tk时提示错误信息

问题描述:安装python3-tk依赖时,错误提示如图1所示。

图 1 错误提示信息

解决方案:

将缺失的文件py_compile.py复制到/usr/lib/python3.7路径,然后重新安装。

cp /usr/local/python3.7.5/lib/python3.7/py_compile.py  /usr/lib/python3.7

/usr/local/python3.7.5/lib/python3.7/py_compile.py请以该文件所在实际路径进行替换。

不支持输入大于两维且bias为True的linear量化

linear算子在bias为True的情况下,AMCT默认该类型算子的数据量化带偏移值,with_offset默认为true。在onnx转换过程中,如果该算子的输入张量的形状大于两维且bias=True,则linear会被转换为matmul和add算子,而atc仅支持matmul算子offset为0,所以AMCT也不支持这种算子的量化,但可以调整该类型算子的实现来规避。

方法一:

把linear算子的输入变成两维,经该算子后,得到的数据再还原回之前的形状。

class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = torch.nn.Linear(8, 4, bias=True)

    def forward(self, inputs):
        out1 = self.linear(inputs)

        # plan A
        b, c, w = inputs.data.shape
 
        out2 = self.linear(inputs.reshape(-1, w)).reshape(b, c, -1)
        return out1, out2

x = torch.randn(1, 4, 8)
model = Model()
model.eval()
out1, out2 = model(x)
assert torch.all(out1 == out2)

方法二:

把linear算子的换成1*1卷积核的conv2d来实现,修改输入和还原输出的形状,加载原来模型的权重文件,把linear的权重和bias复制到新的conv2d中。

class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = torch.nn.Linear(8, 4, bias=True)
        self.conv = torch.nn.Conv2d(8, 4, 1, bias=True)

    def forward(self, inputs):
        out1 = self.linear(inputs)

        # plan B
        # inputs.shape: 3dims
        out2 = self.conv(inputs.unsqueeze(1).permute(0, 3, 1, 2)).permute(0, 2, 3, 1).squeeze(1)
        # inputs.shape: 4dims
        # out2 = self.conv(inputs.permute(0, 3, 1, 2)).permute(0, 2, 3, 1)
        return out1, out2

    def _load_from_state_dict(self, state_dict, prefix, local_metadata, strict,
                              missing_keys, unexpected_keys, error_msgs):
        state_dict['conv.weight'] = state_dict['linear.weight'].unsqueeze(-1).unsqueeze(-1)
        state_dict['conv.bias'] = state_dict['linear.bias']
        super()._load_from_state_dict(state_dict, prefix, local_metadata, strict,
                                      missing_keys, unexpected_keys, error_msgs)

x = torch.randn(1, 4, 8)
model = Model()
model.eval()
ckpt = torch.load(r'original_model.pt')
model.load_state_dict(ckpt)
out1, out2 = model(x)
assert torch.all(out1 == out2)

以上调整在保证修改前后输出等价之后,就可以删掉旧的代码了。

注意:当前场景的报错信息无法获取到具体哪个算子,请排查pytorch模型中的所有的linear算子。

离线计算卷积量化后转om报错

离线计算卷积:输入是获取在线Tensor.data的卷积。

报错信息:[ERROR][OfflineCalc][348] [conv2]: float weight or bias is null, can't gen offline output!

原因:atc不支持量化的离线计算卷积。

解决:跳过离线计算卷积的量化。

方法一:在量化的config.json中,将报错的卷积的quant_enable改为false,跳过量化。

方法二:在简易量化yml配skip_layers: ['layer_name'],在create_quant_config_fx传入yml文件。

避免在pytorch模型脚本中引入无效的操作导致掉点

无效操作示例:

  • 切片前后数据相等

  • 乘除1的操作

  • 加减0的操作

  • sum([tensor0, tensor1, tensor2]),对多个tensor累加求和,第一个tensor会先加0,导致onnx中会出现加0的无效算子,建议改成tensor0+tensor1+tensor2。

解决:在pytorch模型脚本中引入上述无效操作,可能导致量化后模型掉点,请检查模型脚本中是否包含无效操作的代码并进行删除。

兄弟节点量化系数不一致导致atc报错

报错信息:[ERROR][CheckDataTypeForOutputPrint][604] Op[sub_6] input[/Slice_20 output 0] quantize param data type[S8] scale[0.998590] offset[-128.000000] should equal to Op[/mul 6/Mul] input[/Slice 20 output 0] quantize param data type(FP16] scale[1.000000] offset[0.000000].

原因:Slice_20子节点是sub_6和/mul 6/Mul,sub_6算子的量化系数是‘data type[S8] scale[0.998590] offset[-128.000000]’,/mul 6/Mul的量化系数是‘type(FP16] scale[1.000000] offset[0.000000]’,这两个兄弟节点的量化系数应该相等。

图 1 deploy_model.onnx

在deploy_model.onnx中搜索sub_6,可以看到slice和sub_6中间有个cast算子,slice的子节点为cast和mul_6,在amct中sub_6和mul_6算子不是兄弟节点。

图 2 cnn_net_tree_parser.dot和quant_param_record.txt

从atc生成的cnn_net_tree_parser.dot转的pdf图模型中可以看到cast算子被融合掉,导致sub_6和mul_6算子变成了兄弟节点,此时amct生成的quant_param_record.txt中,sub_6算子为s8量化,而mul_6算子为fp16,兄弟节点量化系数不一致导致atc报错。

解决:

  1. 在config.json中将sub_6的量化开关关掉,此时sub_6和mul_6算子均不量化,数据类型均为fp16。

  2. cast算子在pytorch中是类型转换操作,有可能是fp32转fp32,属于无效算子,建议手动修改模型的代码,从源头消除cast算子。

以上问题,在其他结构中也可能出现,大部分都是atc融合导致,建议对比deploy_model.onnx和atc生成的dot图模型,结合quant_param_record.txt量化系数,通过修改config.json和模型结构来处理。

找不到dot文件

报错信息:FileNotFoundError: [Errno 2] No such file or directory: 'dot': 'dot'

报错原因:缺少graphviz工具。

解决方案:sudo apt-get install graphviz

ModuleNotFoundError: No module named 'wheel' or No module named 'torch'

报错原因:缺少wheel或torch安装包

解决方法:使用“pip list”查看python开发环境中,是否有wheel和torch。如果缺少wheel,则使用“pip install wheel”命令进行安装。如果缺少torch,参考安装依赖中关于pytorch的安装。

libcudart.so.*: cannot open shared object file: No such file or directory

报错原因:amct基于某一个版本的cuda进行编译,依赖了cuda的部分so包。

解决方法:1、按照环境准备指导安装CUDA toolkit,2、保证torch的cuda版本和amct的cuda版本一致,如果不一致,先卸载torch和amct,然后安装对应版本的torch,再安装和torch的cuda版本一致的amct。

mamtul第二路值域较宽导致掉点问题

掉点原因:MatMul 算子第二路仅能执行8bit对称量化,当第二路浮点输入的范围值域较大(比如:-30.6857~12.2666),量化到int8(-128~127),会导致scale值较大,从而产生较大量化误差较大。

解决方法: 将该mamtul的左右输入调换,使第一路进行做高精度量化,示例如下:

import torch 
A = torch.randn(1, 3, 3) 
B = torch.randn(1, 3, 3) 
assert torch.all(torch.matmul(A, B) == (torch.matmul(B.permute(0, 2, 1), A.permute(0, 2, 1))).permute(0, 2, 1))

附录

支持量化的算子列表

训练后量化,支持量化的层以及约束如下。

表 1 量化支持的层以及约束

量化类型

算子类型

约束

备注

支持权重及数据量化的层

torch.nn.Linear

-

复用层(共用weight和bias参数)支持量化。

torch.nn.Conv2d

-

torch.nn.ConvTranspose2d

-

支持数据量化的层

torch.nn.MaxPool2d,

torch.nn.functional.max_pool2d

-

-

torch.nn.AvgPool2d

torch.nn.functional.avg_pool2d,

-

-

torch.nn.AdaptiveMaxPool2d

torch.nn.functional.adaptive_max_pool2d

-

-

torch.nn.AdaptiveAvgPool2d

torch.nn.functional.adaptive_avg_pool2d

-

-

torch.nn.LayerNorm

-

-

+(operator.add)

torch.add

Tensor.add

Tensor.add_(inplace不推荐使用)

至少有一路输入类型是Tensor

-

-(operator.sub)

torch.sub

Tensor.sub

Tensor.sub_(inplace不推荐使用)

至少有一路输入类型是Tensor

-

*(operator.mul)

torch.mul

Tensor.mul

Tensor.mul_(inplace不推荐使用)

至少有一路输入类型是Tensor

-

/(operator.truediv)

torch.div

Tensor.div

Tensor.div_(inplace不推荐使用)

至少有一路输入类型是Tensor

-

**(operator.pow)

torch.pow,

Tensor.pow

Tensor.pow_(inplace不推荐使用)

至少有一路输入类型是Tensor

-

torch.bmm

-

-

torch.sum

-

-

torch.matmul

operator.matmul

Tensor.matmul

不支持对离线输入的constant常量和parameter进行量化场景

-

torch.abs

Tensor.abs

Tensor.abs_(inplace不推荐使用)

-

-

torch.max

Tensor.max

keepdim=True

-

torch.min

Tensor.min

-

-

Tensor.mean

torch.mean

-

-

torch.clamp

Tensor.clamp

Tensor.clamp_(inplace不推荐使用)

-

-

torch.permute

Tensor.permute

-

-

torch.nn.Upsample

torch.nn.functional.upsample

-

-

torch.nn.functional.interpolate

-

-

Tensor.transpose

torch.transpose

-

-

torch.concat

-

-

torch.cat

-

-

torch.stack

-

-

Tensor.flatten

torch.flatten

和非shape类算子搭配使用时才量化

-

torch.squeeze

Tensor.squeeze

和非shape类算子搭配使用时才量化

-

torch.unsqueeze

Tensor.unsqueeze

和非shape类算子搭配使用时才量化

-

Tensor.reshape

torch.reshape

和非shape类算子搭配使用时才量化

-

Tensor.view

和非shape类算子搭配使用时才量化

-

torch.split

Tensor.split

-

-

torch.nn.ReLU

torch.nn.functional.relu

torch.nn.functional.relu_(inplace不推荐使用)

Tensor.relu

Tensor.relu_(inplace不推荐使用)

该算子在Conv2d或ConvTranspose2d后面时不进行量化

-

torch.nn.ReLU6

torch.nn.functional.relu6

该算子在Conv2d或ConvTranspose2d后面时不进行量化

-

Tensor.sigmoid

Tensor.sigmoid_(inplace不推荐使用)

torch.nn.functional.sigmoid

该算子在Conv2d或ConvTranspose2d后面时不进行量化

-

Tensor.tanh

Tensor.tanh_(inplace不推荐使用)

torch.nn.Tanh

torch.nn.functional.tanh

该算子在Conv2d或ConvTranspose2d后面时不进行量化

-

torch.nn.LeakyReLU

torch.nn.functional.leaky_relu

torch.nn.functional.leaky_relu_(inplace不推荐使用)

该算子在Conv2d或ConvTranspose2d后面时不进行量化

-

torch.nn.PReLU

torch.nn.functional.prelu

该算子在Conv2d或ConvTranspose2d后面时不进行量化

-

torch.nn.GELU

torch.nn.functional.gelu

-

-

torch.nn.Hardtanh

torch.nn.functional.hardtanh

torch.nn.functional.hardtanh_(inplace不推荐使用)

-

-

torch.nn.Softmax

torch.nn.functional.softmax

-

-

torch.nn.Softmax2d

-

-

torch.nn.LogSoftmax

torch.nn.functional.log_softmax

-

-

torch.nn.ELU

torch.nn.functional.elu

torch.nn.functional.elu_(inplace不推荐使用)

-

-

torch.nn.LogSigmoid

torch.nn.functional.logsigmoid

-

-

torch.nn.Softsign

torch.nn.functional.softsign

作为激活函数默认不量化,如有需要可在量化配置文件中手动把hardtanh的量化开关打开

-

torch.nn.Softmin

torch.nn.functional.softmin

-

-

torch.nn.Tanhshrink

torch.nn.functional.tanhshrink

-

-

torch.nn.GLU

torch.nn.functional.glu

-

-

torch.nn.Hardswish

torch.nn.functional.hardswish

-

-

torch.nn.SiLU

torch.nn.functional.silu

-

-

torch.nn.Mish

torch.nn.functional.mish

-

-

torch.nn.BatchNorm2d

-

-

torch.nn.SyncBatchNorm2d

不和Conv融合时,会单独做激活量化

  

amct.nn.SpaceToDepth

-

对应onnx 算子中的SpaceToDepth,不同于torch.nn.PixelUnshuffle , 和amct.nn.DepthToSpace成对使用。

amct.nn.DepthToSpace

-

对应onnx 算子中的DepthToSpace的DCR模式,不同于torch.nn.PixelShuffle, PixelShuffle, 对应的是DepthToSpace的CRD模式。

amct.nn.Warp

-

对应torch算子中的torch.nn.functional.grid_sample其参数为mode='nearest', align_corners=True, padding_mode='border',Warp是对自定义roi范围进行处理,GlobalWarp默认对全局数据处理。

amct.nn.GlobalWarp

-

静态图简易量化配置功能说明

静态图简易量化配置功能支持用户通过自定义yaml格式文件生成量化配置,提供简易量化配置yaml文件示例内容如下(用户可根据需求自定义修改)。

# Layer name not to be quantized
skip_layers: []

# om.json generated from model convert for config calibration
#
om_config:
  mapping_file: "./om_json/resnet50.om.json"

# default config
common_config:
  {
    weight_quant_params:
      {
        quantizer: "ArqWeightFakeQuantize",
        quantizer_args:
          {
            observer: "ArqObserver",
            num_bits: 8,
          }
      },
    activation_quant_params:
      {
        quantizer: "UlqFakeQuantize",
        quantizer_args:
          {
            num_bits: 8,
            observer: "IFMRObserver",
          }
      }
  }


# layer config user defined for overriding
override_layer_configs:
  [
    {
      layer_name: "fc",
      weight_quant_params:
        {
          quantizer: "ArqWeightFakeQuantize",
          quantizer_args:
            {
              observer: "ArqObserver",
              num_bits: 8,
              channel_wise: false
            }
        },
      activation_quant_params:
        {
          quantizer: "UlqFakeQuantize",
          quantizer_args:
            {
              num_bits: 8,
              observer: "IFMRObserver",
            }
        }
    },
    {
      # User-defined or third-party quantization algorithm
      layer_name: "conv1",
      weight_quant_params:
        {
          quantizer: "torch.quantization.FakeQuantize",
          quantizer_args:
            {
              observer: "torch.quantization.PerChannelMinMaxObserver",
              quant_min: -128,
              quant_max: 127,
              dtype: "torch.qint8",
              qscheme: "torch.per_channel_symmetric",
              reduce_range: false,
              ch_axis: 0
            }
        },
      activation_quant_params:
        {
          quantizer: "torch.quantization.FakeQuantize",
          quantizer_args:
            {
              observer: "torch.quantization.MovingAverageMinMaxObserver",
              quant_min: -128,
              quant_max: 127,
              dtype: "torch.qint8",
              qscheme: "torch.per_tensor_affine",
              reduce_range: false
            }
        }
    }
  ]

# layer config by types user defined for overriding
override_layer_types:
  [
    {
      layer_type: "torch.nn.MaxPool2d",
      activation_quant_params:
        {
          quantizer: "LuqActivationFakeQuantize",
          quantizer_args:
            {
              num_bits: 8,
              observer: "LuqMseObserver"
            }
        }
    }
  ]

表 1 静态图简易量化配置参数说明

参数名

类型

是否必填

字段

含义

skip_layers

List<String>

optional

-

需要跳过量化的pytorch层名

mapping_file

string

optional

-

Mindstudio工具基于已生成的模型进行模型转换时会生成一个以.om.json为后缀的文件,其中会包含各层输入的量化位宽信息,可以用这个文件进行配置重新生成config.json进行校准

common_config

-

optional

weight_quant_params

权重量化配置参数

-

optional

activation_quant_params

数据量化配置参数

weight_quant_params

string

required

quantizer

权重量化算法类的包名

-

required

quantizer_args

算法类初始化的属性(支持即时注册,存在扩展)

activation_quant_params

string

required

quantizer

数据量化算法类的包名

-

required

quantizer_args

算法类初始化的属性(支持即时注册,存在扩展)

quantizer_args

string

optional

observer

observer对应量化过程中的calibration场景,其对应不同类型的算法,算法详情参见observer算法

bool

optional

channel_wise

  • 取值为true时,每个channel独立量化,量化因子不同。
  • 取值为false时,所有channel同时量化,共享同一个量化因子。

int

optional

num_bits

量化位宽

override_layer_configs

List

required

layer_name

需要覆盖自定义量化配置的层名

-

activation_quant_params

数据量化配置参数

-

weight_quant_params

权重量化配置参数

override_layer_types

List

required

layer_type

需要覆盖自定义量化配置的层类型

-

activation_quant_params

数据量化配置参数

-

weight_quant_params

权重量化配置参数

对于既有json格式的简易量化配置内容,为减少手动构造配置文件的工作量,可以通过yaml包的dump方法保存为yaml格式文件,供量化工具接口读取,示例如下。

import yaml

def trans():
    fx_quant = {
        "skip_layers": [],
        "override_layer_configs": [
            {
                "layer_name": "fc",
                "weight_quant_params": {
                    "quantizer": "ArqWeightFakeQuantize",
                    "quantizer_args": {
                        "observer": "ArqObserver",
                        "num_bits": 8,
                        "channel_wise": False
                    }
                },
                 "activation_quant_params": {
                     "quantizer": "UlqFakeQuantize",
                    "quantizer_args": {
                        "observer": "IFMRObserver",
                         "num_bits": 12
                    }
                }
            }
        ]
    }
with open('./test.yaml', 'w', encoding="utf-8") as f:
    yaml.dump(fx_quant, f, sort_keys=False)

if __name__=='__main__':
    trans()

保存得到yaml格式简易量化配置文件内容如下。

skip_layers: []
override_layer_configs:
- layer_name: fc
  weight_quant_params:
    quantizer: ArqWeightFakeQuantize
    quantizer_args:
      observer: ArqObserver
      num_bits: 8
      channel_wise: false
  activation_quant_params:
    quantizer: UlqFakeQuantize
    quantizer_args:
      observer: IFMRObserver
      num_bits: 12

cube only 量化

仅量化CUBE算子(Conv2d、ConvTranspose2d、Linear、matmul和pooling系列算子),可以减少量化误差,但性能会相应降低,带宽会相应提高,推荐在全网8bit PTQ量化误差较大时使用。

  1. cube_only_8bit.yaml,cube only 8bit量化配置。

common_config: # 全局量化算法配置
  quant_enable: false  # 是否量化
  weight_quant_params:  # 权重量化配置参数
    quantizer: ArqWeightFakeQuantize  # 权重量化算法
  activation_quant_params:  # 数据量化配置参数
    quantizer: UlqFakeQuantize  # 数据量化算法
override_layer_types:  # 根据算子类型设置量化算法
- layer_type:  # cube算子
  - torch.nn.Conv2d
  - torch.nn.ConvTranspose2d
  weight_quant_params:
    quantizer: ArqWeightFakeQuantize
    quantizer_args:  # 权重量化算法参数
      observer: ArqObserver  # 权重校准算法
      num_bits: 8  # 量化位宽
  activation_quant_params:  
    quantizer: UlqFakeQuantize  # 数据量化算法
    quantizer_args:  # 数据量化算法参数
      observer: IFMRObserver  # 数据校准算法
      num_bits: 8  # 位宽
- layer_type:  # cube算子
  - torch.nn.Linear
  weight_quant_params:
    quantizer: ArqWeightFakeQuantize
    quantizer_args:  # 权重量化算法参数
      observer: ArqObserver  # 权重校准算法
      num_bits: 8  # 量化位宽
  activation_quant_params:  
    quantizer_args:  # 数据量化算法参数
      observer: IFMRObserver  # 数据校准算法
      num_bits: 8  # 位宽
- layer_type:  # matmul算子
  - torch.matmul
  - operator.matmul
  - matmul
  activation_quant_params:
  - quantizer: LuqActivationFakeQuantize
    quantizer_args:
      observer: IFMRObserver
      num_bits: 8
      batch_num: 1
      qscheme: symmetric  # 对称量化
      with_offset: false  # 对称量化
  - quantizer: LuqActivationFakeQuantize
    quantizer_args:
      observer: IFMRObserver
      num_bits: 8  # matmul算子的第二路默认为权重,不支持高精。
      batch_num: 1
      qscheme: symmetric
      with_offset: false
- layer_type:  # pooling算子
  - torch.nn.MaxPool2d
  - torch.nn.AdaptiveMaxPool2d
  - torch.nn.functional.max_pool2d
  - torch.nn.functional.max_pool2d_with_indices
  - torch.nn.functional.adaptive_max_pool2d
  - torch.nn.functional.adaptive_max_pool2d_with_indices
  - torch.nn.functional.adaptive_avg_pool2d
  - torch.nn.AdaptiveAvgPool2d
  - torch.nn.AvgPool2d
  - torch.nn.functional.avg_pool2d
  activation_quant_params:
    quantizer: UlqFakeQuantize
    quantizer_args:
      observer: IFMRObserver
      num_bits: 8
override_layer_configs: # 指定算子名设置量化配置参数
- layer_name: []
  weight_quant_params:
    quantizer: ArqWeightFakeQuantize
    quantizer_args:
      observer: ArqObserver
      num_bits: 8  # 权重仅支持4和8bit量化
      channel_wise: true  # True:perchannel量化,False:pertensor量化
      eps: 0.000000119209290  # 权重scale的最小截断值,当bias大于6.4031265时,自动增大eps为0.00000763312164,防止bias溢出。
  activation_quant_params:
    quantizer: UlqFakeQuantize
    quantizer_args:
      num_bits: 16  # 高精[9-16], 对应芯片中类型S16
      observer: IFMRObserver
# skip_layers:  # 指定算子名跳过量化,算子名可在deploy.onnx、quant.json、float.svg和quant.svg中找到。
#  - add
#  - add_1

  1. cube_only_12bit.yaml,cube only 12bit量化配置。

common_config: # 全局量化算法配置
  quant_enable: false  # 是否量化
  weight_quant_params:  # 权重量化配置参数
    quantizer: ArqWeightFakeQuantize  # 权重量化算法
  activation_quant_params:  # 数据量化配置参数
    quantizer: UlqFakeQuantize  # 数据量化算法
override_layer_types:  # 根据算子类型设置量化算法
- layer_type:  # cube算子
  - torch.nn.Conv2d
  - torch.nn.ConvTranspose2d
  weight_quant_params:
    quantizer: ArqWeightFakeQuantize
    quantizer_args:  # 权重量化算法参数
      observer: ArqObserver  # 权重校准算法
      num_bits: 8  # 量化位宽
  activation_quant_params:  
    quantizer: UlqFakeQuantize  # 数据量化算法
    quantizer_args:  # 数据量化算法参数
      observer: IFMRObserver  # 数据校准算法
      num_bits: 12  # 位宽
- layer_type:  # cube算子
  - torch.nn.Linear
  weight_quant_params:
    quantizer: ArqWeightFakeQuantize
    quantizer_args:  # 权重量化算法参数
      observer: ArqObserver  # 权重校准算法
      num_bits: 8  # 量化位宽
  activation_quant_params:  
    quantizer_args:  # 数据量化算法参数
      observer: IFMRObserver  # 数据校准算法
      num_bits: 12  # 位宽
- layer_type:  # matmul算子
  - torch.matmul
  - operator.matmul
  - matmul
  activation_quant_params:
  - quantizer: LuqActivationFakeQuantize
    quantizer_args:
      observer: IFMRObserver
      num_bits: 12
      batch_num: 1
      qscheme: symmetric  # 对称量化
      with_offset: false  # 对称量化
  - quantizer: LuqActivationFakeQuantize
    quantizer_args:
      observer: IFMRObserver
      num_bits: 8  # matmul算子的第二路默认为权重,不支持高精。
      batch_num: 1
      qscheme: symmetric
      with_offset: false
- layer_type:  # pooling算子
  - torch.nn.MaxPool2d
  - torch.nn.AdaptiveMaxPool2d
  - torch.nn.functional.max_pool2d
  - torch.nn.functional.max_pool2d_with_indices
  - torch.nn.functional.adaptive_max_pool2d
  - torch.nn.functional.adaptive_max_pool2d_with_indices
  - torch.nn.functional.adaptive_avg_pool2d
  - torch.nn.AdaptiveAvgPool2d
  - torch.nn.AvgPool2d
  - torch.nn.functional.avg_pool2d
  activation_quant_params:
    quantizer: UlqFakeQuantize
    quantizer_args:
      observer: IFMRObserver
      num_bits: 12
override_layer_configs: # 指定算子名设置量化配置参数
- layer_name: []
  weight_quant_params:
    quantizer: ArqWeightFakeQuantize
    quantizer_args:
      observer: ArqObserver
      num_bits: 8  # 权重仅支持4和8bit量化
      channel_wise: true  # True:perchannel量化,False:pertensor量化
      eps: 0.000000119209290  # 权重scale的最小截断值,当bias大于6.4031265时,自动增大eps为0.00000763312164,防止bias溢出。
  activation_quant_params:
    quantizer: UlqFakeQuantize
    quantizer_args:
      num_bits: 12  # 高精[9-16], 对应芯片中类型S16
      observer: IFMRObserver
# skip_layers:  # 指定算子名跳过量化,算子名可在deploy.onnx、quant.json、float.svg和quant.svg中找到。
#  - add
#  - add_1

使用方法:

  • 通过mindcmd oneclick 指定命令行参数 --quant_config cube_only_8bit.yaml。

  • 通过create_quant_config_fx函数传入参数config_definition=r'cube_only_8bit.yaml'。

误差分析功能的使用介绍

  1. 使用mindcmd进行误差分析:

    示例:

    model.py # 模型所在python文件

    import torch
    
    class Model(torch.nn.Module):
        def __init__(self, *args, **kwargs):
            super().__init__()
            self.conv = torch.nn.Conv2d(3, 3, 1)
    
        def forward(self, x):
            x = self.conv(x)
            return x
    
    def load_pytorch_model():
        # 模型实例化,可以加载浮点权重、一些自定义处理等操作
        model = Model(args, kwargs)
        model.eval()
        ckpt = torch.load(r'ckpt.pt', map_location='cpu')
        model.load_state_dict(ckpt)
        return model
    

    mindcmd 一键比对:

    # 将上面的model.py所在文件夹路径设置为python的导包路径
    export PYTHONPATH=dirpath:$PYTHONPATH
    
    # 打开mindcmd.ini的IS_QUANT_ANALYSIS_OPEN
    mindcmd config -g is_quant_analysis_open=1
    注意:误差分析功能仅支持模型的输出的类型为List[torch.Tensor, torch.Tensor]、Tuple(torch.Tensor, torch.Tensor)和torch.Tensor, torch.Tensor
    
    # mindcmd 一键推理,更多参数请查阅《MindCmd 使用指南》一键推理章节
    mkdir ./save_dir
    mindcmd oneclick -k ./save_dir pytorch -m model.load_pytorch_model -w quant_model_ckpt.pt --quant_config quant_config.json --input_shape 1,3,64,64 -i input.npy --realquant
    # 执行完成之后,会在workspace中./*/output/latest_result/amct/pytorch/quant_analyzer生成三份分析报告
    # layerwise_analysis_report.html:分析网络中单独的量化层对网络输出的影响情况
    # graphwise_analysis_report.html:分析网络中的每层的fakequant和realquant输出分别对浮点的余弦相似度情况
    # statistic_analysis_report.html:统计每一层量化前后的输出以及权重的统计分布情况和可视化直方图
    # workspace中./*/output/latest_result/dump中有模型每一层输出的比对结果。
    
    
  2. 手动调用amct误差分析接口:

    import torchvision
    
    model = torchvision.models.resnet50(pretrained=True)
    model.eval()
    # 加载已量化的模型
    quant_model = amct.restore_quant_model_fx('quant_config.json', model, "quant_model_ckpt.pt")
    
    # 1、可以对已量化的模型进行分析
    quant_analyzer = amct.QuantAnalyzer(quant_model, inputs, save_dir)
    quant_analyzer.analyze()
    
    # 2、可以对未量化的模型进行分析
    quant_analyzer = amct.QuantAnalyzer(model, inputs, save_dir)
    quant_analyzer.analyze()
    
    # 执行完成之后会在save_dir中生成分析报告,详细请参考《AMCT使用指南(PyTorch)》的“量化误差分析”章节。
    

量化因子记录文件说明

量化因子记录文件格式说明

量化因子record文件格式,为基于protobuf协议的序列化数据结构文件,其对应的protobuf原型定义为(或查看_AMCT安装目录_/hotwheels/amct_pytorch/proto/scale_offset_record_pytorch.proto文件):

syntax = "proto2";
package AMCTPytorchProto;
 
message ActivationQuantParam
{
    required float scale_d = 1;
    required int32 offset_d = 2;
    required int32 index = 3;
    optional int32 num_bits_d = 9;
}
 
message SingleLayerRecord {
    repeated ActivationQuantParam record_d = 1;
    repeated float scale_w = 3;
    repeated int32 offset_w = 4;
    repeated uint32 shift_bit = 5;
    optional int32 num_bits_w = 6;
    optional bool skip_fusion = 9 [default = false];
    repeated int32 params = 10;
    repeated float centroids = 11;
    optional float weight_clip_max = 12;
}
 
message MapFiledEntry {
    optional string key = 1;
    optional SingleLayerRecord value = 2;
}
 
message ScaleOffsetRecord {
    repeated MapFiledEntry record = 1;
}

参数说明如下。

消息

是否必填

类型

字段

说明

SingleLayerRecord

-

-

-

包含了量化层所需要的所有量化因子记录信息。

repeated

ActivationQuantParam

record_d

数据量化因子

repeated

float

scale_w

权重量化scale因子,支持标量(对当前层的权重进行统一量化),向量(对当前层的权重按channel_wise方式进行量化)两种模式,仅支持Conv2d类型进行channel_wise量化模式。

repeated

int32

offset_w

权重量化offset因子,同scale_w一样支持标量和向量两种模式,且需要同scale_w维度一致,当前不支持权重带offset量化模式,offset_w仅支持0。

repeated

uint32

shift_bit

移位因子

optional

int32

num_bits_w

权重量化位宽

optional

bool

skip_fusion

配置当前层是否要跳过Conv+BN融合,默认为false,即当前层要做上述融合。

repeated

int32

params

量化参数

repeated

float

centroids

聚类中心

optional

float

weight_clip_max

权重量化学习的最大值

ScaleOffsetRecord

-

-

-

map结构,为保证兼容性,采用离散的map结构。

repeated

MapFiledEntry

record

每个record对应一个量化层的量化因子记录;record包括两个成员:

  • key为所记录量化层的layer name。
  • value对应SingleLayerRecord定义的具体量化因子。

MapFiledEntry

optional

string

key

层名。

optional

SingleLayerRecord

value

量化因子配置。

ActivationQuantParam

-

-

-

数据量化因子

required

float

scale_d

数据量化scale因子,仅支持对数据进行统一量化。

required

int32

offset_d

数据量化offset因子,仅支持对数据进行统一量化。

required

int32

index

记录第几路输入

optinal

int32

num_bits_d

数据量化位宽

对于optional字段,由于protobuf协议未对重复出现的值报错,而是采用覆盖处理,因此出现重复配置的optional字段内容时会默认保留最后一次配置的值,需要用户自己保证文件的正确性

对于一般量化层需要配置包含scale_d、offset_d、scale_w、offset_w、shift_bit参数,量化因子record文件格式参考示例如下。

record { 
  key: "conv1" 
  value { 
    scale_d: 0.0798481479 
    offset_d: 1 
    scale_w: 0.00297622895 
    offset_w: 0 
    shift_bit: 1 
    skip_fusion: true 
  } 
} 
record { 
  key: "fc"
  value {
    record_d {
      scale_d: 0.016266866
      offset_d: -128
      index: 0
      num_bits_d: 8
    }
    scale_w: 0.00089841383
    offset_w: 0
    num_bits_w: 4
    params: -85
    params: -59
    params: -45
    centroids: -0.65496659
    centroids: -0.45462388
    centroids: -0.34674704
    weight_clip_max: 3.6236975
   }
 }

量化因子说明

对于量化层数据和权重分别需要提供量化因子scale(浮点数的缩放因子),offset(偏移量)两项,AMCT采用的是统一的量化数据格式,参考如下,其应用表达式为:

支持的取值范围为:

量化通常分为对称量化算法、非对称量化算法两类:

  1. 对称量化算法原理:

    原始高精度数据和量化后int8数据的转换为:,其中scale是float32的浮点数,为了能够表示正负数,采用signed int8的数据类型,通过原始高精度数据转换到int8数据的操作如下,其中round为取整函数,量化算法需要确定的数值即为常数scale:

    对权值和数据的量化可以归结为寻找scale的过程,由于为有符号数,要保证正负数值表示范围的对称性,因此对所有数据首先进行取绝对值的操作,使待量化数据的范围变换为,再来确定_scale_。由于INT8在正数范围内能表示的数值范围为[0,127],因此_scale_可以通过如下方式计算得到。

    确定了_scale_之后,INT8数据对应的表示范围为,量化操作即为对量化数据以进行饱和,即超过范围的数据饱和到边界值,然后进行公式所示量化操作即可。

  2. 非对称量化算法原理:

    与对称量化算法主要区别在于数据转换的方式不同,如下,同样需要确定_scale_与_offset_这两个常数。

    确定后通过原始高精度数据计算得到UINT8数据的转换,即为如下公式所示:

    其中,scale是FP32浮点数,为unsigned INT8定点数,offset是INT8定点数。其表示的数据范围为。若待量化数据的取值范围为,则scale和offset的计算方式如下。

    量化数据格式统一:通过将非对称量化公式进行简单的数据变换,可以使得量化后的数据与对称量化算法在数据格式上保持一致,均为int格式。具体变换过程如下。

    以int8量化为例进行说明,公式符号与之前保持一致,输入原始高精度浮点数据为,原始量化后的定点数为,量化scale,原始量化(算法要求强制过零点,否则可能会出现精度问题),原始量化的计算原理公式如下。

    其中。通过上述变换,可以将量化数据也转成int8格式。确定scale和变换后的offset'后,通过原始高精度浮点数据计算得到INT8数据的转换即为如下公式所示。

安装Python3.7.5(Ubuntu)

  1. 检查系统是否安装python3.7.5开发环境。

    分别使用命令python3.7.5 --versionpython3.7 --versionpip3.7.5 --version、pip3.7 --version检查是否已经安装,如果返回如下信息则说明已经安装,否则请参见下一步。

    Python 3.7.5 
    pip 19.2.3 from /usr/local/python3.7.5/lib/python3.7/site-packages/pip (python 3.7)
    
  2. 安装python3.7.5依赖的包。

    sudo apt-get install -y make zlib1g zlib1g-dev build-essential libbz2-dev libsqlite3-dev libssl-dev libxslt1-dev libffi-dev openssl python3-tk
    

    libsqlite3-dev需要在python安装之前安装,如果用户操作系统已经安装python3.7.5环境,在此之后再安装libsqlite3-dev,则需要重新编译python环境。如果安装python3-tk失败,请参见安装python3-tk时提示错误信息。

  3. 安装python3.7.5。

    1. 使用wget下载python3.7.5源码包,可以下载到AMCT所在服务器任意目录,命令为:

      wget https://www.python.org/ftp/python/3.7.5/Python-3.7.5.tgz
      
    2. 进入下载后的目录,解压源码包,命令为:

      tar -zxvf Python-3.7.5.tgz
      
    3. 进入解压后的文件夹,执行配置、编译和安装命令:

      cd Python-3.7.5
      ./configure --prefix=/usr/local/python3.7.5 --enable-loadable-sqlite-extensions --enable-shared
      make
      sudo make install
      

      其中“--prefix”参数用于指定python安装路径,用户根据实际情况进行修改,“--enable-shared”参数用于编译出libpython3.7m.so.1.0动态库,“--enable-loadable-sqlite-extensions”参数用于加载sqlite-devel依赖。

      本手册以--prefix=/usr/local/python3.7.5路径为例进行说明。执行配置、编译和安装命令后,安装包在/usr/local/python3.7.5路径,libpython3.7m.so.1.0动态库在/usr/local/python3.7.5/lib/libpython3.7m.so.1.0路径。

    4. 执行如下命令设置软链接:

      sudo ln -s /usr/local/python3.7.5/bin/python3 /usr/local/python3.7.5/bin/python3.7.5
      sudo ln -s /usr/local/python3.7.5/bin/pip3 /usr/local/python3.7.5/bin/pip3.7.5
      
    5. 设置python3.7.5环境变量。

      • 如果python安装用户为root:

        该场景下AMCT使用root用户进行安装,请在当前终端窗口直接执行如下命令设置环境变量。

        #用于设置python3.7.5库文件路径
        export LD_LIBRARY_PATH=/usr/local/python3.7.5/lib:$LD_LIBRARY_PATH
        #如果用户环境存在多个python3版本,则指定使用python3.7.5版本
        export PATH=/usr/local/python3.7.5/bin:$PATH
        

        须知: 运行用户是root,不建议修改.bashrc,否则可能会影响其它系统提供的python工具的使用,如果仍想使用系统默认工具,则请重新开启终端窗口。

    • 如果python安装用户为非root:

      该场景下AMCT使用非root用户进行安装,请以非root用户在任意目录下执行vi ~/.bashrc命令,打开**.bashrc**文件,在文件最后一行后面添加如下内容。

      #用于设置python3.7.5库文件路径
      export LD_LIBRARY_PATH=/usr/local/python3.7.5/lib:$LD_LIBRARY_PATH
      #如果用户环境存在多个python3版本,则指定使用python3.7.5版本
      export PATH=/usr/local/python3.7.5/bin:$PATH
      

      执行**:wq!命令保存文件并退出,执行source ~/.bashrc**命令使其立即生效。

  4. 安装完成之后,执行如下命令查看安装版本,如果返回相关版本信息,则说明安装成功。

    python3.7.5 --version
    pip3.7.5  --version
    python3.7 --version
    pip3.7  --version
    

dot图可视化

使用python工具将atc生成dot可视化。

demo:

# dot2pdf.py
import glob
import sys
import os.path

from graphviz import Source  # pip install graphviz


def dot2pdf(dot_path):
    if os.path.isfile(dot_path) and dot_path.endswith(".dot"):
        dot_paths = [dot_path]
    elif os.path.isdir(dot_path):
        dot_paths = list(glob.glob(f'{dot_path}/*.dot'))
    else:
        raise FileNotFoundError

    if len(dot_paths) == 0:
        raise FileNotFoundError
    for dot_path in dot_paths:
        with open(dot_path) as f:
            dot_data = f.read()
        graph = Source(dot_data)
        output = os.path.splitext(dot_path)[0]
        graph.render(output, format="pdf", cleanup=True)
        print('generated file {}.pdf successfully'.format(output))


if __name__ == '__main__':
    dot_source = sys.argv[1]
    dot2pdf(dot_source)

执行命令:python dot2pdf.py your_dot_path