
思否收录 · 深度学习系统工程系列
当人们谈论大模型训练时,聚光灯永远打在GPU上——H100、A100、算力、FLOPS。存储系统像一个不起眼的"幕后工作者",默默承载着一切,却极少被提及。
但现实是残酷的:在大模型训练中,存储系统的性能直接决定了GPU的利用率。一个存储瓶颈,可以让价值百万美元的GPU集群闲置30%以上的算力。
这不是危言耸听。
以GPT-4级别的训练为例(约1.8万亿参数,13万亿tokens),训练过程中的数据读取、Checkpoint保存、日志写入,对存储系统的压力是惊人的:
如果你只懂GPU而忽略存储,就像买了一辆法拉利却给它装了个摩托车的油箱——跑不起来。
本文将从工程实战角度,系统梳理AI分布式存储的架构设计、技术选型、性能调优和踩坑经验,全文约7000字,来自多个万卡集群的实战总结。
很多人把AI存储简单理解为"大容量共享盘",这是致命的认知偏差。
特征 | 传统企业存储 | AI训练存储 |
|---|---|---|
访问模式 | 随机读写为主 | 顺序大块读 + 小文件随机读 |
I/O大小 | 4K-64K混合 | 1MB-100MB大块顺序读(数据加载)+ 小文件(元数据) |
并发模式 | 数十到数百客户端 | 数千到数万GPU并发访问 |
读写比例 | 读写混合 | 读远大于写(训练时),写突发(Checkpoint) |
元数据压力 | 中等 | 极高(海量小样本文件) |
训练生命周期 存储需求特征
─────────────────────────────────────────────────────
│
▼
数据准备阶段 → 大容量、高吞吐写入
(数据清洗/预处理) 持续数天到数周
│
▼
训练阶段 → 高带宽读取(TB/s级)
(模型迭代) 低延迟元数据操作
│ 高并发(数千GPU同时读)
▼
Checkpoint阶段 → 突发大块写入
(模型保存) 极高带宽(网络和存储都要扛住)
│
▼
推理/部署阶段 → 低延迟读取
QPS优先在AI训练场景中,有三个关键指标直接决定存储系统是否"合格":
一个残酷的事实:很多团队花数百万买GPU,却在存储上省几十万,结果GPU利用率只有60%。这相当于白白浪费了40%的算力投资。
方案 | 代表产品 | 优势 | 劣势 | 适用规模 |
|---|---|---|---|---|
并行文件系统 | Lustre、GPFS、BeeGFS | 性能最强、元数据优秀 | 运维复杂、成本高 | 千卡以上 |
分布式对象存储 | Ceph、MinIO | 容量大、成本低 | 延迟高、小文件性能差 | 数据湖/备份 |
AI原生加速层 | WekaIO、VAST Data、JuiceFS | 架构新、云原生友好 | 相对较新、生态待验证 | 中等规模 |
经过多轮选型和压测,我们最终选择了Lustre并行文件系统作为热数据层 + 对象存储作为冷数据层的分层架构。
┌─────────────────────────────────────────────────────────────┐
│ 训练集群(GPU节点) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ GPU Node │ │ GPU Node │ │ GPU Node │ │ GPU Node │ │
│ │ RoCE/IB │ │ RoCE/IB │ │ RoCE/IB │ │ RoCE/IB │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ └─────────────┴─────────────┴─────────────┘ │
│ │ 高速网络(RoCE/InfiniBand) │
└──────────────────────────┼──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Lustre并行文件系统(热层) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ MGS/MDT │ │ MGS/MDT │ │ MGS/MDT │ 元数据服务器 │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ OSS+OST │ │ OSS+OST │ │ OSS+OST │ │ OSS+OST │ │
│ │ (NVMe) │ │ (NVMe) │ │ (NVMe) │ │ (NVMe) │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ 全闪存NVMe SSD(热数据) │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 对象存储(冷层 / S3兼容) │
│ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ Ceph │ │ Ceph │ │ Ceph │ │ Ceph │ │
│ │ RGW │ │ RGW │ │ RGW │ │ RGW │ │
│ └───────┘ └───────┘ └───────┘ └───────┘ │
│ HDD大容量存储(冷数据/备份) │
└─────────────────────────────────────────────────────────────┘为什么这样选?
数据流动策略:
万卡集群的存储硬件配置参考:
角色 | 配置 | 数量 | 说明 |
|---|---|---|---|
MDS/MDT(元数据服务器) | 双路CPU 64核 + 256GB RAM + NVMe RAID10 | 2台(主备) | 元数据性能是关键 |
OSS/OST(数据服务器) | 双路CPU 32核 + 128GB RAM + 8×3.84TB NVMe | 16台 | 每台8块NVMe,RAID0(Lustre自带冗余) |
网络 | RoCE 100Gbps(或InfiniBand EDR/HDR) | - | 存储网络与计算网络隔离 |
客户端(GPU节点) | 安装Lustre客户端 + 内核模块 | 按GPU节点数量 | 每节点挂载同一文件系统 |
存储总容量估算:
组件 | 全称 | 功能 |
|---|---|---|
MGS | Management Server | 管理整个文件系统的配置信息 |
MDT | Metadata Target | 存储元数据(文件名、权限、inode等) |
MDS | Metadata Server | 运行MDT的服务进程 |
OST | Object Storage Target | 存储实际数据块 |
OSS | Object Storage Server | 运行OST的服务进程 |
Client | - | 挂载Lustre文件系统的GPU节点 |
第一步:环境准备
# 所有存储节点(CentOS 7/8或Rocky Linux)
# 1. 配置内核参数
cat >> /etc/sysctl.conf << EOF
net.core.rmem_max = 67108864
net.core.wmem_max = 67108864
net.ipv4.tcp_rmem = 4096 87380 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864
vm.swappiness = 10
EOF
sysctl -p
# 2. 关闭NUMA平衡(提高性能)
echo 0 > /proc/sys/kernel/numa_balancing
# 3. 安装Lustre依赖
yum install -y kernel-devel kernel-headers gcc make perl第二步:安装Lustre服务端
# 从官方源或编译安装(推荐使用官方预编译包)
# 注意:Lustre版本必须与内核版本严格匹配
# 添加Lustre仓库
cat > /etc/yum.repos.d/lustre.repo << EOF
[lustre-server]
name=Lustre Server
baseurl=https://downloads.whamcloud.com/public/lustre/latest-release/el8/server/
enabled=1
gpgcheck=0
EOF
yum install -y kmod-lustre lustre lustre-osd-ldiskfs lustre-tests
# 加载内核模块
modprobe lustre第三步:配置MDT(元数据)
# 在MDS节点上执行
# 使用NVMe SSD设备 /dev/nvme0n1 作为MDT
# 格式化MDT
mkfs.lustre --fsname=ai-fs --mdt --index=0 /dev/nvme0n1
# 创建挂载点并挂载
mkdir -p /mnt/mdt
mount -t lustre /dev/nvme0n1 /mnt/mdt
# 验证
lctl dl # 应该看到MDT设备第四步:配置OST(数据存储)
# 在每台OSS节点上执行
# 假设有8块NVMe:/dev/nvme0n1 ~ /dev/nvme7n1
for i in {0..7}; do
mkfs.lustre --fsname=ai-fs --ost --index=$(($i + 10)) /dev/nvme${i}n1
done
# 创建挂载点并挂载
mkdir -p /mnt/ost
mount -t lustre /dev/nvme0n1 /mnt/ost
# ... 依次挂载所有OST
# 检查集群状态
lctl dl第五步:配置客户端(GPU节点)
# 每个GPU节点执行
yum install -y kmod-lustre-client lustre-client
# 挂载Lustre文件系统
mkdir -p /mnt/ai-fs
mount -t lustre mds01@tcp0:/ai-fs /mnt/ai-fs
# 设置挂载选项(性能优化)
# 在/etc/fstab中添加:
mds01@tcp0:/ai-fs /mnt/ai-fs lustre defaults,_netdev,flock,user_xattr 0 0Lustre的默认配置是"通用"的,在AI训练场景下必须针对性调优。
# 客户端调优(在GPU节点执行)
# 1. 增大读写缓冲区
echo 16777216 > /proc/sys/fs/lustre/read_ahead_kb # 16MB预读
# 2. 设置OST条带数(关键!)
lfs setstripe -c 4 /mnt/ai-fs/train_data
# -c 4 表示将文件条带化到4个OST,提升并行读性能
# 对于大文件(>10GB),推荐条带数=OST数量的1/2到2/3
# 3. 批量设置目录默认条带
lfs setstripe -c 4 /mnt/ai-fs/train_data/现象:训练启动时,数据加载速度只有预期的1/5,GPU利用率从95%暴跌到60%。
排查过程:
lfs getstripe检查数据集目录——发现条带数为1(默认值)根因:数据是在客户端部署之前拷贝到Lustre的,当时用的是默认条带数。默认值=1,意味着所有数据只存到一个OST上,其他OST闲置。
解决方案:
# 1. 对新数据设置正确的条带数
lfs setstripe -c 8 /mnt/ai-fs/train_data_new/
# 2. 迁移历史数据(用lfs migrate)
lfs migrate -c 8 /mnt/ai-fs/train_data_old/*
# 3. 验证
lfs getstripe /mnt/ai-fs/train_data_old/sample.bin修复后,读带宽恢复到85GB/s,GPU利用率回升至93%。
很多训练数据集是海量小文件——比如ImageNet有1400万张图片,每张几百KB。当数据加载器并发读取时,元数据服务器需要处理每秒数万次的stat和open操作。
Lustre MDT的性能瓶颈:
Lustre 2.10+支持多MDT(Distributed Namespace),可以将不同目录的元数据分散到多个MDT上。
# 添加第二个MDT
mkfs.lustre --fsname=ai-fs --mdt --index=1 /dev/nvme1n1
mount -t lustre /dev/nvme1n1 /mnt/mdt1
# 将特定目录迁移到新MDT
lfs migrate -m 1 /mnt/ai-fs/train_data/规划原则:
这是更根本的解决方案:把海量小文件打包成大文件。
WebDataset格式:
# WebDataset:将多个样本打包成tar文件(每个tar包含100-10000个样本)
import webdataset as wds
# 写入
sink = wds.TarWriter("dataset-000001.tar")
for idx, sample in enumerate(dataset):
sink.write({
"__key__": f"sample_{idx}",
"jpg": sample["image"], # 图片
"cls": sample["label"] # 标签
})
sink.close()
# 读取(训练时直接流式读取tar文件,而非打开千万个小文件)
dataset = wds.WebDataset("dataset-*.tar") \
.decode() \
.to_tuple("jpg", "cls")效果对比:
指标 | 原始小文件 | WebDataset打包 |
|---|---|---|
文件数量 | 1400万 | 1400个tar(每包1万张) |
元数据OPS | ~50,000/s | ~500/s |
数据加载速度 | 2GB/s(受限于MDT) | 15GB/s(受限于OST带宽) |
GPU利用率 | 65% | 92% |
结论:在AI训练场景下,尽量把数据打包成大文件,这是对存储最友好的方式。
大模型训练中,Checkpoint的频率和大小都极为惊人:
目标:将Checkpoint时间压缩到60秒以内。
策略一:异步Checkpoint
在Lustre上,写操作是同步的——训练进程调用write()后会阻塞直到数据落盘。
# 异步Checkpoint(使用Python线程或异步IO)
import threading
import torch
def async_save_checkpoint(model, path):
# 先在内存中保存模型状态
state_dict = model.state_dict()
# 在后台线程中写入
thread = threading.Thread(target=torch.save, args=(state_dict, path))
thread.start()
return thread # 训练可以继续,无需等待注意:异步写入需要确保在下次Checkpoint之前完成,否则会产生覆盖冲突。
策略二:分层Checkpoint
GPU节点本地NVMe(写延迟<1ms)
│ 异步后台同步
▼
Lustre并行文件系统(写延迟~10ms)
│ 异步后台归档
▼
对象存储(写延迟~100ms,但容量巨大)策略三:使用NVIDIA Magnum IO GPUDirect Storage(GDS)
GDS允许GPU直接读写存储设备,绕过CPU内存,大幅降低延迟和CPU开销。
# Lustre + GDS 配置
# 需要:Lustre 2.14+ + NVIDIA GPU + 支持GDS的网卡(Mellanox ConnectX-6+)
# 在挂载时启用GDS:
mount -t lustre -o gds /dev/nvme0n1 /mnt/ai-fs实测数据(500GB模型Checkpoint) :
方案 | 耗时 | GPU停等时间 |
|---|---|---|
同步写入Lustre | 120秒 | 120秒 |
异步写入(本地NVMe先存) | 本地写入5秒 + 后台同步120秒 | 5秒 |
GDS直接写入Lustre | 45秒 | 45秒 |
组合最优方案:本地NVMe + GDS异步同步到Lustre,GPU停等时间仅5秒。
类别 | 指标 | 告警阈值 |
|---|---|---|
带宽 | OST读/写吞吐量 | <80%峰值持续5分钟 |
延迟 | OST读/写延迟(P95) | >50ms |
元数据 | MDT操作延迟(P95) | >10ms |
饱和度 | OST使用率 | >85% |
健康 | OST/MDT状态 | 任何非"ACTIVE" |
网络 | RoCE/IB丢包率 | >0.01% |
Lustre提供了/proc和/sys接口导出状态信息,我们使用lustre_exporter(社区开源)采集:
# docker-compose.yml
version: '3'
services:
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana
ports:
- "3000:3000"
volumes:
- ./grafana-dashboards:/etc/grafana/provisioning/dashboards
lustre-exporter:
image: huichoi/lustre_exporter
volumes:
- /proc:/proc:ro
- /sys/fs/lustre:/sys/fs/lustre:ro
ports:
- "9501:9501"Grafana看板关键Panel:
使用Lustre的HSM(Hierarchical Storage Management) 功能,自动将冷数据迁移到对象存储。
# 配置HSM策略
# 1. 定义策略:文件超过30天未访问 → 迁移到对象存储
lfs hsm_set --archive 1 /mnt/ai-fs/old_data/*
# 2. 用户访问时,Lustre自动从对象存储回迁(透明)
# 首次访问会有延迟,后续自动缓存到热层成本节省:热层(NVMe)≈¥20/GB/月,冷层(HDD对象存储)≈¥0.5/GB/月。对于PB级数据,每月节省百万级成本。
# 启用Lustre压缩(Lustre 2.14+)
lfs setstripe --compression=lz4 /mnt/ai-fs/train_data/故障类型 | 现象 | 恢复时间目标 |
|---|---|---|
OSS节点宕机 | 部分OST不可用,读写hang | <5分钟(自动Failover) |
网络分区 | 部分客户端无法访问 | <2分钟(路由收敛) |
MDT损坏 | 文件系统不可挂载 | 取决于备份(目标<1小时) |
OST磁盘损坏 | 数据丢失(如有RAID则降级) | 热换盘(<30分钟) |
# 使用Corosync + Pacemaker实现MDS/OSS自动Failover
# 1. 配置主备MDS(Active/Passive)
# 两台MDS共享同一个MDT存储(通过SAN或DRBD)
# 2. 使用虚拟IP
# MDS主节点拥有VIP,备节点监控主节点状态
# 主节点宕机 → 备节点接管VIP + 挂载MDT
# 3. OST的Failover
# 每个OSS配置一台备机,共享存储(或使用远程复制)# 备份MDT元数据(每天)
e2image -r /dev/nvme0n1 /backup/mdt.img
# 备份MDT配置(每次变更后)
lctl get_param -n mdd.*.root > /backup/mdt_root.txt
# 对象存储中的数据不备份(EC纠删码提供冗余)
# 但会定期做快照(每天一次,保留7天)趋势 | 说明 | 代表 |
|---|---|---|
存算分离 | 存储和计算独立扩展 | 当前主流(Lustre + GPU集群) |
存算一体 | 存储和计算融合,数据本地化 | NVIDIA DGX + DDN、华为OceanStor |
存算分离的优点是灵活扩展,缺点是网络开销。存算一体的优势是数据本地性,但成本高、扩展不灵活。
结论:未来3-5年,主流仍是存算分离,但会有更多"近数据计算"(将部分预处理放到存储端执行)的趋势。
AI分布式存储是一个"隐形但致命"的领域。
它不像GPU那样光鲜,也不像模型架构那样引人讨论。但它是整个AI训练体系的地基——地基不稳,上面的一切都是空中楼阁。
本文分享的所有经验,都来自真实的万卡集群建设过程中的"血泪教训"。希望这些内容能帮你少走一些弯路,让你的GPU真正发挥出它应有的价值。
记住三句话:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。