cera 0.2.1

Rust-native LLM inference engine
Documentation
// Argmax over an f32 array: writes the index of the maximum element to out[0].
//
// Single-workgroup, 256 threads. Each thread strides through `x` finding its
// local (max_val, max_idx); then a tree reduction over workgroup memory
// collapses to thread 0, which writes the global argmax. Tie-break: the
// lower index wins (matches CPU `cpu_argmax`'s `>` comparator + iter order).
//
// Bind group 0:
//   @binding(0) x: array<f32>     (read-only logits)
//   @binding(1) out: array<u32>   (read-write, len >= 1; writes out[0])
//   @binding(2) params: vec2<u32> (n, 0)
//
// Dispatch: (1, 1, 1) — single workgroup. n up to ~few hundred K is fine
// (typical vocab_size is 32K-200K); per-thread stride loop handles any n.

@group(0) @binding(0) var<storage, read> x: array<f32>;
@group(0) @binding(1) var<storage, read_write> out: array<u32>;
@group(0) @binding(2) var<storage, read> params: vec2<u32>;

var<workgroup> shared_val: array<f32, 256>;
var<workgroup> shared_idx: array<u32, 256>;

@compute @workgroup_size(256, 1, 1)
fn argmax_f32(@builtin(local_invocation_id) lid: vec3<u32>) {
    let tid = lid.x;
    let n = params.x;

    // Phase 1: thread-local max with stride 256.
    // Init with -FLT_MAX so any real value wins on first compare.
    var local_max: f32 = -3.402823e+38;
    var local_idx: u32 = 0u;
    var i = tid;
    while i < n {
        let v = x[i];
        // Strict `>`: tie-break favors the lower index (the value already
        // recorded), matching `cpu_argmax`'s behavior.
        if v > local_max {
            local_max = v;
            local_idx = i;
        }
        i += 256u;
    }
    shared_val[tid] = local_max;
    shared_idx[tid] = local_idx;
    workgroupBarrier();

    // Tree reduction. Pair up halves: each thread keeps the (val, idx) of
    // whichever half has the larger value (lower idx on tie, same as above).
    var stride: u32 = 128u;
    loop {
        if tid < stride {
            let other_val = shared_val[tid + stride];
            let other_idx = shared_idx[tid + stride];
            if other_val > shared_val[tid] {
                shared_val[tid] = other_val;
                shared_idx[tid] = other_idx;
            } else if other_val == shared_val[tid] && other_idx < shared_idx[tid] {
                shared_idx[tid] = other_idx;
            }
        }
        workgroupBarrier();
        if stride == 1u { break; }
        stride = stride >> 1u;
    }

    if tid == 0u {
        out[0] = shared_idx[0];
    }
}