使用 Git 管理代码是常见做法,但将同样的方式直接应用于 LLMOps 中常见的数TB数据集和数GB模型检查点时,很快就变得不实际。标准的 Git 仓库并非为有效处理这类大型二进制文件而设计。克隆仓库会变得慢得难以接受,由于 Git 对大型文件的历史追踪,存储成本会大幅增加,并且性能会显著下降。这需要专门的策略来对这些大型资源进行版本管理,同时保持可复现性和可追溯性。
目标与标准 MLOps 保持一致:将特定版本的代码、数据和模型关联起来,以确保实验和部署可复现。但是,机制必须适应所涉及的规模。
标准 Git 为何表现不佳
Git 通过存储文件快照来追踪变更。对于文本文件(如源代码),它在压缩和存储差异(增量)方面很有效。然而,对于大型二进制文件(数据集、模型权重 (weight))来说:
- 仓库膨胀: 直接存储在 Git 中的大型文件的每个版本都会显著增加仓库大小。即使是微小的改动也可能导致存储近似重复项。
- 性能下降:
git clone、git checkout 和 git push/pull 等操作涉及传输这些大型文件,导致极长的等待时间,特别是在分布式团队或 CI/CD 系统中。
- 差异比较低效: Git 的增量压缩算法对二进制文件通常无效,这意味着即使是小改动也会存储几乎完整的副本。
大型文件版本管理方法
已出现多种方法来应对这些挑战,通常涉及将大型文件存储在 Git 仓库之外,同时将元数据或指针保存在 Git 内部。
1. Git 大型文件存储 (LFS)
Git LFS 是 Git 的一个扩展,它将仓库中的大型文件替换为小型文本指针。实际的大型文件存储在单独的 LFS 服务器上(可以自行托管,或托管在 GitHub、GitLab 等平台)。
- 工作方式: 在
git add 期间,LFS 会拦截大型文件(根据配置),将它们存储在 LFS 服务器上,并向 Git 仓库添加一个指针文件。在 git checkout 或 git pull 期间,LFS 使用指针文件从 LFS 服务器下载对应的大型文件。
- 优点: 与现有 Git 工作流相对顺畅地集成。用户与 Git 命令的交互基本保持不变。得到 Git 托管平台的广泛支持。
- 缺点: 仍然依赖 Git 来管理指针文件,这会增加开销。性能可能严重依赖于 LFS 服务器的带宽和位置。本身不管理数据管道或依赖项。可能会根据 LFS 服务器的存储和带宽使用情况变得昂贵。对于超大型(多 PB)数据集或大型文件更新非常频繁的场景,可能无法有效扩展。
2. 数据版本控制 (DVC)
DVC 是一款专门为数据版本管理、机器学习 (machine learning)管道管理和实验追踪而设计的开源工具。它与 Git 一同运行。
- 工作方式: DVC 将数据集和模型的元数据(通常是包含哈希值和远程存储位置的小型
.dvc 文件)存储在您的 Git 仓库中。实际的数据文件存储在外部存储(S3、GCS、Azure Blob、HDFS、本地存储等)中,使用内容寻址存储。这意味着文件通过其内容哈希值识别,避免重复。dvc add 追踪文件/目录,dvc push 将其上传到远程存储,dvc pull 则根据当前 Git 提交中的 .dvc 文件下载相应数据。
- 优点: 明确为数据版本管理而设计。存储无关。通过内容哈希有效去除数据重复。集成管道定义(DAGs)和指标追踪。对大型数据集扩展性良好。
- 缺点: 在 Git 之外引入了一个单独的工具和命令(
dvc add、dvc push、dvc pull),需要一定的学习曲线。工作流程与纯 Git/LFS 略有不同。
演示了 Git 如何追踪代码和 DVC 元数据文件,同时 DVC 根据 .dvc 文件中存储的内容哈希值管理大型文件在远程存储之间的推送/拉取。
3. 湖仓表格式 (Delta Lake, Apache Iceberg, Apache Hudi)
这些格式常用于数据湖仓中,主要为 Spark、Trino 或 Flink 等引擎管理的大型表格数据集提供 ACID 事务、版本控制(时间旅行)和模式演变能力。
- 工作方式: 数据以 Parquet 等格式存储在底层对象存储(如 S3)中,但表格式会维护事务日志,定义表在不同时间点的状态。您可以查询特定时间戳或版本下的表状态。
- 优点: 非常适合管理训练中使用的、不断演变的大型结构化或半结构化数据集。提供原子性和一致性。能够轻松查询历史数据状态。与数据处理生态系统良好结合。
- 缺点: 主要为表格类数据设计,不太适合直接对非结构化数据块或单一模型检查点进行版本管理(尽管关于它们的元数据可能存储在表中)。需要与兼容的查询引擎集成。
4. 本地对象存储版本管理
云对象存储服务(AWS S3、Google Cloud Storage、Azure Blob Storage)提供内置的版本管理能力。
- 工作方式: 启用后,修改或删除对象会创建一个新版本,而非覆盖旧版本。每个版本都有唯一的 ID,并且可以列出和恢复以前的版本。
- 优点: 启用简单。由存储提供商管理。提供基本的版本回溯能力。
- 缺点: 版本管理与对象本身绑定,如果没有额外追踪机制,无法明确关联到代码提交。管理代码版本与特定对象版本之间的关系通常需要手动操作或定制工具。如果生命周期策略(例如,删除旧版本)未正确配置,可能导致显著的存储成本。不提供复杂的差异比较或管道感知能力。
5. 文件仓库和元数据追踪
MLOps 平台和实验追踪工具(MLflow、Weights & Biases、Neptune.ai、Vertex AI Experiments、SageMaker Experiments)通常包含文件追踪功能。
- 工作方式: 这些工具会记录与特定实验运行或代码版本关联的文件(数据集、模型)。它们存储元数据,将运行 ID(通常与 Git 提交关联)与文件位置(例如 S3 URI)以及可能的哈希值关联起来。
- 优点: 将版本管理与更广泛的实验管理环境结合。提供用户界面以浏览文件及其关联的运行/指标。灵活。
- 缺点: 侧重于记录和关联,而非直接的版本控制操作(如数据差异比较或合并)。严重依赖训练/评估脚本中一致的记录习惯。可能不像 DVC 等专用工具那样严格执行版本管理。
模型检查点版本管理
模型检查点,特别是对于大型基础模型,可能从数十到数百 GB 甚至更大。其挑战类似于大型数据集的版本管理。
- 常用方案: Git LFS、DVC 和文件仓库是经常使用的方案。如果中间检查点共享层,DVC 的内容寻址存储特别有利。
- 检查点策略: 对长时间训练运行中的每个检查点都进行版本管理可能过多。常见的策略是对以下内容进行版本管理:
- 初始预训练 (pre-training)模型(如果适用)。
- 中间检查点(例如,基于评估指标提升或定期生成)。
- 最终训练模型。
- 从基础模型派生的微调 (fine-tuning)模型版本。
- PEFT 考量: 参数 (parameter)高效微调 (PEFT) 方法(如 LoRA)生成的适配器权重 (weight)小得多(兆字节而非千兆字节)。这些使用标准 Git 或 Git LFS 更容易进行版本管理。但是,您仍然需要对它们所适用的巨型基础模型进行版本管理或可靠引用。您的版本管理系统必须同时追踪基础模型版本和适配器版本。
推荐做法
- 分离代码和文件: 仅将 Git 用于代码和配置文件。对于大型数据和模型文件,使用专用方案(LFS、DVC、对象版本管理 + 元数据追踪)。
- 使用内容哈希: DVC 等工具运用内容哈希,能自然地去除相同文件的重复,节省存储空间和带宽。
- 标签和约定: 建立清晰的命名约定,并使用 Git 标签标记 (token)与生产发布中使用的特定数据集版本或训练模型对应的主要提交。
- 自动化: 将文件版本管理命令(
dvc push/pull、git lfs push/pull)集成到 CI/CD 管道中,以确保一致性和可复现性。将 CI/CD 运行明确地与 Git 提交和所使用/生成的文件版本关联起来。
- 元数据的重要性: 即使使用更简单的对象存储版本管理,也要维护元数据,将代码提交与用于训练或部署的特定对象版本关联。实验追踪工具在此处具有重要价值。
- 存储生命周期管理: 实施管理远程存储中旧文件版本的策略以控制成本,特别是在使用本地对象存储版本管理或 Git LFS 时。决定历史版本需要保留多久。
选择正确的方法取决于团队的工作流程、数据/模型的规模、现有基础设施(云提供商、本地部署)以及对集成管道功能的需求。通常,Git 用于代码,再结合 DVC 等工具或谨慎使用文件仓库,就能为 LLMOps 提供一个解决方案。