哈佛-CS249r-机器学习系统第二卷-三-

哈佛 CS249r:机器学习系统第二卷(三)

原文:Machine-Learning-Systems-Vol2

译者:飞龙

协议:CC BY-NC-SA 4.0

FSDP 用每层的两次集合操作替换了每步的一次集合操作。

FSDP/ZeRO 策略值得特别关注,因为其通信模式从根本上不同于标准数据并行(Rajbhandari et al. 2020)。在标准数据并行中,每个 GPU 持有模型的完整副本,在本地计算梯度,并在反向传播结束时通过单次 AllReduce 进行同步。完全分片通过将模型参数跨 GPU 分区来消除这种冗余,因此每个 GPU 仅在必须重建某层的时刻持有其本地分片。

由于每个 GPU 现在仅持有 1/N 的参数,它必须在使用某层之前重建完整张量,而这种重建正是改变通信模式的原因。在每层的前向传播之前,GPU 调用 AllGather 从所有其他 GPU 收集分片。在反向传播之后,梯度通过 ReduceScatter 进行归约和重新分发,因此每个 GPU 仅持有其自身参数分片的梯度。然后可以丢弃分片参数(从内存释放),直到下一次前向传播需要它们。

其后果是开销的转移:分片缓解了内存瓶颈,但增加了通信频率。标准数据并行每训练步通信一次(对完整梯度进行单次 AllReduce)。FSDP 每层每步通信两次(前向时 AllGather,反向时 ReduceScatter),但每次通信较小(仅该层的参数,跨秩分片)。对于具有 N[L] 层的模型,FSDP 每步发出 2*N[L] 次集合操作,而不是 1 次;跨所有这些操作,总通信量可能与单次完整梯度 AllReduce 相当,但分散在许多较小的操作中。

较高的操作次数使 FSDP 对延迟(α)的敏感度高于标准数据并行。如果每个 2*N[L] 次集合操作都支付完整的 NCCL 启动开销(根据表 6.3 为每次操作 25–50 μs),则 100 层模型的聚合开销为 5–10 ms,这可能占步时间的有意义比例。FSDP 实现通过预取(在当前层计算时启动下一层的 AllGather)和通信流流水线(使用专用 CUDA 流进行通信,与计算流重叠)来缓解这一问题。

在每次调用中选择正确的集合算法需要理解这些不对称模式。FSDP 中的 AllGather 操作规模中等(一层参数,Transformer 模型通常为 10–100 MB)且对延迟敏感(因为计算会阻塞直到完整参数可用)。ReduceScatter 操作规模相似,但可以与下一层的反向计算重叠。这种不对称意味着 AllGather 操作更受益于低延迟算法(树形或混合),而 ReduceScatter 操作可以使用带宽最优算法(环形),因为其延迟被计算隐藏。

上述集合原语目录定义了必须通信什么;现在的问题变成了如何在真实网络上高效执行这些原语。最关键的性能原语 AllReduce 允许多种算法实现,其延迟和带宽特性相差数量级。算法的选择可以将 AllReduce 时间改变一个数量级,使其成为分布式训练中后果最重大的单一实现决策。

构建高效流:AllReduce 算法

一个正确的 AllReduce 必须将所有 1,000 个 1GB 梯度的精确总和提供给所有 1,000 个 GPU,而交付它的算法决定了集群的扩展上限。朴素的答案——每个 GPU 发送到一台中心服务器,其网卡瞬间饱和——首先失败,这使其成为切入点。

朴素方法与带宽瓶颈

考虑使用参数服务器(星型拓扑)的朴素实现。所有 N 个工作节点将梯度发送给秩 0;秩 0 对它们求和并将结果发回。约束在于秩 0 的带宽:它必须接收 N × M 字节并发送 N × M 字节,因此时间增长为 T ∝ N × M/β。在 1,000 个 GPU 时,这使得中心秩成为扩展瓶颈,而非算力。

这种朴素方法捕捉了早期分布式 ML 框架必须管理的压力:参数服务器系统通过服务器层接收、聚合并重新分发模型更新,而实际实现对参数空间进行分片以分散该负载(Dean et al. 2012; Li et al. 2014)。分片将每服务器流量从 N × M 降低到 N × M/K[srv](其中 K[srv] 为服务器数量),但服务器层仍随工作节点和模型状态增长而增长,并引入了参数分区和一致性管理的额外复杂性。

根本限制在于任何星型拓扑方法都将流量集中在中心点。无论参与多少服务器,通过中心层的聚合流量均按 O(N × M) 缩放。要实现真正的可扩展性,我们需要每个节点的通信量无论 N 如何均为常数的算法。此属性称为带宽最优性,是可扩展集合算法的设计目标。

背景:2017 年,Alexander Sergeev 和 Mike Del Balso 在 Uber 的团队开源了 Horovod,作为 Uber Michelangelo ML 平台的一部分,TensorFlow 训练作业需要在许多 GPU 上扩展,而无需强迫每个模型作者重写大量训练代码(Sergeev and Balso 2018)。

失效模式:TensorFlow 的原生分布式训练依赖参数服务器模式,将梯度流量集中在中心层,并施加不可忽视的通信开销。扩展至单节点之外需要大量代码修改,且星型拓扑聚合随工作节点数量增长而退化。

后果:Horovod 采用了百度的 Ring-AllReduce 草案实现,并用极小的 API 表面(只需在现有 TensorFlow 代码中添加几行)封装了高效的环形归约(Gibiansky 2017;Sergeev 和 Balso 2018)。带宽瓶颈从中心服务器转移到了带宽最优的集合通信上,使得不具备分布式系统专业知识的团队也能进行分布式训练。

系统教训:集合算法也是一种开发者接口。当通信原语既带宽高效,又简单到让团队真正能正确使用它时,扩展性就会提升。

带宽最优下界

在研究具体算法之前,建立理论下界很有用。在任何正确的 AllReduce 中,每个 GPU 起始拥有 M 字节的本地数据,最终拥有 M 字节的全局归约数据。最终结果的每个字节都包含来自所有 N 个 GPU 的信息,这意味着每个 GPU 必须至少接收 M ⋅ (N − 1)/N 字节的“新”信息(来自所有其他 GPU 的贡献)。对称地,每个 GPU 也必须至少发送 M ⋅ (N − 1)/N 字节(其对其他 GPU 结果的贡献)。

因此每个 GPU 的最小总传输量为 2 ⋅ M ⋅ (N − 1)/N 字节(发送加接收)。除以链路带宽 β 即得带宽下界

\[T_{\text{bandwidth}}^{\text{min}} = \frac{2(N-1)}{N} \cdot \frac{M}{\beta} \]

N 很大时,这趋近于 2M/β,与 N 无关。达到该界的算法即为带宽最优。环形 AllReduce 精确达到该界;简单树形变体则不能(Patarasuk 和 Yuan 2009;Thakur 等 2005)。这一区别有实际后果:在同步 1 GB 梯度的 64-GPU 集群上,最优算法与仅对数级算法的带宽差异每步达数毫秒,在多日训练中会累积为数小时。

环形 AllReduce

环形 AllReduce⁷⁶ 将节点排列成逻辑环(0 → 1 → … → N−1 → 0)。它通过流水线实现带宽最优:每个节点在每条链路上同时发送和接收(Patarasuk 和 Yuan 2009)。

算法将大小为 M 的向量分成 N 个块,随后在两个命名阶段交替进行通信与本地归约:Scatter-Reduce(散射-归约)后接 AllGather(全收集)。算法 2 在图解和时序图使数据流具体化之前,陈述了其机制。

算法 2 环形 AllReduce

前置条件N 个秩构成逻辑环;每个秩 i 起始拥有 M 字节的张量 G[i],已分成 N 个块

后置条件:每个秩均持有 \(G = \sum_{i=0}^{N-1} G_i\)

  1. 分配环顺序 0 → 1 → … → N−1 → 0

  2. for N−1 个 scatter-reduce 步骤 do

  3. 每个秩向右发送一个块(或部分和),从左接收一个,并将其加入匹配的本地和

  4. end for

  5. 每个秩现拥有 G 的一个完全归约的块

  6. for N−1 个 all-gather 步骤 do

  7. 每个秩将一个已归约块沿环转发,并存储接收到的块

  8. end for

  9. return 完整的归约张量 G,驻留在每个秩上

跨越 2(N−1) 个发送/接收轮次,每轮每个秩移动 M/N 字节,共发送 2(N−1)M/N 字节且接收相同量:带宽项达到信息论下界,而延迟项增长为 2(N−1)α。Scatter-Reduce 阶段的数据流如图 6.5 所示。

图 6.5:环形 AllReduce 数据流:逻辑环上排列的节点分两个阶段交换块:Scatter-Reduce 阶段(每个节点累积一个块的部分和)随后是 AllGather 阶段(完整和沿环传播到所有节点)。图中环水平展开并由环绕总线闭合;每条链路同步被利用,实现带宽最优性。

图 6.5 中可见的关键特性是每条链路在每一步都同时承载数据,没有链路空闲。这种均匀的链路利用率使环形 AllReduce 成为带宽最优:每个节点跨两阶段总共恰好发送 2(N−1)/N ⋅ M 字节,匹配信息论下界。D.3.1 节通过并列示例对比了环形、树形和递归减半-加倍,展示了最佳算法如何随消息大小变化,给出具体的交叉数值,供读者结合此处推导的各算法分析进行权衡。

为使环形算法具体化,考虑 4 个 GPU 通过求和归约 4 元素向量的逐步跟踪。每个 GPU i 起始持有本地梯度向量 g[i] = [a[i], b[i], c[i], d[i]]。向量分成 4 个块(每 GPU 一个),算法经两阶段进行。

设置:4 个 GPU 成环(0 → 1 → 2 → 3 → 0)。每 GPU 持有一个 4 值向量。为清晰起见使用具体数值:

  • GPU 0:[1, 5, 3, 7]

  • GPU 1:[2, 6, 4, 8]

  • GPU 2:[3, 7, 5, 9]

  • GPU 3:[4, 8, 6, 10]

预期结果(逐元素求和):[10, 26, 18, 34]

每 GPU“拥有”一个块:GPU 0 拥有块 A(元素 0),GPU 1 拥有块 B(元素 1),以此类推。

阶段 1:Scatter-Reduce(3 步)

每个 GPU 向右邻居发送一个块或部分和,接收方将收到的值加入其本地副本。表 6.6 跟踪所有 4 个 GPU 上的三个顺序步骤:

| 步骤 | GPU 0 发送 | GPU 1 发送 | GPU 2 发送 | GPU 3 发送 | 接收并加后 |

| --- | --- | --- | --- | --- | --- |

| 1 | 块 A (1) → GPU 1 | 块 B (6) → GPU 2 | 块 C (5) → GPU 3 | 块 D (10) → GPU 0 | GPU 0: D=17, GPU 1: A=3, GPU 2: B=13, GPU 3: C=11 |

| 2 | 块 D (17) → GPU 1 | 块 A (3) → GPU 2 | 块 B (13) → GPU 3 | 块 C (11) → GPU 0 | GPU 0: C=14, GPU 1: D=25, GPU 2: A=6, GPU 3: B=21 |

| 3 | 块 C (14) → GPU 1 | 块 D (25) → GPU 2 | 块 A (6) → GPU 3 | 块 B (21) → GPU 0 | GPU 0: B=26, GPU 1: C=18, GPU 2: D=34, GPU 3: A=10 |

表 6.6:环形 AllReduce Scatter-Reduce 跟踪:4 个 GPU 上的三个顺序步骤。

阶段 1 后,每个 GPU 持有恰好一个块的完整和:GPU 0 持有元素 B 的全局和 (26),GPU 1 持有 C (18),GPU 2 持有 D (34),GPU 3 持有 A (10)。

阶段 2:AllGather(3 步)

每个 GPU 将其完全归约的块沿环发送,使所有 GPU 接收所有结果。表 6.7 展示完成集合操作的三个转发步骤:

| 步骤 | 动作 | 结果 |

| --- | --- | --- |

| 4 | 每个将其完整块发送给右邻居 | 每 GPU 现有 4 个最终块中的 2 个 |

| 5 | 继续转发 | 每 GPU 现有 4 个最终块中的 3 个 |

| 6 | 最终转发 | 所有 GPU 持有 [10, 26, 18, 34] |

表 6.7:环形 AllReduce AllGather 跟踪:每个 GPU 将其完全归约的块沿环转发的三个顺序步骤。

每 GPU 的总数据移动量由环形调度固定:在 2(N−1) = 6 个步骤中,每步每 GPU 恰好发送 M/N = 1 个元素。每 GPU 共计:发送 6 × 1 = 6 个元素,接收 6 个。

该跟踪在继续前留下两个机制待验证:2(N−1) 的步数,以及每个秩在每一步中都同时发送和接收。

来自共同起点的两条趋势线。一条陡峭的红线(Ring,延迟随节点数线性增长)高于一条近乎平坦的蓝线(Tree,延迟随节点数对数增长),随着集群规模扩大,两者差距逐渐拉大。

Ring 延迟随 N 线性增长;Tree 延迟保持对数增长。

验证您对带宽最优归约的理解:

上图展示了 Ring AllReduce 的关键特性:在每一步中,环中的每条链路都是活跃的,数据朝同一方向流动。没有 GPU 会处于空闲状态,也没有链路被低估利用。这种均匀的链路利用率正是 Ring 达到带宽最优的原因。

性能直接取决于算法结构。每个节点在 \(2(N-1)\) 步中的每一步都发送和接收 \(\frac{M}{N}\) 字节数据。

\[T_{\text{ring}} = \underbrace{2(N-1)\alpha}_{\text{延迟项}} + \underbrace{2\frac{N-1}{N} \frac{M}{\beta}}_{\text{带宽项}} \]

  • 带宽:当 \(N \to \infty\) 时,该项趋近于 \(2M/\beta\)。这在理论上是最优的(每个字节只需发送一次并接收一次)。

  • 延迟:延迟随 \(N\) 线性扩展。对于 10,000 个节点,20,000 次顺序跳转会产生巨大的延迟。这就是为什么 Ring 不适合小消息。

Tree AllReduce(树形归约)

Ring AllReduce 为其带宽最优性付出了高昂的延迟代价。当集群扩展到数千个 GPU 且消息大小适中(几兆字节)时,\(2(N-1)\alpha\) 延迟项远大于带宽项,算法将大部分时间花费在顺序跳转上,而非有效的数据传输。为解决这种线性延迟,Tree AllReduce 采用二叉树结构,分两阶段工作。在归约阶段,叶子节点向父节点发送数据,父节点对接收值求和并向上传递,在 \(\log_2 N\) 步内到达根节点。在广播阶段,根节点将结果向下分发回叶子节点,同样耗时 \(\log_2 N\) 步。

Tree AllReduce 的带宽效率较差,原因是链路利用率低。在 Ring AllReduce 中,每个节点同时发送和接收,因此每一步所有 \(N\) 条链路均处于活跃状态。在 Tree AllReduce 中,任意时刻仅有一小部分链路活跃,且该比例随层级上升而收缩。在叶层,\(N/2\) 个节点向 \(N/2\) 个父节点发送,导致一半节点作为空闲接收者;再上一层,\(N/4\) 个节点向 \(N/4\) 个父节点发送,四分之三节点空闲;在根节点,仅两个节点通信,其余 \((N-2)\) 个节点全都空闲。结果是,虽然 Ring 在均衡环上针对大消息可实现接近 100% 的链路利用率,但简单的树形结构会让许多链路闲置,并将流量集中在根节点或内部链路上。

由此产生的时间复杂度取决于具体的树形变体。简单的先归约后广播树具有对数延迟,但流量不均匀且存在根/内部节点瓶颈;递归加倍式变体可能导致每个秩产生 \(\mathcal{O}(\log N)\) 全消息带宽开销。下面的简化模型捕捉了类树集合操作的延迟优势和潜在带宽惩罚:

\[T_{\text{tree-like}} \approx \underbrace{2\log_2 N \cdot \alpha}_{\text{延迟}} + \underbrace{2 \log_2 N \frac{M}{\beta}}_{\text{全消息交换变体中的带宽惩罚}} \]

延迟项为对数级(\(\mathcal{O}(\log N)\)):对于 1024 个节点,Ring 需 2046 步,而 Tree 仅需 20 步,这 100 倍的步数减少使 Tree 成为对延迟敏感的集合操作(如张量并行的逐层 AllReduce)的首选算法,此类场景消息量小但频率高。带宽项承担了惩罚,因为类树算法可能导致链路利用率低或流量集中在内部链路,且递归加倍式的全消息交换引入了 \(\log_2 N\) 带宽因子;这些惩罚使得朴素树形变体成为数据并行大梯度 AllReduce 的不佳选择,后者的带宽效率决定了网络是饱和还是部分空闲。

当实现反复交换全消息或造成根/内部瓶颈时,这种带宽惩罚对大规模数据并行是灾难性的。对于跨 1,000 个 GPU 进行的 70B 参数模型的 140 GB 梯度 AllReduce,一个带宽低效的类树变体可能抵消掉促使其采用的延迟优势。优化的双树算法正是为了在保持对数延迟的同时,避免这种朴素的带宽惩罚而存在的。

然而,当延迟是主要瓶颈时(通常是消息尺寸较小的小规模集合操作),Tree AllReduce 是正确的选择。典型用例是张量并行所需的对延迟敏感的 AllReduce,它作用于单层内的激活值或权重梯度。对于节点内 8 个 GPU 组通信 1 MB 小张量的场景,延迟从 Ring 的 \(2(8-1)\alpha = 14\alpha\) 降至 Tree 的 \(2\log_2(8)\alpha = 6\alpha\)。该尺寸下的带宽惩罚较小,因此减少顺序启动步骤是划算的。

递归减半-加倍(蝴蝶算法)

Ring 和 Tree 代表了延迟-带宽权衡的两个极端:Ring 是带宽最优但延迟差,而 Tree 是延迟最优但带宽差。第三种方法将 Tree 的对数延迟与更好的带宽利用率结合起来。递归减半-加倍算法(有时称为蝴蝶算法)在 \(\log_2 N\) 轮中运行。在第 \(k\) 轮,每个 GPU 与逻辑编号中距离为 \(2^{(k)}\) 的伙伴交换数据,消息大小在 ReduceScatter 阶段减半,在 AllGather 阶段加倍。

在 ReduceScatter 阶段(前 \(\log_2 N\) 轮),GPU \(i\) 与 GPU \(i \oplus 2^{(k)}\)(索引异或)配对,它们交换各自当前数据的一半。接收后,每个 GPU 将自己的那一半与接收到的那一半求和,然后丢弃另一半。经过 \(\log_2 N\) 轮后,每个 GPU 持有 \(M/N\) 字节的完全归约结果。AllGather 阶段反向执行:每轮伙伴交换其归约后的数据块,每个 GPU 持有的数据量翻倍,直到所有 GPU 拥有完整结果。

递归减半-加倍的性能表现为:

\[T_{\text{butterfly}} = 2\log_2 N \cdot \alpha + 2\frac{N-1}{N} \cdot \frac{M}{\beta} \]

这实现了两全其美:对数延迟(\(\mathcal{O}(\log N)\),如 Tree)和带宽最优的数据移动(\(2(N-1)/N \cdot M/\beta\),如 Ring)。缺点是它需要非邻居通信(GPU \(i\) 必须与 GPU \(i \oplus 2^{(k)}\) 通信,后者在物理上可能很远),且要求 \(N\) 为 2 的幂。对于 \(N\) 非 2 的幂的集群,需要额外的复杂性来处理不规则情况。

递归减半-加倍用于 MPI 风格的集合实现,也是延迟-带宽权衡中的一个有用理论参考点 (Thakur et al. 2005)。NCCL 的实现家族包括 ring、tree-derived、hierarchical、NVLink/NVSwitch-aware 和 pattern-aware 等选择,具体名称和可用性随版本和拓扑变化 (Jeaugey 2017; NVIDIA 2026b)。持久的要点不在于某个特定版本的标签:实用的通信库通过拓扑感知成本模型来选择算法,因为非局部通信模式可能在共享网络链路上造成争用,从而削弱理论优势。

序列并行与混合专家路由:拓扑敏感性

序列并行和混合专家(MoE)路由最敏锐地暴露了递归减半-加倍算法的拓扑敏感性。在序列并行中,AllGather 重构激活分片,ReduceScatter 沿序列维度重新分发它们——秩间交换调度遵循与蝴蝶算法相同的距离加倍逻辑。在 MoE 路由中,每个令牌必须到达任意 N 个专家 GPU 中的一个,产生了相同的扇出模式:蝴蝶式调度在最后一轮产生跨边界配对,因为令牌分发必须遍历完整的集群直径。以下 8-GPU ReduceScatter 追踪精确展示了这些长距离跨越是如何出现的。

8-GPU ReduceScatter 追踪

为了使其具体化,考虑 8 个 GPU 的 ReduceScatter 阶段,该过程在 log2 = 3 轮中进行。

  • k = 0 轮:每个 GPU i 与 GPU i ⊕ 1 配对,形成邻居对:(0,1)、(2,3)、(4,5)、(6,7)。它们交换一半数据并进行归约。

  • k = 1 轮:距离加倍:GPU i 与 GPU i ⊕ 2 配对,形成对 (0,2)、(1,3)、(4,6)、(5,7)。

  • k = 2 轮:距离再次加倍:GPU i 与 GPU i ⊕ 4 配对,形成对 (0,4)、(1,5)、(2,6)、(3,7)。

这种模式的问题在真实硬件上变得清晰。第 k = 2 轮的配对强制跨越物理边界进行通信。如果 GPU 0-3 在一个节点上,GPU 4-7 在另一个节点上,最后一轮会在两个节点间产生跨节点流量。对于我们的 1,000-GPU 集群(算法上近似为 1,024),最后一轮将 GPU i 与 GPU i ⊕ 512 配对,迫使集群前半部分的 GPU 与后半部分的伙伴通信,并可能淹没连接服务器机架的高层网络结构。这种拓扑无关的通信模式,正是蝴蝶算法的理论最优性无法转化为大型分层集群上的实践优势的原因。

双二叉树

NCCL 可以针对许多消息大小和拓扑使用双二叉树,这解决了标准二叉树的带宽低效问题,且不需要蝴蝶算法的非本地通信(Jeaugey 2017;NVIDIA 2026b)。其思路是构建两棵独立的二叉树,它们共同覆盖所有链路,然后同时运行这两棵树,每棵树承载一半的数据。

在标准二叉树中,每一层级只有半数链路处于活跃状态(另一半闲置,因为那些节点在接收而非发送)。通过构建第二棵互补树(以不同节点为根,其边覆盖第一棵树未使用的链路),两棵树可以并行运行。每棵树传输 M/2 字节,由于它们的链路利用是互补的,总链路利用率接近 100%,在保持树算法 𝒪(log N) 延迟的同时,达到了环算法的带宽效率。

组合性能约为:

T_double-tree ≈ 2log₂ N · α + 2M/β

在实践中,带宽项趋近于 2M/β(最优),同时保持对数级延迟。这使得双二叉树成为跨越广泛消息大小和集群规模的有力选择,这也是通信库在许多配置中选择它的原因。该算法需要仔细构建两棵互补树,以确保它们不产生链路争用,NCCL 的拓扑感知图搜索在初始化期间解决了这一问题。

双二叉树在消息大小上的广泛竞争力,解释了为什么 PyTorch DDP 和 FSDP 的桶大小(通常为 25-100 MB)经常恰好落在环算法和纯树算法均非明显最优的交叉区域。25 MB 的桶在中等规模集群上接近环-树交叉点,而 100 MB 的桶在慢速网络上开始倾向于环算法。双二叉树很好地处理了这一中间地带,这也是 NCCL 为许多 DDP/FSDP 梯度同步调用动态选择它、而非无条件承诺使用环算法的原因之一。

AllReduce 算法对比

表 6.8 总结了 AllReduce 算法对比及四种算法的性能特征。正如图 6.6 所示,算法选择取决于消息大小:树算法主导小消息,环算法赢得大消息。

| 算法 | 延迟 | 带宽 | 带宽最优? | 约束 |

| --- | --- | --- | --- | --- |

| | 𝒪(N)α | 2\frac{N-1}{N}\frac{M}{\beta} | 是 | 无 |

| | 𝒪(log N)α | \mathcal{O}(\log N)\frac{M}{\beta} | 否 | 无 |

| 蝴蝶 | 𝒪(log N)α | 2\frac{N-1}{N}\frac{M}{\beta} | 是 | N = 2^{(k)} |

| 双树 | 𝒪(log N)α | ≈\frac{2M}{\beta} | 接近最优 | 互补树构建 |

表 6.8:AllReduce 算法对比:每种算法在延迟-带宽权衡空间中占据不同位置。环和蝴蝶算法以不同的延迟特性实现带宽最优,而双二叉树提供了最佳的实用折中方案。

图 6.6:集合通信中的算法交叉点

图 6.6:集合通信中的算法交叉点:针对拥有 200 Gbps 互联的 256-GPU 集群,环、树和双二叉树 AllReduce 算法在消息大小(1 KB 至 10 GB)上的性能分析。

算法交叉点

图 6.6 中的交叉点(环超越树)源于令 T[ring] = T[tree] 并求解 M。完整的时间方程使权衡显式化:

T_ring = 2(N-1)α + 2\frac{N-1}{N}\frac{M}{\beta}
T_tree = 2log₂ N · α + 2log₂ N · \frac{M}{\beta}

对于大 N,(N − 1) ≈ N 且 \frac{N-1}{N} ≈ 1,因此环付出的延迟项与 N 成正比,同时以接近带宽极限的速度移动每个字节:

T_ring ≈ 2Nα + \frac{2M}{\beta},  T_tree ≈ 2log₂ N · α + \frac{2log₂ N · M}{\beta}

树呈现相反形状:对数级启动成本,但每个消息字节需经过更多阶段。令估值相等,可分离出较廉价的延迟路径不再补偿额外带宽工作的消息大小:

2Nα + \frac{2M}{\beta} = 2log₂ N · α + \frac{2log₂ N · M}{\beta}
\frac{2M}{\beta} - \frac{2log₂ N · M}{\beta} = 2log₂ N · α - 2Nα
\frac{2M}{\beta}(1 - log₂ N) = 2α(log₂ N - N)

由于对于大型集群 N ≫ log[2]N,(log[2]NN) ≈ −N 且 (1 − log[2]N) ≈ −log[2]N,可得:

\frac{2M}{\beta}(-log₂ N) ≈ 2α(-N)
M_crossover ≈ \frac{N · α · β}{log₂ N}

作为大致上界的心智模型,工程师有时会省去对数因子:

M_crossover ≈ N · α · β

带对数的交叉公式提供了有用的直觉,而非硬性选择规则。交叉点以上,消息大小占主导,环的带宽效率获胜;以下,启动延迟占主导,树的对数深度获胜。

对于 α = 5 μs、β = 50 GB/s、N = 100 的集群,直接近似给出:

M_crossover ≈ \frac{100 × 5·10^{-6} × 50·10⁹}{log₂ 100} ≈ 3.8 MB

Ring 与 Tree AllReduce:交叉点分析

理解算法选择

在实际应用中,NCCL 的算法选择比简单的二元分割更为细致。双二叉树算法提供了对数延迟和接近最优带宽,使其在消息大小的广泛范围内既能与 Ring 算法竞争,也能与 Tree 算法竞争。NCCL 还会考虑通道数量(并行通信流)、网络拓扑以及是使用节点间还是节点内链路。有效的选择逻辑类似于以(消息大小、GPU 数量、拓扑类型)为索引的多路决策树,而非单一交叉点。

尽管如此,交叉公式仍然是理解库为何做出特定选择的核心心智模型。当库选择了一个意外的算法时,交叉分析提供了评估该选择是否正确或是否需要手动覆盖的推理框架。

具体示例:缓冲区大小计算

问题:一个集群在 64 个 GPU 上同步一个 1 MB 的缓冲区。网络的延迟为 α = 10 μs,带宽为 β = 10 GB/s。哪种算法表现更好:Ring 或 Tree?

数学公式

延迟

  1. Ring 延迟2 × (N - 1) × α = 2 × 63 × 10 μs = 1260 μs

  2. Tree 延迟2 × log₂(N) × α = 2 × 6 × 10 μs = 120 μs

带宽(注意差异)

  1. Ring 带宽2 × (N-1)/N × M/β ≈ 2 × 1 MB / 10 GB/s = 196.9 μs(最优:每个字节仅发送一次)

  2. Tree 带宽2 × log₂(N) × M/β = 12 × 1 MB / 10 GB/s = 1200 μs(每个级别发送完整消息)

表 6.9 将延迟和带宽贡献相加,进行直接对比:

| 算法 | 延迟 | 带宽 | 总计 |

| :--- | :--- | :--- | :--- |

| Ring | 1260 μs | 196.9 μs | 1456.9 μs |

| Tree | 120 μs | 1200 μs | 1320 μs |

表 6.9:64 个 GPU 上 1 MB 缓冲区的 Ring 与 Tree AllReduce 时间比较:延迟、带宽和总时间项,α = 10 μsβ = 10 GB/s

Tree 获胜,但仅领先 9%。对于此 1 MB 消息,我们正处于交叉点附近。

系统洞察:Ring 的延迟惩罚(比 Tree 差 10 倍)几乎抵消了 Tree 的带宽惩罚(比 Ring 差 6 倍)。考虑对数的交叉点估计公式预测 M_crossover ≈ N × α × β / log₂(N) = 64 × 10 μs × 10 GB/s / 6 ≈ 1.1 MB。在 1 MB 时,我们略低于交叉点,因此 Tree 胜出。在 10 MB 时,Ring 将占据主导。

实际情境

交叉点位于 DDP 和 FSDP 工作负载在正常训练过程中遍历的范围内。

  • 1 MB 的消息对应于较小 transformer 中单个前馈层的梯度,或 MoE 令牌路由有效载荷——Tree 算法的领域。

  • 中等规模 transformer 层的 10 MB DDP 梯度桶位于交叉点附近。

  • 跨多层融合的 DDP 桶可达 50–300 MB;70B 参数模型的完整 BF16 梯度张量可达 1120 GB——两者均牢固处于 Ring 算法的领域。

大多数实际训练工作负载会随着桶大小的调整而遍历整个范围,而交叉分析设定了算法边界,该边界区分了 bucket 级别的 Tree 操作和全模型的 Ring 同步。

交叉分析表明,算法选择不是静态的,而是取决于消息大小、集群规模和网络参数的具体组合。在实践中,像 NCCL 这样的通信库维护内部查找表,将消息大小、GPU 数量、拓扑和协议设置映射到诸如 Ring、Tree 或混合方法之类的选定算法。理解底层的交叉数学使工程师能够预测库的内置选择何时可能次优,并在必要时进行覆盖。

验证

验证您对 Ring 与 Tree AllReduce 权衡的理解:

分层通信

上述算法假设网络是平坦的,即每个链路具有相同的带宽。实际数据中心违反了这一假设,程度高达一个数量级或更多。并非所有链路都是平等的。

分层通信是对这种不平等的响应。节点内链路丰富且快速,节点间链路稀缺且昂贵,算法必须尽可能在跨越稀缺层之前缩减负载。这就是为什么同样的 AllReduce 可以实现为局部 ReduceScatter、跨节点缩减和局部 AllGather,而不是在每个 rank 上进行一次平坦的交换。

一个 GPU 节点通过节点内带宽(NVLink 为 900 GB/s)提供的带宽是节点间带宽(InfiniBand 为 50 GB/s)的一个数量级。

分层 AllReduce

上述算法(Ring、Tree、Butterfly、双二叉树)都假设网络是平坦的,即每个链路具有相同的带宽。这一假设在单个节点内成立(其中所有 GPU 通过具有相等带宽的 NVLink 相连),但在多节点集群中会失效 9 倍。真实集群是分层网络,各层具有根本不同的带宽,如表 6.10 所量化。

| 层级 | 互连 | 带宽 | 相对速度 |

| :--- | :--- | :--- | :--- |

| 节点内 | NVLink 4.0 | ~900 GB/s | 9× 更快 |

| 节点间 | InfiniBand NDR 400G | ~50 GB/s | 1×(基准) |

表 6.10:带宽层次结构:现代 GPU 集群在节点内和节点间通信上存在数量级的带宽差距。分层算法利用这一差距。相对速度比较的是单向速率(NVLink 的单向 450 GB/s 对 InfiniBand 每端口的 50 GB/s);带宽列显示的是 NVLink 的数据表双向总和。

纯粹的平坦 Ring AllReduce 无视这一结构,可能在 NVLink 就能满足需求时仍在 InfiniBand 上路由数据,从而浪费稀缺的节点间带宽。分层 AllReduce 将全局操作分解为三个阶段,以尊重带宽层次结构,如图 6.7 所示。

带有 M、M/8 和 M/32 载荷的三级有效载荷阶梯,并标注 32x。

分层集合在跨越慢速层之前先缩减载荷。

图 6.7:分层 AllReduce:利用节点内/节点间带宽差距的三阶段分解。第一阶段在 NVLink 上执行节点内聚合(一个充当 ReduceScatter 的 NVLink 环,使每个 GPU 保持一个节点分片的部分和),从而将节点间流量减少 G 倍(每节点 GPU 数)。第二阶段在相应的 GPU 上使用 InfiniBand 执行 AllReduce。第三阶段在 NVLink 上完成节点内分发(一个 AllGather,将每个 GPU 返还到完整结果)。图中通过它们的 NVLink/IB 机制标注这些阶段;下面的正文则命名对应的集合原语。

图 6.7 中的三阶段分解将昂贵的节点间流量限制在第二阶段,此时每个 GPU 仅传输 M/G 字节而非 M。假设每节点有 G = 8 个 GPU(典型的 DGX 配置),这将节点间流量减少 8 倍,相当于将稀缺的 InfiniBand 带宽乘以每节点的 GPU 数量。

这些阶段形成一条因果链:

  1. 节点内 ReduceScatter(通过 NVLink):每个节点中的 G 个 GPU 之间的局部缩减,使每个 GPU 拥有 1/G 的部分缩减数据,仅使用丰富的节点内带宽。

  2. 节点间 AllReduce(通过 InfiniBand):每个 GPU 将其节点分片发送到对应的 GPU 位置,仅传输 M/G 字节而不是 M

每个节点在内部重新分配最终分片,在不消耗额外节点间带宽的情况下完成结果。

一个简单的 8 节点预算能具体说明这种带宽乘法效应:

问题:集群有 8 个节点,每个节点有 8 个 GPU(共 64 个)。系统必须对 1 GB 梯度缓冲区执行 AllReduce。对比扁平 Ring AllReduce 与分层 AllReduce。

扁平 Ring AllReduce(忽略层级)

  • 每个 GPU 总共发送约 2 GB(带宽最优的 Ring AllReduce 公式)。

  • 环多次跨越节点边界。

  • 有效带宽:受限于最慢链路 = 50 GB/s (InfiniBand)。

  • 时间:≈ 2 × 1 GB / 50 GB/s = 40 ms(带宽项占主导)。

分层 AllReduce(三步分解)

  1. 节点内 ReduceScatter:每个 GPU 以双向 450 GB/s 发送 875 MB → ~1.94 ms

  2. 节点间 AllReduce:每个 GPU 在 InfiniBand 上 AllReduce 一个 1 GB / 8 = 125 MB 的分片;环移动大约 2(N − 1) / N 倍的载荷,加上微小的延迟项后,该阶段在 50 GB/s 下耗时 ~4.42 ms(只有 1/8 的数据跨越 InfiniBand!

  3. 节点内 AllGather:每个 GPU 以双向 450 GB/s 接收 875 MB → ~1.94 ms

  4. 总时间:≈ 1.94 ms + 4.42 ms + 1.94 ms = 8.3 ms

系统洞察:分层 AllReduce 通过在环流量乘数生效前将节点间载荷从 1 GB 降至每 GPU 125 MB,实现了 4.8× 加速。有了每节点 8 个 GPU,我们实际上获得了 8× 表观节点间带宽。这就是为什么 NVIDIA 的 NCCL 和类似库在拓扑支持时,常在多节点集群上选择分层算法。

这三个阶段将大部分流量限制在各节点内部,随后再跨越较慢的节点间网络结构,同一思想可推广到两层以上。由脊叶交换机连接的多机架大型集群引入第三个带宽层级(机架间处于降低的分割带宽)。在 128 个 GPU 按 4 机架 × 4 节点 × 8 GPU 排列且具有 2:1 跨机架过载比的情况下,在每一层应用相同分解,使每 GPU 跨机架载荷比原始梯度缩减 32×——总耗时约 15 ms,而扁平 AllReduce 需 160 ms。分层分解将流量集中在带宽充裕之处,将稀缺带宽处的流量降至最低。

网络内归约:SHARP 及其演进

图 6.8 中的对比展示了下一步:将归约工作从端点 GPU 移入交换机 ASIC,使数据包在穿越网络结构时即被聚合。

图 6.8:网络内归约 (SHARP):由垂直分割线分隔的左右对比。左侧展示传统树形 AllReduce 在中间 GPU 执行归约,产生多次存储转发延迟。右侧展示 SHARP 将归约卸载到网络交换机 ASIC,允许部分和在数据包穿越交换机时以线速聚合。这消除了 GPU 内存流量和存储转发延迟,显著加速中小消息集合操作。

分层 AllReduce 减少了跨节点流量体量,但聚合仍需多次网络往返。NVIDIA 的可扩展分层聚合与归约协议 (SHARP) 落地了这一理念:梯度不再传送到目标 GPU 求和,而是 InfiniBand 交换机在数据包通过时聚合部分和。

收益有二。第一,SHARP 降低端点内存流量,并通过在数据包离开交换机层级前聚合,可减轻上层链路压力。在基于软件的树形归约中,数据到达 GPU、写入内存、与本地数据求和,再发送到下一层。使用 SHARP 时,交换机即时组合入向包并转发聚合结果以供归约树使用。第二,SHARP 消除了经过中间 GPU 的存储转发路径。每次软件跳转增加完整的 α-β 成本;而交换机内聚合将归约放在交换机 ASIC 内部,而非端点内存中。

实际影响在聚合延迟对总 AllReduce 时间有显著贡献的消息大小下最为明显。对于极大消息,带宽占主导,SHARP 的延迟降低占比变小。对于极小消息,固定的交换机处理开销可能限制收益。Graham 等人 (2020) 报告,在 HDR/Quantum InfiniBand 交换机上启用 SHARP Streaming-Aggregation 时,归约带宽提升数倍,PyTorch 工作负载的应用层增益也有提升。

强扩展战役——在不成比例增加批大小的情况下跨更多加速器训练固定模型——正是 SHARP 的延迟降低能转化为步时间缩减的工作负载。随着每加速器批大小缩减,每个梯度张量保持较大(正比于模型大小),但每加速器的反向传播缩短,通信占步时间比例上升,环公式中的延迟项 α 成为瓶颈。交换机内聚合直接削减该延迟成本。交换机 ASIC 厂商已明确扩展 InfiniBand ASIC 以支持 ML 原生数据类型,包括 FP16 和 BF16,确认 SHARP 正与 ML 工作负载需求协同演进,而非仅服务于推动其最初设计的传统 HPC 浮点运算。为扩展战役选择 SHARP 基础设施的 ML 系统团队,应核实具体交换机代数是否支持 FP16/BF16 聚合,因为早期 ASIC 仅支持 FP32 和 INT32。

SHARP 也施加约束。交换机必须支持特定归约操作(通常限于浮点和整数类型的求和、最小值、最大值)。并发 SHARP 聚合树的数量受交换机资源限制,因此运行许多并发训练作业的大型集群可能耗尽 SHARP 容量。此外,SHARP 要求 InfiniBand 基础设施;基于以太网的网络结构不可用。

拓扑感知路由

分层 AllReduce 减少节点间流量,SHARP 消除部分流量,但两者均未解决一个更微妙的问题:逻辑通信模式如何映射到物理网络拓扑。64 个 GPU 的 Ring AllReduce 创建一个逻辑环,但哪些物理链路承载每一跳,决定了环是达到峰值带宽还是产生拥塞。在相同硬件上运行同一训练作业两次,通信吞吐量可能相差 2×,取决于秩如何分配给 GPU 以及随之产生的流量模式如何与网络物理结构交互。

NCCL 等通信库在初始化时执行拓扑检测,运行图搜索算法以发现物理网络结构并寻找高带宽通信路径 (NVIDIA 2026b)。库必须确定每对秩的对端是位于本地 NVLink 交换机上,还是 100 米外跨越 InfiniBand 网络结构,因为同一集合算法的吞吐量可能因逻辑秩到物理硬件的映射方式而相差 2× 或更多。

拓扑检测流程始于硬件枚举

NCCL 查询 PCIe 总线以发现 GPU 布局、GPU 间的 NVLink 连通性以及 NIC 到 PCIe 交换机的亲和性。根据这些信息,它构建一个内部图,其中节点代表 GPU,边代表带有带宽和延迟标注的物理链路。随后,图搜索算法寻找在最大化聚合带宽的同时最小化跨域跳数(NVLink、PCIe 和 InfiniBand 域之间的转换)的通信路径。这种拓扑感知的路径选择使得 NCCL 能够在配置良好的系统上接近理论峰值带宽,而拓扦感知实现可能会让大量带宽闲置。

环面拓扑(TPU Pod)

图 6.9 中的维度有序归约展示了为何环面结构直接使用物理网格,而非假装 Pod 是一个扁平的全连接网络。

图 6.9:环面拓扑上的维度有序归约:在二维环面网格中(三维 TPU Pod 的简化表示),AllReduce 被分解为沿 X 和 Y 维度的两个顺序步骤。通过一次沿一个维度进行归约,系统最小化了网络直径并避免了链路争用,确保每条直接邻居链路都在满带宽下运行。

Google 的张量处理单元(TPU)Pod 采用三维环面拓扑,每个 TPU 直接连接到 6 个邻居(±X、±Y、±Z);图 6.9 以二维简化形式描绘了相同的维度有序思想。与 InfiniBand 集群的分层胖树拓扑不同,环面提供统一的直接连接:无论 TPU 芯片在网格中的位置如何,每个芯片拥有相同数量的链路(6 条)和相同的单链路带宽。该拓扑的最优 AllReduce 策略是维度有序归约

  1. 对每条固定的 (Y, Z) 线沿 X 维度归约,生成 X 维度上的部分和

  2. 对每条固定的 (X, Z) 线将这些部分和沿 Y 维度归约,生成 X × Y 平面上的部分和

  3. 对每条固定的 (X, Y) 线将这些部分和沿 Z 维度归约,完成全局结果

每个维度有序归约本身就是沿一条轴对齐的环面环进行的 Ring AllReduce。对于拥有 X × Y × Z 个 TPU 的 Pod,第一步为每个固定的 (Y, Z) 坐标运行独立的 X 维度环,因此每个参与者接收该 X 线上的部分和。第二步运行 Y 维度环以跨 Y 合并那些 X 部分和,第三步运行 Z 维度环以完成全局归约。这种级联归约实现了总带宽成本 2M/β(与单个 Ring 相同),但延迟与 2(X + Y + Z) 成正比,而非 2(X × Y × Z),因为每个维度的环都比全局环短。

维度有序归约最小化了网络直径,并确保每条链路在同一时间仅单向传输流量,从而避免拥塞。环面拓扑还通过备选路由路径提供天然的容错能力:如果 X 维度中的一条链路故障,流量可绕道通过 Y 或 Z 维度(代价是增加延迟)。Google 的加速线性代数(XLA)编译器在针对 TPU Pod 时会自动生成维度有序集合操作,向用户屏蔽拓扑细节。

链路优化路由(NVIDIA DGX)

在 NVIDIA DGX 系统中,每个 GPU 拥有专属的 NIC(网络接口卡)。链路优化路由利用这一点,确保不同节点中位置相同的 GPU 仅相互通信:

  • 节点 A 上的 GPU 0 仅与节点 B、节点 C 等上的 GPU 0 通信。

  • 节点 A 上的 GPU 1 仅与其他节点上的 GPU 1 通信。

  • 以此类推,直到 GPU 2–7。

正如表 6.11 所示,这创建了 8 条并行运行且无争用的独立“链路”。

| 链路 | 参与者 | 流量 |

| --- | --- | --- |

| 链路 0 | Node0-GPU0 ↔ Node1-GPU0 ↔ Node2-GPU0 ↔ … | M/8 each |

| 链路 1 | Node0-GPU1 ↔ Node1-GPU1 ↔ Node2-GPU1 ↔ … | M/8 each |

| … | … | … |

| 链路 7 | Node0-GPU7 ↔ Node1-GPU7 ↔ Node2-GPU7 ↔ … | M/8 each |

表 6.11:链路优化流量分布:每条链路独立承载总流量的 1/8。

链路对齐至关重要,因为若无对齐,一个节点上的所有 8 个 GPU 可能尝试同时发送给同一个远程 GPU,导致单个 NIC 上产生 8 倍争用。链路对齐路由确保每个 NIC 精准处理 1/8 的流量,实现全分割带宽利用率。

链路优化路由与分层 AllReduce 相辅相成。在前文所述的分层分解中,步骤 2(节点间 AllReduce)天然与链路拓扑对齐。每个节点上的 GPU i 仅与其他节点上的 GPU i 通信,这正是链路模式。这种对齐并非巧合;NVIDIA 在设计 DGX 硬件时即考虑了链路优化通信,当拓扑被检测且秩映射正确时,NCCL 的分层算法可利用这一结构。当逻辑通信模式与物理拓扑匹配时,每个 NIC 承担其预期的流量份额,集群可逼近其理论峰值分割带宽。

逻辑拓扑与物理拓扑的错位是性能下降的常见根源,若无仔细剖析极难诊断。若进程秩被任意分配(例如由作业调度器在无拓扑感知下分配),分层 AllReduce 可能通过错误的 NIC 路由跨节点流量,形成热点,使有效带宽降低 2–4 倍。大规模部署采用拓扑感知秩分配(通过 NCCL 的 CUDA_VISIBLE_DEVICES 和调度器的 GPU 绑定策略配置)以确保对齐。

分层方法结合拓扑感知路由,代表了本节探讨算法的集大成:α-β 模型识别瓶颈(节点间带宽),分层分解减少跨越该瓶颈的流量,链路优化确保减少后的流量无争用流动。这些技术协同作用,在配置良好的集群上缩小了理论峰值带宽与实测带宽的差距。

带宽稀缺下的梯度压缩

即便经过拓扑调优和算法选择,系统仍可能因物理结构无法足够快地移动梯度载荷而保持带宽受限。在此领域,减少发送比特数成为一种深思熟虑的设计选择,而非通用的最后手段。上一节从调度和拓扑侧面攻克通信瓶颈;载荷压缩则通过改变跨越结构的内容来攻克同一瓶颈。

带宽墙在两种场景下最为严峻:通过广域网连接的数据中心间训练(带宽比 InfiniBand 低 10–100 倍),以及在无 RDMA 或 RDMA 受限的云端商品以太网实例上训练。在这些带宽受限场景下,梯度压缩技术可将通信量减少 4–1000 倍,代价是向优化过程引入噪声。核心张力在于带宽减少与优化过程噪声容忍度之间——优化器能吸收多少压缩而不损害收敛。

量化:降低精度

梯度压缩中的量化与稀疏化

量化是在带宽成为瓶颈时破坏性最小的压缩手段,因为它保留每个梯度元素,但减少用于表示它的位数。大多数梯度在 FP32(32 位浮点数)或 BF16(16 位脑部浮点数)中计算,因此降低位宽可直接减少通信量。关键问题是优化器能容忍多少梯度保真度损失,从温和到激进的量化演进展示了压缩比与梯度保真度之间的权衡:

  1. FP16 (16 位):加速器训练的常见基线。位数仅为 FP32 的一半,对许多模型的收敛影响极小。与 FP32 相比提供 2× 压缩。

  2. INT8 (8 位):将每个梯度向量量化为 256 个离散层级。这需要计算每个张量的缩放因子:g[int8] = round(g/s),其中 s = max(|g|)/127。接收端重构 ĝ = g[int8] × s。与 FP32 相比提供 4× 压缩,但引入的量化噪声与梯度幅度成正比。

  3. 1-bit SGD:极端情况:仅传输每个梯度元素的符号(+1 或 -1)。接收端使用学习到的或自适应缩放因子进行重构。这在 FP32 基础上实现 32× 压缩,但引入大量噪声,若无额外机制会降低收敛性(Seide et al. 2014;Karimireddy et al. 2019)。

用于梯度通信的分块量化

上述量化演进对整个张量使用单一缩放因子,这隐含假设梯度分布在张量中是均匀的。实际上,梯度分布高度非均匀:注意力层产生具有重尾分布的梯度,嵌入层产生极度稀疏的梯度,归一化层产生集中在零附近的梯度。当梯度分布在张量各处变化时,单一张量缩放因子是次优的。小梯度区域在由张量最大值主导的缩放因子量化下会丢失大部分信息。分块量化 通过将梯度张量划分为 b[block] 个元素的块(通常 b[block] = 64 或 128)并为每个块计算独立缩放因子来解决此问题。每个块的缩放因子适应局部梯度分布,以降低量化误差为代价,代价是传输 d[grad]/b[block] 个额外的缩放因子。

对于维度为 d[grad] 的梯度向量,使用块大小 b[block] 量化为 INT8 时,消息大小为量化值的 d[grad] × 1 byte 加上 FP32 缩放因子的 (d[grad]/b[block]) × 4 bytes,总计 d[grad] + 4d[grad]/b[block] 字节。因此有效压缩比为 4d[grad]/(d[grad] + 4d[grad]/b[block]) = 4b[block]/(b[block] + 4),当 b[block] = 128 时达到 3.88×,接近理论 4×。质量提升源于替换全局误差界:分块量化将最大量化误差从 s ⋅ 0.5s 为全局缩放因子)降低到 s[b] ⋅ 0.5s[b] 为分块局部缩放因子),对于注意力层常见的重尾梯度分布,与逐张量量化相比,可将量化误差减少 2–5×。

融合量化-归约操作的接口使分块量化在通信压缩中变得实用,但每一次位宽降低都会引入量化噪声。这种噪声作为一种有偏扰动作用于真实梯度方向。对于激进量化(INT8 尤其是 1-bit),系统性偏差会阻碍收敛,除非系统跨步骤携带误差反馈校正。因此,量化应仅在收敛预算允许的范围内沿精度阶梯向下移动。

稀疏化:仅传输重要梯度

量化通过减少每个梯度元素的比特数来攻击通信量,同时传输每个元素。一种正交方法 稀疏化 从另一个方向切入:保持全精度,但仅传输梯度元素的一个子集,其余置零。

Top-k 稀疏化

最常用的方法是 Top-k 压缩:对于梯度向量 g ∈ ℝ^(d[grad]),仅传输绝对幅度最大的 K[top] 个元素,其余置零(Aji and Heafield 2017;Lin et al. 2018):

TopK(g) = g ⊙ 1[|g| ≥ |g|[(K[top])]]

其中 |g|[(K[top])] 是按幅度排序的第 K[top] 大元素。当 K[top] = 0.001 × d[grad](仅保留 0.1% 的元素)时,可实现 1000× 压缩。

压缩表面数值必须扣除编码稀疏表示的成本。传输稀疏梯度需要同时发送非零值及其索引。对于维度为 d[grad]、包含 K[top] 个非零元素的梯度向量,编码消息包含 K[top] 个值(FP32FP16)加上 K[top] 个索引(通常为 INT32)。使用 FP32 值和 INT32 索引时,总消息大小为 K[top] × (4 + 4) = 8K[top] 字节。因此有效压缩比为 4d[grad]/(8K[top]) = d[grad]/(2K[top])

K[top] = 0.001d[grad] 时,编码消息产生 500× 压缩比,而非仅保留元素比例所暗示的 1000×,因为索引开销显著。将索引大小减小至 INT16(适用于 d[grad] < 65536)或对结构化稀疏模式使用游程编码,可提高有效压缩比。

Top-k 并非唯一的稀疏化规则。Random-k 稀疏化 以均匀概率随机选择 K[rand] 个元素,并按 d[grad]/K[rand] 缩放以维持无偏梯度估计。Random-k 的优势在于即使无误差反馈也是无偏压缩器,因为 𝔼[RandomK(g)] = g。然而,它引入的方差高于 Top-k,因为它以相同概率丢弃大梯度和小梯度。实践中,在相同压缩比下,Top-k 配合误差反馈收敛快于 Random-k,因为 Top-k 在每步保留最具信息量的梯度分量。

收敛性问题

天真地丢弃小梯度会产生系统性偏差。如果某参数持续接收小梯度(如每步 0.01),它将不会更新,因为 0.01 始终低于 Top-k 阈值。经过数千步,这些“丢失”的梯度累积成显著误差,阻止模型收敛到真实最优解。

这不仅是实际问题,更是根本的数学障碍。无校正时,稀疏化 SGD 是真实梯度的 有偏估计量,有偏梯度下降可收敛到任意错误的解。激进量化面临同一问题:将梯度舍入为 INT8 或 1-bit 会引入跨训练步骤累积的系统性舍入误差。误差反馈可解决量化梯度(Seide et al. 2014)和稀疏化梯度(Stich et al. 2018;Karimireddy et al. 2019)的这种累积。

误差反馈:旅程的记忆

我们用误差反馈解决压缩与收敛的冲突。系统立即应用压缩器以回收带宽,然后将丢弃的残差存入本地累加器,并在下一梯度中重新注入。误差被延迟,而非销毁。 我们维护一个本地误差累加器 e[t] 来存储压缩残差。

Error-feedback loop with residual carried into next step.

错误反馈将残差传递到下一个压缩步骤。

错误反馈

错误反馈 是一种分布式训练技术,用于梯度压缩,它为每个工作进程维护一个残差累加器 e[t],并将压缩误差重新注入到下一次梯度更新中,以确保压缩器延迟的信息永远不会被永久丢弃。

  1. 意义:在 1% 时的 Top-k 稀疏化仅保留幅度最大的 1% 的梯度值,使得对于 70B 模型,AllReduce 数据量从 140 GB 减少到 1.4 GB,在稀疏索引开销之前实现了 100× 的有效载荷减少。如果没有错误反馈,被丢弃的 99% 梯度值将导致有偏更新,可能损害收敛;而有了错误反馈,残差会累积,直到被延迟的分量在后续步骤中超过压缩阈值,从而在其假设下恢复了具有内存/错误反馈变体的收敛保证(Stich et al. 2018; Karimireddy et al. 2019)。

  2. 区别:与有损压缩(永久丢弃低于阈值的梯度分量)不同,错误反馈是一种延迟传输策略:残差 e[t] 在步骤之间发生望远镜式抵消,使得当 K → ∞ 时,\(\frac{1}{K}\sum_{t=1}^{K} v_t \to \frac{1}{K}\sum_{t=1}^{K} g_t\),因此传输梯度的长期平均值等于真实梯度的平均值。

  3. 常见陷阱:一个常见的误解是,只要使用压缩器,错误反馈就会自动发生。事实并非如此:训练系统必须显式地保存并重新注入残差。如果没有这种残差路径,Top-k 和 1 位量化将引入可能在步骤间累积的系统性偏差(Karimireddy et al. 2019)。

错误反馈在时间上保存了累积的梯度信息。考虑在 K 步骤中发生的情况:

\[\sum_{t=1}^{K} v_t = \sum_{t=1}^{K} \left[(g_t + e_t) - e_{t+1}\right] = \sum_{t=1}^{K} g_t + e_1 - e_{K+1} \]

如果误差累加器保持有界(对于合理的压缩方案,它确实如此),那么当 K → ∞ 时:

\[\frac{1}{K}\sum_{t=1}^{K} v_t \to \frac{1}{K}\sum_{t=1}^{K} g_t \]

传输梯度的长期平均值等于真实梯度的长期平均值。没有梯度信息被永久丢失;它只是被延迟。反复被丢弃的小梯度最终会在 e[t] 中累积,直到超过压缩阈值并被传输。

压缩误差在时间上的望远镜式性质是错误反馈能够将有偏压缩器转化为收敛训练方法的原因。理论分析表明,在稀疏化 SGD 具有时间记忆以及在广泛压缩算子下的 EF-SGD 中,在其陈述的假设下存在收敛保证(Stich et al. 2018; Karimireddy et al. 2019)。一个简短的追踪展示了错误反馈如何保存 naive 压缩会丢失的梯度信息。表 6.12 在无残差路径的贪婪压缩器上运行了该追踪,仅传输值 ≥ 0.5 的梯度:

| 步骤 | 真实梯度 g[t] | 传输的 v[t] | 累计传输 | 累计真实 |

| --- | --- | --- | --- | --- |

| 1 | 0.4 | 0 | 0 | 0.4 |

| 2 | 0.3 | 0 | 0 | 0.7 |

| 3 | 0.2 | 0 | 0 | 0.9 |

| 4 | 0.4 | 0 | 0 | 1.3 |

| 5 | 0.3 | 0 | 0 | 1.6 |

表 6.12:无错误反馈的 naive 压缩追踪:五步演示追踪,展示了贪婪 1 位量化器在单个梯度低于阈值时传输零的情况。

带有错误反馈的追踪见 表 6.13,它在相同的序列上运行,并使用残差累加器 e[t] 将被丢弃的余量重新注入到下一步。下面的示例展示了两种追踪的运行过程,以证明被延迟的信息是被守恒而非丢失的。

问题:在五个训练步骤中,一个单一参数接收到小梯度(全部低于 0.5)。贪婪压缩器仅传输值 ≥ 0.5 的梯度(向最近的整数取整:0, 0.5) → 0,[0.5, 1.5) → 1,等等)。经过五步后,naive 压缩丢失了多少梯度信息,而错误反馈累加器又是如何恢复它的?

naive 压缩结果:在 [表 6.12 中,经过 5 步后,系统已传输 0,但真实累积梯度为 1.6。参数从未更新,100% 的梯度信息被丢失。

错误反馈结果:在 表 6.13 中,累加器 e[t] 在步骤间望远镜式地传递残差。经过 5 步后,系统已传输 2,剩余误差为 −0.4。真实累积为 1.6,因此传输 + 误差 = 2 + (−0.4) = 1.6 ✓

系统洞察

  1. 未丢失任何信息:(传输 + 误差缓冲区)的和始终等于累计真实梯度。

  2. 小梯度累积:单个梯度为 0.3–0.4,单独传输时太小,但它们会累积直到超过阈值。

  3. 误差在零附近振荡:误差缓冲区 e[t] 不会无限增长;它会在梯度通过传输“偿还”时在零附近振荡。

  4. 收敛得以保存:随着时间的推移,模型接收到的总梯度大致正确,只是有一定的延迟。

| 步骤 | g[t] | e[t] | g[t] + e[t] | v[t] | e[t + 1] = (g[t] + e[t]) - v[t] | 累计 v |

| --- | --- | --- | --- | --- | --- | --- |

| 1 | 0.4 | 0 | 0.4 | 0 | 0.4 | 0 |

| 2 | 0.3 | 0.4 | 0.7 | 1 | −0.3 | 1 |

| 3 | 0.2 | −0.3 | −0.1 | 0 | −0.1 | 1 |

| 4 | 0.4 | −0.1 | 0.3 | 0 | 0.3 | 1 |

| 5 | 0.3 | 0.3 | 0.6 | 1 | −0.4 | 2 |

表 6.13:错误反馈压缩追踪:在错误反馈压缩下的相同五步序列。

1-bit Adam:压缩感知优化

错误反馈为任何压缩方案恢复了收敛保证,但它将优化器视为黑箱。梯度被压缩、传输、解压,然后像什么都没发生一样馈送到优化器。这种分离留下了性能空间:优化器维护着内部状态(动量、方差估计),其中包含关于梯度分布的信息,但压缩完全忽略了这种状态。关键的决定是:压缩应作用于原始梯度,还是作用于已经对它们进行了汇总的优化器状态。

由微软 DeepSpeed 团队先驱的一体化方法,压缩的是 优化器的通信,而不是 原始梯度1-bit Adam (Tang et al. 2021) 观察到,Adam 优化器维护两个动量状态(m[t]v[t]),它们在步骤之间变化缓慢。与其让每个工作进程独立地传输完整梯度并在每个工作进程上重新构建 Adam 状态,不如 1-bit Adam 仅传输动量的符号(每个参数 1 位)以及每个张量的单个 FP32 缩放因子。

算法分为两个阶段。在 预热阶段(通常为训练的 15–20%),标准 Adam 以全精度通信运行,以建立稳定的动量估计。一旦动量方差稳定(由 Var(m[t])/E[|m[t]|]² 的比率降低到某个阈值以下指示),算法切换到 压缩模式。在压缩模式下,每个工作进程计算其局部 Adam 更新,提取自上次通信以来动量差的符号,并传输每个参数 1 位以及每个张量的单个 FP32 缩放因子。

理论依据建立在这样一个观察之上:Adam 的自适应学习率将梯度信息集中到动量项的大小中。一旦这些大小稳定下来,方向(符号)便携带了大部分信息,而大小则可以用单一缩放因子来近似。误差反馈被应用于符号压缩,以确保没有方向信息永久丢失。

1-bit Adam 论文报告称,在匹配未压缩 Adam 收敛速度的同时,通信量最多可减少 5×,在多达 256 块 GPU 的实验中,BERT-Large 预训练的吞吐量最高提升 3.3×,SQuAD 微调的吞吐量最高提升 2.9×(Tang et al. 2021)。实际经验法则比“压缩总是有益”要狭窄得多:压缩开销必须与节省的通信时间相比保持较小。

1-bit Adam 的成功阐释了一个更广泛的原则:当压缩与优化算法协同设计时效果最佳。压缩原始梯度会无差别地丢弃信息。压缩优化器状态则利用了优化轨迹的结构,以更小的收敛影响实现了更高的压缩比。

问题:一个训练作业同步梯度缓冲区需要 40 ms。系统实现了 1-bit Adam,将通信量减少了 32×,但为压缩逻辑增加了 2 ms 的 CPU/GPU 开销。缩放因子、优化器状态协调和协议开销将实际网络收益降低到 8× 有效吞吐量。实际通信加速比是多少?

数学计算

  1. 压缩后通信时间:40 ms 除以 8 = 5 ms。

  2. 新总时间:5 ms(通信)+ 2 ms(开销)= 7 ms。

  3. 有效加速比:40 ms 除以 7 ms ≈ 5.7×。

系统洞察:压缩是用算力换带宽。仅当 T[overhead] < Tcomm × (1 - 1/Ratio) 时才划算。在本例中,花费 2 ms 以节省 35 ms 的网络时间是极佳的交易。然而,在 Tcomm 仅为 1 ms 的高速 NVLink 网络上,相同的压缩逻辑会拖慢训练。在添加压缩之前,始终要对网络进行剖析。

收益计算使压缩成为一项有条件的优化:它必须节省足够的通信时间,以覆盖编码开销和收敛风险。

验证你对何时以及如何应用梯度压缩的理解:

压缩权衡:带宽与收敛

梯度压缩不是免费的;它以优化过程中增加的方差或偏差为代价,换取通信量的减少。表 6.14 中的对比总结了来自量化、稀疏化、误差反馈和优化器感知压缩工作的源码支持模式(Seide et al. 2014;Aji and Heafield 2017;Lin et al. 2018;Stich et al. 2018;Karimireddy et al. 2019;Tang et al. 2021)。

| 方法 | 压缩比 | 收敛影响 | 最佳用例 |

| --- | --- | --- | --- |

| FP16 | 2× | 可忽略 | 常见加速器基线 |

| INT8 + Error FB | 4× | 轻微减速(~5–10%) | 带宽受限集群 |

| Top-k (1%) + Error FB | 100× | 中等减速(~10–20%) | 跨数据中心训练 |

| 1-bit + Error FB | 32× | 显著减速(~20–30%) | 极端带宽约束 |

表 6.14:梯度压缩方法:四种典型梯度压缩器的压缩比与收敛影响权衡。正确的选择取决于带宽还是收敛速度是瓶颈约束;激进的压缩器仅在通信主导迭代时间时才划算。

何时使用压缩

决策规则不仅仅取决于压缩比。只有当每步节省的墙钟时间超过编码开销和因收敛变慢而增加的额外步骤时间时,压缩才值得。当通信时间主导计算时间时(较高的 Tcomm / (T[compute] / N) 比率),激进压缩才划算,这通常发生在大集群上的小模型;反之,如果计算时间占主导,收敛减速就不值得节省的通信时间,因为系统不在通信上受瓶颈限制。无论该比率如何,除非优化器分析和收敛测试证明省略误差反馈是合理的,否则任何超出 FP16 的有损压缩都应携带误差反馈,因为没有修正收敛可能会失败。

第 6.2 节 中的 α-β 分析有助于确定压缩何时划算:如果梯度足够大以至于受带宽限制(M > n*²),压缩直接减少墙钟时间。如果受延迟限制(M < n*²),压缩将无济于事,因为无论消息大小如何,延迟项都占主导地位。

决定压缩是否值得需要将通信时间节省与收敛惩罚进行比较。考虑一个具体场景:一次训练运行在无压缩下需要 100,000 步收敛,每步耗时 500 ms(300 ms 计算,200 ms 通信)。总训练时间为 50,000 秒。应用带有误差反馈的 INT8 压缩将通信时间减少 4×(每步从 200 ms 降至 50 ms),但所需步骤增加 10%(从 100,000 步增至 110,000 步)。新总时间为 110,000 × 0.35 = 38,500 秒,提升 23%。压缩是值得的,因为每步通信节省(150 ms)超过了所需的额外步骤。

现在考虑同一模型在更快网络上(通信每步仅 30 ms)的情况。压缩将其降至 7.5 ms(每步节省 22.5 ms),但仍增加 10% 的步骤。新总时间为 110,000 × 0.3075 = 33,825 秒,而无压缩为 100,000 × 0.33 = 33,000 秒。压缩使总训练时间增加 2.5%,因为每步节省(22.5 ms)相对于收敛惩罚(10,000 个额外步骤,每个 307.5 ms)太小。这个例子说明了为什么只有当通信与计算比率(Tcomm / (T[compute] / N))超过阈值(取决于所选方法的具体收敛惩罚)时,才应应用压缩。

通信库全景

从头用 C++ 编写一个高度优化、拓扑感知、分层的 Ring AllReduce 需要一个专门的工程师团队数月工作。工程决策在于:哪个库能在目标硬件上保留本章的模型、拓扑和重叠假设。前几节开发了驯服通信成本的三种互补策略;生产库通过四个决策轴来实现这些策略:设备内存路径、拓扑感知、可移植性和可观测性。

NCCL:为何它是 NVIDIA GPU 工作负载的核心

NVIDIA Collective Communications Library (NCCL)⁷⁸ 是许多 NVIDIA 多 GPU 训练部署的核心,因为它将上述的集合算法转化为驻留在 GPU 上的数据移动操作(Jeaugey 2017; NVIDIA 2026b)。其被广泛采用源于三项 MPI 和 Gloo 无法在没有硬件厂商支持的情况下复制的 GPU 专用优化。Kernel fusion(内核融合)将归约操作符(求和、求平均)直接融入内存拷贝内核,因此 NCCL 不是将数据拷贝到缓冲区、执行归约、再将结果拷贝回去,而是在传输过程中进行归约,消除了原本会限制 HBM 带宽利用率的中间内存流量。Channel pipelining(通道流水线)开启多个并行通信通道,以一次性饱和所有网络接口,因此拥有 8 个 NIC 的 DGX 节点能达到单通道实现的 8 倍带宽。GPUDirect RDMA 允许网卡通过 PCIe 直接从 GPU 内存读取数据,完全绕过 CPU;没有它,数据将不得不遍历 GPU → CPU 内存 → NIC → 网络的路径,增加微秒级延迟并消耗 CPU 周期。这些因素共同解释了为什么在回退到通用 MPI 实现之前,NCCL 通常是针对 NVIDIA GPU 集合操作进行基准测试的首选后端。

除了原始性能,NCCL 的拓扑自动发现也是一项关键差异化优势。来自第 6.5.3 节的拓扑检测阶段,在 NCCL 中由 NVIDIA Management Library (NVML) 查询和 PCI 总线枚举 NVLink 连接及 NIC 布局来驱动。库专用的收益是硬件感知的算法选择:在配备 NVSwitch 的 DGX H100 上,NCCL 能识别出所有 8 块 GPU 互联互通,并选择基于 NVSwitch 的算法,这与用于点对点 NVLink 连接系统的环形算法截然不同。

NCCL 还根据消息大小和 GPU 数量实现了自动算法选择。内部维护着调优逻辑,将消息大小、GPU 数量和拓扑映射到选定的算法或协议族。用户可以通过文档化的环境变量(如 NCCL_ALGONCCL_PROTONCCL_DEBUG)来覆盖或检查某些内置选择,以便在基准测试揭示特定工作负载存在次优选择时进行干预(NVIDIA 2026b)。调试输出为性能调试提供了必要的可视性。

NCCL 的一项较新能力是对用户定义归约操作符的有限支持,例如针对通信器和数据类型的文档化预乘和归约(NVIDIA 2026b)。这种支持比 MPI 风格的任意归约和自定义数据类型要窄得多,因此应用程序专用的聚合操作(如离群值裁剪)通常仍需要单独的内核或框架级逻辑。

尽管被广泛采用,NCCL 并非没有局限性。它虽然开源,但与 NVIDIA GPU 和网络紧密耦合,无法作为 AMD 或 Intel 加速器的原生集合库。对于构建多厂商基础设施或需要完全控制通信栈的组织而言,这些限制促使其使用替代库。

MPI:HPC 基石

Message Passing Interface (MPI)⁷⁹ 在深度学习出现几十年前就已标准化了集合操作。MPI Forum 的标准历史记录了 1994 年 5 月发布的 1.0 版本,定义了一套用于点对点消息传递、集合操作和进程管理的厂商中立 API,它成为了高性能计算的通用语言(Message Passing Interface Forum 2015, 1993)。当可移植性、CPU 端协调或 HPC 集成是决定性需求时,MPI 对 ML 系统依然具有相关性。

对于基于 CPU 的分布式计算,MPI 实现(OpenMPI、MPICH、Intel MPI)成熟且经过良好优化,拥有跨越多种网络结构数十年的性能调优经验。早于 GPU 训练时代的 HPC 设施通常将 MPI 深度集成到其作业调度器和资源管理器中,使 MPI 成为在这些系统上部署 ML 工作负载的阻力最小路径。

MPI 规范包含 NCCL 所缺乏的功能,最著名的是持久集合操作和具有细粒度完成语义的非阻塞集合操作。持久集合操作(MPI 4.0 引入)允许应用程序“预注册”一个集合操作,将设置开销分摊到数千次调用中。这对于每一步都重复相同 AllReduce 形状的训练循环极具价值。非阻塞集合操作(MPI_Iallreduce)提供显式的请求句柄供完成情况测试,支持比 NCCL 异步操作更灵活的重叠模式。

MPI 还提供单边通信MPI_PutMPI_GetMPI_Accumulate),可自然映射到 RDMA 硬件。这些操作允许一个进程在远程进程未显式参与的情况下读取或写入其内存,从而实现仅靠集合操作难以表达的通信模式。参数服务器架构和某些异步训练算法受益于单边语义。

对 ML 从业者的主要限制在于 GPU 感知能力。标准 MPI 实现假设使用主机内存缓冲区。在 GPU 内存上调用 MPI_Allreduce 通常需要显式的设备到主机拷贝,这抵消了将数据保留在 GPU 上的性能优势。CUDA-aware MPI 扩展(OpenMPI 和 MVAPICH2 中可用)可直接接受 GPU 指针,但其内部实现很少能媲美 NCCL 的内核融合和通道流水线优化。实用的指导原则是硬件特定的:当硬件栈支持时,对 NVIDIA GPU 间集合操作使用 NCCL;对 CPU 集合操作、进程管理(mpirunmpiexec)或跨非 NVIDIA 硬件的平台可移植性使用 MPI。

Gloo:跨平台灵活性

Gloo 是 Meta 的开源集合通信库,作为后端选项与 NCCL 一起集成在 PyTorch 中。虽然 NCCL 通常是性能关键型 NVIDIA GPU 训练的首选,但 Gloo 在 PyTorch 生态系统中扮演着互补角色:当可移植性比饱和 GPU 互联更重要时,它保留了分布式语义。

Gloo 的主要优势在于其可移植性。它支持 Linux、macOS 和 Windows,无需 CUDA 或任何厂商专用运行时。这使得 Gloo 成为开发和调试工作流的自然选择,工程师可在笔记本电脑或 CI 服务器上原型设计分布式训练逻辑,随后再部署到 GPU 集群。Gloo 的 TCP/IP 传输可在任何网络栈上工作,包括用于单机多进程测试的回环接口,消除了 NCCL 所要求的基础设施需求。

对于纯 CPU 训练(数据预处理管线、特征工程、基于 CPU 的模型架构),Gloo 的实现在中小规模集群上与 MPI 具有竞争力。Gloo 为 PyTorch 分布式训练中的常见场景进行了优化:进程组对存储在 CPU 内存中的张量执行 AllReduce 和 Broadcast。其共享内存传输实现了无网络开销的高带宽节点内通信,为本地进程组实现了接近 memcpy 的吞吐量。

Gloo 还在 PyTorch 的 torch.distributed 模块中充当备用后端。当 NCCL 不可用或不合适(非 NVIDIA 硬件、缺少驱动、不支持的平台)时,PyTorch 配置可使用 Gloo 进行集体操作。这种备用行为对于混合供应商环境以及确保分布式训练代码在不同硬件配置间的可移植性极具价值。

主要局限在于 GPU 集群上的性能。Gloo 缺乏内核融合、GPUDirect RDMA 和 NVLink 感知路由,因此 GPU 张量集体操作需要显式的设备到主机拷贝。在 NVIDIA GPU 集群上,对于大消息 AllReduce 操作,Gloo 能达到的带宽远低于 NCCL。对于延迟敏感的小消息操作,差距进一步扩大,因为 Gloo 无法绕过操作系统内核进行 GPU 内存访问。给从业者的指导很直接:将 Gloo 用于开发、CPU 工作负载和可移植性;为性能关键的 GPU 训练切换到针对硬件优化的后端。

库选择指南

表 6.15 中的选择矩阵总结了这些维度。关键不在于死记库名,而在于选择其内存路径、拓扑模型、可移植性约束和调试可见性与作业相匹配的后端。

| 场景 | 推荐库 | 理由 |

| --- | --- | --- |

| 多 GPU 训练 (NVIDIA) | NCCL | GPUDirect、内核融合、NVLink 感知 |

| 纯 CPU 分布式训练 | Gloo 或 MPI | 成熟的 CPU 优化 |

| 开发/调试 | Gloo | 跨平台,无 CUDA 依赖 |

| 混合厂商 GPU | Gloo (备用) | NCCL 专用于 NVIDIA |

| HPC 集成 | MPI + NCCL | MPI 用于作业启动,NCCL 用于 GPU 集体操作 |

表 6.15:通信库选择:基于硬件和工作负载需求选择。许多 NVIDIA GPU 训练部署使用 NCCL;Gloo 作为可移植的备用选项。

该表直接源于本章的成本模型。后端并非在抽象意义上更快;只有当其内存路径避免了主机拷贝、其拓扑模型与网络结构匹配、且其故障信号暴露了限制作业的 αβ 或处理器开销的那一部分时,它才更快。

厂商专用库

上述三个库并未穷尽通信领域,因为同一集体算法在不同加速器平台上具有不同的有效 αβ 和拓扑约束。模式是一致的:使用理解实际运行作业的硬件上的设备内存路径和互联互通的后端。AMD 的 RCCL (ROCm Communication Collectives Library) 镜像了 NCCL 的 API 以适配 AMD GPU,并针对 ROCm 技术栈和 Infinity Fabric 优化了集体操作,但经过基准测试的 RCCL 部署可能仅能达到同等 NCCL 带宽的 70%-85%,这取决于节点内网络结构的成熟度或软件调优是否落后于 NVIDIA 技术栈。Intel 的 oneCCL (oneAPI Collective Communications Library) 面向 Intel 加速器和 CPU 部署,提供其自身的拓扑感知集体操作。

更广泛的模式是标准化的前端 API,底层搭配硬件专用的后端。PyTorch 的 torch.distributed 模块根据配置的进程组和可用后端,将集体调用路由到 NCCL、RCCL、oneCCL 或 Gloo。实践中,团队往往混合使用后端而非全局选择单一后端:GPU 进程组可能使用 NCCL 进行梯度 AllReduce,而 CPU 侧协调组可能使用 Gloo 进行屏障、标量广播或采样器状态同步。本章算法具有普适性,但高效的执行计划是硬件专用的。当该计划表现不佳时,通信后端也就成为诊断的起点。

当分布式训练作业运行速度低于预期时,通信库提供首要诊断信号,因为它暴露了所选算法、测量带宽、秩映射和重叠行为。系统化的工作流可隔离瓶颈是出在计算、通信还是两者的交互中:

  1. 使用 NCCL 调试日志进行分析:设置 NCCL_DEBUG=INFO 以查看 NCCL 为每个集体操作选择的算法(Ring、Tree)和协议(Simple 用于大带宽,LL/LL128 作为小消息的低延迟协议)。意外的算法选择通常表明拓扑检测错误。

  2. 测量裸集体性能:使用集群的确切拓扑运行 NCCL 内置基准测试(nccl-tests),以建立可实现带宽的基线。如果 nccl-tests 达到理论带宽的 90%,但训练作业仅达 50%,则瓶颈在于训练框架调用集体操作的方式,而不在通信库本身。

  3. 检查拖后腿者 (stragglers):在每个集体操作前后使用 torch.cuda.synchronize() 以测量单次操作耗时。耗时比预期长 2 倍的集体操作,通常表明某个 GPU 延迟了(热节流、ECC 错误恢复或数据加载不均衡),从而拖垮整个屏障。

  4. 验证重叠有效性:使用 NVIDIA Nsight Systems 在同一轴上可视化计算内核和通信操作的时间线。有效的重叠显示通信操作与反向传播内核并行运行。差的重叠显示 GPU 在通信期间空闲的间隙。

  5. 隔离网络与主机开销:如果 nccl-tests 显示低带宽,问题在网络层面(坏线缆、拥塞、路由配置错误)。如果 nccl-tests 显示满带宽但训练缓慢,问题在主机层面(重叠不足、桶大小过小、数据加载中的 CPU 瓶颈)。

一旦后端正确匹配硬件并针对裸集体基线进行测量,剩余暴露的通信时间就必须隐藏在计算之后。

通信-计算重叠

20 毫秒的网络延迟可以有效地“消失”——不是通过升级物理网络,而是将通信隐藏在算术运算之后。前面几节通过算法选择、拓扑感知和载荷压缩降低了通信成本。重叠通过改变通信发生的时机来攻击残余开销。

逐层重叠

反向传播期间的梯度计算逐层进行,从输出层回传至输入层。层 的梯度在该层反向传播完成后即可获得,即使层  − 1、 − 2、…… 仍在计算中。这创造了一个机会:立即开始通信层 的梯度,同时 GPU 计算层  − 1 的梯度。

在 PyTorch 的 DistributedDataParallel (DDP) 中,这种重叠通过 梯度钩子 实现。每个参数注册一个钩子,当其梯度就绪时触发。该钩子为该参数的梯度桶触发异步 AllReduce。同时,反向传播继续计算更早层的梯度。如果剩余层的反向计算耗时长于 AllReduce(大模型的常见情况),通信将被完全隐藏。

这种重叠的有效性取决于每层计算与通信的相对大小。对于 Transformer 模型,最大的梯度张量属于注意力和前馈权重矩阵,这些也是计算量最大的层。这种有利的相关性意味着,数据通信量最大的层,也正是拥有最多计算量可供隐藏通信的层。

重叠策略与前几节的算法选择相互作用。环形 AllReduce 具有较高的延迟但最优的带宽,比树形 AllReduce 更能从重叠中获益,因为环形的延迟惩罚(在中等大小消息下本会占主导地位)可以隐藏在计算之后。这也是 NCCL 可能会在理论交叉点以下仍选择环形算法的一个原因:当其检测到框架支持异步操作时,环形的延迟劣势被重叠中和,而其带宽优势得以保留。

一个关键的同步屏障限制了这种重叠:优化器步骤无法开始,直到所有梯度完成归约。最后处理的层(最接近输入层)的梯度暴露了其通信延迟,因为没有后续的反向计算来掩盖它们。

存储桶融合

前面描述的逐层重叠假设每层的梯度作为单个消息进行通信。实际上,一个 Transformer 层包含数十个独立的参数张量(查询、键、值投影矩阵、输出投影、层归一化参数、前馈网络权重和偏置)。为每个单独参数启动独立的 AllReduce 会产生数千个小消息集合操作,每个都要支付完整的 α 开销。为避免此问题,DDP 使用 存储桶融合 将参数分组到存储桶中(通常配置为几十兆字节,25 MB 是常见的 PyTorch 参考值),并为每个存储桶启动一个 AllReduce。存储桶大小代表了一种权衡:较大的存储桶分摊了 α 开销,但会延迟通信开始(因为必须等到存储桶内所有参数都计算出梯度才能发送)。较小的存储桶能更早开始通信,但需支付更多 α 开销。

最优存储桶大小取决于模型架构和网络特性。对于包含许多小层的模型(如深度残差网络),较小的存储桶(5–10 MB)通过更早启动通信来改善重叠。对于包含少数大层的模型(如具有多千兆字节嵌入表的大语言模型),较大的存储桶(50–100 MB)更优,因为大层主导了计算和通信时间。PyTorch DDP 中的 bucket_cap_mb 参数允许调整这种权衡。

参数添加到存储桶的顺序决定了重叠的有效性。DDP 按前向传播中使用顺序的逆序将参数分配到存储桶,这对应于反向传播中梯度变得可用的顺序。这种逆序分配确保第一个填满的存储桶(从而启动的第一个 AllReduce)包含前向传播最后几层的参数,这些是反向传播的前几层。这种排序最大化了重叠窗口:最早的存储桶拥有最多的后续计算来隐藏其通信。

重叠极限:隐藏失效时

通信-计算重叠存在根本限制。LogP 模型的开销参数 o 代表启动和完成传输所消耗的不可减少的 GPU 时间。即使重叠完美,GPU 也必须在每个 AllReduce 操作上至少花费 2o 用于启动和完成。如果模型层数很多但每层的反向传播很短(小于 o),GPU 在通信开销上花费的时间将多于计算,重叠便不再提供收益。

此外,反向传播的第一层和最后一层无法重叠。第一个完成的层(输出层)没有先前的通信可供重叠。最后通信的层(输入层)没有后续的计算可供重叠。对于浅层模型(2–3 层),这些边界效应占总时间的显著比例,将可实现的重叠限制在 50–60%。对于深层模型(几十层或更多),边界效应可忽略不计,重叠可隐藏 90–95% 的通信时间。

第三个限制源于内存压力。将通信与计算重叠需要维护额外的 GPU 内存缓冲区:正在通信的梯度必须保留在内存中,同时反向传播继续为更早的层产生新梯度。对于 FSDP 工作负载,内存优化是主要动机,这种额外的缓冲需求可能与 FSDP 提供的内存节省相冲突。工程挑战在于找到最佳平衡点:重叠要足够激进以隐藏通信,但又不能过于激进导致 GPU 陷入显存不足(OOM)。PyTorch FSDP 的设置如 limit_all_gathersbackward_prefetchforward_prefetch 以及重分片或预取选择,为此权衡提供了调节旋钮。

一个现实的训练配置展示了重叠后仍有多少通信暴露在外。

问题:一个 32 层的 Transformer 模型(7B 参数)在 64 张 GPU 上训练。每层的反向传播耗时 15 ms。对于该重叠预算,通信存储桶采用保守的 FP32 梯度核算,因此每层梯度的分层 AllReduce(约 880 MB/层)使用 100 MB 存储桶耗时 26.4 ms。有无重叠时的步耗时分别是多少?

无重叠(顺序)

T[顺序] = T[反向] + T通信 = 480 ms + 32 × 26.4 ms = 1324.8 ms。

有重叠(流水线)

每层的 AllReduce(26.4 ms)与下一层的反向传播(15 ms)并行运行。因为 26.4 ms > 15 ms,每层有 11.4 ms 的暴露通信无法隐藏。

T[流水线] = T[反向] + T[首层通信] + (N[L] − 1) × T[暴露] = 859.8 ms。

系统洞察:重叠将总步耗时降低了 35.1%。剩余的暴露通信源于 AllReduce 比逐层反向传播慢。要消除此残余,需增加反向传播计算量(增大批次大小)或减少 AllReduce 时间(更激进的压缩、更优拓扑)。当 T[逐层反向] > T[逐层 AllReduce] 时,重叠最有效。

分层 AllReduce(减少通信数据量)、链路优化路由(最大化每次通信操作的吞吐量)和逐层重叠(将剩余通信隐藏在计算后)的组合,构成了分布式训练常见的通信优化栈。每种技术针对性能方程中的不同项:分层算法减少 M,拓扑感知路由最大化有效 β,重叠通过并行通信与计算来分摊 α。当三者结合应用时,配置良好的系统通信开销可降至总步耗时的 5–15%,远低于朴素的平面同步方法所带来的 50–80%。

即使是节点内基准情况,也展示了这套栈在任何多节点机制介入前的成功:一个 70B 参数模型在单个 8 张 NVLink 互联 GPU 的节点上,每步必须 AllReduce 140 GB 的 FP16 梯度,NVLink 上的环形 AllReduce 大约在 0.3 秒内完成,而每步计算预算为 2.1 秒,舒适地以约 15% 的计算时间完成重叠。这种量化的成功为下文的陷阱奠定了基础,因为每个陷阱都始于优化了错误的项、选择了错误的原语、或信任了不再匹配工作负载的拓扑假设。

谬误与陷阱

通信优化吸引了持久的误解,因为延迟、带宽、拓扑和压缩之间的相互作用,创造了简单心智模型无法捕捉的非显而易见的失效模式。

谬误带宽是唯一重要的指标。

通信陷阱与误区:分布式训练中的关键考量

概述

对于小消息(流水线并行激活、MoE token),延迟(α)占主导地位。如果消息在软件中序列化需要 5 μs,购买 400G 网络将无济于事。关键消息大小 n^* = αβ 决定了要优化的指标:低于 n^* 时,降低延迟;高于 n^* 时,增加带宽。

常见陷阱与误区

误区:假设 AllReduce 适用于所有场景

AllReduce 创建全局屏障,并假设所有参与者贡献相同的数据形状。在专家并行(MoE)和推荐系统中,每个工作节点需要向每个其他工作节点发送不同的数据,AllReduce 从根本上就是错误的。这些工作负载需要 AllToAll,它具有 𝒪(N²) 个逻辑连接,并在更小的集群规模下就会触及网络争用限制。

误区:流水线并行消除了通信开销

流水线并行用相邻阶段之间的点对点激活传输替代了 AllReduce,单条消息的成本确实更低廉。但流水线引入了不同的开销:气泡空闲。对于 p 个流水线阶段,至少有 (p − 1)/(p − 1 + m) 的 GPU 时间在流水线填充和排空时处于空闲状态,其中 m 是微批次的数量。对于深度流水线(p ≥ 8),这需要 m ≥ 32 个微批次才能将气泡控制在 20% 以下,这限制了批次大小、激活内存和收敛性。数据并行与流水线并行的选择不是“通信与无通信”的对立,而是带宽受限的 AllReduce 与时间受限的气泡开销之间的权衡。

陷阱:默认使用 Ring AllReduce 而不检查消息大小和拓扑

Ring 实现了带宽最优的 \(2\\frac{N-1}{N}\\frac{M}{\\beta}\),但付出了 𝒪(N) 的延迟代价。对于跨 64 个 GPU 的 1 MB 梯度且 α = 10 μs 的情况,Tree AllReduce 更优,因为 Ring 的 1,260 μs 延迟惩罚超过了 Tree 的 1,000 μs 带宽惩罚。对数感知的交叉估计 M[crossover] ≈ Nαβ/log[2]N 决定了何时切换算法;Nαβ 只是一个粗略的上界启发式指标。

误区:没有误差反馈的梯度压缩是安全的

Top-k 稀疏化可以实现 99% 的压缩,但朴素地丢弃小梯度会导致发散。没有误差反馈(e[t+1] = (g[t] + e[t]) − v[t]),低于阈值的梯度将永远丢失,累积系统性偏差,最终使训练不稳定。

陷阱:仅凭通信加速评估梯度压缩

如果压缩方案导致模型质量下降,以至于需要 2 倍的训练迭代才能达到目标精度,那么实现 100 倍通信加速的压缩方案也是毫无价值的。正确的指标是 time-to-accuracy(达到精度所需时间),而不是 time-per-iteration(每次迭代时间)。通信微基准测试衡量的是每次迭代时间,而工程决策取决于达到精度所需时间,当压缩引入梯度偏差或方差从而减慢收敛时,两者就会背离。应在目标收敛基准上使用部署训练计划验证压缩,而不仅仅是在合成 AllReduce 微基准测试上。

误区:异步集合操作总是能隐藏延迟

Python 的 dist.all_reduce(..., async_op=True) 仅将控制权返回给 CPU。LogP 模型将网络延迟 L[lat](可重叠)与处理器开销 o(不可重叠)区分开来。如果 GPU 计算内核短于通信开销,GPU 仍会停顿。只有当 T[compute] > o 时,通信才能被隐藏。

陷阱:网络中的静默数据损坏

网络并非完美无缺。劣质线缆、故障 NIC 或固件 Bug 可能在训练框架看到显式错误的层级以下损坏数据。在 10,000 个节点 7×24 小时运行的情况下,即使非常罕见的比特错误也可能频繁到足以产生影响。具体到通信的教训是,没有端到端验证的带宽调优是不完整的:校验和、集合操作结果检查、梯度范数合理性测试和容错机制必须在损坏的梯度成为模型更新之前捕获它们。

误区:扁平 AllReduce 足以应对多节点训练

扁平 Ring AllReduce 将所有链路视为平等,在节点内有 900 GB/s NVLink 可用时,却将数据路由到 50 GB/s InfiniBand 上。对于每节点 8 个 GPU 的 8 节点集群,分层 AllReduce 与扁平 Ring 相比将节点间流量减少了 8 倍,在上述工作示例中实现了约 4.8 倍的加速。忽略带宽层级会让大部分节点内带宽闲置,同时使稀缺的节点间带宽过载。

陷阱:将 Ring AllReduce 公式用于节点内通信

Ring AllReduce 成本模型假设逻辑环拓扑,这符合跨 InfiniBand 的节点间通信模式。但它不符合节点内通信。在 DGX 级节点内,GPU 通过 NVLink 和 NVSwitch 通信,后者提供全互联连接而非环。实际的节点内 AllReduce 通常通过 NVSwitch 交叉开关单步归约完成,延迟更接近单跳而非 𝒪(N) 环成本。统一应用环公式进行容量规划会将节点内通信时间高估一个数量级,并不必要地向外推并行边界。节点内使用交叉开关模型,节点间使用环或树模型,全局 AllReduce 使用分层求和。

误区:秩到 GPU 的映射不影响集合操作性能

如果作业调度器在不考虑物理拓扑的情况下将秩分配给 GPU,分层 AllReduce 和轨道优化路由可能会次优地路由流量。常见症状是 nccl-tests 在集群上达到满带宽,但实际训练作业仅达到 50-60% 的带宽,因为秩跨节点分配的方式阻止了轨道对齐。在基准测试通信性能前,验证秩分配是否匹配预期拓扑(例如:秩 0-7 在节点 0,秩 8-15 在节点 1)。

陷阱:基准测试集合操作时未记录放置上下文

除非记录秩顺序、NIC 轨道绑定、NCCL 拓扑配置和节点分配,否则集合操作基准测试不是可复用的证据。没有该上下文,团队会对比来自不同物理布局的结果,将放置差异误认为是库或硬件回归。

总结

通信是规模化的摩擦。计算是局部的,但学习是全局的,α-β 和 LogP 模型将通信瓶颈分析从猜测转变为定量工程。关键消息大小 n^* = αβ 分离了延迟受限与带宽受限的区域,理论预测与测量的 NCCL 性能之间的差距揭示了软件栈开销使小消息延迟膨胀 5-10 倍,在实践中改变了算法交叉点。

集合通信原语(AllReduce、AllGather/ReduceScatter、AllToAll)直接映射到并行策略,每个原语决定了一个独特的扩展上限。AllReduce 工作负载受带宽限制且平滑扩展,而 AllToAll 工作负载受争用限制并在较小的集群规模下触及实际极限。

Ring 和 Tree AllReduce 算法占据了延迟-带宽权衡的两端,对数感知的交叉估计 M[crossover] ≈ Nαβ/log[2]N 决定了给定配置下的最优算法。分层 AllReduce 和轨道优化路由利用 GPU 集群的带宽层级来提升有效节点间带宽,而网络内归约(SHARP)通过在网络交换机内部执行部分聚合来减少主机可见的通信量。

故障容忍

舰队恢复蓝图,展示训练路径因节点故障中断、检查点状态得以保留,并通过替换工作节点恢复工作。

目的

为何规模会将硬件故障从罕见的例外转变为系统必须持续吸收的常态?

通信:作为单一机器行动的代价

即便最快的导线也不够用时,梯度压缩提供了最后的挤压。量化、稀疏化以及像 1-bit Adam 这样的压缩感知优化器,可将通信量减少 4–1000 倍。误差反馈确保虽然旅程可能被压缩或延迟,但没有关键信息永久丢失,从而保持模型收敛的数学真理。最后,通过逐层流水线和桶融合实现的通信计算重叠,将剩余的通信时间隐藏在有用的计算之后,使在配置良好的系统中,有效通信开销降至总步骤时间的 5–15%。

不同灯塔架构的通信流量模式存在根本差异。

梯度的“出行清单”取决于系统的目标函数和约束机制。每种灯塔架构面临着不同的通信挑战,本章开发的技术对这些挑战的适用方式也各不相同,汇总见 表 6.16。

表 6.16:架构到集体通信的映射

| 架构 | 主要集体通信 | 主要摩擦 | 优化策略 |

| --- | --- | --- | --- |

| 架构 A (GPT-4/Llama-3) | AllReduce | 带宽 (β) | 分层 AllReduce;铁路优化 |

| 架构 B (大规模 DLRM) | AllToAll | 延迟 (α) 和 争用 | 拓扑感知路由;Token 负载均衡 |

| 架构 C (联邦 MobileNet) | P2P/异步 | 连通性和延迟 | 激进量化;误差反馈 |

每种灯塔架构的通信模式由其主要瓶颈决定。

关键洞见在于,即使都在训练神经网络,不同架构面临的通信瓶颈也截然不同。架构 A (GPT-4/Llama-3) 受限于带宽,受益于分层 AllReduce 结合通信计算重叠。架构 B (大规模 DLRM) 受限于延迟,受益于低延迟交换机和拓扑感知 AllToAll 路由。架构 C (联邦 MobileNet) 受限于间歇性连接和小型设备,因此激进量化和误差反馈比数据中心拓扑更重要。将错误的优化应用于错误的架构,会浪费工程力却无法提升性能。

本章开发的压缩和误差反馈机制在数据中心之外同样重要:对于架构 C 和类似的间歇性或带宽匮乏场景,主要成本可能在于能否发送任何有用的更新,而非填满高速网络结构。

本章确立的通信模式揭示了一个事实:分布式训练从根本上说是一个伪装成机器学习问题的网络工程问题。α-β 模型为推理此问题提供了分析框架:从算法选择到压缩再到重叠,每一个通信设计决策都可以根据其对延迟和带宽项的影响进行评估。理解这些模型和技术,能将从业者从分布式框架的使用者转变为能够诊断瓶颈、优化拓扑配置、并实现决定万亿参数训练是在数周还是数月内完成的扩展效率的工程师。

关键要点

  • α-β 模型揭示瓶颈:关键消息大小 n^* = α ⋅ β 决定了是优化软件延迟(小消息)还是硬件带宽(大载荷)。实践中,NCCL 的有效 α 比线路级延迟高 5–10 倍。

  • 算法选择依赖规模:环形 AllReduce 在带宽上最优但付出 𝒪(N) 延迟;树形 AllReduce 在延迟上最优 (𝒪(log N)) 但带宽利用率低。利用对数感知交叉估算 M[crossover] ≈ Nαβ/log₂N 推断切换点,并将 Nαβ 仅视为粗略的上限规模启发式指标。

  • 分层算法成倍增加带宽:通过在跨越缓慢的 InfiniBand 桥梁前,先在快速 NVLink 上执行本地归约,分层集体通信实际上将节点间带宽乘以了每节点 GPU 数量。

  • AllToAll 是争用之王:与 AllReduce 不同,AllToAll 创建 𝒪(N²) 逻辑连接。这使得专家并行 和推荐系统比稠密 LLM 从根本上更难扩展。

  • 误差反馈使有损压缩安全:稀疏化可丢弃 99% 的梯度,前提是将“误差”在本地累积并加到下一步。这将有偏估计量随时间转变为无偏估计量。

  • 重叠是最后的乘数:通过梯度钩子和桶融合实现的通信计算流水线,可为深度模型将 90–95% 的通信时间隐藏在反向传播计算之后。

  • 拓扑发现非可选:NCCL 等高性能库会动态将逻辑环映射到物理导线以避免“热点”并最大化剖分带宽。秩到 GPU 的错误映射会导致性能下降 2–4 倍。

如果并行主义决定了必须通信什么,本章关注的则是该通信的实际代价。每个集体操作都是舰队真实指令集中的一条指令,其代理由 α-β 模型设定:启动的固定延迟和完成的每字节带宽。工程的任务在于降低两者,在带宽受限时选环,在延迟受限时选树,将缓慢的节点间跳跃折叠为快速的本地跳跃,并最重要的是将传输与计算重叠,让舰队用本该花在计算上的时间来支付这笔费用。通信是计算为作为单一机器行动所支付的罚金,本章讲的就是如何让这笔罚金保持在值得支付的程度。

舰队现已拥有其逻辑(并行性)和流量模式(通信)。地图绘就,车辆制成。然而在生产中,硬件故障是统计上的必然:GPU 过热、网络丢包、节点在计算中途失效。在故障容忍(第 7 章)中,我们将探讨如何在不完美的硬件上维持完美超级计算机的假象。我们从移动的逻辑转向生存的机制,探索检查点、恢复与弹性训练。

故障容忍指引问题

  • 实测 α-β 或 LogP 参数应如何决定在环形、树形、蝴蝶形、双树形和分层集体通信中的选择?

  • 数据、张量、流水线和专家并行如何施加不同的集体模式和扩展上限?

  • 拓扑感能通信何时优于理论最优的扁平算法?

  • 梯度压缩何时合理,何种收敛证据足以让人信任它?

Fault Tolerance and Reliability Engineering

A single accelerator runs for years between hardware failures. A thousand accelerators turn the same component risk into a failure every few days. A ten-thousand-device cluster, including hosts, power, and network domains, fails every few hours. The arithmetic is unavoidable: individual component reliability does not change, but system reliability falls multiplicatively as components are added. In large clusters, the question is not whether a failure will occur during a training run, but how many; a system that cannot absorb failures without losing progress simply does not run. The same logic applies to serving: globally distributed inference systems treat regional outages, network partitions, and capacity fluctuations as continuous background conditions rather than anomalous events. Fault tolerance at scale is not about preventing failures—because prevention is impossible—but about designing systems that anticipate, detect, isolate, and automatically recover from failures so that useful work continues through the constant churn of components entering and leaving service. In C³ terms, fault tolerance spends compute and communication on coordination: because component failure at cluster scale is a statistical certainty rather than an anomaly, state must be saved and work recovered.

  • Calculate cluster-level MTBF from component failure rates and estimate failure frequency for large training clusters

  • Classify hardware, software, and silent data corruption failures by detectability, blast radius, and recovery path

  • Derive checkpoint intervals that balance write overhead, lost work, and cluster failure rate

  • Design distributed checkpointing and elastic recovery for multi-terabyte model state across GPU clusters

  • Evaluate fault injection and observability evidence to validate detection, isolation, and recovery behavior

  • Implement service redundancy, failover, and state replication under millisecond latency and partial-failure constraints

  • Select graceful degradation strategies that preserve useful service when models, features, regions, or capacity fail

Failure Analysis at Scale

Imagine a 10,000-GPU cluster midway through a three-month foundation-model training run. The communication layer does its job: thousands of devices exchange gradients through AllReduce, AllGather, and AllToAll as if they were one machine. Then the arithmetic of scale takes hold. A GPU fails every few hours, and if the system cannot absorb that routine physical event, synchronous training stops and millions of dollars of compute sit idle. Fault tolerance is the system property that lets distributed execution continue making useful progress when components fail, degrade, or disappear. In the fleet stack shown in Figure 1.13, fault tolerance serves as the resilience layer of distributed execution. The challenge spans the full failure spectrum from transient bit flips through intermittent aging-related errors to permanent component failures, as shown in Figure 7.1. The annotated failure rates and mean-time-between-failures (MTBF) scaling in that figure, MTBF[system] = MTBF[component] / N, is merely a preview; the subsequent analysis derives the inverse scaling and tabulates cluster failure rates. Gray failures and silent data corruption (SDC) add a second dimension of difficulty: low detectability, large blast radius.

Figure 7.1: ML Failure Taxonomy

Figure 7.1: ML Failure Taxonomy: The large-scale reliability challenge spans a temporal taxonomy from transient errors (bit flips, single-event upsets) through intermittent errors (aging, marginal components) to permanent errors (stuck-at faults, failed components). Each class demands different detection latency and recovery strategy; fault-tolerance engineering must address all three to sustain fleet-scale throughput.

This fragility is the direct consequence of successful synchronization. Distributed training systems achieve scale throughput by coordinating thousands of devices; collective communication holds them in lockstep through rigid exchanges. That same synchronization means one stalled device can stall the entire cluster. Fault-tolerant ML systems preserve progress by detecting failure, checkpointing recoverable state, and restarting or elastically reshaping jobs, treating hardware churn as normal execution rather than an exceptional path.

The shift from small-scale experiment to large-scale production changes the system's relationship with failure. A researcher training a model on a single GPU may see a hardware failure once in years. The same workload on a 1,000-GPU cluster experiences a pure GPU failure every few days; production clusters fail more often once PCIe, power, storage, and network domains enter the failure budget. This transition from "rare exception" to "routine occurrence" demands a different engineering approach. The mathematics below makes the transition precise and quantitative.

Time ladder showing system MTBF collapse with fleet growth: 1 GPU = 50,000 hours, 1,000 GPUs = 50 hours, 10,000 GPUs = 5 hours.

System MTBF collapses with fleet growth: from 50,000 hours to 5 hours.

Because failures cannot be eliminated at this scale, the engineering challenge is not preventing them but sustaining progress despite them: fault-tolerant systems verify completion rather than assuming it, treat failure as a normal code path, rehearse recovery continuously rather than occasionally, and account for partial failure states that naive error handling never anticipated. The techniques to achieve this draw on decades of distributed systems research, but ML workloads change the economic model. Training exhibits properties that make fault-tolerance strategies viable that general-purpose distributed systems cannot adopt: the mathematics of stochastic gradient descent tolerates certain errors that would corrupt other computations, checkpoints are large but predictable, and the recovery target need only be approximate rather than exact. Exploiting these properties makes ML-specific fault tolerance cheaper than the general methods from which it descends.

Mathematics of Inevitable Failure

Systems reliability engineering provides the foundational framework for understanding failure at scale (Birolini 2017). A single component exhibits a failure rate characterized by the hazard rate parameter λ⁸⁰ with units of failures per unit time. For a single component with constant hazard rate λ, the probability of surviving without failure until time t follows the exponential distribution⁸¹, as shown in Equation 7.1:

R_single(t) = e^(−λt) (7.1)

The MTBF of that component equals 1/λ. By the convention used in this book, Mean Time To Failure (MTTF) refers to the expected time to first failure of a single component, while MTBF refers to the expected time between consecutive failures of a repairable system; they coincide numerically under the constant-rate exponential model used here, with fault-tolerance models using MTTF for single-component constants and MTBF for composed system rates. Datacenter GPUs commonly cite planning MTBF values in the tens of thousands of hours; actual field performance depends on operating conditions, thermal management, manufacturing variation, and load stress⁸². Section E.1.1 lists canonical single-component MTTF values (H100, A100, TPU, PCIe, power, network) that anchor the λ and FIT numbers used throughout the fault-tolerance analysis; readers may substitute their own device constants and reproduce the rates used throughout the text.

当多个独立组件在一个系统中运行,且任何单一故障都会导致系统故障时,公式 7.2 将系统可靠性形式化为各个组件可靠性的乘积:

R_system(t) = ∏_{i=1}^{N} R_i(t) = ∏_{i=1}^{N} e^{-λ_i t}      (7.2)

对于具有相同故障率 λN 个相同组件,公式 7.3 给出了相同组件的简化形式:

Rsystem = e^(−N * λ * t) (7.3)

系统故障率变为 N * λ,而 公式 7.4 表达了系统 MTBF 如何与组件数量成反比缩放。这种反比缩放揭示了集群规模下可靠性“9”的反直觉现实:

MTBF_system = 1 / (N * λ) = MTBF_component / N      (7.4)

图 7.2 形象地展示了这种根本张力:随着集群规模增长,故障间的预期时间缩短至低于常见的训练时长,使得容错对于长时间运行的集群作业成为必要。

图 7.2:可靠性鸿沟:随着 GPU 数量增加,集群 MTBF 下降至常见训练时长之下。一个拥有 10,000 张 GPU 的集群大约每 5 小时就会发生一次故障,若无容错机制,将无法进行长达 3 个月的训练。

Young-Daly 定律:最优检查点策略

当故障不可避免时,关键的工程决策是保存进度的频率。检查点过于频繁会在 I/O 上浪费时间;检查点过于稀疏则会在故障后重新计算丢失的工作上浪费时间。Young-Daly 公式 ⁸³ (原理)用一个平方根定律化解了这种张力:

τ_opt = √(2 * T_write * MTBF_system)

在分布式 ML 训练中,T_write 并非刷新少量进程状态所需的时间——它是将数百 GB 的 FP32 优化器状态(每个可训练参数的动量和方差张量)以及模型权重传输到持久存储所需的时间。对于一个带有完整 Adam 状态的 70B 参数模型,单次检查点可达数百 GB;对于后文 175B 参数的运行示例,该数值将超过 1 TB。因此,压缩 T_write 需要专用的高带宽存储,而 T_write 本身是决定检查点间隔能设得多紧的核心约束,以免 I/O 开销挤占生产性的训练算力。

最优间隔平衡了写入检查点的成本与重做丢失进度的预期成本,图 7.3 绘制了这种 U 型权衡。其缩放后果是关键所在:随着集群增长,系统 MTBF 下降,因此最优间隔缩短,这要求更高带宽的存储(第 4 章)以保持 T_write 足够小,防止“检查点税”消耗集群的算力容量。由于关系是平方根,MTBF 减半仅会使间隔收紧 √2 ≈ 1.4×,因此集群规模翻倍并不要求检查点 I/O 带宽翻倍。附录 E.2.1 节 给出了完整推导及更严格的二阶界限;后文的检查点章节(第 7.6.1 节)在掌握本地集群的系统 MTBF 后应用该公式。

图 7.3:Young-Daly 最优检查点:总浪费工作量是 检查点开销(随间隔 τ[ckpt] 增加而减小)与 重做成本(随 τ[ckpt] 增加而增大)之和。最小值点定义了最优间隔 τ_opt = √(2 * T_write * MTBF_system)。以一个系统 MTBF 为 5 小时、写入时间为 15 分钟的示例集群为例,最优间隔约为 1.6 小时。

一个快速的可用性计算从另一个角度展示了相同的缩放压力。

问题:一个包含 10,000 张 GPU 的集群运行,每张 GPU 的可用性为 99.99%(每年停机仅 52.6 分钟)。整个集群同时处于运行状态的概率是多少?

数学计算

  1. 单 GPU 可用性概率 (R_GPU):一张可用性为 99.99% 的 GPU 在随机选取的时刻处于全运行状态的概率 R_GPU = 0.9999。

  2. 集群可用性概率 (R_cluster):R_cluster = (R_GPU)^(N)。

  3. 结果:(R_GPU)^(N) ≈ 0.37。

系统洞察:即使硬件可靠性高达 99.99%,一个拥有 10,000 张 GPU 的集群也仅有 37% 的概率使每张 GPU 同时可用。仅靠硬件可靠性是不够的;软件必须能自动处理故障。

组件数量与故障率之间的线性关系有一个直接含义:增加 GPU 会将罕见的组件故障转变为持续的系统状态。

一张 MTBF 为 50,000 小时(5.7 年)的 GPU 可通过人工干预吸收偶发故障,但拥有相同单 GPU 可靠性的 10,000 张 GPU 集群,其仅含 GPU 的系统 MTBF 仅为 5 小时:故障每天会发生多次。系统设计必须预期故障会发生,而非指望避免它。

定量可靠性分析

开篇可靠性示例中的仅 GPU 计算提供了规模直觉;真实的训练系统则将 GPU、HBM、链路、电源、存储路径和冗余组合成一个故障预算。既然 MTBF 和 FIT 率已定义(FIT = 10⁹/MTBF,因此一张 50,000 小时 MTBF 的 GPU 贡献 20,000 FIT),新的问题在于组件故障率如何 组合。组合规则取决于冗余结构。对于 串联系统(例如一个要求所有 8 张 GPU 均正常工作的节点),任一组件故障即导致整体故障,因此可靠性相乘,故障率相加:

R_system(t) = ∏_{i=1}^{N} R_i(t),      MTBF_system = 1 / (∑ λ_i)

这就是为什么增加组件会线性降低系统 MTBF。对于提供冗余的 并联系统(例如主动-主动模型副本),仅当所有冗余组件同时故障时系统才故障:

R_system(t) = 1 - ∏_{i=1}^{N} (1 - R_i(t))

组合分析还揭示了哪个子系统主导了预算,而内存是关键情况。考虑在 1,024 张 A100 GPU 上训练一个原型 A(GPT-4/Llama-3)70B 模型(第 1.6.1 节)。无保护的 HBM 是约束项:按大约每兆比特 250 FIT 计算,集群的 1,024 × 80 GB 内存积累了如此多的故障点,以至于未经纠正的损坏大约每 20 秒就会发生一次,使得大规模训练变得不可能。因此,纠错码(ECC)内存保护不是可选项。ECC 可检测并纠正常见的单比特错误,将有效软错误率降低约两个数量级,将内存故障率推回到逻辑和互联项之下,尽管残留的多比特或逃逸损坏仍作为静默数据损坏问题存在。

这些单组件 FIT 数值描述了硅片固有的软错误预算,这就是为什么它们不同于后续级联计算中使用的 50,000 小时整器件 MTTF:整器件 MTTF 融合了所有现场失效模式(热应力、电源事件、机械磨损),而不仅仅是此处单独分离出的逻辑和内存软错误率。级联计算将这些整器件失效率组合成实际决定检查点间隔的集群数值;本估算仅确立了为什么内存保护是后续一切的前提条件。

实例演示:集群 MTBF 计算

考虑一个为大语言模型开发设计的训练集群,其规格如下:

  • 10,000 块 NVIDIA H100 GPU

  • 单 GPU MTBF:50,000 小时

  • 每个 GPU 通过 PCIe 连接到主机(MTBF:200,000 小时)

  • 每个节点包含 8 块 GPU,共享电源(MTBF:100,000 小时)

  • 每节点网络基础设施(NIC、线缆):MTBF 150,000 小时

这些组件共同定义了下文失效率计算中使用的故障域。

步骤 1:计算每 GPU 子系统的失效率

每个 GPU 运行在一个包含 GPU 本身、其 PCIe 连接,以及电源和网络基础设施按比例分摊份额的故障域中。

\[ \lambda_{\text{GPU}} = \frac{1}{50{,}000} = 2.0 \times 10^{-5} \text{ failures/hour} \]

\[ \lambda_{\text{PCIe}} = \frac{1}{200{,}000} = 0.5 \times 10^{-5} \text{ failures/hour} \]

\[ \lambda_{\text{power/GPU}} = \frac{1}{8} \times \frac{1}{100{,}000} = 0.125 \times 10^{-5} \text{ failures/hour} \]

\[ \lambda_{\text{network/GPU}} = \frac{1}{8} \times \frac{1}{150{,}000} = 0.083 \times 10^{-5} \text{ failures/hour} \]

步骤 2:计算每 GPU 总失效率

\(\lambda_{\text{total/GPU}} = (2.0 + 0.5 + 0.125 + 0.083) \times 10^{-5} = 2.708 \times 10^{-5}\) failures/hour

步骤 3:计算系统失效率和 MTBF

\(\lambda_{\text{system}} = 10{,}000 \times 2.708 \times 10^{-5} = 0.2708\) failures/hour

\[ \text{MTBF}_{\text{system}} = \frac{1}{0.2708} = 3.69 \text{ hours} \]

这一结果意味着集群平均每 3.7 小时就会经历一次故障。预期故障频率为 6.5 次/天;持续一周的训练运行将经历约 45 次故障。在此规模下运行的任何训练系统都必须将故障视为一种持续状况,而非例外事件。附录 E.1.2 节逐步演示了这一相同的级联计算,将单组件失效率组合成全车队系统 MTBF,以便读者针对不同的节点设计或集群规模重新运行计算。

表 7.1 中的集群 MTBF 缩放隔离了各集群规模下的仅 GPU 基线。一旦纳入 PCIe、电源和网络故障域,全系统计算值会更低。

| 集群规模 (GPU 数) | 单 GPU MTBF | 集群 MTBF | 预期日故障次数 |

| --- | --- | --- | --- |

| 8 | 50,000 小时 | 6250 小时 (260 天) | 0.004 |

| 64 | 50,000 小时 | 781.2 小时 (33 天) | 0.03 |

| 512 | 50,000 小时 | 97.7 小时 (4 天) | 0.25 |

| 1,000 | 50,000 小时 | 50 小时 (2 天) | 0.48 |

| 4,000 | 50,000 小时 | 12.5 小时 | 1.9 |

| 10,000 | 50,000 小时 | 5 小时 | 4.8 |

| 25,000 | 50,000 小时 | 2 小时 | 12 |

表 7.1:集群 MTBF 缩放:仅 GPU 的平均故障间隔时间随集群规模线性下降,使故障从罕见事件转变为大规模下的持续运行条件。额外的主机、电源、存储和网络故障域会进一步降低系统级 MTBF。

表 7.1 中的理论 1/N 缩放不仅仅是教科书式的练习。图 7.4 将 Meta 生产集群的已发布测量值和拟合预测与理论曲线叠加展示。Kokolis 等人(2025)对 Meta 研究超级集群(RSC)的研究报告了观测到的 RSC 作业规模的测量 MTTF 值,并预测 16,384 GPU 作业的 MTTF 为 1.8 小时;Meta 的 Llama 3 训练报告独立记录了在 16,384 块 H100 GPU 上 54 天内发生的 419 次故障,观测 MTTF 约为 3.1 小时。实测的 Llama 运行和 Kokolis 预测共同将 16,384 GPU 训练置于 2 到 3 小时的故障频率区间。这一证据将理论论证从抽象的缩放定律转变为决定检查点频率、恢复架构和基础设施投资的工程约束。

图 7.4:生产集群的已发布故障率:来自 Meta RSC 集群的测量 MTTF 值和拟合预测(Kokolis 等人 2025)与 Llama 3 训练测量值(Dubey 等人 2024)一起绘制在理论 1/N 曲线上。在 16,384 GPU 处,Kokolis 预测和 Llama 3 实测均落在 2 到 3 小时的故障范围内,确认了容错是数学上的必然,而非边缘情况。数据来源:Kokolis 等人,arXiv:2410.21680;Meta,arXiv:2407.21783。

故障分类学

可靠性分析中的 MTBF 计算量化了故障发生的频率,这对设定检查点间隔和确定恢复基础设施规模至关重要。设计有效的容错机制还需要理解发生何种故障。术语使用须精确:故障(fault)是潜在的缺陷或事件,错误(error)是其产生的损坏状态,而失效(failure)是训练系统必须处理的外部可见行为(Avizienis 等人 2004)。几秒内解决的网络分区,其处理方式与永久性 GPU 故障不同。导致梯度错误的静默内存损坏,所需的检测机制与完全停止响应的节点崩溃也不同。故障特性指导着适当恢复机制的选择。此处呈现的分类学沿两个主要维度对故障进行分类:时间行为(瞬态与持久)和故障表现(停止故障与拜占庭故障)(Constantinescu 2008;Lamport 等人 1982)。

瞬态故障

瞬态故障暂时发生且无需干预即可自行恢复,因此恢复的关键问题是系统能否在损坏状态传播前重试或验证受影响的工作。四种常见的瞬态模式对 ML 系统至关重要:

  • 网络丢包:丢弃数据包,但重传成功。

  • 内存位翻转:通过宇宙射线引起的单事件翻转破坏单个比特。⁸⁴

  • 热节流:温度飙升时暂时降低性能。

  • 软件超时:源于暂时的资源争用。

瞬态故障在 ML 训练中尤为隐蔽,因为它们可能不会触发显式错误。梯度计算期间的瞬态内存位翻转会产生错误梯度,并传播到后续训练步骤。模型继续训练,但产生微妙降级的结果。大规模 SDC 研究显示存在逃逸的损坏,而 DRAM 现场研究表明内存错误足够常见,以至于车队级监控至关重要(Dixit 等人 2021;Sridharan 等人 2015;Schroeder 等人 2009)。在 ML 训练中,同样的未检测损坏模式可能表现为梯度退化、异常损失曲线或值得回滚的检查点,而非显式崩溃⁸⁵。

故障停止失败

故障停止失败会导致组件完全停止运行,且这种停止是可检测的。失败的组件停止响应请求,可通过超时机制识别:

  • GPU 硬件故障:导致设备无响应。

  • 节点崩溃:终止机器上的所有进程。

  • 网络分区:将节点与集群隔离。

  • 存储故障:阻止检查点的读取或写入。

在同步分布式训练中,故障停止失败后果尤为严重:它们会停滞整个作业。AllReduce 集合操作要求每个参与的秩贡献其梯度分片,规约才能完成。单个 GPU 沉默——无论源于硬件故障、进程崩溃还是网络分区——都会阻塞环中的所有其他 GPU,直到超时过期。在一个 10,000 GPU 的作业中,这意味着 9,999 块加速器处于空闲状态,针对零前向进度的运行累积计费小时数,直到协调器最终宣布缺失秩失败并触发恢复。

故障停止失败是最容易处理的故障类别,因为检测直截了当:组件停止响应。恢复涉及替换失败组件并从最近的检查点恢复状态。主要挑战是将检测时间和恢复延迟降至最低。

检测时间 T_detect 通常涉及心跳机制,每个 GPU 秩定期向协调器发送存活信号。若在超时周期 T_timeout 内未收到心跳,协调器即宣告故障。设置 T_timeout 需在误报率与检测延迟间权衡。误报会将健康工作节点因瞬时延迟判定为失败,而缓慢检测会在检测窗口期浪费算力——此成本随 AllReduce 集合中被阻塞的 GPU 数量线性增长。

对于心跳间隔 H 秒和网络延迟标准差 σ_d 秒,公式 7.5 给出了超时启发式计算:

T_timeout = H + k * σ_d

此处,k 为无量纲安全乘数,通常取值 3 到 5,以在保持合理检测速度的同时实现低误报率。建立在专用高带宽网络(InfiniBand、NVLink)之上的 ML 训练集群基线抖动极低,因此正常条件下 σ_d 实际上很小。实际难点在于区分真正死亡的秩与暂时变慢的秩——例如,某 GPU 因向附加存储写入大检查点而滞后,而集合操作已在等待。T_timeout 设置过紧会导致检查点写入触发误报故障声明;设置过松则延长 AllReduce 集合中所有其他秩被阻塞空闲的窗口期。

拜占庭失败

故障停止型组件只是沉默并被检测替换,而一类更隐蔽的故障会持续运行却产出错误结果。不报错却返回错误梯度的 GPU、传递通过 CRC 校验的损坏数据包的网络、对相同输入计算出不同结果的工作节点,皆属 拜占庭失败,这是分布式系统中最棘手的一类(Lamport 等 1982)。在 ML 系统中,此类包括静默数据损坏、数值不稳定、确定性违背和对抗性损坏;共同特征是工作节点仍参与计算,但其贡献的值可能毒害共享状态。

静默损坏的物理学

在先进晶体管的纳米尺度上,硬件非 确定性 的;而是 概率性 的。静默数据损坏(SDC)由两大主要机制驱动。单粒子翻转(SEU) 发生于高能粒子(海平面宇宙射线、封装材料的 α 粒子)撞击存储单元或逻辑门,将比特从 0 翻转为 1;在 10,000+ GPU 规模下,这属统计必然。制造差异 表现为:通过初始 QA 的“边缘”芯片,仅在特定电压/温度条件下(如反向传播中剧烈的 d_i/d_t 摆动)出现比特翻转。

Facebook 记录了一起普遍的 SDC 问题:硬件故障导致解压时有效文件被报告为“大小为零”(Dixit 等 2021)。如 图 7.5 所示,系统“工作正常”(未崩溃),但数据被静默删除。在 ML 中,这表现为外观有效但数值已损坏的梯度。

图 7.5:静默数据损坏传播:意外故障可返回错误文件大小,导致解压时数据丢失,并经分布式查询系统传播错误。此 Facebook 案例强调静默错误如何绕过标准异常处理器。来源:(Dixit 等 2021)。

真实生产系统中的 SDC 证实了这些风险。图 7.6 展示了 Google 某 Shuffle 与 Merge 数据库中损坏数据块的累积,即便极小比例的损坏块也能级联引发显著数据质量下降。

图 7.6:Spark 中的静默数据损坏:使用 Spark 等大规模数据处理框架的 AI 系统易受数据传输和存储期间累积的静默数据损坏(SDC)影响。SDC 表现于 Shuffle 与 Merge 数据库,凸显健康数据(蓝/灰)中的损坏数据块(红)。来源:(Dean 2024)。

Google 报告 TPU Pod 中的 SDC 可表现为梯度范数突然、无法解释地飙升(图 7.7)(Dean 2024)。指数部分的单比特翻转可将微小梯度变为数值巨大的值,若异常未被检出,将破坏训练轨迹。

图 7.7:梯度范数偏差:瞬态硬件故障(如静默数据损坏 SDC)可导致梯度范数突变,从而扰乱优化。Google 生产案例显示 SDC 异常以可见的梯度范数尖峰浮现。来源:(Dean 2024)。

Google 采用系统级缓解措施应对此类故障,包括备用容量和合理性检查,当损失或梯度监控标记异常时,可排出可疑芯片(图 7.8)(Dean 2024)。这将可靠性从不可信的组件层面,转移到验证结果的系统层面。

图 7.8:热备冗余:Google 数据中心案例利用备用容量和合理性检查,在监控标记异常时将工作从可疑资源迁出,使 ML 训练在硬件故障下持续进行。此法与双模冗余、三模冗余等并行冗余技术不同,提供了一种响应式容错机制。来源:(Dean 2024)。

图 7.8 中的热备模式展示了一种 SDC 缓解方法,但首先识别静默损坏需要了解什么将其与良性训练噪声区分开来。

验证你对拜占庭故障和 SDC 的理解:

静默损坏检测是第一道防线,但同样的逻辑适用于任何输出在语法上仍保持有效的拜占庭工作节点。这些故障在分布式训练中尤为危险,因为“工作节点对相同数据计算出相同梯度”这一标准假设不再成立。单个拜占庭工作节点即可损坏平均梯度,可能导致训练发散或收敛到劣质解。图 7.9 对比了故障停止模式的简单检测与拜占庭损坏的隐蔽性。

![](https://github.com/OpenDocCN/dsai-notes-pt3-zh/tree/master/docs/hav-cs249r-mlsys-vol2/img/file127.svg)

图 7.9:故障停止 vs. 拜占庭故障:在故障停止模型中(左侧),故障工作节点仅停止发送消息,超时机制即可轻松检测。在拜占庭模型中(右侧),故障工作节点继续参与但发送错误数据(例如,将损坏的梯度报告为有效),若无冗余验证检测,便会毒害全局模型状态。

拜占庭故障检测需要冗余计算。多个工作节点为相同数据计算梯度,以便对比结果。统计异常值检测可识别持续产生异常梯度的工作节点。这些检测机制会增加计算开销,且可能无法捕捉微妙的损坏。

拜占庭弹性分布式训练算法虽存在,但会带来显著开销。Krum 算法(Blanchard et al. 2017)和坐标修剪均值(Yin et al. 2018)等算法计算的聚合结果对有限数量的拜占庭工作节点具有鲁棒性,但它们比简单平均需要更多通信和计算。系统层面的后果显而易见:损坏的梯度会将优化器推向不可靠的模型状态,而训练作业表面看起来却很健康。因此,可靠性必须保护语义正确性,而不仅仅是进程存活性。⁸⁶

相关故障

第 7.1.1 节 中的可靠性计算假设故障独立发生。真实系统中,当共享依赖同时导致多个组件失效时,会出现相关故障:

  • 电源故障:导致节点内每个 GPU 失效。

  • 网络交换机故障:隔离所有连接的节点。

  • 冷却系统故障:强制整机架热关机。

  • 软件 Bug:导致使用有缺陷 CUDA 驱动版本的每个进程崩溃。

  • 操作员失误:错误配置整个集群。

相关故障违背了 公式 7.3 所依赖的独立性假设。当故障相关时,实际系统可靠性低于公式预测值。相关故障还可能击败冗余策略。如果三个模型副本都运行在同一电源域上,电源故障会同时使三者失效,三副本便无法提供任何可用性收益。

防御相关故障需要理解故障域,并确保冗余跨越独立的故障域。表 7.2 列举了 ML 基础设施中常见的故障域,范围从单个 GPU 到整个数据中心区域,每种都需要不同的缓解策略。图 7.10 展示了这些域如何以层级方式嵌套,故障通过包含结构向下传播。

| 故障域 | 影响范围 | 典型恢复时间 | 缓解策略 |

| --- | --- | --- | --- |

| 单 GPU | 1 个 GPU | 秒级(备用) | 热备、弹性训练 |

| 节点(电源/操作系统) | 8 个 GPU | 分钟级 | 检查点、节点替换 |

| 机架(ToR 交换机) | 32–64 个 GPU | 分钟到小时 | 跨机架冗余 |

| 电源域 | 100–500 个 GPU | 小时级 | 多路供电 |

| 数据中心区域 | 所有 GPU | 小时到天级 | 地理分布 |

| 软件版本 | 运行该版本的所有 GPU | 分钟级(回滚) | 分阶段发布 |

表 7.2:ML 基础设施中的故障域:理解故障域边界可将冗余组件置于独立域中,防止相关故障击败冗余策略。

表 7.2 中故障域的嵌套对冗余部署有直接影响:在同一域内放置副本无法防御该域的故障模式。

![](https://github.com/OpenDocCN/dsai-notes-pt3-zh/tree/master/docs/hav-cs249r-mlsys-vol2/img/file128.svg)

图 7.10:故障域层级:故障域通常嵌套或重叠。GPU 故障影响单个设备。节点故障影响 8 个 GPU。机架交换机故障影响 32-64 个 GPU。电源分配单元(PDU)故障可能影响多个机架。有效的容错要求将副本置于独立域(例如不同机架或排)以抵御相关故障。

图 7.10 的关键洞见在于:冗余仅当副本跨越独立故障域时才有效——将三个模型副本放在同一机架上,无法防御三者共享的机架交换机或 PDU 故障。

验证你对故障域嵌套及其运维影响的理解:

背景:Google 的 TPUv4 机器学习超级计算机使用 4,096 个 TPU 芯片,通过定制的 3D 环面芯片互联(ICI)结构相连,Google 工程师发布了其关于自动故障恢复和硬件恢复的运维经验(Zu et al. 2024)。

故障模式:在此规模下,机器、芯片、ICI 线缆或光电路交换机的故障不仅仅是组件损失。它可能破坏同步训练作业依赖的用于集体通信的拓扑结构。

后果:Google 构建了一个控制平面,用于检测故障、重新配置 ICI 结构,并将作业绕过故障或路由到备用的健康立方体。论文报告称在优雅处理约 1% 训练作业遭遇的硬件故障时,实现了 99.98% 的系统可用性。

系统教训:容错是一个拓扑管理问题。在紧密耦合的加速器集群中,恢复必须保留通信图,而不仅仅是替换单个组件;否则一个局部硬件故障可能演变为集群范围的训练中断。

浴盆曲线与硬件生命周期

故障分类法对故障类型和域进行分类,回答了“会发生什么类型的故障”。对于设计容错而言,理解故障在组件生命周期的哪个阶段最可能发生同样重要。硬件故障率在组件生命周期内并非恒定。图 7.11 展示了浴盆曲线,这是可靠性工程中一个成熟模型,描述了故障率在三个不同阶段如何变化:

第一阶段,早期失效期,因制造缺陷、安装不当及边缘组件早期磨损而表现出较高故障率。此阶段通常持续数天至数周(针对电子组件)。烧机测试⁸⁷ 在部署前让组件在应力条件下运行,以在生产使用前暴露早期失效故障。

存活过婴儿期死亡率后,组件进入使用寿命阶段,在标准可靠性工程模型下(Birolini 2017;Klutke 等 2003),它们表现出相对恒定的故障率。该阶段代表了组件寿命中最长的部分,也是公式 7.1 中的指数可靠性模型最准确适用的时期。对于数据中心 GPU,使用寿命窗口是一个由刷新周期、散热、利用率和观测到的舰队健康状况所塑造的运营规划概念,而非固定的物理时长。

随着组件老化,它们进入损耗阶段,由于累积磨损,故障率上升。对于 GPU,损耗机制包括电路中的电迁移⁸⁸、焊点上的热循环应力以及热界面材料的退化。损耗的起始时间在很大程度上取决于运行条件;在高温下运行或经历频繁热循环的组件会更早进入损耗期。

对 ML 系统的实际意义在于,舰队级故障率取决于年龄分布。一个完全由新 GPU 组成的集群在最初几周将经历较高的故障率,随后进入稳定期,然后随着舰队老化故障率增加。混合年龄的舰队表现出更一致的聚合故障率,因为不同批次处于不同的生命周期阶段。

图 7.11:浴盆曲线:硬件故障率 λ(t) 随时间变化。(1) 婴儿期死亡:初期因制造缺陷导致高故障率。(2) 使用寿命:恒定、低故障率,随机故障为主。(3) 损耗期:组件老化导致故障率上升。烧机测试旨在部署前筛除婴儿期死亡故障。

图 7.11 中的三个阶段具有直接的运营后果:烧机测试在部署前筛除婴儿期死亡故障,而利用 GPU 遥测数据(温度趋势、错误计数、性能退化)的预测分析则瞄准损耗阶段,从而在维护窗口期间实现计划内组件更换,而非在训练运行期间发生非计划停机。ML 基础设施团队将此模型直接应用于集群调度和舰队准入。运营人员通常不会将全新的加速器机组视为可立即等同于经验证的生产节点,而是在将硬件分配给高价值训练之前先运行压力负载。持续的矩阵乘法循环、内存测试和通信测试可在边缘设备接触多周预训练运行之前将其暴露出来。运营人员会在筛选阶段维修或更换故障的加速器,使其永远不会接触高价值训练运行;存活下来的加速器进入使用寿命期,此时恒定速率指数模型适用。这种规范至关重要,因为成本不对称显著:在筛选期间捕获婴儿期死亡故障仅耗费数小时的加速器空闲时间,而同样的故障若发生在多周基础模型预训练运行中,可能会使数天的梯度累积失效并强制进行检查点回滚。调度策略反映了这种不对称:集群运营人员通常将全新或刚维修的节点首先分配给短期探索性作业或推理服务,而非长达数月的预训练作业,直到它们积累足够的运行小时数以摆脱婴儿期死亡风险窗口。

按模型类型划分的故障影响差异

虽然故障率数学原理普遍适用,但按模型类型划分的故障影响差异巨大。丢失一小时训练的影响取决于该训练的成本、必须恢复的状态量以及恢复所需时间。表 7.3 量化了跨模型架构的这些因素,揭示了从 LLM 导致数百万美元算力浪费到视觉模型仅损失适度进度的数量级差异。

| 模型类型 | 典型训练时长 | 检查点大小 | 状态敏感度 | 故障成本 |

| --- | --- | --- | --- | --- |

| 原型 A (70B 级稠密 LLM) | 2–4 周 | 350–700 GB | 高 (课程位置) | 每 24 小时损失 $2–5M 算力成本 |

| 视觉 (ViT-Large) | 1–3 天 | 1–2 GB | 中 (增强状态) | 每天损失 $10–50K |

| 原型 B (大规模 DLRM) | 持续 | 2–4 TB (嵌入) | 极高 (嵌入新鲜度) | 每小时收入影响 |

| 语音 (Whisper 规模) | 3–7 天 | 5–10 GB | 中 | 每天损失 $50–200K |

| 科学 (AlphaFold) | 数天至数周 | 10–50 GB | 高 (探索状态) | 研究延迟 |

表 7.3:按模型类型划分的故障影响:训练故障的成本因模型类型而异,差异巨大,这由训练时长、检查点开销和累积训练状态的价值驱动。这些差异要求针对特定模型的容错策略。

大语言模型因其漫长的训练时长和每训练小时的高昂算力成本,经历着最高的绝对故障成本。例如,一个消耗 25,000 个 GPU、每 GPU 小时约 $2 的大规模训练运行,每天产生 $1.2M 的算力成本。导致 24 小时训练进度损失的故障,成本为 $1.2M 浪费算力加上调度延迟。检查点开销跨度很大:70B 级稠密 LLM 检查点可达数百 GB,而包含 FP32 Adam 优化器状态的 1750 亿参数运行示例达到 3.7 TB。

推荐系统带来独特挑战,因为其训练通常是持续而非情景式的。RecSys 模型的价值部分源于其新鲜度。捕捉近期用户行为的嵌入优于陈旧嵌入。导致数小时嵌入更新丢失的故障,可能会以直接影响收入的方式降低推荐质量。Meta 已记录在案,推荐模型新鲜度与参与度指标直接相关,使得恢复时间成为业务关键指标。⁸⁹

视觉模型处于中间地带,具有中等训练时长和可管理的检查点大小。相对较小的检查点支持以最小开销进行频繁检查点保存。1–2 GB 范围内的 ViT-Large 检查点与大语言或嵌入密集型推荐工作负载相比开销极小。数据增强状态是除模型权重外必须保留以实现可复现恢复的主要状态。必须捕获增强参数和数据打乱种子。

科学模型如蛋白质结构预测或气候模拟中使用的模型,往往具有超出模型参数的独特状态需求。AlphaFold 风格的训练可能维护探索状态,跟踪已采样的蛋白质家族,防止恢复期间重复。药物发现模型可能跟踪已评估的分子构型。这种领域特定状态增加了检查点和恢复设计的复杂性。

容错投资的经济框架

容错机制消耗资源:用于检查点的存储、用于检查点写入的带宽、用于冗余计算的计算周期,以及用于实施和维护的工程时间。合理的容错投资要求量化故障成本和预防成本。

The Economics of Fault Tolerance

The cost of faults includes wasted compute, time-to-solution delay, opportunity cost, and engineering time. Wasted compute measures the GPU-hours consumed by training steps that must be repeated. Time-to-solution delay reflects how extended training time affects business timelines. Opportunity cost recognizes that compute used for recovery cannot be used for other training. Engineering cost accounts for time spent debugging faults and manually recovering.

The cost of prevention includes storage, throughput overhead, recovery infrastructure, and complexity. Storage cost scales with model size and checkpoint frequency. Checkpoint writes consume memory bandwidth and can stall training. Recovery infrastructure requires spare capacity and automated restore systems. Fault-tolerant systems are harder to develop and debug.

Optimal fault-tolerance investment balances these costs. For small-scale training (few GPUs) where faults are rare, minimal fault tolerance may be cost-optimal. Infrequent checkpoints and manual recovery suffice. For large-scale training (thousands of GPUs) where faults occur multiple times per day, extensive fault tolerance provides positive ROI. Frequent checkpoints, automated recovery, and elastic training become essential. Figure 7.3 visualizes how the trade-off between checkpoint overhead and recovery cost reaches an optimum, which depends on both system MTBF and checkpoint write time.

Equation 7.6 provides a simplified economic model for expected cost per training run:

\[C\_{\\text{total}} = C\_{\\text{compute}} + E[N\_{\\text{failures}}] \\times C\_{\\text{per-failure}} + C\_{\\text{ft}} \\qquad (7.6) \]

Where \(C\_{\\text{compute}}\) is baseline compute cost, \(E[N\_{\\text{failures}}]\) is the expected number of failures during training, \(C\_{\\text{per-failure}}\) is the cost per failure, and \(C\_{\\text{ft}}\) is the cost of fault tolerance mechanisms. The cost per failure includes wasted compute plus overhead.

Equation 7.7 formalizes when fault-tolerance investment is justified:

\[\\frac{\\partial C\_{\\text{ft}}}{\\partial x} < \\frac{\\partial (E[N\_{\\text{failures}}] \\times C\_{\\text{per-failure}})}{\\partial x} \\qquad (7.7) \]

Where \(x\) represents investment in fault tolerance mechanisms. In practice, this means investing in fault tolerance until the marginal cost of additional protection exceeds the marginal reduction in failure costs.

Three foundational principles guide every design decision in this space.

  1. At scale, failure is continuous, not exceptional. A 10,000-GPU cluster experiences a fault every few hours. Systems must be designed to treat failure as normal operations.

  2. Checkpoint intervals have an optimum. The Young-Daly formula \(\\tau\_{\\text{opt}} = \\sqrt{2 \\times T\_{\\text{write}} \\times \\text{MTBF}\_{\\text{system}}}\) gives quantitative guidance on checkpoint frequency. This formula is derived in Section 7.1.2.

  3. Training and serving have fundamentally different fault-tolerance needs. Training tolerates minutes of recovery; serving requires milliseconds. This divergence demands completely different approaches.

Rule 3—the training/serving divergence—determines the sequence: solve training recovery first, then serving resilience. Before either, one must understand the physical realities that trigger these faults, beginning with the hardware failures that break the substrate.

Hardware Fault Taxonomy

Consider what happens when a cosmic ray flips a single bit in a GPU's high-bandwidth memory, or thermal expansion causes a microscopic fracture in an NVLink connector. These physical events cascade into software errors that can corrupt weeks-long training runs, yet the recovery system does not directly observe "cosmic ray" or "fracture." It observes symptoms: a gradient spike with no process crash, a rank vanishing from collective communication, or a node that fails only under sustained thermal load. The hardware taxonomy is only useful insofar as it maps these symptoms to recovery decisions.

How Hardware Faults Impact ML Systems

ML systems amplify the consequences of hardware faults beyond traditional applications. Compute density creates millions of opportunities per second for a fault to corrupt a result. Training runs lasting days or weeks increase the probability of encountering faults. Model weights exhibit sensitive dependence where tiny corruptions cause large output changes, and distributed dependencies mean a single point of failure can corrupt the entire job.

A single-bit flip in a weight matrix illustrates the severity. If a critical weight in a ResNet-50 model flips from 0.5 to -0.5 due to a transient fault in the sign bit of an IEEE 754[⁹⁰] floating-point representation, the feature map's sign inverts, causing errors to cascade through subsequent layers. Fault injection studies show that DNN resilience depends sharply on the model, layer, structure, and bit position, meaning a small number of faults in vulnerable locations can cause disproportionate accuracy loss (Reagen et al. 2018). Unlike traditional software where a single-bit error might cause a crash, in neural networks it can silently corrupt the learned representations that determine system behavior.

As accelerator devices scale, reliability remains a design pressure. Smaller geometries and lower operating voltages reduce the charge required to disturb a stored value, increasing sensitivity to certain soft error mechanisms (Baumann 2005), while large-scale ML clusters make rare hardware faults frequent enough that software-level detection and recovery becomes a mandatory requirement for training system design (He et al. 2023). ML systems architects must treat hardware as an unreliable substrate where algorithmic fault tolerance (gradient checksums, weight replication, periodic production consistency checks) is a mandatory requirement, not an HPC specialty.

The temporal character of a hardware fault dictates the response. A one-time corruption demands detection and rollback. A persistent defect demands isolation and replacement. A recurring, load-sensitive defect demands evidence-gathering before the node poisons more jobs. Figure 7.12 summarizes the three categories that matter operationally.

Transient Fault (Bit Flip)

Permanent Fault (Stuck-at)

Transient Timing

Intermittent Timing

Figure 7.12: Temporal Taxonomy of Hardware Faults: Four panels show how three temporal categories manifest in practice. Transient panels (bit flip and timing fault) show momentary disturbances that leave no permanent damage. Intermittent panel shows sporadic errors driven by marginal conditions or aging. Permanent panel (stuck-at) shows irreversible physical damage requiring replacement.

The distinction between these three categories is what the recovery system must infer:

  • Transient faults are momentary disturbances caused by external factors (cosmic rays, electromagnetic interference) (Ziegler et al. 1996). The danger is silent corruption: a rank may continue participating while emitting incorrect gradients or activations.

  • Permanent faults represent irreversible damage from physical defects or component wear-out: stuck-at faults, device failures requiring hardware replacement. The danger is repeatability: retrying the same device reproduces the same wrong computation or hard failure.

  • Intermittent faults are the most operationally treacherous. They appear under specific conditions—thermal load, voltage margin, aging—and disappear when conditions change. A node passes diagnostics but fails during training. The danger is ambiguity: is this a transient fluke or a dying component? Intermittent faults require temporal pattern recognition across multiple jobs before the recovery system can confidently quarantine the node.

  • 间歇性故障因松动连接、元件老化或热应力等不稳定条件而零星出现和消失。其危险性在于模糊性:作业可能在一次运行中通过验证,却在略微不同的负载下失败。

恢复策略因每类故障在不同时间尺度上发生并留下不同证据而异。

瞬态故障

故障分类法按恢复姿态对瞬态故障进行分类,询问系统是否能重试或验证受影响的工作。本节提出了不同的问题:产生它们的物理机制及其要求的检测手段。瞬态故障是最常见的类别,图 7.13 展示了基本机制:当内存中的单个比特意外改变状态时会发生位翻转错误,可能以级联方式通过神经网络层改变关键数据或计算。

图 7.13:位翻转错误:瞬态故障可改变内存中的单个比特,破坏数据或程序指令,并可能导致系统故障。这些单比特错误体现了硬件对辐射或电磁干扰等诱发的瞬态故障的脆弱性。

瞬态故障之所以重要,是因为事后可能找不到受损组件。来自辐射的单一事件翻转、不稳定电源路径导致的电压波动 (Reddi 和 Gupta 2013)、电磁干扰、静电放电、串扰、地反弹、时序违例或组合逻辑中的软错误 (Mukherjee 等 2005),最终都会汇聚为同一 ML 症状:一个张量、数据包或指令与集体预期的不同。因此,恢复设计强调在线检测、在可能的情况下进行纠正,以及从已知良好状态回滚,而非人工更换硬件。

定量故障率

随着节点电容、供电电压和存储电荷的收缩,先进半导体工艺可能增加软错误敏感度 (Baumann 2005)。对于 GPU[⁹¹],大规模并行放大了实际风险:成千上万条执行通道和高带宽内存创造了许多瞬态故障可能影响权重、激活值或梯度的位点。因此,运行 MTBF[⁹²] 值取决于工作负载、组件和环境,而非通用常数。对于下文的检查点分析,关键的系统法则是复合效应:一个拥有 1,000 个加速器的集群,若单个加速器的示例性 MTBF 为 50,000 小时,则预计每 50 小时就会发生一次故障,这使得稳健的检查点机制成为必需。

内存子系统是最易受故障影响的组件,容错机制直接征收带宽税。表 7.4 量化了不同内存技术的这一成本:

内存带宽保护分析展示了错误保护可能给不同内存技术吞吐量带来的开销。

| 内存技术 | 基础带宽 | ECC 开销 | 有效带宽 |

| --- | --- | --- | --- |

| DDR4-3200 | 51.2 GB/s | 12.5% | 44.8 GB/s |

| HBM2 | 900 GB/s | 12.5% | 787.5 GB/s |

| HBM3 | 1600 GB/s | 12.5% | 1400 GB/s |

| GDDR6X | 760 GB/s | 通常无 | 760 GB/s |

表 7.4:内存带宽保护分析ECC 保护对用于 ML 加速器的不同内存技术有效内存带宽的影响。带宽开销直接影响内存受限工作负载的训练吞吐量。

不应将带宽表视为内存错误率的通用排名。HBMGDDRDDR 的可靠性取决于器件代数、工作温度、保护方案和错误计数方式。持久系统的教训更为具体:错误保护消耗带宽和容量,而车队研究表明,内存错误发生频率足以证明在大规模部署中采用 ECC、擦洗和监控是合理的 (Schroeder 等 2009; Sridharan 等 2015; Dixit 等 2021)。后台内存擦洗(周期性读写以检测累积的软错误)通常经过设计,使其带宽开销与前台训练流量相比很小。

瞬态故障对 ML 的影响

图 7.14 展示了故障分类法引入的相同电荷干扰机制 (第 7.1.5.3.1 节),现置于器件层面:宇宙射线撞击存储单元或晶体管,感应电荷改变存储或传输的数据。本节补充的是对模型的下游影响,而非物理机制。

图 7.14:瞬态故障机制:宇宙射线和电磁干扰通过改变存储单元和晶体管中的电荷在硬件内诱导位翻转,可能破坏数据并导致系统错误。理解这些故障源对于构建能容忍不可预测硬件行为的稳健 AI 系统至关重要。来源:NTT

训练期间,存储模型权重或梯度的内存中的瞬态故障可能导致错误更新,损害收敛性和准确性 (He 等 2023)。推理期间,神经网络激活值中的位翻转可能改变最终的分类或回归输出 (Mahmoud 等 2020)。在安全关键型应用中,这些故障可能导致错误决策,危及安全 (Li 等 2017; Jha 等 2019; Wan 等 2021)。资源受限环境放大了这些脆弱性:二值神经网络 (BNNs) (Courbariaux 等 2016) 以单比特精度表示权重,当以 10% 的概率注入随机位翻转软错误时,测试准确率会从 98% 下降到 70% (Aygun 等 2021)。在分布式训练中,网络分区[⁹³][⁹⁴]可能隔离秩,而在同步训练中,甚至单个分区秩也会阻塞整个 AllReduce 集体操作。

永久故障

永久故障是不可逆的硬件缺陷,持续存在直到故障组件被修复或更换。运维线索是可重复性:重试后同一加速器、存储单元、链路或存储设备再次失效,往往发生在同一地址、路径或工作负载阶段。制造缺陷(蚀刻不当、掺杂错误、污染)和磨损机制(电迁移[⁹⁵]、氧化层击穿[⁹⁶]、热应力[⁹⁷])都可能产生此特征。最常见的抽象模型是卡住故障 (Seong 等 2010),即信号或存储单元永久固定在 0 或 1,与输入无关。

ML 加速器中最严重的永久性故障是那些无声且可重复地破坏算术运算的故障。Tensor Core 的乘加数据通路出现卡住-at 故障,或存储权重碎片的 HBM 银行中的某个单元失效,都会导致每次运行相同计算时产生相同的错误值。在训练过程中,这意味着受影响矩阵的每次前向传播都会产生确定性偏差的结果,而每次反向传播都会累积出偏斜的梯度。由于错误是可重复但数值较小的,训练损失可能在数百步内看似正常下降,直到累积的偏差表现为梯度发散或意外的验证准确率下降。在推理过程中,同样的缺陷会确定性地歪斜特定输出 logits——这在医疗或自动驾驶部署中是一个安全关键属性,因为故障不会导致系统崩溃,但会系统性地倾斜涉及受影响权重块的每个决策。

1994 年发现的 Intel Pentium FDIV 错误提供了这一故障模式在通用处理器中的典型示例。Pentium 处理器除法单元使用的查找表中的一个错误导致特定操作产生错误结果(图 7.15)。该错误非常小——第五位数字出错——但它在操作间累积,损害了对精度要求高的应用。ML 加速器的情况在几何上有所不同,但原则相同:FDIV 错误破坏了标量除法,而 Tensor Core 中的卡住-at 故障会破坏经过失效通道的每个矩阵乘法输出的特定行或列,影响所有触及该计算分区的特征图或梯度碎片。在安全敏感的应用中,持续的算术错误会成为安全隐患,因为每个下游决策都会继承这种偏差计算。

图 7.15:FDIV 错误区域:向下刺入表面的尖峰标记了 Pentium 处理器故障除法单元在计算 4195835/3145727 时产生错误结果的位置。理想情况下,所有值应四舍五入为 1.3338,但该错误导致受影响的结果从 1.33384 降至 1.33368,错误出现在第五位。来源:《Byte》杂志。

图 7.16 可视化了卡住-at 故障如何通过逻辑门和互连传播,导致错误计算或持续的数据损坏,从而影响下游模型行为。

图 7.16:卡住-at 故障模型:数字电路可能出现永久性故障,即信号线固定在逻辑 0 或 1 上,与输入无关;此图简要描述了一种卡住-at-0 故障,即信号持续低电平,可能导致错误计算或系统故障。来源:accendo reliability

对于 ML 系统,恢复决策是停止信任该组件。永久性加速器数据通路故障可能持续产生错误梯度,直到设备从集群中移除(He et al. 2023;J. J. Zhang et al. 2018),而永久性存储故障可能危及训练数据集和恢复所需的检查点。校验和验证、复制存储、硬件冗余、纠错码(Kim et al. 2015)以及检查点-重启恢复[⁹⁸](Egwutuoha et al. 2013)协同工作,因为每种机制都有助于识别仍可信赖的持久副本。第 7.1.2 节介绍的 Young-Daly 公式随后给出了经济边界。硬件加固提高了 MTBF,但其平方根关系意味着可靠性投资必须与更快的检查点和重启基础设施取得平衡。

间歇性故障

间歇性故障是最难处理的一类,因为它们会产生证据然后消失。一个节点可能通过重启测试,重新加入机群,但仅在下次训练任务将封装驱动至相同的热、电压或通信条件时才会再次失败。物理退化(如焊点裂纹、老化的球栅阵列、残留物引发的电气连接)会产生这些依赖负载的条件(图 7.17)(Constantinescu 2008;Rashid et al. 2015)。ThUnderVolt 等电压降频研究显示了一条独立的时序错误路径:电压裕度降低可能导致信号传播不可靠,从而引发难以复现的错误计算(J. Zhang et al. 2018)。

图 7.17:间歇性故障机制:铜柱与封装焊料之间由于裂纹导致的电阻增加是间歇性故障的常见来源,它会干扰信号传播,并可能导致不可预测的系统行为。诸如此类的微观材料缺陷凸显了硬件对潜在故障的脆弱性——这些故障在测试时难以检测,但在运行时可能表现出来。来源:constantinescu

图 7.18 揭示了 DRAM 芯片中残留物引发的间歇性故障如何创建不可靠的电气连接,导致 sporadic 失败。

图 7.18:DRAM 残留物故障:DRAM 芯片的间歇性失效常由微观残留物积累引起,导致不可靠的电气连接。物理缺陷可能引发散发性错误,凸显了容错系统设计和硬件测试的必要性。来源:Constantinescu

对于 ML 系统,间歇性故障应被视为可疑,直到有足够证据证明 otherwise。零星的处理或内存错误可能在迭代中累积,在不触发显式故障的情况下降低收敛性(He et al. 2023;Rashid et al. 2015)。运行时监控和异常检测提供第一线索,环境控制可减少热和电压触发因素,而自适应资源管理可以在保持作业进度的同时将可疑组件排空、降频或回避。目标不仅仅是让节点保持运行,而是防止非确定性组件使验证和恢复变得不可信。

硬件故障检测与缓解

硬件故障缓解只有在检测机制与故障特征匹配时才有效。永久性缺陷最好在部署前暴露,暂态位翻转需要在线纠正,而间歇性或无声错误则需要运行时证据。在硬件层面,有两种基础机制可防�御前文所述的故障类别。

内置自测(BIST)(Bushnell 和 Agrawal 2002)通过引入额外电路实现自测,使用扫描链[⁹⁹]在系统启动期间对内部逻辑应用预定义测试模式。BIST 能在缺陷损害生产工作负载之前捕获制造缺陷和永久性故障。

错误检测与纠错码

错误检测与纠错码¹⁰⁰ (Hamming 1950) 通过添加冗余位来检测和纠正比特错误。图 7.19 展示了最简单的形式:奇偶校验向每个数据字附加一位额外的比特,从而在比特翻转发生时实现即时检测。更高级的码(如循环冗余校验 CRC¹⁰¹)计算校验和,可检测出 99.9% 以上的传输错误,这种能力对于验证第 6 章中涉及的分布式 AllReduce 操作期间的梯度载荷至关重要。

图 7.19:奇偶校验位错误检测:本图提供了一种简单的错误检测方案,其中额外的一位(奇偶校验位)确保数据序列中 1 的总数为偶数或奇数。第二个序列包含一个翻转的比特,触发了奇偶校验,表明传输或存储过程中发生了数据损坏。来源:computer hope。

硬件冗余利用组件复制和表决来检测和屏蔽故障 (Sheaffer et al. 2007)。双模冗余 (DMR)¹⁰² 复制计算并在 100% 硅面积开销下比较输出;三模冗余 (TMR)¹⁰³ 执行三次计算并采用多数表决,在 200% 开销下实现自动单故障纠正 (Arifeen et al. 2020)。图 7.21 展示了 TMR 表决电路如何通过选择多数输出来屏蔽单个故障单元。特斯拉的全自动驾驶计算机在两个独立的片上系统 (SoC) 单元间使用 DMR (图 7.20),而波音 777 在其主飞行控制计算机中使用 TMR,用于安全关键的航空控制 (Yeh 1996; Bannon et al. 2019)。

图 7.20:双模冗余 (DMR):特斯拉的全自动驾驶计算机采用 DMR 架构,跨两个独立的片上系统 (SoC) 复制关键计算。硬件比较器单元验证两个芯片产生匹配的输出,然后才允许控制命令到达车辆的执行器,确保单芯片硬件故障或比特翻转无法触发危险驾驶行为。

图 7.21:通过表决进行错误屏蔽:在三模冗余 (TMR) 中,相同的计算在三个独立单元上运行。表决电路对结果进行多数表决。即使一个单元发生故障(例如比特翻转),系统也通过选择其他两个单元的匹配输出来“屏蔽”错误,从而允许不间断地持续运行。

在软件层面,同一决策树继续延伸,每个故障特征被路由到以最低成本暴露它的那一个证据流。当梯度、损失、激活值或延迟不再匹配预期分布时,静默统计漂移就会出现,运行时监控加上异常检测(统计离群值检验、单类 SVM)在最廉价的层捕获它,因为这些检查搭便车于训练循环已发出的指标 (Francalanza et al. 2017; Chandola et al. 2009)。损坏但仍存活的状态或检查点,通过数据一致性检查捕获,确认其仍对应可信数据 (Lindholm et al. 2019)。故障停止节点通过心跳机制 (Kawazoe Aguilera et al. 1997) 与仅仅是缓慢的节点区分开来,而未崩溃但停滞的任务,通过看门狗定时器 (Pont and Ong 2002) 捕获,在进度丢失时触发恢复。只有当这些被动信号都不足时,因为计算本身可能错误却无统计特征,系统才付出软件实现冗余(SWIFT 风格指令复制、N 版本编程、里德-所罗门重构)的代价 (Reis et al. 2005; Avizienis et al. 2004; Plank 1997; Reed and Solomon 1960),其复制或重构计算是最昂贵的层,因此保留给最高价值的状态。

这些机制降低了不可靠底层基础设施的风险,但它们无法防范软件栈本身引入的故障。下一层必须推理那些看起来像有效计算实则破坏 ML 流水线的 Bug。

验证你对暂时性、永久性和间歇性故障类别以及故障、错误、失效演进的理解:

软件故障

一个团队花费三个月针对复杂的提示词级失败案例测试 LLM,结果才意识到其预处理脚本意外地将所有输入在 512 个 token 处截断,静默丢弃了整个系统提示词。在追求复杂算法鲁棒性的过程中,工程师们往往忽视 ML 失败的一个常见来源:平凡的软件 Bug。在 ML 系统中,数据加载器中的逻辑错误不会使流水线崩溃;它会微妙地降低梯度质量,这使得软件故障具有独特的破坏性。

软件故障需要与硬件故障不同的恢复视角。硬件故障源于硅片、电子和物理磨损;软件故障源于设计选择、依赖版本和流水线胶水代码。容错问题从替换故障组件转变为识别哪个看似有效的转换损坏了数据、梯度或预测。

实际挑战在于软件故障与每一个其他系统威胁相互作用。数据预处理中的 Bug 可能造成分布偏移,数值计算中的实现错误可能在保持有效张量形状的同时破坏模型行为,分布式训练中的竞态条件可能导致不同工作节点从不一致的状态更新。这些相互作用产生的原因是 AI 软件栈跨越了框架、库、运行时环境、分布式系统和部署基础设施。每个层边界都为故障的涌现和传播创造了机会,使得软件级缓解对于生产规模的可靠性至关重要。

软件故障属性与传播

ML 框架中的软件故障范围涵盖语法和逻辑错误到内存泄漏¹⁰⁴、并发 Bug 和集成失败。它们跨越系统边界传播:张量分配例程中的错误可能级联干扰看似无关模块中的训练、推理或评估。一些故障是间歇性的,仅在特定条件下显现,如高系统负载、特定硬件配置或罕见数据输入。

资源管理不善是一个突出的失败类别。GPU 内存分配跨训练迭代累积,因为中间激活值、优化器状态和梯度缓冲区被分配但未释放,直到分配器耗尽可用容量并在批次中途抛出 out-of-memory 错误。采用 AdamW 优化器状态的 70B 参数 BF16 模型在单节点上大约需要 840 GB GPU 内存,几乎不留任何空间给意外的缓冲区增长。内存压力在数百次迭代中逐渐累积,若无逐层内存剖析,根因归因极难。

并发与同步错误构成另一类反复出现的故障。在分布式或多线程环境中,并行进程间的错误协调导致竞态条件¹⁰⁵或不一致状态。

死锁

死锁会造成一种相关的同步失败,即工作线程无限期地等待永远不会到达的资源或消息。高级库中的 Bug 可能只有在与特定版本的低级数值库(如 cuDNNMKL)搭配使用时才会显现,这需要对系统有整体视角才能识别根本原因。

软件故障检测与预防

由于许多软件故障会在损坏梯度的同时让流水线保持运行,因此软件故障缓解策略必须为每个生命周期阶段分配一个检测任务。开篇示例中的提示词截断 Bug 不应存活到生产监控阶段:单元测试应捕获分词器边界,集成测试应捕获批次形状和元数据路径,回归测试应保护具有代表性的提示词,运行时监控应检测任何逃过开发阶段的意外截断分布。表 7.5 按照其设计捕获的损坏类型组织了这些关卡。

| 类别 | 技术 | 捕获的损坏 | 应用时机 |

| --- | --- | --- | --- |

| 测试与验证 | 单元测试、集成测试、回归测试 | 分词器边界、批次形状、黄金示例回归 | 开发期间 |

| 静态分析与代码检查 | 静态分析器、代码检查工具、代码审查 | 不安全的切片、隐式转换、无保护的形状假设 | 集成前 |

| 运行时监控与日志 | 指标收集、错误日志、性能分析 | 长度分布偏移、NaN 梯度、内存增长异常 | 训练和部署期间 |

| 容错设计 | 异常处理、模块化架构、检查点 | 坏批次封闭式失败且恢复从已知良好状态继续 | 设计和实现阶段 |

| 更新管理 | 依赖审计、测试暂存、版本跟踪 | 分词器、数据整理器、框架和 CUDA 兼容性漂移 | 系统升级或部署前 |

| 环境隔离 | 容器化(例如 Docker、Kubernetes)、虚拟环境 | 环境特定行为和不可复现的依赖栈 | 开发、测试、部署 |

| CI/CD 与自动化 | 自动化测试流水线、监控钩子、部署关卡 | 未经测试的模型、数据或预处理变更进入生产 | 开发全过程持续进行 |

表 7.5:故障缓解策略:ML 系统中的软件故障需要分层关卡来捕获不同的损坏模式:分词器边界错误、批次形状漂移、不安全切片、依赖不兼容和运行时异常。

这些防护措施只有在每个关卡都针对具体的损坏路径时才有意义。仅检查分词器是否运行的单元测试太弱了;它必须断言系统提示词、标签、掩码和序列长度元数据在预处理过程中保留了预期的语义。集成测试随后跟踪一个批次经历加载、分片、整理和模型输入构建,而回归测试则在代表性提示词和边缘情况进入分布式执行前予以保留(图 7.22)。图 7.23 中的持续集成/持续部署 (CI/CD) 结构,只有当发布流水线将模型、数据和预处理变更视为可能损坏训练目标的 ML 制品时才有用;否则,它只是一个通用的软件交付图。自动化关卡将这些检查变为发布要求,而非可选习惯。

图 7.22:自动化回归测试:回归测试的经常性关注点,涵盖平台覆盖、基于风险的选择、并行执行、单元和 API 覆盖、性能影响及持续方法论。对于 ML 流水线,这些相同的关卡必须在代码变更中保留分词器行为、特征语义和批次构建。来源:UTOR

图 7.23:CI/CD 流水线:标准软件交付流程,从开发者提交经版本控制、构建、自动化单元和 UI 测试、打包,到发布正式可用版本,分组为持续集成构建流水线和持续交付发布流水线。随附文本解释了该结构必须增加的额外关卡——针对模型制品、数据契约、依赖版本和预处理语义——以使其成为面向 ML 的容错结构。来源:geeksforgeeks

这些软件实践捕获通过测试或部署关卡显现的实现故障。静默损坏更难应对,因为系统可能在产生错误值的同时继续运行,因此下一层防御是显式验证。

检查与验证:抵御静默数据损坏

训练步数上两条趋势线:红色加速上升的静默损坏风险曲线高于较低的蓝色基线,间隙标红。

静默数据损坏累积至成为常态。

随着集群扩展至 100,000+ GPU,在大规模集合操作期间发生“静默数据损坏”(SDC)事件(即 ALU 或 HBM 位翻转发生却未触发 ECC 或硬件告警)的概率趋近于必然。标准 AllReduce 算法假设如果节点存活,其数据就是正确的。在机器学习集群中,我们必须转向拜占庭容错思维:“信任,但要验证。”

检查与验证方法将这种思维转化为数据路径不变量。系统对每个梯度分片或归约缓冲区计算校验和、哈希或冗余归约,跨秩比较结果或与独立计算的摘要比较,并在验证不一致时阻塞优化器步骤。快速估算使风险具体化。

问题:计算在一个 100,000 GPU 集群中,至少有一个 GPU 在单个 2 秒训练步骤期间经历静默 ALU 错误的概率。

  1. 集群规模:100,000 个加速器。

  2. 单体风险:每小时 10⁻⁶(SDC 的保守估计)。

  3. 风险暴露:在 2 秒窗口内,集群有 100,000 × (2/3600) ≈ 56 个“GPU 小时”的暴露量。

  4. 概率:Pr(至少发生一次 SDC) ≈ 0.0056%。

系统洞察:在 10 万 GPU 集群中,每 18,000 步(约每 10 小时)就会发生一次静默错误。检查与验证方法为每个梯度分片或归约缓冲区计算 CRC 或哈希,跨秩比较摘要或与冗余归约比较,并在摘要不一致时阻塞优化器步骤。如果没有带校验和的集合通信或哈希验证梯度,模型参数会在半天内悄悄累积损坏的贡献并发生漂移。鲁棒性从重启问题转变为验证问题:集群必须执行冗余归约或使用受奇偶校验保护的梯度来捕获静默参数损坏。

该计算使检查与验证成为强制要求,但验证只有在针对真实故障经过测试时才能赢得信任。因此下一步是故意注入故障,并确认相同的检查能在损坏到达模型状态前将其捕获。

故障注入工具与框架

只有当注入的故障类似于系统必须经受的生产故障时,验证才具有价值。一个声称能容忍网络分区的多节点训练集群,应在预发布环境中切断该连接,以便工程师观察编排层的行为。故障注入 是一门工程学科,通过有意扰动系统,在生产流量发现缺口之前,用实证方式验证鲁棒性机制是否有效。ML/张量/GPU 故障注入工具覆盖位翻转、张量故障和加速器导向的故障 (Z. Chen 等 2020;Tsai 等 2021;Gräfe 等 2023),而网络分区、延迟注入、依赖终止和 API 响应故障则属于混沌工程和分布式系统测试实践 (Basiri 等 2016)。

故障与错误模型

这种相似性是建模决策,而非实现细节。瞬态位翻转、永久性加速器缺陷和损坏的梯度需要不同的检测逻辑,因此实验必须在开始前指定持续时间、位置、粒度和传播路径。

这些选择定义了 故障模型:硬件故障如何在系统中显现。相应的 错误模型 则表示该故障如何传播并影响系统行为。

第一个建模选择固定了故障的物理特征。持续时间决定了恢复机制应预期瞬态事件、永久缺陷,还是难以复现的间歇性状况。位置决定了注入故障是通过存储单元、功能单元还是互连进入。粒度决定了实验研究的是单个比特(如位翻转)还是多比特突发错误。

错误模型将该物理特征带入 ML 系统。它描述了初始的硬件级扰动如何变为损坏的权重、错误计算的激活值,或 ML 框架中的高层逻辑错误。抽象层级很重要,因为每一层暴露不同的传播路径并掩盖不同的效应。

故障或错误模型的选择是鲁棒性评估的核心。例如,一个为研究单比特瞬态故障而构建的系统 (Sangchoolie 等 2017),无法为永久多比特故障的影响提供有意义的洞察,因为其设计和假设完全建立在另一种故障模型之上。

错误模型的实现上下文也很重要。在架构寄存器层面建模的单比特翻转(使用 gem5 等模拟器 Binkert 等 2011),与 PyTorch 模型权重张量中的类似比特翻转有着本质区别。虽然两者都模拟值级扰动,但底层模型捕捉了通常在软件框架中被抽象掉的微架构效应。

某些故障行为模式可跨抽象层级迁移,而另一些则不能。比较单比特和多比特故障模型的研究表明,影响取决于故障落在何处及如何传播,因此不应将一种注入故障模型的鲁棒性结果视为通用结论 (Sangschoolie 等 2017;Papadimitriou 和 Gizopoulos 2021)。其他重要行为如 错误掩盖 (Mohanram 和 Touba 2003) 可能仅在较低抽象层级可见。这种掩盖现象可能导致故障在传播到高层之前被过滤掉 (图 7.24) (Ko 2021),意味着基于软件的工具可能完全漏掉这些效应。

图 7.24:错误掩盖:微架构冗余可在单比特故障传播到可观测的系统错误前将其吸收,凸显了硬件级和软件级故障模型之间的差异。该图详细说明了故障掩盖如何发生在微架构组件内部,表明基于软件的错误检测工具可能低估系统对瞬态错误的真实韧性。

故障注入方法

故障注入方法在真实性与实验规模之间权衡。基于硬件的注入是校准基准:它将故障引入物理系统,以便对照真实硬件行为核查软件层面的假设。基于软件的注入是可扩展的对应方:它快速覆盖许多模型状态,但在其结论成为生产可靠性声明前,必须对照硬件行为进行校准。

基于硬件的故障注入

方法选择取决于实验是否需要比特级定位或物理辐射真实性。基于 FPGA 的故障注入使用现场可编程门阵列 (FPGA)¹⁰⁷,一种可重构集成电路,可编程实现各种硬件设计。通过修改 FPGA 配置,可在 ML 模型执行期间的特定位置和时间引入故障。

辐射或束流测试 (Velazco 等 2010) 让运行 ML 模型的硬件暴露于高能粒子(如质子或中子)中。专用测试设施支持可控辐射照射以诱发位翻转和其他硬件级故障,提供高度真实的故障场景,镜像辐射丰富环境中的条件。

基于软件的故障注入

基于软件的故障注入以物理真实性换取实验规模。这些工具通过修改模型的底层计算图、张量值或中间计算来模拟硬件故障的效应。它们直接集成到 ML 开发流水线,无需专用硬件,并允许研究人员快速、低成本地开展大规模故障注入实验。

PyTorchFI (Mahmoud 等 2020),一个由 Nvidia Research 合作开发的 PyTorch 专用故障注入库,适用于研究张量可见扰动如何影响模型行为。它向权重、激活值和梯度注入故障,表明即使简单的比特级故障也能导致严重的视觉和分类错误,包括出现并不存在的“幽灵”物体。

弥合软硬件鸿沟

抽象层级的选择是基于软件的故障注入的核心风险。这些工具提供速度和灵活性,但并不总能捕捉硬件故障能施加于系统的全部效应范围。抽象鸿沟 产生的原因在于:基于软件的工具在较高层级运行,可能忽略底层硬件交互或细微的错误传播机制。

Fidelity (He 等 2020) 等工具通过将底层硬件错误行为映射到软件可见效应来解决这一鸿沟。通过研究源自硬件的故障如何穿过架构寄存器、存储层级和数值运算,Fidelity 使软件注入的故障类似于故障在物理系统中显现的方式。

场景:某团队用软件故障注入验证训练系统,得出结论:特定张量位翻转总是损坏模型输出。

机制:物理束流测试可能产生不同结果,因为硬件包含软件抽象层以下的 掩盖效应。硬件寄存器中的位翻转可能被 ECC 内存纠正,或被逻辑门掩盖,从而在到达软件工具直接修改的变量之前就被处理掉了。

系统课程:软件故障注入对于快速覆盖模型可见状态很有用,但当它绕过度触发电路级防御时可能高估脆弱性。因此,生产环境的恢复力声明需要硬件校准的故障模型,而不仅仅是软件注入。

硬件故障摘要

虽然硬件故障和软件故障代表不同的故障机制,但它们最终会表现为必须由本章前面建立的可靠性逻辑管理的系统级事件。表 7.6 中的故障特性从故障模型闭环到恢复选择。该表比较了瞬态故障、永久性故障和间歇性故障在持续时间、持续性、原因和对机器学习系统的影响方面的差异,以便检测和缓解策略与被测试的故障类别相匹配。

| 维度 | 瞬态故障 | 永久性故障 | 间歇性故障 |

| --- | --- | --- | --- |

| 持续时间 | 短暂、临时 | 持续存在,直至修复或更换 | 偶发,出现后又消失 |

| 持续性 | 故障条件解除后消失 | 在被处理之前始终存在 | 不规则重复出现,不始终存在 |

| 原因 | 外部因素(例如,电磁干扰或宇宙射线) | 硬件缺陷、物理损坏、磨损 | 硬件条件不稳定、连接松动、组件老化 |

| 表现 | 位翻转、毛刺、临时数据损坏 | 卡死故障、部件损坏、设备完全失效 | 偶尔位翻转、间歇性信号问题、偶发故障 |

| 对机器学习系统的影响 | 在计算中引入临时错误或噪声 | 导致一致的错误或故障,影响可靠性 | 导致零星且不可预测的错误,难以诊断和缓解 |

| 检测 | 错误检测码、与期望值比较 | 自检、错误检测码、一致性检查 | 监控异常、分析错误模式和相关性 |

| 缓解 | 错误纠正码、冗余、检查点和重启 | 硬件修复或更换、组件冗余、故障转移机制 | 健壮设计、环境控制、运行时监控、容错技术 |

表 7.6:故障特性:瞬态故障、永久性故障和间歇性故障在持续时间、持续性和重复性方面有所不同,影响系统可靠性,并需要不同的缓解策略以实现健壮的人工智能部署(Constantinescu 2008 的故障分析表明,大规模训练系统将频繁遇到此类故障,因此需要健壮的机制来保存和恢复进度。

训练容错有三项义务:保存状态、检测并分类故障,以及恢复或重新调整作业。检查点满足第一项义务。后续章节将在此基础上构建恢复模型的其余部分:故障检测决定发生了什么,恢复过程恢复一致的进程组,弹性恢复则决定是否可以在工作节点集合发生变化的情况下继续作业。

检查点 是将完整的训练状态(参数、优化器状态和数据加载器位置)定期序列化到持久存储中的过程。

  1. 意义:它通过将检查点 I/O 与重新工作进行权衡,最小化系统故障后的工作损失。写入过于频繁会浪费加速器时间在存储流量上;写入过于罕见则在故障后可能需要重放许多已完成的步骤。Young-Daly 公式 (\tau_{\text{opt}} = \sqrt{2 \cdot T_{\text{write}} \cdot \text{MTBF}_{\text{system}}}) 通过选择能够平衡写入成本与预期丢失工作的间隔来捕捉这种局部权衡。

  2. 区别:与增量备份不同,检查点必须捕获确切的执行上下文(包括随机种子和学习率计划),以确保优化循环的确定性恢复。

  3. 常见误区:一个常见的误解是认为检查点只是“写入磁盘”。实际上,对于大型模型来说,这是存储系统的压力测试:数千个 GPU 的同时写入可能引发检查点风暴,导致整个网络结构(带宽)饱和。

一个在没有状态保存的情况下断电的训练集群会损失数百万美元的梯度更新——这些更新是在前几周计算得出的。防御措施是定期将模型状态写入持久存储。检查点捕获足够的状态以便从记录点恢复训练:模型参数、优化器状态、¹⁰⁸ 训练进度指标以及用于可重复性的随机状态。

堆叠的检查点有效载荷条形图,其中权重占据四分之一,而 Adam 优化器状态占据大约四分之三。

优化器状态,而非权重,主导检查点大小。

基于故障分析的检查点间隔

第 7.1.2 节 中所述的 Young-Daly 公式给出了最优检查点间隔,\tau_{\text{opt}} = \sqrt{2 \times T_{\text{write}} \times \text{MTBF}_{\text{system}}},但只有在两个输入都已知时才能应用。本章前面的故障分析提供了系统 MTBF 项,而上文建立的检查点有效载荷提供了T_write。在同时掌握两者之后,该公式不再是一个抽象定律,而是本章自身集群的具体调度计划。

图 7.25 为一个示例性的 1750 亿参数集群绘制了这种权衡:随着间隔延长,检查点保存开销下降,而故障导致的重复工作增加;Young-Daly 最优间隔位于它们之和的最小值处。其圆润的输入传达了曲线的形状;下面的逐步计算则基于本章自身计算出的 MTBF 和写入时间,精确地确定了该间隔。

图 7.25:检查点权衡:检查点保存开销随间隔延长而减少,但故障导致的浪费计算增加。Young-Daly 最优间隔使总开销最小。对于一个示例性的 1750 亿模型集群(~2.5 分钟的检查点写入时间,2 小时 MTBF),最优间隔大约为 24 分钟;下面的逐步计算则得出本章 10,000-GPU 集群的确切间隔。

本章自身的 10,000-GPU 集群提供了两个输入,用计算值替换了图 7.25 中的近似值。逐步级联计算([第 7.1.4 节](#ch017.xhtml_sec-fault-tolerance-reliability-reliability-worked-example-cluster-mtbf-calculation-9255))将系统 MTBF 设定为 3.69 小时,而上述检查点有效载荷的写入时间为 37 秒(在 100 GB/s 下写入 3.7 TB 的检查点)。将这些值代入 Young-Daly 公式,即可得到该集群的规范间隔。

问题:一个拥有 10,000 个 GPU 的集群,其 MTBF 为 3.69 小时。完整模型检查点的写入时间为 37 秒。最优检查点频率是多少?

数学:应用 Young-Daly 公式:\tau_{\text{opt}} = \sqrt{2 \cdot T_{\text{write}} \cdot \text{MTBF}_{\text{system}}}

检查点间隔优化

转换为通用单位

MTBF[system] = 3.69 小时 × 3600 秒/小时 = 13284 秒。

代入求解

已知 T[write] = 37 秒,MTBF = 13284 秒,代入公式得 τ[opt] ≈ 991.4 秒,约 16.5 分钟。

系统洞察:在此间隔下,“检查点税”(保存时间 + 重新计算时间)被最小化至约 7.5%。如果改为每小时检查点一次,会增加故障风险,导致约 14.6% 的集群算力被浪费。随着集群规模扩大,MTBF 下降,最优间隔必须相应缩短以适应这一变化。

这一结果展示了故障分析的重要性:不了解系统 MTBF,我们就无法理性地设置检查点间隔。采用该间隔时,检查点开销约消耗 7.5% 的训练时间,但该估算依赖的假设在生产环境中往往不成立。

杨-达利公式提供了宝贵的直觉,但建立在实践中可能不成立的假设之上:

  • 指数分布故障:假设故障率恒定。真实系统呈现“浴盆曲线”特征,在磨合期和损耗期故障率较高。

  • 确定性检查点时间:假设 T[write] 恒定。实践中,由于存储争用、网络拥塞和内存压力,检查点时间会有 2–3 倍的波动。

  • 恢复时间等于检查点时间:假设恢复读取的数据量与检查点写入的相同。实际上恢复往往需要 3–5 倍的时间,原因包括作业调度延迟、拓扑重构和预热。

  • 单一故障模式:假设一次只发生一处故障。关联故障(电源、散热、共享交换机)违背了该假设。

  • 无限时间线:针对长时训练优化。总时长与 MTBF 相当的短时运行需要不同的分析方法。

当假设被违背时,最优间隔可能会显著偏移,但重启开销不应被视为在每个检查点都要支付的代价。在一阶损失时间模型中,重启时间以每次故障恢复项的形式增加到总预期浪费中;除非显式推导并校准了恢复感知的达利类模型,否则它不会取代 \(\sqrt{2 \cdot T_{\text{write}} \cdot \text{MTBF}_{\text{system}}}\) 中的 T[write]

图 7.26 通过展示训练运行期间的事件序列,具体化了故障的时间成本:有效计算、周期性检查点、故障事件,以及带有相关浪费工作的恢复过程。

图 7.26:检查点-恢复时间线:训练运行经历计算阶段(绿色)和检查点写入阶段(蓝色)的交替。发生故障时(红色闪电),上一个已完成检查点之后的所有工作均丢失(灰色阴影)。恢复涉及作业重启开销、检查点加载和流水线预热,随后有效训练才能恢复。故障的总成本包括丢失的工作和恢复延迟两部分。

图 7.26 中的时间线揭示了为什么 T[restart]T[write] 同等重要:故障总成本是丢失工作量(由 τ[opt] 上界约束)和恢复时间(含作业调度、检查点加载、流水线预热)之和。在 T[restart] 超过 T[write] 3–5 倍的生产系统中,应将恢复时间纳入总浪费和 SLO 预算,并应使用引用的恢复感知检查点模型,而非将重启时间折算进单次检查点写入项。

检查点开销分析

除了检查点写入消耗的时间,检查点还通过内存消耗和训练中断带来额外开销。检查点序列化需要内存缓冲区来收集分布式状态并准备写入数据。对于同步检查点,所有工作进程必须在检查点完成前将其检查点数据保持在内存中。这可能需要大量额外内存分配。

同步检查点在检查点写入期间暂停训练。即使使用快速存储,暂停也会中断训练流水线,并可能导致 GPU 空闲。数据加载和前向传播在检查点操作期间无法进行。

公式 7.8 量化了因检查点导致的时间浪费:

f_ckpt = T_write / τ_ckpt + T_pause / τ_ckpt  (7.8)

此处,f[ckpt] 为无量纲的检查点时间损失比例,T[write] 为检查点写入时间,τ[ckpt] 为检查点间隔,T[pause] 为除检查点写入本身之外的任何训练暂停时间。这包括内存分配、协调和序列化。

“Stop-the-world” 的代价

大规模同步检查点的财务影响极为严重。当一个 10,000 GPU 的集群为在共享存储上进行多 TB 级检查点(通常耗时 2 分钟)而暂停时,它消耗了 \(10,000 × \frac{2}{60} \approx 333\) 空闲 GPU 小时,按约 3 美元/GPU 小时计算,单次检查点就浪费约 1,000 美元的算力。若每小时检查点一次,该空闲时间每天累积达 24,000 美元,皆因等待存储 I/O。这一经济现实驱动了异步检查点策略的激进采用,旨在将数据移动移出关键路径。表 7.7 揭示了不同模型架构的检查点特征差异巨大:大模型需要更长的写入时间,但也受益于相应更长的最优间隔。

下表 Archetype A 行使用的是混合精度优化器占用(2.1 TB),而非简介中引用的完整 FP32 训练状态总量(3.7 TB)。

| 模型类型 | 混合精度检查点大小 | 写入时间 (100 GB/s) | 最优间隔 (5 小时仅 GPU MTBF) | 保存开销 |

| --- | --- | --- | --- | --- |

| Archetype A (175B 稠密 LLM) | 2.1 TB | 21 秒 | 14.5 分钟 | 2.4% |

| 20B 稠密 Transformer 类 | 240 GB | 2.4 秒 | 4.9 分钟 | 0.8% |

| BERT-Large | 1.4 GB | 0.014 秒 | 22 秒 | 0.06% |

| Archetype B (大规模 DLRM) | 4 TB | 40 秒 | 20 分钟 | 3.3% |

| ResNet-50 | 102.4 MB | 0.001 秒 | 6 秒 | 0.02% |

| Vision Transformer 类 | 1.2 GB | 0.012 秒 | 21 秒 | 0.06% |

表 7.7:按模型类型划分的检查点开销:大型 LLM 的检查点大小使用混合精度优化器占用(见上文 Archetype A 注释),而非简介中的 3.7 TB 完整 FP32 训练状态总量。大模型需要更长的保存时间,但也受益于更长的最优检查点间隔。百分比列报告的是仅保存的检查点写入税,而非总杨-达利浪费;在最优点处,预期重做工作增加了相当的项。

虽然单个模型的检查点理论开销看似可控,但写入这些海量状态文件引入了新瓶颈。当成千上万 GPU 尝试同时向共享文件系统写入 GB 级数据时,由此产生的 I/O 拥塞可能让整个集群陷入停滞。将单模型示例扩展到数千个并发写入者,最直观地展示了该 I/O 突发的规模。

场景:训练作业从数千个加速器同一时刻保存分布式检查点。

机制:以 Archetype A 行中 2.1 TB 混合精度优化器占用为例——而非 3.7 TB 完整 FP32 总量——1,000 个工作进程各自写入约 2.1 GB 状态。在嵌入密集型推荐或万亿参数专家混合作业中,同一机制会演变成灾难:10,000 个 GPU,每个持有 10 GB 模型或嵌入状态分片,可向网络结构倾泻 100 TB 同时写入。

系统教训:检查点风暴发生在大规模并行写入淹没集群 I/O 基础设施时,导致交换机缓冲区溢出、存储控制器锁死。

虽然 表 7.7 显示开销百分比不高,但实际部署中往往遇到的检查点时间远超这些理论估算。诊断此类差异需要检查完整的系统栈。

问题

某团队训练 70B 参数模型时发现,每次检查点耗时 10 分钟,远超预期的 2 分钟目标。由于集群在检查点期间空转,训练吞吐量下降了 30%。请诊断瓶颈并确定恢复路径。

诊断

车队栈(fleet stack)揭示了检查点开销的来源。

图 1.13 中的车队栈框架将我们的调查构建在三个层面上。

分析:基础设施层

基础设施层揭示了硬件约束。

诊断从栈底开始:

  • 模型状态大小70B 参数 × 2 字节 (BF16) = 权重 140 GB,加上 FP32 Adam 优化器状态(一阶和二阶矩)560 GB,总计每次检查点约 700 GB(不含梯度、主权重、元数据或框架开销)

  • 存储系统:共享 NFS 文件服务器,10 Gbps 网络连接

  • 理论带宽:10 Gbps = 最大吞吐量 1.25 GB/s

  • 最小写入时间:700 GB / 1.25 GB/s = 560 秒 (9.3 分钟)

基础设施层揭示了第一个洞见:即使效率完美,NFS 带宽也无法为该模型规模实现 2 分钟的检查点。

分析:分发层

分发层将诊断重点从原始带宽转移到检查点协调上。

  • 检查点模式:同步,所有 512 块 GPU 暂停并等待

  • 写入模式:所有 64 个节点同时写入共享 NFS

  • 观测行为:写入耗时 10 分钟,而非理论最小值 9.3 分钟

分发层揭示了共享存储瓶颈。64 个节点争抢 10 Gbps 带宽,每个节点实际上仅分得 20 MB/s(10 Gbps 均分给 64 个节点)。若检查点均匀分片,每节点写入约 10.9 GB,写入时间仍接近 9.3 分钟。观测到的 10 分钟因此接近带宽下限,并非单纯增加 NFS 并发写入者所能解决。

分析:治理层

治理层将瓶颈与完成目标关联起来。

  • SLA 要求:训练必须在 2 周内完成

  • 当前轨迹:30% 的开销将训练延长至 2.6 周

  • 预算影响:训练延长导致额外 50 万美元算力成本

修复

面向分层的检查点路径消除了关键路径上的存储瓶颈。

  1. 基础设施层:安装本地非易失性内存表达 (NVMe) 缓存盘(每节点 3.5 GB/s)。

  2. 分发层:实施异步检查点:

    • 阶段 1:GPU → CPU 内存拷贝(快速,32 GB/s PCIe)

    • 阶段 2:CPU → 本地 NVMe(3.5 GB/s,训练恢复)

    • 阶段 3:后台 NVMe → NFS 拷贝(与训练重叠)

  3. 验证:若 GPU 到 CPU 的暂存跨 64 节点并行进行,关键路径数据拷贝降至约 0.34 秒(若通过单条 32 GB/s 路径串行则为 21.9 秒),一旦后台 NVMe 与 NFS 拷贝与训练重叠,训练影响从 30% 降至 1% 以下。

系统教训:诊断分布式系统故障需审视所有层面。纯算法修复(分发层)会因物理带宽不足而失败。纯硬件修复(基础设施层)若不懂协调模式则会浪费。解决方案需要多层变更协同工作。

间隔计算决定了系统何时应保存状态。下一个实现问题是如何写入和恢复该状态,而不让检查点本身成为关键路径。

同步与异步检查点

同步与异步检查点方法创造了不同的故障恢复权衡。同步检查点保证全局一致状态,所有工作节点处于同一训练步骤,简化了恢复逻辑。所有工作节点协调达到一致状态,写入各自部分,仅在所有写入完成后恢复训练。

异步检查点减少了训练干扰,但需追踪哪些工作节点完成了哪些检查点,增加了恢复协调的复杂性。工作节点将状态快照至 CPU 内存或暂存存储,随后继续训练,而后台进程将快照写入持久化存储。[¹⁰⁹]

检查点存储与恢复

第 4.7 节 所述的分级检查点存储架构——本地 NVMe 提供速度、分布式文件系统提供持久性、对象存储提供长期保留——为恢复机制提供了存储基础。容错的关键不在于检查点存储在何处,而在于恢复时能多快读取。恢复时间取决于存储层级带宽:本地 NVMe 支持最快恢复(每节点 5–10 GB/s),分布式文件系统提供中等速度与持久性(聚合 50–200 GB/s),对象存储恢复最慢但持久性最高,适合灾难恢复场景。

检查点协调模式

从分片模型的分布式检查点恢复,要求理解确保检查点一致性的协调协议。当训练跨越多个工作节点时,存在两种主要方法:中心化检查点(协调器收集所有状态并写入单个检查点)和分布式检查点(每个工作节点写入自己的检查点部分)。

中心化检查点

中心化检查点中,工作节点将状态发送给协调器进程,由其汇总并写入完整检查点。此法简化了检查点管理并生成自包含的检查点文件,但所有优势的代价都由协调器承担:所有状态穿越协调器网络链路,协调器需有容纳完整检查点的内存,且协调器故障会丢失检查点操作。此模式在数十个工作节点时尚可接受(管理简易性可能胜过瓶颈),但对数百或数千个工作节点变得不切实际,因为协调器成为吞吐瓶颈和单点故障。

决策边界在于状态大小和写入者数量。当运维简易性重于聚合带宽时,中心化检查点更具吸引力;但一旦检查点本身成为车队级对象,分布式和分片方法就成为必需。

分布式检查点

分布式检查点中,每个工作节点将其检查点部分直接写入共享文件系统或对象存储,如 图 7.27 与中心化方法的对比。协调器发出检查点信号并确认完成,但状态直接从工作节点流向存储,无需聚合。

![](https://github.com/OpenDocCN/dsai-notes-pt3-zh/tree/master/docs/hav-cs249r-mlsys-vol2/img/file150.svg)

分布式检查点架构

图 7.27:分布式检查点架构:集中式模式与分布式模式的对比。(顶部)集中式聚合在协调器处产生瓶颈。(底部)分布式分片使每个工作节点能够并行直接写入并行文件系统 (PFS),聚合存储结构的带宽并最小化训练暂停。

协调协议

协调协议分六步进行:

  1. 协调器广播带有检查点 ID 的检查点请求

  2. 每个工作节点达到一致状态(屏障同步)

  3. 每个工作节点将其分片写入 checkpoint_<id>/worker_<rank>.pt

  4. 每个工作节点向协调器确认写入完成

  5. 所有确认收到后,协调器写入检查点元数据

  6. 协调器广播检查点完成,训练恢复

该协议确保要么所有工作节点完成写入,要么检查点不完整。有效检查点具有完整的元数据。不完整检查点缺少元数据且可被检测。部分检查点可被垃圾回收。在实践中,这种严格性决定了系统能否声称提供严格一致性、有界异步一致性或最终一致性。

理想化协议假设第 2 步快速完成。在大规模场景下,屏障同步成为主要的检查点开销,因为在 10,000+ GPU 的集群中几乎总至少存在一个慢速工作节点。

一致性模型

严格同步:所有工作节点在完全相同的训练步骤进行检查点。提供最强一致性,但屏障同步开销最高。

有界异步:工作节点之间最多相差 k 步(通常 1 ≤ k ≤ 3)。检查点管理器跨工作节点追踪“检查点波前”。恢复使用所有分片间最早的一致性切片。这以大幅降低的同步开销为代价牺牲了完美一致性,也是生产系统实际采用的方案。

最终一致性:工作节点在方便时进行检查点,恢复时协调。开销最低,但需要复杂的恢复逻辑来重构一致状态。

两阶段提交

基本协议存在一个微妙的正确性缺陷:如果协调器在部分工作节点确认后但在写入元数据前崩溃,这些工作节点会认为检查点成功,而系统实际上没有有效检查点。生产系统使用[两阶段提交][¹¹⁰](一种经典的分布式系统协议 [Gray 1978])使检查点决策原子化:

  • 准备阶段:工作节点写入暂存位置并向协调器报告成功。

  • 提交阶段:协调器原子性地重命名或提交所有分片,将文件从 staging/ 移动到 checkpoints/。如果协调器在两阶段间故障,工作节点在下一次心跳时检测到故障并回滚暂存写入,因此要么所有分片一起提交,要么全不提交。

一致性仍是独立要求:同步梯度更新自然创建所有工作节点应用相同更新的步骤边界,但异步训练和流水线并行需要更仔细的协调来定义一致性切片。

分片检查点

现代分布式训练框架使用 ZeRO (Zero Redundancy Optimizer) 和 FSDP (Fully Sharded Data Parallel) 等技术将模型状态跨工作节点分区。在这些配置中,没有单个工作节点持有完整模型状态。每个工作节点仅持有其分配的参数分片及对应的优化器状态。

分片检查点[¹¹¹] ([Rajbhandari et al. 2020]) 利用这种分布:每个工作节点仅写入其分片,大幅降低单节点写入量。恢复时加载分片并根据恢复配置将状态重新分发给工作节点。

这种方法即使对于超大模型也能实现高效检查点。一个 1750 亿参数模型的 3.7 TB 检查点分布在 1,024 个工作节点上,每个节点仅需写入 3.6 GB,配合本地 NVMe 存储可在数秒内完成。

当恢复时的工作节点数量与检查点不同时,分片重分发必须将状态重映射到新的工作节点配置。这由弹性扩缩容或硬件变更引起。框架对灵活重分片的支持使即使工作节点数量变化也能恢复。然而,拥有有效检查点本身还不够。分片检查点缓解了 I/O 风暴,但系统仍必须识别故障、触发恢复并协调分布式分片,训练才能恢复。检测和恢复的速度决定了中断的真实代价。


验证理解:故障率和检查点代价如何设定最优检查点间隔?

故障检测与恢复

一个 GPU 悄无声息地挂起,利用率降为零,而其对等 GPU 在 AllReduce 屏障处无限期等待。系统每多花一分钟注意到这个慢速节点并重启节点,就要损失数千美元的集群空闲时间。检查点保存了状态,但恢复过程本身决定了故障浪费了多少算力。

[公式 7.9] 将恢复时间分解为四个主要组件:

Trecovery = Tdetect + Trestart + Tload + Twarmup (7.9)

其中:

  • Tdetect:实际硬件故障发生到系统将其分类为故障之间的时间

  • Trestart:作业调度器分配新资源并启动替换进程的时间

  • Tload:从分布式存储读取检查点状态到 GPU 内存的 I/O 时间

  • Twarmup:系统重填数据管线、编译即时 (JIT) 内核并稳定吞吐量的时间

每个组件提供不同的优化机会,主导项随集群配置而异。理解这种分解能实现针对瓶颈的精准投资,而非在所有组件上均匀改进。

故障检测机制

检测延迟阶梯图(对数刻度),按故障可见难度排序:进程崩溃约 30 秒,GPU 挂起约 120 秒,网络分区约 180 秒,静默数据损坏约 2 小时。跨度达两到三个数量级。

检测延迟从崩溃的秒级攀升至静默损坏的小时级。

检测是第一道防线,受速度与误报率的根本权衡制约。过激的超时会将暂时网络抖动误判为节点故障,触发不必要且昂贵的重启;过保守的超时会让整个集群空转,而死节点阻塞同步。

心跳监控是标准机制:每个工作节点定期向中央协调器或监控服务发送“我活着”信号。心跳丢失触发故障分类。心跳间隔 H 和超时 Ttimeout 控制权衡。在大规模集群中,心跳到达时间常因网络拥塞呈重尾分布,需自适应超时而非静态阈值。生产系统通常采用 Ttimeout = H + [d],其中 k 取 3 到 5,σ[d] 为观测到的网络延迟标准差。

集合通信超时提供了第二层检测机制。在同步训练期间,集合操作(AllReduce、Broadcast)是阻塞的:如果某个秩静默失败(例如 GPU 驱动程序冻结),通信器中的所有其他秩将无限期挂起,等待永远不会到达的数据。NCCL¹¹²为此提供了可配置的传输和 RAS 超时参数,而高层框架可能施加进程组操作超时。这些设置通常保守配置,以避免在合法的通信慢速期间崩溃作业,但这不幸地延长了T[detect]。

容器编排健康检查提供了第三层。Kubernetes 和 SLURM 提供存活探针(验证进程正在运行)和就绪探针(验证进程准备好处理请求)。这些独立于训练框架运行,能捕获应用级心跳可能遗漏的故障——例如进程存活但已死锁的情况。

损失峰值检测捕捉了最隐蔽的故障模式:静默数据损坏。那些不会导致进程崩溃但会破坏数学结果的硬件错误(例如 ALU 逻辑中的位翻转)会表现为损失函数突然的、灾难性的峰值。损失瞬间跳升 10–100 倍或崩塌为 NaN。与由高学习率导致的梯度爆炸不同,这些峰值在没有超参数变化的情况下发生。健壮的系统会在训练循环中添加检测此类异常的仪表,立即暂停,通过校验和或重放定位具有损坏梯度的秩,并在从最后一个健康检查点重启前排空该节点。

训练动态监控将检测范围扩展到显式错误之外。监控损失值、梯度范数和激活统计可以检测到那些产生错误结果但不触发异常的拜占庭故障。突然的损失峰值、梯度爆炸或各秩梯度分布中的统计异常,可能表明存在原本会在数小时内未被发现的静默损坏。

因此,运维层面的问题是:一旦这些层相互作用,检测到底需要多长时间?

生产经验表明,故障检测所需时间远超理论心跳超时的建议。核心挑战在于区分故障与拖后腿者,如表 7.8 所记录。这些延迟存在的原因是:激进的超时会导致误报(杀死健康但缓慢的工作进程),而保守的超时会延迟真实故障的检测。生产系统通常采用多阶段检测:快速的初始超时触发调查,较慢的确认超时触发恢复。

| 故障类型 || 典型检测时间 || 原因 |

| --- | --- | --- |

| 进程崩溃 || 5–30 秒 || 心跳超时 + 验证重试 |

| GPU 挂起 || 30–120 秒 || 必须与合法的慢内核区分 |

| 网络分区 || 60–180 秒 || 必须与暂时性拥塞区分 |

| 静默数据损坏 || 数分钟到数小时 || 需要统计异常检测 |

表 7.8:生产故障检测延迟:进程崩溃、GPU 挂起、网络分区和静默数据损坏的真实检测时间,以及每类的主要延迟原因,展示了为何仅靠静态心跳超时是不够的。

恢复程序

故障分类完成后,恢复程序执行一套严格的序列以恢复一致性:

  1. 作业终止:向所有存活工作进程广播 SIGTERM。在同步分布式数据并行(DDP)训练中,单个工作进程的丢失会使全局通信器失效,强制完全拆除。

  2. 资源回收:调度器将失败节点标记为“排水中”以防止立即重新调度,并从备用池请求替换节点。

  3. 作业重启:启动新容器(如有缓存则从缓存启动),并在所有节点上重新初始化训练二进制文件。

  4. 检查点加载:每个工作进程从分布式文件系统读取其状态分片。对于分片检查点,每个工作进程仅加载其分区。

  5. 状态同步:秩之间握手以建立新通信器(例如 ncclCommInitRank),工作进程验证它们都处于同一训练步骤。

  6. 训练恢复:数据加载器快进到正确的批次索引,训练循环从检查点步骤恢复。

自动恢复系统在无人工干预下执行这些步骤。现代训练框架与集群管理器集成,以自动化部分序列。DeepSpeed 记录了模型和 ZeRO 优化器检查点的保存/加载例程,提供了故障后所需的持久状态(DeepSpeed Developers 2026a)。PyTorch 的 torchrun 弹性启动通过其集合点机制记录了故障和成员变更行为,该机制是存活工作进程在重启后就成员资格和秩达成一致的协调器支持协议(PyTorch Contributors 2026b,2026a)¹¹³。

恢复验证是最后一步,但常被忽视。加载检查点后,验证通过确认模型参数与预期形状和 dtypes 匹配、运行几个训练步骤并检查损失是否与故障前值一致、确认梯度计算产生预期统计量,来确认恢复成功。如果恢复后损失立即发散,检查点本身可能已损坏,需要回退到更早的快照。

问题:考虑我们在 1,024 块 GPU 上训练 175B 参数模型。检查点大小约为 3.7 TB(权重 + Adam 优化器状态)。估算单次故障事件的成本。

设置:恢复预算 T[recovery] 包含四项。

  • T[detect]:60 秒(保守的心跳超时加验证重试)。

  • T[restart]:3 分钟(调度器排队时间 + 容器启动 + Python 导入开销 + NCCL 初始化)。

  • T[load]:37 秒(这是以 100 GB/s 聚合吞吐量从共享存储读取完整 3.7 TB 检查点的聚合读取时间;使用分片本地 NVMe 以 5 GB/s 读取时,每个 3.6 GB 分片加载时间远低于 1 秒)。

  • T[warmup]:2 分钟(JIT 内核编译、数据管道缓冲区填充、TCP 连接重建)。

总计T[recovery] = T[detect] + T[restart] + T[load] + T[warmup] ≈ 每次故障事件 6.6 分钟。

影响:随着集群规模增长,恢复时间优化愈发重要。根据表 7.1 的 GPU 基线,1,024 GPU 集群的 MTBF 为 49 小时,每天约 0.49 次故障,每日损失约 3 分钟,开销为 0.2%,较为适中。然而,10,000 GPU 集群的 GPU 级 MTBF 为 5 小时,每天约 4.8 次故障,每日损失约 32 分钟——开销达 2.2%,相当于每天浪费约 5,293 GPU 小时(每日损失分钟数乘以所有 10,000 块 GPU)。

温重启 vs. 冷重启

前文描述的标准恢复程序是冷重启:集群中的每个进程都被杀死,整个状态从持久存储重新加载。冷重启健壮且简单(它不对内存中状态的有效性做任何假设),但很浪费。当 1,000 GPU 集群中仅有一块 GPU 失败时,冷重启会丢弃 999 个健康工作进程的有效内存状态,强制它们全部从磁盘重新加载。

预热重启与恢复自动化

预热重启

预热重启可保留存活工作进程的状态。当某秩(rank)发生故障时,存活的秩会检测到故障但不会退出。它们进入等待状态,在 GPU 内存中保留已加载的模型权重和优化器状态。调度器仅替换故障节点。新节点加入后,从磁盘加载其对应的状态分区(或通过广播从对等节点接收),随后重建通信器。训练以最小干扰恢复。

预热重启可将 99.9% 集群的 T[load]T[warmup] 降至接近零,将总恢复时间从分钟级缩短至秒级。以 1,024 GPU 为例,预热重启避免了从存储重新加载 3.7 TB 数据,除替换节点外,为所有节点节省了 37 秒的 T[load] 和 2 分钟的 T[warmup]

权衡在于软件复杂性。预热重启要求应用程序能处理动态成员变更,且不泄漏 CUDA 内存、不破坏共享状态、在通信器重建期间不发生死锁。TorchElasticDeepSpeed 等框架提供此能力,但预热重启过程中的故障模式(例如通信器重建期间崩溃)必须通过回退到冷启动来处理。成熟的部署可将预热重启作为快速路径,冷启动作为安全网。

表 7.9:冷启动与预热重启的权衡

| 方面 | 冷启动 | 预热重启 |

| :--- | :--- | :--- |

| 恢复时间 | 4–10 分钟(完全重载) | 30–90 秒(单节点重载) |

| 状态保证 | 干净:所有状态来自检查点 | 假设存活状态有效 |

| 实现 | 简单:终止全部,重载全部 | 复杂:动态成员管理 |

| 恢复期间故障 | 重试冷启动 | 回退到冷启动 |

| 最适用场景 | 相关故障、SDC 事件 | 单节点故障、GPU 错误 |

表 7.9:冷启动与预热重启的权衡:冷启动终止所有进程并从磁盘重载;预热重启保留存活工作进程,仅替换故障节点。预热重启将典型单节点故障的恢复时间从分钟级缩短至秒级,但引入了动态成员管理方面的软件复杂性。成熟部署可将预热重启作为快速路径,冷启动作为安全网。

恢复自动化流水线

在 10,000+ GPU 的规模下,人工干预每次故障是不可能的:故障每天会发生多次。恢复必须是由集群控制平面管理的自主控制回路。Meta、Google 和 Microsoft 发布的大规模系统展示了多阶段自动化流水线,它们对故障进行分类并选择最小可行的补救措施。

流水线按顺序分四个阶段运行:

  1. 健康监控守护进程:持续抓取 GPU 遥测数据(ECC 错误计数器、温度、风扇转速、NVLink 状态)以及应用指标(如训练损失和步吞吐量),通常来自 sidecar 容器。

  2. 故障分类器:判断信号是表示致命错误(例如 NVIDIA Xid 错误 48:双比特 ECC 错误)、瞬态停顿(例如暂时性网络拥塞)还是性能下降(例如热节流)。

  3. 动作选择器:选择合适的响应。进程挂起触发容器重启(快速、本地);GPU 硬件错误触发节点驱逐和替换(较慢,需要备用容量);网络分区触发暂停并等待策略(保留内存中状态)。

  4. 验证阶段:恢复后运行“金丝雀批次”以确认损失值与故障前匹配。如果损失立即发散,检查点可能已损坏,触发自动回退到更早的快照。

这种自动化将大多数故障类型的平均恢复时间(MTTR)从人工干预典型的 30–60 分钟缩短至 10 分钟以内。分类阶段至关重要:将每次故障都视为冷启动会在瞬态问题上浪费算力;将硬件故障视为瞬态则会让损坏的计算继续进行。

区分掉队者与故障

掉队者 是分布式训练作业中处理任务速度显著慢于同伴的工作进程,造成同步瓶颈。

  1. 重要性:在同步系统(BSP)中,集群吞吐量受限于最慢秩的速度。单个节点 10% 的性能下降可使数千个节点的有效算力降低 10%。

  2. 区别:与硬件故障(节点停止)不同,掉队者持续产出正确结果,但违反了高效并行执行所需的时间一致性。

  3. 常见误区:常见误解认为掉队者仅由“坏硬件”引起。实际上,它们常由系统抖动导致:后台操作系统进程、网络拥塞、或数据中心机房地板上各异的热节流。

掉队者是指功能上保持正确但执行速度显著慢于同伴的工作进程。在同步训练中,掉队者是性能毒药:整个集群的速度由其最慢组件决定,因为 AllReduce 无法在每个秩提交梯度前完成。

对于简化的无重叠掉队者模型,步时间变为:Tstep,straggler = max(T[rank[0]], T[rank[1]], ..., T[rank[N-1]]) + Tcomm

掉队者源于不触发显式错误的“灰色故障”:热节流降低时钟频率,退化的互联电缆增加通信延迟,操作系统后台进程(内存清洗、日志轮转)消耗 CPU 周期,以及来自拥塞网络文件系统的数据加载引入可变 I/O 延迟。与硬故障不同,掉队者不触发超时,从而允许它们静默地拖累全局效率数小时。

挑战在于区分掉队者与故障。掉队者应触发缓解(重新分配工作、替换慢节点)。故障应触发恢复(检查点重启)。激进的超时将掉队者视为故障,导致不必要的作业重启,浪费的算力比掉队者本身更多。保守的超时则在永远不会加速的掉队者身上浪费算力等待。

掉队者缓解策略

掉队者缓解策略涵盖不同激进程度的手段:

  • 备用工作进程:复制分配给慢工作进程的工作,采用最先完成的结果,以算力换取低延迟。

  • 有界陈旧性:允许训练使用慢工作进程的陈旧梯度继续进行,接受微小的收敛惩罚。

  • 动态负载均衡:将数据分片从慢工作进程重新分配出去,减少其每步工作量。

  • 主动替换:利用 GPU 遥测趋势(温度上升、ECC 错误计数增加)检测退化的工作进程,并在它们变成掉队者前替换它们。

简单的成本计算表明,对于严重的掉队者,替换可能比容忍更划算。

一个慢秩在同步屏障处阻塞健康秩

单个掉队者让所有健康加速器空转。

以我们的 1,024 GPU 集群为例,正常训练迭代耗时 1 秒。单个 GPU 进入热节流状态,频率降至 50%,完成计算现在需要 2 秒。

因为 AllReduce 无法在每个秩提交梯度前完成,其他 1,023 个健康 GPU 只能空闲等待掉队者。

影响

  • 正常步时间:1 秒

  • 掉队者步时间:2 秒

  • 集群有效速度:1 步/2 秒 = 0.5 步/秒

单个故障设备(占硬件的 0.1%)将整个集群的吞吐量降低了 50%。按 $3/GPU 小时计算,这个落后者每小时浪费 $1,536 的闲置算力。

系统洞察:将严重落后者视为硬故障在数学上是最优的。检测并终止慢节点以强制其在健康硬件上重启,比容忍性能下降能获得更高的长期吞吐量。盈亏平衡点:如果落后者使集群减速超过 T_recovery / MTBF_system(因故障恢复所花费的时间比例),立即替换比等待更划算。

然而,终止慢节点并重启作业意味着要等待替换节点以维持原来的 GPU 数量。检查点重启恢复模型假设恢复相同的资源分配:检查点、等待替换、重启。弹性恢复打破了这一假设,它允许作业以更少的工作节点继续运行,而不是让整个集群空闲等待直到恢复原始数量。

验证你对检测延迟和恢复策略如何相互权衡的理解:

弹性恢复

假设一个 1,024 GPU 的训练作业因硬件故障丢失了一个 8 GPU 的节点,但集群没有备用节点。在检查点重启恢复模式下,剩余的 1,016 个 GPU 会空闲数小时等待维修。弹性恢复则允许作业动态调整规模并以略少的算力继续运行,打破了固定工作节点数量的刚性假设。

弹性训练 是分布式训练作业在执行期间动态调整其工作节点数量的能力,且无需完全重启。

  1. 意义:它将硬故障转化为吞吐量下降而非完全停止。丢失 8 个 GPU 的 1,024 GPU 作业可继续以 99.2% 的产能运行,而不是降为零。这需要重新校准学习率并调整梯度累积,以在全局批次大小变化时保持数学一致性。

  2. 区别:与检查点重启恢复(暂停作业、等待替换硬件、重载状态)不同,弹性恢复始终是在线的:优化循环永不停止,仅改变其并行宽度。

  3. 常见陷阱:一个常见的误解是弹性是“自动的”。实际上,恢复需要协同适应:训练框架、数据加载器和编排器必须同步其状态,以确保在调整规模期间没有数据样本丢失或重复计算。

图 7.28 追踪了恢复序列:检测故障、暂停、在幸存工作节点间重新缩放、恢复。

![](https://github.com/OpenDocCN/dsai-notes-pt3-zh/tree/master/docs/hav-cs249r-mlsys-vol2/img/file153.svg)

图 7.28:弹性训练恢复:与失败即中止的静态训练不同,弹性训练能够适应。当工作节点故障时,作业暂停,将数据集和模型分片重新分发到剩余的 N - 1 个工作节点,并从最后一致状态恢复训练。这种能力将硬故障转化为暂时的吞吐量下降。

恢复适应机制

如图 7.28 所示,弹性恢复将本应是硬故障的事件转化为优雅的容量调整:丢失的工作节点减少了活跃数量而非中止作业,运行中的步骤适应更小的资源分配,集群在几秒内而非几分钟到几小时内恢复生产性工作,无需等待替换硬件。当故障减少工作节点数量时,幸存工作节点必须在恢复前适应几个训练组件。每项适应都解决一个特定的一致性要求,如果违反,将损坏模型或浪费计算。

批次大小调整是首要考虑:工作节点减少时,每个工作节点必须处理更多样本以维持全局批次大小,或者必须减小全局批次大小。恢复期间减小全局批次大小可能需要调整学习率以保持收敛特性。

批次大小与最佳学习率之间的关系决定了作业在不破坏训练稳定性的情况下能多激进地调整规模。Goyal 等人(2017)证明,对于带预热的大批次 ImageNet 训练,线性缩放规则效果很好:将批次大小按因子 k 缩放时,学习率也按因子 k 缩放。公式 7.10 给出了一个替代的平方根启发式方法,在恢复期间提供更保守的调整:

\[ \eta_{\text{new}} = \eta_{\text{base}} \times \sqrt{\frac{N_{\text{new}}}{N_{\text{base}}}} \qquad(7.10) \]

其中 N 代表工作节点数量(从而决定全局批次大小)。当其他优化器假设与原始配方匹配时,线性规则(Goyal 等人 2017)通常在带预热的大批次训练中更受青睐,而平方根缩放是一种保守的恢复策略,而非有论文支撑的定律。

梯度累积提供了一种替代学习率调整的方案:为了用更少的工作节点维持原始有效批次大小,每个幸存工作节点可在同步前跨多个微批次累积梯度。如果工作节点数量减半,将累积步数加倍可保持相同的有效批次大小并避免任何学习率变化,代价是单步墙钟时间翻倍。

数据加载器重新分发是恢复必须原子性处理的协调要求。当工作节点丢失时,数据加载器必须重新分配数据分片,以确保仍处理所有训练数据且无样本重复或丢弃。失败的重新分发会静默地破坏训练分布。

状态重新分片在使用分片模型并行(ZeRO/FSDP)时增加了进一步的复杂性,因为工作节点的丢失意味着模型状态的一个分片不再本地驻留。恢复可在线进行(将孤儿分片迁移到幸存工作节点)或通过检查点重载(从最后一个持久检查点重新分片)。

抢占式实例抢占作为一种故障类别

硬件故障不是将工作节点从运行中作业移除的唯一事件。云服务商通常将抢占式(“竞价”)实例定价低于按需容量,条件是可在极短预警下回收。从训练框架角度看,竞价实例回收与硬件故障无法区分:工作节点消失,作业必须决定是停止还是适应。

传统静态作业无法在竞价实例抢占下存活,因为单个节点丢失会终止整个运行。弹性恢复以与硬件故障相同的方式处理抢占:

  1. 作业检测到节点丢失(机制相同:超时或心跳失败)。

  2. 它短暂暂停以在幸存节点间重新分配工作负载。

  3. 训练以缩减规模恢复。

这种恢复能力可解锁显著的成本优势。组织可在更便宜的抢占式硬件上训练,因为弹性恢复将每次抢占转化为暂时的吞吐量降低而非致命错误。在竞价容量成本为 $0.60/小时而非 $3.00/小时的场景中,节省的费用可超过偶尔调整规模暂停带来的效率损失。

框架对弹性恢复的支持

框架问题在于它能自动强制执行哪些调整规模不变量:组成员资格、分片所有权、数据覆盖率和批次数学。上述恢复适应机制(批次大小调整、学习率重新校准、数据加载器重新分发、状态重新分片)必须由训练框架实现,框架在故障后自动化多少恢复逻辑各不相同。

服务容错

当用户要求语音助手关灯时,他们无法容忍推理服务器从检查点重载而导致的五分钟停顿。模型服务面临着一个截然不同的挑战:即使用户端 GPU 崩溃,用户也期望毫秒级的响应速度。

服务系统依赖副本、路由、就绪检查、负载均衡、KV 缓存状态(缓存的 Transformer 注意力状态)和优雅降级作为构建模块。服务容错探讨的是当副本、GPU 或状态存储在实时延迟预算下发生故障时,这些机制会如何表现。此处的关注点比完整的服务架构更窄:将每种机制视为实时推理的一个可靠性边界。

无状态与有状态服务

首个服务决策在于请求状态的存放位置,因为该选择决定了故障转移是一次重试,还是一个状态重构问题。在无状态服务中,每个请求都是独立的。服务系统不维护每会话状态;处理请求所需的所有信息都包含在请求本身及静态模型权重中。

当输入本身携带所有上下文时,便会出现无状态模式:图像分类器处理一张图片,目标检测器处理一帧画面,单轮文本分类器处理一段文本片段,嵌入服务将一个输入映射为一个向量。此时容错可聚焦于副本健康与请求路由。冗余副本并行服务请求,负载均衡仅将流量发送给健康副本,健康检查将故障副本移出轮转,自动替换会在容量下降时启动新副本。当副本故障时,发往该副本的在途请求可在别处重试,因为未丢失任何承载质量的会话状态。

弹性训练框架

PyTorch Elastic (TorchElastic)

PyTorch Elastic (TorchElastic) 通过一种会在工作组发生变化时重新执行的汇聚机制¹¹⁴ 来检测工作进程故障 (PyTorch Contributors 2026b, 2026a)。幸存的工作进程在重启后会以新的 RANKWORLD_SIZE 分配重新组成通信组。TorchElastic 处理秩重分配和通信组重建,但将批大小、学习率和检查点语义留给用户代码。

DeepSpeed

DeepSpeed (Rasley et al. 2020) 为大模型提供基于 ZeRO 的分布式训练和检查点构建模块 (DeepSpeed Developers 2026a)。其恢复模型基于检查点:故障发生后,作业从最新的持久化检查点重载,通用检查点解决了跨部分并行度和拓扑变更的可移植性问题 (DeepSpeed Developers 2026b)。其弹性行为仍依赖于外部启动器和资源管理器,而非仅由框架透明化实现。

Ray Train

Ray Train 基于 Ray 的 Actor 模型构建,为工作进程故障和节点抢占提供了一条面向检查点的恢复路径。Ray 可在故障后重启工作组并从最新可用检查点恢复,而训练函数仍负责保存和加载足够的状态 (Ray Project 2026)。

Horovod Elastic

Horovod Elastic 构建于 Horovod 的数据并行训练系统之上 (Sergeev and Balso 2018),具有用于工作进程增减的文档化弹性状态 API (Horovod Developers 2026)。当工作组发生变化时,Horovod 可重置秩并重建通信,而训练脚本仍负责诸如学习率或批大小策略等敏感收敛选择。

框架对比

表 7.10 总结的弹性训练框架支持情况,对比了这些框架在恢复自动化和状态管理方面的方法。

| 框架 | 故障检测 | 自动恢复 | 状态重分片 | 集群集成 |

| --- | --- | --- | --- | --- |

| PyTorch Elastic | 汇聚超时 | 是 | 手动 | Kubernetes |

| DeepSpeed | 外部 | 基于检查点 | 部署依赖 | 外部调度器 |

| Ray Train | Actor 监管 | 检查点重试路径 | 检查点重载 | Ray 集群 |

| Horovod Elastic | 驱动心跳 | 是 | 手动 | SLURM, Kubernetes |

表 7.10:弹性恢复框架对比:框架在如何检测工作进程故障,以及它们将恢复序列(组重建、状态重分布、批大小调整)自动化到何种程度上存在差异。

面向模型的训练容错

上述开发的检查点和恢复策略需要适配,因为不同工作负载在故障时会丢失不同种类的状态。因此,设计问题不仅在于多久打一次检查点,而在于哪个状态变量会让重启在数学上或经济上变得错误。对于推荐工作负载,增量检查点、分级检查点和嵌入版本控制可在全状态精确恢复代价过高时保护新鲜度。表 7.11 将该问题转化为一个紧凑的恢复映射。

| 工作负载 | 风险状态 | 检查点启示 |

| --- | --- | --- |

| LLM 训练 | 优化器状态、课程位置、文档位置、长上下文调度 | 在精度敏感处保留 FP32 优化器状态,配合 ZeRO/FSDP 分片写入,将序列化移出关键路径,并包含数据调度位置,以便优化轨迹正确恢复。 |

| 推荐 | 嵌入新鲜度、特征存储版本 | 采用增量检查点、分级检查点和嵌入版本控制,优先保障新鲜度而非全状态精确性。 |

| 视觉 | 增强种子、洗牌顺序、批归一化统计量、渐进式调度 | 捕获控制输入流的随机性和归一化状态;仅有权重不足以复现训练轨迹。 |

| 科学机器学习 | 模拟器状态、搜索前沿、已探索配置、验证种子 | 将模型、模拟器、搜索过程和随机状态视为一致性单元,以免恢复导致重复探索或对比失效。 |

表 7.11:训练恢复状态不变量:工作负载需要不同的检查点内容,因为它们使不同的状态变量成为质量承载体。正确的恢复策略始于会使重启变错的状态,随后据此选择分片、增量或领域状态捕获。

通用模式在于恢复正确性超越权重恢复。LLM 可重载权重却因错误的课程位置而恢复;推荐模型可重载稠密参数却提供陈旧嵌入;视觉模型可重载权重却改变了增强或归一化状态;科学模型可重载神经网络却丢失了使数据具有意义的模拟器状态。

弹性与检查点为长时间运行的批训练作业提供了必要的韧性,但一旦模型部署面向用户,运维核算便完全改变。挑战从保护数周的批计算转移到保护毫秒级的实时延迟

无状态服务的简单性使其在应用允许的情况下成为更简化的架构。然而,许多机器学习应用在请求之间固有地需要状态。以聊天机器人为例:单轮问答系统可以无状态运行,独立处理每个问题。而一个能够记住过往交互的对话式助手则必须维护对话历史,这将容错从简单的重试转变为了状态保留。

只要当前请求依赖于早期请求,就会出现有状态服务。大语言模型(LLM)对话会跨轮次累积 KV 缓存(所有先前 token 的键和值投影的缓存,其管理详见 第 10 章),流式语音识别维护来自前几段音频的上下文,推荐会话累积用户上下文,交互式编辑维护跨编辑的文档状态。其容错后果是:故障会导致承载质量的状态丢失,而不仅仅是服务能力的丢失。KV 缓存丢失需要重新处理之前的轮次,会话上下文丢失会迫使用户重复之前的交互,累积用户状态丢失会在上下文不可用时降低质量。缓解措施的选择取决于该状态的大小、更新率和质量价值:会话亲和性将会话路由到同一副本,状态检查点定期保存会话状态,状态复制维护一个备用副本,而优雅降级则在状态无法恢复时以较低质量保持服务可用。

表 7.12 对比了无状态与有状态服务的容错能力,展示了根本差异如何体现在设计的方方面面,从请求路由到恢复复杂性。

| 方面 || 无状态服务 || 有状态服务 |

| --- | --- | --- | --- |

| 请求路由 || 任意副本 || 会话亲和副本 |

| --- | --- | --- | --- |

| 故障影响 || 在另一副本上重试 || 潜在状态丢失 |

| 恢复复杂性 || 重启并加载权重 || 重载状态 + 重构上下文 |

| 冗余方法 || 主动-主动副本 || 复制状态 + 备用 |

| 故障切换延迟 || 毫秒级(负载均衡器) || 秒级(状态传输) |

表 7.12:无状态 vs. 有状态服务容错:由于需要保留累积的会话状态,有状态服务在容错方面引入了显著的复杂性。

冗余与复制

仅当副本放置和备用容量与所防御的故障域相匹配时,冗余才能带来可用性。多份服务能力的副本使得系统在单个副本故障时能继续运行。

可用性计算

对于具有可用性 A[single](任意给定时间处于运行状态的概率)的单个副本,公式 7.11 量化了多个独立副本如何实现更高的系统可用性:

A[system] = 1 − (1 − A[single])^(k)   (7.11)

其中 k 为副本数量。对于单副本可用性为 99% 的服务,冗余的复合效果如下。

对于单副本,A[single] = 99% 对应每年 3.65 天的停机时间。增加独立副本会迅速改变故障分布的尾部:两个副本提供 A = 1 − (0.01)² = 99.99%,即每年 52.6 分钟停机;三个副本提供 A = 1 − (0.01)³ = 99.9999%,即每年 31.5 秒。数学原理很有力,但它建立在独立性假设之上;共享电源、共享网络和共享软件漏洞会使实际可用性低于这些理论值。

时间阶梯图显示年停机时间从一个副本的 3.65 天降至两个副本的 52.6 分钟,再至三个副本的 31.5 秒。

冗余将天级停机时间转化为秒级。

复制策略

可用性公式说明了冗余能提供多大帮助;复制策略决定了该冗余在正常运行和故障切换期间的成本。在主动-主动复制图 7.29,左)中,所有副本主动服务请求,因此容量利用率高,但故障副本会立即增加幸存者的负载。在主动-被动复制图 7.29,右)中,主副本服务流量,而备用副本保持空闲但随时待命,以牺牲正常运行期间的资源利用率为代价简化了故障切换逻辑。

图 7.29:服务冗余策略:主动-主动与主动-被动复制的对比。主动-主动(左)将负载分布在三个活跃副本上,每个副本接收约三分之一的流量,最大化利用率但需要容量余量以吸收故障。主动-被动(右)通过心跳保持备用副本空闲并同步,简化故障切换逻辑,代价是闲置资源利用率。

跨区域复制将相同的选择扩展到区域层面。它防范数据中心或区域网络故障,但路由到远程区域的请求会支付额外的延迟代价。多层级设计同时结合多种复制策略:边缘缓存为延迟而复制,区域服务集群为可用性而复制,全局主节点或协调层则保持一致性和新鲜度。

副本放置与故障域

有效的冗余要求将副本放置在独立的故障域中。不同机器可容忍单机故障;不同机架可容忍因电源或机顶交换机问题导致的机架级故障;不同可用区可容忍数据中心区域故障;不同区域可容忍整个数据中心故障。独立性级别应与可用性要求和成本约束相匹配:区域复制成本高昂,因为它复制了算力和网络容量,但当区域故障不得不转化为用户可见的停机时间时,它是必要的。

放置仅创造了恢复的可能性。服务系统仍需要一个控制回路,以察觉故障、将受影响的副本从流量中移除,并保留足够的状态,使面向用户的服务能在有界降级内继续运行。

故障切换机制

当副本发生故障时,流量必须重定向到健康的副本。该故障切换的速度和可靠性决定了故障对用户的影响。该机制可分解为三个问题:系统如何检测健康状况,路由层如何根据该信号采取行动,以及已连接到故障副本的有状态会话会发生什么。

健康检查

健康检查决定何时转移流量,因此它们不仅要测试存活性,还要测试就绪性和推理正确性。存活性检查验证进程正在运行且响应正常,通常通过简单的 HTTP 端点返回 200 来实现,失败时触发重启。就绪性检查更进一步:对于机器学习服务,副本在模型权重加载完毕、GPU 初始化并响应正常、预热完成、以及特征存储和缓存等依赖可用之前,均不视为就绪。推理健康检查通过运行已知输入并验证预期输出,增加了正确性探测,可捕获进程健康但模型响应错误的静默故障。

健康检查参数设定了误报/延迟的权衡。诸如 5 s 的检查间隔控制采样频率,2 s 的超时控制耐心值,3 次探测失败的阈值将副本标记为不健康,2 次探测成功的阈值将其恢复服务。

负载均衡器集成

负载均衡器设计与故障转移

负载均衡器的设计决定了路由层在故障转移期间能利用多少请求上下文。

L4 与 L7 负载均衡对比

L4 负载均衡 基于 IP 和端口进行路由,操作简单快速。L7 负载均衡 基于 HTTP 和 gRPC 内容进行路由,支持更具体的路由规则。服务网格 在这些路由决策之上增加了流量管理、可观测性和安全性。

负载均衡器故障转移延迟

负载均衡器故障转移延迟取决于健康检查频率和故障检测逻辑。激进的设置能实现快速故障转移,但会增加误报率,导致在瞬时问题期间将健康的副本标记为不健康。

会话亲和性与有状态故障转移

对于有状态服务,会话亲和性将同一会话内的所有请求路由到同一副本。负载均衡器通过 Cookie、Header 或 IP 哈希实现的粘性会话,维护会话到副本的映射。状态故障转移的选择则在恢复速度、一致性和运维复杂度之间进行权衡:

  • 状态丢失 通过从头重建状态来接受质量降级。

  • 检查点 定期保存会话状态以供恢复。

  • 复制 将状态复制到备用副本。

  • 分布式状态存储 将会话状态移入 RedisMemcached 等外部系统。

选择取决于状态大小、更新频率以及状态丢失对质量的影响。表 7.13 总结了四种常见方法的权衡:

| 方法 | 恢复延迟 | 一致性 | 运维复杂度 |

| :--- | :--- | :--- | :--- |

| 状态丢失 | 快 | 无 | 低 |

| 检查点 | 中 | 最终一致性 | 中 |

| 同步复制 | 快 | 强 | 高 |

| 分布式状态 | 快 | 可配置 | 中 |

表 7.13:有状态故障转移方法:当服务副本发生故障时,处理会话状态的四种策略。状态丢失最快且最简单,但在故障转移时会丢弃质量;检查点以恢复延迟换取最终一致性;同步复制以运维复杂度为代价提供强一致性;分布式状态存储将状态管理拆分为独立层级,具备可配置的一致性。

模型特定的服务故障容错

模型特定的服务差异源于:哪些状态丢失代价高昂,以及用户需要多快得到响应。因此,策略问题归结为状态丢失预算:哪些状态可重建,哪些状态必须复制,哪些状态可暂时降级而不违反产品契约。表 7.14 从三种常见场景映射了这一问题。

| 服务工作负载 | 丢失代价高昂的状态 | 故障容错策略 | 降级路径 |

| :--- | :--- | :--- | :--- |

| LLM 对话 | KV 缓存、对话上下文、前缀状态 | 复制或检查点高价值会话状态;仅当延迟预算允许时才重新生成。 | 从对话记录重建、复用缓存前缀、或请求重试。 |

| 推荐 | 新鲜用户特征、物品特征、嵌入向量 | 复制特征存储、缓存热门特征,并将新鲜度作为可靠性信号进行监控。 | 提供过期或默认特征,并量化质量损失。 |

| 视觉服务 | 预处理状态、设备健康、边缘输入 | 在健康副本上重试无状态请求,在预处理或加速器健康故障时采用闭合故障模式。 | 使用更小的本地模型、推迟预测、或返回低置信度结果。 |

表 7.14:服务恢复状态不变量:服务故障容错始于其丢失会改变用户可见质量的状态。LLM 保护实时上下文,推荐系统保护特征新鲜度,视觉服务保护预处理和设备正确性。

LLM 服务中的 KV 缓存管理

KV 缓存[¹¹⁵] 可能非常大(跨注意力层的长上下文可达数 GB)。丢失 KV 缓存 需要重新生成所有之前的轮次,这可能需要数秒到数分钟。

内存阶梯对比:64 头 KV 缓存 344 GB 与分组查询 KV 缓存 43 GB,8 倍差距标记为比率注释。

一次长对话携带数十 GB 的实时状态。

LLM 服务的选择在于 KV 缓存 恢复延迟与内存/存储开销之间的权衡。最简单的策略是接受重新生成成本,故障后从对话历史重建 KV 缓存,但这可能将长对话变成数秒或数分钟的重启过程。KV 缓存检查点 定期保存实时状态并限制重新生成量,而 KV 缓存复制 以额外内存为代价保留备用副本以实现快速故障转移。前缀缓存 通过单独存储通用系统提示词和共享上下文,缩小了必须恢复的状态范围,因此仅需重新生成会话特定的状态。该方法产品化为云厂商提供的提示词缓存服务,存储并复用常见前缀的 KV 缓存,降低了故障时的成本和恢复时间。

推荐服务

推荐服务保护的是新鲜度而非对话连续性。推荐依赖来自特征存储的用户和物品特征,因此特征存储不可用会降低排序质量或完全阻断推荐。服务决策在于过期预算:跨可用区复制的特征存储保持近期特征可用,本地缓存覆盖热门特征,回退到过期特征接受可量化的质量损失,当查找失败时默认特征允许服务返回低质量结果。实时特征(如近期用户行为)使新鲜度监控成为故障容错的一部分,因为持续提供旧特征的管道在运维上是可用的,但在语义上是错误的。大型嵌入表也可能位于专用嵌入服务之后,这些服务需要自己的复制和故障转移计划。

视觉服务

视觉服务通常更接近无状态端,因为每张图片或帧都可在另一副本上重试。这种简单性并未消除故障容错工作;它只是改变了检查的位置。GPU 健康监控必须在副本返回损坏结果前,移除因热节流或内存错误而异常的副本;当调整大小、裁剪、颜色转换或归一化阶段发生故障时,预处理必须采用闭合故障模式。对于边缘视觉,主要故障模式可能是断连而非数据中心副本崩溃,因此系统通常通过使用更小的本地模型、延迟非紧急预测、或返回较低置信度结果来降级,直到云连接恢复。

当完全冗余失效时(例如边缘设备失去连接,或巨大流量峰值压垮可用的数据中心副本),系统不能简单崩溃。相反,它必须主动以输出质量换取持续可用性,这种策略称为 优雅降级

优雅降级

在大规模区域网络中断期间,某电商网站突然失去对其重型 GPU 加速推荐集群的访问。该网站没有向用户展示空页面或崩溃,而是立即切换为提供预计算的通用热门商品。上述开发的服务故障容错机制旨在维持全功能服务,但优雅降级规定了当这些防线被压垮时会发生什么。

优雅降级

优雅降级 是一种故障容错策略,系统通过主动降低服务质量(回退到更小模型、提供缓存结果、或返回部分输出)来应对资源耗尽或组件故障,而不是完全失效,从而在降低能力的前提下维持可衡量的可用性。

[¹¹⁵]: 脚注引用 115。

优雅降级

核心原则

重要性

优雅降级将全面停机风险转化为可控的质量下降。当推荐系统在 GPU 故障时从 70 亿参数的排序模型回退到协同过滤模型,点击率可能下降 8–15%,但能维持请求可用性,而不是将每个受影响的请求都变成错误。工程权衡的重点从总停机时长转移到了降级期间的可量化质量损失。

区别

与完全系统故障(服务向所有请求返回错误)不同,优雅降级通过预定义的能力层级提供受管的过渡——每个降级层级都有已知的准确性、延迟和资源特性,使系统能在降低质量的前提下继续满足 SLO。

常见误区

一个常见的误解是,在设计良好的系统中降级是自动发生的。优雅降级需要显式的预工程:回退模型必须预加载或预计算,切换逻辑必须能检测故障条件并无需人工干预即可触发过渡,并且每个降级层级在投入生产使用前都必须经过验证,确认能实际满足其降级后的 SLO。

降级维度

降级方案必须在事故发生前明确将牺牲哪项服务属性。表 7.15 总结了主要的控制杠杆。该表是事故前的设计清单,而非事故时的头脑风暴辅助:每个被牺牲的属性都需要有可度量的质量预算、激活条件和恢复路径,且这些都必须在故障发生前就已就绪。

| 维度 | 系统牺牲的内容 | 典型回退方案 |

| --- | --- | --- |

| 质量 | 模型准确性或排序质量 | 使用更简单的模型,例如为多塔推荐器使用协同过滤回退模型。 |

| 延迟 | 响应速度 | 将更多请求批处理在一起,以在高负载下保持吞吐量和模型质量。 |

| 覆盖范围 | 结果完整性 | 返回前 10 条搜索结果而非前 100 条。 |

| 新鲜度 | 计算结果的时效性 | 提供缓存或陈旧的候选项,例如故障期间提供一小时前的新闻推荐。 |

| 功能完整性 | 输入丰富度 | 当用户历史或实时上下文不可用时,使用内容特征。 |

表 7.15:优雅降级维度:降级方案选择哪项服务属性可以降低,同时仍能返回有用的答案。提前命名维度可防止系统将每次故障都视为非此即彼的“服务或崩溃”决策。

这些维度不可互换。搜索产品可能通过返回较少结果来牺牲覆盖范围,同时保持新鲜度完好;推荐系统可能通过提供缓存候选项来牺牲新鲜度,同时保持延迟不变;交互式助手可能通过使用更小的模型来牺牲质量,同时保持会话存活。因此,正确的回退方案取决于具体负载:它遵循其暂时性损失对应用危害最小且恢复路径可在事故前验证的那个属性。

优雅降级策略

表 7.15 中的维度只有在服务将每个被牺牲的属性绑定到特定的回退机制时才具备操作性。策略阶梯从模型回退开始(当主推理路径过于昂贵或不可用时),然后转移到特征回退(当输入质量下降时),最后是削峰限流(当需求超出容量且系统必须优先保留最高价值请求时)。

模型回退

模型回退通过维护具有不同资源需求的多个模型版本,将质量转化为显式的可用性杠杆。主模型以最高资源成本提供全功能,次级模型降低功能和资源需求,第三级模型保留最低限度功能,静态回退则无需推理即可提供预计算的默认值。当主路径不可用或过载时,策略仅按资源压力所需程度下降,主路径恢复健康后再向上回升。

图像分类级联使这种权衡具体化。主路径可能运行拥有 3.07 亿参数、ImageNet top-1 准确率 88% 的 ViT-Large 模型,压力下回退到拥有 1900 万参数、准确率 83% 的 EfficientNet-B4,当只能负担最小分类开销时再回退到拥有 540 万参数、准确率 75% 的 MobileNet-V3-Large。如果即使该路径也无法满足 SLO,服务可返回缓存标签、粗略类别或“分类不可用”,而不是无限期阻塞请求。要使该阶梯切实可行而非仅停留在愿景层面,生产系统必须保持多模型部署、将请求路由至适当层级、监控回退频率,并度量各层级的质量影响。

推荐和排序系统在主服务故障时,采用相同模式从复杂深度学习模型回退到 Top-N 热门列表、线性排序器或缓存响应。复杂模型往往为最后一哩准确率而战,而简单启发式提供了大部分效用,因此回退路径必须作为一等质量信号被监控,而非视为静默故障转移。否则产品表面看似健康,实则在无声地用质量换取可用性。

特征回退

特征回退控制在请求必须被阻塞前,模型可容忍多少输入质量损失。当特征检索失败时,预计算的人群级默认值可替代缺失的用户或物品特征:推荐系统可能使用平均用户嵌入、流派级物品默认值,或实时信号最近的缓存值。当默认值过弱时,系统可从可用数据计算近似特征,例如用于缺失用户历史的人口统计相似性、用于缺失物品属性的文本嵌入、或用于缺失上下文的基于时间的默认值。

关键设计步骤是按特征对预测质量的贡献进行优先级排序。表 7.16 总结了一个四级策略:关键特征阻塞请求,重要特征使用默认值,有用特征使用缓存值,可选特征可省略且质量影响有界:

| 层级 | 示例特征 | 缺失时的动作 | 质量影响 |

| --- | --- | --- | --- |

| 关键 | 用户 ID、物品 ID | 阻塞请求 | 无法服务 |

| 重要 | 用户历史、物品属性 | 使用默认值 | 5–10% 质量损失 |

| 有用 | 实时上下文 | 使用缓存 | 2–5% 质量损失 |

| 可选 | 次要信号 | 省略 | < 2% 质量损失 |

表 7.16:优雅特征降级层级:按对预测质量的重要性对特征分类,并给出各层级不可用时的相应响应。关键特征完全阻塞请求;低层级允许系统回退到默认值、缓存值或省略,且质量影响有界。这些层级将二元的“特征是否可用”问题转化为四步降级阶梯。

削峰限流

负载削减

负载削减通过在过载蔓延到每个请求之前牺牲选定的请求来保护系统。随机削减是最简单的策略:丢弃一部分传入请求,并为其余请求保留足够的容量。其弱点在于将所有请求视为同等价值。基于优先级的削减通过先服务付费用户而非免费用户、先服务产生收入的请求而非分析请求、先服务交互式流量而非后台批处理工作,将服务质量降级转化为显式策略。系统仍在降级,但它是根据预先声明的服务合同降级,而不是取决于哪个队列恰好先满。

入站控制

入站控制在系统入口点应用相同的理念。它拒绝会超出容量的请求,而不是接受会降低系统中已有每个请求性能的工作。熔断器[¹¹⁶]通过在模型副本、特征存储或支持服务不健康时快速失败来保护下游依赖。

熔断器状态

熔断器在三种状态下运行:关闭(正常运行)、打开(快速失败以防止资源耗尽)和半开(探测恢复情况)。图 7.30 展示了这些状态转换如何既能保护系统免受级联故障的影响,又能自动测试条件是否已改善。

图 7.30:熔断器状态:熔断器保护系统免受级联故障。关闭:正常运行。打开:错误阈值超标;所有请求快速失败以防止资源耗尽。半开:超时后,允许有限数量的请求通过以探测依赖的健康状况。成功则重置为关闭;失败则返回打开。

优雅降级实现

三态循环防止单个故障依赖消耗整个系统的连接池:打开状态快速失败,半开状态在恢复全量流量前探测恢复情况。实现始于将降级梯转化为控制回路。尾延迟百分位数显示用户何时感受到延迟,错误率显示哪个依赖出现故障,CPU/GPU/内存利用率显示系统是否饱和,队列深度显示服务接受工作的速度是否快于完成速度。这些健康信号决定何时进入和离开每个降级级别。

降级触发器

降级触发器定义激活降级的条件。清单 7.1 展示了三种常见的触发条件,它们根据延迟、错误率和特征存储健康状况逐步激活回退机制。

清单 7.1:渐进式降级触发器:基于尾延迟、错误率和特征存储健康状况激活优雅降级的条件。每个触发器激活一个适合检测到的问题的不同回退机制。

降级应随条件恶化而逐步增加,而不是在一个悬崖点突然切换。系统可以随着负载增加逐渐提高回退百分比,然后在流量返回高能力路径前要求持续改进。滞后机制防止当服务在阈值附近徘徊时在主模式和回退模式之间振荡。

降级监控与告警

由于优雅降级以质量换取可用性,监控必须使这种权衡可见,而不能让系统在提供低保真结果时看起来依然健康。监控界面必须暴露牺牲了多少服务质量。表 7.17 将该界面转化为行动图谱:回退模型份额揭示模型质量降低的频率,默认特征份额揭示输入质量损失,丢包率揭示容量保护,主/回退质量差距揭示降级路径是否仍可接受。

| 信号 | 暴露的质量权衡 | 升级规则 | 事后复盘问题 |

| --- | --- | --- | --- |

| 回退模型请求份额 | 为维持服务而降低模型能力 | 持续激活超过 5 分钟升级为警告。 | 回退是否保留了足够的用户价值? |

| 默认特征请求份额 | 因依赖滞后而降低输入丰富度 | 影响超过 50% 请求的严重降级升级为严重。 | 哪个特征依赖需要复制或缓存? |

| 负载削减请求率 | 为保护服务而降低覆盖率 | 熔断器打开后的任何增长触发容量调查。 | 入站控制是否足够早以防止级联故障? |

| 主/回退质量差距 | 事件期间丢失的产品质量 | 降级持续超过 1 小时升级,即使可用性指标看起来健康。 | 回退路径是否仍是有效的产品体验? |

表 7.17:降级监控行动图谱:只有当质量权衡可观测时,优雅降级才是安全的。每个信号将降级服务模式与防止静默质量损失的升级和事后复盘问题联系起来。

事件发生后,事后分析应识别根本原因,评估回退有效性,衡量用户和业务影响,并定义防止复发的改进措施。安全实施这些回退机制需要对系统运行时状态有深度可见性。当复杂的推荐管线开始降级时,运维人员必须迅速确定是哪个微服务出现故障,而这种诊断依赖于分布式调试和可观测性。

分布式调试与可观测性

凌晨 2 点,旗舰生成式 API 的延迟从 200 ms 飙升至 5 秒,但 CPU、内存和网络指标看起来完全正常。决定是削减负载、重启副本还是优雅降级,需要知道请求究竟在数百个微服务中的哪里停滞。分布式调试和可观测性提供了定位这些不可见瓶颈所需的诊断能力。

为什么分布式 ML 系统难以调试

分布式 ML 调试之所以困难,是因为恢复所需的证据分散在时序、状态、规模和模型行为中。分布式系统表现出源自多个来源的非确定性行为[¹¹⁷]。网络时序变化改变执行顺序,线程调度差异改变竞态条件,GPU 内核执行顺序在不同运行中变化,浮点运算顺序改变结果。在一次执行中显现的 bug 可能在后续执行中无法复现。这些“海森堡 bug”在被观察时似乎会消失。

部分故障将这种非确定性转化为恢复问题。与故障通常是全局性的单机系统不同,分布式系统经历部分故障,其中某些组件故障而其他组件继续运行,工作组件和故障组件之间的交互产生复杂的故障模式。规模使得人工检查不再是可行的调试方法:面对数千个组件,自动化工具必须过滤海量遥测流并识别出解释事故的极少数信号。ML 系统将模型行为添加到同一个诊断问题中,因为静默的准确率下降在无错误的情况下产生错误结果,NaN 和无穷大等数值问题通过计算传播,数据依赖型 bug 仅针对特定输入显现,学习导致预期行为变化,这可能类似于 bug。

可观测性支柱

可观测性

可观测性只有在能将症状与行动关联起来时才有用:削峰填谷、重启副本、回滚检查点或优雅降级。假设推荐服务突然违反其延迟 SLO,而模型副本仍报告健康的 GPU 利用率。指标确立症状的时间和范围,追踪显示哪个服务跨度消耗了请求预算,日志解释在级联开始时该服务内部发生了什么。图 7.31 总结了这三种证据类型,但操作规则是关联:除非信号能与同一请求、模型版本、特征版本和部署事件关联,否则单一信号是不充分的。

图 7.31:可观测性的三大支柱:车队故障诊断需要关联三种信号类型。指标揭示问题发生的时间地点(例如,GPU 利用率下降)。日志通过事件上下文提供内容(例如,Out-of-Memory 异常)。追踪通过跨分布式生命周期链接事件提供原因(例如,识别触发内存峰值的特定请求)。

指标是首要证据,因为它们将车队行为压缩为可触发行动的时间序列。在 ML 车队中,有用的指标涵盖基础设施信号,如 CPU 和 GPU 利用率、内存分配、网络带宽和存储 I/O;应用信号,如请求率、延迟百分位数、错误计数、队列深度和缓存命中率;以及 ML 特有信号,如按模型划分的推理延迟、批处理利用率、特征检索延迟、特征新鲜度和用于漂移检测的预测分布。只有当告警映射到恢复决策时,指标流才成为容错基础设施。高 p99 延迟可能触发削峰填谷,缓存命中率下降可能触发降级特征,突发的预测分布偏移可能在损害面向用户的决策前阻止发布。

日志提供指标有意丢弃的事件上下文。结构化日志使用如 JavaScript Object Notation (JSON) 等格式并带有一致的字段,因此事件工具可按服务、请求、模型版本、GPU、特征存储或追踪标识符搜索。清单 7.2 展示了一个 GPU 内存分配失败,它记录了本地故障上下文和连接事件到分布式指标和跨度所需的追踪标识符。

清单 7.2:结构化 JSON 日志条目:GPU 内存分配失败的结构化日志条目,包含用于关联分布式追踪和资源指标以进行诊断的追踪和跨度标识符。

对于容错而言,重要的日志属性是一致的关联性,而非特定的后端。日志级别应跨组件一致地将事件映射到升级流程,但恢复系统更依赖于操作员是否能按出现在指标和跨度中的同一请求、追踪、模型版本、特征版本和部署标识符查询事件。因此日志聚合很有用,因为它保留了本地故障上下文与车队级症状之间的关联。

追踪显示延迟或故障如何跨分布式组件传播。追踪是请求的端到端旅程,跨度是该旅程中的一个操作,上下文传播跨服务边界携带追踪身份。通过 ML 推理管道的追踪说明了这些跨度如何组合:

Trace: user-request-12345
├── Span: api-gateway (5 ms)
│   └── Span: auth-service (2 ms)
├── Span: feature-service (15 ms)
│   ├── Span: user-feature-lookup (8 ms)
│   └── Span: item-feature-lookup (12 ms)
├── Span: inference-service (45 ms)
│   ├── Span: preprocessing (3 ms)
│   ├── Span: model-inference (40 ms)
│   └── Span: postprocessing (2 ms)
└── Span: response-formatting (1 ms)
Total: 66 ms

OpenTelemetry 为分布式追踪提供标准 API。Jaeger、Zipkin 或云追踪服务等后端系统存储和可视化追踪。持久的要求不是特定的追踪后端;而是保留足够的上下文来回答故障是位于请求路由、特征检索、模型执行、后处理,还是模型服务外部的依赖中。

ML 特有调试

当计算在数值上有效但对模型错误时,运行信号是不够的。ML 系统还需要专门的调试能力。

数值调试

数值调试针对张量仍为有效对象但包含无效值的故障。NaN 检测¹¹⁸ 至关重要,因为 NaN 值会无声地传播到所有下游计算。清单 7.3 展示了一个在损坏到达用户前捕获损坏的最小检查。

清单 7.3:NaN 检测:检查模型输出的 NaN 值,防止无声损坏传播给下游消费者。记录输入哈希可在诊断时实现复现。

训练期间,梯度统计在数值故障损害整个运行前使其可见。梯度范数检测爆炸或消失,梯度分布检测异常,逐层梯度识别有问题的层。

混合精度训练¹¹⁹ 可能引入数值问题。监控指示下溢的损失缩放调整、超出 FP16 范围的梯度溢出,以及 FP16 和 FP32 结果间的不一致。

数据调试与验证

数据 Bug 表现为看起来有效但错误的输入,因此调试目标不仅是数值有效性,而是每个管道边界的语义正确性。验证在数据到达模型前检查预期格式、形状、值范围、必填字段和编码,在故障仍局限于输入路径时捕获格式错误的示例。

一旦输入通过这些静态检查,系统需要随时间变化的行为证据。分布监控跟踪特征漂移、空值率变化和异常值的出现,而转换插桩记录中间形状和统计数据,以便团队将每个阶段与已知良好的数据进行比较。这些信号共同将损坏的输入路径与模型内部的数值故障区分开来。

直尾检测与分析

直尾诊断询问哪个组件足够慢以成为等同于故障的瓶颈。同一请求可能在每个副本上都成功,但最慢的副本仍决定尾部延迟,并可能强制重试,在负载下看起来像故障。操作级计时插桩测量每个操作的时间,实现跨管道阶段的延迟归因。清单 7.4 用上下文管理器包装操作,发出逐跨度计时指标,使跨副本比较性能变得简单。

清单 7.4:操作级计时:上下文管理器插桩将延迟归因于单个管道阶段,通过跨副本比较相同跨度实现直尾检测。

使用百分位分析跨副本比较组件计时。p50 显示典型性能,而 p99 显示尾部延迟。跨副本比较 p99 识别出慢速路径已成为车队级瓶颈的工作进程。

诊断必须进一步区分产生相同慢工作进程症状的根本原因。硬件问题包括热节流和内存错误,数据倾斜意味着某些输入处理较慢,资源争用发生在其他进程消耗资源时,网络问题导致到数据存储的连接缓慢。

常见故障模式

可观测性支柱使我们能够检测到经验表明大规模机器学习系统中会出现的反复失败模式。图 7.32 列出了最常见的训练损失签名,每种签名都需要不同的诊断和恢复响应。

训练失败

训练损失签名之所以有用,是因为它将一条曲线映射为一个恢复决策。图 7.32 首先展示了这些曲线;表 7.18 随后使这种操作映射变得显式,将曲线视为特定恢复动作的证据。

图 7.32:训练失败签名:模型损失随时间变化的常见模式,指示底层系统或算法故障。(A) 正常训练。(B) 梯度爆炸。(C) 损失平台期。(D) NaN 发散。自动识别这些签名对于自主训练管道中的快速恢复至关重要。

| 签名 | 可能的解释 | 恢复响应 |

| :--- | :--- | :--- |

| 尖峰后恢复 | 瞬时数据问题或数值不稳定 | 继续训练,但调查该事件以排除系统性输入问题。 |

| 尖峰后平台期 | 学习率过高、检查点损坏或数据 Bug | 回滚到早期检查点,在恢复前验证数据路径。 |

| 逐渐发散 | 静默数据损坏、硬件错误或分布式训练不同步 | 隔离失败的秩,验证检查点完整性,并跨工作节点比较遥测数据。 |

| 无错误挂起 | 集体死锁或崩溃的工作节点阻塞同步 | 使用超时检测并重启或重构工作节点组。 |

表 7.18:训练失败响应映射:训练损失曲线只有在导致不同恢复动作时才是操作证据。响应映射将每个签名与最可能解释它的故障类别,以及操作员应采取的首要动作联系起来。

服务失败

服务失败需要在严格得多的延迟约束下进行相同的症状到动作映射。表 7.19 根据症状揭示的信息和服务系统必须首先采取的动作,对常见症状进行分类。

| 症状 | 可能的解释 | 即时响应 |

| :--- | :--- | :--- |

| 延迟尖峰 | 资源争用、垃圾回收、冷缓存或模型重载 | 检查放置和容量;重复的尖峰通常意味着问题不再是瞬态的。 |

| 错误率上升 | 依赖故障、数据格式变更或模型 Bug | 立即调查,因为错误会跨依赖服务复合传播。 |

| 静默质量下降 | 模型漂移、特征退化或数据管道故障 | 使用超出标准运营指标的质量监控。 |

| 级联故障 | 超时耗尽、资源耗尽或来自某个故障组件的错误传播 | 触发熔断器并在故障扩散前保持隔离边界。 |

表 7.19:服务失败响应映射:服务事故必须快速分类,因为每一次诊断都在与面向用户的延迟预算竞争。该表将每个症状与首要操作响应联系起来,而不是将所有服务故障视为通用错误。

级联故障尤为隐蔽,因为根本原因被其产生的症状所掩盖。一个具体的诊断展示了三大可观测性支柱如何协同工作,将级联故障追溯到其源头。

场景:推荐系统遇到突发延迟尖峰。

诊断:三大可观测性支柱协同工作以诊断根本原因。指标揭示了症状:P99 延迟于 14:32 从 45 ms 跃升至 800 ms,错误率从 0.1% 上升至 15%。追踪隔离了瓶颈:feature-service 跨度耗时 700 ms(正常为 12 ms),而 model-inference 跨度保持正常的 40 ms。日志识别了根本原因:feature-service 日志显示于 14:31 对用户嵌入缓存的连接反复超时,随后出现缓存未命中风暴,请求绕过故障缓存直接命中嵌入数据库。

系统教训:缓存节点故障导致缓存未命中雪崩,压垮嵌入数据库并将延迟传播到所有请求。修复方法是在缓存访问上增加熔断器,当缓存不可用时回退到默认嵌入。

案例研究

已发布的生产系统使本章的规模教训具体化:故障检测、状态保存和可观测性以检查点开销、拓扑重配置、混沌测试和弹性恢复的形式重现。以下案例使用 Meta、Google、Netflix 和 Microsoft 的公开报告来展示代表性的工程模式。请将运营数字视为揭示设计压力的报告量级,而非通用常数。Meta 在 992 GPU 集群上的 OPT-175B 训练运行为我们提供了第一个例子:数十次硬件故障必须在不中止训练的情况下被吸收。

Meta 的大规模 LLM 训练

Meta 对开源预训练 Transformer (OPT-175B) 的训练,为大规模深度学习作业的故障物理学提供了一个有据可查的研究案例 (Zhang et al. 2022)。团队在由 992 块 NVIDIA A100 GPU 组成的集群上连续运行了两个月,面临着统计上的必然性:硬件组件会发生故障,且频繁发生。在整个训练过程中,团队记录了约 90 次由硬件故障(ECC 内存错误、NCCL/InfiniBand 网络问题、GPU 掉线)和训练稳定性事件驱动的手动重启,以及 100 多台主机的轮换 (Meta AI Research 2022)。在同步数据并行模式下,单个 GPU 故障会停止整个集群,使得聚合系统的平均故障间隔时间 (MTBF) 仅为单个组件可靠性的一小部分。对于 OPT-175B,大约每天有两台机器宕机,导致有效的系统级 MTBF 降至不足一天,这迫使容错策略将中断视为常态而非例外。

在 OPT 运行期间,团队通过训练日志新鲜度检查来监控进度:公开日志记录了作为重启监控一部分的 15 分钟修改文件阈值,后放宽至一小时 (Meta AI Research 2022)。检测只是战斗的一半;关键的工程挑战是最小化“重启税”——从持久化存储重新加载模型和优化器状态所损失的时间。项目早期,同步检查点写入远程分布式文件系统消耗了总有效训练时间的近 12%,这一令人望而却步的开销将项目时间线延长了数周。为了应对这一问题,团队实现了异步检查点,先将 350 GB 模型状态的序列化卸载到 CPU 内存,然后在后台流式传输到磁盘,同时立即恢复下一批次的计算。这一优化将检查点开销降低到了 3% 以下,挽回了数百 GPU 小时。

非硬件故障的恢复程序

恢复程序还必须考虑到非硬件故障,特别是由数值不稳定性引起的“损失尖峰”。与上一检查点有效的硬件崩溃不同,损失尖峰意味着模型权重轨迹已损坏。恢复策略将“回滚至最后一个良好检查点”与优化器干预相结合:当观察到梯度溢出或损失缩放触底时,团队会回退到更早的检查点,并以降低的学习率恢复训练(最终运行速率约为 OpenAI 用于 GPT-3 的三分之二)以稳定轨迹(Meta AI Research 2022)。这种双层弹性——同时处理物理硅片故障和数学发散——使 Meta 能够在每天中断的情况下,维持每个 A100 约 147 TFLOP/s 的吞吐量(接近 FP16 峰值的 50%)(Zhang et al. 2022)。OPT-175B 的持久教训是,在大规模下,检查点频率与训练吞吐量之间的权衡是决定模型可行性的核心变量;检查点过于频繁会浪费算力,而过于稀疏则冒着因单次位翻转而丢失数天进度的风险。

Google TPU Pod 弹性

Google 的 TPUv4 超级计算机采用了一种根本不同的架构方法来实现容错,这由定制的芯片间互连(ICI)结构和一个可动态重构拓扑的光电路交换机(OCS)网络驱动。一个 TPUv4 Pod 包含 4,096 个芯片,组织为 64 个“立方体”,每个立方体 64 个芯片,每个立方体排列成 4 × 4 × 4 的 3D 网格(Zu et al. 2024)。在这种架构中,网络实际上就是计算机:单个芯片或光链路故障不仅不会仅使容量减少 1/4096——它会在通信拓扑中制造一个“洞”,导致集体操作(如 AllReduce)所需的同步网格发生死锁。

因此,Google 的策略侧重于切片抽象,而非原地修复。OCS 网络将多个健康的立方体组合成逻辑“切片”,其大小根据训练作业的需求调整。当通过两个软件组件 libtpunethealthd 检测到故障时——它们监控链路完整性和机器健康状况——编排系统不会尝试在活动切片内绕过故障。相反,控制平面会抢占作业并重新配置 OCS 结构,将工作负载连接到 Pod 中其他地方的备用健康立方体。这将硬件视为作业执行期间不可变的基础设施:系统不是修复运行中的拓扑,而是从幸存的健康组件中重组一个形状相同的拓扑。

这种方法的定量成功体现在系统可用性上。在 Google 的生产 TPUv4 集群中,动态重配置策略实现了 99.98% 的系统可用性,同时妥善处理了约 1% 训练作业遇到的硬件中断(Zu et al. 2024)。从相对角度看,每日组件故障率很低——TPU 机器 0.08%,ICI 电缆 0.005%,OCS 单元 0.04%——但在舰队规模下,它们汇聚成一股持续的重配置事件流,控制平面必须吸收这些事件而不中断训练吞吐量。TPU 经验的关键教训是,对于紧密耦合、高带宽系统,试图修复运行中的拓扑往往是徒劳的;将立方体视为故障的原子单元,并重新配置光结构以围绕幸存的健康组件组装新的切片,效率更高。

Netflix 面向服务依赖的混沌工程

虽然训练弹性侧重于长时间运行的批处理作业,但 Netflix 发布的关于混沌工程的工作(Basiri et al. 2016)提供了服务端的对比。前提简单但对运营要求苛刻:由于生产系统具有许多相互作用的依赖项,团队应通过主动注入故障来验证稳态行为,而不是等待真实事件发生以发现降级方案是否有效。

对于 ML 服务,同样的原则适用于特征存储、模型服务依赖项、缓存和降级路径。模型端点可能是健康的,而特征服务却缓慢、过时或不可用;如果系统缺乏经过测试的降级方案,局部依赖问题可能变为用户可见的延迟或降级推荐。混沌测试通过在受控条件下注入延迟、终止依赖项或强制降级路径,使这些假设变得可执行。

这些注入故障的恢复程序以降级层级为中心。如果主要深度学习模型失败或超时,服务系统可以切换到更轻量的模型、缓存响应或简单的基于流行度的默认值。系统层面的教训是,ML 服务中的弹性不是静态属性,而是主动验证的持续实践:从未被触发的降级路径只是文档,而非容错能力。

Microsoft DeepSpeed 容错

Microsoft 的 DeepSpeed 库阐释了一个更窄的框架层面教训:大模型容错必须协调检查点、分片优化器状态和资源管理,而不能依赖单一的重启脚本。DeepSpeed 为拥有超过 1000 亿参数的模型提供了基于 ZeRO 的分布式训练和检查点构建模块(Rasley et al. 2020;DeepSpeed Developers 2026a)。采用零冗余优化器(ZeRO),模型参数、梯度和优化器状态分片分布在所有可用 GPU 上,而非复制(Rajbhandari et al. 2020)。因此,单个节点故障会导致其持有的状态分片搁置,因此恢复依赖于持久化检查点,以及知晓如何恢复或重新分发这些分片的编排。

支持的恢复模型以检查点为中心,而非暗示在每次节点丢失后进行内存修复。每个秩将其状态分片作为分布式检查点的一部分写入,故障发生后,作业在启动器和资源管理器选定的工作拓扑下重新加载最后一个持久检查点。通用检查点通过使检查点在选定的并行度和拓扑变化中更具可移植性来扩展该模型,但仍依赖显式的保存/加载规范(DeepSpeed Developers 2026b)。如果存在替代容量,作业可按原规模恢复;如果周围的弹性启动器以更少的工作进程恢复,训练脚本仍必须保持数据覆盖率和批量大小或学习率不变量。

因此,系统层面的教训不是 DeepSpeed 单独就能使工作进程丢失透明化。而是分片训练框架必须暴露检查点和状态管理原语,以便调度器将其组合成弹性恢复策略。将这些原语构建到框架层可减少定制恢复工程,但端到端保障仍来自框架支持、检查点规范和调度器集成的组合。

通用原则

这些案例研究揭示了三条通用原则:

  • 检测速度决定恢复成本:Meta 的日志新鲜度监控、Google 的自动化 ICI 重配置和 Netflix 的混沌实验都表明,更快的检测意味着更少的状态丢失。

  • 故障的原子单元很重要:Google 重新路由通信结构,DeepSpeed 通过检查点恢复分片,Netflix 验证服务级降级。每个组织都选择了与其架构耦合度相匹配的粒度。

容错是一个谱系,而非二元选择

从 Meta 的检查点回滚到 Netflix 的优雅降级层级,系统实现了多层防御,每一层都在用保真度换取速度。

尽管存在这些模式,工程团队在设计弹性 ML 系统时,仍经常因常见误区而跌倒。

误区与陷阱

基础设施团队可能花费数周时间加固存储层以抵御磁盘故障,却因 PyTorch 分布式后端中微妙的软件 Bug 导致整个训练运行被毁。分布式 ML 系统的容错涉及反直觉的数学和微妙的权衡,传统数据中心的经验往往在此失效。

误区:硬件故障是主要关注点

这种直觉源于传统系统,其中磁盘故障、停电和网络分区占主导地位。在 ML 系统中,硬件故障只是更广泛故障组合的一部分。

大规模 ML 系统的行业经验指向更广泛的故障组合,因为 ML 作业具有长寿命、有状态,且对微小的控制平面错误敏感。硬件故障仍然重要,但软件 Bug、配置错误、资源耗尽和跨层原因往往主导事故数量:格式错误的检查点路径会使恢复变得不可能,不匹配的集合通信库会挂起所有秩,过小的共享内存限制会崩溃数据加载器,陈旧的特征模式会静默地破坏训练。这些故障并非因为是“软件”问题就不那么严重;它们能摧毁硬件冗余原本旨在保护的多日作业。

在忽视软件健壮性(输入验证、渐进式发布、配置管理)的同时大量投资硬件冗余,会使许多重要的故障模式得不到解决。最可靠的 ML 系统将软件 Bug 视为不可避免,并进行防御性设计。

陷阱:凭直觉设置检查点间隔

组织通常根据“感觉对劲”来设置检查点间隔:“每小时似乎合理”或“每 1000 步”。Young-Daly 公式揭示了这些直觉往往是错误的。对于一个拥有 1000 个 GPU、MTBF(平均故障间隔时间)为 4 小时、检查点耗时 5 分钟的集群:

\[\\tau\_{\\text{opt}} = \\sqrt{2 \\times 5 \\times 240} = \\sqrt{2400} \\approx 49 \\text{ 分钟} \]

直觉上的“每小时”虽接近但非最优。如果检查点时间增加到 15 分钟(模型更大、存储更慢),最优间隔变为 85 分钟,而非某些团队为“求稳”而采用的“每 15 分钟”。过于频繁的检查点浪费的算力比节省的更多。定量方法揭示,基于直觉的间隔往往在任一方向上偏离最优值 2–3 倍。

误区:若每个 GPU 可靠性为 99.99%,则 10,000 GPU 集群也为 99.99% 可靠

可靠性不通过平均组合,而通过乘法复合。对于 N 个串行组件,每个可用性为 A,且假设其故障相互独立,聚合可用性为 A^(N)。当 A = 0.9999 且 N = 10, 000 时,集群可用性为 0.9999^(10, 000) ≈ 0.37:集群有 63% 的时间处于宕机状态。单个组件的可靠性是必要的,但在车队规模下远非充分。系统级容错(检查点、弹性恢复、跨故障域冗余)必须显式设计,而非假设能从单组件 MTTF 中涌现。

陷阱:假装故障独立地使用 MTBF 计算

可靠性方程 MTBF[system] = MTBF[component] / N 假设组件故障在统计上相互独立。在生产环境中,故障会关联:

  • 共享电源域:UPS 故障会拉垮整个机架。

  • 共享交换机:顶层交换机故障会分区所有连接的 GPU。

  • 共享软件:特定输入触发的 Bug 会同时使所有副本失败。

  • 热关联:制冷故障导致 GPU 成簇降频。

关联故障改变了简单 MTBF 方程隐藏的两个量:事故频率和爆炸半径。拥有 1000 个独立 GPU、每个 MTBF 为 50,000 小时的集群,其首个 GPU 故障的 MTBF 为 50 小时。如果共享电源、制冷或软件隐患增加的事故率是独立首次故障率的 10 倍,有效事故 MTBF 将降至约 5 小时。此外,关联事故可能一次性拉垮 10 个 GPU,即使独立 GPU 故障率未变,也会增加恢复成本。可靠性工程必须通过多样性识别并缓解关联:不同的电源馈线、不同的网络路径、金丝雀部署中的不同软件版本。

误区:组件 MTTF 值可预测单个故障时机

工程师读到 GPU MTTF 为 50,000 小时,便按每设备五年更换周期规划。MTTF 是总体的统计属性,而非对任意单个组件的预测。MTTF 为 50,000 小时的 GPU 可能在 200 小时时故障,也可能运行 100,000 小时;分布很宽。MTTF 可靠预测的是聚合故障率:在 10,000 GPU 的车队中,稳态故障率约为 10, 000/50, 000 = 0.2 次/小时,即约每 5 小时一次故障。车队规模的可靠性工程依赖这种统计规律来规划自动化恢复、备件池深度和值班轮次。试图预测下一个会故障的是哪个具体 GPU 属于范畴错误,也是监控精力的浪费。

陷阱:检查点规划中忽略重启开销

Young-Daly 公式考虑了检查点保存时间,但实践者常忘记重启开销:

  • 作业调度延迟:在共享集群中获取替代 GPU 需要数分钟。

  • 检查点加载:从存储读取分布式检查点。

  • 预热时间:学习率预热、批归一化统计重新计算。

  • 通信重建NCCL 环拓扑重建。

总重启时间可达检查点保存时间的 3–5 倍。5 分钟的检查点保存加上 20 分钟的重启,意味着每次故障在扣除上次检查点以来的丢失工作前就已耗费 25 分钟。该恢复项应计入预期浪费和恢复 SLO 预算;除非特定的恢复感知检查点模型推导出该依赖,否则不应将其加到 T[write] 中,仿佛每个检查点都要支付该成本。

误区:每次故障都应作为完全重启处理

工程师常将所有故障视为“节点崩溃,从检查点重启”,但不同故障模式需要不同响应。瞬态故障(网络拥塞、热节流)应触发重试/暂停,而非重启,因为状态仍在内存中。永久故障(GPU 死亡、节点崩溃)需通过检查点重启并迁移到新硬件。静默损坏(位翻转、ECC 错误)要求回滚到更早的检查点,而非仅最新检查点,这要求保留检查点历史。资源耗尽(OOM、内存碎片)需在重启前重新配置;否则作业会立即再次崩溃。正如 7.1.5 节 所述,误判故障类型会浪费算力:将瞬态网络抖动视为永久故障会浪费数小时重新初始化,而忽略静默损坏会使模型权重在未被检测到的情况下中毒。

陷阱:假设检查点一致且未验证状态

现代框架透明地进行检查点,造成了自动一致的假象。实践中,分布式检查点需要协调,这可能微妙地失败:

故障容忍的谬误与陷阱

静默数据损坏并非微不足道

谬误静默数据损坏(SDC)可以忽略不计,因为硬件具备 ECC。

GPU 和内存系统包含广泛的错误纠正机制(ECC、CRC、校验位),这造成了一种直觉,即静默数据损坏微不足道。然而,大规模 CPU SDC 和内存错误研究表明并非如此:静默损坏和内存故障在舰队规模下仍会发生,并可能逃脱常规错误报告(Dixit et al. 2021;Sridharan et al. 2015)。对于一个拥有 10,000 张 GPU 的集群,即使是罕见的单设备损坏事件,由于系统持续执行海量内存和算术运算,也会变得具有操作相关性。静默损坏会导致神秘的训练异常:归因于“坏批次”的损失峰值可能是硬件错误,归因于学习率的梯度 NaN 可能是位翻转,尽管超参数正确但模型无法收敛可能是权重损坏。检测策略包括冗余计算(在多个工作节点上计算批次并比较)、梯度校验和(验证 AllReduce 一致性)以及梯度/激活分布的统计监控。与可检测故障不同,静默损坏不会触发错误;训练“成功”了,但产生了微妙损坏的模型,需要如第 7.11.3 节所述的检测机制。

陷阱仅依赖硬件 ECC 而非端到端验证。

ECC 是必要的一层,但它不是训练运行端到端正确性的证明。管道还需要数据分片的校验和、检查点分片的验证、梯度和激活异常检测,以及可重放的测试,以区分坏批次和损坏状态。端到端验证使静默损坏问题在 ML 系统边界变得可观测,否则损害只会表现为无法解释的训练行为。

检查点一致性风险

生产系统必须实施检查点验证:验证所有分片是否存在、验证迭代编号是否匹配、验证优化器状态是否与模型状态匹配。在故障恢复期间发现检查点损坏的组织,除了从更早(可能早得多)的检查点重启外,别无选择。

  • 秩不同步:若秩 0 在迭代 1000 打检查点,而秩 1 在迭代 1001 打检查点,则检查点不一致。

  • 部分写入:检查点过程中存储故障会留下不完整的分片。

  • 优化器状态滞后:如果在不同时间捕获,分片优化器状态可能与模型权重不匹配。

  • 在飞梯度:检查点期间进行中的 AllReduce 可能被包含,也可能不被包含。

故障容忍测试谬误

谬误只有在故障发生时才能测试故障容忍能力。

故障容忍机制是正常运行中很少执行的代码路径。就像灾难发生前从未测试的备份系统一样,故障容忍代码路径会积累 Bug:

  • 因训练从未崩溃,检查点恢复逻辑未测试

  • 因主模型从未故障,备用模型从未加载

  • 熔断器阈值针对旧流量模式调优

混沌工程(故意注入故障)将故障容忍从“我们认为它行”转变为“我们知道它行”。定期在训练中随机终止 GPU、注入网络分区、故障主模型的组织,会在 Bug 造成影响前发现它们。定期故障注入的成本(一些失败的实验、一些小故障)远低于在实际故障期间发现故障容忍机制损坏的成本。

弹性训练非检查点替代品

陷阱将弹性训练作为检查点的替代品。

弹性训练在工作节点故障时调整并行度,以减容继续,这似乎消除了检查点重启开销。然而,状态一致性挑战依然存在:减少活跃工作节点数要求一致地重新分发模型分片、优化器状态和数据分配。低于某个最小可行规模时,训练变得不可行(模型装不下、批次太小),无论如何都需要检查点重启。每移除一个工作节点都会降低吞吐量;累积故障逐步降低训练速度,直到检查点重启比持续降级更可取。如果故障是由特定数据触发的软件 Bug 引起,该 Bug 会持续存在于剩余工作节点中。弹性训练是检查点的补充,而非替代;降低的检查点频率仍需偶尔检查点以应对灾难性故障和训练完成。

开销预算非固定比例

谬误开销预算是训练时间的固定比例。

参考表中显示的“流水线气泡:10%,检查点开销:5%,故障恢复:3%”易被读作物理定律并作为产能计划中的行项目。它们是工程目标,而非常数。流水线气泡开销取决于微批次数量和交错调度;有弹性训练避免全量重启时,故障恢复开销大幅下降;检查点开销是模型大小、存储带宽、检查点是同步还是异步的函数。将这些数字视为固定百分比会导致被动接受可避免的低效。Young-Daly 公式已表明检查点节奏是一项决策;参考表中列出的每一项开销亦是如此。

忽略通信与恢复开销扩容

陷阱增加 GPU 时未核算通信和恢复开销。

产能计划通常核算 Amdahl 定律和 AllReduce 开销,但将可靠性视为固定背景条件。在极端规模下,每增加一张 GPU 都会增加聚合故障率,从而膨胀每个作业的预期恢复和重放时间。存在一个集群规模,超过它则因故障和恢复损失的时间超过增加并行性节省的时间,导致壁钟训练时间随 N 增加而上升而非下降。这是可靠性版的边际效益递减,它在 Amdahl 上限之上生效。必须将两者联合建模:有效吞吐 = MFU × 扩展效率 × (1 - 故障开销),其中故障开销项随 N 增长。

总结

故障容忍将硬件故障的统计必然性从毁灭项目的灾难转化为可管理的操作常规。数学无情:单个组件的可靠性在数千设备间复合,将系统级 MTBF 从年级压缩至小时级。在大规模舰队下,故障不是待调试的异常事件,而是系统必须在不丢失前向进度的情况下吸收的持续背景条件。因此,工程挑战不是预防故障,而是构建恢复自动、快速、且对训练或服务工作负载不可见的系统。

检查点提供了跨故障保留训练进度的基础机制。同步检查点虽然简单,但会带来随模型规模扩展的 I/O 开销,而异步方法则以额外的一致性复杂性为代价,将检查点写入与计算重叠。Young-Daly 公式 \(\tau_{\text{opt}} = \sqrt{2 \times T_{\text{write}} \times \text{MTBF}_{\text{system}}}\) 为工程师提供了一种原则性的方式,在检查点频率与开销之间取得平衡;视检查点大小和集群 MTBF 而定,最优间隔可从小模型的数秒,范围延伸至大作业的数十分钟。除基本检查点外,弹性训练打破了“工作进程数量必须保持固定”这一僵化假设:当节点故障时,系统会跨存活工作进程重新分发数据和模型分片,调整批大小和学习率,以降低吞吐量的方式恢复训练,而非完全停止。

服务容错呈现出与训练截然不同的挑战。训练能容忍数分钟的恢复延迟,并得益于 SGD 对近似重启的数学容忍度;而服务则要求毫秒级的响应能力,且必须保留会话级状态,如 KV 缓存和对话历史。无状态服务通过简单的副本冗余和负载均衡实现容错,但 LLM 的有状态服务则需要主动状态复制、会话亲和路由,以及优雅降级层级——当主系统不可用时回退到更轻量的模型。本章探讨的案例研究——从 Meta 因硬件故障和训练稳定性事故导致 OPT-175B 训练经历约 90 次重启,到 Netflix 面向 ML 服务的混沌工程——表明这些原则并非理论构想,而是生产规模下的运营必需。

内化这些原则的工程师,将获得一个诊断框架,可用于推理任意规模下的韧性。当训练运行停滞时,他们能立即评估瓶颈是检查点 I/O 开销、检测速度不足,还是弹性训练无法吸收的故障模式。当服务系统丢弃请求时,他们能沿冗余层级追溯故障,判断根因是副本健康、状态复制滞后,还是回退策略不足。这种系统性推理,将把容错视为事后诸葛亮的组织,与将其作为一等系统属性来设计的组织区分开来。随着 ML 系统规模扩大,非计划停机的成本也随之成比例增长:一个 10,000 GPU 集群闲置一小时意味着数万美元的算力浪费,使得本章开发的技术成为经济必需,而非可选的精进。

  • 规模使故障常态化:在本章的组件故障率假设下,一个 10,000 GPU 集群每几小时就会遭遇硬件故障;软件必须将故障视为正常状态。

  • 检查点是基线:同步检查点简单但开销高昂;异步方法以一致性复杂性为代价隐藏了 I/O 延迟。

  • Young-Daly 公式决定检查点间隔:最优检查点间隔 \(\tau_{\text{opt}} = \sqrt{2 \times T_{\text{write}} \times \text{MTBF}_{\text{system}}}\) 在保存状态的成本与丢失工作的成本之间取得平衡,对于大型训练作业通常落在数十分钟范围,而对于小检查点则短得多。

  • 弹性实现持久性:设计训练作业为“弹性”(如图 7.28 所示,围绕丢失节点重新配置),只要训练脚本保留必要的数据、批大小、学习率和检查点不变量,就能在节点丢失时减少空闲时间和工作损失。

  • 有状态服务暴露了服务侧状态问题:对 LLM 而言,KV 缓存代表了巨大的服务状态,必须复制或迁移以防止高延迟的会话重启,策略权衡总结于表 7.13。

  • 训练与服务需要不同策略:训练容错以检查点为中心,可容忍数分钟恢复;服务容错以状态迁移为中心,要求亚秒级故障转移。

  • 生产规模验证了理论:本章的训练和服务案例研究表明,大型系统只有在容错被持续演练而非被假设时,才能保持可靠。

如果无法预防故障,唯一的出路就是降低其代价,这正是将韧性从愿望转化为计算的关键。检查点在用户从未要求的事情上消耗算力和通信,仅为了节点故障那一天而持有状态副本,而 Young-Daly 间隔正是这种支出的算术:不是如何避免丢失工作,而是两次保存之间丢失多少工作是可负担的。设置得当时,节点故障只会耗费分钟而非天数。故障到达的频率完全一样;改变的是它们不再决定结局。这是显性化的协调税,有目的地支付它,以便在舰队规模下——组件故障是统计确定性的地方——确定性不再重要。

尽管硬件故障不可避免,要让舰队持续运行需要三个层面:检查点保留进度,弹性吸收节点损失,冗余保护有状态服务会话。然而,容错集群仍需编排,以决定哪些作业运行在何处、何时抢占、如何在竞争工作负载间公平共享资源。第 8 章将探讨编排层——从 Slurm 和 Kubernetes 调度到帮派调度、多租户和容量规划——如何将一组容错节点转化为托管的生产系统。

此处用于确保测验在部分开始前正确插入。

  • 检查点节奏应如何综合考量 MTBF、检查点写入时间、重启开销和验证成本?

  • 并行度和集合通信选择如何改变单个节点、链路或秩故障的影响范围?

  • 何时弹性恢复优于完全重启,且必须保留哪些训练不变量?

  • 何种端到端证据能在静默数据损坏表现为模型行为退化前将其检测出来?

Fleet Orchestration

调度器控制平面蓝图,其中排队作业被放置到拓扑感知的加速器容量上,具备抢占、恢复和策略约束。

Purpose

为何当硬件充足时,资源分配反而成为主要瓶颈?

调度问题

一千个加速器因等待调度决策而闲置,其成本与一千个加速器执行有用计算的成本相同。在大规模场景下,限制因素从“拥有足够硬件”转变为“高效利用硬件”:作业在队列中等待而资源却闲置,碎片化留下的空隙太小无法容纳任何待运行作业,多个作业各自持有部分资源却等待更多资源从而导致死锁。编排是从共享基础设施中榨取有用工作的学科,它决定哪些作业在哪些节点上运行、何时抢占服务于整体利益、如何在团队间的公平性与原始利用率之间取得平衡、以及如何防止协调机制本身成为瓶颈。糟糕的编排将昂贵的硬件变为昂贵的浪费;有效的编排将一堆机器变为一个连贯的计算资源,使产能可靠地转化为已完成的工作。用 C³ 术语来说,编排是有目的地支付的协调税:理论峰值利用率被牺牲,以便舰队的共享资源在负载下不会死锁或碎片化。

  • 分析共享集群约束下分布式训练作业的调度目标、碎片化与死锁风险

  • 对比面向批量训练与自愈服务工作负载的 HPC、云原生及混合编排范式

  • 在 NVLink 域与交换机层级间应用帮派调度、装箱策略与拓扑感知放置策略

  • 设计弹性训练与抢占策略,在吞吐量、恢复开销与抢占式实例节省之间权衡

  • 使用作业时长、迭代进度、公平性与自适应资源分配信号评估 ML 专用调度器

  • 利用队列深度、尾延迟与 GPU 显存压力设计推理自动扩缩容、隔离与路由策略

  • 诊断因配额囤积、吵闹邻居、碎片化、拓扑不匹配与数据饥饿导致的利用率损失

设想两个研究团队共享一个 1,000 GPU 的集群:团队 A 提交一个运行一个月的 512 GPU 作业,而团队 B 提交数百个每个运行一小时的 8 GPU 实验。如果调度器仅按到达顺序处理作业,团队 B 的微小实验可能会无限期地阻塞团队 A 的大规模训练运行。调度问题就是在吞吐量、公平性与集群利用率这多维冲突中寻找出路的挑战。

将计算进行分区、高效同步以及从硬件故障中恢复,解决了在多机器上运行单个作业的问题。编排解决了下一个问题:决定运行哪些作业、在哪里运行、以及如何在竞争性需求间共享资源。

在 图 1.13 所示的舰队栈中,编排运行在分发层,但其决策从根本上受限于基础设施层。调度器必须推理物理现实:GPU 异构性、NVLink 拓扑、机架功率限制与网络分割带宽。当调度出错时,调试需要检查所有四层以确定根因是基础设施(硬件约束)、分发(算法局限)、服务(负载不匹配)还是治理(策略配置错误)。编排是物理基础设施与分布式算法需求的交汇点,将资源请求转化为同时尊重硬件拓扑与组织策略的放置决策。

为使调度问题具体化,考虑一个运营 10,000 GPU 集群的研究机构。Brown 等人(2020)报告,在 V100 GPU 集群上训练 GPT-3 175B 使用了 3.14 × 10²³ 次浮点运算。设想有 100 个同规模的大规模训练作业排队,外加数千个较小的实验与推理工作负载,所有这些都在争夺同一资源。系统必须决定运行哪些作业、何时运行、以及在哪里运行。天真的先到先得策略会让前几个大作业垄断集群数周,饿死研究人员需要快速迭代的小型实验。严格的公平份额策略会将 GPU 碎片化分散到许多小分配中,阻止任何大作业组装起其所需的连续分配。两极都行不通。调度器必须在一个多维权衡空间中导航,其中每个决策同时影响吞吐量、公平性、成本与研究人员生产力。

经济利害使这些决策在各规模下都至关重要。一个 10,000 GPU 集群按 $2/GPU-hour 计算,每天运营成本为 $480,000,无论 GPU 是在执行有用工作还是在队列中闲置。若调度低效导致 30% 的 GPU 闲置,这意味着每天浪费 $144,000 产能,约合每年 $5260 万。反之,将利用率从 50% 提升至 80%,无需购买额外硬件即可增加 3,000 个充分利用的 GPU 等效产能。若按旧有的 50% 利用率运行,这相当于购买 6,000 块原始 GPU。在此规模下,利用率每提升 1 个百分点,其年价值超过实现该提升的工程师薪资。调度不仅非运营开销;它是 ML 基础设施中杠杆率最高的工程投资之一。

ML 工作负载呈现的调度挑战,使其区别于普通云服务放置,也区别于可容忍灵活资源数量的 HPC 工作负载。帮派调度¹²⁰ 代表了同步分布式训练最关键的差异:一个需要 1,024 GPU 的作业,无法 仅用 512 GPU 取得有用的迭代进度。许多 HPC 作业也需要协同调度的秩,但同步数据并行训练在每次 AllReduce 都暴露了这一代价。每个工作进程必须参与每一次集合通信,缺失的工作进程会阻塞所有其他进程。D.2.1 节 给出的环形与树形 AllReduce 成本模型使这一约束物理化:集合通信直到最后一个工作进程到达才会继续,因此分配了 N 个 GPU 却只落地 N - 1 个,会导致缺失秩的对等进程在同步屏障上全速空转。部分分配资源的调度器会制造死锁:多个作业各自持有部分 GPU 却等待更多,导致没有任何作业能推进。这种全有或全无的要求意味着,调度器不能像传统装箱器那样简单地“紧凑打包作业”;它必须在整个集群范围内对原子性的多资源分配进行推理。

如果说帮派调度是第一个硬约束,那么通过放置组¹²¹实现的拓扑感知就是第二个:为支持张量并行,调度器必须确保 GPU 放置在同一个高带宽 NVLink 域内,而不是散布在数据中心各处。

GPU 异构性是第三个维度。许多集群包含 A100 与 H100 加速器的混合,它们拥有不同的计算吞吐、可比的 HBM 容量(A100 80 GB 对比 H100 80 GB)以及不同的互联带宽。对于 Transformer 工作负载,H100 提供的训练吞吐大约是 A100 的两倍,但成本也成比例地更高。某些模型配合适当的并行策略可装入 A100 显存;另一些则需要 H100 更大的显存以避免过度模型分片。调度器必须基于实际需求而非用户偏好将工作负载匹配到合适硬件,同时在异构资源池中保持高利用率。当用户出于习惯而非必要请求特定 GPU 类型时,请求资源与所需资源的错配会导致整个硬件资源池闲置,而首选硬件的队列却溢出。

作业时长不可预测性

作业时长的不可预测性进一步加剧了这些挑战。训练运行可能提前收敛,在几天而非几周内完成。硬件故障可能意外中止作业,在不可预测的时间释放资源。超参数搜索可能尽早揭示某些配置注定失败,从而导致自愿终止。传统科学计算工作负载(如天气模拟或分子动力学)具有更可预测的运行时长,从而支持更好的调度决策:调度器可以预测资源何时可用并相应规划。ML 工作负载剥夺了这一优势,迫使调度器在对当前作业何时释放资源存在深度不确定性的情况下运行。

这些挑战之间的相互作用产生了组合复杂性。调度器必须同时处理集团调度约束(原子分配)、异构资源(能力匹配)、拓扑要求(用于张量并行的 NVLink 局部性)、优先级和公平策略(组织公平性)、抢占决策(中断哪些正在运行的作业)以及成本优化(抢占式实例与按需实例的放置)。每个维度都制约着其他维度:拓扑最优的放置可能违反公平策略,成本最优的抢占式实例放置可能违反可靠性要求,而公平性驱动的分配可能以阻碍集团调度的方式碎片化资源。

这些约束定义了编排的核心问题:调度器不仅仅是将作业打包到 GPU 上,而是要维护一个故障频发、异构机群的一致且经济有用的视图。第一步是理解为什么集群调度从根本上比单机进程调度更难。在机群规模下,放置不再仅仅是一个优化挑战,而成为一个分布式系统问题,即在数千台机器间保持状态一致。

分布式调度复杂性

单机调度器享有集群调度器无法拥有的奢侈:它对所有资源状态拥有即时、一致的可见性;它可以做出即时生效的原子决策;且故障是二元的(机器要么开启要么关闭)。集群调度放弃了这三个属性,因此几个基本的分布式系统问题应运而生。

部分故障构成了首个挑战。节点可能在分配与作业启动之间发生故障,造成调度器决策与其生效之间的鸿沟。调度器可能成功地在 4 个节点上分配了 32 个 GPU,却在作业启动前遭遇一个节点故障。剩余的 24 个 GPU 处于空闲状态,而调度器正在检测故障、更新状态并重新规划分配。与此同时,本可使用这 24 个 GPU 的其他作业已被安置在他处,且可能运行在次优硬件上。第 7 章中的容错机制处理执行期间的故障;调度器必须处理放置期间的故障,这是一个截然不同的问题,因为作业尚未建立任何可供恢复的状态。

网络分区创造了分布式调度特有的第二个问题。调度器可能与一部分节点失去连接,而这些节点仍在正常运行。从调度器的视角看,节点似乎故障,其 GPU 显示不可用。从节点的视角看,作业可能仍在运行并产出有用工作。这种模糊性造成了一个无通用正确解法的两难境地。将 GPU 重新分配给新作业,若分区愈合则面临双重分配风险;等待重连则浪费可能完全正常的容量。分区持续时间事先不可知,因此任何固定超时都代表了对网络行为的猜测,且可能被证明是错误的。

状态不一致作为第三个挑战浮现,它使前两个问题复杂化。资源状态在调度器视图与各节点现实之间可能存在差异。调度器认为空闲的 GPU 可能仍在清理上一个作业:刷新缓存、释放设备内存、运行失败容器留下的僵尸进程,或在错误后完成 CUDA 驱动重置。反之,标记为“使用中”的 GPU 可能已被在两次心跳间隔中终止的作业释放。这种不一致意味着调度器运行在一个总是略微过时的集群状态模型上,且过时程度因心跳频率、网络延迟和清理操作复杂度而在各节点间有所不同。

无全局时间的排序呈现了第四个根本问题。没有全局时钟,要判断分配 A 是否发生在分配 B 之前(跨不同节点),需要使用逻辑时钟或共识算法进行精心的协议设计。如果系统不通过共识协议或集中式协调强制执行排序,两个作业都可能认为它们“拥有”同一个 GPU。这并非理论场景:在从网络分区恢复期间,分区前后的分配消息可能以错误顺序到达不同节点,造成必须检测并解决的资源分配冲突。

这四大挑战呼应了 CAP 定理¹²²所述的一致性、可用性、分区容忍性权衡。CAP 是关于分布式数据系统的定理,而非关于每个调度器的字面证明,但这种类比很有用:控制平面必须选择在做出分配决策前愿容忍多少陈旧或不确定的状态。Slurm 风格的集中式批处理控制强调作业启动前的权威分配视图。Kubernetes 风格的协调强调持续将实际集群驱动至声明的期望状态。自定义 ML 调度器常接受有界不一致以换取调度吞吐量,采用乐观并发,即事后检测并解决冲突,而非通过加锁预防。

当调度器在分区期间与 ML 框架自身的分布式协调平面交互时,控制平面的张力变得具体化。调度器可能判定一部分节点不可用,而这些节点上的工作进程仍阻塞在集体操作内部。这创造了一个短暂的意图冲突窗口:调度器想要回收分配,但训练作业尚未就新成员视图达成一致。TorchElastic 等弹性启动和会合机制通过使用心跳和重新会合减少了这种模糊性,使框架能够重组可行的工作进程组或干净地终止,从而给调度器比不透明的集体超时更清晰的恢复信号(PyTorch Contributors 2026b, 2026a)。这种分层协调——调度器层面的粗粒度分配策略,框架层面的工作进程成员管理——是生产级分布式训练集群的典型架构。

大规模故障率

在大规模下,故障是常态运行,而非例外。这一原则已在第 7 章的可靠性分析中确立,对调度有直接后果:调度器不得不只是容忍故障,而是必须在每个分配决策中主动为故障做计划。组件可靠性不随集群规模变化,但聚合系统可靠性呈乘法下降。仅统计 GPU 硬件故障会低估故障率;以 99.9% 的 GPU 年度可靠性(数据中心硬件典型值)计算,一个 4,096 GPU 集群仅从硬件角度预期:

机群故障率从单 GPU 上升至 10,000 GPU。

正常的组件故障率使机群故障成为常态。

\[\text{Expected failures per day} = 4096 \times \frac{0.001}{365} \approx 0.01 \text{ GPU failures/day} \]

此计算仅捕获 GPU 硬件故障。包括软件故障(驱动程序崩溃、CUDA 上下文损坏)、热事件(节流、紧急关闭)、网络接口故障、宿主机操作系统问题和容器运行时错误,大型集群每 1,000 个 GPU 每天可能会出现 1 到 4 次故障。一个 10,000-GPU 的集群因此每天可能会经历 10 到 40 次组件故障。在 4,096 个 GPU 上进行的多周训练运行因此极有可能遇到多次故障。Section E.1.1 列出了支撑 99.9% 年可靠性假设的 FIT 率和 MTTF 数据,使操作员能够替换为实际测量的硬件速率,并验证每天 1 到 4 次故障的经验范围是否仍然成立。

调度影响深远,并塑造了调度器几乎每一个设计决策。调度器不能将集群视为静态资源池,在这种池中,资源一旦分配,就会一直保持可用状态,直至自愿释放。相反,它必须预见到在作业执行过程中分配的资源可能会消失,维持备用容量或实施快速重新调度,以在硬件不断进入和离开操作状态的持续变化中保持作业运行,并区分瞬时故障(可能在几分钟内自行恢复,不应触发昂贵的重新分配)和永久性故障(需要新的资源分配和检查点恢复)(Tiwari et al. 2015)。

考虑调度器在节点心跳丢失时的决策。调度器有三种选择:

  • 等待恢复:调度器等待以查看心跳是否恢复,如果节点确实已死,则有浪费 GPU 时间的风险。

  • 立即重新分配:调度器将节点的资源分配给其他作业,如果节点恢复且其原始作业仍在运行,则有冲突的风险。

  • 标记为可疑:调度器将节点标记为可疑,并在等待确认的同时开始预置替代资源,消耗了本来可用于其他工作的备用容量。

每种选择都在响应性和正确性之间进行权衡,有效选择取决于集群中特定硬件的故障模式分布,调度器必须从历史故障数据中学习此信息。Section E.3.1 将恢复时间分解为检测、重新调度、重新加载和重放阶段,这些阶段限定了调度器在错过心跳时可以等待多长时间才提交重新分配,而不是押注于瞬时自行解决。

这些需求将调度器直接与来自 第 7 章 的检查点和恢复基础设施连接起来:关于备用容量、抢占政策和弹性训练支持的调度决策决定了系统从统计预期故障中恢复的速度。一个保留备用容量或支持弹性恢复的调度器可以比要求每个失败作业等待完整全新分配和重新加载的调度器更快地恢复吞吐量,但正确的备用比例和恢复时间是特定于工作负载和机队的参数,而不是普遍常数。

调度目标及其冲突

每个调度决策都代表着在四个基本且常常相互矛盾的目标之间进行权衡:

  • 吞吐量:单位时间内完成的有用工作总量自然倾向于大型、长时间运行的作业,这些作业能够充分利用硬件并最小化上下文切换和数据移动开销。

  • 公平性:资源在用户或团队之间的公平分配可以防止单个大团队垄断整个机队。主导资源公平性 (Ghodsi et al. 2011) 通过在诸如 GPU、CPU 和内存等资源上使每个用户的最大归一化份额相等来实现公平。

  • 延迟:短作业的快速完成可以保持研究者的速度,但激进的抢占可能导致大型训练作业饥饿。

  • 成本效率:可中断的竞价实例、非高峰调度和紧密打包可以减少预算消耗,但通常会增加故障率或完成时间。

这些目标存在冲突,因为每个目标都奖励不同的调度行为。以吞吐量最大化为目标的政策会将集群填充为大规模训练运行,实现近乎完美的利用率,但迫使其他每个用户等待数周才能获得一个时间槽。以公平性驱动的分配会导致资源碎片化,留下无法被大型作业填补的小缺口,为了社会和谐而牺牲总体吞吐量。类似于最短作业优先的延迟最优调度器会通过激进抢占长时间运行的训练作业来服务交互式调试会话或小实验。成本优化通常意味着接受更高的故障率和更长的完成时间,以预算保存为代价牺牲研究者生产力。

同时优化这四个目标是不可能的;每个调度政策代表这个四维权衡空间中的一个特定点。吞吐量和延迟之间的冲突最为直接,这种紧张关系类似于计算机架构中的吞吐量-延迟权衡。以吞吐量为最优的调度器优先考虑最大且最易并行化的作业,因为它们使用硬件最为高效。然而,这种政策对小作业来说是灾难性的延迟:研究者提交一个 10 分钟的调试任务可能需要等待数天才能等到大型作业完成。相反,以延迟为最优的调度器最小化平均等待时间,但可能导致大型作业无限期饥饿,因为等待“再来一个”小作业完成而导致大量资源闲置,从而减少集群的总体吞吐量。

A wait-time curve that stays flat at low utilization then turns sharply upward toward the right, with a marked knee and a shaded danger zone past it.

过去约 70% 利用率时,队列等待时间会爆炸式增长。

考虑我们 175B 模型的 64-GPU 微调运行,这是一个与完整的 1,024-GPU 预训练运行不同的调度演示工作负载,在机队讨论的其他部分会反复出现。从吞吐量角度看,这是一个理想的作业:启动后它会以高利用率运行数天,且没有调度开销。从延迟角度看,它是流中的一块巨石。为了调度它,系统可能需要清空 64 个 GPU 上的所有其他工作,迫使数百个较小的作业等待。一旦开始运行,它会固定占用这些资源。如果调度器优先考虑此运行(吞吐量),那么小作业的 P99 延迟将爆炸式增长。如果通过允许它们抢占或通过碎片填充集群来优先考虑小作业(延迟),那么这个 64-GPU 作业可能永远无法组装它启动所需的连续资源块。这种零和博弈迫使组织做出明确的政策选择。在调度器术语中,这些选择通常表现为服务质量类别:决定在吞吐量、延迟、公平性和成本发生冲突时哪个目标胜出的作业优先级别。

GPU 集群可以建模为 M/G/1 队列,其中作业到达遵徠泊松过程(λ[job]),服务时间遵循一般分布(G),其平均值为 1/μ,标准差为 σ。波拉克泽克-赫辛公式定义了队列中的预期等待时间(W[q]):

\frac{W\_q}{1/\\mu} = \frac{\\rho}{1-\\rho} \cdot \frac{1+C\_s²}{2}

这里,ρ = λ[job]/μ 表示集群利用率,1/μ 是平均作业持续时间,C[s] = σ**μ 是作业持续时间的变异系数。因此,等式右边表示等待时间作为平均作业持续时间的倍数。

在标准的 Web 服务中,请求服务时间通常可用指数分布来近似,从而得到 C[s] ≈ 1。而在 ML 集群中,作业持续时间遵循重尾分布:大量短暂的调试作业(分钟级)与极少数大规模训练任务(周级)混合。此处说明中代表性的 C[s] = 3 计算捕捉到了这种方差,并不声称存在通用的机群常数。其对等待时间的影响是乘法性的。对于此处的调度论证,单服务器乘数已足够;F.1.1 节将该结果推广至 M/G/c/K 队列,并针对重尾到达场景下调度器准入队列的尺寸规划,通过利特尔定律进行了验证。

在 80% 利用率(ρ = 0.8)下:对于指数工作负载(C[s] = 1),W[q] = 4 × 平均作业时长。对于典型的 ML 工作负载(C[s] = 3),W[q] = 20 × 平均作业时长。这解释了 ML 基础设施中的“利用率墙”。Web 服务器集群在 80% 负载下仍感觉响应灵敏,但在相同利用率下,ML 集群却让人感觉“坏了”,作业在队列中滞留数天。服务分布的重尾充当了延迟乘数,迫使运维人员以降低利用率(通常为 60% 至 70%)运行 ML 集群,以保持交互式用户可接受的响应性。

装箱问题

最基础的调度算法是装箱问题¹²³:将大小不一的作业塞入固定容量的节点中。该问题在一般形式下属于 NP-hard 问题,意味着没有已知算法能在多项式时间内找到最优解。幸运的是,首次适应递减和最佳适应递减等实用启发式算法,对于 ML 集群典型的负载分布(作业大小遵循重尾分布,即大量小作业,极少大作业),能达到近似最优的结果。

设想一个拥有 64 个节点、每节点 8 块 GPU、共 512 块 GPU 的集群。若每个作业请求 6 块 GPU,则每个作业占满一个完整节点,但每个节点浪费 2 块 GPU,导致有效容量降至 75%。剩余的每节点 2 块 GPU 无法跨节点合并,因为 GPU 工作负载需要本地内存访问。随着异构作业尺寸的混合(1-GPU、3-GPU 和 7-GPU 作业混杂),这种碎片化会愈发严重:在许多节点上产生无法容纳任何单个待调度作业的不规则空隙,尽管若这些空隙连续起来,总空闲容量本已足够。

ML 工作负载以传统调度极少遇到的方式,使装箱问题变成了多维问题。每个作业不仅需要 GPU,还需要 CPU 核心用于数据预处理、主机内存用于数据加载和增强管线、本地 SSD 存储用于数据集缓存,以及网络带宽用于梯度同步。一个请求 4 块 GPU、32 个 CPU 核心和 256 GB 内存的作业,可能无法部署在一个拥有 4 块空闲 GPU 但只有 16 个空闲 CPU 核心的节点上,因为其他作业已占用主机 CPU 进行预处理。调度器必须同时满足所有资源维度,只有当所选节点上每个维度都有足够容量时,作业才能放入。与单维装箱相比,这种多维约束极大地缩小了解空间,因为任意单一资源维度的瓶颈都可能导致所有其他维度的容量被搁置。

局部性约束在单纯的资源可用性之外,进一步限制了放置位置。使用张量并行的训练作业,要求 GPU 通过 NVLink 连接在同一节点内,因为跨节点的 InfiniBand 通信慢一个数量级(节点内每向 450 GB/s vs. 节点间每端口 50 GB/s)。一个请求“具有 NVLink 连接的 4 块 GPU”的作业,无法使用来自节点 A 的 2 块 GPU 和节点 B 的 2 块 GPU,即使这 4 块 GPU 单独看来都可用,且总容量也足够。这些拓扑约束将装箱问题转化为放置问题:具体资源的身份与可用资源的数量同样重要。放置问题比装箱问题更难,因为它在现有容量约束之上增加了空间约束。

调度器通过多种互补策略解决碎片化问题,每种策略针对问题的不同方面。回填调度允许较小作业填补空隙,而较大作业等待连续资源,在不违反优先级顺序的前提下提高利用率。其核心洞见在于:小作业可在空隙中执行并完成,在大作业的目标开始时间前释放资源。回填调度需要估算当前作业的完成时间(以判断回填候选作业能否在大作业启动前完成),这正是为何精确的运行时估算如此重要的原因。

定期碎片整理会迁移或抢占低优先级作业,以将空闲资源合并为连续块,这类似于操作系统中的内存紧凑。调度器识别出因部分分配而留下搁置资源的节点,抢占占用这些资源的作业,并将它们重新调度到能更高效打包的节点上。碎片整理的成本(抢占正在运行的工作,浪费上一次检查点与抢占事件之间的算力)必须与其收益(让大作业更早启动,提升整体吞吐量)权衡。运维人员通常在低需求期间安排碎片整理,此时抢占的影响较小。

第三种策略超售,允许准入的作业数量超过严格容量限制,依靠统计复用避免所有资源维度同时达到峰值使用。若每个作业的 GPU 利用率平均为 70%(在计算密集型与数据加载阶段间交替),集群可支持的作业数量约为严格 GPU 计数的 1.4 倍。当资源利用率真正具有突发性且各作业的峰值使用期不相关时,超售效果良好;但当多个作业同时达到峰值时,会导致严重的性能下降(抖动、内存压力、网络拥塞)。安全超售的关键在于实时监控实际利用率,并在检测到争用时限流准入。图 8.1 展示了综合效果:尽管有 19 块 GPU(约占集群 30%)处于空闲,但没有任何单个节点拥有连续的 8 块空闲 GPU,因此全节点作业无法调度。

图 8.1:碎片化问题:热力图展示了一个 8 节点集群(行)的快照,每节点 8 块 GPU(列)。不同颜色代表不同的活跃作业,灰色槽位代表空闲 GPU。尽管全局有 19 块 GPU(约 30% 容量)可用,但它们分散在各节点上。一个需要 8 块连续 GPU(一个完整节点)的新作业无法被调度,因为没有任何一行完全为空,这说明了装箱问题的低效如何降低了集群的有效容量。

团调度

多 GPU 作业的部分分配会产生一种 ML 集群特有的故障模式:两个作业各自持有所需资源的一半,互相阻塞且无限期等待,而每块被分配的 GPU 都处于空闲状态。图 8.2 对比了这种死锁场景与消除死锁的原子分配方法。

图 8.2:分布式死锁(持有并等待)

在非帮派调度器(左侧)中,作业 J1 获取了半个集群并等待其余部分,而作业 J2 获取了另一半并等待 J1 释放其 GPU。两者均无法继续,100% 的集群处于闲置状态。帮派调度(右侧)通过强制原子分配解决了这一问题:要么同时授予作业其全部 N 个 GPU 的需求,要么作业在队列中等待而不持有任何资源。

帮派调度

帮派调度 是一种 ML 集群调度策略,它以原子方式(要么全给,要么全不给)分配作业的所有加速器,因为同步分布式训练使得部分分配的边际价值为零:每个工作进程都必须到达每一个集合通信操作,因此作业无法用更少的 GPU 来换取成比例的较慢进度。

  1. 重要性:一个 1,024-GPU 的同步作业如果持有 512 个 GPU,完成的训练步数为零,而非减半:每个 AllReduce 都会停滞,直到所有秩到达,因此被持有的 GPU 在 0% 有效产出的情况下消耗电力和资本。若无原子分配,两个此类作业各自可获取半个集群并陷入死锁,双方进度均为零,却消耗着整个集群的运营成本。

  2. 区别:这不同于云服务部署(获取一半请求副本的服务大约能服务一半的流量),也不同于灵活的 HPC 工作负载(随秩数减少而优雅降级)。同步训练具有阶跃函数效用:要么全额分配,要么一无所有,这迫使调度器必须针对原子多资源分配进行推理,而非增量装箱。

  3. 常见陷阱:一个常见的误解是仅靠优先级或配额就能解决此问题。原子性必须延伸至作业取得进展所需的每一种共调度资源(加速器、高带宽互联域、用于集合通信的网络容量);仅在 GPU 粒度上强制执行会在资源层级下一层重现死锁。

问题

一个 1024-GPU 的集群收到来自不同团队的 2 个大型训练作业,每个都需要完整的集群。一个天真的非帮派调度器向团队 A 分配 512 个 GPU,向团队 B 分配 512 个 GPU,然后等待更多 GPU 变为可用。稳态结果会是什么?

分析

  1. 团队 A 状态:持有 512 个 GPU,需要 1024 个 GPU。进度 = 0%。

  2. 团队 B 状态:持有 512 个 GPU,需要 1024 个 GPU。进度 = 0%。

  3. 集群状态:1024 个 GPU 已分配,0 个样本被处理。

  4. 浪费:每小时 2048 美元的空闲电力和资本成本。

系统洞察

这是一个循环依赖:团队 A 在等待团队 B 的 GPU,团队 B 在等待团队 A 的。没有帮派调度(全有或全无分配),集群效率降为零。在大规模机群中,即使是 5 分钟的死锁也可能造成数千美元的损失。批处理调度器和 Kubernetes 批处理扩展通过强制执行作业级准入边界来解决此问题:如果完整的帮派无法启动,作业应保持排队状态,而不应持有部分分配。

图 8.2 中的部分分配模式是一种持有并等待死锁¹²⁴,其中两个作业各自持有半个集群,且双方均无法继续。实现保证很简单:对于请求 N 个 GPU 的作业 J,调度器必须保证要么原子地分配所有 N 个 GPU,要么作业留在队列中而不持有任何资源。这种二元结果通过构造消除了死锁,因为不持有资源的作业无法阻塞其他作业。然而,高效实现这一保证才是定义集群调度算法设计大部分内容的挑战。

天真的帮派调度以可预测的方式造成浪费。如果集群有 900 个空闲 GPU,而一个作业请求 1,024 个,这 900 个 GPU 将闲置直到再有 124 个变为可用,可能浪费数千 GPU 小时。 回填调度 通过识别队列中能在 900 个可用 GPU 范围内运行且不延迟大作业预期开始时间的作业来解决此问题,这一想法在 ANL/IBM SP 调度系统中普及(Lifka 1995)。一个预估运行时间为 2 小时的 128-GPU 作业可以安全回填,如果大作业所需的额外 124 个 GPU 无论如何将在 2 小时内(来自其他完成的作业)变为可用。回填作业利用了原本会闲置的资源,在不违反大作业调度保证的前提下提高了利用率。

运行时间估计的准确性决定了回填的有效性,这引入了一个博弈论挑战。如果用户持续高估运行时间,回填时隙会人为变窄,能放入的作业变少,降低利用率。如果用户低估,回填作业可能在大作业资源可用时仍在运行,迫使抢占从而浪费回填作业的进度。实践中,用户有强烈动机高估(以避免完成前被终止)而缺乏准确估计的动力,导致系统性膨胀降低回填性能。像 Tiresias 这样的研究调度器(Gu et al. 2019)通过完全消除对运行时间估计的需求来解决这一根本问题,转而使用观测到的资源消耗来动态调整优先级。这一详细讨论于 8.6.1 节 的方法,将运行时间估计问题从面向用户的负担转变为系统级的观测。

一个快速的成本估算显示,为何即使在该平衡上的微小改进也值得投入工程精力。

8.1 节 引入的集群经济设定了赌注;帮派调度决策是调度器首次赚取或烧掉它的地方。以相同的 10,000-GPU 集群为例:

  • 运营成本:480,000 美元/天(1.752 亿美元/年)

  • 较低利用率(60%):6,000 个生产,4,000 个闲置 = 192,000 美元/天浪费

  • 较高利用率(80%):8,000 个生产,2,000 个闲置 = 96,000 美元/天浪费

  • 改进价值:从 60% 提升至 80% 每天节省 96,000 美元,约合 3,500 万美元/年

帮派调度所拥有的增量是它防止的死锁:一次持有并等待死锁可能使整个集群搁浅,在一次事件中抹去超过一天的这种利用率收益。

帮派调度从利用率示例中移除了持有并等待死锁,但另一种等待模式依然存在:优先级反转仍可能导致预留 GPU 困在无法完成自身退出路径(检查点)的作业之后。

优先级反转等待草图:高优先级作业等待持有 GPU 的低优先级作业,而中优先级作业饿死了低优先级作业的检查点退出路径。

优先级反转是一个等待链,而非根因故障树。

死锁预防与检测

帮派调度消除了四个正式的 Coffman 死锁条件之一——持有并等待条件,但它并不能使集群对所有形式的资源争用免疫。死锁仍可能源于优先级规则、抢占策略和辅助资源依赖的交互。在拥有数千个 GPU 和 PB 级状态的集群中,这些边缘情况从理论上的奇观转变为日常运维事故。

最有害的是优先级反转,这是一个源自实时系统的场景,高优先级作业被低优先级作业无限期阻塞。考虑一个 175B 参数的训练任务(高优先级)请求 64 个 GPU 的集团(gang)。调度器已预留 60 个可用 GPU,但剩余的 4 个被一个低优先级的数据处理作业占用。通常,调度器会抢占低优先级作业以满足高优先级请求。然而,如果一系列中优先级的开发作业耗尽了集群的 CPU 或网络带宽,导致低优先级作业缺乏执行检查点和退出所需的资源,低优先级作业就会停滞。它无法释放 GPU,因为无法完成退出序列。高优先级作业等待低优先级作业,而低优先级作业实际上被中优先级作业阻塞。结果是 60 个 GPU 的空闲块持续存在,直到运维人员手动干预。

优先级反转是 ML 集群调度中的一种病理现象,高优先级任务被迫等待低优先级任务释放共享资源。

  1. 重要性:它将整个集群的进度速率降低到最低优先级作业的水平。在 ML 集群中,这通常发生在持有 GPU 的低优先级作业因缺乏辅助资源(例如用于检查点的带宽)而饥饿,导致其无法完成并释放高优先级工作负载所需的加速器。

  2. 区别:与标准排队(任务轮流等待)不同,优先级反转涉及主动阻塞:高优先级任务已准备好运行,但传递性地依赖于调度器未优先处理的任务。

  3. 常见陷阱:一个常见的误解是严格的优先级级别能解决此问题。实际上,如果没有整体抢占(预留任务退出所需的所有资源),增加优先级级别反而可能通过创建更复杂的依赖链来增加反转的可能性。

解决此问题需要在预防和检测之间做出选择。预防机制(如严格的集团调度和激进的超时)通过构建消除死锁状态,但通常牺牲利用率。检测机制允许系统进入潜在的不安全状态以最大化吞吐量,依靠监控来识别和解决发生的死锁。

对于大规模机群,精确的死锁检测在计算上是非平凡的。一个拥有 10,000 个 GPU 和 500 个待处理作业的集群,其状态空间超过\(10^{15}\)种可能的分配,因此实时构建和遍历全局等待图通常是不可行的。大规模调度器因此近似存活性而非证明它。基于租约的资源为每个分配赋予生存时间(TTL),允许调度器在作业停止续租时回收 GPU。进度监控随后检查作业是否实际在推进,使用 GPU 利用率、步数计数器或心跳时间戳等信号来识别僵尸分配。当调度器必须抢占低优先级作业时,整体抢占会预留该作业进行检查点和干净退出所需的 CPU、网络和存储带宽;否则,受害者可能因过度饥饿而无法释放加速器。这些策略接受非零的误报率以保持集群的存活性。

异构集团调度

传统的集团调度模型假设同构的批量同步并行(BSP)工作负载:\(N\)个相同的工作进程执行完全相同的计算图。某些训练工作流打破了这一假设,因为一个逻辑作业包含多个具有不同资源配置的角色。例如,在基于人类反馈的强化学习(RLHF)等偏好训练管道中,一个组件可能更新策略,而另一个对输出进行评分或提供参考行为。调度器仍视为一个作业,但集团包含训练器、评估器和推理风格的工作进程,它们需要不同的 GPU 数量、内存占用和生命周期。

异构集团调度重新定义了这种分配。调度器必须原子性地分配一组多样化的资源:针对内存密集型推理生成优化的高 HBM 节点,以及针对计算密集型反向传播优化的高算力节点。编排挑战从静态的拓扑装箱转移到动态的数据路由。激活值和梯度不再仅在同构环中规约;相反,数据必须在生成展开的推理节点和计算策略更新的训练节点之间持续流动。如果异构集团分配在这些不同的工作负载配置文件之间没有完美平衡,整个集群就会失去同步,导致昂贵的加速器因缺乏数据而空闲。

异构 RLHF 集团是例外;对于常见的同构训练作业,反复出现的张力在于集团调度的安全性与回填的利用率之间。集团调度防止死锁但浪费资源;回填提高利用率但需要用户无法准确提供的运行时间估计。两种常见的编排范式通过根本不同的架构理念解决了这种张力。

编排范式

集团调度/回填的张力没有工具中立的答案:编排范式决定了平台优先保护哪种保证。Slurm 由 HPC 批处理系统塑造,倾向于显式资源请求和可预测的分配。Kubernetes 由云原生服务管理塑造,倾向于声明期望状态和持续协调。因此,选择不是 Slurm 与 Kubernetes 作为产品的对比;而是哪种控制模型最适合机群的工作负载组合、故障行为和运维文化。

根本区别在于命令式声明式资源管理。在命令式系统中,用户指定确切需要的资源,系统直接分配它们。在声明式系统中,用户指定想要运行的内容,系统收敛到该状态。这一区别熟悉于编程语言设计,它决定了 ML 工作负载如何被调度、监控和从故障中恢复。

Slurm:HPC 传承

Slurm Workload Manager¹²⁵起源于劳伦斯利弗莫尔国家实验室,作为 Linux 集群的可扩展、容错资源管理器(Yoo et al. 2003)。其面向批处理的设计契合科学计算的独特需求:长时间运行的作业、昂贵的共享硬件、以及能够预先指定资源需求的用户。在 ML 基础设施中,同一模型映射到通过分区实现的异构加速器池和长时间运行分布式作业的可预测分配上的 GPU 训练。

Slurm 使用命令式调度模型:用户提交指定确切资源需求的作业脚本,调度器根据优先级、公平性和资源可用性将作业放入分区。这种直接性使得推理分配保证变得简单直接。当用户提交sbatch --gres=gpu:8 --nodes=4时,如果作业启动,Slurm 保证其将在 4 个节点上拥有恰好 32 个 GPU。用户精确知道他们得到什么,调度器精确知道必须提供什么。这种直接性的代价是用户必须精确了解其资源需求;请求过多浪费资源,请求过少导致内存溢出错误或性能下降。

Slurm 架构与 ML 集群配置

核心架构

Slurm 的架构由一个中央控制器守护进程(slurmctld)组成,它维护集群状态的全局视图并做出调度决策,以及每个节点上的守护进程(slurmd),它们管理本地资源并执行作业。这种集中式架构提供了 8.1.1 节 中讨论的权威分配模型:控制器是资源分配的事实来源,降低了冲突分配的风险。代价是潜在的单点故障(通过主备故障转移解决)和调度吞吐量限制。随着机群规模增长,组织可能会对集群进行分区或联邦化调度器,以限制调度延迟和故障影响。

ML 集群典型配置

典型的 ML 集群配置按加速器类型和互联定义 Slurm 分区配置,如 表 8.1 所示:

| 分区 | GPU/节点 | 互联 | 典型用途 |

| :--- | :--- | :--- | :--- |

| dgx-a100 | 8× A100 | NVLink + IB NDR | 大规模 LLM 训练 |

| a100-pcie | 4× A100 | PCIe + IB HDR | 中型训练 |

| inference | 2× A10G | 以太网 | 模型服务 |

| debug | 1× V100 | 以太网 | 开发调试 |

表 8.1:Slurm 分区配置 分区将异构加速器组织为与工作负载特征相匹配的逻辑池。NVLink 连接的分区支持张量并行,而 PCIe 分区服务于主要依赖数据并行的工作负载。将推理和调试分区分开,可防止实验性工作负载影响生产服务。

GPU 分配策略

这些分区使 GPU 分配策略具体化,Slurm 提供了多种控制放置的机制。标志 --gres=gpu:N 请求每个节点 N 个 GPU,而 --gpus=N 请求作业的总 GPU 数量。朴素分配可能导致节点碎片化:如果作业在 8 GPU 节点上请求 6 个 GPU,每个作业每个节点浪费 2 个 GPU,将有效容量降低至 75%。Slurm 的 select/cons_tres 插件启用了 GPU 感知的可消耗资源调度,跟踪单个 GPU 而非将节点视为不可分单元。标志 --gpus-per-node 在 NVLink 通信模式使部分节点分配适得其反时,请求每个分配节点上固定的 GPU 数量;--gpus-per-task 则将 GPU 均匀分布在任务间,用于数据并行工作负载。

GPU 分配与节点选择之间的相互作用产生了一些微妙之处,影响性能和利用率。请求 --gpus=16 --gpus-per-node=8 的作业将始终恰好获得 2 个完整节点,确保每个节点内的 8 个 GPU 通过 NVLink 通信。同一作业若请求 --gpus=16 但无每节点约束,可能会获得分散在 3 或 4 个部分占用节点上的 GPU,降低节点内通信性能。对于使用张量并行的训练作业,每节点约束至关重要;对于仅通过 AllReduce 通信的数据并行作业,无约束放置的灵活性提高了调度器找到有效分配的能力,并减少了碎片化。

公平共享调度

公平共享调度 机制防止任何单个用户或项目随时间垄断资源。核心洞见是过去的使用量应影响未来的优先级:当集群争用时,重度用户应让位于轻度用户。有效优先级启发式算法结合了基础优先级、用户的目标公平份额(F[target])、用户的近期实际消耗(F[actual])和一个小的稳定常数(ϵ):

\[\\text{Priority}_{\\text{effective}} = \\text{Priority}_{\\text{base}} \\times \\frac{F_{\\text{target}}}{F_{\\text{actual}} + \\epsilon} \\qquad(8.1) \]

公式 8.1 中的公平共享公式自然地降低了重度用户的优先级,同时在资源空闲时允许突发访问。消耗了两倍公平份额的研究人员将看到其优先级减半,使新提交排在使用较少的同事之后。相反,近期未使用资源的研究人员获得最高优先级,使其重返活跃工作时能快速获取资源。

使用历史的时间衰减决定了系统“原谅”过去重度使用的速度。一周的半衰期意味着研究人员两周前的重度使用仅对当前使用计算贡献 25%。短半衰期产生快速再平衡,但可能导致振荡行为,用户在资源匮乏和大量获取间交替。长半衰期创建稳定的优先级排序,但可能惩罚刚完成合法大型项目、现欲为小型项目获取资源的研究人员。大多数生产集群将半衰期配置在 3 到 14 天之间,在响应性与稳定性间取得平衡。

抢占策略

抢占策略允许高优先级作业从运行中的工作负载回收资源,对 ML 训练而言,这需要与检查点基础设施仔细协调。Slurm 可用 SIGTERM 通知选定作业,并使用 GraceTime 延迟最终终止,通常在最终 SIGKILL 前给予 60 至 300 秒进行检查点。PreemptMode=REQUEUE 将合格的批处理作业重新入队(例如,用 --requeue 提交的作业或设置了 JobRequeue=1 的集群),作业的启动路径必须从最新检查点恢复。第 7 章 中开发的检查点与恢复基础设施使这种抢占变得实用;没有可靠的检查点,抢占将丢失自上次手动触发保存以来的所有进度,使抢占对长时间运行的训练作业在经济上毁灭性。

抢占引入了一个调度悖论:它提高了调度器服务高优先级工作的能力,但可能降低整体集群吞吐量。每次抢占都会浪费上次检查点与抢占事件间的计算量,加上重启和从检查点重载的时间。如果检查点间隔长(如每小时)且抢占频繁,浪费的计算量可能巨大。生产系统在抢占频率与检查点开销间取得平衡,常配置抢占冷却期,防止同一作业在可配置间隔内被抢占超过一次。

Slurm 在 ML 训练中的优劣势

Slurm 用于 ML 训练的优势显而易见:可预测的分配保证、成熟的公平共享策略、与基于 MPI 的分布式训练框架的直接集成,以及在管理大规模科学计算方面数十年的运维经验。其劣势在于推理工作负载和混合训练-服务集群,面向批处理的模型难以应对服务流量的动态、延迟敏感特性。Slurm 没有“应始终运行的服务”原生概念,使其难以用于必须持续响应请求的推理端点。添加或移除推理副本需要提交或取消 Slurm 作业,这比扩缩 Kubernetes 部署要重得多的操作。

针对 ML 的高级 Slurm 配置

一旦 Slurm 接管批处理训练,剩下的问题是批处理系统能感知多少 ML 语义。标准分区和公平共享策略调度普通作业;大规模深度学习需要能暴露扫描结构、非对称流水线资源、作业边界处的硬件健康状况,以及不同加速器类型经济权重的配置。

作业数组将成千上万个实验性超参数扫描试验的混乱提交转化为一个连贯且可调度的单元。用户不再通过提交大量单独的 sbatch 请求来淹没控制器,而是提交一个单一的数组作业:#SBATCH --array=0-99。Slurm 将其视为一个对象,用于解析和排队开销,但将每个元素调度为独立任务,使得调度器能够用单个试验填充集群中的小空隙。每个任务接收一个唯一的 SLURM_ARRAY_TASK_ID 环境变量,训练脚本使用该变量来索引超参数配置文件(例如,为任务 0 选择学习率 η = 10^(−4),为任务 1 选择 η = 3 × 10^(−4))。这种机制在大规模消融研究中很常见,使研究人员能够通过单个命令启动 1,000 个实验,同时保持调度器的吞吐量。

异构作业解决了预处理瓶颈问题,其中昂贵的 GPU 在 CPU 准备数据时闲置。传统作业为每个节点分配对称资源,迫使用户为整个管道预留 GPU 节点。Slurm 的异构作业支持通过在命令行中用 : 分隔组件,或在脚本中使用 #SBATCH hetjob,让单个提交跨越不同的资源类型。对于我们的 175B 模型(调度演示工作负载,而非完整的 1,024-GPU 预训练运行)的 64-GPU 微调运行,它使用了这种模式:请求 8 个 GPU 节点(64 个 A100)用于模型,以及 2 个仅 CPU 节点用于即时数据标记化。srun --het-group=0 命令在 GPU 上启动训练过程,而 srun --het-group=1 启动数据工作进程,使得昂贵的加速器能够专注于梯度计算,而更便宜的 CPU 节点为其提供数据。

prolog 和 epilog 脚本充当集群的免疫系统,在作业开始前和结束后运行管理代码。在 ML 集群中,典型的 prolog 脚本会对分配的硬件进行预检:它验证 NVLink 拓扑(使用 nvidia-smi topo -m),检查 ECC 错误计数器,并验证 InfiniBand 链路宽度。如果节点未通过这些检查,prolog 返回非零退出码,导致 Slurm 排空该节点并在其他地方重新排队作业,以防止静默硬件故障损坏一周的训练运行。epilog 脚本通过终止孤儿 Python 进程、清除共享内存段(/dev/shm)以及将最终 GPU 利用率指标记录到会计数据库来确保卫生。对于 64-GPU 微调运行,prolog 脚本特别验证每个节点上的所有 8 个 GPU 是否具有完整的 P2P 带宽访问,以防止单个退化的 NVLink 通道成为整个张量并行组的瓶颈。

最后,可跟踪资源(TRES) 将 Slurm 的会计功能从简单的 CPU/内存跟踪扩展到诸如 GPU-小时、许可证令牌或功率预算之类的资产。重要的是,每个跟踪的资源都携带一个计费权重,因此公平份额可以由实际经济成本驱动,而不是原始核心数量。这确保了使用 100 个较旧 GPU 的团队不会比使用 100 个旗舰 H100 的团队更快耗尽预算,并且它允许组织在调度器的公平性计算中直接实施昂贵资源的项目计费。

这些扩展使 Slurm 更具 ML 感知能力,同时保持其批处理调度器模型。Kubernetes 则从不同的契约出发:用户声明服务和作业的期望状态,控制器不断地将集群调和到该状态。

Kubernetes: 声明式编排

Kubernetes 是 ML 基础设施的一个常见平台,尤其适用于需要统一管理训练和服务工作负载的组织(Burns et al. 2016; Cloud Native Computing Foundation 2024)。Slurm 的模型是 imperative(“在这些资源上运行此作业”),而 Kubernetes 使用 声明式模型 (“确保此状态存在”),通过控制循环持续协调期望状态与实际状态。这种根本性的差异塑造了 ML 工作负载管理的各个方面,从作业提交到故障恢复。

声明式模型的力量在于它对故障处理的方法。Slurm 的恢复依赖于配置且面向批处理:slurmctld 可以通过 slurmd 心跳检测节点故障,并在设置 Requeue=1 时重新排队作业,但其恢复语义需要显式配置才能表现得像一个持续协调的控制循环。而在 Kubernetes 中,控制循环持续协调期望状态(“此服务工作者的 4 个副本应处于运行状态” —— 对于 Deployment,或针对 Job 的配置重试策略)与实际状态,当检测到偏离时,会在健康节点上自动创建替换 pod。具体到分布式训练,副本替换语义通常来自更高级别的操作员,如 Kubeflow 的 PyTorchJobMPIJob,而不是核心 Kubernetes 原语。这种自愈行为降低了运维负担,但引入了复杂性:系统的行为源于控制循环的交互而不是明确命令,当事情出错时,调试变得更具挑战性。

Kubernetes 的控制循环架构建立在 控制器模式¹²⁶ 上:控制器观察集群的实际状态(存储在 etcd 中,这是 Kubernetes 用于 API 状态的分布式键值存储),将其与用户通过 API 对象(如 Deployments、Jobs 和 StatefulSets)指定的期望状态进行比较,并采取纠正措施以缩小差距。此模式在每一层重复:Deployment 控制器确保正确数量的 pod 存在,调度器将 pod 分配到节点,每个节点上的 kubelet 确保容器正在运行,节点控制器检测节点故障。每个控制器独立运行,仅通过 etcd 中的共享状态进行通信,这使得系统对单个控制器故障具有韧性,但当多个控制器交互时可能产生 emergent 行为。

原生 Kubernetes 缺乏 ML 感知的调度,但扩展填补了这一差距。GPU 调度依赖于 设备插件,它们将加速器暴露为可调度的扩展资源。NVIDIA 设备插件将 GPU 注册到 kubelet(节点级代理),使得 pod 规格能够通过标准 Kubernetes 资源模型声明式地请求 GPU 资源。清单 8.1 展示了由此产生的 pod 片段。

# Example pod fragment requesting GPU resources
resources:
  limits:
    nvidia.com/gpu: 1

清单 8.1: Kubernetes GPU 分配:通过 NVIDIA 设备插件请求 GPU 资源的 pod 资源规格。nvidia.com/gpu 资源名称遵循 Kubernetes 扩展资源惯例,其中域前缀标识设备插件供应商。这种声明式语法使得在任何已安装 NVIDIA 设备插件的 Kubernetes 集群上,GPU 工作负载定义具有可移植性。

这种二进制分配模型对于推理工作负载造成了显著的低效率。一个仅偶尔提供服务的小型模型可能只需要 GPU 计算和内存容量的一小部分,但 Kubernetes 仍会分配一个完整的 GPU,导致剩余容量被浪费。对于能够饱和 GPU 计算的训练工作负载,完整设备分配是合适的。而对于资源需求可变且通常较 modest 的推理工作负载,它会产生与 Slurm 中部分节点分配相同的碎片化问题。

多实例 GPU (MIG) 技术

多实例 GPU (MIG)¹²⁷ 技术通过将 A100 (80 GB) 和 H100 (80 GB) GPU 划分为硬件隔离的实例,解决了资源利用效率低下的问题。与基于软件的 GPU 共享不同,MIG 为每个实例专门分配内存、缓存和计算资源,防止一个工作负载干扰另一个,消除了“吵闹邻居”¹²⁸ 效应。表 8.2 中的 A100 MIG 配置文件展示了调度后果:单个物理 A100 变成若干固定大小的资源,设备插件可将其作为独立可调度的加速器暴露出来。

表 8.2:A100 MIG 配置文件

| MIG 配置文件 | GPU 显存 | SM 数量 | 典型工作负载 |

| --- | --- | --- | --- |

| 1g.10gb | 10 GB | 14 SMs | 小规模推理 |

| 2g.20gb | 20 GB | 28 SMs | 中规模推理 |

| 3g.40gb | 40 GB | 42 SMs | 大规模推理 |

| 7g.80gb | 80 GB | 98 SMs | 训练 |

表 8.2:A100 MIG 配置文件:多实例 GPU 配置通过牺牲 GPU 内存和计算资源来换取更高的共享密度。每个配置文件提供硬件隔离的资源,使 MIG 适用于多租户推理集群,其中不同团队或客户的工作负载不得相互干扰。SM(流式多处理器)数量决定了每个实例内的计算吞吐量。

正如 8.1.5 节 所述,默认的 Kubernetes Pod 调度是独立的,因此当“全有或全无”的启动很重要时,ML 平台会添加准入或 Gang 语义。默认调度器逐个评估 Pod,将每个 Pod 放置在最佳可用节点上。对于包含 64 个 Pod 的分布式训练作业,这意味着 64 个独立的调度决策,无法保证所有 64 个都能成功。如果集群有 60 个 Pod 的资源但没有 64 个的资源,默认调度器可能会放置 60 个 Pod 并让剩余 4 个处于 Pending 状态,这正是 Gang 调度所防止的部分分配浪费。PodGroup 风格的抽象,无论是由调度器插件还是更高级的批控制器实现,都为平台提供了一个与训练作业匹配而非单个 Pod 匹配的准入单位。

Volcano¹²⁹ 批调度器和 Coscheduling 调度器插件通过实现基于 PodGroup 抽象的 Gang 语义来弥补这一空白。PodGroup 声明一组 Pod 必须一起调度,指定必须可满足的最小成员数量,然后该组中任何 Pod 才能启动。调度器原子地评估整个组:要么所有最小成员都能被放置并同时放置,要么一个都不放置,该组在队列中等待。这将 Kubernetes 的 Pod 级调度转变为尊重分布式训练全有或全无需求的作业级调度。

优先级类控制 Kubernetes 中的抢占行为,建立层级结构以确定哪些工作负载向哪些其他工作负载让出资源。典型的生产配置为推理工作负载分配高优先级(确保满足服务 SLO),为训练工作负载分配中等优先级,为开发作业分配低优先级。默认的 PriorityClass 行为 preemptionPolicy: PreemptLowerPriority 允许高优先级的 Pending Pod 在腾出空间的情况下驱逐低优先级 Pod。具有 preemptionPolicy: NeverPriorityClass 是非抢占式的:其 Pod 可以在调度队列中排在低优先级 Pod 前面,但它们不会驱逐正在运行的 Pod,并且仍可能被更高优先级的 Pod 抢占。组织必须仔细平衡优先级层级:过度激进的推理抢占会使训练作业颠簸(反复抢占和重启),而过度宽松的训练优先级可能在需求激增时饿死推理。Kubernetes 的优先级和抢占系统与 第 7 章 中开发的检查点感知抢占模式集成,被抢占的训练作业在让出资源前保存状态,最大限度地减少浪费的计算。

Kueue¹³⁰ 是一个较新的 Kubernetes 原生作业排队系统,代表了一种新兴方法,将准入控制调度分离。Kueue 不完全替换 Kubernetes 调度器(如 Volcano 所做),而是管理作业何时进入集群,而默认调度器(或任何兼容调度器)处理 Pod 放置在何处。这种关注点分离具有实际优势:Kueue 可以与现有 Kubernetes 基础设施一起部署,而不会中断正在运行的工作负载,并且它自然地与用于拓扑感知放置和其他功能的调度器插件生态系统集成。

Kueue 通过代表组织团队或项目的 ClusterQueues 提供公平共享调度。每个队列都有资源配额、允许队列使用其他队列闲置容量的借用策略,以及当拥有队列需要时通过抢占回收借用资源的抢占策略。研究团队队列可能在非高峰时段借用生产团队队列的闲置容量,当生产团队提交紧急工作时通过抢占自动归还资源。这种借用机制镜像了 Slurm 中的分层公平共享方法,但通过 Kubernetes 的声明式模型而非 Slurm 的命令式配置来表达。

Kubernetes ML 训练生态系统

Kubernetes 提供了编排原语,但需要专门的 Operator 和插件生态系统来弥合微服务编排与高性能训练之间的鸿沟。每个扩展都使一个隐藏的操作约束对平台可见:作业原子性、网络旁路、检查点存储或遥测。该生态系统用 HPC 环境中发现的批处理、硬件加速和可观测性能力增强了 Kubernetes。

训练 Operator 生态系统

训练 Operator 生态系统通过特定于作业的自定义资源定义 (CRD) 扩展 Kubernetes,简化了分布式训练。Kubeflow Training Operator 为 PyTorchJobTFJobMPIJob 资源提供控制器,自动化分布式工作负载的复杂生命周期。PyTorchJob 规范定义 Worker 数量、容器镜像和资源需求;Operator 随后创建必要的 Pod 并配置分布式环境变量,如 MASTER_ADDRMASTER_PORTWORLD_SIZERANK。如果 Worker Pod 失败,Operator 可以检测到偏差并创建替代 Pod 以恢复期望的副本数量,但训练状态恢复仍取决于框架的检查点和会聚逻辑。

网络性能优化

在 Kubernetes 中实现高网络性能通常需要绕过为普通微服务而非紧密同步的集体操作设计的网络层。CNI 配置、覆盖网络和 CPU 切换位于关键的梯度同步路径上时,会增加延迟或降低有效带宽。对于带宽敏感的训练,主机网络 (hostNetwork: true) 允许 Pod 完全绕过 CNI,授予对主机网络命名空间和 RDMA 设备的直接访问。NVIDIA Network Operator 自动化实现此性能所需的低级管道,管理 RDMA 设备插件、SR-IOV 虚拟功能分配和 GPUDirect RDMA 配置。当剩余堆栈配置正确时,这种旁路能力使容器化工作负载能够接近裸金属互联性能。

存储集成与训练集群的可观测性

存储集成解决了“检查点风暴”问题,即数千个 GPU 同时写入 TB 级别的状态。Kubernetes 的 PersistentVolumeClaims(PVCs)抽象了底层存储结构,该结构可能是类似 Lustre 或 GPFS 的并行文件系统,也可能是通过 CSI 驱动程序提供的高性能对象存储。关键的架构挑战在于防止这些同步写入导致存储后端饱和。具有服务质量(QoS)策略和客户端限流的存储类是必不可少的,以确保一个 1,024-GPU 的训练作业写入 2 TB 检查点时不会饿死其他工作负载或导致存储元数据服务器崩溃。

可观测性堆栈

可观测性堆栈 用于训练集群,将通用的集群监控与专用硬件遥测相结合。Prometheus 和 Grafana 提供了数据收集和可视化层,而 DCGM(数据中心 GPU 管理器)导出器则暴露出细粒度的 GPU 指标:利用率、内存带宽、温度和 ECC 错误。此堆栈启用了多租户效率所必需的利用率仪表板,使运维人员能够区分真实的计算饱和和 I/O 绑定的空闲状态。

1,024-GPU 预训练运行

我们的 175B 模型的 1,024-GPU 预训练运行使用了完整的 Kubernetes 堆栈。工作负载被定义为一个 PyTorchJob,包含 128 个工作器 Pod,每个 Pod 请求 8 个 GPU。启用了主机网络,以允许 NVLink 连接节点直接访问 InfiniBand。由高吞吐量 Lustre 文件系统支持的 PVC 提供对 100 TB 训练数据集的访问,并吸收周期性检查点。DCGM 指标流向 Prometheus,如果任何 GPU 报告无法纠正的 ECC 错误或 NVLink 带宽低于预期基准,将触发警报。

在范式之间进行选择

在 Slurm 和 Kubernetes 之间进行选择很少是二元的;它取决于工作负载组成、组织背景以及不同系统特性的相对重要性。了解每种范式何时表现出色有助于指导架构决策。

Slurm:纯训练集群

Slurm 在纯训练集群中表现出色,此时可预测性和裸机性能是最重要的。其直接的资源管理避免了 Kubernetes 引入的容器开销(容器网络增加微秒级延迟并降低有效网络带宽 1% 至 5%),其批量调度模型与长时间运行的训练作业自然契合,这些作业需要保证、不间断地访问特定硬件。Slurm 的中央调度器对集群状态具有全局可见性,能够进行考虑公平份额、作业大小、队列深度和资源可用性的复杂多因素优先级决策。国家实验室、大学研究集群和专用训练基础设施通常倾向于使用 Slurm,因为这些环境优先考虑对相对同质的批量工作负载实现最大硬件利用率和最小开销。

Kubernetes:混合训练-服务集群

Kubernetes 在混合训练-服务集群中表现出色,此时统一管理、滚动更新和服务网格集成可降低运营复杂性。同时运行训练管道和生产推理端点的组织受益于一个单一控制平面,它通过一致的 API 管理两种工作负载类型。Kubernetes 的声明式模型简化了运营工作流:部署新模型版本意味着更新 Deployment 规格,而 Kubernetes 会自动处理滚动更新、健康检查和回滚。云原生组织、将 ML 部署为微服务的团队以及已有 Kubernetes 专业知识的组织通常倾向于使用 Kubernetes,因为将 ML 工作负载添加到现有平台的边际成本低于运行独立的调度系统。

混合方法

许多生产环境认识到单一范式无法满足所有需求,因此他们以互补角色部署两种系统。一种常见架构是使用 Slurm 进行专用训练集群,此时原始性能和调度复杂性至关重要;而使用 Kubernetes 进行推理服务、管道编排和较轻量的训练工作负载(微调、小规模实验)。新兴模式是使用 Kubernetes 作为外层编排层,通过 Kubernetes 算子提交 Slurm 作业以进行大规模训练运行。这种混合架构在不牺牲 Slurm 对最需要它的工作负载的批量调度能力的前提下,实现了统一管理和可观测性。

比较维度

Slurm 与 Kubernetes 的权衡围绕与 ML 工作负载最相关的维度展开,见表 8.3:

| 维度 | Slurm | Kubernetes |

| --- | --- | --- |

| 调度模型 | 命令式:用户指定确切资源,调度器进行分配 | 声明式:用户指定期望状态,系统持续协调 |

| 故障处理 | 依赖配置的重新排队和检查点工作流 | 通过控制循环实现自愈 |

| 帮派/原子多节点启动 | 原子作业分配加回填;Slurm “帮派调度” 特指时间片分割/抢占 | 生产集群通常使用基于 PodGroup 的支持,如 Volcano、Coscheduling、Kueue 或上游 Alpha 帮派调度 |

| 公平份额 | 成熟的多因素优先级,具有可配置衰减 | Kueue 通过 ClusterQueue 借贷提供基本的公平份额 |

| 容器开销 | 当作业直接在节点上运行时,无容器运行时开销 | 取决于配置的网络开销和容器启动时间 |

| 服务管理 | 不原生支持长期运行的服务 | 原生支持 Deployment、滚动更新、健康检查 |

| 生态系统 | MPI、OpenMP、科学计算库 | 云原生、微服务网格、可观测性堆栈 |

表 8.3:Slurm 与 Kubernetes 在 ML 工作负载上的对比:两种范式反映了针对不同工作负载特性优化的不同设计理念。大多数生产 ML 平台同时使用两者的元素,Slurm 管理专用训练集群,而 Kubernetes 管理推理服务和管道编排。

正如表 8.3 所示,选择也反映了组织的发展轨迹。从 HPC 导向训练基础设施起步的团队可能先从 Slurm 开始,再为服务添加 Kubernetes。从云原生应用基础设施起步的团队可能先从 Kubernetes 开始,再添加面向 HPC 的扩展(如 Volcano、Kueue)用于训练。无论哪条路径都可以达到功能性的混合架构,但起点决定了哪些能力最强,以及哪些需要最多投入来发展。

实际中的混合架构

虽然 Slurm 和 Kubernetes 之间的选择常常呈二元状态出现,但先进的工程组织越来越多地部署 混合架构,以充分利用两种系统的优势。这些架构承认一个基本事实:训练工作负载的行为类似于 HPC 应用(批处理、同步、单体),而服务工作负载的行为类似于微服务(连续、异步、弹性)。将两者强行纳入单一范式往往会导致一个“最低公倍数”系统,该系统无法很好地服务于任何一方。

GPU 集群编排范式

Kubernetes-over-Slurm

Kubernetes-over-Slurm模式将 HPC 调度器包裹在云原生控制平面之内。在此架构中,Kubernetes 充当外层编排层,通过自定义 Operator 管理训练作业的生命周期,同时将实际的 Pod 放置委托给 Slurm。开发者向 Kubernetes 提交一个TrainingJob自定义资源定义(CRD),Operator 将其转换为 Slurm sbatch脚本。随后,Operator 通过 Slurm 的 REST API 监控作业状态,将日志和指标流式传回 Kubernetes 控制平面。这种方法兼得两者之长:拥有 Kubernetes 统一的可观测性、CI/CD 集成和访问控制,同时结合了 Slurm 精妙的帮派调度、拓扑感知能力及裸金属性能。

Slurm-alongside-Kubernetes

Slurm-alongside-Kubernetes模式采取了一种粗粒度方法,为训练和服务维护独立的集群,二者从共享的物理节点池中获取资源。元调度器或容量代理充当仲裁者,根据需求信号在 Slurm 分区与 Kubernetes 集群之间动态重分配节点。在高强度训练活动期间,节点从 Kubernetes 排出并加入 Slurm;在产品发布或流量激增时,流程反向。主要挑战在于模式切换成本:排空 Kubernetes 节点、重新镜像或重新配置并将其加入 Slurm(反之亦然)通常需要 5 到 15 分钟。这种延迟限制了共享的粒度:系统可适应昼夜模式或周度周期,却无法利用 Slurm 容量来吸收秒级推理突发。

统一平台:Ray

Ray 等新兴统一平台试图通过提供单一运行时以同时支撑训练与服务,从而彻底消除这种二分法。Ray 实现了自己的分布式调度器,原生处理 Actor 放置、任务调度与资源管理。这消除了训练(有状态 Actor)与服务(无状态副本)之间的阻抗失配,允许单个 Python 脚本定义的训练循环无缝过渡为部署流水线。代价在于成熟度:尽管 Ray 在编排灵活性上表现优异,但其调度逻辑缺乏 Slurm 数十年的打磨与 Kubernetes 庞大的生态支持,实际上要求平台团队承担更多可靠性与隔离方面的责任。

端到端生命周期:1750 亿参数模型

将这些模式串联起来,以我们的 1750 亿参数模型生命周期为例。训练阶段在 Slurm 上运行,以最大化 NVLink 拓扑效率与帮派调度可靠性,支撑数月长跑。训练完成后,模型制品移交 Kubernetes 用于服务,通过滚动更新与自动扩缩容保障最终用户的高可用性。两者间的桥梁是一个 Kubernetes Operator,它监控训练进度;当验证损失稳定时,自动触发量化流水线并将生成的模型部署至推理集群。

运维权衡

这些选择的运维影响可量化,但具体数值取决于负载组合与本地平台成熟度。纯 Slurm 环境将批处理作业开销降至最低,能使专用训练集群保持高利用率,但需额外工程构建生产级服务基础设施。纯 Kubernetes 环境利用标准云原生工具减少了运维碎片化,但分布式训练可能需要批处理扩展以避免调度碎片化。混合方法瞄准高效前沿:在保留强批训练放置保证的同时,为服务与流水线工作负载维持统一的管理面,代价是架构复杂度增加。

HPC 与云原生调度的融合

HPC 与云原生调度间的界限正因双方社区互采对方特性而迅速模糊。Kubernetes 通过 Volcano 和 Kueue 项目演进批处理能力,引入了此前 HPC 独有的队列、优先级与帮派调度概念。反之,Slurm 正采纳 REST API、容器运行时支持与弹性等云特性。行业正趋同于统一的集群操作系统:兼具云的声明式 API 与容错性,以及超算的严格放置保证与性能。

这种融合将范式选择转化为负载特定的设计决策:团队必须先决定哪些调度保障至关重要,继而叠加拓扑约束。

核心权衡小结

在转向拓扑感知调度前,回顾核心权衡:

  • Kubernetes-over-Slurm:双控制平面优势兼得;新增 Operator 复杂度。

  • Slurm-alongside-Kubernetes:粗粒度资源分区;模式切换缓慢(5–15 分钟)。

  • 统一平台:训练/服务单一运行时;调度/隔离成熟度较低。

  • 纯净环境:运维更简但服务端或训练放置端较弱。


拓扑感知调度

在大规模数据中心随机分配 64 块 GPU 会重创同步训练作业,导致其比将这 64 块 GPU 紧凑部署在单个机架内时慢 15%至 30%。前述调度算法将 GPU 视为可互换单元,但拓扑感知调度认识到:网络拓扑中的物理邻近性与原始算力同等关键。

拓扑层级

现代 GPU 集群呈现多级通信层级,带宽随层级递减、延迟随层级递增。此层级非调度器可忽视的实现细节;它是直接决定通信密集型负载训练吞吐量的物理约束。理解层级对于作出能将分配资源转化为有效工作的放置决策至关重要。

一组呈阶梯状的互联带宽对数刻度图,紫色显示:节点内 NVLink 900 GB/s,机架内 InfiniBand 50 GB/s,跨机架脊叶约 12 GB/s。每个边界皆为数量级带宽断崖。

从 NVLink 到脊叶的跨结构带宽断崖。

单节点内,GPU 通过 NVLink 通信,A100 系统提供每 GPU 600 GB/s,H100 系统达 900 GB/s。这种高带宽、低延迟互联实现了高效张量并行,将矩阵运算拆分至必须在每层 Transformer 交换中间结果(激活值与梯度)的 GPU 上。NVLink 带宽足以将通信与计算重叠,适用于大多数模型架构,意味着张量并行操作若限于单节点内,可接近理想吞吐运行。

机架内/叶交换机域:InfiniBand / RoCE

同一机架或叶交换机域内节点间,通信经由 InfiniBand 或 RoCE,速率 400 Gb/s(NDR InfiniBand),约每链路 50 GB/s。这比 NVLink 带宽低一个数量级。数据并行要求每训练步 AllReduce 梯度,在此层级运行高效,因梯度同步模式允许通信与下一迭代前向传播重叠。延迟惩罚适中(微秒级对纳秒级),聚合带宽足以应对通常小于张量并行中激活张量的梯度张量。

在通过 spine switches 相连的机架之间进行通信时,需要经过额外的交换跳数,这会导致更高的延迟,并可能因网络拥塞而降低有效带宽。第 3.5.2 节 中分析的胖树拓扑在理论上能够提供全双工带宽,但在实际中,由并发流量、路由低效率和交换机缓冲区限制引起的拥塞会使跨机架通信的有效带宽比机内通信降低 10%至 30%。跨机架通信还会增加尾部延迟的变异性:虽然中位延迟可能仅略有增加,但在拥塞事件期间,P99 延迟可能会激增,在同步训练中形成“落后者”效应,因为最慢的通信者决定了所有工作节点的进度。

这意味着,将一个 64-GPU 的作业完全分配在单个机架内(8 个节点,全部位于同一个叶式交换机下)将比将同一作业分散到 8 个机架上(每个机架 1 个节点,每次 AllReduce 都需要穿越 spine 交换机)表现更好。性能差异直接源自第 6 章中的通信分析:环形 AllReduce 的延迟取决于环中最慢的链路,而跨机架跳数会同时引入更高的延迟和增加的争用风险。对于同步训练,每次迭代都必须等待最慢的工作节点完成其 AllReduce,因此即使偶尔发生的跨机架拥塞事件也会导致持续的吞吐量下降。

因此,调度器必须将集群建模为一个层级图,而不仅仅是一个平坦的计算槽列表。在每个层级(GPU、节点、机架和 Pod)都存在不同的带宽“悬崖”,分别对应于 NVLink、InfiniBand 和 spine 交换机的边界。每次跨越这些边界都会带来一个数量级的带宽降低,而在机架和 Pod 层级,网络过订阅(通常在 spine 交换机处为 2:1 或 4:1)会进一步限制双工带宽,低于原始链路速度所暗示的水平。一个拓扑感知的调度器会明确针对这些约束进行优化,将“局部性”视为一级资源。对于一个需要 1,024 个 GPU 的分布式训练作业,如果将工作节点分散到随机机架上的碎片化放置相比于在单个 Pod 内的紧凑放置,可能会使端到端训练吞吐量降低 15%至 30%。

图 8.3 对比了 4-GPU 张量并行组的最优放置与分散放置,展示了通信路径如何决定 GPU 是使用单个叶式交换机,还是必须经过较慢的叶- spines-叶跳数。

图 8.3:拓扑感知与未感知放置:一个 4-GPU 作业分布在两个机架上(左侧,拓扑未感知)必须经过叶- spines-叶跳数:每条 AllReduce 消息约 3 个交换跳,有效带宽约为 12 GB/s。相同的 4-GPU 作业若紧凑放置在一个机架内(右侧,拓扑感知),则使用单个叶式交换机:1 跳,有效带宽约为 50 GB/s,大约快 4 倍。图表下方的定量摘要报告了交换跳数和有效带宽。

放置算法

图 8.3 中所示的带宽差距是关键约束:大约 4×的带宽降低,从机内叶路径到跨机架叶- spines-叶路径,意味着分散放置会迫使张量并行组在通信上花费比计算更多的时间,这种惩罚会在模型的每个 Transformer 层中累积。拓扑感知的放置算法将作业分配到能够最小化通信成本的节点上,将抽象的 bin packing 问题转化为在集群物理拓扑上的图优化问题。最简单的方法使用局部性成本,在方程 8.2中定义,用于惩罚跨越多个拓扑域的分配:

Cost[locality] = ∑[i < j]w(d(g[i], g[j]))   (8.2)

其中,g[i]和g[j]是分配给作业的 GPU,d(g[i], g[j])是它们的拓扑距离(同一节点为 0,同一机架为 1,不同机架为 2),w(⋅)是一个随距离增加而增加的加权函数。调度器在从可行放置集合中选择分配时,会最小化此分数。加权函数编码了每个拓扑层级的性能影响:w(0) = 0(同一节点,NVLink,无惩罚),w(1) = 1(同一机架,一次交换跳),w(2) = 10(不同机架,多次交换跳)。同机架与跨机架放置之间十倍的权重差异反映了跨越 spine 交换机带来的不成比例的性能影响。

更复杂的算法会区分并行策略,认识到不同的通信模式具有不同的带宽和延迟需求。张量并行需要最高的带宽和最低的延迟,因为它在每一层都会交换激活张量;应将其限制在单个节点内的 NVLink 域。流水线并行可以容忍中等延迟,因为它在阶段边界之间发送激活的频率较低(每个 microbatch 每个阶段边界一次,而不是每一层);它可以跨机架内的节点进行,此时 InfiniBand 提供足够的带宽。数据并行由于其可以与计算重叠的批量梯度同步,可以在机架之间高效运行,因为通信对延迟不敏感,并且能够有效利用可用带宽。

算法 3 将这些要素整合为一个决策:第 8.1.5 节 的“全有或全无”gang 约束、方程 8.2 的局部性成本,以及前述的并行到拓扑映射。调度器枚举所有可行的放置方案,根据通信成本对每个方案进行评分,并选择成本最低的方案;如果没有可行方案,则将作业保持在队列中,而不是让加速器闲置。

算法 3 拓扑感知放置

Require: 请求N个加速器的作业,包含张量、流水线和数据并行组;具有每层级链路成本w(⋅)的集群拓扑

posted @ 2026-09-06 04:02  绝不原创的飞龙  阅读(3)  评论(0)    收藏  举报