我們知道 LLM 是在大規模計算機叢集上使用海量資料訓練得到的,機器之心曾介紹過不少用於輔助和改進 LLM 訓練流程的方法和技術。而今天,我們要分享的是一篇深入技術底層的文章,介紹如何將一堆連作業系統也沒有的「裸機」變成用於訓練 LLM 的計算機叢集。
這篇文章來自於 AI 初創公司 Imbue,該公司致力於透過理解機器的思維方式來實現通用智慧。
當然,將一堆連作業系統也沒有的「裸機」變成用於訓練 LLM 的計算機叢集並不是一個輕鬆的過程,充滿了探索和試錯,但 Imbue 最終成功訓練了一個 700 億引數的 LLM,並在此過程中積累了許多有用的經驗。
本文將深入介紹該團隊構建自己的 LLM 訓練基礎設施的全過程,並會分享他們為方便監控、檢查和糾錯而編寫的諸多工具和指令碼。
如果你有心構建自己的 LLM 訓練基礎設施或好奇 LLM 是如何煉成的,那麼這篇文章值得你閱讀和收藏。
以下是 Imbue 團隊文章原文。
引言
我們這個由研究者和工程師組成的小團隊用了幾個月時間在自己的基礎設施上從頭開始訓練了一個 700 億引數量的模型,並且該模型在推理相關的任務上勝過了零樣本的 GPT-4o。
今天,我們要分享的是設定所需基礎設施的過程:從組合初始叢集和安裝作業系統到設定在訓練期間遭遇錯誤時自動恢復。我們會詳細說明在每一步遭遇到的難題和解決方法。除了這些心得之外,我們還將釋出我們一路上開發的許多指令碼,以便其他團隊能更輕鬆地為自己的模型訓練建立穩定的基礎設施。
在整個過程中,我們的工程師團隊與 Voltage Park 一起準備好了計算機叢集,構建了生產應用的基礎。這整個過程包括:
1. 配置各臺機器
2. 配置 InfiniBand
3. 確保機器完全健康
4. 診斷常見的訓練問題
5. 改進基礎設施工具
下面將詳細描述每個步驟。
背景:這是如何煉成的
我們執行計算的目標是確保能快速實驗大型語言模型。為此,我們需要大量高速 GPU,並且這些 GPU 之間也能高速通訊。
本文將重點關注一個叢集,其中包含分散在 511 臺計算機中的 4088 臺 H100 GPU,也就是每臺計算機 8 臺 GPU。之所以有 511 臺帶 GPU 的計算機,是因為需要保留一些連線給統一結構管理器(Unified Fabric Manager)節點,其作用是管理 InfiniBand 網路。在 511 臺帶 GPU 的主機上,每臺 GPU 都直接與一塊 ConnectX-7 網路卡相連,其能以 400 Gbps 的速度與該 InfiniBand 網路中的任意 GPU 傳輸資料。
我們的 InfiniBand 網路拓撲結構的形式是「fully non-blocking」,即完全無阻塞;理論上講,這能讓 GPU 以最大速度互相通訊。為此,我們使用了一種三層式 InfiniBand 網路架構:三層 InfiniBand 交換機。只要連線正確,便能讓整個網路都獲得這樣高水平的吞吐量。下圖展示了這個 InfiniBand 網路的概況:
請注意,訓練網路時的通訊發生在 InfiniBand 上,而不是乙太網上。儘管這些機器也連線了乙太網,但該網路的作用是傳輸資料集和檢查點等資料。如果使用乙太網來傳送資料,速度會慢得多,因為資料會先從 GPU 傳輸到 CPU,然後再透過 100 Gbps 速度的乙太網卡發出去。儘管也可以使用名為 RDMA over Converged Ethernet(RoCE)的技術基於乙太網進行訓練,但那需要在硬體和軟體方面都做大量額外工作,並且可靠程度通常也不及 InfiniBand。詳情可參閱這篇論文:https://arxiv.org/pdf/2402.15627
另外還有一個僅用於配置和管理的輔助乙太網,從而可訪問 BIOS(基本輸入輸出系統)、電源和其他低層機器介面的控制介面。如果沒有這個管理網路,我們就必須透過 USB 驅動、鍵盤和顯示器來手動設定每個節點。對於有幾百臺機器的情況,這並非一種可持續的方法。
要在這個叢集上實現高效能訓練,就需要每個元件(InfiniBand、乙太網、GPU 和節點本身)都近乎完美地工作。如果這 12,000 多個連線中有任何一個有點不穩定,就會拖慢整體的訓練執行速度。
本文接下來的內容就是介紹如何讓這一切都完美且穩定地執行。
過程:如何將裸機變成完全可執行的叢集
配置各臺機器
在透過管理網路建立了與叢集的初始乙太網連線之後,就獲得了基板管理控制器(BMC/baseboard management controller)的訪問憑證。BMC 是一種遠端監控主機系統的專用服務處理器,並且通常連線到一個分立的網路。它能讓我們就像是親身在現場一樣操作機器,並還額外提供了硬體健康狀況、BIOS 設定和電源管理的 API。
配備好這些元件後,我們就可以擼起袖子,開始設定叢集了。
步驟 0:先配置好一臺機器
我們首先使用 iDRAC(戴爾的基板管理控制器)在一臺伺服器上安裝 Ubuntu 22.04,然後再基於這個作業系統設定其他一切。iDRAC 的一項能力是允許從本地計算機安裝和啟動 ISO 映象,並透過瀏覽器提供一個虛擬控制檯。理想情況下,這是該過程中唯一的手動安裝步驟。
步驟 1:在每臺機器上安裝作業系統
在配置完第一臺機器之後,繼續安裝 Ubuntu 的 Metal-as-a-Service (MAAS) 軟體以幫助配置剩餘的伺服器。使用預啟動執行環境協議(PXE)啟動和自動化 iDRAC 工具,可指示每臺機器從網路啟動並配置 MAAS 以響應 PXE 啟動請求。在執行初始的網路啟動時,伺服器會透過動態 IP 分配協議 DHCP 從 MAAS 獲得一個 IP 和一個初始核心,而無需在本地驅動器上安裝任何東西。這是用於自動重複執行作業系統安裝的基本環境。從理論上講,我們只需等待第一次啟動,然後一切都會被處理好。但實際上,MAAS 與 BMC 的整合並不可靠,因此我們使用 iDRAC API 來事先收集每臺機器的 MAC 地址(一種唯一的物理硬體識別符號)。
在這整個訓練過程中,MAAS 通常是椎棧中比較可靠的元件。但是,我們在開始時遇到了一些我們的設定特有的問題。舉個例子,在配置前幾臺機器時,由於時鐘相差太大,HTTPS 證書驗證問題導致無法透過 apt 安裝任何東西。與此相關的是,由於 MAAS 伺服器必須負責很多事情(DHCP 伺服器、用於將主機名解析成 IP 的 DNS 伺服器、主機和官方 Ubuntu 軟體包伺服器之間的 HTTP 代理、NTP 伺服器、cloud-init 配置管理、用於將 MAC 地址連線到 IP 到主機名再到自定義後設資料的 ground truth 資料庫),因此我們很難從根源上解決這些問題。此外,還有 MAAS 配置生命週期的學習曲線問題,因為是設計目標是處理管理綠地部署(greenfield deployment)的複雜性以及節點的逐步遷移和各種除錯 / 不健康的中間狀態。
步驟 2:診斷損壞的機器
我們發現大約 10% 的機器都無法啟動,主要原因是伺服器的物理問題。這是設定大型 GPU 叢集的常見情況。我們遇到的情況包括:沒接網線或接錯了網線、iDRAC 中的硬體問題、電源單元損壞、NVME(快速非易失性記憶體)驅動損壞、內部線路缺失、網路卡或 GPU 無法顯示。我們自動檢查了這些問題,將一些機器退回給了戴爾以重新測試,併為資料中心工作人員提交相應的工單。我們自己上手配置叢集的一個優勢是:在等待維護某些機器的同時就能立即使用健康的機器。
步驟 3:最小可行可觀察機器
我們繼續在每臺伺服器上進行如下設定:
1.Docker(以便更輕鬆地執行服務和訓練作業)
2. 資料中心 GPU 驅動
3.Prometheus 節點匯出工具(用於匯出穩定的硬體 / 作業系統指標資料流)
4.DCGM 匯出工具(用於從英偉達 GPU 匯出額外的指標資料,如 GPU 狀態、時鐘、利用率)
5. 所有非作業系統驅動的 RAIDZ ZFS 池,這讓機器在某個驅動失效時也能繼續工作,同時還能免費提供透明的壓縮(這對純文字資料集和重複性日誌尤其有用 —— 相比於不使用該工具,使用該工具通常能將可使用的空間增大 10 倍)
然後我們執行基本的 GPU 診斷以確定 GPU 是否大體功能正常 —— 不正常的通常會在幾個小時內出現硬體問題。
在此期間,當我們試圖同時在全部 400 個節點上安裝軟體包時,我們遭遇了頻寬瓶頸。這是我們第一次在資料中心部署的多個元件上收到高溫過熱警報。這首批發熱問題基本上都透過韌體更新得到了解決。
步驟 4:單節點的 GPU 訓練
下一步是確保每臺機器都能夠單獨處理真實的 GPU 工作負載。很多機器都無法做到這一點,問題包括:
GPU 相關的錯誤,這類問題基本都可透過將 GPU 卡重新插入卡槽來解決:將 200 磅重的伺服器從機架上滑出來,移除機蓋和 GPU 之間的所有線纜,然後取出 GPU,再重新裝上 GPU,之後再重新接上線纜並把伺服器推回機架。
根據 Ubuntu 伺服器日誌,GPU 和 PCIe 匯流排或網路卡之間的許多線纜都發出了這樣的報錯:「limited width: x4 < x16」。在更新 PCIe 交換機匯流排韌體後,我們發現大約四分之一的主機需要重新安裝內部 PCIe 線纜 —— 大概是因為外殼和 GPU 之間的線纜相當脆弱,這意味著每當對 GPU 進行維護時,這些線纜都會被推擠或拔掉。
還有一些雜項故障也影響了幾臺主機。戴爾透過韌體升級幫助我們解決了一些問題:
NVMe 驅動器沒有顯示故障,但觸控時會鎖定整臺機器。
硬碟驅動器在 Linux 下以隨機順序顯示,這給 MAAS 造成了混亂,並會導致作業系統被安裝在錯誤的驅動器上。
溫度讀數錯誤,這會導致風扇一直全速運轉。其原因可能是英偉達驅動有問題,這透過降級驅動版本而得到了解決。
CPU 的動態調頻失控,將工作核心限制為 2 GHz。
直接的 GPU-GPU 通訊(GDR 或 GPUDirect RDMA Peer Memory Client)無法成功應用。
配置 InfiniBand
步驟 0:安裝 UFM
InfiniBand 的一個優勢是其中心化的設計,這樣一來整個網路就有了一個大腦。因此,對於整個網路結構中的 320 個網路交換機,我們只需處理其中一個例項。我們的首個任務是搞清楚哪臺交換機連線了哪些機器,然後將其與接線圖關聯起來,並根據交換機的物理位置重新命名它們。
步驟 1:重新佈線
一開始,UFM 無法檢測到那 320 臺交換機,更別說本應出現在網路結構中的主機了。在與我們的資料中心合作伙伴商討之後,我們確認這些交換機已通電並接好了線,但依然無法檢測到。在檢查網路佈線列表後,我們注意到該網路結構的頂層設計有誤:這個結構不是統一的,而是分成了八個沒有公共路由路徑的互相脫離的網路。在重新接線之後,我們新增了檢查步驟,以驗證所有物理連線是否與新設計一致。
步驟 2:一萬次溫度告警(alert)
在解決了物理接線問題之後,InfiniBand 成功建立了與網路結構中所有 InfiniBand 交換機的聯絡。但是,幾乎每個交換機埠都開始報告溫度過高,有時候超過 70 ℃,即便它們還沒有傳輸資料。我們發現這個問題源自同一機架中交換機之間的開放空間,這導致熱空氣迴流到了前面。我們的資料中心合作伙伴幫助我們快速診斷出了該問題並制定了合適的解決方法。
步驟 3:1800 次告警
許多埠還有很高的錯誤率,或在正常和損壞狀態之間來回變動,這被稱為「抖動(flapping)」。這些問題只有在實際使用這些埠時才會出現,所以很難預先檢測,因為我們的整個結構由 10,000 條高度冗餘的鏈路組成。我們的資料中心合作伙伴幫助清潔和重新安裝告警的埠,我們在等待替換時禁用了剩餘的警報收發器。
儘管 InfiniBand 能彈性地應對硬體故障,但一旦大約 10% 的結構開始出現問題,自適應路由等功能就無法可靠地執行,無法解決偶爾丟失鏈路的問題。
在此期間,我們成功使用 100 到 200 臺機器執行了多節點訓練。我們的流程比較即興:我們有時會隨機啟動一組節點,觀察它們的效能,然後盡力讓其中儘可能多的節點保持執行。該方法可讓我們找到該 InfiniBand 網路結構中一組可靠的子集,但難度卻很大,因為每次都需要改變用於訓練的節點集合,由此改變預設的 InfiniBand 鏈路。
步驟 4:InfiniBand 瘋狂燃燒
為了更高效地診斷 InfiniBand 問題,我們專門為整個叢集設計了一個工作負載,其作用是同時將盡可能多的資料推送經過整個結構中的每個埠。這不同於在整個叢集上執行一個大型的 all-reduce 工作負載,這需要使用 NCCL 來最佳化各個節點之中的通訊,方法是使用 NVLink 經由 Server PCIe Module (SXM) 插槽來實現 GPU 通訊。
相反,我們選擇了一種蠻力方法,並輕鬆取得了成功。UFM 會在大多數埠的資料傳輸量超過理論容量的 97% 時開始發出警報,同時一些交換機會暫時當機。我們認為能堅持到當天結束時的每個埠都是足夠穩健的,其餘的都被禁用或移除以待維修。
步驟 5:GPUDirect RDMA
要讓 GPU 通訊時不產生 CPU 計算開銷,我們啟用了一個名為 GPUDirect RDMA 的功能,其允許 InfiniBand 網路卡之間直接通訊。這涉及兩個關鍵步驟:
1. 啟動一個額外的核模組
2. 確保 PCIe Access Control Service (ACS) 被禁用,以防止 immediate hangs(立即掛起)
步驟 6:擴增「黃金」伺服器
要使用最新硬體構建 GPU 叢集,一個經驗法則是:每週都有大約 3% 的機器出問題,要做好準備。
但是,需要說明一點:並不是每臺機器都統一有 3% 的機率發生故障,而是少量不對付的機器反覆出現各種不同問題,直到將它們妥善修復。這就凸顯了在同一網路結構中配備大量機器的優勢。因此,我們的做法不是隨便找些機器來執行大規模訓練,就像打地鼠一樣看哪些出問題,而是專注於擴增已知可靠的伺服器,也就是「黃金」伺服器。
步驟 7:維護
InfiniBand 的維護主要涉及到響應 UFM 警報、更換故障線纜和收發器,以及偶爾診斷更困難的錯誤(比如交換機故障)。導致大規模維護的因素通常有兩個:
1. 韌體更新,尤其是叢集中僅有一半完成更新時,這可能導致 UFM 狀態損壞並必需重啟所有 InfiniBand 交換機上的 UFM。
2.GPU 盒同時大規模重啟,這可能會向 UFM 狀態灌入大量更新,並同樣需要重啟 UFM 服務。
確保機器完全健康
在此過程中,我們發現了單臺機器的多種故障或減慢訓練速度的方式。其中許多故障模式並不會立即顯現,因此我們編寫了許多健康檢查指令碼,以檢查主機是否足夠健康。我們在這裡釋出了這些程式碼:https://github.com/imbue-ai/cluster-health
請注意,這些健康檢查中的很多都特定於我們的執行時環境,並不一定與基礎硬體相關,也不一定容易修復或自動化。這是設計決定的:為了實現讓我們的機器準備好訓練的總體目標,我們想要一個可以直接了當地回答「是」或「否」的單一入口點,並且可以概括總結任意數量的細微細節。
GPU 健康檢查
我們檢查了 GPU 數量是否正確、ECC(錯誤更正程式碼)檢查是否已啟用以及是否存在 ECC 錯誤。我們還檢查了 NVLink 拓撲(將 GPU 彼此連線起來)是否已啟動且無錯誤。
磁碟空間健康檢查
我們檢查了主機的磁碟空間利用率是否超過 95%。
Docker 健康檢查
我們檢查了 Docker 能否在連線了 GPU 的情況下執行容器(即 NVIDIA Container Runtime 是否正常工作),還檢查了與監控 / 分析相關的 Docker 容器是否已啟用並獲得了正確的主機許可權。
Dmesg 健康檢查
我們檢查了 dmesg 中是否存在硬體 Xids 或 SXid 錯誤(由 NVIDIA GPU 或 GPU 間 NVIDIA 交換機引發的故障)。我們還讀取了所有 dmesg 日誌行,以驗證它們是否都可以歸類到「常見 / 預期日誌行」列表中。
iDRAC 健康檢查
我們檢查了機器上的 iDRAC 錯誤,其中忽略了非致命錯誤訊息。這是戴爾計算機特有的檢查,所以沒有被包括在我們開源的程式碼中。
磁碟健康檢查
我們檢查了 zpool 是否已安裝,Docker 是否已正確連線到它,以及它是否真的可以在不鎖定 CPU 的情況下觸及它。
InfiniBand 健康檢查
我們檢查了 InfiniBand 的錯誤率是否會增加和 / 或驅動韌體是否過時。
Nvlink 健康檢查
我們檢查了機器上的 NVLink 錯誤。實踐中看,這似乎不會導致訓練失敗,但可能會降低訓練速度。
GDR 健康檢查
我們檢查了機器上的 GDR 是否已啟用。
VBIOS 健康檢查
我們檢查了 GPU 的 VBIOS 版本以及 H100 基板韌體是否是最新的。
Flint 健康檢查
我們使用 flint 和 hca_self_test 檢查了 Mellanox OFED 驅動、網路卡韌體和收發器韌體的版本是否正確,以及它們是否針對英偉達驅動進行了正確編譯。
PSB 健康檢查
我們查詢了 PCIe 裝置,以檢查 GPU、PSB(PCIe 交換機匯流排)和網路卡之間的連線速度和寬度是否符合我們的預期。我們還檢查了交換機韌體是否為最新版本。該指令碼由戴爾而非 Imbue 開發,所以我們目前無法共享它。
除了這些快速健康檢查,我們還進行了一些更復雜的健康檢查,包括:
透過 PyTorch 初始化矩陣計算,以及測量 NVLink 頻寬和 GPU 計算速度和記憶體。我們設定了適當的 GDR 標誌來測試 InfiniBand 和 NVLink。
使用 ib_write_bw 和 –use_cuda 透過 IB 卡傳送資料並測量 PCIe 和 InfiniBand 卡頻寬。這個過程持續了較長時間(約 15 分鐘),以確保能找出抖動的 InfiniBand 鏈路。
執行多節點診斷執行以檢查 NCCL 初始化能力以及它是否會隨機停頓。如有停頓,則我們的分叉版 NCCL 程式碼會新增額外的日誌記錄。這需要 12 到 24 小時才能檢測到問題,因此我們通常只對新節點或我們懷疑存在問題時執行此操作。
檢查 DCGM 匯出是否存在任何 GPU 時鐘節流事件(不包括預期的 gpu_idle 和 power_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 日誌檢測出來。當 OOM Killer 呼叫停止一個分叉程序或同級網路(network peer)時,我們可以看到它們主要表現為 CalledProcessError 或 ConnectionError。當從 dmesg 檢測到了 OOM Killer 呼叫時,我們更傾向於直接放棄健康檢查,並重啟該機箱。我們還檢查了我們的程式碼路徑是否有足夠的手動垃圾收集(下面有一部分介紹瞭如何禁用它),並還檢查了是否有意外嘗試進行計算或將張量移動到 CPU 上。
在訓練過程中崩潰
首要任務是讓系統能自動執行,讓其能自動重新執行所有健康檢查,然後在沒發現不健康主機時重啟執行。我們遇到了一些隨機的硬體錯誤,包括 Xid 和 SXid 錯誤;這些錯誤可能會導致執行崩潰,卻不會發出有意義的 Python 棧跟蹤記錄。行重對映等一些問題可透過重啟恢復。不可糾正的 ECC 錯誤等另一些問題則往往需要硬體維護或更換零件。
此外,我們還觀察到格式錯誤的訓練資料也會導致崩潰。舉個例子,如果語料庫中有一個非常大的單個文件,就可能導致 GPU 或 CPU 出現記憶體不足錯誤。為了防止出現這個問題,我們採用了完全確定式的資料載入器 —— 透過與 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 和 InfiniBand 連線崩潰,導致其他所有主機同時卡在了某個張量運算上,直到達到 NCCL_TIMEOUT 超時時間。不幸的是,受 NCCL 軟體庫本質所限,我們很難找到究竟是哪臺主機出了問題。
我們對 NCCL 軟體庫的日誌記錄做了一些修改,參見我們的分叉版:https://github.com/boweiliu/nccl 。從而在崩潰發生時可能更好地揭示正在執行的訊息或操作,從而確定阻止執行的可能是哪臺主機或 GPU。
請注意,為了識別行為不當的主機,我們通常需要搞清楚哪些主機沒有生成某些日誌訊息。缺少此類訊息表明該主機上的工作器已落後或已崩潰。
其他沒有可用錯誤訊息的無響應情況通常與硬體相關問題有關,比如之前提到的 Xid/SXid/ECC 錯誤會導致英偉達驅動或英偉達 Docker 通訊驅動鎖定。為了區分 NCCL 掛起與驅動掛起以及 Python 程式碼中的競爭條件或死鎖,我們使用 Py-Spy 和 GNU Project Debugger(GDB)等工具來實時除錯遇到的停滯程序。使用此方法可發現一個特定問題:由於 Python 執行緒設定中的配置錯誤,我們無法在某些主機上正確啟動八個多執行緒 NCCL GPU 程序,這些程序在 PyTorch 之前的初始化程式碼階段遇到了競爭條件。
訓練減速(由 MFU 測量)
缺乏工具讓這類問題比前一類更讓人沮喪。除了使用 Py-Spy、棧跟蹤檢查和 GDB 之外,我們還採用了 NVIDIA Nsight 和 profiling 工具,其中一些工具在高度分散式的設定中很難使用。
遺憾的是,導致一般減速或讓速度低於之前演示的模型浮點數利用率(MFU)的原因有很多。
首先,事實證明多次檢查配置、程式碼和環境變數是有用的。我們經歷過的錯誤包括:執行了錯誤的模型、批次大小錯誤、UFM 或 NCCL 設定出錯、CUDA_DEVICE_MAX_CONNECTIONS 出錯。這都會導致效能無法達到最優。
我們還發現測量瞬時(即每批次)MFU(而不是平滑或視窗平均值)很有用,因為未平滑處理的 MFU 曲線通常有助於診斷問題類別。導致訓練速度下降的問題包括:
從非常低的 MFU(低於預期的十分之一)立即開始訓練並保持穩定
這多半是 InfiniBand 網路連線的硬體問題,例如 T2 或 T3 層的交換機當機。GPU 和 NIC 之間的硬體問題也可能導致該情況,對此 dmesg 會這樣報錯:PCIe x16 lanes limited by …
從預期 MFU 的 30% 立即開始訓練並保持穩定
其原因可能是一臺主機的 GDR 設定不正確(NVIDIA 對等記憶體)或 GDR 環境變數不正確。
從預期 MFU 的約 60-80% 立即開始訓練並保持穩定
最常見的原因是 InfiniBand 鏈路質量不行或故障,尤其是單臺 GPU 出現了與 InfiniBand NIC 相關的故障,導致 NCCL 嘗試經由本地 NVLink 路由流量並在同一主機上的另一臺 GPU 上使用 NIC。CPU 節流也可能導致該問題,這需要調整某些主機的 BIOS 設定。
在處理某些資料批次時突然大幅減速(下降 10 倍),並且經常發生這種情況
這基本上都與檢查點或評估有關 —— 可透過檢查 epoch 數或步數來驗證。惱火的是,如果設定了在 MFU 異常時自動告警,就會出現許多誤報。
在處理某些資料批次時突然大幅減速(下降 10 倍)
這種情況是隨機發生的並且相當罕見(大概每 15 分鐘一次),並且之後馬上就會完全恢復到良好的 MFU。
最常見的原因似乎是其他需要大量 CPU 計算的工作負載被排程到了一臺執行中的主機上。我們發現,與其構建分析工具來識別特定的主機,透過 PID 來粗略地監控 CPU 會更容易。其原因可能是偶爾出現的網路連線問題,比如資料載入器遭遇瓶頸。我們監控了資料載入、檢查點和任何非 NCCL 程式碼的指標資料並新增了 Python 程式碼計時日誌,事實證明這非常可靠。
MFU 在執行過程中逐漸減慢,但每次重啟後又會回到 100%
理論上講,其原因可能是交換機上的熱量積累,但我們從未見過這種情況。不過,我們使用 Python 和 NVIDIA 分析器確定:效能下降的原因似乎是自動垃圾收集。
在除錯解決這些減速問題時,我們發現吞吐量幾乎必然會週期性地下降。隨著訓練地推進,這種下降會對分散式運算帶來越來越多的影響。這讓我們猜想下降的原因可能與自動垃圾收集有關 —— 我們透過分析和測試驗證了這個猜想。當我們禁用了自動垃圾收集,並在所有主機上設定只在特定的間隔內收集垃圾,這種吞吐量「下降」就消失了。
我們使用了一種基於 ZeRO-3 的同步分散式訓練演算法 FSDP。在阻塞操作期間,執行垃圾收集的單個工作器程序可能會減慢其他所有工作器的速度。如果有數百個工作器程序,就可能導致速度大幅下降。
一開始效能良好,然後突然下降(達到預期的 70%),並且以高頻持續(每 15 秒)
我們觀察到這與英偉達 GPU 的「時鐘節流原因」相關,這可透過對英偉達 DCGM 進行適當的設定來解決。發熱問題(GPU 高溫或主機冷卻風扇故障 / 效果下降)或電源故障會導致該問題。另外,當我們同時最大化所有 8 臺 GPU 利用率和 8x NIC InfiniBand 利用率以及 CPU/RAM/ 磁碟時,我們的一些具有特定電源硬體的主機會出現電壓問題,但只有全部使用它們(通常只在實際訓練執行期間)時才會出現這種情況。
效能優良但噪聲比平常情況多(高頻白噪聲方差在預期 MFU 的 90% 和 100% 之間)
這也與 InfiniBand 硬體有關,但通常是由於網路中較高層的鏈路出現一定程度的效能下降或抖動,而不是冗餘度較低的主機到 T2 層。
不幸的是,很多這類問題都難以定位到某臺具體主機,而與 InfiniBand 相關的問題尤其難以定位,這是由於 InfiniBand 交換機技術的拓撲感知特性。InfiniBand 似乎更偏好 InfiniBand fat-tree 設計中的相鄰主機,而 UFM 能以不對稱的鏈路速度路由資料包。
以下是用於除錯吞吐量問題的簡單摘要 / 流程圖 / 完備性檢查表:
這套系統之前能正常工作嗎?
你最近做了什麼修改(比如合併程式碼、更新驅動)?
你執行的主機是否健康?你的所有依賴服務是否都執行正常,包括第三方的 SaaS,比如 Docker Hub、GitHub 等等?
你能確定現在執行的程式碼、環境、配置、版本、主機列表、排名順序、隨機種子與上次完全相同嗎?(如果能實現這樣的檢查的話。)
問題可復現嗎?
與其他事物有何關聯?其他程序?每日 crontab 定時任務?主機或 DCGM 或 UFM 指標?
你的工具是否能正確度量這些指標?
在執行約簡版的程式碼(使用更小的模型、假資料、不儲存或載入檢查點)時,問題是否依然存在?
改進基礎設施工具
完成了以上步驟之後,就能在訓練模型時實現優良效能了…… 至少在某個地方出故障之前是這樣。
本節將介紹一些用於確保訓練持續穩定的工具和系統,同時最好儘可能地少地需要人類干預。由於我們這個團隊很小,因此我們沒有足夠的人手來進行人工維修,所以我們也希望能儘可能地自動化這個過程。
我們在訓練過程中遇到的所有問題幾乎都可歸因於機器或網路元件故障。這類故障在大型叢集中很常見,因此我們的做法是自動禁用出故障的機器和網路元件併傳送維修請求。
機器故障
我們開發了一個系統,可在執行崩潰時自動從最近的檢查點重啟。在這個重啟過程中,首先是對每臺可用機器進行健康檢查,然後基於其傳遞的健康檢查結果對每臺機器進行分類;然後嘗試在最健康的機器上重啟訓練。
網路元件故障
我們觀察到的所有網路故障都可透過 UFM 檢測到,並會被記錄到 UFM 事件日誌中,因此響應方式也很簡單:解析 UFM 日誌並採取相應措施。
UFM 事件系統非常複雜,包含數十種事件型別。但在實踐中,我們發現只有少數事件有問題,主要與鏈路故障或符號錯誤技術較高有關。在識別出這些事件後,我們可以編寫指令碼來解析 UFM 事件日誌、禁用與最近事件相關的鏈路和埠、為這些網路元件申請維護工單、維護完成後重新啟用這些元件。
本地映象檔案系統
對於這些大型分散式訓練,人們很早就發現叢集與乙太網的資料交換速度是一大瓶頸。一條共享乙太網連線的頻寬大約為 10Gbit/s;如果有數百個工作器同時下載資料集和模型檢查點,那麼這點頻寬會很快飽和。
為此,我們決定在我們叢集內部構建一個本地檔案系統,以作為雲端儲存的映象,其本質上就是一個快取空間,可以減少從 S3 讀取的檔案量。為了解決叢集流失問題(即因為維修原因而禁用或更換機器的情況),我們為每份檔案都準備了三個副本,並使用了一致性雜湊以均勻分配負載,從而在叢集流失期間最大限度地減少檔案移動。由於叢集的磁碟空間有限,所以我們必須開發多種工具來跟蹤檔案的生命週期和清除不再有用的檔案。
本地分散式 Docker 登錄檔
我們使用了 Kraken,這是一個可點對點傳輸 Docker 映象的出色開源軟體。這個軟體幾乎沒出現過任何問題,我們還是挺驚訝的,畢竟我們的任務和實現都很複雜。工具地址:https://github.com/uber/kraken
各種效能監控工具
我們設定了預設的 Torch 分析器以及英偉達的 Nsight Systems。後者可幫助我們瞭解前向 / 反向透過以及 NCCL 通訊所需的確切時間,並進一步幫助我們確定給定的模型大小和工作器數量是否會成為瓶頸。然而,Nsight Systems 有點難用,因為其需要在特權模式下執行 Docker,這需要禁用與效能監控事件相關的安全檢查,並且儲存其配置時往往需要停止整個訓練程序。
此外,我們也編寫了工具來檢測訓練速度慢的資料批次並理解其可能原因。我們發現這很有用。其中最有用的工具的作用是監控每一批次所需的時間並在某批次過於慢時丟棄該工作器的棧跟蹤 —— 這讓我們可以更輕鬆地找到硬體或軟體有些小問題的主機。
將機器分成不同的組別以定位故障主機
在使用該叢集的前幾個月(那時候健康檢查還不如現在這般透徹),我們經常遇到這種情況:在一組機器上訓練時出現故障,但並不清楚究竟是哪臺機器有問題。為了找到故障主機,我們開發了一些工具,可輕鬆地把一組機器分成不同的小組,然後在每個機器小組上執行更小的訓練。
舉個例子,如果一個在 48 臺機器上執行的訓練失敗了,那麼就在 6 組各 8 臺機器上進行更小規模的訓練,然後在 8 組各 6 臺機器上執行更小規模的訓練。通常情況下,這兩個階段中只有一次執行會失敗,這讓我們有信心得出結論:在兩個階段中都出問題的機器是有問題的。
反思和學習到的經驗教訓
在設定和維護基礎設施的過程中,我們獲得了一些有用的經驗教訓:
一種有用的做法是交換機器的位置。在執行時,使用多於所需機器數量 10-20% 的機器會很有幫助,這樣就能在機器故障時輕鬆重啟訓練了。設定叢集網路連線時讓每臺機器都與其他每臺機器緊密相連,這樣一來我們就能使用這些機器中任意可工作的子集。
對遇到的每一個硬體或軟體故障,編寫測試和自動化解決方案是值得的,因為訓練中遇到的每一個問題都會再次出現。類似地,對於每一個含混不清的報錯訊息,都值得編寫更能解釋該錯誤的工具。
可重複性是優秀科研的關鍵。我們立馬就採用的一大原則是:「一次只修改一個地方」,即便最簡單的地方也是如此。
信任,但也要驗證。每當我們引入外部工具或加入新人員(無論是來自公司內還是公司外)時,我們都會仔細檢查他們聲稱的東西,尤其是當後續步驟依賴於這些聲稱的東西時。
總結
訓練大型語言模型一開始就需要複雜的基礎設施。我們之所以選擇深入參與基礎設施的設定細節,是因為我們相信完全理解我們操作的系統是非常重要的,也因為我們認為這樣做的效率更高。
現在,經歷過整個流程之後,我們很高興我們採用了這樣的方法 —— 事實證明,能完全控制我們的基礎設施以及有能力輕鬆地在每個抽象層級上進行除錯具有至關重要的價值。雖然這個過程需要大量的監督和迭代,但它讓我們可以深入地理解其底層工作流程、構建一系列用於確保主機健康的工具、學習如何讓系統自動執行以確保訓練持續平滑,最終構建起一套讓我們可以快速迭代訓練前沿語言模型的基礎設施。
這個基礎設施構建流程體現了我們研究和構建 AI 智慧體的基礎方法論:探究細枝末節,不斷改進現有流程,並構建有用的工具和系統使我們這個積極奮進的團隊能夠應對更大的挑戰。