ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

模型部署框架实战:从单模型服务到LLM推理平台

模型部署框架实战:从单模型服务到LLM推理平台 把训练好的模型真正压上生产跟训练时跑通一个脚本是两码事。我接过第一个BERT意图识别服务时以为写完FastAPI、扔到K8s里就结束了结果被线上流量教育了两个月。后来一路做到能管几十个模型、扛住LLM推理请求的部署平台这中间的经历让我确定一件事模型部署框架在正式环境里的价值大概率比模型本身还容易被低估。这篇文章就从“单模型服务”讲起一路拆到“LLM推理平台”把为什么需要平台、平台由哪些件组成、参数怎么调、踩过哪些坑尽量一次说透。适合刚要把模型服务化的工程师也适合正在评估自建推理平台的团队参考。1. 正式环境对模型部署的要求远比“能跑”复杂得多1.1 稳定性、可观测性、可运维性三个绕不开的维度先说清楚一个概念陷阱。很多人以为部署框架解决的是“模型怎么能跑起来”的问题其实不是。模型在Python脚本里怎么跑都行单卡、多卡、batch调大一点程序能出结果就完事。但正式环境面临的是另一套逻辑流量会波动、某个上游调用方会发来畸形请求、GPU显存会被一个异常的长请求瞬间打满。框架真正要处理的东西是这些“跑起来之后才冒出来的事”。我习惯用三个维度去看一个部署方案是否成熟。第一个维度是稳定性。线上服务最怕的不是高并发而是慢请求把整个系统拖死。传统推理一个请求几十毫秒返回慢也慢不到哪去到了LLM时代一个请求生成上千token可能要几十秒如果它占住了并发槽后面所有请求都得陪着排队。所以部署框架必须在请求级别做隔离、排队、优先级控制这比单纯把接口写对要难得多。第二个维度是可观测性。不能只监控“进程活着没”还要能回答延迟为什么高了、吞吐掉到了多少、GPU到底用没用到。传统模型看QPS和延迟就够了LLM还要加一层token级指标比如首token延迟TTFT、每个输出token的平均生成时间TPOT、整体生成吞吐。没有这些指标“要不要扩容”这个问题基本只能靠拍脑袋。第三个维度是可运维性。模型版本怎么发怎么灰度怎么一键回滚很多团队把模型服务做成一个黑盒接口新版本上线靠改镜像名模型效果一崩就只能手动回滚、重启、等加载。正式环境的部署框架本质上是把模型当成持续迭代的“线上资产”来管理用固定的流程和约定去兜住每一次变更。这三个维度加在一起才构成“正式环境部署”这个词的真正含义。1.2 从“一个模型一个服务”到“统一推理平台”的演进逻辑单模型服务是最朴素的形态一个模型一个进程外面套HTTP接口配上Docker和K8s就能上线。这套方案没错也适合模型数量少、业务稳定的阶段。但模型数量一旦过十或者GPU资源需要多个模型共享又或者开始接入LLM时问题就会成片出现。每个服务都要单独做鉴权、限流、健康检查、监控告警重复劳动不说GPU利用率还稀碎。推理平台的本质是把“每个服务都要做一遍的事”抽出来下沉成公共能力统一网关负责入口、路由、鉴权和限流调度层负责按GPU卡维度分配资源模型仓库负责存储权重和版本可观测层统一采集指标。它并不是要抛弃单模型服务而是把单模型服务里那些共性的问题集中解决让每个模型只需要关心自己的“业务逻辑”也就是模型的输入输出和效果。这里还有个现实原因部署LLM和部署普通模型完全不是一个量级。一个7B模型权重就占十几GB显存KV Cache还会动态吃掉剩余的显存算力调度、显存管理、排队策略全都得在更高的平台层面才能统一解决。所以“模型部署框架”这个词越到后面越和“推理平台”绑定出现不是概念炒作是被实际问题逼出来的。2. 单模型服务最基础的落地形态天花板也很明显2.1 最小可用骨架模型封装、HTTP接口、多进程并发单模型服务的技术栈非常成熟十来年没有大变模型加载进进程外面包一个Web服务框架处理输入、调用模型、整理输出。Python生态里最常见的组合是FastAPI加Uvicorn生产环境再套一层Gunicorn一个接口、两个配置文件就能上线。但这套组合在处理GPU模型时第一个大坑就来了worker数量怎么定。很多人照搬CPU服务经验Gunicorn直接起4个worker结果4个worker各自加载了一份模型副本显存直接翻倍甚至翻四倍batch做不起来性能反而更差。GPU模型跟CPU模型不一样模型权重就在显存里多worker不代表多并发只代表多份冗余。我的建议是GPU推理服务用单worker加内部动态批处理或者每个worker独占一块GPU靠卡数扩并发而不是靠进程数硬顶。I/O模型也要较真。FastAPI的事件循环如果直接处理模型推理这种阻塞操作等于把事件循环卡死新的请求全部排队。正确姿势是请求进来先做轻量预处理然后把推理任务丢给线程池主线程继续接受连接等结果用了future再异步返回。这样API进程至少不会变成“接一个请求、卡一分钟”的木头人。健康检查也要拆开。/livez给K8s的存活探针用/readyz给就绪探针用。模型权重加载很慢时如果就绪探针没配好新Pod还没准备完成就被K8s判定失败反复重启形成“永远起不来”的死循环。这个细节特别基础但我在真实生产环境里见过不止一次。2.2 容器化与K8s编排镜像、HPA、滚动发布的正确姿势模型服务的Docker镜像有一个反直觉的经验权重文件别塞进镜像。几十GB的模型文件放进镜像里每次代码一改就要重新构建、重新推送镜像仓库和拉取时间都会被拖垮。正确做法是把权重放到对象存储或者挂载的PVC目录里容器启动时去加载镜像只包含代码和依赖。这样“发布一个新模型版本”就只是一个配置变更而不是一次几十GB的镜像传输。HPA弹性伸缩对GPU服务有个特殊的问题GPU节点池扩容经常要等好几分钟冷启动期间如果没有排队或缓存兜底用户体感就是“突然全超时”。别指望缩容后再扩容能扛住真实流量高峰在线推理服务最好保留一个最小GPU备机池或者接受“高峰期提前扩容”的运维节奏。滚动发布时最常见的坑是版本切换断流。新Pod已经创建了但模型还没加载完成、ready探针没通过旧的Pod就被缩掉了流量直接打到未就绪的实例上导致一批请求失败。正确做法是配置Deployment的maxSurge先启动新Pod等新Pod ready后再缩旧Pod并且给模型加载预留足够的启动时间。这些字段K8s里都有但默认配置不会帮你规避业务风险。2.3 单模型服务的上限与转向平台的信号那到底什么时候该考虑平台化我个人的判断是出现下面两个信号之一就可以开始做了一是模型副本数量变多但每张GPU的利用率长期低于50%资源碎片化严重二是模型版本迭代频繁每次上线都要手动改一堆Deployment、Service、HPA配置发布一次要半小时起步。单模型服务不是错它胜在简单直接。但简单的代价是每个模型都要重复维护一套上线链路。平台化的操作本质是“抽公共层”把路由、鉴权、指标、资源调度从各个模型服务里解耦出来让新增一个模型变成“配置一条记录”而不是“搭建一套服务”。走到这一步部署框架就不再是一个进程的封装而是一套组件加约定的组合。3. LLM让部署框架发生了质变3.1 生成式推理的两个阶段与KV Cache带来的显存难题LLM和传统模型在推理特征上的差异是质变级的不理解这个差异后面所有决策都无从谈起。LLM生成一个回答分两个阶段prefill阶段并行处理输入prompt速度快、计算密集decode阶段一个token一个token地生成每个新token都要依赖之前所有token的计算结果延迟高、内存带宽受限。两个阶段对计算资源的需求完全不同混在一起跑是很多性能问题的根源。decode阶段的大头显存开销来自KV Cache。自注意力机制中每个token在计算时都会产生Key向量和Value向量为了后续token能够复用它们会被缓存下来。于是输入越长、并发越多KV Cache就越大。你可以把模型权重理解为固定开销KV Cache则是随请求量和序列长度动态增长的开销跟水龙头没关紧一样会慢慢把显存空间蚕食掉。所以线上LLM服务经常出现CUDA OOM而普通模型很少见。场景很简单你设置了max_num_seqs为256假设每个请求平均生成512个tokenKV Cache的显存占用就能超过总显存的一半。如果再有几个超长文档的请求进来直接就把显存顶爆。这也是为什么LLM推理不能像传统模型那样“一个模型服务随便复制几个副本”的原因光显存这一关就过不去。3.2 推理引擎选型vLLM、TensorRT-LLM、SGLang、TGI既然直接在FastAPI里加载LLM不可行就得用专门的推理引擎。我用实际部署体验给一个参考引擎核心优势适合场景配置成本vLLMPagedAttention按块管理KV Cache吞吐高上手快绝大多数在线业务、多模型共享低一个Python依赖就能跑TensorRT-LLM编译优化单请求延迟下限最低延迟极度敏感、模型长期不变的场景高需要构建EngineSGLangRadixAttention前缀复用支持复杂推理结构多轮对话、Agent、共享前缀场景中TGIHuggingFace生态一致已深度使用HF生态、需要快速起步低vLLM能成为默认选择核心在于PagedAttention机制把KV Cache切分成固定大小的块按需分配、按块管理显存碎片大幅减少再配合持续批处理让GPU在decode阶段也保持高利用率。我实测在同样的硬件条件下vLLM的吞吐经常是朴素方案的3到5倍。TensorRT-LLM性能上限更高但代价是每种模型、每种精度都要重新构建engine而且构建过程最好在目标GPU型号上进行调试成本明显偏高。除非业务对延迟有极苛刻的要求否则不建议作为第一个生产引擎。3.3 平台化三件套统一网关、调度层、可观测性推理引擎解决的是“单机怎么算得快”平台还差三块拼图。第一块是统一网关客户端只面向一个域名网关负责把请求路由到对应模型的推理副本统一做鉴权、限流、超时管理。到了LLM阶段限流单位不再是QPS而是token/s或者并发数。原因很简单一个请求可能几十毫秒就返回也可能憋几十秒生成上千tokenQPS完全没有反映真实负载。第二块是调度层解决“模型应该跑在哪、跑几个副本”。K8s的Deployment配HPA是最基础的方案再往上可以做到按GPU卡维度分配、按模型池隔离还可以在模型冷启动时做排队等待。正式环境可以先不引入复杂调度器用节点亲和性加固定副本数也能跑但架构上要留出“能加调度逻辑”的扩展位。第三块是可观测性这是我最想说的一块。传统看“延迟-QPS”就够LLM平台必须补上TTFT、平均解码token数、每token生成时间、tokens/s吞吐、GPU显存水位和KV Cache利用率。这些指标从推理引擎暴露出来打到Prometheus里做大盘你才知道“要不要扩容”“为什么慢”。传统模型部署看“延迟QPS”LLM还要看“token显存吞吐”三者环环相扣。忽略任何一环排障的时候就像摸黑走路。4. 关键参数与配置从能跑变成跑得稳4.1 显存、并发、队列三个绕不开的旋钮vLLM这类引擎的显存里既要放模型权重又要给KV Cache留空间。gpu_memory_utilization这个参数默认在0.9左右意思是预留90%显存用于推理。我的建议是调到0.85到0.90之间不要顶着0.95以上。拉太满容易出“模型能加载一跑就OOM”的情况因为CUDA上下文、中间计算图也要占空间说白了就是没有余量。max_num_seqs决定最大并发序列数它是吞吐和延迟之间的天平。设得高持续批处理能打满GPU但每个请求的排队时间也会变长。我的定法很简单先用业务的在线P99容忍度反推。假设单请求平均生成耗时2秒P99容忍5秒那排队深度控制在3左右就够了max_num_seqs对应的就是“GPU能同时跑多少路”而不是无脑塞满整个显存的并发上限。队列策略同样重要。线上最常见的事故是一个长摘要请求占住并发槽后面所有短query全部超时。网关上要做有界队列队列长度一旦超限直接返回503并提示重试比无限排队要好得多。更高阶一点的做法是支持优先级短query可以插队长生成任务走独立队列或者路由到单独的资源池。这一步做好服务的骨架才算是能抗住混合负载。4.2 模型池、路由与资源隔离平台化之后别把所有模型塞进一个大池子里一定要按业务特征划分推理池。我的习惯是至少分两种一个池跑高QPS、低延迟的小模型比如意图识别、实体抽取另一个池跑LLM生成任务。两个池的节点配置、限流策略、扩缩容参数完全不同分开才能细化分开才能隔离。池之间互相隔离还能防止一种最恶心的连锁故障LLM把GPU吃满后小模型跟着一起不可用整个业务被一个长文本请求带崩。路由层要做成可配置、可灰度。接口层可以用一个轻量API网关做转发路由规则放在Redis里动态更新。上线新版本时先放5%流量验证稳定后再逐步放量一旦效果不对一键切回旧版本。不建议一开始就上重型的服务网格先把“规则可配置、配置可生效、生效可回滚”这三点做到就已经超过了大多数团队。4.3 一份可直接抄的部署参数清单综合上面的经验给一份基础参数参考场景是vLLM加K8s单卡A100 80G跑一个7B模型gpu_memory_utilization: 0.88留出余量防止上下文抢占max_model_len: 4096按业务长度预算裁剪别给默认满值max_num_seqs: 128到256之间最终由压测结果确定网关层单实例并发上限、连接超时、请求总超时分开配置K8sreadiness探针每10秒一次失败3次摘流模型加载预留15分钟启动时间限流按token/s限流例如池子总吞吐500 tokens/s超过则503这些参数不是抄完就完事的。换显卡、换模型、换prompt分布都要重新压测。但有一个方向是固定的显存给KV Cache多少、并发放多高、队列排多长这三个值必须一起算。孤立调其中一个调完也是白调。5. 从单模型服务到LLM推理平台一次真实迁移记录5.1 第一阶段一个BERT意图识别服务最早接线上模型的场景特别简单一个BERT意图识别服务。FastAPI接收文本预处理成token id丢给模型推理输出归一化成几个意图概率。模型不到500MB一台8核16G的CPU机器都能扛高峰QPS大概80P99延迟45ms几乎不需要运维。当时团队的普遍心态是部署模型不就这点事吗。之后的剧情就向着“太天真”的方向走。先是模型数量上来了每个模型都要走一遍同样的上线流程然后是GPU资源开始吃紧但利用率又长期上不去。也就是从这个阶段开始我才认真去思考单模型服务的“简单”到底把哪些成本转移给了后面的运维。5.2 第二阶段三个模型三种折腾第二个阶段团队上了摘要模型和实体抽取模型三个模型变成了三套K8s Deployment。第一个坑是重复建设三套配置互相独立每套都要单独配HPA、PV、监控告警。模型要发新版本时光改yaml加发布就得半小时起步。第二个坑是GPU碎片化摘要模型占半张卡实体抽取占另外半张GPU利用率长期不到40%想缩容又怕高峰扛不住只能看着算力白白浪费。第三个坑是版本管理。某次摘要模型效果回归要回滚旧版本结果上一版镜像已经被覆盖临时从对象存储拉权重、改环境变量重新发布前后折腾了40分钟。这40分钟让我彻底想明白一件事单模型服务的本质是把所有成本转嫁给了“重复劳动”和“人肉运维”平台化不是选择而是必然。5.3 第三阶段vLLM接入与统一网关真正逼着平台化落地的是接入LLM做话术生成。当时面临一个很现实的问题vLLM裸奔完全没法接生产。请求没限流、没鉴权、没有版本管理、没有可观测更关键的是慢请求能把整个推理实例拖死。所以落地动作分了三步先引入统一网关把三个模型的入口收敛到一个域名网关统一做鉴权和按模型路由再引入模型仓库每个模型目录下放权重文件和配置文件版本号严格命名最后单独给LLM开一个推理池池内用vLLM实例。上线初期还是踩了大坑。直接把vLLM端口暴露给了内部调用方一个长文本生成请求连续生成两千token把并发槽全部占住后面的短请求全部排队整体P99从1秒涨到8秒。排查后发现就是缺少限流和优先级改造后网关按模型配置并发上限短请求优先路由长任务限制数量单独排队系统才算真正稳住。这条迁移路径不炫技但它验证了一个关键结论平台化不是第一天就搭一个完整平台而是当单模型服务的维护成本高到难以承受时一步步把公共能力抽出来下沉成组件。6. 常见问题与排查技巧实录6.1 线上LLM部署高频问题速查表现象可能原因排查方向服务启动后随机CUDA OOMKV Cache占满或超出预算调低gpu_memory_utilization、收紧max_model_len、降低max_num_seqs首token延迟越来越高长文本prefill占住GPU短请求被排挤短长请求分池、限制单请求超长、必要时拆分prefill/decode一个慢请求拖垮所有短请求无优先级、无有界队列网关层加队列长度限制和请求优先级GPU利用率高但tokens/s很低decode阶段batch不足内存带宽打不满提高max_num_seqs、检查外部限流是否卡住并发新版本上线后流量仍打旧版本网关路由没切、灰度配置没生效查路由规则版本、检查新Pod readiness状态模型更新时请求批量失败旧Pod销毁过快、新Pod未就绪滚动发布设maxSurge1确认ready后再切流这个表里的问题我在不同项目里几乎全遇到过而且每次的根因都指向同一点没有把显存、并发、队列这三个维度放在一起看。单独调其中一个参数能缓解一时过两天换个流量模型又会爆。6.2 压测和排障时容易忽略的细节压测最容易骗人的地方是用固定长度的prompt做基准测试。线上真实的prompt分布一定长短混合长token请求会显著抢占KV Cache和计算资源。我的做法是先统计线上真实请求长度分布再按这个分布构造压测集出来的数据才有参考意义。固定长度的压测结果往往比真实结果乐观30%以上。另一个细节是链路基准。别直接打到vLLM端口测出一个漂亮吞吐就以为万事大吉从网关进来的请求会经过鉴权、限流、排队、路由这些全都会影响实际延迟。压测必须从网关入口打模拟完整链路得到的数字才是用户真正感知的数字。直接打引擎端口测出来的数据只能用来评估引擎自身能力不能用来指导容量规划。最后补一条涉及钱的建议日志里一定要记录请求级别的token消耗。LLM计费按token算线上如果出现token异常增长大概率是prompt构造出了问题比如把历史对话无界地拼进去。每个请求在入口记录prompt长度、返回长度、耗时和状态码这些数据既是排障的依据也是做容量规划、评估算力成本的底稿。我个人在实际操作中最大的体会是别追求一步到位搭一个“完美平台”。先有一个能跑的vLLM加一层能限流的网关再配一套能看到token级指标的可观测面板这就已经足够支撑起大部分业务。后面要加调度、加多租户、加复杂路由策略等业务流量真正逼到那一步再说。模型部署框架的价值在正式环境下会被放大十倍但前提是你得让这套框架先跑起来再一步步长胖。
返回列表