MLflow在LLM评估中的工程实践:实现可追溯、可比较、可归因的模型管理
1. 为什么我坚持用 MLflow 做 LLM 评估——一个实战派的坦白局
你有没有过这种经历:上周跑通的微调实验,这周想复现时发现连自己都搞不清当时用的是哪个 tokenizer、哪版数据清洗脚本、甚至 batch size 是 8 还是 16?更别提团队协作时,同事问你“那个在 IMDB 上准确率 92.3% 的 BERT 模型,用的是哪个 learning rate 和 dropout?”——你翻了三遍 Jupyter Notebook,最后只能尴尬地回一句:“好像是……5e-5?我找找看。”
这就是 LLM 开发里最真实的“混沌现场”。模型参数动辄上亿,训练配置组合爆炸,评估指标又不止一个维度(accuracy、F1、perplexity、BLEU、ROUGE、甚至人工打分),光靠 Excel 表格和本地文件夹命名(bert_v2_final_really_final_20240415.ipynb)来管理,不是在赌运气,就是在给未来埋雷。
我做过 7 个不同场景的 LLM 项目:从电商评论情感分析、金融研报摘要生成,到医疗问诊对话补全、法律合同条款比对。踩过的坑里,80% 的返工不是因为模型没训好,而是因为实验过程不可追溯、结果不可比较、结论不可复现。直到我把 MLflow 作为“实验操作系统”嵌进整个工作流,才真正把 LLM 评估从玄学拉回工程。
这不是一个讲“MLflow 多厉害”的工具课,而是一个老手掏心窝子的实操笔记:它到底怎么解决你每天都在面对的具体问题?比如——
- 当你同时跑 5 个不同 prompt 工程策略 + 3 种 embedding 模型 + 2 种 reranker 配置时,如何一眼锁定综合得分最高的那条 pipeline?
- 当线上服务突然出现响应延迟升高、生成内容重复率上升,你怎么快速判断是模型 drift、数据分布偏移,还是 API 网关异常?
- 当产品经理拿着竞品报告问“咱们的摘要模型 ROUGE-L 比他们低 0.8,差在哪?”,你怎么在 10 分钟内给出可验证的归因(是训练数据噪声大?还是 beam search 参数不合适?)
关键词就三个:可追溯、可比较、可归因。MLflow 不是万能胶,但它是一把精准的手术刀——把 LLM 评估这个模糊、庞大、易失真的过程,切成一个个可测量、可存储、可回放的原子单元。接下来的内容,我会带你从零开始,用真实代码、真实报错、真实决策逻辑,重建一套经得起推敲的 LLM 评估体系。不讲虚的,只说你明天就能抄作业的操作。
2. 整体设计思路:为什么是 MLflow,而不是 TensorBoard、Weights & Biases 或自建数据库?
在动手装包之前,必须先回答一个灵魂拷问:为什么非得是 MLflow?毕竟市面上有 TensorBoard(轻量、可视化强)、Weights & Biases(云原生、协作友好)、甚至有人直接用 PostgreSQL + Flask 自建追踪系统。我的选择不是拍脑袋,而是基于过去三年在生产环境里反复验证的四个硬性约束:
2.1 约束一:必须原生支持“模型即一等公民”,而非仅记录指标
LLM 评估的核心对象是模型本身,不是某次训练的 loss 曲线。你需要能随时加载、推理、对比任意两个版本的模型(比如llama3-8b-finetuned-v3vsllama3-8b-finetuned-v5),并确保加载时用的 tokenizer、config、甚至量化方式都完全一致。
TensorBoard 擅长画图,但它的add_graph()只能存计算图结构,无法保存模型权重、tokenizer 词表、甚至model.generate()所需的eos_token_id。W&B 虽然能 log model files,但它的artifact本质是二进制 blob,没有内置的模型版本控制、stage 管理(staging/production)、或一键部署能力。
而 MLflow 的Model Registry是为这个需求量身定制的:
- 每个模型注册后自动分配唯一
model_name(如"sentiment-classifier")和递增version(如1,2,3); - 每个 version 可标记
stage(Staging/Production/Archived),点击即可切换线上服务所用的模型; - 它强制要求你定义
python_function或pytorch_model等 flavor,这意味着你必须显式声明“如何加载这个模型”——这恰恰堵死了“本地能跑,服务器报错”的经典漏洞。
提示:我见过太多团队把
.pt文件丢进 S3,然后写个load_model(path)函数。但当模型升级需要新增trust_remote_code=True参数,或 tokenizer 从AutoTokenizer改为LlamaTokenizerFast时,旧的加载函数就失效了。MLflow 的 flavor 机制逼你把所有依赖固化进代码,这是工程可靠性的第一道防线。
2.2 约束二:必须无缝兼容 Hugging Face 生态,拒绝“二次封装”
90% 的 LLM 实验始于from transformers import AutoModelForSeq2SeqLM。任何要求你“先用 X 框架包装模型,再喂给 Y 工具”的方案,都会在第一步就增加认知负担和出错概率。
MLflow 对 Hugging Face 的支持是深度的:
mlflow.transformers模块原生支持AutoModel、AutoTokenizer、Pipeline的 logging;- 它能自动 infer 模型类型(
text-generation、text-classification),并为你生成标准的predict()接口; - 更关键的是,它会自动打包 tokenizer 的
vocab.json、merges.txt、config.json等所有必要文件,而不是只存模型权重。这意味着你 log 的不是一个孤立的.bin,而是一个开箱即用的完整推理单元。
对比一下:如果用自建数据库,你得自己写逻辑去扫描model_dir下所有文件,判断哪些是必需的,再序列化上传。而 MLflow 一行mlflow.transformers.log_model(model, artifact_path="model", task="text-classification")就搞定全部。
2.3 约束三:必须支持离线与在线双模,不绑架你的基础设施
初创团队可能只有几台 GPU 服务器,大厂则有 Kubernetes 集群和私有云。MLflow 的设计哲学是“追踪即服务,部署即选择”:
- 本地开发:
mlflow.start_run()默认使用file:///mlruns,所有数据存在本地 SQLite 和文件系统,零配置; - 团队协作:启动
mlflow server --backend-store-uri postgresql://...,所有客户端通过MLFLOW_TRACKING_URI指向该地址,立刻共享实验空间; - 云原生:后端 store 支持 PostgreSQL、MySQL、Databricks Unity Catalog,artifact root 支持 S3、Azure Blob、GCS,无缝对接现有基建。
我曾在一个客户现场看到,他们用 W&B 时,所有实验数据强制上传到云端,导致内部合规审计卡壳;而用 MLflow,我们当天就切到私有 PostgreSQL + MinIO,全程无代码修改。
2.4 约束四:必须提供“可执行”的比较能力,而非静态图表
评估 LLM 最痛苦的不是画不出图,而是图看不懂。比如一张 accuracy 对比柱状图,你看到model_A: 89.2%,model_B: 90.1%,但你不知道:
- 这个 90.1% 是在 test set 的哪个子集上算的?(是全部样本,还是只含长文本的样本?)
- 它的 F1-score 是否同步提升?还是 precision 升高、recall 暴跌?
- 如果换用 ROUGE-2,排名会不会反转?
MLflow 的Compare Runs功能直击痛点:选中多个 run,它自动对齐所有 logged metrics(accuracy、f1、rouge1、rouge2、bleu、inference_time),生成可排序的表格,并支持按任意 metric 筛选 top-k。更重要的是,它允许你导出完整的 run 数据为 CSV,用 pandas 做深度交叉分析——这才是工程师该有的比较姿势。
实操心得:我从不用 MLflow UI 做最终决策。UI 是“初筛器”,我把 top-3 runs 的 metrics 导出,用 seaborn 画热力图(x=模型名,y=metric,color=value),再叠加人工标注的“业务敏感点”(比如“客服场景下 recall > 0.85 是硬门槛”)。这才是把工具用透。
3. 核心细节解析:从安装到模型注册,每一步背后的“为什么”
现在进入实操环节。我会拆解每一个命令、每一行代码背后的工程考量,而不是让你盲目复制粘贴。记住:理解“为什么”,才能在报错时快速定位,而不是百度搜“mlflow error 123”。
3.1 安装与环境隔离:为什么虚拟环境不是可选项,而是必选项?
# 错误示范:全局 pip install mlflow pip install mlflow # 正确做法:创建独立环境 python -m venv llm-eval-env source llm-eval-env/bin/activate # Linux/Mac # llm-eval-env\Scripts\activate # Windows pip install --upgrade pip pip install mlflow transformers datasets scikit-learn torch为什么必须用虚拟环境?
- MLflow 的
mlflow.pytorch、mlflow.transformers等模块对 PyTorch/TensorFlow 版本极其敏感。全局环境里可能有torch==2.0.1,而你的新项目需要torch==2.3.0,冲突直接导致mlflow.log_model()报ModuleNotFoundError; - 更隐蔽的坑是
transformers库。Hugging Face 经常发布 breaking change(比如 v4.35 移除了Trainer.predict()的return_dict_in_generate参数),如果你的旧项目依赖 v4.30,新项目用 v4.35,全局安装会让两者互相污染; - 我的血泪教训:曾在一个客户环境里,因为全局安装了
mlflow[extras],它自动拉取了gunicorn==21.2,而客户线上服务强制要求gunicorn==20.1,导致 CI/CD 流水线构建失败,排查了两天才发现是 MLflow 的副作用。
注意:
pip install mlflow[extras]是危险操作。[extras]会安装所有可选依赖(gunicorn,azure-storage-blob,boto3等),但你大概率只用其中 1-2 个。正确做法是按需安装:pip install mlflow boto3(如果要用 S3)或pip install mlflow azure-storage-blob(如果要用 Azure)。
3.2 本地追踪 vs 远程服务器:什么时候该启动mlflow server?
本地模式(默认):
import mlflow mlflow.set_experiment("llm-sentiment-eval") with mlflow.start_run(): mlflow.log_param("model", "bert-base-uncased") mlflow.log_metric("accuracy", 0.892)此时 MLflow 自动创建./mlruns目录,SQLite 数据库存于./mlruns/mlflow.db,模型文件存于./mlruns/0/<run_id>/artifacts/。适合单人开发、快速验证。
远程服务器模式(团队协作必备):
# 启动服务器,指定后端存储和 artifact 根目录 mlflow server \ --backend-store-uri postgresql://user:pass@localhost:5432/mlflow_db \ --default-artifact-root s3://my-bucket/mlflow-artifacts/ \ --host 0.0.0.0 \ --port 5000然后在代码中设置环境变量:
import os os.environ["MLFLOW_TRACKING_URI"] = "http://localhost:5000" # 后续所有 mlflow.* 调用都指向该服务器为什么必须用 PostgreSQL 而非 SQLite?
- SQLite 是单文件数据库,不支持并发写入。当 3 个工程师同时运行
mlflow.start_run(),会出现database is locked错误; - PostgreSQL 支持 ACID 事务,保证多用户写入安全;
- 它提供用户权限管理(
CREATE USER ... WITH PASSWORD),避免实习生误删productionstage 的模型。
提示:Artifact root 用 S3/Azure/GCS 而非本地路径,是为了让模型文件天然具备高可用性。当服务器宕机,只要对象存储还在,模型就永远不会丢失。这是生产环境的底线。
3.3 模型加载与预处理:为什么 tokenizer 必须和 model 一起 log?
很多教程教你这样加载模型:
from transformers import AutoModel, AutoTokenizer model = AutoModel.from_pretrained("bert-base-uncased") tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")然后只mlflow.pytorch.log_model(model, "model")。这是重大错误。
原因在于:AutoTokenizer的行为高度依赖其tokenizer_config.json中的do_lower_case、strip_accents等字段。如果只存 model,下次加载时用AutoTokenizer.from_pretrained("bert-base-uncased"),它会重新下载最新版 tokenizer(可能已更新),导致 tokenization 结果不一致。
正确做法(MLflow 原生支持):
from transformers import AutoModelForSequenceClassification, AutoTokenizer import mlflow.transformers # 1. 加载 model 和 tokenizer model = AutoModelForSequenceClassification.from_pretrained("textattack/bert-base-uncased-yelp-polarity") tokenizer = AutoTokenizer.from_pretrained("textattack/bert-base-uncased-yelp-polarity") # 2. 创建 pipeline(关键!) pipeline = transformers.pipeline( "text-classification", model=model, tokenizer=tokenizer, device=0 if torch.cuda.is_available() else -1 ) # 3. 用 transformers flavor log 整个 pipeline mlflow.transformers.log_model( transformers_model=pipeline, artifact_path="model", task="text-classification", input_example=["This movie is great!"], # 提供示例输入,用于 schema inference signature=mlflow.models.infer_signature( # 自动生成 input/output schema ["This movie is great!"], pipeline(["This movie is great!"]) ) )这段代码做了三件事:
- 把 model、tokenizer、pipeline 配置全部打包进
artifact_path; input_example让 MLflow 知道“这个模型接受什么格式的输入”,后续部署时能自动生成 REST API 文档;signature显式声明输入是str列表,输出是dict(含label,score),杜绝了“API 返回格式和文档不一致”的线上事故。
3.4 评估指标选择:为什么不能只看 accuracy?
在 sentiment analysis 任务中,accuracy是最常被滥用的指标。假设你的测试集有 95% 正面评论、5% 负面评论,一个永远预测“正面”的模型,accuracy 也能达到 95%。但这显然毫无价值。
LLM 评估必须建立多维指标矩阵:
| 指标类型 | 具体指标 | 适用场景 | 计算方式(sklearn) |
|---|---|---|---|
| 分类性能 | Accuracy | 数据均衡时参考 | accuracy_score(y_true, y_pred) |
| Precision/Recall/F1 | 关注特定类别(如负面评论) | classification_report(y_true, y_pred, output_dict=True) | |
| Confusion Matrix | 诊断模型错误模式 | confusion_matrix(y_true, y_pred) | |
| 生成质量 | BLEU-4 | 机器翻译、摘要 | sacrebleu.corpus_bleu(hypotheses, [references]) |
| ROUGE-L | 长文本摘要 | rouge_score.RougeScore('rougeL') | |
| Perplexity | 语言建模能力 | model(input_ids).loss.exp().item() | |
| 工程指标 | Inference Latency | 线上服务 SLA | time.time()包裹model.generate() |
| Memory Usage | GPU 显存占用 | torch.cuda.memory_allocated() |
实操要点:
mlflow.log_metric()只接受标量(float/int),所以classification_report返回的 dict 需要拆解:from sklearn.metrics import classification_report report = classification_report(y_true, y_pred, output_dict=True) for label in ["0", "1", "macro avg"]: # 0=negative, 1=positive mlflow.log_metric(f"f1_{label}", report[label]["f1-score"]) mlflow.log_metric(f"precision_{label}", report[label]["precision"])- Perplexity 必须在
eval_loss计算后立即取exp(),因为 loss 是负对数似然,perplexity = exp(loss); - Latency 要测多次取平均(至少 10 次),避免单次抖动影响判断。
4. 实操过程:从零开始跑通一个完整的 LLM 评估流水线
现在,我们把前面所有知识点串起来,完成一个端到端的实战:评估两个微调后的 BERT 模型在 IMDB 数据集上的表现,并用 MLflow UI 进行可视化对比。全程使用真实代码,包含所有报错处理和调试技巧。
4.1 数据准备与预处理:为什么datasets.map()必须用batched=True?
from datasets import load_dataset import mlflow # 1. 加载数据(注意:IMDB 有 train/test,但无 validation,需手动划分) dataset = load_dataset("imdb") # 划分:train 20k -> 15k train + 5k validation train_test = dataset["train"].train_test_split(test_size=0.25, seed=42) dataset = { "train": train_test["train"], "validation": train_test["test"], "test": dataset["test"] } # 2. Tokenization(核心:必须 batched=True!) from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") def tokenize_function(examples): return tokenizer( examples["text"], truncation=True, padding=True, max_length=512 ) # 关键!不加 batched=True 会慢 10 倍以上 tokenized_datasets = dataset.map( tokenize_function, batched=True, # ✅ 强制启用批处理 remove_columns=["text", "label"] # 删除原始列,只保留 tokenized 字段 ) # 3. Log dataset info to MLflow(便于追溯) with mlflow.start_run(run_name="data-prep"): mlflow.log_param("dataset_name", "imdb") mlflow.log_param("train_size", len(dataset["train"])) mlflow.log_param("val_size", len(dataset["validation"])) mlflow.log_param("test_size", len(dataset["test"])) mlflow.log_param("max_length", 512) mlflow.log_artifact("tokenized_datasets", artifact_path="data") # 存储 tokenized 数据(可选)为什么batched=True如此重要?
datasets.map()默认逐条处理(batched=False),每次调用tokenize_function只传入 1 条样本。而 tokenizer 的padding=True在单样本时会 pad 到max_length,造成巨大浪费;batched=True时,它一次传入 1000 条样本,tokenizer 内部会动态计算这批样本的最大长度(比如 327),然后统一 pad 到 327,内存和速度提升 5-10 倍;- 我实测过:对 25k 条 IMDB 文本,
batched=False耗时 12 分钟,batched=True仅需 1.3 分钟。
4.2 模型训练与评估:如何用mlflow.evaluate()自动完成全流程?
MLflow 2.4+ 引入了mlflow.evaluate(),这是 LLM 评估的“核武器”。它能自动:
- 加载模型;
- 在指定数据集上运行预测;
- 计算预设指标(分类/回归/生成);
- 生成可视化报告(混淆矩阵、ROC 曲线);
- 保存所有 artifacts。
完整代码:
import mlflow from transformers import ( AutoModelForSequenceClassification, TrainingArguments, Trainer, DataCollatorWithPadding ) import torch # 1. 设置 MLflow 实验 mlflow.set_experiment("llm-bert-eval") # 2. 定义两个待评估的模型 model_configs = [ { "name": "bert-base-uncased", "path": "textattack/bert-base-uncased-yelp-polarity" }, { "name": "distilbert-base-uncased", "path": "distilbert-base-uncased-finetuned-sst-2-english" } ] # 3. 为每个模型创建独立 run 并评估 for config in model_configs: with mlflow.start_run(run_name=f"eval-{config['name']}"): # Log model metadata mlflow.log_param("model_name", config["name"]) mlflow.log_param("model_source", config["path"]) # 加载模型和 tokenizer model = AutoModelForSequenceClassification.from_pretrained(config["path"]) tokenizer = AutoTokenizer.from_pretrained(config["path"]) # Log model to registry(关键步骤) mlflow.transformers.log_model( transformers_model=model, artifact_path="model", task="text-classification", input_example=["This is a test sentence."], signature=mlflow.models.infer_signature( ["This is a test sentence."], [{"label": "POSITIVE", "score": 0.99}] ) ) # 使用 mlflow.evaluate(自动完成评估) # 注意:data 必须是 datasets.Dataset 格式,且包含 'text' 和 'label' 列 eval_results = mlflow.evaluate( model=f"runs:/{mlflow.active_run().info.run_id}/model", # 指向刚 log 的模型 data=dataset["test"], # 测试集 model_type="classifier", # 指定任务类型 targets="label", # 标签列名 evaluators=["default"], # 使用内置评估器 evaluator_config={ "log_samples": True, # 记录预测样本 "sample_dataset": dataset["test"].select(range(100)) # 只记录前100个样本 } ) # mlflow.evaluate() 会自动 log 所有指标,无需手动调用 log_metric() # 但你可以额外 log 自定义指标 mlflow.log_metric("inference_speed_tokens_per_sec", 1250.5)mlflow.evaluate()的隐藏能力:
- 它会自动检测
model_type并调用对应评估逻辑:classifier→accuracy,f1,precision,recall;regressor→mse,mae;text→bleu,rouge; log_samples=True会将预测结果(输入文本、真实标签、预测标签、置信度)存为eval_samples.json,方便人工抽检;- 它生成的
eval_results对象包含所有指标的详细 breakdown,可编程访问:eval_results.metrics["f1_score"]["value"]。
4.3 多模型对比:如何在 MLflow UI 中高效筛选最优模型?
启动服务器后,访问http://localhost:5000,你会看到:
- 左侧导航栏:
Experiments→llm-bert-eval; - 主界面:所有 runs 列表,默认按
start_time排序。
高效对比三步法:
- 筛选 runs:在右上角搜索框输入
eval-,勾选所有eval-bert-base-uncased和eval-distilbert-base-uncased; - 对齐 metrics:点击
Compare按钮,进入对比视图。左侧Metrics面板会列出所有 logged metrics(f1_score,precision,recall,accuracy,inference_speed_tokens_per_sec); - 深度分析:
- 点击
f1_score,表格按该指标降序排列,立刻看到哪个模型 F1 更高; - 点击
Confusion Matrix图标,查看每个模型的混淆矩阵热力图; - 点击
Evaluation Samples,下载eval_samples.json,用 VS Code 打开,搜索"label": "NEGATIVE",检查模型是否把讽刺句("This movie is so good it's bad!")误判为正面。
- 点击
实操心得:我从不在 UI 里做最终决策。我会导出
Compare Runs的 CSV,用 pandas 加载:import pandas as pd df = pd.read_csv("mlflow-compare.csv") # 计算综合得分:F1 * 0.6 + speed * 0.4(按业务权重) df["score"] = df["f1_score"] * 0.6 + df["inference_speed_tokens_per_sec"] * 0.4 print(df.sort_values("score", ascending=False))这样得到的排名,才是可解释、可复现、可辩论的。
4.4 模型注册与部署:如何把评估结果转化为线上服务?
评估结束,选出distilbert-base-uncased为优胜者。下一步是注册并部署:
# 1. 获取最优 run 的 model_uri best_run_id = "a1b2c3d4..." # 从 UI 复制 model_uri = f"runs:/{best_run_id}/model" # 2. 注册到 Model Registry from mlflow.tracking import MlflowClient client = MlflowClient() result = client.create_registered_model("sentiment-classifier") print(f"Created model: {result.name}") # 3. 将该 run 的模型版本化 client.create_model_version( name="sentiment-classifier", source=model_uri, run_id=best_run_id ) # 4. 将 version 1 标记为 Staging client.transition_model_version_stage( name="sentiment-classifier", version=1, stage="Staging" )部署到 REST API(本地测试):
# 启动本地服务 mlflow models serve \ -m "models:/sentiment-classifier/Staging" \ -p 5001 \ --no-conda # 发送测试请求 curl -X POST "http://127.0.0.1:5001/invocations" \ -H "Content-Type: application/json" \ -d '{"inputs": ["This movie is terrible!"]}' # 返回:{"predictions": [{"label": "NEGATIVE", "score": 0.998}]}关键细节:
models:/sentiment-classifier/Staging是模型 URI,它总是指向当前Stagingstage 的最新 version,无需硬编码 version 号;--no-conda表示不创建 conda 环境,直接用当前 Python 环境,避免依赖冲突;- 服务启动后,它会自动加载 tokenizer、model,并暴露标准
/invocations端点,兼容任何 HTTP 客户端。
5. 常见问题与排查技巧实录:那些官方文档不会告诉你的坑
以下是我在 7 个项目中踩过的、最典型、最高频的 5 类问题,附带根因分析和秒级解决方案。
5.1 问题一:mlflow.evaluate()报错ValueError: Input contains NaN, infinity or a value too large for dtype('float64')
现象:
eval_results = mlflow.evaluate(...) # 报错 # ValueError: Input contains NaN, infinity...根因:
- 测试集
dataset["test"]中存在空字符串""或纯空白符(\n\t),tokenizer 处理后生成全 0 的input_ids,模型 forward 时产生 NaN; - 或标签列
label中有非整数(如字符串"0"),sklearn 指标计算时崩溃。
解决方案(两步走):
- 数据清洗(必须在 evaluate 前做):
def clean_dataset(example): # 过滤空文本 if not example["text"] or not example["text"].strip(): return {"text": "dummy", "label": 0} # 临时填充,后续 filter return example dataset["test"] = dataset["test"].map(clean_dataset) dataset["test"] = dataset["test"].filter(lambda x: x["text"] != "dummy") - 强制转换标签类型:
dataset["test"] = dataset["test"].cast_column("label", datasets.ClassLabel(names=["NEGATIVE", "POSITIVE"])) # 或直接转 int dataset["test"] = dataset["test"].map(lambda x: {"label": int(x["label"])})
5.2 问题二:MLflow UI 显示 metrics,但mlflow.search_runs()查不到
现象:
UI 里能看到accuracy: 0.892,但代码里:
runs = mlflow.search_runs(filter_string="metrics.accuracy > 0.89") print(len(runs)) # 输出 0根因:
mlflow.search_runs()默认只查activeruns(未结束的),而mlflow.evaluate()创建的 run 是FINISHED状态;- 或
filter_string语法错误(如>前后必须有空格)。
解决方案:
# 显式指定状态 runs = mlflow.search_runs( filter_string="metrics.accuracy > 0.89", run_view_type=mlflow.entities.ViewType.ALL # 查所有状态 ) # 或用更健壮的写法(推荐) runs = mlflow.search_runs( experiment_names=["llm-bert-eval"], filter_string="attributes.status = 'FINISHED'" ) # 然后用 pandas 筛选 import pandas as pd df = pd.DataFrame(runs) df = df[df["metrics.accuracy"] > 0.89]5.3 问题三:mlflow.transformers.log_model()报错OSError: Can't load tokenizer for ...
现象:
mlflow.transformers.log_model(pipeline, "model") # 报错 # OSError: Can't load tokenizer for 'bert-base-uncased'根因:
- 本地没有缓存该 tokenizer,而
log_model()在打包时尝试重新下载,但网络受限或 Hugging Face Hub 限速; - 或 tokenizer 名称拼写错误(如
bert-base-uncaesd)。
解决方案:
- 提前下载并指定本地路径:
from transformers import AutoTokenizer # 先手动下载到本地 tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased", cache_dir="./hf_cache") # 然后 log 时指定 local path mlflow.transformers.log_model( transformers_model=pipeline, artifact_path="model", # 强制使用本地缓存 code_paths=["./hf_cache"] ) - 或使用
trust_remote_code=True(针对 custom tokenizer):tokenizer = AutoTokenizer.from_pretrained("my-custom-tokenizer", trust_remote_code=True)
5.4 问题四:模型部署后curl返回500 Internal Server Error
现象:
curl -X POST "http://127.0.0.1:5001/invocations" -d '{"inputs": ["hi"]}' # 返回 500根因:
- 输入 JSON 格式错误:
mlflow.models.serve严格要求inputs是 list,不能是单个 string; - 或模型加载失败(如 GPU 内存不足,
CUDA out of memory)。
解决方案:
- 检查输入格式(必须是 list):
# 正确 curl -X POST "http://127.0.0.1:5001/invocations" \ -H "Content-Type: application/json" \ -d '{"inputs": ["This is a test."]}' # 错误(少了一层 []) -d '{"inputs": "This is a test."}' - 查看服务日志定位 GPU 问题:
# 启动时加 --verbose mlflow models serve -m "models:/sentiment-classifier/Staging" -p 5001 --verbose # 日志中会显示 "CUDA out of memory" 或 "OOM when allocating tensor" # 解决:加 --no-conda 并设置环境变量 CUDA_VISIBLE_DEVICES=0 mlflow models serve ...
5.5 问题五:mlflow.evaluate()生成的Confusion Matrix图片是空白
现象:
UI 里Confusion Matrix图标显示,但点击后图片为空白。
根因:
- MLflow 2.4+ 的
evaluate()默认
