cera 0.2.1

Rust-native LLM inference engine
Documentation
#include <metal_stdlib>
using namespace metal;

// Q8_0 GEMV matching llama.cpp's kernel_mul_mv_q8_0_f32 pattern.
// NQ=8 elements per thread, 4 threads per Q8_0 block, 2 rows per TG.
// Dispatch: ceil(m/2) threadgroups × 32 threads.

struct Params {
    uint m;
    uint k;
};

constant constexpr uint QK8_0 = 32;
constant constexpr uint BLOCK_BYTES = 34;
constant constexpr short NQ = 8;       // elements per thread
constant constexpr short NR = 2;       // rows per TG
constant constexpr short NW = 32;      // simdgroup width

kernel void gemv_q8_0(
    const device uchar* src0 [[buffer(0)]],
    const device float* src1 [[buffer(1)]],
    device float* dst [[buffer(2)]],
    constant Params& params [[buffer(3)]],
    uint tg_id [[threadgroup_position_in_grid]],
    uint tiisg [[thread_index_in_simdgroup]]
) {
    const uint m = params.m;
    const uint k = params.k;
    const uint nb = k / QK8_0;
    const uint row_bytes = nb * BLOCK_BYTES;

    const short ix = tiisg / (NW/NQ);  // 0..7: which block in the stride
    const short il = tiisg % (NW/NQ);  // 0..3: which 8-element chunk

    for (short r = 0; r < NR; r++) {
        const uint row = tg_id * NR + r;
        if (row >= m) continue;

        const device uchar* row_ptr = src0 + row * row_bytes;
        const device float* y = src1;

        float sumf = 0.0f;

        for (uint ib = ix; ib < nb; ib += NQ) {
            // Read scale from block header (2-byte f16)
            const device uchar* blk = row_ptr + ib * BLOCK_BYTES;
            float d = float(*(const device half*)blk);

            // Read 8 quants from this block, offset by il*NQ
            const device int8_t* qs = (const device int8_t*)(blk + 2) + il * NQ;

            // Read corresponding 8 input values
            const device float* yb = y + ib * QK8_0 + il * NQ;

            float partial = 0.0f;
            for (short i = 0; i < NQ; i++) {
                partial += float(qs[i]) * yb[i];
            }

            sumf += partial * d;
        }

        float total = simd_sum(sumf);
        if (tiisg == 0) {
            dst[row] = total;
        }
    }
}

kernel void gemv_q8_0_accum(
    const device uchar* src0 [[buffer(0)]],
    const device float* src1 [[buffer(1)]],
    device float* dst [[buffer(2)]],
    constant Params& params [[buffer(3)]],
    uint tg_id [[threadgroup_position_in_grid]],
    uint tiisg [[thread_index_in_simdgroup]]
) {
    const uint m = params.m;
    const uint k = params.k;
    const uint nb = k / QK8_0;
    const uint row_bytes = nb * BLOCK_BYTES;

    const short ix = tiisg / (NW/NQ);
    const short il = tiisg % (NW/NQ);

    for (short r = 0; r < NR; r++) {
        const uint row = tg_id * NR + r;
        if (row >= m) continue;

        const device uchar* row_ptr = src0 + row * row_bytes;
        const device float* y = src1;

        float sumf = 0.0f;

        for (uint ib = ix; ib < nb; ib += NQ) {
            const device uchar* blk = row_ptr + ib * BLOCK_BYTES;
            float d = float(*(const device half*)blk);
            const device int8_t* qs = (const device int8_t*)(blk + 2) + il * NQ;
            const device float* yb = y + ib * QK8_0 + il * NQ;

            float partial = 0.0f;
            for (short i = 0; i < NQ; i++) {
                partial += float(qs[i]) * yb[i];
            }

            sumf += partial * d;
        }

        float total = simd_sum(sumf);
        if (tiisg == 0) {
            dst[row] += total;
        }
    }
}