附录Ⅱ. 特殊模型转换要点
特殊模型 | ||
---|---|---|
灰度模型 | 输入是单通道图片的模型,即输入C维度上为1的模型 | |
非图片数据输入的网络模型 | input_config.ini中使用RAWDATA_F32_NHWC或RAWDATA_S16_NHWC配置的模型 | |
分段模型 | 网络中有不支持的Layer,可将完整网络分段执行,如FasterRCNN | |
多输入模型 |
1. 灰度模型¶
灰度模型,指输入是单通道图片的模型,即输入C维度上为1的模型。
灰度模型的网络转换:需注意input_config配置
灰度模型的网络验证: 需注意前处理
1.1. 灰度模型input_config配置信息要点¶
转换灰度模型时需注意input_config配置信息中如下要点:(input_config.ini文件详细说明请参考Convert: input config配置信息设置)
Note
-
input_config.ini文件中
training_input_formats
为GRAY,input_formats
可以为GRAY,也可以为YUV_NV12。-
input_formats
为GRAY时:实际给Fixed或Offline模型的数据为灰度图片,此时对应的[INPUT_CONFIG] ...... training_input_formats=GRAY; input_formats=GRAY; ......
input_width_alignment
和input_height_alignment
默认为1,如不设定input_width_alignment
和input_height_alignment
,实际输入与模型输入形状相同。 -
input_formats
为YUV_NV12时:实际给Fixed或Offline模型的数据为YUV_NV12数据,此时对应的[INPUT_CONFIG] ...... training_input_formats=GRAY; input_formats=YUV_NV12; ......
input_width_alignment
和input_height_alignment
默认为2。
-
-
input_config.ini文件中仅需设置单通道的mean值
[INPUT_CONFIG] ...... mean=33.318; std_value=1.0;
1.2. 灰度图片输入模型前处理方法¶
灰度图片输入的前处理方法与Calibrator: 图片前处理方法要求相同,前处理方法示例请参考前处理和配置文件注意要点
Note
- 灰度图片输入模型在PC上运行与开发板根据
input_formats
的配置可以支持GRAY或YUV_NV12图片输入数据。
2. RAWDATA_F32_NHWC与RAWDATA_S16_NHWC输入的模型¶
RAWDATA_F32_NHWC
与RAWDATA_S16_NHWC
可以用于分段网络的后端网络模型输入,或者非图片数据输入的网络模型。
非图片输入的网络转换:需注意input_config配置
非图片输入的模型网络验证: 需注意前处理
2.1. 非图片输入模型input_config配置信息要点¶
(1) RAWDATA_F32_NHWC
模型
转换非图片输入模型时需注意input_config配置信息中如下要点:(input_config.ini文件详细说明请参考Convert: input config配置信息设置)
Note
-
input_formats
和training_input_formats
都填写RAWDATA_F32_NHWC
,quantizations
配置为TRUE
。 -
input_config.ini中
mean
和std_value
不要填写。 -
模型在使用calibrator.py工具转换为定点网络时使用浮点数据,因此转换方法与正常网络相同。可以参考Calibrator的使用方法。
(2) RAWDATA_S16_NHWC
模型
转换非图片输入模型时需注意input_config配置信息中如下要点:(input_config.ini文件详细说明请参考Convert: input config配置信息设置)
Note
-
input_formats
和training_input_formats
都填写RAWDATA_S16_NHWC
,quantizations
配置为TRUE
。 -
input_config.ini中
mean
和std_value
不要填写。 -
模型在使用calibrator.py工具转换为定点网络时使用浮点数据,因此转换方法与正常网络相同。可以参考Calibrator的使用方法。
-
使用该种方式输入时,由于硬件的限制条件,所以需要提前排列好数据后才能进行计算。
2.2 RAWDATA_F32_NHWC模型推演¶
(1) 模型前处理方法
请参考前处理和配置文件注意要点:training_input_formats和input_formats均为RAWDATA_S16_NHWC或者均为RAWDATA_F32_NHWC
(2) 板上运行注意事项
在板上运行RAWDATA_F32_NHWC
的网络时,可以参考如下代码:
void FillInputData(MI_FLOAT* pfData, MI_IPU_TensorVector_t& InputTensorVector, MI_IPU_SubNet_InputOutputDesc_t& desc)
{
const MI_U32 H = desc.astMI_InputTensorDescs[0].u32TensorShape[1];
const MI_U32 W = desc.astMI_InputTensorDescs[0].u32TensorShape[2];
const MI_U32 C = desc.astMI_InputTensorDescs[0].u32TensorShape[3];
MI_FLOAT* pTensorData = (MI_FLOAT*)InputTensorVector.astArrayTensors[0].ptTensorData[0];
if (desc.astMI_InputTensorDescs[0].eElmFormat == MI_IPU_FORMAT_FP32)
{
memcpy(pTensorData, pfData, H * W * C * sizeof(MI_FLOAT));
}
}
FillInputData函数的输入分别是浮点输入数据的数组
MI_IPU输入Tensor结构体和MI_IPU网络描述结构体
只有使用
RAWDATA_F32_NHWC
配置的网络在板上memcpy完数据后不要调用MI_SYS_FlushInvCache。
补充说明
-
如需使用calibrator_custom.fixed_simulator,需自行实现定点化:
-
使用calibrator_custom.fixed_simulator时,输入数据类型为float32。
>>> import calibrator_custom >>> model = calibrator_custom.fixed_simulator('./mobilenet_v2_fixed.sim') >>> input_details = model.get_input_details() >>> print(input_details) [{'shape': array([ 1, 513, 513, 3]), 'name': 'sub_7', 'dtype': <class 'numpy.float32'>, 'index': 0}]
-
calibrator_custom.fixed_simulator中检查了模型的输入格式,计算出需要输入的shape信息。
-
已使用float网络的前处理处理完图片,返回numpy.ndarray格式的变量img
输入模型数据>>> print(img.shape) (1, 513, 513, 3) >>> print(img.dtype) float32
>>> model.set_input(input_details[0]['index'], img)
-
-
2.3. RAWDATA_S16_NHWC模型推演¶
(1) 模型前处理方法
请参考前处理和配置文件注意要点:training_input_formats和input_formats均为RAWDATA_S16_NHWC或者均为RAWDATA_F32_NHWC
simulator.py会读入原浮点数据,进行反量化后输入给定点模型。
(2) 板上运行注意事项
在板上运行RAWDATA_S16_NHWC
的网络时,也需要完成输入数据的定点化过程,可以参考如下代码,FillInputData函数的输入分别是浮点输入数据的数组,MI_IPU输入Tensor结构体和MI_IPU网络描述结构体。
#define CLIP3(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x)))
void FillInputData(MI_FLOAT* pfData, MI_IPU_TensorVector_t& InputTensorVector, MI_IPU_SubNet_InputOutputDesc_t& desc)
{
const MI_U32 H = desc.astMI_InputTensorDescs[0].u32TensorShape[1];
const MI_U32 W = desc.astMI_InputTensorDescs[0].u32TensorShape[2];
const MI_U32 C = desc.astMI_InputTensorDescs[0].u32TensorShape[3];
const MI_FLOAT Scale = desc.astMI_InputTensorDescs[0].fScalar;
const MI_S64 ZeroPoint = desc.astMI_InputTensorDescs[0].s64ZeroPoint;
MI_S16* pData = (MI_S16*)InputTensorVector.astArrayTensors[0].ptTensorData[0];
for (MI_U32 i = 0; i < H * W * C; i++)
{
*(pData + i) = (MI_S16)CLIP3(pfData[i] / Scale + ZeroPoint, -32768 , 32767);
}
MI_SYS_FlushInvCache(pData, desc.astMI_InputTensorDescs[0].s32AlignedBufSize);
}
FillInputData函数的输入分别是浮点输入数据的数组
MI_IPU输入Tensor结构体和MI_IPU网络描述结构体
补充说明
-
如需使用calibrator_custom.fixed_simulator,需自行实现定点化:
-
使用calibrator_custom.fixed_simulator时,输入数据类型为int16。
>>> import calibrator_custom >>> model = calibrator_custom.fixed_simulator('./mobilenet_v2_s16_fixed.sim') >>> input_details = model.get_input_details() >>> print(input_details) [{'input_formats': 'RAWDATA_S16_NHWC', 'training_input_formats': 'RAWDATA_S16_NHWC', 'shape': array([ 1, 513, 513, 3]), 'name': 'sub_7', 'dtype': <class 'numpy.int16'>, 'index': 0, 'quantization': (3.0518509447574615e-05, 0)}]
-
-
calibrator_custom.fixed_simulator中检查了模型的输入格式,计算出需要输入的shape信息。
-
已使用float网络的前处理处理完图片,返回numpy.ndarray格式的变量img
定点化img的数据,并转换数据类型>>> print(img.shape) (1, 513, 513, 3)
输入模型数据>>> ins, zp = input_details[0]['quantization'] >>> img = (img / ins + zp).astype(input_details[0]['dtype']) >>> print(img.dtype) int16
>>> model.set_input(input_details[0]['index'], img)
-
3. 分段网络转换要点¶
网络中有不支持的Layer,可将完整网络分段执行:
前一段网络运行完成后将结果输入给自定义实现层,
再将自定义层的输出结果作为第二段网络的输入运行。
以Faster_RCNN网络为例:
Faster_RCNN网络中的Proposal Layer是不支持的,需要将网络从该层分成两段。
3.1 网络切分¶
网络切分示意图:
(1) 图中的Python Layer是Proposal Layer,通过修改prototxt文件,将网络从Proposal Layer处拆成两段。
(2) 第一段网络将有3个输出,
> 其中rpn_cls_prob_reshape和rpn_bbox_pred两个输出结果将作为Proposal Layer的输入,
> conv5_3和Proposal Layer的输出将作为第二段网络的输入。
(3) 第一段网络的输入是图片数据,input_config.ini和前处理文件分别按照Convert:input config配置信息设置和Calibrator:图片前处理方法处理。
(4) 第二段网络的输入不是图片数据,可以对两个输入同时采用RAWDATA_S16_NHWC的格式配置。配置完成后,通过ConvertTool.py分别将两个网络转换成float.sim模型文件。
具体配置文件和转换命令
-
第一段网络:
python3 ~/SGS_IPU_SDK/Scripts/ConvertTool/ConvertTool.py caffe \ --model_file test_stageone.prototxt \ --weight_file VGG16_faster_rcnn_final.caffemodel \ --input_arrays data \ --output_arrays rpn_cls_prob_reshape,rpn_bbox_pred,conv5_3 \ --input_config input_config.ini \ --output_file faster_rcnn_main_float.sim
设定conv5_3 这个输出的dequantizations 为FALSE,由于该输出直接输入第二段网络,所以在板端运行时不需要 转换成float,可以直接将该输出送进第二段网络。[INPUT_CONFIG] inputs=data; input_formats=BGR; quantizations=TRUE; mean=122.7717:115.9465:102.9801; std_value=1; [OUTPUT_CONFIG] outputs=rpn_cls_prob_reshape,rpn_bbox_pred,conv5_3; dequantizations=TRUE,TRUE,FALSE;
-
第二段网络:
ROIPooling的rois输入维度为(N×5),当后段网络全部是InnerProduct时,N才可以设置为300(如上图所示),如果后段网络中有卷积时,N仅可以设置为1,第二段网络需要循环执行N次。
python3 ~/SGS_IPU_SDK/Scripts/ConvertTool/ConvertTool.py caffe \ --model_file second_stage.prototxt \ --weight_file VGG16_faster_rcnn_final.caffemodel \ --input_arrays conv5_3,rois \ --output_arrays cls_prob,bbox_pred \ --input_config input_config_stage2.ini \ --output_file faster_rcnn_stage2_float.sim
[INPUT_CONFIG] inputs=conv5_3,rois input_formats=RAWDATA_F32_NHWC,RAWDATA_F32_NHWC; quantizations=TRUE,TRUE; [OUTPUT_CONFIG] outputs=cls_prob,bbox_pred; dequantizations=TRUE,TRUE;
3.2 转换网络¶
路径SGS_IPU_SDK/Scripts/examples/caffe_faster_rcnn/faster_rcnn_calibrator.py
下有Faster_RCNN网络转换工具demo,直接运行即可将两段网络直接转换成fixed模型。
(1) 指令
python3 ~/SGS_IPU_SDK/Scripts/examples/caffe_faster_rcnn/faster_rcnn_calibrator.py \
-i ~/SGS_Models/resource/detection/voc_calibration_set32/ \
-m0 faster_rcnn_main_float.sim \
-m1 faster_rcnn_stage2_float.sim \
--input_config0 input_config.ini \
--input_config1 input_config_stage2.ini
(2) 转换分段网络
- 首先定义网络的两段网络,再组织两段网络的运行方式,定义在forward方法里:
class Net(calibrator_custom.SIM_Calibrator):
def __init__(self, main_model_path, main_input_config, second_model_path, second_input_config):
super().__init__()
self.main_model = calibrator_custom.calibrator(main_model_path, main_input_config)
self.second_model = calibrator_custom.calibrator(second_model_path, second_input_config)
self.rpn = rpn.ProposalLayer()
def forward(self, x):
out_details = self.main_model.get_output_details()
input_data, im_scale = fill_inputImg2main(x)
self.main_model.set_input(0, input_data)
self.main_model.invoke()
result_list = []
for idx, _ in enumerate(out_details):
result = self.main_model.get_output(idx)
result_list.append(result)
im_info = np.array([x.shape[0], x.shape[1], im_scale]).reshape(1, 3)
bottom = [result_list[0], result_list[1], im_info]
roi = self.rpn.forward(bottom)
out2_details = self.second_model.get_output_details()
self.second_model.set_input(0, result_list[2])
self.second_model.set_input(1, roi)
self.second_model.invoke()
second_result = []
for idx, _ in enumerate(out2_details):
result = self.second_model.get_output(idx)
second_result.append(result)
return second_result
net = Net()
net.convert(img_gen, num_process=num_subsets, fix_model=[out_main_model, out_second_model])
3.3 推演网络¶
路径SGS_IPU_SDK/Scripts/examples/caffe_faster_rcnn/faster_rcnn_simulator.py
下有Faster_RCNN网络运行工具demo。
(1) 指令
python3 ~/SGS_IPU_SDK/Scripts/examples/caffe_faster_rcnn/faster_rcnn_simulator.py \
-i ~/SGS_Models/resource/detection/004545.jpg \
-m0 faster_rcnn_main_float.sim \
-m1 faster_rcnn_stage2_float.sim \
-t Float
(2) 运行两段模型的方法与转换网络时类似。
class Net(calibrator_custom.SIM_Simulator):
def __init__(self, main_model_path, second_model_path, phase):
super().__init__()
if phase == 'Float':
self.main_model = calibrator_custom.float_simulator(main_model_path)
self.second_model = calibrator_custom.float_simulator(second_model_path)
self.norm = True
elif phase == 'Fixed':
self.main_model = calibrator_custom.fixed_simulator(main_model_path)
self.second_model = calibrator_custom.fixed_simulator(second_model_path)
self.norm = False
else:
self.main_model = calibrator_custom.offline_simulator(main_model_path)
self.second_model = calibrator_custom.offline_simulator(second_model_path)
self.norm = False
self.rpn = rpn.ProposalLayer()
def forward(self, x):
# Run main model
out_details = self.main_model.get_output_details()
input_data, im_scale = fill_inputImg2main(x, norm=norm)
self.main_model.set_input(0, input_data)
self.main_model.invoke()
def forward(self, x):
out_details = self.main_model.get_output_details()
input_data, im_scale = fill_inputImg2main(x)
self.main_model.set_input(0, input_data)
self.main_model.invoke()
result_list = []
for idx, _ in enumerate(out_details):
result = self.main_model.get_output(idx)
result_list.append(result)
im_info = np.array([x.shape[0], x.shape[1], im_scale]).reshape(1, 3)
bottom = [result_list[0], result_list[1], im_info]
roi = self.rpn.forward(bottom)
out2_details = self.second_model.get_output_details()
self.second_model.set_input(0, result_list[2])
if self.norm:
self.second_model.set_input(1, roi)
else:
self.second_model.set_input(1, roi)
self.second_model.invoke()
second_result = []
for idx, _ in enumerate(out2_details):
result = self.second_model.get_output(idx)
second_result.append(result)
return second_result
net = Net()
results = net(img_gen, num_process=num_subsets)
4. 多输入网络转换要点¶
4.1 构建多输入网络¶
使用Tensorflow1.14.0构建一个双输入网络,该网络用来做矩阵乘法,计算两个向量的内积。
import tensorflow as tf
import numpy as np
SHAPE_DIMS = 256
input_np0 = np.random.rand(1, SHAPE_DIMS).astype(np.float32)
input_np1 = np.random.rand(1, SHAPE_DIMS).astype(np.float32)
input_0 = tf.placeholder(dtype=tf.float32, shape=(1, SHAPE_DIMS), name='input0')
input_1 = tf.placeholder(dtype=tf.float32, shape=(1, SHAPE_DIMS), name='input1')
out = tf.matmul(input_0, tf.reshape(input_1, (SHAPE_DIMS, 1)), name='output')
with tf.Session() as sess:
results = sess.run(out, feed_dict={input_0: input_np0, input_1: input_np1})
# Convert .pb
output_graph_def = tf.graph_util.convert_variables_to_constants(
sess,
sess.graph_def,
['output']
)
with tf.gfile.GFile('matmul.pb', 'wb') as f:
f.write(output_graph_def.SerializeToString())
print('matmul.pb Saved!')
# convert to .tflite
tflite_model = tf.lite.TFLiteConverter.from_frozen_graph(
'matmul.pb',
['input0', 'input1'],
['output']
)
tflite_model = tflite_model.convert()
with open('matmul.tflite', 'wb') as f:
f.write(tflite_model)
print('matmul.tflite Saved!')
4.2 转换多输入网络¶
下面将matmul.tflte转换成可以在板上运行的模型文件。 matmul.tflite模型不是图片输入,因此配置input_config.ini文件请参考RAWDATA_F32_NHWC与RAWDATA_S16_NHWC输入的模型。 配置input_config.ini脚本
[INPUT_CONFIG]
inputs=input0,input1;
input_formats=RAWDATA_F32_NHWC,RAWDATA_F32_NHWC;
quantization=TRUE,TRUE;
[OUTPUT_CONFIG]
outputs=output;
dequantization=TRUE;
请先在SGS_IPU_SDK⽬录下运⾏以下脚本,输出Library的路径(已经做过该步骤可忽略):
cd ~/SGS_IPU_SDK
source cfg_env.sh
python3 ~/SGS_IPU_SDK/Scripts/ConvertTool/ConvertTool.py tflite \
--model_file /path/to/matmul.tflite \
--input_config /path/to/input_config.ini \
--output_file /path/to/matmul_float.sim
多输入网络的calibrator过程需要使用calibrator_custom模块,可以参考Calibrator:calibrator_custom 。不同的是输入的生成器需要配置成双输入。将如下示例文件保存为matmul_calibrator.py。
# -*- coding: utf-8 -*-
import calibrator_custom
import os
import sys
import numpy as np
import argparse
from calibrator_custom import utils
class Net(calibrator_custom.SIM_Calibrator):
def __init__(self, model_path, input_config):
super().__init__()
self.model = calibrator_custom.calibrator(model_path, input_config)
def forward(self, x, y):
out_details = self.model.get_output_details()
self.model.set_input(0, x)
self.model.set_input(1, y)
self.model.invoke()
result_list = []
for idx in range(len(out_details)):
result = self.model.get_output(idx)
result_list.append(result)
return result_list
def arg_parse():
parser = argparse.ArgumentParser(description='Calibrator Tool')
parser.add_argument('-m', '--model', type=str, required=True,
help='Model path.')
parser.add_argument('--input_config', type=str, required=True,
help='Input config path.')
parser.add_argument('--quant_level', type=str, default='L5',
choices=['L1', 'L2', 'L3', 'L4', 'L5'],
help='Indicate Quantilization level. The higher the level,\
the slower the speed and the higher the accuracy.')
parser.add_argument('--num_process', default=10, type=int,
help='Amount of processes run at same time.')
parser.add_argument('-o', '--output', default=None, type=str,
help='Output path for fixed model.')
return parser.parse_args()
def data_gen():
calibrator_data = np.random.rand(100, 1, 256).astype(np.float32)
bench_data = np.random.rand(100, 1, 256).astype(np.float32)
for i, j in zip(calibrator_data, bench_data):
yield [i, j]
def main():
args = arg_parse()
model_path = args.model
input_config = args.input_config
quant_level = args.quant_level
num_subsets = args.num_process
output = args.output
if not os.path.exists(model_path):
raise FileNotFoundError('No such {} model'.format(model_path))
if not os.path.exists(input_config):
raise FileNotFoundError('input_config.ini file not found.')
net = Net(model_path, input_config)
print(net)
# random generate data
# must change real data when using
img_gen = data_gen()
print('\033[31m[WARNING] random generate data,\
must change real data when using!\033[0m',
file=sys.stderr)
out_model = utils.get_out_model_name(model_path, output)
net.convert(img_gen, num_process=num_subsets,
quant_level=quant_level, fix_model=[out_model])
if __name__ == '__main__':
main()
使用编写的matmul_calibrator.py转换matmul_float.sim,生成matmul_fixed.sim。
python3 matmul_calibrator.py \
-m /path/to/matmul_float.sim \
--input_config /path/to/input_config.ini
需要注意的是,量化该模型的输入数据一定要换成真实数据,不然统计生成的fixed模型精度会出错。如果单独量化该模型比较困难,比如它是接在某个网络后面运行的,可以参考多输入网络转换要点,按照多段网络处理,在calibrator_custom.SIM_Calibrator类里定义多个模型,可以直接生成出多个fixed网络。 最后使用compiler.py转换matmul_fixed.sim,生成matmul_fixed.sim_sgsimg.img。
4.3 在PC上推演多输入网络¶
需要注意的是,运行Float的网络时,输入数据的type需要为float32,输入数据的shape与模型输入shape相同。但是由于matmul的Fixed和Offline模型是RAWDATA_S16_NHWC输入,输入数据需要先反量化到int16才能输入到模型。所以定义calibrator_custom.SIM_Simulator的forward函数时需要注意该模型的特殊性:
class Net(calibrator_custom.SIM_Simulator):
def __init__(self, model_path, phase):
super().__init__()
if phase == 'Float':
self.model = calibrator_custom.float_simulator(model_path)
elif phase == 'Fixed':
self.model = calibrator_custom.fixed_simulator(model_path)
else:
self.model = calibrator_custom.offline_simulator(model_path)
def forward(self, x, y):
out_details = self.model.get_output_details()
self.model.set_input(0, x)
self.model.set_input(1, y)
self.model.invoke()
result_list = []
for idx, _ in enumerate(out_details):
result = self.model.get_output(idx)
result_list.append(result)
return result_list
4.4 在板上推演多输入网络¶
由于matmul_fixed.sim_sgsimg.img网络输入是RAWDATA_F32_NHWC
,可以参考FillInputData函数,与在PC上相同,需要将输入数据处理后再送入模型。 多输入模型只需在FillInputData函数中增加对模型的第二个输入灌入数据:
// input0
MI_FLOAT* pData0 = (MI_FLOAT*)InputTensorVector.astArrayTensors[0].ptTensorData[0];
// input1
MI_FLOAT* pData1 = (MI_FLOAT*)InputTensorVector.astArrayTensors[1].ptTensorData[0];