YAOTU INSIGHTS

用ClearML搭建可复现的MLOps实验管理闭环:训练跟踪与数据集版本化实践

用ClearML搭建可复现的MLOps实验管理闭环:训练跟踪与数据集版本化实践
半年前我们团队做了一次大模型微调项目的复盘结果非常尴尬。三个月前的“最佳模型”翻出来之后没人能说清楚当时到底用的哪个数据集版本、学习率多少、在哪个迭代轮次挑的权重。我和另一位同事翻遍聊天记录、Shell 历史、还有几个版本的训练脚本最后拼凑出一个大概能复现的配置。那一刻我就决定不能再靠人肉记忆和 Excel 表格管理实验了。当时我对比了一圈实验管理方案最后在项目里选定了 ClearML并且带着小团队从零开始搭起了一套“训练跟踪 远程执行 数据集版本管理”的工作流。这篇文章把整个落地过程里最有用的部分整理出来服务端怎么部署、SDK 怎么接入、Agent 远程执行怎么用、数据集和流水线又该怎么接进日常工作。适合正在考虑自建实验管理平台、或者跟我一样被“不可复现实验”折磨过的算法工程师和数据科学团队参考。1. 为什么是 ClearML团队实验管理的痛点和工具定位1.1 我们曾经是怎么“管理”实验的在没有引入任何实验管理工具之前团队的工作流大概是这样的训练脚本在各自的开发机上改config靠 argparse 和各种 YAML 堆。每个实验跑完把模型路径、关键指标往共享表格里填。部分同事会用 TensorBoard 截图发群但截图本身不带上下文。“帮我看一下这个模型”靠口头传达等一两天后想追踪细节基本查无可查。这种方式不是没用而是信息密度太低。一个指标背后对应的代码版本、数据版本、超参组合、随机种子全部散落在不同的地方。一旦人员变动或者项目交叉所谓“最佳模型”就变成了一个没有依据的结论。所以我把选型目标列成三条实验记录必须自动关联代码、环境依赖和超参不能靠手动填表。得有自托管能力训练数据的隐私要求下不能什么都往外部 SaaS 送。最好还能顺手解决“把任务发给训练机跑”的问题而不只是一个看板的孤岛。1.2 ClearML 到底解决什么问题边界又在哪里ClearML 最常被拿来和 MLflow、Weights Biases 对比较。它以前叫 Trains是开源 MLOps 平台走的是整套闭环路线实验跟踪训练指标、日志、图表、模型文件、配置、Git 仓库状态。Agent 远程执行把任务从开发机调度到任意一台注册了 Worker 的机器。数据集管理给数据集打版本训练前可以精确锁定某一版数据。流水线编排把数据准备、训练、评估等环节串起来。模型部署把训练好的模型发布成推理服务。把它当成“TensorBoard 替代品”是低估了它的定位其实是轻量级的团队 MLOps 底座。边界在哪如果你需要复杂的模型部署策略、多集群调度、特征存储这些重型能力那 ClearML 不是最佳选择它会负责“让实验能被复现”这一层而不是包办整条生产链路。1.3 和同类工具的取舍对比我把自己实际在几个项目里试用过的感受整理了一下仅供参考维度ClearMLMLflowWeights Biases部署方式可自托管Docker Compose 即可跑起自托管为主轻量托管服务为主跟踪功能自动记录依赖和 Git指标图表丰富Tracking 组件很简洁界面交互强图表体验好远程执行内置 Agent 队列机制开箱即用依赖外部调度器配合比较折腾云端后台执行但需要托管数据集版本有独立 Dataset API支持上传下载偏目录路径记录没有完整数据版本概念有 Artifact 但定位不同Pipeline内置但偏自动化实验流程需配合 Airflow / 自研有流水线相关模块上手成本中等配置文件稍多一点较低最低几乎注册即用对“想要保留数据在自己手里、同时把实验调度一起解决”的团队来说ClearML 是收益很完整的方案。接下去我就从部署开始把使用细节拆开讲。2. 服务端部署与本地环境接入让一切从一个配置文件开始2.1 三种部署方式怎么选ClearML 的服务端由三个组件组成API Server、Web Server 和 File Server。API Server 负责业务逻辑Web Server 负责可视化界面入口File Server 负责存产物比如模型文件和图片日志。实际部署时有三个层次的选择直接用官方 Demo 服务器适合第一次体验注册账号就能用但数据在别人的机器上我不建议任何正经项目这么干。单机 Docker Compose团队内网一台 8 核 16G 的机器就够了适合十人左右的小团队这也是我这次落地选择的方案。Kubernetes 部署适合公司级统一平台多团队共享一套服务需要维护存储和网络的成本更高。单机部署最省心的一点是官方把 Docker Compose 编排文件直接公开维护机器上装好 Docker 和 Docker Compose拉取编排文件后就能把三个服务拉起来。真正要注意的是服务器要有稳定的磁盘和网络因为 File Server 会承担所有模型和实验产物的读写。2.2 clearml-init 和 API 凭证的生成过程服务端起来之后开发机上装 SDKpip install clearml然后执行clearml-init这一步会弹出一个交互式向导要填三样东西Web Server 地址、API Server 地址、File Server 地址。比如我们团队内网机器地址是10.10.10.20那么填进去大概是Web Server: http://10.10.10.20:8080 API Server: http://10.10.10.20:8081 File Server: http://10.10.10.20:8080填完向导后会生成一对 API 访问密钥也就是 Access Key 和 Secret Key然后自动写入当前用户目录下的~/clearml.conf。这一步非常关键后面所有机器学习训练代码里通过Task.init发起的实验都会读这个配置文件去连服务端。有一点容易漏掉如果服务器地址以后变更了或者开发机重装不用重新跑向导直接编辑~/clearml.conf改api.web_server、api.api_server、api.files_server三项地址即可。2.3 配置文件中真正需要关注的参数配置文件里项目非常多但大多数用默认值就行。我实际改动过的就几项api.web_server、api.api_server、api.files_server连接地址这是最先要确认的。sdk.aws.s3如果模型和数据集统一存放在 S3 兼容对象存储里在这里配密钥。内网团队也可以让 File Server 作为默认存储不一定上对象存储。sdk.storage.default_base_path指定 File Server 的存储路径方便后续归档。agent.default_docker_image如果 Agent 要跑在容器里这里定义默认镜像。虽然配置文件很长但别被它吓住。ClearML 的配置项多是为了覆盖复杂场景对于一个从零开始的团队只改上面这几处其他保持默认就能顺畅跑起来。2.4 连接失败的高频原因排查我在部署和接入阶段遇到过不少团队伙伴问类似的问题汇总成一张排查表现象可能原因处理方式Web 界面能开但 SDK 连不上服务端API Server 和 Web Server 地址端口不匹配确认 8081 端口对应 API Server并在配置里区分正确本地能连训练机连不上跨网段防火墙拦住了 8080/8081在训练机上用curl验证端口连通性并检查防火墙策略上报指标很慢File Server 网络带宽瓶颈优先检查模型文件、图片日志的传输量必要时在训练机上配置 HTTP 代理界面看得到任务但一直显示“未运行”Agent 没有注册队列或者队列名不一致确认《Agent 远程执行》章节里的 Worker 是否在线部署阶段稳住了接下来就可以开始真正写代码接入。3. 核心 API 的实际接法从自动扫描到手动日志3.1 Task.init一行代码开启自动记录ClearML 里的基本概念是 Task对应一次实验运行。只要把训练代码的入口处加上from clearml import Task task Task.init( project_name模拟项目X, task_name实验-学习率扫描, tags[微调, v2], reuse_last_task_idFalse )它就会自动完成下面这些事情把当前环境里的 Git 仓库信息记录进任务包括 commit 哈希和未提交的修改。扫描执行程序里 argparse 解析出来的所有参数。记录 Python 环境依赖依赖包的名称和版本。如果你在脚本里用了 TensorBoard 或者 MatplotlibClearML 会尝试接管这些可视化输出自动转发到服务端界面。注意reuse_last_task_idFalse这一行。默认情况下如果同名任务已经存在ClearML 可能会复用上次的任务把新的指标追加到旧任务下面。这个行为在迭代调试时很方便但如果你希望每次都是全新的独立记录务必要显式设成 False。一个容易被忽略的前提自动记录 Git 信息要求代码处于 Git 仓库里。如果脚本在普通目录下运行ClearML 也能创建任务但版本信息会缺失。所以我建议团队里强制要求实验代码进版本库哪怕是很小的测试脚本也放进仓库再跑任务。3.2 Logger 报告指标、图表和调试图片自动记录能做很多事但训练循环里的 loss、accuracy 这类动态指标依然推荐手动上报这样更新频率和语义都更可控。先获取 loggerlogger task.get_logger() for epoch in range(epochs): train_loss train_one_epoch(...) val_acc evaluate(...) logger.report_scalar( titleloss, seriestrain, valuetrain_loss, iterationepoch ) logger.report_scalar( titleacc, seriesval, valueval_acc, iterationepoch )还可以在验证阶段画图并上传fig, axes plt.subplots(1, 2, figsize(10, 4)) # 绘制混淆矩阵、预测样本 logger.report_matplotlib_figure( title验证可视化, series混淆矩阵, iterationepoch, figurefig )ClearML 给每种指标都定了title和series两级维度这个设计和画图习惯很像。建议把title固定成“loss”“acc”这种语义把series用来区分 train、val、test之后在 Web 端同一张图里对比多组实验会非常直观。如果是图像类项目还可以直接上报调试图片logger.report_image( title样本预览, seriesaugmented, iterationepoch, imageaugmented_img )这类图片日志会被服务端归档在界面左侧的图像标签页里可以翻看任意迭代轮次的输出。很多同事一开始不习惯觉得没必要但模型出现异常收敛或过拟合时回看这些中间可视化比翻代码更快。3.3 Artifact 模型产物与配置归档训练结束以后模型保存路径和测试指标如果只放在脚本里下一次实验很容易把它覆盖。ClearML 推荐的方式是上传为 Artifact 产物task.upload_artifact( namebest_model.pth, artifact_object./checkpoints/best.pth ) task.upload_artifact( nametrain_config, artifact_object{ lr: args.lr, batch_size: args.batch_size, seed: args.seed, } )artifact 可以是单文件、整个目录、字典、序列化对象。上传后任务详情页的“产物”标签页能够直接下载这就把“模型文件在哪”的疑问彻底解决了。如果你做了很多组实验建议再调用模型注册 API把候选模型发布成可比较的注册模型from clearml import Model model Model(model_idtask.models[output][-1].id) model.publish()发布之后模型会进入服务端专门的模型页签带有版本、标签、所属任务。团队里后续如果要选型上线可以甩出这个页面的链接而不是靠口头争论哪个模型指标“感觉上更好”。3.4 完整训练脚本的改造过程把上面几部分拼起来一个 PyTorch 风格的训练脚本接入部分大致长这样from clearml import Task # 初始化实验任务 task Task.init( project_name某图像识别项目, task_nameresnet50 微调实验A, reuse_last_task_idFalse ) logger task.get_logger() # 自定义配置这段字典会作为超参会展示在实验详情里 args { lr: 1e-4, epochs: 30, backbone: resnet50, } model build_model(args[backbone]) for epoch in range(args[epochs]): train_loss run_one_epoch(model, train_loader) val_acc validate(model, val_loader) logger.report_scalar(loss, train, iterationepoch, valuetrain_loss) logger.report_scalar(acc, val, iterationepoch, valueval_acc) torch.save(model.state_dict(), fcheckpoints/{epoch:03d}.pth) # 把最终模型挂到任务上 task.upload_artifact( namefinal_model.pth, artifact_objectcheckpoints/030.pth )这样改完代码本身几乎没增加多少量但每跑一次实验服务端就会留下完整可查的“快照”代码、参数、指标、模型产物。3.5 自动记录与手动记录的最佳边界实际用下来我的建议是各有分工自动记录负责环境类信息例如 Python 包、Git 版本。argparse 参数和框架类配置尽量也走自动记录不用额外重复上报。训练过程中变化的值、图像、中间结果一定要手动上报。模型文件通过 artifact 上传挂在任务下面而不是放在某个共享目录里靠路径管理和注释维护。手动上报过多会显得杂乱手动上报过少又会回到“查无实据”的老路。团队可以约定一个底线每条实验至少上报 loss 曲线、验证指标、最好模型的 artifact这三样齐了复现基本不缺信息。4. Agent 远程执行不再依赖本地跑任务的“脏流程”4.1 Agent 工作机制队列、Worker、TaskAgent 是 ClearML 里很容易被低估的模块。简单说它就是一台训练机上的常驻进程持续监听一个或多个队列。当实验任务被加进队列Agent 就把它取下来创建独立环境执行其中的训练代码。它要解决的问题很具体开发机性能不够训练必须跑到另一台有 GPU 的机器上但你不能让同事每次都手动去那台机器 clone 代码、装依赖、跑命令。Agent 把这个过程自动化了并且保证执行现场和任务记录里描述的一致。实际结构里一台训练机可以注册多个 Worker一个 Worker 监听一个队列队列与队列之间可以设置优先级。我把资源划分成两个队列一个给日常开发跑的小实验一个给正式训练和调参跑的长任务互不阻塞。4.2 从开发机把任务调度到训练机的完整操作流程在训练机上安装并启动 Agentpip install clearml-agent clearml-agent daemon --queue default启动前确保这台机器上已经执行过clearml-init并且配置连的是同一个服务端。然后创建任务时给任务指定一个队列名task Task.init( project_name模拟项目X, task_name远程训练任务, reuse_last_task_idFalse ) # 将当前任务排队到 default 队列 task.execute_remotely(queue_namedefault)execute_remotely的逻辑是先把当前代码的上下文打包成可执行任务提交到服务端并放入队列然后本地的这个进程就结束。接下来训练机上的 Agent 会接管任务拉取 git 代码、创建虚拟环境、执行训练脚本。对我来说最舒服的地方是开发机上没有任何 GPU 也能完整调试确认脚本逻辑没问题后把执行方式从本地跑改成execute_remotely训练就到带 GPU 的机器上继续了。也可以不借助代码在 Web 界面上把某个已完成任务点“入队”重新执行这非常适合做超参扫描。实际复现实验时这个能力价值极大——不再依赖“原作者那台电脑还在不在”。4.3 实际踩过的三个 Agent 坑关于 Agent踩坑的案例非常多挑三个影响最深的第一个是 git 版本不一致。Agent 在远程机器重构环境时会依据任务里记录的 git commit 执行代码重建。如果本地代码有未提交的修改Agent 拉不下来导致任务跑起来的是旧代码。这是最容易出现“本地能跑、远程跑出不一样结果”的原因。我后来强制要求执行远程任务前必须先提交代码。第二个是 Docker 模式的启动方式。如果 Agent 配置了容器化执行训练机上的 Docker 网络和宿主机不同模型保存路径、数据集挂载都容易出问题。我的建议是初期先绕过 Docker直接用裸金属方式跑 Agent调试稳定后再上容器不要在第一天就把两个复杂度叠加在一起。第三个是被“卡住”的任务。有时候 Agent 因为网络原因和服务端短暂断连任务会一直处于排队或卡住状态。解决方案是给 Agent 启动脚本加上自动重启同时定时检查队列clearml-agent daemon --queue default --stop-timeout 60这个命令可以让 Agent 在任务结束前预留缓冲时间避免任务刚结束就被误杀对长训练任务很友好。我见过不少同学在 Agent 上栽跟头后放弃它退回“手动拷代码到训练机跑”的原始方案其实调整好这几个细节稳定性完全够用。5. 数据集版本化管理与 Pipeline面向复现的进阶用法5.1 数据集为什么会成为复现的瓶颈实验复现困难模型代码只是其中一个原因数据集版本才是最大的隐形变量。同一份“车辆检测数据”可能上周删了一部分错误标注图这周又补充了一批新采集样本文件夹路径不变但内容已经完全不同。等你拿到三个月前的脚本时根本不知道当时的data/vehicle_det到底是什么状态。ClearML 的 Dataset API 就是为了给数据集打上正式版本像管代码一样管数据。5.2 Dataset API 的创建、上传、获取闭环创建并发布一个数据集版本from clearml import Dataset dataset Dataset.create( dataset_name车辆检测数据, dataset_project数据集仓库, version2025.06.01, ) dataset.add_files(path/data/vehicle_det) dataset.upload() dataset.finalize()finalize之后这个版本就被冻结不能再改了。如果数据有更新就基于旧版本派生新版本dataset2 Dataset.create( dataset_name车辆检测数据, dataset_project数据集仓库, parent_dataset_iddataset.id, version2025.06.15, ) dataset2.add_files(path/data/vehicle_det_new) dataset2.finalize()训练脚本里获取数据时直接用名称拉取dataset Dataset.get( dataset_name车辆检测数据, dataset_project数据集仓库, version2025.06.15, ) local_path dataset.get_local_copy()get_local_copy会把数据集下载到本地缓存目录下次再取同一版本时直接命中缓存。数据版本还有一个很实用的点训练任务的日志里会记录你到底用了哪个数据集版本。这样三个月后再回看实验打开任务详情数据版本字段清清楚楚不会再出现“当时好像用的新数据”这种模糊记忆。对于小数据集Dataset API 完全是正向收益对于超大对象存储数据集速度会受网络影响建议结合 S3/OSS 存储来做上传下载这一点如果你的团队有成熟对象存储会很顺畅。5.3 Pipeline 的适用场景和不适用场景Pipeline 相当于把多步骤实验串起来数据准备完成后再启动训练评估完成后自动关闭。一个简单示例from clearml.automation import PipelineController pipe PipelineController( project某项目Pipeline, name数据准备-训练-评估, version0.1, ) pipe.set_default_execution_queue(default) pipe.add_step( namedata_prepare, base_task_idid_of_data_prepare_task, ) pipe.add_step( nametraining, parents[data_prepare], base_task_idid_of_training_task, ) pipe.add_step( nameevaluation, parents[training], base_task_idid_of_evaluation_task, ) pipe.start()这个价值在于每次跑全流程都会自动记录每一步的任务 ID、依赖关系和产物位置如果中间某一步失败能快速定位。但我的建议是不要一开始就上 Pipeline。只有当你频繁重复“多步骤实验”并且人工串联成本很高时它才值得引入。如果每个实验都是在单步训练上反复试探超参那 Pipeline 反而多了一层心智负担。6. 让团队真正用起来的几个实际经验6.1 最小可用闭环的落地顺序很多团队引入 MLOps 工具失败往往不是因为工具本身不行而是想一次把闭环铺得太大。我们这次分三步走第一阶段只做跟踪所有训练脚本统一接入Task.init去开会时不再贴截图直接给实验链接。第二阶段加 Agent把常用训练机注册成 Worker团队成员习惯在界面操作“入队”谁也不用再问“训练机空闲吗”。第三阶段上数据集和 Pipeline等大家都尝到了自动记录数据版本的甜头之后再引入 Pipeline接受度会高很多。这个顺序让每个阶段都有清晰的收益。如果一开始就要求所有人上手 Dataset Pipeline很多同事会直接被复杂度劝退。6.2 权限、存储与服务端维护的小提醒服务端跑起来只是开始维护才是长期的活。权限要给每个团队成员单独创建账号和 API 凭证不要共用同一个 Key。否则等有人误删任务或者上传了错误数据时根本没法定位是谁干的。存储File Server 的磁盘非常容易被模型文件和训练日志占满尤其是频繁上传调试图片的项目。我建议定期清理历史任务的 artifact或者规定只保留最终模型并不定期清理中间产物。备份服务端的 PostgreSQL 数据要定期备份它承载了所有任务、指标和元数据。一旦数据库出问题界面还能打开但历史记录可能会丢。这些看起来琐碎但真正决定一个平台能不能用一年以上恰恰是这些“看不见的工程”。6.3 一些容易被忽略但很有用的细节最后分享几个耗时不一定多、但收益特别高的使用细节把任务名规范化。任务名建议统一成“项目-目标-版本”的格式比如“模拟项目X-lr扫描-20250602”不然实验积累起来之后界面上一堆同名任务会让你非常痛苦。多利用标签功能。可以用tags区分不同用途例如“debug”“baseline”“candidate”后面想找候选模型时按标签筛选比一个个翻任务快得多。把“取消本地跑任务”变成习惯。只要代码逻辑确认没问题就上 Agent 远程执行。即使本地机器性能足够远程执行也有好处任务期间你的开发机不会被长期占用来做计算并且所有上下文都会被服务端完整记录。对我来说ClearML 最爽的一点是引入之后团队里再也没人会说“这个实验我忘了记录”“那版数据找不到了”。所有环节的服务端都有据可查。如果你正被实验复现和团队协作折磨建议按照上面这套思路先把实验跟踪和 Agent 跑起来数据集版本和流水线可以等稳定了再逐步加进去。工具本身不是银弹但一套清晰的工作流确实能让团队的模型产出质量上一个台阶。