TT Lab
开始
学习 学习路径 课程

上周那个模型更好,可没人找得到

版本是事实,别名是角色

在 TT Lab 中继续学习

一句话总结

注册表不是堆放模型文件的仓库,而是用版本和别名,让“现在到底是什么在提供服务”这个问题能在一个地方得到回答的装置。

为什么需要它

实验整理好之后,下一个问题就来了:“那么现在线上跑的是哪一个?”令人吃惊的是,这个问题最常卡住。模型文件以 model_final_v3_really.pkl 的名字放在 S3 的某个角落,服务配置里把那个路径写成了字符串,而那个文件出自哪次运行,只在创建者的脑子里。那个人一休假,整个组织就无法说明自己的模型。

回滚也因为同样的原因变得困难。如果部署是“改路径字符串”,那么回滚也就是“把路径字符串改回去”,情况紧急时,是谁回滚到了哪个文件,不会留下任何记录。事后想画出时间线,就只能去翻聊天记录。

工作原理

MLflow 模型注册表的文档把这个问题归纳为四个概念(MLflow Model Registry)。

概念 内容
Registered Model 一个有名字的模型。装着版本、别名、标签
Model Version 每次以同一个名字注册,编号就加 1。第一次注册是 1
Model Alias 指向特定版本的 可以改变的名字。比如 champion
Tag 键值标签。也可以挂在版本上(例如 validation_status: approved)

关键在于把版本和别名分开。版本是一旦产生就不会改变的事实,别名则是指向“现在担任这个角色的那个”的旋钮。如果让线上服务指向 models:/MyModel@champion,那么部署就成了改变别名所指向的版本这件事。文档对这种方式的说明是:给要接收线上流量的版本一个别名,并以这个别名为目标,只要把别名重新指向另一个版本,就能改变服务对象。

在此基础上还有谱系(lineage)。每个注册的版本都与创建它的运行或已记录的模型相连,所以能往回追溯,它是用什么数据和参数训练的。前一个模块里认真记录运行的原因,在这里得到了回收——注册表站在跟踪之上,如果跟踪是空的,注册表也就只不过是一份文件清单。

registry.json   버전 1, 2, 3 …   (변하지 않는 사실)
aliases.json    champion → 2     (지금 누가 그 역할인가)
audit.jsonl     champion 1→2, 2→1 …  (어떻게 여기까지 왔는가)

移动别名这件事,还附带一条纪律:必须把移动这件事写在某个地方。只看别名文件,只能知道“现在 champion 是 2 号”这个当前状态,而“到昨天为止还是 1 号”这个事实不会留下来。所以别名旁边要跟着变更历史。把变更前的值、变更后的值、谁为什么移动的,一行一行地累积下来,从头重放这份记录时,得到的应当恰好就是现在的状态。如果得不到,就是有人没留记录就动了手。

标签是比别名更轻的标记。给等待验证的版本加上 validation_status: pending,给通过的版本加上 approved,用这种方式表示状态。别名对一个角色只能挂一个,而标签可以挂多个,所以很适合用作自动化读取的条件。

在现场相遇的样子

没有别名,而是把版本号直接写进服务配置的团队,回滚时得把部署流水线重新跑一遍。越是紧急,这条流水线就越慢。使用别名的团队,在同样的情况下只需改变指向的位置,并且这次变更会以一行的形式留在历史里。

也有相反方向的错误:别名建得太多。如果 champion、stable、prod、prod-real、prod-new 同时存在,就没有人知道哪个才是真的。别名 只按角色的数量来设。通常一个线上、一个挑战者就够了。

第三是删除版本的习惯。把失败的版本从注册表里删掉,列表是干净了,但那个版本为什么失败也随之消失。下个季度想再尝试类似做法的人,就会撞上同一堵墙。

第四是把注册表和制品存储当成同一样东西的误解。模型文件的字节放在哪里,是存储的事,而注册表回答的是“那些字节是第几号版本、现在担任什么角色、来自哪里”。把两者混在一起,每次搬动文件,部署就会坏;反过来,文件没变、角色变了这件事,却哪里都留不下记录。实验里把 registry.json 和模型文件分开放,原因就在这里。

下一项测验要确认什么

确认版本和别名各自负责什么,用同一个名字注册新模型时编号会怎样,以及为什么回滚必须是移动别名这件事。