1. 概述
AI算法开发流程由以下流程组成:
2. 需求分析
算法的功能常常可以用一个短词概括,如人脸识别、司机行为检测、商场顾客行为分析等系统,但是却需要依靠多个子算法的有序运作才能达成。其原因在于子算法的神经网络结构各有不同,这些结构的差异化优化了各个子算法在其功能上的实现效果。
模型分类名称 | 功能 |
目标检测模型 | 检测图像中是否存在目标物体,并给出其在图像中的具体坐标,可同时提供分类功能 |
关键点定位模型 | 检测图像中的特定目标,并标出关键点位,常见骨骼点位、面部器官点位等 |
相似度比对模型 | 比较两个不同的个体的相似度,常见人脸、猪脸识别,商品识别 |
分割模型 | 检测图像中存在的物体,按轮廓或其他标准分割出物体所在的不规则像素区域,可同时带分类功能 |
OCR模型 |
以下我们列出组成例子:
例子a: 人脸识别算法 = 人脸检测(检测模型) 矫正人脸姿态模型(关键点定位模型) 人脸比对模型(相似度比对模型)
例子b: 司机行为检测算法 = 人脸识别算法(具体组成如上例) 抽烟玩手机等危险动作识别(检测模型) 疲劳驾驶检测(关键点定位模型) 车道线偏移检测(检测模型)
例子c: 商场分析 = 人脸识别算法(具体组成如首例) 人体跟踪算法(检测模型 相似度比对模型)
只有在确定了具体需求所需要的步骤后,我们才能有的放矢的采集数据,优化模型,训练出合乎我们需求的模型。
3. 准备数据
即使准备数据在大多数人看来是繁琐重复的工作,这期间仍有许多细节需要注意的。
数据样本需要良好的多样性。样本多样性是保证算法泛化能力的基础,例如想要识别农产品的功能中,假如我们只是搜集红苹果的数据,那么训练出来的网络就很难将绿色的苹果准确识别出。同时还需要加入充足的负样本,例如我们只是单纯地把农产品的图片数据喂给神经网络,那么我们就很难期望训练出来的神经网络可以有效区分真苹果还有塑料苹果。为了增强算法的可靠性,我们就需要充分的考虑到实际应用场景中会出现什么特殊情况,并将该种情况的数据添加进我们的训练数据里面。
数据样本是否可被压缩。单个样本数据的大小往往决定了网络模型的运行效率,在保证效果的情况下,应当尽量压缩图片的大小来提高运行效率,如112x112的图片,在相同环境下的处理速度将比224x224图片的快4倍左右。但是有些场景却是需要完整的图片来保证图片信息不会丢失,如山火检测一般需要很高的查全率,过度的压缩都会导致查全率下降导致算法效果不佳。
数据需要合适正确的标注与预处理。数据标注在一定程度上决定了训练效果能达到的高度,过多的错误标记将带来一个无效的训练结果。而数据的预处理,是指先对数据做出一定的操作,使其更容易被机器读懂,例如农产品在画面中的位置,如果是以像素点为单位,如农产品的中心点在左起第200个像素点,这种处理方式虽然直观准确,但是会因为不同像素点之间的差距过大,导致训练困难,这个时候就需要将距离归一化,如中心点在图中左起40%宽的位置上。而音频的预处理更为多样,不同的分词方式、傅里叶变换都会影响训练结果。
数据的准备不一定得在一开始就做到毫无遗漏。模型训练完成后,如果有一定的效果但还存在部分缺陷,就可以考虑添加或优化训练样本数据,对已有模型进行复训练修正。即使是后期的优化,增添合适的照片往往是最有效的效果。所以对数据的考量优化应该贯穿整个流程,不能在只是在开头阶段才关注数据样本的问题。
4. 选取模型
通常来讲,对于同一个功能,存在着不同的模型,它们在精度、计算速率上各有长处。模型来发现主要来源于学术研究、公司之间的公开比赛等,所以在研发过程中,就需从业人员持续地关注有关ai新模型的文章;同时对旧模型的积累分析也是十分重要的,这里我们在 下表 中列出目前在各个功能上较优的模型结构以供参考。
模型类型 | 模型名称 | 效果 | 速率 |
检测模型 | yolov5 | 精度高 | 中等 |
检测模型 | ssd | 精度中等,对小物体的识别一般 | 快速 |
关键点定位模型 | mtcnn | 精度一般,关键点较少 | 快 |
关键点的定位模型 | openpose | 精度高,关键点多 | 中等 |
相似度比对模型 | resnet18 | 精度高 | 快速 |
相似度比对模型 | resnet50 | 精度高,鲁棒性强,有比较强的抗干扰能力 | 中等 |
分割模型 | mask-rcnn | 精度中,分割出画面中的不规则物体 | 慢 |
5. 训练模型
对于有AI开发经验的研发人员,可以用自己熟悉的常见框架训练即可,如tensorflow、pytorch、caffe等主流框架,我们的开发套件可以将其转为EASY EAI Orin-nano的专用模型。
6. 模型转换
研发tensorflow、pytorch、caffe等自主的模型后,需先将模型转换为rknn模型。同时一般需要对模型进行量化与预编译,以达到运行效率的提升。
6.1 模型转换环境搭建
6.1.1 概述
模型转换环境搭建流程如下所示:
6.1.2 下载模型转换工具
为了保证模型转换工具顺利运行,请下载网盘里“06.AI算法开发/01.rknn-toolkit2模型转换工具/rknn-toolkit2-v2.3.0/docker/rknn-toolkit2-v2.3.0-cp38-docker.tar.gz”。
网盘下载链接:https://pan.baidu.com/s/1J86chdq1klKFnpCO1RCcEA?pwd=1234提取码:1234
6.1.3 把工具移到ubuntu20.04
把下载完成的docker镜像移到我司的虚拟机ubuntu20.04的rknn-toolkit目录,如下图所示:
6.1.4 运行模型转换工具环境
6.1.4.1 打开终端
在该目录打开终端
6.1.4.2 加载docker镜像
执行以下指令加载模型转换工具docker镜像:
docker load --input rknn-toolkit2-v2.3.0-cp38-docker.tar.gz
6.1.4.3 进入镜像bash环境
执行以下指令进入镜像bash环境:
docker run -t -i --privileged -v /dev/bus/usb:/dev/bus/usb rknn-toolkit2:2.3.0-cp38 /bin/bash
现象如下图所示:
输入“python”加载python相关库,尝试加载rknn库,如下图环境测试成功:
至此,模型转换工具环境搭建完成。
6.2 模型转换教程示例
6.2.1 模型转换为RKNN
EASY EAI Monster支持.rknn后缀的模型的评估及运行,对于常见的tensorflow、tensroflow lite、caffe、darknet、onnx和Pytorch模型都可以通过我们提供的 toolkit 工具将其转换至 rknn 模型,而对于其他框架训练出来的模型,也可以先将其转至 onnx 模型再转换为 rknn 模型。
模型转换操作流程入下图所示:
6.2.2 模型转换Demo下载
下载百度网链接:https://pan.baidu.com/s/1l5vcbS-w4dGetSdKQdhAuA?pwd=1234 提取码:1234。把yolov5_model_convert.tar.bz2和quant_dataset.zip解压到虚拟机,如下图所示:
6.2.3 进入模型转换工具docker环境
执行以下指令把工作区域映射进docker镜像,其中/home/developer/rknn-toolkit2/model_convert为工作区域,/test为映射到docker镜像,/dev/bus/usb:/dev/bus/usb为映射usb到docker镜像:
docker run -t -i --privileged -v /dev/bus/usb:/dev/bus/usb -v /home/developer/rknn-toolkit2/model_convert:/test rknn-toolkit2:2.3.0-cp38 /bin/bash
执行成功如下图所示:
6.2.4 模型转换操作说明
6.2.4.1 模型转换Demo目录结构
模型转换测试Demo由yolov5_model_convert和quant_dataset组成。yolov5_model_convert存放软件脚本,quant_dataset存放量化模型所需的数据。如下图所示:
yolov5_model_convert文件夹存放以下内容,如下图所示:
6.2.4.2 生成量化图片列表
在docker环境切换到模型转换工作目录:
cd /test/yolov5_model_convert
如下图所示:
执行gen_list.py生成量化图片列表:
python gen_list.py
命令行现象如下图所示:
生成“量化图片列表”如下文件夹所示:
6.2.4.3 onnx模型转换为rknn模型
rknn_convert.py脚本默认进行int8量化操作,脚本代码清单如下所示:
import os import urllib import traceback import time import sys import numpy as np import cv2 from rknn.api import RKNN ONNX_MODEL = 'best.onnx' RKNN_MODEL = './yolov5_mask_rk3576.rknn' DATASET = './pic_path.txt' QUANTIZE_ON = True if __name__ == '__main__': # Create RKNN object rknn = RKNN(verbose=True) if not os.path.exists(ONNX_MODEL): print('model not exist') exit(-1) # pre-process config print('--> Config model') rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform = 'rk3576') print('done') # Load ONNX model print('--> Loading model') ret = rknn.load_onnx(model=ONNX_MODEL) if ret != 0: print('Load yolov5 failed!') exit(ret) print('done') # Build model print('--> Building model') ret = rknn.build(do_quantization=QUANTIZE_ON, dataset=DATASET) if ret != 0: print('Build yolov5 failed!') exit(ret) print('done') # Export RKNN model print('--> Export RKNN model') ret = rknn.export_rknn(RKNN_MODEL) if ret != 0: print('Export yolov5rknn failed!') exit(ret) print('done')
把onnx模型best.onnx放到yolov5_model_convert目录,并执行rknn_convert.py脚本进行模型转换:
python rknn_convert.py
生成模型如下图所示,此模型可以在rknn环境和EASY EAI Orin-nano环境运行:
6.3 模型转换API说明
6.3.1 API详细说明
6.3.1.1 RKNN初始化及释放
在使用RKNN-Toolkit2的所有API接口时,都需要先调用RKNN()方法初始化RKNN对象,不再使用该对象时通过调用该对象的release()方法进行释放。
初始化RKNN对象时,可以设置verbose和verbose_file参数,以打印详细的日志信息。其中verbose参数指定是否要打印详细日志信息;如果设置了verbose_file参数,且verbose参数值为True,日志信息还将写到该参数指定的文件中。
举例如下:
# 打印详细的日志信息 rknn = RKNN(verbose=True) … rknn.release()
6.3.1.2 模型配置
在构建RKNN模型之前,需要先对模型进行通道均值、量化图片RGB2BGR转换、量化类型等的配置,这些操作可以通过config接口进行配置。
API | config |
描述 | 设置模型转换参数。 |
参数 | mean_values:输入的均值。参数格式是一个列表,列表中包含一个或多个均值子列表,多输入模型对应多个子列表,每个子列表的长度与该输入的通道数一致,例如[[128,128,128]],表示一个输入的三个通道的值减去128。 默认值为None,表示所有的mean值为0。 |
std_values:输入的归一化值。参数格式是一个列表,列表中包含一个或多个归一化值子列表,多输入模型对应多个子列表,每个子列表的长度与该输入的通道数一致,例如[[128,128,128]],表示设置一个输入的三个通道的值减去均值后再除以128。 默认值为None,表示所有的std值为1。 |
|
quant_img_RGB2BGR:表示在加载量化图像时是否需要先做RGB2BGR的操作。如果有多个输入,则用列表包含起来,如[True, True, False]。默认值为False。 该配置一般用在Caffe的模型上,Caffe模型训练时大多会先对数据集图像进行RGB2BGR转换,此时需将该配置设为True。 另外,该配置只对量化图像格式为jpg/png/bmp有效,npy格式读取时会忽略该配置,因此当模型输入为BGR时,npy也需要为BGR格式。 该配置仅用于在量化阶段(build接口)读取量化图像或量化精度分析(accuracy_analysis 接口),并不会保存在最终的RKNN模型中,因此如果模型的输入为BGR,则在调用toolkit2的inference或C-API的run函数之前,需要保证传入的图像数据也为BGR格式。 |
|
quantized_dtype:量化类型,目前支持的量化类型有w8a8、w4a16、w8a16、w4a8、w16a16i和w16a16i_dfp。默认值为w8a8。 - w8a8:权重为8bit非对称量化精度,激活值为8bit非对称量化精度。(RK2118不支持) - w4a16:权重为4bit非对称量化精度,激活值为16bit浮点精度。(仅RK3576支持) - w8a16:权重为8bit非对称量化精度,激活值为16bit浮点精度。(仅RK3562支持) - w4a8:权重为4bit非对称量化精度,激活值为8bit非对称量化精度。(暂不支持) - w16a16i:权重为16bit非对称量化精度,激活值为16bit非对称量化精度。(仅RV1103/RV1106支持) - w16a16i_dfp:权重为16bit动态定点量化精度,激活值为16bit动态定点量化精度。(仅RV1103/RV1106支持) |
|
quantized_algorithm:计算每一层的量化参数时采用的量化算法,目前支持的量化算法有:normal,mmse及kl_divergence。默认值为normal。 normal量化算法的特点是速度较快,推荐量化数据量一般为20-100张左右,更多的数据量下精度未必会有进一步提升。 mmse量化算法由于采用暴力迭代的方式,速度较慢,但通常会比normal具有更高的精度,推荐量化数据量一般为20-50张左右,用户也可以根据量化时间长短对量化数据量进行适当增减。 kl_divergence量化算法所用时间会比normal多一些,但比mmse会少很多,在某些场景下(feature分布不均匀时)可以得到较好的改善效果,推荐量化数据量一般为20-100张左右。 |
|
quantized_method:目前支持layer或者channel。默认值为channel。 - layer:每层的weight只有一套量化参数; - channel:每层的weight的每个通道都有一套量化参数,通常情况下channel会比layer精度更高。 |
|
float_dtype:用于指定非量化情况下的浮点的数据类型,目前支持的数据类型有float16。 默认值为float16。 |
|
optimization_level:模型优化等级。默认值为3。通过修改模型优化等级,可以关掉部分或全部模型转换过程中使用到的优化规则。该参数的默认值为3,打开所有优化选项。值为2或1时关闭一部分可能会对部分模型精度产生影响的优化选项,值为0时关闭所有优化选项。 | |
target_platform:指定RKNN模型是基于哪个目标芯片平台生成的。目前支持“rv1103”、“rv1103b”、“rv1106”、“rv1106b”、“rk2118”、“rk3562”、“rk3566、“rk3568”、“rk3576”和“rk3588”。该参数对大小写不敏感。默认值为None。 | |
custom_string:添加自定义字符串信息到RKNN模型,可以在runtime时通过query查询到该信息,方便部署时根据不同的RKNN模型做特殊的处理。默认值为None。 | |
remove_weight:去除conv等权重以生成一个RKNN的从模型,该从模型可以与带完整权重的RKNN模型共享权重以减少内存消耗。默认值为False。 | |
compress_weight:压缩模型权重,可以减小RKNN模型的大小。默认值为False。 | |
single_core_mode:是否仅生成单核模型,可以减小RKNN模型的大小和内存消耗。默认值为False。目前仅对RK3588 / RK3576生效。默认值为False。 | |
model_pruning:对模型进行无损剪枝。对于权重稀疏的模型,可以减小转换后RKNN模型的大小和计算量。默认值为False。 | |
op_target:用于指定OP的具体执行目标(如NPU/CPU/GPU等),格式为{'op0_output_name':'cpu', 'op1_output_name':'npu', ...}。默认值为None。其中,'op0_output_name'和'op1_output_name'为对应OP的输出tensor名,可以通过精度分析(accuracy_analysis)功能的返回结果中获取。'cpu'和'npu'则表示该tensor对应的OP的执行目标是CPU或NPU,目前可选的选项有:'cpu' / 'npu' / 'gpu' / 'auto',其中,'auto'是自动选择执行目标。 | |
dynamic_input:用于根据用户指定的多组输入shape,来模拟动态输入的功能。格式为[[input0_shapeA, input1_shapeA, ...], [input0_shapeB, input1_shapeB, ...], ...]。 默认值为None,实验性功能。 假设原始模型只有一个输入,shape为[1,3,224,224],或者原始模型的输入shape本身就是动态的,如shape为[1,3,height,width]或[1,3,-1,-1],但部署的时候,需要该模型支持3种不同的 输入shape,如[1,3,224,224], [1,3,192,192]和[1,3,160,160],此时可以设置dynamic_input=[[[1,3,224,224]], [[1,3,192,192]], [[1,3,160,160]]],转换成RKNN模型后进行推理时,需传入对应shape的输入数据。 注: 需要原始模型本身支持动态输入才可开启此功能,否则会报错。 如果原始模型输入shape本身就是动态的,则只有动态的轴可以设置不同的值。 |
|
quantize_weight:在build接口的do_quantization为False情况下, 通过对一些权重进行量化以减小rknn模型的大小。 默认值为False。 |
|
remove_reshape:删除模型的输入和输出中可能存在的Reshape的OP,以提高模型运行时性能(因为目前很多平台的Reshape是跑在cpu上,相对较慢)。 默认为 False。 注:开启后可能会修改模型的输入或输出节点的shape,需要留意观察转换过程中的warning打印,并在部署时也需要考虑输入和输出shape变化的影响。 |
|
sparse_infer:在已经稀疏化过的模型上进行稀疏化推理,以提高推理性能。 目前仅对RK3576生效。默认为 False。 | |
enable_flash_attention:是否启用Flash Attention。默认为 False。 注:FlashAttention是基于 https://arxiv.org/abs/2307.08691 实现, 通过高速缓存内循环实现加速以及减少宽带使用,但是会导致模型增大,请根据具体场景和模型选择是否开启使用。更多详情请查看“RKNN Compiler Support Operator List”的exSDPAttention说明。 |
|
返回值 | 无。 |
举例如下:
# model config rknn.config(mean_values=[[103.94, 116.78, 123.68]], std_values=[[58.82, 58.82, 58.82]], quant_img_RGB2BGR=True, target_platform='rk3566')
6.3.1.3 模型加载
RKNN-Toolkit2目前支持Caffe、TensorFlow、TensorFlow Lite、ONNX、DarkNet、PyTorch等模型的加载转换,这些模型在加载时需调用对应的接口,以下为这些接口的详细说明。
(1)Caffe模型加载接口
API | load_caffe |
描述 | 加载Caffe模型。(ARM64版本暂不支持该接口) |
参数 | model:Caffe模型文件(.prototxt后缀文件)路径。 |
blobs:Caffe模型的二进制数据文件(.caffemodel后缀文件)路径。 | |
input_name:Caffe模型存在多输入时,可以通过该参数指定输入层名的顺序,形如['input1','input2','input3'],注意名字需要与模型输入名一致;默认值为None,表示按Caffe 模型文件(.prototxt后缀文件)自动给定。 |
|
返回值 | 0:导入成功。 |
-1:导入失败。 |
举例如下:
# 从当前路径加载mobilenet_v2模型 ret = rknn.load_caffe(model='./mobilenet_v2.prototxt', blobs='./mobilenet_v2.caffemodel')
(2)TensorFlow模型加载接口
API | load_tensorflow |
描述 | 加载TensorFlow模型。(ARM64版本暂不支持该接口) |
参数 | tf_pb:TensorFlow模型文件(.pb后缀)路径。 |
inputs:模型的输入节点(tensor名),支持多个输入节点。所有输入节点名放在一个列表中。 | |
input_size_list:每个输入节点对应的shape,所有输入shape放在一个列表中。如示例中的ssd_mobilenet_v1模型,其输入节点对应的输入shape是[[1, 300, 300, 3]]。 | |
outputs:模型的输出节点(tensor名),支持多个输出节点。所有输出节点名放在一个列表中。 | |
input_is_nchw:模型的输入的layout是否已经是NCHW。默认值为False,表示默认输入layout为NHWC。 | |
返回值 | 0:导入成功。 |
-1:导入失败。 |
举例如下:
# 从当前目录加载ssd_mobilenet_v1_coco_2017_11_17模型 ret = rknn.load_tensorflow(tf_pb='./ssd_mobilenet_v1_coco_2017_11_17.pb', inputs=['Preprocessor/sub'], outputs=['concat', 'concat_1'], input_size_list=[[300, 300, 3]])
(3)TensorFlow Lite模型加载接口
API | load_tflite |
描述 | 加载TensorFlow Lite模型。(ARM64版本暂不支持该接口) |
参数 | model:TensorFlow Lite模型文件(.tflite后缀)路径。 |
input_is_nchw:模型的输入的layout是否已经是NCHW。默认值为False,即默认输入layout为NHWC。 | |
返回值 | 0:导入成功。 |
-1:导入失败。 |
举例如下:
# 从当前目录加载mobilenet_v1模型 ret = rknn.load_tflite(model='./mobilenet_v1.tflite')
(4)ONNX模型加载
API | load_onnx |
描述 | 加载ONNX模型。 |
参数 | model:ONNX模型文件(.onnx后缀)路径。 |
inputs:模型输入节点(tensor名),支持多个输入节点,所有输入节点名放在一个列表中。默认值为None,表示从模型里获取。 | |
input_size_list:每个输入节点对应的shape,所有输入shape放在一个列表中。如inputs有设置,则input_size_list也需要被设置。默认值为None。 | |
input_initial_val:设置模型输入的初始值,格式为ndarray的列表。默认值为None。 主要用于将某些输入固化为常量,对于不需要固化为常量的输入可以设为None,如[None,np.array([1])]。 | |
outputs:模型的输出节点(tensor名),支持多个输出节点,所有输出节点名放在一个列表中。默认值为None,表示从模型里获取。 | |
返回值 | 0:导入成功。 |
-1:导入失败。 |
举例如下:
# 从当前目录加载arcface模型 ret = rknn.load_onnx(model='./arcface.onnx')
(5)DarkNet模型加载接口
API | load_darknet |
描述 | 加载DarkNet模型。(ARM64版本暂不支持该接口) |
参数 | model:DarkNet模型文件(.cfg后缀)路径。 |
weight:权重文件(.weights后缀)路径。 | |
返回值 | 0:导入成功。 |
-1:导入失败。 |
举例如下:
# 从当前目录加载yolov3-tiny模型 ret = rknn.load_darknet(model='./yolov3-tiny.cfg', weight='./yolov3.weights')
(6)PyTorch模型加载接口
API | load_pytorch |
描述 | 加载PyTorch模型。 支持量化感知训练(QAT)模型,但需要将torch版本更新至1.9.0以上。 |
参数 | model:PyTorch模型文件(.pt后缀)路径,而且需要是torchscript格式的模型。 |
input_size_list:每个输入节点对应的shape,所有输入shape放在一个列表中。 | |
返回值 | 0:导入成功。 |
-1:导入失败。 |
举例如下:
# 从当前目录加载resnet18模型 ret = rknn.load_pytorch(model='./resnet18.pt', input_size_list=[[1,3,224,224]])
6.3.1.4 构建RKNN模型
API | build |
描述 | 构建RKNN模型。 |
参数 | do_quantization:是否对模型进行量化。默认值为True。 |
dataset:用于量化校正的数据集。目前支持文本文件格式,用户可以把用于校正的图片 (jpg或png格式)或npy文件路径放到一个.txt文件中。文本文件里每一行一条路径信息。 如: a.jpg b.jpg 或 a.npy b.npy 如有多个输入,则每个输入对应的文件用空格隔开,如: a.jpg a2.jpg b.jpg b2.jpg 或 a.npy a2.npy b.npy b2.npy 注:量化图片建议选择与预测场景较吻合的图片。 |
|
rknn_batch_size:模型的输入Batch参数调整。默认值为None,表示不进行调整。 如果大于1,则可以在一次推理中同时推理多帧输入图像或输入数据,如MobileNet模型的原始input维度为[1, 224, 224, 3],output维度为[1, 1001],当rknn_batch_size设为4时,input的维度变为[4, 224, 224, 3],output维度变为[4, 1001]。 注: rknn_batch_size只有在NPU多核的平台上可以提高性能(提升核心利用率),因此rknn_batch_size的值建议与核心数匹配。 rknn_batch_size修改后,模型的input/output的shape都会被修改,使用inference推理模型时需要设置相应的input的大小,后处理时,也需要对返回的outputs进行处理。 |
|
返回值 | 0:构建成功。 |
-1:构建失败。 |
举例如下:
# 构建RKNN模型,并且做量化 ret = rknn.build(do_quantization=True, dataset='./dataset.txt')
6.3.1.5 导出RKNN模型
通过本工具构建的RKNN模型通过该接口可以导出存储为RKNN模型文件,用于模型部署。
API | export_rknn |
描述 | 将RKNN模型保存到指定文件中(.rknn后缀)。 |
参数 | export_path:导出模型文件的路径。 |
返回值 | 0:导出成功。 |
-1:导出失败。 |
举例如下:
# 将构建好的RKNN模型保存到当前路径的mobilenet_v1.rknn文件中 ret = rknn.export_rknn(export_path='./mobilenet_v1.rknn')
6.3.1.6 加载RKNN模型
API | load_rknn |
描述 | 加载RKNN模型。 加载完RKNN模型后,不需要再进行模型配置、模型加载和构建RKNN模型的步骤。并且加载后的模型仅限于连接NPU硬件进行推理或获取性能数据等,不能用于模拟器或精度分析等。 |
参数 | path:RKNN模型文件路径。 |
返回值 | 0:加载成功。 |
-1:加载失败。 |
举例如下:
# 从当前路径加载mobilenet_v1.rknn模型 ret = rknn.load_rknn(path='./mobilenet_v1.rknn')
6.3.1.7 初始化运行时环境
在模型推理或性能评估之前,必须先初始化运行时环境,明确模型的运行平台(具体的目标硬件平台或软件模拟器)。
API | init_runtime |
描述 | 初始化运行时环境。 |
参数 | target:目标硬件平台,支持“rv1103”、“rv1103b”、“rv1106”、“rv1106b”、“rk3562”、“rk3566”、“rk3568”、“rk3576”和“rk3588”。默认值为None,即在PC使用工具时,模型在模拟器上运行。 注:target设为None时,需要先调用build或hybrid_quantization接口才可让模型在模拟器上运行。 |
device_id:设备编号,如果PC连接多台设备时,需要指定该参数,设备编号可以通过“list_devices”接口查看。默认值为None。 | |
perf_debug:进行性能评估时是否开启debug模式。在debug模式下,可以获取到每一层的运行时间,否则只能获取模型运行的总时间。默认值为False。 | |
eval_mem:是否进入内存评估模式。进入内存评估模式后,可以调用eval_memory接口获取模型运行时的内存使用情况。默认值为False。 | |
async_mode:是否使用异步模式。默认值为False。 调用推理接口时,涉及设置输入图片、模型推理、获取推理结果三个阶段。如果开启了异步模式,设置当前帧的输入将与推理上一帧同时进行,所以除第一帧外,之后的每一帧都可以隐藏设置输入的时间,从而提升性能。在异步模式下,每次返回的推理结果都是上一帧的。(目前版本该参数暂不支持) |
|
core_mask:设置运行时的NPU核心。支持的平台为RK3588 / RK3576,支持的配置如下: RKNN.NPU_CORE_AUTO:表示自动调度模型,自动运行在当前空闲的NPU核上。 RKNN.NPU_CORE_0:表示运行在NPU0核心上。 RKNN.NPU_CORE_1:表示运行在NPU1核心上。 RKNN.NPU_CORE_2:表示运行在NPU2核心上。 RKNN.NPU_CORE_0_1:表示同时运行在NPU0、NPU1核心上。 RKNN.NPU_CORE_0_1_2:表示同时运行在NPU0、NPU1、NPU2核心上。 RKNN.NPU_CORE_ALL:表示根据平台自动配置NPU核心数量。 默认值为RKNN.NPU_CORE_AUTO。 注:RK3576只有2个核心,因此不能设置NPU_CORE_2和NPU_CORE_0_1_2。 |
|
fallback_prior_device:设置当OP超出NPU规格时fallback的优先级,当前支持“cpu”或“gpu”,“gpu”只有在存在GPU硬件的平台上有效。默认值是“cpu”。 | |
返回值 | 0:初始化运行时环境成功。 |
-1:初始化运行时环境失败。 |
举例如下:
# 初始化运行时环境 ret = rknn.init_runtime(target='rk3566')
6.3.1.8 模型推理
在进行模型推理前,必须先构建或加载一个RKNN模型。
API | inference |
描述 | 对当前模型进行推理,并返回推理结果。 如果初始化运行环境时有设置target为Rockchip NPU设备,得到的是模型在硬件平台上的推理结果。如果没有设置target,得到的则是模型在模拟器上的推理结果。 |
参数 | inputs:待推理的输入列表,格式为ndarray。 |
data_format:输入数据的layout列表,“nchw”或“nhwc”,只对4维的输入有效。默认值为None,表示所有输入的layout都为NHWC。 | |
inputs_pass_through:输入的透传列表。默认值为None,表示所有输入都不透传。 非透传模式下,在将输入传给NPU驱动之前,工具会对输入进行减均值、除方差等操作;而透传模式下,不会做这些操作,而是直接将输入传给NPU。 该参数的值是一个列表,比如要透传input0,不透传input1,则该参数的值为[1, 0]。 |
|
返回值 | results:推理结果,类型是ndarray list。 |
举例如下:
对于分类模型,如mobilenet_v1,代码如下(完整代码参考example/tflite/mobilent_v1):
# 使用模型对图片进行推理,得到TOP5结果 outputs = rknn.inference(inputs=[img]) show_outputs(outputs)
输出的TOP5结果如下:
-----TOP 5----- [ 156] score:0.928223 class:"Shih-Tzu" [ 155] score:0.063171 class:"Pekinese, Pekingese, Peke" [ 205] score:0.004299 class:"Lhasa, Lhasa apso" [ 284] score:0.003096 class:"Persian cat" [ 285] score:0.000171 class:"Siamese cat, Siamese"
6.3.1.9 评估模型性能
API | eval_perf |
描述 | 评估模型性能。 模型必须运行在与PC连接的RV1103 / RV1103B / RV1106 / RV1106B / RK3562 / RK3566 /RK3568 / RK3576 / RK3588上。如果调用“init_runtime”的接口来初始化运行环境时设置 perf_debug为False,则获得的是模型在硬件上运行的总时间;如果设置perf_debug为True,除了返回总时间外,还将返回每一层的耗时情况。 |
参数 | is_print:是否打印性能信息,默认值为True。 |
fix_freq:是否固定硬件设备的频率,默认值为True。 | |
返回值 | perf_result:性能信息(字符串)。 |
举例如下:
# 对模型性能进行评估 perf_detail = rknn.eval_perf()
6.3.1.10 获取内存使用情况
API | eval_memory |
描述 | 获取模型在硬件平台运行时的内存使用情况。 模型必须运行在与PC连接的RV1103 / RV1103B / RV1106 / RV1106B / RK3562 / RK3566 /RK3568 / RK3576 / RK3588上。 |
参数 | is_print:是否以规范格式打印内存使用情况,默认值为True。 |
返回值 | memory_detail:内存使用情况,类型为字典。 内存使用情况按照下面的格式封装在字典中: { 'weight_memory': 3698688, 'internal_memory': 1756160, 'other_memory': 484352, 'total_memory': 5939200, } - 'weight_memory' 字段表示运行时模型权重的内存占用。 - 'internal_memory' 字段表示运行时模型中间tensor内存占用。 - 'other_memory' 字段表示运行时其他的内存占用。 - 'total_model_allocation' 表示运行时的总内存占用,即权重、中间tensor和其他的内存占用之和。 |
举例如下:
# 对模型内存使用情况进行评估 memory_detail = rknn.eval_memory()
如examples/caffe/mobilenet_v2,它在RK3588上运行时内存占用情况如下:
====================================================== Memory Profile Info Dump ====================================================== NPU model memory detail(bytes): Weight Memory: 3.53 MiB Internal Tensor Memory: 1.67 MiB Other Memory: 473.00 KiB Total Memory: 5.66 MiB INFO: When evaluating memory usage, we need consider the size of model, current model size is: 4.09 MiB ======================================================
6.3.1.11 查询SDK版本
API | get_sdk_version |
描述 | 获取SDK API和驱动的版本号。 注:使用该接口前必须完成模型加载和初始化运行环境,且该接口只能在硬件平台RV1103 / RV1103B / RV1106 / RV1106B / RK3562 / RK3566 / RK3568 / RK3576 / RK3588 上使用。 |
参数 | 无。 |
返回值 | sdk_version:API和驱动版本信息,类型为字符串 |
举例如下:
# 获取SDK版本信息 sdk_version = rknn.get_sdk_version() print(sdk_version)
返回的SDK信息类似如下:
============================================== RKNN VERSION: API: 1.5.2 (8babfea build@2023-08-25T02:31:12) DRV: rknn_server: 1.5.2 (8babfea build@2023-08-25T10:30:12) DRV: rknnrt: 1.5.3b13 (42cbca6f5@2023-10-27T10:13:21) ==============================================
6.3.1.12 混合量化
(1)hybrid_quantization_step1
使用混合量化功能时,第一阶段调用的主要接口是hybrid_quantization_step1,用于生成临时模型文件
(.model)、数据文件(.data)和量化配置文件
(.quantization.cfg)。接口详情如下:
API | hybrid_quantization_step1 |
描述 | 根据加载的原始模型,生成对应的临时模型文件、配置文件和量化配置文件。 |
参数 | dataset:见1.4 构建RKNN模型的dataset说明。 |
rknn_batch_size:见1.4 构建RKNN模型的rknn_batch_size说明。 | |
proposal:产生混合量化的配置建议值。 默认值为False。 | |
proposal_dataset_size:proposal使用的dataset的张数。默认值为1。 因为proposal功能比较耗时,所以默认只使用1张,也就是dataset里的第一张。 | |
custom_hybrid:用于根据用户指定的多组输入和输出名,选取混合量化对应子图。格式为[[input0_name, output0_name], [input1_name, output1_name], …]。默认值为None。 注:输入和输出名应根据生成的临时模型文件(.model)来选择。 |
|
返回值 | 0:成功。 |
-1:失败。 |
举例如下:
# 调用hybrid_quantization_step1 产生量化配置文件 ret = rknn.hybrid_quantization_step1(dataset='./dataset.txt')
(2)hybrid_quantization_step2
用于使用混合量化功能时生成RKNN模型,接口详情如下:
API | hybrid_quantization_step2 |
描述 | 接收临时模型文件、配置文件、量化配置文件和校正数据集作为输入,生成混合量化后的RKNN模型。 |
参数 | model_input:hybrid_quantization_step1生成的临时模型文件(.model)路径。 |
data_input:hybrid_quantization_step1生成的数据文件(.data)路径。 | |
model_quantization_cfg:hybrid_quantization_step1生成并经过修改后的模型量化配置文件(.quantization.cfg)路径。 | |
返回值 | 0:成功。 |
-1:失败。 |
举例如下:
# Call hybrid_quantization_step2 to generate hybrid quantized RKNN model ret = rknn.hybrid_quantization_step2( model_input='./ssd_mobilenet_v2.model', data_input='./ssd_mobilenet_v2.data', model_quantization_cfg='./ssd_mobilenet_v2.quantization.cfg')
6.3.1.13 量化精度分析
该接口的功能是进行浮点、量化推理并产生每层的数据,并进行量化精度分析。
API | accuracy_analysis |
描述 | 推理并产生快照,也就是dump出每一层的tensor数据。会dump出包括fp32和quant两种数据类型的快照,用于计算量化误差。 注: 该接口只能在 build 或 hybrid_quantization_step2 之后调用。 如未指定target,并且原始模型应该为已量化的模型(QAT模型),则会调用失败。 该接口使用的量化方式与config中指定的一致。 |
参数 | inputs:图像(jpg / png / bmp / npy等)路径 list。 |
output_dir:输出目录,所有快照都保存在该目录。默认值为'./snapshot'。 如果没有设置target,在output_dir下会输出: - simulator目录:保存整个量化模型在simulator上完整运行时每一层的结果(已转成float32); - golden目录:保存整个浮点模型在simulator上完整跑下来时每一层的结果; - error_analysis.txt:记录simulator上量化模型逐层运行时每一层的结果与golden浮点模型逐层运行时每一层的结果的余弦距离(entire_error cosine),以及量化模型取上一层的浮点结果作为输入时,输出与浮点模型的余弦距离(single_error cosine),更详细的信息请查看error_analysis.txt文件。 如果有设置target,则在output_dir里还会多输出: - runtime目录:保存整个量化模型在NPU上完整运行时每一层的结果(已转成float32)。 - error_analysis.txt:在上述记录的内容的基础上,还会记录量化模型在simulator上逐层运行时每一层的结果与NPU上逐层运行时每一层的结果的余弦距离(entire_error cosine)等信息,更详细的信息请查看error_analysis.txt文件。 |
|
target:目标硬件平台,支持“rv1103”、“rv1103b”、“rv1106”、“rv1106b”、“rk3562”、“rk3566”、“rk3568”、“rk3576”和“rk3588”,默认为None。 如果设置了target,则会获取NPU运行时每一层的结果,并进行精度的分析。 |
|
device_id:设备编号,如果PC连接多台设备时,需要指定该参数,设备编号可以通过“list_devices”接口查看。默认值为None。 | |
返回值 | 0:成功。 |
-1:失败。 |
举例如下:
# Accuracy analysis ret = rknn.accuracy_analysis(inputs=['./dog_224x224.jpg'])
6.3.1.14 获取设备列表
API | list_devices |
描述 | 列出已连接的RV1103 / RV1103B / RV1106 / RV1106B / RK3562 / RK3566 / RK3568 /RK3576 / RK3588。 注:目前设备连接模式有两种:ADB和NTB。多设备连接时请确保他们的模式都是一样的。 |
参数 | 无。 |
返回值 | 返回adb_devices列表和ntb_devices列表,如果设备为空,则返回空列表。 |
举例如下:
rknn.list_devices()
返回的设备列表信息如下:
************************* all device(s) with adb mode: VD46C3KM6N *************************
注:使用多设备时,需要保证它们的连接模式都是一致的,否则会引起冲突,导致设备连接失败。
6.3.1.15 导出加密模型
该接口的功能是将普通的RKNN模型进行加密,得到加密后的模型。
API | export_encrypted_rknn_model |
描述 | 根据用户指定的加密等级对普通的RKNN模型进行加密。 注:RV1103/RV1103B/RV1106/RV1106B/RK2118平台暂不支持。 |
参数 | input_model:待加密的RKNN模型路径。 |
output_model:模型加密后的保存路径。默认值为None,表示使用{original_model_name}.crypt.rknn作为加密后的模型名字。 | |
crypt_level:加密等级,有1,2和3三个等级。默认值为1。 等级越高,安全性越高,解密越耗时;反之,安全性越低,解密越快。数据类型为整型 |
|
返回值 | 0:成功。 |
-1:失败。 |
举例如下:
ret = rknn.export_encrypted_rknn_model('test.rknn')
6.3.1.16 注册自定义算子
该接口的功能是注册一个自定义算子。
API | reg_custom_op |
描述 | 注册用户提供的自定义算子类。目前只支持ONNX模型。 |
参数 | custom_op:用户自定义的算子类。用于用户需要自定义一个不存在于ONNX算子规范内的新算子。该算子的op_type推荐以“cst”字符开头,并且其算子类的shape_infer和compute 函数需要用户自己实现。 注:custom_op算子类仅用于模型转换并生成带有自定义算子的RKNN模型。 |
返回值 | 0:成功。 |
-1:失败。 |
举例如下:
import numpy as np from rknn.api.custom_op import get_node_attr class cstSoftmax: op_type = 'cstSoftmax' def shape_infer(self, node, in_shapes, in_dtypes): out_shapes = in_shapes.copy() out_dtypes = in_dtypes.copy() return out_shapes, out_dtypes def compute(self, node, inputs): x = inputs[0] axis = get_node_attr(node, 'axis') x_max = np.max(x, axis=axis, keepdims=True) tmp = np.exp(x - x_max) s = np.sum(tmp, axis=axis, keepdims=True) outputs = [tmp / s] return outputs ret = rknn.reg_custom_op(cstSoftmax)
6.3.1.17 生成C 部署示例
API | codegen |
描述 | 自动生成C 的部署示例。 |
参数 | output_path:输出文件夹目录,用户可配置目录名称。 |
inputs:填写模型输入的路径列表,允许不填。有效文件格式为jpg/png/npy,以npy文件为输入时,npy数据的维度信息应与模型输入的维度信息保持一致。 | |
overwrite:设为True时,会覆盖output_path指定目录下的文件。默认值为alse。 | |
返回值 | 0:成功。 |
-1:失败。 |
举例如下:
ret = rknn.codegen(output_path='./rknn_app_demo', inputs=['./mobilenet_v2/dog_224x224.jpg'], overwrite=True)
7. 模型部署
模型转换为rknn模型后,需再参考NPU API说明文档,编写应用工程。经过编译后传输至EASY EAI Orin-nano平台上实现部署。
7.1 模型部署示例
7.1.1 模型部署示例介绍
本小节展示yolov5模型的在EASY EAI Orin-nano的部署过程,该模型仅经过简单训练供示例使用,不保证模型精度。
7.1.2 准备工作
7.1.2.1 硬件准备
EASY EAI Orin-nano开发板,microUSB数据线,该模型仅经过简单训练供示例使用,不保证模型精度。
7.1.3 源码下载以及例程编译
下载yolov5 C Demo示例文件。
百度网盘链接: (https://pan.baidu.com/s/1Ah8x8Rzo-iofyZPDHfzo2w?pwd=1234 提取码:1234)。
下载程序包移至ubuntu环境后,执行以下指令解压:
tar -xvf yolov5_detect_C_demo.tar.bz2
下载解压后如下图所示:
通过adb接口连接EASY-EAI-Orin-nano,,连接方式如下图所示:
接下来需要通过adb把源码传输到板卡上,先切换目录然后执行以下指令:
cd ~/rknn-toolkit2 adb push yolov5_detect_C_demo /userdata
登录到板子切换到例程目录执行编译操作:
adb shell cd /userdata/yolov5_detect_C_demo chmod 777 build.sh ./build.sh
7.1.4 在开发板执行yolov5 demo
编译成功后切换到可执行程序目录,如下所示:
cd /userdata/yolov5_detect_C_demo/yolov5_detect_demo_release
运行例程命令如下所示:
chmod 777 yolov5_detect_demo ./yolov5_detect_demo
执行结果如下图所示,算法执行时间为58ms:
退出板卡环境,取回测试图片:
exit adb pull /userdata/yolov5_detect_C_demo/yolov5_detect_demo_release/result.jpg .
测试结果如下图所示:
至此,yolov5目标检测例程已成功在板卡运行。
7.2 模型部署API说明
7.2.1 基础数据结构定义
7.2.1.1 rknn_sdk_version
结构体rknn_sdk_version用来表示RKNN SDK的版本信息,结构体的定义如下:
成员变量 | 数据类型 | 含义 |
api_version | char[] | SDK的版本信息。 |
drv_version | char[] | SDK所基于的驱动版本信息。 |
7.2.1.2 rknn_input_output_num
结构体rknn_input_output_num表示输入输出tensor个数,其结构体成员变量如下表所示:
成员变量 | 数据类型 | 含义 |
n_input | uint32_t | 输入tensor个数。 |
n_output | uint32_t | 输出tensor个数。 |
7.2.1.3 rknn_input_range
结构体rknn_input_range表示一个输入的支持形状列表信息。它包含了输入的索引、支持的形状个数、数据布局格式、名称以及形状列表,具体的结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
index | uint32_t | 表示该形状对应输入的索引位置。 |
shape_number | uint32_t | 表示RKNN模型支持的输入形状个数。 |
fmt | rknn_tensor_format | 表示形状对应的数据布局格式。 |
name | char[] | 表示输入的名称。 |
dyn_rang | uint32_t[][] | 表示输入形状列表,它是包含多个形状数组的二维数组,形状优先存储。 |
n_dims | uint32_ | 表示每个形状数组的有效维度个数。 |
7.2.1.4 rknn_tensor_attr
结构体rknn_tensor_attr表示模型的tensor的属性,结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
index | uint32_t | 表示输入输出tensor的索引位置。 |
n_dims | uint32_t | Tensor维度个数。 |
dims | uint32_t[] | Tensor形状。 |
name | char[] | Tensor名称。 |
n_elems | uint32_t | Tensor数据元素个数。 |
size | uint32_t | Tensor数据所占内存大小。 |
fmt | rknn_tensor_format | Tensor维度的格式,有以下格式: RKNN_TENSOR_NCHW,RKNN_TENSOR_NHWC, RKNN_TENSOR_NC1HWC2,RKNN_TENSOR_UNDEFINED |
type | rknn_tensor_type | Tensor数据类型,有以下数据类型:RKNN_TENSOR_FLOAT32, RKNN_TENSOR_FLOAT16,RKNN_TENSOR_INT8, RKNN_TENSOR_UINT8,RKNN_TENSOR_INT16, RKNN_TENSOR_UINT16,RKNN_TENSOR_INT32, RKNN_TENSOR_INT64,RKNN_TENSOR_BOOL |
qnt_type | rknn_tensor_qnt_type | Tensor量化类型,有以下的量化类型:RKNN_TENSOR_QNT_NONE:未量化;RKNN_TENSOR_QNT_DFP:动态定点量化;RKNN_TENSOR_QNT_AFFINE_ASYMMETRIC:非对称量化。 |
fl | int8_t | RKNN_TENSOR_QNT_DFP量化类型的参数。 |
scale | float | RKNN_TENSOR_QNT_AFFINE_ASYMMETRIC量化类型的参数。 |
w_stride | uint32_t | 实际存储一行图像数据的像素数目,等于一行的有效数据像素数目 为硬件快速跨越到下一行而补齐的一些无效像素数目, 单位是像素。 |
size_with_stride | uint32_t | 实际存储图像数据所占的存储空间的大小(包括了补齐的无效像素的存储空间大小)。 |
pass_through | uint8_t | 0表示未转换的数据,1表示转换后的数据,转换包括归一化和量化。 |
h_stride | uint32_t | 仅用于多batch输入场景,且该值由用户设置。目的是NPU正确地读取每batch数据的起始地址,它等于原始模型的输入高度 跨越下一列而补齐的无效像素数目。如果设置成0,表示与原始模型输入高度一致,单位是像素。 |
7.2.1.5 rknn_perf_detail
结构体rknn_perf_detail表示模型的性能详情,结构体的定义如下表所示(RV1106/RV1106B/RV1103/RV1103B/RK2118暂不支持):
成员变量 | 数据类型 | 含义 |
perf_data | char* | 性能详情包含网络每层运行时间,能够直接打印出来查看。 |
data_len | uint64_t | 存放性能详情的字符串数组的长度。 |
7.2.1.6 rknn_perf_run
结构体rknn_perf_run表示模型的总体性能,结构体的定义如下表所示(RV1106/RV1106B/RV1103/RV1103B/RK2118暂不支持):
成员变量 | 数据类型 | 含义 |
run_duration | int64_t | 网络总体运行(不包含设置输入/输出)时间,单位是微秒。 |
7.2.1.7 rknn_mem_size
结构体rknn_mem_size表示初始化模型时的内存分配情况,结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
total_weight_size | uint32_t | 模型的权重占用的内存大小。 |
total_internal_size | uint32_t | 模型的中间tensor占用的内存大小。 |
total_dma_allocated_size | uint64_t | 模型申请的所有dma内存之和。 |
total_sram_size | uint32_t | 只针对RK3588有效,为NPU预留的系统SRAM大小(具体使用方式参考 《RK3588_NPU_SRAM_usage.md》 )。 |
free_sram_size | uint32_t | 只针对RK3588有效,当前可用的空闲SRAM大小(具体使用方式参考《RK3588_NPU_SRAM_usage.md》)。 |
reserved[12] | uint32_t | 预留。 |
7.2.1.8 rknn_tensor_mem
结构体rknn_tensor_mem表示tensor的内存信息。结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
virt_addr | void* | 该tensor的虚拟地址。 |
phys_addr | uint64_t | 该tensor的物理地址。 |
fd | int32_t | 该tensor的文件描述符。 |
offset | int32_t | 相较于文件描述符和虚拟地址的偏移量。 |
size | uint32_t | 该tensor占用的内存大小。 |
flags | uint32_t | rknn_tensor_mem的标志位,有以下标志:RKNN_TENSOR_MEMORY_FALGS_ALLOC_INSIDE: 表rknn_tensor_mem结构体由运行时创建;RKNN_TENSOR_MEMORY_FLAGS_FROM_FD: 表明rknn_tensor_mem结构体由fd构造;RKNN_TENSOR_MEMORY_FLAGS_FROM_PHYS: 表明rknn_tensor_mem结构体由物理地址构造; 用户不用关注该标志。 |
priv_data | void* | 内存的私有数据。 |
7.2.1.9 rknn_input
结构体rknn_input表示模型的一个数据输入,用来作为参数传入给rknn_inputs_set函数。结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
index | uint32_t | 该输入的索引位置。 |
buf | void* | 输入数据的指针。 |
size | uint32_t | 输入数据所占内存大小。 |
pass_through | uint8_t | 设置为1时会将buf存放的输入数据直接设置给模型的输入节点,不做任何预处理。 |
type | rknn_tensor_type | 输入数据的类型。 |
fmt | rknn_tensor_format | 输入数据的格式。 |
7.2.1.10 rknn_output
结构体rknn_output表示模型的一个数据输出,用来作为参数传入给rknn_outputs_get函数,在函数执行后,结构体对象将会被赋值。结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
want_float | uint8_t | 标识是否需要将输出数据转为float类型输出,该字段由用户设置。 |
is_prealloc | uint8_t | 标识存放输出数据是否是预分配,该字段由用户设置。 |
index | uint32_t | 该输出的索引位置,该字段由用户设置。 |
buf | void* | 输出数据的指针,该字段由接口返回。 |
size | uint32_t | 输出数据所占内存大小,该字段由接口返回。 |
7.2.1.11 rknn_init_extend
结构体rknn_init_extend表示初始化模型时的扩展信息。结构体的定义如下表所示(RV1106/RV1106B/RV1103/RV1103B/RK2118暂不支持):
成员变量 | 数据类型 | 含义 |
ctx | rknn_context | 已初始化的rknn_context对象。 |
real_model_offset | int32_t | 真正rknn模型在文件中的偏移,只有以文件路径为参数或零拷贝模型内存初始化时才生效。 |
real_model_size | uint32_t | 真正rknn模型在文件中的大小,只有以文件路径为参数或另拷贝模型内存初始化时才生效。 |
model_buffer_fd | int32_t | 使用RKNN_FLAG_MODEL_BUFFER_ZERO_COPY标志初始化后,NPU分配的模型内存代表的fd。 |
model_buffer_flags | uint32_t | 使用RKNN_FLAG_MODEL_BUFFER_ZERO_COPY标志初始化后,NPU分配的模型内存代表的内存标志。 |
reserved | uint8_t[] | 预留数据位。 |
7.2.1.12 rknn_run_extend
结构体rknn_run_extend表示模型推理时的扩展信息,目前暂不支持使用。结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
frame_id | uint64_t | 表示当前推理的帧序号。 |
non_block | int32_t | 0表示阻塞模式,1表示非阻塞模式,非阻塞即rknn_run调用直接返回。 |
timeout_ms | int32_t | 推理超时的上限,单位毫秒。 |
fence_fd | int32_t | 用于非阻塞执行推理,暂不支持。 |
7.2.1.13 rknn_output_extend
结构体rknn_output_extend表示获取输出的扩展信息,目前暂不支持使用。结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
frame_id | int32_t | 输出结果的帧序号。 |
7.2.1.14 rknn_custom_string
结构体rknn_custom_string表示转换RKNN模型时,用户设置的自定义字符串,结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
string | char[] | 用户自定义字符串。 |
7.2.2 基础API说明
7.2.2.1 rknn_init
rknn_init初始化函数功能为创建rknn_context对象、加载RKNN模型以及根据flag和rknn_init_extend结构体执行特定的初始化行为。
API | rknn_init |
功能 | 初始化rknn上下文。 |
参数 | rknn_context *context:rknn_context指针。 |
void *model:RKNN模型的二进制数据或者RKNN模型路径。当参数size大于0时,model表示二进制数据;当参数size等于0时,model表示RKNN模型路径。 | |
uint32_t size:当model是二进制数据,表示模型大小,当model是路径,则设置为0。 | |
uint32_t flag:初始化标志,默认初始化行为需要设置为0。 | |
rknn_init_extend:特定初始化时的扩展信息。没有使用,传入NULL即可。如果需要共享模型weight内存,则需要传入另个模型rknn_context指针。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
rknn_context ctx; int ret = rknn_init(&ctx, model_data, model_data_size, 0, NULL);
各个初始化标志说明如下:
RKNN_FLAG_COLLECT_PERF_MASK:用于运行时查询网络各层时间;RKNN_FLAG_MEM_ALLOC_OUTSIDE:用于表示模型输入、输出、权重、中间tensor内存全部由用户分配,它主要有两方面的作用:
所有内存均是用户自行分配,便于对整个系统内存进行统筹安排。
用于内存复用,特别是针对RV1103/RV1106/RV1103B/RV1106B/RK2118这种内存极为紧张的情况。假设有模型A、B 两个模型,这两个模型在设计上串行运行的,那么这两个模型的中间tensor的内存就可以复用。示例代码如下:
rknn_context ctx_a, ctx_b; rknn_init(&ctx_a, model_path_a, 0, RKNN_FLAG_MEM_ALLOC_OUTSIDE, NULL); rknn_query(ctx_a, RKNN_QUERY_MEM_SIZE, &mem_size_a, sizeof(mem_size_a)); rknn_init(&ctx_b, model_path_b, 0, RKNN_FLAG_MEM_ALLOC_OUTSIDE, NULL); rknn_query(ctx_b, RKNN_QUERY_MEM_SIZE, &mem_size_b, sizeof(mem_size_b)); max_internal_size = MAX(mem_size_a.total_internal_size, mem_size_b.total_internal_size); internal_mem_max = rknn_create_mem(ctx_a, max_internal_size); internal_mem_a = rknn_create_mem_from_fd(ctx_a, internal_mem_max->fd, internal_mem_max->virt_addr, mem_size_a.total_internal_size, 0); rknn_set_internal_mem(ctx_a, internal_mem_a); internal_mem_b = rknn_create_mem_from_fd(ctx_b, internal_mem_max->fd, internal_mem_max->virt_addr, mem_size_b.total_internal_size, 0); rknn_set_internal_mem(ctx_b, internal_mem_b);
RKNN_FLAG_SHARE_WEIGHT_MEM:用于共享另一个模型的weight权重。主要用于模拟不定长度模型输入(RKNPU运行时库版本大于等于1.5.0后该功能被动态shape功能替代)。比如对于某些语音模型,输入长度不定,但由于NPU无法支持不定长输入,因此需要生成几个不同分辨率的RKNN模,其中,只有一个RKNN模型的保留完整权重,其他RKNN模型不带权重。在初始化不带权重RKNN模型时,使用该标志能让当前上下文共享完整RKNN模型的权重。假设需要分辨率A、B两个模型,则使用流程如下:
使用RKNN-Toolkit2生成分辨率A的模型。
使用RKNN-Toolkit2生成不带权重的分辨率B的模型,rknn.config()中,remove_weight要设置成True,主要目的是减少模型B的大小。
在板子上,正常初始化模型A。
通过RKNN_FLAG_SHARE_WEIGHT_MEM的flags初始化模型B。
其他按照原来的方式使用。板端参考代码如下:
rknn_context ctx_a, ctx_b; rknn_init(&ctx_a, model_path_a, 0, 0, NULL); rknn_init_extend extend; extend.ctx = ctx_a; rknn_init(&ctx_b, model_path_b, 0, RKNN_FLAG_SHARE_WEIGHT_MEM, &extend);
RKNN_FLAG_COLLECT_MODEL_INFO_ONLY:用于初始化一个空上下文,仅用于调用rknn_query接口查询模型weight内存总大小和中间tensor总大小,无法进行推理;
RKNN_FLAG_INTERNAL_ALLOC_OUTSIDE: 表示模型中间tensor由用户分配,常用于用户自行管理和复用多个模型之间的中间tensor内存;
RKNN_FLAG_EXECUTE_FALLBACK_PRIOR_DEVICE_GPU: 表示所有NPU不支持的层优先选择运行在GPU上,但并不保证运行在GPU上,实际运行的后端设备取决于运行时对该算子的支持情况;
RKNN_FLAG_ENABLE_SRAM: 表示中间tensor内存尽可能分配在SRAM上;
RKNN_FLAG_SHARE_SRAM: 用于当前上下文尝试共享另一个上下文的SRAM内存地址空间,要求当前上下文初始化时必须同时启用RKNN_FLAG_ENABLE_SRAM标志;
RKNN_FLAG_DISABLE_PROC_HIGH_PRIORITY: 表示当前上下文使用默认进程优先级。不设置该标志,进程nice值是-19;
RKNN_FLAG_DISABLE_FLUSH_INPUT_MEM_CACHE: 设置该标志后,runtime内部不主动刷新输入tensor缓存,用户必须确保输入tensor在调用 rknn_run 之前已刷新缓存。主要用于当输入数据没有CPU访问时,减少runtime内部刷cache的耗时。
RKNN_FLAG_DISABLE_FLUSH_OUTPUT_MEM_CACHE: 设置该标志后,runtime不主动清除输出tensor缓存。此时用户不能直接访问output_mem->virt_addr,这会导致缓存一致性问题。 如果用户想使用output_mem->virt_addr,必须使用 rknn_mem_sync (ctx, mem, RKNN_MEMORY_SYNC_FROM_DEVICE)来刷新缓存。该标志一般在NPU的输出数据不被CPU访问时使用,比如输出数据由 GPU 或 RGA 访问以减少刷新缓存所需的时间。
RKNN_FLAG_MODEL_BUFFER_ZERO_COPY:表示rknn_init接口的传入的模型buffer是rknn_create_mem或者rknn_create_mem2接口分配的内存,runtime内部不需要拷贝一次模型buffer,减少运行时的内存占用,但需要用户保证上下文销毁前模型内存有效,并且在销毁上下文后释放该内存。初始化上下文时,rknn_init接口的rknn_init_extend参数,其成员real_model_offset、real_model_size、model_buffer_fd和model_buffer_flags根据rknn_create_mem2接口返回的rknn_tensor_mem设置。
RKNN_MEM_FLAG_ALLOC_NO_CONTEXT:在使用rknn_create_mem2接口分配内存时,设置该标志后,允许ctx参数是0或者NULL。从而用户在未初始化任何一个上下文之前就能获取NPU驱动分配的内存,返回的内存结构体需使用rknn_destroy_mem接口释放,释放的接口可使用任意一个上下文作为参数。
示例代码如下:
rknn_tensor_mem* model_mem = rknn_create_mem2(ctx, model_size, RKNN_MEM_FLAG_ALLOC_NO_CONTEXT); memcpy(model_mem->virt_addr, model_data, model_size); rknn_init_extend init_ext; memset(&init_ext, 0, sizeof(rknn_init_extend)); init_ext.real_model_offset = 0; init_ext.real_model_size = model_size; init_ext.model_buffer_fd = model_mem->fd; init_ext.model_buffer_flags = model_mem->flags; int ret = rknn_init(&ctx, model_mem->virt_addr, model_size, RKNN_FLAG_MODEL_BUFFER_ZERO_COPY,&init_ext); // do rknn inference... rknn_destroy_mem(ctx, model_mem); rknn_destroy(ctx);
7.2.2.2 rknn_set_core_mask
rknn_set_core_mask函数指定工作的NPU核心,该函数仅支持RK3576/RK3588平台,在单核NPU架构的平台上设置会返回错误。
API | rknn_set_core_mask |
功能 | 设置运行的NPU核心。 |
参数 | rknn_context context:rknn_context对象。 |
rknn_core_mask core_mask:NPU核心的枚举类型,目前有如下方式配置: RKNN_NPU_CORE_AUTO:表示自动调度模型,自动运行在当前空闲的NPU核上; RKNN_NPU_CORE_0:表示运行在NPU0核上; RKNN_NPU_CORE_1:表示运行在NPU1核上; RKNN_NPU_CORE_2:表示运行在NPU2核上; RKNN_NPU_CORE_0_1:表示同时工作在NPU0、NPU1核上; RKNN_NPU_CORE_0_1_2:表示同时工作在NPU0、NPU1、NPU2核上; RKNN_NPU_CORE_ALL:表示工作在所有的NPU核心上 |
|
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
rknn_context ctx; rknn_core_mask core_mask = RKNN_NPU_CORE_0; int ret = rknn_set_core_mask(ctx, core_mask);
在RKNN_NPU_CORE_0_1及RKNN_NPU_CORE_0_1_2模式下,目前以下OP能获得更好的加速:Conv、DepthwiseConvolution、Add、Concat、Relu、Clip、Relu6、ThresholdedRelu、PRelu、LeakyRelu,其余类型OP将fallback至单核Core0中运行,部分类型OP(如Pool类、ConvTranspose等)将在后续更新版本中支持。
7.2.2.3 rknn_set_batch_core_num
rknn_set_batch_core_num函数指定多batch RKNN模型(RKNN-Toolkit2转换时设置rknn_batch_size大于1导出的模型)的NPU核心数量,该函数仅支持RK3588/RK3576平台。
API | rknn_set_batch_core_num |
功能 | 设置多batch RKNN模型运行的NPU核心数量。 |
参数 | rknn_context context:rknn_context对象。 |
int core_num:指定运行的核心数量。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
rknn_context ctx; int ret = rknn_set_batch_core_num(ctx, 2);
7.2.2.4 rknn_dup_context
rknn_dup_context生成一个指向同一个模型的新context,可用于多线程执行相同模型时的权重复用。RV1106/RV1103/RV1106B/RV1103B/RK2118平台暂不支持 。
API | rknn_dup_context |
功能 | 生成同一个模型的两个ctx,复用模型的权重信息。 |
参数 | rknn_context * context_in:rknn_context指针。初始化后的rknn_context对象。 |
rknn_context * context_out:rknn_context指针。生成新的rknn_context对象。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
rknn_context ctx_in; rknn_context ctx_out; int ret = rknn_dup_context(&ctx_in, &ctx_out);
7.2.2.5 rknn_destroy
rknn_destroy函数将释放传入的rknn_context及其相关资源。
API | rknn_destroy |
功能 | 销毁rknn_context对象及其相关资源。 |
参数 | rknn_context context:要销毁的rknn_context对象。 |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
rknn_context ctx; int ret = rknn_destroy(ctx);
7.2.2.6 rknn_query
rknn_query函数能够查询获取到模型输入输出信息、逐层运行时间、模型推理的总时间、SDK版本、内存占用信息、用户自定义字符串等信息。
API | rknn_query |
功能 | 查询模型与SDK的相关信息。 |
参数 | rknn_context context:rknn_context对象。 |
rknn_query_cmd :查询命令。 | |
void* info:存放返回结果的结构体变量。 | |
uint32_t size:info对应的结构体变量的大小。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
当前SDK支持的查询命令如下表所示:
查询命令 | 返回结果结构体 | 功能 |
RKNN_QUERY_IN_OUT_NUM | rknn_input_output_num | 查询输入输出tensor个数。 |
RKNN_QUERY_INPUT_ATTR | rknn_tensor_attr | 查询输入tensor属性。 |
RKNN_QUERY_OUTPUT_ATTR | rknn_tensor_attr | 查询输出tensor属性。 |
RKNN_QUERY_PERF_DETAIL | rknn_perf_detail | 查询网络各层运行时间,需要调用rknn_init接口时,设置RKNN_FLAG_COLLECT_PERF_MASK标志才能生效。 |
RKNN_QUERY_PERF_RUN | rknn_perf_run | 查询推理模型(不包含设置输入/输出)的耗时,单位是微秒。 |
RKNN_QUERY_SDK_VERSION | rknn_sdk_version | 查询SDK版本。 |
RKNN_QUERY_MEM_SIZE | rknn_mem_size | 查询分配给权重和网络中间tensor的内存大小。 |
RKNN_QUERY_CUSTOM_STRING | rknn_custom_string | 查询RKNN模型里面的用户自定义字符串信息。 |
RKNN_QUERY_NATIVE_INPUT_ATTR | rknn_tensor_attr | 使用零拷贝API接口时,查询原生输入tensor属性,它是NPU直接读取的模型输入属性。 |
RKNN_QUERY_NATIVE_OUTPUT_ATTR | rknn_tensor_attr | 使用零拷贝API接口时,查询原生输出tensor属性,它是NPU直接输出的模型输出属性。 |
RKNN_QUERY_NATIVE_NC1HWC2_INPUT_ATTR | rknn_tensor_attr | 使用零拷贝API接口时,查询原生输入tensor属性,它是NPU直接读取的模型输入属性与RKNN_QUERY_NATIVE_INPUT_ATTR查询结果一致。 |
RKNN_QUERY_NATIVE_NC1HWC2_OUTPUT_ATTR | rknn_tensor_attr | 使用零拷贝API接口时,查询原生输出tensor属性,它是NPU直接输出的模型输出属与RKNN_QUERY_NATIVE_OUTPUT_ATTR查询结果一致性。 |
RKNN_QUERY_NATIVE_NHWC_INPUT_ATTR | rknn_tensor_attr | 使用零拷贝API接口时,查询原生输入 tensor属性与RKNN_QUERY_NATIVE_INPUT_ATTR查询结果一致 。 |
RKNN_QUERY_NATIVE_NHWC_OUTPUT_ATTR | rknn_tensor_attr | 使用零拷贝API接口时,查询原生输出NHWC tensor属性。 |
RKNN_QUERY_DEVICE_MEM_INFO | rknn_tensor_mem | 查询模型buffer的内存属性。 |
RKNN_QUERY_INPUT_DYNAMIC_RANGE | rknn_input_range | 使用支持动态形状RKNN模型时,查询模型支持输入形状数量、列表、形状对应的数据布局和名称等信息。 |
RKNN_QUERY_CURRENT_INPUT_ATTR | rknn_tensor_attr | 使用支持动态形状RKNN模型时,查询模型当前推理所使用的输入属性。 |
RKNN_QUERY_CURRENT_OUTPUT_ATTR | rknn_tensor_attr | 使用支持动态形状RKNN模型时,查询模型当前推理所使用的输出属性。 |
RKNN_QUERY_CURRENT_NATIVE_INPUT_ATTR | rknn_tensor_attr | 使用支持动态形状RKNN模型时,查询模型当前推理所使用的NPU原生输入属性。 |
RKNN_QUERY_CURRENT_NATIVE_OUTPUT_ATTR | rknn_tensor_att | 使用支持动态形状RKNN模型时,查询模型当前推理所使用的NPU原生输出属性。 |
各个指令用法的详细说明,如下:
1.查询SDK版本
传入RKNN_QUERY_SDK_VERSION命令可以查询RKNN SDK的版本信息。其中需要先创建rknn_sdk_version结构体对象。
示例代码如下:
rknn_sdk_version version; ret = rknn_query(ctx, RKNN_QUERY_SDK_VERSION, &version, sizeof(rknn_sdk_version)); printf("sdk api version: %sn", version.api_version); printf("driver version: %sn", version.drv_version);
2. 查询输入输出tensor个数
在rknn_init接口调用完毕后,传入RKNN_QUERY_IN_OUT_NUM命令可以查询模型输入输出tensor的个数。其中需要先创建rknn_input_output_num结构体对象。
示例代码如下:
rknn_input_output_num io_num; ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num)); printf("model input num: %d, output num: %dn", io_num.n_input, io_num.n_output);
3. 查询输入tensor属性(用于通用API接口)
在rknn_init接口调用完毕后,传入RKNN_QUERY_INPUT_ATTR命令可以查询模型输入tensor的属性。其中需要先创建rknn_tensor_attr结构体对象 (注意:RV1106/RV1103/RV1106B/RV1103B/RK2118 查询出来的tensor是原始输入native的tensor) 。
示例代码如下:
rknn_tensor_attr input_attrs[io_num.n_input]; memset(input_attrs, 0, sizeof(input_attrs)); for (int i = 0; i < io_num.n_input; i ) { input_attrs[i].index = i; ret = rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &(input_attrs[i]), sizeof(rknn_tensor_attr)); }
4. 查询输出tensor属性(用于通用API接口)
在rknn_init接口调用完毕后,传入RKNN_QUERY_OUTPUT_ATTR命令可以查询模型输出tensor的属性。其中需要先创建rknn_tensor_attr结构体对象。
示例代码如下:
rknn_tensor_attr output_attrs[io_num.n_output]; memset(output_attrs, 0, sizeof(output_attrs)); for (int i = 0; i < io_num.n_output; i ) { output_attrs[i].index = i; ret = rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs[i]), sizeof(rknn_tensor_attr)); }
5. 查询模型推理的逐层耗时
在rknn_run接口调用完毕后,rknn_query接口传入RKNN_QUERY_PERF_DETAIL可以查询网络推理时逐层的耗时,单位是微秒。使用该命令的前提是,在rknn_init接口的flag参数需要包含RKNN_FLAG_COLLECT_PERF_MASK标志。
示例代码如下:
rknn_context ctx; int ret = rknn_init(&ctx, model_data, model_data_size, RKNN_FLAG_COLLECT_PERF_MASK, NULL); ... ret = rknn_run(ctx,NULL); ... rknn_perf_detail perf_detail; ret = rknn_query(ctx, RKNN_QUERY_PERF_DETAIL, &perf_detail, sizeof(perf_detail));
6. 查询模型推理的总耗时
在rknn_run接口调用完毕后,rknn_query接口传入RKNN_QUERY_PERF_RUN可以查询上模型推理(不包含设置输入/输出)的耗时,单位是微秒。
示例代码如下:
rknn_context ctx; int ret = rknn_init(&ctx, model_data, model_data_size, 0, NULL); ... ret = rknn_run(ctx,NULL); ... rknn_perf_run perf_run; ret = rknn_query(ctx, RKNN_QUERY_PERF_RUN, &perf_run, sizeof(perf_run));
7. 查询模型的内存占用情况
在rknn_init接口调用完毕后,当用户需要自行分配网络的内存时,rknn_query接口传入RKNN_QUERY_MEM_SIZE可以查询模型的权重、网络中间tensor的内存(不包括输入和输出)、推演模型所用的所有DMA内存的以及SRAM内存(如果sram没开或者没有此项功能则为0)的占用情况。使用该命令的前提是在rknn_init接口的flag参数需要包含RKNN_FLAG_MEM_ALLOC_OUTSIDE标志。
示例代码如下:
rknn_context ctx; int ret = rknn_init(&ctx, model_data, model_data_size, RKNN_FLAG_MEM_ALLOC_OUTSIDE , NULL); rknn_mem_size mem_size; ret = rknn_query(ctx, RKNN_QUERY_MEM_SIZE, &mem_size, sizeof(mem_size));
8. 查询模型中用户自定义字符串
在rknn_init接口调用完毕后,当用户需要查询生成RKNN模型时加入的自定义字符串,rknn_query接口传入RKNN_QUERY_CUSTOM_STRING可以获取该字符串。例如,在转换RKNN模型时,用户填入“RGB”的自定义字符来标识RKNN模型输入是RGB格式三通道图像而不是BGR格式三通道图像,在运行时则根据查询到的“RGB”信息将数据转换成RGB图像。
示例代码如下:
rknn_context ctx; int ret = rknn_init(&ctx, model_data, model_data_size, 0, NULL); rknn_custom_string custom_string; ret = rknn_query(ctx, RKNN_QUERY_CUSTOM_STRING, &custom_string, sizeof(custom_string));
9. 查询原生输入tensor属性(用于零拷贝API接口)
在rknn_init接口调用完毕后,传入RKNN_QUERY_NATIVE_INPUT_ATTR命令(同RKNN_QUERY_NATIVE_NC1HWC2_INPUT_ATTR)可以查询模型原生输入tensor的属性。其中需要先创建rknn_tensor_attr结构体对象。
示例代码如下:
rknn_tensor_attr input_attrs[io_num.n_input]; memset(input_attrs, 0, sizeof(input_attrs)); for (int i = 0; i < io_num.n_input; i ) { input_attrs[i].index = i; ret = rknn_query(ctx, RKNN_QUERY_NATIVE_INPUT_ATTR, &(input_attrs[i]), sizeof(rknn_tensor_attr)); }
10. 查询原生输出tensor属性(用于零拷贝API接口)
在rknn_init接口调用完毕后,传入RKNN_QUERY_NATIVE_OUTPUT_ATTR命令(同RKNN_QUERY_NATIVE_NC1HWC2_OUTPUT_ATTR)可以查询模型原生输出tensor的属性。其中需要先创建rknn_tensor_attr结构体对象。
示例代码如下:
rknn_tensor_attr output_attrs[io_num.n_output]; memset(output_attrs, 0, sizeof(output_attrs)); for (int i = 0; i < io_num.n_output; i ) { output_attrs[i].index = i; ret = rknn_query(ctx, RKNN_QUERY_NATIVE_OUTPUT_ATTR, &(output_attrs[i]), sizeof(rknn_tensor_attr)); }
11. 查询NHWC格式原生输入tensor属性(用于零拷贝API接口)
在rknn_init接口调用完毕后,传入RKNN_QUERY_NATIVE_NHWC_INPUT_ATTR命令可以查询模型NHWC格式输入tensor的属性。其中需要先创建rknn_tensor_attr结构体对象。
示例代码如下:
rknn_tensor_attr input_attrs[io_num.n_input]; memset(input_attrs, 0, sizeof(input_attrs)); for (int i = 0; i < io_num.n_input; i ) { input_attrs[i].index = i; ret = rknn_query(ctx, RKNN_QUERY_NATIVE_NHWC_INPUT_ATTR, &(input_attrs[i]), sizeof(rknn_tensor_attr)); }
12. 查询NHWC格式原生输出tensor属性(用于零拷贝API接口)
在rknn_init接口调用完毕后,传入RKNN_QUERY_NATIVE_NHWC_OUTPUT_ATTR命令可以查询模型NHWC格式输出tensor的属性。其中需要先创建rknn_tensor_attr结构体对象。
示例代码如下:
rknn_tensor_attr output_attrs[io_num.n_output]; memset(output_attrs, 0, sizeof(output_attrs)); for (int i = 0; i < io_num.n_output; i ) { output_attrs[i].index = i; ret = rknn_query(ctx, RKNN_QUERY_NATIVE_NHWC_OUTPUT_ATTR, &(output_attrs[i]), sizeof(rknn_tensor_attr)); }
13. 查询模型buffer的内存属性(注:仅RV1106/RV1103/RV1106B/RV1103B/RK2118支持该查询)
在rknn_init接口调用完毕后,传入RKNN_QUERY_DEVICE_MEM_INFO命令可以查询Runtime内部开辟的模型buffer的包括fd、物理地址等属性。
rknn_tensor_mem mem_info; memset(&mem_info, 0, sizeof(mem_info)); ret = rknn_query(ctx, RKNN_QUERY_DEVICE_MEM_INFO, &mem_info, sizeof(mem_info));
14. 查询RKNN模型支持的动态输入形状信息(注:RV1106/RV1103/RV1106B/RV1103B/RK2118不支持该接口)
在rknn_init接口调用完毕后,传入RKNN_QUERY_INPUT_DYNAMIC_RANGE命令可以查询模型支持的输入形状信息,包含输入形状个数 、输入形状列表、输入形状对应的布局和名称等信息。其中需要先创建rknn_input_range结构体对象。
示例代码如下:
rknn_input_range dyn_range[io_num.n_input]; memset(dyn_range, 0, io_num.n_input * sizeof(rknn_input_range)); for (uint32_t i = 0; i < io_num.n_input; i ) { dyn_range[i].index = i; ret = rknn_query(ctx, RKNN_QUERY_INPUT_DYNAMIC_RANGE, &dyn_range[i], sizeof(rknn_input_range)); }
15. 查询RKNN模型当前使用的输入动态形状
在rknn_set_input_shapes接口调用完毕后,传入RKNN_QUERY_CURRENT_INPUT_ATTR命令可以查询模型当前使用的输入属性信息。其中需要先创建rknn_tensor_attr结构体(注:RV1106/RV1103/RV1106B/RV1103B/RK2118不支持该命令)。
示例代码如下:
rknn_tensor_attr cur_input_attrs[io_num.n_input]; memset(cur_input_attrs, 0, io_num.n_input * sizeof(rknn_tensor_attr)); for (uint32_t i = 0; i < io_num.n_input; i ) { cur_input_attrs[i].index = i; ret = rknn_query(ctx, RKNN_QUERY_CURRENT_INPUT_ATTR, &(cur_input_attrs[i]), sizeof(rknn_tensor_attr)); }
16. 查询RKNN模型当前使用的输出动态形状
在rknn_set_input_shapes接口调用完毕后,传入RKNN_QUERY_CURRENT_OUTPUT_ATTR命令可以查询模型当前使用的输出属性信息。其中需要先创建rknn_tensor_attr结构体(注:RV1106/RV1103/RV1106B/RV1103B/RK2118不支持该命令)。
示例代码如下:
rknn_tensor_attr cur_output_attrs[io_num.n_output]; memset(cur_output_attrs, 0, io_num.n_output * sizeof(rknn_tensor_attr)); for (uint32_t i = 0; i < io_num.n_output; i ) { cur_output_attrs[i].index = i; ret = rknn_query(ctx, RKNN_QUERY_CURRENT_OUTPUT_ATTR, &(cur_output_attrs[i]), sizeof(rknn_tensor_attr)); }
17. 查询RKNN模型当前使用的原生输入动态形状
在rknn_set_input_shapes接口调用完毕后,传入RKNN_QUERY_CURRENT_NATIVE_INPUT_ATTR命令可以查询模型当前使用的原生输入属性信息。其中需要先创建rknn_tensor_attr结构体(注:RV1106/RV1103/RV1106B/RV1103B/RK2118不支持该命令)。
示例代码如下:
rknn_tensor_attr cur_input_attrs[io_num.n_input]; memset(cur_input_attrs, 0, io_num.n_input * sizeof(rknn_tensor_attr)); for (uint32_t i = 0; i < io_num.n_input; i ) { cur_input_attrs[i].index = i; ret = rknn_query(ctx, RKNN_QUERY_CURRENT_NATIVE_INPUT_ATTR, &(cur_input_attrs[i]), sizeof(rknn_tensor_attr)); }
18. 查询RKNN模型当前使用的原生输出动态形状
在rknn_set_input_shapes接口调用完毕后,传入RKNN_QUERY_CURRENT_NATIVE_OUTPUT_ATTR命令可以查询模型当前使用的原生输出属性信息。其中需要先创建rknn_tensor_attr结构体(注:RV1106/RV1103/RV1106B/RV1103B/RK2118不支持该命令)。
示例代码如下:
rknn_tensor_attr cur_output_attrs[io_num.n_output]; memset(cur_output_attrs, 0, io_num.n_output * sizeof(rknn_tensor_attr)); for (uint32_t i = 0; i < io_num.n_output; i ) { cur_output_attrs[i].index = i; ret = rknn_query(ctx, RKNN_QUERY_CURRENT_NATIVE_OUTPUT_ATTR, &(cur_output_attrs[i]), sizeof(rknn_tensor_attr)); }
7.2.2.7 rknn_inputs_set
通过rknn_inputs_set函数可以设置模型的输入数据。该函数能够支持多个输入,其中每个输入是rknn_input结构体对象,在传入之前用户需要设置该对象,注:RV1106/RV1103/RV1106B/RV1103B/RK2118不支持该接口。
API | rknn_inputs_set |
功能 | 设置模型输入数据。 |
参数 | rknn_context context:rknn_context对象。 |
uint32_t n_inputs:输入数据个数。 | |
rknn_input inputs[]:输入数据数组,数组每个元素是rknn_input结构体对象。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
rknn_input inputs[1]; memset(inputs, 0, sizeof(inputs)); inputs[0].index = 0; inputs[0].type = RKNN_TENSOR_UINT8; inputs[0].size = img_width*img_height*img_channels; inputs[0].fmt = RKNN_TENSOR_NHWC; inputs[0].buf = in_data; inputs[0].pass_through = 0; ret = rknn_inputs_set(ctx, 1, inputs);
7.2.2.8 rknn_run
rknn_run函数将执行一次模型推理,调用之前需要先通过rknn_inputs_set函数或者零拷贝的接口设置输入数据。
API | rknn_run |
功能 | 执行一次模型推理。 |
参数 | rknn_context context:rknn_context对象。 |
rknn_run_extend* extend:保留扩展,当前没有使用,传入NULL即可。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
ret = rknn_run(ctx, NULL);
7.2.2.9 rknn_outputs_get
rknn_outputs_get函数可以获取模型推理的输出数据。该函数能够一次获取多个输出数据。其中每个输出是rknn_output结构体对象,在函数调用之前需要依次创建并设置每个rknn_output对象。
对于输出数据的buffer存放可以采用两种方式:一种是用户自行申请和释放,此时rknn_output对象的is_prealloc需要设置为1,并且将buf指针指向用户申请的buffer;另一种是由rknn来进行分配,此时rknn_output对象的is_prealloc设置为0即可,函数执行之后buf将指向输出数据。注:RV1106/RV1103/RV1106B/RV1103B/RK2118不支持该接口
API | rknn_outputs_get |
功能 | 获取模型推理输出。 |
参数 | rknn_context context:rknn_context对象。 |
uint32_t n_outputs:输出数据个数。 | |
rknn_output outputs[]:输出数据的数组,其中数组每个元素为rknn_output结构体对象,代表模型的一个输出。 | |
rknn_output_extend* extend:保留扩展,当前没有使用,传入NULL即可。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
rknn_output outputs[io_num.n_output]; memset(outputs, 0, sizeof(outputs)); for (int i = 0; i < io_num.n_output; i ) { outputs[i].index = i; outputs[i].is_prealloc = 0; outputs[i].want_float = 1; } ret = rknn_outputs_get(ctx, io_num.n_output, outputs, NULL);
7.2.2.10 rknn_outputs_release
rknn_outputs_release函数将释放rknn_outputs_get函数得到的输出的相关资源。
API | rknn_outputs_release |
功能 | 释放rknn_output对象。 |
参数 | rknn_context context:rknn_context对象。 |
uint32_t n_outputs:输出数据个数。 | |
rknn_output outputs[]:要销毁的rknn_output数组。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
ret = rknn_outputs_release(ctx, io_num.n_output, outputs);
7.2.2.11 rknn_create_mem_from_phys
当用户需要自己分配内存让NPU使用时,通过rknn_create_mem_from_phys函数可以创建一个rknn_tensor_mem结构体并得到它的指针,该函数通过传入物理地址、虚拟地址以及大小,外部内存相关的信息会赋值给rknn_tensor_mem结构体。
API | rknn_create_mem_from_phys |
功能 | 通过物理地址创建rknn_tensor_mem结构体并分配内存。 |
参数 | rknn_context context:rknn_context对象。 |
uint64_t phys_addr:内存的物理地址。 | |
void *virt_addr:内存的虚拟地址。 | |
uint32_t size:内存的大小。 | |
返回值 | rknn_tensor_mem*: tensor内存信息结构体指针。 |
示例代码如下:
//suppose we have got buffer information as input_phys, input_virt and size rknn_tensor_mem* input_mems [1]; input_mems[0] = rknn_create_mem_from_phys(ctx, input_phys, input_virt, size);
7.2.2.12 rknn_create_mem_from_fd
当用户要自己分配内存让NPU使用时,rknn_create_mem_from_fd函数可以创建一个rknn_tensor_mem结构体并得到它的指针,该函数通过传入文件描述符fd、偏移、虚拟地址以及大小,外部内存相关的信息会赋值给rknn_tensor_mem结构体。
API | rknn_create_mem_from_fd |
功能 | 通过文件描述符创建rknn_tensor_mem结构体。 |
参数 | rknn_context context:rknn_context对象。 |
int32_t fd:内存的文件描述符。 | |
void *virt_addr:内存的虚拟地址,fd对应的内存的首地址。 | |
uint32_t size:内存的大小。 | |
int32_t offset:内存相对于文件描述符和虚拟地址的偏移量。 | |
返回值 | rknn_tensor_mem*: tensor内存信息结构体指针。 |
示例代码如下:
//suppose we have got buffer information as input_fd, input_virt and size rknn_tensor_mem* input_mems [1]; input_mems[0] = rknn_create_mem_from_fd(ctx, input_fd, input_virt, size, 0);
7.2.2.13 rknn_create_mem
当用户要NPU内部分配内存时,rknn_create_mem函数可以分配用户指定的内存大小,并返回一个rknn_tensor_mem结构体。
API | rknn_create_mem |
功能 | 创建rknn_tensor_mem结构体并分配内存。 |
参数 | rknn_context context:rknn_context对象。 |
uint32_t size:内存的大小。 | |
返回值 | rknn_tensor_mem*: tensor内存信息结构体指针。 |
示例代码如下:
//suppose we have got buffer size rknn_tensor_mem* input_mems [1]; input_mems[0] = rknn_create_mem(ctx, size);
7.2.2.14 rknn_create_mem2
当用户要NPU内部分配内存时,rknn_create_mem2函数可以分配用户指定的内存大小及内存类型,并返回一个rknn_tensor_mem结构体。
API | rknn_create_mem2 |
功能 | 创建rknn_tensor_mem结构体并分配内存。 |
参数 | rknn_context context:rknn_context对象。 |
uint64_t size:内存的大小。 | |
uint64_t alloc_flags: 控制内存是否是cacheable的。 RKNN_FLAG_MEMORY_CACHEABLE:创建cacheable内存 RKNN_FLAG_MEMORY_NON_CACHEABLE: 创建non -cacheable内存 RKNN_FLAG_MEMORY_FLAGS_DEFAULT:同RKNN_FLAG_MEMORY_CACHEABLE |
|
返回值 | rknn_tensor_mem*: tensor内存信息结构体指针。 |
rknn_create_mem2与rknn_create_mem的主要区别是rknn_create_mem2带了一个alloc_flags,可以指定分配的内存是否cacheable的,而rknn_create_mem不能指定,默认就是cacheable。
示例代码如下:
//suppose we have got buffer size rknn_tensor_mem* input_mems [1]; input_mems[0] = rknn_create_mem2(ctx, size, RKNN_FLAG_MEMORY_NON_CACHEABLE);
7.2.2.15 rknn_destroy_mem
rknn_destroy_mem函数会销毁rknn_tensor_mem结构体,用户分配的内存需要自行释放。
API | rknn_destroy_mem |
功能 | 销毁rknn_tensor_mem结构体。 |
参数 | rknn_context context:rknn_context对象。 |
rknn_tensor_mem*:tensor内存信息结构体指针。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
rknn_tensor_mem* input_mems [1]; int ret = rknn_destroy_mem(ctx, input_mems[0]);
7.2.2.16 rknn_set_weight_mem
如果用户自己为网络权重分配内存,初始化相应的rknn_tensor_mem结构体后,在调用rknn_run前,通过rknn_set_weight_mem函数可以让NPU使用该内存。
API | rknn_set_weight_mem |
功能 | 设置包含权重内存信息的rknn_tensor_mem结构体。 |
参数 | rknn_context context:rknn_context对象。 |
rknn_tensor_mem*:权重tensor内存信息结构体指针。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
rknn_tensor_mem* weight_mems [1]; int ret = rknn_set_weight_mem(ctx, weight_mems[0]);
7.2.2.17 rknn_set_internal_mem
如果用户自己为网络中间tensor分配内存,初始化相应的rknn_tensor_mem结构体后,在调用rknn_run前,通过rknn_set_internal_mem函数可以让NPU使用该内存。
API | rknn_set_internal_mem |
功能 | 设置包含中间tensor内存信息的rknn_tensor_mem结构体。 |
参数 | rknn_context context:rknn_context对象。 |
rknn_tensor_mem*:模型中间tensor内存信息结构体指针。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
rknn_tensor_mem* internal_tensor_mems [1]; int ret = rknn_set_internal_mem(ctx, internal_tensor_mems[0]);
7.2.2.18 rknn_set_io_mem
如果用户自己为网络输入/输出tensor分配内存,初始化相应的rknn_tensor_mem结构体后,在调用rknn_run前,通过rknn_set_io_mem函数可以让NPU使用该内存。
API | rknn_set_io_mem |
功能 | 设置包含模型输入/输出内存信息的rknn_tensor_mem结构体。 |
参数 | rknn_context context:rknn_context对象。 |
rknn_tensor_mem*:输入/输出tensor内存信息结构体指针。 | |
rknn_tensor_attr *: 输入/输出tensor的属性。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
rknn_tensor_attr output_attrs[1]; rknn_tensor_mem* output_mems[1]; ret = rknn_query(ctx, RKNN_QUERY_NATIVE_OUTPUT_ATTR, &(output_attrs[0]), sizeof(rknn_tensor_attr)); output_mems[0] = rknn_create_mem(ctx, output_attrs[0].size_with_stride); rknn_set_io_mem(ctx, output_mems[0], &output_attrs[0]);
7.2.2.19 rknn_set_input_shape(deprecated)
该接口已经废弃,请使用rknn_set_input_shapes接口绑定输入形状。当前版本不可用,如要继续使用该接口,请使用1.5.0版本SDK并参考1.5.0版本的使用指南文档。
7.2.2.20 rknn_set_input_shapes
对于动态形状输入RKNN模型,在推理前必须指定当前使用的输入形状。该接口传入输入个数和rknn_tensor_attr数组,包含了每个输入形状和对应的数据布局信息,将每个rknn_tensor_attr结构体对象的索引、名称、形状(dims)和内存布局信息(fmt)必须填充,rknn_tensor_attr结构体其他成员无需设置。在使用该接口前,可先通过rknn_query函数查询RKNN模型支持的输入形状数量和动态形状列表,要求输入数据的形状在模型支持的输入形状列表中。初次运行或每次切换新的输入形状,需要调用该接口设置新的形状,否则,不需要重复调用该接口。
API | rknn_set_input_shapes |
功能 | 设置模型当前使用的输入形状。 |
参数 | rknn_context context:rknn_context对象。 |
uint32_t n_inputs:输入Tensor的数量。 | |
rknn_tensor_attr *: 输入tensor的属性数组指针,传递所有输入的形状信息,用户需要设置每个输入属性结构体中的index、name、dims、fmt、n_dims成员,其他成员无需设置。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
for (int i = 0; i < io_num.n_input; i ) { for (int j = 0; j < input_attrs[i].n_dims; j) { //使用第一个动态输入形状 input_attrs[i].dims[j] = dyn_range[i].dyn_range[0][j]; } } ret = rknn_set_input_shapes(ctx, io_num.n_input, input_attrs); if (ret < 0) { fprintf(stderr, "rknn_set_input_shapes error! ret=%dn", ret); return -1; }
7.2.2.21 rknn_mem_sync
rknn_create_mem函数创建的内存默认是带cacheable标志的,对于带cacheable标志创建的内存,在被CPU和NPU同时使用时,由于cache行为会导致数据一致性问题。该接口用于同步一块带cacheable标志创建的内存,保证CPU和NPU访问这块内存的数据是一致的。
API | rknn_mem_sync |
功能 | 同步CPU cache和DDR数据。 |
参数 | rknn_context context:rknn_context对象。 |
rknn_tensor_mem* mem:tensor内存信息结构体指针。 | |
rknn_mem_sync_mode mode: 表示刷新CPU cache和DDR数据的模式。 RKNN_MEMORY_SYNC_TO_DEVICE:表示CPU cache数据同步到DDR中,通常用于CPU写入内存后,NPU访问相同内存前使用该模式将cache中的数据写回DDR。 RKNN_MEMORY_SYNC_FROM_DEVICE:表示DDR数据同步到CPU cache,通常用于NPU写入内存后,使用该模式让下次CPU访问相同内存时,cache数据无效,CPU从DDR重新读取数据。 RKNN_MEMORY_SYNC_BIDIRECTIONAL:表示CPU cache数据同步到DDR同时令CPU 重新从DDR读取数据。 |
|
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
ret =rknn_mem_sync(ctx, &outputs[0].mem, RKNN_MEMORY_SYNC_FROM_DEVICE); if (ret < 0) { fprintf(stderr, " rknn_mem_sync error! ret=%dn", ret); return -1; }
7.2.3 矩阵乘法数据结构定义
7.2.3.1 rknn_matmul_info
rknn_matmul_info表示用于执行矩阵乘法的规格信息,它包含了矩阵乘法的规模、输入和输出矩阵的数据类型和内存排布。结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
M | int32_t | A矩阵的行数 |
K | int32_t | A矩阵的列数 |
N | int32_t | B矩阵的列数 |
type | rknn_matmul_type | 输入输出矩阵的数据类型: RKNN_FLOAT16_MM_FLOAT16_TO_FLOAT32:表示矩阵A和B是float16类型,矩阵C是float类型; RKNN_INT8_MM_INT8_TO_INT32:表示矩阵A和B是int8类型,矩阵C是int32类型; RKNN_INT8_MM_INT8_TO_INT8:表示矩阵A、B和C是int8类型; RKNN_FLOAT16_MM_FLOAT16_TO_FLOAT16:表示矩阵A、B和C是float16类型; RKNN_FLOAT16_MM_INT8_TO_FLOAT32:表示矩阵A是float16类型,矩阵B是int8类型,矩阵C是float类型; RKNN_FLOAT16_MM_INT8_TO_FLOAT16:表示矩阵A是float16类型,矩阵B是int8类型,矩阵C是float16类型; RKNN_FLOAT16_MM_INT4_TO_FLOAT32:表示矩阵A是float16类型,矩阵B是int4类型,矩阵C是float类型; RKNN_FLOAT16_MM_INT4_TO_FLOAT16:表示矩阵A是float16类型,矩阵B是int4类型,矩阵C是float16类型; RKNN_INT8_MM_INT8_TO_FLOAT32:表示矩阵A和B是int8类型,矩阵C是float类型; RKNN_INT4_MM_INT4_TO_INT16:表示矩阵A和B是int4类型,矩阵C是int16类型; RKNN_INT8_MM_INT4_TO_INT32:表示矩阵A是int8类型,B是int4类型,矩阵C是int32类型 |
B_layout | int16_t | 矩阵B的数据排列方式。 RKNN_MM_LAYOUT_NORM:表示矩阵B按照原始形状排列 ,即KxN的形状排列; RKNN_MM_LAYOUT_NATIVE:表示矩阵B按照高性能形状排列; RKNN_MM_LAYOUT_TP_NORM:表示矩阵B按照Transpose后的形状排列,即NxK的形状排列 |
B_quant_type | int16_t | 矩阵B的量化方式类型。 RKNN_QUANT_TYPE_PER_LAYER_SYM:表示矩阵B按照Per-Layer方式对称量化; RKNN_QUANT_TYPE_PER_LAYER_ASYM:表示矩阵B按照Per-Layer方式非对称量化; RKNN_QUANT_TYPE_PER_CHANNEL_SYM:表示矩阵B按照Per-Channel方式对称量化; RKNN_QUANT_TYPE_PER_CHANNEL_ASYM:表示矩阵B按照Per-Channel方式非对称量化; RKNN_QUANT_TYPE_PER_GROUP_SYM:表示矩阵B按照Per-Group方式对称量化; RKNN_QUANT_TYPE_PER_GROUP_ASYM:表示矩阵B按照Per-Group方式非对称量化 |
AC_layout | int16_t | 矩阵A和C的数据排列方式。 RKNN_MM_LAYOUT_NORM:表示矩阵A和C按照原始形状排列; RKNN_MM_LAYOUT_NATIVE:表示矩阵A和C按照高性能形状排列 |
AC_quant_type | int16_t | 矩阵A和C的量化方式类型。 RKNN_QUANT_TYPE_PER_LAYER_SYM:表示矩阵A和C按照Per-Layer方式对称量化; RKNN_QUANT_TYPE_PER_LAYER_ASYM:表示矩阵A和C按照Per-Layer方式非对称量化 |
iommu_domain_id | int32_t | 矩阵上下文所在的IOMMU地址空间域的索引。IOMMU地址空间与上下文一一对应,每个IOMMU地址空间大小为 4GB。该参数主要用于矩阵A、B和C的参数规格较大,某个域内NPU分配的内存超过4GB以后需切换另一个域时使 用。 |
group_size | int16_t | 一个组的元素数量,仅分组量化开启后生效。 |
reserved | int8_t[] | 预留字段 |
7.2.3.2 rknn_matmul_tensor_attr
rknn_matmul_tensor_attr表示每个矩阵tensor的属性,它包含了矩阵的名字、形状、大小和数据类型。结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
name | char[] | 矩阵的名字 |
n_dims | uint32_t | 矩阵的维度个数 |
dims | uint32_t[] | 矩阵的形状 |
size | uint32_t | 矩阵的大小,以字节为单位 |
ype | rknn_tensor_type | 矩阵的数据类型 |
7.2.3.3 rknn_matmul_io_attr
rknn_matmul_io_attr表示矩阵所有输入和输出tensor的属性,它包含了矩阵A、B和C的属性。结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
A | rknn_matmul_tensor_attr | 矩阵A的tensor属性 |
B | rknn_matmul_tensor_attr | 矩阵B的tensor属性 |
C | rknn_matmul_tensor_attr | 矩阵C的tensor属性 |
7.2.3.4 rknn_quant_params
rknn_quant_params表示矩阵的量化参数,包括name以及scale和zero_point数组的指针和长度,name用来标识矩阵的名称,它可以从初始化矩阵上下文时得到的rknn_matmul_io_attr结构体中获取。结构体定义如下表所示:
成员变量 | 数据类型 | 含义 |
name | char[] | 矩阵的名字 |
scale | float* | 矩阵的scale数组指针 |
scale_len | int32_t | 矩阵的scale数组长度 |
zp | int32_t* | 矩阵的zero_point数组指针 |
zp_len | int32_t | 矩阵的zero_point数组长度 |
7.2.3.5 rknn_matmul_shape
rknn_matmul_shape表示某个特定shape的矩阵乘法的M、K和N,在初始化动态shape的矩阵乘法上下文时,需要提供shape的数量,并使用rknn_matmul_shape结构体数组表示所有的输入的shape。结构体定义如下表所示:
成员变量 | 数据类型 | 含义 |
M | int32_t | 矩阵A的行数 |
K | int32_t | 矩阵A的列数 |
N | int32_t | 矩阵B的列数 |
7.2.4 矩阵乘法API说明
7.2.4.1 rknn_matmul_create
该函数的功能是根据传入的矩阵乘法规格等信息,完成矩阵乘法上下文的初始化,并返回输入和输出tensor的形状、大小和数据类型等信息。
API | rknn_matmul_create |
功能 | 初始化矩阵乘法上下文。 |
参数 | rknn_matmul_ctx* ctx:矩阵乘法上下文指针。 |
rknn_matmul_info* info:矩阵乘法的规格信息结构体指针。 | |
rknn_matmul_io_attr* io_attr:矩阵乘法输入和输出tensor属性结构体指针。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
rknn_matmul_info info; memset(&info, 0, sizeof(rknn_matmul_info)); info.M = 4; info.K = 64; info.N = 32; info.type = RKNN_INT8_MM_INT8_TO_INT32; info.B_layout = RKNN_MM_LAYOUT_NORM; info.AC_layout = RKNN_MM_LAYOUT_NORM; rknn_matmul_io_attr io_attr; memset(&io_attr, 0, sizeof(rknn_matmul_io_attr)); int ret = rknn_matmul_create(&ctx, &info, &io_attr); if (ret < 0) { printf("rknn_matmul_create fail! ret=%dn", ret); return -1; }
7.2.4.2 rknn_matmul_set_io_mem
该函数用于设置矩阵乘法运算的输入/输出内存。在调用该函数前,先使用rknn_create_mem接口创建的rknn_tensor_mem结构体指针,接着将其与rknn_matmul_create函数返回的矩阵A、B或C的rknn_matmul_tensor_attr结构体指针传入该函数,把输入和输出内存设置到矩阵乘法上下文中。在调用该函数前,要根据rknn_matmul_info中配置的内存排布准备好矩阵A和矩阵B的数据。
API | rknn_matmul_set_io_mem |
功能 | 设置矩阵乘法的输入/输出内存。 |
参数 | rknn_matmul_ctx ctx:矩阵乘法上下文。 |
rknn_tensor_mem* mem:tensor内存信息结构体指针。 | |
rknn_matmul_tensor_attr* attr:矩阵乘法输入和输出tensor属性结构体指针。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
// Create A rknn_tensor_mem* A = rknn_create_mem(ctx, io_attr.A.size); if (A == NULL) { printf("rknn_create_mem fail!n"); return -1; } memset(A->virt_addr, 1, A->size); rknn_matmul_io_attr io_attr; memset(&io_attr, 0, sizeof(rknn_matmul_io_attr)); int ret = rknn_matmul_create(&ctx, &info, &io_attr); if (ret < 0) { printf("rknn_matmul_create fail! ret=%dn", ret); return -1; } // Set A ret = rknn_matmul_set_io_mem(ctx, A, &io_attr.A); if (ret < 0) { printf("rknn_matmul_set_io_mem fail! ret=%dn", ret); return -1; }
7.2.4.3 rknn_matmul_set_core_mask
该函数用于设置矩阵乘法运算时可用的NPU核心(仅支持RK3588和RK3576平台)。在调用该函数前,需要先通过rknn_matmul_create函数初始化矩阵乘法上下文。可通过该函数设置的掩码值,指定需要使用的核心,以提高矩阵乘法运算的性能和效率。
API | rknn_matmul_set_core_mask |
功能 | 设置矩阵乘法运算的NPU核心掩码。 |
参数 | rknn_matmul_ctx ctx:矩阵乘法上下文。 |
rknn_core_mask core_mask:矩阵乘法运算的NPU核心掩码值,用于指定可用的NPU核心。掩码的每一位代表一个核心,如果对应位为1,则表示该核心可用;否则,表示该核心不可用(详细掩码说明见rknn_set_core_mask API参数)。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
rknn_matmul_set_core_mask(ctx, RKNN_NPU_CORE_AUTO);
7.2.4.4 rknn_matmul_set_quant_params
rknn_matmul_set_quant_params用于设置每个矩阵的量化参数,支持Per-Channel量化、Per-Layer量化和PerGroup量化方式的量化参数设置。当使用Per-Group量化时,rknn_quant_params中的scale和zp数组的长度等于N*K/group_size。当使用Per-Channel量化时,rknn_quant_params中的scale和zp数组的长度等于N。当使用Per-Layer量化时,rknn_quant_params中的scale和zp数组的长度为1。在rknn_matmul_run之前调用此接口设置所有矩阵的量化参数。如果不调用此接口,则默认量化方式为Per-Layer量化,scale=1.0,zero_point=0。
API | rknn_matmul_set_quant_params |
功能 | 设置矩阵的量化参数。 |
参数 | rknn_matmul_ctx ctx:矩阵乘法上下文。 |
rknn_quant_params* params: 矩阵的量化参数信息。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
rknn_quant_params params_a; memcpy(params_a.name, io_attr.A.name, RKNN_MAX_NAME_LEN); params_a.scale_len = 1; params_a.scale = (float *)malloc(params_a.scale_len * sizeof(float)); params_a.scale[0] = 0.2; params_a.zp_len = 1; params_a.zp = (int32_t *)malloc(params_a.zp_len * sizeof(int32_t)); params_a.zp[0] = 0; rknn_matmul_set_quant_params(ctx, ¶ms_a);
7.2.4.5 rknn_matmul_get_quant_params
rknn_matmul_get_quant_params用于rknn_matmul_type类型等于RKNN_INT8_MM_INT8_TO_INT32并且Per-Channel量化方式时,获取矩阵B所有通道scale归一化后的scale值,获取的scale值和A的原始scale值相乘可以得到C的scale值。可以用于在矩阵C没有真实scale时,近似计算得到C的scale。
API | rknn_matmul_get_quant_params |
功能 | 获取矩阵B的量化参数。 |
参数 | rknn_matmul_ctx ctx:矩阵乘法上下文。 |
rknn_quant_params* params: 矩阵B的量化参数信息。 | |
float* scale: 矩阵B的scale指针。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
float b_scale; rknn_matmul_get_quant_params(ctx, ¶ms_b, &b_scale);
7.2.4.6 rknn_matmul_create_dyn_shape(deprecated)
该接口已废弃,改用rknn_matmul_create_dynamic_shape接口。
7.2.4.7 rknn_matmul_create_dynamic_shape
rknn_matmul_create_dynamic_shape用于创建动态shape矩阵乘法上下文,该接口需要传入rknn_matmul_info结构体、shape数量以及对应的shape数组,shape数组会记录多个M、K和N值。在初始化成功后,会得到
rknn_matmul_io_attr的数组,数组中包含了所有的输入输出矩阵的shape、大小和数据类型等信息。目前支持设置多个不同的M,K和N。
API | rknn_matmul_create_dynamic_shape |
功能 | 初始化动态shape矩阵乘法的上下文。 |
参数 | rknn_matmul_ctx *ctx:矩阵乘法上下文指针。 |
rknn_matmul_info* info:矩阵乘法的规格信息结构体指针。其中M、K和N不需要设置。 | |
int shape_num:矩阵上下文支持的shape数量。 | |
rknn_matmul_shape dynamic_shapes[]:矩阵上下文支持的shape数组。 | |
rknn_matmul_io_attr io_attrs[]:矩阵乘法输入和输出tensor属性结构体数组。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
const int shape_num = 2; rknn_matmul_shape shapes[shape_num]; for (int i = 0; i < shape_num; i) { shapes[i].M = i 1; shapes[i].K = 64; shapes[i].N = 32; } rknn_matmul_io_attr io_attr[shape_num]; memset(io_attr, 0, sizeof(rknn_matmul_io_attr) * shape_num); int ret = rknn_matmul_create_dynamic_shape(&ctx, &info, shape_num, shapes, io_attr); if (ret < 0) { fprintf(stderr, " rknn_matmul_create_dynamic_shape fail! ret=%dn", ret); return -1; }
7.2.4.8 rknn_matmul_set_dynamic_shape
rknn_matmul_set_dynamic_shape用于指定矩阵乘法使用的某一个shape。在创建动态shape的矩阵乘法上下文后,选取其中一个rknn_matmul_shape结构体作为输入参数,调用此接口设置运算使用的shape。
API | rknn_matmul_set_dynamic_shape |
功能 | 设置矩阵乘法shape。 |
参数 | rknn_matmul_ctx ctx:矩阵乘法上下文。 |
rknn_matmul_shape* shape:指定矩阵乘法使用的shape。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
ret = rknn_matmul_set_dynamic_shape(ctx, &shapes[0]); if (ret != 0) { fprintf(stderr, "rknn_matmul_set_dynamic_shapes fail!n"); return -1; }
7.2.4.9 rknn_B_normal_layout_to_native_layout
rknn_B_normal_layout_to_native_layout用于将矩阵B的原始形状排列的数据(KxN)转换为高性能数据排列方式的数据。
API | rknn_B_normal_layout_to_native_layout |
功能 | 将矩阵B的数据排列从原始形状转换成高性能形状。 |
参数 | void* B_input:原始形状的矩阵B数据指针。 |
void* B_output:高性能形状的矩阵B数据指针。 | |
int K:矩阵B的行数。 | |
int N:矩阵B的列数。 | |
int subN:等于rknn_matmul_io_attr结构体中的B.dims[2]。 | |
int subK:等于rknn_matmul_io_attr结构体中的B.dims[3]。 | |
rknn_matmul_info* info:矩阵乘法的规格信息结构体指针。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
int32_t subN = io_attr.B.dims[2]; int32_t subK = io_attr.B.dims[3]; rknn_B_normal_layout_to_native_layout(B_Matrix, B->virt_addr, K, N, subN, subK, &info);
7.2.4.10 rknn_matmul_run
该函数用于运行矩阵乘法运算,并将结果保存在输出矩阵C中。在调用该函数前,输入矩阵A和B需要先准备好数据,并通过rknn_matmul_set_io_mem函数设置到输入缓冲区。输出矩阵C需要先通过rknn_matmul_set_io_mem函数设置到输出缓冲区,而输出矩阵的tensor属性则通过rknn_matmul_create函数获取。
API | rknn_matmul_run |
功能 | 运行矩阵乘法运算。 |
参数 | rknn_matmul_ctx ctx:矩阵乘法上下文。 |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
int ret = rknn_matmul_run(ctx);
7.2.4.11 rknn_matmul_destroy
该函数用于销毁矩阵乘法运算上下文,释放相关资源。在使用完rknn_matmul_create函数创建的矩阵乘法上下文指针后,需要调用该函数进行销毁。
API | rknn_matmul_destroy |
功能 | 销毁矩阵乘法运算上下文。 |
参数 | rknn_matmul_ctx ctx:矩阵乘法上下文。 |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
int ret = rknn_matmul_destroy(ctx);
7.2.5 自定义算子数据结构定义
7.2.5.1 rknn_gpu_op_context
rknn_gpu_op_context表示指定GPU运行的自定义算子的上下文信息。结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
cl_context | void* | OpenCL的cl_context对象,使用时请强制类型转换成cl_context。 |
cl_command_queue | void* | OpenCL的cl_command_queue对象,使用时请强制类型转换成cl_command_queue。 |
cl_kernel | void* | OpenCL的cl_kernel对象,使用时请强制类型转换成cl_kernel。 |
7.2.5.2 rknn_custom_op_context
rknn_custom_op_context表示自定义算子的上下文信息。结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
target | rknn_target_type | 执行自定义算子的后端设备:RKNN_TARGET_TYPE_CPU: CPURKNN_TARGET_TYPE_CPU: GPU |
internal_ctx | rknn_custom_op_interal_context | 算子内部的私有上下文。 |
gpu_ctx | rknn_gpu_op_context | 包含自定义算子的OpenCL上下文信息,当执行后端设备是GPU时,在回调函数中从该结构体获取OpenCL的cl_context等对象。 |
priv_data | void* | 留给开发者管理的数据指针。 |
7.2.5.3 rknn_custom_op_tensor
rknn_custom_op_tensor表示自定义算子的输入/输出的tensor信息。结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
attr | rknn_tensor_attr | 包含tensor的名称、形状、大小等信息。 |
mem | rknn_tensor_mem | 包含tensor的内存地址、fd、有效数据偏移等信息。 |
7.2.5.4 rknn_custom_op_attr
rknn_custom_op_attr表示自定义算子的参数或属性信息。结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
name | char[] | 自定义算子的参数名。 |
dtype | rknn_tensor_type | 每个元素的数据类型。 |
n_elems | uint32_t | 元素数量。 |
data | void* | 参数数据内存段的虚拟地址。 |
7.2.5.5 rknn_custom_op
rknn_custom_op表示自定义算子的注册信息。结构体的定义如下表所示:
成员变量 | 数据类型 | 含义 |
version | uint32_t | 自定义算子版本号。 |
target | rknn_target_type | 自定义算子执行后端类型。 |
op_type | char[] | 自定义算子类型。 |
cl_kernel_name | char[] | OpenCL的kernel函数名。 |
cl_kernel_source | char* | OpenCL的资源名称。当cl_source_size等于0时,表示文件绝对路径;当cl_source_size大于0时,表示kernel函数代码的字符串。 |
cl_source_size | uint64_t | 当cl_kernel_source是字符串,表示字符串长度;当cl_kernel_source是文件路径,则设置为0。 |
cl_build_options | char[] | OpenCL kernel的编译选项。 |
init | int (*)(rknn_custom_op_context* op_ctx,rknn_custom_op_tensor* inputs, uint32_t n_inputs, rknn_custom_op_tensor* outputs, uint32_t n_outputs); |
自定义算子初始化回调函数指针。在注册时,调用一次。不需要时可以设置为NULL。 |
prepare | int (*)(rknn_custom_op_context* op_ctx, rknn_custom_op_tensor* inputs, uint32_t n_inputs, rknn_custom_op_tensor* outputs, uint32_t n_outputs); |
预处理回调函数指针。在rknn_run时调用一次。不需要时可以设置为NULL。 |
compute | int (*)(rknn_custom_op_context* op_ctx, rknn_custom_op_tensor* inputs, uint32_t n_inputs, rknn_custom_op_tensor* outputs, uint32_t n_outputs); |
自定义算子功能的回调函数指针。在rknn_run时调用一次。不能设置为NULL。 |
compute_native | int (*)(rknn_custom_op_context* op_ctx, rknn_custom_op_tensor* inputs, uint32_t n_inputs, rknn_custom_op_tensor* outputs, uint32_t n_outputs); |
高性能计算的回调函数指针,它与compute回调函数区别是输入和输出的tensor的格式存在差异。暂不支持,目前设置为 NULL。 |
destroy | int (*)(rknn_custom_op_context* op_ctx); | 销毁资源的回调函数指针。在rknn_destroy时调用一次。 |
7.2.6 自定义算子API说明
7.2.6.1 rknn_register_custom_ops
在初始化上下文成功后,该函数用于在上下文中注册若干个自定义算子的信息,包括自定义算子类型、运行后端类型、OpenCL内核信息以及回调函数指针。注册成功后,在推理阶段,rknn_run接口会调用开发者实现的回调函数。
API | rknn_register_custom_ops |
功能 | 注册若干个自定义算子到上下文中。 |
参数 | rknn_context *context:rknn_context指针。函数调用之前,context必须已经初始化成功。 |
rknn_custom_op* op:自定义算子信息数组,数组每个元素是rknn_custom_op结构体对象。 | |
uint32_t custom_op_num:自定义算子信息数组长度。 | |
返回值 | int 错误码(见RKNN返回值错误码)。 |
示例代码如下:
// CPU operators rknn_custom_op user_op[2]; memset(user_op, 0, 2 * sizeof(rknn_custom_op)); strncpy(user_op[0].op_type, "cstSoftmax", RKNN_MAX_NAME_LEN - 1); user_op[0].version = 1; user_op[0].target = RKNN_TARGET_TYPE_CPU; user_op[0].init = custom_op_init_callback; user_op[0].compute = compute_custom_softmax_float32; user_op[0].destroy = custom_op_destroy_callback; strncpy(user_op[1].op_type, "ArgMax", RKNN_MAX_NAME_LEN - 1); user_op[1].version = 1; user_op[1].target = RKNN_TARGET_TYPE_CPU; user_op[1].init = custom_op_init_callback; user_op[1].compute = compute_custom_argmax_float32; user_op[1].destroy = custom_op_destroy_callback; ret = rknn_register_custom_ops(ctx, user_op, 2); if (ret < 0) { printf("rknn_register_custom_ops fail! ret = %dn", ret); return -1; }
7.2.6.2 rknn_custom_op_get_op_attr
该函数用于在自定义算子的回调函数中获取自定义算子的参数信息,例如Softmax算子的axis参数。它传入自定义算子参数的字段名称和一个rknn_custom_op_attr结构体指针,调用该接口后,参数值会存储rknn_custom_op_attr结构体中的data成员中,开发者根据返回的结构体内dtype成员将该指针强制转换成C语言中特定数据类型的数组首地址,再按照元素数量读取出完整参数值。
API | rknn_custom_op_get_op_attr |
功能 | 获取自定义算子的参数或属性。 |
参数 | rknn_custom_op_context* op_ctx:自定义算子上下文指针。 |
const char* attr_name:自定义算子参数的字段名称。 | |
rknn_custom_op_attr* op_attr:表示自定义算子参数值的结构体。 | |
返回值 | 无 |
示例代码如下:
rknn_custom_op_attr op_attr; rknn_custom_op_get_op_attr(op_ctx, "axis", &op_attr); if (op_attr.n_elems == 1 && op_attr.dtype == RKNN_TENSOR_INT64) { axis = ((int64_t*)op_attr.data)[0]; } …
7.2.7.RKNN返回值错误码
RKNN API函数的返回值错误码定义如下表所示:
错误码 | 错误详情 |
RKNN_SUCC(0) | 执行成功。 |
RKNN_ERR_FAIL(-1) | 执行出错。 |
RKNN_ERR_TIMEOUT(-2) | 执行超时。 |
RKNN_ERR_DEVICE_UNAVAILABLE(-3) | NPU设备不可用。 |
RKNN_ERR_MALLOC_FAIL(-4) | 内存分配失败。 |
RKNN_ERR_PARAM_INVALID(-5) | 传入参数错误。 |
RKNN_ERR_MODEL_INVALID(-6) | 传入的RKNN模型无效。 |
RKNN_ERR_CTX_INVALID(-7) | 传入的rknn_context无效。 |
RKNN_ERR_INPUT_INVALID(-8) | 传入的rknn_input对象无效。 |
RKNN_ERR_OUTPUT_INVALID(-9) | 传入的rknn_output对象无效。 |
RKNN_ERR_DEVICE_UNMATCH(-10) | 版本不匹配。 |
RKNN_ERR_INCOMPATILE_OPTIMIZATION_LEVEL_VERSION(-12) | RKNN模型设置了优化等级的选项,但是和当前驱动不兼容。 |
RKNN_ERR_TARGET_PLATFORM_UNMATCH(-13) | RKNN模型和当前平台不兼容。 |
审核编辑 黄宇
关注
42文章
4801浏览量
102530免责声明:本文为转载,非本网原创内容,不代表本网观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
如有疑问请发送邮件至:bangqikeconnect@gmail.com