char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Max Krasnyanskyhexagon: support for multi-device model split (aka row-split) (#28589)eafe15a5e

char/aliceai-t5-moe
218.3 KiB4247 linesraw
1#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
2#pragma clang diagnostic ignored "-Wunused-function"
3#pragma clang diagnostic ignored "-Wunused-variable"
4#pragma clang diagnostic ignored "-Wunused-but-set-variable"
5
6#include <HAP_farf.h>
7#include <HAP_perf.h>
8#include <HAP_compute_res.h>
9
10#include <math.h>
11#include <string.h>
12#include <stdatomic.h>
13
14#include "hex-dma.h"
15#include "hvx-utils.h"
16#include "hvx-dump.h"
17#include "hvx-arith.h"
18#include "hvx-reduce.h"
19
20#define GGML_COMMON_DECL_C
21#include "ggml-common.h"
22#include "htp-ctx.h"
23#include "htp-ops.h"
24#include "htp-tensor.h"
25#include "matmul-ops.h"
26#include "htp-vtcm.h"
27
28static void hvx_tensor_add_f32_grid(
29    const struct htp_tensor * restrict dst,
30    const struct htp_tensor * restrict src2,
31    uint32_t start_row,
32    uint32_t end_row,
33    uint32_t start_col,
34    uint32_t end_col,
35    const struct fastdiv_values * div_ne11_12,
36    const struct fastdiv_values * div_ne11
37);
38
39typedef struct {
40    float        *dst;
41    const float  *src2;
42    const float  *activation;
43    const __fp16 *weight;
44    int           m;
45    int           k;
46    int           n;
47    int           act_stride;
48    int           weight_stride;
49    int           dst_stride;
50    uint32_t      src2_stride;
51    int           ne02;
52    int           ne03;
53    int           ne12;
54    int           ne13;
55    size_t        src0_nb2;
56    size_t        src0_nb3;
57    size_t        src1_nb2;
58    size_t        src1_nb3;
59    size_t        src2_nb2;
60    size_t        src2_nb3;
61    size_t        dst_nb2;
62    size_t        dst_nb3;
63    int           r2;
64    int           r3;
65    struct fastdiv_values div_r2;
66    struct fastdiv_values div_r3;
67} hmx_mm_f16_f32_batched_params_t;
68
69struct htp_mm_context {
70    const char * type;
71    struct htp_ops_context * octx;
72    const struct htp_tensor * act;
73
74    void (*vec_dot_1x1)(const uint32_t n, float * restrict s0,
75         const void * restrict vx0,
76         const void * restrict vy0);
77
78    void (*vec_dot_2x1)(const uint32_t n, float * restrict s0,
79         const void * restrict vx0, const void * restrict vx1,
80         const void * restrict vy0);
81
82    void (*vec_dot_2x2)(const uint32_t n, float * restrict s0, float * restrict s1,
83         const void * restrict vx0, const void * restrict vx1,
84         const void * restrict vy0, const void * restrict vy1);
85
86    void (*vec_dot_32x1)(const uint32_t n, float * restrict s,
87         const void * restrict vx,
88         const void * restrict vy, uint32_t valid_rows,
89         const float * restrict sz);
90
91    // Precomputed values
92    uint32_t src0_nrows_per_thread;
93    uint32_t src0_row_start;
94    uint32_t src0_row_end;
95    uint32_t src0_row_size_padded;
96    uint32_t src1_nrows;
97
98    struct fastdiv_values mm_div_ne12_ne1;
99    struct fastdiv_values mm_div_ne1;
100    struct fastdiv_values mm_div_r2;
101    struct fastdiv_values mm_div_r3;
102    struct fastdiv_values mm_div_ne11;
103
104    // Per thread quant tasks
105    // Precomputed block-parallel quantization values
106    worker_callback_t quant_task_func;
107    uint32_t          quant_ib_first[WORK_QUEUE_MAX_N_THREADS];
108    uint32_t          quant_ib_last[WORK_QUEUE_MAX_N_THREADS];
109    uint32_t          quant_r[WORK_QUEUE_MAX_N_THREADS];
110    uint32_t          quant_c[WORK_QUEUE_MAX_N_THREADS];
111    uint32_t          n_quant_tasks;
112    uint32_t          n_quant_rows_per_thread;
113    atomic_uint       quant_barrier;
114
115    // Fields for scattered mapping & HMX support in MUL_MAT_ID
116    const uint32_t * matrix_row_counts;
117    const struct mmid_row_mapping * matrix_rows;
118    uint32_t mapping_stride;
119
120    // Dynamic VTCM pointers allocated sequentially
121    uint8_t * vtcm_src0;
122    uint8_t * vtcm_src1;
123    uint8_t * vtcm_src2;
124    uint8_t * vtcm_src3;
125    uint8_t * vtcm_dst;
126
127    // Cached strides
128    uint32_t vtcm_src0_stride;
129    uint32_t vtcm_src1_stride;
130    uint32_t vtcm_src2_stride;
131    uint32_t vtcm_src3_stride;
132
133    // Cached thread offsets/sizes
134    uint32_t vtcm_src0_size_per_thread;
135    uint32_t vtcm_src1_size_per_thread;
136    uint32_t vtcm_src2_size_per_thread;
137    uint32_t vtcm_src3_size_per_thread;
138    uint32_t vtcm_dst_size_per_thread;
139};
140
141static int htp_mm_init_context(
142    struct htp_ops_context * octx,
143    const struct htp_mm_kernel_params * kparams
144) {
145    if (!htp_ops_context_set_n_threads(octx, (uint32_t) kparams->n_threads)) {
146        return HTP_STATUS_INVAL_PARAMS;
147    }
148
149    if (kparams->n_hmx) {
150        if (kparams->n_act_threads <= 0 || kparams->n_act_threads > (int32_t) octx->n_threads) {
151            return HTP_STATUS_INVAL_PARAMS;
152        }
153    }
154
155    return HTP_STATUS_OK;
156}
157
158// vdelta control to expand first 32 e8m0 values into 32 uint32 elements
159static const uint8_t __attribute__((aligned(128))) expand_x32_e8m0[128] = {
160    0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x02, 0x00, 0x08, 0x08, 0x01, 0x02, 0x00, 0x04, 0x04, 0x00, 0x00,
161    0x00, 0x11, 0x10, 0x10, 0x10, 0x02, 0x00, 0x04, 0x00, 0x01, 0x02, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x01, 0x04,
162    0x00, 0x00, 0x22, 0x20, 0x20, 0x20, 0x21, 0x22, 0x20, 0x24, 0x04, 0x00, 0x00, 0x00, 0x09, 0x08, 0x00, 0x00, 0x02,
163    0x00, 0x04, 0x00, 0x11, 0x12, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x01, 0x04, 0x00, 0x00, 0x02, 0x00, 0x08, 0x08,
164    0x01, 0x02, 0x00, 0x04, 0x44, 0x40, 0x40, 0x40, 0x41, 0x40, 0x40, 0x40, 0x42, 0x40, 0x44, 0x40, 0x41, 0x42, 0x48,
165    0x48, 0x08, 0x08, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x12, 0x10, 0x10, 0x10, 0x01, 0x02, 0x00, 0x04, 0x04, 0x00,
166    0x00, 0x00, 0x09, 0x08, 0x00, 0x00, 0x22, 0x20, 0x24, 0x20, 0x21, 0x22, 0x20, 0x20,
167};
168
169// IQ4_NL dequantization LUT: maps 4-bit index (0-15) to int8 kvalue
170// kvalues: -127, -104, -83, -65, -49, -35, -22, -10, 1, 13, 25, 38, 53, 69, 89, 113
171static const uint8_t __attribute__((aligned(VLEN))) kvalues_iq4nl_lut[] = {
172    0x81, 0, 0x98, 0, 0xAD, 0, 0xBF, 0, 0xCF, 0, 0xDD, 0, 0xEA, 0, 0xF6, 0, 0x01, 0, 0x0D, 0, 0x19, 0, 0x26, 0,
173    0x35, 0, 0x45, 0, 0x59, 0, 0x71, 0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0,
174    0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0,
175    0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0,
176    0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0, 0,    0,
177};
178
179static const uint8_t __attribute__((aligned(VLEN))) kvalues_mxfp4_lut[] = {
180    0,    0, 1,    0, 2,    0, 3, 0, 4, 0, 6, 0, 8, 0, 12, 0, 0, 0, 0xff, 0, 0xfe, 0, 0xfd, 0, 0xfc, 0,
181    0xfa, 0, 0xf8, 0, 0xf4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0,    0, 0,    0, 0,    0, 0,    0,
182    0,    0, 0,    0, 0,    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0,    0, 0,    0, 0,    0, 0,    0,
183    0,    0, 0,    0, 0,    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0,    0, 0,    0, 0,    0, 0,    0,
184    0,    0, 0,    0, 0,    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0,    0, 0,    0, 0,    0,
185};
186
187#define htp_matmul_tensors_preamble                                 \
188    const struct htp_tensor * restrict src0 = octx->src[0];         \
189    const struct htp_tensor * restrict src1 = octx->src[1];         \
190    const struct htp_tensor * restrict src2 = octx->src[2];         \
191    const struct htp_tensor * restrict  dst = octx->dst;            \
192                                                                    \
193    const uint32_t ne00 = src0->ne[0];                              \
194    const uint32_t ne01 = src0->ne[1];                              \
195    const uint32_t ne02 = src0->ne[2];                              \
196    const uint32_t ne03 = src0->ne[3];                              \
197                                                                    \
198    const uint32_t ne10 = src1->ne[0];                              \
199    const uint32_t ne11 = src1->ne[1];                              \
200    const uint32_t ne12 = src1->ne[2];                              \
201    const uint32_t ne13 = src1->ne[3];                              \
202                                                                    \
203    const uint32_t ne20 = src2 ? src2->ne[0] : 0;                   \
204    const uint32_t ne21 = src2 ? src2->ne[1] : 0;                   \
205    const uint32_t ne22 = src2 ? src2->ne[2] : 0;                   \
206    const uint32_t ne23 = src2 ? src2->ne[3] : 0;                   \
207                                                                    \
208    const uint32_t ne0 = dst->ne[0];                                \
209    const uint32_t ne1 = dst->ne[1];                                \
210    const uint32_t ne2 = dst->ne[2];                                \
211    const uint32_t ne3 = dst->ne[3];                                \
212                                                                    \
213    const uint32_t nb00 = src0->nb[0];                              \
214    const uint32_t nb01 = src0->nb[1];                              \
215    const uint32_t nb02 = src0->nb[2];                              \
216    const uint32_t nb03 = src0->nb[3];                              \
217                                                                    \
218    const uint32_t nb10 = src1->nb[0];                              \
219    const uint32_t nb11 = src1->nb[1];                              \
220    const uint32_t nb12 = src1->nb[2];                              \
221    const uint32_t nb13 = src1->nb[3];                              \
222                                                                    \
223    const uint32_t nb0 = dst->nb[0];                                \
224    const uint32_t nb1 = dst->nb[1];                                \
225    const uint32_t nb2 = dst->nb[2];                                \
226    const uint32_t nb3 = dst->nb[3];
227
228#define htp_matmul_preamble                                         \
229    struct htp_mm_context * mmctx  = data;                          \
230    struct htp_ops_context * octx  = mmctx->octx;                   \
231    dma_queue *dma_queue           = octx->ctx->dma[ith];           \
232    uint32_t src0_nrows_per_thread = mmctx->src0_nrows_per_thread;  \
233    htp_matmul_tensors_preamble;
234
235static inline void hvx_mm_run_quant_task(struct htp_mm_context * mmctx, unsigned int ith) {
236    if (mmctx->quant_task_func) {
237        if (ith < mmctx->n_quant_tasks) {
238            mmctx->quant_task_func(mmctx->n_quant_tasks, ith, mmctx);
239            atomic_fetch_sub(&mmctx->quant_barrier, 1);
240        }
241        while (atomic_load(&mmctx->quant_barrier) > 0) {
242            // spin
243        }
244    }
245}
246
247// *** matmul with support for 4d tensors and full broadcasting
248
249static void hvx_mm_4d(unsigned int nth, unsigned int ith, void * data) {
250    htp_matmul_preamble;
251
252    assert(ne12 % ne02 == 0);
253    assert(ne13 % ne03 == 0);
254
255    // This is the size of the first dimension of the result, so we can iterate that way. (see the ASSERT above, these are the same numbers)
256    const uint32_t nr0 = ne0;
257
258    // This is the size of the rest of the dimensions of the result
259    const uint32_t nr1 = ne1 * ne2 * ne3;
260
261    const uint32_t src0_nrows = mmctx->src0_row_end - mmctx->src0_row_start;
262
263    // distribute the thread work across the inner or outer loop based on which one is larger
264    uint32_t dr0, dr1, ith0, ith1;
265    if (nr0 > nr1) {
266        dr0  = fastdiv(src0_nrows + nth - 1, &octx->n_threads_div);
267        dr1  = nr1;
268        ith0 = ith;
269        ith1 = 0;
270    } else {
271        dr0  = src0_nrows;
272        dr1  = fastdiv(nr1 + nth - 1, &octx->n_threads_div);
273        ith0 = 0;
274        ith1 = ith;
275    }
276
277    const uint32_t ir0_start = mmctx->src0_row_start + dr0 * ith0;
278    const uint32_t ir0_end   = MIN(ir0_start + dr0, mmctx->src0_row_end);
279
280    const uint32_t ir1_start = dr1 * ith1;
281    const uint32_t ir1_end   = MIN(ir1_start + dr1, nr1);
282
283    // no work for this thread
284    if (ir0_start >= ir0_end || ir1_start >= ir1_end) {
285        return;
286    }
287
288    struct htp_thread_trace * tr = &octx->ctx->trace[ith];
289    htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, ir0_start);
290
291    const uint32_t blck_0 = 64;
292    const uint32_t blck_1 = 64;
293
294    for (uint32_t iir1 = ir1_start; iir1 < ir1_end; iir1 += blck_1) {
295        for (uint32_t iir0 = ir0_start; iir0 < ir0_end; iir0 += blck_0) {
296            for (uint32_t ir1 = iir1; ir1 < MIN(iir1 + blck_1, ir1_end); ir1++) {
297                const uint32_t i13 = fastdiv(ir1, &mmctx->mm_div_ne12_ne1);
298                const uint32_t i12 = fastdiv(ir1 - i13 * ne12 * ne1, &mmctx->mm_div_ne1);
299                const uint32_t i11 = (ir1 - i13 * ne12 * ne1 - i12 * ne1);
300
301                // broadcast src0 into src1
302                const uint32_t i03 = fastdiv(i13, &mmctx->mm_div_r3);
303                const uint32_t i02 = fastdiv(i12, &mmctx->mm_div_r2);
304
305                const uint32_t i1 = i11;
306                const uint32_t i2 = i12;
307                const uint32_t i3 = i13;
308
309                const uint8_t * restrict src0_base = (const uint8_t *) src0->data + (0 + i02 * nb02 + i03 * nb03);
310                const uint8_t * restrict src1_col  = (const uint8_t *) src1->data + (i11 * nb11 + i12 * nb12 + i13 * nb13);
311                float * dst_col = (float *) ((uint8_t * restrict) dst->data + (i1 * nb1 + i2 * nb2 + i3 * nb3));
312
313                const uint32_t ir0_block_end = MIN(iir0 + blck_0, ir0_end);
314                for (uint32_t ir0 = iir0; ir0 < ir0_block_end; ir0++) {
315                    const uint8_t * restrict src0_row = src0_base + ir0 * nb01;
316                    mmctx->vec_dot_1x1(ne00, &dst_col[ir0], src0_row, src1_col);
317                }
318            }
319        }
320    }
321
322    htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, ir0_start);
323    if (src2) {
324        hvx_tensor_add_f32_grid(dst, src2, ir1_start, ir1_end, ir0_start, ir0_end, &mmctx->mm_div_ne12_ne1, &mmctx->mm_div_ne1);
325    }
326}
327
328#include "hmx-mm-kernels-tiled.h"
329#include "hvx-mm-kernels-tiled.h"
330#include "hvx-mm-kernels-flat.h"
331
332// Specialized repacked matmul macros
333#define MATMUL_2D_REPACKED_IMPL(SUFFIX, TILE_SIZE, DOT_2X2, DOT_2X1)                                                              \
334static void hvx_mm_2d_repacked_##SUFFIX(unsigned int nth, unsigned int ith, void * data) {                                        \
335    htp_matmul_preamble;                                                                                                          \
336                                                                                                                                  \
337    const uint32_t src0_nrows = mmctx->src0_row_end - mmctx->src0_row_start;                                                      \
338    const uint32_t src1_nrows = ne11 * ne12 * ne13;                                                                               \
339                                                                                                                                  \
340    const uint32_t src0_start_row  = mmctx->src0_row_start + src0_nrows_per_thread * ith;                                         \
341    const uint32_t src0_end_row    = MIN(src0_start_row + src0_nrows_per_thread, mmctx->src0_row_end);                            \
342                                                                                                                                  \
343    struct htp_thread_trace * tr = &octx->ctx->trace[ith];                                                                        \
344                                                                                                                                  \
345    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;                      \
346    const uint32_t n_prefetch = kparams->n_prefetch;                                                                              \
347    assert(n_prefetch >= 2 && n_prefetch <= HTP_MM_MAX_PREFETCH && (n_prefetch & (n_prefetch - 1)) == 0);                         \
348                                                                                                                                  \
349    const size_t dst_row_size  = nb1;                                                                                             \
350    const size_t src1_row_size = nb11;                                                                                            \
351    const size_t src1_stride = mmctx->vtcm_src1_stride;                                                                           \
352    const size_t src2_stride = src2 ? ((src2->ne[1] == 1) ? 0 : src2->nb[1]) : 0;                                                 \
353                                                                                                                                  \
354    uint8_t * restrict vtcm_dst_ptr  = mmctx->vtcm_dst  + mmctx->vtcm_dst_size_per_thread  * ith;                                 \
355    uint8_t * restrict vtcm_src0_ptr = mmctx->vtcm_src0 + mmctx->vtcm_src0_size_per_thread * ith;                                 \
356    uint8_t * restrict src1_data = mmctx->vtcm_src1;                                                                              \
357                                                                                                                                  \
358    const uint8_t * restrict src0_row = (const uint8_t *) src0->data;                                                             \
359                                                                                                                                  \
360    const uint32_t tile_size = TILE_SIZE;                                                                                         \
361    const uint32_t aligned_tile_size = hex_align_up(tile_size, 128);                                                              \
362                                                                                                                                  \
363    uint32_t n_k_tiles_w = ne00 / 32;                                                                                             \
364    uint32_t n_k_tiles_a = ne10 / 32;                                                                                             \
365    uint32_t tile_row_stride = n_k_tiles_w * tile_size;                                                                           \
366    uint32_t tile_row_transfer_size_aligned = n_k_tiles_a * aligned_tile_size;                                                    \
367                                                                                                                                  \
368    uint32_t ct_start = src0_start_row / 32;                                                                                      \
369    uint32_t ct_end   = (src0_end_row + 31) / 32;                                                                                 \
370                                                                                                                                  \
371    uint32_t push_ct = ct_start;                                                                                                  \
372    if (src0_start_row < src0_end_row) {                                                                                          \
373        for (uint32_t d = 0; d < n_prefetch && push_ct < ct_end; d++, push_ct++) {                                                \
374            dma_queue_push(dma_queue, dma_make_ptr(vtcm_src0_ptr + d * tile_row_transfer_size_aligned,                            \
375                           src0_row + push_ct * tile_row_stride), aligned_tile_size, tile_size, tile_size, n_k_tiles_a);          \
376        }                                                                                                                         \
377    }                                                                                                                             \
378                                                                                                                                  \
379    hvx_mm_run_quant_task(mmctx, ith);                                                                                            \
380                                                                                                                                  \
381    if (src0_start_row >= src0_end_row) {                                                                                         \
382        return;                                                                                                                   \
383    }                                                                                                                             \
384                                                                                                                                  \
385    for (uint32_t ct = ct_start; ct < ct_end; ct++) {                                                                             \
386        const uint8_t * w_tile = dma_queue_pop(dma_queue).dst;                                                                    \
387                                                                                                                                  \
388        int valid_rows = (int)ne0 - (int)(ct * 32);                                                                               \
389        valid_rows = MIN(32, MAX(0, valid_rows));                                                                                 \
390                                                                                                                                  \
391        htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, ct);                                                                    \
392        uint32_t ir1 = 0;                                                                                                         \
393        for (; ir1 + 1 < src1_nrows; ir1 += 2) {                                                                                  \
394            const uint8_t * restrict src1_col0 = (const uint8_t *) (src1_data + (ir1+0) * src1_stride);                           \
395            const uint8_t * restrict src1_col1 = (const uint8_t *) (src1_data + (ir1+1) * src1_stride);                           \
396            float * restrict dst_row0 = (float *) (dst->data + ((ir1+0) * dst_row_size));                                         \
397            float * restrict dst_row1 = (float *) (dst->data + ((ir1+1) * dst_row_size));                                         \
398                                                                                                                                  \
399            float * dst_ptr0 = &dst_row0[ct * 32];                                                                                \
400            float * dst_ptr1 = &dst_row1[ct * 32];                                                                                \
401                                                                                                                                  \
402            const float * src2_ptr0 = NULL;                                                                                       \
403            const float * src2_ptr1 = NULL;                                                                                       \
404            if (src2) {                                                                                                           \
405                const float * restrict src2_row0 = (const float *) ((const uint8_t *) src2->data + ((ir1+0) * src2_stride));      \
406                const float * restrict src2_row1 = (const float *) ((const uint8_t *) src2->data + ((ir1+1) * src2_stride));      \
407                src2_ptr0 = &src2_row0[ct * 32];                                                                                  \
408                src2_ptr1 = &src2_row1[ct * 32];                                                                                  \
409            }                                                                                                                     \
410            DOT_2X2(ne10, dst_ptr0, dst_ptr1, w_tile, src1_col0, src1_col1, valid_rows, src2_ptr0, src2_ptr1);                    \
411        }                                                                                                                         \
412                                                                                                                                  \
413        for (; ir1 < src1_nrows; ++ir1) {                                                                                         \
414            const uint8_t * restrict src1_col = (const uint8_t *) (src1_data + ir1 * src1_stride);                                \
415            float * restrict dst_row          = (float *) (dst->data + (ir1 * dst_row_size));                                     \
416            float * dst_ptr = &dst_row[ct * 32];                                                                                  \
417                                                                                                                                  \
418            const float * src2_ptr = NULL;                                                                                        \
419            if (src2) {                                                                                                           \
420                const float * restrict src2_row = (const float *) ((const uint8_t *) src2->data + (ir1 * src2_stride));           \
421                src2_ptr = &src2_row[ct * 32];                                                                                    \
422            }                                                                                                                     \
423            DOT_2X1(ne10, dst_ptr, w_tile, src1_col, valid_rows, src2_ptr);                                                       \
424        }                                                                                                                         \
425        htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, ct);                                                                     \
426                                                                                                                                  \
427        if (push_ct < ct_end) {                                                                                                   \
428            dma_queue_push(dma_queue, dma_make_ptr((uint8_t *)w_tile, src0_row + push_ct * tile_row_stride),                      \
429                           aligned_tile_size, tile_size, tile_size, n_k_tiles_a);                                                 \
430            push_ct++;                                                                                                            \
431        }                                                                                                                         \
432    }                                                                                                                             \
433}
434
435#define MATVEC_2D_REPACKED_IMPL(SUFFIX, TILE_SIZE, DOT_2X1)                                                                       \
436static void hvx_mv_2d_repacked_##SUFFIX(unsigned int nth, unsigned int ith, void * data) {                                        \
437    htp_matmul_preamble;                                                                                                          \
438                                                                                                                                  \
439    const uint32_t src0_nrows = mmctx->src0_row_end - mmctx->src0_row_start;                                                      \
440                                                                                                                                  \
441    const uint32_t src0_start_row  = mmctx->src0_row_start + src0_nrows_per_thread * ith;                                         \
442    const uint32_t src0_end_row    = MIN(src0_start_row + src0_nrows_per_thread, mmctx->src0_row_end);                            \
443                                                                                                                                  \
444    struct htp_thread_trace * tr = &octx->ctx->trace[ith];                                                                        \
445                                                                                                                                  \
446    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;                      \
447    const uint32_t n_prefetch = kparams->n_prefetch;                                                                              \
448    assert(n_prefetch >= 2 && n_prefetch <= HTP_MM_MAX_PREFETCH && (n_prefetch & (n_prefetch - 1)) == 0);                         \
449                                                                                                                                  \
450    const size_t dst_row_size  = nb1;                                                                                             \
451    const size_t src1_row_size = nb11;                                                                                            \
452    const size_t src1_stride = mmctx->vtcm_src1_stride;                                                                           \
453                                                                                                                                  \
454    uint8_t * vtcm_dst_ptr  = mmctx->vtcm_dst + mmctx->vtcm_dst_size_per_thread * ith;                                            \
455    uint8_t * vtcm_src0_ptr = mmctx->vtcm_src0 + mmctx->vtcm_src0_size_per_thread * ith;                                          \
456    uint8_t * src1_data = mmctx->vtcm_src1;                                                                                       \
457                                                                                                                                  \
458    float * tmp = (float *) vtcm_dst_ptr;                                                                                         \
459                                                                                                                                  \
460    const uint8_t * restrict src0_row = (const uint8_t *) src0->data;                                                             \
461                                                                                                                                  \
462    const uint8_t * restrict src1_col = (const uint8_t *) src1_data;                                                              \
463    float * restrict dst_col          = (float *) dst->data;                                                                      \
464                                                                                                                                  \
465    const uint32_t tile_size = TILE_SIZE;                                                                                         \
466    const uint32_t aligned_tile_size = hex_align_up(tile_size, 128);                                                              \
467                                                                                                                                  \
468    uint32_t n_k_tiles_w = ne00 / 32;                                                                                             \
469    uint32_t n_k_tiles_a = ne10 / 32;                                                                                             \
470    uint32_t tile_row_stride = n_k_tiles_w * tile_size;                                                                           \
471    uint32_t tile_row_transfer_size_aligned = n_k_tiles_a * aligned_tile_size;                                                    \
472                                                                                                                                  \
473    uint32_t ct_start = src0_start_row / 32;                                                                                      \
474    uint32_t ct_end   = (src0_end_row + 31) / 32;                                                                                 \
475                                                                                                                                  \
476    uint32_t push_ct = ct_start;                                                                                                  \
477    if (src0_start_row < src0_end_row) {                                                                                          \
478        if (src2) {                                                                                                               \
479            float * vtcm_src2_ptr = (float *) mmctx->vtcm_src2 + src0_start_row;                                                  \
480            const float * src2_ptr = (const float *) src2->data + src0_start_row;                                                 \
481            int slice_size = (int)MIN(src0_end_row, ne0) - (int)src0_start_row;                                                   \
482            if (slice_size > 0) {                                                                                                 \
483                dma_queue_push(dma_queue, dma_make_ptr(vtcm_src2_ptr, src2_ptr),                                                  \
484                               slice_size * sizeof(float), slice_size * sizeof(float), slice_size * sizeof(float), 1);            \
485                dma_queue_pop_nowait(dma_queue);                                                                                  \
486            }                                                                                                                     \
487        }                                                                                                                         \
488        for (uint32_t d = 0; d < n_prefetch && push_ct < ct_end; d++, push_ct++) {                                                \
489            dma_queue_push(dma_queue, dma_make_ptr(vtcm_src0_ptr + d * tile_row_transfer_size_aligned,                            \
490                           src0_row + push_ct * tile_row_stride), aligned_tile_size, tile_size, tile_size, n_k_tiles_a);          \
491        }                                                                                                                         \
492    }                                                                                                                             \
493                                                                                                                                  \
494    hvx_mm_run_quant_task(mmctx, ith);                                                                                            \
495                                                                                                                                  \
496    if (src0_start_row >= src0_end_row) {                                                                                         \
497        return;                                                                                                                   \
498    }                                                                                                                             \
499                                                                                                                                  \
500    for (uint32_t ct = ct_start; ct < ct_end; ct++) {                                                                             \
501        const uint8_t * w_tile = dma_queue_pop(dma_queue).dst;                                                                    \
502                                                                                                                                  \
503        float * dst_ptr = &tmp[ct * 32 - src0_start_row];                                                                         \
504        int valid_rows = (int)ne0 - (int)(ct * 32);                                                                               \
505        valid_rows = MIN(32, MAX(0, valid_rows));                                                                                 \
506                                                                                                                                  \
507        htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, ct);                                                                    \
508        DOT_2X1(ne10, dst_ptr, w_tile, src1_col, valid_rows, NULL);                                                               \
509        htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, ct);                                                                     \
510                                                                                                                                  \
511        if (push_ct < ct_end) {                                                                                                   \
512            dma_queue_push(dma_queue, dma_make_ptr((uint8_t *)w_tile, src0_row + push_ct * tile_row_stride),                      \
513                           aligned_tile_size, tile_size, tile_size, n_k_tiles_a);                                                 \
514            push_ct++;                                                                                                            \
515        }                                                                                                                         \
516    }                                                                                                                             \
517                                                                                                                                  \
518    int copy_cnt = (int)MIN(src0_end_row, ne0) - (int)src0_start_row;                                                             \
519    if (copy_cnt > 0) {                                                                                                           \
520        htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, ct_end);                                                                \
521        if (src2) {                                                                                                               \
522            hvx_add_f32_uaa((uint8_t *) &dst_col[src0_start_row],                                                                 \
523                            (const uint8_t *) tmp,                                                                                \
524                            (const uint8_t *) ((const float *) mmctx->vtcm_src2 + src0_start_row),                                \
525                            copy_cnt);                                                                                            \
526        } else {                                                                                                                  \
527            hvx_copy_f32_ua((uint8_t *) &dst_col[src0_start_row], (uint8_t *) tmp, copy_cnt);                                     \
528        }                                                                                                                         \
529        htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, ct_end);                                                                 \
530    }                                                                                                                             \
531}
532
533#define MATMUL_NX_2D_REPACKED_IMPL(SUFFIX, TILE_SIZE, DOT_2X2, DOT_2X1)                                                           \
534static void hvx_mm_nx_2d_repacked_##SUFFIX(unsigned int nth, unsigned int ith, void * data) {                                     \
535    struct htp_mm_context * mmctx = data;                                                                                         \
536    struct htp_ops_context * octx = mmctx->octx;                                                                                  \
537    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;                      \
538    const uint32_t n_weights = kparams->n_weights;                                                                                \
539                                                                                                                                  \
540    const struct htp_tensor * restrict act = octx->src[n_weights]; /* x */                                                        \
541    const uint32_t ne10 = act->ne[0];                                                                                             \
542    const uint32_t src1_nrows = act->ne[1] * act->ne[2] * act->ne[3];                                                             \
543    const size_t src1_stride = mmctx->vtcm_src1_stride;                                                                           \
544                                                                                                                                  \
545    uint8_t * restrict vtcm_weight_ptr = mmctx->vtcm_src0 + mmctx->vtcm_src0_size_per_thread * ith;                               \
546    uint8_t * restrict src1_data       = mmctx->vtcm_src1;                                                                        \
547                                                                                                                                  \
548    struct htp_thread_trace * tr = &octx->ctx->trace[ith];                                                                        \
549    const uint32_t n_prefetch = kparams->n_prefetch;                                                                              \
550    assert(n_prefetch >= 2 && n_prefetch <= HTP_MM_MAX_PREFETCH && (n_prefetch & (n_prefetch - 1)) == 0);                         \
551                                                                                                                                  \
552    const uint32_t tile_size = TILE_SIZE;                                                                                         \
553    const uint32_t aligned_tile_size = hex_align_up(tile_size, 128);                                                              \
554    uint32_t n_k_tiles_a = ne10 / 32;                                                                                             \
555    uint32_t tile_row_transfer_size_aligned = n_k_tiles_a * aligned_tile_size;                                                    \
556                                                                                                                                  \
557    dma_queue * dma_queue = octx->ctx->dma[ith];                                                                                  \
558                                                                                                                                  \
559    hvx_mm_run_quant_task(mmctx, ith);                                                                                            \
560                                                                                                                                  \
561    for (uint32_t widx = 0; widx < n_weights; widx++) {                                                                           \
562        const struct htp_tensor * restrict src_w = octx->src[widx];                                                               \
563        const struct htp_tensor * restrict dst   = octx->dsts[widx];                                                              \
564        if (!src_w || !dst) continue;                                                                                             \
565                                                                                                                                  \
566        const uint32_t ne00 = src_w->ne[0];                                                                                       \
567        const uint32_t ne01 = src_w->ne[1];                                                                                       \
568        const size_t dst_row_size = dst->nb[1];                                                                                   \
569        const uint8_t * restrict src_w_row = (const uint8_t *) src_w->data;                                                       \
570                                                                                                                                  \
571        uint32_t n_k_tiles_w = ne00 / 32;                                                                                         \
572        uint32_t tile_row_stride = n_k_tiles_w * tile_size;                                                                       \
573                                                                                                                                  \
574        uint32_t src0_start_row = 0;                                                                                              \
575        uint32_t src0_end_row   = ne01;                                                                                           \
576        if (octx->ctx->mdev.count > 1) {                                                                                          \
577            const bool can_split = htp_tensor_can_row_partition(dst, sizeof(float));                                              \
578            const struct htp_tensor_mdev_range range = htp_tensor_mdev_partition(ne01, can_split ? 32 : 0,                        \
579                                                         octx->ctx->mdev.idx, octx->ctx->mdev.count, &octx->ctx->mdev.count_div); \
580            src0_start_row = range.start;                                                                                         \
581            src0_end_row   = range.start + range.count;                                                                           \
582        }                                                                                                                         \
583                                                                                                                                  \
584        const uint32_t nrows = src0_end_row - src0_start_row;                                                                     \
585        uint32_t src0_nrows_per_thread = fastdiv(nrows + nth - 1, &octx->n_threads_div);                                          \
586        src0_nrows_per_thread = hex_round_up(src0_nrows_per_thread, 32);                                                          \
587                                                                                                                                  \
588        const uint32_t start_row = src0_start_row + src0_nrows_per_thread * ith;                                                  \
589        const uint32_t end_row   = MIN(start_row + src0_nrows_per_thread, src0_end_row);                                          \
590        if (start_row >= end_row) continue;                                                                                       \
591                                                                                                                                  \
592        uint32_t ct_start = start_row / 32;                                                                                       \
593        uint32_t ct_end   = (end_row + 31) / 32;                                                                                  \
594                                                                                                                                  \
595        uint32_t push_ct = ct_start;                                                                                              \
596        for (uint32_t d = 0; d < n_prefetch && push_ct < ct_end; d++, push_ct++) {                                                \
597            dma_queue_push(dma_queue, dma_make_ptr(vtcm_weight_ptr + d * tile_row_transfer_size_aligned,                          \
598                           src_w_row + push_ct * tile_row_stride), aligned_tile_size, tile_size, tile_size, n_k_tiles_a);         \
599        }                                                                                                                         \
600                                                                                                                                  \
601        for (uint32_t ct = ct_start; ct < ct_end; ct++) {                                                                         \
602            const uint8_t * w_tile = dma_queue_pop(dma_queue).dst;                                                                \
603            int valid_rows = (int)ne01 - (int)(ct * 32);                                                                          \
604            valid_rows = MIN(32, MAX(0, valid_rows));                                                                             \
605                                                                                                                                  \
606            htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, ct);                                                                \
607            uint32_t ir1 = 0;                                                                                                     \
608            for (; ir1 + 1 < src1_nrows; ir1 += 2) {                                                                              \
609                const uint8_t * restrict src1_col0 = (const uint8_t *) (src1_data + (ir1+0) * src1_stride);                       \
610                const uint8_t * restrict src1_col1 = (const uint8_t *) (src1_data + (ir1+1) * src1_stride);                       \
611                                                                                                                                  \
612                float * restrict dst_row0 = (float *) (dst->data + ((ir1+0) * dst_row_size));                                     \
613                float * restrict dst_row1 = (float *) (dst->data + ((ir1+1) * dst_row_size));                                     \
614                float * dst_ptr0 = &dst_row0[ct * 32];                                                                            \
615                float * dst_ptr1 = &dst_row1[ct * 32];                                                                            \
616                                                                                                                                  \
617                DOT_2X2(ne10, dst_ptr0, dst_ptr1, w_tile, src1_col0, src1_col1, valid_rows, NULL, NULL);                          \
618            }                                                                                                                     \
619                                                                                                                                  \
620            for (; ir1 < src1_nrows; ++ir1) {                                                                                     \
621                const uint8_t * restrict src1_col = (const uint8_t *) (src1_data + ir1 * src1_stride);                            \
622                float * restrict dst_row = (float *) (dst->data + (ir1 * dst_row_size));                                          \
623                float * dst_ptr = &dst_row[ct * 32];                                                                              \
624                DOT_2X1(ne10, dst_ptr, w_tile, src1_col, valid_rows, NULL);                                                       \
625            }                                                                                                                     \
626            htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, ct);                                                                 \
627                                                                                                                                  \
628            if (push_ct < ct_end) {                                                                                               \
629                dma_queue_push(dma_queue, dma_make_ptr((uint8_t *)w_tile, src_w_row + push_ct * tile_row_stride),                 \
630                               aligned_tile_size, tile_size, tile_size, n_k_tiles_a);                                             \
631                push_ct++;                                                                                                        \
632            }                                                                                                                     \
633        }                                                                                                                         \
634    }                                                                                                                             \
635}
636
637MATMUL_2D_REPACKED_IMPL(q4_0,       576,  tiled_vec_dot_q4_0_32x2,  tiled_vec_dot_q4_0_32x1)
638MATMUL_2D_REPACKED_IMPL(q4_1,       640,  tiled_vec_dot_q4_1_32x2,  tiled_vec_dot_q4_1_32x1)
639MATMUL_2D_REPACKED_IMPL(q8_0,       1088, tiled_vec_dot_q8_0_32x2,  tiled_vec_dot_q8_0_32x1)
640MATMUL_2D_REPACKED_IMPL(iq4nl,      576,  tiled_vec_dot_iq4nl_32x2, tiled_vec_dot_iq4nl_32x1)
641MATMUL_2D_REPACKED_IMPL(mxfp4,      544,  tiled_vec_dot_mxfp4_32x2, tiled_vec_dot_mxfp4_32x1)
642
643MATMUL_2D_REPACKED_IMPL(q4_0_flat,  576,  flat_vec_dot_q4_0_32x2,   flat_vec_dot_q4_0_32x1)
644MATMUL_2D_REPACKED_IMPL(q4_1_flat,  640,  flat_vec_dot_q4_1_32x2,   flat_vec_dot_q4_1_32x1)
645MATMUL_2D_REPACKED_IMPL(q8_0_flat,  1088, flat_vec_dot_q8_0_32x2,   flat_vec_dot_q8_0_32x1)
646MATMUL_2D_REPACKED_IMPL(iq4nl_flat, 576,  flat_vec_dot_iq4nl_32x2,  flat_vec_dot_iq4nl_32x1)
647MATMUL_2D_REPACKED_IMPL(mxfp4_flat, 544,  flat_vec_dot_mxfp4_32x2,  flat_vec_dot_mxfp4_32x1)
648
649#define QUANTIZE_IMPL(name, log_name, kernel_fn, dst_row_size_expr)                                        \
650static void name(unsigned int nth, unsigned int ith, void * data) {                                        \
651    struct htp_mm_context * mmctx = data;                                                                  \
652    struct htp_ops_context * octx = mmctx->octx;                                                           \
653    const struct htp_tensor * src = mmctx->act;                                                            \
654    const uint32_t ne0 = src->ne[0];                                                                       \
655    const uint32_t ne1 = src->ne[1];                                                                       \
656    const uint32_t ne2 = src->ne[2];                                                                       \
657    const uint32_t ne3 = src->ne[3];                                                                       \
658    const uint32_t nrows = ne1 * ne2 * ne3;                                                                \
659    const uint32_t nrows_per_thread = mmctx->n_quant_rows_per_thread;                                      \
660                                                                                                           \
661    const uint32_t ir_first = nrows_per_thread * ith;                                                      \
662    if (ir_first >= nrows) {                                                                               \
663        return;                                                                                            \
664    }                                                                                                      \
665                                                                                                           \
666    struct htp_thread_trace * tr = &octx->ctx->trace[ith];                                                 \
667    htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_A_QUANT, ir_first);                                        \
668                                                                                                           \
669    uint8_t * restrict dst = mmctx->vtcm_src1;                                                             \
670    const uint32_t ir_last = MIN(ir_first + nrows_per_thread, nrows);                                      \
671    const size_t src_row_size = src->nb[1];                                                                \
672    const size_t dst_row_size = (dst_row_size_expr);                                                       \
673    const uint8_t * restrict src_data = (const uint8_t *) src->data + (src_row_size * ir_first);           \
674    uint8_t * restrict dst_data = (uint8_t *) dst + (dst_row_size * ir_first);                             \
675    uint8_t * restrict tmp_data = (uint8_t *) mmctx->vtcm_dst + (mmctx->vtcm_dst_size_per_thread * ith);   \
676    kernel_fn(src_data, dst_data, tmp_data, ne0, ir_last - ir_first, src_row_size, dst_row_size);          \
677                                                                                                           \
678    htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_A_QUANT, ir_first);                                         \
679}
680
681QUANTIZE_IMPL(quantize_f32_q8_0_tiled, "quantize-f32-q8_0_tiled", quantize_f32_q8_0_tiled_kernel, htp_mm_q8_0_tiled_row_size(ne0))
682QUANTIZE_IMPL(quantize_f32_q8_1_tiled, "quantize-f32-q8_1_tiled", quantize_f32_q8_1_tiled_kernel, htp_mm_q8_1_tiled_row_size(ne0))
683QUANTIZE_IMPL(quantize_f32_q8_0_flat,  "quantize-f32-q8_0_flat",  quantize_f32_q8_0_flat_kernel,  htp_mm_q8_0_flat_row_size(ne0))
684QUANTIZE_IMPL(quantize_f32_q8_1_flat,  "quantize-f32-q8_1_flat",  quantize_f32_q8_1_flat_kernel,  htp_mm_q8_1_flat_row_size(ne0))
685QUANTIZE_IMPL(quantize_f32_f32_flat,   "quantize-f32-f32",        quantize_f32_f32_flat_kernel,   mmctx->vtcm_src1_stride)
686QUANTIZE_IMPL(quantize_f32_f16_flat,   "quantize-f32-f16",        quantize_f32_f16_flat_kernel,   mmctx->vtcm_src1_stride)
687QUANTIZE_IMPL(quantize_f16_f16_flat,   "quantize-f16-f16",        quantize_f16_f16_flat_kernel,   mmctx->vtcm_src1_stride)
688
689static void quantize_f32_q8_0_tiled_block(unsigned int nth, unsigned int ith, void * data) {
690    struct htp_mm_context * mmctx = data;
691    struct htp_ops_context * octx = mmctx->octx;
692    struct htp_thread_trace * tr = &octx->ctx->trace[ith];
693    htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_A_QUANT, mmctx->quant_ib_first[ith]);
694
695    const struct htp_tensor * src = mmctx->act;
696
697    quantize_f32_q8_0_tiled_block_kernel(
698        (const float *) src->data,
699        mmctx->vtcm_src1,
700        (uint8_t *) mmctx->vtcm_dst + (mmctx->vtcm_dst_size_per_thread * ith),
701        src->ne[0],
702        mmctx->quant_ib_first[ith],
703        mmctx->quant_ib_last[ith],
704        src->nb[1],
705        htp_mm_q8_0_tiled_row_size(src->ne[0]),
706        mmctx->quant_r[ith],
707        mmctx->quant_c[ith]
708    );
709
710    htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_A_QUANT, mmctx->quant_ib_first[ith]);
711}
712
713static void quantize_f32_q8_1_tiled_block(unsigned int nth, unsigned int ith, void * data) {
714    struct htp_mm_context * mmctx = data;
715    struct htp_ops_context * octx = mmctx->octx;
716    struct htp_thread_trace * tr = &octx->ctx->trace[ith];
717    htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_A_QUANT, mmctx->quant_ib_first[ith]);
718
719    const struct htp_tensor * src = mmctx->act;
720
721    quantize_f32_q8_1_tiled_block_kernel(
722        (const float *) src->data,
723        mmctx->vtcm_src1,
724        (uint8_t *) mmctx->vtcm_dst + (mmctx->vtcm_dst_size_per_thread * ith),
725        src->ne[0],
726        mmctx->quant_ib_first[ith],
727        mmctx->quant_ib_last[ith],
728        src->nb[1],
729        htp_mm_q8_1_tiled_row_size(src->ne[0]),
730        mmctx->quant_r[ith],
731        mmctx->quant_c[ith]
732    );
733
734    htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_A_QUANT, mmctx->quant_ib_first[ith]);
735}
736
737MATVEC_2D_REPACKED_IMPL(q4_0,       576,  tiled_vec_dot_q4_0_32x1)
738MATVEC_2D_REPACKED_IMPL(q4_1,       640,  tiled_vec_dot_q4_1_32x1)
739MATVEC_2D_REPACKED_IMPL(q8_0,       1088, tiled_vec_dot_q8_0_32x1)
740MATVEC_2D_REPACKED_IMPL(iq4nl,      576,  tiled_vec_dot_iq4nl_32x1)
741MATVEC_2D_REPACKED_IMPL(mxfp4,      544,  tiled_vec_dot_mxfp4_32x1)
742
743MATVEC_2D_REPACKED_IMPL(q4_0_flat,  576,  flat_vec_dot_q4_0_32x1)
744MATVEC_2D_REPACKED_IMPL(q4_1_flat,  640,  flat_vec_dot_q4_1_32x1)
745MATVEC_2D_REPACKED_IMPL(q8_0_flat,  1088, flat_vec_dot_q8_0_32x1)
746MATVEC_2D_REPACKED_IMPL(iq4nl_flat, 576,  flat_vec_dot_iq4nl_32x1)
747MATVEC_2D_REPACKED_IMPL(mxfp4_flat, 544,  flat_vec_dot_mxfp4_32x1)
748
749
750MATMUL_NX_2D_REPACKED_IMPL(q4_0,       576,  tiled_vec_dot_q4_0_32x2,  tiled_vec_dot_q4_0_32x1)
751MATMUL_NX_2D_REPACKED_IMPL(q4_1,       640,  tiled_vec_dot_q4_1_32x2,  tiled_vec_dot_q4_1_32x1)
752MATMUL_NX_2D_REPACKED_IMPL(q8_0,       1088, tiled_vec_dot_q8_0_32x2,  tiled_vec_dot_q8_0_32x1)
753MATMUL_NX_2D_REPACKED_IMPL(iq4nl,      576,  tiled_vec_dot_iq4nl_32x2, tiled_vec_dot_iq4nl_32x1)
754MATMUL_NX_2D_REPACKED_IMPL(mxfp4,      544,  tiled_vec_dot_mxfp4_32x2, tiled_vec_dot_mxfp4_32x1)
755
756MATMUL_NX_2D_REPACKED_IMPL(q4_0_flat,  576,  flat_vec_dot_q4_0_32x2,   flat_vec_dot_q4_0_32x1)
757MATMUL_NX_2D_REPACKED_IMPL(q4_1_flat,  640,  flat_vec_dot_q4_1_32x2,   flat_vec_dot_q4_1_32x1)
758MATMUL_NX_2D_REPACKED_IMPL(q8_0_flat,  1088, flat_vec_dot_q8_0_32x2,   flat_vec_dot_q8_0_32x1)
759MATMUL_NX_2D_REPACKED_IMPL(iq4nl_flat, 576,  flat_vec_dot_iq4nl_32x2,  flat_vec_dot_iq4nl_32x1)
760MATMUL_NX_2D_REPACKED_IMPL(mxfp4_flat, 544,  flat_vec_dot_mxfp4_32x2,  flat_vec_dot_mxfp4_32x1)
761
762static void hvx_mm_2d(unsigned int nth, unsigned int ith, void * data) {
763    htp_matmul_preamble;
764
765    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;
766    const uint32_t n_prefetch = kparams->n_prefetch;
767    assert(n_prefetch >= 2 && n_prefetch <= HTP_MM_MAX_PREFETCH && (n_prefetch & (n_prefetch - 1)) == 0);
768    const uint32_t prefetch_mask = n_prefetch - 1;
769
770    const uint32_t src0_nrows = mmctx->src0_row_end - mmctx->src0_row_start;  // src0 rows
771    const uint32_t src1_nrows = ne11 * ne12 * ne13;                          // src1 rows
772
773    const uint32_t src0_start_row  = mmctx->src0_row_start + src0_nrows_per_thread * ith;
774    const uint32_t src0_end_row    = MIN(src0_start_row + src0_nrows_per_thread, mmctx->src0_row_end);
775    const uint32_t src0_end_row_x2 = src0_start_row + ((src0_end_row - src0_start_row) & ~1U);
776
777    struct htp_thread_trace * tr = &octx->ctx->trace[ith];
778
779    const size_t dst_row_size  = nb1;
780    const size_t src0_row_size = nb01;
781    const size_t src1_row_size = nb11;
782
783    const size_t src0_stride = mmctx->vtcm_src0_stride;
784    const size_t src1_stride = mmctx->vtcm_src1_stride;
785
786    // Per-thread VTCMs for all tensors
787    uint8_t * restrict vtcm_dst_ptr  = mmctx->vtcm_dst  + mmctx->vtcm_dst_size_per_thread  * ith;
788    uint8_t * restrict vtcm_src0_ptr = mmctx->vtcm_src0 + mmctx->vtcm_src0_size_per_thread * ith;
789    uint8_t * restrict src1_data     = mmctx->vtcm_src1;
790
791    const uint8_t * restrict src0_row = (const uint8_t *) src0->data;
792
793    // Prefill vtcm with src0 rows
794    if (src0_start_row < src0_end_row) {
795        for (uint32_t ir0 = src0_start_row; ir0 < src0_end_row_x2; ir0 += 2) {
796            const int is0 = (ir0 - src0_start_row);
797            if (is0 >= (int)n_prefetch) {
798                break;
799            }
800            dma_queue_push(dma_queue, dma_make_ptr(vtcm_src0_ptr + is0 * src0_stride, src0_row + ir0 * src0_row_size),
801                           src0_stride, src0_row_size, src0_row_size, 2);
802        }
803    }
804
805    hvx_mm_run_quant_task(mmctx, ith);
806
807    if (src0_start_row >= src0_end_row) {
808        return;
809    }
810
811    // Process src0 rows
812    for (uint32_t ir0 = src0_start_row; ir0 < src0_end_row_x2; ir0 += 2) {
813        const uint8_t * ss0 = dma_queue_pop(dma_queue).dst;
814
815        htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, ir0);
816        // Process src1 columns in pairs (2x2 tiling)
817        uint32_t ir1 = 0;
818        for (; ir1 + 1 < src1_nrows; ir1 += 2) {
819            const uint8_t * restrict src1_col0 = (const uint8_t *) (src1_data + (ir1+0) * src1_stride);
820            const uint8_t * restrict src1_col1 = (const uint8_t *) (src1_data + (ir1+1) * src1_stride);
821            float * restrict dst_row0 = (float *) (dst->data + ((ir1+0) * dst_row_size));
822            float * restrict dst_row1 = (float *) (dst->data + ((ir1+1) * dst_row_size));
823            mmctx->vec_dot_2x2(ne00, &dst_row0[ir0], &dst_row1[ir0], ss0, ss0 + src0_stride, src1_col0, src1_col1);
824        }
825
826        // Handle remaining src1 rows (fallback to 2x1)
827        for (; ir1 < src1_nrows; ++ir1) {
828            const uint8_t * restrict src1_col = (const uint8_t *) (src1_data + ir1 * src1_stride);
829            float * restrict dst_row          = (float *) (dst->data + (ir1 * dst_row_size));
830            mmctx->vec_dot_2x1(ne00, &dst_row[ir0], ss0, ss0 + src0_stride, src1_col);
831        }
832        htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, ir0);
833
834        // Prefetch next (n + vtcm_nrows) row
835        const int pr0 = (ir0 + n_prefetch);
836        const int is0 = (pr0 - src0_start_row) & prefetch_mask;
837        if (pr0 < src0_end_row_x2) {
838            dma_queue_push(dma_queue, dma_make_ptr(vtcm_src0_ptr + is0 * src0_stride, src0_row + pr0 * src0_row_size),
839                           src0_stride, src0_row_size, src0_row_size, 2);
840        }
841    }
842
843    // Process the last row (if any)
844    if (src0_end_row != src0_end_row_x2) {
845        uint32_t  ir0 = src0_end_row_x2;
846        const int is0 = (ir0 - src0_start_row) & prefetch_mask;
847        dma_queue_push(dma_queue, dma_make_ptr(vtcm_src0_ptr + is0 * src0_stride, src0_row + ir0 * src0_row_size),
848                       src0_stride, src0_row_size, src0_row_size, 1);
849        const uint8_t * ss0 = dma_queue_pop(dma_queue).dst;
850
851        htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, ir0);
852        #pragma unroll(2)
853        for (uint32_t ir1 = 0; ir1 < src1_nrows; ++ir1) {
854            const uint8_t * restrict src1_col = (const uint8_t *) (src1_data + ir1 * src1_stride);
855            float * restrict dst_row          = (float *) (dst->data + (ir1 * dst_row_size));
856            mmctx->vec_dot_1x1(ne00, &dst_row[ir0], ss0, src1_col);
857        }
858        htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, ir0);
859    }
860    if (src2) {
861        hvx_tensor_add_f32_grid(dst, src2, 0, src1_nrows, src0_start_row, src0_end_row, &kparams->div_ne12_ne1, &kparams->div_ne1);
862    }
863}
864
865static void hvx_mv_2d(unsigned int nth, unsigned int ith, void * data) {
866    htp_matmul_preamble;
867
868    const uint32_t src0_nrows = mmctx->src0_row_end - mmctx->src0_row_start;
869
870    const uint32_t src0_start_row  = mmctx->src0_row_start + src0_nrows_per_thread * ith;
871    const uint32_t src0_end_row    = MIN(src0_start_row + src0_nrows_per_thread, mmctx->src0_row_end);
872
873    struct htp_thread_trace * tr = &octx->ctx->trace[ith];
874
875    const size_t dst_row_size  = nb1;
876    const size_t src0_row_size = nb01;
877    const size_t src1_row_size = nb11;
878
879    const size_t src0_stride = mmctx->vtcm_src0_stride;
880    const size_t src1_stride = mmctx->vtcm_src1_stride;
881
882    // Per-thread VTCMs for all tensors
883    uint8_t * vtcm_dst_ptr  = mmctx->vtcm_dst  + mmctx->vtcm_dst_size_per_thread  * ith;
884    uint8_t * vtcm_src0_ptr = mmctx->vtcm_src0 + mmctx->vtcm_src0_size_per_thread * ith;
885    uint8_t * src1_data     = mmctx->vtcm_src1;
886
887    float * tmp = (float *) vtcm_dst_ptr;
888
889    const uint8_t * restrict src0_row = (const uint8_t *) src0->data;
890    const uint8_t * restrict src1_col = (const uint8_t *) src1_data;
891    float * restrict dst_col          = (float *) dst->data;
892
893    const uint32_t src0_end_row_x2 = src0_start_row + ((src0_end_row - src0_start_row) & ~1U);
894
895    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;
896    const uint32_t n_prefetch = kparams->n_prefetch;
897    assert(n_prefetch >= 2 && n_prefetch <= HTP_MM_MAX_PREFETCH && (n_prefetch & (n_prefetch - 1)) == 0);
898    const uint32_t prefetch_mask = n_prefetch - 1;
899
900    // Prefill vtcm with 2x src0 rows
901    if (src0_start_row < src0_end_row) {
902        if (src2) {
903            float * vtcm_src2_ptr = (float *) mmctx->vtcm_src2 + src0_start_row;
904            const float * src2_ptr = (const float *) src2->data + src0_start_row;
905            int slice_size = (int)src0_end_row - (int)src0_start_row;
906            if (slice_size > 0) {
907                dma_queue_push(dma_queue, dma_make_ptr(vtcm_src2_ptr, src2_ptr),
908                               slice_size * sizeof(float), slice_size * sizeof(float), slice_size * sizeof(float), 1);
909                dma_queue_pop_nowait(dma_queue);
910            }
911        }
912        for (uint32_t ir0 = src0_start_row; ir0 < src0_end_row_x2; ir0 += 2) {
913            const uint32_t is0 = (ir0 - src0_start_row);
914            if (is0 >= n_prefetch) {
915                break;
916            }
917            dma_queue_push(dma_queue, dma_make_ptr(vtcm_src0_ptr + is0 * src0_stride, src0_row + ir0 * src0_row_size),
918                           src0_stride, src0_row_size, src0_row_size, 2);
919        }
920    }
921
922    hvx_mm_run_quant_task(mmctx, ith);
923
924    if (src0_start_row >= src0_end_row) {
925        return;
926    }
927
928    // Process src0 rows
929    for (uint32_t ir0 = src0_start_row; ir0 < src0_end_row_x2; ir0 += 2) {
930        const uint8_t * ss0 = dma_queue_pop(dma_queue).dst;
931        htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, ir0);
932        mmctx->vec_dot_2x1(ne00, &tmp[ir0 - src0_start_row], ss0, ss0 + src0_stride, src1_col);
933        htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, ir0);
934
935        // Prefetch next (n + vtcm_nrows) row
936        const uint32_t pr0 = (ir0 + n_prefetch);
937        const uint32_t is0 = (pr0 - src0_start_row) & prefetch_mask;
938        if (pr0 < src0_end_row_x2) {
939            dma_queue_push(dma_queue, dma_make_ptr(vtcm_src0_ptr + is0 * src0_stride, src0_row + pr0 * src0_row_size),
940                           src0_stride, src0_row_size, src0_row_size, 2);
941        }
942    }
943
944    // Process the last row (if any)
945    if (src0_end_row != src0_end_row_x2) {
946        const uint32_t ir0 = src0_end_row_x2;
947        const uint32_t is0 = (ir0 - src0_start_row) & prefetch_mask;
948        dma_queue_push(dma_queue, dma_make_ptr(vtcm_src0_ptr + is0 * src0_stride, src0_row + ir0 * src0_row_size),
949                       src0_stride, src0_row_size, src0_row_size, 1);
950        const uint8_t * ss0 = dma_queue_pop(dma_queue).dst;
951        htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, ir0);
952        mmctx->vec_dot_1x1(ne00, &tmp[ir0 - src0_start_row], ss0, src1_col);
953        htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, ir0);
954    }
955
956    int copy_cnt = src0_end_row - src0_start_row;
957    if (copy_cnt > 0) {
958        htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, src0_end_row);
959        if (src2) {
960            hvx_add_f32_uaa((uint8_t *) &dst_col[src0_start_row],
961                            (const uint8_t *) tmp,
962                            (const uint8_t *) ((const float *) mmctx->vtcm_src2 + src0_start_row),
963                            copy_cnt);
964        } else {
965            hvx_copy_f32_ua((uint8_t *) &dst_col[src0_start_row], (uint8_t *) tmp, copy_cnt);
966        }
967        htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, src0_end_row);
968    }
969}
970
971#define MMID_MATRIX_ROW(row_id, i1) matrix_rows[(row_id) * mmctx->mapping_stride + (i1)]
972
973static void hvx_mm_id(unsigned int nth, unsigned int ith, void * data) {
974    htp_matmul_preamble;
975
976    const struct htp_tensor * restrict ids = octx->src[2];
977
978    const uint32_t src0_nrows      = mmctx->src0_row_end - mmctx->src0_row_start;  // src0 rows per expert
979    const uint32_t src1_nrows      = ne11;
980    const uint32_t src0_start_row  = mmctx->src0_row_start + src0_nrows_per_thread * ith;
981    const uint32_t src0_end_row    = MIN(src0_start_row + src0_nrows_per_thread, mmctx->src0_row_end);
982
983    hvx_mm_run_quant_task(mmctx, ith);
984
985    if (src0_start_row >= src0_end_row) {
986        return;
987    }
988
989    struct htp_thread_trace * tr = &octx->ctx->trace[ith];
990
991    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;
992    const uint32_t n_prefetch = kparams->n_prefetch;
993    assert(n_prefetch >= 2 && n_prefetch <= HTP_MM_MAX_PREFETCH && (n_prefetch & (n_prefetch - 1)) == 0);
994
995    const uint32_t n_ids = ids->ne[0];  // n_expert_used
996    const uint32_t n_as  = ne02;        // n_expert
997
998    const uint32_t *                matrix_row_counts = mmctx->matrix_row_counts;
999    const struct mmid_row_mapping * matrix_rows       = mmctx->matrix_rows;
1000
1001    const size_t dst_row_size  = nb1;
1002    const size_t src1_row_size = htp_mm_q8_0_tiled_row_size(ne10);
1003
1004    const size_t src1_stride = mmctx->vtcm_src1_stride;
1005
1006    // Per-thread VTCMs for all tensors
1007    uint8_t * restrict vtcm_src0_ptr = mmctx->vtcm_src0 + mmctx->vtcm_src0_size_per_thread * ith;
1008    uint8_t * restrict src1_data = mmctx->vtcm_src1;
1009
1010    for (uint32_t cur_a = 0; cur_a < n_as; ++cur_a) {
1011        const int32_t cne1 = matrix_row_counts[cur_a];
1012        if (cne1 == 0) {
1013            continue;
1014        }
1015
1016        const uint8_t * src0_row = (const uint8_t *) src0->data + cur_a * nb02;
1017
1018        const uint32_t tile_size = htp_mm_get_weight_tile_size(src0->type);
1019        const uint32_t aligned_tile_size = htp_mm_get_weight_aligned_tile_size(src0->type);
1020        const uint32_t n_k_tiles_w = ne00 / 32;
1021        const uint32_t n_k_tiles_a = ne10 / 32;
1022        const uint32_t tile_row_stride = n_k_tiles_w * tile_size;
1023        const uint32_t tile_row_transfer_size_aligned = n_k_tiles_a * aligned_tile_size;
1024
1025        const uint32_t ct_start = src0_start_row / 32;
1026        const uint32_t ct_end   = (src0_end_row + 31) / 32;
1027
1028        uint32_t push_ct = ct_start;
1029        for (uint32_t d = 0; d < n_prefetch && push_ct < ct_end; d++, push_ct++) {
1030            dma_queue_push(dma_queue, dma_make_ptr(vtcm_src0_ptr + d * tile_row_transfer_size_aligned, src0_row + push_ct * tile_row_stride),
1031                           aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
1032        }
1033
1034        for (uint32_t ct = ct_start; ct < ct_end; ct++) {
1035            const uint8_t * w_tile = dma_queue_pop(dma_queue).dst;
1036
1037            int valid_rows = (int)ne01 - (int)(ct * 32);
1038            valid_rows = MIN(32, MAX(0, valid_rows));
1039
1040            htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, ct);
1041            for (uint32_t cid = 0; cid < cne1; ++cid) {
1042                struct mmid_row_mapping row_mapping = MMID_MATRIX_ROW(cur_a, cid);
1043                const int               rm1         = row_mapping.i1;  // expert idx
1044                const int               rm2         = row_mapping.i2;  // token idx
1045
1046                const uint32_t ir1 = fastmodulo(rm1, ne11, &mmctx->mm_div_ne11);        // src1 row idx
1047                const uint8_t * restrict src1_col = (const uint8_t *) (src1_data + (ir1 + rm2 * ne11 + 0) * src1_stride);
1048                float * restrict dst_row = (float *) (dst->data + (rm1 * nb1 + rm2 * nb2 + 0));
1049
1050                mmctx->vec_dot_32x1(ne10, &dst_row[ct * 32], w_tile, src1_col, valid_rows, NULL);
1051            }
1052            htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, ct);
1053
1054            if (push_ct < ct_end) {
1055                dma_queue_push(dma_queue, dma_make_ptr((uint8_t *)w_tile, src0_row + push_ct * tile_row_stride),
1056                               aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
1057                push_ct++;
1058            }
1059        }
1060    }
1061}
1062
1063static void hvx_mv_id(unsigned int nth, unsigned int ith, void * data) {
1064    htp_matmul_preamble;
1065
1066    const struct htp_tensor * restrict ids = octx->src[2];
1067
1068    const uint32_t src0_nrows      = mmctx->src0_row_end - mmctx->src0_row_start;  // src0 rows per expert
1069    const uint32_t src0_start_row  = mmctx->src0_row_start + src0_nrows_per_thread * ith;
1070    const uint32_t src0_end_row    = MIN(src0_start_row + src0_nrows_per_thread, mmctx->src0_row_end);
1071
1072    hvx_mm_run_quant_task(mmctx, ith);
1073
1074    if (src0_start_row >= src0_end_row) {
1075        return;
1076    }
1077
1078    struct htp_thread_trace * tr = &octx->ctx->trace[ith];
1079
1080    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;
1081    const uint32_t n_prefetch = kparams->n_prefetch;
1082    assert(n_prefetch >= 2 && n_prefetch <= HTP_MM_MAX_PREFETCH && (n_prefetch & (n_prefetch - 1)) == 0);
1083
1084    assert(ne13 % ne03 == 0);
1085
1086    const size_t dst_row_size  = nb1;
1087    const size_t src1_row_size = htp_mm_q8_0_tiled_row_size(ne10);
1088
1089    const uint32_t n_aids = src2->ne[0];  // num activated experts
1090    const uint32_t n_ids  = ne02;         // num experts
1091
1092    // Per-thread VTCMs for all tensors
1093    uint8_t * restrict vtcm_src0_ptr = mmctx->vtcm_src0 + mmctx->vtcm_src0_size_per_thread * ith;
1094    uint8_t * restrict src1_data = mmctx->vtcm_src1;
1095
1096    for (uint32_t ie1 = 0; ie1 < n_aids; ++ie1) {  // for each expert
1097        const int32_t eid = *(const int32_t *) ((const uint8_t *) src2->data + ie1 * src2->nb[0]);
1098        if (eid < 0) {
1099            continue;
1100        }
1101        assert(eid < (int32_t) n_ids);
1102
1103        const uint8_t * restrict src0_row = (const uint8_t *) src0->data + eid * nb02;
1104        const uint8_t * restrict src1_col = (const uint8_t *) src1_data;
1105        float * restrict dst_row          = (float *) (dst->data + ie1 * nb1);
1106
1107        const uint32_t tile_size = htp_mm_get_weight_tile_size(src0->type);
1108        const uint32_t aligned_tile_size = htp_mm_get_weight_aligned_tile_size(src0->type);
1109        const uint32_t n_k_tiles_w = ne00 / 32;
1110        const uint32_t n_k_tiles_a = ne10 / 32;
1111        const uint32_t tile_row_stride = n_k_tiles_w * tile_size;
1112        const uint32_t tile_row_transfer_size_aligned = n_k_tiles_a * aligned_tile_size;
1113
1114        const uint32_t ct_start = src0_start_row / 32;
1115        const uint32_t ct_end   = (src0_end_row + 31) / 32;
1116
1117        uint32_t push_ct = ct_start;
1118        for (uint32_t d = 0; d < n_prefetch && push_ct < ct_end; d++, push_ct++) {
1119            dma_queue_push(dma_queue, dma_make_ptr(vtcm_src0_ptr + d * tile_row_transfer_size_aligned, src0_row + push_ct * tile_row_stride),
1120                           aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
1121        }
1122
1123        for (uint32_t ct = ct_start; ct < ct_end; ct++) {
1124            const uint8_t * w_tile = dma_queue_pop(dma_queue).dst;
1125
1126            int valid_rows = (int)ne01 - (int)(ct * 32);
1127            valid_rows = MIN(32, MAX(0, valid_rows));
1128
1129            htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, ct);
1130            mmctx->vec_dot_32x1(ne10, &dst_row[ct * 32], w_tile, src1_col, valid_rows, NULL);
1131            htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, ct);
1132
1133            if (push_ct < ct_end) {
1134                dma_queue_push(dma_queue, dma_make_ptr((uint8_t *)w_tile, src0_row + push_ct * tile_row_stride),
1135                               aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
1136                push_ct++;
1137            }
1138        }
1139    }
1140}
1141
1142static void hvx_mv_id_nx(unsigned int nth, unsigned int ith, void * data) {
1143    struct htp_mm_context * mmctx = (struct htp_mm_context *) data;
1144    struct htp_ops_context * octx = mmctx->octx;
1145    dma_queue * dma_queue         = octx->ctx->dma[ith];
1146    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;
1147    const uint32_t n_weights      = kparams->n_weights;
1148    const struct htp_tensor * restrict src0 = octx->src[0];
1149    const struct htp_tensor * restrict act  = octx->src[n_weights];
1150    const struct htp_tensor * restrict ids  = octx->src[n_weights + 1];
1151
1152    hvx_mm_run_quant_task(mmctx, ith);
1153
1154    struct htp_thread_trace * tr = &octx->ctx->trace[ith];
1155
1156    const uint32_t n_prefetch = kparams->n_prefetch;
1157    assert(n_prefetch >= 2 && n_prefetch <= HTP_MM_MAX_PREFETCH && (n_prefetch & (n_prefetch - 1)) == 0);
1158
1159    const uint32_t n_aids = ids->ne[0];
1160    const uint32_t n_ids  = src0->ne[2];
1161
1162    uint8_t * restrict vtcm_src0_ptr = mmctx->vtcm_src0 + mmctx->vtcm_src0_size_per_thread * ith;
1163    uint8_t * restrict src1_data = mmctx->vtcm_src1;
1164
1165    for (uint32_t ie1 = 0; ie1 < n_aids; ++ie1) {
1166        const int32_t eid = *(const int32_t *) ((const uint8_t *) ids->data + ie1 * ids->nb[0]);
1167        if (eid < 0) continue;
1168        assert(eid < (int32_t) n_ids);
1169
1170        for (uint32_t p = 0; p < n_weights; ++p) {
1171            const struct htp_tensor * restrict src_w = octx->src[p];
1172            const struct htp_tensor * restrict dst   = octx->dsts[p];
1173            if (!src_w || !dst) continue;
1174
1175            const uint32_t ne01 = src_w->ne[1];
1176            uint32_t start_row = 0;
1177            uint32_t end_row   = ne01;
1178            if (octx->ctx->mdev.count > 1) {
1179                const bool can_split = htp_tensor_can_row_partition(dst, sizeof(float));
1180                const struct htp_tensor_mdev_range range = htp_tensor_mdev_partition(ne01, can_split ? 32 : 0, octx->ctx->mdev.idx, octx->ctx->mdev.count, &octx->ctx->mdev.count_div);
1181                start_row = range.start;
1182                end_row   = range.start + range.count;
1183            }
1184
1185            const uint32_t nrows = end_row - start_row;
1186            uint32_t src0_nrows_per_thread = fastdiv(nrows + nth - 1, &octx->n_threads_div);
1187            src0_nrows_per_thread = hex_round_up(src0_nrows_per_thread, 32);
1188
1189            const uint32_t src0_start_row = start_row + src0_nrows_per_thread * ith;
1190            const uint32_t src0_end_row   = MIN(src0_start_row + src0_nrows_per_thread, end_row);
1191            if (src0_start_row >= src0_end_row) continue;
1192
1193            const uint8_t * restrict src0_row = (const uint8_t *) src_w->data + eid * src_w->nb[2];
1194            const uint8_t * restrict src1_col = (const uint8_t *) src1_data;
1195            float * restrict dst_row = (float *) (dst->data + ie1 * dst->nb[1]);
1196
1197            const uint32_t tile_size = htp_mm_get_weight_tile_size(src_w->type);
1198            const uint32_t aligned_tile_size = htp_mm_get_weight_aligned_tile_size(src_w->type);
1199            const uint32_t n_k_tiles_w = src_w->ne[0] / 32;
1200            const uint32_t n_k_tiles_a = act->ne[0] / 32;
1201            const uint32_t tile_row_stride = n_k_tiles_w * tile_size;
1202            const uint32_t tile_row_transfer_size_aligned = n_k_tiles_a * aligned_tile_size;
1203
1204            const uint32_t ct_start = src0_start_row / 32;
1205            const uint32_t ct_end   = (src0_end_row + 31) / 32;
1206
1207            uint32_t push_ct = ct_start;
1208            for (uint32_t d = 0; d < n_prefetch && push_ct < ct_end; d++, push_ct++) {
1209                dma_queue_push(dma_queue, dma_make_ptr(vtcm_src0_ptr + d * tile_row_transfer_size_aligned, src0_row + push_ct * tile_row_stride),
1210                               aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
1211            }
1212
1213            for (uint32_t ct = ct_start; ct < ct_end; ct++) {
1214                const uint8_t * w_tile = dma_queue_pop(dma_queue).dst;
1215
1216                int valid_rows = (int)src_w->ne[1] - (int)(ct * 32);
1217                valid_rows = MIN(32, MAX(0, valid_rows));
1218
1219                htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, ct);
1220                mmctx->vec_dot_32x1(act->ne[0], &dst_row[ct * 32], w_tile, src1_col, valid_rows, NULL);
1221                htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, ct);
1222
1223                if (push_ct < ct_end) {
1224                    dma_queue_push(dma_queue, dma_make_ptr((uint8_t *)w_tile, src0_row + push_ct * tile_row_stride),
1225                                   aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
1226                    push_ct++;
1227                }
1228            }
1229        }
1230    }
1231}
1232
1233static void hvx_mm_id_nx(unsigned int nth, unsigned int ith, void * data) {
1234    struct htp_mm_context * mmctx = (struct htp_mm_context *) data;
1235    struct htp_ops_context * octx = mmctx->octx;
1236    dma_queue * dma_queue         = octx->ctx->dma[ith];
1237    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;
1238    const uint32_t n_weights      = kparams->n_weights;
1239    const struct htp_tensor * restrict src0 = octx->src[0];
1240    const struct htp_tensor * restrict act  = octx->src[n_weights];
1241    const struct htp_tensor * restrict ids  = octx->src[n_weights + 1];
1242
1243    hvx_mm_run_quant_task(mmctx, ith);
1244
1245    struct htp_thread_trace * tr = &octx->ctx->trace[ith];
1246
1247    const uint32_t n_prefetch = kparams->n_prefetch;
1248    assert(n_prefetch >= 2 && n_prefetch <= HTP_MM_MAX_PREFETCH && (n_prefetch & (n_prefetch - 1)) == 0);
1249
1250    const uint32_t n_as = src0->ne[2];
1251
1252    const uint32_t * matrix_row_counts = mmctx->matrix_row_counts;
1253    const struct mmid_row_mapping * matrix_rows = mmctx->matrix_rows;
1254
1255    const size_t src1_stride = mmctx->vtcm_src1_stride;
1256
1257    uint8_t * restrict vtcm_src0_ptr = mmctx->vtcm_src0 + mmctx->vtcm_src0_size_per_thread * ith;
1258    uint8_t * restrict src1_data = mmctx->vtcm_src1;
1259
1260    for (uint32_t cur_a = 0; cur_a < n_as; ++cur_a) {
1261        const int32_t cne1 = matrix_row_counts[cur_a];
1262        if (cne1 == 0) continue;
1263
1264        for (uint32_t p = 0; p < n_weights; ++p) {
1265            const struct htp_tensor * restrict src_w = octx->src[p];
1266            const struct htp_tensor * restrict dst   = octx->dsts[p];
1267            if (!src_w || !dst) continue;
1268
1269            const uint32_t ne01 = src_w->ne[1];
1270            uint32_t start_row = 0;
1271            uint32_t end_row   = ne01;
1272            if (octx->ctx->mdev.count > 1) {
1273                const bool can_split = htp_tensor_can_row_partition(dst, sizeof(float));
1274                const struct htp_tensor_mdev_range range = htp_tensor_mdev_partition(ne01, can_split ? 32 : 0, octx->ctx->mdev.idx, octx->ctx->mdev.count, &octx->ctx->mdev.count_div);
1275                start_row = range.start;
1276                end_row   = range.start + range.count;
1277            }
1278
1279            const uint32_t nrows = end_row - start_row;
1280            uint32_t src0_nrows_per_thread = fastdiv(nrows + nth - 1, &octx->n_threads_div);
1281            src0_nrows_per_thread = hex_round_up(src0_nrows_per_thread, 32);
1282
1283            const uint32_t src0_start_row = start_row + src0_nrows_per_thread * ith;
1284            const uint32_t src0_end_row   = MIN(src0_start_row + src0_nrows_per_thread, end_row);
1285            if (src0_start_row >= src0_end_row) continue;
1286
1287            const uint8_t * src0_row = (const uint8_t *) src_w->data + cur_a * src_w->nb[2];
1288
1289            const uint32_t tile_size = htp_mm_get_weight_tile_size(src_w->type);
1290            const uint32_t aligned_tile_size = htp_mm_get_weight_aligned_tile_size(src_w->type);
1291            const uint32_t n_k_tiles_w = src_w->ne[0] / 32;
1292            const uint32_t n_k_tiles_a = act->ne[0] / 32;
1293            const uint32_t tile_row_stride = n_k_tiles_w * tile_size;
1294            const uint32_t tile_row_transfer_size_aligned = n_k_tiles_a * aligned_tile_size;
1295
1296            const uint32_t ct_start = src0_start_row / 32;
1297            const uint32_t ct_end   = (src0_end_row + 31) / 32;
1298
1299            uint32_t push_ct = ct_start;
1300            for (uint32_t d = 0; d < n_prefetch && push_ct < ct_end; d++, push_ct++) {
1301                dma_queue_push(dma_queue, dma_make_ptr(vtcm_src0_ptr + d * tile_row_transfer_size_aligned, src0_row + push_ct * tile_row_stride),
1302                               aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
1303            }
1304
1305            for (uint32_t ct = ct_start; ct < ct_end; ct++) {
1306                const uint8_t * w_tile = dma_queue_pop(dma_queue).dst;
1307
1308                int valid_rows = (int)src_w->ne[1] - (int)(ct * 32);
1309                valid_rows = MIN(32, MAX(0, valid_rows));
1310
1311                htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, ct);
1312                for (uint32_t cid = 0; cid < (uint32_t) cne1; ++cid) {
1313                    struct mmid_row_mapping row_mapping = MMID_MATRIX_ROW(cur_a, cid);
1314                    const int rm1 = row_mapping.i1;
1315                    const int rm2 = row_mapping.i2;
1316
1317                    const uint32_t ir1 = fastmodulo(rm1, act->ne[1], &mmctx->mm_div_ne11);
1318                    const uint8_t * restrict src1_col = (const uint8_t *) (src1_data + (ir1 + rm2 * act->ne[1]) * src1_stride);
1319                    float * restrict dst_row = (float *) (dst->data + (rm1 * dst->nb[1] + rm2 * dst->nb[2]));
1320
1321                    mmctx->vec_dot_32x1(act->ne[0], &dst_row[ct * 32], w_tile, src1_col, valid_rows, NULL);
1322                }
1323                htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, ct);
1324
1325                if (push_ct < ct_end) {
1326                    dma_queue_push(dma_queue, dma_make_ptr((uint8_t *)w_tile, src0_row + push_ct * tile_row_stride),
1327                                   aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
1328                    push_ct++;
1329                }
1330            }
1331        }
1332    }
1333}
1334
1335static int hvx_mm_init_vec_dot(struct htp_mm_context * mmctx, enum htp_data_type type) {
1336    switch (type) {
1337        case HTP_TYPE_Q4_0:
1338            mmctx->type         = "q4_0_tiled-f32";
1339            mmctx->vec_dot_32x1 = tiled_vec_dot_q4_0_32x1;
1340            return 0;
1341        case HTP_TYPE_Q4_1:
1342            mmctx->type         = "q4_1_tiled-f32";
1343            mmctx->vec_dot_32x1 = tiled_vec_dot_q4_1_32x1;
1344            return 0;
1345        case HTP_TYPE_Q8_0:
1346            mmctx->type         = "q8_0_tiled-f32";
1347            mmctx->vec_dot_32x1 = tiled_vec_dot_q8_0_32x1;
1348            return 0;
1349        case HTP_TYPE_IQ4_NL:
1350            mmctx->type         = "iq4nl_tiled-f32";
1351            mmctx->vec_dot_32x1 = tiled_vec_dot_iq4nl_32x1;
1352            return 0;
1353        case HTP_TYPE_MXFP4:
1354            mmctx->type         = "mxfp4_tiled-f32";
1355            mmctx->vec_dot_32x1 = tiled_vec_dot_mxfp4_32x1;
1356            return 0;
1357        default:
1358            return -1;
1359    }
1360}
1361
1362static int hvx_mm_matmul(struct htp_ops_context * octx) {
1363    htp_matmul_tensors_preamble;
1364
1365    struct htp_thread_trace * tr = &octx->ctx->trace[0];
1366    htp_trace_event_start(tr, HTP_TRACE_EVT_INIT, 0);
1367
1368    struct htp_mm_context mmctx_struct = {0};
1369    struct htp_mm_context * mmctx = &mmctx_struct;
1370    mmctx->octx = octx;
1371    mmctx->act = src1;
1372
1373    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;
1374
1375    const uint32_t src0_nrows = ne01;
1376    const uint32_t src1_nrows = ne11 * ne12 * ne13;
1377
1378    uint32_t src0_row_start = 0;
1379    uint32_t src0_row_end   = src0_nrows;
1380
1381    if (octx->ctx->mdev.count > 1) {
1382        const bool can_split = htp_tensor_can_row_partition(dst, sizeof(float));
1383        const struct htp_tensor_mdev_range range = htp_tensor_mdev_partition(src0_nrows, can_split ? 32 : 0, octx->ctx->mdev.idx, octx->ctx->mdev.count, &octx->ctx->mdev.count_div);
1384        src0_row_start = range.start;
1385        src0_row_end   = range.start + range.count;
1386    }
1387
1388    if (src0_row_start >= src0_row_end) {
1389        return HTP_STATUS_OK;
1390    }
1391
1392    const uint32_t nrows = src0_row_end - src0_row_start;
1393    mmctx->src0_row_start = src0_row_start;
1394    mmctx->src0_row_end   = src0_row_end;
1395
1396    bool is_repacked = (src0->type == HTP_TYPE_Q4_0 || src0->type == HTP_TYPE_Q4_1 ||
1397                        src0->type == HTP_TYPE_Q8_0 || src0->type == HTP_TYPE_IQ4_NL ||
1398                        src0->type == HTP_TYPE_MXFP4);
1399
1400    // Compute src0_nrows_per_thread
1401    mmctx->src0_nrows_per_thread  = fastdiv(nrows + octx->n_threads - 1, &octx->n_threads_div);
1402    if (is_repacked) {
1403        mmctx->src0_nrows_per_thread = hex_round_up(mmctx->src0_nrows_per_thread, 32);
1404    } else {
1405        mmctx->src0_nrows_per_thread += (mmctx->src0_nrows_per_thread & 1); // round up to even
1406    }
1407
1408    const size_t src0_row_size = nb01;
1409    const size_t dst_row_size  = nb1;
1410    size_t       src1_row_size = nb11;
1411
1412    const size_t src0_row_size_padded = hex_round_up(src0_row_size, 128);
1413    size_t       src1_row_size_padded;
1414
1415    worker_callback_t quant_task_func;
1416    worker_callback_t matmul_job_func;
1417    uint32_t n_quant_tasks = 1;
1418    if (src1_nrows > 1) {
1419        if (is_repacked) {
1420            switch (src0->type) {
1421                case HTP_TYPE_Q4_0:   matmul_job_func = hvx_mm_2d_repacked_q4_0;   break;
1422                case HTP_TYPE_Q4_1:   matmul_job_func = hvx_mm_2d_repacked_q4_1;   break;
1423                case HTP_TYPE_Q8_0:   matmul_job_func = hvx_mm_2d_repacked_q8_0;   break;
1424                case HTP_TYPE_IQ4_NL: matmul_job_func = hvx_mm_2d_repacked_iq4nl;  break;
1425                case HTP_TYPE_MXFP4:  matmul_job_func = hvx_mm_2d_repacked_mxfp4;  break;
1426                default:              return HTP_STATUS_NO_SUPPORT;
1427            }
1428        } else {
1429            matmul_job_func = hvx_mm_2d;
1430        }
1431    } else {
1432        if (is_repacked) {
1433            switch (src0->type) {
1434                case HTP_TYPE_Q4_0:   matmul_job_func = hvx_mv_2d_repacked_q4_0;   break;
1435                case HTP_TYPE_Q4_1:   matmul_job_func = hvx_mv_2d_repacked_q4_1;   break;
1436                case HTP_TYPE_Q8_0:   matmul_job_func = hvx_mv_2d_repacked_q8_0;   break;
1437                case HTP_TYPE_IQ4_NL: matmul_job_func = hvx_mv_2d_repacked_iq4nl;  break;
1438                case HTP_TYPE_MXFP4:  matmul_job_func = hvx_mv_2d_repacked_mxfp4;  break;
1439                default:              return HTP_STATUS_NO_SUPPORT;
1440            }
1441        } else {
1442            matmul_job_func = hvx_mv_2d;
1443        }
1444    }
1445
1446    bool need_quant = true;
1447
1448    switch (kparams->kernel_type) {
1449        case HTP_MM_KERNEL_HVX_F16_F16_VTCM:
1450            quant_task_func        = (src1->type == HTP_TYPE_F32) ? quantize_f32_f16_flat : quantize_f16_f16_flat;
1451            mmctx->type            = "f16-f16";
1452            mmctx->vec_dot_1x1     = vec_dot_f16_f16_aa_1x1;
1453            mmctx->vec_dot_2x1     = vec_dot_f16_f16_aa_2x1;
1454            mmctx->vec_dot_2x2     = vec_dot_f16_f16_aa_2x2;
1455            src1_row_size          = hex_round_up(ne10 * 2, 128);
1456            break;
1457
1458        case HTP_MM_KERNEL_HVX_F16_F32_DDR:
1459            mmctx->type            = "f16-f32";
1460            mmctx->vec_dot_1x1     = vec_dot_f16_f32_uu_1x1;
1461            matmul_job_func        = hvx_mm_4d;
1462            mmctx->mm_div_ne12_ne1 = kparams->div_ne12_ne1;
1463            mmctx->mm_div_ne1      = kparams->div_ne1;
1464            mmctx->mm_div_r2       = kparams->div_r2;
1465            mmctx->mm_div_r3       = kparams->div_r3;
1466            need_quant             = false;
1467            quant_task_func        = NULL;
1468            src1_row_size          = nb11;
1469            break;
1470
1471        case HTP_MM_KERNEL_HVX_F16_F16_DDR:
1472            mmctx->type            = "f16-f16";
1473            mmctx->vec_dot_1x1     = vec_dot_f16_f16_uu_1x1;
1474            matmul_job_func        = hvx_mm_4d;
1475            mmctx->mm_div_ne12_ne1 = kparams->div_ne12_ne1;
1476            mmctx->mm_div_ne1      = kparams->div_ne1;
1477            mmctx->mm_div_r2       = kparams->div_r2;
1478            mmctx->mm_div_r3       = kparams->div_r3;
1479            src1_row_size          = nb11;
1480            need_quant             = false;
1481            quant_task_func        = NULL;
1482            break;
1483
1484        case HTP_MM_KERNEL_HVX_F32_F32_VTCM:
1485            quant_task_func        = quantize_f32_f32_flat;
1486            mmctx->type            = "f32-f32";
1487            mmctx->vec_dot_1x1     = vec_dot_f32_f32_aa_1x1;
1488            mmctx->vec_dot_2x1     = vec_dot_f32_f32_aa_2x1;
1489            mmctx->vec_dot_2x2     = vec_dot_f32_f32_aa_2x2;
1490            src1_row_size          = hex_round_up(ne10 * 4, 128);
1491            break;
1492
1493        case HTP_MM_KERNEL_HVX_F32_F32_DDR:
1494            quant_task_func        = NULL;
1495            mmctx->type            = "f32-f32";
1496            mmctx->vec_dot_1x1     = vec_dot_f32_f32_uu_1x1;
1497            mmctx->mm_div_ne12_ne1 = kparams->div_ne12_ne1;
1498            mmctx->mm_div_ne1      = kparams->div_ne1;
1499            mmctx->mm_div_r2       = kparams->div_r2;
1500            mmctx->mm_div_r3       = kparams->div_r3;
1501            src1_row_size          = nb11;
1502            need_quant             = false;
1503            matmul_job_func        = hvx_mm_4d;
1504            break;
1505
1506        case HTP_MM_KERNEL_HVX_QUANT_ROW_FLAT: {
1507            n_quant_tasks = MIN(src1_nrows, octx->n_threads);
1508            quant_task_func = (src0->type == HTP_TYPE_Q4_1) ? quantize_f32_q8_1_flat : quantize_f32_q8_0_flat;
1509            src1_row_size = (src0->type == HTP_TYPE_Q4_1) ? htp_mm_q8_1_flat_row_size(ne10) : htp_mm_q8_0_flat_row_size(ne10);
1510
1511            if (src1_nrows > 1) {
1512                switch (src0->type) {
1513                    case HTP_TYPE_Q4_0:   matmul_job_func = hvx_mm_2d_repacked_q4_0_flat;   break;
1514                    case HTP_TYPE_Q4_1:   matmul_job_func = hvx_mm_2d_repacked_q4_1_flat;   break;
1515                    case HTP_TYPE_Q8_0:   matmul_job_func = hvx_mm_2d_repacked_q8_0_flat;   break;
1516                    case HTP_TYPE_IQ4_NL: matmul_job_func = hvx_mm_2d_repacked_iq4nl_flat;  break;
1517                    case HTP_TYPE_MXFP4:  matmul_job_func = hvx_mm_2d_repacked_mxfp4_flat;  break;
1518                    default:              return HTP_STATUS_NO_SUPPORT;
1519                }
1520            } else {
1521                switch (src0->type) {
1522                    case HTP_TYPE_Q4_0:   matmul_job_func = hvx_mv_2d_repacked_q4_0_flat;   break;
1523                    case HTP_TYPE_Q4_1:   matmul_job_func = hvx_mv_2d_repacked_q4_1_flat;   break;
1524                    case HTP_TYPE_Q8_0:   matmul_job_func = hvx_mv_2d_repacked_q8_0_flat;   break;
1525                    case HTP_TYPE_IQ4_NL: matmul_job_func = hvx_mv_2d_repacked_iq4nl_flat;  break;
1526                    case HTP_TYPE_MXFP4:  matmul_job_func = hvx_mv_2d_repacked_mxfp4_flat;  break;
1527                    default:              return HTP_STATUS_NO_SUPPORT;
1528                }
1529            }
1530            break;
1531        }
1532
1533        case HTP_MM_KERNEL_HVX_QUANT_BLOCK:
1534        case HTP_MM_KERNEL_HVX_QUANT_ROW:
1535        default:
1536            if (hvx_mm_init_vec_dot(mmctx, src0->type) != 0) {
1537                return HTP_STATUS_NO_SUPPORT;
1538            }
1539
1540            const uint32_t qk = QK_Q8_0_TILED;
1541            const uint32_t nb = (ne10 + qk - 1) / qk;
1542            const uint32_t total_nb = src1_nrows * nb;
1543
1544            if (src1_nrows < octx->n_threads) {
1545                n_quant_tasks = MIN(total_nb, octx->n_threads);
1546                quant_task_func = (src0->type == HTP_TYPE_Q4_1) ? quantize_f32_q8_1_tiled_block : quantize_f32_q8_0_tiled_block;
1547                for (uint32_t ith = 0; ith < n_quant_tasks; ++ith) {
1548                    uint32_t ib_first = (total_nb * ith) / n_quant_tasks;
1549                    uint32_t ib_last  = (total_nb * (ith + 1)) / n_quant_tasks;
1550                    mmctx->quant_ib_first[ith] = ib_first;
1551                    mmctx->quant_ib_last[ith]  = ib_last;
1552                    mmctx->quant_r[ith]        = ib_first / nb;
1553                    mmctx->quant_c[ith]        = ib_first % nb;
1554                }
1555            } else {
1556                n_quant_tasks = MIN(src1_nrows, octx->n_threads);
1557                quant_task_func = (src0->type == HTP_TYPE_Q4_1) ? quantize_f32_q8_1_tiled : quantize_f32_q8_0_tiled;
1558            }
1559            src1_row_size = (src0->type == HTP_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(ne10) : htp_mm_q8_0_tiled_row_size(ne10);
1560            break;
1561    }
1562
1563    struct htp_mm_hvx_vtcm_layout L;
1564    htp_mm_hvx_vtcm_layout_build(&L, kparams->kernel_type, src0->type, ne10, src1_nrows, octx->n_threads,
1565                                 dst_row_size, src0_row_size, src1_row_size, src2 ? src2->nb[1] : 0, kparams->n_prefetch, false, false);
1566
1567    if (kparams->kernel_type == HTP_MM_KERNEL_HVX_F16_F16_VTCM ||
1568        kparams->kernel_type == HTP_MM_KERNEL_HVX_F32_F32_VTCM ||
1569        kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_ROW ||
1570        kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_BLOCK) {
1571        mmctx->vtcm_src1_size_per_thread = L.src1_bytes;
1572    } else {
1573        mmctx->vtcm_src1_size_per_thread = fastdiv(L.src1_bytes, &octx->n_threads_div);
1574    }
1575
1576    mmctx->vtcm_src0_size_per_thread = fastdiv(L.src0_bytes, &octx->n_threads_div);
1577    mmctx->vtcm_dst_size_per_thread  = fastdiv(L.dst_bytes, &octx->n_threads_div);
1578
1579    const size_t vtcm_size = L.total_bytes;
1580
1581    FARF(HIGH, "matmul-%s : src0-vtcm-size %zu src1-vtcm-size %zu dst-vtcm-size %zu (%zu)\n", mmctx->type,
1582         L.src0_bytes, L.src1_bytes, L.dst_bytes, vtcm_size);
1583
1584    FARF(HIGH, "matmul-%s : %ux%ux%ux%u * %ux%ux%ux%u-> %ux%ux%ux%u (0x%p, 0x%p, 0x%p)\n", mmctx->type, src0->ne[0],
1585         src0->ne[1], src0->ne[2], src0->ne[3], src1->ne[0], src1->ne[1], src1->ne[2], src1->ne[3], dst->ne[0],
1586         dst->ne[1], dst->ne[2], dst->ne[3], src0->data, src1->data, dst->data);
1587
1588    if (octx->ctx->vtcm_size < vtcm_size) {
1589        FARF(ERROR, "matmul-%s : current VTCM reservation %zu is too small, needed %zu\n", mmctx->type,
1590             octx->ctx->vtcm_size, vtcm_size);
1591        return HTP_STATUS_VTCM_TOO_SMALL;
1592    }
1593
1594    uint8_t * const base = (uint8_t *) octx->ctx->vtcm_base;
1595    mmctx->vtcm_src1 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src1);
1596    mmctx->vtcm_src0 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src0);
1597    mmctx->vtcm_src2 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src2);
1598    mmctx->vtcm_dst  = VTCM_LAYOUT_PTR(uint8_t, base, L.off_dst);
1599
1600    octx->src1_spad.src  = NULL;
1601    octx->src0_spad.src  = NULL;
1602    octx->dst_spad.src   = NULL;
1603
1604    mmctx->vtcm_src0_stride = src0_row_size_padded;
1605    mmctx->vtcm_src1_stride = src1_row_size;
1606
1607    if (need_quant) {
1608        mmctx->n_quant_rows_per_thread = (src1_nrows + n_quant_tasks - 1) / n_quant_tasks;
1609        mmctx->quant_task_func = quant_task_func;
1610        mmctx->n_quant_tasks = n_quant_tasks;
1611        atomic_init(&mmctx->quant_barrier, n_quant_tasks);
1612    } else {
1613        mmctx->quant_task_func = NULL;
1614        mmctx->n_quant_tasks = 0;
1615    }
1616
1617    htp_trace_event_stop(tr, HTP_TRACE_EVT_INIT, 0);
1618
1619    worker_pool_run_func(octx->ctx->worker_pool, matmul_job_func, mmctx, octx->n_threads);
1620
1621    return HTP_STATUS_OK;
1622}
1623
1624static void hvx_mm_nx_2d(unsigned int nth, unsigned int ith, void * data) {
1625    struct htp_mm_context * mmctx = data;
1626    struct htp_ops_context * octx = mmctx->octx;
1627    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;
1628    const uint32_t n_weights = kparams->n_weights;
1629
1630    const struct htp_tensor * restrict act = octx->src[n_weights];
1631    const uint32_t src1_nrows = act->ne[1] * act->ne[2] * act->ne[3];
1632    const size_t src1_stride = mmctx->vtcm_src1_stride;
1633
1634    uint8_t * restrict vtcm_src0_ptr = mmctx->vtcm_src0 + mmctx->vtcm_src0_size_per_thread * ith;
1635    uint8_t * restrict src1_data     = mmctx->vtcm_src1;
1636
1637    dma_queue * dma_queue = octx->ctx->dma[ith];
1638    const uint32_t n_prefetch = kparams->n_prefetch;
1639    assert(n_prefetch >= 2 && n_prefetch <= HTP_MM_MAX_PREFETCH && (n_prefetch & (n_prefetch - 1)) == 0);
1640    const uint32_t prefetch_mask = n_prefetch - 1;
1641
1642    struct htp_thread_trace * tr = &octx->ctx->trace[ith];
1643
1644    hvx_mm_run_quant_task(mmctx, ith);
1645
1646    for (uint32_t widx = 0; widx < n_weights; widx++) {
1647        const struct htp_tensor * restrict src_w = octx->src[widx];
1648        const struct htp_tensor * restrict dst   = octx->dsts[widx];
1649        if (!src_w || !dst) continue;
1650
1651        const uint32_t ne00 = src_w->ne[0];
1652        const uint32_t ne01 = src_w->ne[1];
1653        uint32_t start_row = 0;
1654        uint32_t end_row   = ne01;
1655        if (octx->ctx->mdev.count > 1) {
1656            const bool can_split = htp_tensor_can_row_partition(dst, sizeof(float));
1657            const struct htp_tensor_mdev_range range = htp_tensor_mdev_partition(ne01, can_split ? 32 : 0, octx->ctx->mdev.idx, octx->ctx->mdev.count, &octx->ctx->mdev.count_div);
1658            start_row = range.start;
1659            end_row   = range.start + range.count;
1660        }
1661
1662        const uint32_t nrows = end_row - start_row;
1663        uint32_t src0_nrows_per_thread = fastdiv(nrows + nth - 1, &octx->n_threads_div);
1664        src0_nrows_per_thread += (src0_nrows_per_thread & 1);
1665
1666        const uint32_t src0_start_row  = start_row + src0_nrows_per_thread * ith;
1667        const uint32_t src0_end_row    = MIN(src0_start_row + src0_nrows_per_thread, end_row);
1668        const uint32_t src0_end_row_x2 = src0_start_row + ((src0_end_row - src0_start_row) & ~1U);
1669        if (src0_start_row >= src0_end_row) continue;
1670
1671        const size_t dst_row_size  = dst->nb[1];
1672        const size_t src0_row_size = src_w->nb[1];
1673        const size_t src0_stride   = hex_round_up(src0_row_size, 128);
1674
1675        const uint8_t * restrict src0_row = (const uint8_t *) src_w->data;
1676
1677        for (uint32_t ir0 = src0_start_row; ir0 < src0_end_row_x2; ir0 += 2) {
1678            const int is0 = (ir0 - src0_start_row);
1679            if (is0 >= (int)n_prefetch) break;
1680            dma_queue_push(dma_queue, dma_make_ptr(vtcm_src0_ptr + is0 * src0_stride, src0_row + ir0 * src0_row_size),
1681                           src0_stride, src0_row_size, src0_row_size, 2);
1682        }
1683
1684        for (uint32_t ir0 = src0_start_row; ir0 < src0_end_row_x2; ir0 += 2) {
1685            const uint8_t * ss0 = dma_queue_pop(dma_queue).dst;
1686            htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, ir0);
1687            uint32_t ir1 = 0;
1688            for (; ir1 + 1 < src1_nrows; ir1 += 2) {
1689                const uint8_t * restrict src1_col0 = (const uint8_t *) (src1_data + (ir1+0) * src1_stride);
1690                const uint8_t * restrict src1_col1 = (const uint8_t *) (src1_data + (ir1+1) * src1_stride);
1691                float * restrict dst_row0 = (float *) (dst->data + ((ir1+0) * dst_row_size));
1692                float * restrict dst_row1 = (float *) (dst->data + ((ir1+1) * dst_row_size));
1693                mmctx->vec_dot_2x2(ne00, &dst_row0[ir0], &dst_row1[ir0], ss0, ss0 + src0_stride, src1_col0, src1_col1);
1694            }
1695            for (; ir1 < src1_nrows; ++ir1) {
1696                const uint8_t * restrict src1_col = (const uint8_t *) (src1_data + ir1 * src1_stride);
1697                float * restrict dst_row = (float *) (dst->data + (ir1 * dst_row_size));
1698                mmctx->vec_dot_2x1(ne00, &dst_row[ir0], ss0, ss0 + src0_stride, src1_col);
1699            }
1700            htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, ir0);
1701
1702            const int pr0 = (ir0 + n_prefetch);
1703            const int is0 = (pr0 - src0_start_row) & prefetch_mask;
1704            if (pr0 < src0_end_row_x2) {
1705                dma_queue_push(dma_queue, dma_make_ptr(vtcm_src0_ptr + is0 * src0_stride, src0_row + pr0 * src0_row_size),
1706                               src0_stride, src0_row_size, src0_row_size, 2);
1707            }
1708        }
1709
1710        if (src0_end_row != src0_end_row_x2) {
1711            uint32_t ir0 = src0_end_row_x2;
1712            const int is0 = (ir0 - src0_start_row) & prefetch_mask;
1713            dma_queue_push(dma_queue, dma_make_ptr(vtcm_src0_ptr + is0 * src0_stride, src0_row + ir0 * src0_row_size),
1714                           src0_stride, src0_row_size, src0_row_size, 1);
1715            const uint8_t * ss0 = dma_queue_pop(dma_queue).dst;
1716            htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, ir0);
1717            for (uint32_t ir1 = 0; ir1 < src1_nrows; ++ir1) {
1718                const uint8_t * restrict src1_col = (const uint8_t *) (src1_data + ir1 * src1_stride);
1719                float * restrict dst_row = (float *) (dst->data + (ir1 * dst_row_size));
1720                mmctx->vec_dot_1x1(ne00, &dst_row[ir0], ss0, src1_col);
1721            }
1722            htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, ir0);
1723        }
1724    }
1725}
1726
1727#define DEQUANTIZE_WORKER_LOOP_IMPL(SUFFIX)                                                     \
1728static void dequantize_tiled_worker_loop_##SUFFIX(unsigned int n, unsigned int i, void *data) { \
1729    tiled_dequantize_state_t *state = (tiled_dequantize_state_t *)data;                         \
1730    struct htp_thread_trace * tr = &state->traces[i];                                           \
1731    htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_W_DEQUANT, i);                                  \
1732    for (unsigned int task_id = i; task_id < (unsigned int)state->n_tasks; task_id += n) {      \
1733        int start = task_id * state->n_tiles_per_task;                                          \
1734        int end   = hex_smin(start + state->n_tiles_per_task, state->n_tot_tiles);              \
1735        dequantize_tiled_weight_to_fp16_task_##SUFFIX(state, start, end);                       \
1736    }                                                                                           \
1737    htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_W_DEQUANT, i);                                   \
1738}
1739
1740DEQUANTIZE_WORKER_LOOP_IMPL(q4_0)
1741DEQUANTIZE_WORKER_LOOP_IMPL(q4_1)
1742DEQUANTIZE_WORKER_LOOP_IMPL(iq4_nl)
1743DEQUANTIZE_WORKER_LOOP_IMPL(mxfp4)
1744DEQUANTIZE_WORKER_LOOP_IMPL(q8_0)
1745
1746static void convert_f16_worker_loop(unsigned int n, unsigned int i, void *data) {
1747    tiled_dequantize_state_t *state = (tiled_dequantize_state_t *)data;
1748    struct htp_thread_trace * tr = &state->traces[i];
1749    htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_W_DEQUANT, i);
1750    for (unsigned int task_id = i; task_id < (unsigned int)state->n_tasks; task_id += n) {
1751        int start = task_id * state->n_tiles_per_task;
1752        int end   = hex_smin(start + state->n_tiles_per_task, state->n_tot_tiles);
1753        convert_f16_weight_to_fp16_tiles_task(state, start, end);
1754    }
1755    htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_W_DEQUANT, i);
1756}
1757
1758static void quantize_f32_worker_loop(unsigned int n, unsigned int i, void *data) {
1759    tiled_dequantize_state_t *state = (tiled_dequantize_state_t *)data;
1760
1761    struct htp_thread_trace * tr = &state->traces[i];
1762    htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_A_QUANT, i);
1763
1764    for (unsigned int task_id = i; task_id < (unsigned int)state->n_tasks; task_id += n) {
1765        int start = task_id * state->n_tiles_per_task;
1766        int end   = hex_smin(start + state->n_tiles_per_task, state->n_tot_tiles);
1767        quantize_f32_weight_to_fp16_tiles_task(state, start, end);
1768    }
1769
1770    htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_A_QUANT, i);
1771}
1772
1773static void transfer_output_chunk_worker_fn(unsigned int n, unsigned int i, void *data) {
1774    output_transfer_task_state_t *st = (output_transfer_task_state_t *) data;
1775
1776    struct htp_thread_trace * tr = &st->traces[i];
1777
1778    int start_chunk_idx = i * st->n_chunks_per_task;
1779    htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_O_PROC, start_chunk_idx);
1780
1781    for (unsigned int task_id = i; task_id < (unsigned int)st->n_tasks; task_id += n) {
1782        int    chunk_idx  = task_id * st->n_chunks_per_task;
1783        size_t chunk_size = hex_smin(st->n_tot_chunks - chunk_idx, st->n_chunks_per_task);
1784
1785        float        *dst      = st->dst      + chunk_idx * st->dst_stride;
1786        const float  *src2     = st->src2     ? (st->src2     + chunk_idx * st->src2_stride) : NULL;
1787        transfer_output_chunk_fp16_to_fp32(dst, src2, st->vtcm_src, chunk_idx, chunk_size, st->n_cols, st->dst_stride, st->src2_stride, st->dst_cols);
1788    }
1789
1790    htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_O_PROC, start_chunk_idx);
1791}
1792
1793typedef struct {
1794    struct htp_context            * ctx;
1795    struct htp_thread_trace       * traces;
1796    __fp16                        * dst;
1797    const float                   * src;
1798    const struct mmid_row_mapping * matrix_rows;
1799    float                         * vtcm_f32_act;
1800    uint32_t                        n_tasks;
1801    uint32_t                        n_tot_chunks;
1802    uint32_t                        n_chunks_per_task;
1803    uint32_t                        k_block;
1804    uint32_t                        k_stride;
1805    uint32_t                        k_valid;
1806    size_t                          vtcm_f32_act_bytes_per_thread;
1807    uint32_t                        dma_step_rows;
1808    uint32_t                        dma_step_rows_shift;
1809} activation_transfer_task_state_t;
1810
1811typedef struct {
1812    struct htp_context            * ctx;
1813    struct htp_thread_trace       * traces;
1814    __fp16                        * dst;
1815    const float                   * src;
1816    float                         * vtcm_f32_act;
1817    uint32_t                        n_rows;
1818    uint32_t                        k_block;
1819    uint32_t                        k_stride;
1820    uint32_t                        k_valid;
1821    uint32_t                        n_col_chunks;
1822    struct fastdiv_values           n_threads_div;
1823    size_t                          vtcm_f32_act_bytes;
1824    uint32_t                        dma_step_rows;
1825    uint32_t                        dma_step_rows_shift;
1826} activation_transfer_col_chunk_state_t;
1827
1828static void transfer_activation_chunk_fp32_to_fp16_dma_pipelined_col_chunk(
1829        dma_queue *dma_q,
1830        __fp16 *restrict vtcm_dst,
1831        const float *restrict src,
1832        uint32_t n_rows,
1833        uint32_t k_block,
1834        uint32_t k_stride,
1835        uint32_t k_chunk_valid,
1836        uint32_t c_first,
1837        uint32_t c_len,
1838        float *thread_f32_act,
1839        struct htp_thread_trace *tr,
1840        uint32_t dma_step_rows,
1841        uint32_t dma_step_rows_shift) {
1842
1843    const uint32_t R = dma_step_rows;
1844    const uint32_t n_rows_padded = hex_align_up(n_rows, HTP_MM_HMX_TILE_N_ROWS);
1845
1846    const uint32_t n_steps = n_rows_padded >> dma_step_rows_shift;
1847
1848    // Push step 0
1849    if (n_steps > 0 && n_rows > 0) {
1850        uint32_t nrows_to_fetch = hex_smin(n_rows, R);
1851        dma_queue_push(dma_q, dma_make_ptr(thread_f32_act, src + c_first),
1852                       c_len * sizeof(float), k_stride * sizeof(float), k_chunk_valid * sizeof(float), nrows_to_fetch);
1853    }
1854    // Push step 1
1855    if (n_steps > 1) {
1856        uint32_t next_r = R * 1;
1857        if (next_r < n_rows) {
1858            uint32_t nrows_to_fetch = hex_smin(n_rows - next_r, R);
1859            const float *next_src = src + next_r * k_stride + c_first;
1860            float *next_buf = thread_f32_act + 1 * R * c_len;
1861            dma_queue_push(dma_q, dma_make_ptr(next_buf, next_src),
1862                           c_len * sizeof(float), k_stride * sizeof(float), k_chunk_valid * sizeof(float), nrows_to_fetch);
1863        }
1864    }
1865    for (uint32_t s = 0; s < n_steps; ++s) {
1866        uint32_t r = s << dma_step_rows_shift;
1867        float *curr_buf = thread_f32_act;
1868
1869        if (r < n_rows) {
1870            curr_buf = (float *) dma_queue_pop(dma_q).dst;
1871        }
1872
1873        htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_A_PREP, r);
1874        for (uint32_t p = 0; p < (R >> 1); ++p) {
1875            uint32_t row_idx = r + (p << 1);
1876            float *pair_buf = curr_buf + (p << 1) * c_len;
1877            bool r0_valid = ((row_idx + 0) < n_rows);
1878            bool r1_valid = ((row_idx + 1) < n_rows);
1879
1880            transfer_activation_row_pair_fp32_to_fp16_col_chunk(
1881                vtcm_dst, pair_buf, pair_buf + c_len, row_idx, k_block, c_first, c_len, k_chunk_valid, r0_valid, r1_valid
1882            );
1883        }
1884        htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_A_PREP, r);
1885
1886        // Push step s + 2
1887        uint32_t next_s = s + 2;
1888        uint32_t next_r = next_s << dma_step_rows_shift;
1889        if (next_r < n_rows) {
1890            uint32_t nrows_to_fetch = hex_smin(n_rows - next_r, R);
1891            const float *next_src = src + next_r * k_stride + c_first;
1892            dma_queue_push(dma_q, dma_make_ptr(curr_buf, next_src),
1893                           c_len * sizeof(float), k_stride * sizeof(float), k_chunk_valid * sizeof(float), nrows_to_fetch);
1894        }
1895    }
1896}
1897
1898static void transfer_activation_chunk_fp32_to_fp16_col_chunk(
1899        __fp16 *restrict vtcm_dst,
1900        const float *restrict src,
1901        uint32_t n_rows,
1902        uint32_t k_block,
1903        uint32_t k_stride,
1904        uint32_t c_first,
1905        uint32_t c_len,
1906        uint32_t k_chunk_valid) {
1907    const uint32_t n_rows_padded = hex_align_up(n_rows, HTP_MM_HMX_TILE_N_ROWS);
1908    const uint32_t n_rows_tiled  = (n_rows / HTP_MM_HMX_TILE_N_ROWS) * HTP_MM_HMX_TILE_N_ROWS;
1909
1910    uint32_t r = 0;
1911
1912    #pragma unroll(2)
1913    for (r = 0; r < n_rows_tiled; r += 2) {
1914        const float *ptr_in0 = src + (r + 0) * k_stride + c_first;
1915        const float *ptr_in1 = src + (r + 1) * k_stride + c_first;
1916
1917        transfer_activation_row_pair_fp32_to_fp16_col_chunk(
1918            vtcm_dst, ptr_in0, ptr_in1, r, k_block, c_first, c_len, k_chunk_valid, true, true
1919        );
1920    }
1921
1922    for (; r < n_rows_padded; r += 2) {
1923        const bool row0_valid = r       < n_rows;
1924        const bool row1_valid = (r + 1) < n_rows;
1925
1926        const float *ptr_in0 = row0_valid ? (src + (r + 0) * k_stride + c_first) : NULL;
1927        const float *ptr_in1 = row1_valid ? (src + (r + 1) * k_stride + c_first) : NULL;
1928
1929        transfer_activation_row_pair_fp32_to_fp16_col_chunk(
1930            vtcm_dst, ptr_in0, ptr_in1, r, k_block, c_first, c_len, k_chunk_valid, row0_valid, row1_valid
1931        );
1932    }
1933}
1934
1935static void transfer_activation_chunk_col_chunk_worker_fn(unsigned int n, unsigned int i, void *data) {
1936    activation_transfer_col_chunk_state_t *st = (activation_transfer_col_chunk_state_t *) data;
1937    struct htp_thread_trace * tr = &st->traces[i];
1938
1939    uint32_t n_blocks = st->k_block / 32;
1940    uint32_t b_first = fastdiv(n_blocks * i, &st->n_threads_div);
1941    uint32_t b_last  = fastdiv(n_blocks * (i + 1), &st->n_threads_div);
1942    uint32_t c_first = b_first * 32;
1943    uint32_t c_last = b_last * 32;
1944    uint32_t c_len = c_last - c_first;
1945
1946    if (c_len == 0) {
1947        return;
1948    }
1949
1950    uint32_t k_chunk_valid = 0;
1951    if (st->k_valid > c_first) {
1952        k_chunk_valid = hex_smin(st->k_valid, c_last) - c_first;
1953    }
1954
1955    __fp16 *dst = st->dst;
1956    const float *src = st->src;
1957
1958    if (st->vtcm_f32_act) {
1959        size_t thread_scratch_bytes = hex_align_down(fastdiv(st->vtcm_f32_act_bytes, &st->n_threads_div), 128);
1960        float *thread_f32_act = (float *)((char *)st->vtcm_f32_act + i * thread_scratch_bytes);
1961
1962        transfer_activation_chunk_fp32_to_fp16_dma_pipelined_col_chunk(
1963            st->ctx->dma[i], dst, src, st->n_rows, st->k_block, st->k_stride, k_chunk_valid,
1964            c_first, c_len, thread_f32_act, tr, st->dma_step_rows, st->dma_step_rows_shift
1965        );
1966    } else {
1967        htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_A_PREP, c_first);
1968        transfer_activation_chunk_fp32_to_fp16_col_chunk(
1969            dst, src, st->n_rows, st->k_block, st->k_stride, c_first, c_len, k_chunk_valid
1970        );
1971        htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_A_PREP, c_first);
1972    }
1973}
1974
1975static void transfer_activation_chunk_fp32_to_fp16_dma_pipelined(
1976        dma_queue *dma_q,
1977        __fp16 *restrict vtcm_dst,
1978        const float *restrict src,
1979        uint32_t n_rows,
1980        uint32_t k_block,
1981        uint32_t k_stride,
1982        uint32_t k_valid,
1983        float *thread_f32_act,
1984        struct htp_thread_trace *tr,
1985        uint32_t dma_step_rows,
1986        uint32_t dma_step_rows_shift) {
1987
1988    const uint32_t R = dma_step_rows;
1989    const uint32_t n_rows_padded = hex_align_up(n_rows, HTP_MM_HMX_TILE_N_ROWS);
1990
1991    const uint32_t n_steps = n_rows_padded >> dma_step_rows_shift;
1992
1993    // Push step 0
1994    if (n_steps > 0 && n_rows > 0) {
1995        uint32_t nrows_to_fetch = hex_smin(n_rows, R);
1996        dma_queue_push(dma_q, dma_make_ptr(thread_f32_act, src),
1997                       k_block * sizeof(float), k_stride * sizeof(float), k_valid * sizeof(float), nrows_to_fetch);
1998    }
1999    // Push step 1 (if valid)
2000    if (n_steps > 1) {
2001        uint32_t next_r = R * 1;
2002        if (next_r < n_rows) {
2003            uint32_t nrows_to_fetch = hex_smin(n_rows - next_r, R);
2004            const float *next_src = src + next_r * k_stride;
2005            float *next_buf = thread_f32_act + 1 * R * k_block;
2006            dma_queue_push(dma_q, dma_make_ptr(next_buf, next_src),
2007                           k_block * sizeof(float), k_stride * sizeof(float), k_valid * sizeof(float), nrows_to_fetch);
2008        }
2009    }
2010    for (uint32_t s = 0; s < n_steps; ++s) {
2011        uint32_t r = s << dma_step_rows_shift;
2012        float *curr_buf = thread_f32_act;
2013
2014        if (r < n_rows) {
2015            curr_buf = (float *) dma_queue_pop(dma_q).dst;
2016        }
2017
2018        htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_A_PREP, r);
2019        for (uint32_t p = 0; p < (R >> 1); ++p) {
2020            uint32_t row_idx = r + (p << 1);
2021            float *pair_buf = curr_buf + (p << 1) * k_block;
2022            bool r0_valid = ((row_idx + 0) < n_rows);
2023            bool r1_valid = ((row_idx + 1) < n_rows);
2024
2025            transfer_activation_row_pair_fp32_to_fp16(vtcm_dst, pair_buf, pair_buf + k_block, row_idx, k_block, k_valid, r0_valid, r1_valid);
2026        }
2027        htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_A_PREP, r);
2028
2029        // Push step s + 2
2030        uint32_t next_s = s + 2;
2031        uint32_t next_r = next_s << dma_step_rows_shift;
2032        if (next_r < n_rows) {
2033            uint32_t nrows_to_fetch = hex_smin(n_rows - next_r, R);
2034            const float *next_src = src + next_r * k_stride;
2035            dma_queue_push(dma_q, dma_make_ptr(curr_buf, next_src),
2036                           k_block * sizeof(float), k_stride * sizeof(float), k_valid * sizeof(float), nrows_to_fetch);
2037        }
2038    }
2039}
2040
2041static void transfer_activation_chunk_worker_fn(unsigned int n, unsigned int i, void *data) {
2042    activation_transfer_task_state_t *st = (activation_transfer_task_state_t *) data;
2043
2044    struct htp_thread_trace * tr = &st->traces[i];
2045
2046    for (unsigned int task_id = i; task_id < (unsigned int)st->n_tasks; task_id += n) {
2047        int    chunk_idx  = task_id * st->n_chunks_per_task;
2048        size_t chunk_size = hex_smin(st->n_tot_chunks - chunk_idx, st->n_chunks_per_task);
2049
2050        __fp16      *dst = st->dst + chunk_idx * st->k_block;
2051        const float *src = st->src + chunk_idx * st->k_stride;
2052
2053        if (st->vtcm_f32_act) {
2054            float *thread_f32_act = (float *)((char *)st->vtcm_f32_act + i * st->vtcm_f32_act_bytes_per_thread);
2055            transfer_activation_chunk_fp32_to_fp16_dma_pipelined(
2056                st->ctx->dma[i], dst, src, chunk_size, st->k_block, st->k_stride, st->k_valid, thread_f32_act, tr, st->dma_step_rows, st->dma_step_rows_shift
2057            );
2058        } else {
2059            htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_A_PREP, chunk_idx);
2060            transfer_activation_chunk_fp32_to_fp16(dst, src, chunk_size, st->k_block, st->k_stride, st->k_valid);
2061            htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_A_PREP, chunk_idx);
2062        }
2063    }
2064}
2065
2066typedef struct {
2067    struct htp_thread_trace       * traces;
2068    const struct mmid_row_mapping * matrix_rows;
2069    __fp16                        * dst;
2070    const float                   * src;
2071    uint32_t                        n_tasks;
2072    uint32_t                        n_tot_chunks;
2073    uint32_t                        n_chunks_per_task;
2074    uint32_t                        k_block;
2075    uint32_t                        cur_a;
2076    uint32_t                        mapping_stride;
2077    uint32_t                        ne11;
2078    struct fastdiv_values           ne11_div;
2079    size_t                          nb11;
2080    size_t                          nb12;
2081    uint32_t                        start_row;
2082    uint32_t                        cne1;
2083    uint32_t                        k_valid;
2084} activation_transfer_gathered_task_state_t;
2085
2086typedef struct {
2087    struct htp_thread_trace       * traces;
2088    const struct mmid_row_mapping * matrix_rows;
2089    const __fp16                  * vtcm_src;
2090    float                         * dst;
2091    uint32_t                        n_tasks;
2092    uint32_t                        n_tot_chunks;
2093    uint32_t                        n_chunks_per_task;
2094    uint32_t                        n_cols;
2095    uint32_t                        cur_a;
2096    uint32_t                        mapping_stride;
2097    size_t                          dst_nb1;
2098    size_t                          dst_nb2;
2099    uint32_t                        start_row;
2100    uint32_t                        cne1;
2101} output_transfer_scattered_task_state_t;
2102
2103static void transfer_activation_chunk_gathered_worker_fn(unsigned int n, unsigned int i, void *data) {
2104    activation_transfer_gathered_task_state_t *st = data;
2105    struct htp_thread_trace * tr = &st->traces[i];
2106    int chunk_idx      = i;
2107    int chunk_size     = st->n_chunks_per_task;
2108    int vtcm_start_row = chunk_idx * chunk_size;
2109    int start_row      = st->start_row + vtcm_start_row;
2110    int n_rows         = hex_smin(st->cne1 - start_row, chunk_size);
2111    if (n_rows > 0) {
2112        htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_A_PREP, chunk_idx);
2113        transfer_activation_chunk_fp32_to_fp16_gathered(
2114            st->dst, st->src, start_row, vtcm_start_row, n_rows, st->k_block,
2115            st->matrix_rows, st->cur_a, st->mapping_stride,
2116            st->ne11, &st->ne11_div, st->nb11, st->nb12, st->cne1, st->k_valid);
2117        htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_A_PREP, chunk_idx);
2118    }
2119}
2120
2121static void transfer_activation_chunk_gathered_worker_flat_fn(unsigned int n, unsigned int i, void *data) {
2122    activation_transfer_gathered_task_state_t *st = data;
2123    struct htp_thread_trace * tr = &st->traces[i];
2124    int chunk_idx = i;
2125    int chunk_size = st->n_chunks_per_task;
2126    int vtcm_start_row = chunk_idx * chunk_size;
2127    int start_row = st->start_row + vtcm_start_row;
2128    int n_rows = hex_smin(st->cne1 - start_row, chunk_size);
2129    if (n_rows > 0) {
2130        htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_A_PREP, chunk_idx);
2131        transfer_activation_chunk_fp32_to_fp16_gathered_flat(
2132            st->dst, st->src, start_row, vtcm_start_row, n_rows, st->k_block,
2133            st->matrix_rows, st->cur_a, st->mapping_stride,
2134            st->nb12, st->cne1, st->k_valid);
2135        htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_A_PREP, chunk_idx);
2136    }
2137}
2138
2139static void transfer_output_chunk_scattered_worker_fn(unsigned int n, unsigned int i, void *data) {
2140    output_transfer_scattered_task_state_t *st = data;
2141    struct htp_thread_trace * tr = &st->traces[i];
2142    int chunk_idx = i;
2143    int chunk_size = st->n_chunks_per_task;
2144    int vtcm_start_row = chunk_idx * chunk_size;
2145    int start_row = st->start_row + vtcm_start_row;
2146    int n_rows = hex_smin(st->cne1 - start_row, chunk_size);
2147    if (n_rows > 0) {
2148        htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_O_PROC, chunk_idx);
2149        transfer_output_chunk_fp16_to_fp32_scattered(
2150            st->dst, st->vtcm_src, start_row, vtcm_start_row, n_rows, st->n_cols,
2151            st->matrix_rows, st->cur_a, st->mapping_stride,
2152            st->dst_nb1, st->dst_nb2, st->cne1);
2153        htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_O_PROC, chunk_idx);
2154    }
2155}
2156
2157// --- HMX Dispatchers & Entry Points ---
2158
2159static void dequantize_tiled_weight_chunk_to_fp16_tiles(
2160        struct htp_context *ctx, __fp16 *vtcm_dst,
2161        const void *weight_src_ddr,
2162        int n_cols, int k_block,
2163        size_t row_stride, int weight_type,
2164        int n_k_tiles, struct fastdiv_values n_k_tiles_div,
2165        worker_callback_t dequant_worker_fn, int n_threads) {
2166
2167    assert(n_cols  % HTP_MM_HMX_TILE_N_COLS == 0);
2168    assert(k_block % HTP_MM_HMX_TILE_N_COLS == 0);
2169
2170    size_t n_col_tiles = n_cols / HTP_MM_HMX_TILE_N_COLS;
2171    size_t n_tot_tiles = n_col_tiles * n_k_tiles;
2172
2173    size_t n_tiles_per_task = (n_threads == 1) ? n_tot_tiles : hmx_ceil_div(n_tot_tiles, n_threads);
2174
2175    tiled_dequantize_state_t state;
2176    state.n_tasks          = (n_tot_tiles + n_tiles_per_task - 1) / n_tiles_per_task;
2177    state.n_tot_tiles      = n_tot_tiles;
2178    state.n_tiles_per_task = n_tiles_per_task;
2179    state.dst              = vtcm_dst;
2180    state.src              = (const uint8_t *)weight_src_ddr;
2181    state.n_cols           = n_cols;
2182    state.k_block          = k_block;
2183    state.row_stride       = row_stride;
2184    state.weight_type      = weight_type;
2185    state.n_k_tiles        = n_k_tiles;
2186    state.n_k_tiles_div    = n_k_tiles_div;
2187    state.traces           = ctx->trace;
2188    state.ctx              = ctx;
2189
2190    state.tile_size = htp_mm_get_weight_tile_size(weight_type);
2191    state.aligned_tile_size = htp_mm_get_weight_aligned_tile_size(weight_type);
2192
2193    if (state.n_tasks == 1 || n_threads == 1) {
2194        dequant_worker_fn(1, 0, &state);
2195    } else {
2196        int n_tasks = hex_smin((int) state.n_tasks, n_threads);
2197        worker_pool_run_func(ctx->worker_pool, dequant_worker_fn, &state, n_tasks);
2198    }
2199}
2200
2201typedef struct {
2202    struct htp_context      * ctx;
2203    struct htp_thread_trace * traces;
2204    float                   * dst;
2205    const __fp16            * vtcm_src;
2206    const float             * src2;
2207    uint32_t                  n_rows;
2208    uint32_t                  n_cols;
2209    uint32_t                  dst_stride;
2210    uint32_t                  src2_stride;
2211    uint32_t                  dst_cols;
2212    struct fastdiv_values     n_threads_div;
2213} output_transfer_col_chunk_state_t;
2214
2215static void transfer_output_chunk_col_chunk_worker_fn(unsigned int n, unsigned int i, void *data) {
2216    (void) n;
2217    output_transfer_col_chunk_state_t *st = (output_transfer_col_chunk_state_t *) data;
2218    struct htp_thread_trace * tr = &st->traces[i];
2219
2220    uint32_t n_blocks = st->n_cols / 32;
2221    uint32_t b_first  = fastdiv(n_blocks * i, &st->n_threads_div);
2222    uint32_t b_last   = fastdiv(n_blocks * (i + 1), &st->n_threads_div);
2223    uint32_t c_first  = b_first * 32;
2224    uint32_t c_last   = b_last * 32;
2225    uint32_t c_len    = c_last - c_first;
2226
2227    if (c_len == 0) return;
2228
2229    htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_O_PROC, c_first);
2230
2231    const __fp16 *vtcm_src = st->vtcm_src + b_first * HTP_MM_HMX_TILE_N_ELMS;
2232    const float *src2      = st->src2 ? (st->src2 + c_first) : NULL;
2233    float *dst             = st->dst + c_first;
2234
2235    int chunk_dst_cols = (int)st->dst_cols - (int)c_first;
2236    if (chunk_dst_cols > 0) {
2237        transfer_output_chunk_fp16_to_fp32_col_chunk(
2238            dst, src2, vtcm_src, 0, st->n_rows, c_len, st->n_cols,
2239            st->dst_stride, st->src2_stride, (uint32_t)chunk_dst_cols
2240        );
2241    }
2242
2243    htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_O_PROC, c_first);
2244}
2245
2246static void transfer_output_chunk_threaded(struct htp_context *ctx, float *dst, const float *src2, const __fp16 *vtcm_src,
2247                                              int n_rows, int n_cols, int dst_stride, uint32_t src2_stride, int dst_cols, int n_threads) {
2248    assert(n_cols % HTP_MM_HMX_TILE_N_COLS == 0);
2249
2250    if (n_rows <= 0) return;
2251
2252    uint32_t n_blocks = (uint32_t)n_cols / 32;
2253    if (n_threads > 1 && n_blocks >= (uint32_t)n_threads) {
2254        struct fastdiv_values n_threads_div = (n_threads == (int)ctx->n_threads) ? ctx->n_threads_div : init_fastdiv_values(n_threads);
2255        output_transfer_col_chunk_state_t col_state;
2256        col_state.dst = dst;
2257        col_state.src2 = src2;
2258        col_state.vtcm_src = vtcm_src;
2259        col_state.n_rows = (uint32_t)n_rows;
2260        col_state.n_cols = (uint32_t)n_cols;
2261        col_state.dst_stride = (uint32_t)dst_stride;
2262        col_state.src2_stride = src2_stride;
2263        col_state.dst_cols = (uint32_t)dst_cols;
2264        col_state.n_threads_div = n_threads_div;
2265        col_state.traces = ctx->trace;
2266        col_state.ctx = ctx;
2267
2268        worker_pool_run_func(ctx->worker_pool, transfer_output_chunk_col_chunk_worker_fn, &col_state, n_threads);
2269        return;
2270    }
2271
2272    size_t n_tot_chunks      = n_rows;
2273    size_t n_chunks_per_task = (n_threads == 1) ? n_tot_chunks : hmx_ceil_div(n_rows, n_threads);
2274    n_chunks_per_task        = hex_align_up(n_chunks_per_task, 2);
2275
2276    int actual_threads = hmx_ceil_div(n_rows, n_chunks_per_task);
2277
2278    output_transfer_task_state_t state;
2279    state.n_tasks           = actual_threads;
2280    state.n_tot_chunks      = n_tot_chunks;
2281    state.n_chunks_per_task = n_chunks_per_task;
2282    state.dst               = dst;
2283    state.src2              = src2;
2284    state.vtcm_src          = vtcm_src;
2285    state.n_cols            = n_cols;
2286    state.dst_stride        = dst_stride;
2287    state.src2_stride       = src2_stride;
2288    state.dst_cols          = dst_cols;
2289    state.traces            = ctx->trace;
2290
2291    if (actual_threads <= 1) {
2292        transfer_output_chunk_worker_fn(1, 0, &state);
2293    } else {
2294        worker_pool_run_func(ctx->worker_pool, transfer_output_chunk_worker_fn, &state, actual_threads);
2295    }
2296}
2297
2298struct activation_transfer_params {
2299    struct htp_context *          ctx;
2300    __fp16 *                      dst;
2301    const float *                 src;
2302    int                           n_rows;
2303    int                           k_block;
2304    int                           k_stride;
2305    int                           n_threads;
2306    const struct fastdiv_values * act_threads_div;
2307    const struct fastdiv_values * k_div;
2308    int                           k_valid;
2309    float *                       vtcm_f32_act;
2310    size_t                        vtcm_f32_act_bytes;
2311};
2312
2313static void transfer_activation_chunk_threaded(const struct activation_transfer_params * params) {
2314    struct htp_context *          ctx                = params->ctx;
2315    __fp16 *                      dst                = params->dst;
2316    const float *                 src                = params->src;
2317    int                           n_rows             = params->n_rows;
2318    int                           k_block            = params->k_block;
2319    int                           k_stride           = params->k_stride;
2320    int                           n_threads          = params->n_threads;
2321    const struct fastdiv_values * act_threads_div    = params->act_threads_div;
2322    const struct fastdiv_values * k_div              = params->k_div;
2323    int                           k_valid            = params->k_valid;
2324    float *                       vtcm_f32_act       = params->vtcm_f32_act;
2325    size_t                        vtcm_f32_act_bytes = params->vtcm_f32_act_bytes;
2326
2327    if (n_rows <= 0) {
2328        return;
2329    }
2330
2331    const size_t n_tasks = (n_rows + 31) >> 5;
2332    if (n_threads > 1 && k_block > 32 && n_tasks < (size_t) n_threads) {
2333        // Calculate step rows parameters for column-chunked dma pipelining
2334        uint32_t dma_step_rows = 2;
2335        uint32_t dma_step_rows_shift = 1;
2336        if (vtcm_f32_act && vtcm_f32_act_bytes > 0 && k_block > 0) {
2337            size_t thread_scratch_bytes = hex_align_down(fastdiv(vtcm_f32_act_bytes, act_threads_div), 128);
2338            size_t thread_scratch_elements = thread_scratch_bytes / sizeof(float);
2339            size_t dma_step_rows_max = fastdiv(thread_scratch_elements / 2, k_div);
2340            if (dma_step_rows_max >= 4) {
2341                dma_step_rows = 4;
2342                dma_step_rows_shift = 2;
2343            }
2344        }
2345
2346        activation_transfer_col_chunk_state_t col_state;
2347        col_state.dst = dst;
2348        col_state.src = src;
2349        col_state.n_rows = n_rows;
2350        col_state.k_block = k_block;
2351        col_state.k_stride = k_stride;
2352        col_state.k_valid = k_valid;
2353        col_state.n_col_chunks = n_threads;
2354        col_state.n_threads_div = *act_threads_div;
2355        col_state.vtcm_f32_act = vtcm_f32_act;
2356        col_state.vtcm_f32_act_bytes = vtcm_f32_act_bytes;
2357        col_state.traces = ctx->trace;
2358        col_state.ctx = ctx;
2359        col_state.dma_step_rows = dma_step_rows;
2360        col_state.dma_step_rows_shift = dma_step_rows_shift;
2361
2362        worker_pool_run_func(ctx->worker_pool, transfer_activation_chunk_col_chunk_worker_fn, &col_state, n_threads);
2363        return;
2364    }
2365
2366    assert(k_block % HTP_MM_HMX_TILE_N_COLS == 0 && k_stride % HTP_MM_HMX_TILE_N_COLS == 0);
2367
2368    size_t n_tot_chunks      = n_rows;
2369    size_t n_chunks_per_task = (n_threads == 1) ? n_tot_chunks : 32;  // must be multiple of 32 to ensure correct destination address
2370
2371    activation_transfer_task_state_t state;
2372    state.n_tasks            = (n_threads == 1) ? 1 : hmx_ceil_div(n_tot_chunks, 32);
2373    state.n_tot_chunks       = n_tot_chunks;
2374    state.n_chunks_per_task  = n_chunks_per_task;
2375    state.dst                = dst;
2376    state.src                = src;
2377    state.k_block            = k_block;
2378    state.k_stride           = k_stride;
2379    state.k_valid            = k_valid;
2380    state.traces             = ctx->trace;
2381    state.ctx                = ctx;
2382    state.vtcm_f32_act       = vtcm_f32_act;
2383
2384    state.vtcm_f32_act_bytes_per_thread = hex_align_down(fastdiv(vtcm_f32_act_bytes, act_threads_div), 128);
2385
2386    uint32_t dma_step_rows = 2;
2387    uint32_t dma_step_rows_shift = 1;
2388    if (vtcm_f32_act && state.vtcm_f32_act_bytes_per_thread > 0 && k_block > 0) {
2389        size_t thread_scratch_elements = state.vtcm_f32_act_bytes_per_thread / sizeof(float);
2390        size_t dma_step_rows_max = fastdiv(thread_scratch_elements / 2, k_div);
2391        if (dma_step_rows_max >= 4) {
2392            dma_step_rows = 4;
2393            dma_step_rows_shift = 2;
2394        }
2395    }
2396    state.dma_step_rows      = dma_step_rows;
2397    state.dma_step_rows_shift = dma_step_rows_shift;
2398
2399    int active_threads = hex_smin(n_threads, (int)state.n_tasks);
2400    if (state.n_tasks == 1 || n_threads == 1) {
2401        transfer_activation_chunk_worker_fn(1, 0, &state);
2402    } else {
2403        worker_pool_run_func(ctx->worker_pool, transfer_activation_chunk_worker_fn, &state, active_threads);
2404    }
2405}
2406// --- Async HMX matmul job (for pipeline overlap) ---
2407
2408typedef struct {
2409    __fp16 *       output;
2410    const __fp16 * activation;
2411    const __fp16 * weight;
2412    const __fp16 * scales;
2413    uint32_t       n_row_tiles;
2414    uint32_t       n_col_tiles;
2415    uint32_t       n_dot_tiles;
2416} hmx_matmul_job_t;
2417
2418static void hmx_matmul_worker_fn(void * data) {
2419    hmx_matmul_job_t * job = (hmx_matmul_job_t *) data;
2420    FARF(HIGH, "hmx-mm-job: n_row_tiles %u n_col_tiles %u n_dot_tiles %u", job->n_row_tiles, job->n_col_tiles, job->n_dot_tiles);
2421    core_dot_chunk_fp16(job->output, job->activation, job->weight, job->scales, job->n_row_tiles, job->n_col_tiles, job->n_dot_tiles);
2422}
2423
2424static inline void hmx_matmul_job_init(hmx_matmul_job_t * job,
2425                                       __fp16 *           output,
2426                                       const __fp16 *     activation,
2427                                       const __fp16 *     weight,
2428                                       const __fp16 *     scales,
2429                                       uint32_t           n_row_tiles,
2430                                       uint32_t           n_col_tiles,
2431                                       uint32_t           n_dot_tiles) {
2432    job->output      = output;
2433    job->activation  = activation;
2434    job->weight      = weight;
2435    job->scales      = scales;
2436    job->n_row_tiles = n_row_tiles;
2437    job->n_col_tiles = n_col_tiles;
2438    job->n_dot_tiles = n_dot_tiles;
2439}
2440
2441static int hmx_mm_2d_f32(struct htp_context *ctx,
2442                                  float *restrict dst,
2443                                  const float *restrict src2,
2444                                  const float *activation,
2445                                  const uint8_t *weight,
2446                                  int m, int k, int n,
2447                                  int act_stride,
2448                                  int weight_stride,
2449                                  int weight_type,
2450                                  int k_valid,
2451                                  int dst_stride,
2452                                  uint32_t src2_stride,
2453                                  int dst_cols,
2454                                  int m_chunk,
2455                                  int n_chunk,
2456                                  int pipeline,
2457                                  int n_threads,
2458                                  int act_threads,
2459                                  const struct fastdiv_values * act_threads_div,
2460                                  const struct fastdiv_values * k_div,
2461                                  int tile_size,
2462                                  int aligned_tile_size,
2463                                  int vtcm_size) {
2464    struct htp_thread_trace * tr = &ctx->trace[0];
2465    htp_trace_event_start(tr, HTP_TRACE_EVT_INIT, 0);
2466
2467    if (k % 32 != 0 || n % 32 != 0) { return -1; }
2468    if (!hex_is_aligned(dst, VLEN) || !hex_is_aligned(activation, VLEN)) { return -1; }
2469
2470    size_t row_stride = htp_mm_get_tiled_row_stride(weight_type, k);
2471    if (row_stride == 0) {
2472        return -1;
2473    }
2474
2475    worker_callback_t dequant_worker_fn = NULL;
2476    switch (weight_type) {
2477        case HTP_TYPE_Q4_0:   dequant_worker_fn = dequantize_tiled_worker_loop_q4_0; break;
2478        case HTP_TYPE_IQ4_NL: dequant_worker_fn = dequantize_tiled_worker_loop_iq4_nl; break;
2479        case HTP_TYPE_Q4_1:   dequant_worker_fn = dequantize_tiled_worker_loop_q4_1; break;
2480        case HTP_TYPE_MXFP4:  dequant_worker_fn = dequantize_tiled_worker_loop_mxfp4; break;
2481        case HTP_TYPE_Q8_0:   dequant_worker_fn = dequantize_tiled_worker_loop_q8_0; break;
2482        case HTP_TYPE_F16:    dequant_worker_fn = convert_f16_worker_loop; break;
2483        case HTP_TYPE_F32:    dequant_worker_fn = quantize_f32_worker_loop; break;
2484        default:
2485            return -1;
2486    }
2487
2488    const int n_k_tiles = k / HTP_MM_HMX_TILE_N_COLS;
2489    const struct fastdiv_values n_k_tiles_div = init_fastdiv_values(n_k_tiles);
2490
2491    const bool is_quant       = (weight_type != HTP_TYPE_F16 && weight_type != HTP_TYPE_F32);
2492    const size_t vec_dot_size = k * sizeof(__fp16);
2493    const size_t vtcm_budget  = ctx->vtcm_size;
2494
2495    const uint32_t dma_dst_stride  = is_quant ? aligned_tile_size : row_stride;
2496    const uint32_t dma_src_stride  = is_quant ? tile_size : weight_stride;
2497    const uint32_t dma_width_bytes = is_quant ? tile_size : row_stride;
2498
2499    size_t m_chunk_n_rows = m_chunk;
2500    size_t n_chunk_n_cols = n_chunk;
2501    size_t vtcm_used      = vtcm_size;
2502
2503    const size_t qweight_row_stride = is_quant ? (size_t)(n_k_tiles * aligned_tile_size) / 32 : 0;
2504
2505    struct htp_mm_hmx_vtcm_layout L;
2506    htp_mm_hmx_vtcm_layout_build(&L, HTP_MM_KERNEL_HMX_2D, weight_type, k, m_chunk_n_rows, n_chunk_n_cols, 1, false, pipeline, act_threads, aligned_tile_size);
2507
2508    vtcm_used = L.total_bytes;
2509    if (vtcm_used > vtcm_budget) {
2510        FARF(ERROR, "hmx-mm-2d-precomputed: VTCM overflow: used %zu budget %zu, m %d k %d n %d mc %zu nc %zu",
2511             vtcm_used, vtcm_budget, m, k, n, m_chunk_n_rows, n_chunk_n_cols);
2512        return -1;
2513    }
2514
2515    uint8_t * const base = (uint8_t *) ctx->vtcm_base;
2516    __fp16  *vtcm_weight_raw[2] = {
2517        VTCM_LAYOUT_PTR(__fp16, base, L.off_weight[0]),
2518        VTCM_LAYOUT_PTR_OPTIONAL(__fp16, base, L.off_weight[1], pipeline)
2519    };
2520
2521    __fp16  *vtcm_f16_act    = VTCM_LAYOUT_PTR(__fp16, base, L.off_act);
2522    float   *vtcm_f32_act    = VTCM_LAYOUT_PTR(float, base, L.off_act_f32);
2523    __fp16  *vtcm_output     = VTCM_LAYOUT_PTR(__fp16, base, L.off_dst[0]);
2524    void    *vtcm_scratch0   = VTCM_LAYOUT_PTR(void, base, L.off_scratch[0]);
2525    void    *vtcm_scratch1   = VTCM_LAYOUT_PTR_OPTIONAL(void, base, L.off_scratch[1], pipeline);
2526    void    *vtcm_scratch2   = VTCM_LAYOUT_PTR_OPTIONAL(void, base, L.off_dst[1], pipeline);
2527    __fp16  *vtcm_scales     = VTCM_LAYOUT_PTR(__fp16, base, L.off_scales);
2528
2529    hmx_init_column_scales(vtcm_scales, Q6_V_vsplat_R(0x3c00));  // scale: 1.0, bias: 0.0 in FP16
2530
2531    FARF(HIGH, "hmx-mm-2d: m %d k %d n %d wtype %d mc %zu nc %zu vtcm %zu/%zu",
2532         m, k, n, weight_type, m_chunk_n_rows, n_chunk_n_cols, vtcm_used, vtcm_budget);
2533
2534    int n_chunk_cnt = hmx_ceil_div(n, n_chunk_n_cols);
2535
2536    htp_trace_event_stop(tr, HTP_TRACE_EVT_INIT, 0);
2537
2538    if (pipeline) {
2539        // --- Asynchronous Pipelined Loop ---
2540        hmx_matmul_job_t job_slots[2];  // persistent double-buffered job descriptors
2541
2542        for (size_t mr = 0; mr < m; mr += m_chunk_n_rows) {
2543            const size_t n_rows = hex_smin(m - mr, m_chunk_n_rows);
2544
2545            void *vtcm_weight_bufs[2] = { vtcm_scratch0, vtcm_scratch1 };
2546            void *vtcm_output_bufs[2] = { vtcm_output,   vtcm_scratch2 };
2547
2548            struct activation_transfer_params act_params = {
2549                .ctx = ctx,
2550                .dst = vtcm_f16_act,
2551                .src = activation + mr * act_stride,
2552                .n_rows = (int) n_rows,
2553                .k_block = k,
2554                .k_stride = act_stride,
2555                .n_threads = act_threads,
2556                .act_threads_div = act_threads_div,
2557                .k_div = k_div,
2558                .k_valid = k_valid,
2559                .vtcm_f32_act = vtcm_f32_act,
2560                .vtcm_f32_act_bytes = L.act_f32_bytes,
2561            };
2562            transfer_activation_chunk_threaded(&act_params);
2563
2564            // Prologue: push A0 and optionally A1 (if n_chunk_cnt > 1)
2565            const size_t   n_cols_A0 = hex_smin(n - 0 * n_chunk_n_cols, n_chunk_n_cols);
2566            const uint32_t height_A0 = is_quant ? (n_cols_A0 / 32) * n_k_tiles : n_cols_A0;
2567            dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_weight_raw[0], weight),
2568                           dma_dst_stride, dma_src_stride, dma_width_bytes, height_A0);
2569
2570            if (1 < n_chunk_cnt) {
2571                const size_t   n_cols_A1 = hex_smin(n - 1 * n_chunk_n_cols, n_chunk_n_cols);
2572                const uint32_t height_A1 = is_quant ? (n_cols_A1 / 32) * n_k_tiles : n_cols_A1;
2573                dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_weight_raw[1], weight + n_chunk_n_cols * weight_stride),
2574                               dma_dst_stride, dma_src_stride, dma_width_bytes, height_A1);
2575            }
2576
2577            // Main loop: pop A_i -> dequantize A_i -> push A_{i+2} -> submit C_i -> wait C_{i-1} and store D_{i-1}
2578            for (int i = 0; i < n_chunk_cnt; ++i) {
2579                const size_t nc    = i * n_chunk_n_cols;
2580                const size_t nc_p2 = nc + 2 * n_chunk_n_cols;
2581
2582                const size_t n_cols    = hex_smin(n - nc, n_chunk_n_cols);
2583                const size_t n_cols_p2 = hex_smin(n - nc_p2, n_chunk_n_cols);
2584
2585                // 1. pop A_i
2586                void * curr_raw = dma_queue_pop(ctx->dma[0]).dst;
2587
2588                // 2. dequantize A_i
2589                dequantize_tiled_weight_chunk_to_fp16_tiles(
2590                    ctx, vtcm_weight_bufs[i % 2], curr_raw,
2591                    n_cols, k, row_stride, weight_type,
2592                    n_k_tiles, n_k_tiles_div, dequant_worker_fn, n_threads);
2593
2594                // 3. push A_{i+2} (if i+2 < n_chunk_cnt)
2595                if (i + 2 < n_chunk_cnt) {
2596                    const uint32_t height_p2 = is_quant ? (n_cols_p2 / 32) * n_k_tiles : n_cols_p2;
2597                    dma_queue_push(ctx->dma[0], dma_make_ptr(curr_raw, weight + nc_p2 * weight_stride),
2598                                   dma_dst_stride, dma_src_stride, dma_width_bytes, height_p2);
2599                }
2600
2601                // 4. submit C_i
2602                hmx_matmul_job_init(&job_slots[i % 2], (__fp16 *) vtcm_output_bufs[i % 2],
2603                                    (__fp16 *) vtcm_f16_act, (__fp16 *) vtcm_weight_bufs[i % 2],
2604                                    vtcm_scales, hmx_ceil_div(n_rows, HTP_MM_HMX_TILE_N_ROWS),
2605                                    hmx_ceil_div(n_cols, HTP_MM_HMX_TILE_N_COLS), k / HTP_MM_HMX_TILE_N_ROWS);
2606                hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_matmul_worker_fn, &job_slots[i % 2]));
2607
2608                // 5. wait C_{i-1} and store D_{i-1} (multi-thread HVX, parallel with C_i)
2609                if (i > 0) {
2610                    hmx_queue_pop(ctx->hmx_queue);
2611                    const size_t nc_prev = (i - 1) * n_chunk_n_cols;
2612                    const size_t n_cols_prev = hex_smin(n - nc_prev, n_chunk_n_cols);
2613                    float *output_chunk = dst + (mr * dst_stride + nc_prev);
2614                    const float *src2_chunk = src2 ? (src2 + mr * src2_stride + nc_prev) : NULL;
2615                    int chunk_dst_cols = dst_cols - (int)nc_prev;
2616                    if (chunk_dst_cols > 0) {
2617                        transfer_output_chunk_threaded(ctx, output_chunk, src2_chunk, vtcm_output_bufs[(i - 1) % 2], n_rows, n_cols_prev, dst_stride, src2_stride, chunk_dst_cols, n_threads);
2618                    }
2619                }
2620            }
2621
2622            // Epilogue: wait C_{last} and store D_{last}
2623            hmx_queue_pop(ctx->hmx_queue);
2624            const size_t nc_last = (n_chunk_cnt - 1) * n_chunk_n_cols;
2625            const size_t n_cols_last = hex_smin(n - nc_last, n_chunk_n_cols);
2626            float *output_chunk = dst + (mr * dst_stride + nc_last);
2627            const float *src2_chunk = src2 ? (src2 + mr * src2_stride + nc_last) : NULL;
2628            int chunk_dst_cols = dst_cols - (int)nc_last;
2629            if (chunk_dst_cols > 0) {
2630                transfer_output_chunk_threaded(ctx, output_chunk, src2_chunk, vtcm_output_bufs[(n_chunk_cnt - 1) % 2], n_rows, n_cols_last, dst_stride, src2_stride, chunk_dst_cols, n_threads);
2631            }
2632        }
2633    } else {
2634        // --- Synchronous loop (m <= 32 or fallback) ---
2635        hmx_matmul_job_t job;
2636        for (size_t mr = 0; mr < m; mr += m_chunk_n_rows) {
2637            const size_t n_rows = hex_smin(m - mr, m_chunk_n_rows);
2638
2639            struct activation_transfer_params act_params = {
2640                .ctx = ctx,
2641                .dst = vtcm_f16_act,
2642                .src = activation + mr * act_stride,
2643                .n_rows = (int) n_rows,
2644                .k_block = k,
2645                .k_stride = act_stride,
2646                .n_threads = act_threads,
2647                .act_threads_div = act_threads_div,
2648                .k_div = k_div,
2649                .k_valid = k_valid,
2650                .vtcm_f32_act = vtcm_f32_act,
2651                .vtcm_f32_act_bytes = L.act_f32_bytes,
2652            };
2653            transfer_activation_chunk_threaded(&act_params);
2654
2655            // A0: Pre-fetch the first weight chunk (nc = 0)
2656            if (n > 0) {
2657                const size_t n_cols = hex_smin(n, n_chunk_n_cols);
2658                const uint32_t height = is_quant ? (n_cols / 32) * n_k_tiles : n_cols;
2659                dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_weight_raw[0], weight), dma_dst_stride, dma_src_stride, dma_width_bytes, height);
2660            }
2661
2662            for (size_t nc = 0; nc < n; nc += n_chunk_n_cols) {
2663                const size_t n_cols = hex_smin(n - nc, n_chunk_n_cols);
2664                const size_t n_row_tiles = hmx_ceil_div(n_rows, HTP_MM_HMX_TILE_N_ROWS);
2665                const size_t n_col_tiles = hmx_ceil_div(n_cols, HTP_MM_HMX_TILE_N_COLS);
2666
2667                // A: Wait for weight DMA
2668                void * curr_raw = dma_queue_pop(ctx->dma[0]).dst;
2669
2670                // B: Weight Dequantize (Threaded)
2671                dequantize_tiled_weight_chunk_to_fp16_tiles(
2672                    ctx, vtcm_scratch0, curr_raw,
2673                    n_cols, k, row_stride, weight_type,
2674                    n_k_tiles, n_k_tiles_div, dequant_worker_fn, n_threads);
2675
2676                // Start weight DMA for the next chunk early
2677                const size_t nc_next = nc + n_chunk_n_cols;
2678                if (nc_next < n) {
2679                    const size_t n_cols_next = hex_smin(n - nc_next, n_chunk_n_cols);
2680                    const uint32_t height_next = is_quant ? (n_cols_next / 32) * n_k_tiles : n_cols_next;
2681                    dma_queue_push(ctx->dma[0], dma_make_ptr(curr_raw, weight + nc_next * weight_stride), dma_dst_stride, dma_src_stride, dma_width_bytes, height_next);
2682                }
2683
2684                // C: HMX Compute (Queue-based)
2685                hmx_matmul_job_init(&job, vtcm_output, vtcm_f16_act, vtcm_scratch0, vtcm_scales, n_row_tiles, n_col_tiles, k / HTP_MM_HMX_TILE_N_ROWS);
2686                hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_matmul_worker_fn, &job));
2687                hmx_queue_pop(ctx->hmx_queue);
2688
2689                // D: Output Store
2690                float *output_chunk = dst + (mr * dst_stride + nc);
2691                const float *src2_chunk = src2 ? (src2 + mr * src2_stride + nc) : NULL;
2692                int chunk_dst_cols = dst_cols - (int)nc;
2693                if (chunk_dst_cols > 0) {
2694                    transfer_output_chunk_threaded(ctx, output_chunk, src2_chunk, vtcm_output, n_rows, n_cols, dst_stride, src2_stride, chunk_dst_cols, n_threads);
2695                }
2696            }
2697        }
2698    }
2699
2700    return 0;
2701}
2702
2703static int hmx_mm_nx_2d_f32(struct htp_ops_context * octx, const struct htp_mm_kernel_params * kparams) {
2704    struct htp_context * ctx = octx->ctx;
2705    struct htp_thread_trace * tr = &ctx->trace[0];
2706    htp_trace_event_start(tr, HTP_TRACE_EVT_INIT, 0);
2707
2708    const uint32_t n_weights = kparams->n_weights;
2709    if (n_weights == 0 || n_weights > HTP_OP_MAX_OUTPUTS) {
2710        return HTP_STATUS_INVAL_PARAMS;
2711    }
2712
2713    const struct htp_tensor * restrict src0 = octx->src[0];
2714    const struct htp_tensor * restrict act  = octx->src[n_weights];
2715
2716    const int weight_type = (int) src0->type;
2717    const int k           = (int) act->ne[0];
2718    const int k_valid     = (int) act->ne[0];
2719    const int m           = (int) (act->ne[1] * act->ne[2] * act->ne[3]);
2720    const int act_stride  = (int) (act->nb[1] / sizeof(float));
2721    const float * activation = (const float *) act->data;
2722
2723    if (k % 32 != 0) { return HTP_STATUS_NO_SUPPORT; }
2724    if (!hex_is_aligned(activation, VLEN)) { return HTP_STATUS_NO_SUPPORT; }
2725
2726    size_t row_stride = htp_mm_get_tiled_row_stride(weight_type, k);
2727    if (row_stride == 0) {
2728        return HTP_STATUS_NO_SUPPORT;
2729    }
2730
2731    worker_callback_t dequant_worker_fn = NULL;
2732    switch (weight_type) {
2733        case HTP_TYPE_Q4_0:   dequant_worker_fn = dequantize_tiled_worker_loop_q4_0; break;
2734        case HTP_TYPE_IQ4_NL: dequant_worker_fn = dequantize_tiled_worker_loop_iq4_nl; break;
2735        case HTP_TYPE_Q4_1:   dequant_worker_fn = dequantize_tiled_worker_loop_q4_1; break;
2736        case HTP_TYPE_MXFP4:  dequant_worker_fn = dequantize_tiled_worker_loop_mxfp4; break;
2737        case HTP_TYPE_Q8_0:   dequant_worker_fn = dequantize_tiled_worker_loop_q8_0; break;
2738        case HTP_TYPE_F16:    dequant_worker_fn = convert_f16_worker_loop; break;
2739        case HTP_TYPE_F32:    dequant_worker_fn = quantize_f32_worker_loop; break;
2740        default:
2741            return HTP_STATUS_NO_SUPPORT;
2742    }
2743
2744    const int n_k_tiles = k / HTP_MM_HMX_TILE_N_COLS;
2745    const struct fastdiv_values n_k_tiles_div = init_fastdiv_values(n_k_tiles);
2746
2747    const bool is_quant       = (weight_type != HTP_TYPE_F16 && weight_type != HTP_TYPE_F32);
2748    const size_t vtcm_budget  = ctx->vtcm_size;
2749
2750    const int m_chunk_n_rows  = kparams->m_chunk;
2751    const int n_chunk_n_cols  = kparams->n_chunk;
2752    const int pipeline        = kparams->pipeline;
2753    const int n_threads       = octx->n_threads;
2754    const int act_threads     = kparams->n_act_threads;
2755    const struct fastdiv_values * act_threads_div = &kparams->div_n_act_threads;
2756    const struct fastdiv_values * k_div           = &kparams->div_ne00_padded;
2757    const int tile_size       = kparams->tile_size;
2758    const int aligned_tile_size = kparams->aligned_tile_size;
2759
2760    const uint32_t dma_dst_stride  = is_quant ? aligned_tile_size : row_stride;
2761    const uint32_t dma_width_bytes = is_quant ? tile_size : row_stride;
2762
2763    struct htp_mm_hmx_vtcm_layout L;
2764    htp_mm_hmx_vtcm_layout_build(&L, HTP_MM_KERNEL_HMX_2D, weight_type, k, m_chunk_n_rows, n_chunk_n_cols, 1, false, pipeline, act_threads, aligned_tile_size);
2765
2766    if (L.total_bytes > vtcm_budget) {
2767        FARF(ERROR, "hmx-mm-nx-2d: VTCM overflow: used %zu budget %zu, m %d k %d mc %d nc %d",
2768             L.total_bytes, vtcm_budget, m, k, m_chunk_n_rows, n_chunk_n_cols);
2769        return HTP_STATUS_VTCM_TOO_SMALL;
2770    }
2771
2772    uint8_t * const base = (uint8_t *) ctx->vtcm_base;
2773    __fp16  *vtcm_weight_raw[2] = {
2774        VTCM_LAYOUT_PTR(__fp16, base, L.off_weight[0]),
2775        VTCM_LAYOUT_PTR_OPTIONAL(__fp16, base, L.off_weight[1], pipeline)
2776    };
2777
2778    __fp16  *vtcm_f16_act    = VTCM_LAYOUT_PTR(__fp16, base, L.off_act);
2779    float   *vtcm_f32_act    = VTCM_LAYOUT_PTR(float, base, L.off_act_f32);
2780    __fp16  *vtcm_output     = VTCM_LAYOUT_PTR(__fp16, base, L.off_dst[0]);
2781    void    *vtcm_scratch0   = VTCM_LAYOUT_PTR(void, base, L.off_scratch[0]);
2782    void    *vtcm_scratch1   = VTCM_LAYOUT_PTR_OPTIONAL(void, base, L.off_scratch[1], pipeline);
2783    void    *vtcm_scratch2   = VTCM_LAYOUT_PTR_OPTIONAL(void, base, L.off_dst[1], pipeline);
2784    __fp16  *vtcm_scales     = VTCM_LAYOUT_PTR(__fp16, base, L.off_scales);
2785
2786    hmx_init_column_scales(vtcm_scales, Q6_V_vsplat_R(0x3c00));  // scale: 1.0, bias: 0.0 in FP16
2787
2788    int m_start = 0;
2789    int m_rows  = m;
2790    if (octx->ctx->mdev.count > 1) {
2791        const bool can_split = htp_tensor_can_row_partition(octx->dsts[0], sizeof(float));
2792        const struct htp_tensor_mdev_range range = htp_tensor_mdev_partition((uint32_t) m, can_split ? 1 : 0, octx->ctx->mdev.idx, octx->ctx->mdev.count, &octx->ctx->mdev.count_div);
2793        m_start = (int) range.start;
2794        m_rows  = (int) range.count;
2795    }
2796
2797    if (m_rows == 0) {
2798        return HTP_STATUS_OK;
2799    }
2800
2801    FARF(HIGH, "hmx-mm-nx-2d: n_weights %u m %d (%d..%d) k %d wtype %d mc %d nc %d vtcm %zu/%zu",
2802         n_weights, m, m_start, m_start + m_rows, k, weight_type, m_chunk_n_rows, n_chunk_n_cols, L.total_bytes, vtcm_budget);
2803
2804    htp_trace_event_stop(tr, HTP_TRACE_EVT_INIT, 0);
2805
2806    const size_t mr_end = (size_t)(m_start + m_rows);
2807
2808    if (pipeline) {
2809        hmx_matmul_job_t job_slots[2];
2810
2811        for (size_t mr = (size_t) m_start; mr < mr_end; mr += m_chunk_n_rows) {
2812            const size_t n_rows = hex_smin(mr_end - mr, m_chunk_n_rows);
2813
2814            void *vtcm_weight_bufs[2] = { vtcm_scratch0, vtcm_scratch1 };
2815            void *vtcm_output_bufs[2] = { vtcm_output,   vtcm_scratch2 };
2816
2817            struct activation_transfer_params act_params = {
2818                .ctx = ctx,
2819                .dst = vtcm_f16_act,
2820                .src = activation + mr * act_stride,
2821                .n_rows = (int) n_rows,
2822                .k_block = k,
2823                .k_stride = act_stride,
2824                .n_threads = act_threads,
2825                .act_threads_div = act_threads_div,
2826                .k_div = k_div,
2827                .k_valid = k_valid,
2828                .vtcm_f32_act = vtcm_f32_act,
2829                .vtcm_f32_act_bytes = L.act_f32_bytes,
2830            };
2831            transfer_activation_chunk_threaded(&act_params);
2832
2833            for (uint32_t p = 0; p < n_weights; p++) {
2834                const struct htp_tensor * restrict src_w = octx->src[p];
2835                const struct htp_tensor * restrict dst   = octx->dsts[p];
2836                if (!src_w || !dst) continue;
2837
2838                const uint8_t * weight     = (const uint8_t *) src_w->data;
2839                float * dst_ptr            = (float *) dst->data;
2840                const size_t n             = src_w->ne[1];
2841                if (n == 0) continue;
2842                const size_t weight_stride = src_w->nb[1];
2843                const size_t dst_stride    = dst->nb[1] / sizeof(float);
2844                const int dst_cols         = (int) dst->ne[0];
2845                const int n_chunk_cnt      = hmx_ceil_div(n, n_chunk_n_cols);
2846
2847                const uint32_t dma_src_stride = is_quant ? tile_size : weight_stride;
2848
2849                const size_t   n_cols_A0 = hex_smin(n - 0 * n_chunk_n_cols, n_chunk_n_cols);
2850                const uint32_t height_A0 = is_quant ? (n_cols_A0 / 32) * n_k_tiles : n_cols_A0;
2851                dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_weight_raw[0], weight),
2852                               dma_dst_stride, dma_src_stride, dma_width_bytes, height_A0);
2853
2854                if (1 < n_chunk_cnt) {
2855                    const size_t   n_cols_A1 = hex_smin(n - 1 * n_chunk_n_cols, n_chunk_n_cols);
2856                    const uint32_t height_A1 = is_quant ? (n_cols_A1 / 32) * n_k_tiles : n_cols_A1;
2857                    dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_weight_raw[1], weight + n_chunk_n_cols * weight_stride),
2858                                   dma_dst_stride, dma_src_stride, dma_width_bytes, height_A1);
2859                }
2860
2861                for (int i = 0; i < n_chunk_cnt; ++i) {
2862                    const size_t nc    = i * n_chunk_n_cols;
2863                    const size_t nc_p2 = nc + 2 * n_chunk_n_cols;
2864
2865                    const size_t n_cols    = hex_smin(n - nc, n_chunk_n_cols);
2866                    const size_t n_cols_p2 = hex_smin(n - nc_p2, n_chunk_n_cols);
2867
2868                    void * curr_raw = dma_queue_pop(ctx->dma[0]).dst;
2869
2870                    dequantize_tiled_weight_chunk_to_fp16_tiles(
2871                        ctx, vtcm_weight_bufs[i % 2], curr_raw,
2872                        n_cols, k, row_stride, weight_type,
2873                        n_k_tiles, n_k_tiles_div, dequant_worker_fn, n_threads);
2874
2875                    if (i + 2 < n_chunk_cnt) {
2876                        const uint32_t height_p2 = is_quant ? (n_cols_p2 / 32) * n_k_tiles : n_cols_p2;
2877                        dma_queue_push(ctx->dma[0], dma_make_ptr(curr_raw, weight + nc_p2 * weight_stride),
2878                                       dma_dst_stride, dma_src_stride, dma_width_bytes, height_p2);
2879                    }
2880
2881                    hmx_matmul_job_init(&job_slots[i % 2], (__fp16 *) vtcm_output_bufs[i % 2],
2882                                        (__fp16 *) vtcm_f16_act, (__fp16 *) vtcm_weight_bufs[i % 2],
2883                                        vtcm_scales, hmx_ceil_div(n_rows, HTP_MM_HMX_TILE_N_ROWS),
2884                                        hmx_ceil_div(n_cols, HTP_MM_HMX_TILE_N_COLS), k / HTP_MM_HMX_TILE_N_ROWS);
2885                    hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_matmul_worker_fn, &job_slots[i % 2]));
2886
2887                    if (i > 0) {
2888                        hmx_queue_pop(ctx->hmx_queue);
2889                        const size_t nc_prev = (i - 1) * n_chunk_n_cols;
2890                        const size_t n_cols_prev = hex_smin(n - nc_prev, n_chunk_n_cols);
2891                        float *output_chunk = dst_ptr + (mr * dst_stride + nc_prev);
2892                        int chunk_dst_cols = dst_cols - (int)nc_prev;
2893                        if (chunk_dst_cols > 0) {
2894                            transfer_output_chunk_threaded(ctx, output_chunk, NULL, vtcm_output_bufs[(i - 1) % 2], n_rows, n_cols_prev, dst_stride, 0, chunk_dst_cols, n_threads);
2895                        }
2896                    }
2897                }
2898
2899                hmx_queue_pop(ctx->hmx_queue);
2900                const size_t nc_last = (n_chunk_cnt - 1) * n_chunk_n_cols;
2901                const size_t n_cols_last = hex_smin(n - nc_last, n_chunk_n_cols);
2902                float *output_chunk = dst_ptr + (mr * dst_stride + nc_last);
2903                int chunk_dst_cols = dst_cols - (int)nc_last;
2904                if (chunk_dst_cols > 0) {
2905                    transfer_output_chunk_threaded(ctx, output_chunk, NULL, vtcm_output_bufs[(n_chunk_cnt - 1) % 2], n_rows, n_cols_last, dst_stride, 0, chunk_dst_cols, n_threads);
2906                }
2907            }
2908        }
2909    } else {
2910        hmx_matmul_job_t job;
2911        for (size_t mr = (size_t) m_start; mr < mr_end; mr += m_chunk_n_rows) {
2912            const size_t n_rows = hex_smin(mr_end - mr, m_chunk_n_rows);
2913
2914            struct activation_transfer_params act_params = {
2915                .ctx = ctx,
2916                .dst = vtcm_f16_act,
2917                .src = activation + mr * act_stride,
2918                .n_rows = (int) n_rows,
2919                .k_block = k,
2920                .k_stride = act_stride,
2921                .n_threads = act_threads,
2922                .act_threads_div = act_threads_div,
2923                .k_div = k_div,
2924                .k_valid = k_valid,
2925                .vtcm_f32_act = vtcm_f32_act,
2926                .vtcm_f32_act_bytes = L.act_f32_bytes,
2927            };
2928            transfer_activation_chunk_threaded(&act_params);
2929
2930            for (uint32_t p = 0; p < n_weights; p++) {
2931                const struct htp_tensor * restrict src_w = octx->src[p];
2932                const struct htp_tensor * restrict dst   = octx->dsts[p];
2933                if (!src_w || !dst) continue;
2934
2935                const uint8_t * weight     = (const uint8_t *) src_w->data;
2936                float * dst_ptr            = (float *) dst->data;
2937                const size_t n             = src_w->ne[1];
2938                if (n == 0) continue;
2939                const size_t weight_stride = src_w->nb[1];
2940                const size_t dst_stride    = dst->nb[1] / sizeof(float);
2941                const int dst_cols         = (int) dst->ne[0];
2942
2943                const uint32_t dma_src_stride = is_quant ? tile_size : weight_stride;
2944
2945                if (n > 0) {
2946                    const size_t n_cols = hex_smin(n, n_chunk_n_cols);
2947                    const uint32_t height = is_quant ? (n_cols / 32) * n_k_tiles : n_cols;
2948                    dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_weight_raw[0], weight), dma_dst_stride, dma_src_stride, dma_width_bytes, height);
2949                }
2950
2951                for (size_t nc = 0; nc < n; nc += n_chunk_n_cols) {
2952                    const size_t n_cols = hex_smin(n - nc, n_chunk_n_cols);
2953                    const size_t n_row_tiles = hmx_ceil_div(n_rows, HTP_MM_HMX_TILE_N_ROWS);
2954                    const size_t n_col_tiles = hmx_ceil_div(n_cols, HTP_MM_HMX_TILE_N_COLS);
2955
2956                    void * curr_raw = dma_queue_pop(ctx->dma[0]).dst;
2957
2958                    dequantize_tiled_weight_chunk_to_fp16_tiles(
2959                        ctx, vtcm_scratch0, curr_raw,
2960                        n_cols, k, row_stride, weight_type,
2961                        n_k_tiles, n_k_tiles_div, dequant_worker_fn, n_threads);
2962
2963                    const size_t nc_next = nc + n_chunk_n_cols;
2964                    if (nc_next < n) {
2965                        const size_t n_cols_next = hex_smin(n - nc_next, n_chunk_n_cols);
2966                        const uint32_t height_next = is_quant ? (n_cols_next / 32) * n_k_tiles : n_cols_next;
2967                        dma_queue_push(ctx->dma[0], dma_make_ptr(curr_raw, weight + nc_next * weight_stride), dma_dst_stride, dma_src_stride, dma_width_bytes, height_next);
2968                    }
2969
2970                    hmx_matmul_job_init(&job, vtcm_output, vtcm_f16_act, vtcm_scratch0, vtcm_scales, n_row_tiles, n_col_tiles, k / HTP_MM_HMX_TILE_N_ROWS);
2971                    hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_matmul_worker_fn, &job));
2972                    hmx_queue_pop(ctx->hmx_queue);
2973
2974                    float *output_chunk = dst_ptr + (mr * dst_stride + nc);
2975                    int chunk_dst_cols = dst_cols - (int)nc;
2976                    if (chunk_dst_cols > 0) {
2977                        transfer_output_chunk_threaded(ctx, output_chunk, NULL, vtcm_output, n_rows, n_cols, dst_stride, 0, chunk_dst_cols, n_threads);
2978                    }
2979                }
2980            }
2981        }
2982    }
2983
2984    return HTP_STATUS_OK;
2985}
2986
2987static inline const __fp16 *hmx_mm_weight_batch_ptr(const hmx_mm_f16_f32_batched_params_t *params,
2988                                                        int dst_b2, int dst_b3) {
2989    const size_t b2_idx = (params->r2 <= 1) ? (size_t) dst_b2 : (size_t) fastdiv((uint32_t) dst_b2, &params->div_r2);
2990    const size_t b3_idx = (params->r3 <= 1) ? (size_t) dst_b3 : (size_t) fastdiv((uint32_t) dst_b3, &params->div_r3);
2991    return (const __fp16 *) ((const uint8_t *) params->weight +
2992                             b2_idx * params->src0_nb2 +
2993                             b3_idx * params->src0_nb3);
2994}
2995
2996static inline const float *hmx_mm_activation_batch_ptr(const hmx_mm_f16_f32_batched_params_t *params,
2997                                                           int dst_b2, int dst_b3) {
2998    return (const float *) ((const uint8_t *) params->activation +
2999                            (size_t) dst_b2 * params->src1_nb2 +
3000                            (size_t) dst_b3 * params->src1_nb3);
3001}
3002
3003static inline float *hmx_mm_dst_batch_ptr(const hmx_mm_f16_f32_batched_params_t *params,
3004                                              int dst_b2, int dst_b3) {
3005    return (float *) ((uint8_t *) params->dst +
3006                      (size_t) dst_b2 * params->dst_nb2 +
3007                      (size_t) dst_b3 * params->dst_nb3);
3008}
3009
3010static inline const float *hmx_mm_src2_batch_ptr(const hmx_mm_f16_f32_batched_params_t *params,
3011                                               int src2_b2, int src2_b3) {
3012    return params->src2 ? (const float *) ((const uint8_t *) params->src2 +
3013                      (size_t) src2_b2 * params->src2_nb2 +
3014                      (size_t) src2_b3 * params->src2_nb3) : NULL;
3015}
3016
3017static int hmx_mm_f16_f32_batched_simple(struct htp_context *ctx,
3018                                                        const hmx_mm_f16_f32_batched_params_t *params,
3019                                                        int m_chunk, int n_chunk, int pipeline, int n_threads, int act_threads, int vtcm_size,
3020                                                        const struct fastdiv_values * act_threads_div, const struct fastdiv_values * k_div) {
3021    int ret = 0;
3022    for (int b3 = 0; b3 < params->ne13 && ret == 0; ++b3) {
3023        for (int b2 = 0; b2 < params->ne12 && ret == 0; ++b2) {
3024            ret = hmx_mm_2d_f32(ctx, hmx_mm_dst_batch_ptr(params, b2, b3),
3025                                           hmx_mm_src2_batch_ptr(params, b2, b3),
3026                                           hmx_mm_activation_batch_ptr(params, b2, b3),
3027                                           (const uint8_t *)hmx_mm_weight_batch_ptr(params, b2, b3),
3028                                           params->m, params->k, params->n,
3029                                           params->act_stride, params->weight_stride * (int)sizeof(__fp16),
3030                                           HTP_TYPE_F16, params->k, params->dst_stride, params->src2_stride, params->n,
3031                                           m_chunk, n_chunk, pipeline, n_threads, act_threads,
3032                                           act_threads_div, k_div, 0, 0, vtcm_size);
3033        }
3034    }
3035    return ret;
3036}
3037
3038static int hmx_mm_f16_f32_batched(struct htp_context *ctx, const hmx_mm_f16_f32_batched_params_t *params,
3039                               int m_chunk, int n_chunk, int pipeline, int n_threads, int act_threads,
3040                               const struct fastdiv_values * act_threads_div,
3041                               const struct fastdiv_values * k_div,
3042                               int vtcm_size) {
3043    if (params->act_stride < params->k || params->weight_stride < params->k || params->dst_stride < params->n) { return -1; }
3044    if (params->ne02 <= 0 || params->ne03 <= 0 || params->ne12 <= 0 || params->ne13 <= 0) { return -1; }
3045    if (params->ne12 % params->ne02 != 0 || params->ne13 % params->ne03 != 0) { return -1; }
3046    if (params->k % 32 != 0 || params->n % 32 != 0) { return -1; }
3047    if (!hex_is_aligned(params->dst, VLEN) || !hex_is_aligned(params->activation, VLEN)) { return -1; }
3048
3049    const int group_size = params->r2;
3050    const size_t vtcm_budget  = ctx->vtcm_size;
3051
3052    // Check if the precomputed parameters are grouped or simple.
3053    // If simple, or if group_size <= 1, we use simple fallback loop.
3054    // Grouped path is only valid if group_size > 1 and it fits within VTCM budget.
3055    bool run_grouped = (group_size > 1 && (size_t)vtcm_size <= vtcm_budget);
3056    if (!run_grouped) {
3057        return hmx_mm_f16_f32_batched_simple(ctx, params, m_chunk, n_chunk, pipeline, n_threads, act_threads, vtcm_size, act_threads_div, k_div);
3058    }
3059
3060    struct htp_thread_trace * tr = &ctx->trace[0];
3061    htp_trace_event_start(tr, HTP_TRACE_EVT_INIT, 0);
3062
3063    const size_t vec_dot_size = params->k * sizeof(__fp16);
3064
3065    const bool use_dma_activation = (params->act_stride > params->k);
3066    const size_t f32_scratch_size = use_dma_activation
3067        ? hex_align_up((size_t)act_threads * HTP_MM_DMA_ACT_MULTIPLIER * (size_t) params->k * sizeof(float), HTP_MM_HMX_TILE_SIZE) : 0;
3068
3069    size_t m_chunk_n_rows = m_chunk;
3070    size_t n_chunk_n_cols = n_chunk;
3071    size_t vtcm_used = vtcm_size;
3072
3073    struct htp_mm_hmx_vtcm_layout L;
3074    htp_mm_hmx_vtcm_layout_build(&L, HTP_MM_KERNEL_HMX_F16_BATCHED, HTP_TYPE_F16, params->k, m_chunk_n_rows, n_chunk_n_cols, group_size, use_dma_activation, false, act_threads, 0);
3075
3076    if (L.total_bytes > vtcm_budget) {
3077        FARF(HIGH, "%s: grouped layout overflowed VTCM, falling back to simple batched loop", __func__);
3078        htp_trace_event_stop(tr, HTP_TRACE_EVT_INIT, 0);
3079        return hmx_mm_f16_f32_batched_simple(ctx, params, m_chunk, n_chunk, pipeline, n_threads, act_threads, vtcm_size, act_threads_div, k_div);
3080    }
3081
3082    uint8_t * const base = (uint8_t *) ctx->vtcm_base;
3083    __fp16  *vtcm_weight     = VTCM_LAYOUT_PTR(__fp16, base, L.off_weight[0]);
3084    __fp16  *vtcm_f16_act    = VTCM_LAYOUT_PTR(__fp16, base, L.off_act);
3085    __fp16  *vtcm_output     = VTCM_LAYOUT_PTR(__fp16, base, L.off_dst[0]);
3086    void    *vtcm_scratch0   = VTCM_LAYOUT_PTR(void, base, L.off_scratch[0]);
3087    void    *vtcm_scratch1   = VTCM_LAYOUT_PTR(void, base, L.off_scratch[1]);
3088    __fp16  *vtcm_scales     = VTCM_LAYOUT_PTR(__fp16, base, L.off_scales);
3089    float   *vtcm_f32_act    = VTCM_LAYOUT_PTR_OPTIONAL(float, base, L.off_act_f32, use_dma_activation);
3090
3091    hmx_init_column_scales(vtcm_scales, Q6_V_vsplat_R(0x3c00));  // scale: 1.0, bias: 0.0 in FP16
3092
3093    FARF(HIGH, "%s: grouped path m=%d k=%d n=%d group=%d streams=%d mc=%zu nc=%zu vtcm=%zu/%zu",
3094            __func__, params->m, params->k, params->n, group_size, params->ne13,
3095            m_chunk_n_rows, n_chunk_n_cols,
3096            L.total_bytes, vtcm_budget);
3097
3098    const size_t fp16_row_bytes   = (size_t) params->k * sizeof(__fp16);
3099    const size_t weight_row_bytes = (size_t) params->weight_stride * sizeof(__fp16);
3100
3101    htp_trace_event_stop(tr, HTP_TRACE_EVT_INIT, 0);
3102
3103    hmx_matmul_job_t job;
3104
3105    for (int b3 = 0; b3 < params->ne13; ++b3) {
3106        for (int b2_base = 0; b2_base < params->ne12; b2_base += group_size) {
3107            const __fp16 *weight_group = hmx_mm_weight_batch_ptr(params, b2_base, b3);
3108
3109            for (size_t mr = 0; mr < (size_t) params->m; mr += m_chunk_n_rows) {
3110                const size_t n_rows = hex_smin((size_t) params->m - mr, m_chunk_n_rows);
3111                const size_t n_row_tiles = hmx_ceil_div((int) n_rows, HTP_MM_HMX_TILE_N_ROWS);
3112
3113                // Pre-load activations for all heads in the group (once per m_chunk).
3114                // When the source is strided (permuted Q), use 2D DMA to gather
3115                // contiguous rows into a VTCM scratch buffer first, then HVX
3116                // converts from the contiguous VTCM buffer.  This avoids L2 cache
3117                // thrashing from HVX loads at large strides.
3118                for (int g = 0; g < group_size; ++g) {
3119                    const float *activation_chunk = hmx_mm_activation_batch_ptr(params, b2_base + g, b3) + mr * params->act_stride;
3120                    __fp16 *vtcm_act_g = vtcm_f16_act + (size_t) g * L.act_head_stride;
3121                    struct activation_transfer_params act_params = {
3122                        .ctx = ctx,
3123                        .dst = vtcm_act_g,
3124                        .src = activation_chunk,
3125                        .n_rows = (int) n_rows,
3126                        .k_block = params->k,
3127                        .k_stride = params->act_stride,
3128                        .n_threads = act_threads,
3129                        .act_threads_div = act_threads_div,
3130                        .k_div = k_div,
3131                        .k_valid = params->k,
3132                        .vtcm_f32_act = vtcm_f32_act,
3133                        .vtcm_f32_act_bytes = L.act_f32_bytes,
3134                    };
3135                    transfer_activation_chunk_threaded(&act_params);
3136                }
3137
3138                // Prologue: Push A0 and A1 (if exists)
3139                {
3140                    const size_t n_cols_first = hex_smin((size_t) params->n, n_chunk_n_cols);
3141                    dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_scratch0, weight_group),
3142                                      fp16_row_bytes, weight_row_bytes, fp16_row_bytes, n_cols_first);
3143                }
3144                if (n_chunk_n_cols < (size_t) params->n) {
3145                    const size_t n_cols_second = hex_smin((size_t) params->n - n_chunk_n_cols, n_chunk_n_cols);
3146                    dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_scratch1, weight_group + params->weight_stride),
3147                                      fp16_row_bytes, weight_row_bytes, fp16_row_bytes, n_cols_second);
3148                }
3149
3150                for (size_t nc = 0; nc < (size_t) params->n; nc += n_chunk_n_cols) {
3151                    const size_t n_cols      = hex_smin((size_t) params->n - nc, n_chunk_n_cols);
3152                    const size_t n_col_tiles = hmx_ceil_div((int) n_cols, HTP_MM_HMX_TILE_N_COLS);
3153
3154                    {
3155                        void * curr_raw = dma_queue_pop(ctx->dma[0]).dst;
3156
3157                        hmx_interleave_rows_to_tiles(vtcm_weight, (const __fp16 *) curr_raw, n_cols, params->k, params->k, 0, n_cols);
3158
3159                        const size_t nc_next = nc + n_chunk_n_cols * 2;
3160                        if (nc_next < (size_t) params->n) {
3161                            const size_t n_cols_next = hex_smin((size_t) params->n - nc_next, n_chunk_n_cols);
3162                            const __fp16 *next_weight_chunk = weight_group + nc_next * params->weight_stride;
3163
3164                            dma_queue_push(ctx->dma[0], dma_make_ptr(curr_raw, next_weight_chunk),
3165                                              fp16_row_bytes, weight_row_bytes, fp16_row_bytes, n_cols_next);
3166                        }
3167                    }
3168
3169                    // Reuse the interleaved weight for every q_head in this GQA group
3170                    for (int g = 0; g < group_size; ++g) {
3171                        {
3172                            const __fp16 * vtcm_act_g = vtcm_f16_act + (size_t) g * L.act_head_stride;
3173                            hmx_matmul_job_init(&job, vtcm_output, vtcm_act_g, vtcm_weight, vtcm_scales, n_row_tiles, n_col_tiles, params->k / 32);
3174                            hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_matmul_worker_fn, &job));
3175                            hmx_queue_pop(ctx->hmx_queue);
3176                        }
3177
3178                        {
3179                            float *output = hmx_mm_dst_batch_ptr(params, b2_base + g, b3) + mr * params->dst_stride + nc;
3180                            const float *src2_chunk = params->src2 ? (hmx_mm_src2_batch_ptr(params, b2_base + g, b3) + mr * params->src2_stride + nc) : NULL;
3181                            int chunk_dst_cols = params->n - (int)nc;
3182                            if (chunk_dst_cols > 0) {
3183                                transfer_output_chunk_threaded(ctx, output, src2_chunk, vtcm_output, (int) n_rows, (int) n_cols,
3184                                                               params->dst_stride, params->src2_stride, chunk_dst_cols, n_threads);
3185                            }
3186                        }
3187                    }
3188                }
3189            }
3190        }
3191    }
3192
3193    return 0;
3194}
3195
3196static void transfer_activation_chunk_gathered_threaded(
3197            struct htp_context *ctx,
3198            __fp16 *dst,
3199            const float *src,
3200            int start_row,
3201            int n_rows,
3202            int k_block,
3203            const struct mmid_row_mapping *matrix_rows,
3204            int cur_a,
3205            int mapping_stride,
3206            int ne11,
3207            size_t nb11,
3208            size_t nb12,
3209            int cne1,
3210            int n_threads,
3211            int k_valid) {
3212    if (n_rows <= 0) return;
3213    int chunks_per_thread = hmx_ceil_div(n_rows, n_threads);
3214    chunks_per_thread = hex_align_up(chunks_per_thread, 2);
3215
3216    int actual_threads = hmx_ceil_div(n_rows, chunks_per_thread);
3217
3218    activation_transfer_gathered_task_state_t state = {
3219        .dst               = dst,
3220        .src               = src,
3221        .n_tasks           = actual_threads,
3222        .n_tot_chunks      = n_rows,
3223        .n_chunks_per_task = chunks_per_thread,
3224        .k_block           = k_block,
3225        .matrix_rows       = matrix_rows,
3226        .cur_a             = cur_a,
3227        .mapping_stride    = mapping_stride,
3228        .ne11              = ne11,
3229        .ne11_div          = ne11 > 1 ? init_fastdiv_values(ne11) : (struct fastdiv_values){0, 0},
3230        .nb11              = nb11,
3231        .nb12              = nb12,
3232        .start_row         = start_row,
3233        .cne1              = cne1,
3234        .k_valid           = k_valid,
3235        .traces            = ctx->trace,
3236    };
3237
3238    worker_callback_t worker_fn = ne11 == 1 ? transfer_activation_chunk_gathered_worker_flat_fn :
3239                                              transfer_activation_chunk_gathered_worker_fn;
3240
3241    if (actual_threads <= 1) {
3242        worker_fn(1, 0, &state);
3243    } else {
3244        worker_pool_run_func(ctx->worker_pool, worker_fn, &state, actual_threads);
3245    }
3246}
3247
3248static void transfer_output_chunk_scattered_threaded(
3249            struct htp_context *ctx,
3250            float *dst,
3251            const __fp16 *vtcm_src,
3252            int start_row,
3253            int n_rows,
3254            int n_cols,
3255            const struct mmid_row_mapping *matrix_rows,
3256            int cur_a,
3257            int mapping_stride,
3258            size_t dst_nb1,
3259            size_t dst_nb2,
3260            int cne1,
3261            int n_threads) {
3262    if (n_rows <= 0) return;
3263    int chunks_per_thread = hmx_ceil_div(n_rows, n_threads);
3264    chunks_per_thread = hex_align_up(chunks_per_thread, 2);
3265
3266    int actual_threads = hmx_ceil_div(n_rows, chunks_per_thread);
3267
3268    output_transfer_scattered_task_state_t state = {
3269        .vtcm_src          = vtcm_src,
3270        .dst               = dst,
3271        .n_tasks           = actual_threads,
3272        .n_tot_chunks      = n_rows,
3273        .n_chunks_per_task = chunks_per_thread,
3274        .n_cols            = n_cols,
3275        .matrix_rows       = matrix_rows,
3276        .cur_a             = cur_a,
3277        .mapping_stride    = mapping_stride,
3278        .dst_nb1           = dst_nb1,
3279        .dst_nb2           = dst_nb2,
3280        .start_row         = start_row,
3281        .cne1              = cne1,
3282        .traces            = ctx->trace,
3283    };
3284
3285    if (actual_threads <= 1) {
3286        transfer_output_chunk_scattered_worker_fn(1, 0, &state);
3287    } else {
3288        worker_pool_run_func(ctx->worker_pool, transfer_output_chunk_scattered_worker_fn, &state, actual_threads);
3289    }
3290}
3291
3292static int hmx_mm_id_2d_f32(struct htp_context *ctx,
3293                                         float *restrict dst,
3294                                         const float *activation,
3295                                         const uint8_t *weight,
3296                                         int m, int k, int n,
3297                                         int k_valid,
3298                                         int ne11,
3299                                         size_t act_nb1, size_t act_nb2,
3300                                         size_t dst_nb1, size_t dst_nb2,
3301                                         int weight_stride,
3302                                         int weight_type,
3303                                         const struct mmid_row_mapping *matrix_rows,
3304                                         int cur_a,
3305                                         int mapping_stride,
3306                                         int m_start,
3307                                         int m_end,
3308                                         int n_threads) {
3309    struct htp_thread_trace * tr = &ctx->trace[0];
3310    htp_trace_event_start(tr, HTP_TRACE_EVT_INIT, 0);
3311
3312    const int cne1 = m;
3313    const int m_padded = hex_align_up(m, 32);
3314
3315    if (k % 32 != 0 || n % 32 != 0) { return -1; }
3316    if (!hex_is_aligned(dst, VLEN) || !hex_is_aligned(activation, VLEN)) { return -1; }
3317
3318    size_t row_stride = htp_mm_get_tiled_row_stride(weight_type, k);
3319    if (row_stride == 0) {
3320        return -1;
3321    }
3322
3323    worker_callback_t dequant_worker_fn = NULL;
3324    switch (weight_type) {
3325        case HTP_TYPE_Q4_0:   dequant_worker_fn = dequantize_tiled_worker_loop_q4_0; break;
3326        case HTP_TYPE_IQ4_NL: dequant_worker_fn = dequantize_tiled_worker_loop_iq4_nl; break;
3327        case HTP_TYPE_Q4_1:   dequant_worker_fn = dequantize_tiled_worker_loop_q4_1; break;
3328        case HTP_TYPE_MXFP4:  dequant_worker_fn = dequantize_tiled_worker_loop_mxfp4; break;
3329        case HTP_TYPE_Q8_0:   dequant_worker_fn = dequantize_tiled_worker_loop_q8_0; break;
3330        case HTP_TYPE_F16:    dequant_worker_fn = convert_f16_worker_loop; break;
3331        case HTP_TYPE_F32:    dequant_worker_fn = quantize_f32_worker_loop; break;
3332        default:
3333            return -1;
3334    }
3335
3336    const int n_k_tiles = k / HTP_MM_HMX_TILE_N_COLS;
3337    const struct fastdiv_values n_k_tiles_div = init_fastdiv_values(n_k_tiles);
3338
3339    const bool is_quant   = (weight_type != HTP_TYPE_F16 && weight_type != HTP_TYPE_F32);
3340
3341    const size_t vec_dot_size = k * sizeof(__fp16);
3342    const size_t vtcm_budget  = ctx->vtcm_size;
3343    size_t vtcm_used = 0;
3344
3345    int tile_size = htp_mm_get_weight_tile_size(weight_type);
3346    int aligned_tile_size = htp_mm_get_weight_aligned_tile_size(weight_type);
3347
3348    const uint32_t dma_dst_stride  = is_quant ? aligned_tile_size : row_stride;
3349    const uint32_t dma_src_stride  = is_quant ? tile_size : weight_stride;
3350    const uint32_t dma_width_bytes = is_quant ? tile_size : row_stride;
3351
3352    const size_t qweight_row_stride = is_quant ? (size_t)(n_k_tiles * aligned_tile_size) / 32 : 0;
3353    const size_t weight_row_stride = is_quant ? qweight_row_stride : row_stride;
3354
3355    size_t size_per_n = 0, size_per_m = 0, size_per_mn = 0;
3356    htp_mm_hmx_get_2d_chunk_costs(weight_type, k, /*pipeline=*/false, aligned_tile_size,
3357                                  &size_per_n, &size_per_m, &size_per_mn);
3358
3359    const size_t overhead = htp_mm_hmx_get_2d_overhead(/*pipeline=*/false, /*is_matmul_id=*/true);
3360    size_t m_chunk_n_rows = 0, n_chunk_n_cols = 0;
3361    if (htp_mm_hmx_compute_chunks(vtcm_budget, overhead, size_per_n, size_per_m, size_per_mn,
3362                           m_padded, n,
3363                           /*m_block_cost=*/(size_t) n * HTP_MM_HMX_COST_W_DEQUANT,
3364                           /*n_block_cost=*/(size_t) m_padded * HTP_MM_HMX_COST_A_CONVERT, &m_chunk_n_rows, &n_chunk_n_cols, &vtcm_used)) {
3365        FARF(ERROR, "hmx-mm-id-2d: VTCM too small : m %d k %d n %d budget %zu", m_padded, k, n, vtcm_budget);
3366        return -1;
3367    }
3368
3369    const size_t weight_area_size = hex_align_up(n_chunk_n_cols * weight_row_stride, HTP_MM_HMX_TILE_SIZE);
3370    const size_t act_area_size    = hex_align_up(m_chunk_n_rows * vec_dot_size, HTP_MM_HMX_TILE_SIZE);
3371    const size_t output_area_size = hex_align_up(m_chunk_n_rows * n_chunk_n_cols * sizeof(__fp16), HTP_MM_HMX_TILE_SIZE);
3372
3373    size_t scratch0_size = hex_align_up(n_chunk_n_cols * vec_dot_size, HTP_MM_HMX_TILE_SIZE);
3374
3375    uint8_t *vtcm_ptr      = (uint8_t *) ctx->vtcm_base;
3376    __fp16  *vtcm_weight   = weight_area_size ? (__fp16 *) vtcm_seq_alloc(&vtcm_ptr, weight_area_size) : NULL;
3377    __fp16  *vtcm_f16_act  = (__fp16 *) vtcm_seq_alloc(&vtcm_ptr, act_area_size);
3378    __fp16  *vtcm_output   = (__fp16 *) vtcm_seq_alloc(&vtcm_ptr, output_area_size);
3379    void    *vtcm_scratch0 = vtcm_seq_alloc(&vtcm_ptr, scratch0_size);
3380    __fp16  *vtcm_scales   = (__fp16 *) vtcm_seq_alloc(&vtcm_ptr, 256);
3381
3382    vtcm_used = vtcm_ptr - (uint8_t *) ctx->vtcm_base;
3383    if (vtcm_used > vtcm_budget) {
3384        FARF(ERROR, "hmx-mm-id-2d: VTCM overflow: used %zu budget %zu", vtcm_used, vtcm_budget);
3385        return -1;
3386    }
3387
3388    hmx_init_column_scales(vtcm_scales, Q6_V_vsplat_R(0x3c00));
3389
3390    htp_trace_event_stop(tr, HTP_TRACE_EVT_INIT, 0);
3391
3392    hmx_matmul_job_t job;
3393
3394    for (size_t mr = (size_t) m_start; mr < (size_t) m_end; mr += m_chunk_n_rows) {
3395        const size_t n_rows = hex_smin((size_t) m_end - mr, m_chunk_n_rows);
3396        const size_t n_row_tiles = hmx_ceil_div(n_rows, HTP_MM_HMX_TILE_N_ROWS);
3397
3398        transfer_activation_chunk_gathered_threaded(
3399            ctx, vtcm_f16_act, activation, (int) mr, (int) n_rows, k,
3400            matrix_rows, cur_a, mapping_stride, ne11, act_nb1, act_nb2, cne1, n_threads, k_valid);
3401
3402        // A0: Pre-fetch the first weight chunk (nc = 0)
3403        if (n > 0) {
3404            const size_t n_cols = hex_smin((size_t) n, n_chunk_n_cols);
3405            const uint32_t height = is_quant ? (n_cols / 32) * n_k_tiles : n_cols;
3406            dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_weight, weight),
3407                           dma_dst_stride, dma_src_stride, dma_width_bytes, height);
3408        }
3409
3410        for (size_t nc = 0; nc < (size_t) n; nc += n_chunk_n_cols) {
3411            const size_t n_cols = hex_smin((size_t) n - nc, n_chunk_n_cols);
3412            const size_t n_col_tiles = hmx_ceil_div(n_cols, HTP_MM_HMX_TILE_N_COLS);
3413
3414            // A: Wait for weight DMA
3415            void * curr_raw = dma_queue_pop(ctx->dma[0]).dst;
3416
3417            // B: Weight Dequantize (Threaded)
3418            dequantize_tiled_weight_chunk_to_fp16_tiles(
3419                ctx, vtcm_scratch0, curr_raw,
3420                n_cols, k, row_stride, weight_type,
3421                n_k_tiles, n_k_tiles_div, dequant_worker_fn, n_threads
3422            );
3423
3424            // Start weight DMA for the next chunk early
3425            const size_t nc_next = nc + n_chunk_n_cols;
3426            if (nc_next < (size_t) n) {
3427                const size_t n_cols_next = hex_smin((size_t) n - nc_next, n_chunk_n_cols);
3428                const uint32_t height_next = is_quant ? (n_cols_next / 32) * n_k_tiles : n_cols_next;
3429                dma_queue_push(ctx->dma[0], dma_make_ptr(curr_raw, weight + nc_next * weight_stride),
3430                               dma_dst_stride, dma_src_stride, dma_width_bytes, height_next);
3431            }
3432
3433            // C: HMX Compute (Queue-based)
3434            hmx_matmul_job_init(&job, vtcm_output, vtcm_f16_act, vtcm_scratch0, vtcm_scales, n_row_tiles, n_col_tiles, k / HTP_MM_HMX_TILE_N_ROWS);
3435            hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_matmul_worker_fn, &job));
3436            hmx_queue_pop(ctx->hmx_queue);
3437
3438            // D: Output Store
3439            transfer_output_chunk_scattered_threaded(
3440                ctx, dst + nc, vtcm_output, (int) mr, (int) n_rows, (int) n_cols,
3441                matrix_rows, cur_a, mapping_stride, dst_nb1, dst_nb2, cne1, n_threads);
3442        }
3443    }
3444
3445    return 0;
3446}
3447
3448// --- Dispatchers and Public Entry Points ---
3449
3450static int hmx_mm_op_matmul(struct htp_ops_context * octx, const struct htp_mm_kernel_params * kparams) {
3451    htp_matmul_tensors_preamble;
3452
3453    int k = (int) src0->ne[0];
3454    int n = (int) src0->ne[1];
3455    const int m_total    = (int) src1->ne[1];
3456    const int act_stride = (int)(src1->nb[1] / sizeof(float));
3457    const int wgt_stride = (int)(src0->nb[1] / sizeof(__fp16));
3458
3459    int m_start = 0;
3460    int m_rows  = m_total;
3461    if (octx->ctx->mdev.count > 1) {
3462        const bool can_split = htp_tensor_can_row_partition(dst, sizeof(float));
3463        const struct htp_tensor_mdev_range range = htp_tensor_mdev_partition((uint32_t) m_total, can_split ? 1 : 0, octx->ctx->mdev.idx, octx->ctx->mdev.count, &octx->ctx->mdev.count_div);
3464        m_start = (int) range.start;
3465        m_rows  = (int) range.count;
3466    }
3467
3468    if (m_rows == 0) {
3469        return HTP_STATUS_OK;
3470    }
3471
3472    const float * src2_ptr = NULL;
3473    uint32_t src2_stride = 0;
3474    size_t src2_nb2 = 0;
3475    size_t src2_nb3 = 0;
3476    if (src2) {
3477        src2_stride = (src2->ne[1] == 1) ? 0 : (uint32_t) (src2->nb[1] / sizeof(float));
3478        src2_ptr = (const float *) src2->data + m_start * src2_stride;
3479        src2_nb2 = (src2->ne[2] == 1) ? 0 : src2->nb[2];
3480        src2_nb3 = (src2->ne[3] == 1) ? 0 : src2->nb[3];
3481    }
3482
3483    const int dst_stride = (int)(dst->nb[1] / sizeof(float));
3484    float       * dst_ptr = (float *)       dst->data  + m_start * dst_stride;
3485    const float * act_ptr = (const float *) src1->data + m_start * act_stride;
3486
3487    int ret = -1;
3488    const int n_threads = kparams->n_threads;
3489    if (kparams->kernel_type == HTP_MM_KERNEL_HMX_F16_BATCHED) {
3490        hmx_mm_f16_f32_batched_params_t batch_params = {
3491            .dst             = dst_ptr,
3492            .src2            = src2_ptr,
3493            .activation      = act_ptr,
3494            .weight          = (const __fp16 *) src0->data,
3495            .m               = m_rows,
3496            .k               = k,
3497            .n               = n,
3498            .act_stride      = act_stride,
3499            .weight_stride   = wgt_stride,
3500            .dst_stride      = dst_stride,
3501            .src2_stride     = src2_stride,
3502            .ne02            = ne02,
3503            .ne03            = ne03,
3504            .ne12            = ne12,
3505            .ne13            = ne13,
3506            .src0_nb2        = src0->nb[2],
3507            .src0_nb3        = src0->nb[3],
3508            .src1_nb2        = src1->nb[2],
3509            .src1_nb3        = src1->nb[3],
3510            .dst_nb2         = dst->nb[2],
3511            .dst_nb3         = dst->nb[3],
3512            .src2_nb2        = src2_nb2,
3513            .src2_nb3        = src2_nb3,
3514            .r2              = (ne02 > 0) ? (ne12 / ne02) : 1,
3515            .r3              = (ne03 > 0) ? (ne13 / ne03) : 1,
3516            .div_r2          = kparams->div_r2,
3517            .div_r3          = kparams->div_r3,
3518        };
3519        ret = hmx_mm_f16_f32_batched(octx->ctx, &batch_params,
3520                                     kparams->m_chunk, kparams->n_chunk,
3521                                     kparams->pipeline, n_threads,
3522                                     kparams->n_act_threads,
3523                                     &kparams->div_n_act_threads,
3524                                     &kparams->div_ne00_padded,
3525                                     kparams->vtcm_size);
3526    } else {
3527        ret = hmx_mm_2d_f32(
3528            octx->ctx, dst_ptr, src2_ptr, act_ptr, (const uint8_t *) src0->data,
3529            m_rows, k, n, act_stride, (int) src0->nb[1], (int) src0->type, (int) src1->ne[0],
3530            dst_stride, src2_stride, (int)dst->ne[0],
3531            kparams->m_chunk, kparams->n_chunk, kparams->pipeline, n_threads,
3532            kparams->n_act_threads,
3533            &kparams->div_n_act_threads,
3534            &kparams->div_ne00_padded,
3535            kparams->tile_size, kparams->aligned_tile_size, kparams->vtcm_size
3536        );
3537    }
3538
3539    if (ret != 0) {
3540        FARF(ERROR, "HMX matmul failed (ret=%d)\n", ret);
3541        return HTP_STATUS_INTERNAL_ERR;
3542    }
3543    return HTP_STATUS_OK;
3544}
3545
3546int op_matmul(struct htp_ops_context * octx) {
3547    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;
3548
3549    const int status = htp_mm_init_context(octx, kparams);
3550    if (status != HTP_STATUS_OK) {
3551        return status;
3552    }
3553
3554    if (kparams->n_hmx) {
3555        return hmx_mm_op_matmul(octx, kparams);
3556    }
3557
3558    return hvx_mm_matmul(octx);
3559}
3560
3561static int hmx_mm_op_matmul_id(
3562    struct htp_ops_context * octx,
3563    struct htp_mm_context * mmctx
3564) {
3565    const uint32_t *                matrix_row_counts = mmctx->matrix_row_counts;
3566    const struct mmid_row_mapping * matrix_rows       = mmctx->matrix_rows;
3567    htp_matmul_tensors_preamble;
3568    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;
3569    const int n_ids = octx->src[2]->ne[0];
3570    const int n_as  = ne02;
3571
3572    for (uint32_t cur_a = 0; cur_a < n_as; ++cur_a) {
3573        const int32_t cne1 = matrix_row_counts[cur_a];
3574        if (cne1 == 0) continue;
3575
3576        const int m_padded = hex_align_up(cne1, 32);
3577        int m_start = 0, m_end = m_padded;
3578        if (octx->ctx->mdev.count > 1) {
3579            const bool can_split = htp_tensor_mdev_data_aligned(dst) && (uint32_t) cne1 >= octx->ctx->mdev.count;
3580            const struct htp_tensor_mdev_range range = htp_tensor_mdev_partition((uint32_t) m_padded, can_split ? 1 : 0, octx->ctx->mdev.idx, octx->ctx->mdev.count, &octx->ctx->mdev.count_div);
3581            m_start = (int) range.start;
3582            m_end   = (int) (range.start + range.count);
3583        }
3584        if (m_start >= m_end) continue;
3585
3586        int ret = hmx_mm_id_2d_f32(octx->ctx, (float*) dst->data, (float*) src1->data,
3587                                   (const uint8_t *) src0->data + cur_a * nb02,
3588                                   cne1, ne00, ne01,
3589                                   ne10,
3590                                   ne11,
3591                                   nb11, nb12,
3592                                   nb1, nb2,
3593                                   (int) src0->nb[1], (int) src0->type,
3594                                   matrix_rows, cur_a, mmctx->mapping_stride,
3595                                   m_start, m_end, (int) octx->n_threads);
3596        if (ret != 0) {
3597            FARF(ERROR, "HMX matmul failed for expert %u, error %d\n", cur_a, ret);
3598            return HTP_STATUS_NO_SUPPORT;
3599        }
3600    }
3601
3602    return HTP_STATUS_OK;
3603}
3604
3605static int hvx_mm_matmul_id(
3606    struct htp_ops_context * octx,
3607    struct htp_mm_context * mmctx,
3608    work_queue_func_t hvx_mmid_task_func
3609) {
3610    htp_matmul_tensors_preamble;
3611    const uint32_t src0_row_size_padded = mmctx->src0_row_size_padded;
3612    const uint32_t src1_nrows           = mmctx->src1_nrows;
3613
3614    struct htp_thread_trace * tr = &octx->ctx->trace[0];
3615    htp_trace_event_start(tr, HTP_TRACE_EVT_INIT, 0);
3616
3617    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;
3618    const struct htp_tensor * restrict ids = octx->src[2];
3619    const size_t src0_row_size = nb01;
3620
3621    const uint32_t qk = QK_Q8_0_TILED;
3622    const uint32_t nb = (ne10 + qk - 1) / qk;
3623    const uint32_t total_nb = src1_nrows * nb;
3624
3625    work_queue_func_t quant_task_func;
3626    uint32_t n_quant_tasks = 1;
3627    if (src1_nrows < octx->n_threads) {
3628        n_quant_tasks = MIN(total_nb, octx->n_threads);
3629        quant_task_func = (src0->type == HTP_TYPE_Q4_1) ? quantize_f32_q8_1_tiled_block : quantize_f32_q8_0_tiled_block;
3630        for (uint32_t ith = 0; ith < n_quant_tasks; ++ith) {
3631            uint32_t ib_first = (total_nb * ith) / n_quant_tasks;
3632            uint32_t ib_last  = (total_nb * (ith + 1)) / n_quant_tasks;
3633            mmctx->quant_ib_first[ith] = ib_first;
3634            mmctx->quant_ib_last[ith]  = ib_last;
3635            mmctx->quant_r[ith]        = ib_first / nb;
3636            mmctx->quant_c[ith]        = ib_first % nb;
3637        }
3638    } else {
3639        n_quant_tasks = MIN(src1_nrows, octx->n_threads);
3640        quant_task_func = (src0->type == HTP_TYPE_Q4_1) ? quantize_f32_q8_1_tiled : quantize_f32_q8_0_tiled;
3641    }
3642    size_t src1_row_size  = (src0->type == HTP_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(ne10) : htp_mm_q8_0_tiled_row_size(ne10);
3643
3644    struct htp_mm_hvx_vtcm_layout L;
3645    htp_mm_hvx_vtcm_layout_build(&L, kparams->kernel_type, src0->type, ne10, src1_nrows, octx->n_threads,
3646                                 0, src0_row_size, src1_row_size, 0, kparams->n_prefetch, true, false);
3647
3648    const size_t vtcm_size = L.total_bytes;
3649
3650    FARF(HIGH, "matmul-id-%s : src0-spad-size %zu src1-spad-size %zu src2-spad-size 0 dst-spad-size %zu (%zu)\n", mmctx->type,
3651         L.src0_bytes, L.src1_bytes, L.dst_bytes, vtcm_size);
3652
3653    FARF(HIGH, "matmul-id-%s : %ux%ux%ux%u * %ux%ux%ux%u (%ux%ux%ux%u) -> %ux%ux%ux%u (0x%p, 0x%p, 0x%p)\n", mmctx->type,
3654         src0->ne[0], src0->ne[1], src0->ne[2], src0->ne[3], src1->ne[0], src1->ne[1], src1->ne[2], src1->ne[3],
3655         ids->ne[0], ids->ne[1], ids->ne[2], ids->ne[3], dst->ne[0], dst->ne[1], dst->ne[2], dst->ne[3], src0->data,
3656         src1->data, dst->data);
3657
3658    // Make sure the reserved vtcm size is sufficient
3659    if (octx->ctx->vtcm_size < vtcm_size) {
3660        FARF(ERROR, "matmul-id-%s : current VTCM reservation %zu is too small, needed %zu\n", mmctx->type, octx->ctx->vtcm_size, vtcm_size);
3661        return HTP_STATUS_VTCM_TOO_SMALL;
3662    }
3663
3664    uint8_t * const base = (uint8_t *) octx->ctx->vtcm_base;
3665    mmctx->vtcm_src1 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src1);
3666    mmctx->vtcm_src0 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src0);
3667    mmctx->vtcm_src2 = NULL;
3668    mmctx->vtcm_dst  = VTCM_LAYOUT_PTR(uint8_t, base, L.off_dst);
3669
3670    octx->src1_spad.src  = NULL;
3671    octx->src0_spad.src  = NULL;
3672    octx->src2_spad.src  = NULL;
3673    octx->dst_spad.src   = NULL;
3674
3675    mmctx->vtcm_src0_stride = src0_row_size_padded;
3676    mmctx->vtcm_src1_stride = src1_row_size;
3677
3678    mmctx->vtcm_src0_size_per_thread = fastdiv(L.src0_bytes, &octx->n_threads_div);
3679    mmctx->vtcm_src1_size_per_thread = L.src1_bytes;
3680    mmctx->vtcm_src2_size_per_thread = 0;
3681    mmctx->vtcm_dst_size_per_thread  = fastdiv(L.dst_bytes, &octx->n_threads_div);
3682
3683    mmctx->n_quant_rows_per_thread = (src1_nrows + n_quant_tasks - 1) / n_quant_tasks;
3684    mmctx->quant_task_func = quant_task_func;
3685    mmctx->n_quant_tasks = n_quant_tasks;
3686    atomic_init(&mmctx->quant_barrier, n_quant_tasks);
3687
3688    htp_trace_event_stop(tr, HTP_TRACE_EVT_INIT, 0);
3689
3690    worker_pool_run_func(octx->ctx->worker_pool, hvx_mmid_task_func, mmctx, octx->n_threads);
3691
3692    return HTP_STATUS_OK;
3693}
3694
3695static int hmx_mm_op_matmul_id_nx(
3696    struct htp_ops_context * octx,
3697    struct htp_mm_context * mmctx
3698) {
3699    const uint32_t *                matrix_row_counts = mmctx->matrix_row_counts;
3700    const struct mmid_row_mapping * matrix_rows       = mmctx->matrix_rows;
3701    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;
3702    const uint32_t n_weights = kparams->n_weights;
3703    const struct htp_tensor * restrict src0 = octx->src[0];
3704    const struct htp_tensor * restrict act  = octx->src[n_weights];
3705    const int n_as = src0->ne[2];
3706
3707    for (uint32_t cur_a = 0; cur_a < (uint32_t) n_as; ++cur_a) {
3708        const int32_t cne1 = matrix_row_counts[cur_a];
3709        if (cne1 == 0) continue;
3710
3711        const int m_padded = hex_align_up(cne1, 32);
3712        int m_start = 0, m_end = m_padded;
3713        if (octx->ctx->mdev.count > 1) {
3714            bool can_split = (uint32_t) cne1 >= octx->ctx->mdev.count;
3715            for (uint32_t p = 0; p < n_weights && can_split; ++p) {
3716                const struct htp_tensor * restrict dst = octx->dsts[p];
3717                can_split = !dst || htp_tensor_mdev_data_aligned(dst);
3718            }
3719            const struct htp_tensor_mdev_range range = htp_tensor_mdev_partition((uint32_t) m_padded, can_split ? 1 : 0, octx->ctx->mdev.idx, octx->ctx->mdev.count, &octx->ctx->mdev.count_div);
3720            m_start = (int) range.start;
3721            m_end   = (int) (range.start + range.count);
3722        }
3723        if (m_start >= m_end) continue;
3724
3725        for (uint32_t p = 0; p < n_weights; ++p) {
3726            const struct htp_tensor * restrict src_w = octx->src[p];
3727            const struct htp_tensor * restrict dst   = octx->dsts[p];
3728            if (!src_w || !dst) continue;
3729
3730            int ret = hmx_mm_id_2d_f32(octx->ctx, (float*) dst->data, (float*) act->data,
3731                                       (const uint8_t *) src_w->data + cur_a * src_w->nb[2],
3732                                       cne1, src_w->ne[0], src_w->ne[1],
3733                                       act->ne[0],
3734                                       act->ne[1],
3735                                       act->nb[1], act->nb[2],
3736                                       dst->nb[1], dst->nb[2],
3737                                       (int) src_w->nb[1], (int) src_w->type,
3738                                       matrix_rows, cur_a, mmctx->mapping_stride,
3739                                       m_start, m_end, (int) octx->n_threads);
3740            if (ret != 0) {
3741                FARF(ERROR, "HMX matmul ID NX failed for expert %u weight %u, error %d\n", cur_a, p, ret);
3742                return HTP_STATUS_NO_SUPPORT;
3743            }
3744        }
3745    }
3746
3747    return HTP_STATUS_OK;
3748}
3749
3750static int hvx_mm_matmul_id_nx(
3751    struct htp_ops_context * octx,
3752    struct htp_mm_context * mmctx,
3753    work_queue_func_t hvx_mmid_task_func
3754) {
3755    const uint32_t src0_row_size_padded = mmctx->src0_row_size_padded;
3756    const uint32_t src1_nrows           = mmctx->src1_nrows;
3757
3758    struct htp_thread_trace * tr = &octx->ctx->trace[0];
3759    htp_trace_event_start(tr, HTP_TRACE_EVT_INIT, 0);
3760
3761    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;
3762    const uint32_t n_weights = kparams->n_weights;
3763    const struct htp_tensor * restrict src0 = octx->src[0];
3764    const struct htp_tensor * restrict act  = octx->src[n_weights];
3765    const struct htp_tensor * restrict ids  = octx->src[n_weights + 1];
3766    const size_t src0_row_size = src0->nb[1];
3767
3768    const uint32_t qk = QK_Q8_0_TILED;
3769    const uint32_t nb = (act->ne[0] + qk - 1) / qk;
3770    const uint32_t total_nb = src1_nrows * nb;
3771
3772    work_queue_func_t quant_task_func;
3773    uint32_t n_quant_tasks = 1;
3774    if (src1_nrows < octx->n_threads) {
3775        n_quant_tasks = MIN(total_nb, octx->n_threads);
3776        quant_task_func = (src0->type == HTP_TYPE_Q4_1) ? quantize_f32_q8_1_tiled_block : quantize_f32_q8_0_tiled_block;
3777        for (uint32_t ith = 0; ith < n_quant_tasks; ++ith) {
3778            uint32_t ib_first = (total_nb * ith) / n_quant_tasks;
3779            uint32_t ib_last  = (total_nb * (ith + 1)) / n_quant_tasks;
3780            mmctx->quant_ib_first[ith] = ib_first;
3781            mmctx->quant_ib_last[ith]  = ib_last;
3782            mmctx->quant_r[ith]        = ib_first / nb;
3783            mmctx->quant_c[ith]        = ib_first % nb;
3784        }
3785    } else {
3786        n_quant_tasks = MIN(src1_nrows, octx->n_threads);
3787        quant_task_func = (src0->type == HTP_TYPE_Q4_1) ? quantize_f32_q8_1_tiled : quantize_f32_q8_0_tiled;
3788    }
3789    size_t src1_row_size = (src0->type == HTP_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(act->ne[0]) : htp_mm_q8_0_tiled_row_size(act->ne[0]);
3790
3791    struct htp_mm_hvx_vtcm_layout L;
3792    htp_mm_hvx_vtcm_layout_build(&L, kparams->kernel_type, src0->type, act->ne[0], src1_nrows, octx->n_threads,
3793                                 0, src0_row_size, src1_row_size, 0, kparams->n_prefetch, true, false);
3794
3795    const size_t vtcm_size = L.total_bytes;
3796
3797    if (octx->ctx->vtcm_size < vtcm_size) {
3798        FARF(ERROR, "matmul-id-nx: current VTCM reservation %zu is too small, needed %zu\n",
3799             octx->ctx->vtcm_size, vtcm_size);
3800        return HTP_STATUS_VTCM_TOO_SMALL;
3801    }
3802
3803    uint8_t * const base = (uint8_t *) octx->ctx->vtcm_base;
3804    mmctx->vtcm_src0 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src0);
3805    mmctx->vtcm_src1 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src1);
3806    mmctx->vtcm_dst  = VTCM_LAYOUT_PTR(uint8_t, base, L.off_dst);
3807
3808    octx->src0_spad.src = NULL;
3809    octx->src1_spad.src = NULL;
3810    octx->src2_spad.src = NULL;
3811    octx->src3_spad.src = NULL;
3812    octx->dst_spad.src  = NULL;
3813
3814    mmctx->vtcm_src0_stride = 0;
3815    mmctx->vtcm_src1_stride = src1_row_size;
3816
3817    mmctx->vtcm_src0_size_per_thread = fastdiv(L.src0_bytes, &octx->n_threads_div);
3818    mmctx->vtcm_src1_size_per_thread = L.src1_bytes;
3819    mmctx->vtcm_dst_size_per_thread  = fastdiv(L.dst_bytes, &octx->n_threads_div);
3820
3821    mmctx->n_quant_rows_per_thread = (src1_nrows + n_quant_tasks - 1) / n_quant_tasks;
3822    mmctx->quant_task_func         = quant_task_func;
3823    mmctx->n_quant_tasks           = n_quant_tasks;
3824    atomic_init(&mmctx->quant_barrier, n_quant_tasks);
3825
3826    FARF(HIGH, "matmul-id-nx: src0 %d:%d:%d type %s nrows %u, src1 %d:%d:%d nrows %u, vtcm %zu/%zu, threads %d\n",
3827         src0->ne[0], src0->ne[1], src0->ne[2], mmctx->type, src0->ne[1],
3828         act->ne[0], act->ne[1], act->ne[2], src1_nrows,
3829         L.total_bytes, octx->ctx->vtcm_size, octx->n_threads);
3830
3831    htp_trace_event_stop(tr, HTP_TRACE_EVT_INIT, 0);
3832
3833    worker_pool_run_func(octx->ctx->worker_pool, hvx_mmid_task_func, mmctx, octx->n_threads);
3834
3835    return HTP_STATUS_OK;
3836}
3837
3838static inline void scan_expert_ids_n(
3839    const struct htp_tensor * ids,
3840    const uint32_t n_ids,
3841    uint32_t n_as,
3842    uint32_t * counts,
3843    struct mmid_row_mapping * matrix_rows,
3844    uint32_t mapping_stride
3845) {
3846    const size_t ids_nb1 = ids->nb[1];
3847    const uint8_t * ids_data = (const uint8_t *) ids->data;
3848
3849    for (uint32_t iid1 = 0; iid1 < ids->ne[1]; ++iid1) {
3850        const int32_t * row_ptr = (const int32_t *) (ids_data + iid1 * ids_nb1);
3851        for (uint32_t id = 0; id < n_ids; ++id) {
3852            const int32_t i02 = row_ptr[id];
3853            if (i02 < 0) {
3854                continue;
3855            }
3856            assert(i02 < n_as);
3857
3858            if (matrix_rows) {
3859                matrix_rows[i02 * mapping_stride + counts[i02]] = (struct mmid_row_mapping) { id, iid1 };
3860            }
3861            counts[i02] += 1;
3862        }
3863    }
3864}
3865
3866static inline void scan_expert_ids(
3867    const struct htp_tensor * ids,
3868    uint32_t n_ids,
3869    uint32_t n_as,
3870    uint32_t * counts,
3871    struct mmid_row_mapping * matrix_rows,
3872    uint32_t mapping_stride
3873) {
3874    const size_t ids_nb0 = ids->nb[0];
3875
3876    if (ids_nb0 == 4) {
3877        switch (n_ids) {
3878            case 8:  scan_expert_ids_n(ids, 8,     n_as, counts, matrix_rows, mapping_stride); break;
3879            case 4:  scan_expert_ids_n(ids, 4,     n_as, counts, matrix_rows, mapping_stride); break;
3880            case 2:  scan_expert_ids_n(ids, 2,     n_as, counts, matrix_rows, mapping_stride); break;
3881            default: scan_expert_ids_n(ids, n_ids, n_as, counts, matrix_rows, mapping_stride); break;
3882        }
3883    } else {
3884        // Strided fallback
3885        const size_t ids_nb1 = ids->nb[1];
3886        const uint8_t * ids_data = (const uint8_t *) ids->data;
3887        for (uint32_t iid1 = 0; iid1 < ids->ne[1]; ++iid1) {
3888            const int32_t * row_ptr = (const int32_t *) (ids_data + iid1 * ids_nb1);
3889            for (uint32_t id = 0; id < n_ids; ++id) {
3890                const int32_t i02 = *(const int32_t *) ((const uint8_t *) row_ptr + id * ids_nb0);
3891                if (i02 < 0) {
3892                    continue;
3893                }
3894                assert(i02 < n_as);
3895
3896                if (matrix_rows) {
3897                    matrix_rows[i02 * mapping_stride + counts[i02]] = (struct mmid_row_mapping) { id, iid1 };
3898                }
3899                counts[i02] += 1;
3900            }
3901        }
3902    }
3903}
3904
3905int op_matmul_id(struct htp_ops_context * octx) {
3906    htp_matmul_tensors_preamble;
3907
3908    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;
3909    struct htp_mm_context mmctx_struct = {0};
3910    struct htp_mm_context * mmctx = &mmctx_struct;
3911
3912    const int status = htp_mm_init_context(octx, kparams);
3913    if (status != HTP_STATUS_OK) {
3914        return status;
3915    }
3916
3917    struct htp_thread_trace * tr = &octx->ctx->trace[0];
3918    htp_trace_event_start(tr, HTP_TRACE_EVT_INIT, 0);
3919
3920    mmctx->octx = octx;
3921    mmctx->act = src1;
3922
3923    const struct htp_tensor * restrict ids = octx->src[2];
3924
3925    const size_t src0_row_size = nb01;
3926    const size_t dst_row_size  = nb1;
3927
3928    const size_t src0_row_size_padded = hex_round_up(src0_row_size, 128);
3929
3930    const uint32_t src0_nrows = ne01;  // per expert
3931    const uint32_t src1_nrows = ne11 * ne12 * ne13;
3932
3933    // row groups
3934    const int n_ids = ids->ne[0];  // n_expert_used
3935    const int n_as  = ne02;        // n_expert
3936
3937    uint8_t  * mapping_buf       = octx->ctx->ddr_spad_base;
3938    uint32_t   mapping_stride    = 1;
3939    uint32_t * matrix_row_counts = (uint32_t *) mapping_buf;
3940    struct mmid_row_mapping * matrix_rows = NULL;
3941
3942    if (src1_nrows > 1) {
3943        const size_t matrix_row_counts_size = n_as * sizeof(uint32_t);
3944        assert(octx->ctx->ddr_spad_size >= matrix_row_counts_size);
3945
3946        hex_l2fetch_block((const void *) ids->data, ids->ne[1] * ids->nb[1]);
3947
3948        memset(matrix_row_counts, 0, matrix_row_counts_size);
3949        scan_expert_ids(ids, n_ids, n_as, matrix_row_counts, NULL, 0);
3950
3951        uint32_t max_count = hvx_reduce_max_i32((const uint8_t *) matrix_row_counts, n_as);
3952        mapping_stride = max_count > 0 ? max_count : 1;
3953
3954        size_t matrix_row_map_size  = n_as * mapping_stride * sizeof(struct mmid_row_mapping);
3955        const size_t total_map_size = matrix_row_counts_size + matrix_row_map_size;
3956
3957        if (total_map_size > octx->ctx->ddr_spad_size) {
3958            mapping_buf = memalign(128, total_map_size);
3959            if (!mapping_buf) {
3960                return HTP_STATUS_INTERNAL_ERR;
3961            }
3962        }
3963
3964        matrix_row_counts = (uint32_t *) mapping_buf;
3965        matrix_rows       = (struct mmid_row_mapping *) (mapping_buf + matrix_row_counts_size);
3966
3967        memset(matrix_row_counts, 0, n_as * sizeof(uint32_t));
3968        scan_expert_ids(ids, n_ids, n_as, matrix_row_counts, matrix_rows, mapping_stride);
3969    }
3970
3971    mmctx->matrix_row_counts    = matrix_row_counts;
3972    mmctx->matrix_rows          = matrix_rows;
3973    mmctx->mapping_stride       = mapping_stride;
3974    mmctx->mm_div_ne11          = kparams->div_ne11;
3975    mmctx->src0_row_size_padded = src0_row_size_padded;
3976    mmctx->src1_nrows           = src1_nrows;
3977
3978    htp_trace_event_stop(tr, HTP_TRACE_EVT_INIT, 0);
3979
3980    int s;
3981    if (kparams->n_hmx) {
3982        s = hmx_mm_op_matmul_id(octx, mmctx);
3983    } else {
3984        uint32_t src0_row_start = 0;
3985        uint32_t src0_row_end   = src0_nrows;
3986        if (octx->ctx->mdev.count > 1) {
3987            const bool can_split = htp_tensor_can_row_partition(dst, sizeof(float));
3988            const struct htp_tensor_mdev_range range = htp_tensor_mdev_partition(src0_nrows, can_split ? 32 : 0, octx->ctx->mdev.idx, octx->ctx->mdev.count, &octx->ctx->mdev.count_div);
3989            src0_row_start = range.start;
3990            src0_row_end   = range.start + range.count;
3991        }
3992
3993        if (src0_row_start >= src0_row_end) {
3994            if (mapping_buf != octx->ctx->ddr_spad_base) {
3995                free(mapping_buf);
3996            }
3997            return HTP_STATUS_OK;
3998        }
3999
4000        const uint32_t nrows = src0_row_end - src0_row_start;
4001        mmctx->src0_row_start = src0_row_start;
4002        mmctx->src0_row_end   = src0_row_end;
4003
4004        mmctx->src0_nrows_per_thread = fastdiv(nrows + octx->n_threads - 1, &octx->n_threads_div);
4005        mmctx->src0_nrows_per_thread = hex_round_up(mmctx->src0_nrows_per_thread, 32);
4006
4007        if (hvx_mm_init_vec_dot(mmctx, src0->type) == 0) {
4008            s = hvx_mm_matmul_id(octx, mmctx, src1_nrows > 1 ? hvx_mm_id : hvx_mv_id);
4009        } else {
4010            s = HTP_STATUS_NO_SUPPORT;
4011        }
4012    }
4013
4014    if (mapping_buf != octx->ctx->ddr_spad_base) {
4015        free(mapping_buf);
4016    }
4017
4018    return s;
4019}
4020
4021int op_matmul_id_nx(struct htp_ops_context * octx) {
4022    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;
4023    struct htp_mm_context mmctx_struct = {0};
4024    struct htp_mm_context * mmctx = &mmctx_struct;
4025
4026    const int status = htp_mm_init_context(octx, kparams);
4027    if (status != HTP_STATUS_OK) {
4028        return status;
4029    }
4030
4031    struct htp_thread_trace * tr = &octx->ctx->trace[0];
4032    htp_trace_event_start(tr, HTP_TRACE_EVT_INIT, 0);
4033
4034    mmctx->octx = octx;
4035    const uint32_t n_weights = kparams->n_weights;
4036    const struct htp_tensor * restrict src0 = octx->src[0];
4037    const struct htp_tensor * restrict act  = octx->src[n_weights];
4038    const struct htp_tensor * restrict ids  = octx->src[n_weights + 1];
4039
4040    mmctx->act = act;
4041
4042    const size_t src0_row_size = src0->nb[1];
4043    const size_t src0_row_size_padded = hex_round_up(src0_row_size, 128);
4044
4045    const uint32_t src1_nrows = act->ne[1] * act->ne[2] * act->ne[3];
4046
4047    const int n_ids = ids->ne[0];
4048    const int n_as  = src0->ne[2];
4049
4050    uint8_t  * mapping_buf       = octx->ctx->ddr_spad_base;
4051    uint32_t   mapping_stride    = 1;
4052    uint32_t * matrix_row_counts = (uint32_t *) mapping_buf;
4053    struct mmid_row_mapping * matrix_rows = NULL;
4054
4055    if (src1_nrows > 1) {
4056        const size_t matrix_row_counts_size = n_as * sizeof(uint32_t);
4057        assert(octx->ctx->ddr_spad_size >= matrix_row_counts_size);
4058
4059        hex_l2fetch_block((const void *) ids->data, ids->ne[1] * ids->nb[1]);
4060
4061        memset(matrix_row_counts, 0, matrix_row_counts_size);
4062        scan_expert_ids(ids, n_ids, n_as, matrix_row_counts, NULL, 0);
4063
4064        uint32_t max_count = hvx_reduce_max_i32((const uint8_t *) matrix_row_counts, n_as);
4065        mapping_stride = max_count > 0 ? max_count : 1;
4066
4067        size_t matrix_row_map_size  = n_as * mapping_stride * sizeof(struct mmid_row_mapping);
4068        const size_t total_map_size = matrix_row_counts_size + matrix_row_map_size;
4069
4070        if (total_map_size > octx->ctx->ddr_spad_size) {
4071            mapping_buf = memalign(128, total_map_size);
4072            if (!mapping_buf) {
4073                return HTP_STATUS_INTERNAL_ERR;
4074            }
4075        }
4076
4077        matrix_row_counts = (uint32_t *) mapping_buf;
4078        matrix_rows       = (struct mmid_row_mapping *) (mapping_buf + matrix_row_counts_size);
4079
4080        memset(matrix_row_counts, 0, n_as * sizeof(uint32_t));
4081        scan_expert_ids(ids, n_ids, n_as, matrix_row_counts, matrix_rows, mapping_stride);
4082    }
4083
4084    mmctx->matrix_row_counts    = matrix_row_counts;
4085    mmctx->matrix_rows          = matrix_rows;
4086    mmctx->mapping_stride       = mapping_stride;
4087    mmctx->mm_div_ne11          = kparams->div_ne11;
4088    mmctx->src0_row_size_padded = src0_row_size_padded;
4089    mmctx->src1_nrows           = src1_nrows;
4090
4091    htp_trace_event_stop(tr, HTP_TRACE_EVT_INIT, 0);
4092
4093    int s;
4094    if (kparams->n_hmx) {
4095        s = hmx_mm_op_matmul_id_nx(octx, mmctx);
4096    } else {
4097        if (hvx_mm_init_vec_dot(mmctx, src0->type) == 0) {
4098            s = hvx_mm_matmul_id_nx(octx, mmctx, src1_nrows > 1 ? hvx_mm_id_nx : hvx_mv_id_nx);
4099        } else {
4100            s = HTP_STATUS_NO_SUPPORT;
4101        }
4102    }
4103
4104    if (mapping_buf != octx->ctx->ddr_spad_base) {
4105        free(mapping_buf);
4106    }
4107
4108    return s;
4109}
4110int op_matmul_nx(struct htp_ops_context * octx) {
4111    const struct htp_mm_kernel_params * kparams = (const struct htp_mm_kernel_params *) octx->kernel_params;
4112
4113    const int status = htp_mm_init_context(octx, kparams);
4114    if (status != HTP_STATUS_OK) {
4115        return status;
4116    }
4117
4118    if (kparams->n_hmx) {
4119        return hmx_mm_nx_2d_f32(octx, kparams);
4120    }
4121
4122    struct htp_thread_trace * tr = &octx->ctx->trace[0];
4123    htp_trace_event_start(tr, HTP_TRACE_EVT_INIT, 0);
4124
4125    const uint32_t n_weights = kparams->n_weights;
4126
4127    const struct htp_tensor * restrict src0 = octx->src[0]; // first weight
4128    const struct htp_tensor * restrict act  = octx->src[n_weights]; // activation x
4129
4130    bool is_repacked = (src0->type == HTP_TYPE_Q4_0 || src0->type == HTP_TYPE_Q4_1 ||
4131                        src0->type == HTP_TYPE_Q8_0 || src0->type == HTP_TYPE_IQ4_NL ||
4132                        src0->type == HTP_TYPE_MXFP4);
4133
4134    struct htp_mm_context mmctx_struct = {0};
4135    struct htp_mm_context * mmctx = &mmctx_struct;
4136    mmctx->octx = octx;
4137    mmctx->act  = act;
4138
4139    const uint32_t src1_nrows = act->ne[1] * act->ne[2] * act->ne[3];
4140
4141    const size_t src0_row_size = src0->nb[1];
4142    const size_t src0_row_size_padded = hex_round_up(src0_row_size, 128);
4143
4144    if (hvx_mm_init_vec_dot(mmctx, src0->type) != 0) {
4145        return HTP_STATUS_NO_SUPPORT;
4146    }
4147
4148    const uint32_t qk = QK_Q8_0_TILED;
4149    const uint32_t nb = (act->ne[0] + qk - 1) / qk;
4150    const uint32_t total_nb = src1_nrows * nb;
4151
4152    worker_callback_t quant_task_func;
4153    uint32_t n_quant_tasks = 1;
4154    if (kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_ROW_FLAT) {
4155        n_quant_tasks = MIN(src1_nrows, octx->n_threads);
4156        quant_task_func = (src0->type == HTP_TYPE_Q4_1) ? quantize_f32_q8_1_flat : quantize_f32_q8_0_flat;
4157    } else if (src1_nrows < octx->n_threads) {
4158        n_quant_tasks = MIN(total_nb, octx->n_threads);
4159        quant_task_func = (src0->type == HTP_TYPE_Q4_1) ? quantize_f32_q8_1_tiled_block : quantize_f32_q8_0_tiled_block;
4160        for (uint32_t ith = 0; ith < n_quant_tasks; ++ith) {
4161            uint32_t ib_first = (total_nb * ith) / n_quant_tasks;
4162            uint32_t ib_last  = (total_nb * (ith + 1)) / n_quant_tasks;
4163            mmctx->quant_ib_first[ith] = ib_first;
4164            mmctx->quant_ib_last[ith]  = ib_last;
4165            mmctx->quant_r[ith]        = ib_first / nb;
4166            mmctx->quant_c[ith]        = ib_first % nb;
4167        }
4168    } else {
4169        n_quant_tasks = MIN(src1_nrows, octx->n_threads);
4170        quant_task_func = (src0->type == HTP_TYPE_Q4_1) ? quantize_f32_q8_1_tiled : quantize_f32_q8_0_tiled;
4171    }
4172
4173    size_t src1_row_size;
4174    if (kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_ROW_FLAT) {
4175        src1_row_size = (src0->type == HTP_TYPE_Q4_1) ? htp_mm_q8_1_flat_row_size(act->ne[0]) : htp_mm_q8_0_flat_row_size(act->ne[0]);
4176    } else {
4177        src1_row_size = (src0->type == HTP_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(act->ne[0]) : htp_mm_q8_0_tiled_row_size(act->ne[0]);
4178    }
4179
4180    struct htp_mm_hvx_vtcm_layout L;
4181    htp_mm_hvx_vtcm_layout_build(&L, kparams->kernel_type, src0->type, act->ne[0], src1_nrows, octx->n_threads,
4182                                 0, src0_row_size, src1_row_size, 0, kparams->n_prefetch, false, true);
4183
4184    const size_t vtcm_size = L.total_bytes;
4185
4186    if (octx->ctx->vtcm_size < vtcm_size) {
4187        FARF(ERROR, "matmul-nx: current VTCM reservation %zu is too small, needed %zu\n",
4188             octx->ctx->vtcm_size, vtcm_size);
4189        return HTP_STATUS_VTCM_TOO_SMALL;
4190    }
4191
4192    uint8_t * const base = (uint8_t *) octx->ctx->vtcm_base;
4193    mmctx->vtcm_src0 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src0);
4194    mmctx->vtcm_src1 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src1);
4195    mmctx->vtcm_dst  = VTCM_LAYOUT_PTR(uint8_t, base, L.off_dst);
4196
4197    octx->src0_spad.src  = NULL;
4198    octx->src1_spad.src  = NULL;
4199    octx->src2_spad.src  = NULL;
4200    octx->src3_spad.src  = NULL;
4201    octx->dst_spad.src   = NULL;
4202
4203    mmctx->vtcm_src0_stride = is_repacked ? 0 : src0_row_size_padded;
4204    mmctx->vtcm_src1_stride = src1_row_size;
4205
4206    mmctx->vtcm_src0_size_per_thread = fastdiv(L.src0_bytes, &octx->n_threads_div);
4207    mmctx->vtcm_src1_size_per_thread = L.src1_bytes;
4208    mmctx->vtcm_dst_size_per_thread  = fastdiv(L.dst_bytes, &octx->n_threads_div);
4209
4210    mmctx->n_quant_rows_per_thread = (src1_nrows + n_quant_tasks - 1) / n_quant_tasks;
4211    mmctx->quant_task_func = quant_task_func;
4212    mmctx->n_quant_tasks = n_quant_tasks;
4213    atomic_init(&mmctx->quant_barrier, n_quant_tasks);
4214
4215    // Run fused matmul
4216    const uint32_t n_matmul_jobs = octx->n_threads;
4217    worker_callback_t matmul_job_func;
4218    if (is_repacked) {
4219        if (kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_ROW_FLAT) {
4220            switch (src0->type) {
4221                case HTP_TYPE_Q4_0:   matmul_job_func = hvx_mm_nx_2d_repacked_q4_0_flat;   break;
4222                case HTP_TYPE_Q4_1:   matmul_job_func = hvx_mm_nx_2d_repacked_q4_1_flat;   break;
4223                case HTP_TYPE_Q8_0:   matmul_job_func = hvx_mm_nx_2d_repacked_q8_0_flat;   break;
4224                case HTP_TYPE_IQ4_NL: matmul_job_func = hvx_mm_nx_2d_repacked_iq4nl_flat;  break;
4225                case HTP_TYPE_MXFP4:  matmul_job_func = hvx_mm_nx_2d_repacked_mxfp4_flat;  break;
4226                default:              return HTP_STATUS_NO_SUPPORT;
4227            }
4228        } else {
4229            switch (src0->type) {
4230                case HTP_TYPE_Q4_0:   matmul_job_func = hvx_mm_nx_2d_repacked_q4_0;   break;
4231                case HTP_TYPE_Q4_1:   matmul_job_func = hvx_mm_nx_2d_repacked_q4_1;   break;
4232                case HTP_TYPE_Q8_0:   matmul_job_func = hvx_mm_nx_2d_repacked_q8_0;   break;
4233                case HTP_TYPE_IQ4_NL: matmul_job_func = hvx_mm_nx_2d_repacked_iq4nl;  break;
4234                case HTP_TYPE_MXFP4:  matmul_job_func = hvx_mm_nx_2d_repacked_mxfp4;  break;
4235                default:              return HTP_STATUS_NO_SUPPORT;
4236            }
4237        }
4238    } else {
4239        matmul_job_func = hvx_mm_nx_2d;
4240    }
4241
4242    htp_trace_event_stop(tr, HTP_TRACE_EVT_INIT, 0);
4243
4244    worker_pool_run_func(octx->ctx->worker_pool, matmul_job_func, mmctx, n_matmul_jobs);
4245
4246    return HTP_STATUS_OK;
4247}