在线判别模型推理服务架构体系:从一次限流报错到完整技术图景
本文源自一次线上问题排查引出的体系化梳理:从一条"请求被限流降级"的报错日志出发,层层下钻到 GPU 资源分配、模型部署形态、推理引擎选型,最终串起在线推理服务(Online Inference / Model Serving)的完整技术架构。文章采用自顶向下与自底向上两条路线展开,并对涉及的每个名词给出说明。
一、引子:一条报错日志告诉我们什么
一个模型服务对外提供推理接口,某天调用方开始收到这样的错误:
Request is Degraded ... strategy: cluster-qps, code: 429, msg: the current limit has been reached
拆解这条日志能读出四层信息:
- 发生了什么:请求被限流组件拒绝(reject),返回 HTTP 429(Too Many Requests)。
- 谁做的:服务端的限流中间件,按"集群 QPS"策略(整个服务集群每秒请求数上限)计数超额后触发。
- 后果是什么:被拒请求走了降级路径(服务降级,Service Degrade)——快速失败返回错误,而不是排队等待或拖垮服务端。
- 在哪儿改:限流阈值配置在服务治理平台上,按"服务 → 接口"维度管理,调大阈值或调整策略即可恢复。
这个入口的启示是:线上问题的表象(报错)背后,是一整套分层的服务治理体系。要真正理解一个模型服务,需要同时看两幅地图——治理地图(流量如何进来、被管控)和推理地图(模型如何部署、被执行)。下文分别展开。
二、自顶向下:一次推理请求的完整旅程
从调用方发起请求,到 GPU 算出结果返回,请求依次穿过以下六层。每层只关心自己的职责,上层不感知下层细节,这是整个架构解耦的关键。
调用方 │ ①服务发现:按服务名查询可用实例列表 ▼ ┌─────────────────────────────────────────────┐ │ ② RPC 服务层(一个"服务",即一组对等进程实例) │ │ 服务注册发现 / 负载均衡 / 限流 / 熔断降级 / 鉴权 │ │ 实例 = 一个独立部署的进程(容器),有 IP、端口 │ ├─────────────────────────────────────────────┤ │ ③ 推理服务框架层(进程内,如 Triton / TF Serving) │ │ 模型加载 / 版本管理 / 按模型名路由 / 动态批处理 │ ├─────────────────────────────────────────────┤ │ ④ 推理引擎层:加载与执行 │ │ (被模型服务器委托:文件解析、显存分配、 │ │ 权重上传、前向计算) │ │ ONNX Runtime / TensorRT / libtorch / vLLM │ ├─────────────────────────────────────────────┤ │ ⑤ 模型文件层(纯数据,无执行能力) │ │ ONNX / .pt / SavedModel / TensorRT engine │ ├─────────────────────────────────────────────┤ │ ⑥ 硬件层:GPU(显存)/ CPU │ └─────────────────────────────────────────────┘① 服务发现层
调用方不直连 IP,而是拿着服务名(一个全局唯一标识,如反向 DNS 风格的域名型 key)向注册中心查询当前存活实例列表,再由客户端负载均衡策略挑一个发送。实例上下线自动同步,调用方零改动——这是"加机器就能扩容"的基础。
② RPC 服务层:治理能力的所在地
一个服务对外是一张接口清单(Thrift/gRPC 风格的 IDL 定义),对内是一组对等实例。这一层承载所有"流量治理"能力:
- 限流(Rate Limiting):按接口维度设定 QPS 上限,超限拒绝,保护服务端不被打垮。策略有单机限流和集群限流(全服务共享计数)之分。
- 熔断降级(Circuit Breaking / Degradation):下游故障或自身过载时快速失败、返回兜底结果,避免故障级联。
- 负载均衡:把请求摊到各实例;配合健康检查自动摘除故障节点。
一个容易忽略的事实:限流配在服务接口维度,而模型部署在更细的粒度上(见第四节),所以"A 业务模型流量暴涨触发限流,会连带拒绝 B 业务的请求"这类问题是这种架构的固有权衡。
③ 推理服务框架层
每个实例进程内跑着一个模型服务器(Model Server),如 Triton Inference Server 或 TensorFlow Serving。它是指挥者而非执行者:决定和管理"什么模型、什么时候、以什么配置"存在于服务中,具体搬运由推理引擎完成(见 ④ 层)。它负责:
- 模型加载与生命周期:发起并管理模型的加载/卸载/版本切换(实际由推理引擎执行文件解析与显存分配),支持多模型共存、版本热更新。
- 按模型名路由:一个服务内可以挂几十个模型,请求带模型名,框架分发到对应模型。
- 动态批处理(Dynamic Batching):把短时间内的多个独立请求凑成一个 batch 一次前向,提升 GPU 利用率。
④ 推理引擎层
模型文件只是结构描述和权重数据,本身不会计算;模型服务器也不亲自执行。这一层解决的核心问题是——把一个静态的模型文件,变成 GPU/CPU 上高效执行的计算过程:解析网络图、加载权重到显存、把算子调度到硬件、管理显存分配与执行依赖。与上层的配合方式是"委托执行":模型服务器决定加载哪个模型、何时加载、用什么配置,并发起加载/推理指令;引擎接到指令后完成全部实际工作——读文件、分配显存、上传权重、执行前向,再把输出 tensor 交还。这也是"后端"概念的由来:模型服务器支持把哪几种引擎插进来执行。它负责:
- 图优化:算子融合、消除无效计算、重排执行顺序,减少 kernel 启动与访存开销。
- 量化压缩:FP32 降到 FP16/INT8,牺牲少量精度换取显存减半、吞吐成倍提升。
- 硬件适配编译:针对特定 GPU 卡型生成优化执行计划(TensorRT 的核心能力,代价是产物绑定卡型)。
- 执行调度:内存池化、异步执行、多流并发,减少 GPU 空转。
各引擎的差异主要在侧重:ONNX Runtime 通用跨硬件,TensorRT 极致优化但绑定 NVIDIA 卡型,vLLM 专为生成式解码做了 KV Cache 分页与请求级动态组批(详见第六、七节)。
⑤ 模型文件层
纯静态数据(见第六节),描述网络结构和权重,不含执行逻辑。
⑥ 硬件层
GPU 的**显存(VRAM)**是推理服务最稀缺的资源:模型权重常驻显存,生成式模型还有动态增长的 KV Cache(见第七节)。资源管理视角详见第八节。
三、自底向上:从一张 GPU 卡到一项在线业务
反向走一遍,看每一层"为什么存在"。
硬件(GPU)。一张卡有固定显存和算力,上面能同时塞多个模型,只要显存放得下。物理资源本身没有"服务"概念。
模型文件。训练框架产出的权重+结构描述,是"死"的。要让它"活"起来需要有人解析、分配显存、调度算子执行——于是有了推理引擎。
推理引擎(ONNX Runtime、TensorRT 等)。能加载模型并执行前向计算,但只是个库/进程级组件:没有 API 服务能力、没有批调度、没有多模型管理、没有版本热更新。生产环境需要把这些工程能力补齐——于是有了模型服务器。
模型服务器(Triton、TF Serving)。补齐了服务化和多模型管理,但通常对外说的是自己的 HTTP/gRPC 协议。企业内部已有成熟的 RPC 体系和治理能力(服务发现、限流、监控),希望模型服务也纳入同一套体系统一管理——于是外面再包一层 RPC 服务进程。
RPC 服务层。对调用方暴露统一的接口协议和治理能力;一个服务名下可以管理所有推理实例。当服务规模变大——几十种业务模型、上百实例、多个机房——需要一层逻辑划分来管理资源配额、发布节奏和容灾——于是引入了服务分组(Group):
- 分组是服务内部的逻辑单元,通常按业务模型划分(不是把多个服务归类,而是把一个服务切分)。
- 每个分组拥有独立的资源配额(哪些机房、多少卡)、独立的发布/回滚/事件状态、独立的容灾与弹性伸缩评估。
- 分组之下才是实例;实例是具体执行体,落在某个机房、某个资源队列上。
最终形成三级包含关系:服务(对外门面)→ 分组(资源+发布+治理单元)→ 实例(执行体)。
调用方。只认服务名 + 接口 + 模型名,完全不感知内部分组怎么切、实例怎么漂移。中间某次发布把模型从 A 组迁到 B 组,调用方零改动——这正是分层解耦的回报。
四、关键架构决策:模型路由的三级坐标
把请求定位到一个具体模型,需要三个坐标,分别回答三个问题:
| 坐标 | 回答的问题 | 所在层 |
|---|---|---|
| 服务名(Service) | 请求发给哪个进程组 | RPC 层 |
| 模型名(Model) | 请求算什么 | 请求体参数,框架分发 |
| 分组(Group) | 模型跑在哪批实例、归谁管 | 平台侧配置 |
服务名解决"发给谁"(服务发现、连接、治理);模型名解决"算什么"(框架按名字分发到加载好的模型);分组解决"跑在哪、怎么管"(实例与模型的绑定关系、资源份额、发布节奏)。分组与模型非严格一对一——一个分组可管理一个业务模型的多个版本,一个实例也可同时加载同组多个模型以摊薄资源。
五、推理引擎与后端体系:Triton 的"多面手"本质
Triton 声称"后端可挂 ONNX、TensorRT、PyTorch、scikit-learn"——这句话需要拆开理解,因为四个名词根本不在同一类别上:
5.1 四类角色的本质区别
| 名词 | 本质类别 | 职责 |
|---|---|---|
| PyTorch | 训练框架 | 训练模型,产出.pt权重;自带运行时可推理 |
| scikit-learn | 传统 ML 库 | 训练线性回归/树模型等,产出 pickle 文件 |
| ONNX | 模型交换格式 | 标准化模型描述(算子+权重的 protobuf),本身不负责加载执行 |
| TensorRT | 推理优化引擎 | 深度编译优化(算子融合、FP16/INT8 量化、按卡型调优),产出绑定特定 GPU 的引擎文件 |
5.2 "后端(Backend)"的真正含义
后端 =Triton 支持把哪几种执行引擎插进来。onnxruntime后端内部封装的是 ONNX Runtime 引擎;tensorrt后端封装 TensorRT;pytorch后端封装 libtorch。Triton 自己不解析任何模型文件——加载与执行全部委托给引擎,自己只做服务化编排。
因此完整分层是:
Triton(服务层:API、批调度、多模型管理) └→ 推理引擎(执行层:ONNX Runtime / TensorRT / libtorch) └→ 模型文件(描述层:ONNX / .pt / engine)一个类比:ONNX 如同标准化的菜谱文件格式(统一的用料与步骤写法),ONNX Runtime 是照谱做菜的厨师,TensorRT 则是按自家厨房(特定卡型)改编出最优做法的主厨,Triton 是统筹接单、排桌、上菜流程的餐厅经理。
5.3 典型生产流水线
PyTorch 训练 → 导出 ONNX(与训练框架解耦)→ TensorRT 编译优化(绑定卡型) → Triton 加载执行 → RPC 层对外服务各环节可按需省略:轻量场景直接挂 ONNX 跑 ONNX Runtime 即可;追求极致吞吐才引入 TensorRT。Triton 同一服务内可混挂不同后端的模型——这正是"多面手"的价值:不同团队产出的模型格式五花八门,统一交给一个模型服务器实例群承载。
六、判别式 vs 生成式:模型类型决定推理架构
这是整个选型体系的第一判据。模型类型一变,瓶颈就变,优化方向随之全变。
6.1 两类模型的本质差异
| 维度 | 判别式模型(Discriminative) | 生成式模型(Generative) |
|---|---|---|
| 数学目标 | 直接学 P(y|x):输入到输出的映射 | 学数据分布 P(x,y) 或 P(x_t+1|x_1…t) |
| 典型代表 | 线性回归、逻辑回归、SVM、BERT 分类、embedding 模型 | GPT/LLaMA/Qwen 等自回归 LLM、朴素贝叶斯、HMM |
| 推理形态 | 一次前向传播出结果,无状态残留 | 逐 token 循环解码,KV Cache 常驻显存 |
| 瓶颈 | 算力 + 批调度 | KV Cache 显存管理 + 长尾调度 |
| 显存画像 | 权重固定,多模型=权重叠加 | KV Cache 随并发数与上下文长度动态膨胀 |
| 输出形态 | 完整 tensor 一次返回 | 流式吐 token |
一个常见的概念分层:统计学语境下"生成式"指建模数据分布(朴素贝叶斯、GPT 都算);部署语境下"生成式"特指自回归逐 token 解码——后者是前者的子集延伸,正是自回归把"生成"变成了循环计算,才催生了专门的推理引擎。
6.2 推理引擎的两条路线
Triton 路线(判别式):优化核心是动态批处理——攒请求凑批一次前向。挂 ONNX/TensorRT/PyTorch 等多后端,服务分类、打标、评分、embedding、相似度等单次前向任务。
vLLM 路线(生成式 LLM):专为自回归解码设计,三大核心技术——
- PagedAttention:把 KV Cache 像操作系统内存分页一样切块管理,消除碎片,显存利用率大幅提升。
- Continuous Batching:新请求随时插入正在生成的批次,无需等整批完成,吞吐提升数倍到数十倍。
- OpenAI 兼容接口:原生 chat/completions,天然匹配流式生成业务。
两者不互斥:Triton 可将 vLLM 作为 backend 挂入。选型口诀:模型只"判断"用 Triton,模型要"写东西"用 vLLM。
七、资源管理视角:GPU 卡数到底由什么决定
从资源台账看,一个模型服务涉及四个概念,各自决定一件事:
- 单实例规格(决定卡数):每个实例声明"CPU 核数 / 内存 / GPU 型号 × 卡数"。配置列中
16核/64G/A30-24G*1即单实例挂 1 张 A30。GPU 数量由规格决定,与队列无关——同一队列完全可混跑 GPU 实例和纯 CPU 实例(后者配置无 GPU 段)。 - 资源额度(决定上限):跟组织(项目组)走。申请新实例时从项目组剩余额度里扣。额度不足时需先扩额度再申请。
- 资源队列(决定调度位置):实例被调度到哪个机房、哪个资源池。队列本身不携带卡数信息,只决定"资源从哪个池里扣"。
- 分组配额(决定份额):总卡数按分组切份额(各组配额之和=服务总卡数),这是比实例列表更上层的资源台账。
核心公式:总 GPU 数 = Σ(每个实例规格中声明的卡数),与队列无换算关系。同理:
- 卡数跟着单实例规格走;
- 额度跟着项目组走;
- 调度位置跟着队列走;
- 份额跟着分组走。
另一个易错点:实例总数 ≠ GPU 实例数。一个服务常混布大量纯 CPU 实例(做预处理、轻量模型、参数服务),统计 GPU 必须按配置过滤。
八、名词速查表
| 名词 | 类别 | 一句话说明 |
|---|---|---|
| 在线推理 / Model Serving | 领域 | 将训练好的模型部署为常驻服务,对外提供实时推理接口 |
| 服务(Service) | RPC 层 | 一组对等进程实例 + 一张接口清单,对外一个全局服务名 |
| 实例(Instance) | 部署层 | 一个独立部署的进程/容器,有 IP、端口、资源规格 |
| 服务发现(Service Discovery) | 治理 | 客户端按服务名从注册中心查询存活实例,实例增删自动同步 |
| 负载均衡(Load Balancing) | 治理 | 把请求摊到各实例;配合健康检查自动摘除故障节点 |
| 限流(Rate Limiting) | 治理 | 按 QPS 等指标限制流量,超限拒绝(HTTP 429);分单机/集群策略 |
| 熔断降级(Degrade) | 治理 | 过载或故障时快速失败返回兜底结果,防止故障级联 |
| 服务分组(Group) | 部署层 | 服务内部的逻辑单元:独立资源配额、发布节奏、容灾评估 |
| 模型服务器(Model Server) | 推理层 | Triton / TF Serving:模型加载、版本管理、按名路由、动态批处理 |
| 推理引擎(Inference Engine) | 执行层 | ONNX Runtime / TensorRT / libtorch / vLLM:实际加载并执行模型 |
| 后端(Backend) | Triton 概念 | Triton 可插拔的执行引擎种类;onnxruntime后端内部即封装 ONNX Runtime |
| ONNX | 模型格式 | Open Neural Network Exchange,读作"欧尼克斯";标准化模型描述,只定义结构不负责执行 |
| TensorRT | 推理引擎 | NVIDIA 优化编译器:算子融合、FP16/INT8 量化、按卡型调优;产物绑定特定卡型 |
| PyTorch / scikit-learn | 训练框架 | 前者训练深度学习模型,后者训练传统 ML(线性回归、树模型) |
| 判别式模型 | 模型类别 | 学 P(y|x),一次前向出结果:分类、打标、回归、embedding |
| 生成式模型 | 模型类别 | 学数据分布;部署语境特指自回归 LLM,逐 token 解码 |
| 自回归解码(Autoregressive) | 推理过程 | 每步用已生成内容预测下一 token,循环至终止符 |
| KV Cache | 推理过程 | 解码中间状态(注意力 Key/Value 缓存),常驻显存且随上下文增长 |
| PagedAttention | vLLM 技术 | KV Cache 分页管理,消除显存碎片 |
| Continuous Batching | vLLM 技术 | 请求级动态组批,新请求随时插入运行中的批次 |
| 动态批处理(Dynamic Batching) | Triton 技术 | 攒独立请求凑 batch 一次前向,提升 GPU 利用率 |
| 显存(VRAM) | 硬件 | GPU 板载内存,推理最稀缺资源:权重常驻 + KV Cache 动态占用 |
| 服务降级 vs 限流 | 治理 | 限流是"门口保安"按量拒绝;降级是"应急预案",被拒后走快速失败路径 |
九、总结:一张全景图
【治理地图】 【推理地图】 调用方 硬件:GPU 显存 / CPU │ 服务发现 ▲ ▼ │ 加载执行 RPC 服务层 ── 限流 / 熔断 / 负载均衡 推理引擎:ONNX Runtime / TensorRT / vLLM │ 一个服务名,一组实例 ▲ (Triton 的"后端") │ │ 封装 ├─ 服务分组(资源配额/发布/治理单元) 模型服务器:Triton / TF Serving │ └─ 实例(规格决定卡数,队列决定调度) │ 模型加载 / 按名路由 / 批调度 ▼ ▲ 请求进入实例进程 ──────────────────────→ 模型文件:ONNX / .pt / SavedModel (纯格式,无执行能力)三条主线贯穿全文:
- 分层解耦主线:服务名→模型名→分组三个坐标各答一问,调用方对内部变化无感知。
- 模型类型主线:判别式(一次前向,Triton 路线)vs 生成式(自回归解码,vLLM 路线),模型类型决定瓶颈,瓶颈决定引擎优化方向。
- 资源管理主线:卡数跟规格、额度跟项目组、调度跟队列、份额跟分组——四件事四个归属,总 GPU 数等于各实例卡数之和。