TenserRT(三)PYTORCH 转 ONNX 详解

第三章:PyTorch 转 ONNX 详解 — mmdeploy 0.12.0 文档

torch.onnx — PyTorch 2.0 documentation

torch.onnx.export 细解

计算图导出方法

TorchScript是一种序列化和优化PyTorch模型的格式,将torch.nn.Module模型转换为TorchScript的torch.jit.ScriptModule模型,也是一种中间表示。

torch.onnx.export中使用的模型实际上是torch.jit.ScriptModule。

将torch.nn.Module转化为TorchScript模型(导出计算图)有两种模式:跟踪(trace)和脚本化(script)。

torch.onnx.export输入一个torch.nn.Module,默认会使用跟踪(trace)的方法导出。

 

import torch

class Model(torch.nn.Module):
    def __init__(self, n):
        super().__init__()
        self.n = n
        self.conv = torch.nn.Conv2d(3, 3, 3)

    def forward(self, x):
        for i in range(self.n):#控制输入张量被卷积的次数
            x = self.conv(x)
        return x

models = [Model(2), Model(3)]# n=2和n=3的模型
model_names = ['model_2', 'model_3']

for model, model_name in zip(models, model_names):
    dummy_input = torch.rand(1, 3, 10, 10)
    dummy_output = model(dummy_input)
    model_trace = torch.jit.trace(model, dummy_input)
    model_script = torch.jit.script(model)

    #torch.onnx.export默认使用trace,所有不需要先trace
    # 跟踪法与直接 torch.onnx.export(model, ...)等价
    # torch.onnx.export(model_trace, dummy_input, f'{model_name}_trace.onnx', example_outputs=dummy_output, opset_version = 11)
    torch.onnx.export(model,  dummy_input,f'{model_name}_trace.onnx', example_outputs=dummy_output, opset_version = 11)
    # 脚本化必须先调用 torch.jit.sciprt
    torch.onnx.export(model_script, dummy_input, f'{model_name}_script.onnx', example_outputs=dummy_output)

    # 如果是先运行了torch.jit.script,将模型转化成TorchScript,则export函数不需要再运行一遍
    # 如果输入不是TorchScript,则export需要运行一遍模型
    # dummy_input和dummy_output表示输入输出张量的数据类型和形状


跟踪法trace中,不同的n得到的ONNX模型结构是不一样的。

脚本法script中,Loop节点表示循环,不同的n可以有相同的结构。

推理引擎对静态图支持更好,不需要显式的将PyTorch模型转换为TorchScript,直接使用torch.onnx.export跟踪法导出即可。

虽然在代码中没有直接将trace的脚本作为export输入,但是可以通过trace来定位export问题是否出现在trace中。

参数讲解

def export(model, args, f, export_params=True, verbose=False, training=TrainingMode.EVAL,
           input_names=None, output_names=None, aten=False, export_raw_ir=False,
           operator_export_type=None, opset_version=None, _retain_param_name=True,
           do_constant_folding=True, example_outputs=None, strip_doc_string=True,
           dynamic_axes=None, keep_initializers_as_inputs=None, custom_opsets=None,
           enable_onnx_checker=True, use_external_data_format=False):
  • 模型(model):必选
  • 输入(args):必选
  • 导出的 onnx 文件名(f):必选
  • 模型中是否保存权重(export_params):一般模型结构和模型权重放在一个文件里存储,所以默认是true,如果是在不同的框架间传递模型,而不是用于部署,则设置为false。
  • 输入/输出张量名称(input_names, output_names):推理引擎一般都需要通过“名称-张量值”的数据对来输入数据,并根据输出张量的名称来获取输出数据,保证ONNX和推理引擎中使用同一套名称。
  • opset_version:ONNX算子集版本。
  • dynamic_axes:指定输入输出张量的哪些维度是动态的,为了追求效率,ONNX默认所有参与运算的张量都是静态的(张量的形状不发生改变)。可以显式的指明输入输出张量的哪几个维度的大小是可变的
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)
        return x


model = Model()
dummy_input = torch.rand(1, 3, 10, 10)
model_names = ['model_static.onnx',
'model_dynamic_0.onnx',
'model_dynamic_23.onnx']

# dynamic_axes_0 = {#第0维动态
#     'in' : [0],
#     'out' : [0]
# }
dynamic_axes_0 = {
    'in' : {0: 'batch'},
    'out' : {0: 'batch'}
}
dynamic_axes_23 = {#第2、3维动态
    'in' : [2, 3],
    'out' : [2, 3]
}

torch.onnx.export(model, dummy_input, model_names[0], input_names=['in'], output_names=['out'])#没有动态维度
torch.onnx.export(model, dummy_input, model_names[1], input_names=['in'], output_names=['out'], dynamic_axes=dynamic_axes_0)#第0维动态
torch.onnx.export(model, dummy_input, model_names[2], input_names=['in'], output_names=['out'], dynamic_axes=dynamic_axes_23)#第2、3维动态
# ONNX 要求每个动态维度都有一个名字,直接这样写会引出一条UserWarning,警告我们通过列表方式设置动态维度的话,系统会自动为它们分配名字

 

import onnxruntime
import numpy as np

origin_tensor = np.random.rand(1, 3, 10, 10).astype(np.float32)
mult_batch_tensor = np.random.rand(2, 3, 10, 10).astype(np.float32)
big_tensor = np.random.rand(1, 3, 20, 20).astype(np.float32)

inputs = [origin_tensor, mult_batch_tensor, big_tensor]
exceptions = dict()
model_names = ['model_static.onnx',#批量或者维度增加就会出错
'model_dynamic_0.onnx',#维度增加就会出错
'model_dynamic_23.onnx']#批量增加就会出错
for model_name in model_names:
    for i, input in enumerate(inputs):
        try:
            ort_session = onnxruntime.InferenceSession(model_name)
            ort_inputs = {'in': input}
            ort_session.run(['out'], ort_inputs)#只有在设置了对应的动态维度后才不会出错
        except Exception as e:
            exceptions[(i, model_name)] = e
            print(f'Input[{i}] on model {model_name} error.')
            print(exceptions[(1, 'model_static.onnx')])

        else:
            print(f'Input[{i}] on model {model_name} succeed.')

使用技巧

torch.onnx.is_in_onnx_export():PyTorch推理时不运行,但是在执行torch.onnx.export()时为真。

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)
        if torch.onnx.is_in_onnx_export():# 仅在模型导出时把输出张量的数值限制在[0,1]之间
            #可以在代码中添加和模型部署相关的逻辑
            x = torch.clip(x, 0, 1)
        return x

利用中断张量跟踪的操作

import torch
class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()

    def forward(self, x):
        #item、for、list等方法都会导致ONNX模型不太正确
        x = x * x[0].item()#跟踪法会把某些取决于输入的中间结果变成常量
        # .item()把torch中的张量转换成普通的Python遍历
        return x, torch.Tensor([i for i in x])#遍历torch张量,并用一个列表新建一个torch张量。

model = Model()
dummy_input = torch.rand(10)
torch.onnx.export(model, dummy_input, 'a.onnx')

涉及到张量与普通变量转换的逻辑都会导致最终ONNX模型不太正确。

利用这个性质,在保证正确性的前提下令模型中间结果变成常量。

这个技巧尝尝用于模型的静态化上,即令模型中所有张量形状都变成常量。

使用张量为输入(PyTorch版本 < 1.9.0)

PyTorch 对 ONNX 的算子支持

如果torch.onnx.export()正常执行后,另一个容易出现的问题就是算子不兼容。

在转换普通torch.nn.Module模型时:

  • Pytorch利用跟踪法执行前向推理,把遇到的算子整合成计算图;
  • Pytorch把遇到的算子翻译成ONNX定义的算子。

算子翻译的过程可能遇到的情况:

  • 算子可以一对一翻译成ONNX算子。
  • 算子没有一对一的ONNX算子,被翻译成一个或多个ONNX算子。
  • 算子没有翻译成ONNX的规则。

ONNX 算子文档

onnx/Operators.md at main · onnx/onnx · GitHub

算子变更表格(算子名,算子变更版本号opset_version),第一次变更的版本号,表示算子第一次被支持,且第一个改动记录可以知道当前算子集中该算子的定义规则。

 表格中的链接可以说明该算子的输入输出参数规定使用示例。

PyTorch 对 ONNX 算子的映射

pytorch/torch/onnx at master · pytorch/pytorch · GitHub

 

symbloic_opset{n}.py表示pytorch对应的ONNX算子集版本。

在vscode中限定在torch/onnx文件夹搜索对应算子

 

 按照调用逻辑直接跳转到

@_onnx_symbolic(
    "aten::upsample_bicubic2d",
    decorate=[_apply_params("upsample_bicubic2d", 4, "cubic")],
)
->

@_beartype.beartype
def _interpolate(name: str, dim: int, interpolate_mode: str):
    return symbolic_helper._interpolate_helper(name, dim, interpolate_mode)

->

@_beartype.beartype
def _interpolate_helper(name, dim, interpolate_mode):
    @quantized_args(True, False, False)
    def symbolic_fn(g, input, output_size, *args):
        ...

    return symbolic_fn

symbolic_fn中插值算子被映射成多个ONNX算子,一个g.op对应ONNX

return g.op(
                "Resize",
                input,
                empty_roi,
                empty_scales,
                output_size,
                coordinate_transformation_mode_s=coordinate_transformation_mode,
                cubic_coeff_a_f=-0.75,  # only valid when mode="cubic"
                mode_s=interpolate_mode,  # nearest, linear, or cubic
                nearest_mode_s="floor",
            )  # only valid when mode="nearest"

 查找对应的ONNXonnx/Operators.md at main · onnx/onnx · GitHub resize算子定义,可以知道对应参数含义。

查询PyTorch到ONNX的映射关系,然后在torch.onnx.export()的opset_version设定一个版本号,然后去PyTorch符号表文件里去查。如果没有对应算子,就需要考虑用其他算子替代,或者自定义算子。

总结

  • 跟踪法和脚本化在导出待控制语句的计算图时有什么区别。
  • torch.onnx.export()中如何设置input_names, output_names, dynamic_axes。
  • 使用torch.onnx.is_in_onnx_export()来使得模型在转换到ONNX时有不同的行为。
  • 查询ONNX 算子文档。
  • 查询ONNX算子对PyTorch算子支持情况。
  • 查询ONNX算子对PyTorch算子使用方式。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

unicloud 模糊查询解决方案

序 1、where和aggregate的模糊搜索 2、第一种是“你好”去匹配“你好啊大家” 3、第二种是“家啊”去匹配“啊&#xff01;你家呢” 只要有1个字匹配就匹配 4、第三种是“家啊”去匹配“啊&#xff01;你家呢” 必须有“家”又有“啊”才匹配” 想看效果&#xff0c;大家可以自…

ROBOGUIDE教程:FANUC机器人摆焊焊接功能介绍与虚拟仿真操作方法

目录 摆焊功能简介 摆焊指令介绍 摆焊功能设置 摆焊条件设置 机器人摆焊示教编程 仿真运行 摆焊功能简介 使用FANCU机器人进行弧焊焊接时&#xff0c;也可以实现摆动焊接&#xff08;简称摆焊&#xff09;。 摆焊功能是在机器人弧焊焊接时&#xff0c;焊枪面对焊接方向…

面试字节,三面HR天坑,想不到自己也会阴沟里翻船....

阎王易见&#xff0c;小鬼难缠。我一直相信这个世界上好人居多&#xff0c;但是也没想到自己也会在阴沟里翻船。我感觉自己被字节跳动的HR坑了。 在这里&#xff0c;我只想告诫大家&#xff0c;offer一定要拿到自己的手里才是真的&#xff0c;口头offer都是不牢靠的&#xff0…

【CE】Mac下的CE教程Tutorial:进阶篇(第8关:多级指针)

▒ 目录 ▒&#x1f6eb; 导读开发环境1️⃣ 第8关&#xff1a;多级指针翻译操作验证其它方案&#x1f6ec; 文章小结&#x1f4d6; 参考资料&#x1f6eb; 导读 开发环境 版本号描述文章日期2023-03-操作系统MacOS Big Sur 11.5Cheat Engine7.4.3 1️⃣ 第8关&#xff1a;多…

MySQL数据库中的函数怎样使用?

函数 是指一段可以直接被另一段程序调用的程序或代码。 也就意味着&#xff0c;这一段程序或代码在MySQL中已经给我们提供了&#xff0c;我们要做的就是在合适的业务场景调用对应的函数完成对应的业务需求即可。 那么&#xff0c;函数到底在哪儿使用呢? 我们先来看两个场景&a…

【FPGA-Spirit_V2】基于FPGA的循迹小车-小精灵V2开发板

&#x1f389;欢迎来到FPGA专栏~基于FPGA的循迹小车 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;FPGA学习之旅 文章作者技术和水平有限&#xff0c;如果文中出现错误&#xff0c;希望大家能…

Android下载apk并安装apk(用于软件版本升级用途)

软件版本更新是每个应用必不可少的功能&#xff0c;基本实现方案是请求服务器最新的版本号与本地的版本号对比&#xff0c;有新版本则下载apk并执行安装。请求服务器版本号与本地对比很容易&#xff0c;本文就不过多讲解&#xff0c;主要讲解下载apk到安装apk的内容。 一、所需…

Socket套接字编程(实现TCP和UDP的通信)

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点!人生格言&#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔&#x1f9be;&am…

(链表)移除链表元素(双指针法)

文章目录前言&#xff1a;问题描述&#xff1a;解题思路&#xff08;双指针法&#xff09;&#xff1a;代码实现&#xff1a;总结&#xff1a;前言&#xff1a; 此篇是针对链表的经典练习题。 问题描述&#xff1a; 给你一个链表的头节点 head 和一个整数 val &#xff0c;请…

Js:apply/call/bind、作用域/闭包、this指向(普通,箭头,JS/Vue的this)

目录1、apply/call/bind2、作用域、作用域链和闭包核心1、预处理&#xff08;解析阶段&#xff09;——JS执行“代码段”之前2、生成执行上下文环境——对代码段(全局/函数体)进行处理3、执行上下文环境小结4、多个执行上下文环境5、作用域6、作用域和执行上下文7、从【自由变量…

小米万兆路由器里的 Docker 安装 Gitea

小米万兆路由器里的 Docker 安装 Gitea准备工作创建存储查看Docker Hub镜像信息拉取 gitea 镜像和运行容器配置通过 ssh 访问(Optional)其他小米2022年12月份发布了万兆路由器&#xff0c;里面可以使用Docker。 今天尝试在小米的万兆路由器里安装Gitea。 准备工作 先将一块US…

Java企业级开发学习笔记(2.1)MyBatis实现简单查询

该文章主要为完成实训任务&#xff0c;详细实现过程及结果见【http://t.csdn.cn/zi0wB】 文章目录零、创建数据库与表一、基于配置文件方式使用MyBatis基本使用1.1 创建Maven项目 - MyBatisDemo1.2 在pom文件里添加相应的依赖1.3 创建与用户表对应的用户实体类 - User1.4 创建用…

没有他们,人工智能只能死翘翘

我过去写过一篇文章《很多所谓伟大的贡献&#xff0c;其实都是狗屎运》&#xff0c;今天我也写写人工智能。&#xff08;1&#xff09;人才深度神经网络如果不从明斯基和罗森布拉特说起&#xff0c;那就应该可以从1965年Ivakhnenko发明前馈神经网络说起。但关键里程碑是出自Rum…

SpringBoot2核心功能 --- 原理解析

一、Profile功能 为了方便多环境适配&#xff0c;springboot简化了profile功能。 1.1、application-profile功能 默认配置文件 application.yaml&#xff1b;任何时候都会加载指定环境配置文件 application-{env}.yaml激活指定环境配置文件激活 命令行激活&#xff1a;java -…

【快乐手撕LeetCode题解系列】—— 环形链表 II

【快乐手撕LeetCode题解系列】—— 环形链表 II&#x1f60e;前言&#x1f64c;环形链表 II&#x1f64c;画图分析&#xff1a;&#x1f60d;思路分析&#xff1a;&#x1f60d;源代码分享&#xff1a;&#x1f60d;总结撒花&#x1f49e;&#x1f60e;博客昵称&#xff1a;博客…

STM32与Python上位机通过USB虚拟串口通信

文章目录前言1. 查看原理图2. 新建工程3.添加代码与烧录4. python代码编写总结问题解决思路前言 在详细阅读广大网友的教程之后&#xff0c;我对STM32和Python通过USB通信的流程烂熟于心。 尝试用ST公司的NUCLEO-L476RG板子进行简单的回环通信测试&#xff0c;发现还是存在网上…

Linux·异步IO编程框架

hi&#xff0c;大家好&#xff0c;今天分享一篇Linux异步IO编程框架文章&#xff0c;对比IO复用的epoll框架&#xff0c;到底性能提高多少&#xff1f;让我们看一看。 译者序 本文组合翻译了以下两篇文章的干货部分&#xff0c;作为 io_uring 相关的入门参考&#xff1a; Ho…

【RocketMQ】顺序消息实现原理

全局有序 在RocketMQ中&#xff0c;如果使消息全局有序&#xff0c;可以为Topic设置一个消息队列&#xff0c;使用一个生产者单线程发送数据&#xff0c;消费者端也使用单线程进行消费&#xff0c;从而保证消息的全局有序&#xff0c;但是这种方式效率低&#xff0c;一般不使用…

Web 攻防之业务安全:接口未授权访问/调用测试(敏感信息泄露)

Web 攻防之业务安全&#xff1a;接口未授权访问/调用测试 业务安全是指保护业务系统免受安全威胁的措施或手段。广义的业务安全应包括业务运行的软硬件平台&#xff08;操作系统、数据库&#xff0c;中间件等&#xff09;、业务系统自身&#xff08;软件或设备&#xff09;、业…

ViT/vit/VIT详解

参考&#xff1a; Vision Transformer详解: https://blog.csdn.net/qq_37541097/article/details/118242600 目录&#xff1a; x.1 (论文中)模型理解x.2 代码理解 建议阅读时间&#xff1a;10min x.1 模型理解 ViT是发表在ICLR2021上的一篇文章&#xff0c;通过将图片分割…
最新文章