File size: 18,486 Bytes
55474c5 | 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 332 333 334 335 336 337 338 339 340 341 342 343 344 345 | // SPDX-License-Identifier: Apache-2.0
// msda_k (tt/msda_k.py, OPT_PLAN #1 / #2 / #10): the data-movement half of the fixed-point MSDA kernel. The same
// source runs on both data-movement RISC-Vs; PART 0 (NCRISC, "reader") owns the points [0, P/2), PART 1 (BRISC,
// "writer") the points [P/2, P) and also writes the output tiles. Each RISC has its own CBs (cb_geo, cb_in, cb_loc),
// so there is no cross-RISC handshake.
//
// Work item w (contiguous range per core, core index from the logical coordinates: common RT args only, probe P3):
// j = w / NB (head block), b = w % NB (32-query block); value map m = j * SUB + b / (NB / SUB);
// loc int32 TILE [1, H, NB * 32, 32], tile j * NB + b: row r, column 2p / 2p + 1 = x / y of point p as
// round(pixel * 2^16) (pixel = (g + 1) size / 2 - 0.5, align_corners=False);
// attn fp32 TILE [1, H, NB * 32, 32], tile j * NB + b: the softmax weight of point p in column p;
// out bf16 TILE [1, 1, NB * 32, H * 32], tile b * H + j.
// value: VSRC 0 = (N, h, w, 32) ROW_MAJOR, page m * h * w + y * w + x; VSRC 1 = the head split [B, heads, rows, 32]
// TILE as nlp_create_qkv_heads writes it (map m = j * SUB + s: slice j, rows s * h * w ..; TPH tiles per slice), the
// stick of a pixel = row r of its tile: two 32-byte face rows (faces 2 (r / 16) and + 1), no untilize needed.
// Per item and RISC: cb_geo <- 3 fp32 tiles (fx, fy, attn with the other RISC's point columns zeroed) and
// cb_in <- 2P bf16 tiles (step k = (p - p0) * 4 + corner; corners NW, NE, SW, SE): row r of step k is the 32-channel
// value stick of that corner, read as two 32-byte halves straight into the tile's face rows; corners outside the map
// (and padding rows) read the zeros page. The compute kernel turns geo into the 4 corner weights on the SFPU.
#include <stdint.h>
#include "api/dataflow/dataflow_api.h"
#include "ttnn/cpp/ttnn/kernel_lib/reduce_helpers_dataflow.hpp"
constexpr uint32_t PART = get_compile_time_arg_val(1);
constexpr uint32_t cb_in = get_compile_time_arg_val(2);
constexpr uint32_t cb_geo = get_compile_time_arg_val(3);
constexpr uint32_t cb_loc = get_compile_time_arg_val(4);
constexpr uint32_t cb_e = get_compile_time_arg_val(5);
constexpr uint32_t cb_out = get_compile_time_arg_val(6);
constexpr int32_t HV = get_compile_time_arg_val(7);
constexpr int32_t WV = get_compile_time_arg_val(8);
constexpr uint32_t NQ = get_compile_time_arg_val(9);
constexpr uint32_t NB = get_compile_time_arg_val(10);
constexpr uint32_t H = get_compile_time_arg_val(11);
constexpr uint32_t SUB = get_compile_time_arg_val(12);
constexpr uint32_t P = get_compile_time_arg_val(13);
constexpr uint32_t NWORK = get_compile_time_arg_val(14);
constexpr uint32_t NC = get_compile_time_arg_val(15);
constexpr uint32_t GX = get_compile_time_arg_val(16);
constexpr uint32_t WRITES_OUT = get_compile_time_arg_val(17);
constexpr uint32_t MAKES_E = get_compile_time_arg_val(18);
// 0: ROW_MAJOR maps (64-byte pages); 1: TILE heads; 2 (BEVFORMER_MSDA_PAIRS): pixel-pair maps [N, h, w + 1, 64]
// (tt/pairmap_k.py: 128-byte page (m, y, i) = the sticks of x = i - 1 and x = i): ONE read per point and row lands
// the NW + NE (or SW + SE) sticks as a row of a [32 x 64] row-major staging block (tilized by the compute kernel)
constexpr uint32_t VSRC = get_compile_time_arg_val(19);
constexpr uint32_t TPH = get_compile_time_arg_val(20); // VSRC 1: tiles per head slice (SUB maps of HV x WV rows)
// SKIP: items strided over the cores (w = core, core + NC, ..: padding blocks spread evenly) and points with all four
// corners outside the map (or rows past NQ) issue no reads: their weight is set to 0, and their tile rows keep
// earlier (finite) gathers; the step tiles are zeroed once. 0: contiguous item ranges, zeros-page reads.
constexpr uint32_t SKIP = get_compile_time_arg_val(21);
// LOC_HS 0: loc is the head split [1, H, NB * 32, 32] (tile j * NB + b, columns 2p / 2p + 1); LOC_HS > 0
// (BEVFORMER_MSDA_LOC=compact): loc is the merged [1, 1, NB * 32, H * LOC_HS] map (the sampling projection's own
// layout: head j in columns j * LOC_HS .. + 2P), LOC_TW tiles per row block: tile b * LOC_TW + j * LOC_HS / 32,
// column (j * LOC_HS) % 32 + 2p
constexpr uint32_t LOC_HS = get_compile_time_arg_val(22);
constexpr uint32_t LOC_TW = get_compile_time_arg_val(23);
// SM 1 (BEVFORMER_MSDA_SOFTMAX=kernel): attn is the compact attention-logit map [1, 1, NB * 32, H * P] (head j in
// columns j * P ..; AT_TW tiles per row block); the PART-0 RISC rebuilds the head's logit tile (columns 0..P-1 the
// logits, the rest PAD_LOGIT: what the head-block projection writes) into cb_lg for the compute kernel's softmax,
// and makes the reduce scalers and the two half masks once. Rows past NQ get logits 0 (finite; their values are 0).
constexpr uint32_t SM = get_compile_time_arg_val(24);
constexpr uint32_t AT_TW = get_compile_time_arg_val(25);
constexpr uint32_t PAD_BITS = get_compile_time_arg_val(26); // fp32 bits of PAD_LOGIT
constexpr uint32_t cb_lg = 9, cb_scmax = 10, cb_scsum = 11, cb_msk = 12, cb_scr = 18;
constexpr auto v_args = TensorAccessorArgs<27>();
constexpr auto l_args = TensorAccessorArgs<v_args.next_compile_time_args_offset()>();
constexpr auto a_args = TensorAccessorArgs<l_args.next_compile_time_args_offset()>();
constexpr auto o_args = TensorAccessorArgs<a_args.next_compile_time_args_offset()>();
constexpr uint32_t PH = P / 2;
constexpr uint32_t P0 = PART * PH;
constexpr uint32_t NS = PH * 4; // steps (tiles) per item and RISC
constexpr uint32_t TB16 = 2048, TB32 = 4096;
constexpr uint32_t VPAGE = VSRC == 0 ? 64 : (VSRC == 2 ? 128 : 2048); // RM stick; pixel pair; TILE: a bf16 tile
static_assert(VSRC != 2 || !SKIP, "msda_k: pair maps and SKIP are exclusive");
constexpr uint32_t BPS = NB / SUB;
constexpr uint32_t MAP = static_cast<uint32_t>(HV * WV);
constexpr int32_t LOC_LIM = 1 << 28; // |pixel| < 4096 after the clamp: no overflow below
static_assert(P % 2 == 0 && P <= 8, "msda_k: P even, <= 8 (x / y of a point share a face)");
static_assert(NB % SUB == 0, "msda_k: blocks split evenly over the value maps of a head");
static_assert(LOC_HS == 0 || (LOC_HS >= 2 * P && 32 % LOC_HS == 0), "msda_k: LOC_HS holds 2P columns, divides 32");
static inline uint32_t tile_elem(uint32_t r, uint32_t c) {
return (((r >> 4) << 1) + (c >> 4)) * 256 + ((r & 15) << 4) + (c & 15);
}
// q in [0, 65536): the fp32 bit pattern of q / 65536 (exact)
static inline uint32_t q16_to_f32(uint32_t q) {
if (q == 0) {
return 0;
}
const uint32_t n = 31 - __builtin_clz(q);
return ((127 - 16 + n) << 23) | ((q << (23 - n)) & 0x7FFFFF);
}
// one corner stick (32 channels) -> the face rows at dst (channels 0..15) and dst + 512 (16..31). pos: RM page
// (VSRC 0) or slice * TPH * 32 + row (VSRC 1).
// VSRC 3 (BEVFORMER_DEC_VALUE=fused, D20 round 3): the value is the projection itself, TILE [1, 1, SUB h w, H * 32]
// (no head split): the head block j of map row pos is tile (pos / 32) * H + j; g_hj = the current item's head
static uint32_t g_hj = 0;
template <typename A>
static inline void gather(const A& v_acc, uint32_t pos, bool ok, uint64_t zeros, uint32_t dst) {
if (!ok) {
noc_async_read(zeros, dst, 32);
noc_async_read(zeros, dst + 512, 32);
return;
}
uint64_t s;
uint32_t hi;
if constexpr (VSRC == 0) {
s = v_acc.get_noc_addr(pos);
hi = 32;
} else {
const uint32_t r = pos & 31;
const uint32_t t = VSRC == 3 ? (pos >> 5) * H + g_hj : pos >> 5;
s = v_acc.get_noc_addr(t, ((r >> 4) << 10) + ((r & 15) << 5));
hi = 512;
}
#ifndef MSDA_K_NOREAD
noc_async_read(s, dst, 32);
noc_async_read(s + hi, dst + 512, 32);
#endif
}
static inline void zero_l1(uint32_t addr, uint32_t bytes) {
const uint64_t z = get_noc_addr(MEM_ZEROS_BASE);
for (uint32_t o = 0; o < bytes; o += MEM_ZEROS_SIZE) {
noc_async_read(z, addr + o, MEM_ZEROS_SIZE);
}
}
void kernel_main() {
const uint32_t ci = get_absolute_logical_y() * GX + get_absolute_logical_x();
if (ci >= NC) {
return;
}
const uint32_t w0 = SKIP ? ci : ci * NWORK / NC;
const uint32_t w1 = SKIP ? NWORK : (ci + 1) * NWORK / NC;
const uint32_t wstep = SKIP ? NC : 1;
if (w0 >= w1) {
return;
}
const auto v_acc = TensorAccessor(v_args, get_common_arg_val<uint32_t>(0), VPAGE);
const auto l_acc = TensorAccessor(l_args, get_common_arg_val<uint32_t>(1), TB32);
const auto a_acc = TensorAccessor(a_args, get_common_arg_val<uint32_t>(2), TB32);
const auto o_acc = TensorAccessor(o_args, get_common_arg_val<uint32_t>(3), TB16);
const uint64_t zeros = get_noc_addr(MEM_ZEROS_BASE);
if constexpr (MAKES_E) {
// E_p (bf16): row p all ones, so W @ E_p broadcasts column p of W over the 32 channels
cb_reserve_back(cb_e, P);
const uint32_t e_addr = get_write_ptr(cb_e);
zero_l1(e_addr, P * TB16);
noc_async_read_barrier();
volatile tt_l1_ptr uint16_t* e = reinterpret_cast<volatile tt_l1_ptr uint16_t*>(e_addr);
for (uint32_t p = 0; p < P; ++p) {
for (uint32_t c = 0; c < 32; ++c) {
e[p * (TB16 / 2) + tile_elem(p, c)] = 0x3F80;
}
}
cb_push_back(cb_e, P);
}
if constexpr (SM && MAKES_E) {
// the softmax's reduce scalers, as ttnn's softmax reader makes them; the half masks (fp32 1 / 0 per column:
// RISC h owns the points [h P / 2, (h + 1) P / 2)); the logit pages pre-filled with PAD_LOGIT
dataflow_kernel_lib::calculate_and_prepare_reduce_scaler<cb_scmax, ckernel::PoolType::MAX,
ckernel::ReduceDim::REDUCE_ROW>();
dataflow_kernel_lib::calculate_and_prepare_reduce_scaler<cb_scsum, ckernel::PoolType::SUM,
ckernel::ReduceDim::REDUCE_ROW>();
cb_reserve_back(cb_msk, 2);
volatile tt_l1_ptr uint32_t* m = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(get_write_ptr(cb_msk));
for (uint32_t h = 0; h < 2; ++h) {
for (uint32_t r = 0; r < 32; ++r) {
for (uint32_t c = 0; c < 32; ++c) {
m[h * 1024 + tile_elem(r, c)] = (c >= h * PH && c < (h + 1) * PH) ? 0x3F800000u : 0u;
}
}
}
cb_push_back(cb_msk, 2);
cb_reserve_back(cb_lg, 2);
volatile tt_l1_ptr uint32_t* lg = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(get_write_ptr(cb_lg));
for (uint32_t i = 0; i < 2048; ++i) {
lg[i] = PAD_BITS;
}
}
// the geo pages: zero once (columns >= P and the right faces stay zero / finite forever)
cb_reserve_back(cb_geo, 6);
zero_l1(get_write_ptr(cb_geo), 6 * TB32);
if constexpr (SKIP) {
cb_reserve_back(cb_in, 2 * NS);
zero_l1(get_write_ptr(cb_in), 2 * NS * TB16);
}
cb_reserve_back(cb_loc, 1);
const uint32_t lbuf = get_write_ptr(cb_loc);
noc_async_read_barrier();
volatile tt_l1_ptr int32_t* loc = reinterpret_cast<volatile tt_l1_ptr int32_t*>(lbuf);
uint32_t prev = 0xFFFFFFFF;
for (uint32_t w = w0; w < w1; w += wstep) {
const uint32_t j = w / NB;
const uint32_t b = w - j * NB;
// VSRC 0: page of the map's pixel 0; VSRC 1: the slice's first tile row + the map's first row in it
const uint32_t vbase = VSRC == 0 ? (j * SUB + b / BPS) * MAP
: VSRC == 2 ? (j * SUB + b / BPS) * HV * (WV + 1)
: VSRC == 3 ? (b / BPS) * MAP
: j * TPH * 32 + (b / BPS) * MAP;
g_hj = j;
cb_reserve_back(cb_geo, 3);
cb_reserve_back(cb_in, NS);
const uint32_t g_addr = get_write_ptr(cb_geo);
const uint32_t in_base = get_write_ptr(cb_in);
const uint32_t ltile = LOC_HS == 0 ? j * NB + b : b * LOC_TW + (j * LOC_HS) / 32;
const uint32_t lcol = LOC_HS == 0 ? 0 : (j * LOC_HS) % 32;
noc_async_read(l_acc.get_noc_addr(ltile), lbuf, TB32);
if constexpr (SM) {
if constexpr (MAKES_E) {
noc_async_read(a_acc.get_noc_addr(b * AT_TW + (j * P) / 32), get_write_ptr(cb_scr), TB32);
}
} else {
noc_async_read(a_acc.get_noc_addr(j * NB + b), g_addr + 2 * TB32, TB32);
}
noc_async_read_barrier();
if constexpr (SM && MAKES_E) {
// the head's logit tile: columns 0..P-1 <- compact columns (j P) % 32 + p; columns >= P keep PAD
cb_reserve_back(cb_lg, 1);
volatile tt_l1_ptr uint32_t* lg = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(get_write_ptr(cb_lg));
volatile tt_l1_ptr uint32_t* src = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(get_write_ptr(cb_scr));
const uint32_t acol = (j * P) % 32;
for (uint32_t r = 0; r < 32; ++r) {
const bool ok = b * 32 + r < NQ;
for (uint32_t p = 0; p < P; ++p) {
lg[tile_elem(r, p)] = ok ? src[tile_elem(r, acol + p)] : 0u;
}
}
cb_push_back(cb_lg, 1);
}
volatile tt_l1_ptr uint32_t* fx = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(g_addr);
volatile tt_l1_ptr uint32_t* fy = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(g_addr + TB32);
volatile tt_l1_ptr uint32_t* at = reinterpret_cast<volatile tt_l1_ptr uint32_t*>(g_addr + 2 * TB32);
// the other RISC's points carry zero weight in this copy (their fx / fy here are stale but finite); SM: the
// compute kernel applies the half mask to the softmax instead
if constexpr (!SM) {
for (uint32_t r = 0; r < 32; ++r) {
for (uint32_t p = 0; p < P; ++p) {
if (p < P0 || p >= P0 + PH) {
at[tile_elem(r, p)] = 0;
}
}
}
}
for (uint32_t r = 0; r < 32; ++r) {
const uint32_t roff = (((r >> 4) << 1) << 9) + ((r & 15) << 5); // face (r / 16, 0), row r % 16
const bool row_ok = b * 32 + r < NQ;
for (uint32_t p = P0; p < P0 + PH; ++p) {
const uint32_t dst = in_base + (p - P0) * 4 * TB16 + roff;
const uint32_t ew = tile_elem(r, p);
if (!row_ok) {
fx[ew] = 0;
fy[ew] = 0;
if constexpr (!SM) {
at[ew] = 0;
}
if constexpr (SKIP) {
continue;
}
if constexpr (VSRC == 2) {
const uint32_t blk = in_base + (p - P0) * 4 * TB16 + r * 128;
noc_async_read(zeros, blk, 128);
noc_async_read(zeros, blk + 2 * TB16, 128);
continue;
}
for (uint32_t c = 0; c < 4; ++c) {
noc_async_read(zeros, dst + c * TB16, 32);
noc_async_read(zeros, dst + c * TB16 + 512, 32);
}
continue;
}
int32_t vx = loc[tile_elem(r, lcol + 2 * p)];
int32_t vy = loc[tile_elem(r, lcol + 2 * p + 1)];
vx = vx > LOC_LIM ? LOC_LIM : (vx < -LOC_LIM ? -LOC_LIM : vx);
vy = vy > LOC_LIM ? LOC_LIM : (vy < -LOC_LIM ? -LOC_LIM : vy);
const int32_t x0 = vx >> 16;
const int32_t y0 = vy >> 16;
fx[ew] = q16_to_f32(static_cast<uint32_t>(vx) & 0xFFFF);
fy[ew] = q16_to_f32(static_cast<uint32_t>(vy) & 0xFFFF);
const bool xv0 = x0 >= 0 && x0 < WV, xv1 = x0 + 1 >= 0 && x0 + 1 < WV;
const bool yv0 = y0 >= 0 && y0 < HV, yv1 = y0 + 1 >= 0 && y0 + 1 < HV;
if constexpr (SKIP) {
static_assert(!(SKIP && SM), "msda_k: SKIP and SM are exclusive");
if (!((yv0 || yv1) && (xv0 || xv1))) {
at[ew] = 0;
continue;
}
}
if constexpr (VSRC == 2) {
// staging blocks of point p: top (NW | NE) at + 0, bottom (SW | SE) at + 2 tiles; row r: 128 B
const uint32_t blk = in_base + (p - P0) * 4 * TB16 + r * 128;
const bool xp = x0 >= -1 && x0 < WV; // page i = x0 + 1 in [0, WV]
const uint32_t pg = vbase + static_cast<uint32_t>(y0 * (WV + 1) + x0 + 1);
if (xp && yv0) {
noc_async_read(v_acc.get_noc_addr(pg), blk, 128);
} else {
noc_async_read(zeros, blk, 128);
}
if (xp && yv1) {
noc_async_read(v_acc.get_noc_addr(pg + WV + 1), blk + 2 * TB16, 128);
} else {
noc_async_read(zeros, blk + 2 * TB16, 128);
}
continue;
}
const uint32_t pos = vbase + static_cast<uint32_t>(y0 * WV + x0);
gather(v_acc, pos, yv0 && xv0, zeros, dst);
gather(v_acc, pos + 1, yv0 && xv1, zeros, dst + TB16);
gather(v_acc, pos + WV, yv1 && xv0, zeros, dst + 2 * TB16);
gather(v_acc, pos + WV + 1, yv1 && xv1, zeros, dst + 3 * TB16);
}
}
noc_async_read_barrier();
cb_push_back(cb_geo, 3);
cb_push_back(cb_in, NS);
if constexpr (WRITES_OUT) {
if (prev != 0xFFFFFFFF) {
const uint32_t jp = prev / NB;
const uint32_t bp = prev - jp * NB;
cb_wait_front(cb_out, 1);
noc_async_write(get_read_ptr(cb_out), o_acc.get_noc_addr(bp * H + jp), TB16);
noc_async_writes_flushed();
cb_pop_front(cb_out, 1);
}
}
prev = w;
}
if constexpr (WRITES_OUT) {
const uint32_t jp = prev / NB;
const uint32_t bp = prev - jp * NB;
cb_wait_front(cb_out, 1);
noc_async_write(get_read_ptr(cb_out), o_acc.get_noc_addr(bp * H + jp), TB16);
noc_async_write_barrier();
cb_pop_front(cb_out, 1);
}
}
|