AI推理单元
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 异步接口进行推理的步骤如下所示:
- 加载离线模型 InferServer::LoadModel() 。
- 创建异步推理节点 InferServer::CreateSession() 。
- 准备输入数据。
- 提交推理请求 InferServer::Request() ,推理任务完成后将结果通过 Observer::Response 发送给用户。
- 完成所有推理任务后,释放推理节点 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推理单元的更多相关文章
- AI推理与Compiler
AI推理与Compiler AI芯片编译器能加深对AI的理解, AI芯片编译器不光涉及编译器知识,还涉及AI芯片架构和并行计算如OpenCL/Cuda等.如果从深度学习平台获得IR输入,还需要了解深度 ...
- 使用函数计算三步实现深度学习 AI 推理在线服务
目前深度学习应用广发, 其中 AI 推理的在线服务是其中一个重要的可落地的应用场景.本文将为大家介绍使用函数计算部署深度学习 AI 推理的最佳实践, 其中包括使用 FUN 工具一键部署安装第三方依赖 ...
- 基于函数计算 + TensorFlow 的 Serverless AI 推理
前言概述 本文介绍了使用函数计算部署深度学习 AI 推理的最佳实践, 其中包括使用 FUN 工具一键部署安装第三方依赖.一键部署.本地调试以及压测评估, 全方位展现函数计算的开发敏捷特性.自动弹性伸缩 ...
- 稀疏性如何为AI推理增加难度
稀疏性如何为AI推理增加难度 NVIDIA Ampere架构使数学运算加倍,以加速对各种神经网络的处理. 如果曾经玩过游戏Jenga,那么将有一些AI稀疏感. 玩家将木制积木交叉成一列.然后,每个玩家 ...
- 全场景AI推理引擎MindSpore Lite, 助力HMS Core视频编辑服务打造更智能的剪辑体验
移动互联网的发展给人们的社交和娱乐方式带来了很大的改变,以vlog.短视频等为代表的新兴文化样态正受到越来越多人的青睐.同时,随着AI智能.美颜修图等功能在图像视频编辑App中的应用,促使视频编辑效率 ...
- 视频结构化 AI 推理流程
「视频结构化」是一种 AI 落地的工程化实现,目的是把 AI 模型推理流程能够一般化.它输入视频,输出结构化数据,将结果给到业务系统去形成某些行业的解决方案. 换个角度,如果你想用摄像头来实现某些智能 ...
- 深度 | AI芯片之智能边缘计算的崛起——实时语言翻译、图像识别、AI视频监控、无人车这些都需要终端具有较强的计算能力,从而AI芯片发展起来是必然,同时5G网络也是必然
from:https://36kr.com/p/5103044.html 到2020年,大多数先进的ML袖珍电脑(你仍称之为手机)将有能力执行一整套任务.个人助理将变的更加智能,它是打造这种功能的切入 ...
- 昇腾AI计算,618冲动消费也不怕
摘要:近期大热的图像识别处理核赔技术,可应对剁手党们冲动购物之后汹涌而至的退货场景.那么,这背后运用的技术原理是怎样? AI计算平台又能否重构企业业务引擎呢? 随着AI技术的挖掘与应用落地,也为每一年 ...
- 所有处理都走向AI
所有处理都走向AI All Processing Bends Toward AI 旧金山--谷歌正在试验机器学习(ML)来执行集成电路设计中的位置和路径,并取得了很好的效果.上周在ISSCC会议上宣布 ...
随机推荐
- Python数据类型之字符串类型
字符串的表示 字符串是Python中最常用的数据类型之一,必须使用成对的引号包围来表示字符串,引号可以是单引号 ' .双引号 " .三引号''' """,格式如 ...
- Java中的线程池用过吧?来说说你是怎么理解线程池吧?
前言 Java中的线程池用过吧?来说说你是怎么使用线程池的?这句话在面试过程中遇到过好几次了.我甚至这次标题都想写成[Java八股文之线程池],但是有点太俗套了.虽然,线程池是一个已经被说烂的知识点了 ...
- Thinkphp5 日期与时间戳相互转换
日期转换为时间戳 $date="2013-10-01 12:23:14"; dump(strtotime($date)); //=>1380601394 时间戳 转换为日期 ...
- 技术面试问题汇总第002篇:猎豹移动反病毒工程师part2
这次拿三个问题来讨论,是关于调试器的.因为对于反病毒工程师而言,类似于OllyDbg和IDA的使用方法是必须掌握的,但是在面试中又不太方便考察,所以只能对其快捷键或者调试器实现原理之类的问题进行提问. ...
- 如何利用C++的time头文件获取系统时间
C++提供了time.h头文件进行时间编辑操作,可以把时间格式化进tm结构体,方便使用.MFC框架中的ctime类就是基于time.h封装的. 代码样例: #include <iostream& ...
- hdu1568斐波那契前4位
题意: 就是求斐波那契数,但是只要求输出前四位,(n<=100000000). 思路: 这个要用到斐波那契的公式和一些log的规律,直接打看着很乱,直接在网上偷张图片吧: ...
- (邹博ML)数学分析与概率论
机器学习入门 深度学习和机器学习? 深度学习在某种意义上可以认为是机器学习的一个分支,只是这个分支非常全面且重要,以至于可以单独作为一门学科来进行研究. 回忆知识 求解S. 对数函数的上升速度 我们使 ...
- idea使用lombok不生效
问题: 在maven项目中引入lombok的依赖,可是依旧无法在实体类中生效 <dependency> <groupId>org.projectlombok</group ...
- Outlook关闭时最小化
一:背景环境: 当使用Outlook的时候,不小心点关闭,会不能及时发现接收的新邮件. 二:解决方法: 利用KeepOutlookRunning.dll插件,可以实现,点击关闭时,outlook没有实 ...
- Java枚举类、注解和反射
本文主要介绍的是枚举类,注解和反射.还有一些基础知识:static,基本数据类型,运算符优先级放在文中,以便查阅复习. 其中牵扯到泛型的部分,可参考本人的另一篇博客:(Collection, List ...