5.2. 算法模型PTQ量化+上板 快速上手

本章节中,我们将为您介绍训练后量化PTQ方案的基本使用流程,便于您实现快速上手。 这里我们以MobileNet-v1模型为例,为您进行使用演示,详细内容将在后续章节为您展开介绍,基本工作流程如下图所示。

../../../_images/quickstart.png

注意

请注意,在您进行以下操作前,请确保您已经参考 环境部署 章节完成了开发机和开发板上的环境安装。

5.2.1. 浮点模型准备

OE包在 ddk/samples/ai_toolchain/horizon_model_convert_sample 路径下为您提供了丰富的PTQ模型示例, 其中MobileNet-v1模型示例位于 03_classificarion/01_mobilenet 路径下。

请先执行其中的 00_init.sh 脚本来获取示例对应的校准数据集和原始模型。 示例模型的模型来源和相关说明请参考 如何准备模型 章节。

如您需要转换私有模型,请参考 浮点模型准备 章节内容提前准备好 caffe1.0 或 opset=10/11 的 onnx 模型。 下表为不同框架到ONNX模型格式转换的参考方案:

训练框架

参考方案

Pytorch

使用官方API导出: https://pytorch.org/tutorials/advanced/super_resolution_with_onnxruntime.html

Tensorflow

使用ONNX社区的onnx/tensorflow-onnx工具转换: https://github.com/onnx/tensorflow-onnx

PaddlePaddle

使用官方API导出: https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/onnx/export_cn.html

MXNet2Onnx

使用官方API导出: https://github.com/dotnet/machinelearning/blob/main/test/Microsoft.ML.Tests/OnnxConversionTest.cs

其他框架

https://github.com/onnx/tutorials#converting-to-onnx-format

5.2.2. 模型验证

在浮点模型准备好之后,我们建议先进行快速的模型验证,以确保其符合计算平台的支持约束。 对于Caffe1.0框架的MobileNet-v1模型,我们可以在命令行中键入以下命令完成模型验证:

hb_mapper checker --model-type caffe \
                  --proto mobilenet_deploy.prototxt \
                  --model mobilenet.caffemodel \
                  --march bayes

对于ONNX格式的Efficientnet_lite0模型,则键入以下命令:

hb_mapper checker --model-type onnx \
                  --model efficientnet_lite0_fp32.onnx \
                  --march bayes

其中,两个模型文件都可从 ddk/samples/model_zoo/mapper/classification 路径获取。 而 hb_mapper checker 工具的主要参数如下,更多参数说明还请参考 验证模型 章节。

参数

说明

--model-type

用于指定检查输入的模型类型,目前只支持设置 caffe 或者 onnx

--march

用于指定需要适配的处理器类型,J5处理器请设置为 bayes (默认值)。

--proto

此参数仅在model-type指定caffe时有效,取值为caffe模型的 prototxt 文件名称; onnx模型无需配置该参数。

--model

在model-type被指定为caffe时,取值为Caffe模型的 caffemodel 文件名称; 在model-type被指定为onnx时,取值为 ONNX模型 文件名称。

以MobileNet-v1模型为例,您可以使用 01_check.sh 脚本快速完成模型验证。

01_check.sh 脚本文件中的主要内容如下所示:

set -ex
cd $(dirname $0) || exit
model_type="caffe"
proto="../../../01_common/model_zoo/mapper/classification/mobilenet/mobilenet_deploy.prototxt"
caffe_model="../../../01_common/model_zoo/mapper/classification/mobilenet/mobilenet.caffemodel"
march="bayes"

hb_mapper checker --model-type ${model_type} \
                  --proto ${proto} --model ${caffe_model} \
                  --march ${march}

如果模型验证不通过,请根据终端打印或在当前路径下生成的 hb_mapper_checker.log 日志文件确认报错信息和修改建议, 更多说明请参考 验证模型 章节。

5.2.3. 模型转换

模型验证通过后,就可以使用 hb_mapper makertbin 工具进行模型转换,参考命令如下:

hb_mapper makertbin --config mobilenet_config.yaml \
                    --model-type caffe

其中, mobilenet_config.yaml 为模型转换对应的配置文件,将在 Yaml配置文件 中进行介绍。 model-type 则用于指定检查输入的模型类型, 可配置为caffe或者onnx,不同模型类型对应的配置文件参数会稍有不同。

另外,PTQ方案的模型量化还需要依赖一定数量预处理后的样本进行校准,将在 校准数据预处理 中进行介绍。

5.2.3.1. Yaml配置文件

Yaml配置文件共包含4个必选参数组( model_parametersinput_parameterscalibration_parameterscompiler_parameters )和1个可选参数组( custom_op ), 每个参数组下也区分必选和可选参数(可选参数默认隐藏), 具体要求和填写方式可以参考 配置文件具体参数信息 章节。

注解

ONNX模型无需配置 model_parameters 参数组中的 caffe_modelprototxt 参数,而是替换为配置 onnx_model 参数。

  • input_parameters 参数组中的 input_type_rtinput_type_train 参数分别用于指定模型在板端实际部署时会接收到的数据类型(如nv12)和本身训练时的数据类型(如rgb)。

    当两种数据类型不一致时,转换工具会自动在模型前端插入一个能 BPU 加速的预处理节点,以完成对应的颜色空间转换。 同时,该参数组中的 norm_typemean_valuescale_value 参数还能用于配置图片输入模型的数据归一化操作,配置后转换工具也会将其集成进预处理节点实现BPU加速。数据归一化的计算公式为:

    \(data\_norm = (data - mean\_value) * scale\_value\)

  • calibration_parameters 参数组中的 cal_data_dir 参数需要配置预处理好的校准数据文件夹路径,预处理方式的说明请参考 校准数据预处理

5.2.3.2. 校准数据预处理

注意

  • 请注意,在进行此步之前,请确保您已经通过执行对应示例目录下的 00_init.sh 脚本完成了校准数据集的获取。

  • 如果当前只关注模型性能,那么可以将yaml文件中的 calibration_type 参数直接配置为 skip ,并跳过本小节, 工具会在模型转换时自动忽略 cal_data_dir 参数。

PTQ方案的校准数据一般是从训练集或验证集中筛选100份左右(可适当增减)的典型数据,并应避免非常少见的异常样本, 如纯色图片、不含任何检测或分类目标的图片等。筛选出的校准数据还需进行与模型inference前一致的预处理操作, 处理后保持与原始模型一样的数据类型( input_type_train )、layout ( input_layout_train )和尺寸( input_shape )。

对于校准数据的预处理,地平线建议直接参考示例代码进行修改使用。以MobileNet-v1模型为例,preprocess.py文件中 的calibration_transformers函数的包含了其校准数据的前处理 transformers,处理完的校准数据与其 yaml 配置文件保持一致,即:

  • input_type_train : ‘bgr’

  • input_layout_train :’NCHW’

def calibration_transformers():
    transformers = [
        ShortSideResizeTransformer(short_size=256),
        CenterCropTransformer(crop_size=224),
        HWC2CHWTransformer(),
        RGB2BGRTransformer(data_format="CHW"),
        ScaleTransformer(scale_value=255),
    ]
    return transformers

其中,transformers都定义在 ../../../01_common/python/data/transformer.py 文件中, 具体说明请参考 图片处理transformer说明 ,您可以按需选用或者自定义修改及扩展。

注意

需要注意的是,如果已经在yaml文件中配置了 mean_valuescale_value 参数进行数据归一化来启用BPU加速,那么应避免在此处的transformers中重复操作。

修改完preprocess.py文件后,即可修改02_preprocess.sh脚本并执行,以完成校准数据的预处理。

bash 02_preprocess.sh

02_preprocess.sh脚本文件的主要内容如下:

set -e -v
cd $(dirname $0) || exit

python3 ../../../data_preprocess.py \
  --src_dir ../../../01_common/calibration_data/imagenet \
  --dst_dir ./calibration_data_bgr \
  --pic_ext .bgr \
  --read_mode skimage \
  --saved_data_type float32

data_preprocess.py文件的传参说明如下:

  • src_dir为原始校准数据路径。

  • dst_dir为处理后数据的存放路径,可自定义。

  • pic_ext为处理后数据的文件后缀,主要用于帮助记忆数据类型,可不配置。

  • read_mode为图片读取方式,可配置为skimage或opencv。需要注意的是,skimage读取的图片类型为RGB, 数据范围为0-1,而opencv读取的图片类型为BGR,数据范围为0-255。

  • saved_data_type为处理后数据的保存类型。

如果您选择自行编写python代码实现校准数据预处理,那么可以使用 numpy.tofile 命令将其保存为float32格式的二进制文件, 工具链校准时会基于 numpy.fromfile 命令进行读取。

5.2.3.3. 转换模型

准备完校准数据和yaml配置文件后,即可一步命令完成模型解析、图优化、校准、量化、编译的全流程转换, 内部过程详解请参考 转换内部过程解读 章节。 以MobileNet-v1模型为例的模型转换参考命令如下:

hb_mapper makertbin --config mobilenet_config.yaml \
                    --model-type caffe

转换完成后,会在yaml文件配置的 working_dir 路径下保存各阶段流程产出的模型文件和编译器预估的模型BPU部分的静态性能评估文件, 详细说明请参考 转换产出物解读 章节。

|-- MOBILENET_subgraph_0.html        # 静态性能评估文件(可读性更好)
|-- MOBILENET_subgraph_0.json        # 静态性能评估文件
|-- cache.json                       # 缓存文件(优化等级optimize_level配置为O3场景下会自动生成)
|-- mobilenetv1_224x224_nv12.bin     # 用于在地平线计算平台上加载运行的模型
|-- mobilenetv1_224x224_nv12_calibrated_model.onnx
|-- mobilenetv1_224x224_nv12_optimized_float_model.onnx
|-- mobilenetv1_224x224_nv12_original_float_model.onnx
`-- mobilenetv1_224x224_nv12_quantized_model.onnx

5.2.4. 性能快速验证

针对转换生成的 xxx.bin 模型文件,地平线既支持先在开发机端预估模型BPU部分的的静态性能, 也在板端提供给了无需任何代码开发的可执行工具快速评测动态性能,以下将分别进行介绍。 更详细的说明和性能调优建议请参考 模型性能分析模型性能调优 章节。

5.2.4.1. 静态性能评估

转换模型 所述,模型成功转换后会在 working_dir 路径下生成包含模型静态性能预估的html和json文件,两者内容相同, 但html文件的可读性更好。以下为MobileNet-v1模型转换生成的html文件,其中:

  • Summary选项卡提供了编译器预估的模型BPU部分性能(不包含CPU算子性能预估)。

../../../_images/summary_tab.png
  • Temporal Statistics选项卡内则主要提供了模型一帧推理时间内的带宽占用情况。

../../../_images/temporal_statistics_tab.png
  • Layer Details选项卡提供了每一层BPU算子的计算量、原始算子输出shape、对齐后的算子输出shape、计算耗时、数据搬运耗时的信息以及编译后layer活跃时间段(不代表该layer执行时间,通常为多个layer交替/并行执行)。

../../../_images/layer_details_tab.png

针对 xxx.bin 模型,地平线在开发机环境还提供了 hb_perf 工具 重新生成静态性能预估文件。其使用方式如下, 详细说明请参考 使用hb_perf工具估计性能 章节。

hb_perf xxx.bin

当模型的静态性能已经不符合预期时,请参考 模型性能调优 章节进行性能调优。

5.2.4.2. 动态性能评估

当模型的静态性能符合预期后,我们可以进一步上板实测模型的动态性能,其参考方式如下:

1.首先请确保已按照 环境部署 章节完成开发板环境部署。

2.将转换生成的 xxx.bin 模型拷贝至开发板 /userdata 文件夹下任意路径。

3.通过 hrt_model_exec perf 工具快捷评估模型的耗时和帧率。

# 将模型拷贝至开发板
scp model_output/mobilenetv1_224x224_nv12.bin root@{board_ip}:/userdata

# 登录开发板评测性能
ssh root@{board_ip}
cd /userdata

# 单BPU核单线程串行状态下评测latency
hrt_model_exec perf --model_file mobilenetv1_224x224_nv12.bin --thread_num 1 --frame_count 1000

# 双BPU核多线程并发状态下评测FPS
hrt_model_exec perf --model_file mobilenetv1_224x224_nv12.bin --core_id 0 --thread_num 8 --frame_count 1000

hrt_model_exec 工具的主要参数说明如下,更多说明请参考 hrt_model_exec工具介绍 章节。

参数

类型

说明

model_file

string

[必选]模型文件路径

core_id

int

[可选]用于指定BPU运行核心,默认值为0。

0:任意核,预测库会根据负载情况自动分配调度。

1:core0。

2:core1。

thread_num

int

[可选]程序运行线程数,可选范围[1,8],默认值1。

frame_count

int

[可选]模型运行总帧数,默认值200。

profile_path

string

[可选]统计工具日志产生路径,运行产生profiler.log和profiler.csv,分析op耗时和调度耗时。

注解

  • 如果您在板端无法找到 hrt_model_exec 工具,可以再执行一次OE包中 ddk/package/board 路径下的 install.sh 脚本。

  • 评测Latency时一般采用单线程串行的推理方式,可以指定 thread_num 为 1。

  • 评测FPS时一般采用多线程并发的推理方式来占满BPU资源,此时可以配置 core_id 为 0,并配置 thread_num 为多线程。

  • 如果您配置了 profile_path 参数,程序需要正常运行结束才会生成 profiler.logprofiler.csv 日志文件,请勿使用 Ctrl+C 命令中断程序。

当模型的动态性能不符合预期时,请参考 模型性能调优 章节进行性能调优。

5.2.5. 精度验证

当模型的性能验证符合预期后,即可进行后续的精度验证。请首先确保您已经准备好相关的评测数据集,并挂载在Docker容器中。 示例模型使用的数据集可以参考 数据集下载 章节的介绍进行获取。

转换模型 所述,模型转换会生成 xxx_quantized_model.onnxxxx.bin 两个量化模型,两者输出是保持数值一致的。 您也可以在开发机环境使用 hb_verifier 工具进行一致性验证,参考命令如下,工具详细说明请参考 hb_verifier 工具 章节。

hb_verifier -m quanti.onnx,model.bin -b *.*.*.* -s True (-i 选填)

参数说明如下:

参数

说明

--model, -m

[必选]定点模型名称和bin模型名称,多模型之间用”,”进行区分。

--board-ip/-b

[可选]上板测试使用的arm board ip地址。

--run-sim/-s

[可选]设置是否使用X86环境的libdnn做bin模型推理,默认为False。

--input-img/-i

[可选]指定推理测试时使用的图片。若不指定则会使用随机生成的tensor数据。 若指定图片为二进制形式的图片文件,其文件形式需要为后缀名为 .bin 形式。

多输入模型添加图片的方式有以下两种传参方式,多张图片之间用”,”分割:

  • input_name1:image1,input_name2:image2, …

  • image1,image2…

相比于 xxx.bin ,地平线更建议优先在开发机Python环境评测 xxx_quantized_model.onnx 模型的量化精度,其评测方式更加简单快捷, 具体请见 开发机Python环境验证xxx.bin 在板端基于C++代码的评测说明请见 开发板C++环境验证 , 更详细的精度验证和优化建议请参考 模型精度分析模型精度优化 章节。

5.2.5.1. 开发机Python环境验证

以 MobileNet-v1 模型为例,mobilenetv1_224x224_nv12_quantized_model.onnx量化模型的单张推理和验证集精度评测示例请参考示例目录中的 04_inference.sh05_evaluate.sh 脚本,参考命令如下:

# 测试量化模型单张图片推理结果
bash 04_inference.sh

# 测试浮点模型单张图片推理结果(可选)
bash 04_inference.sh origin

# 测试量化模型精度,请确保您的评测数据集已正确挂载在Docker容器中
bash 05_evaluate.sh

# 测试浮点模型精度(可选)
bash 05_evaluate.sh origin

两个脚本会分别调用 ../../cls_inference.py../../cls_evaluate.py 文件进行推理, 以 cls_inference.py 文件为例,代码中的主要接口使用逻辑如下:

from horizon_tc_ui import HB_ONNXRuntime
from preprocess import infer_image_preprocess
from postprocess import postprocess

def inference(sess, image_name, input_layout):
  if input_layout is None:
        input_layout = sess.layout[0]
    # 前处理
    image_data = infer_image_preprocess(image_name, input_layout)
    input_name = sess.input_names[0]
    output_names = sess.output_names
    # 模型推理
    output = sess.run(output_names, {input_name: image_data})
    # 后处理
    top_five_label_probs = postprocess(output)

def main(model, image, input_layout):
    sess = HB_ONNXRuntime(model_file=model)
    sess.set_dim_param(0, 0, '?')
    inference(sess, image, input_layout)

if __name__ == '__main__':
    main()

其中,infer_image_preprocess函数的前处理操作来源于 校准数据预处理 章节所述preprocess.py文件, 相比于calibration_transformers函数会额外增加 input_type_rtinput_type_train (参数说明请见 Yaml配置文件 章节)颜色空间的转换来对齐模型实际部署时的输入数据类型,具体代码如下:

def infer_transformers(input_layout="NHWC"):
    transformers = [
        ShortSideResizeTransformer(short_size=256),
        CenterCropTransformer(crop_size=224),
        RGB2BGRTransformer(data_format="HWC"),
        ScaleTransformer(scale_value=255),
        BGR2NV12Transformer(data_format="HWC"),
        NV12ToYUV444Transformer((224, 224),
                                yuv444_output_layout=input_layout[1:]),
    ]
    return transformers

def infer_image_preprocess(image_file, input_layout):
    transformers = infer_transformers(input_layout)
    image = SingleImageDataLoader(transformers,
                                  image_file,
                                  imread_mode='skimage')
    return image

需要注意的是, xxx.bin 模型对于 input_type_rtinput_type_train 颜色空间的转换会配合处理器硬件完成。 而模型转换生成的几个ONNX模型前端插入的预处理节点不包含硬件转换逻辑,所以其实际输入只是一种中间类型, 对应硬件对 input_type_rt 类型的处理结果。下表为每种 input_type_rt 数据类型对应的中间类型。 以MobileNet-v1模型为例,其 input_type_rt 配置为nv12,此处transformers中就会从BGR处理成NV12, 再处理成YUV444。

input_type_rt

nv12

yuv444

rgb

bgr

gray

featuremap

中间类型

yuv444_128

yuv444_128

RGB_128

BGR_128

GRAY_128

featuremap

注解

_128表示数据会减去128,由uint8类型转换为int8类型。在不涉及数据损失的转换场景下,该转换可通过HB_ONNXRuntime内部完成, 如涉及混合类型输入等会出现数据损失的转换,需先自行完成对应数据转换的处理,再进行推理。

5.2.5.2. 开发板C++环境验证

在开发板端,地平线也提供了一套适配所有硬件平台的嵌入式端预测库LibDNN,来帮助您快速完成模型的部署工作, 并提供了相关示例。您可以首先参考 模型部署 章节学习模型部署和BPU SDK API接口的基础使用, 再参考 AI-Benchmark 章节学习示例模型精度评测的完整代码框架。

5.2.5.2.1. 模型部署

OE包提供了模型部署的基础示例,以便于您学习LibDNN预测库API接口的使用方式, 示例的详细说明可以参考 基础示例包使用说明 章节。

注意

请注意,在您进行模型部署前,需要先获取上板使用的模型:

  • ddk/samples/ai_toolchain/model_zoo/runtime/ai_benchmark 目录下, 执行 resolve_ai_benchmark_ptq.shresolve_ai_benchmark_qat.sh 脚本。

  • ddk/samples/ai_toolchain/model_zoo/runtime/horizon_runtime_sample 目录下, 执行 resolve_runtime_sample.sh 脚本。

其中,示例目录下的 code/00_quick_start/src/run_mobileNetV1_224x224.cc 文件提供了MobileNet-v1模型从DDR读取数据到模型推理,再执行后处理出分类结果的完整流程代码。 从摄像头输入的全链路示例可以参考 ddk/samples/ai_forward_view_sample

run_mobileNetV1_224x224.cc中的主要代码逻辑包括以下6个步骤, 代码中所涉及的API接口的具体说明可以参考 BPU SDK API手册 章节。

1.加载模型,获取模型句柄。

2.准备模型输入输出tensor,申请对应的BPU内存空间。

3.读取模型输入数据,并放入申请好的输入tensor中。

4.推理模型,获取模型输出。

5.基于输出tensor中的数据实现模型后处理。

6.释放相关资源。

int main(int argc, char **argv) {

  // Step1: get model handle
  {
    hbDNNInitializeFromFiles(&packed_dnn_handle, &modelFileName, 1);
    hbDNNGetModelNameList(&model_name_list, &model_count, packed_dnn_handle);
    hbDNNGetModelHandle(&dnn_handle, packed_dnn_handle, model_name_list[0]);
  }

  // Step2: prepare input and output tensor
  std::vector<hbDNNTensor> input_tensors;
  std::vector<hbDNNTensor> output_tensors;
  int input_count = 0;
  int output_count = 0;
  {
    hbDNNGetInputCount(&input_count, dnn_handle);
    hbDNNGetOutputCount(&output_count, dnn_handle);
    input_tensors.resize(input_count);
    output_tensors.resize(output_count);
    prepare_tensor(input_tensors.data(), output_tensors.data(), dnn_handle);
  }

  // Step3: set input data to input tensor
  {
    // read a single picture for input_tensor[0], for multi_input model, you
    // should set other input data according to model input properties.
    read_image_2_tensor_as_nv12(FLAGS_image_file, input_tensors.data());
  }

  // Step4: run inference
  {
    // make sure memory data is flushed to DDR before inference
    for (int i = 0; i < input_count; i++) {
      hbSysFlushMem(&input_tensors[i].sysMem[0], HB_SYS_MEM_CACHE_CLEAN);
    }

    hbDNNInferCtrlParam infer_ctrl_param;
    HB_DNN_INITIALIZE_INFER_CTRL_PARAM(&infer_ctrl_param);
    hbDNNInfer(&task_handle,
               &output,
               input_tensors.data(),
               dnn_handle,
               &infer_ctrl_param);
    // wait task done
    hbDNNWaitTaskDone(task_handle, 0);
  }

  // Step5: do postprocess with output data
  std::vector<Classification> top_k_cls;
  {
    // make sure CPU read data from DDR before using output tensor data
    for (int i = 0; i < output_count; i++) {
      hbSysFlushMem(&output_tensors[i].sysMem[0], HB_SYS_MEM_CACHE_INVALIDATE);
    }

    get_topk_result(output, top_k_cls, FLAGS_top_k);
    for (int i = 0; i < FLAGS_top_k; i++) {
      VLOG(EXAMPLE_REPORT) << "TOP " << i << " result id: " << top_k_cls[i].id;
    }
  }

  // Step6: release resources
  {
    // release task handle
    hbDNNReleaseTask(task_handle);
    // free input mem
    for (int i = 0; i < input_count; i++) {
      hbSysFreeMem(&(input_tensors[i].sysMem[0]));
    }
    // free output mem
    for (int i = 0; i < output_count; i++) {
      hbSysFreeMem(&(output_tensors[i].sysMem[0]));
    }
    // release model
    hbDNNRelease(packed_dnn_handle);
  }

  return 0;
}

该示例运行的参考方式如下:

# 开发机环境执行交叉编译,生成可执行程序
cd open_explorer/ddk/samples/ai_toolchain/horizon_runtime_sample/code
bash build_j5.sh

# 拷贝j5目录至板端
mkdir ../j5/runtime
scp -r ../j5/ root@{board_ip}:/userdata
# 拷贝模型文件至板端
scp -r ../../model_zoo/runtime/horizon_runtime_sample/mobilenetv1/ root@{board_ip}:/userdata/j5/model/runtime

# 登录开发板环境
ssh root@{board_ip}
# 进入j5/script/目录下,执行相应运行脚本即可
cd /userdata/j5/script/00_quick_start/
bash run_mobilenetV1.sh

5.2.5.2.2. AI-Benchmark

OE包在 ddk/samples/ai_benchmark 路径下还提供了典型分类、检测、分割、光流示例模型板端性能和精度评测的示例包,您可以基于这些示例进行进一步的应用开发。 更多说明您可参考 AI-Benchmark使用说明 章节。

5.2.6. 应用开发

当模型的性能和精度验证都符合预期后,即可参考 嵌入式应用开发指导 章节实现上层应用的具体开发。