ARTICLE DETAIL

资讯详情

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

模型部署框架演进:从单体服务到LLM推理平台实践指南

模型部署框架演进:从单体服务到LLM推理平台实践指南 训练代码提交的那一刻往往只是开始。真正让人失眠的是模型拿到正式环境后怎么稳定地跑起来——QPS一上来显存就爆、服务重启要等两分钟、日志里全是timeout。我从单体模型服务一路做到LLM推理平台中间踩过的坑足够写一本薄薄的手册。这篇文章想聊的是模型部署框架这条线从简单到复杂、从单体到平台的演进逻辑以及每一级里真实会遇到的问题和值得保留的做法。这里说的模型部署框架不只是某个推理引擎而是从把模型跑起来到把推理变成平台能力的整条链路。适合的读者是正在做模型上线、推理服务优化或者准备把内部模型服务升级成统一推理平台的同学。不管你是用Python写了个FastAPI服务在硬扛还是已经开始用vLLM、Triton这类引擎这篇内容都能帮你把思路捋清楚每个阶段解决什么问题代价是什么什么时候该往上走一层。1. 单模型服务的核心边界单体部署不是所有问题的答案很多人第一次把模型推到正式环境都是从单体服务开始的。我也一样。模型文件放在服务器上写一个FastAPI应用加载模型、定义接口、跑起来然后对着健康检查接口发一个请求验证。这一步很快快到你会有种部署不过如此的错觉。但真正进入生产环境后单体服务的短板很快会暴露。作为起点它没问题作为终点它撑不住。1.1 裸写服务的便利与代价我曾经在项目初期直接用FastAPI包过几个模型包括文本分类和向量化。优点很明显你可以完全控制加载时机、批量策略、错误处理甚至想怎么打日志就怎么打。但代价也在这让你自己实现的东西全是功课。模型热加载、动态批处理、多版本并行、优雅退出、指标暴露每一样都得从零写而且写出来的多半不如成熟框架考虑得周全。最典型的例子是并发控制。我用裸服务跑向量模型时单张显卡能同时处理的样本其实有限但FastAPI本身会往模型里塞并发请求。如果不自己加信号量或者队列GPU就会在OOM和设备间拷贝之间反复横跳服务看起来是活着实际延迟已经烂掉了。这类问题用框架反而天然规避因为框架通常把批处理和并发调度当一等公民来设计。另一个容易翻车的是模型生命周期。版本更新需要重新加载参数你要是直接在服务里reload内存和显存瞬间翻倍如果加载到一半请求进来了还会读到半新的权重。单体服务里这些问题都要自己兜底而团队一旦铺开多个模型服务每处兜底写法可能还不一样线上问题就成了概率问题。1.2 模型服务器框架到底帮你兜了什么底后来我转向TorchServe它的价值不在某一个单独功能而在整套生命周期管理。模型注册、版本管理、worker进程调度、内置metrics官方插件里甚至直接提供了针对不同推理框架的handler。你在单体服务里手写半年才能稳定的东西它从第一天就有只是需要你去读文档、调参数。Triton Inference Server则是另一个方向它把性能作为核心卖点。多框架支持、动态batch、并发模型加载、GPU显存池化全都针对正式环境的性能问题设计。我实测过把同一个小模型从FastAPI切到Triton吞吐提升非常明显因为Triton内置的dynamic batching能把多个请求拼成一批GPU利用率完全不是一个量级。但也要泼一盆冷水Triton的学习曲线陡配置复杂而且它不直接帮你解决部署问题。它只解决模型怎么跑得稳、跑得快这一步至于服务怎么发布、流量怎么调度、指标怎么接进监控体系框架不管。这一点和后来LLM时代的引擎结论一致推理引擎解决算力问题平台化解决工程问题。1.3 LLM时代的新引擎与continuous batching到了LLM时代部署框架的选择又变了。vLLM、SGLang、TGI这些推理引擎在LLM场景下解决了几个非常关键的问题最核心的是continuous batching。传统静态batching要等一批请求全部完成才释放资源而continuous batching在一条序列生成完一个token后立刻插入新请求GPU的空隙被填满吞吐会明显提升。我用vLLM跑过一个7B模型对比过简单的串行服务和vLLM的吞吐差距。同样一块卡裸调HuggingFace的generate接口每个请求一个batchQPS大概只有个位数vLLM把并发请求动态拼batch之后QPS能翻好几倍。这种差距不是调参能追回来的是机制设计决定的。所以单模型服务阶段的第一个判断标准很简单业务上只有一种模型、调用方单一、日请求量可控你可以先用裸服务撑住但尽量在底层上接成熟的推理引擎。如果模型种类开始多起来、流量开始有波动那就要考虑整个服务架构的下一步了。2. 从单体到推理平台架构演进背后的三个真实驱动力我并不赞同平台化是银弹这种说法很多中小团队硬上平台反而被平台架构成本拖死。但当一个团队真正走到需要LLM推理平台这一步时驱动力通常不是领导拍脑袋而是下面这三个问题已经切实影响到日常交付了。2.1 模型数量变多之后重复建设开始浪费人力团队起步阶段通常只有一个模型可能是一个总结模型、一个分类模型或者一个向量化服务。到后面你会发现模型不是一个而是家族。嵌入模型、重排模型、多个不同规模的LLM、不同量化版本的模型可能还有同一模型的多套参数。每上一个新模型就要重新搭一遍服务、接一遍日志、配一遍告警这套重复劳动非常消磨精力。我在一个内部平台上见过最极端的情况同样的GPU资源配置脚本被复制了七八份每个模型服务都维护着自己的一套健康检查逻辑。改一个基础组件的版本要一间一间地跑过去升级。这个状态下做统一平台的收益已经非常清晰——大家要的只是一个配置入口而不是重新搭一遍服务。2.2 流量特征波动和GPU碎片化第二类驱动力是GPU资源和流量不匹配。不同分时段、不同业务流量差异很大。如果每个模型都是独立一组卡高峰期A模型卡到爆、B模型闲到发慌共享资源池就成了必然选择。模型推理平台或者调度层要解决的就是这个将GPU变成资源池多个模型按配额共享同一批卡。这里有个很容易被低估的问题GPU显存的隔离和调度。你不能简单地把多个模型塞进同一块卡上因为显存和算力是两种独立的资源。LLM推理还有KV Cache这种动态显存占用更加不能按静态显存来切。平台层的visibility非常重要——至少要让运维知道每个模型的显存水位、实时算力消耗才能谈得上调度。我在这块投入过比较大的精力后来发现如果指标采集没做好调度做了跟没做一样。2.3 治理需求版本、灰度、故障恢复第三个驱动力是治理。业务流量上来后模型不是更新完就能直接放的。需要灰度、需要回滚、需要在出问题时快速切到备用版本。单体服务阶段这些可以手工做但模型一多手工操作就是事故源头。我曾经在一次模型灰度中出现过新版分词器不兼容旧key直接导致线上回退失控。事后复盘问题不单是模型本身而是缺少一个统一的版本切换入口。推理平台的核心价值之一就是把上线模型从运维脚本操作变成配置化、可审计的动作。版本状态、灰度比例、回滚按钮、调用链路追踪这些东西组成了平台治理能力。严格来说不只是LLM推理任何模型托管平台最终都会走向这一步。差别在于LLM场景token和流式响应的观测更复杂平台需要理解流式语义才能做好监控。3. 正式环境的硬指标与GPU运维实战延迟、显存与弹性伸缩说句实在话很多模型服务在demo环境里跑得飞起上正式环境却频繁出事根子不在模型代码而在对推理场景的指标理解不到位。LLM推理的时延和传统服务不一样它不是简单的一个RT就能概括的。你盯着一个P99数字看半天根本不说明系统哪里出了问题。3.1 从RT到TTFT和TPOTLLM延迟指标要拆开看普通模型服务一个请求进来一个结果出去计一个时间就够了。LLM模型不是这样。流式输出下用户感知的延迟有两段第一段是首token延迟TTFT也就是从请求发出到第一个字出来的时间第二段是后续token生成速度TPOT也就是每生成一个token的耗时。这两段的影响因素完全不同前段主要受排队、prefill计算量影响后段主要受显存带宽、batch大小、KV Cache命中率影响。我在正式环境里排查过一次典型的TTFT暴涨请求本身没变模型没变唯一变化是并发请求量上来了。vLLM内部有max_num_seqs这个参数默认值比较保守并发一旦超过阈值后来的请求就要排队TTFT从几百毫秒直接飙到几秒。如果不看这个指标单纯看整体RT你只会觉得变慢了根本定位不到是排队导致的。所以从第一天起建议你接入监控的时候就按阶段拆散指标。TTFT、TPOT、端到端时延、排队长度、批大小这些都是独立监控项。CPU和GPU利用率反而不如这些业务侧指标更能反映问题。3.2 显存管理的三层坑加载、KV Cache和量化显存问题是LLM部署里最典型的坑而且分层出现。第一层是模型加载。一个7B模型FP16大约需要14GB显存但加载时的峰值还会更高。如果你部署脚本里没有预留loading buffer服务启动时就会OOM。解决方式是分步加载或者用更小的量化版本但不要迷信量化4bit和8bit在长上下文任务上的精度差异可能超过你的预期必须用业务评测数据说话。第二层是KV Cache。vLLM这类引擎里KV Cache是动态分配的显存利用率越高能并发的请求就越多。但这也意味着显存剩余量会随并发变化。我碰到过一次显存水涨船高、最终容器被OOM Kill的线上事故根因是gpu_memory_utilization配得过满给PyTorch框架自身留的余量不足。这个参数官方给的安全建议是0.85-0.9但实际还要看你服务里有没有其他op。我的经验是把KV Cache相关的指标拉出来动态观察不要上来就拉满。第三层是模型之间共享显存。如果一张卡上放多个小模型静态分配显存会浪费动态共享又需要引擎级的调度支持。这里没有银弹只能根据实际用量取舍。Triton对这类场景支持得相对好LLM引擎之间的共享则还在快速演进中。3.3 弹性伸缩为什么不能只看GPU利用率大概每个做过K8s弹性伸缩的人都有过这个冲动按GPU利用率配一个HPA利用率高了就扩容。这个方案在LLM场景下非常不可靠。GPU利用率高不代表服务饱和了因为推理引擎在拼命做continuous batching利用率高可能只是刚好在高效利用而不是请求在排队。反过来说一个长上下文请求会把GPU跑满很久但这并不说明需要扩容。更可靠的扩缩容信号是请求侧的排队情况包括in-flight请求数、队列深度、TTFT的增长趋势。把这些指标接到K8s的custom metrics里做HPA比看GPU利用率可靠得多。另外要留意推理引擎本身是有状态的——加载一个模型要几十秒到几分钟你必须在扩容策略里算上冷启动时间否则扩容出来的Pod还在加载流量已经打满原Pod了。这里我吃过一次教训早期用K8s默认的CPU指标做HPA结果模型服务根本没触发扩容。后来换成基于请求排在队里的prometheus指标扩缩才真正贴合流量。所以不要偷懒用业务指标做弹性把GPU利用率只作为告警参考而不是伸缩依据。3.4 稳定性配置里最容易被跳过的一步健康检查与优雅退出一个很小的配置项但是值得专门写一段因为我在正式环境里翻过车。K8s里livenessProbe和readinessProbe是有本质区别的。readiness表示现在能接流量liveness表示进程还活着不行就杀掉重启。很多人直接拿同一个HTTP接口配到两个探针上结果模型正在加载的时候liveness探针失败Pod被反复杀掉重启服务永远起不来。正确做法是拆分两个端点readiness返回模型是否已加载完成liveness只返回进程是否存活。同时要把preStop钩子配置好让Pod在终止前先停止接收新流量再把正在处理的请求跑完否则滚动更新时丢请求会成为高频事故。我当时踩的坑就是这个导致每次发版都批量报错。排查之后发现不是代码问题就是探针语义没搞清楚。在做推理平台的时候这些稳定性组件更应该是平台默认提供的而不是每个模型服务各写各的。4. 落地方案与工具链选择一条能走通的分阶段演进路线讲了这么多理论最后落回实操。如果现在让我从头设计一条从单体服务到LLM推理平台的路线我不会直接上大平台而会分阶段走每一步都能交付、都能验证而不是憋一个大招然后翻车。4.1 第一阶段把单模型服务做扎实即便你只有一个模型也建议先做几件事把推理引擎从裸写换成成熟引擎vLLM可以当起点也可以继续用Triton跑小模型把所有关键指标通过Prometheus格式暴露出来把健康检查、优雅退出、滚动更新这类基础特性先配置齐。这个阶段的目标很明确单模型服务在正式环境里要能稳定运行出了问题能快速定位。有一个容易被忽略的细节是模型下载。如果你的模型文件是从对象存储或者模型仓库拉取的一定要放在独立的Init Container里而不是在服务启动时现拉。否则每次Pod调度到新节点、或者扩容拉起新副本服务进程会卡在正在下载模型上拖垮启动时间。我见过一个服务模型体积大冷启动时间十几分钟就是因为下载逻辑写在了主进程里期间readiness探针还一直通过流量进来就是报错。4.2 第二阶段统一推理入口做路由与网关单模型稳定之后再往上走的下一步不是马上做平台而是加一个统一的推理入口。这个入口可以理解成一个LLM网关它和普通API网关不太一样。普通网关转发HTTP请求就够了但LLM网关要理解流式语义、要按token做计量、要做模型路由和降级。网关的一个关键职责是模型路由。比如你有多个LLM按价格、延迟、质量路由业务方不好直接感知背后切换。实现这些路由逻辑的位置放在网关照比其他地方都合适。另一个职责是fallback——当主模型挂了或者超时网关能自动切到备选模型。要实现这个网关必须对上游错误有分类哪些值得重试哪些重试也没用得有判断逻辑。我建议在这个阶段把可观测性问题一起解决。网关这一层天然适合给每个请求打上trace_id串联业务调用方、网关、推理引擎每一跳。没有这个链路线上出问题你会非常痛苦尤其是流式场景你根本不知道是响应断了还是token生成断了。4.3 第三阶段上K8s和模型编排平台如果你已经有好几个模型、多个团队在调用那K8s基本是绕不开的。问题只在于上面跑什么。KServe是相对完整的方案自带模型管理、自动扩缩容、流量分配Seldon Core更侧重运行时和算法编排如果只是LLM推理用vLLM镜像加上自己的运维代码也完全可行。这里给个很直接的建议不要为了平台两个字去上过于复杂的框架。如果你们团队只有两三个人负责模型运维先用K8sDeploymentService一套配置化的模型镜像模板就能解决80%的重复部署问题。等团队规模和模型种类大到一定程度再引入一个完整的模型编排平台否则学习成本和排障成本很可能超过收益。关于部署编排我自己偏好的方式是用Kustomize或者Helm做一个推理服务的模板。每个模型只改values里的模型名、显存需求、副本数发布时统一走GitOps流程。这个做法兼顾了配置化和易维护又不至于把整个交付流程复杂化。某次线上故障恢复时间从小时级降到分钟级就是靠这套模板加上统一的路由层业务方只需要等网关切换完成而不用关心具体哪台机器在跑模型。4.4 选型对比和一些省钱经验下面的表格是我个人在实践中的对比结论你可以根据自己的业务特征来选择方案优势代价适用阶段FastAPI裸写推理引擎简单、完全可控生命周期、监控、稳定性全靠自己原型验证或单一模型TorchServe / Triton稳定、性能好、多框架学习成本高、配置复杂传统模型批量上线vLLM / TGI / SGLangLLM吞吐高、continuous batching只解决推断不解决部署以LLM为核心的服务KServe / Seldon平台化特性全组件多、运维门槛高多模型、多团队、平台化自研轻量网关模型模板贴合自身业务、演进平滑需要投入开发维护从单体往平台演进途中省钱经验方面最重要的一条是不要在GPU上做懒配置。同一个模型按需选择量化和并发上限能显著提升单卡吞吐。我见过不少团队把所有模型都按最大显存预留结果GPU实际利用率不到40%。把模型分成bit数更小的版本、合理设置max_num_seqs、把不重要业务的模型路由到低成本GPU上这些都是直接降本的动作。另外如果只是做LLM推理尽量复用现成引擎的批处理和缓存能力不要再写自己的调度器。Prefix Caching这类特性vLLM和SGLang都已经实现了能跨请求复用公共前缀的KV Cache对包含长提示词的业务帮助很大。我当时在一批长文档总结任务上测过开启prefix caching之后TTFT有明显下降因为重复的前缀不需要重新计算。这些能力自己写代价极高直接用框架自带功能才是合理的工程选择。最后说说关于平台化的心态。我见过很多团队把上平台当成KPI结果折腾半年还退步到一套脚本打天下。平台化的正确理由只有一个它让模型上线更快、故障恢复更快、GPU利用更高效。如果你当前的部署链路已经能同时满足这三件事那不必为了架构升级而升级。如果哪一天这三件事里的任何一件卡住了你的正式环境交付那就该往上走一层了。我自己走到LLM推理平台这一步最深的体会不是架构多先进而是有了一个统一的入口之后所有模型相关的问题都能在一个地方定位和处理那种掌控感是单体服务阶段完全没有的。
返回列表