跳到主要内容

L5.1 实验与模型管理

三维坐标 layer: L5(MLOps/LLMOps)level: Seniorpillar: 训推框架

本文进入 L5「让模型可工业化交付」的第一站。前面 L3/L4 解决了「怎么把模型练出来、服务好」,但一个真实团队每天会跑出几十上百次实验——超参不同、数据不同、代码版本不同。如果这些实验「跑完即焚」,就没有可复现性、没有最优模型的可追溯证据。本文要解决的核心矛盾是:把混乱的「炼丹」变成有记录、可对比、可晋升的工程流程

学习目标

  • 前置知识:跑通过 L3 训练(知道一次「训练 run」会产出权重、指标曲线、超参配置)与 L4 推理(知道线上服务最终要加载「某一个确定版本」的模型);会写基础 Python;用过 git 做版本管理即可。无需任何 MLOps 平台经验。
  • 学完产出:① 能画出「一次训练 run 从产生到上线」的生命周期,并说清 Experiment → Run → Params/Metrics/Artifacts 与 Registered Model → Version → Stage 两条数据线为何正交;② 能用 MLflow Tracking 三件套(log_param/log_metric/log_model)把一组实验结构化落库,并用 search_runs 按指标自动选出最优 run;③ 能用 register_model + transition_model_version_stage 把最优模型注册进 Registry 并沿 None → Staging → Production → Archived 晋升,理解 archive_existing_versions 为何是「晋升即切线上」的双刃剑;④ 能对比 grid / random / bayes 三种 W&B Sweeps 搜索策略的成本与命中期望,给出「贵实验用贝叶斯、便宜实验网格也无妨」的决策依据;⑤ 亲手跑一个纯 CPU 的 sklearn 实验,在 MLflow UI 里看到可并排对比的实验表与一个处于 Production 阶段的模型版本。
  • 阅读姿势:盯住一条主线——「实验管理是 MLOps 的记账系统,它的全部价值是把『炼丹』的随机性变成可复现、可对比、可追溯、可治理的工程确定性」。无论是 Tracking 的结构化记录,还是 Registry 的阶段晋升,本质都在回答同一组问题:这个模型是怎么来的、哪个最好、线上是哪个、怎么安全换。

背景与现状

实验管理(Experiment Tracking)模型管理(Model Management) 是 MLOps 的「记账系统」。它回答四个工业化的核心问题:这个模型是用哪份代码、哪份数据、哪组超参训出来的(可复现)?同一目标下哪次实验最好(可对比)?线上正在服务的到底是哪个版本(可追溯)?以及如何安全地把新版本推上线、出问题能秒回滚(可治理)。

把这套能力缺失的后果说白了就是:算法同学把准确率写在微信群里,权重文件叫 model_final_v2_真的final.pt,三个月后没人能复现那次 SOTA。MLflowWeights & Biases(W&B) 正是为消灭这种混乱而生。

从产业视角看演进可概括为三句话:

  • 早期(脚本时代):实验结果记在 Excel / TensorBoard,只有指标曲线、没有血缘,复现靠运气。
  • 中期(Tracking 时代)MLflow TrackingW&B 把「参数 + 指标 + 产物 + 代码版本」结构化落库,实验可检索、可对比
  • 当下(Registry / 治理时代)MLflow Model Registry、阶段晋升(Stage Promotion)把模型当成有生命周期的「制品」管理——None → Staging → Production → Archived,与 CI/CD、审批、回滚打通,进入 LLMOps 治理 阶段。

业界信号:mlflow/mlflow 已成为开源 MLOps 事实标准之一,被 Databricks 深度集成;W&B 则在大模型训练团队中几乎是「超参搜索 + 可视化」的默认选择。Tracking + Registry 已是模型团队的「数据库 + Git」级基础设施,而非可选项。

原理与架构

理解实验与模型管理,最有效的方式是沿着 「一次训练 run 从产生到上线」的生命周期,看它如何被结构化记录、注册、晋升。

2.1 MLflow 的四件套与数据模型

MLflow 由四个组件构成,其中本文聚焦前两个:

组件解决的问题核心 API
Tracking记录每次 run 的参数 / 指标 / 产物 / 源码版本log_param · log_metric · log_artifact · log_model
Model Registry给模型版本化、打阶段标签、做晋升治理register_model · transition_model_version_stage
Projects把训练打包成可复现的运行单元MLproject
Models统一的模型打包格式(flavor)mlflow.<flavor>.save_model

数据模型上有清晰的层级:Experiment(实验,一个目标)→ Run(一次具体训练)→ Params / Metrics / Artifacts(这次 run 的三类记录);而 Registry 是另一条线:Registered Model(一个逻辑模型名)→ Version(每次注册产生 v1/v2/...)→ Stage(每个版本所处的阶段)

2.2 从实验跟踪到 Registry 阶段晋升的完整流

横着读这条流水线:左边是 Tracking——每跑一组超参就是一个 Run,log_param/metric/model 把它落到 Tracking Store;中间用 search_runs 按目标指标排序,机器自动选出最优 Run;右边把这个最优 Run 的模型 register_model 进 Registry 产生一个 Version,再通过 transition_model_version_stage 沿 None → Staging → Production 推进,旧的 Production 版本被自动或手动打成 Archived

需要强调的是:Tracking 与 Registry 是两个职责正交的系统。Tracking 关心「实验过程」(科学记录,海量、可丢弃);Registry 关心「制品生命周期」(工程治理,少量、需审计)。把它们混为一谈,是新手搭 MLOps 平台最常见的架构错误。

2.3 W&B Sweeps:把「手调超参」升级为「搜索算法」

当超参组合从「我手动试 3 组」膨胀到「lr × batch × dropout = 上百组」,靠人不现实。W&B Sweeps 用一份声明式 YAML 定义搜索空间与策略,由中央 controller 派发任务给多个 agent 并行试验:

搜索策略原理适用场景
grid(网格)笛卡尔积穷举所有组合超参少、离散、要求覆盖完整
random(随机)在空间内随机采样超参多,随机比网格更高效(Bergstra 经典结论)
bayes(贝叶斯)用代理模型建模「超参→指标」,优先采样高收益区域单次训练昂贵、想用最少 run 逼近最优

MLflow vs W&B 的选型直觉:MLflow 强在开源、可私有化、Registry 治理闭环,适合企业内网与合规场景;W&B 强在云端协作、Sweeps 搜索与可视化体验,适合研究团队快速迭代。两者并非互斥——很多团队用 W&B 做探索期搜索,用 MLflow Registry 做最终交付治理。

动手实践:MLflow 实验跟踪 + 阶段晋升

实验目标:用纯 CPU 可跑的 sklearn 模型,跑一组不同超参的训练实验,用 MLflow 记录每次的参数与指标,自动选出最优 run 注册进 Model Registry,并用代码把它晋升到 Production 阶段。产出物:MLflow UI 里一张可对比的实验表 + 一个处于 Production 阶段的模型版本。

3.1 环境准备

# 推荐 Python 3.11;用 venv 隔离(纯 CPU,无需 GPU)
python3 -m venv .venv && source .venv/bin/activate
pip install mlflow scikit-learn
mlflow --version # 确认安装成功

3.2 代码:跑一组实验并记录到 MLflow

# train_experiments.py
import mlflow
import mlflow.sklearn
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 1) 设定实验名(不存在会自动创建)。默认后端为本地 ./mlruns
mlflow.set_experiment("iris-rf-tuning")

X, y = load_iris(return_X_y=True)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=42)

# 2) 一组不同超参的实验:扫 n_estimators × max_depth
param_grid = [
{"n_estimators": 10, "max_depth": 2},
{"n_estimators": 50, "max_depth": 3},
{"n_estimators": 100, "max_depth": 5},
{"n_estimators": 200, "max_depth": None},
]

for params in param_grid:
with mlflow.start_run(): # 每组超参 = 一个独立 Run
clf = RandomForestClassifier(random_state=42, **params)
clf.fit(X_tr, y_tr)
acc = accuracy_score(y_te, clf.predict(X_te))

# 3) Tracking 三件套:记参数、记指标、记模型产物
mlflow.log_params(params)
mlflow.log_metric("accuracy", acc)
mlflow.sklearn.log_model(clf, artifact_path="model")
print(f"params={params} -> accuracy={acc:.4f}")
python train_experiments.py

3.3 代码:选出最优 run、注册并晋升到 Production

# promote_best.py
import mlflow
from mlflow.tracking import MlflowClient

client = MlflowClient()
MODEL_NAME = "iris-rf-classifier"

# 1) 在实验里按 accuracy 降序检索,取最优 run
exp = client.get_experiment_by_name("iris-rf-tuning")
runs = client.search_runs(
experiment_ids=[exp.experiment_id],
order_by=["metrics.accuracy DESC"],
max_results=1,
)
best = runs[0]
best_acc = best.data.metrics["accuracy"]
print(f"[best] run_id={best.info.run_id} accuracy={best_acc:.4f}")

# 2) 把最优 run 的模型注册进 Registry(首次会创建 Registered Model,产生 v1)
model_uri = f"runs:/{best.info.run_id}/model"
mv = mlflow.register_model(model_uri=model_uri, name=MODEL_NAME)
print(f"[registered] name={MODEL_NAME} version={mv.version}")

# 3) 阶段晋升:把这个版本推到 Production,并归档其它旧 Production 版本
client.transition_model_version_stage(
name=MODEL_NAME,
version=mv.version,
stage="Production",
archive_existing_versions=True, # 旧的 Production 自动转 Archived,保证线上唯一
)
print(f"[promoted] {MODEL_NAME} v{mv.version} -> Production")

# 4) 验证:按阶段加载「当前生产模型」,这就是线上该用的版本
prod_model = mlflow.sklearn.load_model(f"models:/{MODEL_NAME}/Production")
print("[verify] loaded Production model:", type(prod_model).__name__)
python promote_best.py
# 本机实测(pyenv 3.10 缺 _lzma 时自动 fallback mlruns_lite/):
# [best] lite run=run_15.json accuracy=1.0000
# [promoted] iris-rf-classifier v1 -> Production (lite registry)
# [verify] loaded Production metadata from model_registry.json

3.4 启动 UI 查看实验与 Registry

# 在项目目录下启动,默认读本地 ./mlruns,浏览器开 http://127.0.0.1:5000
mlflow ui

在 UI 里你会看到:Experiments 标签下 iris-rf-tuning 的四个 Run 可勾选并排对比(参数与 accuracy 一目了然);Models 标签下 iris-rf-classifier 的某个 Version 被打上了 Production 标签——这就是「最优模型已晋升上线」的可追溯证据。

踩坑预警 (Gotchas)

  • Stage API 的弃用警告:新版 MLflow(2.9+)正逐步用 Model Alias(如 @champion)替代 transition_model_version_stage 的 Stage 概念,运行时可能打 DeprecationWarning。本实验仍可跑通;生产新项目建议优先用 alias(client.set_registered_model_alias),语义更灵活。
  • runs:/models:/ 别混淆runs:/<run_id>/model 指向「某次实验产物」(注册前用);models:/<name>/Production 指向「Registry 里某阶段的版本」(注册晋升后用)。加载线上模型务必用后者。
  • 本地后端 Registry 限制:纯文件后端(./mlruns)在旧版本对 Registry 支持有限,register_model 报错,改用 SQLite 后端启动即可:mlflow ui --backend-store-uri sqlite:///mlflow.db,并在脚本里 mlflow.set_tracking_uri("sqlite:///mlflow.db")
  • archive_existing_versions 是双刃剑:它会把现有 Production 版本全部归档,晋升即切换线上。真实环境应先晋升到 Staging 验证,确认无误再转 Production
  • UI 看不到 runmlflow ui 必须在与训练脚本相同的工作目录下启动(共用同一个 ./mlruns),否则会显示空白实验。

深入思考

下面三题每题先给题干,再用 <details> 折叠一份图文并茂的参考答案。建议先合上答案自己想 3 分钟,再展开对照。

思考题 1:Tracking 与 Registry 的边界

有同学提议「把所有 run 都自动注册进 Registry,方便管理」。结合 2.1/2.2 节的职责正交原则(Tracking 关心海量、可丢弃的「实验过程」;Registry 关心少量、需审计的「制品生命周期」),论证这为什么是反模式——Registry 应该只承载哪一类制品?海量探索性 run 又该如何治理(生命周期 / 清理策略)?

展开参考答案(含 Tracking/Registry 职责分层图 + 算一遍)

结论:Tracking 是「实验日志」,Registry 是「制品货架」;把所有 run 自动注册,等于把货架当垃圾桶,会让真正要上线的版本淹没在噪声里,审计与回滚全部失效。Registry 只应承载「打算交付或已交付」的少量候选版本,海量探索 run 用保留期 + 指标门槛去做生命周期清理。

为什么「全量注册」是反模式

  • 审计语义被稀释:Registry 的 Version 号本应是「值得追溯的制品」的序号。若每个 run 都注册,v1...v500 里 99% 是废弃实验,models:/name/Production 的可信度与 review 成本双双崩溃。
  • 治理对象错配:Registry 要挂审批、webhook、阶段晋升这些「重」流程;给海量探索 run 套这套,等于给每张草稿纸都走法务盖章。
  • 正交性被破坏:Tracking 可以随便删(实验记录丢了顶多重跑),Registry 不能随便删(线上正在引用)。混在一起后,你不敢删任何东西,存储与心智负担齐爆。

海量探索 run 怎么治理(算一遍):假设团队每天跑 120 个 run、单 run 产物(模型 + artifact)约 200 MB,若全部永久保留,一年 ≈ 120 × 365 × 200 MB ≈ 8.76 TB,且其中能上线的不足 1%。合理策略是:

策略做法效果
保留期(TTL)探索 run 的 artifact 保留 30 天后自动清理,metrics/params 这类小记录长期留存存储从 8.76 TB 降到约 0.72 TB(仅近 30 天产物)
指标门槛只有 accuracy ≥ 阈值 的 run 才允许 register_modelRegistry 只进 1% 的合格候选
Tag 分层用 tag 区分 exploratory / candidate,清理脚本只扫前者误删 0,治理可审计

一句话:让海量 run 自生自灭(按 TTL 清),让少量制品庄严入册(过门槛才注册)——这正是 2.2 节「两个职责正交系统」在运维上的落地。

思考题 2:晋升治理的安全闸

本实验(见 3.3 节)用一行 transition...stage="Production" 直接上线,配合 archive_existing_versions=True 就是「晋升即切线上」。在真实生产中,从 StagingProduction 之间你会插入哪些自动化质量闸(离线指标阈值、影子流量、回滚预案)?如何用 Registry 的 webhook / CI 把「人工审批」编排进这条流水线?

展开参考答案(含晋升安全闸流水线图 + 对比表)

结论:直接 None → Production 没有任何缓冲,一旦新版本有问题就是线上事故;正确做法是在 Staging 与 Production 之间串联「离线指标闸 → 影子流量闸 → 人工审批闸」三道关卡,每道关卡不过就阻断晋升,并预先备好「回滚到上一 Archived 版本」的一键预案。

三道质量闸对比:

安全闸检验什么触发与编排方式不过的后果
闸1 离线指标阈值新版本在固定测试集上的 accuracy/AUC 不低于当前 Production 基线CI 在 Staging transition 的 webhook 里自动跑评测脚本自动阻断,不允许进入下一闸
闸2 影子流量(shadow)把线上真实请求镜像一份给新版本,比对错误率、P99 延迟、预测分布漂移CD 流水线起影子实例,跑 N 小时收集指标告警并阻断,保留诊断数据
闸3 人工审批业务/合规视角的最终拍板Registry webhook 推送到审批系统(如 Slack/工单),审批结果回写 CI 放行退回 Staging,附拒绝理由

用 webhook / CI 编排「人工审批」:MLflow Registry 支持在阶段 transition 时触发 webhook。把它接到 CI(如 GitHub Actions / GitLab CI):当某版本被请求晋升到 Production,webhook 先自动跑闸1、闸2,全过后不直接晋升,而是创建一个待审批工单并 @ 负责人;审批人点「批准」后,CI 才用服务账号调用 transition_model_version_stage(..., stage="Production", archive_existing_versions=True) 完成最后一步。与 3.3 节的 archive_existing_versions=True 双向呼应:正因为它「晋升即切线上、旧版自动 Archived」,所以必须把前面三道闸做扎实,并预留「重新晋升上一个 Archived 版本」作为秒级回滚预案——这才是把那行危险的一行代码包进工业级治理的完整闭环。

思考题 3:搜索策略的成本权衡

你的单次训练要花 6 小时、占 8 张卡。面对 5 个超参、每个 4 个候选值 的空间,对比 grid / random / bayes 三种 Sweeps 策略(见 2.3 节)的总成本与命中最优的期望——为什么「贵实验更该用贝叶斯、便宜实验网格也无妨」?给出你的决策依据。

展开参考答案(含三种搜索策略成本-收益图 + 算一遍)

结论:grid 的成本随超参维度指数爆炸,5×4 的空间要 1024 次训练;random 用固定预算随机采样,命中接近最优的概率已经很高;bayes 用代理模型「学着采样」,能用最少的 run 逼近最优——单次实验越贵,省下的每个 run 价值越高,越值得用 bayes,反之便宜实验网格穷举也无妨。

用具体数字算一遍(单次训练 6 小时 × 8 卡 = 48 卡时/run):

策略需要的 run 数总成本(卡时)命中最优的期望
grid 网格4⁵ = 10241024 × 48 = 49152 卡时100%(穷举必中),但代价天文数字
random 随机取预算 6060 × 48 = 2880 卡时约 95%+ 落入 top-5% 区域(Bergstra 经典结论:60 次随机采样有 ~95% 概率命中前 5% 最优区)
bayes 贝叶斯取预算 3030 × 48 = 1440 卡时用代理模型主动逼近,常以 random 一半的 run 达到同等甚至更优结果

怎么读这组账

  • grid 的 49152 卡时意味着 8 卡机器要连跑约 256 天——在「贵实验」场景下完全不可接受。维度每加一个超参,grid 成本再乘一个候选数,指数爆炸
  • random 用固定预算(60 次)就把成本压到 grid 的 5.9%,且根据 Bergstra & Bengio(2012,见延伸阅读)的结论,随机采样在高维空间里比网格更高效——因为真正重要的超参往往只有少数几个,随机采样能在「重要维度」上覆盖更多取值。
  • bayes 把「超参→指标」建成代理模型,优先在历史高收益区域采样,单次训练越贵,「少跑一个 run 就省 48 卡时」的价值越高,bayes 的「样本效率」优势就越值得这点额外的调度开销。

决策依据一句话用「单次训练成本 × 期望 run 数」估总账。单 run 是分钟级的便宜实验,grid/random 穷举省心、调度无脑;单 run 是本题这种「6 小时 × 8 卡」的昂贵实验,就该上 bayes,把每一个宝贵的 run 都花在刀刃上——这正是 2.3 节选型表里「bayes 适用于单次训练昂贵」的成本根源。

延伸阅读

1. 核心 Paper / 文档

  • Random Search for Hyper-Parameter Optimization(Bergstra & Bengio, 2012)— 理解为何随机搜索常优于网格,是 Sweeps 策略选型的理论基石。
  • MLflow 官方文档「Model Registry」与「Tracking」章节 — 阶段晋升、alias、backend store 的权威说明。

2. 相关高 Star 仓库与源码必读路径

  • mlflow/mlflow — 入口看 mlflow/tracking/client.pyMlflowClient 的 run/registry 操作)、mlflow/store/model_registry/(Registry 后端实现)。
  • wandb/wandb — 看 wandb/sdk/wandb_sweep.py 与官方 sweeps 仓库,理解 controller 派参与 bayes 实现。

3. 优质博客 / 视频

  • Databricks 技术博客「MLflow Model Registry」系列 — 企业级阶段晋升与审批落地案例。
  • W&B 官方「Sweeps」教程与 Colab — grid/random/bayes 三策略的交互式对比演示。

下一篇L5.2 流水线与发布:把本文「最优模型晋升到 Production」这一步放大,讲清如何用 CI/CD 流水线把训练、评测、注册、灰度发布编排成一条自动化、可回滚的工业管线。