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
1220 / an as f64
1221 };
1222 eprintln!(
1223 "[dsv4-профиль] ВНИМАНИЕ НА КАРТЕ на вызов: одиночное {:.3} мс, \
1224 оценки {:.3} мс, применение {:.3} мс",
1225 g(0),
1226 g(1),
1227 g(2),
1228 );
1229 }
1230 let gn = crate::gpu_wgpu::MOE_GPU_N.load(Ordering::Relaxed);
1231 let gns = crate::gpu_wgpu::MOE_GPU_NS[0].load(Ordering::Relaxed);
1232 if gn > 0 && gns > 0 {
1233 eprintln!(
1234 "[dsv4-профиль] MoE НА КАРТЕ: {:.3} мс на вызов ({gn} замеров)",
1235 gns as f64 / 1e6 / gn as f64,
1236 );
1237 } else if gn > 0 {
1238 eprintln!(
1242 "[dsv4-профиль] MoE НА КАРТЕ: метки вернули НОЛЬ на {gn} замерах — \
1243 запрос времени не сработал, число не использовать"
1244 );
1245 }
1246 eprintln!(
1247 "[dsv4-профиль] из кодирования: буферы экспертов {:.2} мс, \
1248 загрузки {:.2} мс, проходы {:.2} мс",
1249 ns(&crate::gpu_wgpu::MOE_BUFS_NS),
1250 ns(&crate::gpu_wgpu::MOE_UP_NS),
1251 ns(&crate::gpu_wgpu::MOE_PASS_NS),
1252 );
1253 }
1254 }
1255 }
1256}
1257
1258pub fn profile_report() {
1260 prof::report();
1261}
1262
1263fn gpu_attn_enabled() -> bool {
1268 #[cfg(feature = "gpu")]
1269 {
1270 use std::sync::OnceLock;
1271 static ON: OnceLock<bool> = OnceLock::new();
1272 *ON.get_or_init(|| {
1273 let want = std::env::var("CMF_DSV4_GPU_ATTN")
1274 .map(|v| v != "0")
1275 .unwrap_or(true);
1276 let have = want && crate::gpu::backend_available();
1277 if want && !have && std::env::var("CMF_DSV4_GPU_ATTN").is_ok() {
1278 tracing::warn!(
1279 "CMF_DSV4_GPU_ATTN задан, но устройства нет — блок внимания остаётся на CPU. Проверьте CMF_GPU=wgpu и Vulkan-ICD."
1280 );
1281 }
1282 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
1283 eprintln!("кадр dsv4: запрошен={want} доступен={have}");
1284 }
1285 have
1286 })
1287 }
1288 #[cfg(not(feature = "gpu"))]
1289 {
1290 false
1291 }
1292}
1293
1294#[cfg(feature = "gpu")]
1298#[allow(clippy::too_many_arguments)]
1299fn attn_frame(
1300 l: &Dsv4Layer,
1301 cfg: &Dsv4Cfg,
1302 st: &Dsv4State,
1303 li: usize,
1304 hidden: &[f32],
1305 qn: &[f32],
1306 idxs: &[usize],
1307 inv_freq: &[f32],
1308 pos: usize,
1309 win_len: usize,
1310 scale: f32,
1311 hc: Option<&crate::gpu_wgpu::Dsv4HcTail>,
1314 out: &mut [f32],
1315) -> bool {
1316 let hd = cfg.head_dim;
1317 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
1318 l.wq_a.model_idx(),
1319 l.wq_b.model_idx(),
1320 l.wo_a.model_idx(),
1321 l.wo_b.model_idx(),
1322 ) else {
1323 return false;
1324 };
1325 let Some(model) = l.wq_b.model_arc() else {
1326 return false;
1327 };
1328 let n_comp = st.compressed[li].len() / hd;
1333 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
1334 let kv_id = st.kv_id;
1335 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap) {
1341 return false;
1342 }
1343 if n_comp > 0
1352 && !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, cfg.window * hd, &st.compressed[li], cap)
1353 {
1354 return false;
1355 }
1356 let idx32: Vec<u32> = idxs
1357 .iter()
1358 .map(|&p| {
1359 if p < win_len {
1360 p as u32
1361 } else {
1362 (cfg.window + (p - win_len)) as u32
1363 }
1364 })
1365 .collect();
1366 let w = crate::gpu_wgpu::Dsv4AttnW {
1367 wq_a,
1368 wq_b,
1369 wo_a,
1370 wo_b,
1371 q_norm: &l.q_norm,
1372 sink: &l.attn_sink,
1373 };
1374 let g = crate::gpu_wgpu::Dsv4AttnGeom {
1375 dim: cfg.dim,
1376 nh: cfg.n_heads,
1377 hd,
1378 rd: cfg.rope_head_dim,
1379 q_lora: cfg.q_lora_rank,
1380 o_lora: cfg.o_lora_rank,
1381 o_groups: cfg.o_groups,
1382 eps: cfg.norm_eps,
1383 scale,
1384 };
1385 crate::gpu_wgpu::dsv4_attn_frame(
1393 &model,
1394 &w,
1395 g,
1396 hidden,
1397 Some(qn),
1398 kv_id,
1399 li,
1400 &idx32,
1401 inv_freq,
1402 pos,
1403 hc,
1404 out,
1405 )
1406}
1407
1408#[derive(Default)]
1411pub struct AttnPrep {
1412 pub qr: Vec<f32>,
1413 pub idxs: Vec<usize>,
1414 pub win_len: usize,
1415}
1416
1417#[allow(clippy::too_many_arguments)]
1418pub fn attention_step(
1419 hidden: &[f32],
1420 l: &Dsv4Layer,
1421 cfg: &Dsv4Cfg,
1422 st: &mut Dsv4State,
1423 li: usize,
1424 inv_freq: &[f32],
1426 pool: Option<&crate::pool::Pool>,
1427 prep_out: Option<&mut AttnPrep>,
1431 out: &mut [f32],
1432) {
1433 let _t0 = prof::on().then(std::time::Instant::now);
1434 let _guard = scopeguard_attn(_t0);
1435 let (hd, rd) = (cfg.head_dim, cfg.rope_head_dim);
1436 let pos = st.pos;
1437 if std::env::var("CMF_FREQ_DEBUG").is_ok() && li == 0 && pos == 0 {
1438 eprintln!(
1439 " [порт] rd={rd} частот={} inv_freq[0..4]={:?}",
1440 inv_freq.len(),
1441 &inv_freq[..4.min(inv_freq.len())]
1442 );
1443 }
1444
1445 let mut qr = vec![0.0f32; cfg.q_lora_rank];
1450 let mut kv = vec![0.0f32; hd];
1451 crate::qtensor::QTensor::matvec_many([&l.wq_a, &l.wkv], hidden, [&mut qr, &mut kv], pool);
1452 rms_weighted(&mut qr, &l.q_norm, cfg.norm_eps);
1453 let split_config = st.partial_set.iter().any(|&p| p) && st.split_deep;
1471 let past_chain =
1472 st.dev_owned && (li >= st.dev_set.len() || !st.dev_set.get(li).copied().unwrap_or(false));
1473 if std::env::var("CMF_DSV4_GATE_DBG").is_ok() {
1474 eprintln!(
1475 "[gate] li={li} pos={} split={split_config} past={past_chain} dev_owned={} set_len={} part_len={}",
1476 st.pos,
1477 st.dev_owned,
1478 st.dev_set.len(),
1479 st.partial_set.len()
1480 );
1481 }
1482 let on_gpu = gpu_attn_enabled() && !(split_config && past_chain);
1483
1484 rms_weighted(&mut kv, &l.kv_norm, cfg.norm_eps);
1485 rope_tail(&mut kv, inv_freq, pos, rd, false);
1486
1487 if let Some(cp) = &l.compressor {
1492 let mut pk = std::mem::take(&mut st.pending_kv[li]);
1493 let mut ps = std::mem::take(&mut st.pending_score[li]);
1494 let mut qk = std::mem::take(&mut st.prev_kv[li]);
1495 let mut qs = std::mem::take(&mut st.prev_score[li]);
1496 let entry = compressor_step(
1497 cp,
1498 hidden,
1499 pos,
1500 rd,
1501 cfg.norm_eps,
1502 inv_freq,
1503 pool,
1504 &mut pk,
1505 &mut ps,
1506 &mut qk,
1507 &mut qs,
1508 );
1509 st.pending_kv[li] = pk;
1510 st.pending_score[li] = ps;
1511 st.prev_kv[li] = qk;
1512 st.prev_score[li] = qs;
1513 if let Some(e) = entry {
1514 st.compressed[li].extend_from_slice(&e);
1515 }
1516 }
1517 if let Some(ix) = &l.indexer {
1522 let mut pk = std::mem::take(&mut st.pending_ix_kv[li]);
1523 let mut ps = std::mem::take(&mut st.pending_ix_score[li]);
1524 let mut qk = std::mem::take(&mut st.prev_ix_kv[li]);
1525 let mut qs = std::mem::take(&mut st.prev_ix_score[li]);
1526 let entry = compressor_step(
1527 &ix.compressor,
1528 hidden,
1529 pos,
1530 rd,
1531 cfg.norm_eps,
1532 inv_freq,
1533 pool,
1534 &mut pk,
1535 &mut ps,
1536 &mut qk,
1537 &mut qs,
1538 );
1539 st.pending_ix_kv[li] = pk;
1540 st.pending_ix_score[li] = ps;
1541 st.prev_ix_kv[li] = qk;
1542 st.prev_ix_score[li] = qs;
1543 if let Some(e) = entry {
1544 st.index_kv[li].extend_from_slice(&e);
1545 }
1546 }
1547
1548 st.window[li].extend_from_slice(&kv);
1549 let cap = cfg.window * hd;
1553 if st.window[li].len() > cap {
1554 let drop = st.window[li].len() - cap;
1555 st.window[li].drain(..drop);
1556 }
1557 let win_len = st.window[li].len() / hd;
1558 let n_pos = win_len + st.compressed[li].len() / hd;
1559
1560 let mut idxs: Vec<usize> = (0..win_len).collect();
1569 if !st.compressed[li].is_empty() && !no_compressed() {
1570 let n_comp = st.compressed[li].len() / hd;
1571 match &l.indexer {
1572 Some(ix) => {
1573 let ih = ix.weights_proj.rows();
1589 let idim = ix.wq_b.rows() / ih.max(1);
1590 let mut qi = vec![0.0f32; ix.wq_b.rows()];
1591 ix.wq_b.matvec(&qr, &mut qi, pool);
1592 for h in 0..ih {
1593 rope_tail(&mut qi[h * idim..(h + 1) * idim], inv_freq, pos, rd, false);
1594 }
1595 let mut hw = vec![0.0f32; ih];
1596 ix.weights_proj.matvec(hidden, &mut hw, pool);
1597 let sc_factor = (idim as f32).powf(-0.5) * (ih as f32).powf(-0.5);
1598 for w in hw.iter_mut() {
1599 *w *= sc_factor;
1600 }
1601 let n_ix = st.index_kv[li].len() / idim.max(1);
1602 let mut sc = Vec::new();
1603 index_scores(
1604 &qi,
1605 &st.index_kv[li],
1606 &hw,
1607 ih,
1608 idim,
1609 n_ix.min(n_comp),
1610 n_ix.min(n_comp),
1611 pool,
1612 &mut sc,
1613 );
1614 let mut picked = Vec::new();
1615 top_k_positions(&sc, cfg.index_topk, &mut picked);
1616 idxs.extend(picked.into_iter().map(|p| win_len + p));
1617 }
1618 None => idxs.extend((0..n_comp).map(|p| win_len + p)),
1619 }
1620 }
1621 debug_assert!(idxs.iter().all(|&p| p < n_pos));
1622 if let Some(p) = prep_out {
1623 p.qr = qr;
1624 p.idxs = idxs;
1625 p.win_len = win_len;
1626 return;
1627 }
1628
1629 let scale = (hd as f32).powf(-0.5);
1631 #[cfg(feature = "gpu")]
1632 if on_gpu
1633 && {
1634 if std::env::var("CMF_DSV4_XCHK").is_ok() {
1635 if let Some(card) = crate::gpu_wgpu::dsv4_dbg_read_tag(45, 0, hidden.len()) {
1639 let md = hidden
1640 .iter()
1641 .zip(card.iter())
1642 .map(|(a, b)| (a - b).abs())
1643 .fold(0.0f32, f32::max);
1644 eprintln!("[xchk] li={li} pos={pos} x2 maxdiff={md:.3e}");
1645 }
1646 }
1647 true
1648 }
1649 && attn_frame(
1650 l, cfg, st, li, hidden, &qr, &idxs, inv_freq, pos, win_len, scale, None, out,
1651 )
1652 {
1653 return;
1654 }
1655
1656 let mut q = vec![0.0f32; cfg.n_heads * hd];
1658 l.wq_b.matvec(&qr, &mut q, pool);
1659 for h in 0..cfg.n_heads {
1660 let head = &mut q[h * hd..(h + 1) * hd];
1661 rms_inplace(head, cfg.norm_eps);
1662 rope_tail(head, inv_freq, pos, rd, false);
1663 }
1664 let mut cache: Vec<f32> = st.window[li].clone();
1665 cache.extend_from_slice(&st.compressed[li]);
1666
1667 let mut attn = vec![0.0f32; cfg.n_heads * hd];
1669 for h in 0..cfg.n_heads {
1670 let qh = &q[h * hd..(h + 1) * hd];
1671 let oh = &mut attn[h * hd..(h + 1) * hd];
1676 sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
1677 rope_tail(oh, inv_freq, pos, rd, true);
1678 }
1679
1680 o_project(
1686 &attn,
1687 &|r, x, sc| l.wo_a.row_dot(r, x, sc),
1688 l.wo_a.cols(),
1689 &|mid, dst| l.wo_b.matvec(mid, dst, pool),
1690 cfg.o_groups,
1691 cfg.o_lora_rank,
1692 pool,
1693 out,
1694 );
1695}
1696
1697pub fn rms_weighted(v: &mut [f32], w: &[f32], eps: f32) {
1699 let ms = v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32;
1700 let inv = 1.0 / (ms + eps).sqrt();
1701 for (x, g) in v.iter_mut().zip(w) {
1702 *x = *x * inv * g;
1703 }
1704}
1705
1706thread_local! {
1718 static ROUTE_COUNTS: std::cell::RefCell<Vec<Vec<u64>>> =
1719 const { std::cell::RefCell::new(Vec::new()) };
1720}
1721
1722fn route_stats_on() -> bool {
1723 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1724 *ON.get_or_init(|| std::env::var("CMF_MOE_STATS").is_ok())
1725}
1726
1727fn record_route(li: usize, n_layers_hint: usize, n_experts: usize, idx: &[usize]) {
1728 ROUTE_COUNTS.with(|c| {
1729 let mut c = c.borrow_mut();
1730 if c.len() <= li.max(n_layers_hint) {
1731 c.resize(li.max(n_layers_hint) + 1, Vec::new());
1732 }
1733 let row = &mut c[li];
1734 if row.len() < n_experts {
1735 row.resize(n_experts, 0);
1736 }
1737 for &e in idx {
1738 if e < row.len() {
1739 row[e] += 1;
1740 }
1741 }
1742 });
1743}
1744
1745pub fn take_route_counts() -> Vec<Vec<u64>> {
1747 ROUTE_COUNTS.with(|c| std::mem::take(&mut *c.borrow_mut()))
1748}
1749
1750struct Charge(
1754 Option<std::time::Instant>,
1755 &'static std::sync::atomic::AtomicU64,
1756);
1757impl Drop for Charge {
1758 fn drop(&mut self) {
1759 if let Some(t) = self.0 {
1760 self.1.fetch_add(
1761 t.elapsed().as_nanos() as u64,
1762 std::sync::atomic::Ordering::Relaxed,
1763 );
1764 }
1765 }
1766}
1767fn scopeguard_attn(t: Option<std::time::Instant>) -> Charge {
1768 Charge(t, &prof::ATTN_NS)
1769}
1770fn scopeguard_moe(t: Option<std::time::Instant>, li: usize) -> Charge {
1771 if t.is_some() {
1772 prof::note_layer(li);
1773 }
1774 Charge(t, &prof::MOE_NS)
1775}
1776
1777#[cfg(feature = "gpu")]
1781#[allow(clippy::too_many_arguments)]
1782fn dsv4_layer_loop(
1783 state: &mut [f32],
1784 layers: &[Dsv4Layer],
1785 g: &Dsv4Globals,
1786 cfg: &Dsv4Cfg,
1787 st: &mut Dsv4State,
1788 token_id: u32,
1789 inv_freq: &[f32],
1790 pool: Option<&crate::pool::Pool>,
1791 scratch: &mut HcScratch,
1792) -> bool {
1793 let dim = cfg.dim;
1794 let freqs_of = |l: &Dsv4Layer| -> &[f32] {
1795 let f = if l.compressor.is_some() {
1796 &g.inv_freq_compress
1797 } else {
1798 &g.inv_freq_window
1799 };
1800 if f.is_empty() { inv_freq } else { f.as_slice() }
1801 };
1802 {
1815 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1816 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
1817 if let (Some(idx), Some(model)) = (g.head.model_idx(), g.head.model_arc()) {
1818 let ok = crate::gpu_wgpu::dsv4_weight_ready(&model, idx);
1819 tracing::info!("dsv4: голова на карте: {}", if ok { "да" } else { "нет" });
1820 }
1821 }
1822 }
1823 let mut on_dev = vec![false; layers.len()];
1824 let mut partial_dev = vec![false; layers.len()];
1825 for (li, l) in layers.iter().enumerate() {
1826 if l.wq_a.model_idx().is_none()
1827 || l.wq_b.model_idx().is_none()
1828 || l.wo_a.model_idx().is_none()
1829 || l.wo_b.model_idx().is_none()
1830 {
1831 return false;
1832 }
1833 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
1834 return false;
1835 };
1836 let gu_q2 = l
1837 .experts
1838 .first()
1839 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
1840 let mut want = vec![
1855 l.wq_a.model_idx(),
1856 l.wq_b.model_idx(),
1857 l.wo_a.model_idx(),
1858 l.wo_b.model_idx(),
1859 ];
1860 if chain_enabled() {
1861 want.push(l.wkv.model_idx());
1862 if let Some(cp) = &l.compressor {
1863 want.push(cp.wkv.model_idx());
1864 want.push(cp.wgate.model_idx());
1865 }
1866 if let Some(ix) = &l.indexer {
1867 want.push(ix.wq_b.model_idx());
1868 want.push(ix.weights_proj.model_idx());
1869 want.push(ix.compressor.wkv.model_idx());
1870 want.push(ix.compressor.wgate.model_idx());
1871 }
1872 }
1873 let attn_ok = want
1874 .into_iter()
1875 .flatten()
1876 .all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
1877 let pk = pack_for(l, cfg, li);
1882 if let Some(pk) = pk {
1883 let dn_q2 = l
1884 .experts
1885 .first()
1886 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
1887 let experts_ok = crate::gpu_wgpu::dsv4_experts_ready(
1888 &model,
1889 &pk.tensors,
1890 cfg.moe_inter,
1891 dim,
1892 gu_q2,
1893 dn_q2,
1894 );
1895 on_dev[li] = attn_ok && experts_ok && pk.globals.len() == cfg.n_routed_experts;
1896 partial_dev[li] = attn_ok && experts_ok && pk.globals.len() < cfg.n_routed_experts;
1897 }
1898 }
1899 let active_dev: Vec<bool> = on_dev
1900 .iter()
1901 .zip(&partial_dev)
1902 .map(|(&full, &partial)| full || partial)
1903 .collect();
1904 if !active_dev.iter().any(|&x| x) {
1905 return false;
1906 }
1907 if st.partial_set.len() != partial_dev.len() || st.partial_set != partial_dev {
1912 st.partial_set = partial_dev.clone();
1913 st.split_deep = active_dev
1914 .iter()
1915 .zip(&partial_dev)
1916 .filter(|(a, p)| !**a || **p)
1917 .count()
1918 > 1;
1919 }
1920
1921 {
1925 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1926 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
1927 let host: Vec<usize> = active_dev
1928 .iter()
1929 .enumerate()
1930 .filter(|&(_, d)| !*d)
1931 .map(|(i, _)| i)
1932 .collect();
1933 let partial: Vec<(usize, usize)> = partial_dev
1934 .iter()
1935 .enumerate()
1936 .filter(|&(_, d)| *d)
1937 .filter_map(|(li, _)| pack_for(&layers[li], cfg, li).map(|p| (li, p.globals.len())))
1938 .collect();
1939 if host.is_empty() && partial.is_empty() {
1940 tracing::info!("dsv4: все {} слоёв на карте", on_dev.len());
1941 } else {
1942 tracing::info!(
1943 "dsv4: {} из {} слоёв используют карту; частичные {:?}; на хосте {:?}",
1944 active_dev.len() - host.len(),
1945 on_dev.len(),
1946 partial,
1947 host,
1948 );
1949 }
1950 }
1951 }
1952
1953 let (mut folded, post0, comb0) = hc_fold_norm(
1955 state,
1956 &layers[0].hc_attn_fn,
1957 &layers[0].hc_attn_scale,
1958 &layers[0].hc_attn_base,
1959 &layers[0].attn_norm,
1960 cfg,
1961 pool,
1962 );
1963 if !crate::gpu_wgpu::dsv4_state_write(state) || !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
1964 {
1965 return false;
1966 }
1967 if st.dev_owned && st.dev_set != active_dev {
1978 let left: Vec<usize> = (0..active_dev.len().min(st.dev_set.len()))
1979 .filter(|&i| st.dev_set[i] && !active_dev[i])
1980 .collect();
1981 if !left.is_empty() {
1982 tracing::warn!("слои {left:?} ушли с карты — кеши на разных сторонах");
1983 return false;
1984 }
1985 }
1989 let chain = chain_enabled();
1990 let layer_cap = {
1998 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
1999 *N.get_or_init(|| {
2000 std::env::var("CMF_DSV4_LAYERS_PROBE")
2001 .ok()
2002 .and_then(|v| v.parse::<usize>().ok())
2003 .unwrap_or(usize::MAX)
2004 })
2005 };
2006 let mut run: Vec<usize> = Vec::new();
2007 let mut sink_out = vec![0.0f32; dim];
2008 let mut state_on_host = true;
2013 for (li, l) in layers.iter().enumerate() {
2014 if li >= layer_cap {
2015 break;
2016 }
2017 if prof::on() {
2023 prof::note_layer(li);
2024 }
2025 if chain && on_dev[li] {
2026 run.push(li);
2031 if run.len() >= chain_max() || dspark_wants(li) {
2036 let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
2037 let captured = *run.last().unwrap();
2038 if !dsv4_chain_run(
2039 layers,
2040 &run,
2041 cfg,
2042 g,
2043 st,
2044 token_id,
2045 &mut folded,
2046 Some(state),
2047 1,
2048 &[],
2049 need_qn,
2050 pool,
2051 ) {
2052 return false;
2053 }
2054 state_on_host = true;
2055 dspark_note(captured, state, cfg);
2056 run.clear();
2057 }
2058 continue;
2059 }
2060 if chain && !run.is_empty() {
2061 if !dsv4_chain_run(
2065 layers,
2066 &run,
2067 cfg,
2068 g,
2069 st,
2070 token_id,
2071 &mut folded,
2072 Some(state),
2073 1,
2074 &[],
2075 run[0] == 0 || !on_dev[run[0] - 1],
2076 pool,
2077 ) {
2078 return false;
2079 }
2080 state_on_host = true;
2081 dspark_note(*run.last().unwrap(), state, cfg);
2082 }
2083 run.clear();
2084 if partial_dev[li] && partial_walk_on() {
2085 let Some(home) = dsv4_partial_layer(
2090 state,
2091 &mut folded,
2092 layers,
2093 l,
2094 cfg,
2095 st,
2096 token_id,
2097 li,
2098 freqs_of(l),
2099 pool,
2100 ) else {
2101 return false;
2102 };
2103 state_on_host = home;
2104 if home {
2105 dspark_note(li, state, cfg);
2106 }
2107 continue;
2108 }
2109 if !on_dev[li] {
2110 if !state_on_host && !crate::gpu_wgpu::dsv4_state_read(state) {
2111 return false;
2112 }
2113 state_on_host = true;
2114 let freqs = freqs_of(l);
2115 hc_block(
2116 state,
2117 &l.hc_attn_fn,
2118 &l.hc_attn_scale,
2119 &l.hc_attn_base,
2120 &l.attn_norm,
2121 cfg,
2122 scratch,
2123 pool,
2124 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
2125 );
2126 hc_block(
2127 state,
2128 &l.hc_ffn_fn,
2129 &l.hc_ffn_scale,
2130 &l.hc_ffn_base,
2131 &l.ffn_norm,
2132 cfg,
2133 scratch,
2134 pool,
2135 |f, o| {
2142 if host_cpu_moe() {
2143 crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
2144 } else {
2145 moe_step(f, l, cfg, token_id, li, pool, o)
2146 }
2147 },
2148 );
2149 if layers.get(li + 1).is_some() && on_dev.get(li + 1).copied().unwrap_or(false) {
2154 let n = &layers[li + 1];
2155 let (f, p2, c2) = hc_fold_norm(
2156 state,
2157 &n.hc_attn_fn,
2158 &n.hc_attn_scale,
2159 &n.hc_attn_base,
2160 &n.attn_norm,
2161 cfg,
2162 pool,
2163 );
2164 folded = f;
2165 if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2) {
2166 return false;
2167 }
2168 if !crate::gpu_wgpu::dsv4_state_write(state) {
2169 return false;
2170 }
2171 }
2172 dspark_note(li, state, cfg);
2173 continue;
2174 }
2175 let mut prep = AttnPrep::default();
2176 attention_step(
2177 &folded,
2178 l,
2179 cfg,
2180 st,
2181 li,
2182 freqs_of(l),
2183 pool,
2184 Some(&mut prep),
2185 &mut sink_out,
2186 );
2187 let hd = cfg.head_dim;
2189 let n_comp = st.compressed[li].len() / hd;
2190 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2191 let kv_id = st.kv_id;
2192 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap)
2193 || (n_comp > 0
2194 && !crate::gpu_wgpu::dsv4_cache_write(
2195 kv_id,
2196 li,
2197 cfg.window * hd,
2198 &st.compressed[li],
2199 cap,
2200 ))
2201 {
2202 return false;
2203 }
2204 let idx32: Vec<u32> = prep
2205 .idxs
2206 .iter()
2207 .map(|&p| {
2208 if p < prep.win_len {
2209 p as u32
2210 } else {
2211 (cfg.window + (p - prep.win_len)) as u32
2212 }
2213 })
2214 .collect();
2215 let Some(pk) = pack_for(l, cfg, li) else {
2216 return false;
2217 };
2218 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
2219 l.wq_a.model_idx(),
2220 l.wq_b.model_idx(),
2221 l.wo_a.model_idx(),
2222 l.wo_b.model_idx(),
2223 ) else {
2224 return false;
2225 };
2226 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
2227 return false;
2228 };
2229 let forced: Option<Vec<usize>> = l.tid2eid.as_ref().and_then(|tbl| {
2230 let v: Vec<usize> = hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
2231 .into_iter()
2232 .map(|gi| pk.to_slot[gi])
2233 .collect();
2234 if v.contains(&usize::MAX) {
2235 None
2236 } else {
2237 Some(v)
2238 }
2239 });
2240 if l.tid2eid.is_some() && forced.is_none() {
2241 return false;
2242 }
2243 let nxt = layers.get(li + 1);
2244 let w = crate::gpu_wgpu::Dsv4LayerW {
2245 attn: crate::gpu_wgpu::Dsv4AttnW {
2246 wq_a,
2247 wq_b,
2248 wo_a,
2249 wo_b,
2250 q_norm: &l.q_norm,
2251 sink: &l.attn_sink,
2252 },
2253 moe: crate::gpu_wgpu::Dsv4MoeW {
2254 router: &[],
2255 experts: &pk.tensors,
2256 logits: &[],
2257 bias: pk.bias.as_deref(),
2264 forced: forced.as_deref(),
2265 remap: None,
2266 },
2267 hc_ffn_fn: &l.hc_ffn_fn,
2268 hc_ffn_scale: &l.hc_ffn_scale,
2269 hc_ffn_base: &l.hc_ffn_base,
2270 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2271 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2272 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2273 ffn_norm: &l.ffn_norm,
2274 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2275 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2276 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2277 router: &pk.router,
2278 };
2279 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2280 attn: crate::gpu_wgpu::Dsv4AttnGeom {
2281 dim,
2282 nh: cfg.n_heads,
2283 hd,
2284 rd: cfg.rope_head_dim,
2285 q_lora: cfg.q_lora_rank,
2286 o_lora: cfg.o_lora_rank,
2287 o_groups: cfg.o_groups,
2288 eps: cfg.norm_eps,
2289 scale: (hd as f32).powf(-0.5),
2290 },
2291 moe: crate::gpu_wgpu::Dsv4MoeGeom {
2292 hidden: dim,
2293 inter: cfg.moe_inter,
2294 top_k: cfg.top_k,
2295 route_scale: cfg.route_scale,
2296 swiglu_limit: cfg.swiglu_limit,
2297 gu_q2: l.experts.first().is_some_and(|e| {
2298 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
2299 }),
2300 },
2301 hc: cfg.hc_mult,
2302 hc_eps: cfg.hc_eps,
2303 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2304 };
2305 let mut next = vec![0.0f32; dim];
2306 if !crate::gpu_wgpu::dsv4_layer_frame(
2307 &model,
2308 &w,
2309 geom,
2310 kv_id,
2311 li,
2312 Some(&prep.qr),
2313 &idx32,
2314 freqs_of(l),
2315 st.pos,
2316 &mut next,
2317 ) {
2318 return false;
2319 }
2320 state_on_host = false;
2321 folded = next;
2322 dspark_note(li, state, cfg);
2323 }
2324 let mut state_home = false;
2325 if chain {
2326 if !run.is_empty() {
2327 let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
2331 let last_on_dev = *on_dev.last().unwrap_or(&false);
2332 let carry = last_on_dev && run.last() == Some(&(layers.len() - 1));
2333 let ok = if carry {
2334 let r = dsv4_chain_run(
2335 layers,
2336 &run,
2337 cfg,
2338 g,
2339 st,
2340 token_id,
2341 &mut folded,
2342 Some(state),
2343 1,
2344 &[],
2345 need_qn,
2346 pool,
2347 );
2348 state_home = r;
2349 state_on_host = r;
2350 if r {
2351 dspark_note(*run.last().unwrap(), state, cfg);
2352 }
2353 r
2354 } else {
2355 let r = dsv4_chain_run(
2356 layers,
2357 &run,
2358 cfg,
2359 g,
2360 st,
2361 token_id,
2362 &mut folded,
2363 None,
2364 1,
2365 &[],
2366 need_qn,
2367 pool,
2368 );
2369 if r {
2370 state_on_host = false;
2371 }
2372 r
2373 };
2374 if !ok {
2375 return false;
2376 }
2377 }
2378 if st.dev_set.is_empty() {
2379 st.dev_set = active_dev.clone();
2380 st.partial_set = partial_dev.clone();
2381 let mut idxs = Vec::new();
2387 for (li, l) in layers.iter().enumerate() {
2388 if !active_dev.get(li).copied().unwrap_or(false) {
2389 continue;
2390 }
2391 for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b, &l.gate] {
2392 idxs.extend(t.model_idx());
2393 }
2394 if let Some(pk) = pack_for(l, cfg, li) {
2395 for &(a, b, c) in &pk.tensors {
2396 idxs.extend([a, b, c]);
2397 }
2398 }
2399 }
2400 for (li, l) in layers.iter().enumerate() {
2408 if active_dev.get(li).copied().unwrap_or(false) {
2409 continue;
2410 }
2411 let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
2412 tracing::info!(
2413 "слой {li} на хосте: упаковано {packed} экспертов из {}",
2414 cfg.n_routed_experts
2415 );
2416 }
2417 let pinned = layers
2418 .iter()
2419 .find_map(|l| l.experts.first().and_then(|e| e.w1.model_arc()))
2420 .map_or(0, |m| crate::gpu_wgpu::pin_weights(&m, &idxs));
2421 tracing::info!(
2422 "закреплено на карте: {pinned} тензоров {} слоёв",
2423 on_dev.iter().filter(|&&x| x).count()
2424 );
2425 }
2426 }
2427 if state_home || state_on_host {
2428 return true;
2429 }
2430 crate::gpu_wgpu::dsv4_state_read(state)
2431}
2432
2433#[cfg(feature = "gpu")]
2438#[allow(clippy::too_many_arguments)]
2439fn dsv4_partial_layer(
2440 state: &mut [f32],
2441 folded: &mut Vec<f32>,
2442 layers: &[Dsv4Layer],
2443 l: &Dsv4Layer,
2444 cfg: &Dsv4Cfg,
2445 st: &mut Dsv4State,
2446 token_id: u32,
2447 li: usize,
2448 freqs: &[f32],
2449 pool: Option<&crate::pool::Pool>,
2450) -> Option<bool> {
2451 let dim = cfg.dim;
2452 let mut prep = AttnPrep::default();
2453 let mut sink = vec![0.0f32; dim];
2454 attention_step(
2455 folded,
2456 l,
2457 cfg,
2458 st,
2459 li,
2460 freqs,
2461 pool,
2462 Some(&mut prep),
2463 &mut sink,
2464 );
2465 let hd = cfg.head_dim;
2466 let n_comp = st.compressed[li].len() / hd;
2467 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2468 if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
2469 || (n_comp > 0
2470 && !crate::gpu_wgpu::dsv4_cache_write(
2471 st.kv_id,
2472 li,
2473 cfg.window * hd,
2474 &st.compressed[li],
2475 cap,
2476 ))
2477 {
2478 return None;
2479 }
2480 let a_tail = crate::gpu_wgpu::Dsv4HcTail {
2481 fn_: &l.hc_ffn_fn,
2482 scale: &l.hc_ffn_scale,
2483 base: &l.hc_ffn_base,
2484 norm: &l.ffn_norm,
2485 hc: cfg.hc_mult,
2486 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2487 hc_eps: cfg.hc_eps,
2488 eps: cfg.norm_eps,
2489 };
2490 let scale = (cfg.head_dim as f32).powf(-0.5);
2491 if !attn_frame(
2492 l,
2493 cfg,
2494 st,
2495 li,
2496 folded,
2497 &prep.qr,
2498 &prep.idxs,
2499 freqs,
2500 st.pos,
2501 prep.win_len,
2502 scale,
2503 Some(&a_tail),
2504 &mut [],
2505 ) {
2506 return None;
2507 }
2508 let nxt = layers.get(li + 1);
2509 let forced = l
2510 .tid2eid
2511 .as_ref()
2512 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
2513 let mut next = vec![0.0f32; dim];
2514 let (cold_sum, cold_count) = moe_frame(
2515 &[],
2516 l,
2517 cfg,
2518 li,
2519 &[],
2520 forced.as_deref(),
2521 pool,
2522 Some(&a_tail),
2523 None,
2528 &mut next,
2529 )?;
2530 let state_ok = if cold_count == 0 {
2536 crate::gpu_wgpu::dsv4_state_read(state)
2537 } else {
2538 crate::gpu_wgpu::dsv4_state_add_cold(&cold_sum, cfg.hc_mult, state)
2539 };
2540 if !state_ok {
2541 return None;
2542 }
2543 if let Some(n) = nxt {
2544 let (f, post, comb) = hc_fold_norm(
2545 state,
2546 &n.hc_attn_fn,
2547 &n.hc_attn_scale,
2548 &n.hc_attn_base,
2549 &n.attn_norm,
2550 cfg,
2551 pool,
2552 );
2553 *folded = f;
2554 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2555 || !crate::gpu_wgpu::dsv4_state_write(state)
2556 {
2557 return None;
2558 }
2559 }
2560 Some(true)
2565}
2566
2567#[cfg(feature = "gpu")]
2568fn chain_max() -> usize {
2569 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
2570 *N.get_or_init(|| {
2571 std::env::var("CMF_DSV4_CHAIN_MAX")
2572 .ok()
2573 .and_then(|v| v.parse().ok())
2574 .unwrap_or(usize::MAX)
2575 })
2576}
2577
2578#[cfg(feature = "gpu")]
2581fn partial_walk_on() -> bool {
2592 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2593 *ON.get_or_init(|| std::env::var("CMF_DSV4_PARTIAL_WALK").is_ok_and(|v| v != "0"))
2594}
2595
2596fn host_cpu_moe() -> bool {
2597 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2598 *ON.get_or_init(|| std::env::var("CMF_DSV4_HOST_CPU_MOE").is_ok_and(|v| v != "0"))
2599}
2600
2601fn chain_enabled() -> bool {
2602 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2603 *ON.get_or_init(|| {
2604 std::env::var("CMF_DSV4_CHAIN")
2605 .map(|v| v != "0")
2606 .unwrap_or(true)
2607 })
2608}
2609
2610#[cfg(feature = "gpu")]
2620#[allow(clippy::too_many_arguments)]
2621fn dsv4_chain_run(
2622 layers: &[Dsv4Layer],
2623 run: &[usize],
2624 cfg: &Dsv4Cfg,
2625 g: &Dsv4Globals,
2626 st: &mut Dsv4State,
2627 token_id: u32,
2628 folded: &mut Vec<f32>,
2634 state_out: Option<&mut [f32]>,
2639 batch: usize,
2642 batch_ids: &[u32],
2646 need_qn: bool,
2653 pool: Option<&crate::pool::Pool>,
2654) -> bool {
2655 if run.is_empty() {
2656 return true;
2657 }
2658 let (dim, hd) = (cfg.dim, cfg.head_dim);
2659 let first = run[0];
2660 let Some(model) = layers[first].experts.first().and_then(|e| e.w1.model_arc()) else {
2661 return false;
2662 };
2663 if batch <= 1 && need_qn {
2667 let mut qn0 = vec![0.0f32; cfg.q_lora_rank];
2668 layers[first].wq_a.matvec(folded, &mut qn0, pool);
2669 rms_weighted(&mut qn0, &layers[first].q_norm, cfg.norm_eps);
2670 if !crate::gpu_wgpu::dsv4_chain_seed(folded, &qn0) {
2671 return false;
2672 }
2673 } else if batch <= 1 && !crate::gpu_wgpu::dsv4_chain_seed_fold(folded) {
2674 return false;
2675 }
2676
2677 let mut packs = Vec::with_capacity(run.len());
2679 let mut forceds: Vec<Option<Vec<usize>>> = Vec::with_capacity(run.len());
2680 for &li in run {
2681 let Some(pk) = pack_for(&layers[li], cfg, li) else {
2682 return false;
2683 };
2684 let forced: Option<Vec<usize>> = layers[li].tid2eid.as_ref().and_then(|tbl| {
2685 let v: Vec<usize> = hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
2686 .into_iter()
2687 .map(|gi| pk.to_slot[gi])
2688 .collect();
2689 if v.contains(&usize::MAX) {
2690 None
2691 } else {
2692 Some(v)
2693 }
2694 });
2695 if layers[li].tid2eid.is_some() && forced.is_none() {
2696 return false;
2697 }
2698 forceds.push(forced);
2699 packs.push(pk);
2700 }
2701
2702 let mut items = Vec::with_capacity(run.len());
2703 let mut freqs = Vec::with_capacity(run.len());
2704 for (i, &li) in run.iter().enumerate() {
2705 let l = &layers[li];
2706 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
2707 l.wq_a.model_idx(),
2708 l.wq_b.model_idx(),
2709 l.wo_a.model_idx(),
2710 l.wo_b.model_idx(),
2711 l.wkv.model_idx(),
2712 ) else {
2713 return false;
2714 };
2715 let comp = match &l.compressor {
2716 None => None,
2717 Some(cp) => {
2718 let (Some(a), Some(b)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
2719 return false;
2720 };
2721 Some((
2722 crate::gpu_wgpu::Dsv4CompW {
2723 wkv: a,
2724 wgate: b,
2725 norm: &cp.norm,
2726 ape: &cp.ape,
2727 },
2728 crate::gpu_wgpu::Dsv4CompGeom {
2729 width: cp.wkv.rows(),
2730 hidden: dim,
2731 ratio: cp.ratio,
2732 overlap: cp.overlap,
2733 rope_dim: cfg.rope_head_dim,
2734 eps: cfg.norm_eps,
2735 },
2736 ))
2737 }
2738 };
2739 let ix = match &l.indexer {
2740 None => None,
2741 Some(ixr) => {
2742 let cp = &ixr.compressor;
2743 let (Some(a), Some(b), Some(qb), Some(wp)) = (
2744 cp.wkv.model_idx(),
2745 cp.wgate.model_idx(),
2746 ixr.wq_b.model_idx(),
2747 ixr.weights_proj.model_idx(),
2748 ) else {
2749 return false;
2750 };
2751 let ih = ixr.weights_proj.rows();
2752 Some((
2753 crate::gpu_wgpu::Dsv4CompW {
2754 wkv: a,
2755 wgate: b,
2756 norm: &cp.norm,
2757 ape: &cp.ape,
2758 },
2759 crate::gpu_wgpu::Dsv4CompGeom {
2760 width: cp.wkv.rows(),
2761 hidden: dim,
2762 ratio: cp.ratio,
2763 overlap: cp.overlap,
2764 rope_dim: cfg.rope_head_dim,
2765 eps: cfg.norm_eps,
2766 },
2767 crate::gpu_wgpu::Dsv4IxW {
2768 wq_b: qb,
2769 weights_proj: wp,
2770 },
2771 crate::gpu_wgpu::Dsv4IxGeom {
2772 ih,
2773 idim: ixr.wq_b.rows() / ih.max(1),
2774 q_lora: cfg.q_lora_rank,
2775 hidden: dim,
2776 rope_dim: cfg.rope_head_dim,
2777 eps: cfg.norm_eps,
2778 top_k: cfg.index_topk,
2779 window: cfg.window,
2780 },
2781 ))
2782 }
2783 };
2784 let ew_c0 = l.compressor.as_ref().map_or(0, |cp| {
2788 if cp.overlap {
2789 cp.wkv.rows() / 2
2790 } else {
2791 cp.wkv.rows()
2792 }
2793 });
2794 let comp_extra = l
2795 .compressor
2796 .as_ref()
2797 .map_or(0, |cp| batch.max(1).div_ceil(cp.ratio.max(1)));
2798 let need = cfg.window * hd
2799 + (st.dev_n_comp[li] + comp_extra + 1) * ew_c0.max(1)
2800 + (batch.max(1) + 1) * hd;
2801 if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
2802 return false;
2803 }
2804 let ew_c = comp.as_ref().map_or(
2805 0,
2806 |(_, cg)| {
2807 if cg.overlap { cg.width / 2 } else { cg.width }
2808 },
2809 );
2810 let ew_i = ix.as_ref().map_or(
2811 0,
2812 |(_, cg, _, _)| {
2813 if cg.overlap { cg.width / 2 } else { cg.width }
2814 },
2815 );
2816 let prep = crate::gpu_wgpu::Dsv4Prep {
2817 wkv,
2818 kv_norm: &l.kv_norm,
2819 comp,
2820 ix,
2821 filled: st.dev_filled[li],
2822 window: cfg.window,
2823 n_comp: st.dev_n_comp[li],
2824 n_ix: st.dev_n_ix[li],
2825 comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
2826 ix_dst_off: st.dev_n_ix[li] * ew_i,
2827 idx_cap: cfg.window
2828 + if l.indexer.is_some() {
2829 cfg.index_topk
2830 } else {
2831 st.dev_n_comp[li] + comp_extra + 1
2832 },
2833 };
2834 let nxt = layers.get(li + 1);
2835 let w = crate::gpu_wgpu::Dsv4LayerW {
2836 attn: crate::gpu_wgpu::Dsv4AttnW {
2837 wq_a,
2838 wq_b,
2839 wo_a,
2840 wo_b,
2841 q_norm: &l.q_norm,
2842 sink: &l.attn_sink,
2843 },
2844 moe: crate::gpu_wgpu::Dsv4MoeW {
2845 router: &packs[i].router,
2846 experts: &packs[i].tensors,
2847 logits: &[],
2848 bias: packs[i].bias.as_deref(),
2851 forced: forceds[i].as_deref(),
2852 remap: None,
2853 },
2854 hc_ffn_fn: &l.hc_ffn_fn,
2855 hc_ffn_scale: &l.hc_ffn_scale,
2856 hc_ffn_base: &l.hc_ffn_base,
2857 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2858 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2859 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2860 ffn_norm: &l.ffn_norm,
2861 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2862 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2863 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2864 router: &packs[i].router,
2865 };
2866 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2867 attn: crate::gpu_wgpu::Dsv4AttnGeom {
2868 dim,
2869 nh: cfg.n_heads,
2870 hd,
2871 rd: cfg.rope_head_dim,
2872 q_lora: cfg.q_lora_rank,
2873 o_lora: cfg.o_lora_rank,
2874 o_groups: cfg.o_groups,
2875 eps: cfg.norm_eps,
2876 scale: (hd as f32).powf(-0.5),
2877 },
2878 moe: crate::gpu_wgpu::Dsv4MoeGeom {
2879 hidden: dim,
2880 inter: cfg.moe_inter,
2881 top_k: cfg.top_k,
2882 route_scale: cfg.route_scale,
2883 swiglu_limit: cfg.swiglu_limit,
2884 gu_q2: l.experts.first().is_some_and(|e| {
2885 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
2886 }),
2887 },
2888 hc: cfg.hc_mult,
2889 hc_eps: cfg.hc_eps,
2890 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2891 };
2892 freqs.push(if l.compressor.is_some() {
2893 g.inv_freq_compress.as_slice()
2894 } else {
2895 g.inv_freq_window.as_slice()
2896 });
2897 items.push((w, geom, prep));
2898 }
2899
2900 let mut out = vec![0.0f32; dim * batch.max(1)];
2901 if batch > 1 {
2902 let mut forced_pt: Vec<Vec<Option<Vec<usize>>>> = Vec::with_capacity(batch);
2907 for t in 0..batch {
2908 let id = batch_ids.get(t).copied().unwrap_or(token_id);
2909 let mut row = Vec::with_capacity(run.len());
2910 for (i, &li) in run.iter().enumerate() {
2911 row.push(layers[li].tid2eid.as_ref().and_then(|tbl| {
2912 let v: Vec<usize> = hash_route(tbl, cfg.vocab, cfg.top_k, id)
2913 .into_iter()
2914 .map(|gi| packs[i].to_slot[gi])
2915 .collect();
2916 if v.contains(&usize::MAX) {
2917 None
2918 } else {
2919 Some(v)
2920 }
2921 }));
2922 if layers[li].tid2eid.is_some() && row[i].is_none() {
2923 return false;
2924 }
2925 }
2926 forced_pt.push(row);
2927 }
2928 if !crate::gpu_wgpu::dsv4_chain_batch(
2929 &model,
2930 &items,
2931 st.kv_id,
2932 first,
2933 &freqs,
2934 st.pos,
2935 batch,
2936 Some(&forced_pt),
2937 &mut out,
2938 state_out,
2939 ) {
2940 return false;
2941 }
2942 *folded = out[(batch - 1) * dim..batch * dim].to_vec();
2945 } else {
2946 if !crate::gpu_wgpu::dsv4_layer_chain(
2947 &model, &items, st.kv_id, first, &freqs, st.pos, &mut out, state_out,
2948 ) {
2949 return false;
2950 }
2951 *folded = out;
2952 }
2953 for (i, &li) in run.iter().enumerate() {
2957 for t in 0..batch.max(1) {
2958 let pos = st.pos + t;
2959 st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
2960 if let Some((_, cg, ..)) = items[i].2.ix.as_ref() {
2961 if (pos + 1) % cg.ratio == 0 {
2962 st.dev_n_ix[li] += 1;
2963 }
2964 }
2965 if let Some((_, cg)) = items[i].2.comp.as_ref() {
2966 if (pos + 1) % cg.ratio == 0 {
2967 st.dev_n_comp[li] += 1;
2968 }
2969 }
2970 }
2971 }
2972 st.dev_owned = true;
2973 true
2974}
2975
2976#[cfg(feature = "gpu")]
2978fn hc_on_device() -> bool {
2979 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2980 *ON.get_or_init(|| {
2981 std::env::var("CMF_DSV4_HC_DEV").is_ok_and(|v| v != "0") && crate::gpu::backend_available()
2987 })
2988}
2989
2990#[cfg(feature = "gpu")]
2999#[allow(clippy::too_many_arguments)]
3000fn dsv4_two_frame_loop(
3001 state: &mut [f32],
3002 layers: &[Dsv4Layer],
3003 g: &Dsv4Globals,
3004 cfg: &Dsv4Cfg,
3005 st: &mut Dsv4State,
3006 token_id: u32,
3007 inv_freq: &[f32],
3008 pool: Option<&crate::pool::Pool>,
3009 scratch: &mut HcScratch,
3010) -> bool {
3011 let dim = cfg.dim;
3012 let freqs_of = |l: &Dsv4Layer| -> &[f32] {
3013 let f = if l.compressor.is_some() {
3014 &g.inv_freq_compress
3015 } else {
3016 &g.inv_freq_window
3017 };
3018 if f.is_empty() { inv_freq } else { f.as_slice() }
3019 };
3020 let (mut folded, post0, comb0) = hc_fold_norm(
3022 state,
3023 &layers[0].hc_attn_fn,
3024 &layers[0].hc_attn_scale,
3025 &layers[0].hc_attn_base,
3026 &layers[0].attn_norm,
3027 cfg,
3028 pool,
3029 );
3030 if !crate::gpu_wgpu::dsv4_state_write(state) || !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
3031 {
3032 return false;
3033 }
3034 let mut on_dev = vec![false; layers.len()];
3039 for (li, l) in layers.iter().enumerate() {
3040 let Some(pk) = pack_for(l, cfg, li) else {
3041 return false;
3042 };
3043 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
3044 return false;
3045 };
3046 let gu_q2 = l
3047 .experts
3048 .first()
3049 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
3050 let attn_ok = [
3051 l.wq_a.model_idx(),
3052 l.wq_b.model_idx(),
3053 l.wo_a.model_idx(),
3054 l.wo_b.model_idx(),
3055 ]
3056 .into_iter()
3057 .flatten()
3058 .all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
3059 on_dev[li] = attn_ok
3060 && pk.globals.len() == cfg.n_routed_experts
3061 && crate::gpu_wgpu::dsv4_experts_ready(
3062 &model,
3063 &pk.tensors,
3064 cfg.moe_inter,
3065 dim,
3066 gu_q2,
3067 l.experts.first().is_some_and(|e| {
3068 e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
3069 }),
3070 );
3071 }
3072 if !on_dev.iter().any(|&x| x) {
3073 return false;
3074 }
3075 let mut sink = vec![0.0f32; dim];
3076 for (li, l) in layers.iter().enumerate() {
3077 if !on_dev[li] {
3081 if !crate::gpu_wgpu::dsv4_state_read(state) {
3082 return false;
3083 }
3084 let freqs = freqs_of(l);
3085 hc_block(
3086 state,
3087 &l.hc_attn_fn,
3088 &l.hc_attn_scale,
3089 &l.hc_attn_base,
3090 &l.attn_norm,
3091 cfg,
3092 scratch,
3093 pool,
3094 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
3095 );
3096 hc_block(
3097 state,
3098 &l.hc_ffn_fn,
3099 &l.hc_ffn_scale,
3100 &l.hc_ffn_base,
3101 &l.ffn_norm,
3102 cfg,
3103 scratch,
3104 pool,
3105 |f, o| moe_step(f, l, cfg, token_id, li, pool, o),
3106 );
3107 let nref = layers.get(li + 1).unwrap_or(l);
3108 let (f, p2, c2) = hc_fold_norm(
3109 state,
3110 &nref.hc_attn_fn,
3111 &nref.hc_attn_scale,
3112 &nref.hc_attn_base,
3113 &nref.attn_norm,
3114 cfg,
3115 pool,
3116 );
3117 folded = f;
3118 if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2)
3119 || !crate::gpu_wgpu::dsv4_state_write(state)
3120 {
3121 return false;
3122 }
3123 continue;
3124 }
3125 let mut prep = AttnPrep::default();
3127 attention_step(
3128 &folded,
3129 l,
3130 cfg,
3131 st,
3132 li,
3133 freqs_of(l),
3134 pool,
3135 Some(&mut prep),
3136 &mut sink,
3137 );
3138 let hd = cfg.head_dim;
3139 let n_comp = st.compressed[li].len() / hd;
3140 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
3141 if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
3142 || (n_comp > 0
3143 && !crate::gpu_wgpu::dsv4_cache_write(
3144 st.kv_id,
3145 li,
3146 cfg.window * hd,
3147 &st.compressed[li],
3148 cap,
3149 ))
3150 {
3151 return false;
3152 }
3153 let _idx32: Vec<u32> = prep
3154 .idxs
3155 .iter()
3156 .map(|&p| {
3157 if p < prep.win_len {
3158 p as u32
3159 } else {
3160 (cfg.window + (p - prep.win_len)) as u32
3161 }
3162 })
3163 .collect();
3164 let nxt = layers.get(li + 1);
3165 let a_tail = crate::gpu_wgpu::Dsv4HcTail {
3166 fn_: &l.hc_ffn_fn,
3167 scale: &l.hc_ffn_scale,
3168 base: &l.hc_ffn_base,
3169 norm: &l.ffn_norm,
3170 hc: cfg.hc_mult,
3171 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3172 hc_eps: cfg.hc_eps,
3173 eps: cfg.norm_eps,
3174 };
3175 let scale = (cfg.head_dim as f32).powf(-0.5);
3176 if !attn_frame(
3177 l,
3178 cfg,
3179 st,
3180 li,
3181 &folded,
3182 &prep.qr,
3183 &prep.idxs,
3184 freqs_of(l),
3185 st.pos,
3186 prep.win_len,
3187 scale,
3188 Some(&a_tail),
3189 &mut [],
3190 ) {
3191 return false;
3192 }
3193 let m_tail = nxt.map(|n| crate::gpu_wgpu::Dsv4HcTail {
3194 fn_: &n.hc_attn_fn,
3195 scale: &n.hc_attn_scale,
3196 base: &n.hc_attn_base,
3197 norm: &n.attn_norm,
3198 hc: cfg.hc_mult,
3199 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3200 hc_eps: cfg.hc_eps,
3201 eps: cfg.norm_eps,
3202 });
3203 let mut next = vec![0.0f32; dim];
3204 let pair = m_tail
3205 .as_ref()
3206 .zip(nxt)
3207 .map(|(t, n)| (t, n.attn_norm.as_slice()));
3208 let forced = l
3209 .tid2eid
3210 .as_ref()
3211 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
3212 if moe_frame(
3213 &[],
3214 l,
3215 cfg,
3216 li,
3217 &[],
3218 forced.as_deref(),
3219 pool,
3220 Some(&a_tail),
3221 pair,
3222 &mut next,
3223 )
3224 .is_none()
3225 {
3226 return false;
3227 }
3228 folded = next;
3229 }
3230 let _ = scratch;
3231 crate::gpu_wgpu::dsv4_state_read(state)
3232}
3233
3234#[cfg(feature = "gpu")]
3238#[allow(clippy::too_many_arguments)]
3239fn hc_fold_norm(
3240 state: &[f32],
3241 hc_fn: &[f32],
3242 hc_scale: &[f32; 3],
3243 hc_base: &[f32],
3244 norm_w: &[f32],
3245 cfg: &Dsv4Cfg,
3246 pool: Option<&crate::pool::Pool>,
3247) -> (Vec<f32>, Vec<f32>, Vec<f32>) {
3248 let (hc, dim) = (cfg.hc_mult, cfg.dim);
3249 let mix_hc = (2 + hc) * hc;
3250 let mut mixes = vec![0.0f32; mix_hc];
3251 hc_mixes(state, hc_fn, mix_hc, cfg.norm_eps, pool, &mut mixes);
3252 let mut pre = vec![0.0f32; hc];
3253 let mut post = vec![0.0f32; hc];
3254 let mut comb = vec![0.0f32; hc * hc];
3255 hc_split_sinkhorn(
3256 &mixes,
3257 hc_scale,
3258 hc_base,
3259 hc,
3260 cfg.hc_sinkhorn_iters,
3261 cfg.hc_eps,
3262 &mut pre,
3263 &mut post,
3264 &mut comb,
3265 );
3266 let mut folded = vec![0.0f32; dim];
3267 hc_fold(state, &pre, hc, dim, &mut folded);
3268 rms_weighted(&mut folded, norm_w, cfg.norm_eps);
3269 (folded, post, comb)
3272}
3273
3274#[cfg(feature = "gpu")]
3291fn gpu_layer_enabled() -> bool {
3292 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3293 *ON.get_or_init(|| {
3294 std::env::var("CMF_DSV4_GPU_LAYER")
3295 .map(|v| v != "0")
3296 .unwrap_or(true)
3297 && crate::gpu::backend_available()
3298 })
3299}
3300
3301#[cfg(feature = "gpu")]
3305struct Pack {
3306 router: Vec<f32>,
3310 to_slot: Vec<usize>,
3312 remap: Vec<u32>,
3314 globals: Vec<usize>,
3316 tensors: Vec<(usize, usize, usize)>,
3317 bias: Option<Vec<f32>>,
3326}
3327
3328#[cfg(feature = "gpu")]
3329fn pack_freq_order(li: usize, n: usize) -> Option<Vec<usize>> {
3335 use std::collections::HashMap;
3336 use std::sync::OnceLock;
3337 static FREQ: OnceLock<Option<HashMap<(usize, usize), u64>>> = OnceLock::new();
3338 let map = FREQ
3339 .get_or_init(|| {
3340 let path = std::env::var("CMF_DSV4_PACK_FREQ").ok()?;
3341 let text = match std::fs::read_to_string(&path) {
3342 Ok(t) => t,
3343 Err(e) => {
3344 eprintln!("CMF_DSV4_PACK_FREQ={path} не читается ({e}) — порядок по id");
3345 return None;
3346 }
3347 };
3348 let mut m = HashMap::new();
3349 for line in text.lines() {
3350 let mut it = line.split('\t');
3351 if let (Some(l), Some(e), Some(c)) = (it.next(), it.next(), it.next()) {
3352 if let (Ok(l), Ok(e), Ok(c)) =
3353 (l.trim().parse(), e.trim().parse(), c.trim().parse::<u64>())
3354 {
3355 *m.entry((l, e)).or_insert(0) += c;
3356 }
3357 }
3358 }
3359 Some(m)
3360 })
3361 .as_ref()?;
3362 if !(0..n).any(|e| map.contains_key(&(li, e))) {
3363 return None;
3364 }
3365 let mut idx: Vec<usize> = (0..n).collect();
3366 idx.sort_by_key(|&e| {
3367 (
3368 std::cmp::Reverse(map.get(&(li, e)).copied().unwrap_or(0)),
3369 e,
3370 )
3371 });
3372 Some(idx)
3373}
3374
3375#[cfg(feature = "gpu")]
3376fn pack_for(l: &Dsv4Layer, cfg: &Dsv4Cfg, li: usize) -> Option<std::sync::Arc<Pack>> {
3377 use std::collections::HashMap;
3378 use std::sync::{Arc, Mutex, OnceLock};
3379 static CACHE: OnceLock<Mutex<HashMap<(u64, usize, usize), Option<Arc<Pack>>>>> =
3380 OnceLock::new();
3381 let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
3382 let model_uid = l
3388 .experts
3389 .first()
3390 .and_then(|e| e.w1.model_arc())
3391 .map_or(0, |m| m.uid());
3392 let first_expert = l
3398 .experts
3399 .first()
3400 .and_then(|e| e.w1.model_idx())
3401 .unwrap_or(usize::MAX);
3402 let key = (model_uid, li, first_expert);
3403 if let Some(v) = cache.lock().unwrap().get(&key) {
3404 return v.clone();
3405 }
3406 if let Ok(v) = std::env::var("CMF_DSV4_PACK_MAX_LI") {
3413 if v.parse::<usize>().is_ok_and(|max| li > max) {
3414 cache.lock().unwrap().insert(key, None);
3415 return None;
3416 }
3417 }
3418 let build = || -> Option<Arc<Pack>> {
3419 let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
3420 let mut globals = Vec::new();
3421 let mut tensors = Vec::new();
3422 let idx3 = |e: &Dsv4Expert| -> Option<(usize, usize, usize)> {
3423 Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
3424 };
3425 let gu_q2 = l
3430 .experts
3431 .first()
3432 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
3433 if let Some(n) = std::env::var("CMF_DSV4_PACK_MAX")
3442 .ok()
3443 .and_then(|v| v.parse::<usize>().ok())
3444 {
3445 let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
3446 let mut globals = Vec::new();
3447 let mut tensors = Vec::new();
3448 for (gi, e) in l.experts.iter().enumerate().take(n) {
3449 to_slot[gi] = globals.len();
3450 globals.push(gi);
3451 tensors.push(idx3(e)?);
3452 }
3453 tensors.push(idx3(&l.shared)?);
3454 let (rows, cols) = (l.gate.rows(), l.gate.cols());
3455 let mut router = vec![0.0f32; rows * cols];
3456 for r in 0..rows {
3457 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
3458 }
3459 let remap: Vec<u32> = to_slot
3460 .iter()
3461 .map(|&sl| {
3462 if sl == usize::MAX {
3463 u32::MAX
3464 } else {
3465 sl as u32
3466 }
3467 })
3468 .collect();
3469 return Some(Arc::new(Pack {
3470 bias: l
3471 .gate_bias
3472 .as_deref()
3473 .map(|b| globals.iter().map(|&g| b[g]).collect()),
3474 router,
3475 to_slot,
3476 remap,
3477 globals,
3478 tensors,
3479 }));
3480 }
3481 let dn_q2_fit = l
3482 .experts
3483 .first()
3484 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
3485 let room = crate::gpu_wgpu::dsv4_experts_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2_fit)
3486 .saturating_sub(1);
3487 let order =
3496 pack_freq_order(li, l.experts.len()).unwrap_or_else(|| (0..l.experts.len()).collect());
3497 for gi in order {
3498 let e = &l.experts[gi];
3499 if l.mask
3500 .as_deref()
3501 .is_some_and(|m| !m.get(gi).copied().unwrap_or(true))
3502 {
3503 continue;
3504 }
3505 if globals.len() >= room {
3506 break;
3507 }
3508 to_slot[gi] = globals.len();
3509 globals.push(gi);
3510 match idx3(e) {
3511 Some(t) => tensors.push(t),
3512 None => {
3513 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
3514 eprintln!("слой {li}: эксперт {gi} без индексов в каталоге");
3515 }
3516 return None;
3517 }
3518 }
3519 }
3520 if globals.is_empty() {
3521 if room == 0 {
3527 static SAID_ZERO: std::sync::atomic::AtomicBool =
3528 std::sync::atomic::AtomicBool::new(false);
3529 if !SAID_ZERO.swap(true, std::sync::atomic::Ordering::Relaxed) {
3530 tracing::warn!(
3531 "начиная со слоя {li}, в бюджете VRAM не осталось места даже под одного \
3532 эксперта — остальные веса остаются mmap-backed и читаются по требованию"
3533 );
3534 }
3535 } else {
3536 tracing::warn!("слой {li}: маска не оставила ни одного эксперта");
3537 }
3538 return None;
3539 }
3540 tensors.push(idx3(&l.shared)?); let (rows, cols) = (l.gate.rows(), l.gate.cols());
3542 let mut router = vec![0.0f32; rows * cols];
3543 for r in 0..rows {
3544 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
3545 }
3546 let remap: Vec<u32> = to_slot
3547 .iter()
3548 .map(|&sl| {
3549 if sl == usize::MAX {
3550 u32::MAX
3551 } else {
3552 sl as u32
3553 }
3554 })
3555 .collect();
3556 Some(Arc::new(Pack {
3557 bias: l
3558 .gate_bias
3559 .as_deref()
3560 .map(|b| globals.iter().map(|&g| b[g]).collect()),
3561 router,
3562 to_slot,
3563 remap,
3564 globals,
3565 tensors,
3566 }))
3567 };
3568 let v = build();
3569 cache.lock().unwrap().insert(key, v.clone());
3570 v
3571}
3572
3573#[cfg(feature = "gpu")]
3581fn moe_frame(
3582 hidden: &[f32],
3583 l: &Dsv4Layer,
3584 cfg: &Dsv4Cfg,
3585 li: usize,
3586 logits: &[f32],
3587 forced: Option<&[usize]>,
3588 pool: Option<&crate::pool::Pool>,
3589 hc_cur: Option<&crate::gpu_wgpu::Dsv4HcTail>,
3592 hc_next: Option<(&crate::gpu_wgpu::Dsv4HcTail, &[f32])>,
3593 out: &mut [f32],
3594) -> Option<(Vec<f32>, usize)> {
3595 macro_rules! no {
3596 ($($t:tt)*) => {{
3597 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
3598 eprintln!("кадр MoE отклонён: {}", format_args!($($t)*));
3599 }
3600 return None;
3601 }};
3602 }
3603 let Some(pk) = pack_for(l, cfg, li) else {
3604 no!("слой {li}: упаковка экспертов не построена");
3605 };
3606 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
3609 no!("слой {li}: эксперты не отображены из файла");
3610 };
3611 let subset = pk.globals.len() < cfg.n_routed_experts;
3612 let fpack: Option<Vec<usize>> = match forced {
3616 Some(f) if subset => Some(f.to_vec()),
3617 Some(f) => {
3618 let v: Vec<usize> = f.iter().map(|&g| pk.to_slot[g]).collect();
3619 if v.contains(&usize::MAX) {
3620 no!("слой {li}: хеш-слой называет эксперта вне упаковки");
3621 }
3622 Some(v)
3623 }
3624 None => None,
3625 };
3626 let lg: Vec<f32> = if logits.is_empty() || subset {
3633 logits.to_vec()
3634 } else {
3635 pk.globals.iter().map(|&g| logits[g]).collect()
3636 };
3637 let bias: Option<Vec<f32>> = l.gate_bias.as_deref().map(|b| {
3638 if subset {
3639 b.to_vec()
3640 } else {
3641 pk.globals.iter().map(|&g| b[g]).collect()
3642 }
3643 });
3644 let w = crate::gpu_wgpu::Dsv4MoeW {
3645 router: &pk.router,
3646 experts: &pk.tensors,
3647 logits: &lg,
3648 bias: bias.as_deref(),
3649 forced: fpack.as_deref(),
3650 remap: if subset { Some(&pk.remap) } else { None },
3651 };
3652 let g = crate::gpu_wgpu::Dsv4MoeGeom {
3653 hidden: cfg.dim,
3654 inter: cfg.moe_inter,
3655 top_k: cfg.top_k,
3656 route_scale: cfg.route_scale,
3657 swiglu_limit: cfg.swiglu_limit,
3658 gu_q2: l
3659 .experts
3660 .first()
3661 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
3662 };
3663 let mut cold = Vec::new();
3664 let mut cold_x = Vec::new();
3665 if !crate::gpu_wgpu::dsv4_moe_frame(
3666 &model,
3667 &w,
3668 g,
3669 hidden,
3670 &mut cold,
3671 &mut cold_x,
3672 hc_cur,
3673 hc_next,
3674 out,
3675 ) {
3676 return None;
3677 }
3678 if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
3681 let csum: f32 = cold.iter().map(|c| c.1).sum();
3682 eprintln!(
3683 "[холодные] слой {li}: вернулось {} из {} | сумма холодных {csum:.4} | \
3684 route_scale {:.4} | {:?}",
3685 cold.len(),
3686 cfg.top_k,
3687 cfg.route_scale,
3688 &cold[..cold.len().min(3)]
3689 );
3690 }
3691 let mut acc = vec![0.0f32; cfg.dim];
3692 let mut cold_sum = vec![0.0f32; cfg.dim];
3693 let cold_input = if hidden.is_empty() {
3694 cold_x.as_slice()
3695 } else {
3696 hidden
3697 };
3698 for &(gi, wt) in &cold {
3699 let Some(exp) = l.experts.get(gi) else {
3700 continue;
3701 };
3702 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
3708 for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
3709 *o += a;
3710 *sum += a;
3711 }
3712 }
3713 Some((cold_sum, cold.len()))
3714}
3715
3716#[cfg(feature = "gpu")]
3722fn last_grew(now: u64) -> u64 {
3723 use std::sync::atomic::{AtomicU64, Ordering};
3724 static SEEN: AtomicU64 = AtomicU64::new(0);
3725 let was = SEEN.load(Ordering::Relaxed);
3726 if was != now {
3727 SEEN.store(now, Ordering::Relaxed);
3728 compressed_map().lock().unwrap().clear();
3729 return u64::MAX; }
3731 now
3732}
3733
3734#[cfg(feature = "gpu")]
3735fn compressed_map() -> &'static std::sync::Mutex<std::collections::HashMap<(u64, usize), usize>> {
3736 use std::collections::HashMap;
3737 use std::sync::{Mutex, OnceLock};
3738 static W: OnceLock<Mutex<HashMap<(u64, usize), usize>>> = OnceLock::new();
3739 W.get_or_init(|| Mutex::new(HashMap::new()))
3740}
3741
3742#[cfg(feature = "gpu")]
3743fn compressed_written(kv_id: u64, li: usize) -> usize {
3744 compressed_map()
3745 .lock()
3746 .unwrap()
3747 .get(&(kv_id, li))
3748 .copied()
3749 .unwrap_or(0)
3750}
3751
3752#[cfg(feature = "gpu")]
3755fn note_compressed(kv_id: u64, li: usize, n: usize) {
3756 compressed_map().lock().unwrap().insert((kv_id, li), n);
3757}
3758
3759#[cfg(feature = "gpu")]
3760fn gpu_moe2_enabled() -> bool {
3761 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3762 *ON.get_or_init(|| {
3763 std::env::var("CMF_DSV4_GPU_MOE2")
3764 .map(|v| v != "0")
3765 .unwrap_or(true)
3766 && crate::gpu::backend_available()
3767 })
3768}
3769
3770pub fn moe_step(
3771 hidden: &[f32],
3772 l: &Dsv4Layer,
3773 cfg: &Dsv4Cfg,
3774 token_id: u32,
3775 li: usize,
3777 pool: Option<&crate::pool::Pool>,
3778 out: &mut [f32],
3779) {
3780 let _t0 = prof::on().then(std::time::Instant::now);
3781 let _guard = scopeguard_moe(_t0, li);
3782 let mut logits = vec![0.0f32; cfg.n_routed_experts];
3783 l.gate.matvec(hidden, &mut logits, pool);
3784 let (mut idx, mut w) = (Vec::new(), Vec::new());
3785 route(
3786 &logits,
3787 l.gate_bias.as_deref(),
3788 cfg.top_k,
3789 cfg.route_scale,
3790 l.tid2eid
3791 .as_ref()
3792 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id))
3793 .as_deref(),
3794 l.mask.as_deref(),
3795 &mut idx,
3796 &mut w,
3797 );
3798 if route_stats_on() {
3799 record_route(li, 0, cfg.n_routed_experts, &idx);
3800 }
3801 #[cfg(feature = "gpu")]
3805 if gpu_moe2_enabled() && crate::gpu::enabled_here() {
3806 let forced = l
3807 .tid2eid
3808 .as_ref()
3809 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
3810 if moe_frame(
3811 hidden,
3812 l,
3813 cfg,
3814 li,
3815 &logits,
3816 forced.as_deref(),
3817 pool,
3818 None,
3819 None,
3820 out,
3821 )
3822 .is_some()
3823 {
3824 if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
3830 let mut want = vec![0.0f32; out.len()];
3831 let mut acc = vec![0.0f32; cfg.dim];
3832 for (e, &ei) in idx.iter().enumerate() {
3833 let Some(exp) = l.experts.get(ei) else {
3834 continue;
3835 };
3836 run_expert(
3837 hidden,
3838 exp,
3839 cfg,
3840 w.get(e).copied().unwrap_or(0.0),
3841 pool,
3842 &mut acc,
3843 );
3844 for (o, a) in want.iter_mut().zip(&acc) {
3845 *o += a;
3846 }
3847 }
3848 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
3849 for (o, a) in want.iter_mut().zip(&acc) {
3850 *o += a;
3851 }
3852 let num: f32 = want
3853 .iter()
3854 .zip(out.iter())
3855 .map(|(a, b)| (a - b) * (a - b))
3856 .sum();
3857 let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
3858 let rel = (num / den).sqrt();
3859 if rel > 1e-3 {
3860 let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
3861 eprintln!(
3862 "[кадр MoE] слой {li}: расхождение {rel:.3e} | выбрано {} | \
3863 упаковано {packed} из {} | хеш={} | смещение={}",
3864 idx.len(),
3865 cfg.n_routed_experts,
3866 l.tid2eid.is_some(),
3867 l.gate_bias.is_some()
3868 );
3869 }
3870 }
3871 return;
3872 }
3873 }
3874 PICK_TALLY.with(|t| {
3880 if let Some(v) = t.borrow_mut().as_mut() {
3881 v.push((li, idx.to_vec()));
3882 }
3883 });
3884 if dump_path().is_some() {
3885 PICKED.with(|p| {
3886 let mut p = p.borrow_mut();
3887 if p.len() <= li {
3888 p.resize(li + 1, Vec::new());
3889 }
3890 p[li] = idx.clone();
3891 });
3892 }
3893 fn gpu_moe_on() -> bool {
3912 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3913 *ON.get_or_init(|| std::env::var("CMF_DSV4_GPU_MOE").is_ok_and(|v| v != "0"))
3914 }
3915 if gpu_moe_on() && crate::gpu::enabled_here() {
3916 let mut jobs = Vec::with_capacity(idx.len() + 1);
3917 let mut model_ref = None;
3918 let mut ok = true;
3919 for (e, &ei) in idx.iter().enumerate() {
3920 let Some(exp) = l.experts.get(ei) else {
3921 continue;
3922 };
3923 ok &= crate::pipeline::moe_push_job_parts(
3924 &exp.w1,
3925 &exp.w3,
3926 &exp.w2,
3927 hidden,
3928 w.get(e).copied().unwrap_or(0.0),
3929 cfg.swiglu_limit,
3930 &mut jobs,
3931 &mut model_ref,
3932 )
3933 .is_some();
3934 }
3935 ok &= crate::pipeline::moe_push_job_parts(
3936 &l.shared.w1,
3937 &l.shared.w3,
3938 &l.shared.w2,
3939 hidden,
3940 1.0,
3941 cfg.swiglu_limit,
3942 &mut jobs,
3943 &mut model_ref,
3944 )
3945 .is_some();
3946 if ok {
3947 if let Some(m) = model_ref.as_ref() {
3948 if crate::gpu::moe_block(m, &jobs, out) {
3949 if std::env::var("CMF_DSV4_GPU_CHECK").is_ok() {
3954 let mut want = vec![0.0f32; out.len()];
3955 let mut acc = vec![0.0f32; cfg.dim];
3956 for (e, &ei) in idx.iter().enumerate() {
3957 let Some(exp) = l.experts.get(ei) else {
3958 continue;
3959 };
3960 run_expert(
3961 hidden,
3962 exp,
3963 cfg,
3964 w.get(e).copied().unwrap_or(0.0),
3965 pool,
3966 &mut acc,
3967 );
3968 for (o, a) in want.iter_mut().zip(&acc) {
3969 *o += a;
3970 }
3971 }
3972 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
3973 for (o, a) in want.iter_mut().zip(&acc) {
3974 *o += a;
3975 }
3976 let num: f32 = want
3977 .iter()
3978 .zip(out.iter())
3979 .map(|(a, b)| (a - b) * (a - b))
3980 .sum();
3981 let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
3982 eprintln!(
3983 "[dsv4-gpu] слой {li}: расхождение {:.3e} | |CPU|={:.5} |GPU|={:.5} | экспертов {}",
3984 (num / den).sqrt(),
3985 den.sqrt(),
3986 out.iter().map(|x| x * x).sum::<f32>().sqrt(),
3987 jobs.len()
3988 );
3989 }
3990 return;
3991 }
3992 }
3993 }
3994 }
3995 out.fill(0.0);
3996 let mut acc = vec![0.0f32; cfg.dim];
3997 for (e, &ei) in idx.iter().enumerate() {
3998 let Some(exp) = l.experts.get(ei) else {
3999 continue;
4000 };
4001 run_expert(
4002 hidden,
4003 exp,
4004 cfg,
4005 w.get(e).copied().unwrap_or(0.0),
4006 pool,
4007 &mut acc,
4008 );
4009 for (o, a) in out.iter_mut().zip(&acc) {
4010 *o += a;
4011 }
4012 }
4013 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
4015 for (o, a) in out.iter_mut().zip(&acc) {
4016 *o += a;
4017 }
4018}
4019
4020fn run_expert(
4023 x: &[f32],
4024 e: &Dsv4Expert,
4025 cfg: &Dsv4Cfg,
4026 weight: f32,
4027 pool: Option<&crate::pool::Pool>,
4028 out: &mut [f32],
4029) {
4030 expert_swiglu(
4031 x,
4032 &|src, dst| e.w1.matvec(src, dst, pool),
4033 &|src, dst| e.w3.matvec(src, dst, pool),
4034 &|src, dst| e.w2.matvec(src, dst, pool),
4035 cfg.moe_inter,
4036 weight,
4037 cfg.swiglu_limit,
4038 out,
4039 );
4040}
4041
4042fn moe_step_block(
4047 xs: &[f32],
4048 b: usize,
4049 l: &Dsv4Layer,
4050 cfg: &Dsv4Cfg,
4051 token_ids: &[u32],
4052 tally_layer: usize,
4053 pool: Option<&crate::pool::Pool>,
4054 out: &mut [f32],
4055) {
4056 let (dim, inter) = (cfg.dim, cfg.moe_inter);
4057 debug_assert_eq!(xs.len(), b * dim);
4058 debug_assert_eq!(out.len(), b * dim);
4059 out.fill(0.0);
4060
4061 let mut logits = vec![0.0f32; b * cfg.n_routed_experts];
4062 l.gate.matmat(xs, b, &mut logits, pool);
4063 let mut picks: Vec<Vec<usize>> = Vec::with_capacity(b);
4064 let mut weights: Vec<Vec<f32>> = Vec::with_capacity(b);
4065 for bi in 0..b {
4066 let mut idx = Vec::new();
4067 let mut wt = Vec::new();
4068 let forced = l.tid2eid.as_ref().map(|tbl| {
4069 hash_route(
4070 tbl,
4071 cfg.vocab,
4072 cfg.top_k,
4073 token_ids.get(bi).copied().unwrap_or(0),
4074 )
4075 });
4076 route(
4077 &logits[bi * cfg.n_routed_experts..(bi + 1) * cfg.n_routed_experts],
4078 l.gate_bias.as_deref(),
4079 cfg.top_k,
4080 cfg.route_scale,
4081 forced.as_deref(),
4082 l.mask.as_deref(),
4083 &mut idx,
4084 &mut wt,
4085 );
4086 PICK_TALLY.with(|t| {
4087 if let Some(v) = t.borrow_mut().as_mut() {
4088 v.push((tally_layer, idx.clone()));
4089 }
4090 });
4091 picks.push(idx);
4092 weights.push(wt);
4093 }
4094
4095 let mut routed = vec![0.0f32; b * cfg.top_k * dim];
4098 for ei in 0..l.experts.len() {
4099 let mut jobs = Vec::new();
4100 for bi in 0..b {
4101 for (slot, &picked) in picks[bi].iter().enumerate() {
4102 if picked == ei {
4103 jobs.push((bi, slot, weights[bi][slot]));
4104 }
4105 }
4106 }
4107 if jobs.is_empty() {
4108 continue;
4109 }
4110 let e = &l.experts[ei];
4111 let n = jobs.len();
4112 let mut xj = vec![0.0f32; n * dim];
4113 for (j, &(bi, _, _)) in jobs.iter().enumerate() {
4114 xj[j * dim..(j + 1) * dim].copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
4115 }
4116 let mut gate = vec![0.0f32; n * inter];
4117 let mut up = vec![0.0f32; n * inter];
4118 e.w1.matmat(&xj, n, &mut gate, pool);
4119 e.w3.matmat(&xj, n, &mut up, pool);
4120 for (j, &(_, _, wt)) in jobs.iter().enumerate() {
4121 let (gj, uj) = (
4122 &mut gate[j * inter..(j + 1) * inter],
4123 &mut up[j * inter..(j + 1) * inter],
4124 );
4125 if cfg.swiglu_limit > 0.0 {
4126 for u in uj.iter_mut() {
4127 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
4128 }
4129 for g in gj.iter_mut() {
4130 *g = g.min(cfg.swiglu_limit);
4131 }
4132 }
4133 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
4134 *g = (*g / (1.0 + (-*g).exp())) * u * wt;
4135 }
4136 }
4137 let mut down = vec![0.0f32; n * dim];
4138 e.w2.matmat(&gate, n, &mut down, pool);
4139 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
4140 routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
4141 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
4142 }
4143 }
4144
4145 let mut sg = vec![0.0f32; b * inter];
4148 let mut su = vec![0.0f32; b * inter];
4149 l.shared.w1.matmat(xs, b, &mut sg, pool);
4150 l.shared.w3.matmat(xs, b, &mut su, pool);
4151 for bi in 0..b {
4152 let (gj, uj) = (
4153 &mut sg[bi * inter..(bi + 1) * inter],
4154 &mut su[bi * inter..(bi + 1) * inter],
4155 );
4156 if cfg.swiglu_limit > 0.0 {
4157 for u in uj.iter_mut() {
4158 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
4159 }
4160 for g in gj.iter_mut() {
4161 *g = g.min(cfg.swiglu_limit);
4162 }
4163 }
4164 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
4165 *g = (*g / (1.0 + (-*g).exp())) * u;
4166 }
4167 }
4168 let mut shared = vec![0.0f32; b * dim];
4169 l.shared.w2.matmat(&sg, b, &mut shared, pool);
4170
4171 for bi in 0..b {
4172 let dst = &mut out[bi * dim..(bi + 1) * dim];
4173 for slot in 0..picks[bi].len() {
4174 let src = &routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim];
4175 for (o, &v) in dst.iter_mut().zip(src) {
4176 *o += v;
4177 }
4178 }
4179 for (o, &v) in dst.iter_mut().zip(&shared[bi * dim..(bi + 1) * dim]) {
4180 *o += v;
4181 }
4182 }
4183}
4184
4185fn o_project_block(
4190 attn: &[f32],
4191 b: usize,
4192 wo_a: &crate::qtensor::QTensor,
4193 wo_b: &crate::qtensor::QTensor,
4194 groups: usize,
4195 lora: usize,
4196 pool: Option<&crate::pool::Pool>,
4197 out: &mut [f32],
4198) {
4199 let attn_len = attn.len() / b;
4200 let per_group = attn_len / groups;
4201 let rows = groups * lora;
4202 let mut mid = vec![0.0f32; b * rows];
4203 let mid_addr = crate::pool::SendMut::new(mid.as_mut_ptr());
4204 let run = |start: usize, end: usize| {
4205 let mut wr = vec![0.0f32; wo_a.cols()];
4206 for r in start..end {
4207 wo_a.row_f32(r, &mut wr);
4208 let group = r / lora;
4209 for bi in 0..b {
4210 let x = &attn
4211 [bi * attn_len + group * per_group..bi * attn_len + (group + 1) * per_group];
4212 let v = wr.iter().zip(x).map(|(w, x)| w * x).sum();
4213 unsafe { *mid_addr.at(bi * rows + r) = v };
4214 }
4215 }
4216 };
4217 match pool {
4218 Some(p) if rows >= 256 => p.run_rows(rows, &run),
4219 _ => run(0, rows),
4220 }
4221 wo_b.matmat(&mid, b, out, pool);
4222}
4223
4224fn no_compressed() -> bool {
4230 static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4231 *OFF.get_or_init(|| std::env::var("CMF_DSV4_NO_COMPRESSED").is_ok_and(|v| v != "0"))
4232}
4233
4234fn trace_on() -> bool {
4235 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4236 *ON.get_or_init(|| std::env::var("CMF_DSV4_TRACE").is_ok_and(|v| v != "0"))
4237}
4238
4239fn rms_of(v: &[f32]) -> f32 {
4240 if v.is_empty() {
4241 return 0.0;
4242 }
4243 (v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32).sqrt()
4244}
4245
4246thread_local! {
4252 static BODY: std::cell::RefCell<Vec<String>> = const { std::cell::RefCell::new(Vec::new()) };
4254 static PICKED: std::cell::RefCell<Vec<Vec<usize>>> =
4258 const { std::cell::RefCell::new(Vec::new()) };
4259 static PICK_TALLY: std::cell::RefCell<Option<Vec<(usize, Vec<usize>)>>> =
4261 const { std::cell::RefCell::new(None) };
4262}
4263
4264pub fn pick_tally_arm() {
4266 PICK_TALLY.with(|t| *t.borrow_mut() = Some(Vec::new()));
4267}
4268
4269pub fn pick_tally_take() -> Vec<(usize, Vec<usize>)> {
4271 PICK_TALLY.with(|t| t.borrow_mut().take().unwrap_or_default())
4272}
4273
4274pub fn tally_unique(picks: &[(usize, Vec<usize>)]) -> (usize, usize) {
4277 let mut seen = std::collections::HashSet::new();
4282 let mut total = 0;
4283 for (li, v) in picks {
4284 total += v.len();
4285 for &e in v {
4286 seen.insert((*li, e));
4287 }
4288 }
4289 (seen.len(), total)
4290}
4291
4292fn dump_path() -> Option<&'static str> {
4293 static P: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
4294 P.get_or_init(|| std::env::var("CMF_DSV4_DUMP").ok())
4295 .as_deref()
4296}
4297
4298fn dump_line(json: &str) {
4299 if let Some(p) = dump_path() {
4300 use std::io::Write as _;
4301 if let Ok(mut f) = std::fs::OpenOptions::new()
4302 .create(true)
4303 .append(true)
4304 .open(p)
4305 {
4306 let _ = writeln!(f, "{json}");
4307 }
4308 }
4309}
4310
4311fn vec_json(v: &[f32]) -> String {
4312 let mut s = String::with_capacity(v.len() * 9);
4313 s.push('[');
4314 for (i, x) in v.iter().enumerate() {
4315 if i > 0 {
4316 s.push(',');
4317 }
4318 s.push_str(&format!("{x:.6e}"));
4319 }
4320 s.push(']');
4321 s
4322}
4323
4324#[allow(clippy::too_many_arguments)]
4332#[allow(clippy::too_many_arguments)]
4342fn batch_prefill() -> usize {
4346 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
4347 *N.get_or_init(|| {
4348 std::env::var("CMF_DSV4_BATCH")
4349 .ok()
4350 .and_then(|v| v.parse::<usize>().ok())
4351 .filter(|&n| (1..=32).contains(&n))
4352 .unwrap_or(1)
4353 })
4354}
4355
4356#[allow(clippy::too_many_arguments)]
4369fn forward_chunk_batched(
4370 g: &Dsv4Globals,
4371 layers: &[Dsv4Layer],
4372 cfg: &Dsv4Cfg,
4373 st: &mut Dsv4State,
4374 ids: &[u32],
4375 pos0: usize,
4376 inv_freq: &[f32],
4377 pool: Option<&crate::pool::Pool>,
4378 logits: &mut Vec<f32>,
4379 want_logits: bool,
4380) -> bool {
4381 #[cfg(not(feature = "gpu"))]
4382 {
4383 let _ = (
4384 g,
4385 layers,
4386 cfg,
4387 st,
4388 ids,
4389 pos0,
4390 inv_freq,
4391 pool,
4392 logits,
4393 want_logits,
4394 );
4395 false
4396 }
4397 #[cfg(feature = "gpu")]
4398 {
4399 let b = ids.len();
4400 let gpu_end = st
4405 .dev_set
4406 .iter()
4407 .enumerate()
4408 .position(|(li, &on)| {
4409 !on || pack_for(&layers[li], cfg, li)
4410 .is_none_or(|p| p.globals.len() < cfg.n_routed_experts)
4411 })
4412 .unwrap_or(st.dev_set.len());
4413 let why = if b < 2 {
4414 "токенов меньше двух"
4415 } else if !chain_enabled() {
4416 "цепочка выключена"
4417 } else if !st.dev_owned {
4418 "карта ещё не владеет состоянием"
4419 } else if st.dev_set.len() != layers.len() {
4420 "набор слоёв ещё не зафиксирован"
4421 } else if gpu_end == 0
4422 || st.dev_set[gpu_end.min(st.dev_set.len())..]
4423 .iter()
4424 .enumerate()
4425 .any(|(i, &on)| on && !st.partial_set.get(gpu_end + i).copied().unwrap_or(false))
4426 {
4427 "слои на карте не образуют префикс"
4428 } else {
4429 ""
4430 };
4431 if !why.is_empty() {
4432 static SAID: std::sync::Once = std::sync::Once::new();
4433 SAID.call_once(|| tracing::warn!("dsv4: пакет отказал — {why}"));
4434 return false;
4435 }
4436 let (hc, dim) = (cfg.hc_mult, cfg.dim);
4437 let mut emb = vec![0.0f32; dim];
4438 for (t, &id) in ids.iter().enumerate() {
4439 let mut state = vec![0.0f32; hc * dim];
4440 g.embed.row_f32(id as usize, &mut emb);
4441 for j in 0..hc {
4442 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
4443 }
4444 let (folded, post0, comb0) = hc_fold_norm(
4445 &state,
4446 &layers[0].hc_attn_fn,
4447 &layers[0].hc_attn_scale,
4448 &layers[0].hc_attn_base,
4449 &layers[0].attn_norm,
4450 cfg,
4451 pool,
4452 );
4453 let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
4454 layers[0].wq_a.matvec(&folded, &mut qn0, pool);
4455 rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
4456 if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
4457 || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
4458 || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
4459 || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
4460 {
4461 return false;
4462 }
4463 }
4464 let run: Vec<usize> = (0..gpu_end).collect();
4465 let mut folded = Vec::new();
4466 let mut states = vec![0.0f32; b * hc * dim];
4467 st.pos = pos0;
4468 if !dsv4_chain_run(
4469 layers,
4470 &run,
4471 cfg,
4472 g,
4473 st,
4474 *ids.last().unwrap(),
4475 &mut folded,
4476 Some(&mut states),
4477 b,
4478 ids,
4479 true,
4480 pool,
4481 ) {
4482 return false;
4483 }
4484 let mut scratch = HcScratch::new(cfg);
4490 for t in 0..b {
4491 st.pos = pos0 + t;
4492 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
4493 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
4494 let freqs = if l.compressor.is_some() {
4495 &g.inv_freq_compress
4496 } else {
4497 &g.inv_freq_window
4498 };
4499 let freqs = if freqs.is_empty() {
4500 inv_freq
4501 } else {
4502 freqs.as_slice()
4503 };
4504 hc_block(
4505 state,
4506 &l.hc_attn_fn,
4507 &l.hc_attn_scale,
4508 &l.hc_attn_base,
4509 &l.attn_norm,
4510 cfg,
4511 &mut scratch,
4512 pool,
4513 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
4514 );
4515 hc_block(
4516 state,
4517 &l.hc_ffn_fn,
4518 &l.hc_ffn_scale,
4519 &l.hc_ffn_base,
4520 &l.ffn_norm,
4521 cfg,
4522 &mut scratch,
4523 pool,
4524 |f, o| {
4525 if host_cpu_moe() {
4526 crate::gpu::cpu_scope(|| moe_step(f, l, cfg, ids[t], li, pool, o))
4527 } else {
4528 moe_step(f, l, cfg, ids[t], li, pool, o)
4529 }
4530 },
4531 );
4532 dspark_note(li, state, cfg);
4533 }
4534 }
4535 st.pos = pos0 + b;
4536 {
4541 static SAID: std::sync::Once = std::sync::Once::new();
4542 SAID.call_once(|| tracing::warn!("dsv4: префилл пакетами по {b}"));
4543 }
4544 if want_logits {
4549 let last = &states[(b - 1) * hc * dim..b * hc * dim];
4550 let mut h = vec![0.0f32; dim];
4551 hc_head_fold(
4552 last,
4553 &g.hc_head_fn,
4554 g.hc_head_scale,
4555 &g.hc_head_base,
4556 cfg,
4557 pool,
4558 &mut h,
4559 );
4560 rms_weighted(&mut h, &g.norm, cfg.norm_eps);
4561 logits.resize(cfg.vocab, 0.0);
4562 g.head.matvec(&h, logits, pool);
4563 } else {
4564 logits.clear();
4565 }
4566 true
4567 }
4568}
4569
4570#[cfg(feature = "gpu")]
4578pub struct Dsv4SpecTxn {
4579 pos0: usize,
4580 batch: usize,
4581 gpu_end: usize,
4582 dev_filled: Vec<usize>,
4583 dev_n_comp: Vec<usize>,
4584 dev_n_ix: Vec<usize>,
4585 host: Vec<(usize, HostLayerSnap)>,
4586 host_steps: Vec<(usize, Vec<HostLayerSnap>)>,
4591 pub states: Vec<f32>,
4595 shadow: Option<crate::gpu_wgpu::Dsv4SpecShadow>,
4596}
4597
4598#[cfg(feature = "gpu")]
4599struct HostLayerSnap {
4600 window: Vec<f32>,
4601 compressed: Vec<f32>,
4602 index_kv: Vec<f32>,
4603 pending_kv: Vec<f32>,
4604 pending_score: Vec<f32>,
4605 prev_kv: Vec<f32>,
4606 prev_score: Vec<f32>,
4607 pending_ix_kv: Vec<f32>,
4608 pending_ix_score: Vec<f32>,
4609 prev_ix_kv: Vec<f32>,
4610 prev_ix_score: Vec<f32>,
4611}
4612
4613#[cfg(feature = "gpu")]
4614fn host_snap(st: &Dsv4State, li: usize) -> HostLayerSnap {
4615 HostLayerSnap {
4616 window: st.window[li].clone(),
4617 compressed: st.compressed[li].clone(),
4618 index_kv: st.index_kv[li].clone(),
4619 pending_kv: st.pending_kv[li].clone(),
4620 pending_score: st.pending_score[li].clone(),
4621 prev_kv: st.prev_kv[li].clone(),
4622 prev_score: st.prev_score[li].clone(),
4623 pending_ix_kv: st.pending_ix_kv[li].clone(),
4624 pending_ix_score: st.pending_ix_score[li].clone(),
4625 prev_ix_kv: st.prev_ix_kv[li].clone(),
4626 prev_ix_score: st.prev_ix_score[li].clone(),
4627 }
4628}
4629
4630#[cfg(feature = "gpu")]
4631fn host_restore(st: &mut Dsv4State, li: usize, s: &HostLayerSnap) {
4632 st.window[li] = s.window.clone();
4633 st.compressed[li] = s.compressed.clone();
4634 st.index_kv[li] = s.index_kv.clone();
4635 st.pending_kv[li] = s.pending_kv.clone();
4636 st.pending_score[li] = s.pending_score.clone();
4637 st.prev_kv[li] = s.prev_kv.clone();
4638 st.prev_score[li] = s.prev_score.clone();
4639 st.pending_ix_kv[li] = s.pending_ix_kv.clone();
4640 st.pending_ix_score[li] = s.pending_ix_score.clone();
4641 st.prev_ix_kv[li] = s.prev_ix_kv.clone();
4642 st.prev_ix_score[li] = s.prev_ix_score.clone();
4643}
4644
4645#[cfg(feature = "gpu")]
4649#[allow(clippy::too_many_arguments)]
4650fn host_tail_walk(
4651 g: &Dsv4Globals,
4652 layers: &[Dsv4Layer],
4653 cfg: &Dsv4Cfg,
4654 st: &mut Dsv4State,
4655 gpu_end: usize,
4656 state: &mut [f32],
4657 token_id: u32,
4658 pos: usize,
4659 inv_freq: &[f32],
4660 scratch: &mut HcScratch,
4661 pool: Option<&crate::pool::Pool>,
4662) {
4663 st.pos = pos;
4664 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
4665 let freqs = if l.compressor.is_some() {
4666 &g.inv_freq_compress
4667 } else {
4668 &g.inv_freq_window
4669 };
4670 let freqs = if freqs.is_empty() {
4671 inv_freq
4672 } else {
4673 freqs.as_slice()
4674 };
4675 hc_block(
4676 state,
4677 &l.hc_attn_fn,
4678 &l.hc_attn_scale,
4679 &l.hc_attn_base,
4680 &l.attn_norm,
4681 cfg,
4682 scratch,
4683 pool,
4684 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
4685 );
4686 hc_block(
4687 state,
4688 &l.hc_ffn_fn,
4689 &l.hc_ffn_scale,
4690 &l.hc_ffn_base,
4691 &l.ffn_norm,
4692 cfg,
4693 scratch,
4694 pool,
4695 |f, o| {
4696 if host_cpu_moe() {
4697 crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
4698 } else {
4699 moe_step(f, l, cfg, token_id, li, pool, o)
4700 }
4701 },
4702 );
4703 dspark_note(li, state, cfg);
4704 }
4705}
4706
4707#[cfg(feature = "gpu")]
4713#[allow(clippy::too_many_arguments)]
4714fn host_tail_walk_batch(
4715 g: &Dsv4Globals,
4716 layers: &[Dsv4Layer],
4717 cfg: &Dsv4Cfg,
4718 st: &mut Dsv4State,
4719 gpu_end: usize,
4720 states: &mut [f32],
4721 ids: &[u32],
4722 pos0: usize,
4723 b: usize,
4724 inv_freq: &[f32],
4725 scratch: &mut HcScratch,
4726 pool: Option<&crate::pool::Pool>,
4727 mut steps: Option<&mut Vec<(usize, Vec<HostLayerSnap>)>>,
4728) {
4729 let (hc, dim) = (cfg.hc_mult, cfg.dim);
4730 let mix_hc = (2 + hc) * hc;
4731 let mut folds = vec![0.0f32; b * dim];
4732 let mut mo = vec![0.0f32; b * dim];
4733 let mut posts = vec![0.0f32; b * hc];
4734 let mut combs = vec![0.0f32; b * hc * hc];
4735 let mut resid = vec![0.0f32; b * hc * dim];
4736 let spec_time = {
4737 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4738 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
4739 };
4740 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
4741 let t_attn = std::time::Instant::now();
4742 let freqs = if l.compressor.is_some() {
4743 &g.inv_freq_compress
4744 } else {
4745 &g.inv_freq_window
4746 };
4747 let freqs = if freqs.is_empty() {
4748 inv_freq
4749 } else {
4750 freqs.as_slice()
4751 };
4752 for t in 0..b {
4753 st.pos = pos0 + t;
4754 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
4755 hc_block(
4756 state,
4757 &l.hc_attn_fn,
4758 &l.hc_attn_scale,
4759 &l.hc_attn_base,
4760 &l.attn_norm,
4761 cfg,
4762 scratch,
4763 pool,
4764 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
4765 );
4766 if let Some(steps) = steps.as_mut() {
4767 match steps.iter_mut().find(|(l, _)| *l == li) {
4768 Some((_, v)) => v.push(host_snap(st, li)),
4769 None => steps.push((li, vec![host_snap(st, li)])),
4770 }
4771 }
4772 }
4773 let t_glue = std::time::Instant::now();
4774 for t in 0..b {
4775 let state = &states[t * hc * dim..(t + 1) * hc * dim];
4776 hc_mixes(
4777 state,
4778 &l.hc_ffn_fn,
4779 mix_hc,
4780 cfg.norm_eps,
4781 pool,
4782 &mut scratch.mixes,
4783 );
4784 hc_split_sinkhorn(
4785 &scratch.mixes,
4786 &l.hc_ffn_scale,
4787 &l.hc_ffn_base,
4788 hc,
4789 cfg.hc_sinkhorn_iters,
4790 cfg.hc_eps,
4791 &mut scratch.pre,
4792 &mut posts[t * hc..(t + 1) * hc],
4793 &mut combs[t * hc * hc..(t + 1) * hc * hc],
4794 );
4795 let fold = &mut folds[t * dim..(t + 1) * dim];
4796 hc_fold(state, &scratch.pre, hc, dim, fold);
4797 let ms = fold.iter().map(|v| v * v).sum::<f32>() / dim as f32;
4798 let inv = 1.0 / (ms + cfg.norm_eps).sqrt();
4799 for (v, w) in fold.iter_mut().zip(&l.ffn_norm) {
4800 *v = *v * inv * w;
4801 }
4802 resid[t * hc * dim..(t + 1) * hc * dim]
4803 .copy_from_slice(&states[t * hc * dim..(t + 1) * hc * dim]);
4804 }
4805 let t_moe = std::time::Instant::now();
4806 let tail_pack = {
4812 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4813 *ON.get_or_init(|| {
4814 std::env::var("CMF_DSV4_TAIL_PACK")
4815 .map(|v| v != "0")
4816 .unwrap_or(true)
4817 })
4818 };
4819 let mut packed_done = false;
4820 if tail_pack && pack_for(l, cfg, li).is_some() {
4821 packed_done = true;
4822 for t in 0..b {
4823 let f = &folds[t * dim..(t + 1) * dim];
4824 let forced = l.tid2eid.as_ref().map(|tbl| {
4825 hash_route(tbl, cfg.vocab, cfg.top_k, ids.get(t).copied().unwrap_or(0))
4826 });
4827 let o = &mut mo[t * dim..(t + 1) * dim];
4828 match moe_frame(f, l, cfg, li, &[], forced.as_deref(), pool, None, None, o) {
4829 Some((cold_sum, n)) => {
4830 if n > 0 {
4831 for (od, cd) in o.iter_mut().zip(cold_sum.iter()) {
4832 *od += cd;
4833 }
4834 }
4835 }
4836 None => {
4837 packed_done = false;
4838 break;
4839 }
4840 }
4841 }
4842 }
4843 if !packed_done {
4844 if host_cpu_moe() {
4845 crate::gpu::cpu_scope(|| moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo));
4846 } else {
4847 moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo);
4848 }
4849 }
4850 let t_exp = std::time::Instant::now();
4851 for t in 0..b {
4852 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
4853 hc_expand(
4854 &mo[t * dim..(t + 1) * dim],
4855 &resid[t * hc * dim..(t + 1) * hc * dim],
4856 &posts[t * hc..(t + 1) * hc],
4857 &combs[t * hc * hc..(t + 1) * hc * hc],
4858 hc,
4859 dim,
4860 state,
4861 );
4862 dspark_note(li, state, cfg);
4863 }
4864 if spec_time {
4865 eprintln!(
4866 "хвост слоя {li}: attn {:.1} мс, клей {:.1}, moe {:.1}, expand {:.1}",
4867 (t_glue - t_attn).as_secs_f64() * 1e3,
4868 (t_moe - t_glue).as_secs_f64() * 1e3,
4869 (t_exp - t_moe).as_secs_f64() * 1e3,
4870 t_exp.elapsed().as_secs_f64() * 1e3,
4871 );
4872 }
4873 }
4874}
4875
4876#[cfg(feature = "gpu")]
4885#[allow(clippy::too_many_arguments)]
4886pub fn dsv4_verify_chunk(
4887 g: &Dsv4Globals,
4888 layers: &[Dsv4Layer],
4889 cfg: &Dsv4Cfg,
4890 st: &mut Dsv4State,
4891 ids: &[u32],
4892 pos0: usize,
4893 inv_freq: &[f32],
4894 pool: Option<&crate::pool::Pool>,
4895 cap_targets: &[usize],
4896 argmax_out: &mut Vec<u32>,
4897 logits_out: &mut Vec<f32>,
4898 walked_out: &mut Vec<f32>,
4899) -> Option<Dsv4SpecTxn> {
4900 let b = ids.len();
4901 let gpu_end = st
4902 .dev_set
4903 .iter()
4904 .enumerate()
4905 .position(|(li, &on)| {
4906 !on || pack_for(&layers[li], cfg, li)
4907 .is_none_or(|p| p.globals.len() < cfg.n_routed_experts)
4908 })
4909 .unwrap_or(st.dev_set.len());
4910 let full_beyond = st.dev_set[gpu_end.min(st.dev_set.len())..]
4914 .iter()
4915 .enumerate()
4916 .any(|(i, &on)| on && !st.partial_set.get(gpu_end + i).copied().unwrap_or(false));
4917 if b < 2
4918 || !chain_enabled()
4919 || !st.dev_owned
4920 || st.dev_set.len() != layers.len()
4921 || gpu_end == 0
4922 || full_beyond
4923 {
4924 return None;
4925 }
4926 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
4927 let metas: Vec<(usize, usize, usize, usize)> = (0..gpu_end)
4929 .map(|li| (li, hd, cfg.window, st.dev_filled[li]))
4930 .collect();
4931 let shadow = crate::gpu_wgpu::dsv4_spec_shadow(st.kv_id, &metas, b)?;
4932 let mut txn = Dsv4SpecTxn {
4933 pos0,
4934 batch: b,
4935 gpu_end,
4936 dev_filled: st.dev_filled.clone(),
4937 dev_n_comp: st.dev_n_comp.clone(),
4938 dev_n_ix: st.dev_n_ix.clone(),
4939 host: (gpu_end..layers.len())
4940 .map(|li| (li, host_snap(st, li)))
4941 .collect(),
4942 states: Vec::new(),
4943 host_steps: Vec::new(),
4944 shadow: Some(shadow),
4945 };
4946 let dev_caps: Vec<usize> = cap_targets
4948 .iter()
4949 .copied()
4950 .filter(|&t| t < gpu_end)
4951 .collect();
4952 crate::gpu_wgpu::dsv4_spec_retain_arm(gpu_end, &dev_caps);
4953
4954 let mut emb = vec![0.0f32; dim];
4956 for (t, &id) in ids.iter().enumerate() {
4957 let mut state = vec![0.0f32; hc * dim];
4958 g.embed.row_f32(id as usize, &mut emb);
4959 for j in 0..hc {
4960 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
4961 }
4962 let (folded, post0, comb0) = hc_fold_norm(
4963 &state,
4964 &layers[0].hc_attn_fn,
4965 &layers[0].hc_attn_scale,
4966 &layers[0].hc_attn_base,
4967 &layers[0].attn_norm,
4968 cfg,
4969 pool,
4970 );
4971 let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
4972 layers[0].wq_a.matvec(&folded, &mut qn0, pool);
4973 rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
4974 if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
4975 || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
4976 || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
4977 || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
4978 {
4979 crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
4980 return None;
4981 }
4982 }
4983 let spec_time = {
4984 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4985 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
4986 };
4987 let t0 = std::time::Instant::now();
4988 let run: Vec<usize> = (0..gpu_end).collect();
4989 let mut folded = Vec::new();
4990 let mut states = vec![0.0f32; b * hc * dim];
4991 st.pos = pos0;
4992 let ok = dsv4_chain_run(
4993 layers,
4994 &run,
4995 cfg,
4996 g,
4997 st,
4998 *ids.last().unwrap(),
4999 &mut folded,
5000 Some(&mut states),
5001 b,
5002 ids,
5003 true,
5004 pool,
5005 );
5006 crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
5007 if !ok {
5008 if let Some(sh) = txn.shadow.take() {
5011 let _ = crate::gpu_wgpu::dsv4_spec_restore(&sh);
5012 }
5013 st.dev_filled = txn.dev_filled;
5014 st.dev_n_comp = txn.dev_n_comp;
5015 st.dev_n_ix = txn.dev_n_ix;
5016 st.pos = pos0;
5017 return None;
5018 }
5019 txn.states = states.clone();
5020 let t_chain = t0.elapsed();
5021 if std::env::var("CMF_DSV4_FOLD_DBG").is_ok() {
5022 for li in 0..gpu_end {
5026 let Some(ixr) = &layers[li].indexer else {
5027 continue;
5028 };
5029 let ratio = ixr.compressor.ratio;
5030 for t in 0..b {
5031 if (pos0 + t + 1) % ratio == 0 {
5032 let ew = {
5033 let w = ixr.compressor.wkv.rows();
5034 if ixr.compressor.overlap { w / 2 } else { w }
5035 };
5036 let idx_new = txn.dev_n_ix[li]
5037 + (0..=t).filter(|k| (pos0 + k + 1) % ratio == 0).count()
5038 - 1;
5039 if let Some(v) =
5040 crate::gpu_wgpu::dsv4_dbg_read_ix(st.kv_id, li, idx_new * ew, ew.min(8))
5041 {
5042 let sum: f32 = v.iter().sum();
5043 eprintln!(
5044 "[fold] li={li} pos={} entry={idx_new} head={:?} sum={sum:.6}",
5045 pos0 + t,
5046 &v[..4.min(v.len())]
5047 );
5048 }
5049 }
5050 }
5051 }
5052 }
5053
5054 let mut scratch = HcScratch::new(cfg);
5056 argmax_out.clear();
5057 logits_out.clear();
5058 logits_out.resize(b * cfg.vocab, 0.0);
5059 let mut head_in = vec![0.0f32; b * dim];
5060 let mut host_steps: Vec<(usize, Vec<HostLayerSnap>)> = Vec::new();
5061 host_tail_walk_batch(
5062 g,
5063 layers,
5064 cfg,
5065 st,
5066 gpu_end,
5067 &mut states,
5068 ids,
5069 pos0,
5070 b,
5071 inv_freq,
5072 &mut scratch,
5073 pool,
5074 Some(&mut host_steps),
5075 );
5076 txn.host_steps = host_steps;
5077 for t in 0..b {
5078 let state = &states[t * hc * dim..(t + 1) * hc * dim];
5079 let h = &mut head_in[t * dim..(t + 1) * dim];
5080 hc_head_fold(
5081 state,
5082 &g.hc_head_fn,
5083 g.hc_head_scale,
5084 &g.hc_head_base,
5085 cfg,
5086 pool,
5087 h,
5088 );
5089 rms_weighted(h, &g.norm, cfg.norm_eps);
5090 }
5091 let head_gpu = g.head.model_idx().is_some_and(|hi| {
5093 let model = layers[0].experts.first().and_then(|e| e.w1.model_arc());
5094 model.is_some_and(|m| {
5095 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
5096 &m, hi, &head_in, b, cfg.vocab, dim, logits_out,
5097 )
5098 })
5099 });
5100 for t in 0..b {
5101 if !head_gpu {
5102 let h = &head_in[t * dim..(t + 1) * dim];
5103 g.head
5104 .matvec(h, &mut logits_out[t * cfg.vocab..(t + 1) * cfg.vocab], pool);
5105 }
5106 let row = &logits_out[t * cfg.vocab..(t + 1) * cfg.vocab];
5107 let mut best = 0usize;
5108 for v in 1..cfg.vocab {
5109 if row[v] > row[best] {
5110 best = v;
5111 }
5112 }
5113 argmax_out.push(best as u32);
5114 }
5115 walked_out.clear();
5116 walked_out.extend_from_slice(&states);
5117 st.pos = pos0 + b;
5118 if spec_time {
5119 eprintln!(
5120 "verify: тень+сид+цепочка {:.1} мс, хвост+голова {:.1} мс",
5121 t_chain.as_secs_f64() * 1e3,
5122 (t0.elapsed() - t_chain).as_secs_f64() * 1e3,
5123 );
5124 }
5125 Some(txn)
5126}
5127
5128#[cfg(feature = "gpu")]
5136pub fn dsv4_spec_finish(
5137 g: &Dsv4Globals,
5138 layers: &[Dsv4Layer],
5139 cfg: &Dsv4Cfg,
5140 st: &mut Dsv4State,
5141 mut txn: Dsv4SpecTxn,
5142 accepted: usize,
5143 ids: &[u32],
5144 inv_freq: &[f32],
5145 pool: Option<&crate::pool::Pool>,
5146) -> bool {
5147 macro_rules! sfail {
5148 ($($t:tt)*) => {{
5149 if std::env::var("CMF_DSV4_SPEC_DEBUG").is_ok() {
5150 eprintln!("spec_finish: {}", format_args!($($t)*));
5151 }
5152 return false;
5153 }};
5154 }
5155 let b = txn.batch;
5156 let k = accepted.min(b);
5157 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
5158 let win_metas: Vec<(usize, usize, usize, usize)> = (0..txn.gpu_end)
5161 .map(|li| (li, txn.dev_filled[li], cfg.window, hd))
5162 .collect();
5163 if !crate::gpu_wgpu::dsv4_spec_commit_windows(st.kv_id, &win_metas, b, k) {
5164 sfail!("коммит окон");
5165 }
5166 if k == b {
5167 return true;
5170 }
5171 let Some(sh) = txn.shadow.take() else {
5173 sfail!("нет тени")
5174 };
5175 if !crate::gpu_wgpu::dsv4_spec_restore(&sh) {
5176 sfail!("restore");
5177 }
5178 let Some(model) = layers[0].experts.first().and_then(|e| e.w1.model_arc()) else {
5179 sfail!("нет модели");
5180 };
5181 let mut plan: Vec<(usize, crate::gpu_wgpu::Dsv4Prep)> = Vec::new();
5182 let mut freqs_own: Vec<&[f32]> = Vec::new();
5183 for li in 0..txn.gpu_end {
5184 let l = &layers[li];
5185 let Some(wkv) = l.wkv.model_idx() else {
5186 sfail!("wkv слоя {li}")
5187 };
5188 let comp = match &l.compressor {
5189 None => None,
5190 Some(cp) => {
5191 let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
5192 sfail!("компрессор слоя {li}");
5193 };
5194 Some((
5195 crate::gpu_wgpu::Dsv4CompW {
5196 wkv: a,
5197 wgate: bx,
5198 norm: &cp.norm,
5199 ape: &cp.ape,
5200 },
5201 crate::gpu_wgpu::Dsv4CompGeom {
5202 width: cp.wkv.rows(),
5203 hidden: dim,
5204 ratio: cp.ratio,
5205 overlap: cp.overlap,
5206 rope_dim: cfg.rope_head_dim,
5207 eps: cfg.norm_eps,
5208 },
5209 ))
5210 }
5211 };
5212 let ix = match &l.indexer {
5213 None => None,
5214 Some(ixr) => {
5215 let cp = &ixr.compressor;
5216 let (Some(a), Some(bx), Some(qb), Some(wp)) = (
5217 cp.wkv.model_idx(),
5218 cp.wgate.model_idx(),
5219 ixr.wq_b.model_idx(),
5220 ixr.weights_proj.model_idx(),
5221 ) else {
5222 sfail!("индексер слоя {li}");
5223 };
5224 let ih = ixr.weights_proj.rows();
5225 Some((
5226 crate::gpu_wgpu::Dsv4CompW {
5227 wkv: a,
5228 wgate: bx,
5229 norm: &cp.norm,
5230 ape: &cp.ape,
5231 },
5232 crate::gpu_wgpu::Dsv4CompGeom {
5233 width: cp.wkv.rows(),
5234 hidden: dim,
5235 ratio: cp.ratio,
5236 overlap: cp.overlap,
5237 rope_dim: cfg.rope_head_dim,
5238 eps: cfg.norm_eps,
5239 },
5240 crate::gpu_wgpu::Dsv4IxW {
5241 wq_b: qb,
5242 weights_proj: wp,
5243 },
5244 crate::gpu_wgpu::Dsv4IxGeom {
5245 ih,
5246 idim: ixr.wq_b.rows() / ih.max(1),
5247 q_lora: cfg.q_lora_rank,
5248 hidden: dim,
5249 rope_dim: cfg.rope_head_dim,
5250 eps: cfg.norm_eps,
5251 top_k: cfg.index_topk,
5252 window: cfg.window,
5253 },
5254 ))
5255 }
5256 };
5257 let ew_c = comp.as_ref().map_or(
5258 0,
5259 |(_, cg)| {
5260 if cg.overlap { cg.width / 2 } else { cg.width }
5261 },
5262 );
5263 let ew_i = ix.as_ref().map_or(
5264 0,
5265 |(_, cg, _, _)| {
5266 if cg.overlap { cg.width / 2 } else { cg.width }
5267 },
5268 );
5269 let prep = crate::gpu_wgpu::Dsv4Prep {
5270 wkv,
5271 kv_norm: &l.kv_norm,
5272 comp,
5273 ix,
5274 filled: txn.dev_filled[li],
5275 window: cfg.window,
5276 n_comp: txn.dev_n_comp[li],
5277 n_ix: txn.dev_n_ix[li],
5278 comp_dst_off: cfg.window * hd + txn.dev_n_comp[li] * ew_c,
5279 ix_dst_off: txn.dev_n_ix[li] * ew_i,
5280 idx_cap: cfg.window
5281 + if l.indexer.is_some() {
5282 cfg.index_topk
5283 } else {
5284 0
5285 },
5286 };
5287 let fr = if l.compressor.is_some() {
5288 g.inv_freq_compress.as_slice()
5289 } else {
5290 g.inv_freq_window.as_slice()
5291 };
5292 freqs_own.push(if fr.is_empty() { inv_freq } else { fr });
5293 plan.push((li, prep));
5294 }
5295 if !crate::gpu_wgpu::dsv4_spec_replay(
5296 &model,
5297 &plan,
5298 st.kv_id,
5299 txn.pos0,
5300 b,
5301 k,
5302 &freqs_own,
5303 hd,
5304 dim,
5305 cfg.rope_head_dim,
5306 cfg.norm_eps,
5307 true,
5308 ) {
5309 sfail!("replay k={k}");
5310 }
5311 let advanced = |ratio: usize| -> usize {
5313 if ratio == 0 {
5314 return 0;
5315 }
5316 (0..k).filter(|t| (txn.pos0 + t + 1) % ratio == 0).count()
5317 };
5318 for li in 0..txn.gpu_end {
5319 let l = &layers[li];
5320 st.dev_filled[li] = (txn.dev_filled[li] + k).min(cfg.window);
5321 let ac = l.compressor.as_ref().map_or(0, |cp| advanced(cp.ratio));
5322 let ai = l
5323 .indexer
5324 .as_ref()
5325 .map_or(0, |ix| advanced(ix.compressor.ratio));
5326 st.dev_n_comp[li] = txn.dev_n_comp[li] + ac;
5327 st.dev_n_ix[li] = txn.dev_n_ix[li] + ai;
5328 note_compressed(st.kv_id, li, st.dev_n_comp[li]);
5329 }
5330 if k >= 1 && txn.host_steps.iter().all(|(_, v)| v.len() >= k) && !txn.host_steps.is_empty() {
5333 for (li, v) in &txn.host_steps {
5334 host_restore(st, *li, &v[k - 1]);
5335 }
5336 } else {
5337 for (li, snap) in &txn.host {
5338 host_restore(st, *li, snap);
5339 }
5340 let mut scratch = HcScratch::new(cfg);
5341 let mut states = txn.states.clone();
5342 host_tail_walk_batch(
5343 g,
5344 layers,
5345 cfg,
5346 st,
5347 txn.gpu_end,
5348 &mut states[..k * hc * dim],
5349 ids,
5350 txn.pos0,
5351 k,
5352 inv_freq,
5353 &mut scratch,
5354 pool,
5355 None,
5356 );
5357 }
5358 st.pos = txn.pos0 + k;
5359 true
5360}
5361
5362pub fn forward_chunk(
5363 g: &Dsv4Globals,
5364 layers: &[Dsv4Layer],
5365 cfg: &Dsv4Cfg,
5366 st: &mut Dsv4State,
5367 ids: &[u32],
5368 pos0: usize,
5369 inv_freq: &[f32],
5370 pool: Option<&crate::pool::Pool>,
5371 logits: &mut Vec<f32>,
5372 want_logits: bool,
5373) {
5374 let bs = batch_prefill();
5375 if bs > 1 {
5376 let mut i = 0;
5383 if !st.dev_owned && !ids.is_empty() {
5384 st.pos = pos0;
5385 forward_token_inner(
5386 g,
5387 layers,
5388 cfg,
5389 st,
5390 ids[0],
5391 inv_freq,
5392 pool,
5393 logits,
5394 ids.len() == 1,
5395 );
5396 i = 1;
5397 }
5398 while i < ids.len() {
5399 let end = (i + bs).min(ids.len());
5400 st.pos = pos0 + i;
5401 if !forward_chunk_batched(
5402 g,
5403 layers,
5404 cfg,
5405 st,
5406 &ids[i..end],
5407 pos0 + i,
5408 inv_freq,
5409 pool,
5410 logits,
5411 want_logits && end == ids.len(),
5412 ) {
5413 break;
5414 }
5415 i = end;
5416 }
5417 if i == ids.len() {
5418 return;
5419 }
5420 for (k, &id) in ids.iter().enumerate().skip(i) {
5422 st.pos = pos0 + k;
5423 let last = want_logits && k + 1 == ids.len();
5424 forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
5425 }
5426 return;
5427 }
5428 for (i, &id) in ids.iter().enumerate() {
5429 st.pos = pos0 + i;
5430 let last = want_logits && i + 1 == ids.len();
5431 forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
5432 }
5433}
5434
5435pub fn forward_token(
5436 g: &Dsv4Globals,
5437 layers: &[Dsv4Layer],
5438 cfg: &Dsv4Cfg,
5439 st: &mut Dsv4State,
5440 token_id: u32,
5441 inv_freq: &[f32],
5442 pool: Option<&crate::pool::Pool>,
5443 logits: &mut Vec<f32>,
5444) {
5445 forward_token_inner(g, layers, cfg, st, token_id, inv_freq, pool, logits, true);
5446}
5447
5448#[allow(clippy::too_many_arguments)]
5449fn forward_token_inner(
5450 g: &Dsv4Globals,
5451 layers: &[Dsv4Layer],
5452 cfg: &Dsv4Cfg,
5453 st: &mut Dsv4State,
5454 token_id: u32,
5455 inv_freq: &[f32],
5456 pool: Option<&crate::pool::Pool>,
5457 logits: &mut Vec<f32>,
5458 want_logits: bool,
5460) {
5461 let _t_all = prof::on().then(std::time::Instant::now);
5462 let _all_guard = Charge(_t_all, &prof::ALL_NS);
5463 let (hc, dim) = (cfg.hc_mult, cfg.dim);
5464
5465 let mut emb = vec![0.0f32; dim];
5467 g.embed.row_f32(token_id as usize, &mut emb);
5468 let mut state = vec![0.0f32; hc * dim];
5469 for j in 0..hc {
5470 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
5471 }
5472
5473 let mut scratch = HcScratch::new(cfg);
5474 let mut dump: Vec<String> = Vec::new();
5475 if dump_path().is_some() {
5476 dump.push(format!("\"embed\":{}", vec_json(&emb)));
5477 PICKED.with(|p| p.borrow_mut().clear());
5478 BODY.with(|b| b.borrow_mut().clear());
5479 dump.push(",\"layers\":[".into());
5480 }
5481 if trace_on() {
5482 eprintln!(
5483 "[dsv4] tok={token_id} pos={} embed rms={:.5}",
5484 st.pos,
5485 rms_of(&emb)
5486 );
5487 }
5488 #[cfg(feature = "gpu")]
5490 let layer_frames = gpu_layer_enabled()
5491 && dsv4_layer_loop(
5492 &mut state,
5493 layers,
5494 g,
5495 cfg,
5496 st,
5497 token_id,
5498 inv_freq,
5499 pool,
5500 &mut scratch,
5501 );
5502 #[cfg(not(feature = "gpu"))]
5503 let layer_frames = false;
5504
5505 #[cfg(feature = "gpu")]
5512 let hc_dev = hc_on_device()
5513 && !layer_frames
5514 && gpu_attn_enabled()
5515 && gpu_moe2_enabled()
5516 && dump_path().is_none();
5517 #[cfg(not(feature = "gpu"))]
5518 let hc_dev = false;
5519 #[cfg(feature = "gpu")]
5526 let two_frame_done = hc_dev
5527 && dsv4_two_frame_loop(
5528 &mut state,
5529 layers,
5530 g,
5531 cfg,
5532 st,
5533 token_id,
5534 inv_freq,
5535 pool,
5536 &mut scratch,
5537 );
5538 #[cfg(not(feature = "gpu"))]
5539 let two_frame_done = false;
5540 if !two_frame_done {
5541 for (li, l) in layers.iter().enumerate() {
5542 if layer_frames {
5543 break;
5544 }
5545 hc_block(
5547 &mut state,
5548 &l.hc_attn_fn,
5549 &l.hc_attn_scale,
5550 &l.hc_attn_base,
5551 &l.attn_norm,
5552 cfg,
5553 &mut scratch,
5554 pool,
5555 |folded, out| {
5556 if dump_path().is_some() {
5557 BODY.with(|b| b.borrow_mut().push(vec_json(folded)));
5560 }
5561 let freqs = if l.compressor.is_some() {
5563 &g.inv_freq_compress
5564 } else {
5565 &g.inv_freq_window
5566 };
5567 let freqs = if freqs.is_empty() {
5568 inv_freq
5569 } else {
5570 freqs.as_slice()
5571 };
5572 attention_step(folded, l, cfg, st, li, freqs, pool, None, out);
5573 if dump_path().is_some() {
5574 BODY.with(|b| b.borrow_mut().push(vec_json(out)));
5575 }
5576 },
5577 );
5578 if dump_path().is_some() {
5579 dump.push(format!(
5582 "{}{}",
5583 if li == 0 { "" } else { "," },
5584 vec_json(&state)
5585 ));
5586 }
5587 let _t_hc2 = prof::on().then(std::time::Instant::now);
5589 hc_block(
5590 &mut state,
5591 &l.hc_ffn_fn,
5592 &l.hc_ffn_scale,
5593 &l.hc_ffn_base,
5594 &l.ffn_norm,
5595 cfg,
5596 &mut scratch,
5597 pool,
5598 |folded, out| moe_step(folded, l, cfg, token_id, li, pool, out),
5599 );
5600 if let Some(t) = _t_hc2 {
5601 prof::HC_NS.fetch_add(
5604 t.elapsed().as_nanos() as u64,
5605 std::sync::atomic::Ordering::Relaxed,
5606 );
5607 }
5608 if dump_path().is_some() {
5609 dump.push(format!(",{}", vec_json(&state)));
5610 }
5611 if trace_on() && (st.pos % 64 == 0 || st.pos == 199) {
5612 eprintln!(
5613 "[dsv4] кеши слоя {li}: окно={} сжатых={} индекс={} (ratio={:?})",
5614 st.window[li].len() / cfg.head_dim.max(1),
5615 st.compressed[li].len() / cfg.head_dim.max(1),
5616 st.index_kv[li].len().max(1) / 128,
5617 l.compressor.as_ref().map(|c| (c.ratio, c.overlap)),
5618 );
5619 }
5620 if trace_on() {
5621 let bad = state.iter().filter(|v| !v.is_finite()).count();
5622 eprintln!(
5623 "[dsv4] layer {li:>2}: rms={:.5}{}",
5624 rms_of(&state),
5625 if bad > 0 {
5626 format!(" NON-FINITE x{bad}")
5627 } else {
5628 String::new()
5629 }
5630 );
5631 }
5632 dspark_note(li, &state, cfg);
5633 }
5634 }
5635 st.pos += 1;
5636
5637 let mut h = vec![0.0f32; dim];
5639 hc_head_fold(
5640 &state,
5641 &g.hc_head_fn,
5642 g.hc_head_scale,
5643 &g.hc_head_base,
5644 cfg,
5645 pool,
5646 &mut h,
5647 );
5648 if !want_logits {
5649 logits.clear();
5650 return;
5651 }
5652 let _t_head = prof::on().then(std::time::Instant::now);
5653 rms_weighted(&mut h, &g.norm, cfg.norm_eps);
5654 logits.clear();
5655 logits.resize(g.head.rows(), 0.0);
5656 g.head.matvec(&h, logits, pool);
5657 if let Some(t) = _t_head {
5658 prof::HEAD_NS.fetch_add(
5659 t.elapsed().as_nanos() as u64,
5660 std::sync::atomic::Ordering::Relaxed,
5661 );
5662 }
5663 if dump_path().is_some() {
5664 dump.push("]".into());
5665 let picked = PICKED.with(|p| {
5666 p.borrow()
5667 .iter()
5668 .map(|v| {
5669 format!(
5670 "[{}]",
5671 v.iter()
5672 .map(|e| e.to_string())
5673 .collect::<Vec<_>>()
5674 .join(",")
5675 )
5676 })
5677 .collect::<Vec<_>>()
5678 .join(",")
5679 });
5680 dump.push(format!(",\"experts\":[{picked}]"));
5681 let body = BODY.with(|b| b.borrow().join(","));
5682 dump.push(format!(",\"attn_io\":[{body}]"));
5683 dump_line(&format!(
5684 "{{\"tok\":{token_id},\"pos\":{},{},\"head\":{},\"logits\":{}}}",
5685 st.pos - 1,
5686 dump.join(""),
5687 vec_json(&h),
5688 vec_json(logits)
5689 ));
5690 }
5691 if trace_on() {
5692 let (mut top, mut best) = (0usize, f32::NEG_INFINITY);
5693 for (i, &v) in logits.iter().enumerate() {
5694 if v > best {
5695 best = v;
5696 top = i;
5697 }
5698 }
5699 let lo = logits.iter().cloned().fold(f32::MAX, f32::min);
5700 eprintln!(
5701 "[dsv4] head: rms={:.5} logits[{}..{:.3}] argmax={top}",
5702 rms_of(&h),
5703 format_args!("{lo:.3}"),
5704 best
5705 );
5706 }
5707}
5708
5709pub fn load(
5716 model: &std::sync::Arc<cortiq_core::CmfModel>,
5717 cfg: &Dsv4Cfg,
5718 n_layers: usize,
5719) -> Result<(Dsv4Globals, Vec<Dsv4Layer>), String> {
5720 let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
5721 crate::qtensor::QTensor::from_model(model, name)
5722 };
5723 let f = |name: &str| -> Result<Vec<f32>, String> {
5727 crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
5728 };
5729
5730 let rope_of = |base: f32, yarn: bool| -> Vec<f32> {
5735 if yarn {
5736 crate::attention::yarn_inv_freq(cfg.rope_head_dim, base, 16.0, 65536, 32.0, 1.0)
5737 } else {
5738 crate::attention::rope_inv_freq(cfg.rope_head_dim, base)
5739 }
5740 };
5741 let globals = Dsv4Globals {
5742 inv_freq_compress: rope_of(160_000.0, true),
5743 inv_freq_window: rope_of(10_000.0, false),
5744 embed: q("model.embed_tokens.weight")?,
5745 norm: f("model.norm.weight")?,
5746 head: q("lm_head.weight")?,
5747 hc_head_fn: f("model.hc_head_fn")?,
5748 hc_head_base: f("model.hc_head_base")?,
5749 hc_head_scale: *f("model.hc_head_scale")?
5750 .first()
5751 .ok_or("dsv4: empty hc_head_scale")?,
5752 };
5753
5754 let mut layers = Vec::with_capacity(n_layers);
5755 for li in 0..n_layers {
5756 layers.push(load_layer(
5757 model,
5758 cfg,
5759 &format!("model.layers.{li}"),
5760 Scheme::Main,
5761 )?);
5762 }
5763 Ok((globals, layers))
5764}
5765
5766#[derive(Clone, Copy, PartialEq, Eq, Debug)]
5773pub enum Scheme {
5774 Main,
5775 Mtp,
5776}
5777
5778impl Scheme {
5779 fn attn(self) -> &'static str {
5780 match self {
5781 Scheme::Main => "self_attn",
5782 Scheme::Mtp => "attn",
5783 }
5784 }
5785 fn attn_norm(self) -> &'static str {
5786 match self {
5787 Scheme::Main => "input_layernorm.weight",
5788 Scheme::Mtp => "attn_norm.weight",
5789 }
5790 }
5791 fn ffn_norm(self) -> &'static str {
5792 match self {
5793 Scheme::Main => "post_attention_layernorm.weight",
5794 Scheme::Mtp => "ffn_norm.weight",
5795 }
5796 }
5797 fn mlp(self) -> &'static str {
5798 match self {
5799 Scheme::Main => "mlp",
5800 Scheme::Mtp => "ffn",
5801 }
5802 }
5803 fn gate_bias(self) -> &'static str {
5806 match self {
5807 Scheme::Main => "expert_bias",
5808 Scheme::Mtp => "gate.bias",
5809 }
5810 }
5811 fn shared(self) -> &'static str {
5812 match self {
5813 Scheme::Main => "shared_expert",
5814 Scheme::Mtp => "shared_experts",
5815 }
5816 }
5817 fn w(self, i: u8) -> &'static str {
5819 match (self, i) {
5820 (Scheme::Main, 1) => "gate_proj.weight",
5821 (Scheme::Main, 2) => "down_proj.weight",
5822 (Scheme::Main, _) => "up_proj.weight",
5823 (Scheme::Mtp, 1) => "w1.weight",
5824 (Scheme::Mtp, 2) => "w2.weight",
5825 (Scheme::Mtp, _) => "w3.weight",
5826 }
5827 }
5828}
5829
5830pub fn load_layer(
5832 model: &std::sync::Arc<cortiq_core::CmfModel>,
5833 cfg: &Dsv4Cfg,
5834 p: &str,
5835 s: Scheme,
5836) -> Result<Dsv4Layer, String> {
5837 let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
5838 crate::qtensor::QTensor::from_model(model, name)
5839 };
5840 let f = |name: &str| -> Result<Vec<f32>, String> {
5841 crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
5842 };
5843 let opt_f = |name: &str| -> Option<Vec<f32>> { f(name).ok() };
5844 let at = s.attn();
5845 let ml = s.mlp();
5846 {
5847 let scale3 = |name: &str| -> Result<[f32; 3], String> {
5848 let v = f(name)?;
5849 if v.len() < 3 {
5850 return Err(format!("{name}: expected 3 scales, got {}", v.len()));
5851 }
5852 Ok([v[0], v[1], v[2]])
5853 };
5854 let compressor = match q(&format!("{p}.{at}.compressor.wkv.weight")) {
5857 Ok(wkv) => {
5858 let ape = f(&format!("{p}.{at}.compressor.ape"))?;
5859 let width = wkv.rows();
5862 let ratio = (ape.len() / width.max(1)).max(1);
5863 Some(Dsv4Compressor {
5864 wkv,
5865 wgate: q(&format!("{p}.{at}.compressor.wgate.weight"))?,
5866 norm: f(&format!("{p}.{at}.compressor.norm.weight"))?,
5867 ape,
5868 ratio,
5869 overlap: ratio == 4,
5870 })
5871 }
5872 Err(_) => None,
5873 };
5874 let indexer = match q(&format!("{p}.{at}.indexer.wq_b.weight")) {
5875 Ok(wq_b) => {
5876 let ape = f(&format!("{p}.{at}.indexer.compressor.ape"))?;
5877 let cwkv = q(&format!("{p}.{at}.indexer.compressor.wkv.weight"))?;
5878 let width = cwkv.rows();
5879 let ratio = (ape.len() / width.max(1)).max(1);
5880 Some(Dsv4Indexer {
5881 wq_b,
5882 weights_proj: q(&format!("{p}.{at}.indexer.weights_proj.weight"))?,
5883 compressor: Dsv4Compressor {
5884 wkv: cwkv,
5885 wgate: q(&format!("{p}.{at}.indexer.compressor.wgate.weight"))?,
5886 norm: f(&format!("{p}.{at}.indexer.compressor.norm.weight"))?,
5887 ape,
5888 ratio,
5889 overlap: ratio == 4,
5890 },
5891 })
5892 }
5893 Err(_) => None,
5894 };
5895
5896 let mut experts = Vec::with_capacity(cfg.n_routed_experts);
5897 for e in 0..cfg.n_routed_experts {
5898 let ep = format!("{p}.{ml}.experts.{e}");
5899 experts.push(Dsv4Expert {
5900 w1: q(&format!("{ep}.{w}", w = s.w(1)))?,
5901 w2: q(&format!("{ep}.{w}", w = s.w(2)))?,
5902 w3: q(&format!("{ep}.{w}", w = s.w(3)))?,
5903 });
5904 }
5905
5906 Ok(Dsv4Layer {
5907 attn_norm: f(&format!("{p}.{an}", an = s.attn_norm()))?,
5908 ffn_norm: f(&format!("{p}.{fnm}", fnm = s.ffn_norm()))?,
5909 wq_a: q(&format!("{p}.{at}.wq_a.weight"))?,
5910 q_norm: f(&format!("{p}.{at}.q_norm.weight"))?,
5911 wq_b: q(&format!("{p}.{at}.wq_b.weight"))?,
5912 wkv: q(&format!("{p}.{at}.wkv.weight"))?,
5913 kv_norm: f(&format!("{p}.{at}.kv_norm.weight"))?,
5914 wo_a: q(&format!("{p}.{at}.wo_a.weight"))?,
5915 wo_b: q(&format!("{p}.{at}.wo_b.weight"))?,
5916 attn_sink: f(&format!("{p}.{at}.attn_sink"))?,
5917 compressor,
5918 indexer,
5919 hc_attn_fn: f(&format!("{p}.hc_attn_fn"))?,
5920 hc_attn_base: f(&format!("{p}.hc_attn_base"))?,
5921 hc_attn_scale: scale3(&format!("{p}.hc_attn_scale"))?,
5922 hc_ffn_fn: f(&format!("{p}.hc_ffn_fn"))?,
5923 hc_ffn_base: f(&format!("{p}.hc_ffn_base"))?,
5924 hc_ffn_scale: scale3(&format!("{p}.hc_ffn_scale"))?,
5925 gate: q(&format!("{p}.{ml}.gate.weight"))?,
5926 gate_bias: opt_f(&format!("{p}.{ml}.{b}", b = s.gate_bias())),
5929 tid2eid: opt_f(&format!("{p}.{ml}.tid2eid")),
5930 experts,
5931 mask: if model.tensor(&format!("{p}.{ml}.tid2eid")).is_some() {
5932 None
5933 } else {
5934 crate::loader::moe_task_mask(&format!("{p}."), cfg.n_routed_experts)
5935 },
5936 shared: Dsv4Expert {
5937 w1: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(1)))?,
5938 w2: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(2)))?,
5939 w3: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(3)))?,
5940 },
5941 })
5942 }
5943}
5944
5945pub struct Dsv4Mtp {
5954 pub layer: Dsv4Layer,
5955 pub main_proj: Option<crate::qtensor::QTensor>,
5959 pub main_norm: Option<Vec<f32>>,
5960 pub norm: Option<Vec<f32>>,
5962 pub hc_head_fn: Option<Vec<f32>>,
5963 pub hc_head_base: Option<Vec<f32>>,
5964 pub hc_head_scale: Option<f32>,
5965 pub confidence: Option<crate::qtensor::QTensor>,
5966 pub markov_w1: Option<crate::qtensor::QTensor>,
5971 pub markov_w2: Option<crate::qtensor::QTensor>,
5972}
5973
5974pub fn load_mtp(
5978 model: &std::sync::Arc<cortiq_core::CmfModel>,
5979 cfg: &Dsv4Cfg,
5980 max_depth: usize,
5981) -> Vec<Dsv4Mtp> {
5982 let f = |name: &str| -> Option<Vec<f32>> {
5983 crate::loader::load_f32(model, name, &crate::loader::Overlay::None).ok()
5984 };
5985 let mut out = Vec::new();
5986 for d in 0..max_depth {
5987 let p = format!("model.mtp.{d}");
5988 if model.tensor(&format!("{p}.attn.wq_a.weight")).is_none() {
5992 break;
5993 }
5994 let layer = match load_layer(model, cfg, &p, Scheme::Mtp) {
5995 Ok(l) => l,
5996 Err(e) => {
5997 eprintln!("MTP {d}: пропущен, {e}");
5998 break;
5999 }
6000 };
6001 out.push(Dsv4Mtp {
6002 layer,
6003 main_proj: crate::qtensor::QTensor::from_model(model, &format!("{p}.main_proj.weight"))
6004 .ok(),
6005 main_norm: f(&format!("{p}.main_norm.weight")),
6006 norm: f(&format!("{p}.norm.weight")),
6007 hc_head_fn: f(&format!("{p}.hc_head_fn")),
6008 hc_head_base: f(&format!("{p}.hc_head_base")),
6009 hc_head_scale: f(&format!("{p}.hc_head_scale")).and_then(|v| v.first().copied()),
6010 confidence: crate::qtensor::QTensor::from_model(
6011 model,
6012 &format!("{p}.confidence_head.proj.weight"),
6013 )
6014 .ok(),
6015 markov_w1: crate::qtensor::QTensor::from_model(
6016 model,
6017 &format!("{p}.markov_head.markov_w1.weight"),
6018 )
6019 .ok(),
6020 markov_w2: crate::qtensor::QTensor::from_model(
6021 model,
6022 &format!("{p}.markov_head.markov_w2.weight"),
6023 )
6024 .ok(),
6025 });
6026 }
6027 dspark_apply_mask(&mut out);
6028 if !out.is_empty() {
6029 let mp = out
6030 .iter()
6031 .find_map(|m| m.main_proj.as_ref())
6032 .map(|t| format!("[{}, {}]", t.rows(), t.cols()))
6033 .unwrap_or_else(|| "нет".into());
6034 eprintln!(
6035 "MTP: {} стади(я/и/й), main_proj {mp}, экспертов {}, \
6036 голова уверенности {}, марков {}",
6037 out.len(),
6038 out[0].layer.experts.len(),
6039 if out.iter().any(|m| m.confidence.is_some()) {
6040 "есть"
6041 } else {
6042 "нет"
6043 },
6044 if out.iter().any(|m| m.markov_w1.is_some()) {
6045 "есть"
6046 } else {
6047 "нет"
6048 },
6049 );
6050 }
6051 out
6052}
6053
6054#[cfg(test)]
6055mod tests {
6056 use super::*;
6057
6058 fn toy() -> (Dsv4Globals, Vec<Dsv4Layer>, Dsv4Cfg) {
6063 use crate::qtensor::QTensor;
6064 let cfg = Dsv4Cfg {
6065 dim: 32,
6066 n_heads: 4,
6067 head_dim: 8,
6068 rope_head_dim: 4,
6069 q_lora_rank: 16,
6070 o_lora_rank: 16,
6071 o_groups: 2,
6072 hc_mult: 4,
6073 hc_sinkhorn_iters: 20,
6074 hc_eps: 1e-6,
6075 norm_eps: 1e-6,
6076 n_routed_experts: 8,
6077 top_k: 2,
6078 moe_inter: 16,
6079 route_scale: 1.0,
6080 swiglu_limit: 10.0,
6081 window: 6,
6082 index_topk: 8,
6083 vocab: 24,
6084 };
6085 let w = |n: usize, seed: usize| -> Vec<f32> {
6088 (0..n)
6089 .map(|i| (((i * 7 + seed * 13) % 101) as f32 / 101.0 - 0.5) * 0.3)
6090 .collect()
6091 };
6092 let t = |rows: usize, cols: usize, seed: usize| {
6093 QTensor::from_f32(w(rows * cols, seed), rows, cols)
6094 };
6095 let ones = |n: usize| vec![1.0f32; n];
6096
6097 let (dim, hc) = (cfg.dim, cfg.hc_mult);
6098 let q_width = cfg.n_heads * cfg.head_dim;
6101 let kv_width = cfg.head_dim;
6102 let o_per_group = q_width / cfg.o_groups;
6103 let mut layers = Vec::new();
6104 for li in 0..2 {
6105 let experts: Vec<Dsv4Expert> = (0..cfg.n_routed_experts)
6106 .map(|e| Dsv4Expert {
6107 w1: t(cfg.moe_inter, dim, 40 + e + li * 8),
6108 w2: t(dim, cfg.moe_inter, 60 + e + li * 8),
6109 w3: t(cfg.moe_inter, dim, 80 + e + li * 8),
6110 })
6111 .collect();
6112 layers.push(Dsv4Layer {
6115 attn_norm: ones(dim),
6116 ffn_norm: ones(dim),
6117 wq_a: t(cfg.q_lora_rank, dim, 1 + li),
6118 q_norm: ones(cfg.q_lora_rank),
6119 wq_b: t(q_width, cfg.q_lora_rank, 3 + li),
6120 wkv: t(kv_width, dim, 5 + li),
6121 kv_norm: ones(kv_width),
6122 wo_a: t(cfg.o_groups * cfg.o_lora_rank, o_per_group, 7 + li),
6123 wo_b: t(dim, cfg.o_groups * cfg.o_lora_rank, 9 + li),
6124 attn_sink: vec![0.1; cfg.n_heads],
6125 compressor: if li == 1 {
6128 Some(Dsv4Compressor {
6129 wkv: t(2 * kv_width, dim, 11),
6130 wgate: t(2 * kv_width, dim, 13),
6131 norm: ones(kv_width),
6132 ape: vec![0.01; 4 * 2 * kv_width],
6133 ratio: 4,
6134 overlap: true,
6135 })
6136 } else {
6137 None
6138 },
6139 indexer: if li == 1 {
6140 Some(Dsv4Indexer {
6141 wq_b: t(2 * 16, cfg.q_lora_rank, 41),
6142 weights_proj: t(2, dim, 43),
6143 compressor: Dsv4Compressor {
6144 wkv: t(2 * 16, dim, 45),
6145 wgate: t(2 * 16, dim, 47),
6146 norm: ones(16),
6147 ape: vec![0.01; 4 * 2 * 16],
6148 ratio: 4,
6149 overlap: true,
6150 },
6151 })
6152 } else {
6153 None
6154 },
6155 hc_attn_fn: w((2 + hc) * hc * hc * dim, 15 + li),
6156 hc_attn_base: w((2 + hc) * hc, 17 + li),
6157 hc_attn_scale: [1.0, 1.0, 1.0],
6158 hc_ffn_fn: w((2 + hc) * hc * hc * dim, 19 + li),
6159 hc_ffn_base: w((2 + hc) * hc, 21 + li),
6160 hc_ffn_scale: [1.0, 1.0, 1.0],
6161 gate: t(cfg.n_routed_experts, dim, 23 + li),
6162 gate_bias: if li == 1 {
6163 Some(vec![0.0; cfg.n_routed_experts])
6164 } else {
6165 None
6166 },
6167 tid2eid: if li == 0 {
6168 Some(
6169 (0..cfg.vocab * cfg.top_k)
6170 .map(|i| (i % cfg.n_routed_experts) as f32)
6171 .collect(),
6172 )
6173 } else {
6174 None
6175 },
6176 experts,
6177 mask: None,
6178 shared: Dsv4Expert {
6179 w1: t(cfg.moe_inter, dim, 25 + li),
6180 w2: t(dim, cfg.moe_inter, 27 + li),
6181 w3: t(cfg.moe_inter, dim, 29 + li),
6182 },
6183 });
6184 }
6185 let inv = |base: f32| -> Vec<f32> {
6186 (0..cfg.rope_head_dim / 2)
6187 .map(|i| 1.0 / base.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
6188 .collect()
6189 };
6190 let g = Dsv4Globals {
6191 inv_freq_compress: inv(160000.0),
6192 inv_freq_window: inv(10000.0),
6193 embed: t(cfg.vocab, dim, 31),
6194 norm: ones(dim),
6195 head: t(cfg.vocab, dim, 33),
6196 hc_head_fn: w(hc * hc * dim, 35),
6197 hc_head_base: w(hc, 37),
6198 hc_head_scale: 1.0,
6199 };
6200 (g, layers, cfg)
6201 }
6202
6203 #[test]
6208 fn forward_token_decodes_a_sequence_without_falling_over() {
6209 let (g, layers, cfg) = toy();
6210 let mut st = Dsv4State::new(layers.len());
6211 let inv_freq: Vec<f32> = (0..cfg.rope_head_dim / 2)
6212 .map(|i| 1.0 / 10000f32.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
6213 .collect();
6214 let mut logits = Vec::new();
6215
6216 let mut first: Option<Vec<f32>> = None;
6219 for (step, tok) in [3u32, 7, 1, 9, 4, 2, 8, 5, 6, 0].into_iter().enumerate() {
6220 forward_token(
6221 &g,
6222 &layers,
6223 &cfg,
6224 &mut st,
6225 tok,
6226 &inv_freq,
6227 None,
6228 &mut logits,
6229 );
6230 assert_eq!(logits.len(), cfg.vocab, "step {step}: logit count");
6231 assert!(
6232 logits.iter().all(|v| v.is_finite()),
6233 "step {step}: non-finite logit — {logits:?}"
6234 );
6235 let spread = logits.iter().cloned().fold(f32::MIN, f32::max)
6238 - logits.iter().cloned().fold(f32::MAX, f32::min);
6239 assert!(spread > 1e-6, "step {step}: logits are flat ({spread})");
6240 if step == 0 {
6241 first = Some(logits.clone());
6242 }
6243 assert_eq!(st.pos, step + 1, "position bookkeeping");
6244 }
6245
6246 assert!(!st.window[0].is_empty(), "sliding window never filled");
6249 for (li, w) in st.window.iter().enumerate() {
6251 assert!(
6252 w.len() / cfg.head_dim <= cfg.window,
6253 "layer {li}: window holds {} positions, cap is {}",
6254 w.len() / cfg.head_dim,
6255 cfg.window
6256 );
6257 }
6258 assert!(
6259 !st.compressed[1].is_empty(),
6260 "compressor layer produced no compressed KV in 10 tokens"
6261 );
6262 assert_eq!(
6266 st.compressed[1].len() / cfg.head_dim,
6267 2,
6268 "expected two folds in ten tokens at ratio 4"
6269 );
6270 assert!(
6271 !st.prev_kv[1].is_empty(),
6272 "the overlapping compressor never kept a previous window"
6273 );
6274 for (li, l) in layers.iter().enumerate() {
6279 if l.indexer.is_some() {
6280 assert!(
6281 !st.index_kv[li].is_empty(),
6282 "layer {li} has an indexer but its cache stayed empty"
6283 );
6284 }
6285 }
6286
6287 let mut fresh = Dsv4State::new(layers.len());
6290 let mut relogits = Vec::new();
6291 forward_token(
6292 &g,
6293 &layers,
6294 &cfg,
6295 &mut fresh,
6296 3,
6297 &inv_freq,
6298 None,
6299 &mut relogits,
6300 );
6301 assert_eq!(
6302 relogits,
6303 first.unwrap(),
6304 "the same token from a fresh state must reproduce exactly"
6305 );
6306 }
6307
6308 #[test]
6312 fn swiglu_limit_clamps_up_both_ways_and_gate_only_from_above() {
6313 let inter = 4;
6314 let gate_src = [-50.0f32, 50.0, 1.0, -1.0];
6316 let up_src = [50.0f32, -50.0, 1.0, -1.0];
6317 let limit = 10.0f32;
6318 let mut got = vec![0.0f32; inter];
6319 expert_swiglu(
6320 &[0.0],
6321 &|_, d| d.copy_from_slice(&gate_src),
6322 &|_, d| d.copy_from_slice(&up_src),
6323 &|src, d| d.copy_from_slice(src),
6324 inter,
6325 1.0,
6326 limit,
6327 &mut got,
6328 );
6329 let silu = |g: f32| g / (1.0 + (-g).exp());
6330 let want = [
6332 silu(-50.0) * limit,
6333 silu(limit) * -limit,
6334 silu(1.0) * 1.0,
6335 -silu(-1.0),
6336 ];
6337 for (i, w) in want.iter().enumerate() {
6338 assert!(
6339 (got[i] - w).abs() < 1e-5,
6340 "lane {i}: got {} want {w}",
6341 got[i]
6342 );
6343 }
6344 let mut raw = vec![0.0f32; inter];
6346 expert_swiglu(
6347 &[0.0],
6348 &|_, d| d.copy_from_slice(&gate_src),
6349 &|_, d| d.copy_from_slice(&up_src),
6350 &|src, d| d.copy_from_slice(src),
6351 inter,
6352 1.0,
6353 0.0,
6354 &mut raw,
6355 );
6356 assert!(
6357 (raw[1] - silu(50.0) * -50.0).abs() < 1e-3,
6358 "limit 0 must not clamp"
6359 );
6360 }
6361
6362 #[test]
6367 fn grouped_projection_is_identical_with_and_without_a_pool() {
6368 let (groups, lora, per_group, dim) = (4usize, 128usize, 64usize, 32usize);
6369 let attn: Vec<f32> = (0..groups * per_group)
6370 .map(|i| ((i * 13) as f32 * 0.021).sin())
6371 .collect();
6372 let wo_a: Vec<f32> = (0..groups * lora * per_group)
6373 .map(|i| ((i * 7) as f32 * 0.011).cos())
6374 .collect();
6375 let wo_b: Vec<f32> = (0..dim * groups * lora)
6376 .map(|i| ((i * 5) as f32 * 0.009).sin())
6377 .collect();
6378 let row = |r: usize, x: &[f32], _sc: &mut [f32]| -> f32 {
6379 wo_a[r * per_group..(r + 1) * per_group]
6380 .iter()
6381 .zip(x)
6382 .map(|(a, b)| a * b)
6383 .sum()
6384 };
6385 let project = |mid: &[f32], dst: &mut [f32]| {
6386 for (d, o) in dst.iter_mut().enumerate() {
6387 *o = wo_b[d * mid.len()..(d + 1) * mid.len()]
6388 .iter()
6389 .zip(mid)
6390 .map(|(a, b)| a * b)
6391 .sum();
6392 }
6393 };
6394
6395 let mut serial = vec![0.0f32; dim];
6396 o_project(
6397 &attn,
6398 &row,
6399 per_group,
6400 &project,
6401 groups,
6402 lora,
6403 None,
6404 &mut serial,
6405 );
6406
6407 let pool = crate::pool::Pool::new(4);
6408 let mut pooled = vec![0.0f32; dim];
6409 o_project(
6410 &attn,
6411 &row,
6412 per_group,
6413 &project,
6414 groups,
6415 lora,
6416 Some(&pool),
6417 &mut pooled,
6418 );
6419 assert_eq!(serial, pooled, "the pooled projection diverged");
6420 assert!(
6421 serial.iter().any(|v| v.abs() > 1e-6),
6422 "test data is degenerate"
6423 );
6424 }
6425
6426 #[test]
6427 fn block_grouped_projection_matches_position_walk() {
6428 let (_g, layers, cfg) = toy();
6429 let l = &layers[1];
6430 let b = 5;
6431 let attn_len = cfg.n_heads * cfg.head_dim;
6432 let attn: Vec<f32> = (0..b * attn_len)
6433 .map(|i| ((i * 17) as f32 * 0.013).sin())
6434 .collect();
6435 let mut walked = vec![0.0f32; b * cfg.dim];
6436 for bi in 0..b {
6437 o_project(
6438 &attn[bi * attn_len..(bi + 1) * attn_len],
6439 &|r, x, sc| l.wo_a.row_dot(r, x, sc),
6440 l.wo_a.cols(),
6441 &|mid, dst| l.wo_b.matvec(mid, dst, None),
6442 cfg.o_groups,
6443 cfg.o_lora_rank,
6444 None,
6445 &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
6446 );
6447 }
6448 let mut batched = vec![0.0f32; b * cfg.dim];
6449 o_project_block(
6450 &attn,
6451 b,
6452 &l.wo_a,
6453 &l.wo_b,
6454 cfg.o_groups,
6455 cfg.o_lora_rank,
6456 None,
6457 &mut batched,
6458 );
6459 assert_eq!(batched, walked);
6460 }
6461
6462 #[test]
6463 fn block_moe_matches_position_walk_in_route_order() {
6464 let (_g, layers, cfg) = toy();
6465 let l = &layers[1];
6468 let b = 5;
6469 let xs: Vec<f32> = (0..b * cfg.dim)
6470 .map(|i| ((i * 11) as f32 * 0.019).cos())
6471 .collect();
6472 let ids = [1u32, 2, 3, 4, 5];
6473 let mut walked = vec![0.0f32; b * cfg.dim];
6474 for bi in 0..b {
6475 moe_step(
6476 &xs[bi * cfg.dim..(bi + 1) * cfg.dim],
6477 l,
6478 &cfg,
6479 ids[bi],
6480 1,
6481 None,
6482 &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
6483 );
6484 }
6485 let mut batched = vec![0.0f32; b * cfg.dim];
6486 moe_step_block(&xs, b, l, &cfg, &ids, 1, None, &mut batched);
6487 assert_eq!(batched, walked);
6488 }
6489
6490 #[test]
6496 fn overlapping_compressor_folds_both_windows() {
6497 let (ratio, d) = (2usize, 3usize);
6498 let cur_kv: Vec<f32> = vec![
6501 1.0, 1.0, 1.0, 10.0, 20.0, 30.0, 2.0, 2.0, 2.0, 40.0, 50.0, 60.0, ];
6504 let cur_sc: Vec<f32> = vec![
6506 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 0.0, 100.0, 100.0, 0.0,
6508 ];
6509 let prev_kv: Vec<f32> = vec![
6511 7.0, 8.0, 9.0, 0.0, 0.0, 0.0, 5.0, 6.0, 7.0, 0.0, 0.0, 0.0,
6513 ];
6514 let prev_sc = vec![0.0f32; ratio * 2 * d];
6515
6516 let mut out = vec![0.0f32; d];
6517 compress_window_overlap(&prev_kv, &prev_sc, &cur_kv, &cur_sc, ratio, d, &mut out);
6518 assert!((out[0] - 40.0).abs() < 1e-3, "dim0 = {}", out[0]);
6520 assert!((out[1] - 50.0).abs() < 1e-3, "dim1 = {}", out[1]);
6521 assert!((out[2] - 30.0).abs() < 1e-3, "dim2 = {}", out[2]);
6523
6524 let mut first = vec![0.0f32; d];
6527 compress_window_overlap(&[], &[], &cur_kv, &cur_sc, ratio, d, &mut first);
6528 assert!(
6529 first.iter().all(|v| v.is_finite()),
6530 "first window: {first:?}"
6531 );
6532 assert!((first[0] - 40.0).abs() < 1e-3, "first dim0 = {}", first[0]);
6533
6534 let mut both = vec![0.0f32; d];
6536 let strong_prev = vec![100.0f32; ratio * 2 * d];
6537 compress_window_overlap(
6538 &prev_kv,
6539 &strong_prev,
6540 &cur_kv,
6541 &cur_sc,
6542 ratio,
6543 d,
6544 &mut both,
6545 );
6546 assert!(
6547 (both[0] - 40.0).abs() > 1.0,
6548 "a scored previous window must move the fold, got {}",
6549 both[0]
6550 );
6551 }
6552
6553 #[test]
6559 fn sinkhorn_matches_the_reference_numbers() {
6560 let hc = 4;
6561 let mixes: Vec<f32> = (0..24).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
6562 let base: Vec<f32> = (0..24).map(|i| (i as f32 * 0.11).cos()).collect();
6563 let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
6564 hc_split_sinkhorn(
6565 &mixes,
6566 &[1.0, 1.0, 1.0],
6567 &base,
6568 hc,
6569 20,
6570 1e-6,
6571 &mut pre,
6572 &mut post,
6573 &mut comb,
6574 );
6575 let want_pre = [0.7310596, 0.8888268, 0.9525191, 0.97424865];
6576 let want_post = [1.9600224, 1.9534285, 1.9201256, 1.8160983];
6577 let want_comb = [
6578 0.5996052,
6579 0.282_535_9,
6580 0.09218107,
6581 0.025676856,
6582 0.17564717,
6583 0.22228767,
6584 0.271_745_4,
6585 0.330_318_8,
6586 0.029528176,
6587 0.12206022,
6588 0.32619134,
6589 0.5222193,
6590 0.19521846,
6591 0.37311527,
6592 0.30988118,
6593 0.12178412,
6594 ];
6595 for (i, w) in want_pre.iter().enumerate() {
6596 assert!((pre[i] - w).abs() < 1e-5, "pre[{i}]: {} vs {w}", pre[i]);
6597 }
6598 for (i, w) in want_post.iter().enumerate() {
6599 assert!((post[i] - w).abs() < 1e-5, "post[{i}]: {} vs {w}", post[i]);
6600 }
6601 for (i, w) in want_comb.iter().enumerate() {
6602 assert!((comb[i] - w).abs() < 1e-4, "comb[{i}]: {} vs {w}", comb[i]);
6603 }
6604 }
6605
6606 #[test]
6611 fn sinkhorn_leaves_the_mixing_matrix_doubly_stochastic() {
6612 let hc = 4;
6613 let mix_hc = (2 + hc) * hc;
6614 let mixes: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
6616 let base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.11).cos()).collect();
6617 let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
6618 hc_split_sinkhorn(
6619 &mixes,
6620 &[1.0, 1.0, 1.0],
6621 &base,
6622 hc,
6623 20,
6624 1e-6,
6625 &mut pre,
6626 &mut post,
6627 &mut comb,
6628 );
6629 for j in 0..hc {
6630 let r: f32 = comb[j * hc..(j + 1) * hc].iter().sum();
6631 assert!((r - 1.0).abs() < 2e-3, "row {j} sums to {r}");
6632 let c: f32 = (0..hc).map(|k| comb[k * hc + j]).sum();
6633 assert!((c - 1.0).abs() < 2e-3, "col {j} sums to {c}");
6634 }
6635 assert!(pre.iter().all(|&v| v > 0.0 && v < 1.001));
6637 assert!(post.iter().all(|&v| (0.0..=2.0).contains(&v)));
6638 }
6639
6640 #[test]
6644 fn expand_of_identical_copies_is_a_fixed_point() {
6645 let (hc, dim) = (4usize, 3usize);
6646 let residual: Vec<f32> = std::iter::repeat_n([1.5f32, -2.0, 0.25], hc)
6647 .flatten()
6648 .collect();
6649 let comb = {
6650 vec![0.25f32; hc * hc]
6652 };
6653 let post = vec![0.0f32; hc];
6654 let mut out = vec![0.0f32; hc * dim];
6655 hc_expand(&[0.0; 3], &residual, &post, &comb, hc, dim, &mut out);
6656 for (o, r) in out.iter().zip(&residual) {
6657 assert!((o - r).abs() < 1e-6, "{o} vs {r}");
6658 }
6659 }
6660
6661 #[test]
6665 fn selection_bias_steers_the_choice_but_not_the_weights() {
6666 let scores = [3.0f32, 0.1, 2.0, 0.05];
6667 let bias = [0.0f32, 10.0, 0.0, 0.0];
6668 let (mut idx, mut w) = (Vec::new(), Vec::new());
6669 route(&scores, Some(&bias), 2, 1.5, None, None, &mut idx, &mut w);
6670 assert_eq!(idx[0], 1, "the biased expert must win selection");
6671 assert_eq!(idx[1], 0);
6672 assert!(w[0] < w[1], "biased expert kept its own (small) weight");
6675 let sum: f32 = w.iter().sum();
6676 assert!((sum - 1.5).abs() < 1e-5, "weights renormalize then scale");
6677 }
6678
6679 #[test]
6683 fn attention_sink_drains_weight_without_contributing_output() {
6684 let hd = 2;
6685 let q = [1.0f32, 0.0];
6686 let kv = [1.0f32, 0.0, 0.0, 1.0];
6687 let mut out = vec![0.0f32; hd];
6688 sparse_attend(&q, &kv, &[0, 1], f32::NEG_INFINITY, 1.0, hd, &mut out);
6689 let plain = out.clone();
6690 assert!(plain[0] > plain[1], "the aligned key must dominate");
6691 sparse_attend(&q, &kv, &[0, 1], 20.0, 1.0, hd, &mut out);
6692 assert!(
6693 out[0] < plain[0] * 0.01 && out[1] < plain[1] * 0.01,
6694 "a large sink must drain nearly all the mass: {out:?}"
6695 );
6696 }
6697
6698 #[test]
6701 fn masked_positions_leave_the_denominator_alone() {
6702 let hd = 2;
6703 let q = [1.0f32, 0.0];
6704 let kv = [1.0f32, 0.0, 0.0, 1.0];
6705 let (mut a, mut b) = (vec![0.0f32; hd], vec![0.0f32; hd]);
6706 sparse_attend(&q, &kv, &[0], f32::NEG_INFINITY, 1.0, hd, &mut a);
6707 sparse_attend(
6708 &q,
6709 &kv,
6710 &[0, usize::MAX],
6711 f32::NEG_INFINITY,
6712 1.0,
6713 hd,
6714 &mut b,
6715 );
6716 for (x, y) in a.iter().zip(&b) {
6717 assert!((x - y).abs() < 1e-6, "{x} vs {y}");
6718 }
6719 }
6720
6721 #[test]
6724 fn rope_tail_inverts_itself() {
6725 let inv_freq = [1.0f32, 0.5];
6726 let orig = [9.0f32, 8.0, 1.0, 2.0, 3.0, 4.0];
6727 let mut v = orig;
6728 rope_tail(&mut v, &inv_freq, 7, 4, false);
6729 assert!(v[..2] == orig[..2], "the non-rope head must not move");
6730 assert!(v[2..] != orig[2..], "the tail must actually rotate");
6731 rope_tail(&mut v, &inv_freq, 7, 4, true);
6732 for (a, b) in v.iter().zip(&orig) {
6733 assert!((a - b).abs() < 1e-5, "{a} vs {b}");
6734 }
6735 }
6736
6737 #[test]
6740 fn compressor_pools_the_window_per_dimension() {
6741 let (ratio, width) = (2usize, 2usize);
6742 let kv = [1.0f32, 10.0, 3.0, 20.0];
6743 let score = [0.0f32, 0.0, 0.0, 50.0];
6745 let ape = vec![0.0f32; ratio * width];
6746 let mut out = vec![0.0f32; width];
6747 compress_window(&kv, &score, &ape, ratio, width, &mut out);
6748 assert!(
6749 (out[0] - 2.0).abs() < 1e-5,
6750 "equal scores average: {}",
6751 out[0]
6752 );
6753 assert!(
6754 (out[1] - 20.0).abs() < 1e-3,
6755 "a dominant score wins: {}",
6756 out[1]
6757 );
6758 }
6759
6760 #[test]
6763 fn index_scores_relu_before_weighting() {
6764 let (nh, hd) = (2usize, 2usize);
6765 let q = [1.0f32, 0.0, -1.0, 0.0];
6767 let kv = [1.0f32, 0.0, 0.0, 1.0];
6768 let w = [1.0f32, 1.0];
6769 let mut sc = Vec::new();
6770 index_scores(&q, &kv, &w, nh, hd, 2, 2, None, &mut sc);
6771 assert!(sc[0] > 0.9, "abstention, not veto: {:?}", sc);
6773 }
6774
6775 #[test]
6776 fn index_scores_mask_the_future() {
6777 let (nh, hd) = (1usize, 2usize);
6778 let q = [1.0f32, 0.0];
6779 let kv = [1.0f32, 0.0, 1.0, 0.0, 1.0, 0.0];
6780 let w = [1.0f32];
6781 let mut sc = Vec::new();
6782 index_scores(&q, &kv, &w, nh, hd, 3, 2, None, &mut sc);
6783 assert!(sc[0].is_finite() && sc[1].is_finite());
6784 assert!(sc[2] == f32::NEG_INFINITY, "position 2 is in the future");
6785 let mut idx = Vec::new();
6786 top_k_positions(&sc, 3, &mut idx);
6787 assert_eq!(idx, vec![0, 1], "a masked slot never wins a slot");
6788 }
6789
6790 #[test]
6791 fn top_k_is_deterministic_on_ties() {
6792 let sc = [1.0f32, 1.0, 1.0, 0.0];
6793 let mut idx = Vec::new();
6794 top_k_positions(&sc, 2, &mut idx);
6795 assert_eq!(idx, vec![0, 1], "ties resolve to the lower index");
6796 }
6797
6798 #[test]
6802 fn hc_block_preserves_the_copy_structure_and_applies_the_block() {
6803 let cfg = Dsv4Cfg {
6804 dim: 4,
6805 n_heads: 1,
6806 head_dim: 4,
6807 rope_head_dim: 2,
6808 q_lora_rank: 4,
6809 o_lora_rank: 2,
6810 o_groups: 1,
6811 hc_mult: 4,
6812 hc_sinkhorn_iters: 20,
6813 hc_eps: 1e-6,
6814 norm_eps: 1e-6,
6815 n_routed_experts: 2,
6816 top_k: 1,
6817 moe_inter: 4,
6818 route_scale: 1.0,
6819 swiglu_limit: 10.0,
6820 window: 128,
6821 index_topk: 4,
6822 vocab: 8,
6823 };
6824 let (hc, dim) = (cfg.hc_mult, cfg.dim);
6825 let mix_hc = (2 + hc) * hc;
6826 let hc_fn: Vec<f32> = (0..mix_hc * hc * dim)
6827 .map(|i| ((i % 13) as f32 - 6.0) * 0.05)
6828 .collect();
6829 let hc_base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.2).sin()).collect();
6830 let norm_w = vec![1.0f32; dim];
6831 let mut state: Vec<f32> = (0..hc * dim).map(|i| (i as f32 * 0.3).cos()).collect();
6832 let before = state.clone();
6833 let mut scratch = HcScratch::new(&cfg);
6834 hc_block(
6835 &mut state,
6836 &hc_fn,
6837 &[1.0, 1.0, 1.0],
6838 &hc_base,
6839 &norm_w,
6840 &cfg,
6841 &mut scratch,
6842 None,
6843 |_folded, out: &mut [f32]| out.iter_mut().for_each(|o| *o = 1.0),
6844 );
6845 assert_eq!(state.len(), before.len(), "copy structure must survive");
6846 assert!(state.iter().all(|v| v.is_finite()), "{state:?}");
6847 assert!(
6848 state.iter().zip(&before).any(|(a, b)| (a - b).abs() > 1e-4),
6849 "the block's output has to reach the state"
6850 );
6851 }
6852
6853 #[test]
6854 fn hash_route_reads_the_table_row() {
6855 let table = [7.0f32, 9.0, 1.0, 2.0, 5.0, 6.0];
6857 assert_eq!(hash_route(&table, 3, 2, 0), vec![7, 9]);
6858 assert_eq!(hash_route(&table, 3, 2, 2), vec![5, 6]);
6859 assert_eq!(hash_route(&table, 3, 2, 99), vec![5, 6]);
6861 }
6862
6863 #[test]
6868 fn a_task_mask_restricts_selection_and_renormalizes() {
6869 let scores = [0.1f32, 4.0, 1.0, 9.0];
6871 let (mut idx, mut w) = (Vec::new(), Vec::new());
6872 route(&scores, None, 2, 1.0, None, None, &mut idx, &mut w);
6873 assert_eq!(idx, vec![3, 1], "unmasked: the two best win");
6874 let sum: f32 = w.iter().sum();
6875 assert!((sum - 1.0).abs() < 1e-5, "weights must sum to route_scale");
6876
6877 let mask = [true, false, true, true];
6880 let (mut i2, mut w2) = (Vec::new(), Vec::new());
6881 route(&scores, None, 2, 1.0, None, Some(&mask), &mut i2, &mut w2);
6882 assert_eq!(i2, vec![3, 2], "masked expert must not be selected");
6883 let sum2: f32 = w2.iter().sum();
6884 assert!((sum2 - 1.0).abs() < 1e-5, "masked weights must renormalize");
6885
6886 let tight = [false, false, false, true];
6888 let (mut i3, mut w3) = (Vec::new(), Vec::new());
6889 route(&scores, None, 2, 1.0, None, Some(&tight), &mut i3, &mut w3);
6890 assert_eq!(i3, vec![3]);
6891 assert_eq!(w3.len(), 1);
6892 }
6893
6894 #[test]
6899 fn hash_layers_weight_the_experts_the_table_names() {
6900 let scores = [0.1f32, 0.4, 0.2, 5.0];
6902 let table = vec![0.0f32, 1.0];
6903 let idx_forced = hash_route(&table, 1, 2, 0);
6904 assert_eq!(idx_forced, vec![0, 1]);
6905
6906 let (mut idx, mut w) = (Vec::new(), Vec::new());
6907 route(
6908 &scores,
6909 None,
6910 2,
6911 1.0,
6912 Some(&idx_forced),
6913 None,
6914 &mut idx,
6915 &mut w,
6916 );
6917 assert_eq!(idx, vec![0, 1], "the table must decide the experts");
6918
6919 let sp = |x: f32| (1.0 + x.exp()).ln().sqrt();
6921 let (s0, s1) = (sp(scores[0]), sp(scores[1]));
6922 let tot = s0 + s1;
6923 assert!(
6924 (w[0] - s0 / tot).abs() < 1e-6,
6925 "w[0]={} want {}",
6926 w[0],
6927 s0 / tot
6928 );
6929 assert!(
6930 (w[1] - s1 / tot).abs() < 1e-6,
6931 "w[1]={} want {}",
6932 w[1],
6933 s1 / tot
6934 );
6935
6936 let (mut idx2, mut w2) = (Vec::new(), Vec::new());
6938 route(&scores, None, 2, 1.0, None, None, &mut idx2, &mut w2);
6939 assert_eq!(idx2[0], 3, "without a table the highest score still wins");
6940 }
6941}
6942
6943pub const DSPARK_NOISE_TOKEN: u32 = 128799;
6960pub fn dspark_block() -> usize {
6968 5
6969}
6970
6971pub fn dspark_verify_k() -> usize {
6978 static K: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
6979 *K.get_or_init(|| {
6980 std::env::var("CMF_DSPARK_VERIFY_K")
6981 .ok()
6982 .and_then(|v| v.parse::<usize>().ok())
6983 .filter(|&n| (1..=DSPARK_BLOCK_MAX).contains(&n))
6984 .unwrap_or(DSPARK_BLOCK_MAX)
6985 })
6986}
6987
6988pub const DSPARK_BLOCK_MAX: usize = 5;
6990
6991pub struct DsparkState {
6994 pub win: Vec<Vec<f32>>,
6996 pub filled: Vec<usize>,
6998 pub main_hidden: Vec<f32>,
7000 pub have_hidden: bool,
7002}
7003
7004impl DsparkState {
7005 pub fn new(stages: usize, cfg: &Dsv4Cfg, targets: usize) -> Self {
7006 Self {
7007 win: vec![Vec::new(); stages],
7008 filled: vec![0; stages],
7009 main_hidden: vec![0.0; cfg.dim * targets],
7010 have_hidden: false,
7011 }
7012 }
7013}
7014
7015pub fn dspark_targets(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, n_layers: usize) -> Vec<usize> {
7021 let Some(mp) = mtp.iter().find_map(|m| m.main_proj.as_ref()) else {
7022 return Vec::new();
7023 };
7024 let n = (mp.cols() / cfg.dim.max(1)).clamp(1, n_layers);
7025 (n_layers - n..n_layers).collect()
7026}
7027
7028thread_local! {
7029 static DSPARK_CAP: std::cell::RefCell<(Vec<usize>, Vec<f32>, usize)> =
7035 const { std::cell::RefCell::new((Vec::new(), Vec::new(), 0)) };
7036}
7037
7038pub fn dspark_arm(targets: &[usize], dim: usize) {
7040 DSPARK_CAP.with(|c| {
7041 let mut c = c.borrow_mut();
7042 c.0 = targets.to_vec();
7043 c.1 = vec![0.0; dim * targets.len()];
7044 c.2 = 0;
7045 });
7046}
7047
7048fn dspark_wants(li: usize) -> bool {
7052 DSPARK_CAP.with(|c| c.borrow().0.contains(&li))
7053}
7054
7055pub fn dspark_note(li: usize, state: &[f32], cfg: &Dsv4Cfg) {
7057 DSPARK_CAP.with(|c| {
7058 let mut c = c.borrow_mut();
7059 if c.0.is_empty() {
7060 return;
7061 }
7062 if let Some(slot) = c.0.iter().position(|&t| t == li) {
7063 let (_, buf, seen) = &mut *c;
7064 dspark_capture(state, cfg, slot, buf);
7065 *seen = if slot == 0 { 1 } else { *seen + 1 };
7070 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
7071 eprintln!("[cap] note li={li} slot={slot} seen={}", *seen);
7072 }
7073 }
7074 });
7075}
7076
7077pub fn dspark_peek_slot(slot: usize, dim: usize, out: &mut [f32]) -> bool {
7082 DSPARK_CAP.with(|c| {
7083 let c = c.borrow();
7084 let lo = slot * dim;
7085 if c.1.len() < lo + dim {
7086 return false;
7087 }
7088 out[..dim].copy_from_slice(&c.1[lo..lo + dim]);
7089 true
7090 })
7091}
7092
7093pub fn dspark_take(out: &mut Vec<f32>) -> bool {
7095 DSPARK_CAP.with(|c| {
7096 let mut c = c.borrow_mut();
7097 if c.0.is_empty() || c.2 != c.0.len() {
7098 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
7099 eprintln!("[cap] take FAIL armed={:?} seen={}", c.0, c.2);
7100 }
7101 return false;
7102 }
7103 out.clear();
7104 out.extend_from_slice(&c.1);
7105 c.2 = 0;
7106 true
7107 })
7108}
7109
7110pub fn dspark_capture(state: &[f32], cfg: &Dsv4Cfg, slot: usize, out: &mut [f32]) {
7114 let (hc, dim) = (cfg.hc_mult, cfg.dim);
7115 let dst = &mut out[slot * dim..(slot + 1) * dim];
7116 let inv = 1.0 / hc as f32;
7117 for d in 0..dim {
7118 let mut s = 0.0;
7119 for j in 0..hc {
7120 s += state[j * dim + d];
7121 }
7122 dst[d] = s * inv;
7123 }
7124}
7125
7126pub fn dspark_freq_note(picks: &[(usize, Vec<usize>)]) {
7131 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
7132 std::sync::Mutex::new(None);
7133 let Ok(path) = std::env::var("CMF_DSPARK_PICK_DUMP") else {
7134 return;
7135 };
7136 let mut g = FREQ.lock().unwrap();
7137 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
7138 for (stage, idx) in picks {
7139 for &e in idx {
7140 *map.entry((*stage, e)).or_insert(0) += 1;
7141 }
7142 }
7143 *blocks += 1;
7144 if *blocks % 32 == 0 {
7145 let mut lines: Vec<_> = map.iter().collect();
7146 lines.sort();
7147 let body: String = lines
7148 .iter()
7149 .map(|((s, e), n)| format!("{s}\t{e}\t{n}\n"))
7150 .collect();
7151 let _ = std::fs::write(&path, body);
7152 }
7153}
7154
7155pub fn trunk_freq_note(picks: &[(usize, Vec<usize>)]) {
7160 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
7161 std::sync::Mutex::new(None);
7162 let Ok(path) = std::env::var("CMF_DSV4_TRUNK_PICK_DUMP") else {
7163 return;
7164 };
7165 let mut g = FREQ.lock().unwrap();
7166 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
7167 for (li, idx) in picks {
7168 for &e in idx {
7169 *map.entry((*li, e)).or_insert(0) += 1;
7170 }
7171 }
7172 *blocks += 1;
7173 if *blocks % 32 == 0 {
7174 let mut lines: Vec<_> = map.iter().collect();
7175 lines.sort();
7176 let body: String = lines
7177 .iter()
7178 .map(|((l, e), n)| format!("{l}\t{e}\t{n}\n"))
7179 .collect();
7180 let _ = std::fs::write(&path, body);
7181 }
7182}
7183
7184fn dspark_apply_mask(out: &mut [Dsv4Mtp]) {
7192 let Ok(path) = std::env::var("CMF_DSPARK_MASK") else {
7193 return;
7194 };
7195 let Ok(text) = std::fs::read_to_string(&path) else {
7196 eprintln!("DSpark: CMF_DSPARK_MASK={path} не читается — маска не применена");
7197 return;
7198 };
7199 for (d, line) in text.lines().enumerate() {
7200 let Some(m) = out.get_mut(d) else { break };
7201 let n = m.layer.experts.len();
7202 let mut mask = vec![false; n];
7203 let mut kept = 0usize;
7204 for tok in line.split(',') {
7205 if let Ok(e) = tok.trim().parse::<usize>() {
7206 if e < n && !mask[e] {
7207 mask[e] = true;
7208 kept += 1;
7209 }
7210 }
7211 }
7212 if kept == 0 {
7213 continue;
7214 }
7215 eprintln!("DSpark: стадия {d} ограничена {kept}/{n} экспертами");
7216 m.layer.mask = Some(mask);
7217 }
7218}
7219
7220#[cfg(feature = "gpu")]
7230pub struct DsparkPack {
7231 pub stages: Vec<DsparkStagePack>,
7232 pub gu_q2: bool,
7235 pub dn_q2: bool,
7238 pub routers: Vec<Vec<f32>>,
7241 pub biases: Vec<Option<Vec<f32>>>,
7242}
7243
7244#[cfg(feature = "gpu")]
7245pub struct DsparkStagePack {
7246 pub mask: Vec<bool>,
7248 pub to_slot: Vec<usize>,
7250 pub mask_u32: Vec<u32>,
7253 pub map_u32: Vec<u32>,
7254 pub tensors: Vec<(usize, usize, usize)>,
7256 pub n_resident: usize,
7257}
7258
7259pub static DSPARK_Q2TP_ENCODE: std::sync::OnceLock<fn(&[f32], usize, usize) -> Vec<u8>> =
7266 std::sync::OnceLock::new();
7267
7268#[cfg(feature = "gpu")]
7271pub fn dspark_gpu_on() -> bool {
7272 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7273 *ON.get_or_init(|| {
7274 std::env::var("CMF_DSPARK_GPU")
7275 .map(|v| v != "0")
7276 .unwrap_or(true)
7277 })
7278}
7279
7280#[cfg(feature = "gpu")]
7282pub fn dspark_pack_get(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<&'static DsparkPack> {
7283 static P: std::sync::OnceLock<Option<Box<DsparkPack>>> = std::sync::OnceLock::new();
7284 P.get_or_init(|| dspark_pack_build(mtp, cfg).map(Box::new))
7285 .as_deref()
7286}
7287
7288#[cfg(feature = "gpu")]
7303pub fn dspark_reserve_note(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, layers: &[Dsv4Layer]) {
7304 if mtp.is_empty() || std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "0") || !dspark_gpu_on()
7305 {
7306 return;
7307 }
7308 let dt = |q2: bool| {
7309 if q2 {
7310 cortiq_core::TensorDtype::Q2TiledP
7311 } else {
7312 cortiq_core::TensorDtype::Q4TiledP
7313 }
7314 };
7315 let gu_q2 = mtp[0]
7316 .layer
7317 .experts
7318 .first()
7319 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
7320 let dn_q2 = mtp[0]
7321 .layer
7322 .experts
7323 .first()
7324 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
7325 let gu = cortiq_core::quant::expected_nbytes(dt(gu_q2), &[cfg.moe_inter, cfg.dim]).unwrap_or(0);
7326 let dn = cortiq_core::quant::expected_nbytes(dt(dn_q2), &[cfg.dim, cfg.moe_inter]).unwrap_or(0);
7327 let per = (2 * gu + dn) as u64;
7328 let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
7329 .ok()
7330 .and_then(|v| v.parse().ok())
7331 .unwrap_or(40);
7334 let bytes = per * (n_res * mtp.len() + mtp.len() + 1) as u64;
7336 let trunk: u64 = layers
7338 .iter()
7339 .map(|l| {
7340 let Some(e) = l.experts.first() else { return 0 };
7341 let gu = cortiq_core::quant::expected_nbytes(
7342 dt(e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
7343 &[cfg.moe_inter, cfg.dim],
7344 )
7345 .unwrap_or(0);
7346 let dn = cortiq_core::quant::expected_nbytes(
7347 dt(e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
7348 &[cfg.dim, cfg.moe_inter],
7349 )
7350 .unwrap_or(0);
7351 ((2 * gu + dn) * (l.experts.len() + 1)) as u64
7352 })
7353 .sum();
7354 if let Some(budget) = crate::gpu_wgpu::dsv4_vram_budget() {
7355 if budget < trunk / 10 * 9 + bytes {
7356 return;
7357 }
7358 }
7359 crate::gpu_wgpu::DRAFT_RESERVE.store(bytes, std::sync::atomic::Ordering::Relaxed);
7360}
7361
7362#[cfg(not(feature = "gpu"))]
7363pub fn dspark_reserve_note(_mtp: &[Dsv4Mtp], _cfg: &Dsv4Cfg, _layers: &[Dsv4Layer]) {}
7364
7365#[cfg(feature = "gpu")]
7366pub fn dspark_pack_build(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<DsparkPack> {
7367 if mtp.is_empty() {
7368 return None;
7369 }
7370 let n_res: usize =
7371 std::env::var("CMF_DSPARK_RESIDENT")
7372 .ok()
7373 .and_then(|v| v.parse().ok())
7374 .unwrap_or_else(|| {
7375 let native_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
7381 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
7382 });
7383 let gu_q2 = native_q2 || DSPARK_Q2TP_ENCODE.get().is_some();
7384 let dn_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
7385 e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
7386 });
7387 let room = crate::gpu_wgpu::dsv4_draft_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2);
7388 (room.saturating_sub(mtp.len() + 1) / mtp.len().max(1)).clamp(8, 64)
7389 });
7390 let mut freq: Vec<Vec<(u64, usize)>> = vec![Vec::new(); mtp.len()];
7394 let pack_path = std::env::var("CMF_DSPARK_PACK").ok().or_else(|| {
7395 let m = mtp[0].layer.experts.first()?.w1.model_arc()?;
7396 let mut s = m.path.as_os_str().to_os_string();
7397 s.push(".dspark.tsv");
7398 let p = std::path::PathBuf::from(s);
7399 p.exists().then(|| p.to_string_lossy().into_owned())
7400 });
7401 if let Some(path) = pack_path {
7402 if let Ok(text) = std::fs::read_to_string(&path) {
7403 for line in text.lines() {
7404 let mut it = line.split_whitespace();
7405 if let (Some(s), Some(e), Some(n)) = (it.next(), it.next(), it.next()) {
7406 if let (Ok(s), Ok(e), Ok(n)) =
7407 (s.parse::<usize>(), e.parse::<usize>(), n.parse::<u64>())
7408 {
7409 if s < freq.len() {
7410 freq[s].push((n, e));
7411 }
7412 }
7413 }
7414 }
7415 }
7416 }
7417 let mut stages = Vec::with_capacity(mtp.len());
7418 let mut routers = Vec::with_capacity(mtp.len());
7419 let mut biases = Vec::with_capacity(mtp.len());
7420 for (si, m) in mtp.iter().enumerate() {
7421 let l = &m.layer;
7422 let n = l.experts.len();
7423 let mut order: Vec<usize> = {
7426 let mut f = freq[si].clone();
7427 f.sort_by(|a, b| b.0.cmp(&a.0));
7428 let mut seen = vec![false; n];
7429 let mut o: Vec<usize> = f
7430 .into_iter()
7431 .map(|(_, e)| e)
7432 .filter(|&e| {
7433 if e < n && !seen[e] {
7434 seen[e] = true;
7435 true
7436 } else {
7437 false
7438 }
7439 })
7440 .collect();
7441 o.extend((0..n).filter(|&e| !seen[e]));
7442 o
7443 };
7444 order.truncate(n_res.min(n));
7445 let mut mask = vec![false; n];
7446 let mut to_slot = vec![usize::MAX; n];
7447 let mut tensors = Vec::with_capacity(order.len() + 1);
7448 for (slot, &e) in order.iter().enumerate() {
7449 let ex = &l.experts[e];
7450 let (Some(w1), Some(w3), Some(w2)) =
7451 (ex.w1.model_idx(), ex.w3.model_idx(), ex.w2.model_idx())
7452 else {
7453 return None;
7454 };
7455 mask[e] = true;
7456 to_slot[e] = slot;
7457 tensors.push((w1, w3, w2));
7458 }
7459 let (Some(s1), Some(s3), Some(s2)) = (
7460 l.shared.w1.model_idx(),
7461 l.shared.w3.model_idx(),
7462 l.shared.w2.model_idx(),
7463 ) else {
7464 return None;
7465 };
7466 tensors.push((s1, s3, s2));
7467 let mut router = vec![0.0f32; n * cfg.dim];
7469 for (r, row) in (0..n).zip(router.chunks_mut(cfg.dim)) {
7470 l.gate.row_f32(r, row);
7471 }
7472 routers.push(router);
7473 biases.push(l.gate_bias.clone());
7474 let mask_u32: Vec<u32> = mask.iter().map(|&m| m as u32).collect();
7475 let map_u32: Vec<u32> = to_slot
7476 .iter()
7477 .map(|&x| if x == usize::MAX { u32::MAX } else { x as u32 })
7478 .collect();
7479 stages.push(DsparkStagePack {
7480 mask,
7481 to_slot,
7482 mask_u32,
7483 map_u32,
7484 tensors,
7485 n_resident: order.len(),
7486 });
7487 }
7488 let model = mtp[0]
7492 .layer
7493 .experts
7494 .first()
7495 .and_then(|e| e.w1.model_arc())?;
7496 let mut skeleton = Vec::new();
7497 for m in mtp {
7498 let l = &m.layer;
7499 for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b] {
7500 skeleton.push(t.model_idx()?);
7501 }
7502 }
7503 if let Some(mp) = mtp[0].main_proj.as_ref() {
7504 skeleton.push(mp.model_idx()?);
7505 }
7506 for &idx in &skeleton {
7507 if !crate::gpu_wgpu::dsv4_weight_ready(&model, idx) {
7508 eprintln!("DSpark: скелет драфта не влез в VRAM — GPU-черновик выключен");
7509 return None;
7510 }
7511 }
7512 let native_q2 = mtp[0]
7518 .layer
7519 .experts
7520 .first()
7521 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
7522 let gu_q2 = native_q2 || crate::dsv4::DSPARK_Q2TP_ENCODE.get().is_some();
7523 let dn_native = mtp[0]
7524 .layer
7525 .experts
7526 .first()
7527 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
7528 for (si, sp) in stages.iter().enumerate() {
7529 let ok = if native_q2 {
7530 crate::gpu_wgpu::dsv4_experts_ready(
7531 &model,
7532 &sp.tensors,
7533 cfg.moe_inter,
7534 cfg.dim,
7535 true,
7536 dn_native,
7537 )
7538 } else if gu_q2 {
7539 crate::gpu_wgpu::moe_expert_bufs_requant_gu(&model, &sp.tensors, cfg.moe_inter, cfg.dim)
7540 .is_some()
7541 } else {
7542 crate::gpu_wgpu::dsv4_experts_ready(
7543 &model,
7544 &sp.tensors,
7545 cfg.moe_inter,
7546 cfg.dim,
7547 false,
7548 false,
7549 )
7550 };
7551 if !ok {
7552 eprintln!(
7553 "DSpark: эксперты стадии {si} ({} + shared) не влезли в VRAM — GPU-черновик выключен",
7554 sp.n_resident
7555 );
7556 return None;
7557 }
7558 }
7559 let _ = crate::gpu_wgpu::pin_weights(&model, &skeleton);
7560 eprintln!(
7561 "DSpark: пак драфта на карте — {} стадии по {} экспертов + shared",
7562 stages.len(),
7563 stages
7564 .iter()
7565 .map(|s| s.n_resident.to_string())
7566 .collect::<Vec<_>>()
7567 .join("/")
7568 );
7569 Some(DsparkPack {
7570 stages,
7571 gu_q2,
7572 dn_q2: dn_native,
7573 routers,
7574 biases,
7575 })
7576}
7577
7578pub fn dspark_ring_append(
7585 g: &Dsv4Globals,
7586 mtp: &[Dsv4Mtp],
7587 cfg: &Dsv4Cfg,
7588 ds: &mut DsparkState,
7589 pos: usize,
7590 pool: Option<&crate::pool::Pool>,
7591) {
7592 let (dim, hd, rd) = (cfg.dim, cfg.head_dim, cfg.rope_head_dim);
7593 let inv_freq = &g.inv_freq_window;
7594 let Some(stage0) = mtp.first() else { return };
7595 let (Some(mp), Some(mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
7596 return;
7597 };
7598 let mut main_x = vec![0.0f32; dim];
7599 mp.matvec(&ds.main_hidden, &mut main_x, pool);
7600 rms_weighted(&mut main_x, mn, cfg.norm_eps);
7601 for (si, m) in mtp.iter().enumerate() {
7602 let kvw = m.layer.wkv.rows();
7603 if ds.win[si].len() < cfg.window * kvw {
7604 ds.win[si].resize(cfg.window * kvw, 0.0);
7605 }
7606 let mut kv = vec![0.0f32; kvw];
7607 m.layer.wkv.matvec(&main_x, &mut kv, pool);
7608 rms_weighted(&mut kv, &m.layer.kv_norm, cfg.norm_eps);
7609 rope_tail(&mut kv[kvw - hd..], inv_freq, pos, rd, false);
7610 let slot = pos % cfg.window;
7611 ds.win[si][slot * kvw..(slot + 1) * kvw].copy_from_slice(&kv);
7612 ds.filled[si] = (pos + 1).min(cfg.window);
7613 }
7614}
7615
7616#[cfg(feature = "gpu")]
7622#[allow(clippy::too_many_arguments)]
7623pub fn dspark_draft_gpu(
7624 g: &Dsv4Globals,
7625 mtp: &[Dsv4Mtp],
7626 cfg: &Dsv4Cfg,
7627 ds: &mut DsparkState,
7628 pack: &DsparkPack,
7629 kv_id: u64,
7630 last_token: u32,
7631 pos: usize,
7632 pool: Option<&crate::pool::Pool>,
7633 out_conf: &mut Vec<f32>,
7634) -> Vec<u32> {
7635 let (hc, dim) = (cfg.hc_mult, cfg.dim);
7636 let block = dspark_block();
7637 let Some(model) = mtp[0].layer.experts.first().and_then(|e| e.w1.model_arc()) else {
7638 return Vec::new();
7639 };
7640 let (Some(mp), Some(mn)) = (mtp[0].main_proj.as_ref(), mtp[0].main_norm.as_ref()) else {
7641 return Vec::new();
7642 };
7643 let Some(mp_idx) = mp.model_idx() else {
7644 return Vec::new();
7645 };
7646 let mut stages = Vec::with_capacity(mtp.len());
7647 for (si, m) in mtp.iter().enumerate() {
7648 let l = &m.layer;
7649 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
7650 l.wq_a.model_idx(),
7651 l.wq_b.model_idx(),
7652 l.wo_a.model_idx(),
7653 l.wo_b.model_idx(),
7654 l.wkv.model_idx(),
7655 ) else {
7656 return Vec::new();
7657 };
7658 let sp = &pack.stages[si];
7659 stages.push(crate::gpu_wgpu::DsparkStageW {
7660 wq_a,
7661 wq_b,
7662 wo_a,
7663 wo_b,
7664 wkv,
7665 q_norm: &l.q_norm,
7666 kv_norm: &l.kv_norm,
7667 attn_norm: &l.attn_norm,
7668 ffn_norm: &l.ffn_norm,
7669 sink: &l.attn_sink,
7670 hc_attn_fn: &l.hc_attn_fn,
7671 hc_attn_scale: &l.hc_attn_scale,
7672 hc_attn_base: &l.hc_attn_base,
7673 hc_ffn_fn: &l.hc_ffn_fn,
7674 hc_ffn_scale: &l.hc_ffn_scale,
7675 hc_ffn_base: &l.hc_ffn_base,
7676 router: &pack.routers[si],
7677 bias: pack.biases[si].as_deref(),
7678 experts: &sp.tensors,
7679 mask_u32: &sp.mask_u32,
7680 map_u32: &sp.map_u32,
7681 });
7682 }
7683 let geom = crate::gpu_wgpu::DsparkGeom {
7684 dim,
7685 hc,
7686 nh: cfg.n_heads,
7687 hd: cfg.head_dim,
7688 rd: cfg.rope_head_dim,
7689 q_lora: cfg.q_lora_rank,
7690 o_lora: cfg.o_lora_rank,
7691 o_groups: cfg.o_groups,
7692 inter: cfg.moe_inter,
7693 n_experts: cfg.n_routed_experts,
7694 top_k: cfg.top_k,
7695 window: cfg.window,
7696 eps: cfg.norm_eps,
7697 hc_eps: cfg.hc_eps,
7698 sinkhorn_iters: cfg.hc_sinkhorn_iters,
7699 route_scale: cfg.route_scale,
7700 swiglu_limit: cfg.swiglu_limit,
7701 scale: (cfg.head_dim as f32).powf(-0.5),
7702 gu_q2: pack.gu_q2,
7703 dn_q2: pack.dn_q2,
7704 };
7705 let ids: Vec<u32> = (0..block)
7707 .map(|i| {
7708 if i == 0 {
7709 last_token
7710 } else {
7711 DSPARK_NOISE_TOKEN
7712 }
7713 })
7714 .collect();
7715 let mut states0 = vec![0.0f32; block * hc * dim];
7716 let mut emb = vec![0.0f32; dim];
7717 for (i, &id) in ids.iter().enumerate() {
7718 g.embed.row_f32(id as usize, &mut emb);
7719 for j in 0..hc {
7720 states0[(i * hc + j) * dim..(i * hc + j + 1) * dim].copy_from_slice(&emb);
7721 }
7722 }
7723 let dspark_time = {
7724 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7725 *ON.get_or_init(|| std::env::var("CMF_DSPARK_TIME").is_ok_and(|v| v != "0"))
7726 };
7727 let t0 = std::time::Instant::now();
7728 let filled = (pos + 1).min(cfg.window);
7729 let mut states = vec![0.0f32; block * hc * dim];
7730 if !crate::gpu_wgpu::dspark_graph(
7731 &model,
7732 &stages,
7733 geom,
7734 kv_id,
7735 mp_idx,
7736 mn,
7737 &ds.main_hidden,
7738 &states0,
7739 pos,
7740 filled,
7741 &g.inv_freq_window,
7742 block,
7743 &mut states,
7744 ) {
7745 return Vec::new();
7746 }
7747 for si in 0..mtp.len() {
7748 ds.filled[si] = filled;
7749 }
7750 let t_graph = t0.elapsed();
7751
7752 let last = &mtp[mtp.len() - 1];
7754 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
7755 last.hc_head_fn.as_ref(),
7756 last.hc_head_base.as_ref(),
7757 last.hc_head_scale,
7758 last.norm.as_ref(),
7759 ) else {
7760 return Vec::new();
7761 };
7762 let mut head_in = vec![0.0f32; block * dim];
7763 let mut pre_norms = vec![vec![0.0f32; dim]; block];
7764 for i in 0..block {
7765 hc_head_fold(
7766 &states[i * hc * dim..(i + 1) * hc * dim],
7767 hfn,
7768 hscale,
7769 hbase,
7770 cfg,
7771 pool,
7772 &mut head_in[i * dim..(i + 1) * dim],
7773 );
7774 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
7775 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
7776 }
7777 let t_fold = t0.elapsed();
7778 let mut logits = vec![0.0f32; block * cfg.vocab];
7779 let head_gpu = g.head.model_idx().is_some_and(|hi| {
7782 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
7783 &model,
7784 hi,
7785 &head_in,
7786 block,
7787 cfg.vocab,
7788 dim,
7789 &mut logits,
7790 )
7791 });
7792 if !head_gpu {
7793 g.head.matmat(&head_in, block, &mut logits, pool);
7794 }
7795 let t_head = t0.elapsed();
7796 let mut proposals = Vec::with_capacity(block);
7801 out_conf.clear();
7802 let mut prev = last_token;
7803 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
7804 let mut bias = vec![0.0f32; cfg.vocab];
7805 for i in 0..block {
7806 let row = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
7807 if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
7808 w1.row_f32(prev as usize, &mut mk_embed);
7809 w2.matvec(&mk_embed, &mut bias, pool);
7810 for (a, b) in row.iter_mut().zip(&bias) {
7811 *a += *b;
7812 }
7813 }
7814 let mut best = 0usize;
7815 for v in 1..row.len() {
7816 if row[v] > row[best] {
7817 best = v;
7818 }
7819 }
7820 if let Some(cf) = last.confidence.as_ref() {
7821 let mut cat = pre_norms[i].clone();
7822 cat.extend_from_slice(&mk_embed);
7823 let mut sc = [0.0f32; 1];
7824 if cat.len() == cf.cols() {
7825 cf.matvec(&cat, &mut sc, pool);
7826 }
7827 out_conf.push(sc[0]);
7828 }
7829 proposals.push(best as u32);
7830 prev = best as u32;
7831 }
7832 if dspark_time {
7833 eprintln!(
7834 "DSpark GPU: граф {:.1} мс, фолды {:.1}, голова {:.1}, марков+argmax {:.1}",
7835 t_graph.as_secs_f64() * 1e3,
7836 (t_fold - t_graph).as_secs_f64() * 1e3,
7837 (t_head - t_fold).as_secs_f64() * 1e3,
7838 (t0.elapsed() - t_head).as_secs_f64() * 1e3,
7839 );
7840 }
7841 proposals
7842}
7843
7844#[allow(clippy::too_many_arguments)]
7850pub fn dspark_draft(
7851 g: &Dsv4Globals,
7852 mtp: &[Dsv4Mtp],
7853 cfg: &Dsv4Cfg,
7854 ds: &mut DsparkState,
7855 last_token: u32,
7856 pos: usize,
7857 pool: Option<&crate::pool::Pool>,
7858 out_conf: &mut Vec<f32>,
7859) -> Vec<u32> {
7860 let (hc, dim, hd, rd) = (cfg.hc_mult, cfg.dim, cfg.head_dim, cfg.rope_head_dim);
7861 let block = dspark_block();
7862 let inv_freq = &g.inv_freq_window;
7863
7864 let Some(stage0) = mtp.first() else {
7866 return Vec::new();
7867 };
7868 let (Some(_mp), Some(_mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
7869 return Vec::new();
7870 };
7871 dspark_ring_append(g, mtp, cfg, ds, pos, pool);
7872
7873 let ids: Vec<u32> = (0..block)
7875 .map(|i| {
7876 if i == 0 {
7877 last_token
7878 } else {
7879 DSPARK_NOISE_TOKEN
7880 }
7881 })
7882 .collect();
7883 let mut states = vec![vec![0.0f32; hc * dim]; block];
7884 let mut emb = vec![0.0f32; dim];
7885 for (i, &id) in ids.iter().enumerate() {
7886 g.embed.row_f32(id as usize, &mut emb);
7887 for j in 0..hc {
7888 states[i][j * dim..(j + 1) * dim].copy_from_slice(&emb);
7889 }
7890 }
7891
7892 let mut scratch = HcScratch::new(cfg);
7893 for (si, m) in mtp.iter().enumerate() {
7894 let l = &m.layer;
7895 let kvw = l.wkv.rows();
7896 let mut post = vec![vec![0.0f32; hc]; block];
7899 let mut comb = vec![vec![0.0f32; hc * hc]; block];
7900 let mut resid = vec![vec![0.0f32; hc * dim]; block];
7901 let mut folded = vec![vec![0.0f32; dim]; block];
7902 let mix_hc = (2 + hc) * hc;
7903 for i in 0..block {
7904 hc_mixes(
7905 &states[i],
7906 &l.hc_attn_fn,
7907 mix_hc,
7908 cfg.norm_eps,
7909 pool,
7910 &mut scratch.mixes,
7911 );
7912 hc_split_sinkhorn(
7913 &scratch.mixes,
7914 &l.hc_attn_scale,
7915 &l.hc_attn_base,
7916 hc,
7917 cfg.hc_sinkhorn_iters,
7918 cfg.hc_eps,
7919 &mut scratch.pre,
7920 &mut post[i],
7921 &mut comb[i],
7922 );
7923 hc_fold(&states[i], &scratch.pre, hc, dim, &mut folded[i]);
7924 rms_weighted(&mut folded[i], &l.attn_norm, cfg.norm_eps);
7925 resid[i].copy_from_slice(&states[i]);
7926 }
7927 let folded_all: Vec<f32> = folded.iter().flatten().copied().collect();
7929 let mut blk_kv = vec![0.0f32; block * kvw];
7930 l.wkv.matmat(&folded_all, block, &mut blk_kv, pool);
7931 for i in 0..block {
7932 let dst = &mut blk_kv[i * kvw..(i + 1) * kvw];
7933 rms_weighted(dst, &l.kv_norm, cfg.norm_eps);
7934 rope_tail(&mut dst[kvw - hd..], inv_freq, pos + 1 + i, rd, false);
7935 }
7936 let win_len = ds.filled[si];
7938 let mut cache = Vec::with_capacity((win_len + block) * hd);
7939 for p in 0..win_len {
7940 let e = &ds.win[si][p * kvw..(p + 1) * kvw];
7941 cache.extend_from_slice(&e[kvw - hd..]);
7942 }
7943 for i in 0..block {
7944 let e = &blk_kv[i * kvw..(i + 1) * kvw];
7945 cache.extend_from_slice(&e[kvw - hd..]);
7946 }
7947 let idxs: Vec<usize> = (0..win_len + block).collect();
7948 let scale = (hd as f32).powf(-0.5);
7949 let qrank = l.wq_a.rows();
7950 let qdim = cfg.n_heads * hd;
7951 let mut qr = vec![0.0f32; block * qrank];
7952 l.wq_a.matmat(&folded_all, block, &mut qr, pool);
7953 for i in 0..block {
7954 rms_weighted(&mut qr[i * qrank..(i + 1) * qrank], &l.q_norm, cfg.norm_eps);
7955 }
7956 let mut q = vec![0.0f32; block * qdim];
7957 l.wq_b.matmat(&qr, block, &mut q, pool);
7958 let mut attn = vec![0.0f32; block * qdim];
7959 for i in 0..block {
7960 let qi = &mut q[i * qdim..(i + 1) * qdim];
7961 let ai = &mut attn[i * qdim..(i + 1) * qdim];
7962 let qpos = pos + 1 + i;
7963 for h in 0..cfg.n_heads {
7964 let head = &mut qi[h * hd..(h + 1) * hd];
7965 rms_inplace(head, cfg.norm_eps);
7966 rope_tail(head, inv_freq, qpos, rd, false);
7967 }
7968 for h in 0..cfg.n_heads {
7969 let qh = &qi[h * hd..(h + 1) * hd];
7970 let oh = &mut ai[h * hd..(h + 1) * hd];
7971 sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
7972 rope_tail(oh, inv_freq, qpos, rd, true);
7973 }
7974 }
7975 let mut blk_out = vec![0.0f32; block * dim];
7976 o_project_block(
7977 &attn,
7978 block,
7979 &l.wo_a,
7980 &l.wo_b,
7981 cfg.o_groups,
7982 cfg.o_lora_rank,
7983 pool,
7984 &mut blk_out,
7985 );
7986 for i in 0..block {
7987 let mut next = vec![0.0f32; hc * dim];
7988 hc_expand(
7989 &blk_out[i * dim..(i + 1) * dim],
7990 &resid[i],
7991 &post[i],
7992 &comb[i],
7993 hc,
7994 dim,
7995 &mut next,
7996 );
7997 states[i] = next;
7998 }
7999 let mut ffn_fold = vec![0.0f32; block * dim];
8002 let mut ffn_post = vec![vec![0.0f32; hc]; block];
8003 let mut ffn_comb = vec![vec![0.0f32; hc * hc]; block];
8004 let mut ffn_resid = vec![vec![0.0f32; hc * dim]; block];
8005 for i in 0..block {
8006 hc_mixes(
8007 &states[i],
8008 &l.hc_ffn_fn,
8009 mix_hc,
8010 cfg.norm_eps,
8011 pool,
8012 &mut scratch.mixes,
8013 );
8014 hc_split_sinkhorn(
8015 &scratch.mixes,
8016 &l.hc_ffn_scale,
8017 &l.hc_ffn_base,
8018 hc,
8019 cfg.hc_sinkhorn_iters,
8020 cfg.hc_eps,
8021 &mut scratch.pre,
8022 &mut ffn_post[i],
8023 &mut ffn_comb[i],
8024 );
8025 hc_fold(
8026 &states[i],
8027 &scratch.pre,
8028 hc,
8029 dim,
8030 &mut ffn_fold[i * dim..(i + 1) * dim],
8031 );
8032 rms_weighted(
8033 &mut ffn_fold[i * dim..(i + 1) * dim],
8034 &l.ffn_norm,
8035 cfg.norm_eps,
8036 );
8037 ffn_resid[i].copy_from_slice(&states[i]);
8038 }
8039 let mut moe_out = vec![0.0f32; block * dim];
8040 moe_step_block(&ffn_fold, block, l, cfg, &ids, si, pool, &mut moe_out);
8041 for i in 0..block {
8042 let mut next = vec![0.0f32; hc * dim];
8043 hc_expand(
8044 &moe_out[i * dim..(i + 1) * dim],
8045 &ffn_resid[i],
8046 &ffn_post[i],
8047 &ffn_comb[i],
8048 hc,
8049 dim,
8050 &mut next,
8051 );
8052 states[i] = next;
8053 }
8054 }
8055
8056 let last = &mtp[mtp.len() - 1];
8058 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
8059 last.hc_head_fn.as_ref(),
8060 last.hc_head_base.as_ref(),
8061 last.hc_head_scale,
8062 last.norm.as_ref(),
8063 ) else {
8064 return Vec::new();
8065 };
8066 let mut proposals = Vec::with_capacity(block);
8067 out_conf.clear();
8068 let mut prev = last_token;
8069 let mut head_in = vec![0.0f32; block * dim];
8070 let mut pre_norms = vec![vec![0.0f32; dim]; block];
8071 for i in 0..block {
8072 hc_head_fold(
8073 &states[i],
8074 hfn,
8075 hscale,
8076 hbase,
8077 cfg,
8078 pool,
8079 &mut head_in[i * dim..(i + 1) * dim],
8080 );
8081 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
8082 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
8083 }
8084 let mut logits = vec![0.0f32; block * cfg.vocab];
8085 g.head.matmat(&head_in, block, &mut logits, pool);
8086 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
8087 for i in 0..block {
8088 let logits_i = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
8089 let no_markov = {
8098 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8099 *ON.get_or_init(|| std::env::var("CMF_DSPARK_NO_MARKOV").is_ok_and(|v| v != "0"))
8100 };
8101 if no_markov {
8102 if let Some(w1) = last.markov_w1.as_ref() {
8104 w1.row_f32(prev as usize, &mut mk_embed);
8105 }
8106 } else if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
8107 w1.row_f32(prev as usize, &mut mk_embed);
8108 let mut bias = vec![0.0f32; cfg.vocab];
8109 w2.matvec(&mk_embed, &mut bias, pool);
8110 for (a, b) in logits_i.iter_mut().zip(&bias) {
8111 *a += *b;
8112 }
8113 }
8114 let mut best = 0usize;
8115 for v in 1..logits_i.len() {
8116 if logits_i[v] > logits_i[best] {
8117 best = v;
8118 }
8119 }
8120 if let Some(cf) = last.confidence.as_ref() {
8121 let mut cat = pre_norms[i].clone();
8122 cat.extend_from_slice(&mk_embed);
8123 let mut s = [0.0f32; 1];
8124 if cat.len() == cf.cols() {
8125 cf.matvec(&cat, &mut s, pool);
8126 }
8127 out_conf.push(s[0]);
8128 }
8129 proposals.push(best as u32);
8130 prev = best as u32;
8131 }
8132 proposals
8133}