如何将MindSpore模型转ONNX格式并使用OnnxRuntime推理---开发测试篇

举报
Super_WZB 发表于 2022/06/20 21:07:12 2022/06/20
【摘要】 Tips1 此文档建立在“onnx前期准备”文档的基础上,默认已完成算子统计,models和mindspore仓库的clone工作,若未全部完成请回看该文档,或查看网页版教程:https://bbs.huaweicloud.com/blogs/360051 。2 文档中的基本概念等知识如果已经掌握可以跳过。3 不需要开发的同学:如果所有算子都已经实现,请直接从第二章开始。4 需要开发的同学:...

Tips

1 此文档建立在“onnx前期准备”文档的基础上,默认已完成算子统计,modelsmindspore仓库的clone工作,若未全部完成请回看该文档,或查看网页版教程:https://bbs.huaweicloud.com/blogs/360051

2 文档中的基本概念等知识如果已经掌握可以跳过

3 不需要开发的同学:如果所有算子都已经实现,请直接从第二章开始。

4 需要开发的同学:文档中用Pad算子来介绍开发流程的部分写的很详细,可能有同学很会觉得很啰嗦,但是还是希望能耐心看完,这样就可以清楚整个开发流程。

5 此文档也有网页版,大家愿意的话可以支持一下,刷一刷阅读量。

6 如有错误,欢迎修正和反馈

常用网址

1 如何将 MindSpore 模型转 ONNX 格式并使用 OnnxRuntime 推理 --- 前期准备篇

2 如何将 MindSpore 模型转 ONNX 格式并使用 OnnxRuntime 推理 --- 开发测试篇

3 CNN+CTC ONNX 导出 onnx_exporter.cc 文件 PR

4 CNN+CTC ONNX 导出脚本 + 推理脚本 PR

5 MindSpore 官方 Model 仓库

6 MindSpore 官方模型 checkpoint 下载网址

7 MindSpore 官方算子查询网址

8 Onnx 算子查询库


目录

Tips     

常用网址     

一、      算子映射      1

1.      基本概念      1

(1)           1

(2)      节点      2

2.      开发流程      3

(1)      算子映射分析      3

(2)      声明映射方法      6

(3)      注册算子映射表      7

(4)      实现映射方法      7

(5)      获取节点输入与属性      7

(6)      数值映射      7

(7)      添加数值节点      8

(8)      添加算子节点      9

(9)      Onnx图添加算子节点      9

3.      官方案例分析      11

(1)      一对一(完全对应):MS Conv2D -> Onnx Conv      11

(2)      一对一(不完全对应):MS ExpandDims -> Onxx Reshape      11

(3)      一对多:MS BatchMatMul -> ONNX Transpose + MatMul      13

二、      编译导出      15

1.      MindSpore编译      16

2.      MindSpore包重安装或Python运行路径设置(推荐)      16

(1)      MindSpore包重安装      16

(2)      Python运行路径设置      16

3.      Onnx模型导出      17

三、      推理测试      17

1.      数据集下载      17

(1)      依据Models README下载      17

(2)      依据模型原论文源码中的README下载      17

2.      MindSpore模型推理      18

(1)      MindSpore推理      18

3.      Onnx模型推理      19

(1)      Onnx推理文件      19

(2)      Onnx推理脚本      19

4.      PR提交      20

(1)      Leader创建分支      20

(2)      下载新MindSpore和Models仓库      20

(3)      更新替换文件      20

(4)      上传代码      20

5.      创建PR      22

6.      门禁测试      23


1、

2、 算子映射

1. 基本概念

(1)

华为官网对于图的定义如下:

实际上开发onnx并不需要完全熟悉和清晰图的概念,相反将图抽象的理解为一个计算流程图会更好理解他的作用和调用流程,假设现在我们需要计算如下表达式:F(x, y, z)=x + y* z - b,则其对应的计算流程图如下:

1 F(x, y, z)计算流程图

1中的每一个圆圈或者椭圆都是一个节点,而所有这些节点合起来就构成了我们Fx, y, z)这个函数的计算流程图,而“图”就是用计算机代码来表示和记录这种计算流程。而在这张图中,我们可以看到有三种不同颜色的节点,对于我们本次onnx映射来说他们就是最常用到的三种节点:输入数值节点(橙色x, y, z)、常量数值节点(灰色b)和算子节点(蓝色+, *, -)。大家也注意到了我把图中的蓝色节点(+, *, -)称之为算子而不是运算符,这就是因为其实这些运算符就是基础最基本的算子,而我们所用到的算子实际上就是这样基础的算子不断叠加起来的,也就是我们调用的库方法和库函数。因此可以将图1 转换为算子计算图2

2 算子计算图

至此我们就对“图”有了一个简单但是我个人觉得完全够用了的理解,下面将节点合并为两类:数值节点和算子节点,并介绍节点的相关概念

(1) 节点

1 节点的name

可以注意到2我除了把运算符+, *, -换成算子的名字外,还在每一个节点边上标了一个序号,这个序号可以理解为每个节点的唯一标识符,也就是计算机用来存储和记录运算流程的一个标记。这种标记在本次onnx算子映射中被设为这个节点的“name”,同时对于这张图来说存在一个变量用于存储其中所有节点的“name”,也就是官方文档中的:node_map_ptr <节点,节点序号>

2 数值节点

前面的输入数值节点和常量数值节点都是用来存储数据数值的节点,其主要包括以下信息:

Name(序号):该节点的唯一标识符(如果不好理解就全部理解为序号)

Value(数值):输入或者自己设定的常量的数值,如1101000等。

Type类型该节点所存储数值的数据类型

Output(输出):数值节点需要传输给其他节点进行计算,因此需要设定此节点的输出(即传什么给下游节点),实际上大多数情况都是add_output(name),也就是把自己的序号传下去,到时候下游节点就可以通过name访问数值了。

3 算子节点

算子节点中定义了数据的计算逻辑,其计算部分是“算子开发”所需要做的事情,本次onnx映射仅仅只需要调整和映射以下信息即可

Name(序号):该节点的唯一标识符(如果不好理解就全部理解为序号)

OpType(算子类型):这个可以简单理解为此算子的名字,比如“+”他的OpType设为“Add”。

Input输入):定义此算子的输入是什么,即将什么数值节点或者其他算子节点的输出传给此算子,大部分情况下都是add_input(name_xxx),即告诉计算机我要序号为name_xxx的节点作为我的输入。

Attr(属性):有些算子除了输入外还有属性值,属性值是在算子初始化的时候赋值的,而在此次项目中可以将其与输入看为同一个东西,只不过获取的方法不同罢了。

Output(输出):定义此算子的输出是什么,即将什么数值传给下游算子,实际上大多数情况也是add_output(name1),即此算子的输出就是其唯一标识符,下游算子需要调用的话可以add_input(name1)来设置这个算子为输入。

通过设置好每一个数值节点的valuetypeoutput和算子节点inputoutput信息后,整张图就连接起来了。

1. 开发流程

此次开发流程将以我本次onnx推理的CNN+CTC模型中缺少的Pad算子为例,以功能描述+代码+映射对应图的形式讲解开发中需要用到的方法,学会了这些方法后就可以通过拼接和组合完成其他算子的映射。

(1) 算子映射分析

1 MindSpore Pad算子接口分析

MindSpore官方算子查询网址:

https://www.mindspore.cn/docs/zh-CN/r1.7/index.html

这个算子其实非常简单,输入一个任意维度的Tensor,根据初始化时赋值的paddings属性在对应的维度前和后增加0值,以2Tensor为例:

输入input_x如下:

属性paddings如下:

input_xshape是(24),这个paddings属性取值的含义就是,在input_x的第0个维度增加3(前面1,后面2),对于这个例子就是增加三行(上面1行,下面2行),然后在input_x的第1个维度(列)增加3列(左边2列,右边一列),所有增加的行和列全部填充数值0,结果如下:

mode属性值是设定填充的方式,分为三种,具体大家可以看官网案例,本次网络使用默认值故此处不分析。Onnx Pad算子接口分析

Onnx算子查询网址

https://github.com/onnx/onnx/blob/main/docs/Operators.md

可以看到Onnx中也是有这个Pad算子,并且简单查阅后发现功能是相同的,那么就进入下一步。

4 差异分析

通过对比分析我们可以看出MindSpore中的Pad算子和Onnx中的Pad算子功能是相同的,但是在输入和属性上存在差异,具体为:

MindSpore中用来设置增加维度的paddings是一个属性,而Onnx中起到相同功能的pads是一个输入,因此我们需要写paddingspads的映射

同时paddings要求是一个shape[N, 2]Ninput_x的维度)tuple,其D0个元素代表在input_x的第D前面增加的数量,而第1个元素代表在后面增加的元素。而pads则必须为一个1Dtensorshape[2 * N],其数值形式为[dim1_begin, dim2_begin,…, dim1_end, dim2_end]其中dim1_begindim1_end就代表在input_x的第1个维度前和后增加的数量。

文字看的可能比较晕,那我们看下面的图:

这张图就可以很清晰的看出paddingspads之间的对应关系,同时可以得到paddingspads映射所需要做的事情:

MindSpore属性paddings映射为Onnx输入pads

paddings2D tuple类型映射为1D tensor类型

paddings数值正确映射为pads数值

由于CNN+CTC网络中使用的是默认mode属性值CONSTANT往新增行列填充0,故此处不分析,同样也不需要映射,如果mode属性值设定了某个特定的数值则需要增加对mode属性的映射。

至此我们完成了对需要映射算子的分析,下面就可以进入开发阶段了如果对于应该在那一行或者哪里写相应的代码可以查看CNN+CTC ONNX导出的PR链接,其中有清晰的增添删改代码和代码行号:

https://gitee.com/mindspore/mindspore/pulls/35915/files

(如果你发现需要映射的算子能够完美映射,即MindSpore XXX算子与Onnx XXX算子的输入输出完全一致那么可以跳过此章,直接进入 3 官方案例分析

(2) 声明映射方法

OnnxExporter类下声明类私有方法ExportPrimXXX

其中四个参数的含义分别为:

func_graphMindSpore的图

node是图中当前的节点,该函数中就是Pad算子节点

node_map_ptr存储图中所有节点<节点,节点序号>

graph_protoONNX的图。

(3) 注册算子映射表

OnnxExporter::ExportCNode方法中注册算子映射表:

(4) 实现映射方法

实现第(2)步声明的映射方法ExportPrimXXX

(5) 获取节点输入与属性

获取MindSpore Pad算子第1个输入input_xname

获取MindSpore Pad算子的属性paddings值:

(6) 数值映射

paddings2D tuple转化为pads_sequence1D vector,并完成数值映射

(7) 添加数值节点

将数值pads_sequence注册为数值节点pads

node_map_ptr登记pads常量数值节点并获取其name

ONNXgraph_proto中新建一个常量数值节点pads_node,并指定其输出为pads常量数值节点的name

pads_node节点添加一个value数值属性,并将数值节点pads中的数值转换为onnx::tensor赋值给value

(8) 添加算子节点

本次Pad算子映射不涉及算子节点的添加, 3 BatchMatMul 算子 映射中会介绍如何添加算子节点。

(9) Onnx图添加算子节点

node_map_ptr中登记当前MindSpore Pad算子节点并获取其name

ONNXgraph_proto中新建ONNX Pad算子节点:

指定ONNX Pad算子节点的输出为MindSpore Pad算子节点的输出(name)即ms_pad_node_name

指定ONNX Pad算子节点的第1个输入为MindSpore Pad算子的第1个输入input_xnamex_name

指定ONNX Pad算子节点的第2个输入为(7)中创建的pads数值节点的namepads_name

映射完成后:

2. 官方案例分析

官方案例就不画图了,以功能描述加代码的方法分析,同时映射方法的声明和算子注册也不赘述,直接分析核心映射代码。

(1) 一对一(完全对应):MS Conv2D -> Onnx Conv

1 映射分析

官方案例,就不分析了,结果就是MindSpore Conv2D Onnx Conv 的输入和属性能够完全对应上,那么就只需要写段代码,把相同作用的参数名映射一下就好了:

(2) 一对一(不完全对应):MS ExpandDims -> Onxx Reshape

1 映射分析

MindSpore ExpandDims 算子 -> ONNX Reshape 算子 , 输入输出可以对应,属性含义不同,无法直接对应, ExpanDims输入axis(int)是要扩展维度的轴,Reshape输入shape是扩展后的维度,需要在转换时作特殊处理。因此我们明确映射所需要做的事:

获取MindSpore ExpandDims算子的输入axis并结合输入input_xshape计算出扩维之后new_shape,而这个new_shape就是Onnx Reshape算子所需要的输入shape

5 获取节点输入与属性

获取MindSpore ExpandDims算子的第1个输入input_x2个输入axis

6 MindSpore输入axis映射为Onnx输入shape

获取MindSpore1个输入input_xshapex_shape

通过MindSpore的输入axisx_shape推导出正确的Onnx输入new_shape,具体算法不分析,如果感兴趣可以自己查看:

7 添加数值节点

将数值new_shape注册为数值节点shape

node_map_ptr中登记shape常量数值节点并获取其namename_shape

ONNXgraph_proto中新建一个常量数值节点node_proto,并指定其输出为常量数值节点的namename_shape

node_proto节点添加一个value数值属性,并将数值节点shape中的数值转换为onnx::tensor后赋值给value

8 Onnx图添加算子节点

node_map_ptr中登记当前MindSpore ExpandDims算子节点并获取其name

ONNXgraph_proto中新建ONNX Reshape算子节点:

指定ONNX Reshape算子节点的输出为MindSpore ExpandDims算子节点的输出(name)即node_name

指定ONNX Pad算子节点的第1个输入为MindSpore ExpandDims算子的第1个输入input_xnameinput_x

指定ONNX Pad算子节点的第2个输入为中创建的shape数值节点的namename_shape

至此完成映射

(3) 一对多:MS BatchMatMul -> ONNX Transpose + MatMul

1 映射分析

MindSpore的 BatchMatMul 算子 有transpose_a,transpose_b属性,控制是否将输入转置,ONNX的 MatMul 算子 无转置属性,因此,需要判断BatchMatMul的transpose_a,transpose_b属性是否为true,如果为true,则需要在MatMul对应输入前添加Transpose算子作转换。因此我们可以明确需要做的事:

获取MindSpore BatchMatMul算子的属性transpose_atranspose_b

获取MindSpore BatchMatMul算子的输入input_xinput_y

transpose_atrue,则需要增加一个Onnx Transpose节点将输入input_x进行转置

transpose_btrue,则需要增加一个Onnx Transpose节点将输入input_y进行转置

9 获取节点输入与属性

获取MindSpore BatchMatMul算子的第1个输入input_x和第2个输入input_y:

获取MindSpore BatchMatMul算子的第1个属性transpose_a和第2个属性transpose_b

10 transpose_aTrue则在input_x后增加Transpose算子节点

获取输入input_xshape

node_map_ptr中登记一个transpose_input_x_name常量数值节点来存储转置后的input_x数值

ONNXgraph_proto中新建ONNX Transpose算子节点transpose_inputx_node_proto,并且指定其输入为MindSpore BatchMatMul算子节点的第1个输入input_x,输出为刚才创建的常量节点transpose_input_x_name

transpose_inputx_node_proto节点添加一个perm数值属性,并依据input_xshape为其赋值

至此input_x进行转置的Onnx Transpose算子节点添加完成

11 transpose_bTrue则在input_y后增加Transpose算子节点

获取输入input_yshape

node_map_ptr中登记一个transpose_input_y_name常量数值节点来存储转置后的input_y数值:

ONNXgraph_proto中新建ONNX Transpose算子节点transpose_inputy_node_proto,并且指定其输入为MindSpore BatchMatMul算子节点的第1个输入input_y,输出为刚才创建的常量节点transpose_input_y_name

transpose_inputy_node_proto节点添加一个perm数值属性,并依据input_yshape为其赋值:

至此对input_y进行转置的Onnx Transpose算子节点添加完成。

12 Onnx添加算子节点

在node_map_ptr中登记当前MindSpore BatchMatMul算子节点并获取其name(node_name):

在ONNX图graph_proto中新建ONNX MatMul算子节点:

指定ONNX Reshape算子节点的输出为MindSpore BatchMatMul算子节点的输出(name)即node_name:

下面这行代码我也不知道啥意思,如果有同学知道了请联系我修改:

若transpose_a为True,则说明input_x需要转置,那么指定ONNX MatMul算子节点的第1个输入为转置后的常量数值节点transpose_input_x_name;若为False,则说明input_x不需要转置,那么指定ONNX MatMul算子节点的第1个输入为原始的input_x:

若transpose_b为True,则说明input_y需要转置,那么指定ONNX MatMul算子节点的第1个输入为转置后的常量数值节点transpose_input_y_name:若为False,则说明input_y不需要转置,那么指定ONNX MatMul算子节点的第1个输入为原始的input_y:

至此完成映射

1、 编译导出

1. MindSpore编译

MindSpore路径下输入以下命令开始编译:

sh build.sh -e gpu -j32

初次编译需要下载很多第三方包所以很慢,之后再次编译就很快了。

编译报错则根据报错修改,编译成功则显示如下界面:

3. MindSpore安装Python运行路径设置(推荐)

(1) MindSpore包重安装

编译完的MindSpore包会生成在mindspore/build/package/路径下:

可以在mindspore路径下输入以下命令重新安装mindspore包:

pip uninstall build/package/xxx.whl

pip install build/package/xxx.whl

(2) Python运行路径设置

输入以下命令设置Python运行路径:

export PYTHONPATH=/disk1/user14/wzb/test/mindspore/build/package:$PYTHONPATH

把其中的路径换成自己编译的mindspore存放的绝对路径,通过这种方式可以避免重复卸载再安装mindspore包的麻烦,只需要设置一次后就可以直接调用每次编译出来的mindspore包了。

4. Onnx模型导出

更新完MindSpore包后,在Models文件夹中自己模型路径下调用README文件中的export命令,设置导出格式--file_formatONNX,若成功导出则在模型路径下生成xxx.onnx文件:

若报错则根据报错信息修改算子映射文件onnx_exporter.cc并重复(1)(2)(3)步骤,直到能够导出ONNX文件为止。

2、 推理测试

1. 数据集下载

(1) 依据Models README下载

查看ModelsREADME文件中对于数据集的描述,通过其给出的超链接下载数据集并放在对应的路径下:

(2) 依据模型原论文源码中的README下载

若发现ModelsREADME文件给出的超链接中数据集太大、下载很慢或者保存在谷歌云盘上需要翻墙,那么可以查阅一下模型原论文中给出的源码链接,查看源码中README提供的数据下载地址。

比如我的CNN+CTC模型Models中给出的超链接是用谷歌云盘存储的,并且把训练集、验证集和测试集打包在一起,导致数据集高达18G,又需要翻墙,直接下载经常中断:

屡次尝试未果后,我查阅了原论文的代码并且找到了其readme文件中给出的数据集下载链接:

点开后我发现了原作者的数据集中既有Models中给出的集合包,还有单独的训练集、验证集和测试集压缩包,由于我们只涉及推理所以只需要下载测试集压缩包即可,其大小只有160M,下载非常顺利:

2. MindSpore模型推理

(1) MindSpore推理

这一步骤只需要参照自己Models模型中的README文件中关于MindSpore模型eval的说明即可,只是需要注意有些模型的脚本可能由于MindSpore的更新导致一些算子的用法发生了改变,如果报错可以自己看着修改一下:

3. Onnx模型推理

(1) Onnx推理文件

Onnx推理文件就是使用onnxruntime接口导入第二章中导出的onnx文件,然后传入符合网络要求的输入,最终输出Onnx离线推理的模型精度。

这一步骤实际上很简单,主要包含两步,完整推理文件可以参考此PR中的eval.pyinfer_cnnctc_onnx.pyhttps://gitee.com/mindspore/models/pulls/2913/files

1 数据获取dataloader

使用Models模型eval脚本中的数据集dataloader获取数据,然后将其MindSpore::Tesnor转化为Onnx一般的输入numpy格式

13 网络替换:MindSpore Net -> Onnx

Models模型eval脚本中关于网络的定义替换成Onnxonnxruntime导入的网络:

MindSpore

Onnx

(2) Onnx推理脚本

推理脚本就是仿照eval脚本写一个可以用bash调用的.sh文件,使得用户可以通过bash命令一键推理,详细可参考此PR中的run_infer_onnx.sh

https://gitee.com/mindspore/models/pulls/2913/files

5. PR提交

(1) Leader创建分支

联系小组Leader fork官方MindSporeModels仓库并给每个模型创建分支。

(2) 下载新MindSporeModels仓库

下载小组用来提交PR的新MindSporeModels仓库:

git clone https://gitee.com/ xxxx /mindspore.git

git clone https://gitee.com/xxxx/models.git

(3) 更新替换文件

进入MindSporeModels路径后切换到自己的分支:

git checkput cnnctc

将自己需要修改的文件传入新下载的MindSporeModels文件夹对应路径下进行替换。

(4) 上传代码

使用如下命令提交pr

1 查看已修改文件:

git status

2 提交到本地仓库:

依据①中输出的已修改文件,核对是否均是自己修改的,如果是则直接git add .若不全是则一个个的git add xxxx文件。

MindSPore仓库:

git add .

git commit -am " ONNX converter: CNN+CTC support"

保存并退出:点击ESC,然后按住shift :” ,输入wq,回车

Models仓库:

git add .

git commit -am " ONNX infer: CNN+CTC support"


保存并退出:点击ESC,然后按住shift :” ,输入wq,回车

3 提交到远程仓库分支

git push origin cnnctc

6. 创建PR

联系Leader创建PR,或自己前往提交代码的仓库创建PR

7. 门禁测试

1 开启测试

在自己PR评论区输入/retest即可开启测试

点击系统自动评论中的Link超链接即可打开门禁界面:

2 测试结果

报错后会中断门禁测试,在评论区找报错信息,点击错评论中的超链接即可查看门禁报错信息:

段代码错误会以两段相同代码的不同格式输出下面一段为自己当前写的代码,而上面一段为门禁系统的修改意见,只需要按照上面一段改就行了(字有点小放大点看)

如果通过则系统自动回复如下:

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。