從裸機到700億參數大模型,這裡有份教程,還有現成可用的腳本
選自imbue.com
作者:Imbue 團隊
機器之心編譯
編輯:panda
我們知道 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 智能體的基礎方法論:探究細枝末節,不斷改進現有流程,並構建有用的工具和系統使我們這個積極奮進的團隊能夠應對更大的挑戰。