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
118.5 KiB2519 linesraw
1#pragma clang diagnostic ignored "-Wunused-variable"
2#pragma clang diagnostic ignored "-Wunused-function"
3#pragma clang diagnostic ignored "-Wunused-but-set-variable"
4
5#include <assert.h>
6#include <HAP_compute_res.h>
7#include <HAP_farf.h>
8#include <math.h>
9#include <stdbool.h>
10#include <stdatomic.h>
11#include <stddef.h>
12#include <stdint.h>
13#include <string.h>
14
15#include "hex-dma.h"
16#include "hex-fastdiv.h"
17#include "hex-profile.h"
18#include "hmx-queue.h"
19#include "hmx-utils.h"
20#include "hvx-utils.h"
21#include "hvx-dump.h"
22#include "hvx-copy.h"
23#include "hvx-reduce.h"
24#include "hvx-flash-attn.h"
25#include "htp-vtcm.h"
26#include "work-queue.h"
27
28#define GGML_COMMON_DECL_C
29#include "ggml-common.h"
30#include "htp-ctx.h"
31#include "htp-ops.h"
32#include "htp-tensor.h"
33#include "hvx-quant.h"
34
35#include "flash-attn-ops.h"
36#include "hvx-fa-kernels.h"
37#include "hmx-fa-kernels.h"
38
39// Must be multiple of 32
40#define FLASH_ATTN_BLOCK_SIZE (32 * 2)
41
42struct htp_fa_context {
43    const struct htp_ops_context * octx;
44
45    struct fastdiv_values src0_div21;
46    struct fastdiv_values src0_div1;
47
48    struct fastdiv_values broadcast_rk2;
49    struct fastdiv_values broadcast_rk3;
50    struct fastdiv_values broadcast_rv2;
51    struct fastdiv_values broadcast_rv3;
52
53    struct fastdiv_values src3_div2;
54    struct fastdiv_values src3_div3;
55
56    float scale;
57    float max_bias;
58    __fp16 logit_softcap;
59
60    uint32_t n_head_log2;
61    float m0;
62    float m1;
63    __fp16 slopes[512];
64
65    uint32_t n_blocks;
66
67    size_t size_q_row_padded;
68    size_t size_k_row_padded;
69    size_t size_v_row_padded;
70
71    size_t size_k_block;
72    size_t size_v_block;
73    size_t size_m_block;
74
75    uint32_t qrows;
76    uint32_t qrows_per_thread;
77    uint32_t qrow_start;
78
79    bool is_q_fp32;
80
81    size_t size_q_block;
82    size_t size_vkq_acc;
83
84    uint8_t * spad_q;
85    uint8_t * spad_k;
86    uint8_t * spad_v;
87    uint8_t * spad_m;
88    uint8_t * spad_a;
89
90    const struct htp_tensor * k;
91    const struct htp_tensor * v;
92};
93
94struct hmx_fa_context {
95    const struct htp_ops_context * octx;
96    const struct htp_tensor *      sinks;  // attention sinks (src[4]), NULL if absent
97    const struct htp_tensor *      k;
98    const struct htp_tensor *      v;
99    bool         pipeline;  // true when n_kv_blocks >= FA_MIN_KV_BLOCKS && n_threads >= 2
100    uint32_t     n_threads;
101
102    // Op parameters
103    __fp16       scale;
104    float        max_bias;
105    __fp16       logit_softcap;
106    uint32_t     n_head_log2;
107    float        m0, m1;
108
109    // Dimensions
110    uint32_t     DK, DV;
111    uint32_t     n_kv;        // kv_len
112    uint32_t     n_kv_heads;  // number of KV heads
113    uint32_t     n_heads;     // number of Q heads
114    uint32_t     G;           // GQA factor = n_heads / n_kv_heads
115    struct fastdiv_values div_G;
116    struct fastdiv_values src3_div2;
117    struct fastdiv_values src3_div3;
118    uint32_t     n_kv_blocks;
119    uint32_t     neq1;        // Q token count
120
121    // Types
122    bool         is_q_fp32;
123    bool         is_dst_fp32;
124
125    // Dynamic block sizes
126    uint32_t     Br;    // Q tokens per block (before GQA expansion)
127    uint32_t     Bc;
128    uint32_t     g_br;  // hex_align_up(G * Br, 32) - actual tile row dim
129
130    // VTCM buffers (allocated by vtcm_seq_alloc)
131    __fp16 *     vtcm_q_dma;           // Q DMA fetch buffer
132    __fp16 *     vtcm_q_tiles;         // Q tile format [g_br, D]
133    __fp16 *     vtcm_o_tiles[2];      // O ping-pong [g_br, D]
134    __fp16 *     vtcm_k_fp16[2];       // K DMA double-buffer [Bc, D]
135    __fp16 *     vtcm_v_fp16[2];       // V DMA double-buffer [Bc, D]
136    __fp16 *     vtcm_k_tiles[2];      // K tiles (transposed, double-buffered)
137    __fp16 *     vtcm_v_tiles[2];      // V tiles (column-major, double-buffered)
138    __fp16 *     vtcm_s_tiles[2];      // S = QK^T [g_br, Bc] (double-buffered)
139    __fp16 *     vtcm_p_tiles[2];      // P = softmax(S) [g_br, Bc]
140    __fp16 *     vtcm_d_tiles[2];      // Diagonal rescale, g_br/32 packed diagonal tiles (double-buffered)
141    __fp16 *     vtcm_d_inv_l;         // Diagonal rescale (1/l), same packed layout
142    HVX_Vector * vtcm_m_vec;           // Row max [g_br]
143    HVX_Vector * vtcm_l_vec;           // Row sum [g_br]
144    HVX_Vector * vtcm_s_rowmax;        // Softmax intermediate [g_br]
145    HVX_Vector * vtcm_p_rowsum;        // Softmax intermediate [g_br]
146    HVX_Vector * vtcm_row_bufs;        // Per-thread softmax row scratch [n_threads][2][Bc/64]
147    uint8_t *    vtcm_hmx_scales_id;   // HMX output scales (identity)
148    uint8_t *    vtcm_hmx_scales_qk;   // HMX output scales (qk_scale)
149    __fp16 *     vtcm_mask_buf;        // VTCM mask buffer [Br * m_line], DMA'd per KV block
150    __fp16 *     vtcm_slopes;          // ALiBi slopes [g_br]
151    size_t       row_buf_stride;       // HVX vectors per row buffer (Bc/64)
152    size_t       mask_buf_row_stride;  // elements (__fp16) per row in mask buffer
153    size_t       q_tile_bytes;
154    size_t       o_tile_bytes;
155    size_t       col_vec_bytes;
156    size_t       d_tile_bytes;
157    bool         mask_broadcast;       // true when mask->ne[2] == 1 (head-independent, single 2D DMA)
158    dma_cache    m_cache;
159};
160
161static void flash_attn_ext_f16_thread(unsigned int nth, unsigned int ith, void * data) {
162    struct htp_fa_context * factx = (struct htp_fa_context *) data;
163    const struct htp_ops_context * octx = factx->octx;
164    const struct htp_tensor * q     = octx->src[0];
165    const struct htp_tensor * k     = octx->src[1];
166    const struct htp_tensor * v     = octx->src[2];
167    const struct htp_tensor * mask  = octx->src[3];
168    const struct htp_tensor * sinks = octx->src[4];
169    const struct htp_tensor * dst   = octx->dst;
170
171    const uint32_t neq0 = q->ne[0];
172    const uint32_t neq1 = q->ne[1];
173    const uint32_t neq2 = q->ne[2];
174    const uint32_t neq3 = q->ne[3];
175
176    const uint32_t nek0 = k->ne[0];
177    const uint32_t nek1 = k->ne[1];
178    const uint32_t nek2 = k->ne[2];
179    const uint32_t nek3 = k->ne[3];
180
181    const uint32_t nev0 = v->ne[0];
182    const uint32_t nev1 = v->ne[1];
183    const uint32_t nev2 = v->ne[2];
184    const uint32_t nev3 = v->ne[3];
185
186    const uint32_t nbq1 = q->nb[1];
187    const uint32_t nbq2 = q->nb[2];
188    const uint32_t nbq3 = q->nb[3];
189
190    const uint32_t nbk1 = k->nb[1];
191    const uint32_t nbk2 = k->nb[2];
192    const uint32_t nbk3 = k->nb[3];
193
194    const uint32_t nbv1 = v->nb[1];
195    const uint32_t nbv2 = v->nb[2];
196    const uint32_t nbv3 = v->nb[3];
197
198    const uint32_t ne1 = dst->ne[1];
199    const uint32_t ne2 = dst->ne[2];
200    const uint32_t ne3 = dst->ne[3];
201
202    const uint32_t nb1 = dst->nb[1];
203    const uint32_t nb2 = dst->nb[2];
204    const uint32_t nb3 = dst->nb[3];
205
206    // total rows in q
207    const uint32_t dr  = factx->qrows_per_thread;
208    const uint32_t ir0 = factx->qrow_start + dr * ith;
209    const uint32_t ir1 = MIN(ir0 + dr, factx->qrow_start + factx->qrows);
210
211    if (ir0 >= ir1) return;
212
213    struct htp_thread_trace * tr = &octx->ctx->trace[ith];
214
215    dma_queue * dma = octx->ctx->dma[ith];
216
217    const uint32_t DK = nek0;
218    const uint32_t DV = nev0;
219
220    const size_t size_q_row = DK * ((q->type == HTP_TYPE_F32) ? 4 : 2);
221    const size_t size_k_row = htp_tensor_get_row_size(k->type, DK);
222    const size_t size_v_row = htp_tensor_get_row_size(v->type, DV);
223
224    // Scratchpad buffers for Q, K, V, Mask, and VKQ32 accumulator
225    uint8_t * spad_q = factx->spad_q + factx->size_q_block * ith;
226    uint8_t * spad_k = factx->spad_k + factx->size_k_block * 2 * ith;
227    uint8_t * spad_v = factx->spad_v + factx->size_v_block * 2 * ith;
228    uint8_t * spad_m = factx->spad_m + (mask ? factx->size_m_block * HVX_FA_DMA_CACHE_SIZE : 0) * ith;
229    uint8_t * spad_a = factx->spad_a + factx->size_vkq_acc * ith;
230
231    dma_cache m_cache;
232    dma_cache_init(&m_cache, spad_m, factx->size_m_block, HVX_FA_DMA_CACHE_SIZE);
233
234    for (uint32_t ir = ir0; ir < ir1; ++ir) {
235        const uint32_t iq3 = fastdiv(ir, &factx->src0_div21);
236        const uint32_t iq2 = fastdiv(ir - iq3*neq2*neq1, &factx->src0_div1);
237        const uint32_t iq1 = (ir - iq3*neq2*neq1 - iq2 * neq1);
238
239        const uint32_t ik3 = fastdiv(iq3, &factx->broadcast_rk3);
240        const uint32_t ik2 = fastdiv(iq2, &factx->broadcast_rk2);
241
242        const uint32_t iv3 = fastdiv(iq3, &factx->broadcast_rv3);
243        const uint32_t iv2 = fastdiv(iq2, &factx->broadcast_rv2);
244
245        const __fp16 * mp_base = NULL;
246        if (mask) {
247            const uint32_t im2 = fastmodulo(iq2, mask->ne[2], &factx->src3_div2);
248            const uint32_t im3 = fastmodulo(iq3, mask->ne[3], &factx->src3_div3);
249            mp_base = (const __fp16 *) ((const uint8_t *) mask->data + iq1*mask->nb[1] + im2*mask->nb[2] + im3*mask->nb[3]);
250        }
251
252        // Precalculate next row variables if there is a next row
253        bool has_next_ir = (ir + 1 < ir1);
254        uint32_t next_ik2 = 0, next_ik3 = 0, next_iv2 = 0, next_iv3 = 0;
255        const uint8_t * next_q_row_ptr = NULL;
256        const __fp16 * next_mp_base = NULL;
257
258        const uint8_t * next_k_src0 = NULL;
259        const uint8_t * next_v_src0 = NULL;
260        const uint8_t * next_m_src0 = NULL;
261        uint32_t next_block_size0 = 0;
262
263        const uint8_t * next_k_src1 = NULL;
264        const uint8_t * next_v_src1 = NULL;
265        const uint8_t * next_m_src1 = NULL;
266        uint32_t next_block_size1 = 0;
267
268        if (has_next_ir) {
269            const uint32_t next_ir = ir + 1;
270            const uint32_t next_iq3 = fastdiv(next_ir, &factx->src0_div21);
271            const uint32_t next_iq2 = fastdiv(next_ir - next_iq3*neq2*neq1, &factx->src0_div1);
272            const uint32_t next_iq1 = (next_ir - next_iq3*neq2*neq1 - next_iq2 * neq1);
273
274            next_ik3 = fastdiv(next_iq3, &factx->broadcast_rk3);
275            next_ik2 = fastdiv(next_iq2, &factx->broadcast_rk2);
276
277            next_iv3 = fastdiv(next_iq3, &factx->broadcast_rv3);
278            next_iv2 = fastdiv(next_iq2, &factx->broadcast_rv2);
279
280            next_q_row_ptr = (const uint8_t *) q->data + (next_iq1*nbq1 + next_iq2*nbq2 + next_iq3*nbq3);
281
282            if (mask) {
283                const uint32_t next_im2 = fastmodulo(next_iq2, mask->ne[2], &factx->src3_div2);
284                const uint32_t next_im3 = fastmodulo(next_iq3, mask->ne[3], &factx->src3_div3);
285                next_mp_base = (const __fp16 *) ((const uint8_t *) mask->data + next_iq1*mask->nb[1] + next_im2*mask->nb[2] + next_im3*mask->nb[3]);
286            }
287
288            // Precalculate next K/V block 0 source pointers
289            {
290                const uint32_t ic_start = 0;
291                next_block_size0 = MIN(FLASH_ATTN_BLOCK_SIZE, nek1 - ic_start);
292                next_k_src0 = (const uint8_t *) k->data + (ic_start*nbk1 + next_ik2*nbk2 + next_ik3*nbk3);
293                next_v_src0 = (const uint8_t *) v->data + (ic_start*nbv1 + next_iv2*nbv2 + next_iv3*nbv3);
294                if (mask) {
295                    next_m_src0 = (const uint8_t *) (next_mp_base + ic_start);
296                }
297            }
298
299            // Precalculate next K/V block 1 source pointers (if n_blocks > 1)
300            if (factx->n_blocks > 1) {
301                const uint32_t ic_start = 1 * FLASH_ATTN_BLOCK_SIZE;
302                next_block_size1 = MIN(FLASH_ATTN_BLOCK_SIZE, nek1 - ic_start);
303                next_k_src1 = (const uint8_t *) k->data + (ic_start*nbk1 + next_ik2*nbk2 + next_ik3*nbk3);
304                next_v_src1 = (const uint8_t *) v->data + (ic_start*nbv1 + next_iv2*nbv2 + next_iv3*nbv3);
305                if (mask) {
306                    next_m_src1 = (const uint8_t *) (next_mp_base + ic_start);
307                }
308            }
309        }
310
311        if (ir == ir0) {
312            // Fetch Q row
313            const uint8_t * q_row_ptr = (const uint8_t *) q->data + (iq1*nbq1 + iq2*nbq2 + iq3*nbq3);
314            dma_queue_push(dma, dma_make_ptr(spad_q, q_row_ptr), factx->size_q_row_padded, nbq1, size_q_row, 1);
315
316            // Prefetch first two blocks
317            for (uint32_t ib = 0; ib < MIN(factx->n_blocks, 2); ++ib) {
318                const uint32_t ic_start = ib * FLASH_ATTN_BLOCK_SIZE;
319                const uint32_t current_block_size = MIN(FLASH_ATTN_BLOCK_SIZE, nek1 - ic_start);
320
321                // K
322                const uint8_t * k_src = (const uint8_t *) k->data + (ic_start*nbk1 + ik2*nbk2 + ik3*nbk3);
323                uint8_t * k_dst = spad_k + (ib % 2) * factx->size_k_block;
324                dma_queue_push(dma, dma_make_ptr(k_dst, k_src), factx->size_k_row_padded, nbk1, size_k_row, current_block_size);
325
326                // V
327                const uint8_t * v_src = (const uint8_t *) v->data + (ic_start*nbv1 + iv2*nbv2 + iv3*nbv3);
328                uint8_t * v_dst = spad_v + (ib % 2) * factx->size_v_block;
329                dma_queue_push(dma, dma_make_ptr(v_dst, v_src), factx->size_v_row_padded, nbv1, size_v_row, current_block_size);
330
331                // Mask
332                if (mask) {
333                    const uint8_t * m_src = (const uint8_t *) (mp_base + ic_start);
334                    // Mask is 1D contiguous for this row
335                    dma_cache_push(dma, &m_cache, m_src, current_block_size * 2, current_block_size * 2, current_block_size * 2, 1);
336                }
337            }
338        }
339
340        const uint32_t h = iq2; // head index
341        const __fp16 slope = factx->slopes[h];
342
343        HVX_Vector S_vec = hvx_vec_splat_f32(0.0f);
344        HVX_Vector M_vec = hvx_vec_splat_f32(HTP_FA_M_INITIAL_VAL);
345
346        // Clear accumulator
347        hvx_splat_f32_a(spad_a, 0, DV);
348        float * VKQ32 = (float *) (spad_a + 0);
349
350        uint8_t * q_ptr_vtcm = dma_queue_pop(dma).dst;
351        if (factx->is_q_fp32) {
352            hvx_copy_f16_f32_aa(q_ptr_vtcm, q_ptr_vtcm, DK);  // inplace convert f32 to f16
353        }
354
355        const HVX_Vector slope_vec = hvx_vec_splat_f16(slope);
356        const HVX_Vector v_neg_inf = Q6_Vh_vsplat_R(0xfbff);
357        const HVX_Vector v_cap     = (factx->logit_softcap != 0.0f) ? hvx_vec_splat_f16(factx->logit_softcap) : Q6_V_vzero();
358        const HVX_Vector vinf      = Q6_Vh_vsplat_R(0xFC00);
359        const HVX_Vector vmin      = Q6_Vh_vsplat_R(0xFBFF);
360        const HVX_Vector v_log2e   = hvx_vec_splat_f16(EXP_LOG2E_F);
361        const uint32_t stride_v2   = factx->size_v_row_padded * 2;
362        for (uint32_t ib = 0; ib < factx->n_blocks; ++ib) {
363            const uint32_t ic_start = ib * FLASH_ATTN_BLOCK_SIZE;
364            const uint32_t current_block_size = MIN(FLASH_ATTN_BLOCK_SIZE, nek1 - ic_start);
365
366            // Wait for DMA
367            uint8_t * k_base = dma_queue_pop(dma).dst; // K
368            uint8_t * v_base = dma_queue_pop(dma).dst; // V
369            __fp16  * m_base = mask ? dma_queue_pop(dma).dst : NULL; // M
370
371            if (factx->k->type == HTP_TYPE_Q8_0) {
372                htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_FA_K_PREP, ir);
373                for (uint32_t r = 0; r < current_block_size; ++r) {
374                    __fp16 * row_k = (__fp16 *)(k_base + r * factx->size_k_row_padded);
375                    hvx_dequantize_row_q8_0_f16(row_k, row_k, DK);
376                }
377                htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_FA_K_PREP, ir);
378            }
379            if (factx->v->type == HTP_TYPE_Q8_0) {
380                htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_FA_V_PREP, ir);
381                for (uint32_t r = 0; r < current_block_size; ++r) {
382                    __fp16 * row_v = (__fp16 *)(v_base + r * factx->size_v_row_padded);
383                    hvx_dequantize_row_q8_0_f16(row_v, row_v, DV);
384                }
385                htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_FA_V_PREP, ir);
386            }
387
388            htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_FA_QK, ir);
389
390            // Inner loop processing the block from VTCM
391            // 1. Compute scores (64 elements FP16)
392            HVX_Vector scores_f16 = Q6_V_vzero();
393            if (current_block_size > 0) {
394                HVX_Vector scores0 = hvx_dot_f16_f16_aa_rx32(q_ptr_vtcm, k_base, factx->size_k_row_padded, DK, factx->scale);
395                HVX_Vector scores1 = (current_block_size > 32) ? hvx_dot_f16_f16_aa_rx32(q_ptr_vtcm, k_base + 32 * factx->size_k_row_padded, factx->size_k_row_padded, DK, factx->scale) : Q6_V_vzero();
396                scores_f16 = hvx_vec_f32_to_f16(scores0, scores1);
397            }
398
399            // 2. Softcap (in FP16)
400            if (factx->logit_softcap != 0.0f) {
401                scores_f16 = hvx_vec_tanh_f16(scores_f16);
402                scores_f16 = hvx_vec_mul_f16_f16(scores_f16, v_cap);
403            }
404
405            HVX_VectorPred q_tail_keep = Q6_Q_vsetq2_R(current_block_size * sizeof(__fp16));
406
407            // 3. Mask (in FP16)
408            if (mask) {
409                HVX_Vector m_vals_f16 = *(const HVX_UVector *) m_base;
410                HVX_VectorPred is_inf = Q6_Q_vcmp_eq_VhVh(m_vals_f16, vinf);
411                m_vals_f16 = Q6_V_vmux_QVV(is_inf, vmin, m_vals_f16);
412
413                HVX_Vector m_scaled = hvx_vec_mul_f16_f16(m_vals_f16, slope_vec);
414                scores_f16 = Q6_V_vmux_QVV(q_tail_keep, hvx_vec_add_f16_f16(scores_f16, m_scaled), v_neg_inf);
415            } else {
416                scores_f16 = Q6_V_vmux_QVV(q_tail_keep, scores_f16, v_neg_inf);
417            }
418
419            // Compute block max in FP16
420            HVX_Vector v_max_f16 = hvx_vec_reduce_max_f16(scores_f16);
421            HVX_Vector v_max     = Q6_V_lo_W(hvx_vec_f16_to_f32(v_max_f16)); // splat block max in FP32
422            htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_FA_QK, ir);
423
424            if (ib + 1 == factx->n_blocks && has_next_ir) {
425                // Queue next row's Q row!
426                dma_queue_push(dma, dma_make_ptr(spad_q, next_q_row_ptr), factx->size_q_row_padded, nbq1, size_q_row, 1);
427
428                if (factx->n_blocks % 2 == 0) {
429                    // Queue next row's block 0 (into buffer slot 0)
430                    uint8_t * k_dst = spad_k + 0 * factx->size_k_block;
431                    uint8_t * v_dst = spad_v + 0 * factx->size_v_block;
432
433                    // K (block 0 of next row)
434                    dma_queue_push(dma, dma_make_ptr(k_dst, next_k_src0), factx->size_k_row_padded, nbk1, size_k_row, next_block_size0);
435
436                    // V (block 0 of next row)
437                    dma_queue_push(dma, dma_make_ptr(v_dst, next_v_src0), factx->size_v_row_padded, nbv1, size_v_row, next_block_size0);
438
439                    // Mask (block 0 of next row)
440                    if (mask) {
441                        dma_cache_push(dma, &m_cache, next_m_src0, next_block_size0 * 2, next_block_size0 * 2, next_block_size0 * 2, 1);
442                    }
443                }
444            }
445
446            htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_FA_SFM, ir);
447            {
448                // 4. Online Softmax Update
449                HVX_Vector M_new_vec = Q6_Vsf_vmax_VsfVsf(v_max, M_vec);
450                HVX_Vector diff_vec  = HVX_OP_SUB_F32(M_vec, M_new_vec);
451
452                HVX_Vector diff_f16   = hvx_vec_f32_to_f16(diff_vec, diff_vec);
453                HVX_Vector diff_base2 = hvx_vec_mul_f16_f16(diff_f16, v_log2e);
454                HVX_Vector ms_f16     = hvx_vec_exp2_f16(diff_base2);
455                HVX_Vector ms_vec     = Q6_V_lo_W(hvx_vec_f16_to_f32(ms_f16));
456
457                M_vec = M_new_vec;
458
459                hvx_scale_vec_f32_aa((uint8_t *) VKQ32, (const uint8_t *) VKQ32, DV, ms_vec);
460
461                // Compute P = exp2((S - M) * log2(e)) in FP16
462                HVX_Vector v_m_vec_f16 = hvx_vec_f32_to_f16(M_vec, M_vec);
463                HVX_Vector v_s_minus_m = Q6_Vqf16_vsub_VhfVhf(scores_f16, v_m_vec_f16);
464
465                HVX_Vector v_s_minus_m_base2 = hvx_vec_mul_f16_f16(Q6_Vhf_equals_Vqf16(v_s_minus_m), v_log2e);
466
467                HVX_Vector P = hvx_vec_exp2_f16(v_s_minus_m_base2);
468                P = Q6_V_vmux_QVV(q_tail_keep, P, Q6_V_vzero());
469
470                // Convert P to FP32 to update the running sum S_vec
471                HVX_VectorPair P_pair = hvx_vec_f16_to_f32(P);
472                HVX_Vector P0 = Q6_V_lo_W(P_pair);
473                HVX_Vector P1 = Q6_V_hi_W(P_pair);
474                HVX_Vector p_sum_vec = hvx_vec_reduce_sum_f32(HVX_OP_ADD_F32(P0, P1));
475
476                S_vec = HVX_OP_ADD_F32(HVX_OP_MUL_F32(S_vec, ms_vec), p_sum_vec);
477
478                // 5. Accumulate V (F16 * F16 -> F32 accumulator)
479                const uint8_t * v_ptr = v_base;
480
481                for (uint32_t j = 0; j < current_block_size; j += 2) {
482                    if (j + 1 == current_block_size) {
483                        HVX_Vector S0 = hvx_vec_repl_f16(Q6_V_vror_VR(P, j * 2));
484                        hvx_mad_f32_f16_aa_vec(VKQ32, v_ptr, S0, DV);
485                        break;
486                    }
487
488                    HVX_Vector S0 = hvx_vec_repl_f16(Q6_V_vror_VR(P, j * 2));
489                    HVX_Vector S1 = hvx_vec_repl_f16(Q6_V_vror_VR(P, (j + 1) * 2));
490
491                    hvx_mad_f32_f16_aa_rx2_vec(VKQ32, v_ptr, v_ptr + factx->size_v_row_padded, S0, S1, DV);
492                    v_ptr += stride_v2;
493                }
494            }
495            htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_FA_SFM, ir);
496
497            // Issue DMA for next+1 block (if exists)
498            if (ib + 2 < factx->n_blocks) {
499                const uint32_t next_ib = ib + 2;
500                const uint32_t next_ic_start = next_ib * FLASH_ATTN_BLOCK_SIZE;
501                const uint32_t next_block_size = MIN(FLASH_ATTN_BLOCK_SIZE, nek1 - next_ic_start);
502
503                // K
504                const uint8_t * k_src = (const uint8_t *) k->data + (next_ic_start*nbk1 + ik2*nbk2 + ik3*nbk3);
505                dma_queue_push(dma, dma_make_ptr(k_base, k_src), factx->size_k_row_padded, nbk1, size_k_row, next_block_size);
506
507                // V
508                const uint8_t * v_src = (const uint8_t *) v->data + (next_ic_start*nbv1 + iv2*nbv2 + iv3*nbv3);
509                dma_queue_push(dma, dma_make_ptr(v_base, v_src), factx->size_v_row_padded, nbv1, size_v_row, next_block_size);
510
511                // Mask
512                if (mask) {
513                    const uint8_t * m_src = (const uint8_t *) (mp_base + next_ic_start);
514                    dma_cache_push(dma, &m_cache, m_src, next_block_size * 2, next_block_size * 2, next_block_size * 2, 1);
515                }
516            }
517        }
518
519        if (has_next_ir) {
520            if (factx->n_blocks % 2 == 0) {
521                // Queue next row's block 1 (into buffer slot 1, if n_blocks > 1)
522                if (factx->n_blocks > 1) {
523                    uint8_t * k_dst = spad_k + 1 * factx->size_k_block;
524                    uint8_t * v_dst = spad_v + 1 * factx->size_v_block;
525
526                    // K (block 1 of next row)
527                    dma_queue_push(dma, dma_make_ptr(k_dst, next_k_src1), factx->size_k_row_padded, nbk1, size_k_row, next_block_size1);
528
529                    // V (block 1 of next row)
530                    dma_queue_push(dma, dma_make_ptr(v_dst, next_v_src1), factx->size_v_row_padded, nbv1, size_v_row, next_block_size1);
531
532                    // Mask (block 1 of next row)
533                    if (mask) {
534                        dma_cache_push(dma, &m_cache, next_m_src1, next_block_size1 * 2, next_block_size1 * 2, next_block_size1 * 2, 1);
535                    }
536                }
537            } else {
538                // Queue next row's block 0 (into buffer slot 0)
539                {
540                    uint8_t * k_dst = spad_k + 0 * factx->size_k_block;
541                    uint8_t * v_dst = spad_v + 0 * factx->size_v_block;
542
543                    // K (block 0 of next row)
544                    dma_queue_push(dma, dma_make_ptr(k_dst, next_k_src0), factx->size_k_row_padded, nbk1, size_k_row, next_block_size0);
545
546                    // V (block 0 of next row)
547                    dma_queue_push(dma, dma_make_ptr(v_dst, next_v_src0), factx->size_v_row_padded, nbv1, size_v_row, next_block_size0);
548
549                    // Mask (block 0 of next row)
550                    if (mask) {
551                        dma_cache_push(dma, &m_cache, next_m_src0, next_block_size0 * 2, next_block_size0 * 2, next_block_size0 * 2, 1);
552                    }
553                }
554
555                // Queue next row's block 1 (into buffer slot 1, if n_blocks > 1)
556                if (factx->n_blocks > 1) {
557                    uint8_t * k_dst = spad_k + 1 * factx->size_k_block;
558                    uint8_t * v_dst = spad_v + 1 * factx->size_v_block;
559
560                    // K (block 1 of next row)
561                    dma_queue_push(dma, dma_make_ptr(k_dst, next_k_src1), factx->size_k_row_padded, nbk1, size_k_row, next_block_size1);
562
563                    // V (block 1 of next row)
564                    dma_queue_push(dma, dma_make_ptr(v_dst, next_v_src1), factx->size_v_row_padded, nbv1, size_v_row, next_block_size1);
565
566                    // Mask (block 1 of next row)
567                    if (mask) {
568                        dma_cache_push(dma, &m_cache, next_m_src1, next_block_size1 * 2, next_block_size1 * 2, next_block_size1 * 2, 1);
569                    }
570                }
571            }
572        }
573
574        htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_O_PROC, ir);
575        // sinks
576        float M = hvx_vec_get_f32(M_vec);
577        float S = hvx_vec_get_f32(S_vec);
578
579        if (sinks) {
580            const float s = ((float *)((char *) sinks->data))[h];
581
582            float vs = 1.0f;
583
584            if (s > M) {
585                HVX_Vector diff_vec = hvx_vec_splat_f32(M - s);
586                HVX_Vector ms_vec   = hvx_vec_exp_f32(diff_vec);
587                hvx_scale_vec_f32_aa((uint8_t *) VKQ32, (const uint8_t *) VKQ32, DV, ms_vec);
588
589                float ms = hvx_vec_get_f32(ms_vec);
590                S = S * ms + vs;
591            } else {
592                HVX_Vector diff_vec = hvx_vec_splat_f32(s - M);
593                vs = hvx_vec_get_f32(hvx_vec_exp_f32(diff_vec));
594                S += vs;
595            }
596        }
597
598        const float S_inv = S == 0.0f ? 0.0f : 1.0f/S;
599        hvx_scale_f32_aa((uint8_t *) VKQ32, (const uint8_t *) VKQ32, DV, S_inv);
600
601        // Store result
602        // dst indices
603        const uint32_t i1 = iq1;
604        const uint32_t i2 = iq2;
605        const uint32_t i3 = iq3;
606
607        // dst is permuted: [DV, n_heads, n_tokens, n_seq]
608        // head stride is nb[1], token stride is nb[2], batch stride is nb[3]
609        uint8_t * dst_ptr = (uint8_t *) dst->data + i2 * dst->nb[1] + i1 * dst->nb[2] + i3 * dst->nb[3];
610
611        if (dst->type == HTP_TYPE_F32) {
612            hvx_copy_f32_ua(dst_ptr, (uint8_t *) VKQ32, DV);
613        } else if (dst->type == HTP_TYPE_F16) {
614            hvx_copy_f16_f32_ua(dst_ptr, (uint8_t *) VKQ32, DV);
615        }
616        htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_O_PROC, ir);
617    }
618}
619
620// ============================================================================
621// HMX Phase args and thread logic
622// ============================================================================
623
624typedef struct {
625    struct hmx_fa_context * factx;
626    uint32_t                kv_rows;
627    size_t                  src_stride;
628    void *                  curr_k;
629    uint32_t                kv_start;
630    uint32_t                rows_per_t;
631    size_t                  buf_idx;
632} fa_k_int_args_t;
633
634static void fa_k_interleave_thread(unsigned int n, unsigned int i, void * data) {
635    fa_k_int_args_t *       args  = (fa_k_int_args_t *) data;
636    struct hmx_fa_context * factx = args->factx;
637
638    const uint32_t total_rows = args->kv_rows;
639    const uint32_t rows_per_t = args->rows_per_t;
640    const uint32_t start      = i * rows_per_t;
641    const uint32_t end        = (uint32_t) hex_smin(start + rows_per_t, total_rows);
642
643    if (start >= total_rows) {
644        return;
645    }
646
647    struct htp_thread_trace * tr = &factx->octx->ctx->trace[i];
648    htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_FA_K_PREP, (uint16_t) (args->kv_start + start));
649    if (factx->k->type == HTP_TYPE_Q8_0) {
650        for (uint32_t r = start; r < end; ++r) {
651            __fp16 * row_k = (__fp16 *)((char *)args->curr_k + r * args->src_stride * sizeof(__fp16));
652            hvx_dequantize_row_q8_0_f16(row_k, row_k, factx->DK);
653        }
654    }
655    hmx_interleave_rows_to_tiles(factx->vtcm_k_tiles[args->buf_idx], (const __fp16 *) args->curr_k, total_rows, factx->DK,
656                             args->src_stride, start, end);
657    htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_FA_K_PREP, (uint16_t) (args->kv_start + start));
658}
659
660static void fa_phase_k_interleave(struct hmx_fa_context * factx, uint32_t kv_rows, size_t src_stride, void * curr_k, uint32_t kv_start, size_t buf_idx) {
661    work_queue_t wp = factx->octx->ctx->work_queue;
662    uint32_t n = 1;
663    if (factx->n_threads > 1 && kv_rows >= factx->n_threads * 2) {
664        n = factx->n_threads;
665    }
666    uint32_t rows_per_t = hex_align_up(hmx_ceil_div(kv_rows, n), 2);
667    fa_k_int_args_t args = { factx, kv_rows, src_stride, curr_k, kv_start, rows_per_t, buf_idx };
668    if (n > 1) {
669        work_queue_run(wp, fa_k_interleave_thread, &args, n);
670    } else {
671        fa_k_interleave_thread(1, 0, &args);
672    }
673}
674
675typedef struct {
676    struct hmx_fa_context * factx;
677    uint32_t                kv_rows;
678    size_t                  src_stride;
679    void *                  v_src;
680    void *                  v_tiles_dst;
681    size_t                  n_col_tiles;
682    uint32_t                kv_start;
683    uint32_t                rows_per_t;
684} fa_v_int_args_t;
685
686static void fa_v_interleave_thread(unsigned int n, unsigned int i, void * data) {
687    fa_v_int_args_t *       args  = (fa_v_int_args_t *) data;
688    struct hmx_fa_context * factx = args->factx;
689
690    const uint32_t total_rows = args->kv_rows;
691    const uint32_t rows_per_t = args->rows_per_t;
692    const uint32_t start      = i * rows_per_t;
693    const uint32_t end        = (uint32_t) hex_smin(start + rows_per_t, total_rows);
694
695    if (start >= total_rows) {
696        return;
697    }
698
699    __fp16 * v_tiles_dst = (__fp16 *) args->v_tiles_dst;
700
701    struct htp_thread_trace * tr = &factx->octx->ctx->trace[i];
702    htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_FA_V_PREP, (uint16_t) (args->kv_start + start));
703    if (factx->v->type == HTP_TYPE_Q8_0) {
704        for (uint32_t r = start; r < end; ++r) {
705            __fp16 * row_v = (__fp16 *)((char *)args->v_src + r * args->src_stride * sizeof(__fp16));
706            hvx_dequantize_row_q8_0_f16(row_v, row_v, factx->DV);
707        }
708    }
709    hmx_interleave_cols_to_tiles(v_tiles_dst, (const __fp16 *) args->v_src, total_rows, factx->DV,
710                             args->src_stride, (uint32_t) args->n_col_tiles, start, end);
711    htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_FA_V_PREP, (uint16_t) (args->kv_start + start));
712}
713
714static void fa_phase_v_interleave(struct hmx_fa_context * factx,
715                                  uint32_t                kv_rows,
716                                  size_t                  src_stride,
717                                  void *                  v_src,
718                                  void *                  v_tiles_dst,
719                                  size_t                  n_col_tiles,
720                                  uint32_t                kv_start) {
721    work_queue_t wp = factx->octx->ctx->work_queue;
722    uint32_t n = 1;
723    if (factx->n_threads > 1 && kv_rows >= factx->n_threads * 2) {
724        n = factx->n_threads;
725    }
726    uint32_t rows_per_t = hex_align_up(hmx_ceil_div(kv_rows, n), 2);
727    fa_v_int_args_t args = { factx, kv_rows, src_stride, v_src, v_tiles_dst, n_col_tiles, kv_start, rows_per_t };
728    if (n > 1) {
729        work_queue_run(wp, fa_v_interleave_thread, &args, n);
730    } else {
731        fa_v_interleave_thread(1, 0, &args);
732    }
733}
734
735typedef struct {
736    struct hmx_fa_context *   factx;
737    const struct htp_tensor * q;
738    uint32_t                  q_start;
739    uint32_t                  kv_head;
740    uint32_t                  ib3;
741    size_t                    n_rows_g;
742    size_t                    rows_per_t;
743    size_t                    n_rows_q;
744    bool                      q_transposed;
745    atomic_uint               barrier;
746} fa_q_load_args_t;
747
748static void fa_q_load_thread(unsigned int n, unsigned int i, void * data) {
749    fa_q_load_args_t *      args  = (fa_q_load_args_t *) data;
750    struct hmx_fa_context * factx = args->factx;
751
752    const size_t n_rows_g = args->n_rows_g;
753    const size_t G        = factx->G;
754    const size_t DK       = factx->DK;
755
756    // Partition the padded Q rows (g_br) across threads.
757    // Keep start/end even so r and r+1 are always in the same thread's range.
758    const size_t rows_per_t = args->rows_per_t;
759    const size_t start      = (size_t) i * rows_per_t;
760    const size_t end        = hex_smin(start + rows_per_t, factx->g_br);
761
762    struct htp_thread_trace * tr = &factx->octx->ctx->trace[i];
763    htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_FA_Q_PREP, (uint16_t) (args->q_start * G + start));
764
765    // Parallel initialization of per-block state
766    {
767        const uint32_t g_br = factx->g_br;
768        const uint32_t DV   = factx->DV;
769
770        const size_t col_vec_bytes = factx->col_vec_bytes;
771        const size_t d_tile_bytes  = factx->d_tile_bytes;
772
773        // Initialize vtcm_l_vec & vtcm_m_vec
774        const size_t l_bytes_per_t = hex_align_up(col_vec_bytes / n, 128);
775        const size_t l_start       = i * l_bytes_per_t;
776        const size_t l_end         = hex_smin(l_start + l_bytes_per_t, col_vec_bytes);
777
778        const size_t m_bytes_per_t = hex_align_up(col_vec_bytes / n, 128);
779        const size_t m_start       = i * m_bytes_per_t;
780        const size_t m_end         = hex_smin(m_start + m_bytes_per_t, col_vec_bytes);
781
782        if (factx->sinks) {
783            const float * sinks_data = (const float *) (uintptr_t) factx->sinks->data;
784            float *       m_vec      = (float *) factx->vtcm_m_vec;
785            const size_t  r_start    = l_start / sizeof(float);
786            const size_t  r_end      = l_end / sizeof(float);
787            const float   scale_factor = EXP_LOG2E_F;
788
789            const HVX_Vector v_scale = hvx_vec_splat_f32(scale_factor);
790
791            for (size_t r = r_start; r < r_end; r += 32) {
792                HVX_VectorAlias local_m;
793                for (size_t j = 0; j < 32; ++j) {
794                    size_t curr_r = r + j;
795                    if (curr_r < n_rows_g) {
796                        const size_t h_idx = fastmodulo(curr_r, G, &factx->div_G);
797                        const size_t head  = args->kv_head * G + h_idx;
798                        local_m.fp32[j] = sinks_data[head];
799                    } else {
800                        local_m.fp32[j] = HTP_FA_M_INITIAL_VAL;
801                    }
802                }
803                HVX_Vector v_scaled = HVX_OP_MUL_F32(local_m.v, v_scale);
804                *(HVX_Vector *) (m_vec + r) = v_scaled;
805            }
806            if (l_start < col_vec_bytes) {
807                hvx_splat_u8_a((char *) factx->vtcm_l_vec + l_start, 0, l_end - l_start);
808            }
809        } else {
810            if (l_start < col_vec_bytes) {
811                hvx_splat_u8_a((char *) factx->vtcm_l_vec + l_start, 0, l_end - l_start);
812            }
813            if (m_start < col_vec_bytes) {
814                hvx_splat_f32_a((char *) factx->vtcm_m_vec + m_start, HTP_FA_M_INITIAL_VAL, (m_end - m_start) / sizeof(float));
815            }
816        }
817
818        // Zero the whole rescale region: vtcm_d_tiles[0], the optional vtcm_d_tiles[1]
819        // and vtcm_d_inv_l are equal-sized and allocated back to back, so one run covers
820        // them all.  The scatter only ever writes the diagonal, ignore the rest.
821        const size_t d_bytes_per_t = hex_align_up(d_tile_bytes / n, 128);
822        const size_t d_start       = i * d_bytes_per_t;
823        const size_t d_end         = hex_smin(d_start + d_bytes_per_t, d_tile_bytes);
824        if (d_start < d_tile_bytes) {
825            hvx_splat_u8_a((char *) factx->vtcm_d_tiles[0] + d_start, 0, d_end - d_start);
826        }
827    }
828
829    if (start < factx->g_br) {
830        const struct htp_tensor * q       = args->q;
831        const uint32_t            q_start = args->q_start;
832        const uint32_t            kv_head = args->kv_head;
833        const uint32_t            ib3     = args->ib3;
834
835        assert(factx->DK == factx->DV);
836
837        const bool use_q_dma = (factx->vtcm_q_dma != NULL);
838
839        __fp16 * q_tiles = factx->vtcm_q_tiles;
840        if (use_q_dma) {
841            const size_t g_rows_end = hex_smin(end, n_rows_g);
842            const uint32_t d_limit = factx->is_q_fp32 ? DK / 32 : DK / 64;
843
844            uint8_t * q_flat  = (uint8_t *) factx->vtcm_q_dma;
845            if (factx->is_q_fp32) {
846                switch (d_limit) {
847                case 2:  hmx_fa_q_prep_fp32_d2(q_tiles, q_flat, start, end, g_rows_end, DK, G, args->n_rows_q, &factx->div_G, args->q_transposed); break;
848                case 4:  hmx_fa_q_prep_fp32_d4(q_tiles, q_flat, start, end, g_rows_end, DK, G, args->n_rows_q, &factx->div_G, args->q_transposed); break;
849                default: hmx_fa_q_prep_fp32(   q_tiles, q_flat, start, end, g_rows_end, DK, G, args->n_rows_q, &factx->div_G, d_limit, args->q_transposed); break;
850                }
851            } else {
852                switch (d_limit) {
853                case 1:  hmx_fa_q_prep_fp16_d1(q_tiles, q_flat, start, end, g_rows_end, DK, G, args->n_rows_q, &factx->div_G, args->q_transposed); break;
854                case 2:  hmx_fa_q_prep_fp16_d2(q_tiles, q_flat, start, end, g_rows_end, DK, G, args->n_rows_q, &factx->div_G, args->q_transposed); break;
855                default: hmx_fa_q_prep_fp16(   q_tiles, q_flat, start, end, g_rows_end, DK, G, args->n_rows_q, &factx->div_G, d_limit, args->q_transposed); break;
856                }
857            }
858        } else {
859            // Fallback: direct-from-DDR/L2 path
860            hmx_fa_q_prep_fallback(q_tiles, q->data, q->nb[1], q->nb[2], q->nb[3],
861                                   q_start, kv_head, ib3, start, end, n_rows_g, G, DK, factx->is_q_fp32, &factx->div_G);
862        }
863    }
864
865    // Synchronize threads before zeroing out vtcm_o_tiles[0] to prevent race condition
866    if (n > 1) {
867        atomic_fetch_sub(&args->barrier, 1);
868        while (atomic_load(&args->barrier) > 0) {
869            // spin wait
870        }
871    }
872
873    // Zero out vtcm_o_tiles[0] as it was used as temp_q_vtcm
874    {
875        const uint32_t g_br = factx->g_br;
876        const uint32_t DV   = factx->DV;
877        const size_t o_tile_bytes  = factx->o_tile_bytes;
878        const size_t o_bytes_per_t = hex_align_up(o_tile_bytes / n, 128);
879        const size_t o_start       = i * o_bytes_per_t;
880        const size_t o_end         = hex_smin(o_start + o_bytes_per_t, o_tile_bytes);
881        if (o_start < o_tile_bytes) {
882            hvx_splat_u8_a((char *) factx->vtcm_o_tiles[0] + o_start, 0, o_end - o_start);
883        }
884    }
885    htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_FA_Q_PREP, (uint16_t) (args->q_start * G + start));
886}
887
888static void fa_phase_q_load(struct hmx_fa_context *   factx,
889                            const struct htp_tensor * q,
890                            uint32_t                  q_start,
891                            uint32_t                  kv_head,
892                            uint32_t                  ib3,
893                            size_t                    n_rows_g) {
894    work_queue_t wp = factx->octx->ctx->work_queue;
895    uint32_t n = 1;
896    if (factx->n_threads > 1 && n_rows_g >= (size_t) (factx->n_threads * 2)) {
897        n = factx->n_threads;
898    }
899    size_t rows_per_t = hex_align_up(hmx_ceil_div(factx->g_br, n), 2);
900    const uint32_t n_rows_q = hex_smin(factx->Br, factx->neq1 - q_start);
901    fa_q_load_args_t args;
902    args.factx = factx;
903    args.q = q;
904    args.q_start = q_start;
905    args.kv_head = kv_head;
906    args.ib3 = ib3;
907    args.n_rows_g = n_rows_g;
908    args.rows_per_t = rows_per_t;
909    args.n_rows_q = n_rows_q;
910    args.q_transposed = q->nb[1] < q->nb[2];
911    atomic_init(&args.barrier, n);
912    if (n > 1) {
913        work_queue_run(wp, fa_q_load_thread, &args, n);
914    } else {
915        fa_q_load_thread(1, 0, &args);
916    }
917}
918
919typedef struct {
920    struct hmx_fa_context *   factx;
921    const struct htp_tensor * dst;
922    const __fp16 *            o_tile_src;
923    uint32_t                  q_start;
924    uint32_t                  kv_head;
925    uint32_t                  ib3;
926    size_t                    n_rows_g;
927    size_t                    rows_per_t;
928} fa_o_store_args_t;
929
930static void fa_o_store_thread_f32(unsigned int n, unsigned int i, void * data) {
931    fa_o_store_args_t *     args  = (fa_o_store_args_t *) data;
932    struct hmx_fa_context * factx = args->factx;
933
934    const size_t n_rows_g = args->n_rows_g;
935    const size_t G        = factx->G;
936    const size_t DV       = factx->DV;
937
938    const size_t rows_per_t = args->rows_per_t;
939    const size_t start      = (size_t) i * rows_per_t;
940    const size_t end        = hex_smin(start + rows_per_t, n_rows_g);
941
942    if (start >= n_rows_g) {
943        return;
944    }
945
946    struct htp_thread_trace * tr = &factx->octx->ctx->trace[i];
947    htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_O_PROC, (uint16_t) (args->q_start * G + start));
948
949    const struct htp_tensor * dst        = args->dst;
950    const __fp16 *            o_tile_src = args->o_tile_src;
951    const uint32_t            q_start    = args->q_start;
952    const uint32_t            kv_head    = args->kv_head;
953    const uint32_t            ib3        = args->ib3;
954
955    size_t q_idx = fastdiv(start, &factx->div_G);
956    size_t h_idx = fastmodulo(start, G, &factx->div_G);
957
958    for (size_t r = start; r < end; ++r) {
959        float * out = (float *) ((uint8_t *) dst->data + (kv_head * G + h_idx) * dst->nb[1] +
960                                 (q_start + q_idx) * dst->nb[2] + ib3 * dst->nb[3]);
961
962        size_t         r0            = r / HMX_FP16_TILE_N_ROWS;
963        size_t         r1            = r % HMX_FP16_TILE_N_ROWS;
964        const __fp16 * tile_row_base = o_tile_src + r0 * HMX_FP16_TILE_N_ROWS * DV;
965
966        for (uint32_t d = 0; d < DV / 32; ++d) {
967            const HVX_Vector * in_tile = (const HVX_Vector *) (tile_row_base + d * HMX_FP16_TILE_N_ELMS);
968            HVX_VectorPair     vp      = hvx_vec_f16_to_f32_shuff(in_tile[r1 / 2]);
969            if (r1 % 2 == 0) {
970                *(HVX_UVector *) (out + d * 32) = Q6_V_lo_W(vp);
971            } else {
972                *(HVX_UVector *) (out + d * 32) = Q6_V_hi_W(vp);
973            }
974        }
975
976        h_idx++;
977        if (h_idx == G) {
978            h_idx = 0;
979            q_idx++;
980        }
981    }
982    htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_O_PROC, (uint16_t) (args->q_start * G + start));
983}
984
985static void fa_o_store_thread_f16(unsigned int n, unsigned int i, void * data) {
986    fa_o_store_args_t *     args  = (fa_o_store_args_t *) data;
987    struct hmx_fa_context * factx = args->factx;
988
989    const size_t n_rows_g   = args->n_rows_g;
990    const size_t rows_per_t = args->rows_per_t;
991    const size_t G          = factx->G;
992    const size_t DV         = factx->DV;
993    const size_t start      = (size_t) i * rows_per_t;
994    const size_t end        = hex_smin(start + rows_per_t, n_rows_g);
995
996    if (start >= n_rows_g) {
997        return;
998    }
999
1000    struct htp_thread_trace * tr = &factx->octx->ctx->trace[i];
1001    htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_O_PROC, (uint16_t) (args->q_start * G + start));
1002
1003    const struct htp_tensor * dst        = args->dst;
1004    const __fp16 *            o_tile_src = args->o_tile_src;
1005    const uint32_t            q_start    = args->q_start;
1006    const uint32_t            kv_head    = args->kv_head;
1007    const uint32_t            ib3        = args->ib3;
1008
1009    size_t q_idx = fastdiv(start, &factx->div_G);
1010    size_t h_idx = fastmodulo(start, G, &factx->div_G);
1011
1012    for (size_t r = start; r < end; ++r) {
1013        __fp16 * out = (__fp16 *) ((uint8_t *) dst->data + (kv_head * G + h_idx) * dst->nb[1] +
1014                                   (q_start + q_idx) * dst->nb[2] + ib3 * dst->nb[3]);
1015
1016        size_t         r0            = r / HMX_FP16_TILE_N_ROWS;
1017        size_t         r1            = r % HMX_FP16_TILE_N_ROWS;
1018        const __fp16 * tile_row_base = o_tile_src + r0 * HMX_FP16_TILE_N_ROWS * DV;
1019
1020        for (uint32_t d = 0; d < DV / 64; ++d) {
1021            const __fp16 *     in_dtile = tile_row_base + d * HMX_FP16_TILE_N_ELMS * 2;
1022            const HVX_Vector * pv_in0   = ((const HVX_Vector *) in_dtile) + r1 / 2;
1023            const HVX_Vector * pv_in1   = pv_in0 + 16;
1024            HVX_VectorPair     vp       = Q6_W_vdeal_VVR(*pv_in1, *pv_in0, -2);
1025            if (r1 % 2 == 0) {
1026                *(HVX_UVector *) (out + d * 64) = Q6_V_lo_W(vp);
1027            } else {
1028                *(HVX_UVector *) (out + d * 64) = Q6_V_hi_W(vp);
1029            }
1030        }
1031
1032        h_idx++;
1033        if (h_idx == G) {
1034            h_idx = 0;
1035            q_idx++;
1036        }
1037    }
1038    htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_O_PROC, (uint16_t) (args->q_start * G + start));
1039}
1040
1041static void fa_phase_o_store(struct hmx_fa_context *   factx,
1042                             const struct htp_tensor * dst,
1043                             const __fp16 *            o_tile_src,
1044                             uint32_t                  q_start,
1045                             uint32_t                  kv_head,
1046                             uint32_t                  ib3,
1047                             size_t                    n_rows_g) {
1048    work_queue_t wp = factx->octx->ctx->work_queue;
1049    uint32_t n = 1;
1050    if (factx->n_threads > 1 && n_rows_g >= (size_t) (factx->n_threads * 2)) {
1051        n = factx->n_threads;
1052    }
1053    size_t rows_per_t = hmx_ceil_div(n_rows_g, n);
1054    fa_o_store_args_t args = { factx, dst, o_tile_src, q_start, kv_head, ib3, n_rows_g, rows_per_t };
1055    worker_callback_t store_fn = factx->is_dst_fp32 ? fa_o_store_thread_f32 : fa_o_store_thread_f16;
1056    if (n > 1) {
1057        work_queue_run(wp, store_fn, &args, n);
1058    } else {
1059        store_fn(1, 0, &args);
1060    }
1061}
1062
1063typedef struct {
1064    struct hmx_fa_context *   factx;
1065    size_t                    buf_idx;
1066    size_t                    kv_rows;
1067    size_t                    n_rows_g;
1068    size_t                    n_col_tiles;
1069    size_t                    n_tiles_per_bc;
1070    size_t                    n_row_tiles;
1071    size_t                    n_row_tiles_g_br;
1072    uint32_t                  Bc;
1073    uint32_t                  G;
1074    uint32_t                  kv_head;
1075    uint32_t                  kv_start;
1076    uint32_t                  q_start;
1077    uint32_t                  ib3;
1078    bool                      has_alibi;  // true when max_bias != 0 (need slope * mask + add)
1079    __fp16 *                  slopes;
1080    const struct htp_tensor * mask;
1081    const __fp16 *            mask_vtcm;             // VTCM mask buffer base (NULL = DDR fallback)
1082    size_t                    mask_vtcm_row_stride;  // elements (__fp16) per row in VTCM mask buffer
1083    struct fastdiv_values     thread_div;
1084} fa_softmax_args_t;
1085
1086static inline void fa_softmax_impl(
1087    unsigned int n, unsigned int i, void * data,
1088    const bool has_mask,
1089    const bool mask_broadcast,
1090    const bool is_g1,
1091    const bool has_alibi,
1092    const bool has_softcap
1093) {
1094    fa_softmax_args_t *     args  = (fa_softmax_args_t *) data;
1095    struct hmx_fa_context * factx = args->factx;
1096
1097    const size_t n_rows_g       = args->n_rows_g;
1098    const size_t kv_rows        = args->kv_rows;
1099    const size_t Bc             = args->Bc;
1100    const size_t G              = args->G;
1101    const size_t n_tiles_per_bc = args->n_tiles_per_bc;
1102    const size_t n_row_vec_cnt  = hmx_ceil_div(n_rows_g, 64);
1103    const uint32_t im3          = has_mask ? fastmodulo(args->ib3, args->mask->ne[3], &factx->src3_div3) : 0;
1104
1105    size_t vec_start = 0;
1106    size_t vec_end   = n_row_vec_cnt;
1107    if (n > 1) {
1108        const size_t vecs_per_t = fastdiv(n_row_vec_cnt + n - 1, &args->thread_div);
1109        vec_start = i * vecs_per_t;
1110        vec_end   = hex_smin(vec_start + vecs_per_t, n_row_vec_cnt);
1111    }
1112
1113    if (vec_start >= n_row_vec_cnt) {
1114        return;
1115    }
1116
1117    struct htp_thread_trace * tr = &factx->octx->ctx->trace[i];
1118    htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_FA_SFM, (uint16_t) (args->q_start * G + vec_start * 64));
1119
1120    // Per-thread row scratch: thread i uses bufs at offset i * 2 * stride
1121    const size_t row_buf_stride = factx->row_buf_stride;
1122    HVX_Vector * my_row_buf0    = factx->vtcm_row_bufs + i * 2 * row_buf_stride;
1123    HVX_Vector * my_row_buf1    = my_row_buf0 + row_buf_stride;
1124
1125    const HVX_Vector v_neg_inf = Q6_Vh_vsplat_R(0xfbff);
1126
1127    for (size_t r_vec_idx = vec_start; r_vec_idx < vec_end; ++r_vec_idx) {
1128        HVX_Vector rowmax_acc_v = v_neg_inf;
1129        HVX_Vector rowsum_acc_v = Q6_V_vzero();
1130        HVX_Vector m_prev_v0    = factx->vtcm_m_vec[r_vec_idx * 2 + 0];
1131        HVX_Vector m_prev_v1    = factx->vtcm_m_vec[r_vec_idx * 2 + 1];
1132
1133        HVX_Vector v_slopes = Q6_V_vzero();
1134        if (has_alibi) {
1135            v_slopes = hvx_vmem(args->slopes + r_vec_idx * 64);
1136        }
1137
1138        for (uint32_t r_vec_off = 0; r_vec_off < 64; r_vec_off += 2) {
1139            uint32_t r = r_vec_idx * 64 + r_vec_off;
1140            if (r >= hex_align_up(n_rows_g, 2)) {
1141                break;
1142            }
1143
1144            uint32_t r0 = r / HMX_FP16_TILE_N_ROWS;
1145            uint32_t r1 = r % HMX_FP16_TILE_N_ROWS;
1146
1147            const __fp16 * s_ld_base = factx->vtcm_s_tiles[args->buf_idx] + r0 * HMX_FP16_TILE_N_ROWS * Bc;
1148            __fp16 *       p_st_base = factx->vtcm_p_tiles[args->buf_idx] + r0 * HMX_FP16_TILE_N_ROWS * Bc;
1149
1150            // Decode 2 rows from S tiles into per-thread row buffers
1151            if (has_softcap) {
1152                const HVX_Vector v_cap = hvx_vec_splat_f16(factx->logit_softcap);
1153                for (size_t c = 0; c < kv_rows; c += 64) {
1154                    size_t             ci       = c / 64;
1155                    const __fp16 *     in_dtile = s_ld_base + ci * HMX_FP16_TILE_N_ELMS * 2;
1156                    const HVX_Vector * pv_s_in0 = ((const HVX_Vector *) in_dtile) + r1 / 2;
1157                    const HVX_Vector * pv_s_in1 = pv_s_in0 + 16;
1158
1159                    HVX_VectorPair vp_s_drow = Q6_W_vdeal_VVR(*pv_s_in1, *pv_s_in0, -2);
1160                    HVX_Vector     v_s_row0  = Q6_V_lo_W(vp_s_drow);
1161                    HVX_Vector     v_s_row1  = Q6_V_hi_W(vp_s_drow);
1162
1163                    HVX_Vector t0   = hvx_vec_tanh_f16(v_s_row0);
1164                    my_row_buf0[ci] = hvx_vec_mul_f16_f16(t0, v_cap);
1165
1166                    HVX_Vector t1   = hvx_vec_tanh_f16(v_s_row1);
1167                    my_row_buf1[ci] = hvx_vec_mul_f16_f16(t1, v_cap);
1168                }
1169            } else {
1170                size_t c = 0;
1171                for (; c + 64 < kv_rows; c += 128) {
1172                    size_t             ci0       = c / 64;
1173                    size_t             ci1       = ci0 + 1;
1174                    const __fp16 *     in_dtile0 = s_ld_base + ci0 * HMX_FP16_TILE_N_ELMS * 2;
1175                    const __fp16 *     in_dtile1 = s_ld_base + ci1 * HMX_FP16_TILE_N_ELMS * 2;
1176                    const HVX_Vector * pv_s_in0_0 = ((const HVX_Vector *) in_dtile0) + r1 / 2;
1177                    const HVX_Vector * pv_s_in1_0 = pv_s_in0_0 + 16;
1178                    const HVX_Vector * pv_s_in0_1 = ((const HVX_Vector *) in_dtile1) + r1 / 2;
1179                    const HVX_Vector * pv_s_in1_1 = pv_s_in0_1 + 16;
1180
1181                    HVX_VectorPair vp_s_drow0 = Q6_W_vdeal_VVR(*pv_s_in1_0, *pv_s_in0_0, -2);
1182                    my_row_buf0[ci0]          = Q6_V_lo_W(vp_s_drow0);
1183                    my_row_buf1[ci0]          = Q6_V_hi_W(vp_s_drow0);
1184
1185                    HVX_VectorPair vp_s_drow1 = Q6_W_vdeal_VVR(*pv_s_in1_1, *pv_s_in0_1, -2);
1186                    my_row_buf0[ci1]          = Q6_V_lo_W(vp_s_drow1);
1187                    my_row_buf1[ci1]          = Q6_V_hi_W(vp_s_drow1);
1188                }
1189                for (; c < kv_rows; c += 64) {
1190                    size_t             ci       = c / 64;
1191                    const __fp16 *     in_dtile = s_ld_base + ci * HMX_FP16_TILE_N_ELMS * 2;
1192                    const HVX_Vector * pv_s_in0 = ((const HVX_Vector *) in_dtile) + r1 / 2;
1193                    const HVX_Vector * pv_s_in1 = pv_s_in0 + 16;
1194
1195                    HVX_VectorPair vp_s_drow = Q6_W_vdeal_VVR(*pv_s_in1, *pv_s_in0, -2);
1196                    my_row_buf0[ci]          = Q6_V_lo_W(vp_s_drow);
1197                    my_row_buf1[ci]          = Q6_V_hi_W(vp_s_drow);
1198                }
1199            }
1200
1201            // Apply mask & compute rowmax(S)
1202            HVX_Vector v_slope0 = Q6_V_vzero();
1203            HVX_Vector v_slope1 = Q6_V_vzero();
1204            if (has_alibi) {
1205                v_slope0 = hvx_vec_repl_f16(Q6_V_vror_VR(v_slopes, r_vec_off * 2));
1206                v_slope1 = (r + 1 < n_rows_g) ? hvx_vec_repl_f16(Q6_V_vror_VR(v_slopes, (r_vec_off + 1) * 2)) : Q6_V_vzero();
1207            }
1208
1209            const HVX_Vector v_threshold = Q6_Vh_vsplat_R(0xcc00);  // fp16 -16.0
1210
1211            HVX_Vector v_s_rowmax0 = v_neg_inf;
1212            HVX_Vector v_s_rowmax1 = v_neg_inf;
1213            if (has_mask) {
1214                for (size_t c = 0; c < kv_rows; c += 64) {
1215                    size_t         ci          = c / 64;
1216                    const size_t   ne          = hex_smin(kv_rows - c, 64);
1217                    HVX_VectorPred q_tail_keep = Q6_Q_vsetq2_R(ne * sizeof(__fp16));
1218
1219                    HVX_Vector v_mask0, v_mask1;
1220
1221                    if (mask_broadcast) {
1222                        if (is_g1) {
1223                            const size_t qi0 = r + 0;
1224                            v_mask0 = *(const HVX_Vector *) (args->mask_vtcm + qi0 * args->mask_vtcm_row_stride + c);
1225                            v_mask1 = v_neg_inf;
1226                            if (r + 1 < n_rows_g) {
1227                                const size_t qi1 = r + 1;
1228                                v_mask1 = *(const HVX_Vector *) (args->mask_vtcm + qi1 * args->mask_vtcm_row_stride + c);
1229                            }
1230                        } else {
1231                            const size_t qi0 = fastdiv(r + 0, &factx->div_G);
1232                            v_mask0 = *(const HVX_Vector *) (args->mask_vtcm + qi0 * args->mask_vtcm_row_stride + c);
1233                            v_mask1 = v_neg_inf;
1234                            if (r + 1 < n_rows_g) {
1235                                const size_t qi1 = fastdiv(r + 1, &factx->div_G);
1236                                if (qi1 == qi0) {
1237                                    v_mask1 = v_mask0;
1238                                } else {
1239                                    v_mask1 = *(const HVX_Vector *) (args->mask_vtcm + qi1 * args->mask_vtcm_row_stride + c);
1240                                }
1241                            }
1242                        }
1243                    } else {
1244                        // Head-dependent mask: pre-interleaved per row r.
1245                        const size_t r0 = r + 0;
1246                        v_mask0 = *(const HVX_Vector *) (args->mask_vtcm + r0 * args->mask_vtcm_row_stride + c);
1247                        v_mask1 = v_neg_inf;
1248                        if (r + 1 < n_rows_g) {
1249                            const size_t r1 = r + 1;
1250                            v_mask1 = *(const HVX_Vector *) (args->mask_vtcm + r1 * args->mask_vtcm_row_stride + c);
1251                        }
1252                    }
1253
1254                    // Threshold: mask values below -16.0 are treated as -inf (causal mask).
1255                    HVX_VectorPred q_keep0 = Q6_Q_and_QQ(Q6_Q_vcmp_gt_VhfVhf(v_mask0, v_threshold), q_tail_keep);
1256                    HVX_VectorPred q_keep1 = Q6_Q_and_QQ(Q6_Q_vcmp_gt_VhfVhf(v_mask1, v_threshold), q_tail_keep);
1257
1258                    // Scale mask values by log2(e) for base-2 calculations
1259                    const HVX_Vector v_log2e = hvx_vec_splat_f16(EXP_LOG2E_F);
1260                    HVX_Vector v_mask0_scaled = hvx_vec_mul_f16_f16(v_mask0, v_log2e);
1261                    HVX_Vector v_mask1_scaled = hvx_vec_mul_f16_f16(v_mask1, v_log2e);
1262
1263                    if (has_alibi) {
1264                        HVX_Vector v_sm0 = hvx_vec_mul_f16_f16(v_mask0_scaled, v_slope0);
1265                        HVX_Vector v_sm1 = hvx_vec_mul_f16_f16(v_mask1_scaled, v_slope1);
1266                        my_row_buf0[ci]  = Q6_V_vmux_QVV(q_keep0, hvx_vec_add_f16_f16(my_row_buf0[ci], v_sm0), v_neg_inf);
1267                        my_row_buf1[ci]  = Q6_V_vmux_QVV(q_keep1, hvx_vec_add_f16_f16(my_row_buf1[ci], v_sm1), v_neg_inf);
1268                    } else {
1269                        my_row_buf0[ci] = Q6_V_vmux_QVV(q_keep0, hvx_vec_add_f16_f16(my_row_buf0[ci], v_mask0_scaled), v_neg_inf);
1270                        my_row_buf1[ci] = Q6_V_vmux_QVV(q_keep1, hvx_vec_add_f16_f16(my_row_buf1[ci], v_mask1_scaled), v_neg_inf);
1271                    }
1272
1273                    v_s_rowmax0 = Q6_Vhf_vmax_VhfVhf(v_s_rowmax0, my_row_buf0[ci]);
1274                    v_s_rowmax1 = Q6_Vhf_vmax_VhfVhf(v_s_rowmax1, my_row_buf1[ci]);
1275                }
1276            } else {
1277                size_t c = 0;
1278                for (; c + 64 < kv_rows; c += 128) {
1279                    size_t ci0 = c / 64;
1280                    size_t ci1 = ci0 + 1;
1281                    v_s_rowmax0 = Q6_Vhf_vmax_VhfVhf(v_s_rowmax0, my_row_buf0[ci0]);
1282                    v_s_rowmax1 = Q6_Vhf_vmax_VhfVhf(v_s_rowmax1, my_row_buf1[ci0]);
1283                    v_s_rowmax0 = Q6_Vhf_vmax_VhfVhf(v_s_rowmax0, my_row_buf0[ci1]);
1284                    v_s_rowmax1 = Q6_Vhf_vmax_VhfVhf(v_s_rowmax1, my_row_buf1[ci1]);
1285                }
1286                for (; c < kv_rows; c += 64) {
1287                    size_t         ci          = c / 64;
1288                    const size_t   ne          = hex_smin(kv_rows - c, 64);
1289                    HVX_VectorPred q_tail_keep = Q6_Q_vsetq2_R(ne * sizeof(__fp16));
1290                    if (ne < 64) {
1291                        my_row_buf0[ci] = Q6_V_vmux_QVV(q_tail_keep, my_row_buf0[ci], v_neg_inf);
1292                        my_row_buf1[ci] = Q6_V_vmux_QVV(q_tail_keep, my_row_buf1[ci], v_neg_inf);
1293                    }
1294                    v_s_rowmax0 = Q6_Vhf_vmax_VhfVhf(v_s_rowmax0, my_row_buf0[ci]);
1295                    v_s_rowmax1 = Q6_Vhf_vmax_VhfVhf(v_s_rowmax1, my_row_buf1[ci]);
1296                }
1297            }
1298
1299            v_s_rowmax0 = hvx_vec_reduce_max_f16(v_s_rowmax0);
1300            v_s_rowmax1 = hvx_vec_reduce_max_f16(v_s_rowmax1);
1301
1302            // Splat m_prev[r], m_prev[r+1] from the float per-row accumulators and convert to fp16 vectors
1303            HVX_Vector v_m_prev0, v_m_prev1;
1304            if (r_vec_off < 32) {
1305                HVX_Vector v0 = hvx_vec_repl_f32(Q6_V_vror_VR(m_prev_v0, r_vec_off * 4));
1306                v_m_prev0 = hvx_vec_f32_to_f16(v0, v0);
1307                if (r + 1 < n_rows_g) {
1308                    HVX_Vector v1 = hvx_vec_repl_f32(Q6_V_vror_VR(m_prev_v0, (r_vec_off + 1) * 4));
1309                    v_m_prev1 = hvx_vec_f32_to_f16(v1, v1);
1310                } else {
1311                    v_m_prev1 = Q6_V_vzero();
1312                }
1313            } else {
1314                HVX_Vector v0 = hvx_vec_repl_f32(Q6_V_vror_VR(m_prev_v1, (r_vec_off - 32) * 4));
1315                v_m_prev0 = hvx_vec_f32_to_f16(v0, v0);
1316                if (r + 1 < n_rows_g) {
1317                    HVX_Vector v1 = hvx_vec_repl_f32(Q6_V_vror_VR(m_prev_v1, (r_vec_off + 1 - 32) * 4));
1318                    v_m_prev1 = hvx_vec_f32_to_f16(v1, v1);
1319                } else {
1320                    v_m_prev1 = Q6_V_vzero();
1321                }
1322            }
1323
1324            HVX_Vector v_dup_m0 = Q6_Vhf_vmax_VhfVhf(v_m_prev0, v_s_rowmax0);
1325            HVX_Vector v_dup_m1 = Q6_Vhf_vmax_VhfVhf(v_m_prev1, v_s_rowmax1);
1326
1327            // Insert row r, r+1 rowmax into rowmax_acc_v
1328            {
1329                HVX_VectorPred p_start = Q6_Q_vsetq_R(r_vec_off * 2);
1330                HVX_VectorPred p_mid   = Q6_Q_vsetq_R((r_vec_off + 1) * 2);
1331                HVX_VectorPred p_end   = Q6_Q_vsetq2_R((r_vec_off + 2) * 2);
1332                HVX_VectorPred p_lane0 = Q6_Q_and_QQn(p_mid, p_start);
1333                HVX_VectorPred p_lane1 = Q6_Q_and_QQn(p_end, p_mid);
1334                rowmax_acc_v           = Q6_V_vmux_QVV(p_lane0, v_dup_m0, rowmax_acc_v);
1335                rowmax_acc_v           = Q6_V_vmux_QVV(p_lane1, v_dup_m1, rowmax_acc_v);
1336            }
1337
1338            // Compute P = exp(S - m_new)
1339            const HVX_Vector v_zero      = Q6_V_vzero();
1340            HVX_Vector       v_p_rowsum0 = v_zero;
1341            HVX_Vector       v_p_rowsum1 = v_zero;
1342
1343            size_t c = 0;
1344            for (; c + 64 < kv_rows; c += 128) {
1345                size_t     ci0          = c / 64;
1346                size_t     ci1          = ci0 + 1;
1347
1348                HVX_Vector v_s_minus_m0_0 = Q6_Vqf16_vsub_VhfVhf(my_row_buf0[ci0], v_dup_m0);
1349                HVX_Vector v_s_minus_m1_0 = Q6_Vqf16_vsub_VhfVhf(my_row_buf1[ci0], v_dup_m1);
1350                HVX_Vector v_s_minus_m0_1 = Q6_Vqf16_vsub_VhfVhf(my_row_buf0[ci1], v_dup_m0);
1351                HVX_Vector v_s_minus_m1_1 = Q6_Vqf16_vsub_VhfVhf(my_row_buf1[ci1], v_dup_m1);
1352
1353                HVX_Vector v_p_row0_hf_0  = hvx_vec_exp2_f16(Q6_Vhf_equals_Vqf16(v_s_minus_m0_0));
1354                HVX_Vector v_p_row1_hf_0  = hvx_vec_exp2_f16(Q6_Vhf_equals_Vqf16(v_s_minus_m1_0));
1355                HVX_Vector v_p_row0_hf_1  = hvx_vec_exp2_f16(Q6_Vhf_equals_Vqf16(v_s_minus_m0_1));
1356                HVX_Vector v_p_row1_hf_1  = hvx_vec_exp2_f16(Q6_Vhf_equals_Vqf16(v_s_minus_m1_1));
1357
1358                __fp16 *     out_dtile0  = p_st_base + ci0 * HMX_FP16_TILE_N_ELMS * 2;
1359                __fp16 *     out_dtile1  = p_st_base + ci1 * HMX_FP16_TILE_N_ELMS * 2;
1360                HVX_Vector * pv_p_out0_0  = ((HVX_Vector *) out_dtile0) + r1 / 2;
1361                HVX_Vector * pv_p_out1_0  = pv_p_out0_0 + 16;
1362                HVX_Vector * pv_p_out0_1  = ((HVX_Vector *) out_dtile1) + r1 / 2;
1363                HVX_Vector * pv_p_out1_1  = pv_p_out0_1 + 16;
1364
1365                HVX_VectorPair vp_p_dual0 = Q6_W_vshuff_VVR(v_p_row1_hf_0, v_p_row0_hf_0, -2);
1366                *pv_p_out0_0               = Q6_V_lo_W(vp_p_dual0);
1367                *pv_p_out1_0               = Q6_V_hi_W(vp_p_dual0);
1368
1369                HVX_VectorPair vp_p_dual1 = Q6_W_vshuff_VVR(v_p_row1_hf_1, v_p_row0_hf_1, -2);
1370                *pv_p_out0_1               = Q6_V_lo_W(vp_p_dual1);
1371                *pv_p_out1_1               = Q6_V_hi_W(vp_p_dual1);
1372
1373                HVX_VectorPair vp_p0_0 = hvx_vec_f16_to_f32_shuff(v_p_row0_hf_0);
1374                HVX_VectorPair vp_p1_0 = hvx_vec_f16_to_f32_shuff(v_p_row1_hf_0);
1375                HVX_VectorPair vp_p0_1 = hvx_vec_f16_to_f32_shuff(v_p_row0_hf_1);
1376                HVX_VectorPair vp_p1_1 = hvx_vec_f16_to_f32_shuff(v_p_row1_hf_1);
1377
1378                v_p_rowsum0 = Q6_Vqf32_vadd_Vqf32Vqf32(v_p_rowsum0, Q6_Vqf32_vadd_VsfVsf(Q6_V_lo_W(vp_p0_0), Q6_V_hi_W(vp_p0_0)));
1379                v_p_rowsum0 = Q6_Vqf32_vadd_Vqf32Vqf32(v_p_rowsum0, Q6_Vqf32_vadd_VsfVsf(Q6_V_lo_W(vp_p0_1), Q6_V_hi_W(vp_p0_1)));
1380                v_p_rowsum1 = Q6_Vqf32_vadd_Vqf32Vqf32(v_p_rowsum1, Q6_Vqf32_vadd_VsfVsf(Q6_V_lo_W(vp_p1_0), Q6_V_hi_W(vp_p1_0)));
1381                v_p_rowsum1 = Q6_Vqf32_vadd_Vqf32Vqf32(v_p_rowsum1, Q6_Vqf32_vadd_VsfVsf(Q6_V_lo_W(vp_p1_1), Q6_V_hi_W(vp_p1_1)));
1382            }
1383            for (size_t c_rem = c; c_rem < kv_rows; c_rem += 64) {
1384                size_t     ci           = c_rem / 64;
1385                HVX_Vector v_s_minus_m0 = Q6_Vqf16_vsub_VhfVhf(my_row_buf0[ci], v_dup_m0);
1386                HVX_Vector v_s_minus_m1 = Q6_Vqf16_vsub_VhfVhf(my_row_buf1[ci], v_dup_m1);
1387
1388                HVX_Vector v_p_row0_hf  = hvx_vec_exp2_f16(Q6_Vhf_equals_Vqf16(v_s_minus_m0));
1389                HVX_Vector v_p_row1_hf  = hvx_vec_exp2_f16(Q6_Vhf_equals_Vqf16(v_s_minus_m1));
1390                __fp16 *     out_dtile  = p_st_base + ci * HMX_FP16_TILE_N_ELMS * 2;
1391                HVX_Vector * pv_p_out0  = ((HVX_Vector *) out_dtile) + r1 / 2;
1392                HVX_Vector * pv_p_out1  = pv_p_out0 + 16;
1393
1394                HVX_VectorPair vp_p_dual = Q6_W_vshuff_VVR(v_p_row1_hf, v_p_row0_hf, -2);
1395                *pv_p_out0               = Q6_V_lo_W(vp_p_dual);
1396                *pv_p_out1               = Q6_V_hi_W(vp_p_dual);
1397
1398                HVX_VectorPair vp_p0 = hvx_vec_f16_to_f32_shuff(v_p_row0_hf);
1399                HVX_VectorPair vp_p1 = hvx_vec_f16_to_f32_shuff(v_p_row1_hf);
1400
1401                v_p_rowsum0 = Q6_Vqf32_vadd_Vqf32Vqf32(v_p_rowsum0, Q6_Vqf32_vadd_VsfVsf(Q6_V_lo_W(vp_p0), Q6_V_hi_W(vp_p0)));
1402                v_p_rowsum1 = Q6_Vqf32_vadd_Vqf32Vqf32(v_p_rowsum1, Q6_Vqf32_vadd_VsfVsf(Q6_V_lo_W(vp_p1), Q6_V_hi_W(vp_p1)));
1403            }
1404
1405            HVX_Vector rowsum0_sf = hvx_vec_reduce_sum_f32(Q6_Vsf_equals_Vqf32(v_p_rowsum0));
1406            HVX_Vector rowsum1_sf = hvx_vec_reduce_sum_f32(Q6_Vsf_equals_Vqf32(v_p_rowsum1));
1407            {
1408                HVX_Vector rv0_v = hvx_vec_f32_to_f16(rowsum0_sf, rowsum0_sf);
1409                HVX_Vector rv1_v = hvx_vec_f32_to_f16(rowsum1_sf, rowsum1_sf);
1410
1411                HVX_VectorPred p_start = Q6_Q_vsetq_R(r_vec_off * 2);
1412                HVX_VectorPred p_mid   = Q6_Q_vsetq_R((r_vec_off + 1) * 2);
1413                HVX_VectorPred p_end   = Q6_Q_vsetq2_R((r_vec_off + 2) * 2);
1414                HVX_VectorPred p_lane0 = Q6_Q_and_QQn(p_mid, p_start);
1415                HVX_VectorPred p_lane1 = Q6_Q_and_QQn(p_end, p_mid);
1416                rowsum_acc_v           = Q6_V_vmux_QVV(p_lane0, rv0_v, rowsum_acc_v);
1417                rowsum_acc_v           = Q6_V_vmux_QVV(p_lane1, rv1_v, rowsum_acc_v);
1418            }
1419        }
1420
1421        // Inline fa_ml_update_and_build_d for this vector (lock-free and in parallel)
1422        HVX_VectorPair rowmax_acc_pair    = hvx_vec_f16_to_f32(rowmax_acc_v);
1423        HVX_Vector     v_rowmax_acc_f32_0 = Q6_V_lo_W(rowmax_acc_pair);
1424        HVX_Vector     v_rowmax_acc_f32_1 = Q6_V_hi_W(rowmax_acc_pair);
1425
1426        HVX_Vector v_m_curr0 = Q6_Vsf_vmax_VsfVsf(m_prev_v0, v_rowmax_acc_f32_0);
1427        HVX_Vector v_m_curr1 = Q6_Vsf_vmax_VsfVsf(m_prev_v1, v_rowmax_acc_f32_1);
1428
1429        HVX_Vector v_m_diff0 = HVX_OP_SUB_F32(m_prev_v0, v_m_curr0);
1430        HVX_Vector v_m_diff1 = HVX_OP_SUB_F32(m_prev_v1, v_m_curr1);
1431
1432        HVX_Vector v_m_diff_f16   = hvx_vec_f32_to_f16(v_m_diff0, v_m_diff1);
1433        HVX_Vector exp_m_diff_f16 = hvx_vec_exp2_f16(v_m_diff_f16);
1434
1435        HVX_VectorPair exp_m_diff_pair = hvx_vec_f16_to_f32(exp_m_diff_f16);
1436        HVX_Vector exp_m_diff0 = Q6_V_lo_W(exp_m_diff_pair);
1437        HVX_Vector exp_m_diff1 = Q6_V_hi_W(exp_m_diff_pair);
1438
1439        HVX_VectorPair rowsum_acc_pair = hvx_vec_f16_to_f32(rowsum_acc_v);
1440        HVX_Vector     v_rowsum_acc_f32_0 = Q6_V_lo_W(rowsum_acc_pair);
1441        HVX_Vector     v_rowsum_acc_f32_1 = Q6_V_hi_W(rowsum_acc_pair);
1442
1443        HVX_Vector v_l_curr0;
1444        HVX_Vector v_l_curr1;
1445        if (args->kv_start == 0 && factx->sinks != NULL) {
1446            // First KV block with sinks: m_prev holds the seeded sink value (not -inf),
1447            // so exp_m_diff = exp2(sink - m_curr) is the sink's contribution to the
1448            // denominator. l_prev is 0 here, so add exp_m_diff directly instead of
1449            // multiplying the (uninitialized) l_prev term.
1450            v_l_curr0 = HVX_OP_ADD_F32(exp_m_diff0, v_rowsum_acc_f32_0);
1451            v_l_curr1 = HVX_OP_ADD_F32(exp_m_diff1, v_rowsum_acc_f32_1);
1452        } else {
1453            HVX_Vector l_prev_v0 = factx->vtcm_l_vec[r_vec_idx * 2 + 0];
1454            HVX_Vector l_prev_v1 = factx->vtcm_l_vec[r_vec_idx * 2 + 1];
1455            v_l_curr0 = HVX_OP_ADD_F32(HVX_OP_MUL_F32(l_prev_v0, exp_m_diff0), v_rowsum_acc_f32_0);
1456            v_l_curr1 = HVX_OP_ADD_F32(HVX_OP_MUL_F32(l_prev_v1, exp_m_diff1), v_rowsum_acc_f32_1);
1457        }
1458
1459        factx->vtcm_m_vec[r_vec_idx * 2 + 0] = v_m_curr0;
1460        factx->vtcm_m_vec[r_vec_idx * 2 + 1] = v_m_curr1;
1461        factx->vtcm_l_vec[r_vec_idx * 2 + 0] = v_l_curr0;
1462        factx->vtcm_l_vec[r_vec_idx * 2 + 1] = v_l_curr1;
1463
1464        // Build diagonal tile D = diag(exp(m_diff))
1465        const HVX_Vector     v_offsets = *(const HVX_Vector *) d_tile_scatter_offsets;
1466        const HVX_VectorPred q_32_mask = Q6_Q_vsetq_R(32 * sizeof(__fp16));
1467        HVX_Vector           v_exp_m_diff = exp_m_diff_f16;
1468
1469        __fp16 * const d_tiles_out = factx->vtcm_d_tiles[args->buf_idx];
1470
1471        size_t t0 = r_vec_idx * 2;
1472        if (t0 < args->n_row_tiles) {
1473            const HVX_Vector v_content = v_exp_m_diff;
1474            __fp16 *         out_base  = d_tiles_out + t0 * HMX_FP16_TILE_N_ELMS;
1475            Q6_vscatter_QRMVhV(q_32_mask, (size_t) out_base, HMX_FP16_TILE_SIZE - 1, v_offsets, v_content);
1476        }
1477
1478        size_t t1 = r_vec_idx * 2 + 1;
1479        if (t1 < args->n_row_tiles) {
1480            const HVX_Vector v_content = Q6_V_vror_VR(v_exp_m_diff, 64);
1481            __fp16 *         out_base  = d_tiles_out + t1 * HMX_FP16_TILE_N_ELMS;
1482            Q6_vscatter_QRMVhV(q_32_mask, (size_t) out_base, HMX_FP16_TILE_SIZE - 1, v_offsets, v_content);
1483        }
1484    }
1485    htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_FA_SFM, (uint16_t) (args->q_start * G + vec_start * 64));
1486}
1487
1488static void fa_softmax_thread_nomask(unsigned int n, unsigned int i, void * data) {
1489    fa_softmax_impl(n, i, data,
1490                    /*has_mask=*/false,
1491                    /*mask_broadcast=*/false,
1492                    /*is_g1=*/false,
1493                    /*has_alibi=*/false,
1494                    /*has_softcap=*/false);
1495}
1496
1497static void fa_softmax_thread_mask_broadcast_g1(unsigned int n, unsigned int i, void * data) {
1498    fa_softmax_impl(n, i, data,
1499                    /*has_mask=*/true,
1500                    /*mask_broadcast=*/true,
1501                    /*is_g1=*/true,
1502                    /*has_alibi=*/false,
1503                    /*has_softcap=*/false);
1504}
1505
1506static void fa_softmax_thread_mask_broadcast_gn(unsigned int n, unsigned int i, void * data) {
1507    fa_softmax_impl(n, i, data,
1508                    /*has_mask=*/true,
1509                    /*mask_broadcast=*/true,
1510                    /*is_g1=*/false,
1511                    /*has_alibi=*/false,
1512                    /*has_softcap=*/false);
1513}
1514
1515static void fa_softmax_thread(unsigned int n, unsigned int i, void * data) {
1516    fa_softmax_args_t *     args  = (fa_softmax_args_t *) data;
1517    struct hmx_fa_context * factx = args->factx;
1518
1519    const bool has_mask       = (args->mask != NULL);
1520    const bool mask_broadcast = factx->mask_broadcast;
1521    const bool is_g1          = (args->G == 1);
1522    const bool has_alibi      = args->has_alibi;
1523    const bool has_softcap    = (factx->logit_softcap != 0.0f);
1524
1525    fa_softmax_impl(n, i, data, has_mask, mask_broadcast, is_g1, has_alibi, has_softcap);
1526}
1527
1528static __attribute__((noinline)) void fa_build_d_diag_inv_l(struct hmx_fa_context * factx,
1529                                                            size_t                  n_row_tiles,
1530                                                            size_t                  n_row_tiles_g_br) {
1531    const HVX_Vector     v_offsets = *(const HVX_Vector *) d_tile_scatter_offsets;
1532    const HVX_VectorPred q_32_mask = Q6_Q_vsetq_R(32 * sizeof(__fp16));
1533    const HVX_Vector     one       = hvx_vec_splat_f32(1.0f);
1534
1535    HVX_Vector v_content = Q6_V_vzero();
1536    for (size_t i = 0; i < n_row_tiles; ++i) {
1537        if ((i % 2) == 0) {
1538            HVX_Vector inv_lo = HVX_OP_MUL_F32(one, hvx_vec_inverse_f32(factx->vtcm_l_vec[i]));
1539            HVX_Vector inv_hi = (i + 1 < n_row_tiles) ? HVX_OP_MUL_F32(one, hvx_vec_inverse_f32(factx->vtcm_l_vec[i + 1])) : Q6_V_vzero();
1540            v_content = hvx_vec_f32_to_f16(inv_lo, inv_hi);
1541        } else {
1542            v_content = Q6_V_vror_VR(v_content, 64);
1543        }
1544
1545        __fp16 * out_base = factx->vtcm_d_inv_l + i * HMX_FP16_TILE_N_ELMS;
1546        Q6_vscatter_QRMVhV(q_32_mask, (size_t) out_base, HMX_FP16_TILE_SIZE - 1, v_offsets, v_content);
1547    }
1548}
1549
1550static void fa_phase_softmax_and_build_d(struct hmx_fa_context * factx,
1551                                         fa_softmax_args_t *     sargs,
1552                                         size_t                  n_row_tiles,
1553                                         size_t                  n_row_tiles_g_br) {
1554    work_queue_t wp = factx->octx->ctx->work_queue;
1555    const size_t n_row_vec_cnt = hmx_ceil_div(sargs->n_rows_g, 64);
1556
1557    worker_callback_t softmax_fn = fa_softmax_thread;
1558    if (sargs->mask == NULL && factx->logit_softcap == 0.0f && !sargs->has_alibi) {
1559        softmax_fn = fa_softmax_thread_nomask;
1560    } else if (sargs->mask != NULL && factx->mask_broadcast && factx->logit_softcap == 0.0f && !sargs->has_alibi) {
1561        if (sargs->G == 1) {
1562            softmax_fn = fa_softmax_thread_mask_broadcast_g1;
1563        } else {
1564            softmax_fn = fa_softmax_thread_mask_broadcast_gn;
1565        }
1566    }
1567
1568    if (factx->n_threads > 1 && n_row_vec_cnt >= 2) {
1569        uint32_t n_use = (uint32_t) hex_smin((size_t) factx->n_threads, n_row_vec_cnt);
1570        sargs->thread_div = init_fastdiv_values(n_use);
1571        work_queue_run(wp, softmax_fn, sargs, n_use);
1572    } else {
1573        softmax_fn(1, 0, sargs);
1574    }
1575}
1576
1577// ============================================================================
1578// HMX job structs and worker functions
1579// ============================================================================
1580
1581typedef struct {
1582    const __fp16 * q_tiles;
1583    const __fp16 * k_tiles;
1584    __fp16 *       s_tiles;
1585    size_t         n_row_tiles;
1586    size_t         n_col_tiles;
1587    size_t         n_dot_tiles;  // DK / 32
1588    size_t         n_tiles_per_bc;
1589    uint8_t *      hmx_scales;
1590} hmx_fa_qk_job_t;
1591
1592static void hmx_fa_qk_dot_worker(void * data) {
1593    hmx_fa_qk_job_t * job            = (hmx_fa_qk_job_t *) data;
1594    const size_t      n_row_tiles    = job->n_row_tiles;
1595    const size_t      n_col_tiles    = job->n_col_tiles;
1596    const size_t      n_dot_tiles    = job->n_dot_tiles;
1597    const size_t      n_tiles_per_bc = job->n_tiles_per_bc;
1598    const __fp16 * restrict q_tiles  = job->q_tiles;
1599    const __fp16 * restrict k_tiles  = job->k_tiles;
1600    __fp16 * restrict s_tiles        = job->s_tiles;
1601    __builtin_assume(n_row_tiles > 0);
1602    __builtin_assume(n_col_tiles > 0);
1603    __builtin_assume(n_dot_tiles > 0);
1604
1605    asm volatile(HMX_SET_BIAS("%0") :: "r"((unsigned int)job->hmx_scales));
1606    const size_t dot_stride = n_dot_tiles * HMX_FP16_TILE_N_ELMS;
1607    for (size_t r = 0; r < n_row_tiles; ++r) {
1608        const __fp16 * row_tiles = q_tiles + r * dot_stride;
1609        const __fp16 * col_tiles = k_tiles;
1610        __fp16 *       out_tile  = s_tiles + r * n_tiles_per_bc * HMX_FP16_TILE_N_ELMS;
1611
1612        for (size_t c = 0; c < n_col_tiles; ++c) {
1613            hmx_fa_qk_dot_tile(row_tiles, col_tiles, out_tile, n_dot_tiles);
1614            col_tiles += dot_stride;
1615            out_tile  += HMX_FP16_TILE_N_ELMS;
1616        }
1617    }
1618}
1619
1620typedef struct {
1621    __fp16 *       o_curr;
1622    const __fp16 * o_prev;
1623    const __fp16 * p_tiles;
1624    const __fp16 * v_tiles;
1625    const __fp16 * d_tiles;
1626    uint8_t *      hmx_scales;
1627    size_t         n_row_tiles;
1628    size_t         n_col_tiles;
1629    size_t         n_row_tiles_g_br;
1630    size_t         n_tiles_per_bc;
1631    size_t         DV;
1632} hmx_fa_o_update_job_t;
1633
1634static void hmx_fa_o_update_worker(void * data) {
1635    hmx_fa_o_update_job_t * job              = (hmx_fa_o_update_job_t *) data;
1636    const size_t            n_row_tiles      = job->n_row_tiles;
1637    const size_t            n_col_tiles      = job->n_col_tiles;
1638    const size_t            n_row_tiles_g_br = job->n_row_tiles_g_br;
1639    const size_t            n_tiles_per_bc   = job->n_tiles_per_bc;
1640    const size_t            DV_tiles         = job->DV / 32;
1641    const __fp16 * restrict d_tiles          = job->d_tiles;
1642    const __fp16 * restrict p_tiles          = job->p_tiles;
1643    const __fp16 * restrict v_tiles          = job->v_tiles;
1644    const __fp16 * restrict o_prev           = job->o_prev;
1645    __fp16 * restrict o_curr                 = job->o_curr;
1646    __builtin_assume(n_row_tiles > 0);
1647    __builtin_assume(n_col_tiles > 0);
1648    __builtin_assume(DV_tiles > 0);
1649
1650    asm volatile(HMX_SET_BIAS("%0") :: "r"((unsigned int)job->hmx_scales));
1651    const size_t o_stride = n_row_tiles_g_br * HMX_FP16_TILE_N_ELMS;
1652    const size_t v_stride = n_tiles_per_bc * HMX_FP16_TILE_N_ELMS;
1653    for (size_t r = 0; r < n_row_tiles; ++r) {
1654        const __fp16 * d_diag     = d_tiles + r * HMX_FP16_TILE_N_ELMS;
1655        const __fp16 * p_tile_in  = p_tiles + (r * n_tiles_per_bc) * HMX_FP16_TILE_N_ELMS;
1656        const __fp16 * o_rc       = o_prev + r * HMX_FP16_TILE_N_ELMS;
1657        const __fp16 * v_tile_in  = v_tiles;
1658        __fp16       * o_tile_out = o_curr + r * HMX_FP16_TILE_N_ELMS;
1659
1660        for (size_t c = 0; c < DV_tiles; ++c) {
1661            hmx_fa_o_update_tile(d_diag, o_rc, p_tile_in, v_tile_in, o_tile_out, n_col_tiles);
1662            o_rc       += o_stride;
1663            v_tile_in  += v_stride;
1664            o_tile_out += o_stride;
1665        }
1666    }
1667}
1668
1669typedef struct {
1670    __fp16 *       o_curr;   // output (row-major tile layout)
1671    const __fp16 * o_prev;   // input (column-major tile layout)
1672    const __fp16 * d_tiles;  // diag(1/l) tiles
1673    uint8_t *      hmx_scales;
1674    size_t         n_row_tiles;
1675    size_t         n_row_tiles_g_br;
1676    size_t         DV;
1677} hmx_fa_o_norm_job_t;
1678
1679static void hmx_fa_o_norm_worker(void * data) {
1680    hmx_fa_o_norm_job_t * job              = (hmx_fa_o_norm_job_t *) data;
1681    const size_t          n_row_tiles      = job->n_row_tiles;
1682    const size_t          n_row_tiles_g_br = job->n_row_tiles_g_br;
1683    const size_t          DV_tiles         = job->DV / 32;
1684    const __fp16 * restrict d_tiles        = job->d_tiles;
1685    const __fp16 * restrict o_prev         = job->o_prev;
1686    __fp16 * restrict o_curr               = job->o_curr;
1687    __builtin_assume(n_row_tiles > 0);
1688    __builtin_assume(DV_tiles > 0);
1689
1690    asm volatile(HMX_SET_BIAS("%0") :: "r"((unsigned int)job->hmx_scales));
1691    const size_t o_stride = n_row_tiles_g_br * HMX_FP16_TILE_N_ELMS;
1692    for (size_t r = 0; r < n_row_tiles; ++r) {
1693        const __fp16 * d_diag = d_tiles + r * HMX_FP16_TILE_N_ELMS;
1694        const __fp16 * o_rc = o_prev + r * HMX_FP16_TILE_N_ELMS;
1695        __fp16 *       o_out = o_curr + r * DV_tiles * HMX_FP16_TILE_N_ELMS;
1696
1697        for (size_t c = 0; c < DV_tiles; ++c) {
1698            hmx_fa_o_norm_tile(d_diag, o_rc, o_out);
1699            o_rc  += o_stride;
1700            o_out += HMX_FP16_TILE_N_ELMS;
1701        }
1702    }
1703}
1704
1705// Populate per-GQA-row ALiBi slopes for a given KV head.
1706static __attribute__((noinline)) void fa_compute_slopes(
1707                              const struct hmx_fa_context * factx,
1708                              uint32_t                      kv_head,
1709                              size_t                        n_rows_g) {
1710    __fp16 * slopes = factx->vtcm_slopes;
1711    if (factx->max_bias == 0.0f) {
1712        hvx_splat_f16_a(slopes, 1.0f, n_rows_g);
1713        return;
1714    }
1715
1716    const uint32_t G           = factx->G;
1717    const uint32_t n_head_log2 = factx->n_head_log2;
1718    const float    m0          = factx->m0;
1719    const float    m1          = factx->m1;
1720
1721    __fp16 temp_slopes[512] __attribute__((aligned(128)));
1722    if (G <= 32) {
1723        // Fast path: Compute G unique slope values in vector registers
1724        HVX_Vector v_val = hvx_alibi_slopes(kv_head, G, n_head_log2, m0, m1);
1725
1726        __fp16 temp_slopes_aligned[64] __attribute__((aligned(128)));
1727        hvx_vmem(temp_slopes_aligned) = hvx_vec_f32_to_f16(v_val, Q6_V_vzero());
1728
1729        for (uint32_t i = 0; i < G; ++i) {
1730            temp_slopes[i] = temp_slopes_aligned[i];
1731        }
1732    } else {
1733        // Fallback path: G > 32 (rare configurations)
1734        for (uint32_t i = 0; i < G; ++i) {
1735            temp_slopes[i] = (__fp16)alibi_slope(kv_head * G + i, n_head_log2, m0, m1);
1736        }
1737    }
1738
1739    // Allocate stack buffer to avoid scalar writes to VTCM (which generates L2 misses)
1740    __fp16 local_slopes[n_rows_g] __attribute__((aligned(128)));
1741    for (size_t r = 0; r < n_rows_g; ++r) {
1742        local_slopes[r] = temp_slopes[fastmodulo(r, G, &factx->div_G)];
1743    }
1744
1745    // Copy to VTCM slopes using HVX block copy (both are aligned to 128 bytes)
1746    hvx_copy_f16_aa((uint8_t *)slopes, (const uint8_t *)local_slopes, n_rows_g);
1747}
1748
1749static void fa_push_mask_dma_gqa(
1750    dma_queue *               dma,
1751    const struct htp_tensor * mask,
1752    uint32_t                  q_start,
1753    uint32_t                  im3,
1754    uint32_t                  kv_start,
1755    uint32_t                  kv_head,
1756    uint32_t                  G,
1757    uint32_t                  m_line_bytes,
1758    uint32_t                  kv_rows,
1759    uint32_t                  n_rows_q,
1760    struct hmx_fa_context *   factx
1761) {
1762    for (uint32_t g = 0; g < G; ++g) {
1763        const uint32_t h_idx = kv_head * G + g;
1764        const uint32_t im2 = fastmodulo(h_idx, mask->ne[2], &factx->src3_div2);
1765        const uint8_t * ms_src = (const uint8_t *) mask->data + q_start * mask->nb[1] +
1766                                 im2 * mask->nb[2] + im3 * mask->nb[3] + kv_start * sizeof(__fp16);
1767        uint8_t * ms_dst = (uint8_t *) factx->vtcm_mask_buf + g * m_line_bytes;
1768        dma_queue_push(dma, dma_make_ptr(ms_dst, ms_src), G * m_line_bytes, mask->nb[1], kv_rows * sizeof(__fp16), n_rows_q);
1769    }
1770}
1771
1772static void fa_pop_mask_dma_gqa(dma_queue * dma, uint32_t G) {
1773    for (uint32_t g = 0; g < G; ++g) {
1774        dma_queue_pop(dma);
1775    }
1776}
1777
1778static inline void fa_prefetch_block(dma_queue * dma, const struct htp_tensor * k, const struct htp_tensor * v, const struct htp_tensor * mask,
1779                                     uint32_t b, size_t Bc, size_t size_k_row_padded, size_t size_k_row, size_t size_v_row_padded, size_t size_v_row,
1780                                     uint32_t ik2, uint32_t ik3, uint32_t iv2, uint32_t iv3, uint32_t q_start, uint32_t im3, uint32_t kv_head, uint32_t G,
1781                                     size_t m_line_bytes, size_t n_rows_q, size_t nek1, size_t prefetch_buf, struct hmx_fa_context * factx) {
1782    const uint32_t  prefetch_start = b * Bc;
1783    const uint32_t  prefetch_rows  = hex_smin(Bc, nek1 - prefetch_start);
1784    const uint8_t * k_prefetch_src = (const uint8_t *) k->data + prefetch_start * k->nb[1] + ik2 * k->nb[2] + ik3 * k->nb[3];
1785    dma_queue_push(dma, dma_make_ptr(factx->vtcm_k_fp16[prefetch_buf], k_prefetch_src), size_k_row_padded, k->nb[1], size_k_row, prefetch_rows);
1786    const uint8_t * v_prefetch_src = (const uint8_t *) v->data + prefetch_start * v->nb[1] + iv2 * v->nb[2] + iv3 * v->nb[3];
1787    dma_queue_push(dma, dma_make_ptr(factx->vtcm_v_fp16[prefetch_buf], v_prefetch_src), size_v_row_padded, v->nb[1], size_v_row, prefetch_rows);
1788
1789    if (mask) {
1790        if (__builtin_expect(factx->mask_broadcast, true)) {
1791            const uint8_t * ms_src = (const uint8_t *) mask->data + q_start * mask->nb[1] + im3 * mask->nb[3] + prefetch_start * sizeof(__fp16);
1792            dma_cache_push(dma, &factx->m_cache, ms_src, m_line_bytes, mask->nb[1], prefetch_rows * sizeof(__fp16), n_rows_q);
1793        } else {
1794            fa_push_mask_dma_gqa(dma, mask, q_start, im3, prefetch_start, kv_head, G, m_line_bytes, prefetch_rows, n_rows_q, factx);
1795        }
1796    }
1797}
1798
1799// ============================================================================
1800// Core HMX flash attention algorithm (GQA-merged)
1801// ============================================================================
1802
1803int hmx_flash_attn_ext(struct htp_ops_context * octx) {
1804    struct htp_thread_trace * tr_hvx = &octx->ctx->trace[0];
1805    struct htp_thread_trace * tr_hmx = &octx->ctx->trace[HTP_MAX_NTHREADS];
1806    const struct htp_tensor * q    = octx->src[0];
1807    const struct htp_tensor * k    = octx->src[1];
1808    const struct htp_tensor * v    = octx->src[2];
1809    const struct htp_tensor * mask = (octx->src[3] && octx->src[3]->data) ? octx->src[3] : NULL;
1810    const struct htp_tensor * dst  = octx->dst;
1811
1812    struct htp_context * const ctx = octx->ctx;
1813
1814    if (!ctx->hmx_enabled) {
1815        return HTP_STATUS_NO_SUPPORT;
1816    }
1817
1818    // Dimensions
1819    const uint32_t neq0 = q->ne[0];  // head_dim (DK)
1820    const uint32_t neq1 = q->ne[1];  // n_tokens
1821    const uint32_t neq2 = q->ne[2];  // n_heads
1822    const uint32_t neq3 = q->ne[3];  // n_seqs
1823
1824    const uint32_t nek0 = k->ne[0];  // head_dim
1825    const uint32_t nek1 = k->ne[1];  // kv_len
1826
1827    const uint32_t nev0 = v->ne[0];  // head_dim (DV)
1828
1829    const uint32_t DK = neq0;
1830    const uint32_t DV = nev0;
1831
1832    // HMX requires head_dim to be multiple of 32
1833    if (DK % 32 != 0 || DV % 32 != 0) {
1834        return HTP_STATUS_NO_SUPPORT;
1835    }
1836
1837    const struct htp_fa_kernel_params * kparams = (const struct htp_fa_kernel_params *) octx->kernel_params;
1838    const uint32_t n_kv_heads = k->ne[2];
1839
1840    // ======== Build context ========
1841    struct hmx_fa_context factx;
1842    memset(&factx, 0, sizeof(factx));
1843    factx.octx           = octx;
1844    factx.sinks          = octx->src[4];  // NULL if this op has no attention sinks
1845    factx.k              = k;
1846    factx.v              = v;
1847    factx.n_threads      = kparams->n_threads;
1848    factx.DK             = DK;
1849    factx.DV             = DV;
1850    factx.n_kv           = nek1;
1851    factx.n_kv_heads     = n_kv_heads;
1852    factx.n_heads        = neq2;
1853    factx.G              = kparams->G;
1854    factx.div_G          = kparams->u.hmx.div_G;
1855    factx.neq1           = neq1;
1856    factx.Br             = kparams->Br;
1857    factx.Bc             = kparams->Bc;
1858    factx.g_br           = kparams->u.hmx.g_br;
1859    factx.n_kv_blocks    = kparams->n_kv_blocks;
1860    factx.is_q_fp32      = (kparams->is_q_fp32 != 0);
1861    factx.is_dst_fp32    = (kparams->is_dst_fp32 != 0);
1862    factx.pipeline       = (kparams->u.hmx.pipeline != 0);
1863    factx.mask_broadcast = (kparams->u.hmx.mask_broadcast != 0);
1864    if (mask) {
1865        factx.src3_div2  = kparams->src3_div2;
1866        factx.src3_div3  = kparams->src3_div3;
1867    }
1868
1869    if (kparams->logit_softcap == 0.0f) {
1870        factx.scale = (__fp16) (kparams->scale * EXP_LOG2E_F);  // log2(e)
1871    } else {
1872        factx.scale = (__fp16) kparams->scale;
1873    }
1874    factx.max_bias      = kparams->max_bias;
1875    factx.logit_softcap = (__fp16) (kparams->logit_softcap * EXP_LOG2E_F);
1876
1877    factx.n_head_log2 = kparams->n_head_log2;
1878    factx.m0          = kparams->m0;
1879    factx.m1          = kparams->m1;
1880
1881    const uint32_t Br = factx.Br;
1882    const uint32_t Bc = factx.Bc;
1883    const uint32_t g_br = factx.g_br;
1884    const bool pipeline = factx.pipeline;
1885    const uint32_t n_threads = factx.n_threads;
1886    const uint32_t G = factx.G;
1887
1888    // Multi-device: split Q blocks across devices
1889    const uint32_t n_q_blocks = (neq1 + Br - 1) / Br;
1890    uint32_t q_start_min = 0;
1891    uint32_t q_start_max = neq1;
1892
1893    if (octx->ctx->mdev.count > 1) {
1894        const struct htp_tensor_mdev_range range = htp_tensor_mdev_partition(n_q_blocks, htp_tensor_mdev_data_aligned(dst) ? 1 : 0, octx->ctx->mdev.idx, octx->ctx->mdev.count, &octx->ctx->mdev.count_div);
1895        const uint32_t block_start = range.start;
1896        const uint32_t block_end   = range.start + range.count;
1897
1898        if (block_start >= block_end) {
1899            return HTP_STATUS_OK;
1900        }
1901
1902        q_start_min = block_start * Br;
1903        q_start_max = MIN(block_end * Br, neq1);
1904    }
1905
1906    // ======== VTCM allocation (GQA-aware) ========
1907    // K/V row sizes drive the DMA descriptors (not the VTCM layout) and are used
1908    // throughout the KV loop below.
1909    const size_t size_k_row        = htp_tensor_get_row_size(k->type, DK);
1910    const size_t size_v_row        = htp_tensor_get_row_size(v->type, DV);
1911    const size_t size_k_row_padded = hex_round_up(DK * sizeof(__fp16), 128);
1912    const size_t size_v_row_padded = hex_round_up(DV * sizeof(__fp16), 128);
1913
1914    // Build the VTCM layout once (shared with the host estimator) and place every
1915    // scratch buffer at its computed offset.
1916    struct hmx_fa_vtcm_layout L;
1917    hmx_fa_vtcm_layout_build(&L, G, DK, DV, Br, Bc, n_threads, pipeline, factx.is_q_fp32);
1918
1919    if (L.total_bytes > ctx->vtcm_size) {
1920        return HTP_STATUS_VTCM_TOO_SMALL;
1921    }
1922
1923    uint8_t * const base = ctx->vtcm_base;
1924
1925    factx.vtcm_q_dma          = VTCM_LAYOUT_PTR(__fp16, base, L.off_q_dma);
1926    factx.vtcm_q_tiles        = VTCM_LAYOUT_PTR(__fp16, base, L.off_q_tiles);
1927    factx.vtcm_o_tiles[0]     = VTCM_LAYOUT_PTR(__fp16, base, L.off_o_tiles[0]);
1928    factx.vtcm_o_tiles[1]     = VTCM_LAYOUT_PTR(__fp16, base, L.off_o_tiles[1]);
1929    factx.vtcm_k_fp16[0]      = VTCM_LAYOUT_PTR(__fp16, base, L.off_k_fp16[0]);
1930    factx.vtcm_k_fp16[1]      = VTCM_LAYOUT_PTR(__fp16, base, L.off_k_fp16[1]);
1931    factx.vtcm_v_fp16[0]      = VTCM_LAYOUT_PTR(__fp16, base, L.off_v_fp16[0]);
1932    factx.vtcm_v_fp16[1]      = VTCM_LAYOUT_PTR(__fp16, base, L.off_v_fp16[1]);
1933    factx.vtcm_k_tiles[0]     = VTCM_LAYOUT_PTR(__fp16, base, L.off_k_tiles[0]);
1934    factx.vtcm_k_tiles[1]     = VTCM_LAYOUT_PTR_OPTIONAL(__fp16, base, L.off_k_tiles[1], pipeline);
1935    factx.vtcm_v_tiles[0]     = VTCM_LAYOUT_PTR(__fp16, base, L.off_v_tiles[0]);
1936    factx.vtcm_v_tiles[1]     = VTCM_LAYOUT_PTR_OPTIONAL(__fp16, base, L.off_v_tiles[1], pipeline);
1937    factx.vtcm_s_tiles[0]     = VTCM_LAYOUT_PTR(__fp16, base, L.off_s_tiles[0]);
1938    factx.vtcm_s_tiles[1]     = VTCM_LAYOUT_PTR_OPTIONAL(__fp16, base, L.off_s_tiles[1], pipeline);
1939    factx.vtcm_p_tiles[0]     = VTCM_LAYOUT_PTR(__fp16, base, L.off_p_tiles[0]);
1940    factx.vtcm_p_tiles[1]     = VTCM_LAYOUT_PTR_OPTIONAL(__fp16, base, L.off_p_tiles[1], pipeline);
1941    factx.vtcm_d_tiles[0]     = VTCM_LAYOUT_PTR(__fp16, base, L.off_d_tiles[0]);
1942    factx.vtcm_d_tiles[1]     = VTCM_LAYOUT_PTR_OPTIONAL(__fp16, base, L.off_d_tiles[1], pipeline);
1943    factx.vtcm_d_inv_l        = VTCM_LAYOUT_PTR(__fp16, base, L.off_d_inv_l);
1944    factx.vtcm_m_vec          = VTCM_LAYOUT_PTR(HVX_Vector, base, L.off_m_vec);
1945    factx.vtcm_l_vec          = VTCM_LAYOUT_PTR(HVX_Vector, base, L.off_l_vec);
1946    factx.vtcm_s_rowmax       = VTCM_LAYOUT_PTR(HVX_Vector, base, L.off_s_rowmax);
1947    factx.vtcm_p_rowsum       = VTCM_LAYOUT_PTR(HVX_Vector, base, L.off_p_rowsum);
1948    factx.vtcm_row_bufs       = VTCM_LAYOUT_PTR(HVX_Vector, base, L.off_row_bufs);
1949    factx.row_buf_stride      = L.row_buf_stride;
1950    factx.vtcm_hmx_scales_id  = VTCM_LAYOUT_PTR(uint8_t, base, L.off_hmx_scales_id);
1951    factx.vtcm_hmx_scales_qk  = VTCM_LAYOUT_PTR(uint8_t, base, L.off_hmx_scales_qk);
1952    factx.vtcm_mask_buf       = VTCM_LAYOUT_PTR(__fp16, base, L.off_mask_buf);
1953    factx.mask_buf_row_stride = L.mask_buf_row_stride;
1954    factx.q_tile_bytes        = L.q_tile_bytes;
1955    factx.o_tile_bytes        = L.o_tile_bytes;
1956    factx.col_vec_bytes       = L.col_vec_bytes;
1957    factx.d_tile_bytes        = L.d_tile_bytes;
1958    factx.vtcm_slopes         = VTCM_LAYOUT_PTR(__fp16, base, L.off_slopes);
1959
1960    const size_t m_line_bytes = L.m_line_bytes;  // used by the mask DMAs in the KV loop
1961
1962    dma_cache_init(&factx.m_cache, (uint8_t *) factx.vtcm_mask_buf, L.m_buf_slot_bytes, HMX_FA_DMA_CACHE_SIZE);
1963
1964    // ======== Initialize HMX output scales ========
1965    hmx_init_column_scales(factx.vtcm_hmx_scales_id, Q6_V_vsplat_R(0x3c00)); // 1.0
1966    hmx_init_column_scales(factx.vtcm_hmx_scales_qk, hvx_vec_splat_f16(factx.scale));
1967
1968    // ======== Skip compute if profiling ========
1969    if (octx->flags & HTP_OPFLAGS_SKIP_COMPUTE) {
1970        return HTP_STATUS_OK;
1971    }
1972
1973    // ======== DMA setup ========
1974    dma_queue * const dma = ctx->dma[0];
1975
1976    const size_t n_row_tiles_g_br = g_br / HMX_FP16_TILE_N_ROWS;
1977    const size_t n_tiles_per_bc   = Bc / HMX_FP16_TILE_N_COLS;
1978
1979    const size_t qo_element_size = factx.is_q_fp32 ? sizeof(float) : sizeof(__fp16);
1980
1981    const bool q_transposed                 = q->nb[1] < q->nb[2];
1982    const size_t q_src_stride               = q_transposed ? q->nb[2] : q->nb[1];
1983    const size_t q_row_bytes_untransposed   = factx.G * factx.DK * qo_element_size;
1984    const size_t q_row_bytes_trans_factor   = factx.DK * qo_element_size;
1985    const uint32_t kv_rows0                 = hex_smin(Bc, nek1);
1986
1987    // ======== Reusable job descriptors for pipeline ========
1988    hmx_fa_qk_job_t       qk_job;
1989    hmx_fa_o_update_job_t ou_job;
1990    hmx_fa_o_norm_job_t   on_job;
1991
1992    // ======== Main loop ========
1993    for (uint32_t ib3 = 0; ib3 < neq3; ++ib3) {
1994        const uint32_t im3 = mask ? fastmodulo(ib3, mask->ne[3], &factx.src3_div3) : 0;
1995        for (uint32_t q_start = q_start_min; q_start < q_start_max; q_start += Br) {
1996            const uint32_t n_rows_q    = hex_smin(Br, neq1 - q_start);
1997            const size_t   n_rows_g    = n_rows_q * G;
1998            const size_t   g_br_actual = hex_align_up(n_rows_g, HMX_FP16_TILE_N_ROWS);
1999            const size_t   n_row_tiles = g_br_actual / HMX_FP16_TILE_N_ROWS;
2000
2001            for (uint32_t kv_head = 0; kv_head < n_kv_heads; ++kv_head) {
2002                const uint32_t ik2 = kv_head;
2003                const uint32_t ik3 = fastdiv(ib3, &kparams->broadcast_rk3);
2004                const uint32_t iv2 = kv_head;
2005                const uint32_t iv3 = fastdiv(ib3, &kparams->broadcast_rv3);
2006
2007                // 1. Push Q and KV DMAs for the very first iteration.
2008                // Subsequent iterations are enqueued early at the end of the previous iteration.
2009                if (ib3 == 0 && q_start == q_start_min && kv_head == 0) {
2010                    const uint8_t * q_ptr = (const uint8_t *) q->data + q_start * q->nb[1] +
2011                                            (kv_head * factx.G) * q->nb[2] + ib3 * q->nb[3];
2012                    const size_t q_row_bytes = q_transposed ? n_rows_q * q_row_bytes_trans_factor : q_row_bytes_untransposed;
2013                    const size_t n_rows      = q_transposed ? factx.G : n_rows_q;
2014                    dma_queue_push(dma, dma_make_ptr(factx.vtcm_q_dma, q_ptr), q_row_bytes, hex_smax(q_src_stride, q_row_bytes), q_row_bytes, n_rows);
2015
2016                    if (factx.n_kv_blocks > 0) {
2017                        const uint8_t * k_src = (const uint8_t *) k->data + ik2 * k->nb[2] + ik3 * k->nb[3];
2018                        dma_queue_push(dma, dma_make_ptr(factx.vtcm_k_fp16[0], k_src), size_k_row_padded, k->nb[1], size_k_row, kv_rows0);
2019
2020                        const uint8_t * v_src = (const uint8_t *) v->data + iv2 * v->nb[2] + iv3 * v->nb[3];
2021                        dma_queue_push(dma, dma_make_ptr(factx.vtcm_v_fp16[0], v_src), size_v_row_padded, v->nb[1], size_v_row, kv_rows0);
2022
2023                        if (factx.pipeline && mask) {
2024                            if (__builtin_expect(factx.mask_broadcast, true)) {
2025                                const uint8_t * ms_src = (const uint8_t *) mask->data + q_start * mask->nb[1] + im3 * mask->nb[3] + 0;
2026                                dma_cache_push(dma, &factx.m_cache, ms_src, m_line_bytes, mask->nb[1], kv_rows0 * sizeof(__fp16), n_rows_q);
2027                            } else {
2028                                fa_push_mask_dma_gqa(dma, mask, q_start, im3, 0, kv_head, G, m_line_bytes, kv_rows0, n_rows_q, &factx);
2029                            }
2030                        }
2031                    }
2032                }
2033
2034                // 2. Pop Q DMA (blocks until Q is loaded)
2035                dma_queue_pop(dma);
2036
2037                // ---- Load Q block & Initialize per-block state ----
2038                fa_phase_q_load(&factx, q, q_start, kv_head, ib3, n_rows_g);
2039
2040                __fp16 * o_tile_prev = factx.vtcm_o_tiles[0];
2041                __fp16 * o_tile_curr = factx.vtcm_o_tiles[1];
2042
2043                // ---- KV block loop with DMA double-buffering ----
2044                size_t buf_idx = 0;
2045
2046                htp_trace_event_start(tr_hvx, HTP_TRACE_EVT_HVX_A_PREP, (uint16_t) q_start);
2047                fa_compute_slopes(&factx, kv_head, n_rows_g);
2048                htp_trace_event_stop(tr_hvx, HTP_TRACE_EVT_HVX_A_PREP, (uint16_t) q_start);
2049
2050                const size_t k_src_stride = size_k_row_padded / sizeof(__fp16);
2051                const size_t v_src_stride = size_v_row_padded / sizeof(__fp16);
2052
2053                hmx_queue_t hmx_q = ctx->hmx_queue;
2054
2055                if (factx.pipeline) {
2056                    // Double-buffered job structs because HMX queue runs asynchronously
2057                    hmx_fa_qk_job_t qk_job[2];
2058                    hmx_fa_o_update_job_t ou_job[2];
2059
2060                    // Prefetch block 1 early if there are multiple blocks
2061                    if (factx.n_kv_blocks > 1) {
2062                        fa_prefetch_block(dma, k, v, mask, 1, Bc, size_k_row_padded, size_k_row, size_v_row_padded, size_v_row,
2063                                          ik2, ik3, iv2, iv3, q_start, im3, kv_head, G, m_line_bytes, n_rows_q, nek1, 1, &factx);
2064                    }
2065
2066                    // Prep and start QK-dot(0)
2067                    void * curr_k0 = dma_queue_pop(dma).dst;
2068                    fa_phase_k_interleave(&factx, kv_rows0, k_src_stride, curr_k0, 0, 0);
2069
2070                    qk_job[0].q_tiles        = factx.vtcm_q_tiles;
2071                    qk_job[0].k_tiles        = factx.vtcm_k_tiles[0];
2072                    qk_job[0].s_tiles        = factx.vtcm_s_tiles[0];
2073                    qk_job[0].n_row_tiles    = n_row_tiles;
2074                    qk_job[0].n_col_tiles    = hmx_ceil_div(kv_rows0, HMX_FP16_TILE_N_COLS);
2075                    qk_job[0].n_dot_tiles    = DK / 32;
2076                    qk_job[0].n_tiles_per_bc = n_tiles_per_bc;
2077                    qk_job[0].hmx_scales     = factx.vtcm_hmx_scales_qk;
2078                    hmx_queue_push(hmx_q, hmx_queue_make_desc(hmx_fa_qk_dot_worker, &qk_job[0]));
2079
2080                    for (uint32_t kv_blk = 0; kv_blk < factx.n_kv_blocks; ++kv_blk) {
2081                        const uint32_t kv_start    = kv_blk * Bc;
2082                        const uint32_t kv_rows     = hex_smin(Bc, nek1 - kv_start);
2083                        const size_t   n_col_tiles = hmx_ceil_div(kv_rows, HMX_FP16_TILE_N_COLS);
2084
2085                        // ---- 1. Pop and run V-prep for current block ----
2086                        void * curr_v = dma_queue_pop(dma).dst;
2087                        fa_phase_v_interleave(&factx, kv_rows, v_src_stride, curr_v, factx.vtcm_v_tiles[buf_idx], n_tiles_per_bc, kv_start);
2088
2089                        // ---- 2. Pop and run mask-prep for current block ----
2090                        __fp16 * current_mask_vtcm = NULL;
2091                        if (mask) {
2092                            if (__builtin_expect(factx.mask_broadcast, true)) {
2093                                current_mask_vtcm = (__fp16 *) dma_queue_pop(dma).dst;
2094                            } else {
2095                                fa_pop_mask_dma_gqa(dma, G);
2096                                current_mask_vtcm = factx.vtcm_mask_buf;
2097                            }
2098                        }
2099
2100                        // ---- 3. Start HMX O update for block kv_blk - 1 (reads P[1 - buf_idx], V[1 - buf_idx], D) ----
2101                        // O update relys on the previous block's P and V tiles.
2102                        // O update MUST be pushed before the next block's QK-dot: hmx_queue_pop() retires the
2103                        // oldest descriptor, so push order alone decides which pop waits for which job.
2104                        // If OU went in after QK(i+1), the pop below would retire QK(i+1) and leave
2105                        // OU(i-1) in flight into the next iteration, where V-prep overwrites V[prev_buf].
2106                        if (kv_blk > 0) {
2107                            const size_t prev_buf        = 1 - buf_idx;
2108                            ou_job[prev_buf].o_curr      = o_tile_curr;
2109                            ou_job[prev_buf].o_prev      = o_tile_prev;
2110                            ou_job[prev_buf].p_tiles     = factx.vtcm_p_tiles[prev_buf];
2111                            ou_job[prev_buf].v_tiles     = factx.vtcm_v_tiles[prev_buf];
2112                            ou_job[prev_buf].d_tiles     = factx.vtcm_d_tiles[prev_buf];
2113                            ou_job[prev_buf].hmx_scales  = factx.vtcm_hmx_scales_id;
2114                            ou_job[prev_buf].n_row_tiles = n_row_tiles;
2115                            ou_job[prev_buf].n_col_tiles =
2116                                hmx_ceil_div(hex_smin(Bc, nek1 - (kv_blk - 1) * Bc), HMX_FP16_TILE_N_COLS);
2117                            ou_job[prev_buf].n_row_tiles_g_br = n_row_tiles_g_br;
2118                            ou_job[prev_buf].n_tiles_per_bc   = n_tiles_per_bc;
2119                            ou_job[prev_buf].DV               = DV;
2120                            hmx_queue_push(hmx_q, hmx_queue_make_desc(hmx_fa_o_update_worker, &ou_job[prev_buf]));
2121                        }
2122
2123                        // ---- 4. Pop and run K-prep for next block & push next QK-dot ----
2124                        if (kv_blk + 1 < factx.n_kv_blocks) {
2125                            const uint32_t next_start = (kv_blk + 1) * Bc;
2126                            const uint32_t next_rows  = hex_smin(Bc, nek1 - next_start);
2127                            const size_t   next_buf   = 1 - buf_idx;
2128
2129                            void * next_k = dma_queue_pop(dma).dst;
2130                            fa_phase_k_interleave(&factx, next_rows, k_src_stride, next_k, next_start, next_buf);
2131
2132                            qk_job[next_buf].q_tiles        = factx.vtcm_q_tiles;
2133                            qk_job[next_buf].k_tiles        = factx.vtcm_k_tiles[next_buf];
2134                            qk_job[next_buf].s_tiles        = factx.vtcm_s_tiles[next_buf];
2135                            qk_job[next_buf].n_row_tiles    = n_row_tiles;
2136                            qk_job[next_buf].n_col_tiles    = hmx_ceil_div(next_rows, HMX_FP16_TILE_N_COLS);
2137                            qk_job[next_buf].n_dot_tiles    = DK / 32;
2138                            qk_job[next_buf].n_tiles_per_bc = n_tiles_per_bc;
2139                            qk_job[next_buf].hmx_scales     = factx.vtcm_hmx_scales_qk;
2140                            hmx_queue_push(hmx_q, hmx_queue_make_desc(hmx_fa_qk_dot_worker, &qk_job[next_buf]));
2141                        }
2142
2143                        // ---- 5. Wait for current block's QK-dot to finish ----
2144                        hmx_queue_pop(hmx_q);
2145
2146                        // ---- 6. Phase 2: softmax + build_D ----
2147                        fa_softmax_args_t sargs;
2148                        memset(&sargs, 0, sizeof(sargs));
2149                        sargs.factx                = &factx;
2150                        sargs.buf_idx              = buf_idx;
2151                        sargs.kv_rows              = kv_rows;
2152                        sargs.n_rows_g             = n_rows_g;
2153                        sargs.n_col_tiles          = n_col_tiles;
2154                        sargs.n_tiles_per_bc       = n_tiles_per_bc;
2155                        sargs.n_row_tiles          = n_row_tiles;
2156                        sargs.n_row_tiles_g_br     = n_row_tiles_g_br;
2157                        sargs.Bc                   = Bc;
2158                        sargs.G                    = G;
2159                        sargs.kv_head              = kv_head;
2160                        sargs.kv_start             = kv_start;
2161                        sargs.q_start              = q_start;
2162                        sargs.ib3                  = ib3;
2163                        sargs.has_alibi            = (factx.max_bias != 0.0f);
2164                        sargs.mask                 = mask;
2165                        sargs.mask_vtcm            = current_mask_vtcm;
2166                        sargs.mask_vtcm_row_stride = factx.mask_buf_row_stride;
2167                        sargs.slopes               = factx.vtcm_slopes;
2168
2169                        // Run Softmax on HVX (blocking call)
2170                        fa_phase_softmax_and_build_d(&factx, &sargs, n_row_tiles, n_row_tiles_g_br);
2171
2172                        // Wait for HMX O update for block kv_blk - 1 to finish
2173                        if (kv_blk > 0) {
2174                            hmx_queue_pop(hmx_q);
2175                            hex_swap_ptr((void **) &o_tile_curr, (void **) &o_tile_prev);
2176                        }
2177
2178                        // Prefetch block kv_blk + 2
2179                        if (kv_blk + 2 < factx.n_kv_blocks) {
2180                            fa_prefetch_block(dma, k, v, mask, kv_blk + 2, Bc, size_k_row_padded, size_k_row, size_v_row_padded, size_v_row,
2181                                              ik2, ik3, iv2, iv3, q_start, im3, kv_head, G, m_line_bytes, n_rows_q, nek1, buf_idx, &factx);
2182                        }
2183
2184                        buf_idx = 1 - buf_idx;
2185                    }
2186
2187                    // Epilogue
2188                    if (factx.n_kv_blocks > 0) {
2189                        const uint32_t last_blk = factx.n_kv_blocks - 1;
2190                        const size_t last_cols  = hmx_ceil_div(hex_smin(Bc, nek1 - last_blk * Bc), HMX_FP16_TILE_N_COLS);
2191                        ou_job[0].o_curr           = o_tile_curr;
2192                        ou_job[0].o_prev           = o_tile_prev;
2193                        ou_job[0].p_tiles          = factx.vtcm_p_tiles[1 - buf_idx];
2194                        ou_job[0].v_tiles          = factx.vtcm_v_tiles[1 - buf_idx];
2195                        ou_job[0].d_tiles          = factx.vtcm_d_tiles[1 - buf_idx];
2196                        ou_job[0].hmx_scales       = factx.vtcm_hmx_scales_id;
2197                        ou_job[0].n_row_tiles      = n_row_tiles;
2198                        ou_job[0].n_col_tiles      = last_cols;
2199                        ou_job[0].n_row_tiles_g_br = n_row_tiles_g_br;
2200                        ou_job[0].n_tiles_per_bc   = n_tiles_per_bc;
2201                        ou_job[0].DV               = DV;
2202                        hmx_queue_push(hmx_q, hmx_queue_make_desc(hmx_fa_o_update_worker, &ou_job[0]));
2203
2204                        // Overlapped: run HVX build diag inv L while HMX is busy executing the update
2205                        htp_trace_event_start(tr_hvx, HTP_TRACE_EVT_HVX_O_PROC, (uint16_t) q_start);
2206                        fa_build_d_diag_inv_l(&factx, n_row_tiles, n_row_tiles_g_br);
2207                        htp_trace_event_stop(tr_hvx, HTP_TRACE_EVT_HVX_O_PROC, (uint16_t) q_start);
2208                        hmx_queue_pop(hmx_q);
2209
2210                        hex_swap_ptr((void **) &o_tile_curr, (void **) &o_tile_prev);
2211                    }
2212
2213                } else {
2214                    // Fallback path
2215                    for (uint32_t kv_blk = 0; kv_blk < factx.n_kv_blocks; ++kv_blk) {
2216                        const uint32_t kv_start    = kv_blk * Bc;
2217                        const uint32_t kv_rows     = hex_smin(Bc, nek1 - kv_start);
2218                        const size_t   n_col_tiles = hmx_ceil_div(kv_rows, HMX_FP16_TILE_N_COLS);
2219
2220                        if (mask) {
2221                            if (__builtin_expect(factx.mask_broadcast, true)) {
2222                                const uint8_t * ms_src = (const uint8_t *) mask->data + q_start * mask->nb[1] + im3 * mask->nb[3] + kv_start * sizeof(__fp16);
2223                                dma_cache_push(dma, &factx.m_cache, ms_src, m_line_bytes, mask->nb[1], kv_rows * sizeof(__fp16), n_rows_q);
2224                            } else {
2225                                fa_push_mask_dma_gqa(dma, mask, q_start, im3, kv_start, kv_head, G, m_line_bytes, kv_rows, n_rows_q, &factx);
2226                            }
2227                        }
2228
2229                        if (kv_blk + 1 < factx.n_kv_blocks) {
2230                            const uint32_t  prefetch_start = (kv_blk + 1) * Bc;
2231                            const uint32_t  prefetch_rows  = hex_smin(Bc, nek1 - prefetch_start);
2232                            const size_t    prefetch_buf   = 1 - buf_idx;
2233                            const uint8_t * k_prefetch_src = (const uint8_t *) k->data + prefetch_start * k->nb[1] + ik2 * k->nb[2] + ik3 * k->nb[3];
2234                            dma_queue_push(dma, dma_make_ptr(factx.vtcm_k_fp16[prefetch_buf], k_prefetch_src), size_k_row_padded, k->nb[1], size_k_row, prefetch_rows);
2235                            const uint8_t * v_prefetch_src = (const uint8_t *) v->data + prefetch_start * v->nb[1] + iv2 * v->nb[2] + iv3 * v->nb[3];
2236                            dma_queue_push(dma, dma_make_ptr(factx.vtcm_v_fp16[prefetch_buf], v_prefetch_src), size_v_row_padded, v->nb[1], size_v_row, prefetch_rows);
2237                        }
2238
2239                        // Wait for current K DMA and interleave
2240                        void * curr_k = dma_queue_pop(dma).dst;
2241                        fa_phase_k_interleave(&factx, kv_rows, k_src_stride, curr_k, kv_start, 0);
2242
2243                        {
2244                            qk_job.q_tiles        = factx.vtcm_q_tiles;
2245                            qk_job.k_tiles        = factx.vtcm_k_tiles[0];
2246                            qk_job.s_tiles        = factx.vtcm_s_tiles[0];
2247                            qk_job.n_row_tiles    = n_row_tiles;
2248                            qk_job.n_col_tiles    = n_col_tiles;
2249                            qk_job.n_dot_tiles    = (size_t) (DK / 32);
2250                            qk_job.n_tiles_per_bc = n_tiles_per_bc;
2251                            qk_job.hmx_scales     = factx.vtcm_hmx_scales_qk;
2252
2253                            hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_fa_qk_dot_worker, &qk_job));
2254                            hmx_queue_pop(ctx->hmx_queue);
2255                        }
2256
2257                        // Wait for current V DMA and interleave
2258                        void * curr_v = dma_queue_pop(dma).dst;
2259                        fa_phase_v_interleave(&factx, kv_rows, v_src_stride, curr_v, factx.vtcm_v_tiles[0], n_tiles_per_bc, kv_start);
2260
2261                        // ---- Phase 3: softmax + build_D ----
2262                        __fp16 * current_mask_vtcm = NULL;
2263                        if (mask) {
2264                            if (__builtin_expect(factx.mask_broadcast, true)) {
2265                                current_mask_vtcm = (__fp16 *) dma_queue_pop(dma).dst;
2266                            } else {
2267                                fa_pop_mask_dma_gqa(dma, G);
2268                                current_mask_vtcm = factx.vtcm_mask_buf;
2269                            }
2270                        }
2271
2272                        fa_softmax_args_t sargs;
2273                        memset(&sargs, 0, sizeof(sargs));
2274                        sargs.factx                = &factx;
2275                        sargs.kv_rows              = kv_rows;
2276                        sargs.n_rows_g             = n_rows_g;
2277                        sargs.n_col_tiles          = n_col_tiles;
2278                        sargs.n_tiles_per_bc       = n_tiles_per_bc;
2279                        sargs.n_row_tiles          = n_row_tiles;
2280                        sargs.n_row_tiles_g_br     = n_row_tiles_g_br;
2281                        sargs.Bc                   = Bc;
2282                        sargs.G                    = G;
2283                        sargs.kv_head              = kv_head;
2284                        sargs.kv_start             = kv_start;
2285                        sargs.q_start              = q_start;
2286                        sargs.ib3                  = ib3;
2287                        sargs.has_alibi            = (factx.max_bias != 0.0f);
2288                        sargs.mask                 = mask;
2289                        sargs.mask_vtcm            = current_mask_vtcm;
2290                        sargs.mask_vtcm_row_stride = factx.mask_buf_row_stride;
2291                        sargs.slopes               = factx.vtcm_slopes;
2292                        fa_phase_softmax_and_build_d(&factx, &sargs, n_row_tiles, n_row_tiles_g_br);
2293
2294                        {
2295                            ou_job.o_curr           = o_tile_curr;
2296                            ou_job.o_prev           = o_tile_prev;
2297                            ou_job.p_tiles          = factx.vtcm_p_tiles[0];
2298                            ou_job.v_tiles          = factx.vtcm_v_tiles[0];
2299                            ou_job.d_tiles          = factx.vtcm_d_tiles[0];
2300                            ou_job.hmx_scales       = factx.vtcm_hmx_scales_id;
2301                            ou_job.n_row_tiles      = n_row_tiles;
2302                            ou_job.n_col_tiles      = n_col_tiles;
2303                            ou_job.n_row_tiles_g_br = n_row_tiles_g_br;
2304                            ou_job.n_tiles_per_bc   = n_tiles_per_bc;
2305                            ou_job.DV               = DV;
2306
2307                            hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_fa_o_update_worker, &ou_job));
2308                            if (kv_blk + 1 == factx.n_kv_blocks) {
2309                                // Overlapped: run HVX build diag inv L while HMX is busy executing the update
2310                                htp_trace_event_start(tr_hvx, HTP_TRACE_EVT_HVX_O_PROC, (uint16_t) q_start);
2311                                fa_build_d_diag_inv_l(&factx, n_row_tiles, n_row_tiles_g_br);
2312                                htp_trace_event_stop(tr_hvx, HTP_TRACE_EVT_HVX_O_PROC, (uint16_t) q_start);
2313                            }
2314                            hmx_queue_pop(ctx->hmx_queue);
2315
2316                            hex_swap_ptr((void **) &o_tile_curr, (void **) &o_tile_prev);
2317                        }
2318
2319                        buf_idx = 1 - buf_idx;
2320                    }
2321                }
2322
2323                // Enqueue DMAs for the next iteration early so they overlap with O-PROC
2324                uint32_t next_kv_head = kv_head + 1;
2325                uint32_t next_q_start = q_start;
2326                uint32_t next_ib3     = ib3;
2327                if (next_kv_head >= n_kv_heads) {
2328                    next_kv_head = 0;
2329                    next_q_start = q_start + Br;
2330                    if (next_q_start >= q_start_max) {
2331                        next_q_start = q_start_min;
2332                        next_ib3     = ib3 + 1;
2333                    }
2334                }
2335                bool has_next = (next_ib3 < neq3);
2336
2337                if (has_next) {
2338                    const uint32_t next_n_rows_q = hex_smin(Br, neq1 - next_q_start);
2339                    const uint8_t * next_q_ptr = (const uint8_t *) q->data + next_q_start * q->nb[1] + (next_kv_head * factx.G) * q->nb[2] + next_ib3 * q->nb[3];
2340                    const size_t next_q_row_bytes = q_transposed ? next_n_rows_q * q_row_bytes_trans_factor : q_row_bytes_untransposed;
2341                    const size_t next_n_rows      = q_transposed ? factx.G : next_n_rows_q;
2342                    dma_queue_push(dma, dma_make_ptr(factx.vtcm_q_dma, next_q_ptr), next_q_row_bytes, hex_smax(q_src_stride, next_q_row_bytes), next_q_row_bytes, next_n_rows);
2343
2344                    if (factx.n_kv_blocks > 0) {
2345                        const uint32_t next_ik2 = next_kv_head;
2346                        const uint32_t next_iv2 = next_kv_head;
2347                        uint32_t next_ik3 = ik3;
2348                        uint32_t next_iv3 = iv3;
2349                        if (next_ib3 != ib3) {
2350                            next_ik3 = fastdiv(next_ib3, &kparams->broadcast_rk3);
2351                            next_iv3 = fastdiv(next_ib3, &kparams->broadcast_rv3);
2352                        }
2353
2354                        const uint8_t * next_k_src = (const uint8_t *) k->data + next_ik2 * k->nb[2] + next_ik3 * k->nb[3];
2355                        dma_queue_push(dma, dma_make_ptr(factx.vtcm_k_fp16[0], next_k_src), size_k_row_padded, k->nb[1], size_k_row, kv_rows0);
2356
2357                        const uint8_t * next_v_src = (const uint8_t *) v->data + next_iv2 * v->nb[2] + next_iv3 * v->nb[3];
2358                        dma_queue_push(dma, dma_make_ptr(factx.vtcm_v_fp16[0], next_v_src), size_v_row_padded, v->nb[1], size_v_row, kv_rows0);
2359
2360                        if (factx.pipeline && mask) {
2361                            uint32_t next_im3 = im3;
2362                            if (next_ib3 != ib3) {
2363                                next_im3 = fastmodulo(next_ib3, mask->ne[3], &factx.src3_div3);
2364                            }
2365                            if (__builtin_expect(factx.mask_broadcast, true)) {
2366                                const uint8_t * ms_src = (const uint8_t *) mask->data + next_q_start * mask->nb[1] + next_im3 * mask->nb[3] + 0;
2367                                dma_cache_push(dma, &factx.m_cache, ms_src, m_line_bytes, mask->nb[1], kv_rows0 * sizeof(__fp16), next_n_rows_q);
2368                            } else {
2369                                fa_push_mask_dma_gqa(dma, mask, next_q_start, next_im3, 0, next_kv_head, G, m_line_bytes, kv_rows0, next_n_rows_q, &factx);
2370                            }
2371                        }
2372                    }
2373                }
2374
2375                // ---- Final normalization ----
2376                {
2377                    on_job.o_curr           = o_tile_curr;
2378                    on_job.o_prev           = o_tile_prev;
2379                    on_job.d_tiles          = factx.vtcm_d_inv_l;
2380                    on_job.hmx_scales       = factx.vtcm_hmx_scales_id;
2381                    on_job.n_row_tiles      = n_row_tiles;
2382                    on_job.n_row_tiles_g_br = n_row_tiles_g_br;
2383                    on_job.DV               = DV;
2384                    hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_fa_o_norm_worker, &on_job));
2385                    hmx_queue_pop(ctx->hmx_queue);
2386                }
2387
2388                // ---- Store O block ----
2389                fa_phase_o_store(&factx, dst, o_tile_curr, q_start, kv_head, ib3, n_rows_g);
2390            }
2391        }
2392    }
2393
2394    return HTP_STATUS_OK;
2395}
2396
2397int op_flash_attn_ext(struct htp_ops_context * octx) {
2398    const struct htp_tensor * q    = octx->src[0];
2399    const struct htp_tensor * k    = octx->src[1];
2400    const struct htp_tensor * v    = octx->src[2];
2401    const struct htp_tensor * mask = octx->src[3];
2402    const struct htp_tensor * dst  = octx->dst;
2403
2404    // Check support
2405    if ((q->type != HTP_TYPE_F16 && q->type != HTP_TYPE_F32) ||
2406        (k->type != HTP_TYPE_F16 && k->type != HTP_TYPE_Q8_0) ||
2407        (v->type != HTP_TYPE_F16 && v->type != HTP_TYPE_Q8_0)) {
2408        return HTP_STATUS_NO_SUPPORT;
2409    }
2410
2411    const struct htp_fa_kernel_params * kparams = (const struct htp_fa_kernel_params *) octx->kernel_params;
2412
2413    if (kparams->kernel_type == HTP_FA_KERNEL_UNSUPPORTED) {
2414        return HTP_STATUS_NO_SUPPORT;
2415    }
2416
2417    if (!htp_ops_context_set_n_threads(octx, kparams->n_threads)) {
2418        return HTP_STATUS_INVAL_PARAMS;
2419    }
2420
2421    if (kparams->kernel_type == HTP_FA_KERNEL_HMX) {
2422        return hmx_flash_attn_ext(octx);
2423    }
2424
2425    struct htp_fa_context factx;
2426    factx.octx = octx;
2427    factx.k = k;
2428    factx.v = v;
2429
2430    factx.src0_div21 = kparams->u.hvx.src0_div21;
2431    factx.src0_div1  = kparams->u.hvx.src0_div1;
2432
2433    factx.broadcast_rk2 = kparams->broadcast_rk2;
2434    factx.broadcast_rk3 = kparams->broadcast_rk3;
2435    factx.broadcast_rv2 = kparams->broadcast_rv2;
2436    factx.broadcast_rv3 = kparams->broadcast_rv3;
2437
2438    if (mask) {
2439        factx.src3_div2 = kparams->src3_div2;
2440        factx.src3_div3 = kparams->src3_div3;
2441    }
2442
2443    factx.is_q_fp32 = (kparams->is_q_fp32 != 0);
2444    factx.size_q_row_padded = kparams->u.hvx.size_q_row_padded;
2445    factx.size_k_row_padded = kparams->u.hvx.size_k_row_padded;
2446    factx.size_v_row_padded = kparams->u.hvx.size_v_row_padded;
2447
2448    size_t size_q_block = factx.size_q_row_padded * 1; // single row for now
2449    factx.size_k_block = factx.size_k_row_padded * FLASH_ATTN_BLOCK_SIZE;
2450    factx.size_v_block = factx.size_v_row_padded * FLASH_ATTN_BLOCK_SIZE;
2451    factx.size_m_block = hex_round_up(FLASH_ATTN_BLOCK_SIZE * sizeof(__fp16), 128);
2452
2453    factx.n_blocks = kparams->n_kv_blocks;
2454
2455    factx.scale = kparams->scale;
2456    factx.max_bias = kparams->max_bias;
2457    factx.logit_softcap = (__fp16) kparams->logit_softcap;
2458
2459    factx.n_head_log2 = kparams->n_head_log2;
2460    factx.m0          = kparams->m0;
2461    factx.m1          = kparams->m1;
2462
2463    const uint32_t n_head = q->ne[2];
2464    if (n_head > 512) {
2465        return HTP_STATUS_NO_SUPPORT;
2466    }
2467    for (uint32_t h = 0; h < n_head; ++h) {
2468        factx.slopes[h] = (__fp16) ((kparams->max_bias > 0.0f) ? alibi_slope(h, factx.n_head_log2, factx.m0, factx.m1) : 1.0f);
2469    }
2470
2471    // total rows in q
2472    const uint32_t neq1 = q->ne[1];
2473    const uint32_t neq2 = q->ne[2];
2474    const uint32_t neq3 = q->ne[3];
2475    const uint32_t total_qrows = neq1 * neq2 * neq3;
2476
2477    uint32_t qrow_start = 0;
2478    uint32_t qrows      = total_qrows;
2479
2480    if (octx->ctx->mdev.count > 1) {
2481        const bool can_split = htp_tensor_mdev_data_aligned(dst) && ((dst->nb[1] & (HTP_TENSOR_MDEV_LINE_SIZE - 1)) == 0);
2482        const struct htp_tensor_mdev_range range = htp_tensor_mdev_partition(total_qrows, can_split ? 1 : 0, octx->ctx->mdev.idx, octx->ctx->mdev.count, &octx->ctx->mdev.count_div);
2483        qrow_start = range.start;
2484        qrows      = range.count;
2485    }
2486
2487    if (qrows == 0) {
2488        return HTP_STATUS_OK;
2489    }
2490
2491    const uint32_t n_threads = octx->n_threads;
2492
2493    factx.qrows            = qrows;
2494    factx.qrow_start       = qrow_start;
2495    factx.qrows_per_thread = fastdiv(qrows + n_threads - 1, &octx->n_threads_div);
2496
2497    size_t size_vkq_acc = hex_round_up(v->ne[0] * sizeof(float), 128); // VKQ32
2498
2499    factx.size_q_block = size_q_block;
2500    factx.size_vkq_acc = size_vkq_acc;
2501
2502    uint8_t * vtcm_cur = octx->ctx->vtcm_base;
2503
2504    factx.spad_q = vtcm_seq_alloc(&vtcm_cur, size_q_block * n_threads);
2505    factx.spad_k = vtcm_seq_alloc(&vtcm_cur, factx.size_k_block * 2 * n_threads);
2506    factx.spad_v = vtcm_seq_alloc(&vtcm_cur, factx.size_v_block * 2 * n_threads);
2507    factx.spad_m = vtcm_seq_alloc(&vtcm_cur, (mask ? factx.size_m_block * HVX_FA_DMA_CACHE_SIZE : 0) * n_threads);
2508    factx.spad_a = vtcm_seq_alloc(&vtcm_cur, size_vkq_acc * n_threads);
2509
2510    if ((size_t) (vtcm_cur - octx->ctx->vtcm_base) > octx->ctx->vtcm_size) {
2511        return HTP_STATUS_VTCM_TOO_SMALL;
2512    }
2513
2514    if (!(octx->flags & HTP_OPFLAGS_SKIP_COMPUTE)) {
2515        work_queue_run(octx->ctx->work_queue, flash_attn_ext_f16_thread, &factx, n_threads);
2516    }
2517
2518    return HTP_STATUS_OK;
2519}