博客

Flight Recorder:理解 NCCL 看门狗超时的全新视角

如果您曾经训练过大型 AI 模型,并遇到过类似以下的错误而导致训练失败:

[Rank 0] Watchdog caught collective operation timeout: WorkNCCL(SeqNum=12345, 
OpType=ALLREDUCE, NumelIn=1, NumelOut=1, Timeout(ms)=600000) ran for 600029
milliseconds before timing out.
Exception raised from checkTimeout at .../torch/csrc/distributed/c10d/ProcessGroupNCCL.cpp:692 (most recent call 
first):

...

# 2  c10d::ProcessGroupNCCL::WorkNCCL::checkTimeout(std::optional<std::chrono::duration<long, std::ratio<1l, 1000l> > >)
# 3  c10d::ProcessGroupNCCL::Watchdog::runLoop()
# 4  c10d::ProcessGroupNCCL::Watchdog::run()
# 5  execute_native_thread_routine
# 6  start_thread
# 7  __clone3

您可能已经遇到过臭名昭著的 NCCL 看门狗(watchdog)超时问题。调试此错误非常困难——错误信息通常很通用,调试需要进行跨 Rank 的遥测分析,且根本原因往往是多层级的,并可能存在复杂的因果链。

本文提供了有关 NCCL 看门狗超时的关键见解,包括:

  • 为何会发生此错误,以及为什么它如此难以调试;
  • 对该错误最常见根本原因的深度解析(例如,CPU 端分歧、GPU 挂起、集合通信配置错误等);
  • 如何使用 PyTorch 飞行记录器(Flight Recorder)快速定位问题并进行修复,包括其在 Meta 内部的使用经验。

阅读完本文后,您将掌握诊断和高效解决 NCCL 看门狗超时问题所需的知识和实用工具,从而更加从容地应对此类挑战。

简介:什么是 PyTorch 中的集合通信?

让我们从考察 PyTorch 在分布式环境下的工作方式开始。

在单 Rank(即单 GPU)训练时代,当用户调用张量操作(如 torch.matmul(tensor1, tensor2))时,请求会通过 C++ 分发器并最终调用后端内核(例如 GPU 的 CUDA 内核或 CPU 的 C++ 实现)。然而,在分布式训练中,用户必须在某些张量操作后执行同步,以避免掉队(straggling)或在各 Rank 间共享计算结果。例如,用户可能会调用 dist.all_reduce(tensor1),它会对分布式设置中所有实例的 tensor1 进行求和,并将结果写回 tensor1。这种同步类型被称为“集合通信”(collective)操作。集合通信在“进程组”(process group)中执行,该组代表一组需要同步的 Rank。集合通信的例子包括 all-reduce(用于 DDP)、all-gather/reduce-scatter(用于 FSDP)以及 all-to-all(用于 TorchRec)。

现在,让我们深入了解当用户调用 dist.all_reduce(tensor1) 时会发生什么。该调用会通过 C++ PyTorch 分发器和 PyBind 层,后者最终会调用到 PyTorch c10d 层。在 c10d 层中,PyTorch 会调用来自各种通信后端库的集合通信 API,例如用于 GPU 通信的 NCCL API 和用于 CPU 通信的 Gloo本文的后续讨论将主要集中在这个 c10d 层。

随之而来的问题是:为什么 PyTorch 需要这个 c10d 层?为什么不让用户直接调用 NCCL API?答案是 PyTorch 在将请求交给通信库之前需要更多的控制信息。c10d 层提供了多种功能;在本文的上下文中,我们重点关注最显著的一个:PyTorch c10d 看门狗。由于大多数现代训练发生在 GPU 上,我们在此进一步将讨论范围缩小到仅针对 NCCL,并在下文中将此看门狗称为 NCCL 看门狗 但请注意,虽然 NCCL 是 Nvidia GPU 特有的,但 c10d 看门狗机制能够监控任何分布式后端。

问题陈述:NCCL 看门狗超时错误

什么是 NCCL 看门狗超时?

在没有 Bug 或集合通信使用错误的理想场景中,集合通信在 CPU 端调度,在 GPU 上异步执行,并将结果返回以供后续使用。然而,NCCL API 本身并不提供任何内置的错误检查。如果集合通信被“误用”(包括使用无效参数调用、各 Rank 间调用了不同的集合通信操作等),集合通信的执行将在 GPU 上无限期挂起。

为了检测这种挂起,PyTorch 在 c10d 层引入了一个 CPU 端的 Work 对象和一个 NCCL 看门狗监控线程。Work 对象通过在 NCCL API 调用之前之后包裹两个 CudaEvent 来跟踪集合通信在 GPU 上的生命周期。该机制使 PyTorch NCCL 看门狗能够定期轮询集合通信的状态,并检查它是否在用户定义的超时时间内(默认 10 分钟)在 GPU 上完成。如果超过此时间,NCCL 看门狗会抛出一个异常来中断训练过程——这个异常就是广为人知的“NCCL 看门狗超时”错误。(注意,该缩写常导致混淆,因为“NCCL 看门狗超时”并非由 NCCL 库引发,而是由 PyTorch NCCL 看门狗引发的。错误名称包含“NCCL”是因为超时发生在集合通信使用 NCCL 后端启动时。)

下图 1 展示了触发 NCCL 看门狗超时的事件序列:

图 1:展示 PyTorch 如何监控 NCCL 集合通信的序列图

为什么 NCCL 看门狗超时难以调试?

NCCL 看门狗超时错误难以调试主要有两个原因。

首先,NCCL 看门狗超时是我们所说的“包罗万象”的错误——任何导致 Rank 在集合通信上无限等待的情况都可能导致 NCCL 看门狗超时,而不仅仅是集合通信速度慢或网络/NCCL 库问题。正如我们在下一节所述,这包括 CPU 端挂起、GPU 挂起、CUDA 死锁、无效的集合通信参数、硬件/网络问题等。

其次,错误信息大多无济于事。由于错误是从看门狗线程引发的,为了降低看门狗线程自身挂起的风险,错误信息中只记录了关于集合通信的有限元数据。关键的调试信息(例如调度该集合通信的 PyTorch 主线程调用栈)在错误信息中缺失(错误信息中的栈追踪来自 NCCL 看门狗线程,这在诊断上无关紧要)。

此外,正如我们在下一节解释的那样:

  • 首先触发 NCCL 看门狗超时的 Rank 很少是导致超时的罪魁祸首;以及
  • 在发生 NCCL 看门狗超时时正在执行的集合通信,可能并不是导致超时的原因。

因此,如果不使用 PyTorch 飞行记录器,调试 NCCL 看门狗超时错误可能需要数小时甚至更久,通常需要通过启用额外的调试标志(如 CUDA_LAUNCH_BLOCKING)来重新运行作业。

深度解析:什么导致了 NCCL 集合通信超时?

为了解释 NCCL 看门狗超时的原因,我们首先需要描述 PyTorch 如何在分布式设置中调度和执行集合通信(在没有像 Monarch 这样的集中式单一控制器的情况下)。

如上所述,PyTorch NCCL 集合通信由 CPU 调度并在 GPU 上异步执行。在大多数训练框架中,当训练资源被高效利用时,工作负载是 GPU 受限的,因此 CPU 会向 GPU 调度一系列 GPU 计算内核(PyTorch 操作)和 NCCL 集合通信,然后间歇性地阻塞以等待 GPU 在下一个 CPU-GPU 同步点完成所有已调度的内核。图 2 展示了 2-Rank 进程组的“快乐路径”行为。

图 2:2-Rank 进程组中 CUDA 内核和 NCCL 集合通信如何启动和执行的序列图。为便于阅读,GPU 内核执行之间的间隙被夸大了。

在此过程中,GPU 上有两种类型的同步操作:

  1. Rank 间的 GPU-GPU 同步(即 NCCL 集合通信),进程组中的所有 GPU 在继续执行其他内核之前同步其状态。并非所有 NCCL 集合通信都充当屏障,但屏障类集合通信(如 all_reduce)是 NCCL 看门狗超时的最常见来源。(还有一些其他操作,如 PyTorch NCCL 进程组初始化/销毁,需要 Rank 间 GPU 同步,但它们超出了本博客的范围。)
  2. 单 Rank 的 CPU-GPU 同步,其中与特定 GPU 关联的 CPU 线程会阻塞,直到 GPU 完成所有已调度的操作(包括计算内核和通信内核)。这种 CPU-GPU 同步可以是显式的(例如调用 torch.cuda.synchronize)或隐式的(例如在 CPU 和 GPU 之间移动张量)。

集合通信超时有两种方式:1) 集合通信内核本身的执行时间超过了超时阈值或挂起,或者 2) Rank 间不同步,导致在超时时各 Rank 上的集合通信元数据或状态不一致。

根据我们在 Meta 集群中调试 NCCL 看门狗超时的经验,几乎所有的 NCCL 看门狗超时错误都是由集合通信不同步(即不匹配)引起的,而不是集合通信缓慢/挂起引起的。这意味着增加超时值并不能解决问题——必须解决导致不同步的原因。

图 3 显示了我们在 Meta 集群中为 4 种不同训练框架(三种用于推荐系统,即 RecSys,一种用于 LLM)观察到的 NCCL 看门狗超时的最常见根本原因分类。在接下来的章节中,我们将解释我们观察到的四大类根本原因:CPU 端问题、GPU 计算内核挂起、集合通信参数配置错误以及网络/硬件问题。

图 3:Meta 内部不同训练栈中观察到的 NCCL 看门狗超时根本原因分布

类别 1:CPU 端问题

在大多数现代模型架构中(没有集中的单一控制器),为了使 NCCL 集合通信成功完成,进程组中的所有 Rank 必须以完全相同的顺序执行相同(或互补)的 NCCL 集合通信。

形式上,对于给定的 NCCL 进程组 G,令 G 代表参与进程组的所有 Rank 的集合。令 CipG 代表在进程组 G 中执行的所有集合通信中,Rank pG 上执行的第 i 个集合通信。那么,对于任意 Rank p, qG,对于所有的 iCipG 必须与 CiqG 相同。违反此预期将导致 NCCL 库在 CPU-GPU 同步期间在违规的集合通信上无限期挂起,最终触发 NCCL 看门狗超时。

如果由于某种原因——与 GPU、网络或硬件完全无关——CPU 最终在某些 Rank 上没有调度集合通信调度了不同的集合通信,那可能会导致集合通信不同步,进而引发 NCCL 看门狗超时。在 Meta 的集群内部,我们观察到这些 CPU 端问题是 NCCL 看门狗超时的主要根本原因(在所有训练框架中占比 >60%)。

我们将 CPU 端问题大致分为以下两类:

  1. CPU 端操作卡死/缓慢
  2. 跨 Rank CPU 执行分歧

CPU 端操作卡死/缓慢

如图 4 所示,如果对于一部分 Rank,CPU 在某些 CPU 端操作(如数据加载、检查点保存或 PT2 编译)上卡死或耗时超过了 NCCL 看门狗超时阈值,那么这些 Rank 将无法在 GPU 上调度集合通信,导致其他已经调度了集合通信的 Rank 触发 NCCL 看门狗超时。

图 4:由 CPU 端缓慢/挂起引起的 NCCL 看门狗超时序列图

此类缓慢的一个显著例子是 PT2 编译,众所周知,编译时间与数据有关,并且在使用编译器缓存和动态形状重新编译时,各 Rank 之间的编译时间差异可能会进一步拉大。如果各 Rank 之间的编译时间差超过了 NCCL 看门狗超时阈值,就可能导致 NCCL 看门狗超时。这个问题直接推动了 PT2 编译器集合通信的引入。

跨 Rank CPU 执行分歧

如果不同 Rank 进入了不同的代码路径,它们最终可能会调度不同的集合通信(图 5)或未能调度集合通信(图 4)。在这两种情况下,某些 Rank 会卡在随后的 CPU-GPU 同步点,并触发 NCCL 看门狗超时。

图 5:由 CPU 执行分歧引起的 NCCL 看门狗超时序列图

根据我们的经验,以下是一些导致 CPU 执行分歧的常见原因:

非对称 PT2 编译

如果模型代码具有数据依赖性,则在 PT2 编译时提供给各 Rank 的数据差异可能会导致非对称编译,从而在各 Rank 上产生不同的编译代码。当这导致编译后的代码中出现不同的 NCCL 集合通信时,就会导致如图 5 所示的 CPU 执行分歧。在启用了 PT2 编译的模型中,这是我们见过的 CPU 执行分歧的常见原因之一。

跨 Rank 数据不平衡或异构性

如果模型或训练框架包含数据依赖的条件逻辑,则各 Rank 之间的数据不平衡或异构性可能导致 NCCL 看门狗超时。我们观察到的一种此类场景是:一个 Rank 比其他 Rank 更早耗尽数据,并最终少运行了一次迭代,导致其更早地跳出训练循环,引发如图 4 所示的 CPU 执行分歧。

不当的异常处理

训练循环中的致命异常通常应该导致出错 Rank 的工作进程被销毁,并且 PyTorch 的错误传播机制会通知其余工作进程执行相同操作,即使它们正在等待 NCCL 集合通信。但是 except 代码块中的代码本质上是特定于 Rank 的条件逻辑,这可能导致 NCCL 看门狗超时在非出错的 Rank上被触发,场景如下:

  • Except 子句在 CPU 上卡住的时间超过了 NCCL 看门狗超时阈值(看起来像图 4)。
  • Except 子句中包含 Rank 间 GPU 同步(例如,另一个 NCCL 集合通信或 destroy_process_group),导致销毁过程与未出错的 Rank 死锁(看起来像图 5)。
  • Except 子句吞掉了异常并继续训练,导致出错的 Rank 要么发起新的 NCCL 集合通信并死锁(图 5),要么卡在 CPU 端屏障或 CPU 绑定操作中(图 4)。

边缘案例:集合通信 GPU 执行重排序

在大多数情况下,集合通信 CPU 调度顺序与 GPU 执行顺序相同。然而,在使用 N-D 并行(如 FSDP)的模型中,当不同进程组将集合通信调度到同一个 GPU 时,如果缺乏适当的同步(即背靠背调度),GPU 通信内核执行顺序在 Rank 之间出现不一致的可能性很小,这会导致如图 5 所示的死锁并引发 NCCL 看门狗超时。为了缓解此问题,NCCL 2.26 引入了一个环境变量 NCCL_LAUNCH_ORDER_IMPLICIT,以强制 GPU 通信顺序与调度顺序完全一致。

类别 2:GPU 计算内核挂起

由于 GPU 执行在单个 CUDA 流中是顺序的,因此 GPU 在执行计算内核时的任何挂起都会阻止 GPU 执行已调度的集合通信,从而导致作业卡在随后的 CPU-GPU 同步点,如下图 6 所示。请注意,症状取决于同步的时间:如果 CPU-GPU 同步发生在计算内核挂起之后但在 NCCL 集合通信调度之前,则表现为图 4 所示的情况。

图 6:由 GPU 挂起引起的 NCCL 看门狗超时序列图

GPU 挂起可能由多种问题引起;我们将此类别指定为由特定内核实现或隔离到特定作业/模型的瞬态 GPU 问题,并将硬件故障引起的挂起描述在下方的第 4 类别中。

类别 3:集合通信参数配置错误

通常,NCCL 集合通信接收一个输入张量并输出一个张量。在大多数情况下,这些输入/输出张量的数据类型和形状在进程组中的所有 Rank 上必须相同。还有一些像 all_to_all_singlebroadcastgather 这样的 NCCL 集合通信,它们对进程组范围内的输入参数有特殊的全局要求(例如,所有输入大小之和必须与所有输出大小之和相同)。PyTorch 无法验证传入的参数是否有效,因此当参数违反这些 NCCL 假设时,NCCL 库将在违规的集合通信上挂起,并最终导致 NCCL 看门狗超时。

我们在 Meta 集群中观察到的一个常见案例是在 all_to_all_single 上。由于其 P2P 实现特性,它要求调用者通过传入输入/输出张量大小分割来定义发送/接收拓扑。在分割无效的情况下——例如 Rank X 预期从 Rank Y 接收的数据量多于 Rank Y 发送给 Rank X 的数据量,Rank X 上的 ncclRecv 将永远阻塞,从而永远无法完成 all_to_all 集合通信。

类别 4:网络或硬件问题

最后但同样重要的是,我们观察到的 20-30% 的超时是由瞬态或持续的网络或硬件问题引起的。

瞬态网络问题(例如链路或端口抖动)构成了此类故障的大部分。瞬态网络问题是极少数无需不同步即可显现的情况之一,其中所有集合通信都已启动但均未完成。然而,对于 Rank 行为不对称的集合通信(如 all_reduce 或 broadcast),我们更常见的情况是看到某些 Rank 已经完成了集合通信,而其他 Rank 仍在执行它们。

有故障的 GPU 硬件是 GPU 挂见的常见原因,其表现与第 2 类超时相同。然而,与第 2 类不同的是,有故障的 GPU 会在多个无关作业中引起故障。诊断需要寻找给定 GPU 的重复故障模式(包括其他 CUDA 错误)或查看硬件信号(例如 XID)。

PyTorch 的诊断方案:飞行记录器 (Flight Recorder)

为了切实帮助用户调试 NCCL 看门狗超时错误,PyTorch 在 c10d 层内实现了飞行记录器 (FR)。当发生超时时,FR 会自动将关键日志信息转储到存储中,使用户能够执行超时后的详细调查分析。

什么是飞行记录器?

FR 是一个进程内、CPU 端的环形缓冲区,在所有进程组之间全局共享。FR 记录与集合通信启动相关的以下重要元数据:

  • 类型: NCCL 集合通信类型(如 all_reduce、all_to_all 等)
  • 状态: 集合通信经历 4 种状态:未调度(即缺失)→ 已调度(来自 CPU)→ 已启动(在 GPU 上)→ 已完成(在 GPU 上)
  • 输入/输出数据类型 (dtype): 与 GPU 计算内核类似,NCCL 集合通信以张量作为输入参数,也可以输出张量。数据类型 (dtype) 指的是输入/输出张量的数据类型
  • 输入/输出大小: NCCL 集合通信的输入/输出张量大小
  • 集合通信调用栈: 调度该 NCCL 集合通信的 CPU 端调用栈(可以记录 Python 和 C++ 调用栈)

集合通信还通过一个在 Rank 参与的每个进程组内单调递增的序列 ID 进行索引。此元数据对于稍后跨 Rank 验证集合通信的使用和顺序至关重要。

Python API 允许用户从环形缓冲区中实时检索 FR 数据以进行流式遥测分析。用户还可以通过写入管道文件(使用环境变量 TORCH_NCCL_DEBUG_INFO_PIPE_FILE 配置)或通过 HTTP 请求手动触发 FR 记录的存储转储。对于本文而言最重要的是,如果设置了 TORCH_NCCL_DUMP_ON_TIMEOUT,当检测到 NCCL 看门狗超时时,PyTorch 会触发立即将 FR 记录转储到存储(默认为本地文件系统,但可扩展)。

FR 转储成功的关键在于确保所有 Rank,甚至包括那些挂起的 Rank,都能转储它们的记录。从历史上看,Rank 可能会在 CUDA 和看门狗线程上同时挂起,导致只能得到部分超时记录。为了解决这个问题,PyTorch 引入了一个辅助 TCP/IP 通道,利用 TCPStore(一种基于 TCP/IP 的键值存储)向所有 Rank 广播超时信号。一个专用的监控线程轮询 TCPStore,并在收到信号后触发 FR 转储。详情请参阅图 7。

图 7:显示飞行记录器如何转储追踪记录的序列图

FR 转储在每个进程组级别触发。在复杂的 N-D 并行场景中,用户管理多个进程组,每个组都有自己的看门狗和监控线程。为了缓解竞争条件,只有默认进程组(其大小与 world size 相同)的监控线程被指定用于检查信号并启动转储。所有其他监控线程被指示短暂休眠(例如 1 分钟),以留出足够的时间让转储完成。PyTorch 飞行记录器依赖于尽力而为的本地转储,因为系统在超时期间本质上是不稳定的。事实证明,这种设计非常有效,在 Meta 集群内部实现了近 100% 的飞行记录器全量转储率(即所有 Rank 在销毁前完成了转储)。

在 Meta 集群内部,FR 超时转储已在大多数训练栈的所有作业中启用。为每个作业启用 FR 的微小开销,与在发生 NCCL 看门狗超时时调试所带来的遥测价值相比,是完全值得的。作业终止后,外部编排系统会从所有 Rank 聚合 FR 转储,并对其进行后期离线处理(描述如下)。

注意:在分析用于 NCCL 看门狗超时的 FR 转储时,优先使用超时后的离线分析而非实时分析。原因在于,超时期间 Rank 之间的协调非常有限(除了信号广播)——系统已经处于碎片化状态,不能指望所有 Rank 都能保持存活。如果即使有一个 Rank 死亡,Rank 间的协调最终都会失败。此外,让线程盲目等待一段较长时间会浪费宝贵的训练资源。过去使用 desync-debug 等工具的经验表明,这会在大规模模型训练中导致性能回归和扩展性问题。因此,我们做出了架构决策,即先转储原始 FR 记录,然后再进行彻底分析。

如何利用 FR 转储进行超时后分析?

为了有效地利用 FR 数据调试 NCCL 看门狗超时,首先需要按调度顺序和集合通信元数据(序列 ID、集合通信类型等)对来自所有 Rank 的集合通信记录进行对齐,并聚合每个进程组 (PG) 内的集合通信记录。然后,可以检查每一组记录(对应于在特定 PG 内执行的单个集合通信),以识别不匹配——即哪些 Rank 未能调度该集合通信(即缺失的 Rank)或与同一 PG 内的其他 Rank 存在分歧。

这些集合通信的不匹配使我们可以精准定位上述深度解析中描述的哪一类是导致 NCCL 看门狗超时的根本原因:

  • 类别 1:CPU 端操作卡死/缓慢通常表现为缺失的 Rank集合通信状态不匹配,而跨 Rank CPU 执行分歧可能表现为缺失的 Rank集合通信类型、调用栈以及偶尔数据类型 (dtype) 的不匹配
  • 类别 2:GPU 挂起最常表现为缺失的 Rank集合通信状态不匹配,具体取决于 CPU-GPU 同步发生在 NCCL 集合通信调度之前还是之后。
  • 类别 3:集合通信参数配置错误通常表现为集合通信数据类型 (dtype) 或大小不匹配
  • 类别 4:硬件故障通常与 GPU 挂起表现相同(缺失的 Rank集合通信状态不匹配),但网络问题可能表现为没有不匹配(集合通信全部处于 started 状态)或集合通信状态不匹配。通常,这些必须通过寻找涉及相同网络/硬件组件时的重复故障模式来进行诊断,因为它们可能是瞬态或灰色故障。

PyTorch 提供了 fr_trace 诊断工具,可以根据来自所有 Rank 的 FR 转储执行上述对齐和聚合操作。fr_trace 枚举所有 NCCL 集合通信的不匹配项,输出参与这些集合通信的 Rank、集合通信元数据以及一个具有代表性的参与 Rank 的集合通信调用栈。

在 Meta 内部,我们通过可视化跨 Rank 和进程组的分布式集合通信活动对 fr_trace 进行了增强,图 8 展示了其原型。该可视化由 fr_trace 生成的对齐集合通信记录驱动,我们在后期处理期间将其写入表格存储(包含集合通信元数据)。

X 轴上的连续列代表 FR 记录的全局、与 PG 无关的集合通信调度顺序,Y 轴上的每一行对应全局 Rank 和进程组的独特组合(如果参与多个 PG,Rank 可能会多次出现)。单个单元格对应单个集合通信,并经过颜色编码,使得 {集合通信类型, 调用栈} 的不同组合具有不同的颜色。选择单元格或单元格组会加载其调用栈的冰柱图(图 9)。

我们已发布了一个概念验证 PyTorch PR 此处,演示了如何使用 OSS 库进行后期处理和可视化。

图 8:Meta 内部用于在调试超时时检查 NCCL 集合通信的可视化原型

图 9:选择多个单元格时生成的调用栈冰柱图原型

这种可视化在调试 NCCL 看门狗超时方面非常强大。它便于快速扫描 Rank 内部和跨 Rank 的集合通信活动历史,颜色编码使其能够轻松直观地识别不匹配的集合通信。快速比较不匹配集合通信的调用栈的能力,对于定位 CPU 端分歧的源头特别有用。

按 PG 分组 Rank 在调试使用 N-D 并行(如 FSDP)的作业时特别有帮助——该可视化使得在检查每个 PG 内部的集合通信活动和检查给定 Rank 参与的所有 PG 跨度的集合通信活动之间切换变得容易。事实证明,它对于调试跨 PG 集合通信调度竞争条件的情况非常有效。

注意:为了进行有效调试,FR 需要与类似的 CPU 主线程调用栈的分布式可视化结合使用,因为了解 CPU 在超时时正在做什么(CPU 端操作?CPU 端屏障?CPU-GPU 同步点?异常处理?)对于三角测量超时属于哪一类是必要的。虽然 PyTorch 目前不提供任何此类诊断工具,但底层遥测数据可以使用 py-spy 等 OSS 工具收集。

基于 Meta 工作负载的案例研究

案例一:CPU 执行分歧

在使用分布式训练扩展推荐系统时,一个常见的挑战是管理跨 Rank 的 CPU 执行分歧。我们在指标计算过程中看到过此问题出现,其中来自多个 Rank 的结果必须使用集合通信原语进行聚合和记录。通常,每个 Rank 都应参与这些 NCCL 集合通信调用,以确保准确和同步的指标聚合。然而,如果实现不够健壮,一些 Rank 可能会无意中跳过这些调用——无论是由于条件逻辑、提前退出还是其他代码路径分歧。

发生这种情况时,参与集合通信操作的 Rank 成员身份变得不完整。参与的 Rank 最终可能会无限期地等待来自已选择退出的 Rank 的输入,导致训练停滞并最终引发 NCCL 看门狗超时。下图 10 说明了这一现象:最后一列显示了代码路径中的分歧,一些 Rank 执行指标聚合集合通信(黄色),而其他 Rank 直接进行下一个 NCCL 操作而未参与指标聚合(黑色)。

图 10:CPU 执行分歧的示例集合通信可视化

在一次此类分歧实例中,通过分析分歧的集合通信和 CPU 调用栈,我们将根本原因隔离为训练代码中存在错误的条件逻辑,导致满足某些数据相关条件的 Rank 跳过了指标计算。

案例二:NCCL 集合通信输入配置错误

GPU 到 GPU 的通信挂起在大规模分布式训练(尤其是推荐系统 (RecSys))中是一个众所周知的挑战。all_to_all 集合通信操作在 RecSys 工作负载中被广泛使用,因为分片嵌入表需要输入和池化输出都被收集并重新分布在所有 Rank 上。这确保了每个 Rank 都能为其批次接收到一组完整的嵌入,从而实现高效的并行处理。使用 all_to_all 的一个关键细节是输入和输出分割的指定。如果这些分割没有显式提供,PyTorch c10d all_to_all_single API 期望输入张量在所有 Rank 上具有相同的长度。此要求允许底层实现均匀地重新分配元素,从而保持同步并防止死锁。

在一次内部 RecSys 场景中,我们遇到了 GPU 卡在两个不同的 all_to_all 调用中的情况:一个具有均匀分割,另一个具有不均匀分割。乍一看,所有 Rank 似乎都在执行相同的 all_to_all 集合通信,这在调试期间引起了混淆。我们最初的假设集中在为什么会触发不同的 all_to_all 变体,但这一探究方向并未揭示根本原因。当我们检查 FR 为 all_to_all 集合通信提供的调用栈时,突破出现了——我们发现不匹配的 all_to_all 变体源自两个不同的、连续的集合通信操作。由于输入大小配置错误,一些 Rank 提前完成了第一个 all_to_all 并继续进行下一个,而其他 Rank 仍卡在第一个集合通信中等待。这种错位导致了通信挂起,因为 Rank 们在不同的集合通信操作中相互等待。

未来工作

我们计划在以下领域投入资金,以推动飞行记录器的未来发展和集成:

  • 与 TorchComm 集成: 我们计划将飞行记录器集成到最近宣布的 TorchComm 库中。
  • 对主机端优化的支持: 随着加速器的发展和 CUDA 图等主机端优化变得普遍,我们必须验证并扩展飞行记录器的设计,以确保与这些技术的组合性。
  • 引入额外的后端: 由于飞行记录器已经被设计为通用的,我们计划将其范围从 NCCL 扩展到其他后端,例如 MTIA 和 Gloo。

致谢

我们要感谢 Meta 的以下合作者为这项工作所做的各种贡献、反馈、支持和指导:Tristan Rice, Will Constable, Zachary DeVito, Shuqiang Zhang, Chirag Pandya, Iris Zhang, Yue Dong, Ke Wen, Chao Chen, Chien-Chin Huang, Zack Cao, Atul Jangra, David Lai, Hang Qi, Jayesh Seshadri, Shai Duvdevani, Haibo Chen, Shyam Sundar Chandrasekaran, Karthik Kambatla。