分布式存储——让数据跑得比 GPU 快 / Distributed Storage That Keeps GPUs Fed with Data
📅 创建时间:2026-06-03 🏷️ 标签:#AI-Infra #集群侧 #存储 #Lustre #GPFS #Alluxio #数据编排 📚 前置知识:[[01-gpu-cluster-hardware]](硬件架构)[[09-cluster-operations]](集群运营) 📚 相关知识:[[../training-infra/09-training-engineering]](Checkpoint 机制)
场景:训练因为存储"饿"了 4 个小时
┌─────────────────────────────────────────────────────────────┐
│ │
│ 512 张 H100 的预训练集群,MFU 平时 60%, │
│ 今天突然降到 15%,持续了 4 小时。 │
│ │
│ 排查过程: │
│ → GPU 利用率:100%(GPU 有任务,但等数据) │
│ → NCCL 通信:正常 │
│ → 显存使用:正常 │
│ → DataLoader:等待 I/O 的时间占总时间 75% │
│ │
│ 根因定位: │
│ → 训练数据总量 100TB,存放在对象存储(S3) │
│ → DataLoader 每次请求都从 S3 拉取 │
│ → 当前有 512 个 DataLoader 同时请求 S3 │
│ → S3 带宽被打满(预期 100 Gbps,实际 12 Gbps) │
│ │
│ 教训: │
│ → GPU 再快,也怕数据跟不上 │
│ → 存储是训练 pipeline 的"第一英里" │
│ → 存储选型和数据布局是集群设计的关键 │
│ │
└─────────────────────────────────────────────────────────────┘第1节:GPU 集群存储的需求分析
训练作业的 IO 模式
┌─────────────────────────────────────────────────────────────┐
│ 训练作业的三种存储 IO 模式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 模式 1:训练数据读取(持续、高并发) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 特点: │ │
│ │ → 512 个 DataLoader 同时读取 │ │
│ │ → 小文件(tokenized dataset),随机读取 │ │
│ │ → 吞吐量要求:512 × 1 GB/s = 512 GB/s 总带宽 │ │
│ │ → 延迟要求:< 10ms(否则 DataLoader 等待) │ │
│ │ │ │
│ │ 最佳存储:分布式文件系统(Lustre / GPFS) │ │
│ │ → POSIX 接口,直接 mount │ │
│ │ → 支持高并发,横向扩展 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 模式 2:Checkpoint 写入(周期性、大块) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 特点: │ │
│ │ → 大文件(模型权重 200-800 GB),顺序写入 │ │
│ │ → 每 2 小时一次 │ │
│ │ → 写入时间目标:< 5 分钟(否则影响训练节拍) │ │
│ │ → 需要 GPUDirect Storage 加速 │ │
│ │ │ │
│ │ 最佳存储:并行文件系统 + GPUDirect RDMA │ │
│ │ → Lustre + BeeGFS(高性能 HPC 方案) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 模式 3:模型权重加载(启动时一次性) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 特点: │ │
│ │ → 单次大文件读取(模型加载) │ │
│ │ → 启动延迟敏感(作业启动到第一 step的时间) │ │
│ │ → 可以预热缓存 │ │
│ │ │ │
│ │ 最佳存储:本地 SSD + 远程文件系统 │ │
│ │ → 模型下载到本地 SSD,二次启动更快 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘存储带宽需求计算
┌─────────────────────────────────────────────────────────────┐
│ 不同规模集群的存储带宽需求 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 公式: │
│ 所需存储带宽 = DataLoader并发数 × 每loader带宽 │
│ │
│ 案例计算: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 512 卡集群,每卡 4 个 DataLoader worker │ │
│ │ → 2048 个并发读取请求 │ │
│ │ → 每请求 10 MB/s │ │
│ │ → 总带宽需求:2048 × 10 MB/s = 20 GB/s = 160 Gbps│ │
│ │ │ │
│ │ Checkpoint 需求: │ │
│ │ → 405B 模型 BF16 = 810 GB │ │
│ │ → 写入时间目标:5 分钟 │ │
│ │ → 所需带宽:810 GB / 300s = 2.7 GB/s = 21.6 Gbps│ │
│ │ → 如果启用 GPUDirect RDMA:2.7 GB/s 完全可以 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 常见存储方案带宽对比: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 存储方案 │ 聚合带宽 │ 适用场景 │ │
│ │ ─────────────────┼───────────────────┼────────────│ │
│ │ 本地 NVMe SSD │ 3-7 GB/s/节点 │ 单机缓存 │ │
│ │ Lustre(MDS+MDS)│ 50-200 GB/s │ 共享文件系统│ │
│ │ GPFS/Spectrum Scale│ 100-500 GB/s │ 企业级 HPC │ │
│ │ BeeGFS │ 100-400 GB/s │ 科研 HPC │ │
│ │ Alluxio(缓存层)│ 依赖底层存储 │ 数据编排 │ │
│ │ 对象存储 S3 │ 10-50 GB/s │ 归档/备份 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 结论: │
│ → 训练数据读取通常需要最高带宽 │
│ → S3 对象存储不适合作为训练数据直接源 │
│ → 需要 Alluxio 等缓存层加速 S3 访问 │
│ │
└─────────────────────────────────────────────────────────────┘第2节:并行文件系统
Lustre 架构
┌─────────────────────────────────────────────────────────────┐
│ Lustre 并行文件系统架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 组件: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ MDS │ │ MDT │ │ OSS │ │ │
│ │ │ 元数据 │ │ 元数据 │ │ 对象存储 │ │ │
│ │ │ 控制器 │ │ 存储 │ │ 靶 │ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ │ │ │ │ │ │
│ │ └────────────────┴────────────────┘ │ │
│ │ │ │ │
│ │ │ │ │
│ │ ┌────────┴────────┐ │ │
│ │ │ LNET 网络 │ │ │
│ │ │ (RDMA/IB/ETH) │ │ │
│ │ └────────┬────────┘ │ │
│ │ │ │ │
│ │ ┌────────────┼────────────┐ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │ Client │ │ Client │ │ Client │ │ │
│ │ │(GPU节点)│ │(GPU节点)│ │(GPU节点)│ │ │
│ │ └────────┘ └────────┘ └────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 组件职责: │
│ → MDS(MetaData Server):处理文件元数据(目录、权限) │
│ → MDT(MetaData Target):MDS 的存储后端 │
│ → OSS(Object Storage Server):存储实际文件数据 │
│ → OST(Object Storage Target):OSS 的存储后端 │
│ → LNET:Lustre 网络(支持 IB/RoCE/TCP) │
│ │
│ 扩展性: │
│ → MDS 通常 2 台(Active-Passive,高可用) │
│ → OSS 可以横向扩展(增加存储节点) │
│ → OST 数量决定聚合带宽(10 OST = 10x 带宽) │
│ │
└─────────────────────────────────────────────────────────────┘GPFS / IBM Spectrum Scale
┌─────────────────────────────────────────────────────────────┐
│ GPFS(IBM Spectrum Scale)架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 核心特点: │
│ → 企业级并行文件系统,成熟度高 │
│ → 支持 RDMA(通过 PowerAI DDN EDE) │
│ → 与 IBM 硬件(Power CPU)集成良好 │
│ → 大型 HPC 集群常用 │
│ │
│ 与 Lustre 的对比: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 维度 │ Lustre │ GPFS │ │
│ │ ────────────┼────────────────┼─────────────────────│ │
│ │ 成熟度 │ 开源 + HPC │ 企业级,IBM 商业支持│ │
│ │ 元数据扩展 │ MDS 是瓶颈 │ 分散式元数据(更强)│ │
│ │ RDMA 支持 │ 好(IB/RoCE) │ 非常好(企业优化) │ │
│ │ 多租户 │ 一般 │ 更好(Quota/ACL) │ │
│ │ 与云集成 │ 一般 │ 更好(IBM Cloud) │ │
│ │ 典型用户 │ 超算、科研 │ 企业 HPC、金融 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ GPFS 在大型 AI 集群中的使用: │
│ → Meta(Facebook)的 AI 存储基于 GPFS │
│ → 存储集群规模达 EB 级 │
│ │
└─────────────────────────────────────────────────────────────┘第3节:数据编排层 Alluxio
Alluxio 的定位
┌─────────────────────────────────────────────────────────────┐
│ Alluxio:存储与计算之间的"缓存加速层" │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题:S3 带宽不足,GPU 等待数据 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ DataLoader ──► S3 ──► 带宽瓶颈 ──► GPU 饥饿 │ │
│ │ 100 Gbps 限额 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 解法:Alluxio 作为缓存层 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ DataLoader ──► Alluxio ──► S3 │ │
│ │ ↑ │ │
│ │ │ 读取一次后缓存到本地 │ │
│ │ ↓ │ │
│ │ 本地 NVMe / 内存 │ │
│ │ ↓ │ │
│ │ 后续访问走缓存,带宽无限 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 效果: │
│ → 首次读取:走 S3,延迟略高 │
│ → 后续读取:走 Alluxio 缓存(接近内存速度) │
│ → 对于 512 卡同时读取相同数据集:只从 S3 读 1 次 │
│ │
└─────────────────────────────────────────────────────────────┘Alluxio 配置与使用
bash
# Alluxio 部署(Kubernetes)
helm install alluxio alluxio/charts \
--set master.persistence.enabled=true \
--set worker.cache.size=500G \
--set tieredstore.level0.alias=SSD \
--set tieredstore.level0.paths=/mnt/ssd \
--set tieredstore.level1.alias=HDD \
--set tieredstore.level1.paths=/mnt/hdd
# 挂载 S3 数据源
alluxio fs mount --s3Ufs s3://my-bucket/training-data /training-data
# 访问数据(和本地文件系统一样)
python train.py --data-path /training-data/dataset/
# Alluxio Web UI
# http://alluxio-master:19999
# 可以看到缓存命中率(Cache Hit Ratio)python
# PyTorch DataLoader 配置(配合 Alluxio)
from alluxio import option as alluxio_option
# Alluxio 读写配置
read_options = alluxio_option.ReadOptions(
load_metadata_only=False,
loadType="CACHE",
recursive=True
)
# DataLoader 正常使用 Alluxio 路径
# Alluxio 会自动处理缓存
train_dataset = TokenizedDataset(
root_path="/training-data/tokenized/",
# Alluxio 会自动缓存读取的文件
)Alluxio 缓存策略
┌─────────────────────────────────────────────────────────────┐
│ Alluxio 缓存策略配置 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 缓存层级: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ L1: 内存(最快,容量小) │ │
│ │ L2: SSD/NVMe(中等速度,容量中等) │ │
│ │ L3: HDD(最慢,容量大) │ │
│ │ │ │
│ │ Alluxio 自动在层级间置换(LRU) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 缓存策略选择: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 策略 │ 适用场景 │ │
│ │ ────────────────┼─────────────────────────────────│ │
│ │ CACHE_THROUGH │ 所有数据都会被重复读取 │ │
│ │ CACHE_PROMOTE │ 热数据优先缓存到内存 │ │
│ │ NO_CACHE │ 只读一次的数据,避免污染缓存 │ │
│ │ ThroughBatch │ 批量预热缓存 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 缓存预热(Warm-up): │
│ $ alluxio fs distributedLoad /training-data/dataset-a │
│ # 在作业启动前预先加载数据集到缓存 │
│ # 消除首次读取的 S3 延迟 │
│ │
└─────────────────────────────────────────────────────────────┘第4节:Checkpoint 存储策略
Checkpoint 的存储挑战
┌─────────────────────────────────────────────────────────────┐
│ 大模型 Checkpoint 的存储挑战 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Checkpoint 规模: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 模型 │ 参数量 │ FP16 大小 │ ZeRO-3 大小 │ │
│ │ ────────────┼───────────┼────────────┼──────────────│ │
│ │ GPT-3 175B │ 175B │ 350 GB │ ~50 GB │ │
│ │ LLaMA-70B │ 70B │ 140 GB │ ~20 GB │ │
│ │ LLaMA-405B │ 405B │ 810 GB │ ~115 GB │ │
│ │ GPT-4(估计)│ ~1.8T │ ~3.6 TB │ ~500 GB │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 问题 1:写入时间 │
│ → 405B 模型 ~115 GB(ZeRO-3) │
│ → 存储带宽 10 GB/s → 需要 11.5 秒 │
│ → 存储带宽 1 GB/s → 需要 115 秒 = 2 分钟 │
│ → 写入期间 GPU 暂停,影响 MFU │
│ │
│ 问题 2:存储空间 │
│ → 每天 3 次 Checkpoint × 115 GB = 345 GB/天 │
│ → 保留最近 3 天 = 1 TB 存储需求 │
│ → 多团队多作业 = 存储快速耗尽 │
│ │
│ 问题 3:读取时间(故障恢复) │
│ → 从 115 GB 恢复,1 GB/s 带宽 = 115 秒 │
│ → 训练中断后需要等待 2 分钟才能恢复 │
│ │
└─────────────────────────────────────────────────────────────┘Checkpoint 存储优化策略
┌─────────────────────────────────────────────────────────────┐
│ Checkpoint 存储优化策略 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 策略 1:异步非阻塞保存 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ DeepSpeed Checkpointing: │ │
│ │ → 训练和保存并行,保存期间 GPU 不暂停 │ │
│ │ → 使用单独 CPU 线程处理 IO │ │
│ │ → MFU 不受影响(除非存储带宽严重不足) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 策略 2:分布式 Checkpoint(分片保存) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ DeepSpeed ZeroDraft/ZeroCheckpoint: │ │
│ │ → 每个 Rank 保存自己的 shard(~20 GB / 8 卡) │ │
│ │ → 并行写入,带宽聚合 │ │
│ │ → 512 卡集群:512 并行写入,总带宽 = 512x单卡 │ │
│ │ → 115 GB / 10 秒 = 11.5 GB/s │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 策略 3:GPUDirect Storage(RDMA 直存) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ BlueField DPU + Lustre: │ │
│ │ → GPU HBM → DPU RDMA → 存储服务器 │ │
│ │ → 绕过 CPU,存储带宽提升 5-10x │ │
│ │ → 115 GB / 2 秒 = ~60 GB/s │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 策略 4:分层存储 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 热存储(SSD):最新 3 个 Checkpoint │ │
│ │ 冷存储(S3/归档):历史 Checkpoint │ │
│ │ → 自动沉降(每小时检查一次) │ │
│ │ → 节省成本,保留历史可追溯 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘Checkpoint 清理策略
python
# Checkpoint 清理脚本(定时任务)
import os
import time
import shutil
CHECKPOINT_DIR = "/mnt/lustre/checkpoints"
MAX_KEEP = 5 # 保留最近 5 个
MIN_AGE_HOURS = 2 # 至少 2 小时前的才清理
def cleanup_old_checkpoints():
"""清理过期的 Checkpoint"""
dirs = []
for d in os.listdir(CHECKPOINT_DIR):
full_path = os.path.join(CHECKPOINT_DIR, d)
if os.path.isdir(full_path):
mtime = os.path.getmtime(full_path)
age_hours = (time.time() - mtime) / 3600
dirs.append((full_path, mtime, age_hours))
# 按时间排序,保留最新的
dirs.sort(key=lambda x: x[1], reverse=True)
for full_path, mtime, age_hours in dirs[MAX_KEEP:]:
if age_hours >= MIN_AGE_HOURS:
print(f"Removing old checkpoint: {full_path}")
shutil.rmtree(full_path)
else:
print(f"Skipping (too new): {full_path}")
if __name__ == "__main__":
cleanup_old_checkpoints()第5节:JuiceFS——云原生的 POSIX 分布式存储
JuiceFS 架构
┌─────────────────────────────────────────────────────────────┐
│ JuiceFS:云原生 POSIX 分布式文件系统 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 架构: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌────────────┐ │ │
│ │ │ JuiceFS │ ← 元数据引擎 │ │
│ │ │ Metadata │ 支持 Redis / TiKV / MySQL / ... │ │
│ │ └──────┬─────┘ │ │
│ │ │ │ │
│ │ ┌──────┴─────┐ │ │
│ │ │ JuiceFS │ ← 对象存储(数据块) │ │
│ │ │ Object │ 支持 S3 / COS / OSS / MinIO / ... │ │
│ │ └────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ FUSE / Kubernetes CSI / HDFS SDK / S3 Gateway │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────────┼────────────┐ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │ Client │ │ Client │ │ Client │ │ │
│ │ └────────┘ └────────┘ └────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 优势: │
│ → POSIX 兼容,现有代码无需修改 │
│ → 对象存储后端(便宜、弹性) │
│ → 本地缓存(NVMe SSD)加速热数据 │
│ → Kubernetes 原生支持(CSI Driver) │
│ → S3 兼容接口(可直接用 boto3 访问) │
│ │
│ 限制: │
│ → 元数据延迟比 Lustre/GPFS 略高 │
│ → 超大规模场景(小文件极多)需要调优 │
│ │
└─────────────────────────────────────────────────────────────┘升华:数据是燃料,存储是管道
┌─────────────────────────────────────────────────────────────┐
│ 存储工程的哲学 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 存储是训练 pipeline 的"第一英里": │
│ → GPU 再快,数据喂不进去也是白搭 │
│ → 存储带宽 = 训练吞吐量的天花板 │
│ │
│ 2. 没有银弹,只有权衡: │
│ → S3 便宜但慢 → 需要 Alluxio 缓存 │
│ → Lustre 快但贵 → 需要合理规划 OST 数量 │
│ → JuiceFS 弹性但有小文件限制 → 需要预分片 │
│ │
│ 3. Checkpoint 是训练的"存档点": │
│ → 存储速度决定 checkpoint 频率 │
│ → checkpoint 频率决定故障最大损失 │
│ → 每 2 小时保存一次 = 最多损失 2 小时训练 │
│ │
│ 一句话总结: │
│ 存储选型是 GPU 集群的"隐性成本": │
│ 存储选对了,GPU 跑满;选错了,GPU 饿着。 │
│ │
└─────────────────────────────────────────────────────────────┘"AI 可查 vs 必须理解"清单
AI 可查:
✅ Lustre MDS/OSS 的具体安装步骤
✅ Alluxio 的完整配置参数
✅ JuiceFS CSI Driver 的 Kubernetes 部署 YAML
✅ GPFS 的具体命令和 API
必须理解:
🔴 训练作业的三种 IO 模式(数据读取 / Checkpoint / 模型加载)
🔴 Lustre 架构(MDS + OST)和扩展方式
🔴 Alluxio 作为缓存层的工作原理(S3 → 缓存 → DataLoader)
🔴 Checkpoint 存储的三大挑战(写入时间 / 存储空间 / 恢复时间)
🔴 分布式 Checkpoint(分片保存)的原理和优势
🔴 为什么 S3 不能直接作为训练数据源(带宽不足)学习状态:🟡 开始学习