
这两年做模型服务的团队几乎都会碰到同一个问题模型训练出来只是开始真正让模型平稳跑在正式环境里才是让人掉头发的事。从最开始给单模型写一个 HTTP 接口到后来在公司内部搭出完整的 LLM 推理平台这条路我踩过不少坑也总结出一套还算靠谱的方法论。这篇内容就围绕“模型部署框架”这个主题聊聊从单模型服务到 LLM 推理平台的演进路径适合正在从实验环境往生产环境过渡的算法工程师、平台开发以及所有被模型上线折磨过的同学参考。需要先说清楚的是单模型服务和 LLM 推理平台看起来是一个递进关系但背后其实是两种完全不同的工程思路。单模型服务解决的是“让一个模型跑起来”而 LLM 推理平台解决的是“让几十上百个模型稳定、高效、可控地对外提供服务”。如果你只需要部署一个 BERT 分类模型直接搞一个大平台反而不划算但如果你要接 GPT 类开源模型、开源多模态模型或者要给多个业务线同时提供推理能力那分布式推理平台基本是绕不开的。下面我把两边的核心细节都摊开来讲。1. 单模型服务一切的起点1.1 模型部署和 Web 服务的“握手”很多第一次做模型部署的同学会把模型部署理解成“把模型 load 起来然后 predict 一下”。这个理解没有错但生产环境里真正难的不是部分 predict而是完整地把模型装进 Web 服务里。拉一个 Flask 应用加一个/predict路由把模型权重丢进内存这件事五分钟就能做完但它离生产可用差着十万八千里。生产级单模型服务核心要考虑四个问题请求接收、预处理、推理执行、结果返回。你把这四个环节按先后顺序串一遍就会立刻发现瓶颈往往不在模型本身而在数据进出模型的那一段路。比如图像模型请求体是 base64 图片你需要在接口层解码、缩放、归一化这一步如果直接卡在 Flask 主线程里那并发一上来CPU 直接被打满GPU 反而在那里闲着吃灰。我在实际项目里见过太多次这种“后端卡死在预处理”的情况。后来统一的做法是预处理步骤不进入推理服务主线程而是单独走异步管线。图像解压、文本清洗这些活儿不管你用 multiprocessing 还是 celery必须和 GPU 推理解耦。理由很简单GPU 推理是微秒到毫秒级的而 base64 解码一个大图要几十毫秒CPU 密集型任务和 GPU 密集任务混在同一个进程里本质上是用木桶短板的方式在浪费硬件。关于接口层选型我自己在单模型阶段用过两套方案。一套是 FastAPI uvicorn胜在开发快、文档生成省心、异步支持好适合几百 QPS 以下的场景另一套是 Triton Inference Server自带动态 batch、并发调度和模型管理适合服务端吞吐敏感的正式环境。FastAPI 更像是“把模型包装成 Web API”的轻量做法Triton 更像是“为模型推理量身定制”的专业框架。如果模型只有一两个、流量不算大FastAPI 完全够用如果对吞吐和延迟指标卡得比较死建议直接 Triton。1.2 部署优化的三个必做项不管用什么框架单模型服务上线前有三件事一定要做不然真到上线当天就要手忙脚乱。第一件事是预热模型。很多模型框架第一次前向推理会有耗时超长的情况尤其是 PyTorch 模型CUDA kernel 初始化、cuDNN autotune、权重缓存分配都会在首批请求里集中触发。我的习惯是服务启动后先往模型里丢几个 dummy 样本跑一遍前向把显存占用稳定下来再注册到服务发现里对外暴露流量。这件事看起来不起眼没做的话线上监控图上经常会出现整齐的 5 秒——10 秒初始超时尖刺。第二件事是设置合理的 batch 维度。GPU 推理天然适合批量处理但批量不是越大越好。开动态 batch 时max_batch_size 设多大取决于你模型的单样本显存占用以及业务能容忍的最大排队延迟。举个例子一个单样本占用 8GB 显存的模型放在 40GB 的 A100 上理论 max_batch_size 是 5但如果你把 5 个请求全部凑齐才推理最晚那个请求的等待时间就取决于最早请求到达后的耗时这在延迟敏感场景是无法接受的。实际调参时我一般先按显存上限算出理论值再砍掉一半作为初始值后续通过压测逐步上调宁可让 GPU 略有余量也不要让请求排队排到超时。第三件事是做好模型版本和配置的持久化。经常有同事把模型权重直接丢在服务器的临时目录里然后训练脚本一跑第二天服务起来发现权重文件没了。正规做法是模型文件放到对象存储按版本号归档推理服务启动时按指定的 version 去拉取代码、依赖、启动脚本全部打进镜像镜像 tag 和模型版本强绑定这样才能做到可复现、可回滚。这算是最小成本的工程化要求了。1.3 单模型服务的瓶颈在哪把单模型服务跑起来之后接下来出现的瓶颈会集中在三个方向并发能力、模型热更新、成本分摊。并发能力方面Flask 这类同步框架天然吃瘪几个慢请求就能把整个进程拖死。换成 FastAPI 之后异步部分能轻松撑起数百 QPS但模型推理本身如果是同步阻塞的异步框架优势也会被抵消最终还是得回到“推理进程多副本 前置负载均衡”的老路上。模型热更新方面单实例跑一个模型参数更新的时候你只有几种选择停服换权重、双实例交替拉起、或者蓝绿部署。停服换权重是最原始的双实例交替拉起在单模型阶段比较常见但要求你的服务注册发现系统能配合流量的摘除和恢复蓝绿部署最稳不过要多准备一份 GPU 资源成本会翻倍。成本分摊方面这是很多人忽视的。单模型服务上线之后GPU 利用率如果长期只有 5% 到 10%本质上是在烧钱。你用一个 40GB 的卡去跑一个 2GB 的模型空闲显存在那里闲置算力也在闲置。这也是为什么很多团队做了一两个单模型服务之后会自然而然地转向“让多个模型共享一张卡”的方向——这就是推理平台要解决的最原始问题。2. 容器化与 K8s从“能跑”到“能扛”2.1 镜像构建与 GPU 资源管理单模型服务用裸机部署还是上容器在模型只有一个的时候差别不大但一旦模型数量超过 3 个环境冲突问题就会集中爆发。不同模型依赖的 CUDA 版本不一样、PyTorch 版本不一样、甚至 Python 版本都不一样裸机上做环境隔离早晚会出事。我见过最惨的一次两个模型服务共用一个环境的 site-packages依赖互相覆盖最后两个都起不来。上容器之后镜像构建要注意 GPU 环境的细节。基础镜像建议直接使用官方发布的 PyTorch CUDA 镜像或者 NGC 镜像不要从 ubuntu 裸镜像开始装 CUDA因为版本匹配问题能让你装一整天。镜像构建时把模型文件排除在外权重通过启动时挂载或下载的方式加载进来这样镜像体积能做得很小构建和推送都快很多。GPU 资源管理是 K8s 里最特殊的一环。节点打上nvidia.com/gpu的资源标记后Pod 里声明resources.limits: nvidia.com/gpu: 1调度器就会把 GPU 作为可分配资源来调度。这里有个经验limits 和 requests 都要写如果只写 limits 不写 requests部分调度插件和 HPA 的决策会异常如果只写 requests 不写 limits则可能出现一个 Pod 把节点显存打满、其他 Pod 没有显存可用的问题。另一个 GPU 调度的隐性坑是显存和算力的隔离。K8s 默认把整块 GPU 调度给 Pod两个 Pod 共享一块卡时如果没有额外的显存隔离机制理论上存在互相挤占的风险。NVIDIA 官方提供了 MIGMulti-Instance GPU和 Time-Slicing 两种方案。MIG 适合在 A100/A30 这类卡上做硬件级隔离资源切分稳定但灵活性差Time-Slicing 是时间片轮转适合把一块卡分给多个小模型用但如果模型显存需求超过切分后的额度还是会调度失败。生产环境里我比较推荐“大模型独占卡 小模型共享卡”的混合策略不要指望一套配置通吃所有场景。2.2 弹性伸缩与参数估算K8s 上做弹性伸缩最常用的就是 HPAHorizontal Pod Autoscaler。但用 HPA 管理推理服务时CPU 指标往往不能真实反映 GPU 服务的负载。一个模型服务进程CPU 占用可能一直很低GPU 利用率却已经飙到 90% 了。所以你会遇到“Pod 还没扩容请求已经大量超时”的尴尬。我的建议是HPA 的指标尽量使用自定义指标比如 GPU 利用率、请求队列深度、P95 延迟这些都是对推理服务更有意义的信号。如果短期不想接自定义指标也可以用 QPS 作为扩缩容依据——QPS 上去了就扩下来了就缩这个指标本身不依赖 GPU 打点采集起来也容易。弹性伸缩的副本算理可以这样估算单副本吞吐 × 期望副本数 ≈ 峰值流量。比如用 vLLM 部署一个 7B 模型单副本实测吞吐是 1000 tokens/s你的业务峰值需要 5000 tokens/s那副本数至少要有 5 个。但这只是乐观值因为流量的时间分布从来不是均匀的还得考虑突刺和排队效应。我一般会在理论值上乘以 1.5 到 2 的冗余系数留足缓冲。反过来缩容时要注意 GPU 服务的优雅退出Pod 被删掉时当前正在处理的请求要等它完成不能直接杀掉进程否则在线用户会看到完整的人工智障表现。2.3 上线发布的灰度与回滚模型服务的发布和普通 Web 服务有点不一样普通 Web 服务回滚只需要换代码模型服务回滚还要换权重。因为你永远不知道一个新模型权重在业务数据上的表现会是什么样——离线指标再好线上也可能出现离谱输出。所以模型发布必须有灰度机制。最简单的灰度发布方式是 K8s 里的多副本分流量旧版本服务保留 80% 副本新版本服务占 20% 副本流量通过 Service 的权重路由或者 Ingress 的 canary 规则切过去。真正执行灰度时我会盯两类信号系统信号看错误率、延迟、GPU 利用率业务信号看模型输出质量、用户反馈、业务侧指标。业务信号往往是决定继续放量还是回滚的关键。比如做一个内容审核模型灰度期间误伤率如果明显上升那不管系统指标多稳都必须立刻回滚。回滚操作本身也有讲究。新版本服务上线的同时旧版本的镜像和模型权重不要急着删保留至少两到三个迭代版本。很多团队只保留“当前版”和“上一个版”结果模型迭代三四版之后想回退到最初版本翻遍了镜像仓库都找不到对应依赖环境这才是真的欲哭无泪。3. LLM 推理为何不能按 CV 模型的思路部署3.1 显存、KV Cache 与并发上限如果你只部署过 BERT、ResNet 这类模型第一次部署 LLM 时最直观的感受就是显存怎么这么吃紧。一个 7B 参数量的模型光权重文件按 FP16 算就要占 14GB 显存再看看 KV Cache 的动态占用一个 4096 上下文长度的请求单并发下 KV Cache 可能吃掉几个 GB。稍微来几个并发整块 A100 就被挤满了。KV Cache 是 LLM 推理中最特殊的内存开销。解码阶段每生成一个 token模型都需要把之前所有 token 的 Key 和 Value 向量缓存下来供注意力计算复用。这个缓存的体积大致可以估算显存占用 层数 × 头数 × 头维度 × 2 × 序列长度 × batch size × 字节数。举个 7B 模型的例子假设 32 层、32 个注意力头、头维度 128序列长度 2048batch size 1FP16 存储KV Cache 大小大约是 32 × 32 × 128 × 2 × 2048 × 1 × 2 字节算出来约 1GB。序列每翻一倍KV Cache 就翻一倍。这就是为什么长上下文场景下显存的核心压力往往不是模型权重而是 KV Cache。生产环境里要控制并发就必须先算清楚一个极限单卡可用显存减去模型权重显存剩下的就是 KV Cache 的预算。这个预算决定了一个推理实例能同时服务多少个请求。很多同学一上来就把并发开到 64结果请求一多OOM 直接崩掉。正确做法是用上面的公式先粗算 KV Cache 总预算除以单请求的 KV Cache 估算值得到理论并发上限然后留出 20% 的显存余量作为安全缓冲最终设定一个保守的并发目标。3.2 连续批处理与 Prefill/Decode 阶段在传统 CV 模型里批量推理就是把 N 个样本一起丢进模型一次前向全部算完简单直接。但 LLM 解码是逐 token 生成的每个请求的生成进度不一样有的请求刚进来处于 Prefill 阶段有的请求已经生成了 500 个 token如果按传统 batch 方式必须先等所有请求都完成一轮解码才能进入下一轮这会产生大量 GPU 空转。业界解决这个问题的主流机制叫 Continuous Batching连续批处理vLLM 和 TensorRT-LLM 都实现了这套机制。它的核心思想是在一个 batch 里每个请求的解码进度可以不一样。某个请求完成生成了立刻从 batch 里退出去腾出来的空间马上给新请求做 Prefill。batch 不再是一整块必须同时开始同时结束的砖头而是一个动态进出的大池子。这种方式能把 GPU 利用率提升数倍吞吐收益非常明显。Prefill 和 Decode 是 LLM 推理中两个计算特征差异巨大的阶段。Prefill 阶段处理用户输入的所有 token计算量大属于计算密集型Decode 阶段每步只生成一个 token但需要反复读取 KV Cache属于访存密集型。不少推理框架允许把 Prefill 和 Decode 拆分到不同的 GPU比如 vLLM 的 chunked prefill就是因为两个阶段对硬件资源的需求不一样。混在一起跑会导致 Prefill 阶段 GPU 算力打满而显存带宽闲着Decode 阶段显存带宽打满而算力闲着。拆开之后两张卡可以各干各的活。但这套做法对工程能力要求极高如果不是日均请求量非常大的场景可以先不做拆分优先保证稳定性。3.3 推理框架选型对比LLM 推理框架最近这两年迭代非常猛选型时容易眼花缭乱。我在生产环境里实际用过的主流方案有这么几类框架核心优势适合场景上手难度vLLMPagedAttention、吞吐高、生态广高吞吐文本生成/OpenAI 兼容 API低TensorRT-LLM针对 NVIDIA 卡深度优化、延迟低对延迟和吞吐要求都极高的场景高TGI(Text Generation Inference)Hugging Face 生态、部署简单快速把 Hugging Face 模型跑起来低llama.cpp纯 CPU/混合推理、量化完善本地开发、小显存环境低Triton TensorRT-LLM Backend统一推理平台、多模型管理已有 Triton 基础设施的团队高vLLM 是我最推荐的起点理由很实在上手快、社区活跃、对主流模型的支持最全。它默认提供 OpenAI 兼容的接口格式这意味着你从单模型服务切换到 LLM 平台时业务代码基本不用改。PagedAttention 这种显存管理机制让 KV Cache 的利用率比朴素实现高不少同样的显存能支撑更多的并发。TensorRT-LLM 的优势在极致性能和可控性缺点也很明显它的构建机制比较复杂换一个模型结构就要重新 build engine模型更新频繁的团队会在构建上投入大量时间。所以我的建议是线上模型数量少且架构稳定的团队可以考虑 TensorRT-LLM 换极致性能需要灵活多变的团队直接 vLLM 起步更稳妥。Triton 的价值不是单模型部署而是它作为一个统一推理服务框架可以同时管理多个后端——你既可以在里面跑一个 TensorRT-LLM 的 LLM也可以跑一个 PyTorch 的 CV 模型共用一个网关、一套监控这是单实例部署无法替代的平台能力。4. 推理平台的架构演进与建设要点4.1 模型网关对外统一入口当你从单模型服务走向 LLM 推理平台最先要补上的基础设施是模型网关。网关的作用不是做一个反向代理那么简单而是把“业务调用模型”这件事从点对点调用变成平台化调用。没有网关的时候业务方调用模型服务得自己拼 URL、自己处理鉴权、自己实现重试和超时。业务方每接一个新模型都要重新联调一次。有了网关之后业务方调用任何模型都走同一个 API 入口网关负责路由到具体的推理实例做鉴权、限流、负载均衡、灰度分流。从业务方的视角看“模型”变成了一个可以按名字调用的抽象资源而不是一个具体的 IP 和端口。网关层的路由策略值得好好设计。模型升级时网关可以按流量比例把一部分请求转发到新模型服务另一部分继续走旧模型服务。这个能力在做模型灰度时非常关键因为模型服务本身的注册和发现在 K8s 里已经做了一部分但流量切分的精细度还不够网关可以根据 header、用户 ID、请求参数做更细粒度的分流。比如你想先让某个内部测试部门用到新模型其他部门继续走旧模型光靠 K8s Service 是做不了的必须靠网关规则。模型网关的另一个价值是统一限流和排队。LLM 推理是资源敏感型任务如果没有限流一个疯狂刷接口的业务方就能把整张卡打爆影响所有其他业务。在网关层按模型、按 API Key、按用户维度设置 QPS 限制是保护下游推理实例不会雪崩的必要手段。限流算法我推荐令牌桶突发流量可以用一次性 burst 额度平衡掉既不会死板地拒绝所有瞬时峰值也不会放任流量冲垮后端。4.2 多模型管理与资源共享平台化和单模型服务最本质的区别就是对 GPU 资源的统一调度。在单模型阶段一个模型独占一块卡是常态在平台阶段目标是让 GPU 资源利用率最大化多个模型可以共享一块物理卡根据流量峰谷动态调配算力。多模型共享 GPU 有两种实现路径一是前面提到的 K8s 的 Time-Slicing 方案把一块 GPU 按时间片分配给多个推理实例实现方式是每个模型服务跑一个独立 Pod大家共用节点的 GPU 资源这种方式适合模型之间完全隔离、各自独立部署的场景问题是单个模型无法使用整卡算力一个大请求进来会受限二是在 Triton 这类统一推理框架里加载多个模型框架内部统一管理显存和调度不同模型可以按需加载和卸载适合需要灵活切换模型的团队问题是对框架的工程能力要求高模型之间的资源隔离能力不如独立 Pod 强。我见过不少团队一开始给每个模型都分配独立 GPU结果显卡利用率惨不忍睹有的卡 20% 都不到。后来切换到共享资源池之后同一个模型服务峰值和低谷刚好可以错开整体利用率能提升两到三倍。这里的关键是不要做静态分配要做动态调度模型网关监控每个模型的实时流量流量下来的时候就缩容把 GPU 资源让给流量高的模型。模型仓库的管理也要提前规划好。每个模型应该有唯一标识包括模型名、版本号、框架类型、权重路径、依赖配置。平台在建的时候就应该实现“模型注册”的概念——算法同学训练完模型传到模型仓库填一张注册表平台自动拉起推理服务。如果这个过程还要运维手动去写 YAML、配 Service、配网关那平台基本就没有真正落地。用 K8s Operator 去实现模型注册到服务拉起的自动化是这一步的最优解。4.3 可观测性与成本控制推理平台运行起来之后第一件要做的事就是建立完整的可观测体系。LLM 推理的监控指标和平常 Web 服务不太一样重点看五个TTFTTime To First Token、TPOTTime Per Output Token、吞吐量tokens/s、GPU 利用率、KV Cache 使用率。这五个指标缺一不可。TTFT 太长表示 Prefill 阶段或排队出了问题TPOT 波动剧烈表示 Decode 阶段或显存带宽出现瓶颈KV Cache 使用率接近 100% 表示并发上限到了需要扩容。可观测性落地上我建议参考网关注入链路追踪的方式每个请求进入网关时分配一个 request_id网关把模型名、版本号、用户维度、模型耗时、生成 token 数全部打点输出配合 Prometheus 和 Grafana 做聚合监控。当线上出现问题时可以直接按 request_id 追踪到具体模型、具体版本、具体节点排查效率会提升很多。这套体系听起来复杂但绝对是平台上线前必须完成的工作否则线上出了问题你连“问题出在哪一层”都说不清。成本控制方面平台化最大的好处是让 GPU 成本从不可控变成可控。具体做法上可以按模型、按业务方、按调用方维度做成本计量。比如你跑一个 7B 模型单次请求平均生成 300 token单卡每小时成本是 30 元那就可以算出每分钟能支撑多少请求、每个请求的边际成本是多少。有了这些数字业务方申请算力资源时就不是拍脑袋而是按真实的成本分摊来申请。平台建设到这一步已经算是比较成熟了。5. 生产落地中的踩坑记录与排查思路5.1 典型故障OOM、超时、排队把推理平台跑起来容易稳定跑下去难。这里记录几个我在生产环境里真实踩过的坑。第一个坑是显存 OOM。看起来是显存不足实际原因往往是并发失控。vLLM 在创建 LLM 实例时有一个max-num-seqs参数控制最大并发序列数。这个值设置得太大一旦请求数上来KV Cache 直接超限整个推理进程就可能挂掉。我一开始图省事直接把参数拉满结果线上某天流量暴涨模型服务直接 OOM 崩溃。后来老老实实按显存预算去算并发上限再降低 20% 作为安全阈值之后再没出过这类问题。第二个坑是首 token 延迟飙升。现象是监控图上 TTFT 突然从 300ms 变成 10 秒但 GPU 利用率看起来很正常。排查了很长时间才发现是网关层排队策略出了问题。当时用了简单的 FIFO 队列一个慢请求在前面占着位置后面所有快请求全被挡住。后来把排队策略改成带优先级的调度比如短请求优先、交互式请求优先、批量请求靠后TTFT 立刻恢复正常。LLM 推理场景天然有长尾请求排队策略必须考虑长短请求混跑的情况不能一刀切。第三个坑是 GPU 利用率监控“看起来正常”。很多监控面板上的 GPU 利用率是整个进程的平均值LLM 解码阶段属于访存密集算力利用率本来就不会打满。如果你只用“GPU compute utilization”这一个指标来判断服务健康度很可能会误判。正确的做法是同时看显存带宽利用率和显存占用率用多个指标交叉验证才能准确判断推理实例是不是真的在高效工作。5.2 压测方法论模型服务上线前压测是必做动作但不少团队的压测方式不对直接一把梭把压力灌满拿到了一个“看起来很厉害”的吞吐数字但根本不能指导生产配置。正确的压测思路应该是分三个步骤。先做单请求延迟基线压测。固定并发数为 1测出 TTFT 和 TPOT 的基准值记录不同输入长度下延迟的变化。这一轮目的是摸清单个请求的资源消耗情况。再做并发梯度压测。并发数从 2 开始翻倍往上加每档跑 3 到 5 分钟观察 TTFT、TPOT、吞吐量和显存占用四条曲线。你很快会看到一个拐点并发继续往上加吞吐量不再增长TTFT 开始急剧上升。这个拐点就是服务的合理并发上限。最后做长稳压测。用生产环境真实流量的混合比例跑几小时甚至一整晚重点观察显存泄漏、长时间运行后性能是否衰减、以及偶发请求超时的情况。LLM 服务跑几个小时之后性能衰减的情况并不少见常见原因是显存碎片化或 KV Cache 命中率变化。只有长稳压测过了服务才敢真正放量接生产流量。5.3 我的实操心得最后分享几条实操心得都是真金白银换来的经验。第一不要追求单实例性能的最大化要追求整体系统的可维护性。曾经为了把一个 7B 模型的延迟压到最低我把所有能调的参数都调了个遍性能确实提升了 15%。但代价是配置极其特殊新来的同事完全看不懂。后来想加一个小改动结果花了整整两天重新调参数。现在我的习惯是配置保持“常规、可解释、可复制”性能够用就好不为了那一点极致收益牺牲可维护性。第二推理平台的演进节奏要跟着业务走。如果业务还没有流量就不要一上来就搭一个完整的平台先从单模型服务慢慢演进等模型数量超过 3 个再上容器化和 K8s等模型种类变多、流量变大再逐步建设网关、模型仓库和资源调度能力。平台建设的每一步都应该有明确的业务驱动力而不是为了技术而技术。第三团队里要有一个人对“全链路”负责。模型服务出问题的链路很长可能是预处理代码有 bug可能是推理框架版本不兼容也可能是网关限流配置不合理甚至是网络问题。如果团队里每个人只负责自己那一小块出了问题就会出现互相推诿、谁都说不清的情况。让一个人具备从入口到出口的全局视角对提升问题排查效率帮助非常大。第四一切配置都要代码化。模型的启动参数、静默参数、网关规则、扩缩容策略全部纳入 git 仓库管理。不要相信任何人“改完配置忘记同步”的记忆力也不要相信手工在服务器上敲命令的可靠性。凡是不能通过代码重建的环境本质上都是不可靠的环境。模型部署这件事走到最后你会发现框架选型、显存估算、并发调优这些硬技能固然重要但更重要的一层是工程思维知道什么时候该做重架构什么时候该做轻方案什么时候该守住稳定什么时候该追求性能。这些都是拿真实流量换来的经验希望这篇内容能让你少走一些弯路。