4. 存储子系统集成

在AI训练集群中,计算与网络往往最先被关注,但真正决定GPU利用率能否持续跑高的,常常是存储子系统。一个典型的千亿参数模型训练任务,每个step都要读取数GB到数十GB的训练样本,同时周期性写入checkpoint。如果存储带宽不足或延迟抖动,GPU就会在数据加载阶段空转,算力被白白浪费。本节围绕NVMe本地盘、分布式块/对象存储、并行文件系统三条路径,讲清它们在AI训练中的定位、配置要点与调优方法。

一、三层存储的定位与数据流

AI训练的数据访问具有明显的层次性:热数据(当前epoch正在读取的样本)需要极高吞吐和低延迟;温数据(完整训练集)需要大容量与较高聚合带宽;冷数据(原始归档、历史checkpoint)则更看重成本。对应地,存储通常分为三层:节点内NVMe用于缓存与临时数据、分布式存储提供共享容量、并行文件系统承载高并发训练读取。下图给出三者在训练数据流中的位置与典型带宽量级。

GPU 计算节点(训练进程) DataLoader 读取样本 → 前向/反向 → 周期性写 checkpoint 需求:高吞吐 + 低延迟 ① 节点内 NVMe 本地缓存 / 临时数据 单盘读 ≈ 3–7 GB/s 延迟 ≈ 80–150 μs ② 并行文件系统 训练集高并发读取 聚合带宽 100+ GB/s POSIX 语义 / 条带化 ③ 分布式存储 共享容量 / 对象存储 checkpoint 归档 容量 PB 级 / 成本低 预取/缓存 归档/回读 关键判断:训练是否被存储拖慢? • GPU 利用率周期性掉坑 → 数据加载瓶颈,优先查并行文件系统读带宽与元数据延迟 • checkpoint 写入时间过长 → 分布式存储写带宽不足,考虑异步写或分层落盘 • 小文件随机读多 → 元数据服务压力大,需调整条带大小或改用打包格式(如 WebDataset) • 本地 NVMe 命中率低 → 缓存策略或预取窗口设置不合理

二、NVMe本地盘的配置要点

节点内NVMe在AI训练中主要承担两类角色:一是作为并行文件系统的客户端缓存,二是作为checkpoint的本地暂存区。配置时需关注以下几点。首先是文件系统选择,XFS在大文件顺序读写上表现稳定,适合checkpoint暂存;ext4在小文件场景更成熟。其次是挂载参数,建议使用 noatime,nodiratime 减少元数据写入,队列深度通过 nr_requests 调整,NVMe设备通常可设为1024。第三是I/O调度器,NVMe应使用 none(即无调度),避免内核调度引入额外延迟。

# 查看NVMe设备与调度器
nvme list
cat /sys/block/nvme0n1/queue/scheduler
# 设置为none
echo none > /sys/block/nvme0n1/queue/scheduler
# 挂载XFS并优化参数
mkfs.xfs /dev/nvme0n1
mount -o noatime,nodiratime,logbsize=256k /dev/nvme0n1 /mnt/nvme0

在缓存场景中,常用做法是用NVMe盘做本地读缓存,配合并行文件系统的预取机制。例如将最近一个epoch的样本缓存在本地,命中率可达60%–80%,显著降低对后端文件系统的压力。需要注意的是,缓存一致性由上层框架维护,训练开始前应确保缓存目录已预热。

三、分布式存储与并行文件系统的协同

分布式存储(如Ceph、MinIO等对象存储)通常提供S3兼容接口,适合存放原始数据集和归档checkpoint。并行文件系统(如Lustre、GPFS、BeeGFS)则提供POSIX语义和高聚合带宽,适合训练时的直接读取。两者并非替代关系,而是分工:训练热数据放并行文件系统,冷数据和归档放对象存储。

并行文件系统的调优核心是条带化(striping)。条带大小(stripe size)和条带数(stripe count)决定了单个文件在多个OST/存储目标上的分布方式。对于大样本文件(如单张图像>1MB),建议条带大小1–4MB、条带数4–8;对于小文件密集场景,应先将小文件打包成tar或WebDataset格式,再以大文件方式存储,避免元数据成为瓶颈。

参数大文件场景小文件打包场景说明
stripe size1–4 MB4–16 MB单次I/O在单个OST上的连续块大小
stripe count4–88–16文件分布的OST数量
readahead16–32 MB32–64 MB客户端预读窗口
max_rpcs_in_flight816并发RPC数,影响吞吐

四、性能调优的实操路径

调优应从测量开始,而不是凭感觉改参数。第一步,用 fio 在单节点上测NVMe和并行文件系统挂载点的顺序读带宽与随机读IOPS,建立基线。第二步,在真实训练脚本中打开DataLoader的耗时统计,观察每个step的数据加载时间占比。第三步,根据瓶颈位置调整参数。

# fio 测试并行文件系统顺序读带宽
fio --name=seqread --directory=/mnt/lustre/data \
    --rw=read --bs=1M --size=10G --numjobs=8 \
    --iodepth=16 --group_reporting

# 观察训练中数据加载耗时(PyTorch示例)
# 在DataLoader中设置 num_workers=8, prefetch_factor=4
# 并用 torch.profiler 查看 dataloader 时间占比

常见调优手段包括:增大 num_workers 与 prefetch_factor 以重叠I/O与计算;使用 pin_memory=True 加速主机到GPU的传输;对checkpoint采用异步写入,先写本地NVMe再后台同步到分布式存储,避免阻塞训练。若发现元数据延迟高,可启用并行文件系统的元数据缓存或增加MDT节点。

调优顺序建议:先确认瓶颈在存储而非计算或网络,再区分是带宽不足还是延迟过高。带宽不足优先调条带与并发,延迟过高优先查元数据和缓存命中。

五、checkpoint写入策略

大模型checkpoint动辄数十GB,同步写入会阻塞训练数十秒。推荐策略是:训练进程将checkpoint先写入本地NVMe(速度可达数GB/s),再由后台进程异步上传到分布式存储。这样训练几乎无感知,代价是需要额外的本地空间和一致性管理。若必须直接写分布式存储,应使用多线程分块上传,并设置合理的超时与重试。

注意:异步checkpoint需保证在训练崩溃时本地暂存文件可恢复。建议保留最近2–3个checkpoint在本地,并记录写入完成标记,避免恢复时读到不完整文件。

综上,存储子系统的集成不是简单挂载几块盘,而是要根据训练数据访问模式,把NVMe、并行文件系统和分布式存储组织成有层次的流水线,再通过条带、预取、并发和异步写入等手段,让GPU尽可能不因等数据而空转。判断调优是否成功的标准很直接:训练step时间稳定、GPU利用率曲线平滑、checkpoint写入不再造成明显停顿。