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 k_gate: Option<(QTensor, Vec<f32>)>,
48}
49
50#[derive(Clone, Copy)]
51pub struct VmfPhaseCfg {
52 pub num_heads: usize,
53 pub nphase: usize,
54 pub value_head_dim: usize,
55 pub hidden_size: usize,
56 pub phase_mass: f32,
65}
66
67impl VmfPhaseCfg {
68 pub fn state_len(&self) -> usize {
69 self.num_heads * 2 * self.nphase * self.value_head_dim
70 }
71}
72
73fn phase_step(
77 thq: &[f32],
78 thk: &[f32],
79 v: &[f32],
80 decay: &[f64],
81 kap: Option<&[f32]>,
82 cfg: &VmfPhaseCfg,
83 state: &mut [f32],
84 out: &mut [f32],
85) {
86 let (nh, nph, dv) = (cfg.num_heads, cfg.nphase, cfg.value_head_dim);
87 let mscale = 1.0f64 / (1.0 + cfg.phase_mass as f64);
89 let p2 = 2 * nph;
90 for h in 0..nh {
91 let s = &mut state[h * p2 * dv..(h + 1) * p2 * dv];
92 let thk_h = &thk[h * nph..(h + 1) * nph];
93 let thq_h = &thq[h * nph..(h + 1) * nph];
94 let vt = &v[h * dv..(h + 1) * dv];
95 let ot = &mut out[h * dv..(h + 1) * dv];
96 let dec = &decay[h * p2..(h + 1) * p2];
97 let kh = kap.map_or(1.0f64, |k| k[h] as f64);
99 for f in 0..p2 {
100 let (fk, fq) = if f < nph {
102 (
103 (thk_h[f] as f64 * mscale).cos(),
104 (thq_h[f] as f64 * mscale).cos(),
105 )
106 } else {
107 (
108 (thk_h[f - nph] as f64 * mscale).sin(),
109 (thq_h[f - nph] as f64 * mscale).sin(),
110 )
111 };
112 let fkw = fk * kh;
113 let row = &mut s[f * dv..(f + 1) * dv];
114 let dcf = dec[f];
115 for d in 0..dv {
116 let cell = dcf * row[d] as f64 + fkw * vt[d] as f64;
118 row[d] = cell as f32;
119 ot[d] += (fq * cell) as f32; }
121 }
122 }
123}
124
125fn kappa_of(x: &[f32], w: &VmfPhaseWeights, nh: usize, pool: Option<&Pool>) -> Option<Vec<f32>> {
128 let (kw, kb) = w.k_gate.as_ref()?;
129 let mut k = vec![0.0f32; nh];
130 kw.matvec(x, &mut k, pool);
131 for (v, b) in k.iter_mut().zip(kb) {
132 *v = 1.0 / (1.0 + (-(*v + b)).exp());
133 }
134 Some(k)
135}
136
137pub fn vmf_phase_forward(
139 x: &[f32],
140 w: &VmfPhaseWeights,
141 cfg: &VmfPhaseCfg,
142 state: &mut Vec<f32>,
143 pool: Option<&Pool>,
144) -> Vec<f32> {
145 if state.len() != cfg.state_len() {
146 *state = vec![0f32; cfg.state_len()];
147 }
148 let (nh, nph, dv) = (cfg.num_heads, cfg.nphase, cfg.value_head_dim);
149
150 let mut thq = vec![0.0f32; nh * nph];
151 w.thq.matvec(x, &mut thq, pool);
152 let mut thk = vec![0.0f32; nh * nph];
153 w.thk.matvec(x, &mut thk, pool);
154 let mut v = vec![0.0f32; nh * dv];
155 w.v_proj.matvec(x, &mut v, pool);
156
157 let kap = kappa_of(x, w, nh, pool);
158 let mut o = vec![0.0f32; nh * dv];
159 phase_step(&thq, &thk, &v, &w.decay, kap.as_deref(), cfg, state, &mut o);
160
161 let mut out = vec![0.0f32; cfg.hidden_size];
162 w.out_proj.matvec(&o, &mut out, pool);
163 out
164}
165
166#[allow(clippy::too_many_arguments)]
171pub fn vmf_phase_pair(
172 x1: &[f32],
173 x2: &[f32],
174 w: &VmfPhaseWeights,
175 cfg: &VmfPhaseCfg,
176 state: &mut Vec<f32>,
177 scratch: &mut Vec<f32>,
178 pool: Option<&Pool>,
179) -> (Vec<f32>, Vec<f32>) {
180 if state.len() != cfg.state_len() {
181 *state = vec![0f32; cfg.state_len()];
182 }
183 let (nh, nph, dv) = (cfg.num_heads, cfg.nphase, cfg.value_head_dim);
184
185 let mut thq1 = vec![0.0f32; nh * nph];
186 let mut thq2 = vec![0.0f32; nh * nph];
187 w.thq.matvec2(x1, x2, &mut thq1, &mut thq2, pool);
188 let mut thk1 = vec![0.0f32; nh * nph];
189 let mut thk2 = vec![0.0f32; nh * nph];
190 w.thk.matvec2(x1, x2, &mut thk1, &mut thk2, pool);
191 let mut v1 = vec![0.0f32; nh * dv];
192 let mut v2 = vec![0.0f32; nh * dv];
193 w.v_proj.matvec2(x1, x2, &mut v1, &mut v2, pool);
194
195 let kap1 = kappa_of(x1, w, nh, pool);
197 let mut o1 = vec![0.0f32; nh * dv];
198 phase_step(
199 &thq1,
200 &thk1,
201 &v1,
202 &w.decay,
203 kap1.as_deref(),
204 cfg,
205 state,
206 &mut o1,
207 );
208
209 let kap2 = kappa_of(x2, w, nh, pool);
211 scratch.clear();
212 scratch.extend_from_slice(state);
213 let mut o2 = vec![0.0f32; nh * dv];
214 phase_step(
215 &thq2,
216 &thk2,
217 &v2,
218 &w.decay,
219 kap2.as_deref(),
220 cfg,
221 scratch,
222 &mut o2,
223 );
224
225 let mut out1 = vec![0.0f32; cfg.hidden_size];
226 let mut out2 = vec![0.0f32; cfg.hidden_size];
227 w.out_proj.matvec2(&o1, &o2, &mut out1, &mut out2, pool);
228 (out1, out2)
229}
230
231pub struct GdnWeights {
236 pub in_proj_qkv: QTensor,
238 pub in_proj_z: QTensor,
240 pub in_proj_a: QTensor,
242 pub in_proj_b: QTensor,
244 pub conv1d: Vec<f32>,
246 pub a_log: Vec<f32>,
248 pub dt_bias: Vec<f32>,
250 pub norm: Vec<f32>,
252 pub out_proj: QTensor,
254}
255
256#[derive(Clone, Copy)]
257pub struct GdnCfg {
258 pub num_v_heads: usize,
259 pub num_k_heads: usize,
260 pub key_head_dim: usize,
261 pub value_head_dim: usize,
262 pub conv_kernel: usize,
263 pub hidden_size: usize,
264 pub rms_eps: f64,
265}
266
267impl GdnCfg {
268 pub fn conv_dim(&self) -> usize {
269 2 * self.num_k_heads * self.key_head_dim + self.num_v_heads * self.value_head_dim
270 }
271
272 pub fn state_len(&self) -> usize {
275 (self.conv_kernel - 1) * self.conv_dim()
276 + self.num_v_heads * self.key_head_dim * self.value_head_dim
277 }
278}
279
280fn softplus(x: f64) -> f64 {
281 if x > 20.0 { x } else { x.exp().ln_1p() }
282}
283
284fn sigmoid(x: f64) -> f64 {
285 1.0 / (1.0 + (-x).exp())
286}
287
288fn silu(x: f64) -> f64 {
289 x / (1.0 + (-x).exp())
290}
291
292#[derive(Clone, Copy)]
295struct SendMutF32(*mut f32);
296unsafe impl Send for SendMutF32 {}
297unsafe impl Sync for SendMutF32 {}
298
299#[allow(clippy::too_many_arguments)]
312fn gdn_step(
313 qkv: &[f32],
314 z: &[f32],
315 a: &[f32],
316 b: &[f32],
317 w: &GdnWeights,
318 cfg: &GdnCfg,
319 state: &mut [f32],
320 of: &mut [f32],
321 pool: Option<&Pool>,
322) {
323 let (nv, nk, dk, dv, kk) = (
324 cfg.num_v_heads,
325 cfg.num_k_heads,
326 cfg.key_head_dim,
327 cfg.value_head_dim,
328 cfg.conv_kernel,
329 );
330 let c_dim = cfg.conv_dim();
331 let (kd, rep) = (nk * dk, nv / nk);
332 let (ring, s_all) = state.split_at_mut((kk - 1) * c_dim);
333
334 let mut cq = vec![0f32; c_dim];
338 for c in 0..c_dim {
339 let taps = &w.conv1d[c * kk..(c + 1) * kk];
340 let mut acc = qkv[c] as f64 * taps[kk - 1] as f64;
341 for j in 0..kk - 1 {
342 acc += ring[j * c_dim + c] as f64 * taps[j] as f64;
343 }
344 cq[c] = silu(acc) as f32;
345 }
346 if kk > 1 {
348 ring.copy_within(c_dim.., 0);
349 let tail = (kk - 2) * c_dim;
350 ring[tail..tail + c_dim].copy_from_slice(&qkv[..c_dim]);
351 }
352
353 let cq = &cq;
354 let s_ptr = SendMutF32(s_all.as_mut_ptr());
355 let of_ptr = SendMutF32(of.as_mut_ptr());
356 let head_range = |h0: usize, h1: usize| {
357 let (s_ptr, of_ptr) = (s_ptr, of_ptr);
360 let mut kv = crate::attention::take_buf(dv);
362 let mut delta = crate::attention::take_buf(dv);
363 let mut o = crate::attention::take_buf(dv);
364 let mut kf = crate::attention::take_buf(dk);
365 let mut qf = crate::attention::take_buf(dk);
366 for h in h0..h1 {
367 let ko = h / rep; let (qs, ks) = (ko * dk, kd + ko * dk);
369 let (mut nq, mut nkn) = (0f64, 0f64);
371 for d in 0..dk {
372 nq += (cq[qs + d] as f64) * (cq[qs + d] as f64);
373 nkn += (cq[ks + d] as f64) * (cq[ks + d] as f64);
374 }
375 let invq = (1.0 / ((nq + 1e-6).sqrt() * (dk as f64).sqrt())) as f32;
376 let invk = (1.0 / (nkn + 1e-6).sqrt()) as f32;
377 for d in 0..dk {
378 qf[d] = cq[qs + d] * invq;
379 kf[d] = cq[ks + d] * invk;
380 }
381
382 let g = (-(w.a_log[h] as f64).exp() * softplus(a[h] as f64 + w.dt_bias[h] as f64)).exp()
383 as f32;
384 let beta = sigmoid(b[h] as f64) as f32;
385
386 let s = unsafe { std::slice::from_raw_parts_mut(s_ptr.0.add(h * dk * dv), dk * dv) };
388 let oh = unsafe { std::slice::from_raw_parts_mut(of_ptr.0.add(h * dv), dv) };
389 let vt = &cq[2 * kd + h * dv..2 * kd + (h + 1) * dv];
390
391 kv[..dv].fill(0.0);
395 for di in 0..dk {
396 let kfd = kf[di];
397 let row = &s[di * dv..(di + 1) * dv];
398 for dj in 0..dv {
399 kv[dj] += row[dj] * kfd; }
401 }
402 for dj in 0..dv {
403 delta[dj] = (vt[dj] - g * kv[dj]) * beta;
404 }
405 o[..dv].fill(0.0);
406 for di in 0..dk {
407 let kfd = kf[di];
408 let qfd = qf[di];
409 let row = &mut s[di * dv..(di + 1) * dv];
410 for dj in 0..dv {
411 let cell = g * row[dj] + kfd * delta[dj];
412 row[dj] = cell;
413 o[dj] += qfd * cell; }
415 }
416 let ss: f64 = o[..dv].iter().map(|&v| (v as f64) * (v as f64)).sum();
418 let inv = 1.0 / (ss / dv as f64 + cfg.rms_eps).sqrt();
419 for dj in 0..dv {
420 oh[dj] =
421 ((o[dj] as f64 * inv) * w.norm[dj] as f64 * silu(z[h * dv + dj] as f64)) as f32;
422 }
423 }
424 crate::attention::recycle_buf(&mut kv);
425 crate::attention::recycle_buf(&mut delta);
426 crate::attention::recycle_buf(&mut o);
427 crate::attention::recycle_buf(&mut kf);
428 crate::attention::recycle_buf(&mut qf);
429 };
430 match pool {
431 Some(pool) if nv >= 4 => pool.run(&|widx, n| {
432 let chunk = nv.div_ceil(n);
433 let h0 = (widx * chunk).min(nv);
434 let h1 = (h0 + chunk).min(nv);
435 if h0 < h1 {
436 head_range(h0, h1);
437 }
438 }),
439 _ => head_range(0, nv),
440 }
441}
442
443pub fn gdn_forward(
445 x: &[f32],
446 w: &GdnWeights,
447 cfg: &GdnCfg,
448 state: &mut Vec<f32>,
449 pool: Option<&Pool>,
450) -> Vec<f32> {
451 if state.len() != cfg.state_len() {
452 *state = vec![0f32; cfg.state_len()];
453 }
454 let (c_dim, vd) = (cfg.conv_dim(), cfg.num_v_heads * cfg.value_head_dim);
455
456 let mut qkv = vec![0.0f32; c_dim];
457 let mut z = vec![0.0f32; vd];
458 let mut a = vec![0.0f32; cfg.num_v_heads];
459 let mut b = vec![0.0f32; cfg.num_v_heads];
460 let cpu_projs = |qkv: &mut Vec<f32>, z: &mut Vec<f32>, a: &mut Vec<f32>, b: &mut Vec<f32>| {
464 QTensor::matvec_many(
465 [&w.in_proj_qkv, &w.in_proj_z, &w.in_proj_a, &w.in_proj_b],
466 x,
467 [
468 qkv.as_mut_slice(),
469 z.as_mut_slice(),
470 a.as_mut_slice(),
471 b.as_mut_slice(),
472 ],
473 pool,
474 );
475 };
476 let mut done = false;
477 if crate::gpu::enabled_here() && gdn_projs_eligible(w) {
478 match crate::gpu::probe_arm(crate::gpu::OpClass::Batch) {
479 crate::gpu::ProbeArm::Gpu => {
480 let t0 = std::time::Instant::now();
481 if gdn_projs_gpu(w, x, &mut qkv, &mut z) {
482 crate::gpu::probe_record(crate::gpu::OpClass::Batch, true, t0.elapsed());
483 w.in_proj_a.matvec(x, &mut a, pool);
484 w.in_proj_b.matvec(x, &mut b, pool);
485 done = true;
486 } else {
487 crate::gpu::probe_note_decline(crate::gpu::OpClass::Batch);
488 }
489 }
490 crate::gpu::ProbeArm::CpuTimed => {
491 let t0 = std::time::Instant::now();
492 crate::gpu::cpu_scope(|| cpu_projs(&mut qkv, &mut z, &mut a, &mut b));
493 crate::gpu::probe_record(crate::gpu::OpClass::Batch, false, t0.elapsed());
494 done = true;
495 }
496 crate::gpu::ProbeArm::Cpu => {
497 crate::gpu::cpu_scope(|| cpu_projs(&mut qkv, &mut z, &mut a, &mut b));
498 done = true;
499 }
500 }
501 }
502 if !done {
503 cpu_projs(&mut qkv, &mut z, &mut a, &mut b);
504 }
505
506 let mut of = vec![0.0f32; vd];
507 gdn_step(&qkv, &z, &a, &b, w, cfg, state, &mut of, pool);
508
509 let mut out = vec![0.0f32; cfg.hidden_size];
510 w.out_proj.matvec(&of, &mut out, pool);
511 out
512}
513
514pub fn gdn_forward_batch(
519 xs: &[f32],
520 b: usize,
521 w: &GdnWeights,
522 cfg: &GdnCfg,
523 state: &mut Vec<f32>,
524 pool: Option<&Pool>,
525) -> Vec<f32> {
526 if state.len() != cfg.state_len() {
527 *state = vec![0f32; cfg.state_len()];
528 }
529 let (c_dim, vd) = (cfg.conv_dim(), cfg.num_v_heads * cfg.value_head_dim);
530 let nv = cfg.num_v_heads;
531
532 let mut qkv = vec![0.0f32; b * c_dim];
533 w.in_proj_qkv.matmat(xs, b, &mut qkv, pool);
534 let mut z = vec![0.0f32; b * vd];
535 w.in_proj_z.matmat(xs, b, &mut z, pool);
536 let mut a = vec![0.0f32; b * nv];
537 w.in_proj_a.matmat(xs, b, &mut a, pool);
538 let mut bb = vec![0.0f32; b * nv];
539 w.in_proj_b.matmat(xs, b, &mut bb, pool);
540
541 let mut of = vec![0.0f32; b * vd];
542 for bi in 0..b {
543 gdn_step(
544 &qkv[bi * c_dim..(bi + 1) * c_dim],
545 &z[bi * vd..(bi + 1) * vd],
546 &a[bi * nv..(bi + 1) * nv],
547 &bb[bi * nv..(bi + 1) * nv],
548 w,
549 cfg,
550 state,
551 &mut of[bi * vd..(bi + 1) * vd],
552 pool,
553 );
554 }
555 let mut out = vec![0.0f32; b * cfg.hidden_size];
556 w.out_proj.matmat(&of, b, &mut out, pool);
557 if std::env::var("CMF_GDN_TRACE").is_ok() {
558 let n = |v: &[f32]| v.iter().map(|x| x * x).sum::<f32>().sqrt();
559 eprintln!(
560 "gdn-batch b={b}: |x0|={:.5} |qkv0|={:.5} |z0|={:.5} |a0|={:.5} |b0|={:.5} |of0|={:.5} |out0|={:.5} |state|={:.5}",
561 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)
562 );
563 }
564 out
565}
566
567fn gdn_projs_eligible(w: &GdnWeights) -> bool {
571 if std::env::var("CMF_GPU_GDN")
577 .map(|v| v == "0")
578 .unwrap_or(false)
579 {
580 return false;
581 }
582 w.in_proj_qkv.is_q1()
583 || w.in_proj_qkv.q4t_parts().is_some()
584 || w.in_proj_qkv.q4tp_parts().is_some()
585 || std::env::var("CMF_GPU_GDN")
586 .map(|v| v == "1")
587 .unwrap_or(false)
588}
589
590fn gdn_projs_gpu(w: &GdnWeights, x: &[f32], qkv: &mut [f32], z: &mut [f32]) -> bool {
592 use crate::gpu::matvec_batch;
593 use crate::qtensor::QTensor;
594 if !crate::gpu::enabled_here() {
595 return false;
596 }
597 fn part<'a>(
598 t: &'a QTensor,
599 x: &[f32],
600 ) -> Option<(
601 std::sync::Arc<cortiq_core::CmfModel>,
602 crate::gpu::BatchJob<'a>,
603 )> {
604 use crate::gpu::BatchJob;
605 use crate::qtensor::prescale;
606 use cortiq_core::TensorDtype;
607 match t {
608 QTensor::Mapped {
609 model,
610 idx,
611 dtype: dt @ (TensorDtype::Q8Row | TensorDtype::Q8_2f),
612 rows,
613 cols,
614 row_scale,
615 col_field,
616 ..
617 } => Some((
618 model.clone(),
619 BatchJob {
620 idx: *idx,
621 rows: *rows,
622 cols: *cols,
623 row_scale,
624 xs: prescale(x, col_field, *dt).into_owned(),
625 layout: crate::gpu::BatchLayout::Q8,
626 },
627 )),
628 QTensor::Mapped {
629 model,
630 idx,
631 dtype: TensorDtype::Q1,
632 rows,
633 cols,
634 ..
635 } => Some((
636 model.clone(),
637 BatchJob {
638 idx: *idx,
639 rows: *rows,
640 cols: *cols,
641 row_scale: &[],
642 xs: x.to_vec(),
643 layout: crate::gpu::BatchLayout::Q1,
644 },
645 )),
646 QTensor::Mapped {
650 model,
651 idx,
652 dtype: dt @ (TensorDtype::Q4Tiled | TensorDtype::Q4TiledP),
653 rows,
654 cols,
655 ..
656 } => Some((
657 model.clone(),
658 BatchJob {
659 idx: *idx,
660 rows: *rows,
661 cols: *cols,
662 row_scale: &[],
663 xs: x.to_vec(),
664 layout: if *dt == TensorDtype::Q4TiledP {
665 crate::gpu::BatchLayout::Q4tp
666 } else {
667 crate::gpu::BatchLayout::Q4t
668 },
669 },
670 )),
671 _ => None,
672 }
673 }
674 let Some((model, jq)) = part(&w.in_proj_qkv, x) else {
675 return false;
676 };
677 let Some((_, jz)) = part(&w.in_proj_z, x) else {
678 return false;
679 };
680 matvec_batch(&model, &[jq, jz], &mut [qkv, z])
681}
682
683#[allow(clippy::too_many_arguments)]
686pub fn gdn_pair(
687 x1: &[f32],
688 x2: &[f32],
689 w: &GdnWeights,
690 cfg: &GdnCfg,
691 state: &mut Vec<f32>,
692 scratch: &mut Vec<f32>,
693 pool: Option<&Pool>,
694) -> (Vec<f32>, Vec<f32>) {
695 if state.len() != cfg.state_len() {
696 *state = vec![0f32; cfg.state_len()];
697 }
698 let (c_dim, vd, nv) = (
699 cfg.conv_dim(),
700 cfg.num_v_heads * cfg.value_head_dim,
701 cfg.num_v_heads,
702 );
703
704 let mut qkv1 = vec![0.0f32; c_dim];
705 let mut qkv2 = vec![0.0f32; c_dim];
706 w.in_proj_qkv.matvec2(x1, x2, &mut qkv1, &mut qkv2, pool);
707 let mut z1 = vec![0.0f32; vd];
708 let mut z2 = vec![0.0f32; vd];
709 w.in_proj_z.matvec2(x1, x2, &mut z1, &mut z2, pool);
710 let mut a1 = vec![0.0f32; nv];
711 let mut a2 = vec![0.0f32; nv];
712 w.in_proj_a.matvec2(x1, x2, &mut a1, &mut a2, pool);
713 let mut b1 = vec![0.0f32; nv];
714 let mut b2 = vec![0.0f32; nv];
715 w.in_proj_b.matvec2(x1, x2, &mut b1, &mut b2, pool);
716
717 let mut of1 = vec![0.0f32; vd];
718 gdn_step(&qkv1, &z1, &a1, &b1, w, cfg, state, &mut of1, pool);
719
720 scratch.clear();
721 scratch.extend_from_slice(state);
722 let mut of2 = vec![0.0f32; vd];
723 gdn_step(&qkv2, &z2, &a2, &b2, w, cfg, scratch, &mut of2, pool);
724
725 let mut out1 = vec![0.0f32; cfg.hidden_size];
726 let mut out2 = vec![0.0f32; cfg.hidden_size];
727 w.out_proj.matvec2(&of1, &of2, &mut out1, &mut out2, pool);
728 (out1, out2)
729}
730
731pub struct ShortConvWeights {
738 pub in_proj: QTensor,
740 pub conv: Vec<f32>,
744 pub out_proj: QTensor,
746}
747
748#[derive(Clone, Copy)]
749pub struct ShortConvCfg {
750 pub hidden_size: usize,
751 pub kernel: usize,
753}
754
755impl ShortConvCfg {
756 pub fn state_len(&self) -> usize {
758 (self.kernel - 1) * self.hidden_size
759 }
760}
761
762fn short_conv_step(
772 bcx: &[f32],
773 conv: &[f32],
774 cfg: &ShortConvCfg,
775 ring_state: &mut [f32],
776 y: &mut [f32],
777) {
778 let (h, k) = (cfg.hidden_size, cfg.kernel);
779 let ring = k - 1;
780 let (bg, cg, xg) = (&bcx[0..h], &bcx[h..2 * h], &bcx[2 * h..3 * h]);
781 for c in 0..h {
782 let bx = bg[c] * xg[c];
783 let wc = &conv[c * k..(c + 1) * k];
784 let mut acc = wc[k - 1] * bx;
786 let rc = &mut ring_state[c * ring..c * ring + ring];
787 for s in 0..ring {
788 acc += wc[k - 2 - s] * rc[s];
789 }
790 y[c] = cg[c] * acc;
791 for s in (1..ring).rev() {
793 rc[s] = rc[s - 1];
794 }
795 if ring > 0 {
796 rc[0] = bx;
797 }
798 }
799}
800
801pub fn short_conv_forward(
803 x: &[f32],
804 w: &ShortConvWeights,
805 cfg: &ShortConvCfg,
806 state: &mut Vec<f32>,
807 pool: Option<&Pool>,
808) -> Vec<f32> {
809 if state.len() != cfg.state_len() {
810 *state = vec![0f32; cfg.state_len()];
811 }
812 let h = cfg.hidden_size;
813 let mut bcx = vec![0.0f32; 3 * h];
814 w.in_proj.matvec(x, &mut bcx, pool);
815 let mut y = vec![0.0f32; h];
816 short_conv_step(&bcx, &w.conv, cfg, state, &mut y);
817 let mut out = vec![0.0f32; h];
818 w.out_proj.matvec(&y, &mut out, pool);
819 out
820}
821
822pub fn short_conv_forward_batch(
827 xs: &[f32],
828 b: usize,
829 w: &ShortConvWeights,
830 cfg: &ShortConvCfg,
831 state: &mut Vec<f32>,
832 pool: Option<&Pool>,
833) -> Vec<f32> {
834 if state.len() != cfg.state_len() {
835 *state = vec![0f32; cfg.state_len()];
836 }
837 let h = cfg.hidden_size;
838 let mut bcx = vec![0.0f32; b * 3 * h];
839 w.in_proj.matmat(xs, b, &mut bcx, pool);
840 let mut y = vec![0.0f32; b * h];
841 for bi in 0..b {
842 short_conv_step(
843 &bcx[bi * 3 * h..(bi + 1) * 3 * h],
844 &w.conv,
845 cfg,
846 state,
847 &mut y[bi * h..(bi + 1) * h],
848 );
849 }
850 let mut out = vec![0.0f32; b * h];
851 w.out_proj.matmat(&y, b, &mut out, pool);
852 out
853}
854
855#[allow(clippy::too_many_arguments)]
860pub fn short_conv_pair(
861 x1: &[f32],
862 x2: &[f32],
863 w: &ShortConvWeights,
864 cfg: &ShortConvCfg,
865 state: &mut Vec<f32>,
866 scratch: &mut Vec<f32>,
867 pool: Option<&Pool>,
868) -> (Vec<f32>, Vec<f32>) {
869 if state.len() != cfg.state_len() {
870 *state = vec![0f32; cfg.state_len()];
871 }
872 let h = cfg.hidden_size;
873 let mut bcx1 = vec![0.0f32; 3 * h];
874 let mut bcx2 = vec![0.0f32; 3 * h];
875 w.in_proj.matvec2(x1, x2, &mut bcx1, &mut bcx2, pool);
876
877 let mut y1 = vec![0.0f32; h];
878 short_conv_step(&bcx1, &w.conv, cfg, state, &mut y1);
879 scratch.clear();
880 scratch.extend_from_slice(state);
881 let mut y2 = vec![0.0f32; h];
882 short_conv_step(&bcx2, &w.conv, cfg, scratch, &mut y2);
883
884 let mut out1 = vec![0.0f32; h];
885 let mut out2 = vec![0.0f32; h];
886 w.out_proj.matvec2(&y1, &y2, &mut out1, &mut out2, pool);
887 (out1, out2)
888}
889
890pub struct KdaWeights {
901 pub q_proj: QTensor,
903 pub k_proj: QTensor,
905 pub v_proj: QTensor,
907 pub conv_q: Vec<f32>,
909 pub conv_k: Vec<f32>,
910 pub conv_v: Vec<f32>,
912 pub f_a: QTensor,
914 pub f_b: QTensor,
916 pub dt_bias: Vec<f32>,
918 pub a_log: Vec<f32>,
921 pub b_proj: QTensor,
923 pub gate: KdaOutGate,
925 pub o_norm: Vec<f32>,
927 pub o_proj: QTensor,
929 pub gate_lower_bound: Option<f32>,
932}
933
934pub enum KdaOutGate {
935 Full(QTensor),
937 LowRank(QTensor, QTensor),
939}
940
941#[derive(Clone, Copy)]
942pub struct KdaCfg {
943 pub num_heads: usize,
944 pub head_k_dim: usize,
945 pub head_v_dim: usize,
946 pub conv_kernel: usize,
947 pub hidden_size: usize,
948 pub rms_eps: f64,
949}
950
951impl KdaCfg {
952 pub fn state_len(&self) -> usize {
955 let (nh, dk, dv, kk) = (
956 self.num_heads,
957 self.head_k_dim,
958 self.head_v_dim,
959 self.conv_kernel,
960 );
961 (kk - 1) * (2 * nh * dk + nh * dv) + nh * dk * dv
962 }
963}
964
965fn kda_conv(raw: &[f32], taps: &[f32], ring: &mut [f32], kk: usize, out: &mut [f32]) {
968 let c_dim = raw.len();
969 for c in 0..c_dim {
970 let t = &taps[c * kk..(c + 1) * kk];
971 let mut acc = raw[c] as f64 * t[kk - 1] as f64;
972 for j in 0..kk - 1 {
973 acc += ring[j * c_dim + c] as f64 * t[j] as f64;
974 }
975 out[c] = silu(acc) as f32;
976 }
977 if kk > 1 {
978 ring.copy_within(c_dim.., 0);
979 let tail = (kk - 2) * c_dim;
980 ring[tail..tail + c_dim].copy_from_slice(raw);
981 }
982}
983
984#[inline]
987fn kda_log_decay(w: &KdaWeights, cfg: &KdaCfg, h: usize, d: usize, f: f32) -> f64 {
988 let (nh, dk) = (cfg.num_heads, cfg.head_k_dim);
989 let a = if w.a_log.len() == nh {
990 w.a_log[h] as f64
991 } else if w.a_log.len() == dk {
992 w.a_log[d] as f64
993 } else {
994 w.a_log[h * dk + d] as f64
995 };
996 let raw = f as f64 + w.dt_bias[h * dk + d] as f64;
997 match w.gate_lower_bound {
998 Some(lb) => lb as f64 * sigmoid(a.exp() * raw),
999 None => -a.exp() * softplus(raw),
1000 }
1001}
1002
1003#[allow(clippy::too_many_arguments)]
1011fn kda_step(
1012 xq: &[f32],
1013 xk: &[f32],
1014 xv: &[f32],
1015 f: &[f32],
1016 b: &[f32],
1017 gate_out: &[f32],
1018 w: &KdaWeights,
1019 cfg: &KdaCfg,
1020 state: &mut [f32],
1021 of: &mut [f32],
1022 pool: Option<&Pool>,
1023) {
1024 let (nh, dk, dv, kk) = (
1025 cfg.num_heads,
1026 cfg.head_k_dim,
1027 cfg.head_v_dim,
1028 cfg.conv_kernel,
1029 );
1030 let (kd, vd) = (nh * dk, nh * dv);
1031 let ring_q_len = (kk - 1) * kd;
1032 let ring_v_len = (kk - 1) * vd;
1033 let (ring_q, rest) = state.split_at_mut(ring_q_len);
1034 let (ring_k, rest) = rest.split_at_mut(ring_q_len);
1035 let (ring_v, s_all) = rest.split_at_mut(ring_v_len);
1036
1037 let mut cq = vec![0f32; kd];
1038 let mut ck = vec![0f32; kd];
1039 let mut cv = vec![0f32; vd];
1040 kda_conv(xq, &w.conv_q, ring_q, kk, &mut cq);
1041 kda_conv(xk, &w.conv_k, ring_k, kk, &mut ck);
1042 kda_conv(xv, &w.conv_v, ring_v, kk, &mut cv);
1043
1044 let (cq, ck, cv) = (&cq, &ck, &cv);
1045 let s_ptr = SendMutF32(s_all.as_mut_ptr());
1046 let of_ptr = SendMutF32(of.as_mut_ptr());
1047 let head_range = |h0: usize, h1: usize| {
1048 let (s_ptr, of_ptr) = (s_ptr, of_ptr);
1049 let mut kv = crate::attention::take_buf(dv);
1050 let mut delta = crate::attention::take_buf(dv);
1051 let mut o = crate::attention::take_buf(dv);
1052 let mut kf = crate::attention::take_buf(dk);
1053 let mut qf = crate::attention::take_buf(dk);
1054 let mut gd = crate::attention::take_buf(dk);
1055 for h in h0..h1 {
1056 let qs = h * dk;
1057 let (mut nq, mut nkn) = (0f64, 0f64);
1059 for d in 0..dk {
1060 nq += (cq[qs + d] as f64) * (cq[qs + d] as f64);
1061 nkn += (ck[qs + d] as f64) * (ck[qs + d] as f64);
1062 }
1063 let invq = (1.0 / ((nq + 1e-6).sqrt() * (dk as f64).sqrt())) as f32;
1064 let invk = (1.0 / (nkn + 1e-6).sqrt()) as f32;
1065 for d in 0..dk {
1066 qf[d] = cq[qs + d] * invq;
1067 kf[d] = ck[qs + d] * invk;
1068 gd[d] = kda_log_decay(w, cfg, h, d, f[qs + d]).exp() as f32;
1069 }
1070 let beta = sigmoid(b[h] as f64) as f32;
1071
1072 let s = unsafe { std::slice::from_raw_parts_mut(s_ptr.0.add(h * dk * dv), dk * dv) };
1074 let oh = unsafe { std::slice::from_raw_parts_mut(of_ptr.0.add(h * dv), dv) };
1075 let vt = &cv[h * dv..(h + 1) * dv];
1076
1077 kv[..dv].fill(0.0);
1079 for di in 0..dk {
1080 let kg = kf[di] * gd[di];
1081 let row = &s[di * dv..(di + 1) * dv];
1082 for dj in 0..dv {
1083 kv[dj] += row[dj] * kg;
1084 }
1085 }
1086 for dj in 0..dv {
1087 delta[dj] = (vt[dj] - kv[dj]) * beta;
1088 }
1089 o[..dv].fill(0.0);
1091 for di in 0..dk {
1092 let (kfd, qfd, gdd) = (kf[di], qf[di], gd[di]);
1093 let row = &mut s[di * dv..(di + 1) * dv];
1094 for dj in 0..dv {
1095 let cell = gdd * row[dj] + kfd * delta[dj];
1096 row[dj] = cell;
1097 o[dj] += qfd * cell;
1098 }
1099 }
1100 let ss: f64 = o[..dv].iter().map(|&v| (v as f64) * (v as f64)).sum();
1102 let inv = 1.0 / (ss / dv as f64 + cfg.rms_eps).sqrt();
1103 for dj in 0..dv {
1104 oh[dj] = ((o[dj] as f64 * inv)
1105 * w.o_norm[dj] as f64
1106 * sigmoid(gate_out[h * dv + dj] as f64)) as f32;
1107 }
1108 }
1109 crate::attention::recycle_buf(&mut kv);
1110 crate::attention::recycle_buf(&mut delta);
1111 crate::attention::recycle_buf(&mut o);
1112 crate::attention::recycle_buf(&mut kf);
1113 crate::attention::recycle_buf(&mut qf);
1114 crate::attention::recycle_buf(&mut gd);
1115 };
1116 match pool {
1117 Some(pool) if nh >= 4 => pool.run(&|widx, n| {
1118 let chunk = nh.div_ceil(n);
1119 let h0 = (widx * chunk).min(nh);
1120 let h1 = (h0 + chunk).min(nh);
1121 if h0 < h1 {
1122 head_range(h0, h1);
1123 }
1124 }),
1125 _ => head_range(0, nh),
1126 }
1127}
1128
1129fn kda_gate_out(w: &KdaWeights, x: &[f32], vd: usize, pool: Option<&Pool>) -> Vec<f32> {
1132 let mut g = vec![0.0f32; vd];
1133 match &w.gate {
1134 KdaOutGate::Full(gp) => gp.matvec(x, &mut g, pool),
1135 KdaOutGate::LowRank(ga, gb) => {
1136 let mut low = vec![0.0f32; ga.rows()];
1137 ga.matvec(x, &mut low, pool);
1138 gb.matvec(&low, &mut g, pool);
1139 }
1140 }
1141 g
1142}
1143
1144pub fn kda_forward(
1146 x: &[f32],
1147 w: &KdaWeights,
1148 cfg: &KdaCfg,
1149 state: &mut Vec<f32>,
1150 pool: Option<&Pool>,
1151) -> Vec<f32> {
1152 if state.len() != cfg.state_len() {
1153 *state = vec![0f32; cfg.state_len()];
1154 }
1155 let (nh, dk, dv) = (cfg.num_heads, cfg.head_k_dim, cfg.head_v_dim);
1156 let (kd, vd) = (nh * dk, nh * dv);
1157
1158 let mut xq = vec![0.0f32; kd];
1159 let mut xk = vec![0.0f32; kd];
1160 let mut xv = vec![0.0f32; vd];
1161 let mut fl = vec![0.0f32; w.f_a.rows()];
1162 let mut b = vec![0.0f32; nh];
1163 QTensor::matvec_many(
1164 [&w.q_proj, &w.k_proj, &w.v_proj, &w.f_a],
1165 x,
1166 [
1167 xq.as_mut_slice(),
1168 xk.as_mut_slice(),
1169 xv.as_mut_slice(),
1170 fl.as_mut_slice(),
1171 ],
1172 pool,
1173 );
1174 w.b_proj.matvec(x, &mut b, pool);
1175 let mut f = vec![0.0f32; kd];
1176 w.f_b.matvec(&fl, &mut f, pool);
1177 let gate_out = kda_gate_out(w, x, vd, pool);
1178
1179 let mut of = vec![0.0f32; vd];
1180 kda_step(
1181 &xq, &xk, &xv, &f, &b, &gate_out, w, cfg, state, &mut of, pool,
1182 );
1183
1184 let mut out = vec![0.0f32; cfg.hidden_size];
1185 w.o_proj.matvec(&of, &mut out, pool);
1186 out
1187}
1188
1189pub fn kda_forward_batch(
1193 xs: &[f32],
1194 bsz: usize,
1195 w: &KdaWeights,
1196 cfg: &KdaCfg,
1197 state: &mut Vec<f32>,
1198 pool: Option<&Pool>,
1199) -> Vec<f32> {
1200 if state.len() != cfg.state_len() {
1201 *state = vec![0f32; cfg.state_len()];
1202 }
1203 let (nh, dk, dv, hs) = (
1204 cfg.num_heads,
1205 cfg.head_k_dim,
1206 cfg.head_v_dim,
1207 cfg.hidden_size,
1208 );
1209 let (kd, vd) = (nh * dk, nh * dv);
1210
1211 let mut xq = vec![0.0f32; bsz * kd];
1212 w.q_proj.matmat(xs, bsz, &mut xq, pool);
1213 let mut xk = vec![0.0f32; bsz * kd];
1214 w.k_proj.matmat(xs, bsz, &mut xk, pool);
1215 let mut xv = vec![0.0f32; bsz * vd];
1216 w.v_proj.matmat(xs, bsz, &mut xv, pool);
1217 let rank = w.f_a.rows();
1218 let mut fl = vec![0.0f32; bsz * rank];
1219 w.f_a.matmat(xs, bsz, &mut fl, pool);
1220 let mut f = vec![0.0f32; bsz * kd];
1221 w.f_b.matmat(&fl, bsz, &mut f, pool);
1222 let mut b = vec![0.0f32; bsz * nh];
1223 w.b_proj.matmat(xs, bsz, &mut b, pool);
1224 let mut gate_out = vec![0.0f32; bsz * vd];
1225 match &w.gate {
1226 KdaOutGate::Full(gp) => gp.matmat(xs, bsz, &mut gate_out, pool),
1227 KdaOutGate::LowRank(ga, gb) => {
1228 let mut low = vec![0.0f32; bsz * ga.rows()];
1229 ga.matmat(xs, bsz, &mut low, pool);
1230 gb.matmat(&low, bsz, &mut gate_out, pool);
1231 }
1232 }
1233
1234 let mut of = vec![0.0f32; bsz * vd];
1235 for bi in 0..bsz {
1236 let mut oh = vec![0.0f32; vd];
1237 kda_step(
1238 &xq[bi * kd..(bi + 1) * kd],
1239 &xk[bi * kd..(bi + 1) * kd],
1240 &xv[bi * vd..(bi + 1) * vd],
1241 &f[bi * kd..(bi + 1) * kd],
1242 &b[bi * nh..(bi + 1) * nh],
1243 &gate_out[bi * vd..(bi + 1) * vd],
1244 w,
1245 cfg,
1246 state,
1247 &mut oh,
1248 pool,
1249 );
1250 of[bi * vd..(bi + 1) * vd].copy_from_slice(&oh);
1251 }
1252
1253 let mut out = vec![0.0f32; bsz * hs];
1254 w.o_proj.matmat(&of, bsz, &mut out, pool);
1255 out
1256}
1257
1258#[cfg(test)]
1259mod tests {
1260 #[test]
1261 fn kda_forward_matches_naive_reference() {
1262 let (nh, dk, dv, kk, hs, rank) = (2usize, 4usize, 4usize, 3usize, 6usize, 3usize);
1268 let synth = |rows: usize, cols: usize, salt: usize| -> QTensor {
1269 QTensor::from_f32(
1270 (0..rows * cols)
1271 .map(|i| (((i * 31 + salt * 17) % 101) as f32 / 101.0 - 0.5) * 0.6)
1272 .collect(),
1273 rows,
1274 cols,
1275 )
1276 };
1277 let vecf = |n: usize, salt: usize| -> Vec<f32> {
1278 (0..n)
1279 .map(|i| (((i * 13 + salt * 7) % 89) as f32 / 89.0 - 0.5) * 0.8)
1280 .collect()
1281 };
1282 for (label, a_log, lb) in [
1283 ("per-head standard", vecf(nh, 40), None),
1284 ("per-dim lower-bound", vecf(dk, 41), Some(-5.0f32)),
1285 ] {
1286 let w = KdaWeights {
1287 q_proj: synth(nh * dk, hs, 1),
1288 k_proj: synth(nh * dk, hs, 2),
1289 v_proj: synth(nh * dv, hs, 3),
1290 conv_q: vecf(nh * dk * kk, 4),
1291 conv_k: vecf(nh * dk * kk, 5),
1292 conv_v: vecf(nh * dv * kk, 6),
1293 f_a: synth(rank, hs, 7),
1294 f_b: synth(nh * dk, rank, 8),
1295 dt_bias: vecf(nh * dk, 9),
1296 a_log: a_log.clone(),
1297 b_proj: synth(nh, hs, 10),
1298 gate: KdaOutGate::LowRank(synth(rank, hs, 11), synth(nh * dv, rank, 12)),
1299 o_norm: (0..dv).map(|i| 1.0 + 0.1 * i as f32).collect(),
1300 o_proj: synth(hs, nh * dv, 13),
1301 gate_lower_bound: lb,
1302 };
1303 let cfg = KdaCfg {
1304 num_heads: nh,
1305 head_k_dim: dk,
1306 head_v_dim: dv,
1307 conv_kernel: kk,
1308 hidden_size: hs,
1309 rms_eps: 1e-6,
1310 };
1311 let xs: Vec<Vec<f32>> = (0..6)
1312 .map(|t| {
1313 (0..hs)
1314 .map(|i| ((t * hs + i) as f32 * 0.37).sin() * 0.5)
1315 .collect()
1316 })
1317 .collect();
1318
1319 let mut state = Vec::new();
1321 let got: Vec<Vec<f32>> = xs
1322 .iter()
1323 .map(|x| kda_forward(x, &w, &cfg, &mut state, None))
1324 .collect();
1325
1326 let mv = |t: &QTensor, x: &[f32]| -> Vec<f32> {
1328 let mut o = vec![0.0f32; t.rows()];
1329 t.matvec(x, &mut o, None);
1330 o
1331 };
1332 let mut hist: Vec<(Vec<f32>, Vec<f32>, Vec<f32>)> = Vec::new(); let mut s_state = vec![0f64; nh * dk * dv];
1334 let mut want: Vec<Vec<f32>> = Vec::new();
1335 for x in &xs {
1336 let (xq, xk, xv) = (mv(&w.q_proj, x), mv(&w.k_proj, x), mv(&w.v_proj, x));
1337 hist.push((xq, xk, xv));
1338 let conv = |sel: fn(&(Vec<f32>, Vec<f32>, Vec<f32>)) -> &Vec<f32>,
1340 taps: &[f32],
1341 n: usize|
1342 -> Vec<f32> {
1343 (0..n)
1344 .map(|c| {
1345 let t = &taps[c * kk..(c + 1) * kk];
1346 let mut acc = 0f64;
1347 for j in 0..kk {
1348 let idx = hist.len() as i64 - (kk as i64 - j as i64);
1349 if idx >= 0 {
1350 acc += sel(&hist[idx as usize])[c] as f64 * t[j] as f64;
1351 }
1352 }
1353 silu(acc)
1354 })
1355 .map(|v| v as f32)
1356 .collect()
1357 };
1358 let cq = conv(|h| &h.0, &w.conv_q, nh * dk);
1359 let ck = conv(|h| &h.1, &w.conv_k, nh * dk);
1360 let cv = conv(|h| &h.2, &w.conv_v, nh * dv);
1361 let f = mv(&w.f_b, &mv(&w.f_a, x));
1362 let bb = mv(&w.b_proj, x);
1363 let gate_out = match &w.gate {
1364 KdaOutGate::LowRank(ga, gb) => mv(gb, &mv(ga, x)),
1365 KdaOutGate::Full(g) => mv(g, x),
1366 };
1367 let mut of = vec![0f32; nh * dv];
1368 for h in 0..nh {
1369 let q: Vec<f64> = {
1371 let sl = &cq[h * dk..(h + 1) * dk];
1372 let n: f64 = sl.iter().map(|&v| (v as f64) * (v as f64)).sum();
1373 let inv = 1.0 / ((n + 1e-6).sqrt() * (dk as f64).sqrt());
1374 sl.iter().map(|&v| v as f64 * inv).collect()
1375 };
1376 let k: Vec<f64> = {
1377 let sl = &ck[h * dk..(h + 1) * dk];
1378 let n: f64 = sl.iter().map(|&v| (v as f64) * (v as f64)).sum();
1379 let inv = 1.0 / (n + 1e-6).sqrt();
1380 sl.iter().map(|&v| v as f64 * inv).collect()
1381 };
1382 let v: Vec<f64> = cv[h * dv..(h + 1) * dv].iter().map(|&v| v as f64).collect();
1383 let g: Vec<f64> = (0..dk)
1385 .map(|d| {
1386 let a = if w.a_log.len() == nh {
1387 w.a_log[h] as f64
1388 } else {
1389 w.a_log[d] as f64
1390 };
1391 let raw = f[h * dk + d] as f64 + w.dt_bias[h * dk + d] as f64;
1392 match w.gate_lower_bound {
1393 Some(lb) => lb as f64 * sigmoid(a.exp() * raw),
1394 None => -a.exp() * softplus(raw),
1395 }
1396 })
1397 .collect();
1398 let beta = sigmoid(bb[h] as f64);
1399 let s = &mut s_state[h * dk * dv..(h + 1) * dk * dv];
1400 for di in 0..dk {
1402 for dj in 0..dv {
1403 s[di * dv + dj] *= g[di].exp();
1404 }
1405 }
1406 let mut kv = vec![0f64; dv];
1408 for di in 0..dk {
1409 for dj in 0..dv {
1410 kv[dj] += k[di] * s[di * dv + dj];
1411 }
1412 }
1413 for di in 0..dk {
1414 for dj in 0..dv {
1415 s[di * dv + dj] += beta * k[di] * (v[dj] - kv[dj]);
1416 }
1417 }
1418 let mut o = vec![0f64; dv];
1419 for di in 0..dk {
1420 for dj in 0..dv {
1421 o[dj] += q[di] * s[di * dv + dj];
1422 }
1423 }
1424 let ss: f64 = o.iter().map(|&v| v * v).sum();
1426 let inv = 1.0 / (ss / dv as f64 + cfg.rms_eps).sqrt();
1427 for dj in 0..dv {
1428 of[h * dv + dj] = (o[dj]
1429 * inv
1430 * w.o_norm[dj] as f64
1431 * sigmoid(gate_out[h * dv + dj] as f64))
1432 as f32;
1433 }
1434 }
1435 want.push(mv(&w.o_proj, &of));
1436 }
1437
1438 for (t, (g, e)) in got.iter().zip(&want).enumerate() {
1439 for (i, (a, b)) in g.iter().zip(e.iter()).enumerate() {
1440 assert!((a - b).abs() < 2e-4, "{label}: t={t} i={i}: {a} vs {b}");
1441 }
1442 }
1443 }
1444
1445 let w = KdaWeights {
1447 q_proj: synth(nh * dk, hs, 1),
1448 k_proj: synth(nh * dk, hs, 2),
1449 v_proj: synth(nh * dv, hs, 3),
1450 conv_q: vecf(nh * dk * kk, 4),
1451 conv_k: vecf(nh * dk * kk, 5),
1452 conv_v: vecf(nh * dv * kk, 6),
1453 f_a: synth(rank, hs, 7),
1454 f_b: synth(nh * dk, rank, 8),
1455 dt_bias: vecf(nh * dk, 9),
1456 a_log: vecf(nh, 40),
1457 b_proj: synth(nh, hs, 10),
1458 gate: KdaOutGate::LowRank(synth(rank, hs, 11), synth(nh * dv, rank, 12)),
1459 o_norm: (0..dv).map(|i| 1.0 + 0.1 * i as f32).collect(),
1460 o_proj: synth(hs, nh * dv, 13),
1461 gate_lower_bound: None,
1462 };
1463 let cfg = KdaCfg {
1464 num_heads: nh,
1465 head_k_dim: dk,
1466 head_v_dim: dv,
1467 conv_kernel: kk,
1468 hidden_size: hs,
1469 rms_eps: 1e-6,
1470 };
1471 let xs: Vec<f32> = (0..5 * hs).map(|i| (i as f32 * 0.29).cos() * 0.4).collect();
1472 let mut st1 = Vec::new();
1473 let seq: Vec<f32> = (0..5)
1474 .flat_map(|t| kda_forward(&xs[t * hs..(t + 1) * hs], &w, &cfg, &mut st1, None))
1475 .collect();
1476 let mut st2 = Vec::new();
1477 let bat = kda_forward_batch(&xs, 5, &w, &cfg, &mut st2, None);
1478 for (i, (a, b)) in seq.iter().zip(&bat).enumerate() {
1479 assert!((a - b).abs() < 1e-5, "batch i={i}: {a} vs {b}");
1480 }
1481 assert_eq!(st1, st2, "state must match after the chunk");
1482 }
1483
1484 use super::*;
1485
1486 fn tiny() -> (VmfPhaseWeights, VmfPhaseCfg) {
1487 let cfg = VmfPhaseCfg {
1488 num_heads: 2,
1489 nphase: 3,
1490 value_head_dim: 4,
1491 hidden_size: 8,
1492 phase_mass: 0.0,
1493 };
1494 let synth = |rows: usize, cols: usize, salt: usize| {
1495 QTensor::from_f32(
1496 (0..rows * cols)
1497 .map(|i| (((i * 13 + salt * 7) % 97) as f32 / 97.0 - 0.5) * 0.4)
1498 .collect(),
1499 rows,
1500 cols,
1501 )
1502 };
1503 let w = VmfPhaseWeights {
1504 thq: synth(cfg.num_heads * cfg.nphase, cfg.hidden_size, 1),
1505 thk: synth(cfg.num_heads * cfg.nphase, cfg.hidden_size, 2),
1506 v_proj: synth(cfg.num_heads * cfg.value_head_dim, cfg.hidden_size, 3),
1507 out_proj: synth(cfg.hidden_size, cfg.num_heads * cfg.value_head_dim, 4),
1508 decay: (0..cfg.num_heads * 2 * cfg.nphase)
1509 .map(|i| 0.9 + 0.005 * (i % 10) as f64)
1510 .collect(),
1511 k_gate: None,
1512 };
1513 (w, cfg)
1514 }
1515
1516 #[test]
1517 fn state_persists_and_changes_output() {
1518 let (w, cfg) = tiny();
1519 let x: Vec<f32> = (0..8).map(|i| (i as f32 * 0.3).sin()).collect();
1520 let mut state = Vec::new();
1521 let o1 = vmf_phase_forward(&x, &w, &cfg, &mut state, None);
1522 let o2 = vmf_phase_forward(&x, &w, &cfg, &mut state, None);
1523 assert!(o1.iter().zip(&o2).any(|(a, b)| (a - b).abs() > 1e-6));
1525 assert_eq!(state.len(), cfg.state_len());
1526 }
1527
1528 #[test]
1532 fn phase_mass_zero_is_noop_and_positive_shifts() {
1533 let (w, cfg0) = tiny();
1534 let mut cfg_m = cfg0;
1535 cfg_m.phase_mass = 1.0;
1536 let x: Vec<f32> = (0..8).map(|i| (i as f32 * 0.4).sin()).collect();
1537
1538 let mut s0 = Vec::new();
1539 let base = vmf_phase_forward(&x, &w, &cfg0, &mut s0, None);
1540 let mut s0b = Vec::new();
1542 let base2 = vmf_phase_forward(&x, &w, &cfg0, &mut s0b, None);
1543 assert_eq!(base, base2, "mass=0 must be deterministic/no-op");
1544 let mut sm = Vec::new();
1546 let massed = vmf_phase_forward(&x, &w, &cfg_m, &mut sm, None);
1547 assert!(
1548 base.iter().zip(&massed).any(|(a, b)| (a - b).abs() > 1e-5),
1549 "mass>0 must change the output"
1550 );
1551 assert!(massed.iter().all(|v| v.is_finite()));
1552 }
1553
1554 #[test]
1559 fn kappa_gate_open_matches_none_and_closed_writes_nothing() {
1560 let (mut w, cfg) = tiny();
1561 let x: Vec<f32> = (0..8).map(|i| (i as f32 * 0.3).sin()).collect();
1562
1563 let mut s_none = Vec::new();
1564 let base1 = vmf_phase_forward(&x, &w, &cfg, &mut s_none, None);
1565 let base2 = vmf_phase_forward(&x, &w, &cfg, &mut s_none, None);
1566
1567 w.k_gate = Some((
1569 QTensor::from_f32(
1570 vec![0.0; cfg.num_heads * cfg.hidden_size],
1571 cfg.num_heads,
1572 cfg.hidden_size,
1573 ),
1574 vec![20.0; cfg.num_heads],
1575 ));
1576 let mut s_open = Vec::new();
1577 let o1 = vmf_phase_forward(&x, &w, &cfg, &mut s_open, None);
1578 let o2 = vmf_phase_forward(&x, &w, &cfg, &mut s_open, None);
1579 for (a, b) in base1.iter().zip(&o1).chain(base2.iter().zip(&o2)) {
1580 assert!(
1581 (a - b).abs() < 1e-5,
1582 "open κ must match gateless: {a} vs {b}"
1583 );
1584 }
1585
1586 w.k_gate = Some((
1588 QTensor::from_f32(
1589 vec![0.0; cfg.num_heads * cfg.hidden_size],
1590 cfg.num_heads,
1591 cfg.hidden_size,
1592 ),
1593 vec![-20.0; cfg.num_heads],
1594 ));
1595 let mut s_closed = Vec::new();
1596 let oc = vmf_phase_forward(&x, &w, &cfg, &mut s_closed, None);
1597 assert!(
1598 s_closed.iter().all(|&v| v.abs() < 1e-7),
1599 "closed κ: state must stay empty"
1600 );
1601 assert!(
1602 oc.iter().all(|&v| v.abs() < 1e-6),
1603 "closed κ: empty-condensate readout"
1604 );
1605 }
1606
1607 #[test]
1608 fn pair_matches_two_singles_bitexact() {
1609 let (w, cfg) = tiny();
1610 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.2).cos()).collect();
1611 let x2: Vec<f32> = (0..8).map(|i| (i as f32 * 0.5).sin()).collect();
1612
1613 let mut s_ref = Vec::new();
1615 let r1 = vmf_phase_forward(&x1, &w, &cfg, &mut s_ref, None);
1616 let r2 = vmf_phase_forward(&x2, &w, &cfg, &mut s_ref, None);
1617
1618 let mut s = Vec::new();
1620 let mut scratch = Vec::new();
1621 let (p1, p2) = vmf_phase_pair(&x1, &x2, &w, &cfg, &mut s, &mut scratch, None);
1622 assert_eq!(r1, p1, "lane 1 must be bit-identical");
1623 assert_eq!(r2, p2, "lane 2 must be bit-identical");
1624 std::mem::swap(&mut s, &mut scratch);
1626 assert_eq!(s, s_ref, "accepted state must equal sequential state");
1627 }
1628
1629 #[test]
1630 fn rejected_draft_leaves_state_at_lane1() {
1631 let (w, cfg) = tiny();
1632 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.7).sin()).collect();
1633 let x2 = vec![0.5f32; 8];
1634
1635 let mut s_ref = Vec::new();
1636 let _ = vmf_phase_forward(&x1, &w, &cfg, &mut s_ref, None);
1637
1638 let mut s = Vec::new();
1639 let mut scratch = Vec::new();
1640 let _ = vmf_phase_pair(&x1, &x2, &w, &cfg, &mut s, &mut scratch, None);
1641 assert_eq!(s, s_ref);
1643 }
1644
1645 fn tiny_gdn() -> (GdnWeights, GdnCfg) {
1648 let cfg = GdnCfg {
1649 num_v_heads: 4,
1650 num_k_heads: 2,
1651 key_head_dim: 3,
1652 value_head_dim: 5,
1653 conv_kernel: 4,
1654 hidden_size: 8,
1655 rms_eps: 1e-6,
1656 };
1657 let c_dim = cfg.conv_dim();
1658 let vd = cfg.num_v_heads * cfg.value_head_dim;
1659 let synth = |rows: usize, cols: usize, salt: usize| {
1660 QTensor::from_f32(
1661 (0..rows * cols)
1662 .map(|i| (((i * 13 + salt * 7) % 97) as f32 / 97.0 - 0.5) * 0.4)
1663 .collect(),
1664 rows,
1665 cols,
1666 )
1667 };
1668 let vecf = |n: usize, salt: usize| -> Vec<f32> {
1669 (0..n)
1670 .map(|i| (((i * 11 + salt * 5) % 89) as f32 / 89.0 - 0.5) * 0.6)
1671 .collect()
1672 };
1673 let w = GdnWeights {
1674 in_proj_qkv: synth(c_dim, cfg.hidden_size, 1),
1675 in_proj_z: synth(vd, cfg.hidden_size, 2),
1676 in_proj_a: synth(cfg.num_v_heads, cfg.hidden_size, 3),
1677 in_proj_b: synth(cfg.num_v_heads, cfg.hidden_size, 4),
1678 conv1d: vecf(c_dim * cfg.conv_kernel, 5),
1679 a_log: (0..cfg.num_v_heads).map(|i| 0.2 + 0.3 * i as f32).collect(),
1680 dt_bias: vecf(cfg.num_v_heads, 6),
1681 norm: vec![1.0; cfg.value_head_dim],
1682 out_proj: synth(cfg.hidden_size, vd, 7),
1683 };
1684 (w, cfg)
1685 }
1686
1687 #[test]
1688 fn gdn_state_persists_and_changes_output() {
1689 let (w, cfg) = tiny_gdn();
1690 let x: Vec<f32> = (0..8).map(|i| (i as f32 * 0.3).sin()).collect();
1691 let mut state = Vec::new();
1692 let o1 = gdn_forward(&x, &w, &cfg, &mut state, None);
1693 let o2 = gdn_forward(&x, &w, &cfg, &mut state, None);
1694 assert!(o1.iter().zip(&o2).any(|(a, b)| (a - b).abs() > 1e-6));
1695 assert_eq!(state.len(), cfg.state_len());
1696 }
1697
1698 #[test]
1699 fn gdn_pair_matches_two_singles_bitexact() {
1700 let (w, cfg) = tiny_gdn();
1701 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.2).cos()).collect();
1702 let x2: Vec<f32> = (0..8).map(|i| (i as f32 * 0.5).sin()).collect();
1703
1704 let mut s_ref = Vec::new();
1705 let r1 = gdn_forward(&x1, &w, &cfg, &mut s_ref, None);
1706 let r2 = gdn_forward(&x2, &w, &cfg, &mut s_ref, None);
1707
1708 let mut s = Vec::new();
1709 let mut scratch = Vec::new();
1710 let (p1, p2) = gdn_pair(&x1, &x2, &w, &cfg, &mut s, &mut scratch, None);
1711 assert_eq!(r1, p1, "lane 1 must be bit-identical");
1712 assert_eq!(r2, p2, "lane 2 must be bit-identical");
1713 std::mem::swap(&mut s, &mut scratch);
1714 assert_eq!(s, s_ref, "accepted state must equal sequential state");
1715 }
1716
1717 #[test]
1718 fn gdn_rejected_draft_leaves_state_at_lane1() {
1719 let (w, cfg) = tiny_gdn();
1720 let x1: Vec<f32> = (0..8).map(|i| (i as f32 * 0.7).sin()).collect();
1721 let x2 = vec![0.5f32; 8];
1722
1723 let mut s_ref = Vec::new();
1724 let _ = gdn_forward(&x1, &w, &cfg, &mut s_ref, None);
1725
1726 let mut s = Vec::new();
1727 let mut scratch = Vec::new();
1728 let _ = gdn_pair(&x1, &x2, &w, &cfg, &mut s, &mut scratch, None);
1729 assert_eq!(s, s_ref);
1730 }
1731
1732 #[test]
1736 fn gdn_conv_ring_matches_explicit_causal_conv() {
1737 let (w, cfg) = tiny_gdn();
1738 let seq: Vec<Vec<f32>> = (0..6)
1739 .map(|t| (0..8).map(|i| ((t * 8 + i) as f32 * 0.17).sin()).collect())
1740 .collect();
1741
1742 let mut s_inc = Vec::new();
1745 for (t, x) in seq.iter().enumerate() {
1746 let inc = gdn_forward(x, &w, &cfg, &mut s_inc, None);
1747 let mut s_replay = Vec::new();
1748 let mut replay = Vec::new();
1749 for xr in &seq[..=t] {
1750 replay = gdn_forward(xr, &w, &cfg, &mut s_replay, None);
1751 }
1752 assert_eq!(inc, replay, "position {t}: ring must equal replay");
1753 }
1754 }
1755
1756 fn tiny_short_conv() -> (ShortConvWeights, ShortConvCfg) {
1757 let cfg = ShortConvCfg {
1758 hidden_size: 8,
1759 kernel: 3,
1760 };
1761 let synth = |rows: usize, cols: usize, salt: usize| {
1762 QTensor::from_f32(
1763 (0..rows * cols)
1764 .map(|i| (((i * 11 + salt * 5) % 89) as f32 / 89.0 - 0.5) * 0.5)
1765 .collect(),
1766 rows,
1767 cols,
1768 )
1769 };
1770 let w = ShortConvWeights {
1771 in_proj: synth(3 * cfg.hidden_size, cfg.hidden_size, 1),
1772 conv: (0..cfg.hidden_size * cfg.kernel)
1773 .map(|i| ((i * 7 % 13) as f32 / 13.0 - 0.5) * 0.8)
1774 .collect(),
1775 out_proj: synth(cfg.hidden_size, cfg.hidden_size, 2),
1776 };
1777 (w, cfg)
1778 }
1779
1780 #[test]
1783 fn short_conv_ring_matches_explicit_causal_conv() {
1784 let (w, cfg) = tiny_short_conv();
1785 let seq: Vec<Vec<f32>> = (0..6)
1786 .map(|t| (0..8).map(|i| ((t * 8 + i) as f32 * 0.19).cos()).collect())
1787 .collect();
1788 let mut s_inc = Vec::new();
1789 for (t, x) in seq.iter().enumerate() {
1790 let inc = short_conv_forward(x, &w, &cfg, &mut s_inc, None);
1791 let mut s_replay = Vec::new();
1792 let mut replay = Vec::new();
1793 for xr in &seq[..=t] {
1794 replay = short_conv_forward(xr, &w, &cfg, &mut s_replay, None);
1795 }
1796 assert_eq!(inc, replay, "position {t}: ring must equal replay");
1797 assert_eq!(s_inc.len(), cfg.state_len());
1798 }
1799 }
1800
1801 #[test]
1804 fn short_conv_batch_matches_sequential() {
1805 let (w, cfg) = tiny_short_conv();
1806 let b = 5;
1807 let xs: Vec<f32> = (0..b * cfg.hidden_size)
1808 .map(|i| (i as f32 * 0.13).sin() * 0.6)
1809 .collect();
1810
1811 let mut s_seq = Vec::new();
1812 let mut seq_out = vec![0.0f32; b * cfg.hidden_size];
1813 for bi in 0..b {
1814 let o = short_conv_forward(
1815 &xs[bi * cfg.hidden_size..(bi + 1) * cfg.hidden_size],
1816 &w,
1817 &cfg,
1818 &mut s_seq,
1819 None,
1820 );
1821 seq_out[bi * cfg.hidden_size..(bi + 1) * cfg.hidden_size].copy_from_slice(&o);
1822 }
1823
1824 let mut s_batch = Vec::new();
1825 let batch_out = short_conv_forward_batch(&xs, b, &w, &cfg, &mut s_batch, None);
1826 assert_eq!(
1827 seq_out, batch_out,
1828 "batch conv must match sequential decode"
1829 );
1830 assert_eq!(s_seq, s_batch, "ring state must match after the chunk");
1831 }
1832}