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