cera 0.1.1

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

// Batched fused conv1d: processes N tokens in a single dispatch.
// Each thread handles one channel and loops over all N tokens sequentially
// (rolling state requires sequential processing per channel).
//
// This eliminates N-1 dispatch round-trips per conv layer.
// Dispatch: ceil(hidden_size / 256) threadgroups × 256 threads.

struct Params {
    uint hidden_size;
    uint kernel_size;
    uint d_conv;
    uint n_tokens;
    uint proj_stride;   // stride between token projections (floats), = 3*hs
    uint out_stride;    // stride between output vectors (floats), = hs
};

kernel void conv1d_fused_batch(
    const device float* proj [[buffer(0)]],    // [n_tokens × 3*hs]: (x, c, b) per token
    device float* rbuffer [[buffer(1)]],       // rolling conv state [d_conv × hs]
    const device float* weight [[buffer(2)]],  // conv weight [hs × kernel_size]
    device float* output [[buffer(3)]],        // output [n_tokens × hs]
    constant Params& params [[buffer(4)]],
    uint ch [[thread_position_in_grid]]
) {
    uint hs = params.hidden_size;
    uint ks = params.kernel_size;
    uint d_conv = params.d_conv;
    uint n_tokens = params.n_tokens;
    uint proj_stride = params.proj_stride;
    uint out_stride = params.out_stride;
    if (ch >= hs) return;

    // Pre-load the conv weight for this channel (small: kernel_size ≤ 4).
    float w[4]; // max kernel_size = 4
    for (uint k = 0; k <= d_conv && k < 4; k++) {
        w[k] = weight[ch * ks + k];
    }

    // Pre-load rolling buffer state for this channel.
    float rb[3]; // max d_conv = 3
    for (uint k = 0; k < d_conv && k < 3; k++) {
        rb[k] = rbuffer[k * hs + ch];
    }

    for (uint t = 0; t < n_tokens; t++) {
        uint base = t * proj_stride;
        // x is at proj[base + ch], c at proj[base + hs + ch], b at proj[base + 2*hs + ch]
        float x_val = proj[base + ch];
        float b_val = proj[base + 2 * hs + ch];
        float c_val = proj[base + hs + ch];

        float bx = x_val * b_val;

        // Conv1d with registers.
        float sum = 0.0f;
        for (uint k = 0; k < d_conv && k < 3; k++) {
            sum += rb[k] * w[k];
        }
        sum += bx * w[d_conv];

        // Shift rolling buffer in registers.
        if (d_conv > 1u) {
            for (uint k = 0; k < d_conv - 1u && k < 2; k++) {
                rb[k] = rb[k + 1];
            }
        }
        if (d_conv > 0u) {
            rb[d_conv - 1u] = bx;
        }

        // output = c * conv_out
        output[t * out_stride + ch] = c_val * sum;
    }

    // Write rolling buffer back.
    for (uint k = 0; k < d_conv && k < 3; k++) {
        rbuffer[k * hs + ch] = rb[k];
    }
}