3. RKNN使用说明
3.1 模型转换
RKNN-Toolkit2 提供了丰富的功能,包括模型转换、性能分析、部署调试等。本节将重点介绍 RKNN-Toolkit2 的模型转换功能。模型转换是 RKNN-Toolkit2 的核心功能之一,它允许用户将各种不同框架的深度学习模型转换为 RKNN 格式以在 RKNPU 上运行。用户可参考如下模型转换流程图以理解如何进行模型转换。
目前 RKNN-Toolkit2 支持多个主流深度学习框架的模型转换,包括:
Caffe(推荐版本为 1.0)
TensorFlow(推荐版本为 1.12.0~2.8.0)
TensorFlow Lite(推荐版本为 Schema version = 3)
ONNX(推荐版本为 1.7.0~1.10.0)
PyTorch(推荐版本为 1.6.0~1.13.1)
Darknet(推荐版本为 Commit ID = 810d7f7) 用户可以使用上述框架训练或获取预训练模型并将它们转换为 RKNN 格式,方便更高效地在 RKNPU 平台上部署和推理。
3.1.1 RKNN 初始化及对象释放
在这一阶段,用户需要先初始化RKNN对象,这是整个工作流程的第一步:
初始化RKNN对象:
使用
RKNN()
构造函数来初始化RKNN对象,用户可以传入参数verbose
和verbose_file
。verbose
参数决定是否在屏幕上显示详细日志信息。如果设置了
verbose_file
参数并且verbose
为True
,日志信息还将写入到指定的文件中。
示例代码:
rknn = RKNN(verbose=True, verbose_file='./mobilenet_build.log')
当完成所有的RKNN相关的操作后,用户需要释放资源,这是整个工作流程的最后一步:
使用
release()
接口释放RKNN对象占用的资源。
示例代码:
rknn.release()
3.1.2 模型转换配置
在模型转换之前,用户需要进行一些配置以确保模型转换的正确性和性能。配置参数通过 rknn.config()
接口设置,包括归一化参数、量化方法、目标平台等。下面列出了一些常用的配置参数:
mean_values
和std_values
:用于设置输入的均值和归一化值。这些值在量化过程中使用,且API推理阶段图片不需再做均值和归一化值,减少部署耗时。quant_img_RGB2BGR
:用于控制量化阶段加载的量化校正图像是否需要进行RGB到BGR的转换,默认值为False。该配置只在量化时生效,模型部署阶段不会生效,需要用户在处理输入数据时提前做好相应转换。注:quant_img_RGB2BGR = True
时输入数据的处理顺序为先做RGB2BGR转换(用户自行完成)再做归一化操作(运行时内部完成),详细注意事项请参考 10.3 章节。target_platform
:用于指定RKNN模型的目标平台,支持RK2118、RK3562、RK3566、RK3568、RK3576、RK3588、RV1103、RV1103B、RV1106、RV1106B。quantized_algorithm
:用于指定计算每一层的量化参数时采用的量化算法,可以选择normal、mmse或kl_divergence,默认算法为normal,详细说明参考 3.1.7、6.1 和 6.2 章节。quantized_method
:支持layer或channel,用于每层的权重是否共享参数,默认为channel,详细说明见 3.1.7、6.1 和 6.2 章节。optimization_level
:通过修改模型优化等级,可以关掉部分或全部模型转换过程中使用到的优化规则。该参数的默认值为 3,打开所有优化选项,值为2或1时关闭一部分可能会对部分模型精度产生影响的优化选项,值为0时关闭所有优化选项。quantized_dtype
:用于指定量化类型,目前支持线性非对称量化的INT8,默认为asymmetric_quantized-8
。custom_string
:添加自定义字符串信息到RKNN模型,例如模型自身迭代的版本信息等。在模型部署阶段可以通过rknn_query
接口设置RKNN_QUERY_CUSTOM_STRING
标志查询这些信息,方便部署时根据模型版本做特殊处理。默认值为None
。dynamic_input
:支持动态输入,根据用户指定的多组输入shape
,来模拟动态输入的功能,默认值为None
,详细说明见 5.4 章节。
更具体的 rknn.config()
接口配置请参考 <Rockchip_RKNPU_API_Reference_RKNN_Toolkit2_CN> 手册。
示例代码:
rknn.config(
mean_values=[[103.94, 116.78, 123.68]],
std_values=[[58.82, 58.82, 58.82]],
quant_img_RGB2BGR=False,
target_platform='rk3566'
)
3.1.3 模型加载接口介绍
用户需要使用相应的加载接口导入不同框架模型文件。RKNN-Toolkit2提供了不同的加载接口,包括Caffe、TensorFlow、TensorFlow Lite、ONNX、PyTorch等,下面是各种框架的模型加载接口的简要介绍:
Caffe模型加载接口:
使用
rknn.load_caffe()
接口加载Caffe模型。需要提供模型文件(
.prototxt
后缀)路径和权重文件(.caffemodel
后缀)路径。如果模型有多个输入层,可以指定输入层名称的顺序。
示例代码:
ret = rknn.load_caffe(model='./mobilenet_v2.prototxt', blobs='./mobilenet_v2.caffemodel')
TensorFlow 模型加载接口:
使用
rknn.load_tensorflow()
接口加载TensorFlow模型。需要提供TensorFlow模型文件(
.pb
后缀)路径、输入节点名、输入节点的形状以及输出节点名。
示例代码:
ret = rknn.load_tensorflow(tf_pb='./ssd_mobilenet_v1_coco_2017_11_17.pb', inputs=['Preprocessor/sub'], outputs=['concat', 'concat_1'], input_size_list=[[1, 300, 300, 3]])
TensorFlow Lite 模型加载接口:
使用
rknn.load_tflite()
接口加载TensorFlow Lite模型。需要提供TensorFlow Lite模型文件(
.tflite
后缀)路径。
示例代码:
ret = rknn.load_tflite(model='./mobilenet_v1.tflite')
ONNX 模型加载接口:
使用
rknn.load_onnx()
接口加载 ONNX 模型。需要提供 ONNX 模型文件(
.onnx
后缀)路径。
示例代码:
ret = rknn.load_onnx(model='./arcface.onnx')
DarkNet 模型加载接口:
使用
rknn.load_darknet()
接口加载 DarkNet 模型。需要提供 DarkNet 模型文件(
.cfg
后缀)路径和权重文件(.weights
后缀)路径。
示例代码:
ret = rknn.load_darknet(model='./yolov3-tiny.cfg', weight='./yolov3.weights')
PyTorch 模型加载接口:
使用
rknn.load_pytorch()
接口加载 PyTorch 模型。需要提供 PyTorch 模型文件(
.pt
后缀)路径,模型必须是torchscript
格式的。
示例代码:
ret = rknn.load_pytorch(model='./resnet18.pt', input_size_list=[[1, 3, 224, 224]])
用户可以根据不同框架的模型选择合适的接口进行加载,确保模型转换的正确性。
3.1.4 构建RKNN模型
用户加载原始模型后,下一步就是通过 rknn.build()
接口构建 RKNN 模型。构建模型时,用户可以选择是否进行量化,量化可以减小模型的大小和提高在 RKNPU 上的性能。rknn.build()
接口参数如下:
do_quantization
:该参数控制是否对模型进行量化,建议设置为True
。dataset
:该参数指定用于量化校准的数据集,数据集的格式是文本文件。
dataset.txt示例:
./imgs/ILSVRC2012_val_00000665.JPEG
./imgs/ILSVRC2012_val_00001123.JPEG
./imgs/ILSVRC2012_val_00001129.JPEG
./imgs/ILSVRC2012_val_00001284.JPEG
./imgs/ILSVRC2012_val_00003026.JPEG
./imgs/ILSVRC2012_val_00005276.JPEG
示例代码:
ret = rknn.build(do_quantization=True, dataset='./dataset.txt')
3.1.5 导出RKNN模型
用户通过 rknn.build()
接口构建了 RKNN 模型后,可以通过 rknn.export_rknn()
接口导出 RKNN 模型文件(.rknn
后缀),以便后续模型的部署。rknn.export_rknn()
接口参数如下:
export_path
:导出模型文件的路径。cpp_gen_cfg
:该参数决定是否在导出模型的同时生成 C++ 部署示例。
示例代码:
ret = rknn.export_rknn(export_path='./mobilenet_v1.rknn')
这些接口涵盖了 RKNN-Toolkit2 模型转换阶段,根据不同的需求和应用场景,用户可以选择不同的模型配置和量化算法进行自定义设置,方便后续进行部署。
3.1.6 模型转换工具RKNN
rknn_convert
是 RKNN-Toolkit2 提供的一套常用模型转换工具,通过封装上述 API 接口,用户只需编辑模型对应的 yml
配置文件,就可以通过指令转换模型。以下是如何使用 rknn_convert
工具的示例命令以及支持的指令参数:
python -m rknn.api.rknn_convert -t rk3588 -i /model_config.yml -o /output_path
通过使用上述命令和参数,用户可以将模型转换为 RKNN 格式,并将转换后的模型保存到指定的输出路径。支持的指令参数说明如下:
-i
: 模型配置文件(.yml
)路径。-o
: 转换后模型输出路径。-t
:target_platform
,目标平台可以选择rv1103
,rv1103b
,rv1106
,rv1106b
,rk2118
,rk3562
,rk3566
,rk3568
,rk3576
或rk3588
。-e
: (选填) 评估连板运行时model
的耗时和内存占用,若开启请输入-e
。注:一定要连接相应开发板并正确设置target_platform
,否则会报错,当有多设备时可通过-d
参数指定设备ID。-a
: (选填)评估生成的 rknn 模型精度,开启模拟器精度评估请输入-a "xx1.jpg xx2.jpg"
,若要开启连板精度评估请配合-d
参数使用。-v
: (选填)指定是否要在屏幕上打印详细日志信息,若开启打印模式请输入-v
。-d
: (选填)单个 adb 设备使用-d
,多 adb 设备使用-d device_id
,device_id
通过adb devices
查询。
下面是一个参考的 yml 配置文件( object_detection.yml
):
models:
# model output name
name: object_detection
# Original model framework
platform: onnx
# Model input file path
model_file_path: ./object_detection.onnx
# Describe information such as input and output shapes
subgraphs:
# model input tensor shape
input_size_list:
- 1,3,512,512
# input tensor name
inputs:
- data
# output tensor name
outputs:
- conv6-1
- conv6-2
- conv6-3
# quantification flag
quantize: true
# Quantify dataset file path (relative yml path)
dataset: ./dataset.txt
configs:
quantized_dtype: asymmetric_quantized-8
# rknn.config mean_values
mean_values: [127.5,127.5,127.5]
# rknn.config std_values
std_values: [128.0,128.0,128.0]
# rknn.config quant_img_RGB2BGR
quant_img_RGB2BGR: false
# rknn.config quantized_algorithm
quantized_algorithm: normal
这个配置文件包括了模型的名称、原始模型使用的框架、模型文件路径、输入输出信息、是否进行量化等详细信息。用户可以根据模型的特定需求编辑相应的配置文件。
模型转换配置详见下表: 表3-1 rknn_convert模型转换配置参数说明
参数名 | 填写内容 |
---|---|
-name | 模型输出名称 |
-platform | 原始模型使用的框架,支持tensorflow、tflite、caffe、onnx、pytorch、darknet |
-model_file_path | 原始模型文件路径,适用于单模型文件输入,例:tensorflow、tflite、onnx、pytorch |
-quantize | 是否开启量化 |
-dataset | 量化dataset文件路径(相对yml配置文件路径),若要开启accuracy_analysis此项必填 |
-prototxt_file_path | platform为caffe时,模型的prototxt文件 |
-caffemodel_file_path | platform为caffe时,模型的caffemodel文件 |
-darknet_cfg_path | platform为darknet时,模型的cfg文件 |
-darknet_weights_path | platform为darknet时,模型的weights文件 |
-subgraphs | 描述输入输出shape等信息,除特定框架外,一般情况下该参数及附带的子参数可不写,使用模型默认值 |
----input_size_list(子参数) | 输入tensor的shape |
----inputs(子参数) | 输入tensor的名称 |
----outputs(子参数) | 输出tensor的名称 |
-configs | 对应rknn.config()配置 |
----quantized_dtype(子参数) | 量化类型,RKNN_Toolkit2 可填写 [asymmetric_quantized-8],不输入用默认值 |
----mean_values(子参数) | 输入的均值归一数,模型为单输入RGB如[123.675,116.28,103.53],若为多输入如[[123,116,103],[255,255,255]] |
----------------------------- | -------------------------------------------------------------------------------------------- |
----std_values(子参数) | 输入的方差归一数,模型为单输入RGB如[58.395,58.295,58.391],若为多输入如[[127,127,127],[255,255,255]] |
----quant_img_RGB2BGR(子参数) | 用于控制量化时加载量化校正图像时是否需要先进行RGB到 BGR 的转换,默认值是False |
----quantized_algorithm(子参数) | 量化算法,可选['normal', 'kl_divergence', 'mmse'],默认为 normal |
----quantized_method(子参数) | 量化方式,RKNN_toolkit2可选['layer', 'channel'],默认为 channel |
----optimization_level(子参数) | 设置优化级别。默认为3,表示使用所有默认优化选项 |
----model_pruning(子参数) | 修剪模型以减小模型大小,默认为false,开启为true |
----quantize_weight(子参数) | 当quantize参数为false时,通过量化一些权重来减小rknn模型的大小。默认为false,开启为true |
----single_core_mode(子参数) | 是否仅生成单核模型,可以减小RKNN模型的大小和内存消耗。默认值为False。目前对RK3588/RK3576生效。默认值为 False |
3.1.7 RKNN-Toolkit2模型量化功能
RKNN-Toolkit2 提供两种量化方式和三种量化算法,用户可以通过 rknn.config()
中的 quantized_algorithm
和 quantized_method
参数来选择。以下是这些量化算法和特征量化方式的介绍:
三种量化算法:
Normal 量化算法:
通过计算模型中特征(feature)的浮点数最大值和最小值来确定量化范围的最大值和最小值。算法的特点是速度较快,但是遇到特征分布不均衡时效果较差,推荐量化数据量一般为20-100张左右。MMSE 量化算法:
通过最小化浮点数与量化反量化后浮点数的均方误差损失确定量化范围的最大值和最小值,能够一定程度的缓解大异常值带来的量化精度丢失问题。由于采用暴力迭代的方式,速度较慢,但通常会比normal具有更高的精度,推荐量化数据量一般为20-50张左右,用户也可以根据量化时间长短对量化数据量进行适当增减。KL-Divergence 量化算法:
将模型特征(feature)中浮点数和定点数抽象成两个分布,通过调整不同的阈值来更新浮点数和定点数的分布,并根据KL散度衡量两个分布的相似性来确定量化范围的最大值和最小值。所用时间会比normal多一些,但比mmse会少很多,在某些场景下(feature分布不均匀时)可以得到较好的改善效果,推荐量化数据量一般为20-100张左右。
两种量化方式:
Layer 量化方式:
Layer 量化方式将同一层网络的所有通道作为一个整体进行量化,所有通道共享相同的量化参数。Channel 量化方式:
Channel 量化方式将同一层网络的各个通道独立进行量化,每个通道有自己的量化参数。通常情况下,Channel 量化方式比Layer量化方式具有更高的精度。根据实际的模型量化效果和需求,用户可以选择合适的量化算法和特征量化方式,更详细的量化说明和原理请见第6章。
3.2 模型评估
3.2.1 模型推理
原始模型转换为 RKNN 模型后,可在模拟器或开发板上进行推理,对推理结果进行后处理检验其是否正确。若推理结果不正确,可以对量化模型进行精度分析和查看前后处理流程是否正确。模型推理阶段使用到的主要接口相关参数说明如下:
运行时初始化接口 rknn.init_runtime()
参数如下:
target
:目标硬件平台。默认值为None
,推理在模拟器上进行。如果要获取板端实际推理结果,则该参数需要填入相应的平台(RK3562 / RK3566 / RK3568 / RK3588 / RK3576 / RV1103 / RV1103B / RV1106 / RV1106B)。device_id
:设备编号。默认值为None
。若有设置target
则选择唯一一台设备进行推理。如果电脑连接多台设备连板推理时,需要指定填入相应的设备ID。若通过网络adb连接设备进行模型推理,则需要用户手动执行命令adb connect [IP]
连接网络设备后再填入设备编号,通常为[IP]
或[IP:Port]
的形式。使用adb devices
命令可以列出所有已连接设备。perf_debug
:进行性能评估时是否开启debug
模式。默认值为False
,调用rknn.val_perf()
接口可以获取模型运行的总时间。设为True
时,可以获取到每一层的运行时间,RV1103、RV1103B、RV1106和RV1106B平台不支持。eval_mem
:是否进入内存评估模式。默认为False
。设为True
进入内存评估模式后,可以调用rknn.eval_memory()
接口获取模型运行时的内存使用情况。
推理接口 rknn.inference()
参数如下:
inputs
:待推理的输入列表,格式为ndarray
。data_format
:输入数据的layout列表,只对4维的输入有效,格式为“NCHW
”或“NHWC
”。默认值为None
,表示所有输入的layout都为NHWC
。
示例代码
ret = rknn.init_runtime(target=platform, device_id='515e9b401c060c0b')
# Preprocess
image_src = cv2.imread(IMG_PATH)
img = preprocess(image_src)
# Inference
outputs = rknn.inference(inputs=[img])
# Postprocess
outputs = postprocess(outputs)
注意事项:
在RV1103 / RV1106平台上推理时若遇到“
ERKN: failed to allocate fd, ret: -1, errno: 12
”报错,可以在开发板终端运行RKLaunch-stop.sh
,关闭其他占用内存的应用后再连板推理。将rknn_server
更新到2.1.0版本也可以缓解该问题。在ARM64版本中
rknn.init_runtime()
无需添加device_id
。
3.2.2 模型精度分析
若量化模型推理结果有问题,可以使用 rknn.accuracy_analysis()
接口进行精度分析,查看每层算子的精度。
精度分析接口 rknn.accuracy_analysis()
参数如下:
inputs
:输入文件路径列表(格式包括jpg
、png
、bmp
和npy
)。output_dir
:分析结果保存目录,默认值为./snapshot
。target
:目标硬件平台。默认值为None
,使用仿真器进行精度分析。如果设置了target
(RK3562 / RK3566 / RK3568 / RK3588 / RK3576 / RV1103 / RV1103B / RV1106 / RV1106B ),则会获取运行时每一层的结果,并进行精度分析。device_id
:设备编号。默认为None
。若有设置target
则选择唯一一台设备进行精度分析。如果电脑连接多台设备时,需要指定相应的设备ID。若通过网络adb连接设备进行精度分析,则需要用户手动执行命令adb connect [IP]
连接网络设备后再填入设备编号,通常为[IP]
或[IP]:[Port]
的形式。
注意事项:
精度分析时可能会因为模型太大且开发板上存储容量不够导致运行失败,可在开发板终端上使用
df -h
命令来确认存储容量,若空间不足请删除无用文件保证output_dir
对应分区有足够的存储空间用于保存结果文件。通过
rknn.load_rknn()
加载 RKNN 模型后,因 RKNN 模型缺失原始模型信息,因此无法使用模型精度分析功能。在ARM64版本中
rknn.accuracy_analysis()
无需添加device_id
,并且该版本不支持adb设备连接到其他设备做精度分析。
示例代码
ret = rknn.accuracy_analysis(inputs=[img_path], target=platform, device_id='515e9b401c060c0b')
精度分析结果如下图
完整的精度分析包括模拟器精度分析结果和板端精度分析结果,同时模拟器和板端的精度分析结果都分为完整模型推理结果对比和逐层推理结果对比。详细说明如下:
simulator_error
:entire
: 从输入层到当前层simulator
结果与golden
结果对比的余弦距离和欧氏距离。single
: 当前层使用golden
输入时,simulator
结果与golden
结果对比的余弦距离和欧氏距离。
runtime_error
:entire
: 从输入层到当前层板端实际推理结果与golden
结果对比的余弦距离和欧氏距离。single
: 当前层使用golden
输入时,板端当前层实际推理结果与golden
结果对比的余弦距离和欧氏距离。
3.2.3 模型性能评估
接口 rknn.eval_perf()
会输出当前的硬件频率并获取模型的性能评估结果,fix_freq
参数表示是否需要尝试对硬件(包括CPU/NPU/DDR)定频,如果要对硬件定频则设置成 True
,否则设置成 False
,默认值为 True
。若初始化时 rknn.init_runtime()
的 perf_debug
参数设置为 True
,将输出每一层的耗时情况和总耗时情况,若为 False
则只输出总耗时情况。
注意:
平台RV1103 / RV1103B / RV1106 / RV1106B不支持
perf_debug
为True
模式,只能输出模型总耗时情况。
示例代码:
ret = rknn.init_runtime(target=platform, perf_debug=True)
perf_detail = rknn.eval_perf()
以 RK3588 为例,执行 eval_perf
后输出的硬件频率如下(不同固件支持的频率可能会有所差异),其中,由于 RK3588 CPU 是大小核架构,CPU 频率栏会输出多个频率值,单位为 KHz,而 NPU 和 DDR 频率单位都是 Hz。
CPU Current Frequency List:
- 1800000
- 2256000
- 2304000
NPU Current Frequency List:
- 1000000000
DDR Current Frequency List:
- 2112000000
性能评估结果如下:
性能评估结果各字段说明如下表:
表3-2 性能评估结果各字段说明
字段 |
详细说明 |
---|---|
ID |
算子编号 |
OpType |
算子类型 |
DataType |
输入的数据类型 |
Target |
算子运行的硬件(CPU/NPU/GPU) |
InputShape |
输入形状 |
OutputShape |
输出形状 |
DDRCycles |
DDR读写时钟周期数 |
NPUCycles |
NPU计算时钟周期数 |
MaxCycles |
DDR Cycles和NPU Cycles的最大值 |
Time(us) |
算子计算耗时(us) |
MacUsage(%) |
MAC使用率 |
WorkLoad(0/1/2) |
0/1/2核负载情况(仅RK3588平台) |
WorkLoad(0/1) |
0/1核负载情况(仅RK3576平台) |
TaskNumber |
NPU任务数 |
RW(KB) |
读写的数据总量(KB) |
FullName |
算子全名 |
Total Operator Elapsed Per Frame Time(us) |
模型推理的单帧总耗时 |
Total Memory Read/Write Per Frame Size(KB) |
模型推理的单帧总带宽消耗 |
CallNumber |
单帧内该算子运行次数 |
CPUTime(us) |
单帧内该算子在CPU上的总耗时 |
GPUTime(us) |
单帧内该算子在GPU上的总耗时 |
NPUTime(us) |
单帧内该算子在NPU上的总耗时 |
TotalTime(us) |
单帧内该算子的总耗时 |
TimeRatio(%) |
单帧内该算子的总耗时与单帧总耗时的比值 |
3.2.4 模型内存评估
接口 rknn.eval_memory()
可以获取模型的内存消耗评估结果。初始化时 rknn.init_runtime()
的 eval_mem
参数设置为 True
,将输出各部分内存消耗情况。
示例代码:
ret = rknn.init_runtime(target=platform, eval_mem=True)
memory_detail = rknn.eval_memory()
内存评估结果如下:
========================================
Memory Profile Info Dump
========================================
NPU model memory detail(bytes):
Weight Memory: 8.67 MiB
Internal Tensor Memory: 7.42 MiB
Other Memory: 3.03 MiB
Total Memory: 19.12 MiB
INFO: When evaluating memory usage, we need consider
the size of model, current model size is: 11.86 MiB
========================================
内存评估结果各字段说明如下:
内存评估结果各字段说明
字段 |
详细说明 |
---|---|
Total Weight Memory |
模型中权重的内存占用(MB) |
Total Internal Tensor Memory |
模型中间 tensor 内存占用(MB) |
Other Memory |
其他内存占用(例如寄存器配置、输入输出 tensor)(MB) |
Total Memory |
模型的内存总占用(MB) |
3.3 板端C API推理
本章节介绍通用C API接口的调用流程。零拷贝API调用流程请参考章节5.2。
RKNN通用API接口调用流程:
调用
rknn_init()
接口初始化模型;调用
rknn_query()
接口查询模型的输入输出属性;对输入进行前处理;
调用
rknn_inputs_set()
接口设置输入数据;调用
rknn_run()
接口进行模型推理;调用
rknn_outputs_get()
接口获取推理结果数据;对输出进行后处理;
调用
rknn_outputs_release()
接口释放输出数据内存;调用
rknn_destroy()
接口销毁RKNN;
通用API调用流程如图3-4所示。
通用API调用流程示例代码:
// Init RKNN model
ret = rknn_init(&ctx, model, model_len, 0, NULL);
// Get Model Input Output Number
rknn_input_output_num io_num;
ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num));
// Get Model Input Info
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));
}
// Get Model Output Info
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));
}
rknn_input inputs[io_num.n_input];
rknn_output outputs[io_num.n_output];
memset(inputs, 0, sizeof(inputs));
memset(outputs, 0, sizeof(outputs));
// Pre-process
// Read Image
image_buffer_t src_image;
memset(&src_image, 0, sizeof(image_buffer_t));
ret = read_image(image_path, &src_image);
// Set Input Data
inputs[0].index = 0;
inputs[0].type = RKNN_TENSOR_UINT8;
inputs[0].fmt = RKNN_TENSOR_NHWC;
inputs[0].size = src_image.size;
inputs[0].buf = src_image.virt_addr;
ret = rknn_inputs_set(rknn_ctx, io_num.n_input, inputs);
// Run
ret = rknn_run(rknn_ctx, nullptr);
// Get Output Data
ret = rknn_outputs_get(rknn_ctx, io_num.n_output, outputs, NULL);
// Post-process
post_process(outputs, results);
// Release RKNN Output
rknn_outputs_release(rknn_ctx, io_num.n_output, outputs);
if (rknn_ctx != 0)
{
rknn_destroy(rknn_ctx);
}
3.4 板端Python API推理
RKNN-Toolkit Lite2 为用户提供板端模型推理的 Python 接口,方便用户使用 Python 语言进行 AI 应用开发。
注:在使用 RKNN-Toolkit Lite2 开发 AI 应用前,需要通过 RKNN-Toolkit2 将各深度学习框架导出的模型转成 RKNN 模型。模型转换详细方法请参考 3.1 章节。
3.4.1 系统依赖说明
使用 RKNN-Toolkit Lite2 需满足以下运行环境要求:
表3-4 RKNN-Toolkit Lite2运行环境
操作系统版本 | Debian 10 / 11(aarch64) |
---|---|
Python版本 | 3.7 / 3.8 / 3.9 / 3.10 / 3.11 / 3.12 |
Python依赖库 | numpy 、ruamel.yaml 、psutil |
3.4.2 工具安装
请通过 pip3 install
命令安装 RKNN-Toolkit Lite2。安装步骤如下:
如果系统中没有安装
python3/pip3
等程序,请先通过apt-get
方式安装,参考命令如下:
sudo apt-get update
sudo apt-get install -y python3 python3-dev python3-pip gcc
注:安装部分依赖模块时,需要编译源码,此时将用到 python3-dev
和 gcc
,因此该步骤将这两个包也一起安装,避免后面安装依赖模块时编译失败。
安装依赖模块:
opencv-python
和numpy
,参考命令如下:
sudo apt-get install -y python3-opencv
sudo apt-get install -y python3-numpy
注:
RKNN-Toolkit Lite2本身并不依赖
opencv-python
,但是在示例中需要使用该模块对图像进行处理。在Debian10固件上通过
pip3
安装numpy
可能失败,建议用上述方法安装。
安装RKNN-Toolkit Lite2
各平台的安装包都放在SDK的rknn-toolkit-lite2/packages
文件夹下。进入packages
文件夹,执行以下命令安装RKNN-Toolkit Lite2:
# Python 3.7
pip3 install rknn_toolkit_lite2-x.y.z-cp37-cp37m-linux_aarch64.whl
# Python 3.8
pip3 install rknn_toolkit_lite2-x.y.z-cp38-cp38-linux_aarch64.whl
# Python 3.9
pip3 install rknn_toolkit_lite2-x.y.z-cp39-cp39-linux_aarch64.whl
# Python 3.10
pip3 install rknn_toolkit_lite2-x.y.z-cp310-cp310-linux_aarch64.whl
# Python 3.11
pip3 install rknn_toolkit_lite2-x.y.z-cp311-cp311-linux_aarch64.whl
# Python 3.12
pip3 install rknn_toolkit_lite2-x.y.z-cp312-cp312-linux_aarch64.whl
3.4.3 基本使用流程
使用 RKNN-Toolkit Lite2 部署 RKNN 模型的基本流程如下图所示:
注:
在调用
inference
接口进行推理之前,需要获取输入数据,并做相应的预处理,然后根据输入信息设置inference
接口中的各项参数。在调用
inference
接口后,通常需要对推理结果进行相应的处理,以完成应用程序相关功能。
3.4.4 运行参考示例
在 SDK/rknn-toolkit-lite2/examples
目录,提供了一个基于 RKNN-Toolkit Lite2 开发的图像分类应用,该应用调用 RKNN-Toolkit Lite2 的接口加载 Resnet18 RKNN 模型,对输入图片进行预测,打印 top5
分类结果。
运行该示例的步骤:
准备一块安装有 RKNN-Toolkit Lite2 的开发板;
将
SDK/rknn-toolkit-lite2/examples
目录推到开发板上;在开发板上进入
examples/resnet18
目录,执行如下命令运行该示例:
python test.py
参考运行结果如下所示:
model: resnet18
-----TOP 5-----
[812] score:0.999680 class:"space shuttle"
[404] score:0.000249 class:"airliner"
[657] score:0.000013 class:"missile"
[466] score:0.000009 class:"bullet train, bullet"
[895] score:0.000008 class:"warplane, military plane"
3.4.5 RKNN-Toolkit Lite2 API详细说明
本章节将详细说明 RKNN-Toolkit Lite2 提供的所有 API 的用法。
3.4.5.1 RKNNLite初始化及对象释放
在使用 RKNN-Toolkit Lite2 时,需要先调用 RKNNLite()
方法初始化一个 RKNNLite
对象,并在用完后调用该对象的 rknn_lite.release()
方法将资源释放掉。
初始化 RKNNLite
对象时,可以设置 verbose
和 verbose_file
参数,以打印详细的日志信息。其中 verbose
参数指定是否要在屏幕上打印详细日志信息;如果设置了 verbose_file
参数,且 verbose
参数值为 True
,日志信息还将写到这个参数指定的文件中。
举例如下:
# 将详细的日志信息输出到屏幕,并写到inference.log文件中
rknn_lite = RKNNLite(verbose=True, verbose_file='./inference.log')
# 只在屏幕打印详细的日志信息
rknn_lite = RKNNLite(verbose=True)
...
rknn_lite.release()
3.4.5.2 加载RKNN模型
表3-5 load_rknn接口详细说明
API | load_rknn |
---|---|
描述 | 加载RKNN模型 |
参数 | path: RKNN模型路径 |
返回值 | 0:加载成功;-1:加载失败。 |
举例如下:
# 从当前目录加载resnet_18.rknn模型
ret = rknn_lite.load_rknn('./resnet_18.rknn')
3.4.5.3 初始化运行时环境
在模型推理之前,必须先初始化运行时环境。
表3-6 init_runtime接口详细说明
API | init_runtime |
---|---|
描述 | 初始化运行时环境。 |
参数 | core_mask :NPU工作核心配置模式。可选值如下:RKNNLite.NPU_CORE_AUTO :自动调度模式,模型将以单核模式自动运行在当前空闲的NPU核上。RKNNLite.NPU_CORE_0 :模型运行在NPU Core0上。RKNNLite.NPU_CORE_1 :模型运行在NPU Core1上。RKNNLite.NPU_CORE_2 :模型运行在NPU Core2上。RKNNLite.NPU_CORE_0_1 :模型同时运行在NPU Core0和NPU Core1上。RKNNLite.NPU_CORE_0_1_2 :模型同时运行在NPU Core0,NPU Core1和NPU Core2上。RKNNLite.NPU_CORE_ALL :模型同时运行在所有NPU核上。默认值为 NPU_CORE_AUTO ,即默认使用的是自动调度模式。注:该参数仅对RK3576 / RK3588有效。 |
返回值 | 0:初始化运行时环境成功;-1:初始化运行时环境失败。 |
举例如下:
# 初始化运行时环境
ret = rknn_lite.init_runtime(core_mask=RKNNLite.NPU_CORE_AUTO)
if ret != 0:
print("Init runtime environment failed")
exit(ret)
3.4.5.4 模型推理
表3-7 inference接口详细说明
API | inference |
---|---|
描述 | 对指定输入进行推理,返回推理结果。 |
参数 | inputs :输入数据,如OpenCV读取的图片(如果输入是四维的,需要手动扩成4维 )。类型是 list ,列表成员是 ndarray 。data_format :数据排列方式,类型是 list ,对于每个输入可选值"nhwc" 、"nchw" ,只对四维输入有效。默认值为None 。如果不填写该参数,对于4维输入,数据buffer 应按照NHWC 排列,对于非4维输入,数据buffer 应按照模型输入要求的格式排列。如果要填写该参数,对于非4维输入,数据buffer 应按照模型输入要求的格式排列(不管填"nhwc" 还是"nchw" );对于4维输入,数据buffer 应按照该参数设置的格式排列;对于多输入模型,填写该参数时要包括所有输入。 |
返回值 | results :推理结果,类型是list ,列表成员是ndarray 。 |
以分类模型为例,模型推理代码参考如下:
# Get input data
img = cv2.imread('./space_shuttle_224.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = np.expand_dims(img, 0)
# Inference
outputs = rknn_lite.inference(inputs=[img])
# Show the classification results
show_top5(outputs)
3.4.5.5 查询SDK版本
表3-8 get_sdk_version接口详细说明
API | get_sdk_version |
---|---|
描述 | 获取Runtime,驱动和RKNN模型版本信息。注:使用该接口前必须完成模型加载和初始化运行环境。 |
参数 | 无 |
返回值 | sdk_version :runtime,驱动版本信息。类型为字符串。 |
举例如下:
# 获取SDK版本信息
sdk_version = rknn_lite.get_sdk_version()
返回的SDK信息参考如下:
===============================================
RKNN VERSION:
API: 1.5.2 (71720f3fc@2023-08-21T09:29:52)
DRV: 0.7.2
===============================================
3.4.5.6 查询模型可运行平台
表3-9 list_support_target_platform接口详细说明
API | list_support_target_platform |
---|---|
描述 | 查询给定RKNN模型可运行的芯片平台。 |
参数 | rknn_model :RKNN模型路径。如果不指定模型路径,则按类别打印RKNN-Toolkit Lite2当前支持的芯片平台。 |
返回值 | support_target_platform :返回模型可运行的芯片平台。如果RKNN模型路径为空或不存在,返回None 。 |
参考代码如下:
rknn_lite.list_support_target_platform(rknn_model='mobilenet_v1.rknn')
参考结果如下:
********************************************
Target platforms filled in RKNN model: ['rk3566']
Target platforms supported by this RKNN model: ['RK3566', 'RK3568']
********************************************
3.5 矩阵乘法接口
Matmul API是Runtime提供的一套单独的C API,用于在NPU上运行矩阵乘法运算,RK2118 / RV1103系列 / RV1106系列暂不支持。矩阵乘法是线性代数中的一种重要操作,该操作定义如下:C=AB。其中,A是一个M×K矩阵,B是一个K×N矩阵,C是一个M×N矩阵。
3.5.1 主要用途和特点
Matmul API 多用于深度学习中的参数计算任务,例如,在当前被广泛应用的 Transformer 模型结构的主要模块(自注意力机制和前馈神经网络层)中大量使用了矩阵乘法进行计算,因此矩阵乘法的运算效率对 Transformer 模型的整体性能至关重要。Matmul API 具有以下特点:
高效:底层使用 RKNPU 实现,具有高性能低功耗的优点。
灵活:无需加载 RKNN 模型,支持 int4、int8 和 float16 三种边缘端计算常用的输入数据类型,提供单独的内存分配接口或使用外部内存的机制,用户可管理和复用矩阵的输入输出内存。
3.5.2 Matmul API使用流程
 Matmul API基础调用流程
使用 Matmul API 通常包括以下步骤:
创建上下文:设置
rknn_matmul_info
结构体,包含 M、K、N、输入和输出矩阵的数据类型、输入和输出矩阵使用的数据排列方式等信息,然后,调用rknn_matmul_create
接口初始化上下文。在初始化后,获取以rknn_matmul_io_attr
结构体指针,它包含了输入和输出矩阵 tensor 信息。指定运行 NPU(仅多核 NPU 的芯片平台有效):调用
rknn_matmul_set_core_mask
,设置掩码来指定某一个 NPU 核做运算,多核 NPU 的芯片平台不调用该接口的默认行为是自动选择空闲的核心。设置矩阵 A、B 和 C 的量化参数:调用
rknn_matmul_set_quant_params
传入包含对应矩阵量化参数值的rknn_quant_params
结构体,不调用该接口的默认行为是各个矩阵的scale=1.0, zero_point=0
。创建输入和输出内存:调用
rknn_create_mem
接口,根据输入和输出矩阵 Tensor 信息中的大小创建内存。填充输入数据:根据形状和数据类型填充输入矩阵 A 和 B 的数据,并且以量化后的值设置量化参数。
设置输入和输出内存:调用
rknn_matmul_set_io_mem
将填充好数据的输入矩阵记录到上下文中,输出内存也同样记录到上下文中。除了记录内存地址外,该接口还会按照所设置的 layout 对数据做重新排列,必须在填充或更新输入数据后调用,与零拷贝 API 中的rknn_set_io_mem
接口行为有区别。执行矩阵乘法运算:设置好输入和输出内存后,调用
rknn_matmul_run
执行矩阵乘法运算。处理输出:执行矩阵乘法运算后,从输出内存中按照所设置的 layout 来读取结果。
销毁资源:执行结束后,调用
rknn_destroy_mem
和rknn_matmul_destroy
分别销毁内存和上下文资源。
3.5.3 矩阵乘法高级用法
在创建上下文时,要求用户设置 rknn_matmul_info
结构体,rknn_matmul_info
表示用于执行矩阵乘法的规格信息,它包含了矩阵乘法的规模、输入和输出矩阵的数据类型和数据排列。其中,B_layout
和 AC_layout
用于设置高性能的数据排列方式。具体的结构体定义如下表所示:
表3-10 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是float32类型;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是float32类型;RKNN_FLOAT16_MM_INT8_TO_FLOAT16 :表示矩阵A是float16类型,矩阵B是int8类型,矩阵C是float16类型;RKNN_FLOAT16_MM_INT4_TO_FLOAT32 :表示矩阵A是float16类型,矩阵B是int4类型,矩阵C是float32类型;RKNN_FLOAT16_MM_INT4_TO_FLOAT16 :表示矩阵A是float16类型,矩阵B是int4类型,矩阵C是float16类型;RKNN_INT8_MM_INT4_TO_FLOAT32 :表示矩阵A和B是int8类型,矩阵C是float32类型;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按照高性能形状排列;详细的排列说明可以参考 3.5.4 |
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[] | 预留字段 |
其中,矩阵A的原始形状是 MxK,矩阵B的原始形状是 KxN,矩阵C的原始形状是 MxN。 |
3.5.3.1 指定量化参数的矩阵乘法
如果矩阵乘积的高比特数据需要根据量化参数量化成低比特数据,例如,矩阵A和B是 int8 类型,矩阵C是 int8 类型,需要设置矩阵的量化参数,量化参数由 scale
和 zero_point
组成,用 rknn_quant_params
结构体表示,通过 rknn_matmul_set_quant_params
接口设置矩阵A和C的量化参数,量化参数设置成功后,Per-Channel 量化方式下的矩阵B的量化参数可以通过 rknn_matmul_get_quant_params
接口读取。
rknn_quant_params
数据结构定义如下表所示:
表3-11 rknn_quant_params结构体定义
成员变量 | 数据类型 | 含义 |
---|---|---|
name | char[] | 矩阵的名称 |
scale | float* | 量化参数scale数组的首地址 |
scale_len | int32_t | 量化参数scale数组的长度 |
zp | int32_t* | 量化参数zero_point数组的首地址 |
zp_len | int32_t | 量化参数zero_point数组的长度 |
Matmul 的量化方式包含 Per-Layer 方式、Per-Channel 方式以及 Per-Group 方式三种,量化参数差别如下:
Per-Layer 方式:scale 和 zp 数组的长度为1。
Per-Channel 方式:scale 和 zp 数组的长度为 N。
Per-Group 方式:scale 和 zp 数组的长度为 N*K/group_size,每个组的元素数量由 group_size 指定,同一个组内的元素共享相同的量化参数,量化参数数组存储方式上,先存储 K/group_size 个组的量化参数,再存储 N 个量化参数。
每种量化方式存在对称量化和非对称量化两种方式,对称量化表示 zp 全为0,非对称量化表示 zp 不全为0。
3.5.3.2 动态shape输入的矩阵乘法
 动态 shape 输入的 Matmul API 用法的流程如上图所示。使用动态 shape Matmul API 与基础的 Matmul API 的流程差异如下:
创建上下文:配置 shape 数量和所需的 shape,设置
rknn_matmul_info
结构体,包含输入和输出矩阵的数据类型、输入和输出矩阵使用的数据排列方式等信息。注意,动态 shape Matmul 接口参数rknn_matmul_info
中的 M、K、N 不需要设置。然后,调用rknn_matmul_create_dynamic_shape
接口初始化上下文。在初始化后,获取rknn_matmul_io_attr
结构体数组,它包含了所有 shape 配置下的输入和输出矩阵 Tensor 信息。创建输入和输出内存:首先从
rknn_matmul_io_attr
结构体数组获取最大的 Tensor 大小,调用rknn_create_mem
接口,使用最大的输入和输出矩阵 Tensor 大小创建内存。设置输入 shape:通过调用
rknn_matmul_set_dynamic_shape
接口,传入rknn_matmul_shape
指针,设置当前推理过程使用的 shape。设置输入和输出内存:调用
rknn_matmul_set_io_mem
将填充好数据的输入矩阵记录到上下文中,输出内存也同样记录到上下文中。传入的rknn_tensor_mem*
参数是前面创建的最大 size 的输入和输出内存,rknn_matmul_tensor_attr*
参数是当前 shape 对应的矩阵属性信息。
3.5.4 高性能的数据排列方式
由于 NPU 是专用的硬件架构,读取 MxK 和 KxN 这种原始形状的数据不是最高效的,同样的,写入 MxN 形状的 C 矩阵也不是最高效的,用户使用特殊的数据排列方式可以实现更高的性能。AC_layout
参数控制矩阵 A 和 C 是否使用高性能数据排列,B_layout
参数控制矩阵 B 是否使用高性能数据排列。
假设矩阵 A 的原始形状是 MxK,矩阵 B 的原始形状是 KxN,矩阵 C 的原始形状是 MxN,要求的数据排列方式如下:
若
AC_layout=RKNN_MM_LAYOUT_NORM
且B_layout=RKNN_MM_LAYOUT_NORM
,矩阵 A 的形状为[M,K]
,矩阵 B 的形状为[K,N]
,矩阵 C 的形状为[M,N]
。若
AC_layout=RKNN_MM_LAYOUT_NATIVE
且B_layout=RKNN_MM_LAYOUT_NATIVE
,不同芯片平台和数据类型下矩阵 A、B 和 C 的高性能数据排列如下表所示(表中除法结果都是上取整,多出部分用0补齐):B_layout=RKNN_MM_LAYOUT_NORM
和B_layout=RKNN_MM_LAYOUT_TP_NORM
的区别在于,前者矩阵 B 的形状为[K, N]
,后者矩阵 B 的形状为[N, K]
,后者的执行效率更优。
表3-12 各个芯片平台矩阵A、B和C的高性能形状
RK3562 |
RK3566/RK3568 |
RK3576 |
RK3588 |
|
---|---|---|---|---|
A形状(int4) |
暂不支持 |
暂不支持 |
[K/32, M, 32] |
[K/32, M, 32] |
B形状(int4) |
暂不支持 |
暂不支持 |
[N/64, K/32, 64, 32]* |
[N/64, K/32, 64, 32] |
C形状(int16) |
暂不支持 |
暂不支持 |
[N/8, M, 8] |
[N/8, M, 8] |
A形状(int8) |
[K/16, M, 16] |
[K/8, M, 8] |
[K/16, M, 16] |
[K/16, M, 16] |
B形状(int8) |
[N/16, K/32, 16, 32] |
[N/16, K/32, 16, 32] |
[N/32, K/32, 32, 32]* |
[N/32, K/32, 32, 32] |
C形状(int32) |
[N/4, M, 4] |
[N/4, M, 4] |
[N/4, M, 4] |
[N/4, M, 4] |
A形状(float16) |
[K/8, M, 8] |
[K/4, M, 4] |
[K/8, M, 8] |
[K/8, M, 8] |
B形状(float16) |
[N/8, K/32, 8, 32] |
[N/8, K/16, 8, 16] |
[N/16, K/32, 16, 32] |
[N/16, K/32, 16, 32] |
C形状(float32) |
[N/4, M, 4] |
[N/4, M, 4] |
[N/4, M, 4] |
[N/4, M, 4] |
上表中的数据排列方式是 A 和 B“同型”(即 A 和 B 相同数据类型)情况下的高性能形状。RK3576 支持“异型”输入,即允许 A 和 B 有不同的数据类型,在“异型”输入情况下,B 的高性能数据排列与 C 类型对应的“同型”情况下 B 的形状相同。例如,A 是 int4 类型,B 是 float16 类型,C 是 float32 类型,则 B 的高性能数据数据排列是 [N/16, K/32, 16, 32]
。rknn_B_normal_layout_to_native_layout
接口提供了对 B 的原始形状转换成高性能排列的功能。
以 RK3566/RK3568 平台,int8 数据类型为例,矩阵 A 从 [M,K]
变换到 [K/8,M,8]
的元素对应关系如下图所示:
其中,矩阵 A 第(i,j)元素对应的数据为 D[i,j],左图是 shape=[M,K]
的矩阵,右图每个小矩阵 shape=[M,8]
,从上到下一共 K/8 个。
矩阵 A 或 C 从原始形状转换成高性能形状的示例代码如下:
template <typename Ti, typename To>
void norm_layout_to_perf_layout(Ti *src, To *dst, int32_t M, int32_t K, int32_t subK)
{
int outer_size = (int)std::ceil(K * 1.0f / subK);
for (int i = 0; i < outer_size; i++) {
for (int m = 0; m < M; m++) {
for (int j = 0; j < subK; j++) {
int ki = i * subK + j;
if (ki >= K) {
dst[i * M * subK + m * subK + j] = 0;
} else {
dst[i * M * subK + m * subK + j] = src[m * K + ki];
}
}
}
}
}
以 RK3566/RK3568 平台,int8 数据类型的矩阵 B 从 [K,N]
变换到 [N/16,K/32,16,32]
的元素对应关系如下图所示:
其中,矩阵B第(i,j)元素对应的数据为D[i,j],左图是shape=[K,N]
的矩阵,右图每个小立方体shape=[K/32,16,32]
,从上到下一共N/16个。
矩阵B从原始形状转换成高性能形状的示例代码如下:
template <typename Ti, typename To>
void norm_layout_to_native_layout(Ti *src, To *dst, int32_t K, int32_t N, int32_t subN, int32_t subK)
{
int N_remain = (int)std::ceil(N * 1.0f / subN);
int K_remain = (int)std::ceil(K * 1.0f / subK);
for (int i = 0; i < N_remain; i++) {
for (int j = 0; j < K_remain; j++) {
for (int n = 0; n < subN; n++) {
int ni = i * subN + n;
for (int k = 0; k < subK; k++) {
int ki = j * subK + k;
if (ki < K && ni < N) {
dst[((i * K_remain + j) * subN + n) * subK + k] = src[ki * N + ni];
} else {
dst[((i * K_remain + j) * subN + n) * subK + k] = 0;
}
}
}
}
}
}
Note
Matmul API完整示例代码见 https://github.com/airockchip/rknn-toolkit2/tree/master/rknpu2/examples/rknn_matmul_api_demo。
3.5.4.1 矩阵规格限制
Matmul API 是基于 NPU 的硬件架构实现,受硬件规格限制。在参数上有如下几点限制:
AC_layout 和 B_layout 可独立设置。所有硬件平台上的 AC_layout 和 B_layout 都支持
RKNN_MM_LAYOUT_NORM
或者RKNN_MM_LAYOUT_TP_NORM
,除了 RK3566/RK3568 平台外,其他平台都支持RKNN_MM_LAYOUT_TP_NORM
。Matmul 接口支持多种输入和输出的数据位宽,目前各个平台支持的矩阵数据类型组合如下表:
表3-13 Matmul接口支持的矩阵A、B和C的数据类型组合
序号 | RK356X | RK3576 | RK3588 |
---|---|---|---|
1 | RKNN_INT8_MM_INT8_TO_INT32 | RKNN_FLOAT16_MM_FLOAT16_TO_FLOAT16 | RKNN_FLOAT16_MM_FLOAT16_TO_FLOAT16 |
2 | RKNN_FLOAT16_MM_FLOAT16_TO_FLOAT32 | RKNN_FLOAT16_MM_FLOAT16_TO_FLOAT32 | RKNN_FLOAT16_MM_FLOAT16_TO_FLOAT32 |
3 | RKNN_INT8_MM_INT8_TO_INT32 | RKNN_INT8_MM_INT8_TO_INT32 | |
4 | RKNN_FLOAT16_MM_INT8_TO_FLOAT32 | RKNN_INT8_MM_INT8_TO_FLOAT32 | |
5 | RKNN_FLOAT16_MM_INT4_TO_FLOAT16 | RKNN_INT8_MM_INT8_TO_INT8 | |
6 | RKNN_FLOAT16_MM_INT4_TO_FLOAT32 | RKNN_INT4_MM_INT4_TO_INT16 | |
7 | RKNN_INT8_MM_INT4_TO_INT32 |
其中,RK356X 表示 RK3562/RK3566/RK3568 等平台,float16 浮点格式遵循 IEEE-754 标准,具体格式请参考 [IEEE-754 half] (https://en.wikipedia.org/wiki/Half-precision_floating-point_format)。
Matmul中K和N大小限制如下表:
表3-14 各个芯片平台K和N的大小限制
RK3562 | RK3566/RK3568 | RK3576 | RK3588 | |
---|---|---|---|---|
K大小限制(int4) | 暂不支持 | 暂不支持 | * | 32对齐 |
K大小限制(int8) | K <= 10240且K mod 32 = 0 | K <= 10240且K mod 32 = 0 | 32对齐 | 32对齐 |
K大小限制(float16) | K <= 10240且K mod 32 = 0 | K <= 10240且K mod 32 = 0 | 32对齐 | 32对齐 |
N大小限制(int4) | 暂不支持 | 暂不支持 | * | N <= 8192且N mod 64 = 0 |
N大小限制(int8) | N <= 4096且N mod 16 = 0 | N <= 4096且N mod 16 = 0 | N <= 4096且N mod 32 = 0 | N <= 4096且N mod 32 = 0 |
N大小限制(float16) | N <= 4096且N mod 16 = 0 | N <= 4096且N mod 16 = 0 | N <= 4096且N mod 32 = 0 | N <= 4096且N mod 32 = 0 |
Note
*表示大小和对齐限制由同平台下A的数据类型对应的B限制相同。例如,RKNN_FLOAT16_MM_INT4_TO_FLOAT32
组合类型B的大小和对齐限制与RKNN_FLOAT16_MM_FLOAT16_TO_FLOAT32
组合相同。
对于 RK3588:
当 K > 8192,B 会被分成 T 段,
int T = std::ceil(K / 8192);
例如: normal layout -> native layout
,
K = 20488, N = 4096, T = 3, 数据会被分成3段,
subN = rknn_matmul_io_attr.B.dims[2];
subK = rknn_matmul_io_attr.B.dims[3];
K、N 转变过程如下:
(8196, 4096) (4096 / subN, 8196 / subK, subN, subK)
(K, N) = (20488, 4096)->(8196, 4096)->(4096 / subN, 8196 / subK, subN, subK)
normal layout (4096, 4096) (4096 / subN, 4096 / subK, subN, subK)
T normal layout T native layout
推荐使用 rknn_B_normal_layout_to_native_layout
接口进行直接数据转换。
对于 RK3576:
当 K > 4096,B 会被分成 T 段,
int T = std::ceil(K / 4096);
例如: normal layout -> native layout
,
K = 10240, N = 2048, T = 3, 数据会被分成3段,
subN = rknn_matmul_io_attr.B.dims[2];
subK = rknn_matmul_io_attr.B.dims[3];
K、N转变过程如下:
(4096, 2048) (2048 / subN, 4096 / subK, subN, subK)
(K, N) = (10240, 2048)->(4096, 2048)->(2048 / subN, 4096 / subK, subN, subK)
normal layout (2048, 2048) (2048 / subN, 2048 / subK, subN, subK)
T normal layout T native layout
推荐使用 rknn_B_normal_layout_to_native_layout
接口进行直接数据转换。