使用TpuLang转换模型的流程
下图(run_eval待测模型列表及参数)填写更多不同精度评估方式的命令字符串,比如图中已有imagenet分类与coco检测精度计算字符串;下图(run_eval待测模型列表及参数)中model_list_all填写模型名到参数的映射,比如:resnet18_qat的[0,0],其中第1个参数表示用postprocess_type_all中第1个的命令串,第2个参数表示用qat_model_path第1个目录(以逗号分隔):
根据需要配置上图postprocess_type_all与model_list_all数组后,执行下面run_eval.py命令:
python3 run_eval.py
#qat验证模式,默认是使用tpu-MLIR/regression/config中配置进行常规的模型精度测试
--qat_eval
--fast_test
#正式测试前的快速测试(只测试30张图的精度),确认所有case都能跑起来
--pool_size 20 #默认起10个进程来跑,若机器闲置资源较多,可多配点
--batch_size 10 #qat导出模型的batch-size,默认为1
--qat_model_path '/workspace/classify_models/,/workspace/yolov5/qat_models' #qat模型所在目录,比如model_list_all[‘resnet18_qat’][1]的取值为0,表示其模型目标在qat_model_path的第1个目录地址:/workspace/classify_models/
--debug_cmd use_pil_resize #使用pil resize方式
测试后或测试过程中,查看以{model_name}_qat命名的子目录下以log_开头的model_eval脚本输出日志文件,比如:log_resnet18_qat.MLIR表示对本目录中resnet18_qat.MLIR进行测试的日志;log_resnet18_qat_bm1684x_tpu_int8_sym.MLIR表示对本目录中resnet18_qat_bm1684x_tpu_int8_sym.MLIR进行测试的日志
使用样例-yolov5s
同前面resnet18类似,在example/yolov5_example中执行如下命令可启动qat训练:
python3 train.py
--cfg=yolov5s.yaml
--weights=yolov5s.pt
--data=coco.yaml
--epochs=5
--output_path=/workspace/yolov5/qat_models
--batch-size=8
--quantize
完成训练后,采取与前面resnet18一样的测试、转换部署流程即可。
TpuLang接口
主要介绍使用TpuLang转换模型的流程。
主要工作
TpuLang提供了MLIR对外的接口函数。用户通过Tpulang可以直接组建用户自己的网络,将模型转换为 Top 层(芯片无关层) MLIR 模型 (不包含 Canonicalize 部分, 因此生成的文件名为*_origin.MLIR)。这个过程会根据输入的接口函数逐 一创建并添加算子(Op), 最终生成 MLIR 文件与保存权重的 npz 文件。
工作流程
1)初始化:设置运行平台,创建模型Graph。
2)添加OPS:循环添加模型的OP。
A)输入参数转为dict格式;
B)推理outputshape,并创建输出张量;
C)设置张量的量化参数(scale, zero_point);
D)创建op(op_type, inputs, outputs, params)并insert到graph中。
3)设置模型的输入输出张量。得到全部模型信息。
1) 初始化TpuLangconverter(initMLIRImporter)。
2) generate_MLIR
A)依次创建输入算子, 模型中间节点算子以及返回算子, 并将其补充到 MLIR 文本中(如果该算子带有权重, 则会特定创建权重算子)。
3) 输出
A)将生成的文本转为 str 并保存为.MLIR文件;
B)将模型权重(tensors)保存为.npz文件。
7)结束:释放 graph。
TpuLang转换的工作流程如图所示。
补充说明:
1)操作接口需要:
2)操作的输入张量(即前一个算子的输出张量或graph输入张量,coeff);
1) 根据接口提取的参数,推理获取 output_shape(即需要进行shape_inference);
4)从接口中提取的属性。属性会通过 MLIRImporter 设定为与 TopOps.td 定义一一对应的属性;
5)如果接口中包括量化参数(scale,zero_point),则该参数对应的张量需要设置(或检查)量化参数;
6)返回该操作的输出张量(tensors);
7)在所有算子都插入graph,并设置graph的输入/输出tensors之后,才会启动转换到 MLIR 文本的工作。该部分由TpuLang转换器来实现;
8)TpuLang转换器转换流程与onnx前端转换流程相同,具体参考(前端转换)。
算子转换样例
以卷积算子为例, 将单卷积算子模型转换为 Top MLIR, 原模型定义如图所示(单卷积模型)。
转换流程为:
1)接口定义
conv_v2 接口定义如下:
defconv_v2(tensor_i,
weight,
bias = None,
stride = None,
dilation = None,
pad = None,
group = 1,
input_zp = None,
weight_zp = None,
out_dtype = None,
out_name = None):
# pass
参数说明
1)tensor_i:张量类型,表示输入张量,4维NCHW格式。
2)weight:张量类型,表示卷积核张量,4维[oc, ic, kh, kw]格式。其中oc表示输出Channel数,ic表示输入channel数,kh是kernel_h,kw是kernel_w。
3)bias:张量类型,表示偏置张量。为None时表示无偏置,反之则要求shape为[1, oc, 1, 1]。
2) dilation:List[int],表示空洞大小,取None则表示[1,1],不为None时要求长度为2。List中顺序为[长,宽]
3) pad:List[int],表示填充大小,取None则表示[0,0,0,0],不为None时要求长度为4。List中顺序为[上, 下, 左, 右]
4) stride:List[int],表示步长大小,取None则表示[1,1],不为None时要求长度为2。List中顺序为[长,宽]
5) groups:int型,表示卷积层的组数。若ic=oc=groups时,则卷积为depthwise卷积
8)input_zp:List[int]型或int型,表示输入偏移。取None则表示0,取List时要求长度为ic。
2) weight_zp:List[int]型或int型,表示卷积核偏移。取None则表示0,取List时要求长度为ic,其中ic表示输入的Channel数。
10)out_dtype:string类型或None,表示输出张量的类型。输入张量类型为float16/float32时,取None表示输出张量类型与输入一致,否则取None表示为int32。取值范围:/int32/uint32/float32/float16
11)out_name:string类型或None,表示输出张量的名称,为None时内部会自动产生名称。
在 TopOps.td 中定义 Top.conv算子。
1.构建 Graph
初始化模型:创建空Graph。
模型输入:给定shape与data type 创建输入张量 x。此处也可以指定张量name。
conv_v2接口:
调用conv_v2接口,指定输入张量以及输入参数。
推理outputshape,并生成输出张量
def _shape_inference():
kh_ext = dilation[0] * (weight.shape[2] - 1) + 1
kw_ext = dilation[1] * (weight.shape[3] - 1) + 1
oh = (input.shape[2] + pad[0] + pad[1] - kh_ext) // stride[0] + 1
ow = (input.shape[3] + pad[2] + pad[3] - kw_ext) // stride[1] + 1
return [input.shape[0], weight.shape[0], oh, ow]
output = tensor(_shape_inference(), dtype=out_dtype, name=out_name)
attributes,将输入参数打包成 定义的attributes
attr = {
kernel_shape: ArrayAttr(weight.shape[2:]),
strides: ArrayAttr(stride),
dilations: ArrayAttr(dilation),
pads: ArrayAttr(pad),
do_relu: Attr(False, bool),
group: Attr(group)
}
插入卷积op,将Top.convOp插入到Graph中。
返回输出张量
设置Graph的输入,输出tensors。
2.init_MLIRImporter:
根据 input_names 与 output_names 从 shapes 中获取了对应的 input_shape 与 output_shape, 加上model_name, 生成了初始的 MLIR 文本 MLIRImporter.MLIR_module, 如图所示。
3.generate_MLIR
1)build输入op, 生成的 Top.inputOp 会被插入到 MLIRImporter.MLIR_module 中。
1) 调用Operation.create 来创建 Top.convOp, 而 create 函数需要的参数有:
A)输入 op: 从接口定义可知,卷积算子的 inputs 一共包含了input, 权重 与 bias, 输入Op 已被创建好, 权重与 bias 的 op 则通过 getWeightOp()创建。
B)output_shape: 利用 Operator 中存储的输出张量中获取其shape。
C)Attributes: 从 Operator 中获取 attributes,并将attributes转换为MLIRImporter识别的Attributes
Top.convOp 创建后会被插入到 MLIR 文本中
3)根据 output_names 从 operands 中获取相应的 op, 创建 return_op 并插入到 MLIR 文本中。到此为止, 生成的 MLIR 文本如图所示。
4.输出
将 MLIR 文本保存为conv_origin.MLIR, tensors 中的权重保存为conv_TOP_F32_all_weight.npz。
人工智能芯片与自动驾驶