Download code/hamer/kernels/cmm/mover1.cpp from changh95/hamer-p150: direct link, hf CLI and curl.
- Browser
- Download file 28.7 kB
-
https://huggingface.co/changh95/hamer-p150/resolve/main/code/hamer/kernels/cmm/mover1.cpp
- Command line
-
hf download hf://changh95/hamer-p150/code/hamer/kernels/cmm/mover1.cpp
-
curl -L -o mover1.cpp https://huggingface.co/changh95/hamer-p150/resolve/main/code/hamer/kernels/cmm/mover1.cpp
28.7 kB
| // SPDX-FileCopyrightText: © 2026 Tenstorrent Inc. | |
| // SPDX-License-Identifier: Apache-2.0 | |
| // cmm data mover v5 ("one RISC per operand"): | |
| // PAR 0 (NCRISC, NOC_0): BOTH weight pipes (even K blocks -> c_w, odd -> c_w1). Member 0 of the weight group | |
| // (the top row of the column) is the column's ONLY weight sender: it reads the host-packed contiguous blocks | |
| // from DRAM (tt_kernels.CmmWeight; one-packet reads tagged with transaction ids 1..3, the library DRAM-sharded | |
| // reader pattern -- never trid 0) into a staging ring of SS slots, LA blocks ahead, and multicasts each block | |
| // down the column (NOC_0 from the top row: no wrap-around). The packed layout removed the per-tile issue cost | |
| // that bound the library's single in1 sender; the top-row sender avoids the mid-column multicast penalty. | |
| // Optionally the first CMM_PF pairs are already in a persistent L1 buffer (tt_kernels.CmmPrefetch). | |
| // PAR 1 (BRISC, NOC_1): BOTH activation pipes (contiguous owners in the row, odd blocks rotated by one member), | |
| // the bias read and the output write. | |
| // compile-time / runtime args: as mover.cpp (the packed table follows the activation group; then the prefetch | |
| // buffer address when CMM_PF). | |
| FORCE_INLINE uint32_t a_page(uint32_t m, uint32_t kk, uint32_t KT) { | |
| return (kk / CMM_A_DT) * (CMM_A_MT * CMM_A_DT) + m * CMM_A_DT + kk % CMM_A_DT; | |
| return m * KT + kk; | |
| } | |
| // round 12 (xmm, CMM_W2): TWO weight senders per group -- the even K blocks are read and multicast by member 0 on NOC_0 | |
| // (NCRISC), the odd ones by member G-1 on NOC_1 (BRISC; with CMM_A_RES it has no in0 work). Twice the DRAM readers, | |
| // each with half the bytes, and the odd blocks' DRAM responses / multicasts use the other NoC. The owner reads its next | |
| // block straight into its own next ring slot (reserving two blocks ahead) while the current one is multicast in place. | |
| template <uint32_t NBP, uint32_t S, uint32_t BT, uint32_t SEM0> | |
| FORCE_INLINE void w2_run(Pipe<NBP, S, BT, SEM0>& p, uint32_t par, uint32_t w_addr, uint32_t wtab, uint32_t blk_bytes) { | |
| NoOther none; | |
| if (p.g.me == p.single_m) { | |
| auto fetch = [&](uint32_t j) { | |
| CZONE("WISS"); | |
| const uint32_t d = get_arg_val<uint32_t>(wtab + 2 * j + par); | |
| uint64_t src = get_noc_addr_from_bank_id<true>(d & 0xff, w_addr + (d >> 8) * blk_bytes); | |
| uint32_t dst = p.slot_addr(j), left = blk_bytes; | |
| const uint32_t t = 1 + j % 3; | |
| noc_async_read_set_trid(t); | |
| while (noc_available_transactions(noc_index, t) < ((NOC_MAX_TRANSACTION_ID_COUNT + 1) / 2)) { | |
| } | |
| while (left) { | |
| const uint32_t n = left < NOC_MAX_BURST_SIZE ? left : NOC_MAX_BURST_SIZE; | |
| noc_async_read<NOC_MAX_BURST_SIZE>(src, dst, n); | |
| src += n; | |
| dst += n; | |
| left -= n; | |
| } | |
| }; | |
| // F blocks in flight: blocks 0 .. F-1 up front; once block j has landed, block j + F is issued (its slot: the | |
| // ring holds S blocks, F + 1 <= S) BEFORE block j is multicast, so F reads stay in flight during the multicast | |
| constexpr uint32_t F = CMM_W2F; | |
| static_assert(F + 1 <= S && F <= 3, "CMM_W2F: ring slots / trids"); | |
| for (uint32_t j = 0; j < F && j < NBP; ++j) { | |
| cb_reserve_back(p.cb, (j + 1) * BT); | |
| fetch(j); | |
| } | |
| for (uint32_t j = 0; j < NBP; ++j) { | |
| { | |
| CZONE("WDRAM"); | |
| noc_async_read_barrier_with_trid(1 + j % 3); | |
| } | |
| if (j + F < NBP) { | |
| cb_reserve_back(p.cb, (F + 1) * BT); // slots of blocks j .. j + F (j not pushed yet) all free | |
| fetch(j + F); | |
| } | |
| const uint32_t slot = p.slot_addr(j); | |
| p.send(slot, slot, j, none); | |
| cb_push_back(p.cb, BT); | |
| } | |
| noc_async_read_set_trid(0); | |
| } else { | |
| for (uint32_t j = 0; j < NBP; ++j) { | |
| p.recv_step2(j, none); | |
| } | |
| } | |
| p.finish(); | |
| } | |
| // round 12 (CMM_A2N): keep the ready signals of two other pipes flowing while one pipe waits (NCRISC runs the two weight | |
| // pipes AND the odd activation pipe) | |
| template <typename A, typename B> | |
| struct Two { | |
| A* a; | |
| B* b; | |
| FORCE_INLINE void signal_free() { | |
| a->signal_free(); | |
| b->signal_free(); | |
| } | |
| }; | |
| void kernel_main() { | |
| constexpr uint32_t NBP = get_compile_time_arg_val(0); | |
| constexpr uint32_t KB = get_compile_time_arg_val(1); | |
| constexpr uint32_t NT = get_compile_time_arg_val(2); | |
| constexpr uint32_t NTOT = get_compile_time_arg_val(3); | |
| constexpr uint32_t KT = get_compile_time_arg_val(4); | |
| constexpr uint32_t S = get_compile_time_arg_val(5); | |
| constexpr uint32_t PAR = get_compile_time_arg_val(6); | |
| constexpr auto w_args = TensorAccessorArgs<7>(); | |
| constexpr auto b_args = TensorAccessorArgs<w_args.next_compile_time_args_offset()>(); | |
| constexpr auto a_args = TensorAccessorArgs<b_args.next_compile_time_args_offset()>(); | |
| constexpr auto o_args = TensorAccessorArgs<a_args.next_compile_time_args_offset()>(); // output (page writes) | |
| constexpr uint32_t BT = KB * NT; | |
| constexpr uint32_t NB = 2 * NBP; | |
| constexpr uint32_t MPC = CMM_MPC; // round 11 (layout k2): token tile rows per core (activation block = KB x MPC) | |
| constexpr uint32_t MPC = 1; | |
| constexpr uint32_t ABT = KB * MPC; | |
| constexpr uint32_t c_a0 = 0, c_w0 = 1, c_bias = 2, c_wst = 3, c_ast0 = 4, c_a1 = 5, c_w1 = 6, c_ast1 = 8; | |
| constexpr uint32_t c_out = 16; | |
| const uint32_t w_addr = get_arg_val<uint32_t>(0); | |
| const uint32_t b_addr = get_arg_val<uint32_t>(1); | |
| const uint32_t a_addr = get_arg_val<uint32_t>(2); | |
| const uint32_t n0 = get_arg_val<uint32_t>(3); | |
| const uint32_t m = get_arg_val<uint32_t>(4); | |
| LineGroup wg, ag; | |
| wg.parse(9); | |
| ag.parse(9 + 13 + 2 * wg.G); | |
| const uint32_t wtab = ag.args + 2 * ag.G; // per global K block of this column: bank | slot << 8 | |
| if constexpr (PAR == 0) { | |
| static_assert(CMM_LA == 1, "CMM_W2: one block ahead per sender"); | |
| static_assert(false, "CMM_W2 without the weight prefetch"); | |
| { | |
| Pipe<NBP, S, BT, 0> p0; | |
| p0.g = wg; | |
| p0.single = true; | |
| p0.single_m = 0; | |
| p0.init(c_w0, get_write_ptr(9)); | |
| w2_run(p0, 0, w_addr, wtab, BT * p0.page); | |
| } | |
| if (false) { | |
| // ---------------- weights (NCRISC / NOC_0) ---------------- | |
| Pipe<NBP, S, BT, 0> p0; | |
| Pipe<NBP, S, BT, S> p1; | |
| p0.g = wg; | |
| p1.g = wg; | |
| p0.single = true; | |
| p1.single = true; | |
| const uint32_t flags = get_write_ptr(9); | |
| p0.init(c_w0, flags); | |
| p1.init(c_w1, flags + 16 * (S + 1)); | |
| // round 12: the ODD activation blocks run on this RISC / NOC_0 (the even ones stay on the BRISC / NOC_1): two | |
| // owner hand-over chains per grid row in parallel on the two NoCs instead of one serial chain | |
| static_assert(MPC >= 1, ""); | |
| static_assert(false, "CMM_A2N: k2 / out cmm with the local (GELU-helper) activation layout only"); | |
| Pipe<NBP, S, ABT, 3 * S> q1; | |
| q1.g = ag; | |
| q1.g.rot = (q1.g.rot + 1) % q1.g.G; // odd blocks: the next member (as on the BRISC before) | |
| q1.init(c_a1, get_write_ptr(10) + 16 * (S + 1)); | |
| Two<decltype(p1), decltype(q1)> o_w0{&p1, &q1}; | |
| Two<decltype(p0), decltype(q1)> o_w1{&p0, &q1}; | |
| Two<decltype(p0), decltype(p1)> o_a1{&p0, &p1}; | |
| auto q1_step = [&](uint32_t j) { | |
| if (q1.owner(j)) { | |
| volatile tt_l1_ptr uint32_t* gs = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(get_semaphore(CMM_GELU_SEM)); | |
| while (true) { | |
| invalidate_l1_cache(); | |
| if (*gs >= ABT) { | |
| break; | |
| } | |
| p0.signal_free(); | |
| p1.signal_free(); | |
| q1.signal_free(); | |
| } | |
| q1.step_from2(j, a_addr + q1.own_index(j) * ABT * q1.page, o_a1); | |
| } else { | |
| q1.recv_step2(j, o_a1); | |
| } | |
| }; | |
| const uint32_t wpage = p0.page; | |
| for (uint32_t j = 0; j < NB; ++j) { | |
| const uint32_t c = (j & 1) ? c_w1 : c_w0; | |
| cb_reserve_back(c, BT); | |
| cb_push_back(c, BT); | |
| } | |
| if (wg.me == 0) { | |
| const uint32_t blk_bytes = BT * wpage; | |
| const uint32_t stg = get_write_ptr(c_wst); | |
| const uint32_t pf = get_arg_val<uint32_t>(wtab + NB); | |
| constexpr uint32_t PFB = 2 * CMM_PF; // prefetched blocks (sequence numbers 0 .. PFB-1) | |
| constexpr uint32_t PFB = 0; | |
| // block s (sequence = 2j + parity) -> its staging slot / trid | |
| auto slot = [&](uint32_t s) -> uint32_t { | |
| if (s < PFB) { | |
| return pf + s * blk_bytes; | |
| } | |
| return stg + ((s - PFB) % CMM_SS) * blk_bytes; | |
| }; | |
| auto trid = [&](uint32_t s) -> uint32_t { return 1 + (s - PFB) % 3; }; | |
| // round 8: no staging ring -- a DRAM block lands straight in this sender's own ring slot of its pipe (the | |
| // next unpushed block of that pipe when LA == 1, so cb_reserve_back gives exactly its slot and waits until | |
| // the compute freed it), and is then multicast to the OTHER members only (Pipe::send in-place mode) | |
| static_assert(CMM_LA == 1, "CMM_NOSTAGE needs a lookahead of exactly one block"); | |
| uint32_t ring[2] = {0, 0}; | |
| auto slot = [&](uint32_t s) -> uint32_t { | |
| if (s < PFB) { | |
| return pf + s * blk_bytes; | |
| } | |
| return ring[s & 1]; | |
| }; | |
| auto fetch = [&](uint32_t s) { | |
| if (s >= NB || s < PFB) { | |
| return; | |
| } | |
| { | |
| const uint32_t cbp = (s & 1) ? c_w1 : c_w0; | |
| cb_reserve_back(cbp, BT); | |
| ring[s & 1] = get_write_ptr(cbp); | |
| } | |
| return; | |
| if (s < CMM_SKIP_FIRST) { | |
| return; | |
| } | |
| CZONE("WISS"); | |
| const uint32_t d = get_arg_val<uint32_t>(wtab + s); | |
| uint64_t src = get_noc_addr_from_bank_id<true>(d & 0xff, w_addr + (d >> 8) * blk_bytes); | |
| uint32_t dst = slot(s), left = blk_bytes; | |
| const uint32_t t = trid(s); | |
| noc_async_read_set_trid(t); | |
| while (noc_available_transactions(noc_index, t) < ((NOC_MAX_TRANSACTION_ID_COUNT + 1) / 2)) { | |
| } | |
| while (left) { | |
| const uint32_t n = left < NOC_MAX_BURST_SIZE ? left : NOC_MAX_BURST_SIZE; | |
| noc_async_read<NOC_MAX_BURST_SIZE>(src, dst, n); | |
| src += n; | |
| dst += n; | |
| left -= n; | |
| } | |
| }; | |
| static_assert(CMM_LA + 1 <= CMM_SS && CMM_SS <= 3, "staging ring / trid ring"); | |
| // blocks PFB .. PFB+LA-1 are fetched up front (except in-place with prefetched blocks ahead of them: their | |
| // pipe slot is not the next one yet), every later block LA steps ahead -- each block exactly once (round 8: | |
| // the old loop fetched block PFB a second time when CMM_PF > 0) | |
| constexpr uint32_t PRE = PFB == 0 ? CMM_LA : 0; | |
| constexpr uint32_t PRE = CMM_LA; | |
| for (uint32_t s = PFB; s < PFB + PRE; ++s) { | |
| fetch(s); | |
| } | |
| for (uint32_t j = 0; j < NBP; ++j) { | |
| for (uint32_t par = 0; par < 2; ++par) { | |
| const uint32_t s = 2 * j + par; | |
| fetch(s + CMM_LA); | |
| if (s + CMM_LA >= PFB + PRE) { | |
| fetch(s + CMM_LA); | |
| } | |
| if (s >= PFB) { | |
| CZONE("WDRAM"); | |
| noc_async_read_barrier_with_trid(trid(s)); | |
| } | |
| if (par == 0) { | |
| p0.step_from2(j, slot(s), CMM_OW0); | |
| } else { | |
| p1.step_from2(j, slot(s), CMM_OW1); | |
| CMM_Q1STEP(j); | |
| } | |
| } | |
| } | |
| } else { | |
| for (uint32_t j = 0; j < NBP; ++j) { | |
| p0.recv_step2(j, CMM_OW0); | |
| p1.recv_step2(j, CMM_OW1); | |
| CMM_Q1STEP(j); | |
| } | |
| } | |
| p0.finish(); | |
| } | |
| { | |
| // the partner core wrote its partial of this core's token row into c_int (acked) and then incremented | |
| // semaphore 4 S: release the compute's PACK thread (c_gate, see compute.cpp) | |
| CZONE("XRECV"); | |
| volatile tt_l1_ptr uint32_t* xs = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(get_semaphore(4 * S)); | |
| while (true) { | |
| invalidate_l1_cache(); | |
| if (*xs >= 1) { | |
| break; | |
| } | |
| } | |
| cb_wait_front(22, 1); | |
| cb_pop_front(22, 1); | |
| } | |
| } else { | |
| // ---------------- activation, bias, output (BRISC / NOC_1) ---------------- | |
| // round 12 (xmm): this core's whole in0 (its MPC token rows, all K blocks) is already resident in L1 -- c_a0 / | |
| // c_a1 are backed by the two resident tensors (even / odd K blocks, written by the preceding add+LN epilogue): | |
| // push it all at once; only the bias is read | |
| { | |
| const uint32_t bpage = get_local_cb_interface(c_bias).fifo_page_size; | |
| const auto bias = TensorAccessor(b_args, b_addr, bpage); | |
| cb_reserve_back(c_bias, NT); | |
| uint32_t bp = get_write_ptr(c_bias); | |
| for (uint32_t i = 0; i < NT; ++i) { | |
| noc_async_read(bias.get_noc_addr(n0 + i), bp, bpage); | |
| bp += bpage; | |
| } | |
| cb_reserve_back(c_a0, NBP * ABT); | |
| cb_push_back(c_a0, NBP * ABT); | |
| cb_reserve_back(c_a1, NBP * ABT); | |
| cb_push_back(c_a1, NBP * ABT); | |
| } | |
| { | |
| Pipe<NBP, S, BT, S> p1; | |
| p1.g = wg; | |
| p1.single = true; | |
| p1.single_m = wg.G - 1; | |
| p1.init(c_w1, get_write_ptr(9) + 16 * (S + 1)); | |
| w2_run(p1, 1, w_addr, wtab, BT * p1.page); | |
| } | |
| // the bias (trid 0) is needed only after the last K block: waited for here, off the weight stream's start | |
| noc_async_read_barrier(); | |
| cb_push_back(c_bias, NT); | |
| static_assert(false, "CMM_W2 needs CMM_A_RES (the BRISC sends the odd weight blocks)"); | |
| Pipe<NBP, S, ABT, 2 * S> q0; | |
| Pipe<NBP, S, ABT, 3 * S> q1; | |
| q0.g = ag; | |
| q1.g = ag; | |
| q1.g.rot = (q1.g.rot + 1) % q1.g.G; // odd blocks: the next member (one block per member when G = NB) | |
| // round 11 (xmm): ONE activation sender per grid column (its bottom core, member G-1), multicasting every K block | |
| // up the column (NOC_1: no wrap-around); it gathers the blocks from the width-sharded input itself | |
| q0.single = true; | |
| q1.single = true; | |
| q0.single_m = ag.G - 1; | |
| q1.single_m = ag.G - 1; | |
| const uint32_t flags = get_write_ptr(10); | |
| q0.init(c_a0, flags); | |
| q1.init(c_a1, flags + 16 * (S + 1)); | |
| const uint32_t apage = q0.page; | |
| const auto a = TensorAccessor(a_args, a_addr, apage); | |
| // the activation is BLOCK-sharded on this grid with ONE K block per core (tt_kernels.gelu writing the | |
| // fc2 input in the cmm owner layout): every member owns exactly the block in its own shard, no gather | |
| const uint32_t ast0 = a_addr, ast1 = a_addr; | |
| const uint32_t ast0 = get_write_ptr(c_ast0), ast1 = get_write_ptr(c_ast1); | |
| CZONE("AGATH"); | |
| // own blocks of each parity into the staging CBs; with the k2 K split (MPC 2) a block is MPC token rows (this | |
| // core's row m first, then the partner row (m + 3) % 6) of KB tiles of this core's K half (KT / 2 * (m / 3) ..) | |
| const uint32_t kbase = (KT / 2) * (m / 3); | |
| constexpr uint32_t kbase = 0; | |
| auto gather = [&](auto& q, uint32_t ast, uint32_t par) { | |
| for (uint32_t j = q.first_own; j < NBP && q.owner(j); ++j) { | |
| uint32_t dst = ast + q.own_index(j) * ABT * apage; | |
| const uint32_t k0 = kbase + (2 * j + par) * KB; | |
| noc_async_read_set_trid(1 + 2 * j + par); // per-block barrier before its multicast | |
| for (uint32_t i = 0; i < MPC; ++i) { | |
| const uint32_t mi = m + i; // consecutive token rows | |
| const uint32_t mi = i == 0 ? m : (m + 3) % 6; | |
| for (uint32_t k = 0; k < KB; ++k) { | |
| noc_async_read(a.get_noc_addr(a_page(mi, k0 + k, KT)), dst, apage); | |
| dst += apage; | |
| } | |
| } | |
| } | |
| }; | |
| gather(q0, ast0, 0); | |
| gather(q1, ast1, 1); | |
| noc_async_read_set_trid(0); | |
| { | |
| CZONE("AGBAR"); | |
| noc_async_read_barrier(); | |
| } | |
| const uint32_t bpage = get_local_cb_interface(c_bias).fifo_page_size; | |
| const auto bias = TensorAccessor(b_args, b_addr, bpage); | |
| // round 11: the bias is only needed after the last K block -- its DRAM reads (slow while the weight senders | |
| // saturate DRAM at kernel start: ~8 us for qkv's 12 tiles) no longer gate the activation pipeline | |
| noc_async_read_barrier(); // own activation blocks gathered | |
| cb_reserve_back(c_bias, NT); | |
| uint32_t bp = get_write_ptr(c_bias); | |
| for (uint32_t i = 0; i < NT; ++i) { | |
| noc_async_read(bias.get_noc_addr(n0 + i), bp, bpage); | |
| bp += bpage; | |
| } | |
| noc_async_read_barrier(); | |
| cb_push_back(c_bias, NT); | |
| for (uint32_t j = 0; j < NB; ++j) { | |
| const uint32_t c = (j & 1) ? c_a1 : c_a0; | |
| cb_reserve_back(c, ABT); // round 11: ABT (KB x MPC) -- KB here deadlocked the k2 compute (chip 3 fault) | |
| cb_push_back(c, ABT); | |
| } | |
| // round 8: the GELU helper cores write this core's own activation block into its shard and count the tiles on | |
| // semaphore CMM_GELU_SEM (zeroed by dispatch): wait for all KB before multicasting it (keep the ready signals | |
| // of both pipes flowing meanwhile) | |
| auto gelu_wait = [&]() { | |
| volatile tt_l1_ptr uint32_t* gs = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(get_semaphore(CMM_GELU_SEM)); | |
| while (true) { | |
| invalidate_l1_cache(); | |
| if (*gs >= ABT) { | |
| break; | |
| } | |
| q0.signal_free(); | |
| q1.signal_free(); | |
| } | |
| }; | |
| auto gelu_wait = [&]() {}; | |
| // round 11: an owner multicasts its block as soon as it is ready (GELU done, every peer's slot and its own slot | |
| // free), not only after it has received all earlier blocks (the in-order loop made the blocks a hand-over | |
| // chain). The data lands in ring slot j % S of every member; the in-order loop below only pushes it. | |
| auto gelu_done = [&]() -> bool { | |
| invalidate_l1_cache(); | |
| return *reinterpret_cast<volatile tt_l1_ptr uint32_t*>(get_semaphore(CMM_GELU_SEM)) >= ABT; | |
| }; | |
| auto gelu_done = [&]() -> bool { return true; }; | |
| uint32_t nx0 = q0.first_own, nx1 = q1.first_own; // next own block to send per pipe | |
| auto try_send = [&]() { | |
| if (nx0 < NBP && q0.owner(nx0) && gelu_done() && q0.can_send(nx0)) { | |
| q0.send(ast0 + q0.own_index(nx0) * ABT * apage, q0.slot_addr(nx0), nx0, q1); | |
| ++nx0; | |
| } | |
| if (nx1 < NBP && q1.owner(nx1) && gelu_done() && q1.can_send(nx1)) { | |
| q1.send(ast1 + q1.own_index(nx1) * ABT * apage, q1.slot_addr(nx1), nx1, q0); | |
| ++nx1; | |
| } | |
| }; | |
| auto step = [&](auto& q, auto& o, uint32_t j, uint32_t& nx) { | |
| q.signal_free(); | |
| o.signal_free(); | |
| if (q.owner(j)) { | |
| while (nx <= j) { | |
| try_send(); | |
| q.signal_free(); | |
| o.signal_free(); | |
| } | |
| cb_reserve_back(q.cb, ABT); | |
| cb_push_back(q.cb, ABT); | |
| return; | |
| } | |
| cb_reserve_back(q.cb, ABT); | |
| { | |
| CZONE("RECV"); | |
| volatile tt_l1_ptr uint32_t* v = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(q.valid_addr(j)); | |
| while (true) { | |
| invalidate_l1_cache(); | |
| if (*v >= j + 1) { | |
| break; | |
| } | |
| try_send(); | |
| q.signal_free(); | |
| o.signal_free(); | |
| } | |
| } | |
| cb_push_back(q.cb, ABT); | |
| }; | |
| for (uint32_t j = 0; j < NBP; ++j) { | |
| step(q0, q1, j, nx0); | |
| step(q1, q0, j, nx1); | |
| } | |
| auto xwait = [&](uint32_t t) { noc_async_read_barrier_with_trid(t); }; | |
| auto xwait = [&](uint32_t) {}; | |
| NoOther none_; | |
| for (uint32_t j = 0; j < NBP; ++j) { | |
| if (q0.owner(j)) { | |
| gelu_wait(); | |
| q0.step_from2(j, ast0 + q0.own_index(j) * ABT * apage, none_); | |
| } else { | |
| q0.recv_step2(j, none_); | |
| } | |
| } | |
| if (false) | |
| for (uint32_t j = 0; j < NBP; ++j) { | |
| if (q0.owner(j)) { | |
| gelu_wait(); | |
| xwait(1 + 2 * j); | |
| q0.step_from2(j, ast0 + q0.own_index(j) * ABT * apage, q1); | |
| } else { | |
| q0.recv_step2(j, q1); | |
| } | |
| if (q1.owner(j)) { | |
| gelu_wait(); | |
| xwait(2 + 2 * j); | |
| q1.step_from2(j, ast1 + q1.own_index(j) * ABT * apage, q0); | |
| } else { | |
| q1.recv_step2(j, q0); | |
| } | |
| } | |
| noc_async_read_barrier(); | |
| cb_push_back(c_bias, NT); | |
| { | |
| // this core's partial of the PARTNER's token row (compute: c_send, NT fp32 tiles) -> the partner's c_int (same | |
| // L1 address on every core: nothing was pushed there yet), acked, then the partner's semaphore 4 S | |
| CZONE("XSEND"); | |
| constexpr uint32_t XA = 1; | |
| constexpr uint32_t XA = 0; | |
| const uint32_t px = get_arg_val<uint32_t>(wtab + NB + XA), py = get_arg_val<uint32_t>(wtab + NB + XA + 1); | |
| constexpr uint32_t c_int = 24; | |
| // wide output (compute_k2w.cpp): the partner half is tiles NT .. 2 NT - 1 of c_part (never popped) | |
| constexpr uint32_t c_send = 26; | |
| const uint32_t xbytes = NT * get_local_cb_interface(c_send).fifo_page_size; | |
| cb_wait_front(c_send, 2 * NT); | |
| const uint32_t xsrc = get_read_ptr(c_send) + xbytes; | |
| constexpr uint32_t c_send = 19; | |
| const uint32_t xbytes = NT * get_local_cb_interface(c_send).fifo_page_size; | |
| cb_wait_front(c_send, NT); | |
| const uint32_t xsrc = get_read_ptr(c_send); | |
| noc_async_write(xsrc, get_noc_addr(px, py, get_write_ptr(c_int)), xbytes); | |
| // round 11: library ordering (same NOC, same VC, same destination): the data flushed out of this core, then a | |
| // plain 4-byte write of the flag (value 1) instead of an acked barrier + atomic increment | |
| noc_async_writes_flushed(); | |
| { | |
| volatile tt_l1_ptr uint32_t* one = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(get_write_ptr(10) + 16 * (2 * S + 2)); | |
| *one = 1; | |
| noc_async_write(get_write_ptr(10) + 16 * (2 * S + 2), get_noc_addr(px, py, get_semaphore(4 * S)), 4); | |
| noc_async_writes_flushed(); | |
| } | |
| noc_async_write_barrier(); | |
| noc_semaphore_inc(get_noc_addr(px, py, get_semaphore(4 * S)), 1); | |
| cb_pop_front(c_send, NT); | |
| } | |
| { | |
| // MPC x NT tiles -> pages of the interleaved output; CMM_OUT_QKV: straight into the head-split q / k / v | |
| // ([1, QH, QMT * 32, QDT * 32] each, addresses in args 5..7) -- the head split folded into the matmul | |
| const uint32_t opage = get_local_cb_interface(c_out).fifo_page_size; | |
| cb_wait_front(c_out, MPC * NT); | |
| uint32_t rp = get_read_ptr(c_out); | |
| for (uint32_t i = 0; i < MPC; ++i) { | |
| for (uint32_t c = 0; c < NT; ++c) { | |
| const uint32_t mr = m + i, n = n0 + c; | |
| constexpr uint32_t QW = CMM_QH * CMM_QDT; | |
| const uint32_t which = n / QW, rem = n % QW; | |
| const uint32_t pg = (rem / CMM_QDT) * (CMM_QMT * CMM_QDT) + mr * CMM_QDT + rem % CMM_QDT; | |
| const auto o = TensorAccessor(o_args, get_arg_val<uint32_t>(5 + which), opage); | |
| noc_async_write(rp, o.get_noc_addr(pg), opage); | |
| const auto o = TensorAccessor(o_args, get_arg_val<uint32_t>(5), opage); | |
| noc_async_write(rp, o.get_noc_addr(mr * NTOT + n), opage); | |
| rp += opage; | |
| } | |
| } | |
| noc_async_write_barrier(); | |
| cb_pop_front(c_out, MPC * NT); | |
| } | |
| const uint32_t out_addr = get_arg_val<uint32_t>(5); | |
| const uint32_t ox = get_arg_val<uint32_t>(6), oy = get_arg_val<uint32_t>(7), ooff = get_arg_val<uint32_t>(8); | |
| const uint32_t opage = get_local_cb_interface(c_out).fifo_page_size; | |
| cb_wait_front(c_out, NT); | |
| noc_async_write(get_read_ptr(c_out), get_noc_addr(ox, oy, out_addr + ooff), NT * opage); | |
| noc_async_write_barrier(); | |
| cb_pop_front(c_out, NT); | |
| noc_async_write_barrier(); | |
| q0.finish(); | |
| } | |
| } | |