NCCL 是什麼?多 GPU 訓練的通訊、調校與排查入門 | GPU 除錯系列 #2

NCCL 是什麼?多 GPU 訓練的通訊、調校與排查入門 | GPU 除錯系列 #2

What Is NCCL? Multi-GPU Communication, Tuning, and Debugging

多顆 GPU 一起訓練同一個模型時,它們之間必須不停交換資料,負責這件事的軟體就是 NCCL。這篇用白話講四件事:NCCL 到底在做什麼、它怎麼決定資料走哪條線、哪些設定真的值得調,以及訓練變慢或卡住時該從哪裡查起。

NCCL 是什麼

訓練一個大語言模型時,同一批資料會被切成幾份分給不同 GPU,每顆卡用自己那一份算出一組梯度,也就是參數該往哪個方向調整的數值。問題是每顆卡看到的資料不一樣,算出來的梯度也不一樣,如果各自更新,跑幾步就會變成好幾個不同的模型。所以在更新參數之前,所有卡得先把手上的梯度交出來、加總平均,再把同一份結果收回去。這個動作叫 AllReduce,是分散式訓練裡最常出現、也最花時間的通訊。

NCCL(NVIDIA Collective Communications Library)就是 NVIDIA 寫來做這件事的函式庫。它提供一整套「一群 GPU 同時交換資料」的操作,除了 AllReduce,還有 Broadcast(一顆卡發給所有人)、AllGather(每顆卡的資料大家都收一份)等等。你在 PyTorch 裡用 torch.distributed,或是用 DeepSpeed、Megatron-LM、JAX,底層都可能有 NCCL 從中協調。1

NCCL 幫你省掉的麻煩是:你不用自己決定資料走哪條線。它啟動時會掃描這台機器有哪些通訊管道(GPU 之間的 NVLink、主機板上的 PCIe、對外的 InfiniBand 或乙太網路),再自動挑一條最快的路和最合適的傳法。

為什麼要在意這個?因為通訊控制的優劣能直接決定 GPU 有多少時間真的在算。在一台調校良好的 8 卡 H100 機器上,通訊大約佔每個訓練步驟 20% 到 30% 的時間;設定沒弄好時會吃掉 40% 到 50%,等於你買的 GPU 有一半時間在等資料。2更麻煩的是這類問題很難查:只要一條線降速,幾百張卡的任務就會集體超時(timeout),而且報錯的那張卡通常不是真正有問題的那張。

資料傳輸路徑

GPU 叢集的資料傳輸可以類比成公司裡的溝通:同一間辦公室的同事轉頭就能講到,跨辦公室得走外線,速度差一個量級。GPU 之間也只有這兩種路,先分清楚資料走的是哪一種,才看得懂 NCCL 在做什麼。

  • 機器裡面。 一台 H100 伺服器的標準配置是 8 顆 GPU,彼此用 NVLink 相連,每顆卡的雙向頻寬合計 900 GB/s。這些連線再匯到 NVSwitch 晶片上,所以任兩顆卡之間都只隔一跳、距離一樣遠。同一台機器內的 8 卡 AllReduce,資料從頭到尾不會碰到網卡。
  • 機器之間。 只能走網卡。H100 的標準機型會為每顆 GPU 配一張 400Gb/s 的 ConnectX-7 網卡,可以跑 InfiniBand,也可以跑 RoCE(在乙太網路上做 RDMA),兩種 NCCL 都支援,選哪一種通常取決於機房既有的架構。跨機時資料的完整路徑大概是這樣:

GPU 記憶體 → PCIe switch → 網卡 → 機房網路 → 對方網卡 → 對方 PCIe switch → 對方 GPU 記憶體

機器內的 8 卡靠 NVSwitch 互通;跨機時每一筆資料都得經過 PCIe switch 和網卡。虛線是沒開 GPUDirect RDMA 時多出來的那一次主機記憶體複製。

機器內的 8 卡靠 NVSwitch 互通;跨機時每一筆資料都得經過 PCIe switch 和網卡。虛線是沒開 GPUDirect RDMA 時多出來的那一次主機記憶體複製。

  • GPUDirect RDMA(常簡稱 GDR):讓網卡直接讀寫 GPU 記憶體,不用先把資料搬到 CPU 的記憶體再轉一手,啟用後能讓提高大資料量的跨機頻寬。3
  • 丟包:RDMA 網路對丟包極度敏感。因為 AllReduce 是同步的,一個封包晚到,其他所有卡都得等。實測上 0.1% 的丟包率就可能讓 GPU 使用率掉一成以上,1% 幾乎等於整個任務癱掉。4這也是為什麼用 RoCE 的機房一定要先把無損網路的流量控制(PFC、ECN)設定調好。

NCCL 怎麼決定資料走哪條路

NCCL 啟動時會先畫出一張它看到的拓撲圖,然後做兩個決定:走哪條線,還有用什麼隊形。

走哪條線。 機器內優先用 NVLink,沒有 NVLink 就退到 PCIe,再不行就經過主機記憶體。機器之間優先用 RDMA,如果找不到 RDMA 硬體或設定不對,就退回普通的 TCP,速度差十倍以上。除錯時很重要的一件事,就是確認 NCCL 沒有默默退到慢的那條路。2

用什麼隊形。 同一份資料要在幾十顆卡之間彙總,傳遞的順序有好幾種設計5

隊形 怎麼運作 適合的場合
Ring(環狀接力) 所有 GPU 圍成一圈,每顆卡同時把一小塊資料交給右邊的鄰居,繞完一圈就彙總完成,所有線路全程都在用 資料量大的時候,也就是同步梯度最常見的情況
Tree(樹狀) 像組織圖一樣往上彙總、再往下發布,經過的跳數少 卡數很多但每次傳的資料很小
NVLS 直接請 NVSwitch 晶片順手把加法做掉,GPU 之間因此少傳一輪資料 H100 之後的機型,單機多卡

NVLS 是 H100 世代比較有感的改進:在 DGX H100 上開啟之後(需要 NCCL 2.17 以上、CUDA 12.1 以上),8 卡 AllReduce 大約可以從 370 GB/s 提升到 480 GB/s。5

效能測試:nccl-tests

要判斷叢集健康或調校有沒有效,標準做法是跑 nccl-tests6

git clone https://github.com/NVIDIA/nccl-tests.git
cd nccl-tests && make MPI=1 CUDA_HOME=/usr/local/cuda

# 單機 8 資料量從 8 bytes 一路測到 8 GB
./build/all_reduce_perf -b 8 -e 8G -f 2 -g 8

簡易問題除錯

NCCL 的錯誤訊息(例如 unhandled system error)本身幾乎不含線索,真正的資訊在 debug log 裡3 7

NCCL_DEBUG=INFO python train.py        # 查問題時
NCCL_DEBUG=WARN                        # 正式跑的時候只記錯誤,不洗版
NCCL_DEBUG_FILE=/tmp/nccl.%h.%p.log    # 每個主機、每個 process 各一個檔案

log 有三個地方要看:

  1. 第一行 WARN。那通常就是根本原因,後面一長串錯誤多半是連帶反應。
  2. 挑到哪張網路介面
  3. 實際走了哪條路。初始化的 log 會逐一列出每個通道是走 NVLink、共享記憶體還是網路;跨機的那幾行如果結尾是 via NET/IB/0/GDRDMA,表示 GPUDirect RDMA 有生效,只寫到 via NET/IB/0 就是沒吃到。

下面為日誌中可能會看到的例子。

第一種:跨機走 InfiniBand/RoCE,且 GPUDirect RDMA 有生效(通常會看到 NET/IB + GDRDMA)。例如:

NCCL INFO Bootstrap : Using eth0:10.0.0.11<0>
NCCL INFO NET/IB : Using [0]mlx5_0:1/RoCE
NCCL INFO Using network IB
NCCL INFO Channel 00/04 : 0 1 2 3
NCCL INFO Channel 00/04 :    0[0] -> 1[0] via NET/IB/0/GDRDMA
NCCL INFO Channel 00/04 :    1[0] -> 2[0] via NET/IB/0/GDRDMA
NCCL INFO Channel 00/04 :    2[0] -> 3[0] via NET/IB/0/GDRDMA
NCCL INFO Connected all rings

第二種:看起來也「能跑」,但其實 RDMA 沒用上、退回 Socket/TCP(常見原因是 RDMA 裝置沒掛進容器、權限/驅動不對、或 RoCE 無損沒配好導致初始化失敗)。log 常會出現 NET/Socket 或類似的警告,例如:

NCCL WARN NET/IB : No device found.
NCCL WARN NET/IB : Failed to init IB verbs, falling back to Socket
NCCL INFO NET/Socket : Using [0]eth0:10.0.0.11<0>
NCCL INFO Using network Socket
NCCL INFO Channel 00/04 :    0[0] -> 1[0] via NET/Socket/0
NCCL INFO Channel 00/04 :    1[0] -> 2[0] via NET/Socket/0
NCCL INFO Connected all rings

常見錯誤和該往哪查

遇到的狀況 最常見的原因 先查什麼
unhandled system error 容器的共享記憶體不夠(NCCL 用 /dev/shm 做機內通訊),或 RDMA 相關呼叫失敗 Docker 加 --ipc=host 或加大 --shm-size;Kubernetes 掛一個記憶體型的 emptyDir 到 /dev/shm;確認 ibstat 正常
unhandled cuda error 驅動、CUDA 與框架的版本對不上 看 log 裡的 WARN 訊息,把驅動和 CUDA 版本對齊
跑到一半整個 timeout 有某張卡掉隊:硬體降速、死鎖,或不同 rank 呼叫通訊的順序不一致 TORCH_NCCL_ASYNC_ERROR_HANDLING=1 讓錯誤浮出來;確認所有 rank 呼叫的通訊順序一致
RDMA 的 timeout 或 retry 超限 網路壅塞,或線材、光模組、交換器有問題 ibstat 看連線狀態和錯誤計數;大叢集可調高 NCCL_IB_TIMEOUT;用 ib_write_bw 做點對點測試找出壞掉的那一段
沒報錯,但就是特別慢 走到慢的路了:退回 TCP、GDR 沒開、挑錯網卡 跑 nccl-tests 對照參考值;看 log 裡的路徑選擇和相關紀錄排查

總結

這篇從 AllReduce 出發,說明 NCCL 在多 GPU 訓練裡負責什麼,接著看資料在機器內走 NVLink、跨機走 RDMA(InfiniBand 或 RoCE)的路徑差異,以及 NCCL 如何挑選傳輸路徑與 Ring/Tree/NVLS 隊形;最後是用 nccl-tests 驗證效能,還有從 NCCL_DEBUG 的 log 判讀常見問題。多 GPU 訓練變慢或卡住時,先確認 NCCL 有沒有挑到對的那條路,通常就能找到答案。

附錄:名詞小抄

名詞 白話解釋
NCCL NVIDIA 的集體通訊函式庫,負責多 GPU 之間交換資料並自動挑選傳輸路徑與演算法。
AllReduce 所有 GPU 交出手上的數字、彙總之後每個人都拿到同一份結果,訓練時用來平均梯度。
NVLink / NVSwitch 同一台機器裡 GPU 之間的高速連線與交換器。
GPUDirect RDMA(GDR) 網卡直接讀寫 GPU 記憶體,不繞道 CPU 記憶體。
RoCE 在乙太網路上跑 RDMA 的協定,對丟包敏感,需要搭配 PFC/ECN 等無損設定。
HCA InfiniBand 或 RoCE 網卡的正式名稱,裝置名通常長得像 mlx5_0
Ring / Tree / NVLS NCCL 的三種通訊隊形:環狀接力、樹狀彙總、交換器代算(NVSwitch 網內計算)。
busbw nccl-tests 報告的線路實際流量,可以直接跟硬體規格比較。
rank 分散式訓練裡每個參與 process 的編號,通常一個 rank 對應一顆 GPU。

參考資料

Eason Cao
Eason Cao Eason is an engineer working at FANNG and living in Europe. He was accredited as AWS Professional Solution Architect, AWS Professional DevOps Engineer and CNCF Certified Kubernetes Administrator. He started his Kubernetes journey in 2017 and enjoys solving real-world business problems.
comments powered by Disqus