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.iter().any(|&x| x == 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.iter().any(|&x| x == 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.iter().any(|&x| x == 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.iter().any(|&s| s == 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) * -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.28253591,
6580 0.09218107,
6581 0.025676856,
6582 0.17564717,
6583 0.22228767,
6584 0.27174541,
6585 0.33031881,
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| v >= 0.0 && v <= 2.0));
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([1.5f32, -2.0, 0.25])
6647 .take(hc)
6648 .flatten()
6649 .collect();
6650 let comb = {
6651 vec![0.25f32; hc * hc]
6653 };
6654 let post = vec![0.0f32; hc];
6655 let mut out = vec![0.0f32; hc * dim];
6656 hc_expand(&[0.0; 3], &residual, &post, &comb, hc, dim, &mut out);
6657 for (o, r) in out.iter().zip(&residual) {
6658 assert!((o - r).abs() < 1e-6, "{o} vs {r}");
6659 }
6660 }
6661
6662 #[test]
6666 fn selection_bias_steers_the_choice_but_not_the_weights() {
6667 let scores = [3.0f32, 0.1, 2.0, 0.05];
6668 let bias = [0.0f32, 10.0, 0.0, 0.0];
6669 let (mut idx, mut w) = (Vec::new(), Vec::new());
6670 route(&scores, Some(&bias), 2, 1.5, None, None, &mut idx, &mut w);
6671 assert_eq!(idx[0], 1, "the biased expert must win selection");
6672 assert_eq!(idx[1], 0);
6673 assert!(w[0] < w[1], "biased expert kept its own (small) weight");
6676 let sum: f32 = w.iter().sum();
6677 assert!((sum - 1.5).abs() < 1e-5, "weights renormalize then scale");
6678 }
6679
6680 #[test]
6684 fn attention_sink_drains_weight_without_contributing_output() {
6685 let hd = 2;
6686 let q = [1.0f32, 0.0];
6687 let kv = [1.0f32, 0.0, 0.0, 1.0];
6688 let mut out = vec![0.0f32; hd];
6689 sparse_attend(&q, &kv, &[0, 1], f32::NEG_INFINITY, 1.0, hd, &mut out);
6690 let plain = out.clone();
6691 assert!(plain[0] > plain[1], "the aligned key must dominate");
6692 sparse_attend(&q, &kv, &[0, 1], 20.0, 1.0, hd, &mut out);
6693 assert!(
6694 out[0] < plain[0] * 0.01 && out[1] < plain[1] * 0.01,
6695 "a large sink must drain nearly all the mass: {out:?}"
6696 );
6697 }
6698
6699 #[test]
6702 fn masked_positions_leave_the_denominator_alone() {
6703 let hd = 2;
6704 let q = [1.0f32, 0.0];
6705 let kv = [1.0f32, 0.0, 0.0, 1.0];
6706 let (mut a, mut b) = (vec![0.0f32; hd], vec![0.0f32; hd]);
6707 sparse_attend(&q, &kv, &[0], f32::NEG_INFINITY, 1.0, hd, &mut a);
6708 sparse_attend(
6709 &q,
6710 &kv,
6711 &[0, usize::MAX],
6712 f32::NEG_INFINITY,
6713 1.0,
6714 hd,
6715 &mut b,
6716 );
6717 for (x, y) in a.iter().zip(&b) {
6718 assert!((x - y).abs() < 1e-6, "{x} vs {y}");
6719 }
6720 }
6721
6722 #[test]
6725 fn rope_tail_inverts_itself() {
6726 let inv_freq = [1.0f32, 0.5];
6727 let orig = [9.0f32, 8.0, 1.0, 2.0, 3.0, 4.0];
6728 let mut v = orig;
6729 rope_tail(&mut v, &inv_freq, 7, 4, false);
6730 assert!(v[..2] == orig[..2], "the non-rope head must not move");
6731 assert!(v[2..] != orig[2..], "the tail must actually rotate");
6732 rope_tail(&mut v, &inv_freq, 7, 4, true);
6733 for (a, b) in v.iter().zip(&orig) {
6734 assert!((a - b).abs() < 1e-5, "{a} vs {b}");
6735 }
6736 }
6737
6738 #[test]
6741 fn compressor_pools_the_window_per_dimension() {
6742 let (ratio, width) = (2usize, 2usize);
6743 let kv = [1.0f32, 10.0, 3.0, 20.0];
6744 let score = [0.0f32, 0.0, 0.0, 50.0];
6746 let ape = vec![0.0f32; ratio * width];
6747 let mut out = vec![0.0f32; width];
6748 compress_window(&kv, &score, &ape, ratio, width, &mut out);
6749 assert!(
6750 (out[0] - 2.0).abs() < 1e-5,
6751 "equal scores average: {}",
6752 out[0]
6753 );
6754 assert!(
6755 (out[1] - 20.0).abs() < 1e-3,
6756 "a dominant score wins: {}",
6757 out[1]
6758 );
6759 }
6760
6761 #[test]
6764 fn index_scores_relu_before_weighting() {
6765 let (nh, hd) = (2usize, 2usize);
6766 let q = [1.0f32, 0.0, -1.0, 0.0];
6768 let kv = [1.0f32, 0.0, 0.0, 1.0];
6769 let w = [1.0f32, 1.0];
6770 let mut sc = Vec::new();
6771 index_scores(&q, &kv, &w, nh, hd, 2, 2, None, &mut sc);
6772 assert!(sc[0] > 0.9, "abstention, not veto: {:?}", sc);
6774 }
6775
6776 #[test]
6777 fn index_scores_mask_the_future() {
6778 let (nh, hd) = (1usize, 2usize);
6779 let q = [1.0f32, 0.0];
6780 let kv = [1.0f32, 0.0, 1.0, 0.0, 1.0, 0.0];
6781 let w = [1.0f32];
6782 let mut sc = Vec::new();
6783 index_scores(&q, &kv, &w, nh, hd, 3, 2, None, &mut sc);
6784 assert!(sc[0].is_finite() && sc[1].is_finite());
6785 assert!(sc[2] == f32::NEG_INFINITY, "position 2 is in the future");
6786 let mut idx = Vec::new();
6787 top_k_positions(&sc, 3, &mut idx);
6788 assert_eq!(idx, vec![0, 1], "a masked slot never wins a slot");
6789 }
6790
6791 #[test]
6792 fn top_k_is_deterministic_on_ties() {
6793 let sc = [1.0f32, 1.0, 1.0, 0.0];
6794 let mut idx = Vec::new();
6795 top_k_positions(&sc, 2, &mut idx);
6796 assert_eq!(idx, vec![0, 1], "ties resolve to the lower index");
6797 }
6798
6799 #[test]
6803 fn hc_block_preserves_the_copy_structure_and_applies_the_block() {
6804 let cfg = Dsv4Cfg {
6805 dim: 4,
6806 n_heads: 1,
6807 head_dim: 4,
6808 rope_head_dim: 2,
6809 q_lora_rank: 4,
6810 o_lora_rank: 2,
6811 o_groups: 1,
6812 hc_mult: 4,
6813 hc_sinkhorn_iters: 20,
6814 hc_eps: 1e-6,
6815 norm_eps: 1e-6,
6816 n_routed_experts: 2,
6817 top_k: 1,
6818 moe_inter: 4,
6819 route_scale: 1.0,
6820 swiglu_limit: 10.0,
6821 window: 128,
6822 index_topk: 4,
6823 vocab: 8,
6824 };
6825 let (hc, dim) = (cfg.hc_mult, cfg.dim);
6826 let mix_hc = (2 + hc) * hc;
6827 let hc_fn: Vec<f32> = (0..mix_hc * hc * dim)
6828 .map(|i| ((i % 13) as f32 - 6.0) * 0.05)
6829 .collect();
6830 let hc_base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.2).sin()).collect();
6831 let norm_w = vec![1.0f32; dim];
6832 let mut state: Vec<f32> = (0..hc * dim).map(|i| (i as f32 * 0.3).cos()).collect();
6833 let before = state.clone();
6834 let mut scratch = HcScratch::new(&cfg);
6835 hc_block(
6836 &mut state,
6837 &hc_fn,
6838 &[1.0, 1.0, 1.0],
6839 &hc_base,
6840 &norm_w,
6841 &cfg,
6842 &mut scratch,
6843 None,
6844 |_folded, out: &mut [f32]| out.iter_mut().for_each(|o| *o = 1.0),
6845 );
6846 assert_eq!(state.len(), before.len(), "copy structure must survive");
6847 assert!(state.iter().all(|v| v.is_finite()), "{state:?}");
6848 assert!(
6849 state.iter().zip(&before).any(|(a, b)| (a - b).abs() > 1e-4),
6850 "the block's output has to reach the state"
6851 );
6852 }
6853
6854 #[test]
6855 fn hash_route_reads_the_table_row() {
6856 let table = [7.0f32, 9.0, 1.0, 2.0, 5.0, 6.0];
6858 assert_eq!(hash_route(&table, 3, 2, 0), vec![7, 9]);
6859 assert_eq!(hash_route(&table, 3, 2, 2), vec![5, 6]);
6860 assert_eq!(hash_route(&table, 3, 2, 99), vec![5, 6]);
6862 }
6863
6864 #[test]
6869 fn a_task_mask_restricts_selection_and_renormalizes() {
6870 let scores = [0.1f32, 4.0, 1.0, 9.0];
6872 let (mut idx, mut w) = (Vec::new(), Vec::new());
6873 route(&scores, None, 2, 1.0, None, None, &mut idx, &mut w);
6874 assert_eq!(idx, vec![3, 1], "unmasked: the two best win");
6875 let sum: f32 = w.iter().sum();
6876 assert!((sum - 1.0).abs() < 1e-5, "weights must sum to route_scale");
6877
6878 let mask = [true, false, true, true];
6881 let (mut i2, mut w2) = (Vec::new(), Vec::new());
6882 route(&scores, None, 2, 1.0, None, Some(&mask), &mut i2, &mut w2);
6883 assert_eq!(i2, vec![3, 2], "masked expert must not be selected");
6884 let sum2: f32 = w2.iter().sum();
6885 assert!((sum2 - 1.0).abs() < 1e-5, "masked weights must renormalize");
6886
6887 let tight = [false, false, false, true];
6889 let (mut i3, mut w3) = (Vec::new(), Vec::new());
6890 route(&scores, None, 2, 1.0, None, Some(&tight), &mut i3, &mut w3);
6891 assert_eq!(i3, vec![3]);
6892 assert_eq!(w3.len(), 1);
6893 }
6894
6895 #[test]
6900 fn hash_layers_weight_the_experts_the_table_names() {
6901 let scores = [0.1f32, 0.4, 0.2, 5.0];
6903 let table = vec![0.0f32, 1.0];
6904 let idx_forced = hash_route(&table, 1, 2, 0);
6905 assert_eq!(idx_forced, vec![0, 1]);
6906
6907 let (mut idx, mut w) = (Vec::new(), Vec::new());
6908 route(
6909 &scores,
6910 None,
6911 2,
6912 1.0,
6913 Some(&idx_forced),
6914 None,
6915 &mut idx,
6916 &mut w,
6917 );
6918 assert_eq!(idx, vec![0, 1], "the table must decide the experts");
6919
6920 let sp = |x: f32| (1.0 + x.exp()).ln().sqrt();
6922 let (s0, s1) = (sp(scores[0]), sp(scores[1]));
6923 let tot = s0 + s1;
6924 assert!(
6925 (w[0] - s0 / tot).abs() < 1e-6,
6926 "w[0]={} want {}",
6927 w[0],
6928 s0 / tot
6929 );
6930 assert!(
6931 (w[1] - s1 / tot).abs() < 1e-6,
6932 "w[1]={} want {}",
6933 w[1],
6934 s1 / tot
6935 );
6936
6937 let (mut idx2, mut w2) = (Vec::new(), Vec::new());
6939 route(&scores, None, 2, 1.0, None, None, &mut idx2, &mut w2);
6940 assert_eq!(idx2[0], 3, "without a table the highest score still wins");
6941 }
6942}
6943
6944pub const DSPARK_NOISE_TOKEN: u32 = 128799;
6961pub fn dspark_block() -> usize {
6969 5
6970}
6971
6972pub fn dspark_verify_k() -> usize {
6979 static K: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
6980 *K.get_or_init(|| {
6981 std::env::var("CMF_DSPARK_VERIFY_K")
6982 .ok()
6983 .and_then(|v| v.parse::<usize>().ok())
6984 .filter(|&n| (1..=DSPARK_BLOCK_MAX).contains(&n))
6985 .unwrap_or(DSPARK_BLOCK_MAX)
6986 })
6987}
6988
6989pub const DSPARK_BLOCK_MAX: usize = 5;
6991
6992pub struct DsparkState {
6995 pub win: Vec<Vec<f32>>,
6997 pub filled: Vec<usize>,
6999 pub main_hidden: Vec<f32>,
7001 pub have_hidden: bool,
7003}
7004
7005impl DsparkState {
7006 pub fn new(stages: usize, cfg: &Dsv4Cfg, targets: usize) -> Self {
7007 Self {
7008 win: vec![Vec::new(); stages],
7009 filled: vec![0; stages],
7010 main_hidden: vec![0.0; cfg.dim * targets],
7011 have_hidden: false,
7012 }
7013 }
7014}
7015
7016pub fn dspark_targets(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, n_layers: usize) -> Vec<usize> {
7022 let Some(mp) = mtp.iter().find_map(|m| m.main_proj.as_ref()) else {
7023 return Vec::new();
7024 };
7025 let n = (mp.cols() / cfg.dim.max(1)).clamp(1, n_layers);
7026 (n_layers - n..n_layers).collect()
7027}
7028
7029thread_local! {
7030 static DSPARK_CAP: std::cell::RefCell<(Vec<usize>, Vec<f32>, usize)> =
7036 const { std::cell::RefCell::new((Vec::new(), Vec::new(), 0)) };
7037}
7038
7039pub fn dspark_arm(targets: &[usize], dim: usize) {
7041 DSPARK_CAP.with(|c| {
7042 let mut c = c.borrow_mut();
7043 c.0 = targets.to_vec();
7044 c.1 = vec![0.0; dim * targets.len()];
7045 c.2 = 0;
7046 });
7047}
7048
7049fn dspark_wants(li: usize) -> bool {
7053 DSPARK_CAP.with(|c| c.borrow().0.contains(&li))
7054}
7055
7056pub fn dspark_note(li: usize, state: &[f32], cfg: &Dsv4Cfg) {
7058 DSPARK_CAP.with(|c| {
7059 let mut c = c.borrow_mut();
7060 if c.0.is_empty() {
7061 return;
7062 }
7063 if let Some(slot) = c.0.iter().position(|&t| t == li) {
7064 let (_, buf, seen) = &mut *c;
7065 dspark_capture(state, cfg, slot, buf);
7066 *seen = if slot == 0 { 1 } else { *seen + 1 };
7071 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
7072 eprintln!("[cap] note li={li} slot={slot} seen={}", *seen);
7073 }
7074 }
7075 });
7076}
7077
7078pub fn dspark_peek_slot(slot: usize, dim: usize, out: &mut [f32]) -> bool {
7083 DSPARK_CAP.with(|c| {
7084 let c = c.borrow();
7085 let lo = slot * dim;
7086 if c.1.len() < lo + dim {
7087 return false;
7088 }
7089 out[..dim].copy_from_slice(&c.1[lo..lo + dim]);
7090 true
7091 })
7092}
7093
7094pub fn dspark_take(out: &mut Vec<f32>) -> bool {
7096 DSPARK_CAP.with(|c| {
7097 let mut c = c.borrow_mut();
7098 if c.0.is_empty() || c.2 != c.0.len() {
7099 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
7100 eprintln!("[cap] take FAIL armed={:?} seen={}", c.0, c.2);
7101 }
7102 return false;
7103 }
7104 out.clear();
7105 out.extend_from_slice(&c.1);
7106 c.2 = 0;
7107 true
7108 })
7109}
7110
7111pub fn dspark_capture(state: &[f32], cfg: &Dsv4Cfg, slot: usize, out: &mut [f32]) {
7115 let (hc, dim) = (cfg.hc_mult, cfg.dim);
7116 let dst = &mut out[slot * dim..(slot + 1) * dim];
7117 let inv = 1.0 / hc as f32;
7118 for d in 0..dim {
7119 let mut s = 0.0;
7120 for j in 0..hc {
7121 s += state[j * dim + d];
7122 }
7123 dst[d] = s * inv;
7124 }
7125}
7126
7127pub fn dspark_freq_note(picks: &[(usize, Vec<usize>)]) {
7132 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
7133 std::sync::Mutex::new(None);
7134 let Ok(path) = std::env::var("CMF_DSPARK_PICK_DUMP") else {
7135 return;
7136 };
7137 let mut g = FREQ.lock().unwrap();
7138 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
7139 for (stage, idx) in picks {
7140 for &e in idx {
7141 *map.entry((*stage, e)).or_insert(0) += 1;
7142 }
7143 }
7144 *blocks += 1;
7145 if *blocks % 32 == 0 {
7146 let mut lines: Vec<_> = map.iter().collect();
7147 lines.sort();
7148 let body: String = lines
7149 .iter()
7150 .map(|((s, e), n)| format!("{s}\t{e}\t{n}\n"))
7151 .collect();
7152 let _ = std::fs::write(&path, body);
7153 }
7154}
7155
7156pub fn trunk_freq_note(picks: &[(usize, Vec<usize>)]) {
7161 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
7162 std::sync::Mutex::new(None);
7163 let Ok(path) = std::env::var("CMF_DSV4_TRUNK_PICK_DUMP") else {
7164 return;
7165 };
7166 let mut g = FREQ.lock().unwrap();
7167 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
7168 for (li, idx) in picks {
7169 for &e in idx {
7170 *map.entry((*li, e)).or_insert(0) += 1;
7171 }
7172 }
7173 *blocks += 1;
7174 if *blocks % 32 == 0 {
7175 let mut lines: Vec<_> = map.iter().collect();
7176 lines.sort();
7177 let body: String = lines
7178 .iter()
7179 .map(|((l, e), n)| format!("{l}\t{e}\t{n}\n"))
7180 .collect();
7181 let _ = std::fs::write(&path, body);
7182 }
7183}
7184
7185fn dspark_apply_mask(out: &mut [Dsv4Mtp]) {
7193 let Ok(path) = std::env::var("CMF_DSPARK_MASK") else {
7194 return;
7195 };
7196 let Ok(text) = std::fs::read_to_string(&path) else {
7197 eprintln!("DSpark: CMF_DSPARK_MASK={path} не читается — маска не применена");
7198 return;
7199 };
7200 for (d, line) in text.lines().enumerate() {
7201 let Some(m) = out.get_mut(d) else { break };
7202 let n = m.layer.experts.len();
7203 let mut mask = vec![false; n];
7204 let mut kept = 0usize;
7205 for tok in line.split(',') {
7206 if let Ok(e) = tok.trim().parse::<usize>() {
7207 if e < n && !mask[e] {
7208 mask[e] = true;
7209 kept += 1;
7210 }
7211 }
7212 }
7213 if kept == 0 {
7214 continue;
7215 }
7216 eprintln!("DSpark: стадия {d} ограничена {kept}/{n} экспертами");
7217 m.layer.mask = Some(mask);
7218 }
7219}
7220
7221#[cfg(feature = "gpu")]
7231pub struct DsparkPack {
7232 pub stages: Vec<DsparkStagePack>,
7233 pub gu_q2: bool,
7236 pub dn_q2: bool,
7239 pub routers: Vec<Vec<f32>>,
7242 pub biases: Vec<Option<Vec<f32>>>,
7243}
7244
7245#[cfg(feature = "gpu")]
7246pub struct DsparkStagePack {
7247 pub mask: Vec<bool>,
7249 pub to_slot: Vec<usize>,
7251 pub mask_u32: Vec<u32>,
7254 pub map_u32: Vec<u32>,
7255 pub tensors: Vec<(usize, usize, usize)>,
7257 pub n_resident: usize,
7258}
7259
7260pub static DSPARK_Q2TP_ENCODE: std::sync::OnceLock<fn(&[f32], usize, usize) -> Vec<u8>> =
7267 std::sync::OnceLock::new();
7268
7269#[cfg(feature = "gpu")]
7272pub fn dspark_gpu_on() -> bool {
7273 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7274 *ON.get_or_init(|| {
7275 std::env::var("CMF_DSPARK_GPU")
7276 .map(|v| v != "0")
7277 .unwrap_or(true)
7278 })
7279}
7280
7281#[cfg(feature = "gpu")]
7283pub fn dspark_pack_get(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<&'static DsparkPack> {
7284 static P: std::sync::OnceLock<Option<Box<DsparkPack>>> = std::sync::OnceLock::new();
7285 P.get_or_init(|| dspark_pack_build(mtp, cfg).map(Box::new))
7286 .as_deref()
7287}
7288
7289#[cfg(feature = "gpu")]
7304pub fn dspark_reserve_note(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, layers: &[Dsv4Layer]) {
7305 if mtp.is_empty() || std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "0") || !dspark_gpu_on()
7306 {
7307 return;
7308 }
7309 let dt = |q2: bool| {
7310 if q2 {
7311 cortiq_core::TensorDtype::Q2TiledP
7312 } else {
7313 cortiq_core::TensorDtype::Q4TiledP
7314 }
7315 };
7316 let gu_q2 = mtp[0]
7317 .layer
7318 .experts
7319 .first()
7320 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
7321 let dn_q2 = mtp[0]
7322 .layer
7323 .experts
7324 .first()
7325 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
7326 let gu = cortiq_core::quant::expected_nbytes(dt(gu_q2), &[cfg.moe_inter, cfg.dim]).unwrap_or(0);
7327 let dn = cortiq_core::quant::expected_nbytes(dt(dn_q2), &[cfg.dim, cfg.moe_inter]).unwrap_or(0);
7328 let per = (2 * gu + dn) as u64;
7329 let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
7330 .ok()
7331 .and_then(|v| v.parse().ok())
7332 .unwrap_or(40);
7335 let bytes = per * (n_res * mtp.len() + mtp.len() + 1) as u64;
7337 let trunk: u64 = layers
7339 .iter()
7340 .map(|l| {
7341 let Some(e) = l.experts.first() else { return 0 };
7342 let gu = cortiq_core::quant::expected_nbytes(
7343 dt(e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
7344 &[cfg.moe_inter, cfg.dim],
7345 )
7346 .unwrap_or(0);
7347 let dn = cortiq_core::quant::expected_nbytes(
7348 dt(e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
7349 &[cfg.dim, cfg.moe_inter],
7350 )
7351 .unwrap_or(0);
7352 ((2 * gu + dn) * (l.experts.len() + 1)) as u64
7353 })
7354 .sum();
7355 if let Some(budget) = crate::gpu_wgpu::dsv4_vram_budget() {
7356 if budget < trunk / 10 * 9 + bytes {
7357 return;
7358 }
7359 }
7360 crate::gpu_wgpu::DRAFT_RESERVE.store(bytes, std::sync::atomic::Ordering::Relaxed);
7361}
7362
7363#[cfg(not(feature = "gpu"))]
7364pub fn dspark_reserve_note(_mtp: &[Dsv4Mtp], _cfg: &Dsv4Cfg, _layers: &[Dsv4Layer]) {}
7365
7366#[cfg(feature = "gpu")]
7367pub fn dspark_pack_build(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<DsparkPack> {
7368 if mtp.is_empty() {
7369 return None;
7370 }
7371 let n_res: usize =
7372 std::env::var("CMF_DSPARK_RESIDENT")
7373 .ok()
7374 .and_then(|v| v.parse().ok())
7375 .unwrap_or_else(|| {
7376 let native_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
7382 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
7383 });
7384 let gu_q2 = native_q2 || DSPARK_Q2TP_ENCODE.get().is_some();
7385 let dn_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
7386 e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
7387 });
7388 let room = crate::gpu_wgpu::dsv4_draft_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2);
7389 (room.saturating_sub(mtp.len() + 1) / mtp.len().max(1)).clamp(8, 64)
7390 });
7391 let mut freq: Vec<Vec<(u64, usize)>> = vec![Vec::new(); mtp.len()];
7395 let pack_path = std::env::var("CMF_DSPARK_PACK").ok().or_else(|| {
7396 let m = mtp[0].layer.experts.first()?.w1.model_arc()?;
7397 let mut s = m.path.as_os_str().to_os_string();
7398 s.push(".dspark.tsv");
7399 let p = std::path::PathBuf::from(s);
7400 p.exists().then(|| p.to_string_lossy().into_owned())
7401 });
7402 if let Some(path) = pack_path {
7403 if let Ok(text) = std::fs::read_to_string(&path) {
7404 for line in text.lines() {
7405 let mut it = line.split_whitespace();
7406 if let (Some(s), Some(e), Some(n)) = (it.next(), it.next(), it.next()) {
7407 if let (Ok(s), Ok(e), Ok(n)) =
7408 (s.parse::<usize>(), e.parse::<usize>(), n.parse::<u64>())
7409 {
7410 if s < freq.len() {
7411 freq[s].push((n, e));
7412 }
7413 }
7414 }
7415 }
7416 }
7417 }
7418 let mut stages = Vec::with_capacity(mtp.len());
7419 let mut routers = Vec::with_capacity(mtp.len());
7420 let mut biases = Vec::with_capacity(mtp.len());
7421 for (si, m) in mtp.iter().enumerate() {
7422 let l = &m.layer;
7423 let n = l.experts.len();
7424 let mut order: Vec<usize> = {
7427 let mut f = freq[si].clone();
7428 f.sort_by(|a, b| b.0.cmp(&a.0));
7429 let mut seen = vec![false; n];
7430 let mut o: Vec<usize> = f
7431 .into_iter()
7432 .map(|(_, e)| e)
7433 .filter(|&e| {
7434 if e < n && !seen[e] {
7435 seen[e] = true;
7436 true
7437 } else {
7438 false
7439 }
7440 })
7441 .collect();
7442 o.extend((0..n).filter(|&e| !seen[e]));
7443 o
7444 };
7445 order.truncate(n_res.min(n));
7446 let mut mask = vec![false; n];
7447 let mut to_slot = vec![usize::MAX; n];
7448 let mut tensors = Vec::with_capacity(order.len() + 1);
7449 for (slot, &e) in order.iter().enumerate() {
7450 let ex = &l.experts[e];
7451 let (Some(w1), Some(w3), Some(w2)) =
7452 (ex.w1.model_idx(), ex.w3.model_idx(), ex.w2.model_idx())
7453 else {
7454 return None;
7455 };
7456 mask[e] = true;
7457 to_slot[e] = slot;
7458 tensors.push((w1, w3, w2));
7459 }
7460 let (Some(s1), Some(s3), Some(s2)) = (
7461 l.shared.w1.model_idx(),
7462 l.shared.w3.model_idx(),
7463 l.shared.w2.model_idx(),
7464 ) else {
7465 return None;
7466 };
7467 tensors.push((s1, s3, s2));
7468 let mut router = vec![0.0f32; n * cfg.dim];
7470 for (r, row) in (0..n).zip(router.chunks_mut(cfg.dim)) {
7471 l.gate.row_f32(r, row);
7472 }
7473 routers.push(router);
7474 biases.push(l.gate_bias.clone());
7475 let mask_u32: Vec<u32> = mask.iter().map(|&m| m as u32).collect();
7476 let map_u32: Vec<u32> = to_slot
7477 .iter()
7478 .map(|&x| if x == usize::MAX { u32::MAX } else { x as u32 })
7479 .collect();
7480 stages.push(DsparkStagePack {
7481 mask,
7482 to_slot,
7483 mask_u32,
7484 map_u32,
7485 tensors,
7486 n_resident: order.len(),
7487 });
7488 }
7489 let model = mtp[0]
7493 .layer
7494 .experts
7495 .first()
7496 .and_then(|e| e.w1.model_arc())?;
7497 let mut skeleton = Vec::new();
7498 for m in mtp {
7499 let l = &m.layer;
7500 for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b] {
7501 skeleton.push(t.model_idx()?);
7502 }
7503 }
7504 if let Some(mp) = mtp[0].main_proj.as_ref() {
7505 skeleton.push(mp.model_idx()?);
7506 }
7507 for &idx in &skeleton {
7508 if !crate::gpu_wgpu::dsv4_weight_ready(&model, idx) {
7509 eprintln!("DSpark: скелет драфта не влез в VRAM — GPU-черновик выключен");
7510 return None;
7511 }
7512 }
7513 let native_q2 = mtp[0]
7519 .layer
7520 .experts
7521 .first()
7522 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
7523 let gu_q2 = native_q2 || crate::dsv4::DSPARK_Q2TP_ENCODE.get().is_some();
7524 let dn_native = mtp[0]
7525 .layer
7526 .experts
7527 .first()
7528 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
7529 for (si, sp) in stages.iter().enumerate() {
7530 let ok = if native_q2 {
7531 crate::gpu_wgpu::dsv4_experts_ready(
7532 &model,
7533 &sp.tensors,
7534 cfg.moe_inter,
7535 cfg.dim,
7536 true,
7537 dn_native,
7538 )
7539 } else if gu_q2 {
7540 crate::gpu_wgpu::moe_expert_bufs_requant_gu(&model, &sp.tensors, cfg.moe_inter, cfg.dim)
7541 .is_some()
7542 } else {
7543 crate::gpu_wgpu::dsv4_experts_ready(
7544 &model,
7545 &sp.tensors,
7546 cfg.moe_inter,
7547 cfg.dim,
7548 false,
7549 false,
7550 )
7551 };
7552 if !ok {
7553 eprintln!(
7554 "DSpark: эксперты стадии {si} ({} + shared) не влезли в VRAM — GPU-черновик выключен",
7555 sp.n_resident
7556 );
7557 return None;
7558 }
7559 }
7560 let _ = crate::gpu_wgpu::pin_weights(&model, &skeleton);
7561 eprintln!(
7562 "DSpark: пак драфта на карте — {} стадии по {} экспертов + shared",
7563 stages.len(),
7564 stages
7565 .iter()
7566 .map(|s| s.n_resident.to_string())
7567 .collect::<Vec<_>>()
7568 .join("/")
7569 );
7570 Some(DsparkPack {
7571 stages,
7572 gu_q2,
7573 dn_q2: dn_native,
7574 routers,
7575 biases,
7576 })
7577}
7578
7579pub fn dspark_ring_append(
7586 g: &Dsv4Globals,
7587 mtp: &[Dsv4Mtp],
7588 cfg: &Dsv4Cfg,
7589 ds: &mut DsparkState,
7590 pos: usize,
7591 pool: Option<&crate::pool::Pool>,
7592) {
7593 let (dim, hd, rd) = (cfg.dim, cfg.head_dim, cfg.rope_head_dim);
7594 let inv_freq = &g.inv_freq_window;
7595 let Some(stage0) = mtp.first() else { return };
7596 let (Some(mp), Some(mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
7597 return;
7598 };
7599 let mut main_x = vec![0.0f32; dim];
7600 mp.matvec(&ds.main_hidden, &mut main_x, pool);
7601 rms_weighted(&mut main_x, mn, cfg.norm_eps);
7602 for (si, m) in mtp.iter().enumerate() {
7603 let kvw = m.layer.wkv.rows();
7604 if ds.win[si].len() < cfg.window * kvw {
7605 ds.win[si].resize(cfg.window * kvw, 0.0);
7606 }
7607 let mut kv = vec![0.0f32; kvw];
7608 m.layer.wkv.matvec(&main_x, &mut kv, pool);
7609 rms_weighted(&mut kv, &m.layer.kv_norm, cfg.norm_eps);
7610 rope_tail(&mut kv[kvw - hd..], inv_freq, pos, rd, false);
7611 let slot = pos % cfg.window;
7612 ds.win[si][slot * kvw..(slot + 1) * kvw].copy_from_slice(&kv);
7613 ds.filled[si] = (pos + 1).min(cfg.window);
7614 }
7615}
7616
7617#[cfg(feature = "gpu")]
7623#[allow(clippy::too_many_arguments)]
7624pub fn dspark_draft_gpu(
7625 g: &Dsv4Globals,
7626 mtp: &[Dsv4Mtp],
7627 cfg: &Dsv4Cfg,
7628 ds: &mut DsparkState,
7629 pack: &DsparkPack,
7630 kv_id: u64,
7631 last_token: u32,
7632 pos: usize,
7633 pool: Option<&crate::pool::Pool>,
7634 out_conf: &mut Vec<f32>,
7635) -> Vec<u32> {
7636 let (hc, dim) = (cfg.hc_mult, cfg.dim);
7637 let block = dspark_block();
7638 let Some(model) = mtp[0].layer.experts.first().and_then(|e| e.w1.model_arc()) else {
7639 return Vec::new();
7640 };
7641 let (Some(mp), Some(mn)) = (mtp[0].main_proj.as_ref(), mtp[0].main_norm.as_ref()) else {
7642 return Vec::new();
7643 };
7644 let Some(mp_idx) = mp.model_idx() else {
7645 return Vec::new();
7646 };
7647 let mut stages = Vec::with_capacity(mtp.len());
7648 for (si, m) in mtp.iter().enumerate() {
7649 let l = &m.layer;
7650 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
7651 l.wq_a.model_idx(),
7652 l.wq_b.model_idx(),
7653 l.wo_a.model_idx(),
7654 l.wo_b.model_idx(),
7655 l.wkv.model_idx(),
7656 ) else {
7657 return Vec::new();
7658 };
7659 let sp = &pack.stages[si];
7660 stages.push(crate::gpu_wgpu::DsparkStageW {
7661 wq_a,
7662 wq_b,
7663 wo_a,
7664 wo_b,
7665 wkv,
7666 q_norm: &l.q_norm,
7667 kv_norm: &l.kv_norm,
7668 attn_norm: &l.attn_norm,
7669 ffn_norm: &l.ffn_norm,
7670 sink: &l.attn_sink,
7671 hc_attn_fn: &l.hc_attn_fn,
7672 hc_attn_scale: &l.hc_attn_scale,
7673 hc_attn_base: &l.hc_attn_base,
7674 hc_ffn_fn: &l.hc_ffn_fn,
7675 hc_ffn_scale: &l.hc_ffn_scale,
7676 hc_ffn_base: &l.hc_ffn_base,
7677 router: &pack.routers[si],
7678 bias: pack.biases[si].as_deref(),
7679 experts: &sp.tensors,
7680 mask_u32: &sp.mask_u32,
7681 map_u32: &sp.map_u32,
7682 });
7683 }
7684 let geom = crate::gpu_wgpu::DsparkGeom {
7685 dim,
7686 hc,
7687 nh: cfg.n_heads,
7688 hd: cfg.head_dim,
7689 rd: cfg.rope_head_dim,
7690 q_lora: cfg.q_lora_rank,
7691 o_lora: cfg.o_lora_rank,
7692 o_groups: cfg.o_groups,
7693 inter: cfg.moe_inter,
7694 n_experts: cfg.n_routed_experts,
7695 top_k: cfg.top_k,
7696 window: cfg.window,
7697 eps: cfg.norm_eps,
7698 hc_eps: cfg.hc_eps,
7699 sinkhorn_iters: cfg.hc_sinkhorn_iters,
7700 route_scale: cfg.route_scale,
7701 swiglu_limit: cfg.swiglu_limit,
7702 scale: (cfg.head_dim as f32).powf(-0.5),
7703 gu_q2: pack.gu_q2,
7704 dn_q2: pack.dn_q2,
7705 };
7706 let ids: Vec<u32> = (0..block)
7708 .map(|i| {
7709 if i == 0 {
7710 last_token
7711 } else {
7712 DSPARK_NOISE_TOKEN
7713 }
7714 })
7715 .collect();
7716 let mut states0 = vec![0.0f32; block * hc * dim];
7717 let mut emb = vec![0.0f32; dim];
7718 for (i, &id) in ids.iter().enumerate() {
7719 g.embed.row_f32(id as usize, &mut emb);
7720 for j in 0..hc {
7721 states0[(i * hc + j) * dim..(i * hc + j + 1) * dim].copy_from_slice(&emb);
7722 }
7723 }
7724 let dspark_time = {
7725 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7726 *ON.get_or_init(|| std::env::var("CMF_DSPARK_TIME").is_ok_and(|v| v != "0"))
7727 };
7728 let t0 = std::time::Instant::now();
7729 let filled = (pos + 1).min(cfg.window);
7730 let mut states = vec![0.0f32; block * hc * dim];
7731 if !crate::gpu_wgpu::dspark_graph(
7732 &model,
7733 &stages,
7734 geom,
7735 kv_id,
7736 mp_idx,
7737 mn,
7738 &ds.main_hidden,
7739 &states0,
7740 pos,
7741 filled,
7742 &g.inv_freq_window,
7743 block,
7744 &mut states,
7745 ) {
7746 return Vec::new();
7747 }
7748 for si in 0..mtp.len() {
7749 ds.filled[si] = filled;
7750 }
7751 let t_graph = t0.elapsed();
7752
7753 let last = &mtp[mtp.len() - 1];
7755 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
7756 last.hc_head_fn.as_ref(),
7757 last.hc_head_base.as_ref(),
7758 last.hc_head_scale,
7759 last.norm.as_ref(),
7760 ) else {
7761 return Vec::new();
7762 };
7763 let mut head_in = vec![0.0f32; block * dim];
7764 let mut pre_norms = vec![vec![0.0f32; dim]; block];
7765 for i in 0..block {
7766 hc_head_fold(
7767 &states[i * hc * dim..(i + 1) * hc * dim],
7768 hfn,
7769 hscale,
7770 hbase,
7771 cfg,
7772 pool,
7773 &mut head_in[i * dim..(i + 1) * dim],
7774 );
7775 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
7776 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
7777 }
7778 let t_fold = t0.elapsed();
7779 let mut logits = vec![0.0f32; block * cfg.vocab];
7780 let head_gpu = g.head.model_idx().is_some_and(|hi| {
7783 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
7784 &model,
7785 hi,
7786 &head_in,
7787 block,
7788 cfg.vocab,
7789 dim,
7790 &mut logits,
7791 )
7792 });
7793 if !head_gpu {
7794 g.head.matmat(&head_in, block, &mut logits, pool);
7795 }
7796 let t_head = t0.elapsed();
7797 let mut proposals = Vec::with_capacity(block);
7802 out_conf.clear();
7803 let mut prev = last_token;
7804 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
7805 let mut bias = vec![0.0f32; cfg.vocab];
7806 for i in 0..block {
7807 let row = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
7808 if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
7809 w1.row_f32(prev as usize, &mut mk_embed);
7810 w2.matvec(&mk_embed, &mut bias, pool);
7811 for (a, b) in row.iter_mut().zip(&bias) {
7812 *a += *b;
7813 }
7814 }
7815 let mut best = 0usize;
7816 for v in 1..row.len() {
7817 if row[v] > row[best] {
7818 best = v;
7819 }
7820 }
7821 if let Some(cf) = last.confidence.as_ref() {
7822 let mut cat = pre_norms[i].clone();
7823 cat.extend_from_slice(&mk_embed);
7824 let mut sc = [0.0f32; 1];
7825 if cat.len() == cf.cols() {
7826 cf.matvec(&cat, &mut sc, pool);
7827 }
7828 out_conf.push(sc[0]);
7829 }
7830 proposals.push(best as u32);
7831 prev = best as u32;
7832 }
7833 if dspark_time {
7834 eprintln!(
7835 "DSpark GPU: граф {:.1} мс, фолды {:.1}, голова {:.1}, марков+argmax {:.1}",
7836 t_graph.as_secs_f64() * 1e3,
7837 (t_fold - t_graph).as_secs_f64() * 1e3,
7838 (t_head - t_fold).as_secs_f64() * 1e3,
7839 (t0.elapsed() - t_head).as_secs_f64() * 1e3,
7840 );
7841 }
7842 proposals
7843}
7844
7845#[allow(clippy::too_many_arguments)]
7851pub fn dspark_draft(
7852 g: &Dsv4Globals,
7853 mtp: &[Dsv4Mtp],
7854 cfg: &Dsv4Cfg,
7855 ds: &mut DsparkState,
7856 last_token: u32,
7857 pos: usize,
7858 pool: Option<&crate::pool::Pool>,
7859 out_conf: &mut Vec<f32>,
7860) -> Vec<u32> {
7861 let (hc, dim, hd, rd) = (cfg.hc_mult, cfg.dim, cfg.head_dim, cfg.rope_head_dim);
7862 let block = dspark_block();
7863 let inv_freq = &g.inv_freq_window;
7864
7865 let Some(stage0) = mtp.first() else {
7867 return Vec::new();
7868 };
7869 let (Some(mp), Some(mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
7870 return Vec::new();
7871 };
7872 dspark_ring_append(g, mtp, cfg, ds, pos, pool);
7873
7874 let ids: Vec<u32> = (0..block)
7876 .map(|i| {
7877 if i == 0 {
7878 last_token
7879 } else {
7880 DSPARK_NOISE_TOKEN
7881 }
7882 })
7883 .collect();
7884 let mut states = vec![vec![0.0f32; hc * dim]; block];
7885 let mut emb = vec![0.0f32; dim];
7886 for (i, &id) in ids.iter().enumerate() {
7887 g.embed.row_f32(id as usize, &mut emb);
7888 for j in 0..hc {
7889 states[i][j * dim..(j + 1) * dim].copy_from_slice(&emb);
7890 }
7891 }
7892
7893 let mut scratch = HcScratch::new(cfg);
7894 for (si, m) in mtp.iter().enumerate() {
7895 let l = &m.layer;
7896 let kvw = l.wkv.rows();
7897 let mut post = vec![vec![0.0f32; hc]; block];
7900 let mut comb = vec![vec![0.0f32; hc * hc]; block];
7901 let mut resid = vec![vec![0.0f32; hc * dim]; block];
7902 let mut folded = vec![vec![0.0f32; dim]; block];
7903 let mix_hc = (2 + hc) * hc;
7904 for i in 0..block {
7905 hc_mixes(
7906 &states[i],
7907 &l.hc_attn_fn,
7908 mix_hc,
7909 cfg.norm_eps,
7910 pool,
7911 &mut scratch.mixes,
7912 );
7913 hc_split_sinkhorn(
7914 &scratch.mixes,
7915 &l.hc_attn_scale,
7916 &l.hc_attn_base,
7917 hc,
7918 cfg.hc_sinkhorn_iters,
7919 cfg.hc_eps,
7920 &mut scratch.pre,
7921 &mut post[i],
7922 &mut comb[i],
7923 );
7924 hc_fold(&states[i], &scratch.pre, hc, dim, &mut folded[i]);
7925 rms_weighted(&mut folded[i], &l.attn_norm, cfg.norm_eps);
7926 resid[i].copy_from_slice(&states[i]);
7927 }
7928 let folded_all: Vec<f32> = folded.iter().flatten().copied().collect();
7930 let mut blk_kv = vec![0.0f32; block * kvw];
7931 l.wkv.matmat(&folded_all, block, &mut blk_kv, pool);
7932 for i in 0..block {
7933 let dst = &mut blk_kv[i * kvw..(i + 1) * kvw];
7934 rms_weighted(dst, &l.kv_norm, cfg.norm_eps);
7935 rope_tail(&mut dst[kvw - hd..], inv_freq, pos + 1 + i, rd, false);
7936 }
7937 let win_len = ds.filled[si];
7939 let mut cache = Vec::with_capacity((win_len + block) * hd);
7940 for p in 0..win_len {
7941 let e = &ds.win[si][p * kvw..(p + 1) * kvw];
7942 cache.extend_from_slice(&e[kvw - hd..]);
7943 }
7944 for i in 0..block {
7945 let e = &blk_kv[i * kvw..(i + 1) * kvw];
7946 cache.extend_from_slice(&e[kvw - hd..]);
7947 }
7948 let idxs: Vec<usize> = (0..win_len + block).collect();
7949 let scale = (hd as f32).powf(-0.5);
7950 let qrank = l.wq_a.rows();
7951 let qdim = cfg.n_heads * hd;
7952 let mut qr = vec![0.0f32; block * qrank];
7953 l.wq_a.matmat(&folded_all, block, &mut qr, pool);
7954 for i in 0..block {
7955 rms_weighted(&mut qr[i * qrank..(i + 1) * qrank], &l.q_norm, cfg.norm_eps);
7956 }
7957 let mut q = vec![0.0f32; block * qdim];
7958 l.wq_b.matmat(&qr, block, &mut q, pool);
7959 let mut attn = vec![0.0f32; block * qdim];
7960 for i in 0..block {
7961 let qi = &mut q[i * qdim..(i + 1) * qdim];
7962 let ai = &mut attn[i * qdim..(i + 1) * qdim];
7963 let qpos = pos + 1 + i;
7964 for h in 0..cfg.n_heads {
7965 let head = &mut qi[h * hd..(h + 1) * hd];
7966 rms_inplace(head, cfg.norm_eps);
7967 rope_tail(head, inv_freq, qpos, rd, false);
7968 }
7969 for h in 0..cfg.n_heads {
7970 let qh = &qi[h * hd..(h + 1) * hd];
7971 let oh = &mut ai[h * hd..(h + 1) * hd];
7972 sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
7973 rope_tail(oh, inv_freq, qpos, rd, true);
7974 }
7975 }
7976 let mut blk_out = vec![0.0f32; block * dim];
7977 o_project_block(
7978 &attn,
7979 block,
7980 &l.wo_a,
7981 &l.wo_b,
7982 cfg.o_groups,
7983 cfg.o_lora_rank,
7984 pool,
7985 &mut blk_out,
7986 );
7987 for i in 0..block {
7988 let mut next = vec![0.0f32; hc * dim];
7989 hc_expand(
7990 &blk_out[i * dim..(i + 1) * dim],
7991 &resid[i],
7992 &post[i],
7993 &comb[i],
7994 hc,
7995 dim,
7996 &mut next,
7997 );
7998 states[i] = next;
7999 }
8000 let mut ffn_fold = vec![0.0f32; block * dim];
8003 let mut ffn_post = vec![vec![0.0f32; hc]; block];
8004 let mut ffn_comb = vec![vec![0.0f32; hc * hc]; block];
8005 let mut ffn_resid = vec![vec![0.0f32; hc * dim]; block];
8006 for i in 0..block {
8007 hc_mixes(
8008 &states[i],
8009 &l.hc_ffn_fn,
8010 mix_hc,
8011 cfg.norm_eps,
8012 pool,
8013 &mut scratch.mixes,
8014 );
8015 hc_split_sinkhorn(
8016 &scratch.mixes,
8017 &l.hc_ffn_scale,
8018 &l.hc_ffn_base,
8019 hc,
8020 cfg.hc_sinkhorn_iters,
8021 cfg.hc_eps,
8022 &mut scratch.pre,
8023 &mut ffn_post[i],
8024 &mut ffn_comb[i],
8025 );
8026 hc_fold(
8027 &states[i],
8028 &scratch.pre,
8029 hc,
8030 dim,
8031 &mut ffn_fold[i * dim..(i + 1) * dim],
8032 );
8033 rms_weighted(
8034 &mut ffn_fold[i * dim..(i + 1) * dim],
8035 &l.ffn_norm,
8036 cfg.norm_eps,
8037 );
8038 ffn_resid[i].copy_from_slice(&states[i]);
8039 }
8040 let mut moe_out = vec![0.0f32; block * dim];
8041 moe_step_block(&ffn_fold, block, l, cfg, &ids, si, pool, &mut moe_out);
8042 for i in 0..block {
8043 let mut next = vec![0.0f32; hc * dim];
8044 hc_expand(
8045 &moe_out[i * dim..(i + 1) * dim],
8046 &ffn_resid[i],
8047 &ffn_post[i],
8048 &ffn_comb[i],
8049 hc,
8050 dim,
8051 &mut next,
8052 );
8053 states[i] = next;
8054 }
8055 }
8056
8057 let last = &mtp[mtp.len() - 1];
8059 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
8060 last.hc_head_fn.as_ref(),
8061 last.hc_head_base.as_ref(),
8062 last.hc_head_scale,
8063 last.norm.as_ref(),
8064 ) else {
8065 return Vec::new();
8066 };
8067 let mut proposals = Vec::with_capacity(block);
8068 out_conf.clear();
8069 let mut prev = last_token;
8070 let mut head_in = vec![0.0f32; block * dim];
8071 let mut pre_norms = vec![vec![0.0f32; dim]; block];
8072 for i in 0..block {
8073 hc_head_fold(
8074 &states[i],
8075 hfn,
8076 hscale,
8077 hbase,
8078 cfg,
8079 pool,
8080 &mut head_in[i * dim..(i + 1) * dim],
8081 );
8082 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
8083 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
8084 }
8085 let mut logits = vec![0.0f32; block * cfg.vocab];
8086 g.head.matmat(&head_in, block, &mut logits, pool);
8087 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
8088 for i in 0..block {
8089 let logits_i = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
8090 let no_markov = {
8099 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8100 *ON.get_or_init(|| std::env::var("CMF_DSPARK_NO_MARKOV").is_ok_and(|v| v != "0"))
8101 };
8102 if no_markov {
8103 if let Some(w1) = last.markov_w1.as_ref() {
8105 w1.row_f32(prev as usize, &mut mk_embed);
8106 }
8107 } else if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
8108 w1.row_f32(prev as usize, &mut mk_embed);
8109 let mut bias = vec![0.0f32; cfg.vocab];
8110 w2.matvec(&mk_embed, &mut bias, pool);
8111 for (a, b) in logits_i.iter_mut().zip(&bias) {
8112 *a += *b;
8113 }
8114 }
8115 let mut best = 0usize;
8116 for v in 1..logits_i.len() {
8117 if logits_i[v] > logits_i[best] {
8118 best = v;
8119 }
8120 }
8121 if let Some(cf) = last.confidence.as_ref() {
8122 let mut cat = pre_norms[i].clone();
8123 cat.extend_from_slice(&mk_embed);
8124 let mut s = [0.0f32; 1];
8125 if cat.len() == cf.cols() {
8126 cf.matvec(&cat, &mut s, pool);
8127 }
8128 out_conf.push(s[0]);
8129 }
8130 proposals.push(best as u32);
8131 prev = best as u32;
8132 }
8133 proposals
8134}