从数据块到区块:加速模型中心的上传和下载

发布于 2025 年 2 月 12 日
在 GitHub 上更新

基于内容的数据分块 (Content-defined chunking, CDC) 技术在实现 Xet 支持的代码仓库内的数据去重中扮演着核心角色。其思路很简单:将每个文件的数据分解成数据块 (chunk),只存储唯一的数据块,从而获得收益。

在实践中,情况更为复杂。如果我们只关注最大化数据去重,设计上会要求数据块的尺寸尽可能小。但这样做会给基础设施和模型中心的构建者带来巨大的开销。

在 Hugging Face 的 Xet 团队,我们正在将 CDC 从理论应用到生产中,为 AI 构建者提供更快的上传和下载速度(在某些情况下可提速 2-3 倍)。我们的指导原则很简单:为构建和迭代模型与数据集的团队实现快速实验和协作。这意味着我们的关注点不仅仅是数据去重;我们还在优化数据在网络中的传输方式、存储方式以及整个开发体验。

扩展数据去重技术的现实挑战

想象一下将一个 200GB 的代码仓库上传到模型中心。目前,有多种方法可以实现,但都采用了以文件为中心的方法。为了给模型中心带来更快的文件传输速度,我们开源了 xet-corehf_xet,这是一个与 huggingface_hub 集成的库,它使用了一种用 Rust 编写的、基于数据块的方法。

如果你考虑一个包含唯一数据块的 200GB 代码仓库,这意味着在支持所有代码仓库的内容寻址存储 (CAS) 中将有 300 万个条目(每个数据块约 64KB)。如果上传了新版本的模型,或者在代码仓库中创建了一个包含不同数据的分支,那么会增加更多的唯一数据块,从而使 CAS 中的条目数量激增。

模型中心上 200 万个模型、数据集和 Space 仓库的总数据量接近 45PB,纯粹基于数据块的方法可能会产生 6900 亿个数据块。仅使用数据块来管理如此庞大的内容量是不可行的,原因如下:

  • 网络开销:如果每个数据块都单独下载或上传,每次上传和下载都会产生数百万个请求,这会使客户端和服务器都不堪重负。即使是批量查询,也只是将问题转移到了存储层。
  • 基础设施开销:一个简单地单独跟踪数据块的 CAS 将需要数十亿个条目,这将导致像 DynamoDBS3 这样的服务产生高昂的月度账单。在 Hugging Face 的规模下,这个成本会迅速累积。

简而言之,在你等待文件传输的同时,网络请求会激增,数据库难以管理元数据,而协调每个数据块的成本也急剧上升。

大规模数据去重技术的设计原则

这些挑战带来了一个关键的认识:

数据去重是一种性能优化手段,而非最终目标。

最终目标是改善构建者在模型和数据集上迭代与协作的体验。从客户端到存储层的系统组件不需要保证数据去重。相反,它们将数据去重作为众多工具之一来辅助实现这一目标。

放宽数据去重的约束后,我们自然而然地得出了第二个设计原则:

避免使用与数据块数量成 1:1 扩展的通信或存储策略。.

这是什么意思?我们通过聚合来实现扩展。

通过聚合扩展数据去重技术

聚合将数据块进行分组,并以巧妙(且实用)的方式智能地引用它们,从而带来益处:

  • 区块 (Blocks):我们不直接传输和存储数据块 (chunks),而是在去重后将数据捆绑成最大 64MB 的区块 (blocks)。区块仍然是内容寻址的,但这将 CAS 条目减少了 1000 倍。
  • 分片 (Shards):分片提供了文件和数据块之间的映射(同时引用区块)。这使我们能够识别文件的哪些部分发生了变化,并引用过去上传时生成的分片。当数据块已知存在于 CAS 中时,它们会被跳过,从而减少不必要的传输和查询。

区块和分片共同带来了显著的优势。然而,当有人上传新文件时,我们如何知道某个数据块是否之前已经上传过,从而避免不必要的请求呢?为每个数据块执行网络查询是不可扩展的,并且违背了我们上面提到的“无 1:1”原则。

解决方案是关键数据块 (key chunks),它们是所有数据块中通过一个基于数据块哈希的简单取模条件选出的 0.1% 的子集。我们为这些关键数据块及其所在的分片提供了一个全局索引,这样当查询该数据块时,会返回相关的分片以提供本地去重。这使我们能够利用空间局部性原理。如果一个关键数据块在某个分片中被引用,那么很可能其他相似的数据块引用也在同一个分片中可用。这进一步提高了数据去重效率,并减少了网络和数据库请求。

Key chunks

聚合数据去重技术在实践中的应用

模型中心目前存储了超过 3.5PB 的 .gguf 文件,其中大部分是模型中心上其他模型的量化版本。由于量化的性质(将值限制在较小的整数范围内并进行缩放),量化模型为数据去重提供了一个有趣的机会。这限制了权重矩阵中值的范围,自然导致了更多的重复。此外,许多量化模型的代码仓库存储了多个不同的变体(例如,Q4_K、Q3_K、Q5_K),它们之间有大量的重叠。

一个很好的实践例子是 bartowski/gemma-2-9b-it-GGUF,它包含了 google/gemma-2-9b-it 的 29 个量化版本,总计 191GB。为了上传,我们使用与 huggingface_hub 集成的 hf_xet 在本地执行数据块级别的去重,然后在区块级别聚合和存储数据。

上传后,我们可以开始看到一些有趣的模式!我们提供了一个可视化图表,显示了每个区块的去重率。区块颜色越深,表示其部分内容在不同模型版本中被引用的频率越高。如果你访问托管此可视化的 Space,将鼠标悬停在任何热力图单元格上,会以橙色高亮显示所有模型中对该区块的所有引用,而点击单元格将选择所有其他共享该区块的文件。

Quantization deduplication visualization

单个去重区块可能只节省几 MB 的存储空间,但正如你所见,有许多重叠的区块!这么多区块加起来,节省的空间就非常可观了。Xet 支持的 gemma-2-9b-it-GGUF 仓库版本并没有上传 191GB,而是存储了 1515 个唯一区块,总计约 97GB 到我们的测试 CAS 环境中(节省了约 94GB)。

虽然存储上的改进很显著,但真正的好处在于这对模型中心的贡献者意味着什么。在 50MB/s 的速度下,数据去重优化意味着上传时间减少了四个小时;速度提升了近 2 倍。

仓库 存储大小 上传时间 @ 50MB/s
原始 191 GB 509 分钟
Xet 支持 97 GB 258 分钟

同样,本地数据块缓存显著加快了下载速度。如果一个文件被更改,或者添加了一个与本地数据块缓存有大量重叠的新量化版本,你将不必重新下载任何未更改的数据块。这与基于文件的方法形成对比,后者必须下载整个新的或更新的文件。

总而言之,这表明了本地数据块级去重与区块级聚合如何不仅极大地简化了存储,还优化了在模型中心上的开发体验。通过提供这种文件传输效率,AI 构建者可以更快地行动,快速迭代,而不用担心遇到基础设施瓶颈。对于任何向模型中心推送大文件的人(无论你是在推送新的模型量化版本还是更新的训练集),这都有助于你将重点转移到构建和共享上,而不是等待和排查问题。

我们正在加紧工作,将在未来几周和几个月内推出首批由 Xet 支持的仓库!在此过程中,我们将发布更多更新,将这些速度带给模型中心的每一位构建者,让文件传输变得无感。

在模型中心上关注我们,了解我们的更多进展!

社区

很高兴看到 Xet 支持的仓库正在模型中心上推出。

📻 🎙️ 嘿,我为这篇博客文章制作了一个播客,快来听听吧!

该播客由 ngxson/kokoro-podcast-generator 生成,使用了 DeepSeek-R1 和 Kokoro-TTS

我们如何通过数据块哈希找到数据块内容?CAS 系统只存储“区块哈希 -> 区块内容”,那么数据块到区块的映射在哪里?

分片存储了什么?是“文件名, 分片ID, 数据块哈希, 区块哈希”吗?

·

我们如何通过数据块哈希找到数据块内容?

数据块哈希是通过基于内容的数据分块 (CDC) 计算的,这意味着如果两个数据块内容相同,它们的哈希值也相同。CDC 消除了存储数据块哈希 -> 数据块内容映射的需要,因为我们知道如果两个数据块哈希相同,它们的内容也必然相同。

CAS 系统只存储“区块哈希 -> 区块内容”,那么数据块到区块的映射在哪里?

这在上面博文的“关键数据块”部分有解释。基本上,我们利用文件中的空间局部性,只存储了数据块 -> 区块映射的一个很小的子集。试图存储每个数据块 -> 区块的映射很快就会变得不切实际。

分片存储了什么?是“文件名, 分片ID, 数据块哈希, 区块哈希”吗?

你可以将分片理解为存储文件(通过文件哈希识别)到构成该文件的数据块列表的映射。

希望这能更好地解释我们底层技术!

如果我们不存储每个数据块哈希到区块哈希的映射,我们如何下载一个模型文件?例如,如果一个模型没有关键数据块,分片信息只包含“文件哈希 -> 数据块列表”,而 CAS 只有“区块哈希 -> 区块内容”,我们怎么知道需要哪个区块?

·

很好的问题 :)

例如,如果一个模型没有关键数据块

关于以上这点,可能需要澄清两点:

  1. 并非所有文件都会包含关键数据块;这纯粹是一种优化。
  2. 关键数据块用于上传路径上的数据去重,而不是用于下载。它们让我们能够查看首次上传到仓库的文件是否在全局存储中已有任何内容。这使我们能够在整个存储范围内进行数据去重。

至于你的另一个问题

如果我们不存储每个数据块哈希到区块哈希的映射,我们如何下载一个模型文件

下载模型文件时,会向我们的服务发出一个带有文件哈希的请求。这个哈希会映射到一个区块子范围列表。逻辑上这些是数据块,但通过存储偏移量,我们节省了元数据存储,并且最终许多偏移量会在一个区块内共享边界(允许我们在响应中将它们组合在一起),这在向客户端发送内容时提供了好处。

文章作者

如果你正在阅读这篇文章并想亲自动手尝试,请加入等待列表以获取访问权限! https://huggingface.co/join/xet

注册登录 发表评论