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 [
1084 &ATTN_NS, &MOE_NS, &HC_NS, &HEAD_NS, &ALL_NS, &CALLS, &PREP_NS, &CACHEW_NS,
1085 ] {
1086 a.store(0, Ordering::Relaxed);
1087 }
1088 TOKENS.store(1, Ordering::Relaxed);
1089 #[cfg(feature = "gpu")]
1090 for a in [
1091 &crate::gpu_wgpu::MOE_ENC_NS,
1092 &crate::gpu_wgpu::MOE_WAIT_NS,
1093 &crate::gpu_wgpu::MOE_BUFS_NS,
1094 &crate::gpu_wgpu::MOE_UP_NS,
1095 &crate::gpu_wgpu::MOE_PASS_NS,
1096 &crate::gpu_wgpu::ATT_ENC_NS,
1097 &crate::gpu_wgpu::ATT_WAIT_NS,
1098 &crate::gpu_wgpu::CHAIN_ENC_NS,
1099 &crate::gpu_wgpu::CHAIN_WAIT_NS,
1100 &crate::gpu_wgpu::CHAIN_LAYERS,
1101 &crate::gpu_wgpu::CHAIN_RUNS,
1102 &crate::gpu_wgpu::SUBMITS,
1103 &crate::gpu_wgpu::PASSES,
1104 ] {
1105 a.store(0, Ordering::Relaxed);
1106 }
1107 }
1108 }
1109 }
1110 static REPORT: AtomicBool = AtomicBool::new(false);
1111 static ZEROED: AtomicBool = AtomicBool::new(false);
1113
1114 pub fn on() -> bool {
1115 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1116 *ON.get_or_init(|| std::env::var("CMF_DSV4_PROFILE").is_ok_and(|v| v != "0"))
1117 }
1118
1119 pub fn report() {
1122 if !on() || REPORT.swap(true, Ordering::Relaxed) {
1123 return;
1124 }
1125 let calls = CALLS.load(Ordering::Relaxed).max(1);
1129 let toks = TOKENS.load(Ordering::Relaxed).max(1);
1130 let (a, m) = (
1131 ATTN_NS.load(Ordering::Relaxed) as f64 / 1e6,
1132 MOE_NS.load(Ordering::Relaxed) as f64 / 1e6,
1133 );
1134 let all = ALL_NS.load(Ordering::Relaxed) as f64 / 1e6;
1135 let hc = (HC_NS.load(Ordering::Relaxed) as f64 / 1e6
1142 - MOE_NS.load(Ordering::Relaxed) as f64 / 1e6)
1143 .max(0.0);
1144 let hd = HEAD_NS.load(Ordering::Relaxed) as f64 / 1e6;
1145 let prep = PREP_NS.load(Ordering::Relaxed) as f64 / 1e6;
1146 let cw = CACHEW_NS.load(Ordering::Relaxed) as f64 / 1e6;
1147 eprintln!(
1148 "[dsv4-профиль] ХОСТ-ПРЕП слоя: {:.0} мс/токен, KV-заливки: {:.0} мс/токен",
1149 prep / toks as f64,
1150 cw / toks as f64
1151 );
1152 #[cfg(feature = "gpu")]
1153 {
1154 let f = crate::gpu_wgpu::DSV4_FILLS.load(Ordering::Relaxed);
1155 let fb = crate::gpu_wgpu::DSV4_FILL_BYTES.load(Ordering::Relaxed);
1156 eprintln!(
1157 "[dsv4-профиль] ЗАЛИВКИ СЛОТОВ: {:.1} эксп/токен, {:.0} МБ/токен",
1158 f as f64 / toks as f64,
1159 fb as f64 / 1e6 / toks as f64
1160 );
1161 }
1162 eprintln!(
1163 "[dsv4-профиль] {calls} вызовов слоя за {toks} токенов | \
1164 на токен: внимание {:.0} мс, MoE {:.0} мс, гипер-связи+нормы {:.0} мс, \
1165 голова {:.0} мс | на вызов: внимание {:.2}, MoE {:.2}, связи {:.2}",
1166 a / toks as f64,
1167 m / toks as f64,
1168 hc / toks as f64,
1169 hd / toks as f64,
1170 a / calls as f64,
1171 m / calls as f64,
1172 hc / calls as f64,
1173 );
1174 eprintln!(
1175 "[dsv4-профиль] весь проход {:.0} мс на токен; вне счётчиков {:.0} мс",
1176 all / toks as f64,
1177 (all - a - m - hd) / toks as f64,
1178 );
1179 #[cfg(feature = "gpu")]
1180 {
1181 let ae = crate::gpu_wgpu::ATT_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6;
1182 let aw = crate::gpu_wgpu::ATT_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6;
1183 if ae + aw > 0.0 {
1184 eprintln!(
1185 "[dsv4-профиль] кадр внимания на вызов: кодирование {:.2} мс, \
1186 отправка и ожидание {:.2} мс",
1187 ae / calls as f64,
1188 aw / calls as f64,
1189 );
1190 }
1191 let ub = crate::gpu_wgpu::UPLOAD_BYTES.load(Ordering::Relaxed);
1196 let un = crate::gpu_wgpu::UPLOAD_NS.load(Ordering::Relaxed);
1197 if ub > 0 && un > 0 {
1198 eprintln!(
1199 "[dsv4-профиль] ЗАЛИВКА весов: {:.1} ГБ за {:.1} с ({:.0} МБ/с)",
1200 ub as f64 / 1e9,
1201 un as f64 / 1e9,
1202 ub as f64 / (un as f64 / 1e9) / 1e6,
1203 );
1204 }
1205 let sub = crate::gpu_wgpu::SUBMITS.load(Ordering::Relaxed);
1206 if sub > 0 {
1207 eprintln!(
1208 "[dsv4-профиль] ОТПРАВОК на карту: {:.1} на токен, ПРОХОДОВ {:.0} \
1209 ({:.1} на слой)",
1210 sub as f64 / toks as f64,
1211 crate::gpu_wgpu::PASSES.load(Ordering::Relaxed) as f64 / toks as f64,
1212 crate::gpu_wgpu::PASSES.load(Ordering::Relaxed) as f64 / calls as f64,
1213 );
1214 }
1215 let cl = crate::gpu_wgpu::CHAIN_LAYERS.load(Ordering::Relaxed);
1216 if cl > 0 {
1217 let toks2 = toks.max(1) as f64;
1218 eprintln!(
1219 "[dsv4-профиль] ЦЕПОЧКА на токен: кодирование {:.2} мс, \
1220 ожидание {:.2} мс ({} слоёв, {} отправок)",
1221 crate::gpu_wgpu::CHAIN_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6 / toks2,
1222 crate::gpu_wgpu::CHAIN_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6 / toks2,
1223 cl / toks.max(1),
1224 crate::gpu_wgpu::CHAIN_RUNS.load(Ordering::Relaxed) / toks.max(1),
1225 );
1226 }
1227 let e = crate::gpu_wgpu::MOE_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6;
1228 let wt = crate::gpu_wgpu::MOE_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6;
1229 if e + wt > 0.0 {
1230 let ns = |a: &std::sync::atomic::AtomicU64| {
1231 a.load(Ordering::Relaxed) as f64 / 1e6 / calls as f64
1232 };
1233 eprintln!(
1234 "[dsv4-профиль] кадр MoE на вызов: кодирование {:.2} мс, \
1235 отправка и ожидание {:.2} мс",
1236 e / calls as f64,
1237 wt / calls as f64,
1238 );
1239 let an = crate::gpu_wgpu::ATT_GPU_N.load(Ordering::Relaxed);
1240 if an > 0 {
1241 let g = |i: usize| {
1242 crate::gpu_wgpu::ATT_GPU_NS[i].load(Ordering::Relaxed) as f64
1243 / 1e6
1244 / an as f64
1245 };
1246 eprintln!(
1247 "[dsv4-профиль] ВНИМАНИЕ НА КАРТЕ на вызов: одиночное {:.3} мс, \
1248 оценки {:.3} мс, применение {:.3} мс",
1249 g(0),
1250 g(1),
1251 g(2),
1252 );
1253 }
1254 let gn = crate::gpu_wgpu::MOE_GPU_N.load(Ordering::Relaxed);
1255 let gns = crate::gpu_wgpu::MOE_GPU_NS[0].load(Ordering::Relaxed);
1256 if gn > 0 && gns > 0 {
1257 eprintln!(
1258 "[dsv4-профиль] MoE НА КАРТЕ: {:.3} мс на вызов ({gn} замеров)",
1259 gns as f64 / 1e6 / gn as f64,
1260 );
1261 } else if gn > 0 {
1262 eprintln!(
1266 "[dsv4-профиль] MoE НА КАРТЕ: метки вернули НОЛЬ на {gn} замерах — \
1267 запрос времени не сработал, число не использовать"
1268 );
1269 }
1270 eprintln!(
1271 "[dsv4-профиль] из кодирования: буферы экспертов {:.2} мс, \
1272 загрузки {:.2} мс, проходы {:.2} мс",
1273 ns(&crate::gpu_wgpu::MOE_BUFS_NS),
1274 ns(&crate::gpu_wgpu::MOE_UP_NS),
1275 ns(&crate::gpu_wgpu::MOE_PASS_NS),
1276 );
1277 }
1278 }
1279 }
1280}
1281
1282pub fn profile_report() {
1284 prof::report();
1285}
1286
1287fn gpu_attn_enabled() -> bool {
1292 #[cfg(feature = "gpu")]
1293 {
1294 use std::sync::OnceLock;
1295 static ON: OnceLock<bool> = OnceLock::new();
1296 *ON.get_or_init(|| {
1297 let want = std::env::var("CMF_DSV4_GPU_ATTN")
1298 .map(|v| v != "0")
1299 .unwrap_or(true);
1300 let have = want && crate::gpu::backend_available();
1301 if want && !have && std::env::var("CMF_DSV4_GPU_ATTN").is_ok() {
1302 tracing::warn!(
1303 "CMF_DSV4_GPU_ATTN задан, но устройства нет — блок внимания остаётся на CPU. Проверьте CMF_GPU=wgpu и Vulkan-ICD."
1304 );
1305 }
1306 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
1307 eprintln!("кадр dsv4: запрошен={want} доступен={have}");
1308 }
1309 have
1310 })
1311 }
1312 #[cfg(not(feature = "gpu"))]
1313 {
1314 false
1315 }
1316}
1317
1318#[cfg(feature = "gpu")]
1322#[allow(clippy::too_many_arguments)]
1323fn attn_frame(
1324 l: &Dsv4Layer,
1325 cfg: &Dsv4Cfg,
1326 st: &Dsv4State,
1327 li: usize,
1328 hidden: &[f32],
1329 qn: &[f32],
1330 idxs: &[usize],
1331 inv_freq: &[f32],
1332 pos: usize,
1333 win_len: usize,
1334 scale: f32,
1335 hc: Option<&crate::gpu_wgpu::Dsv4HcTail>,
1338 out: &mut [f32],
1339) -> bool {
1340 let hd = cfg.head_dim;
1341 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
1342 l.wq_a.model_idx(),
1343 l.wq_b.model_idx(),
1344 l.wo_a.model_idx(),
1345 l.wo_b.model_idx(),
1346 ) else {
1347 return false;
1348 };
1349 let Some(model) = l.wq_b.model_arc() else {
1350 return false;
1351 };
1352 let n_comp = st.compressed[li].len() / hd;
1357 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
1358 let kv_id = st.kv_id;
1359 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap) {
1365 return false;
1366 }
1367 if n_comp > 0
1376 && !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, cfg.window * hd, &st.compressed[li], cap)
1377 {
1378 return false;
1379 }
1380 let idx32: Vec<u32> = idxs
1381 .iter()
1382 .map(|&p| {
1383 if p < win_len {
1384 p as u32
1385 } else {
1386 (cfg.window + (p - win_len)) as u32
1387 }
1388 })
1389 .collect();
1390 let w = crate::gpu_wgpu::Dsv4AttnW {
1391 wq_a,
1392 wq_b,
1393 wo_a,
1394 wo_b,
1395 q_norm: &l.q_norm,
1396 sink: &l.attn_sink,
1397 };
1398 let g = crate::gpu_wgpu::Dsv4AttnGeom {
1399 dim: cfg.dim,
1400 nh: cfg.n_heads,
1401 hd,
1402 rd: cfg.rope_head_dim,
1403 q_lora: cfg.q_lora_rank,
1404 o_lora: cfg.o_lora_rank,
1405 o_groups: cfg.o_groups,
1406 eps: cfg.norm_eps,
1407 scale,
1408 bf16: false,
1409 q_rms: true,
1410 };
1411 crate::gpu_wgpu::dsv4_attn_frame(
1419 &model,
1420 &w,
1421 g,
1422 hidden,
1423 Some(qn),
1424 None,
1425 kv_id,
1426 li,
1427 &idx32,
1428 inv_freq,
1429 pos,
1430 hc,
1431 out,
1432 )
1433}
1434
1435#[derive(Default)]
1438pub struct AttnPrep {
1439 pub qr: Vec<f32>,
1440 pub idxs: Vec<usize>,
1441 pub win_len: usize,
1442}
1443
1444#[allow(clippy::too_many_arguments)]
1445pub fn attention_step(
1446 hidden: &[f32],
1447 l: &Dsv4Layer,
1448 cfg: &Dsv4Cfg,
1449 st: &mut Dsv4State,
1450 li: usize,
1451 inv_freq: &[f32],
1453 pool: Option<&crate::pool::Pool>,
1454 prep_out: Option<&mut AttnPrep>,
1458 out: &mut [f32],
1459) {
1460 let _t0 = prof::on().then(std::time::Instant::now);
1461 let _guard = scopeguard_attn(_t0);
1462 let (hd, rd) = (cfg.head_dim, cfg.rope_head_dim);
1463 let pos = st.pos;
1464 if std::env::var("CMF_FREQ_DEBUG").is_ok() && li == 0 && pos == 0 {
1465 eprintln!(
1466 " [порт] rd={rd} частот={} inv_freq[0..4]={:?}",
1467 inv_freq.len(),
1468 &inv_freq[..4.min(inv_freq.len())]
1469 );
1470 }
1471
1472 let mut qr = vec![0.0f32; cfg.q_lora_rank];
1477 let mut kv = vec![0.0f32; hd];
1478 crate::qtensor::QTensor::matvec_many([&l.wq_a, &l.wkv], hidden, [&mut qr, &mut kv], pool);
1479 rms_weighted(&mut qr, &l.q_norm, cfg.norm_eps);
1480 let split_config = st.partial_set.iter().any(|&p| p) && st.split_deep;
1498 let past_chain =
1499 st.dev_owned && (li >= st.dev_set.len() || !st.dev_set.get(li).copied().unwrap_or(false));
1500 if std::env::var("CMF_DSV4_GATE_DBG").is_ok() {
1501 eprintln!(
1502 "[gate] li={li} pos={} split={split_config} past={past_chain} dev_owned={} set_len={} part_len={}",
1503 st.pos,
1504 st.dev_owned,
1505 st.dev_set.len(),
1506 st.partial_set.len()
1507 );
1508 }
1509 let on_gpu = gpu_attn_enabled() && !(split_config && past_chain);
1510
1511 rms_weighted(&mut kv, &l.kv_norm, cfg.norm_eps);
1512 rope_tail(&mut kv, inv_freq, pos, rd, false);
1513
1514 if let Some(cp) = &l.compressor {
1519 let mut pk = std::mem::take(&mut st.pending_kv[li]);
1520 let mut ps = std::mem::take(&mut st.pending_score[li]);
1521 let mut qk = std::mem::take(&mut st.prev_kv[li]);
1522 let mut qs = std::mem::take(&mut st.prev_score[li]);
1523 let entry = compressor_step(
1524 cp,
1525 hidden,
1526 pos,
1527 rd,
1528 cfg.norm_eps,
1529 inv_freq,
1530 pool,
1531 &mut pk,
1532 &mut ps,
1533 &mut qk,
1534 &mut qs,
1535 );
1536 st.pending_kv[li] = pk;
1537 st.pending_score[li] = ps;
1538 st.prev_kv[li] = qk;
1539 st.prev_score[li] = qs;
1540 if let Some(e) = entry {
1541 st.compressed[li].extend_from_slice(&e);
1542 }
1543 }
1544 if let Some(ix) = &l.indexer {
1549 let mut pk = std::mem::take(&mut st.pending_ix_kv[li]);
1550 let mut ps = std::mem::take(&mut st.pending_ix_score[li]);
1551 let mut qk = std::mem::take(&mut st.prev_ix_kv[li]);
1552 let mut qs = std::mem::take(&mut st.prev_ix_score[li]);
1553 let entry = compressor_step(
1554 &ix.compressor,
1555 hidden,
1556 pos,
1557 rd,
1558 cfg.norm_eps,
1559 inv_freq,
1560 pool,
1561 &mut pk,
1562 &mut ps,
1563 &mut qk,
1564 &mut qs,
1565 );
1566 st.pending_ix_kv[li] = pk;
1567 st.pending_ix_score[li] = ps;
1568 st.prev_ix_kv[li] = qk;
1569 st.prev_ix_score[li] = qs;
1570 if let Some(e) = entry {
1571 st.index_kv[li].extend_from_slice(&e);
1572 }
1573 }
1574
1575 st.window[li].extend_from_slice(&kv);
1576 let cap = cfg.window * hd;
1580 if st.window[li].len() > cap {
1581 let drop = st.window[li].len() - cap;
1582 st.window[li].drain(..drop);
1583 }
1584 let win_len = st.window[li].len() / hd;
1585 let n_pos = win_len + st.compressed[li].len() / hd;
1586
1587 let mut idxs: Vec<usize> = (0..win_len).collect();
1596 if !st.compressed[li].is_empty() && !no_compressed() {
1597 let n_comp = st.compressed[li].len() / hd;
1598 match &l.indexer {
1599 Some(ix) => {
1600 let ih = ix.weights_proj.rows();
1616 let idim = ix.wq_b.rows() / ih.max(1);
1617 let mut qi = vec![0.0f32; ix.wq_b.rows()];
1618 ix.wq_b.matvec(&qr, &mut qi, pool);
1619 for h in 0..ih {
1620 rope_tail(&mut qi[h * idim..(h + 1) * idim], inv_freq, pos, rd, false);
1621 }
1622 let mut hw = vec![0.0f32; ih];
1623 ix.weights_proj.matvec(hidden, &mut hw, pool);
1624 let sc_factor = (idim as f32).powf(-0.5) * (ih as f32).powf(-0.5);
1625 for w in hw.iter_mut() {
1626 *w *= sc_factor;
1627 }
1628 let n_ix = st.index_kv[li].len() / idim.max(1);
1629 let mut sc = Vec::new();
1630 index_scores(
1631 &qi,
1632 &st.index_kv[li],
1633 &hw,
1634 ih,
1635 idim,
1636 n_ix.min(n_comp),
1637 n_ix.min(n_comp),
1638 pool,
1639 &mut sc,
1640 );
1641 let mut picked = Vec::new();
1642 top_k_positions(&sc, cfg.index_topk, &mut picked);
1643 idxs.extend(picked.into_iter().map(|p| win_len + p));
1644 }
1645 None => idxs.extend((0..n_comp).map(|p| win_len + p)),
1646 }
1647 }
1648 debug_assert!(idxs.iter().all(|&p| p < n_pos));
1649 if let Some(p) = prep_out {
1650 p.qr = qr;
1651 p.idxs = idxs;
1652 p.win_len = win_len;
1653 return;
1654 }
1655
1656 let scale = (hd as f32).powf(-0.5);
1658 #[cfg(feature = "gpu")]
1659 if on_gpu
1660 && {
1661 if std::env::var("CMF_DSV4_XCHK").is_ok() {
1662 if let Some(card) = crate::gpu_wgpu::dsv4_dbg_read_tag(45, 0, hidden.len()) {
1666 let md = hidden
1667 .iter()
1668 .zip(card.iter())
1669 .map(|(a, b)| (a - b).abs())
1670 .fold(0.0f32, f32::max);
1671 eprintln!("[xchk] li={li} pos={pos} x2 maxdiff={md:.3e}");
1672 }
1673 }
1674 true
1675 }
1676 && attn_frame(
1677 l, cfg, st, li, hidden, &qr, &idxs, inv_freq, pos, win_len, scale, None, out,
1678 )
1679 {
1680 return;
1681 }
1682
1683 let mut q = vec![0.0f32; cfg.n_heads * hd];
1685 l.wq_b.matvec(&qr, &mut q, pool);
1686 for h in 0..cfg.n_heads {
1687 let head = &mut q[h * hd..(h + 1) * hd];
1688 rms_inplace(head, cfg.norm_eps);
1689 rope_tail(head, inv_freq, pos, rd, false);
1690 }
1691 let mut cache: Vec<f32> = st.window[li].clone();
1692 cache.extend_from_slice(&st.compressed[li]);
1693
1694 let mut attn = vec![0.0f32; cfg.n_heads * hd];
1696 for h in 0..cfg.n_heads {
1697 let qh = &q[h * hd..(h + 1) * hd];
1698 let oh = &mut attn[h * hd..(h + 1) * hd];
1703 sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
1704 rope_tail(oh, inv_freq, pos, rd, true);
1705 }
1706
1707 o_project(
1713 &attn,
1714 &|r, x, sc| l.wo_a.row_dot(r, x, sc),
1715 l.wo_a.cols(),
1716 &|mid, dst| l.wo_b.matvec(mid, dst, pool),
1717 cfg.o_groups,
1718 cfg.o_lora_rank,
1719 pool,
1720 out,
1721 );
1722}
1723
1724pub fn rms_weighted(v: &mut [f32], w: &[f32], eps: f32) {
1726 let ms = v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32;
1727 let inv = 1.0 / (ms + eps).sqrt();
1728 for (x, g) in v.iter_mut().zip(w) {
1729 *x = *x * inv * g;
1730 }
1731}
1732
1733thread_local! {
1748 static ROUTE_COUNTS: std::cell::RefCell<Vec<Vec<u64>>> =
1749 const { std::cell::RefCell::new(Vec::new()) };
1750}
1751
1752fn route_stats_on() -> bool {
1753 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1754 *ON.get_or_init(|| std::env::var("CMF_MOE_STATS").is_ok())
1755}
1756
1757fn record_route(li: usize, n_layers_hint: usize, n_experts: usize, routed: &[(usize, f32)]) {
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 has_shared: true,
2388 shared_weight: 1.0,
2389 preweighted: false,
2390 qwen_softmax: false,
2391 },
2392 hc_ffn_fn: &l.hc_ffn_fn,
2393 hc_ffn_scale: &l.hc_ffn_scale,
2394 hc_ffn_base: &l.hc_ffn_base,
2395 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2396 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2397 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2398 ffn_norm: &l.ffn_norm,
2399 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2400 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2401 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2402 router: &pk.router,
2403 };
2404 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2405 attn: crate::gpu_wgpu::Dsv4AttnGeom {
2406 dim,
2407 nh: cfg.n_heads,
2408 hd,
2409 rd: cfg.rope_head_dim,
2410 q_lora: cfg.q_lora_rank,
2411 o_lora: cfg.o_lora_rank,
2412 o_groups: cfg.o_groups,
2413 eps: cfg.norm_eps,
2414 scale: (hd as f32).powf(-0.5),
2415 bf16: false,
2416 q_rms: true,
2417 },
2418 moe: crate::gpu_wgpu::Dsv4MoeGeom {
2419 hidden: dim,
2420 inter: cfg.moe_inter,
2421 top_k: cfg.top_k,
2422 route_scale: cfg.route_scale,
2423 swiglu_limit: cfg.swiglu_limit,
2424 gu_q2: l.experts.first().is_some_and(|e| {
2425 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
2426 }),
2427 bf16: false,
2428 },
2429 hc: cfg.hc_mult,
2430 hc_eps: cfg.hc_eps,
2431 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2432 };
2433 let mut next = vec![0.0f32; dim];
2434 if !crate::gpu_wgpu::dsv4_layer_frame(
2435 &model,
2436 &w,
2437 geom,
2438 kv_id,
2439 li,
2440 Some(&prep.qr),
2441 &idx32,
2442 freqs_of(l),
2443 st.pos,
2444 &mut next,
2445 None,
2446 None,
2447 &mut Vec::new(),
2448 ) {
2449 return false;
2450 }
2451 state_on_host = false;
2452 folded = next;
2453 dspark_note(li, state, cfg);
2454 }
2455 let mut state_home = false;
2456 if chain {
2457 if !run.is_empty() {
2458 let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
2462 let last_on_dev = *on_dev.last().unwrap_or(&false);
2463 let carry = last_on_dev && run.last() == Some(&(layers.len() - 1));
2464 let ok = if carry {
2465 let r = dsv4_chain_run(
2466 layers,
2467 &run,
2468 cfg,
2469 g,
2470 st,
2471 token_id,
2472 &mut folded,
2473 Some(state),
2474 1,
2475 &[],
2476 need_qn,
2477 pool,
2478 );
2479 state_home = r;
2480 state_on_host = r;
2481 if r {
2482 dspark_note(*run.last().unwrap(), state, cfg);
2483 }
2484 r
2485 } else {
2486 let r = dsv4_chain_run(
2487 layers,
2488 &run,
2489 cfg,
2490 g,
2491 st,
2492 token_id,
2493 &mut folded,
2494 None,
2495 1,
2496 &[],
2497 need_qn,
2498 pool,
2499 );
2500 if r {
2501 state_on_host = false;
2502 }
2503 r
2504 };
2505 if !ok {
2506 return false;
2507 }
2508 }
2509 if st.dev_set.is_empty() {
2510 st.dev_set = active_dev.clone();
2511 st.partial_set = partial_dev.clone();
2512 let mut idxs = Vec::new();
2518 for (li, l) in layers.iter().enumerate() {
2519 if !active_dev.get(li).copied().unwrap_or(false) {
2520 continue;
2521 }
2522 for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b, &l.gate] {
2523 idxs.extend(t.model_idx());
2524 }
2525 if let Some(pk) = pack_for(l, cfg, li) {
2526 for &(a, b, c) in &pk.tensors {
2527 idxs.extend([a, b, c]);
2528 }
2529 }
2530 }
2531 for (li, l) in layers.iter().enumerate() {
2539 if active_dev.get(li).copied().unwrap_or(false) {
2540 continue;
2541 }
2542 let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
2543 tracing::info!(
2544 "слой {li} на хосте: упаковано {packed} экспертов из {}",
2545 cfg.n_routed_experts
2546 );
2547 }
2548 let pinned = layers
2549 .iter()
2550 .find_map(|l| l.experts.first().and_then(|e| e.w1.model_arc()))
2551 .map_or(0, |m| crate::gpu_wgpu::pin_weights(&m, &idxs));
2552 tracing::info!(
2553 "закреплено на карте: {pinned} тензоров {} слоёв",
2554 on_dev.iter().filter(|&&x| x).count()
2555 );
2556 }
2557 }
2558 if state_home || state_on_host {
2559 return true;
2560 }
2561 crate::gpu_wgpu::dsv4_state_read(state)
2562}
2563
2564#[cfg(feature = "gpu")]
2569#[allow(clippy::too_many_arguments)]
2570fn dsv4_partial_layer(
2571 state: &mut [f32],
2572 folded: &mut Vec<f32>,
2573 layers: &[Dsv4Layer],
2574 l: &Dsv4Layer,
2575 cfg: &Dsv4Cfg,
2576 st: &mut Dsv4State,
2577 token_id: u32,
2578 li: usize,
2579 freqs: &[f32],
2580 pool: Option<&crate::pool::Pool>,
2581 state_on_host: bool,
2582) -> Option<bool> {
2583 let dim = cfg.dim;
2584 if state_on_host {
2593 let (f, post, comb) = hc_fold_norm(
2594 state,
2595 &l.hc_attn_fn,
2596 &l.hc_attn_scale,
2597 &l.hc_attn_base,
2598 &l.attn_norm,
2599 cfg,
2600 pool,
2601 );
2602 *folded = f;
2603 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2604 || !crate::gpu_wgpu::dsv4_state_write(state)
2605 {
2606 return None;
2607 }
2608 }
2609 let mut prep = AttnPrep::default();
2610 let mut sink = vec![0.0f32; dim];
2611 attention_step(
2612 folded,
2613 l,
2614 cfg,
2615 st,
2616 li,
2617 freqs,
2618 pool,
2619 Some(&mut prep),
2620 &mut sink,
2621 );
2622 let hd = cfg.head_dim;
2623 let n_comp = st.compressed[li].len() / hd;
2624 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2625 if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
2626 || (n_comp > 0
2627 && !crate::gpu_wgpu::dsv4_cache_write(
2628 st.kv_id,
2629 li,
2630 cfg.window * hd,
2631 &st.compressed[li],
2632 cap,
2633 ))
2634 {
2635 return None;
2636 }
2637 let a_tail = crate::gpu_wgpu::Dsv4HcTail {
2638 fn_: &l.hc_ffn_fn,
2639 scale: &l.hc_ffn_scale,
2640 base: &l.hc_ffn_base,
2641 norm: &l.ffn_norm,
2642 hc: cfg.hc_mult,
2643 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2644 hc_eps: cfg.hc_eps,
2645 eps: cfg.norm_eps,
2646 };
2647 let scale = (cfg.head_dim as f32).powf(-0.5);
2648 let mut ffn_input = if cpu_overlap_on() {
2654 vec![0.0f32; dim]
2655 } else {
2656 Vec::new()
2657 };
2658 if !attn_frame(
2659 l,
2660 cfg,
2661 st,
2662 li,
2663 folded,
2664 &prep.qr,
2665 &prep.idxs,
2666 freqs,
2667 st.pos,
2668 prep.win_len,
2669 scale,
2670 Some(&a_tail),
2671 &mut ffn_input,
2672 ) {
2673 return None;
2674 }
2675 let nxt = layers.get(li + 1);
2676 let forced = l
2677 .tid2eid
2678 .as_ref()
2679 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
2680 let mut next = vec![0.0f32; dim];
2681 let (cold_sum, cold_count) = moe_frame(
2682 &ffn_input,
2683 l,
2684 cfg,
2685 li,
2686 &[],
2687 forced.as_deref(),
2688 pool,
2689 Some(&a_tail),
2690 None,
2695 &mut next,
2696 )?;
2697 let state_ok = if cold_count == 0 {
2703 crate::gpu_wgpu::dsv4_state_read(state)
2704 } else {
2705 crate::gpu_wgpu::dsv4_state_add_cold(&cold_sum, cfg.hc_mult, state)
2706 };
2707 if !state_ok {
2708 return None;
2709 }
2710 if let Some(n) = nxt {
2711 let (f, post, comb) = hc_fold_norm(
2712 state,
2713 &n.hc_attn_fn,
2714 &n.hc_attn_scale,
2715 &n.hc_attn_base,
2716 &n.attn_norm,
2717 cfg,
2718 pool,
2719 );
2720 *folded = f;
2721 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2722 || !crate::gpu_wgpu::dsv4_state_write(state)
2723 {
2724 return None;
2725 }
2726 }
2727 Some(true)
2732}
2733
2734#[cfg(feature = "gpu")]
2741fn chain1_on() -> bool {
2742 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2743 *ON.get_or_init(|| {
2744 std::env::var("CMF_DSV4_CHAIN1")
2745 .map(|v| v != "0")
2746 .unwrap_or(true)
2747 })
2748}
2749
2750#[cfg(feature = "gpu")]
2756fn cpu_overlap_on() -> bool {
2757 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2758 *ON.get_or_init(|| std::env::var("CMF_DSV4_CPU_OVERLAP").is_ok_and(|v| v != "0"))
2759}
2760
2761#[cfg(feature = "gpu")]
2762#[allow(clippy::too_many_arguments)]
2763fn dsv4_chain1_layer(
2764 state: &mut [f32],
2765 folded: &mut Vec<f32>,
2766 layers: &[Dsv4Layer],
2767 l: &Dsv4Layer,
2768 cfg: &Dsv4Cfg,
2769 st: &mut Dsv4State,
2770 token_id: u32,
2771 li: usize,
2772 freqs: &[f32],
2773 pool: Option<&crate::pool::Pool>,
2774 state_on_host: bool,
2775) -> Option<bool> {
2776 let dim = cfg.dim;
2777 let pk = pack_for(l, cfg, li)?;
2778 if pk.route_complete() {
2779 return None;
2780 }
2781 let model = l.experts.first().and_then(|e| e.w1.model_arc())?;
2782 if state_on_host {
2786 let (f, post, comb) = hc_fold_norm(
2787 state,
2788 &l.hc_attn_fn,
2789 &l.hc_attn_scale,
2790 &l.hc_attn_base,
2791 &l.attn_norm,
2792 cfg,
2793 pool,
2794 );
2795 *folded = f;
2796 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2797 || !crate::gpu_wgpu::dsv4_state_write(state)
2798 {
2799 return None;
2800 }
2801 }
2802 let mut prep = AttnPrep::default();
2803 let mut sink = vec![0.0f32; dim];
2804 attention_step(
2805 folded,
2806 l,
2807 cfg,
2808 st,
2809 li,
2810 freqs,
2811 pool,
2812 Some(&mut prep),
2813 &mut sink,
2814 );
2815 let hd = cfg.head_dim;
2816 let n_comp = st.compressed[li].len() / hd;
2817 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2818 let kv_id = st.kv_id;
2819 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap)
2820 || (n_comp > 0
2821 && !crate::gpu_wgpu::dsv4_cache_write(
2822 kv_id,
2823 li,
2824 cfg.window * hd,
2825 &st.compressed[li],
2826 cap,
2827 ))
2828 {
2829 return None;
2830 }
2831 let idx32: Vec<u32> = prep
2832 .idxs
2833 .iter()
2834 .map(|&p| {
2835 if p < prep.win_len {
2836 p as u32
2837 } else {
2838 (cfg.window + (p - prep.win_len)) as u32
2839 }
2840 })
2841 .collect();
2842 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
2843 l.wq_a.model_idx(),
2844 l.wq_b.model_idx(),
2845 l.wo_a.model_idx(),
2846 l.wo_b.model_idx(),
2847 ) else {
2848 return None;
2849 };
2850 let forced: Option<Vec<usize>> = l
2853 .tid2eid
2854 .as_ref()
2855 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
2856 let global_remap = pk
2857 .global
2858 .as_ref()
2859 .map(|gl| gl.pool.remap(gl.layer, cfg.n_routed_experts));
2860 let dynv = pk.dynslots.lock().unwrap();
2861 let live_remap = global_remap.as_deref().unwrap_or(dynv.remap.as_slice());
2862 let nxt = layers.get(li + 1);
2863 let w = crate::gpu_wgpu::Dsv4LayerW {
2864 attn: crate::gpu_wgpu::Dsv4AttnW {
2865 wq_a,
2866 wq_b,
2867 wo_a,
2868 wo_b,
2869 q_norm: &l.q_norm,
2870 sink: &l.attn_sink,
2871 },
2872 moe: crate::gpu_wgpu::Dsv4MoeW {
2873 router: &[],
2874 experts: &pk.tensors,
2875 logits: &[],
2876 bias: pk.bias.as_deref(),
2879 mask: pk.mask.as_deref(),
2880 forced: forced.as_deref(),
2881 remap: Some(live_remap),
2882 global: pk.global.as_ref().map(|gl| crate::gpu_wgpu::Dsv4GlobalMoe {
2883 pool_uid: gl.pool.uid,
2884 shared_slot: gl.shared_slot,
2885 segment_slots: gl.pool.segment_slots as u32,
2886 }),
2887 has_shared: true,
2888 shared_weight: 1.0,
2889 preweighted: false,
2890 qwen_softmax: false,
2891 },
2892 hc_ffn_fn: &l.hc_ffn_fn,
2893 hc_ffn_scale: &l.hc_ffn_scale,
2894 hc_ffn_base: &l.hc_ffn_base,
2895 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2896 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2897 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2898 ffn_norm: &l.ffn_norm,
2899 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2900 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2901 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2902 router: &pk.router,
2903 };
2904 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2905 attn: crate::gpu_wgpu::Dsv4AttnGeom {
2906 dim,
2907 nh: cfg.n_heads,
2908 hd,
2909 rd: cfg.rope_head_dim,
2910 q_lora: cfg.q_lora_rank,
2911 o_lora: cfg.o_lora_rank,
2912 o_groups: cfg.o_groups,
2913 eps: cfg.norm_eps,
2914 scale: (hd as f32).powf(-0.5),
2915 bf16: false,
2916 q_rms: true,
2917 },
2918 moe: crate::gpu_wgpu::Dsv4MoeGeom {
2919 hidden: dim,
2920 inter: cfg.moe_inter,
2921 top_k: cfg.top_k,
2922 route_scale: cfg.route_scale,
2923 swiglu_limit: cfg.swiglu_limit,
2924 gu_q2: l
2925 .experts
2926 .first()
2927 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
2928 bf16: false,
2929 },
2930 hc: cfg.hc_mult,
2931 hc_eps: cfg.hc_eps,
2932 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2933 };
2934 let mut next = vec![0.0f32; dim];
2935 let mut cold: Vec<(usize, f32)> = Vec::new();
2936 let mut routed: Vec<(usize, f32)> = Vec::new();
2937 let mut cold_x: Vec<f32> = Vec::new();
2938 let need_routes = route_stats_on() || pk.global.is_some();
2939 if !crate::gpu_wgpu::dsv4_layer_frame(
2940 &model,
2941 &w,
2942 geom,
2943 kv_id,
2944 li,
2945 Some(&prep.qr),
2946 &idx32,
2947 freqs,
2948 st.pos,
2949 &mut next,
2950 Some(&mut cold),
2951 need_routes.then_some(&mut routed),
2952 &mut cold_x,
2953 ) {
2954 return None;
2955 }
2956 if route_stats_on() {
2957 record_route(li, layers.len(), cfg.n_routed_experts, &routed);
2958 }
2959 drop(dynv);
2960 if let Some(gl) = pk.global.as_ref() {
2965 let picks: Vec<usize> = routed.iter().map(|&(gi, _)| gi).collect();
2966 gl.pool
2967 .ensure_picks(&model, gl.layer, &picks, &l.experts, cfg.top_k, 1);
2968 }
2969 if cold.is_empty() {
2970 *folded = next;
2974 return Some(false);
2975 }
2976 if cold_x.len() < dim {
2979 return None;
2980 }
2981 let mut cold_sum = vec![0.0f32; dim];
2982 {
2983 let results: Vec<std::sync::Mutex<Vec<f32>>> = cold
2984 .iter()
2985 .map(|_| std::sync::Mutex::new(Vec::new()))
2986 .collect();
2987 let (cold_ref, results_ref, x_ref) = (&cold, &results, &cold_x[..dim]);
2988 std::thread::scope(|sc| {
2989 for i in 0..cold_ref.len() {
2990 let (gi, wt) = cold_ref[i];
2991 let Some(exp) = l.experts.get(gi) else {
2992 continue;
2993 };
2994 let r = &results_ref[i];
2995 sc.spawn(move || {
2996 let mut a = vec![0.0f32; cfg.dim];
2997 crate::gpu::cpu_scope(|| run_expert(x_ref, exp, cfg, wt, None, &mut a));
2998 *r.lock().unwrap() = a;
2999 });
3000 }
3001 });
3002 for r in &results {
3003 let a = r.lock().unwrap();
3004 for (o, v) in cold_sum.iter_mut().zip(a.iter()) {
3005 *o += v;
3006 }
3007 }
3008 }
3009 if !crate::gpu_wgpu::dsv4_state_add_cold_preserved(&cold_sum, cfg.hc_mult, state, kv_id, li) {
3010 return None;
3011 }
3012 if pk.global.is_none() {
3015 let mut dynv = pk.dynslots.lock().unwrap();
3016 dynv.clock += 1;
3017 let clock = dynv.clock;
3018 for &(gi, _) in &cold {
3019 if gi >= dynv.remap.len() || dynv.remap[gi] != u32::MAX {
3020 continue;
3021 }
3022 let victim = (0..dynv.owner.len())
3023 .filter(|&sl| dynv.last[sl] != clock)
3024 .min_by_key(|&sl| dynv.last[sl]);
3025 let Some(victim) = victim else { break };
3026 let Some(exp) = l.experts.get(gi) else {
3027 continue;
3028 };
3029 let t3 = (|| {
3030 Some((
3031 exp.w1.model_idx()?,
3032 exp.w3.model_idx()?,
3033 exp.w2.model_idx()?,
3034 ))
3035 })();
3036 let Some(t3) = t3 else { continue };
3037 let gu_q2 = exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
3038 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
3039 if !crate::gpu_wgpu::dsv4_slot_fill(
3040 &model,
3041 pack_first,
3042 victim,
3043 gi,
3044 t3,
3045 cfg.moe_inter,
3046 cfg.dim,
3047 gu_q2,
3048 ) {
3049 break;
3050 }
3051 let old = dynv.owner[victim] as usize;
3052 if old < dynv.remap.len() {
3053 dynv.remap[old] = u32::MAX;
3054 }
3055 dynv.remap[gi] = victim as u32;
3056 dynv.owner[victim] = gi as u32;
3057 dynv.last[victim] = clock;
3058 dynv.mutated = true;
3059 }
3060 }
3061 if let Some(n) = nxt {
3062 let (f, post, comb) = hc_fold_norm(
3063 state,
3064 &n.hc_attn_fn,
3065 &n.hc_attn_scale,
3066 &n.hc_attn_base,
3067 &n.attn_norm,
3068 cfg,
3069 pool,
3070 );
3071 *folded = f;
3072 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb) {
3073 return None;
3074 }
3075 }
3076 Some(true)
3077}
3078
3079#[cfg(feature = "gpu")]
3080fn chain_max() -> usize {
3081 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
3082 *N.get_or_init(|| {
3083 std::env::var("CMF_DSV4_CHAIN_MAX")
3084 .ok()
3085 .and_then(|v| v.parse().ok())
3086 .unwrap_or(usize::MAX)
3087 })
3088}
3089
3090#[cfg(feature = "gpu")]
3093fn partial_walk_on() -> bool {
3104 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3105 *ON.get_or_init(|| std::env::var("CMF_DSV4_PARTIAL_WALK").is_ok_and(|v| v != "0"))
3106}
3107
3108fn host_cpu_moe() -> bool {
3109 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3110 *ON.get_or_init(|| std::env::var("CMF_DSV4_HOST_CPU_MOE").is_ok_and(|v| v != "0"))
3111}
3112
3113fn chain_enabled() -> bool {
3114 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3115 *ON.get_or_init(|| {
3116 std::env::var("CMF_DSV4_CHAIN")
3117 .map(|v| v != "0")
3118 .unwrap_or(true)
3119 })
3120}
3121
3122#[cfg(feature = "gpu")]
3132#[allow(clippy::too_many_arguments)]
3133fn dsv4_chain_run(
3134 layers: &[Dsv4Layer],
3135 run: &[usize],
3136 cfg: &Dsv4Cfg,
3137 g: &Dsv4Globals,
3138 st: &mut Dsv4State,
3139 token_id: u32,
3140 folded: &mut Vec<f32>,
3146 state_out: Option<&mut [f32]>,
3151 batch: usize,
3154 batch_ids: &[u32],
3158 need_qn: bool,
3165 pool: Option<&crate::pool::Pool>,
3166) -> bool {
3167 if run.is_empty() {
3168 return true;
3169 }
3170 let (dim, hd) = (cfg.dim, cfg.head_dim);
3171 let first = run[0];
3172 let Some(model) = layers[first].experts.first().and_then(|e| e.w1.model_arc()) else {
3173 return false;
3174 };
3175 if batch <= 1 && need_qn {
3179 let mut qn0 = vec![0.0f32; cfg.q_lora_rank];
3180 layers[first].wq_a.matvec(folded, &mut qn0, pool);
3181 rms_weighted(&mut qn0, &layers[first].q_norm, cfg.norm_eps);
3182 if !crate::gpu_wgpu::dsv4_chain_seed(folded, &qn0) {
3183 return false;
3184 }
3185 } else if batch <= 1 && !crate::gpu_wgpu::dsv4_chain_seed_fold(folded) {
3186 return false;
3187 }
3188
3189 let mut packs = Vec::with_capacity(run.len());
3191 let mut forceds: Vec<Option<Vec<usize>>> = Vec::with_capacity(run.len());
3192 for &li in run {
3193 let Some(pk) = pack_for(&layers[li], cfg, li) else {
3194 return false;
3195 };
3196 if !pk.route_complete() || pk.is_mutated() {
3203 return false;
3204 }
3205 let forced: Option<Vec<usize>> = layers[li].tid2eid.as_ref().and_then(|tbl| {
3206 let v: Vec<usize> = if pk.needs_remap() {
3207 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
3208 } else {
3209 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
3210 .into_iter()
3211 .map(|gi| pk.to_slot[gi])
3212 .collect()
3213 };
3214 if v.contains(&usize::MAX) {
3215 None
3216 } else {
3217 Some(v)
3218 }
3219 });
3220 if layers[li].tid2eid.is_some() && forced.is_none() {
3221 return false;
3222 }
3223 forceds.push(forced);
3224 packs.push(pk);
3225 }
3226
3227 let mut items = Vec::with_capacity(run.len());
3228 let mut freqs = Vec::with_capacity(run.len());
3229 for (i, &li) in run.iter().enumerate() {
3230 let l = &layers[li];
3231 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
3232 l.wq_a.model_idx(),
3233 l.wq_b.model_idx(),
3234 l.wo_a.model_idx(),
3235 l.wo_b.model_idx(),
3236 l.wkv.model_idx(),
3237 ) else {
3238 return false;
3239 };
3240 let comp = match &l.compressor {
3241 None => None,
3242 Some(cp) => {
3243 let (Some(a), Some(b)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
3244 return false;
3245 };
3246 Some((
3247 crate::gpu_wgpu::Dsv4CompW {
3248 wkv: a,
3249 wgate: b,
3250 norm: &cp.norm,
3251 ape: &cp.ape,
3252 },
3253 crate::gpu_wgpu::Dsv4CompGeom {
3254 width: cp.wkv.rows(),
3255 hidden: dim,
3256 ratio: cp.ratio,
3257 overlap: cp.overlap,
3258 rope_dim: cfg.rope_head_dim,
3259 eps: cfg.norm_eps,
3260 },
3261 ))
3262 }
3263 };
3264 let ix = match &l.indexer {
3265 None => None,
3266 Some(ixr) => {
3267 let cp = &ixr.compressor;
3268 let (Some(a), Some(b), Some(qb), Some(wp)) = (
3269 cp.wkv.model_idx(),
3270 cp.wgate.model_idx(),
3271 ixr.wq_b.model_idx(),
3272 ixr.weights_proj.model_idx(),
3273 ) else {
3274 return false;
3275 };
3276 let ih = ixr.weights_proj.rows();
3277 Some((
3278 crate::gpu_wgpu::Dsv4CompW {
3279 wkv: a,
3280 wgate: b,
3281 norm: &cp.norm,
3282 ape: &cp.ape,
3283 },
3284 crate::gpu_wgpu::Dsv4CompGeom {
3285 width: cp.wkv.rows(),
3286 hidden: dim,
3287 ratio: cp.ratio,
3288 overlap: cp.overlap,
3289 rope_dim: cfg.rope_head_dim,
3290 eps: cfg.norm_eps,
3291 },
3292 crate::gpu_wgpu::Dsv4IxW {
3293 wq_b: qb,
3294 weights_proj: wp,
3295 },
3296 crate::gpu_wgpu::Dsv4IxGeom {
3297 ih,
3298 idim: ixr.wq_b.rows() / ih.max(1),
3299 q_lora: cfg.q_lora_rank,
3300 hidden: dim,
3301 rope_dim: cfg.rope_head_dim,
3302 eps: cfg.norm_eps,
3303 top_k: cfg.index_topk,
3304 window: cfg.window,
3305 },
3306 ))
3307 }
3308 };
3309 let ew_c0 = l.compressor.as_ref().map_or(0, |cp| {
3313 if cp.overlap {
3314 cp.wkv.rows() / 2
3315 } else {
3316 cp.wkv.rows()
3317 }
3318 });
3319 let comp_extra = l
3320 .compressor
3321 .as_ref()
3322 .map_or(0, |cp| batch.max(1).div_ceil(cp.ratio.max(1)));
3323 let need = cfg.window * hd
3324 + (st.dev_n_comp[li] + comp_extra + 1) * ew_c0.max(1)
3325 + (batch.max(1) + 1) * hd;
3326 if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
3327 return false;
3328 }
3329 let ew_c = comp.as_ref().map_or(
3330 0,
3331 |(_, cg)| {
3332 if cg.overlap { cg.width / 2 } else { cg.width }
3333 },
3334 );
3335 let ew_i = ix.as_ref().map_or(
3336 0,
3337 |(_, cg, _, _)| {
3338 if cg.overlap { cg.width / 2 } else { cg.width }
3339 },
3340 );
3341 let prep = crate::gpu_wgpu::Dsv4Prep {
3342 wkv,
3343 kv_norm: &l.kv_norm,
3344 comp,
3345 ix,
3346 filled: st.dev_filled[li],
3347 window: cfg.window,
3348 n_comp: st.dev_n_comp[li],
3349 n_ix: st.dev_n_ix[li],
3350 comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
3351 ix_dst_off: st.dev_n_ix[li] * ew_i,
3352 idx_cap: cfg.window
3353 + if l.indexer.is_some() {
3354 cfg.index_topk
3355 } else {
3356 st.dev_n_comp[li] + comp_extra + 1
3357 },
3358 };
3359 let nxt = layers.get(li + 1);
3360 let w = crate::gpu_wgpu::Dsv4LayerW {
3361 attn: crate::gpu_wgpu::Dsv4AttnW {
3362 wq_a,
3363 wq_b,
3364 wo_a,
3365 wo_b,
3366 q_norm: &l.q_norm,
3367 sink: &l.attn_sink,
3368 },
3369 moe: crate::gpu_wgpu::Dsv4MoeW {
3370 router: &packs[i].router,
3371 experts: &packs[i].tensors,
3372 logits: &[],
3373 bias: packs[i].bias.as_deref(),
3376 mask: packs[i].mask.as_deref(),
3377 forced: forceds[i].as_deref(),
3378 remap: packs[i].needs_remap().then_some(packs[i].remap.as_slice()),
3379 global: None,
3380 has_shared: true,
3381 shared_weight: 1.0,
3382 preweighted: false,
3383 qwen_softmax: false,
3384 },
3385 hc_ffn_fn: &l.hc_ffn_fn,
3386 hc_ffn_scale: &l.hc_ffn_scale,
3387 hc_ffn_base: &l.hc_ffn_base,
3388 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
3389 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
3390 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
3391 ffn_norm: &l.ffn_norm,
3392 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
3393 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
3394 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
3395 router: &packs[i].router,
3396 };
3397 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
3398 attn: crate::gpu_wgpu::Dsv4AttnGeom {
3399 dim,
3400 nh: cfg.n_heads,
3401 hd,
3402 rd: cfg.rope_head_dim,
3403 q_lora: cfg.q_lora_rank,
3404 o_lora: cfg.o_lora_rank,
3405 o_groups: cfg.o_groups,
3406 eps: cfg.norm_eps,
3407 scale: (hd as f32).powf(-0.5),
3408 bf16: false,
3409 q_rms: true,
3410 },
3411 moe: crate::gpu_wgpu::Dsv4MoeGeom {
3412 hidden: dim,
3413 inter: cfg.moe_inter,
3414 top_k: cfg.top_k,
3415 route_scale: cfg.route_scale,
3416 swiglu_limit: cfg.swiglu_limit,
3417 gu_q2: l.experts.first().is_some_and(|e| {
3418 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
3419 }),
3420 bf16: false,
3421 },
3422 hc: cfg.hc_mult,
3423 hc_eps: cfg.hc_eps,
3424 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3425 };
3426 freqs.push(if l.compressor.is_some() {
3427 g.inv_freq_compress.as_slice()
3428 } else {
3429 g.inv_freq_window.as_slice()
3430 });
3431 items.push((w, geom, prep));
3432 }
3433
3434 let mut out = vec![0.0f32; dim * batch.max(1)];
3435 if batch > 1 {
3436 let mut forced_pt: Vec<Vec<Option<Vec<usize>>>> = Vec::with_capacity(batch);
3441 for t in 0..batch {
3442 let id = batch_ids.get(t).copied().unwrap_or(token_id);
3443 let mut row = Vec::with_capacity(run.len());
3444 for (i, &li) in run.iter().enumerate() {
3445 row.push(layers[li].tid2eid.as_ref().and_then(|tbl| {
3446 let v: Vec<usize> = if packs[i].needs_remap() {
3447 hash_route(tbl, cfg.vocab, cfg.top_k, id)
3448 } else {
3449 hash_route(tbl, cfg.vocab, cfg.top_k, id)
3450 .into_iter()
3451 .map(|gi| packs[i].to_slot[gi])
3452 .collect()
3453 };
3454 if v.contains(&usize::MAX) {
3455 None
3456 } else {
3457 Some(v)
3458 }
3459 }));
3460 if layers[li].tid2eid.is_some() && row[i].is_none() {
3461 return false;
3462 }
3463 }
3464 forced_pt.push(row);
3465 }
3466 if !crate::gpu_wgpu::dsv4_chain_batch(
3467 &model,
3468 &items,
3469 st.kv_id,
3470 first,
3471 &freqs,
3472 st.pos,
3473 batch,
3474 Some(&forced_pt),
3475 &mut out,
3476 state_out,
3477 ) {
3478 return false;
3479 }
3480 *folded = out[(batch - 1) * dim..batch * dim].to_vec();
3483 } else {
3484 if !crate::gpu_wgpu::dsv4_layer_chain(
3485 &model, &items, st.kv_id, first, &freqs, st.pos, &mut out, state_out,
3486 ) {
3487 return false;
3488 }
3489 *folded = out;
3490 }
3491 for (i, &li) in run.iter().enumerate() {
3495 for t in 0..batch.max(1) {
3496 let pos = st.pos + t;
3497 st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
3498 if let Some((_, cg, ..)) = items[i].2.ix.as_ref() {
3499 if (pos + 1) % cg.ratio == 0 {
3500 st.dev_n_ix[li] += 1;
3501 }
3502 }
3503 if let Some((_, cg)) = items[i].2.comp.as_ref() {
3504 if (pos + 1) % cg.ratio == 0 {
3505 st.dev_n_comp[li] += 1;
3506 }
3507 }
3508 }
3509 }
3510 st.dev_owned = true;
3511 true
3512}
3513
3514#[cfg(feature = "gpu")]
3516fn hc_on_device() -> bool {
3517 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3518 *ON.get_or_init(|| {
3519 std::env::var("CMF_DSV4_HC_DEV").is_ok_and(|v| v != "0") && crate::gpu::backend_available()
3525 })
3526}
3527
3528#[cfg(feature = "gpu")]
3537#[allow(clippy::too_many_arguments)]
3538fn dsv4_two_frame_loop(
3539 state: &mut [f32],
3540 layers: &[Dsv4Layer],
3541 g: &Dsv4Globals,
3542 cfg: &Dsv4Cfg,
3543 st: &mut Dsv4State,
3544 token_id: u32,
3545 inv_freq: &[f32],
3546 pool: Option<&crate::pool::Pool>,
3547 scratch: &mut HcScratch,
3548) -> bool {
3549 let dim = cfg.dim;
3550 let freqs_of = |l: &Dsv4Layer| -> &[f32] {
3551 let f = if l.compressor.is_some() {
3552 &g.inv_freq_compress
3553 } else {
3554 &g.inv_freq_window
3555 };
3556 if f.is_empty() { inv_freq } else { f.as_slice() }
3557 };
3558 let (mut folded, post0, comb0) = hc_fold_norm(
3560 state,
3561 &layers[0].hc_attn_fn,
3562 &layers[0].hc_attn_scale,
3563 &layers[0].hc_attn_base,
3564 &layers[0].attn_norm,
3565 cfg,
3566 pool,
3567 );
3568 if !crate::gpu_wgpu::dsv4_state_write(state) || !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
3569 {
3570 return false;
3571 }
3572 let mut on_dev = vec![false; layers.len()];
3577 for (li, l) in layers.iter().enumerate() {
3578 let Some(pk) = pack_for(l, cfg, li) else {
3579 return false;
3580 };
3581 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
3582 return false;
3583 };
3584 let gu_q2 = l
3585 .experts
3586 .first()
3587 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
3588 let attn_ok = [
3589 l.wq_a.model_idx(),
3590 l.wq_b.model_idx(),
3591 l.wo_a.model_idx(),
3592 l.wo_b.model_idx(),
3593 ]
3594 .into_iter()
3595 .flatten()
3596 .all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
3597 on_dev[li] = attn_ok
3598 && pk.route_complete()
3599 && crate::gpu_wgpu::dsv4_experts_ready(
3600 &model,
3601 &pk.tensors,
3602 cfg.moe_inter,
3603 dim,
3604 gu_q2,
3605 l.experts.first().is_some_and(|e| {
3606 e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
3607 }),
3608 );
3609 }
3610 if !on_dev.iter().any(|&x| x) {
3611 return false;
3612 }
3613 let mut sink = vec![0.0f32; dim];
3614 for (li, l) in layers.iter().enumerate() {
3615 if !on_dev[li] {
3619 if !crate::gpu_wgpu::dsv4_state_read(state) {
3620 return false;
3621 }
3622 let freqs = freqs_of(l);
3623 hc_block(
3624 state,
3625 &l.hc_attn_fn,
3626 &l.hc_attn_scale,
3627 &l.hc_attn_base,
3628 &l.attn_norm,
3629 cfg,
3630 scratch,
3631 pool,
3632 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
3633 );
3634 hc_block(
3635 state,
3636 &l.hc_ffn_fn,
3637 &l.hc_ffn_scale,
3638 &l.hc_ffn_base,
3639 &l.ffn_norm,
3640 cfg,
3641 scratch,
3642 pool,
3643 |f, o| moe_step(f, l, cfg, token_id, li, pool, o),
3644 );
3645 let nref = layers.get(li + 1).unwrap_or(l);
3646 let (f, p2, c2) = hc_fold_norm(
3647 state,
3648 &nref.hc_attn_fn,
3649 &nref.hc_attn_scale,
3650 &nref.hc_attn_base,
3651 &nref.attn_norm,
3652 cfg,
3653 pool,
3654 );
3655 folded = f;
3656 if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2)
3657 || !crate::gpu_wgpu::dsv4_state_write(state)
3658 {
3659 return false;
3660 }
3661 continue;
3662 }
3663 let mut prep = AttnPrep::default();
3665 attention_step(
3666 &folded,
3667 l,
3668 cfg,
3669 st,
3670 li,
3671 freqs_of(l),
3672 pool,
3673 Some(&mut prep),
3674 &mut sink,
3675 );
3676 let hd = cfg.head_dim;
3677 let n_comp = st.compressed[li].len() / hd;
3678 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
3679 if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
3680 || (n_comp > 0
3681 && !crate::gpu_wgpu::dsv4_cache_write(
3682 st.kv_id,
3683 li,
3684 cfg.window * hd,
3685 &st.compressed[li],
3686 cap,
3687 ))
3688 {
3689 return false;
3690 }
3691 let _idx32: Vec<u32> = prep
3692 .idxs
3693 .iter()
3694 .map(|&p| {
3695 if p < prep.win_len {
3696 p as u32
3697 } else {
3698 (cfg.window + (p - prep.win_len)) as u32
3699 }
3700 })
3701 .collect();
3702 let nxt = layers.get(li + 1);
3703 let a_tail = crate::gpu_wgpu::Dsv4HcTail {
3704 fn_: &l.hc_ffn_fn,
3705 scale: &l.hc_ffn_scale,
3706 base: &l.hc_ffn_base,
3707 norm: &l.ffn_norm,
3708 hc: cfg.hc_mult,
3709 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3710 hc_eps: cfg.hc_eps,
3711 eps: cfg.norm_eps,
3712 };
3713 let scale = (cfg.head_dim as f32).powf(-0.5);
3714 if !attn_frame(
3715 l,
3716 cfg,
3717 st,
3718 li,
3719 &folded,
3720 &prep.qr,
3721 &prep.idxs,
3722 freqs_of(l),
3723 st.pos,
3724 prep.win_len,
3725 scale,
3726 Some(&a_tail),
3727 &mut [],
3728 ) {
3729 return false;
3730 }
3731 let m_tail = nxt.map(|n| crate::gpu_wgpu::Dsv4HcTail {
3732 fn_: &n.hc_attn_fn,
3733 scale: &n.hc_attn_scale,
3734 base: &n.hc_attn_base,
3735 norm: &n.attn_norm,
3736 hc: cfg.hc_mult,
3737 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3738 hc_eps: cfg.hc_eps,
3739 eps: cfg.norm_eps,
3740 });
3741 let mut next = vec![0.0f32; dim];
3742 let pair = m_tail
3743 .as_ref()
3744 .zip(nxt)
3745 .map(|(t, n)| (t, n.attn_norm.as_slice()));
3746 let forced = l
3747 .tid2eid
3748 .as_ref()
3749 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
3750 if moe_frame(
3751 &[],
3752 l,
3753 cfg,
3754 li,
3755 &[],
3756 forced.as_deref(),
3757 pool,
3758 Some(&a_tail),
3759 pair,
3760 &mut next,
3761 )
3762 .is_none()
3763 {
3764 return false;
3765 }
3766 folded = next;
3767 }
3768 let _ = scratch;
3769 crate::gpu_wgpu::dsv4_state_read(state)
3770}
3771
3772#[cfg(feature = "gpu")]
3776#[allow(clippy::too_many_arguments)]
3777fn hc_fold_norm(
3778 state: &[f32],
3779 hc_fn: &[f32],
3780 hc_scale: &[f32; 3],
3781 hc_base: &[f32],
3782 norm_w: &[f32],
3783 cfg: &Dsv4Cfg,
3784 pool: Option<&crate::pool::Pool>,
3785) -> (Vec<f32>, Vec<f32>, Vec<f32>) {
3786 let (hc, dim) = (cfg.hc_mult, cfg.dim);
3787 let mix_hc = (2 + hc) * hc;
3788 let mut mixes = vec![0.0f32; mix_hc];
3789 hc_mixes(state, hc_fn, mix_hc, cfg.norm_eps, pool, &mut mixes);
3790 let mut pre = vec![0.0f32; hc];
3791 let mut post = vec![0.0f32; hc];
3792 let mut comb = vec![0.0f32; hc * hc];
3793 hc_split_sinkhorn(
3794 &mixes,
3795 hc_scale,
3796 hc_base,
3797 hc,
3798 cfg.hc_sinkhorn_iters,
3799 cfg.hc_eps,
3800 &mut pre,
3801 &mut post,
3802 &mut comb,
3803 );
3804 let mut folded = vec![0.0f32; dim];
3805 hc_fold(state, &pre, hc, dim, &mut folded);
3806 rms_weighted(&mut folded, norm_w, cfg.norm_eps);
3807 (folded, post, comb)
3810}
3811
3812#[cfg(feature = "gpu")]
3829fn gpu_layer_enabled() -> bool {
3830 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3831 *ON.get_or_init(|| {
3832 std::env::var("CMF_DSV4_GPU_LAYER")
3833 .map(|v| v != "0")
3834 .unwrap_or(true)
3835 && crate::gpu::backend_available()
3836 })
3837}
3838
3839#[cfg(feature = "gpu")]
3843struct PackDyn {
3844 remap: Vec<u32>,
3845 owner: Vec<u32>,
3846 last: Vec<u64>,
3847 clock: u64,
3848 seen: Vec<u16>,
3855 mutated: bool,
3859}
3860
3861#[cfg(feature = "gpu")]
3866#[derive(Clone, Copy)]
3867struct GlobalOwner {
3868 layer: usize,
3869 expert: usize,
3870 pinned: bool,
3871}
3872
3873#[cfg(feature = "gpu")]
3874struct GlobalPoolDyn {
3875 slot_for: std::collections::HashMap<(usize, usize), u32>,
3876 owner: Vec<Option<GlobalOwner>>,
3877 last: Vec<u64>,
3878 seen: std::collections::HashMap<(usize, usize), u16>,
3879 occupancy: std::collections::HashMap<usize, usize>,
3880 clock: u64,
3881}
3882
3883#[cfg(feature = "gpu")]
3891struct GlobalPool {
3892 uid: u64,
3893 capacity: usize,
3894 segment_slots: usize,
3895 floor: usize,
3896 state: std::sync::Mutex<GlobalPoolDyn>,
3897}
3898
3899#[cfg(feature = "gpu")]
3900impl GlobalPool {
3901 fn remap(&self, layer: usize, n: usize) -> Vec<u32> {
3902 let st = self.state.lock().unwrap();
3903 debug_assert_eq!(self.capacity, st.owner.len());
3904 (0..n)
3905 .map(|e| st.slot_for.get(&(layer, e)).copied().unwrap_or(u32::MAX))
3906 .collect()
3907 }
3908
3909 fn seed_layer(
3910 &self,
3911 model: &std::sync::Arc<cortiq_core::CmfModel>,
3912 layer: usize,
3913 shared: (usize, usize, usize),
3914 _routed: &[(usize, (usize, usize, usize))],
3915 ) -> Option<u32> {
3916 let mut st = self.state.lock().unwrap();
3917 let shared_key = (layer, usize::MAX);
3918 let shared_slot = if let Some(&slot) = st.slot_for.get(&shared_key) {
3919 slot
3920 } else {
3921 let slot = st.owner.iter().position(Option::is_none)?;
3922 if !crate::gpu_wgpu::dsv4_global_slot_fill(model, slot, shared) {
3923 return None;
3924 }
3925 st.owner[slot] = Some(GlobalOwner {
3926 layer,
3927 expert: usize::MAX,
3928 pinned: true,
3929 });
3930 st.slot_for.insert(shared_key, slot as u32);
3931 *st.occupancy.entry(layer).or_insert(0) += 1;
3932 slot as u32
3933 };
3934 Some(shared_slot)
3939 }
3940
3941 fn ensure_picks(
3942 &self,
3943 model: &std::sync::Arc<cortiq_core::CmfModel>,
3944 layer: usize,
3945 picks: &[usize],
3946 experts: &[Dsv4Expert],
3947 quota: usize,
3948 min_seen: u16,
3949 ) -> Vec<u32> {
3950 let mut st = self.state.lock().unwrap();
3951 st.clock = st.clock.saturating_add(1);
3952 let clock = st.clock;
3953 if clock % 64 == 0 {
3954 for v in st.seen.values_mut() {
3955 *v >>= 1;
3956 }
3957 }
3958 for &expert in picks {
3959 let key = (layer, expert);
3960 let seen = st.seen.entry(key).or_insert(0);
3961 *seen = seen.saturating_add(1);
3962 if let Some(&slot) = st.slot_for.get(&key) {
3963 st.last[slot as usize] = clock;
3964 }
3965 }
3966 let mut fetched = 0usize;
3967 for &expert in picks {
3968 if fetched >= quota {
3969 break;
3970 }
3971 let key = (layer, expert);
3972 if st.slot_for.contains_key(&key) || st.seen.get(&key).copied().unwrap_or(0) < min_seen
3973 {
3974 continue;
3975 }
3976 let Some(exp) = experts.get(expert) else {
3977 continue;
3978 };
3979 let Some(tensors) = (|| {
3980 Some((
3981 exp.w1.model_idx()?,
3982 exp.w3.model_idx()?,
3983 exp.w2.model_idx()?,
3984 ))
3985 })() else {
3986 continue;
3987 };
3988 let empty = st.owner.iter().position(Option::is_none);
3989 let over_floor = |o: GlobalOwner, occ: &std::collections::HashMap<usize, usize>| {
3992 occ.get(&o.layer).copied().unwrap_or(0) > self.floor
3993 };
3994 let eligible =
3995 |o: GlobalOwner| !o.pinned && !(o.layer == layer && picks.contains(&o.expert));
3996 let victim = empty
3997 .or_else(|| {
3998 st.owner
3999 .iter()
4000 .enumerate()
4001 .filter_map(|(slot, &o)| {
4002 o.filter(|&x| eligible(x) && over_floor(x, &st.occupancy))
4003 .map(|_| slot)
4004 })
4005 .min_by_key(|&slot| st.last[slot])
4006 })
4007 .or_else(|| {
4008 st.owner
4012 .iter()
4013 .enumerate()
4014 .filter_map(|(slot, &o)| {
4015 o.filter(|&x| eligible(x) && x.layer == layer).map(|_| slot)
4016 })
4017 .min_by_key(|&slot| st.last[slot])
4018 })
4019 .or_else(|| {
4020 st.owner
4024 .iter()
4025 .enumerate()
4026 .filter_map(|(slot, &o)| o.filter(|&x| eligible(x)).map(|_| slot))
4027 .min_by_key(|&slot| st.last[slot])
4028 });
4029 let Some(victim) = victim else { break };
4030 if !crate::gpu_wgpu::dsv4_global_slot_fill(model, victim, tensors) {
4031 break;
4032 }
4033 if let Some(old) = st.owner[victim] {
4034 st.slot_for.remove(&(old.layer, old.expert));
4035 if let Some(n) = st.occupancy.get_mut(&old.layer) {
4036 *n = n.saturating_sub(1);
4037 }
4038 }
4039 st.owner[victim] = Some(GlobalOwner {
4040 layer,
4041 expert,
4042 pinned: false,
4043 });
4044 st.slot_for.insert(key, victim as u32);
4045 *st.occupancy.entry(layer).or_insert(0) += 1;
4046 st.last[victim] = clock;
4047 fetched += 1;
4048 }
4049 drop(st);
4050 self.remap(layer, experts.len())
4051 }
4052
4053 fn pin_picks(
4059 &self,
4060 model: &std::sync::Arc<cortiq_core::CmfModel>,
4061 layer: usize,
4062 picks: &[usize],
4063 experts: &[Dsv4Expert],
4064 ) -> Vec<u32> {
4065 let remap = self.ensure_picks(model, layer, picks, experts, picks.len(), 1);
4066 let mut st = self.state.lock().unwrap();
4067 for &expert in picks {
4068 if let Some(&slot) = st.slot_for.get(&(layer, expert)) {
4069 if let Some(owner) = st.owner[slot as usize].as_mut() {
4070 owner.pinned = true;
4071 }
4072 }
4073 }
4074 remap
4075 }
4076}
4077
4078#[cfg(feature = "gpu")]
4079#[derive(Clone)]
4080struct GlobalPack {
4081 pool: std::sync::Arc<GlobalPool>,
4082 layer: usize,
4083 shared_slot: u32,
4084}
4085
4086#[cfg(feature = "gpu")]
4087impl Pack {
4088 fn is_mutated(&self) -> bool {
4090 self.global.is_some() || self.dynslots.lock().unwrap().mutated
4091 }
4092
4093 fn route_complete(&self) -> bool {
4099 if self.global.is_some() {
4100 return false;
4101 }
4102 let need = self
4103 .mask
4104 .as_deref()
4105 .map_or(self.remap.len(), |m| m.iter().filter(|&&x| x != 0).count());
4106 self.globals.len() >= need
4107 }
4108
4109 fn needs_remap(&self) -> bool {
4113 self.global.is_some()
4114 || self.mask.is_some()
4115 || self
4116 .remap
4117 .iter()
4118 .enumerate()
4119 .any(|(i, &slot)| slot != i as u32)
4120 }
4121}
4122
4123#[cfg(feature = "gpu")]
4127struct Pack {
4128 router: Vec<f32>,
4132 to_slot: Vec<usize>,
4134 remap: Vec<u32>,
4136 globals: Vec<usize>,
4138 tensors: Vec<(usize, usize, usize)>,
4139 mask: Option<Vec<u32>>,
4143 dynslots: std::sync::Mutex<PackDyn>,
4152 bias: Option<Vec<f32>>,
4161 global: Option<GlobalPack>,
4165}
4166
4167#[cfg(feature = "gpu")]
4168fn pack_freq_order(li: usize, n: usize) -> Option<Vec<usize>> {
4174 use std::collections::HashMap;
4175 use std::sync::OnceLock;
4176 static FREQ: OnceLock<Option<HashMap<(usize, usize), u64>>> = OnceLock::new();
4177 let map = FREQ
4178 .get_or_init(|| {
4179 let path = std::env::var("CMF_DSV4_PACK_FREQ").ok()?;
4180 let text = match std::fs::read_to_string(&path) {
4181 Ok(t) => t,
4182 Err(e) => {
4183 eprintln!("CMF_DSV4_PACK_FREQ={path} не читается ({e}) — порядок по id");
4184 return None;
4185 }
4186 };
4187 let mut m = HashMap::new();
4188 for line in text.lines() {
4189 let mut it = line.split('\t');
4190 if let (Some(l), Some(e), Some(c)) = (it.next(), it.next(), it.next()) {
4191 if let (Ok(l), Ok(e), Ok(c)) =
4192 (l.trim().parse(), e.trim().parse(), c.trim().parse::<u64>())
4193 {
4194 *m.entry((l, e)).or_insert(0) += c;
4195 }
4196 }
4197 }
4198 Some(m)
4199 })
4200 .as_ref()?;
4201 if !(0..n).any(|e| map.contains_key(&(li, e))) {
4202 return None;
4203 }
4204 let mut idx: Vec<usize> = (0..n).collect();
4205 idx.sort_by_key(|&e| {
4206 (
4207 std::cmp::Reverse(map.get(&(li, e)).copied().unwrap_or(0)),
4208 e,
4209 )
4210 });
4211 Some(idx)
4212}
4213
4214#[cfg(feature = "gpu")]
4215fn global_pool_for(
4216 model: &std::sync::Arc<cortiq_core::CmfModel>,
4217 cfg: &Dsv4Cfg,
4218 gu_q2: bool,
4219 dn_q2: bool,
4220) -> Option<std::sync::Arc<GlobalPool>> {
4221 use std::collections::HashMap;
4222 use std::sync::{Arc, Mutex, OnceLock};
4223 if dn_q2
4224 || !crate::gpu_wgpu::dsv4_global_moe_supported()
4225 || std::env::var("CMF_MOE_MASK").is_ok()
4226 {
4227 return None;
4228 }
4229 static POOLS: OnceLock<Mutex<HashMap<u64, Arc<GlobalPool>>>> = OnceLock::new();
4230 let pools = POOLS.get_or_init(|| Mutex::new(HashMap::new()));
4231 if let Some(p) = pools.lock().unwrap().get(&model.uid()).cloned() {
4232 return Some(p);
4233 }
4234 let requested = crate::gpu_wgpu::dsv4_experts_fit(cfg.moe_inter, cfg.dim, gu_q2, false);
4235 let (capacity, segment_slots) =
4236 crate::gpu_wgpu::dsv4_global_moe_create(model, requested, cfg.moe_inter, cfg.dim, gu_q2)?;
4237 let layers = model.header.arch.num_layers.max(1);
4238 let p = Arc::new(GlobalPool {
4239 uid: model.uid(),
4240 capacity,
4241 segment_slots,
4242 floor: (capacity / layers).max(2),
4245 state: Mutex::new(GlobalPoolDyn {
4246 slot_for: HashMap::new(),
4247 owner: vec![None; capacity],
4248 last: vec![0; capacity],
4249 seen: HashMap::new(),
4250 occupancy: HashMap::new(),
4251 clock: 0,
4252 }),
4253 });
4254 pools.lock().unwrap().insert(model.uid(), p.clone());
4255 Some(p)
4256}
4257
4258#[cfg(feature = "gpu")]
4259fn pack_for(l: &Dsv4Layer, cfg: &Dsv4Cfg, li: usize) -> Option<std::sync::Arc<Pack>> {
4260 use std::collections::HashMap;
4261 use std::sync::{Arc, Mutex, OnceLock};
4262 static CACHE: OnceLock<Mutex<HashMap<(u64, usize, usize), Option<Arc<Pack>>>>> =
4263 OnceLock::new();
4264 let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
4265 let model_uid = l
4271 .experts
4272 .first()
4273 .and_then(|e| e.w1.model_arc())
4274 .map_or(0, |m| m.uid());
4275 let first_expert = l
4281 .experts
4282 .first()
4283 .and_then(|e| e.w1.model_idx())
4284 .unwrap_or(usize::MAX);
4285 let key = (model_uid, li, first_expert);
4286 if let Some(v) = cache.lock().unwrap().get(&key) {
4287 return v.clone();
4288 }
4289 if let Ok(v) = std::env::var("CMF_DSV4_PACK_MAX_LI") {
4296 if v.parse::<usize>().is_ok_and(|max| li > max) {
4297 cache.lock().unwrap().insert(key, None);
4298 return None;
4299 }
4300 }
4301 let build = || -> Option<Arc<Pack>> {
4302 let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
4303 let mut globals = Vec::new();
4304 let mut tensors = Vec::new();
4305 let idx3 = |e: &Dsv4Expert| -> Option<(usize, usize, usize)> {
4306 Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
4307 };
4308 let route_mask: Option<Vec<u32>> = if l.tid2eid.is_none() {
4309 l.mask
4310 .as_deref()
4311 .map(|m| m.iter().map(|&open| u32::from(open)).collect())
4312 } else {
4313 None
4314 };
4315 let gu_q2 = l
4320 .experts
4321 .first()
4322 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4323 let dn_q2 = l
4324 .experts
4325 .first()
4326 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4327 if route_mask.is_none() {
4332 if let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) {
4333 if let Some(gp) = global_pool_for(&model, cfg, gu_q2, dn_q2) {
4334 let layer_key = first_expert;
4335 let order = pack_freq_order(li, l.experts.len())
4336 .unwrap_or_else(|| (0..l.experts.len()).collect());
4337 let routed: Vec<_> = order
4338 .into_iter()
4339 .filter_map(|gi| Some((gi, idx3(&l.experts[gi])?)))
4340 .collect();
4341 let shared = idx3(&l.shared)?;
4342 let shared_slot = gp.seed_layer(&model, layer_key, shared, &routed)?;
4343 let remap = gp.remap(layer_key, cfg.n_routed_experts);
4344 let to_slot: Vec<usize> = remap
4345 .iter()
4346 .map(|&s| {
4347 if s == u32::MAX {
4348 usize::MAX
4349 } else {
4350 s as usize
4351 }
4352 })
4353 .collect();
4354 let globals: Vec<usize> = remap
4355 .iter()
4356 .enumerate()
4357 .filter_map(|(e, &s)| (s != u32::MAX).then_some(e))
4358 .collect();
4359 let (rows, cols) = (l.gate.rows(), l.gate.cols());
4360 let mut router = vec![0.0f32; rows * cols];
4361 for r in 0..rows {
4362 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4363 }
4364 return Some(Arc::new(Pack {
4365 bias: l.gate_bias.clone(),
4366 mask: None,
4367 router,
4368 to_slot,
4369 remap: remap.clone(),
4370 globals,
4371 tensors: Vec::new(),
4374 dynslots: std::sync::Mutex::new(PackDyn {
4375 remap,
4376 owner: Vec::new(),
4377 last: Vec::new(),
4378 clock: 0,
4379 mutated: true,
4380 seen: vec![0; cfg.n_routed_experts],
4381 }),
4382 global: Some(GlobalPack {
4383 pool: gp,
4384 layer: layer_key,
4385 shared_slot,
4386 }),
4387 }));
4388 }
4389 }
4390 }
4391 if let Some(n) = std::env::var("CMF_DSV4_PACK_MAX")
4400 .ok()
4401 .and_then(|v| v.parse::<usize>().ok())
4402 {
4403 let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
4404 let mut globals = Vec::new();
4405 let mut tensors = Vec::new();
4406 for (gi, e) in l.experts.iter().enumerate() {
4407 if route_mask
4408 .as_deref()
4409 .is_some_and(|m| m.get(gi).copied().unwrap_or(1) == 0)
4410 {
4411 continue;
4412 }
4413 if globals.len() >= n {
4414 break;
4415 }
4416 to_slot[gi] = globals.len();
4417 globals.push(gi);
4418 tensors.push(idx3(e)?);
4419 }
4420 tensors.push(idx3(&l.shared)?);
4421 let (rows, cols) = (l.gate.rows(), l.gate.cols());
4422 let mut router = vec![0.0f32; rows * cols];
4423 for r in 0..rows {
4424 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4425 }
4426 let remap: Vec<u32> = to_slot
4427 .iter()
4428 .map(|&sl| {
4429 if sl == usize::MAX {
4430 u32::MAX
4431 } else {
4432 sl as u32
4433 }
4434 })
4435 .collect();
4436 return Some(Arc::new(Pack {
4437 bias: l.gate_bias.clone(),
4438 mask: route_mask.clone(),
4439 router,
4440 to_slot,
4441 dynslots: std::sync::Mutex::new(PackDyn {
4442 remap: remap.clone(),
4443 owner: globals.iter().map(|&g| g as u32).collect(),
4444 last: vec![0; globals.len()],
4445 clock: 0,
4446 mutated: false,
4447 seen: vec![0; cfg.n_routed_experts],
4448 }),
4449 remap,
4450 globals,
4451 tensors,
4452 global: None,
4453 }));
4454 }
4455 let dn_q2_fit = l
4456 .experts
4457 .first()
4458 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4459 let room = crate::gpu_wgpu::dsv4_experts_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2_fit)
4460 .saturating_sub(1);
4461 let remaining_layers = l
4472 .experts
4473 .first()
4474 .and_then(|e| e.w1.model_arc())
4475 .map(|m| m.header.arch.num_layers.saturating_sub(li).max(1))
4476 .unwrap_or(1);
4477 let auto_cap = if l.mask.is_some() {
4478 room
4483 } else if l.tid2eid.is_some() && room >= cfg.n_routed_experts {
4484 cfg.n_routed_experts
4485 } else {
4486 room.div_ceil(remaining_layers).max(1)
4487 };
4488 let room = match std::env::var("CMF_DSV4_PACK_LAYER_CAP")
4489 .ok()
4490 .and_then(|v| v.parse::<usize>().ok())
4491 {
4492 Some(0) => room,
4493 Some(cap) => room.min(cap),
4494 None => room.min(auto_cap),
4495 };
4496 let order =
4505 pack_freq_order(li, l.experts.len()).unwrap_or_else(|| (0..l.experts.len()).collect());
4506 for gi in order {
4507 let e = &l.experts[gi];
4508 if l.mask
4509 .as_deref()
4510 .is_some_and(|m| !m.get(gi).copied().unwrap_or(true))
4511 {
4512 continue;
4513 }
4514 if globals.len() >= room {
4515 break;
4516 }
4517 to_slot[gi] = globals.len();
4518 globals.push(gi);
4519 match idx3(e) {
4520 Some(t) => tensors.push(t),
4521 None => {
4522 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
4523 eprintln!("слой {li}: эксперт {gi} без индексов в каталоге");
4524 }
4525 return None;
4526 }
4527 }
4528 }
4529 if globals.is_empty() {
4530 if room == 0 {
4536 static SAID_ZERO: std::sync::atomic::AtomicBool =
4537 std::sync::atomic::AtomicBool::new(false);
4538 if !SAID_ZERO.swap(true, std::sync::atomic::Ordering::Relaxed) {
4539 tracing::warn!(
4540 "начиная со слоя {li}, в бюджете VRAM не осталось места даже под одного \
4541 эксперта — остальные веса остаются mmap-backed и читаются по требованию"
4542 );
4543 }
4544 } else {
4545 tracing::warn!("слой {li}: маска не оставила ни одного эксперта");
4546 }
4547 return None;
4548 }
4549 tensors.push(idx3(&l.shared)?); let (rows, cols) = (l.gate.rows(), l.gate.cols());
4551 let mut router = vec![0.0f32; rows * cols];
4552 for r in 0..rows {
4553 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4554 }
4555 let remap: Vec<u32> = to_slot
4556 .iter()
4557 .map(|&sl| {
4558 if sl == usize::MAX {
4559 u32::MAX
4560 } else {
4561 sl as u32
4562 }
4563 })
4564 .collect();
4565 Some(Arc::new(Pack {
4566 bias: l.gate_bias.clone(),
4567 mask: route_mask,
4568 router,
4569 to_slot,
4570 dynslots: std::sync::Mutex::new(PackDyn {
4571 remap: remap.clone(),
4572 owner: globals.iter().map(|&g| g as u32).collect(),
4573 last: vec![0; globals.len()],
4574 clock: 0,
4575 mutated: false,
4576 seen: vec![0; cfg.n_routed_experts],
4577 }),
4578 remap,
4579 globals,
4580 tensors,
4581 global: None,
4582 }))
4583 };
4584 let v = build();
4585 cache.lock().unwrap().insert(key, v.clone());
4586 v
4587}
4588
4589#[cfg(feature = "gpu")]
4597fn moe_frame(
4598 hidden: &[f32],
4599 l: &Dsv4Layer,
4600 cfg: &Dsv4Cfg,
4601 li: usize,
4602 logits: &[f32],
4603 forced: Option<&[usize]>,
4604 pool: Option<&crate::pool::Pool>,
4605 hc_cur: Option<&crate::gpu_wgpu::Dsv4HcTail>,
4608 hc_next: Option<(&crate::gpu_wgpu::Dsv4HcTail, &[f32])>,
4609 out: &mut [f32],
4610) -> Option<(Vec<f32>, usize)> {
4611 macro_rules! no {
4612 ($($t:tt)*) => {{
4613 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
4614 eprintln!("кадр MoE отклонён: {}", format_args!($($t)*));
4615 }
4616 return None;
4617 }};
4618 }
4619 let Some(pk) = pack_for(l, cfg, li) else {
4620 no!("слой {li}: упаковка экспертов не построена");
4621 };
4622 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
4625 no!("слой {li}: эксперты не отображены из файла");
4626 };
4627 let subset = !pk.route_complete();
4628 let needs_remap = pk.needs_remap();
4629 fn fetch_quota() -> usize {
4636 static Q: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
4637 *Q.get_or_init(|| {
4638 std::env::var("CMF_DSV4_FETCH_MAX")
4639 .ok()
4640 .and_then(|v| v.parse().ok())
4641 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().0)
4642 })
4643 }
4644 fn fetch_min_seen() -> u16 {
4645 static M: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
4646 *M.get_or_init(|| {
4647 std::env::var("CMF_DSV4_FETCH_MIN_SEEN")
4648 .ok()
4649 .and_then(|v| v.parse().ok())
4650 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().1)
4651 })
4652 }
4653 let mut dynv = pk.dynslots.lock().unwrap();
4654 let mut pidx = Vec::new();
4659 let mut pwt = Vec::new();
4660 let mut predicted_logits = Vec::new();
4666 let prediction_logits: &[f32] = if subset && logits.is_empty() && !hidden.is_empty() {
4667 predicted_logits.resize(cfg.n_routed_experts, 0.0);
4668 l.gate.matvec(hidden, &mut predicted_logits, pool);
4669 &predicted_logits
4670 } else {
4671 logits
4672 };
4673 if subset && !prediction_logits.is_empty() {
4674 route(
4675 prediction_logits,
4676 l.gate_bias.as_deref(),
4677 cfg.top_k,
4678 cfg.route_scale,
4679 forced,
4680 l.mask.as_deref(),
4681 &mut pidx,
4682 &mut pwt,
4683 );
4684 }
4685 let global_remap = pk.global.as_ref().map(|gl| {
4686 gl.pool.ensure_picks(
4687 &model, gl.layer, &pidx, &l.experts,
4688 cfg.top_k, 1,
4694 )
4695 });
4696 if subset && fetch_quota() > 0 && !pidx.is_empty() && !dynv.owner.is_empty() {
4697 dynv.clock += 1;
4698 let clock = dynv.clock;
4699 if clock % 64 == 0 {
4700 for v in dynv.seen.iter_mut() {
4701 *v >>= 1;
4702 }
4703 }
4704 for &pick in &pidx {
4705 dynv.seen[pick] = dynv.seen[pick].saturating_add(1);
4706 let sl = dynv.remap[pick];
4707 if sl != u32::MAX {
4708 dynv.last[sl as usize] = clock;
4709 }
4710 }
4711 let mut fetched = 0usize;
4712 for &pick in &pidx {
4713 if fetched >= fetch_quota() {
4714 break;
4715 }
4716 if dynv.remap[pick] != u32::MAX {
4717 continue;
4718 }
4719 if dynv.seen[pick] < fetch_min_seen() {
4720 continue; }
4722 let victim = (0..dynv.owner.len())
4724 .filter(|&sl| dynv.last[sl] != clock)
4725 .min_by_key(|&sl| dynv.last[sl]);
4726 let Some(victim) = victim else { break };
4727 let Some(exp) = l.experts.get(pick) else {
4728 continue;
4729 };
4730 let t3 = (|| {
4731 Some((
4732 exp.w1.model_idx()?,
4733 exp.w3.model_idx()?,
4734 exp.w2.model_idx()?,
4735 ))
4736 })();
4737 let Some(t3) = t3 else { continue };
4738 let gu_q2 = exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
4739 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
4740 if !crate::gpu_wgpu::dsv4_slot_fill(
4741 &model,
4742 pack_first,
4743 victim,
4744 pick,
4745 t3,
4746 cfg.moe_inter,
4747 cfg.dim,
4748 gu_q2,
4749 ) {
4750 break;
4751 }
4752 let old = dynv.owner[victim] as usize;
4753 if old < dynv.remap.len() {
4754 dynv.remap[old] = u32::MAX;
4755 }
4756 dynv.remap[pick] = victim as u32;
4757 dynv.owner[victim] = pick as u32;
4758 dynv.last[victim] = clock;
4759 dynv.mutated = true;
4760 fetched += 1;
4761 }
4762 }
4763 let fpack: Option<Vec<usize>> = match forced {
4767 Some(f) if needs_remap => Some(f.to_vec()),
4768 Some(f) => {
4769 let v: Vec<usize> = f.iter().map(|&g| pk.to_slot[g]).collect();
4770 if v.contains(&usize::MAX) {
4771 no!("слой {li}: хеш-слой называет эксперта вне упаковки");
4772 }
4773 Some(v)
4774 }
4775 None => None,
4776 };
4777 let lg: Vec<f32> = if logits.is_empty() || needs_remap {
4784 logits.to_vec()
4785 } else {
4786 pk.globals.iter().map(|&g| logits[g]).collect()
4787 };
4788 let live_remap = global_remap.as_deref().unwrap_or(dynv.remap.as_slice());
4789 let w = crate::gpu_wgpu::Dsv4MoeW {
4790 router: &pk.router,
4791 experts: &pk.tensors,
4792 logits: &lg,
4793 bias: pk.bias.as_deref(),
4794 mask: pk.mask.as_deref(),
4795 forced: fpack.as_deref(),
4796 remap: needs_remap.then_some(live_remap),
4797 global: pk.global.as_ref().map(|gl| crate::gpu_wgpu::Dsv4GlobalMoe {
4798 pool_uid: gl.pool.uid,
4799 shared_slot: gl.shared_slot,
4800 segment_slots: gl.pool.segment_slots as u32,
4801 }),
4802 has_shared: true,
4803 shared_weight: 1.0,
4804 preweighted: false,
4805 qwen_softmax: false,
4806 };
4807 let g = crate::gpu_wgpu::Dsv4MoeGeom {
4808 hidden: cfg.dim,
4809 inter: cfg.moe_inter,
4810 top_k: cfg.top_k,
4811 route_scale: cfg.route_scale,
4812 swiglu_limit: cfg.swiglu_limit,
4813 gu_q2: l
4814 .experts
4815 .first()
4816 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
4817 bf16: false,
4818 };
4819 let mut cold = Vec::new();
4820 let mut cold_x = Vec::new();
4821 let overlap: Vec<usize> = if !hidden.is_empty() {
4830 pidx.iter()
4831 .copied()
4832 .filter(|&pick| live_remap.get(pick).copied().unwrap_or(u32::MAX) == u32::MAX)
4833 .collect()
4834 } else {
4835 Vec::new()
4836 };
4837 let mut early: std::collections::HashMap<usize, Vec<f32>> = std::collections::HashMap::new();
4838 let frame_ok = std::thread::scope(|sc| {
4839 let handles: Vec<_> = overlap
4840 .iter()
4841 .filter_map(|&gi| l.experts.get(gi).map(|exp| (gi, exp)))
4842 .map(|(gi, exp)| {
4843 sc.spawn(move || {
4844 let mut a = vec![0.0f32; cfg.dim];
4845 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, 1.0, None, &mut a));
4846 (gi, a)
4847 })
4848 })
4849 .collect();
4850 let ok = crate::gpu_wgpu::dsv4_moe_frame(
4851 &model,
4852 &w,
4853 g,
4854 hidden,
4855 &mut cold,
4856 &mut cold_x,
4857 hc_cur,
4858 hc_next,
4859 out,
4860 );
4861 for h in handles {
4862 if let Ok((gi, a)) = h.join() {
4863 early.insert(gi, a);
4864 }
4865 }
4866 ok
4867 });
4868 if !frame_ok {
4869 return None;
4870 }
4871 if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
4874 let csum: f32 = cold.iter().map(|c| c.1).sum();
4875 eprintln!(
4876 "[холодные] слой {li}: вернулось {} из {} | сумма холодных {csum:.4} | \
4877 route_scale {:.4} | {:?}",
4878 cold.len(),
4879 cfg.top_k,
4880 cfg.route_scale,
4881 &cold[..cold.len().min(3)]
4882 );
4883 }
4884 let mut acc = vec![0.0f32; cfg.dim];
4885 let mut cold_sum = vec![0.0f32; cfg.dim];
4886 let cold_input = if hidden.is_empty() {
4887 cold_x.as_slice()
4888 } else {
4889 hidden
4890 };
4891 if !early.is_empty() {
4904 for &(gi, wt) in &cold {
4909 if let Some(a) = early.get(&gi) {
4910 for ((o, sum), v) in out.iter_mut().zip(&mut cold_sum).zip(a.iter()) {
4911 *o += v * wt;
4912 *sum += v * wt;
4913 }
4914 continue;
4915 }
4916 let Some(exp) = l.experts.get(gi) else {
4917 continue;
4918 };
4919 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
4920 for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
4921 *o += a;
4922 *sum += a;
4923 }
4924 }
4925 return Some((cold_sum, cold.len()));
4926 }
4927 match pool {
4928 Some(p) if cold.len() > 1 => {
4929 let results: Vec<std::sync::Mutex<Vec<f32>>> = cold
4930 .iter()
4931 .map(|_| std::sync::Mutex::new(Vec::new()))
4932 .collect();
4933 let (cold_ref, results_ref) = (&cold, &results);
4934 p.run_rows(cold.len(), &move |cs, ce| {
4935 for i in cs..ce {
4936 let (gi, wt) = cold_ref[i];
4937 let Some(exp) = l.experts.get(gi) else {
4938 continue;
4939 };
4940 let mut a = vec![0.0f32; cfg.dim];
4941 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, None, &mut a));
4942 *results_ref[i].lock().unwrap() = a;
4943 }
4944 });
4945 for r in &results {
4948 let a = r.lock().unwrap();
4949 if a.is_empty() {
4950 continue;
4951 }
4952 for ((o, sum), v) in out.iter_mut().zip(&mut cold_sum).zip(a.iter()) {
4953 *o += v;
4954 *sum += v;
4955 }
4956 }
4957 }
4958 _ => {
4959 for &(gi, wt) in &cold {
4960 let Some(exp) = l.experts.get(gi) else {
4961 continue;
4962 };
4963 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
4964 for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
4965 *o += a;
4966 *sum += a;
4967 }
4968 }
4969 }
4970 }
4971 Some((cold_sum, cold.len()))
4972}
4973
4974#[cfg(feature = "gpu")]
4980fn last_grew(now: u64) -> u64 {
4981 use std::sync::atomic::{AtomicU64, Ordering};
4982 static SEEN: AtomicU64 = AtomicU64::new(0);
4983 let was = SEEN.load(Ordering::Relaxed);
4984 if was != now {
4985 SEEN.store(now, Ordering::Relaxed);
4986 compressed_map().lock().unwrap().clear();
4987 return u64::MAX; }
4989 now
4990}
4991
4992#[cfg(feature = "gpu")]
4993fn compressed_map() -> &'static std::sync::Mutex<std::collections::HashMap<(u64, usize), usize>> {
4994 use std::collections::HashMap;
4995 use std::sync::{Mutex, OnceLock};
4996 static W: OnceLock<Mutex<HashMap<(u64, usize), usize>>> = OnceLock::new();
4997 W.get_or_init(|| Mutex::new(HashMap::new()))
4998}
4999
5000#[cfg(feature = "gpu")]
5001fn compressed_written(kv_id: u64, li: usize) -> usize {
5002 compressed_map()
5003 .lock()
5004 .unwrap()
5005 .get(&(kv_id, li))
5006 .copied()
5007 .unwrap_or(0)
5008}
5009
5010#[cfg(feature = "gpu")]
5013fn note_compressed(kv_id: u64, li: usize, n: usize) {
5014 compressed_map().lock().unwrap().insert((kv_id, li), n);
5015}
5016
5017#[cfg(feature = "gpu")]
5018fn gpu_moe2_enabled() -> bool {
5019 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5020 *ON.get_or_init(|| {
5021 std::env::var("CMF_DSV4_GPU_MOE2")
5022 .map(|v| v != "0")
5023 .unwrap_or(true)
5024 && crate::gpu::backend_available()
5025 })
5026}
5027
5028pub fn moe_step(
5029 hidden: &[f32],
5030 l: &Dsv4Layer,
5031 cfg: &Dsv4Cfg,
5032 token_id: u32,
5033 li: usize,
5035 pool: Option<&crate::pool::Pool>,
5036 out: &mut [f32],
5037) {
5038 let _t0 = prof::on().then(std::time::Instant::now);
5039 let _guard = scopeguard_moe(_t0, li);
5040 let mut logits = vec![0.0f32; cfg.n_routed_experts];
5041 l.gate.matvec(hidden, &mut logits, pool);
5042 let (mut idx, mut w) = (Vec::new(), Vec::new());
5043 route(
5044 &logits,
5045 l.gate_bias.as_deref(),
5046 cfg.top_k,
5047 cfg.route_scale,
5048 l.tid2eid
5049 .as_ref()
5050 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id))
5051 .as_deref(),
5052 l.mask.as_deref(),
5053 &mut idx,
5054 &mut w,
5055 );
5056 if route_stats_on() {
5057 let routed: Vec<(usize, f32)> = idx.iter().copied().zip(w.iter().copied()).collect();
5058 record_route(li, 0, cfg.n_routed_experts, &routed);
5059 }
5060 crate::pipeline::moe_trace_at(li as i32, &idx);
5066 #[cfg(feature = "gpu")]
5070 if gpu_moe2_enabled() && crate::gpu::enabled_here() {
5071 let forced = l
5072 .tid2eid
5073 .as_ref()
5074 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
5075 if moe_frame(
5076 hidden,
5077 l,
5078 cfg,
5079 li,
5080 &logits,
5081 forced.as_deref(),
5082 pool,
5083 None,
5084 None,
5085 out,
5086 )
5087 .is_some()
5088 {
5089 if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
5095 let mut want = vec![0.0f32; out.len()];
5096 let mut acc = vec![0.0f32; cfg.dim];
5097 crate::gpu::cpu_scope(|| {
5102 for (e, &ei) in idx.iter().enumerate() {
5103 let Some(exp) = l.experts.get(ei) else {
5104 continue;
5105 };
5106 run_expert(
5107 hidden,
5108 exp,
5109 cfg,
5110 w.get(e).copied().unwrap_or(0.0),
5111 pool,
5112 &mut acc,
5113 );
5114 for (o, a) in want.iter_mut().zip(&acc) {
5115 *o += a;
5116 }
5117 }
5118 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
5119 for (o, a) in want.iter_mut().zip(&acc) {
5120 *o += a;
5121 }
5122 });
5123 let num: f32 = want
5124 .iter()
5125 .zip(out.iter())
5126 .map(|(a, b)| (a - b) * (a - b))
5127 .sum();
5128 let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
5129 let rel = (num / den).sqrt();
5130 if rel > 1e-3 {
5131 let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
5132 eprintln!(
5133 "[кадр MoE] слой {li}: расхождение {rel:.3e} | выбрано {} | \
5134 упаковано {packed} из {} | хеш={} | смещение={}",
5135 idx.len(),
5136 cfg.n_routed_experts,
5137 l.tid2eid.is_some(),
5138 l.gate_bias.is_some()
5139 );
5140 }
5141 }
5142 return;
5143 }
5144 }
5145 PICK_TALLY.with(|t| {
5151 if let Some(v) = t.borrow_mut().as_mut() {
5152 v.push((li, idx.to_vec()));
5153 }
5154 });
5155 if dump_path().is_some() {
5156 PICKED.with(|p| {
5157 let mut p = p.borrow_mut();
5158 if p.len() <= li {
5159 p.resize(li + 1, Vec::new());
5160 }
5161 p[li] = idx.clone();
5162 });
5163 }
5164 fn gpu_moe_on() -> bool {
5183 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5184 *ON.get_or_init(|| std::env::var("CMF_DSV4_GPU_MOE").is_ok_and(|v| v != "0"))
5185 }
5186 if gpu_moe_on() && crate::gpu::enabled_here() {
5187 let mut jobs = Vec::with_capacity(idx.len() + 1);
5188 let mut model_ref = None;
5189 let mut ok = true;
5190 for (e, &ei) in idx.iter().enumerate() {
5191 let Some(exp) = l.experts.get(ei) else {
5192 continue;
5193 };
5194 ok &= crate::pipeline::moe_push_job_parts(
5195 &exp.w1,
5196 &exp.w3,
5197 &exp.w2,
5198 hidden,
5199 w.get(e).copied().unwrap_or(0.0),
5200 cfg.swiglu_limit,
5201 &mut jobs,
5202 &mut model_ref,
5203 )
5204 .is_some();
5205 }
5206 ok &= crate::pipeline::moe_push_job_parts(
5207 &l.shared.w1,
5208 &l.shared.w3,
5209 &l.shared.w2,
5210 hidden,
5211 1.0,
5212 cfg.swiglu_limit,
5213 &mut jobs,
5214 &mut model_ref,
5215 )
5216 .is_some();
5217 if ok {
5218 if let Some(m) = model_ref.as_ref() {
5219 if crate::gpu::moe_block(m, &jobs, out) {
5220 if std::env::var("CMF_DSV4_GPU_CHECK").is_ok() {
5225 let mut want = vec![0.0f32; out.len()];
5226 let mut acc = vec![0.0f32; cfg.dim];
5227 for (e, &ei) in idx.iter().enumerate() {
5228 let Some(exp) = l.experts.get(ei) else {
5229 continue;
5230 };
5231 run_expert(
5232 hidden,
5233 exp,
5234 cfg,
5235 w.get(e).copied().unwrap_or(0.0),
5236 pool,
5237 &mut acc,
5238 );
5239 for (o, a) in want.iter_mut().zip(&acc) {
5240 *o += a;
5241 }
5242 }
5243 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
5244 for (o, a) in want.iter_mut().zip(&acc) {
5245 *o += a;
5246 }
5247 let num: f32 = want
5248 .iter()
5249 .zip(out.iter())
5250 .map(|(a, b)| (a - b) * (a - b))
5251 .sum();
5252 let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
5253 eprintln!(
5254 "[dsv4-gpu] слой {li}: расхождение {:.3e} | |CPU|={:.5} |GPU|={:.5} | экспертов {}",
5255 (num / den).sqrt(),
5256 den.sqrt(),
5257 out.iter().map(|x| x * x).sum::<f32>().sqrt(),
5258 jobs.len()
5259 );
5260 }
5261 return;
5262 }
5263 }
5264 }
5265 }
5266 out.fill(0.0);
5267 let jobs: Vec<(Option<usize>, f32)> = idx
5274 .iter()
5275 .enumerate()
5276 .map(|(e, &ei)| (Some(ei), w.get(e).copied().unwrap_or(0.0)))
5277 .chain(std::iter::once((None, 1.0)))
5278 .collect();
5279 match pool {
5280 Some(p) if jobs.len() > 1 => {
5281 let results: Vec<std::sync::Mutex<Vec<f32>>> = jobs
5282 .iter()
5283 .map(|_| std::sync::Mutex::new(Vec::new()))
5284 .collect();
5285 let (jobs_ref, results_ref) = (&jobs, &results);
5286 p.run_rows(jobs.len(), &move |cs, ce| {
5287 for i in cs..ce {
5288 let (ei, wt) = jobs_ref[i];
5289 let exp = match ei {
5290 Some(ei) => match l.experts.get(ei) {
5291 Some(x) => x,
5292 None => continue,
5293 },
5294 None => &l.shared,
5295 };
5296 let mut a = vec![0.0f32; cfg.dim];
5297 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, wt, None, &mut a));
5298 *results_ref[i].lock().unwrap() = a;
5299 }
5300 });
5301 for r in &results {
5302 let a = r.lock().unwrap();
5303 for (o, v) in out.iter_mut().zip(a.iter()) {
5304 *o += v;
5305 }
5306 }
5307 }
5308 _ => {
5309 let mut acc = vec![0.0f32; cfg.dim];
5310 for &(ei, wt) in &jobs {
5311 let exp = match ei {
5312 Some(ei) => match l.experts.get(ei) {
5313 Some(x) => x,
5314 None => continue,
5315 },
5316 None => &l.shared,
5317 };
5318 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, wt, pool, &mut acc));
5319 for (o, a) in out.iter_mut().zip(&acc) {
5320 *o += a;
5321 }
5322 }
5323 }
5324 }
5325}
5326
5327fn run_expert(
5330 x: &[f32],
5331 e: &Dsv4Expert,
5332 cfg: &Dsv4Cfg,
5333 weight: f32,
5334 pool: Option<&crate::pool::Pool>,
5335 out: &mut [f32],
5336) {
5337 expert_swiglu(
5338 x,
5339 &|src, dst| e.w1.matvec(src, dst, pool),
5340 &|src, dst| e.w3.matvec(src, dst, pool),
5341 &|src, dst| e.w2.matvec(src, dst, pool),
5342 cfg.moe_inter,
5343 weight,
5344 cfg.swiglu_limit,
5345 out,
5346 );
5347}
5348
5349fn moe_step_block(
5354 xs: &[f32],
5355 b: usize,
5356 l: &Dsv4Layer,
5357 cfg: &Dsv4Cfg,
5358 token_ids: &[u32],
5359 tally_layer: usize,
5360 pool: Option<&crate::pool::Pool>,
5361 out: &mut [f32],
5362) {
5363 let (dim, inter) = (cfg.dim, cfg.moe_inter);
5364 debug_assert_eq!(xs.len(), b * dim);
5365 debug_assert_eq!(out.len(), b * dim);
5366 out.fill(0.0);
5367
5368 let mut logits = vec![0.0f32; b * cfg.n_routed_experts];
5369 l.gate.matmat(xs, b, &mut logits, pool);
5370 let mut picks: Vec<Vec<usize>> = Vec::with_capacity(b);
5371 let mut weights: Vec<Vec<f32>> = Vec::with_capacity(b);
5372 for bi in 0..b {
5373 let mut idx = Vec::new();
5374 let mut wt = Vec::new();
5375 let forced = l.tid2eid.as_ref().map(|tbl| {
5376 hash_route(
5377 tbl,
5378 cfg.vocab,
5379 cfg.top_k,
5380 token_ids.get(bi).copied().unwrap_or(0),
5381 )
5382 });
5383 route(
5384 &logits[bi * cfg.n_routed_experts..(bi + 1) * cfg.n_routed_experts],
5385 l.gate_bias.as_deref(),
5386 cfg.top_k,
5387 cfg.route_scale,
5388 forced.as_deref(),
5389 l.mask.as_deref(),
5390 &mut idx,
5391 &mut wt,
5392 );
5393 PICK_TALLY.with(|t| {
5394 if let Some(v) = t.borrow_mut().as_mut() {
5395 v.push((tally_layer, idx.clone()));
5396 }
5397 });
5398 picks.push(idx);
5399 weights.push(wt);
5400 }
5401
5402 let mut routed = vec![0.0f32; b * cfg.top_k * dim];
5405 let mut active: Vec<(usize, Vec<(usize, usize, f32)>)> = Vec::new();
5408 for ei in 0..l.experts.len() {
5409 let mut jobs = Vec::new();
5410 for bi in 0..b {
5411 for (slot, &picked) in picks[bi].iter().enumerate() {
5412 if picked == ei {
5413 jobs.push((bi, slot, weights[bi][slot]));
5414 }
5415 }
5416 }
5417 if !jobs.is_empty() {
5418 active.push((ei, jobs));
5419 }
5420 }
5421 let expert_fwd =
5424 |ei: usize, jobs: &[(usize, usize, f32)], inner: Option<&crate::pool::Pool>| -> Vec<f32> {
5425 let e = &l.experts[ei];
5426 let n = jobs.len();
5427 let mut xj = vec![0.0f32; n * dim];
5428 for (j, &(bi, _, _)) in jobs.iter().enumerate() {
5429 xj[j * dim..(j + 1) * dim].copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
5430 }
5431 let mut gate = vec![0.0f32; n * inter];
5432 let mut up = vec![0.0f32; n * inter];
5433 e.w1.matmat(&xj, n, &mut gate, inner);
5434 e.w3.matmat(&xj, n, &mut up, inner);
5435 for (j, &(_, _, wt)) in jobs.iter().enumerate() {
5436 let (gj, uj) = (
5437 &mut gate[j * inter..(j + 1) * inter],
5438 &mut up[j * inter..(j + 1) * inter],
5439 );
5440 if cfg.swiglu_limit > 0.0 {
5441 for u in uj.iter_mut() {
5442 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5443 }
5444 for g in gj.iter_mut() {
5445 *g = g.min(cfg.swiglu_limit);
5446 }
5447 }
5448 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5449 *g = (*g / (1.0 + (-*g).exp())) * u * wt;
5450 }
5451 }
5452 let mut down = vec![0.0f32; n * dim];
5453 e.w2.matmat(&gate, n, &mut down, inner);
5454 down
5455 };
5456 match pool {
5466 Some(p) if active.len() > 1 => {
5467 let results: Vec<std::sync::Mutex<Vec<f32>>> = active
5468 .iter()
5469 .map(|_| std::sync::Mutex::new(Vec::new()))
5470 .collect();
5471 let active_ref = &active;
5472 let results_ref = &results;
5473 let fwd = &expert_fwd;
5474 p.run_rows(active_ref.len(), &move |s, e| {
5475 for i in s..e {
5476 let (ei, jobs) = &active_ref[i];
5477 let d = crate::gpu::cpu_scope(|| fwd(*ei, jobs, None));
5478 *results_ref[i].lock().unwrap() = d;
5479 }
5480 });
5481 for (i, (_, jobs)) in active.iter().enumerate() {
5482 let down = results[i].lock().unwrap();
5483 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5484 routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
5485 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5486 }
5487 }
5488 }
5489 _ => {
5490 for (ei, jobs) in &active {
5491 let down = expert_fwd(*ei, jobs, pool);
5492 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5493 routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
5494 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5495 }
5496 }
5497 }
5498 }
5499
5500 let mut sg = vec![0.0f32; b * inter];
5503 let mut su = vec![0.0f32; b * inter];
5504 l.shared.w1.matmat(xs, b, &mut sg, pool);
5505 l.shared.w3.matmat(xs, b, &mut su, pool);
5506 for bi in 0..b {
5507 let (gj, uj) = (
5508 &mut sg[bi * inter..(bi + 1) * inter],
5509 &mut su[bi * inter..(bi + 1) * inter],
5510 );
5511 if cfg.swiglu_limit > 0.0 {
5512 for u in uj.iter_mut() {
5513 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5514 }
5515 for g in gj.iter_mut() {
5516 *g = g.min(cfg.swiglu_limit);
5517 }
5518 }
5519 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5520 *g = (*g / (1.0 + (-*g).exp())) * u;
5521 }
5522 }
5523 let mut shared = vec![0.0f32; b * dim];
5524 l.shared.w2.matmat(&sg, b, &mut shared, pool);
5525
5526 for bi in 0..b {
5527 let dst = &mut out[bi * dim..(bi + 1) * dim];
5528 for slot in 0..picks[bi].len() {
5529 let src = &routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim];
5530 for (o, &v) in dst.iter_mut().zip(src) {
5531 *o += v;
5532 }
5533 }
5534 for (o, &v) in dst.iter_mut().zip(&shared[bi * dim..(bi + 1) * dim]) {
5535 *o += v;
5536 }
5537 }
5538}
5539
5540fn cold_step_block(
5545 xs: &[f32],
5546 b: usize,
5547 l: &Dsv4Layer,
5548 cfg: &Dsv4Cfg,
5549 cold: &[Vec<(usize, f32)>],
5550 pool: Option<&crate::pool::Pool>,
5551 out: &mut [f32],
5552) {
5553 let (dim, inter) = (cfg.dim, cfg.moe_inter);
5554 debug_assert_eq!(xs.len(), b * dim);
5555 debug_assert_eq!(cold.len(), b);
5556 debug_assert_eq!(out.len(), b * dim);
5557 out.fill(0.0);
5558 let slots = cold.iter().map(Vec::len).max().unwrap_or(0);
5559 if slots == 0 {
5560 return;
5561 }
5562 let grouped = std::env::var("CMF_DSV4_COLD_MATMAT")
5568 .map(|v| v != "0")
5569 .unwrap_or(true);
5570 if !grouped {
5571 let jobs: Vec<(usize, usize, usize, f32)> = cold
5572 .iter()
5573 .enumerate()
5574 .flat_map(|(bi, row)| {
5575 row.iter()
5576 .enumerate()
5577 .map(move |(slot, &(ei, wt))| (bi, slot, ei, wt))
5578 })
5579 .collect();
5580 let results: Vec<std::sync::Mutex<Vec<f32>>> = jobs
5581 .iter()
5582 .map(|_| std::sync::Mutex::new(Vec::new()))
5583 .collect();
5584 match pool {
5585 Some(p) if jobs.len() > 1 => {
5586 let (jobs_ref, results_ref) = (&jobs, &results);
5587 p.run_rows(jobs.len(), &move |s, e| {
5588 for i in s..e {
5589 let (_, _, ei, wt) = jobs_ref[i];
5590 let Some(exp) = l.experts.get(ei) else {
5591 continue;
5592 };
5593 let bi = jobs_ref[i].0;
5594 let mut a = vec![0.0f32; dim];
5595 crate::gpu::cpu_scope(|| {
5596 run_expert(&xs[bi * dim..(bi + 1) * dim], exp, cfg, wt, None, &mut a)
5597 });
5598 *results_ref[i].lock().unwrap() = a;
5599 }
5600 });
5601 }
5602 _ => {
5603 for (i, &(bi, _, ei, wt)) in jobs.iter().enumerate() {
5604 let Some(exp) = l.experts.get(ei) else {
5605 continue;
5606 };
5607 let mut a = vec![0.0f32; dim];
5608 crate::gpu::cpu_scope(|| {
5609 run_expert(&xs[bi * dim..(bi + 1) * dim], exp, cfg, wt, pool, &mut a)
5610 });
5611 *results[i].lock().unwrap() = a;
5612 }
5613 }
5614 }
5615 for (i, &(bi, _, _, _)) in jobs.iter().enumerate() {
5618 let a = results[i].lock().unwrap();
5619 for (o, &v) in out[bi * dim..(bi + 1) * dim].iter_mut().zip(a.iter()) {
5620 *o += v;
5621 }
5622 }
5623 return;
5624 }
5625 let mut active: Vec<(usize, Vec<(usize, usize, f32)>)> = Vec::new();
5626 for ei in 0..l.experts.len() {
5627 let mut jobs = Vec::new();
5628 for bi in 0..b {
5629 for (slot, &(picked, wt)) in cold[bi].iter().enumerate() {
5630 if picked == ei {
5631 jobs.push((bi, slot, wt));
5632 }
5633 }
5634 }
5635 if !jobs.is_empty() {
5636 active.push((ei, jobs));
5637 }
5638 }
5639 let expert_fwd =
5640 |ei: usize, jobs: &[(usize, usize, f32)], inner: Option<&crate::pool::Pool>| -> Vec<f32> {
5641 let e = &l.experts[ei];
5642 let n = jobs.len();
5643 let mut xj = vec![0.0f32; n * dim];
5644 for (j, &(bi, _, _)) in jobs.iter().enumerate() {
5645 xj[j * dim..(j + 1) * dim].copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
5646 }
5647 let mut gate = vec![0.0f32; n * inter];
5648 let mut up = vec![0.0f32; n * inter];
5649 e.w1.matmat(&xj, n, &mut gate, inner);
5650 e.w3.matmat(&xj, n, &mut up, inner);
5651 for (j, &(_, _, wt)) in jobs.iter().enumerate() {
5652 let (gj, uj) = (
5653 &mut gate[j * inter..(j + 1) * inter],
5654 &mut up[j * inter..(j + 1) * inter],
5655 );
5656 if cfg.swiglu_limit > 0.0 {
5657 for u in uj.iter_mut() {
5658 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5659 }
5660 for g in gj.iter_mut() {
5661 *g = g.min(cfg.swiglu_limit);
5662 }
5663 }
5664 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5665 *g = (*g / (1.0 + (-*g).exp())) * u * wt;
5666 }
5667 }
5668 let mut down = vec![0.0f32; n * dim];
5669 e.w2.matmat(&gate, n, &mut down, inner);
5670 down
5671 };
5672 let mut routed = vec![0.0f32; b * slots * dim];
5673 match pool {
5674 Some(p) if active.len() > 1 => {
5675 let results: Vec<std::sync::Mutex<Vec<f32>>> = active
5676 .iter()
5677 .map(|_| std::sync::Mutex::new(Vec::new()))
5678 .collect();
5679 let active_ref = &active;
5680 let results_ref = &results;
5681 let fwd = &expert_fwd;
5682 p.run_rows(active.len(), &move |s, e| {
5683 for i in s..e {
5684 let (ei, jobs) = &active_ref[i];
5685 let d = crate::gpu::cpu_scope(|| fwd(*ei, jobs, None));
5686 *results_ref[i].lock().unwrap() = d;
5687 }
5688 });
5689 for (i, (_, jobs)) in active.iter().enumerate() {
5690 let down = results[i].lock().unwrap();
5691 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5692 routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim]
5693 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5694 }
5695 }
5696 }
5697 _ => {
5698 for (ei, jobs) in &active {
5699 let down = expert_fwd(*ei, jobs, pool);
5700 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5701 routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim]
5702 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5703 }
5704 }
5705 }
5706 }
5707 for bi in 0..b {
5708 let dst = &mut out[bi * dim..(bi + 1) * dim];
5709 for slot in 0..cold[bi].len() {
5710 let src = &routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim];
5711 for (o, &v) in dst.iter_mut().zip(src) {
5712 *o += v;
5713 }
5714 }
5715 }
5716}
5717
5718#[cfg(feature = "gpu")]
5723fn refill_route_slots(l: &Dsv4Layer, cfg: &Dsv4Cfg, pk: &Pack, picks: &[usize]) {
5724 if picks.is_empty() || pk.route_complete() {
5725 return;
5726 }
5727 let quota = {
5728 static Q: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
5729 *Q.get_or_init(|| {
5730 std::env::var("CMF_DSV4_FETCH_MAX")
5731 .ok()
5732 .and_then(|v| v.parse().ok())
5733 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().0)
5734 })
5735 };
5736 if quota == 0 {
5737 return;
5738 }
5739 let min_seen = {
5740 static M: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
5741 *M.get_or_init(|| {
5742 std::env::var("CMF_DSV4_FETCH_MIN_SEEN")
5743 .ok()
5744 .and_then(|v| v.parse().ok())
5745 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().1)
5746 })
5747 };
5748 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
5749 return;
5750 };
5751 let mut dynv = pk.dynslots.lock().unwrap();
5752 if dynv.owner.is_empty() {
5753 return;
5754 }
5755 dynv.clock += 1;
5756 let clock = dynv.clock;
5757 if clock % 64 == 0 {
5758 for seen in &mut dynv.seen {
5759 *seen >>= 1;
5760 }
5761 }
5762 for &pick in picks {
5763 if pick >= dynv.seen.len() {
5764 continue;
5765 }
5766 dynv.seen[pick] = dynv.seen[pick].saturating_add(1);
5767 let slot = dynv.remap[pick];
5768 if slot != u32::MAX {
5769 dynv.last[slot as usize] = clock;
5770 }
5771 }
5772 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
5773 let mut fetched = 0usize;
5774 for &pick in picks {
5775 if fetched >= quota || pick >= dynv.remap.len() {
5776 break;
5777 }
5778 if dynv.remap[pick] != u32::MAX || dynv.seen[pick] < min_seen {
5779 continue;
5780 }
5781 let victim = (0..dynv.owner.len())
5782 .filter(|&slot| dynv.last[slot] != clock)
5783 .min_by_key(|&slot| dynv.last[slot]);
5784 let Some(victim) = victim else { break };
5785 let Some(exp) = l.experts.get(pick) else {
5786 continue;
5787 };
5788 let tensors = (|| {
5789 Some((
5790 exp.w1.model_idx()?,
5791 exp.w3.model_idx()?,
5792 exp.w2.model_idx()?,
5793 ))
5794 })();
5795 let Some(tensors) = tensors else { continue };
5796 let gu_q2 = exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
5797 if !crate::gpu_wgpu::dsv4_slot_fill(
5798 &model,
5799 pack_first,
5800 victim,
5801 pick,
5802 tensors,
5803 cfg.moe_inter,
5804 cfg.dim,
5805 gu_q2,
5806 ) {
5807 break;
5808 }
5809 let old = dynv.owner[victim] as usize;
5810 if old < dynv.remap.len() {
5811 dynv.remap[old] = u32::MAX;
5812 }
5813 dynv.remap[pick] = victim as u32;
5814 dynv.owner[victim] = pick as u32;
5815 dynv.last[victim] = clock;
5816 dynv.mutated = true;
5817 fetched += 1;
5818 }
5819}
5820
5821fn o_project_block(
5826 attn: &[f32],
5827 b: usize,
5828 wo_a: &crate::qtensor::QTensor,
5829 wo_b: &crate::qtensor::QTensor,
5830 groups: usize,
5831 lora: usize,
5832 pool: Option<&crate::pool::Pool>,
5833 out: &mut [f32],
5834) {
5835 let attn_len = attn.len() / b;
5836 let per_group = attn_len / groups;
5837 let rows = groups * lora;
5838 let mut mid = vec![0.0f32; b * rows];
5839 let mid_addr = crate::pool::SendMut::new(mid.as_mut_ptr());
5840 let run = |start: usize, end: usize| {
5841 let mut wr = vec![0.0f32; wo_a.cols()];
5842 for r in start..end {
5843 wo_a.row_f32(r, &mut wr);
5844 let group = r / lora;
5845 for bi in 0..b {
5846 let x = &attn
5847 [bi * attn_len + group * per_group..bi * attn_len + (group + 1) * per_group];
5848 let v = wr.iter().zip(x).map(|(w, x)| w * x).sum();
5849 unsafe { *mid_addr.at(bi * rows + r) = v };
5850 }
5851 }
5852 };
5853 match pool {
5854 Some(p) if rows >= 256 => p.run_rows(rows, &run),
5855 _ => run(0, rows),
5856 }
5857 wo_b.matmat(&mid, b, out, pool);
5858}
5859
5860fn no_compressed() -> bool {
5866 static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5867 *OFF.get_or_init(|| std::env::var("CMF_DSV4_NO_COMPRESSED").is_ok_and(|v| v != "0"))
5868}
5869
5870fn trace_on() -> bool {
5871 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5872 *ON.get_or_init(|| std::env::var("CMF_DSV4_TRACE").is_ok_and(|v| v != "0"))
5873}
5874
5875fn rms_of(v: &[f32]) -> f32 {
5876 if v.is_empty() {
5877 return 0.0;
5878 }
5879 (v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32).sqrt()
5880}
5881
5882#[cfg(feature = "gpu")]
5883fn verify_fp_on(pos: usize) -> bool {
5884 static POS: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
5885 let want = *POS.get_or_init(|| {
5886 std::env::var("CMF_DSV4_FP_POS")
5887 .ok()
5888 .and_then(|v| v.parse().ok())
5889 });
5890 want == Some(pos)
5891}
5892
5893#[cfg(feature = "gpu")]
5894fn verify_fp(tag: &str, pos: usize, li: usize, state: &[f32]) {
5895 if !verify_fp_on(pos) {
5896 return;
5897 }
5898 let mut fp = 0xcbf29ce484222325u64;
5899 for &x in state {
5900 fp ^= x.to_bits() as u64;
5901 fp = fp.wrapping_mul(0x100000001b3);
5902 }
5903 eprintln!(
5904 "[dsv4-fp] {tag} pos={pos} li={li} fp={fp:016x} rms={:.7} head={:?}",
5905 rms_of(state),
5906 &state[..4.min(state.len())]
5907 );
5908}
5909
5910thread_local! {
5916 static BODY: std::cell::RefCell<Vec<String>> = const { std::cell::RefCell::new(Vec::new()) };
5918 static PICKED: std::cell::RefCell<Vec<Vec<usize>>> =
5922 const { std::cell::RefCell::new(Vec::new()) };
5923 static PICK_TALLY: std::cell::RefCell<Option<Vec<(usize, Vec<usize>)>>> =
5925 const { std::cell::RefCell::new(None) };
5926}
5927
5928pub fn pick_tally_arm() {
5930 PICK_TALLY.with(|t| *t.borrow_mut() = Some(Vec::new()));
5931}
5932
5933pub fn pick_tally_take() -> Vec<(usize, Vec<usize>)> {
5935 PICK_TALLY.with(|t| t.borrow_mut().take().unwrap_or_default())
5936}
5937
5938pub fn tally_unique(picks: &[(usize, Vec<usize>)]) -> (usize, usize) {
5941 let mut seen = std::collections::HashSet::new();
5946 let mut total = 0;
5947 for (li, v) in picks {
5948 total += v.len();
5949 for &e in v {
5950 seen.insert((*li, e));
5951 }
5952 }
5953 (seen.len(), total)
5954}
5955
5956fn dump_path() -> Option<&'static str> {
5957 static P: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
5958 P.get_or_init(|| std::env::var("CMF_DSV4_DUMP").ok())
5959 .as_deref()
5960}
5961
5962fn dump_line(json: &str) {
5963 if let Some(p) = dump_path() {
5964 use std::io::Write as _;
5965 if let Ok(mut f) = std::fs::OpenOptions::new()
5966 .create(true)
5967 .append(true)
5968 .open(p)
5969 {
5970 let _ = writeln!(f, "{json}");
5971 }
5972 }
5973}
5974
5975fn vec_json(v: &[f32]) -> String {
5976 let mut s = String::with_capacity(v.len() * 9);
5977 s.push('[');
5978 for (i, x) in v.iter().enumerate() {
5979 if i > 0 {
5980 s.push(',');
5981 }
5982 s.push_str(&format!("{x:.6e}"));
5983 }
5984 s.push(']');
5985 s
5986}
5987
5988#[allow(clippy::too_many_arguments)]
5996#[allow(clippy::too_many_arguments)]
6006fn batch_prefill() -> usize {
6010 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
6011 *N.get_or_init(|| {
6012 std::env::var("CMF_DSV4_BATCH")
6013 .ok()
6014 .and_then(|v| v.parse::<usize>().ok())
6015 .filter(|&n| (1..=32).contains(&n))
6016 .unwrap_or(8)
6023 })
6024}
6025
6026#[allow(clippy::too_many_arguments)]
6039fn forward_chunk_batched(
6040 g: &Dsv4Globals,
6041 layers: &[Dsv4Layer],
6042 cfg: &Dsv4Cfg,
6043 st: &mut Dsv4State,
6044 ids: &[u32],
6045 pos0: usize,
6046 inv_freq: &[f32],
6047 pool: Option<&crate::pool::Pool>,
6048 logits: &mut Vec<f32>,
6049 want_logits: bool,
6050) -> bool {
6051 #[cfg(not(feature = "gpu"))]
6052 {
6053 let _ = (
6054 g,
6055 layers,
6056 cfg,
6057 st,
6058 ids,
6059 pos0,
6060 inv_freq,
6061 pool,
6062 logits,
6063 want_logits,
6064 );
6065 false
6066 }
6067 #[cfg(feature = "gpu")]
6068 {
6069 let b = ids.len();
6070 let gpu_end = st
6075 .dev_set
6076 .iter()
6077 .enumerate()
6078 .position(|(li, &on)| {
6079 !on || pack_for(&layers[li], cfg, li).is_none_or(|p| !p.route_complete())
6080 })
6081 .unwrap_or(st.dev_set.len());
6082 let why = if b < 2 {
6083 "токенов меньше двух"
6084 } else if !chain_enabled() {
6085 "цепочка выключена"
6086 } else if !st.dev_owned {
6087 "карта ещё не владеет состоянием"
6088 } else if st.dev_set.len() != layers.len() {
6089 "набор слоёв ещё не зафиксирован"
6090 } else if st.dev_set[gpu_end.min(st.dev_set.len())..]
6091 .iter()
6092 .enumerate()
6093 .any(|(i, &on)| on && !st.partial_set.get(gpu_end + i).copied().unwrap_or(false))
6094 {
6095 "слои на карте не образуют префикс"
6096 } else {
6097 ""
6098 };
6099 if !why.is_empty() {
6100 tracing::debug!("dsv4: пакет preflight — {why}");
6106 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
6107 eprintln!("dsv4: пакет preflight — {why}");
6108 }
6109 return false;
6110 }
6111 let (hc, dim) = (cfg.hc_mult, cfg.dim);
6112 let mut emb = vec![0.0f32; dim];
6113 let mut states = vec![0.0f32; b * hc * dim];
6114 for (t, &id) in ids.iter().enumerate() {
6115 let mut state = vec![0.0f32; hc * dim];
6116 g.embed.row_f32(id as usize, &mut emb);
6117 for j in 0..hc {
6118 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
6119 }
6120 let (folded, post0, comb0) = hc_fold_norm(
6121 &state,
6122 &layers[0].hc_attn_fn,
6123 &layers[0].hc_attn_scale,
6124 &layers[0].hc_attn_base,
6125 &layers[0].attn_norm,
6126 cfg,
6127 pool,
6128 );
6129 let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
6130 layers[0].wq_a.matvec(&folded, &mut qn0, pool);
6131 rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
6132 if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
6133 || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
6134 || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
6135 || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
6136 {
6137 return false;
6138 }
6139 states[t * hc * dim..(t + 1) * hc * dim].copy_from_slice(&state);
6140 }
6141 let run: Vec<usize> = (0..gpu_end).collect();
6142 let mut folded = Vec::new();
6143 st.pos = pos0;
6144 if gpu_end > 0 {
6145 if !dsv4_chain_run(
6146 layers,
6147 &run,
6148 cfg,
6149 g,
6150 st,
6151 *ids.last().unwrap(),
6152 &mut folded,
6153 Some(&mut states),
6154 b,
6155 ids,
6156 true,
6157 pool,
6158 ) {
6159 return false;
6160 }
6161 }
6162 let mut scratch = HcScratch::new(cfg);
6168 host_tail_walk_batch(
6169 g,
6170 layers,
6171 cfg,
6172 st,
6173 gpu_end,
6174 &mut states,
6175 ids,
6176 pos0,
6177 b,
6178 inv_freq,
6179 &mut scratch,
6180 pool,
6181 None,
6182 );
6183 st.pos = pos0 + b;
6184 {
6189 static SAID: std::sync::Once = std::sync::Once::new();
6190 SAID.call_once(|| tracing::warn!("dsv4: префилл пакетами по {b}"));
6191 }
6192 if want_logits {
6197 let last = &states[(b - 1) * hc * dim..b * hc * dim];
6198 let mut h = vec![0.0f32; dim];
6199 hc_head_fold(
6200 last,
6201 &g.hc_head_fn,
6202 g.hc_head_scale,
6203 &g.hc_head_base,
6204 cfg,
6205 pool,
6206 &mut h,
6207 );
6208 rms_weighted(&mut h, &g.norm, cfg.norm_eps);
6209 logits.resize(cfg.vocab, 0.0);
6210 g.head.matvec(&h, logits, pool);
6211 } else {
6212 logits.clear();
6213 }
6214 true
6215 }
6216}
6217
6218#[cfg(feature = "gpu")]
6226pub struct Dsv4SpecTxn {
6227 pos0: usize,
6228 batch: usize,
6229 pub(crate) gpu_end: usize,
6230 dev_filled: Vec<usize>,
6231 dev_n_comp: Vec<usize>,
6232 dev_n_ix: Vec<usize>,
6233 host: Vec<(usize, HostLayerSnap)>,
6234 host_steps: Vec<(usize, Vec<HostLayerSnap>)>,
6239 pub states: Vec<f32>,
6243 shadow: Option<crate::gpu_wgpu::Dsv4SpecShadow>,
6244}
6245
6246#[cfg(feature = "gpu")]
6247struct HostLayerSnap {
6248 window: Vec<f32>,
6249 compressed: Vec<f32>,
6250 index_kv: Vec<f32>,
6251 pending_kv: Vec<f32>,
6252 pending_score: Vec<f32>,
6253 prev_kv: Vec<f32>,
6254 prev_score: Vec<f32>,
6255 pending_ix_kv: Vec<f32>,
6256 pending_ix_score: Vec<f32>,
6257 prev_ix_kv: Vec<f32>,
6258 prev_ix_score: Vec<f32>,
6259}
6260
6261#[cfg(feature = "gpu")]
6262fn host_snap(st: &Dsv4State, li: usize) -> HostLayerSnap {
6263 HostLayerSnap {
6264 window: st.window[li].clone(),
6265 compressed: st.compressed[li].clone(),
6266 index_kv: st.index_kv[li].clone(),
6267 pending_kv: st.pending_kv[li].clone(),
6268 pending_score: st.pending_score[li].clone(),
6269 prev_kv: st.prev_kv[li].clone(),
6270 prev_score: st.prev_score[li].clone(),
6271 pending_ix_kv: st.pending_ix_kv[li].clone(),
6272 pending_ix_score: st.pending_ix_score[li].clone(),
6273 prev_ix_kv: st.prev_ix_kv[li].clone(),
6274 prev_ix_score: st.prev_ix_score[li].clone(),
6275 }
6276}
6277
6278#[cfg(feature = "gpu")]
6279fn host_restore(st: &mut Dsv4State, li: usize, s: &HostLayerSnap) {
6280 st.window[li] = s.window.clone();
6281 st.compressed[li] = s.compressed.clone();
6282 st.index_kv[li] = s.index_kv.clone();
6283 st.pending_kv[li] = s.pending_kv.clone();
6284 st.pending_score[li] = s.pending_score.clone();
6285 st.prev_kv[li] = s.prev_kv.clone();
6286 st.prev_score[li] = s.prev_score.clone();
6287 st.pending_ix_kv[li] = s.pending_ix_kv.clone();
6288 st.pending_ix_score[li] = s.pending_ix_score.clone();
6289 st.prev_ix_kv[li] = s.prev_ix_kv.clone();
6290 st.prev_ix_score[li] = s.prev_ix_score.clone();
6291}
6292
6293#[cfg(feature = "gpu")]
6297#[allow(clippy::too_many_arguments)]
6298fn host_tail_walk(
6299 g: &Dsv4Globals,
6300 layers: &[Dsv4Layer],
6301 cfg: &Dsv4Cfg,
6302 st: &mut Dsv4State,
6303 gpu_end: usize,
6304 state: &mut [f32],
6305 token_id: u32,
6306 pos: usize,
6307 inv_freq: &[f32],
6308 scratch: &mut HcScratch,
6309 pool: Option<&crate::pool::Pool>,
6310) {
6311 st.pos = pos;
6312 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
6313 let freqs = if l.compressor.is_some() {
6314 &g.inv_freq_compress
6315 } else {
6316 &g.inv_freq_window
6317 };
6318 let freqs = if freqs.is_empty() {
6319 inv_freq
6320 } else {
6321 freqs.as_slice()
6322 };
6323 hc_block(
6324 state,
6325 &l.hc_attn_fn,
6326 &l.hc_attn_scale,
6327 &l.hc_attn_base,
6328 &l.attn_norm,
6329 cfg,
6330 scratch,
6331 pool,
6332 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
6333 );
6334 hc_block(
6335 state,
6336 &l.hc_ffn_fn,
6337 &l.hc_ffn_scale,
6338 &l.hc_ffn_base,
6339 &l.ffn_norm,
6340 cfg,
6341 scratch,
6342 pool,
6343 |f, o| {
6344 if host_cpu_moe() {
6345 crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
6346 } else {
6347 moe_step(f, l, cfg, token_id, li, pool, o)
6348 }
6349 },
6350 );
6351 dspark_note(li, state, cfg);
6352 }
6353}
6354
6355#[cfg(feature = "gpu")]
6361#[allow(clippy::too_many_arguments)]
6362fn host_tail_walk_batch(
6363 g: &Dsv4Globals,
6364 layers: &[Dsv4Layer],
6365 cfg: &Dsv4Cfg,
6366 st: &mut Dsv4State,
6367 gpu_end: usize,
6368 states: &mut [f32],
6369 ids: &[u32],
6370 pos0: usize,
6371 b: usize,
6372 inv_freq: &[f32],
6373 scratch: &mut HcScratch,
6374 pool: Option<&crate::pool::Pool>,
6375 mut steps: Option<&mut Vec<(usize, Vec<HostLayerSnap>)>>,
6376) {
6377 let (hc, dim) = (cfg.hc_mult, cfg.dim);
6378 let mix_hc = (2 + hc) * hc;
6379 let mut folds = vec![0.0f32; b * dim];
6380 let mut mo = vec![0.0f32; b * dim];
6381 let mut posts = vec![0.0f32; b * hc];
6382 let mut combs = vec![0.0f32; b * hc * hc];
6383 let mut resid = vec![0.0f32; b * hc * dim];
6384 let spec_time = {
6385 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6386 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
6387 };
6388 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
6389 let t_attn = std::time::Instant::now();
6390 let freqs = if l.compressor.is_some() {
6391 &g.inv_freq_compress
6392 } else {
6393 &g.inv_freq_window
6394 };
6395 let freqs = if freqs.is_empty() {
6396 inv_freq
6397 } else {
6398 freqs.as_slice()
6399 };
6400 for t in 0..b {
6401 st.pos = pos0 + t;
6402 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
6403 hc_block(
6404 state,
6405 &l.hc_attn_fn,
6406 &l.hc_attn_scale,
6407 &l.hc_attn_base,
6408 &l.attn_norm,
6409 cfg,
6410 scratch,
6411 pool,
6412 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
6413 );
6414 if let Some(steps) = steps.as_mut() {
6415 match steps.iter_mut().find(|(l, _)| *l == li) {
6416 Some((_, v)) => v.push(host_snap(st, li)),
6417 None => steps.push((li, vec![host_snap(st, li)])),
6418 }
6419 }
6420 }
6421 let t_glue = std::time::Instant::now();
6422 for t in 0..b {
6423 let state = &states[t * hc * dim..(t + 1) * hc * dim];
6424 hc_mixes(
6425 state,
6426 &l.hc_ffn_fn,
6427 mix_hc,
6428 cfg.norm_eps,
6429 pool,
6430 &mut scratch.mixes,
6431 );
6432 hc_split_sinkhorn(
6433 &scratch.mixes,
6434 &l.hc_ffn_scale,
6435 &l.hc_ffn_base,
6436 hc,
6437 cfg.hc_sinkhorn_iters,
6438 cfg.hc_eps,
6439 &mut scratch.pre,
6440 &mut posts[t * hc..(t + 1) * hc],
6441 &mut combs[t * hc * hc..(t + 1) * hc * hc],
6442 );
6443 let fold = &mut folds[t * dim..(t + 1) * dim];
6444 hc_fold(state, &scratch.pre, hc, dim, fold);
6445 let ms = fold.iter().map(|v| v * v).sum::<f32>() / dim as f32;
6446 let inv = 1.0 / (ms + cfg.norm_eps).sqrt();
6447 for (v, w) in fold.iter_mut().zip(&l.ffn_norm) {
6448 *v = *v * inv * w;
6449 }
6450 resid[t * hc * dim..(t + 1) * hc * dim]
6451 .copy_from_slice(&states[t * hc * dim..(t + 1) * hc * dim]);
6452 }
6453 let t_moe = std::time::Instant::now();
6454 let tail_pack = {
6460 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6461 *ON.get_or_init(|| {
6462 std::env::var("CMF_DSV4_TAIL_PACK")
6463 .map(|v| v != "0")
6464 .unwrap_or(true)
6465 })
6466 };
6467 let mut packed_done = false;
6468 if tail_pack && pack_for(l, cfg, li).is_some() {
6469 packed_done = true;
6470 for t in 0..b {
6471 let f = &folds[t * dim..(t + 1) * dim];
6472 let forced = l.tid2eid.as_ref().map(|tbl| {
6473 hash_route(tbl, cfg.vocab, cfg.top_k, ids.get(t).copied().unwrap_or(0))
6474 });
6475 let o = &mut mo[t * dim..(t + 1) * dim];
6476 match moe_frame(f, l, cfg, li, &[], forced.as_deref(), pool, None, None, o) {
6477 Some((cold_sum, n)) => {
6478 if n > 0 {
6479 for (od, cd) in o.iter_mut().zip(cold_sum.iter()) {
6480 *od += cd;
6481 }
6482 }
6483 }
6484 None => {
6485 packed_done = false;
6486 break;
6487 }
6488 }
6489 }
6490 }
6491 if !packed_done {
6492 if host_cpu_moe() {
6493 crate::gpu::cpu_scope(|| moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo));
6494 } else {
6495 moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo);
6496 }
6497 }
6498 let t_exp = std::time::Instant::now();
6499 for t in 0..b {
6500 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
6501 hc_expand(
6502 &mo[t * dim..(t + 1) * dim],
6503 &resid[t * hc * dim..(t + 1) * hc * dim],
6504 &posts[t * hc..(t + 1) * hc],
6505 &combs[t * hc * hc..(t + 1) * hc * hc],
6506 hc,
6507 dim,
6508 state,
6509 );
6510 dspark_note(li, state, cfg);
6511 }
6512 if spec_time {
6513 eprintln!(
6514 "хвост слоя {li}: attn {:.1} мс, клей {:.1}, moe {:.1}, expand {:.1}",
6515 (t_glue - t_attn).as_secs_f64() * 1e3,
6516 (t_moe - t_glue).as_secs_f64() * 1e3,
6517 (t_exp - t_moe).as_secs_f64() * 1e3,
6518 t_exp.elapsed().as_secs_f64() * 1e3,
6519 );
6520 }
6521 }
6522}
6523
6524#[cfg(feature = "gpu")]
6530#[allow(clippy::too_many_arguments)]
6531fn partial_layer_batch(
6532 g: &Dsv4Globals,
6533 layers: &[Dsv4Layer],
6534 cfg: &Dsv4Cfg,
6535 st: &mut Dsv4State,
6536 li: usize,
6537 states: &mut [f32],
6538 ids: &[u32],
6539 pos0: usize,
6540 b: usize,
6541 pool: Option<&crate::pool::Pool>,
6542) -> bool {
6543 let l = &layers[li];
6544 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
6545 if states.len() < b * hc * dim || ids.len() < b {
6546 return false;
6547 }
6548 let Some(pk) = pack_for(l, cfg, li) else {
6549 return false;
6550 };
6551 if pk.route_complete() {
6552 return false;
6553 }
6554 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
6555 return false;
6556 };
6557 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
6558 l.wq_a.model_idx(),
6559 l.wq_b.model_idx(),
6560 l.wo_a.model_idx(),
6561 l.wo_b.model_idx(),
6562 l.wkv.model_idx(),
6563 ) else {
6564 return false;
6565 };
6566 let comp = match &l.compressor {
6567 None => None,
6568 Some(cp) => {
6569 let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
6570 return false;
6571 };
6572 Some((
6573 crate::gpu_wgpu::Dsv4CompW {
6574 wkv: a,
6575 wgate: bx,
6576 norm: &cp.norm,
6577 ape: &cp.ape,
6578 },
6579 crate::gpu_wgpu::Dsv4CompGeom {
6580 width: cp.wkv.rows(),
6581 hidden: dim,
6582 ratio: cp.ratio,
6583 overlap: cp.overlap,
6584 rope_dim: cfg.rope_head_dim,
6585 eps: cfg.norm_eps,
6586 },
6587 ))
6588 }
6589 };
6590 let ix = match &l.indexer {
6591 None => None,
6592 Some(ixr) => {
6593 let cp = &ixr.compressor;
6594 let (Some(a), Some(bx), Some(qb), Some(wp)) = (
6595 cp.wkv.model_idx(),
6596 cp.wgate.model_idx(),
6597 ixr.wq_b.model_idx(),
6598 ixr.weights_proj.model_idx(),
6599 ) else {
6600 return false;
6601 };
6602 let ih = ixr.weights_proj.rows();
6603 Some((
6604 crate::gpu_wgpu::Dsv4CompW {
6605 wkv: a,
6606 wgate: bx,
6607 norm: &cp.norm,
6608 ape: &cp.ape,
6609 },
6610 crate::gpu_wgpu::Dsv4CompGeom {
6611 width: cp.wkv.rows(),
6612 hidden: dim,
6613 ratio: cp.ratio,
6614 overlap: cp.overlap,
6615 rope_dim: cfg.rope_head_dim,
6616 eps: cfg.norm_eps,
6617 },
6618 crate::gpu_wgpu::Dsv4IxW {
6619 wq_b: qb,
6620 weights_proj: wp,
6621 },
6622 crate::gpu_wgpu::Dsv4IxGeom {
6623 ih,
6624 idim: ixr.wq_b.rows() / ih.max(1),
6625 q_lora: cfg.q_lora_rank,
6626 hidden: dim,
6627 rope_dim: cfg.rope_head_dim,
6628 eps: cfg.norm_eps,
6629 top_k: cfg.index_topk,
6630 window: cfg.window,
6631 },
6632 ))
6633 }
6634 };
6635 let ew_c = comp.as_ref().map_or(
6636 0,
6637 |(_, cg)| {
6638 if cg.overlap { cg.width / 2 } else { cg.width }
6639 },
6640 );
6641 let ew_i = ix.as_ref().map_or(
6642 0,
6643 |(_, cg, _, _)| {
6644 if cg.overlap { cg.width / 2 } else { cg.width }
6645 },
6646 );
6647 let comp_extra = comp
6648 .as_ref()
6649 .map_or(0, |(_, cg)| b.div_ceil(cg.ratio.max(1)));
6650 let need = cfg.window * hd + (st.dev_n_comp[li] + comp_extra + 1) * ew_c.max(1) + (b + 1) * hd;
6651 if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
6652 return false;
6653 }
6654 let base = crate::gpu_wgpu::Dsv4Prep {
6655 wkv,
6656 kv_norm: &l.kv_norm,
6657 comp,
6658 ix,
6659 filled: st.dev_filled[li],
6660 window: cfg.window,
6661 n_comp: st.dev_n_comp[li],
6662 n_ix: st.dev_n_ix[li],
6663 comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
6664 ix_dst_off: st.dev_n_ix[li] * ew_i,
6665 idx_cap: cfg.window
6666 + if l.indexer.is_some() {
6667 cfg.index_topk
6668 } else {
6669 st.dev_n_comp[li] + comp_extra + 1
6670 },
6671 };
6672 let mut preps = Vec::with_capacity(b);
6673 for t in 0..b {
6674 let mut p = base.clone();
6675 p.filled = (base.filled + t).min(base.window);
6676 let advanced = |ratio: usize| -> usize {
6677 if ratio == 0 {
6678 0
6679 } else {
6680 (0..t).filter(|k| (pos0 + k + 1) % ratio == 0).count()
6681 }
6682 };
6683 if let Some((_, cg)) = base.comp.as_ref() {
6684 p.n_comp = base.n_comp + advanced(cg.ratio);
6685 p.comp_dst_off = base.comp_dst_off + (p.n_comp - base.n_comp) * ew_c;
6686 }
6687 if let Some((_, cg, _, _)) = base.ix.as_ref() {
6688 p.n_ix = base.n_ix + advanced(cg.ratio);
6689 p.ix_dst_off = base.ix_dst_off + (p.n_ix - base.n_ix) * ew_i;
6690 }
6691 preps.push(p);
6692 }
6693
6694 for t in 0..b {
6698 let state = &states[t * hc * dim..(t + 1) * hc * dim];
6699 let (fold, post, comb) = hc_fold_norm(
6700 state,
6701 &l.hc_attn_fn,
6702 &l.hc_attn_scale,
6703 &l.hc_attn_base,
6704 &l.attn_norm,
6705 cfg,
6706 pool,
6707 );
6708 let mut qn = vec![0.0f32; cfg.q_lora_rank];
6709 l.wq_a.matvec(&fold, &mut qn, pool);
6710 rms_weighted(&mut qn, &l.q_norm, cfg.norm_eps);
6711 if !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, state, &post, &comb, &fold, &qn) {
6712 return false;
6713 }
6714 }
6715 let forced_rows: Vec<Option<Vec<usize>>> = ids
6716 .iter()
6717 .take(b)
6718 .map(|&id| {
6719 l.tid2eid
6720 .as_ref()
6721 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, id))
6722 })
6723 .collect();
6724 let dynv = pk.dynslots.lock().unwrap();
6725 let w = crate::gpu_wgpu::Dsv4LayerW {
6726 attn: crate::gpu_wgpu::Dsv4AttnW {
6727 wq_a,
6728 wq_b,
6729 wo_a,
6730 wo_b,
6731 q_norm: &l.q_norm,
6732 sink: &l.attn_sink,
6733 },
6734 moe: crate::gpu_wgpu::Dsv4MoeW {
6735 router: &[],
6736 experts: &pk.tensors,
6737 logits: &[],
6738 bias: pk.bias.as_deref(),
6739 mask: pk.mask.as_deref(),
6740 forced: None,
6741 remap: Some(&dynv.remap),
6742 global: None,
6743 has_shared: true,
6744 shared_weight: 1.0,
6745 preweighted: false,
6746 qwen_softmax: false,
6747 },
6748 hc_ffn_fn: &l.hc_ffn_fn,
6749 hc_ffn_scale: &l.hc_ffn_scale,
6750 hc_ffn_base: &l.hc_ffn_base,
6751 hc_next_fn: None,
6754 hc_next_scale: &l.hc_attn_scale,
6755 hc_next_base: &l.hc_attn_base,
6756 ffn_norm: &l.ffn_norm,
6757 next_norm: &l.attn_norm,
6758 next_q_norm: &l.q_norm,
6759 next_wq_a: None,
6760 router: &pk.router,
6761 };
6762 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
6763 attn: crate::gpu_wgpu::Dsv4AttnGeom {
6764 dim,
6765 nh: cfg.n_heads,
6766 hd,
6767 rd: cfg.rope_head_dim,
6768 q_lora: cfg.q_lora_rank,
6769 o_lora: cfg.o_lora_rank,
6770 o_groups: cfg.o_groups,
6771 eps: cfg.norm_eps,
6772 scale: (hd as f32).powf(-0.5),
6773 bf16: false,
6774 q_rms: true,
6775 },
6776 moe: crate::gpu_wgpu::Dsv4MoeGeom {
6777 hidden: dim,
6778 inter: cfg.moe_inter,
6779 top_k: cfg.top_k,
6780 route_scale: cfg.route_scale,
6781 swiglu_limit: cfg.swiglu_limit,
6782 gu_q2: l
6783 .experts
6784 .first()
6785 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
6786 bf16: false,
6787 },
6788 hc,
6789 hc_eps: cfg.hc_eps,
6790 sinkhorn_iters: cfg.hc_sinkhorn_iters,
6791 };
6792 let freqs = if l.compressor.is_some() {
6793 g.inv_freq_compress.as_slice()
6794 } else {
6795 g.inv_freq_window.as_slice()
6796 };
6797 let Some(mut got) = crate::gpu_wgpu::dsv4_layer_batch_partial(
6798 &model,
6799 &w,
6800 geom,
6801 st.kv_id,
6802 li,
6803 b,
6804 &preps,
6805 Some(&forced_rows),
6806 freqs,
6807 pos0,
6808 ) else {
6809 return false;
6810 };
6811 drop(dynv);
6812 let mut cold_sum = vec![0.0f32; b * dim];
6813 cold_step_block(&got.cold_x, b, l, cfg, &got.cold, pool, &mut cold_sum);
6814 for t in 0..b {
6815 let state = &mut got.states[t * hc * dim..(t + 1) * hc * dim];
6816 let post = &got.posts[t * hc..(t + 1) * hc];
6817 let cold = &cold_sum[t * dim..(t + 1) * dim];
6818 for j in 0..hc {
6819 for d in 0..dim {
6820 state[j * dim + d] += post[j] * cold[d];
6821 }
6822 }
6823 }
6824 states[..b * hc * dim].copy_from_slice(&got.states);
6825 if !crate::gpu_wgpu::dsv4_spec_cap_write_host(li, b, hc * dim, &got.states) {
6826 return false;
6827 }
6828 if let Some(first_route) = got.routed.first() {
6829 refill_route_slots(l, cfg, &pk, first_route);
6830 }
6831 for t in 0..b {
6832 let pos = pos0 + t;
6833 st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
6834 if let Some((_, cg, ..)) = base.ix.as_ref() {
6835 if (pos + 1) % cg.ratio == 0 {
6836 st.dev_n_ix[li] += 1;
6837 }
6838 }
6839 if let Some((_, cg)) = base.comp.as_ref() {
6840 if (pos + 1) % cg.ratio == 0 {
6841 st.dev_n_comp[li] += 1;
6842 note_compressed(st.kv_id, li, st.dev_n_comp[li]);
6843 }
6844 }
6845 }
6846 true
6847}
6848
6849#[cfg(feature = "gpu")]
6858#[allow(clippy::too_many_arguments)]
6859pub fn dsv4_verify_chunk(
6860 g: &Dsv4Globals,
6861 layers: &[Dsv4Layer],
6862 cfg: &Dsv4Cfg,
6863 st: &mut Dsv4State,
6864 ids: &[u32],
6865 pos0: usize,
6866 inv_freq: &[f32],
6867 pool: Option<&crate::pool::Pool>,
6868 cap_targets: &[usize],
6869 argmax_out: &mut Vec<u32>,
6870 logits_out: &mut Vec<f32>,
6871 walked_out: &mut Vec<f32>,
6872) -> Option<Dsv4SpecTxn> {
6873 let b = ids.len();
6874 let complete_end = st
6878 .dev_set
6879 .iter()
6880 .enumerate()
6881 .position(|(li, &on)| {
6882 !on || pack_for(&layers[li], cfg, li).is_none_or(|p| !p.route_complete())
6883 })
6884 .unwrap_or(st.dev_set.len());
6885 fn partial_batch_on() -> bool {
6886 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6887 *ON.get_or_init(|| {
6888 std::env::var("CMF_DSV4_PARTIAL_BATCH")
6889 .map(|v| v != "0")
6890 .unwrap_or(true)
6891 })
6892 }
6893 let device_end = if partial_batch_on() {
6894 (complete_end..layers.len())
6895 .take_while(|&li| {
6896 st.dev_set.get(li).copied().unwrap_or(false)
6897 && st.partial_set.get(li).copied().unwrap_or(false)
6898 && pack_for(&layers[li], cfg, li).is_some_and(|p| !p.route_complete())
6899 })
6900 .last()
6901 .map_or(complete_end, |li| li + 1)
6902 } else {
6903 complete_end
6904 };
6905 let full_beyond = st.dev_set[device_end.min(st.dev_set.len())..]
6908 .iter()
6909 .enumerate()
6910 .any(|(i, &on)| on && !st.partial_set.get(device_end + i).copied().unwrap_or(false));
6911 fn host_verify_on() -> bool {
6918 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6919 *ON.get_or_init(|| {
6920 std::env::var("CMF_DSV4_HOST_VERIFY")
6921 .map(|v| v != "0")
6922 .unwrap_or(true)
6927 })
6928 }
6929 if b < 2
6930 || !chain_enabled()
6931 || !st.dev_owned
6932 || st.dev_set.len() != layers.len()
6933 || (device_end == 0 && !host_verify_on())
6934 || full_beyond
6935 {
6936 return None;
6937 }
6938 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
6939 let metas: Vec<(usize, usize, usize, usize)> = (0..device_end)
6941 .map(|li| (li, hd, cfg.window, st.dev_filled[li]))
6942 .collect();
6943 let shadow = crate::gpu_wgpu::dsv4_spec_shadow(st.kv_id, &metas, b)?;
6944 let mut txn = Dsv4SpecTxn {
6945 pos0,
6946 batch: b,
6947 gpu_end: device_end,
6948 dev_filled: st.dev_filled.clone(),
6949 dev_n_comp: st.dev_n_comp.clone(),
6950 dev_n_ix: st.dev_n_ix.clone(),
6951 host: (device_end..layers.len())
6952 .map(|li| (li, host_snap(st, li)))
6953 .collect(),
6954 states: Vec::new(),
6955 host_steps: Vec::new(),
6956 shadow: Some(shadow),
6957 };
6958 let dev_caps: Vec<usize> = cap_targets
6960 .iter()
6961 .copied()
6962 .filter(|&t| t < device_end)
6963 .collect();
6964 crate::gpu_wgpu::dsv4_spec_retain_arm(device_end, &dev_caps);
6965
6966 let mut emb = vec![0.0f32; dim];
6968 let mut states = vec![0.0f32; b * hc * dim];
6969 for (t, &id) in ids.iter().enumerate() {
6970 let mut state = vec![0.0f32; hc * dim];
6971 g.embed.row_f32(id as usize, &mut emb);
6972 for j in 0..hc {
6973 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
6974 }
6975 states[t * hc * dim..(t + 1) * hc * dim].copy_from_slice(&state);
6976 let (folded, post0, comb0) = hc_fold_norm(
6977 &state,
6978 &layers[0].hc_attn_fn,
6979 &layers[0].hc_attn_scale,
6980 &layers[0].hc_attn_base,
6981 &layers[0].attn_norm,
6982 cfg,
6983 pool,
6984 );
6985 let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
6986 layers[0].wq_a.matvec(&folded, &mut qn0, pool);
6987 rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
6988 if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
6989 || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
6990 || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
6991 || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
6992 {
6993 crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
6994 return None;
6995 }
6996 }
6997 let spec_time = {
6998 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6999 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
7000 };
7001 let t0 = std::time::Instant::now();
7002 let mut folded = Vec::new();
7003 st.pos = pos0;
7004 let fp_split = verify_fp_on(pos0) && std::env::var("CMF_DSV4_FP_SPLIT").is_ok_and(|v| v != "0");
7008 let mut ok = true;
7009 if fp_split {
7010 for li in 0..complete_end {
7011 let one = [li];
7012 ok = dsv4_chain_run(
7013 layers,
7014 &one,
7015 cfg,
7016 g,
7017 st,
7018 *ids.last().unwrap(),
7019 &mut folded,
7020 Some(&mut states),
7021 b,
7022 ids,
7023 li == 0,
7024 pool,
7025 );
7026 if !ok {
7027 break;
7028 }
7029 verify_fp("verify", pos0, li, &states[..hc * dim]);
7030 }
7031 } else if complete_end > 0 {
7032 let run: Vec<usize> = (0..complete_end).collect();
7033 ok = dsv4_chain_run(
7034 layers,
7035 &run,
7036 cfg,
7037 g,
7038 st,
7039 *ids.last().unwrap(),
7040 &mut folded,
7041 Some(&mut states),
7042 b,
7043 ids,
7044 true,
7045 pool,
7046 );
7047 if ok {
7048 verify_fp("verify", pos0, complete_end - 1, &states[..hc * dim]);
7049 }
7050 }
7051 if ok {
7052 for li in complete_end..device_end {
7053 if !partial_layer_batch(g, layers, cfg, st, li, &mut states, ids, pos0, b, pool) {
7054 ok = false;
7055 break;
7056 }
7057 verify_fp("verify", pos0, li, &states[..hc * dim]);
7058 }
7059 }
7060 crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
7061 if !ok {
7062 if let Some(sh) = txn.shadow.take() {
7065 let _ = crate::gpu_wgpu::dsv4_spec_restore(&sh);
7066 }
7067 st.dev_filled = txn.dev_filled;
7068 st.dev_n_comp = txn.dev_n_comp;
7069 st.dev_n_ix = txn.dev_n_ix;
7070 st.pos = pos0;
7071 return None;
7072 }
7073 txn.states = states.clone();
7074 let t_chain = t0.elapsed();
7075 if std::env::var("CMF_DSV4_FOLD_DBG").is_ok() {
7076 for li in 0..device_end {
7080 let Some(ixr) = &layers[li].indexer else {
7081 continue;
7082 };
7083 let ratio = ixr.compressor.ratio;
7084 for t in 0..b {
7085 if (pos0 + t + 1) % ratio == 0 {
7086 let ew = {
7087 let w = ixr.compressor.wkv.rows();
7088 if ixr.compressor.overlap { w / 2 } else { w }
7089 };
7090 let idx_new = txn.dev_n_ix[li]
7091 + (0..=t).filter(|k| (pos0 + k + 1) % ratio == 0).count()
7092 - 1;
7093 if let Some(v) =
7094 crate::gpu_wgpu::dsv4_dbg_read_ix(st.kv_id, li, idx_new * ew, ew.min(8))
7095 {
7096 let sum: f32 = v.iter().sum();
7097 eprintln!(
7098 "[fold] li={li} pos={} entry={idx_new} head={:?} sum={sum:.6}",
7099 pos0 + t,
7100 &v[..4.min(v.len())]
7101 );
7102 }
7103 }
7104 }
7105 }
7106 }
7107
7108 let mut scratch = HcScratch::new(cfg);
7110 argmax_out.clear();
7111 logits_out.clear();
7112 logits_out.resize(b * cfg.vocab, 0.0);
7113 let mut head_in = vec![0.0f32; b * dim];
7114 let mut host_steps: Vec<(usize, Vec<HostLayerSnap>)> = Vec::new();
7115 host_tail_walk_batch(
7116 g,
7117 layers,
7118 cfg,
7119 st,
7120 device_end,
7121 &mut states,
7122 ids,
7123 pos0,
7124 b,
7125 inv_freq,
7126 &mut scratch,
7127 pool,
7128 Some(&mut host_steps),
7129 );
7130 txn.host_steps = host_steps;
7131 for t in 0..b {
7132 let state = &states[t * hc * dim..(t + 1) * hc * dim];
7133 let h = &mut head_in[t * dim..(t + 1) * dim];
7134 hc_head_fold(
7135 state,
7136 &g.hc_head_fn,
7137 g.hc_head_scale,
7138 &g.hc_head_base,
7139 cfg,
7140 pool,
7141 h,
7142 );
7143 rms_weighted(h, &g.norm, cfg.norm_eps);
7144 }
7145 let batch_head = std::env::var("CMF_DSV4_SPEC_BATCH_HEAD").is_ok_and(|v| v != "0");
7150 let head_gpu = batch_head
7151 && g.head.model_idx().is_some_and(|hi| {
7152 let model = layers[0].experts.first().and_then(|e| e.w1.model_arc());
7153 model.is_some_and(|m| {
7154 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
7155 &m, hi, &head_in, b, cfg.vocab, dim, logits_out,
7156 )
7157 })
7158 });
7159 for t in 0..b {
7160 if !head_gpu {
7161 let h = &head_in[t * dim..(t + 1) * dim];
7162 g.head
7163 .matvec(h, &mut logits_out[t * cfg.vocab..(t + 1) * cfg.vocab], pool);
7164 }
7165 let row = &logits_out[t * cfg.vocab..(t + 1) * cfg.vocab];
7166 let mut best = 0usize;
7167 for v in 1..cfg.vocab {
7168 if row[v] > row[best] {
7169 best = v;
7170 }
7171 }
7172 argmax_out.push(best as u32);
7173 }
7174 walked_out.clear();
7175 walked_out.extend_from_slice(&states);
7176 st.pos = pos0 + b;
7177 if spec_time {
7178 eprintln!(
7179 "verify: тень+сид+цепочка {:.1} мс, хвост+голова {:.1} мс",
7180 t_chain.as_secs_f64() * 1e3,
7181 (t0.elapsed() - t_chain).as_secs_f64() * 1e3,
7182 );
7183 }
7184 Some(txn)
7185}
7186
7187#[cfg(feature = "gpu")]
7195pub fn dsv4_spec_finish(
7196 g: &Dsv4Globals,
7197 layers: &[Dsv4Layer],
7198 cfg: &Dsv4Cfg,
7199 st: &mut Dsv4State,
7200 mut txn: Dsv4SpecTxn,
7201 accepted: usize,
7202 ids: &[u32],
7203 inv_freq: &[f32],
7204 pool: Option<&crate::pool::Pool>,
7205) -> bool {
7206 macro_rules! sfail {
7207 ($($t:tt)*) => {{
7208 if std::env::var("CMF_DSV4_SPEC_DEBUG").is_ok() {
7209 eprintln!("spec_finish: {}", format_args!($($t)*));
7210 }
7211 return false;
7212 }};
7213 }
7214 let b = txn.batch;
7215 let k = accepted.min(b);
7216 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
7217 let win_metas: Vec<(usize, usize, usize, usize)> = (0..txn.gpu_end)
7220 .map(|li| (li, txn.dev_filled[li], cfg.window, hd))
7221 .collect();
7222 if !crate::gpu_wgpu::dsv4_spec_commit_windows(st.kv_id, &win_metas, b, k) {
7223 sfail!("коммит окон");
7224 }
7225 if k == b {
7226 return true;
7229 }
7230 let Some(sh) = txn.shadow.take() else {
7232 sfail!("нет тени")
7233 };
7234 if !crate::gpu_wgpu::dsv4_spec_restore(&sh) {
7235 sfail!("restore");
7236 }
7237 let Some(model) = layers[0].experts.first().and_then(|e| e.w1.model_arc()) else {
7238 sfail!("нет модели");
7239 };
7240 let mut plan: Vec<(usize, crate::gpu_wgpu::Dsv4Prep)> = Vec::new();
7241 let mut freqs_own: Vec<&[f32]> = Vec::new();
7242 for li in 0..txn.gpu_end {
7243 let l = &layers[li];
7244 let Some(wkv) = l.wkv.model_idx() else {
7245 sfail!("wkv слоя {li}")
7246 };
7247 let comp = match &l.compressor {
7248 None => None,
7249 Some(cp) => {
7250 let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
7251 sfail!("компрессор слоя {li}");
7252 };
7253 Some((
7254 crate::gpu_wgpu::Dsv4CompW {
7255 wkv: a,
7256 wgate: bx,
7257 norm: &cp.norm,
7258 ape: &cp.ape,
7259 },
7260 crate::gpu_wgpu::Dsv4CompGeom {
7261 width: cp.wkv.rows(),
7262 hidden: dim,
7263 ratio: cp.ratio,
7264 overlap: cp.overlap,
7265 rope_dim: cfg.rope_head_dim,
7266 eps: cfg.norm_eps,
7267 },
7268 ))
7269 }
7270 };
7271 let ix = match &l.indexer {
7272 None => None,
7273 Some(ixr) => {
7274 let cp = &ixr.compressor;
7275 let (Some(a), Some(bx), Some(qb), Some(wp)) = (
7276 cp.wkv.model_idx(),
7277 cp.wgate.model_idx(),
7278 ixr.wq_b.model_idx(),
7279 ixr.weights_proj.model_idx(),
7280 ) else {
7281 sfail!("индексер слоя {li}");
7282 };
7283 let ih = ixr.weights_proj.rows();
7284 Some((
7285 crate::gpu_wgpu::Dsv4CompW {
7286 wkv: a,
7287 wgate: bx,
7288 norm: &cp.norm,
7289 ape: &cp.ape,
7290 },
7291 crate::gpu_wgpu::Dsv4CompGeom {
7292 width: cp.wkv.rows(),
7293 hidden: dim,
7294 ratio: cp.ratio,
7295 overlap: cp.overlap,
7296 rope_dim: cfg.rope_head_dim,
7297 eps: cfg.norm_eps,
7298 },
7299 crate::gpu_wgpu::Dsv4IxW {
7300 wq_b: qb,
7301 weights_proj: wp,
7302 },
7303 crate::gpu_wgpu::Dsv4IxGeom {
7304 ih,
7305 idim: ixr.wq_b.rows() / ih.max(1),
7306 q_lora: cfg.q_lora_rank,
7307 hidden: dim,
7308 rope_dim: cfg.rope_head_dim,
7309 eps: cfg.norm_eps,
7310 top_k: cfg.index_topk,
7311 window: cfg.window,
7312 },
7313 ))
7314 }
7315 };
7316 let ew_c = comp.as_ref().map_or(
7317 0,
7318 |(_, cg)| {
7319 if cg.overlap { cg.width / 2 } else { cg.width }
7320 },
7321 );
7322 let ew_i = ix.as_ref().map_or(
7323 0,
7324 |(_, cg, _, _)| {
7325 if cg.overlap { cg.width / 2 } else { cg.width }
7326 },
7327 );
7328 let prep = crate::gpu_wgpu::Dsv4Prep {
7329 wkv,
7330 kv_norm: &l.kv_norm,
7331 comp,
7332 ix,
7333 filled: txn.dev_filled[li],
7334 window: cfg.window,
7335 n_comp: txn.dev_n_comp[li],
7336 n_ix: txn.dev_n_ix[li],
7337 comp_dst_off: cfg.window * hd + txn.dev_n_comp[li] * ew_c,
7338 ix_dst_off: txn.dev_n_ix[li] * ew_i,
7339 idx_cap: cfg.window
7340 + if l.indexer.is_some() {
7341 cfg.index_topk
7342 } else {
7343 0
7344 },
7345 };
7346 let fr = if l.compressor.is_some() {
7347 g.inv_freq_compress.as_slice()
7348 } else {
7349 g.inv_freq_window.as_slice()
7350 };
7351 freqs_own.push(if fr.is_empty() { inv_freq } else { fr });
7352 plan.push((li, prep));
7353 }
7354 if !crate::gpu_wgpu::dsv4_spec_replay(
7355 &model,
7356 &plan,
7357 st.kv_id,
7358 txn.pos0,
7359 b,
7360 k,
7361 &freqs_own,
7362 hd,
7363 dim,
7364 cfg.rope_head_dim,
7365 cfg.norm_eps,
7366 true,
7367 ) {
7368 sfail!("replay k={k}");
7369 }
7370 let advanced = |ratio: usize| -> usize {
7372 if ratio == 0 {
7373 return 0;
7374 }
7375 (0..k).filter(|t| (txn.pos0 + t + 1) % ratio == 0).count()
7376 };
7377 for li in 0..txn.gpu_end {
7378 let l = &layers[li];
7379 st.dev_filled[li] = (txn.dev_filled[li] + k).min(cfg.window);
7380 let ac = l.compressor.as_ref().map_or(0, |cp| advanced(cp.ratio));
7381 let ai = l
7382 .indexer
7383 .as_ref()
7384 .map_or(0, |ix| advanced(ix.compressor.ratio));
7385 st.dev_n_comp[li] = txn.dev_n_comp[li] + ac;
7386 st.dev_n_ix[li] = txn.dev_n_ix[li] + ai;
7387 note_compressed(st.kv_id, li, st.dev_n_comp[li]);
7388 }
7389 if k >= 1 && txn.host_steps.iter().all(|(_, v)| v.len() >= k) && !txn.host_steps.is_empty() {
7392 for (li, v) in &txn.host_steps {
7393 host_restore(st, *li, &v[k - 1]);
7394 }
7395 } else {
7396 for (li, snap) in &txn.host {
7397 host_restore(st, *li, snap);
7398 }
7399 let mut scratch = HcScratch::new(cfg);
7400 let mut states = txn.states.clone();
7401 host_tail_walk_batch(
7402 g,
7403 layers,
7404 cfg,
7405 st,
7406 txn.gpu_end,
7407 &mut states[..k * hc * dim],
7408 ids,
7409 txn.pos0,
7410 k,
7411 inv_freq,
7412 &mut scratch,
7413 pool,
7414 None,
7415 );
7416 }
7417 st.pos = txn.pos0 + k;
7418 true
7419}
7420
7421pub fn forward_chunk(
7422 g: &Dsv4Globals,
7423 layers: &[Dsv4Layer],
7424 cfg: &Dsv4Cfg,
7425 st: &mut Dsv4State,
7426 ids: &[u32],
7427 pos0: usize,
7428 inv_freq: &[f32],
7429 pool: Option<&crate::pool::Pool>,
7430 logits: &mut Vec<f32>,
7431 want_logits: bool,
7432) {
7433 let bs = batch_prefill();
7434 if bs > 1 {
7435 let mut i = 0;
7442 if !st.dev_owned && !ids.is_empty() {
7443 st.pos = pos0;
7444 forward_token_inner(
7445 g,
7446 layers,
7447 cfg,
7448 st,
7449 ids[0],
7450 inv_freq,
7451 pool,
7452 logits,
7453 ids.len() == 1,
7454 );
7455 i = 1;
7456 }
7457 while i < ids.len() {
7458 let end = (i + bs).min(ids.len());
7459 st.pos = pos0 + i;
7460 if !forward_chunk_batched(
7461 g,
7462 layers,
7463 cfg,
7464 st,
7465 &ids[i..end],
7466 pos0 + i,
7467 inv_freq,
7468 pool,
7469 logits,
7470 want_logits && end == ids.len(),
7471 ) {
7472 break;
7473 }
7474 i = end;
7475 }
7476 if i == ids.len() {
7477 return;
7478 }
7479 for (k, &id) in ids.iter().enumerate().skip(i) {
7481 st.pos = pos0 + k;
7482 let last = want_logits && k + 1 == ids.len();
7483 forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
7484 }
7485 return;
7486 }
7487 for (i, &id) in ids.iter().enumerate() {
7488 st.pos = pos0 + i;
7489 let last = want_logits && i + 1 == ids.len();
7490 forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
7491 }
7492}
7493
7494pub fn forward_token(
7495 g: &Dsv4Globals,
7496 layers: &[Dsv4Layer],
7497 cfg: &Dsv4Cfg,
7498 st: &mut Dsv4State,
7499 token_id: u32,
7500 inv_freq: &[f32],
7501 pool: Option<&crate::pool::Pool>,
7502 logits: &mut Vec<f32>,
7503) {
7504 forward_token_inner(g, layers, cfg, st, token_id, inv_freq, pool, logits, true);
7505}
7506
7507#[allow(clippy::too_many_arguments)]
7508fn forward_token_inner(
7509 g: &Dsv4Globals,
7510 layers: &[Dsv4Layer],
7511 cfg: &Dsv4Cfg,
7512 st: &mut Dsv4State,
7513 token_id: u32,
7514 inv_freq: &[f32],
7515 pool: Option<&crate::pool::Pool>,
7516 logits: &mut Vec<f32>,
7517 want_logits: bool,
7519) {
7520 let _t_all = prof::on().then(std::time::Instant::now);
7521 let _all_guard = Charge(_t_all, &prof::ALL_NS);
7522 let (hc, dim) = (cfg.hc_mult, cfg.dim);
7523
7524 let mut emb = vec![0.0f32; dim];
7526 g.embed.row_f32(token_id as usize, &mut emb);
7527 let mut state = vec![0.0f32; hc * dim];
7528 for j in 0..hc {
7529 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
7530 }
7531
7532 let mut scratch = HcScratch::new(cfg);
7533 let mut dump: Vec<String> = Vec::new();
7534 if dump_path().is_some() {
7535 dump.push(format!("\"embed\":{}", vec_json(&emb)));
7536 PICKED.with(|p| p.borrow_mut().clear());
7537 BODY.with(|b| b.borrow_mut().clear());
7538 dump.push(",\"layers\":[".into());
7539 }
7540 if trace_on() {
7541 eprintln!(
7542 "[dsv4] tok={token_id} pos={} embed rms={:.5}",
7543 st.pos,
7544 rms_of(&emb)
7545 );
7546 }
7547 #[cfg(feature = "gpu")]
7549 let layer_frames = gpu_layer_enabled()
7550 && dsv4_layer_loop(
7551 &mut state,
7552 layers,
7553 g,
7554 cfg,
7555 st,
7556 token_id,
7557 inv_freq,
7558 pool,
7559 &mut scratch,
7560 );
7561 #[cfg(not(feature = "gpu"))]
7562 let layer_frames = false;
7563
7564 #[cfg(feature = "gpu")]
7571 let hc_dev = hc_on_device()
7572 && !layer_frames
7573 && gpu_attn_enabled()
7574 && gpu_moe2_enabled()
7575 && dump_path().is_none();
7576 #[cfg(not(feature = "gpu"))]
7577 let hc_dev = false;
7578 #[cfg(feature = "gpu")]
7585 let two_frame_done = hc_dev
7586 && dsv4_two_frame_loop(
7587 &mut state,
7588 layers,
7589 g,
7590 cfg,
7591 st,
7592 token_id,
7593 inv_freq,
7594 pool,
7595 &mut scratch,
7596 );
7597 #[cfg(not(feature = "gpu"))]
7598 let two_frame_done = false;
7599 if !two_frame_done {
7600 for (li, l) in layers.iter().enumerate() {
7601 if layer_frames {
7602 break;
7603 }
7604 hc_block(
7606 &mut state,
7607 &l.hc_attn_fn,
7608 &l.hc_attn_scale,
7609 &l.hc_attn_base,
7610 &l.attn_norm,
7611 cfg,
7612 &mut scratch,
7613 pool,
7614 |folded, out| {
7615 if dump_path().is_some() {
7616 BODY.with(|b| b.borrow_mut().push(vec_json(folded)));
7619 }
7620 let freqs = if l.compressor.is_some() {
7622 &g.inv_freq_compress
7623 } else {
7624 &g.inv_freq_window
7625 };
7626 let freqs = if freqs.is_empty() {
7627 inv_freq
7628 } else {
7629 freqs.as_slice()
7630 };
7631 attention_step(folded, l, cfg, st, li, freqs, pool, None, out);
7632 if dump_path().is_some() {
7633 BODY.with(|b| b.borrow_mut().push(vec_json(out)));
7634 }
7635 },
7636 );
7637 if dump_path().is_some() {
7638 dump.push(format!(
7641 "{}{}",
7642 if li == 0 { "" } else { "," },
7643 vec_json(&state)
7644 ));
7645 }
7646 let _t_hc2 = prof::on().then(std::time::Instant::now);
7648 hc_block(
7649 &mut state,
7650 &l.hc_ffn_fn,
7651 &l.hc_ffn_scale,
7652 &l.hc_ffn_base,
7653 &l.ffn_norm,
7654 cfg,
7655 &mut scratch,
7656 pool,
7657 |folded, out| moe_step(folded, l, cfg, token_id, li, pool, out),
7658 );
7659 if let Some(t) = _t_hc2 {
7660 prof::HC_NS.fetch_add(
7663 t.elapsed().as_nanos() as u64,
7664 std::sync::atomic::Ordering::Relaxed,
7665 );
7666 }
7667 if dump_path().is_some() {
7668 dump.push(format!(",{}", vec_json(&state)));
7669 }
7670 if trace_on() && (st.pos % 64 == 0 || st.pos == 199) {
7671 eprintln!(
7672 "[dsv4] кеши слоя {li}: окно={} сжатых={} индекс={} (ratio={:?})",
7673 st.window[li].len() / cfg.head_dim.max(1),
7674 st.compressed[li].len() / cfg.head_dim.max(1),
7675 st.index_kv[li].len().max(1) / 128,
7676 l.compressor.as_ref().map(|c| (c.ratio, c.overlap)),
7677 );
7678 }
7679 if trace_on() {
7680 let bad = state.iter().filter(|v| !v.is_finite()).count();
7681 eprintln!(
7682 "[dsv4] layer {li:>2}: rms={:.5}{}",
7683 rms_of(&state),
7684 if bad > 0 {
7685 format!(" NON-FINITE x{bad}")
7686 } else {
7687 String::new()
7688 }
7689 );
7690 }
7691 dspark_note(li, &state, cfg);
7692 }
7693 }
7694 st.pos += 1;
7695
7696 let mut h = vec![0.0f32; dim];
7698 hc_head_fold(
7699 &state,
7700 &g.hc_head_fn,
7701 g.hc_head_scale,
7702 &g.hc_head_base,
7703 cfg,
7704 pool,
7705 &mut h,
7706 );
7707 if !want_logits {
7708 logits.clear();
7709 return;
7710 }
7711 let _t_head = prof::on().then(std::time::Instant::now);
7712 rms_weighted(&mut h, &g.norm, cfg.norm_eps);
7713 logits.clear();
7714 logits.resize(g.head.rows(), 0.0);
7715 g.head.matvec(&h, logits, pool);
7716 if let Some(t) = _t_head {
7717 prof::HEAD_NS.fetch_add(
7718 t.elapsed().as_nanos() as u64,
7719 std::sync::atomic::Ordering::Relaxed,
7720 );
7721 }
7722 if dump_path().is_some() {
7723 dump.push("]".into());
7724 let picked = PICKED.with(|p| {
7725 p.borrow()
7726 .iter()
7727 .map(|v| {
7728 format!(
7729 "[{}]",
7730 v.iter()
7731 .map(|e| e.to_string())
7732 .collect::<Vec<_>>()
7733 .join(",")
7734 )
7735 })
7736 .collect::<Vec<_>>()
7737 .join(",")
7738 });
7739 dump.push(format!(",\"experts\":[{picked}]"));
7740 let body = BODY.with(|b| b.borrow().join(","));
7741 dump.push(format!(",\"attn_io\":[{body}]"));
7742 dump_line(&format!(
7743 "{{\"tok\":{token_id},\"pos\":{},{},\"head\":{},\"logits\":{}}}",
7744 st.pos - 1,
7745 dump.join(""),
7746 vec_json(&h),
7747 vec_json(logits)
7748 ));
7749 }
7750 if trace_on() {
7751 let (mut top, mut best) = (0usize, f32::NEG_INFINITY);
7752 for (i, &v) in logits.iter().enumerate() {
7753 if v > best {
7754 best = v;
7755 top = i;
7756 }
7757 }
7758 let lo = logits.iter().cloned().fold(f32::MAX, f32::min);
7759 eprintln!(
7760 "[dsv4] head: rms={:.5} logits[{}..{:.3}] argmax={top}",
7761 rms_of(&h),
7762 format_args!("{lo:.3}"),
7763 best
7764 );
7765 }
7766}
7767
7768pub fn load(
7775 model: &std::sync::Arc<cortiq_core::CmfModel>,
7776 cfg: &Dsv4Cfg,
7777 n_layers: usize,
7778) -> Result<(Dsv4Globals, Vec<Dsv4Layer>), String> {
7779 let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
7780 crate::qtensor::QTensor::from_model(model, name)
7781 };
7782 let f = |name: &str| -> Result<Vec<f32>, String> {
7786 crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
7787 };
7788
7789 let rope_of = |base: f32, yarn: bool| -> Vec<f32> {
7794 if yarn {
7795 crate::attention::yarn_inv_freq(cfg.rope_head_dim, base, 16.0, 65536, 32.0, 1.0)
7796 } else {
7797 crate::attention::rope_inv_freq(cfg.rope_head_dim, base)
7798 }
7799 };
7800 let globals = Dsv4Globals {
7801 inv_freq_compress: rope_of(160_000.0, true),
7802 inv_freq_window: rope_of(10_000.0, false),
7803 embed: q("model.embed_tokens.weight")?,
7804 norm: f("model.norm.weight")?,
7805 head: q("lm_head.weight")?,
7806 hc_head_fn: f("model.hc_head_fn")?,
7807 hc_head_base: f("model.hc_head_base")?,
7808 hc_head_scale: *f("model.hc_head_scale")?
7809 .first()
7810 .ok_or("dsv4: empty hc_head_scale")?,
7811 };
7812
7813 let mut layers = Vec::with_capacity(n_layers);
7814 for li in 0..n_layers {
7815 layers.push(load_layer(
7816 model,
7817 cfg,
7818 &format!("model.layers.{li}"),
7819 Scheme::Main,
7820 )?);
7821 }
7822 #[cfg(feature = "gpu")]
7828 if crate::gpu_wgpu::host_banks_on() {
7829 let sets: Vec<(usize, Vec<(usize, usize, usize)>, bool)> = layers
7831 .iter()
7832 .filter_map(|l| {
7833 let idx3 = |e: &Dsv4Expert| {
7834 Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
7835 };
7836 let mut v: Vec<_> = l.experts.iter().filter_map(idx3).collect();
7837 let first = v.first()?.0;
7838 v.push(idx3(&l.shared)?);
7839 let gu_q2 =
7840 l.experts.first()?.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
7841 Some((first, v, gu_q2))
7842 })
7843 .collect();
7844 let m2 = model.clone();
7845 let (inter, dim) = (cfg.moe_inter, cfg.dim);
7846 std::thread::spawn(move || {
7847 for (first, v, gu_q2) in sets {
7848 crate::gpu_wgpu::dsv4_host_bank_build(&m2, first, &v, inter, dim, gu_q2);
7849 }
7850 tracing::info!("host banks: built");
7851 });
7852 }
7853 #[cfg(feature = "gpu")]
7854 {
7855 let masks: Vec<Option<Vec<bool>>> = layers.iter().map(|l| l.mask.clone()).collect();
7856 crate::gpu_wgpu::prefetch_tier(model, &|name: &str| {
7857 let Some(i) = name.find(".experts.") else {
7858 return false;
7859 };
7860 let rest = &name[i + 9..];
7861 let e: usize = match rest[..rest.find('.').unwrap_or(rest.len())].parse() {
7862 Ok(v) => v,
7863 Err(_) => return false,
7864 };
7865 let li: usize = {
7866 let Some(j) = name.find("layers.") else {
7867 return false;
7868 };
7869 let r = &name[j + 7..];
7870 match r[..r.find('.').unwrap_or(r.len())].parse() {
7871 Ok(v) => v,
7872 Err(_) => return false,
7873 }
7874 };
7875 match masks.get(li).and_then(|m| m.as_ref()) {
7876 Some(m) => m.get(e).copied().unwrap_or(false),
7877 None => true,
7878 }
7879 });
7880 }
7881 Ok((globals, layers))
7882}
7883
7884#[derive(Clone, Copy, PartialEq, Eq, Debug)]
7891pub enum Scheme {
7892 Main,
7893 Mtp,
7894}
7895
7896impl Scheme {
7897 fn attn(self) -> &'static str {
7898 match self {
7899 Scheme::Main => "self_attn",
7900 Scheme::Mtp => "attn",
7901 }
7902 }
7903 fn attn_norm(self) -> &'static str {
7904 match self {
7905 Scheme::Main => "input_layernorm.weight",
7906 Scheme::Mtp => "attn_norm.weight",
7907 }
7908 }
7909 fn ffn_norm(self) -> &'static str {
7910 match self {
7911 Scheme::Main => "post_attention_layernorm.weight",
7912 Scheme::Mtp => "ffn_norm.weight",
7913 }
7914 }
7915 fn mlp(self) -> &'static str {
7916 match self {
7917 Scheme::Main => "mlp",
7918 Scheme::Mtp => "ffn",
7919 }
7920 }
7921 fn gate_bias(self) -> &'static str {
7924 match self {
7925 Scheme::Main => "expert_bias",
7926 Scheme::Mtp => "gate.bias",
7927 }
7928 }
7929 fn shared(self) -> &'static str {
7930 match self {
7931 Scheme::Main => "shared_expert",
7932 Scheme::Mtp => "shared_experts",
7933 }
7934 }
7935 fn w(self, i: u8) -> &'static str {
7937 match (self, i) {
7938 (Scheme::Main, 1) => "gate_proj.weight",
7939 (Scheme::Main, 2) => "down_proj.weight",
7940 (Scheme::Main, _) => "up_proj.weight",
7941 (Scheme::Mtp, 1) => "w1.weight",
7942 (Scheme::Mtp, 2) => "w2.weight",
7943 (Scheme::Mtp, _) => "w3.weight",
7944 }
7945 }
7946}
7947
7948pub fn load_layer(
7950 model: &std::sync::Arc<cortiq_core::CmfModel>,
7951 cfg: &Dsv4Cfg,
7952 p: &str,
7953 s: Scheme,
7954) -> Result<Dsv4Layer, String> {
7955 let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
7956 crate::qtensor::QTensor::from_model(model, name)
7957 };
7958 let f = |name: &str| -> Result<Vec<f32>, String> {
7959 crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
7960 };
7961 let opt_f = |name: &str| -> Option<Vec<f32>> { f(name).ok() };
7962 let at = s.attn();
7963 let ml = s.mlp();
7964 {
7965 let scale3 = |name: &str| -> Result<[f32; 3], String> {
7966 let v = f(name)?;
7967 if v.len() < 3 {
7968 return Err(format!("{name}: expected 3 scales, got {}", v.len()));
7969 }
7970 Ok([v[0], v[1], v[2]])
7971 };
7972 let compressor = match q(&format!("{p}.{at}.compressor.wkv.weight")) {
7975 Ok(wkv) => {
7976 let ape = f(&format!("{p}.{at}.compressor.ape"))?;
7977 let width = wkv.rows();
7980 let ratio = (ape.len() / width.max(1)).max(1);
7981 Some(Dsv4Compressor {
7982 wkv,
7983 wgate: q(&format!("{p}.{at}.compressor.wgate.weight"))?,
7984 norm: f(&format!("{p}.{at}.compressor.norm.weight"))?,
7985 ape,
7986 ratio,
7987 overlap: ratio == 4,
7988 })
7989 }
7990 Err(_) => None,
7991 };
7992 let indexer = match q(&format!("{p}.{at}.indexer.wq_b.weight")) {
7993 Ok(wq_b) => {
7994 let ape = f(&format!("{p}.{at}.indexer.compressor.ape"))?;
7995 let cwkv = q(&format!("{p}.{at}.indexer.compressor.wkv.weight"))?;
7996 let width = cwkv.rows();
7997 let ratio = (ape.len() / width.max(1)).max(1);
7998 Some(Dsv4Indexer {
7999 wq_b,
8000 weights_proj: q(&format!("{p}.{at}.indexer.weights_proj.weight"))?,
8001 compressor: Dsv4Compressor {
8002 wkv: cwkv,
8003 wgate: q(&format!("{p}.{at}.indexer.compressor.wgate.weight"))?,
8004 norm: f(&format!("{p}.{at}.indexer.compressor.norm.weight"))?,
8005 ape,
8006 ratio,
8007 overlap: ratio == 4,
8008 },
8009 })
8010 }
8011 Err(_) => None,
8012 };
8013
8014 let mut experts = Vec::with_capacity(cfg.n_routed_experts);
8015 for e in 0..cfg.n_routed_experts {
8016 let ep = format!("{p}.{ml}.experts.{e}");
8017 experts.push(Dsv4Expert {
8018 w1: q(&format!("{ep}.{w}", w = s.w(1)))?,
8019 w2: q(&format!("{ep}.{w}", w = s.w(2)))?,
8020 w3: q(&format!("{ep}.{w}", w = s.w(3)))?,
8021 });
8022 }
8023
8024 Ok(Dsv4Layer {
8025 attn_norm: f(&format!("{p}.{an}", an = s.attn_norm()))?,
8026 ffn_norm: f(&format!("{p}.{fnm}", fnm = s.ffn_norm()))?,
8027 wq_a: q(&format!("{p}.{at}.wq_a.weight"))?,
8028 q_norm: f(&format!("{p}.{at}.q_norm.weight"))?,
8029 wq_b: q(&format!("{p}.{at}.wq_b.weight"))?,
8030 wkv: q(&format!("{p}.{at}.wkv.weight"))?,
8031 kv_norm: f(&format!("{p}.{at}.kv_norm.weight"))?,
8032 wo_a: q(&format!("{p}.{at}.wo_a.weight"))?,
8033 wo_b: q(&format!("{p}.{at}.wo_b.weight"))?,
8034 attn_sink: f(&format!("{p}.{at}.attn_sink"))?,
8035 compressor,
8036 indexer,
8037 hc_attn_fn: f(&format!("{p}.hc_attn_fn"))?,
8038 hc_attn_base: f(&format!("{p}.hc_attn_base"))?,
8039 hc_attn_scale: scale3(&format!("{p}.hc_attn_scale"))?,
8040 hc_ffn_fn: f(&format!("{p}.hc_ffn_fn"))?,
8041 hc_ffn_base: f(&format!("{p}.hc_ffn_base"))?,
8042 hc_ffn_scale: scale3(&format!("{p}.hc_ffn_scale"))?,
8043 gate: q(&format!("{p}.{ml}.gate.weight"))?,
8044 gate_bias: opt_f(&format!("{p}.{ml}.{b}", b = s.gate_bias())),
8047 tid2eid: opt_f(&format!("{p}.{ml}.tid2eid")),
8048 experts,
8049 mask: if model.tensor(&format!("{p}.{ml}.tid2eid")).is_some() {
8050 None
8051 } else {
8052 crate::loader::moe_task_mask(model, &format!("{p}."), cfg.n_routed_experts)
8053 },
8054 shared: Dsv4Expert {
8055 w1: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(1)))?,
8056 w2: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(2)))?,
8057 w3: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(3)))?,
8058 },
8059 })
8060 }
8061}
8062
8063pub struct Dsv4Mtp {
8072 pub layer: Dsv4Layer,
8073 pub main_proj: Option<crate::qtensor::QTensor>,
8077 pub main_norm: Option<Vec<f32>>,
8078 pub norm: Option<Vec<f32>>,
8080 pub hc_head_fn: Option<Vec<f32>>,
8081 pub hc_head_base: Option<Vec<f32>>,
8082 pub hc_head_scale: Option<f32>,
8083 pub confidence: Option<crate::qtensor::QTensor>,
8084 pub markov_w1: Option<crate::qtensor::QTensor>,
8089 pub markov_w2: Option<crate::qtensor::QTensor>,
8090}
8091
8092pub fn load_mtp(
8096 model: &std::sync::Arc<cortiq_core::CmfModel>,
8097 cfg: &Dsv4Cfg,
8098 max_depth: usize,
8099) -> Vec<Dsv4Mtp> {
8100 let f = |name: &str| -> Option<Vec<f32>> {
8101 crate::loader::load_f32(model, name, &crate::loader::Overlay::None).ok()
8102 };
8103 let mut out = Vec::new();
8104 for d in 0..max_depth {
8105 let p = format!("model.mtp.{d}");
8106 if model.tensor(&format!("{p}.attn.wq_a.weight")).is_none() {
8110 break;
8111 }
8112 let layer = match load_layer(model, cfg, &p, Scheme::Mtp) {
8113 Ok(l) => l,
8114 Err(e) => {
8115 eprintln!("MTP {d}: пропущен, {e}");
8116 break;
8117 }
8118 };
8119 out.push(Dsv4Mtp {
8120 layer,
8121 main_proj: crate::qtensor::QTensor::from_model(model, &format!("{p}.main_proj.weight"))
8122 .ok(),
8123 main_norm: f(&format!("{p}.main_norm.weight")),
8124 norm: f(&format!("{p}.norm.weight")),
8125 hc_head_fn: f(&format!("{p}.hc_head_fn")),
8126 hc_head_base: f(&format!("{p}.hc_head_base")),
8127 hc_head_scale: f(&format!("{p}.hc_head_scale")).and_then(|v| v.first().copied()),
8128 confidence: crate::qtensor::QTensor::from_model(
8129 model,
8130 &format!("{p}.confidence_head.proj.weight"),
8131 )
8132 .ok(),
8133 markov_w1: crate::qtensor::QTensor::from_model(
8134 model,
8135 &format!("{p}.markov_head.markov_w1.weight"),
8136 )
8137 .ok(),
8138 markov_w2: crate::qtensor::QTensor::from_model(
8139 model,
8140 &format!("{p}.markov_head.markov_w2.weight"),
8141 )
8142 .ok(),
8143 });
8144 }
8145 dspark_apply_mask(&mut out);
8146 if !out.is_empty() {
8147 let mp = out
8148 .iter()
8149 .find_map(|m| m.main_proj.as_ref())
8150 .map(|t| format!("[{}, {}]", t.rows(), t.cols()))
8151 .unwrap_or_else(|| "нет".into());
8152 eprintln!(
8153 "MTP: {} стади(я/и/й), main_proj {mp}, экспертов {}, \
8154 голова уверенности {}, марков {}",
8155 out.len(),
8156 out[0].layer.experts.len(),
8157 if out.iter().any(|m| m.confidence.is_some()) {
8158 "есть"
8159 } else {
8160 "нет"
8161 },
8162 if out.iter().any(|m| m.markov_w1.is_some()) {
8163 "есть"
8164 } else {
8165 "нет"
8166 },
8167 );
8168 }
8169 out
8170}
8171
8172#[cfg(test)]
8173mod tests {
8174 use super::*;
8175
8176 fn toy() -> (Dsv4Globals, Vec<Dsv4Layer>, Dsv4Cfg) {
8181 use crate::qtensor::QTensor;
8182 let cfg = Dsv4Cfg {
8183 dim: 32,
8184 n_heads: 4,
8185 head_dim: 8,
8186 rope_head_dim: 4,
8187 q_lora_rank: 16,
8188 o_lora_rank: 16,
8189 o_groups: 2,
8190 hc_mult: 4,
8191 hc_sinkhorn_iters: 20,
8192 hc_eps: 1e-6,
8193 norm_eps: 1e-6,
8194 n_routed_experts: 8,
8195 top_k: 2,
8196 moe_inter: 16,
8197 route_scale: 1.0,
8198 swiglu_limit: 10.0,
8199 window: 6,
8200 index_topk: 8,
8201 vocab: 24,
8202 };
8203 let w = |n: usize, seed: usize| -> Vec<f32> {
8206 (0..n)
8207 .map(|i| (((i * 7 + seed * 13) % 101) as f32 / 101.0 - 0.5) * 0.3)
8208 .collect()
8209 };
8210 let t = |rows: usize, cols: usize, seed: usize| {
8211 QTensor::from_f32(w(rows * cols, seed), rows, cols)
8212 };
8213 let ones = |n: usize| vec![1.0f32; n];
8214
8215 let (dim, hc) = (cfg.dim, cfg.hc_mult);
8216 let q_width = cfg.n_heads * cfg.head_dim;
8219 let kv_width = cfg.head_dim;
8220 let o_per_group = q_width / cfg.o_groups;
8221 let mut layers = Vec::new();
8222 for li in 0..2 {
8223 let experts: Vec<Dsv4Expert> = (0..cfg.n_routed_experts)
8224 .map(|e| Dsv4Expert {
8225 w1: t(cfg.moe_inter, dim, 40 + e + li * 8),
8226 w2: t(dim, cfg.moe_inter, 60 + e + li * 8),
8227 w3: t(cfg.moe_inter, dim, 80 + e + li * 8),
8228 })
8229 .collect();
8230 layers.push(Dsv4Layer {
8233 attn_norm: ones(dim),
8234 ffn_norm: ones(dim),
8235 wq_a: t(cfg.q_lora_rank, dim, 1 + li),
8236 q_norm: ones(cfg.q_lora_rank),
8237 wq_b: t(q_width, cfg.q_lora_rank, 3 + li),
8238 wkv: t(kv_width, dim, 5 + li),
8239 kv_norm: ones(kv_width),
8240 wo_a: t(cfg.o_groups * cfg.o_lora_rank, o_per_group, 7 + li),
8241 wo_b: t(dim, cfg.o_groups * cfg.o_lora_rank, 9 + li),
8242 attn_sink: vec![0.1; cfg.n_heads],
8243 compressor: if li == 1 {
8246 Some(Dsv4Compressor {
8247 wkv: t(2 * kv_width, dim, 11),
8248 wgate: t(2 * kv_width, dim, 13),
8249 norm: ones(kv_width),
8250 ape: vec![0.01; 4 * 2 * kv_width],
8251 ratio: 4,
8252 overlap: true,
8253 })
8254 } else {
8255 None
8256 },
8257 indexer: if li == 1 {
8258 Some(Dsv4Indexer {
8259 wq_b: t(2 * 16, cfg.q_lora_rank, 41),
8260 weights_proj: t(2, dim, 43),
8261 compressor: Dsv4Compressor {
8262 wkv: t(2 * 16, dim, 45),
8263 wgate: t(2 * 16, dim, 47),
8264 norm: ones(16),
8265 ape: vec![0.01; 4 * 2 * 16],
8266 ratio: 4,
8267 overlap: true,
8268 },
8269 })
8270 } else {
8271 None
8272 },
8273 hc_attn_fn: w((2 + hc) * hc * hc * dim, 15 + li),
8274 hc_attn_base: w((2 + hc) * hc, 17 + li),
8275 hc_attn_scale: [1.0, 1.0, 1.0],
8276 hc_ffn_fn: w((2 + hc) * hc * hc * dim, 19 + li),
8277 hc_ffn_base: w((2 + hc) * hc, 21 + li),
8278 hc_ffn_scale: [1.0, 1.0, 1.0],
8279 gate: t(cfg.n_routed_experts, dim, 23 + li),
8280 gate_bias: if li == 1 {
8281 Some(vec![0.0; cfg.n_routed_experts])
8282 } else {
8283 None
8284 },
8285 tid2eid: if li == 0 {
8286 Some(
8287 (0..cfg.vocab * cfg.top_k)
8288 .map(|i| (i % cfg.n_routed_experts) as f32)
8289 .collect(),
8290 )
8291 } else {
8292 None
8293 },
8294 experts,
8295 mask: None,
8296 shared: Dsv4Expert {
8297 w1: t(cfg.moe_inter, dim, 25 + li),
8298 w2: t(dim, cfg.moe_inter, 27 + li),
8299 w3: t(cfg.moe_inter, dim, 29 + li),
8300 },
8301 });
8302 }
8303 let inv = |base: f32| -> Vec<f32> {
8304 (0..cfg.rope_head_dim / 2)
8305 .map(|i| 1.0 / base.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
8306 .collect()
8307 };
8308 let g = Dsv4Globals {
8309 inv_freq_compress: inv(160000.0),
8310 inv_freq_window: inv(10000.0),
8311 embed: t(cfg.vocab, dim, 31),
8312 norm: ones(dim),
8313 head: t(cfg.vocab, dim, 33),
8314 hc_head_fn: w(hc * hc * dim, 35),
8315 hc_head_base: w(hc, 37),
8316 hc_head_scale: 1.0,
8317 };
8318 (g, layers, cfg)
8319 }
8320
8321 #[test]
8326 fn forward_token_decodes_a_sequence_without_falling_over() {
8327 let (g, layers, cfg) = toy();
8328 let mut st = Dsv4State::new(layers.len());
8329 let inv_freq: Vec<f32> = (0..cfg.rope_head_dim / 2)
8330 .map(|i| 1.0 / 10000f32.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
8331 .collect();
8332 let mut logits = Vec::new();
8333
8334 let mut first: Option<Vec<f32>> = None;
8337 for (step, tok) in [3u32, 7, 1, 9, 4, 2, 8, 5, 6, 0].into_iter().enumerate() {
8338 forward_token(
8339 &g,
8340 &layers,
8341 &cfg,
8342 &mut st,
8343 tok,
8344 &inv_freq,
8345 None,
8346 &mut logits,
8347 );
8348 assert_eq!(logits.len(), cfg.vocab, "step {step}: logit count");
8349 assert!(
8350 logits.iter().all(|v| v.is_finite()),
8351 "step {step}: non-finite logit — {logits:?}"
8352 );
8353 let spread = logits.iter().cloned().fold(f32::MIN, f32::max)
8356 - logits.iter().cloned().fold(f32::MAX, f32::min);
8357 assert!(spread > 1e-6, "step {step}: logits are flat ({spread})");
8358 if step == 0 {
8359 first = Some(logits.clone());
8360 }
8361 assert_eq!(st.pos, step + 1, "position bookkeeping");
8362 }
8363
8364 assert!(!st.window[0].is_empty(), "sliding window never filled");
8367 for (li, w) in st.window.iter().enumerate() {
8369 assert!(
8370 w.len() / cfg.head_dim <= cfg.window,
8371 "layer {li}: window holds {} positions, cap is {}",
8372 w.len() / cfg.head_dim,
8373 cfg.window
8374 );
8375 }
8376 assert!(
8377 !st.compressed[1].is_empty(),
8378 "compressor layer produced no compressed KV in 10 tokens"
8379 );
8380 assert_eq!(
8384 st.compressed[1].len() / cfg.head_dim,
8385 2,
8386 "expected two folds in ten tokens at ratio 4"
8387 );
8388 assert!(
8389 !st.prev_kv[1].is_empty(),
8390 "the overlapping compressor never kept a previous window"
8391 );
8392 for (li, l) in layers.iter().enumerate() {
8397 if l.indexer.is_some() {
8398 assert!(
8399 !st.index_kv[li].is_empty(),
8400 "layer {li} has an indexer but its cache stayed empty"
8401 );
8402 }
8403 }
8404
8405 let mut fresh = Dsv4State::new(layers.len());
8408 let mut relogits = Vec::new();
8409 forward_token(
8410 &g,
8411 &layers,
8412 &cfg,
8413 &mut fresh,
8414 3,
8415 &inv_freq,
8416 None,
8417 &mut relogits,
8418 );
8419 assert_eq!(
8420 relogits,
8421 first.unwrap(),
8422 "the same token from a fresh state must reproduce exactly"
8423 );
8424 }
8425
8426 #[test]
8430 fn swiglu_limit_clamps_up_both_ways_and_gate_only_from_above() {
8431 let inter = 4;
8432 let gate_src = [-50.0f32, 50.0, 1.0, -1.0];
8434 let up_src = [50.0f32, -50.0, 1.0, -1.0];
8435 let limit = 10.0f32;
8436 let mut got = vec![0.0f32; inter];
8437 expert_swiglu(
8438 &[0.0],
8439 &|_, d| d.copy_from_slice(&gate_src),
8440 &|_, d| d.copy_from_slice(&up_src),
8441 &|src, d| d.copy_from_slice(src),
8442 inter,
8443 1.0,
8444 limit,
8445 &mut got,
8446 );
8447 let silu = |g: f32| g / (1.0 + (-g).exp());
8448 let want = [
8450 silu(-50.0) * limit,
8451 silu(limit) * -limit,
8452 silu(1.0) * 1.0,
8453 -silu(-1.0),
8454 ];
8455 for (i, w) in want.iter().enumerate() {
8456 assert!(
8457 (got[i] - w).abs() < 1e-5,
8458 "lane {i}: got {} want {w}",
8459 got[i]
8460 );
8461 }
8462 let mut raw = vec![0.0f32; inter];
8464 expert_swiglu(
8465 &[0.0],
8466 &|_, d| d.copy_from_slice(&gate_src),
8467 &|_, d| d.copy_from_slice(&up_src),
8468 &|src, d| d.copy_from_slice(src),
8469 inter,
8470 1.0,
8471 0.0,
8472 &mut raw,
8473 );
8474 assert!(
8475 (raw[1] - silu(50.0) * -50.0).abs() < 1e-3,
8476 "limit 0 must not clamp"
8477 );
8478 }
8479
8480 #[test]
8485 fn grouped_projection_is_identical_with_and_without_a_pool() {
8486 let (groups, lora, per_group, dim) = (4usize, 128usize, 64usize, 32usize);
8487 let attn: Vec<f32> = (0..groups * per_group)
8488 .map(|i| ((i * 13) as f32 * 0.021).sin())
8489 .collect();
8490 let wo_a: Vec<f32> = (0..groups * lora * per_group)
8491 .map(|i| ((i * 7) as f32 * 0.011).cos())
8492 .collect();
8493 let wo_b: Vec<f32> = (0..dim * groups * lora)
8494 .map(|i| ((i * 5) as f32 * 0.009).sin())
8495 .collect();
8496 let row = |r: usize, x: &[f32], _sc: &mut [f32]| -> f32 {
8497 wo_a[r * per_group..(r + 1) * per_group]
8498 .iter()
8499 .zip(x)
8500 .map(|(a, b)| a * b)
8501 .sum()
8502 };
8503 let project = |mid: &[f32], dst: &mut [f32]| {
8504 for (d, o) in dst.iter_mut().enumerate() {
8505 *o = wo_b[d * mid.len()..(d + 1) * mid.len()]
8506 .iter()
8507 .zip(mid)
8508 .map(|(a, b)| a * b)
8509 .sum();
8510 }
8511 };
8512
8513 let mut serial = vec![0.0f32; dim];
8514 o_project(
8515 &attn,
8516 &row,
8517 per_group,
8518 &project,
8519 groups,
8520 lora,
8521 None,
8522 &mut serial,
8523 );
8524
8525 let pool = crate::pool::Pool::new(4);
8526 let mut pooled = vec![0.0f32; dim];
8527 o_project(
8528 &attn,
8529 &row,
8530 per_group,
8531 &project,
8532 groups,
8533 lora,
8534 Some(&pool),
8535 &mut pooled,
8536 );
8537 assert_eq!(serial, pooled, "the pooled projection diverged");
8538 assert!(
8539 serial.iter().any(|v| v.abs() > 1e-6),
8540 "test data is degenerate"
8541 );
8542 }
8543
8544 #[test]
8545 fn block_grouped_projection_matches_position_walk() {
8546 let (_g, layers, cfg) = toy();
8547 let l = &layers[1];
8548 let b = 5;
8549 let attn_len = cfg.n_heads * cfg.head_dim;
8550 let attn: Vec<f32> = (0..b * attn_len)
8551 .map(|i| ((i * 17) as f32 * 0.013).sin())
8552 .collect();
8553 let mut walked = vec![0.0f32; b * cfg.dim];
8554 for bi in 0..b {
8555 o_project(
8556 &attn[bi * attn_len..(bi + 1) * attn_len],
8557 &|r, x, sc| l.wo_a.row_dot(r, x, sc),
8558 l.wo_a.cols(),
8559 &|mid, dst| l.wo_b.matvec(mid, dst, None),
8560 cfg.o_groups,
8561 cfg.o_lora_rank,
8562 None,
8563 &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
8564 );
8565 }
8566 let mut batched = vec![0.0f32; b * cfg.dim];
8567 o_project_block(
8568 &attn,
8569 b,
8570 &l.wo_a,
8571 &l.wo_b,
8572 cfg.o_groups,
8573 cfg.o_lora_rank,
8574 None,
8575 &mut batched,
8576 );
8577 assert_eq!(batched, walked);
8578 }
8579
8580 #[test]
8581 fn block_moe_matches_position_walk_in_route_order() {
8582 let (_g, layers, cfg) = toy();
8583 let l = &layers[1];
8586 let b = 5;
8587 let xs: Vec<f32> = (0..b * cfg.dim)
8588 .map(|i| ((i * 11) as f32 * 0.019).cos())
8589 .collect();
8590 let ids = [1u32, 2, 3, 4, 5];
8591 let mut walked = vec![0.0f32; b * cfg.dim];
8592 for bi in 0..b {
8593 moe_step(
8594 &xs[bi * cfg.dim..(bi + 1) * cfg.dim],
8595 l,
8596 &cfg,
8597 ids[bi],
8598 1,
8599 None,
8600 &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
8601 );
8602 }
8603 let mut batched = vec![0.0f32; b * cfg.dim];
8604 moe_step_block(&xs, b, l, &cfg, &ids, 1, None, &mut batched);
8605 assert_eq!(batched, walked);
8606 }
8607
8608 #[test]
8614 fn overlapping_compressor_folds_both_windows() {
8615 let (ratio, d) = (2usize, 3usize);
8616 let cur_kv: Vec<f32> = vec![
8619 1.0, 1.0, 1.0, 10.0, 20.0, 30.0, 2.0, 2.0, 2.0, 40.0, 50.0, 60.0, ];
8622 let cur_sc: Vec<f32> = vec![
8624 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 0.0, 100.0, 100.0, 0.0,
8626 ];
8627 let prev_kv: Vec<f32> = vec![
8629 7.0, 8.0, 9.0, 0.0, 0.0, 0.0, 5.0, 6.0, 7.0, 0.0, 0.0, 0.0,
8631 ];
8632 let prev_sc = vec![0.0f32; ratio * 2 * d];
8633
8634 let mut out = vec![0.0f32; d];
8635 compress_window_overlap(&prev_kv, &prev_sc, &cur_kv, &cur_sc, ratio, d, &mut out);
8636 assert!((out[0] - 40.0).abs() < 1e-3, "dim0 = {}", out[0]);
8638 assert!((out[1] - 50.0).abs() < 1e-3, "dim1 = {}", out[1]);
8639 assert!((out[2] - 30.0).abs() < 1e-3, "dim2 = {}", out[2]);
8641
8642 let mut first = vec![0.0f32; d];
8645 compress_window_overlap(&[], &[], &cur_kv, &cur_sc, ratio, d, &mut first);
8646 assert!(
8647 first.iter().all(|v| v.is_finite()),
8648 "first window: {first:?}"
8649 );
8650 assert!((first[0] - 40.0).abs() < 1e-3, "first dim0 = {}", first[0]);
8651
8652 let mut both = vec![0.0f32; d];
8654 let strong_prev = vec![100.0f32; ratio * 2 * d];
8655 compress_window_overlap(
8656 &prev_kv,
8657 &strong_prev,
8658 &cur_kv,
8659 &cur_sc,
8660 ratio,
8661 d,
8662 &mut both,
8663 );
8664 assert!(
8665 (both[0] - 40.0).abs() > 1.0,
8666 "a scored previous window must move the fold, got {}",
8667 both[0]
8668 );
8669 }
8670
8671 #[test]
8677 fn sinkhorn_matches_the_reference_numbers() {
8678 let hc = 4;
8679 let mixes: Vec<f32> = (0..24).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
8680 let base: Vec<f32> = (0..24).map(|i| (i as f32 * 0.11).cos()).collect();
8681 let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
8682 hc_split_sinkhorn(
8683 &mixes,
8684 &[1.0, 1.0, 1.0],
8685 &base,
8686 hc,
8687 20,
8688 1e-6,
8689 &mut pre,
8690 &mut post,
8691 &mut comb,
8692 );
8693 let want_pre = [0.7310596, 0.8888268, 0.9525191, 0.97424865];
8694 let want_post = [1.9600224, 1.9534285, 1.9201256, 1.8160983];
8695 let want_comb = [
8696 0.5996052,
8697 0.282_535_9,
8698 0.09218107,
8699 0.025676856,
8700 0.17564717,
8701 0.22228767,
8702 0.271_745_4,
8703 0.330_318_8,
8704 0.029528176,
8705 0.12206022,
8706 0.32619134,
8707 0.5222193,
8708 0.19521846,
8709 0.37311527,
8710 0.30988118,
8711 0.12178412,
8712 ];
8713 for (i, w) in want_pre.iter().enumerate() {
8714 assert!((pre[i] - w).abs() < 1e-5, "pre[{i}]: {} vs {w}", pre[i]);
8715 }
8716 for (i, w) in want_post.iter().enumerate() {
8717 assert!((post[i] - w).abs() < 1e-5, "post[{i}]: {} vs {w}", post[i]);
8718 }
8719 for (i, w) in want_comb.iter().enumerate() {
8720 assert!((comb[i] - w).abs() < 1e-4, "comb[{i}]: {} vs {w}", comb[i]);
8721 }
8722 }
8723
8724 #[test]
8729 fn sinkhorn_leaves_the_mixing_matrix_doubly_stochastic() {
8730 let hc = 4;
8731 let mix_hc = (2 + hc) * hc;
8732 let mixes: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
8734 let base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.11).cos()).collect();
8735 let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
8736 hc_split_sinkhorn(
8737 &mixes,
8738 &[1.0, 1.0, 1.0],
8739 &base,
8740 hc,
8741 20,
8742 1e-6,
8743 &mut pre,
8744 &mut post,
8745 &mut comb,
8746 );
8747 for j in 0..hc {
8748 let r: f32 = comb[j * hc..(j + 1) * hc].iter().sum();
8749 assert!((r - 1.0).abs() < 2e-3, "row {j} sums to {r}");
8750 let c: f32 = (0..hc).map(|k| comb[k * hc + j]).sum();
8751 assert!((c - 1.0).abs() < 2e-3, "col {j} sums to {c}");
8752 }
8753 assert!(pre.iter().all(|&v| v > 0.0 && v < 1.001));
8755 assert!(post.iter().all(|&v| (0.0..=2.0).contains(&v)));
8756 }
8757
8758 #[test]
8762 fn expand_of_identical_copies_is_a_fixed_point() {
8763 let (hc, dim) = (4usize, 3usize);
8764 let residual: Vec<f32> = std::iter::repeat_n([1.5f32, -2.0, 0.25], hc)
8765 .flatten()
8766 .collect();
8767 let comb = {
8768 vec![0.25f32; hc * hc]
8770 };
8771 let post = vec![0.0f32; hc];
8772 let mut out = vec![0.0f32; hc * dim];
8773 hc_expand(&[0.0; 3], &residual, &post, &comb, hc, dim, &mut out);
8774 for (o, r) in out.iter().zip(&residual) {
8775 assert!((o - r).abs() < 1e-6, "{o} vs {r}");
8776 }
8777 }
8778
8779 #[test]
8783 fn selection_bias_steers_the_choice_but_not_the_weights() {
8784 let scores = [3.0f32, 0.1, 2.0, 0.05];
8785 let bias = [0.0f32, 10.0, 0.0, 0.0];
8786 let (mut idx, mut w) = (Vec::new(), Vec::new());
8787 route(&scores, Some(&bias), 2, 1.5, None, None, &mut idx, &mut w);
8788 assert_eq!(idx[0], 1, "the biased expert must win selection");
8789 assert_eq!(idx[1], 0);
8790 assert!(w[0] < w[1], "biased expert kept its own (small) weight");
8793 let sum: f32 = w.iter().sum();
8794 assert!((sum - 1.5).abs() < 1e-5, "weights renormalize then scale");
8795 }
8796
8797 #[test]
8801 fn attention_sink_drains_weight_without_contributing_output() {
8802 let hd = 2;
8803 let q = [1.0f32, 0.0];
8804 let kv = [1.0f32, 0.0, 0.0, 1.0];
8805 let mut out = vec![0.0f32; hd];
8806 sparse_attend(&q, &kv, &[0, 1], f32::NEG_INFINITY, 1.0, hd, &mut out);
8807 let plain = out.clone();
8808 assert!(plain[0] > plain[1], "the aligned key must dominate");
8809 sparse_attend(&q, &kv, &[0, 1], 20.0, 1.0, hd, &mut out);
8810 assert!(
8811 out[0] < plain[0] * 0.01 && out[1] < plain[1] * 0.01,
8812 "a large sink must drain nearly all the mass: {out:?}"
8813 );
8814 }
8815
8816 #[test]
8819 fn masked_positions_leave_the_denominator_alone() {
8820 let hd = 2;
8821 let q = [1.0f32, 0.0];
8822 let kv = [1.0f32, 0.0, 0.0, 1.0];
8823 let (mut a, mut b) = (vec![0.0f32; hd], vec![0.0f32; hd]);
8824 sparse_attend(&q, &kv, &[0], f32::NEG_INFINITY, 1.0, hd, &mut a);
8825 sparse_attend(
8826 &q,
8827 &kv,
8828 &[0, usize::MAX],
8829 f32::NEG_INFINITY,
8830 1.0,
8831 hd,
8832 &mut b,
8833 );
8834 for (x, y) in a.iter().zip(&b) {
8835 assert!((x - y).abs() < 1e-6, "{x} vs {y}");
8836 }
8837 }
8838
8839 #[test]
8842 fn rope_tail_inverts_itself() {
8843 let inv_freq = [1.0f32, 0.5];
8844 let orig = [9.0f32, 8.0, 1.0, 2.0, 3.0, 4.0];
8845 let mut v = orig;
8846 rope_tail(&mut v, &inv_freq, 7, 4, false);
8847 assert!(v[..2] == orig[..2], "the non-rope head must not move");
8848 assert!(v[2..] != orig[2..], "the tail must actually rotate");
8849 rope_tail(&mut v, &inv_freq, 7, 4, true);
8850 for (a, b) in v.iter().zip(&orig) {
8851 assert!((a - b).abs() < 1e-5, "{a} vs {b}");
8852 }
8853 }
8854
8855 #[test]
8858 fn compressor_pools_the_window_per_dimension() {
8859 let (ratio, width) = (2usize, 2usize);
8860 let kv = [1.0f32, 10.0, 3.0, 20.0];
8861 let score = [0.0f32, 0.0, 0.0, 50.0];
8863 let ape = vec![0.0f32; ratio * width];
8864 let mut out = vec![0.0f32; width];
8865 compress_window(&kv, &score, &ape, ratio, width, &mut out);
8866 assert!(
8867 (out[0] - 2.0).abs() < 1e-5,
8868 "equal scores average: {}",
8869 out[0]
8870 );
8871 assert!(
8872 (out[1] - 20.0).abs() < 1e-3,
8873 "a dominant score wins: {}",
8874 out[1]
8875 );
8876 }
8877
8878 #[test]
8881 fn index_scores_relu_before_weighting() {
8882 let (nh, hd) = (2usize, 2usize);
8883 let q = [1.0f32, 0.0, -1.0, 0.0];
8885 let kv = [1.0f32, 0.0, 0.0, 1.0];
8886 let w = [1.0f32, 1.0];
8887 let mut sc = Vec::new();
8888 index_scores(&q, &kv, &w, nh, hd, 2, 2, None, &mut sc);
8889 assert!(sc[0] > 0.9, "abstention, not veto: {:?}", sc);
8891 }
8892
8893 #[test]
8894 fn index_scores_mask_the_future() {
8895 let (nh, hd) = (1usize, 2usize);
8896 let q = [1.0f32, 0.0];
8897 let kv = [1.0f32, 0.0, 1.0, 0.0, 1.0, 0.0];
8898 let w = [1.0f32];
8899 let mut sc = Vec::new();
8900 index_scores(&q, &kv, &w, nh, hd, 3, 2, None, &mut sc);
8901 assert!(sc[0].is_finite() && sc[1].is_finite());
8902 assert!(sc[2] == f32::NEG_INFINITY, "position 2 is in the future");
8903 let mut idx = Vec::new();
8904 top_k_positions(&sc, 3, &mut idx);
8905 assert_eq!(idx, vec![0, 1], "a masked slot never wins a slot");
8906 }
8907
8908 #[test]
8909 fn top_k_is_deterministic_on_ties() {
8910 let sc = [1.0f32, 1.0, 1.0, 0.0];
8911 let mut idx = Vec::new();
8912 top_k_positions(&sc, 2, &mut idx);
8913 assert_eq!(idx, vec![0, 1], "ties resolve to the lower index");
8914 }
8915
8916 #[test]
8920 fn hc_block_preserves_the_copy_structure_and_applies_the_block() {
8921 let cfg = Dsv4Cfg {
8922 dim: 4,
8923 n_heads: 1,
8924 head_dim: 4,
8925 rope_head_dim: 2,
8926 q_lora_rank: 4,
8927 o_lora_rank: 2,
8928 o_groups: 1,
8929 hc_mult: 4,
8930 hc_sinkhorn_iters: 20,
8931 hc_eps: 1e-6,
8932 norm_eps: 1e-6,
8933 n_routed_experts: 2,
8934 top_k: 1,
8935 moe_inter: 4,
8936 route_scale: 1.0,
8937 swiglu_limit: 10.0,
8938 window: 128,
8939 index_topk: 4,
8940 vocab: 8,
8941 };
8942 let (hc, dim) = (cfg.hc_mult, cfg.dim);
8943 let mix_hc = (2 + hc) * hc;
8944 let hc_fn: Vec<f32> = (0..mix_hc * hc * dim)
8945 .map(|i| ((i % 13) as f32 - 6.0) * 0.05)
8946 .collect();
8947 let hc_base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.2).sin()).collect();
8948 let norm_w = vec![1.0f32; dim];
8949 let mut state: Vec<f32> = (0..hc * dim).map(|i| (i as f32 * 0.3).cos()).collect();
8950 let before = state.clone();
8951 let mut scratch = HcScratch::new(&cfg);
8952 hc_block(
8953 &mut state,
8954 &hc_fn,
8955 &[1.0, 1.0, 1.0],
8956 &hc_base,
8957 &norm_w,
8958 &cfg,
8959 &mut scratch,
8960 None,
8961 |_folded, out: &mut [f32]| out.iter_mut().for_each(|o| *o = 1.0),
8962 );
8963 assert_eq!(state.len(), before.len(), "copy structure must survive");
8964 assert!(state.iter().all(|v| v.is_finite()), "{state:?}");
8965 assert!(
8966 state.iter().zip(&before).any(|(a, b)| (a - b).abs() > 1e-4),
8967 "the block's output has to reach the state"
8968 );
8969 }
8970
8971 #[test]
8972 fn hash_route_reads_the_table_row() {
8973 let table = [7.0f32, 9.0, 1.0, 2.0, 5.0, 6.0];
8975 assert_eq!(hash_route(&table, 3, 2, 0), vec![7, 9]);
8976 assert_eq!(hash_route(&table, 3, 2, 2), vec![5, 6]);
8977 assert_eq!(hash_route(&table, 3, 2, 99), vec![5, 6]);
8979 }
8980
8981 #[test]
8986 fn a_task_mask_restricts_selection_and_renormalizes() {
8987 let scores = [0.1f32, 4.0, 1.0, 9.0];
8989 let (mut idx, mut w) = (Vec::new(), Vec::new());
8990 route(&scores, None, 2, 1.0, None, None, &mut idx, &mut w);
8991 assert_eq!(idx, vec![3, 1], "unmasked: the two best win");
8992 let sum: f32 = w.iter().sum();
8993 assert!((sum - 1.0).abs() < 1e-5, "weights must sum to route_scale");
8994
8995 let mask = [true, false, true, true];
8998 let (mut i2, mut w2) = (Vec::new(), Vec::new());
8999 route(&scores, None, 2, 1.0, None, Some(&mask), &mut i2, &mut w2);
9000 assert_eq!(i2, vec![3, 2], "masked expert must not be selected");
9001 let sum2: f32 = w2.iter().sum();
9002 assert!((sum2 - 1.0).abs() < 1e-5, "masked weights must renormalize");
9003
9004 let tight = [false, false, false, true];
9006 let (mut i3, mut w3) = (Vec::new(), Vec::new());
9007 route(&scores, None, 2, 1.0, None, Some(&tight), &mut i3, &mut w3);
9008 assert_eq!(i3, vec![3]);
9009 assert_eq!(w3.len(), 1);
9010 }
9011
9012 #[test]
9017 fn hash_layers_weight_the_experts_the_table_names() {
9018 let scores = [0.1f32, 0.4, 0.2, 5.0];
9020 let table = vec![0.0f32, 1.0];
9021 let idx_forced = hash_route(&table, 1, 2, 0);
9022 assert_eq!(idx_forced, vec![0, 1]);
9023
9024 let (mut idx, mut w) = (Vec::new(), Vec::new());
9025 route(
9026 &scores,
9027 None,
9028 2,
9029 1.0,
9030 Some(&idx_forced),
9031 None,
9032 &mut idx,
9033 &mut w,
9034 );
9035 assert_eq!(idx, vec![0, 1], "the table must decide the experts");
9036
9037 let sp = |x: f32| (1.0 + x.exp()).ln().sqrt();
9039 let (s0, s1) = (sp(scores[0]), sp(scores[1]));
9040 let tot = s0 + s1;
9041 assert!(
9042 (w[0] - s0 / tot).abs() < 1e-6,
9043 "w[0]={} want {}",
9044 w[0],
9045 s0 / tot
9046 );
9047 assert!(
9048 (w[1] - s1 / tot).abs() < 1e-6,
9049 "w[1]={} want {}",
9050 w[1],
9051 s1 / tot
9052 );
9053
9054 let (mut idx2, mut w2) = (Vec::new(), Vec::new());
9056 route(&scores, None, 2, 1.0, None, None, &mut idx2, &mut w2);
9057 assert_eq!(idx2[0], 3, "without a table the highest score still wins");
9058 }
9059}
9060
9061pub const DSPARK_NOISE_TOKEN: u32 = 128799;
9078pub fn dspark_block() -> usize {
9086 5
9087}
9088
9089#[cfg(feature = "gpu")]
9096fn dspark_native_on() -> bool {
9097 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9098 *ON.get_or_init(|| {
9099 std::env::var("CMF_DSPARK_NATIVE")
9100 .map(|v| v != "0")
9101 .unwrap_or(true)
9104 })
9105}
9106
9107pub fn dspark_verify_k() -> usize {
9108 static K: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
9109 *K.get_or_init(|| {
9110 std::env::var("CMF_DSPARK_VERIFY_K")
9111 .ok()
9112 .and_then(|v| v.parse::<usize>().ok())
9113 .filter(|&n| (1..=DSPARK_BLOCK_MAX).contains(&n))
9114 .unwrap_or(DSPARK_BLOCK_MAX)
9115 })
9116}
9117
9118pub const DSPARK_BLOCK_MAX: usize = 5;
9120
9121pub struct DsparkState {
9124 pub win: Vec<Vec<f32>>,
9126 pub filled: Vec<usize>,
9128 pub main_hidden: Vec<f32>,
9130 pub have_hidden: bool,
9132}
9133
9134impl DsparkState {
9135 pub fn new(stages: usize, cfg: &Dsv4Cfg, targets: usize) -> Self {
9136 Self {
9137 win: vec![Vec::new(); stages],
9138 filled: vec![0; stages],
9139 main_hidden: vec![0.0; cfg.dim * targets],
9140 have_hidden: false,
9141 }
9142 }
9143}
9144
9145pub fn dspark_targets(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, n_layers: usize) -> Vec<usize> {
9151 let Some(mp) = mtp.iter().find_map(|m| m.main_proj.as_ref()) else {
9152 return Vec::new();
9153 };
9154 let n = (mp.cols() / cfg.dim.max(1)).clamp(1, n_layers);
9155 (n_layers - n..n_layers).collect()
9156}
9157
9158thread_local! {
9159 static DSPARK_CAP: std::cell::RefCell<(Vec<usize>, Vec<f32>, usize)> =
9165 const { std::cell::RefCell::new((Vec::new(), Vec::new(), 0)) };
9166}
9167
9168pub fn dspark_arm(targets: &[usize], dim: usize) {
9170 DSPARK_CAP.with(|c| {
9171 let mut c = c.borrow_mut();
9172 c.0 = targets.to_vec();
9173 c.1 = vec![0.0; dim * targets.len()];
9174 c.2 = 0;
9175 });
9176}
9177
9178fn dspark_wants(li: usize) -> bool {
9182 DSPARK_CAP.with(|c| c.borrow().0.contains(&li))
9183}
9184
9185pub fn dspark_note(li: usize, state: &[f32], cfg: &Dsv4Cfg) {
9187 DSPARK_CAP.with(|c| {
9188 let mut c = c.borrow_mut();
9189 if c.0.is_empty() {
9190 return;
9191 }
9192 if let Some(slot) = c.0.iter().position(|&t| t == li) {
9193 let (_, buf, seen) = &mut *c;
9194 dspark_capture(state, cfg, slot, buf);
9195 *seen = if slot == 0 { 1 } else { *seen + 1 };
9200 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
9201 eprintln!("[cap] note li={li} slot={slot} seen={}", *seen);
9202 }
9203 }
9204 });
9205}
9206
9207pub fn dspark_peek_slot(slot: usize, dim: usize, out: &mut [f32]) -> bool {
9212 DSPARK_CAP.with(|c| {
9213 let c = c.borrow();
9214 let lo = slot * dim;
9215 if c.1.len() < lo + dim {
9216 return false;
9217 }
9218 out[..dim].copy_from_slice(&c.1[lo..lo + dim]);
9219 true
9220 })
9221}
9222
9223pub fn dspark_take(out: &mut Vec<f32>) -> bool {
9225 DSPARK_CAP.with(|c| {
9226 let mut c = c.borrow_mut();
9227 if c.0.is_empty() || c.2 != c.0.len() {
9228 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
9229 eprintln!("[cap] take FAIL armed={:?} seen={}", c.0, c.2);
9230 }
9231 return false;
9232 }
9233 out.clear();
9234 out.extend_from_slice(&c.1);
9235 c.2 = 0;
9236 true
9237 })
9238}
9239
9240pub fn dspark_capture(state: &[f32], cfg: &Dsv4Cfg, slot: usize, out: &mut [f32]) {
9244 let (hc, dim) = (cfg.hc_mult, cfg.dim);
9245 let dst = &mut out[slot * dim..(slot + 1) * dim];
9246 let inv = 1.0 / hc as f32;
9247 for d in 0..dim {
9248 let mut s = 0.0;
9249 for j in 0..hc {
9250 s += state[j * dim + d];
9251 }
9252 dst[d] = s * inv;
9253 }
9254}
9255
9256pub fn dspark_freq_note(picks: &[(usize, Vec<usize>)]) {
9261 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
9262 std::sync::Mutex::new(None);
9263 let Ok(path) = std::env::var("CMF_DSPARK_PICK_DUMP") else {
9264 return;
9265 };
9266 let mut g = FREQ.lock().unwrap();
9267 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
9268 for (stage, idx) in picks {
9269 for &e in idx {
9270 *map.entry((*stage, e)).or_insert(0) += 1;
9271 }
9272 }
9273 *blocks += 1;
9274 if *blocks % 32 == 0 {
9275 let mut lines: Vec<_> = map.iter().collect();
9276 lines.sort();
9277 let body: String = lines
9278 .iter()
9279 .map(|((s, e), n)| format!("{s}\t{e}\t{n}\n"))
9280 .collect();
9281 let _ = std::fs::write(&path, body);
9282 }
9283}
9284
9285pub fn trunk_freq_note(picks: &[(usize, Vec<usize>)]) {
9290 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
9291 std::sync::Mutex::new(None);
9292 let Ok(path) = std::env::var("CMF_DSV4_TRUNK_PICK_DUMP") else {
9293 return;
9294 };
9295 let mut g = FREQ.lock().unwrap();
9296 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
9297 for (li, idx) in picks {
9298 for &e in idx {
9299 *map.entry((*li, e)).or_insert(0) += 1;
9300 }
9301 }
9302 *blocks += 1;
9303 if *blocks % 32 == 0 {
9304 let mut lines: Vec<_> = map.iter().collect();
9305 lines.sort();
9306 let body: String = lines
9307 .iter()
9308 .map(|((l, e), n)| format!("{l}\t{e}\t{n}\n"))
9309 .collect();
9310 let _ = std::fs::write(&path, body);
9311 }
9312}
9313
9314fn dspark_apply_mask(out: &mut [Dsv4Mtp]) {
9322 let Ok(path) = std::env::var("CMF_DSPARK_MASK") else {
9323 return;
9324 };
9325 let Ok(text) = std::fs::read_to_string(&path) else {
9326 eprintln!("DSpark: CMF_DSPARK_MASK={path} не читается — маска не применена");
9327 return;
9328 };
9329 for (d, line) in text.lines().enumerate() {
9330 let Some(m) = out.get_mut(d) else { break };
9331 let n = m.layer.experts.len();
9332 let mut mask = vec![false; n];
9333 let mut kept = 0usize;
9334 for tok in line.split(',') {
9335 if let Ok(e) = tok.trim().parse::<usize>() {
9336 if e < n && !mask[e] {
9337 mask[e] = true;
9338 kept += 1;
9339 }
9340 }
9341 }
9342 if kept == 0 {
9343 continue;
9344 }
9345 eprintln!("DSpark: стадия {d} ограничена {kept}/{n} экспертами");
9346 m.layer.mask = Some(mask);
9347 }
9348}
9349
9350#[cfg(feature = "gpu")]
9360pub struct DsparkPack {
9361 pub stages: Vec<DsparkStagePack>,
9362 pub gu_q2: bool,
9365 pub dn_q2: bool,
9368 pub routers: Vec<Vec<f32>>,
9371 pub biases: Vec<Option<Vec<f32>>>,
9372}
9373
9374#[cfg(feature = "gpu")]
9375pub struct DsparkStagePack {
9376 pub mask: Vec<bool>,
9378 pub to_slot: Vec<usize>,
9380 pub mask_u32: Vec<u32>,
9383 pub map_u32: Vec<u32>,
9384 pub tensors: Vec<(usize, usize, usize)>,
9386 pub n_resident: usize,
9387 global: Option<GlobalPack>,
9391}
9392
9393pub static DSPARK_Q2TP_ENCODE: std::sync::OnceLock<fn(&[f32], usize, usize) -> Vec<u8>> =
9400 std::sync::OnceLock::new();
9401
9402#[cfg(feature = "gpu")]
9405pub fn dspark_gpu_on() -> bool {
9406 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9407 *ON.get_or_init(|| {
9408 std::env::var("CMF_DSPARK_GPU")
9409 .map(|v| v != "0")
9410 .unwrap_or(true)
9411 })
9412}
9413
9414#[cfg(feature = "gpu")]
9416pub fn dspark_pack_get(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<&'static DsparkPack> {
9417 static P: std::sync::OnceLock<Option<Box<DsparkPack>>> = std::sync::OnceLock::new();
9418 P.get_or_init(|| dspark_pack_build(mtp, cfg).map(Box::new))
9419 .as_deref()
9420}
9421
9422#[cfg(feature = "gpu")]
9438pub fn dspark_reserve_note(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, layers: &[Dsv4Layer]) {
9439 if mtp.is_empty() || std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "0") || !dspark_gpu_on()
9440 {
9441 return;
9442 }
9443 let forced = std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "1");
9448 let dt = |q2: bool| {
9449 if q2 {
9450 cortiq_core::TensorDtype::Q2TiledP
9451 } else {
9452 cortiq_core::TensorDtype::Q4TiledP
9453 }
9454 };
9455 let gu_q2 = mtp[0]
9456 .layer
9457 .experts
9458 .first()
9459 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9460 let dn_q2 = mtp[0]
9461 .layer
9462 .experts
9463 .first()
9464 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9465 if !gu_q2
9473 && !dn_q2
9474 && std::env::var("CMF_MOE_MASK").is_err()
9475 && crate::gpu_wgpu::dsv4_global_moe_supported()
9476 {
9477 crate::gpu_wgpu::DRAFT_PACK_RESERVE.store(0, std::sync::atomic::Ordering::Relaxed);
9478 crate::gpu_wgpu::DRAFT_RESERVE.store(0, std::sync::atomic::Ordering::Relaxed);
9479 return;
9480 }
9481 let gu = cortiq_core::quant::expected_nbytes(dt(gu_q2), &[cfg.moe_inter, cfg.dim]).unwrap_or(0);
9482 let dn = cortiq_core::quant::expected_nbytes(dt(dn_q2), &[cfg.dim, cfg.moe_inter]).unwrap_or(0);
9483 let per = (2 * gu + dn) as u64;
9484 let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
9485 .ok()
9486 .and_then(|v| v.parse().ok())
9487 .unwrap_or(40);
9490 let bytes = per * (n_res * mtp.len() + mtp.len() + 1) as u64;
9492 let trunk: u64 = layers
9502 .iter()
9503 .map(|l| {
9504 let Some(e) = l.experts.first() else {
9505 return 0;
9506 };
9507 let gu = cortiq_core::quant::expected_nbytes(
9508 dt(e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
9509 &[cfg.moe_inter, cfg.dim],
9510 )
9511 .unwrap_or(0);
9512 let dn = cortiq_core::quant::expected_nbytes(
9513 dt(e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
9514 &[cfg.dim, cfg.moe_inter],
9515 )
9516 .unwrap_or(0);
9517 let routed = l
9518 .mask
9519 .as_deref()
9520 .map_or(l.experts.len(), |m| m.iter().filter(|&&open| open).count());
9521 ((2 * gu + dn) * (routed + 1)) as u64
9522 })
9523 .sum();
9524 if let Some(budget) = crate::gpu_wgpu::dsv4_vram_budget() {
9525 if !forced && budget < trunk / 10 * 9 + bytes {
9526 return;
9527 }
9528 }
9529 let mib = 1024 * 1024u64;
9538 let workspace = match crate::gpu_wgpu::dsv4_vram_budget() {
9539 Some(b) if b <= 64 * 1024 * mib => 1024 * mib,
9543 _ => {
9544 ((cfg.dim * cfg.hc_mult * DSPARK_BLOCK_MAX * 4096) as u64).clamp(512 * mib, 1024 * mib)
9545 }
9546 };
9547 crate::gpu_wgpu::DRAFT_PACK_RESERVE.store(bytes, std::sync::atomic::Ordering::Relaxed);
9548 crate::gpu_wgpu::DRAFT_RESERVE.store(
9549 bytes.saturating_add(workspace),
9550 std::sync::atomic::Ordering::Relaxed,
9551 );
9552}
9553
9554#[cfg(not(feature = "gpu"))]
9555pub fn dspark_reserve_note(_mtp: &[Dsv4Mtp], _cfg: &Dsv4Cfg, _layers: &[Dsv4Layer]) {}
9556
9557#[cfg(feature = "gpu")]
9558pub fn dspark_pack_build(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<DsparkPack> {
9559 if mtp.is_empty() {
9560 return None;
9561 }
9562 let model = mtp[0]
9563 .layer
9564 .experts
9565 .first()
9566 .and_then(|e| e.w1.model_arc())?;
9567 let native_q2 = mtp[0]
9568 .layer
9569 .experts
9570 .first()
9571 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9572 let dn_native = mtp[0]
9573 .layer
9574 .experts
9575 .first()
9576 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9577 let global_share = !native_q2
9578 && !dn_native
9579 && std::env::var("CMF_MOE_MASK").is_err()
9580 && crate::gpu_wgpu::dsv4_global_moe_supported()
9581 && crate::gpu_wgpu::dsv4_global_moe_ready(&model);
9582 let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
9583 .ok()
9584 .and_then(|v| v.parse().ok())
9585 .unwrap_or_else(|| {
9586 if global_share {
9587 return 40;
9591 }
9592 let gu_q2 = native_q2 || (!dspark_native_on() && DSPARK_Q2TP_ENCODE.get().is_some());
9598 let room = crate::gpu_wgpu::dsv4_draft_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_native);
9599 (room.saturating_sub(mtp.len() + 1) / mtp.len().max(1)).clamp(8, 40)
9600 });
9601 let mut freq: Vec<Vec<(u64, usize)>> = vec![Vec::new(); mtp.len()];
9605 let pack_path = std::env::var("CMF_DSPARK_PACK").ok().or_else(|| {
9606 let m = mtp[0].layer.experts.first()?.w1.model_arc()?;
9607 let mut s = m.path.as_os_str().to_os_string();
9608 s.push(".dspark.tsv");
9609 let p = std::path::PathBuf::from(s);
9610 p.exists().then(|| p.to_string_lossy().into_owned())
9611 });
9612 if let Some(path) = pack_path {
9613 if let Ok(text) = std::fs::read_to_string(&path) {
9614 for line in text.lines() {
9615 let mut it = line.split_whitespace();
9616 if let (Some(s), Some(e), Some(n)) = (it.next(), it.next(), it.next()) {
9617 if let (Ok(s), Ok(e), Ok(n)) =
9618 (s.parse::<usize>(), e.parse::<usize>(), n.parse::<u64>())
9619 {
9620 if s < freq.len() {
9621 freq[s].push((n, e));
9622 }
9623 }
9624 }
9625 }
9626 }
9627 }
9628 let mut stages = Vec::with_capacity(mtp.len());
9629 let mut routers = Vec::with_capacity(mtp.len());
9630 let mut biases = Vec::with_capacity(mtp.len());
9631 for (si, m) in mtp.iter().enumerate() {
9632 let l = &m.layer;
9633 let n = l.experts.len();
9634 let mut order: Vec<usize> = {
9637 let mut f = freq[si].clone();
9638 f.sort_by(|a, b| b.0.cmp(&a.0));
9639 let mut seen = vec![false; n];
9640 let mut o: Vec<usize> = f
9641 .into_iter()
9642 .map(|(_, e)| e)
9643 .filter(|&e| {
9644 if e < n && !seen[e] {
9645 seen[e] = true;
9646 true
9647 } else {
9648 false
9649 }
9650 })
9651 .collect();
9652 o.extend((0..n).filter(|&e| !seen[e]));
9653 o
9654 };
9655 order.truncate(n_res.min(n));
9656 let (global, mask, to_slot, tensors) = if global_share {
9657 let pk = pack_for(l, cfg, model.header.arch.num_layers + si)?;
9658 let gl = pk.global.clone()?;
9659 let remap = gl.pool.pin_picks(&model, gl.layer, &order, &l.experts);
9660 let mut mask = vec![false; n];
9661 let mut to_slot = vec![usize::MAX; n];
9662 for e in 0..n {
9663 if remap.get(e).copied().unwrap_or(u32::MAX) != u32::MAX {
9664 mask[e] = true;
9665 to_slot[e] = remap[e] as usize;
9666 }
9667 }
9668 (Some(gl), mask, to_slot, Vec::new())
9669 } else {
9670 let mut mask = vec![false; n];
9671 let mut to_slot = vec![usize::MAX; n];
9672 let mut tensors = Vec::with_capacity(order.len() + 1);
9673 for (slot, &e) in order.iter().enumerate() {
9674 let ex = &l.experts[e];
9675 let (Some(w1), Some(w3), Some(w2)) =
9676 (ex.w1.model_idx(), ex.w3.model_idx(), ex.w2.model_idx())
9677 else {
9678 return None;
9679 };
9680 mask[e] = true;
9681 to_slot[e] = slot;
9682 tensors.push((w1, w3, w2));
9683 }
9684 let (Some(s1), Some(s3), Some(s2)) = (
9685 l.shared.w1.model_idx(),
9686 l.shared.w3.model_idx(),
9687 l.shared.w2.model_idx(),
9688 ) else {
9689 return None;
9690 };
9691 tensors.push((s1, s3, s2));
9692 (None, mask, to_slot, tensors)
9693 };
9694 let mut router = vec![0.0f32; n * cfg.dim];
9696 for (r, row) in (0..n).zip(router.chunks_mut(cfg.dim)) {
9697 l.gate.row_f32(r, row);
9698 }
9699 routers.push(router);
9700 biases.push(l.gate_bias.clone());
9701 let mask_u32: Vec<u32> = mask.iter().map(|&m| m as u32).collect();
9702 let map_u32: Vec<u32> = to_slot
9703 .iter()
9704 .map(|&x| if x == usize::MAX { u32::MAX } else { x as u32 })
9705 .collect();
9706 stages.push(DsparkStagePack {
9707 mask,
9708 to_slot,
9709 mask_u32,
9710 map_u32,
9711 tensors,
9712 n_resident: order.len(),
9713 global,
9714 });
9715 }
9716 let mut skeleton = Vec::new();
9720 for m in mtp {
9721 let l = &m.layer;
9722 for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b] {
9723 skeleton.push(t.model_idx()?);
9724 }
9725 }
9726 if let Some(mp) = mtp[0].main_proj.as_ref() {
9727 skeleton.push(mp.model_idx()?);
9728 }
9729 for &idx in &skeleton {
9730 if !crate::gpu_wgpu::dsv4_weight_ready(&model, idx) {
9731 eprintln!("DSpark: скелет драфта не влез в VRAM — GPU-черновик выключен");
9732 return None;
9733 }
9734 }
9735 let gu_q2 = native_q2
9741 || (!crate::dsv4::dspark_native_on() && crate::dsv4::DSPARK_Q2TP_ENCODE.get().is_some());
9742 for (si, sp) in stages.iter().enumerate() {
9743 let ok = if sp.global.is_some() {
9744 true
9745 } else if native_q2 {
9746 crate::gpu_wgpu::dsv4_experts_ready(
9747 &model,
9748 &sp.tensors,
9749 cfg.moe_inter,
9750 cfg.dim,
9751 true,
9752 dn_native,
9753 )
9754 } else if gu_q2 {
9755 crate::gpu_wgpu::moe_expert_bufs_requant_gu(&model, &sp.tensors, cfg.moe_inter, cfg.dim)
9756 .is_some()
9757 } else {
9758 crate::gpu_wgpu::dsv4_experts_ready(
9759 &model,
9760 &sp.tensors,
9761 cfg.moe_inter,
9762 cfg.dim,
9763 false,
9764 false,
9765 )
9766 };
9767 if !ok {
9768 eprintln!(
9769 "DSpark: эксперты стадии {si} ({} + shared) не влезли в VRAM — GPU-черновик выключен",
9770 sp.n_resident
9771 );
9772 return None;
9773 }
9774 }
9775 let _ = crate::gpu_wgpu::pin_weights(&model, &skeleton);
9776 eprintln!(
9777 "DSpark: {} — {} стадии по {} экспертов + shared",
9778 if global_share {
9779 "защищённая доля единого пула"
9780 } else {
9781 "отдельный пак драфта на карте"
9782 },
9783 stages.len(),
9784 stages
9785 .iter()
9786 .map(|s| s.n_resident.to_string())
9787 .collect::<Vec<_>>()
9788 .join("/")
9789 );
9790 Some(DsparkPack {
9791 stages,
9792 gu_q2: if global_share { false } else { gu_q2 },
9793 dn_q2: dn_native,
9794 routers,
9795 biases,
9796 })
9797}
9798
9799pub fn dspark_ring_append(
9806 g: &Dsv4Globals,
9807 mtp: &[Dsv4Mtp],
9808 cfg: &Dsv4Cfg,
9809 ds: &mut DsparkState,
9810 pos: usize,
9811 pool: Option<&crate::pool::Pool>,
9812) {
9813 let (dim, hd, rd) = (cfg.dim, cfg.head_dim, cfg.rope_head_dim);
9814 let inv_freq = &g.inv_freq_window;
9815 let Some(stage0) = mtp.first() else { return };
9816 let (Some(mp), Some(mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
9817 return;
9818 };
9819 let mut main_x = vec![0.0f32; dim];
9820 mp.matvec(&ds.main_hidden, &mut main_x, pool);
9821 rms_weighted(&mut main_x, mn, cfg.norm_eps);
9822 for (si, m) in mtp.iter().enumerate() {
9823 let kvw = m.layer.wkv.rows();
9824 if ds.win[si].len() < cfg.window * kvw {
9825 ds.win[si].resize(cfg.window * kvw, 0.0);
9826 }
9827 let mut kv = vec![0.0f32; kvw];
9828 m.layer.wkv.matvec(&main_x, &mut kv, pool);
9829 rms_weighted(&mut kv, &m.layer.kv_norm, cfg.norm_eps);
9830 rope_tail(&mut kv[kvw - hd..], inv_freq, pos, rd, false);
9831 let slot = pos % cfg.window;
9832 ds.win[si][slot * kvw..(slot + 1) * kvw].copy_from_slice(&kv);
9833 ds.filled[si] = (pos + 1).min(cfg.window);
9834 }
9835}
9836
9837#[cfg(feature = "gpu")]
9843#[allow(clippy::too_many_arguments)]
9844pub fn dspark_draft_gpu(
9845 g: &Dsv4Globals,
9846 mtp: &[Dsv4Mtp],
9847 cfg: &Dsv4Cfg,
9848 ds: &mut DsparkState,
9849 pack: &DsparkPack,
9850 kv_id: u64,
9851 last_token: u32,
9852 pos: usize,
9853 pool: Option<&crate::pool::Pool>,
9854 out_conf: &mut Vec<f32>,
9855) -> Vec<u32> {
9856 let (hc, dim) = (cfg.hc_mult, cfg.dim);
9857 let block = dspark_block();
9858 let Some(model) = mtp[0].layer.experts.first().and_then(|e| e.w1.model_arc()) else {
9859 return Vec::new();
9860 };
9861 let (Some(mp), Some(mn)) = (mtp[0].main_proj.as_ref(), mtp[0].main_norm.as_ref()) else {
9862 return Vec::new();
9863 };
9864 let Some(mp_idx) = mp.model_idx() else {
9865 return Vec::new();
9866 };
9867 let mut stages = Vec::with_capacity(mtp.len());
9868 for (si, m) in mtp.iter().enumerate() {
9869 let l = &m.layer;
9870 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
9871 l.wq_a.model_idx(),
9872 l.wq_b.model_idx(),
9873 l.wo_a.model_idx(),
9874 l.wo_b.model_idx(),
9875 l.wkv.model_idx(),
9876 ) else {
9877 return Vec::new();
9878 };
9879 let sp = &pack.stages[si];
9880 stages.push(crate::gpu_wgpu::DsparkStageW {
9881 wq_a,
9882 wq_b,
9883 wo_a,
9884 wo_b,
9885 wkv,
9886 q_norm: &l.q_norm,
9887 kv_norm: &l.kv_norm,
9888 attn_norm: &l.attn_norm,
9889 ffn_norm: &l.ffn_norm,
9890 sink: &l.attn_sink,
9891 hc_attn_fn: &l.hc_attn_fn,
9892 hc_attn_scale: &l.hc_attn_scale,
9893 hc_attn_base: &l.hc_attn_base,
9894 hc_ffn_fn: &l.hc_ffn_fn,
9895 hc_ffn_scale: &l.hc_ffn_scale,
9896 hc_ffn_base: &l.hc_ffn_base,
9897 router: &pack.routers[si],
9898 bias: pack.biases[si].as_deref(),
9899 experts: &sp.tensors,
9900 mask_u32: &sp.mask_u32,
9901 map_u32: &sp.map_u32,
9902 global: sp.global.as_ref().map(|gl| crate::gpu_wgpu::Dsv4GlobalMoe {
9903 pool_uid: gl.pool.uid,
9904 shared_slot: gl.shared_slot,
9905 segment_slots: gl.pool.segment_slots as u32,
9906 }),
9907 });
9908 }
9909 let geom = crate::gpu_wgpu::DsparkGeom {
9910 dim,
9911 hc,
9912 nh: cfg.n_heads,
9913 hd: cfg.head_dim,
9914 rd: cfg.rope_head_dim,
9915 q_lora: cfg.q_lora_rank,
9916 o_lora: cfg.o_lora_rank,
9917 o_groups: cfg.o_groups,
9918 inter: cfg.moe_inter,
9919 n_experts: cfg.n_routed_experts,
9920 top_k: cfg.top_k,
9921 window: cfg.window,
9922 eps: cfg.norm_eps,
9923 hc_eps: cfg.hc_eps,
9924 sinkhorn_iters: cfg.hc_sinkhorn_iters,
9925 route_scale: cfg.route_scale,
9926 swiglu_limit: cfg.swiglu_limit,
9927 scale: (cfg.head_dim as f32).powf(-0.5),
9928 gu_q2: pack.gu_q2,
9929 dn_q2: pack.dn_q2,
9930 };
9931 let ids: Vec<u32> = (0..block)
9933 .map(|i| {
9934 if i == 0 {
9935 last_token
9936 } else {
9937 DSPARK_NOISE_TOKEN
9938 }
9939 })
9940 .collect();
9941 let mut states0 = vec![0.0f32; block * hc * dim];
9942 let mut emb = vec![0.0f32; dim];
9943 for (i, &id) in ids.iter().enumerate() {
9944 g.embed.row_f32(id as usize, &mut emb);
9945 for j in 0..hc {
9946 states0[(i * hc + j) * dim..(i * hc + j + 1) * dim].copy_from_slice(&emb);
9947 }
9948 }
9949 let dspark_time = {
9950 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9951 *ON.get_or_init(|| std::env::var("CMF_DSPARK_TIME").is_ok_and(|v| v != "0"))
9952 };
9953 let t0 = std::time::Instant::now();
9954 let filled = (pos + 1).min(cfg.window);
9955 let mut states = vec![0.0f32; block * hc * dim];
9956 if !crate::gpu_wgpu::dspark_graph(
9957 &model,
9958 &stages,
9959 geom,
9960 kv_id,
9961 mp_idx,
9962 mn,
9963 &ds.main_hidden,
9964 &states0,
9965 pos,
9966 filled,
9967 &g.inv_freq_window,
9968 block,
9969 &mut states,
9970 ) {
9971 return Vec::new();
9972 }
9973 for si in 0..mtp.len() {
9974 ds.filled[si] = filled;
9975 }
9976 let t_graph = t0.elapsed();
9977
9978 let last = &mtp[mtp.len() - 1];
9980 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
9981 last.hc_head_fn.as_ref(),
9982 last.hc_head_base.as_ref(),
9983 last.hc_head_scale,
9984 last.norm.as_ref(),
9985 ) else {
9986 return Vec::new();
9987 };
9988 let mut head_in = vec![0.0f32; block * dim];
9989 let mut pre_norms = vec![vec![0.0f32; dim]; block];
9990 for i in 0..block {
9991 hc_head_fold(
9992 &states[i * hc * dim..(i + 1) * hc * dim],
9993 hfn,
9994 hscale,
9995 hbase,
9996 cfg,
9997 pool,
9998 &mut head_in[i * dim..(i + 1) * dim],
9999 );
10000 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
10001 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
10002 }
10003 let t_fold = t0.elapsed();
10004 let mut logits = vec![0.0f32; block * cfg.vocab];
10005 let head_gpu = g.head.model_idx().is_some_and(|hi| {
10008 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
10009 &model,
10010 hi,
10011 &head_in,
10012 block,
10013 cfg.vocab,
10014 dim,
10015 &mut logits,
10016 )
10017 });
10018 if !head_gpu {
10019 g.head.matmat(&head_in, block, &mut logits, pool);
10020 }
10021 let t_head = t0.elapsed();
10022 let mut proposals = Vec::with_capacity(block);
10027 out_conf.clear();
10028 let mut prev = last_token;
10029 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
10030 let mut bias = vec![0.0f32; cfg.vocab];
10031 for i in 0..block {
10032 let row = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
10033 if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
10034 w1.row_f32(prev as usize, &mut mk_embed);
10035 w2.matvec(&mk_embed, &mut bias, pool);
10036 for (a, b) in row.iter_mut().zip(&bias) {
10037 *a += *b;
10038 }
10039 }
10040 let mut best = 0usize;
10041 for v in 1..row.len() {
10042 if row[v] > row[best] {
10043 best = v;
10044 }
10045 }
10046 if let Some(cf) = last.confidence.as_ref() {
10047 let mut cat = pre_norms[i].clone();
10048 cat.extend_from_slice(&mk_embed);
10049 let mut sc = [0.0f32; 1];
10050 if cat.len() == cf.cols() {
10051 cf.matvec(&cat, &mut sc, pool);
10052 }
10053 out_conf.push(sc[0]);
10054 }
10055 proposals.push(best as u32);
10056 prev = best as u32;
10057 }
10058 if dspark_time {
10059 eprintln!(
10060 "DSpark GPU: граф {:.1} мс, фолды {:.1}, голова {:.1}, марков+argmax {:.1}",
10061 t_graph.as_secs_f64() * 1e3,
10062 (t_fold - t_graph).as_secs_f64() * 1e3,
10063 (t_head - t_fold).as_secs_f64() * 1e3,
10064 (t0.elapsed() - t_head).as_secs_f64() * 1e3,
10065 );
10066 }
10067 proposals
10068}
10069
10070#[allow(clippy::too_many_arguments)]
10076pub fn dspark_draft(
10077 g: &Dsv4Globals,
10078 mtp: &[Dsv4Mtp],
10079 cfg: &Dsv4Cfg,
10080 ds: &mut DsparkState,
10081 last_token: u32,
10082 pos: usize,
10083 pool: Option<&crate::pool::Pool>,
10084 out_conf: &mut Vec<f32>,
10085) -> Vec<u32> {
10086 let (hc, dim, hd, rd) = (cfg.hc_mult, cfg.dim, cfg.head_dim, cfg.rope_head_dim);
10087 let block = dspark_block();
10088 let inv_freq = &g.inv_freq_window;
10089
10090 let Some(stage0) = mtp.first() else {
10092 return Vec::new();
10093 };
10094 let (Some(_mp), Some(_mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
10095 return Vec::new();
10096 };
10097 dspark_ring_append(g, mtp, cfg, ds, pos, pool);
10098
10099 let ids: Vec<u32> = (0..block)
10101 .map(|i| {
10102 if i == 0 {
10103 last_token
10104 } else {
10105 DSPARK_NOISE_TOKEN
10106 }
10107 })
10108 .collect();
10109 let mut states = vec![vec![0.0f32; hc * dim]; block];
10110 let mut emb = vec![0.0f32; dim];
10111 for (i, &id) in ids.iter().enumerate() {
10112 g.embed.row_f32(id as usize, &mut emb);
10113 for j in 0..hc {
10114 states[i][j * dim..(j + 1) * dim].copy_from_slice(&emb);
10115 }
10116 }
10117
10118 let mut scratch = HcScratch::new(cfg);
10119 for (si, m) in mtp.iter().enumerate() {
10120 let l = &m.layer;
10121 let kvw = l.wkv.rows();
10122 let mut post = vec![vec![0.0f32; hc]; block];
10125 let mut comb = vec![vec![0.0f32; hc * hc]; block];
10126 let mut resid = vec![vec![0.0f32; hc * dim]; block];
10127 let mut folded = vec![vec![0.0f32; dim]; block];
10128 let mix_hc = (2 + hc) * hc;
10129 for i in 0..block {
10130 hc_mixes(
10131 &states[i],
10132 &l.hc_attn_fn,
10133 mix_hc,
10134 cfg.norm_eps,
10135 pool,
10136 &mut scratch.mixes,
10137 );
10138 hc_split_sinkhorn(
10139 &scratch.mixes,
10140 &l.hc_attn_scale,
10141 &l.hc_attn_base,
10142 hc,
10143 cfg.hc_sinkhorn_iters,
10144 cfg.hc_eps,
10145 &mut scratch.pre,
10146 &mut post[i],
10147 &mut comb[i],
10148 );
10149 hc_fold(&states[i], &scratch.pre, hc, dim, &mut folded[i]);
10150 rms_weighted(&mut folded[i], &l.attn_norm, cfg.norm_eps);
10151 resid[i].copy_from_slice(&states[i]);
10152 }
10153 let folded_all: Vec<f32> = folded.iter().flatten().copied().collect();
10155 let mut blk_kv = vec![0.0f32; block * kvw];
10156 l.wkv.matmat(&folded_all, block, &mut blk_kv, pool);
10157 for i in 0..block {
10158 let dst = &mut blk_kv[i * kvw..(i + 1) * kvw];
10159 rms_weighted(dst, &l.kv_norm, cfg.norm_eps);
10160 rope_tail(&mut dst[kvw - hd..], inv_freq, pos + 1 + i, rd, false);
10161 }
10162 let win_len = ds.filled[si];
10164 let mut cache = Vec::with_capacity((win_len + block) * hd);
10165 for p in 0..win_len {
10166 let e = &ds.win[si][p * kvw..(p + 1) * kvw];
10167 cache.extend_from_slice(&e[kvw - hd..]);
10168 }
10169 for i in 0..block {
10170 let e = &blk_kv[i * kvw..(i + 1) * kvw];
10171 cache.extend_from_slice(&e[kvw - hd..]);
10172 }
10173 let idxs: Vec<usize> = (0..win_len + block).collect();
10174 let scale = (hd as f32).powf(-0.5);
10175 let qrank = l.wq_a.rows();
10176 let qdim = cfg.n_heads * hd;
10177 let mut qr = vec![0.0f32; block * qrank];
10178 l.wq_a.matmat(&folded_all, block, &mut qr, pool);
10179 for i in 0..block {
10180 rms_weighted(&mut qr[i * qrank..(i + 1) * qrank], &l.q_norm, cfg.norm_eps);
10181 }
10182 let mut q = vec![0.0f32; block * qdim];
10183 l.wq_b.matmat(&qr, block, &mut q, pool);
10184 let mut attn = vec![0.0f32; block * qdim];
10185 for i in 0..block {
10186 let qi = &mut q[i * qdim..(i + 1) * qdim];
10187 let ai = &mut attn[i * qdim..(i + 1) * qdim];
10188 let qpos = pos + 1 + i;
10189 for h in 0..cfg.n_heads {
10190 let head = &mut qi[h * hd..(h + 1) * hd];
10191 rms_inplace(head, cfg.norm_eps);
10192 rope_tail(head, inv_freq, qpos, rd, false);
10193 }
10194 for h in 0..cfg.n_heads {
10195 let qh = &qi[h * hd..(h + 1) * hd];
10196 let oh = &mut ai[h * hd..(h + 1) * hd];
10197 sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
10198 rope_tail(oh, inv_freq, qpos, rd, true);
10199 }
10200 }
10201 let mut blk_out = vec![0.0f32; block * dim];
10202 o_project_block(
10203 &attn,
10204 block,
10205 &l.wo_a,
10206 &l.wo_b,
10207 cfg.o_groups,
10208 cfg.o_lora_rank,
10209 pool,
10210 &mut blk_out,
10211 );
10212 for i in 0..block {
10213 let mut next = vec![0.0f32; hc * dim];
10214 hc_expand(
10215 &blk_out[i * dim..(i + 1) * dim],
10216 &resid[i],
10217 &post[i],
10218 &comb[i],
10219 hc,
10220 dim,
10221 &mut next,
10222 );
10223 states[i] = next;
10224 }
10225 let mut ffn_fold = vec![0.0f32; block * dim];
10228 let mut ffn_post = vec![vec![0.0f32; hc]; block];
10229 let mut ffn_comb = vec![vec![0.0f32; hc * hc]; block];
10230 let mut ffn_resid = vec![vec![0.0f32; hc * dim]; block];
10231 for i in 0..block {
10232 hc_mixes(
10233 &states[i],
10234 &l.hc_ffn_fn,
10235 mix_hc,
10236 cfg.norm_eps,
10237 pool,
10238 &mut scratch.mixes,
10239 );
10240 hc_split_sinkhorn(
10241 &scratch.mixes,
10242 &l.hc_ffn_scale,
10243 &l.hc_ffn_base,
10244 hc,
10245 cfg.hc_sinkhorn_iters,
10246 cfg.hc_eps,
10247 &mut scratch.pre,
10248 &mut ffn_post[i],
10249 &mut ffn_comb[i],
10250 );
10251 hc_fold(
10252 &states[i],
10253 &scratch.pre,
10254 hc,
10255 dim,
10256 &mut ffn_fold[i * dim..(i + 1) * dim],
10257 );
10258 rms_weighted(
10259 &mut ffn_fold[i * dim..(i + 1) * dim],
10260 &l.ffn_norm,
10261 cfg.norm_eps,
10262 );
10263 ffn_resid[i].copy_from_slice(&states[i]);
10264 }
10265 let mut moe_out = vec![0.0f32; block * dim];
10266 moe_step_block(&ffn_fold, block, l, cfg, &ids, si, pool, &mut moe_out);
10267 for i in 0..block {
10268 let mut next = vec![0.0f32; hc * dim];
10269 hc_expand(
10270 &moe_out[i * dim..(i + 1) * dim],
10271 &ffn_resid[i],
10272 &ffn_post[i],
10273 &ffn_comb[i],
10274 hc,
10275 dim,
10276 &mut next,
10277 );
10278 states[i] = next;
10279 }
10280 }
10281
10282 let last = &mtp[mtp.len() - 1];
10284 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
10285 last.hc_head_fn.as_ref(),
10286 last.hc_head_base.as_ref(),
10287 last.hc_head_scale,
10288 last.norm.as_ref(),
10289 ) else {
10290 return Vec::new();
10291 };
10292 let mut proposals = Vec::with_capacity(block);
10293 out_conf.clear();
10294 let mut prev = last_token;
10295 let mut head_in = vec![0.0f32; block * dim];
10296 let mut pre_norms = vec![vec![0.0f32; dim]; block];
10297 for i in 0..block {
10298 hc_head_fold(
10299 &states[i],
10300 hfn,
10301 hscale,
10302 hbase,
10303 cfg,
10304 pool,
10305 &mut head_in[i * dim..(i + 1) * dim],
10306 );
10307 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
10308 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
10309 }
10310 let mut logits = vec![0.0f32; block * cfg.vocab];
10311 g.head.matmat(&head_in, block, &mut logits, pool);
10312 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
10313 for i in 0..block {
10314 let logits_i = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
10315 let no_markov = {
10324 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10325 *ON.get_or_init(|| std::env::var("CMF_DSPARK_NO_MARKOV").is_ok_and(|v| v != "0"))
10326 };
10327 if no_markov {
10328 if let Some(w1) = last.markov_w1.as_ref() {
10330 w1.row_f32(prev as usize, &mut mk_embed);
10331 }
10332 } else if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
10333 w1.row_f32(prev as usize, &mut mk_embed);
10334 let mut bias = vec![0.0f32; cfg.vocab];
10335 w2.matvec(&mk_embed, &mut bias, pool);
10336 for (a, b) in logits_i.iter_mut().zip(&bias) {
10337 *a += *b;
10338 }
10339 }
10340 let mut best = 0usize;
10341 for v in 1..logits_i.len() {
10342 if logits_i[v] > logits_i[best] {
10343 best = v;
10344 }
10345 }
10346 if let Some(cf) = last.confidence.as_ref() {
10347 let mut cat = pre_norms[i].clone();
10348 cat.extend_from_slice(&mk_embed);
10349 let mut s = [0.0f32; 1];
10350 if cat.len() == cf.cols() {
10351 cf.matvec(&cat, &mut s, pool);
10352 }
10353 out_conf.push(s[0]);
10354 }
10355 proposals.push(best as u32);
10356 prev = best as u32;
10357 }
10358 proposals
10359}