14. 训练数据怎么做版本管理和血缘追踪?

整理大模型训练数据的版本控制、血缘追踪与可复现性设计。

简单回答

大模型训练数据的版本管理和传统 ML 完全不是一个量级——数据集动辄几 TB 到几 PB,而且每个最终模型背后是几十个数据源经过几十次清洗、过滤、合成、混合的复杂血缘。做不好版本管理的后果是:模型出问题了不知道哪批数据导致的、想复现某个老版本模型的训练数据找不到、合规审计要求"证明这个模型没用某条数据"做不到。落地的做法是给每个数据集打 content hash(基于内容的指纹)、记录每次处理 step 的输入输出和参数(数据血缘 lineage)、把训练 run 和具体数据快照精确绑定。工程上常用 DVC、LakeFS、Delta Lake 这类支持大数据版本化的工具,再叠加自研的元数据系统。

详细解释

为什么传统 Git 不够用

代码用 Git 管理就够了——文件小、变化频繁、diff 直观。训练数据完全不一样:

  • 单个数据集动辄几 GB 到几 TB,Git 根本放不下(即使 Git LFS 也不适合 PB 级)
  • 数据修改通常不是"改几行"而是"加 100 万条新样本"或"过滤掉 200 万条质量差的"——传统 diff 没意义
  • 训练数据是从多个上游数据源经过多次处理融合而来,需要的是"血缘"而不是"diff"
  • 很多数据需要离线一次性大批量计算(去重、嵌入、合成),无法每次小步提交

所以训练数据的版本管理需要专门的工具栈和流程,思路也和代码版本控制不同。

版本管理的三个核心维度

要做好数据版本管理,至少要管三件事:

维度 1:内容指纹(Content Hash)

每个数据集(或每个数据切片)都要有一个基于内容计算的唯一指纹。常见做法:

  • 对数据文件做 SHA256,得到文件级 hash
  • 对数据集整体做 Merkle 树——每个文件 hash 后再 hash 起来,得到数据集 hash
  • 对样本级做 hash 用于去重和精确匹配检测

内容指纹的好处是:相同的数据无论存在什么位置、什么时间,hash 一样。这让"我是不是用了那批数据"变成精确可查询的事。

维度 2:数据血缘(Lineage)

记录数据是怎么来的——上游来源、经过了哪些处理 step、每个 step 的参数、产生了什么输出。

血缘图大致长这样:

[Common Crawl 2024-Q1 raw]   [GitHub code 2024-Q1]   [Wikipedia 2024-Q1]
        ↓ (filter quality ≥ 0.8)        ↓ (dedup)             ↓
[CC filtered v1.2]              [GH dedup v3]          [Wiki dedup v1]
        ↓                               ↓                     ↓
        └────────── (mix ratio 6:3:1) ──────────────────────┘
                              ↓
                     [Pretrain Mix v2024.04]
                              ↓ (training run #421)
                       [Model checkpoint X]

每条边记录处理参数(filter 阈值、dedup 算法、mix 比例),每个节点记录内容 hash 和元数据。这样从最终模型反查到原始数据源是完全可追溯的。

工程实现通常是元数据库(PostgreSQL 或专用 lineage store 如 OpenLineage)记录这张图,数据本身存在对象存储(S3)。

维度 3:训练 run 和数据的绑定

每次训练 run 启动时记录使用的数据集 hash 和血缘指针,固化成不可变的引用。即使后面数据被覆盖、删除,旧的训练 run 仍然指向当时的精确数据快照。

这是模型可复现性的最后一道保障——10 个月后想知道"模型 v3.2 用了什么数据训练的",查 run 元数据就能拿到。

工具栈选择

业界常用的工具组合有几套:

DVC(Data Version Control):把数据当 Git artifact 管理,元数据放 Git 仓库,实际数据放 S3 等对象存储。轻量、适合中小团队。但对超大数据集(百 TB+)支持有限。

LakeFS:把对象存储变成"有 Git 语义的数据湖"——支持 branch、commit、merge 数据。适合大规模数据集的团队协作。

Delta Lake / Apache Iceberg:表格式的版本化存储,支持 ACID、时间旅行(time travel,按时间点查询历史版本)。适合结构化训练数据。

自研元数据 + 对象存储:很多大厂走这条路——OSS/S3 存数据本身,自研一套元数据服务(PostgreSQL + 自定义 schema)记录 hash、血缘、依赖关系、权限。灵活度最高但要自己维护。

Hugging Face Datasets + DVC 组合:开源生态最常见的轻量方案。Datasets 处理加载和缓存,DVC 管理版本。

实际选型看团队规模和数据规模。10TB 以下的小团队 DVC + 对象存储就够,PB 级别的需要自研或商业方案。

血缘追踪的具体落地

血缘的关键是 记录每个处理 step 的所有输入和参数。一个 step 通常是一个脚本/任务(quality filter、minhash dedup、language detection、合成数据生成)。

每次跑这个脚本时记录:

{
  "step_id": "filter_quality_20240415_001",
  "step_type": "quality_filter",
  "inputs": [
    {"dataset_id": "cc-2024-q1-raw", "hash": "0x4a3b..."}
  ],
  "params": {
    "quality_score_threshold": 0.8,
    "filter_model_version": "fasttext-quality-v2.1"
  },
  "outputs": [
    {"dataset_id": "cc-2024-q1-filtered-v1", "hash": "0x9c2d..."}
  ],
  "code_commit": "abc123",
  "started_at": "2024-04-15T10:00:00Z",
  "ended_at": "2024-04-15T18:30:00Z",
  "operator": "alice@team"
}

这条记录入库后,从输出反查就能完整还原"这个数据集是怎么得来的"。

代码 commit 也要记录——同样的输入数据用不同版本的清洗代码可能得到不同输出,代码版本和数据版本要一起锁定。

对训练数据合规的支持

血缘和版本管理是合规的基础设施。常见的合规场景:

用户数据撤回(GDPR、删除请求):用户要求删除他们的数据,需要证明这部分数据从训练数据里移除了。没有血缘就没法证明,要重新训练(成本极高);有血缘就能定位到这部分数据进了哪些下游数据集和模型,做精确清理。

版权和数据授权审计:审计方要求证明"模型没用未授权的 X 数据集"。content hash 能精确证明某条数据是否在训练集里。

有害数据来源追溯:模型输出了某段有问题的内容(比如背诵了某个具体的人的隐私信息),通过血缘追溯到原始来源,从而清理那个数据源。

数据使用记录:审计要求出具"过去一年某数据集被用于哪些训练 run"的报告——基于血缘图能直接生成。

合规要求往往是数据版本管理基建从"nice to have"变成"must have"的强推动力。

大规模数据的实现取舍

百 TB 到 PB 级数据时,几个实际的工程取舍:

全量 hash 还是采样 hash:对每条样本计算 hash 成本很高。一种优化是只对 样本元数据(不含全文)计算精确 hash,对全文用更快的局部敏感哈希(LSH)做近似匹配。

Snapshot vs Reference:是给每个数据集存一份完整快照,还是只存"指向上游的引用 + 处理步骤"?前者占用存储但访问快,后者节约存储但每次复现都要重算。常见折中是关键节点(pretrain mix、SFT mix)存快照,中间步骤只存引用。

热数据 vs 冷数据:当前正在用的数据集放 SSD/快速对象存储,历史版本归档到便宜的冷存储(S3 Glacier)。访问历史版本会慢但成本可控。

Lineage 图的查询效率:超大规模时血缘图节点数百万级,纯关系数据库查询性能可能不够。可以考虑图数据库(Neo4j)或专用 lineage 系统(DataHub、OpenLineage)。

团队协作的实际流程

一个成熟的数据版本管理流程通常是这样的:

  1. 数据工程师在 数据 branch 上开发新的清洗脚本
  2. 在小样本上做 dry-run,验证逻辑
  3. 全量跑产生新数据集,自动入库(hash + 血缘 + 元数据)
  4. 提交"数据 PR"——附评估结果(这批新数据在评估集上的影响、统计指标变化)
  5. 评审通过后 merge 到 main 分支,新数据集对训练 run 可见
  6. 训练 run 引用 main 分支的最新版本时被冻结到具体 hash

这套流程让数据变更和代码变更一样可审计、可回滚。

面试时可以这样答

大模型训练数据的版本管理传统 Git 完全应付不了。数据集几 TB 到几 PB,变更不是"改几行"而是"加几百万样本/过滤掉几百万样本",且数据是从多个上游经过多次处理融合而来,需要的是血缘不是 diff。

要管三个维度。第一是内容指纹——每个数据集打 content hash(SHA256 或基于 Merkle 树的数据集 hash),相同数据无论在哪 hash 一样,让"是不是用了这批数据"变成精确可查询。

第二是数据血缘 lineage。记录每个处理 step 的输入输出 hash、处理参数、代码版本、操作人、时间。从最终模型能反查到所有原始数据源。工程实现通常是元数据库记血缘图,数据本身在对象存储。

第三是训练 run 和数据的绑定。每次训练 run 启动时固化数据集 hash 和血缘指针,让模型可复现性有保障。

工具栈上小团队用 DVC + S3 就够,大规模用 LakeFS、Delta Lake、Iceberg 这类支持版本化的存储,超大规模通常是自研元数据 + 对象存储。

这套基建对合规支撑特别重要——用户数据撤回、版权审计、有害数据追溯、数据使用报告,都要靠 hash 和血缘做精确证明。合规要求往往是这个基建从 nice-to-have 变成 must-have 的推动力。

大规模时几个工程取舍:全量 hash 太贵就用元数据 hash + LSH;关键节点存快照中间步骤存引用;热数据 SSD 历史版本冷存储;血缘图大了用图数据库或专用 lineage 系统。

常见追问

  1. 数据集很大(TB 级)时全量 hash 计算成本很高,怎么做增量更新?
  2. 数据是从外部源(Common Crawl、GitHub)下载来的,每次下载内容可能不同,hash 怎么定?
  3. 跨团队/跨公司的数据交换,血缘怎么衔接?