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 };
1409 crate::gpu_wgpu::dsv4_attn_frame(
1417 &model,
1418 &w,
1419 g,
1420 hidden,
1421 Some(qn),
1422 kv_id,
1423 li,
1424 &idx32,
1425 inv_freq,
1426 pos,
1427 hc,
1428 out,
1429 )
1430}
1431
1432#[derive(Default)]
1435pub struct AttnPrep {
1436 pub qr: Vec<f32>,
1437 pub idxs: Vec<usize>,
1438 pub win_len: usize,
1439}
1440
1441#[allow(clippy::too_many_arguments)]
1442pub fn attention_step(
1443 hidden: &[f32],
1444 l: &Dsv4Layer,
1445 cfg: &Dsv4Cfg,
1446 st: &mut Dsv4State,
1447 li: usize,
1448 inv_freq: &[f32],
1450 pool: Option<&crate::pool::Pool>,
1451 prep_out: Option<&mut AttnPrep>,
1455 out: &mut [f32],
1456) {
1457 let _t0 = prof::on().then(std::time::Instant::now);
1458 let _guard = scopeguard_attn(_t0);
1459 let (hd, rd) = (cfg.head_dim, cfg.rope_head_dim);
1460 let pos = st.pos;
1461 if std::env::var("CMF_FREQ_DEBUG").is_ok() && li == 0 && pos == 0 {
1462 eprintln!(
1463 " [порт] rd={rd} частот={} inv_freq[0..4]={:?}",
1464 inv_freq.len(),
1465 &inv_freq[..4.min(inv_freq.len())]
1466 );
1467 }
1468
1469 let mut qr = vec![0.0f32; cfg.q_lora_rank];
1474 let mut kv = vec![0.0f32; hd];
1475 crate::qtensor::QTensor::matvec_many([&l.wq_a, &l.wkv], hidden, [&mut qr, &mut kv], pool);
1476 rms_weighted(&mut qr, &l.q_norm, cfg.norm_eps);
1477 let split_config = st.partial_set.iter().any(|&p| p) && st.split_deep;
1495 let past_chain =
1496 st.dev_owned && (li >= st.dev_set.len() || !st.dev_set.get(li).copied().unwrap_or(false));
1497 if std::env::var("CMF_DSV4_GATE_DBG").is_ok() {
1498 eprintln!(
1499 "[gate] li={li} pos={} split={split_config} past={past_chain} dev_owned={} set_len={} part_len={}",
1500 st.pos,
1501 st.dev_owned,
1502 st.dev_set.len(),
1503 st.partial_set.len()
1504 );
1505 }
1506 let on_gpu = gpu_attn_enabled() && !(split_config && past_chain);
1507
1508 rms_weighted(&mut kv, &l.kv_norm, cfg.norm_eps);
1509 rope_tail(&mut kv, inv_freq, pos, rd, false);
1510
1511 if let Some(cp) = &l.compressor {
1516 let mut pk = std::mem::take(&mut st.pending_kv[li]);
1517 let mut ps = std::mem::take(&mut st.pending_score[li]);
1518 let mut qk = std::mem::take(&mut st.prev_kv[li]);
1519 let mut qs = std::mem::take(&mut st.prev_score[li]);
1520 let entry = compressor_step(
1521 cp,
1522 hidden,
1523 pos,
1524 rd,
1525 cfg.norm_eps,
1526 inv_freq,
1527 pool,
1528 &mut pk,
1529 &mut ps,
1530 &mut qk,
1531 &mut qs,
1532 );
1533 st.pending_kv[li] = pk;
1534 st.pending_score[li] = ps;
1535 st.prev_kv[li] = qk;
1536 st.prev_score[li] = qs;
1537 if let Some(e) = entry {
1538 st.compressed[li].extend_from_slice(&e);
1539 }
1540 }
1541 if let Some(ix) = &l.indexer {
1546 let mut pk = std::mem::take(&mut st.pending_ix_kv[li]);
1547 let mut ps = std::mem::take(&mut st.pending_ix_score[li]);
1548 let mut qk = std::mem::take(&mut st.prev_ix_kv[li]);
1549 let mut qs = std::mem::take(&mut st.prev_ix_score[li]);
1550 let entry = compressor_step(
1551 &ix.compressor,
1552 hidden,
1553 pos,
1554 rd,
1555 cfg.norm_eps,
1556 inv_freq,
1557 pool,
1558 &mut pk,
1559 &mut ps,
1560 &mut qk,
1561 &mut qs,
1562 );
1563 st.pending_ix_kv[li] = pk;
1564 st.pending_ix_score[li] = ps;
1565 st.prev_ix_kv[li] = qk;
1566 st.prev_ix_score[li] = qs;
1567 if let Some(e) = entry {
1568 st.index_kv[li].extend_from_slice(&e);
1569 }
1570 }
1571
1572 st.window[li].extend_from_slice(&kv);
1573 let cap = cfg.window * hd;
1577 if st.window[li].len() > cap {
1578 let drop = st.window[li].len() - cap;
1579 st.window[li].drain(..drop);
1580 }
1581 let win_len = st.window[li].len() / hd;
1582 let n_pos = win_len + st.compressed[li].len() / hd;
1583
1584 let mut idxs: Vec<usize> = (0..win_len).collect();
1593 if !st.compressed[li].is_empty() && !no_compressed() {
1594 let n_comp = st.compressed[li].len() / hd;
1595 match &l.indexer {
1596 Some(ix) => {
1597 let ih = ix.weights_proj.rows();
1613 let idim = ix.wq_b.rows() / ih.max(1);
1614 let mut qi = vec![0.0f32; ix.wq_b.rows()];
1615 ix.wq_b.matvec(&qr, &mut qi, pool);
1616 for h in 0..ih {
1617 rope_tail(&mut qi[h * idim..(h + 1) * idim], inv_freq, pos, rd, false);
1618 }
1619 let mut hw = vec![0.0f32; ih];
1620 ix.weights_proj.matvec(hidden, &mut hw, pool);
1621 let sc_factor = (idim as f32).powf(-0.5) * (ih as f32).powf(-0.5);
1622 for w in hw.iter_mut() {
1623 *w *= sc_factor;
1624 }
1625 let n_ix = st.index_kv[li].len() / idim.max(1);
1626 let mut sc = Vec::new();
1627 index_scores(
1628 &qi,
1629 &st.index_kv[li],
1630 &hw,
1631 ih,
1632 idim,
1633 n_ix.min(n_comp),
1634 n_ix.min(n_comp),
1635 pool,
1636 &mut sc,
1637 );
1638 let mut picked = Vec::new();
1639 top_k_positions(&sc, cfg.index_topk, &mut picked);
1640 idxs.extend(picked.into_iter().map(|p| win_len + p));
1641 }
1642 None => idxs.extend((0..n_comp).map(|p| win_len + p)),
1643 }
1644 }
1645 debug_assert!(idxs.iter().all(|&p| p < n_pos));
1646 if let Some(p) = prep_out {
1647 p.qr = qr;
1648 p.idxs = idxs;
1649 p.win_len = win_len;
1650 return;
1651 }
1652
1653 let scale = (hd as f32).powf(-0.5);
1655 #[cfg(feature = "gpu")]
1656 if on_gpu
1657 && {
1658 if std::env::var("CMF_DSV4_XCHK").is_ok() {
1659 if let Some(card) = crate::gpu_wgpu::dsv4_dbg_read_tag(45, 0, hidden.len()) {
1663 let md = hidden
1664 .iter()
1665 .zip(card.iter())
1666 .map(|(a, b)| (a - b).abs())
1667 .fold(0.0f32, f32::max);
1668 eprintln!("[xchk] li={li} pos={pos} x2 maxdiff={md:.3e}");
1669 }
1670 }
1671 true
1672 }
1673 && attn_frame(
1674 l, cfg, st, li, hidden, &qr, &idxs, inv_freq, pos, win_len, scale, None, out,
1675 )
1676 {
1677 return;
1678 }
1679
1680 let mut q = vec![0.0f32; cfg.n_heads * hd];
1682 l.wq_b.matvec(&qr, &mut q, pool);
1683 for h in 0..cfg.n_heads {
1684 let head = &mut q[h * hd..(h + 1) * hd];
1685 rms_inplace(head, cfg.norm_eps);
1686 rope_tail(head, inv_freq, pos, rd, false);
1687 }
1688 let mut cache: Vec<f32> = st.window[li].clone();
1689 cache.extend_from_slice(&st.compressed[li]);
1690
1691 let mut attn = vec![0.0f32; cfg.n_heads * hd];
1693 for h in 0..cfg.n_heads {
1694 let qh = &q[h * hd..(h + 1) * hd];
1695 let oh = &mut attn[h * hd..(h + 1) * hd];
1700 sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
1701 rope_tail(oh, inv_freq, pos, rd, true);
1702 }
1703
1704 o_project(
1710 &attn,
1711 &|r, x, sc| l.wo_a.row_dot(r, x, sc),
1712 l.wo_a.cols(),
1713 &|mid, dst| l.wo_b.matvec(mid, dst, pool),
1714 cfg.o_groups,
1715 cfg.o_lora_rank,
1716 pool,
1717 out,
1718 );
1719}
1720
1721pub fn rms_weighted(v: &mut [f32], w: &[f32], eps: f32) {
1723 let ms = v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32;
1724 let inv = 1.0 / (ms + eps).sqrt();
1725 for (x, g) in v.iter_mut().zip(w) {
1726 *x = *x * inv * g;
1727 }
1728}
1729
1730thread_local! {
1745 static ROUTE_COUNTS: std::cell::RefCell<Vec<Vec<u64>>> =
1746 const { std::cell::RefCell::new(Vec::new()) };
1747}
1748
1749fn route_stats_on() -> bool {
1750 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1751 *ON.get_or_init(|| std::env::var("CMF_MOE_STATS").is_ok())
1752}
1753
1754fn record_route(li: usize, n_layers_hint: usize, n_experts: usize, routed: &[(usize, f32)]) {
1755 ROUTE_COUNTS.with(|c| {
1756 let mut c = c.borrow_mut();
1757 if c.len() <= li.max(n_layers_hint) {
1758 c.resize(li.max(n_layers_hint) + 1, Vec::new());
1759 }
1760 let row = &mut c[li];
1761 if row.len() < n_experts {
1762 row.resize(n_experts, 0);
1763 }
1764 for &(e, weight) in routed {
1765 if e < row.len() {
1766 let mass = (weight.abs() as f64 * 1_000_000.0).round() as u64;
1770 row[e] = row[e].saturating_add(mass.max(1));
1771 }
1772 }
1773 });
1774}
1775
1776pub fn take_route_counts() -> Vec<Vec<u64>> {
1778 ROUTE_COUNTS.with(|c| std::mem::take(&mut *c.borrow_mut()))
1779}
1780
1781struct Charge(
1785 Option<std::time::Instant>,
1786 &'static std::sync::atomic::AtomicU64,
1787);
1788impl Drop for Charge {
1789 fn drop(&mut self) {
1790 if let Some(t) = self.0 {
1791 self.1.fetch_add(
1792 t.elapsed().as_nanos() as u64,
1793 std::sync::atomic::Ordering::Relaxed,
1794 );
1795 }
1796 }
1797}
1798fn scopeguard_attn(t: Option<std::time::Instant>) -> Charge {
1799 Charge(t, &prof::ATTN_NS)
1800}
1801fn scopeguard_moe(t: Option<std::time::Instant>, li: usize) -> Charge {
1802 if t.is_some() {
1803 prof::note_layer(li);
1804 }
1805 Charge(t, &prof::MOE_NS)
1806}
1807
1808#[cfg(feature = "gpu")]
1812#[allow(clippy::too_many_arguments)]
1813fn dsv4_layer_loop(
1814 state: &mut [f32],
1815 layers: &[Dsv4Layer],
1816 g: &Dsv4Globals,
1817 cfg: &Dsv4Cfg,
1818 st: &mut Dsv4State,
1819 token_id: u32,
1820 inv_freq: &[f32],
1821 pool: Option<&crate::pool::Pool>,
1822 scratch: &mut HcScratch,
1823) -> bool {
1824 let dim = cfg.dim;
1825 let freqs_of = |l: &Dsv4Layer| -> &[f32] {
1826 let f = if l.compressor.is_some() {
1827 &g.inv_freq_compress
1828 } else {
1829 &g.inv_freq_window
1830 };
1831 if f.is_empty() { inv_freq } else { f.as_slice() }
1832 };
1833 {
1846 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1847 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
1848 if let (Some(idx), Some(model)) = (g.head.model_idx(), g.head.model_arc()) {
1849 let ok = crate::gpu_wgpu::dsv4_weight_ready(&model, idx);
1850 tracing::info!("dsv4: голова на карте: {}", if ok { "да" } else { "нет" });
1851 }
1852 }
1853 }
1854 let mut on_dev = vec![false; layers.len()];
1855 let mut partial_dev = vec![false; layers.len()];
1856 let mut attn_ready = vec![false; layers.len()];
1857 for (li, l) in layers.iter().enumerate() {
1862 if l.wq_a.model_idx().is_none()
1863 || l.wq_b.model_idx().is_none()
1864 || l.wo_a.model_idx().is_none()
1865 || l.wo_b.model_idx().is_none()
1866 {
1867 return false;
1868 }
1869 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
1870 return false;
1871 };
1872 let mut want = vec![
1887 l.wq_a.model_idx(),
1888 l.wq_b.model_idx(),
1889 l.wo_a.model_idx(),
1890 l.wo_b.model_idx(),
1891 ];
1892 if chain_enabled() {
1893 want.push(l.wkv.model_idx());
1894 if let Some(cp) = &l.compressor {
1895 want.push(cp.wkv.model_idx());
1896 want.push(cp.wgate.model_idx());
1897 }
1898 if let Some(ix) = &l.indexer {
1899 want.push(ix.wq_b.model_idx());
1900 want.push(ix.weights_proj.model_idx());
1901 want.push(ix.compressor.wkv.model_idx());
1902 want.push(ix.compressor.wgate.model_idx());
1903 }
1904 }
1905 attn_ready[li] = want
1906 .into_iter()
1907 .flatten()
1908 .all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
1909 }
1910 for (li, l) in layers.iter().enumerate() {
1911 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
1912 return false;
1913 };
1914 let gu_q2 = l
1915 .experts
1916 .first()
1917 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
1918 let pk = pack_for(l, cfg, li);
1922 if let Some(pk) = pk {
1923 let dn_q2 = l
1924 .experts
1925 .first()
1926 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
1927 let experts_ok = if pk.global.is_some() {
1928 crate::gpu_wgpu::dsv4_global_moe_ready(&model)
1929 } else {
1930 crate::gpu_wgpu::dsv4_experts_ready(
1931 &model,
1932 &pk.tensors,
1933 cfg.moe_inter,
1934 dim,
1935 gu_q2,
1936 dn_q2,
1937 )
1938 };
1939 on_dev[li] = attn_ready[li] && experts_ok && pk.route_complete();
1940 partial_dev[li] = attn_ready[li] && experts_ok && !pk.route_complete();
1941 }
1942 }
1943 let active_dev: Vec<bool> = on_dev
1944 .iter()
1945 .zip(&partial_dev)
1946 .map(|(&full, &partial)| full || partial)
1947 .collect();
1948 if !active_dev.iter().any(|&x| x) {
1949 return false;
1950 }
1951 if st.partial_set.len() != partial_dev.len() || st.partial_set != partial_dev {
1956 st.partial_set = partial_dev.clone();
1957 st.split_deep = active_dev
1958 .iter()
1959 .zip(&partial_dev)
1960 .filter(|(a, p)| !**a || **p)
1961 .count()
1962 > 1;
1963 }
1964
1965 {
1969 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1970 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
1971 let host: Vec<usize> = active_dev
1972 .iter()
1973 .enumerate()
1974 .filter(|&(_, d)| !*d)
1975 .map(|(i, _)| i)
1976 .collect();
1977 let partial: Vec<(usize, usize)> = partial_dev
1978 .iter()
1979 .enumerate()
1980 .filter(|&(_, d)| *d)
1981 .filter_map(|(li, _)| pack_for(&layers[li], cfg, li).map(|p| (li, p.globals.len())))
1982 .collect();
1983 if host.is_empty() && partial.is_empty() {
1984 tracing::info!("dsv4: все {} слоёв на карте", on_dev.len());
1985 } else {
1986 tracing::info!(
1987 "dsv4: {} из {} слоёв используют карту; частичные {:?}; на хосте {:?}",
1988 active_dev.len() - host.len(),
1989 on_dev.len(),
1990 partial,
1991 host,
1992 );
1993 }
1994 }
1995 }
1996
1997 let (mut folded, post0, comb0) = hc_fold_norm(
1999 state,
2000 &layers[0].hc_attn_fn,
2001 &layers[0].hc_attn_scale,
2002 &layers[0].hc_attn_base,
2003 &layers[0].attn_norm,
2004 cfg,
2005 pool,
2006 );
2007 if !crate::gpu_wgpu::dsv4_state_write(state) || !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
2008 {
2009 return false;
2010 }
2011 if st.dev_owned && st.dev_set != active_dev {
2022 let left: Vec<usize> = (0..active_dev.len().min(st.dev_set.len()))
2023 .filter(|&i| st.dev_set[i] && !active_dev[i])
2024 .collect();
2025 if !left.is_empty() {
2026 tracing::warn!("слои {left:?} ушли с карты — кеши на разных сторонах");
2027 return false;
2028 }
2029 }
2033 let chain = chain_enabled();
2034 let layer_cap = {
2042 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
2043 *N.get_or_init(|| {
2044 std::env::var("CMF_DSV4_LAYERS_PROBE")
2045 .ok()
2046 .and_then(|v| v.parse::<usize>().ok())
2047 .unwrap_or(usize::MAX)
2048 })
2049 };
2050 let mut run: Vec<usize> = Vec::new();
2051 let mut sink_out = vec![0.0f32; dim];
2052 let mut state_on_host = true;
2057 for (li, l) in layers.iter().enumerate() {
2058 if li >= layer_cap {
2059 break;
2060 }
2061 if prof::on() {
2067 prof::note_layer(li);
2068 }
2069 if chain && on_dev[li] {
2070 run.push(li);
2075 if run.len() >= chain_max() || dspark_wants(li) {
2080 let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
2081 let captured = *run.last().unwrap();
2082 if !dsv4_chain_run(
2083 layers,
2084 &run,
2085 cfg,
2086 g,
2087 st,
2088 token_id,
2089 &mut folded,
2090 Some(state),
2091 1,
2092 &[],
2093 need_qn,
2094 pool,
2095 ) {
2096 return false;
2097 }
2098 state_on_host = true;
2099 verify_fp("walk", st.pos, captured, state);
2100 dspark_note(captured, state, cfg);
2101 run.clear();
2102 }
2103 continue;
2104 }
2105 if chain && !run.is_empty() {
2106 if !dsv4_chain_run(
2110 layers,
2111 &run,
2112 cfg,
2113 g,
2114 st,
2115 token_id,
2116 &mut folded,
2117 Some(state),
2118 1,
2119 &[],
2120 run[0] == 0 || !on_dev[run[0] - 1],
2121 pool,
2122 ) {
2123 return false;
2124 }
2125 state_on_host = true;
2126 let last = *run.last().unwrap();
2127 verify_fp("walk", st.pos, last, state);
2128 dspark_note(last, state, cfg);
2129 }
2130 run.clear();
2131 if partial_dev[li] && chain1_on() {
2132 if let Some(home) = dsv4_chain1_layer(
2133 state,
2134 &mut folded,
2135 layers,
2136 l,
2137 cfg,
2138 st,
2139 token_id,
2140 li,
2141 freqs_of(l),
2142 pool,
2143 state_on_host,
2144 ) {
2145 st.dev_filled[li] = (st.window[li].len() / cfg.head_dim).min(cfg.window);
2153 st.dev_n_comp[li] = st.compressed[li].len() / cfg.head_dim;
2154 st.dev_n_ix[li] = l.indexer.as_ref().map_or(0, |ix| {
2155 let ih = ix.weights_proj.rows();
2156 let idim = ix.wq_b.rows() / ih.max(1);
2157 st.index_kv[li].len() / idim.max(1)
2158 });
2159 state_on_host = home;
2160 if verify_fp_on(st.pos) {
2161 if !state_on_host && !crate::gpu_wgpu::dsv4_state_read(state) {
2162 return false;
2163 }
2164 state_on_host = true;
2165 verify_fp("walk", st.pos, li, state);
2166 }
2167 if dspark_wants(li) {
2171 if !state_on_host && crate::gpu_wgpu::dsv4_state_read(state) {
2172 state_on_host = true;
2173 }
2174 if state_on_host {
2175 dspark_note(li, state, cfg);
2176 }
2177 }
2178 continue;
2179 }
2180 }
2181 if partial_dev[li] && partial_walk_on() {
2182 let Some(home) = dsv4_partial_layer(
2187 state,
2188 &mut folded,
2189 layers,
2190 l,
2191 cfg,
2192 st,
2193 token_id,
2194 li,
2195 freqs_of(l),
2196 pool,
2197 state_on_host,
2198 ) else {
2199 return false;
2200 };
2201 state_on_host = home;
2202 if home {
2203 dspark_note(li, state, cfg);
2204 }
2205 continue;
2206 }
2207 if !on_dev[li] {
2208 if !state_on_host && !crate::gpu_wgpu::dsv4_state_read(state) {
2209 return false;
2210 }
2211 state_on_host = true;
2212 let freqs = freqs_of(l);
2213 hc_block(
2214 state,
2215 &l.hc_attn_fn,
2216 &l.hc_attn_scale,
2217 &l.hc_attn_base,
2218 &l.attn_norm,
2219 cfg,
2220 scratch,
2221 pool,
2222 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
2223 );
2224 hc_block(
2225 state,
2226 &l.hc_ffn_fn,
2227 &l.hc_ffn_scale,
2228 &l.hc_ffn_base,
2229 &l.ffn_norm,
2230 cfg,
2231 scratch,
2232 pool,
2233 |f, o| {
2240 if host_cpu_moe() {
2241 crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
2242 } else {
2243 moe_step(f, l, cfg, token_id, li, pool, o)
2244 }
2245 },
2246 );
2247 if layers.get(li + 1).is_some() && on_dev.get(li + 1).copied().unwrap_or(false) {
2252 let n = &layers[li + 1];
2253 let (f, p2, c2) = hc_fold_norm(
2254 state,
2255 &n.hc_attn_fn,
2256 &n.hc_attn_scale,
2257 &n.hc_attn_base,
2258 &n.attn_norm,
2259 cfg,
2260 pool,
2261 );
2262 folded = f;
2263 if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2) {
2264 return false;
2265 }
2266 if !crate::gpu_wgpu::dsv4_state_write(state) {
2267 return false;
2268 }
2269 }
2270 dspark_note(li, state, cfg);
2271 continue;
2272 }
2273 let mut prep = AttnPrep::default();
2274 let _tp = prof::on().then(std::time::Instant::now);
2275 attention_step(
2276 &folded,
2277 l,
2278 cfg,
2279 st,
2280 li,
2281 freqs_of(l),
2282 pool,
2283 Some(&mut prep),
2284 &mut sink_out,
2285 );
2286 if let Some(t) = _tp {
2287 prof::PREP_NS.fetch_add(
2288 t.elapsed().as_nanos() as u64,
2289 std::sync::atomic::Ordering::Relaxed,
2290 );
2291 }
2292 let hd = cfg.head_dim;
2294 let n_comp = st.compressed[li].len() / hd;
2295 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2296 let kv_id = st.kv_id;
2297 let _tc = prof::on().then(std::time::Instant::now);
2298 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap)
2299 || (n_comp > 0
2300 && !crate::gpu_wgpu::dsv4_cache_write(
2301 kv_id,
2302 li,
2303 cfg.window * hd,
2304 &st.compressed[li],
2305 cap,
2306 ))
2307 {
2308 return false;
2309 }
2310 if let Some(t) = _tc {
2311 prof::CACHEW_NS.fetch_add(
2312 t.elapsed().as_nanos() as u64,
2313 std::sync::atomic::Ordering::Relaxed,
2314 );
2315 }
2316 let idx32: Vec<u32> = prep
2317 .idxs
2318 .iter()
2319 .map(|&p| {
2320 if p < prep.win_len {
2321 p as u32
2322 } else {
2323 (cfg.window + (p - prep.win_len)) as u32
2324 }
2325 })
2326 .collect();
2327 let Some(pk) = pack_for(l, cfg, li) else {
2328 return false;
2329 };
2330 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
2331 l.wq_a.model_idx(),
2332 l.wq_b.model_idx(),
2333 l.wo_a.model_idx(),
2334 l.wo_b.model_idx(),
2335 ) else {
2336 return false;
2337 };
2338 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
2339 return false;
2340 };
2341 let forced: Option<Vec<usize>> = l.tid2eid.as_ref().and_then(|tbl| {
2342 let v: Vec<usize> = if pk.needs_remap() {
2343 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
2344 } else {
2345 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
2346 .into_iter()
2347 .map(|gi| pk.to_slot[gi])
2348 .collect()
2349 };
2350 if v.contains(&usize::MAX) {
2351 None
2352 } else {
2353 Some(v)
2354 }
2355 });
2356 if l.tid2eid.is_some() && forced.is_none() {
2357 return false;
2358 }
2359 let nxt = layers.get(li + 1);
2360 let w = crate::gpu_wgpu::Dsv4LayerW {
2361 attn: crate::gpu_wgpu::Dsv4AttnW {
2362 wq_a,
2363 wq_b,
2364 wo_a,
2365 wo_b,
2366 q_norm: &l.q_norm,
2367 sink: &l.attn_sink,
2368 },
2369 moe: crate::gpu_wgpu::Dsv4MoeW {
2370 router: &[],
2371 experts: &pk.tensors,
2372 logits: &[],
2373 bias: pk.bias.as_deref(),
2380 mask: pk.mask.as_deref(),
2381 forced: forced.as_deref(),
2382 remap: pk.needs_remap().then_some(pk.remap.as_slice()),
2383 global: None,
2384 has_shared: true,
2385 shared_weight: 1.0,
2386 preweighted: false,
2387 qwen_softmax: false,
2388 },
2389 hc_ffn_fn: &l.hc_ffn_fn,
2390 hc_ffn_scale: &l.hc_ffn_scale,
2391 hc_ffn_base: &l.hc_ffn_base,
2392 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2393 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2394 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2395 ffn_norm: &l.ffn_norm,
2396 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2397 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2398 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2399 router: &pk.router,
2400 };
2401 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2402 attn: crate::gpu_wgpu::Dsv4AttnGeom {
2403 dim,
2404 nh: cfg.n_heads,
2405 hd,
2406 rd: cfg.rope_head_dim,
2407 q_lora: cfg.q_lora_rank,
2408 o_lora: cfg.o_lora_rank,
2409 o_groups: cfg.o_groups,
2410 eps: cfg.norm_eps,
2411 scale: (hd as f32).powf(-0.5),
2412 },
2413 moe: crate::gpu_wgpu::Dsv4MoeGeom {
2414 hidden: dim,
2415 inter: cfg.moe_inter,
2416 top_k: cfg.top_k,
2417 route_scale: cfg.route_scale,
2418 swiglu_limit: cfg.swiglu_limit,
2419 gu_q2: l.experts.first().is_some_and(|e| {
2420 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
2421 }),
2422 },
2423 hc: cfg.hc_mult,
2424 hc_eps: cfg.hc_eps,
2425 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2426 };
2427 let mut next = vec![0.0f32; dim];
2428 if !crate::gpu_wgpu::dsv4_layer_frame(
2429 &model,
2430 &w,
2431 geom,
2432 kv_id,
2433 li,
2434 Some(&prep.qr),
2435 &idx32,
2436 freqs_of(l),
2437 st.pos,
2438 &mut next,
2439 None,
2440 None,
2441 &mut Vec::new(),
2442 ) {
2443 return false;
2444 }
2445 state_on_host = false;
2446 folded = next;
2447 dspark_note(li, state, cfg);
2448 }
2449 let mut state_home = false;
2450 if chain {
2451 if !run.is_empty() {
2452 let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
2456 let last_on_dev = *on_dev.last().unwrap_or(&false);
2457 let carry = last_on_dev && run.last() == Some(&(layers.len() - 1));
2458 let ok = if carry {
2459 let r = dsv4_chain_run(
2460 layers,
2461 &run,
2462 cfg,
2463 g,
2464 st,
2465 token_id,
2466 &mut folded,
2467 Some(state),
2468 1,
2469 &[],
2470 need_qn,
2471 pool,
2472 );
2473 state_home = r;
2474 state_on_host = r;
2475 if r {
2476 dspark_note(*run.last().unwrap(), state, cfg);
2477 }
2478 r
2479 } else {
2480 let r = dsv4_chain_run(
2481 layers,
2482 &run,
2483 cfg,
2484 g,
2485 st,
2486 token_id,
2487 &mut folded,
2488 None,
2489 1,
2490 &[],
2491 need_qn,
2492 pool,
2493 );
2494 if r {
2495 state_on_host = false;
2496 }
2497 r
2498 };
2499 if !ok {
2500 return false;
2501 }
2502 }
2503 if st.dev_set.is_empty() {
2504 st.dev_set = active_dev.clone();
2505 st.partial_set = partial_dev.clone();
2506 let mut idxs = Vec::new();
2512 for (li, l) in layers.iter().enumerate() {
2513 if !active_dev.get(li).copied().unwrap_or(false) {
2514 continue;
2515 }
2516 for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b, &l.gate] {
2517 idxs.extend(t.model_idx());
2518 }
2519 if let Some(pk) = pack_for(l, cfg, li) {
2520 for &(a, b, c) in &pk.tensors {
2521 idxs.extend([a, b, c]);
2522 }
2523 }
2524 }
2525 for (li, l) in layers.iter().enumerate() {
2533 if active_dev.get(li).copied().unwrap_or(false) {
2534 continue;
2535 }
2536 let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
2537 tracing::info!(
2538 "слой {li} на хосте: упаковано {packed} экспертов из {}",
2539 cfg.n_routed_experts
2540 );
2541 }
2542 let pinned = layers
2543 .iter()
2544 .find_map(|l| l.experts.first().and_then(|e| e.w1.model_arc()))
2545 .map_or(0, |m| crate::gpu_wgpu::pin_weights(&m, &idxs));
2546 tracing::info!(
2547 "закреплено на карте: {pinned} тензоров {} слоёв",
2548 on_dev.iter().filter(|&&x| x).count()
2549 );
2550 }
2551 }
2552 if state_home || state_on_host {
2553 return true;
2554 }
2555 crate::gpu_wgpu::dsv4_state_read(state)
2556}
2557
2558#[cfg(feature = "gpu")]
2563#[allow(clippy::too_many_arguments)]
2564fn dsv4_partial_layer(
2565 state: &mut [f32],
2566 folded: &mut Vec<f32>,
2567 layers: &[Dsv4Layer],
2568 l: &Dsv4Layer,
2569 cfg: &Dsv4Cfg,
2570 st: &mut Dsv4State,
2571 token_id: u32,
2572 li: usize,
2573 freqs: &[f32],
2574 pool: Option<&crate::pool::Pool>,
2575 state_on_host: bool,
2576) -> Option<bool> {
2577 let dim = cfg.dim;
2578 if state_on_host {
2587 let (f, post, comb) = hc_fold_norm(
2588 state,
2589 &l.hc_attn_fn,
2590 &l.hc_attn_scale,
2591 &l.hc_attn_base,
2592 &l.attn_norm,
2593 cfg,
2594 pool,
2595 );
2596 *folded = f;
2597 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2598 || !crate::gpu_wgpu::dsv4_state_write(state)
2599 {
2600 return None;
2601 }
2602 }
2603 let mut prep = AttnPrep::default();
2604 let mut sink = vec![0.0f32; dim];
2605 attention_step(
2606 folded,
2607 l,
2608 cfg,
2609 st,
2610 li,
2611 freqs,
2612 pool,
2613 Some(&mut prep),
2614 &mut sink,
2615 );
2616 let hd = cfg.head_dim;
2617 let n_comp = st.compressed[li].len() / hd;
2618 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2619 if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
2620 || (n_comp > 0
2621 && !crate::gpu_wgpu::dsv4_cache_write(
2622 st.kv_id,
2623 li,
2624 cfg.window * hd,
2625 &st.compressed[li],
2626 cap,
2627 ))
2628 {
2629 return None;
2630 }
2631 let a_tail = crate::gpu_wgpu::Dsv4HcTail {
2632 fn_: &l.hc_ffn_fn,
2633 scale: &l.hc_ffn_scale,
2634 base: &l.hc_ffn_base,
2635 norm: &l.ffn_norm,
2636 hc: cfg.hc_mult,
2637 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2638 hc_eps: cfg.hc_eps,
2639 eps: cfg.norm_eps,
2640 };
2641 let scale = (cfg.head_dim as f32).powf(-0.5);
2642 let mut ffn_input = if cpu_overlap_on() {
2648 vec![0.0f32; dim]
2649 } else {
2650 Vec::new()
2651 };
2652 if !attn_frame(
2653 l,
2654 cfg,
2655 st,
2656 li,
2657 folded,
2658 &prep.qr,
2659 &prep.idxs,
2660 freqs,
2661 st.pos,
2662 prep.win_len,
2663 scale,
2664 Some(&a_tail),
2665 &mut ffn_input,
2666 ) {
2667 return None;
2668 }
2669 let nxt = layers.get(li + 1);
2670 let forced = l
2671 .tid2eid
2672 .as_ref()
2673 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
2674 let mut next = vec![0.0f32; dim];
2675 let (cold_sum, cold_count) = moe_frame(
2676 &ffn_input,
2677 l,
2678 cfg,
2679 li,
2680 &[],
2681 forced.as_deref(),
2682 pool,
2683 Some(&a_tail),
2684 None,
2689 &mut next,
2690 )?;
2691 let state_ok = if cold_count == 0 {
2697 crate::gpu_wgpu::dsv4_state_read(state)
2698 } else {
2699 crate::gpu_wgpu::dsv4_state_add_cold(&cold_sum, cfg.hc_mult, state)
2700 };
2701 if !state_ok {
2702 return None;
2703 }
2704 if let Some(n) = nxt {
2705 let (f, post, comb) = hc_fold_norm(
2706 state,
2707 &n.hc_attn_fn,
2708 &n.hc_attn_scale,
2709 &n.hc_attn_base,
2710 &n.attn_norm,
2711 cfg,
2712 pool,
2713 );
2714 *folded = f;
2715 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2716 || !crate::gpu_wgpu::dsv4_state_write(state)
2717 {
2718 return None;
2719 }
2720 }
2721 Some(true)
2726}
2727
2728#[cfg(feature = "gpu")]
2735fn chain1_on() -> bool {
2736 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2737 *ON.get_or_init(|| {
2738 std::env::var("CMF_DSV4_CHAIN1")
2739 .map(|v| v != "0")
2740 .unwrap_or(true)
2741 })
2742}
2743
2744#[cfg(feature = "gpu")]
2750fn cpu_overlap_on() -> bool {
2751 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2752 *ON.get_or_init(|| std::env::var("CMF_DSV4_CPU_OVERLAP").is_ok_and(|v| v != "0"))
2753}
2754
2755#[cfg(feature = "gpu")]
2756#[allow(clippy::too_many_arguments)]
2757fn dsv4_chain1_layer(
2758 state: &mut [f32],
2759 folded: &mut Vec<f32>,
2760 layers: &[Dsv4Layer],
2761 l: &Dsv4Layer,
2762 cfg: &Dsv4Cfg,
2763 st: &mut Dsv4State,
2764 token_id: u32,
2765 li: usize,
2766 freqs: &[f32],
2767 pool: Option<&crate::pool::Pool>,
2768 state_on_host: bool,
2769) -> Option<bool> {
2770 let dim = cfg.dim;
2771 let pk = pack_for(l, cfg, li)?;
2772 if pk.route_complete() {
2773 return None;
2774 }
2775 let model = l.experts.first().and_then(|e| e.w1.model_arc())?;
2776 if state_on_host {
2780 let (f, post, comb) = hc_fold_norm(
2781 state,
2782 &l.hc_attn_fn,
2783 &l.hc_attn_scale,
2784 &l.hc_attn_base,
2785 &l.attn_norm,
2786 cfg,
2787 pool,
2788 );
2789 *folded = f;
2790 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2791 || !crate::gpu_wgpu::dsv4_state_write(state)
2792 {
2793 return None;
2794 }
2795 }
2796 let mut prep = AttnPrep::default();
2797 let mut sink = vec![0.0f32; dim];
2798 attention_step(
2799 folded,
2800 l,
2801 cfg,
2802 st,
2803 li,
2804 freqs,
2805 pool,
2806 Some(&mut prep),
2807 &mut sink,
2808 );
2809 let hd = cfg.head_dim;
2810 let n_comp = st.compressed[li].len() / hd;
2811 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2812 let kv_id = st.kv_id;
2813 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap)
2814 || (n_comp > 0
2815 && !crate::gpu_wgpu::dsv4_cache_write(
2816 kv_id,
2817 li,
2818 cfg.window * hd,
2819 &st.compressed[li],
2820 cap,
2821 ))
2822 {
2823 return None;
2824 }
2825 let idx32: Vec<u32> = prep
2826 .idxs
2827 .iter()
2828 .map(|&p| {
2829 if p < prep.win_len {
2830 p as u32
2831 } else {
2832 (cfg.window + (p - prep.win_len)) as u32
2833 }
2834 })
2835 .collect();
2836 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
2837 l.wq_a.model_idx(),
2838 l.wq_b.model_idx(),
2839 l.wo_a.model_idx(),
2840 l.wo_b.model_idx(),
2841 ) else {
2842 return None;
2843 };
2844 let forced: Option<Vec<usize>> = l
2847 .tid2eid
2848 .as_ref()
2849 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
2850 let global_remap = pk
2851 .global
2852 .as_ref()
2853 .map(|gl| gl.pool.remap(gl.layer, cfg.n_routed_experts));
2854 let dynv = pk.dynslots.lock().unwrap();
2855 let live_remap = global_remap.as_deref().unwrap_or(dynv.remap.as_slice());
2856 let nxt = layers.get(li + 1);
2857 let w = crate::gpu_wgpu::Dsv4LayerW {
2858 attn: crate::gpu_wgpu::Dsv4AttnW {
2859 wq_a,
2860 wq_b,
2861 wo_a,
2862 wo_b,
2863 q_norm: &l.q_norm,
2864 sink: &l.attn_sink,
2865 },
2866 moe: crate::gpu_wgpu::Dsv4MoeW {
2867 router: &[],
2868 experts: &pk.tensors,
2869 logits: &[],
2870 bias: pk.bias.as_deref(),
2873 mask: pk.mask.as_deref(),
2874 forced: forced.as_deref(),
2875 remap: Some(live_remap),
2876 global: pk.global.as_ref().map(|gl| crate::gpu_wgpu::Dsv4GlobalMoe {
2877 pool_uid: gl.pool.uid,
2878 shared_slot: gl.shared_slot,
2879 segment_slots: gl.pool.segment_slots as u32,
2880 }),
2881 has_shared: true,
2882 shared_weight: 1.0,
2883 preweighted: false,
2884 qwen_softmax: false,
2885 },
2886 hc_ffn_fn: &l.hc_ffn_fn,
2887 hc_ffn_scale: &l.hc_ffn_scale,
2888 hc_ffn_base: &l.hc_ffn_base,
2889 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2890 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2891 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2892 ffn_norm: &l.ffn_norm,
2893 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2894 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2895 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2896 router: &pk.router,
2897 };
2898 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2899 attn: crate::gpu_wgpu::Dsv4AttnGeom {
2900 dim,
2901 nh: cfg.n_heads,
2902 hd,
2903 rd: cfg.rope_head_dim,
2904 q_lora: cfg.q_lora_rank,
2905 o_lora: cfg.o_lora_rank,
2906 o_groups: cfg.o_groups,
2907 eps: cfg.norm_eps,
2908 scale: (hd as f32).powf(-0.5),
2909 },
2910 moe: crate::gpu_wgpu::Dsv4MoeGeom {
2911 hidden: dim,
2912 inter: cfg.moe_inter,
2913 top_k: cfg.top_k,
2914 route_scale: cfg.route_scale,
2915 swiglu_limit: cfg.swiglu_limit,
2916 gu_q2: l
2917 .experts
2918 .first()
2919 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
2920 },
2921 hc: cfg.hc_mult,
2922 hc_eps: cfg.hc_eps,
2923 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2924 };
2925 let mut next = vec![0.0f32; dim];
2926 let mut cold: Vec<(usize, f32)> = Vec::new();
2927 let mut routed: Vec<(usize, f32)> = Vec::new();
2928 let mut cold_x: Vec<f32> = Vec::new();
2929 let need_routes = route_stats_on() || pk.global.is_some();
2930 if !crate::gpu_wgpu::dsv4_layer_frame(
2931 &model,
2932 &w,
2933 geom,
2934 kv_id,
2935 li,
2936 Some(&prep.qr),
2937 &idx32,
2938 freqs,
2939 st.pos,
2940 &mut next,
2941 Some(&mut cold),
2942 need_routes.then_some(&mut routed),
2943 &mut cold_x,
2944 ) {
2945 return None;
2946 }
2947 if route_stats_on() {
2948 record_route(li, layers.len(), cfg.n_routed_experts, &routed);
2949 }
2950 drop(dynv);
2951 if let Some(gl) = pk.global.as_ref() {
2956 let picks: Vec<usize> = routed.iter().map(|&(gi, _)| gi).collect();
2957 gl.pool
2958 .ensure_picks(&model, gl.layer, &picks, &l.experts, cfg.top_k, 1);
2959 }
2960 if cold.is_empty() {
2961 *folded = next;
2965 return Some(false);
2966 }
2967 if cold_x.len() < dim {
2970 return None;
2971 }
2972 let mut cold_sum = vec![0.0f32; dim];
2973 {
2974 let results: Vec<std::sync::Mutex<Vec<f32>>> = cold
2975 .iter()
2976 .map(|_| std::sync::Mutex::new(Vec::new()))
2977 .collect();
2978 let (cold_ref, results_ref, x_ref) = (&cold, &results, &cold_x[..dim]);
2979 std::thread::scope(|sc| {
2980 for i in 0..cold_ref.len() {
2981 let (gi, wt) = cold_ref[i];
2982 let Some(exp) = l.experts.get(gi) else {
2983 continue;
2984 };
2985 let r = &results_ref[i];
2986 sc.spawn(move || {
2987 let mut a = vec![0.0f32; cfg.dim];
2988 crate::gpu::cpu_scope(|| run_expert(x_ref, exp, cfg, wt, None, &mut a));
2989 *r.lock().unwrap() = a;
2990 });
2991 }
2992 });
2993 for r in &results {
2994 let a = r.lock().unwrap();
2995 for (o, v) in cold_sum.iter_mut().zip(a.iter()) {
2996 *o += v;
2997 }
2998 }
2999 }
3000 if !crate::gpu_wgpu::dsv4_state_add_cold_preserved(&cold_sum, cfg.hc_mult, state, kv_id, li) {
3001 return None;
3002 }
3003 if pk.global.is_none() {
3006 let mut dynv = pk.dynslots.lock().unwrap();
3007 dynv.clock += 1;
3008 let clock = dynv.clock;
3009 for &(gi, _) in &cold {
3010 if gi >= dynv.remap.len() || dynv.remap[gi] != u32::MAX {
3011 continue;
3012 }
3013 let victim = (0..dynv.owner.len())
3014 .filter(|&sl| dynv.last[sl] != clock)
3015 .min_by_key(|&sl| dynv.last[sl]);
3016 let Some(victim) = victim else { break };
3017 let Some(exp) = l.experts.get(gi) else {
3018 continue;
3019 };
3020 let t3 = (|| {
3021 Some((
3022 exp.w1.model_idx()?,
3023 exp.w3.model_idx()?,
3024 exp.w2.model_idx()?,
3025 ))
3026 })();
3027 let Some(t3) = t3 else { continue };
3028 let gu_q2 = exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
3029 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
3030 if !crate::gpu_wgpu::dsv4_slot_fill(
3031 &model,
3032 pack_first,
3033 victim,
3034 gi,
3035 t3,
3036 cfg.moe_inter,
3037 cfg.dim,
3038 gu_q2,
3039 ) {
3040 break;
3041 }
3042 let old = dynv.owner[victim] as usize;
3043 if old < dynv.remap.len() {
3044 dynv.remap[old] = u32::MAX;
3045 }
3046 dynv.remap[gi] = victim as u32;
3047 dynv.owner[victim] = gi as u32;
3048 dynv.last[victim] = clock;
3049 dynv.mutated = true;
3050 }
3051 }
3052 if let Some(n) = nxt {
3053 let (f, post, comb) = hc_fold_norm(
3054 state,
3055 &n.hc_attn_fn,
3056 &n.hc_attn_scale,
3057 &n.hc_attn_base,
3058 &n.attn_norm,
3059 cfg,
3060 pool,
3061 );
3062 *folded = f;
3063 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb) {
3064 return None;
3065 }
3066 }
3067 Some(true)
3068}
3069
3070#[cfg(feature = "gpu")]
3071fn chain_max() -> usize {
3072 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
3073 *N.get_or_init(|| {
3074 std::env::var("CMF_DSV4_CHAIN_MAX")
3075 .ok()
3076 .and_then(|v| v.parse().ok())
3077 .unwrap_or(usize::MAX)
3078 })
3079}
3080
3081#[cfg(feature = "gpu")]
3084fn partial_walk_on() -> bool {
3095 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3096 *ON.get_or_init(|| std::env::var("CMF_DSV4_PARTIAL_WALK").is_ok_and(|v| v != "0"))
3097}
3098
3099fn host_cpu_moe() -> bool {
3100 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3101 *ON.get_or_init(|| std::env::var("CMF_DSV4_HOST_CPU_MOE").is_ok_and(|v| v != "0"))
3102}
3103
3104fn chain_enabled() -> bool {
3105 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3106 *ON.get_or_init(|| {
3107 std::env::var("CMF_DSV4_CHAIN")
3108 .map(|v| v != "0")
3109 .unwrap_or(true)
3110 })
3111}
3112
3113#[cfg(feature = "gpu")]
3123#[allow(clippy::too_many_arguments)]
3124fn dsv4_chain_run(
3125 layers: &[Dsv4Layer],
3126 run: &[usize],
3127 cfg: &Dsv4Cfg,
3128 g: &Dsv4Globals,
3129 st: &mut Dsv4State,
3130 token_id: u32,
3131 folded: &mut Vec<f32>,
3137 state_out: Option<&mut [f32]>,
3142 batch: usize,
3145 batch_ids: &[u32],
3149 need_qn: bool,
3156 pool: Option<&crate::pool::Pool>,
3157) -> bool {
3158 if run.is_empty() {
3159 return true;
3160 }
3161 let (dim, hd) = (cfg.dim, cfg.head_dim);
3162 let first = run[0];
3163 let Some(model) = layers[first].experts.first().and_then(|e| e.w1.model_arc()) else {
3164 return false;
3165 };
3166 if batch <= 1 && need_qn {
3170 let mut qn0 = vec![0.0f32; cfg.q_lora_rank];
3171 layers[first].wq_a.matvec(folded, &mut qn0, pool);
3172 rms_weighted(&mut qn0, &layers[first].q_norm, cfg.norm_eps);
3173 if !crate::gpu_wgpu::dsv4_chain_seed(folded, &qn0) {
3174 return false;
3175 }
3176 } else if batch <= 1 && !crate::gpu_wgpu::dsv4_chain_seed_fold(folded) {
3177 return false;
3178 }
3179
3180 let mut packs = Vec::with_capacity(run.len());
3182 let mut forceds: Vec<Option<Vec<usize>>> = Vec::with_capacity(run.len());
3183 for &li in run {
3184 let Some(pk) = pack_for(&layers[li], cfg, li) else {
3185 return false;
3186 };
3187 if !pk.route_complete() || pk.is_mutated() {
3194 return false;
3195 }
3196 let forced: Option<Vec<usize>> = layers[li].tid2eid.as_ref().and_then(|tbl| {
3197 let v: Vec<usize> = if pk.needs_remap() {
3198 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
3199 } else {
3200 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
3201 .into_iter()
3202 .map(|gi| pk.to_slot[gi])
3203 .collect()
3204 };
3205 if v.contains(&usize::MAX) {
3206 None
3207 } else {
3208 Some(v)
3209 }
3210 });
3211 if layers[li].tid2eid.is_some() && forced.is_none() {
3212 return false;
3213 }
3214 forceds.push(forced);
3215 packs.push(pk);
3216 }
3217
3218 let mut items = Vec::with_capacity(run.len());
3219 let mut freqs = Vec::with_capacity(run.len());
3220 for (i, &li) in run.iter().enumerate() {
3221 let l = &layers[li];
3222 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
3223 l.wq_a.model_idx(),
3224 l.wq_b.model_idx(),
3225 l.wo_a.model_idx(),
3226 l.wo_b.model_idx(),
3227 l.wkv.model_idx(),
3228 ) else {
3229 return false;
3230 };
3231 let comp = match &l.compressor {
3232 None => None,
3233 Some(cp) => {
3234 let (Some(a), Some(b)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
3235 return false;
3236 };
3237 Some((
3238 crate::gpu_wgpu::Dsv4CompW {
3239 wkv: a,
3240 wgate: b,
3241 norm: &cp.norm,
3242 ape: &cp.ape,
3243 },
3244 crate::gpu_wgpu::Dsv4CompGeom {
3245 width: cp.wkv.rows(),
3246 hidden: dim,
3247 ratio: cp.ratio,
3248 overlap: cp.overlap,
3249 rope_dim: cfg.rope_head_dim,
3250 eps: cfg.norm_eps,
3251 },
3252 ))
3253 }
3254 };
3255 let ix = match &l.indexer {
3256 None => None,
3257 Some(ixr) => {
3258 let cp = &ixr.compressor;
3259 let (Some(a), Some(b), Some(qb), Some(wp)) = (
3260 cp.wkv.model_idx(),
3261 cp.wgate.model_idx(),
3262 ixr.wq_b.model_idx(),
3263 ixr.weights_proj.model_idx(),
3264 ) else {
3265 return false;
3266 };
3267 let ih = ixr.weights_proj.rows();
3268 Some((
3269 crate::gpu_wgpu::Dsv4CompW {
3270 wkv: a,
3271 wgate: b,
3272 norm: &cp.norm,
3273 ape: &cp.ape,
3274 },
3275 crate::gpu_wgpu::Dsv4CompGeom {
3276 width: cp.wkv.rows(),
3277 hidden: dim,
3278 ratio: cp.ratio,
3279 overlap: cp.overlap,
3280 rope_dim: cfg.rope_head_dim,
3281 eps: cfg.norm_eps,
3282 },
3283 crate::gpu_wgpu::Dsv4IxW {
3284 wq_b: qb,
3285 weights_proj: wp,
3286 },
3287 crate::gpu_wgpu::Dsv4IxGeom {
3288 ih,
3289 idim: ixr.wq_b.rows() / ih.max(1),
3290 q_lora: cfg.q_lora_rank,
3291 hidden: dim,
3292 rope_dim: cfg.rope_head_dim,
3293 eps: cfg.norm_eps,
3294 top_k: cfg.index_topk,
3295 window: cfg.window,
3296 },
3297 ))
3298 }
3299 };
3300 let ew_c0 = l.compressor.as_ref().map_or(0, |cp| {
3304 if cp.overlap {
3305 cp.wkv.rows() / 2
3306 } else {
3307 cp.wkv.rows()
3308 }
3309 });
3310 let comp_extra = l
3311 .compressor
3312 .as_ref()
3313 .map_or(0, |cp| batch.max(1).div_ceil(cp.ratio.max(1)));
3314 let need = cfg.window * hd
3315 + (st.dev_n_comp[li] + comp_extra + 1) * ew_c0.max(1)
3316 + (batch.max(1) + 1) * hd;
3317 if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
3318 return false;
3319 }
3320 let ew_c = comp.as_ref().map_or(
3321 0,
3322 |(_, cg)| {
3323 if cg.overlap { cg.width / 2 } else { cg.width }
3324 },
3325 );
3326 let ew_i = ix.as_ref().map_or(
3327 0,
3328 |(_, cg, _, _)| {
3329 if cg.overlap { cg.width / 2 } else { cg.width }
3330 },
3331 );
3332 let prep = crate::gpu_wgpu::Dsv4Prep {
3333 wkv,
3334 kv_norm: &l.kv_norm,
3335 comp,
3336 ix,
3337 filled: st.dev_filled[li],
3338 window: cfg.window,
3339 n_comp: st.dev_n_comp[li],
3340 n_ix: st.dev_n_ix[li],
3341 comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
3342 ix_dst_off: st.dev_n_ix[li] * ew_i,
3343 idx_cap: cfg.window
3344 + if l.indexer.is_some() {
3345 cfg.index_topk
3346 } else {
3347 st.dev_n_comp[li] + comp_extra + 1
3348 },
3349 };
3350 let nxt = layers.get(li + 1);
3351 let w = crate::gpu_wgpu::Dsv4LayerW {
3352 attn: crate::gpu_wgpu::Dsv4AttnW {
3353 wq_a,
3354 wq_b,
3355 wo_a,
3356 wo_b,
3357 q_norm: &l.q_norm,
3358 sink: &l.attn_sink,
3359 },
3360 moe: crate::gpu_wgpu::Dsv4MoeW {
3361 router: &packs[i].router,
3362 experts: &packs[i].tensors,
3363 logits: &[],
3364 bias: packs[i].bias.as_deref(),
3367 mask: packs[i].mask.as_deref(),
3368 forced: forceds[i].as_deref(),
3369 remap: packs[i].needs_remap().then_some(packs[i].remap.as_slice()),
3370 global: None,
3371 has_shared: true,
3372 shared_weight: 1.0,
3373 preweighted: false,
3374 qwen_softmax: false,
3375 },
3376 hc_ffn_fn: &l.hc_ffn_fn,
3377 hc_ffn_scale: &l.hc_ffn_scale,
3378 hc_ffn_base: &l.hc_ffn_base,
3379 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
3380 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
3381 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
3382 ffn_norm: &l.ffn_norm,
3383 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
3384 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
3385 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
3386 router: &packs[i].router,
3387 };
3388 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
3389 attn: crate::gpu_wgpu::Dsv4AttnGeom {
3390 dim,
3391 nh: cfg.n_heads,
3392 hd,
3393 rd: cfg.rope_head_dim,
3394 q_lora: cfg.q_lora_rank,
3395 o_lora: cfg.o_lora_rank,
3396 o_groups: cfg.o_groups,
3397 eps: cfg.norm_eps,
3398 scale: (hd as f32).powf(-0.5),
3399 },
3400 moe: crate::gpu_wgpu::Dsv4MoeGeom {
3401 hidden: dim,
3402 inter: cfg.moe_inter,
3403 top_k: cfg.top_k,
3404 route_scale: cfg.route_scale,
3405 swiglu_limit: cfg.swiglu_limit,
3406 gu_q2: l.experts.first().is_some_and(|e| {
3407 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
3408 }),
3409 },
3410 hc: cfg.hc_mult,
3411 hc_eps: cfg.hc_eps,
3412 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3413 };
3414 freqs.push(if l.compressor.is_some() {
3415 g.inv_freq_compress.as_slice()
3416 } else {
3417 g.inv_freq_window.as_slice()
3418 });
3419 items.push((w, geom, prep));
3420 }
3421
3422 let mut out = vec![0.0f32; dim * batch.max(1)];
3423 if batch > 1 {
3424 let mut forced_pt: Vec<Vec<Option<Vec<usize>>>> = Vec::with_capacity(batch);
3429 for t in 0..batch {
3430 let id = batch_ids.get(t).copied().unwrap_or(token_id);
3431 let mut row = Vec::with_capacity(run.len());
3432 for (i, &li) in run.iter().enumerate() {
3433 row.push(layers[li].tid2eid.as_ref().and_then(|tbl| {
3434 let v: Vec<usize> = if packs[i].needs_remap() {
3435 hash_route(tbl, cfg.vocab, cfg.top_k, id)
3436 } else {
3437 hash_route(tbl, cfg.vocab, cfg.top_k, id)
3438 .into_iter()
3439 .map(|gi| packs[i].to_slot[gi])
3440 .collect()
3441 };
3442 if v.contains(&usize::MAX) {
3443 None
3444 } else {
3445 Some(v)
3446 }
3447 }));
3448 if layers[li].tid2eid.is_some() && row[i].is_none() {
3449 return false;
3450 }
3451 }
3452 forced_pt.push(row);
3453 }
3454 if !crate::gpu_wgpu::dsv4_chain_batch(
3455 &model,
3456 &items,
3457 st.kv_id,
3458 first,
3459 &freqs,
3460 st.pos,
3461 batch,
3462 Some(&forced_pt),
3463 &mut out,
3464 state_out,
3465 ) {
3466 return false;
3467 }
3468 *folded = out[(batch - 1) * dim..batch * dim].to_vec();
3471 } else {
3472 if !crate::gpu_wgpu::dsv4_layer_chain(
3473 &model, &items, st.kv_id, first, &freqs, st.pos, &mut out, state_out,
3474 ) {
3475 return false;
3476 }
3477 *folded = out;
3478 }
3479 for (i, &li) in run.iter().enumerate() {
3483 for t in 0..batch.max(1) {
3484 let pos = st.pos + t;
3485 st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
3486 if let Some((_, cg, ..)) = items[i].2.ix.as_ref() {
3487 if (pos + 1) % cg.ratio == 0 {
3488 st.dev_n_ix[li] += 1;
3489 }
3490 }
3491 if let Some((_, cg)) = items[i].2.comp.as_ref() {
3492 if (pos + 1) % cg.ratio == 0 {
3493 st.dev_n_comp[li] += 1;
3494 }
3495 }
3496 }
3497 }
3498 st.dev_owned = true;
3499 true
3500}
3501
3502#[cfg(feature = "gpu")]
3504fn hc_on_device() -> bool {
3505 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3506 *ON.get_or_init(|| {
3507 std::env::var("CMF_DSV4_HC_DEV").is_ok_and(|v| v != "0") && crate::gpu::backend_available()
3513 })
3514}
3515
3516#[cfg(feature = "gpu")]
3525#[allow(clippy::too_many_arguments)]
3526fn dsv4_two_frame_loop(
3527 state: &mut [f32],
3528 layers: &[Dsv4Layer],
3529 g: &Dsv4Globals,
3530 cfg: &Dsv4Cfg,
3531 st: &mut Dsv4State,
3532 token_id: u32,
3533 inv_freq: &[f32],
3534 pool: Option<&crate::pool::Pool>,
3535 scratch: &mut HcScratch,
3536) -> bool {
3537 let dim = cfg.dim;
3538 let freqs_of = |l: &Dsv4Layer| -> &[f32] {
3539 let f = if l.compressor.is_some() {
3540 &g.inv_freq_compress
3541 } else {
3542 &g.inv_freq_window
3543 };
3544 if f.is_empty() { inv_freq } else { f.as_slice() }
3545 };
3546 let (mut folded, post0, comb0) = hc_fold_norm(
3548 state,
3549 &layers[0].hc_attn_fn,
3550 &layers[0].hc_attn_scale,
3551 &layers[0].hc_attn_base,
3552 &layers[0].attn_norm,
3553 cfg,
3554 pool,
3555 );
3556 if !crate::gpu_wgpu::dsv4_state_write(state) || !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
3557 {
3558 return false;
3559 }
3560 let mut on_dev = vec![false; layers.len()];
3565 for (li, l) in layers.iter().enumerate() {
3566 let Some(pk) = pack_for(l, cfg, li) else {
3567 return false;
3568 };
3569 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
3570 return false;
3571 };
3572 let gu_q2 = l
3573 .experts
3574 .first()
3575 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
3576 let attn_ok = [
3577 l.wq_a.model_idx(),
3578 l.wq_b.model_idx(),
3579 l.wo_a.model_idx(),
3580 l.wo_b.model_idx(),
3581 ]
3582 .into_iter()
3583 .flatten()
3584 .all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
3585 on_dev[li] = attn_ok
3586 && pk.route_complete()
3587 && crate::gpu_wgpu::dsv4_experts_ready(
3588 &model,
3589 &pk.tensors,
3590 cfg.moe_inter,
3591 dim,
3592 gu_q2,
3593 l.experts.first().is_some_and(|e| {
3594 e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
3595 }),
3596 );
3597 }
3598 if !on_dev.iter().any(|&x| x) {
3599 return false;
3600 }
3601 let mut sink = vec![0.0f32; dim];
3602 for (li, l) in layers.iter().enumerate() {
3603 if !on_dev[li] {
3607 if !crate::gpu_wgpu::dsv4_state_read(state) {
3608 return false;
3609 }
3610 let freqs = freqs_of(l);
3611 hc_block(
3612 state,
3613 &l.hc_attn_fn,
3614 &l.hc_attn_scale,
3615 &l.hc_attn_base,
3616 &l.attn_norm,
3617 cfg,
3618 scratch,
3619 pool,
3620 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
3621 );
3622 hc_block(
3623 state,
3624 &l.hc_ffn_fn,
3625 &l.hc_ffn_scale,
3626 &l.hc_ffn_base,
3627 &l.ffn_norm,
3628 cfg,
3629 scratch,
3630 pool,
3631 |f, o| moe_step(f, l, cfg, token_id, li, pool, o),
3632 );
3633 let nref = layers.get(li + 1).unwrap_or(l);
3634 let (f, p2, c2) = hc_fold_norm(
3635 state,
3636 &nref.hc_attn_fn,
3637 &nref.hc_attn_scale,
3638 &nref.hc_attn_base,
3639 &nref.attn_norm,
3640 cfg,
3641 pool,
3642 );
3643 folded = f;
3644 if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2)
3645 || !crate::gpu_wgpu::dsv4_state_write(state)
3646 {
3647 return false;
3648 }
3649 continue;
3650 }
3651 let mut prep = AttnPrep::default();
3653 attention_step(
3654 &folded,
3655 l,
3656 cfg,
3657 st,
3658 li,
3659 freqs_of(l),
3660 pool,
3661 Some(&mut prep),
3662 &mut sink,
3663 );
3664 let hd = cfg.head_dim;
3665 let n_comp = st.compressed[li].len() / hd;
3666 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
3667 if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
3668 || (n_comp > 0
3669 && !crate::gpu_wgpu::dsv4_cache_write(
3670 st.kv_id,
3671 li,
3672 cfg.window * hd,
3673 &st.compressed[li],
3674 cap,
3675 ))
3676 {
3677 return false;
3678 }
3679 let _idx32: Vec<u32> = prep
3680 .idxs
3681 .iter()
3682 .map(|&p| {
3683 if p < prep.win_len {
3684 p as u32
3685 } else {
3686 (cfg.window + (p - prep.win_len)) as u32
3687 }
3688 })
3689 .collect();
3690 let nxt = layers.get(li + 1);
3691 let a_tail = crate::gpu_wgpu::Dsv4HcTail {
3692 fn_: &l.hc_ffn_fn,
3693 scale: &l.hc_ffn_scale,
3694 base: &l.hc_ffn_base,
3695 norm: &l.ffn_norm,
3696 hc: cfg.hc_mult,
3697 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3698 hc_eps: cfg.hc_eps,
3699 eps: cfg.norm_eps,
3700 };
3701 let scale = (cfg.head_dim as f32).powf(-0.5);
3702 if !attn_frame(
3703 l,
3704 cfg,
3705 st,
3706 li,
3707 &folded,
3708 &prep.qr,
3709 &prep.idxs,
3710 freqs_of(l),
3711 st.pos,
3712 prep.win_len,
3713 scale,
3714 Some(&a_tail),
3715 &mut [],
3716 ) {
3717 return false;
3718 }
3719 let m_tail = nxt.map(|n| crate::gpu_wgpu::Dsv4HcTail {
3720 fn_: &n.hc_attn_fn,
3721 scale: &n.hc_attn_scale,
3722 base: &n.hc_attn_base,
3723 norm: &n.attn_norm,
3724 hc: cfg.hc_mult,
3725 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3726 hc_eps: cfg.hc_eps,
3727 eps: cfg.norm_eps,
3728 });
3729 let mut next = vec![0.0f32; dim];
3730 let pair = m_tail
3731 .as_ref()
3732 .zip(nxt)
3733 .map(|(t, n)| (t, n.attn_norm.as_slice()));
3734 let forced = l
3735 .tid2eid
3736 .as_ref()
3737 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
3738 if moe_frame(
3739 &[],
3740 l,
3741 cfg,
3742 li,
3743 &[],
3744 forced.as_deref(),
3745 pool,
3746 Some(&a_tail),
3747 pair,
3748 &mut next,
3749 )
3750 .is_none()
3751 {
3752 return false;
3753 }
3754 folded = next;
3755 }
3756 let _ = scratch;
3757 crate::gpu_wgpu::dsv4_state_read(state)
3758}
3759
3760#[cfg(feature = "gpu")]
3764#[allow(clippy::too_many_arguments)]
3765fn hc_fold_norm(
3766 state: &[f32],
3767 hc_fn: &[f32],
3768 hc_scale: &[f32; 3],
3769 hc_base: &[f32],
3770 norm_w: &[f32],
3771 cfg: &Dsv4Cfg,
3772 pool: Option<&crate::pool::Pool>,
3773) -> (Vec<f32>, Vec<f32>, Vec<f32>) {
3774 let (hc, dim) = (cfg.hc_mult, cfg.dim);
3775 let mix_hc = (2 + hc) * hc;
3776 let mut mixes = vec![0.0f32; mix_hc];
3777 hc_mixes(state, hc_fn, mix_hc, cfg.norm_eps, pool, &mut mixes);
3778 let mut pre = vec![0.0f32; hc];
3779 let mut post = vec![0.0f32; hc];
3780 let mut comb = vec![0.0f32; hc * hc];
3781 hc_split_sinkhorn(
3782 &mixes,
3783 hc_scale,
3784 hc_base,
3785 hc,
3786 cfg.hc_sinkhorn_iters,
3787 cfg.hc_eps,
3788 &mut pre,
3789 &mut post,
3790 &mut comb,
3791 );
3792 let mut folded = vec![0.0f32; dim];
3793 hc_fold(state, &pre, hc, dim, &mut folded);
3794 rms_weighted(&mut folded, norm_w, cfg.norm_eps);
3795 (folded, post, comb)
3798}
3799
3800#[cfg(feature = "gpu")]
3817fn gpu_layer_enabled() -> bool {
3818 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3819 *ON.get_or_init(|| {
3820 std::env::var("CMF_DSV4_GPU_LAYER")
3821 .map(|v| v != "0")
3822 .unwrap_or(true)
3823 && crate::gpu::backend_available()
3824 })
3825}
3826
3827#[cfg(feature = "gpu")]
3831struct PackDyn {
3832 remap: Vec<u32>,
3833 owner: Vec<u32>,
3834 last: Vec<u64>,
3835 clock: u64,
3836 seen: Vec<u16>,
3843 mutated: bool,
3847}
3848
3849#[cfg(feature = "gpu")]
3854#[derive(Clone, Copy)]
3855struct GlobalOwner {
3856 layer: usize,
3857 expert: usize,
3858 pinned: bool,
3859}
3860
3861#[cfg(feature = "gpu")]
3862struct GlobalPoolDyn {
3863 slot_for: std::collections::HashMap<(usize, usize), u32>,
3864 owner: Vec<Option<GlobalOwner>>,
3865 last: Vec<u64>,
3866 seen: std::collections::HashMap<(usize, usize), u16>,
3867 occupancy: std::collections::HashMap<usize, usize>,
3868 clock: u64,
3869}
3870
3871#[cfg(feature = "gpu")]
3879struct GlobalPool {
3880 uid: u64,
3881 capacity: usize,
3882 segment_slots: usize,
3883 floor: usize,
3884 state: std::sync::Mutex<GlobalPoolDyn>,
3885}
3886
3887#[cfg(feature = "gpu")]
3888impl GlobalPool {
3889 fn remap(&self, layer: usize, n: usize) -> Vec<u32> {
3890 let st = self.state.lock().unwrap();
3891 debug_assert_eq!(self.capacity, st.owner.len());
3892 (0..n)
3893 .map(|e| st.slot_for.get(&(layer, e)).copied().unwrap_or(u32::MAX))
3894 .collect()
3895 }
3896
3897 fn seed_layer(
3898 &self,
3899 model: &std::sync::Arc<cortiq_core::CmfModel>,
3900 layer: usize,
3901 shared: (usize, usize, usize),
3902 _routed: &[(usize, (usize, usize, usize))],
3903 ) -> Option<u32> {
3904 let mut st = self.state.lock().unwrap();
3905 let shared_key = (layer, usize::MAX);
3906 let shared_slot = if let Some(&slot) = st.slot_for.get(&shared_key) {
3907 slot
3908 } else {
3909 let slot = st.owner.iter().position(Option::is_none)?;
3910 if !crate::gpu_wgpu::dsv4_global_slot_fill(model, slot, shared) {
3911 return None;
3912 }
3913 st.owner[slot] = Some(GlobalOwner {
3914 layer,
3915 expert: usize::MAX,
3916 pinned: true,
3917 });
3918 st.slot_for.insert(shared_key, slot as u32);
3919 *st.occupancy.entry(layer).or_insert(0) += 1;
3920 slot as u32
3921 };
3922 Some(shared_slot)
3927 }
3928
3929 fn ensure_picks(
3930 &self,
3931 model: &std::sync::Arc<cortiq_core::CmfModel>,
3932 layer: usize,
3933 picks: &[usize],
3934 experts: &[Dsv4Expert],
3935 quota: usize,
3936 min_seen: u16,
3937 ) -> Vec<u32> {
3938 let mut st = self.state.lock().unwrap();
3939 st.clock = st.clock.saturating_add(1);
3940 let clock = st.clock;
3941 if clock % 64 == 0 {
3942 for v in st.seen.values_mut() {
3943 *v >>= 1;
3944 }
3945 }
3946 for &expert in picks {
3947 let key = (layer, expert);
3948 let seen = st.seen.entry(key).or_insert(0);
3949 *seen = seen.saturating_add(1);
3950 if let Some(&slot) = st.slot_for.get(&key) {
3951 st.last[slot as usize] = clock;
3952 }
3953 }
3954 let mut fetched = 0usize;
3955 for &expert in picks {
3956 if fetched >= quota {
3957 break;
3958 }
3959 let key = (layer, expert);
3960 if st.slot_for.contains_key(&key) || st.seen.get(&key).copied().unwrap_or(0) < min_seen
3961 {
3962 continue;
3963 }
3964 let Some(exp) = experts.get(expert) else {
3965 continue;
3966 };
3967 let Some(tensors) = (|| {
3968 Some((
3969 exp.w1.model_idx()?,
3970 exp.w3.model_idx()?,
3971 exp.w2.model_idx()?,
3972 ))
3973 })() else {
3974 continue;
3975 };
3976 let empty = st.owner.iter().position(Option::is_none);
3977 let over_floor = |o: GlobalOwner, occ: &std::collections::HashMap<usize, usize>| {
3980 occ.get(&o.layer).copied().unwrap_or(0) > self.floor
3981 };
3982 let eligible =
3983 |o: GlobalOwner| !o.pinned && !(o.layer == layer && picks.contains(&o.expert));
3984 let victim = empty
3985 .or_else(|| {
3986 st.owner
3987 .iter()
3988 .enumerate()
3989 .filter_map(|(slot, &o)| {
3990 o.filter(|&x| eligible(x) && over_floor(x, &st.occupancy))
3991 .map(|_| slot)
3992 })
3993 .min_by_key(|&slot| st.last[slot])
3994 })
3995 .or_else(|| {
3996 st.owner
4000 .iter()
4001 .enumerate()
4002 .filter_map(|(slot, &o)| {
4003 o.filter(|&x| eligible(x) && x.layer == layer).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)| o.filter(|&x| eligible(x)).map(|_| slot))
4015 .min_by_key(|&slot| st.last[slot])
4016 });
4017 let Some(victim) = victim else { break };
4018 if !crate::gpu_wgpu::dsv4_global_slot_fill(model, victim, tensors) {
4019 break;
4020 }
4021 if let Some(old) = st.owner[victim] {
4022 st.slot_for.remove(&(old.layer, old.expert));
4023 if let Some(n) = st.occupancy.get_mut(&old.layer) {
4024 *n = n.saturating_sub(1);
4025 }
4026 }
4027 st.owner[victim] = Some(GlobalOwner {
4028 layer,
4029 expert,
4030 pinned: false,
4031 });
4032 st.slot_for.insert(key, victim as u32);
4033 *st.occupancy.entry(layer).or_insert(0) += 1;
4034 st.last[victim] = clock;
4035 fetched += 1;
4036 }
4037 drop(st);
4038 self.remap(layer, experts.len())
4039 }
4040
4041 fn pin_picks(
4047 &self,
4048 model: &std::sync::Arc<cortiq_core::CmfModel>,
4049 layer: usize,
4050 picks: &[usize],
4051 experts: &[Dsv4Expert],
4052 ) -> Vec<u32> {
4053 let remap = self.ensure_picks(model, layer, picks, experts, picks.len(), 1);
4054 let mut st = self.state.lock().unwrap();
4055 for &expert in picks {
4056 if let Some(&slot) = st.slot_for.get(&(layer, expert)) {
4057 if let Some(owner) = st.owner[slot as usize].as_mut() {
4058 owner.pinned = true;
4059 }
4060 }
4061 }
4062 remap
4063 }
4064}
4065
4066#[cfg(feature = "gpu")]
4067#[derive(Clone)]
4068struct GlobalPack {
4069 pool: std::sync::Arc<GlobalPool>,
4070 layer: usize,
4071 shared_slot: u32,
4072}
4073
4074#[cfg(feature = "gpu")]
4075impl Pack {
4076 fn is_mutated(&self) -> bool {
4078 self.global.is_some() || self.dynslots.lock().unwrap().mutated
4079 }
4080
4081 fn route_complete(&self) -> bool {
4087 if self.global.is_some() {
4088 return false;
4089 }
4090 let need = self
4091 .mask
4092 .as_deref()
4093 .map_or(self.remap.len(), |m| m.iter().filter(|&&x| x != 0).count());
4094 self.globals.len() >= need
4095 }
4096
4097 fn needs_remap(&self) -> bool {
4101 self.global.is_some()
4102 || self.mask.is_some()
4103 || self
4104 .remap
4105 .iter()
4106 .enumerate()
4107 .any(|(i, &slot)| slot != i as u32)
4108 }
4109}
4110
4111#[cfg(feature = "gpu")]
4115struct Pack {
4116 router: Vec<f32>,
4120 to_slot: Vec<usize>,
4122 remap: Vec<u32>,
4124 globals: Vec<usize>,
4126 tensors: Vec<(usize, usize, usize)>,
4127 mask: Option<Vec<u32>>,
4131 dynslots: std::sync::Mutex<PackDyn>,
4140 bias: Option<Vec<f32>>,
4149 global: Option<GlobalPack>,
4153}
4154
4155#[cfg(feature = "gpu")]
4156fn pack_freq_order(li: usize, n: usize) -> Option<Vec<usize>> {
4162 use std::collections::HashMap;
4163 use std::sync::OnceLock;
4164 static FREQ: OnceLock<Option<HashMap<(usize, usize), u64>>> = OnceLock::new();
4165 let map = FREQ
4166 .get_or_init(|| {
4167 let path = std::env::var("CMF_DSV4_PACK_FREQ").ok()?;
4168 let text = match std::fs::read_to_string(&path) {
4169 Ok(t) => t,
4170 Err(e) => {
4171 eprintln!("CMF_DSV4_PACK_FREQ={path} не читается ({e}) — порядок по id");
4172 return None;
4173 }
4174 };
4175 let mut m = HashMap::new();
4176 for line in text.lines() {
4177 let mut it = line.split('\t');
4178 if let (Some(l), Some(e), Some(c)) = (it.next(), it.next(), it.next()) {
4179 if let (Ok(l), Ok(e), Ok(c)) =
4180 (l.trim().parse(), e.trim().parse(), c.trim().parse::<u64>())
4181 {
4182 *m.entry((l, e)).or_insert(0) += c;
4183 }
4184 }
4185 }
4186 Some(m)
4187 })
4188 .as_ref()?;
4189 if !(0..n).any(|e| map.contains_key(&(li, e))) {
4190 return None;
4191 }
4192 let mut idx: Vec<usize> = (0..n).collect();
4193 idx.sort_by_key(|&e| {
4194 (
4195 std::cmp::Reverse(map.get(&(li, e)).copied().unwrap_or(0)),
4196 e,
4197 )
4198 });
4199 Some(idx)
4200}
4201
4202#[cfg(feature = "gpu")]
4203fn global_pool_for(
4204 model: &std::sync::Arc<cortiq_core::CmfModel>,
4205 cfg: &Dsv4Cfg,
4206 gu_q2: bool,
4207 dn_q2: bool,
4208) -> Option<std::sync::Arc<GlobalPool>> {
4209 use std::collections::HashMap;
4210 use std::sync::{Arc, Mutex, OnceLock};
4211 if dn_q2
4212 || !crate::gpu_wgpu::dsv4_global_moe_supported()
4213 || std::env::var("CMF_MOE_MASK").is_ok()
4214 {
4215 return None;
4216 }
4217 static POOLS: OnceLock<Mutex<HashMap<u64, Arc<GlobalPool>>>> = OnceLock::new();
4218 let pools = POOLS.get_or_init(|| Mutex::new(HashMap::new()));
4219 if let Some(p) = pools.lock().unwrap().get(&model.uid()).cloned() {
4220 return Some(p);
4221 }
4222 let requested = crate::gpu_wgpu::dsv4_experts_fit(cfg.moe_inter, cfg.dim, gu_q2, false);
4223 let (capacity, segment_slots) =
4224 crate::gpu_wgpu::dsv4_global_moe_create(model, requested, cfg.moe_inter, cfg.dim, gu_q2)?;
4225 let layers = model.header.arch.num_layers.max(1);
4226 let p = Arc::new(GlobalPool {
4227 uid: model.uid(),
4228 capacity,
4229 segment_slots,
4230 floor: (capacity / layers).max(2),
4233 state: Mutex::new(GlobalPoolDyn {
4234 slot_for: HashMap::new(),
4235 owner: vec![None; capacity],
4236 last: vec![0; capacity],
4237 seen: HashMap::new(),
4238 occupancy: HashMap::new(),
4239 clock: 0,
4240 }),
4241 });
4242 pools.lock().unwrap().insert(model.uid(), p.clone());
4243 Some(p)
4244}
4245
4246#[cfg(feature = "gpu")]
4247fn pack_for(l: &Dsv4Layer, cfg: &Dsv4Cfg, li: usize) -> Option<std::sync::Arc<Pack>> {
4248 use std::collections::HashMap;
4249 use std::sync::{Arc, Mutex, OnceLock};
4250 static CACHE: OnceLock<Mutex<HashMap<(u64, usize, usize), Option<Arc<Pack>>>>> =
4251 OnceLock::new();
4252 let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
4253 let model_uid = l
4259 .experts
4260 .first()
4261 .and_then(|e| e.w1.model_arc())
4262 .map_or(0, |m| m.uid());
4263 let first_expert = l
4269 .experts
4270 .first()
4271 .and_then(|e| e.w1.model_idx())
4272 .unwrap_or(usize::MAX);
4273 let key = (model_uid, li, first_expert);
4274 if let Some(v) = cache.lock().unwrap().get(&key) {
4275 return v.clone();
4276 }
4277 if let Ok(v) = std::env::var("CMF_DSV4_PACK_MAX_LI") {
4284 if v.parse::<usize>().is_ok_and(|max| li > max) {
4285 cache.lock().unwrap().insert(key, None);
4286 return None;
4287 }
4288 }
4289 let build = || -> Option<Arc<Pack>> {
4290 let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
4291 let mut globals = Vec::new();
4292 let mut tensors = Vec::new();
4293 let idx3 = |e: &Dsv4Expert| -> Option<(usize, usize, usize)> {
4294 Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
4295 };
4296 let route_mask: Option<Vec<u32>> = if l.tid2eid.is_none() {
4297 l.mask
4298 .as_deref()
4299 .map(|m| m.iter().map(|&open| u32::from(open)).collect())
4300 } else {
4301 None
4302 };
4303 let gu_q2 = l
4308 .experts
4309 .first()
4310 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4311 let dn_q2 = l
4312 .experts
4313 .first()
4314 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4315 if route_mask.is_none() {
4320 if let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) {
4321 if let Some(gp) = global_pool_for(&model, cfg, gu_q2, dn_q2) {
4322 let layer_key = first_expert;
4323 let order = pack_freq_order(li, l.experts.len())
4324 .unwrap_or_else(|| (0..l.experts.len()).collect());
4325 let routed: Vec<_> = order
4326 .into_iter()
4327 .filter_map(|gi| Some((gi, idx3(&l.experts[gi])?)))
4328 .collect();
4329 let shared = idx3(&l.shared)?;
4330 let shared_slot = gp.seed_layer(&model, layer_key, shared, &routed)?;
4331 let remap = gp.remap(layer_key, cfg.n_routed_experts);
4332 let to_slot: Vec<usize> = remap
4333 .iter()
4334 .map(|&s| {
4335 if s == u32::MAX {
4336 usize::MAX
4337 } else {
4338 s as usize
4339 }
4340 })
4341 .collect();
4342 let globals: Vec<usize> = remap
4343 .iter()
4344 .enumerate()
4345 .filter_map(|(e, &s)| (s != u32::MAX).then_some(e))
4346 .collect();
4347 let (rows, cols) = (l.gate.rows(), l.gate.cols());
4348 let mut router = vec![0.0f32; rows * cols];
4349 for r in 0..rows {
4350 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4351 }
4352 return Some(Arc::new(Pack {
4353 bias: l.gate_bias.clone(),
4354 mask: None,
4355 router,
4356 to_slot,
4357 remap: remap.clone(),
4358 globals,
4359 tensors: Vec::new(),
4362 dynslots: std::sync::Mutex::new(PackDyn {
4363 remap,
4364 owner: Vec::new(),
4365 last: Vec::new(),
4366 clock: 0,
4367 mutated: true,
4368 seen: vec![0; cfg.n_routed_experts],
4369 }),
4370 global: Some(GlobalPack {
4371 pool: gp,
4372 layer: layer_key,
4373 shared_slot,
4374 }),
4375 }));
4376 }
4377 }
4378 }
4379 if let Some(n) = std::env::var("CMF_DSV4_PACK_MAX")
4388 .ok()
4389 .and_then(|v| v.parse::<usize>().ok())
4390 {
4391 let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
4392 let mut globals = Vec::new();
4393 let mut tensors = Vec::new();
4394 for (gi, e) in l.experts.iter().enumerate() {
4395 if route_mask
4396 .as_deref()
4397 .is_some_and(|m| m.get(gi).copied().unwrap_or(1) == 0)
4398 {
4399 continue;
4400 }
4401 if globals.len() >= n {
4402 break;
4403 }
4404 to_slot[gi] = globals.len();
4405 globals.push(gi);
4406 tensors.push(idx3(e)?);
4407 }
4408 tensors.push(idx3(&l.shared)?);
4409 let (rows, cols) = (l.gate.rows(), l.gate.cols());
4410 let mut router = vec![0.0f32; rows * cols];
4411 for r in 0..rows {
4412 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4413 }
4414 let remap: Vec<u32> = to_slot
4415 .iter()
4416 .map(|&sl| {
4417 if sl == usize::MAX {
4418 u32::MAX
4419 } else {
4420 sl as u32
4421 }
4422 })
4423 .collect();
4424 return Some(Arc::new(Pack {
4425 bias: l.gate_bias.clone(),
4426 mask: route_mask.clone(),
4427 router,
4428 to_slot,
4429 dynslots: std::sync::Mutex::new(PackDyn {
4430 remap: remap.clone(),
4431 owner: globals.iter().map(|&g| g as u32).collect(),
4432 last: vec![0; globals.len()],
4433 clock: 0,
4434 mutated: false,
4435 seen: vec![0; cfg.n_routed_experts],
4436 }),
4437 remap,
4438 globals,
4439 tensors,
4440 global: None,
4441 }));
4442 }
4443 let dn_q2_fit = l
4444 .experts
4445 .first()
4446 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4447 let room = crate::gpu_wgpu::dsv4_experts_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2_fit)
4448 .saturating_sub(1);
4449 let remaining_layers = l
4460 .experts
4461 .first()
4462 .and_then(|e| e.w1.model_arc())
4463 .map(|m| m.header.arch.num_layers.saturating_sub(li).max(1))
4464 .unwrap_or(1);
4465 let auto_cap = if l.mask.is_some() {
4466 room
4471 } else if l.tid2eid.is_some() && room >= cfg.n_routed_experts {
4472 cfg.n_routed_experts
4473 } else {
4474 room.div_ceil(remaining_layers).max(1)
4475 };
4476 let room = match std::env::var("CMF_DSV4_PACK_LAYER_CAP")
4477 .ok()
4478 .and_then(|v| v.parse::<usize>().ok())
4479 {
4480 Some(0) => room,
4481 Some(cap) => room.min(cap),
4482 None => room.min(auto_cap),
4483 };
4484 let order =
4493 pack_freq_order(li, l.experts.len()).unwrap_or_else(|| (0..l.experts.len()).collect());
4494 for gi in order {
4495 let e = &l.experts[gi];
4496 if l.mask
4497 .as_deref()
4498 .is_some_and(|m| !m.get(gi).copied().unwrap_or(true))
4499 {
4500 continue;
4501 }
4502 if globals.len() >= room {
4503 break;
4504 }
4505 to_slot[gi] = globals.len();
4506 globals.push(gi);
4507 match idx3(e) {
4508 Some(t) => tensors.push(t),
4509 None => {
4510 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
4511 eprintln!("слой {li}: эксперт {gi} без индексов в каталоге");
4512 }
4513 return None;
4514 }
4515 }
4516 }
4517 if globals.is_empty() {
4518 if room == 0 {
4524 static SAID_ZERO: std::sync::atomic::AtomicBool =
4525 std::sync::atomic::AtomicBool::new(false);
4526 if !SAID_ZERO.swap(true, std::sync::atomic::Ordering::Relaxed) {
4527 tracing::warn!(
4528 "начиная со слоя {li}, в бюджете VRAM не осталось места даже под одного \
4529 эксперта — остальные веса остаются mmap-backed и читаются по требованию"
4530 );
4531 }
4532 } else {
4533 tracing::warn!("слой {li}: маска не оставила ни одного эксперта");
4534 }
4535 return None;
4536 }
4537 tensors.push(idx3(&l.shared)?); let (rows, cols) = (l.gate.rows(), l.gate.cols());
4539 let mut router = vec![0.0f32; rows * cols];
4540 for r in 0..rows {
4541 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4542 }
4543 let remap: Vec<u32> = to_slot
4544 .iter()
4545 .map(|&sl| {
4546 if sl == usize::MAX {
4547 u32::MAX
4548 } else {
4549 sl as u32
4550 }
4551 })
4552 .collect();
4553 Some(Arc::new(Pack {
4554 bias: l.gate_bias.clone(),
4555 mask: route_mask,
4556 router,
4557 to_slot,
4558 dynslots: std::sync::Mutex::new(PackDyn {
4559 remap: remap.clone(),
4560 owner: globals.iter().map(|&g| g as u32).collect(),
4561 last: vec![0; globals.len()],
4562 clock: 0,
4563 mutated: false,
4564 seen: vec![0; cfg.n_routed_experts],
4565 }),
4566 remap,
4567 globals,
4568 tensors,
4569 global: None,
4570 }))
4571 };
4572 let v = build();
4573 cache.lock().unwrap().insert(key, v.clone());
4574 v
4575}
4576
4577#[cfg(feature = "gpu")]
4585fn moe_frame(
4586 hidden: &[f32],
4587 l: &Dsv4Layer,
4588 cfg: &Dsv4Cfg,
4589 li: usize,
4590 logits: &[f32],
4591 forced: Option<&[usize]>,
4592 pool: Option<&crate::pool::Pool>,
4593 hc_cur: Option<&crate::gpu_wgpu::Dsv4HcTail>,
4596 hc_next: Option<(&crate::gpu_wgpu::Dsv4HcTail, &[f32])>,
4597 out: &mut [f32],
4598) -> Option<(Vec<f32>, usize)> {
4599 macro_rules! no {
4600 ($($t:tt)*) => {{
4601 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
4602 eprintln!("кадр MoE отклонён: {}", format_args!($($t)*));
4603 }
4604 return None;
4605 }};
4606 }
4607 let Some(pk) = pack_for(l, cfg, li) else {
4608 no!("слой {li}: упаковка экспертов не построена");
4609 };
4610 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
4613 no!("слой {li}: эксперты не отображены из файла");
4614 };
4615 let subset = !pk.route_complete();
4616 let needs_remap = pk.needs_remap();
4617 fn fetch_quota() -> usize {
4624 static Q: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
4625 *Q.get_or_init(|| {
4626 std::env::var("CMF_DSV4_FETCH_MAX")
4627 .ok()
4628 .and_then(|v| v.parse().ok())
4629 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().0)
4630 })
4631 }
4632 fn fetch_min_seen() -> u16 {
4633 static M: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
4634 *M.get_or_init(|| {
4635 std::env::var("CMF_DSV4_FETCH_MIN_SEEN")
4636 .ok()
4637 .and_then(|v| v.parse().ok())
4638 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().1)
4639 })
4640 }
4641 let mut dynv = pk.dynslots.lock().unwrap();
4642 let mut pidx = Vec::new();
4647 let mut pwt = Vec::new();
4648 let mut predicted_logits = Vec::new();
4654 let prediction_logits: &[f32] = if subset && logits.is_empty() && !hidden.is_empty() {
4655 predicted_logits.resize(cfg.n_routed_experts, 0.0);
4656 l.gate.matvec(hidden, &mut predicted_logits, pool);
4657 &predicted_logits
4658 } else {
4659 logits
4660 };
4661 if subset && !prediction_logits.is_empty() {
4662 route(
4663 prediction_logits,
4664 l.gate_bias.as_deref(),
4665 cfg.top_k,
4666 cfg.route_scale,
4667 forced,
4668 l.mask.as_deref(),
4669 &mut pidx,
4670 &mut pwt,
4671 );
4672 }
4673 let global_remap = pk.global.as_ref().map(|gl| {
4674 gl.pool.ensure_picks(
4675 &model, gl.layer, &pidx, &l.experts,
4676 cfg.top_k, 1,
4682 )
4683 });
4684 if subset && fetch_quota() > 0 && !pidx.is_empty() && !dynv.owner.is_empty() {
4685 dynv.clock += 1;
4686 let clock = dynv.clock;
4687 if clock % 64 == 0 {
4688 for v in dynv.seen.iter_mut() {
4689 *v >>= 1;
4690 }
4691 }
4692 for &pick in &pidx {
4693 dynv.seen[pick] = dynv.seen[pick].saturating_add(1);
4694 let sl = dynv.remap[pick];
4695 if sl != u32::MAX {
4696 dynv.last[sl as usize] = clock;
4697 }
4698 }
4699 let mut fetched = 0usize;
4700 for &pick in &pidx {
4701 if fetched >= fetch_quota() {
4702 break;
4703 }
4704 if dynv.remap[pick] != u32::MAX {
4705 continue;
4706 }
4707 if dynv.seen[pick] < fetch_min_seen() {
4708 continue; }
4710 let victim = (0..dynv.owner.len())
4712 .filter(|&sl| dynv.last[sl] != clock)
4713 .min_by_key(|&sl| dynv.last[sl]);
4714 let Some(victim) = victim else { break };
4715 let Some(exp) = l.experts.get(pick) else {
4716 continue;
4717 };
4718 let t3 = (|| {
4719 Some((
4720 exp.w1.model_idx()?,
4721 exp.w3.model_idx()?,
4722 exp.w2.model_idx()?,
4723 ))
4724 })();
4725 let Some(t3) = t3 else { continue };
4726 let gu_q2 = exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
4727 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
4728 if !crate::gpu_wgpu::dsv4_slot_fill(
4729 &model,
4730 pack_first,
4731 victim,
4732 pick,
4733 t3,
4734 cfg.moe_inter,
4735 cfg.dim,
4736 gu_q2,
4737 ) {
4738 break;
4739 }
4740 let old = dynv.owner[victim] as usize;
4741 if old < dynv.remap.len() {
4742 dynv.remap[old] = u32::MAX;
4743 }
4744 dynv.remap[pick] = victim as u32;
4745 dynv.owner[victim] = pick as u32;
4746 dynv.last[victim] = clock;
4747 dynv.mutated = true;
4748 fetched += 1;
4749 }
4750 }
4751 let fpack: Option<Vec<usize>> = match forced {
4755 Some(f) if needs_remap => Some(f.to_vec()),
4756 Some(f) => {
4757 let v: Vec<usize> = f.iter().map(|&g| pk.to_slot[g]).collect();
4758 if v.contains(&usize::MAX) {
4759 no!("слой {li}: хеш-слой называет эксперта вне упаковки");
4760 }
4761 Some(v)
4762 }
4763 None => None,
4764 };
4765 let lg: Vec<f32> = if logits.is_empty() || needs_remap {
4772 logits.to_vec()
4773 } else {
4774 pk.globals.iter().map(|&g| logits[g]).collect()
4775 };
4776 let live_remap = global_remap.as_deref().unwrap_or(dynv.remap.as_slice());
4777 let w = crate::gpu_wgpu::Dsv4MoeW {
4778 router: &pk.router,
4779 experts: &pk.tensors,
4780 logits: &lg,
4781 bias: pk.bias.as_deref(),
4782 mask: pk.mask.as_deref(),
4783 forced: fpack.as_deref(),
4784 remap: needs_remap.then_some(live_remap),
4785 global: pk.global.as_ref().map(|gl| crate::gpu_wgpu::Dsv4GlobalMoe {
4786 pool_uid: gl.pool.uid,
4787 shared_slot: gl.shared_slot,
4788 segment_slots: gl.pool.segment_slots as u32,
4789 }),
4790 has_shared: true,
4791 shared_weight: 1.0,
4792 preweighted: false,
4793 qwen_softmax: false,
4794 };
4795 let g = crate::gpu_wgpu::Dsv4MoeGeom {
4796 hidden: cfg.dim,
4797 inter: cfg.moe_inter,
4798 top_k: cfg.top_k,
4799 route_scale: cfg.route_scale,
4800 swiglu_limit: cfg.swiglu_limit,
4801 gu_q2: l
4802 .experts
4803 .first()
4804 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
4805 };
4806 let mut cold = Vec::new();
4807 let mut cold_x = Vec::new();
4808 let overlap: Vec<usize> = if !hidden.is_empty() {
4817 pidx.iter()
4818 .copied()
4819 .filter(|&pick| live_remap.get(pick).copied().unwrap_or(u32::MAX) == u32::MAX)
4820 .collect()
4821 } else {
4822 Vec::new()
4823 };
4824 let mut early: std::collections::HashMap<usize, Vec<f32>> = std::collections::HashMap::new();
4825 let frame_ok = std::thread::scope(|sc| {
4826 let handles: Vec<_> = overlap
4827 .iter()
4828 .filter_map(|&gi| l.experts.get(gi).map(|exp| (gi, exp)))
4829 .map(|(gi, exp)| {
4830 sc.spawn(move || {
4831 let mut a = vec![0.0f32; cfg.dim];
4832 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, 1.0, None, &mut a));
4833 (gi, a)
4834 })
4835 })
4836 .collect();
4837 let ok = crate::gpu_wgpu::dsv4_moe_frame(
4838 &model,
4839 &w,
4840 g,
4841 hidden,
4842 &mut cold,
4843 &mut cold_x,
4844 hc_cur,
4845 hc_next,
4846 out,
4847 );
4848 for h in handles {
4849 if let Ok((gi, a)) = h.join() {
4850 early.insert(gi, a);
4851 }
4852 }
4853 ok
4854 });
4855 if !frame_ok {
4856 return None;
4857 }
4858 if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
4861 let csum: f32 = cold.iter().map(|c| c.1).sum();
4862 eprintln!(
4863 "[холодные] слой {li}: вернулось {} из {} | сумма холодных {csum:.4} | \
4864 route_scale {:.4} | {:?}",
4865 cold.len(),
4866 cfg.top_k,
4867 cfg.route_scale,
4868 &cold[..cold.len().min(3)]
4869 );
4870 }
4871 let mut acc = vec![0.0f32; cfg.dim];
4872 let mut cold_sum = vec![0.0f32; cfg.dim];
4873 let cold_input = if hidden.is_empty() {
4874 cold_x.as_slice()
4875 } else {
4876 hidden
4877 };
4878 if !early.is_empty() {
4891 for &(gi, wt) in &cold {
4896 if let Some(a) = early.get(&gi) {
4897 for ((o, sum), v) in out.iter_mut().zip(&mut cold_sum).zip(a.iter()) {
4898 *o += v * wt;
4899 *sum += v * wt;
4900 }
4901 continue;
4902 }
4903 let Some(exp) = l.experts.get(gi) else {
4904 continue;
4905 };
4906 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
4907 for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
4908 *o += a;
4909 *sum += a;
4910 }
4911 }
4912 return Some((cold_sum, cold.len()));
4913 }
4914 match pool {
4915 Some(p) if cold.len() > 1 => {
4916 let results: Vec<std::sync::Mutex<Vec<f32>>> = cold
4917 .iter()
4918 .map(|_| std::sync::Mutex::new(Vec::new()))
4919 .collect();
4920 let (cold_ref, results_ref) = (&cold, &results);
4921 p.run_rows(cold.len(), &move |cs, ce| {
4922 for i in cs..ce {
4923 let (gi, wt) = cold_ref[i];
4924 let Some(exp) = l.experts.get(gi) else {
4925 continue;
4926 };
4927 let mut a = vec![0.0f32; cfg.dim];
4928 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, None, &mut a));
4929 *results_ref[i].lock().unwrap() = a;
4930 }
4931 });
4932 for r in &results {
4935 let a = r.lock().unwrap();
4936 if a.is_empty() {
4937 continue;
4938 }
4939 for ((o, sum), v) in out.iter_mut().zip(&mut cold_sum).zip(a.iter()) {
4940 *o += v;
4941 *sum += v;
4942 }
4943 }
4944 }
4945 _ => {
4946 for &(gi, wt) in &cold {
4947 let Some(exp) = l.experts.get(gi) else {
4948 continue;
4949 };
4950 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
4951 for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
4952 *o += a;
4953 *sum += a;
4954 }
4955 }
4956 }
4957 }
4958 Some((cold_sum, cold.len()))
4959}
4960
4961#[cfg(feature = "gpu")]
4967fn last_grew(now: u64) -> u64 {
4968 use std::sync::atomic::{AtomicU64, Ordering};
4969 static SEEN: AtomicU64 = AtomicU64::new(0);
4970 let was = SEEN.load(Ordering::Relaxed);
4971 if was != now {
4972 SEEN.store(now, Ordering::Relaxed);
4973 compressed_map().lock().unwrap().clear();
4974 return u64::MAX; }
4976 now
4977}
4978
4979#[cfg(feature = "gpu")]
4980fn compressed_map() -> &'static std::sync::Mutex<std::collections::HashMap<(u64, usize), usize>> {
4981 use std::collections::HashMap;
4982 use std::sync::{Mutex, OnceLock};
4983 static W: OnceLock<Mutex<HashMap<(u64, usize), usize>>> = OnceLock::new();
4984 W.get_or_init(|| Mutex::new(HashMap::new()))
4985}
4986
4987#[cfg(feature = "gpu")]
4988fn compressed_written(kv_id: u64, li: usize) -> usize {
4989 compressed_map()
4990 .lock()
4991 .unwrap()
4992 .get(&(kv_id, li))
4993 .copied()
4994 .unwrap_or(0)
4995}
4996
4997#[cfg(feature = "gpu")]
5000fn note_compressed(kv_id: u64, li: usize, n: usize) {
5001 compressed_map().lock().unwrap().insert((kv_id, li), n);
5002}
5003
5004#[cfg(feature = "gpu")]
5005fn gpu_moe2_enabled() -> bool {
5006 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5007 *ON.get_or_init(|| {
5008 std::env::var("CMF_DSV4_GPU_MOE2")
5009 .map(|v| v != "0")
5010 .unwrap_or(true)
5011 && crate::gpu::backend_available()
5012 })
5013}
5014
5015pub fn moe_step(
5016 hidden: &[f32],
5017 l: &Dsv4Layer,
5018 cfg: &Dsv4Cfg,
5019 token_id: u32,
5020 li: usize,
5022 pool: Option<&crate::pool::Pool>,
5023 out: &mut [f32],
5024) {
5025 let _t0 = prof::on().then(std::time::Instant::now);
5026 let _guard = scopeguard_moe(_t0, li);
5027 let mut logits = vec![0.0f32; cfg.n_routed_experts];
5028 l.gate.matvec(hidden, &mut logits, pool);
5029 let (mut idx, mut w) = (Vec::new(), Vec::new());
5030 route(
5031 &logits,
5032 l.gate_bias.as_deref(),
5033 cfg.top_k,
5034 cfg.route_scale,
5035 l.tid2eid
5036 .as_ref()
5037 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id))
5038 .as_deref(),
5039 l.mask.as_deref(),
5040 &mut idx,
5041 &mut w,
5042 );
5043 if route_stats_on() {
5044 let routed: Vec<(usize, f32)> = idx.iter().copied().zip(w.iter().copied()).collect();
5045 record_route(li, 0, cfg.n_routed_experts, &routed);
5046 }
5047 crate::pipeline::moe_trace_at(li as i32, &idx);
5053 #[cfg(feature = "gpu")]
5057 if gpu_moe2_enabled() && crate::gpu::enabled_here() {
5058 let forced = l
5059 .tid2eid
5060 .as_ref()
5061 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
5062 if moe_frame(
5063 hidden,
5064 l,
5065 cfg,
5066 li,
5067 &logits,
5068 forced.as_deref(),
5069 pool,
5070 None,
5071 None,
5072 out,
5073 )
5074 .is_some()
5075 {
5076 if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
5082 let mut want = vec![0.0f32; out.len()];
5083 let mut acc = vec![0.0f32; cfg.dim];
5084 crate::gpu::cpu_scope(|| {
5089 for (e, &ei) in idx.iter().enumerate() {
5090 let Some(exp) = l.experts.get(ei) else {
5091 continue;
5092 };
5093 run_expert(
5094 hidden,
5095 exp,
5096 cfg,
5097 w.get(e).copied().unwrap_or(0.0),
5098 pool,
5099 &mut acc,
5100 );
5101 for (o, a) in want.iter_mut().zip(&acc) {
5102 *o += a;
5103 }
5104 }
5105 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
5106 for (o, a) in want.iter_mut().zip(&acc) {
5107 *o += a;
5108 }
5109 });
5110 let num: f32 = want
5111 .iter()
5112 .zip(out.iter())
5113 .map(|(a, b)| (a - b) * (a - b))
5114 .sum();
5115 let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
5116 let rel = (num / den).sqrt();
5117 if rel > 1e-3 {
5118 let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
5119 eprintln!(
5120 "[кадр MoE] слой {li}: расхождение {rel:.3e} | выбрано {} | \
5121 упаковано {packed} из {} | хеш={} | смещение={}",
5122 idx.len(),
5123 cfg.n_routed_experts,
5124 l.tid2eid.is_some(),
5125 l.gate_bias.is_some()
5126 );
5127 }
5128 }
5129 return;
5130 }
5131 }
5132 PICK_TALLY.with(|t| {
5138 if let Some(v) = t.borrow_mut().as_mut() {
5139 v.push((li, idx.to_vec()));
5140 }
5141 });
5142 if dump_path().is_some() {
5143 PICKED.with(|p| {
5144 let mut p = p.borrow_mut();
5145 if p.len() <= li {
5146 p.resize(li + 1, Vec::new());
5147 }
5148 p[li] = idx.clone();
5149 });
5150 }
5151 fn gpu_moe_on() -> bool {
5170 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5171 *ON.get_or_init(|| std::env::var("CMF_DSV4_GPU_MOE").is_ok_and(|v| v != "0"))
5172 }
5173 if gpu_moe_on() && crate::gpu::enabled_here() {
5174 let mut jobs = Vec::with_capacity(idx.len() + 1);
5175 let mut model_ref = None;
5176 let mut ok = true;
5177 for (e, &ei) in idx.iter().enumerate() {
5178 let Some(exp) = l.experts.get(ei) else {
5179 continue;
5180 };
5181 ok &= crate::pipeline::moe_push_job_parts(
5182 &exp.w1,
5183 &exp.w3,
5184 &exp.w2,
5185 hidden,
5186 w.get(e).copied().unwrap_or(0.0),
5187 cfg.swiglu_limit,
5188 &mut jobs,
5189 &mut model_ref,
5190 )
5191 .is_some();
5192 }
5193 ok &= crate::pipeline::moe_push_job_parts(
5194 &l.shared.w1,
5195 &l.shared.w3,
5196 &l.shared.w2,
5197 hidden,
5198 1.0,
5199 cfg.swiglu_limit,
5200 &mut jobs,
5201 &mut model_ref,
5202 )
5203 .is_some();
5204 if ok {
5205 if let Some(m) = model_ref.as_ref() {
5206 if crate::gpu::moe_block(m, &jobs, out) {
5207 if std::env::var("CMF_DSV4_GPU_CHECK").is_ok() {
5212 let mut want = vec![0.0f32; out.len()];
5213 let mut acc = vec![0.0f32; cfg.dim];
5214 for (e, &ei) in idx.iter().enumerate() {
5215 let Some(exp) = l.experts.get(ei) else {
5216 continue;
5217 };
5218 run_expert(
5219 hidden,
5220 exp,
5221 cfg,
5222 w.get(e).copied().unwrap_or(0.0),
5223 pool,
5224 &mut acc,
5225 );
5226 for (o, a) in want.iter_mut().zip(&acc) {
5227 *o += a;
5228 }
5229 }
5230 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
5231 for (o, a) in want.iter_mut().zip(&acc) {
5232 *o += a;
5233 }
5234 let num: f32 = want
5235 .iter()
5236 .zip(out.iter())
5237 .map(|(a, b)| (a - b) * (a - b))
5238 .sum();
5239 let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
5240 eprintln!(
5241 "[dsv4-gpu] слой {li}: расхождение {:.3e} | |CPU|={:.5} |GPU|={:.5} | экспертов {}",
5242 (num / den).sqrt(),
5243 den.sqrt(),
5244 out.iter().map(|x| x * x).sum::<f32>().sqrt(),
5245 jobs.len()
5246 );
5247 }
5248 return;
5249 }
5250 }
5251 }
5252 }
5253 out.fill(0.0);
5254 let jobs: Vec<(Option<usize>, f32)> = idx
5261 .iter()
5262 .enumerate()
5263 .map(|(e, &ei)| (Some(ei), w.get(e).copied().unwrap_or(0.0)))
5264 .chain(std::iter::once((None, 1.0)))
5265 .collect();
5266 match pool {
5267 Some(p) if jobs.len() > 1 => {
5268 let results: Vec<std::sync::Mutex<Vec<f32>>> = jobs
5269 .iter()
5270 .map(|_| std::sync::Mutex::new(Vec::new()))
5271 .collect();
5272 let (jobs_ref, results_ref) = (&jobs, &results);
5273 p.run_rows(jobs.len(), &move |cs, ce| {
5274 for i in cs..ce {
5275 let (ei, wt) = jobs_ref[i];
5276 let exp = match ei {
5277 Some(ei) => match l.experts.get(ei) {
5278 Some(x) => x,
5279 None => continue,
5280 },
5281 None => &l.shared,
5282 };
5283 let mut a = vec![0.0f32; cfg.dim];
5284 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, wt, None, &mut a));
5285 *results_ref[i].lock().unwrap() = a;
5286 }
5287 });
5288 for r in &results {
5289 let a = r.lock().unwrap();
5290 for (o, v) in out.iter_mut().zip(a.iter()) {
5291 *o += v;
5292 }
5293 }
5294 }
5295 _ => {
5296 let mut acc = vec![0.0f32; cfg.dim];
5297 for &(ei, wt) in &jobs {
5298 let exp = match ei {
5299 Some(ei) => match l.experts.get(ei) {
5300 Some(x) => x,
5301 None => continue,
5302 },
5303 None => &l.shared,
5304 };
5305 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, wt, pool, &mut acc));
5306 for (o, a) in out.iter_mut().zip(&acc) {
5307 *o += a;
5308 }
5309 }
5310 }
5311 }
5312}
5313
5314fn run_expert(
5317 x: &[f32],
5318 e: &Dsv4Expert,
5319 cfg: &Dsv4Cfg,
5320 weight: f32,
5321 pool: Option<&crate::pool::Pool>,
5322 out: &mut [f32],
5323) {
5324 expert_swiglu(
5325 x,
5326 &|src, dst| e.w1.matvec(src, dst, pool),
5327 &|src, dst| e.w3.matvec(src, dst, pool),
5328 &|src, dst| e.w2.matvec(src, dst, pool),
5329 cfg.moe_inter,
5330 weight,
5331 cfg.swiglu_limit,
5332 out,
5333 );
5334}
5335
5336fn moe_step_block(
5341 xs: &[f32],
5342 b: usize,
5343 l: &Dsv4Layer,
5344 cfg: &Dsv4Cfg,
5345 token_ids: &[u32],
5346 tally_layer: usize,
5347 pool: Option<&crate::pool::Pool>,
5348 out: &mut [f32],
5349) {
5350 let (dim, inter) = (cfg.dim, cfg.moe_inter);
5351 debug_assert_eq!(xs.len(), b * dim);
5352 debug_assert_eq!(out.len(), b * dim);
5353 out.fill(0.0);
5354
5355 let mut logits = vec![0.0f32; b * cfg.n_routed_experts];
5356 l.gate.matmat(xs, b, &mut logits, pool);
5357 let mut picks: Vec<Vec<usize>> = Vec::with_capacity(b);
5358 let mut weights: Vec<Vec<f32>> = Vec::with_capacity(b);
5359 for bi in 0..b {
5360 let mut idx = Vec::new();
5361 let mut wt = Vec::new();
5362 let forced = l.tid2eid.as_ref().map(|tbl| {
5363 hash_route(
5364 tbl,
5365 cfg.vocab,
5366 cfg.top_k,
5367 token_ids.get(bi).copied().unwrap_or(0),
5368 )
5369 });
5370 route(
5371 &logits[bi * cfg.n_routed_experts..(bi + 1) * cfg.n_routed_experts],
5372 l.gate_bias.as_deref(),
5373 cfg.top_k,
5374 cfg.route_scale,
5375 forced.as_deref(),
5376 l.mask.as_deref(),
5377 &mut idx,
5378 &mut wt,
5379 );
5380 PICK_TALLY.with(|t| {
5381 if let Some(v) = t.borrow_mut().as_mut() {
5382 v.push((tally_layer, idx.clone()));
5383 }
5384 });
5385 picks.push(idx);
5386 weights.push(wt);
5387 }
5388
5389 let mut routed = vec![0.0f32; b * cfg.top_k * dim];
5392 let mut active: Vec<(usize, Vec<(usize, usize, f32)>)> = Vec::new();
5395 for ei in 0..l.experts.len() {
5396 let mut jobs = Vec::new();
5397 for bi in 0..b {
5398 for (slot, &picked) in picks[bi].iter().enumerate() {
5399 if picked == ei {
5400 jobs.push((bi, slot, weights[bi][slot]));
5401 }
5402 }
5403 }
5404 if !jobs.is_empty() {
5405 active.push((ei, jobs));
5406 }
5407 }
5408 let expert_fwd =
5411 |ei: usize, jobs: &[(usize, usize, f32)], inner: Option<&crate::pool::Pool>| -> Vec<f32> {
5412 let e = &l.experts[ei];
5413 let n = jobs.len();
5414 let mut xj = vec![0.0f32; n * dim];
5415 for (j, &(bi, _, _)) in jobs.iter().enumerate() {
5416 xj[j * dim..(j + 1) * dim].copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
5417 }
5418 let mut gate = vec![0.0f32; n * inter];
5419 let mut up = vec![0.0f32; n * inter];
5420 e.w1.matmat(&xj, n, &mut gate, inner);
5421 e.w3.matmat(&xj, n, &mut up, inner);
5422 for (j, &(_, _, wt)) in jobs.iter().enumerate() {
5423 let (gj, uj) = (
5424 &mut gate[j * inter..(j + 1) * inter],
5425 &mut up[j * inter..(j + 1) * inter],
5426 );
5427 if cfg.swiglu_limit > 0.0 {
5428 for u in uj.iter_mut() {
5429 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5430 }
5431 for g in gj.iter_mut() {
5432 *g = g.min(cfg.swiglu_limit);
5433 }
5434 }
5435 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5436 *g = (*g / (1.0 + (-*g).exp())) * u * wt;
5437 }
5438 }
5439 let mut down = vec![0.0f32; n * dim];
5440 e.w2.matmat(&gate, n, &mut down, inner);
5441 down
5442 };
5443 match pool {
5453 Some(p) if active.len() > 1 => {
5454 let results: Vec<std::sync::Mutex<Vec<f32>>> = active
5455 .iter()
5456 .map(|_| std::sync::Mutex::new(Vec::new()))
5457 .collect();
5458 let active_ref = &active;
5459 let results_ref = &results;
5460 let fwd = &expert_fwd;
5461 p.run_rows(active_ref.len(), &move |s, e| {
5462 for i in s..e {
5463 let (ei, jobs) = &active_ref[i];
5464 let d = crate::gpu::cpu_scope(|| fwd(*ei, jobs, None));
5465 *results_ref[i].lock().unwrap() = d;
5466 }
5467 });
5468 for (i, (_, jobs)) in active.iter().enumerate() {
5469 let down = results[i].lock().unwrap();
5470 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5471 routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
5472 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5473 }
5474 }
5475 }
5476 _ => {
5477 for (ei, jobs) in &active {
5478 let down = expert_fwd(*ei, jobs, pool);
5479 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5480 routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
5481 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5482 }
5483 }
5484 }
5485 }
5486
5487 let mut sg = vec![0.0f32; b * inter];
5490 let mut su = vec![0.0f32; b * inter];
5491 l.shared.w1.matmat(xs, b, &mut sg, pool);
5492 l.shared.w3.matmat(xs, b, &mut su, pool);
5493 for bi in 0..b {
5494 let (gj, uj) = (
5495 &mut sg[bi * inter..(bi + 1) * inter],
5496 &mut su[bi * inter..(bi + 1) * inter],
5497 );
5498 if cfg.swiglu_limit > 0.0 {
5499 for u in uj.iter_mut() {
5500 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5501 }
5502 for g in gj.iter_mut() {
5503 *g = g.min(cfg.swiglu_limit);
5504 }
5505 }
5506 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5507 *g = (*g / (1.0 + (-*g).exp())) * u;
5508 }
5509 }
5510 let mut shared = vec![0.0f32; b * dim];
5511 l.shared.w2.matmat(&sg, b, &mut shared, pool);
5512
5513 for bi in 0..b {
5514 let dst = &mut out[bi * dim..(bi + 1) * dim];
5515 for slot in 0..picks[bi].len() {
5516 let src = &routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim];
5517 for (o, &v) in dst.iter_mut().zip(src) {
5518 *o += v;
5519 }
5520 }
5521 for (o, &v) in dst.iter_mut().zip(&shared[bi * dim..(bi + 1) * dim]) {
5522 *o += v;
5523 }
5524 }
5525}
5526
5527fn cold_step_block(
5532 xs: &[f32],
5533 b: usize,
5534 l: &Dsv4Layer,
5535 cfg: &Dsv4Cfg,
5536 cold: &[Vec<(usize, f32)>],
5537 pool: Option<&crate::pool::Pool>,
5538 out: &mut [f32],
5539) {
5540 let (dim, inter) = (cfg.dim, cfg.moe_inter);
5541 debug_assert_eq!(xs.len(), b * dim);
5542 debug_assert_eq!(cold.len(), b);
5543 debug_assert_eq!(out.len(), b * dim);
5544 out.fill(0.0);
5545 let slots = cold.iter().map(Vec::len).max().unwrap_or(0);
5546 if slots == 0 {
5547 return;
5548 }
5549 let grouped = std::env::var("CMF_DSV4_COLD_MATMAT")
5555 .map(|v| v != "0")
5556 .unwrap_or(true);
5557 if !grouped {
5558 let jobs: Vec<(usize, usize, usize, f32)> = cold
5559 .iter()
5560 .enumerate()
5561 .flat_map(|(bi, row)| {
5562 row.iter()
5563 .enumerate()
5564 .map(move |(slot, &(ei, wt))| (bi, slot, ei, wt))
5565 })
5566 .collect();
5567 let results: Vec<std::sync::Mutex<Vec<f32>>> = jobs
5568 .iter()
5569 .map(|_| std::sync::Mutex::new(Vec::new()))
5570 .collect();
5571 match pool {
5572 Some(p) if jobs.len() > 1 => {
5573 let (jobs_ref, results_ref) = (&jobs, &results);
5574 p.run_rows(jobs.len(), &move |s, e| {
5575 for i in s..e {
5576 let (_, _, ei, wt) = jobs_ref[i];
5577 let Some(exp) = l.experts.get(ei) else {
5578 continue;
5579 };
5580 let bi = jobs_ref[i].0;
5581 let mut a = vec![0.0f32; dim];
5582 crate::gpu::cpu_scope(|| {
5583 run_expert(&xs[bi * dim..(bi + 1) * dim], exp, cfg, wt, None, &mut a)
5584 });
5585 *results_ref[i].lock().unwrap() = a;
5586 }
5587 });
5588 }
5589 _ => {
5590 for (i, &(bi, _, ei, wt)) in jobs.iter().enumerate() {
5591 let Some(exp) = l.experts.get(ei) else {
5592 continue;
5593 };
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, pool, &mut a)
5597 });
5598 *results[i].lock().unwrap() = a;
5599 }
5600 }
5601 }
5602 for (i, &(bi, _, _, _)) in jobs.iter().enumerate() {
5605 let a = results[i].lock().unwrap();
5606 for (o, &v) in out[bi * dim..(bi + 1) * dim].iter_mut().zip(a.iter()) {
5607 *o += v;
5608 }
5609 }
5610 return;
5611 }
5612 let mut active: Vec<(usize, Vec<(usize, usize, f32)>)> = Vec::new();
5613 for ei in 0..l.experts.len() {
5614 let mut jobs = Vec::new();
5615 for bi in 0..b {
5616 for (slot, &(picked, wt)) in cold[bi].iter().enumerate() {
5617 if picked == ei {
5618 jobs.push((bi, slot, wt));
5619 }
5620 }
5621 }
5622 if !jobs.is_empty() {
5623 active.push((ei, jobs));
5624 }
5625 }
5626 let expert_fwd =
5627 |ei: usize, jobs: &[(usize, usize, f32)], inner: Option<&crate::pool::Pool>| -> Vec<f32> {
5628 let e = &l.experts[ei];
5629 let n = jobs.len();
5630 let mut xj = vec![0.0f32; n * dim];
5631 for (j, &(bi, _, _)) in jobs.iter().enumerate() {
5632 xj[j * dim..(j + 1) * dim].copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
5633 }
5634 let mut gate = vec![0.0f32; n * inter];
5635 let mut up = vec![0.0f32; n * inter];
5636 e.w1.matmat(&xj, n, &mut gate, inner);
5637 e.w3.matmat(&xj, n, &mut up, inner);
5638 for (j, &(_, _, wt)) in jobs.iter().enumerate() {
5639 let (gj, uj) = (
5640 &mut gate[j * inter..(j + 1) * inter],
5641 &mut up[j * inter..(j + 1) * inter],
5642 );
5643 if cfg.swiglu_limit > 0.0 {
5644 for u in uj.iter_mut() {
5645 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5646 }
5647 for g in gj.iter_mut() {
5648 *g = g.min(cfg.swiglu_limit);
5649 }
5650 }
5651 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5652 *g = (*g / (1.0 + (-*g).exp())) * u * wt;
5653 }
5654 }
5655 let mut down = vec![0.0f32; n * dim];
5656 e.w2.matmat(&gate, n, &mut down, inner);
5657 down
5658 };
5659 let mut routed = vec![0.0f32; b * slots * dim];
5660 match pool {
5661 Some(p) if active.len() > 1 => {
5662 let results: Vec<std::sync::Mutex<Vec<f32>>> = active
5663 .iter()
5664 .map(|_| std::sync::Mutex::new(Vec::new()))
5665 .collect();
5666 let active_ref = &active;
5667 let results_ref = &results;
5668 let fwd = &expert_fwd;
5669 p.run_rows(active.len(), &move |s, e| {
5670 for i in s..e {
5671 let (ei, jobs) = &active_ref[i];
5672 let d = crate::gpu::cpu_scope(|| fwd(*ei, jobs, None));
5673 *results_ref[i].lock().unwrap() = d;
5674 }
5675 });
5676 for (i, (_, jobs)) in active.iter().enumerate() {
5677 let down = results[i].lock().unwrap();
5678 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5679 routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim]
5680 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5681 }
5682 }
5683 }
5684 _ => {
5685 for (ei, jobs) in &active {
5686 let down = expert_fwd(*ei, jobs, pool);
5687 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5688 routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim]
5689 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5690 }
5691 }
5692 }
5693 }
5694 for bi in 0..b {
5695 let dst = &mut out[bi * dim..(bi + 1) * dim];
5696 for slot in 0..cold[bi].len() {
5697 let src = &routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim];
5698 for (o, &v) in dst.iter_mut().zip(src) {
5699 *o += v;
5700 }
5701 }
5702 }
5703}
5704
5705#[cfg(feature = "gpu")]
5710fn refill_route_slots(l: &Dsv4Layer, cfg: &Dsv4Cfg, pk: &Pack, picks: &[usize]) {
5711 if picks.is_empty() || pk.route_complete() {
5712 return;
5713 }
5714 let quota = {
5715 static Q: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
5716 *Q.get_or_init(|| {
5717 std::env::var("CMF_DSV4_FETCH_MAX")
5718 .ok()
5719 .and_then(|v| v.parse().ok())
5720 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().0)
5721 })
5722 };
5723 if quota == 0 {
5724 return;
5725 }
5726 let min_seen = {
5727 static M: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
5728 *M.get_or_init(|| {
5729 std::env::var("CMF_DSV4_FETCH_MIN_SEEN")
5730 .ok()
5731 .and_then(|v| v.parse().ok())
5732 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().1)
5733 })
5734 };
5735 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
5736 return;
5737 };
5738 let mut dynv = pk.dynslots.lock().unwrap();
5739 if dynv.owner.is_empty() {
5740 return;
5741 }
5742 dynv.clock += 1;
5743 let clock = dynv.clock;
5744 if clock % 64 == 0 {
5745 for seen in &mut dynv.seen {
5746 *seen >>= 1;
5747 }
5748 }
5749 for &pick in picks {
5750 if pick >= dynv.seen.len() {
5751 continue;
5752 }
5753 dynv.seen[pick] = dynv.seen[pick].saturating_add(1);
5754 let slot = dynv.remap[pick];
5755 if slot != u32::MAX {
5756 dynv.last[slot as usize] = clock;
5757 }
5758 }
5759 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
5760 let mut fetched = 0usize;
5761 for &pick in picks {
5762 if fetched >= quota || pick >= dynv.remap.len() {
5763 break;
5764 }
5765 if dynv.remap[pick] != u32::MAX || dynv.seen[pick] < min_seen {
5766 continue;
5767 }
5768 let victim = (0..dynv.owner.len())
5769 .filter(|&slot| dynv.last[slot] != clock)
5770 .min_by_key(|&slot| dynv.last[slot]);
5771 let Some(victim) = victim else { break };
5772 let Some(exp) = l.experts.get(pick) else {
5773 continue;
5774 };
5775 let tensors = (|| {
5776 Some((
5777 exp.w1.model_idx()?,
5778 exp.w3.model_idx()?,
5779 exp.w2.model_idx()?,
5780 ))
5781 })();
5782 let Some(tensors) = tensors else { continue };
5783 let gu_q2 = exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
5784 if !crate::gpu_wgpu::dsv4_slot_fill(
5785 &model,
5786 pack_first,
5787 victim,
5788 pick,
5789 tensors,
5790 cfg.moe_inter,
5791 cfg.dim,
5792 gu_q2,
5793 ) {
5794 break;
5795 }
5796 let old = dynv.owner[victim] as usize;
5797 if old < dynv.remap.len() {
5798 dynv.remap[old] = u32::MAX;
5799 }
5800 dynv.remap[pick] = victim as u32;
5801 dynv.owner[victim] = pick as u32;
5802 dynv.last[victim] = clock;
5803 dynv.mutated = true;
5804 fetched += 1;
5805 }
5806}
5807
5808fn o_project_block(
5813 attn: &[f32],
5814 b: usize,
5815 wo_a: &crate::qtensor::QTensor,
5816 wo_b: &crate::qtensor::QTensor,
5817 groups: usize,
5818 lora: usize,
5819 pool: Option<&crate::pool::Pool>,
5820 out: &mut [f32],
5821) {
5822 let attn_len = attn.len() / b;
5823 let per_group = attn_len / groups;
5824 let rows = groups * lora;
5825 let mut mid = vec![0.0f32; b * rows];
5826 let mid_addr = crate::pool::SendMut::new(mid.as_mut_ptr());
5827 let run = |start: usize, end: usize| {
5828 let mut wr = vec![0.0f32; wo_a.cols()];
5829 for r in start..end {
5830 wo_a.row_f32(r, &mut wr);
5831 let group = r / lora;
5832 for bi in 0..b {
5833 let x = &attn
5834 [bi * attn_len + group * per_group..bi * attn_len + (group + 1) * per_group];
5835 let v = wr.iter().zip(x).map(|(w, x)| w * x).sum();
5836 unsafe { *mid_addr.at(bi * rows + r) = v };
5837 }
5838 }
5839 };
5840 match pool {
5841 Some(p) if rows >= 256 => p.run_rows(rows, &run),
5842 _ => run(0, rows),
5843 }
5844 wo_b.matmat(&mid, b, out, pool);
5845}
5846
5847fn no_compressed() -> bool {
5853 static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5854 *OFF.get_or_init(|| std::env::var("CMF_DSV4_NO_COMPRESSED").is_ok_and(|v| v != "0"))
5855}
5856
5857fn trace_on() -> bool {
5858 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5859 *ON.get_or_init(|| std::env::var("CMF_DSV4_TRACE").is_ok_and(|v| v != "0"))
5860}
5861
5862fn rms_of(v: &[f32]) -> f32 {
5863 if v.is_empty() {
5864 return 0.0;
5865 }
5866 (v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32).sqrt()
5867}
5868
5869#[cfg(feature = "gpu")]
5870fn verify_fp_on(pos: usize) -> bool {
5871 static POS: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
5872 let want = *POS.get_or_init(|| {
5873 std::env::var("CMF_DSV4_FP_POS")
5874 .ok()
5875 .and_then(|v| v.parse().ok())
5876 });
5877 want == Some(pos)
5878}
5879
5880#[cfg(feature = "gpu")]
5881fn verify_fp(tag: &str, pos: usize, li: usize, state: &[f32]) {
5882 if !verify_fp_on(pos) {
5883 return;
5884 }
5885 let mut fp = 0xcbf29ce484222325u64;
5886 for &x in state {
5887 fp ^= x.to_bits() as u64;
5888 fp = fp.wrapping_mul(0x100000001b3);
5889 }
5890 eprintln!(
5891 "[dsv4-fp] {tag} pos={pos} li={li} fp={fp:016x} rms={:.7} head={:?}",
5892 rms_of(state),
5893 &state[..4.min(state.len())]
5894 );
5895}
5896
5897thread_local! {
5903 static BODY: std::cell::RefCell<Vec<String>> = const { std::cell::RefCell::new(Vec::new()) };
5905 static PICKED: std::cell::RefCell<Vec<Vec<usize>>> =
5909 const { std::cell::RefCell::new(Vec::new()) };
5910 static PICK_TALLY: std::cell::RefCell<Option<Vec<(usize, Vec<usize>)>>> =
5912 const { std::cell::RefCell::new(None) };
5913}
5914
5915pub fn pick_tally_arm() {
5917 PICK_TALLY.with(|t| *t.borrow_mut() = Some(Vec::new()));
5918}
5919
5920pub fn pick_tally_take() -> Vec<(usize, Vec<usize>)> {
5922 PICK_TALLY.with(|t| t.borrow_mut().take().unwrap_or_default())
5923}
5924
5925pub fn tally_unique(picks: &[(usize, Vec<usize>)]) -> (usize, usize) {
5928 let mut seen = std::collections::HashSet::new();
5933 let mut total = 0;
5934 for (li, v) in picks {
5935 total += v.len();
5936 for &e in v {
5937 seen.insert((*li, e));
5938 }
5939 }
5940 (seen.len(), total)
5941}
5942
5943fn dump_path() -> Option<&'static str> {
5944 static P: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
5945 P.get_or_init(|| std::env::var("CMF_DSV4_DUMP").ok())
5946 .as_deref()
5947}
5948
5949fn dump_line(json: &str) {
5950 if let Some(p) = dump_path() {
5951 use std::io::Write as _;
5952 if let Ok(mut f) = std::fs::OpenOptions::new()
5953 .create(true)
5954 .append(true)
5955 .open(p)
5956 {
5957 let _ = writeln!(f, "{json}");
5958 }
5959 }
5960}
5961
5962fn vec_json(v: &[f32]) -> String {
5963 let mut s = String::with_capacity(v.len() * 9);
5964 s.push('[');
5965 for (i, x) in v.iter().enumerate() {
5966 if i > 0 {
5967 s.push(',');
5968 }
5969 s.push_str(&format!("{x:.6e}"));
5970 }
5971 s.push(']');
5972 s
5973}
5974
5975#[allow(clippy::too_many_arguments)]
5983#[allow(clippy::too_many_arguments)]
5993fn batch_prefill() -> usize {
5997 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
5998 *N.get_or_init(|| {
5999 std::env::var("CMF_DSV4_BATCH")
6000 .ok()
6001 .and_then(|v| v.parse::<usize>().ok())
6002 .filter(|&n| (1..=32).contains(&n))
6003 .unwrap_or(8)
6010 })
6011}
6012
6013#[allow(clippy::too_many_arguments)]
6026fn forward_chunk_batched(
6027 g: &Dsv4Globals,
6028 layers: &[Dsv4Layer],
6029 cfg: &Dsv4Cfg,
6030 st: &mut Dsv4State,
6031 ids: &[u32],
6032 pos0: usize,
6033 inv_freq: &[f32],
6034 pool: Option<&crate::pool::Pool>,
6035 logits: &mut Vec<f32>,
6036 want_logits: bool,
6037) -> bool {
6038 #[cfg(not(feature = "gpu"))]
6039 {
6040 let _ = (
6041 g,
6042 layers,
6043 cfg,
6044 st,
6045 ids,
6046 pos0,
6047 inv_freq,
6048 pool,
6049 logits,
6050 want_logits,
6051 );
6052 false
6053 }
6054 #[cfg(feature = "gpu")]
6055 {
6056 let b = ids.len();
6057 let gpu_end = st
6062 .dev_set
6063 .iter()
6064 .enumerate()
6065 .position(|(li, &on)| {
6066 !on || pack_for(&layers[li], cfg, li).is_none_or(|p| !p.route_complete())
6067 })
6068 .unwrap_or(st.dev_set.len());
6069 let why = if b < 2 {
6070 "токенов меньше двух"
6071 } else if !chain_enabled() {
6072 "цепочка выключена"
6073 } else if !st.dev_owned {
6074 "карта ещё не владеет состоянием"
6075 } else if st.dev_set.len() != layers.len() {
6076 "набор слоёв ещё не зафиксирован"
6077 } else if st.dev_set[gpu_end.min(st.dev_set.len())..]
6078 .iter()
6079 .enumerate()
6080 .any(|(i, &on)| on && !st.partial_set.get(gpu_end + i).copied().unwrap_or(false))
6081 {
6082 "слои на карте не образуют префикс"
6083 } else {
6084 ""
6085 };
6086 if !why.is_empty() {
6087 tracing::debug!("dsv4: пакет preflight — {why}");
6093 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
6094 eprintln!("dsv4: пакет preflight — {why}");
6095 }
6096 return false;
6097 }
6098 let (hc, dim) = (cfg.hc_mult, cfg.dim);
6099 let mut emb = vec![0.0f32; dim];
6100 let mut states = vec![0.0f32; b * hc * dim];
6101 for (t, &id) in ids.iter().enumerate() {
6102 let mut state = vec![0.0f32; hc * dim];
6103 g.embed.row_f32(id as usize, &mut emb);
6104 for j in 0..hc {
6105 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
6106 }
6107 let (folded, post0, comb0) = hc_fold_norm(
6108 &state,
6109 &layers[0].hc_attn_fn,
6110 &layers[0].hc_attn_scale,
6111 &layers[0].hc_attn_base,
6112 &layers[0].attn_norm,
6113 cfg,
6114 pool,
6115 );
6116 let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
6117 layers[0].wq_a.matvec(&folded, &mut qn0, pool);
6118 rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
6119 if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
6120 || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
6121 || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
6122 || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
6123 {
6124 return false;
6125 }
6126 states[t * hc * dim..(t + 1) * hc * dim].copy_from_slice(&state);
6127 }
6128 let run: Vec<usize> = (0..gpu_end).collect();
6129 let mut folded = Vec::new();
6130 st.pos = pos0;
6131 if gpu_end > 0 {
6132 if !dsv4_chain_run(
6133 layers,
6134 &run,
6135 cfg,
6136 g,
6137 st,
6138 *ids.last().unwrap(),
6139 &mut folded,
6140 Some(&mut states),
6141 b,
6142 ids,
6143 true,
6144 pool,
6145 ) {
6146 return false;
6147 }
6148 }
6149 let mut scratch = HcScratch::new(cfg);
6155 host_tail_walk_batch(
6156 g,
6157 layers,
6158 cfg,
6159 st,
6160 gpu_end,
6161 &mut states,
6162 ids,
6163 pos0,
6164 b,
6165 inv_freq,
6166 &mut scratch,
6167 pool,
6168 None,
6169 );
6170 st.pos = pos0 + b;
6171 {
6176 static SAID: std::sync::Once = std::sync::Once::new();
6177 SAID.call_once(|| tracing::warn!("dsv4: префилл пакетами по {b}"));
6178 }
6179 if want_logits {
6184 let last = &states[(b - 1) * hc * dim..b * hc * dim];
6185 let mut h = vec![0.0f32; dim];
6186 hc_head_fold(
6187 last,
6188 &g.hc_head_fn,
6189 g.hc_head_scale,
6190 &g.hc_head_base,
6191 cfg,
6192 pool,
6193 &mut h,
6194 );
6195 rms_weighted(&mut h, &g.norm, cfg.norm_eps);
6196 logits.resize(cfg.vocab, 0.0);
6197 g.head.matvec(&h, logits, pool);
6198 } else {
6199 logits.clear();
6200 }
6201 true
6202 }
6203}
6204
6205#[cfg(feature = "gpu")]
6213pub struct Dsv4SpecTxn {
6214 pos0: usize,
6215 batch: usize,
6216 pub(crate) gpu_end: usize,
6217 dev_filled: Vec<usize>,
6218 dev_n_comp: Vec<usize>,
6219 dev_n_ix: Vec<usize>,
6220 host: Vec<(usize, HostLayerSnap)>,
6221 host_steps: Vec<(usize, Vec<HostLayerSnap>)>,
6226 pub states: Vec<f32>,
6230 shadow: Option<crate::gpu_wgpu::Dsv4SpecShadow>,
6231}
6232
6233#[cfg(feature = "gpu")]
6234struct HostLayerSnap {
6235 window: Vec<f32>,
6236 compressed: Vec<f32>,
6237 index_kv: Vec<f32>,
6238 pending_kv: Vec<f32>,
6239 pending_score: Vec<f32>,
6240 prev_kv: Vec<f32>,
6241 prev_score: Vec<f32>,
6242 pending_ix_kv: Vec<f32>,
6243 pending_ix_score: Vec<f32>,
6244 prev_ix_kv: Vec<f32>,
6245 prev_ix_score: Vec<f32>,
6246}
6247
6248#[cfg(feature = "gpu")]
6249fn host_snap(st: &Dsv4State, li: usize) -> HostLayerSnap {
6250 HostLayerSnap {
6251 window: st.window[li].clone(),
6252 compressed: st.compressed[li].clone(),
6253 index_kv: st.index_kv[li].clone(),
6254 pending_kv: st.pending_kv[li].clone(),
6255 pending_score: st.pending_score[li].clone(),
6256 prev_kv: st.prev_kv[li].clone(),
6257 prev_score: st.prev_score[li].clone(),
6258 pending_ix_kv: st.pending_ix_kv[li].clone(),
6259 pending_ix_score: st.pending_ix_score[li].clone(),
6260 prev_ix_kv: st.prev_ix_kv[li].clone(),
6261 prev_ix_score: st.prev_ix_score[li].clone(),
6262 }
6263}
6264
6265#[cfg(feature = "gpu")]
6266fn host_restore(st: &mut Dsv4State, li: usize, s: &HostLayerSnap) {
6267 st.window[li] = s.window.clone();
6268 st.compressed[li] = s.compressed.clone();
6269 st.index_kv[li] = s.index_kv.clone();
6270 st.pending_kv[li] = s.pending_kv.clone();
6271 st.pending_score[li] = s.pending_score.clone();
6272 st.prev_kv[li] = s.prev_kv.clone();
6273 st.prev_score[li] = s.prev_score.clone();
6274 st.pending_ix_kv[li] = s.pending_ix_kv.clone();
6275 st.pending_ix_score[li] = s.pending_ix_score.clone();
6276 st.prev_ix_kv[li] = s.prev_ix_kv.clone();
6277 st.prev_ix_score[li] = s.prev_ix_score.clone();
6278}
6279
6280#[cfg(feature = "gpu")]
6284#[allow(clippy::too_many_arguments)]
6285fn host_tail_walk(
6286 g: &Dsv4Globals,
6287 layers: &[Dsv4Layer],
6288 cfg: &Dsv4Cfg,
6289 st: &mut Dsv4State,
6290 gpu_end: usize,
6291 state: &mut [f32],
6292 token_id: u32,
6293 pos: usize,
6294 inv_freq: &[f32],
6295 scratch: &mut HcScratch,
6296 pool: Option<&crate::pool::Pool>,
6297) {
6298 st.pos = pos;
6299 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
6300 let freqs = if l.compressor.is_some() {
6301 &g.inv_freq_compress
6302 } else {
6303 &g.inv_freq_window
6304 };
6305 let freqs = if freqs.is_empty() {
6306 inv_freq
6307 } else {
6308 freqs.as_slice()
6309 };
6310 hc_block(
6311 state,
6312 &l.hc_attn_fn,
6313 &l.hc_attn_scale,
6314 &l.hc_attn_base,
6315 &l.attn_norm,
6316 cfg,
6317 scratch,
6318 pool,
6319 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
6320 );
6321 hc_block(
6322 state,
6323 &l.hc_ffn_fn,
6324 &l.hc_ffn_scale,
6325 &l.hc_ffn_base,
6326 &l.ffn_norm,
6327 cfg,
6328 scratch,
6329 pool,
6330 |f, o| {
6331 if host_cpu_moe() {
6332 crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
6333 } else {
6334 moe_step(f, l, cfg, token_id, li, pool, o)
6335 }
6336 },
6337 );
6338 dspark_note(li, state, cfg);
6339 }
6340}
6341
6342#[cfg(feature = "gpu")]
6348#[allow(clippy::too_many_arguments)]
6349fn host_tail_walk_batch(
6350 g: &Dsv4Globals,
6351 layers: &[Dsv4Layer],
6352 cfg: &Dsv4Cfg,
6353 st: &mut Dsv4State,
6354 gpu_end: usize,
6355 states: &mut [f32],
6356 ids: &[u32],
6357 pos0: usize,
6358 b: usize,
6359 inv_freq: &[f32],
6360 scratch: &mut HcScratch,
6361 pool: Option<&crate::pool::Pool>,
6362 mut steps: Option<&mut Vec<(usize, Vec<HostLayerSnap>)>>,
6363) {
6364 let (hc, dim) = (cfg.hc_mult, cfg.dim);
6365 let mix_hc = (2 + hc) * hc;
6366 let mut folds = vec![0.0f32; b * dim];
6367 let mut mo = vec![0.0f32; b * dim];
6368 let mut posts = vec![0.0f32; b * hc];
6369 let mut combs = vec![0.0f32; b * hc * hc];
6370 let mut resid = vec![0.0f32; b * hc * dim];
6371 let spec_time = {
6372 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6373 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
6374 };
6375 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
6376 let t_attn = std::time::Instant::now();
6377 let freqs = if l.compressor.is_some() {
6378 &g.inv_freq_compress
6379 } else {
6380 &g.inv_freq_window
6381 };
6382 let freqs = if freqs.is_empty() {
6383 inv_freq
6384 } else {
6385 freqs.as_slice()
6386 };
6387 for t in 0..b {
6388 st.pos = pos0 + t;
6389 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
6390 hc_block(
6391 state,
6392 &l.hc_attn_fn,
6393 &l.hc_attn_scale,
6394 &l.hc_attn_base,
6395 &l.attn_norm,
6396 cfg,
6397 scratch,
6398 pool,
6399 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
6400 );
6401 if let Some(steps) = steps.as_mut() {
6402 match steps.iter_mut().find(|(l, _)| *l == li) {
6403 Some((_, v)) => v.push(host_snap(st, li)),
6404 None => steps.push((li, vec![host_snap(st, li)])),
6405 }
6406 }
6407 }
6408 let t_glue = std::time::Instant::now();
6409 for t in 0..b {
6410 let state = &states[t * hc * dim..(t + 1) * hc * dim];
6411 hc_mixes(
6412 state,
6413 &l.hc_ffn_fn,
6414 mix_hc,
6415 cfg.norm_eps,
6416 pool,
6417 &mut scratch.mixes,
6418 );
6419 hc_split_sinkhorn(
6420 &scratch.mixes,
6421 &l.hc_ffn_scale,
6422 &l.hc_ffn_base,
6423 hc,
6424 cfg.hc_sinkhorn_iters,
6425 cfg.hc_eps,
6426 &mut scratch.pre,
6427 &mut posts[t * hc..(t + 1) * hc],
6428 &mut combs[t * hc * hc..(t + 1) * hc * hc],
6429 );
6430 let fold = &mut folds[t * dim..(t + 1) * dim];
6431 hc_fold(state, &scratch.pre, hc, dim, fold);
6432 let ms = fold.iter().map(|v| v * v).sum::<f32>() / dim as f32;
6433 let inv = 1.0 / (ms + cfg.norm_eps).sqrt();
6434 for (v, w) in fold.iter_mut().zip(&l.ffn_norm) {
6435 *v = *v * inv * w;
6436 }
6437 resid[t * hc * dim..(t + 1) * hc * dim]
6438 .copy_from_slice(&states[t * hc * dim..(t + 1) * hc * dim]);
6439 }
6440 let t_moe = std::time::Instant::now();
6441 let tail_pack = {
6447 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6448 *ON.get_or_init(|| {
6449 std::env::var("CMF_DSV4_TAIL_PACK")
6450 .map(|v| v != "0")
6451 .unwrap_or(true)
6452 })
6453 };
6454 let mut packed_done = false;
6455 if tail_pack && pack_for(l, cfg, li).is_some() {
6456 packed_done = true;
6457 for t in 0..b {
6458 let f = &folds[t * dim..(t + 1) * dim];
6459 let forced = l.tid2eid.as_ref().map(|tbl| {
6460 hash_route(tbl, cfg.vocab, cfg.top_k, ids.get(t).copied().unwrap_or(0))
6461 });
6462 let o = &mut mo[t * dim..(t + 1) * dim];
6463 match moe_frame(f, l, cfg, li, &[], forced.as_deref(), pool, None, None, o) {
6464 Some((cold_sum, n)) => {
6465 if n > 0 {
6466 for (od, cd) in o.iter_mut().zip(cold_sum.iter()) {
6467 *od += cd;
6468 }
6469 }
6470 }
6471 None => {
6472 packed_done = false;
6473 break;
6474 }
6475 }
6476 }
6477 }
6478 if !packed_done {
6479 if host_cpu_moe() {
6480 crate::gpu::cpu_scope(|| moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo));
6481 } else {
6482 moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo);
6483 }
6484 }
6485 let t_exp = std::time::Instant::now();
6486 for t in 0..b {
6487 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
6488 hc_expand(
6489 &mo[t * dim..(t + 1) * dim],
6490 &resid[t * hc * dim..(t + 1) * hc * dim],
6491 &posts[t * hc..(t + 1) * hc],
6492 &combs[t * hc * hc..(t + 1) * hc * hc],
6493 hc,
6494 dim,
6495 state,
6496 );
6497 dspark_note(li, state, cfg);
6498 }
6499 if spec_time {
6500 eprintln!(
6501 "хвост слоя {li}: attn {:.1} мс, клей {:.1}, moe {:.1}, expand {:.1}",
6502 (t_glue - t_attn).as_secs_f64() * 1e3,
6503 (t_moe - t_glue).as_secs_f64() * 1e3,
6504 (t_exp - t_moe).as_secs_f64() * 1e3,
6505 t_exp.elapsed().as_secs_f64() * 1e3,
6506 );
6507 }
6508 }
6509}
6510
6511#[cfg(feature = "gpu")]
6517#[allow(clippy::too_many_arguments)]
6518fn partial_layer_batch(
6519 g: &Dsv4Globals,
6520 layers: &[Dsv4Layer],
6521 cfg: &Dsv4Cfg,
6522 st: &mut Dsv4State,
6523 li: usize,
6524 states: &mut [f32],
6525 ids: &[u32],
6526 pos0: usize,
6527 b: usize,
6528 pool: Option<&crate::pool::Pool>,
6529) -> bool {
6530 let l = &layers[li];
6531 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
6532 if states.len() < b * hc * dim || ids.len() < b {
6533 return false;
6534 }
6535 let Some(pk) = pack_for(l, cfg, li) else {
6536 return false;
6537 };
6538 if pk.route_complete() {
6539 return false;
6540 }
6541 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
6542 return false;
6543 };
6544 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
6545 l.wq_a.model_idx(),
6546 l.wq_b.model_idx(),
6547 l.wo_a.model_idx(),
6548 l.wo_b.model_idx(),
6549 l.wkv.model_idx(),
6550 ) else {
6551 return false;
6552 };
6553 let comp = match &l.compressor {
6554 None => None,
6555 Some(cp) => {
6556 let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
6557 return false;
6558 };
6559 Some((
6560 crate::gpu_wgpu::Dsv4CompW {
6561 wkv: a,
6562 wgate: bx,
6563 norm: &cp.norm,
6564 ape: &cp.ape,
6565 },
6566 crate::gpu_wgpu::Dsv4CompGeom {
6567 width: cp.wkv.rows(),
6568 hidden: dim,
6569 ratio: cp.ratio,
6570 overlap: cp.overlap,
6571 rope_dim: cfg.rope_head_dim,
6572 eps: cfg.norm_eps,
6573 },
6574 ))
6575 }
6576 };
6577 let ix = match &l.indexer {
6578 None => None,
6579 Some(ixr) => {
6580 let cp = &ixr.compressor;
6581 let (Some(a), Some(bx), Some(qb), Some(wp)) = (
6582 cp.wkv.model_idx(),
6583 cp.wgate.model_idx(),
6584 ixr.wq_b.model_idx(),
6585 ixr.weights_proj.model_idx(),
6586 ) else {
6587 return false;
6588 };
6589 let ih = ixr.weights_proj.rows();
6590 Some((
6591 crate::gpu_wgpu::Dsv4CompW {
6592 wkv: a,
6593 wgate: bx,
6594 norm: &cp.norm,
6595 ape: &cp.ape,
6596 },
6597 crate::gpu_wgpu::Dsv4CompGeom {
6598 width: cp.wkv.rows(),
6599 hidden: dim,
6600 ratio: cp.ratio,
6601 overlap: cp.overlap,
6602 rope_dim: cfg.rope_head_dim,
6603 eps: cfg.norm_eps,
6604 },
6605 crate::gpu_wgpu::Dsv4IxW {
6606 wq_b: qb,
6607 weights_proj: wp,
6608 },
6609 crate::gpu_wgpu::Dsv4IxGeom {
6610 ih,
6611 idim: ixr.wq_b.rows() / ih.max(1),
6612 q_lora: cfg.q_lora_rank,
6613 hidden: dim,
6614 rope_dim: cfg.rope_head_dim,
6615 eps: cfg.norm_eps,
6616 top_k: cfg.index_topk,
6617 window: cfg.window,
6618 },
6619 ))
6620 }
6621 };
6622 let ew_c = comp.as_ref().map_or(
6623 0,
6624 |(_, cg)| {
6625 if cg.overlap { cg.width / 2 } else { cg.width }
6626 },
6627 );
6628 let ew_i = ix.as_ref().map_or(
6629 0,
6630 |(_, cg, _, _)| {
6631 if cg.overlap { cg.width / 2 } else { cg.width }
6632 },
6633 );
6634 let comp_extra = comp
6635 .as_ref()
6636 .map_or(0, |(_, cg)| b.div_ceil(cg.ratio.max(1)));
6637 let need = cfg.window * hd + (st.dev_n_comp[li] + comp_extra + 1) * ew_c.max(1) + (b + 1) * hd;
6638 if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
6639 return false;
6640 }
6641 let base = crate::gpu_wgpu::Dsv4Prep {
6642 wkv,
6643 kv_norm: &l.kv_norm,
6644 comp,
6645 ix,
6646 filled: st.dev_filled[li],
6647 window: cfg.window,
6648 n_comp: st.dev_n_comp[li],
6649 n_ix: st.dev_n_ix[li],
6650 comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
6651 ix_dst_off: st.dev_n_ix[li] * ew_i,
6652 idx_cap: cfg.window
6653 + if l.indexer.is_some() {
6654 cfg.index_topk
6655 } else {
6656 st.dev_n_comp[li] + comp_extra + 1
6657 },
6658 };
6659 let mut preps = Vec::with_capacity(b);
6660 for t in 0..b {
6661 let mut p = base.clone();
6662 p.filled = (base.filled + t).min(base.window);
6663 let advanced = |ratio: usize| -> usize {
6664 if ratio == 0 {
6665 0
6666 } else {
6667 (0..t).filter(|k| (pos0 + k + 1) % ratio == 0).count()
6668 }
6669 };
6670 if let Some((_, cg)) = base.comp.as_ref() {
6671 p.n_comp = base.n_comp + advanced(cg.ratio);
6672 p.comp_dst_off = base.comp_dst_off + (p.n_comp - base.n_comp) * ew_c;
6673 }
6674 if let Some((_, cg, _, _)) = base.ix.as_ref() {
6675 p.n_ix = base.n_ix + advanced(cg.ratio);
6676 p.ix_dst_off = base.ix_dst_off + (p.n_ix - base.n_ix) * ew_i;
6677 }
6678 preps.push(p);
6679 }
6680
6681 for t in 0..b {
6685 let state = &states[t * hc * dim..(t + 1) * hc * dim];
6686 let (fold, post, comb) = hc_fold_norm(
6687 state,
6688 &l.hc_attn_fn,
6689 &l.hc_attn_scale,
6690 &l.hc_attn_base,
6691 &l.attn_norm,
6692 cfg,
6693 pool,
6694 );
6695 let mut qn = vec![0.0f32; cfg.q_lora_rank];
6696 l.wq_a.matvec(&fold, &mut qn, pool);
6697 rms_weighted(&mut qn, &l.q_norm, cfg.norm_eps);
6698 if !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, state, &post, &comb, &fold, &qn) {
6699 return false;
6700 }
6701 }
6702 let forced_rows: Vec<Option<Vec<usize>>> = ids
6703 .iter()
6704 .take(b)
6705 .map(|&id| {
6706 l.tid2eid
6707 .as_ref()
6708 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, id))
6709 })
6710 .collect();
6711 let dynv = pk.dynslots.lock().unwrap();
6712 let w = crate::gpu_wgpu::Dsv4LayerW {
6713 attn: crate::gpu_wgpu::Dsv4AttnW {
6714 wq_a,
6715 wq_b,
6716 wo_a,
6717 wo_b,
6718 q_norm: &l.q_norm,
6719 sink: &l.attn_sink,
6720 },
6721 moe: crate::gpu_wgpu::Dsv4MoeW {
6722 router: &[],
6723 experts: &pk.tensors,
6724 logits: &[],
6725 bias: pk.bias.as_deref(),
6726 mask: pk.mask.as_deref(),
6727 forced: None,
6728 remap: Some(&dynv.remap),
6729 global: None,
6730 has_shared: true,
6731 shared_weight: 1.0,
6732 preweighted: false,
6733 qwen_softmax: false,
6734 },
6735 hc_ffn_fn: &l.hc_ffn_fn,
6736 hc_ffn_scale: &l.hc_ffn_scale,
6737 hc_ffn_base: &l.hc_ffn_base,
6738 hc_next_fn: None,
6741 hc_next_scale: &l.hc_attn_scale,
6742 hc_next_base: &l.hc_attn_base,
6743 ffn_norm: &l.ffn_norm,
6744 next_norm: &l.attn_norm,
6745 next_q_norm: &l.q_norm,
6746 next_wq_a: None,
6747 router: &pk.router,
6748 };
6749 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
6750 attn: crate::gpu_wgpu::Dsv4AttnGeom {
6751 dim,
6752 nh: cfg.n_heads,
6753 hd,
6754 rd: cfg.rope_head_dim,
6755 q_lora: cfg.q_lora_rank,
6756 o_lora: cfg.o_lora_rank,
6757 o_groups: cfg.o_groups,
6758 eps: cfg.norm_eps,
6759 scale: (hd as f32).powf(-0.5),
6760 },
6761 moe: crate::gpu_wgpu::Dsv4MoeGeom {
6762 hidden: dim,
6763 inter: cfg.moe_inter,
6764 top_k: cfg.top_k,
6765 route_scale: cfg.route_scale,
6766 swiglu_limit: cfg.swiglu_limit,
6767 gu_q2: l
6768 .experts
6769 .first()
6770 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
6771 },
6772 hc,
6773 hc_eps: cfg.hc_eps,
6774 sinkhorn_iters: cfg.hc_sinkhorn_iters,
6775 };
6776 let freqs = if l.compressor.is_some() {
6777 g.inv_freq_compress.as_slice()
6778 } else {
6779 g.inv_freq_window.as_slice()
6780 };
6781 let Some(mut got) = crate::gpu_wgpu::dsv4_layer_batch_partial(
6782 &model,
6783 &w,
6784 geom,
6785 st.kv_id,
6786 li,
6787 b,
6788 &preps,
6789 Some(&forced_rows),
6790 freqs,
6791 pos0,
6792 ) else {
6793 return false;
6794 };
6795 drop(dynv);
6796 let mut cold_sum = vec![0.0f32; b * dim];
6797 cold_step_block(&got.cold_x, b, l, cfg, &got.cold, pool, &mut cold_sum);
6798 for t in 0..b {
6799 let state = &mut got.states[t * hc * dim..(t + 1) * hc * dim];
6800 let post = &got.posts[t * hc..(t + 1) * hc];
6801 let cold = &cold_sum[t * dim..(t + 1) * dim];
6802 for j in 0..hc {
6803 for d in 0..dim {
6804 state[j * dim + d] += post[j] * cold[d];
6805 }
6806 }
6807 }
6808 states[..b * hc * dim].copy_from_slice(&got.states);
6809 if !crate::gpu_wgpu::dsv4_spec_cap_write_host(li, b, hc * dim, &got.states) {
6810 return false;
6811 }
6812 if let Some(first_route) = got.routed.first() {
6813 refill_route_slots(l, cfg, &pk, first_route);
6814 }
6815 for t in 0..b {
6816 let pos = pos0 + t;
6817 st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
6818 if let Some((_, cg, ..)) = base.ix.as_ref() {
6819 if (pos + 1) % cg.ratio == 0 {
6820 st.dev_n_ix[li] += 1;
6821 }
6822 }
6823 if let Some((_, cg)) = base.comp.as_ref() {
6824 if (pos + 1) % cg.ratio == 0 {
6825 st.dev_n_comp[li] += 1;
6826 note_compressed(st.kv_id, li, st.dev_n_comp[li]);
6827 }
6828 }
6829 }
6830 true
6831}
6832
6833#[cfg(feature = "gpu")]
6842#[allow(clippy::too_many_arguments)]
6843pub fn dsv4_verify_chunk(
6844 g: &Dsv4Globals,
6845 layers: &[Dsv4Layer],
6846 cfg: &Dsv4Cfg,
6847 st: &mut Dsv4State,
6848 ids: &[u32],
6849 pos0: usize,
6850 inv_freq: &[f32],
6851 pool: Option<&crate::pool::Pool>,
6852 cap_targets: &[usize],
6853 argmax_out: &mut Vec<u32>,
6854 logits_out: &mut Vec<f32>,
6855 walked_out: &mut Vec<f32>,
6856) -> Option<Dsv4SpecTxn> {
6857 let b = ids.len();
6858 let complete_end = st
6862 .dev_set
6863 .iter()
6864 .enumerate()
6865 .position(|(li, &on)| {
6866 !on || pack_for(&layers[li], cfg, li).is_none_or(|p| !p.route_complete())
6867 })
6868 .unwrap_or(st.dev_set.len());
6869 fn partial_batch_on() -> bool {
6870 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6871 *ON.get_or_init(|| {
6872 std::env::var("CMF_DSV4_PARTIAL_BATCH")
6873 .map(|v| v != "0")
6874 .unwrap_or(true)
6875 })
6876 }
6877 let device_end = if partial_batch_on() {
6878 (complete_end..layers.len())
6879 .take_while(|&li| {
6880 st.dev_set.get(li).copied().unwrap_or(false)
6881 && st.partial_set.get(li).copied().unwrap_or(false)
6882 && pack_for(&layers[li], cfg, li).is_some_and(|p| !p.route_complete())
6883 })
6884 .last()
6885 .map_or(complete_end, |li| li + 1)
6886 } else {
6887 complete_end
6888 };
6889 let full_beyond = st.dev_set[device_end.min(st.dev_set.len())..]
6892 .iter()
6893 .enumerate()
6894 .any(|(i, &on)| on && !st.partial_set.get(device_end + i).copied().unwrap_or(false));
6895 fn host_verify_on() -> bool {
6902 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6903 *ON.get_or_init(|| {
6904 std::env::var("CMF_DSV4_HOST_VERIFY")
6905 .map(|v| v != "0")
6906 .unwrap_or(true)
6911 })
6912 }
6913 if b < 2
6914 || !chain_enabled()
6915 || !st.dev_owned
6916 || st.dev_set.len() != layers.len()
6917 || (device_end == 0 && !host_verify_on())
6918 || full_beyond
6919 {
6920 return None;
6921 }
6922 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
6923 let metas: Vec<(usize, usize, usize, usize)> = (0..device_end)
6925 .map(|li| (li, hd, cfg.window, st.dev_filled[li]))
6926 .collect();
6927 let shadow = crate::gpu_wgpu::dsv4_spec_shadow(st.kv_id, &metas, b)?;
6928 let mut txn = Dsv4SpecTxn {
6929 pos0,
6930 batch: b,
6931 gpu_end: device_end,
6932 dev_filled: st.dev_filled.clone(),
6933 dev_n_comp: st.dev_n_comp.clone(),
6934 dev_n_ix: st.dev_n_ix.clone(),
6935 host: (device_end..layers.len())
6936 .map(|li| (li, host_snap(st, li)))
6937 .collect(),
6938 states: Vec::new(),
6939 host_steps: Vec::new(),
6940 shadow: Some(shadow),
6941 };
6942 let dev_caps: Vec<usize> = cap_targets
6944 .iter()
6945 .copied()
6946 .filter(|&t| t < device_end)
6947 .collect();
6948 crate::gpu_wgpu::dsv4_spec_retain_arm(device_end, &dev_caps);
6949
6950 let mut emb = vec![0.0f32; dim];
6952 let mut states = vec![0.0f32; b * hc * dim];
6953 for (t, &id) in ids.iter().enumerate() {
6954 let mut state = vec![0.0f32; hc * dim];
6955 g.embed.row_f32(id as usize, &mut emb);
6956 for j in 0..hc {
6957 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
6958 }
6959 states[t * hc * dim..(t + 1) * hc * dim].copy_from_slice(&state);
6960 let (folded, post0, comb0) = hc_fold_norm(
6961 &state,
6962 &layers[0].hc_attn_fn,
6963 &layers[0].hc_attn_scale,
6964 &layers[0].hc_attn_base,
6965 &layers[0].attn_norm,
6966 cfg,
6967 pool,
6968 );
6969 let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
6970 layers[0].wq_a.matvec(&folded, &mut qn0, pool);
6971 rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
6972 if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
6973 || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
6974 || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
6975 || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
6976 {
6977 crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
6978 return None;
6979 }
6980 }
6981 let spec_time = {
6982 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6983 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
6984 };
6985 let t0 = std::time::Instant::now();
6986 let mut folded = Vec::new();
6987 st.pos = pos0;
6988 let fp_split = verify_fp_on(pos0) && std::env::var("CMF_DSV4_FP_SPLIT").is_ok_and(|v| v != "0");
6992 let mut ok = true;
6993 if fp_split {
6994 for li in 0..complete_end {
6995 let one = [li];
6996 ok = dsv4_chain_run(
6997 layers,
6998 &one,
6999 cfg,
7000 g,
7001 st,
7002 *ids.last().unwrap(),
7003 &mut folded,
7004 Some(&mut states),
7005 b,
7006 ids,
7007 li == 0,
7008 pool,
7009 );
7010 if !ok {
7011 break;
7012 }
7013 verify_fp("verify", pos0, li, &states[..hc * dim]);
7014 }
7015 } else if complete_end > 0 {
7016 let run: Vec<usize> = (0..complete_end).collect();
7017 ok = dsv4_chain_run(
7018 layers,
7019 &run,
7020 cfg,
7021 g,
7022 st,
7023 *ids.last().unwrap(),
7024 &mut folded,
7025 Some(&mut states),
7026 b,
7027 ids,
7028 true,
7029 pool,
7030 );
7031 if ok {
7032 verify_fp("verify", pos0, complete_end - 1, &states[..hc * dim]);
7033 }
7034 }
7035 if ok {
7036 for li in complete_end..device_end {
7037 if !partial_layer_batch(g, layers, cfg, st, li, &mut states, ids, pos0, b, pool) {
7038 ok = false;
7039 break;
7040 }
7041 verify_fp("verify", pos0, li, &states[..hc * dim]);
7042 }
7043 }
7044 crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
7045 if !ok {
7046 if let Some(sh) = txn.shadow.take() {
7049 let _ = crate::gpu_wgpu::dsv4_spec_restore(&sh);
7050 }
7051 st.dev_filled = txn.dev_filled;
7052 st.dev_n_comp = txn.dev_n_comp;
7053 st.dev_n_ix = txn.dev_n_ix;
7054 st.pos = pos0;
7055 return None;
7056 }
7057 txn.states = states.clone();
7058 let t_chain = t0.elapsed();
7059 if std::env::var("CMF_DSV4_FOLD_DBG").is_ok() {
7060 for li in 0..device_end {
7064 let Some(ixr) = &layers[li].indexer else {
7065 continue;
7066 };
7067 let ratio = ixr.compressor.ratio;
7068 for t in 0..b {
7069 if (pos0 + t + 1) % ratio == 0 {
7070 let ew = {
7071 let w = ixr.compressor.wkv.rows();
7072 if ixr.compressor.overlap { w / 2 } else { w }
7073 };
7074 let idx_new = txn.dev_n_ix[li]
7075 + (0..=t).filter(|k| (pos0 + k + 1) % ratio == 0).count()
7076 - 1;
7077 if let Some(v) =
7078 crate::gpu_wgpu::dsv4_dbg_read_ix(st.kv_id, li, idx_new * ew, ew.min(8))
7079 {
7080 let sum: f32 = v.iter().sum();
7081 eprintln!(
7082 "[fold] li={li} pos={} entry={idx_new} head={:?} sum={sum:.6}",
7083 pos0 + t,
7084 &v[..4.min(v.len())]
7085 );
7086 }
7087 }
7088 }
7089 }
7090 }
7091
7092 let mut scratch = HcScratch::new(cfg);
7094 argmax_out.clear();
7095 logits_out.clear();
7096 logits_out.resize(b * cfg.vocab, 0.0);
7097 let mut head_in = vec![0.0f32; b * dim];
7098 let mut host_steps: Vec<(usize, Vec<HostLayerSnap>)> = Vec::new();
7099 host_tail_walk_batch(
7100 g,
7101 layers,
7102 cfg,
7103 st,
7104 device_end,
7105 &mut states,
7106 ids,
7107 pos0,
7108 b,
7109 inv_freq,
7110 &mut scratch,
7111 pool,
7112 Some(&mut host_steps),
7113 );
7114 txn.host_steps = host_steps;
7115 for t in 0..b {
7116 let state = &states[t * hc * dim..(t + 1) * hc * dim];
7117 let h = &mut head_in[t * dim..(t + 1) * dim];
7118 hc_head_fold(
7119 state,
7120 &g.hc_head_fn,
7121 g.hc_head_scale,
7122 &g.hc_head_base,
7123 cfg,
7124 pool,
7125 h,
7126 );
7127 rms_weighted(h, &g.norm, cfg.norm_eps);
7128 }
7129 let batch_head = std::env::var("CMF_DSV4_SPEC_BATCH_HEAD").is_ok_and(|v| v != "0");
7134 let head_gpu = batch_head
7135 && g.head.model_idx().is_some_and(|hi| {
7136 let model = layers[0].experts.first().and_then(|e| e.w1.model_arc());
7137 model.is_some_and(|m| {
7138 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
7139 &m, hi, &head_in, b, cfg.vocab, dim, logits_out,
7140 )
7141 })
7142 });
7143 for t in 0..b {
7144 if !head_gpu {
7145 let h = &head_in[t * dim..(t + 1) * dim];
7146 g.head
7147 .matvec(h, &mut logits_out[t * cfg.vocab..(t + 1) * cfg.vocab], pool);
7148 }
7149 let row = &logits_out[t * cfg.vocab..(t + 1) * cfg.vocab];
7150 let mut best = 0usize;
7151 for v in 1..cfg.vocab {
7152 if row[v] > row[best] {
7153 best = v;
7154 }
7155 }
7156 argmax_out.push(best as u32);
7157 }
7158 walked_out.clear();
7159 walked_out.extend_from_slice(&states);
7160 st.pos = pos0 + b;
7161 if spec_time {
7162 eprintln!(
7163 "verify: тень+сид+цепочка {:.1} мс, хвост+голова {:.1} мс",
7164 t_chain.as_secs_f64() * 1e3,
7165 (t0.elapsed() - t_chain).as_secs_f64() * 1e3,
7166 );
7167 }
7168 Some(txn)
7169}
7170
7171#[cfg(feature = "gpu")]
7179pub fn dsv4_spec_finish(
7180 g: &Dsv4Globals,
7181 layers: &[Dsv4Layer],
7182 cfg: &Dsv4Cfg,
7183 st: &mut Dsv4State,
7184 mut txn: Dsv4SpecTxn,
7185 accepted: usize,
7186 ids: &[u32],
7187 inv_freq: &[f32],
7188 pool: Option<&crate::pool::Pool>,
7189) -> bool {
7190 macro_rules! sfail {
7191 ($($t:tt)*) => {{
7192 if std::env::var("CMF_DSV4_SPEC_DEBUG").is_ok() {
7193 eprintln!("spec_finish: {}", format_args!($($t)*));
7194 }
7195 return false;
7196 }};
7197 }
7198 let b = txn.batch;
7199 let k = accepted.min(b);
7200 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
7201 let win_metas: Vec<(usize, usize, usize, usize)> = (0..txn.gpu_end)
7204 .map(|li| (li, txn.dev_filled[li], cfg.window, hd))
7205 .collect();
7206 if !crate::gpu_wgpu::dsv4_spec_commit_windows(st.kv_id, &win_metas, b, k) {
7207 sfail!("коммит окон");
7208 }
7209 if k == b {
7210 return true;
7213 }
7214 let Some(sh) = txn.shadow.take() else {
7216 sfail!("нет тени")
7217 };
7218 if !crate::gpu_wgpu::dsv4_spec_restore(&sh) {
7219 sfail!("restore");
7220 }
7221 let Some(model) = layers[0].experts.first().and_then(|e| e.w1.model_arc()) else {
7222 sfail!("нет модели");
7223 };
7224 let mut plan: Vec<(usize, crate::gpu_wgpu::Dsv4Prep)> = Vec::new();
7225 let mut freqs_own: Vec<&[f32]> = Vec::new();
7226 for li in 0..txn.gpu_end {
7227 let l = &layers[li];
7228 let Some(wkv) = l.wkv.model_idx() else {
7229 sfail!("wkv слоя {li}")
7230 };
7231 let comp = match &l.compressor {
7232 None => None,
7233 Some(cp) => {
7234 let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
7235 sfail!("компрессор слоя {li}");
7236 };
7237 Some((
7238 crate::gpu_wgpu::Dsv4CompW {
7239 wkv: a,
7240 wgate: bx,
7241 norm: &cp.norm,
7242 ape: &cp.ape,
7243 },
7244 crate::gpu_wgpu::Dsv4CompGeom {
7245 width: cp.wkv.rows(),
7246 hidden: dim,
7247 ratio: cp.ratio,
7248 overlap: cp.overlap,
7249 rope_dim: cfg.rope_head_dim,
7250 eps: cfg.norm_eps,
7251 },
7252 ))
7253 }
7254 };
7255 let ix = match &l.indexer {
7256 None => None,
7257 Some(ixr) => {
7258 let cp = &ixr.compressor;
7259 let (Some(a), Some(bx), Some(qb), Some(wp)) = (
7260 cp.wkv.model_idx(),
7261 cp.wgate.model_idx(),
7262 ixr.wq_b.model_idx(),
7263 ixr.weights_proj.model_idx(),
7264 ) else {
7265 sfail!("индексер слоя {li}");
7266 };
7267 let ih = ixr.weights_proj.rows();
7268 Some((
7269 crate::gpu_wgpu::Dsv4CompW {
7270 wkv: a,
7271 wgate: bx,
7272 norm: &cp.norm,
7273 ape: &cp.ape,
7274 },
7275 crate::gpu_wgpu::Dsv4CompGeom {
7276 width: cp.wkv.rows(),
7277 hidden: dim,
7278 ratio: cp.ratio,
7279 overlap: cp.overlap,
7280 rope_dim: cfg.rope_head_dim,
7281 eps: cfg.norm_eps,
7282 },
7283 crate::gpu_wgpu::Dsv4IxW {
7284 wq_b: qb,
7285 weights_proj: wp,
7286 },
7287 crate::gpu_wgpu::Dsv4IxGeom {
7288 ih,
7289 idim: ixr.wq_b.rows() / ih.max(1),
7290 q_lora: cfg.q_lora_rank,
7291 hidden: dim,
7292 rope_dim: cfg.rope_head_dim,
7293 eps: cfg.norm_eps,
7294 top_k: cfg.index_topk,
7295 window: cfg.window,
7296 },
7297 ))
7298 }
7299 };
7300 let ew_c = comp.as_ref().map_or(
7301 0,
7302 |(_, cg)| {
7303 if cg.overlap { cg.width / 2 } else { cg.width }
7304 },
7305 );
7306 let ew_i = ix.as_ref().map_or(
7307 0,
7308 |(_, cg, _, _)| {
7309 if cg.overlap { cg.width / 2 } else { cg.width }
7310 },
7311 );
7312 let prep = crate::gpu_wgpu::Dsv4Prep {
7313 wkv,
7314 kv_norm: &l.kv_norm,
7315 comp,
7316 ix,
7317 filled: txn.dev_filled[li],
7318 window: cfg.window,
7319 n_comp: txn.dev_n_comp[li],
7320 n_ix: txn.dev_n_ix[li],
7321 comp_dst_off: cfg.window * hd + txn.dev_n_comp[li] * ew_c,
7322 ix_dst_off: txn.dev_n_ix[li] * ew_i,
7323 idx_cap: cfg.window
7324 + if l.indexer.is_some() {
7325 cfg.index_topk
7326 } else {
7327 0
7328 },
7329 };
7330 let fr = if l.compressor.is_some() {
7331 g.inv_freq_compress.as_slice()
7332 } else {
7333 g.inv_freq_window.as_slice()
7334 };
7335 freqs_own.push(if fr.is_empty() { inv_freq } else { fr });
7336 plan.push((li, prep));
7337 }
7338 if !crate::gpu_wgpu::dsv4_spec_replay(
7339 &model,
7340 &plan,
7341 st.kv_id,
7342 txn.pos0,
7343 b,
7344 k,
7345 &freqs_own,
7346 hd,
7347 dim,
7348 cfg.rope_head_dim,
7349 cfg.norm_eps,
7350 true,
7351 ) {
7352 sfail!("replay k={k}");
7353 }
7354 let advanced = |ratio: usize| -> usize {
7356 if ratio == 0 {
7357 return 0;
7358 }
7359 (0..k).filter(|t| (txn.pos0 + t + 1) % ratio == 0).count()
7360 };
7361 for li in 0..txn.gpu_end {
7362 let l = &layers[li];
7363 st.dev_filled[li] = (txn.dev_filled[li] + k).min(cfg.window);
7364 let ac = l.compressor.as_ref().map_or(0, |cp| advanced(cp.ratio));
7365 let ai = l
7366 .indexer
7367 .as_ref()
7368 .map_or(0, |ix| advanced(ix.compressor.ratio));
7369 st.dev_n_comp[li] = txn.dev_n_comp[li] + ac;
7370 st.dev_n_ix[li] = txn.dev_n_ix[li] + ai;
7371 note_compressed(st.kv_id, li, st.dev_n_comp[li]);
7372 }
7373 if k >= 1 && txn.host_steps.iter().all(|(_, v)| v.len() >= k) && !txn.host_steps.is_empty() {
7376 for (li, v) in &txn.host_steps {
7377 host_restore(st, *li, &v[k - 1]);
7378 }
7379 } else {
7380 for (li, snap) in &txn.host {
7381 host_restore(st, *li, snap);
7382 }
7383 let mut scratch = HcScratch::new(cfg);
7384 let mut states = txn.states.clone();
7385 host_tail_walk_batch(
7386 g,
7387 layers,
7388 cfg,
7389 st,
7390 txn.gpu_end,
7391 &mut states[..k * hc * dim],
7392 ids,
7393 txn.pos0,
7394 k,
7395 inv_freq,
7396 &mut scratch,
7397 pool,
7398 None,
7399 );
7400 }
7401 st.pos = txn.pos0 + k;
7402 true
7403}
7404
7405pub fn forward_chunk(
7406 g: &Dsv4Globals,
7407 layers: &[Dsv4Layer],
7408 cfg: &Dsv4Cfg,
7409 st: &mut Dsv4State,
7410 ids: &[u32],
7411 pos0: usize,
7412 inv_freq: &[f32],
7413 pool: Option<&crate::pool::Pool>,
7414 logits: &mut Vec<f32>,
7415 want_logits: bool,
7416) {
7417 let bs = batch_prefill();
7418 if bs > 1 {
7419 let mut i = 0;
7426 if !st.dev_owned && !ids.is_empty() {
7427 st.pos = pos0;
7428 forward_token_inner(
7429 g,
7430 layers,
7431 cfg,
7432 st,
7433 ids[0],
7434 inv_freq,
7435 pool,
7436 logits,
7437 ids.len() == 1,
7438 );
7439 i = 1;
7440 }
7441 while i < ids.len() {
7442 let end = (i + bs).min(ids.len());
7443 st.pos = pos0 + i;
7444 if !forward_chunk_batched(
7445 g,
7446 layers,
7447 cfg,
7448 st,
7449 &ids[i..end],
7450 pos0 + i,
7451 inv_freq,
7452 pool,
7453 logits,
7454 want_logits && end == ids.len(),
7455 ) {
7456 break;
7457 }
7458 i = end;
7459 }
7460 if i == ids.len() {
7461 return;
7462 }
7463 for (k, &id) in ids.iter().enumerate().skip(i) {
7465 st.pos = pos0 + k;
7466 let last = want_logits && k + 1 == ids.len();
7467 forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
7468 }
7469 return;
7470 }
7471 for (i, &id) in ids.iter().enumerate() {
7472 st.pos = pos0 + i;
7473 let last = want_logits && i + 1 == ids.len();
7474 forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
7475 }
7476}
7477
7478pub fn forward_token(
7479 g: &Dsv4Globals,
7480 layers: &[Dsv4Layer],
7481 cfg: &Dsv4Cfg,
7482 st: &mut Dsv4State,
7483 token_id: u32,
7484 inv_freq: &[f32],
7485 pool: Option<&crate::pool::Pool>,
7486 logits: &mut Vec<f32>,
7487) {
7488 forward_token_inner(g, layers, cfg, st, token_id, inv_freq, pool, logits, true);
7489}
7490
7491#[allow(clippy::too_many_arguments)]
7492fn forward_token_inner(
7493 g: &Dsv4Globals,
7494 layers: &[Dsv4Layer],
7495 cfg: &Dsv4Cfg,
7496 st: &mut Dsv4State,
7497 token_id: u32,
7498 inv_freq: &[f32],
7499 pool: Option<&crate::pool::Pool>,
7500 logits: &mut Vec<f32>,
7501 want_logits: bool,
7503) {
7504 let _t_all = prof::on().then(std::time::Instant::now);
7505 let _all_guard = Charge(_t_all, &prof::ALL_NS);
7506 let (hc, dim) = (cfg.hc_mult, cfg.dim);
7507
7508 let mut emb = vec![0.0f32; dim];
7510 g.embed.row_f32(token_id as usize, &mut emb);
7511 let mut state = vec![0.0f32; hc * dim];
7512 for j in 0..hc {
7513 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
7514 }
7515
7516 let mut scratch = HcScratch::new(cfg);
7517 let mut dump: Vec<String> = Vec::new();
7518 if dump_path().is_some() {
7519 dump.push(format!("\"embed\":{}", vec_json(&emb)));
7520 PICKED.with(|p| p.borrow_mut().clear());
7521 BODY.with(|b| b.borrow_mut().clear());
7522 dump.push(",\"layers\":[".into());
7523 }
7524 if trace_on() {
7525 eprintln!(
7526 "[dsv4] tok={token_id} pos={} embed rms={:.5}",
7527 st.pos,
7528 rms_of(&emb)
7529 );
7530 }
7531 #[cfg(feature = "gpu")]
7533 let layer_frames = gpu_layer_enabled()
7534 && dsv4_layer_loop(
7535 &mut state,
7536 layers,
7537 g,
7538 cfg,
7539 st,
7540 token_id,
7541 inv_freq,
7542 pool,
7543 &mut scratch,
7544 );
7545 #[cfg(not(feature = "gpu"))]
7546 let layer_frames = false;
7547
7548 #[cfg(feature = "gpu")]
7555 let hc_dev = hc_on_device()
7556 && !layer_frames
7557 && gpu_attn_enabled()
7558 && gpu_moe2_enabled()
7559 && dump_path().is_none();
7560 #[cfg(not(feature = "gpu"))]
7561 let hc_dev = false;
7562 #[cfg(feature = "gpu")]
7569 let two_frame_done = hc_dev
7570 && dsv4_two_frame_loop(
7571 &mut state,
7572 layers,
7573 g,
7574 cfg,
7575 st,
7576 token_id,
7577 inv_freq,
7578 pool,
7579 &mut scratch,
7580 );
7581 #[cfg(not(feature = "gpu"))]
7582 let two_frame_done = false;
7583 if !two_frame_done {
7584 for (li, l) in layers.iter().enumerate() {
7585 if layer_frames {
7586 break;
7587 }
7588 hc_block(
7590 &mut state,
7591 &l.hc_attn_fn,
7592 &l.hc_attn_scale,
7593 &l.hc_attn_base,
7594 &l.attn_norm,
7595 cfg,
7596 &mut scratch,
7597 pool,
7598 |folded, out| {
7599 if dump_path().is_some() {
7600 BODY.with(|b| b.borrow_mut().push(vec_json(folded)));
7603 }
7604 let freqs = if l.compressor.is_some() {
7606 &g.inv_freq_compress
7607 } else {
7608 &g.inv_freq_window
7609 };
7610 let freqs = if freqs.is_empty() {
7611 inv_freq
7612 } else {
7613 freqs.as_slice()
7614 };
7615 attention_step(folded, l, cfg, st, li, freqs, pool, None, out);
7616 if dump_path().is_some() {
7617 BODY.with(|b| b.borrow_mut().push(vec_json(out)));
7618 }
7619 },
7620 );
7621 if dump_path().is_some() {
7622 dump.push(format!(
7625 "{}{}",
7626 if li == 0 { "" } else { "," },
7627 vec_json(&state)
7628 ));
7629 }
7630 let _t_hc2 = prof::on().then(std::time::Instant::now);
7632 hc_block(
7633 &mut state,
7634 &l.hc_ffn_fn,
7635 &l.hc_ffn_scale,
7636 &l.hc_ffn_base,
7637 &l.ffn_norm,
7638 cfg,
7639 &mut scratch,
7640 pool,
7641 |folded, out| moe_step(folded, l, cfg, token_id, li, pool, out),
7642 );
7643 if let Some(t) = _t_hc2 {
7644 prof::HC_NS.fetch_add(
7647 t.elapsed().as_nanos() as u64,
7648 std::sync::atomic::Ordering::Relaxed,
7649 );
7650 }
7651 if dump_path().is_some() {
7652 dump.push(format!(",{}", vec_json(&state)));
7653 }
7654 if trace_on() && (st.pos % 64 == 0 || st.pos == 199) {
7655 eprintln!(
7656 "[dsv4] кеши слоя {li}: окно={} сжатых={} индекс={} (ratio={:?})",
7657 st.window[li].len() / cfg.head_dim.max(1),
7658 st.compressed[li].len() / cfg.head_dim.max(1),
7659 st.index_kv[li].len().max(1) / 128,
7660 l.compressor.as_ref().map(|c| (c.ratio, c.overlap)),
7661 );
7662 }
7663 if trace_on() {
7664 let bad = state.iter().filter(|v| !v.is_finite()).count();
7665 eprintln!(
7666 "[dsv4] layer {li:>2}: rms={:.5}{}",
7667 rms_of(&state),
7668 if bad > 0 {
7669 format!(" NON-FINITE x{bad}")
7670 } else {
7671 String::new()
7672 }
7673 );
7674 }
7675 dspark_note(li, &state, cfg);
7676 }
7677 }
7678 st.pos += 1;
7679
7680 let mut h = vec![0.0f32; dim];
7682 hc_head_fold(
7683 &state,
7684 &g.hc_head_fn,
7685 g.hc_head_scale,
7686 &g.hc_head_base,
7687 cfg,
7688 pool,
7689 &mut h,
7690 );
7691 if !want_logits {
7692 logits.clear();
7693 return;
7694 }
7695 let _t_head = prof::on().then(std::time::Instant::now);
7696 rms_weighted(&mut h, &g.norm, cfg.norm_eps);
7697 logits.clear();
7698 logits.resize(g.head.rows(), 0.0);
7699 g.head.matvec(&h, logits, pool);
7700 if let Some(t) = _t_head {
7701 prof::HEAD_NS.fetch_add(
7702 t.elapsed().as_nanos() as u64,
7703 std::sync::atomic::Ordering::Relaxed,
7704 );
7705 }
7706 if dump_path().is_some() {
7707 dump.push("]".into());
7708 let picked = PICKED.with(|p| {
7709 p.borrow()
7710 .iter()
7711 .map(|v| {
7712 format!(
7713 "[{}]",
7714 v.iter()
7715 .map(|e| e.to_string())
7716 .collect::<Vec<_>>()
7717 .join(",")
7718 )
7719 })
7720 .collect::<Vec<_>>()
7721 .join(",")
7722 });
7723 dump.push(format!(",\"experts\":[{picked}]"));
7724 let body = BODY.with(|b| b.borrow().join(","));
7725 dump.push(format!(",\"attn_io\":[{body}]"));
7726 dump_line(&format!(
7727 "{{\"tok\":{token_id},\"pos\":{},{},\"head\":{},\"logits\":{}}}",
7728 st.pos - 1,
7729 dump.join(""),
7730 vec_json(&h),
7731 vec_json(logits)
7732 ));
7733 }
7734 if trace_on() {
7735 let (mut top, mut best) = (0usize, f32::NEG_INFINITY);
7736 for (i, &v) in logits.iter().enumerate() {
7737 if v > best {
7738 best = v;
7739 top = i;
7740 }
7741 }
7742 let lo = logits.iter().cloned().fold(f32::MAX, f32::min);
7743 eprintln!(
7744 "[dsv4] head: rms={:.5} logits[{}..{:.3}] argmax={top}",
7745 rms_of(&h),
7746 format_args!("{lo:.3}"),
7747 best
7748 );
7749 }
7750}
7751
7752pub fn load(
7759 model: &std::sync::Arc<cortiq_core::CmfModel>,
7760 cfg: &Dsv4Cfg,
7761 n_layers: usize,
7762) -> Result<(Dsv4Globals, Vec<Dsv4Layer>), String> {
7763 let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
7764 crate::qtensor::QTensor::from_model(model, name)
7765 };
7766 let f = |name: &str| -> Result<Vec<f32>, String> {
7770 crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
7771 };
7772
7773 let rope_of = |base: f32, yarn: bool| -> Vec<f32> {
7778 if yarn {
7779 crate::attention::yarn_inv_freq(cfg.rope_head_dim, base, 16.0, 65536, 32.0, 1.0)
7780 } else {
7781 crate::attention::rope_inv_freq(cfg.rope_head_dim, base)
7782 }
7783 };
7784 let globals = Dsv4Globals {
7785 inv_freq_compress: rope_of(160_000.0, true),
7786 inv_freq_window: rope_of(10_000.0, false),
7787 embed: q("model.embed_tokens.weight")?,
7788 norm: f("model.norm.weight")?,
7789 head: q("lm_head.weight")?,
7790 hc_head_fn: f("model.hc_head_fn")?,
7791 hc_head_base: f("model.hc_head_base")?,
7792 hc_head_scale: *f("model.hc_head_scale")?
7793 .first()
7794 .ok_or("dsv4: empty hc_head_scale")?,
7795 };
7796
7797 let mut layers = Vec::with_capacity(n_layers);
7798 for li in 0..n_layers {
7799 layers.push(load_layer(
7800 model,
7801 cfg,
7802 &format!("model.layers.{li}"),
7803 Scheme::Main,
7804 )?);
7805 }
7806 #[cfg(feature = "gpu")]
7812 if crate::gpu_wgpu::host_banks_on() {
7813 let sets: Vec<(usize, Vec<(usize, usize, usize)>, bool)> = layers
7815 .iter()
7816 .filter_map(|l| {
7817 let idx3 = |e: &Dsv4Expert| {
7818 Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
7819 };
7820 let mut v: Vec<_> = l.experts.iter().filter_map(idx3).collect();
7821 let first = v.first()?.0;
7822 v.push(idx3(&l.shared)?);
7823 let gu_q2 =
7824 l.experts.first()?.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
7825 Some((first, v, gu_q2))
7826 })
7827 .collect();
7828 let m2 = model.clone();
7829 let (inter, dim) = (cfg.moe_inter, cfg.dim);
7830 std::thread::spawn(move || {
7831 for (first, v, gu_q2) in sets {
7832 crate::gpu_wgpu::dsv4_host_bank_build(&m2, first, &v, inter, dim, gu_q2);
7833 }
7834 tracing::info!("host banks: built");
7835 });
7836 }
7837 #[cfg(feature = "gpu")]
7838 {
7839 let masks: Vec<Option<Vec<bool>>> = layers.iter().map(|l| l.mask.clone()).collect();
7840 crate::gpu_wgpu::prefetch_tier(model, &|name: &str| {
7841 let Some(i) = name.find(".experts.") else {
7842 return false;
7843 };
7844 let rest = &name[i + 9..];
7845 let e: usize = match rest[..rest.find('.').unwrap_or(rest.len())].parse() {
7846 Ok(v) => v,
7847 Err(_) => return false,
7848 };
7849 let li: usize = {
7850 let Some(j) = name.find("layers.") else {
7851 return false;
7852 };
7853 let r = &name[j + 7..];
7854 match r[..r.find('.').unwrap_or(r.len())].parse() {
7855 Ok(v) => v,
7856 Err(_) => return false,
7857 }
7858 };
7859 match masks.get(li).and_then(|m| m.as_ref()) {
7860 Some(m) => m.get(e).copied().unwrap_or(false),
7861 None => true,
7862 }
7863 });
7864 }
7865 Ok((globals, layers))
7866}
7867
7868#[derive(Clone, Copy, PartialEq, Eq, Debug)]
7875pub enum Scheme {
7876 Main,
7877 Mtp,
7878}
7879
7880impl Scheme {
7881 fn attn(self) -> &'static str {
7882 match self {
7883 Scheme::Main => "self_attn",
7884 Scheme::Mtp => "attn",
7885 }
7886 }
7887 fn attn_norm(self) -> &'static str {
7888 match self {
7889 Scheme::Main => "input_layernorm.weight",
7890 Scheme::Mtp => "attn_norm.weight",
7891 }
7892 }
7893 fn ffn_norm(self) -> &'static str {
7894 match self {
7895 Scheme::Main => "post_attention_layernorm.weight",
7896 Scheme::Mtp => "ffn_norm.weight",
7897 }
7898 }
7899 fn mlp(self) -> &'static str {
7900 match self {
7901 Scheme::Main => "mlp",
7902 Scheme::Mtp => "ffn",
7903 }
7904 }
7905 fn gate_bias(self) -> &'static str {
7908 match self {
7909 Scheme::Main => "expert_bias",
7910 Scheme::Mtp => "gate.bias",
7911 }
7912 }
7913 fn shared(self) -> &'static str {
7914 match self {
7915 Scheme::Main => "shared_expert",
7916 Scheme::Mtp => "shared_experts",
7917 }
7918 }
7919 fn w(self, i: u8) -> &'static str {
7921 match (self, i) {
7922 (Scheme::Main, 1) => "gate_proj.weight",
7923 (Scheme::Main, 2) => "down_proj.weight",
7924 (Scheme::Main, _) => "up_proj.weight",
7925 (Scheme::Mtp, 1) => "w1.weight",
7926 (Scheme::Mtp, 2) => "w2.weight",
7927 (Scheme::Mtp, _) => "w3.weight",
7928 }
7929 }
7930}
7931
7932pub fn load_layer(
7934 model: &std::sync::Arc<cortiq_core::CmfModel>,
7935 cfg: &Dsv4Cfg,
7936 p: &str,
7937 s: Scheme,
7938) -> Result<Dsv4Layer, String> {
7939 let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
7940 crate::qtensor::QTensor::from_model(model, name)
7941 };
7942 let f = |name: &str| -> Result<Vec<f32>, String> {
7943 crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
7944 };
7945 let opt_f = |name: &str| -> Option<Vec<f32>> { f(name).ok() };
7946 let at = s.attn();
7947 let ml = s.mlp();
7948 {
7949 let scale3 = |name: &str| -> Result<[f32; 3], String> {
7950 let v = f(name)?;
7951 if v.len() < 3 {
7952 return Err(format!("{name}: expected 3 scales, got {}", v.len()));
7953 }
7954 Ok([v[0], v[1], v[2]])
7955 };
7956 let compressor = match q(&format!("{p}.{at}.compressor.wkv.weight")) {
7959 Ok(wkv) => {
7960 let ape = f(&format!("{p}.{at}.compressor.ape"))?;
7961 let width = wkv.rows();
7964 let ratio = (ape.len() / width.max(1)).max(1);
7965 Some(Dsv4Compressor {
7966 wkv,
7967 wgate: q(&format!("{p}.{at}.compressor.wgate.weight"))?,
7968 norm: f(&format!("{p}.{at}.compressor.norm.weight"))?,
7969 ape,
7970 ratio,
7971 overlap: ratio == 4,
7972 })
7973 }
7974 Err(_) => None,
7975 };
7976 let indexer = match q(&format!("{p}.{at}.indexer.wq_b.weight")) {
7977 Ok(wq_b) => {
7978 let ape = f(&format!("{p}.{at}.indexer.compressor.ape"))?;
7979 let cwkv = q(&format!("{p}.{at}.indexer.compressor.wkv.weight"))?;
7980 let width = cwkv.rows();
7981 let ratio = (ape.len() / width.max(1)).max(1);
7982 Some(Dsv4Indexer {
7983 wq_b,
7984 weights_proj: q(&format!("{p}.{at}.indexer.weights_proj.weight"))?,
7985 compressor: Dsv4Compressor {
7986 wkv: cwkv,
7987 wgate: q(&format!("{p}.{at}.indexer.compressor.wgate.weight"))?,
7988 norm: f(&format!("{p}.{at}.indexer.compressor.norm.weight"))?,
7989 ape,
7990 ratio,
7991 overlap: ratio == 4,
7992 },
7993 })
7994 }
7995 Err(_) => None,
7996 };
7997
7998 let mut experts = Vec::with_capacity(cfg.n_routed_experts);
7999 for e in 0..cfg.n_routed_experts {
8000 let ep = format!("{p}.{ml}.experts.{e}");
8001 experts.push(Dsv4Expert {
8002 w1: q(&format!("{ep}.{w}", w = s.w(1)))?,
8003 w2: q(&format!("{ep}.{w}", w = s.w(2)))?,
8004 w3: q(&format!("{ep}.{w}", w = s.w(3)))?,
8005 });
8006 }
8007
8008 Ok(Dsv4Layer {
8009 attn_norm: f(&format!("{p}.{an}", an = s.attn_norm()))?,
8010 ffn_norm: f(&format!("{p}.{fnm}", fnm = s.ffn_norm()))?,
8011 wq_a: q(&format!("{p}.{at}.wq_a.weight"))?,
8012 q_norm: f(&format!("{p}.{at}.q_norm.weight"))?,
8013 wq_b: q(&format!("{p}.{at}.wq_b.weight"))?,
8014 wkv: q(&format!("{p}.{at}.wkv.weight"))?,
8015 kv_norm: f(&format!("{p}.{at}.kv_norm.weight"))?,
8016 wo_a: q(&format!("{p}.{at}.wo_a.weight"))?,
8017 wo_b: q(&format!("{p}.{at}.wo_b.weight"))?,
8018 attn_sink: f(&format!("{p}.{at}.attn_sink"))?,
8019 compressor,
8020 indexer,
8021 hc_attn_fn: f(&format!("{p}.hc_attn_fn"))?,
8022 hc_attn_base: f(&format!("{p}.hc_attn_base"))?,
8023 hc_attn_scale: scale3(&format!("{p}.hc_attn_scale"))?,
8024 hc_ffn_fn: f(&format!("{p}.hc_ffn_fn"))?,
8025 hc_ffn_base: f(&format!("{p}.hc_ffn_base"))?,
8026 hc_ffn_scale: scale3(&format!("{p}.hc_ffn_scale"))?,
8027 gate: q(&format!("{p}.{ml}.gate.weight"))?,
8028 gate_bias: opt_f(&format!("{p}.{ml}.{b}", b = s.gate_bias())),
8031 tid2eid: opt_f(&format!("{p}.{ml}.tid2eid")),
8032 experts,
8033 mask: if model.tensor(&format!("{p}.{ml}.tid2eid")).is_some() {
8034 None
8035 } else {
8036 crate::loader::moe_task_mask(model, &format!("{p}."), cfg.n_routed_experts)
8037 },
8038 shared: Dsv4Expert {
8039 w1: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(1)))?,
8040 w2: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(2)))?,
8041 w3: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(3)))?,
8042 },
8043 })
8044 }
8045}
8046
8047pub struct Dsv4Mtp {
8056 pub layer: Dsv4Layer,
8057 pub main_proj: Option<crate::qtensor::QTensor>,
8061 pub main_norm: Option<Vec<f32>>,
8062 pub norm: Option<Vec<f32>>,
8064 pub hc_head_fn: Option<Vec<f32>>,
8065 pub hc_head_base: Option<Vec<f32>>,
8066 pub hc_head_scale: Option<f32>,
8067 pub confidence: Option<crate::qtensor::QTensor>,
8068 pub markov_w1: Option<crate::qtensor::QTensor>,
8073 pub markov_w2: Option<crate::qtensor::QTensor>,
8074}
8075
8076pub fn load_mtp(
8080 model: &std::sync::Arc<cortiq_core::CmfModel>,
8081 cfg: &Dsv4Cfg,
8082 max_depth: usize,
8083) -> Vec<Dsv4Mtp> {
8084 let f = |name: &str| -> Option<Vec<f32>> {
8085 crate::loader::load_f32(model, name, &crate::loader::Overlay::None).ok()
8086 };
8087 let mut out = Vec::new();
8088 for d in 0..max_depth {
8089 let p = format!("model.mtp.{d}");
8090 if model.tensor(&format!("{p}.attn.wq_a.weight")).is_none() {
8094 break;
8095 }
8096 let layer = match load_layer(model, cfg, &p, Scheme::Mtp) {
8097 Ok(l) => l,
8098 Err(e) => {
8099 eprintln!("MTP {d}: пропущен, {e}");
8100 break;
8101 }
8102 };
8103 out.push(Dsv4Mtp {
8104 layer,
8105 main_proj: crate::qtensor::QTensor::from_model(model, &format!("{p}.main_proj.weight"))
8106 .ok(),
8107 main_norm: f(&format!("{p}.main_norm.weight")),
8108 norm: f(&format!("{p}.norm.weight")),
8109 hc_head_fn: f(&format!("{p}.hc_head_fn")),
8110 hc_head_base: f(&format!("{p}.hc_head_base")),
8111 hc_head_scale: f(&format!("{p}.hc_head_scale")).and_then(|v| v.first().copied()),
8112 confidence: crate::qtensor::QTensor::from_model(
8113 model,
8114 &format!("{p}.confidence_head.proj.weight"),
8115 )
8116 .ok(),
8117 markov_w1: crate::qtensor::QTensor::from_model(
8118 model,
8119 &format!("{p}.markov_head.markov_w1.weight"),
8120 )
8121 .ok(),
8122 markov_w2: crate::qtensor::QTensor::from_model(
8123 model,
8124 &format!("{p}.markov_head.markov_w2.weight"),
8125 )
8126 .ok(),
8127 });
8128 }
8129 dspark_apply_mask(&mut out);
8130 if !out.is_empty() {
8131 let mp = out
8132 .iter()
8133 .find_map(|m| m.main_proj.as_ref())
8134 .map(|t| format!("[{}, {}]", t.rows(), t.cols()))
8135 .unwrap_or_else(|| "нет".into());
8136 eprintln!(
8137 "MTP: {} стади(я/и/й), main_proj {mp}, экспертов {}, \
8138 голова уверенности {}, марков {}",
8139 out.len(),
8140 out[0].layer.experts.len(),
8141 if out.iter().any(|m| m.confidence.is_some()) {
8142 "есть"
8143 } else {
8144 "нет"
8145 },
8146 if out.iter().any(|m| m.markov_w1.is_some()) {
8147 "есть"
8148 } else {
8149 "нет"
8150 },
8151 );
8152 }
8153 out
8154}
8155
8156#[cfg(test)]
8157mod tests {
8158 use super::*;
8159
8160 fn toy() -> (Dsv4Globals, Vec<Dsv4Layer>, Dsv4Cfg) {
8165 use crate::qtensor::QTensor;
8166 let cfg = Dsv4Cfg {
8167 dim: 32,
8168 n_heads: 4,
8169 head_dim: 8,
8170 rope_head_dim: 4,
8171 q_lora_rank: 16,
8172 o_lora_rank: 16,
8173 o_groups: 2,
8174 hc_mult: 4,
8175 hc_sinkhorn_iters: 20,
8176 hc_eps: 1e-6,
8177 norm_eps: 1e-6,
8178 n_routed_experts: 8,
8179 top_k: 2,
8180 moe_inter: 16,
8181 route_scale: 1.0,
8182 swiglu_limit: 10.0,
8183 window: 6,
8184 index_topk: 8,
8185 vocab: 24,
8186 };
8187 let w = |n: usize, seed: usize| -> Vec<f32> {
8190 (0..n)
8191 .map(|i| (((i * 7 + seed * 13) % 101) as f32 / 101.0 - 0.5) * 0.3)
8192 .collect()
8193 };
8194 let t = |rows: usize, cols: usize, seed: usize| {
8195 QTensor::from_f32(w(rows * cols, seed), rows, cols)
8196 };
8197 let ones = |n: usize| vec![1.0f32; n];
8198
8199 let (dim, hc) = (cfg.dim, cfg.hc_mult);
8200 let q_width = cfg.n_heads * cfg.head_dim;
8203 let kv_width = cfg.head_dim;
8204 let o_per_group = q_width / cfg.o_groups;
8205 let mut layers = Vec::new();
8206 for li in 0..2 {
8207 let experts: Vec<Dsv4Expert> = (0..cfg.n_routed_experts)
8208 .map(|e| Dsv4Expert {
8209 w1: t(cfg.moe_inter, dim, 40 + e + li * 8),
8210 w2: t(dim, cfg.moe_inter, 60 + e + li * 8),
8211 w3: t(cfg.moe_inter, dim, 80 + e + li * 8),
8212 })
8213 .collect();
8214 layers.push(Dsv4Layer {
8217 attn_norm: ones(dim),
8218 ffn_norm: ones(dim),
8219 wq_a: t(cfg.q_lora_rank, dim, 1 + li),
8220 q_norm: ones(cfg.q_lora_rank),
8221 wq_b: t(q_width, cfg.q_lora_rank, 3 + li),
8222 wkv: t(kv_width, dim, 5 + li),
8223 kv_norm: ones(kv_width),
8224 wo_a: t(cfg.o_groups * cfg.o_lora_rank, o_per_group, 7 + li),
8225 wo_b: t(dim, cfg.o_groups * cfg.o_lora_rank, 9 + li),
8226 attn_sink: vec![0.1; cfg.n_heads],
8227 compressor: if li == 1 {
8230 Some(Dsv4Compressor {
8231 wkv: t(2 * kv_width, dim, 11),
8232 wgate: t(2 * kv_width, dim, 13),
8233 norm: ones(kv_width),
8234 ape: vec![0.01; 4 * 2 * kv_width],
8235 ratio: 4,
8236 overlap: true,
8237 })
8238 } else {
8239 None
8240 },
8241 indexer: if li == 1 {
8242 Some(Dsv4Indexer {
8243 wq_b: t(2 * 16, cfg.q_lora_rank, 41),
8244 weights_proj: t(2, dim, 43),
8245 compressor: Dsv4Compressor {
8246 wkv: t(2 * 16, dim, 45),
8247 wgate: t(2 * 16, dim, 47),
8248 norm: ones(16),
8249 ape: vec![0.01; 4 * 2 * 16],
8250 ratio: 4,
8251 overlap: true,
8252 },
8253 })
8254 } else {
8255 None
8256 },
8257 hc_attn_fn: w((2 + hc) * hc * hc * dim, 15 + li),
8258 hc_attn_base: w((2 + hc) * hc, 17 + li),
8259 hc_attn_scale: [1.0, 1.0, 1.0],
8260 hc_ffn_fn: w((2 + hc) * hc * hc * dim, 19 + li),
8261 hc_ffn_base: w((2 + hc) * hc, 21 + li),
8262 hc_ffn_scale: [1.0, 1.0, 1.0],
8263 gate: t(cfg.n_routed_experts, dim, 23 + li),
8264 gate_bias: if li == 1 {
8265 Some(vec![0.0; cfg.n_routed_experts])
8266 } else {
8267 None
8268 },
8269 tid2eid: if li == 0 {
8270 Some(
8271 (0..cfg.vocab * cfg.top_k)
8272 .map(|i| (i % cfg.n_routed_experts) as f32)
8273 .collect(),
8274 )
8275 } else {
8276 None
8277 },
8278 experts,
8279 mask: None,
8280 shared: Dsv4Expert {
8281 w1: t(cfg.moe_inter, dim, 25 + li),
8282 w2: t(dim, cfg.moe_inter, 27 + li),
8283 w3: t(cfg.moe_inter, dim, 29 + li),
8284 },
8285 });
8286 }
8287 let inv = |base: f32| -> Vec<f32> {
8288 (0..cfg.rope_head_dim / 2)
8289 .map(|i| 1.0 / base.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
8290 .collect()
8291 };
8292 let g = Dsv4Globals {
8293 inv_freq_compress: inv(160000.0),
8294 inv_freq_window: inv(10000.0),
8295 embed: t(cfg.vocab, dim, 31),
8296 norm: ones(dim),
8297 head: t(cfg.vocab, dim, 33),
8298 hc_head_fn: w(hc * hc * dim, 35),
8299 hc_head_base: w(hc, 37),
8300 hc_head_scale: 1.0,
8301 };
8302 (g, layers, cfg)
8303 }
8304
8305 #[test]
8310 fn forward_token_decodes_a_sequence_without_falling_over() {
8311 let (g, layers, cfg) = toy();
8312 let mut st = Dsv4State::new(layers.len());
8313 let inv_freq: Vec<f32> = (0..cfg.rope_head_dim / 2)
8314 .map(|i| 1.0 / 10000f32.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
8315 .collect();
8316 let mut logits = Vec::new();
8317
8318 let mut first: Option<Vec<f32>> = None;
8321 for (step, tok) in [3u32, 7, 1, 9, 4, 2, 8, 5, 6, 0].into_iter().enumerate() {
8322 forward_token(
8323 &g,
8324 &layers,
8325 &cfg,
8326 &mut st,
8327 tok,
8328 &inv_freq,
8329 None,
8330 &mut logits,
8331 );
8332 assert_eq!(logits.len(), cfg.vocab, "step {step}: logit count");
8333 assert!(
8334 logits.iter().all(|v| v.is_finite()),
8335 "step {step}: non-finite logit — {logits:?}"
8336 );
8337 let spread = logits.iter().cloned().fold(f32::MIN, f32::max)
8340 - logits.iter().cloned().fold(f32::MAX, f32::min);
8341 assert!(spread > 1e-6, "step {step}: logits are flat ({spread})");
8342 if step == 0 {
8343 first = Some(logits.clone());
8344 }
8345 assert_eq!(st.pos, step + 1, "position bookkeeping");
8346 }
8347
8348 assert!(!st.window[0].is_empty(), "sliding window never filled");
8351 for (li, w) in st.window.iter().enumerate() {
8353 assert!(
8354 w.len() / cfg.head_dim <= cfg.window,
8355 "layer {li}: window holds {} positions, cap is {}",
8356 w.len() / cfg.head_dim,
8357 cfg.window
8358 );
8359 }
8360 assert!(
8361 !st.compressed[1].is_empty(),
8362 "compressor layer produced no compressed KV in 10 tokens"
8363 );
8364 assert_eq!(
8368 st.compressed[1].len() / cfg.head_dim,
8369 2,
8370 "expected two folds in ten tokens at ratio 4"
8371 );
8372 assert!(
8373 !st.prev_kv[1].is_empty(),
8374 "the overlapping compressor never kept a previous window"
8375 );
8376 for (li, l) in layers.iter().enumerate() {
8381 if l.indexer.is_some() {
8382 assert!(
8383 !st.index_kv[li].is_empty(),
8384 "layer {li} has an indexer but its cache stayed empty"
8385 );
8386 }
8387 }
8388
8389 let mut fresh = Dsv4State::new(layers.len());
8392 let mut relogits = Vec::new();
8393 forward_token(
8394 &g,
8395 &layers,
8396 &cfg,
8397 &mut fresh,
8398 3,
8399 &inv_freq,
8400 None,
8401 &mut relogits,
8402 );
8403 assert_eq!(
8404 relogits,
8405 first.unwrap(),
8406 "the same token from a fresh state must reproduce exactly"
8407 );
8408 }
8409
8410 #[test]
8414 fn swiglu_limit_clamps_up_both_ways_and_gate_only_from_above() {
8415 let inter = 4;
8416 let gate_src = [-50.0f32, 50.0, 1.0, -1.0];
8418 let up_src = [50.0f32, -50.0, 1.0, -1.0];
8419 let limit = 10.0f32;
8420 let mut got = vec![0.0f32; inter];
8421 expert_swiglu(
8422 &[0.0],
8423 &|_, d| d.copy_from_slice(&gate_src),
8424 &|_, d| d.copy_from_slice(&up_src),
8425 &|src, d| d.copy_from_slice(src),
8426 inter,
8427 1.0,
8428 limit,
8429 &mut got,
8430 );
8431 let silu = |g: f32| g / (1.0 + (-g).exp());
8432 let want = [
8434 silu(-50.0) * limit,
8435 silu(limit) * -limit,
8436 silu(1.0) * 1.0,
8437 -silu(-1.0),
8438 ];
8439 for (i, w) in want.iter().enumerate() {
8440 assert!(
8441 (got[i] - w).abs() < 1e-5,
8442 "lane {i}: got {} want {w}",
8443 got[i]
8444 );
8445 }
8446 let mut raw = vec![0.0f32; inter];
8448 expert_swiglu(
8449 &[0.0],
8450 &|_, d| d.copy_from_slice(&gate_src),
8451 &|_, d| d.copy_from_slice(&up_src),
8452 &|src, d| d.copy_from_slice(src),
8453 inter,
8454 1.0,
8455 0.0,
8456 &mut raw,
8457 );
8458 assert!(
8459 (raw[1] - silu(50.0) * -50.0).abs() < 1e-3,
8460 "limit 0 must not clamp"
8461 );
8462 }
8463
8464 #[test]
8469 fn grouped_projection_is_identical_with_and_without_a_pool() {
8470 let (groups, lora, per_group, dim) = (4usize, 128usize, 64usize, 32usize);
8471 let attn: Vec<f32> = (0..groups * per_group)
8472 .map(|i| ((i * 13) as f32 * 0.021).sin())
8473 .collect();
8474 let wo_a: Vec<f32> = (0..groups * lora * per_group)
8475 .map(|i| ((i * 7) as f32 * 0.011).cos())
8476 .collect();
8477 let wo_b: Vec<f32> = (0..dim * groups * lora)
8478 .map(|i| ((i * 5) as f32 * 0.009).sin())
8479 .collect();
8480 let row = |r: usize, x: &[f32], _sc: &mut [f32]| -> f32 {
8481 wo_a[r * per_group..(r + 1) * per_group]
8482 .iter()
8483 .zip(x)
8484 .map(|(a, b)| a * b)
8485 .sum()
8486 };
8487 let project = |mid: &[f32], dst: &mut [f32]| {
8488 for (d, o) in dst.iter_mut().enumerate() {
8489 *o = wo_b[d * mid.len()..(d + 1) * mid.len()]
8490 .iter()
8491 .zip(mid)
8492 .map(|(a, b)| a * b)
8493 .sum();
8494 }
8495 };
8496
8497 let mut serial = vec![0.0f32; dim];
8498 o_project(
8499 &attn,
8500 &row,
8501 per_group,
8502 &project,
8503 groups,
8504 lora,
8505 None,
8506 &mut serial,
8507 );
8508
8509 let pool = crate::pool::Pool::new(4);
8510 let mut pooled = vec![0.0f32; dim];
8511 o_project(
8512 &attn,
8513 &row,
8514 per_group,
8515 &project,
8516 groups,
8517 lora,
8518 Some(&pool),
8519 &mut pooled,
8520 );
8521 assert_eq!(serial, pooled, "the pooled projection diverged");
8522 assert!(
8523 serial.iter().any(|v| v.abs() > 1e-6),
8524 "test data is degenerate"
8525 );
8526 }
8527
8528 #[test]
8529 fn block_grouped_projection_matches_position_walk() {
8530 let (_g, layers, cfg) = toy();
8531 let l = &layers[1];
8532 let b = 5;
8533 let attn_len = cfg.n_heads * cfg.head_dim;
8534 let attn: Vec<f32> = (0..b * attn_len)
8535 .map(|i| ((i * 17) as f32 * 0.013).sin())
8536 .collect();
8537 let mut walked = vec![0.0f32; b * cfg.dim];
8538 for bi in 0..b {
8539 o_project(
8540 &attn[bi * attn_len..(bi + 1) * attn_len],
8541 &|r, x, sc| l.wo_a.row_dot(r, x, sc),
8542 l.wo_a.cols(),
8543 &|mid, dst| l.wo_b.matvec(mid, dst, None),
8544 cfg.o_groups,
8545 cfg.o_lora_rank,
8546 None,
8547 &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
8548 );
8549 }
8550 let mut batched = vec![0.0f32; b * cfg.dim];
8551 o_project_block(
8552 &attn,
8553 b,
8554 &l.wo_a,
8555 &l.wo_b,
8556 cfg.o_groups,
8557 cfg.o_lora_rank,
8558 None,
8559 &mut batched,
8560 );
8561 assert_eq!(batched, walked);
8562 }
8563
8564 #[test]
8565 fn block_moe_matches_position_walk_in_route_order() {
8566 let (_g, layers, cfg) = toy();
8567 let l = &layers[1];
8570 let b = 5;
8571 let xs: Vec<f32> = (0..b * cfg.dim)
8572 .map(|i| ((i * 11) as f32 * 0.019).cos())
8573 .collect();
8574 let ids = [1u32, 2, 3, 4, 5];
8575 let mut walked = vec![0.0f32; b * cfg.dim];
8576 for bi in 0..b {
8577 moe_step(
8578 &xs[bi * cfg.dim..(bi + 1) * cfg.dim],
8579 l,
8580 &cfg,
8581 ids[bi],
8582 1,
8583 None,
8584 &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
8585 );
8586 }
8587 let mut batched = vec![0.0f32; b * cfg.dim];
8588 moe_step_block(&xs, b, l, &cfg, &ids, 1, None, &mut batched);
8589 assert_eq!(batched, walked);
8590 }
8591
8592 #[test]
8598 fn overlapping_compressor_folds_both_windows() {
8599 let (ratio, d) = (2usize, 3usize);
8600 let cur_kv: Vec<f32> = vec![
8603 1.0, 1.0, 1.0, 10.0, 20.0, 30.0, 2.0, 2.0, 2.0, 40.0, 50.0, 60.0, ];
8606 let cur_sc: Vec<f32> = vec![
8608 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 0.0, 100.0, 100.0, 0.0,
8610 ];
8611 let prev_kv: Vec<f32> = vec![
8613 7.0, 8.0, 9.0, 0.0, 0.0, 0.0, 5.0, 6.0, 7.0, 0.0, 0.0, 0.0,
8615 ];
8616 let prev_sc = vec![0.0f32; ratio * 2 * d];
8617
8618 let mut out = vec![0.0f32; d];
8619 compress_window_overlap(&prev_kv, &prev_sc, &cur_kv, &cur_sc, ratio, d, &mut out);
8620 assert!((out[0] - 40.0).abs() < 1e-3, "dim0 = {}", out[0]);
8622 assert!((out[1] - 50.0).abs() < 1e-3, "dim1 = {}", out[1]);
8623 assert!((out[2] - 30.0).abs() < 1e-3, "dim2 = {}", out[2]);
8625
8626 let mut first = vec![0.0f32; d];
8629 compress_window_overlap(&[], &[], &cur_kv, &cur_sc, ratio, d, &mut first);
8630 assert!(
8631 first.iter().all(|v| v.is_finite()),
8632 "first window: {first:?}"
8633 );
8634 assert!((first[0] - 40.0).abs() < 1e-3, "first dim0 = {}", first[0]);
8635
8636 let mut both = vec![0.0f32; d];
8638 let strong_prev = vec![100.0f32; ratio * 2 * d];
8639 compress_window_overlap(
8640 &prev_kv,
8641 &strong_prev,
8642 &cur_kv,
8643 &cur_sc,
8644 ratio,
8645 d,
8646 &mut both,
8647 );
8648 assert!(
8649 (both[0] - 40.0).abs() > 1.0,
8650 "a scored previous window must move the fold, got {}",
8651 both[0]
8652 );
8653 }
8654
8655 #[test]
8661 fn sinkhorn_matches_the_reference_numbers() {
8662 let hc = 4;
8663 let mixes: Vec<f32> = (0..24).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
8664 let base: Vec<f32> = (0..24).map(|i| (i as f32 * 0.11).cos()).collect();
8665 let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
8666 hc_split_sinkhorn(
8667 &mixes,
8668 &[1.0, 1.0, 1.0],
8669 &base,
8670 hc,
8671 20,
8672 1e-6,
8673 &mut pre,
8674 &mut post,
8675 &mut comb,
8676 );
8677 let want_pre = [0.7310596, 0.8888268, 0.9525191, 0.97424865];
8678 let want_post = [1.9600224, 1.9534285, 1.9201256, 1.8160983];
8679 let want_comb = [
8680 0.5996052,
8681 0.282_535_9,
8682 0.09218107,
8683 0.025676856,
8684 0.17564717,
8685 0.22228767,
8686 0.271_745_4,
8687 0.330_318_8,
8688 0.029528176,
8689 0.12206022,
8690 0.32619134,
8691 0.5222193,
8692 0.19521846,
8693 0.37311527,
8694 0.30988118,
8695 0.12178412,
8696 ];
8697 for (i, w) in want_pre.iter().enumerate() {
8698 assert!((pre[i] - w).abs() < 1e-5, "pre[{i}]: {} vs {w}", pre[i]);
8699 }
8700 for (i, w) in want_post.iter().enumerate() {
8701 assert!((post[i] - w).abs() < 1e-5, "post[{i}]: {} vs {w}", post[i]);
8702 }
8703 for (i, w) in want_comb.iter().enumerate() {
8704 assert!((comb[i] - w).abs() < 1e-4, "comb[{i}]: {} vs {w}", comb[i]);
8705 }
8706 }
8707
8708 #[test]
8713 fn sinkhorn_leaves_the_mixing_matrix_doubly_stochastic() {
8714 let hc = 4;
8715 let mix_hc = (2 + hc) * hc;
8716 let mixes: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
8718 let base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.11).cos()).collect();
8719 let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
8720 hc_split_sinkhorn(
8721 &mixes,
8722 &[1.0, 1.0, 1.0],
8723 &base,
8724 hc,
8725 20,
8726 1e-6,
8727 &mut pre,
8728 &mut post,
8729 &mut comb,
8730 );
8731 for j in 0..hc {
8732 let r: f32 = comb[j * hc..(j + 1) * hc].iter().sum();
8733 assert!((r - 1.0).abs() < 2e-3, "row {j} sums to {r}");
8734 let c: f32 = (0..hc).map(|k| comb[k * hc + j]).sum();
8735 assert!((c - 1.0).abs() < 2e-3, "col {j} sums to {c}");
8736 }
8737 assert!(pre.iter().all(|&v| v > 0.0 && v < 1.001));
8739 assert!(post.iter().all(|&v| (0.0..=2.0).contains(&v)));
8740 }
8741
8742 #[test]
8746 fn expand_of_identical_copies_is_a_fixed_point() {
8747 let (hc, dim) = (4usize, 3usize);
8748 let residual: Vec<f32> = std::iter::repeat_n([1.5f32, -2.0, 0.25], hc)
8749 .flatten()
8750 .collect();
8751 let comb = {
8752 vec![0.25f32; hc * hc]
8754 };
8755 let post = vec![0.0f32; hc];
8756 let mut out = vec![0.0f32; hc * dim];
8757 hc_expand(&[0.0; 3], &residual, &post, &comb, hc, dim, &mut out);
8758 for (o, r) in out.iter().zip(&residual) {
8759 assert!((o - r).abs() < 1e-6, "{o} vs {r}");
8760 }
8761 }
8762
8763 #[test]
8767 fn selection_bias_steers_the_choice_but_not_the_weights() {
8768 let scores = [3.0f32, 0.1, 2.0, 0.05];
8769 let bias = [0.0f32, 10.0, 0.0, 0.0];
8770 let (mut idx, mut w) = (Vec::new(), Vec::new());
8771 route(&scores, Some(&bias), 2, 1.5, None, None, &mut idx, &mut w);
8772 assert_eq!(idx[0], 1, "the biased expert must win selection");
8773 assert_eq!(idx[1], 0);
8774 assert!(w[0] < w[1], "biased expert kept its own (small) weight");
8777 let sum: f32 = w.iter().sum();
8778 assert!((sum - 1.5).abs() < 1e-5, "weights renormalize then scale");
8779 }
8780
8781 #[test]
8785 fn attention_sink_drains_weight_without_contributing_output() {
8786 let hd = 2;
8787 let q = [1.0f32, 0.0];
8788 let kv = [1.0f32, 0.0, 0.0, 1.0];
8789 let mut out = vec![0.0f32; hd];
8790 sparse_attend(&q, &kv, &[0, 1], f32::NEG_INFINITY, 1.0, hd, &mut out);
8791 let plain = out.clone();
8792 assert!(plain[0] > plain[1], "the aligned key must dominate");
8793 sparse_attend(&q, &kv, &[0, 1], 20.0, 1.0, hd, &mut out);
8794 assert!(
8795 out[0] < plain[0] * 0.01 && out[1] < plain[1] * 0.01,
8796 "a large sink must drain nearly all the mass: {out:?}"
8797 );
8798 }
8799
8800 #[test]
8803 fn masked_positions_leave_the_denominator_alone() {
8804 let hd = 2;
8805 let q = [1.0f32, 0.0];
8806 let kv = [1.0f32, 0.0, 0.0, 1.0];
8807 let (mut a, mut b) = (vec![0.0f32; hd], vec![0.0f32; hd]);
8808 sparse_attend(&q, &kv, &[0], f32::NEG_INFINITY, 1.0, hd, &mut a);
8809 sparse_attend(
8810 &q,
8811 &kv,
8812 &[0, usize::MAX],
8813 f32::NEG_INFINITY,
8814 1.0,
8815 hd,
8816 &mut b,
8817 );
8818 for (x, y) in a.iter().zip(&b) {
8819 assert!((x - y).abs() < 1e-6, "{x} vs {y}");
8820 }
8821 }
8822
8823 #[test]
8826 fn rope_tail_inverts_itself() {
8827 let inv_freq = [1.0f32, 0.5];
8828 let orig = [9.0f32, 8.0, 1.0, 2.0, 3.0, 4.0];
8829 let mut v = orig;
8830 rope_tail(&mut v, &inv_freq, 7, 4, false);
8831 assert!(v[..2] == orig[..2], "the non-rope head must not move");
8832 assert!(v[2..] != orig[2..], "the tail must actually rotate");
8833 rope_tail(&mut v, &inv_freq, 7, 4, true);
8834 for (a, b) in v.iter().zip(&orig) {
8835 assert!((a - b).abs() < 1e-5, "{a} vs {b}");
8836 }
8837 }
8838
8839 #[test]
8842 fn compressor_pools_the_window_per_dimension() {
8843 let (ratio, width) = (2usize, 2usize);
8844 let kv = [1.0f32, 10.0, 3.0, 20.0];
8845 let score = [0.0f32, 0.0, 0.0, 50.0];
8847 let ape = vec![0.0f32; ratio * width];
8848 let mut out = vec![0.0f32; width];
8849 compress_window(&kv, &score, &ape, ratio, width, &mut out);
8850 assert!(
8851 (out[0] - 2.0).abs() < 1e-5,
8852 "equal scores average: {}",
8853 out[0]
8854 );
8855 assert!(
8856 (out[1] - 20.0).abs() < 1e-3,
8857 "a dominant score wins: {}",
8858 out[1]
8859 );
8860 }
8861
8862 #[test]
8865 fn index_scores_relu_before_weighting() {
8866 let (nh, hd) = (2usize, 2usize);
8867 let q = [1.0f32, 0.0, -1.0, 0.0];
8869 let kv = [1.0f32, 0.0, 0.0, 1.0];
8870 let w = [1.0f32, 1.0];
8871 let mut sc = Vec::new();
8872 index_scores(&q, &kv, &w, nh, hd, 2, 2, None, &mut sc);
8873 assert!(sc[0] > 0.9, "abstention, not veto: {:?}", sc);
8875 }
8876
8877 #[test]
8878 fn index_scores_mask_the_future() {
8879 let (nh, hd) = (1usize, 2usize);
8880 let q = [1.0f32, 0.0];
8881 let kv = [1.0f32, 0.0, 1.0, 0.0, 1.0, 0.0];
8882 let w = [1.0f32];
8883 let mut sc = Vec::new();
8884 index_scores(&q, &kv, &w, nh, hd, 3, 2, None, &mut sc);
8885 assert!(sc[0].is_finite() && sc[1].is_finite());
8886 assert!(sc[2] == f32::NEG_INFINITY, "position 2 is in the future");
8887 let mut idx = Vec::new();
8888 top_k_positions(&sc, 3, &mut idx);
8889 assert_eq!(idx, vec![0, 1], "a masked slot never wins a slot");
8890 }
8891
8892 #[test]
8893 fn top_k_is_deterministic_on_ties() {
8894 let sc = [1.0f32, 1.0, 1.0, 0.0];
8895 let mut idx = Vec::new();
8896 top_k_positions(&sc, 2, &mut idx);
8897 assert_eq!(idx, vec![0, 1], "ties resolve to the lower index");
8898 }
8899
8900 #[test]
8904 fn hc_block_preserves_the_copy_structure_and_applies_the_block() {
8905 let cfg = Dsv4Cfg {
8906 dim: 4,
8907 n_heads: 1,
8908 head_dim: 4,
8909 rope_head_dim: 2,
8910 q_lora_rank: 4,
8911 o_lora_rank: 2,
8912 o_groups: 1,
8913 hc_mult: 4,
8914 hc_sinkhorn_iters: 20,
8915 hc_eps: 1e-6,
8916 norm_eps: 1e-6,
8917 n_routed_experts: 2,
8918 top_k: 1,
8919 moe_inter: 4,
8920 route_scale: 1.0,
8921 swiglu_limit: 10.0,
8922 window: 128,
8923 index_topk: 4,
8924 vocab: 8,
8925 };
8926 let (hc, dim) = (cfg.hc_mult, cfg.dim);
8927 let mix_hc = (2 + hc) * hc;
8928 let hc_fn: Vec<f32> = (0..mix_hc * hc * dim)
8929 .map(|i| ((i % 13) as f32 - 6.0) * 0.05)
8930 .collect();
8931 let hc_base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.2).sin()).collect();
8932 let norm_w = vec![1.0f32; dim];
8933 let mut state: Vec<f32> = (0..hc * dim).map(|i| (i as f32 * 0.3).cos()).collect();
8934 let before = state.clone();
8935 let mut scratch = HcScratch::new(&cfg);
8936 hc_block(
8937 &mut state,
8938 &hc_fn,
8939 &[1.0, 1.0, 1.0],
8940 &hc_base,
8941 &norm_w,
8942 &cfg,
8943 &mut scratch,
8944 None,
8945 |_folded, out: &mut [f32]| out.iter_mut().for_each(|o| *o = 1.0),
8946 );
8947 assert_eq!(state.len(), before.len(), "copy structure must survive");
8948 assert!(state.iter().all(|v| v.is_finite()), "{state:?}");
8949 assert!(
8950 state.iter().zip(&before).any(|(a, b)| (a - b).abs() > 1e-4),
8951 "the block's output has to reach the state"
8952 );
8953 }
8954
8955 #[test]
8956 fn hash_route_reads_the_table_row() {
8957 let table = [7.0f32, 9.0, 1.0, 2.0, 5.0, 6.0];
8959 assert_eq!(hash_route(&table, 3, 2, 0), vec![7, 9]);
8960 assert_eq!(hash_route(&table, 3, 2, 2), vec![5, 6]);
8961 assert_eq!(hash_route(&table, 3, 2, 99), vec![5, 6]);
8963 }
8964
8965 #[test]
8970 fn a_task_mask_restricts_selection_and_renormalizes() {
8971 let scores = [0.1f32, 4.0, 1.0, 9.0];
8973 let (mut idx, mut w) = (Vec::new(), Vec::new());
8974 route(&scores, None, 2, 1.0, None, None, &mut idx, &mut w);
8975 assert_eq!(idx, vec![3, 1], "unmasked: the two best win");
8976 let sum: f32 = w.iter().sum();
8977 assert!((sum - 1.0).abs() < 1e-5, "weights must sum to route_scale");
8978
8979 let mask = [true, false, true, true];
8982 let (mut i2, mut w2) = (Vec::new(), Vec::new());
8983 route(&scores, None, 2, 1.0, None, Some(&mask), &mut i2, &mut w2);
8984 assert_eq!(i2, vec![3, 2], "masked expert must not be selected");
8985 let sum2: f32 = w2.iter().sum();
8986 assert!((sum2 - 1.0).abs() < 1e-5, "masked weights must renormalize");
8987
8988 let tight = [false, false, false, true];
8990 let (mut i3, mut w3) = (Vec::new(), Vec::new());
8991 route(&scores, None, 2, 1.0, None, Some(&tight), &mut i3, &mut w3);
8992 assert_eq!(i3, vec![3]);
8993 assert_eq!(w3.len(), 1);
8994 }
8995
8996 #[test]
9001 fn hash_layers_weight_the_experts_the_table_names() {
9002 let scores = [0.1f32, 0.4, 0.2, 5.0];
9004 let table = vec![0.0f32, 1.0];
9005 let idx_forced = hash_route(&table, 1, 2, 0);
9006 assert_eq!(idx_forced, vec![0, 1]);
9007
9008 let (mut idx, mut w) = (Vec::new(), Vec::new());
9009 route(
9010 &scores,
9011 None,
9012 2,
9013 1.0,
9014 Some(&idx_forced),
9015 None,
9016 &mut idx,
9017 &mut w,
9018 );
9019 assert_eq!(idx, vec![0, 1], "the table must decide the experts");
9020
9021 let sp = |x: f32| (1.0 + x.exp()).ln().sqrt();
9023 let (s0, s1) = (sp(scores[0]), sp(scores[1]));
9024 let tot = s0 + s1;
9025 assert!(
9026 (w[0] - s0 / tot).abs() < 1e-6,
9027 "w[0]={} want {}",
9028 w[0],
9029 s0 / tot
9030 );
9031 assert!(
9032 (w[1] - s1 / tot).abs() < 1e-6,
9033 "w[1]={} want {}",
9034 w[1],
9035 s1 / tot
9036 );
9037
9038 let (mut idx2, mut w2) = (Vec::new(), Vec::new());
9040 route(&scores, None, 2, 1.0, None, None, &mut idx2, &mut w2);
9041 assert_eq!(idx2[0], 3, "without a table the highest score still wins");
9042 }
9043}
9044
9045pub const DSPARK_NOISE_TOKEN: u32 = 128799;
9062pub fn dspark_block() -> usize {
9070 5
9071}
9072
9073#[cfg(feature = "gpu")]
9080fn dspark_native_on() -> bool {
9081 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9082 *ON.get_or_init(|| {
9083 std::env::var("CMF_DSPARK_NATIVE")
9084 .map(|v| v != "0")
9085 .unwrap_or(true)
9088 })
9089}
9090
9091pub fn dspark_verify_k() -> usize {
9092 static K: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
9093 *K.get_or_init(|| {
9094 std::env::var("CMF_DSPARK_VERIFY_K")
9095 .ok()
9096 .and_then(|v| v.parse::<usize>().ok())
9097 .filter(|&n| (1..=DSPARK_BLOCK_MAX).contains(&n))
9098 .unwrap_or(DSPARK_BLOCK_MAX)
9099 })
9100}
9101
9102pub const DSPARK_BLOCK_MAX: usize = 5;
9104
9105pub struct DsparkState {
9108 pub win: Vec<Vec<f32>>,
9110 pub filled: Vec<usize>,
9112 pub main_hidden: Vec<f32>,
9114 pub have_hidden: bool,
9116}
9117
9118impl DsparkState {
9119 pub fn new(stages: usize, cfg: &Dsv4Cfg, targets: usize) -> Self {
9120 Self {
9121 win: vec![Vec::new(); stages],
9122 filled: vec![0; stages],
9123 main_hidden: vec![0.0; cfg.dim * targets],
9124 have_hidden: false,
9125 }
9126 }
9127}
9128
9129pub fn dspark_targets(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, n_layers: usize) -> Vec<usize> {
9135 let Some(mp) = mtp.iter().find_map(|m| m.main_proj.as_ref()) else {
9136 return Vec::new();
9137 };
9138 let n = (mp.cols() / cfg.dim.max(1)).clamp(1, n_layers);
9139 (n_layers - n..n_layers).collect()
9140}
9141
9142thread_local! {
9143 static DSPARK_CAP: std::cell::RefCell<(Vec<usize>, Vec<f32>, usize)> =
9149 const { std::cell::RefCell::new((Vec::new(), Vec::new(), 0)) };
9150}
9151
9152pub fn dspark_arm(targets: &[usize], dim: usize) {
9154 DSPARK_CAP.with(|c| {
9155 let mut c = c.borrow_mut();
9156 c.0 = targets.to_vec();
9157 c.1 = vec![0.0; dim * targets.len()];
9158 c.2 = 0;
9159 });
9160}
9161
9162fn dspark_wants(li: usize) -> bool {
9166 DSPARK_CAP.with(|c| c.borrow().0.contains(&li))
9167}
9168
9169pub fn dspark_note(li: usize, state: &[f32], cfg: &Dsv4Cfg) {
9171 DSPARK_CAP.with(|c| {
9172 let mut c = c.borrow_mut();
9173 if c.0.is_empty() {
9174 return;
9175 }
9176 if let Some(slot) = c.0.iter().position(|&t| t == li) {
9177 let (_, buf, seen) = &mut *c;
9178 dspark_capture(state, cfg, slot, buf);
9179 *seen = if slot == 0 { 1 } else { *seen + 1 };
9184 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
9185 eprintln!("[cap] note li={li} slot={slot} seen={}", *seen);
9186 }
9187 }
9188 });
9189}
9190
9191pub fn dspark_peek_slot(slot: usize, dim: usize, out: &mut [f32]) -> bool {
9196 DSPARK_CAP.with(|c| {
9197 let c = c.borrow();
9198 let lo = slot * dim;
9199 if c.1.len() < lo + dim {
9200 return false;
9201 }
9202 out[..dim].copy_from_slice(&c.1[lo..lo + dim]);
9203 true
9204 })
9205}
9206
9207pub fn dspark_take(out: &mut Vec<f32>) -> bool {
9209 DSPARK_CAP.with(|c| {
9210 let mut c = c.borrow_mut();
9211 if c.0.is_empty() || c.2 != c.0.len() {
9212 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
9213 eprintln!("[cap] take FAIL armed={:?} seen={}", c.0, c.2);
9214 }
9215 return false;
9216 }
9217 out.clear();
9218 out.extend_from_slice(&c.1);
9219 c.2 = 0;
9220 true
9221 })
9222}
9223
9224pub fn dspark_capture(state: &[f32], cfg: &Dsv4Cfg, slot: usize, out: &mut [f32]) {
9228 let (hc, dim) = (cfg.hc_mult, cfg.dim);
9229 let dst = &mut out[slot * dim..(slot + 1) * dim];
9230 let inv = 1.0 / hc as f32;
9231 for d in 0..dim {
9232 let mut s = 0.0;
9233 for j in 0..hc {
9234 s += state[j * dim + d];
9235 }
9236 dst[d] = s * inv;
9237 }
9238}
9239
9240pub fn dspark_freq_note(picks: &[(usize, Vec<usize>)]) {
9245 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
9246 std::sync::Mutex::new(None);
9247 let Ok(path) = std::env::var("CMF_DSPARK_PICK_DUMP") else {
9248 return;
9249 };
9250 let mut g = FREQ.lock().unwrap();
9251 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
9252 for (stage, idx) in picks {
9253 for &e in idx {
9254 *map.entry((*stage, e)).or_insert(0) += 1;
9255 }
9256 }
9257 *blocks += 1;
9258 if *blocks % 32 == 0 {
9259 let mut lines: Vec<_> = map.iter().collect();
9260 lines.sort();
9261 let body: String = lines
9262 .iter()
9263 .map(|((s, e), n)| format!("{s}\t{e}\t{n}\n"))
9264 .collect();
9265 let _ = std::fs::write(&path, body);
9266 }
9267}
9268
9269pub fn trunk_freq_note(picks: &[(usize, Vec<usize>)]) {
9274 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
9275 std::sync::Mutex::new(None);
9276 let Ok(path) = std::env::var("CMF_DSV4_TRUNK_PICK_DUMP") else {
9277 return;
9278 };
9279 let mut g = FREQ.lock().unwrap();
9280 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
9281 for (li, idx) in picks {
9282 for &e in idx {
9283 *map.entry((*li, e)).or_insert(0) += 1;
9284 }
9285 }
9286 *blocks += 1;
9287 if *blocks % 32 == 0 {
9288 let mut lines: Vec<_> = map.iter().collect();
9289 lines.sort();
9290 let body: String = lines
9291 .iter()
9292 .map(|((l, e), n)| format!("{l}\t{e}\t{n}\n"))
9293 .collect();
9294 let _ = std::fs::write(&path, body);
9295 }
9296}
9297
9298fn dspark_apply_mask(out: &mut [Dsv4Mtp]) {
9306 let Ok(path) = std::env::var("CMF_DSPARK_MASK") else {
9307 return;
9308 };
9309 let Ok(text) = std::fs::read_to_string(&path) else {
9310 eprintln!("DSpark: CMF_DSPARK_MASK={path} не читается — маска не применена");
9311 return;
9312 };
9313 for (d, line) in text.lines().enumerate() {
9314 let Some(m) = out.get_mut(d) else { break };
9315 let n = m.layer.experts.len();
9316 let mut mask = vec![false; n];
9317 let mut kept = 0usize;
9318 for tok in line.split(',') {
9319 if let Ok(e) = tok.trim().parse::<usize>() {
9320 if e < n && !mask[e] {
9321 mask[e] = true;
9322 kept += 1;
9323 }
9324 }
9325 }
9326 if kept == 0 {
9327 continue;
9328 }
9329 eprintln!("DSpark: стадия {d} ограничена {kept}/{n} экспертами");
9330 m.layer.mask = Some(mask);
9331 }
9332}
9333
9334#[cfg(feature = "gpu")]
9344pub struct DsparkPack {
9345 pub stages: Vec<DsparkStagePack>,
9346 pub gu_q2: bool,
9349 pub dn_q2: bool,
9352 pub routers: Vec<Vec<f32>>,
9355 pub biases: Vec<Option<Vec<f32>>>,
9356}
9357
9358#[cfg(feature = "gpu")]
9359pub struct DsparkStagePack {
9360 pub mask: Vec<bool>,
9362 pub to_slot: Vec<usize>,
9364 pub mask_u32: Vec<u32>,
9367 pub map_u32: Vec<u32>,
9368 pub tensors: Vec<(usize, usize, usize)>,
9370 pub n_resident: usize,
9371 global: Option<GlobalPack>,
9375}
9376
9377pub static DSPARK_Q2TP_ENCODE: std::sync::OnceLock<fn(&[f32], usize, usize) -> Vec<u8>> =
9384 std::sync::OnceLock::new();
9385
9386#[cfg(feature = "gpu")]
9389pub fn dspark_gpu_on() -> bool {
9390 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9391 *ON.get_or_init(|| {
9392 std::env::var("CMF_DSPARK_GPU")
9393 .map(|v| v != "0")
9394 .unwrap_or(true)
9395 })
9396}
9397
9398#[cfg(feature = "gpu")]
9400pub fn dspark_pack_get(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<&'static DsparkPack> {
9401 static P: std::sync::OnceLock<Option<Box<DsparkPack>>> = std::sync::OnceLock::new();
9402 P.get_or_init(|| dspark_pack_build(mtp, cfg).map(Box::new))
9403 .as_deref()
9404}
9405
9406#[cfg(feature = "gpu")]
9422pub fn dspark_reserve_note(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, layers: &[Dsv4Layer]) {
9423 if mtp.is_empty() || std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "0") || !dspark_gpu_on()
9424 {
9425 return;
9426 }
9427 let forced = std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "1");
9432 let dt = |q2: bool| {
9433 if q2 {
9434 cortiq_core::TensorDtype::Q2TiledP
9435 } else {
9436 cortiq_core::TensorDtype::Q4TiledP
9437 }
9438 };
9439 let gu_q2 = mtp[0]
9440 .layer
9441 .experts
9442 .first()
9443 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9444 let dn_q2 = mtp[0]
9445 .layer
9446 .experts
9447 .first()
9448 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9449 if !gu_q2
9457 && !dn_q2
9458 && std::env::var("CMF_MOE_MASK").is_err()
9459 && crate::gpu_wgpu::dsv4_global_moe_supported()
9460 {
9461 crate::gpu_wgpu::DRAFT_PACK_RESERVE.store(0, std::sync::atomic::Ordering::Relaxed);
9462 crate::gpu_wgpu::DRAFT_RESERVE.store(0, std::sync::atomic::Ordering::Relaxed);
9463 return;
9464 }
9465 let gu = cortiq_core::quant::expected_nbytes(dt(gu_q2), &[cfg.moe_inter, cfg.dim]).unwrap_or(0);
9466 let dn = cortiq_core::quant::expected_nbytes(dt(dn_q2), &[cfg.dim, cfg.moe_inter]).unwrap_or(0);
9467 let per = (2 * gu + dn) as u64;
9468 let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
9469 .ok()
9470 .and_then(|v| v.parse().ok())
9471 .unwrap_or(40);
9474 let bytes = per * (n_res * mtp.len() + mtp.len() + 1) as u64;
9476 let trunk: u64 = layers
9486 .iter()
9487 .map(|l| {
9488 let Some(e) = l.experts.first() else {
9489 return 0;
9490 };
9491 let gu = cortiq_core::quant::expected_nbytes(
9492 dt(e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
9493 &[cfg.moe_inter, cfg.dim],
9494 )
9495 .unwrap_or(0);
9496 let dn = cortiq_core::quant::expected_nbytes(
9497 dt(e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
9498 &[cfg.dim, cfg.moe_inter],
9499 )
9500 .unwrap_or(0);
9501 let routed = l
9502 .mask
9503 .as_deref()
9504 .map_or(l.experts.len(), |m| m.iter().filter(|&&open| open).count());
9505 ((2 * gu + dn) * (routed + 1)) as u64
9506 })
9507 .sum();
9508 if let Some(budget) = crate::gpu_wgpu::dsv4_vram_budget() {
9509 if !forced && budget < trunk / 10 * 9 + bytes {
9510 return;
9511 }
9512 }
9513 let mib = 1024 * 1024u64;
9522 let workspace = match crate::gpu_wgpu::dsv4_vram_budget() {
9523 Some(b) if b <= 64 * 1024 * mib => 1024 * mib,
9527 _ => {
9528 ((cfg.dim * cfg.hc_mult * DSPARK_BLOCK_MAX * 4096) as u64).clamp(512 * mib, 1024 * mib)
9529 }
9530 };
9531 crate::gpu_wgpu::DRAFT_PACK_RESERVE.store(bytes, std::sync::atomic::Ordering::Relaxed);
9532 crate::gpu_wgpu::DRAFT_RESERVE.store(
9533 bytes.saturating_add(workspace),
9534 std::sync::atomic::Ordering::Relaxed,
9535 );
9536}
9537
9538#[cfg(not(feature = "gpu"))]
9539pub fn dspark_reserve_note(_mtp: &[Dsv4Mtp], _cfg: &Dsv4Cfg, _layers: &[Dsv4Layer]) {}
9540
9541#[cfg(feature = "gpu")]
9542pub fn dspark_pack_build(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<DsparkPack> {
9543 if mtp.is_empty() {
9544 return None;
9545 }
9546 let model = mtp[0]
9547 .layer
9548 .experts
9549 .first()
9550 .and_then(|e| e.w1.model_arc())?;
9551 let native_q2 = mtp[0]
9552 .layer
9553 .experts
9554 .first()
9555 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9556 let dn_native = mtp[0]
9557 .layer
9558 .experts
9559 .first()
9560 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9561 let global_share = !native_q2
9562 && !dn_native
9563 && std::env::var("CMF_MOE_MASK").is_err()
9564 && crate::gpu_wgpu::dsv4_global_moe_supported()
9565 && crate::gpu_wgpu::dsv4_global_moe_ready(&model);
9566 let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
9567 .ok()
9568 .and_then(|v| v.parse().ok())
9569 .unwrap_or_else(|| {
9570 if global_share {
9571 return 40;
9575 }
9576 let gu_q2 = native_q2 || (!dspark_native_on() && DSPARK_Q2TP_ENCODE.get().is_some());
9582 let room = crate::gpu_wgpu::dsv4_draft_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_native);
9583 (room.saturating_sub(mtp.len() + 1) / mtp.len().max(1)).clamp(8, 40)
9584 });
9585 let mut freq: Vec<Vec<(u64, usize)>> = vec![Vec::new(); mtp.len()];
9589 let pack_path = std::env::var("CMF_DSPARK_PACK").ok().or_else(|| {
9590 let m = mtp[0].layer.experts.first()?.w1.model_arc()?;
9591 let mut s = m.path.as_os_str().to_os_string();
9592 s.push(".dspark.tsv");
9593 let p = std::path::PathBuf::from(s);
9594 p.exists().then(|| p.to_string_lossy().into_owned())
9595 });
9596 if let Some(path) = pack_path {
9597 if let Ok(text) = std::fs::read_to_string(&path) {
9598 for line in text.lines() {
9599 let mut it = line.split_whitespace();
9600 if let (Some(s), Some(e), Some(n)) = (it.next(), it.next(), it.next()) {
9601 if let (Ok(s), Ok(e), Ok(n)) =
9602 (s.parse::<usize>(), e.parse::<usize>(), n.parse::<u64>())
9603 {
9604 if s < freq.len() {
9605 freq[s].push((n, e));
9606 }
9607 }
9608 }
9609 }
9610 }
9611 }
9612 let mut stages = Vec::with_capacity(mtp.len());
9613 let mut routers = Vec::with_capacity(mtp.len());
9614 let mut biases = Vec::with_capacity(mtp.len());
9615 for (si, m) in mtp.iter().enumerate() {
9616 let l = &m.layer;
9617 let n = l.experts.len();
9618 let mut order: Vec<usize> = {
9621 let mut f = freq[si].clone();
9622 f.sort_by(|a, b| b.0.cmp(&a.0));
9623 let mut seen = vec![false; n];
9624 let mut o: Vec<usize> = f
9625 .into_iter()
9626 .map(|(_, e)| e)
9627 .filter(|&e| {
9628 if e < n && !seen[e] {
9629 seen[e] = true;
9630 true
9631 } else {
9632 false
9633 }
9634 })
9635 .collect();
9636 o.extend((0..n).filter(|&e| !seen[e]));
9637 o
9638 };
9639 order.truncate(n_res.min(n));
9640 let (global, mask, to_slot, tensors) = if global_share {
9641 let pk = pack_for(l, cfg, model.header.arch.num_layers + si)?;
9642 let gl = pk.global.clone()?;
9643 let remap = gl.pool.pin_picks(&model, gl.layer, &order, &l.experts);
9644 let mut mask = vec![false; n];
9645 let mut to_slot = vec![usize::MAX; n];
9646 for e in 0..n {
9647 if remap.get(e).copied().unwrap_or(u32::MAX) != u32::MAX {
9648 mask[e] = true;
9649 to_slot[e] = remap[e] as usize;
9650 }
9651 }
9652 (Some(gl), mask, to_slot, Vec::new())
9653 } else {
9654 let mut mask = vec![false; n];
9655 let mut to_slot = vec![usize::MAX; n];
9656 let mut tensors = Vec::with_capacity(order.len() + 1);
9657 for (slot, &e) in order.iter().enumerate() {
9658 let ex = &l.experts[e];
9659 let (Some(w1), Some(w3), Some(w2)) =
9660 (ex.w1.model_idx(), ex.w3.model_idx(), ex.w2.model_idx())
9661 else {
9662 return None;
9663 };
9664 mask[e] = true;
9665 to_slot[e] = slot;
9666 tensors.push((w1, w3, w2));
9667 }
9668 let (Some(s1), Some(s3), Some(s2)) = (
9669 l.shared.w1.model_idx(),
9670 l.shared.w3.model_idx(),
9671 l.shared.w2.model_idx(),
9672 ) else {
9673 return None;
9674 };
9675 tensors.push((s1, s3, s2));
9676 (None, mask, to_slot, tensors)
9677 };
9678 let mut router = vec![0.0f32; n * cfg.dim];
9680 for (r, row) in (0..n).zip(router.chunks_mut(cfg.dim)) {
9681 l.gate.row_f32(r, row);
9682 }
9683 routers.push(router);
9684 biases.push(l.gate_bias.clone());
9685 let mask_u32: Vec<u32> = mask.iter().map(|&m| m as u32).collect();
9686 let map_u32: Vec<u32> = to_slot
9687 .iter()
9688 .map(|&x| if x == usize::MAX { u32::MAX } else { x as u32 })
9689 .collect();
9690 stages.push(DsparkStagePack {
9691 mask,
9692 to_slot,
9693 mask_u32,
9694 map_u32,
9695 tensors,
9696 n_resident: order.len(),
9697 global,
9698 });
9699 }
9700 let mut skeleton = Vec::new();
9704 for m in mtp {
9705 let l = &m.layer;
9706 for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b] {
9707 skeleton.push(t.model_idx()?);
9708 }
9709 }
9710 if let Some(mp) = mtp[0].main_proj.as_ref() {
9711 skeleton.push(mp.model_idx()?);
9712 }
9713 for &idx in &skeleton {
9714 if !crate::gpu_wgpu::dsv4_weight_ready(&model, idx) {
9715 eprintln!("DSpark: скелет драфта не влез в VRAM — GPU-черновик выключен");
9716 return None;
9717 }
9718 }
9719 let gu_q2 = native_q2
9725 || (!crate::dsv4::dspark_native_on() && crate::dsv4::DSPARK_Q2TP_ENCODE.get().is_some());
9726 for (si, sp) in stages.iter().enumerate() {
9727 let ok = if sp.global.is_some() {
9728 true
9729 } else if native_q2 {
9730 crate::gpu_wgpu::dsv4_experts_ready(
9731 &model,
9732 &sp.tensors,
9733 cfg.moe_inter,
9734 cfg.dim,
9735 true,
9736 dn_native,
9737 )
9738 } else if gu_q2 {
9739 crate::gpu_wgpu::moe_expert_bufs_requant_gu(&model, &sp.tensors, cfg.moe_inter, cfg.dim)
9740 .is_some()
9741 } else {
9742 crate::gpu_wgpu::dsv4_experts_ready(
9743 &model,
9744 &sp.tensors,
9745 cfg.moe_inter,
9746 cfg.dim,
9747 false,
9748 false,
9749 )
9750 };
9751 if !ok {
9752 eprintln!(
9753 "DSpark: эксперты стадии {si} ({} + shared) не влезли в VRAM — GPU-черновик выключен",
9754 sp.n_resident
9755 );
9756 return None;
9757 }
9758 }
9759 let _ = crate::gpu_wgpu::pin_weights(&model, &skeleton);
9760 eprintln!(
9761 "DSpark: {} — {} стадии по {} экспертов + shared",
9762 if global_share {
9763 "защищённая доля единого пула"
9764 } else {
9765 "отдельный пак драфта на карте"
9766 },
9767 stages.len(),
9768 stages
9769 .iter()
9770 .map(|s| s.n_resident.to_string())
9771 .collect::<Vec<_>>()
9772 .join("/")
9773 );
9774 Some(DsparkPack {
9775 stages,
9776 gu_q2: if global_share { false } else { gu_q2 },
9777 dn_q2: dn_native,
9778 routers,
9779 biases,
9780 })
9781}
9782
9783pub fn dspark_ring_append(
9790 g: &Dsv4Globals,
9791 mtp: &[Dsv4Mtp],
9792 cfg: &Dsv4Cfg,
9793 ds: &mut DsparkState,
9794 pos: usize,
9795 pool: Option<&crate::pool::Pool>,
9796) {
9797 let (dim, hd, rd) = (cfg.dim, cfg.head_dim, cfg.rope_head_dim);
9798 let inv_freq = &g.inv_freq_window;
9799 let Some(stage0) = mtp.first() else { return };
9800 let (Some(mp), Some(mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
9801 return;
9802 };
9803 let mut main_x = vec![0.0f32; dim];
9804 mp.matvec(&ds.main_hidden, &mut main_x, pool);
9805 rms_weighted(&mut main_x, mn, cfg.norm_eps);
9806 for (si, m) in mtp.iter().enumerate() {
9807 let kvw = m.layer.wkv.rows();
9808 if ds.win[si].len() < cfg.window * kvw {
9809 ds.win[si].resize(cfg.window * kvw, 0.0);
9810 }
9811 let mut kv = vec![0.0f32; kvw];
9812 m.layer.wkv.matvec(&main_x, &mut kv, pool);
9813 rms_weighted(&mut kv, &m.layer.kv_norm, cfg.norm_eps);
9814 rope_tail(&mut kv[kvw - hd..], inv_freq, pos, rd, false);
9815 let slot = pos % cfg.window;
9816 ds.win[si][slot * kvw..(slot + 1) * kvw].copy_from_slice(&kv);
9817 ds.filled[si] = (pos + 1).min(cfg.window);
9818 }
9819}
9820
9821#[cfg(feature = "gpu")]
9827#[allow(clippy::too_many_arguments)]
9828pub fn dspark_draft_gpu(
9829 g: &Dsv4Globals,
9830 mtp: &[Dsv4Mtp],
9831 cfg: &Dsv4Cfg,
9832 ds: &mut DsparkState,
9833 pack: &DsparkPack,
9834 kv_id: u64,
9835 last_token: u32,
9836 pos: usize,
9837 pool: Option<&crate::pool::Pool>,
9838 out_conf: &mut Vec<f32>,
9839) -> Vec<u32> {
9840 let (hc, dim) = (cfg.hc_mult, cfg.dim);
9841 let block = dspark_block();
9842 let Some(model) = mtp[0].layer.experts.first().and_then(|e| e.w1.model_arc()) else {
9843 return Vec::new();
9844 };
9845 let (Some(mp), Some(mn)) = (mtp[0].main_proj.as_ref(), mtp[0].main_norm.as_ref()) else {
9846 return Vec::new();
9847 };
9848 let Some(mp_idx) = mp.model_idx() else {
9849 return Vec::new();
9850 };
9851 let mut stages = Vec::with_capacity(mtp.len());
9852 for (si, m) in mtp.iter().enumerate() {
9853 let l = &m.layer;
9854 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
9855 l.wq_a.model_idx(),
9856 l.wq_b.model_idx(),
9857 l.wo_a.model_idx(),
9858 l.wo_b.model_idx(),
9859 l.wkv.model_idx(),
9860 ) else {
9861 return Vec::new();
9862 };
9863 let sp = &pack.stages[si];
9864 stages.push(crate::gpu_wgpu::DsparkStageW {
9865 wq_a,
9866 wq_b,
9867 wo_a,
9868 wo_b,
9869 wkv,
9870 q_norm: &l.q_norm,
9871 kv_norm: &l.kv_norm,
9872 attn_norm: &l.attn_norm,
9873 ffn_norm: &l.ffn_norm,
9874 sink: &l.attn_sink,
9875 hc_attn_fn: &l.hc_attn_fn,
9876 hc_attn_scale: &l.hc_attn_scale,
9877 hc_attn_base: &l.hc_attn_base,
9878 hc_ffn_fn: &l.hc_ffn_fn,
9879 hc_ffn_scale: &l.hc_ffn_scale,
9880 hc_ffn_base: &l.hc_ffn_base,
9881 router: &pack.routers[si],
9882 bias: pack.biases[si].as_deref(),
9883 experts: &sp.tensors,
9884 mask_u32: &sp.mask_u32,
9885 map_u32: &sp.map_u32,
9886 global: sp.global.as_ref().map(|gl| crate::gpu_wgpu::Dsv4GlobalMoe {
9887 pool_uid: gl.pool.uid,
9888 shared_slot: gl.shared_slot,
9889 segment_slots: gl.pool.segment_slots as u32,
9890 }),
9891 });
9892 }
9893 let geom = crate::gpu_wgpu::DsparkGeom {
9894 dim,
9895 hc,
9896 nh: cfg.n_heads,
9897 hd: cfg.head_dim,
9898 rd: cfg.rope_head_dim,
9899 q_lora: cfg.q_lora_rank,
9900 o_lora: cfg.o_lora_rank,
9901 o_groups: cfg.o_groups,
9902 inter: cfg.moe_inter,
9903 n_experts: cfg.n_routed_experts,
9904 top_k: cfg.top_k,
9905 window: cfg.window,
9906 eps: cfg.norm_eps,
9907 hc_eps: cfg.hc_eps,
9908 sinkhorn_iters: cfg.hc_sinkhorn_iters,
9909 route_scale: cfg.route_scale,
9910 swiglu_limit: cfg.swiglu_limit,
9911 scale: (cfg.head_dim as f32).powf(-0.5),
9912 gu_q2: pack.gu_q2,
9913 dn_q2: pack.dn_q2,
9914 };
9915 let ids: Vec<u32> = (0..block)
9917 .map(|i| {
9918 if i == 0 {
9919 last_token
9920 } else {
9921 DSPARK_NOISE_TOKEN
9922 }
9923 })
9924 .collect();
9925 let mut states0 = vec![0.0f32; block * hc * dim];
9926 let mut emb = vec![0.0f32; dim];
9927 for (i, &id) in ids.iter().enumerate() {
9928 g.embed.row_f32(id as usize, &mut emb);
9929 for j in 0..hc {
9930 states0[(i * hc + j) * dim..(i * hc + j + 1) * dim].copy_from_slice(&emb);
9931 }
9932 }
9933 let dspark_time = {
9934 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9935 *ON.get_or_init(|| std::env::var("CMF_DSPARK_TIME").is_ok_and(|v| v != "0"))
9936 };
9937 let t0 = std::time::Instant::now();
9938 let filled = (pos + 1).min(cfg.window);
9939 let mut states = vec![0.0f32; block * hc * dim];
9940 if !crate::gpu_wgpu::dspark_graph(
9941 &model,
9942 &stages,
9943 geom,
9944 kv_id,
9945 mp_idx,
9946 mn,
9947 &ds.main_hidden,
9948 &states0,
9949 pos,
9950 filled,
9951 &g.inv_freq_window,
9952 block,
9953 &mut states,
9954 ) {
9955 return Vec::new();
9956 }
9957 for si in 0..mtp.len() {
9958 ds.filled[si] = filled;
9959 }
9960 let t_graph = t0.elapsed();
9961
9962 let last = &mtp[mtp.len() - 1];
9964 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
9965 last.hc_head_fn.as_ref(),
9966 last.hc_head_base.as_ref(),
9967 last.hc_head_scale,
9968 last.norm.as_ref(),
9969 ) else {
9970 return Vec::new();
9971 };
9972 let mut head_in = vec![0.0f32; block * dim];
9973 let mut pre_norms = vec![vec![0.0f32; dim]; block];
9974 for i in 0..block {
9975 hc_head_fold(
9976 &states[i * hc * dim..(i + 1) * hc * dim],
9977 hfn,
9978 hscale,
9979 hbase,
9980 cfg,
9981 pool,
9982 &mut head_in[i * dim..(i + 1) * dim],
9983 );
9984 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
9985 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
9986 }
9987 let t_fold = t0.elapsed();
9988 let mut logits = vec![0.0f32; block * cfg.vocab];
9989 let head_gpu = g.head.model_idx().is_some_and(|hi| {
9992 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
9993 &model,
9994 hi,
9995 &head_in,
9996 block,
9997 cfg.vocab,
9998 dim,
9999 &mut logits,
10000 )
10001 });
10002 if !head_gpu {
10003 g.head.matmat(&head_in, block, &mut logits, pool);
10004 }
10005 let t_head = t0.elapsed();
10006 let mut proposals = Vec::with_capacity(block);
10011 out_conf.clear();
10012 let mut prev = last_token;
10013 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
10014 let mut bias = vec![0.0f32; cfg.vocab];
10015 for i in 0..block {
10016 let row = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
10017 if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
10018 w1.row_f32(prev as usize, &mut mk_embed);
10019 w2.matvec(&mk_embed, &mut bias, pool);
10020 for (a, b) in row.iter_mut().zip(&bias) {
10021 *a += *b;
10022 }
10023 }
10024 let mut best = 0usize;
10025 for v in 1..row.len() {
10026 if row[v] > row[best] {
10027 best = v;
10028 }
10029 }
10030 if let Some(cf) = last.confidence.as_ref() {
10031 let mut cat = pre_norms[i].clone();
10032 cat.extend_from_slice(&mk_embed);
10033 let mut sc = [0.0f32; 1];
10034 if cat.len() == cf.cols() {
10035 cf.matvec(&cat, &mut sc, pool);
10036 }
10037 out_conf.push(sc[0]);
10038 }
10039 proposals.push(best as u32);
10040 prev = best as u32;
10041 }
10042 if dspark_time {
10043 eprintln!(
10044 "DSpark GPU: граф {:.1} мс, фолды {:.1}, голова {:.1}, марков+argmax {:.1}",
10045 t_graph.as_secs_f64() * 1e3,
10046 (t_fold - t_graph).as_secs_f64() * 1e3,
10047 (t_head - t_fold).as_secs_f64() * 1e3,
10048 (t0.elapsed() - t_head).as_secs_f64() * 1e3,
10049 );
10050 }
10051 proposals
10052}
10053
10054#[allow(clippy::too_many_arguments)]
10060pub fn dspark_draft(
10061 g: &Dsv4Globals,
10062 mtp: &[Dsv4Mtp],
10063 cfg: &Dsv4Cfg,
10064 ds: &mut DsparkState,
10065 last_token: u32,
10066 pos: usize,
10067 pool: Option<&crate::pool::Pool>,
10068 out_conf: &mut Vec<f32>,
10069) -> Vec<u32> {
10070 let (hc, dim, hd, rd) = (cfg.hc_mult, cfg.dim, cfg.head_dim, cfg.rope_head_dim);
10071 let block = dspark_block();
10072 let inv_freq = &g.inv_freq_window;
10073
10074 let Some(stage0) = mtp.first() else {
10076 return Vec::new();
10077 };
10078 let (Some(_mp), Some(_mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
10079 return Vec::new();
10080 };
10081 dspark_ring_append(g, mtp, cfg, ds, pos, pool);
10082
10083 let ids: Vec<u32> = (0..block)
10085 .map(|i| {
10086 if i == 0 {
10087 last_token
10088 } else {
10089 DSPARK_NOISE_TOKEN
10090 }
10091 })
10092 .collect();
10093 let mut states = vec![vec![0.0f32; hc * dim]; block];
10094 let mut emb = vec![0.0f32; dim];
10095 for (i, &id) in ids.iter().enumerate() {
10096 g.embed.row_f32(id as usize, &mut emb);
10097 for j in 0..hc {
10098 states[i][j * dim..(j + 1) * dim].copy_from_slice(&emb);
10099 }
10100 }
10101
10102 let mut scratch = HcScratch::new(cfg);
10103 for (si, m) in mtp.iter().enumerate() {
10104 let l = &m.layer;
10105 let kvw = l.wkv.rows();
10106 let mut post = vec![vec![0.0f32; hc]; block];
10109 let mut comb = vec![vec![0.0f32; hc * hc]; block];
10110 let mut resid = vec![vec![0.0f32; hc * dim]; block];
10111 let mut folded = vec![vec![0.0f32; dim]; block];
10112 let mix_hc = (2 + hc) * hc;
10113 for i in 0..block {
10114 hc_mixes(
10115 &states[i],
10116 &l.hc_attn_fn,
10117 mix_hc,
10118 cfg.norm_eps,
10119 pool,
10120 &mut scratch.mixes,
10121 );
10122 hc_split_sinkhorn(
10123 &scratch.mixes,
10124 &l.hc_attn_scale,
10125 &l.hc_attn_base,
10126 hc,
10127 cfg.hc_sinkhorn_iters,
10128 cfg.hc_eps,
10129 &mut scratch.pre,
10130 &mut post[i],
10131 &mut comb[i],
10132 );
10133 hc_fold(&states[i], &scratch.pre, hc, dim, &mut folded[i]);
10134 rms_weighted(&mut folded[i], &l.attn_norm, cfg.norm_eps);
10135 resid[i].copy_from_slice(&states[i]);
10136 }
10137 let folded_all: Vec<f32> = folded.iter().flatten().copied().collect();
10139 let mut blk_kv = vec![0.0f32; block * kvw];
10140 l.wkv.matmat(&folded_all, block, &mut blk_kv, pool);
10141 for i in 0..block {
10142 let dst = &mut blk_kv[i * kvw..(i + 1) * kvw];
10143 rms_weighted(dst, &l.kv_norm, cfg.norm_eps);
10144 rope_tail(&mut dst[kvw - hd..], inv_freq, pos + 1 + i, rd, false);
10145 }
10146 let win_len = ds.filled[si];
10148 let mut cache = Vec::with_capacity((win_len + block) * hd);
10149 for p in 0..win_len {
10150 let e = &ds.win[si][p * kvw..(p + 1) * kvw];
10151 cache.extend_from_slice(&e[kvw - hd..]);
10152 }
10153 for i in 0..block {
10154 let e = &blk_kv[i * kvw..(i + 1) * kvw];
10155 cache.extend_from_slice(&e[kvw - hd..]);
10156 }
10157 let idxs: Vec<usize> = (0..win_len + block).collect();
10158 let scale = (hd as f32).powf(-0.5);
10159 let qrank = l.wq_a.rows();
10160 let qdim = cfg.n_heads * hd;
10161 let mut qr = vec![0.0f32; block * qrank];
10162 l.wq_a.matmat(&folded_all, block, &mut qr, pool);
10163 for i in 0..block {
10164 rms_weighted(&mut qr[i * qrank..(i + 1) * qrank], &l.q_norm, cfg.norm_eps);
10165 }
10166 let mut q = vec![0.0f32; block * qdim];
10167 l.wq_b.matmat(&qr, block, &mut q, pool);
10168 let mut attn = vec![0.0f32; block * qdim];
10169 for i in 0..block {
10170 let qi = &mut q[i * qdim..(i + 1) * qdim];
10171 let ai = &mut attn[i * qdim..(i + 1) * qdim];
10172 let qpos = pos + 1 + i;
10173 for h in 0..cfg.n_heads {
10174 let head = &mut qi[h * hd..(h + 1) * hd];
10175 rms_inplace(head, cfg.norm_eps);
10176 rope_tail(head, inv_freq, qpos, rd, false);
10177 }
10178 for h in 0..cfg.n_heads {
10179 let qh = &qi[h * hd..(h + 1) * hd];
10180 let oh = &mut ai[h * hd..(h + 1) * hd];
10181 sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
10182 rope_tail(oh, inv_freq, qpos, rd, true);
10183 }
10184 }
10185 let mut blk_out = vec![0.0f32; block * dim];
10186 o_project_block(
10187 &attn,
10188 block,
10189 &l.wo_a,
10190 &l.wo_b,
10191 cfg.o_groups,
10192 cfg.o_lora_rank,
10193 pool,
10194 &mut blk_out,
10195 );
10196 for i in 0..block {
10197 let mut next = vec![0.0f32; hc * dim];
10198 hc_expand(
10199 &blk_out[i * dim..(i + 1) * dim],
10200 &resid[i],
10201 &post[i],
10202 &comb[i],
10203 hc,
10204 dim,
10205 &mut next,
10206 );
10207 states[i] = next;
10208 }
10209 let mut ffn_fold = vec![0.0f32; block * dim];
10212 let mut ffn_post = vec![vec![0.0f32; hc]; block];
10213 let mut ffn_comb = vec![vec![0.0f32; hc * hc]; block];
10214 let mut ffn_resid = vec![vec![0.0f32; hc * dim]; block];
10215 for i in 0..block {
10216 hc_mixes(
10217 &states[i],
10218 &l.hc_ffn_fn,
10219 mix_hc,
10220 cfg.norm_eps,
10221 pool,
10222 &mut scratch.mixes,
10223 );
10224 hc_split_sinkhorn(
10225 &scratch.mixes,
10226 &l.hc_ffn_scale,
10227 &l.hc_ffn_base,
10228 hc,
10229 cfg.hc_sinkhorn_iters,
10230 cfg.hc_eps,
10231 &mut scratch.pre,
10232 &mut ffn_post[i],
10233 &mut ffn_comb[i],
10234 );
10235 hc_fold(
10236 &states[i],
10237 &scratch.pre,
10238 hc,
10239 dim,
10240 &mut ffn_fold[i * dim..(i + 1) * dim],
10241 );
10242 rms_weighted(
10243 &mut ffn_fold[i * dim..(i + 1) * dim],
10244 &l.ffn_norm,
10245 cfg.norm_eps,
10246 );
10247 ffn_resid[i].copy_from_slice(&states[i]);
10248 }
10249 let mut moe_out = vec![0.0f32; block * dim];
10250 moe_step_block(&ffn_fold, block, l, cfg, &ids, si, pool, &mut moe_out);
10251 for i in 0..block {
10252 let mut next = vec![0.0f32; hc * dim];
10253 hc_expand(
10254 &moe_out[i * dim..(i + 1) * dim],
10255 &ffn_resid[i],
10256 &ffn_post[i],
10257 &ffn_comb[i],
10258 hc,
10259 dim,
10260 &mut next,
10261 );
10262 states[i] = next;
10263 }
10264 }
10265
10266 let last = &mtp[mtp.len() - 1];
10268 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
10269 last.hc_head_fn.as_ref(),
10270 last.hc_head_base.as_ref(),
10271 last.hc_head_scale,
10272 last.norm.as_ref(),
10273 ) else {
10274 return Vec::new();
10275 };
10276 let mut proposals = Vec::with_capacity(block);
10277 out_conf.clear();
10278 let mut prev = last_token;
10279 let mut head_in = vec![0.0f32; block * dim];
10280 let mut pre_norms = vec![vec![0.0f32; dim]; block];
10281 for i in 0..block {
10282 hc_head_fold(
10283 &states[i],
10284 hfn,
10285 hscale,
10286 hbase,
10287 cfg,
10288 pool,
10289 &mut head_in[i * dim..(i + 1) * dim],
10290 );
10291 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
10292 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
10293 }
10294 let mut logits = vec![0.0f32; block * cfg.vocab];
10295 g.head.matmat(&head_in, block, &mut logits, pool);
10296 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
10297 for i in 0..block {
10298 let logits_i = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
10299 let no_markov = {
10308 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10309 *ON.get_or_init(|| std::env::var("CMF_DSPARK_NO_MARKOV").is_ok_and(|v| v != "0"))
10310 };
10311 if no_markov {
10312 if let Some(w1) = last.markov_w1.as_ref() {
10314 w1.row_f32(prev as usize, &mut mk_embed);
10315 }
10316 } else if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
10317 w1.row_f32(prev as usize, &mut mk_embed);
10318 let mut bias = vec![0.0f32; cfg.vocab];
10319 w2.matvec(&mk_embed, &mut bias, pool);
10320 for (a, b) in logits_i.iter_mut().zip(&bias) {
10321 *a += *b;
10322 }
10323 }
10324 let mut best = 0usize;
10325 for v in 1..logits_i.len() {
10326 if logits_i[v] > logits_i[best] {
10327 best = v;
10328 }
10329 }
10330 if let Some(cf) = last.confidence.as_ref() {
10331 let mut cat = pre_norms[i].clone();
10332 cat.extend_from_slice(&mk_embed);
10333 let mut s = [0.0f32; 1];
10334 if cat.len() == cf.cols() {
10335 cf.matvec(&cat, &mut s, pool);
10336 }
10337 out_conf.push(s[0]);
10338 }
10339 proposals.push(best as u32);
10340 prev = best as u32;
10341 }
10342 proposals
10343}