1pub fn hc_split_sinkhorn(
29 mixes: &[f32],
30 hc_scale: &[f32; 3],
31 hc_base: &[f32],
32 hc: usize,
33 iters: usize,
34 eps: f32,
35 pre: &mut [f32],
36 post: &mut [f32],
37 comb: &mut [f32],
38) {
39 debug_assert_eq!(mixes.len(), (2 + hc) * hc);
40 debug_assert_eq!(comb.len(), hc * hc);
41 for j in 0..hc {
42 pre[j] = sigmoid(mixes[j] * hc_scale[0] + hc_base[j]) + eps;
43 post[j] = 2.0 * sigmoid(mixes[j + hc] * hc_scale[1] + hc_base[j + hc]);
46 }
47 for j in 0..hc {
48 for k in 0..hc {
49 let idx = j * hc + k + hc * 2;
50 comb[j * hc + k] = mixes[idx] * hc_scale[2] + hc_base[idx];
51 }
52 }
53 for j in 0..hc {
55 let row = &mut comb[j * hc..(j + 1) * hc];
56 let m = row.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
57 let mut sum = 0.0;
58 for v in row.iter_mut() {
59 *v = (*v - m).exp();
60 sum += *v;
61 }
62 for v in row.iter_mut() {
63 *v = *v / sum + eps;
64 }
65 }
66 normalize_cols(comb, hc, eps);
68 for _ in 0..iters.saturating_sub(1) {
69 normalize_rows(comb, hc, eps);
70 normalize_cols(comb, hc, eps);
71 }
72}
73
74fn normalize_rows(m: &mut [f32], n: usize, eps: f32) {
75 for j in 0..n {
76 let s: f32 = m[j * n..(j + 1) * n].iter().sum::<f32>() + eps;
77 for v in m[j * n..(j + 1) * n].iter_mut() {
78 *v /= s;
79 }
80 }
81}
82
83fn normalize_cols(m: &mut [f32], n: usize, eps: f32) {
84 for k in 0..n {
85 let mut s = eps;
86 for j in 0..n {
87 s += m[j * n + k];
88 }
89 for j in 0..n {
90 m[j * n + k] /= s;
91 }
92 }
93}
94
95#[inline]
96fn sigmoid(x: f32) -> f32 {
97 1.0 / (1.0 + (-x).exp())
98}
99
100pub fn hc_mixes(
104 x_flat: &[f32],
105 hc_fn: &[f32],
106 mix_hc: usize,
107 eps: f32,
108 pool: Option<&crate::pool::Pool>,
109 out: &mut [f32],
110) {
111 let n = x_flat.len();
112 debug_assert_eq!(hc_fn.len(), mix_hc * n);
113 debug_assert_eq!(out.len(), mix_hc);
114 let ms = x_flat.iter().map(|v| v * v).sum::<f32>() / n as f32;
115 let rsqrt = 1.0 / (ms + eps).sqrt();
116 match pool {
120 Some(p) if n >= 4096 => {
121 let addr = crate::pool::SendMut::new(out.as_mut_ptr());
122 p.run_rows(mix_hc, &|start, end| {
123 for i in start..end {
124 let row = &hc_fn[i * n..(i + 1) * n];
125 let v = row.iter().zip(x_flat).map(|(a, b)| a * b).sum::<f32>() * rsqrt;
126 unsafe { *addr.at(i) = v };
127 }
128 });
129 }
130 _ => {
131 for (i, o) in out.iter_mut().enumerate() {
132 let row = &hc_fn[i * n..(i + 1) * n];
133 *o = row.iter().zip(x_flat).map(|(a, b)| a * b).sum::<f32>() * rsqrt;
134 }
135 }
136 }
137}
138
139pub fn hc_fold(x: &[f32], pre: &[f32], hc: usize, dim: usize, out: &mut [f32]) {
141 debug_assert_eq!(x.len(), hc * dim);
142 out.fill(0.0);
143 for j in 0..hc {
144 let w = pre[j];
145 let src = &x[j * dim..(j + 1) * dim];
146 for (o, v) in out.iter_mut().zip(src) {
147 *o += w * v;
148 }
149 }
150}
151
152pub fn hc_expand(
159 block_out: &[f32],
160 residual: &[f32],
161 post: &[f32],
162 comb: &[f32],
163 hc: usize,
164 dim: usize,
165 out: &mut [f32],
166) {
167 debug_assert_eq!(residual.len(), hc * dim);
168 debug_assert_eq!(out.len(), hc * dim);
169 for j in 0..hc {
170 let dst = &mut out[j * dim..(j + 1) * dim];
171 let p = post[j];
172 for (d, o) in dst.iter_mut().enumerate() {
173 *o = p * block_out[d];
174 }
175 for k in 0..hc {
176 let w = comb[k * hc + j];
177 let src = &residual[k * dim..(k + 1) * dim];
178 for (o, v) in dst.iter_mut().zip(src) {
179 *o += w * v;
180 }
181 }
182 }
183}
184
185pub fn hc_head_pre(mixes: &[f32], scale: f32, base: &[f32], hc: usize, eps: f32, pre: &mut [f32]) {
188 for j in 0..hc {
189 pre[j] = sigmoid(mixes[j] * scale + base[j]) + eps;
190 }
191}
192
193pub fn route(
207 scores_in: &[f32],
208 bias: Option<&[f32]>,
209 top_k: usize,
210 route_scale: f32,
211 forced: Option<&[usize]>,
212 mask: Option<&[bool]>,
213 indices: &mut Vec<usize>,
214 weights: &mut Vec<f32>,
215) {
216 let n = scores_in.len();
217 let mut scores = Vec::with_capacity(n);
218 for &s in scores_in {
219 let sp = if s > 20.0 { s } else { (1.0 + s.exp()).ln() };
221 scores.push(sp.sqrt());
222 }
223 indices.clear();
224 weights.clear();
225 match forced {
226 Some(f) => indices.extend(f.iter().copied()),
227 None => {
228 let mut shifted: Vec<f32> = match bias {
229 Some(b) => scores.iter().zip(b).map(|(s, b)| s + b).collect(),
230 None => scores.clone(),
231 };
232 if let Some(m) = mask {
233 for (i, s) in shifted.iter_mut().enumerate() {
234 if !m.get(i).copied().unwrap_or(true) {
235 *s = f32::NEG_INFINITY;
236 }
237 }
238 }
239 for _ in 0..top_k.min(n) {
240 let mut best = 0usize;
241 let mut bv = f32::NEG_INFINITY;
242 for (i, &v) in shifted.iter().enumerate() {
243 if v > bv {
244 bv = v;
245 best = i;
246 }
247 }
248 if !bv.is_finite() {
249 break;
250 }
251 indices.push(best);
252 shifted[best] = f32::NEG_INFINITY;
253 }
254 }
255 }
256 for &i in indices.iter() {
258 weights.push(scores.get(i).copied().unwrap_or(0.0));
259 }
260 let sum: f32 = weights.iter().sum();
261 if sum > 0.0 {
262 for w in weights.iter_mut() {
263 *w = *w / sum * route_scale;
264 }
265 }
266}
267
268pub fn hash_route(tid2eid: &[f32], vocab: usize, top_k: usize, tid: u32) -> Vec<usize> {
272 let row = (tid as usize).min(vocab.saturating_sub(1)) * top_k;
273 (0..top_k)
274 .map(|k| tid2eid.get(row + k).copied().unwrap_or(0.0) as usize)
275 .collect()
276}
277
278pub fn rope_tail(v: &mut [f32], inv_freq: &[f32], pos: usize, rd: usize, inverse: bool) {
285 let n = v.len();
286 debug_assert!(
287 rd <= n && rd % 2 == 0,
288 "rope tail {rd} wider than the vector {n}"
289 );
290 let rd = rd.min(n) & !1;
293 let base = n - rd;
294 for i in 0..rd / 2 {
303 let theta = pos as f32 * inv_freq[i];
304 let (s, c) = (theta.sin(), theta.cos());
305 let s = if inverse { -s } else { s };
306 let a = v[base + 2 * i];
307 let b = v[base + 2 * i + 1];
308 v[base + 2 * i] = a * c - b * s;
309 v[base + 2 * i + 1] = a * s + b * c;
310 }
311}
312
313pub fn rms_inplace(v: &mut [f32], eps: f32) {
317 let ms = v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32;
318 let inv = 1.0 / (ms + eps).sqrt();
319 for x in v.iter_mut() {
320 *x *= inv;
321 }
322}
323
324pub fn sparse_attend(
330 q: &[f32],
331 kv: &[f32],
332 idxs: &[usize],
333 sink: f32,
334 scale: f32,
335 head_dim: usize,
336 out: &mut [f32],
337) {
338 let mut m = sink;
339 let mut scores = Vec::with_capacity(idxs.len());
340 for &p in idxs {
341 if p == usize::MAX {
342 scores.push(f32::NEG_INFINITY);
343 continue;
344 }
345 let k = &kv[p * head_dim..(p + 1) * head_dim];
346 let dot: f32 = q.iter().zip(k).map(|(a, b)| a * b).sum::<f32>() * scale;
347 m = m.max(dot);
348 scores.push(dot);
349 }
350 let mut denom = (sink - m).exp();
351 out.fill(0.0);
352 for (&p, &s) in idxs.iter().zip(&scores) {
353 if p == usize::MAX {
354 continue;
355 }
356 let w = (s - m).exp();
357 denom += w;
358 let v = &kv[p * head_dim..(p + 1) * head_dim];
359 for (o, x) in out.iter_mut().zip(v) {
360 *o += w * x;
361 }
362 }
363 if std::env::var("CMF_ATTN_DEBUG").is_ok() {
364 eprintln!(
365 " [port] positions={} score={:?} sink={sink:.4} denom={denom:.4} |q|={:.3}",
366 idxs.iter().filter(|&&p| p != usize::MAX).count(),
367 scores
368 .iter()
369 .map(|x| (x * 10000.0).round() / 10000.0)
370 .collect::<Vec<_>>(),
371 q.iter().map(|x| x * x).sum::<f32>().sqrt()
372 );
373 }
374 let inv = 1.0 / denom;
375 for o in out.iter_mut() {
376 *o *= inv;
377 }
378}
379
380pub fn o_project(
389 attn: &[f32],
390 wo_a_row: &(dyn Fn(usize, &[f32], &mut [f32]) -> f32 + Sync),
391 scratch_len: usize,
392 wo_b: &dyn Fn(&[f32], &mut [f32]),
393 groups: usize,
394 lora: usize,
395 pool: Option<&crate::pool::Pool>,
396 out: &mut [f32],
397) {
398 let per_group = attn.len() / groups;
399 let mut mid = vec![0.0f32; groups * lora];
400 let slice_of = |i: usize| {
401 let g = i / lora;
402 &attn[g * per_group..(g + 1) * per_group]
403 };
404 match pool {
405 Some(p) if mid.len() >= 256 => {
410 let addr = crate::pool::SendMut::new(mid.as_mut_ptr());
411 p.run_rows(mid.len(), &|start, end| {
412 let mut sc = vec![0.0f32; scratch_len];
413 for i in start..end {
414 let v = wo_a_row(i, slice_of(i), &mut sc);
415 unsafe { *addr.at(i) = v };
416 }
417 });
418 }
419 _ => {
420 let mut sc = vec![0.0f32; scratch_len];
421 for (i, m) in mid.iter_mut().enumerate() {
422 *m = wo_a_row(i, slice_of(i), &mut sc);
423 }
424 }
425 }
426 wo_b(&mid, out);
427}
428
429pub fn compress_window(
430 kv: &[f32],
431 score: &[f32],
432 ape: &[f32],
433 ratio: usize,
434 width: usize,
435 out: &mut [f32],
436) {
437 debug_assert_eq!(kv.len(), ratio * width);
438 debug_assert_eq!(ape.len(), ratio * width);
439 let biased: Vec<f32> = score.iter().zip(ape).map(|(s, a)| s + a).collect();
440 pool_by_score(kv, &biased, ratio, width, out);
441}
442
443pub fn pool_by_score(kv: &[f32], score: &[f32], slots: usize, width: usize, out: &mut [f32]) {
448 debug_assert_eq!(kv.len(), slots * width);
449 debug_assert_eq!(score.len(), slots * width);
450 out.fill(0.0);
451 for d in 0..width {
452 let mut m = f32::NEG_INFINITY;
453 for t in 0..slots {
454 m = m.max(score[t * width + d]);
455 }
456 if !m.is_finite() {
457 continue;
458 }
459 let mut denom = 0.0;
460 for t in 0..slots {
461 denom += (score[t * width + d] - m).exp();
462 }
463 if denom <= 0.0 {
464 continue;
465 }
466 for t in 0..slots {
467 out[d] += ((score[t * width + d] - m).exp() / denom) * kv[t * width + d];
468 }
469 }
470}
471
472#[allow(clippy::too_many_arguments)]
481pub fn compress_window_overlap(
482 prev_kv: &[f32],
483 prev_score: &[f32],
484 cur_kv: &[f32],
485 cur_score: &[f32],
486 ratio: usize,
487 d: usize,
488 out: &mut [f32],
489) {
490 let slots = 2 * ratio;
491 let mut kv = vec![0.0f32; slots * d];
492 let mut sc = vec![f32::NEG_INFINITY; slots * d];
493 let have_prev = prev_kv.len() == ratio * 2 * d;
494 for t in 0..ratio {
495 if have_prev {
496 kv[t * d..(t + 1) * d].copy_from_slice(&prev_kv[t * 2 * d..t * 2 * d + d]);
498 sc[t * d..(t + 1) * d].copy_from_slice(&prev_score[t * 2 * d..t * 2 * d + d]);
499 }
500 let src = t * 2 * d + d;
502 let dst = (ratio + t) * d;
503 kv[dst..dst + d].copy_from_slice(&cur_kv[src..src + d]);
504 sc[dst..dst + d].copy_from_slice(&cur_score[src..src + d]);
505 }
506 pool_by_score(&kv, &sc, slots, d, out);
507}
508
509#[allow(clippy::too_many_arguments)]
524pub fn index_scores(
525 q_heads: &[f32],
526 kv: &[f32],
527 head_weights: &[f32],
528 n_heads: usize,
529 head_dim: usize,
530 n_pos: usize,
531 causal_limit: usize,
532 pool: Option<&crate::pool::Pool>,
533 out: &mut Vec<f32>,
534) {
535 out.clear();
536 out.resize(n_pos, 0.0);
537 let score_at = |t: usize| -> f32 {
538 if t >= causal_limit {
539 return f32::NEG_INFINITY;
540 }
541 let k = &kv[t * head_dim..(t + 1) * head_dim];
542 let mut acc = 0.0;
543 for h in 0..n_heads {
544 let q = &q_heads[h * head_dim..(h + 1) * head_dim];
545 let dot: f32 = q.iter().zip(k).map(|(a, b)| a * b).sum();
546 acc += dot.max(0.0) * head_weights[h];
548 }
549 acc
550 };
551 match pool {
555 Some(p) if n_pos >= 64 => {
556 let addr = crate::pool::SendMut::new(out.as_mut_ptr());
557 p.run_rows(n_pos, &|start, end| {
558 for t in start..end {
559 unsafe { *addr.at(t) = score_at(t) };
560 }
561 });
562 }
563 _ => {
564 for (t, o) in out.iter_mut().enumerate() {
565 *o = score_at(t);
566 }
567 }
568 }
569}
570
571pub fn top_k_positions(scores: &[f32], k: usize, out: &mut Vec<usize>) {
575 out.clear();
576 if k >= scores.len() {
583 out.extend(
584 scores
585 .iter()
586 .enumerate()
587 .filter(|(_, v)| v.is_finite())
588 .map(|(i, _)| i),
589 );
590 return;
591 }
592 let mut taken = vec![false; scores.len()];
593 for _ in 0..k.min(scores.len()) {
594 let mut best = usize::MAX;
595 let mut bv = f32::NEG_INFINITY;
596 for (i, &v) in scores.iter().enumerate() {
597 if !taken[i] && v > bv && v.is_finite() {
598 bv = v;
599 best = i;
600 }
601 }
602 if best == usize::MAX {
603 break;
604 }
605 taken[best] = true;
606 out.push(best);
607 }
608 out.sort_unstable();
609}
610
611#[allow(clippy::too_many_arguments)]
619pub fn expert_swiglu(
620 x: &[f32],
621 w1: &dyn Fn(&[f32], &mut [f32]),
622 w3: &dyn Fn(&[f32], &mut [f32]),
623 w2: &dyn Fn(&[f32], &mut [f32]),
624 inter: usize,
625 weight: f32,
626 limit: f32,
627 out: &mut [f32],
628) {
629 let mut gate = vec![0.0f32; inter];
630 let mut up = vec![0.0f32; inter];
631 w1(x, &mut gate);
632 w3(x, &mut up);
633 if limit > 0.0 {
634 for u in up.iter_mut() {
635 *u = u.clamp(-limit, limit);
636 }
637 for g in gate.iter_mut() {
638 *g = g.min(limit);
639 }
640 }
641 for (g, u) in gate.iter_mut().zip(&up) {
642 let silu = *g / (1.0 + (-*g).exp());
643 *g = silu * u * weight;
644 }
645 w2(&gate, out);
646}
647
648#[derive(Debug, Clone, Copy)]
651pub struct Dsv4Cfg {
652 pub dim: usize,
653 pub n_heads: usize,
654 pub head_dim: usize,
655 pub rope_head_dim: usize,
656 pub q_lora_rank: usize,
657 pub o_lora_rank: usize,
658 pub o_groups: usize,
659 pub hc_mult: usize,
660 pub hc_sinkhorn_iters: usize,
661 pub hc_eps: f32,
662 pub norm_eps: f32,
663 pub n_routed_experts: usize,
664 pub top_k: usize,
665 pub moe_inter: usize,
666 pub route_scale: f32,
667 pub swiglu_limit: f32,
669 pub window: usize,
671 pub index_topk: usize,
672 pub vocab: usize,
673}
674
675#[allow(clippy::too_many_arguments)]
683#[allow(clippy::too_many_arguments)]
684pub fn hc_block<F: FnMut(&[f32], &mut [f32])>(
685 state: &mut [f32],
686 hc_fn: &[f32],
687 hc_scale: &[f32; 3],
688 hc_base: &[f32],
689 norm_w: &[f32],
690 cfg: &Dsv4Cfg,
691 scratch: &mut HcScratch,
692 pool: Option<&crate::pool::Pool>,
693 mut block: F,
694) {
695 let (hc, dim) = (cfg.hc_mult, cfg.dim);
696 let mix_hc = (2 + hc) * hc;
697 hc_mixes(state, hc_fn, mix_hc, cfg.norm_eps, pool, &mut scratch.mixes);
698 hc_split_sinkhorn(
699 &scratch.mixes,
700 hc_scale,
701 hc_base,
702 hc,
703 cfg.hc_sinkhorn_iters,
704 cfg.hc_eps,
705 &mut scratch.pre,
706 &mut scratch.post,
707 &mut scratch.comb,
708 );
709 hc_fold(state, &scratch.pre, hc, dim, &mut scratch.folded);
710 let ms = scratch.folded.iter().map(|v| v * v).sum::<f32>() / dim as f32;
712 let inv = 1.0 / (ms + cfg.norm_eps).sqrt();
713 for (v, w) in scratch.folded.iter_mut().zip(norm_w) {
714 *v = *v * inv * w;
715 }
716 block(&scratch.folded, &mut scratch.block_out);
717 scratch.residual.copy_from_slice(state);
718 hc_expand(
719 &scratch.block_out,
720 &scratch.residual,
721 &scratch.post,
722 &scratch.comb,
723 hc,
724 dim,
725 state,
726 );
727}
728
729pub struct HcScratch {
732 pub mixes: Vec<f32>,
733 pub pre: Vec<f32>,
734 pub post: Vec<f32>,
735 pub comb: Vec<f32>,
736 pub folded: Vec<f32>,
737 pub block_out: Vec<f32>,
738 pub residual: Vec<f32>,
739}
740
741impl HcScratch {
742 pub fn new(cfg: &Dsv4Cfg) -> Self {
743 let (hc, dim) = (cfg.hc_mult, cfg.dim);
744 Self {
745 mixes: vec![0.0; (2 + hc) * hc],
746 pre: vec![0.0; hc],
747 post: vec![0.0; hc],
748 comb: vec![0.0; hc * hc],
749 folded: vec![0.0; dim],
750 block_out: vec![0.0; dim],
751 residual: vec![0.0; hc * dim],
752 }
753 }
754}
755
756pub fn hc_head_fold(
759 state: &[f32],
760 hc_fn: &[f32],
761 hc_scale: f32,
762 hc_base: &[f32],
763 cfg: &Dsv4Cfg,
764 pool: Option<&crate::pool::Pool>,
765 out: &mut [f32],
766) {
767 let (hc, dim) = (cfg.hc_mult, cfg.dim);
768 let mut mixes = vec![0.0f32; hc];
769 hc_mixes(state, hc_fn, hc, cfg.norm_eps, pool, &mut mixes);
770 let mut pre = vec![0.0f32; hc];
771 hc_head_pre(&mixes, hc_scale, hc_base, hc, cfg.hc_eps, &mut pre);
772 hc_fold(state, &pre, hc, dim, out);
773}
774
775pub struct Dsv4Layer {
781 pub attn_norm: Vec<f32>,
782 pub ffn_norm: Vec<f32>,
783 pub wq_a: crate::qtensor::QTensor,
785 pub q_norm: Vec<f32>,
786 pub wq_b: crate::qtensor::QTensor,
787 pub wkv: crate::qtensor::QTensor,
788 pub kv_norm: Vec<f32>,
789 pub wo_a: crate::qtensor::QTensor,
790 pub wo_b: crate::qtensor::QTensor,
791 pub attn_sink: Vec<f32>,
792 pub compressor: Option<Dsv4Compressor>,
794 pub indexer: Option<Dsv4Indexer>,
796 pub hc_attn_fn: Vec<f32>,
798 pub hc_attn_base: Vec<f32>,
799 pub hc_attn_scale: [f32; 3],
800 pub hc_ffn_fn: Vec<f32>,
801 pub hc_ffn_base: Vec<f32>,
802 pub hc_ffn_scale: [f32; 3],
803 pub gate: crate::qtensor::QTensor,
805 pub gate_bias: Option<Vec<f32>>,
807 pub tid2eid: Option<Vec<f32>>,
809 pub experts: Vec<Dsv4Expert>,
810 pub shared: Dsv4Expert,
811 pub mask: Option<Vec<bool>>,
817}
818
819pub struct Dsv4Expert {
820 pub w1: crate::qtensor::QTensor,
821 pub w2: crate::qtensor::QTensor,
822 pub w3: crate::qtensor::QTensor,
823}
824
825pub struct Dsv4Compressor {
826 pub wkv: crate::qtensor::QTensor,
827 pub wgate: crate::qtensor::QTensor,
828 pub norm: Vec<f32>,
829 pub ape: Vec<f32>,
831 pub ratio: usize,
832 pub overlap: bool,
835}
836
837pub struct Dsv4Indexer {
838 pub wq_b: crate::qtensor::QTensor,
839 pub weights_proj: crate::qtensor::QTensor,
840 pub compressor: Dsv4Compressor,
841}
842
843pub struct Dsv4Globals {
846 pub inv_freq_compress: Vec<f32>,
849 pub inv_freq_window: Vec<f32>,
857 pub embed: crate::qtensor::QTensor,
858 pub norm: Vec<f32>,
859 pub head: crate::qtensor::QTensor,
860 pub hc_head_fn: Vec<f32>,
861 pub hc_head_base: Vec<f32>,
862 pub hc_head_scale: f32,
863}
864
865pub struct Dsv4State {
869 pub window: Vec<Vec<f32>>,
871 pub compressed: Vec<Vec<f32>>,
873 pub index_kv: Vec<Vec<f32>>,
875 pub pending_kv: Vec<Vec<f32>>,
877 pub pending_score: Vec<Vec<f32>>,
878 pub prev_kv: Vec<Vec<f32>>,
881 pub prev_score: Vec<Vec<f32>>,
882 pub pending_ix_kv: Vec<Vec<f32>>,
885 pub pending_ix_score: Vec<Vec<f32>>,
886 pub prev_ix_kv: Vec<Vec<f32>>,
887 pub prev_ix_score: Vec<Vec<f32>>,
888 pub pos: usize,
889 pub kv_id: u64,
893 pub dev_filled: Vec<usize>,
899 pub dev_n_comp: Vec<usize>,
900 pub dev_n_ix: Vec<usize>,
901 pub dev_owned: bool,
905 pub dev_set: Vec<bool>,
909 pub partial_set: Vec<bool>,
914 pub split_deep: bool,
919}
920
921impl Dsv4State {
922 pub fn new(layers: usize) -> Self {
923 use std::sync::atomic::{AtomicU64, Ordering};
924 static NEXT: AtomicU64 = AtomicU64::new(1);
925 Self {
926 kv_id: NEXT.fetch_add(1, Ordering::Relaxed),
927 dev_filled: vec![0; layers],
928 dev_n_comp: vec![0; layers],
929 dev_n_ix: vec![0; layers],
930 dev_owned: false,
931 dev_set: Vec::new(),
932 partial_set: Vec::new(),
933 split_deep: false,
934 window: vec![Vec::new(); layers],
935 compressed: vec![Vec::new(); layers],
936 index_kv: vec![Vec::new(); layers],
937 pending_kv: vec![Vec::new(); layers],
938 pending_score: vec![Vec::new(); layers],
939 prev_kv: vec![Vec::new(); layers],
940 prev_score: vec![Vec::new(); layers],
941 pending_ix_kv: vec![Vec::new(); layers],
942 pending_ix_score: vec![Vec::new(); layers],
943 prev_ix_kv: vec![Vec::new(); layers],
944 prev_ix_score: vec![Vec::new(); layers],
945 pos: 0,
946 }
947 }
948}
949
950#[allow(clippy::too_many_arguments)]
959#[allow(clippy::too_many_arguments)]
965fn compressor_step(
966 cp: &Dsv4Compressor,
967 hidden: &[f32],
968 pos: usize,
969 rd: usize,
970 norm_eps: f32,
971 inv_freq: &[f32],
972 pool: Option<&crate::pool::Pool>,
973 pending_kv: &mut Vec<f32>,
974 pending_score: &mut Vec<f32>,
975 prev_kv: &mut Vec<f32>,
976 prev_score: &mut Vec<f32>,
977) -> Option<Vec<f32>> {
978 let width = cp.wkv.rows();
979 let ew = if cp.overlap { width / 2 } else { width };
980 let mut ckv = vec![0.0f32; width];
981 let mut cscore = vec![0.0f32; width];
982 crate::qtensor::QTensor::matvec_many(
985 [&cp.wkv, &cp.wgate],
986 hidden,
987 [&mut ckv, &mut cscore],
988 pool,
989 );
990 if cp.overlap {
991 let slot = pos % cp.ratio;
994 for (c, a) in cscore
995 .iter_mut()
996 .zip(&cp.ape[slot * width..(slot + 1) * width])
997 {
998 *c += a;
999 }
1000 }
1001 pending_kv.extend_from_slice(&ckv);
1002 pending_score.extend_from_slice(&cscore);
1003 if pending_kv.len() / width < cp.ratio {
1004 return None;
1005 }
1006 let mut folded = vec![0.0f32; ew];
1007 if cp.overlap {
1008 compress_window_overlap(
1009 prev_kv,
1010 prev_score,
1011 pending_kv,
1012 pending_score,
1013 cp.ratio,
1014 ew,
1015 &mut folded,
1016 );
1017 *prev_kv = std::mem::take(pending_kv);
1018 *prev_score = std::mem::take(pending_score);
1019 } else {
1020 compress_window(
1021 pending_kv,
1022 pending_score,
1023 &cp.ape,
1024 cp.ratio,
1025 width,
1026 &mut folded,
1027 );
1028 }
1029 rms_weighted(&mut folded, &cp.norm, norm_eps);
1030 rope_tail(&mut folded, inv_freq, pos + 1 - cp.ratio, rd, false);
1033 pending_kv.clear();
1034 pending_score.clear();
1035 Some(folded)
1036}
1037
1038pub(crate) mod prof {
1045 use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
1046
1047 pub static ATTN_NS: AtomicU64 = AtomicU64::new(0);
1048 pub static MOE_NS: AtomicU64 = AtomicU64::new(0);
1049 pub static CALLS: AtomicU64 = AtomicU64::new(0);
1050 pub static HC_NS: AtomicU64 = AtomicU64::new(0);
1053 pub static HEAD_NS: AtomicU64 = AtomicU64::new(0);
1055 pub static PREP_NS: AtomicU64 = AtomicU64::new(0);
1058 pub static CACHEW_NS: AtomicU64 = AtomicU64::new(0);
1060 pub static ALL_NS: AtomicU64 = AtomicU64::new(0);
1064 pub static TOKENS: AtomicU64 = AtomicU64::new(0);
1065
1066 pub fn note_layer(li: usize) {
1069 CALLS.fetch_add(1, Ordering::Relaxed);
1070 if li == 0 {
1071 if TOKENS.fetch_add(1, Ordering::Relaxed) == 1 && !ZEROED.swap(true, Ordering::Relaxed)
1082 {
1083 for a in [
1084 &ATTN_NS, &MOE_NS, &HC_NS, &HEAD_NS, &ALL_NS, &CALLS, &PREP_NS, &CACHEW_NS,
1085 ] {
1086 a.store(0, Ordering::Relaxed);
1087 }
1088 TOKENS.store(1, Ordering::Relaxed);
1089 #[cfg(feature = "gpu")]
1090 for a in [
1091 &crate::gpu_wgpu::MOE_ENC_NS,
1092 &crate::gpu_wgpu::MOE_WAIT_NS,
1093 &crate::gpu_wgpu::MOE_BUFS_NS,
1094 &crate::gpu_wgpu::MOE_UP_NS,
1095 &crate::gpu_wgpu::MOE_PASS_NS,
1096 &crate::gpu_wgpu::ATT_ENC_NS,
1097 &crate::gpu_wgpu::ATT_WAIT_NS,
1098 &crate::gpu_wgpu::CHAIN_ENC_NS,
1099 &crate::gpu_wgpu::CHAIN_WAIT_NS,
1100 &crate::gpu_wgpu::CHAIN_LAYERS,
1101 &crate::gpu_wgpu::CHAIN_RUNS,
1102 &crate::gpu_wgpu::SUBMITS,
1103 &crate::gpu_wgpu::PASSES,
1104 ] {
1105 a.store(0, Ordering::Relaxed);
1106 }
1107 }
1108 }
1109 }
1110 static REPORT: AtomicBool = AtomicBool::new(false);
1111 static ZEROED: AtomicBool = AtomicBool::new(false);
1113
1114 pub fn on() -> bool {
1115 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1116 *ON.get_or_init(|| std::env::var("CMF_DSV4_PROFILE").is_ok_and(|v| v != "0"))
1117 }
1118
1119 pub fn report() {
1122 if !on() || REPORT.swap(true, Ordering::Relaxed) {
1123 return;
1124 }
1125 let calls = CALLS.load(Ordering::Relaxed).max(1);
1129 let toks = TOKENS.load(Ordering::Relaxed).max(1);
1130 let (a, m) = (
1131 ATTN_NS.load(Ordering::Relaxed) as f64 / 1e6,
1132 MOE_NS.load(Ordering::Relaxed) as f64 / 1e6,
1133 );
1134 let all = ALL_NS.load(Ordering::Relaxed) as f64 / 1e6;
1135 let hc = (HC_NS.load(Ordering::Relaxed) as f64 / 1e6
1142 - MOE_NS.load(Ordering::Relaxed) as f64 / 1e6)
1143 .max(0.0);
1144 let hd = HEAD_NS.load(Ordering::Relaxed) as f64 / 1e6;
1145 let prep = PREP_NS.load(Ordering::Relaxed) as f64 / 1e6;
1146 let cw = CACHEW_NS.load(Ordering::Relaxed) as f64 / 1e6;
1147 eprintln!(
1148 "[dsv4-profile] layer HOST PREP: {:.0} ms/token, KV uploads: {:.0} ms/token",
1149 prep / toks as f64,
1150 cw / toks as f64
1151 );
1152 #[cfg(feature = "gpu")]
1153 {
1154 let f = crate::gpu_wgpu::DSV4_FILLS.load(Ordering::Relaxed);
1155 let fb = crate::gpu_wgpu::DSV4_FILL_BYTES.load(Ordering::Relaxed);
1156 eprintln!(
1157 "[dsv4-profile] SLOT UPLOADS: {:.1} experts/token, {:.0} MB/token",
1158 f as f64 / toks as f64,
1159 fb as f64 / 1e6 / toks as f64
1160 );
1161 }
1162 eprintln!(
1163 "[dsv4-profile] {calls} layer calls over {toks} tokens | \
1164 per token: attention {:.0} ms, MoE {:.0} ms, hyper-connections+norms {:.0} ms, \
1165 head {:.0} ms | per call: attention {:.2}, MoE {:.2}, connections {:.2}",
1166 a / toks as f64,
1167 m / toks as f64,
1168 hc / toks as f64,
1169 hd / toks as f64,
1170 a / calls as f64,
1171 m / calls as f64,
1172 hc / calls as f64,
1173 );
1174 eprintln!(
1175 "[dsv4-profile] whole pass {:.0} ms per token; outside the counters {:.0} ms",
1176 all / toks as f64,
1177 (all - a - m - hd) / toks as f64,
1178 );
1179 #[cfg(feature = "gpu")]
1180 {
1181 let ae = crate::gpu_wgpu::ATT_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6;
1182 let aw = crate::gpu_wgpu::ATT_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6;
1183 if ae + aw > 0.0 {
1184 eprintln!(
1185 "[dsv4-profile] attention frame per call: encoding {:.2} ms, \
1186 submit and wait {:.2} ms",
1187 ae / calls as f64,
1188 aw / calls as f64,
1189 );
1190 }
1191 let ub = crate::gpu_wgpu::UPLOAD_BYTES.load(Ordering::Relaxed);
1196 let un = crate::gpu_wgpu::UPLOAD_NS.load(Ordering::Relaxed);
1197 if ub > 0 && un > 0 {
1198 eprintln!(
1199 "[dsv4-profile] weight UPLOAD: {:.1} GB in {:.1} s ({:.0} MB/s)",
1200 ub as f64 / 1e9,
1201 un as f64 / 1e9,
1202 ub as f64 / (un as f64 / 1e9) / 1e6,
1203 );
1204 }
1205 let sub = crate::gpu_wgpu::SUBMITS.load(Ordering::Relaxed);
1206 if sub > 0 {
1207 eprintln!(
1208 "[dsv4-profile] SUBMITS to the card: {:.1} per token, PASSES {:.0} \
1209 ({:.1} per layer)",
1210 sub as f64 / toks as f64,
1211 crate::gpu_wgpu::PASSES.load(Ordering::Relaxed) as f64 / toks as f64,
1212 crate::gpu_wgpu::PASSES.load(Ordering::Relaxed) as f64 / calls as f64,
1213 );
1214 }
1215 let cl = crate::gpu_wgpu::CHAIN_LAYERS.load(Ordering::Relaxed);
1216 if cl > 0 {
1217 let toks2 = toks.max(1) as f64;
1218 eprintln!(
1219 "[dsv4-profile] CHAIN per token: encoding {:.2} ms, \
1220 wait {:.2} ms ({} layers, {} submits)",
1221 crate::gpu_wgpu::CHAIN_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6 / toks2,
1222 crate::gpu_wgpu::CHAIN_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6 / toks2,
1223 cl / toks.max(1),
1224 crate::gpu_wgpu::CHAIN_RUNS.load(Ordering::Relaxed) / toks.max(1),
1225 );
1226 }
1227 let e = crate::gpu_wgpu::MOE_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6;
1228 let wt = crate::gpu_wgpu::MOE_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6;
1229 if e + wt > 0.0 {
1230 let ns = |a: &std::sync::atomic::AtomicU64| {
1231 a.load(Ordering::Relaxed) as f64 / 1e6 / calls as f64
1232 };
1233 eprintln!(
1234 "[dsv4-profile] MoE frame per call: encoding {:.2} ms, \
1235 submit and wait {:.2} ms",
1236 e / calls as f64,
1237 wt / calls as f64,
1238 );
1239 let an = crate::gpu_wgpu::ATT_GPU_N.load(Ordering::Relaxed);
1240 if an > 0 {
1241 let g = |i: usize| {
1242 crate::gpu_wgpu::ATT_GPU_NS[i].load(Ordering::Relaxed) as f64
1243 / 1e6
1244 / an as f64
1245 };
1246 eprintln!(
1247 "[dsv4-profile] ATTENTION ON THE CARD per call: single {:.3} ms, \
1248 scores {:.3} ms, apply {:.3} ms",
1249 g(0),
1250 g(1),
1251 g(2),
1252 );
1253 }
1254 let gn = crate::gpu_wgpu::MOE_GPU_N.load(Ordering::Relaxed);
1255 let gns = crate::gpu_wgpu::MOE_GPU_NS[0].load(Ordering::Relaxed);
1256 if gn > 0 && gns > 0 {
1257 eprintln!(
1258 "[dsv4-profile] MoE ON THE CARD: {:.3} ms per call ({gn} samples)",
1259 gns as f64 / 1e6 / gn as f64,
1260 );
1261 } else if gn > 0 {
1262 eprintln!(
1266 "[dsv4-profile] MoE ON THE CARD: the timestamps returned ZERO on {gn} samples — \
1267 the time query did not work, do not use the number"
1268 );
1269 }
1270 eprintln!(
1271 "[dsv4-profile] of the encoding: expert buffers {:.2} ms, \
1272 uploads {:.2} ms, passes {:.2} ms",
1273 ns(&crate::gpu_wgpu::MOE_BUFS_NS),
1274 ns(&crate::gpu_wgpu::MOE_UP_NS),
1275 ns(&crate::gpu_wgpu::MOE_PASS_NS),
1276 );
1277 }
1278 }
1279 }
1280}
1281
1282pub fn profile_report() {
1284 prof::report();
1285}
1286
1287fn gpu_attn_enabled() -> bool {
1292 #[cfg(feature = "gpu")]
1293 {
1294 use std::sync::OnceLock;
1295 static ON: OnceLock<bool> = OnceLock::new();
1296 *ON.get_or_init(|| {
1297 let want = std::env::var("CMF_DSV4_GPU_ATTN")
1298 .map(|v| v != "0")
1299 .unwrap_or(true);
1300 let have = want && crate::gpu::backend_available();
1301 if want && !have && std::env::var("CMF_DSV4_GPU_ATTN").is_ok() {
1302 tracing::warn!(
1303 "CMF_DSV4_GPU_ATTN is set but there is no device — the attention block stays on the CPU. Check CMF_GPU=wgpu and the Vulkan ICD."
1304 );
1305 }
1306 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
1307 eprintln!("dsv4 frame: requested={want} available={have}");
1308 }
1309 have
1310 })
1311 }
1312 #[cfg(not(feature = "gpu"))]
1313 {
1314 false
1315 }
1316}
1317
1318#[cfg(feature = "gpu")]
1322#[allow(clippy::too_many_arguments)]
1323fn attn_frame(
1324 l: &Dsv4Layer,
1325 cfg: &Dsv4Cfg,
1326 st: &Dsv4State,
1327 li: usize,
1328 hidden: &[f32],
1329 qn: &[f32],
1330 idxs: &[usize],
1331 inv_freq: &[f32],
1332 pos: usize,
1333 win_len: usize,
1334 scale: f32,
1335 hc: Option<&crate::gpu_wgpu::Dsv4HcTail>,
1338 out: &mut [f32],
1339) -> bool {
1340 let hd = cfg.head_dim;
1341 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
1342 l.wq_a.model_idx(),
1343 l.wq_b.model_idx(),
1344 l.wo_a.model_idx(),
1345 l.wo_b.model_idx(),
1346 ) else {
1347 return false;
1348 };
1349 let Some(model) = l.wq_b.model_arc() else {
1350 return false;
1351 };
1352 let n_comp = st.compressed[li].len() / hd;
1357 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
1358 let kv_id = st.kv_id;
1359 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap) {
1365 return false;
1366 }
1367 if n_comp > 0
1376 && !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, cfg.window * hd, &st.compressed[li], cap)
1377 {
1378 return false;
1379 }
1380 let idx32: Vec<u32> = idxs
1381 .iter()
1382 .map(|&p| {
1383 if p < win_len {
1384 p as u32
1385 } else {
1386 (cfg.window + (p - win_len)) as u32
1387 }
1388 })
1389 .collect();
1390 let w = crate::gpu_wgpu::Dsv4AttnW {
1391 wq_a,
1392 wq_b,
1393 wo_a,
1394 wo_b,
1395 q_norm: &l.q_norm,
1396 sink: &l.attn_sink,
1397 };
1398 let g = crate::gpu_wgpu::Dsv4AttnGeom {
1399 dim: cfg.dim,
1400 nh: cfg.n_heads,
1401 hd,
1402 rd: cfg.rope_head_dim,
1403 q_lora: cfg.q_lora_rank,
1404 o_lora: cfg.o_lora_rank,
1405 o_groups: cfg.o_groups,
1406 eps: cfg.norm_eps,
1407 scale,
1408 bf16: false,
1409 q_rms: true,
1410 o_fp8: false,
1411 };
1412 crate::gpu_wgpu::dsv4_attn_frame(
1420 &model,
1421 &w,
1422 g,
1423 hidden,
1424 Some(qn),
1425 None,
1426 kv_id,
1427 li,
1428 &idx32,
1429 inv_freq,
1430 pos,
1431 hc,
1432 out,
1433 )
1434}
1435
1436#[derive(Default)]
1439pub struct AttnPrep {
1440 pub qr: Vec<f32>,
1441 pub idxs: Vec<usize>,
1442 pub win_len: usize,
1443}
1444
1445#[allow(clippy::too_many_arguments)]
1446pub fn attention_step(
1447 hidden: &[f32],
1448 l: &Dsv4Layer,
1449 cfg: &Dsv4Cfg,
1450 st: &mut Dsv4State,
1451 li: usize,
1452 inv_freq: &[f32],
1454 pool: Option<&crate::pool::Pool>,
1455 prep_out: Option<&mut AttnPrep>,
1459 out: &mut [f32],
1460) {
1461 let _t0 = prof::on().then(std::time::Instant::now);
1462 let _guard = scopeguard_attn(_t0);
1463 let (hd, rd) = (cfg.head_dim, cfg.rope_head_dim);
1464 let pos = st.pos;
1465 if std::env::var("CMF_FREQ_DEBUG").is_ok() && li == 0 && pos == 0 {
1466 eprintln!(
1467 " [port] rd={rd} frequencies={} inv_freq[0..4]={:?}",
1468 inv_freq.len(),
1469 &inv_freq[..4.min(inv_freq.len())]
1470 );
1471 }
1472
1473 let mut qr = vec![0.0f32; cfg.q_lora_rank];
1478 let mut kv = vec![0.0f32; hd];
1479 crate::qtensor::QTensor::matvec_many([&l.wq_a, &l.wkv], hidden, [&mut qr, &mut kv], pool);
1480 rms_weighted(&mut qr, &l.q_norm, cfg.norm_eps);
1481 let split_config = st.partial_set.iter().any(|&p| p) && st.split_deep;
1499 let past_chain =
1500 st.dev_owned && (li >= st.dev_set.len() || !st.dev_set.get(li).copied().unwrap_or(false));
1501 if std::env::var("CMF_DSV4_GATE_DBG").is_ok() {
1502 eprintln!(
1503 "[gate] li={li} pos={} split={split_config} past={past_chain} dev_owned={} set_len={} part_len={}",
1504 st.pos,
1505 st.dev_owned,
1506 st.dev_set.len(),
1507 st.partial_set.len()
1508 );
1509 }
1510 let on_gpu = gpu_attn_enabled() && !(split_config && past_chain);
1511
1512 rms_weighted(&mut kv, &l.kv_norm, cfg.norm_eps);
1513 rope_tail(&mut kv, inv_freq, pos, rd, false);
1514
1515 if let Some(cp) = &l.compressor {
1520 let mut pk = std::mem::take(&mut st.pending_kv[li]);
1521 let mut ps = std::mem::take(&mut st.pending_score[li]);
1522 let mut qk = std::mem::take(&mut st.prev_kv[li]);
1523 let mut qs = std::mem::take(&mut st.prev_score[li]);
1524 let entry = compressor_step(
1525 cp,
1526 hidden,
1527 pos,
1528 rd,
1529 cfg.norm_eps,
1530 inv_freq,
1531 pool,
1532 &mut pk,
1533 &mut ps,
1534 &mut qk,
1535 &mut qs,
1536 );
1537 st.pending_kv[li] = pk;
1538 st.pending_score[li] = ps;
1539 st.prev_kv[li] = qk;
1540 st.prev_score[li] = qs;
1541 if let Some(e) = entry {
1542 st.compressed[li].extend_from_slice(&e);
1543 }
1544 }
1545 if let Some(ix) = &l.indexer {
1550 let mut pk = std::mem::take(&mut st.pending_ix_kv[li]);
1551 let mut ps = std::mem::take(&mut st.pending_ix_score[li]);
1552 let mut qk = std::mem::take(&mut st.prev_ix_kv[li]);
1553 let mut qs = std::mem::take(&mut st.prev_ix_score[li]);
1554 let entry = compressor_step(
1555 &ix.compressor,
1556 hidden,
1557 pos,
1558 rd,
1559 cfg.norm_eps,
1560 inv_freq,
1561 pool,
1562 &mut pk,
1563 &mut ps,
1564 &mut qk,
1565 &mut qs,
1566 );
1567 st.pending_ix_kv[li] = pk;
1568 st.pending_ix_score[li] = ps;
1569 st.prev_ix_kv[li] = qk;
1570 st.prev_ix_score[li] = qs;
1571 if let Some(e) = entry {
1572 st.index_kv[li].extend_from_slice(&e);
1573 }
1574 }
1575
1576 st.window[li].extend_from_slice(&kv);
1577 let cap = cfg.window * hd;
1581 if st.window[li].len() > cap {
1582 let drop = st.window[li].len() - cap;
1583 st.window[li].drain(..drop);
1584 }
1585 let win_len = st.window[li].len() / hd;
1586 let n_pos = win_len + st.compressed[li].len() / hd;
1587
1588 let mut idxs: Vec<usize> = (0..win_len).collect();
1597 if !st.compressed[li].is_empty() && !no_compressed() {
1598 let n_comp = st.compressed[li].len() / hd;
1599 match &l.indexer {
1600 Some(ix) => {
1601 let ih = ix.weights_proj.rows();
1617 let idim = ix.wq_b.rows() / ih.max(1);
1618 let mut qi = vec![0.0f32; ix.wq_b.rows()];
1619 ix.wq_b.matvec(&qr, &mut qi, pool);
1620 for h in 0..ih {
1621 rope_tail(&mut qi[h * idim..(h + 1) * idim], inv_freq, pos, rd, false);
1622 }
1623 let mut hw = vec![0.0f32; ih];
1624 ix.weights_proj.matvec(hidden, &mut hw, pool);
1625 let sc_factor = (idim as f32).powf(-0.5) * (ih as f32).powf(-0.5);
1626 for w in hw.iter_mut() {
1627 *w *= sc_factor;
1628 }
1629 let n_ix = st.index_kv[li].len() / idim.max(1);
1630 let mut sc = Vec::new();
1631 index_scores(
1632 &qi,
1633 &st.index_kv[li],
1634 &hw,
1635 ih,
1636 idim,
1637 n_ix.min(n_comp),
1638 n_ix.min(n_comp),
1639 pool,
1640 &mut sc,
1641 );
1642 let mut picked = Vec::new();
1643 top_k_positions(&sc, cfg.index_topk, &mut picked);
1644 idxs.extend(picked.into_iter().map(|p| win_len + p));
1645 }
1646 None => idxs.extend((0..n_comp).map(|p| win_len + p)),
1647 }
1648 }
1649 debug_assert!(idxs.iter().all(|&p| p < n_pos));
1650 if let Some(p) = prep_out {
1651 p.qr = qr;
1652 p.idxs = idxs;
1653 p.win_len = win_len;
1654 return;
1655 }
1656
1657 let scale = (hd as f32).powf(-0.5);
1659 #[cfg(feature = "gpu")]
1660 if on_gpu
1661 && {
1662 if std::env::var("CMF_DSV4_XCHK").is_ok() {
1663 if let Some(card) = crate::gpu_wgpu::dsv4_dbg_read_tag(45, 0, hidden.len()) {
1667 let md = hidden
1668 .iter()
1669 .zip(card.iter())
1670 .map(|(a, b)| (a - b).abs())
1671 .fold(0.0f32, f32::max);
1672 eprintln!("[xchk] li={li} pos={pos} x2 maxdiff={md:.3e}");
1673 }
1674 }
1675 true
1676 }
1677 && attn_frame(
1678 l, cfg, st, li, hidden, &qr, &idxs, inv_freq, pos, win_len, scale, None, out,
1679 )
1680 {
1681 return;
1682 }
1683
1684 let mut q = vec![0.0f32; cfg.n_heads * hd];
1686 l.wq_b.matvec(&qr, &mut q, pool);
1687 for h in 0..cfg.n_heads {
1688 let head = &mut q[h * hd..(h + 1) * hd];
1689 rms_inplace(head, cfg.norm_eps);
1690 rope_tail(head, inv_freq, pos, rd, false);
1691 }
1692 let mut cache: Vec<f32> = st.window[li].clone();
1693 cache.extend_from_slice(&st.compressed[li]);
1694
1695 let mut attn = vec![0.0f32; cfg.n_heads * hd];
1697 for h in 0..cfg.n_heads {
1698 let qh = &q[h * hd..(h + 1) * hd];
1699 let oh = &mut attn[h * hd..(h + 1) * hd];
1704 sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
1705 rope_tail(oh, inv_freq, pos, rd, true);
1706 }
1707
1708 o_project(
1714 &attn,
1715 &|r, x, sc| l.wo_a.row_dot(r, x, sc),
1716 l.wo_a.cols(),
1717 &|mid, dst| l.wo_b.matvec(mid, dst, pool),
1718 cfg.o_groups,
1719 cfg.o_lora_rank,
1720 pool,
1721 out,
1722 );
1723}
1724
1725pub fn rms_weighted(v: &mut [f32], w: &[f32], eps: f32) {
1727 let ms = v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32;
1728 let inv = 1.0 / (ms + eps).sqrt();
1729 for (x, g) in v.iter_mut().zip(w) {
1730 *x = *x * inv * g;
1731 }
1732}
1733
1734thread_local! {
1749 static ROUTE_COUNTS: std::cell::RefCell<Vec<Vec<u64>>> =
1750 const { std::cell::RefCell::new(Vec::new()) };
1751}
1752
1753fn route_stats_on() -> bool {
1754 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1755 *ON.get_or_init(|| std::env::var("CMF_MOE_STATS").is_ok())
1756}
1757
1758fn record_route(li: usize, n_layers_hint: usize, n_experts: usize, routed: &[(usize, f32)]) {
1759 ROUTE_COUNTS.with(|c| {
1760 let mut c = c.borrow_mut();
1761 if c.len() <= li.max(n_layers_hint) {
1762 c.resize(li.max(n_layers_hint) + 1, Vec::new());
1763 }
1764 let row = &mut c[li];
1765 if row.len() < n_experts {
1766 row.resize(n_experts, 0);
1767 }
1768 for &(e, weight) in routed {
1769 if e < row.len() {
1770 let mass = (weight.abs() as f64 * 1_000_000.0).round() as u64;
1774 row[e] = row[e].saturating_add(mass.max(1));
1775 }
1776 }
1777 });
1778}
1779
1780pub fn take_route_counts() -> Vec<Vec<u64>> {
1782 ROUTE_COUNTS.with(|c| std::mem::take(&mut *c.borrow_mut()))
1783}
1784
1785struct Charge(
1789 Option<std::time::Instant>,
1790 &'static std::sync::atomic::AtomicU64,
1791);
1792impl Drop for Charge {
1793 fn drop(&mut self) {
1794 if let Some(t) = self.0 {
1795 self.1.fetch_add(
1796 t.elapsed().as_nanos() as u64,
1797 std::sync::atomic::Ordering::Relaxed,
1798 );
1799 }
1800 }
1801}
1802fn scopeguard_attn(t: Option<std::time::Instant>) -> Charge {
1803 Charge(t, &prof::ATTN_NS)
1804}
1805fn scopeguard_moe(t: Option<std::time::Instant>, li: usize) -> Charge {
1806 if t.is_some() {
1807 prof::note_layer(li);
1808 }
1809 Charge(t, &prof::MOE_NS)
1810}
1811
1812#[cfg(feature = "gpu")]
1816#[allow(clippy::too_many_arguments)]
1817fn dsv4_layer_loop(
1818 state: &mut [f32],
1819 layers: &[Dsv4Layer],
1820 g: &Dsv4Globals,
1821 cfg: &Dsv4Cfg,
1822 st: &mut Dsv4State,
1823 token_id: u32,
1824 inv_freq: &[f32],
1825 pool: Option<&crate::pool::Pool>,
1826 scratch: &mut HcScratch,
1827) -> bool {
1828 let dim = cfg.dim;
1829 let freqs_of = |l: &Dsv4Layer| -> &[f32] {
1830 let f = if l.compressor.is_some() {
1831 &g.inv_freq_compress
1832 } else {
1833 &g.inv_freq_window
1834 };
1835 if f.is_empty() { inv_freq } else { f.as_slice() }
1836 };
1837 {
1850 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1851 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
1852 if let (Some(idx), Some(model)) = (g.head.model_idx(), g.head.model_arc()) {
1853 let ok = crate::gpu_wgpu::dsv4_weight_ready(&model, idx);
1854 tracing::info!("dsv4: head on the card: {}", if ok { "yes" } else { "no" });
1855 }
1856 }
1857 }
1858 let mut on_dev = vec![false; layers.len()];
1859 let mut partial_dev = vec![false; layers.len()];
1860 let mut attn_ready = vec![false; layers.len()];
1861 for (li, l) in layers.iter().enumerate() {
1866 if l.wq_a.model_idx().is_none()
1867 || l.wq_b.model_idx().is_none()
1868 || l.wo_a.model_idx().is_none()
1869 || l.wo_b.model_idx().is_none()
1870 {
1871 return false;
1872 }
1873 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
1874 return false;
1875 };
1876 let mut want = vec![
1891 l.wq_a.model_idx(),
1892 l.wq_b.model_idx(),
1893 l.wo_a.model_idx(),
1894 l.wo_b.model_idx(),
1895 ];
1896 if chain_enabled() {
1897 want.push(l.wkv.model_idx());
1898 if let Some(cp) = &l.compressor {
1899 want.push(cp.wkv.model_idx());
1900 want.push(cp.wgate.model_idx());
1901 }
1902 if let Some(ix) = &l.indexer {
1903 want.push(ix.wq_b.model_idx());
1904 want.push(ix.weights_proj.model_idx());
1905 want.push(ix.compressor.wkv.model_idx());
1906 want.push(ix.compressor.wgate.model_idx());
1907 }
1908 }
1909 attn_ready[li] = want
1910 .into_iter()
1911 .flatten()
1912 .all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
1913 }
1914 for (li, l) in layers.iter().enumerate() {
1915 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
1916 return false;
1917 };
1918 let gu_q2 = l
1919 .experts
1920 .first()
1921 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
1922 let pk = pack_for(l, cfg, li);
1926 if let Some(pk) = pk {
1927 let dn_q2 = l
1928 .experts
1929 .first()
1930 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
1931 let experts_ok = if pk.global.is_some() {
1932 crate::gpu_wgpu::dsv4_global_moe_ready(&model)
1933 } else {
1934 crate::gpu_wgpu::dsv4_experts_ready(
1935 &model,
1936 &pk.tensors,
1937 cfg.moe_inter,
1938 dim,
1939 gu_q2,
1940 dn_q2,
1941 )
1942 };
1943 on_dev[li] = attn_ready[li] && experts_ok && pk.route_complete();
1944 partial_dev[li] = attn_ready[li] && experts_ok && !pk.route_complete();
1945 }
1946 }
1947 let active_dev: Vec<bool> = on_dev
1948 .iter()
1949 .zip(&partial_dev)
1950 .map(|(&full, &partial)| full || partial)
1951 .collect();
1952 if !active_dev.iter().any(|&x| x) {
1953 return false;
1954 }
1955 if st.partial_set.len() != partial_dev.len() || st.partial_set != partial_dev {
1960 st.partial_set = partial_dev.clone();
1961 st.split_deep = active_dev
1962 .iter()
1963 .zip(&partial_dev)
1964 .filter(|(a, p)| !**a || **p)
1965 .count()
1966 > 1;
1967 }
1968
1969 {
1973 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1974 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
1975 let host: Vec<usize> = active_dev
1976 .iter()
1977 .enumerate()
1978 .filter(|&(_, d)| !*d)
1979 .map(|(i, _)| i)
1980 .collect();
1981 let partial: Vec<(usize, usize)> = partial_dev
1982 .iter()
1983 .enumerate()
1984 .filter(|&(_, d)| *d)
1985 .filter_map(|(li, _)| pack_for(&layers[li], cfg, li).map(|p| (li, p.globals.len())))
1986 .collect();
1987 if host.is_empty() && partial.is_empty() {
1988 tracing::info!("dsv4: all {} layers on the card", on_dev.len());
1989 } else {
1990 tracing::info!(
1991 "dsv4: {} of {} layers use the card; partial {:?}; on the host {:?}",
1992 active_dev.len() - host.len(),
1993 on_dev.len(),
1994 partial,
1995 host,
1996 );
1997 }
1998 }
1999 }
2000
2001 let (mut folded, post0, comb0) = hc_fold_norm(
2003 state,
2004 &layers[0].hc_attn_fn,
2005 &layers[0].hc_attn_scale,
2006 &layers[0].hc_attn_base,
2007 &layers[0].attn_norm,
2008 cfg,
2009 pool,
2010 );
2011 if !crate::gpu_wgpu::dsv4_state_write(state) || !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
2012 {
2013 return false;
2014 }
2015 if st.dev_owned && st.dev_set != active_dev {
2026 let left: Vec<usize> = (0..active_dev.len().min(st.dev_set.len()))
2027 .filter(|&i| st.dev_set[i] && !active_dev[i])
2028 .collect();
2029 if !left.is_empty() {
2030 tracing::warn!("layers {left:?} left the card — caches on different sides");
2031 return false;
2032 }
2033 }
2037 let chain = chain_enabled();
2038 let layer_cap = {
2046 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
2047 *N.get_or_init(|| {
2048 std::env::var("CMF_DSV4_LAYERS_PROBE")
2049 .ok()
2050 .and_then(|v| v.parse::<usize>().ok())
2051 .unwrap_or(usize::MAX)
2052 })
2053 };
2054 let mut run: Vec<usize> = Vec::new();
2055 let mut sink_out = vec![0.0f32; dim];
2056 let mut state_on_host = true;
2061 for (li, l) in layers.iter().enumerate() {
2062 if li >= layer_cap {
2063 break;
2064 }
2065 if prof::on() {
2071 prof::note_layer(li);
2072 }
2073 if chain && on_dev[li] {
2074 run.push(li);
2079 if run.len() >= chain_max() || dspark_wants(li) {
2084 let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
2085 let captured = *run.last().unwrap();
2086 if !dsv4_chain_run(
2087 layers,
2088 &run,
2089 cfg,
2090 g,
2091 st,
2092 token_id,
2093 &mut folded,
2094 Some(state),
2095 1,
2096 &[],
2097 need_qn,
2098 pool,
2099 ) {
2100 return false;
2101 }
2102 state_on_host = true;
2103 verify_fp("walk", st.pos, captured, state);
2104 dspark_note(captured, state, cfg);
2105 run.clear();
2106 }
2107 continue;
2108 }
2109 if chain && !run.is_empty() {
2110 if !dsv4_chain_run(
2114 layers,
2115 &run,
2116 cfg,
2117 g,
2118 st,
2119 token_id,
2120 &mut folded,
2121 Some(state),
2122 1,
2123 &[],
2124 run[0] == 0 || !on_dev[run[0] - 1],
2125 pool,
2126 ) {
2127 return false;
2128 }
2129 state_on_host = true;
2130 let last = *run.last().unwrap();
2131 verify_fp("walk", st.pos, last, state);
2132 dspark_note(last, state, cfg);
2133 }
2134 run.clear();
2135 if partial_dev[li] && chain1_on() {
2136 if let Some(home) = dsv4_chain1_layer(
2137 state,
2138 &mut folded,
2139 layers,
2140 l,
2141 cfg,
2142 st,
2143 token_id,
2144 li,
2145 freqs_of(l),
2146 pool,
2147 state_on_host,
2148 ) {
2149 st.dev_filled[li] = (st.window[li].len() / cfg.head_dim).min(cfg.window);
2157 st.dev_n_comp[li] = st.compressed[li].len() / cfg.head_dim;
2158 st.dev_n_ix[li] = l.indexer.as_ref().map_or(0, |ix| {
2159 let ih = ix.weights_proj.rows();
2160 let idim = ix.wq_b.rows() / ih.max(1);
2161 st.index_kv[li].len() / idim.max(1)
2162 });
2163 state_on_host = home;
2164 if verify_fp_on(st.pos) {
2165 if !state_on_host && !crate::gpu_wgpu::dsv4_state_read(state) {
2166 return false;
2167 }
2168 state_on_host = true;
2169 verify_fp("walk", st.pos, li, state);
2170 }
2171 if dspark_wants(li) {
2175 if !state_on_host && crate::gpu_wgpu::dsv4_state_read(state) {
2176 state_on_host = true;
2177 }
2178 if state_on_host {
2179 dspark_note(li, state, cfg);
2180 }
2181 }
2182 continue;
2183 }
2184 }
2185 if partial_dev[li] && partial_walk_on() {
2186 let Some(home) = dsv4_partial_layer(
2191 state,
2192 &mut folded,
2193 layers,
2194 l,
2195 cfg,
2196 st,
2197 token_id,
2198 li,
2199 freqs_of(l),
2200 pool,
2201 state_on_host,
2202 ) else {
2203 return false;
2204 };
2205 state_on_host = home;
2206 if home {
2207 dspark_note(li, state, cfg);
2208 }
2209 continue;
2210 }
2211 if !on_dev[li] {
2212 if !state_on_host && !crate::gpu_wgpu::dsv4_state_read(state) {
2213 return false;
2214 }
2215 state_on_host = true;
2216 let freqs = freqs_of(l);
2217 hc_block(
2218 state,
2219 &l.hc_attn_fn,
2220 &l.hc_attn_scale,
2221 &l.hc_attn_base,
2222 &l.attn_norm,
2223 cfg,
2224 scratch,
2225 pool,
2226 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
2227 );
2228 hc_block(
2229 state,
2230 &l.hc_ffn_fn,
2231 &l.hc_ffn_scale,
2232 &l.hc_ffn_base,
2233 &l.ffn_norm,
2234 cfg,
2235 scratch,
2236 pool,
2237 |f, o| {
2244 if host_cpu_moe() {
2245 crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
2246 } else {
2247 moe_step(f, l, cfg, token_id, li, pool, o)
2248 }
2249 },
2250 );
2251 if layers.get(li + 1).is_some() && on_dev.get(li + 1).copied().unwrap_or(false) {
2256 let n = &layers[li + 1];
2257 let (f, p2, c2) = hc_fold_norm(
2258 state,
2259 &n.hc_attn_fn,
2260 &n.hc_attn_scale,
2261 &n.hc_attn_base,
2262 &n.attn_norm,
2263 cfg,
2264 pool,
2265 );
2266 folded = f;
2267 if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2) {
2268 return false;
2269 }
2270 if !crate::gpu_wgpu::dsv4_state_write(state) {
2271 return false;
2272 }
2273 }
2274 dspark_note(li, state, cfg);
2275 continue;
2276 }
2277 let mut prep = AttnPrep::default();
2278 let _tp = prof::on().then(std::time::Instant::now);
2279 attention_step(
2280 &folded,
2281 l,
2282 cfg,
2283 st,
2284 li,
2285 freqs_of(l),
2286 pool,
2287 Some(&mut prep),
2288 &mut sink_out,
2289 );
2290 if let Some(t) = _tp {
2291 prof::PREP_NS.fetch_add(
2292 t.elapsed().as_nanos() as u64,
2293 std::sync::atomic::Ordering::Relaxed,
2294 );
2295 }
2296 let hd = cfg.head_dim;
2298 let n_comp = st.compressed[li].len() / hd;
2299 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2300 let kv_id = st.kv_id;
2301 let _tc = prof::on().then(std::time::Instant::now);
2302 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap)
2303 || (n_comp > 0
2304 && !crate::gpu_wgpu::dsv4_cache_write(
2305 kv_id,
2306 li,
2307 cfg.window * hd,
2308 &st.compressed[li],
2309 cap,
2310 ))
2311 {
2312 return false;
2313 }
2314 if let Some(t) = _tc {
2315 prof::CACHEW_NS.fetch_add(
2316 t.elapsed().as_nanos() as u64,
2317 std::sync::atomic::Ordering::Relaxed,
2318 );
2319 }
2320 let idx32: Vec<u32> = prep
2321 .idxs
2322 .iter()
2323 .map(|&p| {
2324 if p < prep.win_len {
2325 p as u32
2326 } else {
2327 (cfg.window + (p - prep.win_len)) as u32
2328 }
2329 })
2330 .collect();
2331 let Some(pk) = pack_for(l, cfg, li) else {
2332 return false;
2333 };
2334 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
2335 l.wq_a.model_idx(),
2336 l.wq_b.model_idx(),
2337 l.wo_a.model_idx(),
2338 l.wo_b.model_idx(),
2339 ) else {
2340 return false;
2341 };
2342 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
2343 return false;
2344 };
2345 let forced: Option<Vec<usize>> = l.tid2eid.as_ref().and_then(|tbl| {
2346 let v: Vec<usize> = if pk.needs_remap() {
2347 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
2348 } else {
2349 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
2350 .into_iter()
2351 .map(|gi| pk.to_slot[gi])
2352 .collect()
2353 };
2354 if v.contains(&usize::MAX) {
2355 None
2356 } else {
2357 Some(v)
2358 }
2359 });
2360 if l.tid2eid.is_some() && forced.is_none() {
2361 return false;
2362 }
2363 let nxt = layers.get(li + 1);
2364 let w = crate::gpu_wgpu::Dsv4LayerW {
2365 attn: crate::gpu_wgpu::Dsv4AttnW {
2366 wq_a,
2367 wq_b,
2368 wo_a,
2369 wo_b,
2370 q_norm: &l.q_norm,
2371 sink: &l.attn_sink,
2372 },
2373 moe: crate::gpu_wgpu::Dsv4MoeW {
2374 router: &[],
2375 experts: &pk.tensors,
2376 logits: &[],
2377 bias: pk.bias.as_deref(),
2384 mask: pk.mask.as_deref(),
2385 forced: forced.as_deref(),
2386 remap: pk.needs_remap().then_some(pk.remap.as_slice()),
2387 global: None,
2388 has_shared: true,
2389 shared_weight: 1.0,
2390 preweighted: false,
2391 qwen_softmax: false,
2392 },
2393 hc_ffn_fn: &l.hc_ffn_fn,
2394 hc_ffn_scale: &l.hc_ffn_scale,
2395 hc_ffn_base: &l.hc_ffn_base,
2396 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2397 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2398 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2399 ffn_norm: &l.ffn_norm,
2400 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2401 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2402 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2403 router: &pk.router,
2404 };
2405 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2406 attn: crate::gpu_wgpu::Dsv4AttnGeom {
2407 dim,
2408 nh: cfg.n_heads,
2409 hd,
2410 rd: cfg.rope_head_dim,
2411 q_lora: cfg.q_lora_rank,
2412 o_lora: cfg.o_lora_rank,
2413 o_groups: cfg.o_groups,
2414 eps: cfg.norm_eps,
2415 scale: (hd as f32).powf(-0.5),
2416 bf16: false,
2417 q_rms: true,
2418 o_fp8: false,
2419 },
2420 moe: crate::gpu_wgpu::Dsv4MoeGeom {
2421 hidden: dim,
2422 inter: cfg.moe_inter,
2423 top_k: cfg.top_k,
2424 route_scale: cfg.route_scale,
2425 swiglu_limit: cfg.swiglu_limit,
2426 gu_q2: l.experts.first().is_some_and(|e| {
2427 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
2428 }),
2429 bf16: false,
2430 },
2431 hc: cfg.hc_mult,
2432 hc_eps: cfg.hc_eps,
2433 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2434 };
2435 let mut next = vec![0.0f32; dim];
2436 if !crate::gpu_wgpu::dsv4_layer_frame(
2437 &model,
2438 &w,
2439 geom,
2440 kv_id,
2441 li,
2442 Some(&prep.qr),
2443 &idx32,
2444 freqs_of(l),
2445 st.pos,
2446 &mut next,
2447 None,
2448 None,
2449 &mut Vec::new(),
2450 ) {
2451 return false;
2452 }
2453 state_on_host = false;
2454 folded = next;
2455 dspark_note(li, state, cfg);
2456 }
2457 let mut state_home = false;
2458 if chain {
2459 if !run.is_empty() {
2460 let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
2464 let last_on_dev = *on_dev.last().unwrap_or(&false);
2465 let carry = last_on_dev && run.last() == Some(&(layers.len() - 1));
2466 let ok = if carry {
2467 let r = dsv4_chain_run(
2468 layers,
2469 &run,
2470 cfg,
2471 g,
2472 st,
2473 token_id,
2474 &mut folded,
2475 Some(state),
2476 1,
2477 &[],
2478 need_qn,
2479 pool,
2480 );
2481 state_home = r;
2482 state_on_host = r;
2483 if r {
2484 dspark_note(*run.last().unwrap(), state, cfg);
2485 }
2486 r
2487 } else {
2488 let r = dsv4_chain_run(
2489 layers,
2490 &run,
2491 cfg,
2492 g,
2493 st,
2494 token_id,
2495 &mut folded,
2496 None,
2497 1,
2498 &[],
2499 need_qn,
2500 pool,
2501 );
2502 if r {
2503 state_on_host = false;
2504 }
2505 r
2506 };
2507 if !ok {
2508 return false;
2509 }
2510 }
2511 if st.dev_set.is_empty() {
2512 st.dev_set = active_dev.clone();
2513 st.partial_set = partial_dev.clone();
2514 let mut idxs = Vec::new();
2520 for (li, l) in layers.iter().enumerate() {
2521 if !active_dev.get(li).copied().unwrap_or(false) {
2522 continue;
2523 }
2524 for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b, &l.gate] {
2525 idxs.extend(t.model_idx());
2526 }
2527 if let Some(pk) = pack_for(l, cfg, li) {
2528 for &(a, b, c) in &pk.tensors {
2529 idxs.extend([a, b, c]);
2530 }
2531 }
2532 }
2533 for (li, l) in layers.iter().enumerate() {
2541 if active_dev.get(li).copied().unwrap_or(false) {
2542 continue;
2543 }
2544 let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
2545 tracing::info!(
2546 "layer {li} on the host: packed {packed} experts of {}",
2547 cfg.n_routed_experts
2548 );
2549 }
2550 let pinned = layers
2551 .iter()
2552 .find_map(|l| l.experts.first().and_then(|e| e.w1.model_arc()))
2553 .map_or(0, |m| crate::gpu_wgpu::pin_weights(&m, &idxs));
2554 tracing::info!(
2555 "pinned on the card: {pinned} tensors of {} layers",
2556 on_dev.iter().filter(|&&x| x).count()
2557 );
2558 }
2559 }
2560 if state_home || state_on_host {
2561 return true;
2562 }
2563 crate::gpu_wgpu::dsv4_state_read(state)
2564}
2565
2566#[cfg(feature = "gpu")]
2571#[allow(clippy::too_many_arguments)]
2572fn dsv4_partial_layer(
2573 state: &mut [f32],
2574 folded: &mut Vec<f32>,
2575 layers: &[Dsv4Layer],
2576 l: &Dsv4Layer,
2577 cfg: &Dsv4Cfg,
2578 st: &mut Dsv4State,
2579 token_id: u32,
2580 li: usize,
2581 freqs: &[f32],
2582 pool: Option<&crate::pool::Pool>,
2583 state_on_host: bool,
2584) -> Option<bool> {
2585 let dim = cfg.dim;
2586 if state_on_host {
2595 let (f, post, comb) = hc_fold_norm(
2596 state,
2597 &l.hc_attn_fn,
2598 &l.hc_attn_scale,
2599 &l.hc_attn_base,
2600 &l.attn_norm,
2601 cfg,
2602 pool,
2603 );
2604 *folded = f;
2605 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2606 || !crate::gpu_wgpu::dsv4_state_write(state)
2607 {
2608 return None;
2609 }
2610 }
2611 let mut prep = AttnPrep::default();
2612 let mut sink = vec![0.0f32; dim];
2613 attention_step(
2614 folded,
2615 l,
2616 cfg,
2617 st,
2618 li,
2619 freqs,
2620 pool,
2621 Some(&mut prep),
2622 &mut sink,
2623 );
2624 let hd = cfg.head_dim;
2625 let n_comp = st.compressed[li].len() / hd;
2626 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2627 if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
2628 || (n_comp > 0
2629 && !crate::gpu_wgpu::dsv4_cache_write(
2630 st.kv_id,
2631 li,
2632 cfg.window * hd,
2633 &st.compressed[li],
2634 cap,
2635 ))
2636 {
2637 return None;
2638 }
2639 let a_tail = crate::gpu_wgpu::Dsv4HcTail {
2640 fn_: &l.hc_ffn_fn,
2641 scale: &l.hc_ffn_scale,
2642 base: &l.hc_ffn_base,
2643 norm: &l.ffn_norm,
2644 hc: cfg.hc_mult,
2645 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2646 hc_eps: cfg.hc_eps,
2647 eps: cfg.norm_eps,
2648 };
2649 let scale = (cfg.head_dim as f32).powf(-0.5);
2650 let mut ffn_input = if cpu_overlap_on() {
2656 vec![0.0f32; dim]
2657 } else {
2658 Vec::new()
2659 };
2660 if !attn_frame(
2661 l,
2662 cfg,
2663 st,
2664 li,
2665 folded,
2666 &prep.qr,
2667 &prep.idxs,
2668 freqs,
2669 st.pos,
2670 prep.win_len,
2671 scale,
2672 Some(&a_tail),
2673 &mut ffn_input,
2674 ) {
2675 return None;
2676 }
2677 let nxt = layers.get(li + 1);
2678 let forced = l
2679 .tid2eid
2680 .as_ref()
2681 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
2682 let mut next = vec![0.0f32; dim];
2683 let (cold_sum, cold_count) = moe_frame(
2684 &ffn_input,
2685 l,
2686 cfg,
2687 li,
2688 &[],
2689 forced.as_deref(),
2690 pool,
2691 Some(&a_tail),
2692 None,
2697 &mut next,
2698 )?;
2699 let state_ok = if cold_count == 0 {
2705 crate::gpu_wgpu::dsv4_state_read(state)
2706 } else {
2707 crate::gpu_wgpu::dsv4_state_add_cold(&cold_sum, cfg.hc_mult, state)
2708 };
2709 if !state_ok {
2710 return None;
2711 }
2712 if let Some(n) = nxt {
2713 let (f, post, comb) = hc_fold_norm(
2714 state,
2715 &n.hc_attn_fn,
2716 &n.hc_attn_scale,
2717 &n.hc_attn_base,
2718 &n.attn_norm,
2719 cfg,
2720 pool,
2721 );
2722 *folded = f;
2723 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2724 || !crate::gpu_wgpu::dsv4_state_write(state)
2725 {
2726 return None;
2727 }
2728 }
2729 Some(true)
2734}
2735
2736#[cfg(feature = "gpu")]
2743fn chain1_on() -> bool {
2744 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2745 *ON.get_or_init(|| {
2746 std::env::var("CMF_DSV4_CHAIN1")
2747 .map(|v| v != "0")
2748 .unwrap_or(true)
2749 })
2750}
2751
2752#[cfg(feature = "gpu")]
2758fn cpu_overlap_on() -> bool {
2759 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2760 *ON.get_or_init(|| std::env::var("CMF_DSV4_CPU_OVERLAP").is_ok_and(|v| v != "0"))
2761}
2762
2763#[cfg(feature = "gpu")]
2764#[allow(clippy::too_many_arguments)]
2765fn dsv4_chain1_layer(
2766 state: &mut [f32],
2767 folded: &mut Vec<f32>,
2768 layers: &[Dsv4Layer],
2769 l: &Dsv4Layer,
2770 cfg: &Dsv4Cfg,
2771 st: &mut Dsv4State,
2772 token_id: u32,
2773 li: usize,
2774 freqs: &[f32],
2775 pool: Option<&crate::pool::Pool>,
2776 state_on_host: bool,
2777) -> Option<bool> {
2778 let dim = cfg.dim;
2779 let pk = pack_for(l, cfg, li)?;
2780 if pk.route_complete() {
2781 return None;
2782 }
2783 let model = l.experts.first().and_then(|e| e.w1.model_arc())?;
2784 if state_on_host {
2788 let (f, post, comb) = hc_fold_norm(
2789 state,
2790 &l.hc_attn_fn,
2791 &l.hc_attn_scale,
2792 &l.hc_attn_base,
2793 &l.attn_norm,
2794 cfg,
2795 pool,
2796 );
2797 *folded = f;
2798 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2799 || !crate::gpu_wgpu::dsv4_state_write(state)
2800 {
2801 return None;
2802 }
2803 }
2804 let mut prep = AttnPrep::default();
2805 let mut sink = vec![0.0f32; dim];
2806 attention_step(
2807 folded,
2808 l,
2809 cfg,
2810 st,
2811 li,
2812 freqs,
2813 pool,
2814 Some(&mut prep),
2815 &mut sink,
2816 );
2817 let hd = cfg.head_dim;
2818 let n_comp = st.compressed[li].len() / hd;
2819 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2820 let kv_id = st.kv_id;
2821 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap)
2822 || (n_comp > 0
2823 && !crate::gpu_wgpu::dsv4_cache_write(
2824 kv_id,
2825 li,
2826 cfg.window * hd,
2827 &st.compressed[li],
2828 cap,
2829 ))
2830 {
2831 return None;
2832 }
2833 let idx32: Vec<u32> = prep
2834 .idxs
2835 .iter()
2836 .map(|&p| {
2837 if p < prep.win_len {
2838 p as u32
2839 } else {
2840 (cfg.window + (p - prep.win_len)) as u32
2841 }
2842 })
2843 .collect();
2844 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
2845 l.wq_a.model_idx(),
2846 l.wq_b.model_idx(),
2847 l.wo_a.model_idx(),
2848 l.wo_b.model_idx(),
2849 ) else {
2850 return None;
2851 };
2852 let forced: Option<Vec<usize>> = l
2855 .tid2eid
2856 .as_ref()
2857 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
2858 let global_remap = pk
2859 .global
2860 .as_ref()
2861 .map(|gl| gl.pool.remap(gl.layer, cfg.n_routed_experts));
2862 let dynv = pk.dynslots.lock().unwrap();
2863 let live_remap = global_remap.as_deref().unwrap_or(dynv.remap.as_slice());
2864 let nxt = layers.get(li + 1);
2865 let w = crate::gpu_wgpu::Dsv4LayerW {
2866 attn: crate::gpu_wgpu::Dsv4AttnW {
2867 wq_a,
2868 wq_b,
2869 wo_a,
2870 wo_b,
2871 q_norm: &l.q_norm,
2872 sink: &l.attn_sink,
2873 },
2874 moe: crate::gpu_wgpu::Dsv4MoeW {
2875 router: &[],
2876 experts: &pk.tensors,
2877 logits: &[],
2878 bias: pk.bias.as_deref(),
2881 mask: pk.mask.as_deref(),
2882 forced: forced.as_deref(),
2883 remap: Some(live_remap),
2884 global: pk.global.as_ref().map(|gl| crate::gpu_wgpu::Dsv4GlobalMoe {
2885 pool_uid: gl.pool.uid,
2886 shared_slot: gl.shared_slot,
2887 segment_slots: gl.pool.segment_slots as u32,
2888 }),
2889 has_shared: true,
2890 shared_weight: 1.0,
2891 preweighted: false,
2892 qwen_softmax: false,
2893 },
2894 hc_ffn_fn: &l.hc_ffn_fn,
2895 hc_ffn_scale: &l.hc_ffn_scale,
2896 hc_ffn_base: &l.hc_ffn_base,
2897 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2898 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2899 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2900 ffn_norm: &l.ffn_norm,
2901 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2902 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2903 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2904 router: &pk.router,
2905 };
2906 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2907 attn: crate::gpu_wgpu::Dsv4AttnGeom {
2908 dim,
2909 nh: cfg.n_heads,
2910 hd,
2911 rd: cfg.rope_head_dim,
2912 q_lora: cfg.q_lora_rank,
2913 o_lora: cfg.o_lora_rank,
2914 o_groups: cfg.o_groups,
2915 eps: cfg.norm_eps,
2916 scale: (hd as f32).powf(-0.5),
2917 bf16: false,
2918 q_rms: true,
2919 o_fp8: false,
2920 },
2921 moe: crate::gpu_wgpu::Dsv4MoeGeom {
2922 hidden: dim,
2923 inter: cfg.moe_inter,
2924 top_k: cfg.top_k,
2925 route_scale: cfg.route_scale,
2926 swiglu_limit: cfg.swiglu_limit,
2927 gu_q2: l
2928 .experts
2929 .first()
2930 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
2931 bf16: false,
2932 },
2933 hc: cfg.hc_mult,
2934 hc_eps: cfg.hc_eps,
2935 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2936 };
2937 let mut next = vec![0.0f32; dim];
2938 let mut cold: Vec<(usize, f32)> = Vec::new();
2939 let mut routed: Vec<(usize, f32)> = Vec::new();
2940 let mut cold_x: Vec<f32> = Vec::new();
2941 let need_routes = route_stats_on() || pk.global.is_some();
2942 if !crate::gpu_wgpu::dsv4_layer_frame(
2943 &model,
2944 &w,
2945 geom,
2946 kv_id,
2947 li,
2948 Some(&prep.qr),
2949 &idx32,
2950 freqs,
2951 st.pos,
2952 &mut next,
2953 Some(&mut cold),
2954 need_routes.then_some(&mut routed),
2955 &mut cold_x,
2956 ) {
2957 return None;
2958 }
2959 if route_stats_on() {
2960 record_route(li, layers.len(), cfg.n_routed_experts, &routed);
2961 }
2962 drop(dynv);
2963 if let Some(gl) = pk.global.as_ref() {
2968 let picks: Vec<usize> = routed.iter().map(|&(gi, _)| gi).collect();
2969 gl.pool
2970 .ensure_picks(&model, gl.layer, &picks, &l.experts, cfg.top_k, 1);
2971 }
2972 if cold.is_empty() {
2973 *folded = next;
2977 return Some(false);
2978 }
2979 if cold_x.len() < dim {
2982 return None;
2983 }
2984 let mut cold_sum = vec![0.0f32; dim];
2985 {
2986 let results: Vec<std::sync::Mutex<Vec<f32>>> = cold
2987 .iter()
2988 .map(|_| std::sync::Mutex::new(Vec::new()))
2989 .collect();
2990 let (cold_ref, results_ref, x_ref) = (&cold, &results, &cold_x[..dim]);
2991 std::thread::scope(|sc| {
2992 for i in 0..cold_ref.len() {
2993 let (gi, wt) = cold_ref[i];
2994 let Some(exp) = l.experts.get(gi) else {
2995 continue;
2996 };
2997 let r = &results_ref[i];
2998 sc.spawn(move || {
2999 let mut a = vec![0.0f32; cfg.dim];
3000 crate::gpu::cpu_scope(|| run_expert(x_ref, exp, cfg, wt, None, &mut a));
3001 *r.lock().unwrap() = a;
3002 });
3003 }
3004 });
3005 for r in &results {
3006 let a = r.lock().unwrap();
3007 for (o, v) in cold_sum.iter_mut().zip(a.iter()) {
3008 *o += v;
3009 }
3010 }
3011 }
3012 if !crate::gpu_wgpu::dsv4_state_add_cold_preserved(&cold_sum, cfg.hc_mult, state, kv_id, li) {
3013 return None;
3014 }
3015 if pk.global.is_none() {
3018 let mut dynv = pk.dynslots.lock().unwrap();
3019 dynv.clock += 1;
3020 let clock = dynv.clock;
3021 for &(gi, _) in &cold {
3022 if gi >= dynv.remap.len() || dynv.remap[gi] != u32::MAX {
3023 continue;
3024 }
3025 let victim = (0..dynv.owner.len())
3026 .filter(|&sl| dynv.last[sl] != clock)
3027 .min_by_key(|&sl| dynv.last[sl]);
3028 let Some(victim) = victim else { break };
3029 let Some(exp) = l.experts.get(gi) else {
3030 continue;
3031 };
3032 let t3 = (|| {
3033 Some((
3034 exp.w1.model_idx()?,
3035 exp.w3.model_idx()?,
3036 exp.w2.model_idx()?,
3037 ))
3038 })();
3039 let Some(t3) = t3 else { continue };
3040 let gu_q2 = exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
3041 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
3042 if !crate::gpu_wgpu::dsv4_slot_fill(
3043 &model,
3044 pack_first,
3045 victim,
3046 gi,
3047 t3,
3048 cfg.moe_inter,
3049 cfg.dim,
3050 gu_q2,
3051 ) {
3052 break;
3053 }
3054 let old = dynv.owner[victim] as usize;
3055 if old < dynv.remap.len() {
3056 dynv.remap[old] = u32::MAX;
3057 }
3058 dynv.remap[gi] = victim as u32;
3059 dynv.owner[victim] = gi as u32;
3060 dynv.last[victim] = clock;
3061 dynv.mutated = true;
3062 }
3063 }
3064 if let Some(n) = nxt {
3065 let (f, post, comb) = hc_fold_norm(
3066 state,
3067 &n.hc_attn_fn,
3068 &n.hc_attn_scale,
3069 &n.hc_attn_base,
3070 &n.attn_norm,
3071 cfg,
3072 pool,
3073 );
3074 *folded = f;
3075 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb) {
3076 return None;
3077 }
3078 }
3079 Some(true)
3080}
3081
3082#[cfg(feature = "gpu")]
3083fn chain_max() -> usize {
3084 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
3085 *N.get_or_init(|| {
3086 std::env::var("CMF_DSV4_CHAIN_MAX")
3087 .ok()
3088 .and_then(|v| v.parse().ok())
3089 .unwrap_or(usize::MAX)
3090 })
3091}
3092
3093#[cfg(feature = "gpu")]
3096fn partial_walk_on() -> bool {
3107 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3108 *ON.get_or_init(|| std::env::var("CMF_DSV4_PARTIAL_WALK").is_ok_and(|v| v != "0"))
3109}
3110
3111fn host_cpu_moe() -> bool {
3112 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3113 *ON.get_or_init(|| std::env::var("CMF_DSV4_HOST_CPU_MOE").is_ok_and(|v| v != "0"))
3114}
3115
3116fn chain_enabled() -> bool {
3117 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3118 *ON.get_or_init(|| {
3119 std::env::var("CMF_DSV4_CHAIN")
3120 .map(|v| v != "0")
3121 .unwrap_or(true)
3122 })
3123}
3124
3125#[cfg(feature = "gpu")]
3135#[allow(clippy::too_many_arguments)]
3136fn dsv4_chain_run(
3137 layers: &[Dsv4Layer],
3138 run: &[usize],
3139 cfg: &Dsv4Cfg,
3140 g: &Dsv4Globals,
3141 st: &mut Dsv4State,
3142 token_id: u32,
3143 folded: &mut Vec<f32>,
3149 state_out: Option<&mut [f32]>,
3154 batch: usize,
3157 batch_ids: &[u32],
3161 need_qn: bool,
3168 pool: Option<&crate::pool::Pool>,
3169) -> bool {
3170 if run.is_empty() {
3171 return true;
3172 }
3173 let (dim, hd) = (cfg.dim, cfg.head_dim);
3174 let first = run[0];
3175 let Some(model) = layers[first].experts.first().and_then(|e| e.w1.model_arc()) else {
3176 return false;
3177 };
3178 if batch <= 1 && need_qn {
3182 let mut qn0 = vec![0.0f32; cfg.q_lora_rank];
3183 layers[first].wq_a.matvec(folded, &mut qn0, pool);
3184 rms_weighted(&mut qn0, &layers[first].q_norm, cfg.norm_eps);
3185 if !crate::gpu_wgpu::dsv4_chain_seed(folded, &qn0) {
3186 return false;
3187 }
3188 } else if batch <= 1 && !crate::gpu_wgpu::dsv4_chain_seed_fold(folded) {
3189 return false;
3190 }
3191
3192 let mut packs = Vec::with_capacity(run.len());
3194 let mut forceds: Vec<Option<Vec<usize>>> = Vec::with_capacity(run.len());
3195 for &li in run {
3196 let Some(pk) = pack_for(&layers[li], cfg, li) else {
3197 return false;
3198 };
3199 if !pk.route_complete() || pk.is_mutated() {
3206 return false;
3207 }
3208 let forced: Option<Vec<usize>> = layers[li].tid2eid.as_ref().and_then(|tbl| {
3209 let v: Vec<usize> = if pk.needs_remap() {
3210 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
3211 } else {
3212 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
3213 .into_iter()
3214 .map(|gi| pk.to_slot[gi])
3215 .collect()
3216 };
3217 if v.contains(&usize::MAX) {
3218 None
3219 } else {
3220 Some(v)
3221 }
3222 });
3223 if layers[li].tid2eid.is_some() && forced.is_none() {
3224 return false;
3225 }
3226 forceds.push(forced);
3227 packs.push(pk);
3228 }
3229
3230 let mut items = Vec::with_capacity(run.len());
3231 let mut freqs = Vec::with_capacity(run.len());
3232 for (i, &li) in run.iter().enumerate() {
3233 let l = &layers[li];
3234 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
3235 l.wq_a.model_idx(),
3236 l.wq_b.model_idx(),
3237 l.wo_a.model_idx(),
3238 l.wo_b.model_idx(),
3239 l.wkv.model_idx(),
3240 ) else {
3241 return false;
3242 };
3243 let comp = match &l.compressor {
3244 None => None,
3245 Some(cp) => {
3246 let (Some(a), Some(b)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
3247 return false;
3248 };
3249 Some((
3250 crate::gpu_wgpu::Dsv4CompW {
3251 wkv: a,
3252 wgate: b,
3253 norm: &cp.norm,
3254 ape: &cp.ape,
3255 },
3256 crate::gpu_wgpu::Dsv4CompGeom {
3257 width: cp.wkv.rows(),
3258 hidden: dim,
3259 ratio: cp.ratio,
3260 overlap: cp.overlap,
3261 rope_dim: cfg.rope_head_dim,
3262 eps: cfg.norm_eps,
3263 },
3264 ))
3265 }
3266 };
3267 let ix = match &l.indexer {
3268 None => None,
3269 Some(ixr) => {
3270 let cp = &ixr.compressor;
3271 let (Some(a), Some(b), Some(qb), Some(wp)) = (
3272 cp.wkv.model_idx(),
3273 cp.wgate.model_idx(),
3274 ixr.wq_b.model_idx(),
3275 ixr.weights_proj.model_idx(),
3276 ) else {
3277 return false;
3278 };
3279 let ih = ixr.weights_proj.rows();
3280 Some((
3281 crate::gpu_wgpu::Dsv4CompW {
3282 wkv: a,
3283 wgate: b,
3284 norm: &cp.norm,
3285 ape: &cp.ape,
3286 },
3287 crate::gpu_wgpu::Dsv4CompGeom {
3288 width: cp.wkv.rows(),
3289 hidden: dim,
3290 ratio: cp.ratio,
3291 overlap: cp.overlap,
3292 rope_dim: cfg.rope_head_dim,
3293 eps: cfg.norm_eps,
3294 },
3295 crate::gpu_wgpu::Dsv4IxW {
3296 wq_b: qb,
3297 weights_proj: wp,
3298 },
3299 crate::gpu_wgpu::Dsv4IxGeom {
3300 ih,
3301 idim: ixr.wq_b.rows() / ih.max(1),
3302 q_lora: cfg.q_lora_rank,
3303 hidden: dim,
3304 rope_dim: cfg.rope_head_dim,
3305 eps: cfg.norm_eps,
3306 top_k: cfg.index_topk,
3307 window: cfg.window,
3308 },
3309 ))
3310 }
3311 };
3312 let ew_c0 = l.compressor.as_ref().map_or(0, |cp| {
3316 if cp.overlap {
3317 cp.wkv.rows() / 2
3318 } else {
3319 cp.wkv.rows()
3320 }
3321 });
3322 let comp_extra = l
3323 .compressor
3324 .as_ref()
3325 .map_or(0, |cp| batch.max(1).div_ceil(cp.ratio.max(1)));
3326 let need = cfg.window * hd
3327 + (st.dev_n_comp[li] + comp_extra + 1) * ew_c0.max(1)
3328 + (batch.max(1) + 1) * hd;
3329 if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
3330 return false;
3331 }
3332 let ew_c = comp.as_ref().map_or(
3333 0,
3334 |(_, cg)| {
3335 if cg.overlap { cg.width / 2 } else { cg.width }
3336 },
3337 );
3338 let ew_i = ix.as_ref().map_or(
3339 0,
3340 |(_, cg, _, _)| {
3341 if cg.overlap { cg.width / 2 } else { cg.width }
3342 },
3343 );
3344 let prep = crate::gpu_wgpu::Dsv4Prep {
3345 wkv,
3346 kv_norm: &l.kv_norm,
3347 comp,
3348 ix,
3349 filled: st.dev_filled[li],
3350 window: cfg.window,
3351 n_comp: st.dev_n_comp[li],
3352 n_ix: st.dev_n_ix[li],
3353 comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
3354 ix_dst_off: st.dev_n_ix[li] * ew_i,
3355 idx_cap: cfg.window
3356 + if l.indexer.is_some() {
3357 cfg.index_topk
3358 } else {
3359 st.dev_n_comp[li] + comp_extra + 1
3360 },
3361 };
3362 let nxt = layers.get(li + 1);
3363 let w = crate::gpu_wgpu::Dsv4LayerW {
3364 attn: crate::gpu_wgpu::Dsv4AttnW {
3365 wq_a,
3366 wq_b,
3367 wo_a,
3368 wo_b,
3369 q_norm: &l.q_norm,
3370 sink: &l.attn_sink,
3371 },
3372 moe: crate::gpu_wgpu::Dsv4MoeW {
3373 router: &packs[i].router,
3374 experts: &packs[i].tensors,
3375 logits: &[],
3376 bias: packs[i].bias.as_deref(),
3379 mask: packs[i].mask.as_deref(),
3380 forced: forceds[i].as_deref(),
3381 remap: packs[i].needs_remap().then_some(packs[i].remap.as_slice()),
3382 global: None,
3383 has_shared: true,
3384 shared_weight: 1.0,
3385 preweighted: false,
3386 qwen_softmax: false,
3387 },
3388 hc_ffn_fn: &l.hc_ffn_fn,
3389 hc_ffn_scale: &l.hc_ffn_scale,
3390 hc_ffn_base: &l.hc_ffn_base,
3391 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
3392 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
3393 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
3394 ffn_norm: &l.ffn_norm,
3395 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
3396 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
3397 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
3398 router: &packs[i].router,
3399 };
3400 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
3401 attn: crate::gpu_wgpu::Dsv4AttnGeom {
3402 dim,
3403 nh: cfg.n_heads,
3404 hd,
3405 rd: cfg.rope_head_dim,
3406 q_lora: cfg.q_lora_rank,
3407 o_lora: cfg.o_lora_rank,
3408 o_groups: cfg.o_groups,
3409 eps: cfg.norm_eps,
3410 scale: (hd as f32).powf(-0.5),
3411 bf16: false,
3412 q_rms: true,
3413 o_fp8: false,
3414 },
3415 moe: crate::gpu_wgpu::Dsv4MoeGeom {
3416 hidden: dim,
3417 inter: cfg.moe_inter,
3418 top_k: cfg.top_k,
3419 route_scale: cfg.route_scale,
3420 swiglu_limit: cfg.swiglu_limit,
3421 gu_q2: l.experts.first().is_some_and(|e| {
3422 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
3423 }),
3424 bf16: false,
3425 },
3426 hc: cfg.hc_mult,
3427 hc_eps: cfg.hc_eps,
3428 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3429 };
3430 freqs.push(if l.compressor.is_some() {
3431 g.inv_freq_compress.as_slice()
3432 } else {
3433 g.inv_freq_window.as_slice()
3434 });
3435 items.push((w, geom, prep));
3436 }
3437
3438 let mut out = vec![0.0f32; dim * batch.max(1)];
3439 if batch > 1 {
3440 let mut forced_pt: Vec<Vec<Option<Vec<usize>>>> = Vec::with_capacity(batch);
3445 for t in 0..batch {
3446 let id = batch_ids.get(t).copied().unwrap_or(token_id);
3447 let mut row = Vec::with_capacity(run.len());
3448 for (i, &li) in run.iter().enumerate() {
3449 row.push(layers[li].tid2eid.as_ref().and_then(|tbl| {
3450 let v: Vec<usize> = if packs[i].needs_remap() {
3451 hash_route(tbl, cfg.vocab, cfg.top_k, id)
3452 } else {
3453 hash_route(tbl, cfg.vocab, cfg.top_k, id)
3454 .into_iter()
3455 .map(|gi| packs[i].to_slot[gi])
3456 .collect()
3457 };
3458 if v.contains(&usize::MAX) {
3459 None
3460 } else {
3461 Some(v)
3462 }
3463 }));
3464 if layers[li].tid2eid.is_some() && row[i].is_none() {
3465 return false;
3466 }
3467 }
3468 forced_pt.push(row);
3469 }
3470 if !crate::gpu_wgpu::dsv4_chain_batch(
3471 &model,
3472 &items,
3473 st.kv_id,
3474 first,
3475 &freqs,
3476 st.pos,
3477 batch,
3478 Some(&forced_pt),
3479 &mut out,
3480 state_out,
3481 ) {
3482 return false;
3483 }
3484 *folded = out[(batch - 1) * dim..batch * dim].to_vec();
3487 } else {
3488 if !crate::gpu_wgpu::dsv4_layer_chain(
3489 &model, &items, st.kv_id, first, &freqs, st.pos, &mut out, state_out,
3490 ) {
3491 return false;
3492 }
3493 *folded = out;
3494 }
3495 for (i, &li) in run.iter().enumerate() {
3499 for t in 0..batch.max(1) {
3500 let pos = st.pos + t;
3501 st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
3502 if let Some((_, cg, ..)) = items[i].2.ix.as_ref() {
3503 if (pos + 1) % cg.ratio == 0 {
3504 st.dev_n_ix[li] += 1;
3505 }
3506 }
3507 if let Some((_, cg)) = items[i].2.comp.as_ref() {
3508 if (pos + 1) % cg.ratio == 0 {
3509 st.dev_n_comp[li] += 1;
3510 }
3511 }
3512 }
3513 }
3514 st.dev_owned = true;
3515 true
3516}
3517
3518#[cfg(feature = "gpu")]
3520fn hc_on_device() -> bool {
3521 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3522 *ON.get_or_init(|| {
3523 std::env::var("CMF_DSV4_HC_DEV").is_ok_and(|v| v != "0") && crate::gpu::backend_available()
3529 })
3530}
3531
3532#[cfg(feature = "gpu")]
3541#[allow(clippy::too_many_arguments)]
3542fn dsv4_two_frame_loop(
3543 state: &mut [f32],
3544 layers: &[Dsv4Layer],
3545 g: &Dsv4Globals,
3546 cfg: &Dsv4Cfg,
3547 st: &mut Dsv4State,
3548 token_id: u32,
3549 inv_freq: &[f32],
3550 pool: Option<&crate::pool::Pool>,
3551 scratch: &mut HcScratch,
3552) -> bool {
3553 let dim = cfg.dim;
3554 let freqs_of = |l: &Dsv4Layer| -> &[f32] {
3555 let f = if l.compressor.is_some() {
3556 &g.inv_freq_compress
3557 } else {
3558 &g.inv_freq_window
3559 };
3560 if f.is_empty() { inv_freq } else { f.as_slice() }
3561 };
3562 let (mut folded, post0, comb0) = hc_fold_norm(
3564 state,
3565 &layers[0].hc_attn_fn,
3566 &layers[0].hc_attn_scale,
3567 &layers[0].hc_attn_base,
3568 &layers[0].attn_norm,
3569 cfg,
3570 pool,
3571 );
3572 if !crate::gpu_wgpu::dsv4_state_write(state) || !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
3573 {
3574 return false;
3575 }
3576 let mut on_dev = vec![false; layers.len()];
3581 for (li, l) in layers.iter().enumerate() {
3582 let Some(pk) = pack_for(l, cfg, li) else {
3583 return false;
3584 };
3585 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
3586 return false;
3587 };
3588 let gu_q2 = l
3589 .experts
3590 .first()
3591 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
3592 let attn_ok = [
3593 l.wq_a.model_idx(),
3594 l.wq_b.model_idx(),
3595 l.wo_a.model_idx(),
3596 l.wo_b.model_idx(),
3597 ]
3598 .into_iter()
3599 .flatten()
3600 .all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
3601 on_dev[li] = attn_ok
3602 && pk.route_complete()
3603 && crate::gpu_wgpu::dsv4_experts_ready(
3604 &model,
3605 &pk.tensors,
3606 cfg.moe_inter,
3607 dim,
3608 gu_q2,
3609 l.experts.first().is_some_and(|e| {
3610 e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
3611 }),
3612 );
3613 }
3614 if !on_dev.iter().any(|&x| x) {
3615 return false;
3616 }
3617 let mut sink = vec![0.0f32; dim];
3618 for (li, l) in layers.iter().enumerate() {
3619 if !on_dev[li] {
3623 if !crate::gpu_wgpu::dsv4_state_read(state) {
3624 return false;
3625 }
3626 let freqs = freqs_of(l);
3627 hc_block(
3628 state,
3629 &l.hc_attn_fn,
3630 &l.hc_attn_scale,
3631 &l.hc_attn_base,
3632 &l.attn_norm,
3633 cfg,
3634 scratch,
3635 pool,
3636 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
3637 );
3638 hc_block(
3639 state,
3640 &l.hc_ffn_fn,
3641 &l.hc_ffn_scale,
3642 &l.hc_ffn_base,
3643 &l.ffn_norm,
3644 cfg,
3645 scratch,
3646 pool,
3647 |f, o| moe_step(f, l, cfg, token_id, li, pool, o),
3648 );
3649 let nref = layers.get(li + 1).unwrap_or(l);
3650 let (f, p2, c2) = hc_fold_norm(
3651 state,
3652 &nref.hc_attn_fn,
3653 &nref.hc_attn_scale,
3654 &nref.hc_attn_base,
3655 &nref.attn_norm,
3656 cfg,
3657 pool,
3658 );
3659 folded = f;
3660 if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2)
3661 || !crate::gpu_wgpu::dsv4_state_write(state)
3662 {
3663 return false;
3664 }
3665 continue;
3666 }
3667 let mut prep = AttnPrep::default();
3669 attention_step(
3670 &folded,
3671 l,
3672 cfg,
3673 st,
3674 li,
3675 freqs_of(l),
3676 pool,
3677 Some(&mut prep),
3678 &mut sink,
3679 );
3680 let hd = cfg.head_dim;
3681 let n_comp = st.compressed[li].len() / hd;
3682 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
3683 if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
3684 || (n_comp > 0
3685 && !crate::gpu_wgpu::dsv4_cache_write(
3686 st.kv_id,
3687 li,
3688 cfg.window * hd,
3689 &st.compressed[li],
3690 cap,
3691 ))
3692 {
3693 return false;
3694 }
3695 let _idx32: Vec<u32> = prep
3696 .idxs
3697 .iter()
3698 .map(|&p| {
3699 if p < prep.win_len {
3700 p as u32
3701 } else {
3702 (cfg.window + (p - prep.win_len)) as u32
3703 }
3704 })
3705 .collect();
3706 let nxt = layers.get(li + 1);
3707 let a_tail = crate::gpu_wgpu::Dsv4HcTail {
3708 fn_: &l.hc_ffn_fn,
3709 scale: &l.hc_ffn_scale,
3710 base: &l.hc_ffn_base,
3711 norm: &l.ffn_norm,
3712 hc: cfg.hc_mult,
3713 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3714 hc_eps: cfg.hc_eps,
3715 eps: cfg.norm_eps,
3716 };
3717 let scale = (cfg.head_dim as f32).powf(-0.5);
3718 if !attn_frame(
3719 l,
3720 cfg,
3721 st,
3722 li,
3723 &folded,
3724 &prep.qr,
3725 &prep.idxs,
3726 freqs_of(l),
3727 st.pos,
3728 prep.win_len,
3729 scale,
3730 Some(&a_tail),
3731 &mut [],
3732 ) {
3733 return false;
3734 }
3735 let m_tail = nxt.map(|n| crate::gpu_wgpu::Dsv4HcTail {
3736 fn_: &n.hc_attn_fn,
3737 scale: &n.hc_attn_scale,
3738 base: &n.hc_attn_base,
3739 norm: &n.attn_norm,
3740 hc: cfg.hc_mult,
3741 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3742 hc_eps: cfg.hc_eps,
3743 eps: cfg.norm_eps,
3744 });
3745 let mut next = vec![0.0f32; dim];
3746 let pair = m_tail
3747 .as_ref()
3748 .zip(nxt)
3749 .map(|(t, n)| (t, n.attn_norm.as_slice()));
3750 let forced = l
3751 .tid2eid
3752 .as_ref()
3753 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
3754 if moe_frame(
3755 &[],
3756 l,
3757 cfg,
3758 li,
3759 &[],
3760 forced.as_deref(),
3761 pool,
3762 Some(&a_tail),
3763 pair,
3764 &mut next,
3765 )
3766 .is_none()
3767 {
3768 return false;
3769 }
3770 folded = next;
3771 }
3772 let _ = scratch;
3773 crate::gpu_wgpu::dsv4_state_read(state)
3774}
3775
3776#[cfg(feature = "gpu")]
3780#[allow(clippy::too_many_arguments)]
3781fn hc_fold_norm(
3782 state: &[f32],
3783 hc_fn: &[f32],
3784 hc_scale: &[f32; 3],
3785 hc_base: &[f32],
3786 norm_w: &[f32],
3787 cfg: &Dsv4Cfg,
3788 pool: Option<&crate::pool::Pool>,
3789) -> (Vec<f32>, Vec<f32>, Vec<f32>) {
3790 let (hc, dim) = (cfg.hc_mult, cfg.dim);
3791 let mix_hc = (2 + hc) * hc;
3792 let mut mixes = vec![0.0f32; mix_hc];
3793 hc_mixes(state, hc_fn, mix_hc, cfg.norm_eps, pool, &mut mixes);
3794 let mut pre = vec![0.0f32; hc];
3795 let mut post = vec![0.0f32; hc];
3796 let mut comb = vec![0.0f32; hc * hc];
3797 hc_split_sinkhorn(
3798 &mixes,
3799 hc_scale,
3800 hc_base,
3801 hc,
3802 cfg.hc_sinkhorn_iters,
3803 cfg.hc_eps,
3804 &mut pre,
3805 &mut post,
3806 &mut comb,
3807 );
3808 let mut folded = vec![0.0f32; dim];
3809 hc_fold(state, &pre, hc, dim, &mut folded);
3810 rms_weighted(&mut folded, norm_w, cfg.norm_eps);
3811 (folded, post, comb)
3814}
3815
3816#[cfg(feature = "gpu")]
3833fn gpu_layer_enabled() -> bool {
3834 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3835 *ON.get_or_init(|| {
3836 std::env::var("CMF_DSV4_GPU_LAYER")
3837 .map(|v| v != "0")
3838 .unwrap_or(true)
3839 && crate::gpu::backend_available()
3840 })
3841}
3842
3843#[cfg(feature = "gpu")]
3847struct PackDyn {
3848 remap: Vec<u32>,
3849 owner: Vec<u32>,
3850 last: Vec<u64>,
3851 clock: u64,
3852 seen: Vec<u16>,
3859 mutated: bool,
3863}
3864
3865#[cfg(feature = "gpu")]
3870#[derive(Clone, Copy)]
3871struct GlobalOwner {
3872 layer: usize,
3873 expert: usize,
3874 pinned: bool,
3875}
3876
3877#[cfg(feature = "gpu")]
3878struct GlobalPoolDyn {
3879 slot_for: std::collections::HashMap<(usize, usize), u32>,
3880 owner: Vec<Option<GlobalOwner>>,
3881 last: Vec<u64>,
3882 seen: std::collections::HashMap<(usize, usize), u16>,
3883 occupancy: std::collections::HashMap<usize, usize>,
3884 clock: u64,
3885}
3886
3887#[cfg(feature = "gpu")]
3895struct GlobalPool {
3896 uid: u64,
3897 capacity: usize,
3898 segment_slots: usize,
3899 floor: usize,
3900 state: std::sync::Mutex<GlobalPoolDyn>,
3901}
3902
3903#[cfg(feature = "gpu")]
3904impl GlobalPool {
3905 fn remap(&self, layer: usize, n: usize) -> Vec<u32> {
3906 let st = self.state.lock().unwrap();
3907 debug_assert_eq!(self.capacity, st.owner.len());
3908 (0..n)
3909 .map(|e| st.slot_for.get(&(layer, e)).copied().unwrap_or(u32::MAX))
3910 .collect()
3911 }
3912
3913 fn seed_layer(
3914 &self,
3915 model: &std::sync::Arc<cortiq_core::CmfModel>,
3916 layer: usize,
3917 shared: (usize, usize, usize),
3918 _routed: &[(usize, (usize, usize, usize))],
3919 ) -> Option<u32> {
3920 let mut st = self.state.lock().unwrap();
3921 let shared_key = (layer, usize::MAX);
3922 let shared_slot = if let Some(&slot) = st.slot_for.get(&shared_key) {
3923 slot
3924 } else {
3925 let slot = st.owner.iter().position(Option::is_none)?;
3926 if !crate::gpu_wgpu::dsv4_global_slot_fill(model, slot, shared) {
3927 return None;
3928 }
3929 st.owner[slot] = Some(GlobalOwner {
3930 layer,
3931 expert: usize::MAX,
3932 pinned: true,
3933 });
3934 st.slot_for.insert(shared_key, slot as u32);
3935 *st.occupancy.entry(layer).or_insert(0) += 1;
3936 slot as u32
3937 };
3938 Some(shared_slot)
3943 }
3944
3945 fn ensure_picks(
3946 &self,
3947 model: &std::sync::Arc<cortiq_core::CmfModel>,
3948 layer: usize,
3949 picks: &[usize],
3950 experts: &[Dsv4Expert],
3951 quota: usize,
3952 min_seen: u16,
3953 ) -> Vec<u32> {
3954 let mut st = self.state.lock().unwrap();
3955 st.clock = st.clock.saturating_add(1);
3956 let clock = st.clock;
3957 if clock % 64 == 0 {
3958 for v in st.seen.values_mut() {
3959 *v >>= 1;
3960 }
3961 }
3962 for &expert in picks {
3963 let key = (layer, expert);
3964 let seen = st.seen.entry(key).or_insert(0);
3965 *seen = seen.saturating_add(1);
3966 if let Some(&slot) = st.slot_for.get(&key) {
3967 st.last[slot as usize] = clock;
3968 }
3969 }
3970 let mut fetched = 0usize;
3971 for &expert in picks {
3972 if fetched >= quota {
3973 break;
3974 }
3975 let key = (layer, expert);
3976 if st.slot_for.contains_key(&key) || st.seen.get(&key).copied().unwrap_or(0) < min_seen
3977 {
3978 continue;
3979 }
3980 let Some(exp) = experts.get(expert) else {
3981 continue;
3982 };
3983 let Some(tensors) = (|| {
3984 Some((
3985 exp.w1.model_idx()?,
3986 exp.w3.model_idx()?,
3987 exp.w2.model_idx()?,
3988 ))
3989 })() else {
3990 continue;
3991 };
3992 let empty = st.owner.iter().position(Option::is_none);
3993 let over_floor = |o: GlobalOwner, occ: &std::collections::HashMap<usize, usize>| {
3996 occ.get(&o.layer).copied().unwrap_or(0) > self.floor
3997 };
3998 let eligible =
3999 |o: GlobalOwner| !o.pinned && !(o.layer == layer && picks.contains(&o.expert));
4000 let victim = empty
4001 .or_else(|| {
4002 st.owner
4003 .iter()
4004 .enumerate()
4005 .filter_map(|(slot, &o)| {
4006 o.filter(|&x| eligible(x) && over_floor(x, &st.occupancy))
4007 .map(|_| slot)
4008 })
4009 .min_by_key(|&slot| st.last[slot])
4010 })
4011 .or_else(|| {
4012 st.owner
4016 .iter()
4017 .enumerate()
4018 .filter_map(|(slot, &o)| {
4019 o.filter(|&x| eligible(x) && x.layer == layer).map(|_| slot)
4020 })
4021 .min_by_key(|&slot| st.last[slot])
4022 })
4023 .or_else(|| {
4024 st.owner
4028 .iter()
4029 .enumerate()
4030 .filter_map(|(slot, &o)| o.filter(|&x| eligible(x)).map(|_| slot))
4031 .min_by_key(|&slot| st.last[slot])
4032 });
4033 let Some(victim) = victim else { break };
4034 if !crate::gpu_wgpu::dsv4_global_slot_fill(model, victim, tensors) {
4035 break;
4036 }
4037 if let Some(old) = st.owner[victim] {
4038 st.slot_for.remove(&(old.layer, old.expert));
4039 if let Some(n) = st.occupancy.get_mut(&old.layer) {
4040 *n = n.saturating_sub(1);
4041 }
4042 }
4043 st.owner[victim] = Some(GlobalOwner {
4044 layer,
4045 expert,
4046 pinned: false,
4047 });
4048 st.slot_for.insert(key, victim as u32);
4049 *st.occupancy.entry(layer).or_insert(0) += 1;
4050 st.last[victim] = clock;
4051 fetched += 1;
4052 }
4053 drop(st);
4054 self.remap(layer, experts.len())
4055 }
4056
4057 fn pin_picks(
4063 &self,
4064 model: &std::sync::Arc<cortiq_core::CmfModel>,
4065 layer: usize,
4066 picks: &[usize],
4067 experts: &[Dsv4Expert],
4068 ) -> Vec<u32> {
4069 let remap = self.ensure_picks(model, layer, picks, experts, picks.len(), 1);
4070 let mut st = self.state.lock().unwrap();
4071 for &expert in picks {
4072 if let Some(&slot) = st.slot_for.get(&(layer, expert)) {
4073 if let Some(owner) = st.owner[slot as usize].as_mut() {
4074 owner.pinned = true;
4075 }
4076 }
4077 }
4078 remap
4079 }
4080}
4081
4082#[cfg(feature = "gpu")]
4083#[derive(Clone)]
4084struct GlobalPack {
4085 pool: std::sync::Arc<GlobalPool>,
4086 layer: usize,
4087 shared_slot: u32,
4088}
4089
4090#[cfg(feature = "gpu")]
4091impl Pack {
4092 fn is_mutated(&self) -> bool {
4094 self.global.is_some() || self.dynslots.lock().unwrap().mutated
4095 }
4096
4097 fn route_complete(&self) -> bool {
4103 if self.global.is_some() {
4104 return false;
4105 }
4106 let need = self
4107 .mask
4108 .as_deref()
4109 .map_or(self.remap.len(), |m| m.iter().filter(|&&x| x != 0).count());
4110 self.globals.len() >= need
4111 }
4112
4113 fn needs_remap(&self) -> bool {
4117 self.global.is_some()
4118 || self.mask.is_some()
4119 || self
4120 .remap
4121 .iter()
4122 .enumerate()
4123 .any(|(i, &slot)| slot != i as u32)
4124 }
4125}
4126
4127#[cfg(feature = "gpu")]
4131struct Pack {
4132 router: Vec<f32>,
4136 to_slot: Vec<usize>,
4138 remap: Vec<u32>,
4140 globals: Vec<usize>,
4142 tensors: Vec<(usize, usize, usize)>,
4143 mask: Option<Vec<u32>>,
4147 dynslots: std::sync::Mutex<PackDyn>,
4156 bias: Option<Vec<f32>>,
4165 global: Option<GlobalPack>,
4169}
4170
4171#[cfg(feature = "gpu")]
4172fn pack_freq_order(li: usize, n: usize) -> Option<Vec<usize>> {
4178 use std::collections::HashMap;
4179 use std::sync::OnceLock;
4180 static FREQ: OnceLock<Option<HashMap<(usize, usize), u64>>> = OnceLock::new();
4181 let map = FREQ
4182 .get_or_init(|| {
4183 let path = std::env::var("CMF_DSV4_PACK_FREQ").ok()?;
4184 let text = match std::fs::read_to_string(&path) {
4185 Ok(t) => t,
4186 Err(e) => {
4187 eprintln!("CMF_DSV4_PACK_FREQ={path} is unreadable ({e}) — ordering by id");
4188 return None;
4189 }
4190 };
4191 let mut m = HashMap::new();
4192 for line in text.lines() {
4193 let mut it = line.split('\t');
4194 if let (Some(l), Some(e), Some(c)) = (it.next(), it.next(), it.next()) {
4195 if let (Ok(l), Ok(e), Ok(c)) =
4196 (l.trim().parse(), e.trim().parse(), c.trim().parse::<u64>())
4197 {
4198 *m.entry((l, e)).or_insert(0) += c;
4199 }
4200 }
4201 }
4202 Some(m)
4203 })
4204 .as_ref()?;
4205 if !(0..n).any(|e| map.contains_key(&(li, e))) {
4206 return None;
4207 }
4208 let mut idx: Vec<usize> = (0..n).collect();
4209 idx.sort_by_key(|&e| {
4210 (
4211 std::cmp::Reverse(map.get(&(li, e)).copied().unwrap_or(0)),
4212 e,
4213 )
4214 });
4215 Some(idx)
4216}
4217
4218#[cfg(feature = "gpu")]
4219fn global_pool_for(
4220 model: &std::sync::Arc<cortiq_core::CmfModel>,
4221 cfg: &Dsv4Cfg,
4222 gu_q2: bool,
4223 dn_q2: bool,
4224) -> Option<std::sync::Arc<GlobalPool>> {
4225 use std::collections::HashMap;
4226 use std::sync::{Arc, Mutex, OnceLock};
4227 if dn_q2
4228 || !crate::gpu_wgpu::dsv4_global_moe_supported()
4229 || std::env::var("CMF_MOE_MASK").is_ok()
4230 {
4231 return None;
4232 }
4233 static POOLS: OnceLock<Mutex<HashMap<u64, Arc<GlobalPool>>>> = OnceLock::new();
4234 let pools = POOLS.get_or_init(|| Mutex::new(HashMap::new()));
4235 if let Some(p) = pools.lock().unwrap().get(&model.uid()).cloned() {
4236 return Some(p);
4237 }
4238 let requested = crate::gpu_wgpu::dsv4_experts_fit(cfg.moe_inter, cfg.dim, gu_q2, false);
4239 let (capacity, segment_slots) =
4240 crate::gpu_wgpu::dsv4_global_moe_create(model, requested, cfg.moe_inter, cfg.dim, gu_q2)?;
4241 let layers = model.header.arch.num_layers.max(1);
4242 let p = Arc::new(GlobalPool {
4243 uid: model.uid(),
4244 capacity,
4245 segment_slots,
4246 floor: (capacity / layers).max(2),
4249 state: Mutex::new(GlobalPoolDyn {
4250 slot_for: HashMap::new(),
4251 owner: vec![None; capacity],
4252 last: vec![0; capacity],
4253 seen: HashMap::new(),
4254 occupancy: HashMap::new(),
4255 clock: 0,
4256 }),
4257 });
4258 pools.lock().unwrap().insert(model.uid(), p.clone());
4259 Some(p)
4260}
4261
4262#[cfg(feature = "gpu")]
4263fn pack_for(l: &Dsv4Layer, cfg: &Dsv4Cfg, li: usize) -> Option<std::sync::Arc<Pack>> {
4264 use std::collections::HashMap;
4265 use std::sync::{Arc, Mutex, OnceLock};
4266 static CACHE: OnceLock<Mutex<HashMap<(u64, usize, usize), Option<Arc<Pack>>>>> =
4267 OnceLock::new();
4268 let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
4269 let model_uid = l
4275 .experts
4276 .first()
4277 .and_then(|e| e.w1.model_arc())
4278 .map_or(0, |m| m.uid());
4279 let first_expert = l
4285 .experts
4286 .first()
4287 .and_then(|e| e.w1.model_idx())
4288 .unwrap_or(usize::MAX);
4289 let key = (model_uid, li, first_expert);
4290 if let Some(v) = cache.lock().unwrap().get(&key) {
4291 return v.clone();
4292 }
4293 if let Ok(v) = std::env::var("CMF_DSV4_PACK_MAX_LI") {
4300 if v.parse::<usize>().is_ok_and(|max| li > max) {
4301 cache.lock().unwrap().insert(key, None);
4302 return None;
4303 }
4304 }
4305 let build = || -> Option<Arc<Pack>> {
4306 let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
4307 let mut globals = Vec::new();
4308 let mut tensors = Vec::new();
4309 let idx3 = |e: &Dsv4Expert| -> Option<(usize, usize, usize)> {
4310 Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
4311 };
4312 let route_mask: Option<Vec<u32>> = if l.tid2eid.is_none() {
4313 l.mask
4314 .as_deref()
4315 .map(|m| m.iter().map(|&open| u32::from(open)).collect())
4316 } else {
4317 None
4318 };
4319 let gu_q2 = l
4324 .experts
4325 .first()
4326 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4327 let dn_q2 = l
4328 .experts
4329 .first()
4330 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4331 if route_mask.is_none() {
4336 if let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) {
4337 if let Some(gp) = global_pool_for(&model, cfg, gu_q2, dn_q2) {
4338 let layer_key = first_expert;
4339 let order = pack_freq_order(li, l.experts.len())
4340 .unwrap_or_else(|| (0..l.experts.len()).collect());
4341 let routed: Vec<_> = order
4342 .into_iter()
4343 .filter_map(|gi| Some((gi, idx3(&l.experts[gi])?)))
4344 .collect();
4345 let shared = idx3(&l.shared)?;
4346 let shared_slot = gp.seed_layer(&model, layer_key, shared, &routed)?;
4347 let remap = gp.remap(layer_key, cfg.n_routed_experts);
4348 let to_slot: Vec<usize> = remap
4349 .iter()
4350 .map(|&s| {
4351 if s == u32::MAX {
4352 usize::MAX
4353 } else {
4354 s as usize
4355 }
4356 })
4357 .collect();
4358 let globals: Vec<usize> = remap
4359 .iter()
4360 .enumerate()
4361 .filter_map(|(e, &s)| (s != u32::MAX).then_some(e))
4362 .collect();
4363 let (rows, cols) = (l.gate.rows(), l.gate.cols());
4364 let mut router = vec![0.0f32; rows * cols];
4365 for r in 0..rows {
4366 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4367 }
4368 return Some(Arc::new(Pack {
4369 bias: l.gate_bias.clone(),
4370 mask: None,
4371 router,
4372 to_slot,
4373 remap: remap.clone(),
4374 globals,
4375 tensors: Vec::new(),
4378 dynslots: std::sync::Mutex::new(PackDyn {
4379 remap,
4380 owner: Vec::new(),
4381 last: Vec::new(),
4382 clock: 0,
4383 mutated: true,
4384 seen: vec![0; cfg.n_routed_experts],
4385 }),
4386 global: Some(GlobalPack {
4387 pool: gp,
4388 layer: layer_key,
4389 shared_slot,
4390 }),
4391 }));
4392 }
4393 }
4394 }
4395 if let Some(n) = std::env::var("CMF_DSV4_PACK_MAX")
4404 .ok()
4405 .and_then(|v| v.parse::<usize>().ok())
4406 {
4407 let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
4408 let mut globals = Vec::new();
4409 let mut tensors = Vec::new();
4410 for (gi, e) in l.experts.iter().enumerate() {
4411 if route_mask
4412 .as_deref()
4413 .is_some_and(|m| m.get(gi).copied().unwrap_or(1) == 0)
4414 {
4415 continue;
4416 }
4417 if globals.len() >= n {
4418 break;
4419 }
4420 to_slot[gi] = globals.len();
4421 globals.push(gi);
4422 tensors.push(idx3(e)?);
4423 }
4424 tensors.push(idx3(&l.shared)?);
4425 let (rows, cols) = (l.gate.rows(), l.gate.cols());
4426 let mut router = vec![0.0f32; rows * cols];
4427 for r in 0..rows {
4428 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4429 }
4430 let remap: Vec<u32> = to_slot
4431 .iter()
4432 .map(|&sl| {
4433 if sl == usize::MAX {
4434 u32::MAX
4435 } else {
4436 sl as u32
4437 }
4438 })
4439 .collect();
4440 return Some(Arc::new(Pack {
4441 bias: l.gate_bias.clone(),
4442 mask: route_mask.clone(),
4443 router,
4444 to_slot,
4445 dynslots: std::sync::Mutex::new(PackDyn {
4446 remap: remap.clone(),
4447 owner: globals.iter().map(|&g| g as u32).collect(),
4448 last: vec![0; globals.len()],
4449 clock: 0,
4450 mutated: false,
4451 seen: vec![0; cfg.n_routed_experts],
4452 }),
4453 remap,
4454 globals,
4455 tensors,
4456 global: None,
4457 }));
4458 }
4459 let dn_q2_fit = l
4460 .experts
4461 .first()
4462 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4463 let room = crate::gpu_wgpu::dsv4_experts_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2_fit)
4464 .saturating_sub(1);
4465 let remaining_layers = l
4476 .experts
4477 .first()
4478 .and_then(|e| e.w1.model_arc())
4479 .map(|m| m.header.arch.num_layers.saturating_sub(li).max(1))
4480 .unwrap_or(1);
4481 let auto_cap = if l.mask.is_some() {
4482 room
4487 } else if l.tid2eid.is_some() && room >= cfg.n_routed_experts {
4488 cfg.n_routed_experts
4489 } else {
4490 room.div_ceil(remaining_layers).max(1)
4491 };
4492 let room = match std::env::var("CMF_DSV4_PACK_LAYER_CAP")
4493 .ok()
4494 .and_then(|v| v.parse::<usize>().ok())
4495 {
4496 Some(0) => room,
4497 Some(cap) => room.min(cap),
4498 None => room.min(auto_cap),
4499 };
4500 let order =
4509 pack_freq_order(li, l.experts.len()).unwrap_or_else(|| (0..l.experts.len()).collect());
4510 for gi in order {
4511 let e = &l.experts[gi];
4512 if l.mask
4513 .as_deref()
4514 .is_some_and(|m| !m.get(gi).copied().unwrap_or(true))
4515 {
4516 continue;
4517 }
4518 if globals.len() >= room {
4519 break;
4520 }
4521 to_slot[gi] = globals.len();
4522 globals.push(gi);
4523 match idx3(e) {
4524 Some(t) => tensors.push(t),
4525 None => {
4526 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
4527 eprintln!("layer {li}: expert {gi} has no indices in the catalog");
4528 }
4529 return None;
4530 }
4531 }
4532 }
4533 if globals.is_empty() {
4534 if room == 0 {
4540 static SAID_ZERO: std::sync::atomic::AtomicBool =
4541 std::sync::atomic::AtomicBool::new(false);
4542 if !SAID_ZERO.swap(true, std::sync::atomic::Ordering::Relaxed) {
4543 tracing::warn!(
4544 "from layer {li} on, the VRAM budget has no room for even one \
4545 expert — the remaining weights stay mmap-backed and are read on demand"
4546 );
4547 }
4548 } else {
4549 tracing::warn!("layer {li}: the mask left no expert");
4550 }
4551 return None;
4552 }
4553 tensors.push(idx3(&l.shared)?); let (rows, cols) = (l.gate.rows(), l.gate.cols());
4555 let mut router = vec![0.0f32; rows * cols];
4556 for r in 0..rows {
4557 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4558 }
4559 let remap: Vec<u32> = to_slot
4560 .iter()
4561 .map(|&sl| {
4562 if sl == usize::MAX {
4563 u32::MAX
4564 } else {
4565 sl as u32
4566 }
4567 })
4568 .collect();
4569 Some(Arc::new(Pack {
4570 bias: l.gate_bias.clone(),
4571 mask: route_mask,
4572 router,
4573 to_slot,
4574 dynslots: std::sync::Mutex::new(PackDyn {
4575 remap: remap.clone(),
4576 owner: globals.iter().map(|&g| g as u32).collect(),
4577 last: vec![0; globals.len()],
4578 clock: 0,
4579 mutated: false,
4580 seen: vec![0; cfg.n_routed_experts],
4581 }),
4582 remap,
4583 globals,
4584 tensors,
4585 global: None,
4586 }))
4587 };
4588 let v = build();
4589 cache.lock().unwrap().insert(key, v.clone());
4590 v
4591}
4592
4593#[cfg(feature = "gpu")]
4601fn moe_frame(
4602 hidden: &[f32],
4603 l: &Dsv4Layer,
4604 cfg: &Dsv4Cfg,
4605 li: usize,
4606 logits: &[f32],
4607 forced: Option<&[usize]>,
4608 pool: Option<&crate::pool::Pool>,
4609 hc_cur: Option<&crate::gpu_wgpu::Dsv4HcTail>,
4612 hc_next: Option<(&crate::gpu_wgpu::Dsv4HcTail, &[f32])>,
4613 out: &mut [f32],
4614) -> Option<(Vec<f32>, usize)> {
4615 macro_rules! no {
4616 ($($t:tt)*) => {{
4617 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
4618 eprintln!("MoE frame declined: {}", format_args!($($t)*));
4619 }
4620 return None;
4621 }};
4622 }
4623 let Some(pk) = pack_for(l, cfg, li) else {
4624 no!("layer {li}: the expert packing was not built");
4625 };
4626 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
4629 no!("layer {li}: the experts are not mapped from the file");
4630 };
4631 let subset = !pk.route_complete();
4632 let needs_remap = pk.needs_remap();
4633 fn fetch_quota() -> usize {
4640 static Q: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
4641 *Q.get_or_init(|| {
4642 std::env::var("CMF_DSV4_FETCH_MAX")
4643 .ok()
4644 .and_then(|v| v.parse().ok())
4645 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().0)
4646 })
4647 }
4648 fn fetch_min_seen() -> u16 {
4649 static M: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
4650 *M.get_or_init(|| {
4651 std::env::var("CMF_DSV4_FETCH_MIN_SEEN")
4652 .ok()
4653 .and_then(|v| v.parse().ok())
4654 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().1)
4655 })
4656 }
4657 let mut dynv = pk.dynslots.lock().unwrap();
4658 let mut pidx = Vec::new();
4663 let mut pwt = Vec::new();
4664 let mut predicted_logits = Vec::new();
4670 let prediction_logits: &[f32] = if subset && logits.is_empty() && !hidden.is_empty() {
4671 predicted_logits.resize(cfg.n_routed_experts, 0.0);
4672 l.gate.matvec(hidden, &mut predicted_logits, pool);
4673 &predicted_logits
4674 } else {
4675 logits
4676 };
4677 if subset && !prediction_logits.is_empty() {
4678 route(
4679 prediction_logits,
4680 l.gate_bias.as_deref(),
4681 cfg.top_k,
4682 cfg.route_scale,
4683 forced,
4684 l.mask.as_deref(),
4685 &mut pidx,
4686 &mut pwt,
4687 );
4688 }
4689 let global_remap = pk.global.as_ref().map(|gl| {
4690 gl.pool.ensure_picks(
4691 &model, gl.layer, &pidx, &l.experts,
4692 cfg.top_k, 1,
4698 )
4699 });
4700 if subset && fetch_quota() > 0 && !pidx.is_empty() && !dynv.owner.is_empty() {
4701 dynv.clock += 1;
4702 let clock = dynv.clock;
4703 if clock % 64 == 0 {
4704 for v in dynv.seen.iter_mut() {
4705 *v >>= 1;
4706 }
4707 }
4708 for &pick in &pidx {
4709 dynv.seen[pick] = dynv.seen[pick].saturating_add(1);
4710 let sl = dynv.remap[pick];
4711 if sl != u32::MAX {
4712 dynv.last[sl as usize] = clock;
4713 }
4714 }
4715 let mut fetched = 0usize;
4716 for &pick in &pidx {
4717 if fetched >= fetch_quota() {
4718 break;
4719 }
4720 if dynv.remap[pick] != u32::MAX {
4721 continue;
4722 }
4723 if dynv.seen[pick] < fetch_min_seen() {
4724 continue; }
4726 let victim = (0..dynv.owner.len())
4728 .filter(|&sl| dynv.last[sl] != clock)
4729 .min_by_key(|&sl| dynv.last[sl]);
4730 let Some(victim) = victim else { break };
4731 let Some(exp) = l.experts.get(pick) else {
4732 continue;
4733 };
4734 let t3 = (|| {
4735 Some((
4736 exp.w1.model_idx()?,
4737 exp.w3.model_idx()?,
4738 exp.w2.model_idx()?,
4739 ))
4740 })();
4741 let Some(t3) = t3 else { continue };
4742 let gu_q2 = exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
4743 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
4744 if !crate::gpu_wgpu::dsv4_slot_fill(
4745 &model,
4746 pack_first,
4747 victim,
4748 pick,
4749 t3,
4750 cfg.moe_inter,
4751 cfg.dim,
4752 gu_q2,
4753 ) {
4754 break;
4755 }
4756 let old = dynv.owner[victim] as usize;
4757 if old < dynv.remap.len() {
4758 dynv.remap[old] = u32::MAX;
4759 }
4760 dynv.remap[pick] = victim as u32;
4761 dynv.owner[victim] = pick as u32;
4762 dynv.last[victim] = clock;
4763 dynv.mutated = true;
4764 fetched += 1;
4765 }
4766 }
4767 let fpack: Option<Vec<usize>> = match forced {
4771 Some(f) if needs_remap => Some(f.to_vec()),
4772 Some(f) => {
4773 let v: Vec<usize> = f.iter().map(|&g| pk.to_slot[g]).collect();
4774 if v.contains(&usize::MAX) {
4775 no!("layer {li}: the hash layer names an expert outside the packing");
4776 }
4777 Some(v)
4778 }
4779 None => None,
4780 };
4781 let lg: Vec<f32> = if logits.is_empty() || needs_remap {
4788 logits.to_vec()
4789 } else {
4790 pk.globals.iter().map(|&g| logits[g]).collect()
4791 };
4792 let live_remap = global_remap.as_deref().unwrap_or(dynv.remap.as_slice());
4793 let w = crate::gpu_wgpu::Dsv4MoeW {
4794 router: &pk.router,
4795 experts: &pk.tensors,
4796 logits: &lg,
4797 bias: pk.bias.as_deref(),
4798 mask: pk.mask.as_deref(),
4799 forced: fpack.as_deref(),
4800 remap: needs_remap.then_some(live_remap),
4801 global: pk.global.as_ref().map(|gl| crate::gpu_wgpu::Dsv4GlobalMoe {
4802 pool_uid: gl.pool.uid,
4803 shared_slot: gl.shared_slot,
4804 segment_slots: gl.pool.segment_slots as u32,
4805 }),
4806 has_shared: true,
4807 shared_weight: 1.0,
4808 preweighted: false,
4809 qwen_softmax: false,
4810 };
4811 let g = crate::gpu_wgpu::Dsv4MoeGeom {
4812 hidden: cfg.dim,
4813 inter: cfg.moe_inter,
4814 top_k: cfg.top_k,
4815 route_scale: cfg.route_scale,
4816 swiglu_limit: cfg.swiglu_limit,
4817 gu_q2: l
4818 .experts
4819 .first()
4820 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
4821 bf16: false,
4822 };
4823 let mut cold = Vec::new();
4824 let mut cold_x = Vec::new();
4825 let overlap: Vec<usize> = if !hidden.is_empty() {
4834 pidx.iter()
4835 .copied()
4836 .filter(|&pick| live_remap.get(pick).copied().unwrap_or(u32::MAX) == u32::MAX)
4837 .collect()
4838 } else {
4839 Vec::new()
4840 };
4841 let mut early: std::collections::HashMap<usize, Vec<f32>> = std::collections::HashMap::new();
4842 let frame_ok = std::thread::scope(|sc| {
4843 let handles: Vec<_> = overlap
4844 .iter()
4845 .filter_map(|&gi| l.experts.get(gi).map(|exp| (gi, exp)))
4846 .map(|(gi, exp)| {
4847 sc.spawn(move || {
4848 let mut a = vec![0.0f32; cfg.dim];
4849 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, 1.0, None, &mut a));
4850 (gi, a)
4851 })
4852 })
4853 .collect();
4854 let ok = crate::gpu_wgpu::dsv4_moe_frame(
4855 &model,
4856 &w,
4857 g,
4858 hidden,
4859 &mut cold,
4860 &mut cold_x,
4861 hc_cur,
4862 hc_next,
4863 out,
4864 );
4865 for h in handles {
4866 if let Ok((gi, a)) = h.join() {
4867 early.insert(gi, a);
4868 }
4869 }
4870 ok
4871 });
4872 if !frame_ok {
4873 return None;
4874 }
4875 if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
4878 let csum: f32 = cold.iter().map(|c| c.1).sum();
4879 eprintln!(
4880 "[cold] layer {li}: {} of {} returned | cold sum {csum:.4} | \
4881 route_scale {:.4} | {:?}",
4882 cold.len(),
4883 cfg.top_k,
4884 cfg.route_scale,
4885 &cold[..cold.len().min(3)]
4886 );
4887 }
4888 let mut acc = vec![0.0f32; cfg.dim];
4889 let mut cold_sum = vec![0.0f32; cfg.dim];
4890 let cold_input = if hidden.is_empty() {
4891 cold_x.as_slice()
4892 } else {
4893 hidden
4894 };
4895 if !early.is_empty() {
4908 for &(gi, wt) in &cold {
4913 if let Some(a) = early.get(&gi) {
4914 for ((o, sum), v) in out.iter_mut().zip(&mut cold_sum).zip(a.iter()) {
4915 *o += v * wt;
4916 *sum += v * wt;
4917 }
4918 continue;
4919 }
4920 let Some(exp) = l.experts.get(gi) else {
4921 continue;
4922 };
4923 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
4924 for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
4925 *o += a;
4926 *sum += a;
4927 }
4928 }
4929 return Some((cold_sum, cold.len()));
4930 }
4931 match pool {
4932 Some(p) if cold.len() > 1 => {
4933 let results: Vec<std::sync::Mutex<Vec<f32>>> = cold
4934 .iter()
4935 .map(|_| std::sync::Mutex::new(Vec::new()))
4936 .collect();
4937 let (cold_ref, results_ref) = (&cold, &results);
4938 p.run_rows(cold.len(), &move |cs, ce| {
4939 for i in cs..ce {
4940 let (gi, wt) = cold_ref[i];
4941 let Some(exp) = l.experts.get(gi) else {
4942 continue;
4943 };
4944 let mut a = vec![0.0f32; cfg.dim];
4945 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, None, &mut a));
4946 *results_ref[i].lock().unwrap() = a;
4947 }
4948 });
4949 for r in &results {
4952 let a = r.lock().unwrap();
4953 if a.is_empty() {
4954 continue;
4955 }
4956 for ((o, sum), v) in out.iter_mut().zip(&mut cold_sum).zip(a.iter()) {
4957 *o += v;
4958 *sum += v;
4959 }
4960 }
4961 }
4962 _ => {
4963 for &(gi, wt) in &cold {
4964 let Some(exp) = l.experts.get(gi) else {
4965 continue;
4966 };
4967 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
4968 for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
4969 *o += a;
4970 *sum += a;
4971 }
4972 }
4973 }
4974 }
4975 Some((cold_sum, cold.len()))
4976}
4977
4978#[cfg(feature = "gpu")]
4984fn last_grew(now: u64) -> u64 {
4985 use std::sync::atomic::{AtomicU64, Ordering};
4986 static SEEN: AtomicU64 = AtomicU64::new(0);
4987 let was = SEEN.load(Ordering::Relaxed);
4988 if was != now {
4989 SEEN.store(now, Ordering::Relaxed);
4990 compressed_map().lock().unwrap().clear();
4991 return u64::MAX; }
4993 now
4994}
4995
4996#[cfg(feature = "gpu")]
4997fn compressed_map() -> &'static std::sync::Mutex<std::collections::HashMap<(u64, usize), usize>> {
4998 use std::collections::HashMap;
4999 use std::sync::{Mutex, OnceLock};
5000 static W: OnceLock<Mutex<HashMap<(u64, usize), usize>>> = OnceLock::new();
5001 W.get_or_init(|| Mutex::new(HashMap::new()))
5002}
5003
5004#[cfg(feature = "gpu")]
5005fn compressed_written(kv_id: u64, li: usize) -> usize {
5006 compressed_map()
5007 .lock()
5008 .unwrap()
5009 .get(&(kv_id, li))
5010 .copied()
5011 .unwrap_or(0)
5012}
5013
5014#[cfg(feature = "gpu")]
5017fn note_compressed(kv_id: u64, li: usize, n: usize) {
5018 compressed_map().lock().unwrap().insert((kv_id, li), n);
5019}
5020
5021#[cfg(feature = "gpu")]
5022fn gpu_moe2_enabled() -> bool {
5023 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5024 *ON.get_or_init(|| {
5025 std::env::var("CMF_DSV4_GPU_MOE2")
5026 .map(|v| v != "0")
5027 .unwrap_or(true)
5028 && crate::gpu::backend_available()
5029 })
5030}
5031
5032pub fn moe_step(
5033 hidden: &[f32],
5034 l: &Dsv4Layer,
5035 cfg: &Dsv4Cfg,
5036 token_id: u32,
5037 li: usize,
5039 pool: Option<&crate::pool::Pool>,
5040 out: &mut [f32],
5041) {
5042 let _t0 = prof::on().then(std::time::Instant::now);
5043 let _guard = scopeguard_moe(_t0, li);
5044 let mut logits = vec![0.0f32; cfg.n_routed_experts];
5045 l.gate.matvec(hidden, &mut logits, pool);
5046 let (mut idx, mut w) = (Vec::new(), Vec::new());
5047 route(
5048 &logits,
5049 l.gate_bias.as_deref(),
5050 cfg.top_k,
5051 cfg.route_scale,
5052 l.tid2eid
5053 .as_ref()
5054 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id))
5055 .as_deref(),
5056 l.mask.as_deref(),
5057 &mut idx,
5058 &mut w,
5059 );
5060 if route_stats_on() {
5061 let routed: Vec<(usize, f32)> = idx.iter().copied().zip(w.iter().copied()).collect();
5062 record_route(li, 0, cfg.n_routed_experts, &routed);
5063 }
5064 crate::pipeline::moe_trace_at(li as i32, &idx);
5070 #[cfg(feature = "gpu")]
5074 if gpu_moe2_enabled() && crate::gpu::enabled_here() {
5075 let forced = l
5076 .tid2eid
5077 .as_ref()
5078 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
5079 if moe_frame(
5080 hidden,
5081 l,
5082 cfg,
5083 li,
5084 &logits,
5085 forced.as_deref(),
5086 pool,
5087 None,
5088 None,
5089 out,
5090 )
5091 .is_some()
5092 {
5093 if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
5099 let mut want = vec![0.0f32; out.len()];
5100 let mut acc = vec![0.0f32; cfg.dim];
5101 crate::gpu::cpu_scope(|| {
5106 for (e, &ei) in idx.iter().enumerate() {
5107 let Some(exp) = l.experts.get(ei) else {
5108 continue;
5109 };
5110 run_expert(
5111 hidden,
5112 exp,
5113 cfg,
5114 w.get(e).copied().unwrap_or(0.0),
5115 pool,
5116 &mut acc,
5117 );
5118 for (o, a) in want.iter_mut().zip(&acc) {
5119 *o += a;
5120 }
5121 }
5122 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
5123 for (o, a) in want.iter_mut().zip(&acc) {
5124 *o += a;
5125 }
5126 });
5127 let num: f32 = want
5128 .iter()
5129 .zip(out.iter())
5130 .map(|(a, b)| (a - b) * (a - b))
5131 .sum();
5132 let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
5133 let rel = (num / den).sqrt();
5134 if rel > 1e-3 {
5135 let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
5136 eprintln!(
5137 "[MoE frame] layer {li}: divergence {rel:.3e} | selected {} | \
5138 packed {packed} of {} | hash={} | offset={}",
5139 idx.len(),
5140 cfg.n_routed_experts,
5141 l.tid2eid.is_some(),
5142 l.gate_bias.is_some()
5143 );
5144 }
5145 }
5146 return;
5147 }
5148 }
5149 PICK_TALLY.with(|t| {
5155 if let Some(v) = t.borrow_mut().as_mut() {
5156 v.push((li, idx.to_vec()));
5157 }
5158 });
5159 if dump_path().is_some() {
5160 PICKED.with(|p| {
5161 let mut p = p.borrow_mut();
5162 if p.len() <= li {
5163 p.resize(li + 1, Vec::new());
5164 }
5165 p[li] = idx.clone();
5166 });
5167 }
5168 fn gpu_moe_on() -> bool {
5187 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5188 *ON.get_or_init(|| std::env::var("CMF_DSV4_GPU_MOE").is_ok_and(|v| v != "0"))
5189 }
5190 if gpu_moe_on() && crate::gpu::enabled_here() {
5191 let mut jobs = Vec::with_capacity(idx.len() + 1);
5192 let mut model_ref = None;
5193 let mut ok = true;
5194 for (e, &ei) in idx.iter().enumerate() {
5195 let Some(exp) = l.experts.get(ei) else {
5196 continue;
5197 };
5198 ok &= crate::pipeline::moe_push_job_parts(
5199 &exp.w1,
5200 &exp.w3,
5201 &exp.w2,
5202 hidden,
5203 w.get(e).copied().unwrap_or(0.0),
5204 cfg.swiglu_limit,
5205 &mut jobs,
5206 &mut model_ref,
5207 )
5208 .is_some();
5209 }
5210 ok &= crate::pipeline::moe_push_job_parts(
5211 &l.shared.w1,
5212 &l.shared.w3,
5213 &l.shared.w2,
5214 hidden,
5215 1.0,
5216 cfg.swiglu_limit,
5217 &mut jobs,
5218 &mut model_ref,
5219 )
5220 .is_some();
5221 if ok {
5222 if let Some(m) = model_ref.as_ref() {
5223 if crate::gpu::moe_block(m, &jobs, out) {
5224 if std::env::var("CMF_DSV4_GPU_CHECK").is_ok() {
5229 let mut want = vec![0.0f32; out.len()];
5230 let mut acc = vec![0.0f32; cfg.dim];
5231 for (e, &ei) in idx.iter().enumerate() {
5232 let Some(exp) = l.experts.get(ei) else {
5233 continue;
5234 };
5235 run_expert(
5236 hidden,
5237 exp,
5238 cfg,
5239 w.get(e).copied().unwrap_or(0.0),
5240 pool,
5241 &mut acc,
5242 );
5243 for (o, a) in want.iter_mut().zip(&acc) {
5244 *o += a;
5245 }
5246 }
5247 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
5248 for (o, a) in want.iter_mut().zip(&acc) {
5249 *o += a;
5250 }
5251 let num: f32 = want
5252 .iter()
5253 .zip(out.iter())
5254 .map(|(a, b)| (a - b) * (a - b))
5255 .sum();
5256 let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
5257 eprintln!(
5258 "[dsv4-gpu] layer {li}: divergence {:.3e} | |CPU|={:.5} |GPU|={:.5} | experts {}",
5259 (num / den).sqrt(),
5260 den.sqrt(),
5261 out.iter().map(|x| x * x).sum::<f32>().sqrt(),
5262 jobs.len()
5263 );
5264 }
5265 return;
5266 }
5267 }
5268 }
5269 }
5270 out.fill(0.0);
5271 let jobs: Vec<(Option<usize>, f32)> = idx
5278 .iter()
5279 .enumerate()
5280 .map(|(e, &ei)| (Some(ei), w.get(e).copied().unwrap_or(0.0)))
5281 .chain(std::iter::once((None, 1.0)))
5282 .collect();
5283 match pool {
5284 Some(p) if jobs.len() > 1 => {
5285 let results: Vec<std::sync::Mutex<Vec<f32>>> = jobs
5286 .iter()
5287 .map(|_| std::sync::Mutex::new(Vec::new()))
5288 .collect();
5289 let (jobs_ref, results_ref) = (&jobs, &results);
5290 p.run_rows(jobs.len(), &move |cs, ce| {
5291 for i in cs..ce {
5292 let (ei, wt) = jobs_ref[i];
5293 let exp = match ei {
5294 Some(ei) => match l.experts.get(ei) {
5295 Some(x) => x,
5296 None => continue,
5297 },
5298 None => &l.shared,
5299 };
5300 let mut a = vec![0.0f32; cfg.dim];
5301 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, wt, None, &mut a));
5302 *results_ref[i].lock().unwrap() = a;
5303 }
5304 });
5305 for r in &results {
5306 let a = r.lock().unwrap();
5307 for (o, v) in out.iter_mut().zip(a.iter()) {
5308 *o += v;
5309 }
5310 }
5311 }
5312 _ => {
5313 let mut acc = vec![0.0f32; cfg.dim];
5314 for &(ei, wt) in &jobs {
5315 let exp = match ei {
5316 Some(ei) => match l.experts.get(ei) {
5317 Some(x) => x,
5318 None => continue,
5319 },
5320 None => &l.shared,
5321 };
5322 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, wt, pool, &mut acc));
5323 for (o, a) in out.iter_mut().zip(&acc) {
5324 *o += a;
5325 }
5326 }
5327 }
5328 }
5329}
5330
5331fn run_expert(
5334 x: &[f32],
5335 e: &Dsv4Expert,
5336 cfg: &Dsv4Cfg,
5337 weight: f32,
5338 pool: Option<&crate::pool::Pool>,
5339 out: &mut [f32],
5340) {
5341 expert_swiglu(
5342 x,
5343 &|src, dst| e.w1.matvec(src, dst, pool),
5344 &|src, dst| e.w3.matvec(src, dst, pool),
5345 &|src, dst| e.w2.matvec(src, dst, pool),
5346 cfg.moe_inter,
5347 weight,
5348 cfg.swiglu_limit,
5349 out,
5350 );
5351}
5352
5353fn moe_step_block(
5358 xs: &[f32],
5359 b: usize,
5360 l: &Dsv4Layer,
5361 cfg: &Dsv4Cfg,
5362 token_ids: &[u32],
5363 tally_layer: usize,
5364 pool: Option<&crate::pool::Pool>,
5365 out: &mut [f32],
5366) {
5367 let (dim, inter) = (cfg.dim, cfg.moe_inter);
5368 debug_assert_eq!(xs.len(), b * dim);
5369 debug_assert_eq!(out.len(), b * dim);
5370 out.fill(0.0);
5371
5372 let mut logits = vec![0.0f32; b * cfg.n_routed_experts];
5373 l.gate.matmat(xs, b, &mut logits, pool);
5374 let mut picks: Vec<Vec<usize>> = Vec::with_capacity(b);
5375 let mut weights: Vec<Vec<f32>> = Vec::with_capacity(b);
5376 for bi in 0..b {
5377 let mut idx = Vec::new();
5378 let mut wt = Vec::new();
5379 let forced = l.tid2eid.as_ref().map(|tbl| {
5380 hash_route(
5381 tbl,
5382 cfg.vocab,
5383 cfg.top_k,
5384 token_ids.get(bi).copied().unwrap_or(0),
5385 )
5386 });
5387 route(
5388 &logits[bi * cfg.n_routed_experts..(bi + 1) * cfg.n_routed_experts],
5389 l.gate_bias.as_deref(),
5390 cfg.top_k,
5391 cfg.route_scale,
5392 forced.as_deref(),
5393 l.mask.as_deref(),
5394 &mut idx,
5395 &mut wt,
5396 );
5397 PICK_TALLY.with(|t| {
5398 if let Some(v) = t.borrow_mut().as_mut() {
5399 v.push((tally_layer, idx.clone()));
5400 }
5401 });
5402 picks.push(idx);
5403 weights.push(wt);
5404 }
5405
5406 let mut routed = vec![0.0f32; b * cfg.top_k * dim];
5409 let mut active: Vec<(usize, Vec<(usize, usize, f32)>)> = Vec::new();
5412 for ei in 0..l.experts.len() {
5413 let mut jobs = Vec::new();
5414 for bi in 0..b {
5415 for (slot, &picked) in picks[bi].iter().enumerate() {
5416 if picked == ei {
5417 jobs.push((bi, slot, weights[bi][slot]));
5418 }
5419 }
5420 }
5421 if !jobs.is_empty() {
5422 active.push((ei, jobs));
5423 }
5424 }
5425 let expert_fwd =
5428 |ei: usize, jobs: &[(usize, usize, f32)], inner: Option<&crate::pool::Pool>| -> Vec<f32> {
5429 let e = &l.experts[ei];
5430 let n = jobs.len();
5431 let mut xj = vec![0.0f32; n * dim];
5432 for (j, &(bi, _, _)) in jobs.iter().enumerate() {
5433 xj[j * dim..(j + 1) * dim].copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
5434 }
5435 let mut gate = vec![0.0f32; n * inter];
5436 let mut up = vec![0.0f32; n * inter];
5437 e.w1.matmat(&xj, n, &mut gate, inner);
5438 e.w3.matmat(&xj, n, &mut up, inner);
5439 for (j, &(_, _, wt)) in jobs.iter().enumerate() {
5440 let (gj, uj) = (
5441 &mut gate[j * inter..(j + 1) * inter],
5442 &mut up[j * inter..(j + 1) * inter],
5443 );
5444 if cfg.swiglu_limit > 0.0 {
5445 for u in uj.iter_mut() {
5446 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5447 }
5448 for g in gj.iter_mut() {
5449 *g = g.min(cfg.swiglu_limit);
5450 }
5451 }
5452 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5453 *g = (*g / (1.0 + (-*g).exp())) * u * wt;
5454 }
5455 }
5456 let mut down = vec![0.0f32; n * dim];
5457 e.w2.matmat(&gate, n, &mut down, inner);
5458 down
5459 };
5460 match pool {
5470 Some(p) if active.len() > 1 => {
5471 let results: Vec<std::sync::Mutex<Vec<f32>>> = active
5472 .iter()
5473 .map(|_| std::sync::Mutex::new(Vec::new()))
5474 .collect();
5475 let active_ref = &active;
5476 let results_ref = &results;
5477 let fwd = &expert_fwd;
5478 p.run_rows(active_ref.len(), &move |s, e| {
5479 for i in s..e {
5480 let (ei, jobs) = &active_ref[i];
5481 let d = crate::gpu::cpu_scope(|| fwd(*ei, jobs, None));
5482 *results_ref[i].lock().unwrap() = d;
5483 }
5484 });
5485 for (i, (_, jobs)) in active.iter().enumerate() {
5486 let down = results[i].lock().unwrap();
5487 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5488 routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
5489 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5490 }
5491 }
5492 }
5493 _ => {
5494 for (ei, jobs) in &active {
5495 let down = expert_fwd(*ei, jobs, pool);
5496 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5497 routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
5498 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5499 }
5500 }
5501 }
5502 }
5503
5504 let mut sg = vec![0.0f32; b * inter];
5507 let mut su = vec![0.0f32; b * inter];
5508 l.shared.w1.matmat(xs, b, &mut sg, pool);
5509 l.shared.w3.matmat(xs, b, &mut su, pool);
5510 for bi in 0..b {
5511 let (gj, uj) = (
5512 &mut sg[bi * inter..(bi + 1) * inter],
5513 &mut su[bi * inter..(bi + 1) * inter],
5514 );
5515 if cfg.swiglu_limit > 0.0 {
5516 for u in uj.iter_mut() {
5517 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5518 }
5519 for g in gj.iter_mut() {
5520 *g = g.min(cfg.swiglu_limit);
5521 }
5522 }
5523 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5524 *g = (*g / (1.0 + (-*g).exp())) * u;
5525 }
5526 }
5527 let mut shared = vec![0.0f32; b * dim];
5528 l.shared.w2.matmat(&sg, b, &mut shared, pool);
5529
5530 for bi in 0..b {
5531 let dst = &mut out[bi * dim..(bi + 1) * dim];
5532 for slot in 0..picks[bi].len() {
5533 let src = &routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim];
5534 for (o, &v) in dst.iter_mut().zip(src) {
5535 *o += v;
5536 }
5537 }
5538 for (o, &v) in dst.iter_mut().zip(&shared[bi * dim..(bi + 1) * dim]) {
5539 *o += v;
5540 }
5541 }
5542}
5543
5544fn cold_step_block(
5549 xs: &[f32],
5550 b: usize,
5551 l: &Dsv4Layer,
5552 cfg: &Dsv4Cfg,
5553 cold: &[Vec<(usize, f32)>],
5554 pool: Option<&crate::pool::Pool>,
5555 out: &mut [f32],
5556) {
5557 let (dim, inter) = (cfg.dim, cfg.moe_inter);
5558 debug_assert_eq!(xs.len(), b * dim);
5559 debug_assert_eq!(cold.len(), b);
5560 debug_assert_eq!(out.len(), b * dim);
5561 out.fill(0.0);
5562 let slots = cold.iter().map(Vec::len).max().unwrap_or(0);
5563 if slots == 0 {
5564 return;
5565 }
5566 let grouped = std::env::var("CMF_DSV4_COLD_MATMAT")
5572 .map(|v| v != "0")
5573 .unwrap_or(true);
5574 if !grouped {
5575 let jobs: Vec<(usize, usize, usize, f32)> = cold
5576 .iter()
5577 .enumerate()
5578 .flat_map(|(bi, row)| {
5579 row.iter()
5580 .enumerate()
5581 .map(move |(slot, &(ei, wt))| (bi, slot, ei, wt))
5582 })
5583 .collect();
5584 let results: Vec<std::sync::Mutex<Vec<f32>>> = jobs
5585 .iter()
5586 .map(|_| std::sync::Mutex::new(Vec::new()))
5587 .collect();
5588 match pool {
5589 Some(p) if jobs.len() > 1 => {
5590 let (jobs_ref, results_ref) = (&jobs, &results);
5591 p.run_rows(jobs.len(), &move |s, e| {
5592 for i in s..e {
5593 let (_, _, ei, wt) = jobs_ref[i];
5594 let Some(exp) = l.experts.get(ei) else {
5595 continue;
5596 };
5597 let bi = jobs_ref[i].0;
5598 let mut a = vec![0.0f32; dim];
5599 crate::gpu::cpu_scope(|| {
5600 run_expert(&xs[bi * dim..(bi + 1) * dim], exp, cfg, wt, None, &mut a)
5601 });
5602 *results_ref[i].lock().unwrap() = a;
5603 }
5604 });
5605 }
5606 _ => {
5607 for (i, &(bi, _, ei, wt)) in jobs.iter().enumerate() {
5608 let Some(exp) = l.experts.get(ei) else {
5609 continue;
5610 };
5611 let mut a = vec![0.0f32; dim];
5612 crate::gpu::cpu_scope(|| {
5613 run_expert(&xs[bi * dim..(bi + 1) * dim], exp, cfg, wt, pool, &mut a)
5614 });
5615 *results[i].lock().unwrap() = a;
5616 }
5617 }
5618 }
5619 for (i, &(bi, _, _, _)) in jobs.iter().enumerate() {
5622 let a = results[i].lock().unwrap();
5623 for (o, &v) in out[bi * dim..(bi + 1) * dim].iter_mut().zip(a.iter()) {
5624 *o += v;
5625 }
5626 }
5627 return;
5628 }
5629 let mut active: Vec<(usize, Vec<(usize, usize, f32)>)> = Vec::new();
5630 for ei in 0..l.experts.len() {
5631 let mut jobs = Vec::new();
5632 for bi in 0..b {
5633 for (slot, &(picked, wt)) in cold[bi].iter().enumerate() {
5634 if picked == ei {
5635 jobs.push((bi, slot, wt));
5636 }
5637 }
5638 }
5639 if !jobs.is_empty() {
5640 active.push((ei, jobs));
5641 }
5642 }
5643 let expert_fwd =
5644 |ei: usize, jobs: &[(usize, usize, f32)], inner: Option<&crate::pool::Pool>| -> Vec<f32> {
5645 let e = &l.experts[ei];
5646 let n = jobs.len();
5647 let mut xj = vec![0.0f32; n * dim];
5648 for (j, &(bi, _, _)) in jobs.iter().enumerate() {
5649 xj[j * dim..(j + 1) * dim].copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
5650 }
5651 let mut gate = vec![0.0f32; n * inter];
5652 let mut up = vec![0.0f32; n * inter];
5653 e.w1.matmat(&xj, n, &mut gate, inner);
5654 e.w3.matmat(&xj, n, &mut up, inner);
5655 for (j, &(_, _, wt)) in jobs.iter().enumerate() {
5656 let (gj, uj) = (
5657 &mut gate[j * inter..(j + 1) * inter],
5658 &mut up[j * inter..(j + 1) * inter],
5659 );
5660 if cfg.swiglu_limit > 0.0 {
5661 for u in uj.iter_mut() {
5662 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5663 }
5664 for g in gj.iter_mut() {
5665 *g = g.min(cfg.swiglu_limit);
5666 }
5667 }
5668 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5669 *g = (*g / (1.0 + (-*g).exp())) * u * wt;
5670 }
5671 }
5672 let mut down = vec![0.0f32; n * dim];
5673 e.w2.matmat(&gate, n, &mut down, inner);
5674 down
5675 };
5676 let mut routed = vec![0.0f32; b * slots * dim];
5677 match pool {
5678 Some(p) if active.len() > 1 => {
5679 let results: Vec<std::sync::Mutex<Vec<f32>>> = active
5680 .iter()
5681 .map(|_| std::sync::Mutex::new(Vec::new()))
5682 .collect();
5683 let active_ref = &active;
5684 let results_ref = &results;
5685 let fwd = &expert_fwd;
5686 p.run_rows(active.len(), &move |s, e| {
5687 for i in s..e {
5688 let (ei, jobs) = &active_ref[i];
5689 let d = crate::gpu::cpu_scope(|| fwd(*ei, jobs, None));
5690 *results_ref[i].lock().unwrap() = d;
5691 }
5692 });
5693 for (i, (_, jobs)) in active.iter().enumerate() {
5694 let down = results[i].lock().unwrap();
5695 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5696 routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim]
5697 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5698 }
5699 }
5700 }
5701 _ => {
5702 for (ei, jobs) in &active {
5703 let down = expert_fwd(*ei, jobs, pool);
5704 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5705 routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim]
5706 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5707 }
5708 }
5709 }
5710 }
5711 for bi in 0..b {
5712 let dst = &mut out[bi * dim..(bi + 1) * dim];
5713 for slot in 0..cold[bi].len() {
5714 let src = &routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim];
5715 for (o, &v) in dst.iter_mut().zip(src) {
5716 *o += v;
5717 }
5718 }
5719 }
5720}
5721
5722#[cfg(feature = "gpu")]
5727fn refill_route_slots(l: &Dsv4Layer, cfg: &Dsv4Cfg, pk: &Pack, picks: &[usize]) {
5728 if picks.is_empty() || pk.route_complete() {
5729 return;
5730 }
5731 let quota = {
5732 static Q: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
5733 *Q.get_or_init(|| {
5734 std::env::var("CMF_DSV4_FETCH_MAX")
5735 .ok()
5736 .and_then(|v| v.parse().ok())
5737 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().0)
5738 })
5739 };
5740 if quota == 0 {
5741 return;
5742 }
5743 let min_seen = {
5744 static M: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
5745 *M.get_or_init(|| {
5746 std::env::var("CMF_DSV4_FETCH_MIN_SEEN")
5747 .ok()
5748 .and_then(|v| v.parse().ok())
5749 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().1)
5750 })
5751 };
5752 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
5753 return;
5754 };
5755 let mut dynv = pk.dynslots.lock().unwrap();
5756 if dynv.owner.is_empty() {
5757 return;
5758 }
5759 dynv.clock += 1;
5760 let clock = dynv.clock;
5761 if clock % 64 == 0 {
5762 for seen in &mut dynv.seen {
5763 *seen >>= 1;
5764 }
5765 }
5766 for &pick in picks {
5767 if pick >= dynv.seen.len() {
5768 continue;
5769 }
5770 dynv.seen[pick] = dynv.seen[pick].saturating_add(1);
5771 let slot = dynv.remap[pick];
5772 if slot != u32::MAX {
5773 dynv.last[slot as usize] = clock;
5774 }
5775 }
5776 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
5777 let mut fetched = 0usize;
5778 for &pick in picks {
5779 if fetched >= quota || pick >= dynv.remap.len() {
5780 break;
5781 }
5782 if dynv.remap[pick] != u32::MAX || dynv.seen[pick] < min_seen {
5783 continue;
5784 }
5785 let victim = (0..dynv.owner.len())
5786 .filter(|&slot| dynv.last[slot] != clock)
5787 .min_by_key(|&slot| dynv.last[slot]);
5788 let Some(victim) = victim else { break };
5789 let Some(exp) = l.experts.get(pick) else {
5790 continue;
5791 };
5792 let tensors = (|| {
5793 Some((
5794 exp.w1.model_idx()?,
5795 exp.w3.model_idx()?,
5796 exp.w2.model_idx()?,
5797 ))
5798 })();
5799 let Some(tensors) = tensors else { continue };
5800 let gu_q2 = exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
5801 if !crate::gpu_wgpu::dsv4_slot_fill(
5802 &model,
5803 pack_first,
5804 victim,
5805 pick,
5806 tensors,
5807 cfg.moe_inter,
5808 cfg.dim,
5809 gu_q2,
5810 ) {
5811 break;
5812 }
5813 let old = dynv.owner[victim] as usize;
5814 if old < dynv.remap.len() {
5815 dynv.remap[old] = u32::MAX;
5816 }
5817 dynv.remap[pick] = victim as u32;
5818 dynv.owner[victim] = pick as u32;
5819 dynv.last[victim] = clock;
5820 dynv.mutated = true;
5821 fetched += 1;
5822 }
5823}
5824
5825fn o_project_block(
5830 attn: &[f32],
5831 b: usize,
5832 wo_a: &crate::qtensor::QTensor,
5833 wo_b: &crate::qtensor::QTensor,
5834 groups: usize,
5835 lora: usize,
5836 pool: Option<&crate::pool::Pool>,
5837 out: &mut [f32],
5838) {
5839 let attn_len = attn.len() / b;
5840 let per_group = attn_len / groups;
5841 let rows = groups * lora;
5842 let mut mid = vec![0.0f32; b * rows];
5843 let mid_addr = crate::pool::SendMut::new(mid.as_mut_ptr());
5844 let run = |start: usize, end: usize| {
5845 let mut wr = vec![0.0f32; wo_a.cols()];
5846 for r in start..end {
5847 wo_a.row_f32(r, &mut wr);
5848 let group = r / lora;
5849 for bi in 0..b {
5850 let x = &attn
5851 [bi * attn_len + group * per_group..bi * attn_len + (group + 1) * per_group];
5852 let v = wr.iter().zip(x).map(|(w, x)| w * x).sum();
5853 unsafe { *mid_addr.at(bi * rows + r) = v };
5854 }
5855 }
5856 };
5857 match pool {
5858 Some(p) if rows >= 256 => p.run_rows(rows, &run),
5859 _ => run(0, rows),
5860 }
5861 wo_b.matmat(&mid, b, out, pool);
5862}
5863
5864fn no_compressed() -> bool {
5870 static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5871 *OFF.get_or_init(|| std::env::var("CMF_DSV4_NO_COMPRESSED").is_ok_and(|v| v != "0"))
5872}
5873
5874fn trace_on() -> bool {
5875 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5876 *ON.get_or_init(|| std::env::var("CMF_DSV4_TRACE").is_ok_and(|v| v != "0"))
5877}
5878
5879fn rms_of(v: &[f32]) -> f32 {
5880 if v.is_empty() {
5881 return 0.0;
5882 }
5883 (v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32).sqrt()
5884}
5885
5886#[cfg(feature = "gpu")]
5887fn verify_fp_on(pos: usize) -> bool {
5888 static POS: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
5889 let want = *POS.get_or_init(|| {
5890 std::env::var("CMF_DSV4_FP_POS")
5891 .ok()
5892 .and_then(|v| v.parse().ok())
5893 });
5894 want == Some(pos)
5895}
5896
5897#[cfg(feature = "gpu")]
5898fn verify_fp(tag: &str, pos: usize, li: usize, state: &[f32]) {
5899 if !verify_fp_on(pos) {
5900 return;
5901 }
5902 let mut fp = 0xcbf29ce484222325u64;
5903 for &x in state {
5904 fp ^= x.to_bits() as u64;
5905 fp = fp.wrapping_mul(0x100000001b3);
5906 }
5907 eprintln!(
5908 "[dsv4-fp] {tag} pos={pos} li={li} fp={fp:016x} rms={:.7} head={:?}",
5909 rms_of(state),
5910 &state[..4.min(state.len())]
5911 );
5912}
5913
5914thread_local! {
5920 static BODY: std::cell::RefCell<Vec<String>> = const { std::cell::RefCell::new(Vec::new()) };
5922 static PICKED: std::cell::RefCell<Vec<Vec<usize>>> =
5926 const { std::cell::RefCell::new(Vec::new()) };
5927 static PICK_TALLY: std::cell::RefCell<Option<Vec<(usize, Vec<usize>)>>> =
5929 const { std::cell::RefCell::new(None) };
5930}
5931
5932pub fn pick_tally_arm() {
5934 PICK_TALLY.with(|t| *t.borrow_mut() = Some(Vec::new()));
5935}
5936
5937pub fn pick_tally_take() -> Vec<(usize, Vec<usize>)> {
5939 PICK_TALLY.with(|t| t.borrow_mut().take().unwrap_or_default())
5940}
5941
5942pub fn tally_unique(picks: &[(usize, Vec<usize>)]) -> (usize, usize) {
5945 let mut seen = std::collections::HashSet::new();
5950 let mut total = 0;
5951 for (li, v) in picks {
5952 total += v.len();
5953 for &e in v {
5954 seen.insert((*li, e));
5955 }
5956 }
5957 (seen.len(), total)
5958}
5959
5960fn dump_path() -> Option<&'static str> {
5961 static P: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
5962 P.get_or_init(|| std::env::var("CMF_DSV4_DUMP").ok())
5963 .as_deref()
5964}
5965
5966fn dump_line(json: &str) {
5967 if let Some(p) = dump_path() {
5968 use std::io::Write as _;
5969 if let Ok(mut f) = std::fs::OpenOptions::new()
5970 .create(true)
5971 .append(true)
5972 .open(p)
5973 {
5974 let _ = writeln!(f, "{json}");
5975 }
5976 }
5977}
5978
5979fn vec_json(v: &[f32]) -> String {
5980 let mut s = String::with_capacity(v.len() * 9);
5981 s.push('[');
5982 for (i, x) in v.iter().enumerate() {
5983 if i > 0 {
5984 s.push(',');
5985 }
5986 s.push_str(&format!("{x:.6e}"));
5987 }
5988 s.push(']');
5989 s
5990}
5991
5992#[allow(clippy::too_many_arguments)]
6000#[allow(clippy::too_many_arguments)]
6010fn batch_prefill() -> usize {
6014 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
6015 *N.get_or_init(|| {
6016 std::env::var("CMF_DSV4_BATCH")
6017 .ok()
6018 .and_then(|v| v.parse::<usize>().ok())
6019 .filter(|&n| (1..=32).contains(&n))
6020 .unwrap_or(8)
6027 })
6028}
6029
6030#[allow(clippy::too_many_arguments)]
6043fn forward_chunk_batched(
6044 g: &Dsv4Globals,
6045 layers: &[Dsv4Layer],
6046 cfg: &Dsv4Cfg,
6047 st: &mut Dsv4State,
6048 ids: &[u32],
6049 pos0: usize,
6050 inv_freq: &[f32],
6051 pool: Option<&crate::pool::Pool>,
6052 logits: &mut Vec<f32>,
6053 want_logits: bool,
6054) -> bool {
6055 #[cfg(not(feature = "gpu"))]
6056 {
6057 let _ = (
6058 g,
6059 layers,
6060 cfg,
6061 st,
6062 ids,
6063 pos0,
6064 inv_freq,
6065 pool,
6066 logits,
6067 want_logits,
6068 );
6069 false
6070 }
6071 #[cfg(feature = "gpu")]
6072 {
6073 let b = ids.len();
6074 let gpu_end = st
6079 .dev_set
6080 .iter()
6081 .enumerate()
6082 .position(|(li, &on)| {
6083 !on || pack_for(&layers[li], cfg, li).is_none_or(|p| !p.route_complete())
6084 })
6085 .unwrap_or(st.dev_set.len());
6086 let why = if b < 2 {
6087 "fewer than two tokens"
6088 } else if !chain_enabled() {
6089 "the chain is off"
6090 } else if !st.dev_owned {
6091 "the card does not own the state yet"
6092 } else if st.dev_set.len() != layers.len() {
6093 "the layer set is not fixed yet"
6094 } else if st.dev_set[gpu_end.min(st.dev_set.len())..]
6095 .iter()
6096 .enumerate()
6097 .any(|(i, &on)| on && !st.partial_set.get(gpu_end + i).copied().unwrap_or(false))
6098 {
6099 "the layers on the card do not form a prefix"
6100 } else {
6101 ""
6102 };
6103 if !why.is_empty() {
6104 tracing::debug!("dsv4: batch preflight — {why}");
6110 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
6111 eprintln!("dsv4: batch preflight — {why}");
6112 }
6113 return false;
6114 }
6115 let (hc, dim) = (cfg.hc_mult, cfg.dim);
6116 let mut emb = vec![0.0f32; dim];
6117 let mut states = vec![0.0f32; b * hc * dim];
6118 for (t, &id) in ids.iter().enumerate() {
6119 let mut state = vec![0.0f32; hc * dim];
6120 g.embed.row_f32(id as usize, &mut emb);
6121 for j in 0..hc {
6122 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
6123 }
6124 let (folded, post0, comb0) = hc_fold_norm(
6125 &state,
6126 &layers[0].hc_attn_fn,
6127 &layers[0].hc_attn_scale,
6128 &layers[0].hc_attn_base,
6129 &layers[0].attn_norm,
6130 cfg,
6131 pool,
6132 );
6133 let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
6134 layers[0].wq_a.matvec(&folded, &mut qn0, pool);
6135 rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
6136 if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
6137 || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
6138 || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
6139 || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
6140 {
6141 return false;
6142 }
6143 states[t * hc * dim..(t + 1) * hc * dim].copy_from_slice(&state);
6144 }
6145 let run: Vec<usize> = (0..gpu_end).collect();
6146 let mut folded = Vec::new();
6147 st.pos = pos0;
6148 if gpu_end > 0 {
6149 if !dsv4_chain_run(
6150 layers,
6151 &run,
6152 cfg,
6153 g,
6154 st,
6155 *ids.last().unwrap(),
6156 &mut folded,
6157 Some(&mut states),
6158 b,
6159 ids,
6160 true,
6161 pool,
6162 ) {
6163 return false;
6164 }
6165 }
6166 let mut scratch = HcScratch::new(cfg);
6172 host_tail_walk_batch(
6173 g,
6174 layers,
6175 cfg,
6176 st,
6177 gpu_end,
6178 &mut states,
6179 ids,
6180 pos0,
6181 b,
6182 inv_freq,
6183 &mut scratch,
6184 pool,
6185 None,
6186 );
6187 st.pos = pos0 + b;
6188 {
6193 static SAID: std::sync::Once = std::sync::Once::new();
6194 SAID.call_once(|| tracing::warn!("dsv4: prefill in batches of {b}"));
6195 }
6196 if want_logits {
6201 let last = &states[(b - 1) * hc * dim..b * hc * dim];
6202 let mut h = vec![0.0f32; dim];
6203 hc_head_fold(
6204 last,
6205 &g.hc_head_fn,
6206 g.hc_head_scale,
6207 &g.hc_head_base,
6208 cfg,
6209 pool,
6210 &mut h,
6211 );
6212 rms_weighted(&mut h, &g.norm, cfg.norm_eps);
6213 logits.resize(cfg.vocab, 0.0);
6214 g.head.matvec(&h, logits, pool);
6215 } else {
6216 logits.clear();
6217 }
6218 true
6219 }
6220}
6221
6222#[cfg(feature = "gpu")]
6230pub struct Dsv4SpecTxn {
6231 pos0: usize,
6232 batch: usize,
6233 pub(crate) gpu_end: usize,
6234 dev_filled: Vec<usize>,
6235 dev_n_comp: Vec<usize>,
6236 dev_n_ix: Vec<usize>,
6237 host: Vec<(usize, HostLayerSnap)>,
6238 host_steps: Vec<(usize, Vec<HostLayerSnap>)>,
6243 pub states: Vec<f32>,
6247 shadow: Option<crate::gpu_wgpu::Dsv4SpecShadow>,
6248}
6249
6250#[cfg(feature = "gpu")]
6251struct HostLayerSnap {
6252 window: Vec<f32>,
6253 compressed: Vec<f32>,
6254 index_kv: Vec<f32>,
6255 pending_kv: Vec<f32>,
6256 pending_score: Vec<f32>,
6257 prev_kv: Vec<f32>,
6258 prev_score: Vec<f32>,
6259 pending_ix_kv: Vec<f32>,
6260 pending_ix_score: Vec<f32>,
6261 prev_ix_kv: Vec<f32>,
6262 prev_ix_score: Vec<f32>,
6263}
6264
6265#[cfg(feature = "gpu")]
6266fn host_snap(st: &Dsv4State, li: usize) -> HostLayerSnap {
6267 HostLayerSnap {
6268 window: st.window[li].clone(),
6269 compressed: st.compressed[li].clone(),
6270 index_kv: st.index_kv[li].clone(),
6271 pending_kv: st.pending_kv[li].clone(),
6272 pending_score: st.pending_score[li].clone(),
6273 prev_kv: st.prev_kv[li].clone(),
6274 prev_score: st.prev_score[li].clone(),
6275 pending_ix_kv: st.pending_ix_kv[li].clone(),
6276 pending_ix_score: st.pending_ix_score[li].clone(),
6277 prev_ix_kv: st.prev_ix_kv[li].clone(),
6278 prev_ix_score: st.prev_ix_score[li].clone(),
6279 }
6280}
6281
6282#[cfg(feature = "gpu")]
6283fn host_restore(st: &mut Dsv4State, li: usize, s: &HostLayerSnap) {
6284 st.window[li] = s.window.clone();
6285 st.compressed[li] = s.compressed.clone();
6286 st.index_kv[li] = s.index_kv.clone();
6287 st.pending_kv[li] = s.pending_kv.clone();
6288 st.pending_score[li] = s.pending_score.clone();
6289 st.prev_kv[li] = s.prev_kv.clone();
6290 st.prev_score[li] = s.prev_score.clone();
6291 st.pending_ix_kv[li] = s.pending_ix_kv.clone();
6292 st.pending_ix_score[li] = s.pending_ix_score.clone();
6293 st.prev_ix_kv[li] = s.prev_ix_kv.clone();
6294 st.prev_ix_score[li] = s.prev_ix_score.clone();
6295}
6296
6297#[cfg(feature = "gpu")]
6301#[allow(clippy::too_many_arguments)]
6302fn host_tail_walk(
6303 g: &Dsv4Globals,
6304 layers: &[Dsv4Layer],
6305 cfg: &Dsv4Cfg,
6306 st: &mut Dsv4State,
6307 gpu_end: usize,
6308 state: &mut [f32],
6309 token_id: u32,
6310 pos: usize,
6311 inv_freq: &[f32],
6312 scratch: &mut HcScratch,
6313 pool: Option<&crate::pool::Pool>,
6314) {
6315 st.pos = pos;
6316 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
6317 let freqs = if l.compressor.is_some() {
6318 &g.inv_freq_compress
6319 } else {
6320 &g.inv_freq_window
6321 };
6322 let freqs = if freqs.is_empty() {
6323 inv_freq
6324 } else {
6325 freqs.as_slice()
6326 };
6327 hc_block(
6328 state,
6329 &l.hc_attn_fn,
6330 &l.hc_attn_scale,
6331 &l.hc_attn_base,
6332 &l.attn_norm,
6333 cfg,
6334 scratch,
6335 pool,
6336 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
6337 );
6338 hc_block(
6339 state,
6340 &l.hc_ffn_fn,
6341 &l.hc_ffn_scale,
6342 &l.hc_ffn_base,
6343 &l.ffn_norm,
6344 cfg,
6345 scratch,
6346 pool,
6347 |f, o| {
6348 if host_cpu_moe() {
6349 crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
6350 } else {
6351 moe_step(f, l, cfg, token_id, li, pool, o)
6352 }
6353 },
6354 );
6355 dspark_note(li, state, cfg);
6356 }
6357}
6358
6359#[cfg(feature = "gpu")]
6365#[allow(clippy::too_many_arguments)]
6366fn host_tail_walk_batch(
6367 g: &Dsv4Globals,
6368 layers: &[Dsv4Layer],
6369 cfg: &Dsv4Cfg,
6370 st: &mut Dsv4State,
6371 gpu_end: usize,
6372 states: &mut [f32],
6373 ids: &[u32],
6374 pos0: usize,
6375 b: usize,
6376 inv_freq: &[f32],
6377 scratch: &mut HcScratch,
6378 pool: Option<&crate::pool::Pool>,
6379 mut steps: Option<&mut Vec<(usize, Vec<HostLayerSnap>)>>,
6380) {
6381 let (hc, dim) = (cfg.hc_mult, cfg.dim);
6382 let mix_hc = (2 + hc) * hc;
6383 let mut folds = vec![0.0f32; b * dim];
6384 let mut mo = vec![0.0f32; b * dim];
6385 let mut posts = vec![0.0f32; b * hc];
6386 let mut combs = vec![0.0f32; b * hc * hc];
6387 let mut resid = vec![0.0f32; b * hc * dim];
6388 let spec_time = {
6389 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6390 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
6391 };
6392 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
6393 let t_attn = std::time::Instant::now();
6394 let freqs = if l.compressor.is_some() {
6395 &g.inv_freq_compress
6396 } else {
6397 &g.inv_freq_window
6398 };
6399 let freqs = if freqs.is_empty() {
6400 inv_freq
6401 } else {
6402 freqs.as_slice()
6403 };
6404 for t in 0..b {
6405 st.pos = pos0 + t;
6406 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
6407 hc_block(
6408 state,
6409 &l.hc_attn_fn,
6410 &l.hc_attn_scale,
6411 &l.hc_attn_base,
6412 &l.attn_norm,
6413 cfg,
6414 scratch,
6415 pool,
6416 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
6417 );
6418 if let Some(steps) = steps.as_mut() {
6419 match steps.iter_mut().find(|(l, _)| *l == li) {
6420 Some((_, v)) => v.push(host_snap(st, li)),
6421 None => steps.push((li, vec![host_snap(st, li)])),
6422 }
6423 }
6424 }
6425 let t_glue = std::time::Instant::now();
6426 for t in 0..b {
6427 let state = &states[t * hc * dim..(t + 1) * hc * dim];
6428 hc_mixes(
6429 state,
6430 &l.hc_ffn_fn,
6431 mix_hc,
6432 cfg.norm_eps,
6433 pool,
6434 &mut scratch.mixes,
6435 );
6436 hc_split_sinkhorn(
6437 &scratch.mixes,
6438 &l.hc_ffn_scale,
6439 &l.hc_ffn_base,
6440 hc,
6441 cfg.hc_sinkhorn_iters,
6442 cfg.hc_eps,
6443 &mut scratch.pre,
6444 &mut posts[t * hc..(t + 1) * hc],
6445 &mut combs[t * hc * hc..(t + 1) * hc * hc],
6446 );
6447 let fold = &mut folds[t * dim..(t + 1) * dim];
6448 hc_fold(state, &scratch.pre, hc, dim, fold);
6449 let ms = fold.iter().map(|v| v * v).sum::<f32>() / dim as f32;
6450 let inv = 1.0 / (ms + cfg.norm_eps).sqrt();
6451 for (v, w) in fold.iter_mut().zip(&l.ffn_norm) {
6452 *v = *v * inv * w;
6453 }
6454 resid[t * hc * dim..(t + 1) * hc * dim]
6455 .copy_from_slice(&states[t * hc * dim..(t + 1) * hc * dim]);
6456 }
6457 let t_moe = std::time::Instant::now();
6458 let tail_pack = {
6464 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6465 *ON.get_or_init(|| {
6466 std::env::var("CMF_DSV4_TAIL_PACK")
6467 .map(|v| v != "0")
6468 .unwrap_or(true)
6469 })
6470 };
6471 let mut packed_done = false;
6472 if tail_pack && pack_for(l, cfg, li).is_some() {
6473 packed_done = true;
6474 for t in 0..b {
6475 let f = &folds[t * dim..(t + 1) * dim];
6476 let forced = l.tid2eid.as_ref().map(|tbl| {
6477 hash_route(tbl, cfg.vocab, cfg.top_k, ids.get(t).copied().unwrap_or(0))
6478 });
6479 let o = &mut mo[t * dim..(t + 1) * dim];
6480 match moe_frame(f, l, cfg, li, &[], forced.as_deref(), pool, None, None, o) {
6481 Some((cold_sum, n)) => {
6482 if n > 0 {
6483 for (od, cd) in o.iter_mut().zip(cold_sum.iter()) {
6484 *od += cd;
6485 }
6486 }
6487 }
6488 None => {
6489 packed_done = false;
6490 break;
6491 }
6492 }
6493 }
6494 }
6495 if !packed_done {
6496 if host_cpu_moe() {
6497 crate::gpu::cpu_scope(|| moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo));
6498 } else {
6499 moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo);
6500 }
6501 }
6502 let t_exp = std::time::Instant::now();
6503 for t in 0..b {
6504 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
6505 hc_expand(
6506 &mo[t * dim..(t + 1) * dim],
6507 &resid[t * hc * dim..(t + 1) * hc * dim],
6508 &posts[t * hc..(t + 1) * hc],
6509 &combs[t * hc * hc..(t + 1) * hc * hc],
6510 hc,
6511 dim,
6512 state,
6513 );
6514 dspark_note(li, state, cfg);
6515 }
6516 if spec_time {
6517 eprintln!(
6518 "layer {li} tail: attn {:.1} ms, glue {:.1}, moe {:.1}, expand {:.1}",
6519 (t_glue - t_attn).as_secs_f64() * 1e3,
6520 (t_moe - t_glue).as_secs_f64() * 1e3,
6521 (t_exp - t_moe).as_secs_f64() * 1e3,
6522 t_exp.elapsed().as_secs_f64() * 1e3,
6523 );
6524 }
6525 }
6526}
6527
6528#[cfg(feature = "gpu")]
6534#[allow(clippy::too_many_arguments)]
6535fn partial_layer_batch(
6536 g: &Dsv4Globals,
6537 layers: &[Dsv4Layer],
6538 cfg: &Dsv4Cfg,
6539 st: &mut Dsv4State,
6540 li: usize,
6541 states: &mut [f32],
6542 ids: &[u32],
6543 pos0: usize,
6544 b: usize,
6545 pool: Option<&crate::pool::Pool>,
6546) -> bool {
6547 let l = &layers[li];
6548 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
6549 if states.len() < b * hc * dim || ids.len() < b {
6550 return false;
6551 }
6552 let Some(pk) = pack_for(l, cfg, li) else {
6553 return false;
6554 };
6555 if pk.route_complete() {
6556 return false;
6557 }
6558 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
6559 return false;
6560 };
6561 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
6562 l.wq_a.model_idx(),
6563 l.wq_b.model_idx(),
6564 l.wo_a.model_idx(),
6565 l.wo_b.model_idx(),
6566 l.wkv.model_idx(),
6567 ) else {
6568 return false;
6569 };
6570 let comp = match &l.compressor {
6571 None => None,
6572 Some(cp) => {
6573 let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
6574 return false;
6575 };
6576 Some((
6577 crate::gpu_wgpu::Dsv4CompW {
6578 wkv: a,
6579 wgate: bx,
6580 norm: &cp.norm,
6581 ape: &cp.ape,
6582 },
6583 crate::gpu_wgpu::Dsv4CompGeom {
6584 width: cp.wkv.rows(),
6585 hidden: dim,
6586 ratio: cp.ratio,
6587 overlap: cp.overlap,
6588 rope_dim: cfg.rope_head_dim,
6589 eps: cfg.norm_eps,
6590 },
6591 ))
6592 }
6593 };
6594 let ix = match &l.indexer {
6595 None => None,
6596 Some(ixr) => {
6597 let cp = &ixr.compressor;
6598 let (Some(a), Some(bx), Some(qb), Some(wp)) = (
6599 cp.wkv.model_idx(),
6600 cp.wgate.model_idx(),
6601 ixr.wq_b.model_idx(),
6602 ixr.weights_proj.model_idx(),
6603 ) else {
6604 return false;
6605 };
6606 let ih = ixr.weights_proj.rows();
6607 Some((
6608 crate::gpu_wgpu::Dsv4CompW {
6609 wkv: a,
6610 wgate: bx,
6611 norm: &cp.norm,
6612 ape: &cp.ape,
6613 },
6614 crate::gpu_wgpu::Dsv4CompGeom {
6615 width: cp.wkv.rows(),
6616 hidden: dim,
6617 ratio: cp.ratio,
6618 overlap: cp.overlap,
6619 rope_dim: cfg.rope_head_dim,
6620 eps: cfg.norm_eps,
6621 },
6622 crate::gpu_wgpu::Dsv4IxW {
6623 wq_b: qb,
6624 weights_proj: wp,
6625 },
6626 crate::gpu_wgpu::Dsv4IxGeom {
6627 ih,
6628 idim: ixr.wq_b.rows() / ih.max(1),
6629 q_lora: cfg.q_lora_rank,
6630 hidden: dim,
6631 rope_dim: cfg.rope_head_dim,
6632 eps: cfg.norm_eps,
6633 top_k: cfg.index_topk,
6634 window: cfg.window,
6635 },
6636 ))
6637 }
6638 };
6639 let ew_c = comp.as_ref().map_or(
6640 0,
6641 |(_, cg)| {
6642 if cg.overlap { cg.width / 2 } else { cg.width }
6643 },
6644 );
6645 let ew_i = ix.as_ref().map_or(
6646 0,
6647 |(_, cg, _, _)| {
6648 if cg.overlap { cg.width / 2 } else { cg.width }
6649 },
6650 );
6651 let comp_extra = comp
6652 .as_ref()
6653 .map_or(0, |(_, cg)| b.div_ceil(cg.ratio.max(1)));
6654 let need = cfg.window * hd + (st.dev_n_comp[li] + comp_extra + 1) * ew_c.max(1) + (b + 1) * hd;
6655 if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
6656 return false;
6657 }
6658 let base = crate::gpu_wgpu::Dsv4Prep {
6659 wkv,
6660 kv_norm: &l.kv_norm,
6661 comp,
6662 ix,
6663 filled: st.dev_filled[li],
6664 window: cfg.window,
6665 n_comp: st.dev_n_comp[li],
6666 n_ix: st.dev_n_ix[li],
6667 comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
6668 ix_dst_off: st.dev_n_ix[li] * ew_i,
6669 idx_cap: cfg.window
6670 + if l.indexer.is_some() {
6671 cfg.index_topk
6672 } else {
6673 st.dev_n_comp[li] + comp_extra + 1
6674 },
6675 };
6676 let mut preps = Vec::with_capacity(b);
6677 for t in 0..b {
6678 let mut p = base.clone();
6679 p.filled = (base.filled + t).min(base.window);
6680 let advanced = |ratio: usize| -> usize {
6681 if ratio == 0 {
6682 0
6683 } else {
6684 (0..t).filter(|k| (pos0 + k + 1) % ratio == 0).count()
6685 }
6686 };
6687 if let Some((_, cg)) = base.comp.as_ref() {
6688 p.n_comp = base.n_comp + advanced(cg.ratio);
6689 p.comp_dst_off = base.comp_dst_off + (p.n_comp - base.n_comp) * ew_c;
6690 }
6691 if let Some((_, cg, _, _)) = base.ix.as_ref() {
6692 p.n_ix = base.n_ix + advanced(cg.ratio);
6693 p.ix_dst_off = base.ix_dst_off + (p.n_ix - base.n_ix) * ew_i;
6694 }
6695 preps.push(p);
6696 }
6697
6698 for t in 0..b {
6702 let state = &states[t * hc * dim..(t + 1) * hc * dim];
6703 let (fold, post, comb) = hc_fold_norm(
6704 state,
6705 &l.hc_attn_fn,
6706 &l.hc_attn_scale,
6707 &l.hc_attn_base,
6708 &l.attn_norm,
6709 cfg,
6710 pool,
6711 );
6712 let mut qn = vec![0.0f32; cfg.q_lora_rank];
6713 l.wq_a.matvec(&fold, &mut qn, pool);
6714 rms_weighted(&mut qn, &l.q_norm, cfg.norm_eps);
6715 if !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, state, &post, &comb, &fold, &qn) {
6716 return false;
6717 }
6718 }
6719 let forced_rows: Vec<Option<Vec<usize>>> = ids
6720 .iter()
6721 .take(b)
6722 .map(|&id| {
6723 l.tid2eid
6724 .as_ref()
6725 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, id))
6726 })
6727 .collect();
6728 let dynv = pk.dynslots.lock().unwrap();
6729 let w = crate::gpu_wgpu::Dsv4LayerW {
6730 attn: crate::gpu_wgpu::Dsv4AttnW {
6731 wq_a,
6732 wq_b,
6733 wo_a,
6734 wo_b,
6735 q_norm: &l.q_norm,
6736 sink: &l.attn_sink,
6737 },
6738 moe: crate::gpu_wgpu::Dsv4MoeW {
6739 router: &[],
6740 experts: &pk.tensors,
6741 logits: &[],
6742 bias: pk.bias.as_deref(),
6743 mask: pk.mask.as_deref(),
6744 forced: None,
6745 remap: Some(&dynv.remap),
6746 global: None,
6747 has_shared: true,
6748 shared_weight: 1.0,
6749 preweighted: false,
6750 qwen_softmax: false,
6751 },
6752 hc_ffn_fn: &l.hc_ffn_fn,
6753 hc_ffn_scale: &l.hc_ffn_scale,
6754 hc_ffn_base: &l.hc_ffn_base,
6755 hc_next_fn: None,
6758 hc_next_scale: &l.hc_attn_scale,
6759 hc_next_base: &l.hc_attn_base,
6760 ffn_norm: &l.ffn_norm,
6761 next_norm: &l.attn_norm,
6762 next_q_norm: &l.q_norm,
6763 next_wq_a: None,
6764 router: &pk.router,
6765 };
6766 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
6767 attn: crate::gpu_wgpu::Dsv4AttnGeom {
6768 dim,
6769 nh: cfg.n_heads,
6770 hd,
6771 rd: cfg.rope_head_dim,
6772 q_lora: cfg.q_lora_rank,
6773 o_lora: cfg.o_lora_rank,
6774 o_groups: cfg.o_groups,
6775 eps: cfg.norm_eps,
6776 scale: (hd as f32).powf(-0.5),
6777 bf16: false,
6778 q_rms: true,
6779 o_fp8: false,
6780 },
6781 moe: crate::gpu_wgpu::Dsv4MoeGeom {
6782 hidden: dim,
6783 inter: cfg.moe_inter,
6784 top_k: cfg.top_k,
6785 route_scale: cfg.route_scale,
6786 swiglu_limit: cfg.swiglu_limit,
6787 gu_q2: l
6788 .experts
6789 .first()
6790 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
6791 bf16: false,
6792 },
6793 hc,
6794 hc_eps: cfg.hc_eps,
6795 sinkhorn_iters: cfg.hc_sinkhorn_iters,
6796 };
6797 let freqs = if l.compressor.is_some() {
6798 g.inv_freq_compress.as_slice()
6799 } else {
6800 g.inv_freq_window.as_slice()
6801 };
6802 let Some(mut got) = crate::gpu_wgpu::dsv4_layer_batch_partial(
6803 &model,
6804 &w,
6805 geom,
6806 st.kv_id,
6807 li,
6808 b,
6809 &preps,
6810 Some(&forced_rows),
6811 freqs,
6812 pos0,
6813 ) else {
6814 return false;
6815 };
6816 drop(dynv);
6817 let mut cold_sum = vec![0.0f32; b * dim];
6818 cold_step_block(&got.cold_x, b, l, cfg, &got.cold, pool, &mut cold_sum);
6819 for t in 0..b {
6820 let state = &mut got.states[t * hc * dim..(t + 1) * hc * dim];
6821 let post = &got.posts[t * hc..(t + 1) * hc];
6822 let cold = &cold_sum[t * dim..(t + 1) * dim];
6823 for j in 0..hc {
6824 for d in 0..dim {
6825 state[j * dim + d] += post[j] * cold[d];
6826 }
6827 }
6828 }
6829 states[..b * hc * dim].copy_from_slice(&got.states);
6830 if !crate::gpu_wgpu::dsv4_spec_cap_write_host(li, b, hc * dim, &got.states) {
6831 return false;
6832 }
6833 if let Some(first_route) = got.routed.first() {
6834 refill_route_slots(l, cfg, &pk, first_route);
6835 }
6836 for t in 0..b {
6837 let pos = pos0 + t;
6838 st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
6839 if let Some((_, cg, ..)) = base.ix.as_ref() {
6840 if (pos + 1) % cg.ratio == 0 {
6841 st.dev_n_ix[li] += 1;
6842 }
6843 }
6844 if let Some((_, cg)) = base.comp.as_ref() {
6845 if (pos + 1) % cg.ratio == 0 {
6846 st.dev_n_comp[li] += 1;
6847 note_compressed(st.kv_id, li, st.dev_n_comp[li]);
6848 }
6849 }
6850 }
6851 true
6852}
6853
6854#[cfg(feature = "gpu")]
6863#[allow(clippy::too_many_arguments)]
6864pub fn dsv4_verify_chunk(
6865 g: &Dsv4Globals,
6866 layers: &[Dsv4Layer],
6867 cfg: &Dsv4Cfg,
6868 st: &mut Dsv4State,
6869 ids: &[u32],
6870 pos0: usize,
6871 inv_freq: &[f32],
6872 pool: Option<&crate::pool::Pool>,
6873 cap_targets: &[usize],
6874 argmax_out: &mut Vec<u32>,
6875 logits_out: &mut Vec<f32>,
6876 walked_out: &mut Vec<f32>,
6877) -> Option<Dsv4SpecTxn> {
6878 let b = ids.len();
6879 let complete_end = st
6883 .dev_set
6884 .iter()
6885 .enumerate()
6886 .position(|(li, &on)| {
6887 !on || pack_for(&layers[li], cfg, li).is_none_or(|p| !p.route_complete())
6888 })
6889 .unwrap_or(st.dev_set.len());
6890 fn partial_batch_on() -> bool {
6891 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6892 *ON.get_or_init(|| {
6893 std::env::var("CMF_DSV4_PARTIAL_BATCH")
6894 .map(|v| v != "0")
6895 .unwrap_or(true)
6896 })
6897 }
6898 let device_end = if partial_batch_on() {
6899 (complete_end..layers.len())
6900 .take_while(|&li| {
6901 st.dev_set.get(li).copied().unwrap_or(false)
6902 && st.partial_set.get(li).copied().unwrap_or(false)
6903 && pack_for(&layers[li], cfg, li).is_some_and(|p| !p.route_complete())
6904 })
6905 .last()
6906 .map_or(complete_end, |li| li + 1)
6907 } else {
6908 complete_end
6909 };
6910 let full_beyond = st.dev_set[device_end.min(st.dev_set.len())..]
6913 .iter()
6914 .enumerate()
6915 .any(|(i, &on)| on && !st.partial_set.get(device_end + i).copied().unwrap_or(false));
6916 fn host_verify_on() -> bool {
6923 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6924 *ON.get_or_init(|| {
6925 std::env::var("CMF_DSV4_HOST_VERIFY")
6926 .map(|v| v != "0")
6927 .unwrap_or(true)
6932 })
6933 }
6934 if b < 2
6935 || !chain_enabled()
6936 || !st.dev_owned
6937 || st.dev_set.len() != layers.len()
6938 || (device_end == 0 && !host_verify_on())
6939 || full_beyond
6940 {
6941 return None;
6942 }
6943 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
6944 let metas: Vec<(usize, usize, usize, usize)> = (0..device_end)
6946 .map(|li| (li, hd, cfg.window, st.dev_filled[li]))
6947 .collect();
6948 let shadow = crate::gpu_wgpu::dsv4_spec_shadow(st.kv_id, &metas, b)?;
6949 let mut txn = Dsv4SpecTxn {
6950 pos0,
6951 batch: b,
6952 gpu_end: device_end,
6953 dev_filled: st.dev_filled.clone(),
6954 dev_n_comp: st.dev_n_comp.clone(),
6955 dev_n_ix: st.dev_n_ix.clone(),
6956 host: (device_end..layers.len())
6957 .map(|li| (li, host_snap(st, li)))
6958 .collect(),
6959 states: Vec::new(),
6960 host_steps: Vec::new(),
6961 shadow: Some(shadow),
6962 };
6963 let dev_caps: Vec<usize> = cap_targets
6965 .iter()
6966 .copied()
6967 .filter(|&t| t < device_end)
6968 .collect();
6969 crate::gpu_wgpu::dsv4_spec_retain_arm(device_end, &dev_caps);
6970
6971 let mut emb = vec![0.0f32; dim];
6973 let mut states = vec![0.0f32; b * hc * dim];
6974 for (t, &id) in ids.iter().enumerate() {
6975 let mut state = vec![0.0f32; hc * dim];
6976 g.embed.row_f32(id as usize, &mut emb);
6977 for j in 0..hc {
6978 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
6979 }
6980 states[t * hc * dim..(t + 1) * hc * dim].copy_from_slice(&state);
6981 let (folded, post0, comb0) = hc_fold_norm(
6982 &state,
6983 &layers[0].hc_attn_fn,
6984 &layers[0].hc_attn_scale,
6985 &layers[0].hc_attn_base,
6986 &layers[0].attn_norm,
6987 cfg,
6988 pool,
6989 );
6990 let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
6991 layers[0].wq_a.matvec(&folded, &mut qn0, pool);
6992 rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
6993 if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
6994 || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
6995 || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
6996 || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
6997 {
6998 crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
6999 return None;
7000 }
7001 }
7002 let spec_time = {
7003 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7004 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
7005 };
7006 let t0 = std::time::Instant::now();
7007 let mut folded = Vec::new();
7008 st.pos = pos0;
7009 let fp_split = verify_fp_on(pos0) && std::env::var("CMF_DSV4_FP_SPLIT").is_ok_and(|v| v != "0");
7013 let mut ok = true;
7014 if fp_split {
7015 for li in 0..complete_end {
7016 let one = [li];
7017 ok = dsv4_chain_run(
7018 layers,
7019 &one,
7020 cfg,
7021 g,
7022 st,
7023 *ids.last().unwrap(),
7024 &mut folded,
7025 Some(&mut states),
7026 b,
7027 ids,
7028 li == 0,
7029 pool,
7030 );
7031 if !ok {
7032 break;
7033 }
7034 verify_fp("verify", pos0, li, &states[..hc * dim]);
7035 }
7036 } else if complete_end > 0 {
7037 let run: Vec<usize> = (0..complete_end).collect();
7038 ok = dsv4_chain_run(
7039 layers,
7040 &run,
7041 cfg,
7042 g,
7043 st,
7044 *ids.last().unwrap(),
7045 &mut folded,
7046 Some(&mut states),
7047 b,
7048 ids,
7049 true,
7050 pool,
7051 );
7052 if ok {
7053 verify_fp("verify", pos0, complete_end - 1, &states[..hc * dim]);
7054 }
7055 }
7056 if ok {
7057 for li in complete_end..device_end {
7058 if !partial_layer_batch(g, layers, cfg, st, li, &mut states, ids, pos0, b, pool) {
7059 ok = false;
7060 break;
7061 }
7062 verify_fp("verify", pos0, li, &states[..hc * dim]);
7063 }
7064 }
7065 crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
7066 if !ok {
7067 if let Some(sh) = txn.shadow.take() {
7070 let _ = crate::gpu_wgpu::dsv4_spec_restore(&sh);
7071 }
7072 st.dev_filled = txn.dev_filled;
7073 st.dev_n_comp = txn.dev_n_comp;
7074 st.dev_n_ix = txn.dev_n_ix;
7075 st.pos = pos0;
7076 return None;
7077 }
7078 txn.states = states.clone();
7079 let t_chain = t0.elapsed();
7080 if std::env::var("CMF_DSV4_FOLD_DBG").is_ok() {
7081 for li in 0..device_end {
7085 let Some(ixr) = &layers[li].indexer else {
7086 continue;
7087 };
7088 let ratio = ixr.compressor.ratio;
7089 for t in 0..b {
7090 if (pos0 + t + 1) % ratio == 0 {
7091 let ew = {
7092 let w = ixr.compressor.wkv.rows();
7093 if ixr.compressor.overlap { w / 2 } else { w }
7094 };
7095 let idx_new = txn.dev_n_ix[li]
7096 + (0..=t).filter(|k| (pos0 + k + 1) % ratio == 0).count()
7097 - 1;
7098 if let Some(v) =
7099 crate::gpu_wgpu::dsv4_dbg_read_ix(st.kv_id, li, idx_new * ew, ew.min(8))
7100 {
7101 let sum: f32 = v.iter().sum();
7102 eprintln!(
7103 "[fold] li={li} pos={} entry={idx_new} head={:?} sum={sum:.6}",
7104 pos0 + t,
7105 &v[..4.min(v.len())]
7106 );
7107 }
7108 }
7109 }
7110 }
7111 }
7112
7113 let mut scratch = HcScratch::new(cfg);
7115 argmax_out.clear();
7116 logits_out.clear();
7117 logits_out.resize(b * cfg.vocab, 0.0);
7118 let mut head_in = vec![0.0f32; b * dim];
7119 let mut host_steps: Vec<(usize, Vec<HostLayerSnap>)> = Vec::new();
7120 host_tail_walk_batch(
7121 g,
7122 layers,
7123 cfg,
7124 st,
7125 device_end,
7126 &mut states,
7127 ids,
7128 pos0,
7129 b,
7130 inv_freq,
7131 &mut scratch,
7132 pool,
7133 Some(&mut host_steps),
7134 );
7135 txn.host_steps = host_steps;
7136 for t in 0..b {
7137 let state = &states[t * hc * dim..(t + 1) * hc * dim];
7138 let h = &mut head_in[t * dim..(t + 1) * dim];
7139 hc_head_fold(
7140 state,
7141 &g.hc_head_fn,
7142 g.hc_head_scale,
7143 &g.hc_head_base,
7144 cfg,
7145 pool,
7146 h,
7147 );
7148 rms_weighted(h, &g.norm, cfg.norm_eps);
7149 }
7150 let batch_head = std::env::var("CMF_DSV4_SPEC_BATCH_HEAD").is_ok_and(|v| v != "0");
7155 let head_gpu = batch_head
7156 && g.head.model_idx().is_some_and(|hi| {
7157 let model = layers[0].experts.first().and_then(|e| e.w1.model_arc());
7158 model.is_some_and(|m| {
7159 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
7160 &m, hi, &head_in, b, cfg.vocab, dim, logits_out,
7161 )
7162 })
7163 });
7164 for t in 0..b {
7165 if !head_gpu {
7166 let h = &head_in[t * dim..(t + 1) * dim];
7167 g.head
7168 .matvec(h, &mut logits_out[t * cfg.vocab..(t + 1) * cfg.vocab], pool);
7169 }
7170 let row = &logits_out[t * cfg.vocab..(t + 1) * cfg.vocab];
7171 let mut best = 0usize;
7172 for v in 1..cfg.vocab {
7173 if row[v] > row[best] {
7174 best = v;
7175 }
7176 }
7177 argmax_out.push(best as u32);
7178 }
7179 walked_out.clear();
7180 walked_out.extend_from_slice(&states);
7181 st.pos = pos0 + b;
7182 if spec_time {
7183 eprintln!(
7184 "verify: shadow+seed+chain {:.1} ms, tail+head {:.1} ms",
7185 t_chain.as_secs_f64() * 1e3,
7186 (t0.elapsed() - t_chain).as_secs_f64() * 1e3,
7187 );
7188 }
7189 Some(txn)
7190}
7191
7192#[cfg(feature = "gpu")]
7200pub fn dsv4_spec_finish(
7201 g: &Dsv4Globals,
7202 layers: &[Dsv4Layer],
7203 cfg: &Dsv4Cfg,
7204 st: &mut Dsv4State,
7205 mut txn: Dsv4SpecTxn,
7206 accepted: usize,
7207 ids: &[u32],
7208 inv_freq: &[f32],
7209 pool: Option<&crate::pool::Pool>,
7210) -> bool {
7211 macro_rules! sfail {
7212 ($($t:tt)*) => {{
7213 if std::env::var("CMF_DSV4_SPEC_DEBUG").is_ok() {
7214 eprintln!("spec_finish: {}", format_args!($($t)*));
7215 }
7216 return false;
7217 }};
7218 }
7219 let b = txn.batch;
7220 let k = accepted.min(b);
7221 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
7222 let win_metas: Vec<(usize, usize, usize, usize)> = (0..txn.gpu_end)
7225 .map(|li| (li, txn.dev_filled[li], cfg.window, hd))
7226 .collect();
7227 if !crate::gpu_wgpu::dsv4_spec_commit_windows(st.kv_id, &win_metas, b, k) {
7228 sfail!("window commit");
7229 }
7230 if k == b {
7231 return true;
7234 }
7235 let Some(sh) = txn.shadow.take() else {
7237 sfail!("no shadow")
7238 };
7239 if !crate::gpu_wgpu::dsv4_spec_restore(&sh) {
7240 sfail!("restore");
7241 }
7242 let Some(model) = layers[0].experts.first().and_then(|e| e.w1.model_arc()) else {
7243 sfail!("no model");
7244 };
7245 let mut plan: Vec<(usize, crate::gpu_wgpu::Dsv4Prep)> = Vec::new();
7246 let mut freqs_own: Vec<&[f32]> = Vec::new();
7247 for li in 0..txn.gpu_end {
7248 let l = &layers[li];
7249 let Some(wkv) = l.wkv.model_idx() else {
7250 sfail!("wkv of layer {li}")
7251 };
7252 let comp = match &l.compressor {
7253 None => None,
7254 Some(cp) => {
7255 let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
7256 sfail!("compressor of layer {li}");
7257 };
7258 Some((
7259 crate::gpu_wgpu::Dsv4CompW {
7260 wkv: a,
7261 wgate: bx,
7262 norm: &cp.norm,
7263 ape: &cp.ape,
7264 },
7265 crate::gpu_wgpu::Dsv4CompGeom {
7266 width: cp.wkv.rows(),
7267 hidden: dim,
7268 ratio: cp.ratio,
7269 overlap: cp.overlap,
7270 rope_dim: cfg.rope_head_dim,
7271 eps: cfg.norm_eps,
7272 },
7273 ))
7274 }
7275 };
7276 let ix = match &l.indexer {
7277 None => None,
7278 Some(ixr) => {
7279 let cp = &ixr.compressor;
7280 let (Some(a), Some(bx), Some(qb), Some(wp)) = (
7281 cp.wkv.model_idx(),
7282 cp.wgate.model_idx(),
7283 ixr.wq_b.model_idx(),
7284 ixr.weights_proj.model_idx(),
7285 ) else {
7286 sfail!("indexer of layer {li}");
7287 };
7288 let ih = ixr.weights_proj.rows();
7289 Some((
7290 crate::gpu_wgpu::Dsv4CompW {
7291 wkv: a,
7292 wgate: bx,
7293 norm: &cp.norm,
7294 ape: &cp.ape,
7295 },
7296 crate::gpu_wgpu::Dsv4CompGeom {
7297 width: cp.wkv.rows(),
7298 hidden: dim,
7299 ratio: cp.ratio,
7300 overlap: cp.overlap,
7301 rope_dim: cfg.rope_head_dim,
7302 eps: cfg.norm_eps,
7303 },
7304 crate::gpu_wgpu::Dsv4IxW {
7305 wq_b: qb,
7306 weights_proj: wp,
7307 },
7308 crate::gpu_wgpu::Dsv4IxGeom {
7309 ih,
7310 idim: ixr.wq_b.rows() / ih.max(1),
7311 q_lora: cfg.q_lora_rank,
7312 hidden: dim,
7313 rope_dim: cfg.rope_head_dim,
7314 eps: cfg.norm_eps,
7315 top_k: cfg.index_topk,
7316 window: cfg.window,
7317 },
7318 ))
7319 }
7320 };
7321 let ew_c = comp.as_ref().map_or(
7322 0,
7323 |(_, cg)| {
7324 if cg.overlap { cg.width / 2 } else { cg.width }
7325 },
7326 );
7327 let ew_i = ix.as_ref().map_or(
7328 0,
7329 |(_, cg, _, _)| {
7330 if cg.overlap { cg.width / 2 } else { cg.width }
7331 },
7332 );
7333 let prep = crate::gpu_wgpu::Dsv4Prep {
7334 wkv,
7335 kv_norm: &l.kv_norm,
7336 comp,
7337 ix,
7338 filled: txn.dev_filled[li],
7339 window: cfg.window,
7340 n_comp: txn.dev_n_comp[li],
7341 n_ix: txn.dev_n_ix[li],
7342 comp_dst_off: cfg.window * hd + txn.dev_n_comp[li] * ew_c,
7343 ix_dst_off: txn.dev_n_ix[li] * ew_i,
7344 idx_cap: cfg.window
7345 + if l.indexer.is_some() {
7346 cfg.index_topk
7347 } else {
7348 0
7349 },
7350 };
7351 let fr = if l.compressor.is_some() {
7352 g.inv_freq_compress.as_slice()
7353 } else {
7354 g.inv_freq_window.as_slice()
7355 };
7356 freqs_own.push(if fr.is_empty() { inv_freq } else { fr });
7357 plan.push((li, prep));
7358 }
7359 if !crate::gpu_wgpu::dsv4_spec_replay(
7360 &model,
7361 &plan,
7362 st.kv_id,
7363 txn.pos0,
7364 b,
7365 k,
7366 &freqs_own,
7367 hd,
7368 dim,
7369 cfg.rope_head_dim,
7370 cfg.norm_eps,
7371 true,
7372 ) {
7373 sfail!("replay k={k}");
7374 }
7375 let advanced = |ratio: usize| -> usize {
7377 if ratio == 0 {
7378 return 0;
7379 }
7380 (0..k).filter(|t| (txn.pos0 + t + 1) % ratio == 0).count()
7381 };
7382 for li in 0..txn.gpu_end {
7383 let l = &layers[li];
7384 st.dev_filled[li] = (txn.dev_filled[li] + k).min(cfg.window);
7385 let ac = l.compressor.as_ref().map_or(0, |cp| advanced(cp.ratio));
7386 let ai = l
7387 .indexer
7388 .as_ref()
7389 .map_or(0, |ix| advanced(ix.compressor.ratio));
7390 st.dev_n_comp[li] = txn.dev_n_comp[li] + ac;
7391 st.dev_n_ix[li] = txn.dev_n_ix[li] + ai;
7392 note_compressed(st.kv_id, li, st.dev_n_comp[li]);
7393 }
7394 if k >= 1 && txn.host_steps.iter().all(|(_, v)| v.len() >= k) && !txn.host_steps.is_empty() {
7397 for (li, v) in &txn.host_steps {
7398 host_restore(st, *li, &v[k - 1]);
7399 }
7400 } else {
7401 for (li, snap) in &txn.host {
7402 host_restore(st, *li, snap);
7403 }
7404 let mut scratch = HcScratch::new(cfg);
7405 let mut states = txn.states.clone();
7406 host_tail_walk_batch(
7407 g,
7408 layers,
7409 cfg,
7410 st,
7411 txn.gpu_end,
7412 &mut states[..k * hc * dim],
7413 ids,
7414 txn.pos0,
7415 k,
7416 inv_freq,
7417 &mut scratch,
7418 pool,
7419 None,
7420 );
7421 }
7422 st.pos = txn.pos0 + k;
7423 true
7424}
7425
7426pub fn forward_chunk(
7427 g: &Dsv4Globals,
7428 layers: &[Dsv4Layer],
7429 cfg: &Dsv4Cfg,
7430 st: &mut Dsv4State,
7431 ids: &[u32],
7432 pos0: usize,
7433 inv_freq: &[f32],
7434 pool: Option<&crate::pool::Pool>,
7435 logits: &mut Vec<f32>,
7436 want_logits: bool,
7437) {
7438 let bs = batch_prefill();
7439 if bs > 1 {
7440 let mut i = 0;
7447 if !st.dev_owned && !ids.is_empty() {
7448 st.pos = pos0;
7449 forward_token_inner(
7450 g,
7451 layers,
7452 cfg,
7453 st,
7454 ids[0],
7455 inv_freq,
7456 pool,
7457 logits,
7458 ids.len() == 1,
7459 );
7460 i = 1;
7461 }
7462 while i < ids.len() {
7463 let end = (i + bs).min(ids.len());
7464 st.pos = pos0 + i;
7465 if !forward_chunk_batched(
7466 g,
7467 layers,
7468 cfg,
7469 st,
7470 &ids[i..end],
7471 pos0 + i,
7472 inv_freq,
7473 pool,
7474 logits,
7475 want_logits && end == ids.len(),
7476 ) {
7477 break;
7478 }
7479 i = end;
7480 }
7481 if i == ids.len() {
7482 return;
7483 }
7484 for (k, &id) in ids.iter().enumerate().skip(i) {
7486 st.pos = pos0 + k;
7487 let last = want_logits && k + 1 == ids.len();
7488 forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
7489 }
7490 return;
7491 }
7492 for (i, &id) in ids.iter().enumerate() {
7493 st.pos = pos0 + i;
7494 let last = want_logits && i + 1 == ids.len();
7495 forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
7496 }
7497}
7498
7499pub fn forward_token(
7500 g: &Dsv4Globals,
7501 layers: &[Dsv4Layer],
7502 cfg: &Dsv4Cfg,
7503 st: &mut Dsv4State,
7504 token_id: u32,
7505 inv_freq: &[f32],
7506 pool: Option<&crate::pool::Pool>,
7507 logits: &mut Vec<f32>,
7508) {
7509 forward_token_inner(g, layers, cfg, st, token_id, inv_freq, pool, logits, true);
7510}
7511
7512#[allow(clippy::too_many_arguments)]
7513fn forward_token_inner(
7514 g: &Dsv4Globals,
7515 layers: &[Dsv4Layer],
7516 cfg: &Dsv4Cfg,
7517 st: &mut Dsv4State,
7518 token_id: u32,
7519 inv_freq: &[f32],
7520 pool: Option<&crate::pool::Pool>,
7521 logits: &mut Vec<f32>,
7522 want_logits: bool,
7524) {
7525 let _t_all = prof::on().then(std::time::Instant::now);
7526 let _all_guard = Charge(_t_all, &prof::ALL_NS);
7527 let (hc, dim) = (cfg.hc_mult, cfg.dim);
7528
7529 let mut emb = vec![0.0f32; dim];
7531 g.embed.row_f32(token_id as usize, &mut emb);
7532 let mut state = vec![0.0f32; hc * dim];
7533 for j in 0..hc {
7534 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
7535 }
7536
7537 let mut scratch = HcScratch::new(cfg);
7538 let mut dump: Vec<String> = Vec::new();
7539 if dump_path().is_some() {
7540 dump.push(format!("\"embed\":{}", vec_json(&emb)));
7541 PICKED.with(|p| p.borrow_mut().clear());
7542 BODY.with(|b| b.borrow_mut().clear());
7543 dump.push(",\"layers\":[".into());
7544 }
7545 if trace_on() {
7546 eprintln!(
7547 "[dsv4] tok={token_id} pos={} embed rms={:.5}",
7548 st.pos,
7549 rms_of(&emb)
7550 );
7551 }
7552 #[cfg(feature = "gpu")]
7554 let layer_frames = gpu_layer_enabled()
7555 && dsv4_layer_loop(
7556 &mut state,
7557 layers,
7558 g,
7559 cfg,
7560 st,
7561 token_id,
7562 inv_freq,
7563 pool,
7564 &mut scratch,
7565 );
7566 #[cfg(not(feature = "gpu"))]
7567 let layer_frames = false;
7568
7569 #[cfg(feature = "gpu")]
7576 let hc_dev = hc_on_device()
7577 && !layer_frames
7578 && gpu_attn_enabled()
7579 && gpu_moe2_enabled()
7580 && dump_path().is_none();
7581 #[cfg(not(feature = "gpu"))]
7582 let hc_dev = false;
7583 #[cfg(feature = "gpu")]
7590 let two_frame_done = hc_dev
7591 && dsv4_two_frame_loop(
7592 &mut state,
7593 layers,
7594 g,
7595 cfg,
7596 st,
7597 token_id,
7598 inv_freq,
7599 pool,
7600 &mut scratch,
7601 );
7602 #[cfg(not(feature = "gpu"))]
7603 let two_frame_done = false;
7604 if !two_frame_done {
7605 for (li, l) in layers.iter().enumerate() {
7606 if layer_frames {
7607 break;
7608 }
7609 hc_block(
7611 &mut state,
7612 &l.hc_attn_fn,
7613 &l.hc_attn_scale,
7614 &l.hc_attn_base,
7615 &l.attn_norm,
7616 cfg,
7617 &mut scratch,
7618 pool,
7619 |folded, out| {
7620 if dump_path().is_some() {
7621 BODY.with(|b| b.borrow_mut().push(vec_json(folded)));
7624 }
7625 let freqs = if l.compressor.is_some() {
7627 &g.inv_freq_compress
7628 } else {
7629 &g.inv_freq_window
7630 };
7631 let freqs = if freqs.is_empty() {
7632 inv_freq
7633 } else {
7634 freqs.as_slice()
7635 };
7636 attention_step(folded, l, cfg, st, li, freqs, pool, None, out);
7637 if dump_path().is_some() {
7638 BODY.with(|b| b.borrow_mut().push(vec_json(out)));
7639 }
7640 },
7641 );
7642 if dump_path().is_some() {
7643 dump.push(format!(
7646 "{}{}",
7647 if li == 0 { "" } else { "," },
7648 vec_json(&state)
7649 ));
7650 }
7651 let _t_hc2 = prof::on().then(std::time::Instant::now);
7653 hc_block(
7654 &mut state,
7655 &l.hc_ffn_fn,
7656 &l.hc_ffn_scale,
7657 &l.hc_ffn_base,
7658 &l.ffn_norm,
7659 cfg,
7660 &mut scratch,
7661 pool,
7662 |folded, out| moe_step(folded, l, cfg, token_id, li, pool, out),
7663 );
7664 if let Some(t) = _t_hc2 {
7665 prof::HC_NS.fetch_add(
7668 t.elapsed().as_nanos() as u64,
7669 std::sync::atomic::Ordering::Relaxed,
7670 );
7671 }
7672 if dump_path().is_some() {
7673 dump.push(format!(",{}", vec_json(&state)));
7674 }
7675 if trace_on() && (st.pos % 64 == 0 || st.pos == 199) {
7676 eprintln!(
7677 "[dsv4] caches of layer {li}: window={} compressed={} index={} (ratio={:?})",
7678 st.window[li].len() / cfg.head_dim.max(1),
7679 st.compressed[li].len() / cfg.head_dim.max(1),
7680 st.index_kv[li].len().max(1) / 128,
7681 l.compressor.as_ref().map(|c| (c.ratio, c.overlap)),
7682 );
7683 }
7684 if trace_on() {
7685 let bad = state.iter().filter(|v| !v.is_finite()).count();
7686 eprintln!(
7687 "[dsv4] layer {li:>2}: rms={:.5}{}",
7688 rms_of(&state),
7689 if bad > 0 {
7690 format!(" NON-FINITE x{bad}")
7691 } else {
7692 String::new()
7693 }
7694 );
7695 }
7696 dspark_note(li, &state, cfg);
7697 }
7698 }
7699 st.pos += 1;
7700
7701 let mut h = vec![0.0f32; dim];
7703 hc_head_fold(
7704 &state,
7705 &g.hc_head_fn,
7706 g.hc_head_scale,
7707 &g.hc_head_base,
7708 cfg,
7709 pool,
7710 &mut h,
7711 );
7712 if !want_logits {
7713 logits.clear();
7714 return;
7715 }
7716 let _t_head = prof::on().then(std::time::Instant::now);
7717 rms_weighted(&mut h, &g.norm, cfg.norm_eps);
7718 logits.clear();
7719 logits.resize(g.head.rows(), 0.0);
7720 g.head.matvec(&h, logits, pool);
7721 if let Some(t) = _t_head {
7722 prof::HEAD_NS.fetch_add(
7723 t.elapsed().as_nanos() as u64,
7724 std::sync::atomic::Ordering::Relaxed,
7725 );
7726 }
7727 if dump_path().is_some() {
7728 dump.push("]".into());
7729 let picked = PICKED.with(|p| {
7730 p.borrow()
7731 .iter()
7732 .map(|v| {
7733 format!(
7734 "[{}]",
7735 v.iter()
7736 .map(|e| e.to_string())
7737 .collect::<Vec<_>>()
7738 .join(",")
7739 )
7740 })
7741 .collect::<Vec<_>>()
7742 .join(",")
7743 });
7744 dump.push(format!(",\"experts\":[{picked}]"));
7745 let body = BODY.with(|b| b.borrow().join(","));
7746 dump.push(format!(",\"attn_io\":[{body}]"));
7747 dump_line(&format!(
7748 "{{\"tok\":{token_id},\"pos\":{},{},\"head\":{},\"logits\":{}}}",
7749 st.pos - 1,
7750 dump.join(""),
7751 vec_json(&h),
7752 vec_json(logits)
7753 ));
7754 }
7755 if trace_on() {
7756 let (mut top, mut best) = (0usize, f32::NEG_INFINITY);
7757 for (i, &v) in logits.iter().enumerate() {
7758 if v > best {
7759 best = v;
7760 top = i;
7761 }
7762 }
7763 let lo = logits.iter().cloned().fold(f32::MAX, f32::min);
7764 eprintln!(
7765 "[dsv4] head: rms={:.5} logits[{}..{:.3}] argmax={top}",
7766 rms_of(&h),
7767 format_args!("{lo:.3}"),
7768 best
7769 );
7770 }
7771}
7772
7773pub fn load(
7780 model: &std::sync::Arc<cortiq_core::CmfModel>,
7781 cfg: &Dsv4Cfg,
7782 n_layers: usize,
7783) -> Result<(Dsv4Globals, Vec<Dsv4Layer>), String> {
7784 let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
7785 crate::qtensor::QTensor::from_model(model, name)
7786 };
7787 let f = |name: &str| -> Result<Vec<f32>, String> {
7791 crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
7792 };
7793
7794 let rope_of = |base: f32, yarn: bool| -> Vec<f32> {
7799 if yarn {
7800 crate::attention::yarn_inv_freq(cfg.rope_head_dim, base, 16.0, 65536, 32.0, 1.0)
7801 } else {
7802 crate::attention::rope_inv_freq(cfg.rope_head_dim, base)
7803 }
7804 };
7805 let globals = Dsv4Globals {
7806 inv_freq_compress: rope_of(160_000.0, true),
7807 inv_freq_window: rope_of(10_000.0, false),
7808 embed: q("model.embed_tokens.weight")?,
7809 norm: f("model.norm.weight")?,
7810 head: q("lm_head.weight")?,
7811 hc_head_fn: f("model.hc_head_fn")?,
7812 hc_head_base: f("model.hc_head_base")?,
7813 hc_head_scale: *f("model.hc_head_scale")?
7814 .first()
7815 .ok_or("dsv4: empty hc_head_scale")?,
7816 };
7817
7818 let mut layers = Vec::with_capacity(n_layers);
7819 for li in 0..n_layers {
7820 layers.push(load_layer(
7821 model,
7822 cfg,
7823 &format!("model.layers.{li}"),
7824 Scheme::Main,
7825 )?);
7826 }
7827 #[cfg(feature = "gpu")]
7833 if crate::gpu_wgpu::host_banks_on() {
7834 let sets: Vec<(usize, Vec<(usize, usize, usize)>, bool)> = layers
7836 .iter()
7837 .filter_map(|l| {
7838 let idx3 = |e: &Dsv4Expert| {
7839 Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
7840 };
7841 let mut v: Vec<_> = l.experts.iter().filter_map(idx3).collect();
7842 let first = v.first()?.0;
7843 v.push(idx3(&l.shared)?);
7844 let gu_q2 =
7845 l.experts.first()?.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
7846 Some((first, v, gu_q2))
7847 })
7848 .collect();
7849 let m2 = model.clone();
7850 let (inter, dim) = (cfg.moe_inter, cfg.dim);
7851 std::thread::spawn(move || {
7852 for (first, v, gu_q2) in sets {
7853 crate::gpu_wgpu::dsv4_host_bank_build(&m2, first, &v, inter, dim, gu_q2);
7854 }
7855 tracing::info!("host banks: built");
7856 });
7857 }
7858 #[cfg(feature = "gpu")]
7859 {
7860 let masks: Vec<Option<Vec<bool>>> = layers.iter().map(|l| l.mask.clone()).collect();
7861 crate::gpu_wgpu::prefetch_tier(model, &|name: &str| {
7862 let Some(i) = name.find(".experts.") else {
7863 return false;
7864 };
7865 let rest = &name[i + 9..];
7866 let e: usize = match rest[..rest.find('.').unwrap_or(rest.len())].parse() {
7867 Ok(v) => v,
7868 Err(_) => return false,
7869 };
7870 let li: usize = {
7871 let Some(j) = name.find("layers.") else {
7872 return false;
7873 };
7874 let r = &name[j + 7..];
7875 match r[..r.find('.').unwrap_or(r.len())].parse() {
7876 Ok(v) => v,
7877 Err(_) => return false,
7878 }
7879 };
7880 match masks.get(li).and_then(|m| m.as_ref()) {
7881 Some(m) => m.get(e).copied().unwrap_or(false),
7882 None => true,
7883 }
7884 });
7885 }
7886 Ok((globals, layers))
7887}
7888
7889#[derive(Clone, Copy, PartialEq, Eq, Debug)]
7896pub enum Scheme {
7897 Main,
7898 Mtp,
7899}
7900
7901impl Scheme {
7902 fn attn(self) -> &'static str {
7903 match self {
7904 Scheme::Main => "self_attn",
7905 Scheme::Mtp => "attn",
7906 }
7907 }
7908 fn attn_norm(self) -> &'static str {
7909 match self {
7910 Scheme::Main => "input_layernorm.weight",
7911 Scheme::Mtp => "attn_norm.weight",
7912 }
7913 }
7914 fn ffn_norm(self) -> &'static str {
7915 match self {
7916 Scheme::Main => "post_attention_layernorm.weight",
7917 Scheme::Mtp => "ffn_norm.weight",
7918 }
7919 }
7920 fn mlp(self) -> &'static str {
7921 match self {
7922 Scheme::Main => "mlp",
7923 Scheme::Mtp => "ffn",
7924 }
7925 }
7926 fn gate_bias(self) -> &'static str {
7929 match self {
7930 Scheme::Main => "expert_bias",
7931 Scheme::Mtp => "gate.bias",
7932 }
7933 }
7934 fn shared(self) -> &'static str {
7935 match self {
7936 Scheme::Main => "shared_expert",
7937 Scheme::Mtp => "shared_experts",
7938 }
7939 }
7940 fn w(self, i: u8) -> &'static str {
7942 match (self, i) {
7943 (Scheme::Main, 1) => "gate_proj.weight",
7944 (Scheme::Main, 2) => "down_proj.weight",
7945 (Scheme::Main, _) => "up_proj.weight",
7946 (Scheme::Mtp, 1) => "w1.weight",
7947 (Scheme::Mtp, 2) => "w2.weight",
7948 (Scheme::Mtp, _) => "w3.weight",
7949 }
7950 }
7951}
7952
7953pub fn load_layer(
7955 model: &std::sync::Arc<cortiq_core::CmfModel>,
7956 cfg: &Dsv4Cfg,
7957 p: &str,
7958 s: Scheme,
7959) -> Result<Dsv4Layer, String> {
7960 let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
7961 crate::qtensor::QTensor::from_model(model, name)
7962 };
7963 let f = |name: &str| -> Result<Vec<f32>, String> {
7964 crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
7965 };
7966 let opt_f = |name: &str| -> Option<Vec<f32>> { f(name).ok() };
7967 let at = s.attn();
7968 let ml = s.mlp();
7969 {
7970 let scale3 = |name: &str| -> Result<[f32; 3], String> {
7971 let v = f(name)?;
7972 if v.len() < 3 {
7973 return Err(format!("{name}: expected 3 scales, got {}", v.len()));
7974 }
7975 Ok([v[0], v[1], v[2]])
7976 };
7977 let compressor = match q(&format!("{p}.{at}.compressor.wkv.weight")) {
7980 Ok(wkv) => {
7981 let ape = f(&format!("{p}.{at}.compressor.ape"))?;
7982 let width = wkv.rows();
7985 let ratio = (ape.len() / width.max(1)).max(1);
7986 Some(Dsv4Compressor {
7987 wkv,
7988 wgate: q(&format!("{p}.{at}.compressor.wgate.weight"))?,
7989 norm: f(&format!("{p}.{at}.compressor.norm.weight"))?,
7990 ape,
7991 ratio,
7992 overlap: ratio == 4,
7993 })
7994 }
7995 Err(_) => None,
7996 };
7997 let indexer = match q(&format!("{p}.{at}.indexer.wq_b.weight")) {
7998 Ok(wq_b) => {
7999 let ape = f(&format!("{p}.{at}.indexer.compressor.ape"))?;
8000 let cwkv = q(&format!("{p}.{at}.indexer.compressor.wkv.weight"))?;
8001 let width = cwkv.rows();
8002 let ratio = (ape.len() / width.max(1)).max(1);
8003 Some(Dsv4Indexer {
8004 wq_b,
8005 weights_proj: q(&format!("{p}.{at}.indexer.weights_proj.weight"))?,
8006 compressor: Dsv4Compressor {
8007 wkv: cwkv,
8008 wgate: q(&format!("{p}.{at}.indexer.compressor.wgate.weight"))?,
8009 norm: f(&format!("{p}.{at}.indexer.compressor.norm.weight"))?,
8010 ape,
8011 ratio,
8012 overlap: ratio == 4,
8013 },
8014 })
8015 }
8016 Err(_) => None,
8017 };
8018
8019 let mut experts = Vec::with_capacity(cfg.n_routed_experts);
8020 for e in 0..cfg.n_routed_experts {
8021 let ep = format!("{p}.{ml}.experts.{e}");
8022 experts.push(Dsv4Expert {
8023 w1: q(&format!("{ep}.{w}", w = s.w(1)))?,
8024 w2: q(&format!("{ep}.{w}", w = s.w(2)))?,
8025 w3: q(&format!("{ep}.{w}", w = s.w(3)))?,
8026 });
8027 }
8028
8029 Ok(Dsv4Layer {
8030 attn_norm: f(&format!("{p}.{an}", an = s.attn_norm()))?,
8031 ffn_norm: f(&format!("{p}.{fnm}", fnm = s.ffn_norm()))?,
8032 wq_a: q(&format!("{p}.{at}.wq_a.weight"))?,
8033 q_norm: f(&format!("{p}.{at}.q_norm.weight"))?,
8034 wq_b: q(&format!("{p}.{at}.wq_b.weight"))?,
8035 wkv: q(&format!("{p}.{at}.wkv.weight"))?,
8036 kv_norm: f(&format!("{p}.{at}.kv_norm.weight"))?,
8037 wo_a: q(&format!("{p}.{at}.wo_a.weight"))?,
8038 wo_b: q(&format!("{p}.{at}.wo_b.weight"))?,
8039 attn_sink: f(&format!("{p}.{at}.attn_sink"))?,
8040 compressor,
8041 indexer,
8042 hc_attn_fn: f(&format!("{p}.hc_attn_fn"))?,
8043 hc_attn_base: f(&format!("{p}.hc_attn_base"))?,
8044 hc_attn_scale: scale3(&format!("{p}.hc_attn_scale"))?,
8045 hc_ffn_fn: f(&format!("{p}.hc_ffn_fn"))?,
8046 hc_ffn_base: f(&format!("{p}.hc_ffn_base"))?,
8047 hc_ffn_scale: scale3(&format!("{p}.hc_ffn_scale"))?,
8048 gate: q(&format!("{p}.{ml}.gate.weight"))?,
8049 gate_bias: opt_f(&format!("{p}.{ml}.{b}", b = s.gate_bias())),
8052 tid2eid: opt_f(&format!("{p}.{ml}.tid2eid")),
8053 experts,
8054 mask: if model.tensor(&format!("{p}.{ml}.tid2eid")).is_some() {
8055 None
8056 } else {
8057 crate::loader::moe_task_mask(model, &format!("{p}."), cfg.n_routed_experts)
8058 },
8059 shared: Dsv4Expert {
8060 w1: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(1)))?,
8061 w2: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(2)))?,
8062 w3: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(3)))?,
8063 },
8064 })
8065 }
8066}
8067
8068pub struct Dsv4Mtp {
8077 pub layer: Dsv4Layer,
8078 pub main_proj: Option<crate::qtensor::QTensor>,
8082 pub main_norm: Option<Vec<f32>>,
8083 pub norm: Option<Vec<f32>>,
8085 pub hc_head_fn: Option<Vec<f32>>,
8086 pub hc_head_base: Option<Vec<f32>>,
8087 pub hc_head_scale: Option<f32>,
8088 pub confidence: Option<crate::qtensor::QTensor>,
8089 pub markov_w1: Option<crate::qtensor::QTensor>,
8094 pub markov_w2: Option<crate::qtensor::QTensor>,
8095}
8096
8097pub fn load_mtp(
8101 model: &std::sync::Arc<cortiq_core::CmfModel>,
8102 cfg: &Dsv4Cfg,
8103 max_depth: usize,
8104) -> Vec<Dsv4Mtp> {
8105 let f = |name: &str| -> Option<Vec<f32>> {
8106 crate::loader::load_f32(model, name, &crate::loader::Overlay::None).ok()
8107 };
8108 let mut out = Vec::new();
8109 for d in 0..max_depth {
8110 let p = format!("model.mtp.{d}");
8111 if model.tensor(&format!("{p}.attn.wq_a.weight")).is_none() {
8115 break;
8116 }
8117 let layer = match load_layer(model, cfg, &p, Scheme::Mtp) {
8118 Ok(l) => l,
8119 Err(e) => {
8120 eprintln!("MTP {d}: skipped, {e}");
8121 break;
8122 }
8123 };
8124 out.push(Dsv4Mtp {
8125 layer,
8126 main_proj: crate::qtensor::QTensor::from_model(model, &format!("{p}.main_proj.weight"))
8127 .ok(),
8128 main_norm: f(&format!("{p}.main_norm.weight")),
8129 norm: f(&format!("{p}.norm.weight")),
8130 hc_head_fn: f(&format!("{p}.hc_head_fn")),
8131 hc_head_base: f(&format!("{p}.hc_head_base")),
8132 hc_head_scale: f(&format!("{p}.hc_head_scale")).and_then(|v| v.first().copied()),
8133 confidence: crate::qtensor::QTensor::from_model(
8134 model,
8135 &format!("{p}.confidence_head.proj.weight"),
8136 )
8137 .ok(),
8138 markov_w1: crate::qtensor::QTensor::from_model(
8139 model,
8140 &format!("{p}.markov_head.markov_w1.weight"),
8141 )
8142 .ok(),
8143 markov_w2: crate::qtensor::QTensor::from_model(
8144 model,
8145 &format!("{p}.markov_head.markov_w2.weight"),
8146 )
8147 .ok(),
8148 });
8149 }
8150 dspark_apply_mask(&mut out);
8151 if !out.is_empty() {
8152 let mp = out
8153 .iter()
8154 .find_map(|m| m.main_proj.as_ref())
8155 .map(|t| format!("[{}, {}]", t.rows(), t.cols()))
8156 .unwrap_or_else(|| "no".into());
8157 eprintln!(
8158 "MTP: {} stage(s), main_proj {mp}, experts {}, \
8159 confidence head {}, markov {}",
8160 out.len(),
8161 out[0].layer.experts.len(),
8162 if out.iter().any(|m| m.confidence.is_some()) {
8163 "yes"
8164 } else {
8165 "no"
8166 },
8167 if out.iter().any(|m| m.markov_w1.is_some()) {
8168 "yes"
8169 } else {
8170 "no"
8171 },
8172 );
8173 }
8174 out
8175}
8176
8177#[cfg(test)]
8178mod tests {
8179 use super::*;
8180
8181 fn toy() -> (Dsv4Globals, Vec<Dsv4Layer>, Dsv4Cfg) {
8186 use crate::qtensor::QTensor;
8187 let cfg = Dsv4Cfg {
8188 dim: 32,
8189 n_heads: 4,
8190 head_dim: 8,
8191 rope_head_dim: 4,
8192 q_lora_rank: 16,
8193 o_lora_rank: 16,
8194 o_groups: 2,
8195 hc_mult: 4,
8196 hc_sinkhorn_iters: 20,
8197 hc_eps: 1e-6,
8198 norm_eps: 1e-6,
8199 n_routed_experts: 8,
8200 top_k: 2,
8201 moe_inter: 16,
8202 route_scale: 1.0,
8203 swiglu_limit: 10.0,
8204 window: 6,
8205 index_topk: 8,
8206 vocab: 24,
8207 };
8208 let w = |n: usize, seed: usize| -> Vec<f32> {
8211 (0..n)
8212 .map(|i| (((i * 7 + seed * 13) % 101) as f32 / 101.0 - 0.5) * 0.3)
8213 .collect()
8214 };
8215 let t = |rows: usize, cols: usize, seed: usize| {
8216 QTensor::from_f32(w(rows * cols, seed), rows, cols)
8217 };
8218 let ones = |n: usize| vec![1.0f32; n];
8219
8220 let (dim, hc) = (cfg.dim, cfg.hc_mult);
8221 let q_width = cfg.n_heads * cfg.head_dim;
8224 let kv_width = cfg.head_dim;
8225 let o_per_group = q_width / cfg.o_groups;
8226 let mut layers = Vec::new();
8227 for li in 0..2 {
8228 let experts: Vec<Dsv4Expert> = (0..cfg.n_routed_experts)
8229 .map(|e| Dsv4Expert {
8230 w1: t(cfg.moe_inter, dim, 40 + e + li * 8),
8231 w2: t(dim, cfg.moe_inter, 60 + e + li * 8),
8232 w3: t(cfg.moe_inter, dim, 80 + e + li * 8),
8233 })
8234 .collect();
8235 layers.push(Dsv4Layer {
8238 attn_norm: ones(dim),
8239 ffn_norm: ones(dim),
8240 wq_a: t(cfg.q_lora_rank, dim, 1 + li),
8241 q_norm: ones(cfg.q_lora_rank),
8242 wq_b: t(q_width, cfg.q_lora_rank, 3 + li),
8243 wkv: t(kv_width, dim, 5 + li),
8244 kv_norm: ones(kv_width),
8245 wo_a: t(cfg.o_groups * cfg.o_lora_rank, o_per_group, 7 + li),
8246 wo_b: t(dim, cfg.o_groups * cfg.o_lora_rank, 9 + li),
8247 attn_sink: vec![0.1; cfg.n_heads],
8248 compressor: if li == 1 {
8251 Some(Dsv4Compressor {
8252 wkv: t(2 * kv_width, dim, 11),
8253 wgate: t(2 * kv_width, dim, 13),
8254 norm: ones(kv_width),
8255 ape: vec![0.01; 4 * 2 * kv_width],
8256 ratio: 4,
8257 overlap: true,
8258 })
8259 } else {
8260 None
8261 },
8262 indexer: if li == 1 {
8263 Some(Dsv4Indexer {
8264 wq_b: t(2 * 16, cfg.q_lora_rank, 41),
8265 weights_proj: t(2, dim, 43),
8266 compressor: Dsv4Compressor {
8267 wkv: t(2 * 16, dim, 45),
8268 wgate: t(2 * 16, dim, 47),
8269 norm: ones(16),
8270 ape: vec![0.01; 4 * 2 * 16],
8271 ratio: 4,
8272 overlap: true,
8273 },
8274 })
8275 } else {
8276 None
8277 },
8278 hc_attn_fn: w((2 + hc) * hc * hc * dim, 15 + li),
8279 hc_attn_base: w((2 + hc) * hc, 17 + li),
8280 hc_attn_scale: [1.0, 1.0, 1.0],
8281 hc_ffn_fn: w((2 + hc) * hc * hc * dim, 19 + li),
8282 hc_ffn_base: w((2 + hc) * hc, 21 + li),
8283 hc_ffn_scale: [1.0, 1.0, 1.0],
8284 gate: t(cfg.n_routed_experts, dim, 23 + li),
8285 gate_bias: if li == 1 {
8286 Some(vec![0.0; cfg.n_routed_experts])
8287 } else {
8288 None
8289 },
8290 tid2eid: if li == 0 {
8291 Some(
8292 (0..cfg.vocab * cfg.top_k)
8293 .map(|i| (i % cfg.n_routed_experts) as f32)
8294 .collect(),
8295 )
8296 } else {
8297 None
8298 },
8299 experts,
8300 mask: None,
8301 shared: Dsv4Expert {
8302 w1: t(cfg.moe_inter, dim, 25 + li),
8303 w2: t(dim, cfg.moe_inter, 27 + li),
8304 w3: t(cfg.moe_inter, dim, 29 + li),
8305 },
8306 });
8307 }
8308 let inv = |base: f32| -> Vec<f32> {
8309 (0..cfg.rope_head_dim / 2)
8310 .map(|i| 1.0 / base.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
8311 .collect()
8312 };
8313 let g = Dsv4Globals {
8314 inv_freq_compress: inv(160000.0),
8315 inv_freq_window: inv(10000.0),
8316 embed: t(cfg.vocab, dim, 31),
8317 norm: ones(dim),
8318 head: t(cfg.vocab, dim, 33),
8319 hc_head_fn: w(hc * hc * dim, 35),
8320 hc_head_base: w(hc, 37),
8321 hc_head_scale: 1.0,
8322 };
8323 (g, layers, cfg)
8324 }
8325
8326 #[test]
8331 fn forward_token_decodes_a_sequence_without_falling_over() {
8332 let (g, layers, cfg) = toy();
8333 let mut st = Dsv4State::new(layers.len());
8334 let inv_freq: Vec<f32> = (0..cfg.rope_head_dim / 2)
8335 .map(|i| 1.0 / 10000f32.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
8336 .collect();
8337 let mut logits = Vec::new();
8338
8339 let mut first: Option<Vec<f32>> = None;
8342 for (step, tok) in [3u32, 7, 1, 9, 4, 2, 8, 5, 6, 0].into_iter().enumerate() {
8343 forward_token(
8344 &g,
8345 &layers,
8346 &cfg,
8347 &mut st,
8348 tok,
8349 &inv_freq,
8350 None,
8351 &mut logits,
8352 );
8353 assert_eq!(logits.len(), cfg.vocab, "step {step}: logit count");
8354 assert!(
8355 logits.iter().all(|v| v.is_finite()),
8356 "step {step}: non-finite logit — {logits:?}"
8357 );
8358 let spread = logits.iter().cloned().fold(f32::MIN, f32::max)
8361 - logits.iter().cloned().fold(f32::MAX, f32::min);
8362 assert!(spread > 1e-6, "step {step}: logits are flat ({spread})");
8363 if step == 0 {
8364 first = Some(logits.clone());
8365 }
8366 assert_eq!(st.pos, step + 1, "position bookkeeping");
8367 }
8368
8369 assert!(!st.window[0].is_empty(), "sliding window never filled");
8372 for (li, w) in st.window.iter().enumerate() {
8374 assert!(
8375 w.len() / cfg.head_dim <= cfg.window,
8376 "layer {li}: window holds {} positions, cap is {}",
8377 w.len() / cfg.head_dim,
8378 cfg.window
8379 );
8380 }
8381 assert!(
8382 !st.compressed[1].is_empty(),
8383 "compressor layer produced no compressed KV in 10 tokens"
8384 );
8385 assert_eq!(
8389 st.compressed[1].len() / cfg.head_dim,
8390 2,
8391 "expected two folds in ten tokens at ratio 4"
8392 );
8393 assert!(
8394 !st.prev_kv[1].is_empty(),
8395 "the overlapping compressor never kept a previous window"
8396 );
8397 for (li, l) in layers.iter().enumerate() {
8402 if l.indexer.is_some() {
8403 assert!(
8404 !st.index_kv[li].is_empty(),
8405 "layer {li} has an indexer but its cache stayed empty"
8406 );
8407 }
8408 }
8409
8410 let mut fresh = Dsv4State::new(layers.len());
8413 let mut relogits = Vec::new();
8414 forward_token(
8415 &g,
8416 &layers,
8417 &cfg,
8418 &mut fresh,
8419 3,
8420 &inv_freq,
8421 None,
8422 &mut relogits,
8423 );
8424 assert_eq!(
8425 relogits,
8426 first.unwrap(),
8427 "the same token from a fresh state must reproduce exactly"
8428 );
8429 }
8430
8431 #[test]
8435 fn swiglu_limit_clamps_up_both_ways_and_gate_only_from_above() {
8436 let inter = 4;
8437 let gate_src = [-50.0f32, 50.0, 1.0, -1.0];
8439 let up_src = [50.0f32, -50.0, 1.0, -1.0];
8440 let limit = 10.0f32;
8441 let mut got = vec![0.0f32; inter];
8442 expert_swiglu(
8443 &[0.0],
8444 &|_, d| d.copy_from_slice(&gate_src),
8445 &|_, d| d.copy_from_slice(&up_src),
8446 &|src, d| d.copy_from_slice(src),
8447 inter,
8448 1.0,
8449 limit,
8450 &mut got,
8451 );
8452 let silu = |g: f32| g / (1.0 + (-g).exp());
8453 let want = [
8455 silu(-50.0) * limit,
8456 silu(limit) * -limit,
8457 silu(1.0) * 1.0,
8458 -silu(-1.0),
8459 ];
8460 for (i, w) in want.iter().enumerate() {
8461 assert!(
8462 (got[i] - w).abs() < 1e-5,
8463 "lane {i}: got {} want {w}",
8464 got[i]
8465 );
8466 }
8467 let mut raw = vec![0.0f32; inter];
8469 expert_swiglu(
8470 &[0.0],
8471 &|_, d| d.copy_from_slice(&gate_src),
8472 &|_, d| d.copy_from_slice(&up_src),
8473 &|src, d| d.copy_from_slice(src),
8474 inter,
8475 1.0,
8476 0.0,
8477 &mut raw,
8478 );
8479 assert!(
8480 (raw[1] - silu(50.0) * -50.0).abs() < 1e-3,
8481 "limit 0 must not clamp"
8482 );
8483 }
8484
8485 #[test]
8490 fn grouped_projection_is_identical_with_and_without_a_pool() {
8491 let (groups, lora, per_group, dim) = (4usize, 128usize, 64usize, 32usize);
8492 let attn: Vec<f32> = (0..groups * per_group)
8493 .map(|i| ((i * 13) as f32 * 0.021).sin())
8494 .collect();
8495 let wo_a: Vec<f32> = (0..groups * lora * per_group)
8496 .map(|i| ((i * 7) as f32 * 0.011).cos())
8497 .collect();
8498 let wo_b: Vec<f32> = (0..dim * groups * lora)
8499 .map(|i| ((i * 5) as f32 * 0.009).sin())
8500 .collect();
8501 let row = |r: usize, x: &[f32], _sc: &mut [f32]| -> f32 {
8502 wo_a[r * per_group..(r + 1) * per_group]
8503 .iter()
8504 .zip(x)
8505 .map(|(a, b)| a * b)
8506 .sum()
8507 };
8508 let project = |mid: &[f32], dst: &mut [f32]| {
8509 for (d, o) in dst.iter_mut().enumerate() {
8510 *o = wo_b[d * mid.len()..(d + 1) * mid.len()]
8511 .iter()
8512 .zip(mid)
8513 .map(|(a, b)| a * b)
8514 .sum();
8515 }
8516 };
8517
8518 let mut serial = vec![0.0f32; dim];
8519 o_project(
8520 &attn,
8521 &row,
8522 per_group,
8523 &project,
8524 groups,
8525 lora,
8526 None,
8527 &mut serial,
8528 );
8529
8530 let pool = crate::pool::Pool::new(4);
8531 let mut pooled = vec![0.0f32; dim];
8532 o_project(
8533 &attn,
8534 &row,
8535 per_group,
8536 &project,
8537 groups,
8538 lora,
8539 Some(&pool),
8540 &mut pooled,
8541 );
8542 assert_eq!(serial, pooled, "the pooled projection diverged");
8543 assert!(
8544 serial.iter().any(|v| v.abs() > 1e-6),
8545 "test data is degenerate"
8546 );
8547 }
8548
8549 #[test]
8550 fn block_grouped_projection_matches_position_walk() {
8551 let (_g, layers, cfg) = toy();
8552 let l = &layers[1];
8553 let b = 5;
8554 let attn_len = cfg.n_heads * cfg.head_dim;
8555 let attn: Vec<f32> = (0..b * attn_len)
8556 .map(|i| ((i * 17) as f32 * 0.013).sin())
8557 .collect();
8558 let mut walked = vec![0.0f32; b * cfg.dim];
8559 for bi in 0..b {
8560 o_project(
8561 &attn[bi * attn_len..(bi + 1) * attn_len],
8562 &|r, x, sc| l.wo_a.row_dot(r, x, sc),
8563 l.wo_a.cols(),
8564 &|mid, dst| l.wo_b.matvec(mid, dst, None),
8565 cfg.o_groups,
8566 cfg.o_lora_rank,
8567 None,
8568 &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
8569 );
8570 }
8571 let mut batched = vec![0.0f32; b * cfg.dim];
8572 o_project_block(
8573 &attn,
8574 b,
8575 &l.wo_a,
8576 &l.wo_b,
8577 cfg.o_groups,
8578 cfg.o_lora_rank,
8579 None,
8580 &mut batched,
8581 );
8582 assert_eq!(batched, walked);
8583 }
8584
8585 #[test]
8586 fn block_moe_matches_position_walk_in_route_order() {
8587 let (_g, layers, cfg) = toy();
8588 let l = &layers[1];
8591 let b = 5;
8592 let xs: Vec<f32> = (0..b * cfg.dim)
8593 .map(|i| ((i * 11) as f32 * 0.019).cos())
8594 .collect();
8595 let ids = [1u32, 2, 3, 4, 5];
8596 let mut walked = vec![0.0f32; b * cfg.dim];
8597 for bi in 0..b {
8598 moe_step(
8599 &xs[bi * cfg.dim..(bi + 1) * cfg.dim],
8600 l,
8601 &cfg,
8602 ids[bi],
8603 1,
8604 None,
8605 &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
8606 );
8607 }
8608 let mut batched = vec![0.0f32; b * cfg.dim];
8609 moe_step_block(&xs, b, l, &cfg, &ids, 1, None, &mut batched);
8610 assert_eq!(batched, walked);
8611 }
8612
8613 #[test]
8619 fn overlapping_compressor_folds_both_windows() {
8620 let (ratio, d) = (2usize, 3usize);
8621 let cur_kv: Vec<f32> = vec![
8624 1.0, 1.0, 1.0, 10.0, 20.0, 30.0, 2.0, 2.0, 2.0, 40.0, 50.0, 60.0, ];
8627 let cur_sc: Vec<f32> = vec![
8629 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 0.0, 100.0, 100.0, 0.0,
8631 ];
8632 let prev_kv: Vec<f32> = vec![
8634 7.0, 8.0, 9.0, 0.0, 0.0, 0.0, 5.0, 6.0, 7.0, 0.0, 0.0, 0.0,
8636 ];
8637 let prev_sc = vec![0.0f32; ratio * 2 * d];
8638
8639 let mut out = vec![0.0f32; d];
8640 compress_window_overlap(&prev_kv, &prev_sc, &cur_kv, &cur_sc, ratio, d, &mut out);
8641 assert!((out[0] - 40.0).abs() < 1e-3, "dim0 = {}", out[0]);
8643 assert!((out[1] - 50.0).abs() < 1e-3, "dim1 = {}", out[1]);
8644 assert!((out[2] - 30.0).abs() < 1e-3, "dim2 = {}", out[2]);
8646
8647 let mut first = vec![0.0f32; d];
8650 compress_window_overlap(&[], &[], &cur_kv, &cur_sc, ratio, d, &mut first);
8651 assert!(
8652 first.iter().all(|v| v.is_finite()),
8653 "first window: {first:?}"
8654 );
8655 assert!((first[0] - 40.0).abs() < 1e-3, "first dim0 = {}", first[0]);
8656
8657 let mut both = vec![0.0f32; d];
8659 let strong_prev = vec![100.0f32; ratio * 2 * d];
8660 compress_window_overlap(
8661 &prev_kv,
8662 &strong_prev,
8663 &cur_kv,
8664 &cur_sc,
8665 ratio,
8666 d,
8667 &mut both,
8668 );
8669 assert!(
8670 (both[0] - 40.0).abs() > 1.0,
8671 "a scored previous window must move the fold, got {}",
8672 both[0]
8673 );
8674 }
8675
8676 #[test]
8682 fn sinkhorn_matches_the_reference_numbers() {
8683 let hc = 4;
8684 let mixes: Vec<f32> = (0..24).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
8685 let base: Vec<f32> = (0..24).map(|i| (i as f32 * 0.11).cos()).collect();
8686 let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
8687 hc_split_sinkhorn(
8688 &mixes,
8689 &[1.0, 1.0, 1.0],
8690 &base,
8691 hc,
8692 20,
8693 1e-6,
8694 &mut pre,
8695 &mut post,
8696 &mut comb,
8697 );
8698 let want_pre = [0.7310596, 0.8888268, 0.9525191, 0.97424865];
8699 let want_post = [1.9600224, 1.9534285, 1.9201256, 1.8160983];
8700 let want_comb = [
8701 0.5996052,
8702 0.282_535_9,
8703 0.09218107,
8704 0.025676856,
8705 0.17564717,
8706 0.22228767,
8707 0.271_745_4,
8708 0.330_318_8,
8709 0.029528176,
8710 0.12206022,
8711 0.32619134,
8712 0.5222193,
8713 0.19521846,
8714 0.37311527,
8715 0.30988118,
8716 0.12178412,
8717 ];
8718 for (i, w) in want_pre.iter().enumerate() {
8719 assert!((pre[i] - w).abs() < 1e-5, "pre[{i}]: {} vs {w}", pre[i]);
8720 }
8721 for (i, w) in want_post.iter().enumerate() {
8722 assert!((post[i] - w).abs() < 1e-5, "post[{i}]: {} vs {w}", post[i]);
8723 }
8724 for (i, w) in want_comb.iter().enumerate() {
8725 assert!((comb[i] - w).abs() < 1e-4, "comb[{i}]: {} vs {w}", comb[i]);
8726 }
8727 }
8728
8729 #[test]
8734 fn sinkhorn_leaves_the_mixing_matrix_doubly_stochastic() {
8735 let hc = 4;
8736 let mix_hc = (2 + hc) * hc;
8737 let mixes: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
8739 let base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.11).cos()).collect();
8740 let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
8741 hc_split_sinkhorn(
8742 &mixes,
8743 &[1.0, 1.0, 1.0],
8744 &base,
8745 hc,
8746 20,
8747 1e-6,
8748 &mut pre,
8749 &mut post,
8750 &mut comb,
8751 );
8752 for j in 0..hc {
8753 let r: f32 = comb[j * hc..(j + 1) * hc].iter().sum();
8754 assert!((r - 1.0).abs() < 2e-3, "row {j} sums to {r}");
8755 let c: f32 = (0..hc).map(|k| comb[k * hc + j]).sum();
8756 assert!((c - 1.0).abs() < 2e-3, "col {j} sums to {c}");
8757 }
8758 assert!(pre.iter().all(|&v| v > 0.0 && v < 1.001));
8760 assert!(post.iter().all(|&v| (0.0..=2.0).contains(&v)));
8761 }
8762
8763 #[test]
8767 fn expand_of_identical_copies_is_a_fixed_point() {
8768 let (hc, dim) = (4usize, 3usize);
8769 let residual: Vec<f32> = std::iter::repeat_n([1.5f32, -2.0, 0.25], hc)
8770 .flatten()
8771 .collect();
8772 let comb = {
8773 vec![0.25f32; hc * hc]
8775 };
8776 let post = vec![0.0f32; hc];
8777 let mut out = vec![0.0f32; hc * dim];
8778 hc_expand(&[0.0; 3], &residual, &post, &comb, hc, dim, &mut out);
8779 for (o, r) in out.iter().zip(&residual) {
8780 assert!((o - r).abs() < 1e-6, "{o} vs {r}");
8781 }
8782 }
8783
8784 #[test]
8788 fn selection_bias_steers_the_choice_but_not_the_weights() {
8789 let scores = [3.0f32, 0.1, 2.0, 0.05];
8790 let bias = [0.0f32, 10.0, 0.0, 0.0];
8791 let (mut idx, mut w) = (Vec::new(), Vec::new());
8792 route(&scores, Some(&bias), 2, 1.5, None, None, &mut idx, &mut w);
8793 assert_eq!(idx[0], 1, "the biased expert must win selection");
8794 assert_eq!(idx[1], 0);
8795 assert!(w[0] < w[1], "biased expert kept its own (small) weight");
8798 let sum: f32 = w.iter().sum();
8799 assert!((sum - 1.5).abs() < 1e-5, "weights renormalize then scale");
8800 }
8801
8802 #[test]
8806 fn attention_sink_drains_weight_without_contributing_output() {
8807 let hd = 2;
8808 let q = [1.0f32, 0.0];
8809 let kv = [1.0f32, 0.0, 0.0, 1.0];
8810 let mut out = vec![0.0f32; hd];
8811 sparse_attend(&q, &kv, &[0, 1], f32::NEG_INFINITY, 1.0, hd, &mut out);
8812 let plain = out.clone();
8813 assert!(plain[0] > plain[1], "the aligned key must dominate");
8814 sparse_attend(&q, &kv, &[0, 1], 20.0, 1.0, hd, &mut out);
8815 assert!(
8816 out[0] < plain[0] * 0.01 && out[1] < plain[1] * 0.01,
8817 "a large sink must drain nearly all the mass: {out:?}"
8818 );
8819 }
8820
8821 #[test]
8824 fn masked_positions_leave_the_denominator_alone() {
8825 let hd = 2;
8826 let q = [1.0f32, 0.0];
8827 let kv = [1.0f32, 0.0, 0.0, 1.0];
8828 let (mut a, mut b) = (vec![0.0f32; hd], vec![0.0f32; hd]);
8829 sparse_attend(&q, &kv, &[0], f32::NEG_INFINITY, 1.0, hd, &mut a);
8830 sparse_attend(
8831 &q,
8832 &kv,
8833 &[0, usize::MAX],
8834 f32::NEG_INFINITY,
8835 1.0,
8836 hd,
8837 &mut b,
8838 );
8839 for (x, y) in a.iter().zip(&b) {
8840 assert!((x - y).abs() < 1e-6, "{x} vs {y}");
8841 }
8842 }
8843
8844 #[test]
8847 fn rope_tail_inverts_itself() {
8848 let inv_freq = [1.0f32, 0.5];
8849 let orig = [9.0f32, 8.0, 1.0, 2.0, 3.0, 4.0];
8850 let mut v = orig;
8851 rope_tail(&mut v, &inv_freq, 7, 4, false);
8852 assert!(v[..2] == orig[..2], "the non-rope head must not move");
8853 assert!(v[2..] != orig[2..], "the tail must actually rotate");
8854 rope_tail(&mut v, &inv_freq, 7, 4, true);
8855 for (a, b) in v.iter().zip(&orig) {
8856 assert!((a - b).abs() < 1e-5, "{a} vs {b}");
8857 }
8858 }
8859
8860 #[test]
8863 fn compressor_pools_the_window_per_dimension() {
8864 let (ratio, width) = (2usize, 2usize);
8865 let kv = [1.0f32, 10.0, 3.0, 20.0];
8866 let score = [0.0f32, 0.0, 0.0, 50.0];
8868 let ape = vec![0.0f32; ratio * width];
8869 let mut out = vec![0.0f32; width];
8870 compress_window(&kv, &score, &ape, ratio, width, &mut out);
8871 assert!(
8872 (out[0] - 2.0).abs() < 1e-5,
8873 "equal scores average: {}",
8874 out[0]
8875 );
8876 assert!(
8877 (out[1] - 20.0).abs() < 1e-3,
8878 "a dominant score wins: {}",
8879 out[1]
8880 );
8881 }
8882
8883 #[test]
8886 fn index_scores_relu_before_weighting() {
8887 let (nh, hd) = (2usize, 2usize);
8888 let q = [1.0f32, 0.0, -1.0, 0.0];
8890 let kv = [1.0f32, 0.0, 0.0, 1.0];
8891 let w = [1.0f32, 1.0];
8892 let mut sc = Vec::new();
8893 index_scores(&q, &kv, &w, nh, hd, 2, 2, None, &mut sc);
8894 assert!(sc[0] > 0.9, "abstention, not veto: {:?}", sc);
8896 }
8897
8898 #[test]
8899 fn index_scores_mask_the_future() {
8900 let (nh, hd) = (1usize, 2usize);
8901 let q = [1.0f32, 0.0];
8902 let kv = [1.0f32, 0.0, 1.0, 0.0, 1.0, 0.0];
8903 let w = [1.0f32];
8904 let mut sc = Vec::new();
8905 index_scores(&q, &kv, &w, nh, hd, 3, 2, None, &mut sc);
8906 assert!(sc[0].is_finite() && sc[1].is_finite());
8907 assert!(sc[2] == f32::NEG_INFINITY, "position 2 is in the future");
8908 let mut idx = Vec::new();
8909 top_k_positions(&sc, 3, &mut idx);
8910 assert_eq!(idx, vec![0, 1], "a masked slot never wins a slot");
8911 }
8912
8913 #[test]
8914 fn top_k_is_deterministic_on_ties() {
8915 let sc = [1.0f32, 1.0, 1.0, 0.0];
8916 let mut idx = Vec::new();
8917 top_k_positions(&sc, 2, &mut idx);
8918 assert_eq!(idx, vec![0, 1], "ties resolve to the lower index");
8919 }
8920
8921 #[test]
8925 fn hc_block_preserves_the_copy_structure_and_applies_the_block() {
8926 let cfg = Dsv4Cfg {
8927 dim: 4,
8928 n_heads: 1,
8929 head_dim: 4,
8930 rope_head_dim: 2,
8931 q_lora_rank: 4,
8932 o_lora_rank: 2,
8933 o_groups: 1,
8934 hc_mult: 4,
8935 hc_sinkhorn_iters: 20,
8936 hc_eps: 1e-6,
8937 norm_eps: 1e-6,
8938 n_routed_experts: 2,
8939 top_k: 1,
8940 moe_inter: 4,
8941 route_scale: 1.0,
8942 swiglu_limit: 10.0,
8943 window: 128,
8944 index_topk: 4,
8945 vocab: 8,
8946 };
8947 let (hc, dim) = (cfg.hc_mult, cfg.dim);
8948 let mix_hc = (2 + hc) * hc;
8949 let hc_fn: Vec<f32> = (0..mix_hc * hc * dim)
8950 .map(|i| ((i % 13) as f32 - 6.0) * 0.05)
8951 .collect();
8952 let hc_base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.2).sin()).collect();
8953 let norm_w = vec![1.0f32; dim];
8954 let mut state: Vec<f32> = (0..hc * dim).map(|i| (i as f32 * 0.3).cos()).collect();
8955 let before = state.clone();
8956 let mut scratch = HcScratch::new(&cfg);
8957 hc_block(
8958 &mut state,
8959 &hc_fn,
8960 &[1.0, 1.0, 1.0],
8961 &hc_base,
8962 &norm_w,
8963 &cfg,
8964 &mut scratch,
8965 None,
8966 |_folded, out: &mut [f32]| out.iter_mut().for_each(|o| *o = 1.0),
8967 );
8968 assert_eq!(state.len(), before.len(), "copy structure must survive");
8969 assert!(state.iter().all(|v| v.is_finite()), "{state:?}");
8970 assert!(
8971 state.iter().zip(&before).any(|(a, b)| (a - b).abs() > 1e-4),
8972 "the block's output has to reach the state"
8973 );
8974 }
8975
8976 #[test]
8977 fn hash_route_reads_the_table_row() {
8978 let table = [7.0f32, 9.0, 1.0, 2.0, 5.0, 6.0];
8980 assert_eq!(hash_route(&table, 3, 2, 0), vec![7, 9]);
8981 assert_eq!(hash_route(&table, 3, 2, 2), vec![5, 6]);
8982 assert_eq!(hash_route(&table, 3, 2, 99), vec![5, 6]);
8984 }
8985
8986 #[test]
8991 fn a_task_mask_restricts_selection_and_renormalizes() {
8992 let scores = [0.1f32, 4.0, 1.0, 9.0];
8994 let (mut idx, mut w) = (Vec::new(), Vec::new());
8995 route(&scores, None, 2, 1.0, None, None, &mut idx, &mut w);
8996 assert_eq!(idx, vec![3, 1], "unmasked: the two best win");
8997 let sum: f32 = w.iter().sum();
8998 assert!((sum - 1.0).abs() < 1e-5, "weights must sum to route_scale");
8999
9000 let mask = [true, false, true, true];
9003 let (mut i2, mut w2) = (Vec::new(), Vec::new());
9004 route(&scores, None, 2, 1.0, None, Some(&mask), &mut i2, &mut w2);
9005 assert_eq!(i2, vec![3, 2], "masked expert must not be selected");
9006 let sum2: f32 = w2.iter().sum();
9007 assert!((sum2 - 1.0).abs() < 1e-5, "masked weights must renormalize");
9008
9009 let tight = [false, false, false, true];
9011 let (mut i3, mut w3) = (Vec::new(), Vec::new());
9012 route(&scores, None, 2, 1.0, None, Some(&tight), &mut i3, &mut w3);
9013 assert_eq!(i3, vec![3]);
9014 assert_eq!(w3.len(), 1);
9015 }
9016
9017 #[test]
9022 fn hash_layers_weight_the_experts_the_table_names() {
9023 let scores = [0.1f32, 0.4, 0.2, 5.0];
9025 let table = vec![0.0f32, 1.0];
9026 let idx_forced = hash_route(&table, 1, 2, 0);
9027 assert_eq!(idx_forced, vec![0, 1]);
9028
9029 let (mut idx, mut w) = (Vec::new(), Vec::new());
9030 route(
9031 &scores,
9032 None,
9033 2,
9034 1.0,
9035 Some(&idx_forced),
9036 None,
9037 &mut idx,
9038 &mut w,
9039 );
9040 assert_eq!(idx, vec![0, 1], "the table must decide the experts");
9041
9042 let sp = |x: f32| (1.0 + x.exp()).ln().sqrt();
9044 let (s0, s1) = (sp(scores[0]), sp(scores[1]));
9045 let tot = s0 + s1;
9046 assert!(
9047 (w[0] - s0 / tot).abs() < 1e-6,
9048 "w[0]={} want {}",
9049 w[0],
9050 s0 / tot
9051 );
9052 assert!(
9053 (w[1] - s1 / tot).abs() < 1e-6,
9054 "w[1]={} want {}",
9055 w[1],
9056 s1 / tot
9057 );
9058
9059 let (mut idx2, mut w2) = (Vec::new(), Vec::new());
9061 route(&scores, None, 2, 1.0, None, None, &mut idx2, &mut w2);
9062 assert_eq!(idx2[0], 3, "without a table the highest score still wins");
9063 }
9064}
9065
9066pub const DSPARK_NOISE_TOKEN: u32 = 128799;
9083pub fn dspark_block() -> usize {
9091 5
9092}
9093
9094#[cfg(feature = "gpu")]
9101fn dspark_native_on() -> bool {
9102 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9103 *ON.get_or_init(|| {
9104 std::env::var("CMF_DSPARK_NATIVE")
9105 .map(|v| v != "0")
9106 .unwrap_or(true)
9109 })
9110}
9111
9112pub fn dspark_verify_k() -> usize {
9113 static K: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
9114 *K.get_or_init(|| {
9115 std::env::var("CMF_DSPARK_VERIFY_K")
9116 .ok()
9117 .and_then(|v| v.parse::<usize>().ok())
9118 .filter(|&n| (1..=DSPARK_BLOCK_MAX).contains(&n))
9119 .unwrap_or(DSPARK_BLOCK_MAX)
9120 })
9121}
9122
9123pub const DSPARK_BLOCK_MAX: usize = 5;
9125
9126pub struct DsparkState {
9129 pub win: Vec<Vec<f32>>,
9131 pub filled: Vec<usize>,
9133 pub main_hidden: Vec<f32>,
9135 pub have_hidden: bool,
9137}
9138
9139impl DsparkState {
9140 pub fn new(stages: usize, cfg: &Dsv4Cfg, targets: usize) -> Self {
9141 Self {
9142 win: vec![Vec::new(); stages],
9143 filled: vec![0; stages],
9144 main_hidden: vec![0.0; cfg.dim * targets],
9145 have_hidden: false,
9146 }
9147 }
9148}
9149
9150pub fn dspark_targets(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, n_layers: usize) -> Vec<usize> {
9156 let Some(mp) = mtp.iter().find_map(|m| m.main_proj.as_ref()) else {
9157 return Vec::new();
9158 };
9159 let n = (mp.cols() / cfg.dim.max(1)).clamp(1, n_layers);
9160 (n_layers - n..n_layers).collect()
9161}
9162
9163thread_local! {
9164 static DSPARK_CAP: std::cell::RefCell<(Vec<usize>, Vec<f32>, usize)> =
9170 const { std::cell::RefCell::new((Vec::new(), Vec::new(), 0)) };
9171}
9172
9173pub fn dspark_arm(targets: &[usize], dim: usize) {
9175 DSPARK_CAP.with(|c| {
9176 let mut c = c.borrow_mut();
9177 c.0 = targets.to_vec();
9178 c.1 = vec![0.0; dim * targets.len()];
9179 c.2 = 0;
9180 });
9181}
9182
9183fn dspark_wants(li: usize) -> bool {
9187 DSPARK_CAP.with(|c| c.borrow().0.contains(&li))
9188}
9189
9190pub fn dspark_note(li: usize, state: &[f32], cfg: &Dsv4Cfg) {
9192 DSPARK_CAP.with(|c| {
9193 let mut c = c.borrow_mut();
9194 if c.0.is_empty() {
9195 return;
9196 }
9197 if let Some(slot) = c.0.iter().position(|&t| t == li) {
9198 let (_, buf, seen) = &mut *c;
9199 dspark_capture(state, cfg, slot, buf);
9200 *seen = if slot == 0 { 1 } else { *seen + 1 };
9205 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
9206 eprintln!("[cap] note li={li} slot={slot} seen={}", *seen);
9207 }
9208 }
9209 });
9210}
9211
9212pub fn dspark_peek_slot(slot: usize, dim: usize, out: &mut [f32]) -> bool {
9217 DSPARK_CAP.with(|c| {
9218 let c = c.borrow();
9219 let lo = slot * dim;
9220 if c.1.len() < lo + dim {
9221 return false;
9222 }
9223 out[..dim].copy_from_slice(&c.1[lo..lo + dim]);
9224 true
9225 })
9226}
9227
9228pub fn dspark_take(out: &mut Vec<f32>) -> bool {
9230 DSPARK_CAP.with(|c| {
9231 let mut c = c.borrow_mut();
9232 if c.0.is_empty() || c.2 != c.0.len() {
9233 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
9234 eprintln!("[cap] take FAIL armed={:?} seen={}", c.0, c.2);
9235 }
9236 return false;
9237 }
9238 out.clear();
9239 out.extend_from_slice(&c.1);
9240 c.2 = 0;
9241 true
9242 })
9243}
9244
9245pub fn dspark_capture(state: &[f32], cfg: &Dsv4Cfg, slot: usize, out: &mut [f32]) {
9249 let (hc, dim) = (cfg.hc_mult, cfg.dim);
9250 let dst = &mut out[slot * dim..(slot + 1) * dim];
9251 let inv = 1.0 / hc as f32;
9252 for d in 0..dim {
9253 let mut s = 0.0;
9254 for j in 0..hc {
9255 s += state[j * dim + d];
9256 }
9257 dst[d] = s * inv;
9258 }
9259}
9260
9261pub fn dspark_freq_note(picks: &[(usize, Vec<usize>)]) {
9266 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
9267 std::sync::Mutex::new(None);
9268 let Ok(path) = std::env::var("CMF_DSPARK_PICK_DUMP") else {
9269 return;
9270 };
9271 let mut g = FREQ.lock().unwrap();
9272 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
9273 for (stage, idx) in picks {
9274 for &e in idx {
9275 *map.entry((*stage, e)).or_insert(0) += 1;
9276 }
9277 }
9278 *blocks += 1;
9279 if *blocks % 32 == 0 {
9280 let mut lines: Vec<_> = map.iter().collect();
9281 lines.sort();
9282 let body: String = lines
9283 .iter()
9284 .map(|((s, e), n)| format!("{s}\t{e}\t{n}\n"))
9285 .collect();
9286 let _ = std::fs::write(&path, body);
9287 }
9288}
9289
9290pub fn trunk_freq_note(picks: &[(usize, Vec<usize>)]) {
9295 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
9296 std::sync::Mutex::new(None);
9297 let Ok(path) = std::env::var("CMF_DSV4_TRUNK_PICK_DUMP") else {
9298 return;
9299 };
9300 let mut g = FREQ.lock().unwrap();
9301 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
9302 for (li, idx) in picks {
9303 for &e in idx {
9304 *map.entry((*li, e)).or_insert(0) += 1;
9305 }
9306 }
9307 *blocks += 1;
9308 if *blocks % 32 == 0 {
9309 let mut lines: Vec<_> = map.iter().collect();
9310 lines.sort();
9311 let body: String = lines
9312 .iter()
9313 .map(|((l, e), n)| format!("{l}\t{e}\t{n}\n"))
9314 .collect();
9315 let _ = std::fs::write(&path, body);
9316 }
9317}
9318
9319fn dspark_apply_mask(out: &mut [Dsv4Mtp]) {
9327 let Ok(path) = std::env::var("CMF_DSPARK_MASK") else {
9328 return;
9329 };
9330 let Ok(text) = std::fs::read_to_string(&path) else {
9331 eprintln!("DSpark: CMF_DSPARK_MASK={path} is unreadable — no mask applied");
9332 return;
9333 };
9334 for (d, line) in text.lines().enumerate() {
9335 let Some(m) = out.get_mut(d) else { break };
9336 let n = m.layer.experts.len();
9337 let mut mask = vec![false; n];
9338 let mut kept = 0usize;
9339 for tok in line.split(',') {
9340 if let Ok(e) = tok.trim().parse::<usize>() {
9341 if e < n && !mask[e] {
9342 mask[e] = true;
9343 kept += 1;
9344 }
9345 }
9346 }
9347 if kept == 0 {
9348 continue;
9349 }
9350 eprintln!("DSpark: stage {d} limited to {kept}/{n} experts");
9351 m.layer.mask = Some(mask);
9352 }
9353}
9354
9355#[cfg(feature = "gpu")]
9365pub struct DsparkPack {
9366 pub stages: Vec<DsparkStagePack>,
9367 pub gu_q2: bool,
9370 pub dn_q2: bool,
9373 pub routers: Vec<Vec<f32>>,
9376 pub biases: Vec<Option<Vec<f32>>>,
9377}
9378
9379#[cfg(feature = "gpu")]
9380pub struct DsparkStagePack {
9381 pub mask: Vec<bool>,
9383 pub to_slot: Vec<usize>,
9385 pub mask_u32: Vec<u32>,
9388 pub map_u32: Vec<u32>,
9389 pub tensors: Vec<(usize, usize, usize)>,
9391 pub n_resident: usize,
9392 global: Option<GlobalPack>,
9396}
9397
9398pub static DSPARK_Q2TP_ENCODE: std::sync::OnceLock<fn(&[f32], usize, usize) -> Vec<u8>> =
9405 std::sync::OnceLock::new();
9406
9407#[cfg(feature = "gpu")]
9410pub fn dspark_gpu_on() -> bool {
9411 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9412 *ON.get_or_init(|| {
9413 std::env::var("CMF_DSPARK_GPU")
9414 .map(|v| v != "0")
9415 .unwrap_or(true)
9416 })
9417}
9418
9419#[cfg(feature = "gpu")]
9421pub fn dspark_pack_get(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<&'static DsparkPack> {
9422 static P: std::sync::OnceLock<Option<Box<DsparkPack>>> = std::sync::OnceLock::new();
9423 P.get_or_init(|| dspark_pack_build(mtp, cfg).map(Box::new))
9424 .as_deref()
9425}
9426
9427#[cfg(feature = "gpu")]
9443pub fn dspark_reserve_note(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, layers: &[Dsv4Layer]) {
9444 if mtp.is_empty() || std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "0") || !dspark_gpu_on()
9445 {
9446 return;
9447 }
9448 let forced = std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "1");
9453 let dt = |q2: bool| {
9454 if q2 {
9455 cortiq_core::TensorDtype::Q2TiledP
9456 } else {
9457 cortiq_core::TensorDtype::Q4TiledP
9458 }
9459 };
9460 let gu_q2 = mtp[0]
9461 .layer
9462 .experts
9463 .first()
9464 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9465 let dn_q2 = mtp[0]
9466 .layer
9467 .experts
9468 .first()
9469 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9470 if !gu_q2
9478 && !dn_q2
9479 && std::env::var("CMF_MOE_MASK").is_err()
9480 && crate::gpu_wgpu::dsv4_global_moe_supported()
9481 {
9482 crate::gpu_wgpu::DRAFT_PACK_RESERVE.store(0, std::sync::atomic::Ordering::Relaxed);
9483 crate::gpu_wgpu::DRAFT_RESERVE.store(0, std::sync::atomic::Ordering::Relaxed);
9484 return;
9485 }
9486 let gu = cortiq_core::quant::expected_nbytes(dt(gu_q2), &[cfg.moe_inter, cfg.dim]).unwrap_or(0);
9487 let dn = cortiq_core::quant::expected_nbytes(dt(dn_q2), &[cfg.dim, cfg.moe_inter]).unwrap_or(0);
9488 let per = (2 * gu + dn) as u64;
9489 let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
9490 .ok()
9491 .and_then(|v| v.parse().ok())
9492 .unwrap_or(40);
9495 let bytes = per * (n_res * mtp.len() + mtp.len() + 1) as u64;
9497 let trunk: u64 = layers
9507 .iter()
9508 .map(|l| {
9509 let Some(e) = l.experts.first() else {
9510 return 0;
9511 };
9512 let gu = cortiq_core::quant::expected_nbytes(
9513 dt(e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
9514 &[cfg.moe_inter, cfg.dim],
9515 )
9516 .unwrap_or(0);
9517 let dn = cortiq_core::quant::expected_nbytes(
9518 dt(e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
9519 &[cfg.dim, cfg.moe_inter],
9520 )
9521 .unwrap_or(0);
9522 let routed = l
9523 .mask
9524 .as_deref()
9525 .map_or(l.experts.len(), |m| m.iter().filter(|&&open| open).count());
9526 ((2 * gu + dn) * (routed + 1)) as u64
9527 })
9528 .sum();
9529 if let Some(budget) = crate::gpu_wgpu::dsv4_vram_budget() {
9530 if !forced && budget < trunk / 10 * 9 + bytes {
9531 return;
9532 }
9533 }
9534 let mib = 1024 * 1024u64;
9543 let workspace = match crate::gpu_wgpu::dsv4_vram_budget() {
9544 Some(b) if b <= 64 * 1024 * mib => 1024 * mib,
9548 _ => {
9549 ((cfg.dim * cfg.hc_mult * DSPARK_BLOCK_MAX * 4096) as u64).clamp(512 * mib, 1024 * mib)
9550 }
9551 };
9552 crate::gpu_wgpu::DRAFT_PACK_RESERVE.store(bytes, std::sync::atomic::Ordering::Relaxed);
9553 crate::gpu_wgpu::DRAFT_RESERVE.store(
9554 bytes.saturating_add(workspace),
9555 std::sync::atomic::Ordering::Relaxed,
9556 );
9557}
9558
9559#[cfg(not(feature = "gpu"))]
9560pub fn dspark_reserve_note(_mtp: &[Dsv4Mtp], _cfg: &Dsv4Cfg, _layers: &[Dsv4Layer]) {}
9561
9562#[cfg(feature = "gpu")]
9563pub fn dspark_pack_build(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<DsparkPack> {
9564 if mtp.is_empty() {
9565 return None;
9566 }
9567 let model = mtp[0]
9568 .layer
9569 .experts
9570 .first()
9571 .and_then(|e| e.w1.model_arc())?;
9572 let native_q2 = mtp[0]
9573 .layer
9574 .experts
9575 .first()
9576 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9577 let dn_native = mtp[0]
9578 .layer
9579 .experts
9580 .first()
9581 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9582 let global_share = !native_q2
9583 && !dn_native
9584 && std::env::var("CMF_MOE_MASK").is_err()
9585 && crate::gpu_wgpu::dsv4_global_moe_supported()
9586 && crate::gpu_wgpu::dsv4_global_moe_ready(&model);
9587 let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
9588 .ok()
9589 .and_then(|v| v.parse().ok())
9590 .unwrap_or_else(|| {
9591 if global_share {
9592 return 40;
9596 }
9597 let gu_q2 = native_q2 || (!dspark_native_on() && DSPARK_Q2TP_ENCODE.get().is_some());
9603 let room = crate::gpu_wgpu::dsv4_draft_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_native);
9604 (room.saturating_sub(mtp.len() + 1) / mtp.len().max(1)).clamp(8, 40)
9605 });
9606 let mut freq: Vec<Vec<(u64, usize)>> = vec![Vec::new(); mtp.len()];
9610 let pack_path = std::env::var("CMF_DSPARK_PACK").ok().or_else(|| {
9611 let m = mtp[0].layer.experts.first()?.w1.model_arc()?;
9612 let mut s = m.path.as_os_str().to_os_string();
9613 s.push(".dspark.tsv");
9614 let p = std::path::PathBuf::from(s);
9615 p.exists().then(|| p.to_string_lossy().into_owned())
9616 });
9617 if let Some(path) = pack_path {
9618 if let Ok(text) = std::fs::read_to_string(&path) {
9619 for line in text.lines() {
9620 let mut it = line.split_whitespace();
9621 if let (Some(s), Some(e), Some(n)) = (it.next(), it.next(), it.next()) {
9622 if let (Ok(s), Ok(e), Ok(n)) =
9623 (s.parse::<usize>(), e.parse::<usize>(), n.parse::<u64>())
9624 {
9625 if s < freq.len() {
9626 freq[s].push((n, e));
9627 }
9628 }
9629 }
9630 }
9631 }
9632 }
9633 let mut stages = Vec::with_capacity(mtp.len());
9634 let mut routers = Vec::with_capacity(mtp.len());
9635 let mut biases = Vec::with_capacity(mtp.len());
9636 for (si, m) in mtp.iter().enumerate() {
9637 let l = &m.layer;
9638 let n = l.experts.len();
9639 let mut order: Vec<usize> = {
9642 let mut f = freq[si].clone();
9643 f.sort_by(|a, b| b.0.cmp(&a.0));
9644 let mut seen = vec![false; n];
9645 let mut o: Vec<usize> = f
9646 .into_iter()
9647 .map(|(_, e)| e)
9648 .filter(|&e| {
9649 if e < n && !seen[e] {
9650 seen[e] = true;
9651 true
9652 } else {
9653 false
9654 }
9655 })
9656 .collect();
9657 o.extend((0..n).filter(|&e| !seen[e]));
9658 o
9659 };
9660 order.truncate(n_res.min(n));
9661 let (global, mask, to_slot, tensors) = if global_share {
9662 let pk = pack_for(l, cfg, model.header.arch.num_layers + si)?;
9663 let gl = pk.global.clone()?;
9664 let remap = gl.pool.pin_picks(&model, gl.layer, &order, &l.experts);
9665 let mut mask = vec![false; n];
9666 let mut to_slot = vec![usize::MAX; n];
9667 for e in 0..n {
9668 if remap.get(e).copied().unwrap_or(u32::MAX) != u32::MAX {
9669 mask[e] = true;
9670 to_slot[e] = remap[e] as usize;
9671 }
9672 }
9673 (Some(gl), mask, to_slot, Vec::new())
9674 } else {
9675 let mut mask = vec![false; n];
9676 let mut to_slot = vec![usize::MAX; n];
9677 let mut tensors = Vec::with_capacity(order.len() + 1);
9678 for (slot, &e) in order.iter().enumerate() {
9679 let ex = &l.experts[e];
9680 let (Some(w1), Some(w3), Some(w2)) =
9681 (ex.w1.model_idx(), ex.w3.model_idx(), ex.w2.model_idx())
9682 else {
9683 return None;
9684 };
9685 mask[e] = true;
9686 to_slot[e] = slot;
9687 tensors.push((w1, w3, w2));
9688 }
9689 let (Some(s1), Some(s3), Some(s2)) = (
9690 l.shared.w1.model_idx(),
9691 l.shared.w3.model_idx(),
9692 l.shared.w2.model_idx(),
9693 ) else {
9694 return None;
9695 };
9696 tensors.push((s1, s3, s2));
9697 (None, mask, to_slot, tensors)
9698 };
9699 let mut router = vec![0.0f32; n * cfg.dim];
9701 for (r, row) in (0..n).zip(router.chunks_mut(cfg.dim)) {
9702 l.gate.row_f32(r, row);
9703 }
9704 routers.push(router);
9705 biases.push(l.gate_bias.clone());
9706 let mask_u32: Vec<u32> = mask.iter().map(|&m| m as u32).collect();
9707 let map_u32: Vec<u32> = to_slot
9708 .iter()
9709 .map(|&x| if x == usize::MAX { u32::MAX } else { x as u32 })
9710 .collect();
9711 stages.push(DsparkStagePack {
9712 mask,
9713 to_slot,
9714 mask_u32,
9715 map_u32,
9716 tensors,
9717 n_resident: order.len(),
9718 global,
9719 });
9720 }
9721 let mut skeleton = Vec::new();
9725 for m in mtp {
9726 let l = &m.layer;
9727 for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b] {
9728 skeleton.push(t.model_idx()?);
9729 }
9730 }
9731 if let Some(mp) = mtp[0].main_proj.as_ref() {
9732 skeleton.push(mp.model_idx()?);
9733 }
9734 for &idx in &skeleton {
9735 if !crate::gpu_wgpu::dsv4_weight_ready(&model, idx) {
9736 eprintln!("DSpark: the draft skeleton does not fit in VRAM — the GPU draft is off");
9737 return None;
9738 }
9739 }
9740 let gu_q2 = native_q2
9746 || (!crate::dsv4::dspark_native_on() && crate::dsv4::DSPARK_Q2TP_ENCODE.get().is_some());
9747 for (si, sp) in stages.iter().enumerate() {
9748 let ok = if sp.global.is_some() {
9749 true
9750 } else if native_q2 {
9751 crate::gpu_wgpu::dsv4_experts_ready(
9752 &model,
9753 &sp.tensors,
9754 cfg.moe_inter,
9755 cfg.dim,
9756 true,
9757 dn_native,
9758 )
9759 } else if gu_q2 {
9760 crate::gpu_wgpu::moe_expert_bufs_requant_gu(&model, &sp.tensors, cfg.moe_inter, cfg.dim)
9761 .is_some()
9762 } else {
9763 crate::gpu_wgpu::dsv4_experts_ready(
9764 &model,
9765 &sp.tensors,
9766 cfg.moe_inter,
9767 cfg.dim,
9768 false,
9769 false,
9770 )
9771 };
9772 if !ok {
9773 eprintln!(
9774 "DSpark: the experts of stage {si} ({} + shared) do not fit in VRAM — the GPU draft is off",
9775 sp.n_resident
9776 );
9777 return None;
9778 }
9779 }
9780 let _ = crate::gpu_wgpu::pin_weights(&model, &skeleton);
9781 eprintln!(
9782 "DSpark: {} — {} stages of {} experts + shared",
9783 if global_share {
9784 "a reserved share of the unified pool"
9785 } else {
9786 "a separate draft pack on the card"
9787 },
9788 stages.len(),
9789 stages
9790 .iter()
9791 .map(|s| s.n_resident.to_string())
9792 .collect::<Vec<_>>()
9793 .join("/")
9794 );
9795 Some(DsparkPack {
9796 stages,
9797 gu_q2: if global_share { false } else { gu_q2 },
9798 dn_q2: dn_native,
9799 routers,
9800 biases,
9801 })
9802}
9803
9804pub fn dspark_ring_append(
9811 g: &Dsv4Globals,
9812 mtp: &[Dsv4Mtp],
9813 cfg: &Dsv4Cfg,
9814 ds: &mut DsparkState,
9815 pos: usize,
9816 pool: Option<&crate::pool::Pool>,
9817) {
9818 let (dim, hd, rd) = (cfg.dim, cfg.head_dim, cfg.rope_head_dim);
9819 let inv_freq = &g.inv_freq_window;
9820 let Some(stage0) = mtp.first() else { return };
9821 let (Some(mp), Some(mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
9822 return;
9823 };
9824 let mut main_x = vec![0.0f32; dim];
9825 mp.matvec(&ds.main_hidden, &mut main_x, pool);
9826 rms_weighted(&mut main_x, mn, cfg.norm_eps);
9827 for (si, m) in mtp.iter().enumerate() {
9828 let kvw = m.layer.wkv.rows();
9829 if ds.win[si].len() < cfg.window * kvw {
9830 ds.win[si].resize(cfg.window * kvw, 0.0);
9831 }
9832 let mut kv = vec![0.0f32; kvw];
9833 m.layer.wkv.matvec(&main_x, &mut kv, pool);
9834 rms_weighted(&mut kv, &m.layer.kv_norm, cfg.norm_eps);
9835 rope_tail(&mut kv[kvw - hd..], inv_freq, pos, rd, false);
9836 let slot = pos % cfg.window;
9837 ds.win[si][slot * kvw..(slot + 1) * kvw].copy_from_slice(&kv);
9838 ds.filled[si] = (pos + 1).min(cfg.window);
9839 }
9840}
9841
9842#[cfg(feature = "gpu")]
9848#[allow(clippy::too_many_arguments)]
9849pub fn dspark_draft_gpu(
9850 g: &Dsv4Globals,
9851 mtp: &[Dsv4Mtp],
9852 cfg: &Dsv4Cfg,
9853 ds: &mut DsparkState,
9854 pack: &DsparkPack,
9855 kv_id: u64,
9856 last_token: u32,
9857 pos: usize,
9858 pool: Option<&crate::pool::Pool>,
9859 out_conf: &mut Vec<f32>,
9860) -> Vec<u32> {
9861 let (hc, dim) = (cfg.hc_mult, cfg.dim);
9862 let block = dspark_block();
9863 let Some(model) = mtp[0].layer.experts.first().and_then(|e| e.w1.model_arc()) else {
9864 return Vec::new();
9865 };
9866 let (Some(mp), Some(mn)) = (mtp[0].main_proj.as_ref(), mtp[0].main_norm.as_ref()) else {
9867 return Vec::new();
9868 };
9869 let Some(mp_idx) = mp.model_idx() else {
9870 return Vec::new();
9871 };
9872 let mut stages = Vec::with_capacity(mtp.len());
9873 for (si, m) in mtp.iter().enumerate() {
9874 let l = &m.layer;
9875 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
9876 l.wq_a.model_idx(),
9877 l.wq_b.model_idx(),
9878 l.wo_a.model_idx(),
9879 l.wo_b.model_idx(),
9880 l.wkv.model_idx(),
9881 ) else {
9882 return Vec::new();
9883 };
9884 let sp = &pack.stages[si];
9885 stages.push(crate::gpu_wgpu::DsparkStageW {
9886 wq_a,
9887 wq_b,
9888 wo_a,
9889 wo_b,
9890 wkv,
9891 q_norm: &l.q_norm,
9892 kv_norm: &l.kv_norm,
9893 attn_norm: &l.attn_norm,
9894 ffn_norm: &l.ffn_norm,
9895 sink: &l.attn_sink,
9896 hc_attn_fn: &l.hc_attn_fn,
9897 hc_attn_scale: &l.hc_attn_scale,
9898 hc_attn_base: &l.hc_attn_base,
9899 hc_ffn_fn: &l.hc_ffn_fn,
9900 hc_ffn_scale: &l.hc_ffn_scale,
9901 hc_ffn_base: &l.hc_ffn_base,
9902 router: &pack.routers[si],
9903 bias: pack.biases[si].as_deref(),
9904 experts: &sp.tensors,
9905 mask_u32: &sp.mask_u32,
9906 map_u32: &sp.map_u32,
9907 global: sp.global.as_ref().map(|gl| crate::gpu_wgpu::Dsv4GlobalMoe {
9908 pool_uid: gl.pool.uid,
9909 shared_slot: gl.shared_slot,
9910 segment_slots: gl.pool.segment_slots as u32,
9911 }),
9912 });
9913 }
9914 let geom = crate::gpu_wgpu::DsparkGeom {
9915 dim,
9916 hc,
9917 nh: cfg.n_heads,
9918 hd: cfg.head_dim,
9919 rd: cfg.rope_head_dim,
9920 q_lora: cfg.q_lora_rank,
9921 o_lora: cfg.o_lora_rank,
9922 o_groups: cfg.o_groups,
9923 inter: cfg.moe_inter,
9924 n_experts: cfg.n_routed_experts,
9925 top_k: cfg.top_k,
9926 window: cfg.window,
9927 eps: cfg.norm_eps,
9928 hc_eps: cfg.hc_eps,
9929 sinkhorn_iters: cfg.hc_sinkhorn_iters,
9930 route_scale: cfg.route_scale,
9931 swiglu_limit: cfg.swiglu_limit,
9932 scale: (cfg.head_dim as f32).powf(-0.5),
9933 gu_q2: pack.gu_q2,
9934 dn_q2: pack.dn_q2,
9935 };
9936 let ids: Vec<u32> = (0..block)
9938 .map(|i| {
9939 if i == 0 {
9940 last_token
9941 } else {
9942 DSPARK_NOISE_TOKEN
9943 }
9944 })
9945 .collect();
9946 let mut states0 = vec![0.0f32; block * hc * dim];
9947 let mut emb = vec![0.0f32; dim];
9948 for (i, &id) in ids.iter().enumerate() {
9949 g.embed.row_f32(id as usize, &mut emb);
9950 for j in 0..hc {
9951 states0[(i * hc + j) * dim..(i * hc + j + 1) * dim].copy_from_slice(&emb);
9952 }
9953 }
9954 let dspark_time = {
9955 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9956 *ON.get_or_init(|| std::env::var("CMF_DSPARK_TIME").is_ok_and(|v| v != "0"))
9957 };
9958 let t0 = std::time::Instant::now();
9959 let filled = (pos + 1).min(cfg.window);
9960 let mut states = vec![0.0f32; block * hc * dim];
9961 if !crate::gpu_wgpu::dspark_graph(
9962 &model,
9963 &stages,
9964 geom,
9965 kv_id,
9966 mp_idx,
9967 mn,
9968 &ds.main_hidden,
9969 &states0,
9970 pos,
9971 filled,
9972 &g.inv_freq_window,
9973 block,
9974 &mut states,
9975 ) {
9976 return Vec::new();
9977 }
9978 for si in 0..mtp.len() {
9979 ds.filled[si] = filled;
9980 }
9981 let t_graph = t0.elapsed();
9982
9983 let last = &mtp[mtp.len() - 1];
9985 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
9986 last.hc_head_fn.as_ref(),
9987 last.hc_head_base.as_ref(),
9988 last.hc_head_scale,
9989 last.norm.as_ref(),
9990 ) else {
9991 return Vec::new();
9992 };
9993 let mut head_in = vec![0.0f32; block * dim];
9994 let mut pre_norms = vec![vec![0.0f32; dim]; block];
9995 for i in 0..block {
9996 hc_head_fold(
9997 &states[i * hc * dim..(i + 1) * hc * dim],
9998 hfn,
9999 hscale,
10000 hbase,
10001 cfg,
10002 pool,
10003 &mut head_in[i * dim..(i + 1) * dim],
10004 );
10005 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
10006 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
10007 }
10008 let t_fold = t0.elapsed();
10009 let mut logits = vec![0.0f32; block * cfg.vocab];
10010 let head_gpu = g.head.model_idx().is_some_and(|hi| {
10013 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
10014 &model,
10015 hi,
10016 &head_in,
10017 block,
10018 cfg.vocab,
10019 dim,
10020 &mut logits,
10021 )
10022 });
10023 if !head_gpu {
10024 g.head.matmat(&head_in, block, &mut logits, pool);
10025 }
10026 let t_head = t0.elapsed();
10027 let mut proposals = Vec::with_capacity(block);
10032 out_conf.clear();
10033 let mut prev = last_token;
10034 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
10035 let mut bias = vec![0.0f32; cfg.vocab];
10036 for i in 0..block {
10037 let row = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
10038 if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
10039 w1.row_f32(prev as usize, &mut mk_embed);
10040 w2.matvec(&mk_embed, &mut bias, pool);
10041 for (a, b) in row.iter_mut().zip(&bias) {
10042 *a += *b;
10043 }
10044 }
10045 let mut best = 0usize;
10046 for v in 1..row.len() {
10047 if row[v] > row[best] {
10048 best = v;
10049 }
10050 }
10051 if let Some(cf) = last.confidence.as_ref() {
10052 let mut cat = pre_norms[i].clone();
10053 cat.extend_from_slice(&mk_embed);
10054 let mut sc = [0.0f32; 1];
10055 if cat.len() == cf.cols() {
10056 cf.matvec(&cat, &mut sc, pool);
10057 }
10058 out_conf.push(sc[0]);
10059 }
10060 proposals.push(best as u32);
10061 prev = best as u32;
10062 }
10063 if dspark_time {
10064 eprintln!(
10065 "DSpark GPU: graph {:.1} ms, folds {:.1}, head {:.1}, markov+argmax {:.1}",
10066 t_graph.as_secs_f64() * 1e3,
10067 (t_fold - t_graph).as_secs_f64() * 1e3,
10068 (t_head - t_fold).as_secs_f64() * 1e3,
10069 (t0.elapsed() - t_head).as_secs_f64() * 1e3,
10070 );
10071 }
10072 proposals
10073}
10074
10075#[allow(clippy::too_many_arguments)]
10081pub fn dspark_draft(
10082 g: &Dsv4Globals,
10083 mtp: &[Dsv4Mtp],
10084 cfg: &Dsv4Cfg,
10085 ds: &mut DsparkState,
10086 last_token: u32,
10087 pos: usize,
10088 pool: Option<&crate::pool::Pool>,
10089 out_conf: &mut Vec<f32>,
10090) -> Vec<u32> {
10091 let (hc, dim, hd, rd) = (cfg.hc_mult, cfg.dim, cfg.head_dim, cfg.rope_head_dim);
10092 let block = dspark_block();
10093 let inv_freq = &g.inv_freq_window;
10094
10095 let Some(stage0) = mtp.first() else {
10097 return Vec::new();
10098 };
10099 let (Some(_mp), Some(_mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
10100 return Vec::new();
10101 };
10102 dspark_ring_append(g, mtp, cfg, ds, pos, pool);
10103
10104 let ids: Vec<u32> = (0..block)
10106 .map(|i| {
10107 if i == 0 {
10108 last_token
10109 } else {
10110 DSPARK_NOISE_TOKEN
10111 }
10112 })
10113 .collect();
10114 let mut states = vec![vec![0.0f32; hc * dim]; block];
10115 let mut emb = vec![0.0f32; dim];
10116 for (i, &id) in ids.iter().enumerate() {
10117 g.embed.row_f32(id as usize, &mut emb);
10118 for j in 0..hc {
10119 states[i][j * dim..(j + 1) * dim].copy_from_slice(&emb);
10120 }
10121 }
10122
10123 let mut scratch = HcScratch::new(cfg);
10124 for (si, m) in mtp.iter().enumerate() {
10125 let l = &m.layer;
10126 let kvw = l.wkv.rows();
10127 let mut post = vec![vec![0.0f32; hc]; block];
10130 let mut comb = vec![vec![0.0f32; hc * hc]; block];
10131 let mut resid = vec![vec![0.0f32; hc * dim]; block];
10132 let mut folded = vec![vec![0.0f32; dim]; block];
10133 let mix_hc = (2 + hc) * hc;
10134 for i in 0..block {
10135 hc_mixes(
10136 &states[i],
10137 &l.hc_attn_fn,
10138 mix_hc,
10139 cfg.norm_eps,
10140 pool,
10141 &mut scratch.mixes,
10142 );
10143 hc_split_sinkhorn(
10144 &scratch.mixes,
10145 &l.hc_attn_scale,
10146 &l.hc_attn_base,
10147 hc,
10148 cfg.hc_sinkhorn_iters,
10149 cfg.hc_eps,
10150 &mut scratch.pre,
10151 &mut post[i],
10152 &mut comb[i],
10153 );
10154 hc_fold(&states[i], &scratch.pre, hc, dim, &mut folded[i]);
10155 rms_weighted(&mut folded[i], &l.attn_norm, cfg.norm_eps);
10156 resid[i].copy_from_slice(&states[i]);
10157 }
10158 let folded_all: Vec<f32> = folded.iter().flatten().copied().collect();
10160 let mut blk_kv = vec![0.0f32; block * kvw];
10161 l.wkv.matmat(&folded_all, block, &mut blk_kv, pool);
10162 for i in 0..block {
10163 let dst = &mut blk_kv[i * kvw..(i + 1) * kvw];
10164 rms_weighted(dst, &l.kv_norm, cfg.norm_eps);
10165 rope_tail(&mut dst[kvw - hd..], inv_freq, pos + 1 + i, rd, false);
10166 }
10167 let win_len = ds.filled[si];
10169 let mut cache = Vec::with_capacity((win_len + block) * hd);
10170 for p in 0..win_len {
10171 let e = &ds.win[si][p * kvw..(p + 1) * kvw];
10172 cache.extend_from_slice(&e[kvw - hd..]);
10173 }
10174 for i in 0..block {
10175 let e = &blk_kv[i * kvw..(i + 1) * kvw];
10176 cache.extend_from_slice(&e[kvw - hd..]);
10177 }
10178 let idxs: Vec<usize> = (0..win_len + block).collect();
10179 let scale = (hd as f32).powf(-0.5);
10180 let qrank = l.wq_a.rows();
10181 let qdim = cfg.n_heads * hd;
10182 let mut qr = vec![0.0f32; block * qrank];
10183 l.wq_a.matmat(&folded_all, block, &mut qr, pool);
10184 for i in 0..block {
10185 rms_weighted(&mut qr[i * qrank..(i + 1) * qrank], &l.q_norm, cfg.norm_eps);
10186 }
10187 let mut q = vec![0.0f32; block * qdim];
10188 l.wq_b.matmat(&qr, block, &mut q, pool);
10189 let mut attn = vec![0.0f32; block * qdim];
10190 for i in 0..block {
10191 let qi = &mut q[i * qdim..(i + 1) * qdim];
10192 let ai = &mut attn[i * qdim..(i + 1) * qdim];
10193 let qpos = pos + 1 + i;
10194 for h in 0..cfg.n_heads {
10195 let head = &mut qi[h * hd..(h + 1) * hd];
10196 rms_inplace(head, cfg.norm_eps);
10197 rope_tail(head, inv_freq, qpos, rd, false);
10198 }
10199 for h in 0..cfg.n_heads {
10200 let qh = &qi[h * hd..(h + 1) * hd];
10201 let oh = &mut ai[h * hd..(h + 1) * hd];
10202 sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
10203 rope_tail(oh, inv_freq, qpos, rd, true);
10204 }
10205 }
10206 let mut blk_out = vec![0.0f32; block * dim];
10207 o_project_block(
10208 &attn,
10209 block,
10210 &l.wo_a,
10211 &l.wo_b,
10212 cfg.o_groups,
10213 cfg.o_lora_rank,
10214 pool,
10215 &mut blk_out,
10216 );
10217 for i in 0..block {
10218 let mut next = vec![0.0f32; hc * dim];
10219 hc_expand(
10220 &blk_out[i * dim..(i + 1) * dim],
10221 &resid[i],
10222 &post[i],
10223 &comb[i],
10224 hc,
10225 dim,
10226 &mut next,
10227 );
10228 states[i] = next;
10229 }
10230 let mut ffn_fold = vec![0.0f32; block * dim];
10233 let mut ffn_post = vec![vec![0.0f32; hc]; block];
10234 let mut ffn_comb = vec![vec![0.0f32; hc * hc]; block];
10235 let mut ffn_resid = vec![vec![0.0f32; hc * dim]; block];
10236 for i in 0..block {
10237 hc_mixes(
10238 &states[i],
10239 &l.hc_ffn_fn,
10240 mix_hc,
10241 cfg.norm_eps,
10242 pool,
10243 &mut scratch.mixes,
10244 );
10245 hc_split_sinkhorn(
10246 &scratch.mixes,
10247 &l.hc_ffn_scale,
10248 &l.hc_ffn_base,
10249 hc,
10250 cfg.hc_sinkhorn_iters,
10251 cfg.hc_eps,
10252 &mut scratch.pre,
10253 &mut ffn_post[i],
10254 &mut ffn_comb[i],
10255 );
10256 hc_fold(
10257 &states[i],
10258 &scratch.pre,
10259 hc,
10260 dim,
10261 &mut ffn_fold[i * dim..(i + 1) * dim],
10262 );
10263 rms_weighted(
10264 &mut ffn_fold[i * dim..(i + 1) * dim],
10265 &l.ffn_norm,
10266 cfg.norm_eps,
10267 );
10268 ffn_resid[i].copy_from_slice(&states[i]);
10269 }
10270 let mut moe_out = vec![0.0f32; block * dim];
10271 moe_step_block(&ffn_fold, block, l, cfg, &ids, si, pool, &mut moe_out);
10272 for i in 0..block {
10273 let mut next = vec![0.0f32; hc * dim];
10274 hc_expand(
10275 &moe_out[i * dim..(i + 1) * dim],
10276 &ffn_resid[i],
10277 &ffn_post[i],
10278 &ffn_comb[i],
10279 hc,
10280 dim,
10281 &mut next,
10282 );
10283 states[i] = next;
10284 }
10285 }
10286
10287 let last = &mtp[mtp.len() - 1];
10289 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
10290 last.hc_head_fn.as_ref(),
10291 last.hc_head_base.as_ref(),
10292 last.hc_head_scale,
10293 last.norm.as_ref(),
10294 ) else {
10295 return Vec::new();
10296 };
10297 let mut proposals = Vec::with_capacity(block);
10298 out_conf.clear();
10299 let mut prev = last_token;
10300 let mut head_in = vec![0.0f32; block * dim];
10301 let mut pre_norms = vec![vec![0.0f32; dim]; block];
10302 for i in 0..block {
10303 hc_head_fold(
10304 &states[i],
10305 hfn,
10306 hscale,
10307 hbase,
10308 cfg,
10309 pool,
10310 &mut head_in[i * dim..(i + 1) * dim],
10311 );
10312 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
10313 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
10314 }
10315 let mut logits = vec![0.0f32; block * cfg.vocab];
10316 g.head.matmat(&head_in, block, &mut logits, pool);
10317 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
10318 for i in 0..block {
10319 let logits_i = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
10320 let no_markov = {
10329 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10330 *ON.get_or_init(|| std::env::var("CMF_DSPARK_NO_MARKOV").is_ok_and(|v| v != "0"))
10331 };
10332 if no_markov {
10333 if let Some(w1) = last.markov_w1.as_ref() {
10335 w1.row_f32(prev as usize, &mut mk_embed);
10336 }
10337 } else if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
10338 w1.row_f32(prev as usize, &mut mk_embed);
10339 let mut bias = vec![0.0f32; cfg.vocab];
10340 w2.matvec(&mk_embed, &mut bias, pool);
10341 for (a, b) in logits_i.iter_mut().zip(&bias) {
10342 *a += *b;
10343 }
10344 }
10345 let mut best = 0usize;
10346 for v in 1..logits_i.len() {
10347 if logits_i[v] > logits_i[best] {
10348 best = v;
10349 }
10350 }
10351 if let Some(cf) = last.confidence.as_ref() {
10352 let mut cat = pre_norms[i].clone();
10353 cat.extend_from_slice(&mk_embed);
10354 let mut s = [0.0f32; 1];
10355 if cat.len() == cf.cols() {
10356 cf.matvec(&cat, &mut s, pool);
10357 }
10358 out_conf.push(s[0]);
10359 }
10360 proposals.push(best as u32);
10361 prev = best as u32;
10362 }
10363 proposals
10364}