把训练好的模型真正压上生产,跟训练时跑通一个脚本是两码事。我接过第一个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个token,KV Cache的显存占用就能超过总显存的一半。如果再有几个超长文档的请求进来,直接就把显存顶爆。这也是为什么LLM推理不能像传统模型那样“一个模型服务随便复制几个副本”的原因,光显存这一关就过不去。
3.2 推理引擎选型:vLLM、TensorRT-LLM、SGLang、TGI
既然直接在FastAPI里加载LLM不可行,就得用专门的推理引擎。我用实际部署体验给一个参考:
| 引擎 | 核心优势 | 适合场景 | 配置成本 |
|---|---|---|---|
| vLLM | PagedAttention按块管理KV Cache,吞吐高,上手快 | 绝大多数在线业务、多模型共享 | 低,一个Python依赖就能跑 |
| TensorRT-LLM | 编译优化,单请求延迟下限最低 | 延迟极度敏感、模型长期不变的场景 | 高,需要构建Engine |
| SGLang | RadixAttention前缀复用,支持复杂推理结构 | 多轮对话、Agent、共享前缀场景 | 中 |
| TGI | HuggingFace生态一致 | 已深度使用HF生态、需要快速起步 | 低 |
vLLM能成为默认选择,核心在于PagedAttention机制把KV Cache切分成固定大小的块,按需分配、按块管理,显存碎片大幅减少,再配合持续批处理,让GPU在decode阶段也保持高利用率。我实测在同样的硬件条件下,vLLM的吞吐经常是朴素方案的3到5倍。TensorRT-LLM性能上限更高,但代价是每种模型、每种精度都要重新构建engine,而且构建过程最好在目标GPU型号上进行,调试成本明显偏高。除非业务对延迟有极苛刻的要求,否则不建议作为第一个生产引擎。
3.3 平台化三件套:统一网关、调度层、可观测性
推理引擎解决的是“单机怎么算得快”,平台还差三块拼图。第一块是统一网关,客户端只面向一个域名,网关负责把请求路由到对应模型的推理副本,统一做鉴权、限流、超时管理。到了LLM阶段,限流单位不再是QPS,而是token/s或者并发数。原因很简单:一个请求可能几十毫秒就返回,也可能憋几十秒生成上千token,QPS完全没有反映真实负载。
第二块是调度层,解决“模型应该跑在哪、跑几个副本”。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之间,最终由压测结果确定
- 网关层:单实例并发上限、连接超时、请求总超时分开配置
- K8s:readiness探针每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大概80,P99延迟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 OOM | KV 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未就绪 | 滚动发布设maxSurge=1,确认ready后再切流 |
这个表里的问题我在不同项目里几乎全遇到过,而且每次的根因都指向同一点:没有把显存、并发、队列这三个维度放在一起看。单独调其中一个参数能缓解一时,过两天换个流量模型又会爆。
6.2 压测和排障时容易忽略的细节
压测最容易骗人的地方,是用固定长度的prompt做基准测试。线上真实的prompt分布一定长短混合,长token请求会显著抢占KV Cache和计算资源。我的做法是先统计线上真实请求长度分布,再按这个分布构造压测集,出来的数据才有参考意义。固定长度的压测结果,往往比真实结果乐观30%以上。
另一个细节是链路基准。别直接打到vLLM端口测出一个漂亮吞吐就以为万事大吉,从网关进来的请求会经过鉴权、限流、排队、路由,这些全都会影响实际延迟。压测必须从网关入口打,模拟完整链路,得到的数字才是用户真正感知的数字。直接打引擎端口测出来的数据只能用来评估引擎自身能力,不能用来指导容量规划。
最后补一条涉及钱的建议:日志里一定要记录请求级别的token消耗。LLM计费按token算,线上如果出现token异常增长,大概率是prompt构造出了问题,比如把历史对话无界地拼进去。每个请求在入口记录prompt长度、返回长度、耗时和状态码,这些数据既是排障的依据,也是做容量规划、评估算力成本的底稿。
我个人在实际操作中最大的体会是:别追求一步到位搭一个“完美平台”。先有一个能跑的vLLM,加一层能限流的网关,再配一套能看到token级指标的可观测面板,这就已经足够支撑起大部分业务。后面要加调度、加多租户、加复杂路由策略,等业务流量真正逼到那一步再说。模型部署框架的价值在正式环境下会被放大十倍,但前提是,你得让这套框架先跑起来,再一步步长胖。