cera 0.5.2

Rust-native LLM inference engine
Documentation
// Swap the two outer axes of a `[A][B][K]` f32 tensor, keeping the innermost
// `K`-wide block contiguous: `dst[b][a][k] = src[a][b][k]`.
//
// Two call sites in the audio encoder, which is why it is blocked rather than a
// plain 2D transpose:
//
//   - Conv-stem flatten, `K = f_out`: the stem emits `[channel][time][freq]` and
//     `pre_encode_out` consumes `[time][channel*freq]` rows. That is the
//     `permute(0,2,1,3) + reshape` in the C++ reference, and it is exactly this
//     kernel with the freq axis as the inner block.
//   - The Conformer conv module, `K = 1`: time-major `[t][n_embd]` has to become
//     channel-major `[n_embd][t]` for the depthwise conv1d and back again for
//     the second pointwise conv. `K = 1` degenerates to an ordinary 2D
//     transpose, so both directions are this one kernel.
//
//   binding/buffer 0: src     f32, read  [A][B][K]
//   binding/buffer 1: dst     f32, write [B][A][K]
//   binding/buffer 2: params  (A, B, K, _)
//
// Dispatch: one thread per element, ceil(A*B*K / 256) workgroups of 256.

[[vk::binding(0)]] StructuredBuffer<float>   src_buf : register(t0);
[[vk::binding(1)]] RWStructuredBuffer<float> dst_buf : register(u1);
[[vk::binding(2)]] StructuredBuffer<uint4>   par_buf : register(t2);

[shader("compute")]
[numthreads(256, 1, 1)]
void transpose_blocked(uint3 gid : SV_DispatchThreadID) {
    uint a_dim = par_buf[0].x;
    uint b_dim = par_buf[0].y;
    uint k_dim = par_buf[0].z;

    uint bk = b_dim * k_dim;
    uint total = a_dim * bk;
    uint idx = gid.x;
    if (idx >= total) {
        return;
    }

    uint a = idx / bk;
    uint rem = idx - a * bk;
    uint b = rem / k_dim;
    uint k = rem - b * k_dim;

    dst_buf[(b * a_dim + a) * k_dim + k] = src_buf[idx];
}