MLflow在LLM评估中的工程实践:实现可追溯、可比较、可归因的模型管理

发布时间:2026/7/6 3:54:27
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。WB 虽然能 log model files但它的artifact本质是二进制 blob没有内置的模型版本控制、stage 管理staging/production、或一键部署能力。而 MLflow 的Model Registry是为这个需求量身定制的每个模型注册后自动分配唯一model_name如sentiment-classifier和递增version如1,2,3每个 version 可标记stageStaging/Production/Archived点击即可切换线上服务所用的模型它强制要求你定义python_function或pytorch_model等 flavor这意味着你必须显式声明“如何加载这个模型”——这恰恰堵死了“本地能跑服务器报错”的经典漏洞。提示我见过太多团队把.pt文件丢进 S3然后写个load_model(path)函数。但当模型升级需要新增trust_remote_codeTrue参数或 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_pathmodel, tasktext-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 Catalogartifact root 支持 S3、Azure Blob、GCS无缝对接现有基建。我曾在一个客户现场看到他们用 WB 时所有实验数据强制上传到云端导致内部合规审计卡壳而用 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 metricsaccuracy、f1、rouge1、rouge2、bleu、inference_time生成可排序的表格并支持按任意 metric 筛选 top-k。更重要的是它允许你导出完整的 run 数据为 CSV用 pandas 做深度交叉分析——这才是工程师该有的比较姿势。实操心得我从不用 MLflow UI 做最终决策。UI 是“初筛器”我把 top-3 runs 的 metrics 导出用 seaborn 画热力图x模型名ymetriccolorvalue再叠加人工标注的“业务敏感点”比如“客服场景下 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 版本极其敏感。全局环境里可能有torch2.0.1而你的新项目需要torch2.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]它自动拉取了gunicorn21.2而客户线上服务强制要求gunicorn20.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:passlocalhost: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 而非 SQLiteSQLite 是单文件数据库不支持并发写入。当 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, modelmodel, tokenizertokenizer, device0 if torch.cuda.is_available() else -1 ) # 3. 用 transformers flavor log 整个 pipeline mlflow.transformers.log_model( transformers_modelpipeline, artifact_pathmodel, tasktext-classification, input_example[This movie is great!], # 提供示例输入用于 schema inference signaturemlflow.models.infer_signature( # 自动生成 input/output schema [This movie is great!], pipeline([This movie is great!]) ) )这段代码做了三件事把 model、tokenizer、pipeline 配置全部打包进artifact_pathinput_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_dictTrue)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线上服务 SLAtime.time()包裹model.generate()Memory UsageGPU 显存占用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_dictTrue) for label in [0, 1, macro avg]: # 0negative, 1positive mlflow.log_metric(ff1_{label}, report[label][f1-score]) mlflow.log_metric(fprecision_{label}, report[label][precision])Perplexity 必须在eval_loss计算后立即取exp()因为 loss 是负对数似然perplexity exp(loss)Latency 要测多次取平均至少 10 次避免单次抖动影响判断。4. 实操过程从零开始跑通一个完整的 LLM 评估流水线现在我们把前面所有知识点串起来完成一个端到端的实战评估两个微调后的 BERT 模型在 IMDB 数据集上的表现并用 MLflow UI 进行可视化对比。全程使用真实代码包含所有报错处理和调试技巧。4.1 数据准备与预处理为什么datasets.map()必须用batchedTruefrom 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_size0.25, seed42) dataset { train: train_test[train], validation: train_test[test], test: dataset[test] } # 2. Tokenization核心必须 batchedTrue from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(bert-base-uncased) def tokenize_function(examples): return tokenizer( examples[text], truncationTrue, paddingTrue, max_length512 ) # 关键不加 batchedTrue 会慢 10 倍以上 tokenized_datasets dataset.map( tokenize_function, batchedTrue, # ✅ 强制启用批处理 remove_columns[text, label] # 删除原始列只保留 tokenized 字段 ) # 3. Log dataset info to MLflow便于追溯 with mlflow.start_run(run_namedata-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_pathdata) # 存储 tokenized 数据可选为什么batchedTrue如此重要datasets.map()默认逐条处理batchedFalse每次调用tokenize_function只传入 1 条样本。而 tokenizer 的paddingTrue在单样本时会 pad 到max_length造成巨大浪费batchedTrue时它一次传入 1000 条样本tokenizer 内部会动态计算这批样本的最大长度比如 327然后统一 pad 到 327内存和速度提升 5-10 倍我实测过对 25k 条 IMDB 文本batchedFalse耗时 12 分钟batchedTrue仅需 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_namefeval-{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_modelmodel, artifact_pathmodel, tasktext-classification, input_example[This is a test sentence.], signaturemlflow.models.infer_signature( [This is a test sentence.], [{label: POSITIVE, score: 0.99}] ) ) # 使用 mlflow.evaluate自动完成评估 # 注意data 必须是 datasets.Dataset 格式且包含 text 和 label 列 eval_results mlflow.evaluate( modelfruns:/{mlflow.active_run().info.run_id}/model, # 指向刚 log 的模型 datadataset[test], # 测试集 model_typeclassifier, # 指定任务类型 targetslabel, # 标签列名 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,recallregressor→mse,maetext→bleu,rougelog_samplesTrue会将预测结果输入文本、真实标签、预测标签、置信度存为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 metricsf1_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 its 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, ascendingFalse))这样得到的排名才是可解释、可复现、可辩论的。4.4 模型注册与部署如何把评估结果转化为线上服务评估结束选出distilbert-base-uncased为优胜者。下一步是注册并部署# 1. 获取最优 run 的 model_uri best_run_id a1b2c3d4... # 从 UI 复制 model_uri fruns:/{best_run_id}/model # 2. 注册到 Model Registry from mlflow.tracking import MlflowClient client MlflowClient() result client.create_registered_model(sentiment-classifier) print(fCreated model: {result.name}) # 3. 将该 run 的模型版本化 client.create_model_version( namesentiment-classifier, sourcemodel_uri, run_idbest_run_id ) # 4. 将 version 1 标记为 Staging client.transition_model_version_stage( namesentiment-classifier, version1, stageStaging )部署到 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\ttokenizer 处理后生成全 0 的input_ids模型 forward 时产生 NaN或标签列label中有非整数如字符串0sklearn 指标计算时崩溃。解决方案两步走数据清洗必须在 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_stringmetrics.accuracy 0.89) print(len(runs)) # 输出 0根因mlflow.search_runs()默认只查activeruns未结束的而mlflow.evaluate()创建的 run 是FINISHED状态或filter_string语法错误如前后必须有空格。解决方案# 显式指定状态 runs mlflow.search_runs( filter_stringmetrics.accuracy 0.89, run_view_typemlflow.entities.ViewType.ALL # 查所有状态 ) # 或用更健壮的写法推荐 runs mlflow.search_runs( experiment_names[llm-bert-eval], filter_stringattributes.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: Cant load tokenizer for ...现象mlflow.transformers.log_model(pipeline, model) # 报错 # OSError: Cant 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_modelpipeline, artifact_pathmodel, # 强制使用本地缓存 code_paths[./hf_cache] )或使用trust_remote_codeTrue针对 custom tokenizertokenizer AutoTokenizer.from_pretrained(my-custom-tokenizer, trust_remote_codeTrue)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_DEVICES0 mlflow models serve ...5.5 问题五mlflow.evaluate()生成的Confusion Matrix图片是空白现象UI 里Confusion Matrix图标显示但点击后图片为空白。根因MLflow 2.4 的evaluate()默认