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 " [порт] позиций={} 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 [&ATTN_NS, &MOE_NS, &HC_NS, &HEAD_NS, &ALL_NS, &CALLS, &PREP_NS, &CACHEW_NS] {
1084 a.store(0, Ordering::Relaxed);
1085 }
1086 TOKENS.store(1, Ordering::Relaxed);
1087 #[cfg(feature = "gpu")]
1088 for a in [
1089 &crate::gpu_wgpu::MOE_ENC_NS,
1090 &crate::gpu_wgpu::MOE_WAIT_NS,
1091 &crate::gpu_wgpu::MOE_BUFS_NS,
1092 &crate::gpu_wgpu::MOE_UP_NS,
1093 &crate::gpu_wgpu::MOE_PASS_NS,
1094 &crate::gpu_wgpu::ATT_ENC_NS,
1095 &crate::gpu_wgpu::ATT_WAIT_NS,
1096 &crate::gpu_wgpu::CHAIN_ENC_NS,
1097 &crate::gpu_wgpu::CHAIN_WAIT_NS,
1098 &crate::gpu_wgpu::CHAIN_LAYERS,
1099 &crate::gpu_wgpu::CHAIN_RUNS,
1100 &crate::gpu_wgpu::SUBMITS,
1101 &crate::gpu_wgpu::PASSES,
1102 ] {
1103 a.store(0, Ordering::Relaxed);
1104 }
1105 }
1106 }
1107 }
1108 static REPORT: AtomicBool = AtomicBool::new(false);
1109 static ZEROED: AtomicBool = AtomicBool::new(false);
1111
1112 pub fn on() -> bool {
1113 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1114 *ON.get_or_init(|| std::env::var("CMF_DSV4_PROFILE").is_ok_and(|v| v != "0"))
1115 }
1116
1117 pub fn report() {
1120 if !on() || REPORT.swap(true, Ordering::Relaxed) {
1121 return;
1122 }
1123 let calls = CALLS.load(Ordering::Relaxed).max(1);
1127 let toks = TOKENS.load(Ordering::Relaxed).max(1);
1128 let (a, m) = (
1129 ATTN_NS.load(Ordering::Relaxed) as f64 / 1e6,
1130 MOE_NS.load(Ordering::Relaxed) as f64 / 1e6,
1131 );
1132 let all = ALL_NS.load(Ordering::Relaxed) as f64 / 1e6;
1133 let hc = (HC_NS.load(Ordering::Relaxed) as f64 / 1e6
1140 - MOE_NS.load(Ordering::Relaxed) as f64 / 1e6)
1141 .max(0.0);
1142 let hd = HEAD_NS.load(Ordering::Relaxed) as f64 / 1e6;
1143 let prep = PREP_NS.load(Ordering::Relaxed) as f64 / 1e6;
1144 let cw = CACHEW_NS.load(Ordering::Relaxed) as f64 / 1e6;
1145 eprintln!(
1146 "[dsv4-профиль] ХОСТ-ПРЕП слоя: {:.0} мс/токен, KV-заливки: {:.0} мс/токен",
1147 prep / toks as f64,
1148 cw / toks as f64
1149 );
1150 #[cfg(feature = "gpu")]
1151 {
1152 let f = crate::gpu_wgpu::DSV4_FILLS.load(Ordering::Relaxed);
1153 let fb = crate::gpu_wgpu::DSV4_FILL_BYTES.load(Ordering::Relaxed);
1154 eprintln!(
1155 "[dsv4-профиль] ЗАЛИВКИ СЛОТОВ: {:.1} эксп/токен, {:.0} МБ/токен",
1156 f as f64 / toks as f64,
1157 fb as f64 / 1e6 / toks as f64
1158 );
1159 }
1160 eprintln!(
1161 "[dsv4-профиль] {calls} вызовов слоя за {toks} токенов | \
1162 на токен: внимание {:.0} мс, MoE {:.0} мс, гипер-связи+нормы {:.0} мс, \
1163 голова {:.0} мс | на вызов: внимание {:.2}, MoE {:.2}, связи {:.2}",
1164 a / toks as f64,
1165 m / toks as f64,
1166 hc / toks as f64,
1167 hd / toks as f64,
1168 a / calls as f64,
1169 m / calls as f64,
1170 hc / calls as f64,
1171 );
1172 eprintln!(
1173 "[dsv4-профиль] весь проход {:.0} мс на токен; вне счётчиков {:.0} мс",
1174 all / toks as f64,
1175 (all - a - m - hd) / toks as f64,
1176 );
1177 #[cfg(feature = "gpu")]
1178 {
1179 let ae = crate::gpu_wgpu::ATT_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6;
1180 let aw = crate::gpu_wgpu::ATT_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6;
1181 if ae + aw > 0.0 {
1182 eprintln!(
1183 "[dsv4-профиль] кадр внимания на вызов: кодирование {:.2} мс, \
1184 отправка и ожидание {:.2} мс",
1185 ae / calls as f64,
1186 aw / calls as f64,
1187 );
1188 }
1189 let ub = crate::gpu_wgpu::UPLOAD_BYTES.load(Ordering::Relaxed);
1194 let un = crate::gpu_wgpu::UPLOAD_NS.load(Ordering::Relaxed);
1195 if ub > 0 && un > 0 {
1196 eprintln!(
1197 "[dsv4-профиль] ЗАЛИВКА весов: {:.1} ГБ за {:.1} с ({:.0} МБ/с)",
1198 ub as f64 / 1e9,
1199 un as f64 / 1e9,
1200 ub as f64 / (un as f64 / 1e9) / 1e6,
1201 );
1202 }
1203 let sub = crate::gpu_wgpu::SUBMITS.load(Ordering::Relaxed);
1204 if sub > 0 {
1205 eprintln!(
1206 "[dsv4-профиль] ОТПРАВОК на карту: {:.1} на токен, ПРОХОДОВ {:.0} \
1207 ({:.1} на слой)",
1208 sub as f64 / toks as f64,
1209 crate::gpu_wgpu::PASSES.load(Ordering::Relaxed) as f64 / toks as f64,
1210 crate::gpu_wgpu::PASSES.load(Ordering::Relaxed) as f64 / calls as f64,
1211 );
1212 }
1213 let cl = crate::gpu_wgpu::CHAIN_LAYERS.load(Ordering::Relaxed);
1214 if cl > 0 {
1215 let toks2 = toks.max(1) as f64;
1216 eprintln!(
1217 "[dsv4-профиль] ЦЕПОЧКА на токен: кодирование {:.2} мс, \
1218 ожидание {:.2} мс ({} слоёв, {} отправок)",
1219 crate::gpu_wgpu::CHAIN_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6 / toks2,
1220 crate::gpu_wgpu::CHAIN_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6 / toks2,
1221 cl / toks.max(1),
1222 crate::gpu_wgpu::CHAIN_RUNS.load(Ordering::Relaxed) / toks.max(1),
1223 );
1224 }
1225 let e = crate::gpu_wgpu::MOE_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6;
1226 let wt = crate::gpu_wgpu::MOE_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6;
1227 if e + wt > 0.0 {
1228 let ns = |a: &std::sync::atomic::AtomicU64| {
1229 a.load(Ordering::Relaxed) as f64 / 1e6 / calls as f64
1230 };
1231 eprintln!(
1232 "[dsv4-профиль] кадр MoE на вызов: кодирование {:.2} мс, \
1233 отправка и ожидание {:.2} мс",
1234 e / calls as f64,
1235 wt / calls as f64,
1236 );
1237 let an = crate::gpu_wgpu::ATT_GPU_N.load(Ordering::Relaxed);
1238 if an > 0 {
1239 let g = |i: usize| {
1240 crate::gpu_wgpu::ATT_GPU_NS[i].load(Ordering::Relaxed) as f64
1241 / 1e6
1242 / an as f64
1243 };
1244 eprintln!(
1245 "[dsv4-профиль] ВНИМАНИЕ НА КАРТЕ на вызов: одиночное {:.3} мс, \
1246 оценки {:.3} мс, применение {:.3} мс",
1247 g(0),
1248 g(1),
1249 g(2),
1250 );
1251 }
1252 let gn = crate::gpu_wgpu::MOE_GPU_N.load(Ordering::Relaxed);
1253 let gns = crate::gpu_wgpu::MOE_GPU_NS[0].load(Ordering::Relaxed);
1254 if gn > 0 && gns > 0 {
1255 eprintln!(
1256 "[dsv4-профиль] MoE НА КАРТЕ: {:.3} мс на вызов ({gn} замеров)",
1257 gns as f64 / 1e6 / gn as f64,
1258 );
1259 } else if gn > 0 {
1260 eprintln!(
1264 "[dsv4-профиль] MoE НА КАРТЕ: метки вернули НОЛЬ на {gn} замерах — \
1265 запрос времени не сработал, число не использовать"
1266 );
1267 }
1268 eprintln!(
1269 "[dsv4-профиль] из кодирования: буферы экспертов {:.2} мс, \
1270 загрузки {:.2} мс, проходы {:.2} мс",
1271 ns(&crate::gpu_wgpu::MOE_BUFS_NS),
1272 ns(&crate::gpu_wgpu::MOE_UP_NS),
1273 ns(&crate::gpu_wgpu::MOE_PASS_NS),
1274 );
1275 }
1276 }
1277 }
1278}
1279
1280pub fn profile_report() {
1282 prof::report();
1283}
1284
1285fn gpu_attn_enabled() -> bool {
1290 #[cfg(feature = "gpu")]
1291 {
1292 use std::sync::OnceLock;
1293 static ON: OnceLock<bool> = OnceLock::new();
1294 *ON.get_or_init(|| {
1295 let want = std::env::var("CMF_DSV4_GPU_ATTN")
1296 .map(|v| v != "0")
1297 .unwrap_or(true);
1298 let have = want && crate::gpu::backend_available();
1299 if want && !have && std::env::var("CMF_DSV4_GPU_ATTN").is_ok() {
1300 tracing::warn!(
1301 "CMF_DSV4_GPU_ATTN задан, но устройства нет — блок внимания остаётся на CPU. Проверьте CMF_GPU=wgpu и Vulkan-ICD."
1302 );
1303 }
1304 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
1305 eprintln!("кадр dsv4: запрошен={want} доступен={have}");
1306 }
1307 have
1308 })
1309 }
1310 #[cfg(not(feature = "gpu"))]
1311 {
1312 false
1313 }
1314}
1315
1316#[cfg(feature = "gpu")]
1320#[allow(clippy::too_many_arguments)]
1321fn attn_frame(
1322 l: &Dsv4Layer,
1323 cfg: &Dsv4Cfg,
1324 st: &Dsv4State,
1325 li: usize,
1326 hidden: &[f32],
1327 qn: &[f32],
1328 idxs: &[usize],
1329 inv_freq: &[f32],
1330 pos: usize,
1331 win_len: usize,
1332 scale: f32,
1333 hc: Option<&crate::gpu_wgpu::Dsv4HcTail>,
1336 out: &mut [f32],
1337) -> bool {
1338 let hd = cfg.head_dim;
1339 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
1340 l.wq_a.model_idx(),
1341 l.wq_b.model_idx(),
1342 l.wo_a.model_idx(),
1343 l.wo_b.model_idx(),
1344 ) else {
1345 return false;
1346 };
1347 let Some(model) = l.wq_b.model_arc() else {
1348 return false;
1349 };
1350 let n_comp = st.compressed[li].len() / hd;
1355 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
1356 let kv_id = st.kv_id;
1357 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap) {
1363 return false;
1364 }
1365 if n_comp > 0
1374 && !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, cfg.window * hd, &st.compressed[li], cap)
1375 {
1376 return false;
1377 }
1378 let idx32: Vec<u32> = idxs
1379 .iter()
1380 .map(|&p| {
1381 if p < win_len {
1382 p as u32
1383 } else {
1384 (cfg.window + (p - win_len)) as u32
1385 }
1386 })
1387 .collect();
1388 let w = crate::gpu_wgpu::Dsv4AttnW {
1389 wq_a,
1390 wq_b,
1391 wo_a,
1392 wo_b,
1393 q_norm: &l.q_norm,
1394 sink: &l.attn_sink,
1395 };
1396 let g = crate::gpu_wgpu::Dsv4AttnGeom {
1397 dim: cfg.dim,
1398 nh: cfg.n_heads,
1399 hd,
1400 rd: cfg.rope_head_dim,
1401 q_lora: cfg.q_lora_rank,
1402 o_lora: cfg.o_lora_rank,
1403 o_groups: cfg.o_groups,
1404 eps: cfg.norm_eps,
1405 scale,
1406 };
1407 crate::gpu_wgpu::dsv4_attn_frame(
1415 &model,
1416 &w,
1417 g,
1418 hidden,
1419 Some(qn),
1420 kv_id,
1421 li,
1422 &idx32,
1423 inv_freq,
1424 pos,
1425 hc,
1426 out,
1427 )
1428}
1429
1430#[derive(Default)]
1433pub struct AttnPrep {
1434 pub qr: Vec<f32>,
1435 pub idxs: Vec<usize>,
1436 pub win_len: usize,
1437}
1438
1439#[allow(clippy::too_many_arguments)]
1440pub fn attention_step(
1441 hidden: &[f32],
1442 l: &Dsv4Layer,
1443 cfg: &Dsv4Cfg,
1444 st: &mut Dsv4State,
1445 li: usize,
1446 inv_freq: &[f32],
1448 pool: Option<&crate::pool::Pool>,
1449 prep_out: Option<&mut AttnPrep>,
1453 out: &mut [f32],
1454) {
1455 let _t0 = prof::on().then(std::time::Instant::now);
1456 let _guard = scopeguard_attn(_t0);
1457 let (hd, rd) = (cfg.head_dim, cfg.rope_head_dim);
1458 let pos = st.pos;
1459 if std::env::var("CMF_FREQ_DEBUG").is_ok() && li == 0 && pos == 0 {
1460 eprintln!(
1461 " [порт] rd={rd} частот={} inv_freq[0..4]={:?}",
1462 inv_freq.len(),
1463 &inv_freq[..4.min(inv_freq.len())]
1464 );
1465 }
1466
1467 let mut qr = vec![0.0f32; cfg.q_lora_rank];
1472 let mut kv = vec![0.0f32; hd];
1473 crate::qtensor::QTensor::matvec_many([&l.wq_a, &l.wkv], hidden, [&mut qr, &mut kv], pool);
1474 rms_weighted(&mut qr, &l.q_norm, cfg.norm_eps);
1475 let split_config = st.partial_set.iter().any(|&p| p) && st.split_deep;
1493 let past_chain =
1494 st.dev_owned && (li >= st.dev_set.len() || !st.dev_set.get(li).copied().unwrap_or(false));
1495 if std::env::var("CMF_DSV4_GATE_DBG").is_ok() {
1496 eprintln!(
1497 "[gate] li={li} pos={} split={split_config} past={past_chain} dev_owned={} set_len={} part_len={}",
1498 st.pos,
1499 st.dev_owned,
1500 st.dev_set.len(),
1501 st.partial_set.len()
1502 );
1503 }
1504 let on_gpu = gpu_attn_enabled() && !(split_config && past_chain);
1505
1506 rms_weighted(&mut kv, &l.kv_norm, cfg.norm_eps);
1507 rope_tail(&mut kv, inv_freq, pos, rd, false);
1508
1509 if let Some(cp) = &l.compressor {
1514 let mut pk = std::mem::take(&mut st.pending_kv[li]);
1515 let mut ps = std::mem::take(&mut st.pending_score[li]);
1516 let mut qk = std::mem::take(&mut st.prev_kv[li]);
1517 let mut qs = std::mem::take(&mut st.prev_score[li]);
1518 let entry = compressor_step(
1519 cp,
1520 hidden,
1521 pos,
1522 rd,
1523 cfg.norm_eps,
1524 inv_freq,
1525 pool,
1526 &mut pk,
1527 &mut ps,
1528 &mut qk,
1529 &mut qs,
1530 );
1531 st.pending_kv[li] = pk;
1532 st.pending_score[li] = ps;
1533 st.prev_kv[li] = qk;
1534 st.prev_score[li] = qs;
1535 if let Some(e) = entry {
1536 st.compressed[li].extend_from_slice(&e);
1537 }
1538 }
1539 if let Some(ix) = &l.indexer {
1544 let mut pk = std::mem::take(&mut st.pending_ix_kv[li]);
1545 let mut ps = std::mem::take(&mut st.pending_ix_score[li]);
1546 let mut qk = std::mem::take(&mut st.prev_ix_kv[li]);
1547 let mut qs = std::mem::take(&mut st.prev_ix_score[li]);
1548 let entry = compressor_step(
1549 &ix.compressor,
1550 hidden,
1551 pos,
1552 rd,
1553 cfg.norm_eps,
1554 inv_freq,
1555 pool,
1556 &mut pk,
1557 &mut ps,
1558 &mut qk,
1559 &mut qs,
1560 );
1561 st.pending_ix_kv[li] = pk;
1562 st.pending_ix_score[li] = ps;
1563 st.prev_ix_kv[li] = qk;
1564 st.prev_ix_score[li] = qs;
1565 if let Some(e) = entry {
1566 st.index_kv[li].extend_from_slice(&e);
1567 }
1568 }
1569
1570 st.window[li].extend_from_slice(&kv);
1571 let cap = cfg.window * hd;
1575 if st.window[li].len() > cap {
1576 let drop = st.window[li].len() - cap;
1577 st.window[li].drain(..drop);
1578 }
1579 let win_len = st.window[li].len() / hd;
1580 let n_pos = win_len + st.compressed[li].len() / hd;
1581
1582 let mut idxs: Vec<usize> = (0..win_len).collect();
1591 if !st.compressed[li].is_empty() && !no_compressed() {
1592 let n_comp = st.compressed[li].len() / hd;
1593 match &l.indexer {
1594 Some(ix) => {
1595 let ih = ix.weights_proj.rows();
1611 let idim = ix.wq_b.rows() / ih.max(1);
1612 let mut qi = vec![0.0f32; ix.wq_b.rows()];
1613 ix.wq_b.matvec(&qr, &mut qi, pool);
1614 for h in 0..ih {
1615 rope_tail(&mut qi[h * idim..(h + 1) * idim], inv_freq, pos, rd, false);
1616 }
1617 let mut hw = vec![0.0f32; ih];
1618 ix.weights_proj.matvec(hidden, &mut hw, pool);
1619 let sc_factor = (idim as f32).powf(-0.5) * (ih as f32).powf(-0.5);
1620 for w in hw.iter_mut() {
1621 *w *= sc_factor;
1622 }
1623 let n_ix = st.index_kv[li].len() / idim.max(1);
1624 let mut sc = Vec::new();
1625 index_scores(
1626 &qi,
1627 &st.index_kv[li],
1628 &hw,
1629 ih,
1630 idim,
1631 n_ix.min(n_comp),
1632 n_ix.min(n_comp),
1633 pool,
1634 &mut sc,
1635 );
1636 let mut picked = Vec::new();
1637 top_k_positions(&sc, cfg.index_topk, &mut picked);
1638 idxs.extend(picked.into_iter().map(|p| win_len + p));
1639 }
1640 None => idxs.extend((0..n_comp).map(|p| win_len + p)),
1641 }
1642 }
1643 debug_assert!(idxs.iter().all(|&p| p < n_pos));
1644 if let Some(p) = prep_out {
1645 p.qr = qr;
1646 p.idxs = idxs;
1647 p.win_len = win_len;
1648 return;
1649 }
1650
1651 let scale = (hd as f32).powf(-0.5);
1653 #[cfg(feature = "gpu")]
1654 if on_gpu
1655 && {
1656 if std::env::var("CMF_DSV4_XCHK").is_ok() {
1657 if let Some(card) = crate::gpu_wgpu::dsv4_dbg_read_tag(45, 0, hidden.len()) {
1661 let md = hidden
1662 .iter()
1663 .zip(card.iter())
1664 .map(|(a, b)| (a - b).abs())
1665 .fold(0.0f32, f32::max);
1666 eprintln!("[xchk] li={li} pos={pos} x2 maxdiff={md:.3e}");
1667 }
1668 }
1669 true
1670 }
1671 && attn_frame(
1672 l, cfg, st, li, hidden, &qr, &idxs, inv_freq, pos, win_len, scale, None, out,
1673 )
1674 {
1675 return;
1676 }
1677
1678 let mut q = vec![0.0f32; cfg.n_heads * hd];
1680 l.wq_b.matvec(&qr, &mut q, pool);
1681 for h in 0..cfg.n_heads {
1682 let head = &mut q[h * hd..(h + 1) * hd];
1683 rms_inplace(head, cfg.norm_eps);
1684 rope_tail(head, inv_freq, pos, rd, false);
1685 }
1686 let mut cache: Vec<f32> = st.window[li].clone();
1687 cache.extend_from_slice(&st.compressed[li]);
1688
1689 let mut attn = vec![0.0f32; cfg.n_heads * hd];
1691 for h in 0..cfg.n_heads {
1692 let qh = &q[h * hd..(h + 1) * hd];
1693 let oh = &mut attn[h * hd..(h + 1) * hd];
1698 sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
1699 rope_tail(oh, inv_freq, pos, rd, true);
1700 }
1701
1702 o_project(
1708 &attn,
1709 &|r, x, sc| l.wo_a.row_dot(r, x, sc),
1710 l.wo_a.cols(),
1711 &|mid, dst| l.wo_b.matvec(mid, dst, pool),
1712 cfg.o_groups,
1713 cfg.o_lora_rank,
1714 pool,
1715 out,
1716 );
1717}
1718
1719pub fn rms_weighted(v: &mut [f32], w: &[f32], eps: f32) {
1721 let ms = v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32;
1722 let inv = 1.0 / (ms + eps).sqrt();
1723 for (x, g) in v.iter_mut().zip(w) {
1724 *x = *x * inv * g;
1725 }
1726}
1727
1728thread_local! {
1743 static ROUTE_COUNTS: std::cell::RefCell<Vec<Vec<u64>>> =
1744 const { std::cell::RefCell::new(Vec::new()) };
1745}
1746
1747fn route_stats_on() -> bool {
1748 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1749 *ON.get_or_init(|| std::env::var("CMF_MOE_STATS").is_ok())
1750}
1751
1752fn record_route(
1753 li: usize,
1754 n_layers_hint: usize,
1755 n_experts: usize,
1756 routed: &[(usize, f32)],
1757) {
1758 ROUTE_COUNTS.with(|c| {
1759 let mut c = c.borrow_mut();
1760 if c.len() <= li.max(n_layers_hint) {
1761 c.resize(li.max(n_layers_hint) + 1, Vec::new());
1762 }
1763 let row = &mut c[li];
1764 if row.len() < n_experts {
1765 row.resize(n_experts, 0);
1766 }
1767 for &(e, weight) in routed {
1768 if e < row.len() {
1769 let mass = (weight.abs() as f64 * 1_000_000.0).round() as u64;
1773 row[e] = row[e].saturating_add(mass.max(1));
1774 }
1775 }
1776 });
1777}
1778
1779pub fn take_route_counts() -> Vec<Vec<u64>> {
1781 ROUTE_COUNTS.with(|c| std::mem::take(&mut *c.borrow_mut()))
1782}
1783
1784struct Charge(
1788 Option<std::time::Instant>,
1789 &'static std::sync::atomic::AtomicU64,
1790);
1791impl Drop for Charge {
1792 fn drop(&mut self) {
1793 if let Some(t) = self.0 {
1794 self.1.fetch_add(
1795 t.elapsed().as_nanos() as u64,
1796 std::sync::atomic::Ordering::Relaxed,
1797 );
1798 }
1799 }
1800}
1801fn scopeguard_attn(t: Option<std::time::Instant>) -> Charge {
1802 Charge(t, &prof::ATTN_NS)
1803}
1804fn scopeguard_moe(t: Option<std::time::Instant>, li: usize) -> Charge {
1805 if t.is_some() {
1806 prof::note_layer(li);
1807 }
1808 Charge(t, &prof::MOE_NS)
1809}
1810
1811#[cfg(feature = "gpu")]
1815#[allow(clippy::too_many_arguments)]
1816fn dsv4_layer_loop(
1817 state: &mut [f32],
1818 layers: &[Dsv4Layer],
1819 g: &Dsv4Globals,
1820 cfg: &Dsv4Cfg,
1821 st: &mut Dsv4State,
1822 token_id: u32,
1823 inv_freq: &[f32],
1824 pool: Option<&crate::pool::Pool>,
1825 scratch: &mut HcScratch,
1826) -> bool {
1827 let dim = cfg.dim;
1828 let freqs_of = |l: &Dsv4Layer| -> &[f32] {
1829 let f = if l.compressor.is_some() {
1830 &g.inv_freq_compress
1831 } else {
1832 &g.inv_freq_window
1833 };
1834 if f.is_empty() { inv_freq } else { f.as_slice() }
1835 };
1836 {
1849 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1850 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
1851 if let (Some(idx), Some(model)) = (g.head.model_idx(), g.head.model_arc()) {
1852 let ok = crate::gpu_wgpu::dsv4_weight_ready(&model, idx);
1853 tracing::info!("dsv4: голова на карте: {}", if ok { "да" } else { "нет" });
1854 }
1855 }
1856 }
1857 let mut on_dev = vec![false; layers.len()];
1858 let mut partial_dev = vec![false; layers.len()];
1859 let mut attn_ready = vec![false; layers.len()];
1860 for (li, l) in layers.iter().enumerate() {
1865 if l.wq_a.model_idx().is_none()
1866 || l.wq_b.model_idx().is_none()
1867 || l.wo_a.model_idx().is_none()
1868 || l.wo_b.model_idx().is_none()
1869 {
1870 return false;
1871 }
1872 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
1873 return false;
1874 };
1875 let mut want = vec![
1890 l.wq_a.model_idx(),
1891 l.wq_b.model_idx(),
1892 l.wo_a.model_idx(),
1893 l.wo_b.model_idx(),
1894 ];
1895 if chain_enabled() {
1896 want.push(l.wkv.model_idx());
1897 if let Some(cp) = &l.compressor {
1898 want.push(cp.wkv.model_idx());
1899 want.push(cp.wgate.model_idx());
1900 }
1901 if let Some(ix) = &l.indexer {
1902 want.push(ix.wq_b.model_idx());
1903 want.push(ix.weights_proj.model_idx());
1904 want.push(ix.compressor.wkv.model_idx());
1905 want.push(ix.compressor.wgate.model_idx());
1906 }
1907 }
1908 attn_ready[li] = want
1909 .into_iter()
1910 .flatten()
1911 .all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
1912 }
1913 for (li, l) in layers.iter().enumerate() {
1914 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
1915 return false;
1916 };
1917 let gu_q2 = l
1918 .experts
1919 .first()
1920 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
1921 let pk = pack_for(l, cfg, li);
1925 if let Some(pk) = pk {
1926 let dn_q2 = l
1927 .experts
1928 .first()
1929 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
1930 let experts_ok = if pk.global.is_some() {
1931 crate::gpu_wgpu::dsv4_global_moe_ready(&model)
1932 } else {
1933 crate::gpu_wgpu::dsv4_experts_ready(
1934 &model,
1935 &pk.tensors,
1936 cfg.moe_inter,
1937 dim,
1938 gu_q2,
1939 dn_q2,
1940 )
1941 };
1942 on_dev[li] = attn_ready[li] && experts_ok && pk.route_complete();
1943 partial_dev[li] = attn_ready[li] && experts_ok && !pk.route_complete();
1944 }
1945 }
1946 let active_dev: Vec<bool> = on_dev
1947 .iter()
1948 .zip(&partial_dev)
1949 .map(|(&full, &partial)| full || partial)
1950 .collect();
1951 if !active_dev.iter().any(|&x| x) {
1952 return false;
1953 }
1954 if st.partial_set.len() != partial_dev.len() || st.partial_set != partial_dev {
1959 st.partial_set = partial_dev.clone();
1960 st.split_deep = active_dev
1961 .iter()
1962 .zip(&partial_dev)
1963 .filter(|(a, p)| !**a || **p)
1964 .count()
1965 > 1;
1966 }
1967
1968 {
1972 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1973 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
1974 let host: Vec<usize> = active_dev
1975 .iter()
1976 .enumerate()
1977 .filter(|&(_, d)| !*d)
1978 .map(|(i, _)| i)
1979 .collect();
1980 let partial: Vec<(usize, usize)> = partial_dev
1981 .iter()
1982 .enumerate()
1983 .filter(|&(_, d)| *d)
1984 .filter_map(|(li, _)| pack_for(&layers[li], cfg, li).map(|p| (li, p.globals.len())))
1985 .collect();
1986 if host.is_empty() && partial.is_empty() {
1987 tracing::info!("dsv4: все {} слоёв на карте", on_dev.len());
1988 } else {
1989 tracing::info!(
1990 "dsv4: {} из {} слоёв используют карту; частичные {:?}; на хосте {:?}",
1991 active_dev.len() - host.len(),
1992 on_dev.len(),
1993 partial,
1994 host,
1995 );
1996 }
1997 }
1998 }
1999
2000 let (mut folded, post0, comb0) = hc_fold_norm(
2002 state,
2003 &layers[0].hc_attn_fn,
2004 &layers[0].hc_attn_scale,
2005 &layers[0].hc_attn_base,
2006 &layers[0].attn_norm,
2007 cfg,
2008 pool,
2009 );
2010 if !crate::gpu_wgpu::dsv4_state_write(state) || !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
2011 {
2012 return false;
2013 }
2014 if st.dev_owned && st.dev_set != active_dev {
2025 let left: Vec<usize> = (0..active_dev.len().min(st.dev_set.len()))
2026 .filter(|&i| st.dev_set[i] && !active_dev[i])
2027 .collect();
2028 if !left.is_empty() {
2029 tracing::warn!("слои {left:?} ушли с карты — кеши на разных сторонах");
2030 return false;
2031 }
2032 }
2036 let chain = chain_enabled();
2037 let layer_cap = {
2045 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
2046 *N.get_or_init(|| {
2047 std::env::var("CMF_DSV4_LAYERS_PROBE")
2048 .ok()
2049 .and_then(|v| v.parse::<usize>().ok())
2050 .unwrap_or(usize::MAX)
2051 })
2052 };
2053 let mut run: Vec<usize> = Vec::new();
2054 let mut sink_out = vec![0.0f32; dim];
2055 let mut state_on_host = true;
2060 for (li, l) in layers.iter().enumerate() {
2061 if li >= layer_cap {
2062 break;
2063 }
2064 if prof::on() {
2070 prof::note_layer(li);
2071 }
2072 if chain && on_dev[li] {
2073 run.push(li);
2078 if run.len() >= chain_max() || dspark_wants(li) {
2083 let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
2084 let captured = *run.last().unwrap();
2085 if !dsv4_chain_run(
2086 layers,
2087 &run,
2088 cfg,
2089 g,
2090 st,
2091 token_id,
2092 &mut folded,
2093 Some(state),
2094 1,
2095 &[],
2096 need_qn,
2097 pool,
2098 ) {
2099 return false;
2100 }
2101 state_on_host = true;
2102 verify_fp("walk", st.pos, captured, state);
2103 dspark_note(captured, state, cfg);
2104 run.clear();
2105 }
2106 continue;
2107 }
2108 if chain && !run.is_empty() {
2109 if !dsv4_chain_run(
2113 layers,
2114 &run,
2115 cfg,
2116 g,
2117 st,
2118 token_id,
2119 &mut folded,
2120 Some(state),
2121 1,
2122 &[],
2123 run[0] == 0 || !on_dev[run[0] - 1],
2124 pool,
2125 ) {
2126 return false;
2127 }
2128 state_on_host = true;
2129 let last = *run.last().unwrap();
2130 verify_fp("walk", st.pos, last, state);
2131 dspark_note(last, state, cfg);
2132 }
2133 run.clear();
2134 if partial_dev[li] && chain1_on() {
2135 if let Some(home) = dsv4_chain1_layer(
2136 state,
2137 &mut folded,
2138 layers,
2139 l,
2140 cfg,
2141 st,
2142 token_id,
2143 li,
2144 freqs_of(l),
2145 pool,
2146 state_on_host,
2147 ) {
2148 st.dev_filled[li] = (st.window[li].len() / cfg.head_dim).min(cfg.window);
2156 st.dev_n_comp[li] = st.compressed[li].len() / cfg.head_dim;
2157 st.dev_n_ix[li] = l.indexer.as_ref().map_or(0, |ix| {
2158 let ih = ix.weights_proj.rows();
2159 let idim = ix.wq_b.rows() / ih.max(1);
2160 st.index_kv[li].len() / idim.max(1)
2161 });
2162 state_on_host = home;
2163 if verify_fp_on(st.pos) {
2164 if !state_on_host && !crate::gpu_wgpu::dsv4_state_read(state) {
2165 return false;
2166 }
2167 state_on_host = true;
2168 verify_fp("walk", st.pos, li, state);
2169 }
2170 if dspark_wants(li) {
2174 if !state_on_host && crate::gpu_wgpu::dsv4_state_read(state) {
2175 state_on_host = true;
2176 }
2177 if state_on_host {
2178 dspark_note(li, state, cfg);
2179 }
2180 }
2181 continue;
2182 }
2183 }
2184 if partial_dev[li] && partial_walk_on() {
2185 let Some(home) = dsv4_partial_layer(
2190 state,
2191 &mut folded,
2192 layers,
2193 l,
2194 cfg,
2195 st,
2196 token_id,
2197 li,
2198 freqs_of(l),
2199 pool,
2200 state_on_host,
2201 ) else {
2202 return false;
2203 };
2204 state_on_host = home;
2205 if home {
2206 dspark_note(li, state, cfg);
2207 }
2208 continue;
2209 }
2210 if !on_dev[li] {
2211 if !state_on_host && !crate::gpu_wgpu::dsv4_state_read(state) {
2212 return false;
2213 }
2214 state_on_host = true;
2215 let freqs = freqs_of(l);
2216 hc_block(
2217 state,
2218 &l.hc_attn_fn,
2219 &l.hc_attn_scale,
2220 &l.hc_attn_base,
2221 &l.attn_norm,
2222 cfg,
2223 scratch,
2224 pool,
2225 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
2226 );
2227 hc_block(
2228 state,
2229 &l.hc_ffn_fn,
2230 &l.hc_ffn_scale,
2231 &l.hc_ffn_base,
2232 &l.ffn_norm,
2233 cfg,
2234 scratch,
2235 pool,
2236 |f, o| {
2243 if host_cpu_moe() {
2244 crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
2245 } else {
2246 moe_step(f, l, cfg, token_id, li, pool, o)
2247 }
2248 },
2249 );
2250 if layers.get(li + 1).is_some() && on_dev.get(li + 1).copied().unwrap_or(false) {
2255 let n = &layers[li + 1];
2256 let (f, p2, c2) = hc_fold_norm(
2257 state,
2258 &n.hc_attn_fn,
2259 &n.hc_attn_scale,
2260 &n.hc_attn_base,
2261 &n.attn_norm,
2262 cfg,
2263 pool,
2264 );
2265 folded = f;
2266 if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2) {
2267 return false;
2268 }
2269 if !crate::gpu_wgpu::dsv4_state_write(state) {
2270 return false;
2271 }
2272 }
2273 dspark_note(li, state, cfg);
2274 continue;
2275 }
2276 let mut prep = AttnPrep::default();
2277 let _tp = prof::on().then(std::time::Instant::now);
2278 attention_step(
2279 &folded,
2280 l,
2281 cfg,
2282 st,
2283 li,
2284 freqs_of(l),
2285 pool,
2286 Some(&mut prep),
2287 &mut sink_out,
2288 );
2289 if let Some(t) = _tp {
2290 prof::PREP_NS.fetch_add(
2291 t.elapsed().as_nanos() as u64,
2292 std::sync::atomic::Ordering::Relaxed,
2293 );
2294 }
2295 let hd = cfg.head_dim;
2297 let n_comp = st.compressed[li].len() / hd;
2298 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2299 let kv_id = st.kv_id;
2300 let _tc = prof::on().then(std::time::Instant::now);
2301 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap)
2302 || (n_comp > 0
2303 && !crate::gpu_wgpu::dsv4_cache_write(
2304 kv_id,
2305 li,
2306 cfg.window * hd,
2307 &st.compressed[li],
2308 cap,
2309 ))
2310 {
2311 return false;
2312 }
2313 if let Some(t) = _tc {
2314 prof::CACHEW_NS.fetch_add(
2315 t.elapsed().as_nanos() as u64,
2316 std::sync::atomic::Ordering::Relaxed,
2317 );
2318 }
2319 let idx32: Vec<u32> = prep
2320 .idxs
2321 .iter()
2322 .map(|&p| {
2323 if p < prep.win_len {
2324 p as u32
2325 } else {
2326 (cfg.window + (p - prep.win_len)) as u32
2327 }
2328 })
2329 .collect();
2330 let Some(pk) = pack_for(l, cfg, li) else {
2331 return false;
2332 };
2333 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
2334 l.wq_a.model_idx(),
2335 l.wq_b.model_idx(),
2336 l.wo_a.model_idx(),
2337 l.wo_b.model_idx(),
2338 ) else {
2339 return false;
2340 };
2341 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
2342 return false;
2343 };
2344 let forced: Option<Vec<usize>> = l.tid2eid.as_ref().and_then(|tbl| {
2345 let v: Vec<usize> = if pk.needs_remap() {
2346 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
2347 } else {
2348 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
2349 .into_iter()
2350 .map(|gi| pk.to_slot[gi])
2351 .collect()
2352 };
2353 if v.contains(&usize::MAX) {
2354 None
2355 } else {
2356 Some(v)
2357 }
2358 });
2359 if l.tid2eid.is_some() && forced.is_none() {
2360 return false;
2361 }
2362 let nxt = layers.get(li + 1);
2363 let w = crate::gpu_wgpu::Dsv4LayerW {
2364 attn: crate::gpu_wgpu::Dsv4AttnW {
2365 wq_a,
2366 wq_b,
2367 wo_a,
2368 wo_b,
2369 q_norm: &l.q_norm,
2370 sink: &l.attn_sink,
2371 },
2372 moe: crate::gpu_wgpu::Dsv4MoeW {
2373 router: &[],
2374 experts: &pk.tensors,
2375 logits: &[],
2376 bias: pk.bias.as_deref(),
2383 mask: pk.mask.as_deref(),
2384 forced: forced.as_deref(),
2385 remap: pk.needs_remap().then_some(pk.remap.as_slice()),
2386 global: None,
2387 },
2388 hc_ffn_fn: &l.hc_ffn_fn,
2389 hc_ffn_scale: &l.hc_ffn_scale,
2390 hc_ffn_base: &l.hc_ffn_base,
2391 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2392 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2393 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2394 ffn_norm: &l.ffn_norm,
2395 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2396 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2397 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2398 router: &pk.router,
2399 };
2400 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2401 attn: crate::gpu_wgpu::Dsv4AttnGeom {
2402 dim,
2403 nh: cfg.n_heads,
2404 hd,
2405 rd: cfg.rope_head_dim,
2406 q_lora: cfg.q_lora_rank,
2407 o_lora: cfg.o_lora_rank,
2408 o_groups: cfg.o_groups,
2409 eps: cfg.norm_eps,
2410 scale: (hd as f32).powf(-0.5),
2411 },
2412 moe: crate::gpu_wgpu::Dsv4MoeGeom {
2413 hidden: dim,
2414 inter: cfg.moe_inter,
2415 top_k: cfg.top_k,
2416 route_scale: cfg.route_scale,
2417 swiglu_limit: cfg.swiglu_limit,
2418 gu_q2: l.experts.first().is_some_and(|e| {
2419 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
2420 }),
2421 },
2422 hc: cfg.hc_mult,
2423 hc_eps: cfg.hc_eps,
2424 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2425 };
2426 let mut next = vec![0.0f32; dim];
2427 if !crate::gpu_wgpu::dsv4_layer_frame(
2428 &model,
2429 &w,
2430 geom,
2431 kv_id,
2432 li,
2433 Some(&prep.qr),
2434 &idx32,
2435 freqs_of(l),
2436 st.pos,
2437 &mut next,
2438 None,
2439 None,
2440 &mut Vec::new(),
2441 ) {
2442 return false;
2443 }
2444 state_on_host = false;
2445 folded = next;
2446 dspark_note(li, state, cfg);
2447 }
2448 let mut state_home = false;
2449 if chain {
2450 if !run.is_empty() {
2451 let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
2455 let last_on_dev = *on_dev.last().unwrap_or(&false);
2456 let carry = last_on_dev && run.last() == Some(&(layers.len() - 1));
2457 let ok = if carry {
2458 let r = dsv4_chain_run(
2459 layers,
2460 &run,
2461 cfg,
2462 g,
2463 st,
2464 token_id,
2465 &mut folded,
2466 Some(state),
2467 1,
2468 &[],
2469 need_qn,
2470 pool,
2471 );
2472 state_home = r;
2473 state_on_host = r;
2474 if r {
2475 dspark_note(*run.last().unwrap(), state, cfg);
2476 }
2477 r
2478 } else {
2479 let r = dsv4_chain_run(
2480 layers,
2481 &run,
2482 cfg,
2483 g,
2484 st,
2485 token_id,
2486 &mut folded,
2487 None,
2488 1,
2489 &[],
2490 need_qn,
2491 pool,
2492 );
2493 if r {
2494 state_on_host = false;
2495 }
2496 r
2497 };
2498 if !ok {
2499 return false;
2500 }
2501 }
2502 if st.dev_set.is_empty() {
2503 st.dev_set = active_dev.clone();
2504 st.partial_set = partial_dev.clone();
2505 let mut idxs = Vec::new();
2511 for (li, l) in layers.iter().enumerate() {
2512 if !active_dev.get(li).copied().unwrap_or(false) {
2513 continue;
2514 }
2515 for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b, &l.gate] {
2516 idxs.extend(t.model_idx());
2517 }
2518 if let Some(pk) = pack_for(l, cfg, li) {
2519 for &(a, b, c) in &pk.tensors {
2520 idxs.extend([a, b, c]);
2521 }
2522 }
2523 }
2524 for (li, l) in layers.iter().enumerate() {
2532 if active_dev.get(li).copied().unwrap_or(false) {
2533 continue;
2534 }
2535 let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
2536 tracing::info!(
2537 "слой {li} на хосте: упаковано {packed} экспертов из {}",
2538 cfg.n_routed_experts
2539 );
2540 }
2541 let pinned = layers
2542 .iter()
2543 .find_map(|l| l.experts.first().and_then(|e| e.w1.model_arc()))
2544 .map_or(0, |m| crate::gpu_wgpu::pin_weights(&m, &idxs));
2545 tracing::info!(
2546 "закреплено на карте: {pinned} тензоров {} слоёв",
2547 on_dev.iter().filter(|&&x| x).count()
2548 );
2549 }
2550 }
2551 if state_home || state_on_host {
2552 return true;
2553 }
2554 crate::gpu_wgpu::dsv4_state_read(state)
2555}
2556
2557#[cfg(feature = "gpu")]
2562#[allow(clippy::too_many_arguments)]
2563fn dsv4_partial_layer(
2564 state: &mut [f32],
2565 folded: &mut Vec<f32>,
2566 layers: &[Dsv4Layer],
2567 l: &Dsv4Layer,
2568 cfg: &Dsv4Cfg,
2569 st: &mut Dsv4State,
2570 token_id: u32,
2571 li: usize,
2572 freqs: &[f32],
2573 pool: Option<&crate::pool::Pool>,
2574 state_on_host: bool,
2575) -> Option<bool> {
2576 let dim = cfg.dim;
2577 if state_on_host {
2586 let (f, post, comb) = hc_fold_norm(
2587 state,
2588 &l.hc_attn_fn,
2589 &l.hc_attn_scale,
2590 &l.hc_attn_base,
2591 &l.attn_norm,
2592 cfg,
2593 pool,
2594 );
2595 *folded = f;
2596 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2597 || !crate::gpu_wgpu::dsv4_state_write(state)
2598 {
2599 return None;
2600 }
2601 }
2602 let mut prep = AttnPrep::default();
2603 let mut sink = vec![0.0f32; dim];
2604 attention_step(
2605 folded,
2606 l,
2607 cfg,
2608 st,
2609 li,
2610 freqs,
2611 pool,
2612 Some(&mut prep),
2613 &mut sink,
2614 );
2615 let hd = cfg.head_dim;
2616 let n_comp = st.compressed[li].len() / hd;
2617 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2618 if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
2619 || (n_comp > 0
2620 && !crate::gpu_wgpu::dsv4_cache_write(
2621 st.kv_id,
2622 li,
2623 cfg.window * hd,
2624 &st.compressed[li],
2625 cap,
2626 ))
2627 {
2628 return None;
2629 }
2630 let a_tail = crate::gpu_wgpu::Dsv4HcTail {
2631 fn_: &l.hc_ffn_fn,
2632 scale: &l.hc_ffn_scale,
2633 base: &l.hc_ffn_base,
2634 norm: &l.ffn_norm,
2635 hc: cfg.hc_mult,
2636 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2637 hc_eps: cfg.hc_eps,
2638 eps: cfg.norm_eps,
2639 };
2640 let scale = (cfg.head_dim as f32).powf(-0.5);
2641 let mut ffn_input = if cpu_overlap_on() {
2647 vec![0.0f32; dim]
2648 } else {
2649 Vec::new()
2650 };
2651 if !attn_frame(
2652 l,
2653 cfg,
2654 st,
2655 li,
2656 folded,
2657 &prep.qr,
2658 &prep.idxs,
2659 freqs,
2660 st.pos,
2661 prep.win_len,
2662 scale,
2663 Some(&a_tail),
2664 &mut ffn_input,
2665 ) {
2666 return None;
2667 }
2668 let nxt = layers.get(li + 1);
2669 let forced = l
2670 .tid2eid
2671 .as_ref()
2672 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
2673 let mut next = vec![0.0f32; dim];
2674 let (cold_sum, cold_count) = moe_frame(
2675 &ffn_input,
2676 l,
2677 cfg,
2678 li,
2679 &[],
2680 forced.as_deref(),
2681 pool,
2682 Some(&a_tail),
2683 None,
2688 &mut next,
2689 )?;
2690 let state_ok = if cold_count == 0 {
2696 crate::gpu_wgpu::dsv4_state_read(state)
2697 } else {
2698 crate::gpu_wgpu::dsv4_state_add_cold(&cold_sum, cfg.hc_mult, state)
2699 };
2700 if !state_ok {
2701 return None;
2702 }
2703 if let Some(n) = nxt {
2704 let (f, post, comb) = hc_fold_norm(
2705 state,
2706 &n.hc_attn_fn,
2707 &n.hc_attn_scale,
2708 &n.hc_attn_base,
2709 &n.attn_norm,
2710 cfg,
2711 pool,
2712 );
2713 *folded = f;
2714 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2715 || !crate::gpu_wgpu::dsv4_state_write(state)
2716 {
2717 return None;
2718 }
2719 }
2720 Some(true)
2725}
2726
2727#[cfg(feature = "gpu")]
2734fn chain1_on() -> bool {
2735 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2736 *ON.get_or_init(|| {
2737 std::env::var("CMF_DSV4_CHAIN1")
2738 .map(|v| v != "0")
2739 .unwrap_or(true)
2740 })
2741}
2742
2743#[cfg(feature = "gpu")]
2749fn cpu_overlap_on() -> bool {
2750 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2751 *ON.get_or_init(|| std::env::var("CMF_DSV4_CPU_OVERLAP").is_ok_and(|v| v != "0"))
2752}
2753
2754#[cfg(feature = "gpu")]
2755#[allow(clippy::too_many_arguments)]
2756fn dsv4_chain1_layer(
2757 state: &mut [f32],
2758 folded: &mut Vec<f32>,
2759 layers: &[Dsv4Layer],
2760 l: &Dsv4Layer,
2761 cfg: &Dsv4Cfg,
2762 st: &mut Dsv4State,
2763 token_id: u32,
2764 li: usize,
2765 freqs: &[f32],
2766 pool: Option<&crate::pool::Pool>,
2767 state_on_host: bool,
2768) -> Option<bool> {
2769 let dim = cfg.dim;
2770 let pk = pack_for(l, cfg, li)?;
2771 if pk.route_complete() {
2772 return None;
2773 }
2774 let model = l.experts.first().and_then(|e| e.w1.model_arc())?;
2775 if state_on_host {
2779 let (f, post, comb) = hc_fold_norm(
2780 state,
2781 &l.hc_attn_fn,
2782 &l.hc_attn_scale,
2783 &l.hc_attn_base,
2784 &l.attn_norm,
2785 cfg,
2786 pool,
2787 );
2788 *folded = f;
2789 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2790 || !crate::gpu_wgpu::dsv4_state_write(state)
2791 {
2792 return None;
2793 }
2794 }
2795 let mut prep = AttnPrep::default();
2796 let mut sink = vec![0.0f32; dim];
2797 attention_step(
2798 folded,
2799 l,
2800 cfg,
2801 st,
2802 li,
2803 freqs,
2804 pool,
2805 Some(&mut prep),
2806 &mut sink,
2807 );
2808 let hd = cfg.head_dim;
2809 let n_comp = st.compressed[li].len() / hd;
2810 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2811 let kv_id = st.kv_id;
2812 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap)
2813 || (n_comp > 0
2814 && !crate::gpu_wgpu::dsv4_cache_write(
2815 kv_id,
2816 li,
2817 cfg.window * hd,
2818 &st.compressed[li],
2819 cap,
2820 ))
2821 {
2822 return None;
2823 }
2824 let idx32: Vec<u32> = prep
2825 .idxs
2826 .iter()
2827 .map(|&p| {
2828 if p < prep.win_len {
2829 p as u32
2830 } else {
2831 (cfg.window + (p - prep.win_len)) as u32
2832 }
2833 })
2834 .collect();
2835 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
2836 l.wq_a.model_idx(),
2837 l.wq_b.model_idx(),
2838 l.wo_a.model_idx(),
2839 l.wo_b.model_idx(),
2840 ) else {
2841 return None;
2842 };
2843 let forced: Option<Vec<usize>> = l
2846 .tid2eid
2847 .as_ref()
2848 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
2849 let global_remap = pk
2850 .global
2851 .as_ref()
2852 .map(|gl| gl.pool.remap(gl.layer, cfg.n_routed_experts));
2853 let dynv = pk.dynslots.lock().unwrap();
2854 let live_remap = global_remap.as_deref().unwrap_or(dynv.remap.as_slice());
2855 let nxt = layers.get(li + 1);
2856 let w = crate::gpu_wgpu::Dsv4LayerW {
2857 attn: crate::gpu_wgpu::Dsv4AttnW {
2858 wq_a,
2859 wq_b,
2860 wo_a,
2861 wo_b,
2862 q_norm: &l.q_norm,
2863 sink: &l.attn_sink,
2864 },
2865 moe: crate::gpu_wgpu::Dsv4MoeW {
2866 router: &[],
2867 experts: &pk.tensors,
2868 logits: &[],
2869 bias: pk.bias.as_deref(),
2872 mask: pk.mask.as_deref(),
2873 forced: forced.as_deref(),
2874 remap: Some(live_remap),
2875 global: pk.global.as_ref().map(|gl| crate::gpu_wgpu::Dsv4GlobalMoe {
2876 pool_uid: gl.pool.uid,
2877 shared_slot: gl.shared_slot,
2878 segment_slots: gl.pool.segment_slots as u32,
2879 }),
2880 },
2881 hc_ffn_fn: &l.hc_ffn_fn,
2882 hc_ffn_scale: &l.hc_ffn_scale,
2883 hc_ffn_base: &l.hc_ffn_base,
2884 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2885 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2886 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2887 ffn_norm: &l.ffn_norm,
2888 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2889 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2890 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2891 router: &pk.router,
2892 };
2893 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2894 attn: crate::gpu_wgpu::Dsv4AttnGeom {
2895 dim,
2896 nh: cfg.n_heads,
2897 hd,
2898 rd: cfg.rope_head_dim,
2899 q_lora: cfg.q_lora_rank,
2900 o_lora: cfg.o_lora_rank,
2901 o_groups: cfg.o_groups,
2902 eps: cfg.norm_eps,
2903 scale: (hd as f32).powf(-0.5),
2904 },
2905 moe: crate::gpu_wgpu::Dsv4MoeGeom {
2906 hidden: dim,
2907 inter: cfg.moe_inter,
2908 top_k: cfg.top_k,
2909 route_scale: cfg.route_scale,
2910 swiglu_limit: cfg.swiglu_limit,
2911 gu_q2: l
2912 .experts
2913 .first()
2914 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
2915 },
2916 hc: cfg.hc_mult,
2917 hc_eps: cfg.hc_eps,
2918 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2919 };
2920 let mut next = vec![0.0f32; dim];
2921 let mut cold: Vec<(usize, f32)> = Vec::new();
2922 let mut routed: Vec<(usize, f32)> = Vec::new();
2923 let mut cold_x: Vec<f32> = Vec::new();
2924 let need_routes = route_stats_on() || pk.global.is_some();
2925 if !crate::gpu_wgpu::dsv4_layer_frame(
2926 &model,
2927 &w,
2928 geom,
2929 kv_id,
2930 li,
2931 Some(&prep.qr),
2932 &idx32,
2933 freqs,
2934 st.pos,
2935 &mut next,
2936 Some(&mut cold),
2937 need_routes.then_some(&mut routed),
2938 &mut cold_x,
2939 ) {
2940 return None;
2941 }
2942 if route_stats_on() {
2943 record_route(li, layers.len(), cfg.n_routed_experts, &routed);
2944 }
2945 drop(dynv);
2946 if let Some(gl) = pk.global.as_ref() {
2951 let picks: Vec<usize> = routed.iter().map(|&(gi, _)| gi).collect();
2952 gl.pool.ensure_picks(
2953 &model,
2954 gl.layer,
2955 &picks,
2956 &l.experts,
2957 cfg.top_k,
2958 1,
2959 );
2960 }
2961 if cold.is_empty() {
2962 *folded = next;
2966 return Some(false);
2967 }
2968 if cold_x.len() < dim {
2971 return None;
2972 }
2973 let mut cold_sum = vec![0.0f32; dim];
2974 {
2975 let results: Vec<std::sync::Mutex<Vec<f32>>> =
2976 cold.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
2977 let (cold_ref, results_ref, x_ref) = (&cold, &results, &cold_x[..dim]);
2978 std::thread::scope(|sc| {
2979 for i in 0..cold_ref.len() {
2980 let (gi, wt) = cold_ref[i];
2981 let Some(exp) = l.experts.get(gi) else { continue };
2982 let r = &results_ref[i];
2983 sc.spawn(move || {
2984 let mut a = vec![0.0f32; cfg.dim];
2985 crate::gpu::cpu_scope(|| run_expert(x_ref, exp, cfg, wt, None, &mut a));
2986 *r.lock().unwrap() = a;
2987 });
2988 }
2989 });
2990 for r in &results {
2991 let a = r.lock().unwrap();
2992 for (o, v) in cold_sum.iter_mut().zip(a.iter()) {
2993 *o += v;
2994 }
2995 }
2996 }
2997 if !crate::gpu_wgpu::dsv4_state_add_cold_preserved(&cold_sum, cfg.hc_mult, state, kv_id, li) {
2998 return None;
2999 }
3000 if pk.global.is_none() {
3003 let mut dynv = pk.dynslots.lock().unwrap();
3004 dynv.clock += 1;
3005 let clock = dynv.clock;
3006 for &(gi, _) in &cold {
3007 if gi >= dynv.remap.len() || dynv.remap[gi] != u32::MAX {
3008 continue;
3009 }
3010 let victim = (0..dynv.owner.len())
3011 .filter(|&sl| dynv.last[sl] != clock)
3012 .min_by_key(|&sl| dynv.last[sl]);
3013 let Some(victim) = victim else { break };
3014 let Some(exp) = l.experts.get(gi) else { continue };
3015 let t3 = (|| Some((exp.w1.model_idx()?, exp.w3.model_idx()?, exp.w2.model_idx()?)))();
3016 let Some(t3) = t3 else { continue };
3017 let gu_q2 =
3018 exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
3019 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
3020 if !crate::gpu_wgpu::dsv4_slot_fill(
3021 &model, pack_first, victim, gi, t3, cfg.moe_inter, cfg.dim, gu_q2,
3022 ) {
3023 break;
3024 }
3025 let old = dynv.owner[victim] as usize;
3026 if old < dynv.remap.len() {
3027 dynv.remap[old] = u32::MAX;
3028 }
3029 dynv.remap[gi] = victim as u32;
3030 dynv.owner[victim] = gi as u32;
3031 dynv.last[victim] = clock;
3032 dynv.mutated = true;
3033 }
3034 }
3035 if let Some(n) = nxt {
3036 let (f, post, comb) = hc_fold_norm(
3037 state,
3038 &n.hc_attn_fn,
3039 &n.hc_attn_scale,
3040 &n.hc_attn_base,
3041 &n.attn_norm,
3042 cfg,
3043 pool,
3044 );
3045 *folded = f;
3046 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb) {
3047 return None;
3048 }
3049 }
3050 Some(true)
3051}
3052
3053#[cfg(feature = "gpu")]
3054fn chain_max() -> usize {
3055 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
3056 *N.get_or_init(|| {
3057 std::env::var("CMF_DSV4_CHAIN_MAX")
3058 .ok()
3059 .and_then(|v| v.parse().ok())
3060 .unwrap_or(usize::MAX)
3061 })
3062}
3063
3064#[cfg(feature = "gpu")]
3067fn partial_walk_on() -> bool {
3078 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3079 *ON.get_or_init(|| std::env::var("CMF_DSV4_PARTIAL_WALK").is_ok_and(|v| v != "0"))
3080}
3081
3082fn host_cpu_moe() -> bool {
3083 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3084 *ON.get_or_init(|| std::env::var("CMF_DSV4_HOST_CPU_MOE").is_ok_and(|v| v != "0"))
3085}
3086
3087fn chain_enabled() -> bool {
3088 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3089 *ON.get_or_init(|| {
3090 std::env::var("CMF_DSV4_CHAIN")
3091 .map(|v| v != "0")
3092 .unwrap_or(true)
3093 })
3094}
3095
3096#[cfg(feature = "gpu")]
3106#[allow(clippy::too_many_arguments)]
3107fn dsv4_chain_run(
3108 layers: &[Dsv4Layer],
3109 run: &[usize],
3110 cfg: &Dsv4Cfg,
3111 g: &Dsv4Globals,
3112 st: &mut Dsv4State,
3113 token_id: u32,
3114 folded: &mut Vec<f32>,
3120 state_out: Option<&mut [f32]>,
3125 batch: usize,
3128 batch_ids: &[u32],
3132 need_qn: bool,
3139 pool: Option<&crate::pool::Pool>,
3140) -> bool {
3141 if run.is_empty() {
3142 return true;
3143 }
3144 let (dim, hd) = (cfg.dim, cfg.head_dim);
3145 let first = run[0];
3146 let Some(model) = layers[first].experts.first().and_then(|e| e.w1.model_arc()) else {
3147 return false;
3148 };
3149 if batch <= 1 && need_qn {
3153 let mut qn0 = vec![0.0f32; cfg.q_lora_rank];
3154 layers[first].wq_a.matvec(folded, &mut qn0, pool);
3155 rms_weighted(&mut qn0, &layers[first].q_norm, cfg.norm_eps);
3156 if !crate::gpu_wgpu::dsv4_chain_seed(folded, &qn0) {
3157 return false;
3158 }
3159 } else if batch <= 1 && !crate::gpu_wgpu::dsv4_chain_seed_fold(folded) {
3160 return false;
3161 }
3162
3163 let mut packs = Vec::with_capacity(run.len());
3165 let mut forceds: Vec<Option<Vec<usize>>> = Vec::with_capacity(run.len());
3166 for &li in run {
3167 let Some(pk) = pack_for(&layers[li], cfg, li) else {
3168 return false;
3169 };
3170 if !pk.route_complete() || pk.is_mutated() {
3177 return false;
3178 }
3179 let forced: Option<Vec<usize>> = layers[li].tid2eid.as_ref().and_then(|tbl| {
3180 let v: Vec<usize> = if pk.needs_remap() {
3181 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
3182 } else {
3183 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
3184 .into_iter()
3185 .map(|gi| pk.to_slot[gi])
3186 .collect()
3187 };
3188 if v.contains(&usize::MAX) {
3189 None
3190 } else {
3191 Some(v)
3192 }
3193 });
3194 if layers[li].tid2eid.is_some() && forced.is_none() {
3195 return false;
3196 }
3197 forceds.push(forced);
3198 packs.push(pk);
3199 }
3200
3201 let mut items = Vec::with_capacity(run.len());
3202 let mut freqs = Vec::with_capacity(run.len());
3203 for (i, &li) in run.iter().enumerate() {
3204 let l = &layers[li];
3205 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
3206 l.wq_a.model_idx(),
3207 l.wq_b.model_idx(),
3208 l.wo_a.model_idx(),
3209 l.wo_b.model_idx(),
3210 l.wkv.model_idx(),
3211 ) else {
3212 return false;
3213 };
3214 let comp = match &l.compressor {
3215 None => None,
3216 Some(cp) => {
3217 let (Some(a), Some(b)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
3218 return false;
3219 };
3220 Some((
3221 crate::gpu_wgpu::Dsv4CompW {
3222 wkv: a,
3223 wgate: b,
3224 norm: &cp.norm,
3225 ape: &cp.ape,
3226 },
3227 crate::gpu_wgpu::Dsv4CompGeom {
3228 width: cp.wkv.rows(),
3229 hidden: dim,
3230 ratio: cp.ratio,
3231 overlap: cp.overlap,
3232 rope_dim: cfg.rope_head_dim,
3233 eps: cfg.norm_eps,
3234 },
3235 ))
3236 }
3237 };
3238 let ix = match &l.indexer {
3239 None => None,
3240 Some(ixr) => {
3241 let cp = &ixr.compressor;
3242 let (Some(a), Some(b), Some(qb), Some(wp)) = (
3243 cp.wkv.model_idx(),
3244 cp.wgate.model_idx(),
3245 ixr.wq_b.model_idx(),
3246 ixr.weights_proj.model_idx(),
3247 ) else {
3248 return false;
3249 };
3250 let ih = ixr.weights_proj.rows();
3251 Some((
3252 crate::gpu_wgpu::Dsv4CompW {
3253 wkv: a,
3254 wgate: b,
3255 norm: &cp.norm,
3256 ape: &cp.ape,
3257 },
3258 crate::gpu_wgpu::Dsv4CompGeom {
3259 width: cp.wkv.rows(),
3260 hidden: dim,
3261 ratio: cp.ratio,
3262 overlap: cp.overlap,
3263 rope_dim: cfg.rope_head_dim,
3264 eps: cfg.norm_eps,
3265 },
3266 crate::gpu_wgpu::Dsv4IxW {
3267 wq_b: qb,
3268 weights_proj: wp,
3269 },
3270 crate::gpu_wgpu::Dsv4IxGeom {
3271 ih,
3272 idim: ixr.wq_b.rows() / ih.max(1),
3273 q_lora: cfg.q_lora_rank,
3274 hidden: dim,
3275 rope_dim: cfg.rope_head_dim,
3276 eps: cfg.norm_eps,
3277 top_k: cfg.index_topk,
3278 window: cfg.window,
3279 },
3280 ))
3281 }
3282 };
3283 let ew_c0 = l.compressor.as_ref().map_or(0, |cp| {
3287 if cp.overlap {
3288 cp.wkv.rows() / 2
3289 } else {
3290 cp.wkv.rows()
3291 }
3292 });
3293 let comp_extra = l
3294 .compressor
3295 .as_ref()
3296 .map_or(0, |cp| batch.max(1).div_ceil(cp.ratio.max(1)));
3297 let need = cfg.window * hd
3298 + (st.dev_n_comp[li] + comp_extra + 1) * ew_c0.max(1)
3299 + (batch.max(1) + 1) * hd;
3300 if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
3301 return false;
3302 }
3303 let ew_c = comp.as_ref().map_or(
3304 0,
3305 |(_, cg)| {
3306 if cg.overlap { cg.width / 2 } else { cg.width }
3307 },
3308 );
3309 let ew_i = ix.as_ref().map_or(
3310 0,
3311 |(_, cg, _, _)| {
3312 if cg.overlap { cg.width / 2 } else { cg.width }
3313 },
3314 );
3315 let prep = crate::gpu_wgpu::Dsv4Prep {
3316 wkv,
3317 kv_norm: &l.kv_norm,
3318 comp,
3319 ix,
3320 filled: st.dev_filled[li],
3321 window: cfg.window,
3322 n_comp: st.dev_n_comp[li],
3323 n_ix: st.dev_n_ix[li],
3324 comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
3325 ix_dst_off: st.dev_n_ix[li] * ew_i,
3326 idx_cap: cfg.window
3327 + if l.indexer.is_some() {
3328 cfg.index_topk
3329 } else {
3330 st.dev_n_comp[li] + comp_extra + 1
3331 },
3332 };
3333 let nxt = layers.get(li + 1);
3334 let w = crate::gpu_wgpu::Dsv4LayerW {
3335 attn: crate::gpu_wgpu::Dsv4AttnW {
3336 wq_a,
3337 wq_b,
3338 wo_a,
3339 wo_b,
3340 q_norm: &l.q_norm,
3341 sink: &l.attn_sink,
3342 },
3343 moe: crate::gpu_wgpu::Dsv4MoeW {
3344 router: &packs[i].router,
3345 experts: &packs[i].tensors,
3346 logits: &[],
3347 bias: packs[i].bias.as_deref(),
3350 mask: packs[i].mask.as_deref(),
3351 forced: forceds[i].as_deref(),
3352 remap: packs[i].needs_remap().then_some(packs[i].remap.as_slice()),
3353 global: None,
3354 },
3355 hc_ffn_fn: &l.hc_ffn_fn,
3356 hc_ffn_scale: &l.hc_ffn_scale,
3357 hc_ffn_base: &l.hc_ffn_base,
3358 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
3359 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
3360 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
3361 ffn_norm: &l.ffn_norm,
3362 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
3363 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
3364 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
3365 router: &packs[i].router,
3366 };
3367 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
3368 attn: crate::gpu_wgpu::Dsv4AttnGeom {
3369 dim,
3370 nh: cfg.n_heads,
3371 hd,
3372 rd: cfg.rope_head_dim,
3373 q_lora: cfg.q_lora_rank,
3374 o_lora: cfg.o_lora_rank,
3375 o_groups: cfg.o_groups,
3376 eps: cfg.norm_eps,
3377 scale: (hd as f32).powf(-0.5),
3378 },
3379 moe: crate::gpu_wgpu::Dsv4MoeGeom {
3380 hidden: dim,
3381 inter: cfg.moe_inter,
3382 top_k: cfg.top_k,
3383 route_scale: cfg.route_scale,
3384 swiglu_limit: cfg.swiglu_limit,
3385 gu_q2: l.experts.first().is_some_and(|e| {
3386 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
3387 }),
3388 },
3389 hc: cfg.hc_mult,
3390 hc_eps: cfg.hc_eps,
3391 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3392 };
3393 freqs.push(if l.compressor.is_some() {
3394 g.inv_freq_compress.as_slice()
3395 } else {
3396 g.inv_freq_window.as_slice()
3397 });
3398 items.push((w, geom, prep));
3399 }
3400
3401 let mut out = vec![0.0f32; dim * batch.max(1)];
3402 if batch > 1 {
3403 let mut forced_pt: Vec<Vec<Option<Vec<usize>>>> = Vec::with_capacity(batch);
3408 for t in 0..batch {
3409 let id = batch_ids.get(t).copied().unwrap_or(token_id);
3410 let mut row = Vec::with_capacity(run.len());
3411 for (i, &li) in run.iter().enumerate() {
3412 row.push(layers[li].tid2eid.as_ref().and_then(|tbl| {
3413 let v: Vec<usize> = if packs[i].needs_remap() {
3414 hash_route(tbl, cfg.vocab, cfg.top_k, id)
3415 } else {
3416 hash_route(tbl, cfg.vocab, cfg.top_k, id)
3417 .into_iter()
3418 .map(|gi| packs[i].to_slot[gi])
3419 .collect()
3420 };
3421 if v.contains(&usize::MAX) {
3422 None
3423 } else {
3424 Some(v)
3425 }
3426 }));
3427 if layers[li].tid2eid.is_some() && row[i].is_none() {
3428 return false;
3429 }
3430 }
3431 forced_pt.push(row);
3432 }
3433 if !crate::gpu_wgpu::dsv4_chain_batch(
3434 &model,
3435 &items,
3436 st.kv_id,
3437 first,
3438 &freqs,
3439 st.pos,
3440 batch,
3441 Some(&forced_pt),
3442 &mut out,
3443 state_out,
3444 ) {
3445 return false;
3446 }
3447 *folded = out[(batch - 1) * dim..batch * dim].to_vec();
3450 } else {
3451 if !crate::gpu_wgpu::dsv4_layer_chain(
3452 &model, &items, st.kv_id, first, &freqs, st.pos, &mut out, state_out,
3453 ) {
3454 return false;
3455 }
3456 *folded = out;
3457 }
3458 for (i, &li) in run.iter().enumerate() {
3462 for t in 0..batch.max(1) {
3463 let pos = st.pos + t;
3464 st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
3465 if let Some((_, cg, ..)) = items[i].2.ix.as_ref() {
3466 if (pos + 1) % cg.ratio == 0 {
3467 st.dev_n_ix[li] += 1;
3468 }
3469 }
3470 if let Some((_, cg)) = items[i].2.comp.as_ref() {
3471 if (pos + 1) % cg.ratio == 0 {
3472 st.dev_n_comp[li] += 1;
3473 }
3474 }
3475 }
3476 }
3477 st.dev_owned = true;
3478 true
3479}
3480
3481#[cfg(feature = "gpu")]
3483fn hc_on_device() -> bool {
3484 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3485 *ON.get_or_init(|| {
3486 std::env::var("CMF_DSV4_HC_DEV").is_ok_and(|v| v != "0") && crate::gpu::backend_available()
3492 })
3493}
3494
3495#[cfg(feature = "gpu")]
3504#[allow(clippy::too_many_arguments)]
3505fn dsv4_two_frame_loop(
3506 state: &mut [f32],
3507 layers: &[Dsv4Layer],
3508 g: &Dsv4Globals,
3509 cfg: &Dsv4Cfg,
3510 st: &mut Dsv4State,
3511 token_id: u32,
3512 inv_freq: &[f32],
3513 pool: Option<&crate::pool::Pool>,
3514 scratch: &mut HcScratch,
3515) -> bool {
3516 let dim = cfg.dim;
3517 let freqs_of = |l: &Dsv4Layer| -> &[f32] {
3518 let f = if l.compressor.is_some() {
3519 &g.inv_freq_compress
3520 } else {
3521 &g.inv_freq_window
3522 };
3523 if f.is_empty() { inv_freq } else { f.as_slice() }
3524 };
3525 let (mut folded, post0, comb0) = hc_fold_norm(
3527 state,
3528 &layers[0].hc_attn_fn,
3529 &layers[0].hc_attn_scale,
3530 &layers[0].hc_attn_base,
3531 &layers[0].attn_norm,
3532 cfg,
3533 pool,
3534 );
3535 if !crate::gpu_wgpu::dsv4_state_write(state) || !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
3536 {
3537 return false;
3538 }
3539 let mut on_dev = vec![false; layers.len()];
3544 for (li, l) in layers.iter().enumerate() {
3545 let Some(pk) = pack_for(l, cfg, li) else {
3546 return false;
3547 };
3548 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
3549 return false;
3550 };
3551 let gu_q2 = l
3552 .experts
3553 .first()
3554 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
3555 let attn_ok = [
3556 l.wq_a.model_idx(),
3557 l.wq_b.model_idx(),
3558 l.wo_a.model_idx(),
3559 l.wo_b.model_idx(),
3560 ]
3561 .into_iter()
3562 .flatten()
3563 .all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
3564 on_dev[li] = attn_ok
3565 && pk.route_complete()
3566 && crate::gpu_wgpu::dsv4_experts_ready(
3567 &model,
3568 &pk.tensors,
3569 cfg.moe_inter,
3570 dim,
3571 gu_q2,
3572 l.experts.first().is_some_and(|e| {
3573 e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
3574 }),
3575 );
3576 }
3577 if !on_dev.iter().any(|&x| x) {
3578 return false;
3579 }
3580 let mut sink = vec![0.0f32; dim];
3581 for (li, l) in layers.iter().enumerate() {
3582 if !on_dev[li] {
3586 if !crate::gpu_wgpu::dsv4_state_read(state) {
3587 return false;
3588 }
3589 let freqs = freqs_of(l);
3590 hc_block(
3591 state,
3592 &l.hc_attn_fn,
3593 &l.hc_attn_scale,
3594 &l.hc_attn_base,
3595 &l.attn_norm,
3596 cfg,
3597 scratch,
3598 pool,
3599 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
3600 );
3601 hc_block(
3602 state,
3603 &l.hc_ffn_fn,
3604 &l.hc_ffn_scale,
3605 &l.hc_ffn_base,
3606 &l.ffn_norm,
3607 cfg,
3608 scratch,
3609 pool,
3610 |f, o| moe_step(f, l, cfg, token_id, li, pool, o),
3611 );
3612 let nref = layers.get(li + 1).unwrap_or(l);
3613 let (f, p2, c2) = hc_fold_norm(
3614 state,
3615 &nref.hc_attn_fn,
3616 &nref.hc_attn_scale,
3617 &nref.hc_attn_base,
3618 &nref.attn_norm,
3619 cfg,
3620 pool,
3621 );
3622 folded = f;
3623 if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2)
3624 || !crate::gpu_wgpu::dsv4_state_write(state)
3625 {
3626 return false;
3627 }
3628 continue;
3629 }
3630 let mut prep = AttnPrep::default();
3632 attention_step(
3633 &folded,
3634 l,
3635 cfg,
3636 st,
3637 li,
3638 freqs_of(l),
3639 pool,
3640 Some(&mut prep),
3641 &mut sink,
3642 );
3643 let hd = cfg.head_dim;
3644 let n_comp = st.compressed[li].len() / hd;
3645 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
3646 if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
3647 || (n_comp > 0
3648 && !crate::gpu_wgpu::dsv4_cache_write(
3649 st.kv_id,
3650 li,
3651 cfg.window * hd,
3652 &st.compressed[li],
3653 cap,
3654 ))
3655 {
3656 return false;
3657 }
3658 let _idx32: Vec<u32> = prep
3659 .idxs
3660 .iter()
3661 .map(|&p| {
3662 if p < prep.win_len {
3663 p as u32
3664 } else {
3665 (cfg.window + (p - prep.win_len)) as u32
3666 }
3667 })
3668 .collect();
3669 let nxt = layers.get(li + 1);
3670 let a_tail = crate::gpu_wgpu::Dsv4HcTail {
3671 fn_: &l.hc_ffn_fn,
3672 scale: &l.hc_ffn_scale,
3673 base: &l.hc_ffn_base,
3674 norm: &l.ffn_norm,
3675 hc: cfg.hc_mult,
3676 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3677 hc_eps: cfg.hc_eps,
3678 eps: cfg.norm_eps,
3679 };
3680 let scale = (cfg.head_dim as f32).powf(-0.5);
3681 if !attn_frame(
3682 l,
3683 cfg,
3684 st,
3685 li,
3686 &folded,
3687 &prep.qr,
3688 &prep.idxs,
3689 freqs_of(l),
3690 st.pos,
3691 prep.win_len,
3692 scale,
3693 Some(&a_tail),
3694 &mut [],
3695 ) {
3696 return false;
3697 }
3698 let m_tail = nxt.map(|n| crate::gpu_wgpu::Dsv4HcTail {
3699 fn_: &n.hc_attn_fn,
3700 scale: &n.hc_attn_scale,
3701 base: &n.hc_attn_base,
3702 norm: &n.attn_norm,
3703 hc: cfg.hc_mult,
3704 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3705 hc_eps: cfg.hc_eps,
3706 eps: cfg.norm_eps,
3707 });
3708 let mut next = vec![0.0f32; dim];
3709 let pair = m_tail
3710 .as_ref()
3711 .zip(nxt)
3712 .map(|(t, n)| (t, n.attn_norm.as_slice()));
3713 let forced = l
3714 .tid2eid
3715 .as_ref()
3716 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
3717 if moe_frame(
3718 &[],
3719 l,
3720 cfg,
3721 li,
3722 &[],
3723 forced.as_deref(),
3724 pool,
3725 Some(&a_tail),
3726 pair,
3727 &mut next,
3728 )
3729 .is_none()
3730 {
3731 return false;
3732 }
3733 folded = next;
3734 }
3735 let _ = scratch;
3736 crate::gpu_wgpu::dsv4_state_read(state)
3737}
3738
3739#[cfg(feature = "gpu")]
3743#[allow(clippy::too_many_arguments)]
3744fn hc_fold_norm(
3745 state: &[f32],
3746 hc_fn: &[f32],
3747 hc_scale: &[f32; 3],
3748 hc_base: &[f32],
3749 norm_w: &[f32],
3750 cfg: &Dsv4Cfg,
3751 pool: Option<&crate::pool::Pool>,
3752) -> (Vec<f32>, Vec<f32>, Vec<f32>) {
3753 let (hc, dim) = (cfg.hc_mult, cfg.dim);
3754 let mix_hc = (2 + hc) * hc;
3755 let mut mixes = vec![0.0f32; mix_hc];
3756 hc_mixes(state, hc_fn, mix_hc, cfg.norm_eps, pool, &mut mixes);
3757 let mut pre = vec![0.0f32; hc];
3758 let mut post = vec![0.0f32; hc];
3759 let mut comb = vec![0.0f32; hc * hc];
3760 hc_split_sinkhorn(
3761 &mixes,
3762 hc_scale,
3763 hc_base,
3764 hc,
3765 cfg.hc_sinkhorn_iters,
3766 cfg.hc_eps,
3767 &mut pre,
3768 &mut post,
3769 &mut comb,
3770 );
3771 let mut folded = vec![0.0f32; dim];
3772 hc_fold(state, &pre, hc, dim, &mut folded);
3773 rms_weighted(&mut folded, norm_w, cfg.norm_eps);
3774 (folded, post, comb)
3777}
3778
3779#[cfg(feature = "gpu")]
3796fn gpu_layer_enabled() -> bool {
3797 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3798 *ON.get_or_init(|| {
3799 std::env::var("CMF_DSV4_GPU_LAYER")
3800 .map(|v| v != "0")
3801 .unwrap_or(true)
3802 && crate::gpu::backend_available()
3803 })
3804}
3805
3806#[cfg(feature = "gpu")]
3810struct PackDyn {
3811 remap: Vec<u32>,
3812 owner: Vec<u32>,
3813 last: Vec<u64>,
3814 clock: u64,
3815 seen: Vec<u16>,
3822 mutated: bool,
3826}
3827
3828#[cfg(feature = "gpu")]
3833#[derive(Clone, Copy)]
3834struct GlobalOwner {
3835 layer: usize,
3836 expert: usize,
3837 pinned: bool,
3838}
3839
3840#[cfg(feature = "gpu")]
3841struct GlobalPoolDyn {
3842 slot_for: std::collections::HashMap<(usize, usize), u32>,
3843 owner: Vec<Option<GlobalOwner>>,
3844 last: Vec<u64>,
3845 seen: std::collections::HashMap<(usize, usize), u16>,
3846 occupancy: std::collections::HashMap<usize, usize>,
3847 clock: u64,
3848}
3849
3850#[cfg(feature = "gpu")]
3858struct GlobalPool {
3859 uid: u64,
3860 capacity: usize,
3861 segment_slots: usize,
3862 floor: usize,
3863 state: std::sync::Mutex<GlobalPoolDyn>,
3864}
3865
3866#[cfg(feature = "gpu")]
3867impl GlobalPool {
3868 fn remap(&self, layer: usize, n: usize) -> Vec<u32> {
3869 let st = self.state.lock().unwrap();
3870 debug_assert_eq!(self.capacity, st.owner.len());
3871 (0..n)
3872 .map(|e| st.slot_for.get(&(layer, e)).copied().unwrap_or(u32::MAX))
3873 .collect()
3874 }
3875
3876 fn seed_layer(
3877 &self,
3878 model: &std::sync::Arc<cortiq_core::CmfModel>,
3879 layer: usize,
3880 shared: (usize, usize, usize),
3881 _routed: &[(usize, (usize, usize, usize))],
3882 ) -> Option<u32> {
3883 let mut st = self.state.lock().unwrap();
3884 let shared_key = (layer, usize::MAX);
3885 let shared_slot = if let Some(&slot) = st.slot_for.get(&shared_key) {
3886 slot
3887 } else {
3888 let slot = st.owner.iter().position(Option::is_none)?;
3889 if !crate::gpu_wgpu::dsv4_global_slot_fill(model, slot, shared) {
3890 return None;
3891 }
3892 st.owner[slot] = Some(GlobalOwner {
3893 layer,
3894 expert: usize::MAX,
3895 pinned: true,
3896 });
3897 st.slot_for.insert(shared_key, slot as u32);
3898 *st.occupancy.entry(layer).or_insert(0) += 1;
3899 slot as u32
3900 };
3901 Some(shared_slot)
3906 }
3907
3908 fn ensure_picks(
3909 &self,
3910 model: &std::sync::Arc<cortiq_core::CmfModel>,
3911 layer: usize,
3912 picks: &[usize],
3913 experts: &[Dsv4Expert],
3914 quota: usize,
3915 min_seen: u16,
3916 ) -> Vec<u32> {
3917 let mut st = self.state.lock().unwrap();
3918 st.clock = st.clock.saturating_add(1);
3919 let clock = st.clock;
3920 if clock % 64 == 0 {
3921 for v in st.seen.values_mut() {
3922 *v >>= 1;
3923 }
3924 }
3925 for &expert in picks {
3926 let key = (layer, expert);
3927 let seen = st.seen.entry(key).or_insert(0);
3928 *seen = seen.saturating_add(1);
3929 if let Some(&slot) = st.slot_for.get(&key) {
3930 st.last[slot as usize] = clock;
3931 }
3932 }
3933 let mut fetched = 0usize;
3934 for &expert in picks {
3935 if fetched >= quota {
3936 break;
3937 }
3938 let key = (layer, expert);
3939 if st.slot_for.contains_key(&key)
3940 || st.seen.get(&key).copied().unwrap_or(0) < min_seen
3941 {
3942 continue;
3943 }
3944 let Some(exp) = experts.get(expert) else { continue };
3945 let Some(tensors) = (|| {
3946 Some((
3947 exp.w1.model_idx()?,
3948 exp.w3.model_idx()?,
3949 exp.w2.model_idx()?,
3950 ))
3951 })() else {
3952 continue;
3953 };
3954 let empty = st.owner.iter().position(Option::is_none);
3955 let over_floor = |o: GlobalOwner, occ: &std::collections::HashMap<usize, usize>| {
3958 occ.get(&o.layer).copied().unwrap_or(0) > self.floor
3959 };
3960 let eligible = |o: GlobalOwner| {
3961 !o.pinned && !(o.layer == layer && picks.contains(&o.expert))
3962 };
3963 let victim = empty.or_else(|| {
3964 st.owner
3965 .iter()
3966 .enumerate()
3967 .filter_map(|(slot, &o)| o.filter(|&x| eligible(x) && over_floor(x, &st.occupancy)).map(|_| slot))
3968 .min_by_key(|&slot| st.last[slot])
3969 }).or_else(|| {
3970 st.owner
3974 .iter()
3975 .enumerate()
3976 .filter_map(|(slot, &o)| o.filter(|&x| eligible(x) && x.layer == layer).map(|_| slot))
3977 .min_by_key(|&slot| st.last[slot])
3978 }).or_else(|| {
3979 st.owner
3983 .iter()
3984 .enumerate()
3985 .filter_map(|(slot, &o)| o.filter(|&x| eligible(x)).map(|_| slot))
3986 .min_by_key(|&slot| st.last[slot])
3987 });
3988 let Some(victim) = victim else { break };
3989 if !crate::gpu_wgpu::dsv4_global_slot_fill(model, victim, tensors) {
3990 break;
3991 }
3992 if let Some(old) = st.owner[victim] {
3993 st.slot_for.remove(&(old.layer, old.expert));
3994 if let Some(n) = st.occupancy.get_mut(&old.layer) {
3995 *n = n.saturating_sub(1);
3996 }
3997 }
3998 st.owner[victim] = Some(GlobalOwner {
3999 layer,
4000 expert,
4001 pinned: false,
4002 });
4003 st.slot_for.insert(key, victim as u32);
4004 *st.occupancy.entry(layer).or_insert(0) += 1;
4005 st.last[victim] = clock;
4006 fetched += 1;
4007 }
4008 drop(st);
4009 self.remap(layer, experts.len())
4010 }
4011}
4012
4013#[cfg(feature = "gpu")]
4014#[derive(Clone)]
4015struct GlobalPack {
4016 pool: std::sync::Arc<GlobalPool>,
4017 layer: usize,
4018 shared_slot: u32,
4019}
4020
4021#[cfg(feature = "gpu")]
4022impl Pack {
4023 fn is_mutated(&self) -> bool {
4025 self.global.is_some() || self.dynslots.lock().unwrap().mutated
4026 }
4027
4028 fn route_complete(&self) -> bool {
4034 if self.global.is_some() {
4035 return false;
4036 }
4037 let need = self
4038 .mask
4039 .as_deref()
4040 .map_or(self.remap.len(), |m| m.iter().filter(|&&x| x != 0).count());
4041 self.globals.len() >= need
4042 }
4043
4044 fn needs_remap(&self) -> bool {
4048 self.global.is_some()
4049 || self.mask.is_some()
4050 || self
4051 .remap
4052 .iter()
4053 .enumerate()
4054 .any(|(i, &slot)| slot != i as u32)
4055 }
4056}
4057
4058#[cfg(feature = "gpu")]
4062struct Pack {
4063 router: Vec<f32>,
4067 to_slot: Vec<usize>,
4069 remap: Vec<u32>,
4071 globals: Vec<usize>,
4073 tensors: Vec<(usize, usize, usize)>,
4074 mask: Option<Vec<u32>>,
4078 dynslots: std::sync::Mutex<PackDyn>,
4087 bias: Option<Vec<f32>>,
4096 global: Option<GlobalPack>,
4100}
4101
4102#[cfg(feature = "gpu")]
4103fn pack_freq_order(li: usize, n: usize) -> Option<Vec<usize>> {
4109 use std::collections::HashMap;
4110 use std::sync::OnceLock;
4111 static FREQ: OnceLock<Option<HashMap<(usize, usize), u64>>> = OnceLock::new();
4112 let map = FREQ
4113 .get_or_init(|| {
4114 let path = std::env::var("CMF_DSV4_PACK_FREQ").ok()?;
4115 let text = match std::fs::read_to_string(&path) {
4116 Ok(t) => t,
4117 Err(e) => {
4118 eprintln!("CMF_DSV4_PACK_FREQ={path} не читается ({e}) — порядок по id");
4119 return None;
4120 }
4121 };
4122 let mut m = HashMap::new();
4123 for line in text.lines() {
4124 let mut it = line.split('\t');
4125 if let (Some(l), Some(e), Some(c)) = (it.next(), it.next(), it.next()) {
4126 if let (Ok(l), Ok(e), Ok(c)) =
4127 (l.trim().parse(), e.trim().parse(), c.trim().parse::<u64>())
4128 {
4129 *m.entry((l, e)).or_insert(0) += c;
4130 }
4131 }
4132 }
4133 Some(m)
4134 })
4135 .as_ref()?;
4136 if !(0..n).any(|e| map.contains_key(&(li, e))) {
4137 return None;
4138 }
4139 let mut idx: Vec<usize> = (0..n).collect();
4140 idx.sort_by_key(|&e| {
4141 (
4142 std::cmp::Reverse(map.get(&(li, e)).copied().unwrap_or(0)),
4143 e,
4144 )
4145 });
4146 Some(idx)
4147}
4148
4149#[cfg(feature = "gpu")]
4150fn global_pool_for(
4151 model: &std::sync::Arc<cortiq_core::CmfModel>,
4152 cfg: &Dsv4Cfg,
4153 gu_q2: bool,
4154 dn_q2: bool,
4155) -> Option<std::sync::Arc<GlobalPool>> {
4156 use std::collections::HashMap;
4157 use std::sync::{Arc, Mutex, OnceLock};
4158 if gu_q2
4159 || dn_q2
4160 || !crate::gpu_wgpu::dsv4_global_moe_supported()
4161 || std::env::var("CMF_MOE_MASK").is_ok()
4162 {
4163 return None;
4164 }
4165 static POOLS: OnceLock<Mutex<HashMap<u64, Arc<GlobalPool>>>> = OnceLock::new();
4166 let pools = POOLS.get_or_init(|| Mutex::new(HashMap::new()));
4167 if let Some(p) = pools.lock().unwrap().get(&model.uid()).cloned() {
4168 return Some(p);
4169 }
4170 let requested = crate::gpu_wgpu::dsv4_experts_fit(cfg.moe_inter, cfg.dim, false, false);
4171 let (capacity, segment_slots) =
4172 crate::gpu_wgpu::dsv4_global_moe_create(model, requested, cfg.moe_inter, cfg.dim)?;
4173 let layers = model.header.arch.num_layers.max(1);
4174 let p = Arc::new(GlobalPool {
4175 uid: model.uid(),
4176 capacity,
4177 segment_slots,
4178 floor: (capacity / layers).max(2),
4181 state: Mutex::new(GlobalPoolDyn {
4182 slot_for: HashMap::new(),
4183 owner: vec![None; capacity],
4184 last: vec![0; capacity],
4185 seen: HashMap::new(),
4186 occupancy: HashMap::new(),
4187 clock: 0,
4188 }),
4189 });
4190 pools.lock().unwrap().insert(model.uid(), p.clone());
4191 Some(p)
4192}
4193
4194#[cfg(feature = "gpu")]
4195fn pack_for(l: &Dsv4Layer, cfg: &Dsv4Cfg, li: usize) -> Option<std::sync::Arc<Pack>> {
4196 use std::collections::HashMap;
4197 use std::sync::{Arc, Mutex, OnceLock};
4198 static CACHE: OnceLock<Mutex<HashMap<(u64, usize, usize), Option<Arc<Pack>>>>> =
4199 OnceLock::new();
4200 let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
4201 let model_uid = l
4207 .experts
4208 .first()
4209 .and_then(|e| e.w1.model_arc())
4210 .map_or(0, |m| m.uid());
4211 let first_expert = l
4217 .experts
4218 .first()
4219 .and_then(|e| e.w1.model_idx())
4220 .unwrap_or(usize::MAX);
4221 let key = (model_uid, li, first_expert);
4222 if let Some(v) = cache.lock().unwrap().get(&key) {
4223 return v.clone();
4224 }
4225 if let Ok(v) = std::env::var("CMF_DSV4_PACK_MAX_LI") {
4232 if v.parse::<usize>().is_ok_and(|max| li > max) {
4233 cache.lock().unwrap().insert(key, None);
4234 return None;
4235 }
4236 }
4237 let build = || -> Option<Arc<Pack>> {
4238 let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
4239 let mut globals = Vec::new();
4240 let mut tensors = Vec::new();
4241 let idx3 = |e: &Dsv4Expert| -> Option<(usize, usize, usize)> {
4242 Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
4243 };
4244 let route_mask: Option<Vec<u32>> = if l.tid2eid.is_none() {
4245 l.mask
4246 .as_deref()
4247 .map(|m| m.iter().map(|&open| u32::from(open)).collect())
4248 } else {
4249 None
4250 };
4251 let gu_q2 = l
4256 .experts
4257 .first()
4258 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4259 let dn_q2 = l
4260 .experts
4261 .first()
4262 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4263 if route_mask.is_none() {
4268 if let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) {
4269 if let Some(gp) = global_pool_for(&model, cfg, gu_q2, dn_q2) {
4270 let layer_key = first_expert;
4271 let order = pack_freq_order(li, l.experts.len())
4272 .unwrap_or_else(|| (0..l.experts.len()).collect());
4273 let routed: Vec<_> = order
4274 .into_iter()
4275 .filter_map(|gi| Some((gi, idx3(&l.experts[gi])?)))
4276 .collect();
4277 let shared = idx3(&l.shared)?;
4278 let shared_slot = gp.seed_layer(&model, layer_key, shared, &routed)?;
4279 let remap = gp.remap(layer_key, cfg.n_routed_experts);
4280 let to_slot: Vec<usize> = remap
4281 .iter()
4282 .map(|&s| if s == u32::MAX { usize::MAX } else { s as usize })
4283 .collect();
4284 let globals: Vec<usize> = remap
4285 .iter()
4286 .enumerate()
4287 .filter_map(|(e, &s)| (s != u32::MAX).then_some(e))
4288 .collect();
4289 let (rows, cols) = (l.gate.rows(), l.gate.cols());
4290 let mut router = vec![0.0f32; rows * cols];
4291 for r in 0..rows {
4292 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4293 }
4294 return Some(Arc::new(Pack {
4295 bias: l.gate_bias.clone(),
4296 mask: None,
4297 router,
4298 to_slot,
4299 remap: remap.clone(),
4300 globals,
4301 tensors: Vec::new(),
4304 dynslots: std::sync::Mutex::new(PackDyn {
4305 remap,
4306 owner: Vec::new(),
4307 last: Vec::new(),
4308 clock: 0,
4309 mutated: true,
4310 seen: vec![0; cfg.n_routed_experts],
4311 }),
4312 global: Some(GlobalPack {
4313 pool: gp,
4314 layer: layer_key,
4315 shared_slot,
4316 }),
4317 }));
4318 }
4319 }
4320 }
4321 if let Some(n) = std::env::var("CMF_DSV4_PACK_MAX")
4330 .ok()
4331 .and_then(|v| v.parse::<usize>().ok())
4332 {
4333 let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
4334 let mut globals = Vec::new();
4335 let mut tensors = Vec::new();
4336 for (gi, e) in l.experts.iter().enumerate() {
4337 if route_mask
4338 .as_deref()
4339 .is_some_and(|m| m.get(gi).copied().unwrap_or(1) == 0)
4340 {
4341 continue;
4342 }
4343 if globals.len() >= n {
4344 break;
4345 }
4346 to_slot[gi] = globals.len();
4347 globals.push(gi);
4348 tensors.push(idx3(e)?);
4349 }
4350 tensors.push(idx3(&l.shared)?);
4351 let (rows, cols) = (l.gate.rows(), l.gate.cols());
4352 let mut router = vec![0.0f32; rows * cols];
4353 for r in 0..rows {
4354 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4355 }
4356 let remap: Vec<u32> = to_slot
4357 .iter()
4358 .map(|&sl| {
4359 if sl == usize::MAX {
4360 u32::MAX
4361 } else {
4362 sl as u32
4363 }
4364 })
4365 .collect();
4366 return Some(Arc::new(Pack {
4367 bias: l.gate_bias.clone(),
4368 mask: route_mask.clone(),
4369 router,
4370 to_slot,
4371 dynslots: std::sync::Mutex::new(PackDyn {
4372 remap: remap.clone(),
4373 owner: globals.iter().map(|&g| g as u32).collect(),
4374 last: vec![0; globals.len()],
4375 clock: 0,
4376 mutated: false,
4377 seen: vec![0; cfg.n_routed_experts],
4378 }),
4379 remap,
4380 globals,
4381 tensors,
4382 global: None,
4383 }));
4384 }
4385 let dn_q2_fit = l
4386 .experts
4387 .first()
4388 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4389 let room = crate::gpu_wgpu::dsv4_experts_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2_fit)
4390 .saturating_sub(1);
4391 let remaining_layers = l
4402 .experts
4403 .first()
4404 .and_then(|e| e.w1.model_arc())
4405 .map(|m| m.header.arch.num_layers.saturating_sub(li).max(1))
4406 .unwrap_or(1);
4407 let auto_cap = if l.mask.is_some() {
4408 room
4413 } else if l.tid2eid.is_some() && room >= cfg.n_routed_experts {
4414 cfg.n_routed_experts
4415 } else {
4416 room.div_ceil(remaining_layers).max(1)
4417 };
4418 let room = match std::env::var("CMF_DSV4_PACK_LAYER_CAP")
4419 .ok()
4420 .and_then(|v| v.parse::<usize>().ok())
4421 {
4422 Some(0) => room,
4423 Some(cap) => room.min(cap),
4424 None => room.min(auto_cap),
4425 };
4426 let order =
4435 pack_freq_order(li, l.experts.len()).unwrap_or_else(|| (0..l.experts.len()).collect());
4436 for gi in order {
4437 let e = &l.experts[gi];
4438 if l.mask
4439 .as_deref()
4440 .is_some_and(|m| !m.get(gi).copied().unwrap_or(true))
4441 {
4442 continue;
4443 }
4444 if globals.len() >= room {
4445 break;
4446 }
4447 to_slot[gi] = globals.len();
4448 globals.push(gi);
4449 match idx3(e) {
4450 Some(t) => tensors.push(t),
4451 None => {
4452 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
4453 eprintln!("слой {li}: эксперт {gi} без индексов в каталоге");
4454 }
4455 return None;
4456 }
4457 }
4458 }
4459 if globals.is_empty() {
4460 if room == 0 {
4466 static SAID_ZERO: std::sync::atomic::AtomicBool =
4467 std::sync::atomic::AtomicBool::new(false);
4468 if !SAID_ZERO.swap(true, std::sync::atomic::Ordering::Relaxed) {
4469 tracing::warn!(
4470 "начиная со слоя {li}, в бюджете VRAM не осталось места даже под одного \
4471 эксперта — остальные веса остаются mmap-backed и читаются по требованию"
4472 );
4473 }
4474 } else {
4475 tracing::warn!("слой {li}: маска не оставила ни одного эксперта");
4476 }
4477 return None;
4478 }
4479 tensors.push(idx3(&l.shared)?); let (rows, cols) = (l.gate.rows(), l.gate.cols());
4481 let mut router = vec![0.0f32; rows * cols];
4482 for r in 0..rows {
4483 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4484 }
4485 let remap: Vec<u32> = to_slot
4486 .iter()
4487 .map(|&sl| {
4488 if sl == usize::MAX {
4489 u32::MAX
4490 } else {
4491 sl as u32
4492 }
4493 })
4494 .collect();
4495 Some(Arc::new(Pack {
4496 bias: l.gate_bias.clone(),
4497 mask: route_mask,
4498 router,
4499 to_slot,
4500 dynslots: std::sync::Mutex::new(PackDyn {
4501 remap: remap.clone(),
4502 owner: globals.iter().map(|&g| g as u32).collect(),
4503 last: vec![0; globals.len()],
4504 clock: 0,
4505 mutated: false,
4506 seen: vec![0; cfg.n_routed_experts],
4507 }),
4508 remap,
4509 globals,
4510 tensors,
4511 global: None,
4512 }))
4513 };
4514 let v = build();
4515 cache.lock().unwrap().insert(key, v.clone());
4516 v
4517}
4518
4519#[cfg(feature = "gpu")]
4527fn moe_frame(
4528 hidden: &[f32],
4529 l: &Dsv4Layer,
4530 cfg: &Dsv4Cfg,
4531 li: usize,
4532 logits: &[f32],
4533 forced: Option<&[usize]>,
4534 pool: Option<&crate::pool::Pool>,
4535 hc_cur: Option<&crate::gpu_wgpu::Dsv4HcTail>,
4538 hc_next: Option<(&crate::gpu_wgpu::Dsv4HcTail, &[f32])>,
4539 out: &mut [f32],
4540) -> Option<(Vec<f32>, usize)> {
4541 macro_rules! no {
4542 ($($t:tt)*) => {{
4543 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
4544 eprintln!("кадр MoE отклонён: {}", format_args!($($t)*));
4545 }
4546 return None;
4547 }};
4548 }
4549 let Some(pk) = pack_for(l, cfg, li) else {
4550 no!("слой {li}: упаковка экспертов не построена");
4551 };
4552 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
4555 no!("слой {li}: эксперты не отображены из файла");
4556 };
4557 let subset = !pk.route_complete();
4558 let needs_remap = pk.needs_remap();
4559 fn fetch_quota() -> usize {
4566 static Q: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
4567 *Q.get_or_init(|| {
4568 std::env::var("CMF_DSV4_FETCH_MAX")
4569 .ok()
4570 .and_then(|v| v.parse().ok())
4571 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().0)
4572 })
4573 }
4574 fn fetch_min_seen() -> u16 {
4575 static M: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
4576 *M.get_or_init(|| {
4577 std::env::var("CMF_DSV4_FETCH_MIN_SEEN")
4578 .ok()
4579 .and_then(|v| v.parse().ok())
4580 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().1)
4581 })
4582 }
4583 let mut dynv = pk.dynslots.lock().unwrap();
4584 let mut pidx = Vec::new();
4589 let mut pwt = Vec::new();
4590 let mut predicted_logits = Vec::new();
4596 let prediction_logits: &[f32] = if subset && logits.is_empty() && !hidden.is_empty() {
4597 predicted_logits.resize(cfg.n_routed_experts, 0.0);
4598 l.gate.matvec(hidden, &mut predicted_logits, pool);
4599 &predicted_logits
4600 } else {
4601 logits
4602 };
4603 if subset && !prediction_logits.is_empty() {
4604 route(
4605 prediction_logits,
4606 l.gate_bias.as_deref(),
4607 cfg.top_k,
4608 cfg.route_scale,
4609 forced,
4610 l.mask.as_deref(),
4611 &mut pidx,
4612 &mut pwt,
4613 );
4614 }
4615 let global_remap = pk.global.as_ref().map(|gl| {
4616 gl.pool.ensure_picks(
4617 &model,
4618 gl.layer,
4619 &pidx,
4620 &l.experts,
4621 cfg.top_k,
4627 1,
4628 )
4629 });
4630 if subset && fetch_quota() > 0 && !pidx.is_empty() && !dynv.owner.is_empty() {
4631 dynv.clock += 1;
4632 let clock = dynv.clock;
4633 if clock % 64 == 0 {
4634 for v in dynv.seen.iter_mut() {
4635 *v >>= 1;
4636 }
4637 }
4638 for &pick in &pidx {
4639 dynv.seen[pick] = dynv.seen[pick].saturating_add(1);
4640 let sl = dynv.remap[pick];
4641 if sl != u32::MAX {
4642 dynv.last[sl as usize] = clock;
4643 }
4644 }
4645 let mut fetched = 0usize;
4646 for &pick in &pidx {
4647 if fetched >= fetch_quota() {
4648 break;
4649 }
4650 if dynv.remap[pick] != u32::MAX {
4651 continue;
4652 }
4653 if dynv.seen[pick] < fetch_min_seen() {
4654 continue; }
4656 let victim = (0..dynv.owner.len())
4658 .filter(|&sl| dynv.last[sl] != clock)
4659 .min_by_key(|&sl| dynv.last[sl]);
4660 let Some(victim) = victim else { break };
4661 let Some(exp) = l.experts.get(pick) else { continue };
4662 let t3 = (|| {
4663 Some((
4664 exp.w1.model_idx()?,
4665 exp.w3.model_idx()?,
4666 exp.w2.model_idx()?,
4667 ))
4668 })();
4669 let Some(t3) = t3 else { continue };
4670 let gu_q2 =
4671 exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
4672 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
4673 if !crate::gpu_wgpu::dsv4_slot_fill(
4674 &model,
4675 pack_first,
4676 victim,
4677 pick,
4678 t3,
4679 cfg.moe_inter,
4680 cfg.dim,
4681 gu_q2,
4682 ) {
4683 break;
4684 }
4685 let old = dynv.owner[victim] as usize;
4686 if old < dynv.remap.len() {
4687 dynv.remap[old] = u32::MAX;
4688 }
4689 dynv.remap[pick] = victim as u32;
4690 dynv.owner[victim] = pick as u32;
4691 dynv.last[victim] = clock;
4692 dynv.mutated = true;
4693 fetched += 1;
4694 }
4695 }
4696 let fpack: Option<Vec<usize>> = match forced {
4700 Some(f) if needs_remap => Some(f.to_vec()),
4701 Some(f) => {
4702 let v: Vec<usize> = f.iter().map(|&g| pk.to_slot[g]).collect();
4703 if v.contains(&usize::MAX) {
4704 no!("слой {li}: хеш-слой называет эксперта вне упаковки");
4705 }
4706 Some(v)
4707 }
4708 None => None,
4709 };
4710 let lg: Vec<f32> = if logits.is_empty() || needs_remap {
4717 logits.to_vec()
4718 } else {
4719 pk.globals.iter().map(|&g| logits[g]).collect()
4720 };
4721 let live_remap = global_remap.as_deref().unwrap_or(dynv.remap.as_slice());
4722 let w = crate::gpu_wgpu::Dsv4MoeW {
4723 router: &pk.router,
4724 experts: &pk.tensors,
4725 logits: &lg,
4726 bias: pk.bias.as_deref(),
4727 mask: pk.mask.as_deref(),
4728 forced: fpack.as_deref(),
4729 remap: needs_remap.then_some(live_remap),
4730 global: pk.global.as_ref().map(|gl| crate::gpu_wgpu::Dsv4GlobalMoe {
4731 pool_uid: gl.pool.uid,
4732 shared_slot: gl.shared_slot,
4733 segment_slots: gl.pool.segment_slots as u32,
4734 }),
4735 };
4736 let g = crate::gpu_wgpu::Dsv4MoeGeom {
4737 hidden: cfg.dim,
4738 inter: cfg.moe_inter,
4739 top_k: cfg.top_k,
4740 route_scale: cfg.route_scale,
4741 swiglu_limit: cfg.swiglu_limit,
4742 gu_q2: l
4743 .experts
4744 .first()
4745 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
4746 };
4747 let mut cold = Vec::new();
4748 let mut cold_x = Vec::new();
4749 let overlap: Vec<usize> = if !hidden.is_empty() {
4758 pidx.iter()
4759 .copied()
4760 .filter(|&pick| live_remap.get(pick).copied().unwrap_or(u32::MAX) == u32::MAX)
4761 .collect()
4762 } else {
4763 Vec::new()
4764 };
4765 let mut early: std::collections::HashMap<usize, Vec<f32>> = std::collections::HashMap::new();
4766 let frame_ok = std::thread::scope(|sc| {
4767 let handles: Vec<_> = overlap
4768 .iter()
4769 .filter_map(|&gi| l.experts.get(gi).map(|exp| (gi, exp)))
4770 .map(|(gi, exp)| {
4771 sc.spawn(move || {
4772 let mut a = vec![0.0f32; cfg.dim];
4773 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, 1.0, None, &mut a));
4774 (gi, a)
4775 })
4776 })
4777 .collect();
4778 let ok = crate::gpu_wgpu::dsv4_moe_frame(
4779 &model,
4780 &w,
4781 g,
4782 hidden,
4783 &mut cold,
4784 &mut cold_x,
4785 hc_cur,
4786 hc_next,
4787 out,
4788 );
4789 for h in handles {
4790 if let Ok((gi, a)) = h.join() {
4791 early.insert(gi, a);
4792 }
4793 }
4794 ok
4795 });
4796 if !frame_ok {
4797 return None;
4798 }
4799 if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
4802 let csum: f32 = cold.iter().map(|c| c.1).sum();
4803 eprintln!(
4804 "[холодные] слой {li}: вернулось {} из {} | сумма холодных {csum:.4} | \
4805 route_scale {:.4} | {:?}",
4806 cold.len(),
4807 cfg.top_k,
4808 cfg.route_scale,
4809 &cold[..cold.len().min(3)]
4810 );
4811 }
4812 let mut acc = vec![0.0f32; cfg.dim];
4813 let mut cold_sum = vec![0.0f32; cfg.dim];
4814 let cold_input = if hidden.is_empty() {
4815 cold_x.as_slice()
4816 } else {
4817 hidden
4818 };
4819 if !early.is_empty() {
4832 for &(gi, wt) in &cold {
4837 if let Some(a) = early.get(&gi) {
4838 for ((o, sum), v) in out.iter_mut().zip(&mut cold_sum).zip(a.iter()) {
4839 *o += v * wt;
4840 *sum += v * wt;
4841 }
4842 continue;
4843 }
4844 let Some(exp) = l.experts.get(gi) else { continue };
4845 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
4846 for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
4847 *o += a;
4848 *sum += a;
4849 }
4850 }
4851 return Some((cold_sum, cold.len()));
4852 }
4853 match pool {
4854 Some(p) if cold.len() > 1 => {
4855 let results: Vec<std::sync::Mutex<Vec<f32>>> =
4856 cold.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
4857 let (cold_ref, results_ref) = (&cold, &results);
4858 p.run_rows(cold.len(), &move |cs, ce| {
4859 for i in cs..ce {
4860 let (gi, wt) = cold_ref[i];
4861 let Some(exp) = l.experts.get(gi) else { continue };
4862 let mut a = vec![0.0f32; cfg.dim];
4863 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, None, &mut a));
4864 *results_ref[i].lock().unwrap() = a;
4865 }
4866 });
4867 for r in &results {
4870 let a = r.lock().unwrap();
4871 if a.is_empty() {
4872 continue;
4873 }
4874 for ((o, sum), v) in out.iter_mut().zip(&mut cold_sum).zip(a.iter()) {
4875 *o += v;
4876 *sum += v;
4877 }
4878 }
4879 }
4880 _ => {
4881 for &(gi, wt) in &cold {
4882 let Some(exp) = l.experts.get(gi) else {
4883 continue;
4884 };
4885 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
4886 for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
4887 *o += a;
4888 *sum += a;
4889 }
4890 }
4891 }
4892 }
4893 Some((cold_sum, cold.len()))
4894}
4895
4896#[cfg(feature = "gpu")]
4902fn last_grew(now: u64) -> u64 {
4903 use std::sync::atomic::{AtomicU64, Ordering};
4904 static SEEN: AtomicU64 = AtomicU64::new(0);
4905 let was = SEEN.load(Ordering::Relaxed);
4906 if was != now {
4907 SEEN.store(now, Ordering::Relaxed);
4908 compressed_map().lock().unwrap().clear();
4909 return u64::MAX; }
4911 now
4912}
4913
4914#[cfg(feature = "gpu")]
4915fn compressed_map() -> &'static std::sync::Mutex<std::collections::HashMap<(u64, usize), usize>> {
4916 use std::collections::HashMap;
4917 use std::sync::{Mutex, OnceLock};
4918 static W: OnceLock<Mutex<HashMap<(u64, usize), usize>>> = OnceLock::new();
4919 W.get_or_init(|| Mutex::new(HashMap::new()))
4920}
4921
4922#[cfg(feature = "gpu")]
4923fn compressed_written(kv_id: u64, li: usize) -> usize {
4924 compressed_map()
4925 .lock()
4926 .unwrap()
4927 .get(&(kv_id, li))
4928 .copied()
4929 .unwrap_or(0)
4930}
4931
4932#[cfg(feature = "gpu")]
4935fn note_compressed(kv_id: u64, li: usize, n: usize) {
4936 compressed_map().lock().unwrap().insert((kv_id, li), n);
4937}
4938
4939#[cfg(feature = "gpu")]
4940fn gpu_moe2_enabled() -> bool {
4941 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4942 *ON.get_or_init(|| {
4943 std::env::var("CMF_DSV4_GPU_MOE2")
4944 .map(|v| v != "0")
4945 .unwrap_or(true)
4946 && crate::gpu::backend_available()
4947 })
4948}
4949
4950pub fn moe_step(
4951 hidden: &[f32],
4952 l: &Dsv4Layer,
4953 cfg: &Dsv4Cfg,
4954 token_id: u32,
4955 li: usize,
4957 pool: Option<&crate::pool::Pool>,
4958 out: &mut [f32],
4959) {
4960 let _t0 = prof::on().then(std::time::Instant::now);
4961 let _guard = scopeguard_moe(_t0, li);
4962 let mut logits = vec![0.0f32; cfg.n_routed_experts];
4963 l.gate.matvec(hidden, &mut logits, pool);
4964 let (mut idx, mut w) = (Vec::new(), Vec::new());
4965 route(
4966 &logits,
4967 l.gate_bias.as_deref(),
4968 cfg.top_k,
4969 cfg.route_scale,
4970 l.tid2eid
4971 .as_ref()
4972 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id))
4973 .as_deref(),
4974 l.mask.as_deref(),
4975 &mut idx,
4976 &mut w,
4977 );
4978 if route_stats_on() {
4979 let routed: Vec<(usize, f32)> = idx.iter().copied().zip(w.iter().copied()).collect();
4980 record_route(li, 0, cfg.n_routed_experts, &routed);
4981 }
4982 crate::pipeline::moe_trace_at(li as i32, &idx);
4988 #[cfg(feature = "gpu")]
4992 if gpu_moe2_enabled() && crate::gpu::enabled_here() {
4993 let forced = l
4994 .tid2eid
4995 .as_ref()
4996 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
4997 if moe_frame(
4998 hidden,
4999 l,
5000 cfg,
5001 li,
5002 &logits,
5003 forced.as_deref(),
5004 pool,
5005 None,
5006 None,
5007 out,
5008 )
5009 .is_some()
5010 {
5011 if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
5017 let mut want = vec![0.0f32; out.len()];
5018 let mut acc = vec![0.0f32; cfg.dim];
5019 crate::gpu::cpu_scope(|| {
5024 for (e, &ei) in idx.iter().enumerate() {
5025 let Some(exp) = l.experts.get(ei) else {
5026 continue;
5027 };
5028 run_expert(
5029 hidden,
5030 exp,
5031 cfg,
5032 w.get(e).copied().unwrap_or(0.0),
5033 pool,
5034 &mut acc,
5035 );
5036 for (o, a) in want.iter_mut().zip(&acc) {
5037 *o += a;
5038 }
5039 }
5040 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
5041 for (o, a) in want.iter_mut().zip(&acc) {
5042 *o += a;
5043 }
5044 });
5045 let num: f32 = want
5046 .iter()
5047 .zip(out.iter())
5048 .map(|(a, b)| (a - b) * (a - b))
5049 .sum();
5050 let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
5051 let rel = (num / den).sqrt();
5052 if rel > 1e-3 {
5053 let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
5054 eprintln!(
5055 "[кадр MoE] слой {li}: расхождение {rel:.3e} | выбрано {} | \
5056 упаковано {packed} из {} | хеш={} | смещение={}",
5057 idx.len(),
5058 cfg.n_routed_experts,
5059 l.tid2eid.is_some(),
5060 l.gate_bias.is_some()
5061 );
5062 }
5063 }
5064 return;
5065 }
5066 }
5067 PICK_TALLY.with(|t| {
5073 if let Some(v) = t.borrow_mut().as_mut() {
5074 v.push((li, idx.to_vec()));
5075 }
5076 });
5077 if dump_path().is_some() {
5078 PICKED.with(|p| {
5079 let mut p = p.borrow_mut();
5080 if p.len() <= li {
5081 p.resize(li + 1, Vec::new());
5082 }
5083 p[li] = idx.clone();
5084 });
5085 }
5086 fn gpu_moe_on() -> bool {
5105 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5106 *ON.get_or_init(|| std::env::var("CMF_DSV4_GPU_MOE").is_ok_and(|v| v != "0"))
5107 }
5108 if gpu_moe_on() && crate::gpu::enabled_here() {
5109 let mut jobs = Vec::with_capacity(idx.len() + 1);
5110 let mut model_ref = None;
5111 let mut ok = true;
5112 for (e, &ei) in idx.iter().enumerate() {
5113 let Some(exp) = l.experts.get(ei) else {
5114 continue;
5115 };
5116 ok &= crate::pipeline::moe_push_job_parts(
5117 &exp.w1,
5118 &exp.w3,
5119 &exp.w2,
5120 hidden,
5121 w.get(e).copied().unwrap_or(0.0),
5122 cfg.swiglu_limit,
5123 &mut jobs,
5124 &mut model_ref,
5125 )
5126 .is_some();
5127 }
5128 ok &= crate::pipeline::moe_push_job_parts(
5129 &l.shared.w1,
5130 &l.shared.w3,
5131 &l.shared.w2,
5132 hidden,
5133 1.0,
5134 cfg.swiglu_limit,
5135 &mut jobs,
5136 &mut model_ref,
5137 )
5138 .is_some();
5139 if ok {
5140 if let Some(m) = model_ref.as_ref() {
5141 if crate::gpu::moe_block(m, &jobs, out) {
5142 if std::env::var("CMF_DSV4_GPU_CHECK").is_ok() {
5147 let mut want = vec![0.0f32; out.len()];
5148 let mut acc = vec![0.0f32; cfg.dim];
5149 for (e, &ei) in idx.iter().enumerate() {
5150 let Some(exp) = l.experts.get(ei) else {
5151 continue;
5152 };
5153 run_expert(
5154 hidden,
5155 exp,
5156 cfg,
5157 w.get(e).copied().unwrap_or(0.0),
5158 pool,
5159 &mut acc,
5160 );
5161 for (o, a) in want.iter_mut().zip(&acc) {
5162 *o += a;
5163 }
5164 }
5165 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
5166 for (o, a) in want.iter_mut().zip(&acc) {
5167 *o += a;
5168 }
5169 let num: f32 = want
5170 .iter()
5171 .zip(out.iter())
5172 .map(|(a, b)| (a - b) * (a - b))
5173 .sum();
5174 let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
5175 eprintln!(
5176 "[dsv4-gpu] слой {li}: расхождение {:.3e} | |CPU|={:.5} |GPU|={:.5} | экспертов {}",
5177 (num / den).sqrt(),
5178 den.sqrt(),
5179 out.iter().map(|x| x * x).sum::<f32>().sqrt(),
5180 jobs.len()
5181 );
5182 }
5183 return;
5184 }
5185 }
5186 }
5187 }
5188 out.fill(0.0);
5189 let jobs: Vec<(Option<usize>, f32)> = idx
5196 .iter()
5197 .enumerate()
5198 .map(|(e, &ei)| (Some(ei), w.get(e).copied().unwrap_or(0.0)))
5199 .chain(std::iter::once((None, 1.0)))
5200 .collect();
5201 match pool {
5202 Some(p) if jobs.len() > 1 => {
5203 let results: Vec<std::sync::Mutex<Vec<f32>>> =
5204 jobs.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
5205 let (jobs_ref, results_ref) = (&jobs, &results);
5206 p.run_rows(jobs.len(), &move |cs, ce| {
5207 for i in cs..ce {
5208 let (ei, wt) = jobs_ref[i];
5209 let exp = match ei {
5210 Some(ei) => match l.experts.get(ei) {
5211 Some(x) => x,
5212 None => continue,
5213 },
5214 None => &l.shared,
5215 };
5216 let mut a = vec![0.0f32; cfg.dim];
5217 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, wt, None, &mut a));
5218 *results_ref[i].lock().unwrap() = a;
5219 }
5220 });
5221 for r in &results {
5222 let a = r.lock().unwrap();
5223 for (o, v) in out.iter_mut().zip(a.iter()) {
5224 *o += v;
5225 }
5226 }
5227 }
5228 _ => {
5229 let mut acc = vec![0.0f32; cfg.dim];
5230 for &(ei, wt) in &jobs {
5231 let exp = match ei {
5232 Some(ei) => match l.experts.get(ei) {
5233 Some(x) => x,
5234 None => continue,
5235 },
5236 None => &l.shared,
5237 };
5238 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, wt, pool, &mut acc));
5239 for (o, a) in out.iter_mut().zip(&acc) {
5240 *o += a;
5241 }
5242 }
5243 }
5244 }
5245}
5246
5247fn run_expert(
5250 x: &[f32],
5251 e: &Dsv4Expert,
5252 cfg: &Dsv4Cfg,
5253 weight: f32,
5254 pool: Option<&crate::pool::Pool>,
5255 out: &mut [f32],
5256) {
5257 expert_swiglu(
5258 x,
5259 &|src, dst| e.w1.matvec(src, dst, pool),
5260 &|src, dst| e.w3.matvec(src, dst, pool),
5261 &|src, dst| e.w2.matvec(src, dst, pool),
5262 cfg.moe_inter,
5263 weight,
5264 cfg.swiglu_limit,
5265 out,
5266 );
5267}
5268
5269fn moe_step_block(
5274 xs: &[f32],
5275 b: usize,
5276 l: &Dsv4Layer,
5277 cfg: &Dsv4Cfg,
5278 token_ids: &[u32],
5279 tally_layer: usize,
5280 pool: Option<&crate::pool::Pool>,
5281 out: &mut [f32],
5282) {
5283 let (dim, inter) = (cfg.dim, cfg.moe_inter);
5284 debug_assert_eq!(xs.len(), b * dim);
5285 debug_assert_eq!(out.len(), b * dim);
5286 out.fill(0.0);
5287
5288 let mut logits = vec![0.0f32; b * cfg.n_routed_experts];
5289 l.gate.matmat(xs, b, &mut logits, pool);
5290 let mut picks: Vec<Vec<usize>> = Vec::with_capacity(b);
5291 let mut weights: Vec<Vec<f32>> = Vec::with_capacity(b);
5292 for bi in 0..b {
5293 let mut idx = Vec::new();
5294 let mut wt = Vec::new();
5295 let forced = l.tid2eid.as_ref().map(|tbl| {
5296 hash_route(
5297 tbl,
5298 cfg.vocab,
5299 cfg.top_k,
5300 token_ids.get(bi).copied().unwrap_or(0),
5301 )
5302 });
5303 route(
5304 &logits[bi * cfg.n_routed_experts..(bi + 1) * cfg.n_routed_experts],
5305 l.gate_bias.as_deref(),
5306 cfg.top_k,
5307 cfg.route_scale,
5308 forced.as_deref(),
5309 l.mask.as_deref(),
5310 &mut idx,
5311 &mut wt,
5312 );
5313 PICK_TALLY.with(|t| {
5314 if let Some(v) = t.borrow_mut().as_mut() {
5315 v.push((tally_layer, idx.clone()));
5316 }
5317 });
5318 picks.push(idx);
5319 weights.push(wt);
5320 }
5321
5322 let mut routed = vec![0.0f32; b * cfg.top_k * dim];
5325 let mut active: Vec<(usize, Vec<(usize, usize, f32)>)> = Vec::new();
5328 for ei in 0..l.experts.len() {
5329 let mut jobs = Vec::new();
5330 for bi in 0..b {
5331 for (slot, &picked) in picks[bi].iter().enumerate() {
5332 if picked == ei {
5333 jobs.push((bi, slot, weights[bi][slot]));
5334 }
5335 }
5336 }
5337 if !jobs.is_empty() {
5338 active.push((ei, jobs));
5339 }
5340 }
5341 let expert_fwd = |ei: usize, jobs: &[(usize, usize, f32)], inner: Option<&crate::pool::Pool>| -> Vec<f32> {
5344 let e = &l.experts[ei];
5345 let n = jobs.len();
5346 let mut xj = vec![0.0f32; n * dim];
5347 for (j, &(bi, _, _)) in jobs.iter().enumerate() {
5348 xj[j * dim..(j + 1) * dim].copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
5349 }
5350 let mut gate = vec![0.0f32; n * inter];
5351 let mut up = vec![0.0f32; n * inter];
5352 e.w1.matmat(&xj, n, &mut gate, inner);
5353 e.w3.matmat(&xj, n, &mut up, inner);
5354 for (j, &(_, _, wt)) in jobs.iter().enumerate() {
5355 let (gj, uj) = (
5356 &mut gate[j * inter..(j + 1) * inter],
5357 &mut up[j * inter..(j + 1) * inter],
5358 );
5359 if cfg.swiglu_limit > 0.0 {
5360 for u in uj.iter_mut() {
5361 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5362 }
5363 for g in gj.iter_mut() {
5364 *g = g.min(cfg.swiglu_limit);
5365 }
5366 }
5367 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5368 *g = (*g / (1.0 + (-*g).exp())) * u * wt;
5369 }
5370 }
5371 let mut down = vec![0.0f32; n * dim];
5372 e.w2.matmat(&gate, n, &mut down, inner);
5373 down
5374 };
5375 match pool {
5385 Some(p) if active.len() > 1 => {
5386 let results: Vec<std::sync::Mutex<Vec<f32>>> =
5387 active.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
5388 let active_ref = &active;
5389 let results_ref = &results;
5390 let fwd = &expert_fwd;
5391 p.run_rows(active_ref.len(), &move |s, e| {
5392 for i in s..e {
5393 let (ei, jobs) = &active_ref[i];
5394 let d = crate::gpu::cpu_scope(|| fwd(*ei, jobs, None));
5395 *results_ref[i].lock().unwrap() = d;
5396 }
5397 });
5398 for (i, (_, jobs)) in active.iter().enumerate() {
5399 let down = results[i].lock().unwrap();
5400 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5401 routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
5402 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5403 }
5404 }
5405 }
5406 _ => {
5407 for (ei, jobs) in &active {
5408 let down = expert_fwd(*ei, jobs, pool);
5409 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5410 routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
5411 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5412 }
5413 }
5414 }
5415 }
5416
5417 let mut sg = vec![0.0f32; b * inter];
5420 let mut su = vec![0.0f32; b * inter];
5421 l.shared.w1.matmat(xs, b, &mut sg, pool);
5422 l.shared.w3.matmat(xs, b, &mut su, pool);
5423 for bi in 0..b {
5424 let (gj, uj) = (
5425 &mut sg[bi * inter..(bi + 1) * inter],
5426 &mut su[bi * inter..(bi + 1) * inter],
5427 );
5428 if cfg.swiglu_limit > 0.0 {
5429 for u in uj.iter_mut() {
5430 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5431 }
5432 for g in gj.iter_mut() {
5433 *g = g.min(cfg.swiglu_limit);
5434 }
5435 }
5436 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5437 *g = (*g / (1.0 + (-*g).exp())) * u;
5438 }
5439 }
5440 let mut shared = vec![0.0f32; b * dim];
5441 l.shared.w2.matmat(&sg, b, &mut shared, pool);
5442
5443 for bi in 0..b {
5444 let dst = &mut out[bi * dim..(bi + 1) * dim];
5445 for slot in 0..picks[bi].len() {
5446 let src = &routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim];
5447 for (o, &v) in dst.iter_mut().zip(src) {
5448 *o += v;
5449 }
5450 }
5451 for (o, &v) in dst.iter_mut().zip(&shared[bi * dim..(bi + 1) * dim]) {
5452 *o += v;
5453 }
5454 }
5455}
5456
5457fn cold_step_block(
5462 xs: &[f32],
5463 b: usize,
5464 l: &Dsv4Layer,
5465 cfg: &Dsv4Cfg,
5466 cold: &[Vec<(usize, f32)>],
5467 pool: Option<&crate::pool::Pool>,
5468 out: &mut [f32],
5469) {
5470 let (dim, inter) = (cfg.dim, cfg.moe_inter);
5471 debug_assert_eq!(xs.len(), b * dim);
5472 debug_assert_eq!(cold.len(), b);
5473 debug_assert_eq!(out.len(), b * dim);
5474 out.fill(0.0);
5475 let slots = cold.iter().map(Vec::len).max().unwrap_or(0);
5476 if slots == 0 {
5477 return;
5478 }
5479 let grouped = std::env::var("CMF_DSV4_COLD_MATMAT")
5485 .map(|v| v != "0")
5486 .unwrap_or(true);
5487 if !grouped {
5488 let jobs: Vec<(usize, usize, usize, f32)> = cold
5489 .iter()
5490 .enumerate()
5491 .flat_map(|(bi, row)| {
5492 row.iter()
5493 .enumerate()
5494 .map(move |(slot, &(ei, wt))| (bi, slot, ei, wt))
5495 })
5496 .collect();
5497 let results: Vec<std::sync::Mutex<Vec<f32>>> =
5498 jobs.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
5499 match pool {
5500 Some(p) if jobs.len() > 1 => {
5501 let (jobs_ref, results_ref) = (&jobs, &results);
5502 p.run_rows(jobs.len(), &move |s, e| {
5503 for i in s..e {
5504 let (_, _, ei, wt) = jobs_ref[i];
5505 let Some(exp) = l.experts.get(ei) else { continue };
5506 let bi = jobs_ref[i].0;
5507 let mut a = vec![0.0f32; dim];
5508 crate::gpu::cpu_scope(|| {
5509 run_expert(
5510 &xs[bi * dim..(bi + 1) * dim],
5511 exp,
5512 cfg,
5513 wt,
5514 None,
5515 &mut a,
5516 )
5517 });
5518 *results_ref[i].lock().unwrap() = a;
5519 }
5520 });
5521 }
5522 _ => {
5523 for (i, &(bi, _, ei, wt)) in jobs.iter().enumerate() {
5524 let Some(exp) = l.experts.get(ei) else { continue };
5525 let mut a = vec![0.0f32; dim];
5526 crate::gpu::cpu_scope(|| {
5527 run_expert(
5528 &xs[bi * dim..(bi + 1) * dim],
5529 exp,
5530 cfg,
5531 wt,
5532 pool,
5533 &mut a,
5534 )
5535 });
5536 *results[i].lock().unwrap() = a;
5537 }
5538 }
5539 }
5540 for (i, &(bi, _, _, _)) in jobs.iter().enumerate() {
5543 let a = results[i].lock().unwrap();
5544 for (o, &v) in out[bi * dim..(bi + 1) * dim].iter_mut().zip(a.iter()) {
5545 *o += v;
5546 }
5547 }
5548 return;
5549 }
5550 let mut active: Vec<(usize, Vec<(usize, usize, f32)>)> = Vec::new();
5551 for ei in 0..l.experts.len() {
5552 let mut jobs = Vec::new();
5553 for bi in 0..b {
5554 for (slot, &(picked, wt)) in cold[bi].iter().enumerate() {
5555 if picked == ei {
5556 jobs.push((bi, slot, wt));
5557 }
5558 }
5559 }
5560 if !jobs.is_empty() {
5561 active.push((ei, jobs));
5562 }
5563 }
5564 let expert_fwd = |ei: usize,
5565 jobs: &[(usize, usize, f32)],
5566 inner: Option<&crate::pool::Pool>|
5567 -> Vec<f32> {
5568 let e = &l.experts[ei];
5569 let n = jobs.len();
5570 let mut xj = vec![0.0f32; n * dim];
5571 for (j, &(bi, _, _)) in jobs.iter().enumerate() {
5572 xj[j * dim..(j + 1) * dim].copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
5573 }
5574 let mut gate = vec![0.0f32; n * inter];
5575 let mut up = vec![0.0f32; n * inter];
5576 e.w1.matmat(&xj, n, &mut gate, inner);
5577 e.w3.matmat(&xj, n, &mut up, inner);
5578 for (j, &(_, _, wt)) in jobs.iter().enumerate() {
5579 let (gj, uj) = (
5580 &mut gate[j * inter..(j + 1) * inter],
5581 &mut up[j * inter..(j + 1) * inter],
5582 );
5583 if cfg.swiglu_limit > 0.0 {
5584 for u in uj.iter_mut() {
5585 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5586 }
5587 for g in gj.iter_mut() {
5588 *g = g.min(cfg.swiglu_limit);
5589 }
5590 }
5591 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5592 *g = (*g / (1.0 + (-*g).exp())) * u * wt;
5593 }
5594 }
5595 let mut down = vec![0.0f32; n * dim];
5596 e.w2.matmat(&gate, n, &mut down, inner);
5597 down
5598 };
5599 let mut routed = vec![0.0f32; b * slots * dim];
5600 match pool {
5601 Some(p) if active.len() > 1 => {
5602 let results: Vec<std::sync::Mutex<Vec<f32>>> =
5603 active.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
5604 let active_ref = &active;
5605 let results_ref = &results;
5606 let fwd = &expert_fwd;
5607 p.run_rows(active.len(), &move |s, e| {
5608 for i in s..e {
5609 let (ei, jobs) = &active_ref[i];
5610 let d = crate::gpu::cpu_scope(|| fwd(*ei, jobs, None));
5611 *results_ref[i].lock().unwrap() = d;
5612 }
5613 });
5614 for (i, (_, jobs)) in active.iter().enumerate() {
5615 let down = results[i].lock().unwrap();
5616 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5617 routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim]
5618 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5619 }
5620 }
5621 }
5622 _ => {
5623 for (ei, jobs) in &active {
5624 let down = expert_fwd(*ei, jobs, pool);
5625 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5626 routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim]
5627 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5628 }
5629 }
5630 }
5631 }
5632 for bi in 0..b {
5633 let dst = &mut out[bi * dim..(bi + 1) * dim];
5634 for slot in 0..cold[bi].len() {
5635 let src = &routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim];
5636 for (o, &v) in dst.iter_mut().zip(src) {
5637 *o += v;
5638 }
5639 }
5640 }
5641}
5642
5643#[cfg(feature = "gpu")]
5648fn refill_route_slots(l: &Dsv4Layer, cfg: &Dsv4Cfg, pk: &Pack, picks: &[usize]) {
5649 if picks.is_empty() || pk.route_complete() {
5650 return;
5651 }
5652 let quota = {
5653 static Q: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
5654 *Q.get_or_init(|| {
5655 std::env::var("CMF_DSV4_FETCH_MAX")
5656 .ok()
5657 .and_then(|v| v.parse().ok())
5658 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().0)
5659 })
5660 };
5661 if quota == 0 {
5662 return;
5663 }
5664 let min_seen = {
5665 static M: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
5666 *M.get_or_init(|| {
5667 std::env::var("CMF_DSV4_FETCH_MIN_SEEN")
5668 .ok()
5669 .and_then(|v| v.parse().ok())
5670 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().1)
5671 })
5672 };
5673 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
5674 return;
5675 };
5676 let mut dynv = pk.dynslots.lock().unwrap();
5677 if dynv.owner.is_empty() {
5678 return;
5679 }
5680 dynv.clock += 1;
5681 let clock = dynv.clock;
5682 if clock % 64 == 0 {
5683 for seen in &mut dynv.seen {
5684 *seen >>= 1;
5685 }
5686 }
5687 for &pick in picks {
5688 if pick >= dynv.seen.len() {
5689 continue;
5690 }
5691 dynv.seen[pick] = dynv.seen[pick].saturating_add(1);
5692 let slot = dynv.remap[pick];
5693 if slot != u32::MAX {
5694 dynv.last[slot as usize] = clock;
5695 }
5696 }
5697 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
5698 let mut fetched = 0usize;
5699 for &pick in picks {
5700 if fetched >= quota || pick >= dynv.remap.len() {
5701 break;
5702 }
5703 if dynv.remap[pick] != u32::MAX || dynv.seen[pick] < min_seen {
5704 continue;
5705 }
5706 let victim = (0..dynv.owner.len())
5707 .filter(|&slot| dynv.last[slot] != clock)
5708 .min_by_key(|&slot| dynv.last[slot]);
5709 let Some(victim) = victim else { break };
5710 let Some(exp) = l.experts.get(pick) else {
5711 continue;
5712 };
5713 let tensors = (|| {
5714 Some((
5715 exp.w1.model_idx()?,
5716 exp.w3.model_idx()?,
5717 exp.w2.model_idx()?,
5718 ))
5719 })();
5720 let Some(tensors) = tensors else { continue };
5721 let gu_q2 = exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
5722 if !crate::gpu_wgpu::dsv4_slot_fill(
5723 &model,
5724 pack_first,
5725 victim,
5726 pick,
5727 tensors,
5728 cfg.moe_inter,
5729 cfg.dim,
5730 gu_q2,
5731 ) {
5732 break;
5733 }
5734 let old = dynv.owner[victim] as usize;
5735 if old < dynv.remap.len() {
5736 dynv.remap[old] = u32::MAX;
5737 }
5738 dynv.remap[pick] = victim as u32;
5739 dynv.owner[victim] = pick as u32;
5740 dynv.last[victim] = clock;
5741 dynv.mutated = true;
5742 fetched += 1;
5743 }
5744}
5745
5746fn o_project_block(
5751 attn: &[f32],
5752 b: usize,
5753 wo_a: &crate::qtensor::QTensor,
5754 wo_b: &crate::qtensor::QTensor,
5755 groups: usize,
5756 lora: usize,
5757 pool: Option<&crate::pool::Pool>,
5758 out: &mut [f32],
5759) {
5760 let attn_len = attn.len() / b;
5761 let per_group = attn_len / groups;
5762 let rows = groups * lora;
5763 let mut mid = vec![0.0f32; b * rows];
5764 let mid_addr = crate::pool::SendMut::new(mid.as_mut_ptr());
5765 let run = |start: usize, end: usize| {
5766 let mut wr = vec![0.0f32; wo_a.cols()];
5767 for r in start..end {
5768 wo_a.row_f32(r, &mut wr);
5769 let group = r / lora;
5770 for bi in 0..b {
5771 let x = &attn
5772 [bi * attn_len + group * per_group..bi * attn_len + (group + 1) * per_group];
5773 let v = wr.iter().zip(x).map(|(w, x)| w * x).sum();
5774 unsafe { *mid_addr.at(bi * rows + r) = v };
5775 }
5776 }
5777 };
5778 match pool {
5779 Some(p) if rows >= 256 => p.run_rows(rows, &run),
5780 _ => run(0, rows),
5781 }
5782 wo_b.matmat(&mid, b, out, pool);
5783}
5784
5785fn no_compressed() -> bool {
5791 static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5792 *OFF.get_or_init(|| std::env::var("CMF_DSV4_NO_COMPRESSED").is_ok_and(|v| v != "0"))
5793}
5794
5795fn trace_on() -> bool {
5796 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5797 *ON.get_or_init(|| std::env::var("CMF_DSV4_TRACE").is_ok_and(|v| v != "0"))
5798}
5799
5800fn rms_of(v: &[f32]) -> f32 {
5801 if v.is_empty() {
5802 return 0.0;
5803 }
5804 (v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32).sqrt()
5805}
5806
5807#[cfg(feature = "gpu")]
5808fn verify_fp_on(pos: usize) -> bool {
5809 static POS: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
5810 let want = *POS.get_or_init(|| {
5811 std::env::var("CMF_DSV4_FP_POS")
5812 .ok()
5813 .and_then(|v| v.parse().ok())
5814 });
5815 want == Some(pos)
5816}
5817
5818#[cfg(feature = "gpu")]
5819fn verify_fp(tag: &str, pos: usize, li: usize, state: &[f32]) {
5820 if !verify_fp_on(pos) {
5821 return;
5822 }
5823 let mut fp = 0xcbf29ce484222325u64;
5824 for &x in state {
5825 fp ^= x.to_bits() as u64;
5826 fp = fp.wrapping_mul(0x100000001b3);
5827 }
5828 eprintln!(
5829 "[dsv4-fp] {tag} pos={pos} li={li} fp={fp:016x} rms={:.7} head={:?}",
5830 rms_of(state),
5831 &state[..4.min(state.len())]
5832 );
5833}
5834
5835thread_local! {
5841 static BODY: std::cell::RefCell<Vec<String>> = const { std::cell::RefCell::new(Vec::new()) };
5843 static PICKED: std::cell::RefCell<Vec<Vec<usize>>> =
5847 const { std::cell::RefCell::new(Vec::new()) };
5848 static PICK_TALLY: std::cell::RefCell<Option<Vec<(usize, Vec<usize>)>>> =
5850 const { std::cell::RefCell::new(None) };
5851}
5852
5853pub fn pick_tally_arm() {
5855 PICK_TALLY.with(|t| *t.borrow_mut() = Some(Vec::new()));
5856}
5857
5858pub fn pick_tally_take() -> Vec<(usize, Vec<usize>)> {
5860 PICK_TALLY.with(|t| t.borrow_mut().take().unwrap_or_default())
5861}
5862
5863pub fn tally_unique(picks: &[(usize, Vec<usize>)]) -> (usize, usize) {
5866 let mut seen = std::collections::HashSet::new();
5871 let mut total = 0;
5872 for (li, v) in picks {
5873 total += v.len();
5874 for &e in v {
5875 seen.insert((*li, e));
5876 }
5877 }
5878 (seen.len(), total)
5879}
5880
5881fn dump_path() -> Option<&'static str> {
5882 static P: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
5883 P.get_or_init(|| std::env::var("CMF_DSV4_DUMP").ok())
5884 .as_deref()
5885}
5886
5887fn dump_line(json: &str) {
5888 if let Some(p) = dump_path() {
5889 use std::io::Write as _;
5890 if let Ok(mut f) = std::fs::OpenOptions::new()
5891 .create(true)
5892 .append(true)
5893 .open(p)
5894 {
5895 let _ = writeln!(f, "{json}");
5896 }
5897 }
5898}
5899
5900fn vec_json(v: &[f32]) -> String {
5901 let mut s = String::with_capacity(v.len() * 9);
5902 s.push('[');
5903 for (i, x) in v.iter().enumerate() {
5904 if i > 0 {
5905 s.push(',');
5906 }
5907 s.push_str(&format!("{x:.6e}"));
5908 }
5909 s.push(']');
5910 s
5911}
5912
5913#[allow(clippy::too_many_arguments)]
5921#[allow(clippy::too_many_arguments)]
5931fn batch_prefill() -> usize {
5935 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
5936 *N.get_or_init(|| {
5937 std::env::var("CMF_DSV4_BATCH")
5938 .ok()
5939 .and_then(|v| v.parse::<usize>().ok())
5940 .filter(|&n| (1..=32).contains(&n))
5941 .unwrap_or(8)
5948 })
5949}
5950
5951#[allow(clippy::too_many_arguments)]
5964fn forward_chunk_batched(
5965 g: &Dsv4Globals,
5966 layers: &[Dsv4Layer],
5967 cfg: &Dsv4Cfg,
5968 st: &mut Dsv4State,
5969 ids: &[u32],
5970 pos0: usize,
5971 inv_freq: &[f32],
5972 pool: Option<&crate::pool::Pool>,
5973 logits: &mut Vec<f32>,
5974 want_logits: bool,
5975) -> bool {
5976 #[cfg(not(feature = "gpu"))]
5977 {
5978 let _ = (
5979 g,
5980 layers,
5981 cfg,
5982 st,
5983 ids,
5984 pos0,
5985 inv_freq,
5986 pool,
5987 logits,
5988 want_logits,
5989 );
5990 false
5991 }
5992 #[cfg(feature = "gpu")]
5993 {
5994 let b = ids.len();
5995 let gpu_end = st
6000 .dev_set
6001 .iter()
6002 .enumerate()
6003 .position(|(li, &on)| {
6004 !on || pack_for(&layers[li], cfg, li)
6005 .is_none_or(|p| !p.route_complete())
6006 })
6007 .unwrap_or(st.dev_set.len());
6008 let why = if b < 2 {
6009 "токенов меньше двух"
6010 } else if !chain_enabled() {
6011 "цепочка выключена"
6012 } else if !st.dev_owned {
6013 "карта ещё не владеет состоянием"
6014 } else if st.dev_set.len() != layers.len() {
6015 "набор слоёв ещё не зафиксирован"
6016 } else if st.dev_set[gpu_end.min(st.dev_set.len())..]
6017 .iter()
6018 .enumerate()
6019 .any(|(i, &on)| on && !st.partial_set.get(gpu_end + i).copied().unwrap_or(false))
6020 {
6021 "слои на карте не образуют префикс"
6022 } else {
6023 ""
6024 };
6025 if !why.is_empty() {
6026 static SAID: std::sync::Once = std::sync::Once::new();
6027 SAID.call_once(|| tracing::warn!("dsv4: пакет отказал — {why}"));
6028 return false;
6029 }
6030 let (hc, dim) = (cfg.hc_mult, cfg.dim);
6031 let mut emb = vec![0.0f32; dim];
6032 let mut states = vec![0.0f32; b * hc * dim];
6033 for (t, &id) in ids.iter().enumerate() {
6034 let mut state = vec![0.0f32; hc * dim];
6035 g.embed.row_f32(id as usize, &mut emb);
6036 for j in 0..hc {
6037 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
6038 }
6039 let (folded, post0, comb0) = hc_fold_norm(
6040 &state,
6041 &layers[0].hc_attn_fn,
6042 &layers[0].hc_attn_scale,
6043 &layers[0].hc_attn_base,
6044 &layers[0].attn_norm,
6045 cfg,
6046 pool,
6047 );
6048 let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
6049 layers[0].wq_a.matvec(&folded, &mut qn0, pool);
6050 rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
6051 if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
6052 || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
6053 || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
6054 || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
6055 {
6056 return false;
6057 }
6058 states[t * hc * dim..(t + 1) * hc * dim].copy_from_slice(&state);
6059 }
6060 let run: Vec<usize> = (0..gpu_end).collect();
6061 let mut folded = Vec::new();
6062 st.pos = pos0;
6063 if gpu_end > 0 {
6064 if !dsv4_chain_run(
6065 layers,
6066 &run,
6067 cfg,
6068 g,
6069 st,
6070 *ids.last().unwrap(),
6071 &mut folded,
6072 Some(&mut states),
6073 b,
6074 ids,
6075 true,
6076 pool,
6077 ) {
6078 return false;
6079 }
6080 }
6081 let mut scratch = HcScratch::new(cfg);
6087 host_tail_walk_batch(
6088 g,
6089 layers,
6090 cfg,
6091 st,
6092 gpu_end,
6093 &mut states,
6094 ids,
6095 pos0,
6096 b,
6097 inv_freq,
6098 &mut scratch,
6099 pool,
6100 None,
6101 );
6102 st.pos = pos0 + b;
6103 {
6108 static SAID: std::sync::Once = std::sync::Once::new();
6109 SAID.call_once(|| tracing::warn!("dsv4: префилл пакетами по {b}"));
6110 }
6111 if want_logits {
6116 let last = &states[(b - 1) * hc * dim..b * hc * dim];
6117 let mut h = vec![0.0f32; dim];
6118 hc_head_fold(
6119 last,
6120 &g.hc_head_fn,
6121 g.hc_head_scale,
6122 &g.hc_head_base,
6123 cfg,
6124 pool,
6125 &mut h,
6126 );
6127 rms_weighted(&mut h, &g.norm, cfg.norm_eps);
6128 logits.resize(cfg.vocab, 0.0);
6129 g.head.matvec(&h, logits, pool);
6130 } else {
6131 logits.clear();
6132 }
6133 true
6134 }
6135}
6136
6137#[cfg(feature = "gpu")]
6145pub struct Dsv4SpecTxn {
6146 pos0: usize,
6147 batch: usize,
6148 pub(crate) gpu_end: usize,
6149 dev_filled: Vec<usize>,
6150 dev_n_comp: Vec<usize>,
6151 dev_n_ix: Vec<usize>,
6152 host: Vec<(usize, HostLayerSnap)>,
6153 host_steps: Vec<(usize, Vec<HostLayerSnap>)>,
6158 pub states: Vec<f32>,
6162 shadow: Option<crate::gpu_wgpu::Dsv4SpecShadow>,
6163}
6164
6165#[cfg(feature = "gpu")]
6166struct HostLayerSnap {
6167 window: Vec<f32>,
6168 compressed: Vec<f32>,
6169 index_kv: Vec<f32>,
6170 pending_kv: Vec<f32>,
6171 pending_score: Vec<f32>,
6172 prev_kv: Vec<f32>,
6173 prev_score: Vec<f32>,
6174 pending_ix_kv: Vec<f32>,
6175 pending_ix_score: Vec<f32>,
6176 prev_ix_kv: Vec<f32>,
6177 prev_ix_score: Vec<f32>,
6178}
6179
6180#[cfg(feature = "gpu")]
6181fn host_snap(st: &Dsv4State, li: usize) -> HostLayerSnap {
6182 HostLayerSnap {
6183 window: st.window[li].clone(),
6184 compressed: st.compressed[li].clone(),
6185 index_kv: st.index_kv[li].clone(),
6186 pending_kv: st.pending_kv[li].clone(),
6187 pending_score: st.pending_score[li].clone(),
6188 prev_kv: st.prev_kv[li].clone(),
6189 prev_score: st.prev_score[li].clone(),
6190 pending_ix_kv: st.pending_ix_kv[li].clone(),
6191 pending_ix_score: st.pending_ix_score[li].clone(),
6192 prev_ix_kv: st.prev_ix_kv[li].clone(),
6193 prev_ix_score: st.prev_ix_score[li].clone(),
6194 }
6195}
6196
6197#[cfg(feature = "gpu")]
6198fn host_restore(st: &mut Dsv4State, li: usize, s: &HostLayerSnap) {
6199 st.window[li] = s.window.clone();
6200 st.compressed[li] = s.compressed.clone();
6201 st.index_kv[li] = s.index_kv.clone();
6202 st.pending_kv[li] = s.pending_kv.clone();
6203 st.pending_score[li] = s.pending_score.clone();
6204 st.prev_kv[li] = s.prev_kv.clone();
6205 st.prev_score[li] = s.prev_score.clone();
6206 st.pending_ix_kv[li] = s.pending_ix_kv.clone();
6207 st.pending_ix_score[li] = s.pending_ix_score.clone();
6208 st.prev_ix_kv[li] = s.prev_ix_kv.clone();
6209 st.prev_ix_score[li] = s.prev_ix_score.clone();
6210}
6211
6212#[cfg(feature = "gpu")]
6216#[allow(clippy::too_many_arguments)]
6217fn host_tail_walk(
6218 g: &Dsv4Globals,
6219 layers: &[Dsv4Layer],
6220 cfg: &Dsv4Cfg,
6221 st: &mut Dsv4State,
6222 gpu_end: usize,
6223 state: &mut [f32],
6224 token_id: u32,
6225 pos: usize,
6226 inv_freq: &[f32],
6227 scratch: &mut HcScratch,
6228 pool: Option<&crate::pool::Pool>,
6229) {
6230 st.pos = pos;
6231 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
6232 let freqs = if l.compressor.is_some() {
6233 &g.inv_freq_compress
6234 } else {
6235 &g.inv_freq_window
6236 };
6237 let freqs = if freqs.is_empty() {
6238 inv_freq
6239 } else {
6240 freqs.as_slice()
6241 };
6242 hc_block(
6243 state,
6244 &l.hc_attn_fn,
6245 &l.hc_attn_scale,
6246 &l.hc_attn_base,
6247 &l.attn_norm,
6248 cfg,
6249 scratch,
6250 pool,
6251 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
6252 );
6253 hc_block(
6254 state,
6255 &l.hc_ffn_fn,
6256 &l.hc_ffn_scale,
6257 &l.hc_ffn_base,
6258 &l.ffn_norm,
6259 cfg,
6260 scratch,
6261 pool,
6262 |f, o| {
6263 if host_cpu_moe() {
6264 crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
6265 } else {
6266 moe_step(f, l, cfg, token_id, li, pool, o)
6267 }
6268 },
6269 );
6270 dspark_note(li, state, cfg);
6271 }
6272}
6273
6274#[cfg(feature = "gpu")]
6280#[allow(clippy::too_many_arguments)]
6281fn host_tail_walk_batch(
6282 g: &Dsv4Globals,
6283 layers: &[Dsv4Layer],
6284 cfg: &Dsv4Cfg,
6285 st: &mut Dsv4State,
6286 gpu_end: usize,
6287 states: &mut [f32],
6288 ids: &[u32],
6289 pos0: usize,
6290 b: usize,
6291 inv_freq: &[f32],
6292 scratch: &mut HcScratch,
6293 pool: Option<&crate::pool::Pool>,
6294 mut steps: Option<&mut Vec<(usize, Vec<HostLayerSnap>)>>,
6295) {
6296 let (hc, dim) = (cfg.hc_mult, cfg.dim);
6297 let mix_hc = (2 + hc) * hc;
6298 let mut folds = vec![0.0f32; b * dim];
6299 let mut mo = vec![0.0f32; b * dim];
6300 let mut posts = vec![0.0f32; b * hc];
6301 let mut combs = vec![0.0f32; b * hc * hc];
6302 let mut resid = vec![0.0f32; b * hc * dim];
6303 let spec_time = {
6304 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6305 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
6306 };
6307 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
6308 let t_attn = std::time::Instant::now();
6309 let freqs = if l.compressor.is_some() {
6310 &g.inv_freq_compress
6311 } else {
6312 &g.inv_freq_window
6313 };
6314 let freqs = if freqs.is_empty() {
6315 inv_freq
6316 } else {
6317 freqs.as_slice()
6318 };
6319 for t in 0..b {
6320 st.pos = pos0 + t;
6321 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
6322 hc_block(
6323 state,
6324 &l.hc_attn_fn,
6325 &l.hc_attn_scale,
6326 &l.hc_attn_base,
6327 &l.attn_norm,
6328 cfg,
6329 scratch,
6330 pool,
6331 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
6332 );
6333 if let Some(steps) = steps.as_mut() {
6334 match steps.iter_mut().find(|(l, _)| *l == li) {
6335 Some((_, v)) => v.push(host_snap(st, li)),
6336 None => steps.push((li, vec![host_snap(st, li)])),
6337 }
6338 }
6339 }
6340 let t_glue = std::time::Instant::now();
6341 for t in 0..b {
6342 let state = &states[t * hc * dim..(t + 1) * hc * dim];
6343 hc_mixes(
6344 state,
6345 &l.hc_ffn_fn,
6346 mix_hc,
6347 cfg.norm_eps,
6348 pool,
6349 &mut scratch.mixes,
6350 );
6351 hc_split_sinkhorn(
6352 &scratch.mixes,
6353 &l.hc_ffn_scale,
6354 &l.hc_ffn_base,
6355 hc,
6356 cfg.hc_sinkhorn_iters,
6357 cfg.hc_eps,
6358 &mut scratch.pre,
6359 &mut posts[t * hc..(t + 1) * hc],
6360 &mut combs[t * hc * hc..(t + 1) * hc * hc],
6361 );
6362 let fold = &mut folds[t * dim..(t + 1) * dim];
6363 hc_fold(state, &scratch.pre, hc, dim, fold);
6364 let ms = fold.iter().map(|v| v * v).sum::<f32>() / dim as f32;
6365 let inv = 1.0 / (ms + cfg.norm_eps).sqrt();
6366 for (v, w) in fold.iter_mut().zip(&l.ffn_norm) {
6367 *v = *v * inv * w;
6368 }
6369 resid[t * hc * dim..(t + 1) * hc * dim]
6370 .copy_from_slice(&states[t * hc * dim..(t + 1) * hc * dim]);
6371 }
6372 let t_moe = std::time::Instant::now();
6373 let tail_pack = {
6379 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6380 *ON.get_or_init(|| {
6381 std::env::var("CMF_DSV4_TAIL_PACK")
6382 .map(|v| v != "0")
6383 .unwrap_or(true)
6384 })
6385 };
6386 let mut packed_done = false;
6387 if tail_pack && pack_for(l, cfg, li).is_some() {
6388 packed_done = true;
6389 for t in 0..b {
6390 let f = &folds[t * dim..(t + 1) * dim];
6391 let forced = l.tid2eid.as_ref().map(|tbl| {
6392 hash_route(tbl, cfg.vocab, cfg.top_k, ids.get(t).copied().unwrap_or(0))
6393 });
6394 let o = &mut mo[t * dim..(t + 1) * dim];
6395 match moe_frame(f, l, cfg, li, &[], forced.as_deref(), pool, None, None, o) {
6396 Some((cold_sum, n)) => {
6397 if n > 0 {
6398 for (od, cd) in o.iter_mut().zip(cold_sum.iter()) {
6399 *od += cd;
6400 }
6401 }
6402 }
6403 None => {
6404 packed_done = false;
6405 break;
6406 }
6407 }
6408 }
6409 }
6410 if !packed_done {
6411 if host_cpu_moe() {
6412 crate::gpu::cpu_scope(|| moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo));
6413 } else {
6414 moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo);
6415 }
6416 }
6417 let t_exp = std::time::Instant::now();
6418 for t in 0..b {
6419 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
6420 hc_expand(
6421 &mo[t * dim..(t + 1) * dim],
6422 &resid[t * hc * dim..(t + 1) * hc * dim],
6423 &posts[t * hc..(t + 1) * hc],
6424 &combs[t * hc * hc..(t + 1) * hc * hc],
6425 hc,
6426 dim,
6427 state,
6428 );
6429 dspark_note(li, state, cfg);
6430 }
6431 if spec_time {
6432 eprintln!(
6433 "хвост слоя {li}: attn {:.1} мс, клей {:.1}, moe {:.1}, expand {:.1}",
6434 (t_glue - t_attn).as_secs_f64() * 1e3,
6435 (t_moe - t_glue).as_secs_f64() * 1e3,
6436 (t_exp - t_moe).as_secs_f64() * 1e3,
6437 t_exp.elapsed().as_secs_f64() * 1e3,
6438 );
6439 }
6440 }
6441}
6442
6443#[cfg(feature = "gpu")]
6449#[allow(clippy::too_many_arguments)]
6450fn partial_layer_batch(
6451 g: &Dsv4Globals,
6452 layers: &[Dsv4Layer],
6453 cfg: &Dsv4Cfg,
6454 st: &mut Dsv4State,
6455 li: usize,
6456 states: &mut [f32],
6457 ids: &[u32],
6458 pos0: usize,
6459 b: usize,
6460 pool: Option<&crate::pool::Pool>,
6461) -> bool {
6462 let l = &layers[li];
6463 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
6464 if states.len() < b * hc * dim || ids.len() < b {
6465 return false;
6466 }
6467 let Some(pk) = pack_for(l, cfg, li) else {
6468 return false;
6469 };
6470 if pk.route_complete() {
6471 return false;
6472 }
6473 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
6474 return false;
6475 };
6476 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
6477 l.wq_a.model_idx(),
6478 l.wq_b.model_idx(),
6479 l.wo_a.model_idx(),
6480 l.wo_b.model_idx(),
6481 l.wkv.model_idx(),
6482 ) else {
6483 return false;
6484 };
6485 let comp = match &l.compressor {
6486 None => None,
6487 Some(cp) => {
6488 let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
6489 return false;
6490 };
6491 Some((
6492 crate::gpu_wgpu::Dsv4CompW {
6493 wkv: a,
6494 wgate: bx,
6495 norm: &cp.norm,
6496 ape: &cp.ape,
6497 },
6498 crate::gpu_wgpu::Dsv4CompGeom {
6499 width: cp.wkv.rows(),
6500 hidden: dim,
6501 ratio: cp.ratio,
6502 overlap: cp.overlap,
6503 rope_dim: cfg.rope_head_dim,
6504 eps: cfg.norm_eps,
6505 },
6506 ))
6507 }
6508 };
6509 let ix = match &l.indexer {
6510 None => None,
6511 Some(ixr) => {
6512 let cp = &ixr.compressor;
6513 let (Some(a), Some(bx), Some(qb), Some(wp)) = (
6514 cp.wkv.model_idx(),
6515 cp.wgate.model_idx(),
6516 ixr.wq_b.model_idx(),
6517 ixr.weights_proj.model_idx(),
6518 ) else {
6519 return false;
6520 };
6521 let ih = ixr.weights_proj.rows();
6522 Some((
6523 crate::gpu_wgpu::Dsv4CompW {
6524 wkv: a,
6525 wgate: bx,
6526 norm: &cp.norm,
6527 ape: &cp.ape,
6528 },
6529 crate::gpu_wgpu::Dsv4CompGeom {
6530 width: cp.wkv.rows(),
6531 hidden: dim,
6532 ratio: cp.ratio,
6533 overlap: cp.overlap,
6534 rope_dim: cfg.rope_head_dim,
6535 eps: cfg.norm_eps,
6536 },
6537 crate::gpu_wgpu::Dsv4IxW {
6538 wq_b: qb,
6539 weights_proj: wp,
6540 },
6541 crate::gpu_wgpu::Dsv4IxGeom {
6542 ih,
6543 idim: ixr.wq_b.rows() / ih.max(1),
6544 q_lora: cfg.q_lora_rank,
6545 hidden: dim,
6546 rope_dim: cfg.rope_head_dim,
6547 eps: cfg.norm_eps,
6548 top_k: cfg.index_topk,
6549 window: cfg.window,
6550 },
6551 ))
6552 }
6553 };
6554 let ew_c = comp.as_ref().map_or(0, |(_, cg)| {
6555 if cg.overlap { cg.width / 2 } else { cg.width }
6556 });
6557 let ew_i = ix.as_ref().map_or(0, |(_, cg, _, _)| {
6558 if cg.overlap { cg.width / 2 } else { cg.width }
6559 });
6560 let comp_extra = comp
6561 .as_ref()
6562 .map_or(0, |(_, cg)| b.div_ceil(cg.ratio.max(1)));
6563 let need = cfg.window * hd
6564 + (st.dev_n_comp[li] + comp_extra + 1) * ew_c.max(1)
6565 + (b + 1) * hd;
6566 if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
6567 return false;
6568 }
6569 let base = crate::gpu_wgpu::Dsv4Prep {
6570 wkv,
6571 kv_norm: &l.kv_norm,
6572 comp,
6573 ix,
6574 filled: st.dev_filled[li],
6575 window: cfg.window,
6576 n_comp: st.dev_n_comp[li],
6577 n_ix: st.dev_n_ix[li],
6578 comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
6579 ix_dst_off: st.dev_n_ix[li] * ew_i,
6580 idx_cap: cfg.window
6581 + if l.indexer.is_some() {
6582 cfg.index_topk
6583 } else {
6584 st.dev_n_comp[li] + comp_extra + 1
6585 },
6586 };
6587 let mut preps = Vec::with_capacity(b);
6588 for t in 0..b {
6589 let mut p = base.clone();
6590 p.filled = (base.filled + t).min(base.window);
6591 let advanced = |ratio: usize| -> usize {
6592 if ratio == 0 {
6593 0
6594 } else {
6595 (0..t).filter(|k| (pos0 + k + 1) % ratio == 0).count()
6596 }
6597 };
6598 if let Some((_, cg)) = base.comp.as_ref() {
6599 p.n_comp = base.n_comp + advanced(cg.ratio);
6600 p.comp_dst_off = base.comp_dst_off + (p.n_comp - base.n_comp) * ew_c;
6601 }
6602 if let Some((_, cg, _, _)) = base.ix.as_ref() {
6603 p.n_ix = base.n_ix + advanced(cg.ratio);
6604 p.ix_dst_off = base.ix_dst_off + (p.n_ix - base.n_ix) * ew_i;
6605 }
6606 preps.push(p);
6607 }
6608
6609 for t in 0..b {
6613 let state = &states[t * hc * dim..(t + 1) * hc * dim];
6614 let (fold, post, comb) = hc_fold_norm(
6615 state,
6616 &l.hc_attn_fn,
6617 &l.hc_attn_scale,
6618 &l.hc_attn_base,
6619 &l.attn_norm,
6620 cfg,
6621 pool,
6622 );
6623 let mut qn = vec![0.0f32; cfg.q_lora_rank];
6624 l.wq_a.matvec(&fold, &mut qn, pool);
6625 rms_weighted(&mut qn, &l.q_norm, cfg.norm_eps);
6626 if !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, state, &post, &comb, &fold, &qn) {
6627 return false;
6628 }
6629 }
6630 let forced_rows: Vec<Option<Vec<usize>>> = ids
6631 .iter()
6632 .take(b)
6633 .map(|&id| {
6634 l.tid2eid
6635 .as_ref()
6636 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, id))
6637 })
6638 .collect();
6639 let dynv = pk.dynslots.lock().unwrap();
6640 let w = crate::gpu_wgpu::Dsv4LayerW {
6641 attn: crate::gpu_wgpu::Dsv4AttnW {
6642 wq_a,
6643 wq_b,
6644 wo_a,
6645 wo_b,
6646 q_norm: &l.q_norm,
6647 sink: &l.attn_sink,
6648 },
6649 moe: crate::gpu_wgpu::Dsv4MoeW {
6650 router: &[],
6651 experts: &pk.tensors,
6652 logits: &[],
6653 bias: pk.bias.as_deref(),
6654 mask: pk.mask.as_deref(),
6655 forced: None,
6656 remap: Some(&dynv.remap),
6657 global: None,
6658 },
6659 hc_ffn_fn: &l.hc_ffn_fn,
6660 hc_ffn_scale: &l.hc_ffn_scale,
6661 hc_ffn_base: &l.hc_ffn_base,
6662 hc_next_fn: None,
6665 hc_next_scale: &l.hc_attn_scale,
6666 hc_next_base: &l.hc_attn_base,
6667 ffn_norm: &l.ffn_norm,
6668 next_norm: &l.attn_norm,
6669 next_q_norm: &l.q_norm,
6670 next_wq_a: None,
6671 router: &pk.router,
6672 };
6673 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
6674 attn: crate::gpu_wgpu::Dsv4AttnGeom {
6675 dim,
6676 nh: cfg.n_heads,
6677 hd,
6678 rd: cfg.rope_head_dim,
6679 q_lora: cfg.q_lora_rank,
6680 o_lora: cfg.o_lora_rank,
6681 o_groups: cfg.o_groups,
6682 eps: cfg.norm_eps,
6683 scale: (hd as f32).powf(-0.5),
6684 },
6685 moe: crate::gpu_wgpu::Dsv4MoeGeom {
6686 hidden: dim,
6687 inter: cfg.moe_inter,
6688 top_k: cfg.top_k,
6689 route_scale: cfg.route_scale,
6690 swiglu_limit: cfg.swiglu_limit,
6691 gu_q2: l
6692 .experts
6693 .first()
6694 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
6695 },
6696 hc,
6697 hc_eps: cfg.hc_eps,
6698 sinkhorn_iters: cfg.hc_sinkhorn_iters,
6699 };
6700 let freqs = if l.compressor.is_some() {
6701 g.inv_freq_compress.as_slice()
6702 } else {
6703 g.inv_freq_window.as_slice()
6704 };
6705 let Some(mut got) = crate::gpu_wgpu::dsv4_layer_batch_partial(
6706 &model,
6707 &w,
6708 geom,
6709 st.kv_id,
6710 li,
6711 b,
6712 &preps,
6713 Some(&forced_rows),
6714 freqs,
6715 pos0,
6716 ) else {
6717 return false;
6718 };
6719 drop(dynv);
6720 let mut cold_sum = vec![0.0f32; b * dim];
6721 cold_step_block(&got.cold_x, b, l, cfg, &got.cold, pool, &mut cold_sum);
6722 for t in 0..b {
6723 let state = &mut got.states[t * hc * dim..(t + 1) * hc * dim];
6724 let post = &got.posts[t * hc..(t + 1) * hc];
6725 let cold = &cold_sum[t * dim..(t + 1) * dim];
6726 for j in 0..hc {
6727 for d in 0..dim {
6728 state[j * dim + d] += post[j] * cold[d];
6729 }
6730 }
6731 }
6732 states[..b * hc * dim].copy_from_slice(&got.states);
6733 if !crate::gpu_wgpu::dsv4_spec_cap_write_host(li, b, hc * dim, &got.states) {
6734 return false;
6735 }
6736 if let Some(first_route) = got.routed.first() {
6737 refill_route_slots(l, cfg, &pk, first_route);
6738 }
6739 for t in 0..b {
6740 let pos = pos0 + t;
6741 st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
6742 if let Some((_, cg, ..)) = base.ix.as_ref() {
6743 if (pos + 1) % cg.ratio == 0 {
6744 st.dev_n_ix[li] += 1;
6745 }
6746 }
6747 if let Some((_, cg)) = base.comp.as_ref() {
6748 if (pos + 1) % cg.ratio == 0 {
6749 st.dev_n_comp[li] += 1;
6750 note_compressed(st.kv_id, li, st.dev_n_comp[li]);
6751 }
6752 }
6753 }
6754 true
6755}
6756
6757#[cfg(feature = "gpu")]
6766#[allow(clippy::too_many_arguments)]
6767pub fn dsv4_verify_chunk(
6768 g: &Dsv4Globals,
6769 layers: &[Dsv4Layer],
6770 cfg: &Dsv4Cfg,
6771 st: &mut Dsv4State,
6772 ids: &[u32],
6773 pos0: usize,
6774 inv_freq: &[f32],
6775 pool: Option<&crate::pool::Pool>,
6776 cap_targets: &[usize],
6777 argmax_out: &mut Vec<u32>,
6778 logits_out: &mut Vec<f32>,
6779 walked_out: &mut Vec<f32>,
6780) -> Option<Dsv4SpecTxn> {
6781 let b = ids.len();
6782 let complete_end = st
6786 .dev_set
6787 .iter()
6788 .enumerate()
6789 .position(|(li, &on)| {
6790 !on || pack_for(&layers[li], cfg, li)
6791 .is_none_or(|p| !p.route_complete())
6792 })
6793 .unwrap_or(st.dev_set.len());
6794 fn partial_batch_on() -> bool {
6795 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6796 *ON.get_or_init(|| {
6797 std::env::var("CMF_DSV4_PARTIAL_BATCH")
6798 .map(|v| v != "0")
6799 .unwrap_or(true)
6800 })
6801 }
6802 let device_end = if partial_batch_on() {
6803 (complete_end..layers.len())
6804 .take_while(|&li| {
6805 st.dev_set.get(li).copied().unwrap_or(false)
6806 && st.partial_set.get(li).copied().unwrap_or(false)
6807 && pack_for(&layers[li], cfg, li).is_some_and(|p| !p.route_complete())
6808 })
6809 .last()
6810 .map_or(complete_end, |li| li + 1)
6811 } else {
6812 complete_end
6813 };
6814 let full_beyond = st.dev_set[device_end.min(st.dev_set.len())..]
6817 .iter()
6818 .enumerate()
6819 .any(|(i, &on)| on && !st.partial_set.get(device_end + i).copied().unwrap_or(false));
6820 fn host_verify_on() -> bool {
6827 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6828 *ON.get_or_init(|| {
6829 std::env::var("CMF_DSV4_HOST_VERIFY")
6830 .map(|v| v != "0")
6831 .unwrap_or(true)
6836 })
6837 }
6838 if b < 2
6839 || !chain_enabled()
6840 || !st.dev_owned
6841 || st.dev_set.len() != layers.len()
6842 || (device_end == 0 && !host_verify_on())
6843 || full_beyond
6844 {
6845 return None;
6846 }
6847 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
6848 let metas: Vec<(usize, usize, usize, usize)> = (0..device_end)
6850 .map(|li| (li, hd, cfg.window, st.dev_filled[li]))
6851 .collect();
6852 let shadow = crate::gpu_wgpu::dsv4_spec_shadow(st.kv_id, &metas, b)?;
6853 let mut txn = Dsv4SpecTxn {
6854 pos0,
6855 batch: b,
6856 gpu_end: device_end,
6857 dev_filled: st.dev_filled.clone(),
6858 dev_n_comp: st.dev_n_comp.clone(),
6859 dev_n_ix: st.dev_n_ix.clone(),
6860 host: (device_end..layers.len())
6861 .map(|li| (li, host_snap(st, li)))
6862 .collect(),
6863 states: Vec::new(),
6864 host_steps: Vec::new(),
6865 shadow: Some(shadow),
6866 };
6867 let dev_caps: Vec<usize> = cap_targets
6869 .iter()
6870 .copied()
6871 .filter(|&t| t < device_end)
6872 .collect();
6873 crate::gpu_wgpu::dsv4_spec_retain_arm(device_end, &dev_caps);
6874
6875 let mut emb = vec![0.0f32; dim];
6877 let mut states = vec![0.0f32; b * hc * dim];
6878 for (t, &id) in ids.iter().enumerate() {
6879 let mut state = vec![0.0f32; hc * dim];
6880 g.embed.row_f32(id as usize, &mut emb);
6881 for j in 0..hc {
6882 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
6883 }
6884 states[t * hc * dim..(t + 1) * hc * dim].copy_from_slice(&state);
6885 let (folded, post0, comb0) = hc_fold_norm(
6886 &state,
6887 &layers[0].hc_attn_fn,
6888 &layers[0].hc_attn_scale,
6889 &layers[0].hc_attn_base,
6890 &layers[0].attn_norm,
6891 cfg,
6892 pool,
6893 );
6894 let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
6895 layers[0].wq_a.matvec(&folded, &mut qn0, pool);
6896 rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
6897 if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
6898 || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
6899 || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
6900 || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
6901 {
6902 crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
6903 return None;
6904 }
6905 }
6906 let spec_time = {
6907 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6908 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
6909 };
6910 let t0 = std::time::Instant::now();
6911 let mut folded = Vec::new();
6912 st.pos = pos0;
6913 let fp_split = verify_fp_on(pos0)
6917 && std::env::var("CMF_DSV4_FP_SPLIT").is_ok_and(|v| v != "0");
6918 let mut ok = true;
6919 if fp_split {
6920 for li in 0..complete_end {
6921 let one = [li];
6922 ok = dsv4_chain_run(
6923 layers,
6924 &one,
6925 cfg,
6926 g,
6927 st,
6928 *ids.last().unwrap(),
6929 &mut folded,
6930 Some(&mut states),
6931 b,
6932 ids,
6933 li == 0,
6934 pool,
6935 );
6936 if !ok {
6937 break;
6938 }
6939 verify_fp("verify", pos0, li, &states[..hc * dim]);
6940 }
6941 } else if complete_end > 0 {
6942 let run: Vec<usize> = (0..complete_end).collect();
6943 ok = dsv4_chain_run(
6944 layers,
6945 &run,
6946 cfg,
6947 g,
6948 st,
6949 *ids.last().unwrap(),
6950 &mut folded,
6951 Some(&mut states),
6952 b,
6953 ids,
6954 true,
6955 pool,
6956 );
6957 if ok {
6958 verify_fp("verify", pos0, complete_end - 1, &states[..hc * dim]);
6959 }
6960 }
6961 if ok {
6962 for li in complete_end..device_end {
6963 if !partial_layer_batch(g, layers, cfg, st, li, &mut states, ids, pos0, b, pool) {
6964 ok = false;
6965 break;
6966 }
6967 verify_fp("verify", pos0, li, &states[..hc * dim]);
6968 }
6969 }
6970 crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
6971 if !ok {
6972 if let Some(sh) = txn.shadow.take() {
6975 let _ = crate::gpu_wgpu::dsv4_spec_restore(&sh);
6976 }
6977 st.dev_filled = txn.dev_filled;
6978 st.dev_n_comp = txn.dev_n_comp;
6979 st.dev_n_ix = txn.dev_n_ix;
6980 st.pos = pos0;
6981 return None;
6982 }
6983 txn.states = states.clone();
6984 let t_chain = t0.elapsed();
6985 if std::env::var("CMF_DSV4_FOLD_DBG").is_ok() {
6986 for li in 0..device_end {
6990 let Some(ixr) = &layers[li].indexer else {
6991 continue;
6992 };
6993 let ratio = ixr.compressor.ratio;
6994 for t in 0..b {
6995 if (pos0 + t + 1) % ratio == 0 {
6996 let ew = {
6997 let w = ixr.compressor.wkv.rows();
6998 if ixr.compressor.overlap { w / 2 } else { w }
6999 };
7000 let idx_new = txn.dev_n_ix[li]
7001 + (0..=t).filter(|k| (pos0 + k + 1) % ratio == 0).count()
7002 - 1;
7003 if let Some(v) =
7004 crate::gpu_wgpu::dsv4_dbg_read_ix(st.kv_id, li, idx_new * ew, ew.min(8))
7005 {
7006 let sum: f32 = v.iter().sum();
7007 eprintln!(
7008 "[fold] li={li} pos={} entry={idx_new} head={:?} sum={sum:.6}",
7009 pos0 + t,
7010 &v[..4.min(v.len())]
7011 );
7012 }
7013 }
7014 }
7015 }
7016 }
7017
7018 let mut scratch = HcScratch::new(cfg);
7020 argmax_out.clear();
7021 logits_out.clear();
7022 logits_out.resize(b * cfg.vocab, 0.0);
7023 let mut head_in = vec![0.0f32; b * dim];
7024 let mut host_steps: Vec<(usize, Vec<HostLayerSnap>)> = Vec::new();
7025 host_tail_walk_batch(
7026 g,
7027 layers,
7028 cfg,
7029 st,
7030 device_end,
7031 &mut states,
7032 ids,
7033 pos0,
7034 b,
7035 inv_freq,
7036 &mut scratch,
7037 pool,
7038 Some(&mut host_steps),
7039 );
7040 txn.host_steps = host_steps;
7041 for t in 0..b {
7042 let state = &states[t * hc * dim..(t + 1) * hc * dim];
7043 let h = &mut head_in[t * dim..(t + 1) * dim];
7044 hc_head_fold(
7045 state,
7046 &g.hc_head_fn,
7047 g.hc_head_scale,
7048 &g.hc_head_base,
7049 cfg,
7050 pool,
7051 h,
7052 );
7053 rms_weighted(h, &g.norm, cfg.norm_eps);
7054 }
7055 let batch_head = std::env::var("CMF_DSV4_SPEC_BATCH_HEAD").is_ok_and(|v| v != "0");
7060 let head_gpu = batch_head && g.head.model_idx().is_some_and(|hi| {
7061 let model = layers[0].experts.first().and_then(|e| e.w1.model_arc());
7062 model.is_some_and(|m| {
7063 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
7064 &m, hi, &head_in, b, cfg.vocab, dim, logits_out,
7065 )
7066 })
7067 });
7068 for t in 0..b {
7069 if !head_gpu {
7070 let h = &head_in[t * dim..(t + 1) * dim];
7071 g.head
7072 .matvec(h, &mut logits_out[t * cfg.vocab..(t + 1) * cfg.vocab], pool);
7073 }
7074 let row = &logits_out[t * cfg.vocab..(t + 1) * cfg.vocab];
7075 let mut best = 0usize;
7076 for v in 1..cfg.vocab {
7077 if row[v] > row[best] {
7078 best = v;
7079 }
7080 }
7081 argmax_out.push(best as u32);
7082 }
7083 walked_out.clear();
7084 walked_out.extend_from_slice(&states);
7085 st.pos = pos0 + b;
7086 if spec_time {
7087 eprintln!(
7088 "verify: тень+сид+цепочка {:.1} мс, хвост+голова {:.1} мс",
7089 t_chain.as_secs_f64() * 1e3,
7090 (t0.elapsed() - t_chain).as_secs_f64() * 1e3,
7091 );
7092 }
7093 Some(txn)
7094}
7095
7096#[cfg(feature = "gpu")]
7104pub fn dsv4_spec_finish(
7105 g: &Dsv4Globals,
7106 layers: &[Dsv4Layer],
7107 cfg: &Dsv4Cfg,
7108 st: &mut Dsv4State,
7109 mut txn: Dsv4SpecTxn,
7110 accepted: usize,
7111 ids: &[u32],
7112 inv_freq: &[f32],
7113 pool: Option<&crate::pool::Pool>,
7114) -> bool {
7115 macro_rules! sfail {
7116 ($($t:tt)*) => {{
7117 if std::env::var("CMF_DSV4_SPEC_DEBUG").is_ok() {
7118 eprintln!("spec_finish: {}", format_args!($($t)*));
7119 }
7120 return false;
7121 }};
7122 }
7123 let b = txn.batch;
7124 let k = accepted.min(b);
7125 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
7126 let win_metas: Vec<(usize, usize, usize, usize)> = (0..txn.gpu_end)
7129 .map(|li| (li, txn.dev_filled[li], cfg.window, hd))
7130 .collect();
7131 if !crate::gpu_wgpu::dsv4_spec_commit_windows(st.kv_id, &win_metas, b, k) {
7132 sfail!("коммит окон");
7133 }
7134 if k == b {
7135 return true;
7138 }
7139 let Some(sh) = txn.shadow.take() else {
7141 sfail!("нет тени")
7142 };
7143 if !crate::gpu_wgpu::dsv4_spec_restore(&sh) {
7144 sfail!("restore");
7145 }
7146 let Some(model) = layers[0].experts.first().and_then(|e| e.w1.model_arc()) else {
7147 sfail!("нет модели");
7148 };
7149 let mut plan: Vec<(usize, crate::gpu_wgpu::Dsv4Prep)> = Vec::new();
7150 let mut freqs_own: Vec<&[f32]> = Vec::new();
7151 for li in 0..txn.gpu_end {
7152 let l = &layers[li];
7153 let Some(wkv) = l.wkv.model_idx() else {
7154 sfail!("wkv слоя {li}")
7155 };
7156 let comp = match &l.compressor {
7157 None => None,
7158 Some(cp) => {
7159 let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
7160 sfail!("компрессор слоя {li}");
7161 };
7162 Some((
7163 crate::gpu_wgpu::Dsv4CompW {
7164 wkv: a,
7165 wgate: bx,
7166 norm: &cp.norm,
7167 ape: &cp.ape,
7168 },
7169 crate::gpu_wgpu::Dsv4CompGeom {
7170 width: cp.wkv.rows(),
7171 hidden: dim,
7172 ratio: cp.ratio,
7173 overlap: cp.overlap,
7174 rope_dim: cfg.rope_head_dim,
7175 eps: cfg.norm_eps,
7176 },
7177 ))
7178 }
7179 };
7180 let ix = match &l.indexer {
7181 None => None,
7182 Some(ixr) => {
7183 let cp = &ixr.compressor;
7184 let (Some(a), Some(bx), Some(qb), Some(wp)) = (
7185 cp.wkv.model_idx(),
7186 cp.wgate.model_idx(),
7187 ixr.wq_b.model_idx(),
7188 ixr.weights_proj.model_idx(),
7189 ) else {
7190 sfail!("индексер слоя {li}");
7191 };
7192 let ih = ixr.weights_proj.rows();
7193 Some((
7194 crate::gpu_wgpu::Dsv4CompW {
7195 wkv: a,
7196 wgate: bx,
7197 norm: &cp.norm,
7198 ape: &cp.ape,
7199 },
7200 crate::gpu_wgpu::Dsv4CompGeom {
7201 width: cp.wkv.rows(),
7202 hidden: dim,
7203 ratio: cp.ratio,
7204 overlap: cp.overlap,
7205 rope_dim: cfg.rope_head_dim,
7206 eps: cfg.norm_eps,
7207 },
7208 crate::gpu_wgpu::Dsv4IxW {
7209 wq_b: qb,
7210 weights_proj: wp,
7211 },
7212 crate::gpu_wgpu::Dsv4IxGeom {
7213 ih,
7214 idim: ixr.wq_b.rows() / ih.max(1),
7215 q_lora: cfg.q_lora_rank,
7216 hidden: dim,
7217 rope_dim: cfg.rope_head_dim,
7218 eps: cfg.norm_eps,
7219 top_k: cfg.index_topk,
7220 window: cfg.window,
7221 },
7222 ))
7223 }
7224 };
7225 let ew_c = comp.as_ref().map_or(
7226 0,
7227 |(_, cg)| {
7228 if cg.overlap { cg.width / 2 } else { cg.width }
7229 },
7230 );
7231 let ew_i = ix.as_ref().map_or(
7232 0,
7233 |(_, cg, _, _)| {
7234 if cg.overlap { cg.width / 2 } else { cg.width }
7235 },
7236 );
7237 let prep = crate::gpu_wgpu::Dsv4Prep {
7238 wkv,
7239 kv_norm: &l.kv_norm,
7240 comp,
7241 ix,
7242 filled: txn.dev_filled[li],
7243 window: cfg.window,
7244 n_comp: txn.dev_n_comp[li],
7245 n_ix: txn.dev_n_ix[li],
7246 comp_dst_off: cfg.window * hd + txn.dev_n_comp[li] * ew_c,
7247 ix_dst_off: txn.dev_n_ix[li] * ew_i,
7248 idx_cap: cfg.window
7249 + if l.indexer.is_some() {
7250 cfg.index_topk
7251 } else {
7252 0
7253 },
7254 };
7255 let fr = if l.compressor.is_some() {
7256 g.inv_freq_compress.as_slice()
7257 } else {
7258 g.inv_freq_window.as_slice()
7259 };
7260 freqs_own.push(if fr.is_empty() { inv_freq } else { fr });
7261 plan.push((li, prep));
7262 }
7263 if !crate::gpu_wgpu::dsv4_spec_replay(
7264 &model,
7265 &plan,
7266 st.kv_id,
7267 txn.pos0,
7268 b,
7269 k,
7270 &freqs_own,
7271 hd,
7272 dim,
7273 cfg.rope_head_dim,
7274 cfg.norm_eps,
7275 true,
7276 ) {
7277 sfail!("replay k={k}");
7278 }
7279 let advanced = |ratio: usize| -> usize {
7281 if ratio == 0 {
7282 return 0;
7283 }
7284 (0..k).filter(|t| (txn.pos0 + t + 1) % ratio == 0).count()
7285 };
7286 for li in 0..txn.gpu_end {
7287 let l = &layers[li];
7288 st.dev_filled[li] = (txn.dev_filled[li] + k).min(cfg.window);
7289 let ac = l.compressor.as_ref().map_or(0, |cp| advanced(cp.ratio));
7290 let ai = l
7291 .indexer
7292 .as_ref()
7293 .map_or(0, |ix| advanced(ix.compressor.ratio));
7294 st.dev_n_comp[li] = txn.dev_n_comp[li] + ac;
7295 st.dev_n_ix[li] = txn.dev_n_ix[li] + ai;
7296 note_compressed(st.kv_id, li, st.dev_n_comp[li]);
7297 }
7298 if k >= 1 && txn.host_steps.iter().all(|(_, v)| v.len() >= k) && !txn.host_steps.is_empty() {
7301 for (li, v) in &txn.host_steps {
7302 host_restore(st, *li, &v[k - 1]);
7303 }
7304 } else {
7305 for (li, snap) in &txn.host {
7306 host_restore(st, *li, snap);
7307 }
7308 let mut scratch = HcScratch::new(cfg);
7309 let mut states = txn.states.clone();
7310 host_tail_walk_batch(
7311 g,
7312 layers,
7313 cfg,
7314 st,
7315 txn.gpu_end,
7316 &mut states[..k * hc * dim],
7317 ids,
7318 txn.pos0,
7319 k,
7320 inv_freq,
7321 &mut scratch,
7322 pool,
7323 None,
7324 );
7325 }
7326 st.pos = txn.pos0 + k;
7327 true
7328}
7329
7330pub fn forward_chunk(
7331 g: &Dsv4Globals,
7332 layers: &[Dsv4Layer],
7333 cfg: &Dsv4Cfg,
7334 st: &mut Dsv4State,
7335 ids: &[u32],
7336 pos0: usize,
7337 inv_freq: &[f32],
7338 pool: Option<&crate::pool::Pool>,
7339 logits: &mut Vec<f32>,
7340 want_logits: bool,
7341) {
7342 let bs = batch_prefill();
7343 if bs > 1 {
7344 let mut i = 0;
7351 if !st.dev_owned && !ids.is_empty() {
7352 st.pos = pos0;
7353 forward_token_inner(
7354 g,
7355 layers,
7356 cfg,
7357 st,
7358 ids[0],
7359 inv_freq,
7360 pool,
7361 logits,
7362 ids.len() == 1,
7363 );
7364 i = 1;
7365 }
7366 while i < ids.len() {
7367 let end = (i + bs).min(ids.len());
7368 st.pos = pos0 + i;
7369 if !forward_chunk_batched(
7370 g,
7371 layers,
7372 cfg,
7373 st,
7374 &ids[i..end],
7375 pos0 + i,
7376 inv_freq,
7377 pool,
7378 logits,
7379 want_logits && end == ids.len(),
7380 ) {
7381 break;
7382 }
7383 i = end;
7384 }
7385 if i == ids.len() {
7386 return;
7387 }
7388 for (k, &id) in ids.iter().enumerate().skip(i) {
7390 st.pos = pos0 + k;
7391 let last = want_logits && k + 1 == ids.len();
7392 forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
7393 }
7394 return;
7395 }
7396 for (i, &id) in ids.iter().enumerate() {
7397 st.pos = pos0 + i;
7398 let last = want_logits && i + 1 == ids.len();
7399 forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
7400 }
7401}
7402
7403pub fn forward_token(
7404 g: &Dsv4Globals,
7405 layers: &[Dsv4Layer],
7406 cfg: &Dsv4Cfg,
7407 st: &mut Dsv4State,
7408 token_id: u32,
7409 inv_freq: &[f32],
7410 pool: Option<&crate::pool::Pool>,
7411 logits: &mut Vec<f32>,
7412) {
7413 forward_token_inner(g, layers, cfg, st, token_id, inv_freq, pool, logits, true);
7414}
7415
7416#[allow(clippy::too_many_arguments)]
7417fn forward_token_inner(
7418 g: &Dsv4Globals,
7419 layers: &[Dsv4Layer],
7420 cfg: &Dsv4Cfg,
7421 st: &mut Dsv4State,
7422 token_id: u32,
7423 inv_freq: &[f32],
7424 pool: Option<&crate::pool::Pool>,
7425 logits: &mut Vec<f32>,
7426 want_logits: bool,
7428) {
7429 let _t_all = prof::on().then(std::time::Instant::now);
7430 let _all_guard = Charge(_t_all, &prof::ALL_NS);
7431 let (hc, dim) = (cfg.hc_mult, cfg.dim);
7432
7433 let mut emb = vec![0.0f32; dim];
7435 g.embed.row_f32(token_id as usize, &mut emb);
7436 let mut state = vec![0.0f32; hc * dim];
7437 for j in 0..hc {
7438 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
7439 }
7440
7441 let mut scratch = HcScratch::new(cfg);
7442 let mut dump: Vec<String> = Vec::new();
7443 if dump_path().is_some() {
7444 dump.push(format!("\"embed\":{}", vec_json(&emb)));
7445 PICKED.with(|p| p.borrow_mut().clear());
7446 BODY.with(|b| b.borrow_mut().clear());
7447 dump.push(",\"layers\":[".into());
7448 }
7449 if trace_on() {
7450 eprintln!(
7451 "[dsv4] tok={token_id} pos={} embed rms={:.5}",
7452 st.pos,
7453 rms_of(&emb)
7454 );
7455 }
7456 #[cfg(feature = "gpu")]
7458 let layer_frames = gpu_layer_enabled()
7459 && dsv4_layer_loop(
7460 &mut state,
7461 layers,
7462 g,
7463 cfg,
7464 st,
7465 token_id,
7466 inv_freq,
7467 pool,
7468 &mut scratch,
7469 );
7470 #[cfg(not(feature = "gpu"))]
7471 let layer_frames = false;
7472
7473 #[cfg(feature = "gpu")]
7480 let hc_dev = hc_on_device()
7481 && !layer_frames
7482 && gpu_attn_enabled()
7483 && gpu_moe2_enabled()
7484 && dump_path().is_none();
7485 #[cfg(not(feature = "gpu"))]
7486 let hc_dev = false;
7487 #[cfg(feature = "gpu")]
7494 let two_frame_done = hc_dev
7495 && dsv4_two_frame_loop(
7496 &mut state,
7497 layers,
7498 g,
7499 cfg,
7500 st,
7501 token_id,
7502 inv_freq,
7503 pool,
7504 &mut scratch,
7505 );
7506 #[cfg(not(feature = "gpu"))]
7507 let two_frame_done = false;
7508 if !two_frame_done {
7509 for (li, l) in layers.iter().enumerate() {
7510 if layer_frames {
7511 break;
7512 }
7513 hc_block(
7515 &mut state,
7516 &l.hc_attn_fn,
7517 &l.hc_attn_scale,
7518 &l.hc_attn_base,
7519 &l.attn_norm,
7520 cfg,
7521 &mut scratch,
7522 pool,
7523 |folded, out| {
7524 if dump_path().is_some() {
7525 BODY.with(|b| b.borrow_mut().push(vec_json(folded)));
7528 }
7529 let freqs = if l.compressor.is_some() {
7531 &g.inv_freq_compress
7532 } else {
7533 &g.inv_freq_window
7534 };
7535 let freqs = if freqs.is_empty() {
7536 inv_freq
7537 } else {
7538 freqs.as_slice()
7539 };
7540 attention_step(folded, l, cfg, st, li, freqs, pool, None, out);
7541 if dump_path().is_some() {
7542 BODY.with(|b| b.borrow_mut().push(vec_json(out)));
7543 }
7544 },
7545 );
7546 if dump_path().is_some() {
7547 dump.push(format!(
7550 "{}{}",
7551 if li == 0 { "" } else { "," },
7552 vec_json(&state)
7553 ));
7554 }
7555 let _t_hc2 = prof::on().then(std::time::Instant::now);
7557 hc_block(
7558 &mut state,
7559 &l.hc_ffn_fn,
7560 &l.hc_ffn_scale,
7561 &l.hc_ffn_base,
7562 &l.ffn_norm,
7563 cfg,
7564 &mut scratch,
7565 pool,
7566 |folded, out| moe_step(folded, l, cfg, token_id, li, pool, out),
7567 );
7568 if let Some(t) = _t_hc2 {
7569 prof::HC_NS.fetch_add(
7572 t.elapsed().as_nanos() as u64,
7573 std::sync::atomic::Ordering::Relaxed,
7574 );
7575 }
7576 if dump_path().is_some() {
7577 dump.push(format!(",{}", vec_json(&state)));
7578 }
7579 if trace_on() && (st.pos % 64 == 0 || st.pos == 199) {
7580 eprintln!(
7581 "[dsv4] кеши слоя {li}: окно={} сжатых={} индекс={} (ratio={:?})",
7582 st.window[li].len() / cfg.head_dim.max(1),
7583 st.compressed[li].len() / cfg.head_dim.max(1),
7584 st.index_kv[li].len().max(1) / 128,
7585 l.compressor.as_ref().map(|c| (c.ratio, c.overlap)),
7586 );
7587 }
7588 if trace_on() {
7589 let bad = state.iter().filter(|v| !v.is_finite()).count();
7590 eprintln!(
7591 "[dsv4] layer {li:>2}: rms={:.5}{}",
7592 rms_of(&state),
7593 if bad > 0 {
7594 format!(" NON-FINITE x{bad}")
7595 } else {
7596 String::new()
7597 }
7598 );
7599 }
7600 dspark_note(li, &state, cfg);
7601 }
7602 }
7603 st.pos += 1;
7604
7605 let mut h = vec![0.0f32; dim];
7607 hc_head_fold(
7608 &state,
7609 &g.hc_head_fn,
7610 g.hc_head_scale,
7611 &g.hc_head_base,
7612 cfg,
7613 pool,
7614 &mut h,
7615 );
7616 if !want_logits {
7617 logits.clear();
7618 return;
7619 }
7620 let _t_head = prof::on().then(std::time::Instant::now);
7621 rms_weighted(&mut h, &g.norm, cfg.norm_eps);
7622 logits.clear();
7623 logits.resize(g.head.rows(), 0.0);
7624 g.head.matvec(&h, logits, pool);
7625 if let Some(t) = _t_head {
7626 prof::HEAD_NS.fetch_add(
7627 t.elapsed().as_nanos() as u64,
7628 std::sync::atomic::Ordering::Relaxed,
7629 );
7630 }
7631 if dump_path().is_some() {
7632 dump.push("]".into());
7633 let picked = PICKED.with(|p| {
7634 p.borrow()
7635 .iter()
7636 .map(|v| {
7637 format!(
7638 "[{}]",
7639 v.iter()
7640 .map(|e| e.to_string())
7641 .collect::<Vec<_>>()
7642 .join(",")
7643 )
7644 })
7645 .collect::<Vec<_>>()
7646 .join(",")
7647 });
7648 dump.push(format!(",\"experts\":[{picked}]"));
7649 let body = BODY.with(|b| b.borrow().join(","));
7650 dump.push(format!(",\"attn_io\":[{body}]"));
7651 dump_line(&format!(
7652 "{{\"tok\":{token_id},\"pos\":{},{},\"head\":{},\"logits\":{}}}",
7653 st.pos - 1,
7654 dump.join(""),
7655 vec_json(&h),
7656 vec_json(logits)
7657 ));
7658 }
7659 if trace_on() {
7660 let (mut top, mut best) = (0usize, f32::NEG_INFINITY);
7661 for (i, &v) in logits.iter().enumerate() {
7662 if v > best {
7663 best = v;
7664 top = i;
7665 }
7666 }
7667 let lo = logits.iter().cloned().fold(f32::MAX, f32::min);
7668 eprintln!(
7669 "[dsv4] head: rms={:.5} logits[{}..{:.3}] argmax={top}",
7670 rms_of(&h),
7671 format_args!("{lo:.3}"),
7672 best
7673 );
7674 }
7675}
7676
7677pub fn load(
7684 model: &std::sync::Arc<cortiq_core::CmfModel>,
7685 cfg: &Dsv4Cfg,
7686 n_layers: usize,
7687) -> Result<(Dsv4Globals, Vec<Dsv4Layer>), String> {
7688 let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
7689 crate::qtensor::QTensor::from_model(model, name)
7690 };
7691 let f = |name: &str| -> Result<Vec<f32>, String> {
7695 crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
7696 };
7697
7698 let rope_of = |base: f32, yarn: bool| -> Vec<f32> {
7703 if yarn {
7704 crate::attention::yarn_inv_freq(cfg.rope_head_dim, base, 16.0, 65536, 32.0, 1.0)
7705 } else {
7706 crate::attention::rope_inv_freq(cfg.rope_head_dim, base)
7707 }
7708 };
7709 let globals = Dsv4Globals {
7710 inv_freq_compress: rope_of(160_000.0, true),
7711 inv_freq_window: rope_of(10_000.0, false),
7712 embed: q("model.embed_tokens.weight")?,
7713 norm: f("model.norm.weight")?,
7714 head: q("lm_head.weight")?,
7715 hc_head_fn: f("model.hc_head_fn")?,
7716 hc_head_base: f("model.hc_head_base")?,
7717 hc_head_scale: *f("model.hc_head_scale")?
7718 .first()
7719 .ok_or("dsv4: empty hc_head_scale")?,
7720 };
7721
7722 let mut layers = Vec::with_capacity(n_layers);
7723 for li in 0..n_layers {
7724 layers.push(load_layer(
7725 model,
7726 cfg,
7727 &format!("model.layers.{li}"),
7728 Scheme::Main,
7729 )?);
7730 }
7731 #[cfg(feature = "gpu")]
7737 if crate::gpu_wgpu::host_banks_on() {
7738 let sets: Vec<(usize, Vec<(usize, usize, usize)>, bool)> = layers
7740 .iter()
7741 .filter_map(|l| {
7742 let idx3 = |e: &Dsv4Expert| {
7743 Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
7744 };
7745 let mut v: Vec<_> = l.experts.iter().filter_map(idx3).collect();
7746 let first = v.first()?.0;
7747 v.push(idx3(&l.shared)?);
7748 let gu_q2 = l.experts.first()?.w1.model_dtype()
7749 == Some(cortiq_core::TensorDtype::Q2TiledP);
7750 Some((first, v, gu_q2))
7751 })
7752 .collect();
7753 let m2 = model.clone();
7754 let (inter, dim) = (cfg.moe_inter, cfg.dim);
7755 std::thread::spawn(move || {
7756 for (first, v, gu_q2) in sets {
7757 crate::gpu_wgpu::dsv4_host_bank_build(&m2, first, &v, inter, dim, gu_q2);
7758 }
7759 tracing::info!("host banks: built");
7760 });
7761 }
7762 #[cfg(feature = "gpu")]
7763 {
7764 let masks: Vec<Option<Vec<bool>>> = layers.iter().map(|l| l.mask.clone()).collect();
7765 crate::gpu_wgpu::prefetch_tier(model, &|name: &str| {
7766 let Some(i) = name.find(".experts.") else {
7767 return false;
7768 };
7769 let rest = &name[i + 9..];
7770 let e: usize = match rest[..rest.find('.').unwrap_or(rest.len())].parse() {
7771 Ok(v) => v,
7772 Err(_) => return false,
7773 };
7774 let li: usize = {
7775 let Some(j) = name.find("layers.") else { return false };
7776 let r = &name[j + 7..];
7777 match r[..r.find('.').unwrap_or(r.len())].parse() {
7778 Ok(v) => v,
7779 Err(_) => return false,
7780 }
7781 };
7782 match masks.get(li).and_then(|m| m.as_ref()) {
7783 Some(m) => m.get(e).copied().unwrap_or(false),
7784 None => true,
7785 }
7786 });
7787 }
7788 Ok((globals, layers))
7789}
7790
7791#[derive(Clone, Copy, PartialEq, Eq, Debug)]
7798pub enum Scheme {
7799 Main,
7800 Mtp,
7801}
7802
7803impl Scheme {
7804 fn attn(self) -> &'static str {
7805 match self {
7806 Scheme::Main => "self_attn",
7807 Scheme::Mtp => "attn",
7808 }
7809 }
7810 fn attn_norm(self) -> &'static str {
7811 match self {
7812 Scheme::Main => "input_layernorm.weight",
7813 Scheme::Mtp => "attn_norm.weight",
7814 }
7815 }
7816 fn ffn_norm(self) -> &'static str {
7817 match self {
7818 Scheme::Main => "post_attention_layernorm.weight",
7819 Scheme::Mtp => "ffn_norm.weight",
7820 }
7821 }
7822 fn mlp(self) -> &'static str {
7823 match self {
7824 Scheme::Main => "mlp",
7825 Scheme::Mtp => "ffn",
7826 }
7827 }
7828 fn gate_bias(self) -> &'static str {
7831 match self {
7832 Scheme::Main => "expert_bias",
7833 Scheme::Mtp => "gate.bias",
7834 }
7835 }
7836 fn shared(self) -> &'static str {
7837 match self {
7838 Scheme::Main => "shared_expert",
7839 Scheme::Mtp => "shared_experts",
7840 }
7841 }
7842 fn w(self, i: u8) -> &'static str {
7844 match (self, i) {
7845 (Scheme::Main, 1) => "gate_proj.weight",
7846 (Scheme::Main, 2) => "down_proj.weight",
7847 (Scheme::Main, _) => "up_proj.weight",
7848 (Scheme::Mtp, 1) => "w1.weight",
7849 (Scheme::Mtp, 2) => "w2.weight",
7850 (Scheme::Mtp, _) => "w3.weight",
7851 }
7852 }
7853}
7854
7855pub fn load_layer(
7857 model: &std::sync::Arc<cortiq_core::CmfModel>,
7858 cfg: &Dsv4Cfg,
7859 p: &str,
7860 s: Scheme,
7861) -> Result<Dsv4Layer, String> {
7862 let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
7863 crate::qtensor::QTensor::from_model(model, name)
7864 };
7865 let f = |name: &str| -> Result<Vec<f32>, String> {
7866 crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
7867 };
7868 let opt_f = |name: &str| -> Option<Vec<f32>> { f(name).ok() };
7869 let at = s.attn();
7870 let ml = s.mlp();
7871 {
7872 let scale3 = |name: &str| -> Result<[f32; 3], String> {
7873 let v = f(name)?;
7874 if v.len() < 3 {
7875 return Err(format!("{name}: expected 3 scales, got {}", v.len()));
7876 }
7877 Ok([v[0], v[1], v[2]])
7878 };
7879 let compressor = match q(&format!("{p}.{at}.compressor.wkv.weight")) {
7882 Ok(wkv) => {
7883 let ape = f(&format!("{p}.{at}.compressor.ape"))?;
7884 let width = wkv.rows();
7887 let ratio = (ape.len() / width.max(1)).max(1);
7888 Some(Dsv4Compressor {
7889 wkv,
7890 wgate: q(&format!("{p}.{at}.compressor.wgate.weight"))?,
7891 norm: f(&format!("{p}.{at}.compressor.norm.weight"))?,
7892 ape,
7893 ratio,
7894 overlap: ratio == 4,
7895 })
7896 }
7897 Err(_) => None,
7898 };
7899 let indexer = match q(&format!("{p}.{at}.indexer.wq_b.weight")) {
7900 Ok(wq_b) => {
7901 let ape = f(&format!("{p}.{at}.indexer.compressor.ape"))?;
7902 let cwkv = q(&format!("{p}.{at}.indexer.compressor.wkv.weight"))?;
7903 let width = cwkv.rows();
7904 let ratio = (ape.len() / width.max(1)).max(1);
7905 Some(Dsv4Indexer {
7906 wq_b,
7907 weights_proj: q(&format!("{p}.{at}.indexer.weights_proj.weight"))?,
7908 compressor: Dsv4Compressor {
7909 wkv: cwkv,
7910 wgate: q(&format!("{p}.{at}.indexer.compressor.wgate.weight"))?,
7911 norm: f(&format!("{p}.{at}.indexer.compressor.norm.weight"))?,
7912 ape,
7913 ratio,
7914 overlap: ratio == 4,
7915 },
7916 })
7917 }
7918 Err(_) => None,
7919 };
7920
7921 let mut experts = Vec::with_capacity(cfg.n_routed_experts);
7922 for e in 0..cfg.n_routed_experts {
7923 let ep = format!("{p}.{ml}.experts.{e}");
7924 experts.push(Dsv4Expert {
7925 w1: q(&format!("{ep}.{w}", w = s.w(1)))?,
7926 w2: q(&format!("{ep}.{w}", w = s.w(2)))?,
7927 w3: q(&format!("{ep}.{w}", w = s.w(3)))?,
7928 });
7929 }
7930
7931 Ok(Dsv4Layer {
7932 attn_norm: f(&format!("{p}.{an}", an = s.attn_norm()))?,
7933 ffn_norm: f(&format!("{p}.{fnm}", fnm = s.ffn_norm()))?,
7934 wq_a: q(&format!("{p}.{at}.wq_a.weight"))?,
7935 q_norm: f(&format!("{p}.{at}.q_norm.weight"))?,
7936 wq_b: q(&format!("{p}.{at}.wq_b.weight"))?,
7937 wkv: q(&format!("{p}.{at}.wkv.weight"))?,
7938 kv_norm: f(&format!("{p}.{at}.kv_norm.weight"))?,
7939 wo_a: q(&format!("{p}.{at}.wo_a.weight"))?,
7940 wo_b: q(&format!("{p}.{at}.wo_b.weight"))?,
7941 attn_sink: f(&format!("{p}.{at}.attn_sink"))?,
7942 compressor,
7943 indexer,
7944 hc_attn_fn: f(&format!("{p}.hc_attn_fn"))?,
7945 hc_attn_base: f(&format!("{p}.hc_attn_base"))?,
7946 hc_attn_scale: scale3(&format!("{p}.hc_attn_scale"))?,
7947 hc_ffn_fn: f(&format!("{p}.hc_ffn_fn"))?,
7948 hc_ffn_base: f(&format!("{p}.hc_ffn_base"))?,
7949 hc_ffn_scale: scale3(&format!("{p}.hc_ffn_scale"))?,
7950 gate: q(&format!("{p}.{ml}.gate.weight"))?,
7951 gate_bias: opt_f(&format!("{p}.{ml}.{b}", b = s.gate_bias())),
7954 tid2eid: opt_f(&format!("{p}.{ml}.tid2eid")),
7955 experts,
7956 mask: if model.tensor(&format!("{p}.{ml}.tid2eid")).is_some() {
7957 None
7958 } else {
7959 crate::loader::moe_task_mask(model, &format!("{p}."), cfg.n_routed_experts)
7960 },
7961 shared: Dsv4Expert {
7962 w1: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(1)))?,
7963 w2: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(2)))?,
7964 w3: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(3)))?,
7965 },
7966 })
7967 }
7968}
7969
7970pub struct Dsv4Mtp {
7979 pub layer: Dsv4Layer,
7980 pub main_proj: Option<crate::qtensor::QTensor>,
7984 pub main_norm: Option<Vec<f32>>,
7985 pub norm: Option<Vec<f32>>,
7987 pub hc_head_fn: Option<Vec<f32>>,
7988 pub hc_head_base: Option<Vec<f32>>,
7989 pub hc_head_scale: Option<f32>,
7990 pub confidence: Option<crate::qtensor::QTensor>,
7991 pub markov_w1: Option<crate::qtensor::QTensor>,
7996 pub markov_w2: Option<crate::qtensor::QTensor>,
7997}
7998
7999pub fn load_mtp(
8003 model: &std::sync::Arc<cortiq_core::CmfModel>,
8004 cfg: &Dsv4Cfg,
8005 max_depth: usize,
8006) -> Vec<Dsv4Mtp> {
8007 let f = |name: &str| -> Option<Vec<f32>> {
8008 crate::loader::load_f32(model, name, &crate::loader::Overlay::None).ok()
8009 };
8010 let mut out = Vec::new();
8011 for d in 0..max_depth {
8012 let p = format!("model.mtp.{d}");
8013 if model.tensor(&format!("{p}.attn.wq_a.weight")).is_none() {
8017 break;
8018 }
8019 let layer = match load_layer(model, cfg, &p, Scheme::Mtp) {
8020 Ok(l) => l,
8021 Err(e) => {
8022 eprintln!("MTP {d}: пропущен, {e}");
8023 break;
8024 }
8025 };
8026 out.push(Dsv4Mtp {
8027 layer,
8028 main_proj: crate::qtensor::QTensor::from_model(model, &format!("{p}.main_proj.weight"))
8029 .ok(),
8030 main_norm: f(&format!("{p}.main_norm.weight")),
8031 norm: f(&format!("{p}.norm.weight")),
8032 hc_head_fn: f(&format!("{p}.hc_head_fn")),
8033 hc_head_base: f(&format!("{p}.hc_head_base")),
8034 hc_head_scale: f(&format!("{p}.hc_head_scale")).and_then(|v| v.first().copied()),
8035 confidence: crate::qtensor::QTensor::from_model(
8036 model,
8037 &format!("{p}.confidence_head.proj.weight"),
8038 )
8039 .ok(),
8040 markov_w1: crate::qtensor::QTensor::from_model(
8041 model,
8042 &format!("{p}.markov_head.markov_w1.weight"),
8043 )
8044 .ok(),
8045 markov_w2: crate::qtensor::QTensor::from_model(
8046 model,
8047 &format!("{p}.markov_head.markov_w2.weight"),
8048 )
8049 .ok(),
8050 });
8051 }
8052 dspark_apply_mask(&mut out);
8053 if !out.is_empty() {
8054 let mp = out
8055 .iter()
8056 .find_map(|m| m.main_proj.as_ref())
8057 .map(|t| format!("[{}, {}]", t.rows(), t.cols()))
8058 .unwrap_or_else(|| "нет".into());
8059 eprintln!(
8060 "MTP: {} стади(я/и/й), main_proj {mp}, экспертов {}, \
8061 голова уверенности {}, марков {}",
8062 out.len(),
8063 out[0].layer.experts.len(),
8064 if out.iter().any(|m| m.confidence.is_some()) {
8065 "есть"
8066 } else {
8067 "нет"
8068 },
8069 if out.iter().any(|m| m.markov_w1.is_some()) {
8070 "есть"
8071 } else {
8072 "нет"
8073 },
8074 );
8075 }
8076 out
8077}
8078
8079#[cfg(test)]
8080mod tests {
8081 use super::*;
8082
8083 fn toy() -> (Dsv4Globals, Vec<Dsv4Layer>, Dsv4Cfg) {
8088 use crate::qtensor::QTensor;
8089 let cfg = Dsv4Cfg {
8090 dim: 32,
8091 n_heads: 4,
8092 head_dim: 8,
8093 rope_head_dim: 4,
8094 q_lora_rank: 16,
8095 o_lora_rank: 16,
8096 o_groups: 2,
8097 hc_mult: 4,
8098 hc_sinkhorn_iters: 20,
8099 hc_eps: 1e-6,
8100 norm_eps: 1e-6,
8101 n_routed_experts: 8,
8102 top_k: 2,
8103 moe_inter: 16,
8104 route_scale: 1.0,
8105 swiglu_limit: 10.0,
8106 window: 6,
8107 index_topk: 8,
8108 vocab: 24,
8109 };
8110 let w = |n: usize, seed: usize| -> Vec<f32> {
8113 (0..n)
8114 .map(|i| (((i * 7 + seed * 13) % 101) as f32 / 101.0 - 0.5) * 0.3)
8115 .collect()
8116 };
8117 let t = |rows: usize, cols: usize, seed: usize| {
8118 QTensor::from_f32(w(rows * cols, seed), rows, cols)
8119 };
8120 let ones = |n: usize| vec![1.0f32; n];
8121
8122 let (dim, hc) = (cfg.dim, cfg.hc_mult);
8123 let q_width = cfg.n_heads * cfg.head_dim;
8126 let kv_width = cfg.head_dim;
8127 let o_per_group = q_width / cfg.o_groups;
8128 let mut layers = Vec::new();
8129 for li in 0..2 {
8130 let experts: Vec<Dsv4Expert> = (0..cfg.n_routed_experts)
8131 .map(|e| Dsv4Expert {
8132 w1: t(cfg.moe_inter, dim, 40 + e + li * 8),
8133 w2: t(dim, cfg.moe_inter, 60 + e + li * 8),
8134 w3: t(cfg.moe_inter, dim, 80 + e + li * 8),
8135 })
8136 .collect();
8137 layers.push(Dsv4Layer {
8140 attn_norm: ones(dim),
8141 ffn_norm: ones(dim),
8142 wq_a: t(cfg.q_lora_rank, dim, 1 + li),
8143 q_norm: ones(cfg.q_lora_rank),
8144 wq_b: t(q_width, cfg.q_lora_rank, 3 + li),
8145 wkv: t(kv_width, dim, 5 + li),
8146 kv_norm: ones(kv_width),
8147 wo_a: t(cfg.o_groups * cfg.o_lora_rank, o_per_group, 7 + li),
8148 wo_b: t(dim, cfg.o_groups * cfg.o_lora_rank, 9 + li),
8149 attn_sink: vec![0.1; cfg.n_heads],
8150 compressor: if li == 1 {
8153 Some(Dsv4Compressor {
8154 wkv: t(2 * kv_width, dim, 11),
8155 wgate: t(2 * kv_width, dim, 13),
8156 norm: ones(kv_width),
8157 ape: vec![0.01; 4 * 2 * kv_width],
8158 ratio: 4,
8159 overlap: true,
8160 })
8161 } else {
8162 None
8163 },
8164 indexer: if li == 1 {
8165 Some(Dsv4Indexer {
8166 wq_b: t(2 * 16, cfg.q_lora_rank, 41),
8167 weights_proj: t(2, dim, 43),
8168 compressor: Dsv4Compressor {
8169 wkv: t(2 * 16, dim, 45),
8170 wgate: t(2 * 16, dim, 47),
8171 norm: ones(16),
8172 ape: vec![0.01; 4 * 2 * 16],
8173 ratio: 4,
8174 overlap: true,
8175 },
8176 })
8177 } else {
8178 None
8179 },
8180 hc_attn_fn: w((2 + hc) * hc * hc * dim, 15 + li),
8181 hc_attn_base: w((2 + hc) * hc, 17 + li),
8182 hc_attn_scale: [1.0, 1.0, 1.0],
8183 hc_ffn_fn: w((2 + hc) * hc * hc * dim, 19 + li),
8184 hc_ffn_base: w((2 + hc) * hc, 21 + li),
8185 hc_ffn_scale: [1.0, 1.0, 1.0],
8186 gate: t(cfg.n_routed_experts, dim, 23 + li),
8187 gate_bias: if li == 1 {
8188 Some(vec![0.0; cfg.n_routed_experts])
8189 } else {
8190 None
8191 },
8192 tid2eid: if li == 0 {
8193 Some(
8194 (0..cfg.vocab * cfg.top_k)
8195 .map(|i| (i % cfg.n_routed_experts) as f32)
8196 .collect(),
8197 )
8198 } else {
8199 None
8200 },
8201 experts,
8202 mask: None,
8203 shared: Dsv4Expert {
8204 w1: t(cfg.moe_inter, dim, 25 + li),
8205 w2: t(dim, cfg.moe_inter, 27 + li),
8206 w3: t(cfg.moe_inter, dim, 29 + li),
8207 },
8208 });
8209 }
8210 let inv = |base: f32| -> Vec<f32> {
8211 (0..cfg.rope_head_dim / 2)
8212 .map(|i| 1.0 / base.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
8213 .collect()
8214 };
8215 let g = Dsv4Globals {
8216 inv_freq_compress: inv(160000.0),
8217 inv_freq_window: inv(10000.0),
8218 embed: t(cfg.vocab, dim, 31),
8219 norm: ones(dim),
8220 head: t(cfg.vocab, dim, 33),
8221 hc_head_fn: w(hc * hc * dim, 35),
8222 hc_head_base: w(hc, 37),
8223 hc_head_scale: 1.0,
8224 };
8225 (g, layers, cfg)
8226 }
8227
8228 #[test]
8233 fn forward_token_decodes_a_sequence_without_falling_over() {
8234 let (g, layers, cfg) = toy();
8235 let mut st = Dsv4State::new(layers.len());
8236 let inv_freq: Vec<f32> = (0..cfg.rope_head_dim / 2)
8237 .map(|i| 1.0 / 10000f32.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
8238 .collect();
8239 let mut logits = Vec::new();
8240
8241 let mut first: Option<Vec<f32>> = None;
8244 for (step, tok) in [3u32, 7, 1, 9, 4, 2, 8, 5, 6, 0].into_iter().enumerate() {
8245 forward_token(
8246 &g,
8247 &layers,
8248 &cfg,
8249 &mut st,
8250 tok,
8251 &inv_freq,
8252 None,
8253 &mut logits,
8254 );
8255 assert_eq!(logits.len(), cfg.vocab, "step {step}: logit count");
8256 assert!(
8257 logits.iter().all(|v| v.is_finite()),
8258 "step {step}: non-finite logit — {logits:?}"
8259 );
8260 let spread = logits.iter().cloned().fold(f32::MIN, f32::max)
8263 - logits.iter().cloned().fold(f32::MAX, f32::min);
8264 assert!(spread > 1e-6, "step {step}: logits are flat ({spread})");
8265 if step == 0 {
8266 first = Some(logits.clone());
8267 }
8268 assert_eq!(st.pos, step + 1, "position bookkeeping");
8269 }
8270
8271 assert!(!st.window[0].is_empty(), "sliding window never filled");
8274 for (li, w) in st.window.iter().enumerate() {
8276 assert!(
8277 w.len() / cfg.head_dim <= cfg.window,
8278 "layer {li}: window holds {} positions, cap is {}",
8279 w.len() / cfg.head_dim,
8280 cfg.window
8281 );
8282 }
8283 assert!(
8284 !st.compressed[1].is_empty(),
8285 "compressor layer produced no compressed KV in 10 tokens"
8286 );
8287 assert_eq!(
8291 st.compressed[1].len() / cfg.head_dim,
8292 2,
8293 "expected two folds in ten tokens at ratio 4"
8294 );
8295 assert!(
8296 !st.prev_kv[1].is_empty(),
8297 "the overlapping compressor never kept a previous window"
8298 );
8299 for (li, l) in layers.iter().enumerate() {
8304 if l.indexer.is_some() {
8305 assert!(
8306 !st.index_kv[li].is_empty(),
8307 "layer {li} has an indexer but its cache stayed empty"
8308 );
8309 }
8310 }
8311
8312 let mut fresh = Dsv4State::new(layers.len());
8315 let mut relogits = Vec::new();
8316 forward_token(
8317 &g,
8318 &layers,
8319 &cfg,
8320 &mut fresh,
8321 3,
8322 &inv_freq,
8323 None,
8324 &mut relogits,
8325 );
8326 assert_eq!(
8327 relogits,
8328 first.unwrap(),
8329 "the same token from a fresh state must reproduce exactly"
8330 );
8331 }
8332
8333 #[test]
8337 fn swiglu_limit_clamps_up_both_ways_and_gate_only_from_above() {
8338 let inter = 4;
8339 let gate_src = [-50.0f32, 50.0, 1.0, -1.0];
8341 let up_src = [50.0f32, -50.0, 1.0, -1.0];
8342 let limit = 10.0f32;
8343 let mut got = vec![0.0f32; inter];
8344 expert_swiglu(
8345 &[0.0],
8346 &|_, d| d.copy_from_slice(&gate_src),
8347 &|_, d| d.copy_from_slice(&up_src),
8348 &|src, d| d.copy_from_slice(src),
8349 inter,
8350 1.0,
8351 limit,
8352 &mut got,
8353 );
8354 let silu = |g: f32| g / (1.0 + (-g).exp());
8355 let want = [
8357 silu(-50.0) * limit,
8358 silu(limit) * -limit,
8359 silu(1.0) * 1.0,
8360 -silu(-1.0),
8361 ];
8362 for (i, w) in want.iter().enumerate() {
8363 assert!(
8364 (got[i] - w).abs() < 1e-5,
8365 "lane {i}: got {} want {w}",
8366 got[i]
8367 );
8368 }
8369 let mut raw = vec![0.0f32; inter];
8371 expert_swiglu(
8372 &[0.0],
8373 &|_, d| d.copy_from_slice(&gate_src),
8374 &|_, d| d.copy_from_slice(&up_src),
8375 &|src, d| d.copy_from_slice(src),
8376 inter,
8377 1.0,
8378 0.0,
8379 &mut raw,
8380 );
8381 assert!(
8382 (raw[1] - silu(50.0) * -50.0).abs() < 1e-3,
8383 "limit 0 must not clamp"
8384 );
8385 }
8386
8387 #[test]
8392 fn grouped_projection_is_identical_with_and_without_a_pool() {
8393 let (groups, lora, per_group, dim) = (4usize, 128usize, 64usize, 32usize);
8394 let attn: Vec<f32> = (0..groups * per_group)
8395 .map(|i| ((i * 13) as f32 * 0.021).sin())
8396 .collect();
8397 let wo_a: Vec<f32> = (0..groups * lora * per_group)
8398 .map(|i| ((i * 7) as f32 * 0.011).cos())
8399 .collect();
8400 let wo_b: Vec<f32> = (0..dim * groups * lora)
8401 .map(|i| ((i * 5) as f32 * 0.009).sin())
8402 .collect();
8403 let row = |r: usize, x: &[f32], _sc: &mut [f32]| -> f32 {
8404 wo_a[r * per_group..(r + 1) * per_group]
8405 .iter()
8406 .zip(x)
8407 .map(|(a, b)| a * b)
8408 .sum()
8409 };
8410 let project = |mid: &[f32], dst: &mut [f32]| {
8411 for (d, o) in dst.iter_mut().enumerate() {
8412 *o = wo_b[d * mid.len()..(d + 1) * mid.len()]
8413 .iter()
8414 .zip(mid)
8415 .map(|(a, b)| a * b)
8416 .sum();
8417 }
8418 };
8419
8420 let mut serial = vec![0.0f32; dim];
8421 o_project(
8422 &attn,
8423 &row,
8424 per_group,
8425 &project,
8426 groups,
8427 lora,
8428 None,
8429 &mut serial,
8430 );
8431
8432 let pool = crate::pool::Pool::new(4);
8433 let mut pooled = vec![0.0f32; dim];
8434 o_project(
8435 &attn,
8436 &row,
8437 per_group,
8438 &project,
8439 groups,
8440 lora,
8441 Some(&pool),
8442 &mut pooled,
8443 );
8444 assert_eq!(serial, pooled, "the pooled projection diverged");
8445 assert!(
8446 serial.iter().any(|v| v.abs() > 1e-6),
8447 "test data is degenerate"
8448 );
8449 }
8450
8451 #[test]
8452 fn block_grouped_projection_matches_position_walk() {
8453 let (_g, layers, cfg) = toy();
8454 let l = &layers[1];
8455 let b = 5;
8456 let attn_len = cfg.n_heads * cfg.head_dim;
8457 let attn: Vec<f32> = (0..b * attn_len)
8458 .map(|i| ((i * 17) as f32 * 0.013).sin())
8459 .collect();
8460 let mut walked = vec![0.0f32; b * cfg.dim];
8461 for bi in 0..b {
8462 o_project(
8463 &attn[bi * attn_len..(bi + 1) * attn_len],
8464 &|r, x, sc| l.wo_a.row_dot(r, x, sc),
8465 l.wo_a.cols(),
8466 &|mid, dst| l.wo_b.matvec(mid, dst, None),
8467 cfg.o_groups,
8468 cfg.o_lora_rank,
8469 None,
8470 &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
8471 );
8472 }
8473 let mut batched = vec![0.0f32; b * cfg.dim];
8474 o_project_block(
8475 &attn,
8476 b,
8477 &l.wo_a,
8478 &l.wo_b,
8479 cfg.o_groups,
8480 cfg.o_lora_rank,
8481 None,
8482 &mut batched,
8483 );
8484 assert_eq!(batched, walked);
8485 }
8486
8487 #[test]
8488 fn block_moe_matches_position_walk_in_route_order() {
8489 let (_g, layers, cfg) = toy();
8490 let l = &layers[1];
8493 let b = 5;
8494 let xs: Vec<f32> = (0..b * cfg.dim)
8495 .map(|i| ((i * 11) as f32 * 0.019).cos())
8496 .collect();
8497 let ids = [1u32, 2, 3, 4, 5];
8498 let mut walked = vec![0.0f32; b * cfg.dim];
8499 for bi in 0..b {
8500 moe_step(
8501 &xs[bi * cfg.dim..(bi + 1) * cfg.dim],
8502 l,
8503 &cfg,
8504 ids[bi],
8505 1,
8506 None,
8507 &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
8508 );
8509 }
8510 let mut batched = vec![0.0f32; b * cfg.dim];
8511 moe_step_block(&xs, b, l, &cfg, &ids, 1, None, &mut batched);
8512 assert_eq!(batched, walked);
8513 }
8514
8515 #[test]
8521 fn overlapping_compressor_folds_both_windows() {
8522 let (ratio, d) = (2usize, 3usize);
8523 let cur_kv: Vec<f32> = vec![
8526 1.0, 1.0, 1.0, 10.0, 20.0, 30.0, 2.0, 2.0, 2.0, 40.0, 50.0, 60.0, ];
8529 let cur_sc: Vec<f32> = vec![
8531 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 0.0, 100.0, 100.0, 0.0,
8533 ];
8534 let prev_kv: Vec<f32> = vec![
8536 7.0, 8.0, 9.0, 0.0, 0.0, 0.0, 5.0, 6.0, 7.0, 0.0, 0.0, 0.0,
8538 ];
8539 let prev_sc = vec![0.0f32; ratio * 2 * d];
8540
8541 let mut out = vec![0.0f32; d];
8542 compress_window_overlap(&prev_kv, &prev_sc, &cur_kv, &cur_sc, ratio, d, &mut out);
8543 assert!((out[0] - 40.0).abs() < 1e-3, "dim0 = {}", out[0]);
8545 assert!((out[1] - 50.0).abs() < 1e-3, "dim1 = {}", out[1]);
8546 assert!((out[2] - 30.0).abs() < 1e-3, "dim2 = {}", out[2]);
8548
8549 let mut first = vec![0.0f32; d];
8552 compress_window_overlap(&[], &[], &cur_kv, &cur_sc, ratio, d, &mut first);
8553 assert!(
8554 first.iter().all(|v| v.is_finite()),
8555 "first window: {first:?}"
8556 );
8557 assert!((first[0] - 40.0).abs() < 1e-3, "first dim0 = {}", first[0]);
8558
8559 let mut both = vec![0.0f32; d];
8561 let strong_prev = vec![100.0f32; ratio * 2 * d];
8562 compress_window_overlap(
8563 &prev_kv,
8564 &strong_prev,
8565 &cur_kv,
8566 &cur_sc,
8567 ratio,
8568 d,
8569 &mut both,
8570 );
8571 assert!(
8572 (both[0] - 40.0).abs() > 1.0,
8573 "a scored previous window must move the fold, got {}",
8574 both[0]
8575 );
8576 }
8577
8578 #[test]
8584 fn sinkhorn_matches_the_reference_numbers() {
8585 let hc = 4;
8586 let mixes: Vec<f32> = (0..24).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
8587 let base: Vec<f32> = (0..24).map(|i| (i as f32 * 0.11).cos()).collect();
8588 let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
8589 hc_split_sinkhorn(
8590 &mixes,
8591 &[1.0, 1.0, 1.0],
8592 &base,
8593 hc,
8594 20,
8595 1e-6,
8596 &mut pre,
8597 &mut post,
8598 &mut comb,
8599 );
8600 let want_pre = [0.7310596, 0.8888268, 0.9525191, 0.97424865];
8601 let want_post = [1.9600224, 1.9534285, 1.9201256, 1.8160983];
8602 let want_comb = [
8603 0.5996052,
8604 0.282_535_9,
8605 0.09218107,
8606 0.025676856,
8607 0.17564717,
8608 0.22228767,
8609 0.271_745_4,
8610 0.330_318_8,
8611 0.029528176,
8612 0.12206022,
8613 0.32619134,
8614 0.5222193,
8615 0.19521846,
8616 0.37311527,
8617 0.30988118,
8618 0.12178412,
8619 ];
8620 for (i, w) in want_pre.iter().enumerate() {
8621 assert!((pre[i] - w).abs() < 1e-5, "pre[{i}]: {} vs {w}", pre[i]);
8622 }
8623 for (i, w) in want_post.iter().enumerate() {
8624 assert!((post[i] - w).abs() < 1e-5, "post[{i}]: {} vs {w}", post[i]);
8625 }
8626 for (i, w) in want_comb.iter().enumerate() {
8627 assert!((comb[i] - w).abs() < 1e-4, "comb[{i}]: {} vs {w}", comb[i]);
8628 }
8629 }
8630
8631 #[test]
8636 fn sinkhorn_leaves_the_mixing_matrix_doubly_stochastic() {
8637 let hc = 4;
8638 let mix_hc = (2 + hc) * hc;
8639 let mixes: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
8641 let base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.11).cos()).collect();
8642 let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
8643 hc_split_sinkhorn(
8644 &mixes,
8645 &[1.0, 1.0, 1.0],
8646 &base,
8647 hc,
8648 20,
8649 1e-6,
8650 &mut pre,
8651 &mut post,
8652 &mut comb,
8653 );
8654 for j in 0..hc {
8655 let r: f32 = comb[j * hc..(j + 1) * hc].iter().sum();
8656 assert!((r - 1.0).abs() < 2e-3, "row {j} sums to {r}");
8657 let c: f32 = (0..hc).map(|k| comb[k * hc + j]).sum();
8658 assert!((c - 1.0).abs() < 2e-3, "col {j} sums to {c}");
8659 }
8660 assert!(pre.iter().all(|&v| v > 0.0 && v < 1.001));
8662 assert!(post.iter().all(|&v| (0.0..=2.0).contains(&v)));
8663 }
8664
8665 #[test]
8669 fn expand_of_identical_copies_is_a_fixed_point() {
8670 let (hc, dim) = (4usize, 3usize);
8671 let residual: Vec<f32> = std::iter::repeat_n([1.5f32, -2.0, 0.25], hc)
8672 .flatten()
8673 .collect();
8674 let comb = {
8675 vec![0.25f32; hc * hc]
8677 };
8678 let post = vec![0.0f32; hc];
8679 let mut out = vec![0.0f32; hc * dim];
8680 hc_expand(&[0.0; 3], &residual, &post, &comb, hc, dim, &mut out);
8681 for (o, r) in out.iter().zip(&residual) {
8682 assert!((o - r).abs() < 1e-6, "{o} vs {r}");
8683 }
8684 }
8685
8686 #[test]
8690 fn selection_bias_steers_the_choice_but_not_the_weights() {
8691 let scores = [3.0f32, 0.1, 2.0, 0.05];
8692 let bias = [0.0f32, 10.0, 0.0, 0.0];
8693 let (mut idx, mut w) = (Vec::new(), Vec::new());
8694 route(&scores, Some(&bias), 2, 1.5, None, None, &mut idx, &mut w);
8695 assert_eq!(idx[0], 1, "the biased expert must win selection");
8696 assert_eq!(idx[1], 0);
8697 assert!(w[0] < w[1], "biased expert kept its own (small) weight");
8700 let sum: f32 = w.iter().sum();
8701 assert!((sum - 1.5).abs() < 1e-5, "weights renormalize then scale");
8702 }
8703
8704 #[test]
8708 fn attention_sink_drains_weight_without_contributing_output() {
8709 let hd = 2;
8710 let q = [1.0f32, 0.0];
8711 let kv = [1.0f32, 0.0, 0.0, 1.0];
8712 let mut out = vec![0.0f32; hd];
8713 sparse_attend(&q, &kv, &[0, 1], f32::NEG_INFINITY, 1.0, hd, &mut out);
8714 let plain = out.clone();
8715 assert!(plain[0] > plain[1], "the aligned key must dominate");
8716 sparse_attend(&q, &kv, &[0, 1], 20.0, 1.0, hd, &mut out);
8717 assert!(
8718 out[0] < plain[0] * 0.01 && out[1] < plain[1] * 0.01,
8719 "a large sink must drain nearly all the mass: {out:?}"
8720 );
8721 }
8722
8723 #[test]
8726 fn masked_positions_leave_the_denominator_alone() {
8727 let hd = 2;
8728 let q = [1.0f32, 0.0];
8729 let kv = [1.0f32, 0.0, 0.0, 1.0];
8730 let (mut a, mut b) = (vec![0.0f32; hd], vec![0.0f32; hd]);
8731 sparse_attend(&q, &kv, &[0], f32::NEG_INFINITY, 1.0, hd, &mut a);
8732 sparse_attend(
8733 &q,
8734 &kv,
8735 &[0, usize::MAX],
8736 f32::NEG_INFINITY,
8737 1.0,
8738 hd,
8739 &mut b,
8740 );
8741 for (x, y) in a.iter().zip(&b) {
8742 assert!((x - y).abs() < 1e-6, "{x} vs {y}");
8743 }
8744 }
8745
8746 #[test]
8749 fn rope_tail_inverts_itself() {
8750 let inv_freq = [1.0f32, 0.5];
8751 let orig = [9.0f32, 8.0, 1.0, 2.0, 3.0, 4.0];
8752 let mut v = orig;
8753 rope_tail(&mut v, &inv_freq, 7, 4, false);
8754 assert!(v[..2] == orig[..2], "the non-rope head must not move");
8755 assert!(v[2..] != orig[2..], "the tail must actually rotate");
8756 rope_tail(&mut v, &inv_freq, 7, 4, true);
8757 for (a, b) in v.iter().zip(&orig) {
8758 assert!((a - b).abs() < 1e-5, "{a} vs {b}");
8759 }
8760 }
8761
8762 #[test]
8765 fn compressor_pools_the_window_per_dimension() {
8766 let (ratio, width) = (2usize, 2usize);
8767 let kv = [1.0f32, 10.0, 3.0, 20.0];
8768 let score = [0.0f32, 0.0, 0.0, 50.0];
8770 let ape = vec![0.0f32; ratio * width];
8771 let mut out = vec![0.0f32; width];
8772 compress_window(&kv, &score, &ape, ratio, width, &mut out);
8773 assert!(
8774 (out[0] - 2.0).abs() < 1e-5,
8775 "equal scores average: {}",
8776 out[0]
8777 );
8778 assert!(
8779 (out[1] - 20.0).abs() < 1e-3,
8780 "a dominant score wins: {}",
8781 out[1]
8782 );
8783 }
8784
8785 #[test]
8788 fn index_scores_relu_before_weighting() {
8789 let (nh, hd) = (2usize, 2usize);
8790 let q = [1.0f32, 0.0, -1.0, 0.0];
8792 let kv = [1.0f32, 0.0, 0.0, 1.0];
8793 let w = [1.0f32, 1.0];
8794 let mut sc = Vec::new();
8795 index_scores(&q, &kv, &w, nh, hd, 2, 2, None, &mut sc);
8796 assert!(sc[0] > 0.9, "abstention, not veto: {:?}", sc);
8798 }
8799
8800 #[test]
8801 fn index_scores_mask_the_future() {
8802 let (nh, hd) = (1usize, 2usize);
8803 let q = [1.0f32, 0.0];
8804 let kv = [1.0f32, 0.0, 1.0, 0.0, 1.0, 0.0];
8805 let w = [1.0f32];
8806 let mut sc = Vec::new();
8807 index_scores(&q, &kv, &w, nh, hd, 3, 2, None, &mut sc);
8808 assert!(sc[0].is_finite() && sc[1].is_finite());
8809 assert!(sc[2] == f32::NEG_INFINITY, "position 2 is in the future");
8810 let mut idx = Vec::new();
8811 top_k_positions(&sc, 3, &mut idx);
8812 assert_eq!(idx, vec![0, 1], "a masked slot never wins a slot");
8813 }
8814
8815 #[test]
8816 fn top_k_is_deterministic_on_ties() {
8817 let sc = [1.0f32, 1.0, 1.0, 0.0];
8818 let mut idx = Vec::new();
8819 top_k_positions(&sc, 2, &mut idx);
8820 assert_eq!(idx, vec![0, 1], "ties resolve to the lower index");
8821 }
8822
8823 #[test]
8827 fn hc_block_preserves_the_copy_structure_and_applies_the_block() {
8828 let cfg = Dsv4Cfg {
8829 dim: 4,
8830 n_heads: 1,
8831 head_dim: 4,
8832 rope_head_dim: 2,
8833 q_lora_rank: 4,
8834 o_lora_rank: 2,
8835 o_groups: 1,
8836 hc_mult: 4,
8837 hc_sinkhorn_iters: 20,
8838 hc_eps: 1e-6,
8839 norm_eps: 1e-6,
8840 n_routed_experts: 2,
8841 top_k: 1,
8842 moe_inter: 4,
8843 route_scale: 1.0,
8844 swiglu_limit: 10.0,
8845 window: 128,
8846 index_topk: 4,
8847 vocab: 8,
8848 };
8849 let (hc, dim) = (cfg.hc_mult, cfg.dim);
8850 let mix_hc = (2 + hc) * hc;
8851 let hc_fn: Vec<f32> = (0..mix_hc * hc * dim)
8852 .map(|i| ((i % 13) as f32 - 6.0) * 0.05)
8853 .collect();
8854 let hc_base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.2).sin()).collect();
8855 let norm_w = vec![1.0f32; dim];
8856 let mut state: Vec<f32> = (0..hc * dim).map(|i| (i as f32 * 0.3).cos()).collect();
8857 let before = state.clone();
8858 let mut scratch = HcScratch::new(&cfg);
8859 hc_block(
8860 &mut state,
8861 &hc_fn,
8862 &[1.0, 1.0, 1.0],
8863 &hc_base,
8864 &norm_w,
8865 &cfg,
8866 &mut scratch,
8867 None,
8868 |_folded, out: &mut [f32]| out.iter_mut().for_each(|o| *o = 1.0),
8869 );
8870 assert_eq!(state.len(), before.len(), "copy structure must survive");
8871 assert!(state.iter().all(|v| v.is_finite()), "{state:?}");
8872 assert!(
8873 state.iter().zip(&before).any(|(a, b)| (a - b).abs() > 1e-4),
8874 "the block's output has to reach the state"
8875 );
8876 }
8877
8878 #[test]
8879 fn hash_route_reads_the_table_row() {
8880 let table = [7.0f32, 9.0, 1.0, 2.0, 5.0, 6.0];
8882 assert_eq!(hash_route(&table, 3, 2, 0), vec![7, 9]);
8883 assert_eq!(hash_route(&table, 3, 2, 2), vec![5, 6]);
8884 assert_eq!(hash_route(&table, 3, 2, 99), vec![5, 6]);
8886 }
8887
8888 #[test]
8893 fn a_task_mask_restricts_selection_and_renormalizes() {
8894 let scores = [0.1f32, 4.0, 1.0, 9.0];
8896 let (mut idx, mut w) = (Vec::new(), Vec::new());
8897 route(&scores, None, 2, 1.0, None, None, &mut idx, &mut w);
8898 assert_eq!(idx, vec![3, 1], "unmasked: the two best win");
8899 let sum: f32 = w.iter().sum();
8900 assert!((sum - 1.0).abs() < 1e-5, "weights must sum to route_scale");
8901
8902 let mask = [true, false, true, true];
8905 let (mut i2, mut w2) = (Vec::new(), Vec::new());
8906 route(&scores, None, 2, 1.0, None, Some(&mask), &mut i2, &mut w2);
8907 assert_eq!(i2, vec![3, 2], "masked expert must not be selected");
8908 let sum2: f32 = w2.iter().sum();
8909 assert!((sum2 - 1.0).abs() < 1e-5, "masked weights must renormalize");
8910
8911 let tight = [false, false, false, true];
8913 let (mut i3, mut w3) = (Vec::new(), Vec::new());
8914 route(&scores, None, 2, 1.0, None, Some(&tight), &mut i3, &mut w3);
8915 assert_eq!(i3, vec![3]);
8916 assert_eq!(w3.len(), 1);
8917 }
8918
8919 #[test]
8924 fn hash_layers_weight_the_experts_the_table_names() {
8925 let scores = [0.1f32, 0.4, 0.2, 5.0];
8927 let table = vec![0.0f32, 1.0];
8928 let idx_forced = hash_route(&table, 1, 2, 0);
8929 assert_eq!(idx_forced, vec![0, 1]);
8930
8931 let (mut idx, mut w) = (Vec::new(), Vec::new());
8932 route(
8933 &scores,
8934 None,
8935 2,
8936 1.0,
8937 Some(&idx_forced),
8938 None,
8939 &mut idx,
8940 &mut w,
8941 );
8942 assert_eq!(idx, vec![0, 1], "the table must decide the experts");
8943
8944 let sp = |x: f32| (1.0 + x.exp()).ln().sqrt();
8946 let (s0, s1) = (sp(scores[0]), sp(scores[1]));
8947 let tot = s0 + s1;
8948 assert!(
8949 (w[0] - s0 / tot).abs() < 1e-6,
8950 "w[0]={} want {}",
8951 w[0],
8952 s0 / tot
8953 );
8954 assert!(
8955 (w[1] - s1 / tot).abs() < 1e-6,
8956 "w[1]={} want {}",
8957 w[1],
8958 s1 / tot
8959 );
8960
8961 let (mut idx2, mut w2) = (Vec::new(), Vec::new());
8963 route(&scores, None, 2, 1.0, None, None, &mut idx2, &mut w2);
8964 assert_eq!(idx2[0], 3, "without a table the highest score still wins");
8965 }
8966}
8967
8968pub const DSPARK_NOISE_TOKEN: u32 = 128799;
8985pub fn dspark_block() -> usize {
8993 5
8994}
8995
8996#[cfg(feature = "gpu")]
9003fn dspark_native_on() -> bool {
9004 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9005 *ON.get_or_init(|| {
9006 std::env::var("CMF_DSPARK_NATIVE")
9007 .map(|v| v != "0")
9008 .unwrap_or(true)
9011 })
9012}
9013
9014pub fn dspark_verify_k() -> usize {
9015 static K: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
9016 *K.get_or_init(|| {
9017 std::env::var("CMF_DSPARK_VERIFY_K")
9018 .ok()
9019 .and_then(|v| v.parse::<usize>().ok())
9020 .filter(|&n| (1..=DSPARK_BLOCK_MAX).contains(&n))
9021 .unwrap_or(DSPARK_BLOCK_MAX)
9022 })
9023}
9024
9025pub const DSPARK_BLOCK_MAX: usize = 5;
9027
9028pub struct DsparkState {
9031 pub win: Vec<Vec<f32>>,
9033 pub filled: Vec<usize>,
9035 pub main_hidden: Vec<f32>,
9037 pub have_hidden: bool,
9039}
9040
9041impl DsparkState {
9042 pub fn new(stages: usize, cfg: &Dsv4Cfg, targets: usize) -> Self {
9043 Self {
9044 win: vec![Vec::new(); stages],
9045 filled: vec![0; stages],
9046 main_hidden: vec![0.0; cfg.dim * targets],
9047 have_hidden: false,
9048 }
9049 }
9050}
9051
9052pub fn dspark_targets(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, n_layers: usize) -> Vec<usize> {
9058 let Some(mp) = mtp.iter().find_map(|m| m.main_proj.as_ref()) else {
9059 return Vec::new();
9060 };
9061 let n = (mp.cols() / cfg.dim.max(1)).clamp(1, n_layers);
9062 (n_layers - n..n_layers).collect()
9063}
9064
9065thread_local! {
9066 static DSPARK_CAP: std::cell::RefCell<(Vec<usize>, Vec<f32>, usize)> =
9072 const { std::cell::RefCell::new((Vec::new(), Vec::new(), 0)) };
9073}
9074
9075pub fn dspark_arm(targets: &[usize], dim: usize) {
9077 DSPARK_CAP.with(|c| {
9078 let mut c = c.borrow_mut();
9079 c.0 = targets.to_vec();
9080 c.1 = vec![0.0; dim * targets.len()];
9081 c.2 = 0;
9082 });
9083}
9084
9085fn dspark_wants(li: usize) -> bool {
9089 DSPARK_CAP.with(|c| c.borrow().0.contains(&li))
9090}
9091
9092pub fn dspark_note(li: usize, state: &[f32], cfg: &Dsv4Cfg) {
9094 DSPARK_CAP.with(|c| {
9095 let mut c = c.borrow_mut();
9096 if c.0.is_empty() {
9097 return;
9098 }
9099 if let Some(slot) = c.0.iter().position(|&t| t == li) {
9100 let (_, buf, seen) = &mut *c;
9101 dspark_capture(state, cfg, slot, buf);
9102 *seen = if slot == 0 { 1 } else { *seen + 1 };
9107 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
9108 eprintln!("[cap] note li={li} slot={slot} seen={}", *seen);
9109 }
9110 }
9111 });
9112}
9113
9114pub fn dspark_peek_slot(slot: usize, dim: usize, out: &mut [f32]) -> bool {
9119 DSPARK_CAP.with(|c| {
9120 let c = c.borrow();
9121 let lo = slot * dim;
9122 if c.1.len() < lo + dim {
9123 return false;
9124 }
9125 out[..dim].copy_from_slice(&c.1[lo..lo + dim]);
9126 true
9127 })
9128}
9129
9130pub fn dspark_take(out: &mut Vec<f32>) -> bool {
9132 DSPARK_CAP.with(|c| {
9133 let mut c = c.borrow_mut();
9134 if c.0.is_empty() || c.2 != c.0.len() {
9135 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
9136 eprintln!("[cap] take FAIL armed={:?} seen={}", c.0, c.2);
9137 }
9138 return false;
9139 }
9140 out.clear();
9141 out.extend_from_slice(&c.1);
9142 c.2 = 0;
9143 true
9144 })
9145}
9146
9147pub fn dspark_capture(state: &[f32], cfg: &Dsv4Cfg, slot: usize, out: &mut [f32]) {
9151 let (hc, dim) = (cfg.hc_mult, cfg.dim);
9152 let dst = &mut out[slot * dim..(slot + 1) * dim];
9153 let inv = 1.0 / hc as f32;
9154 for d in 0..dim {
9155 let mut s = 0.0;
9156 for j in 0..hc {
9157 s += state[j * dim + d];
9158 }
9159 dst[d] = s * inv;
9160 }
9161}
9162
9163pub fn dspark_freq_note(picks: &[(usize, Vec<usize>)]) {
9168 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
9169 std::sync::Mutex::new(None);
9170 let Ok(path) = std::env::var("CMF_DSPARK_PICK_DUMP") else {
9171 return;
9172 };
9173 let mut g = FREQ.lock().unwrap();
9174 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
9175 for (stage, idx) in picks {
9176 for &e in idx {
9177 *map.entry((*stage, e)).or_insert(0) += 1;
9178 }
9179 }
9180 *blocks += 1;
9181 if *blocks % 32 == 0 {
9182 let mut lines: Vec<_> = map.iter().collect();
9183 lines.sort();
9184 let body: String = lines
9185 .iter()
9186 .map(|((s, e), n)| format!("{s}\t{e}\t{n}\n"))
9187 .collect();
9188 let _ = std::fs::write(&path, body);
9189 }
9190}
9191
9192pub fn trunk_freq_note(picks: &[(usize, Vec<usize>)]) {
9197 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
9198 std::sync::Mutex::new(None);
9199 let Ok(path) = std::env::var("CMF_DSV4_TRUNK_PICK_DUMP") else {
9200 return;
9201 };
9202 let mut g = FREQ.lock().unwrap();
9203 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
9204 for (li, idx) in picks {
9205 for &e in idx {
9206 *map.entry((*li, e)).or_insert(0) += 1;
9207 }
9208 }
9209 *blocks += 1;
9210 if *blocks % 32 == 0 {
9211 let mut lines: Vec<_> = map.iter().collect();
9212 lines.sort();
9213 let body: String = lines
9214 .iter()
9215 .map(|((l, e), n)| format!("{l}\t{e}\t{n}\n"))
9216 .collect();
9217 let _ = std::fs::write(&path, body);
9218 }
9219}
9220
9221fn dspark_apply_mask(out: &mut [Dsv4Mtp]) {
9229 let Ok(path) = std::env::var("CMF_DSPARK_MASK") else {
9230 return;
9231 };
9232 let Ok(text) = std::fs::read_to_string(&path) else {
9233 eprintln!("DSpark: CMF_DSPARK_MASK={path} не читается — маска не применена");
9234 return;
9235 };
9236 for (d, line) in text.lines().enumerate() {
9237 let Some(m) = out.get_mut(d) else { break };
9238 let n = m.layer.experts.len();
9239 let mut mask = vec![false; n];
9240 let mut kept = 0usize;
9241 for tok in line.split(',') {
9242 if let Ok(e) = tok.trim().parse::<usize>() {
9243 if e < n && !mask[e] {
9244 mask[e] = true;
9245 kept += 1;
9246 }
9247 }
9248 }
9249 if kept == 0 {
9250 continue;
9251 }
9252 eprintln!("DSpark: стадия {d} ограничена {kept}/{n} экспертами");
9253 m.layer.mask = Some(mask);
9254 }
9255}
9256
9257#[cfg(feature = "gpu")]
9267pub struct DsparkPack {
9268 pub stages: Vec<DsparkStagePack>,
9269 pub gu_q2: bool,
9272 pub dn_q2: bool,
9275 pub routers: Vec<Vec<f32>>,
9278 pub biases: Vec<Option<Vec<f32>>>,
9279}
9280
9281#[cfg(feature = "gpu")]
9282pub struct DsparkStagePack {
9283 pub mask: Vec<bool>,
9285 pub to_slot: Vec<usize>,
9287 pub mask_u32: Vec<u32>,
9290 pub map_u32: Vec<u32>,
9291 pub tensors: Vec<(usize, usize, usize)>,
9293 pub n_resident: usize,
9294}
9295
9296pub static DSPARK_Q2TP_ENCODE: std::sync::OnceLock<fn(&[f32], usize, usize) -> Vec<u8>> =
9303 std::sync::OnceLock::new();
9304
9305#[cfg(feature = "gpu")]
9308pub fn dspark_gpu_on() -> bool {
9309 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9310 *ON.get_or_init(|| {
9311 std::env::var("CMF_DSPARK_GPU")
9312 .map(|v| v != "0")
9313 .unwrap_or(true)
9314 })
9315}
9316
9317#[cfg(feature = "gpu")]
9319pub fn dspark_pack_get(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<&'static DsparkPack> {
9320 static P: std::sync::OnceLock<Option<Box<DsparkPack>>> = std::sync::OnceLock::new();
9321 P.get_or_init(|| dspark_pack_build(mtp, cfg).map(Box::new))
9322 .as_deref()
9323}
9324
9325#[cfg(feature = "gpu")]
9341pub fn dspark_reserve_note(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, layers: &[Dsv4Layer]) {
9342 if mtp.is_empty() || std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "0") || !dspark_gpu_on()
9343 {
9344 return;
9345 }
9346 let forced = std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "1");
9351 let dt = |q2: bool| {
9352 if q2 {
9353 cortiq_core::TensorDtype::Q2TiledP
9354 } else {
9355 cortiq_core::TensorDtype::Q4TiledP
9356 }
9357 };
9358 let gu_q2 = mtp[0]
9359 .layer
9360 .experts
9361 .first()
9362 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9363 let dn_q2 = mtp[0]
9364 .layer
9365 .experts
9366 .first()
9367 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9368 let gu = cortiq_core::quant::expected_nbytes(dt(gu_q2), &[cfg.moe_inter, cfg.dim]).unwrap_or(0);
9369 let dn = cortiq_core::quant::expected_nbytes(dt(dn_q2), &[cfg.dim, cfg.moe_inter]).unwrap_or(0);
9370 let per = (2 * gu + dn) as u64;
9371 let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
9372 .ok()
9373 .and_then(|v| v.parse().ok())
9374 .unwrap_or(40);
9377 let bytes = per * (n_res * mtp.len() + mtp.len() + 1) as u64;
9379 let trunk: u64 = layers
9389 .iter()
9390 .map(|l| {
9391 let Some(e) = l.experts.first() else {
9392 return 0;
9393 };
9394 let gu = cortiq_core::quant::expected_nbytes(
9395 dt(e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
9396 &[cfg.moe_inter, cfg.dim],
9397 )
9398 .unwrap_or(0);
9399 let dn = cortiq_core::quant::expected_nbytes(
9400 dt(e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
9401 &[cfg.dim, cfg.moe_inter],
9402 )
9403 .unwrap_or(0);
9404 let routed = l
9405 .mask
9406 .as_deref()
9407 .map_or(l.experts.len(), |m| m.iter().filter(|&&open| open).count());
9408 ((2 * gu + dn) * (routed + 1)) as u64
9409 })
9410 .sum();
9411 if let Some(budget) = crate::gpu_wgpu::dsv4_vram_budget() {
9412 if !forced && budget < trunk / 10 * 9 + bytes {
9413 return;
9414 }
9415 }
9416 let mib = 1024 * 1024u64;
9425 let workspace = match crate::gpu_wgpu::dsv4_vram_budget() {
9426 Some(b) if b <= 64 * 1024 * mib => 1024 * mib,
9430 _ => ((cfg.dim * cfg.hc_mult * DSPARK_BLOCK_MAX * 4096) as u64)
9431 .clamp(512 * mib, 1024 * mib),
9432 };
9433 crate::gpu_wgpu::DRAFT_PACK_RESERVE.store(bytes, std::sync::atomic::Ordering::Relaxed);
9434 crate::gpu_wgpu::DRAFT_RESERVE
9435 .store(bytes.saturating_add(workspace), std::sync::atomic::Ordering::Relaxed);
9436}
9437
9438#[cfg(not(feature = "gpu"))]
9439pub fn dspark_reserve_note(_mtp: &[Dsv4Mtp], _cfg: &Dsv4Cfg, _layers: &[Dsv4Layer]) {}
9440
9441#[cfg(feature = "gpu")]
9442pub fn dspark_pack_build(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<DsparkPack> {
9443 if mtp.is_empty() {
9444 return None;
9445 }
9446 let n_res: usize =
9447 std::env::var("CMF_DSPARK_RESIDENT")
9448 .ok()
9449 .and_then(|v| v.parse().ok())
9450 .unwrap_or_else(|| {
9451 let native_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
9457 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
9458 });
9459 let gu_q2 =
9460 native_q2 || (!dspark_native_on() && DSPARK_Q2TP_ENCODE.get().is_some());
9461 let dn_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
9462 e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
9463 });
9464 let room = crate::gpu_wgpu::dsv4_draft_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2);
9465 (room.saturating_sub(mtp.len() + 1) / mtp.len().max(1)).clamp(8, 40)
9466 });
9467 let mut freq: Vec<Vec<(u64, usize)>> = vec![Vec::new(); mtp.len()];
9471 let pack_path = std::env::var("CMF_DSPARK_PACK").ok().or_else(|| {
9472 let m = mtp[0].layer.experts.first()?.w1.model_arc()?;
9473 let mut s = m.path.as_os_str().to_os_string();
9474 s.push(".dspark.tsv");
9475 let p = std::path::PathBuf::from(s);
9476 p.exists().then(|| p.to_string_lossy().into_owned())
9477 });
9478 if let Some(path) = pack_path {
9479 if let Ok(text) = std::fs::read_to_string(&path) {
9480 for line in text.lines() {
9481 let mut it = line.split_whitespace();
9482 if let (Some(s), Some(e), Some(n)) = (it.next(), it.next(), it.next()) {
9483 if let (Ok(s), Ok(e), Ok(n)) =
9484 (s.parse::<usize>(), e.parse::<usize>(), n.parse::<u64>())
9485 {
9486 if s < freq.len() {
9487 freq[s].push((n, e));
9488 }
9489 }
9490 }
9491 }
9492 }
9493 }
9494 let mut stages = Vec::with_capacity(mtp.len());
9495 let mut routers = Vec::with_capacity(mtp.len());
9496 let mut biases = Vec::with_capacity(mtp.len());
9497 for (si, m) in mtp.iter().enumerate() {
9498 let l = &m.layer;
9499 let n = l.experts.len();
9500 let mut order: Vec<usize> = {
9503 let mut f = freq[si].clone();
9504 f.sort_by(|a, b| b.0.cmp(&a.0));
9505 let mut seen = vec![false; n];
9506 let mut o: Vec<usize> = f
9507 .into_iter()
9508 .map(|(_, e)| e)
9509 .filter(|&e| {
9510 if e < n && !seen[e] {
9511 seen[e] = true;
9512 true
9513 } else {
9514 false
9515 }
9516 })
9517 .collect();
9518 o.extend((0..n).filter(|&e| !seen[e]));
9519 o
9520 };
9521 order.truncate(n_res.min(n));
9522 let mut mask = vec![false; n];
9523 let mut to_slot = vec![usize::MAX; n];
9524 let mut tensors = Vec::with_capacity(order.len() + 1);
9525 for (slot, &e) in order.iter().enumerate() {
9526 let ex = &l.experts[e];
9527 let (Some(w1), Some(w3), Some(w2)) =
9528 (ex.w1.model_idx(), ex.w3.model_idx(), ex.w2.model_idx())
9529 else {
9530 return None;
9531 };
9532 mask[e] = true;
9533 to_slot[e] = slot;
9534 tensors.push((w1, w3, w2));
9535 }
9536 let (Some(s1), Some(s3), Some(s2)) = (
9537 l.shared.w1.model_idx(),
9538 l.shared.w3.model_idx(),
9539 l.shared.w2.model_idx(),
9540 ) else {
9541 return None;
9542 };
9543 tensors.push((s1, s3, s2));
9544 let mut router = vec![0.0f32; n * cfg.dim];
9546 for (r, row) in (0..n).zip(router.chunks_mut(cfg.dim)) {
9547 l.gate.row_f32(r, row);
9548 }
9549 routers.push(router);
9550 biases.push(l.gate_bias.clone());
9551 let mask_u32: Vec<u32> = mask.iter().map(|&m| m as u32).collect();
9552 let map_u32: Vec<u32> = to_slot
9553 .iter()
9554 .map(|&x| if x == usize::MAX { u32::MAX } else { x as u32 })
9555 .collect();
9556 stages.push(DsparkStagePack {
9557 mask,
9558 to_slot,
9559 mask_u32,
9560 map_u32,
9561 tensors,
9562 n_resident: order.len(),
9563 });
9564 }
9565 let model = mtp[0]
9569 .layer
9570 .experts
9571 .first()
9572 .and_then(|e| e.w1.model_arc())?;
9573 let mut skeleton = Vec::new();
9574 for m in mtp {
9575 let l = &m.layer;
9576 for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b] {
9577 skeleton.push(t.model_idx()?);
9578 }
9579 }
9580 if let Some(mp) = mtp[0].main_proj.as_ref() {
9581 skeleton.push(mp.model_idx()?);
9582 }
9583 for &idx in &skeleton {
9584 if !crate::gpu_wgpu::dsv4_weight_ready(&model, idx) {
9585 eprintln!("DSpark: скелет драфта не влез в VRAM — GPU-черновик выключен");
9586 return None;
9587 }
9588 }
9589 let native_q2 = mtp[0]
9595 .layer
9596 .experts
9597 .first()
9598 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9599 let gu_q2 = native_q2
9600 || (!crate::dsv4::dspark_native_on()
9601 && crate::dsv4::DSPARK_Q2TP_ENCODE.get().is_some());
9602 let dn_native = mtp[0]
9603 .layer
9604 .experts
9605 .first()
9606 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9607 for (si, sp) in stages.iter().enumerate() {
9608 let ok = if native_q2 {
9609 crate::gpu_wgpu::dsv4_experts_ready(
9610 &model,
9611 &sp.tensors,
9612 cfg.moe_inter,
9613 cfg.dim,
9614 true,
9615 dn_native,
9616 )
9617 } else if gu_q2 {
9618 crate::gpu_wgpu::moe_expert_bufs_requant_gu(&model, &sp.tensors, cfg.moe_inter, cfg.dim)
9619 .is_some()
9620 } else {
9621 crate::gpu_wgpu::dsv4_experts_ready(
9622 &model,
9623 &sp.tensors,
9624 cfg.moe_inter,
9625 cfg.dim,
9626 false,
9627 false,
9628 )
9629 };
9630 if !ok {
9631 eprintln!(
9632 "DSpark: эксперты стадии {si} ({} + shared) не влезли в VRAM — GPU-черновик выключен",
9633 sp.n_resident
9634 );
9635 return None;
9636 }
9637 }
9638 let _ = crate::gpu_wgpu::pin_weights(&model, &skeleton);
9639 eprintln!(
9640 "DSpark: пак драфта на карте — {} стадии по {} экспертов + shared",
9641 stages.len(),
9642 stages
9643 .iter()
9644 .map(|s| s.n_resident.to_string())
9645 .collect::<Vec<_>>()
9646 .join("/")
9647 );
9648 Some(DsparkPack {
9649 stages,
9650 gu_q2,
9651 dn_q2: dn_native,
9652 routers,
9653 biases,
9654 })
9655}
9656
9657pub fn dspark_ring_append(
9664 g: &Dsv4Globals,
9665 mtp: &[Dsv4Mtp],
9666 cfg: &Dsv4Cfg,
9667 ds: &mut DsparkState,
9668 pos: usize,
9669 pool: Option<&crate::pool::Pool>,
9670) {
9671 let (dim, hd, rd) = (cfg.dim, cfg.head_dim, cfg.rope_head_dim);
9672 let inv_freq = &g.inv_freq_window;
9673 let Some(stage0) = mtp.first() else { return };
9674 let (Some(mp), Some(mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
9675 return;
9676 };
9677 let mut main_x = vec![0.0f32; dim];
9678 mp.matvec(&ds.main_hidden, &mut main_x, pool);
9679 rms_weighted(&mut main_x, mn, cfg.norm_eps);
9680 for (si, m) in mtp.iter().enumerate() {
9681 let kvw = m.layer.wkv.rows();
9682 if ds.win[si].len() < cfg.window * kvw {
9683 ds.win[si].resize(cfg.window * kvw, 0.0);
9684 }
9685 let mut kv = vec![0.0f32; kvw];
9686 m.layer.wkv.matvec(&main_x, &mut kv, pool);
9687 rms_weighted(&mut kv, &m.layer.kv_norm, cfg.norm_eps);
9688 rope_tail(&mut kv[kvw - hd..], inv_freq, pos, rd, false);
9689 let slot = pos % cfg.window;
9690 ds.win[si][slot * kvw..(slot + 1) * kvw].copy_from_slice(&kv);
9691 ds.filled[si] = (pos + 1).min(cfg.window);
9692 }
9693}
9694
9695#[cfg(feature = "gpu")]
9701#[allow(clippy::too_many_arguments)]
9702pub fn dspark_draft_gpu(
9703 g: &Dsv4Globals,
9704 mtp: &[Dsv4Mtp],
9705 cfg: &Dsv4Cfg,
9706 ds: &mut DsparkState,
9707 pack: &DsparkPack,
9708 kv_id: u64,
9709 last_token: u32,
9710 pos: usize,
9711 pool: Option<&crate::pool::Pool>,
9712 out_conf: &mut Vec<f32>,
9713) -> Vec<u32> {
9714 let (hc, dim) = (cfg.hc_mult, cfg.dim);
9715 let block = dspark_block();
9716 let Some(model) = mtp[0].layer.experts.first().and_then(|e| e.w1.model_arc()) else {
9717 return Vec::new();
9718 };
9719 let (Some(mp), Some(mn)) = (mtp[0].main_proj.as_ref(), mtp[0].main_norm.as_ref()) else {
9720 return Vec::new();
9721 };
9722 let Some(mp_idx) = mp.model_idx() else {
9723 return Vec::new();
9724 };
9725 let mut stages = Vec::with_capacity(mtp.len());
9726 for (si, m) in mtp.iter().enumerate() {
9727 let l = &m.layer;
9728 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
9729 l.wq_a.model_idx(),
9730 l.wq_b.model_idx(),
9731 l.wo_a.model_idx(),
9732 l.wo_b.model_idx(),
9733 l.wkv.model_idx(),
9734 ) else {
9735 return Vec::new();
9736 };
9737 let sp = &pack.stages[si];
9738 stages.push(crate::gpu_wgpu::DsparkStageW {
9739 wq_a,
9740 wq_b,
9741 wo_a,
9742 wo_b,
9743 wkv,
9744 q_norm: &l.q_norm,
9745 kv_norm: &l.kv_norm,
9746 attn_norm: &l.attn_norm,
9747 ffn_norm: &l.ffn_norm,
9748 sink: &l.attn_sink,
9749 hc_attn_fn: &l.hc_attn_fn,
9750 hc_attn_scale: &l.hc_attn_scale,
9751 hc_attn_base: &l.hc_attn_base,
9752 hc_ffn_fn: &l.hc_ffn_fn,
9753 hc_ffn_scale: &l.hc_ffn_scale,
9754 hc_ffn_base: &l.hc_ffn_base,
9755 router: &pack.routers[si],
9756 bias: pack.biases[si].as_deref(),
9757 experts: &sp.tensors,
9758 mask_u32: &sp.mask_u32,
9759 map_u32: &sp.map_u32,
9760 });
9761 }
9762 let geom = crate::gpu_wgpu::DsparkGeom {
9763 dim,
9764 hc,
9765 nh: cfg.n_heads,
9766 hd: cfg.head_dim,
9767 rd: cfg.rope_head_dim,
9768 q_lora: cfg.q_lora_rank,
9769 o_lora: cfg.o_lora_rank,
9770 o_groups: cfg.o_groups,
9771 inter: cfg.moe_inter,
9772 n_experts: cfg.n_routed_experts,
9773 top_k: cfg.top_k,
9774 window: cfg.window,
9775 eps: cfg.norm_eps,
9776 hc_eps: cfg.hc_eps,
9777 sinkhorn_iters: cfg.hc_sinkhorn_iters,
9778 route_scale: cfg.route_scale,
9779 swiglu_limit: cfg.swiglu_limit,
9780 scale: (cfg.head_dim as f32).powf(-0.5),
9781 gu_q2: pack.gu_q2,
9782 dn_q2: pack.dn_q2,
9783 };
9784 let ids: Vec<u32> = (0..block)
9786 .map(|i| {
9787 if i == 0 {
9788 last_token
9789 } else {
9790 DSPARK_NOISE_TOKEN
9791 }
9792 })
9793 .collect();
9794 let mut states0 = vec![0.0f32; block * hc * dim];
9795 let mut emb = vec![0.0f32; dim];
9796 for (i, &id) in ids.iter().enumerate() {
9797 g.embed.row_f32(id as usize, &mut emb);
9798 for j in 0..hc {
9799 states0[(i * hc + j) * dim..(i * hc + j + 1) * dim].copy_from_slice(&emb);
9800 }
9801 }
9802 let dspark_time = {
9803 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9804 *ON.get_or_init(|| std::env::var("CMF_DSPARK_TIME").is_ok_and(|v| v != "0"))
9805 };
9806 let t0 = std::time::Instant::now();
9807 let filled = (pos + 1).min(cfg.window);
9808 let mut states = vec![0.0f32; block * hc * dim];
9809 if !crate::gpu_wgpu::dspark_graph(
9810 &model,
9811 &stages,
9812 geom,
9813 kv_id,
9814 mp_idx,
9815 mn,
9816 &ds.main_hidden,
9817 &states0,
9818 pos,
9819 filled,
9820 &g.inv_freq_window,
9821 block,
9822 &mut states,
9823 ) {
9824 return Vec::new();
9825 }
9826 for si in 0..mtp.len() {
9827 ds.filled[si] = filled;
9828 }
9829 let t_graph = t0.elapsed();
9830
9831 let last = &mtp[mtp.len() - 1];
9833 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
9834 last.hc_head_fn.as_ref(),
9835 last.hc_head_base.as_ref(),
9836 last.hc_head_scale,
9837 last.norm.as_ref(),
9838 ) else {
9839 return Vec::new();
9840 };
9841 let mut head_in = vec![0.0f32; block * dim];
9842 let mut pre_norms = vec![vec![0.0f32; dim]; block];
9843 for i in 0..block {
9844 hc_head_fold(
9845 &states[i * hc * dim..(i + 1) * hc * dim],
9846 hfn,
9847 hscale,
9848 hbase,
9849 cfg,
9850 pool,
9851 &mut head_in[i * dim..(i + 1) * dim],
9852 );
9853 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
9854 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
9855 }
9856 let t_fold = t0.elapsed();
9857 let mut logits = vec![0.0f32; block * cfg.vocab];
9858 let head_gpu = g.head.model_idx().is_some_and(|hi| {
9861 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
9862 &model,
9863 hi,
9864 &head_in,
9865 block,
9866 cfg.vocab,
9867 dim,
9868 &mut logits,
9869 )
9870 });
9871 if !head_gpu {
9872 g.head.matmat(&head_in, block, &mut logits, pool);
9873 }
9874 let t_head = t0.elapsed();
9875 let mut proposals = Vec::with_capacity(block);
9880 out_conf.clear();
9881 let mut prev = last_token;
9882 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
9883 let mut bias = vec![0.0f32; cfg.vocab];
9884 for i in 0..block {
9885 let row = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
9886 if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
9887 w1.row_f32(prev as usize, &mut mk_embed);
9888 w2.matvec(&mk_embed, &mut bias, pool);
9889 for (a, b) in row.iter_mut().zip(&bias) {
9890 *a += *b;
9891 }
9892 }
9893 let mut best = 0usize;
9894 for v in 1..row.len() {
9895 if row[v] > row[best] {
9896 best = v;
9897 }
9898 }
9899 if let Some(cf) = last.confidence.as_ref() {
9900 let mut cat = pre_norms[i].clone();
9901 cat.extend_from_slice(&mk_embed);
9902 let mut sc = [0.0f32; 1];
9903 if cat.len() == cf.cols() {
9904 cf.matvec(&cat, &mut sc, pool);
9905 }
9906 out_conf.push(sc[0]);
9907 }
9908 proposals.push(best as u32);
9909 prev = best as u32;
9910 }
9911 if dspark_time {
9912 eprintln!(
9913 "DSpark GPU: граф {:.1} мс, фолды {:.1}, голова {:.1}, марков+argmax {:.1}",
9914 t_graph.as_secs_f64() * 1e3,
9915 (t_fold - t_graph).as_secs_f64() * 1e3,
9916 (t_head - t_fold).as_secs_f64() * 1e3,
9917 (t0.elapsed() - t_head).as_secs_f64() * 1e3,
9918 );
9919 }
9920 proposals
9921}
9922
9923#[allow(clippy::too_many_arguments)]
9929pub fn dspark_draft(
9930 g: &Dsv4Globals,
9931 mtp: &[Dsv4Mtp],
9932 cfg: &Dsv4Cfg,
9933 ds: &mut DsparkState,
9934 last_token: u32,
9935 pos: usize,
9936 pool: Option<&crate::pool::Pool>,
9937 out_conf: &mut Vec<f32>,
9938) -> Vec<u32> {
9939 let (hc, dim, hd, rd) = (cfg.hc_mult, cfg.dim, cfg.head_dim, cfg.rope_head_dim);
9940 let block = dspark_block();
9941 let inv_freq = &g.inv_freq_window;
9942
9943 let Some(stage0) = mtp.first() else {
9945 return Vec::new();
9946 };
9947 let (Some(_mp), Some(_mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
9948 return Vec::new();
9949 };
9950 dspark_ring_append(g, mtp, cfg, ds, pos, pool);
9951
9952 let ids: Vec<u32> = (0..block)
9954 .map(|i| {
9955 if i == 0 {
9956 last_token
9957 } else {
9958 DSPARK_NOISE_TOKEN
9959 }
9960 })
9961 .collect();
9962 let mut states = vec![vec![0.0f32; hc * dim]; block];
9963 let mut emb = vec![0.0f32; dim];
9964 for (i, &id) in ids.iter().enumerate() {
9965 g.embed.row_f32(id as usize, &mut emb);
9966 for j in 0..hc {
9967 states[i][j * dim..(j + 1) * dim].copy_from_slice(&emb);
9968 }
9969 }
9970
9971 let mut scratch = HcScratch::new(cfg);
9972 for (si, m) in mtp.iter().enumerate() {
9973 let l = &m.layer;
9974 let kvw = l.wkv.rows();
9975 let mut post = vec![vec![0.0f32; hc]; block];
9978 let mut comb = vec![vec![0.0f32; hc * hc]; block];
9979 let mut resid = vec![vec![0.0f32; hc * dim]; block];
9980 let mut folded = vec![vec![0.0f32; dim]; block];
9981 let mix_hc = (2 + hc) * hc;
9982 for i in 0..block {
9983 hc_mixes(
9984 &states[i],
9985 &l.hc_attn_fn,
9986 mix_hc,
9987 cfg.norm_eps,
9988 pool,
9989 &mut scratch.mixes,
9990 );
9991 hc_split_sinkhorn(
9992 &scratch.mixes,
9993 &l.hc_attn_scale,
9994 &l.hc_attn_base,
9995 hc,
9996 cfg.hc_sinkhorn_iters,
9997 cfg.hc_eps,
9998 &mut scratch.pre,
9999 &mut post[i],
10000 &mut comb[i],
10001 );
10002 hc_fold(&states[i], &scratch.pre, hc, dim, &mut folded[i]);
10003 rms_weighted(&mut folded[i], &l.attn_norm, cfg.norm_eps);
10004 resid[i].copy_from_slice(&states[i]);
10005 }
10006 let folded_all: Vec<f32> = folded.iter().flatten().copied().collect();
10008 let mut blk_kv = vec![0.0f32; block * kvw];
10009 l.wkv.matmat(&folded_all, block, &mut blk_kv, pool);
10010 for i in 0..block {
10011 let dst = &mut blk_kv[i * kvw..(i + 1) * kvw];
10012 rms_weighted(dst, &l.kv_norm, cfg.norm_eps);
10013 rope_tail(&mut dst[kvw - hd..], inv_freq, pos + 1 + i, rd, false);
10014 }
10015 let win_len = ds.filled[si];
10017 let mut cache = Vec::with_capacity((win_len + block) * hd);
10018 for p in 0..win_len {
10019 let e = &ds.win[si][p * kvw..(p + 1) * kvw];
10020 cache.extend_from_slice(&e[kvw - hd..]);
10021 }
10022 for i in 0..block {
10023 let e = &blk_kv[i * kvw..(i + 1) * kvw];
10024 cache.extend_from_slice(&e[kvw - hd..]);
10025 }
10026 let idxs: Vec<usize> = (0..win_len + block).collect();
10027 let scale = (hd as f32).powf(-0.5);
10028 let qrank = l.wq_a.rows();
10029 let qdim = cfg.n_heads * hd;
10030 let mut qr = vec![0.0f32; block * qrank];
10031 l.wq_a.matmat(&folded_all, block, &mut qr, pool);
10032 for i in 0..block {
10033 rms_weighted(&mut qr[i * qrank..(i + 1) * qrank], &l.q_norm, cfg.norm_eps);
10034 }
10035 let mut q = vec![0.0f32; block * qdim];
10036 l.wq_b.matmat(&qr, block, &mut q, pool);
10037 let mut attn = vec![0.0f32; block * qdim];
10038 for i in 0..block {
10039 let qi = &mut q[i * qdim..(i + 1) * qdim];
10040 let ai = &mut attn[i * qdim..(i + 1) * qdim];
10041 let qpos = pos + 1 + i;
10042 for h in 0..cfg.n_heads {
10043 let head = &mut qi[h * hd..(h + 1) * hd];
10044 rms_inplace(head, cfg.norm_eps);
10045 rope_tail(head, inv_freq, qpos, rd, false);
10046 }
10047 for h in 0..cfg.n_heads {
10048 let qh = &qi[h * hd..(h + 1) * hd];
10049 let oh = &mut ai[h * hd..(h + 1) * hd];
10050 sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
10051 rope_tail(oh, inv_freq, qpos, rd, true);
10052 }
10053 }
10054 let mut blk_out = vec![0.0f32; block * dim];
10055 o_project_block(
10056 &attn,
10057 block,
10058 &l.wo_a,
10059 &l.wo_b,
10060 cfg.o_groups,
10061 cfg.o_lora_rank,
10062 pool,
10063 &mut blk_out,
10064 );
10065 for i in 0..block {
10066 let mut next = vec![0.0f32; hc * dim];
10067 hc_expand(
10068 &blk_out[i * dim..(i + 1) * dim],
10069 &resid[i],
10070 &post[i],
10071 &comb[i],
10072 hc,
10073 dim,
10074 &mut next,
10075 );
10076 states[i] = next;
10077 }
10078 let mut ffn_fold = vec![0.0f32; block * dim];
10081 let mut ffn_post = vec![vec![0.0f32; hc]; block];
10082 let mut ffn_comb = vec![vec![0.0f32; hc * hc]; block];
10083 let mut ffn_resid = vec![vec![0.0f32; hc * dim]; block];
10084 for i in 0..block {
10085 hc_mixes(
10086 &states[i],
10087 &l.hc_ffn_fn,
10088 mix_hc,
10089 cfg.norm_eps,
10090 pool,
10091 &mut scratch.mixes,
10092 );
10093 hc_split_sinkhorn(
10094 &scratch.mixes,
10095 &l.hc_ffn_scale,
10096 &l.hc_ffn_base,
10097 hc,
10098 cfg.hc_sinkhorn_iters,
10099 cfg.hc_eps,
10100 &mut scratch.pre,
10101 &mut ffn_post[i],
10102 &mut ffn_comb[i],
10103 );
10104 hc_fold(
10105 &states[i],
10106 &scratch.pre,
10107 hc,
10108 dim,
10109 &mut ffn_fold[i * dim..(i + 1) * dim],
10110 );
10111 rms_weighted(
10112 &mut ffn_fold[i * dim..(i + 1) * dim],
10113 &l.ffn_norm,
10114 cfg.norm_eps,
10115 );
10116 ffn_resid[i].copy_from_slice(&states[i]);
10117 }
10118 let mut moe_out = vec![0.0f32; block * dim];
10119 moe_step_block(&ffn_fold, block, l, cfg, &ids, si, pool, &mut moe_out);
10120 for i in 0..block {
10121 let mut next = vec![0.0f32; hc * dim];
10122 hc_expand(
10123 &moe_out[i * dim..(i + 1) * dim],
10124 &ffn_resid[i],
10125 &ffn_post[i],
10126 &ffn_comb[i],
10127 hc,
10128 dim,
10129 &mut next,
10130 );
10131 states[i] = next;
10132 }
10133 }
10134
10135 let last = &mtp[mtp.len() - 1];
10137 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
10138 last.hc_head_fn.as_ref(),
10139 last.hc_head_base.as_ref(),
10140 last.hc_head_scale,
10141 last.norm.as_ref(),
10142 ) else {
10143 return Vec::new();
10144 };
10145 let mut proposals = Vec::with_capacity(block);
10146 out_conf.clear();
10147 let mut prev = last_token;
10148 let mut head_in = vec![0.0f32; block * dim];
10149 let mut pre_norms = vec![vec![0.0f32; dim]; block];
10150 for i in 0..block {
10151 hc_head_fold(
10152 &states[i],
10153 hfn,
10154 hscale,
10155 hbase,
10156 cfg,
10157 pool,
10158 &mut head_in[i * dim..(i + 1) * dim],
10159 );
10160 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
10161 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
10162 }
10163 let mut logits = vec![0.0f32; block * cfg.vocab];
10164 g.head.matmat(&head_in, block, &mut logits, pool);
10165 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
10166 for i in 0..block {
10167 let logits_i = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
10168 let no_markov = {
10177 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10178 *ON.get_or_init(|| std::env::var("CMF_DSPARK_NO_MARKOV").is_ok_and(|v| v != "0"))
10179 };
10180 if no_markov {
10181 if let Some(w1) = last.markov_w1.as_ref() {
10183 w1.row_f32(prev as usize, &mut mk_embed);
10184 }
10185 } else if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
10186 w1.row_f32(prev as usize, &mut mk_embed);
10187 let mut bias = vec![0.0f32; cfg.vocab];
10188 w2.matvec(&mk_embed, &mut bias, pool);
10189 for (a, b) in logits_i.iter_mut().zip(&bias) {
10190 *a += *b;
10191 }
10192 }
10193 let mut best = 0usize;
10194 for v in 1..logits_i.len() {
10195 if logits_i[v] > logits_i[best] {
10196 best = v;
10197 }
10198 }
10199 if let Some(cf) = last.confidence.as_ref() {
10200 let mut cat = pre_norms[i].clone();
10201 cat.extend_from_slice(&mk_embed);
10202 let mut s = [0.0f32; 1];
10203 if cat.len() == cf.cols() {
10204 cf.matvec(&cat, &mut s, pool);
10205 }
10206 out_conf.push(s[0]);
10207 }
10208 proposals.push(best as u32);
10209 prev = best as u32;
10210 }
10211 proposals
10212}