// SPDX-License-Identifier: Apache-2.0 // STEM_POOL writer (meteor-p150 OPT round 3 R3.2, tt/pool.py): the horizontal half of the 3x3 / s2 max pool and the // output. Per unit (work split as the reader), the WT vertical-max tiles of CB 16 hold one input row of 32 * WT // columns w (tile tx = w / 32, tile row w % 32) for one channel tile. Output column ox takes max(v[2ox-1], v[2ox], // v[2ox+1]); column -1 is the zero padding (+0.0; W = 2 * OW, so 2ox+1 always exists). The OWT = WT / 2 output tiles // are built in a scratch CB (24) on this RISC and written to the TILE fp32 output [1, 1, N*OH*OW, C]. // Compare: the fp32 bits as int32. The inputs are post-ReLU (>= +0.0, or -0.0), where the int32 order is the float // order with -0.0 < +0.0 (that of the SFPU min / max swap); fp32 is never rounded here. // Tile element (row r, column c): face ((r / 16) * 2 + c / 16) * 256 + (r % 16) * 16 + c % 16. // CT args: [0] NU, [1] NC, [2] GX, [3] WT, [4] CT, [5] OH, then the TensorAccessorArgs of the output. // Common RT args: [out_addr]. #include #include "api/dataflow/dataflow_api.h" constexpr uint32_t NU = get_compile_time_arg_val(0); constexpr uint32_t NC = get_compile_time_arg_val(1); constexpr uint32_t GX = get_compile_time_arg_val(2); constexpr uint32_t WT = get_compile_time_arg_val(3); constexpr uint32_t CT = get_compile_time_arg_val(4); constexpr uint32_t OH = get_compile_time_arg_val(5); constexpr auto out_args = TensorAccessorArgs<6>(); constexpr uint32_t OWT = WT / 2; constexpr uint32_t TB = 4096; constexpr uint32_t cb_v = 16, cb_s = 24; static inline uint32_t row_off(uint32_t r) { return (r >> 4) * 512 + (r & 15) * 16; } // + (c / 16) * 256 + c % 16 void kernel_main() { const uint32_t core = get_absolute_logical_y() * GX + get_absolute_logical_x(); if (core >= NC) { return; } constexpr uint32_t B = NU / NC, E = NU % NC; const uint32_t u0 = core * B + (core < E ? core : E); const uint32_t nu = B + (core < E ? 1 : 0); const auto out = TensorAccessor(out_args, get_common_arg_val(0), TB); volatile tt_l1_ptr int32_t* s = reinterpret_cast(get_write_ptr(cb_s)); for (uint32_t u = u0; u < u0 + nu; ++u) { const uint32_t ct = u % CT; const uint32_t q = u / CT; // n * OH + oy cb_wait_front(cb_v, WT); volatile tt_l1_ptr int32_t* v = reinterpret_cast(get_read_ptr(cb_v)); noc_async_write_barrier(); // the scratch tiles of the previous unit are out for (uint32_t ot = 0; ot < OWT; ++ot) { volatile tt_l1_ptr int32_t* o = s + ot * 1024; for (uint32_t r = 0; r < 32; ++r) { const uint32_t w1 = (ot * 32 + r) * 2; // 2 ox const uint32_t w2 = w1 + 1; volatile tt_l1_ptr int32_t* p1 = v + (w1 >> 5) * 1024 + row_off(w1 & 31); volatile tt_l1_ptr int32_t* p2 = v + (w2 >> 5) * 1024 + row_off(w2 & 31); volatile tt_l1_ptr int32_t* q0 = o + row_off(r); if (w1 == 0) { // column -1: the +0.0 padding for (uint32_t f = 0; f < 2; ++f) { for (uint32_t c = 0; c < 16; ++c) { int32_t m = p1[f * 256 + c]; const int32_t b = p2[f * 256 + c]; m = b > m ? b : m; q0[f * 256 + c] = m > 0 ? m : 0; } } } else { const uint32_t w0 = w1 - 1; volatile tt_l1_ptr int32_t* p0 = v + (w0 >> 5) * 1024 + row_off(w0 & 31); for (uint32_t f = 0; f < 2; ++f) { for (uint32_t c = 0; c < 16; ++c) { int32_t m = p0[f * 256 + c]; const int32_t a = p1[f * 256 + c]; const int32_t b = p2[f * 256 + c]; m = a > m ? a : m; q0[f * 256 + c] = b > m ? b : m; } } } } } cb_pop_front(cb_v, WT); const uint32_t base = (q * OWT) * CT + ct; for (uint32_t ot = 0; ot < OWT; ++ot) { noc_async_write(reinterpret_cast(s + ot * 1024), out.get_noc_addr(base + ot * CT), TB); } } noc_async_write_barrier(); }