第 3 课:存储层级、数据集与 Checkpoint 带宽

本课目标与完成标准

本课产出:计算 checkpoint 的有效写入时间和带宽需求,区分本地 NVMe、共享文件系统与对象存储角色。

通过要求:唯一代码填空题通过给定检查;Q1~Q3 都给出因果链;能指出一个正确性边界和一个性能/运营取舍;总分至少 8/10。

与既有六条主线的边界

runtime/lesson06 讲进程内输入流水;本课关注集群存储层、元数据压力、checkpoint 突发和恢复路径。

前置:Linux/网络基础、runtime 补充线、train 分布式章节。本课只补 AI Infra 缺口,不重复已经完成的 kernel 或并行算法推导。

核心心智模型

1. 它是什么,解决什么问题

在万卡级大模型训练(如 405B 参数量级)与海量多模态数据处理集群中,存储与 IO 子系统面临两类问题:

  1. Checkpoint 突发与训练停顿(Checkpoint IO Bubble):
    405B 全量状态(BF16 权重 + FP32 主权重 + Adam 一阶/二阶矩,约 14 B/参数)约 5.7 TB5.7\text{ TB},同步保存可停顿数十分钟,整点并发写冲击存储网络。

  2. 小文件元数据压力:
    海量小文件则压垮 MDS,GPU 因数据供给不足而空等。

本课通过解析现代 AI 基础设施的五级存储金字塔(如图 3-1 所示),建立“GPU 显存快照 →\rightarrow Host RAM 缓冲 →\rightarrow 本地 NVMe 暂存 →\rightarrow 中央持久对象存储”的多级非阻塞异步 Checkpoint 架构(如图 3-2 所示),将训练阻断时间从几十分钟骤降至秒级。

2. 它如何工作

(1) 五级存储金字塔与跨层级带宽断层

如图 3-1 所示,现代超算存储架构呈现出严密的五级金字塔分层:

图 3-1:大模型集群五级存储金字塔层次与带宽特性

  • L1:GPU 显存(HBM3/HBM3e):单卡带宽约 3∼8 TB/s3\sim 8\text{ TB/s},但容量昂贵且受限(H100 80 GB HBM3;H200 141 GB HBM3e;B200 约 180~192 GB;B300 288 GB),专供 SM 计算核心以纳秒级延迟高频访存;
  • L2:Host 主内存(DDR5 RAM):单节点带宽 200∼400 GB/s200\sim 400\text{ GB/s},容量达数 TB,作为 CPU 与 GPU 之间的高速弹性缓冲垫(Bounce Buffer);
  • L3:节点本地 NVMe SSD:单节点通过 PCIe Gen5 x4 / NVMe 阵列提供 10∼50 GB/s10\sim 50\text{ GB/s} 的本地顺序写带宽与超高 IOPS,容量达数十 TB,具备低时延并发暂存能力;
  • L4:共享并行分布式文件系统(如 Lustre / GPFS / WekaFS / JuiceFS):提供集群全局统一的 POSIX 命名空间,单节点接入带宽通常为 1∼10 GB/s1\sim 10\text{ GB/s},集群聚合带宽可达数 TB/s;
  • L5:冷数据归档对象存储(如 S3 / Ceph / OSS):单节点访问延迟较高(10∼50 ms10\sim 50\text{ ms}),但提供跨数据中心、高持久性(11 个 9)且成本极其低廉的弹性无限容量。

(2) 多级非阻塞异步 Checkpoint 保存流水线

如图 3-2 所示,为消除显卡保存 Checkpoint 时的阻塞停顿,工业界采用多级流水线解耦计算与存储持久化:

图 3-2:非阻塞异步多级 Checkpoint 保存流水线与持久化工作流

  1. 步骤 1:显存快照同步转储(In-Memory Snapshot,耗时 ∼1\sim 1 秒)
    当到达 Checkpoint 保存步数时,主计算流暂停,利用高带宽 PCIe Gen5 D2H 通道(单机聚合可达数代总线带宽),将当前迭代步的权重与优化器状态以只读内存快照形式瞬间拷贝至 Host DDR5 内存中。
    Host 内存接收完毕后,GPU 即可开始下一个迭代的 Forward / Backward 计算(图中“立即恢复训练”分支),整个训练停顿时间被压缩至 1 秒左右。

  2. 步骤 2:后台线程异步刷写本地 NVMe(Async Local Spill)
    Host 内存中的快照数据由后台独立的 I/O 线程接管,以数十 GB/s 的速度流式写入本地 NVMe SSD。此时若训练进程发生崩溃,由于最新状态已保存在本地 NVMe,可在秒级内完成单机原地重启热恢复。

  3. 步骤 3:流式异步上传至远端对象存储与原子提交(Remote Upload & Atomic Commit)
    本地落盘后,后台线程继续流式将分片推送至中央对象存储(S3/OSS)。当全集群所有节点的特定迭代步分片全部上传完毕后,由主节点(Rank 0)原子性写入全局 metadata.json(Manifest 清单)并放置 Commit Token。

(3) 周期带宽需求与写放大数学模型

设单次 Checkpoint 产生的逻辑数据总量为 SS 字节,底层存储系统的物理写放大系数为 WW(考虑多副本、纠删码 EC 头开销、元数据日志与块未对齐填充,通常 W≥1.5W \ge 1.5),存储系统物理可用写入带宽为 BbackendB_{\text{backend}}(B/s),保存周期间隔为 TintervalT_{\text{interval}}(秒):

Physical Bytes (Sphy)=S×W\text{Physical Bytes } (S_{\text{phy}}) = S \times W

Dump Duration (Tdump)=SphyBbackend\text{Dump Duration } (T_{\text{dump}}) = \frac{S_{\text{phy}}}{B_{\text{backend}}}

Required Average Bandwidth (Bavg)=SphyTinterval\text{Required Average Bandwidth } (B_{\text{avg}}) = \frac{S_{\text{phy}}}{T_{\text{interval}}}

系统必须保证 Tdump≤TintervalT_{\text{dump}} \le T_{\text{interval}},且周期内平摊的最低平均后端带宽 BavgB_{\text{avg}} 必须在存储系统的稳态容量承载红线之内。

3. 正确性条件与常见误区

  • 误区一:所有 Rank 同时向分布式共享存储并发写入单个小文件
    在数千张 GPU 规模下,若每个 Rank 都调用 torch.save(..., f"rank_{i}.pt"),成千上万个进程会在同一瞬间向同一个共享目录发起 open/write/fsync/close POSIX 系统调用。这会瞬间引发元数据服务器(MDS)的 CPU 打满与内存缓存击穿,并触发毁灭性的分布式目录锁竞争(Directory Inode Lock Contention),导致写入挂起长达数小时甚至导致存储集群宕机。必须采用分布式聚合存储格式(如 PyTorch Distributed Checkpoint: DCP)。

  • 误区二:本地 NVMe 写入完成即向训练编排器报告“已完成持久化”
    本地 NVMe 仅具备“进程级与单机重启”恢复能力。若物理节点发生硬件致命断电、主板烧毁或网卡损坏,本地 NVMe 的数据将不可读。调度器若误以为该 Checkpoint 已安全,在驱逐漂移任务后将发现新节点根本无法拉取权重,造成严重的灾难性故障。

  • 误区三:忽略后端写放大(Write Amplification)导致带宽规划严重失真
    规划带宽时若仅按裸张量体积(如 4 TB)计算,而忽略存储底层采用的 3 副本(写放大 = 3.0)或 8+4 纠删码(写放大 = 1.5),实际冲击网络与磁盘的物理流量将增加 50%~200%,导致存储集群在写并发峰值期严重丢包。

4. 性能与工程取舍

  • 内存快照的容量预留与 Host RAM 竞争:
    在 Host RAM 中保留一份完整的优化器快照会占用与 GPU 显存相当的物理内存(例如 8 卡服务器需常驻 400~600 GB 的 Host 内存缓冲)。若宿主机总内存规划不当,快照缓冲区将与数据加载器的 Linux Page Cache 发生激烈争抢,甚至诱发 Linux OOM Killer 强杀训练进程。

  • GPUDirect Storage(GDS)的应用场景客观边界:
    GDS 利用 PCIe 直通绕过 CPU,在大块(≥1 MB\ge 1\text{ MB})连续数据的读写中能够极大卸载 CPU 拷贝开销并打满总线带宽。但在小样本随机读取场景下(如海量未打包的小图片或短文本),I/O 开销完全由文件系统路径遍历、元数据检索与设备寻址主导。GDS 绕过内核 Page Cache 预读反而会导致每次读取都硬敲物理磁盘,性能表现远逊于传统的缓存读。

具体演示

设全集群一次 Checkpoint 的原始模型状态数据量为 4 TB4\text{ TB}(4×10124 \times 10^{12} 字节),存储后端采用 8+4 纠删码及元数据开销,物理写放大系数为 W=1.5W = 1.5: Physical Bytes=4×1012×1.5=6×1012 字节 (6 TB)\text{Physical Bytes} = 4 \times 10^{12} \times 1.5 = 6 \times 10^{12}\text{ 字节 (6 TB)}

若分布式存储后端的物理聚合写入带宽为 10 GB/s10\text{ GB/s}(10×109 B/s10 \times 10^9\text{ B/s}),则一次落盘持续时间为: Tdump=6×101210×109=600 秒 (10 分钟)T_{\text{dump}} = \frac{6 \times 10^{12}}{10 \times 10^9} = 600\text{ 秒 (10 分钟)}

若训练计划设定每隔 30 分钟(1800 秒1800\text{ 秒})保存一次,则该任务在周期内平均占用的后端带宽为: Bavg=6×10121800≈3.333×109 B/s (约 3.33 GB/s)B_{\text{avg}} = \frac{6 \times 10^{12}}{1800} \approx 3.333 \times 10^9\text{ B/s (约 3.33 GB/s)}

这表明存储系统必须能在每 30 分钟内稳定承受至少 3.33 GB/s 的常态化带宽消耗,并在落盘期承受 10 GB/s 的突发吞吐。

请先独立复述“输入 → 状态变化 → 输出/指标”,再开始填空。

实践任务:唯一代码填空题

补齐 checkpoint 写入时间与周期内最低平均带宽。

只能修改 TODO/______ 位置,不得删除断言或放宽通过条件。

Code
def checkpoint_io(checkpoint_bytes, backend_Bps, write_amplification, interval_s):
    if min(checkpoint_bytes, backend_Bps, write_amplification, interval_s) <= 0:
        raise ValueError("all values must be positive")
    physical_bytes = checkpoint_bytes * write_amplification
    duration = physical_bytes / backend_Bps
    # TODO:周期内摊销的最低平均后端带宽。
    required_average = ______
    return duration, required_average

duration, avg = checkpoint_io(4e12, 10e9, 1.5, 1800)
assert duration == 600 and abs(avg - 3.3333333333333335e9) < 1

检查方法

运行本单元格,所有断言必须通过;再补一个边界输入并解释预期。

Q1

为什么“所有 rank 各写一个小文件”在规模扩大后常失败?

你的答案:

Q2

本地 NVMe checkpoint 完成后即可向训练宣称“可恢复”吗?

你的答案:

Q3

GDS 能减少 CPU copy,为什么未必提升小样本随机读取?

你的答案:

评分规则

  • 代码 4 分:正常输入 2 分、边界输入 1 分、解释 1 分;
  • Q1~Q3 各 2 分;
  • 一票否决:混淆测量与推测、忽略租户/请求隔离、用平均值掩盖尾延迟、删除失败路径。

参考答案(仅 answer 分支)

先独立完成。核对后请改变一个规模或故障条件重新推演。

Code
def checkpoint_io(checkpoint_bytes, backend_Bps, write_amplification, interval_s):
    if min(checkpoint_bytes, backend_Bps, write_amplification, interval_s) <= 0:
        raise ValueError("all values must be positive")
    physical_bytes = checkpoint_bytes * write_amplification
    duration = physical_bytes / backend_Bps
    required_average = physical_bytes / interval_s
    return duration, required_average

duration, avg = checkpoint_io(4e12, 10e9, 1.5, 1800)
assert duration == 600 and abs(avg - 3.3333333333333335e9) < 1

Q1 参考答案

在千卡或万卡超大规模分布式训练中,若每个 GPU Rank 各自独立向共享文件系统并发写入一个小文件,会导致存储系统发生毁灭性的元数据崩溃与目录锁竞争:

  1. 元数据服务器(MDS)瞬间打穿:数千个 Rank 在同一毫秒内并发发起 open()、create()、fsync()、close() 系统调用,数万次元数据 RPC 瞬间打满 MDS 的 CPU,击穿元数据缓存,导致元数据响应延迟从微秒级飙升至秒级甚至超时断连;

  2. 分布式目录排他锁竞争(Directory Inode Lock Contention):当成千上万个文件同时创建在同一个父目录下时,分布式文件系统(如 Lustre、CephFS、GPFS)为了维护 POSIX 目录的一致性与条目顺序,必须在元数据节点上对该目录 Inode 加互斥锁。这导致原本期望并行的写入被底层强行退化为单线程串行排队;

  3. 小文件碎片化与带宽严重不饱和:每个 Rank 写入的文件较小且未对齐存储条带(Stripe),导致底层磁盘产生海量随机写与写放大,网络与磁盘聚合带宽连 10% 都无法打满,保存时间成倍恶化。工业界必须通过 PyTorch Distributed Checkpoint(DCP)或分片聚合技术,合并 IO 请求为少量大块连续写入。

Q2 参考答案

绝对不能。在分布式基础设施工程中,本地 NVMe 落盘完成仅满足了“低容错级别(Low Fault-Tolerance Tier)”的局部保存,无法作为集群级断点续训的全局安全凭据,原因如下:

  1. 单点物理故障域(Fault Domain)未解耦:本地 NVMe 强绑定在具体的物理宿主机上。它仅能防御“用户态进程崩溃(Crash)”或“驱动/操作系统软重启”。一旦物理机发生硬件级致命故障(如主板供电短路、CPU 烧毁、本地 PCIe 闪存卡掉盘、或物理节点被 Kubernetes 平台驱逐下线并断电),该节点上的本地 NVMe 数据将永久丢失或不可访问;

  2. 集群弹性漂移与重调度死锁:在大规模集群中,节点故障后调度器(如 Slurm 或 K8s)通常会将故障节点移出集群,并在健康的备用物理机上重新拉起任务。新节点上根本不存在旧节点的本地 NVMe 盘,若此前未将 Checkpoint 异步流式上传至具备高可用持久性的中央存储(共享分布式文件系统或 S3/OSS 对象存储),新节点将无从恢复权重,导致全集群训练陷入死锁;

  3. 全局原子性提交原则(Atomic Commit Protocol):只有当全集群所有 Rank 的分片数据均已成功持久化至中央对象存储,并且由 Rank 0 节点成功写入带有全局一致性校验和与版本元数据的 manifest.json 与 Commit Token 后,系统才能正式宣布该版本“具备全局可恢复性”。

Q3 参考答案

GDS 省掉的是经 Host bounce buffer 的 CPU 拷贝,小样本随机读的瓶颈不在这里:

  1. 固定开销主导:几十到几百 KB 的读取,时延主要来自 VFS 路径查找、元数据解析和设备寻址,GDS 不能消除这些开销;
  2. GDS 自身的固定成本:每次请求要经驱动提交 Direct I/O、做显存映射与对齐检查,小块上这部分可能超过省下的拷贝时间;
  3. 失去 Page Cache:GDS 走 Direct I/O,绕过内核预读和热点缓存,重复读取的样本也要再次访问 SSD。

因此小样本应先打包成大分片顺序读取,再考虑 GDS。

参考资料

API 与平台能力会演进;部署前应按目标版本重新核对。