use anyhow::{Result, ensure};
use rlx_cpu::blas;
pub fn layer_norm(
x: &[f32],
gamma: &[f32],
beta: &[f32],
dim: usize,
eps: f32,
) -> Result<Vec<f32>> {
ensure!(
x.len().is_multiple_of(dim),
"layer_norm input len must be divisible by dim"
);
ensure!(gamma.len() == dim, "layer_norm gamma len mismatch");
ensure!(beta.len() == dim, "layer_norm beta len mismatch");
let rows = x.len() / dim;
let mut out = vec![0.0; x.len()];
for r in 0..rows {
let row = &x[r * dim..(r + 1) * dim];
let mean = row.iter().sum::<f32>() / dim as f32;
let var = row
.iter()
.map(|v| {
let d = *v - mean;
d * d
})
.sum::<f32>()
/ dim as f32;
let inv = 1.0 / (var + eps).sqrt();
for c in 0..dim {
out[r * dim + c] = (row[c] - mean) * inv * gamma[c] + beta[c];
}
}
Ok(out)
}
pub fn linear(
x: &[f32],
rows: usize,
in_dim: usize,
w_t: &[f32],
out_dim: usize,
b: &[f32],
) -> Result<Vec<f32>> {
ensure!(x.len() == rows * in_dim, "linear input shape mismatch");
ensure!(
w_t.len() == in_dim * out_dim,
"linear weight shape mismatch"
);
ensure!(b.len() == out_dim, "linear bias shape mismatch");
let mut out = vec![0.0f32; rows * out_dim];
blas::sgemm_bias(x, w_t, b, &mut out, rows, in_dim, out_dim);
Ok(out)
}
pub fn matmul(a: &[f32], b: &[f32], out: &mut [f32], m: usize, k: usize, n: usize) {
blas::sgemm(a, b, out, m, k, n);
}
pub fn matmul_bt(a: &[f32], b: &[f32], out: &mut [f32], m: usize, k: usize, n: usize, alpha: f32) {
blas::sgemm_bt(a, b, out, m, k, n, alpha);
}
pub fn gelu_tanh(x: &mut [f32]) {
const K: f32 = 0.797_884_6;
for v in x {
let x3 = *v * *v * *v;
*v = 0.5 * *v * (1.0 + (K * (*v + 0.044715 * x3)).tanh());
}
}
#[allow(dead_code)]
pub fn sigmoid(x: f32) -> f32 {
1.0 / (1.0 + (-x).exp())
}
#[allow(clippy::too_many_arguments)]
pub fn multihead_attention(
q: &[f32],
k: &[f32],
v: &[f32],
in_proj_w_t: &[f32], in_proj_b: &[f32], out_proj_w_t: &[f32],
out_proj_b: &[f32],
batch: usize,
l_q: usize,
l_k: usize,
embed_dim: usize,
num_heads: usize,
key_padding_mask: Option<&[u8]>,
) -> Result<Vec<f32>> {
ensure!(
embed_dim.is_multiple_of(num_heads),
"embed_dim {embed_dim} not divisible by num_heads {num_heads}"
);
let head_dim = embed_dim / num_heads;
let (wq, wk, wv) = split_in_proj_w(in_proj_w_t, embed_dim);
let bq = &in_proj_b[0..embed_dim];
let bk = &in_proj_b[embed_dim..2 * embed_dim];
let bv = &in_proj_b[2 * embed_dim..3 * embed_dim];
let q_proj = linear(q, batch * l_q, embed_dim, &wq, embed_dim, bq)?;
let k_proj = linear(k, batch * l_k, embed_dim, &wk, embed_dim, bk)?;
let v_proj = linear(v, batch * l_k, embed_dim, &wv, embed_dim, bv)?;
let bh = batch * num_heads;
let mut qh = vec![0f32; bh * l_q * head_dim];
let mut kh = vec![0f32; bh * l_k * head_dim];
let mut vh = vec![0f32; bh * l_k * head_dim];
repack_heads(&q_proj, &mut qh, batch, l_q, num_heads, head_dim);
repack_heads(&k_proj, &mut kh, batch, l_k, num_heads, head_dim);
repack_heads(&v_proj, &mut vh, batch, l_k, num_heads, head_dim);
let scale = 1.0f32 / (head_dim as f32).sqrt();
let mut scores = vec![0f32; l_q * l_k];
let mut attn_out = vec![0f32; bh * l_q * head_dim];
for bi in 0..batch {
for h in 0..num_heads {
let bhi = bi * num_heads + h;
let q_h = &qh[bhi * l_q * head_dim..(bhi + 1) * l_q * head_dim];
let k_h = &kh[bhi * l_k * head_dim..(bhi + 1) * l_k * head_dim];
let v_h = &vh[bhi * l_k * head_dim..(bhi + 1) * l_k * head_dim];
matmul_bt(q_h, k_h, &mut scores, l_q, head_dim, l_k, scale);
if let Some(mask) = key_padding_mask {
let mask_b = &mask[bi * l_k..(bi + 1) * l_k];
for r in 0..l_q {
let row = &mut scores[r * l_k..(r + 1) * l_k];
for (c, m) in mask_b.iter().enumerate() {
if *m != 0 {
row[c] = f32::NEG_INFINITY;
}
}
}
}
softmax_rows(&mut scores, l_q, l_k);
let out_h = &mut attn_out[bhi * l_q * head_dim..(bhi + 1) * l_q * head_dim];
matmul(&scores, v_h, out_h, l_q, l_k, head_dim);
}
}
let mut packed = vec![0f32; batch * l_q * embed_dim];
for bi in 0..batch {
for l in 0..l_q {
for h in 0..num_heads {
let src = ((bi * num_heads + h) * l_q + l) * head_dim;
let dst = (bi * l_q + l) * embed_dim + h * head_dim;
packed[dst..dst + head_dim].copy_from_slice(&attn_out[src..src + head_dim]);
}
}
}
linear(
&packed,
batch * l_q,
embed_dim,
out_proj_w_t,
embed_dim,
out_proj_b,
)
}
fn split_in_proj_w(in_proj_w_t: &[f32], embed_dim: usize) -> (Vec<f32>, Vec<f32>, Vec<f32>) {
let e = embed_dim;
let mut wq = vec![0f32; e * e];
let mut wk = vec![0f32; e * e];
let mut wv = vec![0f32; e * e];
for i in 0..e {
for j in 0..e {
wq[i * e + j] = in_proj_w_t[i * 3 * e + j];
wk[i * e + j] = in_proj_w_t[i * 3 * e + e + j];
wv[i * e + j] = in_proj_w_t[i * 3 * e + 2 * e + j];
}
}
(wq, wk, wv)
}
fn repack_heads(
src: &[f32],
dst: &mut [f32],
batch: usize,
l: usize,
num_heads: usize,
head_dim: usize,
) {
let e = num_heads * head_dim;
for bi in 0..batch {
for li in 0..l {
for h in 0..num_heads {
let s = (bi * l + li) * e + h * head_dim;
let d = ((bi * num_heads + h) * l + li) * head_dim;
dst[d..d + head_dim].copy_from_slice(&src[s..s + head_dim]);
}
}
}
}
pub fn softmax_rows(x: &mut [f32], rows: usize, cols: usize) {
for r in 0..rows {
let row = &mut x[r * cols..(r + 1) * cols];
let mut m = row[0];
for &v in row.iter().skip(1) {
if v > m {
m = v;
}
}
let mut sum = 0.0f32;
for v in row.iter_mut() {
*v = (*v - m).exp();
sum += *v;
}
let inv = 1.0 / sum;
for v in row.iter_mut() {
*v *= inv;
}
}
}