Triton推理服务接昇腾NPU,GE后端怎么搭?
NVIDIA Triton Inference Server通过GE Backend实现对昇腾NPU的支持,将Triton的调度能力与CANN的GE图执行引擎对接。GE Backend采用三层架构设计:Triton C API接口层负责通信,适配层完成Tensor格式转换,执行层调用CANN Runtime进行推理。部署时需先用ATC工具将ONNX模型转为.om格式,并配置Triton使用GE B
前言
NVIDIA的Triton Inference Server是推理服务的事实标准——做模型服务化、多模型调度、动态batching,用Triton准没错。但Triton原生只支持NVIDIA GPU(CUDA),昇腾NPU想接入Triton生态,中间需要一个"翻译层"。
triton-inference-server-ge-backend就是这层翻译。它把Triton的请求调度和CANN的GE(Graph Engine)图执行引擎对接起来,让Triton能调度昇腾NPU上的模型推理,和调度GPU模型一样简单。
这篇会从设计理念讲起,拆解GE Backend的三层架构,最后搭一个完整的Triton+昇腾NPU推理服务。
设计理念:Triton不跑CUDA,跑GE
Triton的标准推理流程是这样的:
HTTP/gRPC请求 → Triton调度 → CUDA Backend → GPU执行 → 返回结果
昇腾NPU没有CUDA,但有GE——图编译引擎。GE把模型编译成NPU可执行的离线模型(.om),然后通过CANN Runtime在NPU上执行。所以思路很自然:把CUDA Backend换成GE Backend:
HTTP/gRPC请求 → Triton调度 → GE Backend → CANN Runtime → NPU执行 → 返回结果
GE Backend的核心设计理念是对Triton透明——Triton不关心后端是CUDA还是GE,只要Backend实现标准的TRITONBACKEND_ModelInstanceExecute接口就行。GE Backend就是这个接口的昇腾实现。
三层架构拆解
GE Backend分三层,每层职责清晰:
第1层:Triton C API接口层
这层是GE Backend的"门面",负责和Triton核心框架通信:
- ModelInstance:对应Triton的一个模型实例,管理模型的加载/卸载/执行生命周期
- 请求队列:接收Triton调度过来的推理请求,按batch组织
- 响应回传:推理完成后,把结果返回给Triton,Triton再返回给客户端
关键接口:
// Triton Backend标准接口——GE Backend要实现这三个核心函数
TRITONSERVER_Error* TRITONBACKEND_ModelInstanceExecute(
TRITONBACKEND_ModelInstance* instance,
TRITONBACKEND_Request** requests,
uint32_t request_count);
第2层:GE Backend适配层
这层是核心的"翻译"逻辑,把Triton的Tensor格式转成CANN的格式:
- Tensor转换:Triton的Tensor(CPU/GPU内存)→ CANN的
aclTensor(NPU内存) - 内存管理:管理NPU显存的分配和释放,支持zero-copy(如果输入已在NPU上)
- Batch合并:把多个请求的输入合并成一个batch,提高NPU利用率
第3层:CANN Runtime执行层
这层直接调用CANN的AscendCL API执行推理:
- 模型加载:用
aclmdlLoadFromFile加载.om离线模型 - 推理执行:用
aclmdlExecute执行模型 - 结果获取:从NPU取回推理结果
数据流转全链路
一个完整的推理请求,从客户端到NPU再返回,经过以下步骤:
1. 客户端发HTTP/gRPC请求
→ POST /v2/models/resnet/infer
→ body: {"inputs": [{"name": "input", "data": [...]}]}
2. Triton核心收到请求,路由到GE Backend
→ 根据model配置找到backend类型="ge"
→ 调用ModelInstanceExecute()
3. GE Backend适配层做Tensor转换
→ 解析请求中的input tensor
→ 分配NPU显存,用aclrtMemcpy把数据从CPU搬到NPU
4. CANN Runtime执行推理
→ aclmdlExecute()执行.om模型
→ 输出tensor在NPU显存中
5. GE Backend适配层做结果转换
→ 用aclrtMemcpy把输出从NPU搬回CPU
→ 封装为Triton的Response对象
6. Triton返回结果给客户端
→ {"outputs": [{"name": "output", "data": [...]}]}
代码实战:搭建Triton+昇腾NPU推理服务
步骤1:模型准备(ONNX → .om)
# 先用ATC把ONNX转为CANN离线模型
atc \
--model=resnet50.onnx \
--framework=5 \
--output=/models/resnet50/1/model \
--soc_version=Ascend910 \
--input_shape="input:1,3,224,224" \
--output_type=FP16
# 模型目录结构(Triton要求的格式)
# /models/resnet50/
# ├── config.pbtxt # 模型配置
# └── 1/
# └── model # .om文件(不带扩展名)
步骤2:Triton模型配置
# config.pbtxt - Triton模型配置文件
name: "resnet50"
platform: "ge_backend" # 指定GE Backend
max_batch_size: 32
input [
{
name: "input" # 要和ATC转换时的input_names一致
data_type: TYPE_FP16
dims: [3, 224, 224] # NCHW格式,不含batch维度
}
]
output [
{
name: "output" # 要和ATC转换时的output_names一致
data_type: TYPE_FP16
dims: [1000] # 1000类分类
}
]
# GE Backend特有配置
backend_parameters {
key: "soc_version"
value: { string_value: "Ascend910" }
}
instance_group [
{
count: 1 # 1个模型实例
kind: KIND_NPU # 运行在NPU上
}
]
dynamic_batching {
preferred_batch_size: [8, 16, 32] # 动态batching
max_queue_delay_microseconds: 1000 # 最多等1ms凑batch
}
代码讲解:platform: "ge_backend"是关键配置,告诉Triton用GE Backend而非CUDA Backend。instance_group的kind: KIND_NPU指定实例运行在NPU上。dynamic_batching配置让Triton自动把并发请求凑成batch,提高吞吐——比如8个并发请求各自发1张图,Triton会自动凑成batch=8一次推理,比逐个推理快3-4倍。
步骤3:启动Triton+GE Backend
# 启动Triton Server,加载GE Backend
tritonserver \
--model-repository=/models \
--backend-dir=/usr/local/triton/backends \
--log-verbose=1
# 预期日志输出
# I0525 10:00:00 model_repository_manager.cc:XXX] loading: resnet50
# I0525 10:00:01 ge_backend.cc:XXX] Loading .om model from /models/resnet50/1/model
# I0525 10:00:02 ge_backend.cc:XXX] Model loaded, device=0
# I0525 10:00:02 server.cc:XXX] Triton server is ready
步骤4:客户端调用
import tritonclient.http as http_client
import numpy as np
# 创建Triton客户端
client = http_client.InferenceServerClient(url="localhost:8000")
# 准备输入
input_data = np.random.randn(1, 3, 224, 224).astype(np.float16)
# 构造推理请求
inputs = [http_client.InferInput("input", [1, 3, 224, 224], "FP16")]
inputs[0].set_data_from_numpy(input_data)
outputs = [http_client.InferRequestedOutput("output")]
# 执行推理
result = client.infer("resnet50", inputs, outputs)
output = result.as_numpy("output")
print(f"分类结果: Top1 = {np.argmax(output)}")
踩坑实录
坑1:模型没ATC转换,直接放ONNX进Triton
现象:Triton启动时报错Failed to load model: model file is not .om format。
原因:GE Backend只认.om离线模型,不支持ONNX/PyTorch等框架格式。CUDA Backend能直接跑ONNX(通过TensorRT),但GE Backend没有这个能力。
解决:先用ATC转换为.om,再放进模型仓库。
# 错误:直接放ONNX
cp resnet50.onnx /models/resnet50/1/model # 报错
# 正确:ATC转换后再放
atc --model=resnet50.onnx --framework=5 --output=resnet50 --soc_version=Ascend910
cp resnet50.om /models/resnet50/1/model
坑2:多模型实例分配到同一张NPU卡,显存溢出
现象:同时部署3个模型,每个模型1个实例,都分配到NPU:0,总显存超限。
原因:默认情况下所有实例都分配到device 0,没有自动负载均衡。
解决:在config.pbtxt里手动指定不同实例的device。
# 错误:3个模型都跑在NPU:0
instance_group [{ count: 1, kind: KIND_NPU }]
# 正确:指定不同device
instance_group [
{ count: 1, kind: KIND_NPU, device: 0 } # 模型1在NPU:0
]
# 另一个模型的配置
instance_group [
{ count: 1, kind: KIND_NPU, device: 1 } # 模型2在NPU:1
]
坑3:动态batching时输入shape不一致
现象:两个请求分别发batch=1和batch=4,Triton合并batch时报错。
原因:.om模型编译时input_shape是固定的(如1,3,224,224),动态batch要求模型支持可变batch维度。
解决:ATC转换时指定动态batch范围。
# 错误:固定batch=1
atc --input_shape="input:1,3,224,224"
# 正确:支持batch 1-32
atc --input_shape="input:-1,3,224,224" --dynamic_dims="1,2,4,8,16,32"
性能对比数据
测试环境:Ascend 910 × 2,Triton 2.40,CANN 8.0。
| 配置 | QPS | P99延迟(ms) | 吞吐提升 |
|---|---|---|---|
| 单请求串行(无batching) | 45 | 22 | 1x |
| 动态batching(max_batch=8) | 180 | 45 | 4x |
| 动态batching(max_batch=32) | 520 | 62 | 11.5x |
| 2卡并行(各1实例) | 980 | 65 | 21.8x |
结尾
triton-inference-server-ge-backend是NVIDIA Triton对接昇腾NPU的桥梁,住在第3层编译层,用三层架构(Triton接口层→GE适配层→CANN Runtime层)实现了Triton调度和昇腾NPU执行的透明对接。
如果在昇腾NPU上做推理服务化,强烈建议用Triton+GE Backend。实测下来,动态batching+2卡并行,QPS比单请求串行高21倍。
昇腾CANN的推理服务生态还在持续完善。如果在用的过程中遇到啥问题,欢迎去AtomGit上的昇腾CANN开源社区逛逛,里面有一手资料和活跃社区。
社区链接
https://atomgit.com/cann/triton-inference-server-ge-backend
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐



所有评论(0)