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}
335
336impl GdnCfg {
337 pub fn conv_dim(&self) -> usize {
338 2 * self.num_k_heads * self.key_head_dim + self.num_v_heads * self.value_head_dim
339 }
340
341 pub fn state_len(&self) -> usize {
344 (self.conv_kernel - 1) * self.conv_dim()
345 + self.num_v_heads * self.key_head_dim * self.value_head_dim
346 }
347}
348
349fn softplus(x: f64) -> f64 {
350 if x > 20.0 { x } else { x.exp().ln_1p() }
351}
352
353fn sigmoid(x: f64) -> f64 {
354 1.0 / (1.0 + (-x).exp())
355}
356
357fn silu(x: f64) -> f64 {
358 x / (1.0 + (-x).exp())
359}
360
361#[derive(Clone, Copy)]
364struct SendMutF32(*mut f32);
365unsafe impl Send for SendMutF32 {}
366unsafe impl Sync for SendMutF32 {}
367
368#[allow(clippy::too_many_arguments)]
381fn gdn_step(
382 qkv: &[f32],
383 z: &[f32],
384 a: &[f32],
385 b: &[f32],
386 w: &GdnWeights,
387 cfg: &GdnCfg,
388 state: &mut [f32],
389 of: &mut [f32],
390 pool: Option<&Pool>,
391) {
392 let (nv, nk, dk, dv, kk) = (
393 cfg.num_v_heads,
394 cfg.num_k_heads,
395 cfg.key_head_dim,
396 cfg.value_head_dim,
397 cfg.conv_kernel,
398 );
399 let c_dim = cfg.conv_dim();
400 let (kd, rep) = (nk * dk, nv / nk);
401 let (ring, s_all) = state.split_at_mut((kk - 1) * c_dim);
402
403 let mut cq = vec![0f32; c_dim];
407 for c in 0..c_dim {
408 let taps = &w.conv1d[c * kk..(c + 1) * kk];
409 let mut acc = qkv[c] as f64 * taps[kk - 1] as f64;
410 for j in 0..kk - 1 {
411 acc += ring[j * c_dim + c] as f64 * taps[j] as f64;
412 }
413 cq[c] = silu(acc) as f32;
414 }
415 if kk > 1 {
417 ring.copy_within(c_dim.., 0);
418 let tail = (kk - 2) * c_dim;
419 ring[tail..tail + c_dim].copy_from_slice(&qkv[..c_dim]);
420 }
421
422 let cq = &cq;
423 let s_ptr = SendMutF32(s_all.as_mut_ptr());
424 let of_ptr = SendMutF32(of.as_mut_ptr());
425 let head_range = |h0: usize, h1: usize| {
426 let (s_ptr, of_ptr) = (s_ptr, of_ptr);
429 let mut kv = crate::attention::take_buf(dv);
431 let mut delta = crate::attention::take_buf(dv);
432 let mut o = crate::attention::take_buf(dv);
433 let mut kf = crate::attention::take_buf(dk);
434 let mut qf = crate::attention::take_buf(dk);
435 for h in h0..h1 {
436 let ko = h / rep; let (qs, ks) = (ko * dk, kd + ko * dk);
438 let (mut nq, mut nkn) = (0f64, 0f64);
440 for d in 0..dk {
441 nq += (cq[qs + d] as f64) * (cq[qs + d] as f64);
442 nkn += (cq[ks + d] as f64) * (cq[ks + d] as f64);
443 }
444 let invq = (1.0 / ((nq + 1e-6).sqrt() * (dk as f64).sqrt())) as f32;
445 let invk = (1.0 / (nkn + 1e-6).sqrt()) as f32;
446 for d in 0..dk {
447 qf[d] = cq[qs + d] * invq;
448 kf[d] = cq[ks + d] * invk;
449 }
450
451 let g = (-(w.a_log[h] as f64).exp() * softplus(a[h] as f64 + w.dt_bias[h] as f64)).exp()
452 as f32;
453 let beta = sigmoid(b[h] as f64) as f32;
454
455 let s = unsafe { std::slice::from_raw_parts_mut(s_ptr.0.add(h * dk * dv), dk * dv) };
457 let oh = unsafe { std::slice::from_raw_parts_mut(of_ptr.0.add(h * dv), dv) };
458 let vt = &cq[2 * kd + h * dv..2 * kd + (h + 1) * dv];
459
460 kv[..dv].fill(0.0);
464 for di in 0..dk {
465 let kfd = kf[di];
466 let row = &s[di * dv..(di + 1) * dv];
467 for dj in 0..dv {
468 kv[dj] += row[dj] * kfd; }
470 }
471 for dj in 0..dv {
472 delta[dj] = (vt[dj] - g * kv[dj]) * beta;
473 }
474 o[..dv].fill(0.0);
475 for di in 0..dk {
476 let kfd = kf[di];
477 let qfd = qf[di];
478 let row = &mut s[di * dv..(di + 1) * dv];
479 for dj in 0..dv {
480 let cell = g * row[dj] + kfd * delta[dj];
481 row[dj] = cell;
482 o[dj] += qfd * cell; }
484 }
485 let ss: f64 = o[..dv].iter().map(|&v| (v as f64) * (v as f64)).sum();
487 let inv = 1.0 / (ss / dv as f64 + cfg.rms_eps).sqrt();
488 for dj in 0..dv {
489 oh[dj] =
490 ((o[dj] as f64 * inv) * w.norm[dj] as f64 * silu(z[h * dv + dj] as f64)) as f32;
491 }
492 }
493 crate::attention::recycle_buf(&mut kv);
494 crate::attention::recycle_buf(&mut delta);
495 crate::attention::recycle_buf(&mut o);
496 crate::attention::recycle_buf(&mut kf);
497 crate::attention::recycle_buf(&mut qf);
498 };
499 match pool {
500 Some(pool) if nv >= 4 => pool.run(&|widx, n| {
501 let chunk = nv.div_ceil(n);
502 let h0 = (widx * chunk).min(nv);
503 let h1 = (h0 + chunk).min(nv);
504 if h0 < h1 {
505 head_range(h0, h1);
506 }
507 }),
508 _ => head_range(0, nv),
509 }
510}
511
512pub fn gdn_forward(
514 x: &[f32],
515 w: &GdnWeights,
516 cfg: &GdnCfg,
517 state: &mut Vec<f32>,
518 pool: Option<&Pool>,
519) -> Vec<f32> {
520 if state.len() != cfg.state_len() {
521 *state = vec![0f32; cfg.state_len()];
522 }
523 let (c_dim, vd) = (cfg.conv_dim(), cfg.num_v_heads * cfg.value_head_dim);
524
525 let mut qkv = vec![0.0f32; c_dim];
526 let mut z = vec![0.0f32; vd];
527 let mut a = vec![0.0f32; cfg.num_v_heads];
528 let mut b = vec![0.0f32; cfg.num_v_heads];
529 let cpu_projs = |qkv: &mut Vec<f32>, z: &mut Vec<f32>, a: &mut Vec<f32>, b: &mut Vec<f32>| {
533 QTensor::matvec_many(
534 [&w.in_proj_qkv, &w.in_proj_z, &w.in_proj_a, &w.in_proj_b],
535 x,
536 [
537 qkv.as_mut_slice(),
538 z.as_mut_slice(),
539 a.as_mut_slice(),
540 b.as_mut_slice(),
541 ],
542 pool,
543 );
544 };
545 let mut done = false;
546 if crate::gpu::enabled_here() && gdn_projs_eligible(w) {
547 match crate::gpu::probe_arm(crate::gpu::OpClass::Batch) {
548 crate::gpu::ProbeArm::Gpu => {
549 let t0 = std::time::Instant::now();
550 if gdn_projs_gpu(w, x, &mut qkv, &mut z) {
551 crate::gpu::probe_record(crate::gpu::OpClass::Batch, true, t0.elapsed());
552 w.in_proj_a.matvec(x, &mut a, pool);
553 w.in_proj_b.matvec(x, &mut b, pool);
554 done = true;
555 } else {
556 crate::gpu::probe_note_decline(crate::gpu::OpClass::Batch);
557 }
558 }
559 crate::gpu::ProbeArm::CpuTimed => {
560 let t0 = std::time::Instant::now();
561 crate::gpu::cpu_scope(|| cpu_projs(&mut qkv, &mut z, &mut a, &mut b));
562 crate::gpu::probe_record(crate::gpu::OpClass::Batch, false, t0.elapsed());
563 done = true;
564 }
565 crate::gpu::ProbeArm::Cpu => {
566 crate::gpu::cpu_scope(|| cpu_projs(&mut qkv, &mut z, &mut a, &mut b));
567 done = true;
568 }
569 }
570 }
571 if !done {
572 cpu_projs(&mut qkv, &mut z, &mut a, &mut b);
573 }
574
575 let mut of = vec![0.0f32; vd];
576 gdn_step(&qkv, &z, &a, &b, w, cfg, state, &mut of, pool);
577
578 let mut out = vec![0.0f32; cfg.hidden_size];
579 w.out_proj.matvec(&of, &mut out, pool);
580 out
581}
582
583pub fn gdn_forward_batch(
588 xs: &[f32],
589 b: usize,
590 w: &GdnWeights,
591 cfg: &GdnCfg,
592 state: &mut Vec<f32>,
593 pool: Option<&Pool>,
594) -> Vec<f32> {
595 if state.len() != cfg.state_len() {
596 *state = vec![0f32; cfg.state_len()];
597 }
598 let (c_dim, vd) = (cfg.conv_dim(), cfg.num_v_heads * cfg.value_head_dim);
599 let nv = cfg.num_v_heads;
600
601 let mut qkv = vec![0.0f32; b * c_dim];
602 w.in_proj_qkv.matmat(xs, b, &mut qkv, pool);
603 let mut z = vec![0.0f32; b * vd];
604 w.in_proj_z.matmat(xs, b, &mut z, pool);
605 let mut a = vec![0.0f32; b * nv];
606 w.in_proj_a.matmat(xs, b, &mut a, pool);
607 let mut bb = vec![0.0f32; b * nv];
608 w.in_proj_b.matmat(xs, b, &mut bb, pool);
609
610 let mut of = vec![0.0f32; b * vd];
611 for bi in 0..b {
612 gdn_step(
613 &qkv[bi * c_dim..(bi + 1) * c_dim],
614 &z[bi * vd..(bi + 1) * vd],
615 &a[bi * nv..(bi + 1) * nv],
616 &bb[bi * nv..(bi + 1) * nv],
617 w,
618 cfg,
619 state,
620 &mut of[bi * vd..(bi + 1) * vd],
621 pool,
622 );
623 }
624 let mut out = vec![0.0f32; b * cfg.hidden_size];
625 w.out_proj.matmat(&of, b, &mut out, pool);
626 if std::env::var("CMF_GDN_TRACE").is_ok() {
627 let n = |v: &[f32]| v.iter().map(|x| x * x).sum::<f32>().sqrt();
628 eprintln!(
629 "gdn-batch b={b}: |x0|={:.5} |qkv0|={:.5} |z0|={:.5} |a0|={:.5} |b0|={:.5} |of0|={:.5} |out0|={:.5} |state|={:.5}",
630 n(&xs[..cfg.hidden_size]), n(&qkv[..c_dim]), n(&z[..vd]), n(&a[..nv]), n(&bb[..nv]), n(&of[..vd]), n(&out[..cfg.hidden_size]), n(state)
631 );
632 }
633 out
634}
635
636fn gdn_projs_eligible(w: &GdnWeights) -> bool {
640 if std::env::var("CMF_GPU_GDN")
646 .map(|v| v == "0")
647 .unwrap_or(false)
648 {
649 return false;
650 }
651 w.in_proj_qkv.is_q1()
652 || w.in_proj_qkv.q4t_parts().is_some()
653 || w.in_proj_qkv.q4tp_parts().is_some()
654 || std::env::var("CMF_GPU_GDN")
655 .map(|v| v == "1")
656 .unwrap_or(false)
657}
658
659fn gdn_projs_gpu(w: &GdnWeights, x: &[f32], qkv: &mut [f32], z: &mut [f32]) -> bool {
661 use crate::gpu::matvec_batch;
662 use crate::qtensor::QTensor;
663 if !crate::gpu::enabled_here() {
664 return false;
665 }
666 fn part<'a>(
667 t: &'a QTensor,
668 x: &[f32],
669 ) -> Option<(
670 std::sync::Arc<cortiq_core::CmfModel>,
671 crate::gpu::BatchJob<'a>,
672 )> {
673 use crate::gpu::BatchJob;
674 use crate::qtensor::prescale;
675 use cortiq_core::TensorDtype;
676 match t {
677 QTensor::Mapped {
678 model,
679 idx,
680 dtype: dt @ (TensorDtype::Q8Row | TensorDtype::Q8_2f),
681 rows,
682 cols,
683 row_scale,
684 col_field,
685 ..
686 } => Some((
687 model.clone(),
688 BatchJob {
689 idx: *idx,
690 rows: *rows,
691 cols: *cols,
692 row_scale,
693 xs: prescale(x, col_field, *dt).into_owned(),
694 layout: crate::gpu::BatchLayout::Q8,
695 },
696 )),
697 QTensor::Mapped {
698 model,
699 idx,
700 dtype: TensorDtype::Q1,
701 rows,
702 cols,
703 ..
704 } => Some((
705 model.clone(),
706 BatchJob {
707 idx: *idx,
708 rows: *rows,
709 cols: *cols,
710 row_scale: &[],
711 xs: x.to_vec(),
712 layout: crate::gpu::BatchLayout::Q1,
713 },
714 )),
715 QTensor::Mapped {
719 model,
720 idx,
721 dtype: dt @ (TensorDtype::Q4Tiled | TensorDtype::Q4TiledP),
722 rows,
723 cols,
724 ..
725 } => Some((
726 model.clone(),
727 BatchJob {
728 idx: *idx,
729 rows: *rows,
730 cols: *cols,
731 row_scale: &[],
732 xs: x.to_vec(),
733 layout: if *dt == TensorDtype::Q4TiledP {
734 crate::gpu::BatchLayout::Q4tp
735 } else {
736 crate::gpu::BatchLayout::Q4t
737 },
738 },
739 )),
740 _ => None,
741 }
742 }
743 let Some((model, jq)) = part(&w.in_proj_qkv, x) else {
744 return false;
745 };
746 let Some((_, jz)) = part(&w.in_proj_z, x) else {
747 return false;
748 };
749 matvec_batch(&model, &[jq, jz], &mut [qkv, z])
750}
751
752#[allow(clippy::too_many_arguments)]
755pub fn gdn_pair(
756 x1: &[f32],
757 x2: &[f32],
758 w: &GdnWeights,
759 cfg: &GdnCfg,
760 state: &mut Vec<f32>,
761 scratch: &mut Vec<f32>,
762 pool: Option<&Pool>,
763) -> (Vec<f32>, Vec<f32>) {
764 if state.len() != cfg.state_len() {
765 *state = vec![0f32; cfg.state_len()];
766 }
767 let (c_dim, vd, nv) = (
768 cfg.conv_dim(),
769 cfg.num_v_heads * cfg.value_head_dim,
770 cfg.num_v_heads,
771 );
772
773 let mut qkv1 = vec![0.0f32; c_dim];
774 let mut qkv2 = vec![0.0f32; c_dim];
775 w.in_proj_qkv.matvec2(x1, x2, &mut qkv1, &mut qkv2, pool);
776 let mut z1 = vec![0.0f32; vd];
777 let mut z2 = vec![0.0f32; vd];
778 w.in_proj_z.matvec2(x1, x2, &mut z1, &mut z2, pool);
779 let mut a1 = vec![0.0f32; nv];
780 let mut a2 = vec![0.0f32; nv];
781 w.in_proj_a.matvec2(x1, x2, &mut a1, &mut a2, pool);
782 let mut b1 = vec![0.0f32; nv];
783 let mut b2 = vec![0.0f32; nv];
784 w.in_proj_b.matvec2(x1, x2, &mut b1, &mut b2, pool);
785
786 let mut of1 = vec![0.0f32; vd];
787 gdn_step(&qkv1, &z1, &a1, &b1, w, cfg, state, &mut of1, pool);
788
789 scratch.clear();
790 scratch.extend_from_slice(state);
791 let mut of2 = vec![0.0f32; vd];
792 gdn_step(&qkv2, &z2, &a2, &b2, w, cfg, scratch, &mut of2, pool);
793
794 let mut out1 = vec![0.0f32; cfg.hidden_size];
795 let mut out2 = vec![0.0f32; cfg.hidden_size];
796 w.out_proj.matvec2(&of1, &of2, &mut out1, &mut out2, pool);
797 (out1, out2)
798}
799
800pub struct ShortConvWeights {
807 pub in_proj: QTensor,
809 pub conv: Vec<f32>,
813 pub out_proj: QTensor,
815}
816
817#[derive(Clone, Copy)]
818pub struct ShortConvCfg {
819 pub hidden_size: usize,
820 pub kernel: usize,
822}
823
824impl ShortConvCfg {
825 pub fn state_len(&self) -> usize {
827 (self.kernel - 1) * self.hidden_size
828 }
829}
830
831fn short_conv_step(
841 bcx: &[f32],
842 conv: &[f32],
843 cfg: &ShortConvCfg,
844 ring_state: &mut [f32],
845 y: &mut [f32],
846) {
847 let (h, k) = (cfg.hidden_size, cfg.kernel);
848 let ring = k - 1;
849 let (bg, cg, xg) = (&bcx[0..h], &bcx[h..2 * h], &bcx[2 * h..3 * h]);
850 for c in 0..h {
851 let bx = bg[c] * xg[c];
852 let wc = &conv[c * k..(c + 1) * k];
853 let mut acc = wc[k - 1] * bx;
855 let rc = &mut ring_state[c * ring..c * ring + ring];
856 for s in 0..ring {
857 acc += wc[k - 2 - s] * rc[s];
858 }
859 y[c] = cg[c] * acc;
860 for s in (1..ring).rev() {
862 rc[s] = rc[s - 1];
863 }
864 if ring > 0 {
865 rc[0] = bx;
866 }
867 }
868}
869
870pub fn short_conv_forward(
872 x: &[f32],
873 w: &ShortConvWeights,
874 cfg: &ShortConvCfg,
875 state: &mut Vec<f32>,
876 pool: Option<&Pool>,
877) -> Vec<f32> {
878 if state.len() != cfg.state_len() {
879 *state = vec![0f32; cfg.state_len()];
880 }
881 let h = cfg.hidden_size;
882 let mut bcx = vec![0.0f32; 3 * h];
883 w.in_proj.matvec(x, &mut bcx, pool);
884 let mut y = vec![0.0f32; h];
885 short_conv_step(&bcx, &w.conv, cfg, state, &mut y);
886 let mut out = vec![0.0f32; h];
887 w.out_proj.matvec(&y, &mut out, pool);
888 out
889}
890
891pub fn short_conv_forward_batch(
896 xs: &[f32],
897 b: usize,
898 w: &ShortConvWeights,
899 cfg: &ShortConvCfg,
900 state: &mut Vec<f32>,
901 pool: Option<&Pool>,
902) -> Vec<f32> {
903 if state.len() != cfg.state_len() {
904 *state = vec![0f32; cfg.state_len()];
905 }
906 let h = cfg.hidden_size;
907 let mut bcx = vec![0.0f32; b * 3 * h];
908 w.in_proj.matmat(xs, b, &mut bcx, pool);
909 let mut y = vec![0.0f32; b * h];
910 for bi in 0..b {
911 short_conv_step(
912 &bcx[bi * 3 * h..(bi + 1) * 3 * h],
913 &w.conv,
914 cfg,
915 state,
916 &mut y[bi * h..(bi + 1) * h],
917 );
918 }
919 let mut out = vec![0.0f32; b * h];
920 w.out_proj.matmat(&y, b, &mut out, pool);
921 out
922}
923
924#[allow(clippy::too_many_arguments)]
929pub fn short_conv_pair(
930 x1: &[f32],
931 x2: &[f32],
932 w: &ShortConvWeights,
933 cfg: &ShortConvCfg,
934 state: &mut Vec<f32>,
935 scratch: &mut Vec<f32>,
936 pool: Option<&Pool>,
937) -> (Vec<f32>, Vec<f32>) {
938 if state.len() != cfg.state_len() {
939 *state = vec![0f32; cfg.state_len()];
940 }
941 let h = cfg.hidden_size;
942 let mut bcx1 = vec![0.0f32; 3 * h];
943 let mut bcx2 = vec![0.0f32; 3 * h];
944 w.in_proj.matvec2(x1, x2, &mut bcx1, &mut bcx2, pool);
945
946 let mut y1 = vec![0.0f32; h];
947 short_conv_step(&bcx1, &w.conv, cfg, state, &mut y1);
948 scratch.clear();
949 scratch.extend_from_slice(state);
950 let mut y2 = vec![0.0f32; h];
951 short_conv_step(&bcx2, &w.conv, cfg, scratch, &mut y2);
952
953 let mut out1 = vec![0.0f32; h];
954 let mut out2 = vec![0.0f32; h];
955 w.out_proj.matvec2(&y1, &y2, &mut out1, &mut out2, pool);
956 (out1, out2)
957}
958
959pub struct KdaWeights {
970 pub q_proj: QTensor,
972 pub k_proj: QTensor,
974 pub v_proj: QTensor,
976 pub conv_q: Vec<f32>,
978 pub conv_k: Vec<f32>,
979 pub conv_v: Vec<f32>,
981 pub f_a: QTensor,
983 pub f_b: QTensor,
985 pub dt_bias: Vec<f32>,
987 pub a_log: Vec<f32>,
990 pub b_proj: QTensor,
992 pub gate: KdaOutGate,
994 pub o_norm: Vec<f32>,
996 pub o_proj: QTensor,
998 pub gate_lower_bound: Option<f32>,
1001}
1002
1003pub enum KdaOutGate {
1004 Full(QTensor),
1006 LowRank(QTensor, QTensor),
1008}
1009
1010#[derive(Clone, Copy)]
1011pub struct KdaCfg {
1012 pub num_heads: usize,
1013 pub head_k_dim: usize,
1014 pub head_v_dim: usize,
1015 pub conv_kernel: usize,
1016 pub hidden_size: usize,
1017 pub rms_eps: f64,
1018}
1019
1020impl KdaCfg {
1021 pub fn state_len(&self) -> usize {
1024 let (nh, dk, dv, kk) = (
1025 self.num_heads,
1026 self.head_k_dim,
1027 self.head_v_dim,
1028 self.conv_kernel,
1029 );
1030 (kk - 1) * (2 * nh * dk + nh * dv) + nh * dk * dv
1031 }
1032}
1033
1034fn kda_conv(raw: &[f32], taps: &[f32], ring: &mut [f32], kk: usize, out: &mut [f32]) {
1037 let c_dim = raw.len();
1038 for c in 0..c_dim {
1039 let t = &taps[c * kk..(c + 1) * kk];
1040 let mut acc = raw[c] as f64 * t[kk - 1] as f64;
1041 for j in 0..kk - 1 {
1042 acc += ring[j * c_dim + c] as f64 * t[j] as f64;
1043 }
1044 out[c] = silu(acc) as f32;
1045 }
1046 if kk > 1 {
1047 ring.copy_within(c_dim.., 0);
1048 let tail = (kk - 2) * c_dim;
1049 ring[tail..tail + c_dim].copy_from_slice(raw);
1050 }
1051}
1052
1053#[inline]
1056fn kda_log_decay(w: &KdaWeights, cfg: &KdaCfg, h: usize, d: usize, f: f32) -> f64 {
1057 let (nh, dk) = (cfg.num_heads, cfg.head_k_dim);
1058 let a = if w.a_log.len() == nh {
1059 w.a_log[h] as f64
1060 } else if w.a_log.len() == dk {
1061 w.a_log[d] as f64
1062 } else {
1063 w.a_log[h * dk + d] as f64
1064 };
1065 let raw = f as f64 + w.dt_bias[h * dk + d] as f64;
1066 match w.gate_lower_bound {
1067 Some(lb) => lb as f64 * sigmoid(a.exp() * raw),
1068 None => -a.exp() * softplus(raw),
1069 }
1070}
1071
1072#[allow(clippy::too_many_arguments)]
1080fn kda_step(
1081 xq: &[f32],
1082 xk: &[f32],
1083 xv: &[f32],
1084 f: &[f32],
1085 b: &[f32],
1086 gate_out: &[f32],
1087 w: &KdaWeights,
1088 cfg: &KdaCfg,
1089 state: &mut [f32],
1090 of: &mut [f32],
1091 pool: Option<&Pool>,
1092) {
1093 let (nh, dk, dv, kk) = (
1094 cfg.num_heads,
1095 cfg.head_k_dim,
1096 cfg.head_v_dim,
1097 cfg.conv_kernel,
1098 );
1099 let (kd, vd) = (nh * dk, nh * dv);
1100 let ring_q_len = (kk - 1) * kd;
1101 let ring_v_len = (kk - 1) * vd;
1102 let (ring_q, rest) = state.split_at_mut(ring_q_len);
1103 let (ring_k, rest) = rest.split_at_mut(ring_q_len);
1104 let (ring_v, s_all) = rest.split_at_mut(ring_v_len);
1105
1106 let mut cq = vec![0f32; kd];
1107 let mut ck = vec![0f32; kd];
1108 let mut cv = vec![0f32; vd];
1109 kda_conv(xq, &w.conv_q, ring_q, kk, &mut cq);
1110 kda_conv(xk, &w.conv_k, ring_k, kk, &mut ck);
1111 kda_conv(xv, &w.conv_v, ring_v, kk, &mut cv);
1112
1113 let (cq, ck, cv) = (&cq, &ck, &cv);
1114 let s_ptr = SendMutF32(s_all.as_mut_ptr());
1115 let of_ptr = SendMutF32(of.as_mut_ptr());
1116 let head_range = |h0: usize, h1: usize| {
1117 let (s_ptr, of_ptr) = (s_ptr, of_ptr);
1118 let mut kv = crate::attention::take_buf(dv);
1119 let mut delta = crate::attention::take_buf(dv);
1120 let mut o = crate::attention::take_buf(dv);
1121 let mut kf = crate::attention::take_buf(dk);
1122 let mut qf = crate::attention::take_buf(dk);
1123 let mut gd = crate::attention::take_buf(dk);
1124 for h in h0..h1 {
1125 let qs = h * dk;
1126 let (mut nq, mut nkn) = (0f64, 0f64);
1128 for d in 0..dk {
1129 nq += (cq[qs + d] as f64) * (cq[qs + d] as f64);
1130 nkn += (ck[qs + d] as f64) * (ck[qs + d] as f64);
1131 }
1132 let invq = (1.0 / ((nq + 1e-6).sqrt() * (dk as f64).sqrt())) as f32;
1133 let invk = (1.0 / (nkn + 1e-6).sqrt()) as f32;
1134 for d in 0..dk {
1135 qf[d] = cq[qs + d] * invq;
1136 kf[d] = ck[qs + d] * invk;
1137 gd[d] = kda_log_decay(w, cfg, h, d, f[qs + d]).exp() as f32;
1138 }
1139 let beta = sigmoid(b[h] as f64) as f32;
1140
1141 let s = unsafe { std::slice::from_raw_parts_mut(s_ptr.0.add(h * dk * dv), dk * dv) };
1143 let oh = unsafe { std::slice::from_raw_parts_mut(of_ptr.0.add(h * dv), dv) };
1144 let vt = &cv[h * dv..(h + 1) * dv];
1145
1146 kv[..dv].fill(0.0);
1148 for di in 0..dk {
1149 let kg = kf[di] * gd[di];
1150 let row = &s[di * dv..(di + 1) * dv];
1151 for dj in 0..dv {
1152 kv[dj] += row[dj] * kg;
1153 }
1154 }
1155 for dj in 0..dv {
1156 delta[dj] = (vt[dj] - kv[dj]) * beta;
1157 }
1158 o[..dv].fill(0.0);
1160 for di in 0..dk {
1161 let (kfd, qfd, gdd) = (kf[di], qf[di], gd[di]);
1162 let row = &mut s[di * dv..(di + 1) * dv];
1163 for dj in 0..dv {
1164 let cell = gdd * row[dj] + kfd * delta[dj];
1165 row[dj] = cell;
1166 o[dj] += qfd * cell;
1167 }
1168 }
1169 let ss: f64 = o[..dv].iter().map(|&v| (v as f64) * (v as f64)).sum();
1171 let inv = 1.0 / (ss / dv as f64 + cfg.rms_eps).sqrt();
1172 for dj in 0..dv {
1173 oh[dj] = ((o[dj] as f64 * inv)
1174 * w.o_norm[dj] as f64
1175 * sigmoid(gate_out[h * dv + dj] as f64)) as f32;
1176 }
1177 }
1178 crate::attention::recycle_buf(&mut kv);
1179 crate::attention::recycle_buf(&mut delta);
1180 crate::attention::recycle_buf(&mut o);
1181 crate::attention::recycle_buf(&mut kf);
1182 crate::attention::recycle_buf(&mut qf);
1183 crate::attention::recycle_buf(&mut gd);
1184 };
1185 match pool {
1186 Some(pool) if nh >= 4 => pool.run(&|widx, n| {
1187 let chunk = nh.div_ceil(n);
1188 let h0 = (widx * chunk).min(nh);
1189 let h1 = (h0 + chunk).min(nh);
1190 if h0 < h1 {
1191 head_range(h0, h1);
1192 }
1193 }),
1194 _ => head_range(0, nh),
1195 }
1196}
1197
1198fn kda_gate_out(w: &KdaWeights, x: &[f32], vd: usize, pool: Option<&Pool>) -> Vec<f32> {
1201 let mut g = vec![0.0f32; vd];
1202 match &w.gate {
1203 KdaOutGate::Full(gp) => gp.matvec(x, &mut g, pool),
1204 KdaOutGate::LowRank(ga, gb) => {
1205 let mut low = vec![0.0f32; ga.rows()];
1206 ga.matvec(x, &mut low, pool);
1207 gb.matvec(&low, &mut g, pool);
1208 }
1209 }
1210 g
1211}
1212
1213pub fn kda_forward(
1215 x: &[f32],
1216 w: &KdaWeights,
1217 cfg: &KdaCfg,
1218 state: &mut Vec<f32>,
1219 pool: Option<&Pool>,
1220) -> Vec<f32> {
1221 if state.len() != cfg.state_len() {
1222 *state = vec![0f32; cfg.state_len()];
1223 }
1224 let (nh, dk, dv) = (cfg.num_heads, cfg.head_k_dim, cfg.head_v_dim);
1225 let (kd, vd) = (nh * dk, nh * dv);
1226
1227 let mut xq = vec![0.0f32; kd];
1228 let mut xk = vec![0.0f32; kd];
1229 let mut xv = vec![0.0f32; vd];
1230 let mut fl = vec![0.0f32; w.f_a.rows()];
1231 let mut b = vec![0.0f32; nh];
1232 QTensor::matvec_many(
1233 [&w.q_proj, &w.k_proj, &w.v_proj, &w.f_a],
1234 x,
1235 [
1236 xq.as_mut_slice(),
1237 xk.as_mut_slice(),
1238 xv.as_mut_slice(),
1239 fl.as_mut_slice(),
1240 ],
1241 pool,
1242 );
1243 w.b_proj.matvec(x, &mut b, pool);
1244 let mut f = vec![0.0f32; kd];
1245 w.f_b.matvec(&fl, &mut f, pool);
1246 let gate_out = kda_gate_out(w, x, vd, pool);
1247
1248 let mut of = vec![0.0f32; vd];
1249 kda_step(
1250 &xq, &xk, &xv, &f, &b, &gate_out, w, cfg, state, &mut of, pool,
1251 );
1252
1253 let mut out = vec![0.0f32; cfg.hidden_size];
1254 w.o_proj.matvec(&of, &mut out, pool);
1255 out
1256}
1257
1258pub fn kda_forward_batch(
1262 xs: &[f32],
1263 bsz: usize,
1264 w: &KdaWeights,
1265 cfg: &KdaCfg,
1266 state: &mut Vec<f32>,
1267 pool: Option<&Pool>,
1268) -> Vec<f32> {
1269 if state.len() != cfg.state_len() {
1270 *state = vec![0f32; cfg.state_len()];
1271 }
1272 let (nh, dk, dv, hs) = (
1273 cfg.num_heads,
1274 cfg.head_k_dim,
1275 cfg.head_v_dim,
1276 cfg.hidden_size,
1277 );
1278 let (kd, vd) = (nh * dk, nh * dv);
1279
1280 let mut xq = vec![0.0f32; bsz * kd];
1281 w.q_proj.matmat(xs, bsz, &mut xq, pool);
1282 let mut xk = vec![0.0f32; bsz * kd];
1283 w.k_proj.matmat(xs, bsz, &mut xk, pool);
1284 let mut xv = vec![0.0f32; bsz * vd];
1285 w.v_proj.matmat(xs, bsz, &mut xv, pool);
1286 let rank = w.f_a.rows();
1287 let mut fl = vec![0.0f32; bsz * rank];
1288 w.f_a.matmat(xs, bsz, &mut fl, pool);
1289 let mut f = vec![0.0f32; bsz * kd];
1290 w.f_b.matmat(&fl, bsz, &mut f, pool);
1291 let mut b = vec![0.0f32; bsz * nh];
1292 w.b_proj.matmat(xs, bsz, &mut b, pool);
1293 let mut gate_out = vec![0.0f32; bsz * vd];
1294 match &w.gate {
1295 KdaOutGate::Full(gp) => gp.matmat(xs, bsz, &mut gate_out, pool),
1296 KdaOutGate::LowRank(ga, gb) => {
1297 let mut low = vec![0.0f32; bsz * ga.rows()];
1298 ga.matmat(xs, bsz, &mut low, pool);
1299 gb.matmat(&low, bsz, &mut gate_out, pool);
1300 }
1301 }
1302
1303 let mut of = vec![0.0f32; bsz * vd];
1304 for bi in 0..bsz {
1305 let mut oh = vec![0.0f32; vd];
1306 kda_step(
1307 &xq[bi * kd..(bi + 1) * kd],
1308 &xk[bi * kd..(bi + 1) * kd],
1309 &xv[bi * vd..(bi + 1) * vd],
1310 &f[bi * kd..(bi + 1) * kd],
1311 &b[bi * nh..(bi + 1) * nh],
1312 &gate_out[bi * vd..(bi + 1) * vd],
1313 w,
1314 cfg,
1315 state,
1316 &mut oh,
1317 pool,
1318 );
1319 of[bi * vd..(bi + 1) * vd].copy_from_slice(&oh);
1320 }
1321
1322 let mut out = vec![0.0f32; bsz * hs];
1323 w.o_proj.matmat(&of, bsz, &mut out, pool);
1324 out
1325}
1326
1327#[cfg(test)]
1328mod tests {
1329 #[test]
1330 fn kda_forward_matches_naive_reference() {
1331 let (nh, dk, dv, kk, hs, rank) = (2usize, 4usize, 4usize, 3usize, 6usize, 3usize);
1337 let synth = |rows: usize, cols: usize, salt: usize| -> QTensor {
1338 QTensor::from_f32(
1339 (0..rows * cols)
1340 .map(|i| (((i * 31 + salt * 17) % 101) as f32 / 101.0 - 0.5) * 0.6)
1341 .collect(),
1342 rows,
1343 cols,
1344 )
1345 };
1346 let vecf = |n: usize, salt: usize| -> Vec<f32> {
1347 (0..n)
1348 .map(|i| (((i * 13 + salt * 7) % 89) as f32 / 89.0 - 0.5) * 0.8)
1349 .collect()
1350 };
1351 for (label, a_log, lb) in [
1352 ("per-head standard", vecf(nh, 40), None),
1353 ("per-dim lower-bound", vecf(dk, 41), Some(-5.0f32)),
1354 ] {
1355 let w = KdaWeights {
1356 q_proj: synth(nh * dk, hs, 1),
1357 k_proj: synth(nh * dk, hs, 2),
1358 v_proj: synth(nh * dv, hs, 3),
1359 conv_q: vecf(nh * dk * kk, 4),
1360 conv_k: vecf(nh * dk * kk, 5),
1361 conv_v: vecf(nh * dv * kk, 6),
1362 f_a: synth(rank, hs, 7),
1363 f_b: synth(nh * dk, rank, 8),
1364 dt_bias: vecf(nh * dk, 9),
1365 a_log: a_log.clone(),
1366 b_proj: synth(nh, hs, 10),
1367 gate: KdaOutGate::LowRank(synth(rank, hs, 11), synth(nh * dv, rank, 12)),
1368 o_norm: (0..dv).map(|i| 1.0 + 0.1 * i as f32).collect(),
1369 o_proj: synth(hs, nh * dv, 13),
1370 gate_lower_bound: lb,
1371 };
1372 let cfg = KdaCfg {
1373 num_heads: nh,
1374 head_k_dim: dk,
1375 head_v_dim: dv,
1376 conv_kernel: kk,
1377 hidden_size: hs,
1378 rms_eps: 1e-6,
1379 };
1380 let xs: Vec<Vec<f32>> = (0..6)
1381 .map(|t| {
1382 (0..hs)
1383 .map(|i| ((t * hs + i) as f32 * 0.37).sin() * 0.5)
1384 .collect()
1385 })
1386 .collect();
1387
1388 let mut state = Vec::new();
1390 let got: Vec<Vec<f32>> = xs
1391 .iter()
1392 .map(|x| kda_forward(x, &w, &cfg, &mut state, None))
1393 .collect();
1394
1395 let mv = |t: &QTensor, x: &[f32]| -> Vec<f32> {
1397 let mut o = vec![0.0f32; t.rows()];
1398 t.matvec(x, &mut o, None);
1399 o
1400 };
1401 let mut hist: Vec<(Vec<f32>, Vec<f32>, Vec<f32>)> = Vec::new(); let mut s_state = vec![0f64; nh * dk * dv];
1403 let mut want: Vec<Vec<f32>> = Vec::new();
1404 for x in &xs {
1405 let (xq, xk, xv) = (mv(&w.q_proj, x), mv(&w.k_proj, x), mv(&w.v_proj, x));
1406 hist.push((xq, xk, xv));
1407 let conv = |sel: fn(&(Vec<f32>, Vec<f32>, Vec<f32>)) -> &Vec<f32>,
1409 taps: &[f32],
1410 n: usize|
1411 -> Vec<f32> {
1412 (0..n)
1413 .map(|c| {
1414 let t = &taps[c * kk..(c + 1) * kk];
1415 let mut acc = 0f64;
1416 for j in 0..kk {
1417 let idx = hist.len() as i64 - (kk as i64 - j as i64);
1418 if idx >= 0 {
1419 acc += sel(&hist[idx as usize])[c] as f64 * t[j] as f64;
1420 }
1421 }
1422 silu(acc)
1423 })
1424 .map(|v| v as f32)
1425 .collect()
1426 };
1427 let cq = conv(|h| &h.0, &w.conv_q, nh * dk);
1428 let ck = conv(|h| &h.1, &w.conv_k, nh * dk);
1429 let cv = conv(|h| &h.2, &w.conv_v, nh * dv);
1430 let f = mv(&w.f_b, &mv(&w.f_a, x));
1431 let bb = mv(&w.b_proj, x);
1432 let gate_out = match &w.gate {
1433 KdaOutGate::LowRank(ga, gb) => mv(gb, &mv(ga, x)),
1434 KdaOutGate::Full(g) => mv(g, x),
1435 };
1436 let mut of = vec![0f32; nh * dv];
1437 for h in 0..nh {
1438 let q: Vec<f64> = {
1440 let sl = &cq[h * dk..(h + 1) * dk];
1441 let n: f64 = sl.iter().map(|&v| (v as f64) * (v as f64)).sum();
1442 let inv = 1.0 / ((n + 1e-6).sqrt() * (dk as f64).sqrt());
1443 sl.iter().map(|&v| v as f64 * inv).collect()
1444 };
1445 let k: Vec<f64> = {
1446 let sl = &ck[h * dk..(h + 1) * dk];
1447 let n: f64 = sl.iter().map(|&v| (v as f64) * (v as f64)).sum();
1448 let inv = 1.0 / (n + 1e-6).sqrt();
1449 sl.iter().map(|&v| v as f64 * inv).collect()
1450 };
1451 let v: Vec<f64> = cv[h * dv..(h + 1) * dv].iter().map(|&v| v as f64).collect();
1452 let g: Vec<f64> = (0..dk)
1454 .map(|d| {
1455 let a = if w.a_log.len() == nh {
1456 w.a_log[h] as f64
1457 } else {
1458 w.a_log[d] as f64
1459 };
1460 let raw = f[h * dk + d] as f64 + w.dt_bias[h * dk + d] as f64;
1461 match w.gate_lower_bound {
1462 Some(lb) => lb as f64 * sigmoid(a.exp() * raw),
1463 None => -a.exp() * softplus(raw),
1464 }
1465 })
1466 .collect();
1467 let beta = sigmoid(bb[h] as f64);
1468 let s = &mut s_state[h * dk * dv..(h + 1) * dk * dv];
1469 for di in 0..dk {
1471 for dj in 0..dv {
1472 s[di * dv + dj] *= g[di].exp();
1473 }
1474 }
1475 let mut kv = vec![0f64; dv];
1477 for di in 0..dk {
1478 for dj in 0..dv {
1479 kv[dj] += k[di] * s[di * dv + dj];
1480 }
1481 }
1482 for di in 0..dk {
1483 for dj in 0..dv {
1484 s[di * dv + dj] += beta * k[di] * (v[dj] - kv[dj]);
1485 }
1486 }
1487 let mut o = vec![0f64; dv];
1488 for di in 0..dk {
1489 for dj in 0..dv {
1490 o[dj] += q[di] * s[di * dv + dj];
1491 }
1492 }
1493 let ss: f64 = o.iter().map(|&v| v * v).sum();
1495 let inv = 1.0 / (ss / dv as f64 + cfg.rms_eps).sqrt();
1496 for dj in 0..dv {
1497 of[h * dv + dj] = (o[dj]
1498 * inv
1499 * w.o_norm[dj] as f64
1500 * sigmoid(gate_out[h * dv + dj] as f64))
1501 as f32;
1502 }
1503 }
1504 want.push(mv(&w.o_proj, &of));
1505 }
1506
1507 for (t, (g, e)) in got.iter().zip(&want).enumerate() {
1508 for (i, (a, b)) in g.iter().zip(e.iter()).enumerate() {
1509 assert!((a - b).abs() < 2e-4, "{label}: t={t} i={i}: {a} vs {b}");
1510 }
1511 }
1512 }
1513
1514 let w = KdaWeights {
1516 q_proj: synth(nh * dk, hs, 1),
1517 k_proj: synth(nh * dk, hs, 2),
1518 v_proj: synth(nh * dv, hs, 3),
1519 conv_q: vecf(nh * dk * kk, 4),
1520 conv_k: vecf(nh * dk * kk, 5),
1521 conv_v: vecf(nh * dv * kk, 6),
1522 f_a: synth(rank, hs, 7),
1523 f_b: synth(nh * dk, rank, 8),
1524 dt_bias: vecf(nh * dk, 9),
1525 a_log: vecf(nh, 40),
1526 b_proj: synth(nh, hs, 10),
1527 gate: KdaOutGate::LowRank(synth(rank, hs, 11), synth(nh * dv, rank, 12)),
1528 o_norm: (0..dv).map(|i| 1.0 + 0.1 * i as f32).collect(),
1529 o_proj: synth(hs, nh * dv, 13),
1530 gate_lower_bound: None,
1531 };
1532 let cfg = KdaCfg {
1533 num_heads: nh,
1534 head_k_dim: dk,
1535 head_v_dim: dv,
1536 conv_kernel: kk,
1537 hidden_size: hs,
1538 rms_eps: 1e-6,
1539 };
1540 let xs: Vec<f32> = (0..5 * hs).map(|i| (i as f32 * 0.29).cos() * 0.4).collect();
1541 let mut st1 = Vec::new();
1542 let seq: Vec<f32> = (0..5)
1543 .flat_map(|t| kda_forward(&xs[t * hs..(t + 1) * hs], &w, &cfg, &mut st1, None))
1544 .collect();
1545 let mut st2 = Vec::new();
1546 let bat = kda_forward_batch(&xs, 5, &w, &cfg, &mut st2, None);
1547 for (i, (a, b)) in seq.iter().zip(&bat).enumerate() {
1548 assert!((a - b).abs() < 1e-5, "batch i={i}: {a} vs {b}");
1549 }
1550 assert_eq!(st1, st2, "state must match after the chunk");
1551 }
1552
1553 use super::*;
1554
1555 fn tiny() -> (VmfPhaseWeights, VmfPhaseCfg) {
1556 let cfg = VmfPhaseCfg {
1557 num_heads: 2,
1558 nphase: 3,
1559 value_head_dim: 4,
1560 hidden_size: 8,
1561 phase_mass: 0.0,
1562 };
1563 let synth = |rows: usize, cols: usize, salt: usize| {
1564 QTensor::from_f32(
1565 (0..rows * cols)
1566 .map(|i| (((i * 13 + salt * 7) % 97) as f32 / 97.0 - 0.5) * 0.4)
1567 .collect(),
1568 rows,
1569 cols,
1570 )
1571 };
1572 let w = VmfPhaseWeights {
1573 thq: synth(cfg.num_heads * cfg.nphase, cfg.hidden_size, 1),
1574 thk: synth(cfg.num_heads * cfg.nphase, cfg.hidden_size, 2),
1575 v_proj: synth(cfg.num_heads * cfg.value_head_dim, cfg.hidden_size, 3),
1576 out_proj: synth(cfg.hidden_size, cfg.num_heads * cfg.value_head_dim, 4),
1577 decay: (0..cfg.num_heads * 2 * cfg.nphase)
1578 .map(|i| 0.9 + 0.005 * (i % 10) as f64)
1579 .collect(),
1580 conv: None,
1581 k_gate: None,
1582 };
1583 (w, cfg)
1584 }
1585
1586 #[test]
1587 fn state_persists_and_changes_output() {
1588 let (w, cfg) = tiny();
1589 let x: Vec<f32> = (0..8).map(|i| (i as f32 * 0.3).sin()).collect();
1590 let mut state = Vec::new();
1591 let o1 = vmf_phase_forward(&x, &w, &cfg, &mut state, None);
1592 let o2 = vmf_phase_forward(&x, &w, &cfg, &mut state, None);
1593 assert!(o1.iter().zip(&o2).any(|(a, b)| (a - b).abs() > 1e-6));
1595 assert_eq!(state.len(), cfg.state_len());
1596 }
1597
1598 #[test]
1602 fn phase_mass_zero_is_noop_and_positive_shifts() {
1603 let (w, cfg0) = tiny();
1604 let mut cfg_m = cfg0;
1605 cfg_m.phase_mass = 1.0;
1606 let x: Vec<f32> = (0..8).map(|i| (i as f32 * 0.4).sin()).collect();
1607
1608 let mut s0 = Vec::new();
1609 let base = vmf_phase_forward(&x, &w, &cfg0, &mut s0, None);
1610 let mut s0b = Vec::new();
1612 let base2 = vmf_phase_forward(&x, &w, &cfg0, &mut s0b, None);
1613 assert_eq!(base, base2, "mass=0 must be deterministic/no-op");
1614 let mut sm = Vec::new();
1616 let massed = vmf_phase_forward(&x, &w, &cfg_m, &mut sm, None);
1617 assert!(
1618 base.iter().zip(&massed).any(|(a, b)| (a - b).abs() > 1e-5),
1619 "mass>0 must change the output"
1620 );
1621 assert!(massed.iter().all(|v| v.is_finite()));
1622 }
1623
1624 #[test]
1629 fn kappa_gate_open_matches_none_and_closed_writes_nothing() {
1630 let (mut w, cfg) = tiny();
1631 let x: Vec<f32> = (0..8).map(|i| (i as f32 * 0.3).sin()).collect();
1632
1633 let mut s_none = Vec::new();
1634 let base1 = vmf_phase_forward(&x, &w, &cfg, &mut s_none, None);
1635 let base2 = vmf_phase_forward(&x, &w, &cfg, &mut s_none, None);
1636
1637 w.k_gate = Some((
1639 QTensor::from_f32(
1640 vec![0.0; cfg.num_heads * cfg.hidden_size],
1641 cfg.num_heads,
1642 cfg.hidden_size,
1643 ),
1644 vec![20.0; cfg.num_heads],
1645 ));
1646 let mut s_open = Vec::new();
1647 let o1 = vmf_phase_forward(&x, &w, &cfg, &mut s_open, None);
1648 let o2 = vmf_phase_forward(&x, &w, &cfg, &mut s_open, None);
1649 for (a, b) in base1.iter().zip(&o1).chain(base2.iter().zip(&o2)) {
1650 assert!(
1651 (a - b).abs() < 1e-5,
1652 "open κ must match gateless: {a} vs {b}"
1653 );
1654 }
1655
1656 w.k_gate = Some((
1658 QTensor::from_f32(
1659 vec![0.0; cfg.num_heads * cfg.hidden_size],
1660 cfg.num_heads,
1661 cfg.hidden_size,
1662 ),
1663 vec![-20.0; cfg.num_heads],
1664 ));
1665 let mut s_closed = Vec::new();
1666 let oc = vmf_phase_forward(&x, &w, &cfg, &mut s_closed, None);
1667 assert!(
1668 s_closed.iter().all(|&v| v.abs() < 1e-7),
1669 "closed κ: state must stay empty"
1670 );
1671 assert!(
1672 oc.iter().all(|&v| v.abs() < 1e-6),
1673 "closed κ: empty-condensate readout"
1674 );
1675 }
1676
1677 #[test]
1678 fn pair_matches_two_singles_bitexact() {
1679 let (w, cfg) = tiny();
1680 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.2).cos()).collect();
1681 let x2: Vec<f32> = (0..8).map(|i| (i as f32 * 0.5).sin()).collect();
1682
1683 let mut s_ref = Vec::new();
1685 let r1 = vmf_phase_forward(&x1, &w, &cfg, &mut s_ref, None);
1686 let r2 = vmf_phase_forward(&x2, &w, &cfg, &mut s_ref, None);
1687
1688 let mut s = Vec::new();
1690 let mut scratch = Vec::new();
1691 let (p1, p2) = vmf_phase_pair(&x1, &x2, &w, &cfg, &mut s, &mut scratch, None);
1692 assert_eq!(r1, p1, "lane 1 must be bit-identical");
1693 assert_eq!(r2, p2, "lane 2 must be bit-identical");
1694 std::mem::swap(&mut s, &mut scratch);
1696 assert_eq!(s, s_ref, "accepted state must equal sequential state");
1697 }
1698
1699 #[test]
1700 fn rejected_draft_leaves_state_at_lane1() {
1701 let (w, cfg) = tiny();
1702 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.7).sin()).collect();
1703 let x2 = vec![0.5f32; 8];
1704
1705 let mut s_ref = Vec::new();
1706 let _ = vmf_phase_forward(&x1, &w, &cfg, &mut s_ref, None);
1707
1708 let mut s = Vec::new();
1709 let mut scratch = Vec::new();
1710 let _ = vmf_phase_pair(&x1, &x2, &w, &cfg, &mut s, &mut scratch, None);
1711 assert_eq!(s, s_ref);
1713 }
1714
1715 fn tiny_gdn() -> (GdnWeights, GdnCfg) {
1718 let cfg = GdnCfg {
1719 num_v_heads: 4,
1720 num_k_heads: 2,
1721 key_head_dim: 3,
1722 value_head_dim: 5,
1723 conv_kernel: 4,
1724 hidden_size: 8,
1725 rms_eps: 1e-6,
1726 };
1727 let c_dim = cfg.conv_dim();
1728 let vd = cfg.num_v_heads * cfg.value_head_dim;
1729 let synth = |rows: usize, cols: usize, salt: usize| {
1730 QTensor::from_f32(
1731 (0..rows * cols)
1732 .map(|i| (((i * 13 + salt * 7) % 97) as f32 / 97.0 - 0.5) * 0.4)
1733 .collect(),
1734 rows,
1735 cols,
1736 )
1737 };
1738 let vecf = |n: usize, salt: usize| -> Vec<f32> {
1739 (0..n)
1740 .map(|i| (((i * 11 + salt * 5) % 89) as f32 / 89.0 - 0.5) * 0.6)
1741 .collect()
1742 };
1743 let w = GdnWeights {
1744 in_proj_qkv: synth(c_dim, cfg.hidden_size, 1),
1745 in_proj_z: synth(vd, cfg.hidden_size, 2),
1746 in_proj_a: synth(cfg.num_v_heads, cfg.hidden_size, 3),
1747 in_proj_b: synth(cfg.num_v_heads, cfg.hidden_size, 4),
1748 conv1d: vecf(c_dim * cfg.conv_kernel, 5),
1749 a_log: (0..cfg.num_v_heads).map(|i| 0.2 + 0.3 * i as f32).collect(),
1750 dt_bias: vecf(cfg.num_v_heads, 6),
1751 norm: vec![1.0; cfg.value_head_dim],
1752 out_proj: synth(cfg.hidden_size, vd, 7),
1753 };
1754 (w, cfg)
1755 }
1756
1757 #[test]
1758 fn gdn_state_persists_and_changes_output() {
1759 let (w, cfg) = tiny_gdn();
1760 let x: Vec<f32> = (0..8).map(|i| (i as f32 * 0.3).sin()).collect();
1761 let mut state = Vec::new();
1762 let o1 = gdn_forward(&x, &w, &cfg, &mut state, None);
1763 let o2 = gdn_forward(&x, &w, &cfg, &mut state, None);
1764 assert!(o1.iter().zip(&o2).any(|(a, b)| (a - b).abs() > 1e-6));
1765 assert_eq!(state.len(), cfg.state_len());
1766 }
1767
1768 #[test]
1769 fn gdn_pair_matches_two_singles_bitexact() {
1770 let (w, cfg) = tiny_gdn();
1771 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.2).cos()).collect();
1772 let x2: Vec<f32> = (0..8).map(|i| (i as f32 * 0.5).sin()).collect();
1773
1774 let mut s_ref = Vec::new();
1775 let r1 = gdn_forward(&x1, &w, &cfg, &mut s_ref, None);
1776 let r2 = gdn_forward(&x2, &w, &cfg, &mut s_ref, None);
1777
1778 let mut s = Vec::new();
1779 let mut scratch = Vec::new();
1780 let (p1, p2) = gdn_pair(&x1, &x2, &w, &cfg, &mut s, &mut scratch, None);
1781 assert_eq!(r1, p1, "lane 1 must be bit-identical");
1782 assert_eq!(r2, p2, "lane 2 must be bit-identical");
1783 std::mem::swap(&mut s, &mut scratch);
1784 assert_eq!(s, s_ref, "accepted state must equal sequential state");
1785 }
1786
1787 #[test]
1788 fn gdn_rejected_draft_leaves_state_at_lane1() {
1789 let (w, cfg) = tiny_gdn();
1790 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.7).sin()).collect();
1791 let x2 = vec![0.5f32; 8];
1792
1793 let mut s_ref = Vec::new();
1794 let _ = gdn_forward(&x1, &w, &cfg, &mut s_ref, None);
1795
1796 let mut s = Vec::new();
1797 let mut scratch = Vec::new();
1798 let _ = gdn_pair(&x1, &x2, &w, &cfg, &mut s, &mut scratch, None);
1799 assert_eq!(s, s_ref);
1800 }
1801
1802 #[test]
1806 fn gdn_conv_ring_matches_explicit_causal_conv() {
1807 let (w, cfg) = tiny_gdn();
1808 let seq: Vec<Vec<f32>> = (0..6)
1809 .map(|t| (0..8).map(|i| ((t * 8 + i) as f32 * 0.17).sin()).collect())
1810 .collect();
1811
1812 let mut s_inc = Vec::new();
1815 for (t, x) in seq.iter().enumerate() {
1816 let inc = gdn_forward(x, &w, &cfg, &mut s_inc, None);
1817 let mut s_replay = Vec::new();
1818 let mut replay = Vec::new();
1819 for xr in &seq[..=t] {
1820 replay = gdn_forward(xr, &w, &cfg, &mut s_replay, None);
1821 }
1822 assert_eq!(inc, replay, "position {t}: ring must equal replay");
1823 }
1824 }
1825
1826 fn tiny_short_conv() -> (ShortConvWeights, ShortConvCfg) {
1827 let cfg = ShortConvCfg {
1828 hidden_size: 8,
1829 kernel: 3,
1830 };
1831 let synth = |rows: usize, cols: usize, salt: usize| {
1832 QTensor::from_f32(
1833 (0..rows * cols)
1834 .map(|i| (((i * 11 + salt * 5) % 89) as f32 / 89.0 - 0.5) * 0.5)
1835 .collect(),
1836 rows,
1837 cols,
1838 )
1839 };
1840 let w = ShortConvWeights {
1841 in_proj: synth(3 * cfg.hidden_size, cfg.hidden_size, 1),
1842 conv: (0..cfg.hidden_size * cfg.kernel)
1843 .map(|i| ((i * 7 % 13) as f32 / 13.0 - 0.5) * 0.8)
1844 .collect(),
1845 out_proj: synth(cfg.hidden_size, cfg.hidden_size, 2),
1846 };
1847 (w, cfg)
1848 }
1849
1850 #[test]
1853 fn short_conv_ring_matches_explicit_causal_conv() {
1854 let (w, cfg) = tiny_short_conv();
1855 let seq: Vec<Vec<f32>> = (0..6)
1856 .map(|t| (0..8).map(|i| ((t * 8 + i) as f32 * 0.19).cos()).collect())
1857 .collect();
1858 let mut s_inc = Vec::new();
1859 for (t, x) in seq.iter().enumerate() {
1860 let inc = short_conv_forward(x, &w, &cfg, &mut s_inc, None);
1861 let mut s_replay = Vec::new();
1862 let mut replay = Vec::new();
1863 for xr in &seq[..=t] {
1864 replay = short_conv_forward(xr, &w, &cfg, &mut s_replay, None);
1865 }
1866 assert_eq!(inc, replay, "position {t}: ring must equal replay");
1867 assert_eq!(s_inc.len(), cfg.state_len());
1868 }
1869 }
1870
1871 #[test]
1874 fn short_conv_batch_matches_sequential() {
1875 let (w, cfg) = tiny_short_conv();
1876 let b = 5;
1877 let xs: Vec<f32> = (0..b * cfg.hidden_size)
1878 .map(|i| (i as f32 * 0.13).sin() * 0.6)
1879 .collect();
1880
1881 let mut s_seq = Vec::new();
1882 let mut seq_out = vec![0.0f32; b * cfg.hidden_size];
1883 for bi in 0..b {
1884 let o = short_conv_forward(
1885 &xs[bi * cfg.hidden_size..(bi + 1) * cfg.hidden_size],
1886 &w,
1887 &cfg,
1888 &mut s_seq,
1889 None,
1890 );
1891 seq_out[bi * cfg.hidden_size..(bi + 1) * cfg.hidden_size].copy_from_slice(&o);
1892 }
1893
1894 let mut s_batch = Vec::new();
1895 let batch_out = short_conv_forward_batch(&xs, b, &w, &cfg, &mut s_batch, None);
1896 assert_eq!(
1897 seq_out, batch_out,
1898 "batch conv must match sequential decode"
1899 );
1900 assert_eq!(s_seq, s_batch, "ring state must match after the chunk");
1901 }
1902}