从 Jupyter 到生产级推理服务:GPU 调度、模型安全更新与弹性伸缩的工程化实战

发布时间:2026/10/8 7:23:32
从 Jupyter 到生产级推理服务:GPU 调度、模型安全更新与弹性伸缩的工程化实战 从 Jupyter 到生产级推理服务:GPU 调度、模型安全更新与弹性伸缩的工程化实战面向 Java/Go 后端工程师、平台工程师与模型服务团队。本文讨论自回归大语言模型的在线生成服务。部署主线为 NVIDIA GPU + Kubernetes + vLLM + Prometheus + KEDA。配置用于建立验证基线,未在本文编写环境执行 GPU 压测,不构成性能承诺;镜像、模型及参数须在目标环境锁定并验证。阅读说明。本文以一个企业内部知识助手贯穿技术原则与实现附录。案例是可复刻的参考实施场景,并非虚构客户数据:所有模型型号、QPS、阈值与性能结论均须以你的压测记录替换。贯穿案例:一个知识助手如何从单卡实验走到可运营服务业务网关接收用户问题、系统消息、检索片段和可选工具定义,以 SSE 返回模型输出。网关由 Java/Go 团队维护,推理层运行一个经审批的 7B 指令模型。目标不是“模型端口可访问”,而是让短问答和长资料摘要混合到达时,服务仍能解释其准入、延迟、拒绝、发布和恢复行为。客户端业务网关鉴权、token 校验、配额版本路由灰度、会话绑定、drain稳定 vLLM 副本候选 vLLM 副本PrometheusKEDA/HPAGPU 节点池、配额推荐的落地顺序是:锁定不可变产物;单副本测出 SLO 内安全容量;固定两副本验证路由和排空;最后才开启自动伸缩。Kubernetes 决定 Pod/GPU 放置,路由层选择版本和副本,推理引擎安排序列与 KV Cache;三种调度不能互相替代。一、Notebook 能生成答案,为什么上线后仍然排队?设想一个典型场景:团队在 Notebook 中验证了一个 7B 指令模型,把model.generate()接到 HTTP 接口,单用户体验正常。业务接入后,短问题与长文摘要混在一起,首字越来越慢,部分请求显存不足,增加 Web worker 后问题反而加剧。这是说明性场景,不对应经过核实的企业事故,也不附带虚构的 QPS、延迟或成本数据。故障通常来自三个缺口:请求没有进入统一的推理调度器。多个 Web 进程可能各自加载权重,重复占用显存;串行调用则无法充分利用批处理。服务容量只按请求数估计。100 token 的问答与数万 token 的文档输入消耗完全不同,平均 QPS 掩盖了长尾负载。上线只部署了进程。模型预热、过载拒绝、版本回滚、流式连接排空与冷启动都没有形成闭环。Flask、FastAPI、Spring Boot 和 Gin 都可以承担业务接口层。真正需要专用引擎的是模型执行与调度层。后端服务负责鉴权、配额、业务编排与审计,推理引擎负责批处理、KV Cache 和 GPU 执行,两者通过内部 API 解耦。本文围绕四个问题展开:一张卡能承载多少负载?请求怎样进入合适的 GPU?新模型怎样安全接替旧模型?扩容何时才能成为真正可用的容量?二、先定义服务目标,再谈吞吐2.1 把用户体验拆成可测量指标流式生成不能只看 HTTP 响应时间。连接建立成功,也可能长时间没有答案。指标建议口径对应问题TTFT客户端发出请求至收到首个有效生成 token首字等待是否过长ITL相邻输出 token 到达时间间隔输出是否卡顿TPOT多 token 输出中,首 token 后的生成时长除以剩余 token 数平均解码速度端到端延迟请求发出至最后一个 token/终止事件整体完成体验输入、输出 token/s分别统计 prefill 与生成吞吐压力主要来自哪里有效吞吐满足质量、延迟和成功率要求的请求完成量产出是否有业务价值排队时间、取消率、拒绝率区分网关与引擎,并保留分母是否通过牺牲用户体验换取吞吐客户端 ITL 包含网络、代理缓冲等因素;引擎 TPOT 的定义可能不同,不能直接混用。单 token 响应不适合计算上述 TPOT。指标应按模型版本、业务类型和长度桶聚合,避免把 user_id、完整 prompt 等放进高基数标签。例如,可以为交互问答设置“P95 TTFT 不超过 2 秒”的内部目标,为长文总结设置另一套目标。这里的数字只是设计示例,须由业务体验与压测结果确定。2.2 工作负载画像比模型参数量更重要建立输入 token、输出 token、到达速率、并发连接和上下文长度的分布,至少覆盖短问答、长输入短输出、短输入长输出及突发流量。容量记录需要同时保存:GPU 型号与显存、互联拓扑、模型 revision、量化格式、KV dtype、引擎版本、张量并行规模、前缀缓存命中率以及采样参数。同一个“7B 模型”,在不同上下文与硬件条件下没有统一的 QPS 上限。三、理解显存账本:权重只是第一项3.1 为什么原来的 KV Cache 公式容易算错对于常见 Transformer decoder,可用下面的简化公式估算未压缩 KV Cache:[M_{KV}=2L H_{KV} D_{head} b_{KV}\sum_{i=1}^{N}T_i]其中,2 对应 K 与 V,L 为层数,(H_{KV}) 为 KV head 数,(D_{head}) 为 head dimension,(b_{KV}) 为每元素字节数,(T_i) 为第 i 个活跃序列已缓存的 token 数。共享前缀、滑动窗口、多模态结构及不同缓存实现会改变实际占用。不能一律用 hidden size 替代 (H_{KV}D_{head}):MHA、GQA、MQA 的 KV head 数不同。以 Llama 2 70B 的典型结构为计算示例:80 层、8 个 KV heads、head dimension 128、FP16 每元素 2 字节:[2\times80\times8\times128\times2=327680\text{ bytes/token}=320\text{ KiB/token}]32 个独立序列,每个缓存 2048 token,理论合计约20 GiB。这不包含权重与其他开销;也不代表每卡占用。TP 下的分片或复制行为需要按具体实现核算。把该结构按普通多头注意力计算,会显著高估缓存。3.2 为显存建立完整预算[M_{device}\ge M_{weights}+M_{KV}+M_{activation}+M_{workspace}+M_{graph}+M_{margin}]7B 参数的 FP16/BF16 权重粗略为 14 GB 十进制字节,约 13 GiB;量化后的占用还包括 scale、元数据与实现开销。权重能装进显存,只能说明模型有机会运行,不能证明目标并发可行。vLLM 的分页式缓存管理减少了连续分配导致的浪费,并支持缓存块共享,但尾块仍可能有内部碎片。它不会消除所有显存浪费,也不保证整张 GPU 的显存或算力利用率达到某个固定百分比。[1]3.3 连续批处理解决了什么,没解决什么?连续批处理允许调度器在迭代边界移除完成请求、接纳新请求,不必等待整个静态批次完成。它通常提高吞吐,但长 prefill、调度竞争和高并发仍可能拉高 TTFT 或 ITL。分页缓存优化内存管理,连续批处理优化请求调度,chunked prefill 改善长输入与 decode 混跑的调度条件。三者相互配合,效果取决于工作负载。论文中的 2–4 倍提升对应指定模型、基线与实验条件,不能改写成所有部署都能提升 10–20 倍。[1]四、引擎选型:用同一份负载比较方案优先验证的场景需要付出的代价vLLM通用在线生成、多模型适配、OpenAI 兼容接口新版本行为、模型与硬件支持要回归TensorRT-LLMNVIDIA 硬件固定、愿意投入专项性能优化关注模型、执行后端、构建配置和升级兼容性SGLang结构化生成、前缀复用及对应模型生态用真实业务评估缓存收益和运行复杂度TGI已经稳定运行的 HF 生态服务官方已进入维护模式,新项目需评估长期支持方向截至核验日期,TGI 官方仓库明确标注 maintenance mode,并推荐后续关注 vLLM、SGLang 等引擎。[2] TensorRT-LLM 也不应被简化成“所有模型必须先离线编译、一定快于 vLLM”的固定结论,部署路径与收益取决于采用的版本和后端。[3]公平比较必须固定硬件、模型 revision、精度、上下文、请求分布、输出长度与质量要求,并检查默认采样参数。选出满足 SLO 时成本最低且团队能够维护的方案,比追逐峰值 token/s 更有意义。五、架构分三层,避免把调度能力混在一起