外观
服务化与推理 API
一句话定义:服务化(serving)是把一个离线可推理的模型,封装成一个可供业务方稳定调用的 API 服务的工程过程——让任何客户端通过一次网络请求拿到推理结果,而不用关心模型权重、框架与硬件细节。
行业洞察:"模型能跑"和"模型能被业务用起来"之间隔着整整一层服务化工程。很多团队在笔记本上推理秒出结果,一接线上就翻车:并发一高就超时、请求格式三天两变、模型更新要停服。业界共识是——推理 API 是"产品",模型只是它的内核;接口稳定性、可观测性、生命周期管理占服务化工作量的 70% 以上。这节讲清这 70%。
一、服务化解决什么问题
| 问题 | 没服务化 | 服务化了 |
|---|---|---|
| 依赖 | 调用方要装 Python、PyTorch、CUDA | 只发 HTTP/gRPC 请求 |
| 并发 | 单进程串行,秒级排队 | 多实例 + 负载均衡 + batching |
| 模型更新 | 调用方改代码重部署 | 服务方灰度替换,调用方无感 |
| 资源复用 | 每个业务各自跑一份模型 | 一个服务供多业务共享 |
| 可观测 | 黑盒 | 指标、日志、追踪齐备 |
一句话:服务化让"模型"从研究产物变成可计量的基础设施。
二、协议选择:HTTP/REST vs gRPC
| 维度 | HTTP/REST | gRPC |
|---|---|---|
| 数据格式 | JSON(人类可读、调试友好) | Protobuf(二进制、紧凑) |
| 性能 | 序列化开销大,大 payload 明显 | 序列化快 3~10 倍 |
| 流式 | SSE(Server-Sent Events)可行 | 原生双向流,LLM 首选 |
| 生态 | 任何语言/工具都能调 | 需生成客户端,工具链略重 |
| 调试 | curl 即调 | 需 grpcurl 等工具 |
| 典型场景 | 通用业务 API、网关集成 | 高性能内部服务、LLM 流式 |
现实中的主流姿势
对外(业务方/网关)用 REST/JSON,对内(模型服务之间、高吞吐路径)用 gRPC。Triton、vLLM 都同时提供两种接口,别选一个用到死。
三、推理 API 设计
1. 输入输出 schema:先定契约,再写代码
一个推荐模型在线推理 API 的典型契约:
json
{
"model": "ctr-v3",
"inputs": {
"user_id": "u_10086",
"item_ids": ["i_1", "i_2", "i_3"],
"context": { "scene": "home", "hour": 21 }
},
"params": { "top_k": 5 }
}json
{
"code": 0,
"data": {
"scores": [0.91, 0.82, 0.76, 0.61, 0.44]
},
"trace_id": "6f8c..."
}设计原则:
- 输入输出都用稳定的领域语义(如
user_id、top_k),而不是原始张量——业务方不需要懂模型; - 版本化:URL 带版本(
/v1/predict)或在 payload 里带model字段,别让调用方猜; - 单条优先:在线接口默认单条,批量用专门端点(见下)。
2. 单条 vs 批量
- 单条:延迟优先,适合实时场景;
- 批量:同 schema 数组,一次算多个,吞吐优先;
- 规则:不要两个端点逻辑互相复制,内部都走同一个推理核心,只是外层循环。
3. 流式响应(LLM 必备)
对话类模型的输出是逐 token 生成的(见 大模型推理优化),等全部生成完才返回,首 token 延迟会拖到几十秒。标准做法:
text
HTTP + SSE:
data: {"delta": "你"}
data: {"delta": "好"}
data: {"delta": ","}
data: {"delta": "世界"}
data: [DONE]TTFT(首 token 时间)与 TPOT(每 token 时间)是这类接口的核心指标,压测时别再用"整体延迟"一锅端。
4. 错误码设计
| 场景 | 建议 |
|---|---|
| 参数不合法 | 400 + 明确字段错误信息 |
| 模型超时 | 504(网关层)或 408 |
| 模型未就绪/过载 | 503 + Retry-After |
| 服务端异常 | 500 + trace_id(方便查日志) |
别在错误信息里泄露内部细节
500: cuDNN error in MatMul 这种报错会直接暴露你的框架与 CUDA 版本,给攻击者省事。线上统一"code + 简短 message + trace_id"即可,详见 安全、隐私与合规。
四、预处理 / 后处理:必须与训练一致
推理服务里最容易翻车、又最难排查的,就是特征口径漂移:训练时归一化用 (x - mean)/std,线上预处理忘了减 mean;文本分类训练用全角标点清洗,线上没做——模型整体掉点,而模型本身没动过。
三条铁律:
- 特征工程代码与训练代码同源:把预处理抽成独立模块,训练和推理共用同一个包,禁止线上"重写一遍";
- 版本化:预处理代码随模型版本一起发版,模型回滚时预处理必须同步回滚;
- 上线前离线对拍:同一批样本,训练侧预处理输出 vs 线上预处理输出逐字段比对。
更多坑见 常见陷阱与反模式。
五、模型生命周期管理
1. 加载策略
text
启动即加载(eager load) :首请求无延迟,但启动慢(7B 权重加载数十秒)
懒加载(lazy load) :首请求慢、之后正常,适合低频服务
预热(warmup) :加载后先跑几个假请求,让 kernel 与内存布局就绪(TensorRT 必需)生产建议:健康检查通过前必须完成加载 + 预热,否则一扩容,新实例首请求全部超时——这是服务扩容事故的头号原因。
2. 热更新与多版本共存
- 多版本共存:一个服务实例加载 v1、v2 两版模型,新请求走 v2、存量连接走 v1,平滑切换;
- 灰度:先放 5% 流量到新版本,看监控再全量(详见 发布策略:灰度与回滚);
- 模型与代码解耦:模型文件放独立存储(如对象存储 + 哈希寻址),模型更新不动服务代码。
六、并发、调度与请求队列
在线服务天然是排队系统:
text
请求 ──► 接收 ──► 限流(可选) ──► 排队队列 ──► 调度器(动态 batching) ──► GPU 推理 ──► 响应- 队列有界:无界队列在流量尖峰时会攒几百万请求,恢复时间被拖死;设上限,超了直接 503。
- 动态 batching:调度器把 10ms 窗口内到达的请求凑成一个 batch 一起算,吞吐翻倍而延迟只增几毫秒——这是在线服务最核心的调度技巧,详见 性能优化与容量规划。
- 并发模型:异步非阻塞 + 多实例,别用"每请求一个线程 + GIL 内 Python 计算"。
七、服务框架盘点
| 框架 | 一句话定位 | 适合谁 |
|---|---|---|
| FastAPI | 轻量 Python API 框架,自己管预处理/编排 | 中小团队、快速上线 |
| TorchServe | PyTorch 官方,模型打包/多版本开箱即用 | PyTorch 生态、要官方支持 |
| Triton Inference Server | 多框架多模型高吞吐,动态 batching 一等公民 | 生产级、混合模型 |
| KServe | Kubernetes 原生,Serverless 扩缩容 | 云原生 K8s 团队 |
| BentoML | 把模型打包成标准制品(Bento) | 需要工程化打包流程 |
| Ray Serve | 分布式 + 灵活编排 | 复杂服务、多模型组合 |
框架深度对比见 框架与平台怎么选;FastAPI 与 Triton 的实战见 FastAPI + Docker 在线服务 与 NVIDIA Triton 多模型服务。
八、可靠性:超时、重试、限流、熔断
| 机制 | 作用 | 经验值 |
|---|---|---|
| 超时(timeout) | 防止调用方无限等待 | 设为模型 P99 延迟的 3~5 倍 |
| 重试(retry) | 容忍瞬时失败 | 指数退避 + 抖动;最多 2~3 次 |
| 限流(rate limit) | 保护后端不被打爆 | 令牌桶;对单 key/单 IP 均限 |
| 熔断(circuit breaker) | 后端故障时快速失败 | 连续错误率 > 50% 断开 30s |
重试放大(retry storm)
上游超时重试 + 下游恰好过载 = 雪崩。必须限制重试次数与并发,并给不同调用方设不同配额,避免一次故障拖垮整个集群。
九、健康检查与就绪探针
- 存活探针(liveness):进程活着就行;
- 就绪探针(readiness):模型加载完毕 + 预热完成 + 能处理请求才算就绪,否则从负载均衡摘除;
- 启动探针(startup):给慢加载(如 7B 模型)留出加载时间,避免就绪探针误杀。
在 K8s 里的配置示例:
yaml
readinessProbe:
httpGet: { path: /health/ready, port: 8000 }
initialDelaySeconds: 60 # 等模型加载完成
periodSeconds: 10
livenessProbe:
httpGet: { path: /health/live, port: 8000 }
periodSeconds: 30权衡与取舍
| 决策点 | 选项 | 怎么选 |
|---|---|---|
| REST vs gRPC | 通用 vs 高性能 | 对外 REST,内部高吞吐 gRPC |
| 框架 | 轻量 FastAPI vs 重型 Triton | 单模型简单场景 FastAPI;多模型高吞吐 Triton |
| 加载方式 | eager vs lazy | 生产 eager + 预热;低频服务 lazy |
| 超时重试 | 宽松 vs 严格 | 严格限制,防雪崩优先 |
| 队列 | 有界 vs 无界 | 永远有界,超限即 503 |
一句话总结:服务化是把模型"产品化"——先定契约(API),再保稳定(超时/限流/重试),最后管好生命周期(加载/预热/灰度),模型本身反而是最简单的一环。
延伸阅读
- FastAPI + Docker 在线服务 —— 从 0 到 1 的最小完整服务
- NVIDIA Triton 多模型服务 —— 生产级多模型服务实战
- 部署架构模式 —— 在线服务与批处理/Serverless 的定位差异
- 性能优化与容量规划 —— 动态 batching 与并发模型
- 常见陷阱与反模式 —— 特征口径漂移等经典坑