Mnn Converter Debug [进阶] ---- 微架构设计 & 模型定制化转换概述

1 概述

文章:Mnn Converter Debug
上述文章中,我们介绍了Mnn Converter Debug 工程的构建办法,构建了该工程,我们可以更便捷地通过调试快速理解 Mnn Converter 的架构 与 流程,并进行简单的调试修改。

但当我们转化的模型越来越多,越来越复杂。我们会发现:
1)不同模型的转换过程可能遇到相似的问题
2)转换文件的代码复杂度变得愈发复杂
3)复杂的定制化转换变得异常困难甚至无法进行
4)模型更新时,再次转换甚至找不到原始的模型转换代码

我们需要 对我们实现的 定制化模型转换进行必要的管理

所以该篇文章,
1)我会以 onnx-->mnn 的转换需求为例,对 MnnConvertDebug 添加微架构设计。
2)简单介绍基于该架构的 定制化模型转换实现办法
3)简单介绍定制化模型转换过程中的常见问题

2 MnnConverterDebug 微架构设计

2.1 基本架构

Mnn Converter Debug [进阶] ---- 微架构设计 & 模型定制化转换概述_第1张图片
MNN Converter Debug 基本架构设计

2.2 模块说明

2.2.1 JsonDump

Mnn模型 基于 FlatBuffers 结构存储,难以直接阅读。

有关 Mnn FlatBuffers 的相关介绍可以参考:
文章:FlatBuffers,MNN模型存储结构基础 ---- 无法解读MNN模型文件的秘密
文章:做个游戏吧?玩儿转Mnn模型存储结构

所以, Mnn 提供了 JsonDump工具, 可以藉由 Mnn 模型导出 Mnn 基于json描述的网络结构。我们 将这个工具集入框架,在完成Mnn模型转换的同时,通过JsonDumpMnn的网络结构Dump出来,方便对模型进行分析。

2.2.2 Template

约定的 “死”的框架,即可抽离 “活”的定制化
我们将框架相关的模板文件放置在此模块,避免定制化文件中存在大量的重复代码,降低维护代价。

2.2.3 Tools

模型转换的通用工具支持,包括:
1)插入、链接 Mnn 操作节点
2)生成 Mnn 操作节点
3)转换 Mnn 操作节点
4)未实现的 Mnn 节点转换器

2.2.4 Converters

这边将一个 特定模型(一个网络)的转换工具 定义为一个转换器(Converter)
当然,相似的模型可能复用同一个转换器。
比如大多数简单模型可以直接使用Mnn提供的转换器。(不使用我们定制化的)

2.2.3 ConverterRed

非定制化代码,即MnnConverterDebug构建时必要的代码依赖,参考:
文章:Mnn Converter Debug

2.2.6 Main

主流程控制器,主要配置包括:

  1. 转换器 的选择
  2. onnx模型文件 路径配置
  3. 目标mnn模型文件 路径配置
  4. mnn json dump(不含权重)文件 路径配置
  5. mnn json dump(含权重信息)文件 路径配置

2.3 转换流程

Mnn Converter Debug [进阶] ---- 微架构设计 & 模型定制化转换概述_第2张图片
转换流程

注:图中的【MnnDebug】【ModelManager】【Python】该篇文章暂不进行具体的说明。

框架执行流程:
1)首先,选择一个 onnx模型 作为输入;
2)基于模板创建一个 转换器(比如ConverterY)进行模型调试,转换出 mnn 模型;
3)对转换好的 Mnn 模型进行 json dump
4)测试模型效果(比如构建一个MnnDebug工程,或使用其他办法);
5)Run onnx 或者 更原始的模型(比如pytorch、mxnet等),设置与MnnDebug相同的输入( 比如全部值设置为1.1),比对输出,保证一致。

2.4 Demo

工程Demo Git链接
构建与集成说明:Mnn Converter Debug

3 使用 & 调试

3.1 新转换器 的构建 & 配置

1)于 MnnConverterDebug/Custom/Converters 中建立 新的转换器文件(.hpp/.cpp)
2)基于 MnnConverterDebug /Custom/Template/CYCustomConverter_Template.hpp/.cpp 填写 新创建的新转换器
3)在 main.mm 中引入 新创建的转换器 的 .hpp 文件
4)在 main.mm 中替换转换函数 onnx2MNNNet(modelPath.modelFile, modelPath.bizCode, netT); 为 新创建的转换函数。

3.2 调试步骤(新创建的转换器文件的定制化修改)

我们假设已经完成了3.1接2)的模板代码拷贝

3.2.1 测试原始转换

__BREAK_TAG、__MANUAL_TAG 设置一个较大的值(如3000,目前的网络几乎不会有这么多操作)。 此时,转换器没有任何定制化修改,完全走 Mnn原始的自动转换逻辑
[转换顺利]:即给予相同的输入的情况下,转换的mnn模型 与 转换前模型的输出完全一致是,表示模型转换顺利,无须定制化调试修改,流程结束。
[转换异常]:没有转换出mnn模型,或者转换出的mnn模型输出异常…… 孩纸!苦逼的时刻到了!做好准备吧!继续往下看

3.2.2 查看控制台输出

参考控制台输出,模板代码会自动打印所有 onnx模型操作索引、操作名称、操作类型,如图:

Mnn Converter Debug [进阶] ---- 微架构设计 & 模型定制化转换概述_第3张图片
控制台输出

3.3.3 寻找问题操作

参考依据 控制台输出索引,修改 __BREAK_TAG 的值,找到转换开始出现问题的操作。 出现问题指:转换崩溃、执行崩溃、执行结果不合期望 等。

3.3.4 分析问题原因

通过 查看onnx网络结构(推荐工具Netron)、分析onnx转换前的模型网络结构(比如pytorch、mxnet等)、又或调试Mnn源码 等方法 尝试 确认问题产生的原因

3.3.5 尝试解决问题

确认问题后进行问题修改,可能需要通过 修改Mnn源码、添加新的转换器等、添加新的Mnn
操作、甚至设计重写设计局部网络
等方法解决问题。

3.3.6 保证最终输出

循环进行 3.3.3 ~ 3.3.5 直到保证Mnn模型最终输出与原始模型的一致性。
一致性标准参考:每个输出对应值的小数点后至少三位完全一致。

4 模型转换常见问题参考

4.1 转换异常

魔性转换过程中,可能根本无法正常输出mnn模型文件

解决参考:
1)检查输入、输出路径配置
2)参考 [3.2 调试步骤],找到造成 转换异常 的操作,然后针对修改。
3)分析过程可以适当参考模型的JsonDump结构

常见问题原因:
1)对应操作参数没有设置好
2)操作链接设置错误

4.2 模型推理异常

模型转换顺利,并不代表模型可以被正常使用。即在模型的加载、推理过程中出现崩溃。

解决参考:
1)参考 [3.2 调试步骤],找到造成 转换异常 的操作,然后针对修改。
2)分析过程可以适当参考模型的JsonDump结构

常见问题原因:
1)操作链接设置错误
2)相关操作输入输出不匹配(比如A-B操作相关,A操作输入时2维,而B操作的输入期望是4维)

4.3 操作结果不一致

模型推理顺利,同样不等同于转换成功。
我们必须保证 转换的mnn模型 在与原始模型 输入一致的情况下,输出结果与原始模型完全一致。

思路参考:
1)依次对齐对应操作的输出形状(shape)
2)依次对齐对应操作的输出值(至少要保证三位小数是完全一致的)

目标参考:
网络输出层数据完全符合期望。

4.4 操作转换不支持

有些操作Mnn有实现,但相关的转换代码没有实现,这时候我们需要手动转换对应的操作。

解决参考:
1)简单的操作,可以直接使用Demo中的CYOpCreater中定义的操作生成方法生成。
2)也可以手动完成对应操作的生成代码

4.4 操作不支持

有些Mnn本身没有实现,而我们使用又比较着急,此时可以先自行添加对应操作的简易版本。

解决参考:
1)参考Mnn官方文档,添加新操作
文档 - Mnn添加操作
2)有些操作可以基于Mnn已存在的操作添加参数配置逻辑来完成我们的目标

4.5 操作运算异常

Mnn的部分偏门操作还是必可避免的存在一些逻辑bug,造成输出异常。

解决参考:
1)阅读操作源码,理清思路,修改bug。
2)对于比较确认的bug的非补丁式修改,建议向mnn提交merge request,惠及更多的朋友哦~

4.6 网络中间结构复杂

有时候 原始模型(比如pytorch)结构很简单,但是转换成 中间模型(比如onnx)后,部分操作变得异常复杂,更糟糕的是,后续转换的问题也处在这些复杂的操作中。这时候我们或许可以考虑直接 基于原始模型(比如pytorch)的结构 对目标模型(mnn模型)的部分网络结构进行重设计代价很大,但针对老板的一些硬性需求,还是值得一做。

举例:原始模型操作被onnx复杂化:

Mnn Converter Debug [进阶] ---- 微架构设计 & 模型定制化转换概述_第4张图片
某onnx模型中间结构

针对 该种场景,建议将转换设计用绘图的方式绘制出来,因为在调试过程中会难免会对设计的结构进行修改。当对应的操作比较复杂时,这些修改完全基于代码去调试 几乎是一个不可能完成的任务。设计绘图关键要素参考如下:
1)拟定操作类型(如:Reshape)
2)拟定操作名称(如:C0)
3)拟定操作关键参数
4)计算操作输出形状(如1,512,1,119,方便测试比对)

Mnn Converter Debug [进阶] ---- 微架构设计 & 模型定制化转换概述_第5张图片
网络结构重设计举例

结语

定制化模型转换是一个大坑,其中很多具体的填坑场景也是不尽相同的。
该篇文章提供一种基于onnx-->mnn 的调试框架。一定程度上可以简化了模型转换的复杂度。

再次附上Demo的参考链接:
工程Demo Git链接
(基于公司的保密需要,我不方便将具体模型的转换相关的框架应用代码进行分享参考,只能手写一些简单模型,提供一些简单场景的示例,大家见谅咯~)

模型转换不是一件容易的事情
模型转换目前还很难成为一个自动化的工作

Mnn的模型定制化转换更加复杂
基于Mnn本身架构的复杂性(模型的flatbuffers存储、模型运算中间结果数据排布的多样性:NCHW,NC4HW4、Mnn缓存机制的处理、Mnn并发操作代码的阅读等) ,Mnn的模型转换遇到的问题的解决更加复杂。

所以,转换Mnn模型,信心、耐心 每样都必不可少。
希望该篇文章能为转换模型遇到困难无法继续进行的同学提供一些思路的参考,也欢迎这些朋友将遇到的问题分享出来,我们一起讨论、解决、借鉴。

你可能感兴趣的