本系列文章分为五篇
- 分布式存储的战争(一)大数据的基石-HDFS的崛起
- 分布式存储的战争(二)对象存储的挑战 MinIO/SeaweedFS/Ceph
- 分布式存储的战争(三)存算分离的必然性-Alluxio/JuiceFS数据编排层
- 分布式存储的战争(四)AI的咆哮-GPFS/Deepseek 3FS 并行文件系统
- 创作中
书接上回:
1. 算力困局:GPU利用率上不去是为何
在计算机的世界里,总是存在着一个铁律:廉价设备适配昂贵设备,慢速设备适配快速设备。
传统非 LLM 模型以机器学习与浅层深度学习为主,训练与推理大多依赖 CPU 即可完成,仅少量视觉、小网络模型会用到中低端 GPU。这类模型计算量小、参数量低,对算力和文件系统等系统的要求不高。而大模型LLM的参数量以Billion来计算,传统的CPU和中低端GPU根本无法在人类可以容忍的时间范围内完成。这使得我们不得不使用高端GPU如A100/H100来训练模型。
对于类LLM模型训推来说,最昂贵的设备当属GPU。一个A100约8万~15万以上。一次模型训练涉及数百~数万个GPU,总价上千万起步(据粗略估算,GPT-4的GPU成本在30亿人民币左右)。所以在模型工程中,所有设备和系统都是围绕着GPU这位大哥转。
GPU利用率低是ML Infra工程师头疼的一个问题。通常来说,在模型训练中拖累GPU利用率的头号元凶就是数据加载和预处理阶段。在没有大量优化的情况下,一个Epoch(使用所有训练样本进行一次模型训练的过程)中可能会有60%~70%的时间都花在数据读取和预处理的阶段。假如你能通过优化提升10%的GPU利用率,那资源节省就是上百万起步。
为什么传统文件系统或者对象存储会拖累GPU呢?因为他们的读取速度太慢了。模型训练典型的数据读取模式是多个节点并行读取不同的文件。到这里,一种高性能的文件系统就呼之欲出了。在工业界,大家常常称它为Paralllel File System并行文件系统。事实上,当前还有全闪文件系统,本文不做过多讨论。
2. 为AI模型而生的文件系统需求
模型训练推场景对文件系统提出了更高的要求,比如高吞吐读、低延迟写、高IOPS、海量小文件无瓶颈等等。但在计算机软件的世界里是没有“银弹”的,一个系统不可能解决所有问题,必须要进行权衡,舍弃一些不那么重要的需求,而满足核心诉求。
分阶段来看模型训推对于文件系统的需求:
- 数据收集阶段:支持海量小文件。典型场景比如自动驾驶领域,一张图片就是一个文件,一张图片百KB,总的训练数据多达几十亿、甚至一百亿文件。
- 数据读取和预处理阶段:
- 需要支撑高吞吐读取。模型训练的典型特征就是多个节点并行读取多个不同的文件。大模型训练每秒需读取上百 GB 或TB级的训练样本(tokens)。若存储系统无法持续提供高带宽,GPU 将会饿死,算力利用率骤降。传统分布式存储的吞吐扩展存在天花板(如HDFS的NameNode 限制),导致计算节点 “喂不饱”,GPU 利用率常低于 40%。
- 高性能随机读能力。一般场景下,模型训练在读取样本时需要进行随机采样shuffle读取,以确保模型不会过拟合特定顺序的数据。这种随机性意味着数据访问不是连续的,而是分散在整个数据集中的。比如先大量喂猫的图片然后再大量喂狗的图片,模型可能会对狗过拟合。
- 模型训练阶段:瞬时顺序写吞吐。模型训练需要将某一时刻的模型参数快照 (checkpoint) 保存下来,千卡集群训练一次 checkpoint 可达 百GB/TB 级。在保存checkpoint时,GPU会停止训练,若保存耗时过长,会大幅影响GPU的利用率。(业界实践通常先保存至CPU内存,然后再异步保存至文件系统)
- 模型加载阶段:需要强一致性。在load checkpoint文件到模型时,假如文件系统不是最终一致的,会有可能导致模型加载的checkpoint不一致,导致推理训练崩溃。
- 模型编写阶段:需要兼容POSIX接口。因为当前不论是TensorFlow还是PyTorch都是基于POSIX接口。
- 模型推理阶段:大容量低延迟的KV cache。KVCache 是一种用于优化 LLM 推理过程的技术。它通过缓存先前 token 的 key 和 value 向量来避免冗余计算。虽然GPU的HBM和CPU的内存是最常用的KV cache方案,但当两者不够用时,还是期望能够有类似于CPU的L3 cache类的KV cache(HBM相当于L1,CPU RAM相当于L2)。
此外,模型训推所依赖的文件系统是典型的读多写少场景。
下面两节内容,我们一起来深入看看老牌HPC领域的并行文件系统GPFS和当前火热的Deepseek的并行文件系统。
3. GPFS (IBM Spectrum Scale):老牌HPC领域的佼佼者
GPFS全称是General Parallel File System,现在官方改名叫 IBM Spectrum Scale,是IBM商用闭源的并行文件系统,诞生于上世纪90年代。最早用于高性能计算HPC领域,在AI大模型爆发后呈现出“枯木逢春”的态势。由于是闭源系统,所以只能通过公开资源对其进行有限的分析。
GPFS支持多种连接方式,包括Storage Area Network(一种比较古老的大容量存储实现方式)、Network Shared Disk、Shared Nothing Cluster(SNC)。其中Shared Nothing Cluster (SNC)是目前最主流的部署模式,尤其是在大规模集群和云环境中。在 SNC 模式下,每个节点拥有独立的存储,不依赖外部的共享存储阵列。这种架构具有极高的扩展性和成本效益,通过 GPFS 自身的数据冗余来保证数据的高可用性。下面的内容是基于SNC模式的。
GPFS是一个非常复杂的系统,这里只挑一些重点来分析。
3.1 整体架构
下图展示了GPFS的架构,可能你第一眼看上去它像是一个无主架构,因为找不见传统Master-Slave架构的Master节点。但别被外表迷惑了,GPFS 不是无主架构。它允许管理角色与数据存储复用一个节点,不强制使用独立专有管理节点,但仍然存在明确的中心化组件,包括集群管理器、文件系统管理器、锁管理器等。这些节点承担中心化管理功能,因此节点之间并非完全对等,部分节点扮演管理者角色。从架构本质上看,GPFS 属于 Master-Slave 中心化架构,与 HDFS 等主流分布式系统的设计思路一致。
如上图所示,GPFS把底层存储抽象为Network Shared Disk(以下简称NSD),每个节点用自己的本地盘,并把自己声明为NSD的提供者,一般使用NVMe盘,一个节点可以挂载多个NSD。GPFS不依赖linux文件系统,而是直接在裸金属上构建。
由于GPFS中的客户端是有状态的,所以一个GPFS集群可以分Server Side和Client Side分开来看,可以认为客户端和服务端共同组成了GPFS集群。
Server Side角色
- Cluster Manager:负责监控节点状态并管理集群的节点加入、下线,选举File System Manager。有状态节点,全局只有一个Active Cluster Manager。当节点宕机后,剩余的quorum节点负责选举出新的Cluster Manager。
- File System Manager:用于管理文件系统的quota、磁盘空间分配,并且用于分配哪个Token Manager管理哪一部分文件块的Token。有状态节点,一个文件系统分配一个File System Manager。当File System Manager宕机时,由Cluster Manager触发选举一个继任者。
- Token Manager:Token指的是锁,Token Manager是分布式锁管理器。Token Manager管理一部分文件对应的锁(实际上是byte-range锁,下面会介绍),用于控制元数据和文件的一致性,也是有状态节点。一个集群会有多个Token Manager,用于均摊锁操作压力。如果Token Manager宕机,File System Manger会将该Token Manager上的管理权限分配给其他Token Manager。
- NSD Worker:实际工作线程,处理网络共享磁盘的 IO 请求。
- CCR:Clustered Configuration Repository,类似数据库的角色。基于 Paxos 的集群配置仓库,分布式强一致存储所有节点、磁盘、文件系统与集群配置,是整个集群的权威配置来源。
Client Side角色
- Metanode:是每个被打开的文件动态分配的一个节点。一个Metanode负责维护一部分文件的元信息,收集其他节点对该文件的更新,并将文件元信息保存至NSD。这样可以尽可能分摊文件元信息管理压力。所有客户端节点都可以作为Metanode。
- Cache:GPFS为了提高性能,会将文件元数据和文件数据内容缓存在客户端(读写缓存);缓存就意味着会有一致性问题, GPFS借助上面介绍的分布式锁实现缓存的一致性。
由于客户端持有锁和写缓存等数据,所以客户端是有状态的。客户端的加入和退出都需要Cluster Manager和File System Manager进行协调,做一些状态回收的操作。
GPFS把所有功能都放在了一个叫做mmfsd(multi-media file system daemon)的进程里,通过配置来实现不同的角色,包括实现客户端和服务端角色。
3.2文件条带化与Failure Group
条带化(striping)指的是将一个文件按照固定大小(如1MB)切分成数据块,将数据块按照一定规则(比如 round-robin、failure group)分散存储到多个磁盘(NSD)上。
这样做的核心目的是:
- 负载均衡:避免单个磁盘成为瓶颈,所有磁盘均匀分担 I/O。
- 并行访问:单个大文件读写时,能同时从多个磁盘拉取/推送数据,从而实现并行 I/O,聚合带宽,大幅提升读写吞吐量和性能。
如上图所示,一个文件被切分为多个数据块,不同副本数据放在不同的Failure Group内。
Failure Group是用户手动定义的一组磁盘(NSD),这些磁盘被认为“属于同一个故障域”。如果一个故障域整体失效(例如同一个机架、同一电源、同一交换机等),这个故障组内的所有磁盘都可能同时不可用。GPFS 使用 Failure Group 来确保数据副本或纠删码片段不会放在同一个故障域内,从而实现更高的容错能力。当 replication factor = 2 或 3 时,GPFS 会强制每个块的多个副本分布到不同的 Failure Group。
3.3 缓存与强一致保证
GPFS为实现高性能并行 I/O会尽可能将元数据与数据文件缓存至客户端节点中,包括:
- 元数据:包括inode信息(size、mtime、atime 等)、block allocation map(文件数据块分布关系)等。
- 数据文件:缓存文件的实际内容至RAM的page pool。
以上数据都可能会被缓存在多个节点中,GPFS采用如下机制保证POSIX 语义下的强一致性:
- 当某个节点需要访问/修改某个文件或元数据对象时,它会向 Token Manager 请求相应的 token(读 token、写 token、byte-range token 等)。此时Token 持有者节点可以安全地在本地缓存数据/元数据,并进行读写。
- 当出现冲突访问(例如另一个节点要写同一文件/同一 byte range,或修改同一元数据)时,Token Manager 检测到冲突,向当前持有冲突 token 的节点发送 revoke 请求。被 revoke 的节点必须将脏页flush到磁盘,并使本地对应的缓存失效。完成上述操作后,向 Token Manager 确认释放 token。
3.4 元数据代理Metanode与一致性保证
对于 GPFS 文件系统中的每个文件或目录,系统会动态选择一个客户端节点作为其 Metanode。这个 Metanode 负责处理该文件/目录的所有元数据操作,包括inode 更新(文件大小、修改时间、权限等属性)、目录操作(创建/删除/重命名子项)、扩展属性(xattr)。
具体规则是第一个打开该文件/目录的客户端作为该文件/目录的Metanode。Metanode 不是永久的,如果 Metanode 节点故障、下线,或负载过高,系统会自动迁移 Metanode 到另一个节点。
这种设计非常巧妙,有两点值得Highlight下:
- 它解决了传统分布式文件系统中元数据瓶颈的问题。不同于集中式元数据服务器(如 Lustre 中的 MDS),GPFS将元数据管理分摊至成百上千个客户端节点中。这种设计还能在一定程度上避免热点问题,根据局部性原理,通常情况下活跃文件节点也是对应Metanode是访问最频繁的节点。
- 将元数据管理和数据管理解耦:Metanode 不直接管理数据块(用户数据),而是专注于元数据;数据一致性由 Token Manager 的 byte-range token 处理。这是一种解耦设计,让元数据和数据路径独立优化。
Metanode 的这种设计体现了 GPFS 的“shared-nothing”哲学:无中心化瓶颈,一切动态分布,适应大规模集群。
为什么不是NSD来做这个角色呢?我认为有两方面原因,一方面客户端节点更多,可以将压力均摊的更均匀;另一方面,通常文件的写入和读取通常满足本地局部性,即本地节点读取该文件的概率更高,这样可以避免额外的网络请求,操作基本在本地完成。
3.5 读流程简述
客户端A请求一个block文件的流程如下:
- 申请读锁:
- 客户端A查看是否已经持有读锁,如果已有,且 token 权限足够(read / caching allowed),且数据已在 page pool 缓存 → 直接从本地 page pool 返回。若token不够或没有,则查看本地缓存或请求File System Manager以确定该文件inode的Token Manger。
- 请求Token Manger获取读锁。Token授予后,客户端A可以缓存文件数据。
- 获取文件元数据和物理布局:这部分数据由Metanode维护,如上述所说,一个文件的元数据只由一个Metanode维护。客户端检查本地缓存或请求对应的Metanode获取元数据。如果Metanode未知,则请求Token Manager获取Metanode对应的节点ID,或者客户端A称为该文件的Metanode。请求Metanode获取文件元数据和文件物理布局。
- 读取文件:客户端根据block map请求对应的NSD获取文件数据,数据通过RDMA网络直接传输至GPU节点的CPU RAM或者使用GPUDirect Storage 直接传输至GPU的VRAM。客户端A会缓存文件内容数据至page pool。
3.6 Byte-range Token细粒度分布式锁
在AI场景下,文件读取的并发度非常高,而GPFS是需要用锁来保障强一致性的,锁是并发的天敌。想要在有锁的情况下实现高并发,一种常用的方法是将锁的粒度设置的更细。比如Java JDK1.7 中ConcurrentHashMap的分段锁。
GPFS采用的思路就是将锁的粒度设置的更细,其锁不是文件级别的,而是byte-range级别的。举个例子,当Node A写入一个文件的0~100MB,而Node B读取或写入100MB~200MB,NodeA和NodeB会获取不同的锁,保证互不干扰。这就实现了多个GPU节点同时读取或写入一个文件的不同部分。
具体来说:
- 第一个访问文件的节点通常会拿到整个文件范围的 token(0 到 ∞)。
- 当出现并发写时,token manager 会协调拆分范围,只 revoke(收回)冲突的部分,让不同节点持有不同 byte-range 的写 token。
- 这实现了真正的高性能并行写同一个文件(只要写范围不重叠,基本无锁冲突)。
这种设计使得 GPFS 在无并发写共享时表现得像整文件锁一样高效,而在有并发写时又能自动降级为细粒度 byte-range 锁。
3.7 写流程简述
- 申请写锁:
- 与读流程类似,客户端A查看是否已经持有写锁。若没有或锁范围不够,则查看本地缓存或请求对应的文件的Token Manager。
- 请求Token Manager获取byte-range写锁。如果此时有节点持有读锁,Token Manager会收回这些节点的锁,这些节点必须将写缓存flush到磁盘,并将相关元数据缓存和数据缓存置为失效,用以保证强一致。
- 文件切分:客户端将文件切分为固定大小的块(如1MB)。客户端向File System Manager请求分配新块NSD列表。
- 数据写入:客户端 A 可在本地 page pool 分配 buffer并写入数据,实际写入NSD磁盘是异步的。
- 元数据更新:写操作会改变元数据,这些更新不直接写入NSD磁盘,而是发送给当前文件的Metanode。Metanode收集所有节点的元数据更新,合并后定期或在token被revoke时协会到NSD中。元数据的可靠性由类似HDFS的Journals保证。
3.8 总结
GPFS能够完美胜任大规模AI模型分布式训练,主要得益于几点设计:
- 高效的条带化(Striping) :这个特性是能够支撑大规模的并行读写 I/O,提高集群的聚合带宽吞吐。
- Metanode + 分布式元数据管理:元数据管理和瓶颈是分布式系统的难题,而GPFS将元数据的压力分散到成千上万个客户端上,而不像传统的分布式系统将元数据集中到少数几个服务器上。在大规模集群下元数据不会成为瓶颈。
- Byte-range token 机制(细粒度分布式锁):锁粒度可以细到字节范围(byte-range),而不是文件级或 inode 级。这就允许多个 GPU 节点同时并发写入同一个 checkpoint 文件的不同部分。
- 此外,还包括POSIX 完整语义、GPUDirect Storage (GDS) 支持 + RDMA 直通等特性。
4. Deepseek 3FS:AI 原生存储新贵
DeepSeek 3FS(Fire-Flyer File System)是 DeepSeek 开源的、专为大规模 AI 训练与推理设计的高性能分布式并行文件系统,核心目标是榨干SSD 与 RDMA 带宽,解决大模型训练的 I/O 瓶颈。
4.1 整体架构
如图所示,Deepseek 3FS的设计相比于GPFS要简单一些。主要由4部分构成。
- Cluster Manager:集群中控,管理集群中的节点。其中Meta Service、Chunk Storage Service、Client均通过周期性租约机制维持在线状态,一旦这些节点状态有变化,由 Cluster Manager 负责通知到整个集群;并负责处理有状态节点的资源(比如关闭Client上的文件打开状态)。
- Meta Service:管理文件元数据,响应客户端写入请求,为chunk分配物理存储节点。元数据存储至独立的FundationDB 集群,FoundationDB 本身是一个支持事务的分布式 KV存储。Meta Service是无状态的,负责将 POSIX 定义的目录树操作翻译成 FoundationDB 的读写事务来执行。不过Meta Service这一层可以去掉,将逻辑封装在客户端,这样可以将元数据请求的路径缩短。
- Storage Service:提供数据存储服务,每个存储节点管理本地 SSD 存储资源,提供读写能力;每份数据 3 副本存储,采用的链式复制协议 CRAQ(Chain Replication with Apportioned Queries)提供 write-all-read-any 语义,对读更友好;系统将数据进行分块,尽可能打散到多个节点的 SSD 上进行数据和负载均摊。
- Client:提供 FUSE Client 以实现 POSIX 兼容性,以及 Native Client API 用于高性能零拷贝操作。Native Client 提供的是 SDK 接入方式,应用需要改造代码才能使用,但性能相比 FUSE 客户端可提升 3-5 倍。
以上所有组件均接入RDMA on InfiniBand。3FS还依赖外置的Zookeeper/etcd集群来实现Cluster Manager选主。
4.2 元数据可靠性保证 - FoundationDB
在分布式高性能文件系统的设计中,元数据的设计是十分关键的。一方面原因是分布式文件系统通常都有支持海量文件的需求,这就导致元数据的数量就比较庞大;另一方面元数据的查询和变更是比较频繁的操作,如何能够降低查询成本以及保证元数据变更的一致性也是一个复杂的问题。
如今的分布式文件系统在设计时,大多开始采用第三方分布式高性能KV存储系统作为元数据的存储方,如 JuiceFS。
Deepseek 3FS也是这么做的,3FS依赖了FoundationDB以大幅降低文件系统元数据的设计和实现成本,复杂性都交由数据库解决。FoundationDB提供了Serializable Snapshot Isolation(可串行化快照隔离)事务隔离级别,既有高性能,也能保证严格可串行化(Serializable)的强一致性。在这个基础上,3FS的元数据操作都可以认为是串行化执行的,所以就不存在一致性问题。
4.3 CRAQ算法解决副本一致性问题
CRAQ全称是Chain Replication with Apportioned Queries,翻译成中文是带有分摊查询功能的链式复制算法。想要理解CRAQ,先看看其前身CR(Chain Replication)的工作原理。
通常保证数据可靠性所采用的方案是冗余复制多份。由于数据存在多份,数据的一致性又是一个问题。而CR算法就是在多副本的情况下解决数据一致性问题。
CR将一组副本组织成一个线性链条,如副本1 -> 副本2 -> 副本3。这个链条拥有一个唯一的头节点 (Head)和一个唯一的尾节点(Tail),在前面的例子中副本1就是Head,副本3就是Tail。为了保证一致性,CR规定:写请求均由Head处理,然后顺序传导至后续节点,直至Tail节点。Tail节点的写入完成代表写操作在存储系统中提交,此时向客户端返回确认。对于读操作,所有请求均由Tail节点处理。通过这种串型化提交和读取只能由Tail节点处理,保证了数据的强一致性。但缺点也是显而易见的,由于读时只能有一个副本节点提供服务,无法承载过大的读压力。
这时CRAQ祭出了它AQ来提高读能力,即分摊查询。CRAQ规定读取时可以从任意节点中读数据,所损失的一致性通过如下机制来保证:
- 链式写入标记:写入请求顺序仍是Head -> Tail,不同的是Head->Tail的过程中,写入的数据会被标记为dirty。当Tail写入完成后,Tail的数据被标记为clean。此时执行Tail -> Head的反向传播,将刚刚提交的数据被标记为clean。
- 读时检查与请求转发:当所有副本的标记都为clean,所有副本均可以直接提供读服务。当部分副本的标记是dirty时且dirty副本接收到了读请求,该副本将请求转发至Tail节点,因为Tail节点的数据始终都是clean的。
可以看到CRAQ是牺牲写入性能来换取读吞吐,这在模型训练场景是一个不错的trade-off。
4.4 文件存储与条带化
3FS中的文件通过条带化切分并组织成Chain来存储。
- 文件条带化:目的是最大化读写吞吐。3FS将文件切分为chunk(默认是512KB),ChunkID = {inode编号}_{chunk_index},chunk_index 由文件偏移量 / Chunk 大小计算得出,这样使得无需存储chunk元数据,避免查询数据库。ChunkID全局唯一。
- Chain:是一组 CRAQ 存储节点构成的复制链(chunk_副本1 -> chunk_副本2 -> chunk_副本3),每个 Chunk 永久归属一条 Chain。多个不同文件的 Chunk,或同一个文件的不同 Chunk,都可能被哈希映射到同一条 Chain。
文件切分后的chunk和存储chain的分布如下所示。此处采用stripe_width=3的配置,代表一个文件的所有chunk会被分配到3个chain中(实际场景中的stripe_width比较大,比如3FS官方采用200)。chain_list=[2, 1, 3]代表,该文件的chunk会被轮流写至chain id为2, 1, 3中。比如chunk0写入chain2,chunk1写入chain1,...,chunk7写入chain1。
Meta service对应的FoundationDB会存储一个文件对应的chain_list。chain_list的顺序是2, 1, 3而不是1, 2, 3的原因是3FS期望将数据打散至存储中,这样不仅能够避免数据存储的不平衡问题,也能够在读写时压榨带宽,最大化读写吞吐。
4.5 近乎无锁的设计
传统的文件系统对于读写和写写都会进行加锁,以避免脏读和写冲突。比如上面提到的GPFS是用中心化的分布式锁来解决,HDFS通过只允许追加写 + 元数据延迟更新 + 租约机制来规避并发问题。
AI 模型训练的文件操作模式几乎不会碰到写写冲突,甚至读写冲突也很少见,所以3FS的设计取舍非常明确:放弃对同一文件高并发读写的 POSIX 完整支持,通过“无锁”设计换来了极致的吞吐和极简实现。
具体来说,3FS 在写写冲突和读写一致性上依赖以下机制:
- 对于写写冲突来说,3FS通过FoundationDB的事务冲突检测和写重试,以及Chunk粒度的轻量级互斥锁实现。具体来说,3FS 将一致性分为两层
- chunk数据:在写入数据时需要先获取chunk lock,然后按照CRAQ链式复制(write-all)完成所有副本写入,再释放锁。这保证了同一个 chunk 的多个并发写被串行化,避免数据覆盖或中间状态。
- 元数据:通过FoundationDB的事务冲突检测和重试解决。多个 meta service 并发改同一个文件属性(如 append 后更新 size)会产生事务冲突 → FDB 检测到冲突 → 自动重试。
- 考虑交替写的场景,假如A先写chunk,在还未提交元数据时,B写chunk,B写chunk前需要先获取chunk锁,所以A和B的写入也是串行化的,对于元数据更新,A和B总有一个会遇到冲突,FoundationDB会重试更新,保证两个也是串行化的。
- 对于读写冲突来说,3FS在chunk级别通过CRAQ避免了脏读问题,但元数据比没有保证强一致,而是保证最终一致。因为保证强一致需要使用分布式锁或者版本号来解决。
上述流程并没有分布式锁的介入,但代价是当极高并发写同一文件时,冲突检测和重试会使资源的使用率上升进而使得性能恶化。但AI场景下并没有这种高并发写的场景,所以这个是可接受的。
4.6 缓存数据的一致性
3FS 是一个比较特殊的文件系统,因为它几乎只用在AI训练时计算节点中的模型批量读取样本数据这个场景上,通过高速的计算存储交互加快模型训练。这是一个大规模的随机读取任务,而且读上来的数据不会在短时间内再次被用到,因此我们无法使用“读取缓存”这一最重要的工具来优化文件读取,即使是超前读取也是毫无用武之地。 因此,3FS的实现也和其他文件系统有着比较大的区别。 —— 摘自3FS官方博客
由于上述原因,客户端不会保存读缓存和写缓存。但为了避免元数据的频繁访问,3FS会在客户端缓存元数据文件。该缓存元数据文件的不保证强一致,而是通过定期使客户端元数据缓存过期来达到最终一致。
5. 并行文件系统PFS到底在设计什么?从传统存储到「存网一体」
GPFS和3FS虽然都是适配AI模型训练的并行文件系统,抛开RDMA、GPUDirect Storage、NVMe等硬件技术应用,从软件设计上看它们两个似乎是两个物种。
- 比如对于元数据保存和处理,GPFS将元数据的处理分摊至客户端Metanode并将元数据存储至自有NSD中,而3FS将元数据的处理交给专有的无状态Meta Serivce组件并将元数据存储至第三方FoundationDB中;
- 再比如对于读写冲突和写写冲突的处理,GPFS使用全局分布式锁来保证强一致,而3FS使用Chunk粒度的锁+CRAQ算法+FoundationDB的冲突检测与重试来保证一致性;
- 还有对于缓存数据方面,GPFS支持数据和元数据的缓存并通过锁撤销使得客户端缓存失效来保证强一致,而3FS只支持元数据缓存并通过缓存过期来提供最终一致性。
- 设计复杂度:GPFS 作为全场景通用文件系统,内部子系统极其复杂;3FS 针对 AI 训练深度定制,舍弃大量通用功能,整体更简单、更专注。
GPFS 代表“通用 + 强一致”的传统思路,3FS 则代表“AI 专用 + 高吞吐 + 简化一致性”的新一代思路。两者都能适配大模型训练,这也能够说明解决问题的办法不止一种,可以是多种多样的。
基于上面的设计分析,可以提炼一些指导未来 AI 存储系统设计的方法论:
- 文件条带化以提供并行读写能力。
- 尽量无锁化或乐观并发(基于版本/epoch + 重试),牺牲部分强一致换取吞吐,尤其适合 checkpoint、日志、数据集加载等 workload。
- 如果不能无锁,那就尽量分摊元数据,让不变的元数据跟随数据存储,元数据存储上只存储必要的元数据,让元数据体积小,尽量不做分布式事务,以提高性能。
- 适配RDMA摒弃网络流程中不必要的数据复制流程(磁盘-> 内核态内存 -> 用户态内存 -> 内核态内存 -> 网卡 -> 网卡 -> 内核态内存 -> 用户态内存)。
最后,我们回归本源,当前AI模型所需要的文件系统不是“能存数据”的系统,而是“能喂饱GPU”的系统。它不再是HDFS那样的存算一体,不是对象存储为了做极致的弹性存储,也不是数据缓存层那样通过缓存提高吞吐,而是「存网一体」(存储和网络一体),即在高速的存储和网络硬件下(RDMA、CXL、NVMe-oF)提供极致的吞吐能力。 从 2006 年 HDFS 的诞生,到如今 2026 年的今天,分布式文件存储领域已走过了波澜壮阔的二十年。在这段历程中,技术不断推陈出新,架构几经迭代,只为适应数据洪流下层出不穷的新需求。在下一篇中,我们将回望这跨越二十年的技术演进之路,试图从历史的脉络中提炼出那些能够指导未来系统设计的底层方法论。(关注可获得及时推送)。