OpenHarmony AI 业务子系统
整体代码信息
- Version : code-v3.1-Beta
目录名 | 描述 |
---|---|
applications | 应用程序样例,包括camera等 |
base | 基础软件服务子系统集&硬件服务子系统集 |
build | 组件化编译、构建和配置脚本 |
docs | 说明文档 |
domains | 增强软件服务子系统集 |
drivers | 驱动子系统 |
foundation | 系统基础能力子系统集 |
kernel | 内核子系统 |
prebuilts | 编译器及工具链子系统 |
test | 测试子系统 |
third_party | 开源第三方组件 |
utils | 常用的工具集 |
vendor | 厂商提供的软件 |
build.py | 编译脚本文件 |
关于目前的状态
实际模型推理的逻辑,以及支持新的推理引擎
-
例子中使用的模型: wk
- 如何将训练得到的模型,导出成该格式?–> 该格式是华为硬件平台对应的模型格式。将模型转为NNIE框架支持的wk模型——以tensorflow框架为例 - 知乎 (zhihu.com)
- 每个算法sdk 封装成一个插件 plugin 的方式,实现i_plugin.h 的接口
- Plugin 需要实现的接口
class IPlugin {public: virtual ~IPlugin() = default; virtual const long long GetVersion() const = 0; virtual const char *GetName() const = 0; /** * Get plugin inference mode. * * @return Inference mode, synchronous or asynchronous. */ virtual const char *GetInferMode() const = 0; /** * Algorithmic inference interface for synchronous tasks. * * @param [in] request Request task which contains the specific information of the task. * @param [out] response Results of encapsulated algorithmic inference. * @return Returns 0 if the operation is successful, returns a non-zero value otherwise. */ virtual int SyncProcess(IRequest *request, IResponse *&response) = 0; /** * Algorithmic inference interface for asynchronous tasks. * * @param [in] request Request task which contains the specific information of the task. * @param [in] callback Callback which is used to return the result of asynchronous inference. * @return Returns 0 if the operation is successful, returns a non-zero value otherwise. */ virtual int AsyncProcess(IRequest *request, IPluginCallback *callback) = 0; /** * Initialize plugin. * * @param [in] transactionId Transaction ID. * @param [in] inputInfo Data information needed to initialize plugin. * @param [out] outputInfo The returned data information of initializing plugin. * @return Returns 0 if the operation is successful, returns a non-zero value otherwise. */ virtual int Prepare(long long transactionId, const DataInfo &inputInfo, DataInfo &outputInfo) = 0; /** * Unload model and plugin. * * @param [in] isFullUnload Whether to unload completely. * @param [in] transactionId Transaction ID. * @param [in] inputInfo Data information needed to unload model and plugin. * @return Returns 0 if the operation is successful, returns a non-zero value otherwise. */ virtual int Release(bool isFullUnload, long long transactionId, const DataInfo &inputInfo) = 0; /** * Set the configuration parameters of the plugin. * * @param [in] optionType The type of setting option. * @param [in] inputInfo Configuration parameter needed to set up the plugin. * @return Returns 0 if the operation is successful, returns a non-zero value otherwise. */ virtual int SetOption(int optionType, const DataInfo &inputInfo) = 0; /** * Get the configuration parameters of plugin. * * @param [in] optionType The type of getting option. * @param [in] inputInfo Parameter information for getting options. * @param [out] outputInfo The configuration information of plugin. * @return Returns 0 if the operation is successful, returns a non-zero value otherwise. */ virtual int GetOption(int optionType, const DataInfo &inputInfo, DataInfo &outputInfo) = 0;};typedef IPlugin *(*IPLUGIN_INTERFACE)();} // namespace AI} // namespace OHOS#endif // I_PLUGIN_H
- 关于支持新的推理引擎(无论是硬件厂商的还是第三方软件厂商),可通过继承EngineAdapter该类,实现对应的接口,可以参考华为提供的NNIE的方式
- EngineAdapter
foundation/ai/engine/services/common/platform/os_wrapper/engine_hal/interfaces/engine_adapter.h
class EngineAdapter {public: virtual ~EngineAdapter() = default; /* Initializes the algorithm and get the algorithm execution handle */ virtual int32_t Init(const char *modelPath, intptr_t &handle) = 0; /* De-Initializes all the algorithms. */ virtual int32_t Deinit() = 0; /* Makes the model based on the given handle Inference once. */ virtual int32_t Invoke(intptr_t handle) = 0; /* Gets the inputBuffer and inputSize after the handle related model is initialized. */ virtual int32_t GetInputAddr(intptr_t handle, uint16_t nodeId, uintptr_t &inputBuffer, size_t &inputSize) = 0; /* Gets the outputBuffer and outputSize after the handle related model is initialized. */ virtual int32_t GetOutputAddr(intptr_t handle, uint16_t nodeId, uintptr_t &outputBuffer, size_t &outputSize) = 0; /* Release the algorithm based on the given handle. */ virtual int32_t ReleaseHandle(intptr_t handle) = 0;};
- NNIE实现的方式
device/hisilicon/hardware/ai/hal/hispark_taurus/include/nnie_adapter.h
- 存档的头文件,以及对应的静态库和动态库
- 通过宏来选择不同的引擎
现状
- 基于目前的OpenHarmony 版本来看,虽然在子系统的配置文件中,存在定义,但是并没有定义相关的ohos.build文件。
- 关于使用./build.sh脚本编译时,编译rk3568没有涉及到这部分功能。
Exameple
将AI engine 注册成为一个server
- 首先定义一个新的服务,并实现服务的生命周期函数
- 宏INHERIT_SERVICE 定义一些函数指针
- 宏INHERIT_IUNKNOWNENTRY类似一个模板,定义了一些变量,通过调用INHERIT_IUNKNOWNENTRY(AiInterface),在该结构体中定义一些一些属性。
- 查看AiInterface结构体,可以知道其中定义了如下函数指针,作为一些方法。
- 通过上面几部分,完成了一个struct 或 class的定义。
- 宏INHERIT_SERVICE 定义一些函数指针
- 其次,创建一个服务对象并同时定义默认功能,并向SAMGR注册服务即接口,定义服务的初始化入口。
- 在创建对象的时候,将struct中的函数指针完成赋值。注意其中的使用到的宏SERVER_IPROXY_IMPL_BEGIN和IPROXY_END 是为了对应之前使用到的INHERIT_IUNKNOWNENTRY(AiInterface),可以查看这两个宏的定义。
- 在文件foundation/ai/engine/services/server/communication_adapter/source/sa_server.c中,定了变量g_aiEngine中赋值号右边的函数。此时完成最终的定义。
- 在创建对象的时候,将struct中的函数指针完成赋值。注意其中的使用到的宏SERVER_IPROXY_IMPL_BEGIN和IPROXY_END 是为了对应之前使用到的INHERIT_IUNKNOWNENTRY(AiInterface),可以查看这两个宏的定义。
- 在client端调用跨进程服务的对外接口,并调用IPC消息接口
- 在server查看Invoke函数的处理IPC消息
More
该部分内容,更过可以查看foundation/distributedschedule/samgr_lite/README_zh.md文件,该部分是软总线部分的外部接口的介绍。
通过ID_LOAD_ALGORITHM来看这个调用栈
- 从一个app端开始,
- 最后,APP端调用的接口,开始到AI Engine中的cline
- 通过不同的FUNID来调用对应的响应函数,以ID_LOAD_ALGORITHM为例来看。大概是三部分,一部分在client端,一部分在通信部分,最后一部分在server端。
- client
- 中间
- 关于跨client 和server必然存在一个双方共同使用部分,现在使用IPC传递一个IpcIo 一个双向链表, client和server双方对同一个IpcIo进行读写。
- 首先可以查看IpcIo的定义, 可以通过查看IpcIoInit函数对于其中的各个内容起到的作用有更深刻的理解
- 以LoadAlgorithmProxy为例,关于其中的ParcelClientInfo, ParcelAlgorithmInfo 和 ParcelDataInfo 最终操作的均是将数据push到IpcIo中。
- 对比看server端,
- 跳入UnParcelInfo函数,可以发现是对于client中ParcelClientInfo, ParcelAlgorithmInfo 和 ParcelDataInfo 的逆过程,读出client中写入的数据
- 关于在server端结果写入到另外一个IpcIo 对象 reply, 关于该对象的初始化,应该是samgr_lite组件来完成(推理),关于client如何获取这些server写入的数据,查看关于IClientProxy接口的定义,可以发现,最后的参数CallbackBuff是一个用来处理响应数据的回调函数。
- 首先可以查看IpcIo的定义, 可以通过查看IpcIoInit函数对于其中的各个内容起到的作用有更深刻的理解
- 查看回调函数CallbackBuff定义,可以发现从另外一个IpcIo对象中读取server吸入的数据。这样子,基于已有的samgr_lite接口,完成了clinet和server端数据的通信。
- 关于跨client 和server必然存在一个双方共同使用部分,现在使用IPC传递一个IpcIo 一个双向链表, client和server双方对同一个IpcIo进行读写。
- server端
- 在engine->GetPlugin()调用中获取一个std::shared_ptr, 查看关于class Plugin的定义,可以发现其中包含IPlugin *pluginAlgorithm_, 通过该指针,调用IPlugin相关的方法, 自定义的算法插件,继承IPlugin并完成对应的接口定义。
- 在engine->GetPlugin()调用中获取一个std::shared_ptr, 查看关于class Plugin的定义,可以发现其中包含IPlugin *pluginAlgorithm_, 通过该指针,调用IPlugin相关的方法, 自定义的算法插件,继承IPlugin并完成对应的接口定义。
- client
- 通过不同的FUNID来调用对应的响应函数,以ID_LOAD_ALGORITHM为例来看。大概是三部分,一部分在client端,一部分在通信部分,最后一部分在server端。
同步执行 Sync
- 我们还是从client 端来看整个调用栈,目的理解对于整个然间作拓展的缘由,进而学习整个软件设计,像整个软件开发生态链,向上挺进。
- 其实到现在的话,与之前设置算法ID其实并没有太多的区别。
- 查看其中的msgHandler_,类型IHandler, 关于同步运行类型SyncMsgHandler也继承自该类型,同样异步运行类型也继承自该类型AsyncMsgHandler
- 查看模板类SimpleEventNotifier, 存在一个关于ISemaphore的智能指针producer_, 并相关的方法AddToBack, 将数据保存到属性T *value_, 并让信号量自增,GetFromFront, 等待一段时间,查看信号量是否成功自减,如果成功,将对应的数据返回。
- 关于ISemaphore信号量的定义,可以具体查看下面的内容,若了解,可以直接跳过。 其中关于宏定义的使用还是挺有意思的。
- 接着SyncMsgHandler继续往下走,查看SendRequest方法具体做什么,将request和notifier封装成一个Task,压入queue_,查看Task的定义,发现也没做什么,只是封装下数据,然后关于quque_ 其实是从engine中直接传递下去的。
- 关于查看queue_ 数据传递。
- 关于查看queue_ 数据传递。
- 关于目前为止,只是发现将request及相关的数据压入了queue_,并且在IHandler 与Engine层面使用的是同一分queue_, 那么这个数据到底是怎么处理的?
- 这个需要回到,算法加载,创建Engine部分,并开始初始化该Engine,在调用Engine的构造函数中,将queue 引用同时传递给EngineWorker,实例化对象, EngineWorker继承于IWorker。
- 查看Engine::Initialize(), 可以发现SyncMsgHandler(*queue_, plugin_->GetPluginAlgorithm()),并在线程中,开始执行thread_->StartThread(&worker_), 之后调用EngineWorker::OneAction() , 先取出一个任务queue_.PopFront(task);,然后处理任务task.handler->Process(task); 实际调用SyncMsgHandler::Process(const Task &task), 开始调用pluginAlgorithm_->SyncProcess(request, response);完成实际处理,(task.notifier)->AddToBack(response); 通知信号量,自增。 最终,完成SyncMsgHandler::ReceiveResponse(int timeOut, SimpleEventNotifier ¬ifier, IResponse *&response)
- 这个需要回到,算法加载,创建Engine部分,并开始初始化该Engine,在调用Engine的构造函数中,将queue 引用同时传递给EngineWorker,实例化对象, EngineWorker继承于IWorker。
异步执行 Async
- 关于异步执行主要有两部分,即注册回调函数,以及完成执行。
注册回调函数
- 目前提供的两个demo ,图片分类以及语音识别,均使用的是同步执行,在文件foundation/ai/engine/services/client/client_executor/include/i_aie_client.inl 没有发现 直接的接口,往下一层foundation/ai/engine/services/client/communication_adapter/source/sa_client_proxy.cpp中,可以发现存在预留的接口RegisterCallbackProxy
- 先查看注册saAsyncHandler->RegisterAsyncHandler(clientInfo->clientId);中做的工作
- 接着查看启动中做的工作saAsyncHandler->StartAsyncProcess(clientInfo->clientId, adapter);
- 接着查看下classAsyncProcessWorker 构造函数所作工作。初始化属性工作
- 关于线程的启动,跳过,直接查看OneAction工作,关于线程具体如何工作,可以查看前面的同步执行,过程是一样。
- 先跳过如果获取response,查看IpcIoResponse 工作。
- 关于SvcIdentity *svcIdentity = adapter_->GetEngineListener(); 就是将之前保存好的SvcIdentity 取出来。
- 关于Transact(nullptr, *svcIdentity, ON_ASYNC_PROCESS_CODE, &io, &reply, LITEIPC_FLAG_ONEWAY, nullptr); 工作内容,
- 我们接着查看下 如果获取 response IResponse *response = handler_->FetchCallbackRecord(); 首先,前面实例化了一个ClientListenerHandler 对象。查看下构造函数。实例化了一个对象Event。
- 那么,接着IResponse *response = handler_->FetchCallbackRecord(); 从类ClientListenerHandler 中属性responses_ 取出 响应, 该属性十一两个list 专门保存 响应, 那么可以知道,异步执行时,必然是将属性保存到responses_, 通过上面的Event 来控制这个写入,读出。
异步执行
- 通过上面注册回调函数过程,响应必然会保存到ClientListenerHandler 的responses_ 中。
- 首先,与同步执行相似,先初始化Engine。
- 查看下Handler 具体定义
- bool isStarted = thread_->StartThread(&worker_); 开始执行任务队列,现在任务队列还是为空。 等待
- 直接从foundation/ai/engine/services/client/communication_adapter/source/sa_client_adapter.cpp 开始这个过程,前面的部分,与之前相似。
- 查看实例化future 工作内容。并且在Future 中, 包含请求和响应。
- 随着Task 压入队列, 那么开始执行工作,也就是开始执行请求。 之前的队列是复制到EngineWorker 中,
- 到现在为止,reques 终于执行了
- 关键是,response 如何返回呢? ? ?
- 其实是因为算法SDK 中的异步执行没有实现导致,没有联通。
- 在异步执行完之后,调用int OnEvent(PluginEvent event, IResponse *response) override;
- 其实是因为算法SDK 中的异步执行没有实现导致,没有联通。
- 到此,就可以获取到响应了。
- zh-cn/readme/AI业务子系统.md · OpenHarmony/docs - 码云 - 开源中国 (gitee.com)
- (18条消息) 跨平台:GN实践详解(ninja, 编译, windows/mac/android实战)_TechGhost的博客-CSDN博客_gn编译