1use crate::pool::Pool;
27use crate::qtensor::QTensor;
28use std::sync::OnceLock;
29use std::sync::atomic::{AtomicU64, Ordering};
30
31static PERF_GDN_FORWARD_CALLS: AtomicU64 = AtomicU64::new(0);
32static PERF_GDN_FORWARD_NS: AtomicU64 = AtomicU64::new(0);
33static PERF_GDN_BATCH_CALLS: AtomicU64 = AtomicU64::new(0);
34static PERF_GDN_BATCH_NS: AtomicU64 = AtomicU64::new(0);
35static PERF_GDN_STEP_CALLS: AtomicU64 = AtomicU64::new(0);
36static PERF_GDN_STEP_NS: AtomicU64 = AtomicU64::new(0);
37
38fn perf_enabled() -> bool {
39 static ON: OnceLock<bool> = OnceLock::new();
40 *ON.get_or_init(|| std::env::var("CMF_PERF_PROFILE").as_deref() == Ok("1"))
41}
42
43pub struct VmfPhaseWeights {
45 pub thq: QTensor,
47 pub thk: QTensor,
49 pub v_proj: QTensor,
51 pub out_proj: QTensor,
53 pub decay: Vec<f64>,
55 pub conv: Option<Vec<f32>>,
61 pub k_gate: Option<(QTensor, Vec<f32>)>,
68}
69
70#[derive(Clone, Copy)]
71pub struct VmfPhaseCfg {
72 pub num_heads: usize,
73 pub nphase: usize,
74 pub value_head_dim: usize,
75 pub hidden_size: usize,
76 pub phase_mass: f32,
85}
86
87impl VmfPhaseCfg {
88 pub fn state_len(&self) -> usize {
89 self.num_heads * 2 * self.nphase * self.value_head_dim
90 }
91}
92
93fn phase_step(
97 thq: &[f32],
98 thk: &[f32],
99 v: &[f32],
100 decay: &[f64],
101 kap: Option<&[f32]>,
102 cfg: &VmfPhaseCfg,
103 state: &mut [f32],
104 out: &mut [f32],
105) {
106 let (nh, nph, dv) = (cfg.num_heads, cfg.nphase, cfg.value_head_dim);
107 let mscale = 1.0f64 / (1.0 + cfg.phase_mass as f64);
109 let p2 = 2 * nph;
110 for h in 0..nh {
111 let s = &mut state[h * p2 * dv..(h + 1) * p2 * dv];
112 let thk_h = &thk[h * nph..(h + 1) * nph];
113 let thq_h = &thq[h * nph..(h + 1) * nph];
114 let vt = &v[h * dv..(h + 1) * dv];
115 let ot = &mut out[h * dv..(h + 1) * dv];
116 let dec = &decay[h * p2..(h + 1) * p2];
117 let kh = kap.map_or(1.0f64, |k| k[h] as f64);
119 for f in 0..p2 {
120 let (fk, fq) = if f < nph {
122 (
123 (thk_h[f] as f64 * mscale).cos(),
124 (thq_h[f] as f64 * mscale).cos(),
125 )
126 } else {
127 (
128 (thk_h[f - nph] as f64 * mscale).sin(),
129 (thq_h[f - nph] as f64 * mscale).sin(),
130 )
131 };
132 let fkw = fk * kh;
133 let row = &mut s[f * dv..(f + 1) * dv];
134 let dcf = dec[f];
135 for d in 0..dv {
136 let cell = dcf * row[d] as f64 + fkw * vt[d] as f64;
138 row[d] = cell as f32;
139 ot[d] += (fq * cell) as f32; }
141 }
142 }
143}
144
145fn kappa_of(x: &[f32], w: &VmfPhaseWeights, nh: usize, pool: Option<&Pool>) -> Option<Vec<f32>> {
148 let (kw, kb) = w.k_gate.as_ref()?;
149 let mut k = vec![0.0f32; nh];
150 kw.matvec(x, &mut k, pool);
151 for (v, b) in k.iter_mut().zip(kb) {
152 *v = 1.0 / (1.0 + (-(*v + b)).exp());
153 }
154 Some(k)
155}
156
157fn conv_in(
161 x: &[f32],
162 w: &VmfPhaseWeights,
163 cfg: &VmfPhaseCfg,
164 state: &mut Vec<f32>,
165) -> Option<Vec<f32>> {
166 let taps = w.conv.as_ref()?;
167 let h = cfg.hidden_size;
168 let k = taps.len() / h.max(1);
169 if k < 2 || taps.len() != h * k {
170 return None;
171 }
172 let ring = (k - 1) * h;
173 let base = cfg.state_len();
174 if state.len() != base + ring {
175 let mut ns = vec![0f32; base + ring];
177 let n = state.len().min(base);
178 ns[..n].copy_from_slice(&state[..n]);
179 *state = ns;
180 }
181 let mut y = vec![0.0f32; h];
182 for c in 0..h {
183 let mut acc = taps[c * k + k - 1] * x[c];
185 for j in 0..k - 1 {
186 acc += taps[c * k + j] * state[base + j * h + c];
187 }
188 y[c] = acc;
189 }
190 conv_ring_push(x, h, base, state);
191 Some(y)
192}
193
194fn conv_ring_push(x: &[f32], h: usize, base: usize, state: &mut [f32]) {
196 let ring = state.len() - base;
197 state.copy_within(base + h.., base);
198 let at = base + ring - h;
199 state[at..at + h].copy_from_slice(&x[..h]);
200}
201
202pub fn vmf_phase_forward(
204 x: &[f32],
205 w: &VmfPhaseWeights,
206 cfg: &VmfPhaseCfg,
207 state: &mut Vec<f32>,
208 pool: Option<&Pool>,
209) -> Vec<f32> {
210 if w.conv.is_none() && state.len() != cfg.state_len() {
211 *state = vec![0f32; cfg.state_len()];
212 }
213 let xc = conv_in(x, w, cfg, state);
214 let x = xc.as_deref().unwrap_or(x);
215 let (nh, nph, dv) = (cfg.num_heads, cfg.nphase, cfg.value_head_dim);
216
217 let mut thq = vec![0.0f32; nh * nph];
218 w.thq.matvec(x, &mut thq, pool);
219 let mut thk = vec![0.0f32; nh * nph];
220 w.thk.matvec(x, &mut thk, pool);
221 let mut v = vec![0.0f32; nh * dv];
222 w.v_proj.matvec(x, &mut v, pool);
223
224 let kap = kappa_of(x, w, nh, pool);
225 let mut o = vec![0.0f32; nh * dv];
226 phase_step(&thq, &thk, &v, &w.decay, kap.as_deref(), cfg, state, &mut o);
227
228 let mut out = vec![0.0f32; cfg.hidden_size];
229 w.out_proj.matvec(&o, &mut out, pool);
230 out
231}
232
233#[allow(clippy::too_many_arguments)]
238pub fn vmf_phase_pair(
239 x1: &[f32],
240 x2: &[f32],
241 w: &VmfPhaseWeights,
242 cfg: &VmfPhaseCfg,
243 state: &mut Vec<f32>,
244 scratch: &mut Vec<f32>,
245 pool: Option<&Pool>,
246) -> (Vec<f32>, Vec<f32>) {
247 if w.conv.is_none() && state.len() != cfg.state_len() {
248 *state = vec![0f32; cfg.state_len()];
249 }
250 let xc1 = conv_in(x1, w, cfg, state);
253 let x1 = xc1.as_deref().unwrap_or(x1);
254 let (xc2, x2raw) = if w.conv.is_some() {
255 let mut tmp = state.clone();
256 (conv_in(x2, w, cfg, &mut tmp), Some(x2))
257 } else {
258 (None, None)
259 };
260 let x2 = xc2.as_deref().unwrap_or(x2);
261 let (nh, nph, dv) = (cfg.num_heads, cfg.nphase, cfg.value_head_dim);
262
263 let mut thq1 = vec![0.0f32; nh * nph];
264 let mut thq2 = vec![0.0f32; nh * nph];
265 w.thq.matvec2(x1, x2, &mut thq1, &mut thq2, pool);
266 let mut thk1 = vec![0.0f32; nh * nph];
267 let mut thk2 = vec![0.0f32; nh * nph];
268 w.thk.matvec2(x1, x2, &mut thk1, &mut thk2, pool);
269 let mut v1 = vec![0.0f32; nh * dv];
270 let mut v2 = vec![0.0f32; nh * dv];
271 w.v_proj.matvec2(x1, x2, &mut v1, &mut v2, pool);
272
273 let kap1 = kappa_of(x1, w, nh, pool);
275 let mut o1 = vec![0.0f32; nh * dv];
276 phase_step(
277 &thq1,
278 &thk1,
279 &v1,
280 &w.decay,
281 kap1.as_deref(),
282 cfg,
283 state,
284 &mut o1,
285 );
286
287 let kap2 = kappa_of(x2, w, nh, pool);
289 scratch.clear();
290 scratch.extend_from_slice(state);
291 let mut o2 = vec![0.0f32; nh * dv];
292 phase_step(
293 &thq2,
294 &thk2,
295 &v2,
296 &w.decay,
297 kap2.as_deref(),
298 cfg,
299 scratch,
300 &mut o2,
301 );
302 if let Some(xr) = x2raw {
305 conv_ring_push(xr, cfg.hidden_size, cfg.state_len(), scratch);
306 }
307
308 let mut out1 = vec![0.0f32; cfg.hidden_size];
309 let mut out2 = vec![0.0f32; cfg.hidden_size];
310 w.out_proj.matvec2(&o1, &o2, &mut out1, &mut out2, pool);
311 (out1, out2)
312}
313
314pub struct GdnWeights {
319 pub in_proj_qkv: QTensor,
321 pub in_proj_z: QTensor,
323 pub in_proj_a: QTensor,
325 pub in_proj_b: QTensor,
327 pub conv1d: Vec<f32>,
329 pub a_log: Vec<f32>,
331 pub dt_bias: Vec<f32>,
333 pub norm: Vec<f32>,
335 pub out_proj: QTensor,
337}
338
339#[derive(Clone, Copy)]
340pub struct GdnCfg {
341 pub num_v_heads: usize,
342 pub num_k_heads: usize,
343 pub key_head_dim: usize,
344 pub value_head_dim: usize,
345 pub conv_kernel: usize,
346 pub hidden_size: usize,
347 pub rms_eps: f64,
348 pub output_gate_sigmoid: bool,
351}
352
353impl GdnCfg {
354 pub fn conv_dim(&self) -> usize {
355 2 * self.num_k_heads * self.key_head_dim + self.num_v_heads * self.value_head_dim
356 }
357
358 pub fn state_len(&self) -> usize {
361 (self.conv_kernel - 1) * self.conv_dim()
362 + self.num_v_heads * self.key_head_dim * self.value_head_dim
363 }
364}
365
366fn softplus(x: f64) -> f64 {
367 if x > 20.0 { x } else { x.exp().ln_1p() }
368}
369
370fn sigmoid(x: f64) -> f64 {
371 1.0 / (1.0 + (-x).exp())
372}
373
374fn silu(x: f64) -> f64 {
375 x / (1.0 + (-x).exp())
376}
377
378#[derive(Clone, Copy)]
381struct SendMutF32(*mut f32);
382unsafe impl Send for SendMutF32 {}
383unsafe impl Sync for SendMutF32 {}
384
385#[allow(clippy::too_many_arguments)]
398fn gdn_step(
399 qkv: &[f32],
400 z: &[f32],
401 a: &[f32],
402 b: &[f32],
403 w: &GdnWeights,
404 cfg: &GdnCfg,
405 state: &mut [f32],
406 of: &mut [f32],
407 pool: Option<&Pool>,
408) {
409 let perf_t0 = perf_enabled().then(std::time::Instant::now);
410 let (nv, nk, dk, dv, kk) = (
411 cfg.num_v_heads,
412 cfg.num_k_heads,
413 cfg.key_head_dim,
414 cfg.value_head_dim,
415 cfg.conv_kernel,
416 );
417 let c_dim = cfg.conv_dim();
418 let (kd, rep) = (nk * dk, nv / nk);
419 let (ring, s_all) = state.split_at_mut((kk - 1) * c_dim);
420
421 let mut cq = vec![0f32; c_dim];
425 for c in 0..c_dim {
426 let taps = &w.conv1d[c * kk..(c + 1) * kk];
427 let mut acc = qkv[c] as f64 * taps[kk - 1] as f64;
428 for j in 0..kk - 1 {
429 acc += ring[j * c_dim + c] as f64 * taps[j] as f64;
430 }
431 cq[c] = silu(acc) as f32;
432 }
433 if kk > 1 {
435 ring.copy_within(c_dim.., 0);
436 let tail = (kk - 2) * c_dim;
437 ring[tail..tail + c_dim].copy_from_slice(&qkv[..c_dim]);
438 }
439
440 let cq = &cq;
441 let s_ptr = SendMutF32(s_all.as_mut_ptr());
442 let of_ptr = SendMutF32(of.as_mut_ptr());
443 let head_range = |h0: usize, h1: usize| {
444 let (s_ptr, of_ptr) = (s_ptr, of_ptr);
447 let mut kv = crate::attention::take_buf(dv);
449 let mut delta = crate::attention::take_buf(dv);
450 let mut o = crate::attention::take_buf(dv);
451 let mut kf = crate::attention::take_buf(dk);
452 let mut qf = crate::attention::take_buf(dk);
453 for h in h0..h1 {
454 let ko = h / rep; let (qs, ks) = (ko * dk, kd + ko * dk);
456 let (mut nq, mut nkn) = (0f64, 0f64);
458 for d in 0..dk {
459 nq += (cq[qs + d] as f64) * (cq[qs + d] as f64);
460 nkn += (cq[ks + d] as f64) * (cq[ks + d] as f64);
461 }
462 let invq = (1.0 / ((nq + 1e-6).sqrt() * (dk as f64).sqrt())) as f32;
463 let invk = (1.0 / (nkn + 1e-6).sqrt()) as f32;
464 for d in 0..dk {
465 qf[d] = cq[qs + d] * invq;
466 kf[d] = cq[ks + d] * invk;
467 }
468
469 let g = (-(w.a_log[h] as f64).exp() * softplus(a[h] as f64 + w.dt_bias[h] as f64)).exp()
470 as f32;
471 let beta = sigmoid(b[h] as f64) as f32;
472
473 let s = unsafe { std::slice::from_raw_parts_mut(s_ptr.0.add(h * dk * dv), dk * dv) };
475 let oh = unsafe { std::slice::from_raw_parts_mut(of_ptr.0.add(h * dv), dv) };
476 let vt = &cq[2 * kd + h * dv..2 * kd + (h + 1) * dv];
477
478 kv[..dv].fill(0.0);
482 for di in 0..dk {
483 let kfd = kf[di];
484 let row = &s[di * dv..(di + 1) * dv];
485 for dj in 0..dv {
486 kv[dj] += row[dj] * kfd; }
488 }
489 for dj in 0..dv {
490 delta[dj] = (vt[dj] - g * kv[dj]) * beta;
491 }
492 o[..dv].fill(0.0);
493 for di in 0..dk {
494 let kfd = kf[di];
495 let qfd = qf[di];
496 let row = &mut s[di * dv..(di + 1) * dv];
497 for dj in 0..dv {
498 let cell = g * row[dj] + kfd * delta[dj];
499 row[dj] = cell;
500 o[dj] += qfd * cell; }
502 }
503 let ss: f64 = o[..dv].iter().map(|&v| (v as f64) * (v as f64)).sum();
505 let inv = 1.0 / (ss / dv as f64 + cfg.rms_eps).sqrt();
506 for dj in 0..dv {
507 let gate = if cfg.output_gate_sigmoid {
508 sigmoid(z[h * dv + dj] as f64)
509 } else {
510 silu(z[h * dv + dj] as f64)
511 };
512 oh[dj] = ((o[dj] as f64 * inv) * w.norm[dj] as f64 * gate) as f32;
513 }
514 }
515 crate::attention::recycle_buf(&mut kv);
516 crate::attention::recycle_buf(&mut delta);
517 crate::attention::recycle_buf(&mut o);
518 crate::attention::recycle_buf(&mut kf);
519 crate::attention::recycle_buf(&mut qf);
520 };
521 match pool {
522 Some(pool) if nv >= 4 => pool.run(&|widx, n| {
523 let chunk = nv.div_ceil(n);
524 let h0 = (widx * chunk).min(nv);
525 let h1 = (h0 + chunk).min(nv);
526 if h0 < h1 {
527 head_range(h0, h1);
528 }
529 }),
530 _ => head_range(0, nv),
531 }
532 if let Some(t0) = perf_t0 {
533 PERF_GDN_STEP_CALLS.fetch_add(1, Ordering::Relaxed);
534 PERF_GDN_STEP_NS.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
535 }
536}
537
538pub fn gdn_forward(
540 x: &[f32],
541 w: &GdnWeights,
542 cfg: &GdnCfg,
543 state: &mut Vec<f32>,
544 pool: Option<&Pool>,
545) -> Vec<f32> {
546 let perf_t0 = perf_enabled().then(std::time::Instant::now);
547 if state.len() != cfg.state_len() {
548 *state = vec![0f32; cfg.state_len()];
549 }
550 let (c_dim, vd) = (cfg.conv_dim(), cfg.num_v_heads * cfg.value_head_dim);
551
552 let mut qkv = vec![0.0f32; c_dim];
553 let mut z = vec![0.0f32; vd];
554 let mut a = vec![0.0f32; cfg.num_v_heads];
555 let mut b = vec![0.0f32; cfg.num_v_heads];
556 let cpu_projs = |qkv: &mut Vec<f32>, z: &mut Vec<f32>, a: &mut Vec<f32>, b: &mut Vec<f32>| {
560 QTensor::matvec_many(
561 [&w.in_proj_qkv, &w.in_proj_z, &w.in_proj_a, &w.in_proj_b],
562 x,
563 [
564 qkv.as_mut_slice(),
565 z.as_mut_slice(),
566 a.as_mut_slice(),
567 b.as_mut_slice(),
568 ],
569 pool,
570 );
571 };
572 let mut done = false;
573 if crate::gpu::enabled_here() && gdn_projs_eligible(w) {
574 match crate::gpu::probe_arm(crate::gpu::OpClass::Batch) {
575 crate::gpu::ProbeArm::Gpu => {
576 let t0 = std::time::Instant::now();
577 if gdn_projs_gpu(w, x, &mut qkv, &mut z) {
578 crate::gpu::probe_record(crate::gpu::OpClass::Batch, true, t0.elapsed());
579 w.in_proj_a.matvec(x, &mut a, pool);
580 w.in_proj_b.matvec(x, &mut b, pool);
581 done = true;
582 } else {
583 crate::gpu::probe_note_decline(crate::gpu::OpClass::Batch);
584 }
585 }
586 crate::gpu::ProbeArm::CpuTimed => {
587 let t0 = std::time::Instant::now();
588 crate::gpu::cpu_scope(|| cpu_projs(&mut qkv, &mut z, &mut a, &mut b));
589 crate::gpu::probe_record(crate::gpu::OpClass::Batch, false, t0.elapsed());
590 done = true;
591 }
592 crate::gpu::ProbeArm::Cpu => {
593 crate::gpu::cpu_scope(|| cpu_projs(&mut qkv, &mut z, &mut a, &mut b));
594 done = true;
595 }
596 }
597 }
598 if !done {
599 cpu_projs(&mut qkv, &mut z, &mut a, &mut b);
600 }
601
602 let mut of = vec![0.0f32; vd];
603 gdn_step(&qkv, &z, &a, &b, w, cfg, state, &mut of, pool);
604
605 let mut out = vec![0.0f32; cfg.hidden_size];
606 w.out_proj.matvec(&of, &mut out, pool);
607 if let Some(t0) = perf_t0 {
608 PERF_GDN_FORWARD_CALLS.fetch_add(1, Ordering::Relaxed);
609 PERF_GDN_FORWARD_NS.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
610 }
611 out
612}
613
614pub fn gdn_forward_batch(
619 xs: &[f32],
620 b: usize,
621 w: &GdnWeights,
622 cfg: &GdnCfg,
623 state: &mut Vec<f32>,
624 pool: Option<&Pool>,
625) -> Vec<f32> {
626 let perf_t0 = perf_enabled().then(std::time::Instant::now);
627 if state.len() != cfg.state_len() {
628 *state = vec![0f32; cfg.state_len()];
629 }
630 let (c_dim, vd) = (cfg.conv_dim(), cfg.num_v_heads * cfg.value_head_dim);
631 let nv = cfg.num_v_heads;
632
633 let mut qkv = vec![0.0f32; b * c_dim];
634 w.in_proj_qkv.matmat(xs, b, &mut qkv, pool);
635 let mut z = vec![0.0f32; b * vd];
636 w.in_proj_z.matmat(xs, b, &mut z, pool);
637 let mut a = vec![0.0f32; b * nv];
638 w.in_proj_a.matmat(xs, b, &mut a, pool);
639 let mut bb = vec![0.0f32; b * nv];
640 w.in_proj_b.matmat(xs, b, &mut bb, pool);
641
642 let mut of = vec![0.0f32; b * vd];
643 for bi in 0..b {
644 gdn_step(
645 &qkv[bi * c_dim..(bi + 1) * c_dim],
646 &z[bi * vd..(bi + 1) * vd],
647 &a[bi * nv..(bi + 1) * nv],
648 &bb[bi * nv..(bi + 1) * nv],
649 w,
650 cfg,
651 state,
652 &mut of[bi * vd..(bi + 1) * vd],
653 pool,
654 );
655 }
656 let mut out = vec![0.0f32; b * cfg.hidden_size];
657 w.out_proj.matmat(&of, b, &mut out, pool);
658 if std::env::var("CMF_GDN_TRACE").is_ok() {
659 let n = |v: &[f32]| v.iter().map(|x| x * x).sum::<f32>().sqrt();
660 eprintln!(
661 "gdn-batch b={b}: |x0|={:.5} |qkv0|={:.5} |z0|={:.5} |a0|={:.5} |b0|={:.5} |of0|={:.5} |out0|={:.5} |state|={:.5}",
662 n(&xs[..cfg.hidden_size]),
663 n(&qkv[..c_dim]),
664 n(&z[..vd]),
665 n(&a[..nv]),
666 n(&bb[..nv]),
667 n(&of[..vd]),
668 n(&out[..cfg.hidden_size]),
669 n(state)
670 );
671 }
672 if let Some(t0) = perf_t0 {
673 PERF_GDN_BATCH_CALLS.fetch_add(1, Ordering::Relaxed);
674 PERF_GDN_BATCH_NS.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
675 }
676 out
677}
678
679pub fn perf_report() {
681 if !perf_enabled() {
682 return;
683 }
684 let fc = PERF_GDN_FORWARD_CALLS.load(Ordering::Relaxed);
685 let bc = PERF_GDN_BATCH_CALLS.load(Ordering::Relaxed);
686 let sc = PERF_GDN_STEP_CALLS.load(Ordering::Relaxed);
687 eprintln!(
688 "[perf-gdn] forward_calls={} forward_ms={:.3} forward_ms_per_call={:.3} batch_calls={} batch_ms={:.3} step_calls={} step_ms={:.3} step_ms_per_call={:.3}",
689 fc,
690 PERF_GDN_FORWARD_NS.load(Ordering::Relaxed) as f64 / 1e6,
691 PERF_GDN_FORWARD_NS.load(Ordering::Relaxed) as f64 / 1e6 / fc.max(1) as f64,
692 bc,
693 PERF_GDN_BATCH_NS.load(Ordering::Relaxed) as f64 / 1e6,
694 sc,
695 PERF_GDN_STEP_NS.load(Ordering::Relaxed) as f64 / 1e6,
696 PERF_GDN_STEP_NS.load(Ordering::Relaxed) as f64 / 1e6 / sc.max(1) as f64,
697 );
698}
699
700fn gdn_projs_eligible(w: &GdnWeights) -> bool {
704 if std::env::var("CMF_GPU_GDN")
710 .map(|v| v == "0")
711 .unwrap_or(false)
712 {
713 return false;
714 }
715 w.in_proj_qkv.is_q1()
716 || w.in_proj_qkv.q4t_parts().is_some()
717 || w.in_proj_qkv.q4tp_parts().is_some()
718 || std::env::var("CMF_GPU_GDN")
719 .map(|v| v == "1")
720 .unwrap_or(false)
721}
722
723fn gdn_projs_gpu(w: &GdnWeights, x: &[f32], qkv: &mut [f32], z: &mut [f32]) -> bool {
725 use crate::gpu::matvec_batch;
726 use crate::qtensor::QTensor;
727 if !crate::gpu::enabled_here() {
728 return false;
729 }
730 fn part<'a>(
731 t: &'a QTensor,
732 x: &[f32],
733 ) -> Option<(
734 std::sync::Arc<cortiq_core::CmfModel>,
735 crate::gpu::BatchJob<'a>,
736 )> {
737 use crate::gpu::BatchJob;
738 use crate::qtensor::prescale;
739 use cortiq_core::TensorDtype;
740 match t {
741 QTensor::Mapped {
742 model,
743 idx,
744 dtype: dt @ (TensorDtype::Q8Row | TensorDtype::Q8_2f),
745 rows,
746 cols,
747 row_scale,
748 col_field,
749 ..
750 } => Some((
751 model.clone(),
752 BatchJob {
753 idx: *idx,
754 rows: *rows,
755 cols: *cols,
756 row_scale,
757 xs: prescale(x, col_field, *dt).into_owned(),
758 layout: crate::gpu::BatchLayout::Q8,
759 },
760 )),
761 QTensor::Mapped {
762 model,
763 idx,
764 dtype: TensorDtype::Q1,
765 rows,
766 cols,
767 ..
768 } => Some((
769 model.clone(),
770 BatchJob {
771 idx: *idx,
772 rows: *rows,
773 cols: *cols,
774 row_scale: &[],
775 xs: x.to_vec(),
776 layout: crate::gpu::BatchLayout::Q1,
777 },
778 )),
779 QTensor::Mapped {
783 model,
784 idx,
785 dtype: dt @ (TensorDtype::Q4Tiled | TensorDtype::Q4TiledP),
786 rows,
787 cols,
788 ..
789 } => Some((
790 model.clone(),
791 BatchJob {
792 idx: *idx,
793 rows: *rows,
794 cols: *cols,
795 row_scale: &[],
796 xs: x.to_vec(),
797 layout: if *dt == TensorDtype::Q4TiledP {
798 crate::gpu::BatchLayout::Q4tp
799 } else {
800 crate::gpu::BatchLayout::Q4t
801 },
802 },
803 )),
804 _ => None,
805 }
806 }
807 let Some((model, jq)) = part(&w.in_proj_qkv, x) else {
808 return false;
809 };
810 let Some((_, jz)) = part(&w.in_proj_z, x) else {
811 return false;
812 };
813 matvec_batch(&model, &[jq, jz], &mut [qkv, z])
814}
815
816#[allow(clippy::too_many_arguments)]
819pub fn gdn_pair(
820 x1: &[f32],
821 x2: &[f32],
822 w: &GdnWeights,
823 cfg: &GdnCfg,
824 state: &mut Vec<f32>,
825 scratch: &mut Vec<f32>,
826 pool: Option<&Pool>,
827) -> (Vec<f32>, Vec<f32>) {
828 if state.len() != cfg.state_len() {
829 *state = vec![0f32; cfg.state_len()];
830 }
831 let (c_dim, vd, nv) = (
832 cfg.conv_dim(),
833 cfg.num_v_heads * cfg.value_head_dim,
834 cfg.num_v_heads,
835 );
836
837 let mut qkv1 = vec![0.0f32; c_dim];
838 let mut qkv2 = vec![0.0f32; c_dim];
839 w.in_proj_qkv.matvec2(x1, x2, &mut qkv1, &mut qkv2, pool);
840 let mut z1 = vec![0.0f32; vd];
841 let mut z2 = vec![0.0f32; vd];
842 w.in_proj_z.matvec2(x1, x2, &mut z1, &mut z2, pool);
843 let mut a1 = vec![0.0f32; nv];
844 let mut a2 = vec![0.0f32; nv];
845 w.in_proj_a.matvec2(x1, x2, &mut a1, &mut a2, pool);
846 let mut b1 = vec![0.0f32; nv];
847 let mut b2 = vec![0.0f32; nv];
848 w.in_proj_b.matvec2(x1, x2, &mut b1, &mut b2, pool);
849
850 let mut of1 = vec![0.0f32; vd];
851 gdn_step(&qkv1, &z1, &a1, &b1, w, cfg, state, &mut of1, pool);
852
853 scratch.clear();
854 scratch.extend_from_slice(state);
855 let mut of2 = vec![0.0f32; vd];
856 gdn_step(&qkv2, &z2, &a2, &b2, w, cfg, scratch, &mut of2, pool);
857
858 let mut out1 = vec![0.0f32; cfg.hidden_size];
859 let mut out2 = vec![0.0f32; cfg.hidden_size];
860 w.out_proj.matvec2(&of1, &of2, &mut out1, &mut out2, pool);
861 (out1, out2)
862}
863
864pub struct ShortConvWeights {
871 pub in_proj: QTensor,
873 pub conv: Vec<f32>,
877 pub out_proj: QTensor,
879}
880
881#[derive(Clone, Copy)]
882pub struct ShortConvCfg {
883 pub hidden_size: usize,
884 pub kernel: usize,
886}
887
888impl ShortConvCfg {
889 pub fn state_len(&self) -> usize {
891 (self.kernel - 1) * self.hidden_size
892 }
893}
894
895fn short_conv_step(
905 bcx: &[f32],
906 conv: &[f32],
907 cfg: &ShortConvCfg,
908 ring_state: &mut [f32],
909 y: &mut [f32],
910) {
911 let (h, k) = (cfg.hidden_size, cfg.kernel);
912 let ring = k - 1;
913 let (bg, cg, xg) = (&bcx[0..h], &bcx[h..2 * h], &bcx[2 * h..3 * h]);
914 for c in 0..h {
915 let bx = bg[c] * xg[c];
916 let wc = &conv[c * k..(c + 1) * k];
917 let mut acc = wc[k - 1] * bx;
919 let rc = &mut ring_state[c * ring..c * ring + ring];
920 for s in 0..ring {
921 acc += wc[k - 2 - s] * rc[s];
922 }
923 y[c] = cg[c] * acc;
924 for s in (1..ring).rev() {
926 rc[s] = rc[s - 1];
927 }
928 if ring > 0 {
929 rc[0] = bx;
930 }
931 }
932}
933
934pub fn short_conv_forward(
936 x: &[f32],
937 w: &ShortConvWeights,
938 cfg: &ShortConvCfg,
939 state: &mut Vec<f32>,
940 pool: Option<&Pool>,
941) -> Vec<f32> {
942 if state.len() != cfg.state_len() {
943 *state = vec![0f32; cfg.state_len()];
944 }
945 let h = cfg.hidden_size;
946 let mut bcx = vec![0.0f32; 3 * h];
947 w.in_proj.matvec(x, &mut bcx, pool);
948 let mut y = vec![0.0f32; h];
949 short_conv_step(&bcx, &w.conv, cfg, state, &mut y);
950 let mut out = vec![0.0f32; h];
951 w.out_proj.matvec(&y, &mut out, pool);
952 out
953}
954
955pub fn short_conv_forward_batch(
960 xs: &[f32],
961 b: usize,
962 w: &ShortConvWeights,
963 cfg: &ShortConvCfg,
964 state: &mut Vec<f32>,
965 pool: Option<&Pool>,
966) -> Vec<f32> {
967 if state.len() != cfg.state_len() {
968 *state = vec![0f32; cfg.state_len()];
969 }
970 let h = cfg.hidden_size;
971 let mut bcx = vec![0.0f32; b * 3 * h];
972 w.in_proj.matmat(xs, b, &mut bcx, pool);
973 let mut y = vec![0.0f32; b * h];
974 for bi in 0..b {
975 short_conv_step(
976 &bcx[bi * 3 * h..(bi + 1) * 3 * h],
977 &w.conv,
978 cfg,
979 state,
980 &mut y[bi * h..(bi + 1) * h],
981 );
982 }
983 let mut out = vec![0.0f32; b * h];
984 w.out_proj.matmat(&y, b, &mut out, pool);
985 out
986}
987
988#[allow(clippy::too_many_arguments)]
993pub fn short_conv_pair(
994 x1: &[f32],
995 x2: &[f32],
996 w: &ShortConvWeights,
997 cfg: &ShortConvCfg,
998 state: &mut Vec<f32>,
999 scratch: &mut Vec<f32>,
1000 pool: Option<&Pool>,
1001) -> (Vec<f32>, Vec<f32>) {
1002 if state.len() != cfg.state_len() {
1003 *state = vec![0f32; cfg.state_len()];
1004 }
1005 let h = cfg.hidden_size;
1006 let mut bcx1 = vec![0.0f32; 3 * h];
1007 let mut bcx2 = vec![0.0f32; 3 * h];
1008 w.in_proj.matvec2(x1, x2, &mut bcx1, &mut bcx2, pool);
1009
1010 let mut y1 = vec![0.0f32; h];
1011 short_conv_step(&bcx1, &w.conv, cfg, state, &mut y1);
1012 scratch.clear();
1013 scratch.extend_from_slice(state);
1014 let mut y2 = vec![0.0f32; h];
1015 short_conv_step(&bcx2, &w.conv, cfg, scratch, &mut y2);
1016
1017 let mut out1 = vec![0.0f32; h];
1018 let mut out2 = vec![0.0f32; h];
1019 w.out_proj.matvec2(&y1, &y2, &mut out1, &mut out2, pool);
1020 (out1, out2)
1021}
1022
1023pub struct KdaWeights {
1034 pub q_proj: QTensor,
1036 pub k_proj: QTensor,
1038 pub v_proj: QTensor,
1040 pub conv_q: Vec<f32>,
1042 pub conv_k: Vec<f32>,
1043 pub conv_v: Vec<f32>,
1045 pub f_a: QTensor,
1047 pub f_b: QTensor,
1049 pub dt_bias: Vec<f32>,
1051 pub a_log: Vec<f32>,
1054 pub b_proj: QTensor,
1056 pub gate: KdaOutGate,
1058 pub o_norm: Vec<f32>,
1060 pub o_proj: QTensor,
1062 pub gate_lower_bound: Option<f32>,
1065}
1066
1067pub enum KdaOutGate {
1068 Full(QTensor),
1070 LowRank(QTensor, QTensor),
1072}
1073
1074#[derive(Clone, Copy)]
1075pub struct KdaCfg {
1076 pub num_heads: usize,
1077 pub head_k_dim: usize,
1078 pub head_v_dim: usize,
1079 pub conv_kernel: usize,
1080 pub hidden_size: usize,
1081 pub rms_eps: f64,
1082}
1083
1084impl KdaCfg {
1085 pub fn state_len(&self) -> usize {
1088 let (nh, dk, dv, kk) = (
1089 self.num_heads,
1090 self.head_k_dim,
1091 self.head_v_dim,
1092 self.conv_kernel,
1093 );
1094 (kk - 1) * (2 * nh * dk + nh * dv) + nh * dk * dv
1095 }
1096}
1097
1098fn kda_conv(raw: &[f32], taps: &[f32], ring: &mut [f32], kk: usize, out: &mut [f32]) {
1101 let c_dim = raw.len();
1102 for c in 0..c_dim {
1103 let t = &taps[c * kk..(c + 1) * kk];
1104 let mut acc = raw[c] as f64 * t[kk - 1] as f64;
1105 for j in 0..kk - 1 {
1106 acc += ring[j * c_dim + c] as f64 * t[j] as f64;
1107 }
1108 out[c] = silu(acc) as f32;
1109 }
1110 if kk > 1 {
1111 ring.copy_within(c_dim.., 0);
1112 let tail = (kk - 2) * c_dim;
1113 ring[tail..tail + c_dim].copy_from_slice(raw);
1114 }
1115}
1116
1117#[inline]
1120fn kda_log_decay(w: &KdaWeights, cfg: &KdaCfg, h: usize, d: usize, f: f32) -> f64 {
1121 let (nh, dk) = (cfg.num_heads, cfg.head_k_dim);
1122 let a = if w.a_log.len() == nh {
1123 w.a_log[h] as f64
1124 } else if w.a_log.len() == dk {
1125 w.a_log[d] as f64
1126 } else {
1127 w.a_log[h * dk + d] as f64
1128 };
1129 let raw = f as f64 + w.dt_bias[h * dk + d] as f64;
1130 match w.gate_lower_bound {
1131 Some(lb) => lb as f64 * sigmoid(a.exp() * raw),
1132 None => -a.exp() * softplus(raw),
1133 }
1134}
1135
1136#[allow(clippy::too_many_arguments)]
1144fn kda_step(
1145 xq: &[f32],
1146 xk: &[f32],
1147 xv: &[f32],
1148 f: &[f32],
1149 b: &[f32],
1150 gate_out: &[f32],
1151 w: &KdaWeights,
1152 cfg: &KdaCfg,
1153 state: &mut [f32],
1154 of: &mut [f32],
1155 pool: Option<&Pool>,
1156) {
1157 let (nh, dk, dv, kk) = (
1158 cfg.num_heads,
1159 cfg.head_k_dim,
1160 cfg.head_v_dim,
1161 cfg.conv_kernel,
1162 );
1163 let (kd, vd) = (nh * dk, nh * dv);
1164 let ring_q_len = (kk - 1) * kd;
1165 let ring_v_len = (kk - 1) * vd;
1166 let (ring_q, rest) = state.split_at_mut(ring_q_len);
1167 let (ring_k, rest) = rest.split_at_mut(ring_q_len);
1168 let (ring_v, s_all) = rest.split_at_mut(ring_v_len);
1169
1170 let mut cq = vec![0f32; kd];
1171 let mut ck = vec![0f32; kd];
1172 let mut cv = vec![0f32; vd];
1173 kda_conv(xq, &w.conv_q, ring_q, kk, &mut cq);
1174 kda_conv(xk, &w.conv_k, ring_k, kk, &mut ck);
1175 kda_conv(xv, &w.conv_v, ring_v, kk, &mut cv);
1176
1177 let (cq, ck, cv) = (&cq, &ck, &cv);
1178 let s_ptr = SendMutF32(s_all.as_mut_ptr());
1179 let of_ptr = SendMutF32(of.as_mut_ptr());
1180 let head_range = |h0: usize, h1: usize| {
1181 let (s_ptr, of_ptr) = (s_ptr, of_ptr);
1182 let mut kv = crate::attention::take_buf(dv);
1183 let mut delta = crate::attention::take_buf(dv);
1184 let mut o = crate::attention::take_buf(dv);
1185 let mut kf = crate::attention::take_buf(dk);
1186 let mut qf = crate::attention::take_buf(dk);
1187 let mut gd = crate::attention::take_buf(dk);
1188 for h in h0..h1 {
1189 let qs = h * dk;
1190 let (mut nq, mut nkn) = (0f64, 0f64);
1192 for d in 0..dk {
1193 nq += (cq[qs + d] as f64) * (cq[qs + d] as f64);
1194 nkn += (ck[qs + d] as f64) * (ck[qs + d] as f64);
1195 }
1196 let invq = (1.0 / ((nq + 1e-6).sqrt() * (dk as f64).sqrt())) as f32;
1197 let invk = (1.0 / (nkn + 1e-6).sqrt()) as f32;
1198 for d in 0..dk {
1199 qf[d] = cq[qs + d] * invq;
1200 kf[d] = ck[qs + d] * invk;
1201 gd[d] = kda_log_decay(w, cfg, h, d, f[qs + d]).exp() as f32;
1202 }
1203 let beta = sigmoid(b[h] as f64) as f32;
1204
1205 let s = unsafe { std::slice::from_raw_parts_mut(s_ptr.0.add(h * dk * dv), dk * dv) };
1207 let oh = unsafe { std::slice::from_raw_parts_mut(of_ptr.0.add(h * dv), dv) };
1208 let vt = &cv[h * dv..(h + 1) * dv];
1209
1210 kv[..dv].fill(0.0);
1212 for di in 0..dk {
1213 let kg = kf[di] * gd[di];
1214 let row = &s[di * dv..(di + 1) * dv];
1215 for dj in 0..dv {
1216 kv[dj] += row[dj] * kg;
1217 }
1218 }
1219 for dj in 0..dv {
1220 delta[dj] = (vt[dj] - kv[dj]) * beta;
1221 }
1222 o[..dv].fill(0.0);
1224 for di in 0..dk {
1225 let (kfd, qfd, gdd) = (kf[di], qf[di], gd[di]);
1226 let row = &mut s[di * dv..(di + 1) * dv];
1227 for dj in 0..dv {
1228 let cell = gdd * row[dj] + kfd * delta[dj];
1229 row[dj] = cell;
1230 o[dj] += qfd * cell;
1231 }
1232 }
1233 let ss: f64 = o[..dv].iter().map(|&v| (v as f64) * (v as f64)).sum();
1235 let inv = 1.0 / (ss / dv as f64 + cfg.rms_eps).sqrt();
1236 for dj in 0..dv {
1237 oh[dj] = ((o[dj] as f64 * inv)
1238 * w.o_norm[dj] as f64
1239 * sigmoid(gate_out[h * dv + dj] as f64)) as f32;
1240 }
1241 }
1242 crate::attention::recycle_buf(&mut kv);
1243 crate::attention::recycle_buf(&mut delta);
1244 crate::attention::recycle_buf(&mut o);
1245 crate::attention::recycle_buf(&mut kf);
1246 crate::attention::recycle_buf(&mut qf);
1247 crate::attention::recycle_buf(&mut gd);
1248 };
1249 match pool {
1250 Some(pool) if nh >= 4 => pool.run(&|widx, n| {
1251 let chunk = nh.div_ceil(n);
1252 let h0 = (widx * chunk).min(nh);
1253 let h1 = (h0 + chunk).min(nh);
1254 if h0 < h1 {
1255 head_range(h0, h1);
1256 }
1257 }),
1258 _ => head_range(0, nh),
1259 }
1260}
1261
1262fn kda_gate_out(w: &KdaWeights, x: &[f32], vd: usize, pool: Option<&Pool>) -> Vec<f32> {
1265 let mut g = vec![0.0f32; vd];
1266 match &w.gate {
1267 KdaOutGate::Full(gp) => gp.matvec(x, &mut g, pool),
1268 KdaOutGate::LowRank(ga, gb) => {
1269 let mut low = vec![0.0f32; ga.rows()];
1270 ga.matvec(x, &mut low, pool);
1271 gb.matvec(&low, &mut g, pool);
1272 }
1273 }
1274 g
1275}
1276
1277pub fn kda_forward(
1279 x: &[f32],
1280 w: &KdaWeights,
1281 cfg: &KdaCfg,
1282 state: &mut Vec<f32>,
1283 pool: Option<&Pool>,
1284) -> Vec<f32> {
1285 if state.len() != cfg.state_len() {
1286 *state = vec![0f32; cfg.state_len()];
1287 }
1288 let (nh, dk, dv) = (cfg.num_heads, cfg.head_k_dim, cfg.head_v_dim);
1289 let (kd, vd) = (nh * dk, nh * dv);
1290
1291 let mut xq = vec![0.0f32; kd];
1292 let mut xk = vec![0.0f32; kd];
1293 let mut xv = vec![0.0f32; vd];
1294 let mut fl = vec![0.0f32; w.f_a.rows()];
1295 let mut b = vec![0.0f32; nh];
1296 QTensor::matvec_many(
1297 [&w.q_proj, &w.k_proj, &w.v_proj, &w.f_a],
1298 x,
1299 [
1300 xq.as_mut_slice(),
1301 xk.as_mut_slice(),
1302 xv.as_mut_slice(),
1303 fl.as_mut_slice(),
1304 ],
1305 pool,
1306 );
1307 w.b_proj.matvec(x, &mut b, pool);
1308 let mut f = vec![0.0f32; kd];
1309 w.f_b.matvec(&fl, &mut f, pool);
1310 let gate_out = kda_gate_out(w, x, vd, pool);
1311
1312 let mut of = vec![0.0f32; vd];
1313 kda_step(
1314 &xq, &xk, &xv, &f, &b, &gate_out, w, cfg, state, &mut of, pool,
1315 );
1316
1317 let mut out = vec![0.0f32; cfg.hidden_size];
1318 w.o_proj.matvec(&of, &mut out, pool);
1319 out
1320}
1321
1322pub fn kda_forward_batch(
1326 xs: &[f32],
1327 bsz: usize,
1328 w: &KdaWeights,
1329 cfg: &KdaCfg,
1330 state: &mut Vec<f32>,
1331 pool: Option<&Pool>,
1332) -> Vec<f32> {
1333 if state.len() != cfg.state_len() {
1334 *state = vec![0f32; cfg.state_len()];
1335 }
1336 let (nh, dk, dv, hs) = (
1337 cfg.num_heads,
1338 cfg.head_k_dim,
1339 cfg.head_v_dim,
1340 cfg.hidden_size,
1341 );
1342 let (kd, vd) = (nh * dk, nh * dv);
1343
1344 let mut xq = vec![0.0f32; bsz * kd];
1345 w.q_proj.matmat(xs, bsz, &mut xq, pool);
1346 let mut xk = vec![0.0f32; bsz * kd];
1347 w.k_proj.matmat(xs, bsz, &mut xk, pool);
1348 let mut xv = vec![0.0f32; bsz * vd];
1349 w.v_proj.matmat(xs, bsz, &mut xv, pool);
1350 let rank = w.f_a.rows();
1351 let mut fl = vec![0.0f32; bsz * rank];
1352 w.f_a.matmat(xs, bsz, &mut fl, pool);
1353 let mut f = vec![0.0f32; bsz * kd];
1354 w.f_b.matmat(&fl, bsz, &mut f, pool);
1355 let mut b = vec![0.0f32; bsz * nh];
1356 w.b_proj.matmat(xs, bsz, &mut b, pool);
1357 let mut gate_out = vec![0.0f32; bsz * vd];
1358 match &w.gate {
1359 KdaOutGate::Full(gp) => gp.matmat(xs, bsz, &mut gate_out, pool),
1360 KdaOutGate::LowRank(ga, gb) => {
1361 let mut low = vec![0.0f32; bsz * ga.rows()];
1362 ga.matmat(xs, bsz, &mut low, pool);
1363 gb.matmat(&low, bsz, &mut gate_out, pool);
1364 }
1365 }
1366
1367 let mut of = vec![0.0f32; bsz * vd];
1368 for bi in 0..bsz {
1369 let mut oh = vec![0.0f32; vd];
1370 kda_step(
1371 &xq[bi * kd..(bi + 1) * kd],
1372 &xk[bi * kd..(bi + 1) * kd],
1373 &xv[bi * vd..(bi + 1) * vd],
1374 &f[bi * kd..(bi + 1) * kd],
1375 &b[bi * nh..(bi + 1) * nh],
1376 &gate_out[bi * vd..(bi + 1) * vd],
1377 w,
1378 cfg,
1379 state,
1380 &mut oh,
1381 pool,
1382 );
1383 of[bi * vd..(bi + 1) * vd].copy_from_slice(&oh);
1384 }
1385
1386 let mut out = vec![0.0f32; bsz * hs];
1387 w.o_proj.matmat(&of, bsz, &mut out, pool);
1388 out
1389}
1390
1391#[cfg(test)]
1392mod tests {
1393 #[test]
1394 fn kda_forward_matches_naive_reference() {
1395 let (nh, dk, dv, kk, hs, rank) = (2usize, 4usize, 4usize, 3usize, 6usize, 3usize);
1401 let synth = |rows: usize, cols: usize, salt: usize| -> QTensor {
1402 QTensor::from_f32(
1403 (0..rows * cols)
1404 .map(|i| (((i * 31 + salt * 17) % 101) as f32 / 101.0 - 0.5) * 0.6)
1405 .collect(),
1406 rows,
1407 cols,
1408 )
1409 };
1410 let vecf = |n: usize, salt: usize| -> Vec<f32> {
1411 (0..n)
1412 .map(|i| (((i * 13 + salt * 7) % 89) as f32 / 89.0 - 0.5) * 0.8)
1413 .collect()
1414 };
1415 for (label, a_log, lb) in [
1416 ("per-head standard", vecf(nh, 40), None),
1417 ("per-dim lower-bound", vecf(dk, 41), Some(-5.0f32)),
1418 ] {
1419 let w = KdaWeights {
1420 q_proj: synth(nh * dk, hs, 1),
1421 k_proj: synth(nh * dk, hs, 2),
1422 v_proj: synth(nh * dv, hs, 3),
1423 conv_q: vecf(nh * dk * kk, 4),
1424 conv_k: vecf(nh * dk * kk, 5),
1425 conv_v: vecf(nh * dv * kk, 6),
1426 f_a: synth(rank, hs, 7),
1427 f_b: synth(nh * dk, rank, 8),
1428 dt_bias: vecf(nh * dk, 9),
1429 a_log: a_log.clone(),
1430 b_proj: synth(nh, hs, 10),
1431 gate: KdaOutGate::LowRank(synth(rank, hs, 11), synth(nh * dv, rank, 12)),
1432 o_norm: (0..dv).map(|i| 1.0 + 0.1 * i as f32).collect(),
1433 o_proj: synth(hs, nh * dv, 13),
1434 gate_lower_bound: lb,
1435 };
1436 let cfg = KdaCfg {
1437 num_heads: nh,
1438 head_k_dim: dk,
1439 head_v_dim: dv,
1440 conv_kernel: kk,
1441 hidden_size: hs,
1442 rms_eps: 1e-6,
1443 };
1444 let xs: Vec<Vec<f32>> = (0..6)
1445 .map(|t| {
1446 (0..hs)
1447 .map(|i| ((t * hs + i) as f32 * 0.37).sin() * 0.5)
1448 .collect()
1449 })
1450 .collect();
1451
1452 let mut state = Vec::new();
1454 let got: Vec<Vec<f32>> = xs
1455 .iter()
1456 .map(|x| kda_forward(x, &w, &cfg, &mut state, None))
1457 .collect();
1458
1459 let mv = |t: &QTensor, x: &[f32]| -> Vec<f32> {
1461 let mut o = vec![0.0f32; t.rows()];
1462 t.matvec(x, &mut o, None);
1463 o
1464 };
1465 let mut hist: Vec<(Vec<f32>, Vec<f32>, Vec<f32>)> = Vec::new(); let mut s_state = vec![0f64; nh * dk * dv];
1467 let mut want: Vec<Vec<f32>> = Vec::new();
1468 for x in &xs {
1469 let (xq, xk, xv) = (mv(&w.q_proj, x), mv(&w.k_proj, x), mv(&w.v_proj, x));
1470 hist.push((xq, xk, xv));
1471 let conv = |sel: fn(&(Vec<f32>, Vec<f32>, Vec<f32>)) -> &Vec<f32>,
1473 taps: &[f32],
1474 n: usize|
1475 -> Vec<f32> {
1476 (0..n)
1477 .map(|c| {
1478 let t = &taps[c * kk..(c + 1) * kk];
1479 let mut acc = 0f64;
1480 for j in 0..kk {
1481 let idx = hist.len() as i64 - (kk as i64 - j as i64);
1482 if idx >= 0 {
1483 acc += sel(&hist[idx as usize])[c] as f64 * t[j] as f64;
1484 }
1485 }
1486 silu(acc)
1487 })
1488 .map(|v| v as f32)
1489 .collect()
1490 };
1491 let cq = conv(|h| &h.0, &w.conv_q, nh * dk);
1492 let ck = conv(|h| &h.1, &w.conv_k, nh * dk);
1493 let cv = conv(|h| &h.2, &w.conv_v, nh * dv);
1494 let f = mv(&w.f_b, &mv(&w.f_a, x));
1495 let bb = mv(&w.b_proj, x);
1496 let gate_out = match &w.gate {
1497 KdaOutGate::LowRank(ga, gb) => mv(gb, &mv(ga, x)),
1498 KdaOutGate::Full(g) => mv(g, x),
1499 };
1500 let mut of = vec![0f32; nh * dv];
1501 for h in 0..nh {
1502 let q: Vec<f64> = {
1504 let sl = &cq[h * dk..(h + 1) * dk];
1505 let n: f64 = sl.iter().map(|&v| (v as f64) * (v as f64)).sum();
1506 let inv = 1.0 / ((n + 1e-6).sqrt() * (dk as f64).sqrt());
1507 sl.iter().map(|&v| v as f64 * inv).collect()
1508 };
1509 let k: Vec<f64> = {
1510 let sl = &ck[h * dk..(h + 1) * dk];
1511 let n: f64 = sl.iter().map(|&v| (v as f64) * (v as f64)).sum();
1512 let inv = 1.0 / (n + 1e-6).sqrt();
1513 sl.iter().map(|&v| v as f64 * inv).collect()
1514 };
1515 let v: Vec<f64> = cv[h * dv..(h + 1) * dv].iter().map(|&v| v as f64).collect();
1516 let g: Vec<f64> = (0..dk)
1518 .map(|d| {
1519 let a = if w.a_log.len() == nh {
1520 w.a_log[h] as f64
1521 } else {
1522 w.a_log[d] as f64
1523 };
1524 let raw = f[h * dk + d] as f64 + w.dt_bias[h * dk + d] as f64;
1525 match w.gate_lower_bound {
1526 Some(lb) => lb as f64 * sigmoid(a.exp() * raw),
1527 None => -a.exp() * softplus(raw),
1528 }
1529 })
1530 .collect();
1531 let beta = sigmoid(bb[h] as f64);
1532 let s = &mut s_state[h * dk * dv..(h + 1) * dk * dv];
1533 for di in 0..dk {
1535 for dj in 0..dv {
1536 s[di * dv + dj] *= g[di].exp();
1537 }
1538 }
1539 let mut kv = vec![0f64; dv];
1541 for di in 0..dk {
1542 for dj in 0..dv {
1543 kv[dj] += k[di] * s[di * dv + dj];
1544 }
1545 }
1546 for di in 0..dk {
1547 for dj in 0..dv {
1548 s[di * dv + dj] += beta * k[di] * (v[dj] - kv[dj]);
1549 }
1550 }
1551 let mut o = vec![0f64; dv];
1552 for di in 0..dk {
1553 for dj in 0..dv {
1554 o[dj] += q[di] * s[di * dv + dj];
1555 }
1556 }
1557 let ss: f64 = o.iter().map(|&v| v * v).sum();
1559 let inv = 1.0 / (ss / dv as f64 + cfg.rms_eps).sqrt();
1560 for dj in 0..dv {
1561 of[h * dv + dj] = (o[dj]
1562 * inv
1563 * w.o_norm[dj] as f64
1564 * sigmoid(gate_out[h * dv + dj] as f64))
1565 as f32;
1566 }
1567 }
1568 want.push(mv(&w.o_proj, &of));
1569 }
1570
1571 for (t, (g, e)) in got.iter().zip(&want).enumerate() {
1572 for (i, (a, b)) in g.iter().zip(e.iter()).enumerate() {
1573 assert!((a - b).abs() < 2e-4, "{label}: t={t} i={i}: {a} vs {b}");
1574 }
1575 }
1576 }
1577
1578 let w = KdaWeights {
1580 q_proj: synth(nh * dk, hs, 1),
1581 k_proj: synth(nh * dk, hs, 2),
1582 v_proj: synth(nh * dv, hs, 3),
1583 conv_q: vecf(nh * dk * kk, 4),
1584 conv_k: vecf(nh * dk * kk, 5),
1585 conv_v: vecf(nh * dv * kk, 6),
1586 f_a: synth(rank, hs, 7),
1587 f_b: synth(nh * dk, rank, 8),
1588 dt_bias: vecf(nh * dk, 9),
1589 a_log: vecf(nh, 40),
1590 b_proj: synth(nh, hs, 10),
1591 gate: KdaOutGate::LowRank(synth(rank, hs, 11), synth(nh * dv, rank, 12)),
1592 o_norm: (0..dv).map(|i| 1.0 + 0.1 * i as f32).collect(),
1593 o_proj: synth(hs, nh * dv, 13),
1594 gate_lower_bound: None,
1595 };
1596 let cfg = KdaCfg {
1597 num_heads: nh,
1598 head_k_dim: dk,
1599 head_v_dim: dv,
1600 conv_kernel: kk,
1601 hidden_size: hs,
1602 rms_eps: 1e-6,
1603 };
1604 let xs: Vec<f32> = (0..5 * hs).map(|i| (i as f32 * 0.29).cos() * 0.4).collect();
1605 let mut st1 = Vec::new();
1606 let seq: Vec<f32> = (0..5)
1607 .flat_map(|t| kda_forward(&xs[t * hs..(t + 1) * hs], &w, &cfg, &mut st1, None))
1608 .collect();
1609 let mut st2 = Vec::new();
1610 let bat = kda_forward_batch(&xs, 5, &w, &cfg, &mut st2, None);
1611 for (i, (a, b)) in seq.iter().zip(&bat).enumerate() {
1612 assert!((a - b).abs() < 1e-5, "batch i={i}: {a} vs {b}");
1613 }
1614 assert_eq!(st1, st2, "state must match after the chunk");
1615 }
1616
1617 use super::*;
1618
1619 fn tiny() -> (VmfPhaseWeights, VmfPhaseCfg) {
1620 let cfg = VmfPhaseCfg {
1621 num_heads: 2,
1622 nphase: 3,
1623 value_head_dim: 4,
1624 hidden_size: 8,
1625 phase_mass: 0.0,
1626 };
1627 let synth = |rows: usize, cols: usize, salt: usize| {
1628 QTensor::from_f32(
1629 (0..rows * cols)
1630 .map(|i| (((i * 13 + salt * 7) % 97) as f32 / 97.0 - 0.5) * 0.4)
1631 .collect(),
1632 rows,
1633 cols,
1634 )
1635 };
1636 let w = VmfPhaseWeights {
1637 thq: synth(cfg.num_heads * cfg.nphase, cfg.hidden_size, 1),
1638 thk: synth(cfg.num_heads * cfg.nphase, cfg.hidden_size, 2),
1639 v_proj: synth(cfg.num_heads * cfg.value_head_dim, cfg.hidden_size, 3),
1640 out_proj: synth(cfg.hidden_size, cfg.num_heads * cfg.value_head_dim, 4),
1641 decay: (0..cfg.num_heads * 2 * cfg.nphase)
1642 .map(|i| 0.9 + 0.005 * (i % 10) as f64)
1643 .collect(),
1644 conv: None,
1645 k_gate: None,
1646 };
1647 (w, cfg)
1648 }
1649
1650 #[test]
1651 fn state_persists_and_changes_output() {
1652 let (w, cfg) = tiny();
1653 let x: Vec<f32> = (0..8).map(|i| (i as f32 * 0.3).sin()).collect();
1654 let mut state = Vec::new();
1655 let o1 = vmf_phase_forward(&x, &w, &cfg, &mut state, None);
1656 let o2 = vmf_phase_forward(&x, &w, &cfg, &mut state, None);
1657 assert!(o1.iter().zip(&o2).any(|(a, b)| (a - b).abs() > 1e-6));
1659 assert_eq!(state.len(), cfg.state_len());
1660 }
1661
1662 #[test]
1666 fn phase_mass_zero_is_noop_and_positive_shifts() {
1667 let (w, cfg0) = tiny();
1668 let mut cfg_m = cfg0;
1669 cfg_m.phase_mass = 1.0;
1670 let x: Vec<f32> = (0..8).map(|i| (i as f32 * 0.4).sin()).collect();
1671
1672 let mut s0 = Vec::new();
1673 let base = vmf_phase_forward(&x, &w, &cfg0, &mut s0, None);
1674 let mut s0b = Vec::new();
1676 let base2 = vmf_phase_forward(&x, &w, &cfg0, &mut s0b, None);
1677 assert_eq!(base, base2, "mass=0 must be deterministic/no-op");
1678 let mut sm = Vec::new();
1680 let massed = vmf_phase_forward(&x, &w, &cfg_m, &mut sm, None);
1681 assert!(
1682 base.iter().zip(&massed).any(|(a, b)| (a - b).abs() > 1e-5),
1683 "mass>0 must change the output"
1684 );
1685 assert!(massed.iter().all(|v| v.is_finite()));
1686 }
1687
1688 #[test]
1693 fn kappa_gate_open_matches_none_and_closed_writes_nothing() {
1694 let (mut w, cfg) = tiny();
1695 let x: Vec<f32> = (0..8).map(|i| (i as f32 * 0.3).sin()).collect();
1696
1697 let mut s_none = Vec::new();
1698 let base1 = vmf_phase_forward(&x, &w, &cfg, &mut s_none, None);
1699 let base2 = vmf_phase_forward(&x, &w, &cfg, &mut s_none, None);
1700
1701 w.k_gate = Some((
1703 QTensor::from_f32(
1704 vec![0.0; cfg.num_heads * cfg.hidden_size],
1705 cfg.num_heads,
1706 cfg.hidden_size,
1707 ),
1708 vec![20.0; cfg.num_heads],
1709 ));
1710 let mut s_open = Vec::new();
1711 let o1 = vmf_phase_forward(&x, &w, &cfg, &mut s_open, None);
1712 let o2 = vmf_phase_forward(&x, &w, &cfg, &mut s_open, None);
1713 for (a, b) in base1.iter().zip(&o1).chain(base2.iter().zip(&o2)) {
1714 assert!(
1715 (a - b).abs() < 1e-5,
1716 "open κ must match gateless: {a} vs {b}"
1717 );
1718 }
1719
1720 w.k_gate = Some((
1722 QTensor::from_f32(
1723 vec![0.0; cfg.num_heads * cfg.hidden_size],
1724 cfg.num_heads,
1725 cfg.hidden_size,
1726 ),
1727 vec![-20.0; cfg.num_heads],
1728 ));
1729 let mut s_closed = Vec::new();
1730 let oc = vmf_phase_forward(&x, &w, &cfg, &mut s_closed, None);
1731 assert!(
1732 s_closed.iter().all(|&v| v.abs() < 1e-7),
1733 "closed κ: state must stay empty"
1734 );
1735 assert!(
1736 oc.iter().all(|&v| v.abs() < 1e-6),
1737 "closed κ: empty-state readout"
1738 );
1739 }
1740
1741 #[test]
1742 fn pair_matches_two_singles_bitexact() {
1743 let (w, cfg) = tiny();
1744 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.2).cos()).collect();
1745 let x2: Vec<f32> = (0..8).map(|i| (i as f32 * 0.5).sin()).collect();
1746
1747 let mut s_ref = Vec::new();
1749 let r1 = vmf_phase_forward(&x1, &w, &cfg, &mut s_ref, None);
1750 let r2 = vmf_phase_forward(&x2, &w, &cfg, &mut s_ref, None);
1751
1752 let mut s = Vec::new();
1754 let mut scratch = Vec::new();
1755 let (p1, p2) = vmf_phase_pair(&x1, &x2, &w, &cfg, &mut s, &mut scratch, None);
1756 assert_eq!(r1, p1, "lane 1 must be bit-identical");
1757 assert_eq!(r2, p2, "lane 2 must be bit-identical");
1758 std::mem::swap(&mut s, &mut scratch);
1760 assert_eq!(s, s_ref, "accepted state must equal sequential state");
1761 }
1762
1763 #[test]
1764 fn rejected_draft_leaves_state_at_lane1() {
1765 let (w, cfg) = tiny();
1766 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.7).sin()).collect();
1767 let x2 = vec![0.5f32; 8];
1768
1769 let mut s_ref = Vec::new();
1770 let _ = vmf_phase_forward(&x1, &w, &cfg, &mut s_ref, None);
1771
1772 let mut s = Vec::new();
1773 let mut scratch = Vec::new();
1774 let _ = vmf_phase_pair(&x1, &x2, &w, &cfg, &mut s, &mut scratch, None);
1775 assert_eq!(s, s_ref);
1777 }
1778
1779 fn tiny_gdn() -> (GdnWeights, GdnCfg) {
1782 let cfg = GdnCfg {
1783 num_v_heads: 4,
1784 num_k_heads: 2,
1785 key_head_dim: 3,
1786 value_head_dim: 5,
1787 conv_kernel: 4,
1788 hidden_size: 8,
1789 rms_eps: 1e-6,
1790 output_gate_sigmoid: false,
1791 };
1792 let c_dim = cfg.conv_dim();
1793 let vd = cfg.num_v_heads * cfg.value_head_dim;
1794 let synth = |rows: usize, cols: usize, salt: usize| {
1795 QTensor::from_f32(
1796 (0..rows * cols)
1797 .map(|i| (((i * 13 + salt * 7) % 97) as f32 / 97.0 - 0.5) * 0.4)
1798 .collect(),
1799 rows,
1800 cols,
1801 )
1802 };
1803 let vecf = |n: usize, salt: usize| -> Vec<f32> {
1804 (0..n)
1805 .map(|i| (((i * 11 + salt * 5) % 89) as f32 / 89.0 - 0.5) * 0.6)
1806 .collect()
1807 };
1808 let w = GdnWeights {
1809 in_proj_qkv: synth(c_dim, cfg.hidden_size, 1),
1810 in_proj_z: synth(vd, cfg.hidden_size, 2),
1811 in_proj_a: synth(cfg.num_v_heads, cfg.hidden_size, 3),
1812 in_proj_b: synth(cfg.num_v_heads, cfg.hidden_size, 4),
1813 conv1d: vecf(c_dim * cfg.conv_kernel, 5),
1814 a_log: (0..cfg.num_v_heads).map(|i| 0.2 + 0.3 * i as f32).collect(),
1815 dt_bias: vecf(cfg.num_v_heads, 6),
1816 norm: vec![1.0; cfg.value_head_dim],
1817 out_proj: synth(cfg.hidden_size, vd, 7),
1818 };
1819 (w, cfg)
1820 }
1821
1822 #[test]
1823 fn gdn_state_persists_and_changes_output() {
1824 let (w, cfg) = tiny_gdn();
1825 let x: Vec<f32> = (0..8).map(|i| (i as f32 * 0.3).sin()).collect();
1826 let mut state = Vec::new();
1827 let o1 = gdn_forward(&x, &w, &cfg, &mut state, None);
1828 let o2 = gdn_forward(&x, &w, &cfg, &mut state, None);
1829 assert!(o1.iter().zip(&o2).any(|(a, b)| (a - b).abs() > 1e-6));
1830 assert_eq!(state.len(), cfg.state_len());
1831 }
1832
1833 #[test]
1834 fn gdn_pair_matches_two_singles_bitexact() {
1835 let (w, cfg) = tiny_gdn();
1836 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.2).cos()).collect();
1837 let x2: Vec<f32> = (0..8).map(|i| (i as f32 * 0.5).sin()).collect();
1838
1839 let mut s_ref = Vec::new();
1840 let r1 = gdn_forward(&x1, &w, &cfg, &mut s_ref, None);
1841 let r2 = gdn_forward(&x2, &w, &cfg, &mut s_ref, None);
1842
1843 let mut s = Vec::new();
1844 let mut scratch = Vec::new();
1845 let (p1, p2) = gdn_pair(&x1, &x2, &w, &cfg, &mut s, &mut scratch, None);
1846 assert_eq!(r1, p1, "lane 1 must be bit-identical");
1847 assert_eq!(r2, p2, "lane 2 must be bit-identical");
1848 std::mem::swap(&mut s, &mut scratch);
1849 assert_eq!(s, s_ref, "accepted state must equal sequential state");
1850 }
1851
1852 #[test]
1853 fn gdn_rejected_draft_leaves_state_at_lane1() {
1854 let (w, cfg) = tiny_gdn();
1855 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.7).sin()).collect();
1856 let x2 = vec![0.5f32; 8];
1857
1858 let mut s_ref = Vec::new();
1859 let _ = gdn_forward(&x1, &w, &cfg, &mut s_ref, None);
1860
1861 let mut s = Vec::new();
1862 let mut scratch = Vec::new();
1863 let _ = gdn_pair(&x1, &x2, &w, &cfg, &mut s, &mut scratch, None);
1864 assert_eq!(s, s_ref);
1865 }
1866
1867 #[test]
1871 fn gdn_conv_ring_matches_explicit_causal_conv() {
1872 let (w, cfg) = tiny_gdn();
1873 let seq: Vec<Vec<f32>> = (0..6)
1874 .map(|t| (0..8).map(|i| ((t * 8 + i) as f32 * 0.17).sin()).collect())
1875 .collect();
1876
1877 let mut s_inc = Vec::new();
1880 for (t, x) in seq.iter().enumerate() {
1881 let inc = gdn_forward(x, &w, &cfg, &mut s_inc, None);
1882 let mut s_replay = Vec::new();
1883 let mut replay = Vec::new();
1884 for xr in &seq[..=t] {
1885 replay = gdn_forward(xr, &w, &cfg, &mut s_replay, None);
1886 }
1887 assert_eq!(inc, replay, "position {t}: ring must equal replay");
1888 }
1889 }
1890
1891 fn tiny_short_conv() -> (ShortConvWeights, ShortConvCfg) {
1892 let cfg = ShortConvCfg {
1893 hidden_size: 8,
1894 kernel: 3,
1895 };
1896 let synth = |rows: usize, cols: usize, salt: usize| {
1897 QTensor::from_f32(
1898 (0..rows * cols)
1899 .map(|i| (((i * 11 + salt * 5) % 89) as f32 / 89.0 - 0.5) * 0.5)
1900 .collect(),
1901 rows,
1902 cols,
1903 )
1904 };
1905 let w = ShortConvWeights {
1906 in_proj: synth(3 * cfg.hidden_size, cfg.hidden_size, 1),
1907 conv: (0..cfg.hidden_size * cfg.kernel)
1908 .map(|i| ((i * 7 % 13) as f32 / 13.0 - 0.5) * 0.8)
1909 .collect(),
1910 out_proj: synth(cfg.hidden_size, cfg.hidden_size, 2),
1911 };
1912 (w, cfg)
1913 }
1914
1915 #[test]
1918 fn short_conv_ring_matches_explicit_causal_conv() {
1919 let (w, cfg) = tiny_short_conv();
1920 let seq: Vec<Vec<f32>> = (0..6)
1921 .map(|t| (0..8).map(|i| ((t * 8 + i) as f32 * 0.19).cos()).collect())
1922 .collect();
1923 let mut s_inc = Vec::new();
1924 for (t, x) in seq.iter().enumerate() {
1925 let inc = short_conv_forward(x, &w, &cfg, &mut s_inc, None);
1926 let mut s_replay = Vec::new();
1927 let mut replay = Vec::new();
1928 for xr in &seq[..=t] {
1929 replay = short_conv_forward(xr, &w, &cfg, &mut s_replay, None);
1930 }
1931 assert_eq!(inc, replay, "position {t}: ring must equal replay");
1932 assert_eq!(s_inc.len(), cfg.state_len());
1933 }
1934 }
1935
1936 #[test]
1939 fn short_conv_batch_matches_sequential() {
1940 let (w, cfg) = tiny_short_conv();
1941 let b = 5;
1942 let xs: Vec<f32> = (0..b * cfg.hidden_size)
1943 .map(|i| (i as f32 * 0.13).sin() * 0.6)
1944 .collect();
1945
1946 let mut s_seq = Vec::new();
1947 let mut seq_out = vec![0.0f32; b * cfg.hidden_size];
1948 for bi in 0..b {
1949 let o = short_conv_forward(
1950 &xs[bi * cfg.hidden_size..(bi + 1) * cfg.hidden_size],
1951 &w,
1952 &cfg,
1953 &mut s_seq,
1954 None,
1955 );
1956 seq_out[bi * cfg.hidden_size..(bi + 1) * cfg.hidden_size].copy_from_slice(&o);
1957 }
1958
1959 let mut s_batch = Vec::new();
1960 let batch_out = short_conv_forward_batch(&xs, b, &w, &cfg, &mut s_batch, None);
1961 assert_eq!(
1962 seq_out, batch_out,
1963 "batch conv must match sequential decode"
1964 );
1965 assert_eq!(s_seq, s_batch, "ring state must match after the chunk");
1966 }
1967}