赞
踩
一. 基本介绍
需要的工程文件:rknn_toolkit2-1.3.0和rknpu2(具体下载方法以及介绍后面有介绍)
官方教程1:
官方教程2:
下载完rknn-toolkit SDK后,目录下会有Rockchip_Quick_Start_RKNN_SDK_V1.3.0_CN.pdf
非官方教程1:
非官方教程2:
rk3588对npu的再探索,yolov5使用rknn模型推理教程
RKNN模型:
RKNN是Rockchip NPU平台使用的模型类型,以.rknn后缀结尾的模型文件。用户可以通过RKNN SDK提供的工具将自主研发的算法模型转换成RKNN模型。如果已有以.rknn后缀结尾的模型文件,也就是属于Rockchip NPU平台适用的模型类型RKNN,就直接在Linux平台上通过交叉编译得到可执行文件, 然后将可执行文件及对应的库文件,.rknn后缀结尾的模型文件以及图片文件等输入资源拷贝至RK3588进行模型的运行。
非RKNN模型:
对于Ternsorflow, PyTorch等其他模型,想要在RK3588平台运行,需要先进行模型转换。可以在搭载Ubuntu18.04以及以上版本的PC上使用RKNN-Toolkit2工具将模型转化为RKNN格式,在按照前一类方法将其交叉编译后部署到开发板上。
总体开发流程(以pytorch框架开发,C++部署):
总体开发流程(以pytorch框架开发,python部署)
二. 安装RKNN-Toolkit2(PC端)
RKNN-Toolkit2目前版本适用ubuntu18.04(x64)及以上,工具只能安装在PC上,暂不支持Windows, MacOS, Debian等操作系统。
注意PC是X86架构,板子是arm架构的。安装时轮子(.whl文件)的名字里有x86字样的是安装在PC端的(具体位置在rknn-toolkit2-1.3.0/packages下)。轮子名字里有aarch64字样的,是rknn-toolkit-lite,是官方为了在板端方便部署开发的工具(具体位置在rknn-toolkit2-1.3.0/rknn_toolkit_lite2/package下)。
总结来说,rknn-toolkit2适用于x86架构,要安装在PC端,功能下面有介绍。
rknn-toolkit2-lite适用于arm架构,要安装在板子上,是为了方便在板端使用python部署用的。
RKNN-Toolkit2工具功能介绍:
RKNN-Toolkit2-Lite功能介绍:
step1.下载RKNN-Toolkit2
方法1:使用git clone
git clone https://github.com/rockchip-linux/rknn-toolkit2.git
方法2:使用百度网盘下载
下载得到的文件夹包含RK_NPU_SDK_1.2.0和RK_NPU_SDK_1.3.0, 我们使用1.3.0版本,其中RK_NPU_SDK_1.3.0/rknn_toolkit2-1.3.0文件夹中包含:
step2.安装依赖
首先使用anaconda建立一个python3.6的环境(官方提供的也有python3.8的安装包,但是我安装之后在转换模型时遇到了版本相关的报错,所以建议安装python3.6版本):
conda create -n deploy python=3.6
安装rknn-toolkit2依赖:
sudo apt-get install libxslt1-dev zlib1g-dev libglib2.0 libsm6 libgl1-mesa-glx libprotobuf-dev gcc
cd rknn-toolkit2-1.3.0/doc
pip install -r requirements_cp36-1.3.0.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
如果报错BUG:ERROR: Cannot uninstall ‘certifi’. It is a distutils installed project and thus we cannot accurately determine which files belong to it which would lead to only a partial uninstall, 解决方案:
pip install certifi --ignore-installed
pip install certifi --ignore-installed -i https://pypi.tuna.tsinghua.edu.cn/simple
如果报错说没有匹配版本,则更新pip:
pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple
如果中途显示bfloat16安装失败,则手动安装一下numpy:
pip install numpy==1.16.6
step3.安装rknn-toolkit2(有时候会遇到版本不匹配问题,多试几次就好了)
cd rknn-toolkit2-1.3.0/packages
pip install rknn_toolkit2-1.3.0_11912b58-cp36-cp36m-linux_x86_64.whl
step4: 检查是否安装成功
conda activate deploy
python
from rknn.api import RKNN
如果没报错则为成功
三. Pytorch转RKNN模型(在PC端进行)
路线1:.pth --> .onnx --> .rknn
step1: .pth --> .onxx
step2: .onxx --> .rknn
示例1:1
import cv2 import numpy as np from rknn.api import RKNN import os if __name__ == '__main__': platform = 'rk3566' exp = 'yolov5s' Width = 640 Height = 640 MODEL_PATH = './onnx_models/yolov5s_rm_transpose.onnx' NEED_BUILD_MODEL = True # NEED_BUILD_MODEL = False im_file = './dog_bike_car_640x640.jpg' # Create RKNN object rknn = RKNN() OUT_DIR = "rknn_models" RKNN_MODEL_PATH = './{}/{}_rm_transpose.rknn'.format(OUT_DIR,exp+'-'+str(Width)+'-'+str(Height)) if NEED_BUILD_MODEL: DATASET = './dataset.txt' rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform="rk3588") # Load model print('--> Loading model') ret = rknn.load_onnx(MODEL_PATH) if ret != 0: print('load model failed!') exit(ret) print('done') # Build model print('--> Building model') ret = rknn.build(do_quantization=True, dataset=DATASET) if ret != 0: print('build model failed.') exit(ret) print('done') # Export rknn model if not os.path.exists(OUT_DIR): os.mkdir(OUT_DIR) print('--> Export RKNN model: {}'.format(RKNN_MODEL_PATH)) ret = rknn.export_rknn(RKNN_MODEL_PATH) if ret != 0: print('Export rknn model failed.') exit(ret) print('done') else: ret = rknn.load_rknn(RKNN_MODEL_PATH) rknn.release()
示例2:
from rknn.api import RKNN import os if __name__ == '__main__': platform = 'rk3588' '''step 1: create RKNN object''' rknn = RKNN() '''step 2: load the .onnx model''' rknn.config(target_platform='rk3588') print('--> Loading model') ret = rknn.load_onnx('actor_simple.onnx') if ret != 0: print('load model failed') exit(ret) print('done') '''step 3: building model''' print('-->Building model') ret = rknn.build(do_quantization=False) if ret != 0: print('build model failed') exit() print('done') '''step 4: export and save the .rknn model''' OUT_DIR = 'rknn_models' RKNN_MODEL_PATH = './{}/actor_simple.rknn'.format(OUT_DIR) if not os.path.exists(OUT_DIR): os.mkdir(OUT_DIR) print('--> Export RKNN model: {}'.format(RKNN_MODEL_PATH)) ret = rknn.export_rknn(RKNN_MODEL_PATH) if ret != 0: print('Export rknn model failed.') exit(ret) print('done') '''step 5: release the model''' rknn.release()
路线2:.pth --> .rknn
四. 进行推理
共有四种方式,如下所示:
1.在PC上仿真运行(以官方代码举例)
RKNN-Toolkit2自带了一个模拟器,直接在PC上运行Demo即是将转换后的模型部署到仿真NPU上运行。
cd RKNN_SDK/RK_NPU_SDK_1.3.0/rknn-toolkit2-1.3.0/examples/tflite/mobilenet_v1
conda activate deploy
python test.py
2.板子连PC测试自己的RKNN模型(以自己的代码举例):
step1: 使用USB-typeC连接线连接板端typeC口以及电脑的USB口
step2: 安装adb
查看设备ID
如果没有板子的ID号,可能是数据线连接问题,尝试换条数据线试一下
step3: 更新板子的rknn_server和librknnrt.so
librknnrt.so: 是一个板端的runtime库
rknn_server: 是一个运行在板子上的后台代理服务,用于接收PC通过USB传输过来的协议,然后执行板端runtime接口,并返回结果给PC。
下载RKNPU工程:
git clone https://github.com/rockchip-linux/rknpu2
Linux
adb push rknpu2/runtime/RK3588/Linux/rknn_server/aarch64/usr/bin/rknn_server /usr/bin/
adb push rknpu2/runtime/RK3588/Linux/librknn_api/aarch64/librknnrt.so /usr/lib/
adb push rknpu2/runtime/RK3588/Linux/librknn_api/aarch64/librknn_api.so /usr/lib/
# 可以使用 "systemctl status rknn_server" 查看rknn_server服务是否处于运行状态
# 若没有运行,请在板子的串口终端运行rknn_server
chmod +x /usr/bin/rknn_server
/usr/bin/rknn_server
查看是否更新成功
adb shell
su
setenforce 0
pgrep rknn_server
查看到有rknn_server进程id,更新成功。
step4: 测试
from rknn.api import RKNN import numpy as np import env_class import time env = env_class model = 'rknn_models/actor.rknn' '''step 1: Initialize''' rknn = RKNN() '''step2: Load RKNN model''' print('--> Load RKNN model') ret = rknn.load_rknn(path=model) if ret != 0: print('Load RKNN model failed') exit(ret) print('done') '''step3: Init runtime environment''' print('--> Init runtime environment') ret = rknn.init_runtime(target='RK3588', device_id='6b7458087c77686b') if ret != 0: print('Init runtime environment failed') exit(ret) print('done') '''step4: Inference''' for j in range(n_eval): o, info = env.reset() r = 0 d = False ep_ret = 0 ep_len = 0 while not (d or (ep_len == 1000)): o = np.expand_dims(o, axis=0).astype(np.float32) time0 = time.time() a = rknn.inference(inputs=[o])[0][0] time1 = time.time() print('The time spent is:', time1 - time0) o, r, d, _ = env.step(a) ep_ret += r ep_len += 1 ep_return_list.append(ep_ret) print('The rewards are:', ep_return_list) '''step5: Release''' rknn.release()
上面代码中target后面接的是板子的型号,device_id是通过abd deives得到的ID值
3. 在RK3588板子上调试
3.1 以官方代码举例
step1: 下载RKNPU2工程(在PC端)
git clone https://github.com/rockchip-linux/rknpu2.git
RKNPU工程说明:
1)doc:包含RKNN编译器支持的操作符说明,快速入门手册,以及C接口API说明
2)examples:
3)runtime: 包含动态链接库.so和头文件.h
其中我们所需要文件的在runtime/RK3588/Linux下
step2: 编译rknn_yolov5_demo(在PC端)
首先需要安装交叉编译器,方法如下:[2]
sudo apt-get install gcc-9-aarch64-linux-gnu
sudo apt-get install g++-9-aarch64-linux-gnu
sudo apt --fix-broken install
sudo apt-get install gcc-aarch64-linux-gnu
sudo apt-get install g++-aarch64-linux-gnu
aarch64-linux-gnu-g++ -v
aarch64-linux-gnu-gcc -v
然后就可以进行编译了
cd rknpu2/examples/rknn_yolov5_demo/
./build-linux_RK3588.sh
编译后的结果会放在install目录下,将install里所有的文件,包含RKNN模型,库文件和可执行文件等,使用adb命令或者直接复制等方法拷贝至开发板。
step3: 在开发板上测试(在板端)
cd到install/rknn_yolov5_demo_Linux/目录下,然后输入指令
./rknn_yolov5_demo ./model/RK3588/yolov5s-640-640.rknn ./model/bus.jpg
推理结束后可以查看得到的目标检测结果
3.2 参考官方代码,我们实现了一个简单的MLP的部署流程
step1: 参考上面的介绍安装交叉编译器
step2: 新建一个文件夹用来放置代码,例如名为rknn_model_test。在该文件夹下新建include文件夹,里面放置rknn_api.h(该头文件在rknpu2/runtime/RK3588/Linux/librknn_api/include下)。新建lib文件夹,里面放置librknnrt.so文件(该动态库文件包含在rknpu2/runtime/RK3588/Linux/librknn_api/aarch64下)。新建model文件夹,放入之前生成的xxx.rknn模型。
step3: 编写rknn_model.cpp(以通用API接口为例,代码中所用到的API下文中都有介绍)
#include "rknn_api.h" #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <memory.h> #include <fstream> #include <iostream> const int state_dim = 34; const int act_dim = 12; using namespace std; static unsigned char *load_model(const char *filename, int *model_size) { FILE *fp = fopen(filename, "rb"); if (fp == nullptr) { printf("fopen %s fail!\n", filename); return NULL; } fseek(fp, 0, SEEK_END); int model_len = ftell(fp); unsigned char *model = (unsigned char *)malloc(model_len); fseek(fp, 0, SEEK_SET); if (model_len != fread(model, 1, model_len, fp)) { printf("fread %s failed!\n", filename); free(model); return NULL; } *model_size = model_len; if (fp) { fclose(fp); } return model; } float input_data[state_dim] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; int main(int argc, char **argv) { int ret = 0; int model_len = 0; unsigned char *model = nullptr; rknn_context ctx = 0; const char *model_path = argv[1]; // load RKNN Model printf("Loading model ...\n"); model = load_model(model_path, &model_len); printf("rknn_init ...\n"); ret = rknn_init(&ctx, model, model_len, 0, NULL); if (ret < 0) { printf("rknn_init fail! ret=%d\n", ret); } // Get Model Input Ouput Info rknn_input_output_num io_num; ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num)); if (ret != RKNN_SUCC) { printf("rknn_query fail! ret=%d\n", ret); return -1; } printf("model input num:%d, output num: %d\n", io_num.n_input, io_num.n_output); // Set Input data rknn_input inputs[1]; memset(inputs, 0, sizeof(inputs)); inputs[0].index = 0; inputs[0].buf = input_data; inputs[0].size = state_dim * sizeof(float); inputs[0].pass_through = 0; inputs[0].type = RKNN_TENSOR_FLOAT16; inputs[0].fmt = RKNN_TENSOR_NHWC; ret = rknn_inputs_set(ctx, io_num.n_input, inputs); if (ret < 0) { printf("rknn_input_set fail! ret=%d\n", ret); return -1; } // Run printf("rknn_run\n"); ret = rknn_run(ctx, nullptr); if (ret < 0) { printf("rknn_run fail! ret=%d\n", ret); return -1; } // Get output rknn_output outputs[io_num.n_output]; memset(outputs, 0, sizeof(outputs)); for (int i = 0; i < io_num.n_output; i++) { outputs[i].want_float = 1; } ret = rknn_outputs_get(ctx, io_num.n_output, outputs, NULL); if (ret < 0) { printf("rknn_outputs_get fail! ret=%d\n", ret); } /*------------------------------------------*/ cout << "The results are" << endl; float *actions = (float *)(outputs[0].buf); for (int i = 0; i < act_dim; i++) { cout << actions[i] << " "; } /*------------------------------------------*/ // release outputs ret = rknn_outputs_release(ctx, io_num.n_output, outputs); // destory if (ctx > 0) { rknn_destroy(ctx); } if (model) { free(model); } return 0; }
或者rknn_model.cpp(以零拷贝接口为例):
#include "rknn_api.h" #include <float.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/time.h> const int batch_size = 1; const int state_dim = 34; const int act_dim = 12; using namespace std; static inline int64_t getCurrentTimeUs() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * 1000000 + tv.tv_usec; } static void dump_tensor_attr(rknn_tensor_attr *attr) { printf(" index=%d, name=%s, n_dims=%d, dims=[%d, %d, %d, %d], n_elems=%d, size=%d, fmt=%s, type=%s, qnt_type=%s, " "zp=%d, scale=%f\n", attr->index, attr->name, attr->n_dims, attr->dims[0], attr->dims[1], attr->dims[2], attr->dims[3], attr->n_elems, attr->size, get_format_string(attr->fmt), get_type_string(attr->type), get_qnt_type_string(attr->qnt_type), attr->zp, attr->scale); } /*------------------------------------------- Main Functions -------------------------------------------*/ int main(int argc, char *argv[]) { char *model_path = argv[1]; rknn_context ctx = 0; int loop_count = 1; // Load RKNN model int model_len = 0; int ret = rknn_init(&ctx, &model_path[0], 0, 0, NULL); if (ret < 0) { printf("rknn_init fail! ret=%d\n", ret); } // Get sdk and driver version rknn_sdk_version sdk_ver; ret = rknn_query(ctx, RKNN_QUERY_SDK_VERSION, &sdk_ver, sizeof(sdk_ver)); if (ret != RKNN_SUCC) { printf("rknn_query fail! ret=%d\n", ret); return -1; } printf("rknn_api/rknnrt version: %s, driver version: %s\n", sdk_ver.api_version, sdk_ver.drv_version); // Get Model Input Output Info rknn_input_output_num io_num; ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num)); if (ret != RKNN_SUCC) { printf("rknn_query fail! ret=%d\n", ret); return -1; } printf("model input num: %d, output num: %d\n", io_num.n_input, io_num.n_output); printf("input tensors:\n"); rknn_tensor_attr input_attrs; memset(&input_attrs, 0, sizeof(rknn_tensor_attr)); input_attrs.index = 0; ret = rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &(input_attrs), sizeof(rknn_tensor_attr)); if (ret < 0) { printf("rknn_init error! ret=%d\n", ret); return -1; } input_attrs.type = RKNN_TENSOR_FLOAT32; input_attrs.size = input_attrs.size_with_stride = input_attrs.n_elems * sizeof(float); dump_tensor_attr(&input_attrs); printf("output tensors:\n"); rknn_tensor_attr output_attrs; memset(&output_attrs, 0, sizeof(rknn_tensor_attr)); output_attrs.index = 0; ret = rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs), sizeof(rknn_tensor_attr)); if (ret != RKNN_SUCC) { printf("rknn_query fail! ret=%d\n", ret); return -1; } output_attrs.type = RKNN_TENSOR_FLOAT32; output_attrs.size = output_attrs.size_with_stride = output_attrs.n_elems * sizeof(float); dump_tensor_attr(&output_attrs); float input_data[batch_size][state_dim] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; // create input tensor memory rknn_tensor_mem *input_mems; input_mems = rknn_create_mem(ctx, input_attrs.size); // create output tensor memory rknn_tensor_mem *output_mems; output_mems = rknn_create_mem(ctx, output_attrs.size); // Set input tensor memory ret = rknn_set_io_mem(ctx, input_mems, &input_attrs); if (ret < 0) { printf("rknn_set_io_mem fail! ret=%d\n", ret); return -1; } ret = rknn_set_io_mem(ctx, output_mems, &output_attrs); if (ret < 0) { printf("rknn_set_io_mem fail! ret=%d\n", ret); return -1; } // copy input data to input tensor memory memcpy(input_mems->virt_addr, input_data, sizeof(input_data)); // Run printf("Begin perf ...\n"); for (int i = 0; i < loop_count; ++i) { int64_t start_us = getCurrentTimeUs(); ret = rknn_run(ctx, NULL); int64_t elapse_us = getCurrentTimeUs() - start_us; if (ret < 0) { printf("rknn run error %d\n", ret); return -1; } printf("%4d: Elapse Time = %.2fms, FPS = %.2f\n", i, elapse_us / 1000.f, 1000.f * 1000.f / elapse_us); } // print the result float *buffer = (float *)(output_mems->virt_addr); for (int i = 0; i < act_dim; i++) { printf("%f", buffer[i]); } // Destroy rknn memory rknn_destroy_mem(ctx, input_mems); rknn_destroy_mem(ctx, output_mems); // Destroy rknn_destroy(ctx); return 0; }
step4: 编写CMakeLists.txt
cmake_minimum_required(VERSION 3.10) project(rknn_test) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") include_directories(${CMAKE_SOURCE_DIR}/include) add_executable(rknn_test rknn_model.cpp) set(RKNN_RT_LIB ${CMAKE_SOURCE_DIR}/lib/librknnrt.so) target_link_libraries(rknn_test ${RKNN_RT_LIB}) # install target and libraries set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/install/rknn_test_${CMAKE_SYSTEM_NAME}) install(TARGETS rknn_test DESTINATION ./) install(PROGRAMS ${RKNN_RT_LIB} DESTINATION lib) install(DIRECTORY model DESTINATION ./)
step5:编写build-linux_RK3588.sh
set -e TARGET_SOC="rk3588" GCC_COMPILER=aarch64-linux-gnu export LD_LIBRARY_PATH=${TOOL_CHAIN}/lib64:$LD_LIBRARY_PATH export CC=${GCC_COMPILER}-gcc export CXX=${GCC_COMPILER}-g++ ROOT_PWD=$( cd "$( dirname $0 )" && cd -P "$( dirname "$SOURCE" )" && pwd ) # build BUILD_DIR=${ROOT_PWD}/build/build_linux_aarch64 if [[ ! -d "${BUILD_DIR}" ]]; then mkdir -p ${BUILD_DIR} fi cd ${BUILD_DIR} cmake ../.. -DCMAKE_SYSTEM_NAME=Linux -DTARGET_SOC=${TARGET_SOC} make -j4 make install cd -
step6: 编译,在终端输入
bash build-linux_RK3588.sh
step7: 运行
经过step6编译之后会生成build文件夹和install文件夹。install文件夹下包含生成的二进制文件,lib库和model文件夹(对应了CMakeLists.txt中的install指令)。将install文件夹拷贝到板子上,然后输入如下指令,赋予二进制文件权限:
接下来运行即可:
./rknn_test ./model/xxx.rknn
4. 在RK3588板子上调试(python)
step1: 在板子上安装rknn-toolkit-lite
tknn-toolkit-lite的官方手册在rknn-toolkit2-1.3.0/rknn_toolkit_lite2/docs下,官方说需要运行在Debian10/11(aarch64)操作系统,经验证ubuntu20.04和ubuntu22.04也可安装并使用。
ubuntu20.04的python版本是3.8,但是rknn-toolkit-lite需要的安装环境是python3.7或者python3.9。尝试过在系统路径安装python3.9,并将系统python3版本切换为python3.9, 安装后无法使用并且terminal也无法打开。安装miniconda并新建python3.9环境安装成功。
1)下载miniconda并安装
选择适用于aarch64架构的版本(如上图所示)。得到xxx.sh文件,新建终端,输入
bash Miniconda3-latest-Linux-aarch64.sh
然后一路回车+yes(需要输入两次)
完成后在终端输入
查看是否安装成功。
2)新建并激活python3.9环境
conda create -n rknnlite python=3.9
conda activate rknnlite
3)安装轮子
cd rknn-toolkit2-1.3.0/rknn_toolkit_lite2/packages
pip install rknn_toolkit_lite2-1.3.0-cp39-cp39-linux_aarch64.whl
4)测试
python
from rknnlite.api import RKNNLite
如果能够正常导入,则安装成功
step 2: 使用官方例子测试
conda activate rknnlite
cd rknn-toolkit2-1.3.0/rknn_toolkit_lite2/examples/inference_with_lite
python test.py
或者使用自己的代码测试:
import numpy as np from rknnlite.api import RKNNLite import env_class import time env = env_class() model = 'rknn_models.rknn' '''step1: Initialize''' rknn_lite = RKNNLite() '''step2: load RKNN model''' print('--> Load RKNN model') ret = rknn_lite.load_rknn(model) if ret != 0: print('Load RKNN model failed') exit(ret) print('done') '''step3: Init runtime environment''' print('--> Init runtime environment') ret = rknn_lite.init_runtime(core_mask=RKNNLite.NPU_CORE_0) if ret != 0: print('Init runtime environment failed') exit(ret) print('done') '''step4: Inference''' for j in range(n_eval): o, info = env.reset() r = 0 d = False ep_ret = 0 ep_len = 0 while not (d or (ep_len == 50)): o = np.expand_dims(o, axis=0).astype(np.float32) time0 = time.time() a = rknn_lite.inference(inputs=[o])[0][0] time1 = time.time() print('The time spent is:', time1 - time0) o, r, d, _ = env.step(a) ep_ret += r ep_len += 1 np.append(ep_return_list, ep_ret) print('The rewards are:', ep_return_list) '''step5: Release''' rknn_lite.release()
可以看到RKNN(运行在PC端,进行连板推理)和RKNN-lite(运行在板端)的用法基本一样,都是先创建对象,然后导入RKNN模型,接下来初始化运行时环境,再进行推理,结束后释放对象。接口的用法也基本一样,唯一的不同在于初始化运行时的时候,连板推理需要指定设备和ID号,板端推理需要设置工作的核心(有0,1,2三个核可以选择)。
五. 板端代码开发(C++ API)
1.C接口API说明
在使用RKNN SDK之前,用户首先需要使用RKNN-Toolkit2工具将用户的模型转换为RKNN模型。
得到RKNN模型文件之后,用户可以选择使用C接口在RK3588平台开发使用。
对于RK3588, SDK库文件为rknpu/runtime/RK3588/Linux/librknn_api/aarch64下的librknnrt.so
2.API流程说明
RK3588上有两组API可以使用,分别是通用API接口和零拷贝流程API接口。
两组API的主要区别在于,通用接口API每次更新帧数据,需要将外部模块分配的数据拷贝到NPU运行时的输入内存。而零拷贝流程的接口会直接使用预先分配的内存(包括NPU运行时创建的或外部其他框架创建的,比如DRM框架),减少了内存拷贝的花销。
当用户输入数据只有虚拟地址时,只能使用通用API接口,当用户输入数据有物理地址或者fd时,两组接口都可以使用。
对于通用API接口,首先初始化rknn_input结构体,帧数据包含在该结构体中,使用RKNN_inputs_set函数设置模型输入,等待推理结束后,使用rknn_outputs_get函数获取推理的输出,进行后处理。在每次推理前,更新帧数据。通用API调用流程如下所示,黄色字体代表用户行为。
通用API接口示例代码可以参考:rknpu2/examples下
通用API接口内部存在以下两种处理流程:
1)int8量化模型且输入通道数是1或3或4
2)输入通道数是2或大于等于4的量化模型或非量化模型
============================== 手动分割线=======================================
对于零拷贝API接口,在分配内存后使用内存信息初始化rknn_tensor_memory结构体,在推理前创建并设置该结构体,并在推理后读取该结构体中的内存信息。根据用户是否需要自行分配模型的模块内存(输入/输出/权重/中间结果)和内存表示方式(文件描述符/物理地址等)差异,有下列三种典型的零拷贝调用流程:输入/输出内存由运行时分配(如下图所示);输入/输出内存由外部分配;输入/输出/权重/中间结果内存由外部分配。
如上图所示,rknn_create_mem接口创建的输入/输出内存信息结构体包含了文件描述符成员和物理地址,RGA的接口使用到NPU分配的内存信息,preprocess_rga表示RGA的接口,steam_fd表示RGA的接口输入源的内存数据,postprocess_cpu表示处理后的CPU实现。
零拷贝API接口示例代码可以参考:rknpu2/examples下
零拷贝场景的使用条件如下:
1)输入通道数是1或3或4
2)RK356X输入的宽度是8像素对齐,RK3588和RV1106/RV1103输入宽度是16像素对齐
3)int8非对称量化模型
============================== 手动分割线=======================================
3.常用API(具体说明在rknpu2/doc/Rockchip_RKNPU_User_Guide_RKNN_API_V1.4.0_CN.pdf中)
(1) rknn_init: 初始化函数将创建rknn_context对象、加载RKNN模型以及根据flag和rknn_init_extend结构体执行特定的初始化行为。
rknn_context ctx;
int ret = rknn_init(&ctx, model_data, model_data_size, 0, NULL);
(2) rknn_set_core_mask: 指定函数的NPU核心,该函数仅支持RK3588平台。
rknn_context ctx;
rknn_core_mask core_mask = RKNN_NPU_CORE_0;
int ret = rknn_set_core_mask(ctx, core_mask);
(3) rknn_dup_context: 生成一个指向用一个模型的新context, 可用于多线程执行相同模型时的权重复用,支持RK3588芯片。
rknn_context ctx_in;
rknn_context ctx_out;
int ret = rknn_dup_context(&ctx_in, &ctx_out);
(4) rknn_destory: 将释放传入的rknn_context及其相关资源
rknn_context ctx;
int ret = rknn_destroy (ctx);
(5) rknn_query: 能够查询获取到模型输入输出信息、逐层运行时间、模型推理的总时间、SDK版本、内存占用信息、用户自定义字符串等信息。
(6) rknn_inputs_set: 设置模型的输入数据,该函数能够支持多个输入,其中每个输入是rknn_input结构体对象,在传入之前用户需要设置该对象。
(7) rknn_run: 执行一次推理,调用之前需要先通过rknn_inputs_set函数或者零拷贝的接口设置输入数据
ret = rknn_run(ctx, NULL);
(8) rknn_wait: 用于非阻塞模式推理,目前暂未实现。
(9) rknn_outputs_get: 获取模型推理的输出数据,该函数能够一次获取多个输出数据,其中每个输出是rknn_output结构体对象,在函数调用之前需要依次创建并设置每个rknn_output对象。
(10) rknn_outputs_release: 释放rknn_output_get函数得到的输出相关资源。
ret = rknn_outputs_release(ctx, io_num.n_output, outputs);
(11) rknn_create_mem_from_mb_blk: 目前暂未实现。
(12) rknn_create_mem_from_phys: 当用户要自己分配内存让NPU使用时,通过该函数可以创建一个rknn_tensor_mem结构体并得到它的指针,该函数通过传入物理地址、虚拟地址以及大小,外部内存相关的信息会赋值给rknn_tensor_mem结构体。
(13) rknn_create_mem_from_fd: 当用户要自己分配内存让NPU使用时,rknn_create_mem_from_fd函数可以创建一个rknn_tensor_mem结构体并得到它的指针,该函数通过传入文件描述符fd、偏移、虚拟地址以及大小,外部内存相关的信息会赋值给rknn_tensor_mem结构体。
(14) rknn_create_mem: 当用户要NPU内部分配内存时,rknn_create_mem函数可以创建一个rknn_tensor_mem结构体并得到它的指针,该函数通过传入内存大小,运行时会初始化rknn_tensor_mem结构体。
(15) rknn_destory_mem: 销毁rknn_tensor_mem结构体,用户分配的内存需要自行释放。
(16) rknn_set_weight_mem: 如果用户自己为网络权重分配内存,初始化相应的rknn_tensor_mem结构体后,在调用rknn_run前,通过rknn_set_weight_mem函数可以让NPU使用该内存。
(17) rknn_set_internal_mem: 如果用户自己为网络中间tensor分配内存,初始化相应的rknn_tensor_mem结构体后,在调用rknn_run之前,通过rknn_set_internal_mem函数可以让NPU使用该内存。
(18) rknn_set_io_mem: 如果用户自己为网络输入/输出tensor分配内存,初始化相应的rknn_tensor_mem结构体后,在调用rknn_run之前,通过rknn_set_io_mem函数可以让NPU使用该内存。
4.RKNN数据结构定义
(1) rknn_sdk_version: 用来表示RKNN SDK版本信息
(2) rknn_input_output_num: 表示输入输出tensor个数
(3) rknn_tensor_attr: 表示模型的tensor的属性
(4) rknn_perf_detail: 表示模型性能详情
(5) rknn_perf_run: 表示模型的总体性能
(6) rknn_mem_size: 表示初始化模型时的内存分配情况
(7) rknn_tensor_mem: 表示tensor的内存信息
(8) rknn_input: 表示模型的一个数据输入,用来作为参数传入给rknn_inputs_set函数
(9) rknn_output: 表示模型的一个输入输出,用来作为参数传入给rknn_outputs_get函数,在函数执行后,结构体将被赋值。
(10) rknn_init_extend: 表示初始化模型时的扩展信息。
(11) rknn_run_extend: 表示模型推理时的扩展信息。
(12) rknn_output_extend: 表示获取输出的扩展信息。
(13) rknn_custom_string: 表示转换RKNN模型时,用户设置的自定义字符串。
5.输入输出API详细说明
通过输入输出API: rknn_inputs_set, rknn_outputs_get
零拷贝输入输出API: rknn_create_mem, rknn_set_io_mem
六. 板端代码开发(Python API)
1.基本使用流程(官方手册在rknn-toolkit2-1.3.0/rknn_toolkit_lite2/docs下)
2. API详细说明
1)RKNNLite对象初始化以及对象释放:
rknn_lite = RKNNLite()
rknn_lite.release()
ret = rknn_lite.load_rknn('RKNN模型文件路径')
ret = rknn_lite.init_runtime(core_mask=RKNNLite.NPU_CORE_AUTO)
if ret != 0:
print('Init runtime environment failed')
exit(ret)
results = rknn_lite.inference(inputs=[img])
sdk_version = rknn_lite.get_sdk_version()
rknn_lite.list_support_target_platform(rknn_model=’mobilenet_v1.rknn’)
3. 示例代码
请见rknn-toolkit2-1.3.0/rknn_toolkit_lite2/examples/inference_with_lite/test.py
七. 报错解决:
1. 在.pth转.onnx时遇到报错,RuntimeError: Exporting the operator broadcast_tensors to ONNX opset version 9 is not supported. Please open a bug to request ONNX export support for the missing operator.
解决方法:当前pytorch版本不支持Normal操作,更新pytorch至11.0及以上即可。
2. 在.onnx转.rknn时遇到报错,Fatal: Meet unsupported operator: Expand(name=“/Expand”).
使用
查看模型结构,可以看到确实有Expand这个operator, 通过分析代码发现这个操作可以去掉。重新执行pytorch转onnx的过程,得到不包含Expand operator的onnx模型,然后再进行onnx转rknn的操作。
3. ImportError: libpython3.6m.so.1.0: cannot open shared object file: No such file or directory
解决方法:重新安装rknn-toolkit2即可。
八. 推理精度有问题时的排查
1.精度分析工具:
quant_error: 余弦距离,余弦相似性,越接近1表示精度越高
entire: 误差是累积的,表示的是模型每层真实的误差损失情况
per_layer: 单层的误差损失,用于评估该层本身的精度损失情况
2. 模拟器精度排查
当设置proposal=True时
三、板端量化精度筛查
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。