AI 推理优化 | Triton Inference Server 架构解析:单机多模型并发推理的工业级方案

一、为什么需要 Triton

一个典型的算法团队,线上跑的模型往往不止一个:CV 类、NLP 类、推荐类、自研 PyTorch 模型。最常见的"朴素部署"是:

复制代码
模型A → Flask 进程1 (GPU 0)
模型B → Flask 进程2 (GPU 0)   # 抢同一张卡
模型C → Flask 进程3 (GPU 1)

问题立刻来了:

  1. GPU 利用率低:每个进程各自占显存,小模型空闲时整张卡被占着

  2. 没有动态批处理:客户端单次请求,GPU 算力被浪费

  3. 并发调度混乱:多个进程抢卡,互相拖累

  4. 运维成本高:N 个模型 N 套部署代码、N 个健康检查

Triton 的定位就是:一个服务进程,统一托管所有模型,把 GPU 利用率、批处理、并发调度全部做对 。它不是某个模型的专属加速(那是 vLLM/TensorRT-LLM 的活),而是模型服务的"操作系统"。


二、整体架构

Triton 的核心是一个 C++ 主进程,内部按职责分三层:

四个关键点:

  • 模型仓库 :每个模型一个目录,含 config.pbtxt 描述输入输出、后端类型、实例数

  • 调度器:决定请求怎么聚合成 batch、哪个实例处理

  • 后端(Backend):真正执行推理的引擎,支持 TensorRT / PyTorch / ONNX / Python / 自定义 C++

  • 实例组(Instance Group):一个模型可以起多个副本(甚至跨多卡),Triton 自动负载均衡


三、核心机制:Dynamic Batching 源码走读

这是 Triton 吞吐高的根本原因。

3.1 原理

动态批处理不要求客户端主动组 batch。客户端照常发单条请求,Triton 在服务端把时间窗口内到达的多个请求自动拼成 batch:

复制代码
时刻 t:   req1(bs=1)  req2(bs=1)  req3(bs=1)  req4(bs=1)
                    │
                    ▼  Dynamic Batcher 聚合成
            batched_infer(bs=4)  ──▶ GPU 一次算完 ──▶ 拆回 4 个结果

GPU 最怕 bs=1 的小请求------算力利用率可能不到 10%。拼成 bs=32 一次算,利用率直接拉满。

3.2 源码位置与逻辑

核心在 src/core/scheduler.cc 的 DynamicBatchScheduler:

objectivec 复制代码
// 伪代码:动态批处理调度核心循环
void DynamicBatchScheduler::SchedulerThread() {
    while (!stop_) {
        std::vector<InferenceRequest*> requests;
        // 1. 从队列取请求,直到达到 max_batch_size 或 queue_delay 超时
        GetRequestsUntilTimeout(
            &requests,
            config_.max_batch_size(),          // config 里配置的批上限
            dynamic_batching_.max_queue_delay_microseconds());
​
        if (requests.empty()) continue;
​
        // 2. 把多个请求拼成 batch,复用同一块输入 tensor
        auto batched = ComposeBatch(requests);
​
        // 3. 交给某个空闲的模型实例执行
        std::shared_ptr<ModelInstance> inst =
            GetIdleInstance();
        inst->Execute(batched, [requests](Status s, Response resp) {
            // 4. 执行完把 batch 结果拆回给各个原始请求
            ScatterResponse(resp, requests);
        });
    }
}

两个关键参数(都在 config.pbtxt 里):

  • max_batch_size:一个 batch 最多塞多少请求

  • max_queue_delay_microseconds:最多等多久凑 batch(延迟 vs 吞吐的旋钮)


四、实战:双模型部署

假设我们要同时托管 ResNet50(图像)和 Llama-3-8B(文本)。

4.1 模型仓库结构

objectivec 复制代码
model_repo/
├── resnet50/
│   ├── config.pbtxt
│   └── 1/
│       └── model.onnx
└── llama3-8b/
    ├── config.pbtxt
    └── 1/
        └── model.onnx   # 或用 tensorrtllm 后端

4.2 ResNet50 的 config.pbtxt

Puppet 复制代码
name: "resnet50"
platform: "onnxruntime_onnx"      # 用 ONNX Runtime 后端
max_batch_size: 32                # 动态批处理上限
​
input [
  {
    name: "input"
    data_type: TYPE_FP32
    dims: [ 3, 224, 224 ]
  }
]
output [
  {
    name: "output"
    data_type: TYPE_FP32
    dims: [ 1000 ]
  }
]
​
# 动态批处理:最多等 100us 凑批
dynamic_batching {
  max_queue_delay_microseconds: 100
}
​
# 起 2 个实例,跨 GPU 0/1 提升并发
instance_group [
  {
    count: 2
    kind: KIND_GPU
    gpus: [ 0, 1 ]
  }
]

4.3 Llama-3 的 config.pbtxt(关键片段)

Puppet 复制代码
name: "llama3-8b"
platform: "tensorrtllm"           # 用 TensorRT-LLM 后端,复用前面文章优化
max_batch_size: 8
​
input  [ { name: "prompt"  , data_type: TYPE_STRING, dims: [ 1 ] } ]
output [ { name: "completion", data_type: TYPE_STRING, dims: [ 1 ] } ]
​
dynamic_batching { max_queue_delay_microseconds: 200 }
​
instance_group [ { count: 1, kind: KIND_GPU, gpus: [ 0 ] } ]

4.4 启动服务

bash 复制代码
docker run --gpus all -p 8000:8000 -p 8001:8001 -p 8002:8002 \
  -v $(pwd)/model_repo:/models \
  nvcr.io/nvidia/tritonserver:24.08-py3 \
  tritonserver --model-repository=/models

健康检查:

bash 复制代码
curl http://localhost:8000/v2/health/live   # 返回 200 即存活
curl http://localhost:8000/v2/models         # 列出已加载模型

4.5 Python 客户端

python 复制代码
import tritonclient.http as httpclient
import numpy as np
​
client = httpclient.InferenceServerClient("localhost:8000")
​
inputs  = [httpclient.InferInput("input", [1, 3, 224, 224], "FP32")]
outputs = [httpclient.InferRequestedOutput("output")]
​
img = preprocess("cat.jpg").astype(np.float32)
inputs[0].set_data_from_numpy(img)
​
# 客户端始终发单条请求,Triton 后端自动凑批
result = client.infer("resnet50", inputs, outputs=outputs)
print(result.as_numpy("output").argmax())

五、性能实测

环境:1×A100 80G,Triton 24.08,对比"裸 PyTorch + Flask 单请求服务"。

测试方式 :用 perf_client(Triton 自带压测工具)发 100 并发、持续 60 秒。

指标 裸 Flask 服务 Triton (动态批处理) 提升
ResNet50 吞吐 (img/s) 410 2580 6.3×
P50 延迟 24 ms 9 ms ---
P99 延迟 180 ms 45 ms ---
GPU 利用率(平均) 18% 87% ---
同时托管 2 模型 需 2 进程 1 进程 运维↓

动态批处理把 GPU 利用率从 18% 拉到 87%,这就是吞吐差 6 倍的本质原因。


六、Triton vs vLLM vs TorchServe

容易混淆,一次讲清边界:

维度 Triton vLLM TorchServe
定位 多模型服务"操作系统" LLM 专属推理引擎 PyTorch 模型服务
批处理 通用动态批处理 Continuous Batching 简单动态批
多后端 ✅ TRT/PyTorch/ONNX/Python ❌ 仅 LLM ✅ 但弱
LLM 优化 靠 tensorrtllm 后端 原生 PagedAttention ❌
适用 多模型混合生产 大模型高并发 快速起 PyTorch 服务

结论:大模型高并发用 vLLM;多个异构模型统一托管用 Triton;Triton 里跑 LLM 建议走 tensorrtllm 后端,性能和 vLLM 接近。


七、踩坑记录

问题 现象 解决
max_batch_size=0 却开动态批 启动报错 动态批要求 max_batch_size>0
多实例抢同一张卡 OOM 加载失败 instance_group 的 gpus 分散到不同卡
Python 后端冷启动慢 首个请求 3s+ 在模型 __init__ 里预热
客户端 batch 维度错 推理报错 输入 dims 不含 batch 维,由 Triton 拼
GPU 利用率仍低 请求太稀疏 调大 max_queue_delay 凑更多批
模型更新不生效 旧版本在跑 用 model_control 显式 unload/load

八、总结

  • Triton 把"多模型统一服务化"的工程难题一次性解决:模型仓库 + 调度器 + 多后端

  • 动态批处理是吞吐核心,源码上就是"凑批 → 复用 tensor → 拆结果"三步

  • config.pbtxt 一个文件搞定输入输出、批处理、实例数、跨卡

  • 和 vLLM 不冲突:大模型走 vLLM,异构模型混部走 Triton(LLM 用 tensorrtllm 后端)

下一篇:我们已经有了高并发推理服务(vLLM / Triton),但 AI Agent 怎么编排多个模型协作?周三的文章我们用 LangGraph vs AutoGen 把多 Agent 框架彻底讲透。

往期回顾:

相关推荐
Java的搬运工5 小时前
【无标题】
python·机器学习·docker·fastapi·模型部署·mlops·ai工程化
HIT_Weston2 天前
233、【AI】【模型部署】基座模型研究:基座与 Instruct 的差别
人工智能·模型部署
蔡俊锋8 天前
华为昇腾 960 超节点发布:4096 卡、5500 个光引擎替 4.8 万光模块——国产算力拐点到了吗?
大模型·模型部署·ai架构·国产算力·华为昇腾
进阶的猪8 天前
RKNN C API模型部署完整流程
rknn·模型部署·c api
HIT_Weston10 天前
227、【AI】【模型部署】基座模型研究:RoPE 旋转位置编码
人工智能·模型部署
My的梦想已实现10 天前
代工显卡驱动更新
nvidia·显卡驱动·魔改·代工显卡
DeeplyMind10 天前
Part 2.1 — uvm_va_block_t 解剖:每页位图与 PTE 状态
nvidia·uvm
HIT_Weston14 天前
224、【AI】【模型部署】基座模型研究:Qwen2 架构总览与 config 对照
人工智能·模型部署
thesky12345616 天前
用 ONNX Runtime 把 PyTorch 模型变成跨平台极速推理引擎:导出、优化、量化完整实
人工智能·深度学习·模型部署