1#[cfg(not(target_arch = "wasm32"))]
5use rayon::prelude::*;
6
7pub fn add(a: &[f32], b: &[f32]) -> Vec<f32> {
8 a.iter().zip(b).map(|(x, y)| x + y).collect()
9}
10
11pub fn gelu(x: &[f32]) -> Vec<f32> {
13 const C: f32 = 0.797_884_6; x.iter()
15 .map(|&v| 0.5 * v * (1.0 + (C * (v + 0.044715 * v * v * v)).tanh()))
16 .collect()
17}
18
19#[allow(clippy::too_many_arguments)]
25pub fn matmul(
26 a: &[f32],
27 b: &[f32],
28 bias: Option<&[f32]>,
29 m: usize,
30 k: usize,
31 n: usize,
32 batch: usize,
33 a_stride: usize,
34 b_stride: usize,
35 trans_a: bool,
36 trans_b: bool,
37 alpha: f32,
38) -> Vec<f32> {
39 let mut out = vec![0.0f32; batch * m * n];
40 #[cfg(not(target_arch = "wasm32"))]
42 let rows = out.par_chunks_mut(n);
43 #[cfg(target_arch = "wasm32")]
44 let rows = out.chunks_mut(n);
45 rows.enumerate().for_each(|(row_idx, orow)| {
46 let bat = row_idx / m;
47 let i = row_idx % m;
48 let a_base = bat * a_stride;
49 let b_base = bat * b_stride;
50 let a_at = |kk: usize| {
51 if trans_a {
52 a[a_base + kk * m + i]
53 } else {
54 a[a_base + i * k + kk]
55 }
56 };
57 if trans_b {
58 for (j, o) in orow.iter_mut().enumerate() {
59 let b_row = &b[b_base + j * k..b_base + (j + 1) * k];
60 let dot: f32 = b_row
61 .iter()
62 .enumerate()
63 .map(|(kk, &bv)| a_at(kk) * bv)
64 .sum();
65 *o = dot * alpha;
66 }
67 } else {
68 for kk in 0..k {
69 let av = a_at(kk);
70 let b_row = &b[b_base + kk * n..b_base + (kk + 1) * n];
71 for (o, &bv) in orow.iter_mut().zip(b_row) {
72 *o += av * bv;
73 }
74 }
75 if alpha != 1.0 {
76 for o in orow.iter_mut() {
77 *o *= alpha;
78 }
79 }
80 }
81 if let Some(bias) = bias {
82 for (o, &bb) in orow.iter_mut().zip(bias) {
83 *o += bb;
84 }
85 }
86 });
87 out
88}
89
90pub fn kv_append(
92 dst: &mut [f32],
93 src: &[f32],
94 h: usize,
95 t: usize,
96 hd: usize,
97 cap: usize,
98 len: usize,
99) {
100 for hh in 0..h {
101 for tt in 0..t {
102 let d0 = hh * cap * hd + (len + tt) * hd;
103 let s0 = hh * t * hd + tt * hd;
104 dst[d0..d0 + hd].copy_from_slice(&src[s0..s0 + hd]);
105 }
106 }
107}
108
109pub fn softmax(
113 x: &[f32],
114 rows: usize,
115 cols: usize,
116 q_len: usize,
117 causal: bool,
118 off: usize,
119) -> Vec<f32> {
120 let mut out = vec![0.0f32; rows * cols];
121 for r in 0..rows {
122 let base = r * cols;
123 let visible = |j: usize| !causal || j <= (r % q_len) + off;
124 let mut max = f32::NEG_INFINITY;
125 for j in 0..cols {
126 if visible(j) {
127 max = max.max(x[base + j]);
128 }
129 }
130 let mut sum = 0.0f32;
131 for j in 0..cols {
132 if visible(j) {
133 sum += (x[base + j] - max).exp();
134 }
135 }
136 for j in 0..cols {
137 if visible(j) {
138 out[base + j] = (x[base + j] - max).exp() / sum;
139 }
140 }
141 }
142 out
143}
144
145pub fn layernorm(
147 x: &[f32],
148 gamma: &[f32],
149 beta: &[f32],
150 rows: usize,
151 cols: usize,
152 eps: f32,
153) -> Vec<f32> {
154 let mut out = vec![0.0f32; rows * cols];
155 let nf = cols as f32;
156 for r in 0..rows {
157 let row = &x[r * cols..(r + 1) * cols];
158 let mean: f32 = row.iter().sum::<f32>() / nf;
159 let var: f32 = row.iter().map(|&v| (v - mean) * (v - mean)).sum::<f32>() / nf;
160 let inv_std = 1.0 / (var + eps).sqrt();
161 for j in 0..cols {
162 out[r * cols + j] = (row[j] - mean) * inv_std * gamma[j] + beta[j];
163 }
164 }
165 out
166}
167
168pub fn embedding(ids: &[u32], wte: &[f32], wpe: Option<&[f32]>, c: usize, pos: usize) -> Vec<f32> {
170 let mut out = vec![0.0f32; ids.len() * c];
171 for (t, &id) in ids.iter().enumerate() {
172 let dst = &mut out[t * c..(t + 1) * c];
173 dst.copy_from_slice(&wte[id as usize * c..(id as usize + 1) * c]);
174 if let Some(wpe) = wpe {
175 for (d, &pv) in dst.iter_mut().zip(&wpe[(t + pos) * c..(t + pos + 1) * c]) {
176 *d += pv;
177 }
178 }
179 }
180 out
181}
182
183pub fn split_heads(qkv: &[f32], t: usize, c: usize, h: usize) -> (Vec<f32>, Vec<f32>, Vec<f32>) {
185 let hd = c / h;
186 let mut q = vec![0.0f32; t * c];
187 let mut k = vec![0.0f32; t * c];
188 let mut v = vec![0.0f32; t * c];
189 for hh in 0..h {
190 for tt in 0..t {
191 for d in 0..hd {
192 let dst = hh * t * hd + tt * hd + d;
193 let src_row = tt * 3 * c;
194 let col = hh * hd + d;
195 q[dst] = qkv[src_row + col];
196 k[dst] = qkv[src_row + c + col];
197 v[dst] = qkv[src_row + 2 * c + col];
198 }
199 }
200 }
201 (q, k, v)
202}
203
204pub fn merge_heads(x: &[f32], t: usize, c: usize, h: usize) -> Vec<f32> {
206 let hd = c / h;
207 let mut out = vec![0.0f32; t * c];
208 for tt in 0..t {
209 for hh in 0..h {
210 for d in 0..hd {
211 out[tt * c + hh * hd + d] = x[hh * t * hd + tt * hd + d];
212 }
213 }
214 }
215 out
216}
217
218pub fn gelu_bwd(x: &[f32], dy: &[f32]) -> Vec<f32> {
222 const C: f32 = 0.797_884_6; const A: f32 = 0.044715;
224 x.iter()
225 .zip(dy)
226 .map(|(&v, &g)| {
227 let u = C * (v + A * v * v * v);
228 let th = u.tanh();
229 let sech2 = 1.0 - th * th;
230 let d = 0.5 * (1.0 + th) + 0.5 * v * sech2 * C * (1.0 + 3.0 * A * v * v);
231 d * g
232 })
233 .collect()
234}
235
236pub fn softmax_bwd(y: &[f32], dy: &[f32], rows: usize, cols: usize) -> Vec<f32> {
239 let mut out = vec![0.0f32; rows * cols];
240 for r in 0..rows {
241 let base = r * cols;
242 let s: f32 = (0..cols).map(|j| y[base + j] * dy[base + j]).sum();
243 for j in 0..cols {
244 out[base + j] = y[base + j] * (dy[base + j] - s);
245 }
246 }
247 out
248}
249
250pub fn layernorm_bwd_dx(
253 x: &[f32],
254 gamma: &[f32],
255 dy: &[f32],
256 rows: usize,
257 cols: usize,
258 eps: f32,
259) -> Vec<f32> {
260 let nf = cols as f32;
261 let mut out = vec![0.0f32; rows * cols];
262 for r in 0..rows {
263 let row = &x[r * cols..(r + 1) * cols];
264 let mean: f32 = row.iter().sum::<f32>() / nf;
265 let var: f32 = row.iter().map(|&v| (v - mean) * (v - mean)).sum::<f32>() / nf;
266 let inv_std = 1.0 / (var + eps).sqrt();
267 let mut s1 = 0.0f32; let mut s2 = 0.0f32; for j in 0..cols {
270 let gd = gamma[j] * dy[r * cols + j];
271 s1 += gd;
272 s2 += gd * (row[j] - mean) * inv_std;
273 }
274 for j in 0..cols {
275 let xhat = (row[j] - mean) * inv_std;
276 let gd = gamma[j] * dy[r * cols + j];
277 out[r * cols + j] = inv_std * (gd - s1 / nf - xhat * s2 / nf);
278 }
279 }
280 out
281}
282
283pub fn layernorm_bwd_dparams(
285 x: &[f32],
286 dy: &[f32],
287 rows: usize,
288 cols: usize,
289 eps: f32,
290) -> (Vec<f32>, Vec<f32>) {
291 let nf = cols as f32;
292 let mut dgamma = vec![0.0f32; cols];
293 let mut dbeta = vec![0.0f32; cols];
294 for r in 0..rows {
295 let row = &x[r * cols..(r + 1) * cols];
296 let mean: f32 = row.iter().sum::<f32>() / nf;
297 let var: f32 = row.iter().map(|&v| (v - mean) * (v - mean)).sum::<f32>() / nf;
298 let inv_std = 1.0 / (var + eps).sqrt();
299 for j in 0..cols {
300 let d = dy[r * cols + j];
301 dgamma[j] += d * (row[j] - mean) * inv_std;
302 dbeta[j] += d;
303 }
304 }
305 (dgamma, dbeta)
306}
307
308pub fn sum_rows(x: &[f32], rows: usize, cols: usize) -> Vec<f32> {
310 let mut out = vec![0.0f32; cols];
311 for r in 0..rows {
312 for j in 0..cols {
313 out[j] += x[r * cols + j];
314 }
315 }
316 out
317}
318
319pub fn scatter_add_rows(dst: &mut [f32], ids: &[u32], src: &[f32], c: usize) {
321 for (r, &id) in ids.iter().enumerate() {
322 let d0 = id as usize * c;
323 for j in 0..c {
324 dst[d0 + j] += src[r * c + j];
325 }
326 }
327}
328
329pub fn gather_nll(probs: &[f32], ids: &[u32], cols: usize) -> Vec<f32> {
331 ids.iter()
332 .enumerate()
333 .map(|(r, &id)| -probs[r * cols + id as usize].max(f32::MIN_POSITIVE).ln())
334 .collect()
335}
336
337pub fn ce_bwd(probs: &[f32], ids: &[u32], rows: usize, cols: usize, scale: f32) -> Vec<f32> {
339 let mut out = vec![0.0f32; rows * cols];
340 for r in 0..rows {
341 for j in 0..cols {
342 let onehot = (j as u32 == ids[r]) as u32 as f32;
343 out[r * cols + j] = (probs[r * cols + j] - onehot) * scale;
344 }
345 }
346 out
347}
348
349pub fn pcg_hash(x: u32) -> u32 {
352 let state = x.wrapping_mul(747796405).wrapping_add(2891336453);
353 let word = ((state >> ((state >> 28) + 4)) ^ state).wrapping_mul(277803737);
354 (word >> 22) ^ word
355}
356
357pub fn dropout(x: &[f32], p: f32, scale: f32, seed: u32) -> Vec<f32> {
363 x.iter()
364 .enumerate()
365 .map(|(i, &v)| {
366 let r = pcg_hash(seed ^ (i as u32).wrapping_mul(0x9E37_79B9));
367 let u = (r >> 8) as f32 / 16_777_216.0; if u >= p { v * scale } else { 0.0 }
369 })
370 .collect()
371}
372
373pub fn unsplit_head(d: &[f32], t: usize, c: usize, h: usize, which: usize) -> Vec<f32> {
376 let hd = c / h;
377 let mut out = vec![0.0f32; t * 3 * c];
378 for hh in 0..h {
379 for tt in 0..t {
380 for dd in 0..hd {
381 out[tt * 3 * c + which * c + hh * hd + dd] = d[hh * t * hd + tt * hd + dd];
382 }
383 }
384 }
385 out
386}
387
388pub fn unmerge_heads(dy: &[f32], t: usize, c: usize, h: usize) -> Vec<f32> {
390 let hd = c / h;
391 let mut out = vec![0.0f32; t * c];
392 for hh in 0..h {
393 for tt in 0..t {
394 for dd in 0..hd {
395 out[hh * t * hd + tt * hd + dd] = dy[tt * c + hh * hd + dd];
396 }
397 }
398 }
399 out
400}
401
402#[allow(clippy::too_many_arguments)]
404pub fn adamw(
405 param: &mut [f32],
406 grad: &[f32],
407 m: &mut [f32],
408 v: &mut [f32],
409 lr: f32,
410 beta1: f32,
411 beta2: f32,
412 eps: f32,
413 weight_decay: f32,
414 step: u32,
415) {
416 let bc1 = 1.0 - beta1.powi(step as i32);
417 let bc2 = 1.0 - beta2.powi(step as i32);
418 for i in 0..param.len() {
419 m[i] = beta1 * m[i] + (1.0 - beta1) * grad[i];
420 v[i] = beta2 * v[i] + (1.0 - beta2) * grad[i] * grad[i];
421 let mhat = m[i] / bc1;
422 let vhat = v[i] / bc2;
423 param[i] -= lr * (mhat / (vhat.sqrt() + eps) + weight_decay * param[i]);
424 }
425}