OpenPPL PPQ量化(5):执行引擎 源码剖析
目录
PPQ Graph Executor(PPQ 执行引擎)
PPQ Backend Functions(PPQ 算子库)
PPQ Executor(PPQ 执行引擎)
Quantize Delegate (量化代理函数)
Usage (用法示例)
Hook (执行钩子函数)
前面四篇博客其实就讲了下面两行代码:
ppq_ir = load_onnx_graph(onnx_import_file=onnx_import_file)ppq_ir = dispatch_graph(graph=ppq_ir, platform=platform, dispatcher=setting.dispatcher, dispatching_table=setting.dispatching_table)
量化部分的精髓才刚刚开始,但是别急,咱先来点轻松的哈。这一讲看看执行引擎executor是如何实现的。虽然这部分和量化原理关系不大,但是学一学总没有坏处呀!
感动哭了,PPQ的中文注释也写得太详细了吧,这就看看官方注释,让博主水一篇博客吧哈哈哈
PPQ Graph Executor(PPQ 执行引擎)
为了量化并优化神经网络模型,PPQ 实现了基于 Pytorch 的执行引擎,该执行引擎能够执行 Onnx 与 Caffe 的模型文件,目前支持 90 余种常见 Onnx 算子,涵盖 1d, 2d, 3d 视觉、语音、文本模型。
PPQ 的执行引擎位于 ppq.executor 目录下,由两个主要部分组成: ppq.executor.torch.py 文件中包含了执行引擎自身; ppq.executor.op 文件夹中则包含了不同后端的算子库。
在开始阅理解执行引擎之前,我们先介绍算子库的相关内容
PPQ Backend Functions(PPQ 算子库)
核心算子库位于 ppq.executor.op.torch.default 文件中,该文件中包含了所有算子默认的执行逻辑。
我们知道,对于一个量化算子而言,由于硬件的不同其执行逻辑也可能发生变化。例如 LeakyRelu 算子的负数部分在 GPU 上会采用 x * alpha 的方式完成计算,而在 FPGA 则会采用 x = x >> 3 完成计算。正因为这种差异的存在, PPQ 允许相同的算子在不同平台(TargetPlatform)上拥有不同的执行逻辑。
这也意味着针对每一个平台,我们都将实现一个平台独特的算子库文件,这些算子库都继承于 ppq.executor.op.torch.default。
def Mul_forward(op: Operation, values: List[torch.Tensor], ctx: TorchBackendContext = None, **kwargs) -> torch.Tensor:ASSERT_NUM_OF_INPUT(op=op, values=values, min_num_of_input=2, max_num_of_input=2)values = VALUE_TO_EXECUTING_DEVICE(op=op, ctx=ctx, values=values)multiplicand, multiplier = valuesreturn multiplicand * multiplier
上文中的内容即 ppq.executor.op.torch.default 中 Mul 算子的执行逻辑,在 PPQ 中,所有算子在执行时都将接受一系列 torch.Tensor 作为输入,而后我们调用 pytorch 完成算子的计算逻辑。
你可以打开 PPQ 的算子库文件查看其他算子的执行逻辑,并且 PPQ 也提供了 register_operation_handler 函数,借助该函数你可以注册自定义算子的执行逻辑;或是覆盖现有算子的执行逻辑。
def register_operation_handler(handler: Callable, operation_type: str, platform: TargetPlatform):if platform not in GLOBAL_DISPATCHING_TABLE:raise ValueError('Unknown Platform detected, Please check your platform setting.')GLOBAL_DISPATCHING_TABLE[platform][operation_type] = handler
该函数位于 ppq.api, 你可以使用语句 from ppq.api import register_operation_handler 来引入它。
PPQ Executor(PPQ 执行引擎)
接下来我们向你介绍 PPQ 执行引擎 TorchExecutor,你可以使用语句 from ppq import TorchExecutor 导入执行引擎。初始化执行引擎则需要传入一个 PPQ 计算图实例对象,
在这里我们假设已经获取到了一个量化后的计算图对象 ppq_quant_ir,并使用下面的语句初始化计算引擎
executor = TorchExecutor(graph=ppq_quant_ir)executor.forward(inputs=..., output_names=..., hooks=...)
我们使用 executor.forward 接口获取图的执行结果,它将可以传入三个参数:
- inputs: inputs (Union[dict, list, torch.Tensor]): [input tensors or somewhat]
- output_names (List[str], optional): output variable names. default is None.
- hooks (Dict[str, RuntimeHook], optional): A hook table for customizing operation behaviour and collate data during executing.
当执行引擎获取到推理请求时,它将按拓扑顺序依次执行图中的算子,下图展示了一个简单的示例:
在这里,我们的图中包含三个算子 Conv, Relu, Softmax,他们将按照拓扑次序被依次执行。PPQ 的执行引擎会在执行完 Conv 算子后,将 Conv 算子的结果暂存于 Var 1 中,供 Relu 算子取用。
而在执行完 Relu 算子后,PPQ 执行引擎则会及时地释放 Var 1 中暂存的数据,因为他们不会被其他算子取用,而且也不是网络的输出 Variable。在每一次推理过后,PPQ 还会清空网络中所有的暂存变量以释放显存。
下面的代码段展示了一个非量化算子的执行逻辑:
for operation in executing_order:outputs = operation_forward_func(operation, inputs, self._executing_context)outputs = outputs if isinstance(outputs, (list, tuple)) else [outputs]fp_outputs = outputsfor output_idx, output_var in enumerate(operation.outputs):output_var = operation.outputs[output_idx]output_var.value = outputs[output_idx]for var in self._graph.variables.values():if not var.is_parameter:var.value = None
PPQ 的执行引擎是专为量化计算图的执行而设计的————接下来让我们深入到量化算子的执行过程中去。
对于一个量化算子而言,其每一个输入和输出变量都会有一个 Tensor Quantization Config (TQC) 控制结构体对量化过程进行描述。
对于一个量化 Conv 算子而言,PPQ 将为它创建 2-3 个 Input TQC,以及一个 Output TQC。分别对其输入变量以及输出变量的量化行为进行描述。
下面的代码展示了如何为量化算子创建特定的 TQC 描述量化逻辑。
if operation.type == 'Conv':config = self.create_default_quant_config(op = operation,num_of_bits = 8,quant_max = 127,quant_min = -128,observer_algorithm = 'percentile',policy = QuantizationPolicy(QuantizationProperty.PER_TENSOR +QuantizationProperty.LINEAR +QuantizationProperty.SYMMETRICAL),rounding = RoundingPolicy.ROUND_HALF_EVEN)for tensor_quant_config in config.input_quantization_config:tensor_quant_config.state = QuantizationStates.FP32operation.config = config
在 图2 中,我们展示了 PPQ 执行引擎对于量化算子的执行逻辑:
在 PPQ 中,算子的执行被分为四个过程:
- 首先 PPQ 将根据算子上的 TQC 信息量化算子的输入。量化过程并非是原地的,量化后的数据将会是一个新的 torch.Tensor。
- 随后 PPQ 在算子库中寻找算子的执行逻辑,我们已经提到对于每一个平台,他们都可以拥有自己的一套独立的算子库。PPQ 将按照算子的平台找到特定的计算逻辑,并调用他们完成计算得到结果。
- PPQ 将根据算子上的 TQC 信息量化算子的输出。同样地,输出的量化也不是原地的。
- 最后我们将量化好的结果写入到计算图的 Variable 上,从而供后续的算子取用。
对于一个非量化算子而言,上述步骤中的 1,3 是可以省略的。
下 图3 展示了一个量化卷积算子 与 TQC 之间的关系:
Quantize Delegate (量化代理函数)
PPQ 允许你为网络中特定的 TQC 注册量化代理函数。这样你就可以注册自定义的量化处理逻辑,而非使用 PPQLinearQuantFunction 完成量化。
def register_quantize_delegate(self, config: TensorQuantizationConfig,delegator: TorchQuantizeDelegator):
使用 executor.register_quantize_delegate(config, function) 完成函数注册,被注册的函数必须满足 TorchQuantizeDelegator 所定义的接口。
下面我们给出一个简单的量化代理函数例子:
class MyQuantDelegator(TorchQuantizeDelegator):def __call__(self, tensor: torch.Tensor, config: TensorQuantizationConfig) -> torch.Tensor:if config.policy.has_property(QuantizationProperty.ASYMMETRICAL):raise ValueError('Sorry, this delegator handles only Symmetrical Quantizations.')print('You are invoking cusitmized quant function now.')return torch.round(tensor / config.scale) * config.scale
在执行器遇到 TQC 时,将会调用 executor.quantize_function 执行量化,其逻辑为:
def quantize_function(self, tensor: torch.Tensor, config: TensorQuantizationConfig = None) -> torch.Tensor:if config is None or not QuantizationStates.is_activated(config.state): return tensorelif config in self._delegates: return self._delegates[config](tensor, config)else:if config.policy.has_property(QuantizationProperty.DYNAMIC):return self._dynamic_quant_fn(tensor, config)else:return self._default_quant_fn(tensor, config)
Usage (用法示例)
PPQ 的执行器初始化需要一个计算图实例作为参数:
executor = TorchExecutor(graph=ppq_quant_ir)executor.forward(inputs=..., output_names=..., hooks=...)
这一计算图可以是量化过后的,也可以是没有量化的。但 PPQ 希望传入的计算图经过正确调度,传入没有调度的计算图将会触发警报:
if not graph.extension_attrib.get(IS_DISPATCHED_GRAPH, False):ppq_warning('Can not create executor with your graph, graph is not correctly dispatched, ''use dispatch_graph(graph=ir, platform=platfrom, setting=setting) first.')
executor.forward 需要三个参数,下面举例对其进行说明:
# 传入三个变量 a, b, c 作为输入executor.forward(inputs=[a, b, c], output_names=..., hooks=...)# 分别对图中 input, var 1 两个变量传入 a, b 作为输入executor.forward(inputs={'input': a, 'var 1': b}, output_names=..., hooks=...) # 传入一个完整的 tensor 作为输入executor.forward(inputs=torch.zeros(shape=[1,3,224,224]), output_names=..., hooks=...)# 要求网络输出 output, Var 1 的值executor.forward(inputs=..., output_names=['output 1', 'Var 1'], hooks=...)
executor.forward 函数默认不需要梯度,如果希望执行带有梯度的网络,需要使用 executor.forward_with_gradient 函数。 forward 函数的返回值永远是一个 torch.Tensor 数组,其中元素的顺序由 output_names 参数决定。
Hook (执行钩子函数)
在调用 executor.forward 函数时可以传入 hooks 参数。钩子函数是注册在 op 上的,你可以传入一个字典用来说明需要调用的钩子函数:
字典 {'Conv 1': myhook} 说明了希望在算子 Conv 1 的执行器件调用钩子函数 myhook。
钩子函数必须继承于 RuntimeHook 类,必须实现成员函数 pre_forward_hook, post_forward_hook。在这两个函数中,你可以制定特定的逻辑修改算子输入输出的值。
class RuntimeHook(metaclass=ABCMeta):def __init__(self, operation: Operation) -> None:self._hook_to = operationdef pre_forward_hook(self, inputs: list, **kwargs) -> list:return inputsdef post_forward_hook(self, outputs: list, **kwargs) -> list:return outputs
TorchExecutor - executor object which use torch as its backend.
torch backend is used to graph simulating & training(QAT)
all operation forward functions are written with pytorch,so that they will have gradient recorded by torch engine. which means you can directly access to tensor.grad after using output.backward()
Args:
graph (BaseGraph):
executing graph object,
TorchExecutor will automatically send all graph parameters towards executing device.
fp16_mode (bool, optional): [whether the simulator is running in fp16 mode(unimplemented).]. Defaults to True.
device (str, optional): [
executing device, as same as torch.device,
you can not select gpu to executing yet,
graph will always be send to the very first visible cuda device.
]. Defaults to 'cuda'.
相关文章:
OpenPPL PPQ量化(5):执行引擎 源码剖析
目录 PPQ Graph Executor(PPQ 执行引擎) PPQ Backend Functions(PPQ 算子库) PPQ Executor(PPQ 执行引擎) Quantize Delegate (量化代理函数) Usage (用法示例) Hook (执行钩子函数) 前面四篇博客其实就讲了下面两行代码: ppq_ir load_onnx_graph(onnx_impor…...
【脚本开发】运维人员必备技能图谱
脚本(Script)语言是一种动态的、解释性的语言,依据一定的格式编写的可执行文件,又称作宏或批处理文件。脚本语言具有小巧便捷、快速开发的特点;常见的脚本语言有Windows批处理脚本bat、Linux脚本语言shell以及python、…...
N字形变换-力扣6-java
一、题目描述将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下:P A H NA P L S I I GY I R之后,你的输出需要从左往右逐行读…...
概论_第5章_中心极限定理1__定理2(棣莫弗-拉普拉斯中心极限定理)
在概率论中, 把有关论证随机变量和的极限分布为正态分布的一类定理称为中心极限定理称为中心极限定理称为中心极限定理。 本文介绍独立同分布序列的中心极限定理。 一 独立同分布序列的中心极限定理 定理1 设X1,X2,...Xn,...X_1, X_2, ...X_n,...X1,X2,...Xn…...
详细解读503服务不可用的错误以及如何解决503服务不可用
文章目录1. 问题引言2. 什么是503服务不可用错误3 尝试解决问题3.1 重新加载页面3.2 检查该站点是否为其他人关闭3.3 重新启动设备3.3 联系网站4. 其他解决问的方法1. 问题引言 你以前遇到过错误503吗? 例如,您可能会收到消息,如503服务不可…...
【前端vue2面试题】2023前端最新版vue模块,高频17问(上)
🥳博 主:初映CY的前说(前端领域) 🌞个人信条:想要变成得到,中间还有做到! 🤘本文核心:博主收集的关于vue2面试题(上) 目录 vue2面试题 1、$route 和 $router的区别 2、一个…...
数据库(三):多版本并发控制MVCC,行锁的衍生版本,记录锁,间隙锁, Next-Key锁(邻键锁)
文章目录前言一、MVCC以及MVCC的缺点1.1 MVCC可以为数据库解决什么问题1.2 MVCC的基本思想1.3 版本号1.4 Undo日志1.5 ReadView1.6 快照读和当前读1.6.1 快照读1.6.2 当前读二、记录锁三、间隙锁四、邻键锁总结前言 一、MVCC以及MVCC的缺点 MVCC,即多版本并发控制…...
c# 自定义隐式转换与运算符重载
用户定义的显式和隐式转换运算符 参考代码 用户定义的显式和隐式转换运算符 - 提供对不同类型的转换 | Microsoft Learn 代码例程 using System;public readonly struct Digit {private readonly byte digit;public Digit(byte digit){if (digit > 9){throw new Argumen…...
【MyBatis】| MyBatis的逆向⼯程
目录 一:MyBatis的逆向⼯程 1. 逆向⼯程配置与⽣成 2. 测试生成的逆向⼯程 一:MyBatis的逆向⼯程 (1)所谓的逆向⼯程是:根据数据库表逆向⽣成Java的pojo类,SqlMapper.xml⽂件,以及Mapper接⼝…...
Python|每日一练|哈希表|罗马数字|图算法|圆周率|单选记录:给定数列和|罗马数字转整数|计算圆周率
1、要求编写函数fn(a,n) 求aaaaaa⋯aa⋯aa(n个a)之和,fn须返回的是数列和(算法初阶) 要求编写函数fn(a,n) 求aaaaaa⋯aa⋯aa(n个a)之和,fn须返回的是数列和。 从控制台输入正整数a和n的值(两…...
分布式之分布式事务V2
写在前面 本文一起来看下分布式环境下的事务问题,即我们经常听到的分布式事务问题。想要解决分布式事务问题,需要使用到分布式事务相关的协议,主要有2PC即两阶段提交协议,TCC(try-confirm-cancel)…...
算法笔记(二)—— 认识N(logN)的排序算法
递归行为的时间复杂度估算 整个递归过程是一棵多叉树,递归过程相当于利用栈做了一次后序遍历。 对于master公式,T(N)表明母问题的规模为N,T(N/b)表明每次子问题的规模,a为调用次数,加号后面表明,除去调用之…...
最长湍流子数组——滚动窗口,双指针,暴力求解
978. 最长湍流子数组难度中等216收藏分享切换为英文接收动态反馈给定一个整数数组 arr ,返回 arr 的 最大湍流子数组的长度 。如果比较符号在子数组中的每个相邻元素对之间翻转,则该子数组是 湍流子数组 。更正式地来说,当 arr 的子数组 A[i]…...
45.在ROS中实现global planner(1)
前文move_base介绍(4)简单介绍move_base的全局路径规划配置,接下来我们自己实现一个全局的路径规划 1. move_base规划配置 ROS1的move_base可以配置选取不同的global planner和local planner, 默认move_base.cpp#L70中可以看到是…...
Java中导入、导出Excel——HSSFWorkbook 使用
一、介绍 当前B/S模式已成为应用开发的主流,而在企业办公系统中,常常有客户这样子要求:你要把我们的报表直接用Excel打开(电信系统、银行系统)。或者是:我们已经习惯用Excel打印。这样在我们实际的开发中,很多时候需要…...
c#数据结构-列表
列表 数组可以管理大量数组,但缺点是无法更变容量。 创建小了不够用,创建大了浪费空间。 无法预测需要多少大小的时候,可能范围越大,就会浪费越多的空间。 所以,你可能会想要一种可以扩容的东西,代替数组…...
Sa-Token实现分布式登录鉴权(Redis集成 前后端分离)
文章目录1. Sa-Token 介绍2. 登录认证2.1 登录与注销2.2 会话查询2.3 Token 查询3. 权限认证3.1 获取当前账号权限码集合3.2 权限校验3.3 角色校验4. 前后台分离(无Cookie模式)5. Sa-Token 集成 Redis6. SpringBoot 集成 Sa-Token6.1 创建项目6.2 添加依…...
leaflet显示高程
很多地图软件都能随鼠标移动动态显示高程。这里介绍一种方法,我所得出的。1 下载高程数据一般有12.5m数据下载,可惜精度根本不够,比如mapbox的免费在线的,或者91卫图提供百度网盘打包下载的,没法用,差距太大…...
电子学会2022年12月青少年软件编程(图形化)等级考试试卷(三级)答案解析
目录 一、单选题(共25题,共50分) 二、判断题(共10题,共20分) 三、编程题(共3题,共30分) 青少年软件编程(图形化)等级考试试卷(三级) 一、单选题(共25题,共50分) 1. 默认小猫角色…...
ubuntu 驱动更新后导致无法进入界面
**问题描述: **安装新ubuntu系统后未禁止驱动更新导致无法进入登录界面。 解决办法: 首先在进入BIOS中,修改设置以进行命令行操作,然后卸载已有的系统驱动,最后安装新的驱动即可。 开机按F11进入启动菜单栏…...
解决访问GitHub时出现的“您的连接不是私密连接”的问题!
Content问题描述解决办法问题描述 访问github出现您的连接不是私密连接问题,无法正常访问,如下图所示: 解决办法 修改hosts文件。hosts文件位于:C:\Windows\System32\drivers\etc\hosts 首先在https://www.ipaddress.com/查找两…...
初识数据仓库
一、什么是数据仓库数据库 --> OLTP:(on-line transaction processing)翻译为联机事务处理记录某类业务事件的发生,如购买行为,银行交易行为,当行为产生后,系统会记录是谁在何时何地做了何事…...
FilenameUtils工具类部分源码自研
FilenameUtils工具类部分源码自研getExtension(orgFileName)源码如下逐行分析getExtension(orgFileName)源码如下 public class FilenameUtils {public static int indexOfExtension(String fileName) throws IllegalArgumentException {if (fileName null) {return -1;} els…...
【前端领域】3D旋转超美相册(HTML+CSS)
世界上总有一半人不理解另一半人的快乐。 ——《爱玛》 目录 一、前言 二、本期作品介绍 3D旋转相册 三、效果展示 四、详细介绍 五、编码实现 index.html style.css img 六、获取源码 公众号获取源码 获取源码?私信?关注?点赞&…...
Java——聊聊JUC中的原子变量类
文章目录: 1.什么是原子变量类? 2.AtomicInteger(基本类型原子变量类) 3.AtomicIntegerArray(数组类型原子变量类) 4.AtomicMarkableReference(引用类型原子变量类) 5.AtomicInteger…...
elasticsearch索引与搜索初步
ES支持cURL交互,使用http请求完成索引和搜索操作,最基本的格式如下:创建索引我们可以使用PUT方法创建索引,通过指定“索引”、“类型”、“文档ID”锁定文档,通过参数指定文档的数据。红色部分的路由分别指定了“索引”…...
【Python】多线程与多进程学习笔记
本文是一篇学习笔记,学习内容主要来源于莫凡python的文档:https://mofanpy.com/tutorials/python-basic/threading/thread 多线程 线程基本结构 开启子线程的简单方式如下: import threadingdef thread_job():print(This is a thread of %…...
MySQL基础知识点
1.在Linux上安装好MySQL8.0之后,默认数据目录的具体位置是什么?该目录下都保存哪些数据库组件?在目录/usr/sbin、/usr/bin、/etc、/var/log 分别保存哪些组件? 答:默认数据目录:/var/lib/mysql。保存有mysq…...
代码随想录算法训练营第五十九天| 583. 两个字符串的删除操作、72. 编辑距离
Leetcode - 583dp[i][j]代表以i-1结尾的words1的子串 要变成以j-1结尾的words2的子串所需要的次数。初始化: "" 变成"" 所需0次 dp[0][0] 0, ""变成words2的子串 需要子串的长度的次数,所以dp[0][j] j, 同理,dp[i][0] …...
指针引用字符串问题(详解)
通过指针引用字符串可以更加方便灵活的使用字符串。 字符串的引用方式有两种,下面简单介绍一下这两种方法。 1.用字符数组来存放一个字符串。 1.1 可以通过数组名和下标来引用字符串中的一个字符。 1.2 还可以通过数组名和格式声明符%s输出整个字符串。 具体实…...
门户网站模式/海外营销方案
一、安装 安装已编译版本(此方法简便快捷): http://www.codegood.com/downloads 根据自己系统下载,双击安装,搞定 然后import MySQLdb,查看是否成功 我的,win7,32位,2.7版本 MySQL-python-1.2.3.win-amd32-py2.7.…...
东莞php网站建设/百度识图在线识别网页版
我得到两个坐标对,形式为900′0″N 00′0″E作为字符串,并想计算半径R6371km的球体上这些点之间的距离。在我在网上找到了两个公式,哈弗辛公式和余弦球定律,但它们似乎不起作用。对于应返回2*pi*R / 4的90角,haversine…...
wordpress访问后台/目前最火的推广平台
编辑:业余草来源:https://www.xttblog.com/?p4978世上无难事,只怕有心人!今天我们来说说线程池!线程池简介使用线程池,一般会使用JDK提供的几种封装类型,即:newFixedThreadPool、ne…...
便民工具 网站开发/软文写作范文500字
双边滤波很有名,使用广泛,简单的说就是一种同时考虑了像素空间差异与强度差异的滤波器,因此具有保持图像边缘的特性。 先看看我们熟悉的高斯滤波器 其中W是权重,i和j是像素索引,K是归一化常量。公式中可以看出…...
寻找网站开发/百度热搜大数据
函数返回值 多条return语句: defguess(x):if x > 3:return "> 3" else:return "< 3" defshowplus(x):print(x)return x 1 return x 2 #执行结果 10 11 #从结果来看 出现第一个return后 下面的就不会执行 deffn(x):for i inrange(x):…...
wordpress常量/百度公司的业务范围
让我们在命令行里面复制很多执行代码的时候,我们执行后会 发现我们的命令行的双箭头指示没有了,没有了也就是意味着 很多的命令我们不能去实现,那么怎么办呢? 我么这个时候可以按下Ctrlc 组合按键 就可以看到了。...