大模型MLOps实战:从实验环境到线上稳定部署
2026/9/7 15:53:19 网站建设 项目流程

大模型做出来容易,真正能稳定跑在线上,靠的是 MLOps 那套功夫。

我最近刚把一个多模态问答项目从 Jupyter Notebook 一路推到生产环境,中间踩了不少坑,也沉淀了一些比较通用的方法论。很多团队做大模型研发,都会先经历一段"实验室里效果好得飞起,一上线就各种问题"的阶段,比如显存爆掉、响应超时、并发一高就卡死,甚至模型和代码对不上版本,出了问题根本不知道是哪个版本的权重在跑。这篇文章就围绕大模型从实验环境到线上服务的 MLOps 实践,把整个流程拆开讲清楚,包括环境管理、模型版本化、服务化部署、可观测性和告警,以及常见的线上事故排查。适合正在做大模型应用开发,或者打算把自己跑通的模型推上生产的工程师参考。

1. 实验环境和线上服务之间,到底隔着多少坑

1.1 你以为的"能跑"和生产要求的"稳定",是两个概念

很多人做大模型实验,都是在一个 Python 文件或者 Notebook 里写完推理逻辑,本地跑通就觉得自己已经完成了。但实验环境的"能跑"和生产环境的"稳定运行"是完全不同的概念。实验的时候,你只需要保证自己对,模型能出结果就行;但线上服务面对的是不确定的请求流量、不同的输入格式、网络波动、GPU 资源争抢、甚至模型偶尔的胡言乱语,这些都要在服务层面兜住。

我见过不少团队直接把 Notebook 里的代码复制到 FastAPI 里就上线了。结果就是:模型推理占用的显存没做静态分配,多个并发请求直接把显存打爆;输入没有做长度截断,一个超长文本把 context 占满,推理时间直接翻倍;没有做超时控制,模型推理卡住的时候整个请求线程被拖死,最终导致服务雪崩。这些在实验环境里根本不会暴露,因为你自己测试的时候根本不会同时发几十个请求。

1.2 MLOps 解决的就是实验到生产的"最后一公里"

MLOps 的核心目标,是把机器学习模型的整个生命周期——数据准备、训练、评估、部署、监控、告警、回滚——用工程化的手段串起来。对大模型来说,这个诉求更强烈,因为大模型的权重文件动不动就几个 GB 到几十个 GB,训练和推理的资源消耗也大,不可能像普通模型那样随便重训一把。

从实验环境到线上服务,我理解 MLOps 主要解决四类问题:

  • 环境一致性:实验时用的 Python 版本、CUDA 版本、PyTorch 版本,线上必须完全一致,否则模型行为可能微妙地改变。
  • 版本可追溯:线上跑的是哪个 commit 的代码、哪个版本的权重、哪份数据训练出来的,必须能一键查到。
  • 部署可重复:同样的镜像、同样的配置,在任何一台机器上启动都能得到一样的结果。
  • 线上可观测:模型的响应延迟、吞吐、显存占用、输入分布变化,都要有监控和告警,出了问题能快速定位。

这四件事,每一件单独拎出来都不难,但组合在一起,就是大模型上线的分水岭。我见过很多项目死在"部署的时候才发现环境配不起来"或者"模型更新了一次之后效果突然变差但完全不知道差在哪里"。

2. 实验环境搭建:从"在我电脑上能跑"到"在任何机器上能跑"

2.1 依赖管理不是 requirements.txt 就够的

很多人以为把依赖写进 requirements.txt 就算管理好了,其实这是个误区。对大模型项目来说,依赖分为几个层次,每一层都有自己的版本约束:

  • 系统层:CUDA 驱动版本、cuDNN 版本、显卡驱动。这一层最容易出问题,因为 PyTorch 不同版本编译时依赖的 CUDA 版本不一样,如果机器上还跑着其他需要特定 CUDA 版本的任务,随便升级驱动很容易把别的服务搞挂。
  • Python 包层:torch、transformers、accelerate、vllm 这些核心库。这里最头疼的是 torch 和 CUDA 的匹配关系,比如 torch 2.1.0 对应 cu121,torch 2.0.1 对应 cu118,装错了经常出现"torch.cuda.is_available() 返回 False"的诡异问题。
  • 业务代码层:你的预处理逻辑、prompt 模板、后处理解析。这一层也得做版本管理,因为 prompt 模板改一个词,模型输出可能就完全变了。

我自己的做法是,所有大模型项目一律用 Docker 做环境固化。Dockerfile 里锁死基础镜像(比如 nvidia/cuda:12.1.0-cudnn8-devel-ubuntu20.04)、Python 版本、pip 依赖版本,用 requirements.txt 时全部去掉 ">=" 改成 "==" 钉死版本号。这样在一台新机器上,只需要 docker build 一次,就能复现实验环境,不用再去折腾。

2.2 模型权重和实验记录:光有代码仓库是远远不够的

代码可以进 Git,但大模型的权重文件动辄几个 GB,不适合直接塞进 Git 仓库。而且光有权重文件也没用,你还得知道这份权重是怎么训练出来的、用的什么数据、什么超参、经过了哪些轮次、验证集效果如何,否则日后出问题你根本无从排查。

我的建议是引入 MLflow 或者 WandB 这类实验跟踪工具,把每一次实验的关键信息都记录下来,包括:训练参数(learning rate、batch size、epoch)、数据集版本、模型在验证集上的指标(loss、准确率等)、权重文件的存储路径。MLflow 里有一块 Model Registry 功能,可以把训练好的模型注册为一个版本,绑定到具体的实验 run 上,这样线上部署的时候,只要指定注册的模型版本,服务就拉起对应权重的模型,不会出现"其实部署的还是上一个版本"这种尴尬。

这里补充一个重要细节:对于大模型微调,权重管理最好用增量方式。比如基于 llama-7b 做 LoRA 微调,保存权重时只保存 LoRA 的 adapter 权重(几十到几百 MB),基础模型权重单独存放。这样不仅省存储,而且模型切换、回滚都很快。我见过有团队把整个 7B 模型全量另存一份,几十个版本下来,几个 TB 的磁盘直接不够用,纯属自找麻烦。

3. 线上服务构建:推理框架选型和关键配置

3.1 推理框架不是随便选一个就行

实验环境里,你大概率直接用 transformers 的 pipeline 或者 model.generate() 跑推理,简单方便。但线上服务的并发一上来,这种方式立刻成为瓶颈。transformers 的推理是同步的、每请求独占显存,而且没有做 KV Cache 的复用和调度,并发 10 个请求可能就把显存打满了。

线上我推荐用专门的推理加速框架,目前比较主流的有:

  • vLLM:利用 PagedAttention 技术管理 KV Cache,支持 Continuous Batching,可以把多个请求动态拼接到一个 batch 里推理,吞吐量比 transformers 原生推理高很多。如果模型本身支持,vLLM 是最省心的选择,因为 API 格式是 OpenAI 兼容的,客户端接入成本几乎为零。
  • TensorRT-LLM / Triton:如果对延迟有极致要求,想用 TensorRT 做算子融合和量化,可以考虑这条路线。但配置复杂度高,不是所有模型都开箱即用,需要自己做 engine 构建,更适合对团队工程能力有底气的场景。
  • Ollama:如果只是本地或者小规模部署,图省事可以用 Ollama 拉模型直接起服务。它底层也做了不少优化,但对于高并发、定制化部署,还是不如 vLLM 可控。

我个人的选型原则很简单:先看模型的生态成熟度。如果是 LLaMA、Qwen、ChatGLM 这类主流开源模型,直接上 vLLM,社区的坑基本都被踩平了;如果是冷门模型,框架不一定支持,那就只能 transformers + 多进程部署,配好并发控制和超时兜底。

3.2 显存、批处理、并发这几个参数是怎么算出来的

部署大模型服务最核心的参数就是显存估算。很多人上来就问"7B 模型要多少显存",答案取决于精度和上下文长度。

按 FP16 精度算,7B 模型权重本身需要大约 14GB 显存(7B × 2 bytes)。但这只是权重,推理时还有 KV Cache、中间激活值、CUDA context 的开销。经验上,7B 模型 FP16 至少要预留 20GB 显存才比较稳,13B 模型建议 40GB 以上,70B 模型想单卡跑基本不可能,得分卡或者用量化。

量化是省显存的关键手段。4bit 量化下,7B 模型权重只有大约 3.5GB,单张 24GB 的 4090 就能很轻松地跑起来。但量化是有代价的:模型质量会小幅下降,尤其是中文文本生成和一些逻辑推理场景,下降可能比较明显。所以我一般建议,如果显存不是特别紧张,优先用 FP16 或 BF16;只有显存实在不够的时候才考虑量化,而且量化后一定要在测试集上对比效果。

vLLM 的批处理参数也需要根据请求量调整。核心是--max-num-seqs这个参数,它表示一个 batch 里面最多同时处理多少个序列,默认值通常是 256,但实际建议根据请求并发量来。请求量大、延迟要求不高,可以提高这个值来提升吞吐;要是延迟敏感,就调小一点。另一个是--max-model-len,表示最大上下文长度,这个建议按业务实际需求来,设得太大 KV Cache 占显存,设得太小超长输入会被截断。我一般先压测再定这两个参数,不会盲目调。

3.3 线上服务形态:容器化 + 网关 + 弹性伸缩

推理框架选好了,服务不能直接裸跑在一台机器上。我推荐的线上形态是:Docker 容器 + Kubernetes 调度 + 网关层。

推理服务打包成 Docker 镜像的好处是环境完全隔离,镜像里包含推理框架、模型代码、依赖库,但模型权重通常不打包进镜像,而是通过外部存储(比如 NAS、对象存储或者共享盘)挂载进来。这样做的好处是模型更新只需要换挂载路径或者更新镜像里的配置,不需要重新构建一个十几个 GB 的镜像。

Kubernetes 里跑推理服务,重点是把 GPU 当资源调度。给 Pod 设置nvidia.com/gpu: 1的 resource limit,K8s 会自动调度到有 GPU 的节点上;配置存活探针和就绪探针,模型加载完成之前不要接流量;HPA 可以根据 GPU 利用率或者 CPU 指标做弹性伸缩。不过要注意,大模型推理服务的冷启动很慢,因为光模型加载就要几十秒到几分钟不等,所以缩容策略要保守一点,防止流量高峰时扩容跟不上,反而因为冷启动把请求全部超时掉。

我给一个最低可行方案的拍脑袋配置:一台 GPU 机器,部署 vLLM 容器,前面挂一个 Nginx 做反向代理和超时控制,API 路径转发到 vLLM 的 8000 端口。如果只用一台机器,暂时不上 K8s 也没关系,只把这些配置固化到 docker-compose 文件里,保证任何一台同配置机器都能一键拉起来。

3.4 服务上线可观测性:日志、指标、告警三件套

服务上线以后,可观测性是最容易被忽视但又最关键的环节。模型服务和普通 Web 服务不一样的地方在于,不仅要看系统指标,还要看模型质量指标。

系统层面:请求延迟(P50/P95/P99)、吞吐量(QPS)、GPU 显存占用率、GPU 利用率、请求失败率。这些建议通过 Prometheus + Grafana 采集和展示,GPU 相关的指标可以用 nvidia-dcgm-exporter 导出。

业务层面:输入文本长度分布、输出长度分布、模型推理的 token 数、用户对生成结果的反馈(如果有赞/踩功能的话)。这些指标能帮你判断线上输入分布是不是和训练时一致,如果线上输入突然变得特别长,模型的性能和效果都会受影响。

日志不能随便打,每次请求至少要记录:请求 ID、输入摘要(不要打全量,注意隐私)、模型版本、推理耗时、输出 token 数、错误信息。这样线上出了问题,可以通过请求 ID 串联整个链路,排查是服务本身的问题,还是模型生成的问题。

告警这一块,除了常规的延迟、错误率告警,我建议加一个"输入异常检测",比如请求文本长度超过某个阈值、请求频率突然飙升、连续多个请求触发安全过滤等,都值得告警。告警通道可以用钉钉或者企业微信的 Webhook,把 Prometheus Alertmanager 的告警消息推到群里面,实现实时感知。我看到有些团队在问"线上 Sentry 服务能不能配置 Webhook 通知到钉钉",这个是可以的,Sentry 自带集成能力,可以把异常事件推送到钉钉群机器人,对于捕捉线上模型的未捕获异常很有用,建议把 Sentry 接到告警体系里。

具体配置 Sentry 到钉钉的路径大概是:项目设置里找到 Alert 规则,新增一条规则,动作选 Webhook,填上钉钉机器人的 Webhook 地址,再加一些过滤条件(比如 environment 是 production,事件类型是 exception)。钉钉那边创建一个自定义机器人,选"自定义关键词"或者"加签"安全设置就行。这样服务一旦抛异常,钉钉群里马上就收到消息,比人等看板上线快得多。

4. 模型评估与上线决策:不是"看起来还行"就能上线的

4.1 离线评估:指标不只看 loss

模型训练完不能只看训练集 loss 降了多少,更不能只靠人工看几个例子觉得"效果不错"就决定上线。我的习惯是准备一份固定的评估集(不参与训练,且覆盖线上可能的输入场景),每次模型更新都在这份评估集上跑一遍,记录几个关键指标:生成结果的 accuracy(如果是分类抽取类任务)、ROUGE/BLEU(如果是摘要生成类任务)、回答的格式正确率(能不能被下游解析)、空回答率和超长回答率。

这块如果只是粗略看效果,很容易出问题。我踩过一个很典型的坑:模型在测试集上指标还挺好,但上线之后,用户问的是测试集里完全没见过的方式,模型答非所问。后来排查发现,评估集覆盖面太窄,全是训练数据同分布的问题,没覆盖到长尾场景。所以评估集的建设,一定要主动纳入边界情况,比如超长输入、生僻领域词汇、多轮对话中用户突然换话题、诱导性和有害内容输入等。

4.2 线上评估与灰度发布:别让模型裸奔上线

模型再好,我都不建议直接切全量流量。正确的做法是灰度发布:先在影子环境或者一小部分流量上跑新模型,观察一段时间,对比新旧模型的效果,再逐步放大流量比例。

影子模式(Shadow Mode)是我比较喜欢的一种方式:线上正常用旧模型产出结果,同时把同一份请求复制一份发给新模型,但是新模型的结果不返回给用户,只记录日志做对比。这种模式对用户完全无感,却能比较真实地评估新模型在真实流量上的表现。

灰度发布时要把控好流量比例和观测周期。比如先切 5% 流量,观察 2-3 天,看核心指标(用户反馈、延迟、拒绝率)有没有恶化,再逐步加到 10%、30%、100%。如果新模型效果明显更差,直接切回旧版本,不用犹豫。这里就要用到模型注册表里的版本记录了,回滚只是改一下配置指向旧版本权重的问题。

4.3 数据漂移和模型漂移:模型上线只是开始

模型上线不是终点,而是一个持续运营的过程。线上真实用户的输入分布会随着时间发生变化,比如某个热点事件出现后,大家的提问方式突然就不一样了;或者产品做了一次功能改版,输入结构也跟着变了。这种情况下,模型的效果就会慢慢下降,这就是数据漂移。

我的做法是定期(比如每周)对线上输入做一次分布统计,对比训练时的输入分布,重点看:输入长度均值/分位数、高频词/主题分布、Prompt 模板的命中率。发现漂移明显时,就要考虑用最近的数据做增量微调。另外模型自身的输出质量也要盯,比如输出 token 数的趋势、一段时间的生成内容是不是出现重复、无意义或者安全违规,这些可以用规则+定期人工抽检双管齐下,避免模型在线上"悄悄变笨"。

我在实践中甚至遇到过模型生成结果突然大量重复的情况,排查下来发现不是模型变了,而是某次上线时temperature参数被误改成了 0.1,导致随机性大幅降低。这类配置类的坑,靠人工发现很慢,最好的办法是每次发布前把服务配置模板化、Review 一遍,并记录配置版本,出问题能快速 diff。

5. 常见问题与排查技巧实录

5.1 经典故障:一并发就显存爆掉

这是最典型的问题。现象是:单请求测试正常,用压测工具一发并发请求,服务直接 OOM 或者报 CUDA out of memory。排查思路是先看是不是显存整体不够,用nvidia-smi看显存占用曲线,如果单个请求占用的显存高得离谱,多半是模型没有走批处理,每个请求独占了一份 KV Cache。解决办法是换 vLLM 这类带 Continuous Batching 的推理框架,另外把并发数限制、排队策略做好配置。

5.2 经典故障:冷启动导致请求超时

模型加载慢是常态,尤其在 K8s 环境里,Pod 刚创建时还没有完全就绪,流量就进来了,请求全部超时。这个问题常见于 HPA 扩容。我在探针配置上用了一个思路:就绪探针的initialDelaySeconds要大于模型加载时间,探针接口可以专门做一个"模型是否已加载"的检查,而不是简单地返回 HTTP 200。同时缩容策略要保守,避免高峰期频繁扩缩容。

5.3 经典故障:测试环境好,线上效果差

模型效果线上线下不一致,先别急着怀疑模型被换错了。按这个顺序排查:一是确认输入预处理是否一致,有些代码在实验里做了清洗和截断,线上服务忘了做;二是确认推理参数是否一致,比如temperaturetop_pmax_new_tokens有没有被设成不同的值;三是确认模型和 tokenizer 版本是否匹配,模型换了权重但 tokenizer 还是旧的,生成结果就可能乱掉;四是看线上输入分布是不是和测试集差异很大,如果是,那就要重新收集数据做迭代,而不是单纯回滚。

我把这几个常见问题整理成了一张速查表,贴给团队用:

现象排查思路建议方案
并发后显存不足看单请求显存占用、是否没做 batch、KV Cache 是否巨大换 vLLM/Triton;限制并发;调整 KV Cache 策略
冷启动请求超时看就绪探针配置、模型加载耗时调大 initialDelaySeconds;加载完成再注册服务;探针检查模型状态
线上效果变差排查预处理、推理参数、版本匹配、输入分布统一配置模板;评估集验证;定期分布对比;必要时做增量微调
响应延迟抖动明显看是否被其他任务抢占了 GPU、是否为静态 batch、调度是否抖动预留 GPU 显存;绑定核心;单机单推理服务;调整 batch 策略
模型生成内容重复/空洞看推理参数是否被改动、采样温度是否过低恢复默认参数;记录每次发布的配置 diff

5.4 压测与容量规划:多用 ab 和 wrk 试出容量边界

上线前我一定做一次压测,摸清服务的容量底牌。用 ab(ApacheBench)或者 wrk 模拟不同并发量,观测延迟和吞吐的变化曲线。关键要找到两个值:一个是延迟开始急速上升的并发数(可以认为是服务的最优吞吐点),另一个是服务开始报错、显存打满的极限并发数(上线后不能碰触的红线)。

根据压测结果配置限流和队列策略。比如 vLLM 本身有请求队列,压测到极限后,在网关层也可以加限流,防止流量洪峰把服务打垮。容量规划上,我一般按"预估峰值 QPS 的两倍"来准备 GPU 资源,留足扩缩容和单点故障的余量。

5.5 监控告警实战:用 Sentry 接钉钉,把异常播报做到群里

监控告警是我特别想强调的一点。大模型服务比普通服务更需要告警,因为模型推理的失败不仅可能是服务问题,还可能是模型本身"疯"了。我现在的标配是 Prometheus + Grafana + Alertmanager + 钉钉,另外加上 Sentry 抓取异常详情。

Sentry 配置到钉钉的流程很快:在钉钉群添加自定义机器人,拿到 Webhook 地址(注意安全设置,建议用"加签"模式);然后在 Sentry 项目设置里新建一条 Alert Rule,触发条件设为“抛出未捕获异常”或者“错误率超过阈值”,动作选择发送 Webhook,把钉钉机器人的 Webhook URL 填进去。为了不轰炸群,可以配置一个rate limit,比如 5 分钟最多一条。

这套打通之后,线上模型服务如果突然抛出一堆显存错误或者处理超时异常,钉钉群会立刻收到告警,我可以在手机上先判断是服务问题还是模型问题,再决定要不要回滚或者修改配置。这种“被异常推着走”的模式,远比靠监控大屏盯出来的被动响应要有效。

6. 从实践角度聊聊大模型 MLOps 的落地路径

如果团队是第一次做大模型上线,我的建议是先不要追求一步到位搭建全套平台。MLOps 平台工具(训练平台、特征平台、模型仓库、一键部署)听着很美好,但对一个刚开始接大模型的团队来说,复杂度反而会吞噬掉本就不多的迭代速度。

我个人推荐的落地顺序是:先用最朴素的方案把第一个模型推到线上,哪怕只是 Docker + vLLM + 一个简单的日志文件;然后逐步叠加实验记录(MLflow)、监控指标(Prometheus)、告警通知(Sentry + 钉钉)、参数化部署(K8s)。每一步都等稳定跑一段时间再往后加,这样出了问题你清楚地知道是最近哪次改动引起的,排查范围小很多。

我还想强调一点,大模型上线后一定要留好回滚路径,而且回滚操作要能在一分钟内完成。模型服务不像普通代码,出问题时的修复手段有限,很多时候重训模型来不及、微调也来不及,最快、最稳妥的止损方式就是切回上一个稳定版本。所以模型注册表和多版本管理不是可有可无的花架子,它就是应对线上事故最强的安全网。

最后再分享一个我自己的习惯:每次发布新模型前,我会在本地准备一份手动回归清单,包含大约 10 个典型的 prompt,覆盖正常、边界、异常三类情况。发布后第一时间跑一遍清单,用肉眼比对关键输出和上一版的差异。这份工作看着简单粗暴,但已经帮我拦下来好几次"指标没问题但实际体验有明显差别"的模型。做模型上线这件事,很多时候靠谱比花哨重要得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询