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. 1038 Recover the Smallest Number

    Given a collection of number segments, you are supposed to recover the smallest number from them. Fo ...

  2. 从苏宁电器到卡巴斯基第13篇:我在苏宁电器当营业员 V

    强大的竞争对手 与现在遍地开花的苹果店相比,在2010年左右的时候,在长春,真正得到苹果授权的苹果店还是屈指可数的.当时在重庆路上如果想买苹果的产品,要么可以去苏宁国美,要么只能去卓展楼上的苹果专区了 ...

  3. POJ2044 深搜+剪枝(云彩下雨)

    题意:        有一个城镇,是4*4的大小的,然后你控制一块云彩,2*2的,你每天可以有9种走的方法,上下左右,或者不动,走的时候可以走1或者2步,云彩所在的地方肯定会下雨,然后给你做多365天 ...

  4. CVE-2017-11882:Microsoft office 公式编辑器 font name 字段栈溢出通杀漏洞调试分析

    \x01 漏洞简介 在 2017 年 11 月微软的例行系统补丁发布中,修复了一个 Office 远程代码执行漏洞(缓冲区溢出),编号为 CVE-2017-11882,又称为 "噩梦公式&q ...

  5. Win64 驱动内核编程-25.X64枚举和隐藏内核模块

    X64枚举和隐藏内核模块 在 WIN64 上枚举内核模块的人方法:使用 ZwQuerySystemInformation 的第 11 号功能和枚举 KLDR_DATA_TABLE_ENTRY 中的 I ...

  6. 什么?这么精髓的View的Measure流程源码全解析,你确定不看看?

    前言 Android开发中我们平时接触最多的是各种View, View是一个比较大的体系,包含了绘制流程.事件分发.各种动画.自定义View 等等.前几天我写了一篇事件分发源码解析的文章, 今天我们来 ...

  7. pr加字幕

    选择免费字体 自由字体整理了免费的商用字体 安装字体 将下载好的.ttf文件,右键选择为所有用户安装 如果没有选择为所有用户安装,你在Arctime或者premiere中可能无法找到这个字体 而且想要 ...

  8. Django(6)自定义路由转换器

    自定义路径转换器 有时候上面的内置的url转换器并不能满足我们的需求,因此django给我们提供了一个接口可以让我们自己定义自己的url转换器 django内置的路径转换器源码解析 在我们自定义路由转 ...

  9. PTA 第三章 栈与队列

    一.判断题 1.若一个栈的输入序列为1,2,3,--,N,输出序列的第一个元素为i,则第j个输出的元素是j-i-1    (×)解析:应该是不确定的,不能保证数字出栈后不会再入栈 2.所谓" ...

  10. Docker 部署阿里云RocketMQ 4.5.1

    搜索镜像 docker search rocketmq 查看镜像版本 如果要查看其它的镜像,只需要将其中的镜像名称foxiswho/rocketmq替换为其它镜像即可 curl https://reg ...