AI推理单元

推理服务供了一套面向 MLU(Machine Learning Unit,机器学习单元)设备的类似服务器的推理接口(C++11标准),以及模型加载与管理,推理任务调度等功能,极大地简化了面向MLU平台高性能深度学习应用的开发和部署工作。

概述

推理服务在软件栈中的位置,如下图所示:

推理服务共包含以下3个模块的用户接口:

  • Model: 模型加载与管理
  • Processor: 可自定义的后端处理单元
  • InferServer: 执行推理任务

基本概念

本文描述推理服务中所涉及的具体概念。

InferServer

其整体架构如下图所示

推理服务架构

InferServer 是推理服务暴露给用户的功能入口,用户通过此入口进行加载或卸载模型(Model)、创建推理节点(Session)、请求推理任务(Request)等操作。 推理任务划分为预处理,推理,后处理三个环节,分别交由不同的后端处理单元(Processor)完成。 每个推理服务实例维护一个线程池(Scheduler),以处理环节(Task)作为最小调度单元,调度执行在该推理服务实例中运行的所有推理任务。

InferServer 使用pimpl指针隔离接口与实现,内部保证每个设备上仅有一个pimpl实例, 同一设备号下创建的 InferServer 链接同一个pimpl指针,提供对应功能。

InferServer s_0(0);
InferServer s_1(1);
 
// another_s_0 和 s_0 共用同一个任务调度器和相同的推理资源
InferServer another_s_0(0);

使用 InferServer 异步接口进行推理的步骤如下所示:

  1. 加载离线模型 InferServer::LoadModel() 。
  2. 创建异步推理节点 InferServer::CreateSession() 。
  3. 准备输入数据。
  4. 提交推理请求 InferServer::Request() ,推理任务完成后将结果通过 Observer::Response 发送给用户。
  5. 完成所有推理任务后,释放推理节点 InferServer::DestroySession() 。
class MyObserver : public Observer {
  void Response(Status status, PackagePtr output, any user_data) { ... }
};
 
bool PreprocFunction(ModelIO*, const InferData&, const ModelInfo&) { ... }
 
// prepare resources
InferServer server(0);
 
SessionDesc desc;
desc.name = "sample infer";
desc.model = InferServer::LoadModel(model_path, func_name);
// create processors
desc.preproc = PreprocessorHost::Create();
desc.postproc = Postprocessor::Create();
desc.preproc->SetParams("process_function", PreprocFunction);
 
Session_t session = server.CreateSession(desc, std::make_shared<MyObserver>());
 
// run inference
// create an input package with tag "stream_0", containing two piece of InferData
auto input = Package::Create(2, "stream_0");
input->data[0].Set<cv::Mat>(image_1);
input->data[1].Set<cv::Mat>(image_2);
 
server.Request(session, input, nullptr);
// result will be passed to MyObserver::Response after finishing process
 
// wait until the "stream_0" tagged inference task done
server.WaitTaskDone(session, "stream_0");
 
// release resources
server.DestroySession(session);

ModelInfo

ModelInfo 提供了易用的用户侧模型管理和信息读取接口,各种模型实现的基类。 由 InferServer::LoadModel() 加载模型,得到模型基类的智能指针。当所有实例生命周期结束后,模型自动卸载。 用户可随时获取模型的各种信息,包含输入输出个数、输入输出数据形状以及batch_size等。

Session

Session 是推理任务节点的抽象,接收用户的推理请求并完成响应。一个Session为一个处理节点,内部的后端处理单元的顺序结构是固定的,处理同一类请求。

使用 InferServer::CreateSession 创建异步Session,异步Session只能使用异步 Request 接口,处理完毕后通过 Observer::Response 发送响应给用户; 使用 InferServer::CreateSyncSession 创建同步Session,同步Session只能使用同步 RequestSync 接口,响应作为 RequestSync 的输出参数返回。

InferServer s(0);
SessionDesc desc;
/*
 * set desc params
 * ...
 */
Session_t async_session = s.CreateSession(desc, std::make_shared<MyObserver>());
Session_t sync_session = s.CreateSyncSession(desc);
 
s.Request(async_session, input, user_data);
s.RequestSync(sync_session, input, &status, output);

Session根据传入的参数准备 SessionDesc::engine_num 份推理资源,使每份推理资源可以独立执行任务,达成并行推理。

注解

模型键值拼接预处理和后处理单元类型名称(modelkey_preproc_postproc)作为Session的键值,键值相同的Session共用同一簇推理资源。

Processor

后端处理单元,负责处理推理任务中的一个环节,由多个 Processor 链接起来构成整个推理任务处理流程。

BaseObject 基类为 Processor 提供设置任意参数的功能。

MyProcessor p;
p.SetParams("some int param", 1,
            "some string param", "some string");
int a = p.GetParam<int>("some int param");
const char* b = p.GetParam<const char*>("some string");

InferData

InferData 表示一份推理数据,基于C++11编译器实现的 any 类使任意类型的推理数据都可以在 InferData 中设置。

InferData data;
cv::Mat opencv_image;
// 填充数据到InferData
data.Set(opencv_image);
// 获取一份数据的复制
auto image = data.Get<cv::Mat>();
// 获取一份数据的引用
auto& image_ref = data.GetLref<cv::Mat>();
try {
  // 类型不匹配,抛出异常bad_any_cast!
  auto non_sense = data.Get<int>();
} catch (bad_any_cast&) {
  std::cout << "Data stored in any cannot convert to given type!";
}
 
video::VideoFrame vframe;
// 重新设置data后image_ref非法化
data.Set(vframe);
// cv::imwrite("foo.jpg", image_ref);  // may cause segment fault
auto frame = data.Get<video::VideoFrame>();
auto& frame_ref = data.GetLref<video::VideoFrame>();

Package

Package 是一组推理数据的集合,既是Request的输入,也是Response的输出。用户通过Package可以一次请求多份数据进行处理。

使能 CNIS_RECORD_PERF 编译选项时, 输出中 Package::perf 包含每一个处理环节的性能数据,未使能时为空表。

Observer

进行异步请求需要在创建Session时,设置Observer实例。Session完成推理任务后,以通知Observer的方式完成Response。

class MyObserver : public Observer {
  void Response(Status status, PackagePtr output, any user_data) {
    std::cout << "Get one response\n";
  }
};
 
InferServer s(0);
SessionDesc desc;
Session_t async_session = s.CreateSession(desc, std::make_shared<MyObserver>());

功能

本文详细介绍推理服务的功能。

模型加载与管理

推理服务提供模型加载和管理功能,并在全局保有唯一一份模型缓存。

使用 InferServer::LoadModel(const std::string& uri, const std::string& func_name = "subnet0") 从本地路径加载模型, 若使能 CNIS_WITH_CURL 编译选项,则可以从远端下载模型, 并加载至模型存储路径(由 InferServer::SetModelDir 设置, 默认值为当前目录)。 当检测到模型存储路径中已存在同名模型,则跳过下载,直接加载本地模型(请注意不要使用相同的模型名,可能会导致使用错误的模型)。 使用 InferServer::LoadModel(void* mem_ptr, const std::string& func_name = "subnet0") 从内存中加载模型,适用于模型加密的场景, 由用户对存储的模型解密后交由推理服务进行加载。

将模型路径和模型函数名进行拼接作为键值,对模型进行区分(从内存加载的模型路径是内存地址字符串)。 若加载模型时发现缓存中已存在该模型,则直接返回缓存模型。 模型缓存存在上限,超出上限自动卸载未在使用的模型。上限默认值是10,可通过环境变量 CNIS_MODEL_CACHE_LIMIT 更改默认值。 支持运行时清除模型缓存,从缓存中清除不会直接卸载模型,仅在无其他模型的智能指针实例时才会卸载模型(确保模型已经没有在使用,避免功能性错误)。

ModelPtr local_model = InferServer::LoadModel("../../resnet.cambricon", "subnet0");
// use function name "subnet0" as default
ModelPtr local_model = InferServer::LoadModel("../../resnet.cambricon");
ModelPtr net_model = InferServer::LoadModel("http://some-web.com/resnet.cambricon");
 
void *model_mem, *decoded_model_mem;
size_t len = ReadFromFile(model_mem, ...);
DecodeModel(decoded_model_mem, model_mem, len, ...);
ModelPtr mem_model = InferServer::LoadModel(decoded_model_mem);

推理任务调度

推理服务对所有请求的推理任务进行调度,以在保证通用性的前提下尽量达到最优性能。 推理服务使用三种共同作用的调度方式:批处理、优先级和并行处理。

批处理(Batch)

推理服务提供两种批处理模式,Dynamic模式( BatchStrategy::DYNAMIC )和Static模式( BatchStrategy::STATIC ), 在创建Session时通过 SessionDesc::strategy 指定。

  • Dynamic模式会跨Request拼凑批数据,尽可能使用性能最优的批大小进行处理,达到较为理想的吞吐。 但由于跨Request拼凑数据会存在等待数据集齐的时间,单次Request的时延较高。达到设置的timeout时间后,即使未集齐批也会进行处理,以避免时延过高。
  • Static模式以用户每次输入的Request数据为一批,不跨Request拼凑数据,可以达到较为理想的单次Request时延。 但相较于Dynamic模式更难达到性能较优的批大小,总吞吐量较Dynamic模式略低。

注解

目前底层尚未支持带状态的离线模型,待后端支持后,会增量支持Sequence模式的批处理策略。

优先级(Priority)

每个设备上的所有推理任务共用同一个调度器。 用户可以在创建Session时通过 SessionDesc::priority 设置优先级,高优先级的Session中的任务将会优先被处理。 优先级限制为0~9的整数,数值越大优先级越高,低于0的按0处理,高于9的按9处理。

并行处理(Parallel)

为达到最大性能,通常需要在一个设备上并行执行多组推理任务。 若某个Session代表的一类推理任务负载较重,可以通过设置 SessionDesc::engine_num 增大该类推理任务的并行度, 使该Session共占用 engine_num * model_core_number 个计算核,和对应份推理资源(内存,模型指令等)。 超出上限后,继续增加engine_num,可能出现由于资源竞争导致的总吞吐下降的情况。

后端处理单元(Processor)

推理服务内置三种后端处理单元。

预处理

预处理单元完成输入数据预处理的功能,推理服务内置了通用的预处理单元PreprocessorHost(在CPU侧完成运算)。 用户提供单份数据预处理的方法 bool(ModelIO*, const InferData&, const ModelInfo&) , 通过 BaseObject::SetParams("process_function", func_ptr) 设置给 PreprocessorHost , 内置预处理单元内部实现并发对批数据进行任务处理,完成预处理后转换数据摆放 (从用户设置的 SessionDesc::host_input_layout 转换至模型接受的layout),拷贝入MLU,转给推理单元处理。 由于预处理函数由用户设置,预处理函数的输入是可保有任意类型数据的 InferData , 输出是固定类型的 ModelIO,故支持输入任意类型数据做推理。

注解

由用户提供的数据预处理方法仅处理单份数据,假如一个预处理过程(执行一次Process方法)的数据包中存在多份数据,多份数据将会被拆分,每份数据分别调用预处理方法并发执行预处理任务。

InferServer s(0);
SessionDesc desc;
desc.preproc = std::make_shared<PreprocessorHost>();
desc.preproc->SetParams("process_function", some_func);
/*
 * set desc params
 * ...
 */
Session_t sync_session = s.CreateSyncSession(desc);

预处理参数表

参数名称

默认值

范围

描述

parallel

2

[1, 8]

处理并行度

process_function

nullptr

N/A

用户定义的预处理方法

推理

推理单元完成推理任务(暂不支持用户设置推理单元,所有Session默认使用内置的推理单元)。 每个推理单元实例从模型获取包含指令数据的Context,多份Context由 cnrtForkRuntimeContext 生成,以避免指令的多份拷贝。 推理单元接受固定类型的输入,输出固定类型的推理结果( ModelIO ),输入输出数据均在MLU内存上。 推理完成后,将推理结果数据转至后处理单元解析。

后处理

后处理单元完成推理结果的拷贝和解析,推理服务内置了通用的后处理单元Postprocessor(在CPU侧完成运算)。 后处理单元首先将推理结果从MLU设备拷贝回CPU,并转换数据摆放格式(从模型输出的layout转换至用户设置的 SessionDesc::host_output_layout )。 用户提供单份数据后处理的方法 bool(InferData*, const ModelIO&, const ModelInfo&) , 通过 BaseObject::SetParams("process_function", func_ptr) 设置给 Postprocessor , 内置后处理单元内部实现并发,调用用户设置的方法对模型输出的批数据进行解析任务。

注解

由用户提供的数据后处理方法仅处理单份数据,假如一个后处理过程(执行一次Process方法)的数据包中存在多份数据,多份数据将会被拆分,每份数据分别调用后处理方法并发执行后处理任务。

InferServer s(0);
SessionDesc desc;
desc.postproc = std::make_shared<Postprocessor>();
desc.postproc->SetParams("process_function", some_func);
/*
 * set desc params
 * ...
 */
Session_t sync_session = s.CreateSyncSession(desc);

若用户未设置后处理方法,则仅执行拷贝和转换layout操作,后处理单元输出CPU上的 ModelIO 数据。 此种情况下,用户无需在 SessionDesc 中设置postproc,保持默认值nullptr将自动创建一个仅执行拷贝操作的Postprocessor。

后处理参数表

参数名称

默认值

范围

描述

parallel

2

[1, 8]

处理并行度

process_function

nullptr

N/A

用户定义的后处理方法

扩展接口(contrib)

推理服务提供对图像推理任务的特化接口,简化图像推理过程, 其中包括 VideoFrame 数据类型,针对该数据类型的MLU预处理单元 PreprocessorMLU, OpenCV特化数据类型 OpencvFrame ,针对该数据类型的CPU预处理仿函数 DefaultOpencvPreproc , 请求推理任务简化接口的 VideoInferServer ,继承自 InferServer 。

// 一级推理接口
bool Request(Session_t session, const VideoFrame& vframe,
             const std::string& tag, any user_data, int timeout = -1) noexcept;
bool RequestSync(Session_t session, const VideoFrame& vframe, const std::string& tag,
                 Status* status, PackagePtr output, int timeout = -1) noexcept;
 
// 二级分析接口
bool Request(Session_t session, const VideoFrame& vframe, const std::vector<BoundingBox>& objs,
             const std::string& tag, any user_data, int timeout = -1) noexcept;
bool RequestSync(Session_t session, const VideoFrame& vframe,
                 const std::vector<BoundingBox>& objs, const std::string& tag,
                 Status* status, PackagePtr output, int timeout = -1) noexcept;
InferServer s(0);
SessionDesc desc;
desc.preproc = std::make_shared<PreprocessorHost>();
// 使用OpenCV实现的预处理函数
desc.preproc->SetParams("process_function", video::DefaultOpencvPreproc::GetFunction());
 
...
 
Session_t sync_session = s.CreateSyncSession(desc);
 InferServer s(0);
 SessionDesc desc;
 // 使用MLU预处理单元
 desc.preproc = std::make_shared<video::PreprocessorMLU>();
 desc.preproc->SetParams("src_format", video::PixelFmt::NV21,
                         "dst_format", video::PixelFmt::RGBA,
                         "preprocess_type", video::PreprocessType::RESIZE_CONVERT);
 
 ...
 
 Session_t sync_session = s.CreateSyncSession(desc);
 
 
``PreprocessorMLU`` 单元内置了BANG语言实现的 ``ResizeConvert`` 算子,和硬件scaler(仅MLU220支持),提供MLU上的预处理功能,支持YUV转至四通道BGR家族,同时图像缩放至指定大小。
扩展预处理参数表

参数 core_number 和 keep_aspect_ratio 仅对 RESIZE_CONVERT 生效。

AI推理单元的更多相关文章

  1. AI推理与Compiler

    AI推理与Compiler AI芯片编译器能加深对AI的理解, AI芯片编译器不光涉及编译器知识,还涉及AI芯片架构和并行计算如OpenCL/Cuda等.如果从深度学习平台获得IR输入,还需要了解深度 ...

  2. 使用函数计算三步实现深度学习 AI 推理在线服务

    目前深度学习应用广发, 其中 AI 推理的在线服务是其中一个重要的可落地的应用场景.本文将为大家介绍使用函数计算部署深度学习 AI 推理的最佳实践,  其中包括使用 FUN 工具一键部署安装第三方依赖 ...

  3. 基于函数计算 + TensorFlow 的 Serverless AI 推理

    前言概述 本文介绍了使用函数计算部署深度学习 AI 推理的最佳实践, 其中包括使用 FUN 工具一键部署安装第三方依赖.一键部署.本地调试以及压测评估, 全方位展现函数计算的开发敏捷特性.自动弹性伸缩 ...

  4. 稀疏性如何为AI推理增加难度

    稀疏性如何为AI推理增加难度 NVIDIA Ampere架构使数学运算加倍,以加速对各种神经网络的处理. 如果曾经玩过游戏Jenga,那么将有一些AI稀疏感. 玩家将木制积木交叉成一列.然后,每个玩家 ...

  5. 全场景AI推理引擎MindSpore Lite, 助力HMS Core视频编辑服务打造更智能的剪辑体验

    移动互联网的发展给人们的社交和娱乐方式带来了很大的改变,以vlog.短视频等为代表的新兴文化样态正受到越来越多人的青睐.同时,随着AI智能.美颜修图等功能在图像视频编辑App中的应用,促使视频编辑效率 ...

  6. 视频结构化 AI 推理流程

    「视频结构化」是一种 AI 落地的工程化实现,目的是把 AI 模型推理流程能够一般化.它输入视频,输出结构化数据,将结果给到业务系统去形成某些行业的解决方案. 换个角度,如果你想用摄像头来实现某些智能 ...

  7. 深度 | AI芯片之智能边缘计算的崛起——实时语言翻译、图像识别、AI视频监控、无人车这些都需要终端具有较强的计算能力,从而AI芯片发展起来是必然,同时5G网络也是必然

    from:https://36kr.com/p/5103044.html 到2020年,大多数先进的ML袖珍电脑(你仍称之为手机)将有能力执行一整套任务.个人助理将变的更加智能,它是打造这种功能的切入 ...

  8. 昇腾AI计算,618冲动消费也不怕

    摘要:近期大热的图像识别处理核赔技术,可应对剁手党们冲动购物之后汹涌而至的退货场景.那么,这背后运用的技术原理是怎样? AI计算平台又能否重构企业业务引擎呢? 随着AI技术的挖掘与应用落地,也为每一年 ...

  9. 所有处理都走向AI

    所有处理都走向AI All Processing Bends Toward AI 旧金山--谷歌正在试验机器学习(ML)来执行集成电路设计中的位置和路径,并取得了很好的效果.上周在ISSCC会议上宣布 ...

随机推荐

  1. WordPress伪静态规则设置

    伪静态:即网站本身是动态网页如.php..asp..aspx等格式,而这类网页还带"?"加参数来读取数据库.开启伪静态后,动态网页即被转换重写成静态网页类型页面. WordPres ...

  2. PHP 导出 Excel 兼容 CSV XlS格式

    class ExcelRead { /** * 获取Excel文件内容 * @param $file * @return mixed * @throws PHPExcel_Reader_Excepti ...

  3. Openstack 虚拟机宽带限速

    修改Neutron配置文件,使其支持Qos 修改Neutron.conf service_plugins = neutron.services.qos.qos_plugin.QoSPlugin 修改p ...

  4. 『动善时』JMeter基础 — 7、jmeter.properties文件常用配置

    目录 1.默认语言设置 2.配置默认编码格式 3.GUI图标放大比例设置 4.功能区工具栏图标大小设置 5.视图区目录树图标大小设置 6.内容区编辑字体设置 7.添加JMeter元素快捷键设置 8.捕 ...

  5. 去了字节跳动,才知道年薪40W的测试有这么多?

    最近脉脉职言区有一条讨论火了: 哪家互联网公司薪资最'厉害'? 下面的评论多为字节跳动,还炸出了很多年薪40W的测试工程师   我只想问一句,现在的测试都这么有钱了吗? 前几天还有朋友说,从腾讯跳槽去 ...

  6. COM组件对象模型基础

    COM组件对象模型 COM组件对象模型是为了创建一种独立于任何编程语言的对象.COM对象提供统一的接口,在不同的编程环境中通过调用COM对象特定接口的方法来完成特定的任务.一般有三种方式编写COM组件 ...

  7. 7. IDEA概述和安装

    1.1IDEA概述 IDEA全称InteliJ IDEA,是用于Java语言开发的继承环境,它是业界公认的目前用于Java程序开发的最好工具 集成环境:把代码编写,编译,执行,调试等多种功能综合到一起 ...

  8. Redis泛泛而谈(详细2W字)

    本文适合于刚接触redis的,文章内容比较基础,大佬请绕道. 一.NoSQL入门和概述 Ⅰ-入门概述 1.为什么用NoSQL 1)单机MySQL的美好年代 在90年代,一个网站的访问量一般都不大,用单 ...

  9. 【Cocos2d-x】屏蔽Emoji并解决由于Emoji导致的崩溃问题

    IOS的Emoji表情因为编码问题,在Android手机上无法正常显示,如果当前的cc.Label节点使用的是系统字,在系统字库中找不到对应编码的字符,会导致崩溃. 为了解决这个问题,又要兼顾新老版本 ...

  10. istioctl命令整理

    显示配置文件中的差异 istioctl profile diff default demo 显示对应配置的profile istioctl profile dump demo 显示可用的配置 isti ...