Skip to content

约 6 分钟阅读

21. 生产部署与运维 ​

前面二十篇解决的是“怎样让模型跑得快、跑得省”。把资料助手正式交给全公司使用,还有另一类问题:服务挂了怎么办,流量翻倍怎么扩,怎样知道它正在变慢,升级怎样不出事故。这一篇把推理引擎放回一套完整的生产系统里,依次讨论部署形态、健康检查、监控、扩缩容与路由、容量规划、安全和升级。读完你应该能为一个推理服务列出上线前的检查清单。

一套生产推理服务长什么样 ​

生产推理服务的组成:API 网关、路由层、多个推理引擎副本、模型存储与监控

从外到内,通常有这几层:

  • 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 篇的并发扫描结果做容量规划。步骤是:

  1. 定好 SLO,比如 TTFT P95 小于 1 秒、TPOT P95 小于 50 毫秒。
  2. 用接近业务的负载压测一个副本,找出满足 SLO 时它能承载的最大请求速率。
  3. 估算业务的峰值请求速率。
  4. 副本数 = 峰值 ÷ 单副本容量,再加上冗余。

一个简化的例子:资料助手高峰期每秒 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 的实例中任何一张卡故障,整个实例都不可用,这也是能用数据并行就不用张量并行的原因之一。

升级 ​

引擎、模型、驱动的每次升级都可能改变质量和性能。安全的升级流程:

  1. 读发布说明,重点看不兼容变更和正确性修复(第 18 篇)。
  2. 离线回归:用固定的质量集和基准负载对比新旧版本(第 20 篇)。
  3. 灰度发布:先把一小部分流量切到新版本,对比两边的错误率、延迟和用户反馈。
  4. 保留回退:旧版本的镜像、模型和配置保留到新版本稳定运行一段时间之后。

生产推理服务在引擎之外还需要网关、路由、模型存储和监控。启动慢决定了要提前准备权重、放宽健康检查、保留扩容余量;监控从用户体验、引擎状态、硬件三层看;扩容看等待队列和 KV 使用率,而不是 CPU;路由要兼顾前缀缓存和负载;容量规划基于满足 SLO 的单副本容量。鉴权、限流、超时和取消,都要在引擎之外补齐。

最后一篇离开数据中心,看看在手机和笔记本上运行模型时,约束和方法有什么不同:端侧推理。

别急,先让缓存热一下。