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 ALL_NS: AtomicU64 = AtomicU64::new(0);
1059 pub static TOKENS: AtomicU64 = AtomicU64::new(0);
1060
1061 pub fn note_layer(li: usize) {
1064 CALLS.fetch_add(1, Ordering::Relaxed);
1065 if li == 0 {
1066 if TOKENS.fetch_add(1, Ordering::Relaxed) == 1 && !ZEROED.swap(true, Ordering::Relaxed)
1077 {
1078 for a in [&ATTN_NS, &MOE_NS, &HC_NS, &HEAD_NS, &ALL_NS, &CALLS] {
1079 a.store(0, Ordering::Relaxed);
1080 }
1081 TOKENS.store(1, Ordering::Relaxed);
1082 #[cfg(feature = "gpu")]
1083 for a in [
1084 &crate::gpu_wgpu::MOE_ENC_NS,
1085 &crate::gpu_wgpu::MOE_WAIT_NS,
1086 &crate::gpu_wgpu::MOE_BUFS_NS,
1087 &crate::gpu_wgpu::MOE_UP_NS,
1088 &crate::gpu_wgpu::MOE_PASS_NS,
1089 &crate::gpu_wgpu::ATT_ENC_NS,
1090 &crate::gpu_wgpu::ATT_WAIT_NS,
1091 &crate::gpu_wgpu::CHAIN_ENC_NS,
1092 &crate::gpu_wgpu::CHAIN_WAIT_NS,
1093 &crate::gpu_wgpu::CHAIN_LAYERS,
1094 &crate::gpu_wgpu::CHAIN_RUNS,
1095 &crate::gpu_wgpu::SUBMITS,
1096 &crate::gpu_wgpu::PASSES,
1097 ] {
1098 a.store(0, Ordering::Relaxed);
1099 }
1100 }
1101 }
1102 }
1103 static REPORT: AtomicBool = AtomicBool::new(false);
1104 static ZEROED: AtomicBool = AtomicBool::new(false);
1106
1107 pub fn on() -> bool {
1108 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1109 *ON.get_or_init(|| std::env::var("CMF_DSV4_PROFILE").is_ok_and(|v| v != "0"))
1110 }
1111
1112 pub fn report() {
1115 if !on() || REPORT.swap(true, Ordering::Relaxed) {
1116 return;
1117 }
1118 let calls = CALLS.load(Ordering::Relaxed).max(1);
1122 let toks = TOKENS.load(Ordering::Relaxed).max(1);
1123 let (a, m) = (
1124 ATTN_NS.load(Ordering::Relaxed) as f64 / 1e6,
1125 MOE_NS.load(Ordering::Relaxed) as f64 / 1e6,
1126 );
1127 let all = ALL_NS.load(Ordering::Relaxed) as f64 / 1e6;
1128 let hc = (HC_NS.load(Ordering::Relaxed) as f64 / 1e6
1135 - MOE_NS.load(Ordering::Relaxed) as f64 / 1e6)
1136 .max(0.0);
1137 let hd = HEAD_NS.load(Ordering::Relaxed) as f64 / 1e6;
1138 eprintln!(
1139 "[dsv4-профиль] {calls} вызовов слоя за {toks} токенов | \
1140 на токен: внимание {:.0} мс, MoE {:.0} мс, гипер-связи+нормы {:.0} мс, \
1141 голова {:.0} мс | на вызов: внимание {:.2}, MoE {:.2}, связи {:.2}",
1142 a / toks as f64,
1143 m / toks as f64,
1144 hc / toks as f64,
1145 hd / toks as f64,
1146 a / calls as f64,
1147 m / calls as f64,
1148 hc / calls as f64,
1149 );
1150 eprintln!(
1151 "[dsv4-профиль] весь проход {:.0} мс на токен; вне счётчиков {:.0} мс",
1152 all / toks as f64,
1153 (all - a - m - hd) / toks as f64,
1154 );
1155 #[cfg(feature = "gpu")]
1156 {
1157 let ae = crate::gpu_wgpu::ATT_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6;
1158 let aw = crate::gpu_wgpu::ATT_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6;
1159 if ae + aw > 0.0 {
1160 eprintln!(
1161 "[dsv4-профиль] кадр внимания на вызов: кодирование {:.2} мс, \
1162 отправка и ожидание {:.2} мс",
1163 ae / calls as f64,
1164 aw / calls as f64,
1165 );
1166 }
1167 let ub = crate::gpu_wgpu::UPLOAD_BYTES.load(Ordering::Relaxed);
1172 let un = crate::gpu_wgpu::UPLOAD_NS.load(Ordering::Relaxed);
1173 if ub > 0 && un > 0 {
1174 eprintln!(
1175 "[dsv4-профиль] ЗАЛИВКА весов: {:.1} ГБ за {:.1} с ({:.0} МБ/с)",
1176 ub as f64 / 1e9,
1177 un as f64 / 1e9,
1178 ub as f64 / (un as f64 / 1e9) / 1e6,
1179 );
1180 }
1181 let sub = crate::gpu_wgpu::SUBMITS.load(Ordering::Relaxed);
1182 if sub > 0 {
1183 eprintln!(
1184 "[dsv4-профиль] ОТПРАВОК на карту: {:.1} на токен, ПРОХОДОВ {:.0} \
1185 ({:.1} на слой)",
1186 sub as f64 / toks as f64,
1187 crate::gpu_wgpu::PASSES.load(Ordering::Relaxed) as f64 / toks as f64,
1188 crate::gpu_wgpu::PASSES.load(Ordering::Relaxed) as f64 / calls as f64,
1189 );
1190 }
1191 let cl = crate::gpu_wgpu::CHAIN_LAYERS.load(Ordering::Relaxed);
1192 if cl > 0 {
1193 let toks2 = toks.max(1) as f64;
1194 eprintln!(
1195 "[dsv4-профиль] ЦЕПОЧКА на токен: кодирование {:.2} мс, \
1196 ожидание {:.2} мс ({} слоёв, {} отправок)",
1197 crate::gpu_wgpu::CHAIN_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6 / toks2,
1198 crate::gpu_wgpu::CHAIN_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6 / toks2,
1199 cl / toks.max(1),
1200 crate::gpu_wgpu::CHAIN_RUNS.load(Ordering::Relaxed) / toks.max(1),
1201 );
1202 }
1203 let e = crate::gpu_wgpu::MOE_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6;
1204 let wt = crate::gpu_wgpu::MOE_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6;
1205 if e + wt > 0.0 {
1206 let ns = |a: &std::sync::atomic::AtomicU64| {
1207 a.load(Ordering::Relaxed) as f64 / 1e6 / calls as f64
1208 };
1209 eprintln!(
1210 "[dsv4-профиль] кадр MoE на вызов: кодирование {:.2} мс, \
1211 отправка и ожидание {:.2} мс",
1212 e / calls as f64,
1213 wt / calls as f64,
1214 );
1215 let an = crate::gpu_wgpu::ATT_GPU_N.load(Ordering::Relaxed);
1216 if an > 0 {
1217 let g = |i: usize| {
1218 crate::gpu_wgpu::ATT_GPU_NS[i].load(Ordering::Relaxed) as f64
1219 / 1e6 / an as f64
1220 };
1221 eprintln!(
1222 "[dsv4-профиль] ВНИМАНИЕ НА КАРТЕ на вызов: одиночное {:.3} мс, \
1223 оценки {:.3} мс, применение {:.3} мс",
1224 g(0), g(1), g(2),
1225 );
1226 }
1227 let gn = crate::gpu_wgpu::MOE_GPU_N.load(Ordering::Relaxed);
1228 let gns = crate::gpu_wgpu::MOE_GPU_NS[0].load(Ordering::Relaxed);
1229 if gn > 0 && gns > 0 {
1230 eprintln!(
1231 "[dsv4-профиль] MoE НА КАРТЕ: {:.3} мс на вызов ({gn} замеров)",
1232 gns as f64 / 1e6 / gn as f64,
1233 );
1234 } else if gn > 0 {
1235 eprintln!(
1239 "[dsv4-профиль] MoE НА КАРТЕ: метки вернули НОЛЬ на {gn} замерах — \
1240 запрос времени не сработал, число не использовать"
1241 );
1242 }
1243 eprintln!(
1244 "[dsv4-профиль] из кодирования: буферы экспертов {:.2} мс, \
1245 загрузки {:.2} мс, проходы {:.2} мс",
1246 ns(&crate::gpu_wgpu::MOE_BUFS_NS),
1247 ns(&crate::gpu_wgpu::MOE_UP_NS),
1248 ns(&crate::gpu_wgpu::MOE_PASS_NS),
1249 );
1250 }
1251 }
1252 }
1253}
1254
1255pub fn profile_report() {
1257 prof::report();
1258}
1259
1260fn gpu_attn_enabled() -> bool {
1265 #[cfg(feature = "gpu")]
1266 {
1267 use std::sync::OnceLock;
1268 static ON: OnceLock<bool> = OnceLock::new();
1269 *ON.get_or_init(|| {
1270 let want = std::env::var("CMF_DSV4_GPU_ATTN")
1271 .map(|v| v != "0")
1272 .unwrap_or(true);
1273 let have = want && crate::gpu::backend_available();
1274 if want && !have && std::env::var("CMF_DSV4_GPU_ATTN").is_ok() {
1275 tracing::warn!(
1276 "CMF_DSV4_GPU_ATTN задан, но устройства нет — блок внимания остаётся на CPU. Проверьте CMF_GPU=wgpu и Vulkan-ICD."
1277 );
1278 }
1279 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
1280 eprintln!("кадр dsv4: запрошен={want} доступен={have}");
1281 }
1282 have
1283 })
1284 }
1285 #[cfg(not(feature = "gpu"))]
1286 {
1287 false
1288 }
1289}
1290
1291#[cfg(feature = "gpu")]
1295#[allow(clippy::too_many_arguments)]
1296fn attn_frame(
1297 l: &Dsv4Layer,
1298 cfg: &Dsv4Cfg,
1299 st: &Dsv4State,
1300 li: usize,
1301 hidden: &[f32],
1302 qn: &[f32],
1303 idxs: &[usize],
1304 inv_freq: &[f32],
1305 pos: usize,
1306 win_len: usize,
1307 scale: f32,
1308 hc: Option<&crate::gpu_wgpu::Dsv4HcTail>,
1311 out: &mut [f32],
1312) -> bool {
1313 let hd = cfg.head_dim;
1314 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
1315 l.wq_a.model_idx(),
1316 l.wq_b.model_idx(),
1317 l.wo_a.model_idx(),
1318 l.wo_b.model_idx(),
1319 ) else {
1320 return false;
1321 };
1322 let Some(model) = l.wq_b.model_arc() else {
1323 return false;
1324 };
1325 let n_comp = st.compressed[li].len() / hd;
1330 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
1331 let kv_id = st.kv_id;
1332 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap) {
1338 return false;
1339 }
1340 if n_comp > 0
1349 && !crate::gpu_wgpu::dsv4_cache_write(
1350 kv_id,
1351 li,
1352 cfg.window * hd,
1353 &st.compressed[li],
1354 cap,
1355 )
1356 {
1357 return false;
1358 }
1359 let idx32: Vec<u32> = idxs
1360 .iter()
1361 .map(|&p| {
1362 if p < win_len {
1363 p as u32
1364 } else {
1365 (cfg.window + (p - win_len)) as u32
1366 }
1367 })
1368 .collect();
1369 let w = crate::gpu_wgpu::Dsv4AttnW {
1370 wq_a,
1371 wq_b,
1372 wo_a,
1373 wo_b,
1374 q_norm: &l.q_norm,
1375 sink: &l.attn_sink,
1376 };
1377 let g = crate::gpu_wgpu::Dsv4AttnGeom {
1378 dim: cfg.dim,
1379 nh: cfg.n_heads,
1380 hd,
1381 rd: cfg.rope_head_dim,
1382 q_lora: cfg.q_lora_rank,
1383 o_lora: cfg.o_lora_rank,
1384 o_groups: cfg.o_groups,
1385 eps: cfg.norm_eps,
1386 scale,
1387 };
1388 crate::gpu_wgpu::dsv4_attn_frame(
1396 &model, &w, g, hidden, Some(qn), kv_id, li, &idx32, inv_freq, pos, hc, out,
1397 )
1398}
1399
1400#[derive(Default)]
1403pub struct AttnPrep {
1404 pub qr: Vec<f32>,
1405 pub idxs: Vec<usize>,
1406 pub win_len: usize,
1407}
1408
1409#[allow(clippy::too_many_arguments)]
1410pub fn attention_step(
1411 hidden: &[f32],
1412 l: &Dsv4Layer,
1413 cfg: &Dsv4Cfg,
1414 st: &mut Dsv4State,
1415 li: usize,
1416 inv_freq: &[f32],
1418 pool: Option<&crate::pool::Pool>,
1419 prep_out: Option<&mut AttnPrep>,
1423 out: &mut [f32],
1424) {
1425 let _t0 = prof::on().then(std::time::Instant::now);
1426 let _guard = scopeguard_attn(_t0);
1427 let (hd, rd) = (cfg.head_dim, cfg.rope_head_dim);
1428 let pos = st.pos;
1429 if std::env::var("CMF_FREQ_DEBUG").is_ok() && li == 0 && pos == 0 {
1430 eprintln!(
1431 " [порт] rd={rd} частот={} inv_freq[0..4]={:?}",
1432 inv_freq.len(),
1433 &inv_freq[..4.min(inv_freq.len())]
1434 );
1435 }
1436
1437 let mut qr = vec![0.0f32; cfg.q_lora_rank];
1442 let mut kv = vec![0.0f32; hd];
1443 crate::qtensor::QTensor::matvec_many([&l.wq_a, &l.wkv], hidden, [&mut qr, &mut kv], pool);
1444 rms_weighted(&mut qr, &l.q_norm, cfg.norm_eps);
1445 let split_config = st.partial_set.iter().any(|&p| p) && st.split_deep;
1463 let past_chain = st.dev_owned
1464 && (li >= st.dev_set.len() || !st.dev_set.get(li).copied().unwrap_or(false));
1465 if std::env::var("CMF_DSV4_GATE_DBG").is_ok() {
1466 eprintln!(
1467 "[gate] li={li} pos={} split={split_config} past={past_chain} dev_owned={} set_len={} part_len={}",
1468 st.pos, st.dev_owned, st.dev_set.len(), st.partial_set.len()
1469 );
1470 }
1471 let on_gpu = gpu_attn_enabled() && !(split_config && past_chain);
1472
1473 rms_weighted(&mut kv, &l.kv_norm, cfg.norm_eps);
1474 rope_tail(&mut kv, inv_freq, pos, rd, false);
1475
1476 if let Some(cp) = &l.compressor {
1481 let mut pk = std::mem::take(&mut st.pending_kv[li]);
1482 let mut ps = std::mem::take(&mut st.pending_score[li]);
1483 let mut qk = std::mem::take(&mut st.prev_kv[li]);
1484 let mut qs = std::mem::take(&mut st.prev_score[li]);
1485 let entry = compressor_step(
1486 cp,
1487 hidden,
1488 pos,
1489 rd,
1490 cfg.norm_eps,
1491 inv_freq,
1492 pool,
1493 &mut pk,
1494 &mut ps,
1495 &mut qk,
1496 &mut qs,
1497 );
1498 st.pending_kv[li] = pk;
1499 st.pending_score[li] = ps;
1500 st.prev_kv[li] = qk;
1501 st.prev_score[li] = qs;
1502 if let Some(e) = entry {
1503 st.compressed[li].extend_from_slice(&e);
1504 }
1505 }
1506 if let Some(ix) = &l.indexer {
1511 let mut pk = std::mem::take(&mut st.pending_ix_kv[li]);
1512 let mut ps = std::mem::take(&mut st.pending_ix_score[li]);
1513 let mut qk = std::mem::take(&mut st.prev_ix_kv[li]);
1514 let mut qs = std::mem::take(&mut st.prev_ix_score[li]);
1515 let entry = compressor_step(
1516 &ix.compressor,
1517 hidden,
1518 pos,
1519 rd,
1520 cfg.norm_eps,
1521 inv_freq,
1522 pool,
1523 &mut pk,
1524 &mut ps,
1525 &mut qk,
1526 &mut qs,
1527 );
1528 st.pending_ix_kv[li] = pk;
1529 st.pending_ix_score[li] = ps;
1530 st.prev_ix_kv[li] = qk;
1531 st.prev_ix_score[li] = qs;
1532 if let Some(e) = entry {
1533 st.index_kv[li].extend_from_slice(&e);
1534 }
1535 }
1536
1537 st.window[li].extend_from_slice(&kv);
1538 let cap = cfg.window * hd;
1542 if st.window[li].len() > cap {
1543 let drop = st.window[li].len() - cap;
1544 st.window[li].drain(..drop);
1545 }
1546 let win_len = st.window[li].len() / hd;
1547 let n_pos = win_len + st.compressed[li].len() / hd;
1548
1549 let mut idxs: Vec<usize> = (0..win_len).collect();
1558 if !st.compressed[li].is_empty() && !no_compressed() {
1559 let n_comp = st.compressed[li].len() / hd;
1560 match &l.indexer {
1561 Some(ix) => {
1562 let ih = ix.weights_proj.rows();
1578 let idim = ix.wq_b.rows() / ih.max(1);
1579 let mut qi = vec![0.0f32; ix.wq_b.rows()];
1580 ix.wq_b.matvec(&qr, &mut qi, pool);
1581 for h in 0..ih {
1582 rope_tail(&mut qi[h * idim..(h + 1) * idim], inv_freq, pos, rd, false);
1583 }
1584 let mut hw = vec![0.0f32; ih];
1585 ix.weights_proj.matvec(hidden, &mut hw, pool);
1586 let sc_factor = (idim as f32).powf(-0.5) * (ih as f32).powf(-0.5);
1587 for w in hw.iter_mut() {
1588 *w *= sc_factor;
1589 }
1590 let n_ix = st.index_kv[li].len() / idim.max(1);
1591 let mut sc = Vec::new();
1592 index_scores(
1593 &qi,
1594 &st.index_kv[li],
1595 &hw,
1596 ih,
1597 idim,
1598 n_ix.min(n_comp),
1599 n_ix.min(n_comp),
1600 pool,
1601 &mut sc,
1602 );
1603 let mut picked = Vec::new();
1604 top_k_positions(&sc, cfg.index_topk, &mut picked);
1605 idxs.extend(picked.into_iter().map(|p| win_len + p));
1606 }
1607 None => idxs.extend((0..n_comp).map(|p| win_len + p)),
1608 }
1609 }
1610 debug_assert!(idxs.iter().all(|&p| p < n_pos));
1611 if let Some(p) = prep_out {
1612 p.qr = qr;
1613 p.idxs = idxs;
1614 p.win_len = win_len;
1615 return;
1616 }
1617
1618 let scale = (hd as f32).powf(-0.5);
1620 #[cfg(feature = "gpu")]
1621 if on_gpu
1622 && {
1623 if std::env::var("CMF_DSV4_XCHK").is_ok() {
1624 if let Some(card) = crate::gpu_wgpu::dsv4_dbg_read_tag(45, 0, hidden.len()) {
1628 let md = hidden
1629 .iter()
1630 .zip(card.iter())
1631 .map(|(a, b)| (a - b).abs())
1632 .fold(0.0f32, f32::max);
1633 eprintln!("[xchk] li={li} pos={pos} x2 maxdiff={md:.3e}");
1634 }
1635 }
1636 true
1637 }
1638 && attn_frame(
1639 l, cfg, st, li, hidden, &qr, &idxs, inv_freq, pos, win_len, scale, None, out,
1640 )
1641 {
1642 return;
1643 }
1644
1645 let mut q = vec![0.0f32; cfg.n_heads * hd];
1647 l.wq_b.matvec(&qr, &mut q, pool);
1648 for h in 0..cfg.n_heads {
1649 let head = &mut q[h * hd..(h + 1) * hd];
1650 rms_inplace(head, cfg.norm_eps);
1651 rope_tail(head, inv_freq, pos, rd, false);
1652 }
1653 let mut cache: Vec<f32> = st.window[li].clone();
1654 cache.extend_from_slice(&st.compressed[li]);
1655
1656 let mut attn = vec![0.0f32; cfg.n_heads * hd];
1658 for h in 0..cfg.n_heads {
1659 let qh = &q[h * hd..(h + 1) * hd];
1660 let oh = &mut attn[h * hd..(h + 1) * hd];
1665 sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
1666 rope_tail(oh, inv_freq, pos, rd, true);
1667 }
1668
1669 o_project(
1675 &attn,
1676 &|r, x, sc| l.wo_a.row_dot(r, x, sc),
1677 l.wo_a.cols(),
1678 &|mid, dst| l.wo_b.matvec(mid, dst, pool),
1679 cfg.o_groups,
1680 cfg.o_lora_rank,
1681 pool,
1682 out,
1683 );
1684}
1685
1686pub fn rms_weighted(v: &mut [f32], w: &[f32], eps: f32) {
1688 let ms = v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32;
1689 let inv = 1.0 / (ms + eps).sqrt();
1690 for (x, g) in v.iter_mut().zip(w) {
1691 *x = *x * inv * g;
1692 }
1693}
1694
1695thread_local! {
1707 static ROUTE_COUNTS: std::cell::RefCell<Vec<Vec<u64>>> =
1708 const { std::cell::RefCell::new(Vec::new()) };
1709}
1710
1711fn route_stats_on() -> bool {
1712 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1713 *ON.get_or_init(|| std::env::var("CMF_MOE_STATS").is_ok())
1714}
1715
1716fn record_route(li: usize, n_layers_hint: usize, n_experts: usize, idx: &[usize]) {
1717 ROUTE_COUNTS.with(|c| {
1718 let mut c = c.borrow_mut();
1719 if c.len() <= li.max(n_layers_hint) {
1720 c.resize(li.max(n_layers_hint) + 1, Vec::new());
1721 }
1722 let row = &mut c[li];
1723 if row.len() < n_experts {
1724 row.resize(n_experts, 0);
1725 }
1726 for &e in idx {
1727 if e < row.len() {
1728 row[e] += 1;
1729 }
1730 }
1731 });
1732}
1733
1734pub fn take_route_counts() -> Vec<Vec<u64>> {
1736 ROUTE_COUNTS.with(|c| std::mem::take(&mut *c.borrow_mut()))
1737}
1738
1739struct Charge(Option<std::time::Instant>, &'static std::sync::atomic::AtomicU64);
1743impl Drop for Charge {
1744 fn drop(&mut self) {
1745 if let Some(t) = self.0 {
1746 self.1.fetch_add(
1747 t.elapsed().as_nanos() as u64,
1748 std::sync::atomic::Ordering::Relaxed,
1749 );
1750 }
1751 }
1752}
1753fn scopeguard_attn(t: Option<std::time::Instant>) -> Charge {
1754 Charge(t, &prof::ATTN_NS)
1755}
1756fn scopeguard_moe(t: Option<std::time::Instant>, li: usize) -> Charge {
1757 if t.is_some() {
1758 prof::note_layer(li);
1759 }
1760 Charge(t, &prof::MOE_NS)
1761}
1762
1763#[cfg(feature = "gpu")]
1767#[allow(clippy::too_many_arguments)]
1768fn dsv4_layer_loop(
1769 state: &mut [f32],
1770 layers: &[Dsv4Layer],
1771 g: &Dsv4Globals,
1772 cfg: &Dsv4Cfg,
1773 st: &mut Dsv4State,
1774 token_id: u32,
1775 inv_freq: &[f32],
1776 pool: Option<&crate::pool::Pool>,
1777 scratch: &mut HcScratch,
1778) -> bool {
1779 let dim = cfg.dim;
1780 let freqs_of = |l: &Dsv4Layer| -> &[f32] {
1781 let f = if l.compressor.is_some() {
1782 &g.inv_freq_compress
1783 } else {
1784 &g.inv_freq_window
1785 };
1786 if f.is_empty() { inv_freq } else { f.as_slice() }
1787 };
1788 {
1801 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1802 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
1803 if let (Some(idx), Some(model)) = (g.head.model_idx(), g.head.model_arc()) {
1804 let ok = crate::gpu_wgpu::dsv4_weight_ready(&model, idx);
1805 tracing::info!("dsv4: голова на карте: {}", if ok { "да" } else { "нет" });
1806 }
1807 }
1808 }
1809 let mut on_dev = vec![false; layers.len()];
1810 let mut partial_dev = vec![false; layers.len()];
1811 for (li, l) in layers.iter().enumerate() {
1812 if l.wq_a.model_idx().is_none()
1813 || l.wq_b.model_idx().is_none()
1814 || l.wo_a.model_idx().is_none()
1815 || l.wo_b.model_idx().is_none()
1816 {
1817 return false;
1818 }
1819 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
1820 return false;
1821 };
1822 let gu_q2 = l
1823 .experts
1824 .first()
1825 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
1826 let mut want = vec![
1841 l.wq_a.model_idx(),
1842 l.wq_b.model_idx(),
1843 l.wo_a.model_idx(),
1844 l.wo_b.model_idx(),
1845 ];
1846 if chain_enabled() {
1847 want.push(l.wkv.model_idx());
1848 if let Some(cp) = &l.compressor {
1849 want.push(cp.wkv.model_idx());
1850 want.push(cp.wgate.model_idx());
1851 }
1852 if let Some(ix) = &l.indexer {
1853 want.push(ix.wq_b.model_idx());
1854 want.push(ix.weights_proj.model_idx());
1855 want.push(ix.compressor.wkv.model_idx());
1856 want.push(ix.compressor.wgate.model_idx());
1857 }
1858 }
1859 let attn_ok = want
1860 .into_iter()
1861 .flatten()
1862 .all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
1863 let pk = pack_for(l, cfg, li);
1868 if let Some(pk) = pk {
1869 let dn_q2 = l
1870 .experts
1871 .first()
1872 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
1873 let experts_ok = crate::gpu_wgpu::dsv4_experts_ready(
1874 &model,
1875 &pk.tensors,
1876 cfg.moe_inter,
1877 dim,
1878 gu_q2,
1879 dn_q2,
1880 );
1881 on_dev[li] = attn_ok && experts_ok && pk.globals.len() == cfg.n_routed_experts;
1882 partial_dev[li] = attn_ok && experts_ok && pk.globals.len() < cfg.n_routed_experts;
1883 }
1884 }
1885 let active_dev: Vec<bool> = on_dev
1886 .iter()
1887 .zip(&partial_dev)
1888 .map(|(&full, &partial)| full || partial)
1889 .collect();
1890 if !active_dev.iter().any(|&x| x) {
1891 return false;
1892 }
1893 if st.partial_set.len() != partial_dev.len() || st.partial_set != partial_dev {
1898 st.partial_set = partial_dev.clone();
1899 st.split_deep = active_dev
1900 .iter()
1901 .zip(&partial_dev)
1902 .filter(|(a, p)| !**a || **p)
1903 .count()
1904 > 1;
1905 }
1906
1907 {
1911 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1912 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
1913 let host: Vec<usize> = active_dev
1914 .iter()
1915 .enumerate()
1916 .filter(|&(_, d)| !*d)
1917 .map(|(i, _)| i)
1918 .collect();
1919 let partial: Vec<(usize, usize)> = partial_dev
1920 .iter()
1921 .enumerate()
1922 .filter(|&(_, d)| *d)
1923 .filter_map(|(li, _)| pack_for(&layers[li], cfg, li).map(|p| (li, p.globals.len())))
1924 .collect();
1925 if host.is_empty() && partial.is_empty() {
1926 tracing::info!("dsv4: все {} слоёв на карте", on_dev.len());
1927 } else {
1928 tracing::info!(
1929 "dsv4: {} из {} слоёв используют карту; частичные {:?}; на хосте {:?}",
1930 active_dev.len() - host.len(),
1931 on_dev.len(),
1932 partial,
1933 host,
1934 );
1935 }
1936 }
1937 }
1938
1939 let (mut folded, post0, comb0) = hc_fold_norm(
1941 state,
1942 &layers[0].hc_attn_fn,
1943 &layers[0].hc_attn_scale,
1944 &layers[0].hc_attn_base,
1945 &layers[0].attn_norm,
1946 cfg,
1947 pool,
1948 );
1949 if !crate::gpu_wgpu::dsv4_state_write(state)
1950 || !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
1951 {
1952 return false;
1953 }
1954 if st.dev_owned && st.dev_set != active_dev {
1965 let left: Vec<usize> = (0..active_dev.len().min(st.dev_set.len()))
1966 .filter(|&i| st.dev_set[i] && !active_dev[i])
1967 .collect();
1968 if !left.is_empty() {
1969 tracing::warn!("слои {left:?} ушли с карты — кеши на разных сторонах");
1970 return false;
1971 }
1972 }
1976 let chain = chain_enabled();
1977 let layer_cap = {
1985 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
1986 *N.get_or_init(|| {
1987 std::env::var("CMF_DSV4_LAYERS_PROBE")
1988 .ok()
1989 .and_then(|v| v.parse::<usize>().ok())
1990 .unwrap_or(usize::MAX)
1991 })
1992 };
1993 let mut run: Vec<usize> = Vec::new();
1994 let mut sink_out = vec![0.0f32; dim];
1995 let mut state_on_host = true;
2000 for (li, l) in layers.iter().enumerate() {
2001 if li >= layer_cap {
2002 break;
2003 }
2004 if prof::on() {
2010 prof::note_layer(li);
2011 }
2012 if chain && on_dev[li] {
2013 run.push(li);
2018 if run.len() >= chain_max() || dspark_wants(li) {
2023 let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
2024 let captured = *run.last().unwrap();
2025 if !dsv4_chain_run(
2026 layers, &run, cfg, g, st, token_id, &mut folded,
2027 Some(state), 1, &[], need_qn, pool,
2028 ) {
2029 return false;
2030 }
2031 state_on_host = true;
2032 dspark_note(captured, state, cfg);
2033 run.clear();
2034 }
2035 continue;
2036 }
2037 if chain && !run.is_empty() {
2038 if !dsv4_chain_run(
2042 layers,
2043 &run,
2044 cfg,
2045 g,
2046 st,
2047 token_id,
2048 &mut folded,
2049 Some(state),
2050 1,
2051 &[],
2052 run[0] == 0 || !on_dev[run[0] - 1],
2053 pool,
2054 ) {
2055 return false;
2056 }
2057 state_on_host = true;
2058 dspark_note(*run.last().unwrap(), state, cfg);
2059 }
2060 run.clear();
2061 if partial_dev[li] && partial_walk_on() {
2062 let Some(home) = dsv4_partial_layer(
2067 state,
2068 &mut folded,
2069 layers,
2070 l,
2071 cfg,
2072 st,
2073 token_id,
2074 li,
2075 freqs_of(l),
2076 pool,
2077 ) else {
2078 return false;
2079 };
2080 state_on_host = home;
2081 if home {
2082 dspark_note(li, state, cfg);
2083 }
2084 continue;
2085 }
2086 if !on_dev[li] {
2087 if !state_on_host && !crate::gpu_wgpu::dsv4_state_read(state) {
2088 return false;
2089 }
2090 state_on_host = true;
2091 let freqs = freqs_of(l);
2092 hc_block(
2093 state,
2094 &l.hc_attn_fn,
2095 &l.hc_attn_scale,
2096 &l.hc_attn_base,
2097 &l.attn_norm,
2098 cfg,
2099 scratch,
2100 pool,
2101 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
2102 );
2103 hc_block(
2104 state,
2105 &l.hc_ffn_fn,
2106 &l.hc_ffn_scale,
2107 &l.hc_ffn_base,
2108 &l.ffn_norm,
2109 cfg,
2110 scratch,
2111 pool,
2112 |f, o| {
2119 if host_cpu_moe() {
2120 crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
2121 } else {
2122 moe_step(f, l, cfg, token_id, li, pool, o)
2123 }
2124 },
2125 );
2126 if layers.get(li + 1).is_some() && on_dev.get(li + 1).copied().unwrap_or(false) {
2131 let n = &layers[li + 1];
2132 let (f, p2, c2) = hc_fold_norm(
2133 state,
2134 &n.hc_attn_fn,
2135 &n.hc_attn_scale,
2136 &n.hc_attn_base,
2137 &n.attn_norm,
2138 cfg,
2139 pool,
2140 );
2141 folded = f;
2142 if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2) {
2143 return false;
2144 }
2145 if !crate::gpu_wgpu::dsv4_state_write(state) {
2146 return false;
2147 }
2148 }
2149 dspark_note(li, state, cfg);
2150 continue;
2151 }
2152 let mut prep = AttnPrep::default();
2153 attention_step(
2154 &folded,
2155 l,
2156 cfg,
2157 st,
2158 li,
2159 freqs_of(l),
2160 pool,
2161 Some(&mut prep),
2162 &mut sink_out,
2163 );
2164 let hd = cfg.head_dim;
2166 let n_comp = st.compressed[li].len() / hd;
2167 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2168 let kv_id = st.kv_id;
2169 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap)
2170 || (n_comp > 0
2171 && !crate::gpu_wgpu::dsv4_cache_write(
2172 kv_id,
2173 li,
2174 cfg.window * hd,
2175 &st.compressed[li],
2176 cap,
2177 ))
2178 {
2179 return false;
2180 }
2181 let idx32: Vec<u32> = prep
2182 .idxs
2183 .iter()
2184 .map(|&p| {
2185 if p < prep.win_len {
2186 p as u32
2187 } else {
2188 (cfg.window + (p - prep.win_len)) as u32
2189 }
2190 })
2191 .collect();
2192 let Some(pk) = pack_for(l, cfg, li) else {
2193 return false;
2194 };
2195 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
2196 l.wq_a.model_idx(),
2197 l.wq_b.model_idx(),
2198 l.wo_a.model_idx(),
2199 l.wo_b.model_idx(),
2200 ) else {
2201 return false;
2202 };
2203 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
2204 return false;
2205 };
2206 let forced: Option<Vec<usize>> = l.tid2eid.as_ref().and_then(|tbl| {
2207 let v: Vec<usize> = hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
2208 .into_iter()
2209 .map(|gi| pk.to_slot[gi])
2210 .collect();
2211 if v.iter().any(|&x| x == usize::MAX) {
2212 None
2213 } else {
2214 Some(v)
2215 }
2216 });
2217 if l.tid2eid.is_some() && forced.is_none() {
2218 return false;
2219 }
2220 let nxt = layers.get(li + 1);
2221 let w = crate::gpu_wgpu::Dsv4LayerW {
2222 attn: crate::gpu_wgpu::Dsv4AttnW {
2223 wq_a,
2224 wq_b,
2225 wo_a,
2226 wo_b,
2227 q_norm: &l.q_norm,
2228 sink: &l.attn_sink,
2229 },
2230 moe: crate::gpu_wgpu::Dsv4MoeW {
2231 router: &[],
2232 experts: &pk.tensors,
2233 logits: &[],
2234 bias: pk.bias.as_deref(),
2241 forced: forced.as_deref(),
2242 remap: None,
2243 },
2244 hc_ffn_fn: &l.hc_ffn_fn,
2245 hc_ffn_scale: &l.hc_ffn_scale,
2246 hc_ffn_base: &l.hc_ffn_base,
2247 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2248 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2249 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2250 ffn_norm: &l.ffn_norm,
2251 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2252 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2253 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2254 router: &pk.router,
2255 };
2256 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2257 attn: crate::gpu_wgpu::Dsv4AttnGeom {
2258 dim,
2259 nh: cfg.n_heads,
2260 hd,
2261 rd: cfg.rope_head_dim,
2262 q_lora: cfg.q_lora_rank,
2263 o_lora: cfg.o_lora_rank,
2264 o_groups: cfg.o_groups,
2265 eps: cfg.norm_eps,
2266 scale: (hd as f32).powf(-0.5),
2267 },
2268 moe: crate::gpu_wgpu::Dsv4MoeGeom {
2269 hidden: dim,
2270 inter: cfg.moe_inter,
2271 top_k: cfg.top_k,
2272 route_scale: cfg.route_scale,
2273 swiglu_limit: cfg.swiglu_limit,
2274 gu_q2: l.experts.first().is_some_and(|e| {
2275 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
2276 }),
2277 },
2278 hc: cfg.hc_mult,
2279 hc_eps: cfg.hc_eps,
2280 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2281 };
2282 let mut next = vec![0.0f32; dim];
2283 if !crate::gpu_wgpu::dsv4_layer_frame(
2284 &model,
2285 &w,
2286 geom,
2287 kv_id,
2288 li,
2289 Some(&prep.qr),
2290 &idx32,
2291 freqs_of(l),
2292 st.pos,
2293 &mut next,
2294 ) {
2295 return false;
2296 }
2297 state_on_host = false;
2298 folded = next;
2299 dspark_note(li, state, cfg);
2300 }
2301 let mut state_home = false;
2302 if chain {
2303 if !run.is_empty() {
2304 let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
2308 let last_on_dev = *on_dev.last().unwrap_or(&false);
2309 let carry = last_on_dev && run.last() == Some(&(layers.len() - 1));
2310 let ok = if carry {
2311 let r = dsv4_chain_run(
2312 layers, &run, cfg, g, st, token_id, &mut folded,
2313 Some(state), 1, &[], need_qn, pool,
2314 );
2315 state_home = r;
2316 state_on_host = r;
2317 if r {
2318 dspark_note(*run.last().unwrap(), state, cfg);
2319 }
2320 r
2321 } else {
2322 let r = dsv4_chain_run(
2323 layers, &run, cfg, g, st, token_id, &mut folded, None, 1, &[], need_qn, pool,
2324 );
2325 if r {
2326 state_on_host = false;
2327 }
2328 r
2329 };
2330 if !ok {
2331 return false;
2332 }
2333 }
2334 if st.dev_set.is_empty() {
2335 st.dev_set = active_dev.clone();
2336 st.partial_set = partial_dev.clone();
2337 let mut idxs = Vec::new();
2343 for (li, l) in layers.iter().enumerate() {
2344 if !active_dev.get(li).copied().unwrap_or(false) {
2345 continue;
2346 }
2347 for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b, &l.gate] {
2348 idxs.extend(t.model_idx());
2349 }
2350 if let Some(pk) = pack_for(l, cfg, li) {
2351 for &(a, b, c) in &pk.tensors {
2352 idxs.extend([a, b, c]);
2353 }
2354 }
2355 }
2356 for (li, l) in layers.iter().enumerate() {
2364 if active_dev.get(li).copied().unwrap_or(false) {
2365 continue;
2366 }
2367 let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
2368 tracing::info!(
2369 "слой {li} на хосте: упаковано {packed} экспертов из {}",
2370 cfg.n_routed_experts
2371 );
2372 }
2373 let pinned = layers
2374 .iter()
2375 .find_map(|l| l.experts.first().and_then(|e| e.w1.model_arc()))
2376 .map_or(0, |m| crate::gpu_wgpu::pin_weights(&m, &idxs));
2377 tracing::info!(
2378 "закреплено на карте: {pinned} тензоров {} слоёв",
2379 on_dev.iter().filter(|&&x| x).count()
2380 );
2381 }
2382 }
2383 if state_home || state_on_host {
2384 return true;
2385 }
2386 crate::gpu_wgpu::dsv4_state_read(state)
2387}
2388
2389#[cfg(feature = "gpu")]
2394#[allow(clippy::too_many_arguments)]
2395fn dsv4_partial_layer(
2396 state: &mut [f32],
2397 folded: &mut Vec<f32>,
2398 layers: &[Dsv4Layer],
2399 l: &Dsv4Layer,
2400 cfg: &Dsv4Cfg,
2401 st: &mut Dsv4State,
2402 token_id: u32,
2403 li: usize,
2404 freqs: &[f32],
2405 pool: Option<&crate::pool::Pool>,
2406) -> Option<bool> {
2407 let dim = cfg.dim;
2408 let mut prep = AttnPrep::default();
2409 let mut sink = vec![0.0f32; dim];
2410 attention_step(
2411 folded,
2412 l,
2413 cfg,
2414 st,
2415 li,
2416 freqs,
2417 pool,
2418 Some(&mut prep),
2419 &mut sink,
2420 );
2421 let hd = cfg.head_dim;
2422 let n_comp = st.compressed[li].len() / hd;
2423 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2424 if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
2425 || (n_comp > 0
2426 && !crate::gpu_wgpu::dsv4_cache_write(
2427 st.kv_id,
2428 li,
2429 cfg.window * hd,
2430 &st.compressed[li],
2431 cap,
2432 ))
2433 {
2434 return None;
2435 }
2436 let a_tail = crate::gpu_wgpu::Dsv4HcTail {
2437 fn_: &l.hc_ffn_fn,
2438 scale: &l.hc_ffn_scale,
2439 base: &l.hc_ffn_base,
2440 norm: &l.ffn_norm,
2441 hc: cfg.hc_mult,
2442 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2443 hc_eps: cfg.hc_eps,
2444 eps: cfg.norm_eps,
2445 };
2446 let scale = (cfg.head_dim as f32).powf(-0.5);
2447 if !attn_frame(
2448 l,
2449 cfg,
2450 st,
2451 li,
2452 folded,
2453 &prep.qr,
2454 &prep.idxs,
2455 freqs,
2456 st.pos,
2457 prep.win_len,
2458 scale,
2459 Some(&a_tail),
2460 &mut [],
2461 ) {
2462 return None;
2463 }
2464 let nxt = layers.get(li + 1);
2465 let forced = l
2466 .tid2eid
2467 .as_ref()
2468 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
2469 let mut next = vec![0.0f32; dim];
2470 let (cold_sum, cold_count) = moe_frame(
2471 &[],
2472 l,
2473 cfg,
2474 li,
2475 &[],
2476 forced.as_deref(),
2477 pool,
2478 Some(&a_tail),
2479 None,
2484 &mut next,
2485 )?;
2486 let state_ok = if cold_count == 0 {
2492 crate::gpu_wgpu::dsv4_state_read(state)
2493 } else {
2494 crate::gpu_wgpu::dsv4_state_add_cold(&cold_sum, cfg.hc_mult, state)
2495 };
2496 if !state_ok {
2497 return None;
2498 }
2499 if let Some(n) = nxt {
2500 let (f, post, comb) = hc_fold_norm(
2501 state,
2502 &n.hc_attn_fn,
2503 &n.hc_attn_scale,
2504 &n.hc_attn_base,
2505 &n.attn_norm,
2506 cfg,
2507 pool,
2508 );
2509 *folded = f;
2510 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2511 || !crate::gpu_wgpu::dsv4_state_write(state)
2512 {
2513 return None;
2514 }
2515 }
2516 Some(true)
2521}
2522
2523#[cfg(feature = "gpu")]
2524fn chain_max() -> usize {
2525 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
2526 *N.get_or_init(|| {
2527 std::env::var("CMF_DSV4_CHAIN_MAX")
2528 .ok()
2529 .and_then(|v| v.parse().ok())
2530 .unwrap_or(usize::MAX)
2531 })
2532}
2533
2534#[cfg(feature = "gpu")]
2537fn partial_walk_on() -> bool {
2548 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2549 *ON.get_or_init(|| std::env::var("CMF_DSV4_PARTIAL_WALK").is_ok_and(|v| v != "0"))
2550}
2551
2552fn host_cpu_moe() -> bool {
2553 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2554 *ON.get_or_init(|| std::env::var("CMF_DSV4_HOST_CPU_MOE").is_ok_and(|v| v != "0"))
2555}
2556
2557fn chain_enabled() -> bool {
2558 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2559 *ON.get_or_init(|| std::env::var("CMF_DSV4_CHAIN").map(|v| v != "0").unwrap_or(true))
2560}
2561
2562#[cfg(feature = "gpu")]
2572#[allow(clippy::too_many_arguments)]
2573fn dsv4_chain_run(
2574 layers: &[Dsv4Layer],
2575 run: &[usize],
2576 cfg: &Dsv4Cfg,
2577 g: &Dsv4Globals,
2578 st: &mut Dsv4State,
2579 token_id: u32,
2580 folded: &mut Vec<f32>,
2586 state_out: Option<&mut [f32]>,
2591 batch: usize,
2594 batch_ids: &[u32],
2598 need_qn: bool,
2605 pool: Option<&crate::pool::Pool>,
2606) -> bool {
2607 if run.is_empty() {
2608 return true;
2609 }
2610 let (dim, hd) = (cfg.dim, cfg.head_dim);
2611 let first = run[0];
2612 let Some(model) = layers[first].experts.first().and_then(|e| e.w1.model_arc()) else {
2613 return false;
2614 };
2615 if batch <= 1 && need_qn {
2619 let mut qn0 = vec![0.0f32; cfg.q_lora_rank];
2620 layers[first].wq_a.matvec(folded, &mut qn0, pool);
2621 rms_weighted(&mut qn0, &layers[first].q_norm, cfg.norm_eps);
2622 if !crate::gpu_wgpu::dsv4_chain_seed(folded, &qn0) {
2623 return false;
2624 }
2625 } else if batch <= 1 && !crate::gpu_wgpu::dsv4_chain_seed_fold(folded) {
2626 return false;
2627 }
2628
2629 let mut packs = Vec::with_capacity(run.len());
2631 let mut forceds: Vec<Option<Vec<usize>>> = Vec::with_capacity(run.len());
2632 for &li in run {
2633 let Some(pk) = pack_for(&layers[li], cfg, li) else {
2634 return false;
2635 };
2636 let forced: Option<Vec<usize>> = layers[li].tid2eid.as_ref().and_then(|tbl| {
2637 let v: Vec<usize> = hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
2638 .into_iter()
2639 .map(|gi| pk.to_slot[gi])
2640 .collect();
2641 if v.iter().any(|&x| x == usize::MAX) { None } else { Some(v) }
2642 });
2643 if layers[li].tid2eid.is_some() && forced.is_none() {
2644 return false;
2645 }
2646 forceds.push(forced);
2647 packs.push(pk);
2648 }
2649
2650 let mut items = Vec::with_capacity(run.len());
2651 let mut freqs = Vec::with_capacity(run.len());
2652 for (i, &li) in run.iter().enumerate() {
2653 let l = &layers[li];
2654 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
2655 l.wq_a.model_idx(),
2656 l.wq_b.model_idx(),
2657 l.wo_a.model_idx(),
2658 l.wo_b.model_idx(),
2659 l.wkv.model_idx(),
2660 ) else {
2661 return false;
2662 };
2663 let comp = match &l.compressor {
2664 None => None,
2665 Some(cp) => {
2666 let (Some(a), Some(b)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
2667 return false;
2668 };
2669 Some((
2670 crate::gpu_wgpu::Dsv4CompW { wkv: a, wgate: b, norm: &cp.norm, ape: &cp.ape },
2671 crate::gpu_wgpu::Dsv4CompGeom {
2672 width: cp.wkv.rows(),
2673 hidden: dim,
2674 ratio: cp.ratio,
2675 overlap: cp.overlap,
2676 rope_dim: cfg.rope_head_dim,
2677 eps: cfg.norm_eps,
2678 },
2679 ))
2680 }
2681 };
2682 let ix = match &l.indexer {
2683 None => None,
2684 Some(ixr) => {
2685 let cp = &ixr.compressor;
2686 let (Some(a), Some(b), Some(qb), Some(wp)) = (
2687 cp.wkv.model_idx(),
2688 cp.wgate.model_idx(),
2689 ixr.wq_b.model_idx(),
2690 ixr.weights_proj.model_idx(),
2691 ) else {
2692 return false;
2693 };
2694 let ih = ixr.weights_proj.rows();
2695 Some((
2696 crate::gpu_wgpu::Dsv4CompW { wkv: a, wgate: b, norm: &cp.norm, ape: &cp.ape },
2697 crate::gpu_wgpu::Dsv4CompGeom {
2698 width: cp.wkv.rows(),
2699 hidden: dim,
2700 ratio: cp.ratio,
2701 overlap: cp.overlap,
2702 rope_dim: cfg.rope_head_dim,
2703 eps: cfg.norm_eps,
2704 },
2705 crate::gpu_wgpu::Dsv4IxW { wq_b: qb, weights_proj: wp },
2706 crate::gpu_wgpu::Dsv4IxGeom {
2707 ih,
2708 idim: ixr.wq_b.rows() / ih.max(1),
2709 q_lora: cfg.q_lora_rank,
2710 hidden: dim,
2711 rope_dim: cfg.rope_head_dim,
2712 eps: cfg.norm_eps,
2713 top_k: cfg.index_topk,
2714 window: cfg.window,
2715 },
2716 ))
2717 }
2718 };
2719 let ew_c0 = l.compressor.as_ref().map_or(0, |cp| {
2723 if cp.overlap { cp.wkv.rows() / 2 } else { cp.wkv.rows() }
2724 });
2725 let comp_extra = l.compressor.as_ref().map_or(0, |cp| {
2726 batch.max(1).div_ceil(cp.ratio.max(1))
2727 });
2728 let need = cfg.window * hd
2729 + (st.dev_n_comp[li] + comp_extra + 1) * ew_c0.max(1)
2730 + (batch.max(1) + 1) * hd;
2731 if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
2732 return false;
2733 }
2734 let ew_c = comp.as_ref().map_or(0, |(_, cg)| {
2735 if cg.overlap { cg.width / 2 } else { cg.width }
2736 });
2737 let ew_i = ix.as_ref().map_or(0, |(_, cg, _, _)| {
2738 if cg.overlap { cg.width / 2 } else { cg.width }
2739 });
2740 let prep = crate::gpu_wgpu::Dsv4Prep {
2741 wkv,
2742 kv_norm: &l.kv_norm,
2743 comp,
2744 ix,
2745 filled: st.dev_filled[li],
2746 window: cfg.window,
2747 n_comp: st.dev_n_comp[li],
2748 n_ix: st.dev_n_ix[li],
2749 comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
2750 ix_dst_off: st.dev_n_ix[li] * ew_i,
2751 idx_cap: cfg.window
2752 + if l.indexer.is_some() {
2753 cfg.index_topk
2754 } else {
2755 st.dev_n_comp[li] + comp_extra + 1
2756 },
2757 };
2758 let nxt = layers.get(li + 1);
2759 let w = crate::gpu_wgpu::Dsv4LayerW {
2760 attn: crate::gpu_wgpu::Dsv4AttnW {
2761 wq_a, wq_b, wo_a, wo_b,
2762 q_norm: &l.q_norm,
2763 sink: &l.attn_sink,
2764 },
2765 moe: crate::gpu_wgpu::Dsv4MoeW {
2766 router: &packs[i].router,
2767 experts: &packs[i].tensors,
2768 logits: &[],
2769 bias: packs[i].bias.as_deref(),
2772 forced: forceds[i].as_deref(),
2773 remap: None,
2774 },
2775 hc_ffn_fn: &l.hc_ffn_fn,
2776 hc_ffn_scale: &l.hc_ffn_scale,
2777 hc_ffn_base: &l.hc_ffn_base,
2778 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2779 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2780 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2781 ffn_norm: &l.ffn_norm,
2782 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2783 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2784 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2785 router: &packs[i].router,
2786 };
2787 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2788 attn: crate::gpu_wgpu::Dsv4AttnGeom {
2789 dim,
2790 nh: cfg.n_heads,
2791 hd,
2792 rd: cfg.rope_head_dim,
2793 q_lora: cfg.q_lora_rank,
2794 o_lora: cfg.o_lora_rank,
2795 o_groups: cfg.o_groups,
2796 eps: cfg.norm_eps,
2797 scale: (hd as f32).powf(-0.5),
2798 },
2799 moe: crate::gpu_wgpu::Dsv4MoeGeom {
2800 hidden: dim,
2801 inter: cfg.moe_inter,
2802 top_k: cfg.top_k,
2803 route_scale: cfg.route_scale,
2804 swiglu_limit: cfg.swiglu_limit,
2805 gu_q2: l.experts.first().is_some_and(|e| {
2806 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
2807 }),
2808 },
2809 hc: cfg.hc_mult,
2810 hc_eps: cfg.hc_eps,
2811 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2812 };
2813 freqs.push(if l.compressor.is_some() {
2814 g.inv_freq_compress.as_slice()
2815 } else {
2816 g.inv_freq_window.as_slice()
2817 });
2818 items.push((w, geom, prep));
2819 }
2820
2821 let mut out = vec![0.0f32; dim * batch.max(1)];
2822 if batch > 1 {
2823 let mut forced_pt: Vec<Vec<Option<Vec<usize>>>> = Vec::with_capacity(batch);
2828 for t in 0..batch {
2829 let id = batch_ids.get(t).copied().unwrap_or(token_id);
2830 let mut row = Vec::with_capacity(run.len());
2831 for (i, &li) in run.iter().enumerate() {
2832 row.push(layers[li].tid2eid.as_ref().and_then(|tbl| {
2833 let v: Vec<usize> = hash_route(tbl, cfg.vocab, cfg.top_k, id)
2834 .into_iter()
2835 .map(|gi| packs[i].to_slot[gi])
2836 .collect();
2837 if v.iter().any(|&x| x == usize::MAX) { None } else { Some(v) }
2838 }));
2839 if layers[li].tid2eid.is_some() && row[i].is_none() {
2840 return false;
2841 }
2842 }
2843 forced_pt.push(row);
2844 }
2845 if !crate::gpu_wgpu::dsv4_chain_batch(
2846 &model, &items, st.kv_id, first, &freqs, st.pos, batch,
2847 Some(&forced_pt), &mut out, state_out,
2848 ) {
2849 return false;
2850 }
2851 *folded = out[(batch - 1) * dim..batch * dim].to_vec();
2854 } else {
2855 if !crate::gpu_wgpu::dsv4_layer_chain(
2856 &model, &items, st.kv_id, first, &freqs, st.pos, &mut out, state_out,
2857 ) {
2858 return false;
2859 }
2860 *folded = out;
2861 }
2862 for (i, &li) in run.iter().enumerate() {
2866 for t in 0..batch.max(1) {
2867 let pos = st.pos + t;
2868 st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
2869 if let Some((_, cg, ..)) = items[i].2.ix.as_ref() {
2870 if (pos + 1) % cg.ratio == 0 {
2871 st.dev_n_ix[li] += 1;
2872 }
2873 }
2874 if let Some((_, cg)) = items[i].2.comp.as_ref() {
2875 if (pos + 1) % cg.ratio == 0 {
2876 st.dev_n_comp[li] += 1;
2877 }
2878 }
2879 }
2880 }
2881 st.dev_owned = true;
2882 true
2883}
2884
2885#[cfg(feature = "gpu")]
2887fn hc_on_device() -> bool {
2888 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2889 *ON.get_or_init(|| {
2890 std::env::var("CMF_DSV4_HC_DEV").is_ok_and(|v| v != "0")
2896 && crate::gpu::backend_available()
2897 })
2898}
2899
2900#[cfg(feature = "gpu")]
2909#[allow(clippy::too_many_arguments)]
2910fn dsv4_two_frame_loop(
2911 state: &mut [f32],
2912 layers: &[Dsv4Layer],
2913 g: &Dsv4Globals,
2914 cfg: &Dsv4Cfg,
2915 st: &mut Dsv4State,
2916 token_id: u32,
2917 inv_freq: &[f32],
2918 pool: Option<&crate::pool::Pool>,
2919 scratch: &mut HcScratch,
2920) -> bool {
2921 let dim = cfg.dim;
2922 let freqs_of = |l: &Dsv4Layer| -> &[f32] {
2923 let f = if l.compressor.is_some() {
2924 &g.inv_freq_compress
2925 } else {
2926 &g.inv_freq_window
2927 };
2928 if f.is_empty() { inv_freq } else { f.as_slice() }
2929 };
2930 let (mut folded, post0, comb0) = hc_fold_norm(
2932 state,
2933 &layers[0].hc_attn_fn,
2934 &layers[0].hc_attn_scale,
2935 &layers[0].hc_attn_base,
2936 &layers[0].attn_norm,
2937 cfg,
2938 pool,
2939 );
2940 if !crate::gpu_wgpu::dsv4_state_write(state)
2941 || !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
2942 {
2943 return false;
2944 }
2945 let mut on_dev = vec![false; layers.len()];
2950 for (li, l) in layers.iter().enumerate() {
2951 let Some(pk) = pack_for(l, cfg, li) else {
2952 return false;
2953 };
2954 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
2955 return false;
2956 };
2957 let gu_q2 = l
2958 .experts
2959 .first()
2960 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
2961 let attn_ok = [
2962 l.wq_a.model_idx(),
2963 l.wq_b.model_idx(),
2964 l.wo_a.model_idx(),
2965 l.wo_b.model_idx(),
2966 ]
2967 .into_iter()
2968 .flatten()
2969 .all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
2970 on_dev[li] = attn_ok
2971 && pk.globals.len() == cfg.n_routed_experts
2972 && crate::gpu_wgpu::dsv4_experts_ready(
2973 &model,
2974 &pk.tensors,
2975 cfg.moe_inter,
2976 dim,
2977 gu_q2,
2978 l.experts.first().is_some_and(|e| {
2979 e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
2980 }),
2981 );
2982 }
2983 if !on_dev.iter().any(|&x| x) {
2984 return false;
2985 }
2986 let mut sink = vec![0.0f32; dim];
2987 for (li, l) in layers.iter().enumerate() {
2988 if !on_dev[li] {
2992 if !crate::gpu_wgpu::dsv4_state_read(state) {
2993 return false;
2994 }
2995 let freqs = freqs_of(l);
2996 hc_block(
2997 state,
2998 &l.hc_attn_fn,
2999 &l.hc_attn_scale,
3000 &l.hc_attn_base,
3001 &l.attn_norm,
3002 cfg,
3003 scratch,
3004 pool,
3005 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
3006 );
3007 hc_block(
3008 state,
3009 &l.hc_ffn_fn,
3010 &l.hc_ffn_scale,
3011 &l.hc_ffn_base,
3012 &l.ffn_norm,
3013 cfg,
3014 scratch,
3015 pool,
3016 |f, o| moe_step(f, l, cfg, token_id, li, pool, o),
3017 );
3018 let nref = layers.get(li + 1).unwrap_or(l);
3019 let (f, p2, c2) = hc_fold_norm(
3020 state,
3021 &nref.hc_attn_fn,
3022 &nref.hc_attn_scale,
3023 &nref.hc_attn_base,
3024 &nref.attn_norm,
3025 cfg,
3026 pool,
3027 );
3028 folded = f;
3029 if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2)
3030 || !crate::gpu_wgpu::dsv4_state_write(state)
3031 {
3032 return false;
3033 }
3034 continue;
3035 }
3036 let mut prep = AttnPrep::default();
3038 attention_step(
3039 &folded, l, cfg, st, li, freqs_of(l), pool, Some(&mut prep), &mut sink,
3040 );
3041 let hd = cfg.head_dim;
3042 let n_comp = st.compressed[li].len() / hd;
3043 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
3044 if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
3045 || (n_comp > 0
3046 && !crate::gpu_wgpu::dsv4_cache_write(
3047 st.kv_id, li, cfg.window * hd, &st.compressed[li], cap,
3048 ))
3049 {
3050 return false;
3051 }
3052 let idx32: Vec<u32> = prep
3053 .idxs
3054 .iter()
3055 .map(|&p| {
3056 if p < prep.win_len {
3057 p as u32
3058 } else {
3059 (cfg.window + (p - prep.win_len)) as u32
3060 }
3061 })
3062 .collect();
3063 let nxt = layers.get(li + 1);
3064 let a_tail = crate::gpu_wgpu::Dsv4HcTail {
3065 fn_: &l.hc_ffn_fn,
3066 scale: &l.hc_ffn_scale,
3067 base: &l.hc_ffn_base,
3068 norm: &l.ffn_norm,
3069 hc: cfg.hc_mult,
3070 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3071 hc_eps: cfg.hc_eps,
3072 eps: cfg.norm_eps,
3073 };
3074 let scale = (cfg.head_dim as f32).powf(-0.5);
3075 if !attn_frame(
3076 l, cfg, st, li, &folded, &prep.qr, &prep.idxs, freqs_of(l), st.pos,
3077 prep.win_len, scale, Some(&a_tail), &mut [],
3078 ) {
3079 return false;
3080 }
3081 let m_tail = nxt.map(|n| crate::gpu_wgpu::Dsv4HcTail {
3082 fn_: &n.hc_attn_fn,
3083 scale: &n.hc_attn_scale,
3084 base: &n.hc_attn_base,
3085 norm: &n.attn_norm,
3086 hc: cfg.hc_mult,
3087 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3088 hc_eps: cfg.hc_eps,
3089 eps: cfg.norm_eps,
3090 });
3091 let mut next = vec![0.0f32; dim];
3092 let pair = m_tail
3093 .as_ref()
3094 .zip(nxt)
3095 .map(|(t, n)| (t, n.attn_norm.as_slice()));
3096 let forced = l
3097 .tid2eid
3098 .as_ref()
3099 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
3100 if moe_frame(&[], l, cfg, li, &[], forced.as_deref(), pool, Some(&a_tail), pair, &mut next)
3101 .is_none()
3102 {
3103 return false;
3104 }
3105 folded = next;
3106 }
3107 let _ = scratch;
3108 crate::gpu_wgpu::dsv4_state_read(state)
3109}
3110
3111#[cfg(feature = "gpu")]
3115#[allow(clippy::too_many_arguments)]
3116fn hc_fold_norm(
3117 state: &[f32],
3118 hc_fn: &[f32],
3119 hc_scale: &[f32; 3],
3120 hc_base: &[f32],
3121 norm_w: &[f32],
3122 cfg: &Dsv4Cfg,
3123 pool: Option<&crate::pool::Pool>,
3124) -> (Vec<f32>, Vec<f32>, Vec<f32>) {
3125 let (hc, dim) = (cfg.hc_mult, cfg.dim);
3126 let mix_hc = (2 + hc) * hc;
3127 let mut mixes = vec![0.0f32; mix_hc];
3128 hc_mixes(state, hc_fn, mix_hc, cfg.norm_eps, pool, &mut mixes);
3129 let mut pre = vec![0.0f32; hc];
3130 let mut post = vec![0.0f32; hc];
3131 let mut comb = vec![0.0f32; hc * hc];
3132 hc_split_sinkhorn(
3133 &mixes,
3134 hc_scale,
3135 hc_base,
3136 hc,
3137 cfg.hc_sinkhorn_iters,
3138 cfg.hc_eps,
3139 &mut pre,
3140 &mut post,
3141 &mut comb,
3142 );
3143 let mut folded = vec![0.0f32; dim];
3144 hc_fold(state, &pre, hc, dim, &mut folded);
3145 rms_weighted(&mut folded, norm_w, cfg.norm_eps);
3146 (folded, post, comb)
3149}
3150
3151#[cfg(feature = "gpu")]
3168fn gpu_layer_enabled() -> bool {
3169 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3170 *ON.get_or_init(|| {
3171 std::env::var("CMF_DSV4_GPU_LAYER").map(|v| v != "0").unwrap_or(true)
3172 && crate::gpu::backend_available()
3173 })
3174}
3175
3176#[cfg(feature = "gpu")]
3180struct Pack {
3181 router: Vec<f32>,
3185 to_slot: Vec<usize>,
3187 remap: Vec<u32>,
3189 globals: Vec<usize>,
3191 tensors: Vec<(usize, usize, usize)>,
3192 bias: Option<Vec<f32>>,
3201}
3202
3203#[cfg(feature = "gpu")]
3204fn pack_freq_order(li: usize, n: usize) -> Option<Vec<usize>> {
3210 use std::collections::HashMap;
3211 use std::sync::OnceLock;
3212 static FREQ: OnceLock<Option<HashMap<(usize, usize), u64>>> = OnceLock::new();
3213 let map = FREQ
3214 .get_or_init(|| {
3215 let path = std::env::var("CMF_DSV4_PACK_FREQ").ok()?;
3216 let text = match std::fs::read_to_string(&path) {
3217 Ok(t) => t,
3218 Err(e) => {
3219 eprintln!("CMF_DSV4_PACK_FREQ={path} не читается ({e}) — порядок по id");
3220 return None;
3221 }
3222 };
3223 let mut m = HashMap::new();
3224 for line in text.lines() {
3225 let mut it = line.split('\t');
3226 if let (Some(l), Some(e), Some(c)) = (it.next(), it.next(), it.next()) {
3227 if let (Ok(l), Ok(e), Ok(c)) =
3228 (l.trim().parse(), e.trim().parse(), c.trim().parse::<u64>())
3229 {
3230 *m.entry((l, e)).or_insert(0) += c;
3231 }
3232 }
3233 }
3234 Some(m)
3235 })
3236 .as_ref()?;
3237 if !(0..n).any(|e| map.contains_key(&(li, e))) {
3238 return None;
3239 }
3240 let mut idx: Vec<usize> = (0..n).collect();
3241 idx.sort_by_key(|&e| (std::cmp::Reverse(map.get(&(li, e)).copied().unwrap_or(0)), e));
3242 Some(idx)
3243}
3244
3245#[cfg(feature = "gpu")]
3246fn pack_for(l: &Dsv4Layer, cfg: &Dsv4Cfg, li: usize) -> Option<std::sync::Arc<Pack>> {
3247 use std::collections::HashMap;
3248 use std::sync::{Arc, Mutex, OnceLock};
3249 static CACHE: OnceLock<Mutex<HashMap<(u64, usize, usize), Option<Arc<Pack>>>>> =
3250 OnceLock::new();
3251 let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
3252 let model_uid = l
3258 .experts
3259 .first()
3260 .and_then(|e| e.w1.model_arc())
3261 .map_or(0, |m| m.uid());
3262 let first_expert = l
3268 .experts
3269 .first()
3270 .and_then(|e| e.w1.model_idx())
3271 .unwrap_or(usize::MAX);
3272 let key = (model_uid, li, first_expert);
3273 if let Some(v) = cache.lock().unwrap().get(&key) {
3274 return v.clone();
3275 }
3276 if let Ok(v) = std::env::var("CMF_DSV4_PACK_MAX_LI") {
3283 if v.parse::<usize>().is_ok_and(|max| li > max) {
3284 cache.lock().unwrap().insert(key, None);
3285 return None;
3286 }
3287 }
3288 let build = || -> Option<Arc<Pack>> {
3289 let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
3290 let mut globals = Vec::new();
3291 let mut tensors = Vec::new();
3292 let idx3 = |e: &Dsv4Expert| -> Option<(usize, usize, usize)> {
3293 Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
3294 };
3295 let gu_q2 = l
3300 .experts
3301 .first()
3302 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
3303 if let Some(n) = std::env::var("CMF_DSV4_PACK_MAX")
3312 .ok()
3313 .and_then(|v| v.parse::<usize>().ok())
3314 {
3315 let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
3316 let mut globals = Vec::new();
3317 let mut tensors = Vec::new();
3318 for (gi, e) in l.experts.iter().enumerate().take(n) {
3319 to_slot[gi] = globals.len();
3320 globals.push(gi);
3321 tensors.push(idx3(e)?);
3322 }
3323 tensors.push(idx3(&l.shared)?);
3324 let (rows, cols) = (l.gate.rows(), l.gate.cols());
3325 let mut router = vec![0.0f32; rows * cols];
3326 for r in 0..rows {
3327 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
3328 }
3329 let remap: Vec<u32> = to_slot
3330 .iter()
3331 .map(|&sl| if sl == usize::MAX { u32::MAX } else { sl as u32 })
3332 .collect();
3333 return Some(Arc::new(Pack {
3334 bias: l.gate_bias.as_deref().map(|b| {
3335 globals.iter().map(|&g| b[g]).collect()
3336 }),
3337 router,
3338 to_slot,
3339 remap,
3340 globals,
3341 tensors,
3342 }));
3343 }
3344 let dn_q2_fit = l
3345 .experts
3346 .first()
3347 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
3348 let room = crate::gpu_wgpu::dsv4_experts_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2_fit)
3349 .saturating_sub(1);
3350 let order = pack_freq_order(li, l.experts.len())
3359 .unwrap_or_else(|| (0..l.experts.len()).collect());
3360 for gi in order {
3361 let e = &l.experts[gi];
3362 if l.mask.as_deref().is_some_and(|m| !m.get(gi).copied().unwrap_or(true)) {
3363 continue;
3364 }
3365 if globals.len() >= room {
3366 break;
3367 }
3368 to_slot[gi] = globals.len();
3369 globals.push(gi);
3370 match idx3(e) {
3371 Some(t) => tensors.push(t),
3372 None => {
3373 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
3374 eprintln!("слой {li}: эксперт {gi} без индексов в каталоге");
3375 }
3376 return None;
3377 }
3378 }
3379 }
3380 if globals.is_empty() {
3381 if room == 0 {
3387 static SAID_ZERO: std::sync::atomic::AtomicBool =
3388 std::sync::atomic::AtomicBool::new(false);
3389 if !SAID_ZERO.swap(true, std::sync::atomic::Ordering::Relaxed) {
3390 tracing::warn!(
3391 "начиная со слоя {li}, в бюджете VRAM не осталось места даже под одного \
3392 эксперта — остальные веса остаются mmap-backed и читаются по требованию"
3393 );
3394 }
3395 } else {
3396 tracing::warn!("слой {li}: маска не оставила ни одного эксперта");
3397 }
3398 return None;
3399 }
3400 tensors.push(idx3(&l.shared)?); let (rows, cols) = (l.gate.rows(), l.gate.cols());
3402 let mut router = vec![0.0f32; rows * cols];
3403 for r in 0..rows {
3404 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
3405 }
3406 let remap: Vec<u32> = to_slot
3407 .iter()
3408 .map(|&sl| if sl == usize::MAX { u32::MAX } else { sl as u32 })
3409 .collect();
3410 Some(Arc::new(Pack {
3411 bias: l.gate_bias.as_deref().map(|b| {
3412 globals.iter().map(|&g| b[g]).collect()
3413 }),
3414 router,
3415 to_slot,
3416 remap,
3417 globals,
3418 tensors,
3419 }))
3420 };
3421 let v = build();
3422 cache.lock().unwrap().insert(key, v.clone());
3423 v
3424}
3425
3426#[cfg(feature = "gpu")]
3434fn moe_frame(
3435 hidden: &[f32],
3436 l: &Dsv4Layer,
3437 cfg: &Dsv4Cfg,
3438 li: usize,
3439 logits: &[f32],
3440 forced: Option<&[usize]>,
3441 pool: Option<&crate::pool::Pool>,
3442 hc_cur: Option<&crate::gpu_wgpu::Dsv4HcTail>,
3445 hc_next: Option<(&crate::gpu_wgpu::Dsv4HcTail, &[f32])>,
3446 out: &mut [f32],
3447) -> Option<(Vec<f32>, usize)> {
3448 macro_rules! no {
3449 ($($t:tt)*) => {{
3450 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
3451 eprintln!("кадр MoE отклонён: {}", format_args!($($t)*));
3452 }
3453 return None;
3454 }};
3455 }
3456 let Some(pk) = pack_for(l, cfg, li) else {
3457 no!("слой {li}: упаковка экспертов не построена");
3458 };
3459 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
3462 no!("слой {li}: эксперты не отображены из файла");
3463 };
3464 let subset = pk.globals.len() < cfg.n_routed_experts;
3465 let fpack: Option<Vec<usize>> = match forced {
3469 Some(f) if subset => Some(f.to_vec()),
3470 Some(f) => {
3471 let v: Vec<usize> = f.iter().map(|&g| pk.to_slot[g]).collect();
3472 if v.iter().any(|&s| s == usize::MAX) {
3473 no!("слой {li}: хеш-слой называет эксперта вне упаковки");
3474 }
3475 Some(v)
3476 }
3477 None => None,
3478 };
3479 let lg: Vec<f32> = if logits.is_empty() || subset {
3486 logits.to_vec()
3487 } else {
3488 pk.globals.iter().map(|&g| logits[g]).collect()
3489 };
3490 let bias: Option<Vec<f32>> = l.gate_bias.as_deref().map(|b| {
3491 if subset {
3492 b.to_vec()
3493 } else {
3494 pk.globals.iter().map(|&g| b[g]).collect()
3495 }
3496 });
3497 let w = crate::gpu_wgpu::Dsv4MoeW {
3498 router: &pk.router,
3499 experts: &pk.tensors,
3500 logits: &lg,
3501 bias: bias.as_deref(),
3502 forced: fpack.as_deref(),
3503 remap: if subset { Some(&pk.remap) } else { None },
3504 };
3505 let g = crate::gpu_wgpu::Dsv4MoeGeom {
3506 hidden: cfg.dim,
3507 inter: cfg.moe_inter,
3508 top_k: cfg.top_k,
3509 route_scale: cfg.route_scale,
3510 swiglu_limit: cfg.swiglu_limit,
3511 gu_q2: l.experts.first().is_some_and(|e| {
3512 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
3513 }),
3514 };
3515 let mut cold = Vec::new();
3516 let mut cold_x = Vec::new();
3517 if !crate::gpu_wgpu::dsv4_moe_frame(
3518 &model,
3519 &w,
3520 g,
3521 hidden,
3522 &mut cold,
3523 &mut cold_x,
3524 hc_cur,
3525 hc_next,
3526 out,
3527 ) {
3528 return None;
3529 }
3530 if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
3533 let csum: f32 = cold.iter().map(|c| c.1).sum();
3534 eprintln!(
3535 "[холодные] слой {li}: вернулось {} из {} | сумма холодных {csum:.4} | \
3536 route_scale {:.4} | {:?}",
3537 cold.len(),
3538 cfg.top_k,
3539 cfg.route_scale,
3540 &cold[..cold.len().min(3)]
3541 );
3542 }
3543 let mut acc = vec![0.0f32; cfg.dim];
3544 let mut cold_sum = vec![0.0f32; cfg.dim];
3545 let cold_input = if hidden.is_empty() { cold_x.as_slice() } else { hidden };
3546 for &(gi, wt) in &cold {
3547 let Some(exp) = l.experts.get(gi) else { continue };
3548 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
3554 for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
3555 *o += a;
3556 *sum += a;
3557 }
3558 }
3559 Some((cold_sum, cold.len()))
3560}
3561
3562#[cfg(feature = "gpu")]
3568fn last_grew(now: u64) -> u64 {
3569 use std::sync::atomic::{AtomicU64, Ordering};
3570 static SEEN: AtomicU64 = AtomicU64::new(0);
3571 let was = SEEN.load(Ordering::Relaxed);
3572 if was != now {
3573 SEEN.store(now, Ordering::Relaxed);
3574 compressed_map().lock().unwrap().clear();
3575 return u64::MAX; }
3577 now
3578}
3579
3580#[cfg(feature = "gpu")]
3581fn compressed_map() -> &'static std::sync::Mutex<std::collections::HashMap<(u64, usize), usize>> {
3582 use std::collections::HashMap;
3583 use std::sync::{Mutex, OnceLock};
3584 static W: OnceLock<Mutex<HashMap<(u64, usize), usize>>> = OnceLock::new();
3585 W.get_or_init(|| Mutex::new(HashMap::new()))
3586}
3587
3588#[cfg(feature = "gpu")]
3589fn compressed_written(kv_id: u64, li: usize) -> usize {
3590 compressed_map()
3591 .lock()
3592 .unwrap()
3593 .get(&(kv_id, li))
3594 .copied()
3595 .unwrap_or(0)
3596}
3597
3598#[cfg(feature = "gpu")]
3601fn note_compressed(kv_id: u64, li: usize, n: usize) {
3602 compressed_map().lock().unwrap().insert((kv_id, li), n);
3603}
3604
3605#[cfg(feature = "gpu")]
3606fn gpu_moe2_enabled() -> bool {
3607 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3608 *ON.get_or_init(|| {
3609 std::env::var("CMF_DSV4_GPU_MOE2").map(|v| v != "0").unwrap_or(true)
3610 && crate::gpu::backend_available()
3611 })
3612}
3613
3614pub fn moe_step(
3615 hidden: &[f32],
3616 l: &Dsv4Layer,
3617 cfg: &Dsv4Cfg,
3618 token_id: u32,
3619 li: usize,
3621 pool: Option<&crate::pool::Pool>,
3622 out: &mut [f32],
3623) {
3624 let _t0 = prof::on().then(std::time::Instant::now);
3625 let _guard = scopeguard_moe(_t0, li);
3626 let mut logits = vec![0.0f32; cfg.n_routed_experts];
3627 l.gate.matvec(hidden, &mut logits, pool);
3628 let (mut idx, mut w) = (Vec::new(), Vec::new());
3629 route(
3630 &logits,
3631 l.gate_bias.as_deref(),
3632 cfg.top_k,
3633 cfg.route_scale,
3634 l.tid2eid
3635 .as_ref()
3636 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id))
3637 .as_deref(),
3638 l.mask.as_deref(),
3639 &mut idx,
3640 &mut w,
3641 );
3642 if route_stats_on() {
3643 record_route(li, 0, cfg.n_routed_experts, &idx);
3644 }
3645 #[cfg(feature = "gpu")]
3649 if gpu_moe2_enabled() && crate::gpu::enabled_here() {
3650 let forced = l
3651 .tid2eid
3652 .as_ref()
3653 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
3654 if moe_frame(hidden, l, cfg, li, &logits, forced.as_deref(), pool, None, None, out)
3655 .is_some()
3656 {
3657 if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
3663 let mut want = vec![0.0f32; out.len()];
3664 let mut acc = vec![0.0f32; cfg.dim];
3665 for (e, &ei) in idx.iter().enumerate() {
3666 let Some(exp) = l.experts.get(ei) else { continue };
3667 run_expert(hidden, exp, cfg, w.get(e).copied().unwrap_or(0.0), pool, &mut acc);
3668 for (o, a) in want.iter_mut().zip(&acc) {
3669 *o += a;
3670 }
3671 }
3672 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
3673 for (o, a) in want.iter_mut().zip(&acc) {
3674 *o += a;
3675 }
3676 let num: f32 = want.iter().zip(out.iter()).map(|(a, b)| (a - b) * (a - b)).sum();
3677 let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
3678 let rel = (num / den).sqrt();
3679 if rel > 1e-3 {
3680 let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
3681 eprintln!(
3682 "[кадр MoE] слой {li}: расхождение {rel:.3e} | выбрано {} | \
3683 упаковано {packed} из {} | хеш={} | смещение={}",
3684 idx.len(),
3685 cfg.n_routed_experts,
3686 l.tid2eid.is_some(),
3687 l.gate_bias.is_some()
3688 );
3689 }
3690 }
3691 return;
3692 }
3693 }
3694 PICK_TALLY.with(|t| {
3700 if let Some(v) = t.borrow_mut().as_mut() {
3701 v.push((li, idx.to_vec()));
3702 }
3703 });
3704 if dump_path().is_some() {
3705 PICKED.with(|p| {
3706 let mut p = p.borrow_mut();
3707 if p.len() <= li {
3708 p.resize(li + 1, Vec::new());
3709 }
3710 p[li] = idx.clone();
3711 });
3712 }
3713 fn gpu_moe_on() -> bool {
3732 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3733 *ON.get_or_init(|| std::env::var("CMF_DSV4_GPU_MOE").is_ok_and(|v| v != "0"))
3734 }
3735 if gpu_moe_on() && crate::gpu::enabled_here() {
3736 let mut jobs = Vec::with_capacity(idx.len() + 1);
3737 let mut model_ref = None;
3738 let mut ok = true;
3739 for (e, &ei) in idx.iter().enumerate() {
3740 let Some(exp) = l.experts.get(ei) else { continue };
3741 ok &= crate::pipeline::moe_push_job_parts(
3742 &exp.w1,
3743 &exp.w3,
3744 &exp.w2,
3745 hidden,
3746 w.get(e).copied().unwrap_or(0.0),
3747 cfg.swiglu_limit,
3748 &mut jobs,
3749 &mut model_ref,
3750 )
3751 .is_some();
3752 }
3753 ok &= crate::pipeline::moe_push_job_parts(
3754 &l.shared.w1,
3755 &l.shared.w3,
3756 &l.shared.w2,
3757 hidden,
3758 1.0,
3759 cfg.swiglu_limit,
3760 &mut jobs,
3761 &mut model_ref,
3762 )
3763 .is_some();
3764 if ok {
3765 if let Some(m) = model_ref.as_ref() {
3766 if crate::gpu::moe_block(m, &jobs, out) {
3767 if std::env::var("CMF_DSV4_GPU_CHECK").is_ok() {
3772 let mut want = vec![0.0f32; out.len()];
3773 let mut acc = vec![0.0f32; cfg.dim];
3774 for (e, &ei) in idx.iter().enumerate() {
3775 let Some(exp) = l.experts.get(ei) else { continue };
3776 run_expert(
3777 hidden, exp, cfg,
3778 w.get(e).copied().unwrap_or(0.0), pool, &mut acc,
3779 );
3780 for (o, a) in want.iter_mut().zip(&acc) {
3781 *o += a;
3782 }
3783 }
3784 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
3785 for (o, a) in want.iter_mut().zip(&acc) {
3786 *o += a;
3787 }
3788 let num: f32 = want
3789 .iter()
3790 .zip(out.iter())
3791 .map(|(a, b)| (a - b) * (a - b))
3792 .sum();
3793 let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
3794 eprintln!(
3795 "[dsv4-gpu] слой {li}: расхождение {:.3e} | |CPU|={:.5} |GPU|={:.5} | экспертов {}",
3796 (num / den).sqrt(),
3797 den.sqrt(),
3798 out.iter().map(|x| x * x).sum::<f32>().sqrt(),
3799 jobs.len()
3800 );
3801 }
3802 return;
3803 }
3804 }
3805 }
3806 }
3807 out.fill(0.0);
3808 let mut acc = vec![0.0f32; cfg.dim];
3809 for (e, &ei) in idx.iter().enumerate() {
3810 let Some(exp) = l.experts.get(ei) else {
3811 continue;
3812 };
3813 run_expert(
3814 hidden,
3815 exp,
3816 cfg,
3817 w.get(e).copied().unwrap_or(0.0),
3818 pool,
3819 &mut acc,
3820 );
3821 for (o, a) in out.iter_mut().zip(&acc) {
3822 *o += a;
3823 }
3824 }
3825 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
3827 for (o, a) in out.iter_mut().zip(&acc) {
3828 *o += a;
3829 }
3830}
3831
3832fn run_expert(
3835 x: &[f32],
3836 e: &Dsv4Expert,
3837 cfg: &Dsv4Cfg,
3838 weight: f32,
3839 pool: Option<&crate::pool::Pool>,
3840 out: &mut [f32],
3841) {
3842 expert_swiglu(
3843 x,
3844 &|src, dst| e.w1.matvec(src, dst, pool),
3845 &|src, dst| e.w3.matvec(src, dst, pool),
3846 &|src, dst| e.w2.matvec(src, dst, pool),
3847 cfg.moe_inter,
3848 weight,
3849 cfg.swiglu_limit,
3850 out,
3851 );
3852}
3853
3854fn moe_step_block(
3859 xs: &[f32],
3860 b: usize,
3861 l: &Dsv4Layer,
3862 cfg: &Dsv4Cfg,
3863 token_ids: &[u32],
3864 tally_layer: usize,
3865 pool: Option<&crate::pool::Pool>,
3866 out: &mut [f32],
3867) {
3868 let (dim, inter) = (cfg.dim, cfg.moe_inter);
3869 debug_assert_eq!(xs.len(), b * dim);
3870 debug_assert_eq!(out.len(), b * dim);
3871 out.fill(0.0);
3872
3873 let mut logits = vec![0.0f32; b * cfg.n_routed_experts];
3874 l.gate.matmat(xs, b, &mut logits, pool);
3875 let mut picks: Vec<Vec<usize>> = Vec::with_capacity(b);
3876 let mut weights: Vec<Vec<f32>> = Vec::with_capacity(b);
3877 for bi in 0..b {
3878 let mut idx = Vec::new();
3879 let mut wt = Vec::new();
3880 let forced = l.tid2eid.as_ref().map(|tbl| {
3881 hash_route(
3882 tbl,
3883 cfg.vocab,
3884 cfg.top_k,
3885 token_ids.get(bi).copied().unwrap_or(0),
3886 )
3887 });
3888 route(
3889 &logits[bi * cfg.n_routed_experts..(bi + 1) * cfg.n_routed_experts],
3890 l.gate_bias.as_deref(),
3891 cfg.top_k,
3892 cfg.route_scale,
3893 forced.as_deref(),
3894 l.mask.as_deref(),
3895 &mut idx,
3896 &mut wt,
3897 );
3898 PICK_TALLY.with(|t| {
3899 if let Some(v) = t.borrow_mut().as_mut() {
3900 v.push((tally_layer, idx.clone()));
3901 }
3902 });
3903 picks.push(idx);
3904 weights.push(wt);
3905 }
3906
3907 let mut routed = vec![0.0f32; b * cfg.top_k * dim];
3910 for ei in 0..l.experts.len() {
3911 let mut jobs = Vec::new();
3912 for bi in 0..b {
3913 for (slot, &picked) in picks[bi].iter().enumerate() {
3914 if picked == ei {
3915 jobs.push((bi, slot, weights[bi][slot]));
3916 }
3917 }
3918 }
3919 if jobs.is_empty() {
3920 continue;
3921 }
3922 let e = &l.experts[ei];
3923 let n = jobs.len();
3924 let mut xj = vec![0.0f32; n * dim];
3925 for (j, &(bi, _, _)) in jobs.iter().enumerate() {
3926 xj[j * dim..(j + 1) * dim]
3927 .copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
3928 }
3929 let mut gate = vec![0.0f32; n * inter];
3930 let mut up = vec![0.0f32; n * inter];
3931 e.w1.matmat(&xj, n, &mut gate, pool);
3932 e.w3.matmat(&xj, n, &mut up, pool);
3933 for (j, &(_, _, wt)) in jobs.iter().enumerate() {
3934 let (gj, uj) = (
3935 &mut gate[j * inter..(j + 1) * inter],
3936 &mut up[j * inter..(j + 1) * inter],
3937 );
3938 if cfg.swiglu_limit > 0.0 {
3939 for u in uj.iter_mut() {
3940 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
3941 }
3942 for g in gj.iter_mut() {
3943 *g = g.min(cfg.swiglu_limit);
3944 }
3945 }
3946 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
3947 *g = (*g / (1.0 + (-*g).exp())) * u * wt;
3948 }
3949 }
3950 let mut down = vec![0.0f32; n * dim];
3951 e.w2.matmat(&gate, n, &mut down, pool);
3952 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
3953 routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
3954 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
3955 }
3956 }
3957
3958 let mut sg = vec![0.0f32; b * inter];
3961 let mut su = vec![0.0f32; b * inter];
3962 l.shared.w1.matmat(xs, b, &mut sg, pool);
3963 l.shared.w3.matmat(xs, b, &mut su, pool);
3964 for bi in 0..b {
3965 let (gj, uj) = (
3966 &mut sg[bi * inter..(bi + 1) * inter],
3967 &mut su[bi * inter..(bi + 1) * inter],
3968 );
3969 if cfg.swiglu_limit > 0.0 {
3970 for u in uj.iter_mut() {
3971 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
3972 }
3973 for g in gj.iter_mut() {
3974 *g = g.min(cfg.swiglu_limit);
3975 }
3976 }
3977 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
3978 *g = (*g / (1.0 + (-*g).exp())) * u;
3979 }
3980 }
3981 let mut shared = vec![0.0f32; b * dim];
3982 l.shared.w2.matmat(&sg, b, &mut shared, pool);
3983
3984 for bi in 0..b {
3985 let dst = &mut out[bi * dim..(bi + 1) * dim];
3986 for slot in 0..picks[bi].len() {
3987 let src = &routed[(bi * cfg.top_k + slot) * dim
3988 ..(bi * cfg.top_k + slot + 1) * dim];
3989 for (o, &v) in dst.iter_mut().zip(src) {
3990 *o += v;
3991 }
3992 }
3993 for (o, &v) in dst.iter_mut().zip(&shared[bi * dim..(bi + 1) * dim]) {
3994 *o += v;
3995 }
3996 }
3997}
3998
3999fn o_project_block(
4004 attn: &[f32],
4005 b: usize,
4006 wo_a: &crate::qtensor::QTensor,
4007 wo_b: &crate::qtensor::QTensor,
4008 groups: usize,
4009 lora: usize,
4010 pool: Option<&crate::pool::Pool>,
4011 out: &mut [f32],
4012) {
4013 let attn_len = attn.len() / b;
4014 let per_group = attn_len / groups;
4015 let rows = groups * lora;
4016 let mut mid = vec![0.0f32; b * rows];
4017 let mid_addr = crate::pool::SendMut::new(mid.as_mut_ptr());
4018 let run = |start: usize, end: usize| {
4019 let mut wr = vec![0.0f32; wo_a.cols()];
4020 for r in start..end {
4021 wo_a.row_f32(r, &mut wr);
4022 let group = r / lora;
4023 for bi in 0..b {
4024 let x = &attn[bi * attn_len + group * per_group
4025 ..bi * attn_len + (group + 1) * per_group];
4026 let v = wr.iter().zip(x).map(|(w, x)| w * x).sum();
4027 unsafe { *mid_addr.at(bi * rows + r) = v };
4028 }
4029 }
4030 };
4031 match pool {
4032 Some(p) if rows >= 256 => p.run_rows(rows, &run),
4033 _ => run(0, rows),
4034 }
4035 wo_b.matmat(&mid, b, out, pool);
4036}
4037
4038fn no_compressed() -> bool {
4044 static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4045 *OFF.get_or_init(|| std::env::var("CMF_DSV4_NO_COMPRESSED").is_ok_and(|v| v != "0"))
4046}
4047
4048fn trace_on() -> bool {
4049 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4050 *ON.get_or_init(|| std::env::var("CMF_DSV4_TRACE").is_ok_and(|v| v != "0"))
4051}
4052
4053fn rms_of(v: &[f32]) -> f32 {
4054 if v.is_empty() {
4055 return 0.0;
4056 }
4057 (v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32).sqrt()
4058}
4059
4060thread_local! {
4066 static BODY: std::cell::RefCell<Vec<String>> = const { std::cell::RefCell::new(Vec::new()) };
4068 static PICKED: std::cell::RefCell<Vec<Vec<usize>>> =
4072 const { std::cell::RefCell::new(Vec::new()) };
4073 static PICK_TALLY: std::cell::RefCell<Option<Vec<(usize, Vec<usize>)>>> =
4075 const { std::cell::RefCell::new(None) };
4076}
4077
4078pub fn pick_tally_arm() {
4080 PICK_TALLY.with(|t| *t.borrow_mut() = Some(Vec::new()));
4081}
4082
4083pub fn pick_tally_take() -> Vec<(usize, Vec<usize>)> {
4085 PICK_TALLY.with(|t| t.borrow_mut().take().unwrap_or_default())
4086}
4087
4088pub fn tally_unique(picks: &[(usize, Vec<usize>)]) -> (usize, usize) {
4091 let mut seen = std::collections::HashSet::new();
4096 let mut total = 0;
4097 for (li, v) in picks {
4098 total += v.len();
4099 for &e in v {
4100 seen.insert((*li, e));
4101 }
4102 }
4103 (seen.len(), total)
4104}
4105
4106fn dump_path() -> Option<&'static str> {
4107 static P: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
4108 P.get_or_init(|| std::env::var("CMF_DSV4_DUMP").ok())
4109 .as_deref()
4110}
4111
4112fn dump_line(json: &str) {
4113 if let Some(p) = dump_path() {
4114 use std::io::Write as _;
4115 if let Ok(mut f) = std::fs::OpenOptions::new()
4116 .create(true)
4117 .append(true)
4118 .open(p)
4119 {
4120 let _ = writeln!(f, "{json}");
4121 }
4122 }
4123}
4124
4125fn vec_json(v: &[f32]) -> String {
4126 let mut s = String::with_capacity(v.len() * 9);
4127 s.push('[');
4128 for (i, x) in v.iter().enumerate() {
4129 if i > 0 {
4130 s.push(',');
4131 }
4132 s.push_str(&format!("{x:.6e}"));
4133 }
4134 s.push(']');
4135 s
4136}
4137
4138#[allow(clippy::too_many_arguments)]
4146#[allow(clippy::too_many_arguments)]
4156fn batch_prefill() -> usize {
4160 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
4161 *N.get_or_init(|| {
4162 std::env::var("CMF_DSV4_BATCH")
4163 .ok()
4164 .and_then(|v| v.parse::<usize>().ok())
4165 .filter(|&n| (1..=32).contains(&n))
4166 .unwrap_or(1)
4167 })
4168}
4169
4170#[allow(clippy::too_many_arguments)]
4183fn forward_chunk_batched(
4184 g: &Dsv4Globals,
4185 layers: &[Dsv4Layer],
4186 cfg: &Dsv4Cfg,
4187 st: &mut Dsv4State,
4188 ids: &[u32],
4189 pos0: usize,
4190 inv_freq: &[f32],
4191 pool: Option<&crate::pool::Pool>,
4192 logits: &mut Vec<f32>,
4193 want_logits: bool,
4194) -> bool {
4195 #[cfg(not(feature = "gpu"))]
4196 {
4197 let _ = (
4198 g, layers, cfg, st, ids, pos0, inv_freq, pool, logits, want_logits,
4199 );
4200 false
4201 }
4202 #[cfg(feature = "gpu")]
4203 {
4204 let b = ids.len();
4205 let gpu_end = st
4210 .dev_set
4211 .iter()
4212 .enumerate()
4213 .position(|(li, &on)| {
4214 !on
4215 || pack_for(&layers[li], cfg, li)
4216 .is_none_or(|p| p.globals.len() < cfg.n_routed_experts)
4217 })
4218 .unwrap_or(st.dev_set.len());
4219 let why = if b < 2 {
4220 "токенов меньше двух"
4221 } else if !chain_enabled() {
4222 "цепочка выключена"
4223 } else if !st.dev_owned {
4224 "карта ещё не владеет состоянием"
4225 } else if st.dev_set.len() != layers.len() {
4226 "набор слоёв ещё не зафиксирован"
4227 } else if gpu_end == 0
4228 || st.dev_set[gpu_end.min(st.dev_set.len())..]
4229 .iter()
4230 .enumerate()
4231 .any(|(i, &on)| {
4232 on && !st.partial_set.get(gpu_end + i).copied().unwrap_or(false)
4233 })
4234 {
4235 "слои на карте не образуют префикс"
4236 } else {
4237 ""
4238 };
4239 if !why.is_empty() {
4240 static SAID: std::sync::Once = std::sync::Once::new();
4241 SAID.call_once(|| tracing::warn!("dsv4: пакет отказал — {why}"));
4242 return false;
4243 }
4244 let (hc, dim) = (cfg.hc_mult, cfg.dim);
4245 let mut emb = vec![0.0f32; dim];
4246 for (t, &id) in ids.iter().enumerate() {
4247 let mut state = vec![0.0f32; hc * dim];
4248 g.embed.row_f32(id as usize, &mut emb);
4249 for j in 0..hc {
4250 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
4251 }
4252 let (folded, post0, comb0) = hc_fold_norm(
4253 &state,
4254 &layers[0].hc_attn_fn,
4255 &layers[0].hc_attn_scale,
4256 &layers[0].hc_attn_base,
4257 &layers[0].attn_norm,
4258 cfg,
4259 pool,
4260 );
4261 let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
4262 layers[0].wq_a.matvec(&folded, &mut qn0, pool);
4263 rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
4264 if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
4265 || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
4266 || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
4267 || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
4268 {
4269 return false;
4270 }
4271 }
4272 let run: Vec<usize> = (0..gpu_end).collect();
4273 let mut folded = Vec::new();
4274 let mut states = vec![0.0f32; b * hc * dim];
4275 st.pos = pos0;
4276 if !dsv4_chain_run(
4277 layers,
4278 &run,
4279 cfg,
4280 g,
4281 st,
4282 *ids.last().unwrap(),
4283 &mut folded,
4284 Some(&mut states),
4285 b,
4286 ids,
4287 true,
4288 pool,
4289 ) {
4290 return false;
4291 }
4292 let mut scratch = HcScratch::new(cfg);
4298 for t in 0..b {
4299 st.pos = pos0 + t;
4300 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
4301 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
4302 let freqs = if l.compressor.is_some() {
4303 &g.inv_freq_compress
4304 } else {
4305 &g.inv_freq_window
4306 };
4307 let freqs = if freqs.is_empty() {
4308 inv_freq
4309 } else {
4310 freqs.as_slice()
4311 };
4312 hc_block(
4313 state,
4314 &l.hc_attn_fn,
4315 &l.hc_attn_scale,
4316 &l.hc_attn_base,
4317 &l.attn_norm,
4318 cfg,
4319 &mut scratch,
4320 pool,
4321 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
4322 );
4323 hc_block(
4324 state,
4325 &l.hc_ffn_fn,
4326 &l.hc_ffn_scale,
4327 &l.hc_ffn_base,
4328 &l.ffn_norm,
4329 cfg,
4330 &mut scratch,
4331 pool,
4332 |f, o| {
4333 if host_cpu_moe() {
4334 crate::gpu::cpu_scope(|| moe_step(f, l, cfg, ids[t], li, pool, o))
4335 } else {
4336 moe_step(f, l, cfg, ids[t], li, pool, o)
4337 }
4338 },
4339 );
4340 dspark_note(li, state, cfg);
4341 }
4342 }
4343 st.pos = pos0 + b;
4344 {
4349 static SAID: std::sync::Once = std::sync::Once::new();
4350 SAID.call_once(|| tracing::warn!("dsv4: префилл пакетами по {b}"));
4351 }
4352 if want_logits {
4357 let last = &states[(b - 1) * hc * dim..b * hc * dim];
4358 let mut h = vec![0.0f32; dim];
4359 hc_head_fold(
4360 last,
4361 &g.hc_head_fn,
4362 g.hc_head_scale,
4363 &g.hc_head_base,
4364 cfg,
4365 pool,
4366 &mut h,
4367 );
4368 rms_weighted(&mut h, &g.norm, cfg.norm_eps);
4369 logits.resize(cfg.vocab, 0.0);
4370 g.head.matvec(&h, logits, pool);
4371 } else {
4372 logits.clear();
4373 }
4374 true
4375 }
4376}
4377
4378#[cfg(feature = "gpu")]
4386pub struct Dsv4SpecTxn {
4387 pos0: usize,
4388 batch: usize,
4389 gpu_end: usize,
4390 dev_filled: Vec<usize>,
4391 dev_n_comp: Vec<usize>,
4392 dev_n_ix: Vec<usize>,
4393 host: Vec<(usize, HostLayerSnap)>,
4394 host_steps: Vec<(usize, Vec<HostLayerSnap>)>,
4399 pub states: Vec<f32>,
4403 shadow: Option<crate::gpu_wgpu::Dsv4SpecShadow>,
4404}
4405
4406#[cfg(feature = "gpu")]
4407struct HostLayerSnap {
4408 window: Vec<f32>,
4409 compressed: Vec<f32>,
4410 index_kv: Vec<f32>,
4411 pending_kv: Vec<f32>,
4412 pending_score: Vec<f32>,
4413 prev_kv: Vec<f32>,
4414 prev_score: Vec<f32>,
4415 pending_ix_kv: Vec<f32>,
4416 pending_ix_score: Vec<f32>,
4417 prev_ix_kv: Vec<f32>,
4418 prev_ix_score: Vec<f32>,
4419}
4420
4421#[cfg(feature = "gpu")]
4422fn host_snap(st: &Dsv4State, li: usize) -> HostLayerSnap {
4423 HostLayerSnap {
4424 window: st.window[li].clone(),
4425 compressed: st.compressed[li].clone(),
4426 index_kv: st.index_kv[li].clone(),
4427 pending_kv: st.pending_kv[li].clone(),
4428 pending_score: st.pending_score[li].clone(),
4429 prev_kv: st.prev_kv[li].clone(),
4430 prev_score: st.prev_score[li].clone(),
4431 pending_ix_kv: st.pending_ix_kv[li].clone(),
4432 pending_ix_score: st.pending_ix_score[li].clone(),
4433 prev_ix_kv: st.prev_ix_kv[li].clone(),
4434 prev_ix_score: st.prev_ix_score[li].clone(),
4435 }
4436}
4437
4438#[cfg(feature = "gpu")]
4439fn host_restore(st: &mut Dsv4State, li: usize, s: &HostLayerSnap) {
4440 st.window[li] = s.window.clone();
4441 st.compressed[li] = s.compressed.clone();
4442 st.index_kv[li] = s.index_kv.clone();
4443 st.pending_kv[li] = s.pending_kv.clone();
4444 st.pending_score[li] = s.pending_score.clone();
4445 st.prev_kv[li] = s.prev_kv.clone();
4446 st.prev_score[li] = s.prev_score.clone();
4447 st.pending_ix_kv[li] = s.pending_ix_kv.clone();
4448 st.pending_ix_score[li] = s.pending_ix_score.clone();
4449 st.prev_ix_kv[li] = s.prev_ix_kv.clone();
4450 st.prev_ix_score[li] = s.prev_ix_score.clone();
4451}
4452
4453#[cfg(feature = "gpu")]
4457#[allow(clippy::too_many_arguments)]
4458fn host_tail_walk(
4459 g: &Dsv4Globals,
4460 layers: &[Dsv4Layer],
4461 cfg: &Dsv4Cfg,
4462 st: &mut Dsv4State,
4463 gpu_end: usize,
4464 state: &mut [f32],
4465 token_id: u32,
4466 pos: usize,
4467 inv_freq: &[f32],
4468 scratch: &mut HcScratch,
4469 pool: Option<&crate::pool::Pool>,
4470) {
4471 st.pos = pos;
4472 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
4473 let freqs = if l.compressor.is_some() {
4474 &g.inv_freq_compress
4475 } else {
4476 &g.inv_freq_window
4477 };
4478 let freqs = if freqs.is_empty() { inv_freq } else { freqs.as_slice() };
4479 hc_block(
4480 state,
4481 &l.hc_attn_fn,
4482 &l.hc_attn_scale,
4483 &l.hc_attn_base,
4484 &l.attn_norm,
4485 cfg,
4486 scratch,
4487 pool,
4488 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
4489 );
4490 hc_block(
4491 state,
4492 &l.hc_ffn_fn,
4493 &l.hc_ffn_scale,
4494 &l.hc_ffn_base,
4495 &l.ffn_norm,
4496 cfg,
4497 scratch,
4498 pool,
4499 |f, o| {
4500 if host_cpu_moe() {
4501 crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
4502 } else {
4503 moe_step(f, l, cfg, token_id, li, pool, o)
4504 }
4505 },
4506 );
4507 dspark_note(li, state, cfg);
4508 }
4509}
4510
4511#[cfg(feature = "gpu")]
4517#[allow(clippy::too_many_arguments)]
4518fn host_tail_walk_batch(
4519 g: &Dsv4Globals,
4520 layers: &[Dsv4Layer],
4521 cfg: &Dsv4Cfg,
4522 st: &mut Dsv4State,
4523 gpu_end: usize,
4524 states: &mut [f32],
4525 ids: &[u32],
4526 pos0: usize,
4527 b: usize,
4528 inv_freq: &[f32],
4529 scratch: &mut HcScratch,
4530 pool: Option<&crate::pool::Pool>,
4531 mut steps: Option<&mut Vec<(usize, Vec<HostLayerSnap>)>>,
4532) {
4533 let (hc, dim) = (cfg.hc_mult, cfg.dim);
4534 let mix_hc = (2 + hc) * hc;
4535 let mut folds = vec![0.0f32; b * dim];
4536 let mut mo = vec![0.0f32; b * dim];
4537 let mut posts = vec![0.0f32; b * hc];
4538 let mut combs = vec![0.0f32; b * hc * hc];
4539 let mut resid = vec![0.0f32; b * hc * dim];
4540 let spec_time = {
4541 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4542 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
4543 };
4544 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
4545 let t_attn = std::time::Instant::now();
4546 let freqs = if l.compressor.is_some() {
4547 &g.inv_freq_compress
4548 } else {
4549 &g.inv_freq_window
4550 };
4551 let freqs = if freqs.is_empty() { inv_freq } else { freqs.as_slice() };
4552 for t in 0..b {
4553 st.pos = pos0 + t;
4554 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
4555 hc_block(
4556 state,
4557 &l.hc_attn_fn,
4558 &l.hc_attn_scale,
4559 &l.hc_attn_base,
4560 &l.attn_norm,
4561 cfg,
4562 scratch,
4563 pool,
4564 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
4565 );
4566 if let Some(steps) = steps.as_mut() {
4567 match steps.iter_mut().find(|(l, _)| *l == li) {
4568 Some((_, v)) => v.push(host_snap(st, li)),
4569 None => steps.push((li, vec![host_snap(st, li)])),
4570 }
4571 }
4572 }
4573 let t_glue = std::time::Instant::now();
4574 for t in 0..b {
4575 let state = &states[t * hc * dim..(t + 1) * hc * dim];
4576 hc_mixes(state, &l.hc_ffn_fn, mix_hc, cfg.norm_eps, pool, &mut scratch.mixes);
4577 hc_split_sinkhorn(
4578 &scratch.mixes,
4579 &l.hc_ffn_scale,
4580 &l.hc_ffn_base,
4581 hc,
4582 cfg.hc_sinkhorn_iters,
4583 cfg.hc_eps,
4584 &mut scratch.pre,
4585 &mut posts[t * hc..(t + 1) * hc],
4586 &mut combs[t * hc * hc..(t + 1) * hc * hc],
4587 );
4588 let fold = &mut folds[t * dim..(t + 1) * dim];
4589 hc_fold(state, &scratch.pre, hc, dim, fold);
4590 let ms = fold.iter().map(|v| v * v).sum::<f32>() / dim as f32;
4591 let inv = 1.0 / (ms + cfg.norm_eps).sqrt();
4592 for (v, w) in fold.iter_mut().zip(&l.ffn_norm) {
4593 *v = *v * inv * w;
4594 }
4595 resid[t * hc * dim..(t + 1) * hc * dim]
4596 .copy_from_slice(&states[t * hc * dim..(t + 1) * hc * dim]);
4597 }
4598 let t_moe = std::time::Instant::now();
4599 let tail_pack = {
4605 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4606 *ON.get_or_init(|| {
4607 std::env::var("CMF_DSV4_TAIL_PACK").map(|v| v != "0").unwrap_or(true)
4608 })
4609 };
4610 let mut packed_done = false;
4611 if tail_pack && pack_for(l, cfg, li).is_some() {
4612 packed_done = true;
4613 for t in 0..b {
4614 let f = &folds[t * dim..(t + 1) * dim];
4615 let forced = l.tid2eid.as_ref().map(|tbl| {
4616 hash_route(tbl, cfg.vocab, cfg.top_k, ids.get(t).copied().unwrap_or(0))
4617 });
4618 let o = &mut mo[t * dim..(t + 1) * dim];
4619 match moe_frame(f, l, cfg, li, &[], forced.as_deref(), pool, None, None, o) {
4620 Some((cold_sum, n)) => {
4621 if n > 0 {
4622 for (od, cd) in o.iter_mut().zip(cold_sum.iter()) {
4623 *od += cd;
4624 }
4625 }
4626 }
4627 None => {
4628 packed_done = false;
4629 break;
4630 }
4631 }
4632 }
4633 }
4634 if !packed_done {
4635 if host_cpu_moe() {
4636 crate::gpu::cpu_scope(|| {
4637 moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo)
4638 });
4639 } else {
4640 moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo);
4641 }
4642 }
4643 let t_exp = std::time::Instant::now();
4644 for t in 0..b {
4645 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
4646 hc_expand(
4647 &mo[t * dim..(t + 1) * dim],
4648 &resid[t * hc * dim..(t + 1) * hc * dim],
4649 &posts[t * hc..(t + 1) * hc],
4650 &combs[t * hc * hc..(t + 1) * hc * hc],
4651 hc,
4652 dim,
4653 state,
4654 );
4655 dspark_note(li, state, cfg);
4656 }
4657 if spec_time {
4658 eprintln!(
4659 "хвост слоя {li}: attn {:.1} мс, клей {:.1}, moe {:.1}, expand {:.1}",
4660 (t_glue - t_attn).as_secs_f64() * 1e3,
4661 (t_moe - t_glue).as_secs_f64() * 1e3,
4662 (t_exp - t_moe).as_secs_f64() * 1e3,
4663 t_exp.elapsed().as_secs_f64() * 1e3,
4664 );
4665 }
4666 }
4667}
4668
4669#[cfg(feature = "gpu")]
4678#[allow(clippy::too_many_arguments)]
4679pub fn dsv4_verify_chunk(
4680 g: &Dsv4Globals,
4681 layers: &[Dsv4Layer],
4682 cfg: &Dsv4Cfg,
4683 st: &mut Dsv4State,
4684 ids: &[u32],
4685 pos0: usize,
4686 inv_freq: &[f32],
4687 pool: Option<&crate::pool::Pool>,
4688 cap_targets: &[usize],
4689 argmax_out: &mut Vec<u32>,
4690 logits_out: &mut Vec<f32>,
4691 walked_out: &mut Vec<f32>,
4692) -> Option<Dsv4SpecTxn> {
4693 let b = ids.len();
4694 let gpu_end = st
4695 .dev_set
4696 .iter()
4697 .enumerate()
4698 .position(|(li, &on)| {
4699 !on || pack_for(&layers[li], cfg, li)
4700 .is_none_or(|p| p.globals.len() < cfg.n_routed_experts)
4701 })
4702 .unwrap_or(st.dev_set.len());
4703 let full_beyond = st.dev_set[gpu_end.min(st.dev_set.len())..]
4707 .iter()
4708 .enumerate()
4709 .any(|(i, &on)| {
4710 on && !st.partial_set.get(gpu_end + i).copied().unwrap_or(false)
4711 });
4712 if b < 2
4713 || !chain_enabled()
4714 || !st.dev_owned
4715 || st.dev_set.len() != layers.len()
4716 || gpu_end == 0
4717 || full_beyond
4718 {
4719 return None;
4720 }
4721 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
4722 let metas: Vec<(usize, usize, usize, usize)> = (0..gpu_end)
4724 .map(|li| (li, hd, cfg.window, st.dev_filled[li]))
4725 .collect();
4726 let shadow = crate::gpu_wgpu::dsv4_spec_shadow(st.kv_id, &metas, b)?;
4727 let mut txn = Dsv4SpecTxn {
4728 pos0,
4729 batch: b,
4730 gpu_end,
4731 dev_filled: st.dev_filled.clone(),
4732 dev_n_comp: st.dev_n_comp.clone(),
4733 dev_n_ix: st.dev_n_ix.clone(),
4734 host: (gpu_end..layers.len())
4735 .map(|li| (li, host_snap(st, li)))
4736 .collect(),
4737 states: Vec::new(),
4738 host_steps: Vec::new(),
4739 shadow: Some(shadow),
4740 };
4741 let dev_caps: Vec<usize> = cap_targets.iter().copied().filter(|&t| t < gpu_end).collect();
4743 crate::gpu_wgpu::dsv4_spec_retain_arm(gpu_end, &dev_caps);
4744
4745 let mut emb = vec![0.0f32; dim];
4747 for (t, &id) in ids.iter().enumerate() {
4748 let mut state = vec![0.0f32; hc * dim];
4749 g.embed.row_f32(id as usize, &mut emb);
4750 for j in 0..hc {
4751 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
4752 }
4753 let (folded, post0, comb0) = hc_fold_norm(
4754 &state,
4755 &layers[0].hc_attn_fn,
4756 &layers[0].hc_attn_scale,
4757 &layers[0].hc_attn_base,
4758 &layers[0].attn_norm,
4759 cfg,
4760 pool,
4761 );
4762 let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
4763 layers[0].wq_a.matvec(&folded, &mut qn0, pool);
4764 rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
4765 if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
4766 || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
4767 || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
4768 || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
4769 {
4770 crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
4771 return None;
4772 }
4773 }
4774 let spec_time = {
4775 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4776 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
4777 };
4778 let t0 = std::time::Instant::now();
4779 let run: Vec<usize> = (0..gpu_end).collect();
4780 let mut folded = Vec::new();
4781 let mut states = vec![0.0f32; b * hc * dim];
4782 st.pos = pos0;
4783 let ok = dsv4_chain_run(
4784 layers,
4785 &run,
4786 cfg,
4787 g,
4788 st,
4789 *ids.last().unwrap(),
4790 &mut folded,
4791 Some(&mut states),
4792 b,
4793 ids,
4794 true,
4795 pool,
4796 );
4797 crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
4798 if !ok {
4799 if let Some(sh) = txn.shadow.take() {
4802 let _ = crate::gpu_wgpu::dsv4_spec_restore(&sh);
4803 }
4804 st.dev_filled = txn.dev_filled;
4805 st.dev_n_comp = txn.dev_n_comp;
4806 st.dev_n_ix = txn.dev_n_ix;
4807 st.pos = pos0;
4808 return None;
4809 }
4810 txn.states = states.clone();
4811 let t_chain = t0.elapsed();
4812 if std::env::var("CMF_DSV4_FOLD_DBG").is_ok() {
4813 for li in 0..gpu_end {
4817 let Some(ixr) = &layers[li].indexer else { continue };
4818 let ratio = ixr.compressor.ratio;
4819 for t in 0..b {
4820 if (pos0 + t + 1) % ratio == 0 {
4821 let ew = {
4822 let w = ixr.compressor.wkv.rows();
4823 if ixr.compressor.overlap { w / 2 } else { w }
4824 };
4825 let idx_new = txn.dev_n_ix[li] + (0..=t)
4826 .filter(|k| (pos0 + k + 1) % ratio == 0)
4827 .count()
4828 - 1;
4829 if let Some(v) =
4830 crate::gpu_wgpu::dsv4_dbg_read_ix(st.kv_id, li, idx_new * ew, ew.min(8))
4831 {
4832 let sum: f32 = v.iter().sum();
4833 eprintln!(
4834 "[fold] li={li} pos={} entry={idx_new} head={:?} sum={sum:.6}",
4835 pos0 + t,
4836 &v[..4.min(v.len())]
4837 );
4838 }
4839 }
4840 }
4841 }
4842 }
4843
4844 let mut scratch = HcScratch::new(cfg);
4846 argmax_out.clear();
4847 logits_out.clear();
4848 logits_out.resize(b * cfg.vocab, 0.0);
4849 let mut head_in = vec![0.0f32; b * dim];
4850 let mut host_steps: Vec<(usize, Vec<HostLayerSnap>)> = Vec::new();
4851 host_tail_walk_batch(
4852 g, layers, cfg, st, gpu_end, &mut states, ids, pos0, b, inv_freq, &mut scratch, pool,
4853 Some(&mut host_steps),
4854 );
4855 txn.host_steps = host_steps;
4856 for t in 0..b {
4857 let state = &states[t * hc * dim..(t + 1) * hc * dim];
4858 let h = &mut head_in[t * dim..(t + 1) * dim];
4859 hc_head_fold(state, &g.hc_head_fn, g.hc_head_scale, &g.hc_head_base, cfg, pool, h);
4860 rms_weighted(h, &g.norm, cfg.norm_eps);
4861 }
4862 let head_gpu = g.head.model_idx().is_some_and(|hi| {
4864 let model = layers[0].experts.first().and_then(|e| e.w1.model_arc());
4865 model.is_some_and(|m| {
4866 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
4867 &m, hi, &head_in, b, cfg.vocab, dim, logits_out,
4868 )
4869 })
4870 });
4871 for t in 0..b {
4872 if !head_gpu {
4873 let h = &head_in[t * dim..(t + 1) * dim];
4874 g.head.matvec(h, &mut logits_out[t * cfg.vocab..(t + 1) * cfg.vocab], pool);
4875 }
4876 let row = &logits_out[t * cfg.vocab..(t + 1) * cfg.vocab];
4877 let mut best = 0usize;
4878 for v in 1..cfg.vocab {
4879 if row[v] > row[best] {
4880 best = v;
4881 }
4882 }
4883 argmax_out.push(best as u32);
4884 }
4885 walked_out.clear();
4886 walked_out.extend_from_slice(&states);
4887 st.pos = pos0 + b;
4888 if spec_time {
4889 eprintln!(
4890 "verify: тень+сид+цепочка {:.1} мс, хвост+голова {:.1} мс",
4891 t_chain.as_secs_f64() * 1e3,
4892 (t0.elapsed() - t_chain).as_secs_f64() * 1e3,
4893 );
4894 }
4895 Some(txn)
4896}
4897
4898#[cfg(feature = "gpu")]
4906pub fn dsv4_spec_finish(
4907 g: &Dsv4Globals,
4908 layers: &[Dsv4Layer],
4909 cfg: &Dsv4Cfg,
4910 st: &mut Dsv4State,
4911 mut txn: Dsv4SpecTxn,
4912 accepted: usize,
4913 ids: &[u32],
4914 inv_freq: &[f32],
4915 pool: Option<&crate::pool::Pool>,
4916) -> bool {
4917 macro_rules! sfail {
4918 ($($t:tt)*) => {{
4919 if std::env::var("CMF_DSV4_SPEC_DEBUG").is_ok() {
4920 eprintln!("spec_finish: {}", format_args!($($t)*));
4921 }
4922 return false;
4923 }};
4924 }
4925 let b = txn.batch;
4926 let k = accepted.min(b);
4927 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
4928 let win_metas: Vec<(usize, usize, usize, usize)> = (0..txn.gpu_end)
4931 .map(|li| (li, txn.dev_filled[li], cfg.window, hd))
4932 .collect();
4933 if !crate::gpu_wgpu::dsv4_spec_commit_windows(st.kv_id, &win_metas, b, k) {
4934 sfail!("коммит окон");
4935 }
4936 if k == b {
4937 return true;
4940 }
4941 let Some(sh) = txn.shadow.take() else { sfail!("нет тени") };
4943 if !crate::gpu_wgpu::dsv4_spec_restore(&sh) {
4944 sfail!("restore");
4945 }
4946 let Some(model) = layers[0].experts.first().and_then(|e| e.w1.model_arc()) else {
4947 sfail!("нет модели");
4948 };
4949 let mut plan: Vec<(usize, crate::gpu_wgpu::Dsv4Prep)> = Vec::new();
4950 let mut freqs_own: Vec<&[f32]> = Vec::new();
4951 for li in 0..txn.gpu_end {
4952 let l = &layers[li];
4953 let Some(wkv) = l.wkv.model_idx() else { sfail!("wkv слоя {li}") };
4954 let comp = match &l.compressor {
4955 None => None,
4956 Some(cp) => {
4957 let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
4958 sfail!("компрессор слоя {li}");
4959 };
4960 Some((
4961 crate::gpu_wgpu::Dsv4CompW { wkv: a, wgate: bx, norm: &cp.norm, ape: &cp.ape },
4962 crate::gpu_wgpu::Dsv4CompGeom {
4963 width: cp.wkv.rows(),
4964 hidden: dim,
4965 ratio: cp.ratio,
4966 overlap: cp.overlap,
4967 rope_dim: cfg.rope_head_dim,
4968 eps: cfg.norm_eps,
4969 },
4970 ))
4971 }
4972 };
4973 let ix = match &l.indexer {
4974 None => None,
4975 Some(ixr) => {
4976 let cp = &ixr.compressor;
4977 let (Some(a), Some(bx), Some(qb), Some(wp)) = (
4978 cp.wkv.model_idx(),
4979 cp.wgate.model_idx(),
4980 ixr.wq_b.model_idx(),
4981 ixr.weights_proj.model_idx(),
4982 ) else {
4983 sfail!("индексер слоя {li}");
4984 };
4985 let ih = ixr.weights_proj.rows();
4986 Some((
4987 crate::gpu_wgpu::Dsv4CompW { wkv: a, wgate: bx, norm: &cp.norm, ape: &cp.ape },
4988 crate::gpu_wgpu::Dsv4CompGeom {
4989 width: cp.wkv.rows(),
4990 hidden: dim,
4991 ratio: cp.ratio,
4992 overlap: cp.overlap,
4993 rope_dim: cfg.rope_head_dim,
4994 eps: cfg.norm_eps,
4995 },
4996 crate::gpu_wgpu::Dsv4IxW { wq_b: qb, weights_proj: wp },
4997 crate::gpu_wgpu::Dsv4IxGeom {
4998 ih,
4999 idim: ixr.wq_b.rows() / ih.max(1),
5000 q_lora: cfg.q_lora_rank,
5001 hidden: dim,
5002 rope_dim: cfg.rope_head_dim,
5003 eps: cfg.norm_eps,
5004 top_k: cfg.index_topk,
5005 window: cfg.window,
5006 },
5007 ))
5008 }
5009 };
5010 let ew_c = comp.as_ref().map_or(0, |(_, cg)| {
5011 if cg.overlap { cg.width / 2 } else { cg.width }
5012 });
5013 let ew_i = ix.as_ref().map_or(0, |(_, cg, _, _)| {
5014 if cg.overlap { cg.width / 2 } else { cg.width }
5015 });
5016 let prep = crate::gpu_wgpu::Dsv4Prep {
5017 wkv,
5018 kv_norm: &l.kv_norm,
5019 comp,
5020 ix,
5021 filled: txn.dev_filled[li],
5022 window: cfg.window,
5023 n_comp: txn.dev_n_comp[li],
5024 n_ix: txn.dev_n_ix[li],
5025 comp_dst_off: cfg.window * hd + txn.dev_n_comp[li] * ew_c,
5026 ix_dst_off: txn.dev_n_ix[li] * ew_i,
5027 idx_cap: cfg.window + if l.indexer.is_some() { cfg.index_topk } else { 0 },
5028 };
5029 let fr = if l.compressor.is_some() {
5030 g.inv_freq_compress.as_slice()
5031 } else {
5032 g.inv_freq_window.as_slice()
5033 };
5034 freqs_own.push(if fr.is_empty() { inv_freq } else { fr });
5035 plan.push((li, prep));
5036 }
5037 if !crate::gpu_wgpu::dsv4_spec_replay(
5038 &model,
5039 &plan,
5040 st.kv_id,
5041 txn.pos0,
5042 b,
5043 k,
5044 &freqs_own,
5045 hd,
5046 dim,
5047 cfg.rope_head_dim,
5048 cfg.norm_eps,
5049 true,
5050 ) {
5051 sfail!("replay k={k}");
5052 }
5053 let advanced = |ratio: usize| -> usize {
5055 if ratio == 0 {
5056 return 0;
5057 }
5058 (0..k).filter(|t| (txn.pos0 + t + 1) % ratio == 0).count()
5059 };
5060 for li in 0..txn.gpu_end {
5061 let l = &layers[li];
5062 st.dev_filled[li] = (txn.dev_filled[li] + k).min(cfg.window);
5063 let ac = l.compressor.as_ref().map_or(0, |cp| advanced(cp.ratio));
5064 let ai = l.indexer.as_ref().map_or(0, |ix| advanced(ix.compressor.ratio));
5065 st.dev_n_comp[li] = txn.dev_n_comp[li] + ac;
5066 st.dev_n_ix[li] = txn.dev_n_ix[li] + ai;
5067 note_compressed(st.kv_id, li, st.dev_n_comp[li]);
5068 }
5069 if k >= 1 && txn.host_steps.iter().all(|(_, v)| v.len() >= k) && !txn.host_steps.is_empty()
5072 {
5073 for (li, v) in &txn.host_steps {
5074 host_restore(st, *li, &v[k - 1]);
5075 }
5076 } else {
5077 for (li, snap) in &txn.host {
5078 host_restore(st, *li, snap);
5079 }
5080 let mut scratch = HcScratch::new(cfg);
5081 let mut states = txn.states.clone();
5082 host_tail_walk_batch(
5083 g, layers, cfg, st, txn.gpu_end, &mut states[..k * hc * dim], ids, txn.pos0, k,
5084 inv_freq, &mut scratch, pool, None,
5085 );
5086 }
5087 st.pos = txn.pos0 + k;
5088 true
5089}
5090
5091pub fn forward_chunk(
5092 g: &Dsv4Globals,
5093 layers: &[Dsv4Layer],
5094 cfg: &Dsv4Cfg,
5095 st: &mut Dsv4State,
5096 ids: &[u32],
5097 pos0: usize,
5098 inv_freq: &[f32],
5099 pool: Option<&crate::pool::Pool>,
5100 logits: &mut Vec<f32>,
5101 want_logits: bool,
5102) {
5103 let bs = batch_prefill();
5104 if bs > 1 {
5105 let mut i = 0;
5112 if !st.dev_owned && !ids.is_empty() {
5113 st.pos = pos0;
5114 forward_token_inner(g, layers, cfg, st, ids[0], inv_freq, pool, logits, ids.len() == 1);
5115 i = 1;
5116 }
5117 while i < ids.len() {
5118 let end = (i + bs).min(ids.len());
5119 st.pos = pos0 + i;
5120 if !forward_chunk_batched(
5121 g,
5122 layers,
5123 cfg,
5124 st,
5125 &ids[i..end],
5126 pos0 + i,
5127 inv_freq,
5128 pool,
5129 logits,
5130 want_logits && end == ids.len(),
5131 ) {
5132 break;
5133 }
5134 i = end;
5135 }
5136 if i == ids.len() {
5137 return;
5138 }
5139 for (k, &id) in ids.iter().enumerate().skip(i) {
5141 st.pos = pos0 + k;
5142 let last = want_logits && k + 1 == ids.len();
5143 forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
5144 }
5145 return;
5146 }
5147 for (i, &id) in ids.iter().enumerate() {
5148 st.pos = pos0 + i;
5149 let last = want_logits && i + 1 == ids.len();
5150 forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
5151 }
5152}
5153
5154pub fn forward_token(
5155 g: &Dsv4Globals,
5156 layers: &[Dsv4Layer],
5157 cfg: &Dsv4Cfg,
5158 st: &mut Dsv4State,
5159 token_id: u32,
5160 inv_freq: &[f32],
5161 pool: Option<&crate::pool::Pool>,
5162 logits: &mut Vec<f32>,
5163) {
5164 forward_token_inner(g, layers, cfg, st, token_id, inv_freq, pool, logits, true);
5165}
5166
5167#[allow(clippy::too_many_arguments)]
5168fn forward_token_inner(
5169 g: &Dsv4Globals,
5170 layers: &[Dsv4Layer],
5171 cfg: &Dsv4Cfg,
5172 st: &mut Dsv4State,
5173 token_id: u32,
5174 inv_freq: &[f32],
5175 pool: Option<&crate::pool::Pool>,
5176 logits: &mut Vec<f32>,
5177 want_logits: bool,
5179) {
5180 let _t_all = prof::on().then(std::time::Instant::now);
5181 let _all_guard = Charge(_t_all, &prof::ALL_NS);
5182 let (hc, dim) = (cfg.hc_mult, cfg.dim);
5183
5184 let mut emb = vec![0.0f32; dim];
5186 g.embed.row_f32(token_id as usize, &mut emb);
5187 let mut state = vec![0.0f32; hc * dim];
5188 for j in 0..hc {
5189 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
5190 }
5191
5192 let mut scratch = HcScratch::new(cfg);
5193 let mut dump: Vec<String> = Vec::new();
5194 if dump_path().is_some() {
5195 dump.push(format!("\"embed\":{}", vec_json(&emb)));
5196 PICKED.with(|p| p.borrow_mut().clear());
5197 BODY.with(|b| b.borrow_mut().clear());
5198 dump.push(",\"layers\":[".into());
5199 }
5200 if trace_on() {
5201 eprintln!(
5202 "[dsv4] tok={token_id} pos={} embed rms={:.5}",
5203 st.pos,
5204 rms_of(&emb)
5205 );
5206 }
5207 #[cfg(feature = "gpu")]
5209 let layer_frames = gpu_layer_enabled()
5210 && dsv4_layer_loop(
5211 &mut state, layers, g, cfg, st, token_id, inv_freq, pool, &mut scratch,
5212 );
5213 #[cfg(not(feature = "gpu"))]
5214 let layer_frames = false;
5215
5216 #[cfg(feature = "gpu")]
5223 let hc_dev = hc_on_device()
5224 && !layer_frames
5225 && gpu_attn_enabled()
5226 && gpu_moe2_enabled()
5227 && dump_path().is_none();
5228 #[cfg(not(feature = "gpu"))]
5229 let hc_dev = false;
5230 #[cfg(feature = "gpu")]
5237 let two_frame_done = hc_dev
5238 && dsv4_two_frame_loop(
5239 &mut state, layers, g, cfg, st, token_id, inv_freq, pool, &mut scratch,
5240 );
5241 #[cfg(not(feature = "gpu"))]
5242 let two_frame_done = false;
5243 if !two_frame_done {
5244
5245 for (li, l) in layers.iter().enumerate() {
5246 if layer_frames {
5247 break;
5248 }
5249 hc_block(
5251 &mut state,
5252 &l.hc_attn_fn,
5253 &l.hc_attn_scale,
5254 &l.hc_attn_base,
5255 &l.attn_norm,
5256 cfg,
5257 &mut scratch,
5258 pool,
5259 |folded, out| {
5260 if dump_path().is_some() {
5261 BODY.with(|b| b.borrow_mut().push(vec_json(folded)));
5264 }
5265 let freqs = if l.compressor.is_some() {
5267 &g.inv_freq_compress
5268 } else {
5269 &g.inv_freq_window
5270 };
5271 let freqs = if freqs.is_empty() {
5272 inv_freq
5273 } else {
5274 freqs.as_slice()
5275 };
5276 attention_step(folded, l, cfg, st, li, freqs, pool, None, out);
5277 if dump_path().is_some() {
5278 BODY.with(|b| b.borrow_mut().push(vec_json(out)));
5279 }
5280 },
5281 );
5282 if dump_path().is_some() {
5283 dump.push(format!(
5286 "{}{}",
5287 if li == 0 { "" } else { "," },
5288 vec_json(&state)
5289 ));
5290 }
5291 let _t_hc2 = prof::on().then(std::time::Instant::now);
5293 hc_block(
5294 &mut state,
5295 &l.hc_ffn_fn,
5296 &l.hc_ffn_scale,
5297 &l.hc_ffn_base,
5298 &l.ffn_norm,
5299 cfg,
5300 &mut scratch,
5301 pool,
5302 |folded, out| moe_step(folded, l, cfg, token_id, li, pool, out),
5303 );
5304 if let Some(t) = _t_hc2 {
5305 prof::HC_NS.fetch_add(
5308 t.elapsed().as_nanos() as u64,
5309 std::sync::atomic::Ordering::Relaxed,
5310 );
5311 }
5312 if dump_path().is_some() {
5313 dump.push(format!(",{}", vec_json(&state)));
5314 }
5315 if trace_on() && (st.pos % 64 == 0 || st.pos == 199) {
5316 eprintln!(
5317 "[dsv4] кеши слоя {li}: окно={} сжатых={} индекс={} (ratio={:?})",
5318 st.window[li].len() / cfg.head_dim.max(1),
5319 st.compressed[li].len() / cfg.head_dim.max(1),
5320 st.index_kv[li].len().max(1) / 128,
5321 l.compressor.as_ref().map(|c| (c.ratio, c.overlap)),
5322 );
5323 }
5324 if trace_on() {
5325 let bad = state.iter().filter(|v| !v.is_finite()).count();
5326 eprintln!(
5327 "[dsv4] layer {li:>2}: rms={:.5}{}",
5328 rms_of(&state),
5329 if bad > 0 {
5330 format!(" NON-FINITE x{bad}")
5331 } else {
5332 String::new()
5333 }
5334 );
5335 }
5336 dspark_note(li, &state, cfg);
5337 }
5338 }
5339 st.pos += 1;
5340
5341 let mut h = vec![0.0f32; dim];
5343 hc_head_fold(
5344 &state,
5345 &g.hc_head_fn,
5346 g.hc_head_scale,
5347 &g.hc_head_base,
5348 cfg,
5349 pool,
5350 &mut h,
5351 );
5352 if !want_logits {
5353 logits.clear();
5354 return;
5355 }
5356 let _t_head = prof::on().then(std::time::Instant::now);
5357 rms_weighted(&mut h, &g.norm, cfg.norm_eps);
5358 logits.clear();
5359 logits.resize(g.head.rows(), 0.0);
5360 g.head.matvec(&h, logits, pool);
5361 if let Some(t) = _t_head {
5362 prof::HEAD_NS.fetch_add(
5363 t.elapsed().as_nanos() as u64,
5364 std::sync::atomic::Ordering::Relaxed,
5365 );
5366 }
5367 if dump_path().is_some() {
5368 dump.push("]".into());
5369 let picked = PICKED.with(|p| {
5370 p.borrow()
5371 .iter()
5372 .map(|v| {
5373 format!(
5374 "[{}]",
5375 v.iter()
5376 .map(|e| e.to_string())
5377 .collect::<Vec<_>>()
5378 .join(",")
5379 )
5380 })
5381 .collect::<Vec<_>>()
5382 .join(",")
5383 });
5384 dump.push(format!(",\"experts\":[{picked}]"));
5385 let body = BODY.with(|b| b.borrow().join(","));
5386 dump.push(format!(",\"attn_io\":[{body}]"));
5387 dump_line(&format!(
5388 "{{\"tok\":{token_id},\"pos\":{},{},\"head\":{},\"logits\":{}}}",
5389 st.pos - 1,
5390 dump.join(""),
5391 vec_json(&h),
5392 vec_json(logits)
5393 ));
5394 }
5395 if trace_on() {
5396 let (mut top, mut best) = (0usize, f32::NEG_INFINITY);
5397 for (i, &v) in logits.iter().enumerate() {
5398 if v > best {
5399 best = v;
5400 top = i;
5401 }
5402 }
5403 let lo = logits.iter().cloned().fold(f32::MAX, f32::min);
5404 eprintln!(
5405 "[dsv4] head: rms={:.5} logits[{}..{:.3}] argmax={top}",
5406 rms_of(&h),
5407 format_args!("{lo:.3}"),
5408 best
5409 );
5410 }
5411}
5412
5413pub fn load(
5420 model: &std::sync::Arc<cortiq_core::CmfModel>,
5421 cfg: &Dsv4Cfg,
5422 n_layers: usize,
5423) -> Result<(Dsv4Globals, Vec<Dsv4Layer>), String> {
5424 let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
5425 crate::qtensor::QTensor::from_model(model, name)
5426 };
5427 let f = |name: &str| -> Result<Vec<f32>, String> {
5431 crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
5432 };
5433
5434 let rope_of = |base: f32, yarn: bool| -> Vec<f32> {
5439 if yarn {
5440 crate::attention::yarn_inv_freq(cfg.rope_head_dim, base, 16.0, 65536, 32.0, 1.0)
5441 } else {
5442 crate::attention::rope_inv_freq(cfg.rope_head_dim, base)
5443 }
5444 };
5445 let globals = Dsv4Globals {
5446 inv_freq_compress: rope_of(160_000.0, true),
5447 inv_freq_window: rope_of(10_000.0, false),
5448 embed: q("model.embed_tokens.weight")?,
5449 norm: f("model.norm.weight")?,
5450 head: q("lm_head.weight")?,
5451 hc_head_fn: f("model.hc_head_fn")?,
5452 hc_head_base: f("model.hc_head_base")?,
5453 hc_head_scale: *f("model.hc_head_scale")?
5454 .first()
5455 .ok_or("dsv4: empty hc_head_scale")?,
5456 };
5457
5458 let mut layers = Vec::with_capacity(n_layers);
5459 for li in 0..n_layers {
5460 layers.push(load_layer(
5461 model,
5462 cfg,
5463 &format!("model.layers.{li}"),
5464 Scheme::Main,
5465 )?);
5466 }
5467 Ok((globals, layers))
5468}
5469
5470#[derive(Clone, Copy, PartialEq, Eq, Debug)]
5477pub enum Scheme {
5478 Main,
5479 Mtp,
5480}
5481
5482impl Scheme {
5483 fn attn(self) -> &'static str {
5484 match self {
5485 Scheme::Main => "self_attn",
5486 Scheme::Mtp => "attn",
5487 }
5488 }
5489 fn attn_norm(self) -> &'static str {
5490 match self {
5491 Scheme::Main => "input_layernorm.weight",
5492 Scheme::Mtp => "attn_norm.weight",
5493 }
5494 }
5495 fn ffn_norm(self) -> &'static str {
5496 match self {
5497 Scheme::Main => "post_attention_layernorm.weight",
5498 Scheme::Mtp => "ffn_norm.weight",
5499 }
5500 }
5501 fn mlp(self) -> &'static str {
5502 match self {
5503 Scheme::Main => "mlp",
5504 Scheme::Mtp => "ffn",
5505 }
5506 }
5507 fn gate_bias(self) -> &'static str {
5510 match self {
5511 Scheme::Main => "expert_bias",
5512 Scheme::Mtp => "gate.bias",
5513 }
5514 }
5515 fn shared(self) -> &'static str {
5516 match self {
5517 Scheme::Main => "shared_expert",
5518 Scheme::Mtp => "shared_experts",
5519 }
5520 }
5521 fn w(self, i: u8) -> &'static str {
5523 match (self, i) {
5524 (Scheme::Main, 1) => "gate_proj.weight",
5525 (Scheme::Main, 2) => "down_proj.weight",
5526 (Scheme::Main, _) => "up_proj.weight",
5527 (Scheme::Mtp, 1) => "w1.weight",
5528 (Scheme::Mtp, 2) => "w2.weight",
5529 (Scheme::Mtp, _) => "w3.weight",
5530 }
5531 }
5532}
5533
5534pub fn load_layer(
5536 model: &std::sync::Arc<cortiq_core::CmfModel>,
5537 cfg: &Dsv4Cfg,
5538 p: &str,
5539 s: Scheme,
5540) -> Result<Dsv4Layer, String> {
5541 let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
5542 crate::qtensor::QTensor::from_model(model, name)
5543 };
5544 let f = |name: &str| -> Result<Vec<f32>, String> {
5545 crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
5546 };
5547 let opt_f = |name: &str| -> Option<Vec<f32>> { f(name).ok() };
5548 let at = s.attn();
5549 let ml = s.mlp();
5550 {
5551 let scale3 = |name: &str| -> Result<[f32; 3], String> {
5552 let v = f(name)?;
5553 if v.len() < 3 {
5554 return Err(format!("{name}: expected 3 scales, got {}", v.len()));
5555 }
5556 Ok([v[0], v[1], v[2]])
5557 };
5558 let compressor = match q(&format!("{p}.{at}.compressor.wkv.weight")) {
5561 Ok(wkv) => {
5562 let ape = f(&format!("{p}.{at}.compressor.ape"))?;
5563 let width = wkv.rows();
5566 let ratio = (ape.len() / width.max(1)).max(1);
5567 Some(Dsv4Compressor {
5568 wkv,
5569 wgate: q(&format!("{p}.{at}.compressor.wgate.weight"))?,
5570 norm: f(&format!("{p}.{at}.compressor.norm.weight"))?,
5571 ape,
5572 ratio,
5573 overlap: ratio == 4,
5574 })
5575 }
5576 Err(_) => None,
5577 };
5578 let indexer = match q(&format!("{p}.{at}.indexer.wq_b.weight")) {
5579 Ok(wq_b) => {
5580 let ape = f(&format!("{p}.{at}.indexer.compressor.ape"))?;
5581 let cwkv = q(&format!("{p}.{at}.indexer.compressor.wkv.weight"))?;
5582 let width = cwkv.rows();
5583 let ratio = (ape.len() / width.max(1)).max(1);
5584 Some(Dsv4Indexer {
5585 wq_b,
5586 weights_proj: q(&format!("{p}.{at}.indexer.weights_proj.weight"))?,
5587 compressor: Dsv4Compressor {
5588 wkv: cwkv,
5589 wgate: q(&format!("{p}.{at}.indexer.compressor.wgate.weight"))?,
5590 norm: f(&format!("{p}.{at}.indexer.compressor.norm.weight"))?,
5591 ape,
5592 ratio,
5593 overlap: ratio == 4,
5594 },
5595 })
5596 }
5597 Err(_) => None,
5598 };
5599
5600 let mut experts = Vec::with_capacity(cfg.n_routed_experts);
5601 for e in 0..cfg.n_routed_experts {
5602 let ep = format!("{p}.{ml}.experts.{e}");
5603 experts.push(Dsv4Expert {
5604 w1: q(&format!("{ep}.{w}", w = s.w(1)))?,
5605 w2: q(&format!("{ep}.{w}", w = s.w(2)))?,
5606 w3: q(&format!("{ep}.{w}", w = s.w(3)))?,
5607 });
5608 }
5609
5610 Ok(Dsv4Layer {
5611 attn_norm: f(&format!("{p}.{an}", an = s.attn_norm()))?,
5612 ffn_norm: f(&format!("{p}.{fnm}", fnm = s.ffn_norm()))?,
5613 wq_a: q(&format!("{p}.{at}.wq_a.weight"))?,
5614 q_norm: f(&format!("{p}.{at}.q_norm.weight"))?,
5615 wq_b: q(&format!("{p}.{at}.wq_b.weight"))?,
5616 wkv: q(&format!("{p}.{at}.wkv.weight"))?,
5617 kv_norm: f(&format!("{p}.{at}.kv_norm.weight"))?,
5618 wo_a: q(&format!("{p}.{at}.wo_a.weight"))?,
5619 wo_b: q(&format!("{p}.{at}.wo_b.weight"))?,
5620 attn_sink: f(&format!("{p}.{at}.attn_sink"))?,
5621 compressor,
5622 indexer,
5623 hc_attn_fn: f(&format!("{p}.hc_attn_fn"))?,
5624 hc_attn_base: f(&format!("{p}.hc_attn_base"))?,
5625 hc_attn_scale: scale3(&format!("{p}.hc_attn_scale"))?,
5626 hc_ffn_fn: f(&format!("{p}.hc_ffn_fn"))?,
5627 hc_ffn_base: f(&format!("{p}.hc_ffn_base"))?,
5628 hc_ffn_scale: scale3(&format!("{p}.hc_ffn_scale"))?,
5629 gate: q(&format!("{p}.{ml}.gate.weight"))?,
5630 gate_bias: opt_f(&format!("{p}.{ml}.{b}", b = s.gate_bias())),
5633 tid2eid: opt_f(&format!("{p}.{ml}.tid2eid")),
5634 experts,
5635 mask: if model.tensor(&format!("{p}.{ml}.tid2eid")).is_some() {
5636 None
5637 } else {
5638 crate::loader::moe_task_mask(&format!("{p}."), cfg.n_routed_experts)
5639 },
5640 shared: Dsv4Expert {
5641 w1: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(1)))?,
5642 w2: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(2)))?,
5643 w3: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(3)))?,
5644 },
5645 })
5646 }
5647}
5648
5649pub struct Dsv4Mtp {
5658 pub layer: Dsv4Layer,
5659 pub main_proj: Option<crate::qtensor::QTensor>,
5663 pub main_norm: Option<Vec<f32>>,
5664 pub norm: Option<Vec<f32>>,
5666 pub hc_head_fn: Option<Vec<f32>>,
5667 pub hc_head_base: Option<Vec<f32>>,
5668 pub hc_head_scale: Option<f32>,
5669 pub confidence: Option<crate::qtensor::QTensor>,
5670 pub markov_w1: Option<crate::qtensor::QTensor>,
5675 pub markov_w2: Option<crate::qtensor::QTensor>,
5676}
5677
5678pub fn load_mtp(
5682 model: &std::sync::Arc<cortiq_core::CmfModel>,
5683 cfg: &Dsv4Cfg,
5684 max_depth: usize,
5685) -> Vec<Dsv4Mtp> {
5686 let f = |name: &str| -> Option<Vec<f32>> {
5687 crate::loader::load_f32(model, name, &crate::loader::Overlay::None).ok()
5688 };
5689 let mut out = Vec::new();
5690 for d in 0..max_depth {
5691 let p = format!("model.mtp.{d}");
5692 if model.tensor(&format!("{p}.attn.wq_a.weight")).is_none() {
5696 break;
5697 }
5698 let layer = match load_layer(model, cfg, &p, Scheme::Mtp) {
5699 Ok(l) => l,
5700 Err(e) => {
5701 eprintln!("MTP {d}: пропущен, {e}");
5702 break;
5703 }
5704 };
5705 out.push(Dsv4Mtp {
5706 layer,
5707 main_proj: crate::qtensor::QTensor::from_model(model, &format!("{p}.main_proj.weight"))
5708 .ok(),
5709 main_norm: f(&format!("{p}.main_norm.weight")),
5710 norm: f(&format!("{p}.norm.weight")),
5711 hc_head_fn: f(&format!("{p}.hc_head_fn")),
5712 hc_head_base: f(&format!("{p}.hc_head_base")),
5713 hc_head_scale: f(&format!("{p}.hc_head_scale")).and_then(|v| v.first().copied()),
5714 confidence: crate::qtensor::QTensor::from_model(
5715 model,
5716 &format!("{p}.confidence_head.proj.weight"),
5717 )
5718 .ok(),
5719 markov_w1: crate::qtensor::QTensor::from_model(
5720 model,
5721 &format!("{p}.markov_head.markov_w1.weight"),
5722 )
5723 .ok(),
5724 markov_w2: crate::qtensor::QTensor::from_model(
5725 model,
5726 &format!("{p}.markov_head.markov_w2.weight"),
5727 )
5728 .ok(),
5729 });
5730 }
5731 dspark_apply_mask(&mut out);
5732 if !out.is_empty() {
5733 let mp = out
5734 .iter()
5735 .find_map(|m| m.main_proj.as_ref())
5736 .map(|t| format!("[{}, {}]", t.rows(), t.cols()))
5737 .unwrap_or_else(|| "нет".into());
5738 eprintln!(
5739 "MTP: {} стади(я/и/й), main_proj {mp}, экспертов {}, \
5740 голова уверенности {}, марков {}",
5741 out.len(),
5742 out[0].layer.experts.len(),
5743 if out.iter().any(|m| m.confidence.is_some()) { "есть" } else { "нет" },
5744 if out.iter().any(|m| m.markov_w1.is_some()) { "есть" } else { "нет" },
5745 );
5746 }
5747 out
5748}
5749
5750#[cfg(test)]
5751mod tests {
5752 use super::*;
5753
5754 fn toy() -> (Dsv4Globals, Vec<Dsv4Layer>, Dsv4Cfg) {
5759 use crate::qtensor::QTensor;
5760 let cfg = Dsv4Cfg {
5761 dim: 32,
5762 n_heads: 4,
5763 head_dim: 8,
5764 rope_head_dim: 4,
5765 q_lora_rank: 16,
5766 o_lora_rank: 16,
5767 o_groups: 2,
5768 hc_mult: 4,
5769 hc_sinkhorn_iters: 20,
5770 hc_eps: 1e-6,
5771 norm_eps: 1e-6,
5772 n_routed_experts: 8,
5773 top_k: 2,
5774 moe_inter: 16,
5775 route_scale: 1.0,
5776 swiglu_limit: 10.0,
5777 window: 6,
5778 index_topk: 8,
5779 vocab: 24,
5780 };
5781 let w = |n: usize, seed: usize| -> Vec<f32> {
5784 (0..n)
5785 .map(|i| (((i * 7 + seed * 13) % 101) as f32 / 101.0 - 0.5) * 0.3)
5786 .collect()
5787 };
5788 let t = |rows: usize, cols: usize, seed: usize| {
5789 QTensor::from_f32(w(rows * cols, seed), rows, cols)
5790 };
5791 let ones = |n: usize| vec![1.0f32; n];
5792
5793 let (dim, hc) = (cfg.dim, cfg.hc_mult);
5794 let q_width = cfg.n_heads * cfg.head_dim;
5797 let kv_width = cfg.head_dim;
5798 let o_per_group = q_width / cfg.o_groups;
5799 let mut layers = Vec::new();
5800 for li in 0..2 {
5801 let experts: Vec<Dsv4Expert> = (0..cfg.n_routed_experts)
5802 .map(|e| Dsv4Expert {
5803 w1: t(cfg.moe_inter, dim, 40 + e + li * 8),
5804 w2: t(dim, cfg.moe_inter, 60 + e + li * 8),
5805 w3: t(cfg.moe_inter, dim, 80 + e + li * 8),
5806 })
5807 .collect();
5808 layers.push(Dsv4Layer {
5811 attn_norm: ones(dim),
5812 ffn_norm: ones(dim),
5813 wq_a: t(cfg.q_lora_rank, dim, 1 + li),
5814 q_norm: ones(cfg.q_lora_rank),
5815 wq_b: t(q_width, cfg.q_lora_rank, 3 + li),
5816 wkv: t(kv_width, dim, 5 + li),
5817 kv_norm: ones(kv_width),
5818 wo_a: t(cfg.o_groups * cfg.o_lora_rank, o_per_group, 7 + li),
5819 wo_b: t(dim, cfg.o_groups * cfg.o_lora_rank, 9 + li),
5820 attn_sink: vec![0.1; cfg.n_heads],
5821 compressor: if li == 1 {
5824 Some(Dsv4Compressor {
5825 wkv: t(2 * kv_width, dim, 11),
5826 wgate: t(2 * kv_width, dim, 13),
5827 norm: ones(kv_width),
5828 ape: vec![0.01; 4 * 2 * kv_width],
5829 ratio: 4,
5830 overlap: true,
5831 })
5832 } else {
5833 None
5834 },
5835 indexer: if li == 1 {
5836 Some(Dsv4Indexer {
5837 wq_b: t(2 * 16, cfg.q_lora_rank, 41),
5838 weights_proj: t(2, dim, 43),
5839 compressor: Dsv4Compressor {
5840 wkv: t(2 * 16, dim, 45),
5841 wgate: t(2 * 16, dim, 47),
5842 norm: ones(16),
5843 ape: vec![0.01; 4 * 2 * 16],
5844 ratio: 4,
5845 overlap: true,
5846 },
5847 })
5848 } else {
5849 None
5850 },
5851 hc_attn_fn: w((2 + hc) * hc * hc * dim, 15 + li),
5852 hc_attn_base: w((2 + hc) * hc, 17 + li),
5853 hc_attn_scale: [1.0, 1.0, 1.0],
5854 hc_ffn_fn: w((2 + hc) * hc * hc * dim, 19 + li),
5855 hc_ffn_base: w((2 + hc) * hc, 21 + li),
5856 hc_ffn_scale: [1.0, 1.0, 1.0],
5857 gate: t(cfg.n_routed_experts, dim, 23 + li),
5858 gate_bias: if li == 1 {
5859 Some(vec![0.0; cfg.n_routed_experts])
5860 } else {
5861 None
5862 },
5863 tid2eid: if li == 0 {
5864 Some(
5865 (0..cfg.vocab * cfg.top_k)
5866 .map(|i| (i % cfg.n_routed_experts) as f32)
5867 .collect(),
5868 )
5869 } else {
5870 None
5871 },
5872 experts,
5873 mask: None,
5874 shared: Dsv4Expert {
5875 w1: t(cfg.moe_inter, dim, 25 + li),
5876 w2: t(dim, cfg.moe_inter, 27 + li),
5877 w3: t(cfg.moe_inter, dim, 29 + li),
5878 },
5879 });
5880 }
5881 let inv = |base: f32| -> Vec<f32> {
5882 (0..cfg.rope_head_dim / 2)
5883 .map(|i| 1.0 / base.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
5884 .collect()
5885 };
5886 let g = Dsv4Globals {
5887 inv_freq_compress: inv(160000.0),
5888 inv_freq_window: inv(10000.0),
5889 embed: t(cfg.vocab, dim, 31),
5890 norm: ones(dim),
5891 head: t(cfg.vocab, dim, 33),
5892 hc_head_fn: w(hc * hc * dim, 35),
5893 hc_head_base: w(hc, 37),
5894 hc_head_scale: 1.0,
5895 };
5896 (g, layers, cfg)
5897 }
5898
5899 #[test]
5904 fn forward_token_decodes_a_sequence_without_falling_over() {
5905 let (g, layers, cfg) = toy();
5906 let mut st = Dsv4State::new(layers.len());
5907 let inv_freq: Vec<f32> = (0..cfg.rope_head_dim / 2)
5908 .map(|i| 1.0 / 10000f32.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
5909 .collect();
5910 let mut logits = Vec::new();
5911
5912 let mut first: Option<Vec<f32>> = None;
5915 for (step, tok) in [3u32, 7, 1, 9, 4, 2, 8, 5, 6, 0].into_iter().enumerate() {
5916 forward_token(
5917 &g,
5918 &layers,
5919 &cfg,
5920 &mut st,
5921 tok,
5922 &inv_freq,
5923 None,
5924 &mut logits,
5925 );
5926 assert_eq!(logits.len(), cfg.vocab, "step {step}: logit count");
5927 assert!(
5928 logits.iter().all(|v| v.is_finite()),
5929 "step {step}: non-finite logit — {logits:?}"
5930 );
5931 let spread = logits.iter().cloned().fold(f32::MIN, f32::max)
5934 - logits.iter().cloned().fold(f32::MAX, f32::min);
5935 assert!(spread > 1e-6, "step {step}: logits are flat ({spread})");
5936 if step == 0 {
5937 first = Some(logits.clone());
5938 }
5939 assert_eq!(st.pos, step + 1, "position bookkeeping");
5940 }
5941
5942 assert!(!st.window[0].is_empty(), "sliding window never filled");
5945 for (li, w) in st.window.iter().enumerate() {
5947 assert!(
5948 w.len() / cfg.head_dim <= cfg.window,
5949 "layer {li}: window holds {} positions, cap is {}",
5950 w.len() / cfg.head_dim,
5951 cfg.window
5952 );
5953 }
5954 assert!(
5955 !st.compressed[1].is_empty(),
5956 "compressor layer produced no compressed KV in 10 tokens"
5957 );
5958 assert_eq!(
5962 st.compressed[1].len() / cfg.head_dim,
5963 2,
5964 "expected two folds in ten tokens at ratio 4"
5965 );
5966 assert!(
5967 !st.prev_kv[1].is_empty(),
5968 "the overlapping compressor never kept a previous window"
5969 );
5970 for (li, l) in layers.iter().enumerate() {
5975 if l.indexer.is_some() {
5976 assert!(
5977 !st.index_kv[li].is_empty(),
5978 "layer {li} has an indexer but its cache stayed empty"
5979 );
5980 }
5981 }
5982
5983 let mut fresh = Dsv4State::new(layers.len());
5986 let mut relogits = Vec::new();
5987 forward_token(
5988 &g,
5989 &layers,
5990 &cfg,
5991 &mut fresh,
5992 3,
5993 &inv_freq,
5994 None,
5995 &mut relogits,
5996 );
5997 assert_eq!(
5998 relogits,
5999 first.unwrap(),
6000 "the same token from a fresh state must reproduce exactly"
6001 );
6002 }
6003
6004 #[test]
6008 fn swiglu_limit_clamps_up_both_ways_and_gate_only_from_above() {
6009 let inter = 4;
6010 let gate_src = [-50.0f32, 50.0, 1.0, -1.0];
6012 let up_src = [50.0f32, -50.0, 1.0, -1.0];
6013 let limit = 10.0f32;
6014 let mut got = vec![0.0f32; inter];
6015 expert_swiglu(
6016 &[0.0],
6017 &|_, d| d.copy_from_slice(&gate_src),
6018 &|_, d| d.copy_from_slice(&up_src),
6019 &|src, d| d.copy_from_slice(src),
6020 inter,
6021 1.0,
6022 limit,
6023 &mut got,
6024 );
6025 let silu = |g: f32| g / (1.0 + (-g).exp());
6026 let want = [
6028 silu(-50.0) * limit,
6029 silu(limit) * -limit,
6030 silu(1.0) * 1.0,
6031 silu(-1.0) * -1.0,
6032 ];
6033 for (i, w) in want.iter().enumerate() {
6034 assert!(
6035 (got[i] - w).abs() < 1e-5,
6036 "lane {i}: got {} want {w}",
6037 got[i]
6038 );
6039 }
6040 let mut raw = vec![0.0f32; inter];
6042 expert_swiglu(
6043 &[0.0],
6044 &|_, d| d.copy_from_slice(&gate_src),
6045 &|_, d| d.copy_from_slice(&up_src),
6046 &|src, d| d.copy_from_slice(src),
6047 inter,
6048 1.0,
6049 0.0,
6050 &mut raw,
6051 );
6052 assert!(
6053 (raw[1] - silu(50.0) * -50.0).abs() < 1e-3,
6054 "limit 0 must not clamp"
6055 );
6056 }
6057
6058 #[test]
6063 fn grouped_projection_is_identical_with_and_without_a_pool() {
6064 let (groups, lora, per_group, dim) = (4usize, 128usize, 64usize, 32usize);
6065 let attn: Vec<f32> = (0..groups * per_group)
6066 .map(|i| ((i * 13) as f32 * 0.021).sin())
6067 .collect();
6068 let wo_a: Vec<f32> = (0..groups * lora * per_group)
6069 .map(|i| ((i * 7) as f32 * 0.011).cos())
6070 .collect();
6071 let wo_b: Vec<f32> = (0..dim * groups * lora)
6072 .map(|i| ((i * 5) as f32 * 0.009).sin())
6073 .collect();
6074 let row = |r: usize, x: &[f32], _sc: &mut [f32]| -> f32 {
6075 wo_a[r * per_group..(r + 1) * per_group]
6076 .iter()
6077 .zip(x)
6078 .map(|(a, b)| a * b)
6079 .sum()
6080 };
6081 let project = |mid: &[f32], dst: &mut [f32]| {
6082 for (d, o) in dst.iter_mut().enumerate() {
6083 *o = wo_b[d * mid.len()..(d + 1) * mid.len()]
6084 .iter()
6085 .zip(mid)
6086 .map(|(a, b)| a * b)
6087 .sum();
6088 }
6089 };
6090
6091 let mut serial = vec![0.0f32; dim];
6092 o_project(
6093 &attn,
6094 &row,
6095 per_group,
6096 &project,
6097 groups,
6098 lora,
6099 None,
6100 &mut serial,
6101 );
6102
6103 let pool = crate::pool::Pool::new(4);
6104 let mut pooled = vec![0.0f32; dim];
6105 o_project(
6106 &attn,
6107 &row,
6108 per_group,
6109 &project,
6110 groups,
6111 lora,
6112 Some(&pool),
6113 &mut pooled,
6114 );
6115 assert_eq!(serial, pooled, "the pooled projection diverged");
6116 assert!(
6117 serial.iter().any(|v| v.abs() > 1e-6),
6118 "test data is degenerate"
6119 );
6120 }
6121
6122 #[test]
6123 fn block_grouped_projection_matches_position_walk() {
6124 let (_g, layers, cfg) = toy();
6125 let l = &layers[1];
6126 let b = 5;
6127 let attn_len = cfg.n_heads * cfg.head_dim;
6128 let attn: Vec<f32> = (0..b * attn_len)
6129 .map(|i| ((i * 17) as f32 * 0.013).sin())
6130 .collect();
6131 let mut walked = vec![0.0f32; b * cfg.dim];
6132 for bi in 0..b {
6133 o_project(
6134 &attn[bi * attn_len..(bi + 1) * attn_len],
6135 &|r, x, sc| l.wo_a.row_dot(r, x, sc),
6136 l.wo_a.cols(),
6137 &|mid, dst| l.wo_b.matvec(mid, dst, None),
6138 cfg.o_groups,
6139 cfg.o_lora_rank,
6140 None,
6141 &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
6142 );
6143 }
6144 let mut batched = vec![0.0f32; b * cfg.dim];
6145 o_project_block(
6146 &attn,
6147 b,
6148 &l.wo_a,
6149 &l.wo_b,
6150 cfg.o_groups,
6151 cfg.o_lora_rank,
6152 None,
6153 &mut batched,
6154 );
6155 assert_eq!(batched, walked);
6156 }
6157
6158 #[test]
6159 fn block_moe_matches_position_walk_in_route_order() {
6160 let (_g, layers, cfg) = toy();
6161 let l = &layers[1];
6164 let b = 5;
6165 let xs: Vec<f32> = (0..b * cfg.dim)
6166 .map(|i| ((i * 11) as f32 * 0.019).cos())
6167 .collect();
6168 let ids = [1u32, 2, 3, 4, 5];
6169 let mut walked = vec![0.0f32; b * cfg.dim];
6170 for bi in 0..b {
6171 moe_step(
6172 &xs[bi * cfg.dim..(bi + 1) * cfg.dim],
6173 l,
6174 &cfg,
6175 ids[bi],
6176 1,
6177 None,
6178 &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
6179 );
6180 }
6181 let mut batched = vec![0.0f32; b * cfg.dim];
6182 moe_step_block(&xs, b, l, &cfg, &ids, 1, None, &mut batched);
6183 assert_eq!(batched, walked);
6184 }
6185
6186 #[test]
6192 fn overlapping_compressor_folds_both_windows() {
6193 let (ratio, d) = (2usize, 3usize);
6194 let cur_kv: Vec<f32> = vec![
6197 1.0, 1.0, 1.0, 10.0, 20.0, 30.0, 2.0, 2.0, 2.0, 40.0, 50.0, 60.0, ];
6200 let cur_sc: Vec<f32> = vec![
6202 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 0.0, 100.0, 100.0, 0.0,
6204 ];
6205 let prev_kv: Vec<f32> = vec![
6207 7.0, 8.0, 9.0, 0.0, 0.0, 0.0, 5.0, 6.0, 7.0, 0.0, 0.0, 0.0,
6209 ];
6210 let prev_sc = vec![0.0f32; ratio * 2 * d];
6211
6212 let mut out = vec![0.0f32; d];
6213 compress_window_overlap(&prev_kv, &prev_sc, &cur_kv, &cur_sc, ratio, d, &mut out);
6214 assert!((out[0] - 40.0).abs() < 1e-3, "dim0 = {}", out[0]);
6216 assert!((out[1] - 50.0).abs() < 1e-3, "dim1 = {}", out[1]);
6217 assert!((out[2] - 30.0).abs() < 1e-3, "dim2 = {}", out[2]);
6219
6220 let mut first = vec![0.0f32; d];
6223 compress_window_overlap(&[], &[], &cur_kv, &cur_sc, ratio, d, &mut first);
6224 assert!(
6225 first.iter().all(|v| v.is_finite()),
6226 "first window: {first:?}"
6227 );
6228 assert!((first[0] - 40.0).abs() < 1e-3, "first dim0 = {}", first[0]);
6229
6230 let mut both = vec![0.0f32; d];
6232 let strong_prev = vec![100.0f32; ratio * 2 * d];
6233 compress_window_overlap(
6234 &prev_kv,
6235 &strong_prev,
6236 &cur_kv,
6237 &cur_sc,
6238 ratio,
6239 d,
6240 &mut both,
6241 );
6242 assert!(
6243 (both[0] - 40.0).abs() > 1.0,
6244 "a scored previous window must move the fold, got {}",
6245 both[0]
6246 );
6247 }
6248
6249 #[test]
6255 fn sinkhorn_matches_the_reference_numbers() {
6256 let hc = 4;
6257 let mixes: Vec<f32> = (0..24).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
6258 let base: Vec<f32> = (0..24).map(|i| (i as f32 * 0.11).cos()).collect();
6259 let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
6260 hc_split_sinkhorn(
6261 &mixes,
6262 &[1.0, 1.0, 1.0],
6263 &base,
6264 hc,
6265 20,
6266 1e-6,
6267 &mut pre,
6268 &mut post,
6269 &mut comb,
6270 );
6271 let want_pre = [0.7310596, 0.8888268, 0.9525191, 0.97424865];
6272 let want_post = [1.9600224, 1.9534285, 1.9201256, 1.8160983];
6273 let want_comb = [
6274 0.5996052,
6275 0.28253591,
6276 0.09218107,
6277 0.025676856,
6278 0.17564717,
6279 0.22228767,
6280 0.27174541,
6281 0.33031881,
6282 0.029528176,
6283 0.12206022,
6284 0.32619134,
6285 0.5222193,
6286 0.19521846,
6287 0.37311527,
6288 0.30988118,
6289 0.12178412,
6290 ];
6291 for (i, w) in want_pre.iter().enumerate() {
6292 assert!((pre[i] - w).abs() < 1e-5, "pre[{i}]: {} vs {w}", pre[i]);
6293 }
6294 for (i, w) in want_post.iter().enumerate() {
6295 assert!((post[i] - w).abs() < 1e-5, "post[{i}]: {} vs {w}", post[i]);
6296 }
6297 for (i, w) in want_comb.iter().enumerate() {
6298 assert!((comb[i] - w).abs() < 1e-4, "comb[{i}]: {} vs {w}", comb[i]);
6299 }
6300 }
6301
6302 #[test]
6307 fn sinkhorn_leaves_the_mixing_matrix_doubly_stochastic() {
6308 let hc = 4;
6309 let mix_hc = (2 + hc) * hc;
6310 let mixes: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
6312 let base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.11).cos()).collect();
6313 let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
6314 hc_split_sinkhorn(
6315 &mixes,
6316 &[1.0, 1.0, 1.0],
6317 &base,
6318 hc,
6319 20,
6320 1e-6,
6321 &mut pre,
6322 &mut post,
6323 &mut comb,
6324 );
6325 for j in 0..hc {
6326 let r: f32 = comb[j * hc..(j + 1) * hc].iter().sum();
6327 assert!((r - 1.0).abs() < 2e-3, "row {j} sums to {r}");
6328 let c: f32 = (0..hc).map(|k| comb[k * hc + j]).sum();
6329 assert!((c - 1.0).abs() < 2e-3, "col {j} sums to {c}");
6330 }
6331 assert!(pre.iter().all(|&v| v > 0.0 && v < 1.001));
6333 assert!(post.iter().all(|&v| v >= 0.0 && v <= 2.0));
6334 }
6335
6336 #[test]
6340 fn expand_of_identical_copies_is_a_fixed_point() {
6341 let (hc, dim) = (4usize, 3usize);
6342 let residual: Vec<f32> = std::iter::repeat([1.5f32, -2.0, 0.25])
6343 .take(hc)
6344 .flatten()
6345 .collect();
6346 let comb = {
6347 vec![0.25f32; hc * hc]
6349 };
6350 let post = vec![0.0f32; hc];
6351 let mut out = vec![0.0f32; hc * dim];
6352 hc_expand(&[0.0; 3], &residual, &post, &comb, hc, dim, &mut out);
6353 for (o, r) in out.iter().zip(&residual) {
6354 assert!((o - r).abs() < 1e-6, "{o} vs {r}");
6355 }
6356 }
6357
6358 #[test]
6362 fn selection_bias_steers_the_choice_but_not_the_weights() {
6363 let scores = [3.0f32, 0.1, 2.0, 0.05];
6364 let bias = [0.0f32, 10.0, 0.0, 0.0];
6365 let (mut idx, mut w) = (Vec::new(), Vec::new());
6366 route(&scores, Some(&bias), 2, 1.5, None, None, &mut idx, &mut w);
6367 assert_eq!(idx[0], 1, "the biased expert must win selection");
6368 assert_eq!(idx[1], 0);
6369 assert!(w[0] < w[1], "biased expert kept its own (small) weight");
6372 let sum: f32 = w.iter().sum();
6373 assert!((sum - 1.5).abs() < 1e-5, "weights renormalize then scale");
6374 }
6375
6376 #[test]
6380 fn attention_sink_drains_weight_without_contributing_output() {
6381 let hd = 2;
6382 let q = [1.0f32, 0.0];
6383 let kv = [1.0f32, 0.0, 0.0, 1.0];
6384 let mut out = vec![0.0f32; hd];
6385 sparse_attend(&q, &kv, &[0, 1], f32::NEG_INFINITY, 1.0, hd, &mut out);
6386 let plain = out.clone();
6387 assert!(plain[0] > plain[1], "the aligned key must dominate");
6388 sparse_attend(&q, &kv, &[0, 1], 20.0, 1.0, hd, &mut out);
6389 assert!(
6390 out[0] < plain[0] * 0.01 && out[1] < plain[1] * 0.01,
6391 "a large sink must drain nearly all the mass: {out:?}"
6392 );
6393 }
6394
6395 #[test]
6398 fn masked_positions_leave_the_denominator_alone() {
6399 let hd = 2;
6400 let q = [1.0f32, 0.0];
6401 let kv = [1.0f32, 0.0, 0.0, 1.0];
6402 let (mut a, mut b) = (vec![0.0f32; hd], vec![0.0f32; hd]);
6403 sparse_attend(&q, &kv, &[0], f32::NEG_INFINITY, 1.0, hd, &mut a);
6404 sparse_attend(
6405 &q,
6406 &kv,
6407 &[0, usize::MAX],
6408 f32::NEG_INFINITY,
6409 1.0,
6410 hd,
6411 &mut b,
6412 );
6413 for (x, y) in a.iter().zip(&b) {
6414 assert!((x - y).abs() < 1e-6, "{x} vs {y}");
6415 }
6416 }
6417
6418 #[test]
6421 fn rope_tail_inverts_itself() {
6422 let inv_freq = [1.0f32, 0.5];
6423 let orig = [9.0f32, 8.0, 1.0, 2.0, 3.0, 4.0];
6424 let mut v = orig;
6425 rope_tail(&mut v, &inv_freq, 7, 4, false);
6426 assert!(v[..2] == orig[..2], "the non-rope head must not move");
6427 assert!(v[2..] != orig[2..], "the tail must actually rotate");
6428 rope_tail(&mut v, &inv_freq, 7, 4, true);
6429 for (a, b) in v.iter().zip(&orig) {
6430 assert!((a - b).abs() < 1e-5, "{a} vs {b}");
6431 }
6432 }
6433
6434 #[test]
6437 fn compressor_pools_the_window_per_dimension() {
6438 let (ratio, width) = (2usize, 2usize);
6439 let kv = [1.0f32, 10.0, 3.0, 20.0];
6440 let score = [0.0f32, 0.0, 0.0, 50.0];
6442 let ape = vec![0.0f32; ratio * width];
6443 let mut out = vec![0.0f32; width];
6444 compress_window(&kv, &score, &ape, ratio, width, &mut out);
6445 assert!(
6446 (out[0] - 2.0).abs() < 1e-5,
6447 "equal scores average: {}",
6448 out[0]
6449 );
6450 assert!(
6451 (out[1] - 20.0).abs() < 1e-3,
6452 "a dominant score wins: {}",
6453 out[1]
6454 );
6455 }
6456
6457 #[test]
6460 fn index_scores_relu_before_weighting() {
6461 let (nh, hd) = (2usize, 2usize);
6462 let q = [1.0f32, 0.0, -1.0, 0.0];
6464 let kv = [1.0f32, 0.0, 0.0, 1.0];
6465 let w = [1.0f32, 1.0];
6466 let mut sc = Vec::new();
6467 index_scores(&q, &kv, &w, nh, hd, 2, 2, None, &mut sc);
6468 assert!(sc[0] > 0.9, "abstention, not veto: {:?}", sc);
6470 }
6471
6472 #[test]
6473 fn index_scores_mask_the_future() {
6474 let (nh, hd) = (1usize, 2usize);
6475 let q = [1.0f32, 0.0];
6476 let kv = [1.0f32, 0.0, 1.0, 0.0, 1.0, 0.0];
6477 let w = [1.0f32];
6478 let mut sc = Vec::new();
6479 index_scores(&q, &kv, &w, nh, hd, 3, 2, None, &mut sc);
6480 assert!(sc[0].is_finite() && sc[1].is_finite());
6481 assert!(sc[2] == f32::NEG_INFINITY, "position 2 is in the future");
6482 let mut idx = Vec::new();
6483 top_k_positions(&sc, 3, &mut idx);
6484 assert_eq!(idx, vec![0, 1], "a masked slot never wins a slot");
6485 }
6486
6487 #[test]
6488 fn top_k_is_deterministic_on_ties() {
6489 let sc = [1.0f32, 1.0, 1.0, 0.0];
6490 let mut idx = Vec::new();
6491 top_k_positions(&sc, 2, &mut idx);
6492 assert_eq!(idx, vec![0, 1], "ties resolve to the lower index");
6493 }
6494
6495 #[test]
6499 fn hc_block_preserves_the_copy_structure_and_applies_the_block() {
6500 let cfg = Dsv4Cfg {
6501 dim: 4,
6502 n_heads: 1,
6503 head_dim: 4,
6504 rope_head_dim: 2,
6505 q_lora_rank: 4,
6506 o_lora_rank: 2,
6507 o_groups: 1,
6508 hc_mult: 4,
6509 hc_sinkhorn_iters: 20,
6510 hc_eps: 1e-6,
6511 norm_eps: 1e-6,
6512 n_routed_experts: 2,
6513 top_k: 1,
6514 moe_inter: 4,
6515 route_scale: 1.0,
6516 swiglu_limit: 10.0,
6517 window: 128,
6518 index_topk: 4,
6519 vocab: 8,
6520 };
6521 let (hc, dim) = (cfg.hc_mult, cfg.dim);
6522 let mix_hc = (2 + hc) * hc;
6523 let hc_fn: Vec<f32> = (0..mix_hc * hc * dim)
6524 .map(|i| ((i % 13) as f32 - 6.0) * 0.05)
6525 .collect();
6526 let hc_base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.2).sin()).collect();
6527 let norm_w = vec![1.0f32; dim];
6528 let mut state: Vec<f32> = (0..hc * dim).map(|i| (i as f32 * 0.3).cos()).collect();
6529 let before = state.clone();
6530 let mut scratch = HcScratch::new(&cfg);
6531 hc_block(
6532 &mut state,
6533 &hc_fn,
6534 &[1.0, 1.0, 1.0],
6535 &hc_base,
6536 &norm_w,
6537 &cfg,
6538 &mut scratch,
6539 None,
6540 |_folded, out: &mut [f32]| out.iter_mut().for_each(|o| *o = 1.0),
6541 );
6542 assert_eq!(state.len(), before.len(), "copy structure must survive");
6543 assert!(state.iter().all(|v| v.is_finite()), "{state:?}");
6544 assert!(
6545 state.iter().zip(&before).any(|(a, b)| (a - b).abs() > 1e-4),
6546 "the block's output has to reach the state"
6547 );
6548 }
6549
6550 #[test]
6551 fn hash_route_reads_the_table_row() {
6552 let table = [7.0f32, 9.0, 1.0, 2.0, 5.0, 6.0];
6554 assert_eq!(hash_route(&table, 3, 2, 0), vec![7, 9]);
6555 assert_eq!(hash_route(&table, 3, 2, 2), vec![5, 6]);
6556 assert_eq!(hash_route(&table, 3, 2, 99), vec![5, 6]);
6558 }
6559
6560 #[test]
6565 fn a_task_mask_restricts_selection_and_renormalizes() {
6566 let scores = [0.1f32, 4.0, 1.0, 9.0];
6568 let (mut idx, mut w) = (Vec::new(), Vec::new());
6569 route(&scores, None, 2, 1.0, None, None, &mut idx, &mut w);
6570 assert_eq!(idx, vec![3, 1], "unmasked: the two best win");
6571 let sum: f32 = w.iter().sum();
6572 assert!((sum - 1.0).abs() < 1e-5, "weights must sum to route_scale");
6573
6574 let mask = [true, false, true, true];
6577 let (mut i2, mut w2) = (Vec::new(), Vec::new());
6578 route(&scores, None, 2, 1.0, None, Some(&mask), &mut i2, &mut w2);
6579 assert_eq!(i2, vec![3, 2], "masked expert must not be selected");
6580 let sum2: f32 = w2.iter().sum();
6581 assert!((sum2 - 1.0).abs() < 1e-5, "masked weights must renormalize");
6582
6583 let tight = [false, false, false, true];
6585 let (mut i3, mut w3) = (Vec::new(), Vec::new());
6586 route(&scores, None, 2, 1.0, None, Some(&tight), &mut i3, &mut w3);
6587 assert_eq!(i3, vec![3]);
6588 assert_eq!(w3.len(), 1);
6589 }
6590
6591 #[test]
6596 fn hash_layers_weight_the_experts_the_table_names() {
6597 let scores = [0.1f32, 0.4, 0.2, 5.0];
6599 let table = vec![0.0f32, 1.0];
6600 let idx_forced = hash_route(&table, 1, 2, 0);
6601 assert_eq!(idx_forced, vec![0, 1]);
6602
6603 let (mut idx, mut w) = (Vec::new(), Vec::new());
6604 route(
6605 &scores,
6606 None,
6607 2,
6608 1.0,
6609 Some(&idx_forced),
6610 None,
6611 &mut idx,
6612 &mut w,
6613 );
6614 assert_eq!(idx, vec![0, 1], "the table must decide the experts");
6615
6616 let sp = |x: f32| (1.0 + x.exp()).ln().sqrt();
6618 let (s0, s1) = (sp(scores[0]), sp(scores[1]));
6619 let tot = s0 + s1;
6620 assert!(
6621 (w[0] - s0 / tot).abs() < 1e-6,
6622 "w[0]={} want {}",
6623 w[0],
6624 s0 / tot
6625 );
6626 assert!(
6627 (w[1] - s1 / tot).abs() < 1e-6,
6628 "w[1]={} want {}",
6629 w[1],
6630 s1 / tot
6631 );
6632
6633 let (mut idx2, mut w2) = (Vec::new(), Vec::new());
6635 route(&scores, None, 2, 1.0, None, None, &mut idx2, &mut w2);
6636 assert_eq!(idx2[0], 3, "without a table the highest score still wins");
6637 }
6638}
6639
6640
6641pub const DSPARK_NOISE_TOKEN: u32 = 128799;
6658pub fn dspark_block() -> usize {
6666 5
6667}
6668
6669pub fn dspark_verify_k() -> usize {
6676 static K: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
6677 *K.get_or_init(|| {
6678 std::env::var("CMF_DSPARK_VERIFY_K")
6679 .ok()
6680 .and_then(|v| v.parse::<usize>().ok())
6681 .filter(|&n| (1..=DSPARK_BLOCK_MAX).contains(&n))
6682 .unwrap_or(DSPARK_BLOCK_MAX)
6683 })
6684}
6685
6686pub const DSPARK_BLOCK_MAX: usize = 5;
6688
6689pub struct DsparkState {
6692 pub win: Vec<Vec<f32>>,
6694 pub filled: Vec<usize>,
6696 pub main_hidden: Vec<f32>,
6698 pub have_hidden: bool,
6700}
6701
6702impl DsparkState {
6703 pub fn new(stages: usize, cfg: &Dsv4Cfg, targets: usize) -> Self {
6704 Self {
6705 win: vec![Vec::new(); stages],
6706 filled: vec![0; stages],
6707 main_hidden: vec![0.0; cfg.dim * targets],
6708 have_hidden: false,
6709 }
6710 }
6711}
6712
6713pub fn dspark_targets(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, n_layers: usize) -> Vec<usize> {
6719 let Some(mp) = mtp.iter().find_map(|m| m.main_proj.as_ref()) else {
6720 return Vec::new();
6721 };
6722 let n = (mp.cols() / cfg.dim.max(1)).clamp(1, n_layers);
6723 (n_layers - n..n_layers).collect()
6724}
6725
6726thread_local! {
6727 static DSPARK_CAP: std::cell::RefCell<(Vec<usize>, Vec<f32>, usize)> =
6733 const { std::cell::RefCell::new((Vec::new(), Vec::new(), 0)) };
6734}
6735
6736pub fn dspark_arm(targets: &[usize], dim: usize) {
6738 DSPARK_CAP.with(|c| {
6739 let mut c = c.borrow_mut();
6740 c.0 = targets.to_vec();
6741 c.1 = vec![0.0; dim * targets.len()];
6742 c.2 = 0;
6743 });
6744}
6745
6746fn dspark_wants(li: usize) -> bool {
6750 DSPARK_CAP.with(|c| c.borrow().0.contains(&li))
6751}
6752
6753pub fn dspark_note(li: usize, state: &[f32], cfg: &Dsv4Cfg) {
6755 DSPARK_CAP.with(|c| {
6756 let mut c = c.borrow_mut();
6757 if c.0.is_empty() {
6758 return;
6759 }
6760 if let Some(slot) = c.0.iter().position(|&t| t == li) {
6761 let (_, buf, seen) = &mut *c;
6762 dspark_capture(state, cfg, slot, buf);
6763 *seen = if slot == 0 { 1 } else { *seen + 1 };
6768 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
6769 eprintln!("[cap] note li={li} slot={slot} seen={}", *seen);
6770 }
6771 }
6772 });
6773}
6774
6775pub fn dspark_peek_slot(slot: usize, dim: usize, out: &mut [f32]) -> bool {
6780 DSPARK_CAP.with(|c| {
6781 let c = c.borrow();
6782 let lo = slot * dim;
6783 if c.1.len() < lo + dim {
6784 return false;
6785 }
6786 out[..dim].copy_from_slice(&c.1[lo..lo + dim]);
6787 true
6788 })
6789}
6790
6791pub fn dspark_take(out: &mut Vec<f32>) -> bool {
6793 DSPARK_CAP.with(|c| {
6794 let mut c = c.borrow_mut();
6795 if c.0.is_empty() || c.2 != c.0.len() {
6796 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
6797 eprintln!("[cap] take FAIL armed={:?} seen={}", c.0, c.2);
6798 }
6799 return false;
6800 }
6801 out.clear();
6802 out.extend_from_slice(&c.1);
6803 c.2 = 0;
6804 true
6805 })
6806}
6807
6808pub fn dspark_capture(state: &[f32], cfg: &Dsv4Cfg, slot: usize, out: &mut [f32]) {
6812 let (hc, dim) = (cfg.hc_mult, cfg.dim);
6813 let dst = &mut out[slot * dim..(slot + 1) * dim];
6814 let inv = 1.0 / hc as f32;
6815 for d in 0..dim {
6816 let mut s = 0.0;
6817 for j in 0..hc {
6818 s += state[j * dim + d];
6819 }
6820 dst[d] = s * inv;
6821 }
6822}
6823
6824pub fn dspark_freq_note(picks: &[(usize, Vec<usize>)]) {
6829 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
6830 std::sync::Mutex::new(None);
6831 let Ok(path) = std::env::var("CMF_DSPARK_PICK_DUMP") else {
6832 return;
6833 };
6834 let mut g = FREQ.lock().unwrap();
6835 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
6836 for (stage, idx) in picks {
6837 for &e in idx {
6838 *map.entry((*stage, e)).or_insert(0) += 1;
6839 }
6840 }
6841 *blocks += 1;
6842 if *blocks % 32 == 0 {
6843 let mut lines: Vec<_> = map.iter().collect();
6844 lines.sort();
6845 let body: String = lines
6846 .iter()
6847 .map(|((s, e), n)| format!("{s}\t{e}\t{n}\n"))
6848 .collect();
6849 let _ = std::fs::write(&path, body);
6850 }
6851}
6852
6853pub fn trunk_freq_note(picks: &[(usize, Vec<usize>)]) {
6858 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
6859 std::sync::Mutex::new(None);
6860 let Ok(path) = std::env::var("CMF_DSV4_TRUNK_PICK_DUMP") else {
6861 return;
6862 };
6863 let mut g = FREQ.lock().unwrap();
6864 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
6865 for (li, idx) in picks {
6866 for &e in idx {
6867 *map.entry((*li, e)).or_insert(0) += 1;
6868 }
6869 }
6870 *blocks += 1;
6871 if *blocks % 32 == 0 {
6872 let mut lines: Vec<_> = map.iter().collect();
6873 lines.sort();
6874 let body: String = lines
6875 .iter()
6876 .map(|((l, e), n)| format!("{l}\t{e}\t{n}\n"))
6877 .collect();
6878 let _ = std::fs::write(&path, body);
6879 }
6880}
6881
6882fn dspark_apply_mask(out: &mut [Dsv4Mtp]) {
6890 let Ok(path) = std::env::var("CMF_DSPARK_MASK") else {
6891 return;
6892 };
6893 let Ok(text) = std::fs::read_to_string(&path) else {
6894 eprintln!("DSpark: CMF_DSPARK_MASK={path} не читается — маска не применена");
6895 return;
6896 };
6897 for (d, line) in text.lines().enumerate() {
6898 let Some(m) = out.get_mut(d) else { break };
6899 let n = m.layer.experts.len();
6900 let mut mask = vec![false; n];
6901 let mut kept = 0usize;
6902 for tok in line.split(',') {
6903 if let Ok(e) = tok.trim().parse::<usize>() {
6904 if e < n && !mask[e] {
6905 mask[e] = true;
6906 kept += 1;
6907 }
6908 }
6909 }
6910 if kept == 0 {
6911 continue;
6912 }
6913 eprintln!("DSpark: стадия {d} ограничена {kept}/{n} экспертами");
6914 m.layer.mask = Some(mask);
6915 }
6916}
6917
6918#[cfg(feature = "gpu")]
6928pub struct DsparkPack {
6929 pub stages: Vec<DsparkStagePack>,
6930 pub gu_q2: bool,
6933 pub dn_q2: bool,
6936 pub routers: Vec<Vec<f32>>,
6939 pub biases: Vec<Option<Vec<f32>>>,
6940}
6941
6942#[cfg(feature = "gpu")]
6943pub struct DsparkStagePack {
6944 pub mask: Vec<bool>,
6946 pub to_slot: Vec<usize>,
6948 pub mask_u32: Vec<u32>,
6951 pub map_u32: Vec<u32>,
6952 pub tensors: Vec<(usize, usize, usize)>,
6954 pub n_resident: usize,
6955}
6956
6957pub static DSPARK_Q2TP_ENCODE: std::sync::OnceLock<fn(&[f32], usize, usize) -> Vec<u8>> =
6964 std::sync::OnceLock::new();
6965
6966#[cfg(feature = "gpu")]
6969pub fn dspark_gpu_on() -> bool {
6970 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6971 *ON.get_or_init(|| std::env::var("CMF_DSPARK_GPU").map(|v| v != "0").unwrap_or(true))
6972}
6973
6974#[cfg(feature = "gpu")]
6976pub fn dspark_pack_get(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<&'static DsparkPack> {
6977 static P: std::sync::OnceLock<Option<Box<DsparkPack>>> = std::sync::OnceLock::new();
6978 P.get_or_init(|| dspark_pack_build(mtp, cfg).map(Box::new)).as_deref()
6979}
6980
6981#[cfg(feature = "gpu")]
6996pub fn dspark_reserve_note(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, layers: &[Dsv4Layer]) {
6997 if mtp.is_empty()
6998 || std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "0")
6999 || !dspark_gpu_on()
7000 {
7001 return;
7002 }
7003 let dt = |q2: bool| {
7004 if q2 {
7005 cortiq_core::TensorDtype::Q2TiledP
7006 } else {
7007 cortiq_core::TensorDtype::Q4TiledP
7008 }
7009 };
7010 let gu_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
7011 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
7012 });
7013 let dn_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
7014 e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
7015 });
7016 let gu = cortiq_core::quant::expected_nbytes(dt(gu_q2), &[cfg.moe_inter, cfg.dim])
7017 .unwrap_or(0);
7018 let dn = cortiq_core::quant::expected_nbytes(dt(dn_q2), &[cfg.dim, cfg.moe_inter])
7019 .unwrap_or(0);
7020 let per = (2 * gu + dn) as u64;
7021 let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
7022 .ok()
7023 .and_then(|v| v.parse().ok())
7024 .unwrap_or(40);
7027 let bytes = per * (n_res * mtp.len() + mtp.len() + 1) as u64;
7029 let trunk: u64 = layers
7031 .iter()
7032 .map(|l| {
7033 let Some(e) = l.experts.first() else { return 0 };
7034 let gu = cortiq_core::quant::expected_nbytes(
7035 dt(e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
7036 &[cfg.moe_inter, cfg.dim],
7037 )
7038 .unwrap_or(0);
7039 let dn = cortiq_core::quant::expected_nbytes(
7040 dt(e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
7041 &[cfg.dim, cfg.moe_inter],
7042 )
7043 .unwrap_or(0);
7044 ((2 * gu + dn) * (l.experts.len() + 1)) as u64
7045 })
7046 .sum();
7047 if let Some(budget) = crate::gpu_wgpu::dsv4_vram_budget() {
7048 if budget < trunk / 10 * 9 + bytes {
7049 return;
7050 }
7051 }
7052 crate::gpu_wgpu::DRAFT_RESERVE.store(bytes, std::sync::atomic::Ordering::Relaxed);
7053}
7054
7055#[cfg(not(feature = "gpu"))]
7056pub fn dspark_reserve_note(_mtp: &[Dsv4Mtp], _cfg: &Dsv4Cfg, _layers: &[Dsv4Layer]) {}
7057
7058#[cfg(feature = "gpu")]
7059pub fn dspark_pack_build(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<DsparkPack> {
7060 if mtp.is_empty() {
7061 return None;
7062 }
7063 let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
7064 .ok()
7065 .and_then(|v| v.parse().ok())
7066 .unwrap_or_else(|| {
7067 let native_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
7073 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
7074 });
7075 let gu_q2 = native_q2 || DSPARK_Q2TP_ENCODE.get().is_some();
7076 let dn_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
7077 e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
7078 });
7079 let room = crate::gpu_wgpu::dsv4_draft_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2);
7080 (room.saturating_sub(mtp.len() + 1) / mtp.len().max(1)).clamp(8, 64)
7081 });
7082 let mut freq: Vec<Vec<(u64, usize)>> = vec![Vec::new(); mtp.len()];
7086 let pack_path = std::env::var("CMF_DSPARK_PACK").ok().or_else(|| {
7087 let m = mtp[0].layer.experts.first()?.w1.model_arc()?;
7088 let mut s = m.path.as_os_str().to_os_string();
7089 s.push(".dspark.tsv");
7090 let p = std::path::PathBuf::from(s);
7091 p.exists().then(|| p.to_string_lossy().into_owned())
7092 });
7093 if let Some(path) = pack_path {
7094 if let Ok(text) = std::fs::read_to_string(&path) {
7095 for line in text.lines() {
7096 let mut it = line.split_whitespace();
7097 if let (Some(s), Some(e), Some(n)) = (it.next(), it.next(), it.next()) {
7098 if let (Ok(s), Ok(e), Ok(n)) =
7099 (s.parse::<usize>(), e.parse::<usize>(), n.parse::<u64>())
7100 {
7101 if s < freq.len() {
7102 freq[s].push((n, e));
7103 }
7104 }
7105 }
7106 }
7107 }
7108 }
7109 let mut stages = Vec::with_capacity(mtp.len());
7110 let mut routers = Vec::with_capacity(mtp.len());
7111 let mut biases = Vec::with_capacity(mtp.len());
7112 for (si, m) in mtp.iter().enumerate() {
7113 let l = &m.layer;
7114 let n = l.experts.len();
7115 let mut order: Vec<usize> = {
7118 let mut f = freq[si].clone();
7119 f.sort_by(|a, b| b.0.cmp(&a.0));
7120 let mut seen = vec![false; n];
7121 let mut o: Vec<usize> = f
7122 .into_iter()
7123 .map(|(_, e)| e)
7124 .filter(|&e| {
7125 if e < n && !seen[e] {
7126 seen[e] = true;
7127 true
7128 } else {
7129 false
7130 }
7131 })
7132 .collect();
7133 o.extend((0..n).filter(|&e| !seen[e]));
7134 o
7135 };
7136 order.truncate(n_res.min(n));
7137 let mut mask = vec![false; n];
7138 let mut to_slot = vec![usize::MAX; n];
7139 let mut tensors = Vec::with_capacity(order.len() + 1);
7140 for (slot, &e) in order.iter().enumerate() {
7141 let ex = &l.experts[e];
7142 let (Some(w1), Some(w3), Some(w2)) =
7143 (ex.w1.model_idx(), ex.w3.model_idx(), ex.w2.model_idx())
7144 else {
7145 return None;
7146 };
7147 mask[e] = true;
7148 to_slot[e] = slot;
7149 tensors.push((w1, w3, w2));
7150 }
7151 let (Some(s1), Some(s3), Some(s2)) = (
7152 l.shared.w1.model_idx(),
7153 l.shared.w3.model_idx(),
7154 l.shared.w2.model_idx(),
7155 ) else {
7156 return None;
7157 };
7158 tensors.push((s1, s3, s2));
7159 let mut router = vec![0.0f32; n * cfg.dim];
7161 for (r, row) in (0..n).zip(router.chunks_mut(cfg.dim)) {
7162 l.gate.row_f32(r, row);
7163 }
7164 routers.push(router);
7165 biases.push(l.gate_bias.clone());
7166 let mask_u32: Vec<u32> = mask.iter().map(|&m| m as u32).collect();
7167 let map_u32: Vec<u32> = to_slot
7168 .iter()
7169 .map(|&x| if x == usize::MAX { u32::MAX } else { x as u32 })
7170 .collect();
7171 stages.push(DsparkStagePack {
7172 mask,
7173 to_slot,
7174 mask_u32,
7175 map_u32,
7176 tensors,
7177 n_resident: order.len(),
7178 });
7179 }
7180 let model = mtp[0].layer.experts.first().and_then(|e| e.w1.model_arc())?;
7184 let mut skeleton = Vec::new();
7185 for m in mtp {
7186 let l = &m.layer;
7187 for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b] {
7188 skeleton.push(t.model_idx()?);
7189 }
7190 }
7191 if let Some(mp) = mtp[0].main_proj.as_ref() {
7192 skeleton.push(mp.model_idx()?);
7193 }
7194 for &idx in &skeleton {
7195 if !crate::gpu_wgpu::dsv4_weight_ready(&model, idx) {
7196 eprintln!("DSpark: скелет драфта не влез в VRAM — GPU-черновик выключен");
7197 return None;
7198 }
7199 }
7200 let native_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
7206 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
7207 });
7208 let gu_q2 = native_q2 || crate::dsv4::DSPARK_Q2TP_ENCODE.get().is_some();
7209 let dn_native = mtp[0].layer.experts.first().is_some_and(|e| {
7210 e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
7211 });
7212 for (si, sp) in stages.iter().enumerate() {
7213 let ok = if native_q2 {
7214 crate::gpu_wgpu::dsv4_experts_ready(
7215 &model, &sp.tensors, cfg.moe_inter, cfg.dim, true, dn_native,
7216 )
7217 } else if gu_q2 {
7218 crate::gpu_wgpu::moe_expert_bufs_requant_gu(
7219 &model,
7220 &sp.tensors,
7221 cfg.moe_inter,
7222 cfg.dim,
7223 )
7224 .is_some()
7225 } else {
7226 crate::gpu_wgpu::dsv4_experts_ready(
7227 &model, &sp.tensors, cfg.moe_inter, cfg.dim, false, false,
7228 )
7229 };
7230 if !ok {
7231 eprintln!(
7232 "DSpark: эксперты стадии {si} ({} + shared) не влезли в VRAM — GPU-черновик выключен",
7233 sp.n_resident
7234 );
7235 return None;
7236 }
7237 }
7238 let _ = crate::gpu_wgpu::pin_weights(&model, &skeleton);
7239 eprintln!(
7240 "DSpark: пак драфта на карте — {} стадии по {} экспертов + shared",
7241 stages.len(),
7242 stages.iter().map(|s| s.n_resident.to_string()).collect::<Vec<_>>().join("/")
7243 );
7244 Some(DsparkPack {
7245 stages,
7246 gu_q2,
7247 dn_q2: dn_native,
7248 routers,
7249 biases,
7250 })
7251}
7252
7253pub fn dspark_ring_append(
7260 g: &Dsv4Globals,
7261 mtp: &[Dsv4Mtp],
7262 cfg: &Dsv4Cfg,
7263 ds: &mut DsparkState,
7264 pos: usize,
7265 pool: Option<&crate::pool::Pool>,
7266) {
7267 let (dim, hd, rd) = (cfg.dim, cfg.head_dim, cfg.rope_head_dim);
7268 let inv_freq = &g.inv_freq_window;
7269 let Some(stage0) = mtp.first() else { return };
7270 let (Some(mp), Some(mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
7271 return;
7272 };
7273 let mut main_x = vec![0.0f32; dim];
7274 mp.matvec(&ds.main_hidden, &mut main_x, pool);
7275 rms_weighted(&mut main_x, mn, cfg.norm_eps);
7276 for (si, m) in mtp.iter().enumerate() {
7277 let kvw = m.layer.wkv.rows();
7278 if ds.win[si].len() < cfg.window * kvw {
7279 ds.win[si].resize(cfg.window * kvw, 0.0);
7280 }
7281 let mut kv = vec![0.0f32; kvw];
7282 m.layer.wkv.matvec(&main_x, &mut kv, pool);
7283 rms_weighted(&mut kv, &m.layer.kv_norm, cfg.norm_eps);
7284 rope_tail(&mut kv[kvw - hd..], inv_freq, pos, rd, false);
7285 let slot = pos % cfg.window;
7286 ds.win[si][slot * kvw..(slot + 1) * kvw].copy_from_slice(&kv);
7287 ds.filled[si] = (pos + 1).min(cfg.window);
7288 }
7289}
7290
7291#[cfg(feature = "gpu")]
7297#[allow(clippy::too_many_arguments)]
7298pub fn dspark_draft_gpu(
7299 g: &Dsv4Globals,
7300 mtp: &[Dsv4Mtp],
7301 cfg: &Dsv4Cfg,
7302 ds: &mut DsparkState,
7303 pack: &DsparkPack,
7304 kv_id: u64,
7305 last_token: u32,
7306 pos: usize,
7307 pool: Option<&crate::pool::Pool>,
7308 out_conf: &mut Vec<f32>,
7309) -> Vec<u32> {
7310 let (hc, dim) = (cfg.hc_mult, cfg.dim);
7311 let block = dspark_block();
7312 let Some(model) = mtp[0].layer.experts.first().and_then(|e| e.w1.model_arc()) else {
7313 return Vec::new();
7314 };
7315 let (Some(mp), Some(mn)) = (mtp[0].main_proj.as_ref(), mtp[0].main_norm.as_ref()) else {
7316 return Vec::new();
7317 };
7318 let Some(mp_idx) = mp.model_idx() else {
7319 return Vec::new();
7320 };
7321 let mut stages = Vec::with_capacity(mtp.len());
7322 for (si, m) in mtp.iter().enumerate() {
7323 let l = &m.layer;
7324 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
7325 l.wq_a.model_idx(),
7326 l.wq_b.model_idx(),
7327 l.wo_a.model_idx(),
7328 l.wo_b.model_idx(),
7329 l.wkv.model_idx(),
7330 ) else {
7331 return Vec::new();
7332 };
7333 let sp = &pack.stages[si];
7334 stages.push(crate::gpu_wgpu::DsparkStageW {
7335 wq_a,
7336 wq_b,
7337 wo_a,
7338 wo_b,
7339 wkv,
7340 q_norm: &l.q_norm,
7341 kv_norm: &l.kv_norm,
7342 attn_norm: &l.attn_norm,
7343 ffn_norm: &l.ffn_norm,
7344 sink: &l.attn_sink,
7345 hc_attn_fn: &l.hc_attn_fn,
7346 hc_attn_scale: &l.hc_attn_scale,
7347 hc_attn_base: &l.hc_attn_base,
7348 hc_ffn_fn: &l.hc_ffn_fn,
7349 hc_ffn_scale: &l.hc_ffn_scale,
7350 hc_ffn_base: &l.hc_ffn_base,
7351 router: &pack.routers[si],
7352 bias: pack.biases[si].as_deref(),
7353 experts: &sp.tensors,
7354 mask_u32: &sp.mask_u32,
7355 map_u32: &sp.map_u32,
7356 });
7357 }
7358 let geom = crate::gpu_wgpu::DsparkGeom {
7359 dim,
7360 hc,
7361 nh: cfg.n_heads,
7362 hd: cfg.head_dim,
7363 rd: cfg.rope_head_dim,
7364 q_lora: cfg.q_lora_rank,
7365 o_lora: cfg.o_lora_rank,
7366 o_groups: cfg.o_groups,
7367 inter: cfg.moe_inter,
7368 n_experts: cfg.n_routed_experts,
7369 top_k: cfg.top_k,
7370 window: cfg.window,
7371 eps: cfg.norm_eps,
7372 hc_eps: cfg.hc_eps,
7373 sinkhorn_iters: cfg.hc_sinkhorn_iters,
7374 route_scale: cfg.route_scale,
7375 swiglu_limit: cfg.swiglu_limit,
7376 scale: (cfg.head_dim as f32).powf(-0.5),
7377 gu_q2: pack.gu_q2,
7378 dn_q2: pack.dn_q2,
7379 };
7380 let ids: Vec<u32> = (0..block)
7382 .map(|i| if i == 0 { last_token } else { DSPARK_NOISE_TOKEN })
7383 .collect();
7384 let mut states0 = vec![0.0f32; block * hc * dim];
7385 let mut emb = vec![0.0f32; dim];
7386 for (i, &id) in ids.iter().enumerate() {
7387 g.embed.row_f32(id as usize, &mut emb);
7388 for j in 0..hc {
7389 states0[(i * hc + j) * dim..(i * hc + j + 1) * dim].copy_from_slice(&emb);
7390 }
7391 }
7392 let dspark_time = {
7393 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7394 *ON.get_or_init(|| std::env::var("CMF_DSPARK_TIME").is_ok_and(|v| v != "0"))
7395 };
7396 let t0 = std::time::Instant::now();
7397 let filled = (pos + 1).min(cfg.window);
7398 let mut states = vec![0.0f32; block * hc * dim];
7399 if !crate::gpu_wgpu::dspark_graph(
7400 &model,
7401 &stages,
7402 geom,
7403 kv_id,
7404 mp_idx,
7405 mn,
7406 &ds.main_hidden,
7407 &states0,
7408 pos,
7409 filled,
7410 &g.inv_freq_window,
7411 block,
7412 &mut states,
7413 ) {
7414 return Vec::new();
7415 }
7416 for si in 0..mtp.len() {
7417 ds.filled[si] = filled;
7418 }
7419 let t_graph = t0.elapsed();
7420
7421 let last = &mtp[mtp.len() - 1];
7423 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
7424 last.hc_head_fn.as_ref(),
7425 last.hc_head_base.as_ref(),
7426 last.hc_head_scale,
7427 last.norm.as_ref(),
7428 ) else {
7429 return Vec::new();
7430 };
7431 let mut head_in = vec![0.0f32; block * dim];
7432 let mut pre_norms = vec![vec![0.0f32; dim]; block];
7433 for i in 0..block {
7434 hc_head_fold(
7435 &states[i * hc * dim..(i + 1) * hc * dim],
7436 hfn,
7437 hscale,
7438 hbase,
7439 cfg,
7440 pool,
7441 &mut head_in[i * dim..(i + 1) * dim],
7442 );
7443 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
7444 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
7445 }
7446 let t_fold = t0.elapsed();
7447 let mut logits = vec![0.0f32; block * cfg.vocab];
7448 let head_gpu = g.head.model_idx().is_some_and(|hi| {
7451 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
7452 &model,
7453 hi,
7454 &head_in,
7455 block,
7456 cfg.vocab,
7457 dim,
7458 &mut logits,
7459 )
7460 });
7461 if !head_gpu {
7462 g.head.matmat(&head_in, block, &mut logits, pool);
7463 }
7464 let t_head = t0.elapsed();
7465 let mut proposals = Vec::with_capacity(block);
7470 out_conf.clear();
7471 let mut prev = last_token;
7472 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
7473 let mut bias = vec![0.0f32; cfg.vocab];
7474 for i in 0..block {
7475 let row = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
7476 if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
7477 w1.row_f32(prev as usize, &mut mk_embed);
7478 w2.matvec(&mk_embed, &mut bias, pool);
7479 for (a, b) in row.iter_mut().zip(&bias) {
7480 *a += *b;
7481 }
7482 }
7483 let mut best = 0usize;
7484 for v in 1..row.len() {
7485 if row[v] > row[best] {
7486 best = v;
7487 }
7488 }
7489 if let Some(cf) = last.confidence.as_ref() {
7490 let mut cat = pre_norms[i].clone();
7491 cat.extend_from_slice(&mk_embed);
7492 let mut sc = [0.0f32; 1];
7493 if cat.len() == cf.cols() {
7494 cf.matvec(&cat, &mut sc, pool);
7495 }
7496 out_conf.push(sc[0]);
7497 }
7498 proposals.push(best as u32);
7499 prev = best as u32;
7500 }
7501 if dspark_time {
7502 eprintln!(
7503 "DSpark GPU: граф {:.1} мс, фолды {:.1}, голова {:.1}, марков+argmax {:.1}",
7504 t_graph.as_secs_f64() * 1e3,
7505 (t_fold - t_graph).as_secs_f64() * 1e3,
7506 (t_head - t_fold).as_secs_f64() * 1e3,
7507 (t0.elapsed() - t_head).as_secs_f64() * 1e3,
7508 );
7509 }
7510 proposals
7511}
7512
7513#[allow(clippy::too_many_arguments)]
7519pub fn dspark_draft(
7520 g: &Dsv4Globals,
7521 mtp: &[Dsv4Mtp],
7522 cfg: &Dsv4Cfg,
7523 ds: &mut DsparkState,
7524 last_token: u32,
7525 pos: usize,
7526 pool: Option<&crate::pool::Pool>,
7527 out_conf: &mut Vec<f32>,
7528) -> Vec<u32> {
7529 let (hc, dim, hd, rd) = (cfg.hc_mult, cfg.dim, cfg.head_dim, cfg.rope_head_dim);
7530 let block = dspark_block();
7531 let inv_freq = &g.inv_freq_window;
7532
7533 let Some(stage0) = mtp.first() else {
7535 return Vec::new();
7536 };
7537 let (Some(mp), Some(mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
7538 return Vec::new();
7539 };
7540 dspark_ring_append(g, mtp, cfg, ds, pos, pool);
7541
7542 let ids: Vec<u32> = (0..block)
7544 .map(|i| if i == 0 { last_token } else { DSPARK_NOISE_TOKEN })
7545 .collect();
7546 let mut states = vec![vec![0.0f32; hc * dim]; block];
7547 let mut emb = vec![0.0f32; dim];
7548 for (i, &id) in ids.iter().enumerate() {
7549 g.embed.row_f32(id as usize, &mut emb);
7550 for j in 0..hc {
7551 states[i][j * dim..(j + 1) * dim].copy_from_slice(&emb);
7552 }
7553 }
7554
7555 let mut scratch = HcScratch::new(cfg);
7556 for (si, m) in mtp.iter().enumerate() {
7557 let l = &m.layer;
7558 let kvw = l.wkv.rows();
7559 let mut post = vec![vec![0.0f32; hc]; block];
7562 let mut comb = vec![vec![0.0f32; hc * hc]; block];
7563 let mut resid = vec![vec![0.0f32; hc * dim]; block];
7564 let mut folded = vec![vec![0.0f32; dim]; block];
7565 let mix_hc = (2 + hc) * hc;
7566 for i in 0..block {
7567 hc_mixes(
7568 &states[i],
7569 &l.hc_attn_fn,
7570 mix_hc,
7571 cfg.norm_eps,
7572 pool,
7573 &mut scratch.mixes,
7574 );
7575 hc_split_sinkhorn(
7576 &scratch.mixes,
7577 &l.hc_attn_scale,
7578 &l.hc_attn_base,
7579 hc,
7580 cfg.hc_sinkhorn_iters,
7581 cfg.hc_eps,
7582 &mut scratch.pre,
7583 &mut post[i],
7584 &mut comb[i],
7585 );
7586 hc_fold(&states[i], &scratch.pre, hc, dim, &mut folded[i]);
7587 rms_weighted(&mut folded[i], &l.attn_norm, cfg.norm_eps);
7588 resid[i].copy_from_slice(&states[i]);
7589 }
7590 let folded_all: Vec<f32> = folded.iter().flatten().copied().collect();
7592 let mut blk_kv = vec![0.0f32; block * kvw];
7593 l.wkv.matmat(&folded_all, block, &mut blk_kv, pool);
7594 for i in 0..block {
7595 let dst = &mut blk_kv[i * kvw..(i + 1) * kvw];
7596 rms_weighted(dst, &l.kv_norm, cfg.norm_eps);
7597 rope_tail(&mut dst[kvw - hd..], inv_freq, pos + 1 + i, rd, false);
7598 }
7599 let win_len = ds.filled[si];
7601 let mut cache = Vec::with_capacity((win_len + block) * hd);
7602 for p in 0..win_len {
7603 let e = &ds.win[si][p * kvw..(p + 1) * kvw];
7604 cache.extend_from_slice(&e[kvw - hd..]);
7605 }
7606 for i in 0..block {
7607 let e = &blk_kv[i * kvw..(i + 1) * kvw];
7608 cache.extend_from_slice(&e[kvw - hd..]);
7609 }
7610 let idxs: Vec<usize> = (0..win_len + block).collect();
7611 let scale = (hd as f32).powf(-0.5);
7612 let qrank = l.wq_a.rows();
7613 let qdim = cfg.n_heads * hd;
7614 let mut qr = vec![0.0f32; block * qrank];
7615 l.wq_a.matmat(&folded_all, block, &mut qr, pool);
7616 for i in 0..block {
7617 rms_weighted(
7618 &mut qr[i * qrank..(i + 1) * qrank],
7619 &l.q_norm,
7620 cfg.norm_eps,
7621 );
7622 }
7623 let mut q = vec![0.0f32; block * qdim];
7624 l.wq_b.matmat(&qr, block, &mut q, pool);
7625 let mut attn = vec![0.0f32; block * qdim];
7626 for i in 0..block {
7627 let qi = &mut q[i * qdim..(i + 1) * qdim];
7628 let ai = &mut attn[i * qdim..(i + 1) * qdim];
7629 let qpos = pos + 1 + i;
7630 for h in 0..cfg.n_heads {
7631 let head = &mut qi[h * hd..(h + 1) * hd];
7632 rms_inplace(head, cfg.norm_eps);
7633 rope_tail(head, inv_freq, qpos, rd, false);
7634 }
7635 for h in 0..cfg.n_heads {
7636 let qh = &qi[h * hd..(h + 1) * hd];
7637 let oh = &mut ai[h * hd..(h + 1) * hd];
7638 sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
7639 rope_tail(oh, inv_freq, qpos, rd, true);
7640 }
7641 }
7642 let mut blk_out = vec![0.0f32; block * dim];
7643 o_project_block(
7644 &attn, block, &l.wo_a, &l.wo_b, cfg.o_groups, cfg.o_lora_rank, pool,
7645 &mut blk_out,
7646 );
7647 for i in 0..block {
7648 let mut next = vec![0.0f32; hc * dim];
7649 hc_expand(
7650 &blk_out[i * dim..(i + 1) * dim],
7651 &resid[i], &post[i], &comb[i], hc, dim, &mut next,
7652 );
7653 states[i] = next;
7654 }
7655 let mut ffn_fold = vec![0.0f32; block * dim];
7658 let mut ffn_post = vec![vec![0.0f32; hc]; block];
7659 let mut ffn_comb = vec![vec![0.0f32; hc * hc]; block];
7660 let mut ffn_resid = vec![vec![0.0f32; hc * dim]; block];
7661 for i in 0..block {
7662 hc_mixes(
7663 &states[i],
7664 &l.hc_ffn_fn,
7665 mix_hc,
7666 cfg.norm_eps,
7667 pool,
7668 &mut scratch.mixes,
7669 );
7670 hc_split_sinkhorn(
7671 &scratch.mixes,
7672 &l.hc_ffn_scale,
7673 &l.hc_ffn_base,
7674 hc,
7675 cfg.hc_sinkhorn_iters,
7676 cfg.hc_eps,
7677 &mut scratch.pre,
7678 &mut ffn_post[i],
7679 &mut ffn_comb[i],
7680 );
7681 hc_fold(
7682 &states[i], &scratch.pre, hc, dim,
7683 &mut ffn_fold[i * dim..(i + 1) * dim],
7684 );
7685 rms_weighted(
7686 &mut ffn_fold[i * dim..(i + 1) * dim],
7687 &l.ffn_norm,
7688 cfg.norm_eps,
7689 );
7690 ffn_resid[i].copy_from_slice(&states[i]);
7691 }
7692 let mut moe_out = vec![0.0f32; block * dim];
7693 moe_step_block(
7694 &ffn_fold, block, l, cfg, &ids, si, pool, &mut moe_out,
7695 );
7696 for i in 0..block {
7697 let mut next = vec![0.0f32; hc * dim];
7698 hc_expand(
7699 &moe_out[i * dim..(i + 1) * dim],
7700 &ffn_resid[i], &ffn_post[i], &ffn_comb[i], hc, dim, &mut next,
7701 );
7702 states[i] = next;
7703 }
7704 }
7705
7706 let last = &mtp[mtp.len() - 1];
7708 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
7709 last.hc_head_fn.as_ref(),
7710 last.hc_head_base.as_ref(),
7711 last.hc_head_scale,
7712 last.norm.as_ref(),
7713 ) else {
7714 return Vec::new();
7715 };
7716 let mut proposals = Vec::with_capacity(block);
7717 out_conf.clear();
7718 let mut prev = last_token;
7719 let mut head_in = vec![0.0f32; block * dim];
7720 let mut pre_norms = vec![vec![0.0f32; dim]; block];
7721 for i in 0..block {
7722 hc_head_fold(
7723 &states[i], hfn, hscale, hbase, cfg, pool,
7724 &mut head_in[i * dim..(i + 1) * dim],
7725 );
7726 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
7727 rms_weighted(
7728 &mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps,
7729 );
7730 }
7731 let mut logits = vec![0.0f32; block * cfg.vocab];
7732 g.head.matmat(&head_in, block, &mut logits, pool);
7733 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
7734 for i in 0..block {
7735 let logits_i = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
7736 let no_markov = {
7745 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7746 *ON.get_or_init(|| std::env::var("CMF_DSPARK_NO_MARKOV").is_ok_and(|v| v != "0"))
7747 };
7748 if no_markov {
7749 if let Some(w1) = last.markov_w1.as_ref() {
7751 w1.row_f32(prev as usize, &mut mk_embed);
7752 }
7753 } else if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
7754 w1.row_f32(prev as usize, &mut mk_embed);
7755 let mut bias = vec![0.0f32; cfg.vocab];
7756 w2.matvec(&mk_embed, &mut bias, pool);
7757 for (a, b) in logits_i.iter_mut().zip(&bias) {
7758 *a += *b;
7759 }
7760 }
7761 let mut best = 0usize;
7762 for v in 1..logits_i.len() {
7763 if logits_i[v] > logits_i[best] {
7764 best = v;
7765 }
7766 }
7767 if let Some(cf) = last.confidence.as_ref() {
7768 let mut cat = pre_norms[i].clone();
7769 cat.extend_from_slice(&mk_embed);
7770 let mut s = [0.0f32; 1];
7771 if cat.len() == cf.cols() {
7772 cf.matvec(&cat, &mut s, pool);
7773 }
7774 out_conf.push(s[0]);
7775 }
7776 proposals.push(best as u32);
7777 prev = best as u32;
7778 }
7779 proposals
7780}