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 dynv = pk.dynslots.lock().unwrap();
2850 let nxt = layers.get(li + 1);
2851 let w = crate::gpu_wgpu::Dsv4LayerW {
2852 attn: crate::gpu_wgpu::Dsv4AttnW {
2853 wq_a,
2854 wq_b,
2855 wo_a,
2856 wo_b,
2857 q_norm: &l.q_norm,
2858 sink: &l.attn_sink,
2859 },
2860 moe: crate::gpu_wgpu::Dsv4MoeW {
2861 router: &[],
2862 experts: &pk.tensors,
2863 logits: &[],
2864 bias: pk.bias.as_deref(),
2867 mask: pk.mask.as_deref(),
2868 forced: forced.as_deref(),
2869 remap: Some(&dynv.remap),
2870 global: None,
2871 },
2872 hc_ffn_fn: &l.hc_ffn_fn,
2873 hc_ffn_scale: &l.hc_ffn_scale,
2874 hc_ffn_base: &l.hc_ffn_base,
2875 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2876 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2877 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2878 ffn_norm: &l.ffn_norm,
2879 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2880 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2881 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2882 router: &pk.router,
2883 };
2884 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2885 attn: crate::gpu_wgpu::Dsv4AttnGeom {
2886 dim,
2887 nh: cfg.n_heads,
2888 hd,
2889 rd: cfg.rope_head_dim,
2890 q_lora: cfg.q_lora_rank,
2891 o_lora: cfg.o_lora_rank,
2892 o_groups: cfg.o_groups,
2893 eps: cfg.norm_eps,
2894 scale: (hd as f32).powf(-0.5),
2895 },
2896 moe: crate::gpu_wgpu::Dsv4MoeGeom {
2897 hidden: dim,
2898 inter: cfg.moe_inter,
2899 top_k: cfg.top_k,
2900 route_scale: cfg.route_scale,
2901 swiglu_limit: cfg.swiglu_limit,
2902 gu_q2: l
2903 .experts
2904 .first()
2905 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
2906 },
2907 hc: cfg.hc_mult,
2908 hc_eps: cfg.hc_eps,
2909 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2910 };
2911 let mut next = vec![0.0f32; dim];
2912 let mut cold: Vec<(usize, f32)> = Vec::new();
2913 let mut routed: Vec<(usize, f32)> = Vec::new();
2914 let mut cold_x: Vec<f32> = Vec::new();
2915 if !crate::gpu_wgpu::dsv4_layer_frame(
2916 &model,
2917 &w,
2918 geom,
2919 kv_id,
2920 li,
2921 Some(&prep.qr),
2922 &idx32,
2923 freqs,
2924 st.pos,
2925 &mut next,
2926 Some(&mut cold),
2927 route_stats_on().then_some(&mut routed),
2928 &mut cold_x,
2929 ) {
2930 return None;
2931 }
2932 if route_stats_on() {
2933 record_route(li, layers.len(), cfg.n_routed_experts, &routed);
2934 }
2935 drop(dynv);
2936 if cold.is_empty() {
2937 *folded = next;
2941 return Some(false);
2942 }
2943 if cold_x.len() < dim {
2946 return None;
2947 }
2948 let mut cold_sum = vec![0.0f32; dim];
2949 {
2950 let results: Vec<std::sync::Mutex<Vec<f32>>> =
2951 cold.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
2952 let (cold_ref, results_ref, x_ref) = (&cold, &results, &cold_x[..dim]);
2953 std::thread::scope(|sc| {
2954 for i in 0..cold_ref.len() {
2955 let (gi, wt) = cold_ref[i];
2956 let Some(exp) = l.experts.get(gi) else { continue };
2957 let r = &results_ref[i];
2958 sc.spawn(move || {
2959 let mut a = vec![0.0f32; cfg.dim];
2960 crate::gpu::cpu_scope(|| run_expert(x_ref, exp, cfg, wt, None, &mut a));
2961 *r.lock().unwrap() = a;
2962 });
2963 }
2964 });
2965 for r in &results {
2966 let a = r.lock().unwrap();
2967 for (o, v) in cold_sum.iter_mut().zip(a.iter()) {
2968 *o += v;
2969 }
2970 }
2971 }
2972 if !crate::gpu_wgpu::dsv4_state_add_cold_preserved(&cold_sum, cfg.hc_mult, state, kv_id, li) {
2973 return None;
2974 }
2975 {
2978 let mut dynv = pk.dynslots.lock().unwrap();
2979 dynv.clock += 1;
2980 let clock = dynv.clock;
2981 for &(gi, _) in &cold {
2982 if gi >= dynv.remap.len() || dynv.remap[gi] != u32::MAX {
2983 continue;
2984 }
2985 let victim = (0..dynv.owner.len())
2986 .filter(|&sl| dynv.last[sl] != clock)
2987 .min_by_key(|&sl| dynv.last[sl]);
2988 let Some(victim) = victim else { break };
2989 let Some(exp) = l.experts.get(gi) else { continue };
2990 let t3 = (|| Some((exp.w1.model_idx()?, exp.w3.model_idx()?, exp.w2.model_idx()?)))();
2991 let Some(t3) = t3 else { continue };
2992 let gu_q2 =
2993 exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
2994 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
2995 if !crate::gpu_wgpu::dsv4_slot_fill(
2996 &model, pack_first, victim, gi, t3, cfg.moe_inter, cfg.dim, gu_q2,
2997 ) {
2998 break;
2999 }
3000 let old = dynv.owner[victim] as usize;
3001 if old < dynv.remap.len() {
3002 dynv.remap[old] = u32::MAX;
3003 }
3004 dynv.remap[gi] = victim as u32;
3005 dynv.owner[victim] = gi as u32;
3006 dynv.last[victim] = clock;
3007 dynv.mutated = true;
3008 }
3009 }
3010 if let Some(n) = nxt {
3011 let (f, post, comb) = hc_fold_norm(
3012 state,
3013 &n.hc_attn_fn,
3014 &n.hc_attn_scale,
3015 &n.hc_attn_base,
3016 &n.attn_norm,
3017 cfg,
3018 pool,
3019 );
3020 *folded = f;
3021 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb) {
3022 return None;
3023 }
3024 }
3025 Some(true)
3026}
3027
3028#[cfg(feature = "gpu")]
3029fn chain_max() -> usize {
3030 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
3031 *N.get_or_init(|| {
3032 std::env::var("CMF_DSV4_CHAIN_MAX")
3033 .ok()
3034 .and_then(|v| v.parse().ok())
3035 .unwrap_or(usize::MAX)
3036 })
3037}
3038
3039#[cfg(feature = "gpu")]
3042fn partial_walk_on() -> bool {
3053 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3054 *ON.get_or_init(|| std::env::var("CMF_DSV4_PARTIAL_WALK").is_ok_and(|v| v != "0"))
3055}
3056
3057fn host_cpu_moe() -> bool {
3058 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3059 *ON.get_or_init(|| std::env::var("CMF_DSV4_HOST_CPU_MOE").is_ok_and(|v| v != "0"))
3060}
3061
3062fn chain_enabled() -> bool {
3063 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3064 *ON.get_or_init(|| {
3065 std::env::var("CMF_DSV4_CHAIN")
3066 .map(|v| v != "0")
3067 .unwrap_or(true)
3068 })
3069}
3070
3071#[cfg(feature = "gpu")]
3081#[allow(clippy::too_many_arguments)]
3082fn dsv4_chain_run(
3083 layers: &[Dsv4Layer],
3084 run: &[usize],
3085 cfg: &Dsv4Cfg,
3086 g: &Dsv4Globals,
3087 st: &mut Dsv4State,
3088 token_id: u32,
3089 folded: &mut Vec<f32>,
3095 state_out: Option<&mut [f32]>,
3100 batch: usize,
3103 batch_ids: &[u32],
3107 need_qn: bool,
3114 pool: Option<&crate::pool::Pool>,
3115) -> bool {
3116 if run.is_empty() {
3117 return true;
3118 }
3119 let (dim, hd) = (cfg.dim, cfg.head_dim);
3120 let first = run[0];
3121 let Some(model) = layers[first].experts.first().and_then(|e| e.w1.model_arc()) else {
3122 return false;
3123 };
3124 if batch <= 1 && need_qn {
3128 let mut qn0 = vec![0.0f32; cfg.q_lora_rank];
3129 layers[first].wq_a.matvec(folded, &mut qn0, pool);
3130 rms_weighted(&mut qn0, &layers[first].q_norm, cfg.norm_eps);
3131 if !crate::gpu_wgpu::dsv4_chain_seed(folded, &qn0) {
3132 return false;
3133 }
3134 } else if batch <= 1 && !crate::gpu_wgpu::dsv4_chain_seed_fold(folded) {
3135 return false;
3136 }
3137
3138 let mut packs = Vec::with_capacity(run.len());
3140 let mut forceds: Vec<Option<Vec<usize>>> = Vec::with_capacity(run.len());
3141 for &li in run {
3142 let Some(pk) = pack_for(&layers[li], cfg, li) else {
3143 return false;
3144 };
3145 if !pk.route_complete() || pk.is_mutated() {
3152 return false;
3153 }
3154 let forced: Option<Vec<usize>> = layers[li].tid2eid.as_ref().and_then(|tbl| {
3155 let v: Vec<usize> = if pk.needs_remap() {
3156 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
3157 } else {
3158 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
3159 .into_iter()
3160 .map(|gi| pk.to_slot[gi])
3161 .collect()
3162 };
3163 if v.contains(&usize::MAX) {
3164 None
3165 } else {
3166 Some(v)
3167 }
3168 });
3169 if layers[li].tid2eid.is_some() && forced.is_none() {
3170 return false;
3171 }
3172 forceds.push(forced);
3173 packs.push(pk);
3174 }
3175
3176 let mut items = Vec::with_capacity(run.len());
3177 let mut freqs = Vec::with_capacity(run.len());
3178 for (i, &li) in run.iter().enumerate() {
3179 let l = &layers[li];
3180 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
3181 l.wq_a.model_idx(),
3182 l.wq_b.model_idx(),
3183 l.wo_a.model_idx(),
3184 l.wo_b.model_idx(),
3185 l.wkv.model_idx(),
3186 ) else {
3187 return false;
3188 };
3189 let comp = match &l.compressor {
3190 None => None,
3191 Some(cp) => {
3192 let (Some(a), Some(b)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
3193 return false;
3194 };
3195 Some((
3196 crate::gpu_wgpu::Dsv4CompW {
3197 wkv: a,
3198 wgate: b,
3199 norm: &cp.norm,
3200 ape: &cp.ape,
3201 },
3202 crate::gpu_wgpu::Dsv4CompGeom {
3203 width: cp.wkv.rows(),
3204 hidden: dim,
3205 ratio: cp.ratio,
3206 overlap: cp.overlap,
3207 rope_dim: cfg.rope_head_dim,
3208 eps: cfg.norm_eps,
3209 },
3210 ))
3211 }
3212 };
3213 let ix = match &l.indexer {
3214 None => None,
3215 Some(ixr) => {
3216 let cp = &ixr.compressor;
3217 let (Some(a), Some(b), Some(qb), Some(wp)) = (
3218 cp.wkv.model_idx(),
3219 cp.wgate.model_idx(),
3220 ixr.wq_b.model_idx(),
3221 ixr.weights_proj.model_idx(),
3222 ) else {
3223 return false;
3224 };
3225 let ih = ixr.weights_proj.rows();
3226 Some((
3227 crate::gpu_wgpu::Dsv4CompW {
3228 wkv: a,
3229 wgate: b,
3230 norm: &cp.norm,
3231 ape: &cp.ape,
3232 },
3233 crate::gpu_wgpu::Dsv4CompGeom {
3234 width: cp.wkv.rows(),
3235 hidden: dim,
3236 ratio: cp.ratio,
3237 overlap: cp.overlap,
3238 rope_dim: cfg.rope_head_dim,
3239 eps: cfg.norm_eps,
3240 },
3241 crate::gpu_wgpu::Dsv4IxW {
3242 wq_b: qb,
3243 weights_proj: wp,
3244 },
3245 crate::gpu_wgpu::Dsv4IxGeom {
3246 ih,
3247 idim: ixr.wq_b.rows() / ih.max(1),
3248 q_lora: cfg.q_lora_rank,
3249 hidden: dim,
3250 rope_dim: cfg.rope_head_dim,
3251 eps: cfg.norm_eps,
3252 top_k: cfg.index_topk,
3253 window: cfg.window,
3254 },
3255 ))
3256 }
3257 };
3258 let ew_c0 = l.compressor.as_ref().map_or(0, |cp| {
3262 if cp.overlap {
3263 cp.wkv.rows() / 2
3264 } else {
3265 cp.wkv.rows()
3266 }
3267 });
3268 let comp_extra = l
3269 .compressor
3270 .as_ref()
3271 .map_or(0, |cp| batch.max(1).div_ceil(cp.ratio.max(1)));
3272 let need = cfg.window * hd
3273 + (st.dev_n_comp[li] + comp_extra + 1) * ew_c0.max(1)
3274 + (batch.max(1) + 1) * hd;
3275 if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
3276 return false;
3277 }
3278 let ew_c = comp.as_ref().map_or(
3279 0,
3280 |(_, cg)| {
3281 if cg.overlap { cg.width / 2 } else { cg.width }
3282 },
3283 );
3284 let ew_i = ix.as_ref().map_or(
3285 0,
3286 |(_, cg, _, _)| {
3287 if cg.overlap { cg.width / 2 } else { cg.width }
3288 },
3289 );
3290 let prep = crate::gpu_wgpu::Dsv4Prep {
3291 wkv,
3292 kv_norm: &l.kv_norm,
3293 comp,
3294 ix,
3295 filled: st.dev_filled[li],
3296 window: cfg.window,
3297 n_comp: st.dev_n_comp[li],
3298 n_ix: st.dev_n_ix[li],
3299 comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
3300 ix_dst_off: st.dev_n_ix[li] * ew_i,
3301 idx_cap: cfg.window
3302 + if l.indexer.is_some() {
3303 cfg.index_topk
3304 } else {
3305 st.dev_n_comp[li] + comp_extra + 1
3306 },
3307 };
3308 let nxt = layers.get(li + 1);
3309 let w = crate::gpu_wgpu::Dsv4LayerW {
3310 attn: crate::gpu_wgpu::Dsv4AttnW {
3311 wq_a,
3312 wq_b,
3313 wo_a,
3314 wo_b,
3315 q_norm: &l.q_norm,
3316 sink: &l.attn_sink,
3317 },
3318 moe: crate::gpu_wgpu::Dsv4MoeW {
3319 router: &packs[i].router,
3320 experts: &packs[i].tensors,
3321 logits: &[],
3322 bias: packs[i].bias.as_deref(),
3325 mask: packs[i].mask.as_deref(),
3326 forced: forceds[i].as_deref(),
3327 remap: packs[i].needs_remap().then_some(packs[i].remap.as_slice()),
3328 global: None,
3329 },
3330 hc_ffn_fn: &l.hc_ffn_fn,
3331 hc_ffn_scale: &l.hc_ffn_scale,
3332 hc_ffn_base: &l.hc_ffn_base,
3333 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
3334 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
3335 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
3336 ffn_norm: &l.ffn_norm,
3337 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
3338 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
3339 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
3340 router: &packs[i].router,
3341 };
3342 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
3343 attn: crate::gpu_wgpu::Dsv4AttnGeom {
3344 dim,
3345 nh: cfg.n_heads,
3346 hd,
3347 rd: cfg.rope_head_dim,
3348 q_lora: cfg.q_lora_rank,
3349 o_lora: cfg.o_lora_rank,
3350 o_groups: cfg.o_groups,
3351 eps: cfg.norm_eps,
3352 scale: (hd as f32).powf(-0.5),
3353 },
3354 moe: crate::gpu_wgpu::Dsv4MoeGeom {
3355 hidden: dim,
3356 inter: cfg.moe_inter,
3357 top_k: cfg.top_k,
3358 route_scale: cfg.route_scale,
3359 swiglu_limit: cfg.swiglu_limit,
3360 gu_q2: l.experts.first().is_some_and(|e| {
3361 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
3362 }),
3363 },
3364 hc: cfg.hc_mult,
3365 hc_eps: cfg.hc_eps,
3366 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3367 };
3368 freqs.push(if l.compressor.is_some() {
3369 g.inv_freq_compress.as_slice()
3370 } else {
3371 g.inv_freq_window.as_slice()
3372 });
3373 items.push((w, geom, prep));
3374 }
3375
3376 let mut out = vec![0.0f32; dim * batch.max(1)];
3377 if batch > 1 {
3378 let mut forced_pt: Vec<Vec<Option<Vec<usize>>>> = Vec::with_capacity(batch);
3383 for t in 0..batch {
3384 let id = batch_ids.get(t).copied().unwrap_or(token_id);
3385 let mut row = Vec::with_capacity(run.len());
3386 for (i, &li) in run.iter().enumerate() {
3387 row.push(layers[li].tid2eid.as_ref().and_then(|tbl| {
3388 let v: Vec<usize> = if packs[i].needs_remap() {
3389 hash_route(tbl, cfg.vocab, cfg.top_k, id)
3390 } else {
3391 hash_route(tbl, cfg.vocab, cfg.top_k, id)
3392 .into_iter()
3393 .map(|gi| packs[i].to_slot[gi])
3394 .collect()
3395 };
3396 if v.contains(&usize::MAX) {
3397 None
3398 } else {
3399 Some(v)
3400 }
3401 }));
3402 if layers[li].tid2eid.is_some() && row[i].is_none() {
3403 return false;
3404 }
3405 }
3406 forced_pt.push(row);
3407 }
3408 if !crate::gpu_wgpu::dsv4_chain_batch(
3409 &model,
3410 &items,
3411 st.kv_id,
3412 first,
3413 &freqs,
3414 st.pos,
3415 batch,
3416 Some(&forced_pt),
3417 &mut out,
3418 state_out,
3419 ) {
3420 return false;
3421 }
3422 *folded = out[(batch - 1) * dim..batch * dim].to_vec();
3425 } else {
3426 if !crate::gpu_wgpu::dsv4_layer_chain(
3427 &model, &items, st.kv_id, first, &freqs, st.pos, &mut out, state_out,
3428 ) {
3429 return false;
3430 }
3431 *folded = out;
3432 }
3433 for (i, &li) in run.iter().enumerate() {
3437 for t in 0..batch.max(1) {
3438 let pos = st.pos + t;
3439 st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
3440 if let Some((_, cg, ..)) = items[i].2.ix.as_ref() {
3441 if (pos + 1) % cg.ratio == 0 {
3442 st.dev_n_ix[li] += 1;
3443 }
3444 }
3445 if let Some((_, cg)) = items[i].2.comp.as_ref() {
3446 if (pos + 1) % cg.ratio == 0 {
3447 st.dev_n_comp[li] += 1;
3448 }
3449 }
3450 }
3451 }
3452 st.dev_owned = true;
3453 true
3454}
3455
3456#[cfg(feature = "gpu")]
3458fn hc_on_device() -> bool {
3459 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3460 *ON.get_or_init(|| {
3461 std::env::var("CMF_DSV4_HC_DEV").is_ok_and(|v| v != "0") && crate::gpu::backend_available()
3467 })
3468}
3469
3470#[cfg(feature = "gpu")]
3479#[allow(clippy::too_many_arguments)]
3480fn dsv4_two_frame_loop(
3481 state: &mut [f32],
3482 layers: &[Dsv4Layer],
3483 g: &Dsv4Globals,
3484 cfg: &Dsv4Cfg,
3485 st: &mut Dsv4State,
3486 token_id: u32,
3487 inv_freq: &[f32],
3488 pool: Option<&crate::pool::Pool>,
3489 scratch: &mut HcScratch,
3490) -> bool {
3491 let dim = cfg.dim;
3492 let freqs_of = |l: &Dsv4Layer| -> &[f32] {
3493 let f = if l.compressor.is_some() {
3494 &g.inv_freq_compress
3495 } else {
3496 &g.inv_freq_window
3497 };
3498 if f.is_empty() { inv_freq } else { f.as_slice() }
3499 };
3500 let (mut folded, post0, comb0) = hc_fold_norm(
3502 state,
3503 &layers[0].hc_attn_fn,
3504 &layers[0].hc_attn_scale,
3505 &layers[0].hc_attn_base,
3506 &layers[0].attn_norm,
3507 cfg,
3508 pool,
3509 );
3510 if !crate::gpu_wgpu::dsv4_state_write(state) || !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
3511 {
3512 return false;
3513 }
3514 let mut on_dev = vec![false; layers.len()];
3519 for (li, l) in layers.iter().enumerate() {
3520 let Some(pk) = pack_for(l, cfg, li) else {
3521 return false;
3522 };
3523 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
3524 return false;
3525 };
3526 let gu_q2 = l
3527 .experts
3528 .first()
3529 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
3530 let attn_ok = [
3531 l.wq_a.model_idx(),
3532 l.wq_b.model_idx(),
3533 l.wo_a.model_idx(),
3534 l.wo_b.model_idx(),
3535 ]
3536 .into_iter()
3537 .flatten()
3538 .all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
3539 on_dev[li] = attn_ok
3540 && pk.route_complete()
3541 && crate::gpu_wgpu::dsv4_experts_ready(
3542 &model,
3543 &pk.tensors,
3544 cfg.moe_inter,
3545 dim,
3546 gu_q2,
3547 l.experts.first().is_some_and(|e| {
3548 e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
3549 }),
3550 );
3551 }
3552 if !on_dev.iter().any(|&x| x) {
3553 return false;
3554 }
3555 let mut sink = vec![0.0f32; dim];
3556 for (li, l) in layers.iter().enumerate() {
3557 if !on_dev[li] {
3561 if !crate::gpu_wgpu::dsv4_state_read(state) {
3562 return false;
3563 }
3564 let freqs = freqs_of(l);
3565 hc_block(
3566 state,
3567 &l.hc_attn_fn,
3568 &l.hc_attn_scale,
3569 &l.hc_attn_base,
3570 &l.attn_norm,
3571 cfg,
3572 scratch,
3573 pool,
3574 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
3575 );
3576 hc_block(
3577 state,
3578 &l.hc_ffn_fn,
3579 &l.hc_ffn_scale,
3580 &l.hc_ffn_base,
3581 &l.ffn_norm,
3582 cfg,
3583 scratch,
3584 pool,
3585 |f, o| moe_step(f, l, cfg, token_id, li, pool, o),
3586 );
3587 let nref = layers.get(li + 1).unwrap_or(l);
3588 let (f, p2, c2) = hc_fold_norm(
3589 state,
3590 &nref.hc_attn_fn,
3591 &nref.hc_attn_scale,
3592 &nref.hc_attn_base,
3593 &nref.attn_norm,
3594 cfg,
3595 pool,
3596 );
3597 folded = f;
3598 if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2)
3599 || !crate::gpu_wgpu::dsv4_state_write(state)
3600 {
3601 return false;
3602 }
3603 continue;
3604 }
3605 let mut prep = AttnPrep::default();
3607 attention_step(
3608 &folded,
3609 l,
3610 cfg,
3611 st,
3612 li,
3613 freqs_of(l),
3614 pool,
3615 Some(&mut prep),
3616 &mut sink,
3617 );
3618 let hd = cfg.head_dim;
3619 let n_comp = st.compressed[li].len() / hd;
3620 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
3621 if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
3622 || (n_comp > 0
3623 && !crate::gpu_wgpu::dsv4_cache_write(
3624 st.kv_id,
3625 li,
3626 cfg.window * hd,
3627 &st.compressed[li],
3628 cap,
3629 ))
3630 {
3631 return false;
3632 }
3633 let _idx32: Vec<u32> = prep
3634 .idxs
3635 .iter()
3636 .map(|&p| {
3637 if p < prep.win_len {
3638 p as u32
3639 } else {
3640 (cfg.window + (p - prep.win_len)) as u32
3641 }
3642 })
3643 .collect();
3644 let nxt = layers.get(li + 1);
3645 let a_tail = crate::gpu_wgpu::Dsv4HcTail {
3646 fn_: &l.hc_ffn_fn,
3647 scale: &l.hc_ffn_scale,
3648 base: &l.hc_ffn_base,
3649 norm: &l.ffn_norm,
3650 hc: cfg.hc_mult,
3651 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3652 hc_eps: cfg.hc_eps,
3653 eps: cfg.norm_eps,
3654 };
3655 let scale = (cfg.head_dim as f32).powf(-0.5);
3656 if !attn_frame(
3657 l,
3658 cfg,
3659 st,
3660 li,
3661 &folded,
3662 &prep.qr,
3663 &prep.idxs,
3664 freqs_of(l),
3665 st.pos,
3666 prep.win_len,
3667 scale,
3668 Some(&a_tail),
3669 &mut [],
3670 ) {
3671 return false;
3672 }
3673 let m_tail = nxt.map(|n| crate::gpu_wgpu::Dsv4HcTail {
3674 fn_: &n.hc_attn_fn,
3675 scale: &n.hc_attn_scale,
3676 base: &n.hc_attn_base,
3677 norm: &n.attn_norm,
3678 hc: cfg.hc_mult,
3679 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3680 hc_eps: cfg.hc_eps,
3681 eps: cfg.norm_eps,
3682 });
3683 let mut next = vec![0.0f32; dim];
3684 let pair = m_tail
3685 .as_ref()
3686 .zip(nxt)
3687 .map(|(t, n)| (t, n.attn_norm.as_slice()));
3688 let forced = l
3689 .tid2eid
3690 .as_ref()
3691 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
3692 if moe_frame(
3693 &[],
3694 l,
3695 cfg,
3696 li,
3697 &[],
3698 forced.as_deref(),
3699 pool,
3700 Some(&a_tail),
3701 pair,
3702 &mut next,
3703 )
3704 .is_none()
3705 {
3706 return false;
3707 }
3708 folded = next;
3709 }
3710 let _ = scratch;
3711 crate::gpu_wgpu::dsv4_state_read(state)
3712}
3713
3714#[cfg(feature = "gpu")]
3718#[allow(clippy::too_many_arguments)]
3719fn hc_fold_norm(
3720 state: &[f32],
3721 hc_fn: &[f32],
3722 hc_scale: &[f32; 3],
3723 hc_base: &[f32],
3724 norm_w: &[f32],
3725 cfg: &Dsv4Cfg,
3726 pool: Option<&crate::pool::Pool>,
3727) -> (Vec<f32>, Vec<f32>, Vec<f32>) {
3728 let (hc, dim) = (cfg.hc_mult, cfg.dim);
3729 let mix_hc = (2 + hc) * hc;
3730 let mut mixes = vec![0.0f32; mix_hc];
3731 hc_mixes(state, hc_fn, mix_hc, cfg.norm_eps, pool, &mut mixes);
3732 let mut pre = vec![0.0f32; hc];
3733 let mut post = vec![0.0f32; hc];
3734 let mut comb = vec![0.0f32; hc * hc];
3735 hc_split_sinkhorn(
3736 &mixes,
3737 hc_scale,
3738 hc_base,
3739 hc,
3740 cfg.hc_sinkhorn_iters,
3741 cfg.hc_eps,
3742 &mut pre,
3743 &mut post,
3744 &mut comb,
3745 );
3746 let mut folded = vec![0.0f32; dim];
3747 hc_fold(state, &pre, hc, dim, &mut folded);
3748 rms_weighted(&mut folded, norm_w, cfg.norm_eps);
3749 (folded, post, comb)
3752}
3753
3754#[cfg(feature = "gpu")]
3771fn gpu_layer_enabled() -> bool {
3772 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3773 *ON.get_or_init(|| {
3774 std::env::var("CMF_DSV4_GPU_LAYER")
3775 .map(|v| v != "0")
3776 .unwrap_or(true)
3777 && crate::gpu::backend_available()
3778 })
3779}
3780
3781#[cfg(feature = "gpu")]
3785struct PackDyn {
3786 remap: Vec<u32>,
3787 owner: Vec<u32>,
3788 last: Vec<u64>,
3789 clock: u64,
3790 seen: Vec<u16>,
3797 mutated: bool,
3801}
3802
3803#[cfg(feature = "gpu")]
3808#[derive(Clone, Copy)]
3809struct GlobalOwner {
3810 layer: usize,
3811 expert: usize,
3812 pinned: bool,
3813}
3814
3815#[cfg(feature = "gpu")]
3816struct GlobalPoolDyn {
3817 slot_for: std::collections::HashMap<(usize, usize), u32>,
3818 owner: Vec<Option<GlobalOwner>>,
3819 last: Vec<u64>,
3820 seen: std::collections::HashMap<(usize, usize), u16>,
3821 occupancy: std::collections::HashMap<usize, usize>,
3822 clock: u64,
3823}
3824
3825#[cfg(feature = "gpu")]
3833struct GlobalPool {
3834 uid: u64,
3835 capacity: usize,
3836 segment_slots: usize,
3837 floor: usize,
3838 state: std::sync::Mutex<GlobalPoolDyn>,
3839}
3840
3841#[cfg(feature = "gpu")]
3842impl GlobalPool {
3843 fn remap(&self, layer: usize, n: usize) -> Vec<u32> {
3844 let st = self.state.lock().unwrap();
3845 debug_assert_eq!(self.capacity, st.owner.len());
3846 (0..n)
3847 .map(|e| st.slot_for.get(&(layer, e)).copied().unwrap_or(u32::MAX))
3848 .collect()
3849 }
3850
3851 fn seed_layer(
3852 &self,
3853 model: &std::sync::Arc<cortiq_core::CmfModel>,
3854 layer: usize,
3855 shared: (usize, usize, usize),
3856 _routed: &[(usize, (usize, usize, usize))],
3857 ) -> Option<u32> {
3858 let mut st = self.state.lock().unwrap();
3859 let shared_key = (layer, usize::MAX);
3860 let shared_slot = if let Some(&slot) = st.slot_for.get(&shared_key) {
3861 slot
3862 } else {
3863 let slot = st.owner.iter().position(Option::is_none)?;
3864 if !crate::gpu_wgpu::dsv4_global_slot_fill(model, slot, shared) {
3865 return None;
3866 }
3867 st.owner[slot] = Some(GlobalOwner {
3868 layer,
3869 expert: usize::MAX,
3870 pinned: true,
3871 });
3872 st.slot_for.insert(shared_key, slot as u32);
3873 *st.occupancy.entry(layer).or_insert(0) += 1;
3874 slot as u32
3875 };
3876 Some(shared_slot)
3881 }
3882
3883 fn ensure_picks(
3884 &self,
3885 model: &std::sync::Arc<cortiq_core::CmfModel>,
3886 layer: usize,
3887 picks: &[usize],
3888 experts: &[Dsv4Expert],
3889 quota: usize,
3890 min_seen: u16,
3891 ) -> Vec<u32> {
3892 let mut st = self.state.lock().unwrap();
3893 st.clock = st.clock.saturating_add(1);
3894 let clock = st.clock;
3895 if clock % 64 == 0 {
3896 for v in st.seen.values_mut() {
3897 *v >>= 1;
3898 }
3899 }
3900 for &expert in picks {
3901 let key = (layer, expert);
3902 let seen = st.seen.entry(key).or_insert(0);
3903 *seen = seen.saturating_add(1);
3904 if let Some(&slot) = st.slot_for.get(&key) {
3905 st.last[slot as usize] = clock;
3906 }
3907 }
3908 let mut fetched = 0usize;
3909 for &expert in picks {
3910 if fetched >= quota {
3911 break;
3912 }
3913 let key = (layer, expert);
3914 if st.slot_for.contains_key(&key)
3915 || st.seen.get(&key).copied().unwrap_or(0) < min_seen
3916 {
3917 continue;
3918 }
3919 let Some(exp) = experts.get(expert) else { continue };
3920 let Some(tensors) = (|| {
3921 Some((
3922 exp.w1.model_idx()?,
3923 exp.w3.model_idx()?,
3924 exp.w2.model_idx()?,
3925 ))
3926 })() else {
3927 continue;
3928 };
3929 let empty = st.owner.iter().position(Option::is_none);
3930 let over_floor = |o: GlobalOwner, occ: &std::collections::HashMap<usize, usize>| {
3933 occ.get(&o.layer).copied().unwrap_or(0) > self.floor
3934 };
3935 let eligible = |o: GlobalOwner| {
3936 !o.pinned && !(o.layer == layer && picks.contains(&o.expert))
3937 };
3938 let victim = empty.or_else(|| {
3939 st.owner
3940 .iter()
3941 .enumerate()
3942 .filter_map(|(slot, &o)| o.filter(|&x| eligible(x) && over_floor(x, &st.occupancy)).map(|_| slot))
3943 .min_by_key(|&slot| st.last[slot])
3944 }).or_else(|| {
3945 st.owner
3949 .iter()
3950 .enumerate()
3951 .filter_map(|(slot, &o)| o.filter(|&x| eligible(x) && x.layer == layer).map(|_| slot))
3952 .min_by_key(|&slot| st.last[slot])
3953 }).or_else(|| {
3954 st.owner
3958 .iter()
3959 .enumerate()
3960 .filter_map(|(slot, &o)| o.filter(|&x| eligible(x)).map(|_| slot))
3961 .min_by_key(|&slot| st.last[slot])
3962 });
3963 let Some(victim) = victim else { break };
3964 if !crate::gpu_wgpu::dsv4_global_slot_fill(model, victim, tensors) {
3965 break;
3966 }
3967 if let Some(old) = st.owner[victim] {
3968 st.slot_for.remove(&(old.layer, old.expert));
3969 if let Some(n) = st.occupancy.get_mut(&old.layer) {
3970 *n = n.saturating_sub(1);
3971 }
3972 }
3973 st.owner[victim] = Some(GlobalOwner {
3974 layer,
3975 expert,
3976 pinned: false,
3977 });
3978 st.slot_for.insert(key, victim as u32);
3979 *st.occupancy.entry(layer).or_insert(0) += 1;
3980 st.last[victim] = clock;
3981 fetched += 1;
3982 }
3983 drop(st);
3984 self.remap(layer, experts.len())
3985 }
3986}
3987
3988#[cfg(feature = "gpu")]
3989#[derive(Clone)]
3990struct GlobalPack {
3991 pool: std::sync::Arc<GlobalPool>,
3992 layer: usize,
3993 shared_slot: u32,
3994}
3995
3996#[cfg(feature = "gpu")]
3997impl Pack {
3998 fn is_mutated(&self) -> bool {
4000 self.global.is_some() || self.dynslots.lock().unwrap().mutated
4001 }
4002
4003 fn route_complete(&self) -> bool {
4009 if self.global.is_some() {
4010 return false;
4011 }
4012 let need = self
4013 .mask
4014 .as_deref()
4015 .map_or(self.remap.len(), |m| m.iter().filter(|&&x| x != 0).count());
4016 self.globals.len() >= need
4017 }
4018
4019 fn needs_remap(&self) -> bool {
4023 self.global.is_some()
4024 || self.mask.is_some()
4025 || self
4026 .remap
4027 .iter()
4028 .enumerate()
4029 .any(|(i, &slot)| slot != i as u32)
4030 }
4031}
4032
4033#[cfg(feature = "gpu")]
4037struct Pack {
4038 router: Vec<f32>,
4042 to_slot: Vec<usize>,
4044 remap: Vec<u32>,
4046 globals: Vec<usize>,
4048 tensors: Vec<(usize, usize, usize)>,
4049 mask: Option<Vec<u32>>,
4053 dynslots: std::sync::Mutex<PackDyn>,
4062 bias: Option<Vec<f32>>,
4071 global: Option<GlobalPack>,
4075}
4076
4077#[cfg(feature = "gpu")]
4078fn pack_freq_order(li: usize, n: usize) -> Option<Vec<usize>> {
4084 use std::collections::HashMap;
4085 use std::sync::OnceLock;
4086 static FREQ: OnceLock<Option<HashMap<(usize, usize), u64>>> = OnceLock::new();
4087 let map = FREQ
4088 .get_or_init(|| {
4089 let path = std::env::var("CMF_DSV4_PACK_FREQ").ok()?;
4090 let text = match std::fs::read_to_string(&path) {
4091 Ok(t) => t,
4092 Err(e) => {
4093 eprintln!("CMF_DSV4_PACK_FREQ={path} не читается ({e}) — порядок по id");
4094 return None;
4095 }
4096 };
4097 let mut m = HashMap::new();
4098 for line in text.lines() {
4099 let mut it = line.split('\t');
4100 if let (Some(l), Some(e), Some(c)) = (it.next(), it.next(), it.next()) {
4101 if let (Ok(l), Ok(e), Ok(c)) =
4102 (l.trim().parse(), e.trim().parse(), c.trim().parse::<u64>())
4103 {
4104 *m.entry((l, e)).or_insert(0) += c;
4105 }
4106 }
4107 }
4108 Some(m)
4109 })
4110 .as_ref()?;
4111 if !(0..n).any(|e| map.contains_key(&(li, e))) {
4112 return None;
4113 }
4114 let mut idx: Vec<usize> = (0..n).collect();
4115 idx.sort_by_key(|&e| {
4116 (
4117 std::cmp::Reverse(map.get(&(li, e)).copied().unwrap_or(0)),
4118 e,
4119 )
4120 });
4121 Some(idx)
4122}
4123
4124#[cfg(feature = "gpu")]
4125fn global_pool_for(
4126 model: &std::sync::Arc<cortiq_core::CmfModel>,
4127 cfg: &Dsv4Cfg,
4128 gu_q2: bool,
4129 dn_q2: bool,
4130) -> Option<std::sync::Arc<GlobalPool>> {
4131 use std::collections::HashMap;
4132 use std::sync::{Arc, Mutex, OnceLock};
4133 if gu_q2
4134 || dn_q2
4135 || !crate::gpu_wgpu::dsv4_global_moe_supported()
4136 || std::env::var("CMF_MOE_MASK").is_ok()
4137 {
4138 return None;
4139 }
4140 static POOLS: OnceLock<Mutex<HashMap<u64, Arc<GlobalPool>>>> = OnceLock::new();
4141 let pools = POOLS.get_or_init(|| Mutex::new(HashMap::new()));
4142 if let Some(p) = pools.lock().unwrap().get(&model.uid()).cloned() {
4143 return Some(p);
4144 }
4145 let requested = crate::gpu_wgpu::dsv4_experts_fit(cfg.moe_inter, cfg.dim, false, false);
4146 let (capacity, segment_slots) =
4147 crate::gpu_wgpu::dsv4_global_moe_create(model, requested, cfg.moe_inter, cfg.dim)?;
4148 let layers = model.header.arch.num_layers.max(1);
4149 let p = Arc::new(GlobalPool {
4150 uid: model.uid(),
4151 capacity,
4152 segment_slots,
4153 floor: (capacity / layers).max(2),
4156 state: Mutex::new(GlobalPoolDyn {
4157 slot_for: HashMap::new(),
4158 owner: vec![None; capacity],
4159 last: vec![0; capacity],
4160 seen: HashMap::new(),
4161 occupancy: HashMap::new(),
4162 clock: 0,
4163 }),
4164 });
4165 pools.lock().unwrap().insert(model.uid(), p.clone());
4166 Some(p)
4167}
4168
4169#[cfg(feature = "gpu")]
4170fn pack_for(l: &Dsv4Layer, cfg: &Dsv4Cfg, li: usize) -> Option<std::sync::Arc<Pack>> {
4171 use std::collections::HashMap;
4172 use std::sync::{Arc, Mutex, OnceLock};
4173 static CACHE: OnceLock<Mutex<HashMap<(u64, usize, usize), Option<Arc<Pack>>>>> =
4174 OnceLock::new();
4175 let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
4176 let model_uid = l
4182 .experts
4183 .first()
4184 .and_then(|e| e.w1.model_arc())
4185 .map_or(0, |m| m.uid());
4186 let first_expert = l
4192 .experts
4193 .first()
4194 .and_then(|e| e.w1.model_idx())
4195 .unwrap_or(usize::MAX);
4196 let key = (model_uid, li, first_expert);
4197 if let Some(v) = cache.lock().unwrap().get(&key) {
4198 return v.clone();
4199 }
4200 if let Ok(v) = std::env::var("CMF_DSV4_PACK_MAX_LI") {
4207 if v.parse::<usize>().is_ok_and(|max| li > max) {
4208 cache.lock().unwrap().insert(key, None);
4209 return None;
4210 }
4211 }
4212 let build = || -> Option<Arc<Pack>> {
4213 let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
4214 let mut globals = Vec::new();
4215 let mut tensors = Vec::new();
4216 let idx3 = |e: &Dsv4Expert| -> Option<(usize, usize, usize)> {
4217 Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
4218 };
4219 let route_mask: Option<Vec<u32>> = if l.tid2eid.is_none() {
4220 l.mask
4221 .as_deref()
4222 .map(|m| m.iter().map(|&open| u32::from(open)).collect())
4223 } else {
4224 None
4225 };
4226 let gu_q2 = l
4231 .experts
4232 .first()
4233 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4234 let dn_q2 = l
4235 .experts
4236 .first()
4237 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4238 if route_mask.is_none() {
4243 if let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) {
4244 if let Some(gp) = global_pool_for(&model, cfg, gu_q2, dn_q2) {
4245 let layer_key = first_expert;
4246 let order = pack_freq_order(li, l.experts.len())
4247 .unwrap_or_else(|| (0..l.experts.len()).collect());
4248 let routed: Vec<_> = order
4249 .into_iter()
4250 .filter_map(|gi| Some((gi, idx3(&l.experts[gi])?)))
4251 .collect();
4252 let shared = idx3(&l.shared)?;
4253 let shared_slot = gp.seed_layer(&model, layer_key, shared, &routed)?;
4254 let remap = gp.remap(layer_key, cfg.n_routed_experts);
4255 let to_slot: Vec<usize> = remap
4256 .iter()
4257 .map(|&s| if s == u32::MAX { usize::MAX } else { s as usize })
4258 .collect();
4259 let globals: Vec<usize> = remap
4260 .iter()
4261 .enumerate()
4262 .filter_map(|(e, &s)| (s != u32::MAX).then_some(e))
4263 .collect();
4264 let (rows, cols) = (l.gate.rows(), l.gate.cols());
4265 let mut router = vec![0.0f32; rows * cols];
4266 for r in 0..rows {
4267 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4268 }
4269 return Some(Arc::new(Pack {
4270 bias: l.gate_bias.clone(),
4271 mask: None,
4272 router,
4273 to_slot,
4274 remap: remap.clone(),
4275 globals,
4276 tensors: Vec::new(),
4279 dynslots: std::sync::Mutex::new(PackDyn {
4280 remap,
4281 owner: Vec::new(),
4282 last: Vec::new(),
4283 clock: 0,
4284 mutated: true,
4285 seen: vec![0; cfg.n_routed_experts],
4286 }),
4287 global: Some(GlobalPack {
4288 pool: gp,
4289 layer: layer_key,
4290 shared_slot,
4291 }),
4292 }));
4293 }
4294 }
4295 }
4296 if let Some(n) = std::env::var("CMF_DSV4_PACK_MAX")
4305 .ok()
4306 .and_then(|v| v.parse::<usize>().ok())
4307 {
4308 let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
4309 let mut globals = Vec::new();
4310 let mut tensors = Vec::new();
4311 for (gi, e) in l.experts.iter().enumerate() {
4312 if route_mask
4313 .as_deref()
4314 .is_some_and(|m| m.get(gi).copied().unwrap_or(1) == 0)
4315 {
4316 continue;
4317 }
4318 if globals.len() >= n {
4319 break;
4320 }
4321 to_slot[gi] = globals.len();
4322 globals.push(gi);
4323 tensors.push(idx3(e)?);
4324 }
4325 tensors.push(idx3(&l.shared)?);
4326 let (rows, cols) = (l.gate.rows(), l.gate.cols());
4327 let mut router = vec![0.0f32; rows * cols];
4328 for r in 0..rows {
4329 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4330 }
4331 let remap: Vec<u32> = to_slot
4332 .iter()
4333 .map(|&sl| {
4334 if sl == usize::MAX {
4335 u32::MAX
4336 } else {
4337 sl as u32
4338 }
4339 })
4340 .collect();
4341 return Some(Arc::new(Pack {
4342 bias: l.gate_bias.clone(),
4343 mask: route_mask.clone(),
4344 router,
4345 to_slot,
4346 dynslots: std::sync::Mutex::new(PackDyn {
4347 remap: remap.clone(),
4348 owner: globals.iter().map(|&g| g as u32).collect(),
4349 last: vec![0; globals.len()],
4350 clock: 0,
4351 mutated: false,
4352 seen: vec![0; cfg.n_routed_experts],
4353 }),
4354 remap,
4355 globals,
4356 tensors,
4357 global: None,
4358 }));
4359 }
4360 let dn_q2_fit = l
4361 .experts
4362 .first()
4363 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4364 let room = crate::gpu_wgpu::dsv4_experts_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2_fit)
4365 .saturating_sub(1);
4366 let remaining_layers = l
4377 .experts
4378 .first()
4379 .and_then(|e| e.w1.model_arc())
4380 .map(|m| m.header.arch.num_layers.saturating_sub(li).max(1))
4381 .unwrap_or(1);
4382 let auto_cap = if l.mask.is_some() {
4383 room
4388 } else if l.tid2eid.is_some() && room >= cfg.n_routed_experts {
4389 cfg.n_routed_experts
4390 } else {
4391 room.div_ceil(remaining_layers).max(1)
4392 };
4393 let room = match std::env::var("CMF_DSV4_PACK_LAYER_CAP")
4394 .ok()
4395 .and_then(|v| v.parse::<usize>().ok())
4396 {
4397 Some(0) => room,
4398 Some(cap) => room.min(cap),
4399 None => room.min(auto_cap),
4400 };
4401 let order =
4410 pack_freq_order(li, l.experts.len()).unwrap_or_else(|| (0..l.experts.len()).collect());
4411 for gi in order {
4412 let e = &l.experts[gi];
4413 if l.mask
4414 .as_deref()
4415 .is_some_and(|m| !m.get(gi).copied().unwrap_or(true))
4416 {
4417 continue;
4418 }
4419 if globals.len() >= room {
4420 break;
4421 }
4422 to_slot[gi] = globals.len();
4423 globals.push(gi);
4424 match idx3(e) {
4425 Some(t) => tensors.push(t),
4426 None => {
4427 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
4428 eprintln!("слой {li}: эксперт {gi} без индексов в каталоге");
4429 }
4430 return None;
4431 }
4432 }
4433 }
4434 if globals.is_empty() {
4435 if room == 0 {
4441 static SAID_ZERO: std::sync::atomic::AtomicBool =
4442 std::sync::atomic::AtomicBool::new(false);
4443 if !SAID_ZERO.swap(true, std::sync::atomic::Ordering::Relaxed) {
4444 tracing::warn!(
4445 "начиная со слоя {li}, в бюджете VRAM не осталось места даже под одного \
4446 эксперта — остальные веса остаются mmap-backed и читаются по требованию"
4447 );
4448 }
4449 } else {
4450 tracing::warn!("слой {li}: маска не оставила ни одного эксперта");
4451 }
4452 return None;
4453 }
4454 tensors.push(idx3(&l.shared)?); let (rows, cols) = (l.gate.rows(), l.gate.cols());
4456 let mut router = vec![0.0f32; rows * cols];
4457 for r in 0..rows {
4458 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4459 }
4460 let remap: Vec<u32> = to_slot
4461 .iter()
4462 .map(|&sl| {
4463 if sl == usize::MAX {
4464 u32::MAX
4465 } else {
4466 sl as u32
4467 }
4468 })
4469 .collect();
4470 Some(Arc::new(Pack {
4471 bias: l.gate_bias.clone(),
4472 mask: route_mask,
4473 router,
4474 to_slot,
4475 dynslots: std::sync::Mutex::new(PackDyn {
4476 remap: remap.clone(),
4477 owner: globals.iter().map(|&g| g as u32).collect(),
4478 last: vec![0; globals.len()],
4479 clock: 0,
4480 mutated: false,
4481 seen: vec![0; cfg.n_routed_experts],
4482 }),
4483 remap,
4484 globals,
4485 tensors,
4486 global: None,
4487 }))
4488 };
4489 let v = build();
4490 cache.lock().unwrap().insert(key, v.clone());
4491 v
4492}
4493
4494#[cfg(feature = "gpu")]
4502fn moe_frame(
4503 hidden: &[f32],
4504 l: &Dsv4Layer,
4505 cfg: &Dsv4Cfg,
4506 li: usize,
4507 logits: &[f32],
4508 forced: Option<&[usize]>,
4509 pool: Option<&crate::pool::Pool>,
4510 hc_cur: Option<&crate::gpu_wgpu::Dsv4HcTail>,
4513 hc_next: Option<(&crate::gpu_wgpu::Dsv4HcTail, &[f32])>,
4514 out: &mut [f32],
4515) -> Option<(Vec<f32>, usize)> {
4516 macro_rules! no {
4517 ($($t:tt)*) => {{
4518 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
4519 eprintln!("кадр MoE отклонён: {}", format_args!($($t)*));
4520 }
4521 return None;
4522 }};
4523 }
4524 let Some(pk) = pack_for(l, cfg, li) else {
4525 no!("слой {li}: упаковка экспертов не построена");
4526 };
4527 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
4530 no!("слой {li}: эксперты не отображены из файла");
4531 };
4532 let subset = !pk.route_complete();
4533 let needs_remap = pk.needs_remap();
4534 fn fetch_quota() -> usize {
4541 static Q: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
4542 *Q.get_or_init(|| {
4543 std::env::var("CMF_DSV4_FETCH_MAX")
4544 .ok()
4545 .and_then(|v| v.parse().ok())
4546 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().0)
4547 })
4548 }
4549 fn fetch_min_seen() -> u16 {
4550 static M: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
4551 *M.get_or_init(|| {
4552 std::env::var("CMF_DSV4_FETCH_MIN_SEEN")
4553 .ok()
4554 .and_then(|v| v.parse().ok())
4555 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().1)
4556 })
4557 }
4558 let mut dynv = pk.dynslots.lock().unwrap();
4559 let mut pidx = Vec::new();
4564 let mut pwt = Vec::new();
4565 let mut predicted_logits = Vec::new();
4571 let prediction_logits: &[f32] = if subset && logits.is_empty() && !hidden.is_empty() {
4572 predicted_logits.resize(cfg.n_routed_experts, 0.0);
4573 l.gate.matvec(hidden, &mut predicted_logits, pool);
4574 &predicted_logits
4575 } else {
4576 logits
4577 };
4578 if subset && !prediction_logits.is_empty() {
4579 route(
4580 prediction_logits,
4581 l.gate_bias.as_deref(),
4582 cfg.top_k,
4583 cfg.route_scale,
4584 forced,
4585 l.mask.as_deref(),
4586 &mut pidx,
4587 &mut pwt,
4588 );
4589 }
4590 let global_remap = pk.global.as_ref().map(|gl| {
4591 gl.pool.ensure_picks(
4592 &model,
4593 gl.layer,
4594 &pidx,
4595 &l.experts,
4596 cfg.top_k,
4602 1,
4603 )
4604 });
4605 if subset && fetch_quota() > 0 && !pidx.is_empty() && !dynv.owner.is_empty() {
4606 dynv.clock += 1;
4607 let clock = dynv.clock;
4608 if clock % 64 == 0 {
4609 for v in dynv.seen.iter_mut() {
4610 *v >>= 1;
4611 }
4612 }
4613 for &pick in &pidx {
4614 dynv.seen[pick] = dynv.seen[pick].saturating_add(1);
4615 let sl = dynv.remap[pick];
4616 if sl != u32::MAX {
4617 dynv.last[sl as usize] = clock;
4618 }
4619 }
4620 let mut fetched = 0usize;
4621 for &pick in &pidx {
4622 if fetched >= fetch_quota() {
4623 break;
4624 }
4625 if dynv.remap[pick] != u32::MAX {
4626 continue;
4627 }
4628 if dynv.seen[pick] < fetch_min_seen() {
4629 continue; }
4631 let victim = (0..dynv.owner.len())
4633 .filter(|&sl| dynv.last[sl] != clock)
4634 .min_by_key(|&sl| dynv.last[sl]);
4635 let Some(victim) = victim else { break };
4636 let Some(exp) = l.experts.get(pick) else { continue };
4637 let t3 = (|| {
4638 Some((
4639 exp.w1.model_idx()?,
4640 exp.w3.model_idx()?,
4641 exp.w2.model_idx()?,
4642 ))
4643 })();
4644 let Some(t3) = t3 else { continue };
4645 let gu_q2 =
4646 exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
4647 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
4648 if !crate::gpu_wgpu::dsv4_slot_fill(
4649 &model,
4650 pack_first,
4651 victim,
4652 pick,
4653 t3,
4654 cfg.moe_inter,
4655 cfg.dim,
4656 gu_q2,
4657 ) {
4658 break;
4659 }
4660 let old = dynv.owner[victim] as usize;
4661 if old < dynv.remap.len() {
4662 dynv.remap[old] = u32::MAX;
4663 }
4664 dynv.remap[pick] = victim as u32;
4665 dynv.owner[victim] = pick as u32;
4666 dynv.last[victim] = clock;
4667 dynv.mutated = true;
4668 fetched += 1;
4669 }
4670 }
4671 let fpack: Option<Vec<usize>> = match forced {
4675 Some(f) if needs_remap => Some(f.to_vec()),
4676 Some(f) => {
4677 let v: Vec<usize> = f.iter().map(|&g| pk.to_slot[g]).collect();
4678 if v.contains(&usize::MAX) {
4679 no!("слой {li}: хеш-слой называет эксперта вне упаковки");
4680 }
4681 Some(v)
4682 }
4683 None => None,
4684 };
4685 let lg: Vec<f32> = if logits.is_empty() || needs_remap {
4692 logits.to_vec()
4693 } else {
4694 pk.globals.iter().map(|&g| logits[g]).collect()
4695 };
4696 let live_remap = global_remap.as_deref().unwrap_or(dynv.remap.as_slice());
4697 let w = crate::gpu_wgpu::Dsv4MoeW {
4698 router: &pk.router,
4699 experts: &pk.tensors,
4700 logits: &lg,
4701 bias: pk.bias.as_deref(),
4702 mask: pk.mask.as_deref(),
4703 forced: fpack.as_deref(),
4704 remap: needs_remap.then_some(live_remap),
4705 global: pk.global.as_ref().map(|gl| crate::gpu_wgpu::Dsv4GlobalMoe {
4706 pool_uid: gl.pool.uid,
4707 shared_slot: gl.shared_slot,
4708 segment_slots: gl.pool.segment_slots as u32,
4709 }),
4710 };
4711 let g = crate::gpu_wgpu::Dsv4MoeGeom {
4712 hidden: cfg.dim,
4713 inter: cfg.moe_inter,
4714 top_k: cfg.top_k,
4715 route_scale: cfg.route_scale,
4716 swiglu_limit: cfg.swiglu_limit,
4717 gu_q2: l
4718 .experts
4719 .first()
4720 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
4721 };
4722 let mut cold = Vec::new();
4723 let mut cold_x = Vec::new();
4724 let overlap: Vec<usize> = if !hidden.is_empty() {
4733 pidx.iter()
4734 .copied()
4735 .filter(|&pick| live_remap.get(pick).copied().unwrap_or(u32::MAX) == u32::MAX)
4736 .collect()
4737 } else {
4738 Vec::new()
4739 };
4740 let mut early: std::collections::HashMap<usize, Vec<f32>> = std::collections::HashMap::new();
4741 let frame_ok = std::thread::scope(|sc| {
4742 let handles: Vec<_> = overlap
4743 .iter()
4744 .filter_map(|&gi| l.experts.get(gi).map(|exp| (gi, exp)))
4745 .map(|(gi, exp)| {
4746 sc.spawn(move || {
4747 let mut a = vec![0.0f32; cfg.dim];
4748 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, 1.0, None, &mut a));
4749 (gi, a)
4750 })
4751 })
4752 .collect();
4753 let ok = crate::gpu_wgpu::dsv4_moe_frame(
4754 &model,
4755 &w,
4756 g,
4757 hidden,
4758 &mut cold,
4759 &mut cold_x,
4760 hc_cur,
4761 hc_next,
4762 out,
4763 );
4764 for h in handles {
4765 if let Ok((gi, a)) = h.join() {
4766 early.insert(gi, a);
4767 }
4768 }
4769 ok
4770 });
4771 if !frame_ok {
4772 return None;
4773 }
4774 if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
4777 let csum: f32 = cold.iter().map(|c| c.1).sum();
4778 eprintln!(
4779 "[холодные] слой {li}: вернулось {} из {} | сумма холодных {csum:.4} | \
4780 route_scale {:.4} | {:?}",
4781 cold.len(),
4782 cfg.top_k,
4783 cfg.route_scale,
4784 &cold[..cold.len().min(3)]
4785 );
4786 }
4787 let mut acc = vec![0.0f32; cfg.dim];
4788 let mut cold_sum = vec![0.0f32; cfg.dim];
4789 let cold_input = if hidden.is_empty() {
4790 cold_x.as_slice()
4791 } else {
4792 hidden
4793 };
4794 if !early.is_empty() {
4807 for &(gi, wt) in &cold {
4812 if let Some(a) = early.get(&gi) {
4813 for ((o, sum), v) in out.iter_mut().zip(&mut cold_sum).zip(a.iter()) {
4814 *o += v * wt;
4815 *sum += v * wt;
4816 }
4817 continue;
4818 }
4819 let Some(exp) = l.experts.get(gi) else { continue };
4820 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
4821 for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
4822 *o += a;
4823 *sum += a;
4824 }
4825 }
4826 return Some((cold_sum, cold.len()));
4827 }
4828 match pool {
4829 Some(p) if cold.len() > 1 => {
4830 let results: Vec<std::sync::Mutex<Vec<f32>>> =
4831 cold.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
4832 let (cold_ref, results_ref) = (&cold, &results);
4833 p.run_rows(cold.len(), &move |cs, ce| {
4834 for i in cs..ce {
4835 let (gi, wt) = cold_ref[i];
4836 let Some(exp) = l.experts.get(gi) else { continue };
4837 let mut a = vec![0.0f32; cfg.dim];
4838 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, None, &mut a));
4839 *results_ref[i].lock().unwrap() = a;
4840 }
4841 });
4842 for r in &results {
4845 let a = r.lock().unwrap();
4846 if a.is_empty() {
4847 continue;
4848 }
4849 for ((o, sum), v) in out.iter_mut().zip(&mut cold_sum).zip(a.iter()) {
4850 *o += v;
4851 *sum += v;
4852 }
4853 }
4854 }
4855 _ => {
4856 for &(gi, wt) in &cold {
4857 let Some(exp) = l.experts.get(gi) else {
4858 continue;
4859 };
4860 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
4861 for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
4862 *o += a;
4863 *sum += a;
4864 }
4865 }
4866 }
4867 }
4868 Some((cold_sum, cold.len()))
4869}
4870
4871#[cfg(feature = "gpu")]
4877fn last_grew(now: u64) -> u64 {
4878 use std::sync::atomic::{AtomicU64, Ordering};
4879 static SEEN: AtomicU64 = AtomicU64::new(0);
4880 let was = SEEN.load(Ordering::Relaxed);
4881 if was != now {
4882 SEEN.store(now, Ordering::Relaxed);
4883 compressed_map().lock().unwrap().clear();
4884 return u64::MAX; }
4886 now
4887}
4888
4889#[cfg(feature = "gpu")]
4890fn compressed_map() -> &'static std::sync::Mutex<std::collections::HashMap<(u64, usize), usize>> {
4891 use std::collections::HashMap;
4892 use std::sync::{Mutex, OnceLock};
4893 static W: OnceLock<Mutex<HashMap<(u64, usize), usize>>> = OnceLock::new();
4894 W.get_or_init(|| Mutex::new(HashMap::new()))
4895}
4896
4897#[cfg(feature = "gpu")]
4898fn compressed_written(kv_id: u64, li: usize) -> usize {
4899 compressed_map()
4900 .lock()
4901 .unwrap()
4902 .get(&(kv_id, li))
4903 .copied()
4904 .unwrap_or(0)
4905}
4906
4907#[cfg(feature = "gpu")]
4910fn note_compressed(kv_id: u64, li: usize, n: usize) {
4911 compressed_map().lock().unwrap().insert((kv_id, li), n);
4912}
4913
4914#[cfg(feature = "gpu")]
4915fn gpu_moe2_enabled() -> bool {
4916 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4917 *ON.get_or_init(|| {
4918 std::env::var("CMF_DSV4_GPU_MOE2")
4919 .map(|v| v != "0")
4920 .unwrap_or(true)
4921 && crate::gpu::backend_available()
4922 })
4923}
4924
4925pub fn moe_step(
4926 hidden: &[f32],
4927 l: &Dsv4Layer,
4928 cfg: &Dsv4Cfg,
4929 token_id: u32,
4930 li: usize,
4932 pool: Option<&crate::pool::Pool>,
4933 out: &mut [f32],
4934) {
4935 let _t0 = prof::on().then(std::time::Instant::now);
4936 let _guard = scopeguard_moe(_t0, li);
4937 let mut logits = vec![0.0f32; cfg.n_routed_experts];
4938 l.gate.matvec(hidden, &mut logits, pool);
4939 let (mut idx, mut w) = (Vec::new(), Vec::new());
4940 route(
4941 &logits,
4942 l.gate_bias.as_deref(),
4943 cfg.top_k,
4944 cfg.route_scale,
4945 l.tid2eid
4946 .as_ref()
4947 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id))
4948 .as_deref(),
4949 l.mask.as_deref(),
4950 &mut idx,
4951 &mut w,
4952 );
4953 if route_stats_on() {
4954 let routed: Vec<(usize, f32)> = idx.iter().copied().zip(w.iter().copied()).collect();
4955 record_route(li, 0, cfg.n_routed_experts, &routed);
4956 }
4957 crate::pipeline::moe_trace_at(li as i32, &idx);
4963 #[cfg(feature = "gpu")]
4967 if gpu_moe2_enabled() && crate::gpu::enabled_here() {
4968 let forced = l
4969 .tid2eid
4970 .as_ref()
4971 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
4972 if moe_frame(
4973 hidden,
4974 l,
4975 cfg,
4976 li,
4977 &logits,
4978 forced.as_deref(),
4979 pool,
4980 None,
4981 None,
4982 out,
4983 )
4984 .is_some()
4985 {
4986 if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
4992 let mut want = vec![0.0f32; out.len()];
4993 let mut acc = vec![0.0f32; cfg.dim];
4994 for (e, &ei) in idx.iter().enumerate() {
4995 let Some(exp) = l.experts.get(ei) else {
4996 continue;
4997 };
4998 run_expert(
4999 hidden,
5000 exp,
5001 cfg,
5002 w.get(e).copied().unwrap_or(0.0),
5003 pool,
5004 &mut acc,
5005 );
5006 for (o, a) in want.iter_mut().zip(&acc) {
5007 *o += a;
5008 }
5009 }
5010 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
5011 for (o, a) in want.iter_mut().zip(&acc) {
5012 *o += a;
5013 }
5014 let num: f32 = want
5015 .iter()
5016 .zip(out.iter())
5017 .map(|(a, b)| (a - b) * (a - b))
5018 .sum();
5019 let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
5020 let rel = (num / den).sqrt();
5021 if rel > 1e-3 {
5022 let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
5023 eprintln!(
5024 "[кадр MoE] слой {li}: расхождение {rel:.3e} | выбрано {} | \
5025 упаковано {packed} из {} | хеш={} | смещение={}",
5026 idx.len(),
5027 cfg.n_routed_experts,
5028 l.tid2eid.is_some(),
5029 l.gate_bias.is_some()
5030 );
5031 }
5032 }
5033 return;
5034 }
5035 }
5036 PICK_TALLY.with(|t| {
5042 if let Some(v) = t.borrow_mut().as_mut() {
5043 v.push((li, idx.to_vec()));
5044 }
5045 });
5046 if dump_path().is_some() {
5047 PICKED.with(|p| {
5048 let mut p = p.borrow_mut();
5049 if p.len() <= li {
5050 p.resize(li + 1, Vec::new());
5051 }
5052 p[li] = idx.clone();
5053 });
5054 }
5055 fn gpu_moe_on() -> bool {
5074 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5075 *ON.get_or_init(|| std::env::var("CMF_DSV4_GPU_MOE").is_ok_and(|v| v != "0"))
5076 }
5077 if gpu_moe_on() && crate::gpu::enabled_here() {
5078 let mut jobs = Vec::with_capacity(idx.len() + 1);
5079 let mut model_ref = None;
5080 let mut ok = true;
5081 for (e, &ei) in idx.iter().enumerate() {
5082 let Some(exp) = l.experts.get(ei) else {
5083 continue;
5084 };
5085 ok &= crate::pipeline::moe_push_job_parts(
5086 &exp.w1,
5087 &exp.w3,
5088 &exp.w2,
5089 hidden,
5090 w.get(e).copied().unwrap_or(0.0),
5091 cfg.swiglu_limit,
5092 &mut jobs,
5093 &mut model_ref,
5094 )
5095 .is_some();
5096 }
5097 ok &= crate::pipeline::moe_push_job_parts(
5098 &l.shared.w1,
5099 &l.shared.w3,
5100 &l.shared.w2,
5101 hidden,
5102 1.0,
5103 cfg.swiglu_limit,
5104 &mut jobs,
5105 &mut model_ref,
5106 )
5107 .is_some();
5108 if ok {
5109 if let Some(m) = model_ref.as_ref() {
5110 if crate::gpu::moe_block(m, &jobs, out) {
5111 if std::env::var("CMF_DSV4_GPU_CHECK").is_ok() {
5116 let mut want = vec![0.0f32; out.len()];
5117 let mut acc = vec![0.0f32; cfg.dim];
5118 for (e, &ei) in idx.iter().enumerate() {
5119 let Some(exp) = l.experts.get(ei) else {
5120 continue;
5121 };
5122 run_expert(
5123 hidden,
5124 exp,
5125 cfg,
5126 w.get(e).copied().unwrap_or(0.0),
5127 pool,
5128 &mut acc,
5129 );
5130 for (o, a) in want.iter_mut().zip(&acc) {
5131 *o += a;
5132 }
5133 }
5134 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
5135 for (o, a) in want.iter_mut().zip(&acc) {
5136 *o += a;
5137 }
5138 let num: f32 = want
5139 .iter()
5140 .zip(out.iter())
5141 .map(|(a, b)| (a - b) * (a - b))
5142 .sum();
5143 let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
5144 eprintln!(
5145 "[dsv4-gpu] слой {li}: расхождение {:.3e} | |CPU|={:.5} |GPU|={:.5} | экспертов {}",
5146 (num / den).sqrt(),
5147 den.sqrt(),
5148 out.iter().map(|x| x * x).sum::<f32>().sqrt(),
5149 jobs.len()
5150 );
5151 }
5152 return;
5153 }
5154 }
5155 }
5156 }
5157 out.fill(0.0);
5158 let jobs: Vec<(Option<usize>, f32)> = idx
5165 .iter()
5166 .enumerate()
5167 .map(|(e, &ei)| (Some(ei), w.get(e).copied().unwrap_or(0.0)))
5168 .chain(std::iter::once((None, 1.0)))
5169 .collect();
5170 match pool {
5171 Some(p) if jobs.len() > 1 => {
5172 let results: Vec<std::sync::Mutex<Vec<f32>>> =
5173 jobs.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
5174 let (jobs_ref, results_ref) = (&jobs, &results);
5175 p.run_rows(jobs.len(), &move |cs, ce| {
5176 for i in cs..ce {
5177 let (ei, wt) = jobs_ref[i];
5178 let exp = match ei {
5179 Some(ei) => match l.experts.get(ei) {
5180 Some(x) => x,
5181 None => continue,
5182 },
5183 None => &l.shared,
5184 };
5185 let mut a = vec![0.0f32; cfg.dim];
5186 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, wt, None, &mut a));
5187 *results_ref[i].lock().unwrap() = a;
5188 }
5189 });
5190 for r in &results {
5191 let a = r.lock().unwrap();
5192 for (o, v) in out.iter_mut().zip(a.iter()) {
5193 *o += v;
5194 }
5195 }
5196 }
5197 _ => {
5198 let mut acc = vec![0.0f32; cfg.dim];
5199 for &(ei, wt) in &jobs {
5200 let exp = match ei {
5201 Some(ei) => match l.experts.get(ei) {
5202 Some(x) => x,
5203 None => continue,
5204 },
5205 None => &l.shared,
5206 };
5207 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, wt, pool, &mut acc));
5208 for (o, a) in out.iter_mut().zip(&acc) {
5209 *o += a;
5210 }
5211 }
5212 }
5213 }
5214}
5215
5216fn run_expert(
5219 x: &[f32],
5220 e: &Dsv4Expert,
5221 cfg: &Dsv4Cfg,
5222 weight: f32,
5223 pool: Option<&crate::pool::Pool>,
5224 out: &mut [f32],
5225) {
5226 expert_swiglu(
5227 x,
5228 &|src, dst| e.w1.matvec(src, dst, pool),
5229 &|src, dst| e.w3.matvec(src, dst, pool),
5230 &|src, dst| e.w2.matvec(src, dst, pool),
5231 cfg.moe_inter,
5232 weight,
5233 cfg.swiglu_limit,
5234 out,
5235 );
5236}
5237
5238fn moe_step_block(
5243 xs: &[f32],
5244 b: usize,
5245 l: &Dsv4Layer,
5246 cfg: &Dsv4Cfg,
5247 token_ids: &[u32],
5248 tally_layer: usize,
5249 pool: Option<&crate::pool::Pool>,
5250 out: &mut [f32],
5251) {
5252 let (dim, inter) = (cfg.dim, cfg.moe_inter);
5253 debug_assert_eq!(xs.len(), b * dim);
5254 debug_assert_eq!(out.len(), b * dim);
5255 out.fill(0.0);
5256
5257 let mut logits = vec![0.0f32; b * cfg.n_routed_experts];
5258 l.gate.matmat(xs, b, &mut logits, pool);
5259 let mut picks: Vec<Vec<usize>> = Vec::with_capacity(b);
5260 let mut weights: Vec<Vec<f32>> = Vec::with_capacity(b);
5261 for bi in 0..b {
5262 let mut idx = Vec::new();
5263 let mut wt = Vec::new();
5264 let forced = l.tid2eid.as_ref().map(|tbl| {
5265 hash_route(
5266 tbl,
5267 cfg.vocab,
5268 cfg.top_k,
5269 token_ids.get(bi).copied().unwrap_or(0),
5270 )
5271 });
5272 route(
5273 &logits[bi * cfg.n_routed_experts..(bi + 1) * cfg.n_routed_experts],
5274 l.gate_bias.as_deref(),
5275 cfg.top_k,
5276 cfg.route_scale,
5277 forced.as_deref(),
5278 l.mask.as_deref(),
5279 &mut idx,
5280 &mut wt,
5281 );
5282 PICK_TALLY.with(|t| {
5283 if let Some(v) = t.borrow_mut().as_mut() {
5284 v.push((tally_layer, idx.clone()));
5285 }
5286 });
5287 picks.push(idx);
5288 weights.push(wt);
5289 }
5290
5291 let mut routed = vec![0.0f32; b * cfg.top_k * dim];
5294 let mut active: Vec<(usize, Vec<(usize, usize, f32)>)> = Vec::new();
5297 for ei in 0..l.experts.len() {
5298 let mut jobs = Vec::new();
5299 for bi in 0..b {
5300 for (slot, &picked) in picks[bi].iter().enumerate() {
5301 if picked == ei {
5302 jobs.push((bi, slot, weights[bi][slot]));
5303 }
5304 }
5305 }
5306 if !jobs.is_empty() {
5307 active.push((ei, jobs));
5308 }
5309 }
5310 let expert_fwd = |ei: usize, jobs: &[(usize, usize, f32)], inner: Option<&crate::pool::Pool>| -> Vec<f32> {
5313 let e = &l.experts[ei];
5314 let n = jobs.len();
5315 let mut xj = vec![0.0f32; n * dim];
5316 for (j, &(bi, _, _)) in jobs.iter().enumerate() {
5317 xj[j * dim..(j + 1) * dim].copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
5318 }
5319 let mut gate = vec![0.0f32; n * inter];
5320 let mut up = vec![0.0f32; n * inter];
5321 e.w1.matmat(&xj, n, &mut gate, inner);
5322 e.w3.matmat(&xj, n, &mut up, inner);
5323 for (j, &(_, _, wt)) in jobs.iter().enumerate() {
5324 let (gj, uj) = (
5325 &mut gate[j * inter..(j + 1) * inter],
5326 &mut up[j * inter..(j + 1) * inter],
5327 );
5328 if cfg.swiglu_limit > 0.0 {
5329 for u in uj.iter_mut() {
5330 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5331 }
5332 for g in gj.iter_mut() {
5333 *g = g.min(cfg.swiglu_limit);
5334 }
5335 }
5336 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5337 *g = (*g / (1.0 + (-*g).exp())) * u * wt;
5338 }
5339 }
5340 let mut down = vec![0.0f32; n * dim];
5341 e.w2.matmat(&gate, n, &mut down, inner);
5342 down
5343 };
5344 match pool {
5354 Some(p) if active.len() > 1 => {
5355 let results: Vec<std::sync::Mutex<Vec<f32>>> =
5356 active.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
5357 let active_ref = &active;
5358 let results_ref = &results;
5359 let fwd = &expert_fwd;
5360 p.run_rows(active_ref.len(), &move |s, e| {
5361 for i in s..e {
5362 let (ei, jobs) = &active_ref[i];
5363 let d = crate::gpu::cpu_scope(|| fwd(*ei, jobs, None));
5364 *results_ref[i].lock().unwrap() = d;
5365 }
5366 });
5367 for (i, (_, jobs)) in active.iter().enumerate() {
5368 let down = results[i].lock().unwrap();
5369 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5370 routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
5371 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5372 }
5373 }
5374 }
5375 _ => {
5376 for (ei, jobs) in &active {
5377 let down = expert_fwd(*ei, jobs, pool);
5378 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5379 routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
5380 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5381 }
5382 }
5383 }
5384 }
5385
5386 let mut sg = vec![0.0f32; b * inter];
5389 let mut su = vec![0.0f32; b * inter];
5390 l.shared.w1.matmat(xs, b, &mut sg, pool);
5391 l.shared.w3.matmat(xs, b, &mut su, pool);
5392 for bi in 0..b {
5393 let (gj, uj) = (
5394 &mut sg[bi * inter..(bi + 1) * inter],
5395 &mut su[bi * inter..(bi + 1) * inter],
5396 );
5397 if cfg.swiglu_limit > 0.0 {
5398 for u in uj.iter_mut() {
5399 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5400 }
5401 for g in gj.iter_mut() {
5402 *g = g.min(cfg.swiglu_limit);
5403 }
5404 }
5405 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5406 *g = (*g / (1.0 + (-*g).exp())) * u;
5407 }
5408 }
5409 let mut shared = vec![0.0f32; b * dim];
5410 l.shared.w2.matmat(&sg, b, &mut shared, pool);
5411
5412 for bi in 0..b {
5413 let dst = &mut out[bi * dim..(bi + 1) * dim];
5414 for slot in 0..picks[bi].len() {
5415 let src = &routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim];
5416 for (o, &v) in dst.iter_mut().zip(src) {
5417 *o += v;
5418 }
5419 }
5420 for (o, &v) in dst.iter_mut().zip(&shared[bi * dim..(bi + 1) * dim]) {
5421 *o += v;
5422 }
5423 }
5424}
5425
5426fn cold_step_block(
5431 xs: &[f32],
5432 b: usize,
5433 l: &Dsv4Layer,
5434 cfg: &Dsv4Cfg,
5435 cold: &[Vec<(usize, f32)>],
5436 pool: Option<&crate::pool::Pool>,
5437 out: &mut [f32],
5438) {
5439 let (dim, inter) = (cfg.dim, cfg.moe_inter);
5440 debug_assert_eq!(xs.len(), b * dim);
5441 debug_assert_eq!(cold.len(), b);
5442 debug_assert_eq!(out.len(), b * dim);
5443 out.fill(0.0);
5444 let slots = cold.iter().map(Vec::len).max().unwrap_or(0);
5445 if slots == 0 {
5446 return;
5447 }
5448 let grouped = std::env::var("CMF_DSV4_COLD_MATMAT")
5454 .map(|v| v != "0")
5455 .unwrap_or(true);
5456 if !grouped {
5457 let jobs: Vec<(usize, usize, usize, f32)> = cold
5458 .iter()
5459 .enumerate()
5460 .flat_map(|(bi, row)| {
5461 row.iter()
5462 .enumerate()
5463 .map(move |(slot, &(ei, wt))| (bi, slot, ei, wt))
5464 })
5465 .collect();
5466 let results: Vec<std::sync::Mutex<Vec<f32>>> =
5467 jobs.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
5468 match pool {
5469 Some(p) if jobs.len() > 1 => {
5470 let (jobs_ref, results_ref) = (&jobs, &results);
5471 p.run_rows(jobs.len(), &move |s, e| {
5472 for i in s..e {
5473 let (_, _, ei, wt) = jobs_ref[i];
5474 let Some(exp) = l.experts.get(ei) else { continue };
5475 let bi = jobs_ref[i].0;
5476 let mut a = vec![0.0f32; dim];
5477 crate::gpu::cpu_scope(|| {
5478 run_expert(
5479 &xs[bi * dim..(bi + 1) * dim],
5480 exp,
5481 cfg,
5482 wt,
5483 None,
5484 &mut a,
5485 )
5486 });
5487 *results_ref[i].lock().unwrap() = a;
5488 }
5489 });
5490 }
5491 _ => {
5492 for (i, &(bi, _, ei, wt)) in jobs.iter().enumerate() {
5493 let Some(exp) = l.experts.get(ei) else { continue };
5494 let mut a = vec![0.0f32; dim];
5495 crate::gpu::cpu_scope(|| {
5496 run_expert(
5497 &xs[bi * dim..(bi + 1) * dim],
5498 exp,
5499 cfg,
5500 wt,
5501 pool,
5502 &mut a,
5503 )
5504 });
5505 *results[i].lock().unwrap() = a;
5506 }
5507 }
5508 }
5509 for (i, &(bi, _, _, _)) in jobs.iter().enumerate() {
5512 let a = results[i].lock().unwrap();
5513 for (o, &v) in out[bi * dim..(bi + 1) * dim].iter_mut().zip(a.iter()) {
5514 *o += v;
5515 }
5516 }
5517 return;
5518 }
5519 let mut active: Vec<(usize, Vec<(usize, usize, f32)>)> = Vec::new();
5520 for ei in 0..l.experts.len() {
5521 let mut jobs = Vec::new();
5522 for bi in 0..b {
5523 for (slot, &(picked, wt)) in cold[bi].iter().enumerate() {
5524 if picked == ei {
5525 jobs.push((bi, slot, wt));
5526 }
5527 }
5528 }
5529 if !jobs.is_empty() {
5530 active.push((ei, jobs));
5531 }
5532 }
5533 let expert_fwd = |ei: usize,
5534 jobs: &[(usize, usize, f32)],
5535 inner: Option<&crate::pool::Pool>|
5536 -> Vec<f32> {
5537 let e = &l.experts[ei];
5538 let n = jobs.len();
5539 let mut xj = vec![0.0f32; n * dim];
5540 for (j, &(bi, _, _)) in jobs.iter().enumerate() {
5541 xj[j * dim..(j + 1) * dim].copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
5542 }
5543 let mut gate = vec![0.0f32; n * inter];
5544 let mut up = vec![0.0f32; n * inter];
5545 e.w1.matmat(&xj, n, &mut gate, inner);
5546 e.w3.matmat(&xj, n, &mut up, inner);
5547 for (j, &(_, _, wt)) in jobs.iter().enumerate() {
5548 let (gj, uj) = (
5549 &mut gate[j * inter..(j + 1) * inter],
5550 &mut up[j * inter..(j + 1) * inter],
5551 );
5552 if cfg.swiglu_limit > 0.0 {
5553 for u in uj.iter_mut() {
5554 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5555 }
5556 for g in gj.iter_mut() {
5557 *g = g.min(cfg.swiglu_limit);
5558 }
5559 }
5560 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5561 *g = (*g / (1.0 + (-*g).exp())) * u * wt;
5562 }
5563 }
5564 let mut down = vec![0.0f32; n * dim];
5565 e.w2.matmat(&gate, n, &mut down, inner);
5566 down
5567 };
5568 let mut routed = vec![0.0f32; b * slots * dim];
5569 match pool {
5570 Some(p) if active.len() > 1 => {
5571 let results: Vec<std::sync::Mutex<Vec<f32>>> =
5572 active.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
5573 let active_ref = &active;
5574 let results_ref = &results;
5575 let fwd = &expert_fwd;
5576 p.run_rows(active.len(), &move |s, e| {
5577 for i in s..e {
5578 let (ei, jobs) = &active_ref[i];
5579 let d = crate::gpu::cpu_scope(|| fwd(*ei, jobs, None));
5580 *results_ref[i].lock().unwrap() = d;
5581 }
5582 });
5583 for (i, (_, jobs)) in active.iter().enumerate() {
5584 let down = results[i].lock().unwrap();
5585 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5586 routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim]
5587 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5588 }
5589 }
5590 }
5591 _ => {
5592 for (ei, jobs) in &active {
5593 let down = expert_fwd(*ei, jobs, pool);
5594 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5595 routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim]
5596 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5597 }
5598 }
5599 }
5600 }
5601 for bi in 0..b {
5602 let dst = &mut out[bi * dim..(bi + 1) * dim];
5603 for slot in 0..cold[bi].len() {
5604 let src = &routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim];
5605 for (o, &v) in dst.iter_mut().zip(src) {
5606 *o += v;
5607 }
5608 }
5609 }
5610}
5611
5612#[cfg(feature = "gpu")]
5617fn refill_route_slots(l: &Dsv4Layer, cfg: &Dsv4Cfg, pk: &Pack, picks: &[usize]) {
5618 if picks.is_empty() || pk.route_complete() {
5619 return;
5620 }
5621 let quota = {
5622 static Q: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
5623 *Q.get_or_init(|| {
5624 std::env::var("CMF_DSV4_FETCH_MAX")
5625 .ok()
5626 .and_then(|v| v.parse().ok())
5627 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().0)
5628 })
5629 };
5630 if quota == 0 {
5631 return;
5632 }
5633 let min_seen = {
5634 static M: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
5635 *M.get_or_init(|| {
5636 std::env::var("CMF_DSV4_FETCH_MIN_SEEN")
5637 .ok()
5638 .and_then(|v| v.parse().ok())
5639 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().1)
5640 })
5641 };
5642 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
5643 return;
5644 };
5645 let mut dynv = pk.dynslots.lock().unwrap();
5646 if dynv.owner.is_empty() {
5647 return;
5648 }
5649 dynv.clock += 1;
5650 let clock = dynv.clock;
5651 if clock % 64 == 0 {
5652 for seen in &mut dynv.seen {
5653 *seen >>= 1;
5654 }
5655 }
5656 for &pick in picks {
5657 if pick >= dynv.seen.len() {
5658 continue;
5659 }
5660 dynv.seen[pick] = dynv.seen[pick].saturating_add(1);
5661 let slot = dynv.remap[pick];
5662 if slot != u32::MAX {
5663 dynv.last[slot as usize] = clock;
5664 }
5665 }
5666 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
5667 let mut fetched = 0usize;
5668 for &pick in picks {
5669 if fetched >= quota || pick >= dynv.remap.len() {
5670 break;
5671 }
5672 if dynv.remap[pick] != u32::MAX || dynv.seen[pick] < min_seen {
5673 continue;
5674 }
5675 let victim = (0..dynv.owner.len())
5676 .filter(|&slot| dynv.last[slot] != clock)
5677 .min_by_key(|&slot| dynv.last[slot]);
5678 let Some(victim) = victim else { break };
5679 let Some(exp) = l.experts.get(pick) else {
5680 continue;
5681 };
5682 let tensors = (|| {
5683 Some((
5684 exp.w1.model_idx()?,
5685 exp.w3.model_idx()?,
5686 exp.w2.model_idx()?,
5687 ))
5688 })();
5689 let Some(tensors) = tensors else { continue };
5690 let gu_q2 = exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
5691 if !crate::gpu_wgpu::dsv4_slot_fill(
5692 &model,
5693 pack_first,
5694 victim,
5695 pick,
5696 tensors,
5697 cfg.moe_inter,
5698 cfg.dim,
5699 gu_q2,
5700 ) {
5701 break;
5702 }
5703 let old = dynv.owner[victim] as usize;
5704 if old < dynv.remap.len() {
5705 dynv.remap[old] = u32::MAX;
5706 }
5707 dynv.remap[pick] = victim as u32;
5708 dynv.owner[victim] = pick as u32;
5709 dynv.last[victim] = clock;
5710 dynv.mutated = true;
5711 fetched += 1;
5712 }
5713}
5714
5715fn o_project_block(
5720 attn: &[f32],
5721 b: usize,
5722 wo_a: &crate::qtensor::QTensor,
5723 wo_b: &crate::qtensor::QTensor,
5724 groups: usize,
5725 lora: usize,
5726 pool: Option<&crate::pool::Pool>,
5727 out: &mut [f32],
5728) {
5729 let attn_len = attn.len() / b;
5730 let per_group = attn_len / groups;
5731 let rows = groups * lora;
5732 let mut mid = vec![0.0f32; b * rows];
5733 let mid_addr = crate::pool::SendMut::new(mid.as_mut_ptr());
5734 let run = |start: usize, end: usize| {
5735 let mut wr = vec![0.0f32; wo_a.cols()];
5736 for r in start..end {
5737 wo_a.row_f32(r, &mut wr);
5738 let group = r / lora;
5739 for bi in 0..b {
5740 let x = &attn
5741 [bi * attn_len + group * per_group..bi * attn_len + (group + 1) * per_group];
5742 let v = wr.iter().zip(x).map(|(w, x)| w * x).sum();
5743 unsafe { *mid_addr.at(bi * rows + r) = v };
5744 }
5745 }
5746 };
5747 match pool {
5748 Some(p) if rows >= 256 => p.run_rows(rows, &run),
5749 _ => run(0, rows),
5750 }
5751 wo_b.matmat(&mid, b, out, pool);
5752}
5753
5754fn no_compressed() -> bool {
5760 static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5761 *OFF.get_or_init(|| std::env::var("CMF_DSV4_NO_COMPRESSED").is_ok_and(|v| v != "0"))
5762}
5763
5764fn trace_on() -> bool {
5765 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5766 *ON.get_or_init(|| std::env::var("CMF_DSV4_TRACE").is_ok_and(|v| v != "0"))
5767}
5768
5769fn rms_of(v: &[f32]) -> f32 {
5770 if v.is_empty() {
5771 return 0.0;
5772 }
5773 (v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32).sqrt()
5774}
5775
5776#[cfg(feature = "gpu")]
5777fn verify_fp_on(pos: usize) -> bool {
5778 static POS: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
5779 let want = *POS.get_or_init(|| {
5780 std::env::var("CMF_DSV4_FP_POS")
5781 .ok()
5782 .and_then(|v| v.parse().ok())
5783 });
5784 want == Some(pos)
5785}
5786
5787#[cfg(feature = "gpu")]
5788fn verify_fp(tag: &str, pos: usize, li: usize, state: &[f32]) {
5789 if !verify_fp_on(pos) {
5790 return;
5791 }
5792 let mut fp = 0xcbf29ce484222325u64;
5793 for &x in state {
5794 fp ^= x.to_bits() as u64;
5795 fp = fp.wrapping_mul(0x100000001b3);
5796 }
5797 eprintln!(
5798 "[dsv4-fp] {tag} pos={pos} li={li} fp={fp:016x} rms={:.7} head={:?}",
5799 rms_of(state),
5800 &state[..4.min(state.len())]
5801 );
5802}
5803
5804thread_local! {
5810 static BODY: std::cell::RefCell<Vec<String>> = const { std::cell::RefCell::new(Vec::new()) };
5812 static PICKED: std::cell::RefCell<Vec<Vec<usize>>> =
5816 const { std::cell::RefCell::new(Vec::new()) };
5817 static PICK_TALLY: std::cell::RefCell<Option<Vec<(usize, Vec<usize>)>>> =
5819 const { std::cell::RefCell::new(None) };
5820}
5821
5822pub fn pick_tally_arm() {
5824 PICK_TALLY.with(|t| *t.borrow_mut() = Some(Vec::new()));
5825}
5826
5827pub fn pick_tally_take() -> Vec<(usize, Vec<usize>)> {
5829 PICK_TALLY.with(|t| t.borrow_mut().take().unwrap_or_default())
5830}
5831
5832pub fn tally_unique(picks: &[(usize, Vec<usize>)]) -> (usize, usize) {
5835 let mut seen = std::collections::HashSet::new();
5840 let mut total = 0;
5841 for (li, v) in picks {
5842 total += v.len();
5843 for &e in v {
5844 seen.insert((*li, e));
5845 }
5846 }
5847 (seen.len(), total)
5848}
5849
5850fn dump_path() -> Option<&'static str> {
5851 static P: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
5852 P.get_or_init(|| std::env::var("CMF_DSV4_DUMP").ok())
5853 .as_deref()
5854}
5855
5856fn dump_line(json: &str) {
5857 if let Some(p) = dump_path() {
5858 use std::io::Write as _;
5859 if let Ok(mut f) = std::fs::OpenOptions::new()
5860 .create(true)
5861 .append(true)
5862 .open(p)
5863 {
5864 let _ = writeln!(f, "{json}");
5865 }
5866 }
5867}
5868
5869fn vec_json(v: &[f32]) -> String {
5870 let mut s = String::with_capacity(v.len() * 9);
5871 s.push('[');
5872 for (i, x) in v.iter().enumerate() {
5873 if i > 0 {
5874 s.push(',');
5875 }
5876 s.push_str(&format!("{x:.6e}"));
5877 }
5878 s.push(']');
5879 s
5880}
5881
5882#[allow(clippy::too_many_arguments)]
5890#[allow(clippy::too_many_arguments)]
5900fn batch_prefill() -> usize {
5904 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
5905 *N.get_or_init(|| {
5906 std::env::var("CMF_DSV4_BATCH")
5907 .ok()
5908 .and_then(|v| v.parse::<usize>().ok())
5909 .filter(|&n| (1..=32).contains(&n))
5910 .unwrap_or(8)
5917 })
5918}
5919
5920#[allow(clippy::too_many_arguments)]
5933fn forward_chunk_batched(
5934 g: &Dsv4Globals,
5935 layers: &[Dsv4Layer],
5936 cfg: &Dsv4Cfg,
5937 st: &mut Dsv4State,
5938 ids: &[u32],
5939 pos0: usize,
5940 inv_freq: &[f32],
5941 pool: Option<&crate::pool::Pool>,
5942 logits: &mut Vec<f32>,
5943 want_logits: bool,
5944) -> bool {
5945 #[cfg(not(feature = "gpu"))]
5946 {
5947 let _ = (
5948 g,
5949 layers,
5950 cfg,
5951 st,
5952 ids,
5953 pos0,
5954 inv_freq,
5955 pool,
5956 logits,
5957 want_logits,
5958 );
5959 false
5960 }
5961 #[cfg(feature = "gpu")]
5962 {
5963 let b = ids.len();
5964 let gpu_end = st
5969 .dev_set
5970 .iter()
5971 .enumerate()
5972 .position(|(li, &on)| {
5973 !on || pack_for(&layers[li], cfg, li)
5974 .is_none_or(|p| !p.route_complete())
5975 })
5976 .unwrap_or(st.dev_set.len());
5977 let why = if b < 2 {
5978 "токенов меньше двух"
5979 } else if !chain_enabled() {
5980 "цепочка выключена"
5981 } else if !st.dev_owned {
5982 "карта ещё не владеет состоянием"
5983 } else if st.dev_set.len() != layers.len() {
5984 "набор слоёв ещё не зафиксирован"
5985 } else if st.dev_set[gpu_end.min(st.dev_set.len())..]
5986 .iter()
5987 .enumerate()
5988 .any(|(i, &on)| on && !st.partial_set.get(gpu_end + i).copied().unwrap_or(false))
5989 {
5990 "слои на карте не образуют префикс"
5991 } else {
5992 ""
5993 };
5994 if !why.is_empty() {
5995 static SAID: std::sync::Once = std::sync::Once::new();
5996 SAID.call_once(|| tracing::warn!("dsv4: пакет отказал — {why}"));
5997 return false;
5998 }
5999 let (hc, dim) = (cfg.hc_mult, cfg.dim);
6000 let mut emb = vec![0.0f32; dim];
6001 let mut states = vec![0.0f32; b * hc * dim];
6002 for (t, &id) in ids.iter().enumerate() {
6003 let mut state = vec![0.0f32; hc * dim];
6004 g.embed.row_f32(id as usize, &mut emb);
6005 for j in 0..hc {
6006 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
6007 }
6008 let (folded, post0, comb0) = hc_fold_norm(
6009 &state,
6010 &layers[0].hc_attn_fn,
6011 &layers[0].hc_attn_scale,
6012 &layers[0].hc_attn_base,
6013 &layers[0].attn_norm,
6014 cfg,
6015 pool,
6016 );
6017 let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
6018 layers[0].wq_a.matvec(&folded, &mut qn0, pool);
6019 rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
6020 if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
6021 || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
6022 || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
6023 || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
6024 {
6025 return false;
6026 }
6027 states[t * hc * dim..(t + 1) * hc * dim].copy_from_slice(&state);
6028 }
6029 let run: Vec<usize> = (0..gpu_end).collect();
6030 let mut folded = Vec::new();
6031 st.pos = pos0;
6032 if gpu_end > 0 {
6033 if !dsv4_chain_run(
6034 layers,
6035 &run,
6036 cfg,
6037 g,
6038 st,
6039 *ids.last().unwrap(),
6040 &mut folded,
6041 Some(&mut states),
6042 b,
6043 ids,
6044 true,
6045 pool,
6046 ) {
6047 return false;
6048 }
6049 }
6050 let mut scratch = HcScratch::new(cfg);
6056 host_tail_walk_batch(
6057 g,
6058 layers,
6059 cfg,
6060 st,
6061 gpu_end,
6062 &mut states,
6063 ids,
6064 pos0,
6065 b,
6066 inv_freq,
6067 &mut scratch,
6068 pool,
6069 None,
6070 );
6071 st.pos = pos0 + b;
6072 {
6077 static SAID: std::sync::Once = std::sync::Once::new();
6078 SAID.call_once(|| tracing::warn!("dsv4: префилл пакетами по {b}"));
6079 }
6080 if want_logits {
6085 let last = &states[(b - 1) * hc * dim..b * hc * dim];
6086 let mut h = vec![0.0f32; dim];
6087 hc_head_fold(
6088 last,
6089 &g.hc_head_fn,
6090 g.hc_head_scale,
6091 &g.hc_head_base,
6092 cfg,
6093 pool,
6094 &mut h,
6095 );
6096 rms_weighted(&mut h, &g.norm, cfg.norm_eps);
6097 logits.resize(cfg.vocab, 0.0);
6098 g.head.matvec(&h, logits, pool);
6099 } else {
6100 logits.clear();
6101 }
6102 true
6103 }
6104}
6105
6106#[cfg(feature = "gpu")]
6114pub struct Dsv4SpecTxn {
6115 pos0: usize,
6116 batch: usize,
6117 pub(crate) gpu_end: usize,
6118 dev_filled: Vec<usize>,
6119 dev_n_comp: Vec<usize>,
6120 dev_n_ix: Vec<usize>,
6121 host: Vec<(usize, HostLayerSnap)>,
6122 host_steps: Vec<(usize, Vec<HostLayerSnap>)>,
6127 pub states: Vec<f32>,
6131 shadow: Option<crate::gpu_wgpu::Dsv4SpecShadow>,
6132}
6133
6134#[cfg(feature = "gpu")]
6135struct HostLayerSnap {
6136 window: Vec<f32>,
6137 compressed: Vec<f32>,
6138 index_kv: Vec<f32>,
6139 pending_kv: Vec<f32>,
6140 pending_score: Vec<f32>,
6141 prev_kv: Vec<f32>,
6142 prev_score: Vec<f32>,
6143 pending_ix_kv: Vec<f32>,
6144 pending_ix_score: Vec<f32>,
6145 prev_ix_kv: Vec<f32>,
6146 prev_ix_score: Vec<f32>,
6147}
6148
6149#[cfg(feature = "gpu")]
6150fn host_snap(st: &Dsv4State, li: usize) -> HostLayerSnap {
6151 HostLayerSnap {
6152 window: st.window[li].clone(),
6153 compressed: st.compressed[li].clone(),
6154 index_kv: st.index_kv[li].clone(),
6155 pending_kv: st.pending_kv[li].clone(),
6156 pending_score: st.pending_score[li].clone(),
6157 prev_kv: st.prev_kv[li].clone(),
6158 prev_score: st.prev_score[li].clone(),
6159 pending_ix_kv: st.pending_ix_kv[li].clone(),
6160 pending_ix_score: st.pending_ix_score[li].clone(),
6161 prev_ix_kv: st.prev_ix_kv[li].clone(),
6162 prev_ix_score: st.prev_ix_score[li].clone(),
6163 }
6164}
6165
6166#[cfg(feature = "gpu")]
6167fn host_restore(st: &mut Dsv4State, li: usize, s: &HostLayerSnap) {
6168 st.window[li] = s.window.clone();
6169 st.compressed[li] = s.compressed.clone();
6170 st.index_kv[li] = s.index_kv.clone();
6171 st.pending_kv[li] = s.pending_kv.clone();
6172 st.pending_score[li] = s.pending_score.clone();
6173 st.prev_kv[li] = s.prev_kv.clone();
6174 st.prev_score[li] = s.prev_score.clone();
6175 st.pending_ix_kv[li] = s.pending_ix_kv.clone();
6176 st.pending_ix_score[li] = s.pending_ix_score.clone();
6177 st.prev_ix_kv[li] = s.prev_ix_kv.clone();
6178 st.prev_ix_score[li] = s.prev_ix_score.clone();
6179}
6180
6181#[cfg(feature = "gpu")]
6185#[allow(clippy::too_many_arguments)]
6186fn host_tail_walk(
6187 g: &Dsv4Globals,
6188 layers: &[Dsv4Layer],
6189 cfg: &Dsv4Cfg,
6190 st: &mut Dsv4State,
6191 gpu_end: usize,
6192 state: &mut [f32],
6193 token_id: u32,
6194 pos: usize,
6195 inv_freq: &[f32],
6196 scratch: &mut HcScratch,
6197 pool: Option<&crate::pool::Pool>,
6198) {
6199 st.pos = pos;
6200 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
6201 let freqs = if l.compressor.is_some() {
6202 &g.inv_freq_compress
6203 } else {
6204 &g.inv_freq_window
6205 };
6206 let freqs = if freqs.is_empty() {
6207 inv_freq
6208 } else {
6209 freqs.as_slice()
6210 };
6211 hc_block(
6212 state,
6213 &l.hc_attn_fn,
6214 &l.hc_attn_scale,
6215 &l.hc_attn_base,
6216 &l.attn_norm,
6217 cfg,
6218 scratch,
6219 pool,
6220 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
6221 );
6222 hc_block(
6223 state,
6224 &l.hc_ffn_fn,
6225 &l.hc_ffn_scale,
6226 &l.hc_ffn_base,
6227 &l.ffn_norm,
6228 cfg,
6229 scratch,
6230 pool,
6231 |f, o| {
6232 if host_cpu_moe() {
6233 crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
6234 } else {
6235 moe_step(f, l, cfg, token_id, li, pool, o)
6236 }
6237 },
6238 );
6239 dspark_note(li, state, cfg);
6240 }
6241}
6242
6243#[cfg(feature = "gpu")]
6249#[allow(clippy::too_many_arguments)]
6250fn host_tail_walk_batch(
6251 g: &Dsv4Globals,
6252 layers: &[Dsv4Layer],
6253 cfg: &Dsv4Cfg,
6254 st: &mut Dsv4State,
6255 gpu_end: usize,
6256 states: &mut [f32],
6257 ids: &[u32],
6258 pos0: usize,
6259 b: usize,
6260 inv_freq: &[f32],
6261 scratch: &mut HcScratch,
6262 pool: Option<&crate::pool::Pool>,
6263 mut steps: Option<&mut Vec<(usize, Vec<HostLayerSnap>)>>,
6264) {
6265 let (hc, dim) = (cfg.hc_mult, cfg.dim);
6266 let mix_hc = (2 + hc) * hc;
6267 let mut folds = vec![0.0f32; b * dim];
6268 let mut mo = vec![0.0f32; b * dim];
6269 let mut posts = vec![0.0f32; b * hc];
6270 let mut combs = vec![0.0f32; b * hc * hc];
6271 let mut resid = vec![0.0f32; b * hc * dim];
6272 let spec_time = {
6273 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6274 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
6275 };
6276 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
6277 let t_attn = std::time::Instant::now();
6278 let freqs = if l.compressor.is_some() {
6279 &g.inv_freq_compress
6280 } else {
6281 &g.inv_freq_window
6282 };
6283 let freqs = if freqs.is_empty() {
6284 inv_freq
6285 } else {
6286 freqs.as_slice()
6287 };
6288 for t in 0..b {
6289 st.pos = pos0 + t;
6290 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
6291 hc_block(
6292 state,
6293 &l.hc_attn_fn,
6294 &l.hc_attn_scale,
6295 &l.hc_attn_base,
6296 &l.attn_norm,
6297 cfg,
6298 scratch,
6299 pool,
6300 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
6301 );
6302 if let Some(steps) = steps.as_mut() {
6303 match steps.iter_mut().find(|(l, _)| *l == li) {
6304 Some((_, v)) => v.push(host_snap(st, li)),
6305 None => steps.push((li, vec![host_snap(st, li)])),
6306 }
6307 }
6308 }
6309 let t_glue = std::time::Instant::now();
6310 for t in 0..b {
6311 let state = &states[t * hc * dim..(t + 1) * hc * dim];
6312 hc_mixes(
6313 state,
6314 &l.hc_ffn_fn,
6315 mix_hc,
6316 cfg.norm_eps,
6317 pool,
6318 &mut scratch.mixes,
6319 );
6320 hc_split_sinkhorn(
6321 &scratch.mixes,
6322 &l.hc_ffn_scale,
6323 &l.hc_ffn_base,
6324 hc,
6325 cfg.hc_sinkhorn_iters,
6326 cfg.hc_eps,
6327 &mut scratch.pre,
6328 &mut posts[t * hc..(t + 1) * hc],
6329 &mut combs[t * hc * hc..(t + 1) * hc * hc],
6330 );
6331 let fold = &mut folds[t * dim..(t + 1) * dim];
6332 hc_fold(state, &scratch.pre, hc, dim, fold);
6333 let ms = fold.iter().map(|v| v * v).sum::<f32>() / dim as f32;
6334 let inv = 1.0 / (ms + cfg.norm_eps).sqrt();
6335 for (v, w) in fold.iter_mut().zip(&l.ffn_norm) {
6336 *v = *v * inv * w;
6337 }
6338 resid[t * hc * dim..(t + 1) * hc * dim]
6339 .copy_from_slice(&states[t * hc * dim..(t + 1) * hc * dim]);
6340 }
6341 let t_moe = std::time::Instant::now();
6342 let tail_pack = {
6348 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6349 *ON.get_or_init(|| {
6350 std::env::var("CMF_DSV4_TAIL_PACK")
6351 .map(|v| v != "0")
6352 .unwrap_or(true)
6353 })
6354 };
6355 let mut packed_done = false;
6356 if tail_pack && pack_for(l, cfg, li).is_some() {
6357 packed_done = true;
6358 for t in 0..b {
6359 let f = &folds[t * dim..(t + 1) * dim];
6360 let forced = l.tid2eid.as_ref().map(|tbl| {
6361 hash_route(tbl, cfg.vocab, cfg.top_k, ids.get(t).copied().unwrap_or(0))
6362 });
6363 let o = &mut mo[t * dim..(t + 1) * dim];
6364 match moe_frame(f, l, cfg, li, &[], forced.as_deref(), pool, None, None, o) {
6365 Some((cold_sum, n)) => {
6366 if n > 0 {
6367 for (od, cd) in o.iter_mut().zip(cold_sum.iter()) {
6368 *od += cd;
6369 }
6370 }
6371 }
6372 None => {
6373 packed_done = false;
6374 break;
6375 }
6376 }
6377 }
6378 }
6379 if !packed_done {
6380 if host_cpu_moe() {
6381 crate::gpu::cpu_scope(|| moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo));
6382 } else {
6383 moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo);
6384 }
6385 }
6386 let t_exp = std::time::Instant::now();
6387 for t in 0..b {
6388 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
6389 hc_expand(
6390 &mo[t * dim..(t + 1) * dim],
6391 &resid[t * hc * dim..(t + 1) * hc * dim],
6392 &posts[t * hc..(t + 1) * hc],
6393 &combs[t * hc * hc..(t + 1) * hc * hc],
6394 hc,
6395 dim,
6396 state,
6397 );
6398 dspark_note(li, state, cfg);
6399 }
6400 if spec_time {
6401 eprintln!(
6402 "хвост слоя {li}: attn {:.1} мс, клей {:.1}, moe {:.1}, expand {:.1}",
6403 (t_glue - t_attn).as_secs_f64() * 1e3,
6404 (t_moe - t_glue).as_secs_f64() * 1e3,
6405 (t_exp - t_moe).as_secs_f64() * 1e3,
6406 t_exp.elapsed().as_secs_f64() * 1e3,
6407 );
6408 }
6409 }
6410}
6411
6412#[cfg(feature = "gpu")]
6418#[allow(clippy::too_many_arguments)]
6419fn partial_layer_batch(
6420 g: &Dsv4Globals,
6421 layers: &[Dsv4Layer],
6422 cfg: &Dsv4Cfg,
6423 st: &mut Dsv4State,
6424 li: usize,
6425 states: &mut [f32],
6426 ids: &[u32],
6427 pos0: usize,
6428 b: usize,
6429 pool: Option<&crate::pool::Pool>,
6430) -> bool {
6431 let l = &layers[li];
6432 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
6433 if states.len() < b * hc * dim || ids.len() < b {
6434 return false;
6435 }
6436 let Some(pk) = pack_for(l, cfg, li) else {
6437 return false;
6438 };
6439 if pk.route_complete() {
6440 return false;
6441 }
6442 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
6443 return false;
6444 };
6445 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
6446 l.wq_a.model_idx(),
6447 l.wq_b.model_idx(),
6448 l.wo_a.model_idx(),
6449 l.wo_b.model_idx(),
6450 l.wkv.model_idx(),
6451 ) else {
6452 return false;
6453 };
6454 let comp = match &l.compressor {
6455 None => None,
6456 Some(cp) => {
6457 let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
6458 return false;
6459 };
6460 Some((
6461 crate::gpu_wgpu::Dsv4CompW {
6462 wkv: a,
6463 wgate: bx,
6464 norm: &cp.norm,
6465 ape: &cp.ape,
6466 },
6467 crate::gpu_wgpu::Dsv4CompGeom {
6468 width: cp.wkv.rows(),
6469 hidden: dim,
6470 ratio: cp.ratio,
6471 overlap: cp.overlap,
6472 rope_dim: cfg.rope_head_dim,
6473 eps: cfg.norm_eps,
6474 },
6475 ))
6476 }
6477 };
6478 let ix = match &l.indexer {
6479 None => None,
6480 Some(ixr) => {
6481 let cp = &ixr.compressor;
6482 let (Some(a), Some(bx), Some(qb), Some(wp)) = (
6483 cp.wkv.model_idx(),
6484 cp.wgate.model_idx(),
6485 ixr.wq_b.model_idx(),
6486 ixr.weights_proj.model_idx(),
6487 ) else {
6488 return false;
6489 };
6490 let ih = ixr.weights_proj.rows();
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 crate::gpu_wgpu::Dsv4IxW {
6507 wq_b: qb,
6508 weights_proj: wp,
6509 },
6510 crate::gpu_wgpu::Dsv4IxGeom {
6511 ih,
6512 idim: ixr.wq_b.rows() / ih.max(1),
6513 q_lora: cfg.q_lora_rank,
6514 hidden: dim,
6515 rope_dim: cfg.rope_head_dim,
6516 eps: cfg.norm_eps,
6517 top_k: cfg.index_topk,
6518 window: cfg.window,
6519 },
6520 ))
6521 }
6522 };
6523 let ew_c = comp.as_ref().map_or(0, |(_, cg)| {
6524 if cg.overlap { cg.width / 2 } else { cg.width }
6525 });
6526 let ew_i = ix.as_ref().map_or(0, |(_, cg, _, _)| {
6527 if cg.overlap { cg.width / 2 } else { cg.width }
6528 });
6529 let comp_extra = comp
6530 .as_ref()
6531 .map_or(0, |(_, cg)| b.div_ceil(cg.ratio.max(1)));
6532 let need = cfg.window * hd
6533 + (st.dev_n_comp[li] + comp_extra + 1) * ew_c.max(1)
6534 + (b + 1) * hd;
6535 if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
6536 return false;
6537 }
6538 let base = crate::gpu_wgpu::Dsv4Prep {
6539 wkv,
6540 kv_norm: &l.kv_norm,
6541 comp,
6542 ix,
6543 filled: st.dev_filled[li],
6544 window: cfg.window,
6545 n_comp: st.dev_n_comp[li],
6546 n_ix: st.dev_n_ix[li],
6547 comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
6548 ix_dst_off: st.dev_n_ix[li] * ew_i,
6549 idx_cap: cfg.window
6550 + if l.indexer.is_some() {
6551 cfg.index_topk
6552 } else {
6553 st.dev_n_comp[li] + comp_extra + 1
6554 },
6555 };
6556 let mut preps = Vec::with_capacity(b);
6557 for t in 0..b {
6558 let mut p = base.clone();
6559 p.filled = (base.filled + t).min(base.window);
6560 let advanced = |ratio: usize| -> usize {
6561 if ratio == 0 {
6562 0
6563 } else {
6564 (0..t).filter(|k| (pos0 + k + 1) % ratio == 0).count()
6565 }
6566 };
6567 if let Some((_, cg)) = base.comp.as_ref() {
6568 p.n_comp = base.n_comp + advanced(cg.ratio);
6569 p.comp_dst_off = base.comp_dst_off + (p.n_comp - base.n_comp) * ew_c;
6570 }
6571 if let Some((_, cg, _, _)) = base.ix.as_ref() {
6572 p.n_ix = base.n_ix + advanced(cg.ratio);
6573 p.ix_dst_off = base.ix_dst_off + (p.n_ix - base.n_ix) * ew_i;
6574 }
6575 preps.push(p);
6576 }
6577
6578 for t in 0..b {
6582 let state = &states[t * hc * dim..(t + 1) * hc * dim];
6583 let (fold, post, comb) = hc_fold_norm(
6584 state,
6585 &l.hc_attn_fn,
6586 &l.hc_attn_scale,
6587 &l.hc_attn_base,
6588 &l.attn_norm,
6589 cfg,
6590 pool,
6591 );
6592 let mut qn = vec![0.0f32; cfg.q_lora_rank];
6593 l.wq_a.matvec(&fold, &mut qn, pool);
6594 rms_weighted(&mut qn, &l.q_norm, cfg.norm_eps);
6595 if !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, state, &post, &comb, &fold, &qn) {
6596 return false;
6597 }
6598 }
6599 let forced_rows: Vec<Option<Vec<usize>>> = ids
6600 .iter()
6601 .take(b)
6602 .map(|&id| {
6603 l.tid2eid
6604 .as_ref()
6605 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, id))
6606 })
6607 .collect();
6608 let dynv = pk.dynslots.lock().unwrap();
6609 let w = crate::gpu_wgpu::Dsv4LayerW {
6610 attn: crate::gpu_wgpu::Dsv4AttnW {
6611 wq_a,
6612 wq_b,
6613 wo_a,
6614 wo_b,
6615 q_norm: &l.q_norm,
6616 sink: &l.attn_sink,
6617 },
6618 moe: crate::gpu_wgpu::Dsv4MoeW {
6619 router: &[],
6620 experts: &pk.tensors,
6621 logits: &[],
6622 bias: pk.bias.as_deref(),
6623 mask: pk.mask.as_deref(),
6624 forced: None,
6625 remap: Some(&dynv.remap),
6626 global: None,
6627 },
6628 hc_ffn_fn: &l.hc_ffn_fn,
6629 hc_ffn_scale: &l.hc_ffn_scale,
6630 hc_ffn_base: &l.hc_ffn_base,
6631 hc_next_fn: None,
6634 hc_next_scale: &l.hc_attn_scale,
6635 hc_next_base: &l.hc_attn_base,
6636 ffn_norm: &l.ffn_norm,
6637 next_norm: &l.attn_norm,
6638 next_q_norm: &l.q_norm,
6639 next_wq_a: None,
6640 router: &pk.router,
6641 };
6642 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
6643 attn: crate::gpu_wgpu::Dsv4AttnGeom {
6644 dim,
6645 nh: cfg.n_heads,
6646 hd,
6647 rd: cfg.rope_head_dim,
6648 q_lora: cfg.q_lora_rank,
6649 o_lora: cfg.o_lora_rank,
6650 o_groups: cfg.o_groups,
6651 eps: cfg.norm_eps,
6652 scale: (hd as f32).powf(-0.5),
6653 },
6654 moe: crate::gpu_wgpu::Dsv4MoeGeom {
6655 hidden: dim,
6656 inter: cfg.moe_inter,
6657 top_k: cfg.top_k,
6658 route_scale: cfg.route_scale,
6659 swiglu_limit: cfg.swiglu_limit,
6660 gu_q2: l
6661 .experts
6662 .first()
6663 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
6664 },
6665 hc,
6666 hc_eps: cfg.hc_eps,
6667 sinkhorn_iters: cfg.hc_sinkhorn_iters,
6668 };
6669 let freqs = if l.compressor.is_some() {
6670 g.inv_freq_compress.as_slice()
6671 } else {
6672 g.inv_freq_window.as_slice()
6673 };
6674 let Some(mut got) = crate::gpu_wgpu::dsv4_layer_batch_partial(
6675 &model,
6676 &w,
6677 geom,
6678 st.kv_id,
6679 li,
6680 b,
6681 &preps,
6682 Some(&forced_rows),
6683 freqs,
6684 pos0,
6685 ) else {
6686 return false;
6687 };
6688 drop(dynv);
6689 let mut cold_sum = vec![0.0f32; b * dim];
6690 cold_step_block(&got.cold_x, b, l, cfg, &got.cold, pool, &mut cold_sum);
6691 for t in 0..b {
6692 let state = &mut got.states[t * hc * dim..(t + 1) * hc * dim];
6693 let post = &got.posts[t * hc..(t + 1) * hc];
6694 let cold = &cold_sum[t * dim..(t + 1) * dim];
6695 for j in 0..hc {
6696 for d in 0..dim {
6697 state[j * dim + d] += post[j] * cold[d];
6698 }
6699 }
6700 }
6701 states[..b * hc * dim].copy_from_slice(&got.states);
6702 if !crate::gpu_wgpu::dsv4_spec_cap_write_host(li, b, hc * dim, &got.states) {
6703 return false;
6704 }
6705 if let Some(first_route) = got.routed.first() {
6706 refill_route_slots(l, cfg, &pk, first_route);
6707 }
6708 for t in 0..b {
6709 let pos = pos0 + t;
6710 st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
6711 if let Some((_, cg, ..)) = base.ix.as_ref() {
6712 if (pos + 1) % cg.ratio == 0 {
6713 st.dev_n_ix[li] += 1;
6714 }
6715 }
6716 if let Some((_, cg)) = base.comp.as_ref() {
6717 if (pos + 1) % cg.ratio == 0 {
6718 st.dev_n_comp[li] += 1;
6719 note_compressed(st.kv_id, li, st.dev_n_comp[li]);
6720 }
6721 }
6722 }
6723 true
6724}
6725
6726#[cfg(feature = "gpu")]
6735#[allow(clippy::too_many_arguments)]
6736pub fn dsv4_verify_chunk(
6737 g: &Dsv4Globals,
6738 layers: &[Dsv4Layer],
6739 cfg: &Dsv4Cfg,
6740 st: &mut Dsv4State,
6741 ids: &[u32],
6742 pos0: usize,
6743 inv_freq: &[f32],
6744 pool: Option<&crate::pool::Pool>,
6745 cap_targets: &[usize],
6746 argmax_out: &mut Vec<u32>,
6747 logits_out: &mut Vec<f32>,
6748 walked_out: &mut Vec<f32>,
6749) -> Option<Dsv4SpecTxn> {
6750 let b = ids.len();
6751 let complete_end = st
6755 .dev_set
6756 .iter()
6757 .enumerate()
6758 .position(|(li, &on)| {
6759 !on || pack_for(&layers[li], cfg, li)
6760 .is_none_or(|p| !p.route_complete())
6761 })
6762 .unwrap_or(st.dev_set.len());
6763 fn partial_batch_on() -> bool {
6764 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6765 *ON.get_or_init(|| {
6766 std::env::var("CMF_DSV4_PARTIAL_BATCH")
6767 .map(|v| v != "0")
6768 .unwrap_or(true)
6769 })
6770 }
6771 let device_end = if partial_batch_on() {
6772 (complete_end..layers.len())
6773 .take_while(|&li| {
6774 st.dev_set.get(li).copied().unwrap_or(false)
6775 && st.partial_set.get(li).copied().unwrap_or(false)
6776 && pack_for(&layers[li], cfg, li).is_some_and(|p| !p.route_complete())
6777 })
6778 .last()
6779 .map_or(complete_end, |li| li + 1)
6780 } else {
6781 complete_end
6782 };
6783 let full_beyond = st.dev_set[device_end.min(st.dev_set.len())..]
6786 .iter()
6787 .enumerate()
6788 .any(|(i, &on)| on && !st.partial_set.get(device_end + i).copied().unwrap_or(false));
6789 fn host_verify_on() -> bool {
6796 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6797 *ON.get_or_init(|| {
6798 std::env::var("CMF_DSV4_HOST_VERIFY")
6799 .map(|v| v != "0")
6800 .unwrap_or(true)
6805 })
6806 }
6807 if b < 2
6808 || !chain_enabled()
6809 || !st.dev_owned
6810 || st.dev_set.len() != layers.len()
6811 || (device_end == 0 && !host_verify_on())
6812 || full_beyond
6813 {
6814 return None;
6815 }
6816 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
6817 let metas: Vec<(usize, usize, usize, usize)> = (0..device_end)
6819 .map(|li| (li, hd, cfg.window, st.dev_filled[li]))
6820 .collect();
6821 let shadow = crate::gpu_wgpu::dsv4_spec_shadow(st.kv_id, &metas, b)?;
6822 let mut txn = Dsv4SpecTxn {
6823 pos0,
6824 batch: b,
6825 gpu_end: device_end,
6826 dev_filled: st.dev_filled.clone(),
6827 dev_n_comp: st.dev_n_comp.clone(),
6828 dev_n_ix: st.dev_n_ix.clone(),
6829 host: (device_end..layers.len())
6830 .map(|li| (li, host_snap(st, li)))
6831 .collect(),
6832 states: Vec::new(),
6833 host_steps: Vec::new(),
6834 shadow: Some(shadow),
6835 };
6836 let dev_caps: Vec<usize> = cap_targets
6838 .iter()
6839 .copied()
6840 .filter(|&t| t < device_end)
6841 .collect();
6842 crate::gpu_wgpu::dsv4_spec_retain_arm(device_end, &dev_caps);
6843
6844 let mut emb = vec![0.0f32; dim];
6846 let mut states = vec![0.0f32; b * hc * dim];
6847 for (t, &id) in ids.iter().enumerate() {
6848 let mut state = vec![0.0f32; hc * dim];
6849 g.embed.row_f32(id as usize, &mut emb);
6850 for j in 0..hc {
6851 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
6852 }
6853 states[t * hc * dim..(t + 1) * hc * dim].copy_from_slice(&state);
6854 let (folded, post0, comb0) = hc_fold_norm(
6855 &state,
6856 &layers[0].hc_attn_fn,
6857 &layers[0].hc_attn_scale,
6858 &layers[0].hc_attn_base,
6859 &layers[0].attn_norm,
6860 cfg,
6861 pool,
6862 );
6863 let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
6864 layers[0].wq_a.matvec(&folded, &mut qn0, pool);
6865 rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
6866 if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
6867 || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
6868 || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
6869 || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
6870 {
6871 crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
6872 return None;
6873 }
6874 }
6875 let spec_time = {
6876 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6877 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
6878 };
6879 let t0 = std::time::Instant::now();
6880 let mut folded = Vec::new();
6881 st.pos = pos0;
6882 let fp_split = verify_fp_on(pos0)
6886 && std::env::var("CMF_DSV4_FP_SPLIT").is_ok_and(|v| v != "0");
6887 let mut ok = true;
6888 if fp_split {
6889 for li in 0..complete_end {
6890 let one = [li];
6891 ok = dsv4_chain_run(
6892 layers,
6893 &one,
6894 cfg,
6895 g,
6896 st,
6897 *ids.last().unwrap(),
6898 &mut folded,
6899 Some(&mut states),
6900 b,
6901 ids,
6902 li == 0,
6903 pool,
6904 );
6905 if !ok {
6906 break;
6907 }
6908 verify_fp("verify", pos0, li, &states[..hc * dim]);
6909 }
6910 } else if complete_end > 0 {
6911 let run: Vec<usize> = (0..complete_end).collect();
6912 ok = dsv4_chain_run(
6913 layers,
6914 &run,
6915 cfg,
6916 g,
6917 st,
6918 *ids.last().unwrap(),
6919 &mut folded,
6920 Some(&mut states),
6921 b,
6922 ids,
6923 true,
6924 pool,
6925 );
6926 if ok {
6927 verify_fp("verify", pos0, complete_end - 1, &states[..hc * dim]);
6928 }
6929 }
6930 if ok {
6931 for li in complete_end..device_end {
6932 if !partial_layer_batch(g, layers, cfg, st, li, &mut states, ids, pos0, b, pool) {
6933 ok = false;
6934 break;
6935 }
6936 verify_fp("verify", pos0, li, &states[..hc * dim]);
6937 }
6938 }
6939 crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
6940 if !ok {
6941 if let Some(sh) = txn.shadow.take() {
6944 let _ = crate::gpu_wgpu::dsv4_spec_restore(&sh);
6945 }
6946 st.dev_filled = txn.dev_filled;
6947 st.dev_n_comp = txn.dev_n_comp;
6948 st.dev_n_ix = txn.dev_n_ix;
6949 st.pos = pos0;
6950 return None;
6951 }
6952 txn.states = states.clone();
6953 let t_chain = t0.elapsed();
6954 if std::env::var("CMF_DSV4_FOLD_DBG").is_ok() {
6955 for li in 0..device_end {
6959 let Some(ixr) = &layers[li].indexer else {
6960 continue;
6961 };
6962 let ratio = ixr.compressor.ratio;
6963 for t in 0..b {
6964 if (pos0 + t + 1) % ratio == 0 {
6965 let ew = {
6966 let w = ixr.compressor.wkv.rows();
6967 if ixr.compressor.overlap { w / 2 } else { w }
6968 };
6969 let idx_new = txn.dev_n_ix[li]
6970 + (0..=t).filter(|k| (pos0 + k + 1) % ratio == 0).count()
6971 - 1;
6972 if let Some(v) =
6973 crate::gpu_wgpu::dsv4_dbg_read_ix(st.kv_id, li, idx_new * ew, ew.min(8))
6974 {
6975 let sum: f32 = v.iter().sum();
6976 eprintln!(
6977 "[fold] li={li} pos={} entry={idx_new} head={:?} sum={sum:.6}",
6978 pos0 + t,
6979 &v[..4.min(v.len())]
6980 );
6981 }
6982 }
6983 }
6984 }
6985 }
6986
6987 let mut scratch = HcScratch::new(cfg);
6989 argmax_out.clear();
6990 logits_out.clear();
6991 logits_out.resize(b * cfg.vocab, 0.0);
6992 let mut head_in = vec![0.0f32; b * dim];
6993 let mut host_steps: Vec<(usize, Vec<HostLayerSnap>)> = Vec::new();
6994 host_tail_walk_batch(
6995 g,
6996 layers,
6997 cfg,
6998 st,
6999 device_end,
7000 &mut states,
7001 ids,
7002 pos0,
7003 b,
7004 inv_freq,
7005 &mut scratch,
7006 pool,
7007 Some(&mut host_steps),
7008 );
7009 txn.host_steps = host_steps;
7010 for t in 0..b {
7011 let state = &states[t * hc * dim..(t + 1) * hc * dim];
7012 let h = &mut head_in[t * dim..(t + 1) * dim];
7013 hc_head_fold(
7014 state,
7015 &g.hc_head_fn,
7016 g.hc_head_scale,
7017 &g.hc_head_base,
7018 cfg,
7019 pool,
7020 h,
7021 );
7022 rms_weighted(h, &g.norm, cfg.norm_eps);
7023 }
7024 let batch_head = std::env::var("CMF_DSV4_SPEC_BATCH_HEAD").is_ok_and(|v| v != "0");
7029 let head_gpu = batch_head && g.head.model_idx().is_some_and(|hi| {
7030 let model = layers[0].experts.first().and_then(|e| e.w1.model_arc());
7031 model.is_some_and(|m| {
7032 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
7033 &m, hi, &head_in, b, cfg.vocab, dim, logits_out,
7034 )
7035 })
7036 });
7037 for t in 0..b {
7038 if !head_gpu {
7039 let h = &head_in[t * dim..(t + 1) * dim];
7040 g.head
7041 .matvec(h, &mut logits_out[t * cfg.vocab..(t + 1) * cfg.vocab], pool);
7042 }
7043 let row = &logits_out[t * cfg.vocab..(t + 1) * cfg.vocab];
7044 let mut best = 0usize;
7045 for v in 1..cfg.vocab {
7046 if row[v] > row[best] {
7047 best = v;
7048 }
7049 }
7050 argmax_out.push(best as u32);
7051 }
7052 walked_out.clear();
7053 walked_out.extend_from_slice(&states);
7054 st.pos = pos0 + b;
7055 if spec_time {
7056 eprintln!(
7057 "verify: тень+сид+цепочка {:.1} мс, хвост+голова {:.1} мс",
7058 t_chain.as_secs_f64() * 1e3,
7059 (t0.elapsed() - t_chain).as_secs_f64() * 1e3,
7060 );
7061 }
7062 Some(txn)
7063}
7064
7065#[cfg(feature = "gpu")]
7073pub fn dsv4_spec_finish(
7074 g: &Dsv4Globals,
7075 layers: &[Dsv4Layer],
7076 cfg: &Dsv4Cfg,
7077 st: &mut Dsv4State,
7078 mut txn: Dsv4SpecTxn,
7079 accepted: usize,
7080 ids: &[u32],
7081 inv_freq: &[f32],
7082 pool: Option<&crate::pool::Pool>,
7083) -> bool {
7084 macro_rules! sfail {
7085 ($($t:tt)*) => {{
7086 if std::env::var("CMF_DSV4_SPEC_DEBUG").is_ok() {
7087 eprintln!("spec_finish: {}", format_args!($($t)*));
7088 }
7089 return false;
7090 }};
7091 }
7092 let b = txn.batch;
7093 let k = accepted.min(b);
7094 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
7095 let win_metas: Vec<(usize, usize, usize, usize)> = (0..txn.gpu_end)
7098 .map(|li| (li, txn.dev_filled[li], cfg.window, hd))
7099 .collect();
7100 if !crate::gpu_wgpu::dsv4_spec_commit_windows(st.kv_id, &win_metas, b, k) {
7101 sfail!("коммит окон");
7102 }
7103 if k == b {
7104 return true;
7107 }
7108 let Some(sh) = txn.shadow.take() else {
7110 sfail!("нет тени")
7111 };
7112 if !crate::gpu_wgpu::dsv4_spec_restore(&sh) {
7113 sfail!("restore");
7114 }
7115 let Some(model) = layers[0].experts.first().and_then(|e| e.w1.model_arc()) else {
7116 sfail!("нет модели");
7117 };
7118 let mut plan: Vec<(usize, crate::gpu_wgpu::Dsv4Prep)> = Vec::new();
7119 let mut freqs_own: Vec<&[f32]> = Vec::new();
7120 for li in 0..txn.gpu_end {
7121 let l = &layers[li];
7122 let Some(wkv) = l.wkv.model_idx() else {
7123 sfail!("wkv слоя {li}")
7124 };
7125 let comp = match &l.compressor {
7126 None => None,
7127 Some(cp) => {
7128 let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
7129 sfail!("компрессор слоя {li}");
7130 };
7131 Some((
7132 crate::gpu_wgpu::Dsv4CompW {
7133 wkv: a,
7134 wgate: bx,
7135 norm: &cp.norm,
7136 ape: &cp.ape,
7137 },
7138 crate::gpu_wgpu::Dsv4CompGeom {
7139 width: cp.wkv.rows(),
7140 hidden: dim,
7141 ratio: cp.ratio,
7142 overlap: cp.overlap,
7143 rope_dim: cfg.rope_head_dim,
7144 eps: cfg.norm_eps,
7145 },
7146 ))
7147 }
7148 };
7149 let ix = match &l.indexer {
7150 None => None,
7151 Some(ixr) => {
7152 let cp = &ixr.compressor;
7153 let (Some(a), Some(bx), Some(qb), Some(wp)) = (
7154 cp.wkv.model_idx(),
7155 cp.wgate.model_idx(),
7156 ixr.wq_b.model_idx(),
7157 ixr.weights_proj.model_idx(),
7158 ) else {
7159 sfail!("индексер слоя {li}");
7160 };
7161 let ih = ixr.weights_proj.rows();
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 crate::gpu_wgpu::Dsv4IxW {
7178 wq_b: qb,
7179 weights_proj: wp,
7180 },
7181 crate::gpu_wgpu::Dsv4IxGeom {
7182 ih,
7183 idim: ixr.wq_b.rows() / ih.max(1),
7184 q_lora: cfg.q_lora_rank,
7185 hidden: dim,
7186 rope_dim: cfg.rope_head_dim,
7187 eps: cfg.norm_eps,
7188 top_k: cfg.index_topk,
7189 window: cfg.window,
7190 },
7191 ))
7192 }
7193 };
7194 let ew_c = comp.as_ref().map_or(
7195 0,
7196 |(_, cg)| {
7197 if cg.overlap { cg.width / 2 } else { cg.width }
7198 },
7199 );
7200 let ew_i = ix.as_ref().map_or(
7201 0,
7202 |(_, cg, _, _)| {
7203 if cg.overlap { cg.width / 2 } else { cg.width }
7204 },
7205 );
7206 let prep = crate::gpu_wgpu::Dsv4Prep {
7207 wkv,
7208 kv_norm: &l.kv_norm,
7209 comp,
7210 ix,
7211 filled: txn.dev_filled[li],
7212 window: cfg.window,
7213 n_comp: txn.dev_n_comp[li],
7214 n_ix: txn.dev_n_ix[li],
7215 comp_dst_off: cfg.window * hd + txn.dev_n_comp[li] * ew_c,
7216 ix_dst_off: txn.dev_n_ix[li] * ew_i,
7217 idx_cap: cfg.window
7218 + if l.indexer.is_some() {
7219 cfg.index_topk
7220 } else {
7221 0
7222 },
7223 };
7224 let fr = if l.compressor.is_some() {
7225 g.inv_freq_compress.as_slice()
7226 } else {
7227 g.inv_freq_window.as_slice()
7228 };
7229 freqs_own.push(if fr.is_empty() { inv_freq } else { fr });
7230 plan.push((li, prep));
7231 }
7232 if !crate::gpu_wgpu::dsv4_spec_replay(
7233 &model,
7234 &plan,
7235 st.kv_id,
7236 txn.pos0,
7237 b,
7238 k,
7239 &freqs_own,
7240 hd,
7241 dim,
7242 cfg.rope_head_dim,
7243 cfg.norm_eps,
7244 true,
7245 ) {
7246 sfail!("replay k={k}");
7247 }
7248 let advanced = |ratio: usize| -> usize {
7250 if ratio == 0 {
7251 return 0;
7252 }
7253 (0..k).filter(|t| (txn.pos0 + t + 1) % ratio == 0).count()
7254 };
7255 for li in 0..txn.gpu_end {
7256 let l = &layers[li];
7257 st.dev_filled[li] = (txn.dev_filled[li] + k).min(cfg.window);
7258 let ac = l.compressor.as_ref().map_or(0, |cp| advanced(cp.ratio));
7259 let ai = l
7260 .indexer
7261 .as_ref()
7262 .map_or(0, |ix| advanced(ix.compressor.ratio));
7263 st.dev_n_comp[li] = txn.dev_n_comp[li] + ac;
7264 st.dev_n_ix[li] = txn.dev_n_ix[li] + ai;
7265 note_compressed(st.kv_id, li, st.dev_n_comp[li]);
7266 }
7267 if k >= 1 && txn.host_steps.iter().all(|(_, v)| v.len() >= k) && !txn.host_steps.is_empty() {
7270 for (li, v) in &txn.host_steps {
7271 host_restore(st, *li, &v[k - 1]);
7272 }
7273 } else {
7274 for (li, snap) in &txn.host {
7275 host_restore(st, *li, snap);
7276 }
7277 let mut scratch = HcScratch::new(cfg);
7278 let mut states = txn.states.clone();
7279 host_tail_walk_batch(
7280 g,
7281 layers,
7282 cfg,
7283 st,
7284 txn.gpu_end,
7285 &mut states[..k * hc * dim],
7286 ids,
7287 txn.pos0,
7288 k,
7289 inv_freq,
7290 &mut scratch,
7291 pool,
7292 None,
7293 );
7294 }
7295 st.pos = txn.pos0 + k;
7296 true
7297}
7298
7299pub fn forward_chunk(
7300 g: &Dsv4Globals,
7301 layers: &[Dsv4Layer],
7302 cfg: &Dsv4Cfg,
7303 st: &mut Dsv4State,
7304 ids: &[u32],
7305 pos0: usize,
7306 inv_freq: &[f32],
7307 pool: Option<&crate::pool::Pool>,
7308 logits: &mut Vec<f32>,
7309 want_logits: bool,
7310) {
7311 let bs = batch_prefill();
7312 if bs > 1 {
7313 let mut i = 0;
7320 if !st.dev_owned && !ids.is_empty() {
7321 st.pos = pos0;
7322 forward_token_inner(
7323 g,
7324 layers,
7325 cfg,
7326 st,
7327 ids[0],
7328 inv_freq,
7329 pool,
7330 logits,
7331 ids.len() == 1,
7332 );
7333 i = 1;
7334 }
7335 while i < ids.len() {
7336 let end = (i + bs).min(ids.len());
7337 st.pos = pos0 + i;
7338 if !forward_chunk_batched(
7339 g,
7340 layers,
7341 cfg,
7342 st,
7343 &ids[i..end],
7344 pos0 + i,
7345 inv_freq,
7346 pool,
7347 logits,
7348 want_logits && end == ids.len(),
7349 ) {
7350 break;
7351 }
7352 i = end;
7353 }
7354 if i == ids.len() {
7355 return;
7356 }
7357 for (k, &id) in ids.iter().enumerate().skip(i) {
7359 st.pos = pos0 + k;
7360 let last = want_logits && k + 1 == ids.len();
7361 forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
7362 }
7363 return;
7364 }
7365 for (i, &id) in ids.iter().enumerate() {
7366 st.pos = pos0 + i;
7367 let last = want_logits && i + 1 == ids.len();
7368 forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
7369 }
7370}
7371
7372pub fn forward_token(
7373 g: &Dsv4Globals,
7374 layers: &[Dsv4Layer],
7375 cfg: &Dsv4Cfg,
7376 st: &mut Dsv4State,
7377 token_id: u32,
7378 inv_freq: &[f32],
7379 pool: Option<&crate::pool::Pool>,
7380 logits: &mut Vec<f32>,
7381) {
7382 forward_token_inner(g, layers, cfg, st, token_id, inv_freq, pool, logits, true);
7383}
7384
7385#[allow(clippy::too_many_arguments)]
7386fn forward_token_inner(
7387 g: &Dsv4Globals,
7388 layers: &[Dsv4Layer],
7389 cfg: &Dsv4Cfg,
7390 st: &mut Dsv4State,
7391 token_id: u32,
7392 inv_freq: &[f32],
7393 pool: Option<&crate::pool::Pool>,
7394 logits: &mut Vec<f32>,
7395 want_logits: bool,
7397) {
7398 let _t_all = prof::on().then(std::time::Instant::now);
7399 let _all_guard = Charge(_t_all, &prof::ALL_NS);
7400 let (hc, dim) = (cfg.hc_mult, cfg.dim);
7401
7402 let mut emb = vec![0.0f32; dim];
7404 g.embed.row_f32(token_id as usize, &mut emb);
7405 let mut state = vec![0.0f32; hc * dim];
7406 for j in 0..hc {
7407 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
7408 }
7409
7410 let mut scratch = HcScratch::new(cfg);
7411 let mut dump: Vec<String> = Vec::new();
7412 if dump_path().is_some() {
7413 dump.push(format!("\"embed\":{}", vec_json(&emb)));
7414 PICKED.with(|p| p.borrow_mut().clear());
7415 BODY.with(|b| b.borrow_mut().clear());
7416 dump.push(",\"layers\":[".into());
7417 }
7418 if trace_on() {
7419 eprintln!(
7420 "[dsv4] tok={token_id} pos={} embed rms={:.5}",
7421 st.pos,
7422 rms_of(&emb)
7423 );
7424 }
7425 #[cfg(feature = "gpu")]
7427 let layer_frames = gpu_layer_enabled()
7428 && dsv4_layer_loop(
7429 &mut state,
7430 layers,
7431 g,
7432 cfg,
7433 st,
7434 token_id,
7435 inv_freq,
7436 pool,
7437 &mut scratch,
7438 );
7439 #[cfg(not(feature = "gpu"))]
7440 let layer_frames = false;
7441
7442 #[cfg(feature = "gpu")]
7449 let hc_dev = hc_on_device()
7450 && !layer_frames
7451 && gpu_attn_enabled()
7452 && gpu_moe2_enabled()
7453 && dump_path().is_none();
7454 #[cfg(not(feature = "gpu"))]
7455 let hc_dev = false;
7456 #[cfg(feature = "gpu")]
7463 let two_frame_done = hc_dev
7464 && dsv4_two_frame_loop(
7465 &mut state,
7466 layers,
7467 g,
7468 cfg,
7469 st,
7470 token_id,
7471 inv_freq,
7472 pool,
7473 &mut scratch,
7474 );
7475 #[cfg(not(feature = "gpu"))]
7476 let two_frame_done = false;
7477 if !two_frame_done {
7478 for (li, l) in layers.iter().enumerate() {
7479 if layer_frames {
7480 break;
7481 }
7482 hc_block(
7484 &mut state,
7485 &l.hc_attn_fn,
7486 &l.hc_attn_scale,
7487 &l.hc_attn_base,
7488 &l.attn_norm,
7489 cfg,
7490 &mut scratch,
7491 pool,
7492 |folded, out| {
7493 if dump_path().is_some() {
7494 BODY.with(|b| b.borrow_mut().push(vec_json(folded)));
7497 }
7498 let freqs = if l.compressor.is_some() {
7500 &g.inv_freq_compress
7501 } else {
7502 &g.inv_freq_window
7503 };
7504 let freqs = if freqs.is_empty() {
7505 inv_freq
7506 } else {
7507 freqs.as_slice()
7508 };
7509 attention_step(folded, l, cfg, st, li, freqs, pool, None, out);
7510 if dump_path().is_some() {
7511 BODY.with(|b| b.borrow_mut().push(vec_json(out)));
7512 }
7513 },
7514 );
7515 if dump_path().is_some() {
7516 dump.push(format!(
7519 "{}{}",
7520 if li == 0 { "" } else { "," },
7521 vec_json(&state)
7522 ));
7523 }
7524 let _t_hc2 = prof::on().then(std::time::Instant::now);
7526 hc_block(
7527 &mut state,
7528 &l.hc_ffn_fn,
7529 &l.hc_ffn_scale,
7530 &l.hc_ffn_base,
7531 &l.ffn_norm,
7532 cfg,
7533 &mut scratch,
7534 pool,
7535 |folded, out| moe_step(folded, l, cfg, token_id, li, pool, out),
7536 );
7537 if let Some(t) = _t_hc2 {
7538 prof::HC_NS.fetch_add(
7541 t.elapsed().as_nanos() as u64,
7542 std::sync::atomic::Ordering::Relaxed,
7543 );
7544 }
7545 if dump_path().is_some() {
7546 dump.push(format!(",{}", vec_json(&state)));
7547 }
7548 if trace_on() && (st.pos % 64 == 0 || st.pos == 199) {
7549 eprintln!(
7550 "[dsv4] кеши слоя {li}: окно={} сжатых={} индекс={} (ratio={:?})",
7551 st.window[li].len() / cfg.head_dim.max(1),
7552 st.compressed[li].len() / cfg.head_dim.max(1),
7553 st.index_kv[li].len().max(1) / 128,
7554 l.compressor.as_ref().map(|c| (c.ratio, c.overlap)),
7555 );
7556 }
7557 if trace_on() {
7558 let bad = state.iter().filter(|v| !v.is_finite()).count();
7559 eprintln!(
7560 "[dsv4] layer {li:>2}: rms={:.5}{}",
7561 rms_of(&state),
7562 if bad > 0 {
7563 format!(" NON-FINITE x{bad}")
7564 } else {
7565 String::new()
7566 }
7567 );
7568 }
7569 dspark_note(li, &state, cfg);
7570 }
7571 }
7572 st.pos += 1;
7573
7574 let mut h = vec![0.0f32; dim];
7576 hc_head_fold(
7577 &state,
7578 &g.hc_head_fn,
7579 g.hc_head_scale,
7580 &g.hc_head_base,
7581 cfg,
7582 pool,
7583 &mut h,
7584 );
7585 if !want_logits {
7586 logits.clear();
7587 return;
7588 }
7589 let _t_head = prof::on().then(std::time::Instant::now);
7590 rms_weighted(&mut h, &g.norm, cfg.norm_eps);
7591 logits.clear();
7592 logits.resize(g.head.rows(), 0.0);
7593 g.head.matvec(&h, logits, pool);
7594 if let Some(t) = _t_head {
7595 prof::HEAD_NS.fetch_add(
7596 t.elapsed().as_nanos() as u64,
7597 std::sync::atomic::Ordering::Relaxed,
7598 );
7599 }
7600 if dump_path().is_some() {
7601 dump.push("]".into());
7602 let picked = PICKED.with(|p| {
7603 p.borrow()
7604 .iter()
7605 .map(|v| {
7606 format!(
7607 "[{}]",
7608 v.iter()
7609 .map(|e| e.to_string())
7610 .collect::<Vec<_>>()
7611 .join(",")
7612 )
7613 })
7614 .collect::<Vec<_>>()
7615 .join(",")
7616 });
7617 dump.push(format!(",\"experts\":[{picked}]"));
7618 let body = BODY.with(|b| b.borrow().join(","));
7619 dump.push(format!(",\"attn_io\":[{body}]"));
7620 dump_line(&format!(
7621 "{{\"tok\":{token_id},\"pos\":{},{},\"head\":{},\"logits\":{}}}",
7622 st.pos - 1,
7623 dump.join(""),
7624 vec_json(&h),
7625 vec_json(logits)
7626 ));
7627 }
7628 if trace_on() {
7629 let (mut top, mut best) = (0usize, f32::NEG_INFINITY);
7630 for (i, &v) in logits.iter().enumerate() {
7631 if v > best {
7632 best = v;
7633 top = i;
7634 }
7635 }
7636 let lo = logits.iter().cloned().fold(f32::MAX, f32::min);
7637 eprintln!(
7638 "[dsv4] head: rms={:.5} logits[{}..{:.3}] argmax={top}",
7639 rms_of(&h),
7640 format_args!("{lo:.3}"),
7641 best
7642 );
7643 }
7644}
7645
7646pub fn load(
7653 model: &std::sync::Arc<cortiq_core::CmfModel>,
7654 cfg: &Dsv4Cfg,
7655 n_layers: usize,
7656) -> Result<(Dsv4Globals, Vec<Dsv4Layer>), String> {
7657 let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
7658 crate::qtensor::QTensor::from_model(model, name)
7659 };
7660 let f = |name: &str| -> Result<Vec<f32>, String> {
7664 crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
7665 };
7666
7667 let rope_of = |base: f32, yarn: bool| -> Vec<f32> {
7672 if yarn {
7673 crate::attention::yarn_inv_freq(cfg.rope_head_dim, base, 16.0, 65536, 32.0, 1.0)
7674 } else {
7675 crate::attention::rope_inv_freq(cfg.rope_head_dim, base)
7676 }
7677 };
7678 let globals = Dsv4Globals {
7679 inv_freq_compress: rope_of(160_000.0, true),
7680 inv_freq_window: rope_of(10_000.0, false),
7681 embed: q("model.embed_tokens.weight")?,
7682 norm: f("model.norm.weight")?,
7683 head: q("lm_head.weight")?,
7684 hc_head_fn: f("model.hc_head_fn")?,
7685 hc_head_base: f("model.hc_head_base")?,
7686 hc_head_scale: *f("model.hc_head_scale")?
7687 .first()
7688 .ok_or("dsv4: empty hc_head_scale")?,
7689 };
7690
7691 let mut layers = Vec::with_capacity(n_layers);
7692 for li in 0..n_layers {
7693 layers.push(load_layer(
7694 model,
7695 cfg,
7696 &format!("model.layers.{li}"),
7697 Scheme::Main,
7698 )?);
7699 }
7700 #[cfg(feature = "gpu")]
7706 if crate::gpu_wgpu::host_banks_on() {
7707 let sets: Vec<(usize, Vec<(usize, usize, usize)>, bool)> = layers
7709 .iter()
7710 .filter_map(|l| {
7711 let idx3 = |e: &Dsv4Expert| {
7712 Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
7713 };
7714 let mut v: Vec<_> = l.experts.iter().filter_map(idx3).collect();
7715 let first = v.first()?.0;
7716 v.push(idx3(&l.shared)?);
7717 let gu_q2 = l.experts.first()?.w1.model_dtype()
7718 == Some(cortiq_core::TensorDtype::Q2TiledP);
7719 Some((first, v, gu_q2))
7720 })
7721 .collect();
7722 let m2 = model.clone();
7723 let (inter, dim) = (cfg.moe_inter, cfg.dim);
7724 std::thread::spawn(move || {
7725 for (first, v, gu_q2) in sets {
7726 crate::gpu_wgpu::dsv4_host_bank_build(&m2, first, &v, inter, dim, gu_q2);
7727 }
7728 tracing::info!("host banks: built");
7729 });
7730 }
7731 #[cfg(feature = "gpu")]
7732 {
7733 let masks: Vec<Option<Vec<bool>>> = layers.iter().map(|l| l.mask.clone()).collect();
7734 crate::gpu_wgpu::prefetch_tier(model, &|name: &str| {
7735 let Some(i) = name.find(".experts.") else {
7736 return false;
7737 };
7738 let rest = &name[i + 9..];
7739 let e: usize = match rest[..rest.find('.').unwrap_or(rest.len())].parse() {
7740 Ok(v) => v,
7741 Err(_) => return false,
7742 };
7743 let li: usize = {
7744 let Some(j) = name.find("layers.") else { return false };
7745 let r = &name[j + 7..];
7746 match r[..r.find('.').unwrap_or(r.len())].parse() {
7747 Ok(v) => v,
7748 Err(_) => return false,
7749 }
7750 };
7751 match masks.get(li).and_then(|m| m.as_ref()) {
7752 Some(m) => m.get(e).copied().unwrap_or(false),
7753 None => true,
7754 }
7755 });
7756 }
7757 Ok((globals, layers))
7758}
7759
7760#[derive(Clone, Copy, PartialEq, Eq, Debug)]
7767pub enum Scheme {
7768 Main,
7769 Mtp,
7770}
7771
7772impl Scheme {
7773 fn attn(self) -> &'static str {
7774 match self {
7775 Scheme::Main => "self_attn",
7776 Scheme::Mtp => "attn",
7777 }
7778 }
7779 fn attn_norm(self) -> &'static str {
7780 match self {
7781 Scheme::Main => "input_layernorm.weight",
7782 Scheme::Mtp => "attn_norm.weight",
7783 }
7784 }
7785 fn ffn_norm(self) -> &'static str {
7786 match self {
7787 Scheme::Main => "post_attention_layernorm.weight",
7788 Scheme::Mtp => "ffn_norm.weight",
7789 }
7790 }
7791 fn mlp(self) -> &'static str {
7792 match self {
7793 Scheme::Main => "mlp",
7794 Scheme::Mtp => "ffn",
7795 }
7796 }
7797 fn gate_bias(self) -> &'static str {
7800 match self {
7801 Scheme::Main => "expert_bias",
7802 Scheme::Mtp => "gate.bias",
7803 }
7804 }
7805 fn shared(self) -> &'static str {
7806 match self {
7807 Scheme::Main => "shared_expert",
7808 Scheme::Mtp => "shared_experts",
7809 }
7810 }
7811 fn w(self, i: u8) -> &'static str {
7813 match (self, i) {
7814 (Scheme::Main, 1) => "gate_proj.weight",
7815 (Scheme::Main, 2) => "down_proj.weight",
7816 (Scheme::Main, _) => "up_proj.weight",
7817 (Scheme::Mtp, 1) => "w1.weight",
7818 (Scheme::Mtp, 2) => "w2.weight",
7819 (Scheme::Mtp, _) => "w3.weight",
7820 }
7821 }
7822}
7823
7824pub fn load_layer(
7826 model: &std::sync::Arc<cortiq_core::CmfModel>,
7827 cfg: &Dsv4Cfg,
7828 p: &str,
7829 s: Scheme,
7830) -> Result<Dsv4Layer, String> {
7831 let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
7832 crate::qtensor::QTensor::from_model(model, name)
7833 };
7834 let f = |name: &str| -> Result<Vec<f32>, String> {
7835 crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
7836 };
7837 let opt_f = |name: &str| -> Option<Vec<f32>> { f(name).ok() };
7838 let at = s.attn();
7839 let ml = s.mlp();
7840 {
7841 let scale3 = |name: &str| -> Result<[f32; 3], String> {
7842 let v = f(name)?;
7843 if v.len() < 3 {
7844 return Err(format!("{name}: expected 3 scales, got {}", v.len()));
7845 }
7846 Ok([v[0], v[1], v[2]])
7847 };
7848 let compressor = match q(&format!("{p}.{at}.compressor.wkv.weight")) {
7851 Ok(wkv) => {
7852 let ape = f(&format!("{p}.{at}.compressor.ape"))?;
7853 let width = wkv.rows();
7856 let ratio = (ape.len() / width.max(1)).max(1);
7857 Some(Dsv4Compressor {
7858 wkv,
7859 wgate: q(&format!("{p}.{at}.compressor.wgate.weight"))?,
7860 norm: f(&format!("{p}.{at}.compressor.norm.weight"))?,
7861 ape,
7862 ratio,
7863 overlap: ratio == 4,
7864 })
7865 }
7866 Err(_) => None,
7867 };
7868 let indexer = match q(&format!("{p}.{at}.indexer.wq_b.weight")) {
7869 Ok(wq_b) => {
7870 let ape = f(&format!("{p}.{at}.indexer.compressor.ape"))?;
7871 let cwkv = q(&format!("{p}.{at}.indexer.compressor.wkv.weight"))?;
7872 let width = cwkv.rows();
7873 let ratio = (ape.len() / width.max(1)).max(1);
7874 Some(Dsv4Indexer {
7875 wq_b,
7876 weights_proj: q(&format!("{p}.{at}.indexer.weights_proj.weight"))?,
7877 compressor: Dsv4Compressor {
7878 wkv: cwkv,
7879 wgate: q(&format!("{p}.{at}.indexer.compressor.wgate.weight"))?,
7880 norm: f(&format!("{p}.{at}.indexer.compressor.norm.weight"))?,
7881 ape,
7882 ratio,
7883 overlap: ratio == 4,
7884 },
7885 })
7886 }
7887 Err(_) => None,
7888 };
7889
7890 let mut experts = Vec::with_capacity(cfg.n_routed_experts);
7891 for e in 0..cfg.n_routed_experts {
7892 let ep = format!("{p}.{ml}.experts.{e}");
7893 experts.push(Dsv4Expert {
7894 w1: q(&format!("{ep}.{w}", w = s.w(1)))?,
7895 w2: q(&format!("{ep}.{w}", w = s.w(2)))?,
7896 w3: q(&format!("{ep}.{w}", w = s.w(3)))?,
7897 });
7898 }
7899
7900 Ok(Dsv4Layer {
7901 attn_norm: f(&format!("{p}.{an}", an = s.attn_norm()))?,
7902 ffn_norm: f(&format!("{p}.{fnm}", fnm = s.ffn_norm()))?,
7903 wq_a: q(&format!("{p}.{at}.wq_a.weight"))?,
7904 q_norm: f(&format!("{p}.{at}.q_norm.weight"))?,
7905 wq_b: q(&format!("{p}.{at}.wq_b.weight"))?,
7906 wkv: q(&format!("{p}.{at}.wkv.weight"))?,
7907 kv_norm: f(&format!("{p}.{at}.kv_norm.weight"))?,
7908 wo_a: q(&format!("{p}.{at}.wo_a.weight"))?,
7909 wo_b: q(&format!("{p}.{at}.wo_b.weight"))?,
7910 attn_sink: f(&format!("{p}.{at}.attn_sink"))?,
7911 compressor,
7912 indexer,
7913 hc_attn_fn: f(&format!("{p}.hc_attn_fn"))?,
7914 hc_attn_base: f(&format!("{p}.hc_attn_base"))?,
7915 hc_attn_scale: scale3(&format!("{p}.hc_attn_scale"))?,
7916 hc_ffn_fn: f(&format!("{p}.hc_ffn_fn"))?,
7917 hc_ffn_base: f(&format!("{p}.hc_ffn_base"))?,
7918 hc_ffn_scale: scale3(&format!("{p}.hc_ffn_scale"))?,
7919 gate: q(&format!("{p}.{ml}.gate.weight"))?,
7920 gate_bias: opt_f(&format!("{p}.{ml}.{b}", b = s.gate_bias())),
7923 tid2eid: opt_f(&format!("{p}.{ml}.tid2eid")),
7924 experts,
7925 mask: if model.tensor(&format!("{p}.{ml}.tid2eid")).is_some() {
7926 None
7927 } else {
7928 crate::loader::moe_task_mask(model, &format!("{p}."), cfg.n_routed_experts)
7929 },
7930 shared: Dsv4Expert {
7931 w1: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(1)))?,
7932 w2: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(2)))?,
7933 w3: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(3)))?,
7934 },
7935 })
7936 }
7937}
7938
7939pub struct Dsv4Mtp {
7948 pub layer: Dsv4Layer,
7949 pub main_proj: Option<crate::qtensor::QTensor>,
7953 pub main_norm: Option<Vec<f32>>,
7954 pub norm: Option<Vec<f32>>,
7956 pub hc_head_fn: Option<Vec<f32>>,
7957 pub hc_head_base: Option<Vec<f32>>,
7958 pub hc_head_scale: Option<f32>,
7959 pub confidence: Option<crate::qtensor::QTensor>,
7960 pub markov_w1: Option<crate::qtensor::QTensor>,
7965 pub markov_w2: Option<crate::qtensor::QTensor>,
7966}
7967
7968pub fn load_mtp(
7972 model: &std::sync::Arc<cortiq_core::CmfModel>,
7973 cfg: &Dsv4Cfg,
7974 max_depth: usize,
7975) -> Vec<Dsv4Mtp> {
7976 let f = |name: &str| -> Option<Vec<f32>> {
7977 crate::loader::load_f32(model, name, &crate::loader::Overlay::None).ok()
7978 };
7979 let mut out = Vec::new();
7980 for d in 0..max_depth {
7981 let p = format!("model.mtp.{d}");
7982 if model.tensor(&format!("{p}.attn.wq_a.weight")).is_none() {
7986 break;
7987 }
7988 let layer = match load_layer(model, cfg, &p, Scheme::Mtp) {
7989 Ok(l) => l,
7990 Err(e) => {
7991 eprintln!("MTP {d}: пропущен, {e}");
7992 break;
7993 }
7994 };
7995 out.push(Dsv4Mtp {
7996 layer,
7997 main_proj: crate::qtensor::QTensor::from_model(model, &format!("{p}.main_proj.weight"))
7998 .ok(),
7999 main_norm: f(&format!("{p}.main_norm.weight")),
8000 norm: f(&format!("{p}.norm.weight")),
8001 hc_head_fn: f(&format!("{p}.hc_head_fn")),
8002 hc_head_base: f(&format!("{p}.hc_head_base")),
8003 hc_head_scale: f(&format!("{p}.hc_head_scale")).and_then(|v| v.first().copied()),
8004 confidence: crate::qtensor::QTensor::from_model(
8005 model,
8006 &format!("{p}.confidence_head.proj.weight"),
8007 )
8008 .ok(),
8009 markov_w1: crate::qtensor::QTensor::from_model(
8010 model,
8011 &format!("{p}.markov_head.markov_w1.weight"),
8012 )
8013 .ok(),
8014 markov_w2: crate::qtensor::QTensor::from_model(
8015 model,
8016 &format!("{p}.markov_head.markov_w2.weight"),
8017 )
8018 .ok(),
8019 });
8020 }
8021 dspark_apply_mask(&mut out);
8022 if !out.is_empty() {
8023 let mp = out
8024 .iter()
8025 .find_map(|m| m.main_proj.as_ref())
8026 .map(|t| format!("[{}, {}]", t.rows(), t.cols()))
8027 .unwrap_or_else(|| "нет".into());
8028 eprintln!(
8029 "MTP: {} стади(я/и/й), main_proj {mp}, экспертов {}, \
8030 голова уверенности {}, марков {}",
8031 out.len(),
8032 out[0].layer.experts.len(),
8033 if out.iter().any(|m| m.confidence.is_some()) {
8034 "есть"
8035 } else {
8036 "нет"
8037 },
8038 if out.iter().any(|m| m.markov_w1.is_some()) {
8039 "есть"
8040 } else {
8041 "нет"
8042 },
8043 );
8044 }
8045 out
8046}
8047
8048#[cfg(test)]
8049mod tests {
8050 use super::*;
8051
8052 fn toy() -> (Dsv4Globals, Vec<Dsv4Layer>, Dsv4Cfg) {
8057 use crate::qtensor::QTensor;
8058 let cfg = Dsv4Cfg {
8059 dim: 32,
8060 n_heads: 4,
8061 head_dim: 8,
8062 rope_head_dim: 4,
8063 q_lora_rank: 16,
8064 o_lora_rank: 16,
8065 o_groups: 2,
8066 hc_mult: 4,
8067 hc_sinkhorn_iters: 20,
8068 hc_eps: 1e-6,
8069 norm_eps: 1e-6,
8070 n_routed_experts: 8,
8071 top_k: 2,
8072 moe_inter: 16,
8073 route_scale: 1.0,
8074 swiglu_limit: 10.0,
8075 window: 6,
8076 index_topk: 8,
8077 vocab: 24,
8078 };
8079 let w = |n: usize, seed: usize| -> Vec<f32> {
8082 (0..n)
8083 .map(|i| (((i * 7 + seed * 13) % 101) as f32 / 101.0 - 0.5) * 0.3)
8084 .collect()
8085 };
8086 let t = |rows: usize, cols: usize, seed: usize| {
8087 QTensor::from_f32(w(rows * cols, seed), rows, cols)
8088 };
8089 let ones = |n: usize| vec![1.0f32; n];
8090
8091 let (dim, hc) = (cfg.dim, cfg.hc_mult);
8092 let q_width = cfg.n_heads * cfg.head_dim;
8095 let kv_width = cfg.head_dim;
8096 let o_per_group = q_width / cfg.o_groups;
8097 let mut layers = Vec::new();
8098 for li in 0..2 {
8099 let experts: Vec<Dsv4Expert> = (0..cfg.n_routed_experts)
8100 .map(|e| Dsv4Expert {
8101 w1: t(cfg.moe_inter, dim, 40 + e + li * 8),
8102 w2: t(dim, cfg.moe_inter, 60 + e + li * 8),
8103 w3: t(cfg.moe_inter, dim, 80 + e + li * 8),
8104 })
8105 .collect();
8106 layers.push(Dsv4Layer {
8109 attn_norm: ones(dim),
8110 ffn_norm: ones(dim),
8111 wq_a: t(cfg.q_lora_rank, dim, 1 + li),
8112 q_norm: ones(cfg.q_lora_rank),
8113 wq_b: t(q_width, cfg.q_lora_rank, 3 + li),
8114 wkv: t(kv_width, dim, 5 + li),
8115 kv_norm: ones(kv_width),
8116 wo_a: t(cfg.o_groups * cfg.o_lora_rank, o_per_group, 7 + li),
8117 wo_b: t(dim, cfg.o_groups * cfg.o_lora_rank, 9 + li),
8118 attn_sink: vec![0.1; cfg.n_heads],
8119 compressor: if li == 1 {
8122 Some(Dsv4Compressor {
8123 wkv: t(2 * kv_width, dim, 11),
8124 wgate: t(2 * kv_width, dim, 13),
8125 norm: ones(kv_width),
8126 ape: vec![0.01; 4 * 2 * kv_width],
8127 ratio: 4,
8128 overlap: true,
8129 })
8130 } else {
8131 None
8132 },
8133 indexer: if li == 1 {
8134 Some(Dsv4Indexer {
8135 wq_b: t(2 * 16, cfg.q_lora_rank, 41),
8136 weights_proj: t(2, dim, 43),
8137 compressor: Dsv4Compressor {
8138 wkv: t(2 * 16, dim, 45),
8139 wgate: t(2 * 16, dim, 47),
8140 norm: ones(16),
8141 ape: vec![0.01; 4 * 2 * 16],
8142 ratio: 4,
8143 overlap: true,
8144 },
8145 })
8146 } else {
8147 None
8148 },
8149 hc_attn_fn: w((2 + hc) * hc * hc * dim, 15 + li),
8150 hc_attn_base: w((2 + hc) * hc, 17 + li),
8151 hc_attn_scale: [1.0, 1.0, 1.0],
8152 hc_ffn_fn: w((2 + hc) * hc * hc * dim, 19 + li),
8153 hc_ffn_base: w((2 + hc) * hc, 21 + li),
8154 hc_ffn_scale: [1.0, 1.0, 1.0],
8155 gate: t(cfg.n_routed_experts, dim, 23 + li),
8156 gate_bias: if li == 1 {
8157 Some(vec![0.0; cfg.n_routed_experts])
8158 } else {
8159 None
8160 },
8161 tid2eid: if li == 0 {
8162 Some(
8163 (0..cfg.vocab * cfg.top_k)
8164 .map(|i| (i % cfg.n_routed_experts) as f32)
8165 .collect(),
8166 )
8167 } else {
8168 None
8169 },
8170 experts,
8171 mask: None,
8172 shared: Dsv4Expert {
8173 w1: t(cfg.moe_inter, dim, 25 + li),
8174 w2: t(dim, cfg.moe_inter, 27 + li),
8175 w3: t(cfg.moe_inter, dim, 29 + li),
8176 },
8177 });
8178 }
8179 let inv = |base: f32| -> Vec<f32> {
8180 (0..cfg.rope_head_dim / 2)
8181 .map(|i| 1.0 / base.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
8182 .collect()
8183 };
8184 let g = Dsv4Globals {
8185 inv_freq_compress: inv(160000.0),
8186 inv_freq_window: inv(10000.0),
8187 embed: t(cfg.vocab, dim, 31),
8188 norm: ones(dim),
8189 head: t(cfg.vocab, dim, 33),
8190 hc_head_fn: w(hc * hc * dim, 35),
8191 hc_head_base: w(hc, 37),
8192 hc_head_scale: 1.0,
8193 };
8194 (g, layers, cfg)
8195 }
8196
8197 #[test]
8202 fn forward_token_decodes_a_sequence_without_falling_over() {
8203 let (g, layers, cfg) = toy();
8204 let mut st = Dsv4State::new(layers.len());
8205 let inv_freq: Vec<f32> = (0..cfg.rope_head_dim / 2)
8206 .map(|i| 1.0 / 10000f32.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
8207 .collect();
8208 let mut logits = Vec::new();
8209
8210 let mut first: Option<Vec<f32>> = None;
8213 for (step, tok) in [3u32, 7, 1, 9, 4, 2, 8, 5, 6, 0].into_iter().enumerate() {
8214 forward_token(
8215 &g,
8216 &layers,
8217 &cfg,
8218 &mut st,
8219 tok,
8220 &inv_freq,
8221 None,
8222 &mut logits,
8223 );
8224 assert_eq!(logits.len(), cfg.vocab, "step {step}: logit count");
8225 assert!(
8226 logits.iter().all(|v| v.is_finite()),
8227 "step {step}: non-finite logit — {logits:?}"
8228 );
8229 let spread = logits.iter().cloned().fold(f32::MIN, f32::max)
8232 - logits.iter().cloned().fold(f32::MAX, f32::min);
8233 assert!(spread > 1e-6, "step {step}: logits are flat ({spread})");
8234 if step == 0 {
8235 first = Some(logits.clone());
8236 }
8237 assert_eq!(st.pos, step + 1, "position bookkeeping");
8238 }
8239
8240 assert!(!st.window[0].is_empty(), "sliding window never filled");
8243 for (li, w) in st.window.iter().enumerate() {
8245 assert!(
8246 w.len() / cfg.head_dim <= cfg.window,
8247 "layer {li}: window holds {} positions, cap is {}",
8248 w.len() / cfg.head_dim,
8249 cfg.window
8250 );
8251 }
8252 assert!(
8253 !st.compressed[1].is_empty(),
8254 "compressor layer produced no compressed KV in 10 tokens"
8255 );
8256 assert_eq!(
8260 st.compressed[1].len() / cfg.head_dim,
8261 2,
8262 "expected two folds in ten tokens at ratio 4"
8263 );
8264 assert!(
8265 !st.prev_kv[1].is_empty(),
8266 "the overlapping compressor never kept a previous window"
8267 );
8268 for (li, l) in layers.iter().enumerate() {
8273 if l.indexer.is_some() {
8274 assert!(
8275 !st.index_kv[li].is_empty(),
8276 "layer {li} has an indexer but its cache stayed empty"
8277 );
8278 }
8279 }
8280
8281 let mut fresh = Dsv4State::new(layers.len());
8284 let mut relogits = Vec::new();
8285 forward_token(
8286 &g,
8287 &layers,
8288 &cfg,
8289 &mut fresh,
8290 3,
8291 &inv_freq,
8292 None,
8293 &mut relogits,
8294 );
8295 assert_eq!(
8296 relogits,
8297 first.unwrap(),
8298 "the same token from a fresh state must reproduce exactly"
8299 );
8300 }
8301
8302 #[test]
8306 fn swiglu_limit_clamps_up_both_ways_and_gate_only_from_above() {
8307 let inter = 4;
8308 let gate_src = [-50.0f32, 50.0, 1.0, -1.0];
8310 let up_src = [50.0f32, -50.0, 1.0, -1.0];
8311 let limit = 10.0f32;
8312 let mut got = vec![0.0f32; inter];
8313 expert_swiglu(
8314 &[0.0],
8315 &|_, d| d.copy_from_slice(&gate_src),
8316 &|_, d| d.copy_from_slice(&up_src),
8317 &|src, d| d.copy_from_slice(src),
8318 inter,
8319 1.0,
8320 limit,
8321 &mut got,
8322 );
8323 let silu = |g: f32| g / (1.0 + (-g).exp());
8324 let want = [
8326 silu(-50.0) * limit,
8327 silu(limit) * -limit,
8328 silu(1.0) * 1.0,
8329 -silu(-1.0),
8330 ];
8331 for (i, w) in want.iter().enumerate() {
8332 assert!(
8333 (got[i] - w).abs() < 1e-5,
8334 "lane {i}: got {} want {w}",
8335 got[i]
8336 );
8337 }
8338 let mut raw = vec![0.0f32; inter];
8340 expert_swiglu(
8341 &[0.0],
8342 &|_, d| d.copy_from_slice(&gate_src),
8343 &|_, d| d.copy_from_slice(&up_src),
8344 &|src, d| d.copy_from_slice(src),
8345 inter,
8346 1.0,
8347 0.0,
8348 &mut raw,
8349 );
8350 assert!(
8351 (raw[1] - silu(50.0) * -50.0).abs() < 1e-3,
8352 "limit 0 must not clamp"
8353 );
8354 }
8355
8356 #[test]
8361 fn grouped_projection_is_identical_with_and_without_a_pool() {
8362 let (groups, lora, per_group, dim) = (4usize, 128usize, 64usize, 32usize);
8363 let attn: Vec<f32> = (0..groups * per_group)
8364 .map(|i| ((i * 13) as f32 * 0.021).sin())
8365 .collect();
8366 let wo_a: Vec<f32> = (0..groups * lora * per_group)
8367 .map(|i| ((i * 7) as f32 * 0.011).cos())
8368 .collect();
8369 let wo_b: Vec<f32> = (0..dim * groups * lora)
8370 .map(|i| ((i * 5) as f32 * 0.009).sin())
8371 .collect();
8372 let row = |r: usize, x: &[f32], _sc: &mut [f32]| -> f32 {
8373 wo_a[r * per_group..(r + 1) * per_group]
8374 .iter()
8375 .zip(x)
8376 .map(|(a, b)| a * b)
8377 .sum()
8378 };
8379 let project = |mid: &[f32], dst: &mut [f32]| {
8380 for (d, o) in dst.iter_mut().enumerate() {
8381 *o = wo_b[d * mid.len()..(d + 1) * mid.len()]
8382 .iter()
8383 .zip(mid)
8384 .map(|(a, b)| a * b)
8385 .sum();
8386 }
8387 };
8388
8389 let mut serial = vec![0.0f32; dim];
8390 o_project(
8391 &attn,
8392 &row,
8393 per_group,
8394 &project,
8395 groups,
8396 lora,
8397 None,
8398 &mut serial,
8399 );
8400
8401 let pool = crate::pool::Pool::new(4);
8402 let mut pooled = vec![0.0f32; dim];
8403 o_project(
8404 &attn,
8405 &row,
8406 per_group,
8407 &project,
8408 groups,
8409 lora,
8410 Some(&pool),
8411 &mut pooled,
8412 );
8413 assert_eq!(serial, pooled, "the pooled projection diverged");
8414 assert!(
8415 serial.iter().any(|v| v.abs() > 1e-6),
8416 "test data is degenerate"
8417 );
8418 }
8419
8420 #[test]
8421 fn block_grouped_projection_matches_position_walk() {
8422 let (_g, layers, cfg) = toy();
8423 let l = &layers[1];
8424 let b = 5;
8425 let attn_len = cfg.n_heads * cfg.head_dim;
8426 let attn: Vec<f32> = (0..b * attn_len)
8427 .map(|i| ((i * 17) as f32 * 0.013).sin())
8428 .collect();
8429 let mut walked = vec![0.0f32; b * cfg.dim];
8430 for bi in 0..b {
8431 o_project(
8432 &attn[bi * attn_len..(bi + 1) * attn_len],
8433 &|r, x, sc| l.wo_a.row_dot(r, x, sc),
8434 l.wo_a.cols(),
8435 &|mid, dst| l.wo_b.matvec(mid, dst, None),
8436 cfg.o_groups,
8437 cfg.o_lora_rank,
8438 None,
8439 &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
8440 );
8441 }
8442 let mut batched = vec![0.0f32; b * cfg.dim];
8443 o_project_block(
8444 &attn,
8445 b,
8446 &l.wo_a,
8447 &l.wo_b,
8448 cfg.o_groups,
8449 cfg.o_lora_rank,
8450 None,
8451 &mut batched,
8452 );
8453 assert_eq!(batched, walked);
8454 }
8455
8456 #[test]
8457 fn block_moe_matches_position_walk_in_route_order() {
8458 let (_g, layers, cfg) = toy();
8459 let l = &layers[1];
8462 let b = 5;
8463 let xs: Vec<f32> = (0..b * cfg.dim)
8464 .map(|i| ((i * 11) as f32 * 0.019).cos())
8465 .collect();
8466 let ids = [1u32, 2, 3, 4, 5];
8467 let mut walked = vec![0.0f32; b * cfg.dim];
8468 for bi in 0..b {
8469 moe_step(
8470 &xs[bi * cfg.dim..(bi + 1) * cfg.dim],
8471 l,
8472 &cfg,
8473 ids[bi],
8474 1,
8475 None,
8476 &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
8477 );
8478 }
8479 let mut batched = vec![0.0f32; b * cfg.dim];
8480 moe_step_block(&xs, b, l, &cfg, &ids, 1, None, &mut batched);
8481 assert_eq!(batched, walked);
8482 }
8483
8484 #[test]
8490 fn overlapping_compressor_folds_both_windows() {
8491 let (ratio, d) = (2usize, 3usize);
8492 let cur_kv: Vec<f32> = vec![
8495 1.0, 1.0, 1.0, 10.0, 20.0, 30.0, 2.0, 2.0, 2.0, 40.0, 50.0, 60.0, ];
8498 let cur_sc: Vec<f32> = vec![
8500 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 0.0, 100.0, 100.0, 0.0,
8502 ];
8503 let prev_kv: Vec<f32> = vec![
8505 7.0, 8.0, 9.0, 0.0, 0.0, 0.0, 5.0, 6.0, 7.0, 0.0, 0.0, 0.0,
8507 ];
8508 let prev_sc = vec![0.0f32; ratio * 2 * d];
8509
8510 let mut out = vec![0.0f32; d];
8511 compress_window_overlap(&prev_kv, &prev_sc, &cur_kv, &cur_sc, ratio, d, &mut out);
8512 assert!((out[0] - 40.0).abs() < 1e-3, "dim0 = {}", out[0]);
8514 assert!((out[1] - 50.0).abs() < 1e-3, "dim1 = {}", out[1]);
8515 assert!((out[2] - 30.0).abs() < 1e-3, "dim2 = {}", out[2]);
8517
8518 let mut first = vec![0.0f32; d];
8521 compress_window_overlap(&[], &[], &cur_kv, &cur_sc, ratio, d, &mut first);
8522 assert!(
8523 first.iter().all(|v| v.is_finite()),
8524 "first window: {first:?}"
8525 );
8526 assert!((first[0] - 40.0).abs() < 1e-3, "first dim0 = {}", first[0]);
8527
8528 let mut both = vec![0.0f32; d];
8530 let strong_prev = vec![100.0f32; ratio * 2 * d];
8531 compress_window_overlap(
8532 &prev_kv,
8533 &strong_prev,
8534 &cur_kv,
8535 &cur_sc,
8536 ratio,
8537 d,
8538 &mut both,
8539 );
8540 assert!(
8541 (both[0] - 40.0).abs() > 1.0,
8542 "a scored previous window must move the fold, got {}",
8543 both[0]
8544 );
8545 }
8546
8547 #[test]
8553 fn sinkhorn_matches_the_reference_numbers() {
8554 let hc = 4;
8555 let mixes: Vec<f32> = (0..24).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
8556 let base: Vec<f32> = (0..24).map(|i| (i as f32 * 0.11).cos()).collect();
8557 let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
8558 hc_split_sinkhorn(
8559 &mixes,
8560 &[1.0, 1.0, 1.0],
8561 &base,
8562 hc,
8563 20,
8564 1e-6,
8565 &mut pre,
8566 &mut post,
8567 &mut comb,
8568 );
8569 let want_pre = [0.7310596, 0.8888268, 0.9525191, 0.97424865];
8570 let want_post = [1.9600224, 1.9534285, 1.9201256, 1.8160983];
8571 let want_comb = [
8572 0.5996052,
8573 0.282_535_9,
8574 0.09218107,
8575 0.025676856,
8576 0.17564717,
8577 0.22228767,
8578 0.271_745_4,
8579 0.330_318_8,
8580 0.029528176,
8581 0.12206022,
8582 0.32619134,
8583 0.5222193,
8584 0.19521846,
8585 0.37311527,
8586 0.30988118,
8587 0.12178412,
8588 ];
8589 for (i, w) in want_pre.iter().enumerate() {
8590 assert!((pre[i] - w).abs() < 1e-5, "pre[{i}]: {} vs {w}", pre[i]);
8591 }
8592 for (i, w) in want_post.iter().enumerate() {
8593 assert!((post[i] - w).abs() < 1e-5, "post[{i}]: {} vs {w}", post[i]);
8594 }
8595 for (i, w) in want_comb.iter().enumerate() {
8596 assert!((comb[i] - w).abs() < 1e-4, "comb[{i}]: {} vs {w}", comb[i]);
8597 }
8598 }
8599
8600 #[test]
8605 fn sinkhorn_leaves_the_mixing_matrix_doubly_stochastic() {
8606 let hc = 4;
8607 let mix_hc = (2 + hc) * hc;
8608 let mixes: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
8610 let base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.11).cos()).collect();
8611 let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
8612 hc_split_sinkhorn(
8613 &mixes,
8614 &[1.0, 1.0, 1.0],
8615 &base,
8616 hc,
8617 20,
8618 1e-6,
8619 &mut pre,
8620 &mut post,
8621 &mut comb,
8622 );
8623 for j in 0..hc {
8624 let r: f32 = comb[j * hc..(j + 1) * hc].iter().sum();
8625 assert!((r - 1.0).abs() < 2e-3, "row {j} sums to {r}");
8626 let c: f32 = (0..hc).map(|k| comb[k * hc + j]).sum();
8627 assert!((c - 1.0).abs() < 2e-3, "col {j} sums to {c}");
8628 }
8629 assert!(pre.iter().all(|&v| v > 0.0 && v < 1.001));
8631 assert!(post.iter().all(|&v| (0.0..=2.0).contains(&v)));
8632 }
8633
8634 #[test]
8638 fn expand_of_identical_copies_is_a_fixed_point() {
8639 let (hc, dim) = (4usize, 3usize);
8640 let residual: Vec<f32> = std::iter::repeat_n([1.5f32, -2.0, 0.25], hc)
8641 .flatten()
8642 .collect();
8643 let comb = {
8644 vec![0.25f32; hc * hc]
8646 };
8647 let post = vec![0.0f32; hc];
8648 let mut out = vec![0.0f32; hc * dim];
8649 hc_expand(&[0.0; 3], &residual, &post, &comb, hc, dim, &mut out);
8650 for (o, r) in out.iter().zip(&residual) {
8651 assert!((o - r).abs() < 1e-6, "{o} vs {r}");
8652 }
8653 }
8654
8655 #[test]
8659 fn selection_bias_steers_the_choice_but_not_the_weights() {
8660 let scores = [3.0f32, 0.1, 2.0, 0.05];
8661 let bias = [0.0f32, 10.0, 0.0, 0.0];
8662 let (mut idx, mut w) = (Vec::new(), Vec::new());
8663 route(&scores, Some(&bias), 2, 1.5, None, None, &mut idx, &mut w);
8664 assert_eq!(idx[0], 1, "the biased expert must win selection");
8665 assert_eq!(idx[1], 0);
8666 assert!(w[0] < w[1], "biased expert kept its own (small) weight");
8669 let sum: f32 = w.iter().sum();
8670 assert!((sum - 1.5).abs() < 1e-5, "weights renormalize then scale");
8671 }
8672
8673 #[test]
8677 fn attention_sink_drains_weight_without_contributing_output() {
8678 let hd = 2;
8679 let q = [1.0f32, 0.0];
8680 let kv = [1.0f32, 0.0, 0.0, 1.0];
8681 let mut out = vec![0.0f32; hd];
8682 sparse_attend(&q, &kv, &[0, 1], f32::NEG_INFINITY, 1.0, hd, &mut out);
8683 let plain = out.clone();
8684 assert!(plain[0] > plain[1], "the aligned key must dominate");
8685 sparse_attend(&q, &kv, &[0, 1], 20.0, 1.0, hd, &mut out);
8686 assert!(
8687 out[0] < plain[0] * 0.01 && out[1] < plain[1] * 0.01,
8688 "a large sink must drain nearly all the mass: {out:?}"
8689 );
8690 }
8691
8692 #[test]
8695 fn masked_positions_leave_the_denominator_alone() {
8696 let hd = 2;
8697 let q = [1.0f32, 0.0];
8698 let kv = [1.0f32, 0.0, 0.0, 1.0];
8699 let (mut a, mut b) = (vec![0.0f32; hd], vec![0.0f32; hd]);
8700 sparse_attend(&q, &kv, &[0], f32::NEG_INFINITY, 1.0, hd, &mut a);
8701 sparse_attend(
8702 &q,
8703 &kv,
8704 &[0, usize::MAX],
8705 f32::NEG_INFINITY,
8706 1.0,
8707 hd,
8708 &mut b,
8709 );
8710 for (x, y) in a.iter().zip(&b) {
8711 assert!((x - y).abs() < 1e-6, "{x} vs {y}");
8712 }
8713 }
8714
8715 #[test]
8718 fn rope_tail_inverts_itself() {
8719 let inv_freq = [1.0f32, 0.5];
8720 let orig = [9.0f32, 8.0, 1.0, 2.0, 3.0, 4.0];
8721 let mut v = orig;
8722 rope_tail(&mut v, &inv_freq, 7, 4, false);
8723 assert!(v[..2] == orig[..2], "the non-rope head must not move");
8724 assert!(v[2..] != orig[2..], "the tail must actually rotate");
8725 rope_tail(&mut v, &inv_freq, 7, 4, true);
8726 for (a, b) in v.iter().zip(&orig) {
8727 assert!((a - b).abs() < 1e-5, "{a} vs {b}");
8728 }
8729 }
8730
8731 #[test]
8734 fn compressor_pools_the_window_per_dimension() {
8735 let (ratio, width) = (2usize, 2usize);
8736 let kv = [1.0f32, 10.0, 3.0, 20.0];
8737 let score = [0.0f32, 0.0, 0.0, 50.0];
8739 let ape = vec![0.0f32; ratio * width];
8740 let mut out = vec![0.0f32; width];
8741 compress_window(&kv, &score, &ape, ratio, width, &mut out);
8742 assert!(
8743 (out[0] - 2.0).abs() < 1e-5,
8744 "equal scores average: {}",
8745 out[0]
8746 );
8747 assert!(
8748 (out[1] - 20.0).abs() < 1e-3,
8749 "a dominant score wins: {}",
8750 out[1]
8751 );
8752 }
8753
8754 #[test]
8757 fn index_scores_relu_before_weighting() {
8758 let (nh, hd) = (2usize, 2usize);
8759 let q = [1.0f32, 0.0, -1.0, 0.0];
8761 let kv = [1.0f32, 0.0, 0.0, 1.0];
8762 let w = [1.0f32, 1.0];
8763 let mut sc = Vec::new();
8764 index_scores(&q, &kv, &w, nh, hd, 2, 2, None, &mut sc);
8765 assert!(sc[0] > 0.9, "abstention, not veto: {:?}", sc);
8767 }
8768
8769 #[test]
8770 fn index_scores_mask_the_future() {
8771 let (nh, hd) = (1usize, 2usize);
8772 let q = [1.0f32, 0.0];
8773 let kv = [1.0f32, 0.0, 1.0, 0.0, 1.0, 0.0];
8774 let w = [1.0f32];
8775 let mut sc = Vec::new();
8776 index_scores(&q, &kv, &w, nh, hd, 3, 2, None, &mut sc);
8777 assert!(sc[0].is_finite() && sc[1].is_finite());
8778 assert!(sc[2] == f32::NEG_INFINITY, "position 2 is in the future");
8779 let mut idx = Vec::new();
8780 top_k_positions(&sc, 3, &mut idx);
8781 assert_eq!(idx, vec![0, 1], "a masked slot never wins a slot");
8782 }
8783
8784 #[test]
8785 fn top_k_is_deterministic_on_ties() {
8786 let sc = [1.0f32, 1.0, 1.0, 0.0];
8787 let mut idx = Vec::new();
8788 top_k_positions(&sc, 2, &mut idx);
8789 assert_eq!(idx, vec![0, 1], "ties resolve to the lower index");
8790 }
8791
8792 #[test]
8796 fn hc_block_preserves_the_copy_structure_and_applies_the_block() {
8797 let cfg = Dsv4Cfg {
8798 dim: 4,
8799 n_heads: 1,
8800 head_dim: 4,
8801 rope_head_dim: 2,
8802 q_lora_rank: 4,
8803 o_lora_rank: 2,
8804 o_groups: 1,
8805 hc_mult: 4,
8806 hc_sinkhorn_iters: 20,
8807 hc_eps: 1e-6,
8808 norm_eps: 1e-6,
8809 n_routed_experts: 2,
8810 top_k: 1,
8811 moe_inter: 4,
8812 route_scale: 1.0,
8813 swiglu_limit: 10.0,
8814 window: 128,
8815 index_topk: 4,
8816 vocab: 8,
8817 };
8818 let (hc, dim) = (cfg.hc_mult, cfg.dim);
8819 let mix_hc = (2 + hc) * hc;
8820 let hc_fn: Vec<f32> = (0..mix_hc * hc * dim)
8821 .map(|i| ((i % 13) as f32 - 6.0) * 0.05)
8822 .collect();
8823 let hc_base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.2).sin()).collect();
8824 let norm_w = vec![1.0f32; dim];
8825 let mut state: Vec<f32> = (0..hc * dim).map(|i| (i as f32 * 0.3).cos()).collect();
8826 let before = state.clone();
8827 let mut scratch = HcScratch::new(&cfg);
8828 hc_block(
8829 &mut state,
8830 &hc_fn,
8831 &[1.0, 1.0, 1.0],
8832 &hc_base,
8833 &norm_w,
8834 &cfg,
8835 &mut scratch,
8836 None,
8837 |_folded, out: &mut [f32]| out.iter_mut().for_each(|o| *o = 1.0),
8838 );
8839 assert_eq!(state.len(), before.len(), "copy structure must survive");
8840 assert!(state.iter().all(|v| v.is_finite()), "{state:?}");
8841 assert!(
8842 state.iter().zip(&before).any(|(a, b)| (a - b).abs() > 1e-4),
8843 "the block's output has to reach the state"
8844 );
8845 }
8846
8847 #[test]
8848 fn hash_route_reads_the_table_row() {
8849 let table = [7.0f32, 9.0, 1.0, 2.0, 5.0, 6.0];
8851 assert_eq!(hash_route(&table, 3, 2, 0), vec![7, 9]);
8852 assert_eq!(hash_route(&table, 3, 2, 2), vec![5, 6]);
8853 assert_eq!(hash_route(&table, 3, 2, 99), vec![5, 6]);
8855 }
8856
8857 #[test]
8862 fn a_task_mask_restricts_selection_and_renormalizes() {
8863 let scores = [0.1f32, 4.0, 1.0, 9.0];
8865 let (mut idx, mut w) = (Vec::new(), Vec::new());
8866 route(&scores, None, 2, 1.0, None, None, &mut idx, &mut w);
8867 assert_eq!(idx, vec![3, 1], "unmasked: the two best win");
8868 let sum: f32 = w.iter().sum();
8869 assert!((sum - 1.0).abs() < 1e-5, "weights must sum to route_scale");
8870
8871 let mask = [true, false, true, true];
8874 let (mut i2, mut w2) = (Vec::new(), Vec::new());
8875 route(&scores, None, 2, 1.0, None, Some(&mask), &mut i2, &mut w2);
8876 assert_eq!(i2, vec![3, 2], "masked expert must not be selected");
8877 let sum2: f32 = w2.iter().sum();
8878 assert!((sum2 - 1.0).abs() < 1e-5, "masked weights must renormalize");
8879
8880 let tight = [false, false, false, true];
8882 let (mut i3, mut w3) = (Vec::new(), Vec::new());
8883 route(&scores, None, 2, 1.0, None, Some(&tight), &mut i3, &mut w3);
8884 assert_eq!(i3, vec![3]);
8885 assert_eq!(w3.len(), 1);
8886 }
8887
8888 #[test]
8893 fn hash_layers_weight_the_experts_the_table_names() {
8894 let scores = [0.1f32, 0.4, 0.2, 5.0];
8896 let table = vec![0.0f32, 1.0];
8897 let idx_forced = hash_route(&table, 1, 2, 0);
8898 assert_eq!(idx_forced, vec![0, 1]);
8899
8900 let (mut idx, mut w) = (Vec::new(), Vec::new());
8901 route(
8902 &scores,
8903 None,
8904 2,
8905 1.0,
8906 Some(&idx_forced),
8907 None,
8908 &mut idx,
8909 &mut w,
8910 );
8911 assert_eq!(idx, vec![0, 1], "the table must decide the experts");
8912
8913 let sp = |x: f32| (1.0 + x.exp()).ln().sqrt();
8915 let (s0, s1) = (sp(scores[0]), sp(scores[1]));
8916 let tot = s0 + s1;
8917 assert!(
8918 (w[0] - s0 / tot).abs() < 1e-6,
8919 "w[0]={} want {}",
8920 w[0],
8921 s0 / tot
8922 );
8923 assert!(
8924 (w[1] - s1 / tot).abs() < 1e-6,
8925 "w[1]={} want {}",
8926 w[1],
8927 s1 / tot
8928 );
8929
8930 let (mut idx2, mut w2) = (Vec::new(), Vec::new());
8932 route(&scores, None, 2, 1.0, None, None, &mut idx2, &mut w2);
8933 assert_eq!(idx2[0], 3, "without a table the highest score still wins");
8934 }
8935}
8936
8937pub const DSPARK_NOISE_TOKEN: u32 = 128799;
8954pub fn dspark_block() -> usize {
8962 5
8963}
8964
8965#[cfg(feature = "gpu")]
8972fn dspark_native_on() -> bool {
8973 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8974 *ON.get_or_init(|| {
8975 std::env::var("CMF_DSPARK_NATIVE")
8976 .map(|v| v != "0")
8977 .unwrap_or(true)
8980 })
8981}
8982
8983pub fn dspark_verify_k() -> usize {
8984 static K: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
8985 *K.get_or_init(|| {
8986 std::env::var("CMF_DSPARK_VERIFY_K")
8987 .ok()
8988 .and_then(|v| v.parse::<usize>().ok())
8989 .filter(|&n| (1..=DSPARK_BLOCK_MAX).contains(&n))
8990 .unwrap_or(DSPARK_BLOCK_MAX)
8991 })
8992}
8993
8994pub const DSPARK_BLOCK_MAX: usize = 5;
8996
8997pub struct DsparkState {
9000 pub win: Vec<Vec<f32>>,
9002 pub filled: Vec<usize>,
9004 pub main_hidden: Vec<f32>,
9006 pub have_hidden: bool,
9008}
9009
9010impl DsparkState {
9011 pub fn new(stages: usize, cfg: &Dsv4Cfg, targets: usize) -> Self {
9012 Self {
9013 win: vec![Vec::new(); stages],
9014 filled: vec![0; stages],
9015 main_hidden: vec![0.0; cfg.dim * targets],
9016 have_hidden: false,
9017 }
9018 }
9019}
9020
9021pub fn dspark_targets(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, n_layers: usize) -> Vec<usize> {
9027 let Some(mp) = mtp.iter().find_map(|m| m.main_proj.as_ref()) else {
9028 return Vec::new();
9029 };
9030 let n = (mp.cols() / cfg.dim.max(1)).clamp(1, n_layers);
9031 (n_layers - n..n_layers).collect()
9032}
9033
9034thread_local! {
9035 static DSPARK_CAP: std::cell::RefCell<(Vec<usize>, Vec<f32>, usize)> =
9041 const { std::cell::RefCell::new((Vec::new(), Vec::new(), 0)) };
9042}
9043
9044pub fn dspark_arm(targets: &[usize], dim: usize) {
9046 DSPARK_CAP.with(|c| {
9047 let mut c = c.borrow_mut();
9048 c.0 = targets.to_vec();
9049 c.1 = vec![0.0; dim * targets.len()];
9050 c.2 = 0;
9051 });
9052}
9053
9054fn dspark_wants(li: usize) -> bool {
9058 DSPARK_CAP.with(|c| c.borrow().0.contains(&li))
9059}
9060
9061pub fn dspark_note(li: usize, state: &[f32], cfg: &Dsv4Cfg) {
9063 DSPARK_CAP.with(|c| {
9064 let mut c = c.borrow_mut();
9065 if c.0.is_empty() {
9066 return;
9067 }
9068 if let Some(slot) = c.0.iter().position(|&t| t == li) {
9069 let (_, buf, seen) = &mut *c;
9070 dspark_capture(state, cfg, slot, buf);
9071 *seen = if slot == 0 { 1 } else { *seen + 1 };
9076 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
9077 eprintln!("[cap] note li={li} slot={slot} seen={}", *seen);
9078 }
9079 }
9080 });
9081}
9082
9083pub fn dspark_peek_slot(slot: usize, dim: usize, out: &mut [f32]) -> bool {
9088 DSPARK_CAP.with(|c| {
9089 let c = c.borrow();
9090 let lo = slot * dim;
9091 if c.1.len() < lo + dim {
9092 return false;
9093 }
9094 out[..dim].copy_from_slice(&c.1[lo..lo + dim]);
9095 true
9096 })
9097}
9098
9099pub fn dspark_take(out: &mut Vec<f32>) -> bool {
9101 DSPARK_CAP.with(|c| {
9102 let mut c = c.borrow_mut();
9103 if c.0.is_empty() || c.2 != c.0.len() {
9104 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
9105 eprintln!("[cap] take FAIL armed={:?} seen={}", c.0, c.2);
9106 }
9107 return false;
9108 }
9109 out.clear();
9110 out.extend_from_slice(&c.1);
9111 c.2 = 0;
9112 true
9113 })
9114}
9115
9116pub fn dspark_capture(state: &[f32], cfg: &Dsv4Cfg, slot: usize, out: &mut [f32]) {
9120 let (hc, dim) = (cfg.hc_mult, cfg.dim);
9121 let dst = &mut out[slot * dim..(slot + 1) * dim];
9122 let inv = 1.0 / hc as f32;
9123 for d in 0..dim {
9124 let mut s = 0.0;
9125 for j in 0..hc {
9126 s += state[j * dim + d];
9127 }
9128 dst[d] = s * inv;
9129 }
9130}
9131
9132pub fn dspark_freq_note(picks: &[(usize, Vec<usize>)]) {
9137 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
9138 std::sync::Mutex::new(None);
9139 let Ok(path) = std::env::var("CMF_DSPARK_PICK_DUMP") else {
9140 return;
9141 };
9142 let mut g = FREQ.lock().unwrap();
9143 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
9144 for (stage, idx) in picks {
9145 for &e in idx {
9146 *map.entry((*stage, e)).or_insert(0) += 1;
9147 }
9148 }
9149 *blocks += 1;
9150 if *blocks % 32 == 0 {
9151 let mut lines: Vec<_> = map.iter().collect();
9152 lines.sort();
9153 let body: String = lines
9154 .iter()
9155 .map(|((s, e), n)| format!("{s}\t{e}\t{n}\n"))
9156 .collect();
9157 let _ = std::fs::write(&path, body);
9158 }
9159}
9160
9161pub fn trunk_freq_note(picks: &[(usize, Vec<usize>)]) {
9166 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
9167 std::sync::Mutex::new(None);
9168 let Ok(path) = std::env::var("CMF_DSV4_TRUNK_PICK_DUMP") else {
9169 return;
9170 };
9171 let mut g = FREQ.lock().unwrap();
9172 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
9173 for (li, idx) in picks {
9174 for &e in idx {
9175 *map.entry((*li, e)).or_insert(0) += 1;
9176 }
9177 }
9178 *blocks += 1;
9179 if *blocks % 32 == 0 {
9180 let mut lines: Vec<_> = map.iter().collect();
9181 lines.sort();
9182 let body: String = lines
9183 .iter()
9184 .map(|((l, e), n)| format!("{l}\t{e}\t{n}\n"))
9185 .collect();
9186 let _ = std::fs::write(&path, body);
9187 }
9188}
9189
9190fn dspark_apply_mask(out: &mut [Dsv4Mtp]) {
9198 let Ok(path) = std::env::var("CMF_DSPARK_MASK") else {
9199 return;
9200 };
9201 let Ok(text) = std::fs::read_to_string(&path) else {
9202 eprintln!("DSpark: CMF_DSPARK_MASK={path} не читается — маска не применена");
9203 return;
9204 };
9205 for (d, line) in text.lines().enumerate() {
9206 let Some(m) = out.get_mut(d) else { break };
9207 let n = m.layer.experts.len();
9208 let mut mask = vec![false; n];
9209 let mut kept = 0usize;
9210 for tok in line.split(',') {
9211 if let Ok(e) = tok.trim().parse::<usize>() {
9212 if e < n && !mask[e] {
9213 mask[e] = true;
9214 kept += 1;
9215 }
9216 }
9217 }
9218 if kept == 0 {
9219 continue;
9220 }
9221 eprintln!("DSpark: стадия {d} ограничена {kept}/{n} экспертами");
9222 m.layer.mask = Some(mask);
9223 }
9224}
9225
9226#[cfg(feature = "gpu")]
9236pub struct DsparkPack {
9237 pub stages: Vec<DsparkStagePack>,
9238 pub gu_q2: bool,
9241 pub dn_q2: bool,
9244 pub routers: Vec<Vec<f32>>,
9247 pub biases: Vec<Option<Vec<f32>>>,
9248}
9249
9250#[cfg(feature = "gpu")]
9251pub struct DsparkStagePack {
9252 pub mask: Vec<bool>,
9254 pub to_slot: Vec<usize>,
9256 pub mask_u32: Vec<u32>,
9259 pub map_u32: Vec<u32>,
9260 pub tensors: Vec<(usize, usize, usize)>,
9262 pub n_resident: usize,
9263}
9264
9265pub static DSPARK_Q2TP_ENCODE: std::sync::OnceLock<fn(&[f32], usize, usize) -> Vec<u8>> =
9272 std::sync::OnceLock::new();
9273
9274#[cfg(feature = "gpu")]
9277pub fn dspark_gpu_on() -> bool {
9278 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9279 *ON.get_or_init(|| {
9280 std::env::var("CMF_DSPARK_GPU")
9281 .map(|v| v != "0")
9282 .unwrap_or(true)
9283 })
9284}
9285
9286#[cfg(feature = "gpu")]
9288pub fn dspark_pack_get(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<&'static DsparkPack> {
9289 static P: std::sync::OnceLock<Option<Box<DsparkPack>>> = std::sync::OnceLock::new();
9290 P.get_or_init(|| dspark_pack_build(mtp, cfg).map(Box::new))
9291 .as_deref()
9292}
9293
9294#[cfg(feature = "gpu")]
9310pub fn dspark_reserve_note(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, layers: &[Dsv4Layer]) {
9311 if mtp.is_empty() || std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "0") || !dspark_gpu_on()
9312 {
9313 return;
9314 }
9315 let forced = std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "1");
9320 let dt = |q2: bool| {
9321 if q2 {
9322 cortiq_core::TensorDtype::Q2TiledP
9323 } else {
9324 cortiq_core::TensorDtype::Q4TiledP
9325 }
9326 };
9327 let gu_q2 = mtp[0]
9328 .layer
9329 .experts
9330 .first()
9331 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9332 let dn_q2 = mtp[0]
9333 .layer
9334 .experts
9335 .first()
9336 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9337 let gu = cortiq_core::quant::expected_nbytes(dt(gu_q2), &[cfg.moe_inter, cfg.dim]).unwrap_or(0);
9338 let dn = cortiq_core::quant::expected_nbytes(dt(dn_q2), &[cfg.dim, cfg.moe_inter]).unwrap_or(0);
9339 let per = (2 * gu + dn) as u64;
9340 let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
9341 .ok()
9342 .and_then(|v| v.parse().ok())
9343 .unwrap_or(40);
9346 let bytes = per * (n_res * mtp.len() + mtp.len() + 1) as u64;
9348 let trunk: u64 = layers
9358 .iter()
9359 .map(|l| {
9360 let Some(e) = l.experts.first() else {
9361 return 0;
9362 };
9363 let gu = cortiq_core::quant::expected_nbytes(
9364 dt(e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
9365 &[cfg.moe_inter, cfg.dim],
9366 )
9367 .unwrap_or(0);
9368 let dn = cortiq_core::quant::expected_nbytes(
9369 dt(e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
9370 &[cfg.dim, cfg.moe_inter],
9371 )
9372 .unwrap_or(0);
9373 let routed = l
9374 .mask
9375 .as_deref()
9376 .map_or(l.experts.len(), |m| m.iter().filter(|&&open| open).count());
9377 ((2 * gu + dn) * (routed + 1)) as u64
9378 })
9379 .sum();
9380 if let Some(budget) = crate::gpu_wgpu::dsv4_vram_budget() {
9381 if !forced && budget < trunk / 10 * 9 + bytes {
9382 return;
9383 }
9384 }
9385 let mib = 1024 * 1024u64;
9394 let workspace = match crate::gpu_wgpu::dsv4_vram_budget() {
9395 Some(b) if b <= 64 * 1024 * mib => 1024 * mib,
9399 _ => ((cfg.dim * cfg.hc_mult * DSPARK_BLOCK_MAX * 4096) as u64)
9400 .clamp(512 * mib, 1024 * mib),
9401 };
9402 crate::gpu_wgpu::DRAFT_PACK_RESERVE.store(bytes, std::sync::atomic::Ordering::Relaxed);
9403 crate::gpu_wgpu::DRAFT_RESERVE
9404 .store(bytes.saturating_add(workspace), std::sync::atomic::Ordering::Relaxed);
9405}
9406
9407#[cfg(not(feature = "gpu"))]
9408pub fn dspark_reserve_note(_mtp: &[Dsv4Mtp], _cfg: &Dsv4Cfg, _layers: &[Dsv4Layer]) {}
9409
9410#[cfg(feature = "gpu")]
9411pub fn dspark_pack_build(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<DsparkPack> {
9412 if mtp.is_empty() {
9413 return None;
9414 }
9415 let n_res: usize =
9416 std::env::var("CMF_DSPARK_RESIDENT")
9417 .ok()
9418 .and_then(|v| v.parse().ok())
9419 .unwrap_or_else(|| {
9420 let native_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
9426 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
9427 });
9428 let gu_q2 =
9429 native_q2 || (!dspark_native_on() && DSPARK_Q2TP_ENCODE.get().is_some());
9430 let dn_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
9431 e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
9432 });
9433 let room = crate::gpu_wgpu::dsv4_draft_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2);
9434 (room.saturating_sub(mtp.len() + 1) / mtp.len().max(1)).clamp(8, 40)
9435 });
9436 let mut freq: Vec<Vec<(u64, usize)>> = vec![Vec::new(); mtp.len()];
9440 let pack_path = std::env::var("CMF_DSPARK_PACK").ok().or_else(|| {
9441 let m = mtp[0].layer.experts.first()?.w1.model_arc()?;
9442 let mut s = m.path.as_os_str().to_os_string();
9443 s.push(".dspark.tsv");
9444 let p = std::path::PathBuf::from(s);
9445 p.exists().then(|| p.to_string_lossy().into_owned())
9446 });
9447 if let Some(path) = pack_path {
9448 if let Ok(text) = std::fs::read_to_string(&path) {
9449 for line in text.lines() {
9450 let mut it = line.split_whitespace();
9451 if let (Some(s), Some(e), Some(n)) = (it.next(), it.next(), it.next()) {
9452 if let (Ok(s), Ok(e), Ok(n)) =
9453 (s.parse::<usize>(), e.parse::<usize>(), n.parse::<u64>())
9454 {
9455 if s < freq.len() {
9456 freq[s].push((n, e));
9457 }
9458 }
9459 }
9460 }
9461 }
9462 }
9463 let mut stages = Vec::with_capacity(mtp.len());
9464 let mut routers = Vec::with_capacity(mtp.len());
9465 let mut biases = Vec::with_capacity(mtp.len());
9466 for (si, m) in mtp.iter().enumerate() {
9467 let l = &m.layer;
9468 let n = l.experts.len();
9469 let mut order: Vec<usize> = {
9472 let mut f = freq[si].clone();
9473 f.sort_by(|a, b| b.0.cmp(&a.0));
9474 let mut seen = vec![false; n];
9475 let mut o: Vec<usize> = f
9476 .into_iter()
9477 .map(|(_, e)| e)
9478 .filter(|&e| {
9479 if e < n && !seen[e] {
9480 seen[e] = true;
9481 true
9482 } else {
9483 false
9484 }
9485 })
9486 .collect();
9487 o.extend((0..n).filter(|&e| !seen[e]));
9488 o
9489 };
9490 order.truncate(n_res.min(n));
9491 let mut mask = vec![false; n];
9492 let mut to_slot = vec![usize::MAX; n];
9493 let mut tensors = Vec::with_capacity(order.len() + 1);
9494 for (slot, &e) in order.iter().enumerate() {
9495 let ex = &l.experts[e];
9496 let (Some(w1), Some(w3), Some(w2)) =
9497 (ex.w1.model_idx(), ex.w3.model_idx(), ex.w2.model_idx())
9498 else {
9499 return None;
9500 };
9501 mask[e] = true;
9502 to_slot[e] = slot;
9503 tensors.push((w1, w3, w2));
9504 }
9505 let (Some(s1), Some(s3), Some(s2)) = (
9506 l.shared.w1.model_idx(),
9507 l.shared.w3.model_idx(),
9508 l.shared.w2.model_idx(),
9509 ) else {
9510 return None;
9511 };
9512 tensors.push((s1, s3, s2));
9513 let mut router = vec![0.0f32; n * cfg.dim];
9515 for (r, row) in (0..n).zip(router.chunks_mut(cfg.dim)) {
9516 l.gate.row_f32(r, row);
9517 }
9518 routers.push(router);
9519 biases.push(l.gate_bias.clone());
9520 let mask_u32: Vec<u32> = mask.iter().map(|&m| m as u32).collect();
9521 let map_u32: Vec<u32> = to_slot
9522 .iter()
9523 .map(|&x| if x == usize::MAX { u32::MAX } else { x as u32 })
9524 .collect();
9525 stages.push(DsparkStagePack {
9526 mask,
9527 to_slot,
9528 mask_u32,
9529 map_u32,
9530 tensors,
9531 n_resident: order.len(),
9532 });
9533 }
9534 let model = mtp[0]
9538 .layer
9539 .experts
9540 .first()
9541 .and_then(|e| e.w1.model_arc())?;
9542 let mut skeleton = Vec::new();
9543 for m in mtp {
9544 let l = &m.layer;
9545 for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b] {
9546 skeleton.push(t.model_idx()?);
9547 }
9548 }
9549 if let Some(mp) = mtp[0].main_proj.as_ref() {
9550 skeleton.push(mp.model_idx()?);
9551 }
9552 for &idx in &skeleton {
9553 if !crate::gpu_wgpu::dsv4_weight_ready(&model, idx) {
9554 eprintln!("DSpark: скелет драфта не влез в VRAM — GPU-черновик выключен");
9555 return None;
9556 }
9557 }
9558 let native_q2 = mtp[0]
9564 .layer
9565 .experts
9566 .first()
9567 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9568 let gu_q2 = native_q2
9569 || (!crate::dsv4::dspark_native_on()
9570 && crate::dsv4::DSPARK_Q2TP_ENCODE.get().is_some());
9571 let dn_native = mtp[0]
9572 .layer
9573 .experts
9574 .first()
9575 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9576 for (si, sp) in stages.iter().enumerate() {
9577 let ok = if native_q2 {
9578 crate::gpu_wgpu::dsv4_experts_ready(
9579 &model,
9580 &sp.tensors,
9581 cfg.moe_inter,
9582 cfg.dim,
9583 true,
9584 dn_native,
9585 )
9586 } else if gu_q2 {
9587 crate::gpu_wgpu::moe_expert_bufs_requant_gu(&model, &sp.tensors, cfg.moe_inter, cfg.dim)
9588 .is_some()
9589 } else {
9590 crate::gpu_wgpu::dsv4_experts_ready(
9591 &model,
9592 &sp.tensors,
9593 cfg.moe_inter,
9594 cfg.dim,
9595 false,
9596 false,
9597 )
9598 };
9599 if !ok {
9600 eprintln!(
9601 "DSpark: эксперты стадии {si} ({} + shared) не влезли в VRAM — GPU-черновик выключен",
9602 sp.n_resident
9603 );
9604 return None;
9605 }
9606 }
9607 let _ = crate::gpu_wgpu::pin_weights(&model, &skeleton);
9608 eprintln!(
9609 "DSpark: пак драфта на карте — {} стадии по {} экспертов + shared",
9610 stages.len(),
9611 stages
9612 .iter()
9613 .map(|s| s.n_resident.to_string())
9614 .collect::<Vec<_>>()
9615 .join("/")
9616 );
9617 Some(DsparkPack {
9618 stages,
9619 gu_q2,
9620 dn_q2: dn_native,
9621 routers,
9622 biases,
9623 })
9624}
9625
9626pub fn dspark_ring_append(
9633 g: &Dsv4Globals,
9634 mtp: &[Dsv4Mtp],
9635 cfg: &Dsv4Cfg,
9636 ds: &mut DsparkState,
9637 pos: usize,
9638 pool: Option<&crate::pool::Pool>,
9639) {
9640 let (dim, hd, rd) = (cfg.dim, cfg.head_dim, cfg.rope_head_dim);
9641 let inv_freq = &g.inv_freq_window;
9642 let Some(stage0) = mtp.first() else { return };
9643 let (Some(mp), Some(mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
9644 return;
9645 };
9646 let mut main_x = vec![0.0f32; dim];
9647 mp.matvec(&ds.main_hidden, &mut main_x, pool);
9648 rms_weighted(&mut main_x, mn, cfg.norm_eps);
9649 for (si, m) in mtp.iter().enumerate() {
9650 let kvw = m.layer.wkv.rows();
9651 if ds.win[si].len() < cfg.window * kvw {
9652 ds.win[si].resize(cfg.window * kvw, 0.0);
9653 }
9654 let mut kv = vec![0.0f32; kvw];
9655 m.layer.wkv.matvec(&main_x, &mut kv, pool);
9656 rms_weighted(&mut kv, &m.layer.kv_norm, cfg.norm_eps);
9657 rope_tail(&mut kv[kvw - hd..], inv_freq, pos, rd, false);
9658 let slot = pos % cfg.window;
9659 ds.win[si][slot * kvw..(slot + 1) * kvw].copy_from_slice(&kv);
9660 ds.filled[si] = (pos + 1).min(cfg.window);
9661 }
9662}
9663
9664#[cfg(feature = "gpu")]
9670#[allow(clippy::too_many_arguments)]
9671pub fn dspark_draft_gpu(
9672 g: &Dsv4Globals,
9673 mtp: &[Dsv4Mtp],
9674 cfg: &Dsv4Cfg,
9675 ds: &mut DsparkState,
9676 pack: &DsparkPack,
9677 kv_id: u64,
9678 last_token: u32,
9679 pos: usize,
9680 pool: Option<&crate::pool::Pool>,
9681 out_conf: &mut Vec<f32>,
9682) -> Vec<u32> {
9683 let (hc, dim) = (cfg.hc_mult, cfg.dim);
9684 let block = dspark_block();
9685 let Some(model) = mtp[0].layer.experts.first().and_then(|e| e.w1.model_arc()) else {
9686 return Vec::new();
9687 };
9688 let (Some(mp), Some(mn)) = (mtp[0].main_proj.as_ref(), mtp[0].main_norm.as_ref()) else {
9689 return Vec::new();
9690 };
9691 let Some(mp_idx) = mp.model_idx() else {
9692 return Vec::new();
9693 };
9694 let mut stages = Vec::with_capacity(mtp.len());
9695 for (si, m) in mtp.iter().enumerate() {
9696 let l = &m.layer;
9697 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
9698 l.wq_a.model_idx(),
9699 l.wq_b.model_idx(),
9700 l.wo_a.model_idx(),
9701 l.wo_b.model_idx(),
9702 l.wkv.model_idx(),
9703 ) else {
9704 return Vec::new();
9705 };
9706 let sp = &pack.stages[si];
9707 stages.push(crate::gpu_wgpu::DsparkStageW {
9708 wq_a,
9709 wq_b,
9710 wo_a,
9711 wo_b,
9712 wkv,
9713 q_norm: &l.q_norm,
9714 kv_norm: &l.kv_norm,
9715 attn_norm: &l.attn_norm,
9716 ffn_norm: &l.ffn_norm,
9717 sink: &l.attn_sink,
9718 hc_attn_fn: &l.hc_attn_fn,
9719 hc_attn_scale: &l.hc_attn_scale,
9720 hc_attn_base: &l.hc_attn_base,
9721 hc_ffn_fn: &l.hc_ffn_fn,
9722 hc_ffn_scale: &l.hc_ffn_scale,
9723 hc_ffn_base: &l.hc_ffn_base,
9724 router: &pack.routers[si],
9725 bias: pack.biases[si].as_deref(),
9726 experts: &sp.tensors,
9727 mask_u32: &sp.mask_u32,
9728 map_u32: &sp.map_u32,
9729 });
9730 }
9731 let geom = crate::gpu_wgpu::DsparkGeom {
9732 dim,
9733 hc,
9734 nh: cfg.n_heads,
9735 hd: cfg.head_dim,
9736 rd: cfg.rope_head_dim,
9737 q_lora: cfg.q_lora_rank,
9738 o_lora: cfg.o_lora_rank,
9739 o_groups: cfg.o_groups,
9740 inter: cfg.moe_inter,
9741 n_experts: cfg.n_routed_experts,
9742 top_k: cfg.top_k,
9743 window: cfg.window,
9744 eps: cfg.norm_eps,
9745 hc_eps: cfg.hc_eps,
9746 sinkhorn_iters: cfg.hc_sinkhorn_iters,
9747 route_scale: cfg.route_scale,
9748 swiglu_limit: cfg.swiglu_limit,
9749 scale: (cfg.head_dim as f32).powf(-0.5),
9750 gu_q2: pack.gu_q2,
9751 dn_q2: pack.dn_q2,
9752 };
9753 let ids: Vec<u32> = (0..block)
9755 .map(|i| {
9756 if i == 0 {
9757 last_token
9758 } else {
9759 DSPARK_NOISE_TOKEN
9760 }
9761 })
9762 .collect();
9763 let mut states0 = vec![0.0f32; block * hc * dim];
9764 let mut emb = vec![0.0f32; dim];
9765 for (i, &id) in ids.iter().enumerate() {
9766 g.embed.row_f32(id as usize, &mut emb);
9767 for j in 0..hc {
9768 states0[(i * hc + j) * dim..(i * hc + j + 1) * dim].copy_from_slice(&emb);
9769 }
9770 }
9771 let dspark_time = {
9772 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9773 *ON.get_or_init(|| std::env::var("CMF_DSPARK_TIME").is_ok_and(|v| v != "0"))
9774 };
9775 let t0 = std::time::Instant::now();
9776 let filled = (pos + 1).min(cfg.window);
9777 let mut states = vec![0.0f32; block * hc * dim];
9778 if !crate::gpu_wgpu::dspark_graph(
9779 &model,
9780 &stages,
9781 geom,
9782 kv_id,
9783 mp_idx,
9784 mn,
9785 &ds.main_hidden,
9786 &states0,
9787 pos,
9788 filled,
9789 &g.inv_freq_window,
9790 block,
9791 &mut states,
9792 ) {
9793 return Vec::new();
9794 }
9795 for si in 0..mtp.len() {
9796 ds.filled[si] = filled;
9797 }
9798 let t_graph = t0.elapsed();
9799
9800 let last = &mtp[mtp.len() - 1];
9802 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
9803 last.hc_head_fn.as_ref(),
9804 last.hc_head_base.as_ref(),
9805 last.hc_head_scale,
9806 last.norm.as_ref(),
9807 ) else {
9808 return Vec::new();
9809 };
9810 let mut head_in = vec![0.0f32; block * dim];
9811 let mut pre_norms = vec![vec![0.0f32; dim]; block];
9812 for i in 0..block {
9813 hc_head_fold(
9814 &states[i * hc * dim..(i + 1) * hc * dim],
9815 hfn,
9816 hscale,
9817 hbase,
9818 cfg,
9819 pool,
9820 &mut head_in[i * dim..(i + 1) * dim],
9821 );
9822 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
9823 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
9824 }
9825 let t_fold = t0.elapsed();
9826 let mut logits = vec![0.0f32; block * cfg.vocab];
9827 let head_gpu = g.head.model_idx().is_some_and(|hi| {
9830 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
9831 &model,
9832 hi,
9833 &head_in,
9834 block,
9835 cfg.vocab,
9836 dim,
9837 &mut logits,
9838 )
9839 });
9840 if !head_gpu {
9841 g.head.matmat(&head_in, block, &mut logits, pool);
9842 }
9843 let t_head = t0.elapsed();
9844 let mut proposals = Vec::with_capacity(block);
9849 out_conf.clear();
9850 let mut prev = last_token;
9851 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
9852 let mut bias = vec![0.0f32; cfg.vocab];
9853 for i in 0..block {
9854 let row = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
9855 if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
9856 w1.row_f32(prev as usize, &mut mk_embed);
9857 w2.matvec(&mk_embed, &mut bias, pool);
9858 for (a, b) in row.iter_mut().zip(&bias) {
9859 *a += *b;
9860 }
9861 }
9862 let mut best = 0usize;
9863 for v in 1..row.len() {
9864 if row[v] > row[best] {
9865 best = v;
9866 }
9867 }
9868 if let Some(cf) = last.confidence.as_ref() {
9869 let mut cat = pre_norms[i].clone();
9870 cat.extend_from_slice(&mk_embed);
9871 let mut sc = [0.0f32; 1];
9872 if cat.len() == cf.cols() {
9873 cf.matvec(&cat, &mut sc, pool);
9874 }
9875 out_conf.push(sc[0]);
9876 }
9877 proposals.push(best as u32);
9878 prev = best as u32;
9879 }
9880 if dspark_time {
9881 eprintln!(
9882 "DSpark GPU: граф {:.1} мс, фолды {:.1}, голова {:.1}, марков+argmax {:.1}",
9883 t_graph.as_secs_f64() * 1e3,
9884 (t_fold - t_graph).as_secs_f64() * 1e3,
9885 (t_head - t_fold).as_secs_f64() * 1e3,
9886 (t0.elapsed() - t_head).as_secs_f64() * 1e3,
9887 );
9888 }
9889 proposals
9890}
9891
9892#[allow(clippy::too_many_arguments)]
9898pub fn dspark_draft(
9899 g: &Dsv4Globals,
9900 mtp: &[Dsv4Mtp],
9901 cfg: &Dsv4Cfg,
9902 ds: &mut DsparkState,
9903 last_token: u32,
9904 pos: usize,
9905 pool: Option<&crate::pool::Pool>,
9906 out_conf: &mut Vec<f32>,
9907) -> Vec<u32> {
9908 let (hc, dim, hd, rd) = (cfg.hc_mult, cfg.dim, cfg.head_dim, cfg.rope_head_dim);
9909 let block = dspark_block();
9910 let inv_freq = &g.inv_freq_window;
9911
9912 let Some(stage0) = mtp.first() else {
9914 return Vec::new();
9915 };
9916 let (Some(_mp), Some(_mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
9917 return Vec::new();
9918 };
9919 dspark_ring_append(g, mtp, cfg, ds, pos, pool);
9920
9921 let ids: Vec<u32> = (0..block)
9923 .map(|i| {
9924 if i == 0 {
9925 last_token
9926 } else {
9927 DSPARK_NOISE_TOKEN
9928 }
9929 })
9930 .collect();
9931 let mut states = vec![vec![0.0f32; hc * dim]; block];
9932 let mut emb = vec![0.0f32; dim];
9933 for (i, &id) in ids.iter().enumerate() {
9934 g.embed.row_f32(id as usize, &mut emb);
9935 for j in 0..hc {
9936 states[i][j * dim..(j + 1) * dim].copy_from_slice(&emb);
9937 }
9938 }
9939
9940 let mut scratch = HcScratch::new(cfg);
9941 for (si, m) in mtp.iter().enumerate() {
9942 let l = &m.layer;
9943 let kvw = l.wkv.rows();
9944 let mut post = vec![vec![0.0f32; hc]; block];
9947 let mut comb = vec![vec![0.0f32; hc * hc]; block];
9948 let mut resid = vec![vec![0.0f32; hc * dim]; block];
9949 let mut folded = vec![vec![0.0f32; dim]; block];
9950 let mix_hc = (2 + hc) * hc;
9951 for i in 0..block {
9952 hc_mixes(
9953 &states[i],
9954 &l.hc_attn_fn,
9955 mix_hc,
9956 cfg.norm_eps,
9957 pool,
9958 &mut scratch.mixes,
9959 );
9960 hc_split_sinkhorn(
9961 &scratch.mixes,
9962 &l.hc_attn_scale,
9963 &l.hc_attn_base,
9964 hc,
9965 cfg.hc_sinkhorn_iters,
9966 cfg.hc_eps,
9967 &mut scratch.pre,
9968 &mut post[i],
9969 &mut comb[i],
9970 );
9971 hc_fold(&states[i], &scratch.pre, hc, dim, &mut folded[i]);
9972 rms_weighted(&mut folded[i], &l.attn_norm, cfg.norm_eps);
9973 resid[i].copy_from_slice(&states[i]);
9974 }
9975 let folded_all: Vec<f32> = folded.iter().flatten().copied().collect();
9977 let mut blk_kv = vec![0.0f32; block * kvw];
9978 l.wkv.matmat(&folded_all, block, &mut blk_kv, pool);
9979 for i in 0..block {
9980 let dst = &mut blk_kv[i * kvw..(i + 1) * kvw];
9981 rms_weighted(dst, &l.kv_norm, cfg.norm_eps);
9982 rope_tail(&mut dst[kvw - hd..], inv_freq, pos + 1 + i, rd, false);
9983 }
9984 let win_len = ds.filled[si];
9986 let mut cache = Vec::with_capacity((win_len + block) * hd);
9987 for p in 0..win_len {
9988 let e = &ds.win[si][p * kvw..(p + 1) * kvw];
9989 cache.extend_from_slice(&e[kvw - hd..]);
9990 }
9991 for i in 0..block {
9992 let e = &blk_kv[i * kvw..(i + 1) * kvw];
9993 cache.extend_from_slice(&e[kvw - hd..]);
9994 }
9995 let idxs: Vec<usize> = (0..win_len + block).collect();
9996 let scale = (hd as f32).powf(-0.5);
9997 let qrank = l.wq_a.rows();
9998 let qdim = cfg.n_heads * hd;
9999 let mut qr = vec![0.0f32; block * qrank];
10000 l.wq_a.matmat(&folded_all, block, &mut qr, pool);
10001 for i in 0..block {
10002 rms_weighted(&mut qr[i * qrank..(i + 1) * qrank], &l.q_norm, cfg.norm_eps);
10003 }
10004 let mut q = vec![0.0f32; block * qdim];
10005 l.wq_b.matmat(&qr, block, &mut q, pool);
10006 let mut attn = vec![0.0f32; block * qdim];
10007 for i in 0..block {
10008 let qi = &mut q[i * qdim..(i + 1) * qdim];
10009 let ai = &mut attn[i * qdim..(i + 1) * qdim];
10010 let qpos = pos + 1 + i;
10011 for h in 0..cfg.n_heads {
10012 let head = &mut qi[h * hd..(h + 1) * hd];
10013 rms_inplace(head, cfg.norm_eps);
10014 rope_tail(head, inv_freq, qpos, rd, false);
10015 }
10016 for h in 0..cfg.n_heads {
10017 let qh = &qi[h * hd..(h + 1) * hd];
10018 let oh = &mut ai[h * hd..(h + 1) * hd];
10019 sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
10020 rope_tail(oh, inv_freq, qpos, rd, true);
10021 }
10022 }
10023 let mut blk_out = vec![0.0f32; block * dim];
10024 o_project_block(
10025 &attn,
10026 block,
10027 &l.wo_a,
10028 &l.wo_b,
10029 cfg.o_groups,
10030 cfg.o_lora_rank,
10031 pool,
10032 &mut blk_out,
10033 );
10034 for i in 0..block {
10035 let mut next = vec![0.0f32; hc * dim];
10036 hc_expand(
10037 &blk_out[i * dim..(i + 1) * dim],
10038 &resid[i],
10039 &post[i],
10040 &comb[i],
10041 hc,
10042 dim,
10043 &mut next,
10044 );
10045 states[i] = next;
10046 }
10047 let mut ffn_fold = vec![0.0f32; block * dim];
10050 let mut ffn_post = vec![vec![0.0f32; hc]; block];
10051 let mut ffn_comb = vec![vec![0.0f32; hc * hc]; block];
10052 let mut ffn_resid = vec![vec![0.0f32; hc * dim]; block];
10053 for i in 0..block {
10054 hc_mixes(
10055 &states[i],
10056 &l.hc_ffn_fn,
10057 mix_hc,
10058 cfg.norm_eps,
10059 pool,
10060 &mut scratch.mixes,
10061 );
10062 hc_split_sinkhorn(
10063 &scratch.mixes,
10064 &l.hc_ffn_scale,
10065 &l.hc_ffn_base,
10066 hc,
10067 cfg.hc_sinkhorn_iters,
10068 cfg.hc_eps,
10069 &mut scratch.pre,
10070 &mut ffn_post[i],
10071 &mut ffn_comb[i],
10072 );
10073 hc_fold(
10074 &states[i],
10075 &scratch.pre,
10076 hc,
10077 dim,
10078 &mut ffn_fold[i * dim..(i + 1) * dim],
10079 );
10080 rms_weighted(
10081 &mut ffn_fold[i * dim..(i + 1) * dim],
10082 &l.ffn_norm,
10083 cfg.norm_eps,
10084 );
10085 ffn_resid[i].copy_from_slice(&states[i]);
10086 }
10087 let mut moe_out = vec![0.0f32; block * dim];
10088 moe_step_block(&ffn_fold, block, l, cfg, &ids, si, pool, &mut moe_out);
10089 for i in 0..block {
10090 let mut next = vec![0.0f32; hc * dim];
10091 hc_expand(
10092 &moe_out[i * dim..(i + 1) * dim],
10093 &ffn_resid[i],
10094 &ffn_post[i],
10095 &ffn_comb[i],
10096 hc,
10097 dim,
10098 &mut next,
10099 );
10100 states[i] = next;
10101 }
10102 }
10103
10104 let last = &mtp[mtp.len() - 1];
10106 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
10107 last.hc_head_fn.as_ref(),
10108 last.hc_head_base.as_ref(),
10109 last.hc_head_scale,
10110 last.norm.as_ref(),
10111 ) else {
10112 return Vec::new();
10113 };
10114 let mut proposals = Vec::with_capacity(block);
10115 out_conf.clear();
10116 let mut prev = last_token;
10117 let mut head_in = vec![0.0f32; block * dim];
10118 let mut pre_norms = vec![vec![0.0f32; dim]; block];
10119 for i in 0..block {
10120 hc_head_fold(
10121 &states[i],
10122 hfn,
10123 hscale,
10124 hbase,
10125 cfg,
10126 pool,
10127 &mut head_in[i * dim..(i + 1) * dim],
10128 );
10129 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
10130 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
10131 }
10132 let mut logits = vec![0.0f32; block * cfg.vocab];
10133 g.head.matmat(&head_in, block, &mut logits, pool);
10134 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
10135 for i in 0..block {
10136 let logits_i = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
10137 let no_markov = {
10146 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10147 *ON.get_or_init(|| std::env::var("CMF_DSPARK_NO_MARKOV").is_ok_and(|v| v != "0"))
10148 };
10149 if no_markov {
10150 if let Some(w1) = last.markov_w1.as_ref() {
10152 w1.row_f32(prev as usize, &mut mk_embed);
10153 }
10154 } else if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
10155 w1.row_f32(prev as usize, &mut mk_embed);
10156 let mut bias = vec![0.0f32; cfg.vocab];
10157 w2.matvec(&mk_embed, &mut bias, pool);
10158 for (a, b) in logits_i.iter_mut().zip(&bias) {
10159 *a += *b;
10160 }
10161 }
10162 let mut best = 0usize;
10163 for v in 1..logits_i.len() {
10164 if logits_i[v] > logits_i[best] {
10165 best = v;
10166 }
10167 }
10168 if let Some(cf) = last.confidence.as_ref() {
10169 let mut cat = pre_norms[i].clone();
10170 cat.extend_from_slice(&mk_embed);
10171 let mut s = [0.0f32; 1];
10172 if cat.len() == cf.cols() {
10173 cf.matvec(&cat, &mut s, pool);
10174 }
10175 out_conf.push(s[0]);
10176 }
10177 proposals.push(best as u32);
10178 prev = best as u32;
10179 }
10180 proposals
10181}