TenserRT(四)在 PYTORCH 中支持更多 ONNX 算子
第四章:在 PyTorch 中支持更多 ONNX 算子 — mmdeploy 0.12.0 文档
PyTorch扩充。
PyTorch转换成ONNX:
- PyTorch有实现。
- PyTorch可以转化成一个或者多个ONNX算子。
- ONNX有相应算子。
如果即没有PyTorch实现,且缺少PyTorch与ONNX的映射关系,则需要:
- Pytorch算子
- 组合现有算子
- 添加TorchScript算子
- 添加普通C++拓展算子
- 映射方法
- 为ATen算子添加符号函数
- 为TorchScript算子添加符号函数
- 封装成torch.autograd.Function并添加符号函数
- ONNX算子
- 使用现用ONNX算子
- 定义新ONNX算子
不同的情况需要灵活的选用和组合这些方法。
支持 ATen 算子
算子在ATen中已经实现,ONNX也有相关算子定义,但是相关算子映射成ONNX的规则没有写。为ATen算子补充描述映射规则的符号函数。
PyTorch C++ API — PyTorch master documentation ATen是PyTorch内置的C++张量计算库,PyTorch算子在底层绝大多数计算都是用ATen实现的。
例如ONNX的Asinh https://github.com/onnx/onnx/blob/main/docs/Operators.md#Asinh 算子在ATen中有实现,但缺少映射到ONNX算子的符号函数,则需要补全符号函数,并导出一个包含该算子的ONNX模型。
获取 ATen 中算子接口定义
torch/_C/_VariableFunctions.pyi和 torch/nn/functional.pyi 两个文件可以获取函数的输入定义,这两个文件是编译pytorch时自动生成的,里面包含了ATen算子的pytorch调用接口,在torch/_C/_VariableFunctions.pyi中搜索asinh接口为
def asinh(input: Tensor, *, out: Optional[Tensor]=None) -> Tensor: ...
缺失算子为asinh,在ATen中实现的算子,在_VariableFunctions.pyi找到对应接口,需要补充对应的符号函数,使其在转场ONNX时不在报错。
添加符号函数
符号函数,看成pytorch的静态方法,将pytroch转换成ONNX模型时,pytroch算子的符号函数将被依次调用,已完成Pytorch算子到ONNX算子的转换。
def symbolic(g: torch._C.Graph, input_0: torch._C.Value, input_1: torch._C.Value, ...):
torch._C.Graph 和 torch._C.Value对应Pytorch的C++实现里的一些类。第一个参数g,表示和计算图相关内容;后面的参数input是算子输入,需要和算子的前向推理接口输入已知。对于ATen算子来说就时两个.pyi文件里的函数接口。
g有一个op方法。把pytorc算子转换成ONNX算子时,需要在符号函数中调用此方法来最终计算投图添加一个ONNX算子
def op(name: str, input_0: torch._C.Value, input_1: torch._C.Value, ...)
name:算子名称,如ONNX算子名称。
简单情况,将pytorch算子的输入用g.op()一一对应到ONNX算子上,并把g.op()的返回值作为符号函数的返回值。复杂的情况,将一个pytorch算子新建为若干个ONNX算子。
from torch.onnx import register_custom_op_symbolic
def asinh_symbolic(g, input):return g.op("custom_domain::Asinh", input)register_custom_op_symbolic('custom_ops::asinh', asinh_symbolic, 9)
asinh_symbolic就是asinh符号函数,输入参数需要按照在ATen中的定义
def asinh(input: Tensor, *, out: Optional[Tensor]=None) -> Tensor: ...
符号函数的函数体重g.op("custom_domain::Asinh", input)完成了ONNX算子的定义,第一个参数custom_domain::Asinh是算子在ONNX中的名称,之于第二个参数input,这个算子只有一个输入,主需要把符号函数的输入参数input对应过去就可以了。ONNX的custom_domain::Asinh输出和ATen的asinh的输出一直,因此直接把g.op()结果返回即可。
使用pytorch API中register_op将富含函数和原来的ATen算子绑定在一起,
register_custom_op_symbolic('custom_ops::asinh', asinh_symbolic, 9)
第一个参数custom_ops::asinh是目标ATen算子名。
第二个参数asinh_symbolic是要注册的符号函数。
第三个参数9表示算子集注册。
import torch
from torch.onnx import register_custom_op_symbolic#创建一个简单的神经网络层,实现forward方法,
class Model(torch.nn.Module):def __init__(self):super().__init__()def forward(self, x):return torch.asinh(x)#计算反双曲正弦值def asinh_symbolic(g, input, *, out=None):#pytorch的计算图有节点Node和边edge组成,Node表示操作(加减乘除卷积)#边表示张量间数据流关系,一个Asinh的节点,以input作为输入,输出就是Asinh(input)return g.op("Asinh", input)#将Model中的asinh操作重新绑定为asinh_symbolic,重命名该节点为“Asinh”,使用9号算法集
register_custom_op_symbolic('aten::asinh', asinh_symbolic, 9)model = Model()
input = torch.rand(1, 3, 10, 10)
torch.onnx.export(model, input, 'asinh.onnx')
#总结,就是声明一个torch.asinh的操作,该操作通过register_custom_op_symbolic注册为,9号算法集中Asinh操作

import onnxruntime
import torch
import numpy as npclass Model(torch.nn.Module):def __init__(self):super().__init__()def forward(self, x):return torch.asinh(x)model = Model()
input = torch.rand(1, 3, 10, 10)
torch_output = model(input).detach().numpy()#torch做了一次推理,然后转成numpy格式sess = onnxruntime.InferenceSession('asinh.onnx')
ort_output = sess.run(None, {'onnx::Asinh_0': input.numpy()})[0]
#这里的名字要和onnx中图节点的名字一致啊
assert np.allclose(torch_output, ort_output)#判断torch值和onnxruntime值一致,assert断言返回值,allclose判断两个张量是否一致
支持torcscript算子
pytorch算子无法直接满足复杂实现,需要自定义一个pytorch算子,然后转成ONNX形式。
为算子添加符号函数:
1、获取原算子的前向推理接口。#forward
2、获取目标ONNX算子的定义。#https://github.com/onnx/onnx/blob/main/docs/Operators.md
3、编写符号函数并绑定。#asinh_symbolic,register_custom_op_symbolic
使用torchscript算子
import torch
import torchvision#定义一个包含算子的模型
class Model(torch.nn.Module):def __init__(self):super().__init__()self.conv1 = torch.nn.Conv2d(3, 18, 3)#作为形变卷积的偏移张量self.conv2 = torchvision.ops.DeformConv2d(3, 3, 3)#形变卷积def forward(self, x):return self.conv2(x, self.conv1(x))
自定义 ONNX 算子
#定义一个包含算子的模型
class Model(torch.nn.Module):def __init__(self):super().__init__()self.conv1 = torch.nn.Conv2d(3, 18, 3)#作为形变卷积的偏移张量self.conv2 = torchvision.ops.DeformConv2d(3, 3, 3)#形变卷积点https://pytorch.org/vision/stable/ops.htmldef forward(self, x):return self.conv2(x, self.conv1(x))
#@parse_args装饰器,torchscripp算子的符号函数要求标注出么米一个输入参数的数据类型,
#v表示torch库中的value类型,一般用于标注张量
#i表示int类型
#f表示float
#none表示该数据为空。
#可以在torch.onny.symbolic_helper.py中查看
@parse_args("v", "v", "v", "v", "v", "i", "i", "i", "i", "i", "i", "i", "i", "none")
def symbolic(g,input,#张量vweight,#张量voffset,#张量vmask,#张量vbias,#张量vstride_h, stride_w, #int ipad_h, pad_w,dil_h, dil_w,n_weight_grps,n_offset_grps,use_mask):#以查询到的DeformConv2d算子输入参数作为符号函数的输入#custom是命名空间,以区别官方的算子#只使用input和offset来构造ONNX算子return g.op("custom::deform_conv2d", input, offset)#只是简单的例子,如何定义一个onnx中的deform节点,所以不做具体实现。
from torch.onnx import register_custom_op_symbolic
register_custom_op_symbolic("torchvision::deform_conv2d", symbolic, 9)model = Model()
input = torch.rand(1, 3, 10, 10)
torch.onnx.export(model, input, 'dcn.onnx')

使用torch.autograd.Function
为 PyTorch 添加 C++ 拓展
//my_add.cpp
#include <torch/torch.h>torch::Tensor my_add(torch::Tensor a, torch::Tensor b)//torch::Tensor就是c++中torch张量
{return 2 * a + b;
}PYBIND11_MODULE(my_lib, m)//PYBIND11_MODULE为C++提供python调用接口。这里的my_lib是将来要在python中导入的模块名
{m.def("my_add", my_add);//my_add是python调用的接口名称,这里的接口名称与c++函数名称不一定要一样,但是这一命名辨识度比较高。
}
python setup.py develop
#编译文件
用 torch.autograd.Function 封装底层调用
import torch
import my_lib
#Function类本身是pytorch的一个可导函数,只需要实现前向推理和反向传播实现。
class MyAddFunction(torch.autograd.Function):#pytorch自动调用该函数,合适的执行前向和反向计算。@staticmethoddef forward(ctx, a, b):#forward函数中调用c++函数,my_lib是库名,my_add函数名,这两个名字是在#PYBIND11_MODULE中定义的。return my_lib.my_add(a, b)#对模型部署来说,function类有个很好的性质:如果定义了symbolic静态方法,#该function在执行torch.onnx.export()时就可以根据symbolic中定义的规则,#转换成ONNX算子,这个symbolic就是前面提到的符号函数,只是这里的名称必须是symbolic而已。@staticmethoddef symbolic(g, a, b):#g.op()只需要根据ONNX算子定义的规则把输入参数填入即可#ONNX中把新建常量当成一个算子来看待,尽管这个算子并不会以节点形式出现在ONNX模型的可视化结果里。two = g.op("Constant", value_t=torch.tensor([2]))#常量算子,把pytorch张量值传入value_t参数a = g.op('Mul', a, two)#乘法return g.op('Add', a, b)#加法
my_add = MyAddFunction.apply#apply是torch.autograd.Function的方法,这个方法完成了Function在前向推理或者反向传播的调度。
#在使用Function的派生类做推理时,不应该显示的调用forward,而应该调用apply方法。class MyAdd(torch.nn.Module):#把my_add封装成一个神经网络中的计算层。def __init__(self):super().__init__()def forward(self, a, b):return my_add(a, b)
测试算子
import torch
import my_lib
#Function类本身是pytorch的一个可导函数,只需要实现前向推理和反向传播实现。
class MyAddFunction(torch.autograd.Function):#pytorch自动调用该函数,合适的执行前向和反向计算。@staticmethoddef forward(ctx, a, b):#forward函数中调用c++函数,my_lib是库名,my_add函数名,这两个名字是在#PYBIND11_MODULE中定义的。return my_lib.my_add(a, b)#对模型部署来说,function类有个很好的性质:如果定义了symbolic静态方法,#该function在执行torch.onnx.export()时就可以根据symbolic中定义的规则,#转换成ONNX算子,这个symbolic就是前面提到的符号函数,只是这里的名称必须是symbolic而已。@staticmethoddef symbolic(g, a, b):#g.op()只需要根据ONNX算子定义的规则把输入参数填入即可#ONNX中把新建常量当成一个算子来看待,尽管这个算子并不会以节点形式出现在ONNX模型的可视化结果里。two = g.op("Constant", value_t=torch.tensor([2]))#常量算子,把pytorch张量值传入value_t参数a = g.op('Mul', a, two)#乘法return g.op('Add', a, b)#加法
my_add = MyAddFunction.apply#apply是torch.autograd.Function的方法,这个方法完成了Function在前向推理或者反向传播的调度。
#在使用Function的派生类做推理时,不应该显示的调用forward,而应该调用apply方法。class MyAdd(torch.nn.Module):#把my_add封装成一个神经网络中的计算层。def __init__(self):super().__init__()def forward(self, a, b):return my_add(a, b)model = MyAdd()
input = torch.rand(1, 3, 10, 10)
torch.onnx.export(model, (input, input), 'my_add.onnx')
torch_output = model(input, input).detach().numpy()import onnxruntime
import numpy as np
sess = onnxruntime.InferenceSession('my_add.onnx')
ort_output = sess.run(None, {'a.1': input.numpy(), 'b.1': input.numpy()})[0]assert np.allclose(torch_output, ort_output)

总结
- ATen是pytorch的C++张量库,~\Lib\site-packages\torch\_C\_VariableFunctions.pyi 和~\\Lib\site-packages\torch\nn可以知道ATen算子的python接口定义。
- register_op可以为ATen算子补充注册符号函数。
- register_custom_op_symbolic可以为TorchScript算子补充注册符号函数
- 在Pytorch里添加C++扩展,#include <torch/torch.h> 、PYBIND11_MODULE(my_lib, m)、setup
- torch.autograd.Function封装一个自定义的pytorch算子
- symbolic编写符号函数。
- g.op()把一个pytorch算子映射成一个或者多个ONNX算子。
相关文章:
TenserRT(四)在 PYTORCH 中支持更多 ONNX 算子
第四章:在 PyTorch 中支持更多 ONNX 算子 — mmdeploy 0.12.0 文档 PyTorch扩充。 PyTorch转换成ONNX: PyTorch有实现。PyTorch可以转化成一个或者多个ONNX算子。ONNX有相应算子。 如果即没有PyTorch实现,且缺少PyTorch与ONNX的映射关系&…...
前端高级面试题-浏览器
1 事件机制 事件触发三阶段 document 往事件触发处传播,遇到注册的捕获事件会触发 传播到事件触发处时触发注册的事件 从事件触发处往 document 传播,遇到注册的冒泡事件会触发 事件触发⼀般来说会按照上⾯的顺序进⾏,但是也有特例&#x…...
Mongodb 多文档聚合操作处理方法三(聚合管道)
聚合 聚合操作处理多个文档并返回计算结果。您可以使用聚合操作来: 将多个文档中的值分组在一起。 对分组数据执行操作以返回单个结果。 分析数据随时间的变化。 要执行聚合操作,您可以使用: 聚合管道 单一目的聚合方法 Map-reduce 函…...
Zabbix分布式监控配置和使用
目录 1 Zabbix监控的配置流程2 添加主机组3 添加模板4 添加主机5 配置图形6 配置大屏7 新建监控项7.1 简介7.2 添加监控项7.3 查看数据7.4 图表 8 新建触发器8.1 概述8.2 添加触发器8.3 显示触发器状态 1 Zabbix监控的配置流程 在Zabbix-Web管理界面中添加一个主机,…...
XCTF_very_easy_sql
简单的进行sql注入测试后发现不简单尝试一下按照提示 结合这句提示应该是内部访问,所以采用的手段应该是ssrf顺便看看包 唯一值得关注的是set-cookie说回ssrf唯一能使用的方式应该是Gopher协议找到了一个POST的python脚本 import urllib.parsepayload ""…...
[React]useMemoizedFn和useCallback对比
useMemoizedFn文档地址:https://ahooks.js.org/zh-CN/hooks/use-memoized-fn hooks组件内什么时候会更新自定义函数 在 React 中,自定义的 Hooks 内部的函数在以下常见的几种情况下会被重新赋值,导致更新引用: 组件重新渲染&…...
计算机毕设 深度学习人体跌倒检测 -yolo 机器视觉 opencv python
文章目录 0 前言1.前言2.实现效果3.相关技术原理3.1卷积神经网络3.1YOLOV5简介3.2 YOLOv5s 模型算法流程和原理4.数据集处理3.1 数据标注简介3.2 数据保存 5.模型训练 6 最后 0 前言 🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题…...
完全背包
动态规划解题步骤 : 动态规划问题一般从三个步骤进行考虑。 步骤一:集合和集合的状态 所谓的集合,就是一些方案的集合。 用 g[i][j] 表示从前 i 种物品中进行选择,且总体积不大于 j 的各个选法获得的价值的集合。注意:g[i][j] 不是一个数…...
【软件测试】webdriver常用API演示(Java+IDEA+chrome浏览器)
1.元素定位方法 对象的定位应该是自动化测试的核心,要想操作一个对象,首先应该识别这个对象。一个对象就是一个人一样,他会有各种的特征(属性),如比我们可以通过一个人的身份证号,姓名…...
Linux安装MySQL 8.1.0
MySQL是一个流行的开源关系型数据库管理系统,本教程将向您展示如何在Linux系统上安装MySQL 8.1.0版本。请按照以下步骤进行操作: 1. 下载MySQL安装包 首先,从MySQL官方网站或镜像站点下载MySQL 8.1.0的压缩包mysql-8.1.0-linux-glibc2.28-x…...
多线程面试相关的一些问题
面试题 1. 常见的锁策略相关的面试题 2. CAS相关的面试题 3. Synchronized 原理相关的面试题 4. Callable 接口相关的面试题 1. 常见的锁策略 乐观锁 vs 悲观锁 悲观锁: 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都…...
【使用维纳滤波进行信号分离】基于维纳-霍普夫方程的信号分离或去噪维纳滤波器估计(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
Vue+axios如何解决跨域
1、为什么会产生跨域? 出于浏览器的同源策略限制。 同源策略(Sameoriginpolicy)是一种约定,是浏览器的一种安全机…...
网络安全系统中的守护者:如何借助威胁情报 (TI) 提高安全性
在这篇哈巴尔网站上的推文中,我们将解释 TI 缩写背后的含义、为什么需要它、Positive Technologies 收集哪些网络威胁数据以及如何帮助企业预防网络威胁。我们将以四种情况为例,说明公司如何使用 PT Threat Intelligence Feeds 来发现恶意活动并预防攻击…...
并发编程 - CompletableFuture
文章目录 Pre概述FutureFuture的缺陷类继承关系功能概述API提交任务的相关API结果转换的相关APIthenApplyhandlethenRunthenAcceptthenAcceptBoththenCombinethenCompose 回调方法的相关API异常处理的相关API获取结果的相关API DEMO实战注意事项 Pre 每日一博 - Java 异步编程…...
IPIDEA参展ChinaJoy!探索未来创新科技的峰会之旅
中国最大的国际数码互动娱乐展会ChinaJoy即将于7月28日在上海举行,届时将聚集全球来自22个国家和地区的领先科技公司、创业者和技术专家,为参观者呈现一系列引人入胜的展览和活动。而IPIDEA作为参展商之一,将为参观者带来一场关于数字科技的奇…...
2023最新ChatGPT商业运营版网站源码+支持ChatGPT4.0+GPT联网+支持ai绘画(Midjourney)+支持Mind思维导图生成
本系统使用Nestjs和Vue3框架技术,持续集成AI能力到本系统! 支持GPT3模型、GPT4模型Midjourney专业绘画(全自定义调参)、Midjourney以图生图、Dall-E2绘画Mind思维导图生成应用工作台(Prompt)AI绘画广场自定…...
轮趣科技教育版ros小车键盘控制运动
我之前买的ros小车是单独买的底板,以为随便一个树莓派就可以,因为我以前有一个树莓派3B,后来买了单独的小车之后,发现只能使用树莓派4B,然后又单独买了一个树莓派4B,给装上镜像,安装ros-melodic…...
深入理解Python中的os.chdir()方法
深入理解Python中的os.chdir()方法 1. 简介 在Python中,os.chdir()方法用于改变当前的工作目录。工作目录是指当前正在执行的脚本所在的目录。通过使用os.chdir()方法,我们可以在脚本执行过程中切换到不同的目录。 在编写Python脚本时,我们…...
【Golang 接口自动化02】使用标准库net/http发送Post请求
目录 写在前面 发送Post请求 示例代码 源码分析 Post请求参数解析 响应数据解析 验证 发送Json/XMl Json请求示例代码 xml请求示例代码 总结 资料获取方法 写在前面 上一篇我们介绍了使用 net/http 发送get请求,因为考虑到篇幅问题,把Post单…...
关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...
无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
GB/T 43887-2024 核级柔性石墨板材检测
核级柔性石墨板材是指以可膨胀石墨为原料、未经改性和增强、用于核工业的核级柔性石墨板材。 GB/T 43887-2024核级柔性石墨板材检测检测指标: 测试项目 测试标准 外观 GB/T 43887 尺寸偏差 GB/T 43887 化学成分 GB/T 43887 密度偏差 GB/T 43887 拉伸强度…...
Android Framework预装traceroute执行文件到system/bin下
文章目录 Android SDK中寻找traceroute代码内置traceroute到SDK中traceroute参数说明-I 参数(使用 ICMP Echo 请求)-T 参数(使用 TCP SYN 包) 相关文章 Android SDK中寻找traceroute代码 设备使用的是Android 11,在/s…...
Go 语言中的内置运算符
1. 算术运算符 注意: (自增)和--(自减)在 Go 语言中是单独的语句,并不是运算符。 package mainimport "fmt"func main() {fmt.Println("103", 103) // 13fmt.Println("10-3…...
MongoDB $type 操作符详解
MongoDB $type 操作符详解 引言 MongoDB 是一款流行的开源文档型数据库,它提供了丰富的查询操作符来满足不同的数据查询需求。在 MongoDB 中,$type 操作符是一个非常有用的查询操作符,它允许用户根据文档中字段的类型来查询文档。本文将详细介绍 MongoDB 的 $type 操作符,…...
node 进程管理工具 pm2 的详细说明 —— 一步一步配置 Ubuntu Server 的 NodeJS 服务器详细实录 7
前言 我以 Ubuntu Server 打造的 NodeJS 服务器为主题的系列文章,经过五篇博客,我们顺利的 安装了 ubuntu server 服务器,并且配置好了 ssh 免密登录服务器,安装好了 服务器常用软件安装, 配置好了 zsh 和 vim 以及 通过 NVM 安装…...
vue-14(使用 ‘router.push‘ 和 ‘router.replace‘ 进行编程导航)
使用 ‘router.push’ 和 ‘router.replace’ 进行编程导航 编程导航是使用 Vue Router 构建动态和交互式 Web 应用程序的一个重要方面。它允许您根据应用程序逻辑、用户作或特定条件控制用户的导航流。您可以使用 router.push 和 router.replace 方法以编程方式导航到不同的路…...
Appium+python自动化(八)- 认识Appium- 下章
1、界面认识 在之前安装appium的时候说过我们有两种方法安装,也就有两种结果,一种是有界面的(客户端安装),一种是没有界面的(终端安装),首先我们先讲一下有界面的,以及界…...
