iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0
Software Development

在 AI Compiler 工程師的路上系列 第 29

Day28:Loom-dataflow:hardware mapping、reuse analysis、broadcast

  • 分享至 

  • xImage
  •  

昨天的 02_explicit_memory_access.mlir 已經有 DRAM、L1 和 loom.copy,但所有 copy 的範圍仍是 [1, 1],也不知道 tile 要在哪一顆 core 執行。

版本範圍:今天使用舊 loom-dataflow standalone pipeline。完整的 0008 artifact 可從 examples/mm/IR 瀏覽,本文以 2026-07-31 的 loom-dataflow@39d2b858 核對 pass 行為。

今天內容主要位於 Stage 1:Dataflow Exploration

Stage 在這份 matmul 範例中的位置
0. Helion Frontend 0002 已完成 frontend、tensor canonicalization 與 explicit memory access
1. Dataflow Exploration 今天的 03 hardware mapping → 04 reuse analysis → 05 broadcast → staged ETG
2. ETG Resolution 完整架構中的下一階段;本篇舊 run_pipeline.sh 沒有執行
3. Block-Size Solve 完整架構中的 CP-SAT 階段;本篇舊 run_pipeline.sh 沒有執行
4. Materialization Day29 的舊 snapshot 使用 placeholder assignment;新版 monorepo 才能接 solver binding
TT-oriented rewrite Day29 觀察舊 Step 9 的 tt-opt;完整 TT backend lowering 屬於另一條後續路徑

在完整架構中,Stage 2、3 會產生 resolved ETG 與 solver artifact,不會各自對應一份新的 program MLIR。本篇使用的舊 standalone 流程停在 staged ETG。

本篇大綱

  • logical loop 如何放到實體 core mesh?
  • 哪些 core 或時間步會讀到相同資料?
  • 重複讀取能否改成一次搬移、多點接收?

最後再看舊 pipeline 如何產生 staged ETG constraint JSON,以及新版 ecolab-nus/loom monorepo 如何串接 ETG resolution、block-size search 與 materialization。

如何重現

這裡執行的是舊 standalone run_pipeline.sh。Step 6 只產生 staged ETG JSON,不會接著執行 MLAR resolution 或 CP-SAT。新版端到端流程可參考 2026-08-12 的 loom@f65d199b run_pipeline()

./run_pipeline.sh mm "[3,4,5,6]"

這個指令假設昨天的 02 artifact 已經存在,Step 3 還需要硬體規格

build/tool/loom-opt/single_stage/enumerate_hw_mapping \
  --input examples/mm/IR/02_explicit_memory_access.mlir \
  --hw_spec ../loom-mlar/tests/2d_mesh/2d_mesh_torus.mlir

Step 6 不會產生另一份 MLIR,而是把 05 轉成 constraint-space JSON,稍後會再說明。

03:把 logical iteration 映射到 8×8 mesh

以下三張對照圖直接摘錄同一個代表性 candidate 的真實 dump。圖中保留原本的 SSA 名稱、型別、attribute 與 operation 語法;... 只表示省略未顯示的行。

https://ithelp.ithome.com.tw/upload/images/20260829/20183319LQVgFvRD4P.png
完整 dump:Before:02_explicit_memory_access.mlirAfter:03_after_hardware_mapping.mlir

左側的 02 只有 logical tile index,右側的 03 加入 8×8 硬體 dimension,將 loop 拆成多層 spatial mapping,並用 temporal wave 補足 core mesh 一次容納不下的 tile,紅框是這一步真正新增的結構,matmul 的 logical shape 並沒有改變。

開頭合併了 ADL 硬體描述

adl.spatial_dim "dim_x", 8
adl.spatial_dim "dim_y", 8
adl.arch.scale "arch_mesh", [%dim_x, %dim_y]
adl.memory.array "mem_array_L1", [%dim_x, %dim_y]
adl.processor.dmover @proc_dram_l1_noc0
adl.processor.dmover @proc_dram_l1_noc1

IR 裡附上規格只是第一步,mapping pass 還需要知道

  • mesh 有哪些 physical dimension
  • 每個 dimension 的大小
  • 每個位置是否有 L1
  • DRAM ↔ L1、L1 ↔ L1 有哪些 data mover

為什麼一個 @matmul 變成 16 個 function

02 只有一個 @matmul,03 的 snapshot 則有 16 個 matmul__... function。每個 function 是 mapping candidate,還不是已經挑出來的最佳解。

像是

matmul__x8_y1y8__d0i0_d1i0_d2i1__f01

pass 會把追蹤資訊寫進 function suffix,方便區分候選

affine.parallel (%x) = (0) to (8) {
  affine.parallel (%y0) = (0) to (1) {
    affine.parallel (%y1) = (0) to (8) {
      ...
    } {loom.iter_type = #loom.iter_type<spatial>,
       loom.logical_level = 1 : i64,
       loom.physical_dim = @dim_y}
  } {loom.iter_type = #loom.iter_type<spatial>,
     loom.logical_level = 0 : i64,
     loom.physical_dim = @dim_y}
} {loom.iter_type = #loom.iter_type<spatial>,
   loom.logical_level = 0 : i64,
   loom.physical_dim = @dim_x}

這個 candidate 把 logical iteration 拆到 x=8y=1×8。大小為 1 的 level 看起來沒有增加平行度,但保留了 mapping 的階層資訊。

spatial loop 與 temporal wave 如何合成全域 tile index

硬體只有 8 個 x 座標,但 M 方向可能有超過 8 個 tile。因此 pass 另外產生 temporal loop,再重建原本的 logical index

%m_wave_count = arith.ceildivui %m_tiles, %c8
scf.for %wave = %c0 to %m_wave_count step %c1 {
  %mi = affine.apply affine_map<(d0, d1) -> (d0 + d1 * 8)>(%x, %wave)
}

也就是

logical_m_tile = core_x + wave * 8

同一顆 core 在不同 wave 處理不同 output tile,這就是 spatial mapping 與 temporal tiling 同時存在的原因。

04:reuse analysis 看的是 index dependency

https://ithelp.ithome.com.tw/upload/images/20260829/20183319ZLiI4kWyLX.png

完整 dump:Before:03_after_hardware_mapping.mlirAfter:04_after_reuse_analyzation.mlir

這一步幾乎不動 loop,只更新 loom.subview 的 reuse 屬性。A、B 的 offset 各自少依賴一個空間方向,因此出現 spatial reuse;代表性 candidate 中,A、B 也可跨 temporal wave 重用,C 則會隨 wave 寫到不同 output tile。

03 與 04 的 loop 結構、candidate 數量幾乎不變。主要差異在 loom.subview 的 reuse attribute

reuse : [seq = false, spat = true, temp = true]

這裡會直接分析 subview offset 是否依賴周圍 loop 的 induction variable,再決定能不能 reuse,不會看到 matmul 就硬套同一條規則。

用 A tile 推導 spatial reuse

A 的 subview 可寫成

A[m_tile * tm, k_tile * tk]

它依賴 m_tilek_tile,但不依賴 n_tile。所以當不同 core 只改變 N 座標時,它們仍然需要同一塊 A

C[m, n0] 需要 A[m, k]
C[m, n1] 也需要 A[m, k]

如果 N 被映射到某個 spatial dimension,A 就沿該方向有 spatial reuse。

用 B tile 推導另一個方向

B 的 subview 是

B[k_tile * tk, n_tile * tn]

它不依賴 m_tile,所以不同 M 座標的 core 可以共用 B。A 與 B 的 broadcast 方向因此通常互相垂直。

temporal reuse 代表什麼

假設 M tile grid 大於 dim_x=8,同一顆 core 會跑多個 temporal wave。若某個 operand 的 offset 不依賴該 wave,它就有 temporal reuse。

這裡要小心:temp = true 只表示分析發現「資料相同」,不保證程式已經把資料留在 L1,也不保證實際效能一定改善。是否利用這個機會,要看後面的 data movement 與容量 constraint。

C 為什麼不一定有 temporal reuse

代表性 candidate 的 output subview 是

reuse : [seq = false, spat = true, temp = false]

每個 temporal wave 通常對應不同 output tile,因此不能把前一個 wave 的 C 當成下一個 wave 的 C。這正好說明 reuse attribute 是逐一分析 memory access,不是整個 kernel 共用一組答案。

05:把 reuse 機會落成 broadcast region

https://ithelp.ithome.com.tw/upload/images/20260829/20183319WCtNiNKbUS.png

完整 dump:Before:04_after_reuse_analyzation.mlirAfter:05_after_enumerate_broadcast.mlir

reuse analysis 只回答「資料相同」,broadcast enumeration 才把這個機會寫成資料搬移範圍。A 從單點 copy 擴成 [1, 8],B 擴成 [8, 1];C 仍由自己的 owner 寫回,因此維持 [1, 1]

05 的代表性 candidate 名稱多了

__dim_y_level1_bc8_dim_x_level0_bc8_n

目前實作不會列舉所有 broadcast dimension 的子集合。它會保留 no-broadcast 選項,並在找到可 broadcast 的 dimension 時,建立啟用所有可用 dimension 的最佳候選,以免產生組合爆炸。

A tile:沿一個方向送給 8 個位置

loom.copy %a_src, %a_l1
  src_mem_space @mem_DRAM
  dst_mem_space @mem_L1,
  area : [1, 8]
  region : (UL : [%x, %c0], LR : [%x, %c7])

可把它讀成固定 x,y 從 0 到 7,總接收區域為 1 × 8

B tile:沿垂直方向送給 8 個位置

loom.copy %b_src, %b_l1
  src_mem_space @mem_DRAM
  dst_mem_space @mem_L1,
  area : [8, 1]
  region : (UL : [%c0, %y], LR : [%c7, %y])

這次固定 y,x 從 0 到 7。A/B broadcast 方向互換,正好對應前面從 subview dependency 得到的 reuse。

area 與 region 差別

  • area : [8, 1] 描述區域大小。
  • UL / LR 描述這個區域在 mesh 上的左上與右下座標。

相同 area 可以出現在不同位置,因此 compiler 需要兩者才能表示完整的搬移計畫。

Output writeback 仍是單點

area : [1, 1]
region : (UL : [%x, %y], LR : [%x, %y])

因為每個 C tile 有自己的 owner,最後寫回 DRAM 不需要送給整排 core。

Step 6:Staged ETG 把 IR 轉成 constraint model

staged_etg 讀取 05 與硬體規格,產生

examples/mm/constraint_space/staged_etg_dump.json

其中一個 stage 會把運算與搬移表示成可估成本的 function

{
  "name": "matmul_f16",
  "symbols": ["K", "M", "N"]
}

兩個輸入搬移則會帶上 broadcast coefficient

{
  "name": "dram_to_l1_bcst",
  "symbols": ["M", "N", "bcst_x", "bcst_y"]
}

這份 JSON 的角色是 constraint/ETG 輸入,它把 IR 中的

loop trip count
compute stage
memory stage
symbolic tile size
broadcast coefficient

整理成後續 architecture model 與 solver 能消化的結構。在 2026-08-12 的新版快照中,完整 solver 位於 ecolab-nus/loom monorepo 的 loom/solver/loom/pipeline.py 會依序執行

loom-dataflow exploration
  → p01_exploration_etg.json
  → loom-mlar 解析硬體效能模型
  → p02_resolved_etg.json
  → loom.solver:CPMpy + OR-Tools CP-SAT
  → block-size assignments
  → loom-dataflow materialization

loom.solver 會把 resolved ETG 裡的 symbol domain、hard constraint 與 timing objective 建成 CPMpy model,再交給 CP-SAT solver 求解。它尋找的是可行且成本較低的 block-size assignment。

explored MLIR + exploration ETG
  → loom-mlar resolution
  → resolved ETG
  → CPMpy/CP-SAT solver
  → block-size assignments
  → integrated materialization
  → bufferized Loom MLIR

新版 TileLoom 整合 API 會把 solver 產生的 block-size assignments 交給 Materialize。要驗證一次可追溯的求解與 materialization,可以從 ecolab-nus/loomloom/pipeline.py 執行,並核對 exploration ETG、resolved ETG、solver log 與最後寫回 IR 的 block sizes。這段是新版流程補充,不是本篇舊 run_pipeline.sh 的執行結果。

如何逐層驗證這三步

讀 dump 時可以依序問

  1. 03 是否只改 mapping,沒有改 A/B/C 的 logical shape?
  2. 原本一個 function 展開成幾個 candidate?這份 snapshot 是 16 個。
  3. spatial loop 的 upper bound 是否符合硬體 dimension?
  4. core_id + wave × core_count 是否重建正確 logical tile index?
  5. 04 的 reuse 是否能由 subview offset 的 dependency 推導?
  6. 05 的 broadcast 方向是否與 reuse 方向一致?
  7. output writeback 是否仍是 [1, 1]
  8. ETG JSON 描述的是候選與 constraint,還是已求解的結果?

今天的結論

三個階段的因果關係是

hardware mapping
  決定 logical loop 落在哪些 physical dimension
      ↓
reuse analysis
  從 subview offset dependency 找出相同資料
      ↓
broadcast enumeration
  把 reuse 轉成具體 area 與 region
      ↓
staged ETG
  產生 constraint model
      ↓
MLAR resolution → CP-SAT 求解 → integrated materialization

明天會來看 materialization、One-Shot Bufferization 和 TT-specific optimization,並分清楚固定 tile size、tensor 轉 memref 與 linalg.matmulloom.matmul 各在哪一步發生。

參考資料


上一篇
Day27:Loom-dataflow :matmul IR 從 frontend 到 explicit memory access
下一篇
Day29:Loom-dataflow:Materialize、One-Shot Bufferization 和 TT opt
系列文
在 AI Compiler 工程師的路上32
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言