小模型在昇腾NPU上的推理部署:【小模型Triton Inference Server服务化方案】
作者:昇腾实战派
知识地图: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,
- triton service本身是C++开发的,调用python性能无法达到最好,有Python侧的开销,这是Python backend天生的劣势
- Python backend涉及适配model.py文件的三个接口,支持接入推理路线的种类最多,但有一定适配工作量,而GE backend单独针对onnx格式,基本没有代码开发量
- 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。
详情查看官方说明:
以下示例展示了不使能动态批处理、使能动态批处理、使能动态批处理并设置延迟三种场景下的差异
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的环境可以基于两种方式
- 官方triton service镜像适配昇腾(推荐)
21.09版本之后自带Python Backend,镜像标签中以-py3 结束,基于已经安装编译triton service的官方镜像,安装昇腾底层软件栈CANN,torch_npu等组件
triton service官方镜像:https://catalog.ngc.nvidia.com/orgs/nvidia/containers/tritonserver/tags
- 昇腾官方镜像编译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-昇腾社区

调用流程如下:
- 调用GEInitialize进行系统初始化(也可在Graph构建前调用),申请系统资源
- 调用Session构造函数创建Session类对象,申请Session资源
- 调用AddGraph在Session类对象中添加定义好的图
- 调用aclInit接口,初始化acl
- 调用CompileGraph完成图编译
- 调用aclrtSetDevice指定运行的Device,调用aclrtCreateStream创建Stream,然后调用aclrtMallocHost、aclrtMalloc分别申请Host和Device内存
- 调用LoadGraph(异步执行Graph场景),加载图模型到6创建的Stream上
- 调用ExecuteGraphWithStreamAsync异步执行接口,运行Graph
- 调用aclrtSynchronizeStream阻塞程序运行,直到指定Stream中的所有任务都完成
- 调用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的服务化部署方法
- 获得onnx格式模型
yolov9模型通过torch.onnx.export接口导出,加载验证精度无误,导出正确。
- 填写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")
服务启动成功示例截图:
更多推荐




所有评论(0)