OpenPPL PPQ量化:原理与实践

news2024/11/15 14:01:56

目录

量化原理

为什么需要量化?

量化粒度

框架综述

算子划分

量化中的图融合操作

量化实践:以pytorch mobilenet v2 模型为例

源码阅读 

torch模型和onnx量化过程中的区别

后记


量化原理

为什么需要量化?

1、减少内存带宽和存储空间

深度学习模型主要是记录每个 layer(比如卷积层/全连接层) 的 weights 和 bias, FP32 模型中,每个 weight 数值原本需要 32-bit 的存储空间,量化之后只需要 8-bit 即可。因此,模型的大小将直接降为将近 1/4。

不仅模型大小明显降低, activation 采用 8-bit 之后也将明显减少对内存的使用,这也意味着低精度推理过程将明显减少内存的访问带宽需求,提高高速缓存命中率,尤其对于像 batch-norm, relu,elmentwise-sum 这种内存约束(memory bound)的 element-wise 算子来说,效果更为明显。

2、提高系统吞吐量(throughput),降低系统延时(latency)

直观理解,试想对于一个 专用寄存器宽度为 512 位的 SIMD 指令,当数据类型为 FP32 而言一条指令能一次处理 16 个数值,但是当我们采用 8-bit 表示数据时,一条指令一次可以处理 64 个数值。因此,在这种情况下,可以让芯片的理论计算峰值增加 4 倍。在CPU上,英特尔至强可扩展处理器的 AVX-512 和 VNNI 高级矢量指令支持低精度和高精度的累加操作。

量化粒度

量化粒度是指共享量化参数的大小,例如 每个 Tensor 共享一组量化参数,那么量化的粒度为 per-tensor。量化的粒度越小,模型的精度越好,但计算成本越高。

  • per-tensor:整个神经网络层用一组量化参数(scale, zero-point)

  • per-channel:一层神经网络每个通道用一组量化参数(scale, zero-point)。那么就是per-channel需要存更多的量化参数,对的计算速度也有一点影响。在深度学习中,张量的每一个通道通常代表一类特征,因此可能会出现不同的通道之间数据分布较大的情况。对于通道之间差异较大的情况仍然使用张量级的量化方式可能对精度产生一定的影响,因此通道级量化就显得格外重要。

为了获得最大的性能,考虑到整数矩阵乘法,量化的粒度应该是:

  • 对于激活的量化,由于性能原因,推荐per-tensor

  • 对于权重的量化,per-channel和per-tensor都行

可以想象到per-channel量化很明显细粒度更高,所以一般来说效果会更好,但当前主流的量化仍然是权重和激活都采用per-tensor量化。

框架综述

整个框架可以分成三部分:

PPQ Paser 模块可读取 onnx 或 caffe 模型,并解析成内部格式。解析完成后,Scheduler 模块对模型进行切分与调度,粗颗粒度地划分量化与非量化算子。

Quantizer 模块是 PPQ 量化执行的中枢,为模型算子分配特定的部署平台,并初始化量化设置,调用各种优化 Pass,完成量化联合定点、图融合及量化优化。

Executor 模块依据模型拓扑关系,调用底层算子实现,执行前向推理。模型量化完成后,调用 Exporter 模块,导出模型和量化参数。

算子划分

PPQ 使用 graph dispatcher 将图中所有算子划分为三类:

  • 不可量化区:这区域的算子与 shape或者 index 有关,一旦量化将导致图的计算发生错误,因此不可量化,同时默认被调度到 Host 端以浮点精度执行。

  • 可量化区:这区域的算子被认为是可以量化的,它们是 input, conv, gemm 的延伸算子,PPQ 使用数值追踪技术标记这些算子,这些算子处理的运算一定是 input, conv, gemm 的计算结果。它们被调度到设备端以 int8 精度执行。

  • 争议区:这区域的算子同时接收来自不可量化区以及可量化区的输入,所有争议区的算子延伸也是争议算子,量化这些算子是有风险的,PPQ 不能保证量化产生的影响。该区算子被调度到设备端以浮点精度执行。

为了找出这些区域,PPQ 使用图搜索引擎进行区域划分,其基本思想是通过枚举所有算子的计算情况,确定输入的来源是否与 shape 或 index 相关。你可以通过 ppq.scheduler 中的代码看到它们的具体实现。

在 PPQ 中,我们实现了三种不同的调度逻辑,不同的调度逻辑将产生不同的区域划分:

  • 激进式调度:该调度方法将所有争议区算子视作可量化的。

  • 保守式调度:该调度方法将所有争议区算子视作不可量化的(它们依然将被调度到设备端)。

  • pplnn:该调度方法只量化卷积层与其相关算子。

量化中的图融合操作

硬件精度未对齐的主要原因在于 —— 推理库后端会对模型做大量的联合定点和图融合优化,我们写入的量化参数已被后端融合或修改,量化模拟与后端推理并不一致,导致优化算法大打折扣。

PPQ 使用 Tensor Quantization Config 类来描述算子数值量化的细节,其绑定在算子之上。

Executor 模块执行每一个算子时,并不会在模型中插入量化节点,而是通过一种类似于 hook 的形式,直接将量化操作添加到算子的执行逻辑中。模型算子输入/输出变量是否量化,由算子输入/输出的 Tensor Quantization Config 的 state 属性决定。

量化实践:以pytorch mobilenet v2 模型为例

首先按照官方教程安装ppq:ppq/quantize_torch_model.py at master · openppl-public/ppq · GitHub

我是使用 Install PPQ from source 方法安装的,直接安装会报错,可能是库之间相互依赖的问题,把requirements.txt文件中的onnx >= 1.9.0改成onnx == 1.9.0即可。

官方提供了一份完整的例子地址:ppq/quantize_torch_model.py at master · openppl-public/ppq · GitHub

打开文件openppl/ppq/ppq/samples运行脚本python quantize_torch_model.py,注意运行前新建一个文件夹Output存放量化后的模型。

源码阅读 

跑通了这个例子我们再来阅读一下源代码。

因为是静态离线量化,所以需要少量的校准数据,这里用随机生成的方法生成校准数据:

def load_calibration_dataset() -> Iterable:
    return [torch.rand(size=INPUT_SHAPE) for _ in range(32)]

加载pytorch内置的mobilenet V2模型,如果本地cache没有找到的话,会自动下载模型的配置和权重:

model = torchvision.models.mobilenet.mobilenet_v2(pretrained=True)
model = model.to(DEVICE)

PPL需要创建一个 QuantizationSetting 对象用来管理量化过程,这个是由QuantizationSettingFactory实现的:

# create a setting for quantizing your network with PPL CUDA.
quant_setting = QuantizationSettingFactory.pplcuda_setting()
quant_setting.equalization = True # use layerwise equalization algorithm.
quant_setting.dispatcher   = 'conservative' # dispatch this network in conservertive way.

这里设置了三项:

  • 用cuda设置
  • 采用分层均衡算法,这个貌似是这篇论文的,还没细看:https://hailo.ai/wp-content/uploads/2021/03/Exploring-Neural-Networks-Quantizationvia-Layer-Wise-Quantization-Analysis.pdf
  • 以保守的方式调度这个网络,将所有争议区算子视作不可量化的

ppq针对torch模型都封装起来了,只需要调用quantize_torch_model()即可。如果是onnx模型,需要手动自建图调度,最后一样都要使用export_ppq_graph()导出计算图。

# quantize your model.
quantized = quantize_torch_model(
    model=model, calib_dataloader=calibration_dataloader,
    calib_steps=32, input_shape=[BATCHSIZE] + INPUT_SHAPE,
    setting=quant_setting, collate_fn=collate_fn, platform=PLATFORM,
    onnx_export_file='Output/onnx.model', device=DEVICE, verbose=0)

# Quantization Result is a PPQ BaseGraph instance.
assert isinstance(quantized, BaseGraph)

# export quantized graph.
export_ppq_graph(graph=quantized, platform=PLATFORM,
                 graph_save_to='Output/quantized(onnx).onnx',
                 config_save_to='Output/quantized(onnx).json')

torch模型和onnx量化过程中的区别

onnx模型会直接调用quantize_onnx_model(),torch模型会调用quantize_onnx_model(),这个函数会先执行torch转onnx操作,然后再调用quantize_onnx_model():

@ empty_ppq_cache
def quantize_torch_model(
    model: torch.nn.Module,
    calib_dataloader: DataLoader,
    calib_steps: int,
    input_shape: List[int],
    platform: TargetPlatform,
    input_dtype: torch.dtype = torch.float,
    setting: QuantizationSetting = None,
    collate_fn: Callable = None,
    inputs: List[Any] = None,
    do_quantize: bool = True,
    onnx_export_file: str = 'onnx.model',
    device: str = 'cuda',
    verbose: int = 0,
    ) -> BaseGraph:
    """量化一个 Pytorch 原生的模型 输入一个 torch.nn.Module 返回一个量化后的 PPQ.IR.BaseGraph.
        quantize a pytorch model, input pytorch model and return quantized ppq IR graph
    Args:
        model (torch.nn.Module): 被量化的 torch 模型(torch.nn.Module) the pytorch model
        calib_dataloader (DataLoader): 校准数据集 calibration dataloader
        calib_steps (int): 校准步数 calibration steps
        collate_fn (Callable): 校准数据的预处理函数 batch collate func for preprocessing
        input_shape (List[int]): 模型输入尺寸,用于执行 jit.trace,对于动态尺寸的模型,输入一个模型可接受的尺寸即可。
            如果模型存在多个输入,则需要使用 inputs 变量进行传参,此项设置为 None
                                a list of ints indicating size of input, for multiple inputs, please use
                                keyword arg inputs for direct parameter passing and this should be set to None
        input_dtype (torch.dtype): 模型输入数据类型,如果模型存在多个输入,则需要使用 inputs 变量进行传参,此项设置为 None
                                the torch datatype of input, for multiple inputs, please use keyword arg inputs
                                for direct parameter passing and this should be set to None
        setting (OptimSetting): 量化配置信息,用于配置量化的各项参数,设置为 None 时加载默认参数。
                                Quantization setting, default setting will be used when set None
        inputs (List[Any], optional): 对于存在多个输入的模型,在Inputs中直接指定一个输入List,从而完成模型的tracing。
                                for multiple inputs, please give the specified inputs directly in the form of
                                a list of arrays
        do_quantize (Bool, optional): 是否执行量化 whether to quantize the model, defaults to True, defaults to True.
        platform (TargetPlatform, optional): 量化的目标平台 target backend platform, defaults to TargetPlatform.DSP_INT8.
        device (str, optional): 量化过程的执行设备 execution device, defaults to 'cuda'.
        verbose (int, optional): 是否打印详细信息 whether to print details, defaults to 0.
    Raises:
        ValueError: 给定平台不可量化 the given platform doesn't support quantization
        KeyError: 给定平台不被支持 the given platform is not supported yet
    Returns:
        BaseGraph: 量化后的IR,包含了后端量化所需的全部信息
                   The quantized IR, containing all information needed for backend execution
    """
    # dump pytorch model to onnx
    dump_torch_to_onnx(model=model, onnx_export_file=onnx_export_file,
        input_shape=input_shape, input_dtype=input_dtype,
        inputs=inputs, device=device)

    return quantize_onnx_model(onnx_import_file=onnx_export_file,
        calib_dataloader=calib_dataloader, calib_steps=calib_steps, collate_fn=collate_fn,
        input_shape=input_shape, input_dtype=input_dtype, inputs=inputs, setting=setting,
        platform=platform, device=device, verbose=verbose, do_quantize=do_quantize)

返回的都是一个量化IR(中间表示),根据这个中间表示再去保存我们所需要的信息。

后记

openppl的中文文档和教程非常完善,堪比paddle,适合基于此学习模型量化。本篇博客是第一篇,大致了解了ppq的设计思想、框架结构,并通过一个简单的例子实践感受。后续的博客会继续探索openppl模型量化!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/185102.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++Morris遍历

一、关于Morris算法 简介 Morris算法是针对二叉树实现的一个遍历算法,它是一种空间复杂度为O(1)的遍历算法 通常情况下使用迭代或递归的方式遍历二叉树的空间开销都是O(N)级别的,较为理想的情况下可以做到O(logn)级别,而Morris算法通过更改…

Windows Kerberos客户端配置并访问CDH

安装 Kerberos 客户端 配置 hosts 1、配置集群 hosts 到 Windows(C:\Windows\System32\drivers\etc\hosts); 2、调整windows环境变量,将系统环境变量 PATH 中的 C:\Program Files\MIT\Kerberos\bin 放置在最前边,建…

目标跟踪心得篇五:MOT数据集标注、DarkLabel不能自动跟踪解决方案

跟踪方向的标注成本非常很大的 ,那么我们如何尽可能一次性弄好呢? 所选标注工具:DarkLabel DarkLabel是一个轻量的视频标注软件,尤其做MOT任务非常友好,其标注可以通过脚本转化为标准的目标检测数据集格式、ReID数据集格式和MOT数据集格式。 使用之前: darklabel.yml:保…

Python国际化学习教程

很幸运python提供了中文等其他语言的教程! 这里以13.11.1为例 Python 是一门易于学习、功能强大的编程语言。它提供了高效的高级数据结构,还能简单有效地面向对象编程。Python 优雅的语法和动态类型以及解释型语言的本质,使它成为多数平台上写…

nacos的本地配置与启动步骤及NoDataSourceset问题解决

文章前提是本地机器已经安装好了mysql,配置好了mysql与Java环境变量。 首先在网络上找到一个nacos-server包。我本想上传自己的包,但是总是提示资源已经存在,那么可以自行搜索下。 解压开后是个.gz的文件,可以用windows自带的Win…

Golang Profiling - pprof 使用

Golang Profiling - pprof 使用 在编写大型应用程序,处理复杂业务与逻辑时,开发者经常面临系统内存泄漏问题。查找代码是否有效运行的一种有效方法是检查内存堆、CPU、磁盘的使用情况。 要在运行时检查Go应用程序的CPU和内存使用情况以及其他配置文件&…

LeetCode | 704. 二分查找

题:力扣 704. 二分查找 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target , 写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。示例 1:输入: nums [-1,0,3,5,9,1…

Lua 调试(Debug)

Lua 调试(Debug) 参考至菜鸟教程。 Lua 提供了 debug 库用于提供创建我们自定义调试器的功能。Lua 本身并未有内置的调试器,但很多开发者共享了他们的 Lua 调试器代码。 Lua 中 debug 库包含以下函数: 序号方法 & 用途1.debug():进入一个用户交互模…

2022年前端React杂记

以下记录的是,我在学习中的一些学习笔记,这篇笔记是自己学习的学习大杂烩,主要用于记录,方便查找1、学习概述React 是当下最火的前端三大框架之一。之前一直没有时间来学习,国庆得空来快速消掉这一块的盲点。学习react…

python 字典 小白笔记

字典反映的是一种映射关系。1.定义用花括号括起来,每个元素包括键值对,键必须是可以用哈希值计算的对象,通常是数字或者字符串。值可以是任何类型的对象。键和值之间用“:”分割。zidian{jian:zhi,jian1:zhi,}2.查找字典可以用键获…

python采集某所有数据,从此不用money

前言 大家早好、午好、晚好吖 ❤ ~ 基本思路流程: <通用的> 一. 数据来源分析: 明确需求: 明确采集的网站是什么? 明确采集的数据是什么? 通过开发者工具<浏览器自带的工具(谷歌浏览器)>, 进行抓包分析 先分析一章内容, 然后再分析如何采集多章内容 打开开发…

ubuntu docker 安装rocketmq记录

安装链接参考该博客 上面的是非ubuntu安装的docker&#xff0c;下面记录ubuntu安装docker遇到的问题及解决 1 创建挂载目录 ─── rocketmq├── conf│ └── broker.conf└── data├── broker│ ├── logs│ └── store└── namesrv├── logs└── st…

ch1_3计算机硬件的技术指标

机器字长&#xff1a; CPU一次能够处理 数据的位数&#xff1b; 与CPU中的寄存器位数有关&#xff1b; 1. 运算速度 不同的指令&#xff0c;执行的频率不同&#xff1b; 部分指令&#xff0c; 执行起来很慢&#xff0c; 但是很少执行&#xff0c;出现的次数低&#xff0c;对…

gf-v1项目结构及目录说明

文章目录1. gf版本2. 项目结构3. 目录说明1. gf版本 2. 项目结构 / ├── app │ ├── common │ │ ├── adapter │ │ ├── api │ │ ├── dao │ │ ├── service │ │ ├── model │ │ ├── … │ ├── system │ │ ├── api │ │ ├── dao │…

MySQL的14个小技巧

我最近几年用MYSQL数据库挺多的&#xff0c;发现了一些非常有用的小玩意&#xff0c;今天拿出来分享到大家&#xff0c;希望对你会有所帮助。 1.group_concat 在我们平常的工作中&#xff0c;使用group by进行分组的场景&#xff0c;是非常多的。 比如想统计出用户表中&…

C++ 设计模式 外观模式 The Facade Pattern

C 设计模式 外观模式 The Facade Pattern 介绍 Facade Pattern 为一组复杂的子系统提供了一个统一的简单接口&#xff0c;它是一种结构型设计模式。 它隐藏了子系统的复杂性&#xff0c;并向客户端提供了一个简单的接口来访问子系统。通过使用 Facade 模式&#xff0c;客户端…

Tag和Untag相关知识科普

欢迎来到东用知识小课堂&#xff01;端口的出和入是针对交换机而言的&#xff0c;即数据帧进入交换机即为进入某个端口。接下来我们就以PEC系列工业级交换机为例&#xff0c;来给大家详细讲解一下1.Access&#xff1a;接入链路&#xff1a;1&#xff09;.入方向&#xff1a;收到…

c++11 标准模板(STL)(std::forward_list)(十三)

定义于头文件 <forward_list> template< class T, class Allocator std::allocator<T> > class forward_list;(1)(C11 起)namespace pmr { template <class T> using forward_list std::forward_list<T, std::pmr::polymorphic_…

湖仓一体电商项目(一):项目背景和架构介绍

文章目录 项目背景和架构介绍 一、项目背景介绍

vue iframe展示pdf请求接口

<iframe:src"pdfUrl"style"border: none; width: 100%; height: calc(100% - 10px)"frameborder"0">iframe是一个非常好用的标签&#xff0c;用于文件的展示src地址可以一个访问后端的一个地址&#xff08;https://mp.csdn.net/mp_blog/cr…