#version 450

#extension GL_EXT_control_flow_attributes : enable
#extension GL_EXT_shader_16bit_storage : require

#extension GL_EXT_shader_explicit_arithmetic_types_int32 : require

#ifdef FLOAT16
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
#extension GL_EXT_shader_subgroup_extended_types_float16 : require
#endif

#ifdef MMQ
#extension GL_EXT_integer_dot_product : require
#extension GL_KHR_shader_subgroup_clustered : require

#include "mul_mmq_shmem_types.glsl"
#endif

#extension GL_KHR_shader_subgroup_shuffle : enable
#extension GL_KHR_shader_subgroup_vote : enable

#include "types.glsl"
#include "flash_attn_base.glsl"
#include "flash_attn_dequant.glsl"

const uint32_t HSK_per_thread = HSK / D_split;
const uint32_t HSV_per_thread = HSV / D_split;

const uint32_t rows_per_thread = Br / row_split;
const uint32_t cols_per_iter = WorkGroupSize / D_split / row_split;
const uint32_t cols_per_thread = Bc / cols_per_iter;
const uint32_t num_subgroups = SubGroupSize == 0 ? 0 : WorkGroupSize / SubGroupSize;


layout (binding = 0) readonly buffer Q {float data_q[];};
layout (binding = 0) readonly buffer QV4 {vec4 data_qv4[];};
layout (binding = 1) readonly buffer K {float16_t data_k[];};
layout (binding = 1) readonly buffer KV4 {f16vec4 data_kv4[];};
layout (binding = 2) readonly buffer V {float16_t data_v[];};
layout (binding = 2) readonly buffer VV4 {f16vec4 data_vv4[];};
layout (binding = 3) readonly buffer M {float16_t data_m[];};

// If SubGroupSize is set to 0 then only use shmem reductions
const uint32_t tmpsh_size = (SubGroupSize > 0) ? (row_split == 1 ? num_subgroups * D_split : num_subgroups) : WorkGroupSize;
shared float tmpsh[tmpsh_size];
shared FLOAT_TYPEV4 tmpshv4[tmpsh_size];

const uint32_t masksh_stride = Br + 1;
shared FLOAT_TYPE masksh[Bc * masksh_stride];

#ifndef MMQ
const uint32_t qf_stride = HSK / 4 + 1;
shared FLOAT_TYPEV4 Qf[Br * qf_stride];
#else

const uint32_t qf_stride = HSK / 32;
shared block_b_cache Qf[Br * qf_stride];
#endif

#ifndef MMQ
const uint32_t D = HSK > HSV ? HSK : HSV;
#else
const uint32_t D = HSV;
#endif
const uint32_t kvsh_stride = D / 4 + 1;
shared FLOAT_TYPEV4 kvsh[SHMEM_STAGING != 0 ? Bc * kvsh_stride : 1];

#ifdef MMQ

shared block_a_cache kblocksh[SHMEM_STAGING != 0 ? Bc * qf_stride : 1];
#endif

shared vec4 occupancy_limiter[LIMIT_OCCUPANCY_SHMEM > 0 ? LIMIT_OCCUPANCY_SHMEM : 1];

#ifdef MMQ
#include "flash_attn_mmq_funcs.glsl"
#endif

void main() {
#ifdef NEEDS_INIT_IQ_SHMEM
    init_iq_shmem(gl_WorkGroupSize);
#endif

    init_indices();

    const uint32_t tid = gl_LocalInvocationIndex;
    const uint32_t threads_per_rowgroup = gl_WorkGroupSize.x / row_split;
    const uint32_t row_tid = gl_LocalInvocationIndex / threads_per_rowgroup;
    const uint32_t rowgroup_tid = gl_LocalInvocationIndex % threads_per_rowgroup;
    const uint32_t d_tid = gl_LocalInvocationIndex % D_split;
    const uint32_t col_tid = (gl_LocalInvocationIndex % threads_per_rowgroup) / D_split;

    if (LIMIT_OCCUPANCY_SHMEM > 0) {
        // This just exists to avoid the occupancy_limiter array getting optimized out
        occupancy_limiter[tid] = vec4(tid);

        barrier();

        if (occupancy_limiter[tid] == vec4(99999.0)) {
            data_ov4[0] = D_TYPEV4(occupancy_limiter[tid]);
        }
    }

#define tile_row(r) (row_tid * rows_per_thread + (r))

    uint32_t q_offset = gqa_iq1*p.nb01 + (iq2*p.nb02 + iq3*p.nb03) / 4;

    [[unroll]] for (uint32_t idx = 0; idx < Br * HSK / 4; idx += gl_WorkGroupSize.x) {
        uint32_t d = (idx + tid) % (HSK / 4);
        uint32_t r = (idx + tid) / (HSK / 4);
        const bool is_in_bounds = r < Br && d < HSK / 4 && i * Br + r < N;
#ifndef MMQ
        if (is_in_bounds) {
            Qf[r * qf_stride + d] = FLOAT_TYPEV4(data_qv4[q_offset / 4 + (i * Br + r) * q_stride / 4 + d] * p.scale);
        }
#else
        const uint buf_ib = r * qf_stride + d / 8;
        const uint buf_iqs = d % 8;

        FLOAT_TYPEV4 vals = is_in_bounds ? FLOAT_TYPEV4(data_qv4[q_offset / 4 + (i * Br + r) * q_stride / 4 + d] * p.scale) : FLOAT_TYPEV4(0.0f);
        const FLOAT_TYPEV4 abs_vals = abs(vals);

        const FLOAT_TYPE thread_max = max(max(abs_vals.x, abs_vals.y), max(abs_vals.z, abs_vals.w));
        const FLOAT_TYPE amax = subgroupClusteredMax(thread_max, 8);
        const FLOAT_TYPE qd = amax / FLOAT_TYPE(127.0);
        const FLOAT_TYPE qd_inv = qd != FLOAT_TYPE(0.0) ? FLOAT_TYPE(1.0) / qd : FLOAT_TYPE(0.0);
        vals = round(vals * qd_inv);

        Qf[buf_ib].qs[buf_iqs] = pack32(i8vec4(vals));

        // Q8_0 K only needs (qd, _); the asymmetric Q4_*/Q5_* family also stores
        // the row-sum scaled by qd, used in k_dot_correction.
        if (FaTypeK == FA_TYPE_Q8_0) {
            if (buf_iqs == 0) {
                Qf[buf_ib].ds = FLOAT_TYPEV2(qd, 0.0);
            }
        } else {
            const FLOAT_TYPE thread_sum = vals.x + vals.y + vals.z + vals.w;
            const FLOAT_TYPE sum = subgroupClusteredAdd(thread_sum, 8);

            if (buf_iqs == 0) {
                Qf[buf_ib].ds = FLOAT_TYPEV2(qd, sum * qd);
            }
        }
#endif
    }
    barrier();

    FLOAT_TYPEV4 Of[rows_per_thread][HSV_per_thread / 4];
    [[unroll]] for (uint32_t d = 0; d < HSV_per_thread / 4; ++d) {
        [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
            Of[r][d] = FLOAT_TYPEV4(0.0);
        }
    }

    float Lf[rows_per_thread], Mf[rows_per_thread];

    // Use -FLT_MAX/2 rather than -inf to reduce the possibility of NaNs, e.g. when computing Mold-M.
    const float NEG_FLT_MAX_OVER_2 = uintBitsToFloat(0xFEFFFFFF);

    [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
        Lf[r] = 0;
        Mf[r] = NEG_FLT_MAX_OVER_2;
    }

    ACC_TYPE slope[rows_per_thread];
    [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
        slope[r] = ACC_TYPE(1.0);
    }

    // ALiBi
    if (p.max_bias > 0.0f) {
        [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
            slope[r] = perElemOpComputeSlope(tile_row(r), col_tid, ACC_TYPE(0), iq2);
        }
    }

    const uint32_t mo_stride = CEIL_DIV(KV, 16 * Bc);
    // mo_offset will point to the tile starting at row i*Br and col 0
    uint32_t mo_offset = mo_stride * i;

    // FaBlockBytesK/V == 2 for f16, 16 for f32, ggml block byte size for quants.
    uint32_t k_offset = (ik2*p.nb12 + ik3*p.nb13) / FaBlockBytesK;
    uint32_t v_offset = (iv2*p.nb22 + iv3*p.nb23) / FaBlockBytesV;
    uint32_t m_offset = gqa_iq1*KV;
    if (p.nem2 != 1 || p.nem3 != 1) {
        m_offset += ((iq3 % p.nem3) * p.nem2 + (iq2 % p.nem2)) * p.nem1 * KV;
        mo_offset += ((iq3 % p.nem3) * p.nem2 + (iq2 % p.nem2)) * CEIL_DIV(p.nem1, Br) * mo_stride;
    }

    uint32_t mask_opt = 0;
    uint32_t mask_opt_idx = ~0;
    uint32_t mask_opt_bits = 0;

    [[dont_unroll]]
    for (uint32_t j = start_j; j < end_j; ++j) {
        if (MASK_ENABLE) {
            if (USE_MASK_OPT && mask_opt_idx != j / 16) {
                mask_opt_idx = j / 16;
                mask_opt = data_mask_opt[mo_offset + mask_opt_idx];
            }
            mask_opt_bits = (mask_opt >> ((j % 16) * 2)) & 0x3;
            if (mask_opt_bits == MASK_OPT_ALL_NEG_INF) {
                // skip this block
                continue;
            }
            // Only load if the block is not all zeros
            if (mask_opt_bits != MASK_OPT_ALL_ZERO) {
                bool nem1_bounds_check = !(p.gqa_ratio > 1) && (p.nem1 % Br) != 0;

                float max_mask = NEG_FLT_MAX_OVER_2;
                barrier();
                [[unroll]] for (uint32_t idx = 0; idx < Bc * Br; idx += gl_WorkGroupSize.x) {
                    uint32_t c = (idx + tid) % Bc;
                    uint32_t r = (idx + tid) / Bc;
                    if (idx + tid < Bc * Br) {
                        if ((!KV_bounds_check || j * Bc + c < KV) && (!nem1_bounds_check || i * Br + r < p.nem1)) {
                            FLOAT_TYPE m = FLOAT_TYPE(data_m[m_offset + (i * Br + r) * m_stride + (j * Bc + c)]);
                            masksh[c * masksh_stride + r] = m;
                            max_mask = max(max_mask, float(m));
                        } else {
                            masksh[c * masksh_stride + r] = FLOAT_TYPE(0);
                        }
                    }
                }
                // skip the block if the mask is entirely -inf
                bool all_less = subgroupAll(max_mask <= NEG_FLT_MAX_OVER_2);
                barrier();
                if (gl_SubgroupInvocationID == 0) {
                    tmpsh[gl_SubgroupID] = all_less ? NEG_FLT_MAX_OVER_2 : 0.0f;
                }
                barrier();
                [[unroll]] for (uint s = 0; s < gl_NumSubgroups; ++s) {
                    max_mask = max(max_mask, tmpsh[s]);
                }
                if (max_mask <= NEG_FLT_MAX_OVER_2) {
                    continue;
                }
            }
        }

        ACC_TYPE Sf[rows_per_thread][cols_per_thread];
        [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
            [[unroll]] for (uint32_t c = 0; c < cols_per_thread; ++c) {
                Sf[r][c] = ACC_TYPE(0.0);
            }
        }

        if (SHMEM_STAGING != 0) {
            barrier();
#ifndef MMQ
            [[unroll]] for (uint32_t idx = 0; idx < Bc * HSK / 4; idx += gl_WorkGroupSize.x) {
                uint32_t d = (idx + tid) % (HSK / 4);
                uint32_t c = (idx + tid) / (HSK / 4);
                if (idx + gl_WorkGroupSize.x <= Bc * HSK / 4 || c < Bc) {
                    FLOAT_TYPEV4 K_Tf = FLOAT_TYPEV4(0);
                    if (!KV_bounds_check || j * Bc + c < KV) {
                        if (USE_DECODE_K) {
                            uint coord = (j * Bc + c) * k_stride * BLOCK_SIZE_K + 4 * d;
                            uint ib = coord / BLOCK_SIZE_K;
                            uint iqs = (coord % BLOCK_SIZE_K);
                            K_Tf = dequantize4(ib, iqs, k_offset, BINDING_IDX_K);
                        } else {
                            K_Tf = FLOAT_TYPEV4(data_kv4[k_offset / 4 + (j * Bc + c) * k_stride / 4 + d]);
                        }
                    }

                    kvsh[c * kvsh_stride + d] = K_Tf;
                }
            }
#else // MMQ
            const uint ints_per_block = 8u / fa_quant_r_mmq(FaTypeK);
            const uint quant_iters = Bc * HSK / 32 * ints_per_block;
            [[unroll]] for (uint32_t idx = 0; idx < quant_iters; idx += gl_WorkGroupSize.x) {
                const uint32_t iqs = (idx + tid) % ints_per_block;
                const uint32_t ib = (idx + tid) / ints_per_block;
                const uint32_t c = ib / (HSK / 32);
                const uint32_t block = ib % (HSK / 32);
                if (idx + gl_WorkGroupSize.x <= quant_iters || c < Bc) {
                    const uint buf_ib = c * qf_stride + block;
                    if (!KV_bounds_check || j * Bc + c < KV) {
                        const uint global_ib = (j * Bc + c) * k_stride + block;
                        k_block_to_shmem(buf_ib, global_ib, iqs, k_offset);
                    } else {
                        k_block_to_shmem_zero(buf_ib, iqs);
                    }
                }
            }
#endif // MMQ
            barrier();
        }

#ifndef MMQ
        // More d iterations means Q register caching becomes relevant
        // Few iterations means the additional registers needed are worse than the speed-up from caching
        if (HSK_per_thread / 4 > 4) {
            [[unroll]] for (uint32_t d = 0; d < HSK_per_thread / 4; ++d) {
                FLOAT_TYPEV4 Q_cache[rows_per_thread];
                [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
                    Q_cache[r] = Qf[tile_row(r) * qf_stride + d * D_split + d_tid];
                }

                [[unroll]] for (uint32_t c = 0; c < cols_per_thread; ++c) {
                    if (KV_bounds_check && j * Bc + c * cols_per_iter + col_tid >= KV) {
                        continue;
                    }

                    FLOAT_TYPEV4 K_Tf;
                    if (SHMEM_STAGING != 0) {
                        K_Tf = kvsh[(c * cols_per_iter + col_tid) * kvsh_stride + (d * D_split + d_tid)];
                    } else if (USE_DECODE_K) {
                        uint coord = (j * Bc + c * cols_per_iter + col_tid) * k_stride * BLOCK_SIZE_K + 4 * (d * D_split + d_tid);
                        uint ib = coord / BLOCK_SIZE_K;
                        uint iqs = (coord % BLOCK_SIZE_K);
                        K_Tf = dequantize4(ib, iqs, k_offset, BINDING_IDX_K);
                    } else {
                        K_Tf = FLOAT_TYPEV4(data_kv4[k_offset / 4 + (j * Bc + c * cols_per_iter + col_tid) * k_stride / 4 + d * D_split + d_tid]);
                    }
                    [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
                        Sf[r][c] += dot(ACC_TYPEV4(Q_cache[r]), ACC_TYPEV4(K_Tf));
                    }
                }
            }
        } else {
            [[unroll]] for (uint32_t c = 0; c < cols_per_thread; ++c) {
                if (KV_bounds_check && j * Bc + c * cols_per_iter + col_tid >= KV) {
                    continue;
                }

                [[unroll]] for (uint32_t d = 0; d < HSK_per_thread / 4; ++d) {
                    FLOAT_TYPEV4 K_Tf;
                    if (SHMEM_STAGING != 0) {
                        K_Tf = kvsh[(c * cols_per_iter + col_tid) * kvsh_stride + (d * D_split + d_tid)];
                    } else if (USE_DECODE_K) {
                        uint coord = (j * Bc + c * cols_per_iter + col_tid) * k_stride * BLOCK_SIZE_K + 4 * (d * D_split + d_tid);
                        uint ib = coord / BLOCK_SIZE_K;
                        uint iqs = (coord % BLOCK_SIZE_K);
                        K_Tf = dequantize4(ib, iqs, k_offset, BINDING_IDX_K);
                    } else {
                        K_Tf = FLOAT_TYPEV4(data_kv4[k_offset / 4 + (j * Bc + c * cols_per_iter + col_tid) * k_stride / 4 + d * D_split + d_tid]);
                    }
                    [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
                        Sf[r][c] += dot(ACC_TYPEV4(Qf[tile_row(r) * qf_stride + d * D_split + d_tid]), ACC_TYPEV4(K_Tf));
                    }
                }
            }
        }
#else // MMQ
        const uint hsk4 = HSK_per_thread / 4;
        const uint d_per_step = (hsk4 % 8 == 0) ? 8 :
                                (hsk4 % 4 == 0) ? 4 :
                                (hsk4 % 2 == 0) ? 2 : 1;

        [[unroll]] for (uint32_t c = 0; c < cols_per_thread; ++c) {
            if (KV_bounds_check && j * Bc + c * cols_per_iter + col_tid >= KV) {
                continue;
            }

            [[unroll]] for (uint32_t d_block = 0; d_block < HSK_per_thread / 4; d_block += d_per_step) {
                int32_t k_quants[d_per_step];
                ACC_TYPEV2 k_dm;

                // Q4_*/Q5_* take the block-8 fast path when one step covers a full
                // block; Q8_0 always goes through the per-int get_k_qs* helpers
                // (its qs is byte-packed, not nibble-packed).
                const bool block8_fast = (d_per_step == 8) && (FaTypeK != FA_TYPE_Q8_0);

                if (SHMEM_STAGING != 0) {
                    const uint k_block_idx = (d_tid * (HSK_per_thread / 4) + d_block) / 8;
                    const uint buf_ib = (c * cols_per_iter + col_tid) * qf_stride + k_block_idx;
                    k_dm = ACC_TYPEV2(kblocksh[buf_ib].dm);

                    if (block8_fast) {
                        const bool has_qh = (FaTypeK == FA_TYPE_Q5_0) || (FaTypeK == FA_TYPE_Q5_1);
                        [[unroll]] for (uint32_t d = 0; d < 4; d++) {
                            uint vui = kblocksh[buf_ib].qs[d];
                            k_quants[d    ] = int32_t( vui       & 0x0F0F0F0F);
                            k_quants[d + 4] = int32_t((vui >> 4) & 0x0F0F0F0F);
                            if (has_qh) {
                                uint qh_lo = (kblocksh[buf_ib].qh >> (d * 4)) & 0xF;
                                uint qh_hi = (kblocksh[buf_ib].qh >> (d * 4 + 16)) & 0xF;
                                k_quants[d    ] |= int32_t((qh_lo * 0x02040810u) & 0x10101010u);
                                k_quants[d + 4] |= int32_t((qh_hi * 0x02040810u) & 0x10101010u);
                            }
                        }
                    } else {
                        [[unroll]] for (uint32_t d = 0; d < d_per_step; d++) {
                            k_quants[d] = get_k_qs_shmem(buf_ib, (d_tid * (HSK_per_thread / 4) + d_block) % 8 + d);
                        }
                    }
                } else {
                    const uint coord = (j * Bc + c * cols_per_iter + col_tid) * k_stride * BLOCK_SIZE_K + 4 * (d_tid * (HSK_per_thread / 4) + d_block);
                    const uint ib = coord / BLOCK_SIZE_K;
                    const uint iqs = (coord % BLOCK_SIZE_K);

                    k_dm = ACC_TYPEV2(get_k_scale(ib, k_offset));

                    if (block8_fast) {
                        fa_k_qs_block8 blk = get_k_qs_block8(ib, k_offset);
                        [[unroll]] for (uint32_t d = 0; d < 8; d++) {
                            k_quants[d] = blk.qs[d];
                        }
                    } else {
                        [[unroll]] for (uint32_t d = 0; d < d_per_step; d++) {
                            k_quants[d] = get_k_qs(ib, iqs + d * 4, k_offset);
                        }
                    }
                }

                [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
                    const uint qib = tile_row(r) * qf_stride + (d_tid * (HSK_per_thread / 4) + d_block) / 8;
                    const uint qiqs = (d_tid * (HSK_per_thread / 4) + d_block) % 8;

                    int32_t acc = 0;
                    [[unroll]] for (uint32_t d = 0; d < d_per_step; d++) {
                        acc += dotPacked4x8EXT(Qf[qib].qs[qiqs + d], k_quants[d]);
                    }

                    Sf[r][c] += ACC_TYPE(acc) * ACC_TYPE(Qf[qib].ds.x) * k_dm.x;
                    if ((d_tid * (HSK_per_thread / 4) + d_block) % 8 == 0) {
                        Sf[r][c] += k_dot_correction(qib, k_dm);
                    }
                }
            }
        }
#endif // MMQ

        [[unroll]] for (uint32_t c = 0; c < cols_per_thread; ++c) {
            // Compute sum across the D_split
            [[unroll]] for (uint s = D_split / 2; s > 0; s >>= 1) {
                [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
                    Sf[r][c] += subgroupShuffleXor(Sf[r][c], s);
                }
            }
        }

        if (LOGIT_SOFTCAP) {
            [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
                [[unroll]] for (uint32_t c = 0; c < cols_per_thread; ++c) {
                    Sf[r][c] = ACC_TYPE(p.logit_softcap * tanh(Sf[r][c]));
                }
            }
        }

        if (MASK_ENABLE && mask_opt_bits != MASK_OPT_ALL_ZERO) {
            [[unroll]] for (uint32_t c = 0; c < cols_per_thread; ++c) {
                [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
                    FLOAT_TYPE mvf = masksh[(c * cols_per_iter + col_tid) * masksh_stride + tile_row(r)];

                    Sf[r][c] += slope[r]*mvf;
                }
            }
        }

        float eMf[rows_per_thread];
        [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
            float rowmaxf = NEG_FLT_MAX_OVER_2;
            [[unroll]] for (uint32_t c = 0; c < cols_per_thread; ++c) {
                if (KV_bounds_check && j * Bc + c * cols_per_iter + col_tid >= KV) {
                    continue;
                }
                rowmaxf = max(rowmaxf, float(Sf[r][c]));
            }
            float Moldf = Mf[r];

            // M = max(rowmax, Mold)
            // P = e^(S - M)
            // eM = e^(Mold - M)
            Mf[r] = max(rowmaxf, Moldf);
            eMf[r] = exp(Moldf - Mf[r]);
            Lf[r] = eMf[r]*Lf[r];
        }

        [[unroll]] for (uint32_t d = 0; d < HSV_per_thread / 4; ++d) {
            [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
                Of[r][d] = FLOAT_TYPE(eMf[r]) * Of[r][d];
            }
        }

        if (SHMEM_STAGING != 0) {
            barrier();
            [[unroll]] for (uint32_t idx = 0; idx < Bc * HSV / 4; idx += gl_WorkGroupSize.x) {
                uint32_t d = (idx + tid) % (HSV / 4);
                uint32_t c = (idx + tid) / (HSV / 4);
                if (idx + gl_WorkGroupSize.x <= Bc * HSV / 4 || c < Bc) {
                    FLOAT_TYPEV4 V_Tf = FLOAT_TYPEV4(0);
                    if (!KV_bounds_check || j * Bc + c < KV) {
                        if (USE_DECODE_V) {
                            uint coord = (j * Bc + c) * v_stride * BLOCK_SIZE_V + 4 * d;
                            uint ib = coord / BLOCK_SIZE_V;
                            uint iqs = (coord % BLOCK_SIZE_V);
                            V_Tf = dequantize4(ib, iqs, v_offset, BINDING_IDX_V);
                        } else {
                            V_Tf = FLOAT_TYPEV4(data_vv4[v_offset / 4 + (j * Bc + c) * v_stride / 4 + d]);
                        }
                    }

                    kvsh[c * kvsh_stride + d] = V_Tf;
                }
            }
            barrier();
        }

        [[unroll]] for (uint32_t c = 0; c < cols_per_thread; ++c) {
            if (KV_bounds_check && j * Bc + c * cols_per_iter + col_tid >= KV) {
                continue;
            }

            FLOAT_TYPE Pf[rows_per_thread];
            [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
                Pf[r] = FLOAT_TYPE(exp(float(Sf[r][c]) - Mf[r]));
                Lf[r] += Pf[r];
            }

            [[unroll]] for (uint32_t d = 0; d < HSV_per_thread / 4; ++d) {
                FLOAT_TYPEV4 Vf;
                if (SHMEM_STAGING != 0) {
                    Vf = kvsh[(c * cols_per_iter + col_tid) * kvsh_stride + (d * D_split + d_tid)];
                } else if (USE_DECODE_V) {
                    uint coord = (j * Bc + c * cols_per_iter + col_tid) * v_stride * BLOCK_SIZE_V + 4 * (d * D_split + d_tid);
                    uint ib = coord / BLOCK_SIZE_V;
                    uint iqs = (coord % BLOCK_SIZE_V);
                    Vf = dequantize4(ib, iqs, v_offset, BINDING_IDX_V);
                } else {
                    Vf = FLOAT_TYPEV4(data_vv4[v_offset / 4 + (j * Bc + c * cols_per_iter + col_tid) * v_stride / 4 + d * D_split + d_tid]);
                }
                [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
                    Of[r][d] += FLOAT_TYPEV4(Pf[r] * Vf);
                }
            }
        }
    }

    // prevent race on tmpsh
    barrier();

    // reduce across threads

    [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
        float rowmaxf = Mf[r];

        // Compute max across the row
        if (SubGroupSize > 0) {
            [[unroll]] for (uint s = D_split; s < SubGroupSize; s *= 2) {
                rowmaxf = max(rowmaxf, subgroupShuffleXor(rowmaxf, s));
            }
            if (row_split == 1) {
                // Reduce inside workgroup with shmem
                barrier();
                if (gl_SubgroupInvocationID == d_tid) {
                    tmpsh[gl_SubgroupID * D_split + d_tid] = rowmaxf;
                }
                barrier();
                rowmaxf = tmpsh[d_tid];
                [[unroll]] for (uint32_t s = 1; s < num_subgroups; ++s) {
                    rowmaxf = max(rowmaxf, tmpsh[s * D_split + d_tid]);
                }
            }
        } else {
            barrier();
            tmpsh[tid] = rowmaxf;
            barrier();
            [[unroll]] for (int s = int(threads_per_rowgroup) / 2; s >= D_split; s >>= 1) {
                if (rowgroup_tid < s) {
                    tmpsh[tid] = max(tmpsh[tid], tmpsh[tid ^ s]);
                }
                barrier();
            }
            rowmaxf = tmpsh[row_tid * threads_per_rowgroup + d_tid];
        }

        float Moldf = Mf[r];

        // M = max(rowmax, Mold)
        // eM = e^(Mold - M)
        Mf[r] = max(rowmaxf, Moldf);
        float eMf = exp(Moldf - Mf[r]);

        Lf[r] = eMf*Lf[r];

        // Compute sum across the row
        if (SubGroupSize > 0) {
            [[unroll]] for (uint s = D_split; s < SubGroupSize; s *= 2) {
                Lf[r] += subgroupShuffleXor(Lf[r], s);
            }
            if (row_split == 1) {
                barrier();
                if (gl_SubgroupInvocationID == d_tid) {
                    tmpsh[gl_SubgroupID * D_split + d_tid] = Lf[r];
                }
                barrier();
                Lf[r] = tmpsh[d_tid];
                [[unroll]] for (uint32_t s = 1; s < num_subgroups; ++s) {
                    Lf[r] += tmpsh[s * D_split + d_tid];
                }
            }
        } else {
            barrier();
            tmpsh[tid] = Lf[r];
            barrier();
            [[unroll]] for (int s = int(threads_per_rowgroup) / 2; s >= D_split; s >>= 1) {
                if (rowgroup_tid < s) {
                    tmpsh[tid] = tmpsh[tid] + tmpsh[tid ^ s];
                }
                barrier();
            }
            Lf[r] = tmpsh[row_tid * threads_per_rowgroup + d_tid];
        }

        [[unroll]] for (uint32_t d = 0; d < HSV_per_thread / 4; ++d) {
            Of[r][d] = FLOAT_TYPE(eMf) * Of[r][d];

            if (SubGroupSize > 0) {
                [[unroll]] for (uint s = D_split; s < SubGroupSize; s *= 2) {
                    if (!OLD_AMD_WINDOWS) {
                        Of[r][d] += subgroupShuffleXor(Of[r][d], s);
                    } else {
                        // Something about f16vec4 subgroupShuffleXor is broken on AMD Windows RDNA2 and below.
                        // Shuffle full vec4 as workaround.
                        // See https://github.com/ggml-org/llama.cpp/issues/19881#issuecomment-3958643697
                        Of[r][d] += FLOAT_TYPEV4(subgroupShuffleXor(vec4(Of[r][d]), s));
                    }
                }
                if (row_split == 1) {
                    barrier();
                    if (gl_SubgroupInvocationID == d_tid) {
                        tmpshv4[gl_SubgroupID * D_split + d_tid] = Of[r][d];
                    }
                    barrier();
                    Of[r][d] = tmpshv4[d_tid];
                    [[unroll]] for (uint32_t s = 1; s < num_subgroups; ++s) {
                        Of[r][d] += tmpshv4[s * D_split + d_tid];
                    }
                }
            } else {
                barrier();
                tmpshv4[tid] = Of[r][d];
                barrier();
                [[unroll]] for (int s = int(threads_per_rowgroup) / 2; s >= D_split; s >>= 1) {
                    if (rowgroup_tid < s) {
                        Of[r][d] += tmpshv4[tid ^ s];
                        tmpshv4[tid] = Of[r][d];
                    }
                    barrier();
                }
                Of[r][d] = tmpshv4[row_tid * threads_per_rowgroup + d_tid];
            }
        }
    }


    // If there is split_k, then the split_k resolve shader does the final
    // division by L. Store the intermediate O value and per-row m and L values.
    if (p.k_num > 1) {
        if (p.gqa_ratio > 1) {
            // note: O and Q have swapped coord 1,2.
            uint32_t o_offset = HSV * p.ne1 * (split_k_index + p.k_num * (gqa_iq1 + p.ne2 * iq3)) / 4;

            [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
                const uint row = tile_row(r);
                if (row < N) {
                    [[unroll]] for (uint32_t d = 0; d < HSV_per_thread / 4; ++d) {
                        gqaStore(row, d * D_split + d_tid, Of[r][d], o_offset, iq2, N);
                    }
                }
            }

            o_offset = HSV * p.ne1 * p.k_num * p.ne2 * p.ne3 + p.ne1 * 2 * (split_k_index + p.k_num * (gqa_iq1 + p.ne2 * iq3));
            [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
                const uint row = tile_row(r);
                if (row < N) {
                    perElemOpStoreCol0(row, 0u, ACC_TYPE(Lf[r]), o_offset, iq2, N);
                    perElemOpStoreCol0(row, 0u, ACC_TYPE(Mf[r]), o_offset + p.ne1, iq2, N);
                }
            }
        } else {
            [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
                const uint row = tile_row(r);
                const uint global_row = i * Br + row;

                if (global_row < N) {
                    uint32_t o_offset = HSV * p.ne1 * (split_k_index + p.k_num * (global_row + p.ne2 * iq3)) / 4;

                    [[unroll]] for (uint32_t d = 0; d < HSV_per_thread / 4; ++d) {
                        data_ov4[o_offset + iq2 * HSV/4 + d * D_split + d_tid] = D_TYPEV4(Of[r][d]);
                    }
                }

                if (global_row < N && d_tid == 0 && col_tid == 0) {
                    uint32_t lm_offset = HSV * p.ne1 * p.k_num * p.ne2 * p.ne3 + p.ne1 * 2 * (split_k_index + p.k_num * (global_row + p.ne2 * iq3));
                    data_o[lm_offset + iq2] = D_TYPE(Lf[r]);
                    data_o[lm_offset + p.ne1 + iq2] = D_TYPE(Mf[r]);
                }
            }
        }
        return;
    }

    if ((p.mask_n_head_log2 & SINK_ENABLE_BIT) != 0) {
        [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
            float sink = perElemOpGetSink(tile_row(r), 0u, ACC_TYPE(0), iq2);

            float ms = 1.0f;
            float vs = 1.0f;

            if (sink > Mf[r]) {
                ms = exp(Mf[r] - sink);

                [[unroll]] for (uint32_t d = 0; d < HSV_per_thread / 4; ++d) {
                    Of[r][d] *= FLOAT_TYPE(ms);
                }
            } else {
                vs = exp(sink - Mf[r]);
            }

            Lf[r] = Lf[r]*ms + vs;
        }
    }

    float Lfrcp[rows_per_thread];
    [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
        Lfrcp[r] = (Lf[r] == 0.0) ? 0.0 : (1.0 / Lf[r]);
    }

    [[unroll]] for (uint32_t d = 0; d < HSV_per_thread / 4; ++d) {
        [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
            Of[r][d] *= FLOAT_TYPE(Lfrcp[r]);
#if defined(FLOAT_TYPE_MAX)
            Of[r][d] = clamp(Of[r][d], -FLOAT_TYPE_MAX, FLOAT_TYPE_MAX);
#endif
        }
    }

    uint32_t o_offset = (gqa_iq1*p.ne1*HSV + iq3*p.ne2*p.ne1*HSV) / 4;

    if (p.gqa_ratio > 1) {
        [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
            const uint row = tile_row(r);
            if (row < N) {
                [[unroll]] for (uint32_t d = 0; d < HSV_per_thread / 4; ++d) {
                    gqaStore(row, d * D_split + d_tid, Of[r][d], o_offset, iq2, N);
                }
            }
        }
    } else {
        [[unroll]] for (uint32_t r = 0; r < rows_per_thread; ++r) {
            const uint row = tile_row(r);
            if (i * Br + row < N) {
                [[unroll]] for (uint32_t d = 0; d < HSV_per_thread / 4; ++d) {
                    data_ov4[o_offset + (iq2 * HSV + (i * Br + row) * p.ne1 * HSV) / 4 + d * D_split + d_tid] = D_TYPEV4(Of[r][d]);
                }
            }
        }
    }
}
