1use anyhow::{Result, ensure};
24use rlx_cpu::blas;
25
26pub fn layer_norm(
27 x: &[f32],
28 gamma: &[f32],
29 beta: &[f32],
30 dim: usize,
31 eps: f32,
32) -> Result<Vec<f32>> {
33 ensure!(
34 x.len().is_multiple_of(dim),
35 "layer_norm input len must be divisible by dim"
36 );
37 ensure!(gamma.len() == dim, "layer_norm gamma len mismatch");
38 ensure!(beta.len() == dim, "layer_norm beta len mismatch");
39 let rows = x.len() / dim;
40 let mut out = vec![0.0; x.len()];
41 for r in 0..rows {
42 let row = &x[r * dim..(r + 1) * dim];
43 let mean = row.iter().sum::<f32>() / dim as f32;
44 let var = row
45 .iter()
46 .map(|v| {
47 let d = *v - mean;
48 d * d
49 })
50 .sum::<f32>()
51 / dim as f32;
52 let inv = 1.0 / (var + eps).sqrt();
53 for c in 0..dim {
54 out[r * dim + c] = (row[c] - mean) * inv * gamma[c] + beta[c];
55 }
56 }
57 Ok(out)
58}
59
60pub fn linear(
63 x: &[f32],
64 rows: usize,
65 in_dim: usize,
66 w_t: &[f32],
67 out_dim: usize,
68 b: &[f32],
69) -> Result<Vec<f32>> {
70 ensure!(x.len() == rows * in_dim, "linear input shape mismatch");
71 ensure!(
72 w_t.len() == in_dim * out_dim,
73 "linear weight shape mismatch"
74 );
75 ensure!(b.len() == out_dim, "linear bias shape mismatch");
76 let mut out = vec![0.0f32; rows * out_dim];
77 blas::sgemm_bias(x, w_t, b, &mut out, rows, in_dim, out_dim);
78 Ok(out)
79}
80
81pub fn matmul(a: &[f32], b: &[f32], out: &mut [f32], m: usize, k: usize, n: usize) {
83 blas::sgemm(a, b, out, m, k, n);
84}
85
86pub fn matmul_bt(a: &[f32], b: &[f32], out: &mut [f32], m: usize, k: usize, n: usize, alpha: f32) {
88 blas::sgemm_bt(a, b, out, m, k, n, alpha);
89}
90
91pub fn gelu_tanh(x: &mut [f32]) {
92 const K: f32 = 0.797_884_6;
93 for v in x {
94 let x3 = *v * *v * *v;
95 *v = 0.5 * *v * (1.0 + (K * (*v + 0.044715 * x3)).tanh());
96 }
97}
98
99#[allow(dead_code)]
102pub fn sigmoid(x: f32) -> f32 {
103 1.0 / (1.0 + (-x).exp())
104}
105
106#[allow(clippy::too_many_arguments)]
112pub fn multihead_attention(
113 q: &[f32],
114 k: &[f32],
115 v: &[f32],
116 in_proj_w_t: &[f32], in_proj_b: &[f32], out_proj_w_t: &[f32],
119 out_proj_b: &[f32],
120 batch: usize,
121 l_q: usize,
122 l_k: usize,
123 embed_dim: usize,
124 num_heads: usize,
125 key_padding_mask: Option<&[u8]>,
126) -> Result<Vec<f32>> {
127 ensure!(
128 embed_dim.is_multiple_of(num_heads),
129 "embed_dim {embed_dim} not divisible by num_heads {num_heads}"
130 );
131 let head_dim = embed_dim / num_heads;
132
133 let (wq, wk, wv) = split_in_proj_w(in_proj_w_t, embed_dim);
137 let bq = &in_proj_b[0..embed_dim];
138 let bk = &in_proj_b[embed_dim..2 * embed_dim];
139 let bv = &in_proj_b[2 * embed_dim..3 * embed_dim];
140
141 let q_proj = linear(q, batch * l_q, embed_dim, &wq, embed_dim, bq)?;
142 let k_proj = linear(k, batch * l_k, embed_dim, &wk, embed_dim, bk)?;
143 let v_proj = linear(v, batch * l_k, embed_dim, &wv, embed_dim, bv)?;
144
145 let bh = batch * num_heads;
147 let mut qh = vec![0f32; bh * l_q * head_dim];
148 let mut kh = vec![0f32; bh * l_k * head_dim];
149 let mut vh = vec![0f32; bh * l_k * head_dim];
150 repack_heads(&q_proj, &mut qh, batch, l_q, num_heads, head_dim);
151 repack_heads(&k_proj, &mut kh, batch, l_k, num_heads, head_dim);
152 repack_heads(&v_proj, &mut vh, batch, l_k, num_heads, head_dim);
153
154 let scale = 1.0f32 / (head_dim as f32).sqrt();
155 let mut scores = vec![0f32; l_q * l_k];
156 let mut attn_out = vec![0f32; bh * l_q * head_dim];
157 for bi in 0..batch {
158 for h in 0..num_heads {
159 let bhi = bi * num_heads + h;
160 let q_h = &qh[bhi * l_q * head_dim..(bhi + 1) * l_q * head_dim];
161 let k_h = &kh[bhi * l_k * head_dim..(bhi + 1) * l_k * head_dim];
162 let v_h = &vh[bhi * l_k * head_dim..(bhi + 1) * l_k * head_dim];
163 matmul_bt(q_h, k_h, &mut scores, l_q, head_dim, l_k, scale);
164 if let Some(mask) = key_padding_mask {
165 let mask_b = &mask[bi * l_k..(bi + 1) * l_k];
166 for r in 0..l_q {
167 let row = &mut scores[r * l_k..(r + 1) * l_k];
168 for (c, m) in mask_b.iter().enumerate() {
169 if *m != 0 {
170 row[c] = f32::NEG_INFINITY;
171 }
172 }
173 }
174 }
175 softmax_rows(&mut scores, l_q, l_k);
176 let out_h = &mut attn_out[bhi * l_q * head_dim..(bhi + 1) * l_q * head_dim];
177 matmul(&scores, v_h, out_h, l_q, l_k, head_dim);
178 }
179 }
180
181 let mut packed = vec![0f32; batch * l_q * embed_dim];
183 for bi in 0..batch {
184 for l in 0..l_q {
185 for h in 0..num_heads {
186 let src = ((bi * num_heads + h) * l_q + l) * head_dim;
187 let dst = (bi * l_q + l) * embed_dim + h * head_dim;
188 packed[dst..dst + head_dim].copy_from_slice(&attn_out[src..src + head_dim]);
189 }
190 }
191 }
192 linear(
193 &packed,
194 batch * l_q,
195 embed_dim,
196 out_proj_w_t,
197 embed_dim,
198 out_proj_b,
199 )
200}
201
202fn split_in_proj_w(in_proj_w_t: &[f32], embed_dim: usize) -> (Vec<f32>, Vec<f32>, Vec<f32>) {
203 let e = embed_dim;
208 let mut wq = vec![0f32; e * e];
209 let mut wk = vec![0f32; e * e];
210 let mut wv = vec![0f32; e * e];
211 for i in 0..e {
212 for j in 0..e {
213 wq[i * e + j] = in_proj_w_t[i * 3 * e + j];
214 wk[i * e + j] = in_proj_w_t[i * 3 * e + e + j];
215 wv[i * e + j] = in_proj_w_t[i * 3 * e + 2 * e + j];
216 }
217 }
218 (wq, wk, wv)
219}
220
221fn repack_heads(
222 src: &[f32],
223 dst: &mut [f32],
224 batch: usize,
225 l: usize,
226 num_heads: usize,
227 head_dim: usize,
228) {
229 let e = num_heads * head_dim;
230 for bi in 0..batch {
231 for li in 0..l {
232 for h in 0..num_heads {
233 let s = (bi * l + li) * e + h * head_dim;
234 let d = ((bi * num_heads + h) * l + li) * head_dim;
235 dst[d..d + head_dim].copy_from_slice(&src[s..s + head_dim]);
236 }
237 }
238 }
239}
240
241pub fn softmax_rows(x: &mut [f32], rows: usize, cols: usize) {
244 for r in 0..rows {
245 let row = &mut x[r * cols..(r + 1) * cols];
246 let mut m = row[0];
247 for &v in row.iter().skip(1) {
248 if v > m {
249 m = v;
250 }
251 }
252 let mut sum = 0.0f32;
253 for v in row.iter_mut() {
254 *v = (*v - m).exp();
255 sum += *v;
256 }
257 let inv = 1.0 / sum;
258 for v in row.iter_mut() {
259 *v *= inv;
260 }
261 }
262}