1pub fn hc_split_sinkhorn(
29 mixes: &[f32],
30 hc_scale: &[f32; 3],
31 hc_base: &[f32],
32 hc: usize,
33 iters: usize,
34 eps: f32,
35 pre: &mut [f32],
36 post: &mut [f32],
37 comb: &mut [f32],
38) {
39 debug_assert_eq!(mixes.len(), (2 + hc) * hc);
40 debug_assert_eq!(comb.len(), hc * hc);
41 for j in 0..hc {
42 pre[j] = sigmoid(mixes[j] * hc_scale[0] + hc_base[j]) + eps;
43 post[j] = 2.0 * sigmoid(mixes[j + hc] * hc_scale[1] + hc_base[j + hc]);
46 }
47 for j in 0..hc {
48 for k in 0..hc {
49 let idx = j * hc + k + hc * 2;
50 comb[j * hc + k] = mixes[idx] * hc_scale[2] + hc_base[idx];
51 }
52 }
53 for j in 0..hc {
55 let row = &mut comb[j * hc..(j + 1) * hc];
56 let m = row.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
57 let mut sum = 0.0;
58 for v in row.iter_mut() {
59 *v = (*v - m).exp();
60 sum += *v;
61 }
62 for v in row.iter_mut() {
63 *v = *v / sum + eps;
64 }
65 }
66 normalize_cols(comb, hc, eps);
68 for _ in 0..iters.saturating_sub(1) {
69 normalize_rows(comb, hc, eps);
70 normalize_cols(comb, hc, eps);
71 }
72}
73
74fn normalize_rows(m: &mut [f32], n: usize, eps: f32) {
75 for j in 0..n {
76 let s: f32 = m[j * n..(j + 1) * n].iter().sum::<f32>() + eps;
77 for v in m[j * n..(j + 1) * n].iter_mut() {
78 *v /= s;
79 }
80 }
81}
82
83fn normalize_cols(m: &mut [f32], n: usize, eps: f32) {
84 for k in 0..n {
85 let mut s = eps;
86 for j in 0..n {
87 s += m[j * n + k];
88 }
89 for j in 0..n {
90 m[j * n + k] /= s;
91 }
92 }
93}
94
95#[inline]
96fn sigmoid(x: f32) -> f32 {
97 1.0 / (1.0 + (-x).exp())
98}
99
100pub fn hc_mixes(
104 x_flat: &[f32],
105 hc_fn: &[f32],
106 mix_hc: usize,
107 eps: f32,
108 pool: Option<&crate::pool::Pool>,
109 out: &mut [f32],
110) {
111 let n = x_flat.len();
112 debug_assert_eq!(hc_fn.len(), mix_hc * n);
113 debug_assert_eq!(out.len(), mix_hc);
114 let ms = x_flat.iter().map(|v| v * v).sum::<f32>() / n as f32;
115 let rsqrt = 1.0 / (ms + eps).sqrt();
116 match pool {
120 Some(p) if n >= 4096 => {
121 let addr = crate::pool::SendMut::new(out.as_mut_ptr());
122 p.run_rows(mix_hc, &|start, end| {
123 for i in start..end {
124 let row = &hc_fn[i * n..(i + 1) * n];
125 let v = row.iter().zip(x_flat).map(|(a, b)| a * b).sum::<f32>() * rsqrt;
126 unsafe { *addr.at(i) = v };
127 }
128 });
129 }
130 _ => {
131 for (i, o) in out.iter_mut().enumerate() {
132 let row = &hc_fn[i * n..(i + 1) * n];
133 *o = row.iter().zip(x_flat).map(|(a, b)| a * b).sum::<f32>() * rsqrt;
134 }
135 }
136 }
137}
138
139pub fn hc_fold(x: &[f32], pre: &[f32], hc: usize, dim: usize, out: &mut [f32]) {
141 debug_assert_eq!(x.len(), hc * dim);
142 out.fill(0.0);
143 for j in 0..hc {
144 let w = pre[j];
145 let src = &x[j * dim..(j + 1) * dim];
146 for (o, v) in out.iter_mut().zip(src) {
147 *o += w * v;
148 }
149 }
150}
151
152pub fn hc_expand(
159 block_out: &[f32],
160 residual: &[f32],
161 post: &[f32],
162 comb: &[f32],
163 hc: usize,
164 dim: usize,
165 out: &mut [f32],
166) {
167 debug_assert_eq!(residual.len(), hc * dim);
168 debug_assert_eq!(out.len(), hc * dim);
169 for j in 0..hc {
170 let dst = &mut out[j * dim..(j + 1) * dim];
171 let p = post[j];
172 for (d, o) in dst.iter_mut().enumerate() {
173 *o = p * block_out[d];
174 }
175 for k in 0..hc {
176 let w = comb[k * hc + j];
177 let src = &residual[k * dim..(k + 1) * dim];
178 for (o, v) in dst.iter_mut().zip(src) {
179 *o += w * v;
180 }
181 }
182 }
183}
184
185pub fn hc_head_pre(mixes: &[f32], scale: f32, base: &[f32], hc: usize, eps: f32, pre: &mut [f32]) {
188 for j in 0..hc {
189 pre[j] = sigmoid(mixes[j] * scale + base[j]) + eps;
190 }
191}
192
193pub fn route(
207 scores_in: &[f32],
208 bias: Option<&[f32]>,
209 top_k: usize,
210 route_scale: f32,
211 forced: Option<&[usize]>,
212 mask: Option<&[bool]>,
213 indices: &mut Vec<usize>,
214 weights: &mut Vec<f32>,
215) {
216 let n = scores_in.len();
217 let mut scores = Vec::with_capacity(n);
218 for &s in scores_in {
219 let sp = if s > 20.0 { s } else { (1.0 + s.exp()).ln() };
221 scores.push(sp.sqrt());
222 }
223 indices.clear();
224 weights.clear();
225 match forced {
226 Some(f) => indices.extend(f.iter().copied()),
227 None => {
228 let mut shifted: Vec<f32> = match bias {
229 Some(b) => scores.iter().zip(b).map(|(s, b)| s + b).collect(),
230 None => scores.clone(),
231 };
232 if let Some(m) = mask {
233 for (i, s) in shifted.iter_mut().enumerate() {
234 if !m.get(i).copied().unwrap_or(true) {
235 *s = f32::NEG_INFINITY;
236 }
237 }
238 }
239 for _ in 0..top_k.min(n) {
240 let mut best = 0usize;
241 let mut bv = f32::NEG_INFINITY;
242 for (i, &v) in shifted.iter().enumerate() {
243 if v > bv {
244 bv = v;
245 best = i;
246 }
247 }
248 if !bv.is_finite() {
249 break;
250 }
251 indices.push(best);
252 shifted[best] = f32::NEG_INFINITY;
253 }
254 }
255 }
256 for &i in indices.iter() {
258 weights.push(scores.get(i).copied().unwrap_or(0.0));
259 }
260 let sum: f32 = weights.iter().sum();
261 if sum > 0.0 {
262 for w in weights.iter_mut() {
263 *w = *w / sum * route_scale;
264 }
265 }
266}
267
268pub fn hash_route(tid2eid: &[f32], vocab: usize, top_k: usize, tid: u32) -> Vec<usize> {
272 let row = (tid as usize).min(vocab.saturating_sub(1)) * top_k;
273 (0..top_k)
274 .map(|k| tid2eid.get(row + k).copied().unwrap_or(0.0) as usize)
275 .collect()
276}
277
278pub fn rope_tail(v: &mut [f32], inv_freq: &[f32], pos: usize, rd: usize, inverse: bool) {
285 let n = v.len();
286 debug_assert!(
287 rd <= n && rd % 2 == 0,
288 "rope tail {rd} wider than the vector {n}"
289 );
290 let rd = rd.min(n) & !1;
293 let base = n - rd;
294 for i in 0..rd / 2 {
303 let theta = pos as f32 * inv_freq[i];
304 let (s, c) = (theta.sin(), theta.cos());
305 let s = if inverse { -s } else { s };
306 let a = v[base + 2 * i];
307 let b = v[base + 2 * i + 1];
308 v[base + 2 * i] = a * c - b * s;
309 v[base + 2 * i + 1] = a * s + b * c;
310 }
311}
312
313pub fn rms_inplace(v: &mut [f32], eps: f32) {
317 let ms = v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32;
318 let inv = 1.0 / (ms + eps).sqrt();
319 for x in v.iter_mut() {
320 *x *= inv;
321 }
322}
323
324pub fn sparse_attend(
330 q: &[f32],
331 kv: &[f32],
332 idxs: &[usize],
333 sink: f32,
334 scale: f32,
335 head_dim: usize,
336 out: &mut [f32],
337) {
338 let mut m = sink;
339 let mut scores = Vec::with_capacity(idxs.len());
340 for &p in idxs {
341 if p == usize::MAX {
342 scores.push(f32::NEG_INFINITY);
343 continue;
344 }
345 let k = &kv[p * head_dim..(p + 1) * head_dim];
346 let dot: f32 = q.iter().zip(k).map(|(a, b)| a * b).sum::<f32>() * scale;
347 m = m.max(dot);
348 scores.push(dot);
349 }
350 let mut denom = (sink - m).exp();
351 out.fill(0.0);
352 for (&p, &s) in idxs.iter().zip(&scores) {
353 if p == usize::MAX {
354 continue;
355 }
356 let w = (s - m).exp();
357 denom += w;
358 let v = &kv[p * head_dim..(p + 1) * head_dim];
359 for (o, x) in out.iter_mut().zip(v) {
360 *o += w * x;
361 }
362 }
363 if std::env::var("CMF_ATTN_DEBUG").is_ok() {
364 eprintln!(
365 " [порт] позиций={} score={:?} sink={sink:.4} denom={denom:.4} |q|={:.3}",
366 idxs.iter().filter(|&&p| p != usize::MAX).count(),
367 scores
368 .iter()
369 .map(|x| (x * 10000.0).round() / 10000.0)
370 .collect::<Vec<_>>(),
371 q.iter().map(|x| x * x).sum::<f32>().sqrt()
372 );
373 }
374 let inv = 1.0 / denom;
375 for o in out.iter_mut() {
376 *o *= inv;
377 }
378}
379
380pub fn o_project(
389 attn: &[f32],
390 wo_a_row: &(dyn Fn(usize, &[f32], &mut [f32]) -> f32 + Sync),
391 scratch_len: usize,
392 wo_b: &dyn Fn(&[f32], &mut [f32]),
393 groups: usize,
394 lora: usize,
395 pool: Option<&crate::pool::Pool>,
396 out: &mut [f32],
397) {
398 let per_group = attn.len() / groups;
399 let mut mid = vec![0.0f32; groups * lora];
400 let slice_of = |i: usize| {
401 let g = i / lora;
402 &attn[g * per_group..(g + 1) * per_group]
403 };
404 match pool {
405 Some(p) if mid.len() >= 256 => {
410 let addr = crate::pool::SendMut::new(mid.as_mut_ptr());
411 p.run_rows(mid.len(), &|start, end| {
412 let mut sc = vec![0.0f32; scratch_len];
413 for i in start..end {
414 let v = wo_a_row(i, slice_of(i), &mut sc);
415 unsafe { *addr.at(i) = v };
416 }
417 });
418 }
419 _ => {
420 let mut sc = vec![0.0f32; scratch_len];
421 for (i, m) in mid.iter_mut().enumerate() {
422 *m = wo_a_row(i, slice_of(i), &mut sc);
423 }
424 }
425 }
426 wo_b(&mid, out);
427}
428
429pub fn compress_window(
430 kv: &[f32],
431 score: &[f32],
432 ape: &[f32],
433 ratio: usize,
434 width: usize,
435 out: &mut [f32],
436) {
437 debug_assert_eq!(kv.len(), ratio * width);
438 debug_assert_eq!(ape.len(), ratio * width);
439 let biased: Vec<f32> = score.iter().zip(ape).map(|(s, a)| s + a).collect();
440 pool_by_score(kv, &biased, ratio, width, out);
441}
442
443pub fn pool_by_score(kv: &[f32], score: &[f32], slots: usize, width: usize, out: &mut [f32]) {
448 debug_assert_eq!(kv.len(), slots * width);
449 debug_assert_eq!(score.len(), slots * width);
450 out.fill(0.0);
451 for d in 0..width {
452 let mut m = f32::NEG_INFINITY;
453 for t in 0..slots {
454 m = m.max(score[t * width + d]);
455 }
456 if !m.is_finite() {
457 continue;
458 }
459 let mut denom = 0.0;
460 for t in 0..slots {
461 denom += (score[t * width + d] - m).exp();
462 }
463 if denom <= 0.0 {
464 continue;
465 }
466 for t in 0..slots {
467 out[d] += ((score[t * width + d] - m).exp() / denom) * kv[t * width + d];
468 }
469 }
470}
471
472#[allow(clippy::too_many_arguments)]
481pub fn compress_window_overlap(
482 prev_kv: &[f32],
483 prev_score: &[f32],
484 cur_kv: &[f32],
485 cur_score: &[f32],
486 ratio: usize,
487 d: usize,
488 out: &mut [f32],
489) {
490 let slots = 2 * ratio;
491 let mut kv = vec![0.0f32; slots * d];
492 let mut sc = vec![f32::NEG_INFINITY; slots * d];
493 let have_prev = prev_kv.len() == ratio * 2 * d;
494 for t in 0..ratio {
495 if have_prev {
496 kv[t * d..(t + 1) * d].copy_from_slice(&prev_kv[t * 2 * d..t * 2 * d + d]);
498 sc[t * d..(t + 1) * d].copy_from_slice(&prev_score[t * 2 * d..t * 2 * d + d]);
499 }
500 let src = t * 2 * d + d;
502 let dst = (ratio + t) * d;
503 kv[dst..dst + d].copy_from_slice(&cur_kv[src..src + d]);
504 sc[dst..dst + d].copy_from_slice(&cur_score[src..src + d]);
505 }
506 pool_by_score(&kv, &sc, slots, d, out);
507}
508
509#[allow(clippy::too_many_arguments)]
524pub fn index_scores(
525 q_heads: &[f32],
526 kv: &[f32],
527 head_weights: &[f32],
528 n_heads: usize,
529 head_dim: usize,
530 n_pos: usize,
531 causal_limit: usize,
532 pool: Option<&crate::pool::Pool>,
533 out: &mut Vec<f32>,
534) {
535 out.clear();
536 out.resize(n_pos, 0.0);
537 let score_at = |t: usize| -> f32 {
538 if t >= causal_limit {
539 return f32::NEG_INFINITY;
540 }
541 let k = &kv[t * head_dim..(t + 1) * head_dim];
542 let mut acc = 0.0;
543 for h in 0..n_heads {
544 let q = &q_heads[h * head_dim..(h + 1) * head_dim];
545 let dot: f32 = q.iter().zip(k).map(|(a, b)| a * b).sum();
546 acc += dot.max(0.0) * head_weights[h];
548 }
549 acc
550 };
551 match pool {
555 Some(p) if n_pos >= 64 => {
556 let addr = crate::pool::SendMut::new(out.as_mut_ptr());
557 p.run_rows(n_pos, &|start, end| {
558 for t in start..end {
559 unsafe { *addr.at(t) = score_at(t) };
560 }
561 });
562 }
563 _ => {
564 for (t, o) in out.iter_mut().enumerate() {
565 *o = score_at(t);
566 }
567 }
568 }
569}
570
571pub fn top_k_positions(scores: &[f32], k: usize, out: &mut Vec<usize>) {
575 out.clear();
576 if k >= scores.len() {
583 out.extend(
584 scores
585 .iter()
586 .enumerate()
587 .filter(|(_, v)| v.is_finite())
588 .map(|(i, _)| i),
589 );
590 return;
591 }
592 let mut taken = vec![false; scores.len()];
593 for _ in 0..k.min(scores.len()) {
594 let mut best = usize::MAX;
595 let mut bv = f32::NEG_INFINITY;
596 for (i, &v) in scores.iter().enumerate() {
597 if !taken[i] && v > bv && v.is_finite() {
598 bv = v;
599 best = i;
600 }
601 }
602 if best == usize::MAX {
603 break;
604 }
605 taken[best] = true;
606 out.push(best);
607 }
608 out.sort_unstable();
609}
610
611#[allow(clippy::too_many_arguments)]
619pub fn expert_swiglu(
620 x: &[f32],
621 w1: &dyn Fn(&[f32], &mut [f32]),
622 w3: &dyn Fn(&[f32], &mut [f32]),
623 w2: &dyn Fn(&[f32], &mut [f32]),
624 inter: usize,
625 weight: f32,
626 limit: f32,
627 out: &mut [f32],
628) {
629 let mut gate = vec![0.0f32; inter];
630 let mut up = vec![0.0f32; inter];
631 w1(x, &mut gate);
632 w3(x, &mut up);
633 if limit > 0.0 {
634 for u in up.iter_mut() {
635 *u = u.clamp(-limit, limit);
636 }
637 for g in gate.iter_mut() {
638 *g = g.min(limit);
639 }
640 }
641 for (g, u) in gate.iter_mut().zip(&up) {
642 let silu = *g / (1.0 + (-*g).exp());
643 *g = silu * u * weight;
644 }
645 w2(&gate, out);
646}
647
648#[derive(Debug, Clone, Copy)]
651pub struct Dsv4Cfg {
652 pub dim: usize,
653 pub n_heads: usize,
654 pub head_dim: usize,
655 pub rope_head_dim: usize,
656 pub q_lora_rank: usize,
657 pub o_lora_rank: usize,
658 pub o_groups: usize,
659 pub hc_mult: usize,
660 pub hc_sinkhorn_iters: usize,
661 pub hc_eps: f32,
662 pub norm_eps: f32,
663 pub n_routed_experts: usize,
664 pub top_k: usize,
665 pub moe_inter: usize,
666 pub route_scale: f32,
667 pub swiglu_limit: f32,
669 pub window: usize,
671 pub index_topk: usize,
672 pub vocab: usize,
673}
674
675#[allow(clippy::too_many_arguments)]
683#[allow(clippy::too_many_arguments)]
684pub fn hc_block<F: FnMut(&[f32], &mut [f32])>(
685 state: &mut [f32],
686 hc_fn: &[f32],
687 hc_scale: &[f32; 3],
688 hc_base: &[f32],
689 norm_w: &[f32],
690 cfg: &Dsv4Cfg,
691 scratch: &mut HcScratch,
692 pool: Option<&crate::pool::Pool>,
693 mut block: F,
694) {
695 let (hc, dim) = (cfg.hc_mult, cfg.dim);
696 let mix_hc = (2 + hc) * hc;
697 hc_mixes(state, hc_fn, mix_hc, cfg.norm_eps, pool, &mut scratch.mixes);
698 hc_split_sinkhorn(
699 &scratch.mixes,
700 hc_scale,
701 hc_base,
702 hc,
703 cfg.hc_sinkhorn_iters,
704 cfg.hc_eps,
705 &mut scratch.pre,
706 &mut scratch.post,
707 &mut scratch.comb,
708 );
709 hc_fold(state, &scratch.pre, hc, dim, &mut scratch.folded);
710 let ms = scratch.folded.iter().map(|v| v * v).sum::<f32>() / dim as f32;
712 let inv = 1.0 / (ms + cfg.norm_eps).sqrt();
713 for (v, w) in scratch.folded.iter_mut().zip(norm_w) {
714 *v = *v * inv * w;
715 }
716 block(&scratch.folded, &mut scratch.block_out);
717 scratch.residual.copy_from_slice(state);
718 hc_expand(
719 &scratch.block_out,
720 &scratch.residual,
721 &scratch.post,
722 &scratch.comb,
723 hc,
724 dim,
725 state,
726 );
727}
728
729pub struct HcScratch {
732 pub mixes: Vec<f32>,
733 pub pre: Vec<f32>,
734 pub post: Vec<f32>,
735 pub comb: Vec<f32>,
736 pub folded: Vec<f32>,
737 pub block_out: Vec<f32>,
738 pub residual: Vec<f32>,
739}
740
741impl HcScratch {
742 pub fn new(cfg: &Dsv4Cfg) -> Self {
743 let (hc, dim) = (cfg.hc_mult, cfg.dim);
744 Self {
745 mixes: vec![0.0; (2 + hc) * hc],
746 pre: vec![0.0; hc],
747 post: vec![0.0; hc],
748 comb: vec![0.0; hc * hc],
749 folded: vec![0.0; dim],
750 block_out: vec![0.0; dim],
751 residual: vec![0.0; hc * dim],
752 }
753 }
754}
755
756pub fn hc_head_fold(
759 state: &[f32],
760 hc_fn: &[f32],
761 hc_scale: f32,
762 hc_base: &[f32],
763 cfg: &Dsv4Cfg,
764 pool: Option<&crate::pool::Pool>,
765 out: &mut [f32],
766) {
767 let (hc, dim) = (cfg.hc_mult, cfg.dim);
768 let mut mixes = vec![0.0f32; hc];
769 hc_mixes(state, hc_fn, hc, cfg.norm_eps, pool, &mut mixes);
770 let mut pre = vec![0.0f32; hc];
771 hc_head_pre(&mixes, hc_scale, hc_base, hc, cfg.hc_eps, &mut pre);
772 hc_fold(state, &pre, hc, dim, out);
773}
774
775pub struct Dsv4Layer {
781 pub attn_norm: Vec<f32>,
782 pub ffn_norm: Vec<f32>,
783 pub wq_a: crate::qtensor::QTensor,
785 pub q_norm: Vec<f32>,
786 pub wq_b: crate::qtensor::QTensor,
787 pub wkv: crate::qtensor::QTensor,
788 pub kv_norm: Vec<f32>,
789 pub wo_a: crate::qtensor::QTensor,
790 pub wo_b: crate::qtensor::QTensor,
791 pub attn_sink: Vec<f32>,
792 pub compressor: Option<Dsv4Compressor>,
794 pub indexer: Option<Dsv4Indexer>,
796 pub hc_attn_fn: Vec<f32>,
798 pub hc_attn_base: Vec<f32>,
799 pub hc_attn_scale: [f32; 3],
800 pub hc_ffn_fn: Vec<f32>,
801 pub hc_ffn_base: Vec<f32>,
802 pub hc_ffn_scale: [f32; 3],
803 pub gate: crate::qtensor::QTensor,
805 pub gate_bias: Option<Vec<f32>>,
807 pub tid2eid: Option<Vec<f32>>,
809 pub experts: Vec<Dsv4Expert>,
810 pub shared: Dsv4Expert,
811 pub mask: Option<Vec<bool>>,
817}
818
819pub struct Dsv4Expert {
820 pub w1: crate::qtensor::QTensor,
821 pub w2: crate::qtensor::QTensor,
822 pub w3: crate::qtensor::QTensor,
823}
824
825pub struct Dsv4Compressor {
826 pub wkv: crate::qtensor::QTensor,
827 pub wgate: crate::qtensor::QTensor,
828 pub norm: Vec<f32>,
829 pub ape: Vec<f32>,
831 pub ratio: usize,
832 pub overlap: bool,
835}
836
837pub struct Dsv4Indexer {
838 pub wq_b: crate::qtensor::QTensor,
839 pub weights_proj: crate::qtensor::QTensor,
840 pub compressor: Dsv4Compressor,
841}
842
843pub struct Dsv4Globals {
846 pub inv_freq_compress: Vec<f32>,
849 pub inv_freq_window: Vec<f32>,
857 pub embed: crate::qtensor::QTensor,
858 pub norm: Vec<f32>,
859 pub head: crate::qtensor::QTensor,
860 pub hc_head_fn: Vec<f32>,
861 pub hc_head_base: Vec<f32>,
862 pub hc_head_scale: f32,
863}
864
865pub struct Dsv4State {
869 pub window: Vec<Vec<f32>>,
871 pub compressed: Vec<Vec<f32>>,
873 pub index_kv: Vec<Vec<f32>>,
875 pub pending_kv: Vec<Vec<f32>>,
877 pub pending_score: Vec<Vec<f32>>,
878 pub prev_kv: Vec<Vec<f32>>,
881 pub prev_score: Vec<Vec<f32>>,
882 pub pending_ix_kv: Vec<Vec<f32>>,
885 pub pending_ix_score: Vec<Vec<f32>>,
886 pub prev_ix_kv: Vec<Vec<f32>>,
887 pub prev_ix_score: Vec<Vec<f32>>,
888 pub pos: usize,
889 pub kv_id: u64,
893 pub dev_filled: Vec<usize>,
899 pub dev_n_comp: Vec<usize>,
900 pub dev_n_ix: Vec<usize>,
901 pub dev_owned: bool,
905 pub dev_set: Vec<bool>,
909 pub partial_set: Vec<bool>,
914 pub split_deep: bool,
919}
920
921impl Dsv4State {
922 pub fn new(layers: usize) -> Self {
923 use std::sync::atomic::{AtomicU64, Ordering};
924 static NEXT: AtomicU64 = AtomicU64::new(1);
925 Self {
926 kv_id: NEXT.fetch_add(1, Ordering::Relaxed),
927 dev_filled: vec![0; layers],
928 dev_n_comp: vec![0; layers],
929 dev_n_ix: vec![0; layers],
930 dev_owned: false,
931 dev_set: Vec::new(),
932 partial_set: Vec::new(),
933 split_deep: false,
934 window: vec![Vec::new(); layers],
935 compressed: vec![Vec::new(); layers],
936 index_kv: vec![Vec::new(); layers],
937 pending_kv: vec![Vec::new(); layers],
938 pending_score: vec![Vec::new(); layers],
939 prev_kv: vec![Vec::new(); layers],
940 prev_score: vec![Vec::new(); layers],
941 pending_ix_kv: vec![Vec::new(); layers],
942 pending_ix_score: vec![Vec::new(); layers],
943 prev_ix_kv: vec![Vec::new(); layers],
944 prev_ix_score: vec![Vec::new(); layers],
945 pos: 0,
946 }
947 }
948}
949
950#[allow(clippy::too_many_arguments)]
959#[allow(clippy::too_many_arguments)]
965fn compressor_step(
966 cp: &Dsv4Compressor,
967 hidden: &[f32],
968 pos: usize,
969 rd: usize,
970 norm_eps: f32,
971 inv_freq: &[f32],
972 pool: Option<&crate::pool::Pool>,
973 pending_kv: &mut Vec<f32>,
974 pending_score: &mut Vec<f32>,
975 prev_kv: &mut Vec<f32>,
976 prev_score: &mut Vec<f32>,
977) -> Option<Vec<f32>> {
978 let width = cp.wkv.rows();
979 let ew = if cp.overlap { width / 2 } else { width };
980 let mut ckv = vec![0.0f32; width];
981 let mut cscore = vec![0.0f32; width];
982 crate::qtensor::QTensor::matvec_many(
985 [&cp.wkv, &cp.wgate],
986 hidden,
987 [&mut ckv, &mut cscore],
988 pool,
989 );
990 if cp.overlap {
991 let slot = pos % cp.ratio;
994 for (c, a) in cscore
995 .iter_mut()
996 .zip(&cp.ape[slot * width..(slot + 1) * width])
997 {
998 *c += a;
999 }
1000 }
1001 pending_kv.extend_from_slice(&ckv);
1002 pending_score.extend_from_slice(&cscore);
1003 if pending_kv.len() / width < cp.ratio {
1004 return None;
1005 }
1006 let mut folded = vec![0.0f32; ew];
1007 if cp.overlap {
1008 compress_window_overlap(
1009 prev_kv,
1010 prev_score,
1011 pending_kv,
1012 pending_score,
1013 cp.ratio,
1014 ew,
1015 &mut folded,
1016 );
1017 *prev_kv = std::mem::take(pending_kv);
1018 *prev_score = std::mem::take(pending_score);
1019 } else {
1020 compress_window(
1021 pending_kv,
1022 pending_score,
1023 &cp.ape,
1024 cp.ratio,
1025 width,
1026 &mut folded,
1027 );
1028 }
1029 rms_weighted(&mut folded, &cp.norm, norm_eps);
1030 rope_tail(&mut folded, inv_freq, pos + 1 - cp.ratio, rd, false);
1033 pending_kv.clear();
1034 pending_score.clear();
1035 Some(folded)
1036}
1037
1038pub(crate) mod prof {
1045 use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
1046
1047 pub static ATTN_NS: AtomicU64 = AtomicU64::new(0);
1048 pub static MOE_NS: AtomicU64 = AtomicU64::new(0);
1049 pub static CALLS: AtomicU64 = AtomicU64::new(0);
1050 pub static HC_NS: AtomicU64 = AtomicU64::new(0);
1053 pub static HEAD_NS: AtomicU64 = AtomicU64::new(0);
1055 pub static PREP_NS: AtomicU64 = AtomicU64::new(0);
1058 pub static CACHEW_NS: AtomicU64 = AtomicU64::new(0);
1060 pub static ALL_NS: AtomicU64 = AtomicU64::new(0);
1064 pub static TOKENS: AtomicU64 = AtomicU64::new(0);
1065
1066 pub fn note_layer(li: usize) {
1069 CALLS.fetch_add(1, Ordering::Relaxed);
1070 if li == 0 {
1071 if TOKENS.fetch_add(1, Ordering::Relaxed) == 1 && !ZEROED.swap(true, Ordering::Relaxed)
1082 {
1083 for a in [&ATTN_NS, &MOE_NS, &HC_NS, &HEAD_NS, &ALL_NS, &CALLS, &PREP_NS, &CACHEW_NS] {
1084 a.store(0, Ordering::Relaxed);
1085 }
1086 TOKENS.store(1, Ordering::Relaxed);
1087 #[cfg(feature = "gpu")]
1088 for a in [
1089 &crate::gpu_wgpu::MOE_ENC_NS,
1090 &crate::gpu_wgpu::MOE_WAIT_NS,
1091 &crate::gpu_wgpu::MOE_BUFS_NS,
1092 &crate::gpu_wgpu::MOE_UP_NS,
1093 &crate::gpu_wgpu::MOE_PASS_NS,
1094 &crate::gpu_wgpu::ATT_ENC_NS,
1095 &crate::gpu_wgpu::ATT_WAIT_NS,
1096 &crate::gpu_wgpu::CHAIN_ENC_NS,
1097 &crate::gpu_wgpu::CHAIN_WAIT_NS,
1098 &crate::gpu_wgpu::CHAIN_LAYERS,
1099 &crate::gpu_wgpu::CHAIN_RUNS,
1100 &crate::gpu_wgpu::SUBMITS,
1101 &crate::gpu_wgpu::PASSES,
1102 ] {
1103 a.store(0, Ordering::Relaxed);
1104 }
1105 }
1106 }
1107 }
1108 static REPORT: AtomicBool = AtomicBool::new(false);
1109 static ZEROED: AtomicBool = AtomicBool::new(false);
1111
1112 pub fn on() -> bool {
1113 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1114 *ON.get_or_init(|| std::env::var("CMF_DSV4_PROFILE").is_ok_and(|v| v != "0"))
1115 }
1116
1117 pub fn report() {
1120 if !on() || REPORT.swap(true, Ordering::Relaxed) {
1121 return;
1122 }
1123 let calls = CALLS.load(Ordering::Relaxed).max(1);
1127 let toks = TOKENS.load(Ordering::Relaxed).max(1);
1128 let (a, m) = (
1129 ATTN_NS.load(Ordering::Relaxed) as f64 / 1e6,
1130 MOE_NS.load(Ordering::Relaxed) as f64 / 1e6,
1131 );
1132 let all = ALL_NS.load(Ordering::Relaxed) as f64 / 1e6;
1133 let hc = (HC_NS.load(Ordering::Relaxed) as f64 / 1e6
1140 - MOE_NS.load(Ordering::Relaxed) as f64 / 1e6)
1141 .max(0.0);
1142 let hd = HEAD_NS.load(Ordering::Relaxed) as f64 / 1e6;
1143 let prep = PREP_NS.load(Ordering::Relaxed) as f64 / 1e6;
1144 let cw = CACHEW_NS.load(Ordering::Relaxed) as f64 / 1e6;
1145 eprintln!(
1146 "[dsv4-профиль] ХОСТ-ПРЕП слоя: {:.0} мс/токен, KV-заливки: {:.0} мс/токен",
1147 prep / toks as f64,
1148 cw / toks as f64
1149 );
1150 #[cfg(feature = "gpu")]
1151 {
1152 let f = crate::gpu_wgpu::DSV4_FILLS.load(Ordering::Relaxed);
1153 let fb = crate::gpu_wgpu::DSV4_FILL_BYTES.load(Ordering::Relaxed);
1154 eprintln!(
1155 "[dsv4-профиль] ЗАЛИВКИ СЛОТОВ: {:.1} эксп/токен, {:.0} МБ/токен",
1156 f as f64 / toks as f64,
1157 fb as f64 / 1e6 / toks as f64
1158 );
1159 }
1160 eprintln!(
1161 "[dsv4-профиль] {calls} вызовов слоя за {toks} токенов | \
1162 на токен: внимание {:.0} мс, MoE {:.0} мс, гипер-связи+нормы {:.0} мс, \
1163 голова {:.0} мс | на вызов: внимание {:.2}, MoE {:.2}, связи {:.2}",
1164 a / toks as f64,
1165 m / toks as f64,
1166 hc / toks as f64,
1167 hd / toks as f64,
1168 a / calls as f64,
1169 m / calls as f64,
1170 hc / calls as f64,
1171 );
1172 eprintln!(
1173 "[dsv4-профиль] весь проход {:.0} мс на токен; вне счётчиков {:.0} мс",
1174 all / toks as f64,
1175 (all - a - m - hd) / toks as f64,
1176 );
1177 #[cfg(feature = "gpu")]
1178 {
1179 let ae = crate::gpu_wgpu::ATT_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6;
1180 let aw = crate::gpu_wgpu::ATT_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6;
1181 if ae + aw > 0.0 {
1182 eprintln!(
1183 "[dsv4-профиль] кадр внимания на вызов: кодирование {:.2} мс, \
1184 отправка и ожидание {:.2} мс",
1185 ae / calls as f64,
1186 aw / calls as f64,
1187 );
1188 }
1189 let ub = crate::gpu_wgpu::UPLOAD_BYTES.load(Ordering::Relaxed);
1194 let un = crate::gpu_wgpu::UPLOAD_NS.load(Ordering::Relaxed);
1195 if ub > 0 && un > 0 {
1196 eprintln!(
1197 "[dsv4-профиль] ЗАЛИВКА весов: {:.1} ГБ за {:.1} с ({:.0} МБ/с)",
1198 ub as f64 / 1e9,
1199 un as f64 / 1e9,
1200 ub as f64 / (un as f64 / 1e9) / 1e6,
1201 );
1202 }
1203 let sub = crate::gpu_wgpu::SUBMITS.load(Ordering::Relaxed);
1204 if sub > 0 {
1205 eprintln!(
1206 "[dsv4-профиль] ОТПРАВОК на карту: {:.1} на токен, ПРОХОДОВ {:.0} \
1207 ({:.1} на слой)",
1208 sub as f64 / toks as f64,
1209 crate::gpu_wgpu::PASSES.load(Ordering::Relaxed) as f64 / toks as f64,
1210 crate::gpu_wgpu::PASSES.load(Ordering::Relaxed) as f64 / calls as f64,
1211 );
1212 }
1213 let cl = crate::gpu_wgpu::CHAIN_LAYERS.load(Ordering::Relaxed);
1214 if cl > 0 {
1215 let toks2 = toks.max(1) as f64;
1216 eprintln!(
1217 "[dsv4-профиль] ЦЕПОЧКА на токен: кодирование {:.2} мс, \
1218 ожидание {:.2} мс ({} слоёв, {} отправок)",
1219 crate::gpu_wgpu::CHAIN_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6 / toks2,
1220 crate::gpu_wgpu::CHAIN_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6 / toks2,
1221 cl / toks.max(1),
1222 crate::gpu_wgpu::CHAIN_RUNS.load(Ordering::Relaxed) / toks.max(1),
1223 );
1224 }
1225 let e = crate::gpu_wgpu::MOE_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6;
1226 let wt = crate::gpu_wgpu::MOE_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6;
1227 if e + wt > 0.0 {
1228 let ns = |a: &std::sync::atomic::AtomicU64| {
1229 a.load(Ordering::Relaxed) as f64 / 1e6 / calls as f64
1230 };
1231 eprintln!(
1232 "[dsv4-профиль] кадр MoE на вызов: кодирование {:.2} мс, \
1233 отправка и ожидание {:.2} мс",
1234 e / calls as f64,
1235 wt / calls as f64,
1236 );
1237 let an = crate::gpu_wgpu::ATT_GPU_N.load(Ordering::Relaxed);
1238 if an > 0 {
1239 let g = |i: usize| {
1240 crate::gpu_wgpu::ATT_GPU_NS[i].load(Ordering::Relaxed) as f64
1241 / 1e6
1242 / an as f64
1243 };
1244 eprintln!(
1245 "[dsv4-профиль] ВНИМАНИЕ НА КАРТЕ на вызов: одиночное {:.3} мс, \
1246 оценки {:.3} мс, применение {:.3} мс",
1247 g(0),
1248 g(1),
1249 g(2),
1250 );
1251 }
1252 let gn = crate::gpu_wgpu::MOE_GPU_N.load(Ordering::Relaxed);
1253 let gns = crate::gpu_wgpu::MOE_GPU_NS[0].load(Ordering::Relaxed);
1254 if gn > 0 && gns > 0 {
1255 eprintln!(
1256 "[dsv4-профиль] MoE НА КАРТЕ: {:.3} мс на вызов ({gn} замеров)",
1257 gns as f64 / 1e6 / gn as f64,
1258 );
1259 } else if gn > 0 {
1260 eprintln!(
1264 "[dsv4-профиль] MoE НА КАРТЕ: метки вернули НОЛЬ на {gn} замерах — \
1265 запрос времени не сработал, число не использовать"
1266 );
1267 }
1268 eprintln!(
1269 "[dsv4-профиль] из кодирования: буферы экспертов {:.2} мс, \
1270 загрузки {:.2} мс, проходы {:.2} мс",
1271 ns(&crate::gpu_wgpu::MOE_BUFS_NS),
1272 ns(&crate::gpu_wgpu::MOE_UP_NS),
1273 ns(&crate::gpu_wgpu::MOE_PASS_NS),
1274 );
1275 }
1276 }
1277 }
1278}
1279
1280pub fn profile_report() {
1282 prof::report();
1283}
1284
1285fn gpu_attn_enabled() -> bool {
1290 #[cfg(feature = "gpu")]
1291 {
1292 use std::sync::OnceLock;
1293 static ON: OnceLock<bool> = OnceLock::new();
1294 *ON.get_or_init(|| {
1295 let want = std::env::var("CMF_DSV4_GPU_ATTN")
1296 .map(|v| v != "0")
1297 .unwrap_or(true);
1298 let have = want && crate::gpu::backend_available();
1299 if want && !have && std::env::var("CMF_DSV4_GPU_ATTN").is_ok() {
1300 tracing::warn!(
1301 "CMF_DSV4_GPU_ATTN задан, но устройства нет — блок внимания остаётся на CPU. Проверьте CMF_GPU=wgpu и Vulkan-ICD."
1302 );
1303 }
1304 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
1305 eprintln!("кадр dsv4: запрошен={want} доступен={have}");
1306 }
1307 have
1308 })
1309 }
1310 #[cfg(not(feature = "gpu"))]
1311 {
1312 false
1313 }
1314}
1315
1316#[cfg(feature = "gpu")]
1320#[allow(clippy::too_many_arguments)]
1321fn attn_frame(
1322 l: &Dsv4Layer,
1323 cfg: &Dsv4Cfg,
1324 st: &Dsv4State,
1325 li: usize,
1326 hidden: &[f32],
1327 qn: &[f32],
1328 idxs: &[usize],
1329 inv_freq: &[f32],
1330 pos: usize,
1331 win_len: usize,
1332 scale: f32,
1333 hc: Option<&crate::gpu_wgpu::Dsv4HcTail>,
1336 out: &mut [f32],
1337) -> bool {
1338 let hd = cfg.head_dim;
1339 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
1340 l.wq_a.model_idx(),
1341 l.wq_b.model_idx(),
1342 l.wo_a.model_idx(),
1343 l.wo_b.model_idx(),
1344 ) else {
1345 return false;
1346 };
1347 let Some(model) = l.wq_b.model_arc() else {
1348 return false;
1349 };
1350 let n_comp = st.compressed[li].len() / hd;
1355 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
1356 let kv_id = st.kv_id;
1357 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap) {
1363 return false;
1364 }
1365 if n_comp > 0
1374 && !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, cfg.window * hd, &st.compressed[li], cap)
1375 {
1376 return false;
1377 }
1378 let idx32: Vec<u32> = idxs
1379 .iter()
1380 .map(|&p| {
1381 if p < win_len {
1382 p as u32
1383 } else {
1384 (cfg.window + (p - win_len)) as u32
1385 }
1386 })
1387 .collect();
1388 let w = crate::gpu_wgpu::Dsv4AttnW {
1389 wq_a,
1390 wq_b,
1391 wo_a,
1392 wo_b,
1393 q_norm: &l.q_norm,
1394 sink: &l.attn_sink,
1395 };
1396 let g = crate::gpu_wgpu::Dsv4AttnGeom {
1397 dim: cfg.dim,
1398 nh: cfg.n_heads,
1399 hd,
1400 rd: cfg.rope_head_dim,
1401 q_lora: cfg.q_lora_rank,
1402 o_lora: cfg.o_lora_rank,
1403 o_groups: cfg.o_groups,
1404 eps: cfg.norm_eps,
1405 scale,
1406 };
1407 crate::gpu_wgpu::dsv4_attn_frame(
1415 &model,
1416 &w,
1417 g,
1418 hidden,
1419 Some(qn),
1420 kv_id,
1421 li,
1422 &idx32,
1423 inv_freq,
1424 pos,
1425 hc,
1426 out,
1427 )
1428}
1429
1430#[derive(Default)]
1433pub struct AttnPrep {
1434 pub qr: Vec<f32>,
1435 pub idxs: Vec<usize>,
1436 pub win_len: usize,
1437}
1438
1439#[allow(clippy::too_many_arguments)]
1440pub fn attention_step(
1441 hidden: &[f32],
1442 l: &Dsv4Layer,
1443 cfg: &Dsv4Cfg,
1444 st: &mut Dsv4State,
1445 li: usize,
1446 inv_freq: &[f32],
1448 pool: Option<&crate::pool::Pool>,
1449 prep_out: Option<&mut AttnPrep>,
1453 out: &mut [f32],
1454) {
1455 let _t0 = prof::on().then(std::time::Instant::now);
1456 let _guard = scopeguard_attn(_t0);
1457 let (hd, rd) = (cfg.head_dim, cfg.rope_head_dim);
1458 let pos = st.pos;
1459 if std::env::var("CMF_FREQ_DEBUG").is_ok() && li == 0 && pos == 0 {
1460 eprintln!(
1461 " [порт] rd={rd} частот={} inv_freq[0..4]={:?}",
1462 inv_freq.len(),
1463 &inv_freq[..4.min(inv_freq.len())]
1464 );
1465 }
1466
1467 let mut qr = vec![0.0f32; cfg.q_lora_rank];
1472 let mut kv = vec![0.0f32; hd];
1473 crate::qtensor::QTensor::matvec_many([&l.wq_a, &l.wkv], hidden, [&mut qr, &mut kv], pool);
1474 rms_weighted(&mut qr, &l.q_norm, cfg.norm_eps);
1475 let split_config = st.partial_set.iter().any(|&p| p) && st.split_deep;
1493 let past_chain =
1494 st.dev_owned && (li >= st.dev_set.len() || !st.dev_set.get(li).copied().unwrap_or(false));
1495 if std::env::var("CMF_DSV4_GATE_DBG").is_ok() {
1496 eprintln!(
1497 "[gate] li={li} pos={} split={split_config} past={past_chain} dev_owned={} set_len={} part_len={}",
1498 st.pos,
1499 st.dev_owned,
1500 st.dev_set.len(),
1501 st.partial_set.len()
1502 );
1503 }
1504 let on_gpu = gpu_attn_enabled() && !(split_config && past_chain);
1505
1506 rms_weighted(&mut kv, &l.kv_norm, cfg.norm_eps);
1507 rope_tail(&mut kv, inv_freq, pos, rd, false);
1508
1509 if let Some(cp) = &l.compressor {
1514 let mut pk = std::mem::take(&mut st.pending_kv[li]);
1515 let mut ps = std::mem::take(&mut st.pending_score[li]);
1516 let mut qk = std::mem::take(&mut st.prev_kv[li]);
1517 let mut qs = std::mem::take(&mut st.prev_score[li]);
1518 let entry = compressor_step(
1519 cp,
1520 hidden,
1521 pos,
1522 rd,
1523 cfg.norm_eps,
1524 inv_freq,
1525 pool,
1526 &mut pk,
1527 &mut ps,
1528 &mut qk,
1529 &mut qs,
1530 );
1531 st.pending_kv[li] = pk;
1532 st.pending_score[li] = ps;
1533 st.prev_kv[li] = qk;
1534 st.prev_score[li] = qs;
1535 if let Some(e) = entry {
1536 st.compressed[li].extend_from_slice(&e);
1537 }
1538 }
1539 if let Some(ix) = &l.indexer {
1544 let mut pk = std::mem::take(&mut st.pending_ix_kv[li]);
1545 let mut ps = std::mem::take(&mut st.pending_ix_score[li]);
1546 let mut qk = std::mem::take(&mut st.prev_ix_kv[li]);
1547 let mut qs = std::mem::take(&mut st.prev_ix_score[li]);
1548 let entry = compressor_step(
1549 &ix.compressor,
1550 hidden,
1551 pos,
1552 rd,
1553 cfg.norm_eps,
1554 inv_freq,
1555 pool,
1556 &mut pk,
1557 &mut ps,
1558 &mut qk,
1559 &mut qs,
1560 );
1561 st.pending_ix_kv[li] = pk;
1562 st.pending_ix_score[li] = ps;
1563 st.prev_ix_kv[li] = qk;
1564 st.prev_ix_score[li] = qs;
1565 if let Some(e) = entry {
1566 st.index_kv[li].extend_from_slice(&e);
1567 }
1568 }
1569
1570 st.window[li].extend_from_slice(&kv);
1571 let cap = cfg.window * hd;
1575 if st.window[li].len() > cap {
1576 let drop = st.window[li].len() - cap;
1577 st.window[li].drain(..drop);
1578 }
1579 let win_len = st.window[li].len() / hd;
1580 let n_pos = win_len + st.compressed[li].len() / hd;
1581
1582 let mut idxs: Vec<usize> = (0..win_len).collect();
1591 if !st.compressed[li].is_empty() && !no_compressed() {
1592 let n_comp = st.compressed[li].len() / hd;
1593 match &l.indexer {
1594 Some(ix) => {
1595 let ih = ix.weights_proj.rows();
1611 let idim = ix.wq_b.rows() / ih.max(1);
1612 let mut qi = vec![0.0f32; ix.wq_b.rows()];
1613 ix.wq_b.matvec(&qr, &mut qi, pool);
1614 for h in 0..ih {
1615 rope_tail(&mut qi[h * idim..(h + 1) * idim], inv_freq, pos, rd, false);
1616 }
1617 let mut hw = vec![0.0f32; ih];
1618 ix.weights_proj.matvec(hidden, &mut hw, pool);
1619 let sc_factor = (idim as f32).powf(-0.5) * (ih as f32).powf(-0.5);
1620 for w in hw.iter_mut() {
1621 *w *= sc_factor;
1622 }
1623 let n_ix = st.index_kv[li].len() / idim.max(1);
1624 let mut sc = Vec::new();
1625 index_scores(
1626 &qi,
1627 &st.index_kv[li],
1628 &hw,
1629 ih,
1630 idim,
1631 n_ix.min(n_comp),
1632 n_ix.min(n_comp),
1633 pool,
1634 &mut sc,
1635 );
1636 let mut picked = Vec::new();
1637 top_k_positions(&sc, cfg.index_topk, &mut picked);
1638 idxs.extend(picked.into_iter().map(|p| win_len + p));
1639 }
1640 None => idxs.extend((0..n_comp).map(|p| win_len + p)),
1641 }
1642 }
1643 debug_assert!(idxs.iter().all(|&p| p < n_pos));
1644 if let Some(p) = prep_out {
1645 p.qr = qr;
1646 p.idxs = idxs;
1647 p.win_len = win_len;
1648 return;
1649 }
1650
1651 let scale = (hd as f32).powf(-0.5);
1653 #[cfg(feature = "gpu")]
1654 if on_gpu
1655 && {
1656 if std::env::var("CMF_DSV4_XCHK").is_ok() {
1657 if let Some(card) = crate::gpu_wgpu::dsv4_dbg_read_tag(45, 0, hidden.len()) {
1661 let md = hidden
1662 .iter()
1663 .zip(card.iter())
1664 .map(|(a, b)| (a - b).abs())
1665 .fold(0.0f32, f32::max);
1666 eprintln!("[xchk] li={li} pos={pos} x2 maxdiff={md:.3e}");
1667 }
1668 }
1669 true
1670 }
1671 && attn_frame(
1672 l, cfg, st, li, hidden, &qr, &idxs, inv_freq, pos, win_len, scale, None, out,
1673 )
1674 {
1675 return;
1676 }
1677
1678 let mut q = vec![0.0f32; cfg.n_heads * hd];
1680 l.wq_b.matvec(&qr, &mut q, pool);
1681 for h in 0..cfg.n_heads {
1682 let head = &mut q[h * hd..(h + 1) * hd];
1683 rms_inplace(head, cfg.norm_eps);
1684 rope_tail(head, inv_freq, pos, rd, false);
1685 }
1686 let mut cache: Vec<f32> = st.window[li].clone();
1687 cache.extend_from_slice(&st.compressed[li]);
1688
1689 let mut attn = vec![0.0f32; cfg.n_heads * hd];
1691 for h in 0..cfg.n_heads {
1692 let qh = &q[h * hd..(h + 1) * hd];
1693 let oh = &mut attn[h * hd..(h + 1) * hd];
1698 sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
1699 rope_tail(oh, inv_freq, pos, rd, true);
1700 }
1701
1702 o_project(
1708 &attn,
1709 &|r, x, sc| l.wo_a.row_dot(r, x, sc),
1710 l.wo_a.cols(),
1711 &|mid, dst| l.wo_b.matvec(mid, dst, pool),
1712 cfg.o_groups,
1713 cfg.o_lora_rank,
1714 pool,
1715 out,
1716 );
1717}
1718
1719pub fn rms_weighted(v: &mut [f32], w: &[f32], eps: f32) {
1721 let ms = v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32;
1722 let inv = 1.0 / (ms + eps).sqrt();
1723 for (x, g) in v.iter_mut().zip(w) {
1724 *x = *x * inv * g;
1725 }
1726}
1727
1728thread_local! {
1743 static ROUTE_COUNTS: std::cell::RefCell<Vec<Vec<u64>>> =
1744 const { std::cell::RefCell::new(Vec::new()) };
1745}
1746
1747fn route_stats_on() -> bool {
1748 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1749 *ON.get_or_init(|| std::env::var("CMF_MOE_STATS").is_ok())
1750}
1751
1752fn record_route(
1753 li: usize,
1754 n_layers_hint: usize,
1755 n_experts: usize,
1756 routed: &[(usize, f32)],
1757) {
1758 ROUTE_COUNTS.with(|c| {
1759 let mut c = c.borrow_mut();
1760 if c.len() <= li.max(n_layers_hint) {
1761 c.resize(li.max(n_layers_hint) + 1, Vec::new());
1762 }
1763 let row = &mut c[li];
1764 if row.len() < n_experts {
1765 row.resize(n_experts, 0);
1766 }
1767 for &(e, weight) in routed {
1768 if e < row.len() {
1769 let mass = (weight.abs() as f64 * 1_000_000.0).round() as u64;
1773 row[e] = row[e].saturating_add(mass.max(1));
1774 }
1775 }
1776 });
1777}
1778
1779pub fn take_route_counts() -> Vec<Vec<u64>> {
1781 ROUTE_COUNTS.with(|c| std::mem::take(&mut *c.borrow_mut()))
1782}
1783
1784struct Charge(
1788 Option<std::time::Instant>,
1789 &'static std::sync::atomic::AtomicU64,
1790);
1791impl Drop for Charge {
1792 fn drop(&mut self) {
1793 if let Some(t) = self.0 {
1794 self.1.fetch_add(
1795 t.elapsed().as_nanos() as u64,
1796 std::sync::atomic::Ordering::Relaxed,
1797 );
1798 }
1799 }
1800}
1801fn scopeguard_attn(t: Option<std::time::Instant>) -> Charge {
1802 Charge(t, &prof::ATTN_NS)
1803}
1804fn scopeguard_moe(t: Option<std::time::Instant>, li: usize) -> Charge {
1805 if t.is_some() {
1806 prof::note_layer(li);
1807 }
1808 Charge(t, &prof::MOE_NS)
1809}
1810
1811#[cfg(feature = "gpu")]
1815#[allow(clippy::too_many_arguments)]
1816fn dsv4_layer_loop(
1817 state: &mut [f32],
1818 layers: &[Dsv4Layer],
1819 g: &Dsv4Globals,
1820 cfg: &Dsv4Cfg,
1821 st: &mut Dsv4State,
1822 token_id: u32,
1823 inv_freq: &[f32],
1824 pool: Option<&crate::pool::Pool>,
1825 scratch: &mut HcScratch,
1826) -> bool {
1827 let dim = cfg.dim;
1828 let freqs_of = |l: &Dsv4Layer| -> &[f32] {
1829 let f = if l.compressor.is_some() {
1830 &g.inv_freq_compress
1831 } else {
1832 &g.inv_freq_window
1833 };
1834 if f.is_empty() { inv_freq } else { f.as_slice() }
1835 };
1836 {
1849 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1850 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
1851 if let (Some(idx), Some(model)) = (g.head.model_idx(), g.head.model_arc()) {
1852 let ok = crate::gpu_wgpu::dsv4_weight_ready(&model, idx);
1853 tracing::info!("dsv4: голова на карте: {}", if ok { "да" } else { "нет" });
1854 }
1855 }
1856 }
1857 let mut on_dev = vec![false; layers.len()];
1858 let mut partial_dev = vec![false; layers.len()];
1859 for (li, l) in layers.iter().enumerate() {
1860 if l.wq_a.model_idx().is_none()
1861 || l.wq_b.model_idx().is_none()
1862 || l.wo_a.model_idx().is_none()
1863 || l.wo_b.model_idx().is_none()
1864 {
1865 return false;
1866 }
1867 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
1868 return false;
1869 };
1870 let gu_q2 = l
1871 .experts
1872 .first()
1873 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
1874 let mut want = vec![
1889 l.wq_a.model_idx(),
1890 l.wq_b.model_idx(),
1891 l.wo_a.model_idx(),
1892 l.wo_b.model_idx(),
1893 ];
1894 if chain_enabled() {
1895 want.push(l.wkv.model_idx());
1896 if let Some(cp) = &l.compressor {
1897 want.push(cp.wkv.model_idx());
1898 want.push(cp.wgate.model_idx());
1899 }
1900 if let Some(ix) = &l.indexer {
1901 want.push(ix.wq_b.model_idx());
1902 want.push(ix.weights_proj.model_idx());
1903 want.push(ix.compressor.wkv.model_idx());
1904 want.push(ix.compressor.wgate.model_idx());
1905 }
1906 }
1907 let attn_ok = want
1908 .into_iter()
1909 .flatten()
1910 .all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
1911 let pk = pack_for(l, cfg, li);
1916 if let Some(pk) = pk {
1917 let dn_q2 = l
1918 .experts
1919 .first()
1920 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
1921 let experts_ok = crate::gpu_wgpu::dsv4_experts_ready(
1922 &model,
1923 &pk.tensors,
1924 cfg.moe_inter,
1925 dim,
1926 gu_q2,
1927 dn_q2,
1928 );
1929 on_dev[li] = attn_ok && experts_ok && pk.route_complete();
1930 partial_dev[li] = attn_ok && experts_ok && !pk.route_complete();
1931 }
1932 }
1933 let active_dev: Vec<bool> = on_dev
1934 .iter()
1935 .zip(&partial_dev)
1936 .map(|(&full, &partial)| full || partial)
1937 .collect();
1938 if !active_dev.iter().any(|&x| x) {
1939 return false;
1940 }
1941 if st.partial_set.len() != partial_dev.len() || st.partial_set != partial_dev {
1946 st.partial_set = partial_dev.clone();
1947 st.split_deep = active_dev
1948 .iter()
1949 .zip(&partial_dev)
1950 .filter(|(a, p)| !**a || **p)
1951 .count()
1952 > 1;
1953 }
1954
1955 {
1959 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1960 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
1961 let host: Vec<usize> = active_dev
1962 .iter()
1963 .enumerate()
1964 .filter(|&(_, d)| !*d)
1965 .map(|(i, _)| i)
1966 .collect();
1967 let partial: Vec<(usize, usize)> = partial_dev
1968 .iter()
1969 .enumerate()
1970 .filter(|&(_, d)| *d)
1971 .filter_map(|(li, _)| pack_for(&layers[li], cfg, li).map(|p| (li, p.globals.len())))
1972 .collect();
1973 if host.is_empty() && partial.is_empty() {
1974 tracing::info!("dsv4: все {} слоёв на карте", on_dev.len());
1975 } else {
1976 tracing::info!(
1977 "dsv4: {} из {} слоёв используют карту; частичные {:?}; на хосте {:?}",
1978 active_dev.len() - host.len(),
1979 on_dev.len(),
1980 partial,
1981 host,
1982 );
1983 }
1984 }
1985 }
1986
1987 let (mut folded, post0, comb0) = hc_fold_norm(
1989 state,
1990 &layers[0].hc_attn_fn,
1991 &layers[0].hc_attn_scale,
1992 &layers[0].hc_attn_base,
1993 &layers[0].attn_norm,
1994 cfg,
1995 pool,
1996 );
1997 if !crate::gpu_wgpu::dsv4_state_write(state) || !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
1998 {
1999 return false;
2000 }
2001 if st.dev_owned && st.dev_set != active_dev {
2012 let left: Vec<usize> = (0..active_dev.len().min(st.dev_set.len()))
2013 .filter(|&i| st.dev_set[i] && !active_dev[i])
2014 .collect();
2015 if !left.is_empty() {
2016 tracing::warn!("слои {left:?} ушли с карты — кеши на разных сторонах");
2017 return false;
2018 }
2019 }
2023 let chain = chain_enabled();
2024 let layer_cap = {
2032 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
2033 *N.get_or_init(|| {
2034 std::env::var("CMF_DSV4_LAYERS_PROBE")
2035 .ok()
2036 .and_then(|v| v.parse::<usize>().ok())
2037 .unwrap_or(usize::MAX)
2038 })
2039 };
2040 let mut run: Vec<usize> = Vec::new();
2041 let mut sink_out = vec![0.0f32; dim];
2042 let mut state_on_host = true;
2047 for (li, l) in layers.iter().enumerate() {
2048 if li >= layer_cap {
2049 break;
2050 }
2051 if prof::on() {
2057 prof::note_layer(li);
2058 }
2059 if chain && on_dev[li] {
2060 run.push(li);
2065 if run.len() >= chain_max() || dspark_wants(li) {
2070 let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
2071 let captured = *run.last().unwrap();
2072 if !dsv4_chain_run(
2073 layers,
2074 &run,
2075 cfg,
2076 g,
2077 st,
2078 token_id,
2079 &mut folded,
2080 Some(state),
2081 1,
2082 &[],
2083 need_qn,
2084 pool,
2085 ) {
2086 return false;
2087 }
2088 state_on_host = true;
2089 verify_fp("walk", st.pos, captured, state);
2090 dspark_note(captured, state, cfg);
2091 run.clear();
2092 }
2093 continue;
2094 }
2095 if chain && !run.is_empty() {
2096 if !dsv4_chain_run(
2100 layers,
2101 &run,
2102 cfg,
2103 g,
2104 st,
2105 token_id,
2106 &mut folded,
2107 Some(state),
2108 1,
2109 &[],
2110 run[0] == 0 || !on_dev[run[0] - 1],
2111 pool,
2112 ) {
2113 return false;
2114 }
2115 state_on_host = true;
2116 let last = *run.last().unwrap();
2117 verify_fp("walk", st.pos, last, state);
2118 dspark_note(last, state, cfg);
2119 }
2120 run.clear();
2121 if partial_dev[li] && chain1_on() {
2122 if let Some(home) = dsv4_chain1_layer(
2123 state,
2124 &mut folded,
2125 layers,
2126 l,
2127 cfg,
2128 st,
2129 token_id,
2130 li,
2131 freqs_of(l),
2132 pool,
2133 state_on_host,
2134 ) {
2135 st.dev_filled[li] = (st.window[li].len() / cfg.head_dim).min(cfg.window);
2143 st.dev_n_comp[li] = st.compressed[li].len() / cfg.head_dim;
2144 st.dev_n_ix[li] = l.indexer.as_ref().map_or(0, |ix| {
2145 let ih = ix.weights_proj.rows();
2146 let idim = ix.wq_b.rows() / ih.max(1);
2147 st.index_kv[li].len() / idim.max(1)
2148 });
2149 state_on_host = home;
2150 if verify_fp_on(st.pos) {
2151 if !state_on_host && !crate::gpu_wgpu::dsv4_state_read(state) {
2152 return false;
2153 }
2154 state_on_host = true;
2155 verify_fp("walk", st.pos, li, state);
2156 }
2157 if dspark_wants(li) {
2161 if !state_on_host && crate::gpu_wgpu::dsv4_state_read(state) {
2162 state_on_host = true;
2163 }
2164 if state_on_host {
2165 dspark_note(li, state, cfg);
2166 }
2167 }
2168 continue;
2169 }
2170 }
2171 if partial_dev[li] && partial_walk_on() {
2172 let Some(home) = dsv4_partial_layer(
2177 state,
2178 &mut folded,
2179 layers,
2180 l,
2181 cfg,
2182 st,
2183 token_id,
2184 li,
2185 freqs_of(l),
2186 pool,
2187 state_on_host,
2188 ) else {
2189 return false;
2190 };
2191 state_on_host = home;
2192 if home {
2193 dspark_note(li, state, cfg);
2194 }
2195 continue;
2196 }
2197 if !on_dev[li] {
2198 if !state_on_host && !crate::gpu_wgpu::dsv4_state_read(state) {
2199 return false;
2200 }
2201 state_on_host = true;
2202 let freqs = freqs_of(l);
2203 hc_block(
2204 state,
2205 &l.hc_attn_fn,
2206 &l.hc_attn_scale,
2207 &l.hc_attn_base,
2208 &l.attn_norm,
2209 cfg,
2210 scratch,
2211 pool,
2212 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
2213 );
2214 hc_block(
2215 state,
2216 &l.hc_ffn_fn,
2217 &l.hc_ffn_scale,
2218 &l.hc_ffn_base,
2219 &l.ffn_norm,
2220 cfg,
2221 scratch,
2222 pool,
2223 |f, o| {
2230 if host_cpu_moe() {
2231 crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
2232 } else {
2233 moe_step(f, l, cfg, token_id, li, pool, o)
2234 }
2235 },
2236 );
2237 if layers.get(li + 1).is_some() && on_dev.get(li + 1).copied().unwrap_or(false) {
2242 let n = &layers[li + 1];
2243 let (f, p2, c2) = hc_fold_norm(
2244 state,
2245 &n.hc_attn_fn,
2246 &n.hc_attn_scale,
2247 &n.hc_attn_base,
2248 &n.attn_norm,
2249 cfg,
2250 pool,
2251 );
2252 folded = f;
2253 if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2) {
2254 return false;
2255 }
2256 if !crate::gpu_wgpu::dsv4_state_write(state) {
2257 return false;
2258 }
2259 }
2260 dspark_note(li, state, cfg);
2261 continue;
2262 }
2263 let mut prep = AttnPrep::default();
2264 let _tp = prof::on().then(std::time::Instant::now);
2265 attention_step(
2266 &folded,
2267 l,
2268 cfg,
2269 st,
2270 li,
2271 freqs_of(l),
2272 pool,
2273 Some(&mut prep),
2274 &mut sink_out,
2275 );
2276 if let Some(t) = _tp {
2277 prof::PREP_NS.fetch_add(
2278 t.elapsed().as_nanos() as u64,
2279 std::sync::atomic::Ordering::Relaxed,
2280 );
2281 }
2282 let hd = cfg.head_dim;
2284 let n_comp = st.compressed[li].len() / hd;
2285 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2286 let kv_id = st.kv_id;
2287 let _tc = prof::on().then(std::time::Instant::now);
2288 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap)
2289 || (n_comp > 0
2290 && !crate::gpu_wgpu::dsv4_cache_write(
2291 kv_id,
2292 li,
2293 cfg.window * hd,
2294 &st.compressed[li],
2295 cap,
2296 ))
2297 {
2298 return false;
2299 }
2300 if let Some(t) = _tc {
2301 prof::CACHEW_NS.fetch_add(
2302 t.elapsed().as_nanos() as u64,
2303 std::sync::atomic::Ordering::Relaxed,
2304 );
2305 }
2306 let idx32: Vec<u32> = prep
2307 .idxs
2308 .iter()
2309 .map(|&p| {
2310 if p < prep.win_len {
2311 p as u32
2312 } else {
2313 (cfg.window + (p - prep.win_len)) as u32
2314 }
2315 })
2316 .collect();
2317 let Some(pk) = pack_for(l, cfg, li) else {
2318 return false;
2319 };
2320 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
2321 l.wq_a.model_idx(),
2322 l.wq_b.model_idx(),
2323 l.wo_a.model_idx(),
2324 l.wo_b.model_idx(),
2325 ) else {
2326 return false;
2327 };
2328 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
2329 return false;
2330 };
2331 let forced: Option<Vec<usize>> = l.tid2eid.as_ref().and_then(|tbl| {
2332 let v: Vec<usize> = if pk.needs_remap() {
2333 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
2334 } else {
2335 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
2336 .into_iter()
2337 .map(|gi| pk.to_slot[gi])
2338 .collect()
2339 };
2340 if v.contains(&usize::MAX) {
2341 None
2342 } else {
2343 Some(v)
2344 }
2345 });
2346 if l.tid2eid.is_some() && forced.is_none() {
2347 return false;
2348 }
2349 let nxt = layers.get(li + 1);
2350 let w = crate::gpu_wgpu::Dsv4LayerW {
2351 attn: crate::gpu_wgpu::Dsv4AttnW {
2352 wq_a,
2353 wq_b,
2354 wo_a,
2355 wo_b,
2356 q_norm: &l.q_norm,
2357 sink: &l.attn_sink,
2358 },
2359 moe: crate::gpu_wgpu::Dsv4MoeW {
2360 router: &[],
2361 experts: &pk.tensors,
2362 logits: &[],
2363 bias: pk.bias.as_deref(),
2370 mask: pk.mask.as_deref(),
2371 forced: forced.as_deref(),
2372 remap: pk.needs_remap().then_some(pk.remap.as_slice()),
2373 },
2374 hc_ffn_fn: &l.hc_ffn_fn,
2375 hc_ffn_scale: &l.hc_ffn_scale,
2376 hc_ffn_base: &l.hc_ffn_base,
2377 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2378 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2379 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2380 ffn_norm: &l.ffn_norm,
2381 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2382 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2383 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2384 router: &pk.router,
2385 };
2386 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2387 attn: crate::gpu_wgpu::Dsv4AttnGeom {
2388 dim,
2389 nh: cfg.n_heads,
2390 hd,
2391 rd: cfg.rope_head_dim,
2392 q_lora: cfg.q_lora_rank,
2393 o_lora: cfg.o_lora_rank,
2394 o_groups: cfg.o_groups,
2395 eps: cfg.norm_eps,
2396 scale: (hd as f32).powf(-0.5),
2397 },
2398 moe: crate::gpu_wgpu::Dsv4MoeGeom {
2399 hidden: dim,
2400 inter: cfg.moe_inter,
2401 top_k: cfg.top_k,
2402 route_scale: cfg.route_scale,
2403 swiglu_limit: cfg.swiglu_limit,
2404 gu_q2: l.experts.first().is_some_and(|e| {
2405 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
2406 }),
2407 },
2408 hc: cfg.hc_mult,
2409 hc_eps: cfg.hc_eps,
2410 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2411 };
2412 let mut next = vec![0.0f32; dim];
2413 if !crate::gpu_wgpu::dsv4_layer_frame(
2414 &model,
2415 &w,
2416 geom,
2417 kv_id,
2418 li,
2419 Some(&prep.qr),
2420 &idx32,
2421 freqs_of(l),
2422 st.pos,
2423 &mut next,
2424 None,
2425 None,
2426 &mut Vec::new(),
2427 ) {
2428 return false;
2429 }
2430 state_on_host = false;
2431 folded = next;
2432 dspark_note(li, state, cfg);
2433 }
2434 let mut state_home = false;
2435 if chain {
2436 if !run.is_empty() {
2437 let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
2441 let last_on_dev = *on_dev.last().unwrap_or(&false);
2442 let carry = last_on_dev && run.last() == Some(&(layers.len() - 1));
2443 let ok = if carry {
2444 let r = dsv4_chain_run(
2445 layers,
2446 &run,
2447 cfg,
2448 g,
2449 st,
2450 token_id,
2451 &mut folded,
2452 Some(state),
2453 1,
2454 &[],
2455 need_qn,
2456 pool,
2457 );
2458 state_home = r;
2459 state_on_host = r;
2460 if r {
2461 dspark_note(*run.last().unwrap(), state, cfg);
2462 }
2463 r
2464 } else {
2465 let r = dsv4_chain_run(
2466 layers,
2467 &run,
2468 cfg,
2469 g,
2470 st,
2471 token_id,
2472 &mut folded,
2473 None,
2474 1,
2475 &[],
2476 need_qn,
2477 pool,
2478 );
2479 if r {
2480 state_on_host = false;
2481 }
2482 r
2483 };
2484 if !ok {
2485 return false;
2486 }
2487 }
2488 if st.dev_set.is_empty() {
2489 st.dev_set = active_dev.clone();
2490 st.partial_set = partial_dev.clone();
2491 let mut idxs = Vec::new();
2497 for (li, l) in layers.iter().enumerate() {
2498 if !active_dev.get(li).copied().unwrap_or(false) {
2499 continue;
2500 }
2501 for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b, &l.gate] {
2502 idxs.extend(t.model_idx());
2503 }
2504 if let Some(pk) = pack_for(l, cfg, li) {
2505 for &(a, b, c) in &pk.tensors {
2506 idxs.extend([a, b, c]);
2507 }
2508 }
2509 }
2510 for (li, l) in layers.iter().enumerate() {
2518 if active_dev.get(li).copied().unwrap_or(false) {
2519 continue;
2520 }
2521 let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
2522 tracing::info!(
2523 "слой {li} на хосте: упаковано {packed} экспертов из {}",
2524 cfg.n_routed_experts
2525 );
2526 }
2527 let pinned = layers
2528 .iter()
2529 .find_map(|l| l.experts.first().and_then(|e| e.w1.model_arc()))
2530 .map_or(0, |m| crate::gpu_wgpu::pin_weights(&m, &idxs));
2531 tracing::info!(
2532 "закреплено на карте: {pinned} тензоров {} слоёв",
2533 on_dev.iter().filter(|&&x| x).count()
2534 );
2535 }
2536 }
2537 if state_home || state_on_host {
2538 return true;
2539 }
2540 crate::gpu_wgpu::dsv4_state_read(state)
2541}
2542
2543#[cfg(feature = "gpu")]
2548#[allow(clippy::too_many_arguments)]
2549fn dsv4_partial_layer(
2550 state: &mut [f32],
2551 folded: &mut Vec<f32>,
2552 layers: &[Dsv4Layer],
2553 l: &Dsv4Layer,
2554 cfg: &Dsv4Cfg,
2555 st: &mut Dsv4State,
2556 token_id: u32,
2557 li: usize,
2558 freqs: &[f32],
2559 pool: Option<&crate::pool::Pool>,
2560 state_on_host: bool,
2561) -> Option<bool> {
2562 let dim = cfg.dim;
2563 if state_on_host {
2572 let (f, post, comb) = hc_fold_norm(
2573 state,
2574 &l.hc_attn_fn,
2575 &l.hc_attn_scale,
2576 &l.hc_attn_base,
2577 &l.attn_norm,
2578 cfg,
2579 pool,
2580 );
2581 *folded = f;
2582 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2583 || !crate::gpu_wgpu::dsv4_state_write(state)
2584 {
2585 return None;
2586 }
2587 }
2588 let mut prep = AttnPrep::default();
2589 let mut sink = vec![0.0f32; dim];
2590 attention_step(
2591 folded,
2592 l,
2593 cfg,
2594 st,
2595 li,
2596 freqs,
2597 pool,
2598 Some(&mut prep),
2599 &mut sink,
2600 );
2601 let hd = cfg.head_dim;
2602 let n_comp = st.compressed[li].len() / hd;
2603 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2604 if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
2605 || (n_comp > 0
2606 && !crate::gpu_wgpu::dsv4_cache_write(
2607 st.kv_id,
2608 li,
2609 cfg.window * hd,
2610 &st.compressed[li],
2611 cap,
2612 ))
2613 {
2614 return None;
2615 }
2616 let a_tail = crate::gpu_wgpu::Dsv4HcTail {
2617 fn_: &l.hc_ffn_fn,
2618 scale: &l.hc_ffn_scale,
2619 base: &l.hc_ffn_base,
2620 norm: &l.ffn_norm,
2621 hc: cfg.hc_mult,
2622 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2623 hc_eps: cfg.hc_eps,
2624 eps: cfg.norm_eps,
2625 };
2626 let scale = (cfg.head_dim as f32).powf(-0.5);
2627 let mut ffn_input = if cpu_overlap_on() {
2633 vec![0.0f32; dim]
2634 } else {
2635 Vec::new()
2636 };
2637 if !attn_frame(
2638 l,
2639 cfg,
2640 st,
2641 li,
2642 folded,
2643 &prep.qr,
2644 &prep.idxs,
2645 freqs,
2646 st.pos,
2647 prep.win_len,
2648 scale,
2649 Some(&a_tail),
2650 &mut ffn_input,
2651 ) {
2652 return None;
2653 }
2654 let nxt = layers.get(li + 1);
2655 let forced = l
2656 .tid2eid
2657 .as_ref()
2658 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
2659 let mut next = vec![0.0f32; dim];
2660 let (cold_sum, cold_count) = moe_frame(
2661 &ffn_input,
2662 l,
2663 cfg,
2664 li,
2665 &[],
2666 forced.as_deref(),
2667 pool,
2668 Some(&a_tail),
2669 None,
2674 &mut next,
2675 )?;
2676 let state_ok = if cold_count == 0 {
2682 crate::gpu_wgpu::dsv4_state_read(state)
2683 } else {
2684 crate::gpu_wgpu::dsv4_state_add_cold(&cold_sum, cfg.hc_mult, state)
2685 };
2686 if !state_ok {
2687 return None;
2688 }
2689 if let Some(n) = nxt {
2690 let (f, post, comb) = hc_fold_norm(
2691 state,
2692 &n.hc_attn_fn,
2693 &n.hc_attn_scale,
2694 &n.hc_attn_base,
2695 &n.attn_norm,
2696 cfg,
2697 pool,
2698 );
2699 *folded = f;
2700 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2701 || !crate::gpu_wgpu::dsv4_state_write(state)
2702 {
2703 return None;
2704 }
2705 }
2706 Some(true)
2711}
2712
2713#[cfg(feature = "gpu")]
2720fn chain1_on() -> bool {
2721 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2722 *ON.get_or_init(|| {
2723 std::env::var("CMF_DSV4_CHAIN1")
2724 .map(|v| v != "0")
2725 .unwrap_or(true)
2726 })
2727}
2728
2729#[cfg(feature = "gpu")]
2735fn cpu_overlap_on() -> bool {
2736 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2737 *ON.get_or_init(|| std::env::var("CMF_DSV4_CPU_OVERLAP").is_ok_and(|v| v != "0"))
2738}
2739
2740#[cfg(feature = "gpu")]
2741#[allow(clippy::too_many_arguments)]
2742fn dsv4_chain1_layer(
2743 state: &mut [f32],
2744 folded: &mut Vec<f32>,
2745 layers: &[Dsv4Layer],
2746 l: &Dsv4Layer,
2747 cfg: &Dsv4Cfg,
2748 st: &mut Dsv4State,
2749 token_id: u32,
2750 li: usize,
2751 freqs: &[f32],
2752 pool: Option<&crate::pool::Pool>,
2753 state_on_host: bool,
2754) -> Option<bool> {
2755 let dim = cfg.dim;
2756 let pk = pack_for(l, cfg, li)?;
2757 if pk.route_complete() {
2758 return None;
2759 }
2760 let model = l.experts.first().and_then(|e| e.w1.model_arc())?;
2761 if state_on_host {
2765 let (f, post, comb) = hc_fold_norm(
2766 state,
2767 &l.hc_attn_fn,
2768 &l.hc_attn_scale,
2769 &l.hc_attn_base,
2770 &l.attn_norm,
2771 cfg,
2772 pool,
2773 );
2774 *folded = f;
2775 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2776 || !crate::gpu_wgpu::dsv4_state_write(state)
2777 {
2778 return None;
2779 }
2780 }
2781 let mut prep = AttnPrep::default();
2782 let mut sink = vec![0.0f32; dim];
2783 attention_step(
2784 folded,
2785 l,
2786 cfg,
2787 st,
2788 li,
2789 freqs,
2790 pool,
2791 Some(&mut prep),
2792 &mut sink,
2793 );
2794 let hd = cfg.head_dim;
2795 let n_comp = st.compressed[li].len() / hd;
2796 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2797 let kv_id = st.kv_id;
2798 if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap)
2799 || (n_comp > 0
2800 && !crate::gpu_wgpu::dsv4_cache_write(
2801 kv_id,
2802 li,
2803 cfg.window * hd,
2804 &st.compressed[li],
2805 cap,
2806 ))
2807 {
2808 return None;
2809 }
2810 let idx32: Vec<u32> = prep
2811 .idxs
2812 .iter()
2813 .map(|&p| {
2814 if p < prep.win_len {
2815 p as u32
2816 } else {
2817 (cfg.window + (p - prep.win_len)) as u32
2818 }
2819 })
2820 .collect();
2821 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
2822 l.wq_a.model_idx(),
2823 l.wq_b.model_idx(),
2824 l.wo_a.model_idx(),
2825 l.wo_b.model_idx(),
2826 ) else {
2827 return None;
2828 };
2829 let forced: Option<Vec<usize>> = l
2832 .tid2eid
2833 .as_ref()
2834 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
2835 let dynv = pk.dynslots.lock().unwrap();
2836 let nxt = layers.get(li + 1);
2837 let w = crate::gpu_wgpu::Dsv4LayerW {
2838 attn: crate::gpu_wgpu::Dsv4AttnW {
2839 wq_a,
2840 wq_b,
2841 wo_a,
2842 wo_b,
2843 q_norm: &l.q_norm,
2844 sink: &l.attn_sink,
2845 },
2846 moe: crate::gpu_wgpu::Dsv4MoeW {
2847 router: &[],
2848 experts: &pk.tensors,
2849 logits: &[],
2850 bias: pk.bias.as_deref(),
2853 mask: pk.mask.as_deref(),
2854 forced: forced.as_deref(),
2855 remap: Some(&dynv.remap),
2856 },
2857 hc_ffn_fn: &l.hc_ffn_fn,
2858 hc_ffn_scale: &l.hc_ffn_scale,
2859 hc_ffn_base: &l.hc_ffn_base,
2860 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2861 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2862 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2863 ffn_norm: &l.ffn_norm,
2864 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2865 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2866 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2867 router: &pk.router,
2868 };
2869 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2870 attn: crate::gpu_wgpu::Dsv4AttnGeom {
2871 dim,
2872 nh: cfg.n_heads,
2873 hd,
2874 rd: cfg.rope_head_dim,
2875 q_lora: cfg.q_lora_rank,
2876 o_lora: cfg.o_lora_rank,
2877 o_groups: cfg.o_groups,
2878 eps: cfg.norm_eps,
2879 scale: (hd as f32).powf(-0.5),
2880 },
2881 moe: crate::gpu_wgpu::Dsv4MoeGeom {
2882 hidden: dim,
2883 inter: cfg.moe_inter,
2884 top_k: cfg.top_k,
2885 route_scale: cfg.route_scale,
2886 swiglu_limit: cfg.swiglu_limit,
2887 gu_q2: l
2888 .experts
2889 .first()
2890 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
2891 },
2892 hc: cfg.hc_mult,
2893 hc_eps: cfg.hc_eps,
2894 sinkhorn_iters: cfg.hc_sinkhorn_iters,
2895 };
2896 let mut next = vec![0.0f32; dim];
2897 let mut cold: Vec<(usize, f32)> = Vec::new();
2898 let mut routed: Vec<(usize, f32)> = Vec::new();
2899 let mut cold_x: Vec<f32> = Vec::new();
2900 if !crate::gpu_wgpu::dsv4_layer_frame(
2901 &model,
2902 &w,
2903 geom,
2904 kv_id,
2905 li,
2906 Some(&prep.qr),
2907 &idx32,
2908 freqs,
2909 st.pos,
2910 &mut next,
2911 Some(&mut cold),
2912 route_stats_on().then_some(&mut routed),
2913 &mut cold_x,
2914 ) {
2915 return None;
2916 }
2917 if route_stats_on() {
2918 record_route(li, layers.len(), cfg.n_routed_experts, &routed);
2919 }
2920 drop(dynv);
2921 if cold.is_empty() {
2922 *folded = next;
2926 return Some(false);
2927 }
2928 if cold_x.len() < dim {
2931 return None;
2932 }
2933 let mut cold_sum = vec![0.0f32; dim];
2934 {
2935 let results: Vec<std::sync::Mutex<Vec<f32>>> =
2936 cold.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
2937 let (cold_ref, results_ref, x_ref) = (&cold, &results, &cold_x[..dim]);
2938 std::thread::scope(|sc| {
2939 for i in 0..cold_ref.len() {
2940 let (gi, wt) = cold_ref[i];
2941 let Some(exp) = l.experts.get(gi) else { continue };
2942 let r = &results_ref[i];
2943 sc.spawn(move || {
2944 let mut a = vec![0.0f32; cfg.dim];
2945 crate::gpu::cpu_scope(|| run_expert(x_ref, exp, cfg, wt, None, &mut a));
2946 *r.lock().unwrap() = a;
2947 });
2948 }
2949 });
2950 for r in &results {
2951 let a = r.lock().unwrap();
2952 for (o, v) in cold_sum.iter_mut().zip(a.iter()) {
2953 *o += v;
2954 }
2955 }
2956 }
2957 if !crate::gpu_wgpu::dsv4_state_add_cold_preserved(&cold_sum, cfg.hc_mult, state, kv_id, li) {
2958 return None;
2959 }
2960 {
2963 let mut dynv = pk.dynslots.lock().unwrap();
2964 dynv.clock += 1;
2965 let clock = dynv.clock;
2966 for &(gi, _) in &cold {
2967 if gi >= dynv.remap.len() || dynv.remap[gi] != u32::MAX {
2968 continue;
2969 }
2970 let victim = (0..dynv.owner.len())
2971 .filter(|&sl| dynv.last[sl] != clock)
2972 .min_by_key(|&sl| dynv.last[sl]);
2973 let Some(victim) = victim else { break };
2974 let Some(exp) = l.experts.get(gi) else { continue };
2975 let t3 = (|| Some((exp.w1.model_idx()?, exp.w3.model_idx()?, exp.w2.model_idx()?)))();
2976 let Some(t3) = t3 else { continue };
2977 let gu_q2 =
2978 exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
2979 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
2980 if !crate::gpu_wgpu::dsv4_slot_fill(
2981 &model, pack_first, victim, gi, t3, cfg.moe_inter, cfg.dim, gu_q2,
2982 ) {
2983 break;
2984 }
2985 let old = dynv.owner[victim] as usize;
2986 if old < dynv.remap.len() {
2987 dynv.remap[old] = u32::MAX;
2988 }
2989 dynv.remap[gi] = victim as u32;
2990 dynv.owner[victim] = gi as u32;
2991 dynv.last[victim] = clock;
2992 dynv.mutated = true;
2993 }
2994 }
2995 if let Some(n) = nxt {
2996 let (f, post, comb) = hc_fold_norm(
2997 state,
2998 &n.hc_attn_fn,
2999 &n.hc_attn_scale,
3000 &n.hc_attn_base,
3001 &n.attn_norm,
3002 cfg,
3003 pool,
3004 );
3005 *folded = f;
3006 if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb) {
3007 return None;
3008 }
3009 }
3010 Some(true)
3011}
3012
3013#[cfg(feature = "gpu")]
3014fn chain_max() -> usize {
3015 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
3016 *N.get_or_init(|| {
3017 std::env::var("CMF_DSV4_CHAIN_MAX")
3018 .ok()
3019 .and_then(|v| v.parse().ok())
3020 .unwrap_or(usize::MAX)
3021 })
3022}
3023
3024#[cfg(feature = "gpu")]
3027fn partial_walk_on() -> bool {
3038 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3039 *ON.get_or_init(|| std::env::var("CMF_DSV4_PARTIAL_WALK").is_ok_and(|v| v != "0"))
3040}
3041
3042fn host_cpu_moe() -> bool {
3043 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3044 *ON.get_or_init(|| std::env::var("CMF_DSV4_HOST_CPU_MOE").is_ok_and(|v| v != "0"))
3045}
3046
3047fn chain_enabled() -> bool {
3048 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3049 *ON.get_or_init(|| {
3050 std::env::var("CMF_DSV4_CHAIN")
3051 .map(|v| v != "0")
3052 .unwrap_or(true)
3053 })
3054}
3055
3056#[cfg(feature = "gpu")]
3066#[allow(clippy::too_many_arguments)]
3067fn dsv4_chain_run(
3068 layers: &[Dsv4Layer],
3069 run: &[usize],
3070 cfg: &Dsv4Cfg,
3071 g: &Dsv4Globals,
3072 st: &mut Dsv4State,
3073 token_id: u32,
3074 folded: &mut Vec<f32>,
3080 state_out: Option<&mut [f32]>,
3085 batch: usize,
3088 batch_ids: &[u32],
3092 need_qn: bool,
3099 pool: Option<&crate::pool::Pool>,
3100) -> bool {
3101 if run.is_empty() {
3102 return true;
3103 }
3104 let (dim, hd) = (cfg.dim, cfg.head_dim);
3105 let first = run[0];
3106 let Some(model) = layers[first].experts.first().and_then(|e| e.w1.model_arc()) else {
3107 return false;
3108 };
3109 if batch <= 1 && need_qn {
3113 let mut qn0 = vec![0.0f32; cfg.q_lora_rank];
3114 layers[first].wq_a.matvec(folded, &mut qn0, pool);
3115 rms_weighted(&mut qn0, &layers[first].q_norm, cfg.norm_eps);
3116 if !crate::gpu_wgpu::dsv4_chain_seed(folded, &qn0) {
3117 return false;
3118 }
3119 } else if batch <= 1 && !crate::gpu_wgpu::dsv4_chain_seed_fold(folded) {
3120 return false;
3121 }
3122
3123 let mut packs = Vec::with_capacity(run.len());
3125 let mut forceds: Vec<Option<Vec<usize>>> = Vec::with_capacity(run.len());
3126 for &li in run {
3127 let Some(pk) = pack_for(&layers[li], cfg, li) else {
3128 return false;
3129 };
3130 if !pk.route_complete() || pk.is_mutated() {
3137 return false;
3138 }
3139 let forced: Option<Vec<usize>> = layers[li].tid2eid.as_ref().and_then(|tbl| {
3140 let v: Vec<usize> = if pk.needs_remap() {
3141 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
3142 } else {
3143 hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
3144 .into_iter()
3145 .map(|gi| pk.to_slot[gi])
3146 .collect()
3147 };
3148 if v.contains(&usize::MAX) {
3149 None
3150 } else {
3151 Some(v)
3152 }
3153 });
3154 if layers[li].tid2eid.is_some() && forced.is_none() {
3155 return false;
3156 }
3157 forceds.push(forced);
3158 packs.push(pk);
3159 }
3160
3161 let mut items = Vec::with_capacity(run.len());
3162 let mut freqs = Vec::with_capacity(run.len());
3163 for (i, &li) in run.iter().enumerate() {
3164 let l = &layers[li];
3165 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
3166 l.wq_a.model_idx(),
3167 l.wq_b.model_idx(),
3168 l.wo_a.model_idx(),
3169 l.wo_b.model_idx(),
3170 l.wkv.model_idx(),
3171 ) else {
3172 return false;
3173 };
3174 let comp = match &l.compressor {
3175 None => None,
3176 Some(cp) => {
3177 let (Some(a), Some(b)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
3178 return false;
3179 };
3180 Some((
3181 crate::gpu_wgpu::Dsv4CompW {
3182 wkv: a,
3183 wgate: b,
3184 norm: &cp.norm,
3185 ape: &cp.ape,
3186 },
3187 crate::gpu_wgpu::Dsv4CompGeom {
3188 width: cp.wkv.rows(),
3189 hidden: dim,
3190 ratio: cp.ratio,
3191 overlap: cp.overlap,
3192 rope_dim: cfg.rope_head_dim,
3193 eps: cfg.norm_eps,
3194 },
3195 ))
3196 }
3197 };
3198 let ix = match &l.indexer {
3199 None => None,
3200 Some(ixr) => {
3201 let cp = &ixr.compressor;
3202 let (Some(a), Some(b), Some(qb), Some(wp)) = (
3203 cp.wkv.model_idx(),
3204 cp.wgate.model_idx(),
3205 ixr.wq_b.model_idx(),
3206 ixr.weights_proj.model_idx(),
3207 ) else {
3208 return false;
3209 };
3210 let ih = ixr.weights_proj.rows();
3211 Some((
3212 crate::gpu_wgpu::Dsv4CompW {
3213 wkv: a,
3214 wgate: b,
3215 norm: &cp.norm,
3216 ape: &cp.ape,
3217 },
3218 crate::gpu_wgpu::Dsv4CompGeom {
3219 width: cp.wkv.rows(),
3220 hidden: dim,
3221 ratio: cp.ratio,
3222 overlap: cp.overlap,
3223 rope_dim: cfg.rope_head_dim,
3224 eps: cfg.norm_eps,
3225 },
3226 crate::gpu_wgpu::Dsv4IxW {
3227 wq_b: qb,
3228 weights_proj: wp,
3229 },
3230 crate::gpu_wgpu::Dsv4IxGeom {
3231 ih,
3232 idim: ixr.wq_b.rows() / ih.max(1),
3233 q_lora: cfg.q_lora_rank,
3234 hidden: dim,
3235 rope_dim: cfg.rope_head_dim,
3236 eps: cfg.norm_eps,
3237 top_k: cfg.index_topk,
3238 window: cfg.window,
3239 },
3240 ))
3241 }
3242 };
3243 let ew_c0 = l.compressor.as_ref().map_or(0, |cp| {
3247 if cp.overlap {
3248 cp.wkv.rows() / 2
3249 } else {
3250 cp.wkv.rows()
3251 }
3252 });
3253 let comp_extra = l
3254 .compressor
3255 .as_ref()
3256 .map_or(0, |cp| batch.max(1).div_ceil(cp.ratio.max(1)));
3257 let need = cfg.window * hd
3258 + (st.dev_n_comp[li] + comp_extra + 1) * ew_c0.max(1)
3259 + (batch.max(1) + 1) * hd;
3260 if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
3261 return false;
3262 }
3263 let ew_c = comp.as_ref().map_or(
3264 0,
3265 |(_, cg)| {
3266 if cg.overlap { cg.width / 2 } else { cg.width }
3267 },
3268 );
3269 let ew_i = ix.as_ref().map_or(
3270 0,
3271 |(_, cg, _, _)| {
3272 if cg.overlap { cg.width / 2 } else { cg.width }
3273 },
3274 );
3275 let prep = crate::gpu_wgpu::Dsv4Prep {
3276 wkv,
3277 kv_norm: &l.kv_norm,
3278 comp,
3279 ix,
3280 filled: st.dev_filled[li],
3281 window: cfg.window,
3282 n_comp: st.dev_n_comp[li],
3283 n_ix: st.dev_n_ix[li],
3284 comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
3285 ix_dst_off: st.dev_n_ix[li] * ew_i,
3286 idx_cap: cfg.window
3287 + if l.indexer.is_some() {
3288 cfg.index_topk
3289 } else {
3290 st.dev_n_comp[li] + comp_extra + 1
3291 },
3292 };
3293 let nxt = layers.get(li + 1);
3294 let w = crate::gpu_wgpu::Dsv4LayerW {
3295 attn: crate::gpu_wgpu::Dsv4AttnW {
3296 wq_a,
3297 wq_b,
3298 wo_a,
3299 wo_b,
3300 q_norm: &l.q_norm,
3301 sink: &l.attn_sink,
3302 },
3303 moe: crate::gpu_wgpu::Dsv4MoeW {
3304 router: &packs[i].router,
3305 experts: &packs[i].tensors,
3306 logits: &[],
3307 bias: packs[i].bias.as_deref(),
3310 mask: packs[i].mask.as_deref(),
3311 forced: forceds[i].as_deref(),
3312 remap: packs[i].needs_remap().then_some(packs[i].remap.as_slice()),
3313 },
3314 hc_ffn_fn: &l.hc_ffn_fn,
3315 hc_ffn_scale: &l.hc_ffn_scale,
3316 hc_ffn_base: &l.hc_ffn_base,
3317 hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
3318 hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
3319 hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
3320 ffn_norm: &l.ffn_norm,
3321 next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
3322 next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
3323 next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
3324 router: &packs[i].router,
3325 };
3326 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
3327 attn: crate::gpu_wgpu::Dsv4AttnGeom {
3328 dim,
3329 nh: cfg.n_heads,
3330 hd,
3331 rd: cfg.rope_head_dim,
3332 q_lora: cfg.q_lora_rank,
3333 o_lora: cfg.o_lora_rank,
3334 o_groups: cfg.o_groups,
3335 eps: cfg.norm_eps,
3336 scale: (hd as f32).powf(-0.5),
3337 },
3338 moe: crate::gpu_wgpu::Dsv4MoeGeom {
3339 hidden: dim,
3340 inter: cfg.moe_inter,
3341 top_k: cfg.top_k,
3342 route_scale: cfg.route_scale,
3343 swiglu_limit: cfg.swiglu_limit,
3344 gu_q2: l.experts.first().is_some_and(|e| {
3345 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
3346 }),
3347 },
3348 hc: cfg.hc_mult,
3349 hc_eps: cfg.hc_eps,
3350 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3351 };
3352 freqs.push(if l.compressor.is_some() {
3353 g.inv_freq_compress.as_slice()
3354 } else {
3355 g.inv_freq_window.as_slice()
3356 });
3357 items.push((w, geom, prep));
3358 }
3359
3360 let mut out = vec![0.0f32; dim * batch.max(1)];
3361 if batch > 1 {
3362 let mut forced_pt: Vec<Vec<Option<Vec<usize>>>> = Vec::with_capacity(batch);
3367 for t in 0..batch {
3368 let id = batch_ids.get(t).copied().unwrap_or(token_id);
3369 let mut row = Vec::with_capacity(run.len());
3370 for (i, &li) in run.iter().enumerate() {
3371 row.push(layers[li].tid2eid.as_ref().and_then(|tbl| {
3372 let v: Vec<usize> = if packs[i].needs_remap() {
3373 hash_route(tbl, cfg.vocab, cfg.top_k, id)
3374 } else {
3375 hash_route(tbl, cfg.vocab, cfg.top_k, id)
3376 .into_iter()
3377 .map(|gi| packs[i].to_slot[gi])
3378 .collect()
3379 };
3380 if v.contains(&usize::MAX) {
3381 None
3382 } else {
3383 Some(v)
3384 }
3385 }));
3386 if layers[li].tid2eid.is_some() && row[i].is_none() {
3387 return false;
3388 }
3389 }
3390 forced_pt.push(row);
3391 }
3392 if !crate::gpu_wgpu::dsv4_chain_batch(
3393 &model,
3394 &items,
3395 st.kv_id,
3396 first,
3397 &freqs,
3398 st.pos,
3399 batch,
3400 Some(&forced_pt),
3401 &mut out,
3402 state_out,
3403 ) {
3404 return false;
3405 }
3406 *folded = out[(batch - 1) * dim..batch * dim].to_vec();
3409 } else {
3410 if !crate::gpu_wgpu::dsv4_layer_chain(
3411 &model, &items, st.kv_id, first, &freqs, st.pos, &mut out, state_out,
3412 ) {
3413 return false;
3414 }
3415 *folded = out;
3416 }
3417 for (i, &li) in run.iter().enumerate() {
3421 for t in 0..batch.max(1) {
3422 let pos = st.pos + t;
3423 st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
3424 if let Some((_, cg, ..)) = items[i].2.ix.as_ref() {
3425 if (pos + 1) % cg.ratio == 0 {
3426 st.dev_n_ix[li] += 1;
3427 }
3428 }
3429 if let Some((_, cg)) = items[i].2.comp.as_ref() {
3430 if (pos + 1) % cg.ratio == 0 {
3431 st.dev_n_comp[li] += 1;
3432 }
3433 }
3434 }
3435 }
3436 st.dev_owned = true;
3437 true
3438}
3439
3440#[cfg(feature = "gpu")]
3442fn hc_on_device() -> bool {
3443 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3444 *ON.get_or_init(|| {
3445 std::env::var("CMF_DSV4_HC_DEV").is_ok_and(|v| v != "0") && crate::gpu::backend_available()
3451 })
3452}
3453
3454#[cfg(feature = "gpu")]
3463#[allow(clippy::too_many_arguments)]
3464fn dsv4_two_frame_loop(
3465 state: &mut [f32],
3466 layers: &[Dsv4Layer],
3467 g: &Dsv4Globals,
3468 cfg: &Dsv4Cfg,
3469 st: &mut Dsv4State,
3470 token_id: u32,
3471 inv_freq: &[f32],
3472 pool: Option<&crate::pool::Pool>,
3473 scratch: &mut HcScratch,
3474) -> bool {
3475 let dim = cfg.dim;
3476 let freqs_of = |l: &Dsv4Layer| -> &[f32] {
3477 let f = if l.compressor.is_some() {
3478 &g.inv_freq_compress
3479 } else {
3480 &g.inv_freq_window
3481 };
3482 if f.is_empty() { inv_freq } else { f.as_slice() }
3483 };
3484 let (mut folded, post0, comb0) = hc_fold_norm(
3486 state,
3487 &layers[0].hc_attn_fn,
3488 &layers[0].hc_attn_scale,
3489 &layers[0].hc_attn_base,
3490 &layers[0].attn_norm,
3491 cfg,
3492 pool,
3493 );
3494 if !crate::gpu_wgpu::dsv4_state_write(state) || !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
3495 {
3496 return false;
3497 }
3498 let mut on_dev = vec![false; layers.len()];
3503 for (li, l) in layers.iter().enumerate() {
3504 let Some(pk) = pack_for(l, cfg, li) else {
3505 return false;
3506 };
3507 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
3508 return false;
3509 };
3510 let gu_q2 = l
3511 .experts
3512 .first()
3513 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
3514 let attn_ok = [
3515 l.wq_a.model_idx(),
3516 l.wq_b.model_idx(),
3517 l.wo_a.model_idx(),
3518 l.wo_b.model_idx(),
3519 ]
3520 .into_iter()
3521 .flatten()
3522 .all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
3523 on_dev[li] = attn_ok
3524 && pk.route_complete()
3525 && crate::gpu_wgpu::dsv4_experts_ready(
3526 &model,
3527 &pk.tensors,
3528 cfg.moe_inter,
3529 dim,
3530 gu_q2,
3531 l.experts.first().is_some_and(|e| {
3532 e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
3533 }),
3534 );
3535 }
3536 if !on_dev.iter().any(|&x| x) {
3537 return false;
3538 }
3539 let mut sink = vec![0.0f32; dim];
3540 for (li, l) in layers.iter().enumerate() {
3541 if !on_dev[li] {
3545 if !crate::gpu_wgpu::dsv4_state_read(state) {
3546 return false;
3547 }
3548 let freqs = freqs_of(l);
3549 hc_block(
3550 state,
3551 &l.hc_attn_fn,
3552 &l.hc_attn_scale,
3553 &l.hc_attn_base,
3554 &l.attn_norm,
3555 cfg,
3556 scratch,
3557 pool,
3558 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
3559 );
3560 hc_block(
3561 state,
3562 &l.hc_ffn_fn,
3563 &l.hc_ffn_scale,
3564 &l.hc_ffn_base,
3565 &l.ffn_norm,
3566 cfg,
3567 scratch,
3568 pool,
3569 |f, o| moe_step(f, l, cfg, token_id, li, pool, o),
3570 );
3571 let nref = layers.get(li + 1).unwrap_or(l);
3572 let (f, p2, c2) = hc_fold_norm(
3573 state,
3574 &nref.hc_attn_fn,
3575 &nref.hc_attn_scale,
3576 &nref.hc_attn_base,
3577 &nref.attn_norm,
3578 cfg,
3579 pool,
3580 );
3581 folded = f;
3582 if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2)
3583 || !crate::gpu_wgpu::dsv4_state_write(state)
3584 {
3585 return false;
3586 }
3587 continue;
3588 }
3589 let mut prep = AttnPrep::default();
3591 attention_step(
3592 &folded,
3593 l,
3594 cfg,
3595 st,
3596 li,
3597 freqs_of(l),
3598 pool,
3599 Some(&mut prep),
3600 &mut sink,
3601 );
3602 let hd = cfg.head_dim;
3603 let n_comp = st.compressed[li].len() / hd;
3604 let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
3605 if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
3606 || (n_comp > 0
3607 && !crate::gpu_wgpu::dsv4_cache_write(
3608 st.kv_id,
3609 li,
3610 cfg.window * hd,
3611 &st.compressed[li],
3612 cap,
3613 ))
3614 {
3615 return false;
3616 }
3617 let _idx32: Vec<u32> = prep
3618 .idxs
3619 .iter()
3620 .map(|&p| {
3621 if p < prep.win_len {
3622 p as u32
3623 } else {
3624 (cfg.window + (p - prep.win_len)) as u32
3625 }
3626 })
3627 .collect();
3628 let nxt = layers.get(li + 1);
3629 let a_tail = crate::gpu_wgpu::Dsv4HcTail {
3630 fn_: &l.hc_ffn_fn,
3631 scale: &l.hc_ffn_scale,
3632 base: &l.hc_ffn_base,
3633 norm: &l.ffn_norm,
3634 hc: cfg.hc_mult,
3635 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3636 hc_eps: cfg.hc_eps,
3637 eps: cfg.norm_eps,
3638 };
3639 let scale = (cfg.head_dim as f32).powf(-0.5);
3640 if !attn_frame(
3641 l,
3642 cfg,
3643 st,
3644 li,
3645 &folded,
3646 &prep.qr,
3647 &prep.idxs,
3648 freqs_of(l),
3649 st.pos,
3650 prep.win_len,
3651 scale,
3652 Some(&a_tail),
3653 &mut [],
3654 ) {
3655 return false;
3656 }
3657 let m_tail = nxt.map(|n| crate::gpu_wgpu::Dsv4HcTail {
3658 fn_: &n.hc_attn_fn,
3659 scale: &n.hc_attn_scale,
3660 base: &n.hc_attn_base,
3661 norm: &n.attn_norm,
3662 hc: cfg.hc_mult,
3663 sinkhorn_iters: cfg.hc_sinkhorn_iters,
3664 hc_eps: cfg.hc_eps,
3665 eps: cfg.norm_eps,
3666 });
3667 let mut next = vec![0.0f32; dim];
3668 let pair = m_tail
3669 .as_ref()
3670 .zip(nxt)
3671 .map(|(t, n)| (t, n.attn_norm.as_slice()));
3672 let forced = l
3673 .tid2eid
3674 .as_ref()
3675 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
3676 if moe_frame(
3677 &[],
3678 l,
3679 cfg,
3680 li,
3681 &[],
3682 forced.as_deref(),
3683 pool,
3684 Some(&a_tail),
3685 pair,
3686 &mut next,
3687 )
3688 .is_none()
3689 {
3690 return false;
3691 }
3692 folded = next;
3693 }
3694 let _ = scratch;
3695 crate::gpu_wgpu::dsv4_state_read(state)
3696}
3697
3698#[cfg(feature = "gpu")]
3702#[allow(clippy::too_many_arguments)]
3703fn hc_fold_norm(
3704 state: &[f32],
3705 hc_fn: &[f32],
3706 hc_scale: &[f32; 3],
3707 hc_base: &[f32],
3708 norm_w: &[f32],
3709 cfg: &Dsv4Cfg,
3710 pool: Option<&crate::pool::Pool>,
3711) -> (Vec<f32>, Vec<f32>, Vec<f32>) {
3712 let (hc, dim) = (cfg.hc_mult, cfg.dim);
3713 let mix_hc = (2 + hc) * hc;
3714 let mut mixes = vec![0.0f32; mix_hc];
3715 hc_mixes(state, hc_fn, mix_hc, cfg.norm_eps, pool, &mut mixes);
3716 let mut pre = vec![0.0f32; hc];
3717 let mut post = vec![0.0f32; hc];
3718 let mut comb = vec![0.0f32; hc * hc];
3719 hc_split_sinkhorn(
3720 &mixes,
3721 hc_scale,
3722 hc_base,
3723 hc,
3724 cfg.hc_sinkhorn_iters,
3725 cfg.hc_eps,
3726 &mut pre,
3727 &mut post,
3728 &mut comb,
3729 );
3730 let mut folded = vec![0.0f32; dim];
3731 hc_fold(state, &pre, hc, dim, &mut folded);
3732 rms_weighted(&mut folded, norm_w, cfg.norm_eps);
3733 (folded, post, comb)
3736}
3737
3738#[cfg(feature = "gpu")]
3755fn gpu_layer_enabled() -> bool {
3756 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3757 *ON.get_or_init(|| {
3758 std::env::var("CMF_DSV4_GPU_LAYER")
3759 .map(|v| v != "0")
3760 .unwrap_or(true)
3761 && crate::gpu::backend_available()
3762 })
3763}
3764
3765#[cfg(feature = "gpu")]
3769struct PackDyn {
3770 remap: Vec<u32>,
3771 owner: Vec<u32>,
3772 last: Vec<u64>,
3773 clock: u64,
3774 seen: Vec<u16>,
3781 mutated: bool,
3785}
3786
3787#[cfg(feature = "gpu")]
3788impl Pack {
3789 fn is_mutated(&self) -> bool {
3791 self.dynslots.lock().unwrap().mutated
3792 }
3793
3794 fn route_complete(&self) -> bool {
3800 let need = self
3801 .mask
3802 .as_deref()
3803 .map_or(self.remap.len(), |m| m.iter().filter(|&&x| x != 0).count());
3804 self.globals.len() >= need
3805 }
3806
3807 fn needs_remap(&self) -> bool {
3811 self.mask.is_some()
3812 || self
3813 .remap
3814 .iter()
3815 .enumerate()
3816 .any(|(i, &slot)| slot != i as u32)
3817 }
3818}
3819
3820#[cfg(feature = "gpu")]
3824struct Pack {
3825 router: Vec<f32>,
3829 to_slot: Vec<usize>,
3831 remap: Vec<u32>,
3833 globals: Vec<usize>,
3835 tensors: Vec<(usize, usize, usize)>,
3836 mask: Option<Vec<u32>>,
3840 dynslots: std::sync::Mutex<PackDyn>,
3849 bias: Option<Vec<f32>>,
3858}
3859
3860#[cfg(feature = "gpu")]
3861fn pack_freq_order(li: usize, n: usize) -> Option<Vec<usize>> {
3867 use std::collections::HashMap;
3868 use std::sync::OnceLock;
3869 static FREQ: OnceLock<Option<HashMap<(usize, usize), u64>>> = OnceLock::new();
3870 let map = FREQ
3871 .get_or_init(|| {
3872 let path = std::env::var("CMF_DSV4_PACK_FREQ").ok()?;
3873 let text = match std::fs::read_to_string(&path) {
3874 Ok(t) => t,
3875 Err(e) => {
3876 eprintln!("CMF_DSV4_PACK_FREQ={path} не читается ({e}) — порядок по id");
3877 return None;
3878 }
3879 };
3880 let mut m = HashMap::new();
3881 for line in text.lines() {
3882 let mut it = line.split('\t');
3883 if let (Some(l), Some(e), Some(c)) = (it.next(), it.next(), it.next()) {
3884 if let (Ok(l), Ok(e), Ok(c)) =
3885 (l.trim().parse(), e.trim().parse(), c.trim().parse::<u64>())
3886 {
3887 *m.entry((l, e)).or_insert(0) += c;
3888 }
3889 }
3890 }
3891 Some(m)
3892 })
3893 .as_ref()?;
3894 if !(0..n).any(|e| map.contains_key(&(li, e))) {
3895 return None;
3896 }
3897 let mut idx: Vec<usize> = (0..n).collect();
3898 idx.sort_by_key(|&e| {
3899 (
3900 std::cmp::Reverse(map.get(&(li, e)).copied().unwrap_or(0)),
3901 e,
3902 )
3903 });
3904 Some(idx)
3905}
3906
3907#[cfg(feature = "gpu")]
3908fn pack_for(l: &Dsv4Layer, cfg: &Dsv4Cfg, li: usize) -> Option<std::sync::Arc<Pack>> {
3909 use std::collections::HashMap;
3910 use std::sync::{Arc, Mutex, OnceLock};
3911 static CACHE: OnceLock<Mutex<HashMap<(u64, usize, usize), Option<Arc<Pack>>>>> =
3912 OnceLock::new();
3913 let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
3914 let model_uid = l
3920 .experts
3921 .first()
3922 .and_then(|e| e.w1.model_arc())
3923 .map_or(0, |m| m.uid());
3924 let first_expert = l
3930 .experts
3931 .first()
3932 .and_then(|e| e.w1.model_idx())
3933 .unwrap_or(usize::MAX);
3934 let key = (model_uid, li, first_expert);
3935 if let Some(v) = cache.lock().unwrap().get(&key) {
3936 return v.clone();
3937 }
3938 if let Ok(v) = std::env::var("CMF_DSV4_PACK_MAX_LI") {
3945 if v.parse::<usize>().is_ok_and(|max| li > max) {
3946 cache.lock().unwrap().insert(key, None);
3947 return None;
3948 }
3949 }
3950 let build = || -> Option<Arc<Pack>> {
3951 let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
3952 let mut globals = Vec::new();
3953 let mut tensors = Vec::new();
3954 let idx3 = |e: &Dsv4Expert| -> Option<(usize, usize, usize)> {
3955 Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
3956 };
3957 let route_mask: Option<Vec<u32>> = if l.tid2eid.is_none() {
3958 l.mask
3959 .as_deref()
3960 .map(|m| m.iter().map(|&open| u32::from(open)).collect())
3961 } else {
3962 None
3963 };
3964 let gu_q2 = l
3969 .experts
3970 .first()
3971 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
3972 if let Some(n) = std::env::var("CMF_DSV4_PACK_MAX")
3981 .ok()
3982 .and_then(|v| v.parse::<usize>().ok())
3983 {
3984 let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
3985 let mut globals = Vec::new();
3986 let mut tensors = Vec::new();
3987 for (gi, e) in l.experts.iter().enumerate() {
3988 if route_mask
3989 .as_deref()
3990 .is_some_and(|m| m.get(gi).copied().unwrap_or(1) == 0)
3991 {
3992 continue;
3993 }
3994 if globals.len() >= n {
3995 break;
3996 }
3997 to_slot[gi] = globals.len();
3998 globals.push(gi);
3999 tensors.push(idx3(e)?);
4000 }
4001 tensors.push(idx3(&l.shared)?);
4002 let (rows, cols) = (l.gate.rows(), l.gate.cols());
4003 let mut router = vec![0.0f32; rows * cols];
4004 for r in 0..rows {
4005 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4006 }
4007 let remap: Vec<u32> = to_slot
4008 .iter()
4009 .map(|&sl| {
4010 if sl == usize::MAX {
4011 u32::MAX
4012 } else {
4013 sl as u32
4014 }
4015 })
4016 .collect();
4017 return Some(Arc::new(Pack {
4018 bias: l.gate_bias.clone(),
4019 mask: route_mask.clone(),
4020 router,
4021 to_slot,
4022 dynslots: std::sync::Mutex::new(PackDyn {
4023 remap: remap.clone(),
4024 owner: globals.iter().map(|&g| g as u32).collect(),
4025 last: vec![0; globals.len()],
4026 clock: 0,
4027 mutated: false,
4028 seen: vec![0; cfg.n_routed_experts],
4029 }),
4030 remap,
4031 globals,
4032 tensors,
4033 }));
4034 }
4035 let dn_q2_fit = l
4036 .experts
4037 .first()
4038 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4039 let room = crate::gpu_wgpu::dsv4_experts_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2_fit)
4040 .saturating_sub(1);
4041 let remaining_layers = l
4052 .experts
4053 .first()
4054 .and_then(|e| e.w1.model_arc())
4055 .map(|m| m.header.arch.num_layers.saturating_sub(li).max(1))
4056 .unwrap_or(1);
4057 let auto_cap = if l.mask.is_some() {
4058 room
4063 } else if l.tid2eid.is_some() && room >= cfg.n_routed_experts {
4064 cfg.n_routed_experts
4065 } else {
4066 room.div_ceil(remaining_layers).max(1)
4067 };
4068 let room = match std::env::var("CMF_DSV4_PACK_LAYER_CAP")
4069 .ok()
4070 .and_then(|v| v.parse::<usize>().ok())
4071 {
4072 Some(0) => room,
4073 Some(cap) => room.min(cap),
4074 None => room.min(auto_cap),
4075 };
4076 let order =
4085 pack_freq_order(li, l.experts.len()).unwrap_or_else(|| (0..l.experts.len()).collect());
4086 for gi in order {
4087 let e = &l.experts[gi];
4088 if l.mask
4089 .as_deref()
4090 .is_some_and(|m| !m.get(gi).copied().unwrap_or(true))
4091 {
4092 continue;
4093 }
4094 if globals.len() >= room {
4095 break;
4096 }
4097 to_slot[gi] = globals.len();
4098 globals.push(gi);
4099 match idx3(e) {
4100 Some(t) => tensors.push(t),
4101 None => {
4102 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
4103 eprintln!("слой {li}: эксперт {gi} без индексов в каталоге");
4104 }
4105 return None;
4106 }
4107 }
4108 }
4109 if globals.is_empty() {
4110 if room == 0 {
4116 static SAID_ZERO: std::sync::atomic::AtomicBool =
4117 std::sync::atomic::AtomicBool::new(false);
4118 if !SAID_ZERO.swap(true, std::sync::atomic::Ordering::Relaxed) {
4119 tracing::warn!(
4120 "начиная со слоя {li}, в бюджете VRAM не осталось места даже под одного \
4121 эксперта — остальные веса остаются mmap-backed и читаются по требованию"
4122 );
4123 }
4124 } else {
4125 tracing::warn!("слой {li}: маска не оставила ни одного эксперта");
4126 }
4127 return None;
4128 }
4129 tensors.push(idx3(&l.shared)?); let (rows, cols) = (l.gate.rows(), l.gate.cols());
4131 let mut router = vec![0.0f32; rows * cols];
4132 for r in 0..rows {
4133 l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4134 }
4135 let remap: Vec<u32> = to_slot
4136 .iter()
4137 .map(|&sl| {
4138 if sl == usize::MAX {
4139 u32::MAX
4140 } else {
4141 sl as u32
4142 }
4143 })
4144 .collect();
4145 Some(Arc::new(Pack {
4146 bias: l.gate_bias.clone(),
4147 mask: route_mask,
4148 router,
4149 to_slot,
4150 dynslots: std::sync::Mutex::new(PackDyn {
4151 remap: remap.clone(),
4152 owner: globals.iter().map(|&g| g as u32).collect(),
4153 last: vec![0; globals.len()],
4154 clock: 0,
4155 mutated: false,
4156 seen: vec![0; cfg.n_routed_experts],
4157 }),
4158 remap,
4159 globals,
4160 tensors,
4161 }))
4162 };
4163 let v = build();
4164 cache.lock().unwrap().insert(key, v.clone());
4165 v
4166}
4167
4168#[cfg(feature = "gpu")]
4176fn moe_frame(
4177 hidden: &[f32],
4178 l: &Dsv4Layer,
4179 cfg: &Dsv4Cfg,
4180 li: usize,
4181 logits: &[f32],
4182 forced: Option<&[usize]>,
4183 pool: Option<&crate::pool::Pool>,
4184 hc_cur: Option<&crate::gpu_wgpu::Dsv4HcTail>,
4187 hc_next: Option<(&crate::gpu_wgpu::Dsv4HcTail, &[f32])>,
4188 out: &mut [f32],
4189) -> Option<(Vec<f32>, usize)> {
4190 macro_rules! no {
4191 ($($t:tt)*) => {{
4192 if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
4193 eprintln!("кадр MoE отклонён: {}", format_args!($($t)*));
4194 }
4195 return None;
4196 }};
4197 }
4198 let Some(pk) = pack_for(l, cfg, li) else {
4199 no!("слой {li}: упаковка экспертов не построена");
4200 };
4201 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
4204 no!("слой {li}: эксперты не отображены из файла");
4205 };
4206 let subset = !pk.route_complete();
4207 let needs_remap = pk.needs_remap();
4208 fn fetch_quota() -> usize {
4215 static Q: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
4216 *Q.get_or_init(|| {
4217 std::env::var("CMF_DSV4_FETCH_MAX")
4218 .ok()
4219 .and_then(|v| v.parse().ok())
4220 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().0)
4221 })
4222 }
4223 let mut dynv = pk.dynslots.lock().unwrap();
4224 let mut pidx = Vec::new();
4229 let mut pwt = Vec::new();
4230 let mut predicted_logits = Vec::new();
4236 let prediction_logits: &[f32] = if subset && logits.is_empty() && !hidden.is_empty() {
4237 predicted_logits.resize(cfg.n_routed_experts, 0.0);
4238 l.gate.matvec(hidden, &mut predicted_logits, pool);
4239 &predicted_logits
4240 } else {
4241 logits
4242 };
4243 if subset && !prediction_logits.is_empty() {
4244 route(
4245 prediction_logits,
4246 l.gate_bias.as_deref(),
4247 cfg.top_k,
4248 cfg.route_scale,
4249 forced,
4250 l.mask.as_deref(),
4251 &mut pidx,
4252 &mut pwt,
4253 );
4254 }
4255 if subset && fetch_quota() > 0 && !pidx.is_empty() && !dynv.owner.is_empty() {
4256 dynv.clock += 1;
4257 let clock = dynv.clock;
4258 if clock % 64 == 0 {
4259 for v in dynv.seen.iter_mut() {
4260 *v >>= 1;
4261 }
4262 }
4263 for &pick in &pidx {
4264 dynv.seen[pick] = dynv.seen[pick].saturating_add(1);
4265 let sl = dynv.remap[pick];
4266 if sl != u32::MAX {
4267 dynv.last[sl as usize] = clock;
4268 }
4269 }
4270 fn fetch_min_seen() -> u16 {
4271 static M: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
4272 *M.get_or_init(|| {
4273 std::env::var("CMF_DSV4_FETCH_MIN_SEEN")
4274 .ok()
4275 .and_then(|v| v.parse().ok())
4276 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().1)
4277 })
4278 }
4279 let mut fetched = 0usize;
4280 for &pick in &pidx {
4281 if fetched >= fetch_quota() {
4282 break;
4283 }
4284 if dynv.remap[pick] != u32::MAX {
4285 continue;
4286 }
4287 if dynv.seen[pick] < fetch_min_seen() {
4288 continue; }
4290 let victim = (0..dynv.owner.len())
4292 .filter(|&sl| dynv.last[sl] != clock)
4293 .min_by_key(|&sl| dynv.last[sl]);
4294 let Some(victim) = victim else { break };
4295 let Some(exp) = l.experts.get(pick) else { continue };
4296 let t3 = (|| {
4297 Some((
4298 exp.w1.model_idx()?,
4299 exp.w3.model_idx()?,
4300 exp.w2.model_idx()?,
4301 ))
4302 })();
4303 let Some(t3) = t3 else { continue };
4304 let gu_q2 =
4305 exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
4306 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
4307 if !crate::gpu_wgpu::dsv4_slot_fill(
4308 &model,
4309 pack_first,
4310 victim,
4311 pick,
4312 t3,
4313 cfg.moe_inter,
4314 cfg.dim,
4315 gu_q2,
4316 ) {
4317 break;
4318 }
4319 let old = dynv.owner[victim] as usize;
4320 if old < dynv.remap.len() {
4321 dynv.remap[old] = u32::MAX;
4322 }
4323 dynv.remap[pick] = victim as u32;
4324 dynv.owner[victim] = pick as u32;
4325 dynv.last[victim] = clock;
4326 dynv.mutated = true;
4327 fetched += 1;
4328 }
4329 }
4330 let fpack: Option<Vec<usize>> = match forced {
4334 Some(f) if needs_remap => Some(f.to_vec()),
4335 Some(f) => {
4336 let v: Vec<usize> = f.iter().map(|&g| pk.to_slot[g]).collect();
4337 if v.contains(&usize::MAX) {
4338 no!("слой {li}: хеш-слой называет эксперта вне упаковки");
4339 }
4340 Some(v)
4341 }
4342 None => None,
4343 };
4344 let lg: Vec<f32> = if logits.is_empty() || needs_remap {
4351 logits.to_vec()
4352 } else {
4353 pk.globals.iter().map(|&g| logits[g]).collect()
4354 };
4355 let w = crate::gpu_wgpu::Dsv4MoeW {
4356 router: &pk.router,
4357 experts: &pk.tensors,
4358 logits: &lg,
4359 bias: pk.bias.as_deref(),
4360 mask: pk.mask.as_deref(),
4361 forced: fpack.as_deref(),
4362 remap: needs_remap.then_some(dynv.remap.as_slice()),
4363 };
4364 let g = crate::gpu_wgpu::Dsv4MoeGeom {
4365 hidden: cfg.dim,
4366 inter: cfg.moe_inter,
4367 top_k: cfg.top_k,
4368 route_scale: cfg.route_scale,
4369 swiglu_limit: cfg.swiglu_limit,
4370 gu_q2: l
4371 .experts
4372 .first()
4373 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
4374 };
4375 let mut cold = Vec::new();
4376 let mut cold_x = Vec::new();
4377 let overlap: Vec<usize> = if !hidden.is_empty() {
4386 pidx.iter()
4387 .copied()
4388 .filter(|&pick| dynv.remap.get(pick).copied().unwrap_or(u32::MAX) == u32::MAX)
4389 .collect()
4390 } else {
4391 Vec::new()
4392 };
4393 let mut early: std::collections::HashMap<usize, Vec<f32>> = std::collections::HashMap::new();
4394 let frame_ok = std::thread::scope(|sc| {
4395 let handles: Vec<_> = overlap
4396 .iter()
4397 .filter_map(|&gi| l.experts.get(gi).map(|exp| (gi, exp)))
4398 .map(|(gi, exp)| {
4399 sc.spawn(move || {
4400 let mut a = vec![0.0f32; cfg.dim];
4401 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, 1.0, None, &mut a));
4402 (gi, a)
4403 })
4404 })
4405 .collect();
4406 let ok = crate::gpu_wgpu::dsv4_moe_frame(
4407 &model,
4408 &w,
4409 g,
4410 hidden,
4411 &mut cold,
4412 &mut cold_x,
4413 hc_cur,
4414 hc_next,
4415 out,
4416 );
4417 for h in handles {
4418 if let Ok((gi, a)) = h.join() {
4419 early.insert(gi, a);
4420 }
4421 }
4422 ok
4423 });
4424 if !frame_ok {
4425 return None;
4426 }
4427 if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
4430 let csum: f32 = cold.iter().map(|c| c.1).sum();
4431 eprintln!(
4432 "[холодные] слой {li}: вернулось {} из {} | сумма холодных {csum:.4} | \
4433 route_scale {:.4} | {:?}",
4434 cold.len(),
4435 cfg.top_k,
4436 cfg.route_scale,
4437 &cold[..cold.len().min(3)]
4438 );
4439 }
4440 let mut acc = vec![0.0f32; cfg.dim];
4441 let mut cold_sum = vec![0.0f32; cfg.dim];
4442 let cold_input = if hidden.is_empty() {
4443 cold_x.as_slice()
4444 } else {
4445 hidden
4446 };
4447 if !early.is_empty() {
4460 for &(gi, wt) in &cold {
4465 if let Some(a) = early.get(&gi) {
4466 for ((o, sum), v) in out.iter_mut().zip(&mut cold_sum).zip(a.iter()) {
4467 *o += v * wt;
4468 *sum += v * wt;
4469 }
4470 continue;
4471 }
4472 let Some(exp) = l.experts.get(gi) else { continue };
4473 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
4474 for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
4475 *o += a;
4476 *sum += a;
4477 }
4478 }
4479 return Some((cold_sum, cold.len()));
4480 }
4481 match pool {
4482 Some(p) if cold.len() > 1 => {
4483 let results: Vec<std::sync::Mutex<Vec<f32>>> =
4484 cold.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
4485 let (cold_ref, results_ref) = (&cold, &results);
4486 p.run_rows(cold.len(), &move |cs, ce| {
4487 for i in cs..ce {
4488 let (gi, wt) = cold_ref[i];
4489 let Some(exp) = l.experts.get(gi) else { continue };
4490 let mut a = vec![0.0f32; cfg.dim];
4491 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, None, &mut a));
4492 *results_ref[i].lock().unwrap() = a;
4493 }
4494 });
4495 for r in &results {
4498 let a = r.lock().unwrap();
4499 if a.is_empty() {
4500 continue;
4501 }
4502 for ((o, sum), v) in out.iter_mut().zip(&mut cold_sum).zip(a.iter()) {
4503 *o += v;
4504 *sum += v;
4505 }
4506 }
4507 }
4508 _ => {
4509 for &(gi, wt) in &cold {
4510 let Some(exp) = l.experts.get(gi) else {
4511 continue;
4512 };
4513 crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
4514 for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
4515 *o += a;
4516 *sum += a;
4517 }
4518 }
4519 }
4520 }
4521 Some((cold_sum, cold.len()))
4522}
4523
4524#[cfg(feature = "gpu")]
4530fn last_grew(now: u64) -> u64 {
4531 use std::sync::atomic::{AtomicU64, Ordering};
4532 static SEEN: AtomicU64 = AtomicU64::new(0);
4533 let was = SEEN.load(Ordering::Relaxed);
4534 if was != now {
4535 SEEN.store(now, Ordering::Relaxed);
4536 compressed_map().lock().unwrap().clear();
4537 return u64::MAX; }
4539 now
4540}
4541
4542#[cfg(feature = "gpu")]
4543fn compressed_map() -> &'static std::sync::Mutex<std::collections::HashMap<(u64, usize), usize>> {
4544 use std::collections::HashMap;
4545 use std::sync::{Mutex, OnceLock};
4546 static W: OnceLock<Mutex<HashMap<(u64, usize), usize>>> = OnceLock::new();
4547 W.get_or_init(|| Mutex::new(HashMap::new()))
4548}
4549
4550#[cfg(feature = "gpu")]
4551fn compressed_written(kv_id: u64, li: usize) -> usize {
4552 compressed_map()
4553 .lock()
4554 .unwrap()
4555 .get(&(kv_id, li))
4556 .copied()
4557 .unwrap_or(0)
4558}
4559
4560#[cfg(feature = "gpu")]
4563fn note_compressed(kv_id: u64, li: usize, n: usize) {
4564 compressed_map().lock().unwrap().insert((kv_id, li), n);
4565}
4566
4567#[cfg(feature = "gpu")]
4568fn gpu_moe2_enabled() -> bool {
4569 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4570 *ON.get_or_init(|| {
4571 std::env::var("CMF_DSV4_GPU_MOE2")
4572 .map(|v| v != "0")
4573 .unwrap_or(true)
4574 && crate::gpu::backend_available()
4575 })
4576}
4577
4578pub fn moe_step(
4579 hidden: &[f32],
4580 l: &Dsv4Layer,
4581 cfg: &Dsv4Cfg,
4582 token_id: u32,
4583 li: usize,
4585 pool: Option<&crate::pool::Pool>,
4586 out: &mut [f32],
4587) {
4588 let _t0 = prof::on().then(std::time::Instant::now);
4589 let _guard = scopeguard_moe(_t0, li);
4590 let mut logits = vec![0.0f32; cfg.n_routed_experts];
4591 l.gate.matvec(hidden, &mut logits, pool);
4592 let (mut idx, mut w) = (Vec::new(), Vec::new());
4593 route(
4594 &logits,
4595 l.gate_bias.as_deref(),
4596 cfg.top_k,
4597 cfg.route_scale,
4598 l.tid2eid
4599 .as_ref()
4600 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id))
4601 .as_deref(),
4602 l.mask.as_deref(),
4603 &mut idx,
4604 &mut w,
4605 );
4606 if route_stats_on() {
4607 let routed: Vec<(usize, f32)> = idx.iter().copied().zip(w.iter().copied()).collect();
4608 record_route(li, 0, cfg.n_routed_experts, &routed);
4609 }
4610 crate::pipeline::moe_trace_at(li as i32, &idx);
4616 #[cfg(feature = "gpu")]
4620 if gpu_moe2_enabled() && crate::gpu::enabled_here() {
4621 let forced = l
4622 .tid2eid
4623 .as_ref()
4624 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
4625 if moe_frame(
4626 hidden,
4627 l,
4628 cfg,
4629 li,
4630 &logits,
4631 forced.as_deref(),
4632 pool,
4633 None,
4634 None,
4635 out,
4636 )
4637 .is_some()
4638 {
4639 if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
4645 let mut want = vec![0.0f32; out.len()];
4646 let mut acc = vec![0.0f32; cfg.dim];
4647 for (e, &ei) in idx.iter().enumerate() {
4648 let Some(exp) = l.experts.get(ei) else {
4649 continue;
4650 };
4651 run_expert(
4652 hidden,
4653 exp,
4654 cfg,
4655 w.get(e).copied().unwrap_or(0.0),
4656 pool,
4657 &mut acc,
4658 );
4659 for (o, a) in want.iter_mut().zip(&acc) {
4660 *o += a;
4661 }
4662 }
4663 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
4664 for (o, a) in want.iter_mut().zip(&acc) {
4665 *o += a;
4666 }
4667 let num: f32 = want
4668 .iter()
4669 .zip(out.iter())
4670 .map(|(a, b)| (a - b) * (a - b))
4671 .sum();
4672 let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
4673 let rel = (num / den).sqrt();
4674 if rel > 1e-3 {
4675 let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
4676 eprintln!(
4677 "[кадр MoE] слой {li}: расхождение {rel:.3e} | выбрано {} | \
4678 упаковано {packed} из {} | хеш={} | смещение={}",
4679 idx.len(),
4680 cfg.n_routed_experts,
4681 l.tid2eid.is_some(),
4682 l.gate_bias.is_some()
4683 );
4684 }
4685 }
4686 return;
4687 }
4688 }
4689 PICK_TALLY.with(|t| {
4695 if let Some(v) = t.borrow_mut().as_mut() {
4696 v.push((li, idx.to_vec()));
4697 }
4698 });
4699 if dump_path().is_some() {
4700 PICKED.with(|p| {
4701 let mut p = p.borrow_mut();
4702 if p.len() <= li {
4703 p.resize(li + 1, Vec::new());
4704 }
4705 p[li] = idx.clone();
4706 });
4707 }
4708 fn gpu_moe_on() -> bool {
4727 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4728 *ON.get_or_init(|| std::env::var("CMF_DSV4_GPU_MOE").is_ok_and(|v| v != "0"))
4729 }
4730 if gpu_moe_on() && crate::gpu::enabled_here() {
4731 let mut jobs = Vec::with_capacity(idx.len() + 1);
4732 let mut model_ref = None;
4733 let mut ok = true;
4734 for (e, &ei) in idx.iter().enumerate() {
4735 let Some(exp) = l.experts.get(ei) else {
4736 continue;
4737 };
4738 ok &= crate::pipeline::moe_push_job_parts(
4739 &exp.w1,
4740 &exp.w3,
4741 &exp.w2,
4742 hidden,
4743 w.get(e).copied().unwrap_or(0.0),
4744 cfg.swiglu_limit,
4745 &mut jobs,
4746 &mut model_ref,
4747 )
4748 .is_some();
4749 }
4750 ok &= crate::pipeline::moe_push_job_parts(
4751 &l.shared.w1,
4752 &l.shared.w3,
4753 &l.shared.w2,
4754 hidden,
4755 1.0,
4756 cfg.swiglu_limit,
4757 &mut jobs,
4758 &mut model_ref,
4759 )
4760 .is_some();
4761 if ok {
4762 if let Some(m) = model_ref.as_ref() {
4763 if crate::gpu::moe_block(m, &jobs, out) {
4764 if std::env::var("CMF_DSV4_GPU_CHECK").is_ok() {
4769 let mut want = vec![0.0f32; out.len()];
4770 let mut acc = vec![0.0f32; cfg.dim];
4771 for (e, &ei) in idx.iter().enumerate() {
4772 let Some(exp) = l.experts.get(ei) else {
4773 continue;
4774 };
4775 run_expert(
4776 hidden,
4777 exp,
4778 cfg,
4779 w.get(e).copied().unwrap_or(0.0),
4780 pool,
4781 &mut acc,
4782 );
4783 for (o, a) in want.iter_mut().zip(&acc) {
4784 *o += a;
4785 }
4786 }
4787 run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
4788 for (o, a) in want.iter_mut().zip(&acc) {
4789 *o += a;
4790 }
4791 let num: f32 = want
4792 .iter()
4793 .zip(out.iter())
4794 .map(|(a, b)| (a - b) * (a - b))
4795 .sum();
4796 let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
4797 eprintln!(
4798 "[dsv4-gpu] слой {li}: расхождение {:.3e} | |CPU|={:.5} |GPU|={:.5} | экспертов {}",
4799 (num / den).sqrt(),
4800 den.sqrt(),
4801 out.iter().map(|x| x * x).sum::<f32>().sqrt(),
4802 jobs.len()
4803 );
4804 }
4805 return;
4806 }
4807 }
4808 }
4809 }
4810 out.fill(0.0);
4811 let jobs: Vec<(Option<usize>, f32)> = idx
4818 .iter()
4819 .enumerate()
4820 .map(|(e, &ei)| (Some(ei), w.get(e).copied().unwrap_or(0.0)))
4821 .chain(std::iter::once((None, 1.0)))
4822 .collect();
4823 match pool {
4824 Some(p) if jobs.len() > 1 => {
4825 let results: Vec<std::sync::Mutex<Vec<f32>>> =
4826 jobs.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
4827 let (jobs_ref, results_ref) = (&jobs, &results);
4828 p.run_rows(jobs.len(), &move |cs, ce| {
4829 for i in cs..ce {
4830 let (ei, wt) = jobs_ref[i];
4831 let exp = match ei {
4832 Some(ei) => match l.experts.get(ei) {
4833 Some(x) => x,
4834 None => continue,
4835 },
4836 None => &l.shared,
4837 };
4838 let mut a = vec![0.0f32; cfg.dim];
4839 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, wt, None, &mut a));
4840 *results_ref[i].lock().unwrap() = a;
4841 }
4842 });
4843 for r in &results {
4844 let a = r.lock().unwrap();
4845 for (o, v) in out.iter_mut().zip(a.iter()) {
4846 *o += v;
4847 }
4848 }
4849 }
4850 _ => {
4851 let mut acc = vec![0.0f32; cfg.dim];
4852 for &(ei, wt) in &jobs {
4853 let exp = match ei {
4854 Some(ei) => match l.experts.get(ei) {
4855 Some(x) => x,
4856 None => continue,
4857 },
4858 None => &l.shared,
4859 };
4860 crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, wt, pool, &mut acc));
4861 for (o, a) in out.iter_mut().zip(&acc) {
4862 *o += a;
4863 }
4864 }
4865 }
4866 }
4867}
4868
4869fn run_expert(
4872 x: &[f32],
4873 e: &Dsv4Expert,
4874 cfg: &Dsv4Cfg,
4875 weight: f32,
4876 pool: Option<&crate::pool::Pool>,
4877 out: &mut [f32],
4878) {
4879 expert_swiglu(
4880 x,
4881 &|src, dst| e.w1.matvec(src, dst, pool),
4882 &|src, dst| e.w3.matvec(src, dst, pool),
4883 &|src, dst| e.w2.matvec(src, dst, pool),
4884 cfg.moe_inter,
4885 weight,
4886 cfg.swiglu_limit,
4887 out,
4888 );
4889}
4890
4891fn moe_step_block(
4896 xs: &[f32],
4897 b: usize,
4898 l: &Dsv4Layer,
4899 cfg: &Dsv4Cfg,
4900 token_ids: &[u32],
4901 tally_layer: usize,
4902 pool: Option<&crate::pool::Pool>,
4903 out: &mut [f32],
4904) {
4905 let (dim, inter) = (cfg.dim, cfg.moe_inter);
4906 debug_assert_eq!(xs.len(), b * dim);
4907 debug_assert_eq!(out.len(), b * dim);
4908 out.fill(0.0);
4909
4910 let mut logits = vec![0.0f32; b * cfg.n_routed_experts];
4911 l.gate.matmat(xs, b, &mut logits, pool);
4912 let mut picks: Vec<Vec<usize>> = Vec::with_capacity(b);
4913 let mut weights: Vec<Vec<f32>> = Vec::with_capacity(b);
4914 for bi in 0..b {
4915 let mut idx = Vec::new();
4916 let mut wt = Vec::new();
4917 let forced = l.tid2eid.as_ref().map(|tbl| {
4918 hash_route(
4919 tbl,
4920 cfg.vocab,
4921 cfg.top_k,
4922 token_ids.get(bi).copied().unwrap_or(0),
4923 )
4924 });
4925 route(
4926 &logits[bi * cfg.n_routed_experts..(bi + 1) * cfg.n_routed_experts],
4927 l.gate_bias.as_deref(),
4928 cfg.top_k,
4929 cfg.route_scale,
4930 forced.as_deref(),
4931 l.mask.as_deref(),
4932 &mut idx,
4933 &mut wt,
4934 );
4935 PICK_TALLY.with(|t| {
4936 if let Some(v) = t.borrow_mut().as_mut() {
4937 v.push((tally_layer, idx.clone()));
4938 }
4939 });
4940 picks.push(idx);
4941 weights.push(wt);
4942 }
4943
4944 let mut routed = vec![0.0f32; b * cfg.top_k * dim];
4947 let mut active: Vec<(usize, Vec<(usize, usize, f32)>)> = Vec::new();
4950 for ei in 0..l.experts.len() {
4951 let mut jobs = Vec::new();
4952 for bi in 0..b {
4953 for (slot, &picked) in picks[bi].iter().enumerate() {
4954 if picked == ei {
4955 jobs.push((bi, slot, weights[bi][slot]));
4956 }
4957 }
4958 }
4959 if !jobs.is_empty() {
4960 active.push((ei, jobs));
4961 }
4962 }
4963 let expert_fwd = |ei: usize, jobs: &[(usize, usize, f32)], inner: Option<&crate::pool::Pool>| -> Vec<f32> {
4966 let e = &l.experts[ei];
4967 let n = jobs.len();
4968 let mut xj = vec![0.0f32; n * dim];
4969 for (j, &(bi, _, _)) in jobs.iter().enumerate() {
4970 xj[j * dim..(j + 1) * dim].copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
4971 }
4972 let mut gate = vec![0.0f32; n * inter];
4973 let mut up = vec![0.0f32; n * inter];
4974 e.w1.matmat(&xj, n, &mut gate, inner);
4975 e.w3.matmat(&xj, n, &mut up, inner);
4976 for (j, &(_, _, wt)) in jobs.iter().enumerate() {
4977 let (gj, uj) = (
4978 &mut gate[j * inter..(j + 1) * inter],
4979 &mut up[j * inter..(j + 1) * inter],
4980 );
4981 if cfg.swiglu_limit > 0.0 {
4982 for u in uj.iter_mut() {
4983 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
4984 }
4985 for g in gj.iter_mut() {
4986 *g = g.min(cfg.swiglu_limit);
4987 }
4988 }
4989 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
4990 *g = (*g / (1.0 + (-*g).exp())) * u * wt;
4991 }
4992 }
4993 let mut down = vec![0.0f32; n * dim];
4994 e.w2.matmat(&gate, n, &mut down, inner);
4995 down
4996 };
4997 match pool {
5007 Some(p) if active.len() > 1 => {
5008 let results: Vec<std::sync::Mutex<Vec<f32>>> =
5009 active.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
5010 let active_ref = &active;
5011 let results_ref = &results;
5012 let fwd = &expert_fwd;
5013 p.run_rows(active_ref.len(), &move |s, e| {
5014 for i in s..e {
5015 let (ei, jobs) = &active_ref[i];
5016 let d = crate::gpu::cpu_scope(|| fwd(*ei, jobs, None));
5017 *results_ref[i].lock().unwrap() = d;
5018 }
5019 });
5020 for (i, (_, jobs)) in active.iter().enumerate() {
5021 let down = results[i].lock().unwrap();
5022 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5023 routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
5024 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5025 }
5026 }
5027 }
5028 _ => {
5029 for (ei, jobs) in &active {
5030 let down = expert_fwd(*ei, jobs, pool);
5031 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5032 routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
5033 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5034 }
5035 }
5036 }
5037 }
5038
5039 let mut sg = vec![0.0f32; b * inter];
5042 let mut su = vec![0.0f32; b * inter];
5043 l.shared.w1.matmat(xs, b, &mut sg, pool);
5044 l.shared.w3.matmat(xs, b, &mut su, pool);
5045 for bi in 0..b {
5046 let (gj, uj) = (
5047 &mut sg[bi * inter..(bi + 1) * inter],
5048 &mut su[bi * inter..(bi + 1) * inter],
5049 );
5050 if cfg.swiglu_limit > 0.0 {
5051 for u in uj.iter_mut() {
5052 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5053 }
5054 for g in gj.iter_mut() {
5055 *g = g.min(cfg.swiglu_limit);
5056 }
5057 }
5058 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5059 *g = (*g / (1.0 + (-*g).exp())) * u;
5060 }
5061 }
5062 let mut shared = vec![0.0f32; b * dim];
5063 l.shared.w2.matmat(&sg, b, &mut shared, pool);
5064
5065 for bi in 0..b {
5066 let dst = &mut out[bi * dim..(bi + 1) * dim];
5067 for slot in 0..picks[bi].len() {
5068 let src = &routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim];
5069 for (o, &v) in dst.iter_mut().zip(src) {
5070 *o += v;
5071 }
5072 }
5073 for (o, &v) in dst.iter_mut().zip(&shared[bi * dim..(bi + 1) * dim]) {
5074 *o += v;
5075 }
5076 }
5077}
5078
5079fn cold_step_block(
5084 xs: &[f32],
5085 b: usize,
5086 l: &Dsv4Layer,
5087 cfg: &Dsv4Cfg,
5088 cold: &[Vec<(usize, f32)>],
5089 pool: Option<&crate::pool::Pool>,
5090 out: &mut [f32],
5091) {
5092 let (dim, inter) = (cfg.dim, cfg.moe_inter);
5093 debug_assert_eq!(xs.len(), b * dim);
5094 debug_assert_eq!(cold.len(), b);
5095 debug_assert_eq!(out.len(), b * dim);
5096 out.fill(0.0);
5097 let slots = cold.iter().map(Vec::len).max().unwrap_or(0);
5098 if slots == 0 {
5099 return;
5100 }
5101 let grouped = std::env::var("CMF_DSV4_COLD_MATMAT")
5107 .map(|v| v != "0")
5108 .unwrap_or(true);
5109 if !grouped {
5110 let jobs: Vec<(usize, usize, usize, f32)> = cold
5111 .iter()
5112 .enumerate()
5113 .flat_map(|(bi, row)| {
5114 row.iter()
5115 .enumerate()
5116 .map(move |(slot, &(ei, wt))| (bi, slot, ei, wt))
5117 })
5118 .collect();
5119 let results: Vec<std::sync::Mutex<Vec<f32>>> =
5120 jobs.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
5121 match pool {
5122 Some(p) if jobs.len() > 1 => {
5123 let (jobs_ref, results_ref) = (&jobs, &results);
5124 p.run_rows(jobs.len(), &move |s, e| {
5125 for i in s..e {
5126 let (_, _, ei, wt) = jobs_ref[i];
5127 let Some(exp) = l.experts.get(ei) else { continue };
5128 let bi = jobs_ref[i].0;
5129 let mut a = vec![0.0f32; dim];
5130 crate::gpu::cpu_scope(|| {
5131 run_expert(
5132 &xs[bi * dim..(bi + 1) * dim],
5133 exp,
5134 cfg,
5135 wt,
5136 None,
5137 &mut a,
5138 )
5139 });
5140 *results_ref[i].lock().unwrap() = a;
5141 }
5142 });
5143 }
5144 _ => {
5145 for (i, &(bi, _, ei, wt)) in jobs.iter().enumerate() {
5146 let Some(exp) = l.experts.get(ei) else { continue };
5147 let mut a = vec![0.0f32; dim];
5148 crate::gpu::cpu_scope(|| {
5149 run_expert(
5150 &xs[bi * dim..(bi + 1) * dim],
5151 exp,
5152 cfg,
5153 wt,
5154 pool,
5155 &mut a,
5156 )
5157 });
5158 *results[i].lock().unwrap() = a;
5159 }
5160 }
5161 }
5162 for (i, &(bi, _, _, _)) in jobs.iter().enumerate() {
5165 let a = results[i].lock().unwrap();
5166 for (o, &v) in out[bi * dim..(bi + 1) * dim].iter_mut().zip(a.iter()) {
5167 *o += v;
5168 }
5169 }
5170 return;
5171 }
5172 let mut active: Vec<(usize, Vec<(usize, usize, f32)>)> = Vec::new();
5173 for ei in 0..l.experts.len() {
5174 let mut jobs = Vec::new();
5175 for bi in 0..b {
5176 for (slot, &(picked, wt)) in cold[bi].iter().enumerate() {
5177 if picked == ei {
5178 jobs.push((bi, slot, wt));
5179 }
5180 }
5181 }
5182 if !jobs.is_empty() {
5183 active.push((ei, jobs));
5184 }
5185 }
5186 let expert_fwd = |ei: usize,
5187 jobs: &[(usize, usize, f32)],
5188 inner: Option<&crate::pool::Pool>|
5189 -> Vec<f32> {
5190 let e = &l.experts[ei];
5191 let n = jobs.len();
5192 let mut xj = vec![0.0f32; n * dim];
5193 for (j, &(bi, _, _)) in jobs.iter().enumerate() {
5194 xj[j * dim..(j + 1) * dim].copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
5195 }
5196 let mut gate = vec![0.0f32; n * inter];
5197 let mut up = vec![0.0f32; n * inter];
5198 e.w1.matmat(&xj, n, &mut gate, inner);
5199 e.w3.matmat(&xj, n, &mut up, inner);
5200 for (j, &(_, _, wt)) in jobs.iter().enumerate() {
5201 let (gj, uj) = (
5202 &mut gate[j * inter..(j + 1) * inter],
5203 &mut up[j * inter..(j + 1) * inter],
5204 );
5205 if cfg.swiglu_limit > 0.0 {
5206 for u in uj.iter_mut() {
5207 *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5208 }
5209 for g in gj.iter_mut() {
5210 *g = g.min(cfg.swiglu_limit);
5211 }
5212 }
5213 for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5214 *g = (*g / (1.0 + (-*g).exp())) * u * wt;
5215 }
5216 }
5217 let mut down = vec![0.0f32; n * dim];
5218 e.w2.matmat(&gate, n, &mut down, inner);
5219 down
5220 };
5221 let mut routed = vec![0.0f32; b * slots * dim];
5222 match pool {
5223 Some(p) if active.len() > 1 => {
5224 let results: Vec<std::sync::Mutex<Vec<f32>>> =
5225 active.iter().map(|_| std::sync::Mutex::new(Vec::new())).collect();
5226 let active_ref = &active;
5227 let results_ref = &results;
5228 let fwd = &expert_fwd;
5229 p.run_rows(active.len(), &move |s, e| {
5230 for i in s..e {
5231 let (ei, jobs) = &active_ref[i];
5232 let d = crate::gpu::cpu_scope(|| fwd(*ei, jobs, None));
5233 *results_ref[i].lock().unwrap() = d;
5234 }
5235 });
5236 for (i, (_, jobs)) in active.iter().enumerate() {
5237 let down = results[i].lock().unwrap();
5238 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5239 routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim]
5240 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5241 }
5242 }
5243 }
5244 _ => {
5245 for (ei, jobs) in &active {
5246 let down = expert_fwd(*ei, jobs, pool);
5247 for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5248 routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim]
5249 .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5250 }
5251 }
5252 }
5253 }
5254 for bi in 0..b {
5255 let dst = &mut out[bi * dim..(bi + 1) * dim];
5256 for slot in 0..cold[bi].len() {
5257 let src = &routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim];
5258 for (o, &v) in dst.iter_mut().zip(src) {
5259 *o += v;
5260 }
5261 }
5262 }
5263}
5264
5265#[cfg(feature = "gpu")]
5270fn refill_route_slots(l: &Dsv4Layer, cfg: &Dsv4Cfg, pk: &Pack, picks: &[usize]) {
5271 if picks.is_empty() || pk.route_complete() {
5272 return;
5273 }
5274 let quota = {
5275 static Q: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
5276 *Q.get_or_init(|| {
5277 std::env::var("CMF_DSV4_FETCH_MAX")
5278 .ok()
5279 .and_then(|v| v.parse().ok())
5280 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().0)
5281 })
5282 };
5283 if quota == 0 {
5284 return;
5285 }
5286 let min_seen = {
5287 static M: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
5288 *M.get_or_init(|| {
5289 std::env::var("CMF_DSV4_FETCH_MIN_SEEN")
5290 .ok()
5291 .and_then(|v| v.parse().ok())
5292 .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().1)
5293 })
5294 };
5295 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
5296 return;
5297 };
5298 let mut dynv = pk.dynslots.lock().unwrap();
5299 if dynv.owner.is_empty() {
5300 return;
5301 }
5302 dynv.clock += 1;
5303 let clock = dynv.clock;
5304 if clock % 64 == 0 {
5305 for seen in &mut dynv.seen {
5306 *seen >>= 1;
5307 }
5308 }
5309 for &pick in picks {
5310 if pick >= dynv.seen.len() {
5311 continue;
5312 }
5313 dynv.seen[pick] = dynv.seen[pick].saturating_add(1);
5314 let slot = dynv.remap[pick];
5315 if slot != u32::MAX {
5316 dynv.last[slot as usize] = clock;
5317 }
5318 }
5319 let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
5320 let mut fetched = 0usize;
5321 for &pick in picks {
5322 if fetched >= quota || pick >= dynv.remap.len() {
5323 break;
5324 }
5325 if dynv.remap[pick] != u32::MAX || dynv.seen[pick] < min_seen {
5326 continue;
5327 }
5328 let victim = (0..dynv.owner.len())
5329 .filter(|&slot| dynv.last[slot] != clock)
5330 .min_by_key(|&slot| dynv.last[slot]);
5331 let Some(victim) = victim else { break };
5332 let Some(exp) = l.experts.get(pick) else {
5333 continue;
5334 };
5335 let tensors = (|| {
5336 Some((
5337 exp.w1.model_idx()?,
5338 exp.w3.model_idx()?,
5339 exp.w2.model_idx()?,
5340 ))
5341 })();
5342 let Some(tensors) = tensors else { continue };
5343 let gu_q2 = exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
5344 if !crate::gpu_wgpu::dsv4_slot_fill(
5345 &model,
5346 pack_first,
5347 victim,
5348 pick,
5349 tensors,
5350 cfg.moe_inter,
5351 cfg.dim,
5352 gu_q2,
5353 ) {
5354 break;
5355 }
5356 let old = dynv.owner[victim] as usize;
5357 if old < dynv.remap.len() {
5358 dynv.remap[old] = u32::MAX;
5359 }
5360 dynv.remap[pick] = victim as u32;
5361 dynv.owner[victim] = pick as u32;
5362 dynv.last[victim] = clock;
5363 dynv.mutated = true;
5364 fetched += 1;
5365 }
5366}
5367
5368fn o_project_block(
5373 attn: &[f32],
5374 b: usize,
5375 wo_a: &crate::qtensor::QTensor,
5376 wo_b: &crate::qtensor::QTensor,
5377 groups: usize,
5378 lora: usize,
5379 pool: Option<&crate::pool::Pool>,
5380 out: &mut [f32],
5381) {
5382 let attn_len = attn.len() / b;
5383 let per_group = attn_len / groups;
5384 let rows = groups * lora;
5385 let mut mid = vec![0.0f32; b * rows];
5386 let mid_addr = crate::pool::SendMut::new(mid.as_mut_ptr());
5387 let run = |start: usize, end: usize| {
5388 let mut wr = vec![0.0f32; wo_a.cols()];
5389 for r in start..end {
5390 wo_a.row_f32(r, &mut wr);
5391 let group = r / lora;
5392 for bi in 0..b {
5393 let x = &attn
5394 [bi * attn_len + group * per_group..bi * attn_len + (group + 1) * per_group];
5395 let v = wr.iter().zip(x).map(|(w, x)| w * x).sum();
5396 unsafe { *mid_addr.at(bi * rows + r) = v };
5397 }
5398 }
5399 };
5400 match pool {
5401 Some(p) if rows >= 256 => p.run_rows(rows, &run),
5402 _ => run(0, rows),
5403 }
5404 wo_b.matmat(&mid, b, out, pool);
5405}
5406
5407fn no_compressed() -> bool {
5413 static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5414 *OFF.get_or_init(|| std::env::var("CMF_DSV4_NO_COMPRESSED").is_ok_and(|v| v != "0"))
5415}
5416
5417fn trace_on() -> bool {
5418 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5419 *ON.get_or_init(|| std::env::var("CMF_DSV4_TRACE").is_ok_and(|v| v != "0"))
5420}
5421
5422fn rms_of(v: &[f32]) -> f32 {
5423 if v.is_empty() {
5424 return 0.0;
5425 }
5426 (v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32).sqrt()
5427}
5428
5429#[cfg(feature = "gpu")]
5430fn verify_fp_on(pos: usize) -> bool {
5431 static POS: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
5432 let want = *POS.get_or_init(|| {
5433 std::env::var("CMF_DSV4_FP_POS")
5434 .ok()
5435 .and_then(|v| v.parse().ok())
5436 });
5437 want == Some(pos)
5438}
5439
5440#[cfg(feature = "gpu")]
5441fn verify_fp(tag: &str, pos: usize, li: usize, state: &[f32]) {
5442 if !verify_fp_on(pos) {
5443 return;
5444 }
5445 let mut fp = 0xcbf29ce484222325u64;
5446 for &x in state {
5447 fp ^= x.to_bits() as u64;
5448 fp = fp.wrapping_mul(0x100000001b3);
5449 }
5450 eprintln!(
5451 "[dsv4-fp] {tag} pos={pos} li={li} fp={fp:016x} rms={:.7} head={:?}",
5452 rms_of(state),
5453 &state[..4.min(state.len())]
5454 );
5455}
5456
5457thread_local! {
5463 static BODY: std::cell::RefCell<Vec<String>> = const { std::cell::RefCell::new(Vec::new()) };
5465 static PICKED: std::cell::RefCell<Vec<Vec<usize>>> =
5469 const { std::cell::RefCell::new(Vec::new()) };
5470 static PICK_TALLY: std::cell::RefCell<Option<Vec<(usize, Vec<usize>)>>> =
5472 const { std::cell::RefCell::new(None) };
5473}
5474
5475pub fn pick_tally_arm() {
5477 PICK_TALLY.with(|t| *t.borrow_mut() = Some(Vec::new()));
5478}
5479
5480pub fn pick_tally_take() -> Vec<(usize, Vec<usize>)> {
5482 PICK_TALLY.with(|t| t.borrow_mut().take().unwrap_or_default())
5483}
5484
5485pub fn tally_unique(picks: &[(usize, Vec<usize>)]) -> (usize, usize) {
5488 let mut seen = std::collections::HashSet::new();
5493 let mut total = 0;
5494 for (li, v) in picks {
5495 total += v.len();
5496 for &e in v {
5497 seen.insert((*li, e));
5498 }
5499 }
5500 (seen.len(), total)
5501}
5502
5503fn dump_path() -> Option<&'static str> {
5504 static P: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
5505 P.get_or_init(|| std::env::var("CMF_DSV4_DUMP").ok())
5506 .as_deref()
5507}
5508
5509fn dump_line(json: &str) {
5510 if let Some(p) = dump_path() {
5511 use std::io::Write as _;
5512 if let Ok(mut f) = std::fs::OpenOptions::new()
5513 .create(true)
5514 .append(true)
5515 .open(p)
5516 {
5517 let _ = writeln!(f, "{json}");
5518 }
5519 }
5520}
5521
5522fn vec_json(v: &[f32]) -> String {
5523 let mut s = String::with_capacity(v.len() * 9);
5524 s.push('[');
5525 for (i, x) in v.iter().enumerate() {
5526 if i > 0 {
5527 s.push(',');
5528 }
5529 s.push_str(&format!("{x:.6e}"));
5530 }
5531 s.push(']');
5532 s
5533}
5534
5535#[allow(clippy::too_many_arguments)]
5543#[allow(clippy::too_many_arguments)]
5553fn batch_prefill() -> usize {
5557 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
5558 *N.get_or_init(|| {
5559 std::env::var("CMF_DSV4_BATCH")
5560 .ok()
5561 .and_then(|v| v.parse::<usize>().ok())
5562 .filter(|&n| (1..=32).contains(&n))
5563 .unwrap_or(8)
5570 })
5571}
5572
5573#[allow(clippy::too_many_arguments)]
5586fn forward_chunk_batched(
5587 g: &Dsv4Globals,
5588 layers: &[Dsv4Layer],
5589 cfg: &Dsv4Cfg,
5590 st: &mut Dsv4State,
5591 ids: &[u32],
5592 pos0: usize,
5593 inv_freq: &[f32],
5594 pool: Option<&crate::pool::Pool>,
5595 logits: &mut Vec<f32>,
5596 want_logits: bool,
5597) -> bool {
5598 #[cfg(not(feature = "gpu"))]
5599 {
5600 let _ = (
5601 g,
5602 layers,
5603 cfg,
5604 st,
5605 ids,
5606 pos0,
5607 inv_freq,
5608 pool,
5609 logits,
5610 want_logits,
5611 );
5612 false
5613 }
5614 #[cfg(feature = "gpu")]
5615 {
5616 let b = ids.len();
5617 let gpu_end = st
5622 .dev_set
5623 .iter()
5624 .enumerate()
5625 .position(|(li, &on)| {
5626 !on || pack_for(&layers[li], cfg, li)
5627 .is_none_or(|p| !p.route_complete())
5628 })
5629 .unwrap_or(st.dev_set.len());
5630 let why = if b < 2 {
5631 "токенов меньше двух"
5632 } else if !chain_enabled() {
5633 "цепочка выключена"
5634 } else if !st.dev_owned {
5635 "карта ещё не владеет состоянием"
5636 } else if st.dev_set.len() != layers.len() {
5637 "набор слоёв ещё не зафиксирован"
5638 } else if st.dev_set[gpu_end.min(st.dev_set.len())..]
5639 .iter()
5640 .enumerate()
5641 .any(|(i, &on)| on && !st.partial_set.get(gpu_end + i).copied().unwrap_or(false))
5642 {
5643 "слои на карте не образуют префикс"
5644 } else {
5645 ""
5646 };
5647 if !why.is_empty() {
5648 static SAID: std::sync::Once = std::sync::Once::new();
5649 SAID.call_once(|| tracing::warn!("dsv4: пакет отказал — {why}"));
5650 return false;
5651 }
5652 let (hc, dim) = (cfg.hc_mult, cfg.dim);
5653 let mut emb = vec![0.0f32; dim];
5654 let mut states = vec![0.0f32; b * hc * dim];
5655 for (t, &id) in ids.iter().enumerate() {
5656 let mut state = vec![0.0f32; hc * dim];
5657 g.embed.row_f32(id as usize, &mut emb);
5658 for j in 0..hc {
5659 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
5660 }
5661 let (folded, post0, comb0) = hc_fold_norm(
5662 &state,
5663 &layers[0].hc_attn_fn,
5664 &layers[0].hc_attn_scale,
5665 &layers[0].hc_attn_base,
5666 &layers[0].attn_norm,
5667 cfg,
5668 pool,
5669 );
5670 let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
5671 layers[0].wq_a.matvec(&folded, &mut qn0, pool);
5672 rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
5673 if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
5674 || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
5675 || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
5676 || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
5677 {
5678 return false;
5679 }
5680 states[t * hc * dim..(t + 1) * hc * dim].copy_from_slice(&state);
5681 }
5682 let run: Vec<usize> = (0..gpu_end).collect();
5683 let mut folded = Vec::new();
5684 st.pos = pos0;
5685 if gpu_end > 0 {
5686 if !dsv4_chain_run(
5687 layers,
5688 &run,
5689 cfg,
5690 g,
5691 st,
5692 *ids.last().unwrap(),
5693 &mut folded,
5694 Some(&mut states),
5695 b,
5696 ids,
5697 true,
5698 pool,
5699 ) {
5700 return false;
5701 }
5702 }
5703 let mut scratch = HcScratch::new(cfg);
5709 host_tail_walk_batch(
5710 g,
5711 layers,
5712 cfg,
5713 st,
5714 gpu_end,
5715 &mut states,
5716 ids,
5717 pos0,
5718 b,
5719 inv_freq,
5720 &mut scratch,
5721 pool,
5722 None,
5723 );
5724 st.pos = pos0 + b;
5725 {
5730 static SAID: std::sync::Once = std::sync::Once::new();
5731 SAID.call_once(|| tracing::warn!("dsv4: префилл пакетами по {b}"));
5732 }
5733 if want_logits {
5738 let last = &states[(b - 1) * hc * dim..b * hc * dim];
5739 let mut h = vec![0.0f32; dim];
5740 hc_head_fold(
5741 last,
5742 &g.hc_head_fn,
5743 g.hc_head_scale,
5744 &g.hc_head_base,
5745 cfg,
5746 pool,
5747 &mut h,
5748 );
5749 rms_weighted(&mut h, &g.norm, cfg.norm_eps);
5750 logits.resize(cfg.vocab, 0.0);
5751 g.head.matvec(&h, logits, pool);
5752 } else {
5753 logits.clear();
5754 }
5755 true
5756 }
5757}
5758
5759#[cfg(feature = "gpu")]
5767pub struct Dsv4SpecTxn {
5768 pos0: usize,
5769 batch: usize,
5770 pub(crate) gpu_end: usize,
5771 dev_filled: Vec<usize>,
5772 dev_n_comp: Vec<usize>,
5773 dev_n_ix: Vec<usize>,
5774 host: Vec<(usize, HostLayerSnap)>,
5775 host_steps: Vec<(usize, Vec<HostLayerSnap>)>,
5780 pub states: Vec<f32>,
5784 shadow: Option<crate::gpu_wgpu::Dsv4SpecShadow>,
5785}
5786
5787#[cfg(feature = "gpu")]
5788struct HostLayerSnap {
5789 window: Vec<f32>,
5790 compressed: Vec<f32>,
5791 index_kv: Vec<f32>,
5792 pending_kv: Vec<f32>,
5793 pending_score: Vec<f32>,
5794 prev_kv: Vec<f32>,
5795 prev_score: Vec<f32>,
5796 pending_ix_kv: Vec<f32>,
5797 pending_ix_score: Vec<f32>,
5798 prev_ix_kv: Vec<f32>,
5799 prev_ix_score: Vec<f32>,
5800}
5801
5802#[cfg(feature = "gpu")]
5803fn host_snap(st: &Dsv4State, li: usize) -> HostLayerSnap {
5804 HostLayerSnap {
5805 window: st.window[li].clone(),
5806 compressed: st.compressed[li].clone(),
5807 index_kv: st.index_kv[li].clone(),
5808 pending_kv: st.pending_kv[li].clone(),
5809 pending_score: st.pending_score[li].clone(),
5810 prev_kv: st.prev_kv[li].clone(),
5811 prev_score: st.prev_score[li].clone(),
5812 pending_ix_kv: st.pending_ix_kv[li].clone(),
5813 pending_ix_score: st.pending_ix_score[li].clone(),
5814 prev_ix_kv: st.prev_ix_kv[li].clone(),
5815 prev_ix_score: st.prev_ix_score[li].clone(),
5816 }
5817}
5818
5819#[cfg(feature = "gpu")]
5820fn host_restore(st: &mut Dsv4State, li: usize, s: &HostLayerSnap) {
5821 st.window[li] = s.window.clone();
5822 st.compressed[li] = s.compressed.clone();
5823 st.index_kv[li] = s.index_kv.clone();
5824 st.pending_kv[li] = s.pending_kv.clone();
5825 st.pending_score[li] = s.pending_score.clone();
5826 st.prev_kv[li] = s.prev_kv.clone();
5827 st.prev_score[li] = s.prev_score.clone();
5828 st.pending_ix_kv[li] = s.pending_ix_kv.clone();
5829 st.pending_ix_score[li] = s.pending_ix_score.clone();
5830 st.prev_ix_kv[li] = s.prev_ix_kv.clone();
5831 st.prev_ix_score[li] = s.prev_ix_score.clone();
5832}
5833
5834#[cfg(feature = "gpu")]
5838#[allow(clippy::too_many_arguments)]
5839fn host_tail_walk(
5840 g: &Dsv4Globals,
5841 layers: &[Dsv4Layer],
5842 cfg: &Dsv4Cfg,
5843 st: &mut Dsv4State,
5844 gpu_end: usize,
5845 state: &mut [f32],
5846 token_id: u32,
5847 pos: usize,
5848 inv_freq: &[f32],
5849 scratch: &mut HcScratch,
5850 pool: Option<&crate::pool::Pool>,
5851) {
5852 st.pos = pos;
5853 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
5854 let freqs = if l.compressor.is_some() {
5855 &g.inv_freq_compress
5856 } else {
5857 &g.inv_freq_window
5858 };
5859 let freqs = if freqs.is_empty() {
5860 inv_freq
5861 } else {
5862 freqs.as_slice()
5863 };
5864 hc_block(
5865 state,
5866 &l.hc_attn_fn,
5867 &l.hc_attn_scale,
5868 &l.hc_attn_base,
5869 &l.attn_norm,
5870 cfg,
5871 scratch,
5872 pool,
5873 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
5874 );
5875 hc_block(
5876 state,
5877 &l.hc_ffn_fn,
5878 &l.hc_ffn_scale,
5879 &l.hc_ffn_base,
5880 &l.ffn_norm,
5881 cfg,
5882 scratch,
5883 pool,
5884 |f, o| {
5885 if host_cpu_moe() {
5886 crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
5887 } else {
5888 moe_step(f, l, cfg, token_id, li, pool, o)
5889 }
5890 },
5891 );
5892 dspark_note(li, state, cfg);
5893 }
5894}
5895
5896#[cfg(feature = "gpu")]
5902#[allow(clippy::too_many_arguments)]
5903fn host_tail_walk_batch(
5904 g: &Dsv4Globals,
5905 layers: &[Dsv4Layer],
5906 cfg: &Dsv4Cfg,
5907 st: &mut Dsv4State,
5908 gpu_end: usize,
5909 states: &mut [f32],
5910 ids: &[u32],
5911 pos0: usize,
5912 b: usize,
5913 inv_freq: &[f32],
5914 scratch: &mut HcScratch,
5915 pool: Option<&crate::pool::Pool>,
5916 mut steps: Option<&mut Vec<(usize, Vec<HostLayerSnap>)>>,
5917) {
5918 let (hc, dim) = (cfg.hc_mult, cfg.dim);
5919 let mix_hc = (2 + hc) * hc;
5920 let mut folds = vec![0.0f32; b * dim];
5921 let mut mo = vec![0.0f32; b * dim];
5922 let mut posts = vec![0.0f32; b * hc];
5923 let mut combs = vec![0.0f32; b * hc * hc];
5924 let mut resid = vec![0.0f32; b * hc * dim];
5925 let spec_time = {
5926 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5927 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
5928 };
5929 for (li, l) in layers.iter().enumerate().skip(gpu_end) {
5930 let t_attn = std::time::Instant::now();
5931 let freqs = if l.compressor.is_some() {
5932 &g.inv_freq_compress
5933 } else {
5934 &g.inv_freq_window
5935 };
5936 let freqs = if freqs.is_empty() {
5937 inv_freq
5938 } else {
5939 freqs.as_slice()
5940 };
5941 for t in 0..b {
5942 st.pos = pos0 + t;
5943 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
5944 hc_block(
5945 state,
5946 &l.hc_attn_fn,
5947 &l.hc_attn_scale,
5948 &l.hc_attn_base,
5949 &l.attn_norm,
5950 cfg,
5951 scratch,
5952 pool,
5953 |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
5954 );
5955 if let Some(steps) = steps.as_mut() {
5956 match steps.iter_mut().find(|(l, _)| *l == li) {
5957 Some((_, v)) => v.push(host_snap(st, li)),
5958 None => steps.push((li, vec![host_snap(st, li)])),
5959 }
5960 }
5961 }
5962 let t_glue = std::time::Instant::now();
5963 for t in 0..b {
5964 let state = &states[t * hc * dim..(t + 1) * hc * dim];
5965 hc_mixes(
5966 state,
5967 &l.hc_ffn_fn,
5968 mix_hc,
5969 cfg.norm_eps,
5970 pool,
5971 &mut scratch.mixes,
5972 );
5973 hc_split_sinkhorn(
5974 &scratch.mixes,
5975 &l.hc_ffn_scale,
5976 &l.hc_ffn_base,
5977 hc,
5978 cfg.hc_sinkhorn_iters,
5979 cfg.hc_eps,
5980 &mut scratch.pre,
5981 &mut posts[t * hc..(t + 1) * hc],
5982 &mut combs[t * hc * hc..(t + 1) * hc * hc],
5983 );
5984 let fold = &mut folds[t * dim..(t + 1) * dim];
5985 hc_fold(state, &scratch.pre, hc, dim, fold);
5986 let ms = fold.iter().map(|v| v * v).sum::<f32>() / dim as f32;
5987 let inv = 1.0 / (ms + cfg.norm_eps).sqrt();
5988 for (v, w) in fold.iter_mut().zip(&l.ffn_norm) {
5989 *v = *v * inv * w;
5990 }
5991 resid[t * hc * dim..(t + 1) * hc * dim]
5992 .copy_from_slice(&states[t * hc * dim..(t + 1) * hc * dim]);
5993 }
5994 let t_moe = std::time::Instant::now();
5995 let tail_pack = {
6001 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6002 *ON.get_or_init(|| {
6003 std::env::var("CMF_DSV4_TAIL_PACK")
6004 .map(|v| v != "0")
6005 .unwrap_or(true)
6006 })
6007 };
6008 let mut packed_done = false;
6009 if tail_pack && pack_for(l, cfg, li).is_some() {
6010 packed_done = true;
6011 for t in 0..b {
6012 let f = &folds[t * dim..(t + 1) * dim];
6013 let forced = l.tid2eid.as_ref().map(|tbl| {
6014 hash_route(tbl, cfg.vocab, cfg.top_k, ids.get(t).copied().unwrap_or(0))
6015 });
6016 let o = &mut mo[t * dim..(t + 1) * dim];
6017 match moe_frame(f, l, cfg, li, &[], forced.as_deref(), pool, None, None, o) {
6018 Some((cold_sum, n)) => {
6019 if n > 0 {
6020 for (od, cd) in o.iter_mut().zip(cold_sum.iter()) {
6021 *od += cd;
6022 }
6023 }
6024 }
6025 None => {
6026 packed_done = false;
6027 break;
6028 }
6029 }
6030 }
6031 }
6032 if !packed_done {
6033 if host_cpu_moe() {
6034 crate::gpu::cpu_scope(|| moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo));
6035 } else {
6036 moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo);
6037 }
6038 }
6039 let t_exp = std::time::Instant::now();
6040 for t in 0..b {
6041 let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
6042 hc_expand(
6043 &mo[t * dim..(t + 1) * dim],
6044 &resid[t * hc * dim..(t + 1) * hc * dim],
6045 &posts[t * hc..(t + 1) * hc],
6046 &combs[t * hc * hc..(t + 1) * hc * hc],
6047 hc,
6048 dim,
6049 state,
6050 );
6051 dspark_note(li, state, cfg);
6052 }
6053 if spec_time {
6054 eprintln!(
6055 "хвост слоя {li}: attn {:.1} мс, клей {:.1}, moe {:.1}, expand {:.1}",
6056 (t_glue - t_attn).as_secs_f64() * 1e3,
6057 (t_moe - t_glue).as_secs_f64() * 1e3,
6058 (t_exp - t_moe).as_secs_f64() * 1e3,
6059 t_exp.elapsed().as_secs_f64() * 1e3,
6060 );
6061 }
6062 }
6063}
6064
6065#[cfg(feature = "gpu")]
6071#[allow(clippy::too_many_arguments)]
6072fn partial_layer_batch(
6073 g: &Dsv4Globals,
6074 layers: &[Dsv4Layer],
6075 cfg: &Dsv4Cfg,
6076 st: &mut Dsv4State,
6077 li: usize,
6078 states: &mut [f32],
6079 ids: &[u32],
6080 pos0: usize,
6081 b: usize,
6082 pool: Option<&crate::pool::Pool>,
6083) -> bool {
6084 let l = &layers[li];
6085 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
6086 if states.len() < b * hc * dim || ids.len() < b {
6087 return false;
6088 }
6089 let Some(pk) = pack_for(l, cfg, li) else {
6090 return false;
6091 };
6092 if pk.route_complete() {
6093 return false;
6094 }
6095 let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
6096 return false;
6097 };
6098 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
6099 l.wq_a.model_idx(),
6100 l.wq_b.model_idx(),
6101 l.wo_a.model_idx(),
6102 l.wo_b.model_idx(),
6103 l.wkv.model_idx(),
6104 ) else {
6105 return false;
6106 };
6107 let comp = match &l.compressor {
6108 None => None,
6109 Some(cp) => {
6110 let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
6111 return false;
6112 };
6113 Some((
6114 crate::gpu_wgpu::Dsv4CompW {
6115 wkv: a,
6116 wgate: bx,
6117 norm: &cp.norm,
6118 ape: &cp.ape,
6119 },
6120 crate::gpu_wgpu::Dsv4CompGeom {
6121 width: cp.wkv.rows(),
6122 hidden: dim,
6123 ratio: cp.ratio,
6124 overlap: cp.overlap,
6125 rope_dim: cfg.rope_head_dim,
6126 eps: cfg.norm_eps,
6127 },
6128 ))
6129 }
6130 };
6131 let ix = match &l.indexer {
6132 None => None,
6133 Some(ixr) => {
6134 let cp = &ixr.compressor;
6135 let (Some(a), Some(bx), Some(qb), Some(wp)) = (
6136 cp.wkv.model_idx(),
6137 cp.wgate.model_idx(),
6138 ixr.wq_b.model_idx(),
6139 ixr.weights_proj.model_idx(),
6140 ) else {
6141 return false;
6142 };
6143 let ih = ixr.weights_proj.rows();
6144 Some((
6145 crate::gpu_wgpu::Dsv4CompW {
6146 wkv: a,
6147 wgate: bx,
6148 norm: &cp.norm,
6149 ape: &cp.ape,
6150 },
6151 crate::gpu_wgpu::Dsv4CompGeom {
6152 width: cp.wkv.rows(),
6153 hidden: dim,
6154 ratio: cp.ratio,
6155 overlap: cp.overlap,
6156 rope_dim: cfg.rope_head_dim,
6157 eps: cfg.norm_eps,
6158 },
6159 crate::gpu_wgpu::Dsv4IxW {
6160 wq_b: qb,
6161 weights_proj: wp,
6162 },
6163 crate::gpu_wgpu::Dsv4IxGeom {
6164 ih,
6165 idim: ixr.wq_b.rows() / ih.max(1),
6166 q_lora: cfg.q_lora_rank,
6167 hidden: dim,
6168 rope_dim: cfg.rope_head_dim,
6169 eps: cfg.norm_eps,
6170 top_k: cfg.index_topk,
6171 window: cfg.window,
6172 },
6173 ))
6174 }
6175 };
6176 let ew_c = comp.as_ref().map_or(0, |(_, cg)| {
6177 if cg.overlap { cg.width / 2 } else { cg.width }
6178 });
6179 let ew_i = ix.as_ref().map_or(0, |(_, cg, _, _)| {
6180 if cg.overlap { cg.width / 2 } else { cg.width }
6181 });
6182 let comp_extra = comp
6183 .as_ref()
6184 .map_or(0, |(_, cg)| b.div_ceil(cg.ratio.max(1)));
6185 let need = cfg.window * hd
6186 + (st.dev_n_comp[li] + comp_extra + 1) * ew_c.max(1)
6187 + (b + 1) * hd;
6188 if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
6189 return false;
6190 }
6191 let base = crate::gpu_wgpu::Dsv4Prep {
6192 wkv,
6193 kv_norm: &l.kv_norm,
6194 comp,
6195 ix,
6196 filled: st.dev_filled[li],
6197 window: cfg.window,
6198 n_comp: st.dev_n_comp[li],
6199 n_ix: st.dev_n_ix[li],
6200 comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
6201 ix_dst_off: st.dev_n_ix[li] * ew_i,
6202 idx_cap: cfg.window
6203 + if l.indexer.is_some() {
6204 cfg.index_topk
6205 } else {
6206 st.dev_n_comp[li] + comp_extra + 1
6207 },
6208 };
6209 let mut preps = Vec::with_capacity(b);
6210 for t in 0..b {
6211 let mut p = base.clone();
6212 p.filled = (base.filled + t).min(base.window);
6213 let advanced = |ratio: usize| -> usize {
6214 if ratio == 0 {
6215 0
6216 } else {
6217 (0..t).filter(|k| (pos0 + k + 1) % ratio == 0).count()
6218 }
6219 };
6220 if let Some((_, cg)) = base.comp.as_ref() {
6221 p.n_comp = base.n_comp + advanced(cg.ratio);
6222 p.comp_dst_off = base.comp_dst_off + (p.n_comp - base.n_comp) * ew_c;
6223 }
6224 if let Some((_, cg, _, _)) = base.ix.as_ref() {
6225 p.n_ix = base.n_ix + advanced(cg.ratio);
6226 p.ix_dst_off = base.ix_dst_off + (p.n_ix - base.n_ix) * ew_i;
6227 }
6228 preps.push(p);
6229 }
6230
6231 for t in 0..b {
6235 let state = &states[t * hc * dim..(t + 1) * hc * dim];
6236 let (fold, post, comb) = hc_fold_norm(
6237 state,
6238 &l.hc_attn_fn,
6239 &l.hc_attn_scale,
6240 &l.hc_attn_base,
6241 &l.attn_norm,
6242 cfg,
6243 pool,
6244 );
6245 let mut qn = vec![0.0f32; cfg.q_lora_rank];
6246 l.wq_a.matvec(&fold, &mut qn, pool);
6247 rms_weighted(&mut qn, &l.q_norm, cfg.norm_eps);
6248 if !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, state, &post, &comb, &fold, &qn) {
6249 return false;
6250 }
6251 }
6252 let forced_rows: Vec<Option<Vec<usize>>> = ids
6253 .iter()
6254 .take(b)
6255 .map(|&id| {
6256 l.tid2eid
6257 .as_ref()
6258 .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, id))
6259 })
6260 .collect();
6261 let dynv = pk.dynslots.lock().unwrap();
6262 let w = crate::gpu_wgpu::Dsv4LayerW {
6263 attn: crate::gpu_wgpu::Dsv4AttnW {
6264 wq_a,
6265 wq_b,
6266 wo_a,
6267 wo_b,
6268 q_norm: &l.q_norm,
6269 sink: &l.attn_sink,
6270 },
6271 moe: crate::gpu_wgpu::Dsv4MoeW {
6272 router: &[],
6273 experts: &pk.tensors,
6274 logits: &[],
6275 bias: pk.bias.as_deref(),
6276 mask: pk.mask.as_deref(),
6277 forced: None,
6278 remap: Some(&dynv.remap),
6279 },
6280 hc_ffn_fn: &l.hc_ffn_fn,
6281 hc_ffn_scale: &l.hc_ffn_scale,
6282 hc_ffn_base: &l.hc_ffn_base,
6283 hc_next_fn: None,
6286 hc_next_scale: &l.hc_attn_scale,
6287 hc_next_base: &l.hc_attn_base,
6288 ffn_norm: &l.ffn_norm,
6289 next_norm: &l.attn_norm,
6290 next_q_norm: &l.q_norm,
6291 next_wq_a: None,
6292 router: &pk.router,
6293 };
6294 let geom = crate::gpu_wgpu::Dsv4LayerGeom {
6295 attn: crate::gpu_wgpu::Dsv4AttnGeom {
6296 dim,
6297 nh: cfg.n_heads,
6298 hd,
6299 rd: cfg.rope_head_dim,
6300 q_lora: cfg.q_lora_rank,
6301 o_lora: cfg.o_lora_rank,
6302 o_groups: cfg.o_groups,
6303 eps: cfg.norm_eps,
6304 scale: (hd as f32).powf(-0.5),
6305 },
6306 moe: crate::gpu_wgpu::Dsv4MoeGeom {
6307 hidden: dim,
6308 inter: cfg.moe_inter,
6309 top_k: cfg.top_k,
6310 route_scale: cfg.route_scale,
6311 swiglu_limit: cfg.swiglu_limit,
6312 gu_q2: l
6313 .experts
6314 .first()
6315 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
6316 },
6317 hc,
6318 hc_eps: cfg.hc_eps,
6319 sinkhorn_iters: cfg.hc_sinkhorn_iters,
6320 };
6321 let freqs = if l.compressor.is_some() {
6322 g.inv_freq_compress.as_slice()
6323 } else {
6324 g.inv_freq_window.as_slice()
6325 };
6326 let Some(mut got) = crate::gpu_wgpu::dsv4_layer_batch_partial(
6327 &model,
6328 &w,
6329 geom,
6330 st.kv_id,
6331 li,
6332 b,
6333 &preps,
6334 Some(&forced_rows),
6335 freqs,
6336 pos0,
6337 ) else {
6338 return false;
6339 };
6340 drop(dynv);
6341 let mut cold_sum = vec![0.0f32; b * dim];
6342 cold_step_block(&got.cold_x, b, l, cfg, &got.cold, pool, &mut cold_sum);
6343 for t in 0..b {
6344 let state = &mut got.states[t * hc * dim..(t + 1) * hc * dim];
6345 let post = &got.posts[t * hc..(t + 1) * hc];
6346 let cold = &cold_sum[t * dim..(t + 1) * dim];
6347 for j in 0..hc {
6348 for d in 0..dim {
6349 state[j * dim + d] += post[j] * cold[d];
6350 }
6351 }
6352 }
6353 states[..b * hc * dim].copy_from_slice(&got.states);
6354 if !crate::gpu_wgpu::dsv4_spec_cap_write_host(li, b, hc * dim, &got.states) {
6355 return false;
6356 }
6357 if let Some(first_route) = got.routed.first() {
6358 refill_route_slots(l, cfg, &pk, first_route);
6359 }
6360 for t in 0..b {
6361 let pos = pos0 + t;
6362 st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
6363 if let Some((_, cg, ..)) = base.ix.as_ref() {
6364 if (pos + 1) % cg.ratio == 0 {
6365 st.dev_n_ix[li] += 1;
6366 }
6367 }
6368 if let Some((_, cg)) = base.comp.as_ref() {
6369 if (pos + 1) % cg.ratio == 0 {
6370 st.dev_n_comp[li] += 1;
6371 note_compressed(st.kv_id, li, st.dev_n_comp[li]);
6372 }
6373 }
6374 }
6375 true
6376}
6377
6378#[cfg(feature = "gpu")]
6387#[allow(clippy::too_many_arguments)]
6388pub fn dsv4_verify_chunk(
6389 g: &Dsv4Globals,
6390 layers: &[Dsv4Layer],
6391 cfg: &Dsv4Cfg,
6392 st: &mut Dsv4State,
6393 ids: &[u32],
6394 pos0: usize,
6395 inv_freq: &[f32],
6396 pool: Option<&crate::pool::Pool>,
6397 cap_targets: &[usize],
6398 argmax_out: &mut Vec<u32>,
6399 logits_out: &mut Vec<f32>,
6400 walked_out: &mut Vec<f32>,
6401) -> Option<Dsv4SpecTxn> {
6402 let b = ids.len();
6403 let complete_end = st
6407 .dev_set
6408 .iter()
6409 .enumerate()
6410 .position(|(li, &on)| {
6411 !on || pack_for(&layers[li], cfg, li)
6412 .is_none_or(|p| !p.route_complete())
6413 })
6414 .unwrap_or(st.dev_set.len());
6415 fn partial_batch_on() -> bool {
6416 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6417 *ON.get_or_init(|| {
6418 std::env::var("CMF_DSV4_PARTIAL_BATCH")
6419 .map(|v| v != "0")
6420 .unwrap_or(true)
6421 })
6422 }
6423 let device_end = if partial_batch_on() {
6424 (complete_end..layers.len())
6425 .take_while(|&li| {
6426 st.dev_set.get(li).copied().unwrap_or(false)
6427 && st.partial_set.get(li).copied().unwrap_or(false)
6428 && pack_for(&layers[li], cfg, li).is_some_and(|p| !p.route_complete())
6429 })
6430 .last()
6431 .map_or(complete_end, |li| li + 1)
6432 } else {
6433 complete_end
6434 };
6435 let full_beyond = st.dev_set[device_end.min(st.dev_set.len())..]
6438 .iter()
6439 .enumerate()
6440 .any(|(i, &on)| on && !st.partial_set.get(device_end + i).copied().unwrap_or(false));
6441 fn host_verify_on() -> bool {
6448 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6449 *ON.get_or_init(|| {
6450 std::env::var("CMF_DSV4_HOST_VERIFY")
6451 .map(|v| v != "0")
6452 .unwrap_or(true)
6457 })
6458 }
6459 if b < 2
6460 || !chain_enabled()
6461 || !st.dev_owned
6462 || st.dev_set.len() != layers.len()
6463 || (device_end == 0 && !host_verify_on())
6464 || full_beyond
6465 {
6466 return None;
6467 }
6468 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
6469 let metas: Vec<(usize, usize, usize, usize)> = (0..device_end)
6471 .map(|li| (li, hd, cfg.window, st.dev_filled[li]))
6472 .collect();
6473 let shadow = crate::gpu_wgpu::dsv4_spec_shadow(st.kv_id, &metas, b)?;
6474 let mut txn = Dsv4SpecTxn {
6475 pos0,
6476 batch: b,
6477 gpu_end: device_end,
6478 dev_filled: st.dev_filled.clone(),
6479 dev_n_comp: st.dev_n_comp.clone(),
6480 dev_n_ix: st.dev_n_ix.clone(),
6481 host: (device_end..layers.len())
6482 .map(|li| (li, host_snap(st, li)))
6483 .collect(),
6484 states: Vec::new(),
6485 host_steps: Vec::new(),
6486 shadow: Some(shadow),
6487 };
6488 let dev_caps: Vec<usize> = cap_targets
6490 .iter()
6491 .copied()
6492 .filter(|&t| t < device_end)
6493 .collect();
6494 crate::gpu_wgpu::dsv4_spec_retain_arm(device_end, &dev_caps);
6495
6496 let mut emb = vec![0.0f32; dim];
6498 let mut states = vec![0.0f32; b * hc * dim];
6499 for (t, &id) in ids.iter().enumerate() {
6500 let mut state = vec![0.0f32; hc * dim];
6501 g.embed.row_f32(id as usize, &mut emb);
6502 for j in 0..hc {
6503 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
6504 }
6505 states[t * hc * dim..(t + 1) * hc * dim].copy_from_slice(&state);
6506 let (folded, post0, comb0) = hc_fold_norm(
6507 &state,
6508 &layers[0].hc_attn_fn,
6509 &layers[0].hc_attn_scale,
6510 &layers[0].hc_attn_base,
6511 &layers[0].attn_norm,
6512 cfg,
6513 pool,
6514 );
6515 let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
6516 layers[0].wq_a.matvec(&folded, &mut qn0, pool);
6517 rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
6518 if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
6519 || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
6520 || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
6521 || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
6522 {
6523 crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
6524 return None;
6525 }
6526 }
6527 let spec_time = {
6528 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6529 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
6530 };
6531 let t0 = std::time::Instant::now();
6532 let mut folded = Vec::new();
6533 st.pos = pos0;
6534 let fp_split = verify_fp_on(pos0)
6538 && std::env::var("CMF_DSV4_FP_SPLIT").is_ok_and(|v| v != "0");
6539 let mut ok = true;
6540 if fp_split {
6541 for li in 0..complete_end {
6542 let one = [li];
6543 ok = dsv4_chain_run(
6544 layers,
6545 &one,
6546 cfg,
6547 g,
6548 st,
6549 *ids.last().unwrap(),
6550 &mut folded,
6551 Some(&mut states),
6552 b,
6553 ids,
6554 li == 0,
6555 pool,
6556 );
6557 if !ok {
6558 break;
6559 }
6560 verify_fp("verify", pos0, li, &states[..hc * dim]);
6561 }
6562 } else if complete_end > 0 {
6563 let run: Vec<usize> = (0..complete_end).collect();
6564 ok = dsv4_chain_run(
6565 layers,
6566 &run,
6567 cfg,
6568 g,
6569 st,
6570 *ids.last().unwrap(),
6571 &mut folded,
6572 Some(&mut states),
6573 b,
6574 ids,
6575 true,
6576 pool,
6577 );
6578 if ok {
6579 verify_fp("verify", pos0, complete_end - 1, &states[..hc * dim]);
6580 }
6581 }
6582 if ok {
6583 for li in complete_end..device_end {
6584 if !partial_layer_batch(g, layers, cfg, st, li, &mut states, ids, pos0, b, pool) {
6585 ok = false;
6586 break;
6587 }
6588 verify_fp("verify", pos0, li, &states[..hc * dim]);
6589 }
6590 }
6591 crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
6592 if !ok {
6593 if let Some(sh) = txn.shadow.take() {
6596 let _ = crate::gpu_wgpu::dsv4_spec_restore(&sh);
6597 }
6598 st.dev_filled = txn.dev_filled;
6599 st.dev_n_comp = txn.dev_n_comp;
6600 st.dev_n_ix = txn.dev_n_ix;
6601 st.pos = pos0;
6602 return None;
6603 }
6604 txn.states = states.clone();
6605 let t_chain = t0.elapsed();
6606 if std::env::var("CMF_DSV4_FOLD_DBG").is_ok() {
6607 for li in 0..device_end {
6611 let Some(ixr) = &layers[li].indexer else {
6612 continue;
6613 };
6614 let ratio = ixr.compressor.ratio;
6615 for t in 0..b {
6616 if (pos0 + t + 1) % ratio == 0 {
6617 let ew = {
6618 let w = ixr.compressor.wkv.rows();
6619 if ixr.compressor.overlap { w / 2 } else { w }
6620 };
6621 let idx_new = txn.dev_n_ix[li]
6622 + (0..=t).filter(|k| (pos0 + k + 1) % ratio == 0).count()
6623 - 1;
6624 if let Some(v) =
6625 crate::gpu_wgpu::dsv4_dbg_read_ix(st.kv_id, li, idx_new * ew, ew.min(8))
6626 {
6627 let sum: f32 = v.iter().sum();
6628 eprintln!(
6629 "[fold] li={li} pos={} entry={idx_new} head={:?} sum={sum:.6}",
6630 pos0 + t,
6631 &v[..4.min(v.len())]
6632 );
6633 }
6634 }
6635 }
6636 }
6637 }
6638
6639 let mut scratch = HcScratch::new(cfg);
6641 argmax_out.clear();
6642 logits_out.clear();
6643 logits_out.resize(b * cfg.vocab, 0.0);
6644 let mut head_in = vec![0.0f32; b * dim];
6645 let mut host_steps: Vec<(usize, Vec<HostLayerSnap>)> = Vec::new();
6646 host_tail_walk_batch(
6647 g,
6648 layers,
6649 cfg,
6650 st,
6651 device_end,
6652 &mut states,
6653 ids,
6654 pos0,
6655 b,
6656 inv_freq,
6657 &mut scratch,
6658 pool,
6659 Some(&mut host_steps),
6660 );
6661 txn.host_steps = host_steps;
6662 for t in 0..b {
6663 let state = &states[t * hc * dim..(t + 1) * hc * dim];
6664 let h = &mut head_in[t * dim..(t + 1) * dim];
6665 hc_head_fold(
6666 state,
6667 &g.hc_head_fn,
6668 g.hc_head_scale,
6669 &g.hc_head_base,
6670 cfg,
6671 pool,
6672 h,
6673 );
6674 rms_weighted(h, &g.norm, cfg.norm_eps);
6675 }
6676 let batch_head = std::env::var("CMF_DSV4_SPEC_BATCH_HEAD").is_ok_and(|v| v != "0");
6681 let head_gpu = batch_head && g.head.model_idx().is_some_and(|hi| {
6682 let model = layers[0].experts.first().and_then(|e| e.w1.model_arc());
6683 model.is_some_and(|m| {
6684 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
6685 &m, hi, &head_in, b, cfg.vocab, dim, logits_out,
6686 )
6687 })
6688 });
6689 for t in 0..b {
6690 if !head_gpu {
6691 let h = &head_in[t * dim..(t + 1) * dim];
6692 g.head
6693 .matvec(h, &mut logits_out[t * cfg.vocab..(t + 1) * cfg.vocab], pool);
6694 }
6695 let row = &logits_out[t * cfg.vocab..(t + 1) * cfg.vocab];
6696 let mut best = 0usize;
6697 for v in 1..cfg.vocab {
6698 if row[v] > row[best] {
6699 best = v;
6700 }
6701 }
6702 argmax_out.push(best as u32);
6703 }
6704 walked_out.clear();
6705 walked_out.extend_from_slice(&states);
6706 st.pos = pos0 + b;
6707 if spec_time {
6708 eprintln!(
6709 "verify: тень+сид+цепочка {:.1} мс, хвост+голова {:.1} мс",
6710 t_chain.as_secs_f64() * 1e3,
6711 (t0.elapsed() - t_chain).as_secs_f64() * 1e3,
6712 );
6713 }
6714 Some(txn)
6715}
6716
6717#[cfg(feature = "gpu")]
6725pub fn dsv4_spec_finish(
6726 g: &Dsv4Globals,
6727 layers: &[Dsv4Layer],
6728 cfg: &Dsv4Cfg,
6729 st: &mut Dsv4State,
6730 mut txn: Dsv4SpecTxn,
6731 accepted: usize,
6732 ids: &[u32],
6733 inv_freq: &[f32],
6734 pool: Option<&crate::pool::Pool>,
6735) -> bool {
6736 macro_rules! sfail {
6737 ($($t:tt)*) => {{
6738 if std::env::var("CMF_DSV4_SPEC_DEBUG").is_ok() {
6739 eprintln!("spec_finish: {}", format_args!($($t)*));
6740 }
6741 return false;
6742 }};
6743 }
6744 let b = txn.batch;
6745 let k = accepted.min(b);
6746 let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
6747 let win_metas: Vec<(usize, usize, usize, usize)> = (0..txn.gpu_end)
6750 .map(|li| (li, txn.dev_filled[li], cfg.window, hd))
6751 .collect();
6752 if !crate::gpu_wgpu::dsv4_spec_commit_windows(st.kv_id, &win_metas, b, k) {
6753 sfail!("коммит окон");
6754 }
6755 if k == b {
6756 return true;
6759 }
6760 let Some(sh) = txn.shadow.take() else {
6762 sfail!("нет тени")
6763 };
6764 if !crate::gpu_wgpu::dsv4_spec_restore(&sh) {
6765 sfail!("restore");
6766 }
6767 let Some(model) = layers[0].experts.first().and_then(|e| e.w1.model_arc()) else {
6768 sfail!("нет модели");
6769 };
6770 let mut plan: Vec<(usize, crate::gpu_wgpu::Dsv4Prep)> = Vec::new();
6771 let mut freqs_own: Vec<&[f32]> = Vec::new();
6772 for li in 0..txn.gpu_end {
6773 let l = &layers[li];
6774 let Some(wkv) = l.wkv.model_idx() else {
6775 sfail!("wkv слоя {li}")
6776 };
6777 let comp = match &l.compressor {
6778 None => None,
6779 Some(cp) => {
6780 let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
6781 sfail!("компрессор слоя {li}");
6782 };
6783 Some((
6784 crate::gpu_wgpu::Dsv4CompW {
6785 wkv: a,
6786 wgate: bx,
6787 norm: &cp.norm,
6788 ape: &cp.ape,
6789 },
6790 crate::gpu_wgpu::Dsv4CompGeom {
6791 width: cp.wkv.rows(),
6792 hidden: dim,
6793 ratio: cp.ratio,
6794 overlap: cp.overlap,
6795 rope_dim: cfg.rope_head_dim,
6796 eps: cfg.norm_eps,
6797 },
6798 ))
6799 }
6800 };
6801 let ix = match &l.indexer {
6802 None => None,
6803 Some(ixr) => {
6804 let cp = &ixr.compressor;
6805 let (Some(a), Some(bx), Some(qb), Some(wp)) = (
6806 cp.wkv.model_idx(),
6807 cp.wgate.model_idx(),
6808 ixr.wq_b.model_idx(),
6809 ixr.weights_proj.model_idx(),
6810 ) else {
6811 sfail!("индексер слоя {li}");
6812 };
6813 let ih = ixr.weights_proj.rows();
6814 Some((
6815 crate::gpu_wgpu::Dsv4CompW {
6816 wkv: a,
6817 wgate: bx,
6818 norm: &cp.norm,
6819 ape: &cp.ape,
6820 },
6821 crate::gpu_wgpu::Dsv4CompGeom {
6822 width: cp.wkv.rows(),
6823 hidden: dim,
6824 ratio: cp.ratio,
6825 overlap: cp.overlap,
6826 rope_dim: cfg.rope_head_dim,
6827 eps: cfg.norm_eps,
6828 },
6829 crate::gpu_wgpu::Dsv4IxW {
6830 wq_b: qb,
6831 weights_proj: wp,
6832 },
6833 crate::gpu_wgpu::Dsv4IxGeom {
6834 ih,
6835 idim: ixr.wq_b.rows() / ih.max(1),
6836 q_lora: cfg.q_lora_rank,
6837 hidden: dim,
6838 rope_dim: cfg.rope_head_dim,
6839 eps: cfg.norm_eps,
6840 top_k: cfg.index_topk,
6841 window: cfg.window,
6842 },
6843 ))
6844 }
6845 };
6846 let ew_c = comp.as_ref().map_or(
6847 0,
6848 |(_, cg)| {
6849 if cg.overlap { cg.width / 2 } else { cg.width }
6850 },
6851 );
6852 let ew_i = ix.as_ref().map_or(
6853 0,
6854 |(_, cg, _, _)| {
6855 if cg.overlap { cg.width / 2 } else { cg.width }
6856 },
6857 );
6858 let prep = crate::gpu_wgpu::Dsv4Prep {
6859 wkv,
6860 kv_norm: &l.kv_norm,
6861 comp,
6862 ix,
6863 filled: txn.dev_filled[li],
6864 window: cfg.window,
6865 n_comp: txn.dev_n_comp[li],
6866 n_ix: txn.dev_n_ix[li],
6867 comp_dst_off: cfg.window * hd + txn.dev_n_comp[li] * ew_c,
6868 ix_dst_off: txn.dev_n_ix[li] * ew_i,
6869 idx_cap: cfg.window
6870 + if l.indexer.is_some() {
6871 cfg.index_topk
6872 } else {
6873 0
6874 },
6875 };
6876 let fr = if l.compressor.is_some() {
6877 g.inv_freq_compress.as_slice()
6878 } else {
6879 g.inv_freq_window.as_slice()
6880 };
6881 freqs_own.push(if fr.is_empty() { inv_freq } else { fr });
6882 plan.push((li, prep));
6883 }
6884 if !crate::gpu_wgpu::dsv4_spec_replay(
6885 &model,
6886 &plan,
6887 st.kv_id,
6888 txn.pos0,
6889 b,
6890 k,
6891 &freqs_own,
6892 hd,
6893 dim,
6894 cfg.rope_head_dim,
6895 cfg.norm_eps,
6896 true,
6897 ) {
6898 sfail!("replay k={k}");
6899 }
6900 let advanced = |ratio: usize| -> usize {
6902 if ratio == 0 {
6903 return 0;
6904 }
6905 (0..k).filter(|t| (txn.pos0 + t + 1) % ratio == 0).count()
6906 };
6907 for li in 0..txn.gpu_end {
6908 let l = &layers[li];
6909 st.dev_filled[li] = (txn.dev_filled[li] + k).min(cfg.window);
6910 let ac = l.compressor.as_ref().map_or(0, |cp| advanced(cp.ratio));
6911 let ai = l
6912 .indexer
6913 .as_ref()
6914 .map_or(0, |ix| advanced(ix.compressor.ratio));
6915 st.dev_n_comp[li] = txn.dev_n_comp[li] + ac;
6916 st.dev_n_ix[li] = txn.dev_n_ix[li] + ai;
6917 note_compressed(st.kv_id, li, st.dev_n_comp[li]);
6918 }
6919 if k >= 1 && txn.host_steps.iter().all(|(_, v)| v.len() >= k) && !txn.host_steps.is_empty() {
6922 for (li, v) in &txn.host_steps {
6923 host_restore(st, *li, &v[k - 1]);
6924 }
6925 } else {
6926 for (li, snap) in &txn.host {
6927 host_restore(st, *li, snap);
6928 }
6929 let mut scratch = HcScratch::new(cfg);
6930 let mut states = txn.states.clone();
6931 host_tail_walk_batch(
6932 g,
6933 layers,
6934 cfg,
6935 st,
6936 txn.gpu_end,
6937 &mut states[..k * hc * dim],
6938 ids,
6939 txn.pos0,
6940 k,
6941 inv_freq,
6942 &mut scratch,
6943 pool,
6944 None,
6945 );
6946 }
6947 st.pos = txn.pos0 + k;
6948 true
6949}
6950
6951pub fn forward_chunk(
6952 g: &Dsv4Globals,
6953 layers: &[Dsv4Layer],
6954 cfg: &Dsv4Cfg,
6955 st: &mut Dsv4State,
6956 ids: &[u32],
6957 pos0: usize,
6958 inv_freq: &[f32],
6959 pool: Option<&crate::pool::Pool>,
6960 logits: &mut Vec<f32>,
6961 want_logits: bool,
6962) {
6963 let bs = batch_prefill();
6964 if bs > 1 {
6965 let mut i = 0;
6972 if !st.dev_owned && !ids.is_empty() {
6973 st.pos = pos0;
6974 forward_token_inner(
6975 g,
6976 layers,
6977 cfg,
6978 st,
6979 ids[0],
6980 inv_freq,
6981 pool,
6982 logits,
6983 ids.len() == 1,
6984 );
6985 i = 1;
6986 }
6987 while i < ids.len() {
6988 let end = (i + bs).min(ids.len());
6989 st.pos = pos0 + i;
6990 if !forward_chunk_batched(
6991 g,
6992 layers,
6993 cfg,
6994 st,
6995 &ids[i..end],
6996 pos0 + i,
6997 inv_freq,
6998 pool,
6999 logits,
7000 want_logits && end == ids.len(),
7001 ) {
7002 break;
7003 }
7004 i = end;
7005 }
7006 if i == ids.len() {
7007 return;
7008 }
7009 for (k, &id) in ids.iter().enumerate().skip(i) {
7011 st.pos = pos0 + k;
7012 let last = want_logits && k + 1 == ids.len();
7013 forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
7014 }
7015 return;
7016 }
7017 for (i, &id) in ids.iter().enumerate() {
7018 st.pos = pos0 + i;
7019 let last = want_logits && i + 1 == ids.len();
7020 forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
7021 }
7022}
7023
7024pub fn forward_token(
7025 g: &Dsv4Globals,
7026 layers: &[Dsv4Layer],
7027 cfg: &Dsv4Cfg,
7028 st: &mut Dsv4State,
7029 token_id: u32,
7030 inv_freq: &[f32],
7031 pool: Option<&crate::pool::Pool>,
7032 logits: &mut Vec<f32>,
7033) {
7034 forward_token_inner(g, layers, cfg, st, token_id, inv_freq, pool, logits, true);
7035}
7036
7037#[allow(clippy::too_many_arguments)]
7038fn forward_token_inner(
7039 g: &Dsv4Globals,
7040 layers: &[Dsv4Layer],
7041 cfg: &Dsv4Cfg,
7042 st: &mut Dsv4State,
7043 token_id: u32,
7044 inv_freq: &[f32],
7045 pool: Option<&crate::pool::Pool>,
7046 logits: &mut Vec<f32>,
7047 want_logits: bool,
7049) {
7050 let _t_all = prof::on().then(std::time::Instant::now);
7051 let _all_guard = Charge(_t_all, &prof::ALL_NS);
7052 let (hc, dim) = (cfg.hc_mult, cfg.dim);
7053
7054 let mut emb = vec![0.0f32; dim];
7056 g.embed.row_f32(token_id as usize, &mut emb);
7057 let mut state = vec![0.0f32; hc * dim];
7058 for j in 0..hc {
7059 state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
7060 }
7061
7062 let mut scratch = HcScratch::new(cfg);
7063 let mut dump: Vec<String> = Vec::new();
7064 if dump_path().is_some() {
7065 dump.push(format!("\"embed\":{}", vec_json(&emb)));
7066 PICKED.with(|p| p.borrow_mut().clear());
7067 BODY.with(|b| b.borrow_mut().clear());
7068 dump.push(",\"layers\":[".into());
7069 }
7070 if trace_on() {
7071 eprintln!(
7072 "[dsv4] tok={token_id} pos={} embed rms={:.5}",
7073 st.pos,
7074 rms_of(&emb)
7075 );
7076 }
7077 #[cfg(feature = "gpu")]
7079 let layer_frames = gpu_layer_enabled()
7080 && dsv4_layer_loop(
7081 &mut state,
7082 layers,
7083 g,
7084 cfg,
7085 st,
7086 token_id,
7087 inv_freq,
7088 pool,
7089 &mut scratch,
7090 );
7091 #[cfg(not(feature = "gpu"))]
7092 let layer_frames = false;
7093
7094 #[cfg(feature = "gpu")]
7101 let hc_dev = hc_on_device()
7102 && !layer_frames
7103 && gpu_attn_enabled()
7104 && gpu_moe2_enabled()
7105 && dump_path().is_none();
7106 #[cfg(not(feature = "gpu"))]
7107 let hc_dev = false;
7108 #[cfg(feature = "gpu")]
7115 let two_frame_done = hc_dev
7116 && dsv4_two_frame_loop(
7117 &mut state,
7118 layers,
7119 g,
7120 cfg,
7121 st,
7122 token_id,
7123 inv_freq,
7124 pool,
7125 &mut scratch,
7126 );
7127 #[cfg(not(feature = "gpu"))]
7128 let two_frame_done = false;
7129 if !two_frame_done {
7130 for (li, l) in layers.iter().enumerate() {
7131 if layer_frames {
7132 break;
7133 }
7134 hc_block(
7136 &mut state,
7137 &l.hc_attn_fn,
7138 &l.hc_attn_scale,
7139 &l.hc_attn_base,
7140 &l.attn_norm,
7141 cfg,
7142 &mut scratch,
7143 pool,
7144 |folded, out| {
7145 if dump_path().is_some() {
7146 BODY.with(|b| b.borrow_mut().push(vec_json(folded)));
7149 }
7150 let freqs = if l.compressor.is_some() {
7152 &g.inv_freq_compress
7153 } else {
7154 &g.inv_freq_window
7155 };
7156 let freqs = if freqs.is_empty() {
7157 inv_freq
7158 } else {
7159 freqs.as_slice()
7160 };
7161 attention_step(folded, l, cfg, st, li, freqs, pool, None, out);
7162 if dump_path().is_some() {
7163 BODY.with(|b| b.borrow_mut().push(vec_json(out)));
7164 }
7165 },
7166 );
7167 if dump_path().is_some() {
7168 dump.push(format!(
7171 "{}{}",
7172 if li == 0 { "" } else { "," },
7173 vec_json(&state)
7174 ));
7175 }
7176 let _t_hc2 = prof::on().then(std::time::Instant::now);
7178 hc_block(
7179 &mut state,
7180 &l.hc_ffn_fn,
7181 &l.hc_ffn_scale,
7182 &l.hc_ffn_base,
7183 &l.ffn_norm,
7184 cfg,
7185 &mut scratch,
7186 pool,
7187 |folded, out| moe_step(folded, l, cfg, token_id, li, pool, out),
7188 );
7189 if let Some(t) = _t_hc2 {
7190 prof::HC_NS.fetch_add(
7193 t.elapsed().as_nanos() as u64,
7194 std::sync::atomic::Ordering::Relaxed,
7195 );
7196 }
7197 if dump_path().is_some() {
7198 dump.push(format!(",{}", vec_json(&state)));
7199 }
7200 if trace_on() && (st.pos % 64 == 0 || st.pos == 199) {
7201 eprintln!(
7202 "[dsv4] кеши слоя {li}: окно={} сжатых={} индекс={} (ratio={:?})",
7203 st.window[li].len() / cfg.head_dim.max(1),
7204 st.compressed[li].len() / cfg.head_dim.max(1),
7205 st.index_kv[li].len().max(1) / 128,
7206 l.compressor.as_ref().map(|c| (c.ratio, c.overlap)),
7207 );
7208 }
7209 if trace_on() {
7210 let bad = state.iter().filter(|v| !v.is_finite()).count();
7211 eprintln!(
7212 "[dsv4] layer {li:>2}: rms={:.5}{}",
7213 rms_of(&state),
7214 if bad > 0 {
7215 format!(" NON-FINITE x{bad}")
7216 } else {
7217 String::new()
7218 }
7219 );
7220 }
7221 dspark_note(li, &state, cfg);
7222 }
7223 }
7224 st.pos += 1;
7225
7226 let mut h = vec![0.0f32; dim];
7228 hc_head_fold(
7229 &state,
7230 &g.hc_head_fn,
7231 g.hc_head_scale,
7232 &g.hc_head_base,
7233 cfg,
7234 pool,
7235 &mut h,
7236 );
7237 if !want_logits {
7238 logits.clear();
7239 return;
7240 }
7241 let _t_head = prof::on().then(std::time::Instant::now);
7242 rms_weighted(&mut h, &g.norm, cfg.norm_eps);
7243 logits.clear();
7244 logits.resize(g.head.rows(), 0.0);
7245 g.head.matvec(&h, logits, pool);
7246 if let Some(t) = _t_head {
7247 prof::HEAD_NS.fetch_add(
7248 t.elapsed().as_nanos() as u64,
7249 std::sync::atomic::Ordering::Relaxed,
7250 );
7251 }
7252 if dump_path().is_some() {
7253 dump.push("]".into());
7254 let picked = PICKED.with(|p| {
7255 p.borrow()
7256 .iter()
7257 .map(|v| {
7258 format!(
7259 "[{}]",
7260 v.iter()
7261 .map(|e| e.to_string())
7262 .collect::<Vec<_>>()
7263 .join(",")
7264 )
7265 })
7266 .collect::<Vec<_>>()
7267 .join(",")
7268 });
7269 dump.push(format!(",\"experts\":[{picked}]"));
7270 let body = BODY.with(|b| b.borrow().join(","));
7271 dump.push(format!(",\"attn_io\":[{body}]"));
7272 dump_line(&format!(
7273 "{{\"tok\":{token_id},\"pos\":{},{},\"head\":{},\"logits\":{}}}",
7274 st.pos - 1,
7275 dump.join(""),
7276 vec_json(&h),
7277 vec_json(logits)
7278 ));
7279 }
7280 if trace_on() {
7281 let (mut top, mut best) = (0usize, f32::NEG_INFINITY);
7282 for (i, &v) in logits.iter().enumerate() {
7283 if v > best {
7284 best = v;
7285 top = i;
7286 }
7287 }
7288 let lo = logits.iter().cloned().fold(f32::MAX, f32::min);
7289 eprintln!(
7290 "[dsv4] head: rms={:.5} logits[{}..{:.3}] argmax={top}",
7291 rms_of(&h),
7292 format_args!("{lo:.3}"),
7293 best
7294 );
7295 }
7296}
7297
7298pub fn load(
7305 model: &std::sync::Arc<cortiq_core::CmfModel>,
7306 cfg: &Dsv4Cfg,
7307 n_layers: usize,
7308) -> Result<(Dsv4Globals, Vec<Dsv4Layer>), String> {
7309 let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
7310 crate::qtensor::QTensor::from_model(model, name)
7311 };
7312 let f = |name: &str| -> Result<Vec<f32>, String> {
7316 crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
7317 };
7318
7319 let rope_of = |base: f32, yarn: bool| -> Vec<f32> {
7324 if yarn {
7325 crate::attention::yarn_inv_freq(cfg.rope_head_dim, base, 16.0, 65536, 32.0, 1.0)
7326 } else {
7327 crate::attention::rope_inv_freq(cfg.rope_head_dim, base)
7328 }
7329 };
7330 let globals = Dsv4Globals {
7331 inv_freq_compress: rope_of(160_000.0, true),
7332 inv_freq_window: rope_of(10_000.0, false),
7333 embed: q("model.embed_tokens.weight")?,
7334 norm: f("model.norm.weight")?,
7335 head: q("lm_head.weight")?,
7336 hc_head_fn: f("model.hc_head_fn")?,
7337 hc_head_base: f("model.hc_head_base")?,
7338 hc_head_scale: *f("model.hc_head_scale")?
7339 .first()
7340 .ok_or("dsv4: empty hc_head_scale")?,
7341 };
7342
7343 let mut layers = Vec::with_capacity(n_layers);
7344 for li in 0..n_layers {
7345 layers.push(load_layer(
7346 model,
7347 cfg,
7348 &format!("model.layers.{li}"),
7349 Scheme::Main,
7350 )?);
7351 }
7352 #[cfg(feature = "gpu")]
7358 if crate::gpu_wgpu::host_banks_on() {
7359 let sets: Vec<(usize, Vec<(usize, usize, usize)>, bool)> = layers
7361 .iter()
7362 .filter_map(|l| {
7363 let idx3 = |e: &Dsv4Expert| {
7364 Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
7365 };
7366 let mut v: Vec<_> = l.experts.iter().filter_map(idx3).collect();
7367 let first = v.first()?.0;
7368 v.push(idx3(&l.shared)?);
7369 let gu_q2 = l.experts.first()?.w1.model_dtype()
7370 == Some(cortiq_core::TensorDtype::Q2TiledP);
7371 Some((first, v, gu_q2))
7372 })
7373 .collect();
7374 let m2 = model.clone();
7375 let (inter, dim) = (cfg.moe_inter, cfg.dim);
7376 std::thread::spawn(move || {
7377 for (first, v, gu_q2) in sets {
7378 crate::gpu_wgpu::dsv4_host_bank_build(&m2, first, &v, inter, dim, gu_q2);
7379 }
7380 tracing::info!("host banks: built");
7381 });
7382 }
7383 #[cfg(feature = "gpu")]
7384 {
7385 let masks: Vec<Option<Vec<bool>>> = layers.iter().map(|l| l.mask.clone()).collect();
7386 crate::gpu_wgpu::prefetch_tier(model, &|name: &str| {
7387 let Some(i) = name.find(".experts.") else {
7388 return false;
7389 };
7390 let rest = &name[i + 9..];
7391 let e: usize = match rest[..rest.find('.').unwrap_or(rest.len())].parse() {
7392 Ok(v) => v,
7393 Err(_) => return false,
7394 };
7395 let li: usize = {
7396 let Some(j) = name.find("layers.") else { return false };
7397 let r = &name[j + 7..];
7398 match r[..r.find('.').unwrap_or(r.len())].parse() {
7399 Ok(v) => v,
7400 Err(_) => return false,
7401 }
7402 };
7403 match masks.get(li).and_then(|m| m.as_ref()) {
7404 Some(m) => m.get(e).copied().unwrap_or(false),
7405 None => true,
7406 }
7407 });
7408 }
7409 Ok((globals, layers))
7410}
7411
7412#[derive(Clone, Copy, PartialEq, Eq, Debug)]
7419pub enum Scheme {
7420 Main,
7421 Mtp,
7422}
7423
7424impl Scheme {
7425 fn attn(self) -> &'static str {
7426 match self {
7427 Scheme::Main => "self_attn",
7428 Scheme::Mtp => "attn",
7429 }
7430 }
7431 fn attn_norm(self) -> &'static str {
7432 match self {
7433 Scheme::Main => "input_layernorm.weight",
7434 Scheme::Mtp => "attn_norm.weight",
7435 }
7436 }
7437 fn ffn_norm(self) -> &'static str {
7438 match self {
7439 Scheme::Main => "post_attention_layernorm.weight",
7440 Scheme::Mtp => "ffn_norm.weight",
7441 }
7442 }
7443 fn mlp(self) -> &'static str {
7444 match self {
7445 Scheme::Main => "mlp",
7446 Scheme::Mtp => "ffn",
7447 }
7448 }
7449 fn gate_bias(self) -> &'static str {
7452 match self {
7453 Scheme::Main => "expert_bias",
7454 Scheme::Mtp => "gate.bias",
7455 }
7456 }
7457 fn shared(self) -> &'static str {
7458 match self {
7459 Scheme::Main => "shared_expert",
7460 Scheme::Mtp => "shared_experts",
7461 }
7462 }
7463 fn w(self, i: u8) -> &'static str {
7465 match (self, i) {
7466 (Scheme::Main, 1) => "gate_proj.weight",
7467 (Scheme::Main, 2) => "down_proj.weight",
7468 (Scheme::Main, _) => "up_proj.weight",
7469 (Scheme::Mtp, 1) => "w1.weight",
7470 (Scheme::Mtp, 2) => "w2.weight",
7471 (Scheme::Mtp, _) => "w3.weight",
7472 }
7473 }
7474}
7475
7476pub fn load_layer(
7478 model: &std::sync::Arc<cortiq_core::CmfModel>,
7479 cfg: &Dsv4Cfg,
7480 p: &str,
7481 s: Scheme,
7482) -> Result<Dsv4Layer, String> {
7483 let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
7484 crate::qtensor::QTensor::from_model(model, name)
7485 };
7486 let f = |name: &str| -> Result<Vec<f32>, String> {
7487 crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
7488 };
7489 let opt_f = |name: &str| -> Option<Vec<f32>> { f(name).ok() };
7490 let at = s.attn();
7491 let ml = s.mlp();
7492 {
7493 let scale3 = |name: &str| -> Result<[f32; 3], String> {
7494 let v = f(name)?;
7495 if v.len() < 3 {
7496 return Err(format!("{name}: expected 3 scales, got {}", v.len()));
7497 }
7498 Ok([v[0], v[1], v[2]])
7499 };
7500 let compressor = match q(&format!("{p}.{at}.compressor.wkv.weight")) {
7503 Ok(wkv) => {
7504 let ape = f(&format!("{p}.{at}.compressor.ape"))?;
7505 let width = wkv.rows();
7508 let ratio = (ape.len() / width.max(1)).max(1);
7509 Some(Dsv4Compressor {
7510 wkv,
7511 wgate: q(&format!("{p}.{at}.compressor.wgate.weight"))?,
7512 norm: f(&format!("{p}.{at}.compressor.norm.weight"))?,
7513 ape,
7514 ratio,
7515 overlap: ratio == 4,
7516 })
7517 }
7518 Err(_) => None,
7519 };
7520 let indexer = match q(&format!("{p}.{at}.indexer.wq_b.weight")) {
7521 Ok(wq_b) => {
7522 let ape = f(&format!("{p}.{at}.indexer.compressor.ape"))?;
7523 let cwkv = q(&format!("{p}.{at}.indexer.compressor.wkv.weight"))?;
7524 let width = cwkv.rows();
7525 let ratio = (ape.len() / width.max(1)).max(1);
7526 Some(Dsv4Indexer {
7527 wq_b,
7528 weights_proj: q(&format!("{p}.{at}.indexer.weights_proj.weight"))?,
7529 compressor: Dsv4Compressor {
7530 wkv: cwkv,
7531 wgate: q(&format!("{p}.{at}.indexer.compressor.wgate.weight"))?,
7532 norm: f(&format!("{p}.{at}.indexer.compressor.norm.weight"))?,
7533 ape,
7534 ratio,
7535 overlap: ratio == 4,
7536 },
7537 })
7538 }
7539 Err(_) => None,
7540 };
7541
7542 let mut experts = Vec::with_capacity(cfg.n_routed_experts);
7543 for e in 0..cfg.n_routed_experts {
7544 let ep = format!("{p}.{ml}.experts.{e}");
7545 experts.push(Dsv4Expert {
7546 w1: q(&format!("{ep}.{w}", w = s.w(1)))?,
7547 w2: q(&format!("{ep}.{w}", w = s.w(2)))?,
7548 w3: q(&format!("{ep}.{w}", w = s.w(3)))?,
7549 });
7550 }
7551
7552 Ok(Dsv4Layer {
7553 attn_norm: f(&format!("{p}.{an}", an = s.attn_norm()))?,
7554 ffn_norm: f(&format!("{p}.{fnm}", fnm = s.ffn_norm()))?,
7555 wq_a: q(&format!("{p}.{at}.wq_a.weight"))?,
7556 q_norm: f(&format!("{p}.{at}.q_norm.weight"))?,
7557 wq_b: q(&format!("{p}.{at}.wq_b.weight"))?,
7558 wkv: q(&format!("{p}.{at}.wkv.weight"))?,
7559 kv_norm: f(&format!("{p}.{at}.kv_norm.weight"))?,
7560 wo_a: q(&format!("{p}.{at}.wo_a.weight"))?,
7561 wo_b: q(&format!("{p}.{at}.wo_b.weight"))?,
7562 attn_sink: f(&format!("{p}.{at}.attn_sink"))?,
7563 compressor,
7564 indexer,
7565 hc_attn_fn: f(&format!("{p}.hc_attn_fn"))?,
7566 hc_attn_base: f(&format!("{p}.hc_attn_base"))?,
7567 hc_attn_scale: scale3(&format!("{p}.hc_attn_scale"))?,
7568 hc_ffn_fn: f(&format!("{p}.hc_ffn_fn"))?,
7569 hc_ffn_base: f(&format!("{p}.hc_ffn_base"))?,
7570 hc_ffn_scale: scale3(&format!("{p}.hc_ffn_scale"))?,
7571 gate: q(&format!("{p}.{ml}.gate.weight"))?,
7572 gate_bias: opt_f(&format!("{p}.{ml}.{b}", b = s.gate_bias())),
7575 tid2eid: opt_f(&format!("{p}.{ml}.tid2eid")),
7576 experts,
7577 mask: if model.tensor(&format!("{p}.{ml}.tid2eid")).is_some() {
7578 None
7579 } else {
7580 crate::loader::moe_task_mask(model, &format!("{p}."), cfg.n_routed_experts)
7581 },
7582 shared: Dsv4Expert {
7583 w1: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(1)))?,
7584 w2: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(2)))?,
7585 w3: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(3)))?,
7586 },
7587 })
7588 }
7589}
7590
7591pub struct Dsv4Mtp {
7600 pub layer: Dsv4Layer,
7601 pub main_proj: Option<crate::qtensor::QTensor>,
7605 pub main_norm: Option<Vec<f32>>,
7606 pub norm: Option<Vec<f32>>,
7608 pub hc_head_fn: Option<Vec<f32>>,
7609 pub hc_head_base: Option<Vec<f32>>,
7610 pub hc_head_scale: Option<f32>,
7611 pub confidence: Option<crate::qtensor::QTensor>,
7612 pub markov_w1: Option<crate::qtensor::QTensor>,
7617 pub markov_w2: Option<crate::qtensor::QTensor>,
7618}
7619
7620pub fn load_mtp(
7624 model: &std::sync::Arc<cortiq_core::CmfModel>,
7625 cfg: &Dsv4Cfg,
7626 max_depth: usize,
7627) -> Vec<Dsv4Mtp> {
7628 let f = |name: &str| -> Option<Vec<f32>> {
7629 crate::loader::load_f32(model, name, &crate::loader::Overlay::None).ok()
7630 };
7631 let mut out = Vec::new();
7632 for d in 0..max_depth {
7633 let p = format!("model.mtp.{d}");
7634 if model.tensor(&format!("{p}.attn.wq_a.weight")).is_none() {
7638 break;
7639 }
7640 let layer = match load_layer(model, cfg, &p, Scheme::Mtp) {
7641 Ok(l) => l,
7642 Err(e) => {
7643 eprintln!("MTP {d}: пропущен, {e}");
7644 break;
7645 }
7646 };
7647 out.push(Dsv4Mtp {
7648 layer,
7649 main_proj: crate::qtensor::QTensor::from_model(model, &format!("{p}.main_proj.weight"))
7650 .ok(),
7651 main_norm: f(&format!("{p}.main_norm.weight")),
7652 norm: f(&format!("{p}.norm.weight")),
7653 hc_head_fn: f(&format!("{p}.hc_head_fn")),
7654 hc_head_base: f(&format!("{p}.hc_head_base")),
7655 hc_head_scale: f(&format!("{p}.hc_head_scale")).and_then(|v| v.first().copied()),
7656 confidence: crate::qtensor::QTensor::from_model(
7657 model,
7658 &format!("{p}.confidence_head.proj.weight"),
7659 )
7660 .ok(),
7661 markov_w1: crate::qtensor::QTensor::from_model(
7662 model,
7663 &format!("{p}.markov_head.markov_w1.weight"),
7664 )
7665 .ok(),
7666 markov_w2: crate::qtensor::QTensor::from_model(
7667 model,
7668 &format!("{p}.markov_head.markov_w2.weight"),
7669 )
7670 .ok(),
7671 });
7672 }
7673 dspark_apply_mask(&mut out);
7674 if !out.is_empty() {
7675 let mp = out
7676 .iter()
7677 .find_map(|m| m.main_proj.as_ref())
7678 .map(|t| format!("[{}, {}]", t.rows(), t.cols()))
7679 .unwrap_or_else(|| "нет".into());
7680 eprintln!(
7681 "MTP: {} стади(я/и/й), main_proj {mp}, экспертов {}, \
7682 голова уверенности {}, марков {}",
7683 out.len(),
7684 out[0].layer.experts.len(),
7685 if out.iter().any(|m| m.confidence.is_some()) {
7686 "есть"
7687 } else {
7688 "нет"
7689 },
7690 if out.iter().any(|m| m.markov_w1.is_some()) {
7691 "есть"
7692 } else {
7693 "нет"
7694 },
7695 );
7696 }
7697 out
7698}
7699
7700#[cfg(test)]
7701mod tests {
7702 use super::*;
7703
7704 fn toy() -> (Dsv4Globals, Vec<Dsv4Layer>, Dsv4Cfg) {
7709 use crate::qtensor::QTensor;
7710 let cfg = Dsv4Cfg {
7711 dim: 32,
7712 n_heads: 4,
7713 head_dim: 8,
7714 rope_head_dim: 4,
7715 q_lora_rank: 16,
7716 o_lora_rank: 16,
7717 o_groups: 2,
7718 hc_mult: 4,
7719 hc_sinkhorn_iters: 20,
7720 hc_eps: 1e-6,
7721 norm_eps: 1e-6,
7722 n_routed_experts: 8,
7723 top_k: 2,
7724 moe_inter: 16,
7725 route_scale: 1.0,
7726 swiglu_limit: 10.0,
7727 window: 6,
7728 index_topk: 8,
7729 vocab: 24,
7730 };
7731 let w = |n: usize, seed: usize| -> Vec<f32> {
7734 (0..n)
7735 .map(|i| (((i * 7 + seed * 13) % 101) as f32 / 101.0 - 0.5) * 0.3)
7736 .collect()
7737 };
7738 let t = |rows: usize, cols: usize, seed: usize| {
7739 QTensor::from_f32(w(rows * cols, seed), rows, cols)
7740 };
7741 let ones = |n: usize| vec![1.0f32; n];
7742
7743 let (dim, hc) = (cfg.dim, cfg.hc_mult);
7744 let q_width = cfg.n_heads * cfg.head_dim;
7747 let kv_width = cfg.head_dim;
7748 let o_per_group = q_width / cfg.o_groups;
7749 let mut layers = Vec::new();
7750 for li in 0..2 {
7751 let experts: Vec<Dsv4Expert> = (0..cfg.n_routed_experts)
7752 .map(|e| Dsv4Expert {
7753 w1: t(cfg.moe_inter, dim, 40 + e + li * 8),
7754 w2: t(dim, cfg.moe_inter, 60 + e + li * 8),
7755 w3: t(cfg.moe_inter, dim, 80 + e + li * 8),
7756 })
7757 .collect();
7758 layers.push(Dsv4Layer {
7761 attn_norm: ones(dim),
7762 ffn_norm: ones(dim),
7763 wq_a: t(cfg.q_lora_rank, dim, 1 + li),
7764 q_norm: ones(cfg.q_lora_rank),
7765 wq_b: t(q_width, cfg.q_lora_rank, 3 + li),
7766 wkv: t(kv_width, dim, 5 + li),
7767 kv_norm: ones(kv_width),
7768 wo_a: t(cfg.o_groups * cfg.o_lora_rank, o_per_group, 7 + li),
7769 wo_b: t(dim, cfg.o_groups * cfg.o_lora_rank, 9 + li),
7770 attn_sink: vec![0.1; cfg.n_heads],
7771 compressor: if li == 1 {
7774 Some(Dsv4Compressor {
7775 wkv: t(2 * kv_width, dim, 11),
7776 wgate: t(2 * kv_width, dim, 13),
7777 norm: ones(kv_width),
7778 ape: vec![0.01; 4 * 2 * kv_width],
7779 ratio: 4,
7780 overlap: true,
7781 })
7782 } else {
7783 None
7784 },
7785 indexer: if li == 1 {
7786 Some(Dsv4Indexer {
7787 wq_b: t(2 * 16, cfg.q_lora_rank, 41),
7788 weights_proj: t(2, dim, 43),
7789 compressor: Dsv4Compressor {
7790 wkv: t(2 * 16, dim, 45),
7791 wgate: t(2 * 16, dim, 47),
7792 norm: ones(16),
7793 ape: vec![0.01; 4 * 2 * 16],
7794 ratio: 4,
7795 overlap: true,
7796 },
7797 })
7798 } else {
7799 None
7800 },
7801 hc_attn_fn: w((2 + hc) * hc * hc * dim, 15 + li),
7802 hc_attn_base: w((2 + hc) * hc, 17 + li),
7803 hc_attn_scale: [1.0, 1.0, 1.0],
7804 hc_ffn_fn: w((2 + hc) * hc * hc * dim, 19 + li),
7805 hc_ffn_base: w((2 + hc) * hc, 21 + li),
7806 hc_ffn_scale: [1.0, 1.0, 1.0],
7807 gate: t(cfg.n_routed_experts, dim, 23 + li),
7808 gate_bias: if li == 1 {
7809 Some(vec![0.0; cfg.n_routed_experts])
7810 } else {
7811 None
7812 },
7813 tid2eid: if li == 0 {
7814 Some(
7815 (0..cfg.vocab * cfg.top_k)
7816 .map(|i| (i % cfg.n_routed_experts) as f32)
7817 .collect(),
7818 )
7819 } else {
7820 None
7821 },
7822 experts,
7823 mask: None,
7824 shared: Dsv4Expert {
7825 w1: t(cfg.moe_inter, dim, 25 + li),
7826 w2: t(dim, cfg.moe_inter, 27 + li),
7827 w3: t(cfg.moe_inter, dim, 29 + li),
7828 },
7829 });
7830 }
7831 let inv = |base: f32| -> Vec<f32> {
7832 (0..cfg.rope_head_dim / 2)
7833 .map(|i| 1.0 / base.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
7834 .collect()
7835 };
7836 let g = Dsv4Globals {
7837 inv_freq_compress: inv(160000.0),
7838 inv_freq_window: inv(10000.0),
7839 embed: t(cfg.vocab, dim, 31),
7840 norm: ones(dim),
7841 head: t(cfg.vocab, dim, 33),
7842 hc_head_fn: w(hc * hc * dim, 35),
7843 hc_head_base: w(hc, 37),
7844 hc_head_scale: 1.0,
7845 };
7846 (g, layers, cfg)
7847 }
7848
7849 #[test]
7854 fn forward_token_decodes_a_sequence_without_falling_over() {
7855 let (g, layers, cfg) = toy();
7856 let mut st = Dsv4State::new(layers.len());
7857 let inv_freq: Vec<f32> = (0..cfg.rope_head_dim / 2)
7858 .map(|i| 1.0 / 10000f32.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
7859 .collect();
7860 let mut logits = Vec::new();
7861
7862 let mut first: Option<Vec<f32>> = None;
7865 for (step, tok) in [3u32, 7, 1, 9, 4, 2, 8, 5, 6, 0].into_iter().enumerate() {
7866 forward_token(
7867 &g,
7868 &layers,
7869 &cfg,
7870 &mut st,
7871 tok,
7872 &inv_freq,
7873 None,
7874 &mut logits,
7875 );
7876 assert_eq!(logits.len(), cfg.vocab, "step {step}: logit count");
7877 assert!(
7878 logits.iter().all(|v| v.is_finite()),
7879 "step {step}: non-finite logit — {logits:?}"
7880 );
7881 let spread = logits.iter().cloned().fold(f32::MIN, f32::max)
7884 - logits.iter().cloned().fold(f32::MAX, f32::min);
7885 assert!(spread > 1e-6, "step {step}: logits are flat ({spread})");
7886 if step == 0 {
7887 first = Some(logits.clone());
7888 }
7889 assert_eq!(st.pos, step + 1, "position bookkeeping");
7890 }
7891
7892 assert!(!st.window[0].is_empty(), "sliding window never filled");
7895 for (li, w) in st.window.iter().enumerate() {
7897 assert!(
7898 w.len() / cfg.head_dim <= cfg.window,
7899 "layer {li}: window holds {} positions, cap is {}",
7900 w.len() / cfg.head_dim,
7901 cfg.window
7902 );
7903 }
7904 assert!(
7905 !st.compressed[1].is_empty(),
7906 "compressor layer produced no compressed KV in 10 tokens"
7907 );
7908 assert_eq!(
7912 st.compressed[1].len() / cfg.head_dim,
7913 2,
7914 "expected two folds in ten tokens at ratio 4"
7915 );
7916 assert!(
7917 !st.prev_kv[1].is_empty(),
7918 "the overlapping compressor never kept a previous window"
7919 );
7920 for (li, l) in layers.iter().enumerate() {
7925 if l.indexer.is_some() {
7926 assert!(
7927 !st.index_kv[li].is_empty(),
7928 "layer {li} has an indexer but its cache stayed empty"
7929 );
7930 }
7931 }
7932
7933 let mut fresh = Dsv4State::new(layers.len());
7936 let mut relogits = Vec::new();
7937 forward_token(
7938 &g,
7939 &layers,
7940 &cfg,
7941 &mut fresh,
7942 3,
7943 &inv_freq,
7944 None,
7945 &mut relogits,
7946 );
7947 assert_eq!(
7948 relogits,
7949 first.unwrap(),
7950 "the same token from a fresh state must reproduce exactly"
7951 );
7952 }
7953
7954 #[test]
7958 fn swiglu_limit_clamps_up_both_ways_and_gate_only_from_above() {
7959 let inter = 4;
7960 let gate_src = [-50.0f32, 50.0, 1.0, -1.0];
7962 let up_src = [50.0f32, -50.0, 1.0, -1.0];
7963 let limit = 10.0f32;
7964 let mut got = vec![0.0f32; inter];
7965 expert_swiglu(
7966 &[0.0],
7967 &|_, d| d.copy_from_slice(&gate_src),
7968 &|_, d| d.copy_from_slice(&up_src),
7969 &|src, d| d.copy_from_slice(src),
7970 inter,
7971 1.0,
7972 limit,
7973 &mut got,
7974 );
7975 let silu = |g: f32| g / (1.0 + (-g).exp());
7976 let want = [
7978 silu(-50.0) * limit,
7979 silu(limit) * -limit,
7980 silu(1.0) * 1.0,
7981 -silu(-1.0),
7982 ];
7983 for (i, w) in want.iter().enumerate() {
7984 assert!(
7985 (got[i] - w).abs() < 1e-5,
7986 "lane {i}: got {} want {w}",
7987 got[i]
7988 );
7989 }
7990 let mut raw = vec![0.0f32; inter];
7992 expert_swiglu(
7993 &[0.0],
7994 &|_, d| d.copy_from_slice(&gate_src),
7995 &|_, d| d.copy_from_slice(&up_src),
7996 &|src, d| d.copy_from_slice(src),
7997 inter,
7998 1.0,
7999 0.0,
8000 &mut raw,
8001 );
8002 assert!(
8003 (raw[1] - silu(50.0) * -50.0).abs() < 1e-3,
8004 "limit 0 must not clamp"
8005 );
8006 }
8007
8008 #[test]
8013 fn grouped_projection_is_identical_with_and_without_a_pool() {
8014 let (groups, lora, per_group, dim) = (4usize, 128usize, 64usize, 32usize);
8015 let attn: Vec<f32> = (0..groups * per_group)
8016 .map(|i| ((i * 13) as f32 * 0.021).sin())
8017 .collect();
8018 let wo_a: Vec<f32> = (0..groups * lora * per_group)
8019 .map(|i| ((i * 7) as f32 * 0.011).cos())
8020 .collect();
8021 let wo_b: Vec<f32> = (0..dim * groups * lora)
8022 .map(|i| ((i * 5) as f32 * 0.009).sin())
8023 .collect();
8024 let row = |r: usize, x: &[f32], _sc: &mut [f32]| -> f32 {
8025 wo_a[r * per_group..(r + 1) * per_group]
8026 .iter()
8027 .zip(x)
8028 .map(|(a, b)| a * b)
8029 .sum()
8030 };
8031 let project = |mid: &[f32], dst: &mut [f32]| {
8032 for (d, o) in dst.iter_mut().enumerate() {
8033 *o = wo_b[d * mid.len()..(d + 1) * mid.len()]
8034 .iter()
8035 .zip(mid)
8036 .map(|(a, b)| a * b)
8037 .sum();
8038 }
8039 };
8040
8041 let mut serial = vec![0.0f32; dim];
8042 o_project(
8043 &attn,
8044 &row,
8045 per_group,
8046 &project,
8047 groups,
8048 lora,
8049 None,
8050 &mut serial,
8051 );
8052
8053 let pool = crate::pool::Pool::new(4);
8054 let mut pooled = vec![0.0f32; dim];
8055 o_project(
8056 &attn,
8057 &row,
8058 per_group,
8059 &project,
8060 groups,
8061 lora,
8062 Some(&pool),
8063 &mut pooled,
8064 );
8065 assert_eq!(serial, pooled, "the pooled projection diverged");
8066 assert!(
8067 serial.iter().any(|v| v.abs() > 1e-6),
8068 "test data is degenerate"
8069 );
8070 }
8071
8072 #[test]
8073 fn block_grouped_projection_matches_position_walk() {
8074 let (_g, layers, cfg) = toy();
8075 let l = &layers[1];
8076 let b = 5;
8077 let attn_len = cfg.n_heads * cfg.head_dim;
8078 let attn: Vec<f32> = (0..b * attn_len)
8079 .map(|i| ((i * 17) as f32 * 0.013).sin())
8080 .collect();
8081 let mut walked = vec![0.0f32; b * cfg.dim];
8082 for bi in 0..b {
8083 o_project(
8084 &attn[bi * attn_len..(bi + 1) * attn_len],
8085 &|r, x, sc| l.wo_a.row_dot(r, x, sc),
8086 l.wo_a.cols(),
8087 &|mid, dst| l.wo_b.matvec(mid, dst, None),
8088 cfg.o_groups,
8089 cfg.o_lora_rank,
8090 None,
8091 &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
8092 );
8093 }
8094 let mut batched = vec![0.0f32; b * cfg.dim];
8095 o_project_block(
8096 &attn,
8097 b,
8098 &l.wo_a,
8099 &l.wo_b,
8100 cfg.o_groups,
8101 cfg.o_lora_rank,
8102 None,
8103 &mut batched,
8104 );
8105 assert_eq!(batched, walked);
8106 }
8107
8108 #[test]
8109 fn block_moe_matches_position_walk_in_route_order() {
8110 let (_g, layers, cfg) = toy();
8111 let l = &layers[1];
8114 let b = 5;
8115 let xs: Vec<f32> = (0..b * cfg.dim)
8116 .map(|i| ((i * 11) as f32 * 0.019).cos())
8117 .collect();
8118 let ids = [1u32, 2, 3, 4, 5];
8119 let mut walked = vec![0.0f32; b * cfg.dim];
8120 for bi in 0..b {
8121 moe_step(
8122 &xs[bi * cfg.dim..(bi + 1) * cfg.dim],
8123 l,
8124 &cfg,
8125 ids[bi],
8126 1,
8127 None,
8128 &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
8129 );
8130 }
8131 let mut batched = vec![0.0f32; b * cfg.dim];
8132 moe_step_block(&xs, b, l, &cfg, &ids, 1, None, &mut batched);
8133 assert_eq!(batched, walked);
8134 }
8135
8136 #[test]
8142 fn overlapping_compressor_folds_both_windows() {
8143 let (ratio, d) = (2usize, 3usize);
8144 let cur_kv: Vec<f32> = vec![
8147 1.0, 1.0, 1.0, 10.0, 20.0, 30.0, 2.0, 2.0, 2.0, 40.0, 50.0, 60.0, ];
8150 let cur_sc: Vec<f32> = vec![
8152 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 0.0, 100.0, 100.0, 0.0,
8154 ];
8155 let prev_kv: Vec<f32> = vec![
8157 7.0, 8.0, 9.0, 0.0, 0.0, 0.0, 5.0, 6.0, 7.0, 0.0, 0.0, 0.0,
8159 ];
8160 let prev_sc = vec![0.0f32; ratio * 2 * d];
8161
8162 let mut out = vec![0.0f32; d];
8163 compress_window_overlap(&prev_kv, &prev_sc, &cur_kv, &cur_sc, ratio, d, &mut out);
8164 assert!((out[0] - 40.0).abs() < 1e-3, "dim0 = {}", out[0]);
8166 assert!((out[1] - 50.0).abs() < 1e-3, "dim1 = {}", out[1]);
8167 assert!((out[2] - 30.0).abs() < 1e-3, "dim2 = {}", out[2]);
8169
8170 let mut first = vec![0.0f32; d];
8173 compress_window_overlap(&[], &[], &cur_kv, &cur_sc, ratio, d, &mut first);
8174 assert!(
8175 first.iter().all(|v| v.is_finite()),
8176 "first window: {first:?}"
8177 );
8178 assert!((first[0] - 40.0).abs() < 1e-3, "first dim0 = {}", first[0]);
8179
8180 let mut both = vec![0.0f32; d];
8182 let strong_prev = vec![100.0f32; ratio * 2 * d];
8183 compress_window_overlap(
8184 &prev_kv,
8185 &strong_prev,
8186 &cur_kv,
8187 &cur_sc,
8188 ratio,
8189 d,
8190 &mut both,
8191 );
8192 assert!(
8193 (both[0] - 40.0).abs() > 1.0,
8194 "a scored previous window must move the fold, got {}",
8195 both[0]
8196 );
8197 }
8198
8199 #[test]
8205 fn sinkhorn_matches_the_reference_numbers() {
8206 let hc = 4;
8207 let mixes: Vec<f32> = (0..24).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
8208 let base: Vec<f32> = (0..24).map(|i| (i as f32 * 0.11).cos()).collect();
8209 let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
8210 hc_split_sinkhorn(
8211 &mixes,
8212 &[1.0, 1.0, 1.0],
8213 &base,
8214 hc,
8215 20,
8216 1e-6,
8217 &mut pre,
8218 &mut post,
8219 &mut comb,
8220 );
8221 let want_pre = [0.7310596, 0.8888268, 0.9525191, 0.97424865];
8222 let want_post = [1.9600224, 1.9534285, 1.9201256, 1.8160983];
8223 let want_comb = [
8224 0.5996052,
8225 0.282_535_9,
8226 0.09218107,
8227 0.025676856,
8228 0.17564717,
8229 0.22228767,
8230 0.271_745_4,
8231 0.330_318_8,
8232 0.029528176,
8233 0.12206022,
8234 0.32619134,
8235 0.5222193,
8236 0.19521846,
8237 0.37311527,
8238 0.30988118,
8239 0.12178412,
8240 ];
8241 for (i, w) in want_pre.iter().enumerate() {
8242 assert!((pre[i] - w).abs() < 1e-5, "pre[{i}]: {} vs {w}", pre[i]);
8243 }
8244 for (i, w) in want_post.iter().enumerate() {
8245 assert!((post[i] - w).abs() < 1e-5, "post[{i}]: {} vs {w}", post[i]);
8246 }
8247 for (i, w) in want_comb.iter().enumerate() {
8248 assert!((comb[i] - w).abs() < 1e-4, "comb[{i}]: {} vs {w}", comb[i]);
8249 }
8250 }
8251
8252 #[test]
8257 fn sinkhorn_leaves_the_mixing_matrix_doubly_stochastic() {
8258 let hc = 4;
8259 let mix_hc = (2 + hc) * hc;
8260 let mixes: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
8262 let base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.11).cos()).collect();
8263 let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
8264 hc_split_sinkhorn(
8265 &mixes,
8266 &[1.0, 1.0, 1.0],
8267 &base,
8268 hc,
8269 20,
8270 1e-6,
8271 &mut pre,
8272 &mut post,
8273 &mut comb,
8274 );
8275 for j in 0..hc {
8276 let r: f32 = comb[j * hc..(j + 1) * hc].iter().sum();
8277 assert!((r - 1.0).abs() < 2e-3, "row {j} sums to {r}");
8278 let c: f32 = (0..hc).map(|k| comb[k * hc + j]).sum();
8279 assert!((c - 1.0).abs() < 2e-3, "col {j} sums to {c}");
8280 }
8281 assert!(pre.iter().all(|&v| v > 0.0 && v < 1.001));
8283 assert!(post.iter().all(|&v| (0.0..=2.0).contains(&v)));
8284 }
8285
8286 #[test]
8290 fn expand_of_identical_copies_is_a_fixed_point() {
8291 let (hc, dim) = (4usize, 3usize);
8292 let residual: Vec<f32> = std::iter::repeat_n([1.5f32, -2.0, 0.25], hc)
8293 .flatten()
8294 .collect();
8295 let comb = {
8296 vec![0.25f32; hc * hc]
8298 };
8299 let post = vec![0.0f32; hc];
8300 let mut out = vec![0.0f32; hc * dim];
8301 hc_expand(&[0.0; 3], &residual, &post, &comb, hc, dim, &mut out);
8302 for (o, r) in out.iter().zip(&residual) {
8303 assert!((o - r).abs() < 1e-6, "{o} vs {r}");
8304 }
8305 }
8306
8307 #[test]
8311 fn selection_bias_steers_the_choice_but_not_the_weights() {
8312 let scores = [3.0f32, 0.1, 2.0, 0.05];
8313 let bias = [0.0f32, 10.0, 0.0, 0.0];
8314 let (mut idx, mut w) = (Vec::new(), Vec::new());
8315 route(&scores, Some(&bias), 2, 1.5, None, None, &mut idx, &mut w);
8316 assert_eq!(idx[0], 1, "the biased expert must win selection");
8317 assert_eq!(idx[1], 0);
8318 assert!(w[0] < w[1], "biased expert kept its own (small) weight");
8321 let sum: f32 = w.iter().sum();
8322 assert!((sum - 1.5).abs() < 1e-5, "weights renormalize then scale");
8323 }
8324
8325 #[test]
8329 fn attention_sink_drains_weight_without_contributing_output() {
8330 let hd = 2;
8331 let q = [1.0f32, 0.0];
8332 let kv = [1.0f32, 0.0, 0.0, 1.0];
8333 let mut out = vec![0.0f32; hd];
8334 sparse_attend(&q, &kv, &[0, 1], f32::NEG_INFINITY, 1.0, hd, &mut out);
8335 let plain = out.clone();
8336 assert!(plain[0] > plain[1], "the aligned key must dominate");
8337 sparse_attend(&q, &kv, &[0, 1], 20.0, 1.0, hd, &mut out);
8338 assert!(
8339 out[0] < plain[0] * 0.01 && out[1] < plain[1] * 0.01,
8340 "a large sink must drain nearly all the mass: {out:?}"
8341 );
8342 }
8343
8344 #[test]
8347 fn masked_positions_leave_the_denominator_alone() {
8348 let hd = 2;
8349 let q = [1.0f32, 0.0];
8350 let kv = [1.0f32, 0.0, 0.0, 1.0];
8351 let (mut a, mut b) = (vec![0.0f32; hd], vec![0.0f32; hd]);
8352 sparse_attend(&q, &kv, &[0], f32::NEG_INFINITY, 1.0, hd, &mut a);
8353 sparse_attend(
8354 &q,
8355 &kv,
8356 &[0, usize::MAX],
8357 f32::NEG_INFINITY,
8358 1.0,
8359 hd,
8360 &mut b,
8361 );
8362 for (x, y) in a.iter().zip(&b) {
8363 assert!((x - y).abs() < 1e-6, "{x} vs {y}");
8364 }
8365 }
8366
8367 #[test]
8370 fn rope_tail_inverts_itself() {
8371 let inv_freq = [1.0f32, 0.5];
8372 let orig = [9.0f32, 8.0, 1.0, 2.0, 3.0, 4.0];
8373 let mut v = orig;
8374 rope_tail(&mut v, &inv_freq, 7, 4, false);
8375 assert!(v[..2] == orig[..2], "the non-rope head must not move");
8376 assert!(v[2..] != orig[2..], "the tail must actually rotate");
8377 rope_tail(&mut v, &inv_freq, 7, 4, true);
8378 for (a, b) in v.iter().zip(&orig) {
8379 assert!((a - b).abs() < 1e-5, "{a} vs {b}");
8380 }
8381 }
8382
8383 #[test]
8386 fn compressor_pools_the_window_per_dimension() {
8387 let (ratio, width) = (2usize, 2usize);
8388 let kv = [1.0f32, 10.0, 3.0, 20.0];
8389 let score = [0.0f32, 0.0, 0.0, 50.0];
8391 let ape = vec![0.0f32; ratio * width];
8392 let mut out = vec![0.0f32; width];
8393 compress_window(&kv, &score, &ape, ratio, width, &mut out);
8394 assert!(
8395 (out[0] - 2.0).abs() < 1e-5,
8396 "equal scores average: {}",
8397 out[0]
8398 );
8399 assert!(
8400 (out[1] - 20.0).abs() < 1e-3,
8401 "a dominant score wins: {}",
8402 out[1]
8403 );
8404 }
8405
8406 #[test]
8409 fn index_scores_relu_before_weighting() {
8410 let (nh, hd) = (2usize, 2usize);
8411 let q = [1.0f32, 0.0, -1.0, 0.0];
8413 let kv = [1.0f32, 0.0, 0.0, 1.0];
8414 let w = [1.0f32, 1.0];
8415 let mut sc = Vec::new();
8416 index_scores(&q, &kv, &w, nh, hd, 2, 2, None, &mut sc);
8417 assert!(sc[0] > 0.9, "abstention, not veto: {:?}", sc);
8419 }
8420
8421 #[test]
8422 fn index_scores_mask_the_future() {
8423 let (nh, hd) = (1usize, 2usize);
8424 let q = [1.0f32, 0.0];
8425 let kv = [1.0f32, 0.0, 1.0, 0.0, 1.0, 0.0];
8426 let w = [1.0f32];
8427 let mut sc = Vec::new();
8428 index_scores(&q, &kv, &w, nh, hd, 3, 2, None, &mut sc);
8429 assert!(sc[0].is_finite() && sc[1].is_finite());
8430 assert!(sc[2] == f32::NEG_INFINITY, "position 2 is in the future");
8431 let mut idx = Vec::new();
8432 top_k_positions(&sc, 3, &mut idx);
8433 assert_eq!(idx, vec![0, 1], "a masked slot never wins a slot");
8434 }
8435
8436 #[test]
8437 fn top_k_is_deterministic_on_ties() {
8438 let sc = [1.0f32, 1.0, 1.0, 0.0];
8439 let mut idx = Vec::new();
8440 top_k_positions(&sc, 2, &mut idx);
8441 assert_eq!(idx, vec![0, 1], "ties resolve to the lower index");
8442 }
8443
8444 #[test]
8448 fn hc_block_preserves_the_copy_structure_and_applies_the_block() {
8449 let cfg = Dsv4Cfg {
8450 dim: 4,
8451 n_heads: 1,
8452 head_dim: 4,
8453 rope_head_dim: 2,
8454 q_lora_rank: 4,
8455 o_lora_rank: 2,
8456 o_groups: 1,
8457 hc_mult: 4,
8458 hc_sinkhorn_iters: 20,
8459 hc_eps: 1e-6,
8460 norm_eps: 1e-6,
8461 n_routed_experts: 2,
8462 top_k: 1,
8463 moe_inter: 4,
8464 route_scale: 1.0,
8465 swiglu_limit: 10.0,
8466 window: 128,
8467 index_topk: 4,
8468 vocab: 8,
8469 };
8470 let (hc, dim) = (cfg.hc_mult, cfg.dim);
8471 let mix_hc = (2 + hc) * hc;
8472 let hc_fn: Vec<f32> = (0..mix_hc * hc * dim)
8473 .map(|i| ((i % 13) as f32 - 6.0) * 0.05)
8474 .collect();
8475 let hc_base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.2).sin()).collect();
8476 let norm_w = vec![1.0f32; dim];
8477 let mut state: Vec<f32> = (0..hc * dim).map(|i| (i as f32 * 0.3).cos()).collect();
8478 let before = state.clone();
8479 let mut scratch = HcScratch::new(&cfg);
8480 hc_block(
8481 &mut state,
8482 &hc_fn,
8483 &[1.0, 1.0, 1.0],
8484 &hc_base,
8485 &norm_w,
8486 &cfg,
8487 &mut scratch,
8488 None,
8489 |_folded, out: &mut [f32]| out.iter_mut().for_each(|o| *o = 1.0),
8490 );
8491 assert_eq!(state.len(), before.len(), "copy structure must survive");
8492 assert!(state.iter().all(|v| v.is_finite()), "{state:?}");
8493 assert!(
8494 state.iter().zip(&before).any(|(a, b)| (a - b).abs() > 1e-4),
8495 "the block's output has to reach the state"
8496 );
8497 }
8498
8499 #[test]
8500 fn hash_route_reads_the_table_row() {
8501 let table = [7.0f32, 9.0, 1.0, 2.0, 5.0, 6.0];
8503 assert_eq!(hash_route(&table, 3, 2, 0), vec![7, 9]);
8504 assert_eq!(hash_route(&table, 3, 2, 2), vec![5, 6]);
8505 assert_eq!(hash_route(&table, 3, 2, 99), vec![5, 6]);
8507 }
8508
8509 #[test]
8514 fn a_task_mask_restricts_selection_and_renormalizes() {
8515 let scores = [0.1f32, 4.0, 1.0, 9.0];
8517 let (mut idx, mut w) = (Vec::new(), Vec::new());
8518 route(&scores, None, 2, 1.0, None, None, &mut idx, &mut w);
8519 assert_eq!(idx, vec![3, 1], "unmasked: the two best win");
8520 let sum: f32 = w.iter().sum();
8521 assert!((sum - 1.0).abs() < 1e-5, "weights must sum to route_scale");
8522
8523 let mask = [true, false, true, true];
8526 let (mut i2, mut w2) = (Vec::new(), Vec::new());
8527 route(&scores, None, 2, 1.0, None, Some(&mask), &mut i2, &mut w2);
8528 assert_eq!(i2, vec![3, 2], "masked expert must not be selected");
8529 let sum2: f32 = w2.iter().sum();
8530 assert!((sum2 - 1.0).abs() < 1e-5, "masked weights must renormalize");
8531
8532 let tight = [false, false, false, true];
8534 let (mut i3, mut w3) = (Vec::new(), Vec::new());
8535 route(&scores, None, 2, 1.0, None, Some(&tight), &mut i3, &mut w3);
8536 assert_eq!(i3, vec![3]);
8537 assert_eq!(w3.len(), 1);
8538 }
8539
8540 #[test]
8545 fn hash_layers_weight_the_experts_the_table_names() {
8546 let scores = [0.1f32, 0.4, 0.2, 5.0];
8548 let table = vec![0.0f32, 1.0];
8549 let idx_forced = hash_route(&table, 1, 2, 0);
8550 assert_eq!(idx_forced, vec![0, 1]);
8551
8552 let (mut idx, mut w) = (Vec::new(), Vec::new());
8553 route(
8554 &scores,
8555 None,
8556 2,
8557 1.0,
8558 Some(&idx_forced),
8559 None,
8560 &mut idx,
8561 &mut w,
8562 );
8563 assert_eq!(idx, vec![0, 1], "the table must decide the experts");
8564
8565 let sp = |x: f32| (1.0 + x.exp()).ln().sqrt();
8567 let (s0, s1) = (sp(scores[0]), sp(scores[1]));
8568 let tot = s0 + s1;
8569 assert!(
8570 (w[0] - s0 / tot).abs() < 1e-6,
8571 "w[0]={} want {}",
8572 w[0],
8573 s0 / tot
8574 );
8575 assert!(
8576 (w[1] - s1 / tot).abs() < 1e-6,
8577 "w[1]={} want {}",
8578 w[1],
8579 s1 / tot
8580 );
8581
8582 let (mut idx2, mut w2) = (Vec::new(), Vec::new());
8584 route(&scores, None, 2, 1.0, None, None, &mut idx2, &mut w2);
8585 assert_eq!(idx2[0], 3, "without a table the highest score still wins");
8586 }
8587}
8588
8589pub const DSPARK_NOISE_TOKEN: u32 = 128799;
8606pub fn dspark_block() -> usize {
8614 5
8615}
8616
8617#[cfg(feature = "gpu")]
8624fn dspark_native_on() -> bool {
8625 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8626 *ON.get_or_init(|| {
8627 std::env::var("CMF_DSPARK_NATIVE")
8628 .map(|v| v != "0")
8629 .unwrap_or(true)
8632 })
8633}
8634
8635pub fn dspark_verify_k() -> usize {
8636 static K: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
8637 *K.get_or_init(|| {
8638 std::env::var("CMF_DSPARK_VERIFY_K")
8639 .ok()
8640 .and_then(|v| v.parse::<usize>().ok())
8641 .filter(|&n| (1..=DSPARK_BLOCK_MAX).contains(&n))
8642 .unwrap_or(DSPARK_BLOCK_MAX)
8643 })
8644}
8645
8646pub const DSPARK_BLOCK_MAX: usize = 5;
8648
8649pub struct DsparkState {
8652 pub win: Vec<Vec<f32>>,
8654 pub filled: Vec<usize>,
8656 pub main_hidden: Vec<f32>,
8658 pub have_hidden: bool,
8660}
8661
8662impl DsparkState {
8663 pub fn new(stages: usize, cfg: &Dsv4Cfg, targets: usize) -> Self {
8664 Self {
8665 win: vec![Vec::new(); stages],
8666 filled: vec![0; stages],
8667 main_hidden: vec![0.0; cfg.dim * targets],
8668 have_hidden: false,
8669 }
8670 }
8671}
8672
8673pub fn dspark_targets(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, n_layers: usize) -> Vec<usize> {
8679 let Some(mp) = mtp.iter().find_map(|m| m.main_proj.as_ref()) else {
8680 return Vec::new();
8681 };
8682 let n = (mp.cols() / cfg.dim.max(1)).clamp(1, n_layers);
8683 (n_layers - n..n_layers).collect()
8684}
8685
8686thread_local! {
8687 static DSPARK_CAP: std::cell::RefCell<(Vec<usize>, Vec<f32>, usize)> =
8693 const { std::cell::RefCell::new((Vec::new(), Vec::new(), 0)) };
8694}
8695
8696pub fn dspark_arm(targets: &[usize], dim: usize) {
8698 DSPARK_CAP.with(|c| {
8699 let mut c = c.borrow_mut();
8700 c.0 = targets.to_vec();
8701 c.1 = vec![0.0; dim * targets.len()];
8702 c.2 = 0;
8703 });
8704}
8705
8706fn dspark_wants(li: usize) -> bool {
8710 DSPARK_CAP.with(|c| c.borrow().0.contains(&li))
8711}
8712
8713pub fn dspark_note(li: usize, state: &[f32], cfg: &Dsv4Cfg) {
8715 DSPARK_CAP.with(|c| {
8716 let mut c = c.borrow_mut();
8717 if c.0.is_empty() {
8718 return;
8719 }
8720 if let Some(slot) = c.0.iter().position(|&t| t == li) {
8721 let (_, buf, seen) = &mut *c;
8722 dspark_capture(state, cfg, slot, buf);
8723 *seen = if slot == 0 { 1 } else { *seen + 1 };
8728 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
8729 eprintln!("[cap] note li={li} slot={slot} seen={}", *seen);
8730 }
8731 }
8732 });
8733}
8734
8735pub fn dspark_peek_slot(slot: usize, dim: usize, out: &mut [f32]) -> bool {
8740 DSPARK_CAP.with(|c| {
8741 let c = c.borrow();
8742 let lo = slot * dim;
8743 if c.1.len() < lo + dim {
8744 return false;
8745 }
8746 out[..dim].copy_from_slice(&c.1[lo..lo + dim]);
8747 true
8748 })
8749}
8750
8751pub fn dspark_take(out: &mut Vec<f32>) -> bool {
8753 DSPARK_CAP.with(|c| {
8754 let mut c = c.borrow_mut();
8755 if c.0.is_empty() || c.2 != c.0.len() {
8756 if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
8757 eprintln!("[cap] take FAIL armed={:?} seen={}", c.0, c.2);
8758 }
8759 return false;
8760 }
8761 out.clear();
8762 out.extend_from_slice(&c.1);
8763 c.2 = 0;
8764 true
8765 })
8766}
8767
8768pub fn dspark_capture(state: &[f32], cfg: &Dsv4Cfg, slot: usize, out: &mut [f32]) {
8772 let (hc, dim) = (cfg.hc_mult, cfg.dim);
8773 let dst = &mut out[slot * dim..(slot + 1) * dim];
8774 let inv = 1.0 / hc as f32;
8775 for d in 0..dim {
8776 let mut s = 0.0;
8777 for j in 0..hc {
8778 s += state[j * dim + d];
8779 }
8780 dst[d] = s * inv;
8781 }
8782}
8783
8784pub fn dspark_freq_note(picks: &[(usize, Vec<usize>)]) {
8789 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
8790 std::sync::Mutex::new(None);
8791 let Ok(path) = std::env::var("CMF_DSPARK_PICK_DUMP") else {
8792 return;
8793 };
8794 let mut g = FREQ.lock().unwrap();
8795 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
8796 for (stage, idx) in picks {
8797 for &e in idx {
8798 *map.entry((*stage, e)).or_insert(0) += 1;
8799 }
8800 }
8801 *blocks += 1;
8802 if *blocks % 32 == 0 {
8803 let mut lines: Vec<_> = map.iter().collect();
8804 lines.sort();
8805 let body: String = lines
8806 .iter()
8807 .map(|((s, e), n)| format!("{s}\t{e}\t{n}\n"))
8808 .collect();
8809 let _ = std::fs::write(&path, body);
8810 }
8811}
8812
8813pub fn trunk_freq_note(picks: &[(usize, Vec<usize>)]) {
8818 static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
8819 std::sync::Mutex::new(None);
8820 let Ok(path) = std::env::var("CMF_DSV4_TRUNK_PICK_DUMP") else {
8821 return;
8822 };
8823 let mut g = FREQ.lock().unwrap();
8824 let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
8825 for (li, idx) in picks {
8826 for &e in idx {
8827 *map.entry((*li, e)).or_insert(0) += 1;
8828 }
8829 }
8830 *blocks += 1;
8831 if *blocks % 32 == 0 {
8832 let mut lines: Vec<_> = map.iter().collect();
8833 lines.sort();
8834 let body: String = lines
8835 .iter()
8836 .map(|((l, e), n)| format!("{l}\t{e}\t{n}\n"))
8837 .collect();
8838 let _ = std::fs::write(&path, body);
8839 }
8840}
8841
8842fn dspark_apply_mask(out: &mut [Dsv4Mtp]) {
8850 let Ok(path) = std::env::var("CMF_DSPARK_MASK") else {
8851 return;
8852 };
8853 let Ok(text) = std::fs::read_to_string(&path) else {
8854 eprintln!("DSpark: CMF_DSPARK_MASK={path} не читается — маска не применена");
8855 return;
8856 };
8857 for (d, line) in text.lines().enumerate() {
8858 let Some(m) = out.get_mut(d) else { break };
8859 let n = m.layer.experts.len();
8860 let mut mask = vec![false; n];
8861 let mut kept = 0usize;
8862 for tok in line.split(',') {
8863 if let Ok(e) = tok.trim().parse::<usize>() {
8864 if e < n && !mask[e] {
8865 mask[e] = true;
8866 kept += 1;
8867 }
8868 }
8869 }
8870 if kept == 0 {
8871 continue;
8872 }
8873 eprintln!("DSpark: стадия {d} ограничена {kept}/{n} экспертами");
8874 m.layer.mask = Some(mask);
8875 }
8876}
8877
8878#[cfg(feature = "gpu")]
8888pub struct DsparkPack {
8889 pub stages: Vec<DsparkStagePack>,
8890 pub gu_q2: bool,
8893 pub dn_q2: bool,
8896 pub routers: Vec<Vec<f32>>,
8899 pub biases: Vec<Option<Vec<f32>>>,
8900}
8901
8902#[cfg(feature = "gpu")]
8903pub struct DsparkStagePack {
8904 pub mask: Vec<bool>,
8906 pub to_slot: Vec<usize>,
8908 pub mask_u32: Vec<u32>,
8911 pub map_u32: Vec<u32>,
8912 pub tensors: Vec<(usize, usize, usize)>,
8914 pub n_resident: usize,
8915}
8916
8917pub static DSPARK_Q2TP_ENCODE: std::sync::OnceLock<fn(&[f32], usize, usize) -> Vec<u8>> =
8924 std::sync::OnceLock::new();
8925
8926#[cfg(feature = "gpu")]
8929pub fn dspark_gpu_on() -> bool {
8930 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8931 *ON.get_or_init(|| {
8932 std::env::var("CMF_DSPARK_GPU")
8933 .map(|v| v != "0")
8934 .unwrap_or(true)
8935 })
8936}
8937
8938#[cfg(feature = "gpu")]
8940pub fn dspark_pack_get(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<&'static DsparkPack> {
8941 static P: std::sync::OnceLock<Option<Box<DsparkPack>>> = std::sync::OnceLock::new();
8942 P.get_or_init(|| dspark_pack_build(mtp, cfg).map(Box::new))
8943 .as_deref()
8944}
8945
8946#[cfg(feature = "gpu")]
8962pub fn dspark_reserve_note(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, layers: &[Dsv4Layer]) {
8963 if mtp.is_empty() || std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "0") || !dspark_gpu_on()
8964 {
8965 return;
8966 }
8967 let forced = std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "1");
8972 let dt = |q2: bool| {
8973 if q2 {
8974 cortiq_core::TensorDtype::Q2TiledP
8975 } else {
8976 cortiq_core::TensorDtype::Q4TiledP
8977 }
8978 };
8979 let gu_q2 = mtp[0]
8980 .layer
8981 .experts
8982 .first()
8983 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
8984 let dn_q2 = mtp[0]
8985 .layer
8986 .experts
8987 .first()
8988 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
8989 let gu = cortiq_core::quant::expected_nbytes(dt(gu_q2), &[cfg.moe_inter, cfg.dim]).unwrap_or(0);
8990 let dn = cortiq_core::quant::expected_nbytes(dt(dn_q2), &[cfg.dim, cfg.moe_inter]).unwrap_or(0);
8991 let per = (2 * gu + dn) as u64;
8992 let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
8993 .ok()
8994 .and_then(|v| v.parse().ok())
8995 .unwrap_or(40);
8998 let bytes = per * (n_res * mtp.len() + mtp.len() + 1) as u64;
9000 let trunk: u64 = layers
9010 .iter()
9011 .map(|l| {
9012 let Some(e) = l.experts.first() else {
9013 return 0;
9014 };
9015 let gu = cortiq_core::quant::expected_nbytes(
9016 dt(e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
9017 &[cfg.moe_inter, cfg.dim],
9018 )
9019 .unwrap_or(0);
9020 let dn = cortiq_core::quant::expected_nbytes(
9021 dt(e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
9022 &[cfg.dim, cfg.moe_inter],
9023 )
9024 .unwrap_or(0);
9025 let routed = l
9026 .mask
9027 .as_deref()
9028 .map_or(l.experts.len(), |m| m.iter().filter(|&&open| open).count());
9029 ((2 * gu + dn) * (routed + 1)) as u64
9030 })
9031 .sum();
9032 if let Some(budget) = crate::gpu_wgpu::dsv4_vram_budget() {
9033 if !forced && budget < trunk / 10 * 9 + bytes {
9034 return;
9035 }
9036 }
9037 let mib = 1024 * 1024u64;
9046 let workspace = match crate::gpu_wgpu::dsv4_vram_budget() {
9047 Some(b) if b <= 64 * 1024 * mib => 1024 * mib,
9051 _ => ((cfg.dim * cfg.hc_mult * DSPARK_BLOCK_MAX * 4096) as u64)
9052 .clamp(512 * mib, 1024 * mib),
9053 };
9054 crate::gpu_wgpu::DRAFT_PACK_RESERVE.store(bytes, std::sync::atomic::Ordering::Relaxed);
9055 crate::gpu_wgpu::DRAFT_RESERVE
9056 .store(bytes.saturating_add(workspace), std::sync::atomic::Ordering::Relaxed);
9057}
9058
9059#[cfg(not(feature = "gpu"))]
9060pub fn dspark_reserve_note(_mtp: &[Dsv4Mtp], _cfg: &Dsv4Cfg, _layers: &[Dsv4Layer]) {}
9061
9062#[cfg(feature = "gpu")]
9063pub fn dspark_pack_build(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<DsparkPack> {
9064 if mtp.is_empty() {
9065 return None;
9066 }
9067 let n_res: usize =
9068 std::env::var("CMF_DSPARK_RESIDENT")
9069 .ok()
9070 .and_then(|v| v.parse().ok())
9071 .unwrap_or_else(|| {
9072 let native_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
9078 e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
9079 });
9080 let gu_q2 =
9081 native_q2 || (!dspark_native_on() && DSPARK_Q2TP_ENCODE.get().is_some());
9082 let dn_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
9083 e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
9084 });
9085 let room = crate::gpu_wgpu::dsv4_draft_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2);
9086 (room.saturating_sub(mtp.len() + 1) / mtp.len().max(1)).clamp(8, 40)
9087 });
9088 let mut freq: Vec<Vec<(u64, usize)>> = vec![Vec::new(); mtp.len()];
9092 let pack_path = std::env::var("CMF_DSPARK_PACK").ok().or_else(|| {
9093 let m = mtp[0].layer.experts.first()?.w1.model_arc()?;
9094 let mut s = m.path.as_os_str().to_os_string();
9095 s.push(".dspark.tsv");
9096 let p = std::path::PathBuf::from(s);
9097 p.exists().then(|| p.to_string_lossy().into_owned())
9098 });
9099 if let Some(path) = pack_path {
9100 if let Ok(text) = std::fs::read_to_string(&path) {
9101 for line in text.lines() {
9102 let mut it = line.split_whitespace();
9103 if let (Some(s), Some(e), Some(n)) = (it.next(), it.next(), it.next()) {
9104 if let (Ok(s), Ok(e), Ok(n)) =
9105 (s.parse::<usize>(), e.parse::<usize>(), n.parse::<u64>())
9106 {
9107 if s < freq.len() {
9108 freq[s].push((n, e));
9109 }
9110 }
9111 }
9112 }
9113 }
9114 }
9115 let mut stages = Vec::with_capacity(mtp.len());
9116 let mut routers = Vec::with_capacity(mtp.len());
9117 let mut biases = Vec::with_capacity(mtp.len());
9118 for (si, m) in mtp.iter().enumerate() {
9119 let l = &m.layer;
9120 let n = l.experts.len();
9121 let mut order: Vec<usize> = {
9124 let mut f = freq[si].clone();
9125 f.sort_by(|a, b| b.0.cmp(&a.0));
9126 let mut seen = vec![false; n];
9127 let mut o: Vec<usize> = f
9128 .into_iter()
9129 .map(|(_, e)| e)
9130 .filter(|&e| {
9131 if e < n && !seen[e] {
9132 seen[e] = true;
9133 true
9134 } else {
9135 false
9136 }
9137 })
9138 .collect();
9139 o.extend((0..n).filter(|&e| !seen[e]));
9140 o
9141 };
9142 order.truncate(n_res.min(n));
9143 let mut mask = vec![false; n];
9144 let mut to_slot = vec![usize::MAX; n];
9145 let mut tensors = Vec::with_capacity(order.len() + 1);
9146 for (slot, &e) in order.iter().enumerate() {
9147 let ex = &l.experts[e];
9148 let (Some(w1), Some(w3), Some(w2)) =
9149 (ex.w1.model_idx(), ex.w3.model_idx(), ex.w2.model_idx())
9150 else {
9151 return None;
9152 };
9153 mask[e] = true;
9154 to_slot[e] = slot;
9155 tensors.push((w1, w3, w2));
9156 }
9157 let (Some(s1), Some(s3), Some(s2)) = (
9158 l.shared.w1.model_idx(),
9159 l.shared.w3.model_idx(),
9160 l.shared.w2.model_idx(),
9161 ) else {
9162 return None;
9163 };
9164 tensors.push((s1, s3, s2));
9165 let mut router = vec![0.0f32; n * cfg.dim];
9167 for (r, row) in (0..n).zip(router.chunks_mut(cfg.dim)) {
9168 l.gate.row_f32(r, row);
9169 }
9170 routers.push(router);
9171 biases.push(l.gate_bias.clone());
9172 let mask_u32: Vec<u32> = mask.iter().map(|&m| m as u32).collect();
9173 let map_u32: Vec<u32> = to_slot
9174 .iter()
9175 .map(|&x| if x == usize::MAX { u32::MAX } else { x as u32 })
9176 .collect();
9177 stages.push(DsparkStagePack {
9178 mask,
9179 to_slot,
9180 mask_u32,
9181 map_u32,
9182 tensors,
9183 n_resident: order.len(),
9184 });
9185 }
9186 let model = mtp[0]
9190 .layer
9191 .experts
9192 .first()
9193 .and_then(|e| e.w1.model_arc())?;
9194 let mut skeleton = Vec::new();
9195 for m in mtp {
9196 let l = &m.layer;
9197 for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b] {
9198 skeleton.push(t.model_idx()?);
9199 }
9200 }
9201 if let Some(mp) = mtp[0].main_proj.as_ref() {
9202 skeleton.push(mp.model_idx()?);
9203 }
9204 for &idx in &skeleton {
9205 if !crate::gpu_wgpu::dsv4_weight_ready(&model, idx) {
9206 eprintln!("DSpark: скелет драфта не влез в VRAM — GPU-черновик выключен");
9207 return None;
9208 }
9209 }
9210 let native_q2 = mtp[0]
9216 .layer
9217 .experts
9218 .first()
9219 .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9220 let gu_q2 = native_q2
9221 || (!crate::dsv4::dspark_native_on()
9222 && crate::dsv4::DSPARK_Q2TP_ENCODE.get().is_some());
9223 let dn_native = mtp[0]
9224 .layer
9225 .experts
9226 .first()
9227 .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9228 for (si, sp) in stages.iter().enumerate() {
9229 let ok = if native_q2 {
9230 crate::gpu_wgpu::dsv4_experts_ready(
9231 &model,
9232 &sp.tensors,
9233 cfg.moe_inter,
9234 cfg.dim,
9235 true,
9236 dn_native,
9237 )
9238 } else if gu_q2 {
9239 crate::gpu_wgpu::moe_expert_bufs_requant_gu(&model, &sp.tensors, cfg.moe_inter, cfg.dim)
9240 .is_some()
9241 } else {
9242 crate::gpu_wgpu::dsv4_experts_ready(
9243 &model,
9244 &sp.tensors,
9245 cfg.moe_inter,
9246 cfg.dim,
9247 false,
9248 false,
9249 )
9250 };
9251 if !ok {
9252 eprintln!(
9253 "DSpark: эксперты стадии {si} ({} + shared) не влезли в VRAM — GPU-черновик выключен",
9254 sp.n_resident
9255 );
9256 return None;
9257 }
9258 }
9259 let _ = crate::gpu_wgpu::pin_weights(&model, &skeleton);
9260 eprintln!(
9261 "DSpark: пак драфта на карте — {} стадии по {} экспертов + shared",
9262 stages.len(),
9263 stages
9264 .iter()
9265 .map(|s| s.n_resident.to_string())
9266 .collect::<Vec<_>>()
9267 .join("/")
9268 );
9269 Some(DsparkPack {
9270 stages,
9271 gu_q2,
9272 dn_q2: dn_native,
9273 routers,
9274 biases,
9275 })
9276}
9277
9278pub fn dspark_ring_append(
9285 g: &Dsv4Globals,
9286 mtp: &[Dsv4Mtp],
9287 cfg: &Dsv4Cfg,
9288 ds: &mut DsparkState,
9289 pos: usize,
9290 pool: Option<&crate::pool::Pool>,
9291) {
9292 let (dim, hd, rd) = (cfg.dim, cfg.head_dim, cfg.rope_head_dim);
9293 let inv_freq = &g.inv_freq_window;
9294 let Some(stage0) = mtp.first() else { return };
9295 let (Some(mp), Some(mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
9296 return;
9297 };
9298 let mut main_x = vec![0.0f32; dim];
9299 mp.matvec(&ds.main_hidden, &mut main_x, pool);
9300 rms_weighted(&mut main_x, mn, cfg.norm_eps);
9301 for (si, m) in mtp.iter().enumerate() {
9302 let kvw = m.layer.wkv.rows();
9303 if ds.win[si].len() < cfg.window * kvw {
9304 ds.win[si].resize(cfg.window * kvw, 0.0);
9305 }
9306 let mut kv = vec![0.0f32; kvw];
9307 m.layer.wkv.matvec(&main_x, &mut kv, pool);
9308 rms_weighted(&mut kv, &m.layer.kv_norm, cfg.norm_eps);
9309 rope_tail(&mut kv[kvw - hd..], inv_freq, pos, rd, false);
9310 let slot = pos % cfg.window;
9311 ds.win[si][slot * kvw..(slot + 1) * kvw].copy_from_slice(&kv);
9312 ds.filled[si] = (pos + 1).min(cfg.window);
9313 }
9314}
9315
9316#[cfg(feature = "gpu")]
9322#[allow(clippy::too_many_arguments)]
9323pub fn dspark_draft_gpu(
9324 g: &Dsv4Globals,
9325 mtp: &[Dsv4Mtp],
9326 cfg: &Dsv4Cfg,
9327 ds: &mut DsparkState,
9328 pack: &DsparkPack,
9329 kv_id: u64,
9330 last_token: u32,
9331 pos: usize,
9332 pool: Option<&crate::pool::Pool>,
9333 out_conf: &mut Vec<f32>,
9334) -> Vec<u32> {
9335 let (hc, dim) = (cfg.hc_mult, cfg.dim);
9336 let block = dspark_block();
9337 let Some(model) = mtp[0].layer.experts.first().and_then(|e| e.w1.model_arc()) else {
9338 return Vec::new();
9339 };
9340 let (Some(mp), Some(mn)) = (mtp[0].main_proj.as_ref(), mtp[0].main_norm.as_ref()) else {
9341 return Vec::new();
9342 };
9343 let Some(mp_idx) = mp.model_idx() else {
9344 return Vec::new();
9345 };
9346 let mut stages = Vec::with_capacity(mtp.len());
9347 for (si, m) in mtp.iter().enumerate() {
9348 let l = &m.layer;
9349 let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
9350 l.wq_a.model_idx(),
9351 l.wq_b.model_idx(),
9352 l.wo_a.model_idx(),
9353 l.wo_b.model_idx(),
9354 l.wkv.model_idx(),
9355 ) else {
9356 return Vec::new();
9357 };
9358 let sp = &pack.stages[si];
9359 stages.push(crate::gpu_wgpu::DsparkStageW {
9360 wq_a,
9361 wq_b,
9362 wo_a,
9363 wo_b,
9364 wkv,
9365 q_norm: &l.q_norm,
9366 kv_norm: &l.kv_norm,
9367 attn_norm: &l.attn_norm,
9368 ffn_norm: &l.ffn_norm,
9369 sink: &l.attn_sink,
9370 hc_attn_fn: &l.hc_attn_fn,
9371 hc_attn_scale: &l.hc_attn_scale,
9372 hc_attn_base: &l.hc_attn_base,
9373 hc_ffn_fn: &l.hc_ffn_fn,
9374 hc_ffn_scale: &l.hc_ffn_scale,
9375 hc_ffn_base: &l.hc_ffn_base,
9376 router: &pack.routers[si],
9377 bias: pack.biases[si].as_deref(),
9378 experts: &sp.tensors,
9379 mask_u32: &sp.mask_u32,
9380 map_u32: &sp.map_u32,
9381 });
9382 }
9383 let geom = crate::gpu_wgpu::DsparkGeom {
9384 dim,
9385 hc,
9386 nh: cfg.n_heads,
9387 hd: cfg.head_dim,
9388 rd: cfg.rope_head_dim,
9389 q_lora: cfg.q_lora_rank,
9390 o_lora: cfg.o_lora_rank,
9391 o_groups: cfg.o_groups,
9392 inter: cfg.moe_inter,
9393 n_experts: cfg.n_routed_experts,
9394 top_k: cfg.top_k,
9395 window: cfg.window,
9396 eps: cfg.norm_eps,
9397 hc_eps: cfg.hc_eps,
9398 sinkhorn_iters: cfg.hc_sinkhorn_iters,
9399 route_scale: cfg.route_scale,
9400 swiglu_limit: cfg.swiglu_limit,
9401 scale: (cfg.head_dim as f32).powf(-0.5),
9402 gu_q2: pack.gu_q2,
9403 dn_q2: pack.dn_q2,
9404 };
9405 let ids: Vec<u32> = (0..block)
9407 .map(|i| {
9408 if i == 0 {
9409 last_token
9410 } else {
9411 DSPARK_NOISE_TOKEN
9412 }
9413 })
9414 .collect();
9415 let mut states0 = vec![0.0f32; block * hc * dim];
9416 let mut emb = vec![0.0f32; dim];
9417 for (i, &id) in ids.iter().enumerate() {
9418 g.embed.row_f32(id as usize, &mut emb);
9419 for j in 0..hc {
9420 states0[(i * hc + j) * dim..(i * hc + j + 1) * dim].copy_from_slice(&emb);
9421 }
9422 }
9423 let dspark_time = {
9424 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9425 *ON.get_or_init(|| std::env::var("CMF_DSPARK_TIME").is_ok_and(|v| v != "0"))
9426 };
9427 let t0 = std::time::Instant::now();
9428 let filled = (pos + 1).min(cfg.window);
9429 let mut states = vec![0.0f32; block * hc * dim];
9430 if !crate::gpu_wgpu::dspark_graph(
9431 &model,
9432 &stages,
9433 geom,
9434 kv_id,
9435 mp_idx,
9436 mn,
9437 &ds.main_hidden,
9438 &states0,
9439 pos,
9440 filled,
9441 &g.inv_freq_window,
9442 block,
9443 &mut states,
9444 ) {
9445 return Vec::new();
9446 }
9447 for si in 0..mtp.len() {
9448 ds.filled[si] = filled;
9449 }
9450 let t_graph = t0.elapsed();
9451
9452 let last = &mtp[mtp.len() - 1];
9454 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
9455 last.hc_head_fn.as_ref(),
9456 last.hc_head_base.as_ref(),
9457 last.hc_head_scale,
9458 last.norm.as_ref(),
9459 ) else {
9460 return Vec::new();
9461 };
9462 let mut head_in = vec![0.0f32; block * dim];
9463 let mut pre_norms = vec![vec![0.0f32; dim]; block];
9464 for i in 0..block {
9465 hc_head_fold(
9466 &states[i * hc * dim..(i + 1) * hc * dim],
9467 hfn,
9468 hscale,
9469 hbase,
9470 cfg,
9471 pool,
9472 &mut head_in[i * dim..(i + 1) * dim],
9473 );
9474 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
9475 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
9476 }
9477 let t_fold = t0.elapsed();
9478 let mut logits = vec![0.0f32; block * cfg.vocab];
9479 let head_gpu = g.head.model_idx().is_some_and(|hi| {
9482 crate::gpu_wgpu::q4tp_matvec_batch_for_test(
9483 &model,
9484 hi,
9485 &head_in,
9486 block,
9487 cfg.vocab,
9488 dim,
9489 &mut logits,
9490 )
9491 });
9492 if !head_gpu {
9493 g.head.matmat(&head_in, block, &mut logits, pool);
9494 }
9495 let t_head = t0.elapsed();
9496 let mut proposals = Vec::with_capacity(block);
9501 out_conf.clear();
9502 let mut prev = last_token;
9503 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
9504 let mut bias = vec![0.0f32; cfg.vocab];
9505 for i in 0..block {
9506 let row = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
9507 if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
9508 w1.row_f32(prev as usize, &mut mk_embed);
9509 w2.matvec(&mk_embed, &mut bias, pool);
9510 for (a, b) in row.iter_mut().zip(&bias) {
9511 *a += *b;
9512 }
9513 }
9514 let mut best = 0usize;
9515 for v in 1..row.len() {
9516 if row[v] > row[best] {
9517 best = v;
9518 }
9519 }
9520 if let Some(cf) = last.confidence.as_ref() {
9521 let mut cat = pre_norms[i].clone();
9522 cat.extend_from_slice(&mk_embed);
9523 let mut sc = [0.0f32; 1];
9524 if cat.len() == cf.cols() {
9525 cf.matvec(&cat, &mut sc, pool);
9526 }
9527 out_conf.push(sc[0]);
9528 }
9529 proposals.push(best as u32);
9530 prev = best as u32;
9531 }
9532 if dspark_time {
9533 eprintln!(
9534 "DSpark GPU: граф {:.1} мс, фолды {:.1}, голова {:.1}, марков+argmax {:.1}",
9535 t_graph.as_secs_f64() * 1e3,
9536 (t_fold - t_graph).as_secs_f64() * 1e3,
9537 (t_head - t_fold).as_secs_f64() * 1e3,
9538 (t0.elapsed() - t_head).as_secs_f64() * 1e3,
9539 );
9540 }
9541 proposals
9542}
9543
9544#[allow(clippy::too_many_arguments)]
9550pub fn dspark_draft(
9551 g: &Dsv4Globals,
9552 mtp: &[Dsv4Mtp],
9553 cfg: &Dsv4Cfg,
9554 ds: &mut DsparkState,
9555 last_token: u32,
9556 pos: usize,
9557 pool: Option<&crate::pool::Pool>,
9558 out_conf: &mut Vec<f32>,
9559) -> Vec<u32> {
9560 let (hc, dim, hd, rd) = (cfg.hc_mult, cfg.dim, cfg.head_dim, cfg.rope_head_dim);
9561 let block = dspark_block();
9562 let inv_freq = &g.inv_freq_window;
9563
9564 let Some(stage0) = mtp.first() else {
9566 return Vec::new();
9567 };
9568 let (Some(_mp), Some(_mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
9569 return Vec::new();
9570 };
9571 dspark_ring_append(g, mtp, cfg, ds, pos, pool);
9572
9573 let ids: Vec<u32> = (0..block)
9575 .map(|i| {
9576 if i == 0 {
9577 last_token
9578 } else {
9579 DSPARK_NOISE_TOKEN
9580 }
9581 })
9582 .collect();
9583 let mut states = vec![vec![0.0f32; hc * dim]; block];
9584 let mut emb = vec![0.0f32; dim];
9585 for (i, &id) in ids.iter().enumerate() {
9586 g.embed.row_f32(id as usize, &mut emb);
9587 for j in 0..hc {
9588 states[i][j * dim..(j + 1) * dim].copy_from_slice(&emb);
9589 }
9590 }
9591
9592 let mut scratch = HcScratch::new(cfg);
9593 for (si, m) in mtp.iter().enumerate() {
9594 let l = &m.layer;
9595 let kvw = l.wkv.rows();
9596 let mut post = vec![vec![0.0f32; hc]; block];
9599 let mut comb = vec![vec![0.0f32; hc * hc]; block];
9600 let mut resid = vec![vec![0.0f32; hc * dim]; block];
9601 let mut folded = vec![vec![0.0f32; dim]; block];
9602 let mix_hc = (2 + hc) * hc;
9603 for i in 0..block {
9604 hc_mixes(
9605 &states[i],
9606 &l.hc_attn_fn,
9607 mix_hc,
9608 cfg.norm_eps,
9609 pool,
9610 &mut scratch.mixes,
9611 );
9612 hc_split_sinkhorn(
9613 &scratch.mixes,
9614 &l.hc_attn_scale,
9615 &l.hc_attn_base,
9616 hc,
9617 cfg.hc_sinkhorn_iters,
9618 cfg.hc_eps,
9619 &mut scratch.pre,
9620 &mut post[i],
9621 &mut comb[i],
9622 );
9623 hc_fold(&states[i], &scratch.pre, hc, dim, &mut folded[i]);
9624 rms_weighted(&mut folded[i], &l.attn_norm, cfg.norm_eps);
9625 resid[i].copy_from_slice(&states[i]);
9626 }
9627 let folded_all: Vec<f32> = folded.iter().flatten().copied().collect();
9629 let mut blk_kv = vec![0.0f32; block * kvw];
9630 l.wkv.matmat(&folded_all, block, &mut blk_kv, pool);
9631 for i in 0..block {
9632 let dst = &mut blk_kv[i * kvw..(i + 1) * kvw];
9633 rms_weighted(dst, &l.kv_norm, cfg.norm_eps);
9634 rope_tail(&mut dst[kvw - hd..], inv_freq, pos + 1 + i, rd, false);
9635 }
9636 let win_len = ds.filled[si];
9638 let mut cache = Vec::with_capacity((win_len + block) * hd);
9639 for p in 0..win_len {
9640 let e = &ds.win[si][p * kvw..(p + 1) * kvw];
9641 cache.extend_from_slice(&e[kvw - hd..]);
9642 }
9643 for i in 0..block {
9644 let e = &blk_kv[i * kvw..(i + 1) * kvw];
9645 cache.extend_from_slice(&e[kvw - hd..]);
9646 }
9647 let idxs: Vec<usize> = (0..win_len + block).collect();
9648 let scale = (hd as f32).powf(-0.5);
9649 let qrank = l.wq_a.rows();
9650 let qdim = cfg.n_heads * hd;
9651 let mut qr = vec![0.0f32; block * qrank];
9652 l.wq_a.matmat(&folded_all, block, &mut qr, pool);
9653 for i in 0..block {
9654 rms_weighted(&mut qr[i * qrank..(i + 1) * qrank], &l.q_norm, cfg.norm_eps);
9655 }
9656 let mut q = vec![0.0f32; block * qdim];
9657 l.wq_b.matmat(&qr, block, &mut q, pool);
9658 let mut attn = vec![0.0f32; block * qdim];
9659 for i in 0..block {
9660 let qi = &mut q[i * qdim..(i + 1) * qdim];
9661 let ai = &mut attn[i * qdim..(i + 1) * qdim];
9662 let qpos = pos + 1 + i;
9663 for h in 0..cfg.n_heads {
9664 let head = &mut qi[h * hd..(h + 1) * hd];
9665 rms_inplace(head, cfg.norm_eps);
9666 rope_tail(head, inv_freq, qpos, rd, false);
9667 }
9668 for h in 0..cfg.n_heads {
9669 let qh = &qi[h * hd..(h + 1) * hd];
9670 let oh = &mut ai[h * hd..(h + 1) * hd];
9671 sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
9672 rope_tail(oh, inv_freq, qpos, rd, true);
9673 }
9674 }
9675 let mut blk_out = vec![0.0f32; block * dim];
9676 o_project_block(
9677 &attn,
9678 block,
9679 &l.wo_a,
9680 &l.wo_b,
9681 cfg.o_groups,
9682 cfg.o_lora_rank,
9683 pool,
9684 &mut blk_out,
9685 );
9686 for i in 0..block {
9687 let mut next = vec![0.0f32; hc * dim];
9688 hc_expand(
9689 &blk_out[i * dim..(i + 1) * dim],
9690 &resid[i],
9691 &post[i],
9692 &comb[i],
9693 hc,
9694 dim,
9695 &mut next,
9696 );
9697 states[i] = next;
9698 }
9699 let mut ffn_fold = vec![0.0f32; block * dim];
9702 let mut ffn_post = vec![vec![0.0f32; hc]; block];
9703 let mut ffn_comb = vec![vec![0.0f32; hc * hc]; block];
9704 let mut ffn_resid = vec![vec![0.0f32; hc * dim]; block];
9705 for i in 0..block {
9706 hc_mixes(
9707 &states[i],
9708 &l.hc_ffn_fn,
9709 mix_hc,
9710 cfg.norm_eps,
9711 pool,
9712 &mut scratch.mixes,
9713 );
9714 hc_split_sinkhorn(
9715 &scratch.mixes,
9716 &l.hc_ffn_scale,
9717 &l.hc_ffn_base,
9718 hc,
9719 cfg.hc_sinkhorn_iters,
9720 cfg.hc_eps,
9721 &mut scratch.pre,
9722 &mut ffn_post[i],
9723 &mut ffn_comb[i],
9724 );
9725 hc_fold(
9726 &states[i],
9727 &scratch.pre,
9728 hc,
9729 dim,
9730 &mut ffn_fold[i * dim..(i + 1) * dim],
9731 );
9732 rms_weighted(
9733 &mut ffn_fold[i * dim..(i + 1) * dim],
9734 &l.ffn_norm,
9735 cfg.norm_eps,
9736 );
9737 ffn_resid[i].copy_from_slice(&states[i]);
9738 }
9739 let mut moe_out = vec![0.0f32; block * dim];
9740 moe_step_block(&ffn_fold, block, l, cfg, &ids, si, pool, &mut moe_out);
9741 for i in 0..block {
9742 let mut next = vec![0.0f32; hc * dim];
9743 hc_expand(
9744 &moe_out[i * dim..(i + 1) * dim],
9745 &ffn_resid[i],
9746 &ffn_post[i],
9747 &ffn_comb[i],
9748 hc,
9749 dim,
9750 &mut next,
9751 );
9752 states[i] = next;
9753 }
9754 }
9755
9756 let last = &mtp[mtp.len() - 1];
9758 let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
9759 last.hc_head_fn.as_ref(),
9760 last.hc_head_base.as_ref(),
9761 last.hc_head_scale,
9762 last.norm.as_ref(),
9763 ) else {
9764 return Vec::new();
9765 };
9766 let mut proposals = Vec::with_capacity(block);
9767 out_conf.clear();
9768 let mut prev = last_token;
9769 let mut head_in = vec![0.0f32; block * dim];
9770 let mut pre_norms = vec![vec![0.0f32; dim]; block];
9771 for i in 0..block {
9772 hc_head_fold(
9773 &states[i],
9774 hfn,
9775 hscale,
9776 hbase,
9777 cfg,
9778 pool,
9779 &mut head_in[i * dim..(i + 1) * dim],
9780 );
9781 pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
9782 rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
9783 }
9784 let mut logits = vec![0.0f32; block * cfg.vocab];
9785 g.head.matmat(&head_in, block, &mut logits, pool);
9786 let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
9787 for i in 0..block {
9788 let logits_i = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
9789 let no_markov = {
9798 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9799 *ON.get_or_init(|| std::env::var("CMF_DSPARK_NO_MARKOV").is_ok_and(|v| v != "0"))
9800 };
9801 if no_markov {
9802 if let Some(w1) = last.markov_w1.as_ref() {
9804 w1.row_f32(prev as usize, &mut mk_embed);
9805 }
9806 } else if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
9807 w1.row_f32(prev as usize, &mut mk_embed);
9808 let mut bias = vec![0.0f32; cfg.vocab];
9809 w2.matvec(&mk_embed, &mut bias, pool);
9810 for (a, b) in logits_i.iter_mut().zip(&bias) {
9811 *a += *b;
9812 }
9813 }
9814 let mut best = 0usize;
9815 for v in 1..logits_i.len() {
9816 if logits_i[v] > logits_i[best] {
9817 best = v;
9818 }
9819 }
9820 if let Some(cf) = last.confidence.as_ref() {
9821 let mut cat = pre_norms[i].clone();
9822 cat.extend_from_slice(&mk_embed);
9823 let mut s = [0.0f32; 1];
9824 if cat.len() == cf.cols() {
9825 cf.matvec(&cat, &mut s, pool);
9826 }
9827 out_conf.push(s[0]);
9828 }
9829 proposals.push(best as u32);
9830 prev = best as u32;
9831 }
9832 proposals
9833}