最近因为项目需要,对intel openVINO的源码进行了解,以便为后面移植开发做准备。

OpenVINO的源码在opencv的github主页上可以找到,最新的opencv 4.1.2已经全新支持了OpenVINO,意味着一个新的平台即将展开,并且在嵌入式领域,边缘计算等场景,movidius大有赶超英伟达的趋势,因特尔在AI上的发力今后几年不可小看,很有可能在今后几年内占有一席之地。

废话不多说,OpenVINO的github源码地址为:https://github.com/opencv/dldt

由于openvino是18年刚刚出来,目前针对openvino源码分析还没有官方渠道,因特尔开发openvino工具包本意是将各种硬件平台进行封装操作,达到用户拿来就可以直接用,一般不需要注意里面细节,因为后面项目需要做移植,所以需要对openvino源码进行一定了解。

Class Core

core是interference Engine的管理核心,一般使用openvino之前都需要先创建一个core,负责其中的设备管理,网络下载等等初始化造成,是整个interference Engine的核心。

core类头文件位于\inference-engine\include\ie_core.hpp 

cpp文件位于:inference-engine\src\inference_engine\ie_core.cpp

Core构造函数

core构造函数是整个openvino的程序的入口,主要功能是负责openvino中所支持的硬件设备类型注册(主要包括CPU,GPU, Modividus,VPU等),源码如下:

主要分三步:

  • 获取Impl类地址
  • 获取支持的plugins.xml配置文件
  • 根据plugins.xml配置文件获取支持的设备类型进行注册

获取Imple类地址

OpenVINO源码中大量使用了将具体实施的类和接口分类的模式,core类只是定义了一个接口类,类core类的具体实施的类是在Impl类中,首先保存其Imp类的地址到_impl变量中,方便以后调用。这样设计的好处就是能够将接口与实现分类,减少模块之间的耦合,Imple类的实现还是在inference-engine\src\inference_engine\ie_core.cpp文件中。

涉及到一个OpenVINO的编码规范,在类中的变量命名规则为在变量名前面要加"_",这一点和caffe变量命名规则不一样,请注意。

获取支持的plugins.xml配置文件

Core类的构造函数可以带一个xmlConfigFile配置文件参数,该配置文件就是openvino支持的设备类型配置xml文件,如果该参数没有配置(大部分情况下都是不配置的,使用默认配置),使用默认配置文件plugins.xml,该配置文件一般都是在安装的openvino的lib路径下,是和libinference_engine.so等lib放在同一个路径下面,如果不放在同一路径下面将会出错

下面是官方安装之后的plugins.xml文件配置,和安装时选择的支持设备类型有关:

<ie>
    <plugins>
        <plugin name="GNA" location="libGNAPlugin.so">
        </plugin>
        <plugin name="HDDL" location="libHDDLPlugin.so">
        </plugin>
        <plugin name="CPU" location="libMKLDNNPlugin.so">
        </plugin>
        <plugin name="GPU" location="libclDNNPlugin.so">
        </plugin>
        <plugin name="FPGA" location="libdliaPlugin.so">
        </plugin>
        <plugin name="MYRIAD" location="libmyriadPlugin.so">
        </plugin>
    </plugins>
</ie>

name为支持的设备类型名称,有GPU,CPU,MYRIAD等,location代表的时支持该设备所对应的lib,

各个设备所对应的lib可以查看《学习OpenVINO笔记之Inference Engine》

 代码中是怎样找到这些路径的?接下来需要查看getIELibraryPath()函数实现

 linux操作系统下 调用一个系统函数dladdr(), 用来获取动态库中getIELibraryPath函数的相关信息,其信息结构为Dl_info,其成员结构为:

struct {
const char *dli_fname;
void *dli_fbase;
const char *dli_sname;
void *dli_saddr;
size_t dli_size; /* ELF only */
int dli_bind; /* ELF only */
int dli_type;
};

其中dli_fname为该函数符号位于的lib名称(包含其路径信息),而 getIELibraryPath函数是位于libinference_engine.so中,获取的dli_fname为libinference_engine.so名称及路径,这样通过getPathName解析出路径名(libGNAPlugin.so这些lib都是以及plugins.xml等文件和libinference_engine.so放在一个路径),这样就可以获取到plugins.xml文件路径名,这是一个实现的小技巧

最后将获取到的路径名和plugins.xml拼接成配置文件名,进行下步处理。

根据plugins.xml配置文件获取支持的设备类型进行注册

接下来就是调用RegisterPlugins()函数读取xml中的信息,将其注册进去,

可以看到最后是调用的Imp类RegisterPluginsInRegistry接口

RegisterPluginsInRegistry

RegisterPluginsInRegistry接口代码分步骤来看:

OpenVINO解析xml文件使用的是开源第三方软件pugixml,调用load_file()加载并解析xml配置文件到xmlDoc,并检查其返回值res.status,是否成功,如果失败则直接扔出异常打断言。

读取成功之后,就要获取xml配置文件:

解析xml里面的节点,上述代码不在一一解释,每个设备类型挂载一个<plugin >解析,获取到每个plugin节点下的name以及location,还有extension等信息,将其注册到pluginRegistry中,以供后面查看使用。

上述xml文件格式,官方有明确解释:https://docs.openvinotoolkit.org/latest/classInferenceEngine_1_1Core.html

 name为所支持的设备类型,location为所对应的设备类型的lib. Properties为所对应的该设备类型的一些属性,Externsion为该设备类型的外挂第三方的一些库。

总结

Core的构造函数比较简单,本质上就是将plugin.xml文件的所支持的设备给注册上去,但是真是的环境中是否有这个设备目前并没有涉及到。

SetConfig()

SetConfig()函数为设置设备的某些属性值,该API第一个参数为设置的属性及值,其类型为一个map, std::map<std::string, std::string>, 第一个为属性名称,第二个为属性值。

首先解析其入参deviceName_,是否带有device id,如有带id其格式为:

<devicename><.><deviceid> 

 接下来调用Impl类的SetConfigForPlugins()函数

SetConfigForPlugins

将设置的属性保存到所对应的pluginRegistry和plugins中,其中pluginRegistry为所支持的所有设备类型,而plugins是真正运行所创建的plugins。

 支持config配置项可以从ie_plugin_config.h头文件可以看到,官方链接:https://docs.openvinotoolkit.org/latest/ie__plugin__config_8hpp.html

LoadNetwork 

loadNetwork是整个OpenVINO的实施的关键,负责将IR下载到movidius中,整个loadNetwork的流程调用比较复杂,里面涉及了很多C++中的编程技巧。首先来看下入口源码:

入口函数比较简单,就是先对特殊的设备类型进行检查,如何deviceName是“HETERO:"则将冒号去掉,解析出deviceName中的device name和dedevice id,再次看到OpenVINO的另外一个编程规范,局部变量后面要加上"_".

最后会调用Imple类的GetCPPPluginByName,获取到相应的设备类型创建的Plugin,最后调用LoadNetwork下载网络,再次看到比较重要的一个概念Plugin,OpenVINO为每个设备类型创建相对应的Plugin,将设备抽象为具体的Plugin进行处理,这是OpenVINO的一个管理核心 

下一步比较关键是LoadNetwork中实施比较关键的一步,调用过程比较繁琐:

将上述过程安装六个步骤分析

第一步

再次看到了plugins这个东东,主要是记录以及创建的plugin.如果已经创建则不再创建。

第二步

加载相对应的设备类型的动态lib,并获取lib中的CreatePluginEngine函数接口地址,这么复杂的操作,被完全藏在了SOPointer的构造函数中,InferenceEnginePluginPtr定义如下:

 最后是调用SOPointer的构造函数:

该函数仅有短短几句话,但是包含的信息比较多。

首先调用创建一个Loader类:

 Loader类在此主要指的是SharedObjectLoader类,该类主要是加载一个共享库

猜的没错,调用dlopen加载打开一个共享库,并返回其句柄指针。

将共享库的句柄指针保存在_so_loader中。

 接着查找该共享库的CreatePluginEngine函数地址:

 _pointedObj(details::shared_from_irelease(
            SymbolLoader<Loader>(_so_loader).template instantiateSymbol<T>(SOCreatorTrait<T>::name)

SOCreatorTrait<T>::name的定义如下:

为 CreatePluginEngine函数名, instantiateSymbol()函数定义如下:

最后调用bind_function()函数:

 获取到CreatePluginEngine在动态库的地址。

第二步骤的主要作用就是从对应的设备so中找到CreatePluginEngine()函数地址,由此可以看到每个设备对应的so都有一个CreatePluginEngine函数,该函数是其对应的主要入口,具体每个设备对应的相应的处理函数后面再详细描述。

第三步

根据获取到的CreatePluginEngine()函数地址,运行该函数生成一个相对应InferencePlugin, Plugin是整个OpenVINO的管理核心。

第四步

获取到相对应InferencePlugin中的主要API, IInferencePluginAPI该API是一个通用的API,是实际设备具体的执行

IInferencePluginAPI类接口

API名称作用
virtual void SetName(const std::string & pluginName) 设置Plugin name
virtual std::string GetName()获取Plugin name
virtual void SetCore(ICore *core)设置Plugin中的Core
virtual const ICore& GetCore() 获取Plugin中的Core
virtual Parameter GetConfig(const std::string& name, const std::map<std::string, Parameter> & options)获取Plugin中的配置
virtual Parameter GetMetric(const std::string& name, const std::map<std::string, Parameter> & options) 获取Plugin中的Metric

该类中的函数都是纯虚函数,具体的实现都是在各自的so中实现。

第五步

根据获取到的API,设置相对应的配置,如Core、Extension之类的,并生成一个新的InferencePlugin类

第六步

将该plugin对应的InferencePlugin类保存在plugins中。

最后将获取到InferencePlugin中调用LoadNetwork加载网络。

遗留两个问题需要待下一步进行剥洋葱

1:Myriad的CreatePluginEngine()实现

2:LoadNetwork()具体实现

 

 

 

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐