1use crate::pool::Pool;
32use crate::qtensor::QTensor;
33use cortiq_core::TensorDtype;
34use std::sync::OnceLock;
35use std::sync::atomic::{AtomicU64, Ordering};
36
37static PERF_GDN_FORWARD_CALLS: AtomicU64 = AtomicU64::new(0);
38static PERF_GDN_FORWARD_NS: AtomicU64 = AtomicU64::new(0);
39static PERF_GDN_BATCH_CALLS: AtomicU64 = AtomicU64::new(0);
40static PERF_GDN_BATCH_NS: AtomicU64 = AtomicU64::new(0);
41static PERF_GDN_STEP_CALLS: AtomicU64 = AtomicU64::new(0);
42static PERF_GDN_STEP_NS: AtomicU64 = AtomicU64::new(0);
43
44fn perf_enabled() -> bool {
45 static ON: OnceLock<bool> = OnceLock::new();
46 *ON.get_or_init(|| std::env::var("CMF_PERF_PROFILE").as_deref() == Ok("1"))
47}
48
49pub struct VmfPhaseWeights {
51 pub thq: QTensor,
53 pub thk: QTensor,
55 pub v_proj: QTensor,
57 pub out_proj: QTensor,
59 pub decay: Vec<f64>,
61 pub conv: Option<Vec<f32>>,
67 pub k_gate: Option<(QTensor, Vec<f32>)>,
74 pub phase_delta: bool,
79}
80
81#[derive(Clone, Copy)]
82pub struct VmfPhaseCfg {
83 pub num_heads: usize,
84 pub nphase: usize,
85 pub value_head_dim: usize,
86 pub hidden_size: usize,
87 pub phase_mass: f32,
96}
97
98impl VmfPhaseCfg {
99 pub fn state_len(&self) -> usize {
100 self.num_heads * 2 * self.nphase * self.value_head_dim
101 }
102}
103
104fn phase_delta_step_f32(
110 thq: &[f32],
111 thk: &[f32],
112 v: &[f32],
113 decay: &[f64],
114 kap: Option<&[f32]>,
115 cfg: &VmfPhaseCfg,
116 state: &mut [f32],
117 out: &mut [f32],
118) {
119 let (nh, nph, dv) = (cfg.num_heads, cfg.nphase, cfg.value_head_dim);
120 let p2 = 2 * nph;
121 let scale = 1.0f64 / (nph as f64).sqrt();
122 debug_assert_eq!(thq.len(), nh * nph);
123 debug_assert_eq!(thk.len(), nh * nph);
124 debug_assert_eq!(v.len(), nh * dv);
125 debug_assert_eq!(decay.len(), nh * p2);
126 debug_assert!(state.len() >= nh * p2 * dv);
127 debug_assert!(out.len() >= nh * dv);
128
129 let mut r = vec![0.0f64; dv];
133 let mut key = vec![0.0f64; p2];
134 for h in 0..nh {
135 let s = &mut state[h * p2 * dv..(h + 1) * p2 * dv];
136 let thk_h = &thk[h * nph..(h + 1) * nph];
137 let thq_h = &thq[h * nph..(h + 1) * nph];
138 let vt = &v[h * dv..(h + 1) * dv];
139 let ot = &mut out[h * dv..(h + 1) * dv];
140 let dec = &decay[h * p2..(h + 1) * p2];
141 let beta = kap.map_or(1.0f64, |k| k[h] as f64);
142
143 r.fill(0.0);
147 for f in 0..p2 {
148 let kf = if f < nph {
149 scale * (thk_h[f] as f64).cos()
150 } else {
151 scale * (thk_h[f - nph] as f64).sin()
152 };
153 key[f] = kf;
154 let row = &s[f * dv..(f + 1) * dv];
155 for d in 0..dv {
156 r[d] += kf * (dec[f] * row[d] as f64);
157 }
158 }
159
160 for f in 0..p2 {
168 let kf = key[f];
169 #[cfg(target_arch = "aarch64")]
170 let qf = if f < nph {
171 scale * (thq_h[f] as f64).cos()
172 } else {
173 scale * (thq_h[f - nph] as f64).sin()
174 };
175 let row = &mut s[f * dv..(f + 1) * dv];
176 for d in 0..dv {
177 let p = dec[f] * row[d] as f64;
178 row[d] = (p + beta * kf * (vt[d] as f64 - r[d])) as f32;
179 #[cfg(target_arch = "aarch64")]
180 {
181 ot[d] += (qf * row[d] as f64) as f32;
182 }
183 }
184 }
185 #[cfg(not(target_arch = "aarch64"))]
186 for f in 0..p2 {
187 let qf = if f < nph {
188 scale * (thq_h[f] as f64).cos()
189 } else {
190 scale * (thq_h[f - nph] as f64).sin()
191 };
192 let row = &s[f * dv..(f + 1) * dv];
193 for d in 0..dv {
194 ot[d] += (qf * row[d] as f64) as f32;
195 }
196 }
197 }
198}
199
200fn phase_step(
204 thq: &[f32],
205 thk: &[f32],
206 v: &[f32],
207 decay: &[f64],
208 kap: Option<&[f32]>,
209 cfg: &VmfPhaseCfg,
210 state: &mut [f32],
211 out: &mut [f32],
212 phase_delta: bool,
213) {
214 let (nh, nph, dv) = (cfg.num_heads, cfg.nphase, cfg.value_head_dim);
215 if phase_delta {
216 phase_delta_step_f32(thq, thk, v, decay, kap, cfg, state, out);
217 return;
218 }
219 let mscale = 1.0f64 / (1.0 + cfg.phase_mass as f64);
221 let p2 = 2 * nph;
222 for h in 0..nh {
223 let s = &mut state[h * p2 * dv..(h + 1) * p2 * dv];
224 let thk_h = &thk[h * nph..(h + 1) * nph];
225 let thq_h = &thq[h * nph..(h + 1) * nph];
226 let vt = &v[h * dv..(h + 1) * dv];
227 let ot = &mut out[h * dv..(h + 1) * dv];
228 let dec = &decay[h * p2..(h + 1) * p2];
229 let kh = kap.map_or(1.0f64, |k| k[h] as f64);
231 for f in 0..p2 {
232 let (fk, fq) = if f < nph {
234 (
235 (thk_h[f] as f64 * mscale).cos(),
236 (thq_h[f] as f64 * mscale).cos(),
237 )
238 } else {
239 (
240 (thk_h[f - nph] as f64 * mscale).sin(),
241 (thq_h[f - nph] as f64 * mscale).sin(),
242 )
243 };
244 let fkw = fk * kh;
245 let row = &mut s[f * dv..(f + 1) * dv];
246 let dcf = dec[f];
247 for d in 0..dv {
248 let cell = dcf * row[d] as f64 + fkw * vt[d] as f64;
250 row[d] = cell as f32;
251 ot[d] += (fq * cell) as f32; }
253 }
254 }
255}
256
257fn kappa_of(x: &[f32], w: &VmfPhaseWeights, nh: usize, pool: Option<&Pool>) -> Option<Vec<f32>> {
260 let (kw, kb) = w.k_gate.as_ref()?;
261 let mut k = vec![0.0f32; nh];
262 kw.matvec(x, &mut k, pool);
263 for (v, b) in k.iter_mut().zip(kb) {
264 *v = 1.0 / (1.0 + (-(*v + b)).exp());
265 }
266 Some(k)
267}
268
269fn conv_in(
273 x: &[f32],
274 w: &VmfPhaseWeights,
275 cfg: &VmfPhaseCfg,
276 state: &mut Vec<f32>,
277) -> Option<Vec<f32>> {
278 let taps = w.conv.as_ref()?;
279 let h = cfg.hidden_size;
280 let k = taps.len() / h.max(1);
281 if k < 2 || taps.len() != h * k {
282 return None;
283 }
284 let ring = (k - 1) * h;
285 let base = cfg.state_len();
286 if state.len() != base + ring {
287 let mut ns = vec![0f32; base + ring];
289 let n = state.len().min(base);
290 ns[..n].copy_from_slice(&state[..n]);
291 *state = ns;
292 }
293 let mut y = vec![0.0f32; h];
294 for c in 0..h {
295 let mut acc = taps[c * k + k - 1] * x[c];
297 for j in 0..k - 1 {
298 acc += taps[c * k + j] * state[base + j * h + c];
299 }
300 y[c] = acc;
301 }
302 conv_ring_push(x, h, base, state);
303 Some(y)
304}
305
306fn conv_ring_push(x: &[f32], h: usize, base: usize, state: &mut [f32]) {
308 let ring = state.len() - base;
309 state.copy_within(base + h.., base);
310 let at = base + ring - h;
311 state[at..at + h].copy_from_slice(&x[..h]);
312}
313
314pub fn vmf_phase_forward(
316 x: &[f32],
317 w: &VmfPhaseWeights,
318 cfg: &VmfPhaseCfg,
319 state: &mut Vec<f32>,
320 pool: Option<&Pool>,
321) -> Vec<f32> {
322 if w.conv.is_none() && state.len() != cfg.state_len() {
323 *state = vec![0f32; cfg.state_len()];
324 }
325 let xc = conv_in(x, w, cfg, state);
326 let x = xc.as_deref().unwrap_or(x);
327 let (nh, nph, dv) = (cfg.num_heads, cfg.nphase, cfg.value_head_dim);
328
329 let mut thq = vec![0.0f32; nh * nph];
330 w.thq.matvec(x, &mut thq, pool);
331 let mut thk = vec![0.0f32; nh * nph];
332 w.thk.matvec(x, &mut thk, pool);
333 let mut v = vec![0.0f32; nh * dv];
334 w.v_proj.matvec(x, &mut v, pool);
335
336 let kap = kappa_of(x, w, nh, pool);
337 let mut o = vec![0.0f32; nh * dv];
338 phase_step(
339 &thq,
340 &thk,
341 &v,
342 &w.decay,
343 kap.as_deref(),
344 cfg,
345 state,
346 &mut o,
347 w.phase_delta,
348 );
349
350 let mut out = vec![0.0f32; cfg.hidden_size];
351 w.out_proj.matvec(&o, &mut out, pool);
352 out
353}
354
355#[allow(clippy::too_many_arguments)]
360pub fn vmf_phase_pair(
361 x1: &[f32],
362 x2: &[f32],
363 w: &VmfPhaseWeights,
364 cfg: &VmfPhaseCfg,
365 state: &mut Vec<f32>,
366 scratch: &mut Vec<f32>,
367 pool: Option<&Pool>,
368) -> (Vec<f32>, Vec<f32>) {
369 if w.conv.is_none() && state.len() != cfg.state_len() {
370 *state = vec![0f32; cfg.state_len()];
371 }
372 let xc1 = conv_in(x1, w, cfg, state);
375 let x1 = xc1.as_deref().unwrap_or(x1);
376 let (xc2, x2raw) = if w.conv.is_some() {
377 let mut tmp = state.clone();
378 (conv_in(x2, w, cfg, &mut tmp), Some(x2))
379 } else {
380 (None, None)
381 };
382 let x2 = xc2.as_deref().unwrap_or(x2);
383 let (nh, nph, dv) = (cfg.num_heads, cfg.nphase, cfg.value_head_dim);
384
385 let mut thq1 = vec![0.0f32; nh * nph];
386 let mut thq2 = vec![0.0f32; nh * nph];
387 w.thq.matvec2(x1, x2, &mut thq1, &mut thq2, pool);
388 let mut thk1 = vec![0.0f32; nh * nph];
389 let mut thk2 = vec![0.0f32; nh * nph];
390 w.thk.matvec2(x1, x2, &mut thk1, &mut thk2, pool);
391 let mut v1 = vec![0.0f32; nh * dv];
392 let mut v2 = vec![0.0f32; nh * dv];
393 w.v_proj.matvec2(x1, x2, &mut v1, &mut v2, pool);
394
395 let kap1 = kappa_of(x1, w, nh, pool);
397 let mut o1 = vec![0.0f32; nh * dv];
398 phase_step(
399 &thq1,
400 &thk1,
401 &v1,
402 &w.decay,
403 kap1.as_deref(),
404 cfg,
405 state,
406 &mut o1,
407 w.phase_delta,
408 );
409
410 let kap2 = kappa_of(x2, w, nh, pool);
412 scratch.clear();
413 scratch.extend_from_slice(state);
414 let mut o2 = vec![0.0f32; nh * dv];
415 phase_step(
416 &thq2,
417 &thk2,
418 &v2,
419 &w.decay,
420 kap2.as_deref(),
421 cfg,
422 scratch,
423 &mut o2,
424 w.phase_delta,
425 );
426 if let Some(xr) = x2raw {
429 conv_ring_push(xr, cfg.hidden_size, cfg.state_len(), scratch);
430 }
431
432 let mut out1 = vec![0.0f32; cfg.hidden_size];
433 let mut out2 = vec![0.0f32; cfg.hidden_size];
434 w.out_proj.matvec2(&o1, &o2, &mut out1, &mut out2, pool);
435 (out1, out2)
436}
437
438pub struct GdnWeights {
443 pub in_proj_qkv: QTensor,
445 pub in_proj_z: QTensor,
447 pub in_proj_a: QTensor,
449 pub in_proj_b: QTensor,
451 pub conv1d: Vec<f32>,
453 pub a_log: Vec<f32>,
455 pub dt_bias: Vec<f32>,
457 pub norm: Vec<f32>,
459 pub out_proj: QTensor,
461}
462
463#[derive(Clone, Copy)]
464pub struct GdnCfg {
465 pub num_v_heads: usize,
466 pub num_k_heads: usize,
467 pub key_head_dim: usize,
468 pub value_head_dim: usize,
469 pub conv_kernel: usize,
470 pub hidden_size: usize,
471 pub rms_eps: f64,
472 pub output_gate_sigmoid: bool,
475}
476
477impl GdnCfg {
478 pub fn conv_dim(&self) -> usize {
479 2 * self.num_k_heads * self.key_head_dim + self.num_v_heads * self.value_head_dim
480 }
481
482 pub fn state_len(&self) -> usize {
485 (self.conv_kernel - 1) * self.conv_dim()
486 + self.num_v_heads * self.key_head_dim * self.value_head_dim
487 }
488}
489
490fn softplus(x: f64) -> f64 {
491 if x > 20.0 { x } else { x.exp().ln_1p() }
492}
493
494fn sigmoid(x: f64) -> f64 {
495 1.0 / (1.0 + (-x).exp())
496}
497
498fn silu(x: f64) -> f64 {
499 x / (1.0 + (-x).exp())
500}
501
502#[derive(Clone, Copy)]
505struct SendMutF32(*mut f32);
506unsafe impl Send for SendMutF32 {}
507unsafe impl Sync for SendMutF32 {}
508
509#[allow(clippy::too_many_arguments)]
522fn gdn_step(
523 qkv: &[f32],
524 z: &[f32],
525 a: &[f32],
526 b: &[f32],
527 w: &GdnWeights,
528 cfg: &GdnCfg,
529 state: &mut [f32],
530 of: &mut [f32],
531 pool: Option<&Pool>,
532) {
533 let perf_t0 = perf_enabled().then(std::time::Instant::now);
534 let (nv, nk, dk, dv, kk) = (
535 cfg.num_v_heads,
536 cfg.num_k_heads,
537 cfg.key_head_dim,
538 cfg.value_head_dim,
539 cfg.conv_kernel,
540 );
541 let c_dim = cfg.conv_dim();
542 let (kd, rep) = (nk * dk, nv / nk);
543 let (ring, s_all) = state.split_at_mut((kk - 1) * c_dim);
544
545 let mut cq = vec![0f32; c_dim];
549 for c in 0..c_dim {
550 let taps = &w.conv1d[c * kk..(c + 1) * kk];
551 let mut acc = qkv[c] as f64 * taps[kk - 1] as f64;
552 for j in 0..kk - 1 {
553 acc += ring[j * c_dim + c] as f64 * taps[j] as f64;
554 }
555 cq[c] = silu(acc) as f32;
556 }
557 if kk > 1 {
559 ring.copy_within(c_dim.., 0);
560 let tail = (kk - 2) * c_dim;
561 ring[tail..tail + c_dim].copy_from_slice(&qkv[..c_dim]);
562 }
563
564 let cq = &cq;
565 let s_ptr = SendMutF32(s_all.as_mut_ptr());
566 let of_ptr = SendMutF32(of.as_mut_ptr());
567 let head_range = |h0: usize, h1: usize| {
568 let (s_ptr, of_ptr) = (s_ptr, of_ptr);
571 let mut kv = crate::attention::take_buf(dv);
573 let mut delta = crate::attention::take_buf(dv);
574 let mut o = crate::attention::take_buf(dv);
575 let mut kf = crate::attention::take_buf(dk);
576 let mut qf = crate::attention::take_buf(dk);
577 for h in h0..h1 {
578 let ko = h / rep; let (qs, ks) = (ko * dk, kd + ko * dk);
580 let (mut nq, mut nkn) = (0f64, 0f64);
582 for d in 0..dk {
583 nq += (cq[qs + d] as f64) * (cq[qs + d] as f64);
584 nkn += (cq[ks + d] as f64) * (cq[ks + d] as f64);
585 }
586 let invq = (1.0 / ((nq + 1e-6).sqrt() * (dk as f64).sqrt())) as f32;
587 let invk = (1.0 / (nkn + 1e-6).sqrt()) as f32;
588 for d in 0..dk {
589 qf[d] = cq[qs + d] * invq;
590 kf[d] = cq[ks + d] * invk;
591 }
592
593 let g = (-(w.a_log[h] as f64).exp() * softplus(a[h] as f64 + w.dt_bias[h] as f64)).exp()
594 as f32;
595 let beta = sigmoid(b[h] as f64) as f32;
596
597 let s = unsafe { std::slice::from_raw_parts_mut(s_ptr.0.add(h * dk * dv), dk * dv) };
599 let oh = unsafe { std::slice::from_raw_parts_mut(of_ptr.0.add(h * dv), dv) };
600 let vt = &cq[2 * kd + h * dv..2 * kd + (h + 1) * dv];
601
602 kv[..dv].fill(0.0);
606 for di in 0..dk {
607 let kfd = kf[di];
608 let row = &s[di * dv..(di + 1) * dv];
609 for dj in 0..dv {
610 kv[dj] += row[dj] * kfd; }
612 }
613 for dj in 0..dv {
614 delta[dj] = (vt[dj] - g * kv[dj]) * beta;
615 }
616 o[..dv].fill(0.0);
617 for di in 0..dk {
618 let kfd = kf[di];
619 let qfd = qf[di];
620 let row = &mut s[di * dv..(di + 1) * dv];
621 for dj in 0..dv {
622 let cell = g * row[dj] + kfd * delta[dj];
623 row[dj] = cell;
624 o[dj] += qfd * cell; }
626 }
627 let ss: f64 = o[..dv].iter().map(|&v| (v as f64) * (v as f64)).sum();
629 let inv = 1.0 / (ss / dv as f64 + cfg.rms_eps).sqrt();
630 for dj in 0..dv {
631 let gate = if cfg.output_gate_sigmoid {
632 sigmoid(z[h * dv + dj] as f64)
633 } else {
634 silu(z[h * dv + dj] as f64)
635 };
636 oh[dj] = ((o[dj] as f64 * inv) * w.norm[dj] as f64 * gate) as f32;
637 }
638 }
639 crate::attention::recycle_buf(&mut kv);
640 crate::attention::recycle_buf(&mut delta);
641 crate::attention::recycle_buf(&mut o);
642 crate::attention::recycle_buf(&mut kf);
643 crate::attention::recycle_buf(&mut qf);
644 };
645 match pool {
646 Some(pool) if nv >= 4 => pool.run(&|widx, n| {
647 let chunk = nv.div_ceil(n);
648 let h0 = (widx * chunk).min(nv);
649 let h1 = (h0 + chunk).min(nv);
650 if h0 < h1 {
651 head_range(h0, h1);
652 }
653 }),
654 _ => head_range(0, nv),
655 }
656 if let Some(t0) = perf_t0 {
657 PERF_GDN_STEP_CALLS.fetch_add(1, Ordering::Relaxed);
658 PERF_GDN_STEP_NS.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
659 }
660}
661
662fn gdn_scan_on() -> bool {
665 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
666 *ON.get_or_init(|| std::env::var("CMF_GDN_SCAN").as_deref() != Ok("0"))
667}
668
669#[allow(clippy::too_many_arguments)]
680fn gdn_scan_batch(
681 qkv: &[f32],
682 z: &[f32],
683 a: &[f32],
684 bb: &[f32],
685 b: usize,
686 w: &GdnWeights,
687 cfg: &GdnCfg,
688 state: &mut [f32],
689 of: &mut [f32],
690 pool: Option<&Pool>,
691) {
692 let (nv, nk, dk, dv, kk) = (
693 cfg.num_v_heads,
694 cfg.num_k_heads,
695 cfg.key_head_dim,
696 cfg.value_head_dim,
697 cfg.conv_kernel,
698 );
699 let c_dim = cfg.conv_dim();
700 let vd = nv * dv;
701 let (kd, rep) = (nk * dk, nv / nk);
702 let hist = kk.saturating_sub(1);
703 let (ring, s_all) = state.split_at_mut(hist * c_dim);
704
705 let mut cq = vec![0f32; b * c_dim];
710 {
711 let ring_ro: &[f32] = ring;
712 let cq_ptr = SendMutF32(cq.as_mut_ptr());
713 let conv_range = |c0: usize, c1: usize| {
714 let cq_ptr = cq_ptr;
715 for c in c0..c1 {
716 let taps = &w.conv1d[c * kk..(c + 1) * kk];
717 for t in 0..b {
718 let mut acc = qkv[t * c_dim + c] as f64 * taps[kk - 1] as f64;
719 for j in 0..hist {
720 let raw = if t + j >= hist {
721 qkv[(t + j - hist) * c_dim + c]
722 } else {
723 ring_ro[(t + j) * c_dim + c]
724 };
725 acc += raw as f64 * taps[j] as f64;
726 }
727 unsafe { *cq_ptr.0.add(t * c_dim + c) = silu(acc) as f32 };
729 }
730 }
731 };
732 match pool {
733 Some(pool) if c_dim >= 256 => pool.run(&|widx, n| {
734 let chunk = c_dim.div_ceil(n);
735 let c0 = (widx * chunk).min(c_dim);
736 let c1 = (c0 + chunk).min(c_dim);
737 if c0 < c1 {
738 conv_range(c0, c1);
739 }
740 }),
741 _ => conv_range(0, c_dim),
742 }
743 }
744 if hist > 0 {
747 let mut next = vec![0f32; hist * c_dim];
748 for j in 0..hist {
749 let src = if b + j >= hist {
750 &qkv[(b + j - hist) * c_dim..(b + j - hist + 1) * c_dim]
751 } else {
752 &ring[(b + j) * c_dim..(b + j + 1) * c_dim]
753 };
754 next[j * c_dim..(j + 1) * c_dim].copy_from_slice(src);
755 }
756 ring.copy_from_slice(&next);
757 }
758
759 let cq = &cq;
760 let s_ptr = SendMutF32(s_all.as_mut_ptr());
761 let of_ptr = SendMutF32(of.as_mut_ptr());
762 let head_range = |h0: usize, h1: usize| {
763 let (s_ptr, of_ptr) = (s_ptr, of_ptr);
764 let mut kv = crate::attention::take_buf(dv);
765 let mut delta = crate::attention::take_buf(dv);
766 let mut o = crate::attention::take_buf(dv);
767 let mut kf = crate::attention::take_buf(dk);
768 let mut qf = crate::attention::take_buf(dk);
769 for h in h0..h1 {
770 let ko = h / rep;
771 let (qs, ks) = (ko * dk, kd + ko * dk);
772 let g_base = -(w.a_log[h] as f64).exp();
773 let s = unsafe { std::slice::from_raw_parts_mut(s_ptr.0.add(h * dk * dv), dk * dv) };
775 for t in 0..b {
776 let cqt = &cq[t * c_dim..(t + 1) * c_dim];
777 let (mut nq, mut nkn) = (0f64, 0f64);
778 for d in 0..dk {
779 nq += (cqt[qs + d] as f64) * (cqt[qs + d] as f64);
780 nkn += (cqt[ks + d] as f64) * (cqt[ks + d] as f64);
781 }
782 let invq = (1.0 / ((nq + 1e-6).sqrt() * (dk as f64).sqrt())) as f32;
783 let invk = (1.0 / (nkn + 1e-6).sqrt()) as f32;
784 for d in 0..dk {
785 qf[d] = cqt[qs + d] * invq;
786 kf[d] = cqt[ks + d] * invk;
787 }
788 let g = (g_base * softplus(a[t * nv + h] as f64 + w.dt_bias[h] as f64)).exp() as f32;
789 let beta = sigmoid(bb[t * nv + h] as f64) as f32;
790 let oh = unsafe { std::slice::from_raw_parts_mut(of_ptr.0.add(t * vd + h * dv), dv) };
791 let vt = &cqt[2 * kd + h * dv..2 * kd + (h + 1) * dv];
792 kv[..dv].fill(0.0);
793 for di in 0..dk {
794 let kfd = kf[di];
795 let row = &s[di * dv..(di + 1) * dv];
796 for dj in 0..dv {
797 kv[dj] += row[dj] * kfd;
798 }
799 }
800 for dj in 0..dv {
801 delta[dj] = (vt[dj] - g * kv[dj]) * beta;
802 }
803 o[..dv].fill(0.0);
804 for di in 0..dk {
805 let kfd = kf[di];
806 let qfd = qf[di];
807 let row = &mut s[di * dv..(di + 1) * dv];
808 for dj in 0..dv {
809 let cell = g * row[dj] + kfd * delta[dj];
810 row[dj] = cell;
811 o[dj] += qfd * cell;
812 }
813 }
814 let ss: f64 = o[..dv].iter().map(|&v| (v as f64) * (v as f64)).sum();
815 let inv = 1.0 / (ss / dv as f64 + cfg.rms_eps).sqrt();
816 let zt = &z[t * vd..(t + 1) * vd];
817 for dj in 0..dv {
818 let gate = if cfg.output_gate_sigmoid {
819 sigmoid(zt[h * dv + dj] as f64)
820 } else {
821 silu(zt[h * dv + dj] as f64)
822 };
823 oh[dj] = ((o[dj] as f64 * inv) * w.norm[dj] as f64 * gate) as f32;
824 }
825 }
826 }
827 crate::attention::recycle_buf(&mut kv);
828 crate::attention::recycle_buf(&mut delta);
829 crate::attention::recycle_buf(&mut o);
830 crate::attention::recycle_buf(&mut kf);
831 crate::attention::recycle_buf(&mut qf);
832 };
833 match pool {
834 Some(pool) if nv >= 4 => pool.run(&|widx, n| {
835 let chunk = nv.div_ceil(n);
836 let h0 = (widx * chunk).min(nv);
837 let h1 = (h0 + chunk).min(nv);
838 if h0 < h1 {
839 head_range(h0, h1);
840 }
841 }),
842 _ => head_range(0, nv),
843 }
844}
845
846pub fn gdn_forward(
848 x: &[f32],
849 w: &GdnWeights,
850 cfg: &GdnCfg,
851 state: &mut Vec<f32>,
852 pool: Option<&Pool>,
853) -> Vec<f32> {
854 let perf_t0 = perf_enabled().then(std::time::Instant::now);
855 if state.len() != cfg.state_len() {
856 *state = vec![0f32; cfg.state_len()];
857 }
858 let (c_dim, vd) = (cfg.conv_dim(), cfg.num_v_heads * cfg.value_head_dim);
859
860 let mut qkv = vec![0.0f32; c_dim];
861 let mut z = vec![0.0f32; vd];
862 let mut a = vec![0.0f32; cfg.num_v_heads];
863 let mut b = vec![0.0f32; cfg.num_v_heads];
864 let cpu_projs = |qkv: &mut Vec<f32>, z: &mut Vec<f32>, a: &mut Vec<f32>, b: &mut Vec<f32>| {
868 QTensor::matvec_many(
869 [&w.in_proj_qkv, &w.in_proj_z, &w.in_proj_a, &w.in_proj_b],
870 x,
871 [
872 qkv.as_mut_slice(),
873 z.as_mut_slice(),
874 a.as_mut_slice(),
875 b.as_mut_slice(),
876 ],
877 pool,
878 );
879 };
880 let mut done = false;
881 if crate::gpu::enabled_here() && gdn_projs_eligible(w) {
882 match crate::gpu::probe_arm(crate::gpu::OpClass::Batch) {
883 crate::gpu::ProbeArm::Gpu => {
884 let t0 = std::time::Instant::now();
885 if gdn_projs_gpu(w, x, &mut qkv, &mut z) {
886 crate::gpu::probe_record(crate::gpu::OpClass::Batch, true, t0.elapsed());
887 w.in_proj_a.matvec(x, &mut a, pool);
888 w.in_proj_b.matvec(x, &mut b, pool);
889 done = true;
890 } else {
891 crate::gpu::probe_note_decline(crate::gpu::OpClass::Batch);
892 }
893 }
894 crate::gpu::ProbeArm::CpuTimed => {
895 let t0 = std::time::Instant::now();
896 crate::gpu::cpu_scope(|| cpu_projs(&mut qkv, &mut z, &mut a, &mut b));
897 crate::gpu::probe_record(crate::gpu::OpClass::Batch, false, t0.elapsed());
898 done = true;
899 }
900 crate::gpu::ProbeArm::Cpu => {
901 crate::gpu::cpu_scope(|| cpu_projs(&mut qkv, &mut z, &mut a, &mut b));
902 done = true;
903 }
904 }
905 }
906 if !done {
907 cpu_projs(&mut qkv, &mut z, &mut a, &mut b);
908 }
909
910 let mut of = vec![0.0f32; vd];
911 gdn_step(&qkv, &z, &a, &b, w, cfg, state, &mut of, pool);
912
913 let mut out = vec![0.0f32; cfg.hidden_size];
914 w.out_proj.matvec(&of, &mut out, pool);
915 if let Some(t0) = perf_t0 {
916 PERF_GDN_FORWARD_CALLS.fetch_add(1, Ordering::Relaxed);
917 PERF_GDN_FORWARD_NS.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
918 }
919 out
920}
921
922pub fn gdn_forward_batch(
927 xs: &[f32],
928 b: usize,
929 w: &GdnWeights,
930 cfg: &GdnCfg,
931 state: &mut Vec<f32>,
932 pool: Option<&Pool>,
933) -> Vec<f32> {
934 let perf_t0 = perf_enabled().then(std::time::Instant::now);
935 if state.len() != cfg.state_len() {
936 *state = vec![0f32; cfg.state_len()];
937 }
938 let (c_dim, vd) = (cfg.conv_dim(), cfg.num_v_heads * cfg.value_head_dim);
939 let nv = cfg.num_v_heads;
940
941 let mut qkv = vec![0.0f32; b * c_dim];
942 w.in_proj_qkv.matmat(xs, b, &mut qkv, pool);
943 let mut z = vec![0.0f32; b * vd];
944 w.in_proj_z.matmat(xs, b, &mut z, pool);
945 let mut a = vec![0.0f32; b * nv];
946 w.in_proj_a.matmat(xs, b, &mut a, pool);
947 let mut bb = vec![0.0f32; b * nv];
948 w.in_proj_b.matmat(xs, b, &mut bb, pool);
949
950 let mut of = vec![0.0f32; b * vd];
951 if b > 1 && gdn_scan_on() {
952 gdn_scan_batch(&qkv, &z, &a, &bb, b, w, cfg, state, &mut of, pool);
953 } else {
954 for bi in 0..b {
955 gdn_step(
956 &qkv[bi * c_dim..(bi + 1) * c_dim],
957 &z[bi * vd..(bi + 1) * vd],
958 &a[bi * nv..(bi + 1) * nv],
959 &bb[bi * nv..(bi + 1) * nv],
960 w,
961 cfg,
962 state,
963 &mut of[bi * vd..(bi + 1) * vd],
964 pool,
965 );
966 }
967 }
968 let mut out = vec![0.0f32; b * cfg.hidden_size];
969 w.out_proj.matmat(&of, b, &mut out, pool);
970 if std::env::var("CMF_GDN_TRACE").is_ok() {
971 let n = |v: &[f32]| v.iter().map(|x| x * x).sum::<f32>().sqrt();
972 eprintln!(
973 "gdn-batch b={b}: |x0|={:.5} |qkv0|={:.5} |z0|={:.5} |a0|={:.5} |b0|={:.5} |of0|={:.5} |out0|={:.5} |state|={:.5}",
974 n(&xs[..cfg.hidden_size]),
975 n(&qkv[..c_dim]),
976 n(&z[..vd]),
977 n(&a[..nv]),
978 n(&bb[..nv]),
979 n(&of[..vd]),
980 n(&out[..cfg.hidden_size]),
981 n(state)
982 );
983 }
984 if let Some(t0) = perf_t0 {
985 PERF_GDN_BATCH_CALLS.fetch_add(1, Ordering::Relaxed);
986 PERF_GDN_BATCH_NS.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
987 }
988 out
989}
990
991pub fn perf_report() {
993 if !perf_enabled() {
994 return;
995 }
996 let fc = PERF_GDN_FORWARD_CALLS.load(Ordering::Relaxed);
997 let bc = PERF_GDN_BATCH_CALLS.load(Ordering::Relaxed);
998 let sc = PERF_GDN_STEP_CALLS.load(Ordering::Relaxed);
999 eprintln!(
1000 "[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}",
1001 fc,
1002 PERF_GDN_FORWARD_NS.load(Ordering::Relaxed) as f64 / 1e6,
1003 PERF_GDN_FORWARD_NS.load(Ordering::Relaxed) as f64 / 1e6 / fc.max(1) as f64,
1004 bc,
1005 PERF_GDN_BATCH_NS.load(Ordering::Relaxed) as f64 / 1e6,
1006 sc,
1007 PERF_GDN_STEP_NS.load(Ordering::Relaxed) as f64 / 1e6,
1008 PERF_GDN_STEP_NS.load(Ordering::Relaxed) as f64 / 1e6 / sc.max(1) as f64,
1009 );
1010}
1011
1012fn gdn_projs_eligible(w: &GdnWeights) -> bool {
1016 if std::env::var("CMF_GPU_GDN")
1022 .map(|v| v == "0")
1023 .unwrap_or(false)
1024 {
1025 return false;
1026 }
1027 w.in_proj_qkv.is_q1()
1028 || w.in_proj_qkv.q4t_parts().is_some()
1029 || w.in_proj_qkv.q4tp_parts().is_some()
1030 || std::env::var("CMF_GPU_GDN")
1031 .map(|v| v == "1")
1032 .unwrap_or(false)
1033}
1034
1035fn gdn_projs_gpu(w: &GdnWeights, x: &[f32], qkv: &mut [f32], z: &mut [f32]) -> bool {
1037 use crate::gpu::matvec_batch;
1038 use crate::qtensor::QTensor;
1039 if !crate::gpu::enabled_here() {
1040 return false;
1041 }
1042 fn part<'a>(
1043 t: &'a QTensor,
1044 x: &[f32],
1045 ) -> Option<(
1046 std::sync::Arc<cortiq_core::CmfModel>,
1047 crate::gpu::BatchJob<'a>,
1048 )> {
1049 use crate::gpu::BatchJob;
1050 use crate::qtensor::prescale;
1051 use cortiq_core::TensorDtype;
1052 match t {
1053 QTensor::Mapped {
1054 model,
1055 idx,
1056 dtype: dt @ (TensorDtype::Q8Row | TensorDtype::Q8_2f),
1057 rows,
1058 cols,
1059 row_scale,
1060 col_field,
1061 ..
1062 } => Some((
1063 model.clone(),
1064 BatchJob {
1065 idx: *idx,
1066 rows: *rows,
1067 cols: *cols,
1068 row_scale,
1069 xs: prescale(x, col_field, *dt).into_owned(),
1070 layout: crate::gpu::BatchLayout::Q8,
1071 },
1072 )),
1073 QTensor::Mapped {
1074 model,
1075 idx,
1076 dtype: TensorDtype::Q1,
1077 rows,
1078 cols,
1079 ..
1080 } => Some((
1081 model.clone(),
1082 BatchJob {
1083 idx: *idx,
1084 rows: *rows,
1085 cols: *cols,
1086 row_scale: &[],
1087 xs: x.to_vec(),
1088 layout: crate::gpu::BatchLayout::Q1,
1089 },
1090 )),
1091 QTensor::Mapped {
1092 model,
1093 idx,
1094 dtype: TensorDtype::Q1G,
1095 rows,
1096 cols,
1097 ..
1098 } => Some((
1099 model.clone(),
1100 BatchJob {
1101 idx: *idx,
1102 rows: *rows,
1103 cols: *cols,
1104 row_scale: &[],
1105 xs: x.to_vec(),
1106 layout: crate::gpu::BatchLayout::Q1g,
1107 },
1108 )),
1109 QTensor::Mapped {
1113 model,
1114 idx,
1115 dtype: dt @ (TensorDtype::Q4Tiled | TensorDtype::Q4TiledP),
1116 rows,
1117 cols,
1118 ..
1119 } => Some((
1120 model.clone(),
1121 BatchJob {
1122 idx: *idx,
1123 rows: *rows,
1124 cols: *cols,
1125 row_scale: &[],
1126 xs: x.to_vec(),
1127 layout: if *dt == TensorDtype::Q4TiledP {
1128 crate::gpu::BatchLayout::Q4tp
1129 } else {
1130 crate::gpu::BatchLayout::Q4t
1131 },
1132 },
1133 )),
1134 _ => None,
1135 }
1136 }
1137 let Some((model, jq)) = part(&w.in_proj_qkv, x) else {
1138 return false;
1139 };
1140 let Some((_, jz)) = part(&w.in_proj_z, x) else {
1141 return false;
1142 };
1143 matvec_batch(&model, &[jq, jz], &mut [qkv, z])
1144}
1145
1146#[allow(clippy::too_many_arguments)]
1149pub fn gdn_pair(
1150 x1: &[f32],
1151 x2: &[f32],
1152 w: &GdnWeights,
1153 cfg: &GdnCfg,
1154 state: &mut Vec<f32>,
1155 scratch: &mut Vec<f32>,
1156 pool: Option<&Pool>,
1157) -> (Vec<f32>, Vec<f32>) {
1158 if state.len() != cfg.state_len() {
1159 *state = vec![0f32; cfg.state_len()];
1160 }
1161 let (c_dim, vd, nv) = (
1162 cfg.conv_dim(),
1163 cfg.num_v_heads * cfg.value_head_dim,
1164 cfg.num_v_heads,
1165 );
1166
1167 let mut qkv1 = vec![0.0f32; c_dim];
1168 let mut qkv2 = vec![0.0f32; c_dim];
1169 w.in_proj_qkv.matvec2(x1, x2, &mut qkv1, &mut qkv2, pool);
1170 let mut z1 = vec![0.0f32; vd];
1171 let mut z2 = vec![0.0f32; vd];
1172 w.in_proj_z.matvec2(x1, x2, &mut z1, &mut z2, pool);
1173 let mut a1 = vec![0.0f32; nv];
1174 let mut a2 = vec![0.0f32; nv];
1175 w.in_proj_a.matvec2(x1, x2, &mut a1, &mut a2, pool);
1176 let mut b1 = vec![0.0f32; nv];
1177 let mut b2 = vec![0.0f32; nv];
1178 w.in_proj_b.matvec2(x1, x2, &mut b1, &mut b2, pool);
1179
1180 let mut of1 = vec![0.0f32; vd];
1181 gdn_step(&qkv1, &z1, &a1, &b1, w, cfg, state, &mut of1, pool);
1182
1183 scratch.clear();
1184 scratch.extend_from_slice(state);
1185 let mut of2 = vec![0.0f32; vd];
1186 gdn_step(&qkv2, &z2, &a2, &b2, w, cfg, scratch, &mut of2, pool);
1187
1188 let mut out1 = vec![0.0f32; cfg.hidden_size];
1189 let mut out2 = vec![0.0f32; cfg.hidden_size];
1190 w.out_proj.matvec2(&of1, &of2, &mut out1, &mut out2, pool);
1191 (out1, out2)
1192}
1193
1194pub struct ShortConvWeights {
1201 pub in_proj: QTensor,
1203 pub conv: Vec<f32>,
1207 pub out_proj: QTensor,
1209}
1210
1211#[derive(Clone, Copy)]
1212pub struct ShortConvCfg {
1213 pub hidden_size: usize,
1214 pub kernel: usize,
1216}
1217
1218impl ShortConvCfg {
1219 pub fn state_len(&self) -> usize {
1221 (self.kernel - 1) * self.hidden_size
1222 }
1223}
1224
1225fn short_conv_step(
1235 bcx: &[f32],
1236 conv: &[f32],
1237 cfg: &ShortConvCfg,
1238 ring_state: &mut [f32],
1239 y: &mut [f32],
1240) {
1241 let (h, k) = (cfg.hidden_size, cfg.kernel);
1242 let ring = k - 1;
1243 let (bg, cg, xg) = (&bcx[0..h], &bcx[h..2 * h], &bcx[2 * h..3 * h]);
1244 for c in 0..h {
1245 let bx = bg[c] * xg[c];
1246 let wc = &conv[c * k..(c + 1) * k];
1247 let mut acc = wc[k - 1] * bx;
1249 let rc = &mut ring_state[c * ring..c * ring + ring];
1250 for s in 0..ring {
1251 acc += wc[k - 2 - s] * rc[s];
1252 }
1253 y[c] = cg[c] * acc;
1254 for s in (1..ring).rev() {
1256 rc[s] = rc[s - 1];
1257 }
1258 if ring > 0 {
1259 rc[0] = bx;
1260 }
1261 }
1262}
1263
1264pub fn short_conv_forward(
1266 x: &[f32],
1267 w: &ShortConvWeights,
1268 cfg: &ShortConvCfg,
1269 state: &mut Vec<f32>,
1270 pool: Option<&Pool>,
1271) -> Vec<f32> {
1272 if state.len() != cfg.state_len() {
1273 *state = vec![0f32; cfg.state_len()];
1274 }
1275 let h = cfg.hidden_size;
1276 let mut bcx = vec![0.0f32; 3 * h];
1277 w.in_proj.matvec(x, &mut bcx, pool);
1278 let mut y = vec![0.0f32; h];
1279 short_conv_step(&bcx, &w.conv, cfg, state, &mut y);
1280 let mut out = vec![0.0f32; h];
1281 w.out_proj.matvec(&y, &mut out, pool);
1282 out
1283}
1284
1285pub fn short_conv_forward_batch(
1290 xs: &[f32],
1291 b: usize,
1292 w: &ShortConvWeights,
1293 cfg: &ShortConvCfg,
1294 state: &mut Vec<f32>,
1295 pool: Option<&Pool>,
1296) -> Vec<f32> {
1297 if state.len() != cfg.state_len() {
1298 *state = vec![0f32; cfg.state_len()];
1299 }
1300 let h = cfg.hidden_size;
1301 let mut bcx = vec![0.0f32; b * 3 * h];
1302 w.in_proj.matmat(xs, b, &mut bcx, pool);
1303 let mut y = vec![0.0f32; b * h];
1304 for bi in 0..b {
1305 short_conv_step(
1306 &bcx[bi * 3 * h..(bi + 1) * 3 * h],
1307 &w.conv,
1308 cfg,
1309 state,
1310 &mut y[bi * h..(bi + 1) * h],
1311 );
1312 }
1313 let mut out = vec![0.0f32; b * h];
1314 w.out_proj.matmat(&y, b, &mut out, pool);
1315 out
1316}
1317
1318#[allow(clippy::too_many_arguments)]
1323pub fn short_conv_pair(
1324 x1: &[f32],
1325 x2: &[f32],
1326 w: &ShortConvWeights,
1327 cfg: &ShortConvCfg,
1328 state: &mut Vec<f32>,
1329 scratch: &mut Vec<f32>,
1330 pool: Option<&Pool>,
1331) -> (Vec<f32>, Vec<f32>) {
1332 if state.len() != cfg.state_len() {
1333 *state = vec![0f32; cfg.state_len()];
1334 }
1335 let h = cfg.hidden_size;
1336 let mut bcx1 = vec![0.0f32; 3 * h];
1337 let mut bcx2 = vec![0.0f32; 3 * h];
1338 w.in_proj.matvec2(x1, x2, &mut bcx1, &mut bcx2, pool);
1339
1340 let mut y1 = vec![0.0f32; h];
1341 short_conv_step(&bcx1, &w.conv, cfg, state, &mut y1);
1342 scratch.clear();
1343 scratch.extend_from_slice(state);
1344 let mut y2 = vec![0.0f32; h];
1345 short_conv_step(&bcx2, &w.conv, cfg, scratch, &mut y2);
1346
1347 let mut out1 = vec![0.0f32; h];
1348 let mut out2 = vec![0.0f32; h];
1349 w.out_proj.matvec2(&y1, &y2, &mut out1, &mut out2, pool);
1350 (out1, out2)
1351}
1352
1353pub struct KdaWeights {
1364 pub q_proj: QTensor,
1366 pub k_proj: QTensor,
1368 pub v_proj: QTensor,
1370 pub conv_q: Vec<f32>,
1372 pub conv_k: Vec<f32>,
1373 pub conv_v: Vec<f32>,
1375 pub f_a: QTensor,
1377 pub f_b: QTensor,
1379 pub dt_bias: Vec<f32>,
1381 pub a_log: Vec<f32>,
1384 pub b_proj: QTensor,
1386 pub gate: KdaOutGate,
1388 pub o_norm: Vec<f32>,
1390 pub o_proj: QTensor,
1392 pub gate_lower_bound: Option<f32>,
1395}
1396
1397pub enum KdaOutGate {
1398 Full(QTensor),
1400 LowRank(QTensor, QTensor),
1402}
1403
1404#[derive(Clone, Copy)]
1405pub struct KdaCfg {
1406 pub num_heads: usize,
1407 pub head_k_dim: usize,
1408 pub head_v_dim: usize,
1409 pub conv_kernel: usize,
1410 pub hidden_size: usize,
1411 pub rms_eps: f64,
1412}
1413
1414impl KdaCfg {
1415 pub fn state_len(&self) -> usize {
1418 let (nh, dk, dv, kk) = (
1419 self.num_heads,
1420 self.head_k_dim,
1421 self.head_v_dim,
1422 self.conv_kernel,
1423 );
1424 (kk - 1) * (2 * nh * dk + nh * dv) + nh * dk * dv
1425 }
1426}
1427
1428fn kda_conv(raw: &[f32], taps: &[f32], ring: &mut [f32], kk: usize, out: &mut [f32]) {
1431 let c_dim = raw.len();
1432 for c in 0..c_dim {
1433 let t = &taps[c * kk..(c + 1) * kk];
1434 let mut acc = raw[c] as f64 * t[kk - 1] as f64;
1435 for j in 0..kk - 1 {
1436 acc += ring[j * c_dim + c] as f64 * t[j] as f64;
1437 }
1438 out[c] = silu(acc) as f32;
1439 }
1440 if kk > 1 {
1441 ring.copy_within(c_dim.., 0);
1442 let tail = (kk - 2) * c_dim;
1443 ring[tail..tail + c_dim].copy_from_slice(raw);
1444 }
1445}
1446
1447#[inline]
1450fn kda_log_decay(w: &KdaWeights, cfg: &KdaCfg, h: usize, d: usize, f: f32) -> f64 {
1451 let (nh, dk) = (cfg.num_heads, cfg.head_k_dim);
1452 let a = if w.a_log.len() == nh {
1453 w.a_log[h] as f64
1454 } else if w.a_log.len() == dk {
1455 w.a_log[d] as f64
1456 } else {
1457 w.a_log[h * dk + d] as f64
1458 };
1459 let raw = f as f64 + w.dt_bias[h * dk + d] as f64;
1460 match w.gate_lower_bound {
1461 Some(lb) => lb as f64 * sigmoid(a.exp() * raw),
1462 None => -a.exp() * softplus(raw),
1463 }
1464}
1465
1466#[allow(clippy::too_many_arguments)]
1474fn kda_step(
1475 xq: &[f32],
1476 xk: &[f32],
1477 xv: &[f32],
1478 f: &[f32],
1479 b: &[f32],
1480 gate_out: &[f32],
1481 w: &KdaWeights,
1482 cfg: &KdaCfg,
1483 state: &mut [f32],
1484 of: &mut [f32],
1485 pool: Option<&Pool>,
1486) {
1487 let (nh, dk, dv, kk) = (
1488 cfg.num_heads,
1489 cfg.head_k_dim,
1490 cfg.head_v_dim,
1491 cfg.conv_kernel,
1492 );
1493 let (kd, vd) = (nh * dk, nh * dv);
1494 let ring_q_len = (kk - 1) * kd;
1495 let ring_v_len = (kk - 1) * vd;
1496 let (ring_q, rest) = state.split_at_mut(ring_q_len);
1497 let (ring_k, rest) = rest.split_at_mut(ring_q_len);
1498 let (ring_v, s_all) = rest.split_at_mut(ring_v_len);
1499
1500 let mut cq = vec![0f32; kd];
1501 let mut ck = vec![0f32; kd];
1502 let mut cv = vec![0f32; vd];
1503 kda_conv(xq, &w.conv_q, ring_q, kk, &mut cq);
1504 kda_conv(xk, &w.conv_k, ring_k, kk, &mut ck);
1505 kda_conv(xv, &w.conv_v, ring_v, kk, &mut cv);
1506
1507 let (cq, ck, cv) = (&cq, &ck, &cv);
1508 let s_ptr = SendMutF32(s_all.as_mut_ptr());
1509 let of_ptr = SendMutF32(of.as_mut_ptr());
1510 let head_range = |h0: usize, h1: usize| {
1511 let (s_ptr, of_ptr) = (s_ptr, of_ptr);
1512 let mut kv = crate::attention::take_buf(dv);
1513 let mut delta = crate::attention::take_buf(dv);
1514 let mut o = crate::attention::take_buf(dv);
1515 let mut kf = crate::attention::take_buf(dk);
1516 let mut qf = crate::attention::take_buf(dk);
1517 let mut gd = crate::attention::take_buf(dk);
1518 for h in h0..h1 {
1519 let qs = h * dk;
1520 let (mut nq, mut nkn) = (0f64, 0f64);
1522 for d in 0..dk {
1523 nq += (cq[qs + d] as f64) * (cq[qs + d] as f64);
1524 nkn += (ck[qs + d] as f64) * (ck[qs + d] as f64);
1525 }
1526 let invq = (1.0 / ((nq + 1e-6).sqrt() * (dk as f64).sqrt())) as f32;
1527 let invk = (1.0 / (nkn + 1e-6).sqrt()) as f32;
1528 for d in 0..dk {
1529 qf[d] = cq[qs + d] * invq;
1530 kf[d] = ck[qs + d] * invk;
1531 gd[d] = kda_log_decay(w, cfg, h, d, f[qs + d]).exp() as f32;
1532 }
1533 let beta = sigmoid(b[h] as f64) as f32;
1534
1535 let s = unsafe { std::slice::from_raw_parts_mut(s_ptr.0.add(h * dk * dv), dk * dv) };
1537 let oh = unsafe { std::slice::from_raw_parts_mut(of_ptr.0.add(h * dv), dv) };
1538 let vt = &cv[h * dv..(h + 1) * dv];
1539
1540 kv[..dv].fill(0.0);
1542 for di in 0..dk {
1543 let kg = kf[di] * gd[di];
1544 let row = &s[di * dv..(di + 1) * dv];
1545 for dj in 0..dv {
1546 kv[dj] += row[dj] * kg;
1547 }
1548 }
1549 for dj in 0..dv {
1550 delta[dj] = (vt[dj] - kv[dj]) * beta;
1551 }
1552 o[..dv].fill(0.0);
1554 for di in 0..dk {
1555 let (kfd, qfd, gdd) = (kf[di], qf[di], gd[di]);
1556 let row = &mut s[di * dv..(di + 1) * dv];
1557 for dj in 0..dv {
1558 let cell = gdd * row[dj] + kfd * delta[dj];
1559 row[dj] = cell;
1560 o[dj] += qfd * cell;
1561 }
1562 }
1563 let ss: f64 = o[..dv].iter().map(|&v| (v as f64) * (v as f64)).sum();
1565 let inv = 1.0 / (ss / dv as f64 + cfg.rms_eps).sqrt();
1566 for dj in 0..dv {
1567 oh[dj] = ((o[dj] as f64 * inv)
1568 * w.o_norm[dj] as f64
1569 * sigmoid(gate_out[h * dv + dj] as f64)) as f32;
1570 }
1571 }
1572 crate::attention::recycle_buf(&mut kv);
1573 crate::attention::recycle_buf(&mut delta);
1574 crate::attention::recycle_buf(&mut o);
1575 crate::attention::recycle_buf(&mut kf);
1576 crate::attention::recycle_buf(&mut qf);
1577 crate::attention::recycle_buf(&mut gd);
1578 };
1579 match pool {
1580 Some(pool) if nh >= 4 => pool.run(&|widx, n| {
1581 let chunk = nh.div_ceil(n);
1582 let h0 = (widx * chunk).min(nh);
1583 let h1 = (h0 + chunk).min(nh);
1584 if h0 < h1 {
1585 head_range(h0, h1);
1586 }
1587 }),
1588 _ => head_range(0, nh),
1589 }
1590}
1591
1592fn kda_gate_out(w: &KdaWeights, x: &[f32], vd: usize, pool: Option<&Pool>) -> Vec<f32> {
1595 let mut g = vec![0.0f32; vd];
1596 match &w.gate {
1597 KdaOutGate::Full(gp) => gp.matvec(x, &mut g, pool),
1598 KdaOutGate::LowRank(ga, gb) => {
1599 let mut low = vec![0.0f32; ga.rows()];
1600 ga.matvec(x, &mut low, pool);
1601 gb.matvec(&low, &mut g, pool);
1602 }
1603 }
1604 g
1605}
1606
1607pub fn kda_forward(
1609 x: &[f32],
1610 w: &KdaWeights,
1611 cfg: &KdaCfg,
1612 state: &mut Vec<f32>,
1613 pool: Option<&Pool>,
1614) -> Vec<f32> {
1615 if state.len() != cfg.state_len() {
1616 *state = vec![0f32; cfg.state_len()];
1617 }
1618 let (nh, dk, dv) = (cfg.num_heads, cfg.head_k_dim, cfg.head_v_dim);
1619 let (kd, vd) = (nh * dk, nh * dv);
1620
1621 let mut xq = vec![0.0f32; kd];
1622 let mut xk = vec![0.0f32; kd];
1623 let mut xv = vec![0.0f32; vd];
1624 let mut fl = vec![0.0f32; w.f_a.rows()];
1625 let mut b = vec![0.0f32; nh];
1626 let q4_group = [&w.q_proj, &w.k_proj, &w.v_proj, &w.f_a, &w.b_proj];
1631 let q4_uniform = q4_group.iter().all(|t| {
1632 matches!(
1633 t,
1634 QTensor::Mapped {
1635 dtype: TensorDtype::Q4TiledP,
1636 ..
1637 }
1638 )
1639 });
1640 if q4_uniform {
1641 QTensor::matvec_many(
1642 q4_group,
1643 x,
1644 [
1645 xq.as_mut_slice(),
1646 xk.as_mut_slice(),
1647 xv.as_mut_slice(),
1648 fl.as_mut_slice(),
1649 b.as_mut_slice(),
1650 ],
1651 pool,
1652 );
1653 } else {
1654 QTensor::matvec_many(
1655 [&w.q_proj, &w.k_proj, &w.v_proj, &w.f_a],
1656 x,
1657 [
1658 xq.as_mut_slice(),
1659 xk.as_mut_slice(),
1660 xv.as_mut_slice(),
1661 fl.as_mut_slice(),
1662 ],
1663 pool,
1664 );
1665 w.b_proj.matvec(x, &mut b, pool);
1666 }
1667 let mut f = vec![0.0f32; kd];
1668 w.f_b.matvec(&fl, &mut f, pool);
1669 let gate_out = kda_gate_out(w, x, vd, pool);
1670
1671 let mut of = vec![0.0f32; vd];
1672 kda_step(
1673 &xq, &xk, &xv, &f, &b, &gate_out, w, cfg, state, &mut of, pool,
1674 );
1675
1676 let mut out = vec![0.0f32; cfg.hidden_size];
1677 w.o_proj.matvec(&of, &mut out, pool);
1678 out
1679}
1680
1681pub fn kda_forward_batch(
1685 xs: &[f32],
1686 bsz: usize,
1687 w: &KdaWeights,
1688 cfg: &KdaCfg,
1689 state: &mut Vec<f32>,
1690 pool: Option<&Pool>,
1691) -> Vec<f32> {
1692 if state.len() != cfg.state_len() {
1693 *state = vec![0f32; cfg.state_len()];
1694 }
1695 let (nh, dk, dv, hs) = (
1696 cfg.num_heads,
1697 cfg.head_k_dim,
1698 cfg.head_v_dim,
1699 cfg.hidden_size,
1700 );
1701 let (kd, vd) = (nh * dk, nh * dv);
1702
1703 let mut xq = vec![0.0f32; bsz * kd];
1704 w.q_proj.matmat(xs, bsz, &mut xq, pool);
1705 let mut xk = vec![0.0f32; bsz * kd];
1706 w.k_proj.matmat(xs, bsz, &mut xk, pool);
1707 let mut xv = vec![0.0f32; bsz * vd];
1708 w.v_proj.matmat(xs, bsz, &mut xv, pool);
1709 let rank = w.f_a.rows();
1710 let mut fl = vec![0.0f32; bsz * rank];
1711 w.f_a.matmat(xs, bsz, &mut fl, pool);
1712 let mut f = vec![0.0f32; bsz * kd];
1713 w.f_b.matmat(&fl, bsz, &mut f, pool);
1714 let mut b = vec![0.0f32; bsz * nh];
1715 w.b_proj.matmat(xs, bsz, &mut b, pool);
1716 let mut gate_out = vec![0.0f32; bsz * vd];
1717 match &w.gate {
1718 KdaOutGate::Full(gp) => gp.matmat(xs, bsz, &mut gate_out, pool),
1719 KdaOutGate::LowRank(ga, gb) => {
1720 let mut low = vec![0.0f32; bsz * ga.rows()];
1721 ga.matmat(xs, bsz, &mut low, pool);
1722 gb.matmat(&low, bsz, &mut gate_out, pool);
1723 }
1724 }
1725
1726 let mut of = vec![0.0f32; bsz * vd];
1727 for bi in 0..bsz {
1728 let mut oh = vec![0.0f32; vd];
1729 kda_step(
1730 &xq[bi * kd..(bi + 1) * kd],
1731 &xk[bi * kd..(bi + 1) * kd],
1732 &xv[bi * vd..(bi + 1) * vd],
1733 &f[bi * kd..(bi + 1) * kd],
1734 &b[bi * nh..(bi + 1) * nh],
1735 &gate_out[bi * vd..(bi + 1) * vd],
1736 w,
1737 cfg,
1738 state,
1739 &mut oh,
1740 pool,
1741 );
1742 of[bi * vd..(bi + 1) * vd].copy_from_slice(&oh);
1743 }
1744
1745 let mut out = vec![0.0f32; bsz * hs];
1746 w.o_proj.matmat(&of, bsz, &mut out, pool);
1747 out
1748}
1749
1750#[cfg(test)]
1751mod tests {
1752 #[test]
1753 fn kda_forward_matches_naive_reference() {
1754 let (nh, dk, dv, kk, hs, rank) = (2usize, 4usize, 4usize, 3usize, 6usize, 3usize);
1760 let synth = |rows: usize, cols: usize, salt: usize| -> QTensor {
1761 QTensor::from_f32(
1762 (0..rows * cols)
1763 .map(|i| (((i * 31 + salt * 17) % 101) as f32 / 101.0 - 0.5) * 0.6)
1764 .collect(),
1765 rows,
1766 cols,
1767 )
1768 };
1769 let vecf = |n: usize, salt: usize| -> Vec<f32> {
1770 (0..n)
1771 .map(|i| (((i * 13 + salt * 7) % 89) as f32 / 89.0 - 0.5) * 0.8)
1772 .collect()
1773 };
1774 for (label, a_log, lb) in [
1775 ("per-head standard", vecf(nh, 40), None),
1776 ("per-dim lower-bound", vecf(dk, 41), Some(-5.0f32)),
1777 ] {
1778 let w = KdaWeights {
1779 q_proj: synth(nh * dk, hs, 1),
1780 k_proj: synth(nh * dk, hs, 2),
1781 v_proj: synth(nh * dv, hs, 3),
1782 conv_q: vecf(nh * dk * kk, 4),
1783 conv_k: vecf(nh * dk * kk, 5),
1784 conv_v: vecf(nh * dv * kk, 6),
1785 f_a: synth(rank, hs, 7),
1786 f_b: synth(nh * dk, rank, 8),
1787 dt_bias: vecf(nh * dk, 9),
1788 a_log: a_log.clone(),
1789 b_proj: synth(nh, hs, 10),
1790 gate: KdaOutGate::LowRank(synth(rank, hs, 11), synth(nh * dv, rank, 12)),
1791 o_norm: (0..dv).map(|i| 1.0 + 0.1 * i as f32).collect(),
1792 o_proj: synth(hs, nh * dv, 13),
1793 gate_lower_bound: lb,
1794 };
1795 let cfg = KdaCfg {
1796 num_heads: nh,
1797 head_k_dim: dk,
1798 head_v_dim: dv,
1799 conv_kernel: kk,
1800 hidden_size: hs,
1801 rms_eps: 1e-6,
1802 };
1803 let xs: Vec<Vec<f32>> = (0..6)
1804 .map(|t| {
1805 (0..hs)
1806 .map(|i| ((t * hs + i) as f32 * 0.37).sin() * 0.5)
1807 .collect()
1808 })
1809 .collect();
1810
1811 let mut state = Vec::new();
1813 let got: Vec<Vec<f32>> = xs
1814 .iter()
1815 .map(|x| kda_forward(x, &w, &cfg, &mut state, None))
1816 .collect();
1817
1818 let mv = |t: &QTensor, x: &[f32]| -> Vec<f32> {
1820 let mut o = vec![0.0f32; t.rows()];
1821 t.matvec(x, &mut o, None);
1822 o
1823 };
1824 let mut hist: Vec<(Vec<f32>, Vec<f32>, Vec<f32>)> = Vec::new(); let mut s_state = vec![0f64; nh * dk * dv];
1826 let mut want: Vec<Vec<f32>> = Vec::new();
1827 for x in &xs {
1828 let (xq, xk, xv) = (mv(&w.q_proj, x), mv(&w.k_proj, x), mv(&w.v_proj, x));
1829 hist.push((xq, xk, xv));
1830 let conv = |sel: fn(&(Vec<f32>, Vec<f32>, Vec<f32>)) -> &Vec<f32>,
1832 taps: &[f32],
1833 n: usize|
1834 -> Vec<f32> {
1835 (0..n)
1836 .map(|c| {
1837 let t = &taps[c * kk..(c + 1) * kk];
1838 let mut acc = 0f64;
1839 for j in 0..kk {
1840 let idx = hist.len() as i64 - (kk as i64 - j as i64);
1841 if idx >= 0 {
1842 acc += sel(&hist[idx as usize])[c] as f64 * t[j] as f64;
1843 }
1844 }
1845 silu(acc)
1846 })
1847 .map(|v| v as f32)
1848 .collect()
1849 };
1850 let cq = conv(|h| &h.0, &w.conv_q, nh * dk);
1851 let ck = conv(|h| &h.1, &w.conv_k, nh * dk);
1852 let cv = conv(|h| &h.2, &w.conv_v, nh * dv);
1853 let f = mv(&w.f_b, &mv(&w.f_a, x));
1854 let bb = mv(&w.b_proj, x);
1855 let gate_out = match &w.gate {
1856 KdaOutGate::LowRank(ga, gb) => mv(gb, &mv(ga, x)),
1857 KdaOutGate::Full(g) => mv(g, x),
1858 };
1859 let mut of = vec![0f32; nh * dv];
1860 for h in 0..nh {
1861 let q: Vec<f64> = {
1863 let sl = &cq[h * dk..(h + 1) * dk];
1864 let n: f64 = sl.iter().map(|&v| (v as f64) * (v as f64)).sum();
1865 let inv = 1.0 / ((n + 1e-6).sqrt() * (dk as f64).sqrt());
1866 sl.iter().map(|&v| v as f64 * inv).collect()
1867 };
1868 let k: Vec<f64> = {
1869 let sl = &ck[h * dk..(h + 1) * dk];
1870 let n: f64 = sl.iter().map(|&v| (v as f64) * (v as f64)).sum();
1871 let inv = 1.0 / (n + 1e-6).sqrt();
1872 sl.iter().map(|&v| v as f64 * inv).collect()
1873 };
1874 let v: Vec<f64> = cv[h * dv..(h + 1) * dv].iter().map(|&v| v as f64).collect();
1875 let g: Vec<f64> = (0..dk)
1877 .map(|d| {
1878 let a = if w.a_log.len() == nh {
1879 w.a_log[h] as f64
1880 } else {
1881 w.a_log[d] as f64
1882 };
1883 let raw = f[h * dk + d] as f64 + w.dt_bias[h * dk + d] as f64;
1884 match w.gate_lower_bound {
1885 Some(lb) => lb as f64 * sigmoid(a.exp() * raw),
1886 None => -a.exp() * softplus(raw),
1887 }
1888 })
1889 .collect();
1890 let beta = sigmoid(bb[h] as f64);
1891 let s = &mut s_state[h * dk * dv..(h + 1) * dk * dv];
1892 for di in 0..dk {
1894 for dj in 0..dv {
1895 s[di * dv + dj] *= g[di].exp();
1896 }
1897 }
1898 let mut kv = vec![0f64; dv];
1900 for di in 0..dk {
1901 for dj in 0..dv {
1902 kv[dj] += k[di] * s[di * dv + dj];
1903 }
1904 }
1905 for di in 0..dk {
1906 for dj in 0..dv {
1907 s[di * dv + dj] += beta * k[di] * (v[dj] - kv[dj]);
1908 }
1909 }
1910 let mut o = vec![0f64; dv];
1911 for di in 0..dk {
1912 for dj in 0..dv {
1913 o[dj] += q[di] * s[di * dv + dj];
1914 }
1915 }
1916 let ss: f64 = o.iter().map(|&v| v * v).sum();
1918 let inv = 1.0 / (ss / dv as f64 + cfg.rms_eps).sqrt();
1919 for dj in 0..dv {
1920 of[h * dv + dj] = (o[dj]
1921 * inv
1922 * w.o_norm[dj] as f64
1923 * sigmoid(gate_out[h * dv + dj] as f64))
1924 as f32;
1925 }
1926 }
1927 want.push(mv(&w.o_proj, &of));
1928 }
1929
1930 for (t, (g, e)) in got.iter().zip(&want).enumerate() {
1931 for (i, (a, b)) in g.iter().zip(e.iter()).enumerate() {
1932 assert!((a - b).abs() < 2e-4, "{label}: t={t} i={i}: {a} vs {b}");
1933 }
1934 }
1935 }
1936
1937 let w = KdaWeights {
1939 q_proj: synth(nh * dk, hs, 1),
1940 k_proj: synth(nh * dk, hs, 2),
1941 v_proj: synth(nh * dv, hs, 3),
1942 conv_q: vecf(nh * dk * kk, 4),
1943 conv_k: vecf(nh * dk * kk, 5),
1944 conv_v: vecf(nh * dv * kk, 6),
1945 f_a: synth(rank, hs, 7),
1946 f_b: synth(nh * dk, rank, 8),
1947 dt_bias: vecf(nh * dk, 9),
1948 a_log: vecf(nh, 40),
1949 b_proj: synth(nh, hs, 10),
1950 gate: KdaOutGate::LowRank(synth(rank, hs, 11), synth(nh * dv, rank, 12)),
1951 o_norm: (0..dv).map(|i| 1.0 + 0.1 * i as f32).collect(),
1952 o_proj: synth(hs, nh * dv, 13),
1953 gate_lower_bound: None,
1954 };
1955 let cfg = KdaCfg {
1956 num_heads: nh,
1957 head_k_dim: dk,
1958 head_v_dim: dv,
1959 conv_kernel: kk,
1960 hidden_size: hs,
1961 rms_eps: 1e-6,
1962 };
1963 let xs: Vec<f32> = (0..5 * hs).map(|i| (i as f32 * 0.29).cos() * 0.4).collect();
1964 let mut st1 = Vec::new();
1965 let seq: Vec<f32> = (0..5)
1966 .flat_map(|t| kda_forward(&xs[t * hs..(t + 1) * hs], &w, &cfg, &mut st1, None))
1967 .collect();
1968 let mut st2 = Vec::new();
1969 let bat = kda_forward_batch(&xs, 5, &w, &cfg, &mut st2, None);
1970 for (i, (a, b)) in seq.iter().zip(&bat).enumerate() {
1971 assert!((a - b).abs() < 1e-5, "batch i={i}: {a} vs {b}");
1972 }
1973 assert_eq!(st1, st2, "state must match after the chunk");
1974 }
1975
1976 use super::*;
1977
1978 fn tiny() -> (VmfPhaseWeights, VmfPhaseCfg) {
1979 let cfg = VmfPhaseCfg {
1980 num_heads: 2,
1981 nphase: 3,
1982 value_head_dim: 4,
1983 hidden_size: 8,
1984 phase_mass: 0.0,
1985 };
1986 let synth = |rows: usize, cols: usize, salt: usize| {
1987 QTensor::from_f32(
1988 (0..rows * cols)
1989 .map(|i| (((i * 13 + salt * 7) % 97) as f32 / 97.0 - 0.5) * 0.4)
1990 .collect(),
1991 rows,
1992 cols,
1993 )
1994 };
1995 let w = VmfPhaseWeights {
1996 thq: synth(cfg.num_heads * cfg.nphase, cfg.hidden_size, 1),
1997 thk: synth(cfg.num_heads * cfg.nphase, cfg.hidden_size, 2),
1998 v_proj: synth(cfg.num_heads * cfg.value_head_dim, cfg.hidden_size, 3),
1999 out_proj: synth(cfg.hidden_size, cfg.num_heads * cfg.value_head_dim, 4),
2000 decay: (0..cfg.num_heads * 2 * cfg.nphase)
2001 .map(|i| 0.9 + 0.005 * (i % 10) as f64)
2002 .collect(),
2003 conv: None,
2004 k_gate: None,
2005 phase_delta: false,
2006 };
2007 (w, cfg)
2008 }
2009
2010 #[test]
2011 fn state_persists_and_changes_output() {
2012 let (w, cfg) = tiny();
2013 let x: Vec<f32> = (0..8).map(|i| (i as f32 * 0.3).sin()).collect();
2014 let mut state = Vec::new();
2015 let o1 = vmf_phase_forward(&x, &w, &cfg, &mut state, None);
2016 let o2 = vmf_phase_forward(&x, &w, &cfg, &mut state, None);
2017 assert!(o1.iter().zip(&o2).any(|(a, b)| (a - b).abs() > 1e-6));
2019 assert_eq!(state.len(), cfg.state_len());
2020 }
2021
2022 #[test]
2023 fn phase_delta_matches_independent_token_oracle_and_ignores_phase_mass() {
2024 let cfg = VmfPhaseCfg {
2029 num_heads: 1,
2030 nphase: 2,
2031 value_head_dim: 1,
2032 hidden_size: 2,
2033 phase_mass: 17.0,
2035 };
2036 let w = VmfPhaseWeights {
2037 thq: QTensor::from_f32(vec![1.0, 0.0, 0.0, 1.0], 2, 2),
2038 thk: QTensor::from_f32(vec![0.0, 1.0, 1.0, 0.0], 2, 2),
2039 v_proj: QTensor::from_f32(vec![1.0, 0.0], 1, 2),
2040 out_proj: QTensor::from_f32(vec![1.0, 0.0], 2, 1),
2041 decay: vec![0.9, 0.8, 0.7, 0.6],
2042 conv: None,
2043 k_gate: None, phase_delta: true,
2045 };
2046 let xs = [[0.3f32, 0.7f32], [-0.2, 0.4], [0.8, -0.6]];
2047 let c = 1.0f64 / 2.0f64.sqrt();
2048 let mut oracle_state = [0.0f64; 4];
2049 let mut state = Vec::new();
2050 for x in xs {
2051 let q = [
2052 c * (x[0] as f64).cos(),
2053 c * (x[1] as f64).cos(),
2054 c * (x[0] as f64).sin(),
2055 c * (x[1] as f64).sin(),
2056 ];
2057 let k = [
2058 c * (x[1] as f64).cos(),
2059 c * (x[0] as f64).cos(),
2060 c * (x[1] as f64).sin(),
2061 c * (x[0] as f64).sin(),
2062 ];
2063 let value = x[0] as f64;
2064 let mut read = 0.0;
2065 for f in 0..4 {
2066 oracle_state[f] *= w.decay[f];
2067 read += k[f] * oracle_state[f];
2068 }
2069 for f in 0..4 {
2070 oracle_state[f] += k[f] * (value - read);
2071 }
2072 let want: f32 = q
2073 .iter()
2074 .zip(oracle_state)
2075 .map(|(qf, sf)| (qf * sf) as f32)
2076 .sum();
2077 let got = vmf_phase_forward(&x, &w, &cfg, &mut state, None);
2078 assert!(
2079 (got[0] - want).abs() < 2e-6,
2080 "oracle {want} vs runtime {}",
2081 got[0]
2082 );
2083 assert!(got[1].abs() < 2e-7);
2084 }
2085 assert_eq!(state.len(), cfg.state_len());
2086 for (got, want) in state.iter().zip(oracle_state) {
2087 assert!((*got as f64 - want).abs() < 2e-6);
2088 }
2089 }
2090
2091 #[test]
2092 fn phase_delta_fused_readout_is_bit_exact_over_long_continuation() {
2093 for (nh, nph, dv) in [(1, 1, 1), (3, 5, 7), (8, 32, 128)] {
2096 let cfg = VmfPhaseCfg {
2097 num_heads: nh,
2098 nphase: nph,
2099 value_head_dim: dv,
2100 hidden_size: nh * dv,
2101 phase_mass: 19.0,
2102 };
2103 let p2 = 2 * nph;
2104 let scale = 1.0f64 / (nph as f64).sqrt();
2105 let mut state: Vec<f32> = (0..cfg.state_len() + 11)
2106 .map(|i| (i as f32 * 0.37).sin())
2107 .collect();
2108 let mut reference = state.clone();
2109 let tail = state[cfg.state_len()..].to_vec();
2110 let decay: Vec<f64> = (0..nh * p2).map(|i| [0.0, 0.8, 0.99, 1.0][i % 4]).collect();
2111 for step in 0..128 {
2112 let q: Vec<f32> = (0..nh * nph)
2113 .map(|i| ((i + step * 3) as f32 * 0.13).sin() * 4.0)
2114 .collect();
2115 let k: Vec<f32> = (0..nh * nph)
2116 .map(|i| ((i + step * 7) as f32 * 0.29).cos() * 4.0)
2117 .collect();
2118 let v: Vec<f32> = (0..nh * dv)
2119 .map(|i| ((i + step) as f32 * 0.43).sin())
2120 .collect();
2121 let gates: Vec<f32> = (0..nh).map(|h| [0.0, 0.3, 1.0][(step + h) % 3]).collect();
2122 let kap = (step % 4 != 0).then_some(gates.as_slice());
2123 let mut out = vec![0.125f32; nh * dv];
2124 let mut want = out.clone();
2125 for h in 0..nh {
2126 let feature = |theta: &[f32], f: usize| {
2127 let angle = theta[h * nph + f % nph] as f64;
2128 scale * if f < nph { angle.cos() } else { angle.sin() }
2129 };
2130 let beta = kap.map_or(1.0f64, |g| g[h] as f64);
2131 let mut read = vec![0.0f64; dv];
2132 for f in 0..p2 {
2133 for d in 0..dv {
2134 let at = (h * p2 + f) * dv + d;
2135 read[d] += feature(&k, f) * (decay[h * p2 + f] * reference[at] as f64);
2136 }
2137 }
2138 for f in 0..p2 {
2139 for d in 0..dv {
2140 let at = (h * p2 + f) * dv + d;
2141 reference[at] = (decay[h * p2 + f] * reference[at] as f64
2142 + beta * feature(&k, f) * (v[h * dv + d] as f64 - read[d]))
2143 as f32;
2144 }
2145 }
2146 for f in 0..p2 {
2147 for d in 0..dv {
2148 want[h * dv + d] +=
2149 (feature(&q, f) * reference[(h * p2 + f) * dv + d] as f64) as f32;
2150 }
2151 }
2152 }
2153 phase_delta_step_f32(&q, &k, &v, &decay, kap, &cfg, &mut state, &mut out);
2154 assert_eq!(out, want, "shape {nh}/{nph}/{dv}, step {step}");
2155 assert_eq!(state, reference, "state at step {step}");
2156 assert_eq!(&state[cfg.state_len()..], tail);
2157 }
2158 }
2159 }
2160
2161 #[test]
2162 fn phase_delta_pair_reset_and_legacy_paths_are_distinct() {
2163 let (mut delta, mut cfg) = tiny();
2164 delta.phase_delta = true;
2165 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.2).cos()).collect();
2168 let x2: Vec<f32> = (0..8).map(|i| (i as f32 * 0.5).sin()).collect();
2169 let mut seq_state = Vec::new();
2170 let d1 = vmf_phase_forward(&x1, &delta, &cfg, &mut seq_state, None);
2171 let d2 = vmf_phase_forward(&x2, &delta, &cfg, &mut seq_state, None);
2172
2173 let mut pair_state = Vec::new();
2174 let mut scratch = Vec::new();
2175 let (p1, p2) = vmf_phase_pair(&x1, &x2, &delta, &cfg, &mut pair_state, &mut scratch, None);
2176 assert_eq!(d1, p1);
2177 assert_eq!(d2, p2);
2178 std::mem::swap(&mut pair_state, &mut scratch);
2179 assert_eq!(seq_state, pair_state);
2180
2181 let mut reset = seq_state;
2184 reset.clear();
2185 let after_reset = vmf_phase_forward(&x1, &delta, &cfg, &mut reset, None);
2186 assert_eq!(after_reset, d1);
2187
2188 cfg.phase_mass = 0.0;
2189 let mut legacy = delta;
2190 legacy.phase_delta = false;
2191 let mut legacy_state = Vec::new();
2192 let old = vmf_phase_forward(&x1, &legacy, &cfg, &mut legacy_state, None);
2193 assert!(
2194 old.iter().zip(&d1).any(|(a, b)| (a - b).abs() > 1e-5),
2195 "legacy additive and normalized Phase-Delta paths must differ"
2196 );
2197 }
2198
2199 #[test]
2203 fn phase_mass_zero_is_noop_and_positive_shifts() {
2204 let (w, cfg0) = tiny();
2205 let mut cfg_m = cfg0;
2206 cfg_m.phase_mass = 1.0;
2207 let x: Vec<f32> = (0..8).map(|i| (i as f32 * 0.4).sin()).collect();
2208
2209 let mut s0 = Vec::new();
2210 let base = vmf_phase_forward(&x, &w, &cfg0, &mut s0, None);
2211 let mut s0b = Vec::new();
2213 let base2 = vmf_phase_forward(&x, &w, &cfg0, &mut s0b, None);
2214 assert_eq!(base, base2, "mass=0 must be deterministic/no-op");
2215 let mut sm = Vec::new();
2217 let massed = vmf_phase_forward(&x, &w, &cfg_m, &mut sm, None);
2218 assert!(
2219 base.iter().zip(&massed).any(|(a, b)| (a - b).abs() > 1e-5),
2220 "mass>0 must change the output"
2221 );
2222 assert!(massed.iter().all(|v| v.is_finite()));
2223 }
2224
2225 #[test]
2230 fn kappa_gate_open_matches_none_and_closed_writes_nothing() {
2231 let (mut w, cfg) = tiny();
2232 let x: Vec<f32> = (0..8).map(|i| (i as f32 * 0.3).sin()).collect();
2233
2234 let mut s_none = Vec::new();
2235 let base1 = vmf_phase_forward(&x, &w, &cfg, &mut s_none, None);
2236 let base2 = vmf_phase_forward(&x, &w, &cfg, &mut s_none, None);
2237
2238 w.k_gate = Some((
2240 QTensor::from_f32(
2241 vec![0.0; cfg.num_heads * cfg.hidden_size],
2242 cfg.num_heads,
2243 cfg.hidden_size,
2244 ),
2245 vec![20.0; cfg.num_heads],
2246 ));
2247 let mut s_open = Vec::new();
2248 let o1 = vmf_phase_forward(&x, &w, &cfg, &mut s_open, None);
2249 let o2 = vmf_phase_forward(&x, &w, &cfg, &mut s_open, None);
2250 for (a, b) in base1.iter().zip(&o1).chain(base2.iter().zip(&o2)) {
2251 assert!(
2252 (a - b).abs() < 1e-5,
2253 "open κ must match gateless: {a} vs {b}"
2254 );
2255 }
2256
2257 w.k_gate = Some((
2259 QTensor::from_f32(
2260 vec![0.0; cfg.num_heads * cfg.hidden_size],
2261 cfg.num_heads,
2262 cfg.hidden_size,
2263 ),
2264 vec![-20.0; cfg.num_heads],
2265 ));
2266 let mut s_closed = Vec::new();
2267 let oc = vmf_phase_forward(&x, &w, &cfg, &mut s_closed, None);
2268 assert!(
2269 s_closed.iter().all(|&v| v.abs() < 1e-7),
2270 "closed κ: state must stay empty"
2271 );
2272 assert!(
2273 oc.iter().all(|&v| v.abs() < 1e-6),
2274 "closed κ: empty-state readout"
2275 );
2276 }
2277
2278 #[test]
2279 fn pair_matches_two_singles_bitexact() {
2280 let (w, cfg) = tiny();
2281 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.2).cos()).collect();
2282 let x2: Vec<f32> = (0..8).map(|i| (i as f32 * 0.5).sin()).collect();
2283
2284 let mut s_ref = Vec::new();
2286 let r1 = vmf_phase_forward(&x1, &w, &cfg, &mut s_ref, None);
2287 let r2 = vmf_phase_forward(&x2, &w, &cfg, &mut s_ref, None);
2288
2289 let mut s = Vec::new();
2291 let mut scratch = Vec::new();
2292 let (p1, p2) = vmf_phase_pair(&x1, &x2, &w, &cfg, &mut s, &mut scratch, None);
2293 assert_eq!(r1, p1, "lane 1 must be bit-identical");
2294 assert_eq!(r2, p2, "lane 2 must be bit-identical");
2295 std::mem::swap(&mut s, &mut scratch);
2297 assert_eq!(s, s_ref, "accepted state must equal sequential state");
2298 }
2299
2300 #[test]
2301 fn rejected_draft_leaves_state_at_lane1() {
2302 let (w, cfg) = tiny();
2303 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.7).sin()).collect();
2304 let x2 = vec![0.5f32; 8];
2305
2306 let mut s_ref = Vec::new();
2307 let _ = vmf_phase_forward(&x1, &w, &cfg, &mut s_ref, None);
2308
2309 let mut s = Vec::new();
2310 let mut scratch = Vec::new();
2311 let _ = vmf_phase_pair(&x1, &x2, &w, &cfg, &mut s, &mut scratch, None);
2312 assert_eq!(s, s_ref);
2314 }
2315
2316 fn tiny_gdn() -> (GdnWeights, GdnCfg) {
2319 let cfg = GdnCfg {
2320 num_v_heads: 4,
2321 num_k_heads: 2,
2322 key_head_dim: 3,
2323 value_head_dim: 5,
2324 conv_kernel: 4,
2325 hidden_size: 8,
2326 rms_eps: 1e-6,
2327 output_gate_sigmoid: false,
2328 };
2329 let c_dim = cfg.conv_dim();
2330 let vd = cfg.num_v_heads * cfg.value_head_dim;
2331 let synth = |rows: usize, cols: usize, salt: usize| {
2332 QTensor::from_f32(
2333 (0..rows * cols)
2334 .map(|i| (((i * 13 + salt * 7) % 97) as f32 / 97.0 - 0.5) * 0.4)
2335 .collect(),
2336 rows,
2337 cols,
2338 )
2339 };
2340 let vecf = |n: usize, salt: usize| -> Vec<f32> {
2341 (0..n)
2342 .map(|i| (((i * 11 + salt * 5) % 89) as f32 / 89.0 - 0.5) * 0.6)
2343 .collect()
2344 };
2345 let w = GdnWeights {
2346 in_proj_qkv: synth(c_dim, cfg.hidden_size, 1),
2347 in_proj_z: synth(vd, cfg.hidden_size, 2),
2348 in_proj_a: synth(cfg.num_v_heads, cfg.hidden_size, 3),
2349 in_proj_b: synth(cfg.num_v_heads, cfg.hidden_size, 4),
2350 conv1d: vecf(c_dim * cfg.conv_kernel, 5),
2351 a_log: (0..cfg.num_v_heads).map(|i| 0.2 + 0.3 * i as f32).collect(),
2352 dt_bias: vecf(cfg.num_v_heads, 6),
2353 norm: vec![1.0; cfg.value_head_dim],
2354 out_proj: synth(cfg.hidden_size, vd, 7),
2355 };
2356 (w, cfg)
2357 }
2358
2359 #[test]
2363 fn gdn_scan_matches_steps_bitexact() {
2364 for sigmoid_gate in [false, true] {
2365 let (w, mut cfg) = tiny_gdn();
2366 cfg.output_gate_sigmoid = sigmoid_gate;
2367 let (c_dim, vd, nv) = (cfg.conv_dim(), cfg.num_v_heads * cfg.value_head_dim, cfg.num_v_heads);
2368 let row = |n: usize, t: usize, salt: usize| -> Vec<f32> {
2369 (0..n).map(|i| (((i * 7 + t * 31 + salt * 3) % 83) as f32 / 83.0 - 0.5) * 1.3).collect()
2370 };
2371 let mut s_ref = vec![0f32; cfg.state_len()];
2372 let mut s_scan = s_ref.clone();
2373 for (chunk, b) in [(0usize, 2usize), (1, 7), (2, 1), (3, 5)] {
2374 let qkv: Vec<f32> = (0..b).flat_map(|t| row(c_dim, t + 10 * chunk, 1)).collect();
2375 let z: Vec<f32> = (0..b).flat_map(|t| row(vd, t + 10 * chunk, 2)).collect();
2376 let a: Vec<f32> = (0..b).flat_map(|t| row(nv, t + 10 * chunk, 3)).collect();
2377 let bb: Vec<f32> = (0..b).flat_map(|t| row(nv, t + 10 * chunk, 4)).collect();
2378 let mut of_ref = vec![0f32; b * vd];
2379 for t in 0..b {
2380 gdn_step(
2381 &qkv[t * c_dim..(t + 1) * c_dim],
2382 &z[t * vd..(t + 1) * vd],
2383 &a[t * nv..(t + 1) * nv],
2384 &bb[t * nv..(t + 1) * nv],
2385 &w,
2386 &cfg,
2387 &mut s_ref,
2388 &mut of_ref[t * vd..(t + 1) * vd],
2389 None,
2390 );
2391 }
2392 let mut of_scan = vec![0f32; b * vd];
2393 gdn_scan_batch(&qkv, &z, &a, &bb, b, &w, &cfg, &mut s_scan, &mut of_scan, None);
2394 assert_eq!(of_scan, of_ref, "outputs, chunk {chunk} (b = {b})");
2395 assert_eq!(s_scan, s_ref, "state, chunk {chunk} (b = {b})");
2396 }
2397 }
2398 }
2399
2400 #[test]
2401 fn gdn_state_persists_and_changes_output() {
2402 let (w, cfg) = tiny_gdn();
2403 let x: Vec<f32> = (0..8).map(|i| (i as f32 * 0.3).sin()).collect();
2404 let mut state = Vec::new();
2405 let o1 = gdn_forward(&x, &w, &cfg, &mut state, None);
2406 let o2 = gdn_forward(&x, &w, &cfg, &mut state, None);
2407 assert!(o1.iter().zip(&o2).any(|(a, b)| (a - b).abs() > 1e-6));
2408 assert_eq!(state.len(), cfg.state_len());
2409 }
2410
2411 #[test]
2412 fn gdn_pair_matches_two_singles_bitexact() {
2413 let (w, cfg) = tiny_gdn();
2414 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.2).cos()).collect();
2415 let x2: Vec<f32> = (0..8).map(|i| (i as f32 * 0.5).sin()).collect();
2416
2417 let mut s_ref = Vec::new();
2418 let r1 = gdn_forward(&x1, &w, &cfg, &mut s_ref, None);
2419 let r2 = gdn_forward(&x2, &w, &cfg, &mut s_ref, None);
2420
2421 let mut s = Vec::new();
2422 let mut scratch = Vec::new();
2423 let (p1, p2) = gdn_pair(&x1, &x2, &w, &cfg, &mut s, &mut scratch, None);
2424 assert_eq!(r1, p1, "lane 1 must be bit-identical");
2425 assert_eq!(r2, p2, "lane 2 must be bit-identical");
2426 std::mem::swap(&mut s, &mut scratch);
2427 assert_eq!(s, s_ref, "accepted state must equal sequential state");
2428 }
2429
2430 #[test]
2431 fn gdn_rejected_draft_leaves_state_at_lane1() {
2432 let (w, cfg) = tiny_gdn();
2433 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.7).sin()).collect();
2434 let x2 = vec![0.5f32; 8];
2435
2436 let mut s_ref = Vec::new();
2437 let _ = gdn_forward(&x1, &w, &cfg, &mut s_ref, None);
2438
2439 let mut s = Vec::new();
2440 let mut scratch = Vec::new();
2441 let _ = gdn_pair(&x1, &x2, &w, &cfg, &mut s, &mut scratch, None);
2442 assert_eq!(s, s_ref);
2443 }
2444
2445 #[test]
2449 fn gdn_conv_ring_matches_explicit_causal_conv() {
2450 let (w, cfg) = tiny_gdn();
2451 let seq: Vec<Vec<f32>> = (0..6)
2452 .map(|t| (0..8).map(|i| ((t * 8 + i) as f32 * 0.17).sin()).collect())
2453 .collect();
2454
2455 let mut s_inc = Vec::new();
2458 for (t, x) in seq.iter().enumerate() {
2459 let inc = gdn_forward(x, &w, &cfg, &mut s_inc, None);
2460 let mut s_replay = Vec::new();
2461 let mut replay = Vec::new();
2462 for xr in &seq[..=t] {
2463 replay = gdn_forward(xr, &w, &cfg, &mut s_replay, None);
2464 }
2465 assert_eq!(inc, replay, "position {t}: ring must equal replay");
2466 }
2467 }
2468
2469 fn tiny_short_conv() -> (ShortConvWeights, ShortConvCfg) {
2470 let cfg = ShortConvCfg {
2471 hidden_size: 8,
2472 kernel: 3,
2473 };
2474 let synth = |rows: usize, cols: usize, salt: usize| {
2475 QTensor::from_f32(
2476 (0..rows * cols)
2477 .map(|i| (((i * 11 + salt * 5) % 89) as f32 / 89.0 - 0.5) * 0.5)
2478 .collect(),
2479 rows,
2480 cols,
2481 )
2482 };
2483 let w = ShortConvWeights {
2484 in_proj: synth(3 * cfg.hidden_size, cfg.hidden_size, 1),
2485 conv: (0..cfg.hidden_size * cfg.kernel)
2486 .map(|i| ((i * 7 % 13) as f32 / 13.0 - 0.5) * 0.8)
2487 .collect(),
2488 out_proj: synth(cfg.hidden_size, cfg.hidden_size, 2),
2489 };
2490 (w, cfg)
2491 }
2492
2493 #[test]
2496 fn short_conv_ring_matches_explicit_causal_conv() {
2497 let (w, cfg) = tiny_short_conv();
2498 let seq: Vec<Vec<f32>> = (0..6)
2499 .map(|t| (0..8).map(|i| ((t * 8 + i) as f32 * 0.19).cos()).collect())
2500 .collect();
2501 let mut s_inc = Vec::new();
2502 for (t, x) in seq.iter().enumerate() {
2503 let inc = short_conv_forward(x, &w, &cfg, &mut s_inc, None);
2504 let mut s_replay = Vec::new();
2505 let mut replay = Vec::new();
2506 for xr in &seq[..=t] {
2507 replay = short_conv_forward(xr, &w, &cfg, &mut s_replay, None);
2508 }
2509 assert_eq!(inc, replay, "position {t}: ring must equal replay");
2510 assert_eq!(s_inc.len(), cfg.state_len());
2511 }
2512 }
2513
2514 #[test]
2517 fn short_conv_batch_matches_sequential() {
2518 let (w, cfg) = tiny_short_conv();
2519 let b = 5;
2520 let xs: Vec<f32> = (0..b * cfg.hidden_size)
2521 .map(|i| (i as f32 * 0.13).sin() * 0.6)
2522 .collect();
2523
2524 let mut s_seq = Vec::new();
2525 let mut seq_out = vec![0.0f32; b * cfg.hidden_size];
2526 for bi in 0..b {
2527 let o = short_conv_forward(
2528 &xs[bi * cfg.hidden_size..(bi + 1) * cfg.hidden_size],
2529 &w,
2530 &cfg,
2531 &mut s_seq,
2532 None,
2533 );
2534 seq_out[bi * cfg.hidden_size..(bi + 1) * cfg.hidden_size].copy_from_slice(&o);
2535 }
2536
2537 let mut s_batch = Vec::new();
2538 let batch_out = short_conv_forward_batch(&xs, b, &w, &cfg, &mut s_batch, None);
2539 assert_eq!(
2540 seq_out, batch_out,
2541 "batch conv must match sequential decode"
2542 );
2543 assert_eq!(s_seq, s_batch, "ring state must match after the chunk");
2544 }
2545}