记录一个解决onnx转ncnn时op不支持的trick,最近准备整理一下使用TNN、MNN和NCNN的系列笔记,好记性不如烂笔头(记性也不好),方便自己以后踩坑的时候爬的利索点~

这里 ,目前70多C++推理例子,能编个lib来用,感兴趣的同学可以看看,就不多介绍了

【Lite.AI.ToolKit : A lite C++ toolkit of awesome AI models】— https://github.com/DefTruth/lite.ai.toolkit 

首先说明一下,选择什么样的推理引擎完全是个人喜好,所以这篇短文仅记录技术问题。今天,想捏一下SCRFD的C++推理,我的习惯通常是同时捏多个版本,比如MNN、NCNN、TNN和ONNXRuntime。今天在将onnx转ncnn的时候遇到无法转换的问题。这里仅简单记录一下,一个取巧的方式。如果你确实希望得到一个ncnn的模型文件,并不在乎是什么方式,那么在你遇到奇怪的op无法转换时,可以尝试下文的方式。

遇到不支持的op

onnx模型文件来源于https://github.com/ppogg/onnx-scrfd-flaskgithub.com/ppogg/onnx-scrfd-flask

嗯,刚开始,我转换时,遇到的问题长这样:

➜ onnx2ncnn SCRFD/scrfd_1g.onnx SCRFD/scrfd_1g.param SCRFD/scrfd_1g.bin
Shape not supported yet!
Gather not supported yet!
  # axis=0
Shape not supported yet!
Gather not supported yet!
  # axis=0
Unsupported unsqueeze axes !
Unsupported unsqueeze axes !
Shape not supported yet!
Unknown data type 0
Unsupported Resize scales and sizes are all empty!
Shape not supported yet!
Gather not supported yet!
  # axis=0
Shape not supported yet!
Gather not supported yet!
  # axis=0
Unsupported unsqueeze axes !
...
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

这?!这一大堆不就是传说中的胶水op?基操onnxsim先过一遍。

➜ python3 -m onnxsim scrfd_1g.onnx scrfd_1g-sim.onnx --dynamic-input-shape --input-shape 1,3,320,320
Simplifying...
Checking 0/3...
Checking 1/3...
Checking 2/3...
Ok!
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

再来转ncnn看看?

➜ onnx2ncnn SCRFD/scrfd_1g-sim.onnx SCRFD/scrfd_1g.param SCRFD/scrfd_1g.bin
Shape not supported yet!
Gather not supported yet!
  # axis=0
Shape not supported yet!
Gather not supported yet!
  # axis=0
Unsupported unsqueeze axes !
Unsupported unsqueeze axes !
Shape not supported yet!
Unknown data type 0
Unsupported Resize scales and sizes are all empty!
Shape not supported yet!
Gather not supported yet!
  # axis=0
Shape not supported yet!
Gather not supported yet!
  # axis=0
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

还是失败了,所以这一堆胶水op要准备造反了??那咋办????

当然,这时还可以考虑参考大佬的文章来手捏param

【nihui:手工优化ncnn模型结构】— https://zhuanlan.zhihu.com/p/93017149

但,问题来了,手工优化ncnn模型结构,这时会遇到两个比较主要的问题:

  • 明显,看这个error,不支持的op在辣么多的位置出现了,手捏的难度太大了,放弃
  • 手捏本身,需要你对ncnn的op十分熟悉,并同时对模型本身的结构十分熟悉,放弃

但是,如果你确实希望得到一个ncnn的模型文件,并不在乎是什么方式,那么在你遇到奇怪的op无法转换时,可以另一个(取巧)的方式。

另一种取巧的方式

这个方式,就是利用TNN。是的,你没听错,就是利用TNN作为中间商\。你的目的是得到一个能用的ncnn文件,所以,不用管是什么方式。经常用TNN的同学一定清楚,ONNX转TNN的模型的时候,可以指定一个 --optimize 参数。指定这个参数后,TNN会对输入的原始onnx文件,先做一个优化,会包含一些胶水op的去除和合并,感觉是和onnxsim做类似的事情,但似乎针对推理引擎本身进行了优化,一些在onnxsim之后依然被保留的胶水op,会被TNN的optimize阶段进行合并或去除,然后得到一个 xxx.opt.onnx 的中间文件。TNN会继续通过这个优化后的onnx文件生成最终的tnn模型文件。

/opt/TNN/tools/convert2tnn# python3 ./converter.py onnx2tnn ./tnn_models/SCRFD/scrfd_1g.onnx -o ./tnn_models/SCRFD/ -optimize -v v1.0 -align -in 1,3,320,320
----------  convert model, please wait a moment ----------
Converter ONNX to TNN Model...
Converter ONNX to TNN check_onnx_dim...
Converter ONNX to TNN check_onnx_dim...
Converter ONNX to TNN model succeed!
----------  align model (tflite or ONNX vs TNN),please wait a moment ----------
input.1: input shape of onnx and tnn is aligned!
Run tnn model_check...
----------  Congratulations!   ----------
The onnx model is aligned with tnn model
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

生成的文件包括:

/opt/TNN/tools/convert2tnn/tnn_models/SCRFD# ls | grep opt
scrfd_1g.opt.onnx
scrfd_1g.opt.tnnmodel
scrfd_1g.opt.tnnproto
scrfd_2.5g.opt.onnx
scrfd_2.5g.opt.tnnmodel
scrfd_2.5g.opt.tnnproto
scrfd_2.5g_bnkps_shape160x160.opt.onnx
scrfd_2.5g_bnkps_shape160x160.opt.tnnmodel
scrfd_2.5g_bnkps_shape160x160.opt.tnnproto
scrfd_2.5g_bnkps_shape320x320.opt.onnx
scrfd_2.5g_bnkps_shape320x320.opt.tnnmodel
scrfd_2.5g_bnkps_shape320x320.opt.tnnproto
scrfd_500m.opt.onnx
scrfd_500m.opt.tnnmodel
scrfd_500m.opt.tnnproto
scrfd_500m_bnkps_shape160x160.opt.onnx
scrfd_500m_bnkps_shape160x160.opt.tnnmodel
scrfd_500m_bnkps_shape160x160.opt.tnnproto
scrfd_500m_bnkps_shape320x320.opt.onnx
scrfd_500m_bnkps_shape320x320.opt.tnnmodel
scrfd_500m_bnkps_shape320x320.opt.tnnproto
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

讲到这里,大家应该想得到,我说的trick是什么了。没错,就是利用这里的 xxx.opt.onnx 去转换ncnn模型。这真是个野路子啊。我们来看看结果如何吧。来转换ncnn试一下

➜  onnx2ncnn SCRFD/scrfd_1g.opt.onnx SCRFD/scrfd_1g.param SCRFD/scrfd_1g.bin
➜  onnx2ncnn SCRFD/scrfd_2.5g.opt.onnx SCRFD/scrfd_2.5g.param SCRFD/scrfd_2.5g.bin
➜  onnx2ncnn SCRFD/scrfd_2.5g_bnkps_shape160x160.opt.onnx SCRFD/scrfd_2.5g_bnkps_shape160x160.param SCRFD/scrfd_2.5g_bnkps_shape160x160.bin
➜  onnx2ncnn SCRFD/scrfd_2.5g_bnkps_shape320x320.opt.onnx SCRFD/scrfd_2.5g_bnkps_shape320x320.param SCRFD/scrfd_2.5g_bnkps_shape320x320.bin
➜  onnx2ncnn SCRFD/scrfd_500m.opt.onnx SCRFD/scrfd_500m.param SCRFD/scrfd_500m.bin
➜  onnx2ncnn SCRFD/scrfd_500m_bnkps_shape160x160.opt.onnx SCRFD/scrfd_500m_bnkps_shape160x160.param SCRFD/scrfd_500m_bnkps_shape160x160.bin
➜  onnx2ncnn SCRFD/scrfd_500m_bnkps_shape320x320.opt.onnx SCRFD/scrfd_500m_bnkps_shape320x320.param SCRFD/scrfd_500m_bnkps_shape320x320.bin
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

舒服~ 都转换成功了,看来TNN的 --optimze 比 onnxsim 会更有针对性一些。我们来用ncnnoptimize过一遍,看看有没有问题。 

➜  ncnnoptimize SCRFD/scrfd_1g.param SCRFD/scrfd_1g.bin SCRFD/scrfd_1g.opt.param SCRFD/scrfd_1g.opt.bin 0
fuse_convolution_activation Conv_0 Relu_1
fuse_convolution_activation Conv_4 Relu_5
fuse_convolution_activation Conv_8 Relu_9
fuse_convolution_activation Conv_12 Relu_13
fuse_convolution_activation Conv_16 Relu_17
fuse_convolution_activation Conv_20 Relu_21
fuse_convolution_activation Conv_24 Relu_25
fuse_convolution_activation Conv_28 Relu_29
fuse_convolution_activation Conv_32 Relu_33
fuse_convolution_activation Conv_36 Relu_37
fuse_convolution_activation Conv_40 Relu_41
fuse_convolution_activation Conv_44 Relu_45
fuse_convolution_activation Conv_48 Relu_49
fuse_convolutiondepthwise_activation Conv_2 Relu_3
fuse_convolutiondepthwise_activation Conv_6 Relu_7
fuse_convolutiondepthwise_activation Conv_10 Relu_11
fuse_convolutiondepthwise_activation Conv_14 Relu_15
fuse_convolutiondepthwise_activation Conv_18 Relu_19
fuse_convolutiondepthwise_activation Conv_22 Relu_23
fuse_convolutiondepthwise_activation Conv_26 Relu_27
fuse_convolutiondepthwise_activation Conv_30 Relu_31
fuse_convolutiondepthwise_activation Conv_34 Relu_35
fuse_convolutiondepthwise_activation Conv_38 Relu_39
fuse_convolutiondepthwise_activation Conv_42 Relu_43
fuse_convolutiondepthwise_activation Conv_46 Relu_47
Input layer input.1 without shape info, shape_inference skipped
Input layer input.1 without shape info, estimate_memory_footprint skipped
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

是的,也很顺利。那么,看到这里,自然而然地,大家就会剩下最后一个疑问了。为了用这种方式,不还得编译个TNN,不也很麻烦?emmmm,其实,不用编译TNN哦。TNN官方提供了tnn_converter的镜像哦,如果你只是为了转换模型,获得中间的 xxx.opt.onnx,而不需要用TNN做推理。那么直接用 tnn_converter的镜像就完事了,不需要编译TNN库。搭建tnn_converter 参考原作者