iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
Software Development

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

Day22:TT-Metal:用 reader、compute、writer 看 Tenstorrent 程式模型

  • 分享至 

  • xImage
  •  

昨天先看 Wormhole 的 tile 和 NoC,今天再往下走一層,看 Tenstorrent software stack,最後把焦點放到 TT-Metal / Metalium。我最想先搞懂兩件事,上層模型怎麼一路接到硬體,還有 TT-Metal 程式最常見的 reader、compute、writer 分工。

本篇大綱

  • 先看 Tenstorrent software stack:TT-Forge、TT-NN、TT-Metal、TT-LLK、TT-Lang。
  • 再看 TT-Metalium 要解的程式問題。
  • 再拆 reader / compute / writer 三種 kernel。
  • 接著看 circular buffer 為什麼會出現在每個範例裡。
  • 最後把 TT-Metal 接到下一篇的 TT-MLIR,再準備進入 Triton NPU Flow 與 TileLoom。

Tenstorrent software stack

在看 TT-Metal 之前,可以先理解 Tenstorrent 的 software stack。

https://ithelp.ithome.com.tw/upload/images/20260821/20183319kIwoH3jckJ.png

圖片來源:Tenstorrent 官方 GitHub 組織頁面的 AI SW 圖

TT-Forge 是比較上層的 compiler stack。官方文件把它描述成 end-to-end compiler stack,包含 TT-XLA、TT-Forge-ONNX、TT-MLIR、TT-Blacksmith 等元件。它負責把 PyTorch、JAX、ONNX、TensorFlow 這類模型來源接進 Tenstorrent 的 compiler pipeline。

TT-NN 是高階 neural network operation library,有 Python 和 C++ API。對熟悉 PyTorch 的開發者來說,它比較像「我想呼叫一個 tensor op 或跑一段模型,不想一開始就管理 NoC、L1、circular buffer」。TT-NN 底下仍然建立在 TT-Metalium 上。

TT-Metalium,也常被簡稱成 TT-Metal,是低階 C++ SDK。官方文件把它定位成讓開發者直接控制 Tenstorrent 硬體的 programming environment。這一層會碰到 program、kernel、buffer、command queue、NoC、L1、Tensix compute。

TT-LLK 可以先理解成更靠近 Tensix Engine 的 low-level kernel 層。Tenstorrent 的 TT-LLK 頁面提到 LLK 會控制 Tensix Engine,提供使用 Tensix instruction set 的路徑,讓低階數學操作能貼近硬體執行。

TT-Lang 則是比較新的 Python-based DSL,用來寫 Tenstorrent 上的高效 custom kernel / fused op。官方文件把 TT-Lang 放在 TT-NN 和 TT-Metalium 之間的使用情境:它讓開發者用 Python DSL 顯式描述 computation 和 data movement,也支援 simulator / compiler / hardware flow。

所以今天看 TT-Metal 時,可以先把它放在這個位置

想快速跑模型:
  TT-Forge / TT-NN

想寫常見 NN op:
  TT-NN

想寫 fused custom kernel,但希望用 Python DSL:
  TT-Lang

想直接控制 buffer、NoC、kernel、program:
  TT-Metalium

想理解 Tensix engine 最低階數學 kernel:
  TT-LLK

後面讀 TileLoom 會一直碰到 TT-Metalium,所以今天先把這層看懂。可以暫時把 TileLoom 想成一個 compiler 系統:它會把高階 tile program 轉成能在 Tenstorrent 上執行的資料流計畫,最後再落到 TT-Metalium C++ API。

TT-Metalium 的位置

Tenstorrent 官方 guide 把 TT-Metalium 定位成低階 programming model。它負責讓開發者在 Tensix processor 上建立 program、配置 buffer、啟動 kernel、管理資料流。

如果只畫出這一篇要追的低階落點,可以寫成

TT-Forge 編譯模型 ───────────┐
TT-NN 呼叫高階 tensor op ────┼─→ TT-Metalium → Tensix hardware
TT-Lang 寫 fused custom kernel ┘

TT-Forge 比較像模型進 Tenstorrent compiler stack 的入口。TT-NN 讓你用高階 tensor op 跑模型或組 op。TT-Lang 讓你用 Python DSL 寫 custom / fused kernel。TT-Metalium 再往下,會直接碰到資料從 DRAM 搬到 L1、kernel 等 circular buffer、compute 做 tile operation、writer 把結果送回去。

這也是後面 TileLoom 會選 TT-Metalium 當 backend 的原因。TileLoom 做 dataflow planning 後,需要一個能表達 buffer allocation、synchronization、data movement、compute operation 的後端。

一個 TT-Metalium 程式有 host 與 device 兩側

直接看 reader、compute、writer 時,很容易漏掉「誰建立這三個 kernel」。完整程式還有 host 端,兩側分工如下

Host C++ program
  1. 開啟 device、取得 command queue
  2. 建立 DRAM/L1 buffer 與 device program
  3. 建立 circular buffer
  4. 建立 reader、compute、writer kernel
  5. 設定 compile-time/runtime arguments
  6. 把輸入寫到 device、enqueue program、讀回結果

Device kernels on Tensix
  reader → compute → writer

Host 端負責配置與啟動,device kernel 負責實際資料搬移與運算。本文聚焦 device 端,是因為這三個 kernel 最能把 Day21 的 NoC、L1、Unpack/Math/Pack 接到程式碼;若要自己跑完整範例,仍需要 host 端把 program 組起來。

三種 kernel:reader、compute、writer

Metalium guide 裡的典型資料流會用三種 kernel

Reader kernel
  -> 從 DRAM 讀 input tile
  -> 寫進 circular buffer

Compute kernel
  -> 等 input circular buffer 有資料
  -> 做 tile compute
  -> 寫到 output circular buffer

Writer kernel
  -> 等 output circular buffer 有資料
  -> 寫回 DRAM 或指定輸出位置

官方圖把這三種 kernel 實際框在 Tensix 方塊圖上

https://ithelp.ithome.com.tw/upload/images/20260821/2018331975kaH9cOXy.png

圖片來源:Tenstorrent TT Architecture and Metalium Guide。圖中的 Movement kernel 0/1 對應常見的 reader/writer 分工;這是典型配置,其他 operation 也能採用不同安排。

這張圖回答了「為什麼程式看起來只有三個 kernel,硬體卻有五個 Baby RISC-V core」:reader 與 writer 各由一個 Data Movement core 執行;同一份 compute kernel 會針對 Unpack、Math、Pack 編譯成三份對應的 binary,由三個 core 並行合作。開發者不需要把 compute kernel 手寫成三個 thread。

CPU 函式通常依序完成載入、運算與儲存;Tenstorrent 在這裡採用一條小型管線:資料搬移和計算可以分開排程,中間用 circular buffer 溝通。

用 vector addition 看三支 kernel

下面用 C = A + B 看一個 tile 如何穿過三支 kernel。為了先看懂協作關係,程式碼省略 #includeTensorAccessor 建立、compile-time arguments 與錯誤處理,只保留主要控制流程。因此,這些片段是依照官方範例縮短的教學版,不能單獨複製編譯。完整 host 與 device 程式可對照官方的 Eltwise binary example

三個 circular buffer 的角色先固定下來

Circular buffer 放什麼 Producer Consumer
cb_in0c_0 A 的 input tile reader compute
cb_in1c_1 B 的 input tile reader compute
cb_outc_16 C 的 output tile compute writer

Reader:把 A、B 從 DRAM 搬進 input circular buffers

Reader 的迴圈可以縮成

for (uint32_t tile = 0; tile < n_tiles; ++tile) {
    cb_reserve_back(cb_in0, 1);
    cb_reserve_back(cb_in1, 1);

    noc_async_read_page(tile, input_a, get_write_ptr(cb_in0));
    noc_async_read_page(tile, input_b, get_write_ptr(cb_in1));
    noc_async_read_barrier();

    cb_push_back(cb_in0, 1);
    cb_push_back(cb_in1, 1);
}

這裡的順序不能交換

  1. cb_reserve_back 先確認 circular buffer 有可寫入的空位。
  2. get_write_ptr 取得這個空位在 L1 的位址。
  3. noc_async_read_page 發出 DRAM 到 L1 的非同步 NoC read。
  4. noc_async_read_barrier 等兩筆讀取完成。
  5. cb_push_back 才把 A、B 公布給 compute。

如果 reader 在 barrier 前就呼叫 cb_push_back,compute 可能看到「buffer 已有資料」,實際的 NoC read 卻還沒完成。

Compute:等待兩個 input tiles,做加法,再產生 output tile

Compute kernel 先用 compute_kernel_hw_startupadd_init 準備運算,再進入 tile loop

compute_kernel_hw_startup(cb_in0, cb_in1, cb_out);
add_init(cb_in0, cb_in1);

for (uint32_t tile = 0; tile < n_tiles; ++tile) {
    cb_wait_front(cb_in0, 1);
    cb_wait_front(cb_in1, 1);

    tile_regs_acquire();
    add_tiles(cb_in0, cb_in1, 0, 0, dst_reg);
    tile_regs_commit();

    cb_reserve_back(cb_out, 1);
    tile_regs_wait();
    pack_tile(dst_reg, cb_out, 0);
    tile_regs_release();

    cb_pop_front(cb_in0, 1);
    cb_pop_front(cb_in1, 1);
    cb_push_back(cb_out, 1);
}

這段可分成三個部分

  • cb_wait_front:等 reader 公布 A、B。
  • add_tiles:把兩個 input tiles 加到 destination register。
  • pack_tile:把 register 裡的結果 pack 到 cb_out 的 L1 空間。

最後,cb_pop_front 表示 input tiles 已經用完;cb_push_back(cb_out, 1) 則向 writer 公布一個 output tile。

Compute kernel 雖然只有一份 source,Metalium 會替 Unpack、Math、Pack 三個 Baby RISC-V core 產生對應 binary。像 add_tiles 會同時涉及 Unpack 與 Math,pack_tile 則由 Pack 負責。

Writer:把 output circular buffer 寫回 DRAM

Writer 只需要等待 compute 的結果

for (uint32_t tile = 0; tile < n_tiles; ++tile) {
    cb_wait_front(cb_out, 1);

    noc_async_write_page(tile, output_c, get_read_ptr(cb_out));
    noc_async_write_barrier();

    cb_pop_front(cb_out, 1);
}

cb_wait_front 確認 cb_out 有資料;get_read_ptr 取得結果的 L1 位址;NoC write 完成後,cb_pop_front 才能釋放這個 slot,讓 compute 後續重用。

Host:把三支 kernel 放進同一個 program

Reader、compute、writer 都是 device kernels。Host 會把它們建立在同一個 Metalium program,替三支 kernel 分別設定 runtime arguments,再 enqueue 整個 program

reader  = CreateKernel(program, reader_source,  core, DataMovementConfig{RISCV_0, ...});
writer  = CreateKernel(program, writer_source,  core, DataMovementConfig{RISCV_1, ...});
compute = CreateKernel(program, compute_source, core, ComputeConfig{...});

SetRuntimeArgs(program, reader,  core, {a_addr, b_addr, n_tiles});
SetRuntimeArgs(program, compute, core, {n_tiles});
SetRuntimeArgs(program, writer,  core, {c_addr, n_tiles});

EnqueueMeshWorkload(command_queue, workload, false);

三支 kernel 啟動後可以同時向前推進。Circular buffer 的 producer/consumer 狀態與 NoC barrier 共同維持資料相依:reader 可以開始搬下一個 tile,compute 處理目前的 tile,writer 同時送出上一個結果。

跟著一個 tile 走一次

以第 i 個 output tile 為例,可以把三個 kernel 的協作排成以下時間順序

  1. Reader 呼叫 cb_reserve_back,先確認 input circular buffer 還有空位。
  2. Reader 取得該空位的 L1 address,發出 A、B 的 noc_async_read_page
  3. noc_async_read_barrier 確認兩次 NoC read 已完成,reader 才能 cb_push_back 公布資料。
  4. Compute 的 cb_wait_front 等到 A、B 都可用,再由 Unpack core 把資料送進 compute engine。
  5. Math core 執行 add_tiles,結果先停在 destination register。
  6. Pack core 等 output circular buffer 有空位,把結果 pack_tile 到 L1,再 cb_push_back
  7. Writer 的 cb_wait_front 看到結果後,發出 noc_async_write_page
  8. noc_async_write_barrier 確認資料已送出,writer 才能 cb_pop_front 釋放空位。

這個順序裡有兩種等待:NoC barrier 等「硬體搬完資料」,circular buffer API 等「producer/consumer 的 buffer 狀態」。兩者解決的問題不同,少了任何一種都可能在資料尚未準備好時往下執行。

Circular buffer:kernel 之間的契約

Circular buffer 是 TT-Metal 程式裡很重要的結構,它除了保存 tile,也帶著同步語意。

https://ithelp.ithome.com.tw/upload/images/20260821/20183319ldAoDz46Vv.png

圖片來源:Tenstorrent TT Architecture and Metalium Guide。綠色方塊代表位於 SRAM 的 circular buffer。Circular buffer 也能讓同一個 kernel 把資料送回自己,並不只用於三段式 pipeline。

對 reader 來說

  • cb_reserve_back 表示我要寫一個 tile 進來,先保留空間。
  • cb_push_back 表示資料已經準備好,可以給下一段使用。

對 compute 來說

  • cb_wait_front 表示我要等資料真的出現。
  • cb_pop_front 表示這筆資料我用完了,可以釋放。

Vector addition 會使用至少三個 circular buffer:cb_in0 放 A、cb_in1 放 B、cb_out 放 C。Reader 是前兩個 buffer 的 producer,compute 是它們的 consumer;compute 同時又是 cb_out 的 producer,writer 則是 consumer。用 producer/consumer 角色來讀 API,比背函式名稱更容易檢查同步是否完整。

這個設計讓 reader、compute、writer 可以各自前進。只要 buffer 和 barrier 設計正確,資料搬移和計算就能形成 pipeline。

這裡要小心 race condition。guide 也強調 NoC interface 和 compute engine 是獨立 peripheral,很多操作是 asynchronous。程式要自己放 barrier 和 buffer synchronization,否則 compute 可能讀到還沒搬完的資料,writer 也可能寫出還沒算完的結果。

Compute kernel 內部還會分工

Metalium guide 裡提到 compute API 會被編譯成給不同小核心執行的 code section,例如 Unpack、Math、Pack。像 add_tiles 這類 API 會對應到資料 unpack、FPU 運算、pack result 這些硬體步驟。

可以先用這個簡化圖理解

input circular buffer
  -> Unpack
  -> Math / FPU
  -> Pack
  -> output circular buffer

官方資料流圖把 NoC 與 L1 也放回來

https://ithelp.ithome.com.tw/upload/images/20260821/20183319D6LJgWYSn3.png

圖片來源:Tenstorrent TT Architecture and Metalium Guide。粉紅線是這張圖示範的一條典型資料路徑。

把三張圖合起來後,可以得到一個實用的讀碼順序:先看資料從哪個 DRAM/core 進來,再看它進入哪個 circular buffer,最後才看 compute API 做什麼。TT-Metalium 的錯誤常發生在 address、tile count、buffer 容量或同步;若只盯著 add_tiles,會漏掉大部分資料流。

這和昨天的架構能接起來,Tensix core 裡的資料搬移、unpack、math、pack 由專門部件分工完成。Metalium API 再把這些步驟包成開發者可以呼叫的單位。

從 TT-Metal 反推 compiler 要做什麼

如果手寫 TT-Metal,開發者要決定

  • input / output buffer 放哪裡。
  • circular buffer 要多大。
  • reader / compute / writer 怎麼同步。
  • 哪些資料從 DRAM 讀,哪些資料從其他 core 來。
  • tile operation 怎麼接 compute API。

後面要看的 TileLoom,價值就在這裡。它希望讓使用者從 Triton / Helion 這類 tile-based program 開始,讓 compiler 自動規劃一部分 dataflow,再產生能接到 TT-Metalium 的程式。

今天先走到這裡

今天先記住幾個 TT-Metal 基本觀念

  • TT-Metalium 是 Tenstorrent 的低階 programming model。
  • 常見 kernel 分工是 reader、compute、writer。
  • Circular buffer 負責資料暫存,也負責 kernel 之間的同步契約。
  • NoC async operation 需要 barrier,否則資料流會出錯。
  • Compute kernel 內部會對應 Unpack、Math、Pack 等硬體步驟。
  • TileLoom 後面要做的事,就是把高階 tile program 轉成能落到這些 buffer、copy、compute、sync API 的形式。

明天先來看一下 TT-MLIR 的 dialect 與 pipeline 文件,看 TTIR、TTNN、D2M、TTKernel 和 TTMetal 分別保留什麼資訊。

參考資料


上一篇
Day21:Tenstorrent 架構介紹: Wormhole/Blackhole
下一篇
Day23:TT-MLIR:Tenstorrent 的 MLIR dialect 與 compiler flow
系列文
在 AI Compiler 工程師的路上24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言