这是用户在 2024-10-1 7:19 为 https://imbue.com/research/70b-infrastructure/ 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?


从裸机到 70B 模型:基础设施设置和脚本

 2024 年 6 月 25 日


我们衷心感谢电压公园、戴尔、H5、NVIDIA 对我们合作的无价贡献,以及帮助我们建立集群的大力支持。特别要感谢电压公园的奥赞、梅丽莎、戴维、迈克尔和大卫,他们对整个项目的持续支持。搭建这样一个规模的集群是一项巨大的挑战,没有他们的支持,我们不可能完成。


这是关于我们在训练 70B 模型时的系列文章中的第二部分。我们涵盖了设置基础设施、进行评估以及超参数优化的内容。

 介绍


在几个月内,通过一个由研究人员和工程师组成的较小团队,我们从零开始在我们的基础设施上训练了一个参数量为 70B 的模型,这个模型在与推理相关的任务上超越了零样本 GPT-4o。


今天,我们分享了一个从搭建初始集群到安装操作系统,再到自动从训练中遇到的错误中恢复的端到端指南。在每个步骤中,我们详细描述了我们遇到的挑战以及我们如何解决它们。除了我们的学习成果,我们还发布了我们开发的一些基础设施脚本,以确保健康主机,以便其他团队更容易为自己的模型训练创建稳定的基础架构。


在我们的详细流程中,我们正在发布:


  • 主机级健康检查:脚本用于确保给定主机未因已知错误而出现故障

  • 一个改进错误和暂停时日志记录的 NVIDIA 集成通信库 (NCCL) 版本的补丁

  • 为确认 GPU 是否能够分配大张量并执行标准操作进行压力测试

  • 网络测试以确保给定机器上的 GPU 能够互相通信(通过 NVLink)以及与其它机器上的 GPU 互相通信(通过 InfiniBand)

  • 一个脚本,用于解析统一布线管理器(UFM)事件日志,检查相关事件,确定应禁用的网络端口

  • 生成 InfiniBand 网络完整烧焊工作负载的脚本,旨在激活所有可用的链路,以进行性能测试


在整个过程中,我们工程团队与位于电压公园的合作伙伴一起,准备了集群用于生产用途。整个过程包括:


  1. 为个人机器提供服务
  2.  配置 InfiniBand

  3. 确保完全健康机器的人工智能

  4. 诊断常见的训练问题

  5. 改善基础设施工具的改进工具


这些步骤在下面的描述中得到了更详细的说明。


背景:这是怎么工作的


我们的计算目的就是为了快速实验大规模语言模型。为了做到这一点,我们需要大量的快速 GPU,这些 GPU 能够以高速互相通信。


这篇帖子专注于一个集群,该集群拥有 4,088 台 H100 GPU,分布在 511 台计算机上,每台计算机有八个 GPU。由于一些连接需要为统一网络管理节点保留,这些节点管理 InfiniBand 网络,所以有 511 台计算机配备了 GPU。在 511 台 GPU 配备了计算机上,每台 GPU 直接连接到一个可以同时以每秒 400 Gbps 的速度在 InfiniBand 网络中发送和接收数据的 ConnectX-7 卡,通过自己的 ConnectX-7 卡与任何其他 GPU 进行通信。


我们的 InfiniBand 网络拓扑被称为“完全非阻塞”,因为理论上,每块 GPU 可以同时以最大速率与另一块 GPU 进行通信。这得益于三层架构的 InfiniBand 网络:当 InfiniBand 交换机按照正确的方式连接时,这种高吞吐量在整个网络中得以实现。以下是 InfiniBand 网络的概述:

 点击放大。


使用我们的集群进行高性能训练意味着,每件设备--InfiniBand,以太网,GPU,以及节点本身--都必须几乎完美地协同工作。即使是一个连接,即使是微小的不准确,也可能拖慢整个训练运行。


本文后面的内容详细描述了实际达到一切运行完美状态的过程,并确保它保持这种状态的方法。


过程:如何从裸金属到完全运行的集群是怎么回事


为单台机器提供服务


通过管理网络与集群建立初始以太网连接后,我们获取了基板管理控制器(BMC)的访问权限。BMC 是一种专门的服务处理器,远程监控主机系统,通常连接到一个单独的网络。它允许我们像真的在场一样与每一个机器交互,并提供了硬件健康、BIOS 设置和电源管理的额外 API。


有了这些组件,我们就动手准备搭建集群。


步骤 0:获取一台机器进行配置


我们首先使用 iDRAC(戴尔的基板管理控制器)在单服务器上安装了 Ubuntu 22.04,这将为其他一切的设置提供基础。除此之外,iDRAC 允许我们在本地计算机上通过浏览器挂载并从 ISOM 光盘启动系统,提供虚拟控制台。这在这一过程中,我们希望是唯一的手动安装步骤。


步骤 1:在每台机器上安装操作系统


当患者零得到妥善处理后,我们开始安装 Ubuntu 的 Metal-as-a-Service (MAAS) 软件,以帮助为剩余的服务器进行部署。使用预启动执行环境协议(PXE)启动,以及自动化 iDRAC 工具,我们指示每台机器从网络启动,并配置 MAAS 以响应 PXE 启动请求。 在进行初始网络启动时,服务器通过动态 IP 分配协议(DHCP)接收了一个 IP 地址(由 MAAS 提供),并且不需要在本地硬盘上安装任何东西,就可以获得一个初始的内核。在这种仅有的环境下,自动使用它来完成持久化操作系统安装。理论上,我们会在第一次启动时等待,一切都会顺其自然地解决。 然而,在实践中,MAAS 与 BMC 的集成并不是可靠的,所以我们提前使用 iDRAC API 收集每台机器的 MAC 地址(一个唯一的物理硬件标识符)。


在训练过程中,MAAS 是整个堆栈的一个总体上可信赖的组成部分。然而,在开始时,我们遇到了一些起始时特定于我们设置的困难,例如,在最初几个配置期间,时间戳如此之远,以至于 HTTPS 证书验证问题阻止了通过 apt 安装任何内容。 相关地,因为 MAAS 服务器不得不负责许多责任(DHCP 服务器,主机名到 IP 地址解析的 DNS 服务器,主机与官方 Ubuntu 包服务器之间的 HTTP 代理,NTP 服务器,云初始化配置管理,以及连接主机 MAC 地址到 IP 地址以连接到主机名到自定义元数据的地面真相数据库),我们难以追踪问题到根本原因组件。 这增加了 MAAS 的发布生命周期的学习曲线,因为它被设计用来处理管理绿色场部署的复杂性,以及节点的逐步迁移和各种调试/不健康中间状态的逐步迁移。


步骤 2:诊断故障设备


在设置大型 GPU 集群方面,正如通常的情况一样,我们发现大约 10%的机器无法启动,主要是由于服务器的物理问题。我们遇到的问题包括:未连接或错误配置的以太网电缆,iDRAC 硬件问题,损坏的电源供应单元,坏的 NVME(非易失性内存表达)驱动器,内部线缆缺失,网卡或 GPU 无法显示。 我们自动化检查这些问题,将一些设备返回给戴尔再次进行测试,并为数据中心的工作人员提交了适当的工单。自行处理集群的设置的一个优势是,在等待其他设备进行维护时,我们能够立即使用那些健康的设备。


步骤 3:最小可实现的可观察金属观察值


我们就在每台服务器上设置了以下内容:


  1. Docker(用于更轻松地运行服务和训练任务)

  2. 数据中心的 GPU 驱动程序

  3. Prometheus 节点出口器(用于以持续流的形式导出硬件/操作系统指标)

  4. DCGM 指导者 (NVIDIA 为 GPU 状态 / 钟表 / 利用率从额外指标)

  5. 所有非操作系统驱动的 RAIDZ ZFS 池(这使得机器能够在单个驱动器出现故障时存活,同时还提供了免费的透明压缩,这对纯文本数据集和重复的日志特别有帮助,这通常使我们能够比我们本应能够使用的空间多使用大约 10 倍的空间)


我们随后进行了基本的 GPU 故障诊断,以确定 GPU 是否大多功能正常——那些不正常的部分通常会在几小时内出现硬件问题。


在这段时间里,我们在并行安装软件包时遇到了一些带宽瓶颈。这也是我们第一次收到数据中心部署中各种组件高温警报的第一批热问题。这第一波热问题主要通过固件更新得到了解决。


步骤 4: 单节点 GPU 训练


下一步是确保每台机器都能够独立处理真实的 GPU 负载。许多无法做到这一点,原因在于多个问题:


  • 与 GPU 相关的错误,主要通过重新插入卡到其插槽中来解决:物理上从承载 200 磅服务器的机架中滑出,移除覆盖板和 GPU 之间的所有电缆,然后从 GPU 中取出并重新插入,然后再更换所有电缆和重新安装服务器。

  • 多次杂项故障影响了单个数字主机。戴尔通过固件升级帮助我们解决了这些:

    • NVMe 硬盘没有显示为故障,但触摸时完全锁住了整个机器。

    • 在 Linux 中,出现了一个乱序的硬盘,这使得 MAAS 等系统难以分辨,导致操作系统被安装在了错误的硬盘上。

    • 错误的温度读数,导致风扇持续以 100%速率运行。这在一定程度上是由使用坏的 NVIDIA 驱动程序引起的,我们通过降级到之前的驱动版本来解决这个问题。

    • 动态频率缩放以防止 CPU 过热,限制到 2 GHz 的活跃核心。

    • 直接的 GPU 与 GPU 之间的通信(GDR,或称为 GPU Direct RDMA 连接的 Peer Memory Client)无法成功应用。

 配置 InfiniBand


步骤 0:安装 UFM


InfiniBand 的一个优势在于其集中式的架构,因为它有一个大脑来运行整个网络。因此,我们在网络交换机的 320 个实体中只处理了一个实体。我们的第一项任务是找出哪个交换机连接到哪个机器,然后将其与布线图进行关联,并根据物理位置重命名交换机。


步骤 1:重新连接电线


最初,UFM 无法检测到 320 网络交换机,更不用说预计在网状布阵中存在的所有主机了。在与我们的数据中心合作伙伴交谈后,我们确认了这些交换机已经通电并以常规方式连接,但却仍未被检测到。在检查网络布线清单后,我们注意到网状布阵的顶层设计存在问题:我们实际上有八条相互独立的网络,没有共同的路由路径。 在重新连接线路后,我们添加了检查,以确保所有物理连接都与新的设计相匹配。


步骤 2:发布 10000 次温度警报


在解决了物理接线问题之后,UFM 成功与所有网络交换机建立联系。然而,几乎每台交换机端口都报告了异常高的温度,有时高达 70 摄氏度,尽管它们还未传输数据。我们发现问题源于同一网络机柜中的交换机间有空隙,导致热空气循环到前部。 我们数据中心的合作伙伴帮助我们迅速诊断了问题,并开发了合适的 workaround。


步骤 3:收到 1800 个警告


许多港口也表现出高错误率或在工作和故障状态之间摇摆不定,这种情况被称为“摇摆”。这些问题只有在港口被主动使用时才会出现,因此主动检测证明具有挑战性,因为整个网络结构由 10,000 个连接组成,且冗余度很高。我们的数据中心伙伴帮助清理和重新安装告警端口,并在等待替换设备时关闭剩余的告警收发器。


尽管 InfiniBand 非常耐受硬件故障,但一旦约 10% 的布线出现故障,采用自适应路由等功能就无法可靠地工作,无法绕过随机丢弃的链路。


在这段时间里,我们能够进行多节点训练运行,使用 100 到 200 台机器。我们的方法基本上是随机的:我们有时会在随机的节点上启动训练,观察它们的性能,然后尽量保持尽可能多的这些节点运行。这种方法让我们找到了一个可靠的 InfiniBand 网络子集,但证明起来有些困难,因为每次改变训练中使用的节点集,InfiniBand 的默认连接集就会发生变化。


第 4 步:烧坏 InfiniBand,放烟花,照亮天堂


为了更高效地诊断 InfiniBand 问题,我们为整个集群设计了一个专有的工作负载,该工作负载集中在同时将尽可能多的数据通过集群中的所有端口传输到整个布线中。这与在集群上运行一个大型的全部并行减少工作负载不同,这将利用 NCCL 来优化单个节点内通信,通过使用 Server PCIe Module (SXM) 插槽中的 GPU 通信来实现。


相反,我们采用了暴力的手段,成功了。UFM 开始发送关于数据传输超过理论容量 97% 通过大多数端口的警报,一些交换机临时崩溃。在一天结束时,所有未关闭的端口都被认定足够强大可以继续,而其余的则被关闭或转交给后来维修。


步骤 5:GPUDirect RDMA


为了使 GPU 之间无需 CPU 负担就能通信,我们启用了一个功能叫做 GPUDirect RDMA,这使得可以直接与 InfiniBand 网络卡通信。这涉及了两个关键步骤:


  1. 启用额外的内核模块

  2. 确保 PCIe 访问控制服务 (ACS) 被禁用,以防止立即发生挂起


第 6 步:培养黄金服务器集


在使用最新硬件的 GPU 集群中,一个大致的规则是:每周大约有 3% 的机器会出故障。


然而,却有一个关键的细微差别经常被忽视:并非每台机器都有相同的 3%的故障概率;相反,一小部分不满意的机器会以不同方式反复出问题,直到它们得到妥善的修复。这强调了拥有相同织物上的大量机器的优势。相反,我们可以在大型训练运行中,随机选择机器来玩“扫雷战”,而我们可以将重点放在培养一组已知可靠的或“黄金”的机器上。

 步骤 7:维护


InfiniBand 维护主要涉及响应 UFM 报警,更换故障的电缆和收发器,以及偶尔诊断更复杂的错误,如故障的交换机。大规模的回归通常由以下两个因素引起:


  1. 固件升级,特别是仅应用于集群的一半,这可能会损坏 UFM 状态,并对所有 InfiniBand 交换机的 UFM 启动。

  2. 同时重启同一台 GPU 服务器上的多个重启,这可能会向 UFM 状态发送大量更新,同样需要 UFM 服务重启。


确保机器完全健康


在整个过程中,我们发现了许多个体机器可能失败或减慢训练运行的方法。其中许多故障模式并不立即显而易见,因此我们编写了多项健康检查,以确定哪些主机健康到足以用于训练。我们已经在这里发布了这些代码。


注意,其中许多健康检查针对的是我们的运行时环境,不一定与基础硬件相关,或者修复或自动化它们极为简单。这是设计的一部分:为了实现让我们的机器准备用于训练的整体目标,我们希望有一个单一的入口点,他会回答“是”或“否”,并且会掩盖任何数量的“简单”细节。

 GPU 健康检查


我们检查了我们是否拥有了正确的 GPU 数量,是否启用了 ECC(纠错编码)检查,以及是否没有 ECC 错误。我们还检查了连接 GPU 之间的 NVLink 架构,以确保其运行正常且无错误。


磁盘空间健康检查


我们检查了主机的磁盘空间使用率没有超过 95%。

 Docker 健康检查


我们检查了 Docker 是否能运行带有 GPU 的容器(即,NVIDIA 容器运行时工作正常),并且所有与监控/剖析相关的所有 Docker 容器都已启动,并且拥有正确的主机权限。

 Dmesg 健康检查


我们检查了 dmesg 中没有出现硬件 Xids 或 SXid 错误(由 NVIDIA GPU 或 GPU 间 NVIDIA 交换机抛出的错误)。我们还读出了所有 dmesg 日志行,以验证它们都被分类在了“通常/预期日志行”的列表中。

 iDRAC 健康检查


我们在机器上检查了 iDRAC 错误,忽略了非致命错误消息。这是特定于我们的戴尔机器,不是我们开源的健康检查的一部分。

 磁盘健康检查


我们检查了 zpool 是否已挂载,Docker 是否正确连接到它,并且可以徒手触碰它,而不会让 CPU 锁住。

 InfiniBand 健康检查


我们检查了是否有增加的 InfiniBand 错误率以及/或过时的驱动程序固件。


我们在机器上检查了 NVLink 错误。从经验出发,这看起来并没有导致训练失败,但可能会影响速度。

 德国健康检查


我们检查了机器上是否启用了 GDR。

 VBIOS 健康检查


我们检查了显卡的 VBIOS 版本以及 H100 基板固件是否更新。

 氟喹诺酮评估


我们使用燧石和 hca_self_test 来验证我们已经安装了正确版本的 Mellanox OFED 驱动程序、卡固件和收发器固件,并且这些驱动程序和固件是正确地编译的以与 NVIDIA 驱动程序兼容。

 PSB 健康检查


我们查询了 PCIe 设备,以检查 GPU、PSB(PCIe 交换机总线)和网络卡之间连接的速度和宽度是否符合预期。我们还检查了交换机固件是否当前版本。这个脚本是由 Dell 开发的,而不是 Imbue,所以我们目前无法分享它。


除了这些快速的健康检查,我们还进行了几个更具挑战性的健康检查,包括:


  • 通过 PyTorch 初始化矩阵计算,并测量 NVLink 总线带宽和 GPU 计算速度和内存。我们将正确设置 GDR 标志以测试 InfiniBand 和 NVLink。

  • 使用 ib_write_bw–use_cuda 通过 IB 卡发送数据并测量 PCIe 和 InfiniBand 卡的带宽。我们为此运行了更长的时间(大约 15 分钟)以确保我们抓住了易变的 InfiniBand 网络连接。

  • 运行一个多节点诊断运行,以检查 NCCL 初始化能力,以及它是否会随机卡住。如果卡住了,我们的分叉的 NCCL 代码会添加额外的日志记录。这可能需要 12 到 24 小时才能检测到问题,因此我们通常只在新节点上运行这个检测,或者怀疑存在某个问题时。

  • 检查 DCGM 导出以查找任何 GPU 钟速限制事件(不包括预期的 gpu_idlepower_cap )。多节点训练同时运行所有 GPU、InfiniBand 卡和 CPU 和磁盘是最好地执行这些功率事件的方法。


诊断常见的训练问题


一旦硬件开始正常工作,就开始训练的时间到了。


在这一部分,我们分享了我们通过在我们的集群上运行大规模语言模型训练任务过程中所观察到的一些具体调试步骤和见解。

 启动时崩溃


在某种程度上,遇到这种错误是最理想的,因为理论上它很容易重现并迭代。


我们首先检查了我们是否正在使用正确的版本、配置和环境变量。虽然简单,但发现确保训练启动时可以重复重现并且易于检查至关重要,尤其是在中间抽象,如 Docker 图像缓存或不透明的密钥配置,可能会使情况变得复杂。


另一个基本的检查是确认所有的机器都在线,以及产生的堆栈跟踪或日志能够容易地汇总和检查。我们使用了一个 Loki, Prometheus,和 Grafana 的堆栈,但任何合适的日志汇总或跟踪的 SaaS 都是合适的。由于这些运行的同步和分布式性质,往往在引发第一个错误时,会触发一系列不相关的错误。 在这里,健康检查也帮助立即检测出明显的问题,如损坏的硬盘或缺失或无效的 GPU。


我们构建了一个系统来自动在失败时重试,这使得日志和错误汇总的重要性更高,以避免在不同重试中混淆错误。我们遇到的一些常见错误包括:


  1. Forward order differs across ranks: rank 0 is all-gathering 43 parameters while rank 1228 is all-gathering 1 parameters 这样的错误。我们发现这是 PyTorch 完全分片数据并行(FSDP)实现中的一个特性,可以通过重新启动来解决。

  2. GPU 内存溢出(OOM)错误,看起来像是 CUDA out of memory. Tried to allocate … 我们通过重新检查我们的配置和代码,以及回滚任何可能由于在启动时使用不正确的 PyTorch 设备规格造成 GPU#0 过度使用而引起的最近的代码更改,来解决了这些问题。

  3. CPU/RAM OOM 错误,这些错误在错误日志中不易被发现,并通常在宿主机外部 Docker 容器中通过 dmesg 日志检测到。我们通常看到它们以 CalledProcessErrorConnectionError 的形式出现,当 OOM 杀手触发时,被 forked 进程或网络伙伴被回收。我们更倾向于当从 dmesg 中检测到 OOM 杀手触发时,就只失败健康检查并重启整块设备。 我们还检查了我们的代码路径是否足够手动收集垃圾(见以下关于如何禁用其内容)和没有意外尝试执行计算或将张量移动到 CPU 上。


训练中途突然崩溃


第一项任务是自动化系统,这些系统会重新运行所有诊断健康检查(参见上文部分),然后自动重启运行,以避免运行在健康主机上。我们遇到了一些随机的硬件故障,包括 Xid 和 SXid 错误,这些错误可能导致运行被无意义的 Python 栈跟踪崩溃。一些实例,如行重映射,可以通过重启来恢复。其他实例,如不可纠正的 ECC 错误,通常需要硬件维护或更换配件。


此外,我们观察到由特别畸形的训练数据引起的崩溃。例如,当语料库中的单篇文档规模非常大时,在 GPU 或 CPU 上可能会出现 OOM 错误。为了防止这种情况,我们使用了一个完全确定性的数据加载器,这使得每一起崩溃都可以通过与 epoch 或步数的关联来重现。我们发现,禁用数据加载或替换假数据(如全零)有助于确认数据是否真的是根本原因。


最后,通过任何适当的方法进行指标聚合,记录网络和一般节点健康状况的信息也是很有帮助的。比如,以太网突然断开或者磁盘空间耗尽等状况可能不会显示为有用的错误信息,但可以通过收集的数据进行轻松关联。


与无堆栈跟踪信息(可能随后是超时)一起进行交互(可能随后是超时)


这些类型的问题非常让人困扰,因为缺少了有用的线索,而且它们往往难以可靠重现。


最难忘的类型通常以错误消息如

Watchdog caught collective operation timeout: WorkNCCL(SeqNum=408951, OpType=_ALLGATHER_BASE, … , Timeout(ms)=600000) ran for 600351 milliseconds before timing out


同时出现在训练运行的所有 GPU 作业中。


这意味着一个或多个主机未能完成一个 NCCL 操作,甚至出错,导致所有其他主机在特定张量操作上同步阻塞,直到 NCCL_TIMEOUT 达到。不幸的是,NCCL 库的性质使得找到具体主机(s)的难度非常高。


我们在 NCCL 库中进行了些日志更改(见我们的分支),以更好地展示崩溃时正在传输的消息或操作,从而识别出哪个是主机或 GPU 能够阻止运行。


请注意,为了识别行为不端的主机,我们经常需要确定哪些主机没有产生特定的日志消息。缺乏这些日志消息表明该主机上的工作负载是慢速的或已崩溃。


其他没有帮助的错误消息情况下未表现出反应的情况通常可能与硬件相关的问题有关,例如前面所提到的 Xid/SXid/ECC 错误导致 NVIDIA 驱动程序或 NVIDIA docker 连接驱动程序卡死。为了区分 NCCL 卡住与驱动程序卡住以及 Python 代码中的竞态条件或死锁,我们使用了包括 Py-Spy 和 GNU 项目调试器(GDB)在内的工具来实时调试遇到卡住的进程。 使用这种方法,我们能够解决一个特定的配置错误,该错误导致在某些主机上,由于 Python 线程配置中的错误,我们无法正确启动八台多线程的 NCCL GPU 进程,这些主机在预 PyTorch 初始化代码中出现了竞争条件。


衡量训练减速(通过 MFU)的情况


缺乏仪器可能使这些类型的问题比之前的类别更 frustrating。除了分解 Py-Spy,堆栈追踪检查,和 GDB,我们还启动了 NVIDIA Nsight 和性能监控工具来帮助,其中一些在高度分布式设置中使用起来很困难。


不幸的是,通用的放缓或低于先前展示的模型倍频利用率(MFU)可能会由多种原因引起。


首先,检查配置、代码和环境变量的双倍检查证明是有帮助的。我们经历了运行错误的模型,错误的批次大小,错误的 UFM 或 NCCL 设置,错误的 CUDA_DEVICE_MAX_CONNECTIONS ,所有这些都导致了不理想的表现。


我们也发现测量瞬时(即每批次)的 MFU 而不是平滑或窗口平均值是有用的,因为预平滑的 MFU 曲线常常帮助我们诊断问题的类别。问题包括:


训练立即开始时,最低的感染微粒单位(MFU)(不到预期值的 1/10)非常低,且保持稳定


这通常是因为 InfiniBand 网络硬件问题,如在 T2 或 T3 层的死开关。它也可能由 GPU 和网卡之间的硬件问题引起,通过 dmesg 显示 PCIe x16 lanes limited by …


训练立即从预期的 MFU 的 30%开始,保持稳定


这可能是由于拥有一台主机设置不正确的 GDR(NVIDIA 互连内存)或错误的 GDR 环境变量所导致的。


训练立即从预期的 MFU 的 60-80%开始,保持稳定


最常见的情况是,这通常是由于衰减的或故障的 InfiniBand 网络接口,特别是如果某个特定的 GPU 有一个故障的关联的 InfiniBand 网络适配器,导致 NCCL 尝试将流量路由到本地 NVLink,而使用同一主机上另一 GPU 的网络适配器。它也可能由 CPU 限速引起,需要对特定主机的 BIOS 设置进行微调。


单批次突然急剧下降(下降了 10 倍)的现象经常发生


这很可能与检查点相关,或者是评估的一部分——这可以通过与 epoch 或 step 计数进行比对来验证。令人讨厌的是,这会导致如果只设置自动警报来触发 MFU 异常,会产生许多假阳性。


突然出现的单批次骤减(约 10 倍),这些骤减随机发生,几乎不常见(每 15 分钟发生一次),而随后立即恢复到良好的 MCFU 状态


这似乎是由在运行中调度在其中一个主机上 CPU 密集型的工作负载所导致的最常见。而非通过构建性能分析工具来识别特定的主机,我们发现用 PID 粗略监控 CPU 使用率更为简单。这可能也与间歇性较差的网络性能有关,例如数据加载瓶颈。我们使用了指标监控,并添加了 Python 代码定时日志来追踪数据加载、检查点和任何非 NCCL 代码,效果相当可靠。


MFU 图随着跑动的进行逐渐向下弯曲,但在任何重启后都会恢复到 100%


理论上,这可能是由于开关上的热积累造成的,但我们从未观察到这种现象。相反,我们使用 Python 和 NVIDIA 诊断工具来确定降级似乎是由于自动垃圾收集的结果。


在调试这些延误时,我们注意到一个模式,即通过率的周期性下降,几乎看起来是确定性的。随着训练运行的进行,这些下降影响了分布式操作的逐渐增加百分比。这导致了我们对这些下降可能与自动垃圾回收有关的假设,我们通过进行性能分析和测试进行了验证。 当我们关闭了自动垃圾回收并将其设置为在所有主机上以特定间隔自动执行时,这些吞吐量“泄洪”不见了。


我们使用了一种同步分布式训练算法,FSDP,这基于 ZeRO-3。在阻塞操作期间,一个运行垃圾收集的单个工作进程可能会减缓其他工作进程。在数百个工作进程中,这可能会导致显著的减慢。


好的开始,然后突然下降(预期的 70%)并持续在高频(每 15 秒一次)


我们观察到这与 NVIDIA GPU 的 "clock throttle 原因" 相关,这些原因我们通过为 NVIDIA DCGM 设置正确的设置来收集。热问题(GPU 温度或损坏/降级的主机冷却风扇)或电源供应故障导致了这一点。 此外,我们的某些使用特定电源硬件的访问者在我们同时将所有 8 块 GPU 利用率和 8 个网络适配器 InfiniBand 利用率,以及 CPU/RAM/disk 全部启用到最大时,遇到了电压问题,但只有在所有这些都使用时——通常只是在实际训练过程中。


表现良好,但比平常“嘈杂”(高频白噪声方差在预期的 MFU 90% 到 100% 之间)


这也是与 InfiniBand 硬件相关的,但通常由于网络中较高位置的中等度衰减或摇摆的链路,而不是在不太冗余的主机到 T2 层。


不幸的是,许多这些问题并不容易归咎于特定的主机,并且与 InfiniBand 相关的问题尤其难以捉摸,因为 InfiniBand 交换机技术具有拓扑感知的特性。在 InfiniBand 滚动树设计中,InfiniBand 偏好相邻主机,而 UFM 可以通过路由方式导致异步链路速度。


这里有一个快速的总结/流程图/ sanity 检查清单,用于调试通过率下降:


  • 它真的有用吗?

  • 你最近有什么更改吗(例如合并代码,更新驱动程序)?

  • 你是在健康的目的地运行吗?你的所有依赖服务都运行了,包括第三方 SaaS,例如 Docker Hub、GitHub,或者你栈依赖的其他任何事情?

  • 你确定你用的代码、环境、配置、版本、主机列表、排名顺序、随机种子和上一次一样(如果有机会的话)?
  •  是否可以重复进行?

  • 与什么相关吗?其他过程?日常的 cron 表?主机或 DCGM 或 UFM 监控指标?

  • 你的工具来衡量指标是否正确?

  • 当运行减小代码时(较小的模型,伪造数据,不保存或加载检查点)问题是否仍然存在?


提升基础设施工具的能力


完成上述步骤后,可以至少在训练模型时获得良好的性能...直到某件事情总有一天会出问题。


在这一部分,我们介绍了几个不同的工具和系统,目的是确保训练能持续顺畅运行,最好能以尽量少的人工干预进行。因为我们是一个小团队,我们根本不够人手来不断进行手动修复,所以我们尽量自动化尽可能多的流程。


我们的大部分培训问题都能归结到故障的机器或网络组件。这些故障在大型集群中经常发生,因此自动化关闭故障设备和组件的过程请求维修修复是至关重要的。

 故障的机器


我们开发了一个系统,可以自动重新启动崩溃的运行,从最近的检查点开始。重新启动过程将首先在每个可用的机器上运行我们的健康检查,并根据它通过的健康检查来分类每台机器的健康状况;然后它将尝试在最健康的机器上重新启动训练工作。


故障的网络组件


我们观察到的所有网络组件故障都被 UFM 检测并记录在 UFM 事件日志中,因此处理网络组件故障只需解析 UFM 日志,并针对每个事件采取适当行动。


UFM 事件系统相当复杂,包含数十种事件类型。然而,在实践中,我们发现只有少量事件存在问题,大部分与链路故障或高符号错误计数相关。在识别这些事件后,我们能够编写脚本来解析 UFM 事件日志,禁用与最近事件相关的链路和端口,提交维护工作单在这些网络组件上,并在完成维护后重新启用这些组件。


本地镜像文件系统


早期就很明显,大规模分布式训练运行的主要瓶颈之一是集群内的以太网速度。如果数百个工人同时下载数据集和模型检查点,共享以太网连接的带宽大约 10Gbit/s 就会很快饱和。


因此,我们决定在集群内部构建一个本地文件系统,以镜像云存储,并实质上充当缓存,以减少从 S3 导入的文件数量。为了应对集群的替换和迁移(由于维护或其他原因,机器可能会被停用或更换),我们对每个文件进行了三倍的数据复制,使用一致性哈希来均匀分配负载,以在替换期间最小化文件移动。 集群有限的磁盘空间要求我们也开发了各种工具来追踪文件生命周期和清理不再相关的文件。


本地分布式的 Docker 集成仓库


我们也利用了 Kraken(一款卓越的开源软件),以实现 Docker 镜像的点对点传输。我们几乎没有遇到任何问题,这在很大程度上出乎意料,因为任务的复杂性和实现的复杂性都不小。


各种性能监控工具


我们还设置了默认的 PyTorch 优化器和 NVIDIA 的 Nsight Systems。后者对于了解前向/后向传递和 NCCL 通信的持续时间非常有帮助,并且在确定给定模型大小和工作负载下是否由通信或计算瓶颈所限制提供了指导。 然而,Nsight Systems 使用起来不太容易,因为它需要在特权模式下运行 Docker,禁用与性能监控事件相关的安全检查,并且保存出去的架构通常需要整个训练过程的停止。


此外,我们发现编写工具来检测慢训练批次并理解潜在原因是有帮助的。这些工具中最有用的是一个工具,它监控每个批次所需的时间,并在批次速度异常缓慢时,将每个工人的堆栈跟踪记录下来 - 这使得更容易识别具有微妙硬件或软件问题的特定主机。


将机器组分割以定位故障主机


在我们刚开始使用集群时(那时我们的健康检查不是现在这么详细),我们经常遇到一个情况,那就是特定机器集上的训练跑在失败,但不清楚哪个机器有问题。为了定位故障主机,我们开发了工具,使得我们可以将机器集分成子集,并在每个子集上启动一个小任务。


例如,如果一组 48 台机器上的任务失败了,我们会在 6 组 8 台机器上进行较小的运行,然后在 8 组 6 台机器上进行较小的运行。通常情况下,这两个阶段中每台机器只有一次运行失败,这使我们能够以高程度的信心推断出在两个阶段中都出现问题的机器有问题。


反思和收获


在设置和维护我们的基础设施的过程中,我们总结了一些关于整个过程的有用心得:


  • 能够互相替换机器非常有用。对于任何给定的训练运行,我们发现有必要比实际需要的多出 10-20%的机器,以便在机器发生故障时能够轻松重新启动。通过如此设置集群网络,使得每个机器与每个其他机器紧密相连,我们实际上可以使用任何工作机器的子集。

  • 对于在训练过程中遇到的每种硬件或软件故障,都值得编写测试和自动解决方案,因为每一次遇到的问题都会重演。同样,对于每条晦涩难懂的错误消息,编写工具来使错误更可解释都是值得的。

  • 可重复性是良好科学的关键。我们很快采纳了一条“一次只改变一个因素”的规则,即使对于最简单的事情也是如此。

  • 信赖,但核实。每次我们引入外部工具到流程中,或者内部分配新成员时,无论是外部还是内部,我们都会确保再次核实他们的说法,尤其是如果后续步骤依赖于这些结果。

 结论


训练大规模语言模型需要复杂的基础设施才能开始。我们选择对基础设施设置的细节进行深入参与,原因是我们认为这很重要,因为我们工作时要真正理解所使用的系统,而且我们也怀疑最终会证明这种方法更高效。 现在,经过全面的流程后,我们非常高兴我们采取了这种做法——它最终成为拥有我们基础设施完全控制力的关键,以及能够从任何抽象级别的容易调试问题。 尽管这个过程需要大量的监督和迭代,但它使我们能够深入理解底层的流程,构建一系列工具以确保健康的服务,学习如何自动化系统以确保持续的平稳训练,最终创建基础设施,使我们能够以快速的速度迭代前沿语言模型的训练。


这一基础设施过程体现了我们在研究和构建强大基础的 AI 代理方面的方法:深入研究细节,持续改进现有流程,构建有用的工具和系统,以帮助我们的敏捷团队应对更大的挑战。


如果你认同这种全栈方法,我们总是有招聘需求的!