File size: 15,681 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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
// SPDX-FileCopyrightText: © 2026 Tenstorrent Inc.
// SPDX-License-Identifier: Apache-2.0
// add+LayerNorm writer (BRISC, NOC_1): two all-gathers of the per-token partial statistics inside each
// grid row, then this core's NT normalized tiles -> the interleaved output.
// Gather (unicast only; round 2's multicast version hung chip 7 with a NOC_1-unswapped rectangle):
//   the compute packs the partial as a ROW vector (fp32 tile, row 0 = 16 floats in face 0 + 16 in face 1);
//   that 2 x 64-byte row goes to row `col` of the 1-tile gather buffer of every core of the grid row
//   (itself included).  Rows NX..31 of the buffer are zeroed locally first (disjoint from the peers'
//   writes), so the compute can column-sum the whole tile.  Then each peer's round semaphore is
//   incremented and we wait for NX-1 increments.  Semaphores are initialised (0) per launch.
// compile-time: NT, NX, TensorAccessorArgs(out)
// runtime: out_addr, out_start (first page of this core), col, sem1, sem2, then NX (x, y) peer coordinates
// OUT_WSHARD (define): the output is WIDTH-sharded on NS cores, SW tiles wide (the sharded in0 of the
//   1D-mcast qkv / fc1 matmuls); extra compile-time args SW, NS; extra runtime args: row, then NS (x, y).
#include <stdint.h>
#include "api/dataflow/dataflow_api.h"
#include "addln_ids.hpp"
#ifdef ADDLN_PROF
#include "tools/profiler/kernel_profiler.hpp"
#define WZONE(n) DeviceZoneScopedN(n)
#else
#define WZONE(n)
#endif

FORCE_INLINE void zero_tail(uint32_t base, uint32_t NX) {
    // fp32 tile: face0 rows 0..15 at [0, 1024), face1 [1024, 2048), faces 2, 3 [2048, 4096); a row = 64 B
    const uint64_t zeros = get_noc_addr(MEM_ZEROS_BASE);
    const uint32_t tail = (16 - NX) * 64;
    if (tail) {
        noc_async_read(zeros, base + NX * 64, tail);
        noc_async_read(zeros, base + 1024 + NX * 64, tail);
    }
    for (uint32_t off = 2048; off < 4096; off += MEM_ZEROS_SIZE) {
        noc_async_read(zeros, base + off, MEM_ZEROS_SIZE);
    }
}

template <uint32_t c_part, uint32_t c_gath>
FORCE_INLINE void gather_round(uint32_t col, uint32_t sem_id, uint32_t NX, uint32_t my_x, uint32_t my_y) {
    cb_reserve_back(c_gath, 1);
    const uint32_t base = get_write_ptr(c_gath);
    const uint32_t row = base + col * 64;
    cb_wait_front(c_part, 1);
    const uint32_t src = get_read_ptr(c_part);
#ifdef ADDLN_NOSYNC  // timing experiment only: own row, no peers (numerically wrong)
    noc_async_write(src, get_noc_addr(row), 64);
    noc_async_write(src + 1024, get_noc_addr(row + 1024), 64);
    noc_async_write_barrier();
    cb_pop_front(c_part, 1);
    cb_push_back(c_gath, 1);
    return;
#endif
    for (uint32_t k = 0; k < NX; ++k) {
        const uint32_t px = get_arg_val<uint32_t>(ADDLN_RT0 + 5 + 2 * k), py = get_arg_val<uint32_t>(ADDLN_RT0 + 6 + 2 * k);
        noc_async_write(src, get_noc_addr(px, py, row), 64);
        noc_async_write(src + 1024, get_noc_addr(px, py, row + 1024), 64);
    }
#ifdef ADDLN_FLUSHED  // experiment: rely on in-order delivery of the writes and the following atomic
    noc_async_writes_flushed();
#else
    noc_async_write_barrier();
#endif
    const uint32_t sem_addr = get_semaphore(sem_id);
    for (uint32_t k = 0; k < NX; ++k) {
        const uint32_t px = get_arg_val<uint32_t>(ADDLN_RT0 + 5 + 2 * k), py = get_arg_val<uint32_t>(ADDLN_RT0 + 6 + 2 * k);
        if (px == my_x && py == my_y) {
            continue;
        }
        noc_semaphore_inc(get_noc_addr(px, py, sem_addr), 1);
    }
    volatile tt_l1_ptr uint32_t* sem = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(sem_addr);
    noc_semaphore_wait(sem, NX - 1);
    noc_semaphore_set(sem, 0);
    noc_async_atomic_barrier();
    cb_pop_front(c_part, 1);
    cb_push_back(c_gath, 1);
}

#ifdef ADDLN_MCAST
// round 12: the partial row goes to the same offset of every row peer's gather buffer -> ONE multicast (loopback, the
// physical rectangle spanned by the NX peers = exactly the grid row) instead of NX unicasts (~0.6 us of issue per partial)
FORCE_INLINE uint64_t row_mcast_addr(uint32_t NX, uint32_t addr) {
    uint32_t sx = 0xffff, sy = 0xffff, ex = 0, ey = 0;
    for (uint32_t k = 0; k < NX; ++k) {
        const uint32_t px = get_arg_val<uint32_t>(ADDLN_RT0 + 5 + 2 * k), py = get_arg_val<uint32_t>(ADDLN_RT0 + 6 + 2 * k);
        sx = px < sx ? px : sx;
        sy = py < sy ? py : sy;
        ex = px > ex ? px : ex;
        ey = py > ey ? py : ey;
    }
    return noc_index == 1 ? get_noc_multicast_addr(ex, ey, sx, sy, addr) : get_noc_multicast_addr(sx, sy, ex, ey, addr);
}
#endif

#ifdef ADDLN_1R
// one round for both partials (round 6): rows `col` of G1 (sum s) and G2 (sum s^2) on every row peer, one increment
template <uint32_t c_pa, uint32_t c_ga, uint32_t c_pb, uint32_t c_gb>
FORCE_INLINE void gather_round2(uint32_t col, uint32_t sem_id, uint32_t NX, uint32_t my_x, uint32_t my_y) {
    cb_reserve_back(c_ga, 1);
    cb_reserve_back(c_gb, 1);
    const uint32_t ra = get_write_ptr(c_ga) + col * 64, rb = get_write_ptr(c_gb) + col * 64;
    const uint32_t sem_addr = get_semaphore(sem_id);
#ifdef ADDLN_SPLITSEND
    // round 9: the sum partial goes out while the compute still reduces the sum-of-squares partial
    {WZONE("W-pwait");
    cb_wait_front(c_pa, 1);
    }
    const uint32_t sa = get_read_ptr(c_pa);
    {WZONE("W-send");
#ifdef ADDLN_MCAST
    noc_async_write_multicast_loopback_src(sa, row_mcast_addr(NX, ra), 64, NX);
    noc_async_write_multicast_loopback_src(sa + 1024, row_mcast_addr(NX, ra + 1024), 64, NX);
#else
    for (uint32_t k = 0; k < NX; ++k) {
        const uint32_t px = get_arg_val<uint32_t>(ADDLN_RT0 + 5 + 2 * k), py = get_arg_val<uint32_t>(ADDLN_RT0 + 6 + 2 * k);
        noc_async_write(sa, get_noc_addr(px, py, ra), 64);
        noc_async_write(sa + 1024, get_noc_addr(px, py, ra + 1024), 64);
    }
#endif
    }
    {WZONE("W-pwait2");
    cb_wait_front(c_pb, 1);
    }
    const uint32_t sb = get_read_ptr(c_pb);
    {WZONE("W-send2");
#ifdef ADDLN_MCAST
    noc_async_write_multicast_loopback_src(sb, row_mcast_addr(NX, rb), 64, NX);
    noc_async_write_multicast_loopback_src(sb + 1024, row_mcast_addr(NX, rb + 1024), 64, NX);
#else
    for (uint32_t k = 0; k < NX; ++k) {
        const uint32_t px = get_arg_val<uint32_t>(ADDLN_RT0 + 5 + 2 * k), py = get_arg_val<uint32_t>(ADDLN_RT0 + 6 + 2 * k);
        noc_async_write(sb, get_noc_addr(px, py, rb), 64);
        noc_async_write(sb + 1024, get_noc_addr(px, py, rb + 1024), 64);
    }
#endif
    }
#else
    {WZONE("W-pwait");
    cb_wait_front(c_pa, 1);
    cb_wait_front(c_pb, 1);
    }
    const uint32_t sa = get_read_ptr(c_pa), sb = get_read_ptr(c_pb);
    {WZONE("W-send");
    for (uint32_t k = 0; k < NX; ++k) {
        const uint32_t px = get_arg_val<uint32_t>(ADDLN_RT0 + 5 + 2 * k), py = get_arg_val<uint32_t>(ADDLN_RT0 + 6 + 2 * k);
        noc_async_write(sa, get_noc_addr(px, py, ra), 64);
        noc_async_write(sa + 1024, get_noc_addr(px, py, ra + 1024), 64);
        noc_async_write(sb, get_noc_addr(px, py, rb), 64);
        noc_async_write(sb + 1024, get_noc_addr(px, py, rb + 1024), 64);
    }
    }
#endif
    {WZONE("W-bar");
    noc_async_write_barrier();
    }
    {WZONE("W-inc");
    for (uint32_t k = 0; k < NX; ++k) {
        const uint32_t px = get_arg_val<uint32_t>(ADDLN_RT0 + 5 + 2 * k), py = get_arg_val<uint32_t>(ADDLN_RT0 + 6 + 2 * k);
        if (px == my_x && py == my_y) {
            continue;
        }
        noc_semaphore_inc(get_noc_addr(px, py, sem_addr), 1);
    }
    }
    volatile tt_l1_ptr uint32_t* sem = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(sem_addr);
    {WZONE("W-swait");
    noc_semaphore_wait(sem, NX - 1);
    }
    noc_semaphore_set(sem, 0);
    noc_async_atomic_barrier();
    cb_pop_front(c_pa, 1);
    cb_pop_front(c_pb, 1);
    cb_push_back(c_ga, 1);
    cb_push_back(c_gb, 1);
}
#endif

#ifdef ADDLN_LEADER
// round 12: gather through the row LEADER (peer 0) instead of all-to-all.  Every other core writes its two partial rows
// (sum, sum of squares; face 0 / face 1 halves) into row `col` of the LEADER's gather buffers only (4 small writes instead
// of 4 NX), acks them and increments the leader's sem_a.  The leader waits for NX-1 increments, then multicasts the
// filled rows 0 .. NX-1 of both gather buffers to the rest of the row (ONE source per row: no concurrent multicasts) and
// sets everyone's sem_b to 1 with a multicast after flushing the data (same NoC / VC: the flag cannot overtake it).
template <uint32_t c_pa, uint32_t c_ga, uint32_t c_pb, uint32_t c_gb>
FORCE_INLINE void gather_leader(uint32_t col, uint32_t sem_a, uint32_t sem_b, uint32_t NX) {
    cb_reserve_back(c_ga, 1);
    cb_reserve_back(c_gb, 1);
    const uint32_t ga = get_write_ptr(c_ga), gb = get_write_ptr(c_gb);
    const uint32_t ra = ga + col * 64, rb = gb + col * 64;
    const uint32_t lx = get_arg_val<uint32_t>(ADDLN_RT0 + 5), ly = get_arg_val<uint32_t>(ADDLN_RT0 + 6);
    {WZONE("W-pwait");
    cb_wait_front(c_pa, 1);
    }
    const uint32_t sa = get_read_ptr(c_pa);
    noc_async_write(sa, get_noc_addr(lx, ly, ra), 64);
    noc_async_write(sa + 1024, get_noc_addr(lx, ly, ra + 1024), 64);
    {WZONE("W-pwait2");
    cb_wait_front(c_pb, 1);
    }
    const uint32_t sb = get_read_ptr(c_pb);
    noc_async_write(sb, get_noc_addr(lx, ly, rb), 64);
    noc_async_write(sb + 1024, get_noc_addr(lx, ly, rb + 1024), 64);
    {WZONE("W-bar");
    noc_async_write_barrier();
    }
    volatile tt_l1_ptr uint32_t* sa_p = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(get_semaphore(sem_a));
    volatile tt_l1_ptr uint32_t* sb_p = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(get_semaphore(sem_b));
    if (col == 0) {
        {WZONE("W-lwait");
        noc_semaphore_wait(sa_p, NX - 1);
        }
        WZONE("W-lmc");
        uint32_t sx = 0xffff, sy = 0xffff, ex = 0, ey = 0;
        for (uint32_t k = 0; k < NX; ++k) {
            const uint32_t px = get_arg_val<uint32_t>(ADDLN_RT0 + 5 + 2 * k), py = get_arg_val<uint32_t>(ADDLN_RT0 + 6 + 2 * k);
            sx = px < sx ? px : sx;
            sy = py < sy ? py : sy;
            ex = px > ex ? px : ex;
            ey = py > ey ? py : ey;
        }
        auto mc = [&](uint32_t addr) -> uint64_t {
            return noc_index == 1 ? get_noc_multicast_addr(ex, ey, sx, sy, addr) : get_noc_multicast_addr(sx, sy, ex, ey, addr);
        };
        // the leader sits inside the rectangle: multicast to the NX - 1 others (no loopback), as cmm's in-place sends
        noc_async_write_multicast(ga, mc(ga), NX * 64, NX - 1);
        noc_async_write_multicast(ga + 1024, mc(ga + 1024), NX * 64, NX - 1);
        noc_async_write_multicast(gb, mc(gb), NX * 64, NX - 1);
        noc_async_write_multicast(gb + 1024, mc(gb + 1024), NX * 64, NX - 1);
        noc_async_writes_flushed();
        *sb_p = 1;  // the flag value (the leader's own sem_b is the multicast source)
        noc_semaphore_set_multicast(get_semaphore(sem_b), mc(get_semaphore(sem_b)), NX - 1);
        noc_async_write_barrier();
    } else {
        {WZONE("W-inc");
        noc_semaphore_inc(get_noc_addr(lx, ly, get_semaphore(sem_a)), 1);
        }
        {WZONE("W-swait");
        noc_semaphore_wait(sb_p, 1);
        }
    }
    noc_async_atomic_barrier();
    cb_pop_front(c_pa, 1);
    cb_pop_front(c_pb, 1);
    cb_push_back(c_ga, 1);
    cb_push_back(c_gb, 1);
}
#endif

ADDLN_KERNEL_MAIN {
    constexpr uint32_t NT = get_compile_time_arg_val(ADDLN_CT0);
    constexpr uint32_t NX = get_compile_time_arg_val(ADDLN_CT0 + 1);
    constexpr auto out_args = TensorAccessorArgs<ADDLN_CT0 + 2>();
    const uint32_t out_addr = get_arg_val<uint32_t>(ADDLN_RT0 + 0);
    const uint32_t out_start = get_arg_val<uint32_t>(ADDLN_RT0 + 1);
    const uint32_t col = get_arg_val<uint32_t>(ADDLN_RT0 + 2);
    const uint32_t sem1 = get_arg_val<uint32_t>(ADDLN_RT0 + 3), sem2 = get_arg_val<uint32_t>(ADDLN_RT0 + 4);
    const uint32_t my_x = get_arg_val<uint32_t>(ADDLN_RT0 + 5 + 2 * col), my_y = get_arg_val<uint32_t>(ADDLN_RT0 + 6 + 2 * col);
    constexpr uint32_t c_g1 = ADDLN_C_G1, c_g2 = ADDLN_C_G2, c_p1 = ADDLN_C_P1, c_p2 = ADDLN_C_P2, c_out = ADDLN_C_OUT;

    zero_tail(get_write_ptr(c_g1), NX);
    zero_tail(get_write_ptr(c_g2), NX);
    noc_async_read_barrier();

#ifdef ADDLN_1R
#ifdef ADDLN_LEADER
    gather_leader<c_p1, c_g1, c_p2, c_g2>(col, sem1, sem2, NX);
#else
    gather_round2<c_p1, c_g1, c_p2, c_g2>(col, sem1, NX, my_x, my_y);
#endif
#else
    gather_round<c_p1, c_g1>(col, sem1, NX, my_x, my_y);
    gather_round<c_p2, c_g2>(col, sem2, NX, my_x, my_y);
#endif

    const uint32_t page = get_local_cb_interface(c_out).fifo_page_size;
    {WZONE("W-owait");
    cb_wait_front(c_out, NT);
    }
    uint32_t rp = get_read_ptr(c_out);
#if defined(OUT_XRES)
    // round 12: the LN output goes straight into the RESIDENT in0 of the following xmm fc1 (tt_kernels.xmm_res_alloc: two
    // tensors, even / odd K blocks, same L1 address on all 12 x 10 cores).  This core's NT tiles = K block `col` (kb = NT)
    // of token row `row`: one NT-tile write to every core whose token pair is row / 2 -- three full grid rows
    // (multicast, loopback when this core sits in one) and the row-9 cores x = p, p + 3, p + 6, p + 9 (unicasts).
    // extra runtime args after the peers: row, res0, res1, then 3 x (sx, sy, ex, ey, self_in), then 4 x (x, y)
    {
        constexpr uint32_t a0 = 5 + 2 * NX;
        const uint32_t row = get_arg_val<uint32_t>(ADDLN_RT0 + a0);
        const uint32_t res = get_arg_val<uint32_t>(ADDLN_RT0 + a0 + 1 + (col & 1));
        // shard tile (j, r, k): j = col / 2, r = row % 2, k = 0 .. NT-1; a block is 2 x NT tiles
        const uint32_t dst = res + ((col >> 1) * 2 * NT + (row & 1) * NT) * page;
        const uint32_t bytes = NT * page;
        for (uint32_t i = 0; i < 3; ++i) {
            const uint32_t b = ADDLN_RT0 + a0 + 3 + 5 * i;
            const uint32_t sx = get_arg_val<uint32_t>(b), sy = get_arg_val<uint32_t>(b + 1);
            const uint32_t ex = get_arg_val<uint32_t>(b + 2), ey = get_arg_val<uint32_t>(b + 3);
            const uint64_t ma = noc_index == 1 ? get_noc_multicast_addr(ex, ey, sx, sy, dst) : get_noc_multicast_addr(sx, sy, ex, ey, dst);
            if (get_arg_val<uint32_t>(b + 4)) {
                noc_async_write_multicast_loopback_src(rp, ma, bytes, 12);
            } else {
                noc_async_write_multicast(rp, ma, bytes, 12);
            }
        }
        for (uint32_t i = 0; i < 4; ++i) {
            const uint32_t b = ADDLN_RT0 + a0 + 18 + 2 * i;
            noc_async_write(rp, get_noc_addr(get_arg_val<uint32_t>(b), get_arg_val<uint32_t>(b + 1), dst), bytes);
        }
    }
#elif defined(OUT_WSHARD)
    constexpr uint32_t SW = get_compile_time_arg_val(out_args.next_compile_time_args_offset());
    constexpr uint32_t NS = get_compile_time_arg_val(out_args.next_compile_time_args_offset() + 1);
    constexpr uint32_t a0 = 5 + 2 * NX;
    const uint32_t row = get_arg_val<uint32_t>(ADDLN_RT0 + a0);
    for (uint32_t i = 0; i < NT; ++i) {
        const uint32_t t = col * NT + i;  // global tile column
        const uint32_t j = t / SW;        // shard core
        const uint32_t sx = get_arg_val<uint32_t>(ADDLN_RT0 + a0 + 1 + 2 * j), sy = get_arg_val<uint32_t>(ADDLN_RT0 + a0 + 2 + 2 * j);
        noc_async_write(rp, get_noc_addr(sx, sy, out_addr + (row * SW + (t - j * SW)) * page), page);
        rp += page;
    }
#else
    const auto out = TensorAccessor(out_args, out_addr, page);
    for (uint32_t i = 0; i < NT; ++i) {
        noc_async_write(rp, out.get_noc_addr(out_start + i), page);
        rp += page;
    }
#endif
    noc_async_write_barrier();
    cb_pop_front(c_out, NT);
}