作者​:昇腾实战派
知识地图​:https://blog.csdn.net/Lumos_Lovegood/article/details/161601003

背景概述

本文主要介绍目前小模型领域的服务化情况和Triton Inference Server方案,这也是目前昇腾在小模型服务化的主打方案,并详细介绍三条路径,Python backend、GE custom backend和ONNX-RUNTIME backend子方案。

一、小模型服务化综述

1.1 小模型服务化的价值

在各行各业中,小模型的应用越来越广泛,凭借其精准、高效、低成本等优势,小模型始终占据一定的地位,同时,与之关联密切的推理小卡也存在一定的市场空间,小模型对于整个昇腾生态也是非常重要的一环。

当前,小模型类别和架构百花齐放,OCR、语音、CV等模型快速迭代,在各自领域的模型不断刷新SOTA,小模型在完成昇腾上的迁移适配后,模型服务化调用成为刚需,需提供服务接口进行业务编排/串流,如提供API集成到客户ERP软件中调用,成为一项基础能力提供。

1.2 小模型服务化总览

目前可行和可用的小模型服务化主要有Triton Inference Server、FastAPI、Flask、vLLM-Ascend、vLLM-Omni、FunASR、TEI等,本文主要介绍Triton Inference Server方案(以下简称triton,注意不是OpenAI的triton),这也是传统小模型的通用服务化方案,triton在小模型领域也是生态较好的框架,本身较为成熟,适合商用化。
在这里插入图片描述

1.3 服务化适配总体流程

对于拿到一个模型,总体两步走,服务化的基础是模型功能已完成迁移适配,如有性能诉求,需先完成离线推理的性能调优,再接入服务化,以及后续的服务调优。
在这里插入图片描述

二、Triton Inference Server总览

2.1 简介

Triton Inference Server是 NVIDIA 开发的开源高性能推理服务框架,专为生产环境中的 AI 模型部署设计,作为一个服务化框架,客户端可以通过http/gRPC请求给triton server发送请求,triton server底层可以用tensorRT,pytorch,onnxruntime等不同的后端做具体的模型推理,并将推理后的结果返回给客户端。

官方GitHub仓:https://github.com/triton-inference-server

2.2 核心模块

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

triton server主要分为以下几个主要的模块

模块名 功能
server Triton服务主程序
core 核心组件,Triton核心逻辑和调度
common 公共工具和基础设施
backend 后端统一接口定义
***backend onnxruntime_backend、tensorrt_backend等具体backend实现

2.3 架构图与核心流程

在这里插入图片描述

其中Model Repository可以存放不同的模型,Triton Inference Server通过该仓库提供推理模型。推理请求通过HTTP/REST、GRPC或C API到达服务器,然后被路由到相应的模型调度器。Triton Inference Server实现了多种调度和批处理算法,这些算法可以根据模型进行配置。每个模型的调度器可选择地对推理请求进行批处理,然后将请求传递给与模型类型对应的后端。后端使用批处理请求中提供的输入进行推理,以生成所需的输出,并将输出返回。

三、Backend详解

3.1 简介

backend是triton中的核心推理部件,是triton中用来进行具体的模型推理的模块,backend可以是由一些推理框架比如onnxruntime/tensorRT等封装得到,也可以按照backend要求的api格式自己写一个backend,每个backend对应一个.so动态库,库文件的名字必须遵循libtriton_.so的命名格式,在config.pbtxt配置文件中配置backend:“mybackend”,则对应为libtriton_mybackend.so。

3.2 Backend机制

Triton Server启动时,会扫描默认后端目录下的子目录,每个子目录对应一个后端,通过目录名称和其中的配置文件确定后端信息,读取并解析 config.pbtxt 配置文件,选择对应的后端,通过dlopen接口动态加载后端动态库libtriton_.so,加载成功后,后端相关的代码与资源被映射到进程空间。Triton通过dlsym等机制获取后端动态库库必须实现暴露的一组标准接口函数,并保存到后端管理结构中,执行后续调用。

其中libtriton_.so动态库搜索路径,优先在模型版本路径搜索,依次会搜索:

/model//libtriton_.so

<model_repository>/model/libtriton_.so

<global_backend_directory>/backends/libtriton_.so

3.3 Backend选择推荐

triton server虽然原生不支持NPU,但是得益于服务化与推理后端的解耦,通过扩展推理后端,即可在NPU上运行triton server。本方案中,可选三种不同的backend,分别是Python backend、GE custom backend和ONNX-RUNTIME backend。

简要对比Python backend和GE custom backend,

  1. triton service本身是C++开发的,调用python性能无法达到最好,有Python侧的开销,这是Python backend天生的劣势
  2. Python backend涉及适配model.py文件的三个接口,支持接入推理路线的种类最多,但有一定适配工作量,而GE backend单独针对onnx格式,基本没有代码开发量
  3. GE backend基于图进行深度优化,并且提供了图下沉、多流并行、锁核等方法,Python backend中,通常使用aisbench工具的Python api进行om模型的推理,本身性能就比不过GE,且不支持异步推理

针对不同的模型推理技术路线,选取不同的backend方案

(1)ONNX

路线一:通过onnx+ge_backend的方式进行服务化

路线二:通过onnx+onnxruntime_backend的方式进行服务化

对于pytorch、paddle、tensorflow、mindspore等框架下的模型均可转换为onnx后接入使用该方案

(2)OM/torch

路线一:通过om +python backend的方式进行服务化

路线二:通过torch_npu +python backend的方式进行服务化

路线三:通过torchAir+python backend的方式进行服务化

分别对应torch在线推理与OM的离线推理

四、Triton Inference Server特性

4.1 动态批处理

动态批处理 (Dynamic batching) 允许将一个或多个推理请求合并成一个batch,批量处理请求,提高资源利用率,提高吞吐,在config.pbtxt文件中添加下面即可开启该特性:

dynamic_batching {
	max_batch_size: 32
	max_queue_delay_microseconds: 10000
	preferred_batch_size: [4, 8, 16]
}
  • max_batch_size:最大的batch大小
  • preferred_batch_size:期望达到的batchsize大小,可以是一个数,也可以是一个数组
  • max_queue_delay_microseconds:单位是微秒,组batch的时间限制,超过该时间会停止组batch。

详情查看官方说明:

https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/tutorials/Conceptual_Guide/Part_2-improving_resource_utilization/README.html

以下示例展示了不使能动态批处理、使能动态批处理、使能动态批处理并设置延迟三种场景下的差异
在这里插入图片描述

4.2 多实例

在模型服务化部署中,单实例推理容易成为性能瓶颈,尤其是在小模型高QPS或多NPU场景下。Triton通过 Instance Group 提供多实例部署能力:

多实例特性支持生成单模型的多个副本,允许并行处理,以提高资源利用率,提高模型吞吐,在config.pbtxt文件中添加下面即可开启该特性:

  • count: 每个设备上的实例数量
  • kind: 设备类型(GPU/CPU)
  • gpus: 指定GPU设备 ID(默认使用所有GPU)
instance_group [{
	count: 2
	kind: KIND_CPU
}]

结合动态批处理和模型并行,以下示例展示了两个模型示例并行执行下,不使能动态批处理、使能动态批处理、使能动态批处理并设置延迟三种场景下的差异
在这里插入图片描述

4.3 模型组合

可以将多个模型,如 预处理模型、中间模型、后处理串联组合,由Triton统一调用并且进行内部的数据流转控制,简化复杂的流水线部署(tokenizer -> encoder -> decoder)。

五、Python backend方案

5.1 环境说明

对于Python backend,在NPU上使用triton service的环境可以基于两种方式

  1. 官方triton service镜像适配昇腾(推荐)

21.09版本之后自带Python Backend,镜像标签中以-py3 结束,基于已经安装编译triton service的官方镜像,安装昇腾底层软件栈CANN,torch_npu等组件

triton service官方镜像:https://catalog.ngc.nvidia.com/orgs/nvidia/containers/tritonserver/tags

  1. 昇腾官方镜像编译triton service

基于昇腾基础CANN镜像,编译triton service仓库,该方式涉及的编译情况比较复杂

5.2 仓库整体结构

triton service规定了model_repository实现时的目录结构,以如下形式

/

<model-name>/

  [config.pbtxt]

  [<output-labels-file> ...]

  <version>/

    <model-definition-file>

  <version>/

    <model-definition-file>

  ...

<model-name>/

  [config.pbtxt]

  [<output-labels-file> ...]

  <version>/

    <model-definition-file>

  <version>/

    <model-definition-file>

  ...

...

简单的例子

model_repository/

├── model1

│ ├── 1

│ │ └── model.py

│ └── config.pbtxt

└── model2

├── 1

│ └── model.py

└── config.pbtxt

model_repository是模型仓库根目录,可以包含多个模型

model*表示一个模型,文件夹名即为模型名称

1 表示版本信息,一个模型可以有多个版本,triton service可以同时管理多个版本(官方规定,需要单纯的数字命名)

model.py是Python backend推理实现文件,放在对应的版本目录下

config.pbtxt是每个模型的配置文件

5.3 model.py结构

model.py是Python backend对应的核心推理文件,实现模型的初始化,推理,释放等流程,具体需要实现TritonPythonModel类,包含initialize、execute、finalize方法,execute是核心推理逻辑,用户可以最大程度的自定义推理方法,这也是Python backend灵活的原因。

  • initialize(可选)

在模型首次加载到 Triton 后端进程时执行,用于完成一次性的初始化操作,例如权重加载、缓存结构构建、设备资源绑定等。通常这里完成所有需要在请求处理前准备好的工作。

  • execute(必选)

每次收到推理请求时由 Triton 调用,是模型的核心入口。在此方法中读取输入张量、执行前向计算、生成输出并返回响应。该方法必须实现,因为它直接承载模型的推理逻辑。

  • finalize(可选)

当模型实例从后台卸载或进程退出时调用,用于释放资源、关闭句柄、清理缓存等收尾任务。此阶段只执行一次,以确保不会遗留系统资源或内存泄露。

import triton_python_backend_utils as pb_utils

class TritonPythonModel:
	def initialize(self, args):
		"""
		模型初始化逻辑(类似构造函数)
		- args: dict,包含 model_repository 路径、model 名称、version 等信息
		- 这里可以加载模型权重、初始化依赖库等
		"""
		pass

	def execute(self, requests):
		"""
		推理核心函数
		- requests: list,包含多个 inference request(可能是 batch)
		- 必须返回 list of pb_utils.InferenceResponse
		"""
		responses = []
		for request in requests:
			# 获取输入张量
			in_tensor = pb_utils.get_input_tensor_by_name(request, "INPUT_NAME")
			in_numpy = in_tensor.as_numpy()
			# 模型计算逻辑(这里可以调用 PyTorch、TensorFlow、NumPy)
			out_numpy = in_numpy + 1 # 示例逻辑
			# 构造输出
			out_tensor = pb_utils.Tensor("OUTPUT_NAME", out_numpy)
			responses.append(pb_utils.InferenceResponse(output_tensors=[out_tensor]))
		return responses

	def finalize(self):
		"""
		资源清理逻辑(类似析构函数)
		- 在模型卸载时调用
		- 可以释放 GPU/CPU 资源,关闭文件句柄
		"""
		pass

5.4 config.pbtxt结构

config.pbtxt是一个protobuf格式的配置文件,定义了模型的名称、输入、输出等参数。

name: "model1" # 模型名称,必须与文件夹名一致

backend: "python" # 后端类型(Python 模型用 python)

max_batch_size: 8 # 最大 batch 大小(0 表示不支持 batching)

input [
{
name: "INPUT_NAME"
data_type: TYPE_FP32 # 输入数据类型 (FP32, INT64 等)
dims: [ -1, 224, 224, 3 ] # 输入维度,-1 表示动态维度
}
]
output [
{
name: "OUTPUT_NAME"
data_type: TYPE_FP32
dims: [ -1, 1000 ] # 输出维度
}
]

5.5 推理方式

由于推理执行的解耦,triton service的Python backend下的推理方式即为NPU上模型推理的方式,总共分为三种,在线推理,即基于pytorch框架下的推理,直接基于torch_npu推理或者使用torchair成图,分别对应pytorch下的eager和图模式推理方式;离线推理,即基于OM的推理方式,可以使用aisbench提供的infer interface或直接基于pyacl api来写推理逻辑

在这里插入图片描述

例如,对于yolo模型,我们使用两种推理方式接入Python backed:

在这里插入图片描述

六、GE Custom backend方案

6.1 自定义backend实现流程

Triton Inference Server提供了自定义backend的接入方式,对于NPU上使用,除了使用自带的Python backend,还可以使用自定义的NPU专属backend,此方案就是使用的GE图引擎,直接对onnx模型进行服务化。

通过对TRITONBACKEND_Backend类的实现,我们就可以自动定义backend,具体需要实现8个接口函数:

接口 功能
TRITONBACKEND_Initialize backend初始化,资源分配、配置加载
TRITONBACKEND_Finalize backend收尾工作,资源释放、状态清理
TRITONBACKEND_ModelInitialize 解析并存储模型配置信息,如模型名称、输入输出shape、config.pbtxt 中的参数等
TRITONBACKEND_ModelFinalize 模型级别的收尾清理函数
TRITONBACKEND_ModelInstanceInitialize 模型实例初始化,解析实例的配置、初始化实例的运行环境
TRITONBACKEND_ModelInstanceFinalize 清理实例初始化时分配的资源
TRITONBACKEND_ModelInstanceExecute 真正执行模型推理的核心函数
TRITONBACKEND_BackendControl (可选) 封装后端的配置、资源管理、生命周期交互

其中,triton对于模型管理分为了层次抽象的四层六类:

ModelRepositoryManager负责扫描模型仓库目录,解析模型配置(config.pbtxt),识别模型版本,并触发模型加载流程;ModelLifeCycle是模型生命周期调度管理,负责模型的真正加载/卸载/更新;TritonModel类对应一个具体模型,管理该模型的所有推理实例TritonModelInstance,ModelState是与TritonModel紧密关联的状态管理类,一个TritonModel对象会对应一个唯一的ModelState,通过TRITONBACKEND_ModelSetState绑定在一起,后续后端的推理调用、资源分配、状态查询等操作,都由ModelState来承担;TritonModelInstance是实际执行推理的最小调度单元,拥有自己的线程上下文、device绑定、batch策略等,对应instance_group中设置的实例数;ModelInstanceState同样对应一个TritonModelInstance,掌控了单个推理实例的执行状态和资源细节,聚焦于实例级的运行时管理。

在这里插入图片描述

上层接口与类之间的调用关系如下

在这里插入图片描述

6.2 GE backend方案架构图

开源仓链接:https://gitcode.com/cann/triton-inference-server-ge-backend
在这里插入图片描述

6.3 GE backend特性

名称 介绍 支持情况
多实例 模型可同时处理多个请求,此特性需搭配多流并行或多卡使用
多卡支持 一个模型可同时跑在多张卡上,每张卡可配置>1 的实例
多卡负载均衡 多卡情况下能根据每张卡上任务数量动态分配请求 目前仅支持所有请求shape一致场景
动态batch 支持input、output 的0轴为可变场景
GE静态图 通过shape固定,实现初始化图时分配好所有显存,提高图执行效率
多流并行 静态图搭配
锁核 配置每一条stream使用Cube以及Vector核心数量,以便多stream情况下提高吞吐
非0轴动态 支持非0轴情况下的动态shape

6.4 GE backend具体实现

ge_backend实现主要有以下几个文件:

npu_ge.cpp 对应Triton 的7个Custom backend规范接口;

model_state.cpp 完成 ModelState创建、以及均衡器Scheduler的初始化;

scheduler.cpp 实现了执行块的计算、分配过程;

model_instance_state.cpp实现了Instance的初始化以及推理接口。

通过cmake 编译后,会生成相应.so 动态链接库,即GE backend的动态库

核心调用函数如下:

ONNX图解析,通过Parser接口将原始ONNX模型解析为图,可参考链接:使用Parser接口将原始模型解析为Graph-CANN商用版8.3.RC1-昇腾社区

在这里插入图片描述

异步图执行,通过GE提供的图开发C++接口ExecuteGraphWithStreamAsync实现,可参考链接:带参数配置的图运行(单进程单卡)-CANN商用版8.3.RC1-昇腾社区

在这里插入图片描述

调用流程如下:

  1. 调用GEInitialize进行系统初始化(也可在Graph构建前调用),申请系统资源
  2. 调用Session构造函数创建Session类对象,申请Session资源
  3. 调用AddGraph在Session类对象中添加定义好的图
  4. 调用aclInit接口,初始化acl
  5. 调用CompileGraph完成图编译
  6. 调用aclrtSetDevice指定运行的Device,调用aclrtCreateStream创建Stream,然后调用aclrtMallocHost、aclrtMalloc分别申请Host和Device内存
  7. 调用LoadGraph(异步执行Graph场景),加载图模型到6创建的Stream上
  8. 调用ExecuteGraphWithStreamAsync异步执行接口,运行Graph
  9. 调用aclrtSynchronizeStream阻塞程序运行,直到指定Stream中的所有任务都完成
  10. 调用aclrtFree、aclrtFreeHost释放内存;调用GEFinalize,释放系统资源;调用aclFinalize释放acl相关资源

在这里插入图片描述

6.5 模型适配流程

模型接入GE backend的流程如下:

在这里插入图片描述

仓库目录结构如下:

在这里插入图片描述

在config.pbtxt文件中,backend要填写我们自己的backend:npu_ge,并填写onnx对应的输入输出信息

在这里插入图片描述

六、服务启动与客户端侧逻辑

使用下述格式来拉起服务,其中

–model-repository指定模型仓库

–load-model指定加载的模型

tritonserver --model-repository=*** --load-model=***

客户端负责发送请求到triton service,需借助tritonclient组件

客户端侧代码示例如下:

import tritonclient.grpc as grpcclient

import numpy as np

import logging

# 配置日志

...

# Triton服务器配置

url = "localhost:8000"

model_name = "model"

# 创建Triton客户端

logger.info(f"连接到Triton服务器: {url}")

client = httpclient.InferenceServerClient(url=url)

#client = grpcclient.InferenceServerClient(url=url)

# 准备输入数据

...

# 创建Triton输入

...

# 准备输出

...

# 执行推理

logger.info(f"发送推理请求到模型: {model_name}")

response = client.infer(model_name, inputs=inputs, outputs=outputs)

# 检查响应状态

...

# 获取并打印结果

...

七、案例

本节以yolov9为例,介绍Triton+Python backend+om的服务化部署方法

  1. 获得onnx格式模型

yolov9模型通过torch.onnx.export接口导出,加载验证精度无误,导出正确。

  1. 填写confi.pbtxt文件

注意backend为python,这里的max_batch_size是10,图片分辨率为640*640。

3.实现model.py文件

使用ais_bench提供的推理api,实现模型推理,设置为static模式,调用non_max_suppression实现后处理,再返回请求。

from ais_bench.infer.interface import InferSession

from torch.utils.dlpack import to_dlpack, from_dlpack

import triton_python_backend_utils as pb_utils

import sys

import torch

import numpy as np

sys.path.append("/home/yolo9/yolov9")

from utils.general import non_max_suppression

class TritonPythonModel:

    def initialize(self, args):
        print(args)
        self.model_path = "/home/yolo9/yolov9s.om"
        self.device_id = 0
        self.session = InferSession(self.device_id, self.model_path)

        print("load om success")


    def execute(self, requests):
        responses = []
        for res in requests:
            images = from_dlpack(pb_utils.get_input_tensor_by_name(res, 'images').to_dlpack())
            outputs = self.session.infer([images], mode="static")
            preds = torch.tensor(outputs[0])
            output_tensor = non_max_suppression(preds, 0.25, 0.45)[0]
            output_tensor = pb_utils.Tensor('preds', np.array(output_tensor))
            response = pb_utils.InferenceResponse(output_tensors=[output_tensor])
            responses.append(response)
        return responses


    def finalize(self):
        print("Inference finalized")

服务启动成功示例截图:
在这里插入图片描述

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐