cera 0.3.0

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

// Fused: bx = x * b → conv1d(bx, state) → output = c * conv_out
// Combines 3 dispatches into 1 per token per conv layer.
// Dispatch: ceil(hidden_size / 256) threadgroups × 256 threads.

struct Params {
    uint hidden_size;
    uint kernel_size;
    uint d_conv;
    uint _pad;
};

kernel void conv1d_fused(
    const device float* x [[buffer(0)]],       // proj[i*3*hs]     (x component)
    const device float* b [[buffer(1)]],       // proj[i*3*hs+2*hs] (b component)
    const device float* c [[buffer(2)]],       // proj[i*3*hs+hs]  (c component)
    device float* rbuffer [[buffer(3)]],       // rolling conv state
    const device float* weight [[buffer(4)]],  // conv weight
    device float* output [[buffer(5)]],        // result written here
    constant Params& params [[buffer(6)]],
    uint ch [[thread_position_in_grid]]
) {
    uint hs = params.hidden_size;
    uint ks = params.kernel_size;
    uint d_conv = params.d_conv;
    if (ch >= hs) return;

    // Step 1: bx = x * b
    float bx = x[ch] * b[ch];

    // Step 2: conv1d with rolling buffer
    float sum = 0.0f;
    for (uint k_idx = 0u; k_idx < d_conv; k_idx++) {
        sum += rbuffer[k_idx * hs + ch] * weight[ch * ks + k_idx];
    }
    sum += bx * weight[ch * ks + d_conv];

    // Update rolling buffer: shift left, append bx
    if (d_conv > 1u) {
        for (uint k_idx = 0u; k_idx < d_conv - 1u; k_idx++) {
            rbuffer[k_idx * hs + ch] = rbuffer[(k_idx + 1u) * hs + ch];
        }
    }
    if (d_conv > 0u) {
        rbuffer[(d_conv - 1u) * hs + ch] = bx;
    }

    // Step 3: output = c * conv_out
    output[ch] = c[ch] * sum;
}