虽然2020年的时候,飞桨与瑞芯微Rockchip旗下AI芯片RK1808、RK1806正式完成适配,充分兼容飞桨轻量化推理引擎Paddle Lite。但那个时候用起来还是很难。两年之后,再来看这部分,感觉提升的部分很有限,但是思路还是清晰了不少。
瑞芯微对于隔壁的TF和Pytorch的支持其实是比飞桨更完备的,这其中有一部分历史原因,但是随着飞桨的不断发展,之后瑞芯微肯定会对飞桨有更好的支持。
现在想在瑞芯微的板子上跑起飞桨的模型,还是需要先转成onnx,再转成rknn这条道路。

一、飞桨模型的导出

这里我简单使用paddle.vision下的模型做例子。

import paddle
from paddle.vision.models import mobilenet_v1

model = mobilenet_v1(pretrained=True)
# 默认的模型是没有softmax层的,当然在这个结果中找topk也是没有问题的,只是我比较习惯看softmax后的值,所以在此添加一层。
model.fc = paddle.nn.Sequential(model.fc, paddle.nn.Softmax())

print(model)
from paddle.static import InputSpec

model.eval()
x = InputSpec(shape=[None, 3, 224, 224], dtype='float32', name='x')

net = paddle.jit.save(model, path="test", input_spec=[x])

这个时候,会生成三个文件“test.pdiparams”,“test.pdmodel”和“test.pdiparams.info”。前两个是我们要再下一步中使用的。

二、安装paddle2onnx并将模型转为onnx

这里就直接使用pip安装了,具体的使用方法,可以参考
https://github.com/PaddlePaddle/Paddle2ONNX

!pip install paddle2onnx -i https://mirror.baidu.com/pypi/simple
!paddle2onnx --version
!paddle2onnx --model_dir . --model_filename test.pdmodel --params_filename test.pdiparams --save_file test.onnx --enable_dev_version True
Looking in indexes: https://mirror.baidu.com/pypi/simple
Requirement already satisfied: paddle2onnx in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (0.9.8)

[1m[[0m[34;49mnotice[0m[1;39;49m][0m[39;49m A new release of pip available: [0m[31;49m22.1.2[0m[39;49m -> [0m[32;49m22.2.1[0m
[1m[[0m[34;49mnotice[0m[1;39;49m][0m[39;49m To update, run: [0m[32;49mpip install --upgrade pip[0m
2022-08-02 00:06:33 [INFO]	paddle2onnx-0.9.8 with python>=3.6, paddlepaddle>=2.0.0
[Paddle2ONNX] Start to parse PaddlePaddle model...
[Paddle2ONNX] Model file path: ./test.pdmodel
[Paddle2ONNX] Paramters file path: ./test.pdiparams
[Paddle2ONNX] Start to parsing Paddle model...
[Paddle2ONNX] Use opset_version = 9 for ONNX export.
[Paddle2ONNX] PaddlePaddle model is exported as ONNX format now.
2022-08-02 00:06:33 [INFO]	===============Make PaddlePaddle Better!================
2022-08-02 00:06:33 [INFO]	A little survey: https://iwenjuan.baidu.com/?code=r8hu2s

如果,你厌倦(突然来点翻译腔?)了上面两个步骤,咳咳,其实可以直接通过下面这段代码来获得onnx模型,现在paddle自带了onnx的导出(但是貌似还是要先安装paddle2onnx,阿这…)

from paddle.static import InputSpec

save_path = 'test'
x = InputSpec([None, 3, 224, 224], 'float32', 'x')
paddle.onnx.export(model, save_path, input_spec=[x])
2022-08-03 10:01:54 [INFO]	Static PaddlePaddle model saved in paddle_model_static_onnx_temp_dir.
[Paddle2ONNX] Start to parse PaddlePaddle model...
[Paddle2ONNX] Model file path: paddle_model_static_onnx_temp_dir/model.pdmodel
[Paddle2ONNX] Paramters file path: paddle_model_static_onnx_temp_dir/model.pdiparams
[Paddle2ONNX] Start to parsing Paddle model...
[Paddle2ONNX] Use opset_version = 9 for ONNX export.
[Paddle2ONNX] PaddlePaddle model is exported as ONNX format now.
2022-08-03 10:01:55 [INFO]	ONNX model saved in test.onnx.

如果对自己转出来的模型感兴趣或者想看具体的结构,可以通过https://netron.app/ 来查看自己转出来的模型。效果大致是这个样子:

飞桨NLP模型本地部署 飞桨与pytorch_paddle

当然,你也可以通过下面的代码直接校验你的onnx模型。主要包括两个方面,一是算子是否符合对应版本的协议,二是网络结构是否完整。

!pip install onnx -i https://mirror.baidu.com/pypi/simple

import onnx

onnx_file = 'test.onnx'
onnx_model = onnx.load(onnx_file)
onnx.checker.check_model(onnx_model)
print('The model is checked!')
The model is checked!

在传出onnx后,下一步则是转为rknn模型。

三、使用rknn-toolkit,将onnx转成rknn格式

rknn-toolkit的地址如下,
https://github.com/rockchip-linux/rknn-toolkit

正如其readme中所说:RKNN-Toolkit is a software development kit for users to perform model conversion, inference and performance evaluation on PC, RK3399Pro(D), RK1806, RK1808, RV1109, RV1126.
其支持的板子写的很明确,如果你是rk3588之类的板子,则不在是使用这个仓库了。
至于这个rknn_toolkit的安装,是一件比较坑的事,它对于linux提供的版本有限,看了一眼aistudio的环境,竟然还装不上…(此处无语三秒钟)
那就在本地完成下述操作吧:

3.1 按照仓库中的指引,安装好rknn_toolkit这个库
3.2 转换代码
from ast import arg
from rknn.api import RKNN
import os
import argparse

def ONNX2RKNN(args):
    rknn = RKNN(verbose=True)

    rknn.config(batch_size=4, mean_values=[[0.485, 0.456, 0.406]], std_values=[[0.229, 0.224, 0.225]], reorder_channel='0 1 2', target_platform='rv1126')

    ret = rknn.load_onnx(args.model, inputs=['x'], input_size_list=[[3, 224, 224]], outputs=['softmax_5.tmp_0'])
    
    
    rknn.build(do_quantization=True, dataset="PATH_TO_YOUR_DATA.TXT", pre_compile=True)

    if args.output is None:
        export_rknn_model_path = args.model[:-4] + "rknn"
    else:
        export_rknn_model_path = args.output
    
    rknn.export_rknn(export_path=export_rknn_model_path)

    rknn.release()

if __name__ == '__main__':

    parser = argparse.ArgumentParser()
    parser.add_argument("--model", type=str, required=True)
    parser.add_argument("--output", type=str, default=None)

    args = parser.parse_args()

    ONNX2RKNN(args)
3.3 代码解释:

rknn.load_onnx中的x与softmax_5.tmp_0是我通过netron查看后添加的,实际上这两个参数不写应该也没问题,这里写了其实是想告诉大家对于多个输入时可以指定对应的归一化方式。

rknn.build那里可以选择不量化,而pre_compile部分则是相当于是一个小小的加速,不然的话,在实际跑的时候,会发现光是加载模型就要花十几秒甚至几十秒的时间。

四、通过可视化工具,从onnx转到rknn

如果你安装好了rknn_toolkit,那么可以通过

python -m rknn.bin.visualization

的方式,打开rknn的可视化模型转换工具。(可以看到它对隔壁的模型支持得挺好的,哈哈哈)

飞桨NLP模型本地部署 飞桨与pytorch_paddle_02

飞桨NLP模型本地部署 飞桨与pytorch_paddlepaddle_03

基本很傻瓜,只要理解上面的代码,就可以通过填空/选择的方式完成转换模型。

五、效果展示

使用rknn_toolkit中提供的mobilenet的例子(其实对于大多数的分类模型都可以使用这个历程,只是一个input-output的过程,至于后处理,这里一般也没有。)

运行的结果大致如下

飞桨NLP模型本地部署 飞桨与pytorch_飞桨NLP模型本地部署_04

总结

简单介绍了从飞桨模型转换到ONNX再RKNN模型的过程,展示了两种转换方法,之后会进一步介绍模型在瑞芯微板子上的运行以及对比。