假设你是一家 AI 公司的“算法食堂”大管家:一千位厨师(GPU 训练进程)同时开工,要从中枢仓库取食材(训练数据)——上亿张图片、几十 TB 语料。每位厨师脚边的小菜篮(本地磁盘)只能装下几份菜,而传统文件服务器(NFS)就像仓库只有一个取菜窗口,一千人排队等着,灶台全空着等菜。这就是大模型训练中最典型的“存储饥饿”。要破局,得在仓库和后厨之间架起很多条传送带,让厨师们能从多个冷藏柜(存储节点)同时搬货——这套“大厨房仓储管理系统”,就是并行文件系统。Lustre 和 GPFS 是两套最出名的方案:Lustre 像“总调度台制”,一位大管家握着全库清单;GPFS 像“分区店长制”,每个冷柜区的库管各管一摊、协同作业。接下来,我们一起看看这两套厨房管理系统怎么选、怎么用。
对 AI 从业者而言,存储往往是训练集群最隐蔽的瓶颈。搞懂 Lustre 与 GPFS 的架构差异与选型逻辑,能让你在“千卡训练、数据管饱”这件事上,成为团队里最懂底层的人。
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 3KB)。
系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T
并行存储总览
回到“算法食堂”。一千位厨师同时开工,每人每秒都要“叮叮当当”切好几份菜,而食材却堆在远处仓库里。本地磁盘只是脚边小菜篮,装不下几份;传统 NFS 文件服务器则像仓库唯一的取菜窗口——所有人都挤在这一个窗口前,窗口后面的库管员还要一边找货一边记账,忙到冒烟。明明后厨有几十个灶台闲着,却因为取不到菜而集体停工。这种“算力等数据”的浪费,就是大模型训练最隐蔽的成本黑洞。
并行文件系统要解决的,就是让成百上千台机器同时、高速地从共享存储集群中读写数据。它的核心思路是:把数据切成小块,分散存放在很多台存储服务器上;同时把“食材清单”(文件名、目录、大小、权限,即元数据)和“食材本体”(文件内容,即数据)分开管理。于是取菜不再依赖一个窗口,而是几十上百个冷藏柜一起出货,这就是“并行”二字的本质。
存储系统的总吞吐能力可以用一个朴素的公式描述:
1. 聚合带宽符号:$B_{agg}$ 表示整个存储集群对外能提供的总读取速度,相当于食堂所有取菜传送带的合计吞吐量。
2. 单节点带宽:$B_{node}$ 是一台存储服务器(一个冷藏柜)能提供的读写速度,通常由磁盘和网卡决定。
3. 节点数量:$N$ 是参与提供存储服务的服务器数量,冷柜越多,理论总吞吐越高。
4. 并行效率:$\eta_{eff}$ 是一个小于 1 的效率系数,代表网络开销、锁竞争、负载不均等造成的损耗,冷柜再多也难做到完美 1.0。
这个公式解释了为什么并行文件系统动辄能到几十 GB/s:它不追求单机快,而是让几百台机器一起扛。但效率系数也提醒我们:管理协调的成本从来不会消失。下一节,我们看看“总调度台制”的 Lustre 是怎么协调这些冷柜的。
Lustre核心架构
Lustre 就像一家“总调度台制”的大食堂:仓库里只有一位大管家,手里握着全库的食材清单。这位管家就是元数据服务器(MDS,MetaData Server)——每个厨师取菜前,都得先跑到管家那儿问一句:“鸡胸肉在第几号冷柜?”管家查完本子告诉他,厨师再自己去对应冷柜搬货。搬货的过程管家不插手,但“查清单”这一步,全食堂只有这一个窗口。
Lustre 的角色分工很清晰:MGS(管理服务器)负责分发全系统的配置信息,相当于食堂的规章制度公告板;MDS 维护目录和权限,是食材清单大管家;OSS(对象存储服务器)们则各自管理着一批 OST(对象存储目标,可以理解成一个个冷藏柜),真正负责存放文件内容。数据读写由客户端直接与 OSS 完成,完全不经过 MDS,所以大文件搬家不会被“查清单”拖慢。
为了让单个大文件也能享受多个冷柜同时出货,Lustre 引入了条带化:把一个大文件切成若干段,均匀分散到多个 OST 上。此时单文件带宽可以近似表示为:
1. 单文件带宽:$B_{file}$ 表示读取一个大文件时能获得的实际速度,比如训练数据集里动辄几 GB 的样本包。
2. 单个 OST 带宽:$B_{ost}$ 是一个冷藏柜(一块 OST)独自服务的速度上限,可以理解为一条传送带的运力。
3. 条带数量:$N_{ost}$ 是文件被切成的段数,也是参与搬运的冷柜个数——条带数越多,参与搬运的传送带越多。
4. 条带并行效率:$\eta_{stripe}$ 是条带并行时的损耗系数;段数太多时,网络包变多、锁开销变大,这个系数会明显下降。
所以“条带数越多越快”是有边界的。总调度台制的优势是管理清晰、路径短;代价是所有人都挤一个窗口——当目录里挤满百万个小文件时,MDS 就成了瓶颈。这是理解 Lustre 一切优劣势的钥匙。
GPFS核心架构
如果说 Lustre 是“总调度台制”,那 GPFS 就像“分区店长制”的连锁食堂:没有统揽全局的大管家,每个冷柜区都有自己的小库管(叫作 NSD Server,网络共享磁盘服务器),他们按片区各管一摊食材清单,彼此通过一套对讲机协议(分布式锁管理 DLM)保持同步。厨师取菜时,不用绕到总台,直接问身边最近的店长就行。
GPFS 最显著的架构特征就是没有独立的元数据服务器。元数据(目录、inode、权限)被打散,分布在整个集群的所有 NSD 节点上;数据则存放在网络共享磁盘(NSD)中,任何节点都可以通过高速网络直接访问所有磁盘。这样一来,“查清单”这件事不再是单点排队,而是多窗口并行。当客户端数量翻倍,元数据服务能力也能跟着扩容:
1. 元数据总吞吐:$R_{meta}$ 表示整个集群每秒能处理的元数据请求总数,比如 open、stat、create 等操作。
2. 单节点处理速率:$r_{node}$ 是每台 NSD 服务器每秒能处理的元数据请求数,相当于每个店长每分钟能接待的厨师数。
3. 服务器数量:$N_{server}$ 是参与元数据服务的节点总数,店长越多,接待能力越强。
这个公式的潜台词是:GPFS 的元数据能力可以“加人”来扩展,而 Lustre 在很长一段时间里只能靠升级单台 MDS 的硬件。当然,分布式不是免费午餐——店长们需要不断开会同步(DLM 协议开销),这让 GPFS 的架构更复杂,商业授权也更贵。但面对百万级小文件的场景,这种“多窗口查清单”的能力价值极高。
一致性机制
食堂最怕什么?两个厨师同时瞄上了同一块五花肉:一个要拿去煎,一个要拿去炖,而冷柜上的记录还停留在“卤肉”——两人各记各的账,最后谁都没吃上对的东西。POSIX 语义就是全体厨师必须遵守的“食材状态规则”:谁改了什么,别人必须立刻看到,不允许出现两份互相矛盾的黑板记录。
并行文件系统为了让读写更快,会在客户端本地做缓存,就像厨师手边放一个“备用食材盒”。但缓存带来了新麻烦:A 厨师改写了文件,B 厨师手里的旧缓存怎么办?这时就需要锁和缓存失效机制。锁的粗细直接决定并发效率:Lustre 通过基于区间的 extent 锁管理缓存一致性,GPFS 则支持更精细的字节范围锁,只锁住正在写的那些字节。平均访问时延可以建模为:
1. 平均访问时延:$T_{avg}$ 表示一次文件读写的平均耗时,厨师从动筷到吃上菜的总时间。
2. 获取锁开销:$T_{lock}$ 是无论是否有冲突都要付出的固定成本——申请许可、登记、释放,像借书时填表格。
3. 冲突重叠概率:$P_{overlap}$ 是本次写操作与其他人写操作在文件区间上重叠的概率;区间越小,重叠概率越低。
4. 冲突等待时间:$T_{wait}$ 是发现冲突后等待对方释放锁的时间,相当于发现书被人借走后的等待。
锁粒度越大,$P_{overlap}$ 越高,大家排队等;锁粒度越小,并发越顺畅,但 $T_{lock}$ 的登记成本也越高。理解了这把“精细与开销”的跷跷板,就理解了为什么 GPFS 在混合负载下更从容、而 Lustre 更擅长让大块数据顺序飞驰。
性能实战对比
出餐高峰是检验厨房的唯一标准。先看大块烤肉:几十 GB 的模型 checkpoint 文件,考验的是传送带总吞吐——Lustre 条带化后能把数据分散到几十个 OST 上同时搬运,GPFS 同样不遑多让,两者都能冲到几十甚至上百 GB/s。再看一万个小饺子:百万张图片、海量小 json 文档,考验的是取菜窗口的处理速度——每秒几万到几十万的元数据请求才是真正的分水岭。
这里要澄清一个关键概念:带宽高 ≠ 延迟低。聚合带宽是“总吞吐量”,而单次请求的延迟包含元数据查询、网络往返、锁等待等固定成本。对于 GPU 训练,真正重要的是别让 GPU 闲着,于是有:
1. IO 效率:$\eta_{io}$ 表示 GPU 真正花在计算上的时间占比,这个值越接近 1,算力浪费越少。
2. 计算时间:$T_{compute}$ 是每个训练步骤中 GPU 做矩阵乘、梯度更新等纯计算的时间。
3. IO 等待时间:$T_{io}$ 是 GPU 等待数据从存储到达的时间,包括网络传输、元数据查询和锁等待。
实战中常看到的现象是:小文件密集的数据预处理阶段,Lustre 单 MDS 被打满,吞吐断崖下跌;而 GPFS 分布式元数据能平滑承接。反之,如果数据已经是整齐的大文件流式读取,Lustre 凭借极致的条带并行和开源生态,往往性价比更高。所以别问“谁更快”,先问“你的菜是大块的还是碎末的”。
选型决策指南
开食堂选管理模式,本质是匹配菜单。如果你只做千人大锅饭——超大规模训练、数据样本都是大文件、追求极致聚合带宽,那“总调度台制”的 Lustre 非常合适:开源免费、生态成熟、条带化后大文件吞吐惊人。如果你既要炒菜又要做寿司还要接外卖——训练、推理、小文件预处理、多租户混合负载,那“分区店长制”的 GPFS 更能扛:分布式元数据、字节范围锁、弹性扩缩容,代价是真金白银的授权费。
选型时,很多团队只盯着采购价,却忽略了长期持有成本。存储系统的总成本应该这样算:
1. 总拥有成本:$C_{total}$ 是存储系统在 Y 年生命周期内的全部花费,相当于食堂的长期经营总账。
2. 硬件成本:$C_{hw}$ 是采购服务器、磁盘、网络交换机的初始投入,冷柜和传送带的购买价。
3. 年度运维成本:$C_{ops}$ 是每年电费、机房租金、工程师维护人力;Lustre 开源但调优复杂,人力成本往往不低。
4. 年度停机损失:$C_{downtime}$ 是每年因存储故障或性能瓶颈导致的 GPU 空转损失,训练集群停 1 小时,几十万卡时预算就蒸发了。
真实案例中,某千卡大模型训练集群选用 Lustre 承载 checkpoint 与大文件数据集,聚合带宽做到 80GB/s;另一家自动驾驶公司面对亿级小图片,则选择 GPFS 用分布式元数据扛住每秒几十万的 inode 查询。选型没有银弹,只有“拿你的菜单去匹配厨房”。
总结与趋势
回头看这两套“厨房管理系统”:Lustre 用一位大管家的简单逻辑换来了极致的大文件吞吐;GPFS 用一群店长的分布式协作换来了混合负载下的从容。但无论哪种架构,都逃不过一个朴素的道理——整个厨房的出餐速度,取决于必须要排队的那部分环节。这正是 Amdahl 定律描绘的图景:
1. 加速比:$S_{speedup}$ 表示增加存储节点后,整体性能提升的倍数,比如从 10 台扩到 100 台冷柜。
2. 可并行比例:$p$ 是任务中能被并行化的部分,比如数据读写这样能分摊到多个冷柜的工作。
3. 并行度:$N$ 是参与的节点数量,冷柜数量。
4. 串行部分:$(1-p)$ 是必须排队、无法并行的部分,比如单点 MDS 的元数据查询或锁协调;这部分决定了扩展的天花板。
这正是两个系统都在演进的动力:Lustre 引入分布式命名空间 DNE,想把“大管家”变成“店长团”;GPFS 持续增强全闪存与容器化支持,把“分区店长制”打磨得更顺滑。新一代存储如 DAOS、WekaFS 也沿着“更分散、更贴近 NVMe”的方向狂奔。作为 AI 从业者,你不需要精通每块磁盘的转速,但必须记得:存储选型的本质,是让你的千家灶台永远有菜可做。
AI 应用场景
大模型 Checkpoint 高速保存
阶段:模型训练与容错(训练中周期性保存模型状态)
解决的问题:训练 70 亿参数模型时,每几千步就要保存一次权重、优化器状态和随机数种子,快照体积动辄几十 GB。传统单机磁盘无法共享,NFS 只有 2GB/s 写入,保存一次要等好几分钟,GPU 全部空转。并行文件系统通过条带化把 checkpoint 分散到几十个 OST 上并发写入,保存时间从几分钟压缩到十几秒。
具体案例:某大模型团队在千卡集群上用 Lustre 承载 checkpoint,条带数设为 32,聚合写入带宽达到 40GB/s,保存一次从 3 分钟降到 10 秒;训练中断恢复的频率因此大幅提高,GPU 利用率从 78% 提升到 92%。
多模态数据预处理流水线
阶段:数据准备与特征工程(图片、视频、文本小文件的读取与清洗)
解决的问题:自动驾驶和视觉模型的数据集动辄上亿个几十 KB 的小文件。小文件读写的瓶颈不在带宽,而在元数据查询——每个文件都要先问“它在哪”,每秒几十万次的 open/stat 请求会让单一 MDS 直接打满。GPFS 的分布式元数据让查询能力随节点数扩展,是这类负载的天然解药;如果坚持用 Lustre,则必须配合小文件打包(tar/lmdb)和元数据缓存来缓解。
具体案例:某自动驾驶公司用 GPFS 承接 1.2 亿张图片的预处理流水线,把元数据服务分散到 16 台 NSD 节点上,每秒可处理 30 万次元数据请求,数据预处理耗时从 20 小时降到 6 小时。
Python 演示
# 组合式演示: 对比集中式MDS与分布式元数据在并发增长下的排队时延class MetaNode: def __init__(self, capacity): self.capacity = capacity # 每秒可处理请求数 self.queue_len = 0.0 # 当前排队请求数 def step(self, arrival, dt=0.1): # 每个时间片: 新请求到达, 服务器处理掉一部分 self.queue_len = max(0.0, self.queue_len + arrival * dt - self.capacity * dt) if self.queue_len < 1e-6: self.queue_len = 0.0 return self.queue_len / self.capacity # 近似排队时延(秒) class LustreSim: def __init__(self, mds_capacity): self.mds = MetaNode(mds_capacity) def delay(self, clients, rate): return self.mds.step(clients * rate) class GpfsSim: def __init__(self, nsd_count, per_capacity): self.nodes = [MetaNode(per_capacity) for _ in range(nsd_count)] def delay(self, clients, rate): arrival = clients * rate / len(self.nodes) total = sum(n.step(arrival) for n in self.nodes) return total / len(self.nodes) def main(): lustre = LustreSim(mds_capacity=800) gpfs = GpfsSim(nsd_count=8, per_capacity=200) print("客户端数 | Lustre时延(ms) | GPFS时延(ms)") for clients in [8, 16, 32, 64, 128]: l_delay = lustre.delay(clients, 50) * 1000 g_delay = gpfs.delay(clients, 50) * 1000 print("%4d | %12.1f | %12.1f" % (clients, l_delay, g_delay)) if __name__ == "__main__": main()
self.queue_len = max(0.0, self.queue_len + arrival * dt - self.capacity * dt)每个时间片先加入新到达的请求,再减去被处理掉的请求,队列永远不会为负数。self.queue_len / self.capacity排队长度除以处理能力,得出近似的排队等待秒数。meta = MetaNode(mds_capacity)Lustre 只有一个 MDS 节点,所有元数据请求都汇聚到这个节点上。clients * rate总请求速率是客户端数量乘以每客户端速率,Lustre 的 MDS 要独自吃下全部压力。clients * rate / len(self.nodes)GPFS 把总请求平均分摊给 8 台 NSD 节点,每台压力只有八分之一。total / len(self.nodes)对所有元数据节点取平均时延,代表客户端的整体感知。for clients in [8, 16, 32, 64, 128]不断增加客户端数量,观察两种架构的时延变化:Lustre 很快排队爆炸,GPFS 保持平缓。
常见误区
误区训练代码里的文件操作在本地盘上能跑多快,搬到 Lustre/GPFS 上就能跑多快——反正接口一样。
正解POSIX 接口兼容不等于性能兼容。本地盘的延迟是微秒级,并行文件系统经过网络和锁,单次小请求延迟是毫秒级;更关键的是,小文件随机写、频繁 fsync、边写边 seek 这类“本地盘友好”模式在并行 FS 上会触发大量锁和元数据请求,性能断崖下跌。正确姿势是大块顺序读写、批量提交、把小文件打包。
为何常见:接口长得一模一样,让人本能地忽略了底层是分布式系统这个事实;只有在压测出现神秘卡顿时,人们才开始研究 IO 模式。
误区反正 Lustre 的 MDS 会瓶颈,那所有场景都应该用 GPFS,一步到位。
正解架构弱点要放到具体负载里看。如果数据是大文件流式读写,Lustre 的条带化能把带宽打满,而且开源免费、HPC 生态成熟;GPFS 的分布式元数据在小文件密集时优势明显,但授权费高、架构复杂。实际选型应看 IO 画像:大文件选 Lustre,小文件混合负载选 GPFS。
为何常见:把“架构上有瓶颈”和“实际场景中会瓶颈”混为一谈,忽略了工作负载这个关键前提。
误区条带数设成最大,所有冷柜都来搬数据,带宽不就最高了吗?
正解条带数提升的是单文件并行度,但受 OST 总能力和网络上限约束;条带数过大时,小文件要跨多个 OST 做多次 RPC,seek 和锁开销反而拖慢速度。对几十 KB 的小文件,默认 1 个条带往往最优;只有几 GB 的大文件才适合提高条带数。
为何常见:人们把聚合带宽的直觉直接套到单文件上,忽略了并行本身也有协调成本。
误区存储系统标称 100GB/s,那每次读文件都应该飞快。
正解带宽是吞吐量,延迟是单次请求的响应时间,两者是不同维度。高带宽并行文件系统的单次小请求仍然要经过客户端、网络、元数据服务器、锁管理器等多个环节,延迟通常在毫秒级。对 GPU 训练而言,更要关注的是 IO 效率和带宽是否匹配,而不是盯着延迟数字。
为何常见:广告宣传总是强调惊人的聚合带宽,容易让人忽略小请求的固定开销。
误区GPFS 既然没有集中的元数据,那肯定也没有锁,少了锁自然更快。
正解GPFS 不仅没有去掉锁,反而有一套精巧的分布式锁管理(DLM)——它是让各节点协同工作的核心。GPFS 的优势不是“不用锁”,而是锁更分散、粒度更细(字节范围锁),把冲突概率和等待时间降低了。任何承诺 POSIX 一致性的并行文件系统都离不开锁。
为何常见:把“无中心元数据”误解为“无协调机制”,其实分布式系统最难的恰恰是协调。
面试题
Q1. Lustre 中负责维护文件目录和权限的组件是哪一项?
- A.元数据服务器(MDS)← 正确答案
- B.对象存储目标(OST)
- C.逻辑卷管理器(LVM)
- D.管理服务器(MGS)
答案:A。Lustre 把“食材清单”(目录、权限、文件名)交给 MDS 管理,就像食堂的总调度台。OST 只管存放食材本体,MGS 负责分发配置,LVM 是本地磁盘概念,与 Lustre 组件无关。
Q2. 与 Lustre 相比,GPFS 最显著的架构差异是什么?
- A.采用分布式元数据管理← 正确答案
- B.完全不支持 POSIX 语义
- C.只能运行在 IBM 硬件上
- D.取消了全部数据缓存
答案:A。GPFS 没有独立元数据服务器,元数据分散在各 NSD 节点上,相当于“分区店长制”。它仍然支持 POSIX,也不依赖特定厂商硬件,更没有取消缓存——这些说法都把差异夸大了。
Q3. 某文件大小 8MB,在 Lustre 上设置条带数为 4,则每个 OST 上大约存放多少数据?
- A.大约 1MB 数据
- B.大约 2MB 数据← 正确答案
- C.大约 4MB 数据
- D.大约 8MB 数据
答案:B。条带化就是把文件切成 N 段均匀分散到 N 个 OST,8MB 除以 4 个条带等于每个 OST 约 2MB。这就像把一块大肉切成四份分放到四个冷柜,每个冷柜只负责其中一份。
Q4. Lustre 的 MDS 在大规模小文件场景中成为瓶颈的根本原因是什么?
- A.所有元数据请求集中处理← 正确答案
- B.数据读写都经过 MDS 转发
- C.MDS 没有冗余备份机制
- D.OST 的数据条带化过深
答案:A。Lustre 的数据面不经过 MDS,瓶颈来自元数据请求全部汇聚到一个节点,就像全食堂只有一个大管家查清单。MDS 可以做高可用备份,条带化针对的是数据面,都不是根因。
Q5. GPFS 的字节范围锁相比 Lustre 的常规锁机制有何优势?
- A.并发访问粒度更精细← 正确答案
- B.完全消除锁等待开销
- C.所有锁改为硬件实现
- D.不需要元数据参与锁
答案:A。字节范围锁只锁住正在写的字节区间,两个进程写同一文件的不同区域可以并行,相当于借书时只登记你正在读的那一页。锁开销不可能完全消除,锁管理也仍需元数据参与。
Q6. 小文件密集读取时,直接扩大 Lustre 条带数通常会导致什么后果?
- A.小文件性能反而会恶化← 正确答案
- B.文件聚合带宽立即翻倍
- C.元数据请求显著下降
- D.POSIX 一致性自动关闭
答案:A。小文件本身很小,扩大条带数意味着要访问更多 OST、发起更多 RPC,协调开销超过并行收益,性能反而变差。这就好比为了一小碟花生米派十个厨师去搬,搬菜的时间比做菜还长。
Q7. 某 Lustre 集群实测聚合带宽 120GB/s,若保持 OST 总数不变,仅将单个大文件的条带数翻倍,带宽最可能如何变化?
- A.接近翻倍达到 240GB/s
- B.维持不变约 120GB/s 左右← 正确答案
- C.下降为 60GB/s 左右
- D.会自动变为 480GB/s 左右
答案:B。条带数只影响单个文件能利用多少个 OST,而集群总带宽由所有 OST 的总能力决定。OST 没变,总吞吐上限就还是 120GB/s;条带数翻倍只是让这一个大文件占用了更多冷柜,相当于把冷柜重新分配,总量不变。
Q8. 以下关于 Lustre 与 GPFS 的说法,正确的是哪些?
- A.Lustre 元数据管理是集中式的← 正确答案
- B.GPFS 元数据管理是分布式的← 正确答案
- C.两者都面向共享存储设计← 正确答案
- D.Lustre 完全不支持数据条带化
答案:ABC。Lustre 用单一 MDS 集中管理元数据,GPFS 把元数据分布到各节点,两者都面向大规模共享存储场景。Lustre 的表单化是它的招牌特性,D 明显错误。
Q9. 以下哪些措施可以缓解并行文件系统的元数据压力?
- A.把小文件打包合并成大块数据← 正确答案
- B.提升元数据节点的缓存命中率← 正确答案
- C.人为增大文件条带数量
- D.关闭所有客户端缓存直写后端
答案:AB。小文件打包减少了 inode 数量,缓存命中降低了对 MDS 的查询次数,都是对症下药。增大条带数只会让每个小文件涉及更多 OST,反而加重协调开销;关闭缓存会放大每个请求直接打到后端,元数据压力更大。
Q10. 关于并行文件系统承载大模型训练负载,正确的说法有哪些?
- A.数据预取可减少训练等待时间← 正确答案
- B.checkpoint 并发写需要锁保护← 正确答案
- C.小文件越多元数据压力越小
- D.存储带宽需要匹配 GPU 吞吐← 正确答案
答案:ABD。预取把数据提前搬进缓存,能让 GPU 少挨饿;多个进程写同一 checkpoint 必须靠锁保证一致性;存储带宽若是跟不上多个 GPU 同时读数据,计算就会空转。小文件越多元数据压力越大,C 说反了。