Skip to content

附录Ⅱ. 特殊模型转换要点

特殊模型
灰度模型 输入是单通道图片的模型,即输入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时:

      [INPUT_CONFIG]
      ......
      training_input_formats=GRAY;
      input_formats=GRAY;
      ......
      
      实际给Fixed或Offline模型的数据为灰度图片,此时对应的input_width_alignmentinput_height_alignment默认为1,如不设定input_width_alignmentinput_height_alignment,实际输入与模型输入形状相同。

    • input_formats为YUV_NV12时:

      [INPUT_CONFIG]
      ......
      training_input_formats=GRAY;
      input_formats=YUV_NV12;
      ......
      
      实际给Fixed或Offline模型的数据为YUV_NV12数据,此时对应的input_width_alignmentinput_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图片输入数据。

box_process


2. RAWDATA_F32_NHWC与RAWDATA_S16_NHWC输入的模型

RAWDATA_F32_NHWCRAWDATA_S16_NHWC可以用于分段网络的后端网络模型输入,或者非图片数据输入的网络模型。

非图片输入的网络转换:需注意input_config配置

非图片输入的模型网络验证: 需注意前处理

2.1. 非图片输入模型input_config配置信息要点

(1) RAWDATA_F32_NHWC模型

转换非图片输入模型时需注意input_config配置信息中如下要点:(input_config.ini文件详细说明请参考Convert: input config配置信息设置)

Note

  • input_formatstraining_input_formats都填写RAWDATA_F32_NHWCquantizations配置为TRUE

  • input_config.ini中meanstd_value不要填写。

  • 模型在使用calibrator.py工具转换为定点网络时使用浮点数据,因此转换方法与正常网络相同。可以参考Calibrator的使用方法。

(2) RAWDATA_S16_NHWC模型

转换非图片输入模型时需注意input_config配置信息中如下要点:(input_config.ini文件详细说明请参考Convert: input config配置信息设置)

Note

  • input_formatstraining_input_formats都填写RAWDATA_S16_NHWCquantizations配置为TRUE

  • input_config.ini中meanstd_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

      >>> print(img.shape)
      (1, 513, 513, 3)
      
      定点化img的数据,并转换数据类型

      >>> 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 网络切分

网络切分示意图:

box_process

(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
    
    [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;
    
    设定conv5_3 这个输出的dequantizations 为FALSE,由于该输出直接输入第二段网络,所以在板端运行时不需要 转换成float,可以直接将该输出送进第二段网络。

  • 第二段网络:

    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
* Faster_RCNN网络中,使用了自定义的rpn层,因此将第一段网络的第1个和第2个输出以及图片信息作为rpn层的输入,第一段网络的第3个输出和rpn层的输出roi作为第二段网络的输入。 最后调用Net的convert方法,同时生成两段网络的fixed模型。

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的实例,然后调用自身的方法就可以运行。具体参考Calibrator:calibrator_custom.SIM_Simulator

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!')
运行该脚本,就能生成matmul.tflite模型文件了。


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
然后使用ConvertTool工具转换matmul.tflite:
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];