File size: 8,641 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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// SPDX-FileCopyrightText: © 2026 Tenstorrent Inc.
// SPDX-License-Identifier: Apache-2.0
// hgemv reader (NCRISC, NOC_0), round 6: one output tile column n per core of y = [LN](x) @ W (+ b) [GELU] [+ r]
// for the 1-token MANO head.  Vectors are "row-form" tensors [1, 1, 32, L] (TILE, interleaved L1): only row 0 of
// every tile is meaningful (face 0 row 0 = elements 0..15, face 1 row 0 = 16..31 of that tile).
//   weights: the core's packed contiguous [KT x 1] tile column (CmmWeight, GX = N/32, NT = 1, one K block), one DRAM
//            run, transaction id 1 (waited last); the small reads below use transaction id 2.  Never trid 0.
//   HG_LN:   x row 0 of the KT = 32 tiles gathered into ONE dense tile (row t <- tile t's row 0), the LayerNorm runs
//            on that tile; else x's row 0s go straight into the row-form matmul operand c_hrow.
//   HG_DIN=D: x is instead a DENSE vector with D replica tiles [1, 1, 32*D, 32] (one page read, replica n % D); without
//            LN the writer spreads it into c_hrow.
//   bias tile n (full tile), HG_RES: residual tile n row 0.
//   HG_TINY (round 10): the dense vectors are ROW-MAJOR (element 32t + j at word 32t + j of the tile buffer, so row t
//            of the "tile" is exactly a [1, 32] tiny tile), the matmul operand c_hrow is KT [1, 32] tiny tiles (an
//            alias of the dense tile's memory, or gathered contiguously), and bias / residual are [1, 32] tiny tiles.
// compile-time: KT, ES (x element bytes: 4 fp32 / 2 bf16), RES_ES, TensorAccessorArgs(x, bias[, res])
// runtime: x_addr, w_addr, w_bank, w_off, b_addr, res_addr, n
#include <stdint.h>
#include "api/dataflow/dataflow_api.h"

void kernel_main() {
    constexpr uint32_t KT = get_compile_time_arg_val(0);
    constexpr uint32_t ES = get_compile_time_arg_val(1);
    constexpr uint32_t RES_ES = get_compile_time_arg_val(2);
    constexpr auto x_args = TensorAccessorArgs<3>();
    constexpr auto b_args = TensorAccessorArgs<x_args.next_compile_time_args_offset()>();
#ifdef HG_RES
    constexpr auto r_args = TensorAccessorArgs<b_args.next_compile_time_args_offset()>();
#endif
    constexpr uint32_t c_xd = 0, c_w = 1, c_bias = 2, c_scal = 3, c_res = 4, c_hrow = 5;
    const uint32_t x_addr = get_arg_val<uint32_t>(0);
    const uint32_t w_addr = get_arg_val<uint32_t>(1), w_bank = get_arg_val<uint32_t>(2), w_off = get_arg_val<uint32_t>(3);
    const uint32_t b_addr = get_arg_val<uint32_t>(4), r_addr = get_arg_val<uint32_t>(5), n = get_arg_val<uint32_t>(6);

    noc_async_read_set_trid(2);  // the small latency-critical reads first, the weight run (trid 1) behind them
    constexpr uint32_t FB = 256 * ES;  // face bytes of x
    const uint32_t xpage = 1024 * ES;
    const auto x = TensorAccessor(x_args, x_addr, xpage);
#ifdef HG_DIN
    // dense input: replica (n % ND) of the dense vector tile (written by the producing hgemv), one 4 KB / 2 KB read
    cb_reserve_back(c_xd, 1);
#if defined(HG_TINY) && !defined(HG_LN)
    cb_reserve_back(c_hrow, KT);  // the same memory as KT [1, 32] tiny tiles (row-major dense vector)
#endif
    noc_async_read(x.get_noc_addr(n % HG_DIN), get_write_ptr(c_xd), xpage);
#elif defined(HG_LN)
    cb_reserve_back(c_xd, 1);
    const uint32_t xd = get_write_ptr(c_xd);
    for (uint32_t t = 0; t < KT; ++t) {
        const uint64_t s = x.get_noc_addr(t);
#ifdef HG_TINY
        const uint32_t d = xd + t * 32 * ES;  // row-major dense tile
        noc_async_read(s, d, 16 * ES);
        noc_async_read(s + FB, d + 16 * ES, 16 * ES);
#else
        const uint32_t d = xd + ((t / 16) * 2) * FB + (t % 16) * 16 * ES;
        noc_async_read(s, d, 16 * ES);
        noc_async_read(s + FB, d + FB, 16 * ES);
#endif
    }
#endif
#ifdef HG_LN
    // SUM scaler: bf16 1.0 in row 0 of every face
    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));
#ifdef HG_FASTX
    // round 10: zero the tile with NoC reads of the hardware zero region (512 single-word stores took ~1 us), then the
    // 32 non-zero words; x and the scaler go to the compute before the bias / residual / weight reads are issued
    {
        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, get_write_ptr(c_scal) + off, MEM_ZEROS_SIZE);
        }
        noc_async_read_barrier_with_trid(2);  // also x
    }
#else
    for (uint32_t i = 0; i < 512; ++i) {
        sc[i] = 0;
    }
#endif
    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);
#ifdef HG_FASTX
    cb_push_back(c_xd, 1);
#endif
#elif !defined(HG_DIN)
    cb_reserve_back(c_hrow, KT);
    const uint32_t hr = get_write_ptr(c_hrow);
    for (uint32_t t = 0; t < KT; ++t) {
        const uint64_t s = x.get_noc_addr(t);
#ifdef HG_TINY
        noc_async_read(s, hr + t * 32 * ES, 16 * ES);  // tiny tile t = row 0 of x tile t, contiguous
        noc_async_read(s + FB, hr + t * 32 * ES + 16 * ES, 16 * ES);
#else
        noc_async_read(s, hr + t * xpage, 16 * ES);
        noc_async_read(s + FB, hr + t * xpage + FB, 16 * ES);
#endif
    }
#endif
#if defined(HG_FASTX) && defined(HG_DIN) && !defined(HG_LN)
    noc_async_read_barrier_with_trid(2);
    cb_push_back(c_xd, 1);
#ifdef HG_TINY
    cb_push_back(c_hrow, KT);
#endif
#endif
#ifdef HG_TINY
    {
        // bias: row 0 of the row-form bias tile n -> one [1, 32] tiny tile (BES = its element bytes)
        constexpr uint32_t BES = HG_BES;
        const auto bias = TensorAccessor(b_args, b_addr, 1024 * BES);
        cb_reserve_back(c_bias, 1);
        const uint64_t s = bias.get_noc_addr(n);
        noc_async_read(s, get_write_ptr(c_bias), 16 * BES);
        noc_async_read(s + 256 * BES, get_write_ptr(c_bias) + 16 * BES, 16 * BES);
    }
#else
    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, 1);
    noc_async_read(bias.get_noc_addr(n), get_write_ptr(c_bias), bpage);
#endif
#ifdef HG_RES
    constexpr uint32_t RFB = 256 * RES_ES;
    const auto res = TensorAccessor(r_args, r_addr, 1024 * RES_ES);
    cb_reserve_back(c_res, 1);
    {
        const uint64_t s = res.get_noc_addr(n);
        noc_async_read(s, get_write_ptr(c_res), 16 * RES_ES);
#ifdef HG_TINY
        noc_async_read(s + RFB, get_write_ptr(c_res) + 16 * RES_ES, 16 * RES_ES);
#else
        noc_async_read(s + RFB, get_write_ptr(c_res) + RFB, 16 * RES_ES);
#endif
    }
#endif
#ifdef HG_XFIRST
    // round 10: x / bias / residual land (and go to the compute) BEFORE the 64 KB weight run is issued -- the weight
    // responses otherwise share this core's inbound port with the x reads (x landed at ~1.9 us median, 4.5 us max)
    noc_async_read_barrier_with_trid(2);
#if defined(HG_LN) || defined(HG_DIN)
#ifndef HG_FASTX
    cb_push_back(c_xd, 1);
#if defined(HG_TINY) && defined(HG_DIN) && !defined(HG_LN)
    cb_push_back(c_hrow, KT);
#endif
#endif
#else
    cb_push_back(c_hrow, KT);
#endif
    cb_push_back(c_bias, 1);
#ifdef HG_RES
    cb_push_back(c_res, 1);
#endif
#endif
    // weights: one contiguous run, trid 1 (issued after the small reads so x is not queued behind 64 KB)
    const uint32_t wpage = get_local_cb_interface(c_w).fifo_page_size;
    cb_reserve_back(c_w, KT);
#ifndef HG_WL1  // round 10: HG_WL1 -- the weights are already in this core's L1 (prefetched by the previous hgemv)
    {
        noc_async_read_set_trid(1);
        uint64_t src = get_noc_addr_from_bank_id<true>(w_bank, w_addr + w_off);
        uint32_t dst = get_write_ptr(c_w), left = KT * wpage;
        while (left) {
            const uint32_t m = left < NOC_MAX_BURST_SIZE ? left : NOC_MAX_BURST_SIZE;
            noc_async_read<NOC_MAX_BURST_SIZE>(src, dst, m);
            src += m;
            dst += m;
            left -= m;
        }
    }
#endif
#ifndef HG_XFIRST
    noc_async_read_barrier_with_trid(2);
#if defined(HG_LN) || defined(HG_DIN)
#ifndef HG_FASTX
    cb_push_back(c_xd, 1);
#if defined(HG_TINY) && defined(HG_DIN) && !defined(HG_LN)
    cb_push_back(c_hrow, KT);
#endif
#endif
#else
    cb_push_back(c_hrow, KT);
#endif
    cb_push_back(c_bias, 1);
#ifdef HG_RES
    cb_push_back(c_res, 1);
#endif
#endif
    // (round 10: replacing this with the plain noc_async_read_barrier() hung chip 1 in scripts/micro/hgemv_test.py
    // -- OPT_REPORT "known hangs"; keep the transaction-id barrier)
    noc_async_read_barrier_with_trid(1);
    cb_push_back(c_w, KT);
}