首页 技术 正文
技术 2022年11月20日
0 收藏 303 点赞 2,370 浏览 9445 个字

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 生效。

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:8,903
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,429
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,245
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,057
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,689
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,726