1. 这不是PPT动画,而是一套让机器学习模型真正跑起来的“产线”逻辑
你有没有遇到过这样的场景:花了三周时间调出一个在测试集上准确率92%的模型,兴冲冲打包发给工程团队,结果对方回一句:“这个模型怎么加载?输入格式是什么?依赖哪些库?GPU显存要多少?有没做数据漂移监控?”——然后项目就卡在了“交付”这道窄门上。这不是个别现象,而是过去五年里我参与过的17个AI落地项目中,14个都踩过的坑。所谓MLOps,从来不是给机器学习加个“Ops”后缀那么简单;它本质上是一套把算法工程师的“实验室成果”,变成运维团队能像部署Nginx或MySQL一样稳定维护的生产服务的整套协作语言、流程规范和工具链。今天这篇Part 1,不讲抽象概念,不堆术语金字塔,我们就从一个真实电商推荐模型的上线过程切入,拆解MLOps最核心的骨架: 版本控制如何覆盖数据、代码、模型三要素;实验追踪为什么不能只记acc和loss;以及模型注册表(Model Registry)到底在解决什么具体问题 。关键词里的“Towards AI”和“Medium”只是发布渠道,真正值得你花时间琢磨的,是背后这套让AI不再“一跑就灵、一上线就崩”的工程化思维。无论你是刚跑通第一个TensorFlow示例的新手,还是带过三个以上算法团队的技术负责人,只要你希望模型最终不是躺在Jupyter Notebook里吃灰,而是每天为真实用户产生商业价值,这篇就是为你写的实操手册。
2. MLOps整体设计与思路拆解:为什么必须打破“算法-工程”之间的那堵墙
2.1 传统机器学习工作流的致命断点
先看一张我画过不下二十遍的草图:左边是算法工程师的世界——Jupyter Notebook里写代码、调参、画ROC曲线;右边是运维/工程团队的地盘——Kubernetes集群、Docker镜像、Prometheus监控告警。中间那条虚线,就是绝大多数AI项目死亡的温床。我见过最典型的断点有三个:
第一, 数据漂移无人感知 。某金融风控模型上线后第三个月,审批通过率突然从68%跳到82%。排查发现,上游业务方悄悄把“用户是否持有本行信用卡”这个字段的取值逻辑从“当前持有”改成了“历史曾持有”,但数据管道没做Schema校验,特征工程脚本照常运行,模型输入悄然变异,而监控系统只盯着accuracy,对输入分布变化完全失明。
第二, 模型复现性彻底丧失 。一位同事离职前留下的模型,文档里只有一句“用XGBoost训练,参数见config.py”。三个月后业务方要求复现并微调,结果发现config.py里引用了一个本地路径的CSV文件,而那个文件在Git仓库里根本不存在;更糟的是,他当时用的XGBoost版本是1.3.0,而新环境默认装的是1.7.0,两个版本在缺失值处理上存在细微差异,导致相同参数下AUC相差1.2个百分点。
第三, 上线决策缺乏客观依据 。当新模型A在离线测试中AUC比旧模型B高0.5%,但推理延迟高了30ms,该不该上线?如果只看指标,答案似乎是肯定的;但如果这个服务承载着每秒5000次的实时推荐请求,30ms延迟意味着需要多扩容4台GPU服务器,年成本增加18万元。没有统一的评估框架,这种决策只能靠拍脑袋。
MLOps的设计起点,就是直面这三个断点。它不追求“一步到位建个大平台”,而是用最小可行闭环(MVP Loop)先打通最关键的三环: 可复现的实验记录 → 可验证的模型包 → 可审计的上线流程 。这就像修一条高速公路,不必一开始就铺完所有车道,但得确保第一段路基扎实、标线清晰、有明确的入口和出口标识。
2.2 核心架构选型:为什么我们放弃自研,选择MLflow + DVC + Custom Registry组合
在2021年我们启动MLOps基建时,团队内部激烈争论过技术栈。有人主张自研——“开源工具太重,我们业务场景简单,自己写个轻量版够用”;也有人力推SageMaker Pipelines——“AWS全家桶,省心”。最终我们选了MLflow + DVC + 自建轻量Registry的组合,理由非常务实:
-
MLflow解决实验追踪的“最后一公里” 。它的Tracking Server能自动捕获代码、参数、指标、输出文件,甚至支持PyTorch Lightning等主流框架的原生集成。最关键的是,它不强制你改写训练逻辑——你只需在训练脚本开头加
mlflow.start_run(),结尾加mlflow.log_artifact("model.pkl"),就能获得完整的实验快照。对比Kubeflow Pipelines那种需要把整个训练流程定义成YAML的方案,MLflow的学习成本低一个数量级,算法工程师当天就能上手。 -
DVC(Data Version Control)专治数据顽疾 。Git天生不适合管大文件,而我们的用户行为日志单日就超20GB。DVC把数据文件替换成小的元数据指针(.dvc文件),实际数据存放在S3或MinIO里,Git只跟踪指针变更。这样,当你
git checkout v1.2时,执行dvc pull就能精准拉取该版本对应的数据集,彻底解决“数据在哪”“用的是哪天的数据”这种基础问题。我们实测过,用DVC管理10TB级数据集,Git仓库体积稳定在2MB以内,而直接Git LFS会让仓库膨胀到40GB+且频繁卡死。 -
自建Registry而非用MLflow Model Registry,源于一个血泪教训 。MLflow自带的Registry功能强大,但它把模型元数据(如owner、上线时间、AB测试流量比例)和模型二进制文件强耦合在同一个后端。去年一次线上事故中,因后端数据库连接池耗尽,Registry API全部超时,导致所有模型上线审批流程瘫痪。我们痛定思痛,将Registry拆分为两层: 元数据层用PostgreSQL(高可用集群) ,只存模型ID、版本号、负责人、上线状态、关联实验ID等轻量信息; 模型文件层用对象存储(MinIO) ,按
<model_id>/<version>/路径存放,通过元数据层的URL字段指向。这样,即使对象存储短暂不可用,审批流程仍可进行;反之,元数据库宕机,模型服务也能继续运行。这个“解耦”设计,是我们用两次P0级故障换来的经验。
这个组合不是技术炫技,而是每个组件都在解决一个具体、高频、痛感强烈的痛点。它不追求“最先进”,但求“最稳、最易用、最易排查”。
2.3 设计哲学:MLOps不是自动化,而是“可追溯性”的极致追求
很多团队把MLOps等同于“用CI/CD自动训练模型”,这是巨大误区。真正的MLOps核心价值,不在“自动”,而在“可追溯”。举个例子:当线上模型效果下跌时,传统做法是让算法工程师凭记忆回忆“上周改了什么”,而MLOps完备的系统会给你一份精确到毫秒的因果链:
2023-09-15 14:22:07—— 模型v2.3上线(Registry记录)
↑ 关联实验ID: mlflow-8a3f2c(Registry元数据)
↑ 该实验使用数据版本: dvc-7b1e9d(MLflow Artifact中记录的DVC指针)
↑ 数据版本dvc-7b1e9d对应S3路径: s3://data-bucket/raw/20230910/(DVC元数据)
↑ 实验代码提交哈希: a1b2c3d...(MLflow自动捕获的Git commit)
↑ 训练时Python环境: conda-env-20230914.yaml(MLflow logged conda env)
有了这条链,定位问题不再是大海捞针。上周效果下跌,你只需检查
20230910/
这个数据目录下,是否有新增字段或Schema变更;再比对
a1b2c3d...
这个commit,看特征工程代码是否引入了新的归一化逻辑。这种追溯能力,才是MLOps赋予团队的真正免疫力。它不保证模型永远不坏,但保证坏的时候,你能以最快速度找到病灶。
3. 核心细节解析与实操要点:手把手构建你的第一个可追溯实验
3.1 数据版本控制:DVC实战,告别“数据在哪”的灵魂拷问
DVC的安装和初始化极其简单,但几个关键配置点决定了后续是否顺滑。我们以电商用户行为数据为例,演示完整流程:
# 1. 初始化DVC(在已有的Git仓库内)
pip install dvc
dvc init
git add .dvc/
git commit -m "init dvc"
# 2. 将原始数据目录加入DVC管理(假设数据在data/raw/下)
dvc add data/raw/
# 此时DVC会:
# - 在data/raw/下生成data/raw.dvc文件(元数据指针)
# - 将实际数据移动到.dvc/cache/下(默认本地缓存)
# - 在.gitignore中添加data/raw/,防止被Git跟踪
提示:
.dvc/cache/是DVC的本地缓存,默认在项目根目录下。生产环境务必修改为共享存储,否则团队成员dvc pull时会各自下载一份副本,浪费空间。我们在dvc remote add myremote s3://my-bucket/dvc-cache后,执行dvc remote modify myremote --local region us-east-1,并dvc remote set-default myremote。这样所有dvc push/pull操作都指向S3,本地只存指针。
最关键的一步是
数据版本打标
。不要依赖Git分支或Tag来标识数据,因为数据变更频率远高于代码。我们采用语义化数据版本号:
YYYYMMDD-HHMMSS
(如
20230915-142207
)。每次新数据就绪,执行:
# 假设新数据已放入data/raw/,更新DVC指针
dvc add data/raw/
# 提交DVC元数据变更(注意:不提交实际数据!)
git add data/raw.dvc .gitignore
git commit -m "update raw data to version 20230915-142207"
# 推送数据到远程存储
dvc push
此时,
data/raw.dvc
文件内容类似:
outs:
- md5: a1b2c3d4e5f67890...
path: data/raw/
cache: true
metric: false
persist: false
这个
md5
值就是该数据集的唯一指纹。当算法工程师需要复现某个实验时,只需
git checkout
到对应commit,再
dvc pull
,DVC会根据
.dvc
文件中的md5,从S3精准拉取那一份数据。我们曾用此方法,在客户现场成功复现了半年前的一个异常检测模型,而当时的数据源API早已下线。
注意:DVC不替代数据库。它只管理用于训练/验证的静态快照数据(如每日导出的ODS表)。实时流数据、在线特征库,需用Flink/Kafka等专门方案,DVC不介入。
3.2 实验追踪:MLflow的正确打开方式,不止于log_metric
MLflow Tracking Server的部署有两种模式:本地(
mlflow server
)和托管(如Databricks MLflow)。我们选择自建Server,原因在于对元数据的完全掌控。部署命令如下:
mlflow server \
--backend-store-uri postgresql://user:pass@db-host:5432/mlflow \
--default-artifact-root s3://mlflow-bucket/artifacts/ \
--host 0.0.0.0 \
--port 5000
这里有两个极易被忽略的坑:
-
--default-artifact-root必须是对象存储(S3/MinIO),绝不能是本地路径 。MLflow默认把模型、日志等大文件存本地,一旦Server重启或迁移,所有Artifact丢失。我们吃过亏:早期用file:///mlflow/artifacts,某次磁盘满导致服务崩溃,恢复后发现所有模型文件全没了。 -
PostgreSQL后端必须开启连接池 。默认配置下,高并发实验提交时会出现
Too many connections错误。我们在postgresql.conf中设置max_connections = 200,并在MLflow启动参数中加--gunicorn-opts "--workers=4 --worker-class=gevent"提升并发处理能力。
在训练脚本中,MLflow的集成要遵循“最小侵入”原则。以下是我们标准模板:
import mlflow
import mlflow.sklearn
from sklearn.ensemble import RandomForestClassifier
# 1. 启动实验(自动创建或复用同名实验)
mlflow.set_experiment("ecommerce-recommender")
with mlflow.start_run(run_name="rf-v1.2-20230915"):
# 2. 记录所有关键参数(不只是模型参数!)
mlflow.log_param("data_version", "20230915-142207") # 关键!关联DVC版本
mlflow.log_param("feature_list", ["user_age", "item_price_log", "session_length"])
mlflow.log_param("n_estimators", 100)
# 3. 训练模型
model = RandomForestClassifier(n_estimators=100)
model.fit(X_train, y_train)
# 4. 记录指标(分类任务必记:precision, recall, f1, auc)
y_pred = model.predict(X_test)
mlflow.log_metric("precision", precision_score(y_test, y_pred))
mlflow.log_metric("auc", roc_auc_score(y_test, model.predict_proba(X_test)[:, 1]))
# 5. 记录模型(自动保存为sklearn格式,含conda环境)
mlflow.sklearn.log_model(model, "model")
# 6. 记录代码快照(关键!)
mlflow.log_artifact("train.py") # 主训练脚本
mlflow.log_artifact("features.py") # 特征工程模块
# 7. 记录数据指针(手动关联DVC)
with open("data_version.txt", "w") as f:
f.write("20230915-142207")
mlflow.log_artifact("data_version.txt")
实操心得:
mlflow.log_artifact()比mlflow.log_model()更灵活。后者会自动打包模型和环境,但有时你需要记录中间产物,比如特征重要性图(plt.savefig("feature_importance.png"))、混淆矩阵热力图,这些用log_artifact直接上传,UI里点开就能看,比在Notebook里截图靠谱得多。
3.3 模型注册表:从“模型文件”到“可管理资产”的跃迁
MLflow自带的Model Registry功能虽好,但我们坚持自建轻量Registry,核心在于 分离关注点 。以下是我们的PostgreSQL表结构设计(精简版):
CREATE TABLE model_registry (
id SERIAL PRIMARY KEY,
model_id VARCHAR(64) NOT NULL, -- 业务唯一标识,如 "recommender-v2"
version VARCHAR(16) NOT NULL, -- 语义化版本,如 "2.3"
experiment_id VARCHAR(64), -- 关联MLflow实验ID
status VARCHAR(16) DEFAULT 'staging', -- staging / production / archived
owner VARCHAR(64) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
description TEXT,
artifact_url VARCHAR(255) NOT NULL -- 指向MinIO的完整URL,如 "https://minio.example.com/models/recommender-v2/2.3/model.pkl"
);
-- 索引加速查询
CREATE INDEX idx_model_id_status ON model_registry(model_id, status);
模型注册流程是人工触发的,而非全自动。我们刻意保留“人工审批”环节,因为上线决策涉及业务影响评估。流程如下:
-
算法工程师在MLflow UI中找到目标实验,点击“Register Model”,输入
model_id(如recommender-v2)和version(如2.3); -
后端服务(一个简单的Flask API)收到请求后:
-
校验
experiment_id在MLflow中是否存在且状态为FINISHED; - 从MLflow Artifact API下载模型文件到临时目录;
-
将模型文件上传至MinIO的
models/{model_id}/{version}/路径; -
向
model_registry表插入一条记录,status='staging',artifact_url填入MinIO URL;
-
校验
- 邮件通知相关方(算法Owner、SRE、产品经理)进入审批队列。
审批通过后,执行SQL更新:
UPDATE model_registry
SET status = 'production', updated_at = NOW()
WHERE model_id = 'recommender-v2' AND version = '2.3';
注意:
artifact_url必须是可公开访问的URL(或内网可访问),且 绝不包含任何认证信息 。MinIO我们配置了基于Bucket Policy的细粒度权限,models/目录只读,models/staging/目录仅CI/CD服务可写。这样,模型服务(如Flask API)只需用requests.get(artifact_url)即可加载,无需硬编码密钥。
这个设计带来的最大好处是
审计友好
。当合规部门要求提供“某模型上线的所有凭证”时,我们只需导出
model_registry
表中该
model_id
的所有记录,附上对应的MLflow实验链接和DVC数据版本,一份完整的证据链就齐了。
4. 实操过程与核心环节实现:从零搭建一个端到端可追溯流水线
4.1 环境准备:五分钟搞定本地开发沙盒
所有操作均在Ubuntu 22.04 LTS上验证。我们不推荐用Windows开发,因DVC在Windows上的符号链接(symlink)支持不稳定。
# 1. 创建独立Python环境(避免污染全局)
python3 -m venv mlops-env
source mlops-env/bin/activate
# 2. 安装核心工具
pip install --upgrade pip
pip install mlflow dvc scikit-learn pandas numpy matplotlib
# 3. 安装MinIO客户端(用于对象存储操作)
curl https://dl.min.io/client/mc/release/linux-amd64/mc \
--create-dirs -o ~/bin/mc
chmod +x ~/bin/mc
# 4. 配置MinIO(本地测试用)
mc alias set myminio http://localhost:9000 minioadmin minioadmin
mc mb myminio/mlflow-artifacts
mc mb myminio/dvc-cache
mc mb myminio/models
提示:MinIO是S3协议兼容的对象存储,本地开发用
minio/minioDocker镜像即可。启动命令:docker run -p 9000:9000 -p 9001:9001 \ -e "MINIO_ROOT_USER=minioadmin" \ -e "MINIO_ROOT_PASSWORD=minioadmin" \ -v $(pwd)/minio-data:/data \ quay.io/minio/minio server /data --console-address ":9001"浏览器访问
http://localhost:9001,用minioadmin/minioadmin登录,创建对应Bucket。
4.2 构建第一个可追溯实验:电商用户流失预测
我们用经典的
telco-customer-churn
数据集(Kaggle)模拟电商场景。完整代码结构如下:
mlops-demo/
├── data/
│ └── raw/ # DVC管理的原始数据
├── notebooks/
│ └── explore.ipynb # EDA,不纳入DVC
├── src/
│ ├── features.py # 特征工程函数
│ └── train.py # 主训练脚本(MLflow集成)
├── models/ # 模型注册后的存放目录(由Registry管理)
├── requirements.txt
├── data.dvc # DVC元数据文件
└── mlflow-tracking.db # 本地MLflow SQLite DB(仅开发用)
src/train.py
的核心逻辑(精简):
import pandas as pd
import mlflow
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report
import joblib
# 加载DVC管理的数据(关键!)
def load_data():
# 读取DVC指针指向的实际数据
df = pd.read_csv("data/raw/telco-churn.csv")
return df
def main():
mlflow.set_experiment("telco-churn-prediction")
with mlflow.start_run(run_name="rf-20230915"):
# 1. 记录数据版本(从DVC文件提取)
with open("data.dvc", "r") as f:
# 简单解析DVC文件获取md5(生产环境用dvc.api)
import re
md5_match = re.search(r'md5: ([a-f0-9]+)', f.read())
data_version = md5_match.group(1) if md5_match else "unknown"
mlflow.log_param("data_md5", data_version)
# 2. 加载并预处理
df = load_data()
X, y = preprocess(df) # features.py中的函数
# 3. 划分数据集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 4. 训练
model = RandomForestClassifier(n_estimators=50, random_state=42)
model.fit(X_train, y_train)
# 5. 评估
y_pred = model.predict(X_test)
report = classification_report(y_test, y_pred, output_dict=True)
for k, v in report.items():
if isinstance(v, dict):
mlflow.log_metric(f"{k}_f1", v["f1-score"])
# 6. 保存模型(MLflow格式)
mlflow.sklearn.log_model(model, "model")
# 7. 保存为joblib(供生产服务直接加载)
joblib.dump(model, "model.joblib")
mlflow.log_artifact("model.joblib")
# 8. 记录特征列表(供下游服务校验输入)
feature_list = list(X.columns)
mlflow.log_param("feature_list", str(feature_list))
if __name__ == "__main__":
main()
执行训练:
# 确保DVC数据已拉取
dvc pull
# 启动MLflow Server(开发模式)
mlflow server --backend-store-uri sqlite:///mlflow-tracking.db \
--default-artifact-root ./mlruns \
--host 0.0.0.0 --port 5000 &
# 运行训练脚本
python src/train.py
训练完成后,访问
http://localhost:5000
,你将看到一个完整的实验记录:参数、指标、Artifacts(包括
model.joblib
和
data.dvc
),点击
model.joblib
可直接下载。这就是可追溯性的起点——所有东西都在一个地方,且相互关联。
4.3 模型注册与上线:从Staging到Production的审批流
现在,我们模拟将
telco-churn-prediction
实验中的模型注册为
churn-model-v1
。
-
手动注册 :在MLflow UI中,找到该实验,点击右上角“Register Model”,输入
model_id="churn-model",version="1.0"; -
后端服务处理 (伪代码):
# 从MLflow API获取实验详情 experiment = mlflow_client.get_experiment_by_name("telco-churn-prediction") runs = mlflow_client.search_runs(experiment.experiment_id, "tags.mlflow.runName = 'rf-20230915'") run = runs[0] # 下载模型文件 model_path = mlflow.artifacts.download_artifacts( run_id=run.info.run_id, artifact_path="model.joblib" ) # 上传至MinIO minio_client.fput_object( "models", "churn-model/1.0/model.joblib", model_path ) # 写入Registry cursor.execute(""" INSERT INTO model_registry (model_id, version, experiment_id, owner, artifact_url) VALUES (%s, %s, %s, %s, %s) """, ("churn-model", "1.0", run.info.run_id, "alice", "https://minio.example.com/models/churn-model/1.0/model.joblib")) -
审批与上线 :DBA执行SQL将
status改为production; -
生产服务加载 :一个极简的Flask API:
from flask import Flask, request, jsonify import joblib import requests app = Flask(__name__) @app.route("/predict", methods=["POST"]) def predict(): # 1. 从Registry获取最新production模型URL registry_url = "http://registry-api/model/churn-model/latest?status=production" resp = requests.get(registry_url) model_url = resp.json()["artifact_url"] # 2. 下载模型(生产环境应加缓存,此处简化) model_file = requests.get(model_url).content model = joblib.load(io.BytesIO(model_file)) # 3. 解析请求,预测 data = request.json pred = model.predict([data["features"]]) return jsonify({"prediction": int(pred[0])})
整个流程中, 没有任何一步是黑盒 。数据版本、代码提交、模型参数、上线审批,全部可查、可溯、可审计。这才是MLOps的实质。
5. 常见问题与排查技巧实录:那些只有踩过才懂的坑
5.1 DVC常见问题速查表
| 问题现象 | 根本原因 | 排查与解决 |
|---|---|---|
dvc pull
报错
ERROR: failed to download 'xxx' - The specified key does not exist.
|
S3上对应数据文件被手动删除,或DVC指针(
.dvc
文件)未提交到Git
|
执行
git status
查看
.dvc
文件是否已
git add
;用
dvc remote list
确认远程地址正确;检查S3 Bucket中
dvc-cache/
目录下是否存在对应md5前缀的文件(DVC缓存文件名是md5值)
|
dvc repro
时提示
Stage 'train' cmd failed
,但训练脚本单独运行正常
| DVC默认在隔离环境中执行命令,PATH、Python环境与当前shell不同 |
在
dvc.yaml
中显式指定
deps
和
outs
,并用
env
关键字注入环境变量,如
env: PYTHONPATH: "."
;或改用
dvc run -n train --no-exec
先生成stage,再手动调试
|
多人协作时,
dvc push
提示
ERROR: failed to push data - AccessDenied
|
MinIO/Bucket Policy未授予
PutObject
权限,或IAM角色无S3写权限
|
检查MinIO控制台中对应Bucket的Policy,确认
Statement[].Action
包含
s3:PutObject
;AWS环境则检查EC2实例角色的IAM Policy
|
5.2 MLflow追踪失效的三大陷阱
-
陷阱一:
mlflow.start_run()未关闭 。如果你在Jupyter Notebook中多次执行start_run()而未配对end_run(),会导致MLflow创建大量空Run,UI卡顿。 解决方案 :始终用with mlflow.start_run():上下文管理器,或在Notebook中执行mlflow.end_run()清理。 -
陷阱二:
log_artifact()路径错误 。mlflow.log_artifact("model.pkl")要求文件在当前工作目录下。若脚本在src/目录执行,而model.pkl在models/目录,则需传入相对路径"../models/model.pkl"。 实操技巧 :用os.path.join(os.path.dirname(__file__), "..", "models", "model.pkl")动态构造路径,绝对可靠。 -
陷阱三:Conda环境捕获不全 。
mlflow.sklearn.log_model()会自动保存conda.yaml,但它只捕获pip list和conda list的输出, 不捕获系统级依赖 (如libgomp.so.1)。当模型在无GPU的服务器上加载时报ImportError: libgomp.so.1: cannot open shared object file,就是此因。 终极方案 :放弃log_model(),改用log_artifact()上传model.joblib,并在requirements.txt中明确声明numpy==1.21.0等关键包版本,生产服务用pip install -r requirements.txt重建环境。
5.3 模型注册表的“幽灵版本”问题
现象:Registry中出现多个
status='production'
的同一
model_id
版本,或
status='staging'
的版本长期无人审批。
原因:缺乏强制的状态互斥约束。我们的解决方案是在数据库层面加唯一索引:
-- 确保每个model_id只有一个production版本
CREATE UNIQUE INDEX idx_unique_production
ON model_registry(model_id)
WHERE status = 'production';
同时,开发一个定时Job(每天凌晨执行),自动将超过7天未审批的
staging
版本标记为
archived
,并邮件通知Owner。这个“自动归档”机制,让我们团队的Registry常年保持干净,避免了“哪个才是最新生产版”的扯皮。
最后分享一个小技巧:在MLflow UI中,给每个Run添加
tags是提升可检索性的神器。我们约定:tags.team = "recommendation"、tags.priority = "high"、tags.dataset = "20230915"。这样,用搜索框输入dataset:"20230915",所有当天训练的实验瞬间聚合,比翻页找快十倍。这些看似琐碎的约定,正是MLOps从“能用”走向“好用”的分水岭。
我在实际使用中发现,MLOps最大的阻力从来不是技术,而是习惯。当算法工程师第一次认真填写
mlflow.log_param("data_version", ...)
,当数据工程师第一次为数据集打上
20230915-142207
这样的版本号,当SRE第一次在K8s部署清单里写上
MODEL_URL: https://minio.example.com/models/churn-model/1.0/model.joblib
——那一刻,那堵隔开算法与工程的墙,才算真正开始松动。这个过程不会一蹴而就,但每一步都算数。

807

被折叠的 条评论
为什么被折叠?



