cuda编程笔记(22)-- NVIDIA Triton Inference Server
Triton Inference Server 是什么
全称:NVIDIA Triton Inference Server(以前叫 TensorRT Inference Server)。
作用:在生产环境里 统一部署和管理推理任务,不用每个模型自己写推理服务。
特点:
支持 多框架(TensorRT, PyTorch, TensorFlow, ONNX Runtime, JAX, RAPIDS…)。
支持 多 GPU / 多模型调度,提升 GPU 利用率。
提供 HTTP/gRPC 接口,方便接入后端系统。
内置 动态 batch、并发执行、模型 ensemble 等性能优化机制。
一句话总结:TensorRT 是推理引擎,Triton 是推理服务平台。
Triton 的核心功能
多框架支持
TensorRT / ONNX Runtime / PyTorch TorchScript / TensorFlow SavedModel 都能直接加载。
多模型管理
一个 Triton 实例可以同时管理成百上千个模型。
支持 热加载(不用停服务就能更新模型)。
动态批处理 (Dynamic Batching)
自动把多个小请求合并成一个大 batch,提高 GPU 吞吐。
比如在线服务场景,很多用户发来
batch=1
请求,Triton 能拼接成batch=32
一次跑完。
模型 Ensemble Pipeline
可以把多个模型串起来(预处理 → 主模型 → 后处理),由 Triton 内部调度。
类似数据流图,减少客户端调用复杂度。
多实例 & 并发执行
一个模型可以开多个实例,跑在不同 GPU / stream 上,支持并行执行。
推理请求接口
HTTP/REST 和 gRPC 两种接口,方便后端系统调用。
Triton 和 TensorRT 的关系
TensorRT:底层推理引擎,做 算子融合、精度优化 (FP16/INT8)、内存调度,提升单模型单 GPU 性能。
Triton:在 TensorRT 之上做 服务化和调度,负责 多模型管理、batch 合并、并发调度。
可以理解为:
TensorRT = 单个模型的“赛车引擎”。
Triton = 把一堆赛车调度在赛道上高效跑起来的“车队管理系统”。
Triton 的工作流程(简化版)
模型存放在 Model Repository(本地文件夹 / 云存储)。
模型的存放必须是这个结构;有一个模型名的文件夹,里面有一个模型配置文件config;还有一个1文件夹(必须叫这个),有一个引擎文件(可以是onnx或者plan),但是文件名必须是model
models/├── resnet50/│ ├── 1/│ │ └── model.plan # TensorRT 引擎文件│ └── config.pbtxt # 模型配置文件
启动 Triton Server(Docker 常用)。
单看可能看不懂,后面会有具体例子
docker run --gpus=all -v /path/to/models:/models nvcr.io/nvidia/tritonserver:23.10-py3 tritonserver --model-repository=/models
客户端发请求(HTTP/gRPC)。
不管客户端是啥类型,可以往这个Triton Server发送请求,获得推理后的结果。
下载使用Triton
操作系统用Linux;肯定要有GPU;推荐用docker,之后的使用都是在docker上的。如果不用docker,很多配置不一定对的上,将会更加麻烦。
拉取Triton镜像
docker pull nvcr.io/nvidia/tritonserver:23.10-py3
构造文件夹
models/├── resnet50/│ ├── 1/│ │ └── model.onnx # TensorRT 引擎文件│ └── config.pbtxt # 模型配置文件
这个模型可以去网上找,但是我去网上找发现动态batch匹配不了,干脆直接用python生成一个了
import torch
import torch.nn as nn
import torchvision.models as models
import os# -----------------------------
# 1. 创建目录
# -----------------------------
model_dir = "/home/huangxy/models/resnet50/1"
os.makedirs(model_dir, exist_ok=True)# -----------------------------
# 2. 初始化 ResNet50
# -----------------------------
resnet50 = models.resnet50(pretrained=True)
resnet50.eval() # 推理模式# -----------------------------
# 3. 定义动态 batch 输入
# -----------------------------
# Triton要求输入 shape: [B, 3, 224, 224],B 动态
dummy_input = torch.randn(1, 3, 224, 224) # batch=1 占位
dynamic_axes = {'gpu_0/data_0': {0: 'batch_size'}, # 输入第0维动态'gpu_0/softmax_1': {0: 'batch_size'} # 输出第0维动态
}# -----------------------------
# 4. 导出 ONNX
# -----------------------------
onnx_path = os.path.join(model_dir, "model.onnx")
torch.onnx.export(resnet50,dummy_input,onnx_path,input_names=['gpu_0/data_0'],output_names=['gpu_0/softmax_1'],dynamic_axes=dynamic_axes,opset_version=17, # 最新 ONNX opsetdo_constant_folding=True
)print(f"ONNX 模型已生成: {onnx_path}")
config文件可以参考这个
name: "resnet50"
platform: "onnxruntime_onnx"#这里用的onnx,所以这么写,用.plan、.pt啥的就不是这么写了
max_batch_size: 8 #大于0代表接受动态batch推理input [{name: "gpu_0/data_0"#这个得和模型设置的一样data_type: TYPE_FP32dims: [3,224,224]#这是resnet的输入维度(不包括batch)}
]output [{name: "gpu_0/softmax_1"#这个也是data_type: TYPE_FP32dims: [1000]#输出维度也不包括batch}
]
启动docker里的Triton
docker run --gpus=all --rm -it \-v ~/models:/models \-p 8000:8000 -p 8001:8001 -p 8002:8002 \nvcr.io/nvidia/tritonserver:23.10-py3 \tritonserver --model-repository=/models
--gpus=all
把 所有可见的 GPU 分配给容器。
(相当于容器里的 Triton 能使用宿主机的 GPU 进行推理。)
--rm
容器退出后自动删除,不保留历史。
(方便测试,避免到处都是旧容器残留。)
-it
-i
:交互模式(保持 STDIN 打开)-t
:分配一个伪终端
👉 合起来就是让你能看到 Triton 的日志,并能 Ctrl+C 停止。
-v ~/models:/models
把 宿主机的 ~/models
目录 挂载到容器内的 /models
路径。
这样 Triton 就能读到你放在宿主机的模型。
比如你在宿主机放
/home/huangxy/models/resnet50/1/model.onnx
容器里能直接访问/models/resnet50/1/model.onnx
-p 8000:8000 -p 8001:8001 -p 8002:8002
端口映射,把容器内的 Triton 服务暴露给宿主机:
8000 → HTTP endpoint
8001 → gRPC endpoint
8002 → Prometheus metrics
你在本机访问 http://localhost:8000/v2/health/ready
就能检查服务。
nvcr.io/nvidia/tritonserver:23.10-py3
我们之前下载的镜像
tritonserver --model-repository=/models
这是容器启动后运行的命令:
tritonserver
:启动 Triton 推理服务--model-repository=/models
:指定模型仓库目录(刚才挂载的/models
)
这条命令的效果是:
启动一个带 GPU 的临时 Triton 容器
把你本地的
~/models
映射进去在 8000(HTTP)、8001(gRPC)、8002(Metrics)端口暴露服务
并用
/models
目录里的模型仓库启动 Triton
客户端发送请求测试
import numpy as np
import requests
import json# Triton HTTP endpoint (模型推理接口地址)
# 格式: http://<host>:8000/v2/models/<model_name>/infer;这是http的请求推理的路径,gRPC会有不同
# 这里模型名是 "resnet50",端口 8000 是 Triton HTTP 服务
TRITON_URL = "http://localhost:8000/v2/models/resnet50/infer"# ====== 构造输入数据 ======
# 创建一个随机输入,模拟 1 张图片
# ResNet50 的输入格式是 [batch, channel, height, width]
batch_size = 1
input_data = np.random.rand(batch_size, 3, 224, 224).astype(np.float32)# Triton HTTP 请求体
payload = {"inputs": [{# 输入名字必须和模型配置 (config.pbtxt / onnx graph) 一致"name": "gpu_0/data_0",# 输入数据的形状,必须与模型要求一致"shape": list(input_data.shape),# 数据类型,对应 Triton 的 FP32"datatype": "FP32",# 数据展平为一维数组,并转为 Python 列表"data": input_data.flatten().tolist()}],"outputs": [{"name": "gpu_0/softmax_1"}]
}# ====== 发送 HTTP POST 请求给 Triton ======
response = requests.post(TRITON_URL, json=payload)
# ====== 解析响应 ======
if response.status_code == 200:# Triton 返回的结果是 JSON 格式result = response.json()# 输出张量数据,转成 numpy 数组output_data = np.array(result['outputs'][0]['data'])# 打印输出形状 (这里应该是 [1000],对应 ImageNet 分类)print("Output shape:", output_data.shape)# 打印概率最大的前 5 个类别索引print("Top-5 indices:", output_data.argsort()[-5:][::-1])
else:print("Error:", response.status_code, response.text)
输出结果:
Output shape: (1000,)
Top-5 indices: [610 549 783 446 892]
这样就完成了一个远程推理的过程