File size: 5,064 Bytes
58ba6df | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | // SPDX-FileCopyrightText: © 2026 Tenstorrent Inc.
// SPDX-License-Identifier: Apache-2.0
// add+LayerNorm reader (NCRISC): marks the block-sharded x (and y) shards as resident, builds the SUM
// reduce scaler tile locally (bf16 1.0 in the first row of each face; no DRAM round trip on the
// critical path), then reads this core's NT gamma / beta tiles (full 32-row tiles, interleaved DRAM),
// which the compute needs only for its last pass.
// compile-time: NT, TensorAccessorArgs(gamma), TensorAccessorArgs(beta)
// runtime: gamma_addr, beta_addr, col0 (first gamma/beta tile of this core)
#include <stdint.h>
#include "api/dataflow/dataflow_api.h"
#include "addln_ids.hpp"
ADDLN_KERNEL_MAIN {
constexpr uint32_t NT = get_compile_time_arg_val(ADDLN_CT0);
[[maybe_unused]] constexpr auto ga_args = TensorAccessorArgs<ADDLN_CT0 + 1>();
[[maybe_unused]] constexpr auto be_args = TensorAccessorArgs<ga_args.next_compile_time_args_offset()>();
const uint32_t ga_addr = get_arg_val<uint32_t>(ADDLN_RT0 + 0);
const uint32_t be_addr = get_arg_val<uint32_t>(ADDLN_RT0 + 1);
const uint32_t col0 = get_arg_val<uint32_t>(ADDLN_RT0 + 2);
constexpr uint32_t c_x = ADDLN_C_X, c_y = ADDLN_C_Y, c_scal = ADDLN_C_SCAL, c_gam = ADDLN_C_GAM, c_bet = ADDLN_C_BET;
// sharded inputs live in the CB already (globally allocated)
cb_reserve_back(c_x, NT);
cb_push_back(c_x, NT);
#if defined(HAS_Y) && !defined(ADDLN_Y_INTERNAL) // fused after cmm: the compute produces y itself
cb_reserve_back(c_y, NT);
cb_push_back(c_y, NT);
#endif
#ifndef ADDLN_SLOWSCAL
// round 8: the scaler first and fast -- the compute needs it right after the add (~0.5 us in), and 512 single-word
// stores from NCRISC took ~1.2 us of the critical path: zero the tile with NoC reads of the hardware zero region,
// then write the 32 non-zero words
{
cb_reserve_back(c_scal, 1);
const uint32_t sp = get_write_ptr(c_scal);
const uint64_t zeros = get_noc_addr(MEM_ZEROS_BASE);
for (uint32_t off = 0; off < 2048; off += MEM_ZEROS_SIZE) {
noc_async_read(zeros, sp + off, MEM_ZEROS_SIZE);
}
noc_async_read_barrier();
volatile tt_l1_ptr uint32_t* sc = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(sp);
for (uint32_t f = 0; f < 4; ++f) {
for (uint32_t j = 0; j < 8; ++j) {
sc[f * 128 + j] = 0x3F803F80u;
}
}
cb_push_back(c_scal, 1);
}
#endif
#ifndef ADDLN_NOAFF // round 9: with the LN affine folded into the next weights there is no gamma / beta pass
// gamma / beta reads first (in flight while the scaler is built)
const uint32_t gb_page = get_local_cb_interface(c_gam).fifo_page_size;
const auto ga = TensorAccessor(ga_args, ga_addr, gb_page);
[[maybe_unused]] const auto be = TensorAccessor(be_args, be_addr, gb_page);
cb_reserve_back(c_gam, NT);
#ifndef ADDLN_NOBETA // round 9: beta folded into the next matmul's bias
cb_reserve_back(c_bet, NT);
#endif
uint32_t gp = get_write_ptr(c_gam);
[[maybe_unused]] uint32_t bp = get_write_ptr(c_bet);
for (uint32_t i = 0; i < NT; ++i) {
noc_async_read(ga.get_noc_addr(col0 + i), gp, gb_page);
#ifndef ADDLN_NOBETA
noc_async_read(be.get_noc_addr(col0 + i), bp, gb_page);
#endif
gp += gb_page;
bp += gb_page;
}
// scaler: bf16 tile (4 faces of 16x16, 512 B each), 1.0 (0x3F80) in row 0 of every face, 0 elsewhere
#ifdef ADDLN_SLOWSCAL
cb_reserve_back(c_scal, 1);
volatile tt_l1_ptr uint32_t* sc = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(get_write_ptr(c_scal));
for (uint32_t i = 0; i < 512; ++i) {
sc[i] = 0;
}
for (uint32_t f = 0; f < 4; ++f) {
for (uint32_t j = 0; j < 8; ++j) {
sc[f * 128 + j] = 0x3F803F80u;
}
}
cb_push_back(c_scal, 1);
#endif
noc_async_read_barrier();
cb_push_back(c_gam, NT);
#ifndef ADDLN_NOBETA
cb_push_back(c_bet, NT);
#endif
#elif defined(ADDLN_SLOWSCAL)
#error "ADDLN_NOAFF needs the fast scaler path"
#endif
#ifdef ADDLN_PREFETCH
// cross-op weight prefetch (tt_kernels.CmmPrefetch): this LN is latency-bound and leaves the DRAM idle, so the
// owner cores of a later cmm_linear read its weight pairs into that op's persistent L1 buffer.
// runtime args 3..: npf, w_addr, npf x (bank, bank offset, local dst, bytes). Plain reads + one plain barrier.
const uint32_t npf = get_arg_val<uint32_t>(ADDLN_RT0 + 3);
if (npf) {
const uint32_t w_addr = get_arg_val<uint32_t>(ADDLN_RT0 + 4);
for (uint32_t i = 0; i < npf; ++i) {
const uint32_t bank = get_arg_val<uint32_t>(ADDLN_RT0 + 5 + 4 * i), off = get_arg_val<uint32_t>(ADDLN_RT0 + 6 + 4 * i);
const uint32_t ldst = get_arg_val<uint32_t>(ADDLN_RT0 + 7 + 4 * i), bytes = get_arg_val<uint32_t>(ADDLN_RT0 + 8 + 4 * i);
noc_async_read(get_noc_addr_from_bank_id<true>(bank, w_addr + off), ldst, bytes);
}
noc_async_read_barrier();
}
#endif
}
|