K230 nncase开发指南
1. 概述
1.1 什么是nncase
nncase是一个为 AI 加速器设计的神经网络编译器, 目前支持的 target有cpu/K210/K510/K230等.
nncase提供的功能
- 支持多输入多输出网络,支持多分支结构
- 静态内存分配,不需要堆内存
- 算子合并和优化
- 支持 float 和uint8/int8量化推理
- 支持训练后量化,使用浮点模型和量化校准集
- 平坦模型,支持零拷贝加载
nncase支持的神经网络模型格式
- tflite
- onnx
1.2 nncase架构
nncase软件栈包括compiler和runtime两部分。
Compiler: 用于在PC上编译神经网络模型,最终生成kmodel文件。主要包括importer, IR, Evaluator, Quantize, Transform优化, Tiling, Partition, Schedule, Codegen等模块。
- Importer: 将其它神经网络框架的模型导入到nncase中
- IR: 中间表示, 分为importer导入的Neutral IR(设备无关)和Neutral IR经lowering转换生成的Target IR(设备相关)
- Evaluator: Evaluator提供IR的解释执行能力,常被用于Constant Folding/PTQ Calibration等场景
- Transform: 用于IR转换和图的遍历优化等
- Quantize: 训练后量化, 对要量化的tensor加入量化标记, 根据输入的校正集, 调用 Evaluator进行解释执行, 收集tensor的数据范围, 插入量化/反量化结点, 最后优化消除不必要的量化/反量化结点等
- Tiling: 受限于NPU较低的存储器容量,需要将大块计算进行拆分. 另外, 计算 存在大量数据复用时选择Tiling参数会对时延和带宽产生影响
- Partition: 将图按ModuleType进行切分, 切分后的每个子图会对应RuntimeModule, 不同类型的RuntimeModule对应不同的Device(cpu/K230)
- Schedule: 根据优化后图中的数据依赖关系生成计算顺序并分配Buffer
- Codegen: 对每个子图分别调用ModuleType对应的codegen,生成RuntimeModule
Runtime: 集成于用户App, 提供加载kmodel/设置输入数据/KPU执行/获取输出数据等功能.
1.3 开发环境
1.3.1 操作系统
支持的操作系统包括Ubuntu 18.04/Ubuntu 20.04
1.3.2 软件环境
序号 | 软件 | 版本号 |
---|---|---|
1 | python | 3.6/3.7/3.8/3.9/3.10 |
2 | pip | >=20.3 |
3 | numpy | 1.19.5 |
4 | onnx | 1.9.0 |
5 | onnx-simplifier | 0.3.6 |
6 | Onnxoptimizer | 0.2.6 |
7 | Onnxruntime | 1.8.0 |
8 | dotnet-runtime | 7.0 |
1.3.3 硬件环境
K230 evb
2. 编译模型APIs(Python)
nncase提供了Python APIs, 用于在PC上编译神经网络模型
2.1 支持的算子
2.1.1 tflite算子
Operator | Is Supported |
---|---|
ABS | Yes |
ADD | Yes |
ARG_MAX | Yes |
ARG_MIN | Yes |
AVERAGE_POOL_2D | Yes |
BATCH_MATMUL | Yes |
CAST | Yes |
CEIL | Yes |
CONCATENATION | Yes |
CONV_2D | Yes |
COS | Yes |
CUSTOM | Yes |
DEPTHWISE_CONV_2D | Yes |
DIV | Yes |
EQUAL | Yes |
EXP | Yes |
EXPAND_DIMS | Yes |
FLOOR | Yes |
FLOOR_DIV | Yes |
FLOOR_MOD | Yes |
FULLY_CONNECTED | Yes |
GREATER | Yes |
GREATER_EQUAL | Yes |
L2_NORMALIZATION | Yes |
LEAKY_RELU | Yes |
LESS | Yes |
LESS_EQUAL | Yes |
LOG | Yes |
LOGISTIC | Yes |
MAX_POOL_2D | Yes |
MAXIMUM | Yes |
MEAN | Yes |
MINIMUM | Yes |
MUL | Yes |
NEG | Yes |
NOT_EQUAL | Yes |
PAD | Yes |
PADV2 | Yes |
MIRROR_PAD | Yes |
PACK | Yes |
POW | Yes |
REDUCE_MAX | Yes |
REDUCE_MIN | Yes |
REDUCE_PROD | Yes |
RELU | Yes |
PRELU | Yes |
RELU6 | Yes |
RESHAPE | Yes |
RESIZE_BILINEAR | Yes |
RESIZE_NEAREST_NEIGHBOR | Yes |
ROUND | Yes |
RSQRT | Yes |
SHAPE | Yes |
SIN | Yes |
SLICE | Yes |
SOFTMAX | Yes |
SPACE_TO_BATCH_ND | Yes |
SQUEEZE | Yes |
BATCH_TO_SPACE_ND | Yes |
STRIDED_SLICE | Yes |
SQRT | Yes |
SQUARE | Yes |
SUB | Yes |
SUM | Yes |
TANH | Yes |
TILE | Yes |
TRANSPOSE | Yes |
TRANSPOSE_CONV | Yes |
QUANTIZE | Yes |
FAKE_QUANT | Yes |
DEQUANTIZE | Yes |
GATHER | Yes |
GATHER_ND | Yes |
ONE_HOT | Yes |
SQUARED_DIFFERENCE | Yes |
LOG_SOFTMAX | Yes |
SPLIT | Yes |
HARD_SWISH | Yes |
2.1.2 onnx算子
Operator | Is Supported |
---|---|
Abs | Yes |
Acos | Yes |
Acosh | Yes |
And | Yes |
ArgMax | Yes |
ArgMin | Yes |
Asin | Yes |
Asinh | Yes |
Add | Yes |
AveragePool | Yes |
BatchNormalization | Yes |
Cast | Yes |
Ceil | Yes |
Celu | Yes |
Clip | Yes |
Compress | Yes |
Concat | Yes |
Constant | Yes |
ConstantOfShape | Yes |
Conv | Yes |
ConvTranspose | Yes |
Cos | Yes |
Cosh | Yes |
CumSum | Yes |
DepthToSpace | Yes |
DequantizeLinear | Yes |
Div | Yes |
Dropout | Yes |
Elu | Yes |
Exp | Yes |
Expand | Yes |
Equal | Yes |
Erf | Yes |
Flatten | Yes |
Floor | Yes |
Gather | Yes |
GatherElements | Yes |
GatherND | Yes |
Gemm | Yes |
GlobalAveragePool | Yes |
GlobalMaxPool | Yes |
Greater | Yes |
GreaterOrEqual | Yes |
GRU | Yes |
Hardmax | Yes |
HardSigmoid | Yes |
HardSwish | Yes |
Identity | Yes |
InstanceNormalization | Yes |
LayerNormalization | Yes |
LpNormalization | Yes |
LeakyRelu | Yes |
Less | Yes |
LessOrEqual | Yes |
Log | Yes |
LogSoftmax | Yes |
LRN | Yes |
LSTM | Yes |
MatMul | Yes |
MaxPool | Yes |
Max | Yes |
Min | Yes |
Mul | Yes |
Neg | Yes |
Not | Yes |
OneHot | Yes |
Pad | Yes |
Pow | Yes |
PRelu | Yes |
QuantizeLinear | Yes |
RandomNormal | Yes |
RandomNormalLike | Yes |
RandomUniform | Yes |
RandomUniformLike | Yes |
ReduceL1 | Yes |
ReduceL2 | Yes |
ReduceLogSum | Yes |
ReduceLogSumExp | Yes |
ReduceMax | Yes |
ReduceMean | Yes |
ReduceMin | Yes |
ReduceProd | Yes |
ReduceSum | Yes |
ReduceSumSquare | Yes |
Relu | Yes |
Reshape | Yes |
Resize | Yes |
ReverseSequence | Yes |
RoiAlign | Yes |
Round | Yes |
Rsqrt | Yes |
Selu | Yes |
Shape | Yes |
Sign | Yes |
Sin | Yes |
Sinh | Yes |
Sigmoid | Yes |
Size | Yes |
Slice | Yes |
Softmax | Yes |
Softplus | Yes |
Softsign | Yes |
SpaceToDepth | Yes |
Split | Yes |
Sqrt | Yes |
Squeeze | Yes |
Sub | Yes |
Sum | Yes |
Tanh | Yes |
Tile | Yes |
TopK | Yes |
Transpose | Yes |
Trilu | Yes |
ThresholdedRelu | Yes |
Upsample | Yes |
Unsqueeze | Yes |
Where | Yes |
2.2 APIs
目前编译模型APIs支持tflite/onnx等格式的深度学习模型。
2.2.1 CompileOptions
【描述】
CompileOptions类, 用于配置nncase编译选项,各属性说明如下
属性名称 | 类型 | 是否必须 | 描述 |
---|---|---|---|
target | string | 是 | 指定编译目标, 如’cpu’, ‘k230’ |
dump_ir | bool | 否 | 指定是否dump IR, 默认为False |
dump_asm | bool | 否 | 指定是否dump asm汇编文件, 默认为False |
dump_dir | string | 否 | 前面指定dump_ir等开关后, 这里指定dump的目录, 默认为”” |
input_file | string | 否 | onnx模型超过2GB时,用于指定参数文件路径,默认为”” |
preprocess | bool | 否 | 是否开启前处理,默认为False。以下参数仅在 preprocess=True 时生效 |
input_type | string | 否 | 开启前处理时指定输入数据类型,默认为”float”。当 preprocess 为 True 时,必须指定为”uint8”或者”float32” |
input_shape | list[int] | 否 | 开启前处理时指定输入数据的shape,默认为[]。当 preprocess 为 True 时,必须指定 |
input_range | list[float] | 否 | 开启前处理时指定输入数据反量化后的浮点数范围,默认为[ ]。当 preprocess 为 True 且 input_type 为 uint8 时,必须指定 |
input_layout | string | 否 | 指定输入数据的layout,默认为”” |
swapRB | bool | 否 | 是否在 channel 维度反转数据,默认为False |
mean | list[float] | 否 | 前处理标准化参数均值,默认为[0,0,0] |
std | list[float] | 否 | 前处理标准化参数方差,默认为[1,1,1] |
letterbox_value | float | 否 | 指定前处理letterbox的填充值,默认为0 |
output_layout | string | 否 | 指定输出数据的layout, 默认为”” |
shape_bucket_enable | bool | 是 | 是否开启ShapeBucket功能,默认为False。在 dump_ir=True 时生效 |
shape_bucket_range_info | Dict[str, [int, int]] | 是 | 每个输入shape维度信息中的变量的范围,最小值必须大于等于1 |
shape_bucket_segments_count | int | 是 | 输入变量的范围划分为几段 |
shape_bucket_fix_var_map | Dict[str, int] | 否 | 固定shape维度信息中的变量为特定的值 |
2.2.1.1 前处理流程说明
目前暂不支持自定义前处理顺序,可以根据以下流程示意图,选择所需要的前处理参数进行配置。
参数说明:
-
input_range
为输入数据类型为定点时,反量化后的浮点数范围。a. 输入数据类型为uint8,range为[0,255],
input_range
为[0,255],则反量化的作用只是进行类型转化,将uint8的数据转化为float32,mean
和std
参数仍然按照[0,255]的数据进行指定。b. 输入数据类型为uint8,range为[0,255],
input_range
为[0,1],则反量化会将定点数转化为浮点数[0,1],mean
和std
参数需要按照0~1的数据进行指定。 -
input_shape
为输入数据的shape,layout为input_layout
,现在支持字符串("NHWC"
、"NCHW"
)和index两种方式作为input_layout
,并且支持非4D的数据处理。 当按照字符串形式配置input_layout
时,表示输入数据的layout;当按照index形式配置input_layout
时,表示输入数据会按照当前配置的input_layout
进行数据转置,即input_layout
为Transpose
的perm
参数。
output_layout
同理,如下图所示。
2.2.1.2 动态shape参数说明
ShapeBucket是针对动态shape的一种解决方案,会根据输入长度的范围以及指定的段的数量来对动态shape进行优化。该功能默认为false,需要打开对应的option才能生效,除了指定对应的字段信息,其他流程与编译静态模型没有区别。
- onnx
在模型的shape中会有些维度为变量名字,这里以一个onnx模型的输入为例
tokens: int64[batch_size, tgt_seq_len] step: float32[seq_len, batch_size]
shape的维度信息中存在seq_len,tgt_seq_len,batch_size这三个变量。 首先是batch_size,虽然是变量的但实际应用的时候固定为3,因此在fix_var_map中添加batch_size = 3,在运行的时候会将这个维度固定为3。 seq_len,tgt_seq_len两个是实际会发生改变的,因此需要配置这两个变量的实际范围,也就是range_info的信息。segments_count是实际分段的数量,会根据范围等分为几份,对应的编译时间也会相应增加几倍。
以下为对应的编译参数示例:
compile_options = nncase.CompileOptions()
compile_options.shape_bucket_enable = True
compile_options.shape_bucket_range_info = {"seq_len": [1, 100], "tgt_seq_len": [1, 100]}
compile_options.shape_bucket_segments_count = 2
compile_options.shape_bucket_fix_var_map = {"batch_size": 3}
- tflite
tflite的模型与onnx不同,shape上暂未标注维度的名称,目前只支持输入中具有一个维度是动态的,并且名称统一配置为-1,配置方式如下:
compile_options = nncase.CompileOptions()
compile_options.shape_bucket_enable = True
compile_options.shape_bucket_range_info = {"-1":[1, 100]}
compile_options.shape_bucket_segments_count = 2
compile_options.shape_bucket_fix_var_map = {"batch_size" : 3}
配置完这些选项后整个编译的流程和静态shape一致。
2.2.1.3 参数配置示例
实例化CompileOptions,配置各属性的值。
compile_options = nncase.CompileOptions()
compile_options.target = "cpu" #"k230"
compile_options.dump_ir = True # if False, will not dump the compile-time result.
compile_options.dump_asm = True
compile_options.dump_dir = "dump_path"
compile_options.input_file = ""
# preprocess args
compile_options.preprocess = False
if compile_options.preprocess:
compile_options.input_type = "uint8" # "uint8" "float32"
compile_options.input_shape = [1,224,320,3]
compile_options.input_range = [0,1]
compile_options.input_layout = "NHWC" # "NHWC" ”NCHW“
compile_options.swapRB = False
compile_options.mean = [0,0,0]
compile_options.std = [1,1,1]
compile_options.letterbox_value = 0
compile_options.output_layout = "NHWC" # "NHWC" "NCHW"
# Dynamic shape args
compile_options.shape_bucket_enable = False
if compile_options.shape_bucket_enable:
compile_options.shape_bucket_range_info = {"seq_len": [1, 100], "tgt_seq_len": [1, 100]}
compile_options.shape_bucket_segments_count = 2
compile_options.shape_bucket_fix_var_map = {"batch_size": 3}
2.2.2 ImportOptions
【描述】
ImportOptions类, 用于配置nncase导入选项
【定义】
class ImportOptions:
def __init__(self) -> None:
pass
【示例】
实例化ImportOptions, 配置各属性的值
#import_options
import_options = nncase.ImportOptions()
2.2.3 PTQTensorOptions
【描述】
PTQTensorOptions类, 用于配置nncase PTQ选项
名称 | 类型 | 描述 |
---|---|---|
calibrate_method | string | 否 |
samples_count | int | 否 |
finetune_weights_method | string | 否 |
quant_type | string | 否 |
w_quant_type | string | 否 |
quant_scheme | string | 否 |
quant_scheme_strict_mode | bool | 否 |
export_quant_scheme | bool | 否 |
export_weight_range_by_channel | bool | 否 |
- 混合量化参数说明
- quant_scheme:导入量化参数配置文件的路径
- quant_scheme_strict_mode:是否严格按照quant_scheme执行量化
- export_quant_scheme:是否导出量化参数配置文件
- export_weight_range_by_channel:是否导出
bychannel
形式的weights量化参数,为了保证量化效果,该参数建议设置为True
具体使用流程见 MixQuant说明
【示例】
# ptq_options
ptq_options = nncase.PTQTensorOptions()
ptq_options.samples_count = 6
ptq_options.finetune_weights_method = "NoFineTuneWeights"
ptq_options.quant_type = "uint8"
ptq_options.w_quant_type = "uint8"
ptq_options.set_tensor_data(generate_data(input_shape, ptq_options.samples_count, args.dataset))
ptq_options.quant_scheme = ""
ptq_options.quant_scheme_strict_mode = False
ptq_options.export_quant_scheme = True
ptq_options.export_weight_range_by_channel = True
compiler.use_ptq(ptq_options)
2.2.4 set_tensor_data
【描述】
设置tensor数据
【定义】
def set_tensor_data(self, data: List[List[np.ndarray]]) -> None:
reshape_data = list(map(list, zip(*data)))
self.cali_data = [RuntimeTensor.from_numpy(
d) for d in itertools.chain.from_iterable(reshape_data)]
【参数】
名称 | 类型 | 描述 |
---|---|---|
data | List[List[np.ndarray] | 读取的校准数据 |
【返回值】
无
【示例】
# ptq_options
ptq_options = nncase.PTQTensorOptions()
ptq_options.samples_count = 6
ptq_options.set_tensor_data(generate_data(input_shape, ptq_options.samples_count, args.dataset))
compiler.use_ptq(ptq_options)
2.2.5 Compiler
【描述】
Compiler类, 用于编译神经网络模型
【定义】
class Compiler:
_target: _nncase.Target
_session: _nncase.CompileSession
_compiler: _nncase.Compiler
_compile_options: _nncase.CompileOptions
_quantize_options: _nncase.QuantizeOptions
_module: IRModule
2.2.6 import_tflite
【描述】
导入tflite模型
【定义】
def import_tflite(self, model_content: bytes, options: ImportOptions) -> None:
self._compile_options.input_format = "tflite"
self._import_module(model_content)
【参数】
名称 | 类型 | 描述 |
---|---|---|
model_content | byte[] | 读取的模型内容 |
import_options | ImportOptions | 导入选项 |
【返回值】
无
【示例】
model_content = read_model_file(model)
compiler.import_tflite(model_content, import_options)
2.2.7 import_onnx
【描述】
导入onnx模型
【定义】
def import_onnx(self, model_content: bytes, options: ImportOptions) -> None:
self._compile_options.input_format = "onnx"
self._import_module(model_content)
【参数】
名称 | 类型 | 描述 |
---|---|---|
model_content | byte[] | 读取的模型内容 |
import_options | ImportOptions | 导入选项 |
【返回值】
无
【示例】
model_content = read_model_file(model)
compiler.import_onnx(model_content, import_options)
2.2.8 use_ptq
【描述】
设置PTQ配置选项.
- K230默认必须使用量化。
【定义】
use_ptq(ptq_options)
【参数】
名称 | 类型 | 描述 |
---|---|---|
ptq_options | PTQTensorOptions | PTQ配置选项 |
【返回值】
无
【示例】
compiler.use_ptq(ptq_options)
2.2.9 compile
【描述】
编译神经网络模型
【定义】
compile()
【参数】
无
【返回值】
无
【示例】
compiler.compile()
2.2.10 gencode_tobytes
【描述】
生成kmodel字节流
【定义】
gencode_tobytes()
【参数】
无
【返回值】
bytes[]
【示例】
kmodel = compiler.gencode_tobytes()
with open(os.path.join(infer_dir, 'test.kmodel'), 'wb') as f:
f.write(kmodel)
2.3 示例
下面示例中使用到的模型和python编译脚本
- 原始模型文件位于/path/to/k230_sdk/src/big/nncase/examples/models目录
- python编译脚本位于/path/to/k230_sdk/src/big/nncase/examples/scripts目录
2.3.1 编译tflite模型
mbv2_tflite.py脚本如下
import os
import argparse
import numpy as np
from PIL import Image
import nncase
def read_model_file(model_file):
with open(model_file, 'rb') as f:
model_content = f.read()
return model_content
def generate_data(shape, batch, calib_dir):
img_paths = [os.path.join(calib_dir, p) for p in os.listdir(calib_dir)]
data = []
for i in range(batch):
assert i < len(img_paths), "calibration images not enough."
img_data = Image.open(img_paths[i]).convert('RGB')
img_data = img_data.resize((shape[3], shape[2]), Image.BILINEAR)
img_data = np.asarray(img_data, dtype=np.uint8)
img_data = np.transpose(img_data, (2, 0, 1))
data.append([img_data[np.newaxis, ...]])
return data
def main():
parser = argparse.ArgumentParser(prog="nncase")
parser.add_argument("--target", type=str, help='target to run')
parser.add_argument("--model", type=str, help='model file')
parser.add_argument("--dataset", type=str, help='calibration_dataset')
args = parser.parse_args()
input_shape = [1, 3, 224, 224]
dump_dir = 'tmp/mbv2_tflite'
# compile_options
compile_options = nncase.CompileOptions()
compile_options.target = args.target
compile_options.preprocess = True
compile_options.swapRB = False
compile_options.input_shape = input_shape
compile_options.input_type = 'uint8'
compile_options.input_range = [0, 255]
compile_options.mean = [127.5, 127.5, 127.5]
compile_options.std = [127.5, 127.5, 127.5]
compile_options.input_layout = 'NCHW'
compile_options.dump_ir = True
compile_options.dump_asm = True
compile_options.dump_dir = dump_dir
# compiler
compiler = nncase.Compiler(compile_options)
# import
model_content = read_model_file(args.model)
import_options = nncase.ImportOptions()
compiler.import_tflite(model_content, import_options)
# ptq_options
ptq_options = nncase.PTQTensorOptions()
ptq_options.samples_count = 6
ptq_options.set_tensor_data(generate_data(input_shape, ptq_options.samples_count, args.dataset))
compiler.use_ptq(ptq_options)
# compile
compiler.compile()
# kmodel
kmodel = compiler.gencode_tobytes()
with open(os.path.join(dump_dir, 'test.kmodel'), 'wb') as f:
f.write(kmodel)
if __name__ == '__main__':
main()
执行如下命令即可编译mobilenetv2的tflite模型, target为k230
root@c285a41a7243:/mnt/# cd src/big/nncase/examples
root@c285a41a7243:/mnt/src/big/nncase/examples# python3 ./scripts/mbv2_tflite.py --target k230 --model models/mbv2.tflite --dataset calibration_dataset
2.3.2 编译onnx模型
针对onnx模型, 建议先使用ONNX Simplifier进行简化, 然后再使用nncase编译.
yolov5s_onnx.py 脚本如下
import os
import argparse
import numpy as np
from PIL import Image
import onnxsim
import onnx
import nncase
def parse_model_input_output(model_file):
onnx_model = onnx.load(model_file)
input_all = [node.name for node in onnx_model.graph.input]
input_initializer = [node.name for node in onnx_model.graph.initializer]
input_names = list(set(input_all) - set(input_initializer))
input_tensors = [
node for node in onnx_model.graph.input if node.name in input_names]
# input
inputs = []
for _, e in enumerate(input_tensors):
onnx_type = e.type.tensor_type
input_dict = {}
input_dict['name'] = e.name
input_dict['dtype'] = onnx.mapping.TENSOR_TYPE_TO_NP_TYPE[onnx_type.elem_type]
input_dict['shape'] = [(i.dim_value if i.dim_value != 0 else d) for i, d in zip(
onnx_type.shape.dim, [1, 3, 224, 224])]
inputs.append(input_dict)
return onnx_model, inputs
def onnx_simplify(model_file, dump_dir):
onnx_model, inputs = parse_model_input_output(model_file)
onnx_model = onnx.shape_inference.infer_shapes(onnx_model)
input_shapes = {}
for input in inputs:
input_shapes[input['name']] = input['shape']
onnx_model, check = onnxsim.simplify(onnx_model, input_shapes=input_shapes)
assert check, "Simplified ONNX model could not be validated"
model_file = os.path.join(dump_dir, 'simplified.onnx')
onnx.save_model(onnx_model, model_file)
return model_file
def read_model_file(model_file):
with open(model_file, 'rb') as f:
model_content = f.read()
return model_content
def generate_data_ramdom(shape, batch):
data = []
for i in range(batch):
data.append([np.random.randint(0, 256, shape).astype(np.uint8)])
return data
def generate_data(shape, batch, calib_dir):
img_paths = [os.path.join(calib_dir, p) for p in os.listdir(calib_dir)]
data = []
for i in range(batch):
assert i < len(img_paths), "calibration images not enough."
img_data = Image.open(img_paths[i]).convert('RGB')
img_data = img_data.resize((shape[3], shape[2]), Image.BILINEAR)
img_data = np.asarray(img_data, dtype=np.uint8)
img_data = np.transpose(img_data, (2, 0, 1))
data.append([img_data[np.newaxis, ...]])
return data
def main():
parser = argparse.ArgumentParser(prog="nncase")
parser.add_argument("--target", type=str, help='target to run')
parser.add_argument("--model", type=str, help='model file')
parser.add_argument("--dataset", type=str, help='calibration_dataset')
args = parser.parse_args()
input_shape = [1, 3, 320, 320]
dump_dir = 'tmp/yolov5s_onnx'
if not os.path.exists(dump_dir):
os.makedirs(dump_dir)
# onnx simplify
model_file = onnx_simplify(args.model, dump_dir)
# compile_options
compile_options = nncase.CompileOptions()
compile_options.target = args.target
compile_options.preprocess = True
compile_options.swapRB = False
compile_options.input_shape = input_shape
compile_options.input_type = 'uint8'
compile_options.input_range = [0, 255]
compile_options.mean = [0, 0, 0]
compile_options.std = [255, 255, 255]
compile_options.input_layout = 'NCHW'
compile_options.output_layout = 'NCHW'
compile_options.dump_ir = True
compile_options.dump_asm = True
compile_options.dump_dir = dump_dir
# compiler
compiler = nncase.Compiler(compile_options)
# import
model_content = read_model_file(model_file)
import_options = nncase.ImportOptions()
compiler.import_onnx(model_content, import_options)
# ptq_options
ptq_options = nncase.PTQTensorOptions()
ptq_options.samples_count = 6
ptq_options.set_tensor_data(generate_data(input_shape, ptq_options.samples_count, args.dataset))
compiler.use_ptq(ptq_options)
# compile
compiler.compile()
# kmodel
kmodel = compiler.gencode_tobytes()
with open(os.path.join(dump_dir, 'test.kmodel'), 'wb') as f:
f.write(kmodel)
if __name__ == '__main__':
main()
执行如下命令即可编译onnx模型, target为k230
root@c285a41a7243:/mnt/# cd src/big/nncase/examples
root@c285a41a7243: /mnt/src/big/nncase/examples # python3 ./scripts/yolov5s_onnx.py --target k230 --model models/yolov5s.onnx --dataset calibration_dataset
3. 模拟器APIs(Python)
除了编译模型APIs, nncase还提供了推理模型的APIs, 在PC上可推理编译模型生成的kmodel, 用来验证nncase推理结果和相应深度学习框架的runtime的结果是否一致等.
3.1 APIs
3.1.1 MemoryRange
【描述】
MemoryRange类, 用于表示内存范围
【定义】
py::class_<memory_range>(m, "MemoryRange")
.def_readwrite("location", &memory_range::memory_location)
.def_property(
"dtype", [](const memory_range &range) { return to_dtype(range.datatype); },
[](memory_range &range, py::object dtype) { range.datatype = from_dtype(py::dtype::from_args(dtype)); })
.def_readwrite("start", &memory_range::start)
.def_readwrite("size", &memory_range::size);
【属性】
名称 | 类型 | 描述 |
---|---|---|
location | int | 内存位置, 0表示input, 1表示output, 2表示rdata, 3表示data, 4表示shared_data |
dtype | python数据类型 | 数据类型 |
start | int | 内存起始地址 |
Size | int | 内存大小 |
【示例】
mr = nncase.MemoryRange()
3.1.2 RuntimeTensor
【描述】
RuntimeTensor类, 用于表示运行时tensor
【定义】
py::class_<runtime_tensor>(m, "RuntimeTensor")
.def_static("from_numpy", [](py::array arr) {
auto src_buffer = arr.request();
auto datatype = from_dtype(arr.dtype());
auto tensor = host_runtime_tensor::create(
datatype,
to_rt_shape(src_buffer.shape),
to_rt_strides(src_buffer.itemsize, src_buffer.strides),
gsl::make_span(reinterpret_cast<gsl::byte *>(src_buffer.ptr), src_buffer.size * src_buffer.itemsize),
[=](gsl::byte *) { arr.dec_ref(); })
.unwrap_or_throw();
arr.inc_ref();
return tensor;
})
.def("copy_to", [](runtime_tensor &from, runtime_tensor &to) {
from.copy_to(to).unwrap_or_throw();
})
.def("to_numpy", [](runtime_tensor &tensor) {
auto host = tensor.as_host().unwrap_or_throw();
auto src_map = std::move(hrt::map(host, hrt::map_read).unwrap_or_throw());
auto src_buffer = src_map.buffer();
return py::array(
to_dtype(tensor.datatype()),
tensor.shape(),
to_py_strides(runtime::get_bytes(tensor.datatype()), tensor.strides()),
src_buffer.data());
})
.def_property_readonly("dtype", [](runtime_tensor &tensor) {
return to_dtype(tensor.datatype());
})
.def_property_readonly("shape", [](runtime_tensor &tensor) {
return to_py_shape(tensor.shape());
});
【属性】
名称 | 类型 | 描述 |
---|---|---|
dtype | python数据类型 | Tensor的数据类型 |
shape | list | tensor的形状 |
3.1.3 from_numpy
【描述】
从numpy.ndarray构造RuntimeTensor对象
【定义】
from_numpy(py::array arr)
【参数】
名称 | 类型 | 描述 |
---|---|---|
Arr | numpy.ndarray | numpy.ndarray对象 |
【返回值】
RuntimeTensor
【示例】
tensor = nncase.RuntimeTensor.from_numpy(self.inputs[i]['data'])
3.1.4 copy_to
【描述】
拷贝RuntimeTensor
【定义】
copy_to(RuntimeTensor to)
【参数】
名称 | 类型 | 描述 |
---|---|---|
to | RuntimeTensor | RuntimeTensor对象 |
【返回值】
无
【示例】
sim.get_output_tensor(i).copy_to(to)
3.1.5 to_numpy
【描述】
将RuntimeTensor转换为numpy.ndarray对象
【定义】
to_numpy()
【参数】
无
【返回值】
numpy.ndarray对象
【示例】
arr = sim.get_output_tensor(i).to_numpy()
3.1.6 Simulator
【描述】
Simulator类, 用于在PC上推理kmodel
【定义】
py::class_<interpreter>(m, "Simulator")
.def(py::init())
.def("load_model", [](interpreter &interp, gsl::span<const gsl::byte> buffer) { interp.load_model(buffer).unwrap_or_throw(); })
.def_property_readonly("inputs_size", &interpreter::inputs_size)
.def_property_readonly("outputs_size", &interpreter::outputs_size)
.def("get_input_desc", &interpreter::input_desc)
.def("get_output_desc", &interpreter::output_desc)
.def("get_input_tensor", [](interpreter &interp, size_t index) { return interp.input_tensor(index).unwrap_or_throw(); })
.def("set_input_tensor", [](interpreter &interp, size_t index, runtime_tensor tensor) { return interp.input_tensor(index, tensor).unwrap_or_throw(); })
.def("get_output_tensor", [](interpreter &interp, size_t index) { return interp.output_tensor(index).unwrap_or_throw(); })
.def("set_output_tensor", [](interpreter &interp, size_t index, runtime_tensor tensor) { return interp.output_tensor(index, tensor).unwrap_or_throw(); })
.def("run", [](interpreter &interp) { interp.run().unwrap_or_throw(); });
【属性】
名称 | 类型 | 描述 |
---|---|---|
inputs_size | int | 输入个数 |
outputs_size | int | 输出个数 |
【示例】
sim = nncase.Simulator()
3.1.7 load_model
【描述】
加载kmodel
【定义】
load_model(model_content)
【参数】
名称 | 类型 | 描述 |
---|---|---|
model_content | byte[] | kmodel字节流 |
【返回值】
无
【示例】
sim.load_model(kmodel)