在AI训练和推理系统中,存储架构很少只是“选一个最快的磁盘”这么简单。真正影响GPU利用率的,是数据从持久化存储一路进入GPU显存的完整路径:数据集存在哪里,计算节点如何读取,网络需要传输多少数据,本地是否有缓存,文件格式是否适合并行读取,数据预处理是否成为瓶颈,以及多个训练任务同时访问同一批数据时,存储系统能否维持稳定吞吐。
因此,对象存储和分布式文件系统并不是简单的“两个存储设备叠加”。更合理的设计,是根据数据生命周期和访问模式,让不同存储层承担不同职责。对象存储通常适合作为大规模数据集、原始数据、训练数据集、检查点以及归档数据的持久化底座;分布式文件系统则更适合为计算集群提供POSIX文件语义、低延迟访问、共享目录以及需要文件系统接口的软件环境;计算节点上的本地NVMe则负责进一步缩短热数据到GPU之间的距离。
典型的数据访问路径可以理解为:对象存储保存权威数据集,计算集群通过对象存储协议或者数据访问层读取数据,数据进入训练节点之后经过本地缓存和预取,再通过主机内存、Pinned Memory以及PCIe等路径进入GPU显存。对于采用分布式文件系统的环境,则可能变成对象存储作为数据湖和持久化层,分布式文件系统作为计算集群的共享文件层,本地NVMe作为每个训练节点的高速缓存层。具体采用哪一种路径,取决于训练框架、数据格式、网络架构以及应用是否真正需要POSIX语义。
这里首先需要纠正一个很常见的概念混淆。CephFS确实使用MDS,也就是Metadata Server,处理文件系统元数据,但CephFS的数据并不是简单地“由MDS管理元数据,然后把数据块放到外部S3对象存储”。CephFS的数据和元数据都属于Ceph集群体系,只是数据对象通常由Ceph的RADOS层负责存储,而MDS负责文件系统命名空间和元数据服务。Ceph还提供RGW,可以通过S3兼容接口访问RADOS对象存储。这两种接口可以同时存在,但不能把CephFS、RADOS和外部S3混成一个简单的上下层关系。
如果采用Amazon S3、Google Cloud Storage、Azure Blob Storage或者阿里云OSS等对象存储作为训练数据底座,数据通常按照对象而不是传统文件系统中的目录和块来组织。所谓“目录”更多是对象Key的命名结构,而不是传统POSIX文件系统中的真实目录树。对象存储的优势在于容量扩展、持久性、生命周期管理和大规模并发访问,而不是提供一个让所有训练进程随意执行POSIX open()、rename()、fcntl()等操作的本地文件系统。
AI训练数据尤其适合根据访问方式设计对象粒度。最麻烦的一种情况,是把数亿个很小的图片、JSON或者文本文件直接丢进对象存储,然后让数千个训练worker随机读取。此时瓶颈可能不是对象存储的原始带宽,而是大量小对象请求造成的请求数、元数据操作、连接管理和网络往返开销。
因此,大规模训练通常会考虑把大量小文件重新组织成适合顺序和并行读取的数据格式,例如Parquet、WebDataset/TAR、RecordIO或者训练框架适合的数据分片格式。具体格式取决于数据类型和训练框架。对于图像、视频、文本等数据,最重要的不是追求一个所谓“最佳分片大小”,而是让单个训练worker能够持续获得足够大的连续数据块,同时避免一个巨型文件成为并行读取的瓶颈。
分片大小也不能简单地按照“随机读取就小、顺序读取就大”一刀切。真正需要观察的是单个样本大小、worker数量、并发读取数、网络带宽、对象存储请求延迟、解压CPU成本以及训练batch的组成。如果分片过小,请求和元数据开销增加;如果分片过大,随机访问浪费的读取数据更多,而且多个worker之间可能产生热点。最佳粒度必须通过实际工作负载测试确定。
对象存储与分布式文件系统组合时,一个非常实用的架构是把数据生命周期拆开。原始数据和经过处理的权威数据集放在对象存储中,训练节点通过数据处理或挂载层访问;对于需要POSIX语义的训练程序,可以在计算集群中使用CephFS、Lustre、BeeGFS等分布式文件系统;训练节点本地再配置NVMe缓存,用于存放近期使用的数据分片、预处理结果和临时文件。这样每一层都有明确职责,而不是让一个系统承担所有工作。
其中本地NVMe缓存往往比增加一个Redis Cluster更加符合AI训练的实际需求。训练数据通常是大文件或大数据块,真正需要缓存的是数据本身,而不是把数十GB甚至数TB的数据块全部塞进Redis。Redis更适合元数据、任务状态、短期索引、分布式协调或者某些小型热数据场景,而不是作为通用的AI训练数据缓存层。
训练节点的缓存设计也不能只看“缓存命中率”。假设缓存命中率达到90%,但一次miss需要从远端读取数百GB数据,那么少量miss仍然可能让GPU长时间等待。相反,如果数据miss本身只需要几百毫秒,而GPU计算时间很长,缓存带来的收益可能并不明显。因此应该同时观察GPU idle time、训练step time、DataLoader等待时间、远端存储吞吐、网络利用率、缓存命中率以及cache miss penalty。
更成熟的实现通常会把缓存和预取结合起来。训练程序知道接下来需要哪些数据分片时,可以提前将数据从对象存储或者共享文件系统读取到本地NVMe,然后在当前batch计算期间进行下一批数据的异步读取。这样CPU、网络、存储和GPU计算可以形成流水线。目标并不是让某一个存储设备达到最高IOPS,而是尽可能让GPU计算阶段不要因为数据供应不足而停顿。
例如,一个训练节点可能同时进行远程数据读取、CPU解码、数据增强、Pinned Memory缓存以及GPU计算。如果数据加载线程已经把GPU喂满,那么继续增加NVMe缓存容量可能几乎没有收益;如果GPU利用率只有50%,而DataLoader长期处于等待状态,此时才应该继续向存储和数据管道寻找瓶颈。
对象存储与分布式文件系统之间也不一定需要实时同步元数据。原文提出使用Kafka同步元数据,再通过etcd实现跨存储系统的一致性和锁控制,这种设计对于绝大多数AI训练数据集属于过度复杂,而且引入了新的故障点。训练数据通常可以采用不可变版本管理:数据集生成一个明确版本,完成后作为只读数据集提供给训练任务。这样计算节点只需要读取指定版本,不需要不断协调“这个文件刚刚被修改了吗”。
对于需要频繁变化的数据,例如实时推荐、在线特征、持续写入的数据湖,则可以采用事件流、消息队列和元数据服务。但这属于另一类数据系统问题,不能因为AI集群使用对象存储和分布式文件系统,就默认需要Kafka和etcd维持所谓的跨层元数据一致性。
对于训练数据,最有价值的元数据往往包括数据集版本、分片清单、样本数量、标签版本、预处理程序版本、tokenizer版本以及数据校验信息。训练任务启动时读取一个manifest,就可以知道这一轮训练究竟使用了哪些数据。这种方式比让多个存储系统之间持续同步目录变化更容易保证可重复性。
推理系统的数据路径又有所不同。大型模型推理通常首先需要解决模型权重加载问题。模型权重可以持久化在对象存储或者共享文件系统中,但真正执行推理时,活跃模型权重必须进入GPU显存或者根据具体架构进入CPU内存及其他offload层。不能把“模型热数据缓存到NVMe”理解成NVMe可以替代GPU显存。NVMe更适合作为模型加载缓存、checkpoint存储或者内存不足时的offload层,而不是GPU计算阶段的高速工作内存。
推理服务还经常需要缓存tokenizer、模型配置、动态batch相关数据以及部分应用层数据。对于LLM推理,KV Cache则属于GPU或CPU内存管理问题,其用途与训练数据缓存完全不同。把KV Cache、模型权重缓存和训练数据缓存统称为“缓存层”,会掩盖完全不同的性能特征。
所谓“动态路由”也不能简单设计成“一个数据块每秒访问超过1000次,就自动迁移到对象存储”。如果数据已经位于对象存储,热点数据通常应该向更靠近计算节点的位置移动,例如本地NVMe缓存或者高速共享文件系统,而不是从缓存层迁移回对象存储。数据迁移方向应该由数据生命周期、访问频率、容量成本和访问延迟共同决定,而不是一个固定访问次数阈值。
一个比较合理的策略是建立明确的数据温度层。权威数据保存在持久化对象存储;正在使用的数据进入共享高速文件系统或者节点本地缓存;近期频繁访问的数据尽可能留在本地NVMe;训练结束后产生的checkpoint根据恢复需求保存到持久化存储。这样热数据靠近计算,冷数据回到低成本持久化层,而不是让所有数据都永久停留在昂贵的高速存储中。
如果采用分布式文件系统作为计算层,还需要特别注意元数据压力。AI训练集如果由数千万甚至数亿个小文件组成,即使底层SSD拥有极高吞吐,MDS或者其他元数据服务也可能先达到瓶颈。此时增加更多存储盘未必有效。减少小文件数量、重新打包数据、合理设计目录结构以及降低元数据操作频率,往往比简单增加磁盘数量更加有效。
网络则是整个访问路径中经常被低估的一环。100GbE理论线速约为12.5GB/s,200GbE约为25GB/s,400GbE约为50GB/s,但这些数字只是理论带宽,并不等于应用可以获得的有效吞吐。实际性能还受到协议开销、网络拓扑、交换机、TCP或RDMA配置、存储服务器数量、并发连接、对象请求模式以及客户端CPU处理能力影响。
对于大规模AI集群,如果训练节点通过高速网络访问共享存储,可以考虑RDMA、RoCE或InfiniBand等方案,但不能把NVLink当成普通服务器之间的存储网络。NVLink主要用于GPU之间以及特定GPU与CPU/内存拓扑中的高速互连,和存储集群使用的以太网、InfiniBand或其他网络体系不是一个概念。
安全方面,对象存储和分布式文件系统也应该分别设计权限边界。对象存储可以使用IAM、Bucket Policy、临时凭证、KMS以及服务角色控制访问;分布式文件系统则根据具体产品使用POSIX权限、ACL、Kerberos、客户端认证等机制。不要把“IP白名单”当成文件系统安全的主要模型,也不要把服务器端加密和网络传输加密混为一谈。
例如S3 Server-Side Encryption解决的是对象在存储端如何加密的问题,而HTTPS/TLS解决的是客户端到对象存储服务之间传输过程中的保护。AWS Direct Connect则主要提供专用网络连接,并不天然等于“加密隧道”。如果业务要求网络层加密,还需要根据具体架构采用适当的IPsec、TLS或者其他安全机制。
数据完整性同样不能简单依赖MD5。对于文件传输和对象存储,可以利用服务提供的ETag、校验和以及更现代的checksum机制,根据具体API和存储产品选择适合的完整性校验方案。MD5在某些非安全用途上仍可能用于错误检测,但它不应该被描述成现代系统中的通用密码学完整性保障方案。
性能指标也不能直接写成“混合架构可以把延迟降低到5ms”或者“吞吐提升60%”。这些数字高度依赖硬件、网络、数据格式、访问模式、缓存命中率和并发规模,没有普适性。AI存储系统真正应该关注的是端到端指标,例如每秒训练样本数、tokens/s、GPU有效利用率、step time、数据加载等待时间、存储吞吐、P95/P99读取延迟、缓存命中率以及网络利用率。
对于训练任务,还应该把“存储吞吐”和“训练吞吐”区分开。一个存储系统可以达到几十GB/s,但如果CPU解码速度只有5GB/s,或者数据增强成为瓶颈,GPU仍然可能吃不饱。反过来,一个训练任务只需要2GB/s有效数据供应时,部署几百GB/s的存储系统也没有意义。最终应该根据实际训练工作负载确定存储规模,而不是追求一个漂亮的存储带宽数字。
因此,一个成熟的AI数据访问架构通常不是“对象存储加分布式文件系统再加Redis、Kafka、etcd,然后动态迁移数据”这么复杂。更清晰的设计是让对象存储承担持久化数据底座,让分布式文件系统在确实需要共享文件语义和高并发文件访问时承担计算存储层,让本地NVMe承担节点级缓存和预取,再通过高带宽网络把数据供应给计算节点。
对于需要POSIX接口的训练框架,可以使用CephFS、Lustre、BeeGFS等合适的分布式文件系统;对于原生支持对象存储的数据管道,则可以直接从S3兼容对象存储读取,减少不必要的中间层。对于大量小文件,应优先从数据格式和分片方式解决问题;对于GPU等待数据,则重点分析本地缓存、预取、网络和数据预处理;对于容量和长期保存,则让对象存储发挥优势。
最终判断一个AI存储架构是否合理,不应该看部署了多少种存储产品,而应该看从“数据集”到“GPU计算”之间的数据路径是否短、是否稳定、是否能够并行,以及当计算规模扩大以后是否还能保持可预测的性能。对象存储负责把数据可靠地保存下来,分布式文件系统负责在需要时提供高并发共享访问,本地NVMe负责把热点数据尽可能拉近GPU,网络负责把这些层连接起来。每一层都有明确边界,系统才容易扩展,也容易定位真正的瓶颈。
对象存储与分布式文件系统如何组合使用,AI 数据访问路径应该怎样设计
图片说明:示意图 图片来源:Public Domain(公有领域)
喜欢这篇报道?
使用下面的功能,方便以后继续阅读和分享 MNewsTV
关于文章收藏
收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。
删除收藏请进入「我的收藏」进行管理。
捐助(Paypal): https://www.paypal.me/observeccp 订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP