cera 0.5.5

Rust-native LLM inference engine
Documentation
// slang-entries: relu_inplace silu_inplace gelu_erf_inplace
//
// Unary in-place f32 activations, one Slang source for both GPU backends. These
// three share `gelu.slang`'s binding contract (buffer in place at 0, params at
// 1) but live in their own file so the ViT's tanh-GELU kernel keeps its
// single-purpose source and does not have to be regenerated to add an unrelated
// entry point.
//
// All three are on the LFM2A audio-encoder path:
//   - `relu_inplace`     between the conv subsampling stem's layers,
//   - `silu_inplace`     inside each Conformer macaron FFN,
//   - `gelu_erf_inplace` in the MLP adapter that projects to the LLM's width.
//
// **`gelu_erf_inplace` is the erf form, not the tanh form.** `gelu.slang`
// computes the tanh approximation that CLIP-family ViTs expect; the audio
// adapter was trained against the exact erf GELU, and the two differ by ~1e-3
// relative around |x| ~ 1. Picking the wrong one degrades output measurably even
// though any single call looks fine (see the note on `cpu::gelu_inplace`).
//
// `erf` has no WGSL or MSL builtin, so it is the same Abramowitz & Stegun 7.1.26
// approximation `cpu::erff` uses, with the same f32-truncated constants (max
// abs error ~1.5e-7). The CPU feeds that formula `ggml_expf` where this uses the
// hardware `exp`, which is the only intended divergence.
//
//   binding/buffer 0: x       f32, read-write, activated in place (n elements)
//   binding/buffer 1: params  (n, unused)
//
// Dispatch: (ceil(n / 256), 1, 1) workgroups of 256.

[[vk::binding(0)]] RWStructuredBuffer<float> x_buf   : register(u0);
[[vk::binding(1)]] StructuredBuffer<uint2>   par_buf : register(t1);

[shader("compute")]
[numthreads(256, 1, 1)]
void relu_inplace(uint3 gid : SV_DispatchThreadID) {
    uint i = gid.x;
    if (i >= par_buf[0].x) {
        return;
    }
    x_buf[i] = max(x_buf[i], 0.0f);
}

[shader("compute")]
[numthreads(256, 1, 1)]
void silu_inplace(uint3 gid : SV_DispatchThreadID) {
    uint i = gid.x;
    if (i >= par_buf[0].x) {
        return;
    }
    float v = clamp(x_buf[i], -80.0f, 80.0f);
    x_buf[i] = v / (1.0f + exp(-v));
}

[shader("compute")]
[numthreads(256, 1, 1)]
void gelu_erf_inplace(uint3 gid : SV_DispatchThreadID) {
    uint i = gid.x;
    if (i >= par_buf[0].x) {
        return;
    }
    float v = x_buf[i];

    // erf(v / sqrt(2)), Abramowitz & Stegun 7.1.26.
    float a = abs(v * 0.70710678f);
    float sign = (v < 0.0f) ? -1.0f : 1.0f;
    float t = 1.0f / (1.0f + 0.3275911f * a);
    float poly = ((((1.0614054f * t - 1.453152f) * t) + 1.4214137f) * t - 0.2844967f) * t
               + 0.2548296f;
    float erf = sign * (1.0f - poly * t * exp(-a * a));

    x_buf[i] = 0.5f * v * (1.0f + erf);
}