乐趣区

关于openharmony:OpenHarmony-AI框架开发指导

一、概述

1、性能简介
AI 业务子系统是 OpenHarmony 提供原生的分布式 AI 能力的子系统。AI 业务子系统提供了对立的 AI 引擎框架,实现算法能力疾速插件化集成。

AI 引擎框架次要蕴含插件治理、模块治理和通信治理模块,实现对 AI 算法能力的生命周期治理和按需部署。插件治理次要实现插件的生命周期治理及插件的按需部署,疾速集成 AI 能力插件;模块治理次要实现工作的调度及治理客户端的实例;通信治理次要实现客户端和服务端之间的跨过程通信治理及 AI 服务与插件之间的数据传输。后续,会逐渐定义对立的 AI 能力接口,便于 AI 能力的分布式调用。同时,框架提供适配不同推理框架层级的对立推理接口。

AI 引擎框架结构如下图所示。

图 1 AI 引擎框架

2、搭建环境
筹备开发板:Hi3516DV300 或 Hi3518EV300 下载源码二、技术规范 1、代码治理标准 AI 引擎框架蕴含 client、server 和 common 三个次要模块,其中 client 提供 server 端连贯治理性能,OpenHarmony SDK 在算法对外接口中需封装调用 client 提供的公共接口;server 提供插件加载以及工作治理等性能,各 Plugin 实现由 server 提供的插件接口,实现插件接入;common 提供与平台相干的操作方法、引擎协定以及相干工具类,供其余各模块调用。

AI 引擎框架各模块之间的代码依赖关系如下图所示:

图 2 AI 引擎代码依赖关系

倡议:插件与 OpenHarmony SDK 在 AI 引擎指定的门路下进行代码开发

在 AI 引擎框架的整体规划中,OpenHarmony SDK 属于 client 端的一部分,插件由 server 端调用,属于 server 端的一部分,因而 AI 引擎框架为接入的插件与 OpenHarmony SDK 布局的门路:

SDK 代码门路://foundation/ai/engine/services/client/algorithm_sdk
示例 1://foundation/ai/engine/services/client/algorithm_sdk/cv
示例 2://foundation/ai/engine/services/client/algorithm_sdk/nlu

插件代码门路://foundation/ai/engine/services/server/plugin
示例 1://foundation/ai/engine/services/server/plugin/cv
示例 2://foundation/ai/engine/services/server/plugin/nlu

规定:插件提供的全副对外接口,对立寄存在 AI 业务子系统 interfaces/kits 目录

OpenHarmony SDK 对外接口是 AI 业务子系统提供能力的对外裸露形式,依照 OpenHarmony 的接口治理要求,需对立寄存在各子系统的 interfaces/kits 目录中。以后 AI 业务子系统插件对外接口门路为 //foundation/ai/engine/interfaces/kits,不同插件可在该门路下增加目录,比方减少 cv 插件,则在门路 //foundation/ai/engine/interfaces/kits/cv 上面寄存接口文件。

规定:插件编译输入门路必须是在 /usr/lib

server 端加载插件是采纳 dlopen 形式,只反对在 /usr/lib 门路进行,因而插件在编译 so 时,须要在编译配置文件中指定输入门路为 /usr/lib。

2、命名标准
SDK 命名规定:畛域_关键词 < 其余信息 1_其余信息 2…>_sdk.so

对于畛域,倡议应用以后支流简称,比方图片视频相干的应用 ”cv”,语音辨认相干的应用“asr”,翻译相干的应用“translation”等,存在其余畛域的可减少定义;关键词则须要失当精确的形容所对应插件的算法能力,比方唤醒词辨认,则应用 keyword_spotting;对于其余信息,比方插件反对的芯片类型、国内海内等信息,可在关键词与“sdk”之间顺次增加,信息之间以下划线连贯;SDK 命名,必须以“_sdk”结尾。

例如:唤醒词辨认插件对应的 SDK,只反对麒麟 9000 芯片,实用于中国国内地区实用,则对应的 SDK 命名为:asr_keyword_spotting_kirin9000_china_sdk.so

插件命名规定:畛域_关键词 < 其余信息 1_其余信息 2…>.so

插件与 SDK 存在一一对应的关系,故插件命名的畛域、关键词、其余信息等名词解释与要求,均与 SDK 命名要求保持一致。两者惟一的不同之处在于 SDK 命名多了个“_sdk”结尾;比方插件命名为“asr_keyword_spotting.so”,则对应 SDK 命名为“asr_keyword_spotting_sdk.so”。

例如:唤醒词辨认插件对应的 SDK,只反对麒麟 9000 芯片,实用于中国国内地区实用,则对应的插件命名为:asr_keyword_spotting_kirin9000_china.so

3、接口开发标准

规定:SDK 需按算法调用程序,封装 client 对外提供接口;对于异步插件对应的 SDK,须要实现 client 提供的回调接口 IClientCb

AI 引擎的 client 端对外提供的接口包含 AieClientInit、AieClientPrepare、AieClientSyncProcess、AieClientAsyncProcess、AieClientRelease、AieClientDestroy、AieClientSetOption、AieClientGetOption,SDK 须要依据插件的接口依照程序至多封装 AieClientInit、AieClientPrepare、AieClientSyncProcess/AieClientAsyncProcess、AieClientRelease、AieClientDestroy 五个接口,否则会呈现调用问题或者内存透露。比方封装过程脱漏了 AieClientPrepare 接口,则 server 端无奈实现插件加载,故前面的接口都无奈调用胜利。

对于异步插件,SDK 须要实现 IClientCb 接口,用于接管来自 client 端的算法推理后果,并将该后果返回给三方调用者。

规定:SDK 接口实现中,须要保留与 client 交互的相干通用数据

AI 引擎将的 client 端采纳单例实现,对接多个 SDK,因而各 SDK 须要保留与 client 交互的通用数据,用于连贯 server 端进行工作推理、后果返回等;需保留数据蕴含 clientInfo、algorithmInfo、configInfo 三种数据类型,定义在 SDK 的成员变量里即可。

倡议:SDK 实现 client 定义的 IServiceDeadCb 接口

Server 端是零碎常驻过程,以零碎能力的模式为多个 client 提供服务;client 定义的 IServiceDeadCb 接口,是在 server 端异样死亡后,会被触发调用。这种异样场景,SDK 可在死亡告诉接口中,实现相干操作,比方进行调用或者再次拉起 server 端等。

IServiceDeadCb 接口实现示例:

class ServiceDeadCb : public IServiceDeadCb {
public:
ServiceDeadCb() = default;
~ServiceDeadCb() override = default;
void OnServiceDead() override
{printf("[ServiceDeadCb]OnServiceDead Callback happens");
}
};

如上示例,SDK 可在 OnServiceDead()办法中实现本人的操作,比方进行所有的接口调用等等。

规定:SDK 与 plugin 须要应用编解码模块,将特定算法数据转换成 AI 引擎的通用数据类型

插件的推理数据,会由三方调用者通过 client、server 传递到插件中;不同的算法所须要的数据类型是不统一的,比方 cv 的须要图片数据、asr 的须要语音数据;为了适配数据类型的差别,AI 引擎对外提供了对根本数据类型的编解码能力,将不同数据类型转换为 AI 引擎能够应用的通用数据类型。

编码后的数据类型定义:

struct DataInfo {
unsigned char *data;
int length;
} DataInfo;

如上示例,DataInfo 数据结构包含指向数据内存的指针和数据长度两个变量组成。

框架接口应用办法:
1. 增加依赖的头文件:”utils/encdec/include/encdec.h”。
2. 增加 build.gn 中的依赖项:
include_dirs 增加 ”//foundation/ai/engine/services/common”。
deps 增加 ”//foundation/ai/engine/services/common/utils/encdec:encdec”。

3. 编解码示例:

// 编码调用函数示例:arg1,arg2,arg3 等为需编码的变量,dataInfo 为编码后的后果
retCode = ProcessEncode(dataInfo, arg1, arg2, arg3) // 能够接管任意多个参数
// 解码调用函数示例:dataInfo 为须要解码的信息,arg1,arg2,arg3 等为解码后的后果
retCode = ProcessDecode(dataInfo, arg1, arg2, arg3) // 能够接管任意多个参数

留神:
●编码和解码调用时的参数程序须要保障统一。
● 编码后 dataInfo 的内存须要调用者手动进行开释。
● server 端和 client 端的内存是离开治理和开释的。
●如果蕴含共享内存的指针,不须要额定解决。
●如果其余类型的指针,则须要解援用后应用 ProcessEncode/ ProcessDecode。
● 该编解码模块未适配 class 数据类型,不倡议应用。

规定:在 SDK 中,对以编解码返回的出参数据类型,须要进行内存开释,否则会呈现内存透露

编码失去的通用数据,实质上是将不同类型数据封装在同一块内存中,而后将这块内存的首地址与长度封装到构造体中。通过编码返回到 SDK 中的出参数据,在插件中申请了内存,但插件无奈开释;因而 SDK 在拿到数据之后,须要对内存进行开释,否则 SDK 将无奈拿到数据。

内存开释示例:

DataInfo outputInfo = {
.data = nullptr,
.length = 0,
};
AieClientPrepare(clientInfo_, algorithmInfo_, inputInfo, outputInfo, nullptr);
if (outputInfo.data != nullptr) {free(outputInfo.data);
outputInfo.data = nullptr;
outputInfo.length = 0;
}

规定:plugin 须要实现 server 定义的 IPlugin 接口,并应用宏 PLUGIN_INTERFACE_IMPL 对外提供插件函数指针

Server 端治理的插件外部接口实现逻辑各不相同,为了对立插件的加载流程,AI 引擎定义了插件接口 IPlugin;在运行态,插件是以动态链接库的模式被 AI 引擎框架通过 dlopen 形式加载,各插件须要应用 PLUGIN_INTERFACE_IMPL 语句对外裸露函数指针,否则插件将无奈被失常加载应用。

规定:plugin 须要应用 AI 引擎提供的对立数据通道

AI 引擎在 server 与插件之间,提供了一个对立的数据通道,用来解决来自 SDK 的推理申请和来自插件的后果返回;plugin 在推理接口中,需按数据通道实现申请数据的获取以及推理后果的封装。

数据通道应用示例:

int SyncProcess(IRequest *request, IResponse *&response)
{HILOGI("[IvpPlugin]Begin SyncProcess");
if (request == nullptr) {HILOGE("[IvpPlugin]SyncProcess request is nullptr");
return RETCODE_NULL_PARAM;
}
DataInfo inputInfo = request->GetMsg();
if (inputInfo.data == nullptr) {HILOGE("[IvpPlugin]InputInfo data is nullptr");
return RETCODE_NULL_PARAM;
}

...

response = IResponse::Create(request);
response->SetResult(outputInfo);
return RETCODE_SUCCESS;
}

示例中 request 和 response 是数据通道的内容主体。server 端会将数据封装在 request 中,传递到插件,插件进行算法解决之后,则须要将后果封装成 response 进行返回。

三、开发领导

1、开发 SDK

SDK 头文件的性能实现是基于对 SDK 的调用映射到对客户端的调用。Client 端提供的接口如下表所示。

表 1 Client 端提供的接口

其中,ConfigInfo,ClientInfo,AlgorithmInfo,DataInfo 的数据结构如下表所示。

表 2 ConfigInfo,ClientInfo,AlgorithmInfo,DataInfo 的数据结构

具体开发过程可参考唤醒词辨认 SDK 开发示例

2、开发插件

AI 引擎框架规定了一套算法插件接入标准,各插件需实现规定接口以实现获取插件版本信息、算法推理类型、同步执行算法、异步执行算法、加载算法插件、卸载算法插件、设置算法配置信息、获取指定算法配置信息等性能。(同步算法实现 SyncProcess 接口,异步算法实现 AsyncProcess 接口)。

算法插件类 IPlugin 接口设计如下表所示。

表 3 算法插件类 IPlugin 接口设计

算法插件类接口:Prepare、SyncProcess、AsyncProcess、Release、SetOption、GetOption 别离于客户端接口 AieClientPrepare、AieClientSyncProcess、AieClientAsyncProcess、AieClientRelease、AieClientSetOption、AieClientGetOption 一一对应;GetInferMode 接口用于返回算法执行类型——同步或异步。

算法插件回调类 IPluginCallback 接口设计如下表所示。

表 4 算法插件回调类 IPluginCallback 接口设计

Request、Response 是 AI 引擎服务端与算法插件进行通信的对象。Request 封装了调用方的申请、输出数据等,而插件次要通过 Response 将运算之后的后果返回给 AI 引擎服务端。

Request 类的属性如下表所示。

表 5 Request 类的属性

Response 类的属性如下表所示。

表 6 Response 类的属性

具体开发过程可参考唤醒词辨认插件开发示例

3、开发配置文件

开发者开发的 SDK 通过 AlgorithmInfo 构造体中 algorithmVersion 以及 algorithmType 辨认出具体的插件类型,实现插件能力的调用。因而开发者需实现以下步骤:
1、代码门路 //foundation/ai/engine/services/common/protocol/plugin_config/plugin_config_ini/ 中增加插件的配置文件。
2、代码门路 //foundation/ai/engine/services/common/protocol/plugin_config/ 中的 aie_algorithm_type.h 文件增加算法类型。
3、代码门路 //foundation/ai/engine/services/server/plugin_manager/include/ 中的 aie_plugin_info.h 文件增加唤醒词辨认的算法名称及其在 ALGORITHM_TYPE_ID_LIST 中的序号。

具体开发过程可参考唤醒词辨认配置文件开发示例

四、开发实例

1、唤醒词辨认 SDK 的开发示例

1、在 //foundation/ai/engine /interfaces/kits 目录中增加唤醒词辨认 SDK 的 API 接口定义,该接口可用三方利用进行调用。如下代码片段即为唤醒词辨认定义的 API 接口示例,其相干代码参考门路为://foundation/ai/engine/interfaces/kits/asr/keyword_spotting。

class KWSSdk {
public:
    KWSSdk();
    virtual ~KWSSdk();

    // 定义创立唤醒词检测工具包的办法
    int32_t Create();

    // 定义同步执行唤醒词检测工作的办法
    int32_t SyncExecute(const Array<int16_t> &audioInput);

    // 定义设置唤醒词检测回调器的办法。int32_t SetCallback(const std::shared_ptr<KWSCallback> &callback);

    // 定义销毁唤醒词工具包的办法,开释与插件的会话信息
    int32_t Destroy();};

2. 在 //foundation/ai/engine/services/client/algorithm_sdk 目录中减少 SDK 中 API 接口的具体实现,调用 client 端提供的接口,实现算法插件能力的应用。如下代码片段即为唤醒词辨认的 API 接口中 create 办法的具体实现示例,更多具体代码请参考://foundation/ai/engine/services/client/algorithm_sdk/asr/keyword_spotting。

int32_t KWSSdk::KWSSdkImpl::Create()
{if (kwsHandle_ != INVALID_KWS_HANDLE) {HILOGE("[KWSSdkImpl]The SDK has been created");
        return KWS_RETCODE_FAILURE;
    }
    if (InitComponents() != RETCODE_SUCCESS) {HILOGE("[KWSSdkImpl]Fail to init sdk components");
        return KWS_RETCODE_FAILURE;
    }
    // 调用 client 端提供的 AieClientInit 接口,实现初始化引擎服务,激活跨过程调用
    int32_t retCode = AieClientInit(configInfo_, clientInfo_, algorithmInfo_, nullptr);
    if (retCode != RETCODE_SUCCESS) {HILOGE("[KWSSdkImpl]AieClientInit failed. Error code[%d]", retCode);
        return KWS_RETCODE_FAILURE;
    }
    if (clientInfo_.clientId == INVALID_CLIENT_ID) {HILOGE("[KWSSdkImpl]Fail to allocate client id");
        return KWS_RETCODE_FAILURE;
    }
    DataInfo inputInfo = {
        .data = nullptr,
        .length = 0,
    };
    DataInfo outputInfo = {
        .data = nullptr,
        .length = 0,
    };
    // 调用 client 端提供的 AieClientPrepare 接口,实现加载算法插件
    retCode = AieClientPrepare(clientInfo_, algorithmInfo_, inputInfo, outputInfo, nullptr);
    if (retCode != RETCODE_SUCCESS) {HILOGE("[KWSSdkImpl]AieclientPrepare failed. Error code[%d]", retCode);
        return KWS_RETCODE_FAILURE;
    }
    if (outputInfo.data == nullptr || outputInfo.length <= 0) {HILOGE("[KWSSdkImpl]The data or length of output info is invalid");
        return KWS_RETCODE_FAILURE;
    }
    MallocPointerGuard<unsigned char> pointerGuard(outputInfo.data);
    retCode = PluginHelper::UnSerializeHandle(outputInfo, kwsHandle_);
    if (retCode != RETCODE_SUCCESS) {HILOGE("[KWSSdkImpl]Get handle from inputInfo failed");
        return KWS_RETCODE_FAILURE;
    }
    return KWS_RETCODE_SUCCESS;
}

上述代码为 API 接口的具体实现。在示例代码中,SDK 中 create 接口的具体实现即为上述示例代码中 create 办法,该办法调用了 AI 引擎框架 client 端提供的 AieClientInit 及 AieClientPrepare 接口,从而实现与 server 端建设连贯及加载算法模型的能力。

阐明:SDK 调用 AI 引擎 client 端接口应遵循 AieClientInit->AieClientPrepare->AieClientSyncProcess/AieClientAsyncProcess->AieClientRelease->AieClientDestroy 程序,否则调用接口会返回错误码。

2、唤醒词辨认插件的开发示例

在代码门路 //foundation/ai/engine/services/server/plugin 中增加唤醒词辨认插件的接口定义(IPlugin),并实现 AI 能力的调用。如下代码片段即实现唤醒词辨认的算法插件的接口定义。更多插件开发的相干代码参考门路如下://foundation/ai/engine/services/server/plugin/asr/keyword_spotting

#include "plugin/i_plugin.h
class KWSPlugin : public IPlugin {
public:
    KWSPlugin();
    ~KWSPlugin();
    const long long GetVersion() const override;
    const char* GetName() const override;
    const char* GetInferMode() const override;
    int32_t Prepare(long long transactionId, const DataInfo &amp;amp;inputInfo, DataInfo &amp;amp;outputInfo) override;
    int32_t SetOption(int optionType, const DataInfo &amp;amp;inputInfo) override;
    int32_t GetOption(int optionType, const DataInfo &amp;amp;inputInfo, DataInfo &amp;amp;outputInfo) override;
    int32_t SyncProcess(IRequest *request, IResponse *&amp;amp;response) override;
    int32_t AsyncProcess(IRequest *request, IPluginCallback*callback) override;
    int32_t Release(bool isFullUnload, long long transactionId, const DataInfo &amp;amp;inputInfo) override;
};

上述代码实现 server 提供的 IPlugin 接口。唤醒词辨认的 sample 中调用的 client 端接口与插件中的接口对应关系及其实现性能如下表所示。

表 7 唤醒词辨认中 client 端接口与插件中的接口对应关系

留神:
1. 接口 AieClientInit、AieClientDestroy 别离用于与 server 端建设和断开连接,未调用到插件算法中,因而插件中无需定义与之对应的接口。
2. 唤醒词辨认插件须要应用 PLUGIN_INTERFACE_IMPL 语句对外裸露函数指针,否则插件将无奈被失常加载应用。

PLUGIN_INTERFACE_IMPL(KWSPlugin);

3、唤醒词辨认配置文件的开发示例
1、在代码门路 //foundation/ai/engine/services/common/protocol/plugin_config/plugin_config_ini/ 中增加唤醒词辨认的配置文件。

[base]
supported_boards = hi3516dv300
related_sessions = asr_keyword_spotting+20001002

//[asr_keyword_spotting+20001002]的命名规定为[算法名称 + 算法 version]
[asr_keyword_spotting+20001002]
AID         = asr_keyword_spotting
VersionCode = 20001002
VersionName = 2.00.01.002
XPU         = NNIE
District    = China
// 编译出的插件 so 文件所在的地位
FullPath    = /usr/lib/libasr_keyword_spotting.so
Chipset     = ALL
ChkSum      = ''Key         =''

2、在代码门路 //foundation/ai/engine/services/common/protocol/plugin_config/ 中的 aie_algorithm_type.h 文件增加唤醒词辨认算法类型 id。

// 唤醒词辨认的算法类型 id 与唤醒词辨认在 ALGORITHM_TYPE_ID_LIST 中的序号一一对应
const int ALGORITHM_TYPE_KWS = 3;

3. 在代码门路 //foundation/ai/engine/services/server/plugin_manager/include/ 中的 aie_plugin_info.h 文件增加唤醒词辨认算法名称及在 ALGORITHM_TYPE_ID_LIST 中的序号。

const std::string ALGORITHM_ID_SAMPLE_1 = "sample_plugin_1";
const std::string ALGORITHM_ID_SAMPLE_2 = "sample_plugin_2";
const std::string ALGORITHM_ID_IVP = "cv_human_detect";
// 增加唤醒词辨认的算法名称 asr_keyword_spotting
// 算法的变量名称与 ALGORITHM_TYPE_ID_LIST 中算法 typeId 命名雷同,例如:ALGORITHM_ID_KWS 
const std::string ALGORITHM_ID_KWS = "asr_keyword_spotting";
const std::string ALGORITHM_ID_IC = "cv_image_classification";
const std::string ALGORITHM_ID_INVALID = "invalid algorithm id";

const std::vector<std::string> ALGORITHM_TYPE_ID_LIST = {
    ALGORITHM_ID_SAMPLE_1,
    ALGORITHM_ID_SAMPLE_2,
    ALGORITHM_ID_IVP,
    // 增加唤醒词辨认在 ALGORITHM_TYPE_ID_LIST 中的序号,通过该序号可取得唤醒词辨认的算法名称
    // 唤醒词辨认的算法名称和唤醒词辨认在 ALGORITHM_TYPE_ID_LIST 中的序号程序需保持一致
    ALGORITHM_ID_KWS,
    ALGORITHM_ID_IC,
};
退出移动版