1. 部署挑战
将深度学习网络从培训环境部署到嵌入式平台进行推理可能是一项复杂的任务,它带来了许多必须解决的技术挑战:
- 行业内有许多广泛使用的深度学习框架,如:Caffe, TensorFlow, MXNet, Kaldi等。
- 通常,深度学习网络的训练在数据中心或服务器群中执行,而推理可能在嵌入式平台上进行,针对性能和功耗进行了优化。这些平台通常受限于软件(编程语言,第三方依赖,内存消耗,支持的操作系统),和硬件(不同的数据类型,有限的功率范围),因此通常不推荐(有时甚至不可能) 使用原始训练框架进行推理。
- 部署过程的其他复杂性包括支持各种层类型和网络正变得越来越复杂。显然,确保变换网络的准确性并非易事。
2. 部署工作流程
该过程假定您使用受支持的框架之一训练了网络模型。下面的方案说明了部署典型的深度学习模型的工作流程:
工作流程:
- 配置指定框架的模型优化器。
- 运行模型优化器,根据经过训练的网络拓扑,权重和偏差值以及其他可选参数,生成模型的优化中间表示(IR)。
- 使用推理引擎示例程序在目标环境中以IR格式测试模型。
- 在应用程序中集成推理引擎,以在目标环境中部署模型。
3. 模型优化器
模型优化器是一个跨平台的命令行工具,可促进培训和部署环境之间的转换,执行静态模型分析并自动调整深度学习模型,以便在端点目标设备上实现最佳执行。
模型优化器支持多种深度学习框架和格式。运行模型优化器时你不需要考虑目标平台的类型,因为相同的输出(MO)可以运用于所有的目标设备。
3.1 模型优化器工作流程
此过程假定您使用受支持的框架之一训练了网络模型。模型优化器的工作流程如下:
- 针对训练模型的深度学习框架配置模型优化器
- 提供包含特定网络拓扑,调整后的权重和偏差(带有一些可选参数)的训练网络作为输入。
- 运行模型优化器来执行指定的模型优化。精确优化是针对特定框架的,可参照相应文档。
- 模型优化器生成网络的中间表示(IR)作为输出,该网络用作所有目标上的推理引擎的输入。该IR是一对用于描述真个模型的文件:
- .xml:拓扑文件 - 描述网络拓扑
- .bin :训练的数据文件 - 包含权重和偏差的二进制数据
3.2 模型优化器是如何工作的
模型优化器加载一个模型到内存中,然后读取它生成一个内部表示(internal representation)并优化它,最后产生中间表示(Intermediate Representation)。IR是推理引擎唯一识别的形式。
模型优化器有两个目的:
- 产生一个有效的IR。
如果这个主要的中间文件无效,那么推理引擎将无法工作。模型优化器的主要责任就是生成两个文件(.xml 和 .bin)来描述IR。 - 产生一个经过优化的IR
预训练模型包含对训练很重要的层,例如Dropout层。这些层在推理期间是无用的,并且可能会增加推理时间。在许多情况下,可以从生成的中间表示中自动删除这些层。但是,如果一组图层可以表示为一个数学运算,因此可以表示为单个图层,则模型优化程序会识别这些图案并用唯一的图层替换这些图层。结果IR具有比原始模型更少的层。这样就减少了推理时间。
为了生成有效的IR,模型优化器必须能够读取原始模型层,处理它们的属性并以IR格式表示它们,同时保持生成的IR的有效性。
3.3 对于模型你需要知道什么
在已知的框架和神经网络拓扑中存在许多共同的层。比如:Convolution, Pooling和Activation。要读取原始模型并生成模型的IR,模型优化程序必须能够使用这些层。
模型优化器已支持的框架层见:Supported Framework Layers
如果你使用的拓扑层不在上述列表内,请参考Custom Layers in the Model Optimizer来优化模型。
3.4 模型优化器的目录结构
所有的OpenVINO或DLDT工具包中,模型优化器的目录结构都一样:
|-- model_optimizer
|-- extensions
|-- front - Front-End framework agnostic transformations (operations output shapes are not defined yet).
|-- caffe - Front-End Caffe-specific transformations and Caffe layers extractors
|-- CustomLayersMapping.xml.example - example of file for registering custom Caffe layers (compatible with the 2017R3 release)
|-- kaldi - Front-End Kaldi-specific transformations and Kaldi operations extractors
|-- mxnet - Front-End MxNet-specific transformations and MxNet symbols extractors
|-- onnx - Front-End ONNX-specific transformations and ONNX operators extractors
|-- tf - Front-End TensorFlow-specific transformations, TensorFlow operations extractors, sub-graph replacements configuration files.
|-- middle - Middle-End framework agnostic transformations (layers output shapes are defined).
|-- back - Back-End framework agnostic transformations (preparation for IR generation).
|-- mo
|-- back - Back-End logic: contains IR emitting logic
|-- front - Front-End logic: contains matching between Framework-specific layers and IR specific, calculation of output shapes for each registered layer
|-- graph - Graph utilities to work with internal IR representation
|-- middle - Graph transformations - optimizations of the model
|-- pipeline - Sequence of steps required to create IR for each framework
|-- utils - Utility functions
|-- tf_call_ie_layer - Source code that enables TensorFlow fallback in Inference Engine during model inference
|-- mo.py - Centralized entry point that can be used for any supported framework
|-- mo_caffe.py - Entry point particularly for Caffe
|-- mo_kaldi.py - Entry point particularly for Kaldi
|-- mo_mxnet.py - Entry point particularly for MXNet
|-- mo_onnx.py - Entry point particularly for ONNX
|-- mo_tf.py - Entry point particularly for TensorFlow
3.5 配置模型优化器
脚本自动配置
可以使用脚本一次配置所有支持的框架的模型优化器,也可以只针对某一个框架进行优化,配置脚本在:"<INSTALL_DIR>/deployment_tools/model_optimizer/install_prerequisites"
,Linux下通常是/opt/intel/deployment_tools/model_optimizer/install_prerequisites
运行"install_prerequisites.sh"
手动配置
- 进入模型优化器的目录
cd <INSTALL_DIR>/deployment_tools/model_optimizer/
- 创建虚拟化环境
这一步不是必须的,但Intel官方文档强烈建议这么做,因为这样不会影响到全局的Python环境。
# 创建虚拟环境
virtualenv -p /usr/bin/python3.6 .env3 --system-site-packages
# 激活虚拟环境
virtualenv -p /usr/bin/python3.6 .env3/bin/activate
- 安装依赖文件
# 安装所有框架的依赖
pip3 install -r requirements.txt
#安装指定框架的依赖
pip3 install -r requirements_xxx.txt
使用protobuf库配置caffe的模型优化器
参考Configuring the Model Optimizer
3.6 将模型转换为IR
简单默认转换
可以在模型目录下通过mo.py脚本来进行转换
cd <INSTALL_DIR>/deployment_tools/model_optimizer
# 简单转换所有支持框架的模型
python3 mo.py --input_model INPUT_MODEL
#For example:
python3 mo.py --input_model /user/models/model.pb
python3 mo.py --framework tf --input_model /user/models/model.pb
4. 推理引擎
推理引擎是一个运行时库,它提供统一的API以将推理与应用程序逻辑集成:
- 作为模型的输入。该模型以模型优化器生成的中间表示(IR)的特定形式呈现。
- 针对目标硬件的优化推理执行。
- 在嵌入式推理平台上提供减少工作流程的推理解决方案。
推理引擎支持多个图像分类网络的推断,包括AlexNet,GoogLeNet,VGG和ResNet系列网络,完全卷积网络(如用于图像分割的FCN8)和物体检测网络(如Faster R-CNN)。
推理引擎软件包包含headers, 运行时库和控制台应用程序,演示了如何在应用程序中使用推理引擎。
4.1 推理引擎的组件
核心库
libinference_engine.so,windos下为inference_engine.dll
该库以C++语言编写,包含下列类:
读取网络: InferenceEngine::CNNNetReader
处理网络信息: InferenceEngine::CNNNetwork
创建推理引擎核心对象以使用设备: InferenceEngine::Core
执行并传递输入输出: InferenceEngine::ExecutableNetwork and InferenceEngine::InferRequest
特定设备的插件库
针对每一个支持的目标设备,推理引擎提供一个插件(动态或共享库),其中包含对该设备进行推理的完整实现。如下:
PLUGIN | 库名称For Linux | 依赖库 For Linux |
---|---|---|
CPU | libMKLDNNPlugin.so | libmklml_tiny.so, libiomp5md.so |
GPU | libclDNNPlugin.so | libclDNN64.so |
FPGA | libdliaPlugin.so | libdla_compiler_core.so, libdla_runtime_core.so |
MYRIAD | libmyriadPlugin.so | No dependencies |
HDDL | libHDDLPlugin.so | libbsl.so, libhddlapi.so, libmvnc-hddl.so |
GNA | libGNAPlugin.so | libgna_api.so |
HETERO | libHeteroPlugin.so | Same as for selected plugins |
MULTI | libMultiDevicePlugin.so | Same as for selected plugins |
注:WindowsOS下库及依赖库名称相同,只是前缀没有lib以及后缀改为.dll。
需确保所有的库及依赖库都添加到了环境变量里。可使用setupvars.sh来设置环境变量。
使用推理引擎API的常用流程
-
读取IR
使用InferenceEngine::CNNNetReader
类读取IR文件到目标类InferenceEngine::CNNNetwork
中。这个类表明网络已经加载到内存中。 -
准备输入输出格式
加载网络后,指定输入和输出精度以及网络上的布局。使用InferenceEngine::CNNNetwork::getInputsInfo()
和InferenceEngine::CNNNetwork::getOutputsInfo()
. -
创建推理引擎核心对象
创建对象以工作在不同的设备上,所有设备插件都有Core对象在内部管理。通过每个设备加载特定的配置(InferenceEngine::Core::SetConfig
),并注册此设备的扩展(InferenceEngine::Core::AddExtension
)。 -
编译并加载网络到设备
将(InferenceEngine::Core::LoadNetwork()
)方法与特定设备(例如CPU,GPU等)一起使用,以在设备上编译和加载网络。传递每个目标的负载配置以进行此编译和加载操作。 -
设置输入数据
加载网络后,可以使用对象(InferenceEngine::ExecutableNetwork
)创建InferenceEngine :: InferRequest
,在其中用信号通知输入缓冲区以用于输入和输出。指定设备分配的内存并直接将其复制到设备内存中,或者告诉设备使用应用程序内存来保存副本。 -
执行
定义输入和输出内存后,选择执行模式:
同步 - 使用InferenceEngine::InferRequest::Infer()
方法,程序会阻塞直到推理结束。
异步模式 - 使用InferenceEngine::InferRequest::StartAsync()
方法。可以通过InferenceEngine::InferRequest::Wait()
来检查状态。 -
获取输出
推理结束后,获取输出内存或去读先前提供的内存。使用InferenceEngine::IInferRequest::GetBlob()
。
4.2 推理引擎内存原语
Blobs
InferenceEngine::Blob
是操作内存的主要的类。通过这个类可以读写内存,获取内存结构体信息等。
针对特定布局创建Blob对象的方法是使用带有
使用特定布局创建Blob对象的正确方法是使用带有InferenceEngine :: TensorDesc的构造函数。
如:
InferenceEngige::TensorDesc tdesc(FP32, {1, 3, 227, 227}, InferenceEngine::Layout::NCHW);
InferenceEngine::Blob::Ptr blob = InferenceEngine::make_shared_blob<float>(tdesc);
布局
InferenceEngine :: TensorDesc
是一个提供布局格式描述的特殊类。
这个类允许使用标准格式创建平面布局,也可以使用InferenceEngine :: BlockingDesc
创建非平面布局。
如果要创建复杂的布局,可以使用InferenceEngine :: BlockingDesc
,它允许使用偏移和strides来定义被阻塞的内存。
示例
- 使用参数 {N: 1, C: 25, H: 20, W: 20} 定义一个blob
InferenceEngine::BlockingDesc({1, 20, 20, 25}, {0, 2, 3, 1}); // or
InferenceEngine::BlockingDesc({1, 20, 20, 25}, InferenceEngine::Layout::NHWC);
- 如果内存的实际参数为 {N: 1, C: 25, H: 20, W: 20}但是被限制在8通道,那么可以使用下一个参数定义它
InferenceEngine::BlockingDesc({1, 4, 20, 20, 8}, {0, 1, 2, 3, 1})
-
如果布局内包含,也可以设置strides和偏移
-
如果有一个复杂的blob布局,又不想计算数据的实际偏移量,则可以使用方法
InferenceEngine::TensorDesc::offset(size_t l) 或 InferenceEngine::TensorDesc::offset(SizeVector v).
InferenceEngine::BlockingDesc blk({1, 4, 20, 20, 8}, {0, 1, 2, 3, 1});
InferenceEngine::TensorDesc tdesc(FP32, {1, 25, 20, 20}, blk);
tdesc.offset(0); // = 0
tdesc.offset(1); // = 8
tdesc.offset({0, 0, 0, 2}); // = 16
tdesc.offset({0, 1, 0, 2}); // = 17
- 如果想创建一个平面格式的TensorDesc和N维(N可以是不同的1,2,4等),可以使用方法
InferenceEngine :: TensorDesc :: getLayoutByDims
。
InferenceEngine::TensorDesc::getLayoutByDims({1}); // InferenceEngine::Layout::C
InferenceEngine::TensorDesc::getLayoutByDims({1, 2}); // InferenceEngine::Layout::NC
InferenceEngine::TensorDesc::getLayoutByDims({1, 2, 3, 4}); // InferenceEngine::Layout::NCHW
InferenceEngine::TensorDesc::getLayoutByDims({1, 2, 3}); // InferenceEngine::Layout::BLOCKED
InferenceEngine::TensorDesc::getLayoutByDims({1, 2, 3, 4, 5}); // InferenceEngine::Layout::NCDHW
InferenceEngine::TensorDesc::getLayoutByDims({1, 2, 3, 4, 5, ...}); // InferenceEngine::Layo
参考链接
https://docs.openvinotoolkit.org/2019_R2/_docs_IE_DG_Introduction.html