城东有家开了十年的“深度烘焙坊”,店主小墨把招牌欧包卖出了名气。她最头疼的不是揉面,而是“改配方”:今天多加 5 克盐,明天换一种面粉,后天调整发酵时间。客人尝着口味时好时坏,小墨自己也想不起来哪一版最好吃、到底改了什么。直到她给每个配方编号、记录原料批次和操作步骤,面包才真正稳定下来。AI 训练模型的过程和烤面包几乎一模一样:模型文件像烤好的面包,训练代码像烘焙步骤,数据集像面粉,超参数像温度和时间。“模型版本控制”就是小墨那本配方账本,它用一套规范方法,让每个模型版本是谁做的、用什么料、怎么烤出来的,全都清晰可查。

岗位关联

掌握模型版本控制,你就能让团队在模型迭代中告别混乱,从容实现模型的可追溯、可复现、可回滚,这是 MLOps 工程的基本功。

目录
  1. 可视化演示
  2. 1. 版本语义化编号
  3. 2. Artifact版本管理
  4. 3. 数据超参绑定
  5. 4. 版本对比与回滚
  6. 5. 多侧版本联动
  7. 6. 审计日志与合规
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

分步展示模型版本控制的核心环节:语义化编号体系、Artifact不可变存储、训练数据与超参数版本绑定、版本对比与灰度回滚,交互切换理解全链路可追溯管理。

本节配有交互动效(进入视口后自动加载,约 3KB)。

语义化编号:Major.Minor.Patch三级体系 Major表示架构级变更,Minor表示性能显著提升,Patch表示bug修复 v = Major.Minor.Patch 三级编号实现模型迭代的精确标识和语义化沟通。

1. 版本语义化编号

小墨的配方卡升级到第 37 版时,她发现“第 37 版”根本说不清变化有多大。是换了个新口味,还是仅仅微调了烘烤时间?于是她改用 v2.3.1 这种写法,并定下规矩:面粉基底完全换掉算大版本,加一种新配料算小版本,烤制时间差一两分钟只算补丁。这样无论过了多久,任何人看到版本号,都能立刻判断这次改动的分量。

AI 模型同样需要语义化版本号。主版本(MAJOR)代表架构或行为的不兼容变化,比如从 CNN 换成 Transformer;次版本(MINOR)代表兼容性增强,比如新增一个特征、扩充训练语料;补丁版本(PATCH)代表缺陷修复或小范围调参。模型发布前必须遵守“先改版本号再发布”的纪律,已经发布过的版本号不允许覆盖。这是团队协作中的“沟通契约”:下游的推荐系统、风控引擎看到主版本变化,就会自动触发回归评估,而不是盲目信任新模型。

在模型注册表里,版本号通常被记录成三元组:

客人看到 v2.0.0 就知道是“全新配方”,看到 v2.0.1 就放心这还是熟悉的味道。版本号让沟通变得高效而准确,也让下游团队知道要不要重新做一轮验证。

V=(M,N,P)\displaystyle V = (M, N, P)
版本号由一个三元组描述,其中 M 是主版本,N 是次版本,P 是补丁号,三者共同精确表达一次模型迭代的性质。
  1. 版本三元组模型版本号由主版本、次版本、补丁号三个整数组成,例如 v2.3.1 对应 M=2, N=3, P=1。
    V=(M,N,P)\displaystyle V = (M, N, P)
  2. 主版本递增当模型架构或行为发生不兼容变化时,主版本加一,次版本和补丁号清零,避免新版本被误认为旧版的小升级。
    M′=M+1,N′=0,P′=0\displaystyle M' = M + 1,\quad N' = 0,\quad P' = 0
  3. 次版本递增新增功能且保持向后兼容时,次版本加一,补丁号清零。
    N′=N+1,P′=0\displaystyle N' = N + 1,\quad P' = 0
  4. 补丁递增修改训练缺陷或调整少量超参数时,只需要补丁号加一。
    P′=P+1\displaystyle P' = P + 1

2. Artifact版本管理

烤好的面包必须装进密封包装、贴上生产批号,否则顾客不知道是不是最新款,也无法判断有没有被掉包。模型 artifact 也是一样:权重文件、模型图结构、分词器、推理代码等打包在一起,必须被安全地存储和标识,才能成为可交付的“成品”。

模型 artifact 不只是 model.pkl 这一个裸文件,还包含运行所需的框架版本、推理依赖、输入输出规范和模型说明。AI 团队通常把 artifact 上传到模型注册表,例如 MLflow Model Registry 或云对象存储加元数据库。每个 artifact 都要遵循统一的命名规范,比如 recommender_v3.2.1.pkl,并且不允许原地修改。如果发现权重有问题,只能生成一个新版本,不能覆盖旧文件。

为了确保文件不被篡改,版本注册表会计算内容哈希:

面包包装袋上的防伪码,拿到一验就知道有没有被调包;模型 artifact 的哈希就是数字世界的防伪码,让每个版本都经得起溯源。

H=SHA256(F)\displaystyle H = \mathrm{SHA256}(F)
这一步把任意大小的模型文件压缩成固定长度的“指纹”,内容变了指纹就变,因此可以作为防篡改标识。
  1. 输入文件模型文件 F 是一串二进制字节流,无论多大都能参与哈希计算。
    F=bytes(model)\displaystyle F = \mathrm{bytes}(model)
  2. 哈希计算对 F 做 SHA256 运算,得到固定 256 位的摘要 H,内容只要改动一个字节,H 就会完全不同。
    H=SHA256(F)\displaystyle H = \mathrm{SHA256}(F)
  3. 内容寻址存储系统以 H 作为对象标识的一部分,读取时重新计算哈希比对,不一致就说明文件被篡改或传输损坏。
    verify(F′)=(HF′==H)\displaystyle \mathrm{verify}(F') = (H_{F'} == H)

3. 数据超参绑定

小墨发现,同一张配方卡,在不同批次的面粉和不同室温下发酵,烤出来的面包味道会有差异。AI 模型也一样:数据分布变了,超参数改了,模型表现就会天差地别。要想复现一个模型,必须同时记录“原料”和“火候”。

训练模型时,数据版本号(如 DVC 管理的 data/2025/03/15)、超参数配置的哈希、训练代码的 commit id 三者必须绑定到一次运行记录中。超参数配置通常用 JSON 保存,再对其计算哈希得到 params_hash,防止参数被静默修改。运行记录与最终模型 artifact 关联,形成一条完整的实验链。

可复现性可以用一个组合签名来表示:

小墨的烘焙日志记录“面粉批次 + 发酵温度曲线 + 揉面手法”,才能在第二天烤出一模一样的味道;模型的运行记录就是这样的“烘焙日志”。

R=HASH(Cid+Did+Phash)\displaystyle R = \mathrm{HASH}(C_{id} + D_{id} + P_{hash})
R 是整个训练环境的“指纹”,由代码提交号 C_id、数据版本号 D_id 和超参哈希 P_hash 共同计算;任何一个输入变化,R 都会改变,从而提醒团队需要生成新的模型版本。
  1. 输入组合将训练代码的提交号、训练数据版本号和超参数配置哈希拼接成一段字符串。
    input=Cid+Did+Phash\displaystyle \mathrm{input} = C_{id} + D_{id} + P_{hash}
  2. 生成签名对组合字符串计算哈希,得到本次训练的复现 ID。
    R=HASH(input)\displaystyle R = \mathrm{HASH}(\mathrm{input})
  3. 复现判定只要三个输入都和记录一致,R 就一致;没有完整的绑定,就无法复现模型效果。
    R1=R2⇔(Cid,Did,Phash)1=(Cid,Did,Phash)2\displaystyle R_1 = R_2 \Leftrightarrow (C_{id}, D_{id}, P_{hash})_1 = (C_{id}, D_{id}, P_{hash})_2

4. 版本对比与回滚

小墨推出新版面包后,老顾客反馈“太硬了”。她没有被新配方困住,而是立刻把上一版经典款重新上架,新款放到角落继续试吃。线上模型发布也应该有这样的“后悔药”,不能一上线就再无退路。

业界常用“冠军挑战者”模式。Champion 是线上稳定版本,Challenger 是候选新版本。候选模型先在影子环境或小流量 A/B 测试中运行,对比准确率、延迟、业务转化率等指标。只有候选版本显著优于或至少不低于稳定版,才允许晋升为新的 Champion。

对比决策可以用一个简单公式表达:

回滚不是删掉新版本,而是“重新上架经典款”。新版本依然保留在注册表里,等修复后再作为新挑战者出现。

Δ=Echallenger−Echampion,Δ<−θ⇒rollback\displaystyle \Delta = E_{challenger} - E_{champion},\quad \Delta < -\theta \Rightarrow \text{rollback}
Delta 是候选版本指标与稳定版本指标的差值;当差值跌破负阈值 theta 时,系统判定候选版本明显倒退,立即触发回滚。
  1. 评估差计算候选版本在评估集上的指标 E_challenger 减去稳定版本指标 E_champion,得到差值 Delta。
    Δ=Echallenger−Echampion\displaystyle \Delta = E_{challenger} - E_{champion}
  2. 阈值判断如果 Delta 小于负阈值 -θ,说明候选版本表现显著差于线上版本,触发回滚信号。
    Δ<−θ\displaystyle \Delta < -\theta
  3. 回滚动作从模型注册表拉取稳定版本的完整清单,按发布流程重新部署,并记录审计日志。
    action=rollback_to(champion)\displaystyle \mathrm{action} = \mathrm{rollback\_to}(champion)

5. 多侧版本联动

要复刻上周日卖爆的面包,小墨必须同时找到当时的配方卡、面粉批次记录和烤箱温度日志;只找到配方却找不到原料记录,照样烤不出来。模型版本也是一个“三方联动的产物”,只盯着模型文件远远不够。

模型版本与代码版本、数据版本、配置版本强绑定。训练代码的 commit id 决定了模型结构和训练逻辑;数据版本决定了模型看到的世界;超参配置决定了训练策略。三者任何一个变化,产出的模型都应当视为新版本。为此,AI 团队会在模型注册表中记录一份 manifest 清单,例如 model_v2.1.0+code_a3f9d2+data_d7e11b+param_8e2f0c,并在上线前强制检查“未绑定代码/数据版本就不允许发布”。

这种联动关系可以抽象为一个函数:

小墨的面包店同时记录配方卡、面粉批次、烤箱参数,缺一环就无法复刻;AI 团队通过联合清单保证模型在任何时间点都能被完整还原。

Mv=Φ(Cc,Dd,Pp)\displaystyle M_v = \Phi(C_c, D_d, P_p)
模型版本 M_v 是代码版本 C_c、数据版本 D_d 和配置版本 P_p 共同作用的结果;函数 Φ 表示训练与打包过程。只有三者确定,才能确定唯一的模型产物。
  1. 输入变量每一次训练都从代码库、数据版本库和配置库中取得确定的三元组。
    (Cc,Dd,Pp)\displaystyle (C_c, D_d, P_p)
  2. 训练映射训练过程把三元组映射为一个模型产物,模型版本号由本次映射的结果决定。
    Mv=Φ(Cc,Dd,Pp)\displaystyle M_v = \Phi(C_c, D_d, P_p)
  3. 联动校验如果某个输入发生变化,继续沿用旧版本号会导致误导,因此必须生成新的模型版本并更新 manifest。
    (Cc,Dd,Pp)≠(Cc′,Dd′,Pp′)⇒Mv≠Mv′\displaystyle (C_c, D_d, P_p) \neq (C'_c, D'_d, P'_p) \Rightarrow M_v \neq M'_v

6. 审计日志与合规

面包店的每张配方卡背面都有一页“操作记录”:谁在几点几分改了配方、为什么改、上一版是什么。遇到卫生检查或顾客投诉,小墨能拿出完整台账。模型审计日志就是 AI 团队的台账,是长期合规的基石。

在模型注册表中,每次发布、晋升、回滚、下线操作都应追加一条日志,记录操作人、时间、动作、版本变化和原因。日志一旦写入就不能修改,通常存放在防篡改的对象存储或带有追加锁的日志服务中。在金融风控、医疗和自动驾驶等强监管场景,这条链路是回答“为什么这个模型会做出这个决策”的重要证据。

一条审计日志可以形式化表示为:

小墨被检查时拿出台账,每笔改动都有据可查;AI 团队面对监管质询时,审计日志就是最硬气的回答。

L=(t,u,a,vfrom,vto,r)\displaystyle L = (t, u, a, v_{from}, v_{to}, r)
L 是一条不可变的操作记录:t 是操作时间,u 是操作人,a 是动作类型,v_from 是变更前版本,v_to 是变更后版本,r 是变更原因。六要素齐备,才能支撑事后审计。
  1. 时间与操作人每次操作都绑定时间戳 t 和身份标识 u,回答“谁在什么时候做了操作”。
    (t,u)\displaystyle (t, u)
  2. 动作与版本变化记录动作类型 a(注册、晋升、回滚等),以及从版本 v_from 到 v_to 的变化。
    (a,vfrom,vto)\displaystyle (a, v_{from}, v_{to})
  3. 原因与审计闭环记录原因 r,把技术操作和业务意图连接起来,形成可追溯的证据链。
    L=(t,u,a,vfrom,vto,r)\displaystyle L = (t, u, a, v_{from}, v_{to}, r)

AI 应用场景

金融风控模型迭代与回滚

阶段:模型部署与监控

解决的问题:信贷风控模型上线后因数据漂移导致坏账率上升,传统做法只能人工排查并紧急重训,很容易错过止损窗口。AI 版本控制体系让每次上线都有稳定版本兜底,监控指标异常时自动回滚。

具体案例:某银行每天处理 500 万笔交易,风控模型 v2.3.0 上线后信用评分分布发生偏移。MLOps 平台在 shadow 环境对比 Champion 版本,发现坏账率指标超过阈值,自动回滚到 v2.2.1,同时生成包含操作人、时间和原因的回滚报告,提交风控委员会审计。

推荐系统 Champion-Challenger 对比

阶段:模型评估与 A/B 测试

解决的问题:推荐算法频繁迭代时,仅靠直觉判断新旧版本效果容易误判。版本对比策略让每个候选版本都在受控流量中验证,数据说话,且随时可回退。

具体案例:某电商平台用模型注册表管理 20 多个推荐模型。新模型作为 Challenger 先在 5% 流量上运行,点击率相比 Champion 提升 3% 后晋升;另一版模型指标下降,系统自动将其标记为失败并保留在注册表中,供后续分析。

自动驾驶数据与模型联动

阶段:数据管理与模型训练

解决的问题:自动驾驶模型依赖海量路测数据,数据版本、代码版本和模型版本若不一致,事故复现和追责会变得极其困难。多侧版本联动为每一版模型建立精确的“出生证明”。

具体案例:某自动驾驶团队用 manifest 绑定每个模型版本的传感器数据子集、标注规则版本和训练代码 commit id。当新版本在雨夜场景失效时,团队能立即定位是数据缺口还是代码回归,并回滚到经过验证的安全版本。

Python 演示

python87 行
import hashlibimport jsonimport osimport tempfilefrom datetime import datetime  class ModelRegistry:    def __init__(self):        self.versions = {}        self.current = {}     def _hash_file(self, path):        h = hashlib.sha256()        with open(path, "rb") as f:            while True:                chunk = f.read(65536)                if not chunk:                    break                h.update(chunk)        return h.hexdigest()[:16]     def register(self, name, path, data_id, params, major, minor, patch, note=""):        version = f"v{major}.{minor}.{patch}"        if name in self.current and version in self.versions[name]:            raise ValueError("version already exists")        record = {            "version": version,            "artifact_hash": self._hash_file(path),            "data_id": data_id,            "params": params,            "note": note,            "time": datetime.utcnow().isoformat(),        }        self.versions.setdefault(name, {})[version] = record        self.current[name] = version        return record     def rollback(self, name, version):        if version not in self.versions.get(name, {}):            raise ValueError("target version not found")        self.current[name] = version        return self.versions[name][version]     def audit_log(self, name):        logs = []        for ver, rec in self.versions[name].items():            logs.append({                "version": ver,                "data_id": rec["data_id"],                "hash": rec["artifact_hash"],                "note": rec["note"],            })        return logs     def save_manifest(self, name, out_path):        manifest = {            "model": name,            "current": self.current[name],            "records": self.versions[name],        }        with open(out_path, "w", encoding="utf-8") as f:            json.dump(manifest, f, ensure_ascii=False, indent=2)        return out_path  if __name__ == "__main__":    reg = ModelRegistry()    with tempfile.NamedTemporaryFile(delete=False) as tmp:        tmp.write(b"v1.1.0 weights")        tmp_path_1 = tmp.name    with tempfile.NamedTemporaryFile(delete=False) as tmp:        tmp.write(b"v2.0.0 weights")        tmp_path_2 = tmp.name     reg.register("recommender", tmp_path_1, "data_20250315",                 {"lr": 0.001, "batch": 256}, 1, 1, 0, note="baseline")    reg.register("recommender", tmp_path_2, "data_20250401",                 {"lr": 0.0005, "batch": 256}, 2, 0, 0, note="new arch")     print("current:", reg.current["recommender"])    reg.rollback("recommender", "v1.1.0")    print("current after rollback:", reg.current["recommender"])    print("audit:", json.dumps(reg.audit_log("recommender"), ensure_ascii=False, indent=2))     os.unlink(tmp_path_1)    os.unlink(tmp_path_2)
目的:实现一个轻量模型注册表,演示注册模型版本、绑定数据与超参、一键回滚和输出审计日志的核心流程。
  1. h = hashlib.sha256()创建哈希计算器,相当于给模型文件盖“防伪章”。
  2. chunk = f.read(65536)分块读取文件,避免超大权重文件一次性占用内存。
  3. version = f"v{major}.{minor}.{patch}"按语义化格式生成版本号,保证命名统一。
  4. self.versions.setdefault(name, {})[version] = record以“模型名→版本→记录”的两级字典保存所有版本,永不覆盖历史。
  5. self.current[name] = version更新当前版本指针,表示线上应使用哪个版本。
  6. reg.rollback("recommender", "v1.1.0")演示一键回滚:目标版本必须存在,否则报错,就像重新上架旧配方。
  7. json.dump(manifest, f, ensure_ascii=False, indent=2)把绑定清单保存成人可读的 JSON,供审计和复现使用。

常见误区

误区我觉得把模型文件按时间命名,比如 `model_20250101.pkl`,就已经算版本管理了,何必搞得那么复杂。

正解时间戳无法表达“变更是否兼容”。语义化版本号 v2.1.0 用主、次、补丁三个维度告诉团队这是重构、新功能还是修 bug。文件名可以保留,但版本注册表里必须以语义化版本为唯一标识。

为何常见:从普通文件管理习惯出发,直觉认为“按时间区分新旧”就够了,忽略了模型之间的兼容语义。

误区我只要把权重文件保存下来,以后想复现就 load 这个文件,不需要记录训练数据和代码版本。

正解模型权重只是产物,不包含数据清洗规则、代码 bug 和超参数上下文。没有数据版本和代码 commit,权重只可推理,不可复现。就像只看面包不看配方和面粉批次,无法烤出第二炉同样味道。

为何常见:初学者常用 torch.save 保存一个小模型,感觉权重文件能复现一切,未遇到工程协作和长期演化问题。

误区线上模型出问题,我把旧权重重新部署一下就算回滚了。

正解旧模型往往依赖旧代码、旧数据和旧依赖,只换权重会导致环境不匹配。正确回滚是从模型注册表中拉取该版本的完整 manifest,包括权重、代码 commit、数据版本和配置,再走发布流程。

为何常见:在本地实验时,权重和环境都在同一台机器上,容易忽略线上部署的一致性。

误区既然每次发布都计算了 SHA256,文件一定没被改过,不用再写操作日志。

正解哈希只能证明内容没变,但不能回答“谁在什么时候为什么发布或回滚”。审计日志记录的是操作行为,哈希和日志二者互补。合规审计需要的是“人 + 时间 + 原因”的完整链路。

为何常见:把技术性防篡改手段与治理性追踪手段混为一谈。

误区先把模型训练好,最后存到一个文件夹里,再补一个版本号就行。

正解版本控制应贯穿数据准备、实验记录、评估、发布全流程。每个实验跑完就登记版本,训练完成后才能真正召回最优版本。如果最后补登记,中间几百个实验早就乱套了。

为何常见:工程管理意识不足,把版本号当成“交付时贴标签”,忽略了版本控制对实验过程的价值。

面试题

Q1. 模型语义化版本号 v3.1.2 中,次版本号 1 的正确含义是?

单选题 · easy

  • A.修复了训练中的小 bug
  • B.新增了特征但保持兼容← 正确答案
  • C.模型架构发生了不兼容变化
  • D.回滚到了上一个稳定版本

答案:B。语义化版本中次版本号代表向后兼容的新功能或增强,比如新增特征;如果是不兼容变化应递增主版本,修复 bug 应递增补丁号。

Q2. 模型 artifact 存储中“内容寻址”的核心思想是什么?

单选题 · easy

  • A.用文件名称区分不同版本
  • B.用内容哈希作为唯一标识← 正确答案
  • C.用文件修改时间排序版本
  • D.用 Git 提交编号作为标识

答案:B。内容寻址以文件内容计算出的哈希值作为对象标识,内容一旦被篡改,哈希就会变化。这和面包包装上的防伪码原理相同,能从根本上防止版本被掉包。

Q3. 以下哪项不是模型可复现的必要绑定?

单选题 · mid

  • A.训练代码 commit id
  • B.训练数据集版本
  • C.训练机器的 GPU 数量← 正确答案
  • D.超参数配置哈希

答案:C。可复现需要代码、数据、超参三者确定,GPU 数量虽然影响训练速度,但同一套输入在相同环境下通常可以得到一致结果,不属于必要绑定条件。

Q4. 线上稳定版 A 的候选模型 B 在 shadow 环境评估指标显著下降,正确做法是?

单选题 · mid

  • A.立刻删除 B 并继续 A
  • B.回滚到 A 并记录审计日志← 正确答案
  • C.直接停掉线上服务
  • D.忽略指标以人工判断为准

答案:B。B 指标显著下降时应触发回滚到稳定版 A,同时记录操作日志。删除 B 会丢失失败经验,停服和忽略指标都会放大风险。

Q5. 模型版本、代码版本、数据版本联动时,模型版本只发生 PATCH 递增,说明?

单选题 · hard

  • A.训练数据全量换新并变更
  • B.仅修复推理脚本中的一个 bug← 正确答案
  • C.模型架构发生了不兼容变化
  • D.新增一个业务功能并发布

答案:B。PATCH 递增代表向后兼容的缺陷修复。训练数据全量更换和新增业务功能应属于次版本甚至主版本变化,架构不兼容变化则应递增主版本。

Q6. 关于模型版本审计日志,以下描述正确的是?

单选题 · mid

  • A.审计日志仅在出问题时才记录
  • B.日志需包含用户时间动作和原因← 正确答案
  • C.日志存内存且重启丢失也无妨
  • D.审计日志可完全由模型自动生成

答案:B。审计日志必须以追加方式持久化,包含时间、用户、动作、版本变化和原因。它记录了操作行为,是事后追踪的台账,不能只存在于内存。

Q7. 使用内容哈希校验模型 artifact 的主要目的是?

单选题 · easy

  • A.压缩模型文件体积以便存储
  • B.验证文件是否被篡改或损坏← 正确答案
  • C.提升模型在线上推理的速度
  • D.让模型代码易于阅读并维护

答案:B。内容哈希是文件的唯一“指纹”,通过比对哈希可以发现文件是否被改动或传输损坏。它不负责压缩、加速或代码可读性。

Q8. 以下哪些属于版本对比与回滚的关键策略?

多选题 · mid

  • A.冠军挑战者对比模式← 正确答案
  • B.金丝雀发布逐步放量← 正确答案
  • C.训练时随机丢弃部分数据
  • D.设定评估差异回滚阈值← 正确答案

答案:ABD。冠军挑战者模式、金丝雀发布和阈值触发都是发布与回滚策略的一部分;训练时随机丢弃数据属于数据增强手段,与回滚策略无关。

Q9. 要实现模型全链路可追溯,版本注册表需要记录哪些信息?

多选题 · hard

  • A.训练数据版本 ID← 正确答案
  • B.超参数配置哈希← 正确答案
  • C.训练代码的 commit id← 正确答案
  • D.开发工程师的生日信息

答案:ABC。数据版本、超参哈希、代码 commit 是构成复现签名的三要素,缺一不可;生日与模型可追溯性无关,不属于合规审计必需字段。

Q10. 以下关于模型版本控制实践的说法,正确的是?

多选题 · mid

  • A.主版本变化表示不兼容变更← 正确答案
  • B.artifact 哈希防止文件被掉包← 正确答案
  • C.回滚不需要保留旧数据版本
  • D.审计日志有助于满足合规要求← 正确答案

答案:ABD。主版本对应不兼容变化,哈希保证 artifact 完整性,审计日志满足合规;但回滚同样需要旧数据版本,否则旧模型无法在原始数据语义下复现和验证。