笔记本里的模型并不是一个系统。Hidden Technical Debt in Machine Learning Systems(Sculley 等,NeurIPS 2015)里那张著名的图用方框说明了这一点:在真实的机器学习系统中,机器学习代码只占很小一部分。其余部分是数据采集、特征提取、模型服务、监控,以及连接它们的各种管道,而生产环境中的问题正是从这些地方冒出来的。
MLOps 架构图画的就是这“其余部分”。本文以一个欺诈评分模型为例绘制这样一张图,讲解每个生产模型都有的两个闭环,并列出需要标注的内容,让这张图能回答模型出问题时人们会提出的那些疑问。
两个闭环和一个触发器
每个生产模型都有一个训练闭环和一个服务闭环:
- 训练闭环。 采集数据,把数据转换为特征,用来训练候选模型,与当前模型比较,然后注册。
- 服务闭环。 请求到达,查询特征,由模型打分,并把预测记录下来。
- 触发器把两者连接起来。对服务闭环的监控、定时计划或新数据,都会让训练闭环重新开始。
| 组件 | 职责 | 回答的问题 |
|---|---|---|
| 特征存储 | 每个特征只有一个定义,离线供训练使用,在线供评分使用 | 训练和服务用的是同一个特征吗? |
| 流水线编排器 | 按顺序执行训练步骤,并记录每次运行 | 能重新构建出这个模型吗? |
| 模型注册表 | 每个模型的版本、指标和审批状态 | 线上是哪个模型?之前是哪个? |
| 模型服务 | 在线端点或批处理作业 | 多快、多频繁、为谁服务? |
| 监控 | 输入、预测和结果随时间的变化 | 模型现在还准吗? |
Google 的 MLOps 指南描述了三个成熟度级别:第 0 级是手动流程,第 1 级为持续训练实现训练流水线的自动化,第 2 级再为流水线本身加上 CI/CD。画出你现在所处的级别,而不是计划中的级别,并标出手动步骤。
训练与服务不一致
MLOps 架构图最常防住的 bug,是训练与服务不一致(training-serving skew):同一个特征在训练 SQL 里用一种方式计算,在服务代码里又用另一种方式计算。模型离线看起来不错,上线后却变差,而且没人说得清原因。上面提到的 Google 指南建议把特征存储作为实验、持续训练和在线服务的唯一数据源,让每个特征只有一个定义。
第二个经典 bug 是跨时间的数据泄漏。一笔支付的训练数据行,必须使用该笔支付发生时的特征值,而不是今天的特征值。把这个时间点(point-in-time)要求写在从特征存储指向训练流水线的连线上。
示例:银行卡支付的欺诈评分
支付 API 在授权一笔银行卡支付之前,会向模型请求一个欺诈评分。设计如下:
- 事件。 支付 API 把每一笔支付发布到 Kafka。一个流处理作业在在线存储中维护滚动特征,比如每张卡过去一小时内的支付次数。同样的事件也会进入数据仓库。
- 标签。 一笔支付是否属于欺诈,要通过数周甚至数月后才到达的拒付(chargeback)才能知道。拒付数据被加载到数据仓库,并与原始支付关联。
- 特征。 特征存储为每个特征只定义一次,从数据仓库计算离线值,并把最新值写入在线存储。
- 训练。 编排好的流水线按时间点构建训练集,校验数据,训练候选模型,在同一评估集上与线上模型比较,然后连同指标一起注册。
- 发布。 经批准的版本部署到评分端点:先以影子模式运行,对线上流量的副本打分而不影响决策;然后承接一小部分流量;最后承接全部流量。
- 监控。 每一个评分都连同特征一起被记录。监控把输入分布和评分分布与训练数据对比,并在标签陆续到达时计算精确率和召回率。漂移告警或质量告警会启动重新训练。
图表内容
- Card network disputes
- Warehouse
- Feature store
- Training pipeline
- Model registry
- Drift alert
- Payments API
- Kafka
- Online features
- Fraud scoring
- Prediction log
- Model monitoring
- chargebacks, later
- payment events
- event history
- stream features
- batch features
- materialize
- point-in-time set
- candidate + metrics
- approved version
- features
- score(payment)
- scores
- scores + features
- late labels
- drift, quality drop
- retrain
这张图的读法如下:
- 上排,训练。 拒付标签和事件历史在数据仓库中汇合。特征存储为流水线构建训练集,流水线再把候选模型注册到模型注册表。
- 下排,服务。 支付 API 把事件发布到 Kafka,并同步调用评分端点。端点从在线存储读取特征,并把每个评分写入预测日志。
- 注册表的连线是模型进入生产环境的唯一途径:永远是经批准的版本,绝不是从笔记本里复制出来的文件。
- 右侧,回流闭环。 监控读取预测结果和迟到的标签。出现漂移或质量下降时会产生告警,进而启动训练流水线。
需要标注什么
- 训练集连线上的时间点规则。
- 端点和注册表上的模型版本和阶段。
- 特征查询和评分的延迟预算,因为支付在等待它们。
- 标签延迟。 如果标签要几周后才到,就写出来:它决定了你能多快衡量模型质量。
- 重新训练的触发条件:定时计划、漂移阈值、数据量。
- 负责人。 数据、特征、模型和端点往往归不同的团队负责。
常见错误
- 只画一个叫“机器学习模型”的方框。 它把特征存储、注册表和监控闭环都藏了起来,而故障恰恰是从这些地方开始的。
- 没有反馈路径。 如果标签从不回流,就没人知道模型是什么时候变差的。
- 生产路径上有笔记本。 模型只能通过流水线和注册表进入服务。
- 同一个特征有两种实现。 训练 SQL 和服务代码用不同方式计算“同一个”值,就会导致不一致。
- 无法回滚。 在注册表中保留上一个版本,并画出端点如何切换回去。
- 只监控基础设施。 模型出错时,延迟和错误率可能依然完美;输入、评分和结果也都要监控。
服务这一侧属于常规的分布式系统工作。事件驱动架构图介绍了 Kafka 部分;对于带检索的语言模型,RAG 架构图介绍了索引和服务。
在 Archboard 中构建
- 使用 Google 或 GitHub 登录,并新建一张图。
- 添加 ML 节点包中的 特征存储、一个表示注册表的 模型,以及一个用于评分的 端点。
- 添加数据节点包中的 Kafka、一个 数据仓库、一个作为在线存储的 Redis,以及一个作为预测日志的 主题。
- 添加 DevOps 节点包中的一个 流水线 用于训练、一个 指标 节点用于模型监控,以及一个 告警 作为重新训练的触发器。
- 添加 C4 模型节点包中的一个 容器 表示支付 API,以及一个 外部系统 表示卡组织的拒付数据。
- 把它们连接起来,在每条连线上标注所传输的数据,并把迟到的标签和重新训练触发器设为虚线。再添加一个基础节点包中的 Markdown 块,写明发布步骤。
这个示例共用 13 个图表节点,在 Free 套餐的限制之内。借助通过 MCP 连接的 AI 智能体,你可以描述这条流水线,让它摆放节点,然后再调整标签。
常见问题
我需要特征存储吗? 第一天不需要。你需要的是每个特征只有一个定义,并且训练和服务都使用它。当多个模型共享特征,或者你需要在线获取新鲜特征时,特征存储是实现这一点的常见方式。
模型注册表和制品存储有什么区别? 制品存储只保存文件。注册表在此基础上增加了版本、指标、血缘关系和审批状态,这样部署步骤就能请求经批准的版本,而不是某个文件路径。
模型应该多久重新训练一次? 数据发生实质变化有多频繁,就多久训练一次。先从定时计划开始,监控就绪后再加上漂移和质量触发条件,并记录每次重新训练的原因。
标签迟到时,如何监控模型? 先监控眼下就能看到的东西,比如输入分布、评分分布和被标记支付的占比;等标签到达后再计算精确率和召回率。两条路径都要画出来。
训练和服务应该共享基础设施吗? 它们可以共享特征定义和注册表,但负载特征不同。把它们画成两条独立的路径,在特征存储和注册表处汇合。