iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
AI Engineering

GPU很忙?他真的有在做事嗎?系列 第 19

Day 19|訓練 LLM 的通訊成本藏得好嗎?拆解 NCCL overlap

  • 分享至 

  • xImage
  •  

Day 18 的結尾留下一個問題:主檔 A 已錄到的 device 1–7,NCCL 與其他 GPU kernel 的 overlap 落在 33.8–39.3%。也就是說,大部分通訊還是單獨出現在 timeline 上。

不過,只看一個平均百分比還不夠。今天要繼續追三件事:

  1. overlap ratio 的分母到底是什麼?
  2. 沒藏好的通訊,是平均散在整個 step,還是集中在某種 collective 與某個相位?
  3. Day 18 還讀不到的 message size,能不能從 NCCL 的 NVTX payload 補回來?

本文沿用同一份主檔 A、同一段 sample_0(repeat=5),分析範圍是 39.964–46.123 秒。單卡細節以 device 4 為主,再用 device 1 與 6 檢查型態是否只出現在單一卡上。工具是實際執行的 nsys-ai v0.3.0-21-g1cc03b5,不是示意資料。

先提醒一個容易混淆的範圍:這是八卡作業,CPU 端的 NCCL NVTX payload 留有 8 個 ranks;但這份 profile 的 GPU kernel timeline 只錄到 device 1–7。因此 payload 次數要用 8 ranks 換算,單卡 kernel 分析則只能談已錄到的 7 張卡,兩邊不能混著除。

overlap ratio 的分母是通訊時間

先把 timeline 簡化成兩條 lane:一條放 NCCL kernel,另一條放其餘 GPU kernel。把每條 lane 內互相重疊的區間先取聯集,再比較兩條 lane,就能把整段時間拆成四個互斥部分:

  • 只有其他 GPU kernel
  • 只有 NCCL kernel
  • 兩者同時執行
  • 兩條 lane 都沒有 kernel

Holistic Trace Analysis 採用的 communication-computation overlap 定義是:

overlap ratio = NCCL 與其他 GPU kernel 重疊的時間 ÷ NCCL 總時間

它回答的是「通訊有多少比例與其他 GPU 工作同時發生」。另一個也常用、但分母不同的數字是:

NCCL-only fraction = 只有 NCCL kernel 的時間 ÷ 整段 wall time

後者回答的是「整段時間裡,有多少比例只有 NCCL kernel 在跑」。它不是可直接回收的時間,也不等於通訊造成的 critical path;資料相依、CPU 工作與 copy engine 都可能影響實際能省下多少時間。

先看 device 4 的 overlap_breakdown 實際輸出:

  Total span:    6155.8ms
  Compute only:  4260.4ms
  NCCL only:     1094.7ms
  Overlap:        709.2ms (39.3% of NCCL overlapped)
  Idle:            91.4ms

四個數字相加正好是 6,155.8 ms,這是檢查區間拆分是否重複計算的第一關。這裡的 Idle 也要照工具口徑解讀:它只表示「NCCL 與其他 GPU kernel 兩條 lane 都沒有事件」,不保證整張 GPU 完全沒事做,因為同一時間仍可能有 memcpy 或其他引擎在工作。

如果只做算術,把 1,094.7 ms 的 NCCL-only 全部從 6,155.8 ms 扣掉,五步會剩 5,061.1 ms,平均每步約 1.012 秒。這可以當成一個很寬鬆的理論上限,用來判斷值不值得繼續查;不能把它當成打開某個開關後就能得到的預測。通訊有相依性,重疊也可能和計算爭用 GPU 資源,實際收益通常會小於這個算術上限。

overlap ratio 的定義:NCCL 與其他 GPU kernel 兩條 lane 取聯集與交集,得到只有其他 kernel、只有 NCCL、重疊與兩條 lane 皆無事件四個互斥區間;overlap ratio 以 NCCL 時間為分母,NCCL-only fraction 則以牆鐘時間為分母

從 NCCL payload 補回 message size

Day 18 還無法從 kernel 名稱讀出每筆傳輸的 bytes。這次改用 nccl_payload_breakdown,解開 NCCL 寫在 NVTX 上的 typed payload:

nsys-ai skill run nccl_payload_breakdown megatron.sqlite \
  --trim 39.964 46.123
── NCCL Payload Breakdown ──
  Total payload events:  1,920
  Message-carrying:      1,920   (458.96 GiB total)
  Distinct schemas:      3

  [collective] schema=16777220  calls=1,120  communicators=9
      msg_size: p50=256.00 MiB  p99=256.00 MiB  total=278.96 GiB
  [collective] schema=16777221  calls=   80  communicators=1
      msg_size: p50=0.00 MiB    p99=0.00 MiB    total=0.00 GiB
  [collective] schema=16777223  calls=  720  communicators=1
      msg_size: p50=256.00 MiB  p99=256.00 MiB  total=180.00 GiB

終端輸出四捨五入到 MiB,看不出第二個 schema 的大小,所以我又讀了 JSON 的原始 bytes,並核對每種大小的次數:

推定 collective 事件數 message size 分布 每 rank、每步
AllGather 1,120 1,080 筆 × 256 MiB;40 筆 × 229.3 MiB 27 × 256 MiB + 1 × 229.3 MiB
AllReduce 80 全部都是 4 bytes 2 × 4 bytes
ReduceScatter 720 全部都是 256 MiB 18 × 256 MiB

事件數也能反向核對五個 step:

AllGather:     1,120 ÷ 8 ranks ÷ 5 steps = 28 次/rank/step
AllReduce:        80 ÷ 8 ranks ÷ 5 steps =  2 次/rank/step
ReduceScatter:   720 ÷ 8 ranks ÷ 5 steps = 18 次/rank/step

這裡有三個值得記下來的觀察。

第一,主力 collective 的 message size 很規律,但不是每一筆都一樣。ReduceScatter 全是 256 MiB;AllGather 每個 rank、每步有 27 筆 256 MiB,另有 1 筆約 229.3 MiB。256 MiB 若用 BF16 表示,相當於 2²⁷ 個元素,符合大型張量分片的量級;要進一步還原成 sequence length、hidden size 或特定 layer,仍需要模型設定,不能只憑 bytes 猜形狀。

第二,AllReduce 不是「接近零」,而是每筆精確的 4 bytes。這已經可以排除 MiB 等級的參數梯度 bucket,更像某個 FP32 純量的同步。不過純量究竟是 loss、token 數、旗標,還是其他統計值,仍要回到 call site 才能命名。

第三,每 rank 每步的邏輯 payload 約為:

27 × 256 MiB + 1 × 229.3 MiB + 18 × 256 MiB ≈ 11.47 GiB

乘上 8 ranks、5 steps,會得到表頭的 458.96 GiB。這是 NCCL 呼叫側記錄的邏輯 payload,不是 NVLink 上實際搬動的 bytes;collective 演算法會讓實體鏈路流量再乘上對應係數,Day 18 講的 algbwbusbw 差異就是從這裡來的。

工具有數字,不代表可以直接引用

有了 message size,我原本也嘗試用 nccl_communicator_analysis 直接算頻寬。不過同一個範圍內,它列出的 AllGather/ReduceScatter/AllReduce 次數是 1,129/744/76,和 payload 已核對的 1,120/720/80 對不起來。因此本篇不採用它輸出的 33–37 GB/s,也不拿這個數字除以 H200 的 900 GB/s 宣稱「NVLink 只用了 4%」。

這是一個很實用的驗證習慣:工具同時輸出 countbytesduration 時,先確認事件數能不能和其他視角閉合,再談速率。即使事件配對正確,message size ÷ collective duration 也只是應用層的有效速率;duration 可能包含等待其他 rank,而 collective 的邏輯 payload 又不等於單條鏈路上的 bytes,所以不能直接視為實體鏈路利用率。若要判斷通訊本身是否跑得慢,還是 rank 到得不整齊,仍需要同拓撲、同大小的 nccl-tests baseline 和 straggler 分析。

平均 39.3%,到底是哪一段沒藏好?

先用 kernel_overlap_matrix 把 device 4 的 collective 拆開。以下只節錄和本文有關的欄位:

nsys-ai skill run kernel_overlap_matrix megatron.sqlite \
  --trim 39.964 46.123 -p device=4
                       compute      allgather  reducescatter
        compute       4969.6ms        386.0ms        323.2ms
      allgather        386.0ms       1167.9ms          0.0ms
  reducescatter        323.2ms          0.0ms        628.5ms

  NCCL overlap efficiency: 39.3% of NCCL time hidden behind compute

AllGather 的 1,167.9 ms 裡有 386.0 ms 與其他 GPU kernel 重疊,約 33.0%;ReduceScatter 的 628.5 ms 裡重疊 323.2 ms,約 51.4%。另外還有 7.5 ms 的 AllReduce 沒有和其他 GPU kernel 重疊。光是拆到 collective 類型,就已經看到接近 20 個百分點的差距。

接著我用 SQL 讀出 kernel interval,再以 flash attention kernel 當錨點,把五個 step 粗分成 forward 與 backward:相鄰 flash_fwd 間隔超過 300 ms 時視為新 step;每步從第一個 flash_fwd 到第一個 flash_bwd 算 forward,之後算 backward。這不是 nsys-ai 內建輸出,也不是精確的框架 phase annotation,所以只能拿來看結構,不能把邊界附近的單筆 kernel 過度解讀。

device 4 的結果如下:

操作 × 相位 NCCL 時間 與其他 GPU kernel 重疊 overlap
AllGather,forward 608.4 ms 296.6 ms 48.8%
AllGather,backward 554.2 ms 89.4 ms 16.1%
ReduceScatter,forward 396.7 ms 149.5 ms 37.7%
ReduceScatter,backward 225.9 ms 173.7 ms 76.9%

四格的重疊時間合計 709.2 ms,和 overlap_breakdown 完全一致。平均值底下其實藏著 16.1% 到 76.9% 的巨大落差。

同樣的方法在 device 1 與 device 6 重跑後,型態沒有改變:

  • backward ReduceScatter 是 75.0% 與 73.7%,三張卡都藏得最好。
  • backward AllGather 是 20.5% 與 22.0%,三張卡都最曝露。
  • forward AllGather 在 device 1、6 約為 30%,device 4 的 48.8% 比較樂觀。

所以「單卡不能代表全體」仍然成立,但這次要追的主結構很穩定:backward AllGather 是最沒有藏好的那一格。

device 4 的 overlap 四格帳:AllGather 與 ReduceScatter 各自拆成 forward 與 backward,overlap 從 16.1% 到 76.9% 不等;device 1 與 6 也呈現 backward AllGather 最曝露、backward ReduceScatter 藏得最好的型態

backward AllGather 為什麼難藏?

相位表只能告訴我們「發生在哪裡」,還不能直接回答「這筆資料是做什麼用」。幸好這份 profile 保留了 Transformer Engine 的 NVTX range,可以再往 call site 靠近一步。

我把 device 4 的 NCCL kernel 經由 CUDA runtime event 接回外層 NVTX,看到部分 backward AllGather 落在這兩種標籤下:

Transformer Engine NVTX 標籤 五步內看到的 AllGather 可以確定的事
LayerNormLinear.backward.column_parallel_comm_input 40 筆 column-parallel backward 正在收集 input
Linear.backward.grad_output_preprocess 40 筆 backward 正在預處理 grad output;確切用途仍取決於平行化設定

再對照 Transformer Engine 現行原始碼,column_parallel_comm_input 這段會在需要時非同步呼叫 gather_along_first_dim:input 是稍後計算 weight gradient(wgrad)需要的資料,而中間的 data gradient(dgrad)就是可用來遮住這次通訊的計算窗口;進入 wgrad 前則必須等待 gather 完成。

這讓相位表有了更具體的解釋:backward AllGather 並不是等 dgrad 做完才開始,而是可以先非同步發起、嘗試和 dgrad 重疊;但在主檔 A 裡,device 4 只有 16.1% 藏住,表示可用的 dgrad 窗口或實際排程還不足以蓋住大部分通訊。這和 sequence/tensor parallel 的 backward 資料流相容,但不能把所有 backward AllGather 都歸成同一個原因,因為仍有其他 NVTX 標籤與未能精確配對的事件。

這個差別會直接影響調校方向:

  • TP 內的 GEMM/通訊重疊,應檢查 Megatron 的 --tp-comm-overlap 與 Transformer Engine userbuffers 路徑。
  • distributed optimizer 的梯度與參數同步,才對應 --overlap-grad-reduce--overlap-param-gather
  • 如果根因是某一層的 gather 發起得太晚,就要查依賴與排程;只調 NCCL protocol 未必有用。

換句話說,優化前要先把 collective 對回 call site,不能只看到「AllGather 很慢」就隨便開一個名稱相近的選項。

1.09 秒不是一大塊,而是 273 段累積

最後把 device 4 五步窗內「NCCL kernel 在跑、其他 GPU kernel 沒在跑」的區間逐段列出來。為了和 overlap_breakdown 採用相同邊界,這裡只納入完整落在 39.964–46.123 秒內的 kernel:

  • 共 273 段
  • 合計 1,094.7 ms
  • 中位數 5.4 ms
  • 最長 12.9 ms

分布並不是某一個超大的停頓。93 段小於 2 ms,合計 35 ms,只占曝露時間約 3.2%;真正的主力是 140 段 4–6 ms 的區間,合計約 758 ms,占 69.3%。這比較像「每層都有一筆 collective 藏住一小段、剩下 4–6 ms 露在外面」,反覆累積後才變成 1.09 秒。

因此,最長的 12.9 ms 不一定是第一個該處理的目標;它就算完全消失,對五步也只省十幾毫秒。更值得先做的是把 4–6 ms 那批事件對回 layer、NVTX call site 和前後依賴,確認它們為什麼規律地出現,再決定適合提早發起、做融合,還是調整平行化配置。小於 2 ms 的 93 段也不是「不用管」,只是目前合計占比小,優先級可以排後面。

device 4 的 NCCL-only 區間長度分布:273 段中有 140 段落在 4 到 6 ms,合計約 758 ms、占 69.3%;曝露時間主要是每層反覆出現的小等待,而不是單一大停頓

三個容易踩到的坑

第一,把 overlap ratio 當成跨 job 的成績單。它的上限會受模型結構、平行化方式與資料相依影響;比較同一個 job 改動前後,通常比拿兩個不同工作負載直接比高低更有意義。

第二,只看平均 overlap。主檔 A 的 39.3% 底下,其實是 16.1% 到 76.9% 的四格。若不先拆 collective 和 phase,很可能把時間花在本來就藏得不錯的 backward ReduceScatter,卻漏掉真正曝露的 backward AllGather。

第三,message size ÷ duration 當成鏈路利用率。這個商最多只能描述應用層的有效速率;要談 NVLink 吃了幾成,至少還要確認事件配對、collective 流量係數、拓撲,以及 rank 間的等待。

小結與明天

今天先把 overlap 的口徑定清楚:device 4 有 709.2 ms 的 NCCL 與其他 GPU kernel 同時執行,占 NCCL 時間 39.3%;另有 1,094.7 ms 是 NCCL-only,占 wall time 17.8%,但不能直接當成可省下的時間。

message size 也補齊了。每 rank、每步有 28 次 AllGather、18 次 ReduceScatter 和 2 次 4-byte AllReduce,邏輯 payload 約 11.47 GiB。最重要的是,平均數拆開後,backward AllGather 的 overlap 只有 16.1–22.0%;NVTX call site 進一步顯示,其中一部分是 wgrad 前需要的 input gather。

最後,這 1.09 秒不是一個大洞,而是 273 段小等待累積,其中 4–6 ms 的區間占了約七成。下一步要看的就不是「NCCL 總共跑多久」,而是哪些 rank 或哪些 layer 反覆晚到,讓本來可以非同步發起的 collective 還是露在外面。

參考資料


上一篇
Day 18|多卡訓練在傳什麼?讀懂 NCCL 的帳
系列文
GPU很忙?他真的有在做事嗎?19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言