21. 生产部署与运维
前面二十篇解决的是“怎样让模型跑得快、跑得省”。把资料助手正式交给全公司使用,还有另一类问题:服务挂了怎么办,流量翻倍怎么扩,怎样知道它正在变慢,升级怎样不出事故。这一篇把推理引擎放回一套完整的生产系统里,依次讨论部署形态、健康检查、监控、扩缩容与路由、容量规划、安全和升级。读完你应该能为一个推理服务列出上线前的检查清单。
一套生产推理服务长什么样
从外到内,通常有这几层:
- API 网关:鉴权、限流、配额、TLS、请求日志。推理引擎本身通常不做这些事。
- 路由层:决定请求发往哪个副本,考虑负载和前缀缓存。
- 推理引擎副本:一个或多个 vLLM、SGLang 实例,每个实例占用一张或多张 GPU。
- 模型存储:存放模型权重的位置,副本启动时从这里加载。
- 监控与告警:收集指标、日志和链路追踪。
规模小的时候,网关和路由可以是同一个组件,甚至直接用一个反向代理;规模大的时候,可以使用 KServe、Ray Serve、NVIDIA Dynamo 这类专门的推理编排方案,它们处理副本生命周期、路由和扩缩容。
部署:启动慢是最大的特点
推理服务和普通 Web 服务最大的不同,是启动很慢。一个副本要依次完成:拉取镜像(含 CUDA 的镜像常有十几 GB)、加载几十到几百 GB 的权重、预热、编译、录制 CUDA Graph,才能开始接请求。这个过程可能要好几分钟。
由此带来几条实践:
- 权重提前放到本地。 每次启动都从网上下载模型是最慢也最不稳定的做法。把权重放在节点本地盘或高速共享存储上,并固定模型版本。
- 健康检查分开设置。 Kubernetes 的启动探针要给足时间,否则副本还在加载权重就被判定失败、反复重启。就绪探针要在预热完成后才通过,vLLM 提供
/health接口。 - 优雅退出。 下线一个副本时,先停止接收新请求,等进行中的请求完成(或达到超时)再退出,避免用户的回答被截断。
- 固定版本。 镜像用摘要而不是
latest标签,引擎版本、驱动、CUDA 版本都要记录。驱动和 CUDA 不匹配,是启动失败最常见的原因之一。 - GPU 资源独占。 一个引擎实例默认会占用所在 GPU 的大部分显存(第 4 篇的
gpu_memory_utilization),不要和其他程序共用同一张卡。
监控:看哪些指标
推理服务的监控分三层:
| 层次 | 指标 | 用来发现什么 |
|---|---|---|
| 用户体验 | TTFT、TPOT 的 P50/P95/P99,错误率,截断率 | 用户是否在变慢、是否在失败 |
| 引擎状态 | 运行中与等待中的请求数,KV 缓存使用率,前缀命中率,抢占次数 | 为什么变慢:排队、显存不足还是缓存失效 |
| 硬件 | GPU 显存、温度、功耗,节点网络 | 硬件故障或降频 |
vLLM 和 SGLang 都以 Prometheus 格式暴露这些指标,可以直接接入 Grafana。几个值得设置告警的信号:
- 等待队列持续增长:处理能力已经不够,需要扩容或限流。
- KV 缓存使用率长期接近 100%,同时抢占次数上升:并发太高或上下文太长,P99 会变差(第 9 篇)。
- TTFT P95 上升而 TPOT 正常:排队或长输入变多(第 5 篇)。
- 前缀命中率突然下降:可能有人改了系统提示,或者路由策略变了(第 10 篇)。
日志要记录请求 ID、输入和输出的 token 数、结束原因、各阶段耗时。是否记录对话内容,要按数据合规要求决定,很多场景下不应该记录原文。
扩缩容与路由
用什么信号扩容。 推理服务的瓶颈在 GPU,不在 CPU,所以按 CPU 使用率扩容通常是错的。更合适的信号是等待中的请求数、KV 缓存使用率或 TTFT 的分位数。
扩容有延迟。 一个新副本要几分钟才能就绪,流量突增时扩容来不及。常见做法是保持一定的余量,按历史的日内流量提前扩容,同时在网关做限流,扛不住时给出明确的拒绝,而不是让所有请求都超时。缩容到零能省钱,但第一个请求要等完整的冷启动,适合可以接受延迟的内部或离线场景。
路由不只是轮询。 第 10 篇说过,每个副本的前缀缓存是独立的。轮询或随机分发会让同一份系统提示在每个副本上都要冷启动一次。更好的做法是:
- 前缀感知路由:按提示词的开头(比如系统提示的哈希)把请求分到固定的副本,提高命中率。
- 会话亲和:同一个多轮对话的后续请求发往同一个副本,复用上一轮的 KV。
- 负载感知:在缓存命中和负载均衡之间取舍,不能把所有热门请求都压到同一个副本上。
容量规划:需要几张卡
用第 20 篇的并发扫描结果做容量规划。步骤是:
- 定好 SLO,比如 TTFT P95 小于 1 秒、TPOT P95 小于 50 毫秒。
- 用接近业务的负载压测一个副本,找出满足 SLO 时它能承载的最大请求速率。
- 估算业务的峰值请求速率。
- 副本数 = 峰值 ÷ 单副本容量,再加上冗余。
一个简化的例子:资料助手高峰期每秒 30 个请求,压测得出单张 H100 上的 8B 副本在满足 SLO 时每秒能处理 4 个请求。30 ÷ 4 = 7.5,向上取整为 8 个副本;为了在一个副本故障或升级时仍能满足 SLO,再加 1 到 2 个,共需 9 到 10 张卡。
这个数字只对这个模型、这个硬件、这个输入输出分布成立。业务的输入长度变长一倍,单副本容量可能明显下降,需要重新测。
安全
- 鉴权要自己加。 vLLM 的 OpenAI 兼容服务默认不做认证。不要把引擎端口直接暴露到公网,要放在网关后面,由网关负责鉴权和 TLS。
- 限制输入。 限制单个请求的最大输入长度和
max_tokens,按用户限流。一个故意构造的超长请求,可以占用一张 GPU 很长时间。 - 多租户隔离。 不同租户共享服务时,考虑第 10 篇说的缓存隔离(
cache_salt),避免通过响应时间推测别人的输入。 - 内部接口不对外。 指标、性能分析、管理类接口只在内网开放。
- 谨慎加载远程代码。 部分模型需要
--trust-remote-code才能加载,这会执行模型仓库里的 Python 代码。只对可信来源的模型开启,并固定版本。 - 提示注入是应用层的问题。 推理引擎不判断输入是否恶意,检索内容、工具调用的权限控制要由应用负责。
可靠性
- 超时:为 TTFT 和总时长分别设置超时,超时后要把取消传到引擎,释放 KV(第 7 篇)。
- 重试要谨慎:流式请求失败时,用户可能已经看到了一部分回答,直接重试会生成不同的内容。是否重试、怎样提示用户,要在应用层设计。
- 降级:主模型过载时,可以切换到更小的模型,或者对非关键请求返回“稍后再试”。
- 故障隔离:一张 GPU 出现硬件错误,只应该影响它上面的副本。多卡 TP 的实例中任何一张卡故障,整个实例都不可用,这也是能用数据并行就不用张量并行的原因之一。
升级
引擎、模型、驱动的每次升级都可能改变质量和性能。安全的升级流程:
- 读发布说明,重点看不兼容变更和正确性修复(第 18 篇)。
- 离线回归:用固定的质量集和基准负载对比新旧版本(第 20 篇)。
- 灰度发布:先把一小部分流量切到新版本,对比两边的错误率、延迟和用户反馈。
- 保留回退:旧版本的镜像、模型和配置保留到新版本稳定运行一段时间之后。
生产推理服务在引擎之外还需要网关、路由、模型存储和监控。启动慢决定了要提前准备权重、放宽健康检查、保留扩容余量;监控从用户体验、引擎状态、硬件三层看;扩容看等待队列和 KV 使用率,而不是 CPU;路由要兼顾前缀缓存和负载;容量规划基于满足 SLO 的单副本容量。鉴权、限流、超时和取消,都要在引擎之外补齐。
最后一篇离开数据中心,看看在手机和笔记本上运行模型时,约束和方法有什么不同:端侧推理。
