Triton:06 NVIDIA Triton 基础部署
这篇文章解决什么问题
前面的 00 到 05 解决的是 OpenAI Triton 这一侧的问题,也就是怎样理解和编写 GPU kernel。接下来系列切到另一条主线:NVIDIA Triton Inference Server。
这条线关注的不是单个算子,而是怎样把一个已有模型组织成可调用、可检查、可继续调优的在线服务。
这一篇只解决最基础也最关键的一个问题:
如何把一个 ONNX 模型放进 NVIDIA Triton,成功启动服务,并完成一次最小推理闭环。
这里有意只做最小闭环,不讨论动态批处理、实例副本或压测工具。原因很简单:如果连最基本的模型加载、配置文件和请求调用都没有先打通,后续所有调优话题都没有稳定起点。
先明确 Triton Server 在做什么
用一句话概括,NVIDIA Triton Inference Server 解决的是:
怎样把模型组织成标准化的推理服务,而不是把推理逻辑散落在自写脚本或接口代码里。
它和“自己写一个 FastAPI 包住模型”的差别,不是能不能提供 HTTP 接口,而是是否具备一套围绕推理服务的标准结构,包括:
- 模型目录与版本管理;
- 后端选择;
- 模型元信息配置;
- 服务健康检查;
- 后续调度与压测入口。
从工程角度看,它的价值不在于替代所有业务层,而在于先把模型服务这一层标准化。
最小闭环长什么样
本文的最小闭环包含四个环节:
- 准备一个 ONNX 模型。
- 按 Triton 约定组织
model repository。 - 写出最小可用的
config.pbtxt。 - 启动服务并发送一次推理请求。
只要这四步成立,说明以下几个关键点都已经打通:
- Triton Server 能启动。
- 模型能被识别。
- 配置文件与模型输入输出一致。
- 客户端能拿到有效结果。
第一步:按 Triton 规范组织模型目录
Triton 并不是“把一个模型文件放到某个目录里就能跑”。它要求模型以固定结构出现在一个 model repository 中。
最常见的最小结构如下:
1 | models/ |
这里每一层都有明确语义:
models/是整个 model repository 根目录。simple_mlp/是模型名。config.pbtxt是 Triton 读取模型配置的入口文件。1/是模型版本目录,必须是数字。model.onnx是模型文件本体。
如果目录结构不符合这个约定,Triton 即使启动成功,也不会按预期加载模型。
为什么版本目录必须是数字
这不是随意设计出来的命名风格,而是 Triton 管理模型版本的基本机制。数字目录允许同一个模型在同一仓库下并存多个版本,例如:
1 | models/ |
这为后续灰度、对比和回滚提供了基础结构。即使当前只部署一个最小模型,也应该从一开始就遵守这个约定。
第二步:准备一个最小 ONNX 模型
为了让后续示例统一,沿用环境篇中导出的两层 MLP:
1 | 输入: [batch, 128] |
如果还没有导出模型,可以使用下面这段脚本:
1 | import os |
执行完成后,模型文件应位于:
1 | models/simple_mlp/1/model.onnx |
第三步:写出最小可用的 config.pbtxt
模型目录存在,不代表 Triton 就知道这个模型该怎样解释。真正把模型暴露成服务的是配置文件。
最小版本的 config.pbtxt 可以写成这样:
1 | name: "simple_mlp" |
这段配置里最容易出错的地方有三个。
name
必须与模型目录名一致。这里是 simple_mlp,因此目录也必须叫 simple_mlp。
backend
这里写成 onnxruntime,表示该模型由 ONNX Runtime backend 执行,而不是 TensorRT、PyTorch 或 Python backend。
dims
dims 只描述单条样本的形状,不包含 batch 维。batch 维由 Triton 结合 max_batch_size 统一解释。
因此:
- 输入真实形状是
[batch, 128] - 输出真实形状是
[batch, 10]
如果这里把 batch 维也写进 dims,配置就会和 Triton 的 batch 语义冲突。
第四步:启动 Triton Server
NVIDIA Triton Inference Server 最常见的运行方式是容器。假设当前目录下已经有 models/ 目录,可以这样启动:
1 | docker run --gpus all --rm \ |
这条命令里几个关键部分分别对应:
--gpus all:把 GPU 暴露给容器。-p 8000:8000:HTTP 推理接口。-p 8001:8001:gRPC 推理接口。-p 8002:8002:metrics 接口。-v $(pwd)/models:/models:把宿主机上的模型目录挂载到容器里。--model-repository=/models:告诉 Triton model repository 的根路径。
这里需要注意,本文默认命令在远端 Linux GPU 服务器上执行,不是 Windows 本地机。Windows 本地只适合作为 SSH 客户端和编辑环境。
如何判断服务是否真正启动成功
只看容器是不是在运行还不够。更可靠的判断至少包括两层。
第一层:看日志
启动成功后,日志里通常会出现三类服务启动信息:
- HTTP Service
- GRPC Inference Service
- Metrics Service
如果服务进程一启动就退出,优先检查:
model repository是否挂载正确;config.pbtxt是否有语法错误;- 模型文件是否与 backend 匹配。
第二层:做健康检查
即使容器还活着,也不代表服务已经 ready。最简单的 ready 检查是:
1 | curl -s http://localhost:8000/v2/health/ready |
如果返回空 JSON,说明服务层已经就绪。
如果要再进一步看模型元信息,可以请求:
1 | curl -s http://localhost:8000/v2/models/simple_mlp |
这样可以确认目标模型是否已被 Triton 识别。
第五步:发送一次最小推理请求
服务 ready 之后,还需要再确认一次:模型不只是“被看见了”,而是真的可以完成推理。
先安装 HTTP 客户端:
1 | pip install tritonclient[http]==2.40.0 |
然后使用下面这段最小客户端代码:
1 | import numpy as np |
如果输出形状是 (4, 10),说明最小闭环已经成立。
这个闭环的意义在于,它同时验证了:
- 服务端接口可访问;
- 模型名称可解析;
- 配置文件里的输入输出定义是对的;
- ONNX backend 能完成实际推理。
这里为什么不直接讲动态批处理
很多教程会在第一次部署时就把 dynamic_batching、instance_group、并发参数一起塞进来。这样做信息量很大,但通常不利于定位问题。
如果当前目标只是先打通最小闭环,那么最稳妥的顺序应该是:
- 先保证模型能被正确加载。
- 再保证一次请求能顺利返回。
- 最后再引入调度与性能变量。
这样做的好处是,任何一个阶段出问题时,排查范围都比较清晰。
Triton 和自写接口的边界
这一节也顺带澄清一个常见问题:用了 Triton Server,并不代表业务层接口全部不需要了。
Triton 更像是标准化推理服务层,解决的是:
- 模型加载;
- 模型版本;
- backend 执行;
- 后续调度和压测。
而业务系统仍然可能需要:
- 身份认证;
- 业务协议封装;
- 请求编排;
- 结果缓存;
- 上层路由逻辑。
因此,把 Triton 理解为“模型服务层”,会比理解成“完整业务 API 替代品”更准确。
常见误区
误区一:把 config.pbtxt 当成可选文件
在很多最小教程里,读者容易把注意力全部放在 model.onnx 上,忽略 config.pbtxt。但对 Triton 来说,配置文件不是附属说明,而是模型服务定义的一部分。
误区二:把 batch 维写进 dims
这是最常见的配置错误之一。只要 max_batch_size 大于 0,dims 就描述单样本形状,而不是完整请求张量形状。
误区三:服务 ready 就等于模型可用
ready 只能说明服务层已经就绪,不能替代真实推理请求。只有客户端成功拿到正确形状的返回结果,才能说明模型加载与配置路径都正常。
结论
NVIDIA Triton Inference Server 的基础部署,关键不在于把容器拉起来,而在于建立一个稳定、可继续扩展的最小服务闭环:
- 模型按
model repository规范组织; config.pbtxt正确描述输入输出与 backend;- 服务成功启动并 ready;
- 一次最小推理请求可以拿到正确结果。
只要这一步打通,后续关于动态批处理、实例并发、压测和调优的讨论才有共同起点。
下一篇会继续沿着这条主线往前走,专门讨论 dynamic batching、instance group 以及吞吐和时延之间的权衡关系。
