代码提交到上线监控,大模型的 MLOps 和传统软件部署根本不是一回事。这篇文章不讲概念,只谈那些在真实生产环境里踩过的坑,以及背后的系统思维。


开篇:为什么大模型一上线就"水土不服"

先说清楚一件事:很多人把 MLOps 当成"软件 CI/CD 套个模型",这是上线翻车的根源。

传统软件的 CI/CD,只管代码的编译和打包。 代码如果没变,每次打包出来的程序一定是一样的——这是确定性的。

而 MLOps 管的是代码 + 数据 + 超参数。 即便代码一行没改,今天的数据分布变了、或者随机种子不同,训出来的模型表现都可能天差地别。所以 MLOps 的流水线不仅要测代码逻辑,还要在部署前测模型的数据分布(Data Drift)和输出置信度。

到了大模型(LLM)这一层,问题又升级了一个维度:你要直面硬件的物理极限。 一个 70B 的模型,启动时光是把权重从分布式存储拉进 GPU 显存就要好几分钟;一次推理请求 10 个 token 还是 4000 个 token,对算力的消耗差出几个数量级。在这种约束下,大量传统的微服务治理策略会直接失效,必须结合 GPU 的特性重新设计。

所有可靠性设计的底层逻辑只有一句话:它本质上是在处理"流量的不可预测性"与"硬件的物理极限"之间的冲突。 下面这六场硬仗,就是围绕这句话展开的。


第一仗:环境一致性 —— 训练和推理是两个世界

这是业界最痛的点:"模型在我机器上训得好好的,一上线就跑不通。"

根源在于,训练用的是动辄 10GB 起步的巨型 PyTorch / TensorFlow 镜像,而部署要求的是极速启动、极致精简。两者一旦混用,必然出事。

终极解法只有一条:彻底分离训练镜像和推理镜像。 训练镜像可以臃肿,推理镜像必须做减法。

推理镜像瘦身的三板斧:

  1. 抛弃开发框架。 把 PyTorch / TF 模型导出为 ONNX 或 TensorRT 格式(Engine),推理时不再依赖庞大的训练框架。

  2. 多阶段构建(Multi-stage Build)。 第一阶段负责编译 C++ 依赖、拉取模型;第二阶段只保留极简的 Runtime(如 ONNX Runtime 或 Triton Inference Server),把中间产物全部丢弃。

  3. 剔除大毒瘤。 坚决不把训练集、Jupyter Notebook、甚至开发环境的 pip cache 打进推理镜像。靠这几招,能把动辄 10GB 的镜像压到 1GB 以下。

顺带聊聊:容器启动太慢怎么排查?

典型现象:K8s 扩容时,新 Pod 长时间卡在 ContainerCreating,迟迟接不了流量,结果把还在硬扛的老 Pod 压垮,服务可用性反而下降。

排查分两条线:

  • 查网络瓶颈。 模型文件太大(百 B 级模型动辄几百 GB),从远端对象存储拉取超时。解法是挂载对象存储(S3/OSS)或用 K8s 的 PVC,也可以把大模型做成宿主机的只读缓存盘(HostPath),避免每次冷启动重复下载。

  • 查 Lazy Load 耗时。 很多模型在启动时才去反序列化权重,这个过程很慢。解法是配置 K8s 的 startupProbereadinessProbe,并在代码里做"影子预热"——发几个假请求让模型先跑一次热身,等完全就绪后再接入真实流量。


第二仗:版本与血缘 —— 线上炸了,你能复现吗

面试官真正想问的是:线上模型出 Bug 了,你怎么证明它是哪批代码、哪批数据训出来的?

第一条铁律:Git 只管代码,绝对不能存模型和数据。 几百兆的权重和几十 G 的数据集塞进 Git,仓库会被直接拉爆。

代码层面用分支管理策略(如 GitFlow)各司其职:feature 分支写实验代码,develop 分支触发每日自动训练验证,main 分支绑定生产环境部署。

那模型和数据怎么管?这就是 DVC / MLflow 登场的地方,也是 MLOps 的灵魂所在。在实际项目里,我们用 DVC 给数据集和模型文件打快照,生成一个极小的 .dvc 校验文件,然后把这个校验文件存进 Git。

这样就建立起了完整的血缘追踪(Lineage):线上一旦出问题,我只需要拿到线上容器的 Git Hash,就能精准还原出当时训练用的超参数、数据集版本和生成的模型权重,实现 100% 的实验复现。出了事不再靠猜,而是靠回溯。


第三仗:上线前的体检 —— 别让新模型变"智障"

挑战在于:怎么在不拖慢发布速度的前提下,保证新模型没有变笨?

常规软件的测试当然要有——单元测试(测预处理逻辑会不会抛空指针)、集成测试(测接口连不连得通)。但模型有它独有的、必须做的测试:

  • 错题本测试(Corner Case)。 把历史上模型最容易判错的数据挑出来,攒成一个"错题本"数据集。新模型在 CI 流水线里必须 100% 答对错题本,只要错一道,直接阻断发布。

  • 性能基准测试(Latency / QPS)。 跑压力测试,盯 P99 延迟有没有超标。哪怕精度涨了 1%,只要推理耗时翻倍,照样不予通过——精度和延迟要一起看。

效率怎么平衡?

模型评测很慢。如果每次提交代码都在 CI 里跑全量数据集,程序员一天都合不进一次代码,谁也受不了。

答案是分级测试:提交代码时只跑一个极小的数据子集(Smoke Test,几分钟跑完,快速挡掉低级错误);合并到主分支后,再触发夜间的全量自动测试(Nightly Build)。用速度换覆盖率,各取所需。


第四仗:安全上线 —— 在高速上换轮胎

线上替换大模型,就像在高速公路上给行驶中的汽车换轮胎,稍有不慎就是事故。这里有两层东西要讲清楚:生命周期管理发布策略

K8s 探针:挡流量 + 杀僵尸

在传统微服务里,健康检查是瞬间完成的;但在大模型场景下,它是个极其沉重的物理过程。

就绪探针(Readiness Probe)的职责是"挡流量"。 70B 模型启动时,从分布式存储拉权重、加载进 GPU 显存,往往要好几分钟甚至十几分钟。这期间就绪探针必须一直返回 Failed,直到模型完全进入显存,并且成功跑完一次"影子预热"(Dummy Forward Pass,建立完整的 CUDA Graph),才能返回 Success。否则冷启动的请求一进来就会全部超时。

存活探针(Liveness Probe)的职责是"杀僵尸"。 LLM 推理中最致命的僵尸状态是 CUDA OOM(显存溢出)或 NCCL 通信死锁。存活探针不能只检查 HTTP 端口活没活,必须深度检查推理引擎(如 vLLM)的底层状态。连续失败达到阈值后,K8s 会直接发 SIGTERM 杀掉 Pod,触发重启。

频率要平衡。 探测太频繁会抢占 CPU 资源,太慢则会出现"流量黑洞"。通常存活探针的间隔设在 10–15 秒,而就绪探针在启动初期的探测窗口可以适当拉长。

蓝绿部署 vs 金丝雀发布

蓝绿部署: 准备两套完全一样的硬件集群,平时流量在蓝集群,新模型上绿集群,测试没问题后网关一把切过去。缺点很现实——极其费钱,大模型场景下根本备不起两套卡。

金丝雀(Canary)发布,大厂首选:

  1. 新模型上线,先只给 1% 的真实流量(通常针对内测用户,或按哈希分流)。

  2. 观察大约 1 小时,看两类指标:一看系统指标(显存有没有 OOM、延迟有没有飙),二看业务指标(模型输出的置信度是不是整体变低了)。

  3. 确认稳定,再按 5% → 20% → 50% → 100% 逐步放量。

回滚的第一准则:先止损,别查 Bug

一旦发现金丝雀版本表现异常,千万不要第一时间扎进去查 Bug。 第一准则永远是立刻止损:直接在 K8s Gateway 或 Istio 里,把那 1% 的流量切回原有的稳定版本(通常是版本仓库里打了 Last Known Good 标签的版本)。等系统恢复正常,再慢慢通过离线日志去查原因。线上不是 debug 的地方。


第五仗:流量治理 —— 大模型的请求天生不平等

大模型的流量治理有一个致命特点:请求的算力开销极度不均衡。 处理一个 10 token 的请求和一个 4000 token 的请求,对 GPU 的消耗天差地别。这一点决定了传统那套流量治理几乎全得重写。

先厘清三个常被混淆的概念:

  • SLA 是商务合同(承诺达不到要赔钱的标准);

  • SLO 是内部架构目标(如 99.9% 的请求延迟低于 2 秒);

  • SLI 是具体的监控数据指标(如实际测量出的 P99 延迟)。

负载均衡:轮询是灾难

在大模型场景下,传统的轮询(Round-Robin)是灾难。如果你把几个长文本请求恰好都分到了同一个节点,该节点会立刻显存碎片化并卡死。全局负载均衡必须基于"最小活跃 Token 数"或"KV Cache 剩余容量"来做路由决策,而不是简单地一人一个轮着发。

熔断与降级:保住主链路

  • 降级(Degradation): 流量高峰期,如果 vLLM 队列积压严重,网关可以主动降级。比如关掉部分非核心业务的调用,或者把请求从 70B 大模型透明降级路由到备用的 8B 小模型上,以此保住核心主链路的 SLO。宁可答得糙一点,也不能不答。

  • 熔断(Circuit Breaking): 这是微服务架构的"保险丝"。当某个节点的错误率飙升时,果断切断对它的请求,避免故障像滚雪球一样把整个集群拖垮(雪崩)。


第六仗:可观测性 —— 你得知道模型"变笨了"

监控系统是排障的眼睛。Prometheus + Grafana 是工业界的事实标准:Prometheus 是时间序列数据库,通过 Pull(拉取)方式去抓各个节点暴露出来的指标;Grafana 负责把这些枯燥的数字画成漂亮的仪表盘。但用在大规模 AI 集群里,极易踩坑。

Pull 还是 Push?

Prometheus 默认采用 Pull 模型,监控端主动去各个 Pod 拉 /metrics。好处是架构简单,而且不会因为业务流量突增就把监控中心打挂。Push 模型(通过 Pushgateway)通常只用在短期批处理任务里——任务跑完就没了,拉不到,只能让它主动推。

三层监控,缺一不可

只盯系统资源,在 MLOps 里是不及格的。一套合格的监控体系至少分三层:

  • 系统层(基础设施): GPU 利用率、显存使用率、网络 I/O。

  • 服务层(接口表现): QPS、P99 延迟、HTTP 500 错误率。

  • 模型层(最核心,Data Drift 监控): 如果前两层都正常,但业务转化率掉了,极大概率是数据分布漂移。要监控模型输出的预测概率分布——平时置信度都在 0.8 以上,今天大量掉到 0.4,就说明线上进来的数据是模型根本没见过的。

LLM 专属指标

面试绝不能只答 QPS、延迟、错误率(RED 原则)。大模型必须额外监控这几个核心指标,它们才是诊断算力瓶颈的关键:

  • TTFT(Time To First Token,首字延迟)

  • TPOT(Time Per Output Token,输出每个 token 的延迟)

  • MFU(GPU 算力利用率)

  • KV Cache 命中率 / 剩余率

告警分级:别当"狼来了"

告警最忌讳告警风暴,喊多了大家就麻木了。务必分级:

  • P0(致命级): 电话直接打醒值班人。例如多个推理 Pod OOM 崩溃、错误率突破 10%、主链路受影响。

  • P1(高危级): 钉钉 / 飞书群强提醒。例如 P99 延迟持续 5 分钟超出 SLA 阈值、GPU 利用率持续处于极限水位。

  • P2(关注级): 邮件或看板提示。例如检测到轻微的数据分布偏移,留给算法工程师第二天早上去判断要不要重训。

两个大规模监控的杀手

  • 高基数灾难(High Cardinality): 这是监控系统的头号杀手。如果往 Prometheus 的 Label 里塞了 user_idrequest_id 这种无限增长的变量,TSDB 的索引会瞬间爆炸,查询卡死甚至内存 OOM。Label 里只能放枚举值(如 model_versionhttp_status)。

  • 长期存储扛不住: 当 Prometheus 的本地 TSDB 撑不住长期数据时,引入 ThanosCortex。Thanos 利用对象存储(如 S3)做近乎无限容量的长期冷备,并通过分片机制解决跨地域大集群的统一查询问题。


结语:所有可靠性,最终都收敛到物理资源

把上面六场仗连起来看你会发现:无论是镜像分离、版本血缘、灰度发布,还是熔断与监控,最终都要收敛到一件事上——实际的物理资源调度。可靠性工程,说到底就是在和 GPU 的物理极限谈判。

那么留一个真正考验功力的开放问题来收尾:

大模型服务偶尔出现"响应时间突然升高,但 GPU 利用率并没有打满"的情况,你会优先怀疑哪个环节出现了阻塞?

这个现象本身就是最大的线索:它几乎可以排除"算力不足"。 卡都没吃满,说明瓶颈不在计算,而在计算之外。按经验,排查顺序大致是:

  • 请求排队 / 批处理调度。 最常见。vLLM 等引擎的调度队列积压,请求大量时间花在排队等待组 batch,而不是真正在算。GPU 自然不满,但端到端延迟飙了。

  • CPU 侧瓶颈。 Tokenization、反序列化、输入预处理这些步骤通常跑在 CPU 上,一旦单线程化、撞上 Python GIL,就会成为隐形的串行瓶颈。

  • 网络 I/O。 请求 / 响应的序列化、gRPC/HTTP 开销、过大的 payload,都会把时间消耗在数据搬运上而非计算上。

  • KV Cache 抖动。 显存碎片化或 cache 被频繁驱逐,导致重复计算和调度抖动。

  • 同步与锁竞争。 多卡推理时的 NCCL 同步等待、拖尾请求(straggler)拖累整批的完成时间。

一句话总结:当延迟和利用率背离时,八成是"算力之外"的环节在拖后腿——先去翻队列和 CPU,而不是急着加卡。 这,也正是大模型 MLOps 与传统运维最不一样的地方。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐