模型不是"写出来的代码",而是"数据喂出来的产物"——这是 MLOps 与传统 CI/CD 最大的分水岭。传统 CI/CD 保证"代码正确",MLOps 还要保证"数据新鲜、特征一致、模型没跑偏、线上达标"。本文按一条可落地的流水线展开:数据准备 → 训练 → 评估门禁 → 模型注册 → 金丝雀/A-B 发布 → 监控回滚,并给出 Kubeflow、Airflow、MLflow、DVC 的真实配置与坑。
目录
- 1. MLOps 与传统软件交付的差异
- 2. 训练流水线:数据准备与特征工程
- 3. 评估流水线:离线指标与模型门禁
- 4. 模型注册与版本化:MLflow 与 DVC
- 5. CI 里的模型测试:数据漂移与公平性
- 6. 模型发布与 A/B:金丝雀与影子流量
- 7. GPU 资源编排与 CI 成本
- 8. 模型回滚与生产监控
- 9. 案例与最佳实践
1. MLOps 与传统软件交付的差异
1.1 传统 CI/CD 的适用边界
传统交付:提交代码 → 测试 → 构建 → 部署 → 监控(确定性系统)
ML 交付: 数据变更 → 训练 → 评估 → 注册 → 发布 → 监控 → 再训练
每个环节都有不确定因素:数据分布、模型指标、线上行为都会变
1.2 ML 交付的四个特有挑战
| 挑战 | 表现 | 对策 |
|---|---|---|
| 数据即代码 | 模型质量取决于训练数据 | DVC 数据集版本化 + 血缘 |
| 指标非布尔 | 不是过/不过而是 AUC 涨跌 | 评估门禁 + 阈值策略 |
| 试错成本高 | 一次全量训练数小时 | 缓存中间结果 + 增量训练 |
| 线上漂移 | 生产分布随时间变化 | 漂移监控 + 自动回滚 |
1.3 MLOps 成熟度分层
0 层 Notebook 手工训练 / 1 层训练脚本进 CI/CD / 2 层模型注册+自动发布 / 3 层监控+自动回滚
多数团队从第 1 层起步:训练/评估脚本进 Git 统一流水线触发,收益最大
2. 训练流水线:数据准备与特征工程
2.1 数据集版本化:DVC
模型复现的前提是"数据可回滚"。DVC 把数据指针存进 Git,数据本体存对象存储。
dvc init && dvc add data/train.parquet data/test.parquet
dvc remote add -d storage s3://ml-data-bucket/dvc
dvc push # Git 只提交几十字节的 .dvc 指针
# 线上跑偏:git checkout <旧commit> && dvc checkout 拉回数据快照
2.2 特征存储消除训练-服务偏差
训练用特征工程与在线推理不一致会造成 Train-Serve Skew。把特征定义收敛到 Feature Store(Feast/Tecton),训练与在线共用同一特征视图,从源头消除偏差。
from feast import FeatureView, Field
from feast.types import Float32
view = FeatureView(name="user_features", entities=["user_id"],
schema=[Field(name="ctr_7d", dtype=Float32)],
online=True, source=parquet_source)
2.3 训练编排与触发
Airflow 管调度依赖(天级重训、跨系统协调),Kubeflow 管实验流水线,两者可并存。触发方式:定时(0 2 * * *)、数据分区新增、漂移超阈值(需人工确认)、手动。
# Kubeflow Pipelines(概念)
@dsl.pipeline(name="train-pipeline")
def pipe(data_uri, epochs):
prep = data_prep_op(data_uri)
train = train_op(prep, epochs) # GPU 训练
eval_ = evaluate_op(train) # 离线评估
register_op(eval_) # 写回 MLflow Registry
3. 评估流水线:离线指标与模型门禁
3.1 评估集分层
整体指标:AUC / F1(全局);分组指标:按地域/设备/年龄切片防"偏科"
鲁棒指标:新样本与对抗样本稳定性;业务指标:转化率、召回率(离线近似)
3.2 评估门禁设计
评估脚本输出指标如 {"auc":0.873,"group_auc_android":0.81,"drift_score":0.12},门禁规则任一不满足则流水线失败。
rules:
auc: { min: 0.86 }
group_auc_android: { min: 0.85 } # 安卓端偏科 → 拦截
drift_score: { max: 0.15 }
3.3 评估的两种形态
联内评估:训练完在流水线内评估(训练=构建,评估=测试)
持续评估:对已注册模型定时拉生产数据跑离线指标,发现劣化提前预警
💡 要点:评估必须可复现(固定代码、数据版本、随机种子),否则门禁失效。
4. 模型注册与版本化:MLflow 与 DVC
4.1 MLflow Model Registry
mlflow models register -m runs:/<run_id>/model -n recommendation_model
# 阶段流转:None → Staging → Production → Archived,评估门禁通过后自动置 Staging
4.2 模型与数据血缘
血缘链:数据版本(DVC) → 特征版本(Feast) → 训练 run(MLflow) → 模型版本(Registry) → 部署 revision → 线上流量。出问题顺着血缘反查"用哪份数据、哪个特征版本训的"。
# MLflow run 固化血缘标签
mlflow.start_run(run_name="daily_retrain_20260930")
mlflow.log_params({"data_version": "v42", "feast_view": "user_features@2026-09-29"})
4.3 版本策略
不可变:模型一经发布不可覆盖,只能新增版本
命名:<业务>-<日期>-<run_id前8位>,如 reco-20260930-a3f2c9d1
保留:生产版本 + 最近 N 个历史版本可回滚,其余归档清缓存
5. CI 里的模型测试:数据漂移与公平性
5.1 数据漂移检测
漂移指线上输入分布与训练时不一致,常用 PSI、KS 检验、分布距离衡量。
from evidently.report import Report
from evidently.metric_preset import DataDriftPreset
Report(metrics=[DataDriftPreset()]).run(
reference_data=train_sample, current_data=production_sample)
# 输出每个特征的 PSI/drifted 标记,作为 CI 断言
门禁:>5% 特征漂移 → 黄灯;>15% → 红灯拦截并通知数据团队。
5.2 公平性与偏差测试
公平性=分组指标不塌方:按性别/年龄/地域分组计算指标,检查预测差异率是否超阈值(如转化差 >20% 视为有偏)。检测到偏差 → 阻止自动发布,走人工审查。
5.3 影子评估
用历史流量回放:过去 N 天线上请求喂给新模型打分,对比新旧模型预测分布与命中指标。
不切流量即可评估新模型线上表现
6. 模型发布与 A/B:金丝雀与影子流量
6.1 发布策略对比
| 策略 | 风险 | 流量 | 回滚 | 适用 |
|---|---|---|---|---|
| 蓝绿 | 低 | 100% 切换 | 秒级 | 模型无关流量 |
| 金丝雀 | 低 | 5%→50% | 分钟级 | 在线模型服务 |
| A/B | 可控 | 按实验组 | 随实验结束 | 业务指标验证 |
| 影子 | 零 | 0% 全量打分 | 无需 | 新模型预评估 |
6.2 服务网格做流量切分
# Istio A/B 切分 90/10
http:
- match: [{ headers: { ab-group: { exact: "B" } } }]
route: [{ destination: { host: reco-svc, subset: v2 } }]
- route:
- destination: { host: reco-svc, subset: v1, weight: 90 }
- destination: { host: reco-svc, subset: v2, weight: 10 }
6.3 自动回滚触发器
任一触发即回滚:线上 AUC/点击率环比下降超阈值 / P99 超 SLO 3 个周期 /
预测失败率 >1%。回滚 = 把流量切回上一稳定模型版本
7. GPU 资源编排与 CI 成本
7.1 GPU 队列与调度
训练 Pod 声明独占一卡:resources: { limits: { nvidia.com/gpu: "1" }, requests: { nvidia.com/gpu: "1" } }。调度策略:按优先级分队列(生产 > 实验 > Notebook),Kueue/Volcano 做 quota 与抢占;MIG/时间片共享只给 Notebook,训练任务一律独占。
7.2 CI 触发 GPU 训练
train:
stage: train
image: pytorch/pytorch:2.3-cuda12.1-cudnn8
tags: [gpu-runner] # 专用 GPU Runner 池
script:
- dvc pull && python train.py --epochs 20
- python evaluate.py
artifacts: { paths: [metrics.json] }
7.3 成本控制三板斧
缓存:特征结果与预训练 checkpoint 存对象存储
并发:同机型训练全局最多 N 个并发
计量:按部门/项目打标签,FinOps 分摊 GPU 账单(nvidia-smi + gpu_utilization 看板)
8. 模型回滚与生产监控
8.1 监控指标分层
系统层:延迟/吞吐/错误率/GPU 利用率;模型层:预测分布、空预测率、置信度均值
业务层:点击率、转化率;漂移层:特征漂移、标签漂移
8.2 PromQL 监控示例
sum(rate(reco_click_total[1h])) / sum(rate(reco_impression_total[1h])) # 点击率
histogram_quantile(0.99,
sum(rate(reco_inference_duration_seconds_bucket[5m])) by (le)) # P99 延迟
rate(reco_prediction_errors_total[5m]) > 0.01 # 错误率超阈值
8.3 回滚机制
回滚不是"删掉新模型"而是"指回上一稳定版本":
拉 Registry 上一 Production 版本 → 更新服务镜像 tag(不可变)→
切流量 + 观察 → 复盘(数据/特征/模型问题)再决定修复重训
ℹ️ 提示:把"模型+数据+特征版本 + 部署 revision"打成一条记录写审计日志,回滚复盘都靠它。
9. 案例与最佳实践
9.1 真实案例
某电商推荐团队(概念案例):每天凌晨 2 点重训,先手工跑脚本再手动部署,返工率高
改造:Airflow 定时触发 Kubeflow → MLflow 自动注册 → CI 门禁(AUC/分组/漂移)
→ 金丝雀 10% 观察 1 小时 → 全量
一次:监控发现召回率环比降 8%,自动回滚到上一版模型
效果:发布从"周"缩短到"日",回滚从小时级降到分钟级
9.2 最佳实践 Checklist
□ 数据用 DVC 版本化,代码/数据/模型三者对齐
□ 特征收敛到 Feature Store,消除训练-服务偏差
□ 评估门禁覆盖整体 + 分组 + 漂移,阈值可配
□ 模型进 Registry,阶段流转走审批,不可变
□ 血缘链完整:数据→特征→run→模型→部署 revision
□ 发布走金丝雀/A-B,配置自动回滚触发器
□ GPU 分队列调度,训练独占卡,缓存中间产物
□ 线上监控分层(系统/模型/业务/漂移)
□ 每次发布记录版本指纹,可随时追溯与回滚
9.3 常见坑
| 坑 | 现象 | 对策 |
|---|---|---|
| 只训不评 | 上线才发现指标差 | 评估门禁进流水线 |
| 评估不可复现 | 每次 AUC 不同,门禁失效 | 固定数据版本与随机种子 |
| 特征不一致 | 线上与训练偏差大 | 特征存储统一来源 |
| 直接全量发布 | 跑偏影响全量用户 | 金丝雀/A-B + 自动回滚 |
| 只监控系统层 | 模型质量劣化看不见 | 加模型层与业务层指标 |
| 版本覆盖 | 回滚找不到旧模型 | 不可变版本 + 归档策略 |
小结
MLOps 流水线 = 数据版本化(DVC)→ 训练编排(Airflow/Kubeflow)→ 评估门禁(整体+分组+漂移)→ 模型注册(MLflow)→ 金丝雀/A-B 发布 → 分层监控 → 自动回滚。核心三点:数据即代码要版本化、模型指标要门禁化、线上行为要监控回滚化。先做最小闭环(训练脚本进 CI + 评估门禁),再叠加发布策略与自动回滚,是性价比最高的路线。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。