ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

从单模型服务到LLM推理平台:模型部署框架全景复盘

从单模型服务到LLM推理平台:模型部署框架全景复盘 模型部署框架这个词前两年还只是后端工程师和算法工程师交界地带的小众话题现在几乎每个做 AI 的团队都得直面它从把单个模型包装成生产服务到搭起支撑多模型、多租户、大规模并发的 LLM 推理平台这中间的跨越比想象中大得多。这篇文章不是某个框架的官方文档而是我从单模型服务一步步走到 LLM 推理平台之后的全景复盘希望能给正在选型或正在从 0 到 1 的朋友一些参照。1. 先搞清楚模型部署框架到底在解决什么问题1.1 部署不是“把模型文件拷到服务器”我见过不少团队把“模型上线”等同于“写个 Flask 接口load 一下权重然后跑起来”。这个做法在 demo 阶段完全没问题但一旦进入正式环境事情就会迅速失控。模型部署的本质是把训练产物权重、计算图、预处理逻辑变成一套能对外提供稳定服务的系统。这里面有四个绕不开的问题请求怎么进、显存怎么分、并发怎么扛、故障怎么处理。模型部署框架的价值就是用一套成熟机制把这些问题标准化让你不用在每次上线新模型时都从头造轮子。很多人第一次理解不了的是部署框架优化的核心不是“算得快”而是“扛得住”。单张卡算一个请求延迟很漂亮但线上是 100 个并发每个请求的输入长度还不一样框架要决定哪些请求合在一起算、哪些请求插队、哪些请求可以先拒绝。这个调度过程才是部署框架真正的灵魂。1.2 线上推理的三个核心指标评估一个部署框架的好坏最终都要落到三个指标上。延迟Latency用户发出请求到拿到完整响应的时间。对于在线服务我们更关注 p50 和 p99而不是平均值。平均值会被少数极快请求拉低p99 才是真实体验的底线。吞吐Throughput单位时间内能处理多少请求常用 QPS每秒请求数或者每秒处理的 token 数来衡量。吞吐和延迟通常是矛盾的——追求极致吞吐会把单个请求拖慢追求极致延迟会浪费宝贵的 GPU 算力。显存效率Memory Efficiency模型权重、KV Cache、临时激活值都在抢显存。框架能不能把显存利用率从 40% 提到 80%直接决定你资金成本的大头。这里有一个容易被忽略的认知延迟和吞吐的平衡点不是拍脑袋定的而是要结合业务场景反推。比如 C 端聊天产品首 token 延迟超过 1 秒用户就会明显感觉“卡”而离线的批量数据处理任务吞吐优先延迟稍微高一点无所谓。先想清楚业务要什么再谈框架选型。1.3 部署框架的职责边界市面上的模型部署框架职责边界并不完全一样但成熟方案通常覆盖这几个层面模型管理加载、卸载、版本管理、多副本。推理调度并发控制、动态批处理、排队策略。接口协议HTTP/gRPC 暴露能力统一输入输出结构。资源编排和容器平台尤其是 Kubernetes对接管理 GPU 资源。可观测性吞吐、延迟、GPU 利用率等指标暴露。理解了这个边界你就明白为什么“自己写个 FastAPI 接口”在初期总是够用到了后期又总是最先崩盘——因为上面每一层都要你手写而且每层的坑都足够你踩上一个月。2. 单模型服务时代一台 GPU、一个服务、一堆脏活2.1 从 FastAPI 快速验证到专业化推理服务单模型服务是大多数团队进入正式环境的起点。一个模型对应一个部署单元暴露一个 API背后挂着一两张 GPU 卡。最轻量的做法是 FastAPI 加 PyTorch自己写 load、predict、unload 的生命周期管理。我早期很多项目就是这么干的优点是灵活、好调试、代码透明缺点是所有工程问题都要自己扛并发上来了GPU 计算队列怎么排队多副本部署时模型权重是常驻内存还是按需加载模型更新时怎么做到不漏请求这些问题在流量小的时候根本不是问题流量一上来每一个都会变成事故。所以单模型时代真正的分水岭是引入专业化推理服务框架。它们把这个生命周期和调度逻辑都做成了标准化能力你只需要描述模型输入输出、指定硬件后端剩下的交给框架。以 TorchServe 为例它把模型打包成 .mar 文件自带版本管理和参数服务器模式对 PyTorch 生态的亲和度极高。Triton Inference Server 则更进一步支持多后端TensorRT、ONNX Runtime、PyTorch、TensorFlow核心优势是动态批处理和并发模型实例管理。2.2 动态批处理吞吐量的第一桶金单模型服务里性价比最高的优化手段就是动态批处理Dynamic Batching也称连续批处理的前身。原理其实很简单GPU 是典型的吞吐型硬件它喜欢“大块干活”。一个请求单独跑GPU 利用率可能只有 30%但如果把一段时间内到达的 8 个请求攒在一起拼成一个 batch 再进 GPU利用率就能到 80% 以上。代价是每个请求多等了一点时间——攒 batch 的时间。Triton 把这个机制做成了调度策略。你可以配置max_batch_size和delay参数最多攒多少请求、最多等多少毫秒。这里有个权衡# Triton 的 model config 片段 dynamic_batching: preferred_batch_size: [4, 8] max_queue_delay_microseconds: 2000max_queue_delay_microseconds是调度器愿意等待的最长时间。2000 微秒意味着请求最多多等 2 毫秒用来换吞吐收益。我实践下来对于 DNN 模型动态批处理通常能把吞吐提升 2 到 3 倍而 p99 延迟只增加 10% 左右。这个性价比几乎是白捡的所以线上推理服务我都会第一时间让它生效。注意一点动态批处理不是所有模型都适用。如果你的模型输入长度极度不均匀比如文本长度从 10 到 2000 不等强行拼 batch 会导致 padding 浪费可能得不偿失。这时候要考虑的是后续会讲的连续批处理思路或者干脆用单实例多副本的方式扛。2.3 容器化与弹性伸缩的注意点单模型服务上生产基本都会容器化加 Kubernetes 编排。这一步看起来标准实际上细节很多。GPU 资源要用 NVIDIA Device Plugin 暴露给 Pod。显存不够的 Pod 会一直处于 Pending 状态你要学会看events而不是只盯 pod status。如果一张卡想分给多个小模型可以启用 MIGMulti-Instance GPU或者配置显存限额但要注意MIG 切分后单实例的算力上限也固定了不适合波峰明显的场景。弹性伸缩方面传统的 HPA 基于 CPU 指标是不靠谱的——GPU 推理服务的瓶颈通常不是 CPU而是显存或者推理队列长度。更合理的做法是基于自定义指标伸缩比如 Triton 暴露的队列积压数量、平均推理时长等。KEDA 可以做这件事把 Prometheus 里的推理指标喂给缩容控制器比拍脑袋调副本数科学得多。我踩过的一个坑是模型加载耗时长大模型几十秒很常见HPA 扩容到了 3 个副本但 Pod 就绪之前流量已经打爆了旧副本。解决思路是给大模型加“预热”机制——在容器启动时就完成权重加载通过就绪探针把未就绪的实例摘掉流量同时给扩容预留足够的提前量别等 qps 真的飙了才扩。3. 单模型推理框架怎么选主流选项的真实横评3.1 四类框架的定位差异单模型时代的部署框架我用一个表格概括它们的定位这比看文档更直观框架核心优势典型场景学习成本生产成熟度Triton Inference Server多后端、动态批处理、GPU 调度精细复杂模型的统一推理入口中极高TorchServePyTorch 原生模型版本管理完善PyTorch 模型的标准部署低高ONNX Runtime Serving跨框架模型转换CPU/GPU 通吃已有 ONNX 导出流程的团队低高TensorFlow ServingTF 生态最稳久经考验TF SavedModel 存量资产低高但已显老旧自研 FastAPI灵活可控性最强定制逻辑多、原型验证高维护成本低很多团队选型时会纠结“哪个最强”实际上应该先问“我的模型资产在哪个生态”。模型是 PyTorch 训练的就用 TorchServe 起步最简单模型要跨框架部署、要跑多模型混合负载Triton 是不二之选存量是 TensorFlow 资产老老实实 TFServing 别折腾。3.2 Triton vs TorchServe生产环境的正面交锋这两个是我被问得最多的对比我直接给结论生产环境多模型混部时Triton 占优纯 PyTorch 单模型、团队人力少时TorchServe 上手更快。Triton 的优势在于“一套服务管所有模型”。它可以同时加载两个不同框架的模型并根据请求的某个字段把流量路由到对应后端。它还支持模型集成Ensemble把预处理、推理、后处理串成一条流水线。实际使用中Triton 显存释放和模型热加载的稳定性也明显好。TorchServe 的优势在于和 PyTorch 的零缝隙。模型导出、量化、torch.compile 的兼容性都不需要额外折腾。它的 workflow 功能0.6 版本之后也能做多模型编排但能力和易用性比起 Triton 的集成还差点火候。我的建议是如果你预计未来模型数量会涨到几十个直接上 Triton别先 TorchServe 再迁移迁移成本比部署成本高得多。3.3 什么时候该自己写推理服务这里必须说一句公道话自研推理服务不是洪水猛兽某些场景下它反而是最优解。比如你的模型前处理逻辑极其复杂涉及外部系统查询、动态拼接、业务规则注入这时候强行塞进框架的预处理流程里代码可维护性会变得很差再比如你的推理服务需要和内部鉴权、风控系统深度耦合框架提供的接口层可能不够灵活。但自研有一个前提你的并发模型清晰、模型数量少、团队有后端工程能力。而且要记得自己补齐动态批处理、超时控制、排队策略、优雅退出这几块缺一块线上都会出问题。大部分团队翻车都是因为当了一年的 FastAPI 单兵选手突然发现同事离职后没人看得懂那坨推理状态机。3.4 ONNX Runtime 的优势和局限ONNX Runtime 这个选项值得单独说。它的价值在于“一次导出到处运行”把 PyTorch、TensorFlow 模型统一导出成 ONNX 格式然后用同一个 runtime 在不同硬件上跑支持 CPU、CUDA、TensorRT 以及各种 NPU 后端。ONNX Runtime 的推理性能通常比 PyTorch eager 模式快尤其在小 batch 和动态 shape 场景。它的 Serving 组件可以直接搭配 Triton 后端使用作为中间转换层非常合适。局限也很明显不是所有算子都能导出。遇到自定义算子、控制流复杂的模型ONNX 导出会报错或者性能退化。我的经验是训练时就要注意算子兼容性常用模块尽量避免写太花哨的自定义 autograd Function不然后期部署全得返工。量化场景里 ONNX Runtime 也支持 INT8 动态量化但效果不如训练感知量化QAT稳。4. LLM 来了以后部署逻辑为什么被推翻4.1 自回归生成打破了“一次请求一次推理”的心智模型LLM 的推理和传统 DNN 有一个本质区别它不是一次前向传播出结果而是自回归地逐 token 生成。一个 10 token 的回答实际上执行了 10 次前向传播每次都依赖前一次的输出。这意味着LLM 服务的延迟不是一个数而是两个首 token 延迟TTFT和每个输出 token 的间隔TPOT/ITL。用户感受到的是这两个的叠加。而吞吐的含义也从“每秒多少请求”变成了“每秒多少 token”——这是一个完全不同的量纲也彻底改变了对部署框架的要求。传统 DNN 部署框架里那套“请求进来、算完、走人”的模型在 LLM 场景直接失效。框架必须在 token 粒度上进行调度而不是在请求粒度上调度。4.2 KV Cache显存账本也是调度难题LLM 自回归推理的每一步都要把历史 token 的 key 和 value 缓存下来用于计算注意力这就是 KV Cache。它的存在让显存消费不再是线性的权重占用的显存是固定的KV Cache 却随并发请求数和上下文长度动态增长。我算过一笔账以 7B 参数、无 GQA 的模型为例每个 token 的 KV Cache 大约能到 500KB 左右。一张 24GB 的显卡扣掉权重和算力开销后实际只能缓存几十 K 到一两百 K 的 token。这意味着同时服务的请求越多、上下文越长显存爆得越快。所以在 LLM 部署框架里KV Cache 的分配与管理是最核心的课题。框架要决定哪个请求的 KV Cache 可以被一直留在显存里、哪个该被逐出、显存碎片怎么整理。这个问题和操作系统的内存管理是同一个性质只是发生在 GPU 上。4.3 连续批处理取代静态批处理早期 LLM 服务用静态批处理Static Batching凑够一个 batch一起前向。这种方法浪费惊人——batch 里最短的请求早就算完了但整批都要等最长的生成完GPU 在干等新来的请求也不让进。后来的方案是连续批处理Continuous Batching也叫在途批处理。它把 batch 的粒度从“请求”变成“step”每一轮前向传播结束完成的请求立刻退出、新请求马上进来。GPU 几乎永远在处理不同生成进度的请求。这个改进的收益是静态批处理时代的性能往往只有连续批处理的 1/5 到 1/10。这个优化是 LLM 推理部署的分水岭目前所有主流引擎vLLM、TensorRT-LLM、TGI、SGLang都实现了连续批处理能力。4.4 从“模型服务”到“推理平台”的架构跃迁连续批处理和 KV Cache 管理的实现复杂度决定了 LLM 时代没有团队会从头写推理引擎大家的选择是站在成熟引擎之上搭建推理平台。这也是标题里“从单模型服务到 LLM 推理平台”这句话的关键单模型时代你部署的是“一个模型一个服务”LLM 时代你搭的是“一个平台管所有模型所有引擎”。平台架构通常分四层接入层统一暴露 OpenAI 兼容 API负责鉴权、限流、计量。路由层根据模型名、负载、成本策略把请求路由到不同引擎实例。引擎层vLLM、TensorRT-LLM、TGI 等推理引擎组成的计算池。资源层GPU 集群、Kubernetes、显存调度。每一层都有对应方案但难点从来不在选一个开源组件而在把它们之间的数据流、失败模式、容量模型都打通。5. LLM 推理平台的引擎选型与性能优化5.1 主流引擎速览与选型要点现在主流的 LLM 推理引擎各有各的脾气vLLM工程成熟度最高社区生态最好支持模型最广PagedAttention 解决了 KV Cache 碎片问题。大多数团队的首选。新版本对量化、前缀缓存、结构化输出的支持都很全。TensorRT-LLMNVIDIA 官方路线延迟和吞吐的极限性能通常比 vLLM 高 10% 到 30%但代价是编译时间长、模型适配工作量大、调试困难。适合模型相对固定、追求极致性能的业务。TGIHugging Face 出品对 HF 生态模型兼容性极佳部署简单性能也很不错但灵活性略逊。SGLang主打 RadixAttention 前缀复用和结构化生成在多轮对话、共享系统提示词等场景下吞吐优势明显但生态仍不如 vLLM。选择建议先从 vLLM 入手因为它的社区反馈最快、问题最多人踩过。当你确认 vLLM 的性能瓶颈无法满足业务时再评估 TensorRT-LLM。这里有个反直觉的点——TensorRT-LLM 的“快”往往在大 batch、追求吞吐的场景下才明显小并发聊天场景两者的差距并不大。5.2 压榨引擎性能的四个方向引擎选完了优化才算开始。我把这些年落地 LLM 平台的优化手段归纳成四个方向。第一前缀缓存Prefix Caching。很多产品的用户请求共享同一段系统提示词或者对话历史有公共前缀。vLLM 的 Automatic Prefix Caching 能复用 KV Cache命中时首 token 延迟能降低一个数量级。用法上要留意系统提示词尽量固定别在每轮请求里拼不同字符串否则缓存反复失效。第二投机推理Speculative Decoding。用一个小模型先草拟多个 token再用大模型一次验证。单流延迟在低并发时能明显下降但高并发下收益会被批处理抵消。适合在线交互场景。第三量化推理。用低精度格式减少显存占用、提升吞吐。现在主流是 INT8/FP8尤其在 H 系列卡上和 INT4AWQ/GPTQ。量化的收益和损失需要具体模型具体测不能只看公开 benchmark。第四并发调参。每个引擎都有类似max_num_seqs、max_num_batched_tokens、max_model_len的配置。这些参数决定 KV Cache 怎么分配、请求怎么排队。调参的核心是理解业务请求的 token 分布如果业务上下文平均 2000 token你却给每请求预留 8192 token 的 KV 空间显存利用率就废了。好的做法是真实采集线上请求长度分布再反推配置。5.3 量化不是无脑上 INT4量化这个坑我说得直白一点很多团队看到 INT4 “显存减半、吞吐翻倍” 的 benchmark 就热血沸腾上线后却发现回答质量下滑、p99 延迟反而变差。原因有几个。一是量化对敏感层的影响远大于对常规层的影响尾部任务数学、代码更容易退化二是低精度在长上下文场景的累积误差更明显三是有些引擎的 INT4 kernel 在小 batch 下反而不如 FP16 快。我的实践建议是优先上 FP8它能提供比较好的性价比H 系列硬件原生支持INT4 用在显存吃紧、需要塞进更多并发的大场景而且要逐模型做质量回归测试。所有量化决定都应该用线上真实 prompt 抽样做验证而不是跑几个公开 benchmark 就完事。6. 生产级 LLM 平台落地要打的硬仗6.1 容量规划先算显存账再谈扩容LLM 平台的容量规划比单模型时代复杂得多因为显存里同时住着权重和 KV Cache二者互相挤占。合理的步骤是确定模型权重显存比如 70B FP16 约 140GB需要多卡张量并行。估算单 token KV Cache 大小。估算单用户平均上下文 token 数和并发峰值。计算单 GPU 能支撑的最大同时生成请求数再反推 GPU 总数。这个计算模型不追求精确追求一个量级感知GPU 数量不是按 QPS 拍脑袋定的而是按“并发生成链路数”算的。对话场景里一个用户连续多轮生成会同时占用 KV Cache所以并发数往往远大于 QPS 数值本身。这也是很多团队“QPS 看着不高但 GPU 全被打满”的根本原因。6.2 冷启动、热池与 GPU 碎片化LLM 引擎的冷启动是个大痛点。加载一个 7B 模型只要十几秒但 70B 模型在多卡上加载可能要好几分钟。如果每次扩容都现场加载流量早把现有实例打爆了。解决思路是准备“热池”预先加载好模型副本接上健康检查但不放量只在流量上升时快速切入。代价是常驻 GPU 成本。折中方案是按“基础池 波动池”设计——基础池常驻波动池用优先级调度抢占空闲 GPU。GPU 碎片化则要靠调度策略解决尽量整卡调度、支持显存切分模型按需混部同时避免一张卡上碎成好几个互不兼容的实例。6.3 可观测性TTFT 之外还要看什么LLM 平台的可观测性绝不能只盯着“接口是否返回 200”。要建立一套以 token 为中心的指标体系TTFT首 token 延迟反映排队和 prefill 阶段压力。TPOT/ITL每输出 token 间隔反映 decode 阶段流畅度。生成吞吐tokens/s引擎效率的直观体现。KV Cache 使用率显存账本的实时数字接近饱和就是事故前兆。排队拒绝数引擎过载的真实信号比 QPS 更有预警意义。这些指标要从引擎内部暴露出来Prometheus 采集Grafana 展示。我在平台里还会把“每百万 token 成本”做成一个指标让业务方直观理解用量和钱的对应关系这对约束滥用特别有效。6.4 计费、限流与多模型路由LLM 平台既然是平台就得面对多租户的问题。接入层必须做统一鉴权、按用户/按应用限流、按 token 计量。限流不能只看 QPS要按 token 维度限——一个请求可能消耗几十万 tokenQPS 限流完全拦不住。计费用prompt_tokens completion_tokens做账配合 OpenAI 兼容协议的 usage 字段返回业务方能直接对账。多模型路由则是成本治理的大杀器。同一个需求场景可以配置多档模型默认跑 7B 便宜模型超时或者置信度不够再降级到 70B 大模型或者负载高时把部分流量切到低档模型。这个策略能显著控制成本但需要积累业务数据才能设好触发阈值。6.5 模型更新的灰度与回滚单模型服务时代更新模型就是发个版LLM 平台里换一个模型版本牵涉权重、prompt、引擎配置、量化参数多个变量必须走灰度。我的做法是在路由层做流量切分先在影子流量上验证新引擎和新模型组合的 token 级指标再按 5%、10%、30%、50%、100% 逐步放量同时盯 TTFT、TPOT、拒绝率、打标数据质量。一旦指标劣化一键回滚到旧版本。这里特别提醒模型回滚要连同 KV Cache 策略、量化参数一起回滚只回权重不解配置很容易出现隐性问题。7. 我踩过的坑以及一个务实的落地路径7.1 容易翻车的五个细节这几个坑都是我在真实项目里踩过、也看别人反复踩的列出来供参考第一忘了显存碎片化。Kubernetes 调度 GPU 是按整卡来的小模型塞不满一张卡多模型混部又互相干扰。这个问题没有银弹只能靠合理的实例分组和引擎级调度解决。别指望 Kubernetes 原生调度能力帮你搞定 GPU 碎片。第二把模型长度限制设得太大。max_model_len设得很大KV Cache 预留就大并发能力就小。要根据业务真实上下文分布设定并加上层截断策略。第三低估了排队行为的重要性。引擎的排队策略决定了高负载下是“大家一起变慢”还是“新请求被拒绝”。如果业务无法接受拒绝就要用更长排队换取所有请求最终完成但要把排队超时设好避免请求堆积成雪崩。第四压测用的数据太规整。拿固定长度的 prompt 压测和线上真实长尾输入完全是两码事。用线上采样数据做压测结果才有参考价值。第五忽略多副本之间的 KV Cache 连续性。会话级前缀缓存要求同一用户尽量路由到同一实例否则缓存永远不命中。路由策略要加一层亲和性逻辑或者干脆关闭跨实例缓存依赖。7.2 一条从单模型走到推理平台的务实路线如果你现在还在单模型服务阶段别急着一步跳到完全体平台。我推荐这条务实路径第一步先把现有单模型服务稳定下来指标体系建好动态批处理打开Kubernetes 编排和灰度流程跑通。第二步引入 vLLM 把第一个 LLM 请求接到服务里用 OpenAI 兼容协议对外同时部署好 TTFT/TPOT 监控。第三步等模型数量超过两三个再引入统一接入层和路由层把鉴权、限额、计量收拢到一处。第四步逐步把容量模型、热池策略、灰度回滚做成平台能力。每一步都可以独立落地并产生价值不至于一上手就被架构复杂度压垮。我个人在实践里最深的一条体会是单一模型服务的核心是以“算得快、扛得住”为目标而 LLM 推理平台的核心是以“资源调度、成本治理、多模型协同”为目标。这两个目标的切换是部署思路最大的分水岭。框架和引擎只是手段真正决定平台成败的是你对 KV Cache、token 调度和容量模型的底层理解。先把这几件事想透部署框架怎么选、平台怎么搭都会自然清晰起来。
返回列表