1use crate::attention::{self, QwenAttnCfg};
10use crate::inference;
11use crate::kv_cache::KvCache;
12use crate::linear_core::{
13 GdnCfg, GdnWeights, ShortConvCfg, ShortConvWeights, VmfPhaseCfg, VmfPhaseWeights, gdn_forward,
14 gdn_pair, short_conv_forward, short_conv_forward_batch, short_conv_pair, vmf_phase_forward,
15 vmf_phase_pair,
16};
17use crate::pool::Pool;
18use crate::qtensor::QTensor;
19use crate::sampler::{self, SamplerConfig, SamplerScratch, SplitMix64};
20use crate::tokenizer::Tokenizer;
21use cortiq_core::mask::TaskMask;
22use cortiq_core::types::NormStyle;
23
24pub static GLOBAL_USE_GPU: std::sync::atomic::AtomicBool =
25 std::sync::atomic::AtomicBool::new(false);
26
27struct ForwardScratch {
31 n1: Vec<f32>,
32 n2: Vec<f32>,
33 p1: Vec<f32>,
34 p2: Vec<f32>,
35}
36
37impl ForwardScratch {
38 fn new(hidden: usize) -> Self {
39 Self {
40 n1: vec![0.0; hidden],
41 n2: vec![0.0; hidden],
42 p1: vec![0.0; hidden],
43 p2: vec![0.0; hidden],
44 }
45 }
46}
47
48pub struct Pipeline {
50 pub tokenizer: std::sync::Arc<Tokenizer>,
53 pub kv_cache: KvCache,
54 pub sampler_config: SamplerConfig,
55 pub weights: PipelineWeights,
56 pub hidden_size: usize,
57 pub intermediate_size: usize,
58 pub num_heads: usize,
59 pub num_kv_heads: usize,
60 pub head_dim: usize,
61 pub num_layers: usize,
63 pub physical_layers: usize,
65 pub loop_final_norm: bool,
67 pub vocab_size: usize,
68 pub rms_eps: f64,
69 pub rope_base: f32,
70 pub norm_style: NormStyle,
71 pub rotary_dim: usize,
73 pub attention_heads_per_layer: Option<Vec<usize>>,
75 pub vmf_cfg: Option<VmfPhaseCfg>,
77 pub gdn_cfg: Option<GdnCfg>,
79 pub logit_multiplier: Option<f32>,
81 pub cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
86 pub kv_history: Vec<u32>,
91 pub kda_cfg: Option<crate::linear_core::KdaCfg>,
93 pub g3n: Option<Box<(crate::g3n::G3nGlobals, Vec<crate::g3n::G3nLayer>)>>,
96 pub dsv4: Option<
100 Box<(
101 crate::dsv4::Dsv4Globals,
102 Vec<crate::dsv4::Dsv4Layer>,
103 crate::dsv4::Dsv4Cfg,
104 crate::dsv4::Dsv4State,
105 )>,
106 >,
107 pub dsv4_mtp: Vec<crate::dsv4::Dsv4Mtp>,
111 pub dspark: Option<crate::dsv4::DsparkState>,
113 pub dspark_pending: Vec<(usize, Vec<u32>, bool, usize)>,
116 pub dspark_hist: Vec<usize>,
118 pub dspark_real: Vec<u32>,
122 pub dspark_trunk_picks: Vec<Vec<(usize, Vec<usize>)>>,
126 pub dspark_exp: Vec<(usize, usize, usize, usize)>,
128 pub dspark_draft_ns: u128,
132 pub short_conv_cfg: Option<ShortConvCfg>,
135 pub mtp: Option<MtpModule>,
137 pub speculative: bool,
139 rng: SplitMix64,
140 sampler_scratch: SamplerScratch,
141 pub(crate) inv_freq: std::sync::Arc<Vec<f32>>,
145 ws: ForwardScratch,
149 pool: Option<std::sync::Arc<Pool>>,
151 pub(crate) model: Option<std::sync::Arc<cortiq_core::CmfModel>>,
155 pub(crate) dyn_force_f32: bool,
157 pub(crate) dyn_skill_layers: Vec<Option<Vec<usize>>>,
162 pub(crate) dyn_active: Option<usize>,
168 pub(crate) dyn_blend_loaded: bool,
172 pub(crate) dyn_phi_layer: Option<usize>,
175 dyn_phi_ema: Vec<f32>,
177 dyn_phi_seen: usize,
178 pub dyn_router: Option<crate::swarm::DynRouter>,
181 o1_cfg: Option<crate::nystrom::O1Cfg>,
184 o1_epoch: u64,
187 o1_flags: Vec<bool>,
189 trace: bool,
192 calib_temp: f32,
195 #[cfg_attr(not(target_os = "macos"), allow(dead_code))]
197 graph_kv_id: u64,
198 #[cfg_attr(not(target_os = "macos"), allow(dead_code))]
201 graph_want_logits: bool,
202 graph_logits: Option<Vec<f32>>,
205 pub embed_multiplier: f32,
207 pub attn_scale: f32,
210 pub swa: Option<(usize, usize)>,
213 pub sliding_layers: Option<Vec<bool>>,
216 pub inv_freq_local: Option<std::sync::Arc<Vec<f32>>>,
219 pub rotary_dim_local: Option<usize>,
220 pub rope_scale: f32,
221 pub rope_scale_local: f32,
222 pub global_attn: Option<(usize, usize)>,
225 pub inv_freq_global: Option<std::sync::Arc<Vec<f32>>>,
228 pub attn_v_norm: bool,
230 pub final_softcap: Option<f32>,
232 pub attn_softcap: f32,
234 confidence_on: bool,
238}
239
240#[cfg(target_os = "macos")]
241impl Drop for Pipeline {
242 fn drop(&mut self) {
243 crate::gpu::kv_mirror_drop(self.graph_kv_id);
244 }
245}
246
247pub struct PipelineWeights {
252 pub embed_tokens: QTensor,
254 pub layers: Vec<LayerWeights>,
256 pub lm_head: QTensor,
258 pub final_norm: Vec<f32>,
260}
261
262pub struct LayerWeights {
264 pub input_norm: Vec<f32>,
265 pub post_norm: Vec<f32>,
268 pub attn_out_norm: Option<Vec<f32>>,
271 pub layer_scale: Option<f32>,
273 pub ffn_out_norm: Option<Vec<f32>>,
276 pub ffn: FfnKind,
277 pub attn: AttnKind,
278}
279
280#[derive(Clone, Copy, PartialEq, Debug, Default)]
283pub enum Act {
284 #[default]
285 Silu,
286 GeluTanh,
287 Situ {
290 beta: f32,
291 linear_beta: f32,
292 },
293}
294
295impl Act {
296 pub fn from_arch(name: &str) -> Self {
297 if name == "gelu_tanh" {
298 Self::GeluTanh
299 } else {
300 Self::Silu
301 }
302 }
303
304 pub fn from_arch_full(arch: &cortiq_core::ModelArch) -> Self {
306 match arch.hidden_act.as_str() {
307 "situ" => Self::Situ {
308 beta: arch.activation_situ_beta.unwrap_or(1.0) as f32,
309 linear_beta: arch.activation_situ_linear_beta.unwrap_or(0.0) as f32,
310 },
311 other => Self::from_arch(other),
312 }
313 }
314
315 #[inline]
316 pub fn apply(self, x: f32) -> f32 {
317 match self {
318 Self::Silu => inference::silu(x),
319 Self::GeluTanh => inference::gelu_tanh(x),
320 Self::Situ { beta, .. } => beta * (x / beta).tanh() * (1.0 / (1.0 + (-x).exp())),
321 }
322 }
323
324 #[inline]
327 pub fn combine(self, g: f32, u: f32) -> f32 {
328 match self {
329 Self::Situ { linear_beta, .. } if linear_beta > 0.0 => {
330 self.apply(g) * (linear_beta * (u / linear_beta).tanh())
331 }
332 _ => self.apply(g) * u,
333 }
334 }
335}
336
337pub struct DenseFfn {
339 pub gate_proj: QTensor,
340 pub up_proj: QTensor,
341 pub down_proj: QTensor,
342 pub act: Act,
344}
345
346pub enum FfnKind {
349 Dense(DenseFfn),
350 Moe(MoeFfn),
354 DenseMoe(Box<DenseMoeFfn>),
361}
362
363pub struct DenseMoeFfn {
365 pub dense: DenseFfn,
366 pub moe: MoeFfn,
367 pub post_norm_1: Vec<f32>,
369 pub pre_norm_2: Vec<f32>,
372 pub post_norm_2: Vec<f32>,
374}
375
376pub struct MoeFfn {
377 pub router: QTensor,
379 pub experts: Vec<DenseFfn>,
380 pub top_k: usize,
381 pub norm_topk_prob: bool,
382 pub router_sigmoid: bool,
385 pub expert_bias: Option<Vec<f32>>,
389 pub routed_scaling: f32,
392 pub route_tau: Option<f32>,
398 pub shared: Option<(DenseFfn, Option<QTensor>)>,
401 pub stats: std::cell::RefCell<Vec<u64>>,
405 pub act_sq: std::cell::RefCell<Vec<f64>>,
412 pub act_rows: std::cell::RefCell<Vec<f32>>,
418 pub mask: Option<Vec<bool>>,
423 pub per_expert_scale: Option<Vec<f32>>,
426 pub router_input_norm: bool,
430}
431
432pub enum AttnKind {
435 Full {
437 wq: QTensor,
438 wk: QTensor,
439 wv: QTensor,
440 wo: QTensor,
441 q_norm: Option<Vec<f32>>,
442 k_norm: Option<Vec<f32>>,
443 output_gate: bool,
444 softplus_gate: Option<(QTensor, bool)>,
448 bias: Option<(Vec<f32>, Vec<f32>, Vec<f32>)>,
450 },
451 Linear(VmfPhaseWeights),
453 LinearGdn(GdnWeights),
455 ShortConv(ShortConvWeights),
458 Mla(Box<MlaWeights>),
466 Kda(Box<crate::linear_core::KdaWeights>),
470}
471
472pub struct MlaWeights {
474 pub q_proj: QTensor,
478 pub q_a: Option<QTensor>,
481 pub q_a_norm: Option<Vec<f32>>,
482 pub kv_a: QTensor,
484 pub kv_a_norm: Vec<f32>,
486 pub kv_b: QTensor,
488 pub o_proj: QTensor,
490 pub nh: usize,
491 pub qk_rope: usize,
492 pub qk_nope: usize,
493 pub v_dim: usize,
494 pub lora: usize,
495 pub scale: f32,
497 pub nope: bool,
499}
500
501pub struct MtpModule {
506 pub enorm: Vec<f32>,
507 pub hnorm: Vec<f32>,
508 pub eh_proj: QTensor,
510 pub layer: LayerWeights,
511 pub final_norm: Vec<f32>,
512 pub kv: crate::kv_cache::LayerKvCache,
513}
514
515pub struct GenerateResult {
517 pub text: String,
518 pub token_ids: Vec<u32>,
519 pub prompt_tokens: usize,
520 pub tokens_generated: usize,
521 pub finish_reason: String,
522 pub mtp_drafted: usize,
524 pub mtp_accepted: usize,
525 pub token_confidence: Vec<f32>,
530 pub traces: Vec<TokenTrace>,
533}
534
535#[derive(Clone, Debug)]
540pub struct TokenTrace {
541 pub t: usize,
543 pub token_id: u32,
545 pub confidence: f32,
547 pub active_skill: Option<String>,
549 pub recon: Option<f32>,
553 pub switched: bool,
556}
557
558fn top1_prob_t(logits: &[f32], id: u32, temp: f32) -> f32 {
563 let t = if temp > 1e-3 { temp } else { 1.0 };
564 let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
565 let sum: f32 = logits.iter().map(|&v| ((v - max) / t).exp()).sum();
566 if sum > 0.0 {
567 (((logits[id as usize] - max) / t).exp()) / sum
568 } else {
569 0.0
570 }
571}
572
573fn prefill_batched() -> bool {
576 std::env::var("CMF_PREFILL")
577 .map(|v| v != "seq")
578 .unwrap_or(true)
579}
580
581impl Pipeline {
588 fn can_prefill_batched(&self) -> bool {
589 prefill_batched() && !self.weights.layers.is_empty()
590 }
591}
592
593fn prefill_chunk() -> usize {
598 if let Some(n) = std::env::var("CMF_PREFILL_CHUNK")
599 .ok()
600 .and_then(|v| v.parse::<usize>().ok())
601 {
602 return n.max(1);
603 }
604 if cfg!(target_os = "macos") {
605 512
606 } else if cfg!(target_arch = "aarch64") {
607 256
610 } else {
611 48
612 }
613}
614
615pub type TokenCallback = Box<dyn FnMut(&str) -> bool + Send>;
617
618impl Pipeline {
619 #[inline]
623 pub fn phys_layer(&self, virtual_idx: usize) -> usize {
624 virtual_idx % self.physical_layers
625 }
626
627 #[inline]
630 pub fn is_loop_end(&self, virtual_idx: usize) -> bool {
631 self.loop_final_norm && (virtual_idx + 1) % self.physical_layers == 0
632 }
633
634 #[allow(clippy::too_many_arguments)]
636
637 #[cfg(target_os = "macos")]
656 fn graph_prefill_preferred(&self) -> bool {
657 if !crate::gpu::enabled_here()
658 || !crate::gpu::q1_force()
659 || std::env::var("CMF_GPU_BLOCK")
660 .map(|v| v == "0")
661 .unwrap_or(false)
662 {
663 return false;
664 }
665 self.weights
666 .layers
667 .iter()
668 .any(|lw| matches!(&lw.attn, AttnKind::LinearGdn(w) if w.in_proj_qkv.is_q1()))
669 }
670
671 #[cfg(not(target_os = "macos"))]
672 fn graph_prefill_preferred(&self) -> bool {
673 let graph_on = std::env::var("CMF_GPU_WGPU_GRAPH")
681 .map(|v| v != "0")
682 .unwrap_or_else(|_| {
683 crate::gpu::wgpu_graph_default()
687 });
688 if !graph_on || !crate::gpu::enabled_here() {
689 return false;
690 }
691 self.weights
692 .layers
693 .iter()
694 .any(|lw| matches!(&lw.attn, AttnKind::LinearGdn(_)))
695 }
696
697 #[cfg(target_os = "macos")]
698 fn q1_graph_gpu(
699 &mut self,
700 start: usize,
701 upto: Option<usize>,
702 position: usize,
703 h: &mut [f32],
704 ) -> usize {
705 use crate::gpu::{AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GraphDims, TokenGraph};
706 if self.attn_softcap > 0.0 || !crate::gpu::enabled_here()
708 || !crate::gpu::q1_force()
709 || std::env::var("CMF_GPU_BLOCK")
710 .map(|v| v == "0")
711 .unwrap_or(false)
712 {
713 return start;
714 }
715 if self.swa.is_some()
720 || self.global_attn.is_some()
721 || self.attention_heads_per_layer.is_some()
722 || self.attn_v_norm
723 || (self.attn_scale - 1.0 / (self.head_dim as f32).sqrt()).abs() > 1e-9
724 || self.weights.layers.iter().any(|lw| {
725 lw.attn_out_norm.is_some()
726 || lw.ffn_out_norm.is_some()
727 || lw.layer_scale.is_some()
728 || matches!(&lw.ffn, FfnKind::Dense(d) if d.act != Act::Silu)
729 })
730 {
731 return start;
732 }
733 let limit = upto
736 .map(|u| u + 1)
737 .unwrap_or(self.num_layers)
738 .min(self.num_layers);
739
740 enum Item<'a> {
741 Gdn {
742 run: Vec<GdnGpuLayer<'a>>,
743 first: usize,
744 },
745 Attn {
746 l: AttnGpuLayer<'a>,
747 li: usize,
748 q_norm: Option<&'a [f32]>,
749 k_norm: Option<&'a [f32]>,
750 output_gate: bool,
751 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
752 full_gpu: bool,
755 },
756 }
757
758 let attend_mode = std::env::var("CMF_GPU_ATTEND").unwrap_or_else(|_| "auto".into());
760 let dev_attend = attend_mode != "0"
761 && attend_mode != "off"
762 && (self.head_dim <= 128 || attend_mode == "force" || attend_mode == "256")
766 && self.head_dim % 4 == 0
767 && self.head_dim <= 256
768 && self.rotary_dim >= 2
769 && self.rotary_dim <= self.head_dim
770 && (self.rotary_dim / 2) % 32 == 0
771 && self.num_kv_heads > 0
772 && self.num_heads % self.num_kv_heads == 0;
773
774 let mut plan: Vec<Item> = Vec::new();
775 let mut model_ref: Option<std::sync::Arc<cortiq_core::CmfModel>> = None;
776 let mut scan = start;
777 while scan < limit {
778 let lw = &self.weights.layers[self.phys_layer(scan)];
779 let FfnKind::Dense(d) = &lw.ffn else { break };
780 let (Some(g), Some(u), Some(dn)) = (
781 d.gate_proj.q1_parts(),
782 d.up_proj.q1_parts(),
783 d.down_proj.q1_parts(),
784 ) else {
785 break;
786 };
787 match &lw.attn {
788 AttnKind::LinearGdn(w) if self.gdn_cfg.is_some() => {
789 let parts = (
790 w.in_proj_qkv.q1_parts(),
791 w.in_proj_z.q1_parts(),
792 w.in_proj_a.f32_parts(),
793 w.in_proj_b.f32_parts(),
794 w.out_proj.q1_parts(),
795 );
796 let (Some(qkv), Some(z), Some(a), Some(b), Some(out)) = parts else {
797 break;
798 };
799 if let QTensor::Mapped { model, .. } = &w.in_proj_qkv {
800 model_ref.get_or_insert_with(|| model.clone());
801 }
802 let gl = GdnGpuLayer {
803 attn_norm: &lw.input_norm,
804 post_norm: &lw.post_norm,
805 qkv,
806 z,
807 a,
808 b,
809 out,
810 gate: g,
811 up: u,
812 down: dn,
813 conv1d: &w.conv1d,
814 a_log: &w.a_log,
815 dt_bias: &w.dt_bias,
816 gnorm: &w.norm,
817 };
818 match plan.last_mut() {
819 Some(Item::Gdn { run, .. }) => run.push(gl),
820 _ => plan.push(Item::Gdn {
821 run: vec![gl],
822 first: scan,
823 }),
824 }
825 }
826 AttnKind::Full {
827 wq,
828 wk,
829 wv,
830 wo,
831 q_norm,
832 k_norm,
833 output_gate,
834 softplus_gate: None,
835 bias,
836 } if !self.kv_cache.layers[scan].o1_sealed() => {
837 let parts = (wq.q1_parts(), wk.q1_parts(), wv.q1_parts(), wo.q1_parts());
838 let (Some(pq), Some(pk), Some(pv), Some(po)) = parts else {
839 break;
840 };
841 if let QTensor::Mapped { model, .. } = wq {
842 model_ref.get_or_insert_with(|| model.clone());
843 }
844 let cache = &self.kv_cache.layers[scan];
845 let full_gpu = dev_attend
846 && cache.mode == crate::kv_cache::KvMode::F32
847 && cache.o1.is_none()
848 && bias.is_none()
849 && pq.1 == self.num_heads * self.head_dim * (1 + *output_gate as usize)
850 && pk.1 == self.num_kv_heads * self.head_dim
851 && pv.1 == self.num_kv_heads * self.head_dim
852 && po.2 == self.num_heads * self.head_dim;
853 plan.push(Item::Attn {
854 l: AttnGpuLayer {
855 attn_norm: &lw.input_norm,
856 post_norm: &lw.post_norm,
857 wq: pq,
858 wk: pk,
859 wv: pv,
860 wo: po,
861 gate: g,
862 up: u,
863 down: dn,
864 },
865 li: scan,
866 q_norm: q_norm.as_deref(),
867 k_norm: k_norm.as_deref(),
868 output_gate: *output_gate,
869 bias: bias
870 .as_ref()
871 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
872 full_gpu,
873 });
874 }
875 _ => break,
876 }
877 scan += 1;
878 }
879 let Some(model) = model_ref else {
880 if std::env::var("CMF_GRAPH_DBG").is_ok() {
881 eprintln!("q1-graph: no model ref (start {start}, scanned to {scan})");
882 }
883 return start;
884 };
885 if plan.is_empty() {
886 if std::env::var("CMF_GRAPH_DBG").is_ok() {
887 eprintln!("q1-graph: empty plan at layer {start}");
888 }
889 return start;
890 }
891 if std::env::var("CMF_GRAPH_DBG").is_ok() {
892 use std::sync::atomic::{AtomicBool, Ordering};
893 static SAID: AtomicBool = AtomicBool::new(false);
894 if !SAID.swap(true, Ordering::Relaxed) {
895 let fg = plan
896 .iter()
897 .filter(|it| matches!(it, Item::Attn { full_gpu: true, .. }))
898 .count();
899 let att = plan
900 .iter()
901 .filter(|it| matches!(it, Item::Attn { .. }))
902 .count();
903 eprintln!(
904 "q1-graph: plan of {} items from layer {start} to {scan} | dev_attend={dev_attend} full_gpu {fg}/{att} | hd={} rd={} nkv={} nh={}",
905 plan.len(),
906 self.head_dim,
907 self.rotary_dim,
908 self.num_kv_heads,
909 self.num_heads,
910 );
911 }
912 }
913 let dims = GraphDims {
914 hidden: self.hidden_size,
915 eps: self.rms_eps as f32,
916 gemma: self.norm_style == cortiq_core::NormStyle::Gemma,
917 };
918 let Some(mut graph) = TokenGraph::new(&model, dims, h) else {
919 return start;
920 };
921 let gcfg = self.gdn_cfg.map(|cfg| GdnGpuCfg {
922 nv: cfg.num_v_heads,
923 nk: cfg.num_k_heads,
924 dk: cfg.key_head_dim,
925 dv: cfg.value_head_dim,
926 kk: cfg.conv_kernel,
927 hidden: self.hidden_size,
928 inter: self.intermediate_size,
929 c_dim: cfg.conv_dim(),
930 eps: cfg.rms_eps as f32,
931 gemma: self.norm_style == cortiq_core::NormStyle::Gemma,
932 });
933 let mut valid = 0usize;
937 let mut end = start;
938 for item in &plan {
939 let ok = match item {
940 Item::Gdn { run, .. } => gcfg
941 .as_ref()
942 .map(|gc| run.iter().all(|l| graph.gdn_ok(l, gc)))
943 .unwrap_or(false),
944 Item::Attn { l, .. } => graph.attn_ok(l),
945 };
946 if !ok {
947 break;
948 }
949 valid += 1;
950 end += match item {
951 Item::Gdn { run, .. } => run.len(),
952 Item::Attn { .. } => 1,
953 };
954 }
955 plan.truncate(valid);
956 if plan.is_empty() {
957 return start;
958 }
959
960 let inv_freq = self.inv_freq.clone();
961 let pool = self.pool.clone();
962 let (nh, nkv, hd, hs, rd, eps) = (
963 self.num_heads,
964 self.num_kv_heads,
965 self.head_dim,
966 self.hidden_size,
967 self.rotary_dim,
968 self.rms_eps,
969 );
970 let norm_style = self.norm_style;
971 let gemma = norm_style == cortiq_core::NormStyle::Gemma;
972 let want = self.gdn_cfg.map(|c| c.state_len()).unwrap_or(0);
973 let kv_id = self.graph_kv_id;
974 let mut pending: Vec<(usize, usize)> = Vec::new();
977 let mut dev_attn: Vec<usize> = Vec::new();
980 for item in &plan {
981 if self.loop_final_norm {
983 let item_start = match item {
984 Item::Gdn { first, .. } => *first,
985 Item::Attn { li, .. } => *li,
986 };
987 if item_start > start && self.is_loop_end(item_start - 1) {
988 graph.encode_loop_norm(&self.weights.final_norm);
989 }
990 }
991 match item {
992 Item::Gdn { run, first } => {
993 for l in &mut self.kv_cache.layers[*first..*first + run.len()] {
994 if l.linear_state.len() != want {
995 l.linear_state = vec![0f32; want];
996 }
997 }
998 let ro: Vec<&[f32]> = self.kv_cache.layers[*first..*first + run.len()]
999 .iter()
1000 .map(|l| l.linear_state.as_slice())
1001 .collect();
1002 if !graph.encode_gdn_run(run, &ro, gcfg.as_ref().unwrap()) {
1003 tracing::error!("q1 graph: GDN run refused after validation");
1005 return start;
1006 }
1007 graph.commit();
1010 pending.push((*first, run.len()));
1011 }
1012 Item::Attn {
1013 l,
1014 li,
1015 q_norm,
1016 k_norm,
1017 output_gate,
1018 bias,
1019 full_gpu,
1020 } => {
1021 if *full_gpu {
1023 let cache = &self.kv_cache.layers[*li];
1024 let cpu_k: Vec<&[f32]> = (0..nkv).map(|g| cache.head_keys(g)).collect();
1025 let cpu_v: Vec<&[f32]> = (0..nkv).map(|g| cache.head_values(g)).collect();
1026 let cpu_stored = cpu_k[0].len() / hd;
1027 let p = crate::gpu::AttnDeviceParams {
1028 kv_id,
1029 layer: *li,
1030 nh,
1031 nkv,
1032 hd,
1033 rd,
1034 position,
1035 eps: eps as f32,
1036 gemma,
1037 output_gate: *output_gate,
1038 q_norm: *q_norm,
1039 k_norm: *k_norm,
1040 inv_freq: &inv_freq,
1041 cpu_k,
1042 cpu_v,
1043 cpu_stored,
1044 };
1045 if graph.attn_device_ok(l, &p) && graph.encode_attn_device(l, &p) {
1046 graph.commit();
1047 dev_attn.push(*li);
1048 continue;
1049 }
1050 }
1052 graph.encode_attn_prefix(l);
1053 graph.sync();
1054 if !pending.is_empty() {
1055 let idxs: Vec<usize> =
1056 pending.drain(..).flat_map(|(f, n)| f..f + n).collect();
1057 let mut outs: Vec<&mut [f32]> = self
1058 .kv_cache
1059 .layers
1060 .iter_mut()
1061 .enumerate()
1062 .filter(|(i, _)| idxs.binary_search(i).is_ok())
1063 .map(|(_, s)| s.linear_state.as_mut_slice())
1064 .collect();
1065 graph.read_states(&mut outs);
1066 }
1067 let mut q_raw = attention::take_buf(l.wq.1);
1068 let mut k = attention::take_buf(l.wk.1);
1069 let mut v = attention::take_buf(l.wv.1);
1070 graph.read_qkv(&mut q_raw, &mut k, &mut v);
1071 let cfg = QwenAttnCfg {
1072 num_heads: nh,
1073 num_kv_heads: nkv,
1074 head_dim: hd,
1075 hidden_size: hs,
1076 position,
1077 inv_freq: &inv_freq,
1078 rotary_dim: rd,
1079 scale: self.attn_scale,
1080 softcap: self.attn_softcap,
1081 window: None,
1082 v_norm: false,
1083 q_norm: *q_norm,
1084 k_norm: *k_norm,
1085 output_gate: *output_gate,
1086 softplus_gate: None,
1087 rope_scale: 1.0,
1088 bias: *bias,
1089 rms_eps: eps,
1090 norm_style,
1091 pool: pool.as_deref(),
1092 };
1093 let mut ao = attention::qwen_attention_core(
1094 q_raw,
1095 k,
1096 v,
1097 &mut self.kv_cache.layers[*li],
1098 &cfg,
1099 );
1100 graph.encode_attn_suffix(l, &ao);
1101 graph.commit();
1104 attention::recycle_buf(&mut ao);
1105 }
1106 }
1107 }
1108 let mut lm_rows = None;
1113 if self.graph_want_logits
1114 && upto.is_none()
1115 && end == self.num_layers
1116 && std::env::var("CMF_GPU_LMHEAD")
1117 .map(|v| v != "0")
1118 .unwrap_or(true)
1119 {
1120 if let Some(lm) = self.weights.lm_head.q1_parts() {
1121 if graph.lm_head_ok(lm) {
1122 graph.encode_lm_head(&self.weights.final_norm, lm);
1123 lm_rows = Some(lm.1);
1124 }
1125 }
1126 }
1127 graph.sync();
1128 if !pending.is_empty() {
1129 let idxs: Vec<usize> = pending.drain(..).flat_map(|(f, n)| f..f + n).collect();
1130 let mut outs: Vec<&mut [f32]> = self
1131 .kv_cache
1132 .layers
1133 .iter_mut()
1134 .enumerate()
1135 .filter(|(i, _)| idxs.binary_search(i).is_ok())
1136 .map(|(_, s)| s.linear_state.as_mut_slice())
1137 .collect();
1138 graph.read_states(&mut outs);
1139 }
1140 if let Some(rows) = lm_rows {
1141 let mut lg = attention::take_buf(rows.min(self.vocab_size));
1142 graph.read_logits(&mut lg);
1143 lg.resize(self.vocab_size, 0.0);
1144 if let Some(c) = self.final_softcap {
1145 for l in lg.iter_mut() {
1146 *l = c * (*l / c).tanh();
1147 }
1148 }
1149 self.graph_logits = Some(lg);
1150 }
1151 graph.finish(h);
1152 for li in dev_attn {
1156 let mut krow = attention::take_buf(nkv * hd);
1157 let mut vrow = attention::take_buf(nkv * hd);
1158 if crate::gpu::kv_mirror_read_last(kv_id, li, nkv, hd, &mut krow, &mut vrow) {
1159 let cache = &mut self.kv_cache.layers[li];
1160 cache.append(&krow, &vrow, &[]);
1161 let n = cache.seq_len;
1162 let mut imp = attention::take_buf(n);
1163 crate::gpu::kv_mirror_take_imp(kv_id, li, &mut imp);
1164 cache.accumulate_imp(&imp);
1165 attention::recycle_buf(&mut imp);
1166 }
1167 attention::recycle_buf(&mut krow);
1168 attention::recycle_buf(&mut vrow);
1169 }
1170 end
1171 }
1172
1173 pub fn new(
1174 tokenizer: Tokenizer,
1175 weights: PipelineWeights,
1176 hidden_size: usize,
1177 intermediate_size: usize,
1178 num_heads: usize,
1179 num_kv_heads: usize,
1180 head_dim: usize,
1181 num_layers: usize,
1182 physical_layers: usize,
1183 loop_final_norm: bool,
1184 vocab_size: usize,
1185 rms_eps: f64,
1186 rope_base: f32,
1187 norm_style: NormStyle,
1188 max_seq_len: usize,
1189 sampler_config: SamplerConfig,
1190 ) -> Self {
1191 let rng = match sampler_config.seed {
1192 Some(s) => SplitMix64::new(s),
1193 None => SplitMix64::from_entropy(),
1194 };
1195 let inv_freq = std::sync::Arc::new(attention::rope_inv_freq(head_dim, rope_base));
1196 let pool = Pool::from_env();
1197 if let Some(p) = &pool {
1198 tracing::info!("worker pool: {} threads", p.n_workers());
1199 }
1200 Self {
1201 tokenizer: std::sync::Arc::new(tokenizer),
1202 kv_cache: KvCache::new(num_layers, num_kv_heads, head_dim, max_seq_len),
1203 sampler_config,
1204 weights,
1205 hidden_size,
1206 intermediate_size,
1207 num_heads,
1208 num_kv_heads,
1209 head_dim,
1210 num_layers,
1211 physical_layers,
1212 loop_final_norm,
1213 vocab_size,
1214 rms_eps,
1215 rope_base,
1216 norm_style,
1217 rotary_dim: head_dim,
1218 attention_heads_per_layer: None,
1219 vmf_cfg: None,
1220 gdn_cfg: None,
1221 kda_cfg: None,
1222 g3n: None,
1223 dsv4: None,
1224 dsv4_mtp: Vec::new(),
1225 dspark: None,
1226 dspark_pending: Vec::new(),
1227 dspark_hist: Vec::new(),
1228 dspark_real: Vec::new(),
1229 dspark_trunk_picks: Vec::new(),
1230 dspark_exp: Vec::new(),
1231 dspark_draft_ns: 0,
1232 logit_multiplier: None,
1233 cancel: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
1234 kv_history: Vec::new(),
1235 short_conv_cfg: None,
1236 mtp: None,
1237 speculative: std::env::var("CMF_MTP").map(|v| v != "0").unwrap_or(true),
1238 rng,
1239 sampler_scratch: SamplerScratch::default(),
1240 inv_freq,
1241 ws: ForwardScratch::new(hidden_size),
1242 pool,
1243 model: None,
1244 dyn_force_f32: false,
1245 dyn_skill_layers: Vec::new(),
1246 dyn_active: None,
1247 dyn_blend_loaded: false,
1248 dyn_phi_layer: None,
1249 dyn_phi_ema: Vec::new(),
1250 dyn_phi_seen: 0,
1251 dyn_router: None,
1252 o1_cfg: None,
1253 o1_epoch: 0,
1254 o1_flags: Vec::new(),
1255 trace: false,
1256 calib_temp: 1.0,
1257 confidence_on: true,
1258 embed_multiplier: 1.0,
1259 attn_scale: 1.0 / (head_dim as f32).sqrt(),
1260 swa: None,
1261 sliding_layers: None,
1262 inv_freq_local: None,
1263 rotary_dim_local: None,
1264 rope_scale: 1.0,
1265 rope_scale_local: 1.0,
1266 global_attn: None,
1267 inv_freq_global: None,
1268 attn_v_norm: false,
1269 final_softcap: None,
1270 attn_softcap: 0.0,
1271 graph_want_logits: false,
1272 graph_logits: None,
1273 graph_kv_id: {
1274 static NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
1275 NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
1276 },
1277 }
1278 }
1279
1280 pub fn set_o1(&mut self, cfg: Option<crate::nystrom::O1Cfg>) {
1287 self.o1_flags = match &cfg {
1288 Some(c) => {
1289 let mut flags = c.layer_flags(self.num_layers);
1290 for (li, f) in flags.iter_mut().enumerate() {
1291 if *f
1292 && !matches!(
1293 self.weights.layers[self.phys_layer(li)].attn,
1294 AttnKind::Full { .. }
1295 )
1296 {
1297 *f = false;
1298 }
1299 }
1300 flags
1301 }
1302 None => Vec::new(),
1303 };
1304 if let Some(c) = &cfg {
1305 let n = self.o1_flags.iter().filter(|&&f| f).count();
1306 tracing::info!(
1307 "o1 nystrom attention: {n}/{} layer(s), m={} w={} sink={} rect={:?}",
1308 self.num_layers,
1309 c.m,
1310 c.w,
1311 c.sink,
1312 c.rect
1313 );
1314 }
1315 self.o1_cfg = cfg;
1316 }
1317
1318 pub fn o1_active(&self) -> bool {
1320 self.o1_cfg.is_some() && self.o1_flags.iter().any(|&f| f)
1321 }
1322
1323 fn o1_begin(&mut self) {
1325 if let Some(c) = &self.o1_cfg {
1326 let (m, w, sink, rect) = (c.m, c.w, c.sink, c.rect);
1327 for (li, &f) in self.o1_flags.iter().enumerate() {
1328 if f {
1329 self.kv_cache.layers[li].o1_begin(m, w, sink, rect);
1330 }
1331 }
1332 }
1333 }
1334
1335 fn o1_seal(&mut self) {
1338 self.o1_epoch = self.o1_epoch.wrapping_add(1);
1339 if self.o1_cfg.is_none() {
1340 return;
1341 }
1342 for li in 0..self.num_layers {
1343 if self.o1_flags.get(li).copied().unwrap_or(false) {
1344 self.kv_cache.layers[li].o1_seal(self.num_heads);
1345 }
1346 }
1347 }
1348
1349 pub fn set_trace(&mut self, on: bool) {
1351 self.trace = on;
1352 }
1353
1354 pub fn set_sampler_config(&mut self, config: SamplerConfig) {
1357 self.rng = match config.seed {
1358 Some(seed) => SplitMix64::new(seed),
1359 None => SplitMix64::from_entropy(),
1360 };
1361 self.sampler_config = config;
1362 }
1363
1364 pub fn set_confidence(&mut self, on: bool) {
1369 self.confidence_on = on;
1370 }
1371
1372 pub fn set_calib_temp(&mut self, t: f32) {
1375 self.calib_temp = if t > 1e-3 { t } else { 1.0 };
1376 }
1377
1378 pub fn calib_temp(&self) -> f32 {
1380 self.calib_temp
1381 }
1382
1383 pub fn set_rotary(&mut self, rotary_dim: usize, base: f32) {
1386 self.rotary_dim = rotary_dim.min(self.head_dim);
1387 self.inv_freq = std::sync::Arc::new(attention::rope_inv_freq(self.rotary_dim, base));
1388 }
1389
1390 fn attn_cfg(&self, position: usize) -> QwenAttnCfg<'_> {
1391 QwenAttnCfg {
1392 num_heads: self.num_heads,
1393 num_kv_heads: self.num_kv_heads,
1394 head_dim: self.head_dim,
1395 hidden_size: self.hidden_size,
1396 position,
1397 inv_freq: &self.inv_freq,
1398 rotary_dim: self.rotary_dim,
1399 scale: self.attn_scale,
1400 softcap: self.attn_softcap,
1401 window: None,
1402 v_norm: false,
1403 q_norm: None,
1404 k_norm: None,
1405 output_gate: false,
1406 softplus_gate: None,
1407 rope_scale: self.rope_scale,
1408 bias: None,
1409 rms_eps: self.rms_eps,
1410 norm_style: self.norm_style,
1411 pool: self.pool.as_deref(),
1412 }
1413 }
1414
1415 pub fn generate(
1417 &mut self,
1418 prompt: &str,
1419 max_tokens: usize,
1420 task_mask: Option<&TaskMask>,
1421 on_token: Option<TokenCallback>,
1422 ) -> Result<GenerateResult, String> {
1423 let input_ids = self.tokenizer.with_bos(self.tokenizer.encode(prompt));
1424 self.generate_from_ids(&input_ids, max_tokens, task_mask, on_token)
1425 }
1426
1427 pub fn generate_from_ids(
1435 &mut self,
1436 input_ids: &[u32],
1437 max_tokens: usize,
1438 task_mask: Option<&TaskMask>,
1439 mut on_token: Option<TokenCallback>,
1440 ) -> Result<GenerateResult, String> {
1441 if std::env::var("CMF_TRACE_H").is_ok() {
1442 eprintln!("input_ids: {input_ids:?}");
1443 }
1444 if input_ids.is_empty() {
1445 return Err("empty prompt: nothing to generate from".to_string());
1446 }
1447
1448 let reuse_from = {
1456 let on = !std::env::var("CMF_KV_REUSE").is_ok_and(|v| v == "0");
1457 let h = &self.kv_history;
1458 if on
1459 && task_mask.is_none()
1460 && self.mtp.is_none()
1461 && self.o1_cfg.is_none()
1462 && !h.is_empty()
1463 && h.len() < input_ids.len()
1464 && input_ids[..h.len()] == h[..]
1465 {
1466 h.len()
1467 } else {
1468 0
1469 }
1470 };
1471 if reuse_from == 0 {
1472 self.kv_cache.clear();
1474 self.kv_history.clear();
1475 crate::gpu::graph_kv_reset(self.graph_kv_id);
1476 } else if std::env::var("CMF_PREFILL_PROF").is_ok() {
1477 eprintln!(
1478 "kv-reuse: {} of {} prompt positions already cached",
1479 reuse_from,
1480 input_ids.len()
1481 );
1482 }
1483 crate::gpu::graph_race_begin_generation();
1484 self.o1_begin();
1485
1486 let graph_on = std::env::var("CMF_GPU_WGPU_GRAPH")
1492 .map(|v| v != "0")
1493 .unwrap_or_else(|_| {
1494 crate::gpu::wgpu_graph_default()
1498 });
1499 let graph_spec = self.speculative
1506 && graph_on
1507 && self.mtp.is_some()
1508 && task_mask.is_none()
1509 && !self.o1_active()
1510 && self.sampler_config.temperature < 1e-6
1511 && self.sampler_config.repetition_penalty == 1.0
1512 && std::env::var("CMF_GRAPH_SPEC").is_ok_and(|v| v != "0");
1513 let pair_pays = self.gdn_cfg.is_none()
1520 || std::env::var("CMF_MTP").as_deref() == Ok("1");
1521 let spec_active = self.speculative
1522 && self.mtp.is_some()
1523 && task_mask.is_none()
1524 && !self.o1_active()
1525 && ((!graph_on && pair_pays) || graph_spec)
1526 && self.sampler_config.temperature < 1e-6;
1527 let mut mtp = if spec_active { self.mtp.take() } else { None };
1530 if std::env::var("CMF_MTP_CHAIN_PROBE").is_ok() {
1531 eprintln!(
1532 "mtp-probe gate: spec_active={spec_active} mtp={} speculative={} graph_on={graph_on} temp_ok={}",
1533 mtp.is_some(),
1534 self.speculative,
1535 self.sampler_config.temperature < 1e-6,
1536 );
1537 }
1538 if let Some(m) = &mut mtp {
1539 m.kv.clear();
1540 }
1541 let mut router = if mtp.is_none() {
1545 self.dyn_router.take()
1546 } else {
1547 None
1548 };
1549 if let Some(r) = &mut router {
1550 r.reset(); self.dyn_phi_seen = 0; let _ = self.set_active_skill(None);
1553 }
1554
1555 let mut all_ids = input_ids.to_vec();
1556 let mut generated = 0usize;
1557 let mut finish_reason = "max_tokens".to_string();
1558 let mut drafted = 0usize;
1559 let mut accepted = 0usize;
1560 let mut confidence: Vec<f32> = Vec::new();
1561 let trace_on = self.trace;
1562 let calib_temp = self.calib_temp;
1563 let mut traces: Vec<TokenTrace> = Vec::new();
1564
1565 let mut hidden = vec![0.0f32; self.hidden_size];
1571 let mut pos = reuse_from;
1572 let fuse_lm = mtp.is_none()
1581 && router.is_none()
1582 && std::env::var("CMF_GPU_LMHEAD").as_deref() != Ok("0");
1583 self.graph_logits = None;
1584 self.graph_want_logits = false;
1585 let _tpf = std::time::Instant::now();
1586 let batch_k = std::env::var("CMF_BATCH_K")
1587 .ok()
1588 .and_then(|v| v.parse::<usize>().ok())
1589 .unwrap_or(0);
1590 while self.dsv4.is_some()
1601 && mtp.is_none()
1602 && pos < input_ids.len()
1603 && !self.cancel.load(std::sync::atomic::Ordering::Relaxed)
1604 {
1605 let end = (pos + prefill_chunk()).min(input_ids.len());
1606 let ids: Vec<u32> = input_ids[pos..end].to_vec();
1607 let mut lg = Vec::new();
1608 if let Some(b) = &mut self.dsv4 {
1609 let (g, layers, cfg, st) = (&b.0, &b.1, b.2, &mut b.3);
1610 crate::dsv4::forward_chunk(
1611 g,
1612 layers,
1613 &cfg,
1614 st,
1615 &ids,
1616 pos,
1617 &self.inv_freq,
1618 self.pool.as_deref(),
1619 &mut lg,
1620 end == input_ids.len(),
1621 );
1622 }
1623 if end == input_ids.len() {
1624 self.graph_logits = Some(lg);
1625 }
1626 pos = end;
1627 hidden = vec![0.0; self.hidden_size];
1628 }
1629 let dyn_prefill = router.is_some();
1634 let graph_prefill = self.graph_prefill_preferred();
1640 if task_mask.is_none()
1641 && !dyn_prefill
1642 && !graph_prefill
1643 && self.can_prefill_batched()
1644 && self.g3n.is_none()
1645 && input_ids.len() > 2
1646 {
1647 let chunk = prefill_chunk();
1653 let hs = self.hidden_size;
1654 while pos < input_ids.len() && !self.cancel.load(std::sync::atomic::Ordering::Relaxed) {
1655 let end = (pos + chunk).min(input_ids.len());
1656 let hb = self.prefill_batch(&input_ids[pos..end], pos);
1657 if let Some(m) = &mut mtp {
1658 let probe: usize = std::env::var("CMF_MTP_CHAIN_PROBE")
1659 .ok()
1660 .and_then(|v| v.parse().ok())
1661 .unwrap_or(0);
1662 for p in pos..end {
1663 if p + 1 < input_ids.len() {
1664 if probe >= 1 && p + 2 < input_ids.len() {
1665 let (d1, mut hx) = self.mtp_step_h(
1669 m,
1670 &hb[(p - pos) * hs..(p - pos + 1) * hs],
1671 input_ids[p + 1],
1672 p,
1673 );
1674 let mut ok = d1 == input_ids[p + 2];
1675 Self::chain_probe_note(0, ok);
1676 let mut d_prev = d1;
1677 let mut extra = 0usize;
1678 for j in 1..probe {
1679 if p + 2 + j >= input_ids.len() {
1680 break;
1681 }
1682 let (dj, hj) =
1683 self.mtp_step_h(m, &hx, d_prev, p + 1 + j);
1684 extra += 1;
1685 ok = ok && dj == input_ids[p + 2 + j];
1686 Self::chain_probe_note(j, ok);
1687 d_prev = dj;
1688 hx = hj;
1689 }
1690 m.kv.truncate_last(extra);
1691 } else {
1692 let _ = self.mtp_step(
1693 m,
1694 &hb[(p - pos) * hs..(p - pos + 1) * hs],
1695 input_ids[p + 1],
1696 p,
1697 );
1698 }
1699 }
1700 }
1701 }
1702 hidden.copy_from_slice(&hb[(end - pos - 1) * hs..]);
1703 pos = end;
1704 }
1705 }
1706 let pair_off = std::env::var("CMF_PAIR").is_ok_and(|v| v == "0");
1707 if task_mask.is_none()
1708 && !dyn_prefill
1709 && !graph_prefill
1710 && !pair_off
1711 && self.pair_supported()
1712 {
1713 while pos + 1 < input_ids.len()
1714 && !self.cancel.load(std::sync::atomic::Ordering::Relaxed)
1715 {
1716 let e1 = self.embed_single(input_ids[pos]);
1717 let e2 = self.embed_single(input_ids[pos + 1]);
1718 let (h1, h2) = self.forward_pair(&e1, &e2, pos);
1719 self.commit_linear_scratch();
1721 if let Some(m) = &mut mtp {
1722 let _ = self.mtp_step(m, &h1, input_ids[pos + 1], pos);
1723 if pos + 2 < input_ids.len() {
1724 let probe: usize = std::env::var("CMF_MTP_CHAIN_PROBE")
1725 .ok()
1726 .and_then(|v| v.parse().ok())
1727 .unwrap_or(0);
1728 if probe >= 1 && pos + 3 < input_ids.len() {
1729 let (d1, mut hx) =
1733 self.mtp_step_h(m, &h2, input_ids[pos + 2], pos + 1);
1734 let mut ok = d1 == input_ids[pos + 3];
1735 Self::chain_probe_note(0, ok);
1736 let mut d_prev = d1;
1737 let mut extra = 0usize;
1738 for j in 1..probe {
1739 if pos + 3 + j >= input_ids.len() {
1740 break;
1741 }
1742 let (dj, hj) =
1743 self.mtp_step_h(m, &hx, d_prev, pos + 2 + j);
1744 extra += 1;
1745 ok = ok && dj == input_ids[pos + 3 + j];
1746 Self::chain_probe_note(j, ok);
1747 d_prev = dj;
1748 hx = hj;
1749 }
1750 m.kv.truncate_last(extra);
1751 } else {
1752 let _ = self.mtp_step(m, &h2, input_ids[pos + 2], pos + 1);
1753 }
1754 }
1755 }
1756 hidden = h2;
1757 pos += 2;
1758 }
1759 }
1760 if batch_k > 0
1769 && graph_prefill
1770 && task_mask.is_none()
1771 && !self.o1_active()
1772 && mtp.is_none()
1773 && !dyn_prefill
1774 && pos + 1 < input_ids.len()
1775 {
1776 let hs = self.hidden_size;
1777 let chunk = batch_k;
1778 while pos < input_ids.len() {
1779 let end = (pos + chunk).min(input_ids.len());
1780 let bk = end - pos;
1781 let mut hiddens = vec![0f32; bk * hs];
1782 for (j, &id) in input_ids[pos..end].iter().enumerate() {
1783 hiddens[j * hs..(j + 1) * hs].copy_from_slice(&self.embed_single(id));
1784 }
1785 let positions: Vec<usize> = (pos..end).collect();
1786 let t_chunk = std::time::Instant::now();
1787 let ok_b = self.try_batch_graph_wgpu(&mut hiddens, &positions, bk, None);
1788 if std::env::var("CMF_GRAPH_PROF").is_ok() {
1789 let ms = t_chunk.elapsed().as_secs_f64() * 1000.0;
1790 eprintln!(
1791 "batch-chunk: k={bk} ok={ok_b} {ms:.1} ms ({:.1} tok/s)",
1792 bk as f64 / (ms / 1000.0)
1793 );
1794 }
1795 {
1796 use std::sync::atomic::{AtomicBool, Ordering};
1797 static SAID: AtomicBool = AtomicBool::new(false);
1798 if !SAID.swap(true, Ordering::Relaxed) {
1799 if ok_b {
1800 tracing::info!("batched prefill: ACTIVE (k={bk})");
1801 } else {
1802 tracing::warn!("batched prefill declined — per-position graph");
1803 }
1804 }
1805 }
1806 if ok_b {
1807 hidden.copy_from_slice(&hiddens[(bk - 1) * hs..]);
1808 pos = end;
1809 } else {
1810 break; }
1812 }
1813 }
1814 while pos < input_ids.len() && !self.cancel.load(std::sync::atomic::Ordering::Relaxed) {
1815 self.graph_want_logits = fuse_lm && pos + 1 == input_ids.len();
1816 hidden = self.forward_layers(&self.embed_single(input_ids[pos]), pos, task_mask);
1817 if let Some(m) = &mut mtp {
1818 if pos + 1 < input_ids.len() {
1819 let probe: usize = std::env::var("CMF_MTP_CHAIN_PROBE")
1825 .ok()
1826 .and_then(|v| v.parse().ok())
1827 .unwrap_or(0);
1828 if probe >= 1 && pos + 2 < input_ids.len() {
1829 let (d1, mut hx) = self.mtp_step_h(m, &hidden, input_ids[pos + 1], pos);
1830 let mut ok = d1 == input_ids[pos + 2];
1831 Self::chain_probe_note(0, ok);
1832 let mut d_prev = d1;
1833 let mut extra = 0usize;
1834 for j in 1..probe {
1835 if pos + 2 + j >= input_ids.len() {
1836 break;
1837 }
1838 let (dj, hj) =
1839 self.mtp_step_h(m, &hx, d_prev, pos + 1 + j);
1840 extra += 1;
1841 ok = ok && dj == input_ids[pos + 2 + j];
1842 Self::chain_probe_note(j, ok);
1843 d_prev = dj;
1844 hx = hj;
1845 }
1846 m.kv.truncate_last(extra);
1849 } else {
1850 let _ = self.mtp_step(m, &hidden, input_ids[pos + 1], pos);
1851 }
1852 }
1853 }
1854 pos += 1;
1855 }
1856 if std::env::var("CMF_PREFILL_PROF").is_ok() {
1857 eprintln!(
1858 "prefill: {} tokens in {:.1} ms (batch_k={batch_k})",
1859 input_ids.len(),
1860 _tpf.elapsed().as_secs_f64() * 1000.0
1861 );
1862 }
1863 if self
1866 .cancel
1867 .swap(false, std::sync::atomic::Ordering::Relaxed)
1868 {
1869 self.kv_history.clear();
1870 if let Some(m) = mtp {
1871 self.mtp = Some(m);
1872 }
1873 return Ok(GenerateResult {
1874 text: String::new(),
1875 token_ids: Vec::new(),
1876 prompt_tokens: input_ids.len(),
1877 tokens_generated: 0,
1878 finish_reason: "cancelled".to_string(),
1879 mtp_drafted: 0,
1880 mtp_accepted: 0,
1881 token_confidence: Vec::new(),
1882 traces: Vec::new(),
1883 });
1884 }
1885
1886 self.o1_seal();
1889
1890 macro_rules! commit {
1892 ($id:expr) => {{
1893 all_ids.push($id);
1894 generated += 1;
1895 if self.tokenizer.is_eos($id) {
1896 finish_reason = "stop".to_string();
1897 false
1898 } else {
1899 let token_text = self.tokenizer.decode_token($id);
1900 let mut go = true;
1901 if let Some(ref mut cb) = on_token {
1902 if !cb(&token_text) {
1903 finish_reason = "cancelled".to_string();
1904 go = false;
1905 }
1906 }
1907 go
1908 }
1909 }};
1910 }
1911
1912 let mut next_pos = input_ids.len();
1914 'decode: while generated < max_tokens {
1915 if self
1916 .cancel
1917 .swap(false, std::sync::atomic::Ordering::Relaxed)
1918 {
1919 finish_reason = "cancelled".to_string();
1920 break 'decode;
1921 }
1922 let mut logits = match self.graph_logits.take() {
1923 Some(lg) => lg,
1924 None => {
1925 inference::rms_norm_into(
1926 &hidden,
1927 &self.weights.final_norm,
1928 self.rms_eps,
1929 self.norm_style,
1930 &mut self.ws.n1,
1931 );
1932 self.lm_head_forward(&self.ws.n1)
1933 }
1934 };
1935 let t_next = sampler::sample_with_scratch(
1936 &logits,
1937 &self.sampler_config,
1938 &all_ids,
1939 &mut self.rng,
1940 &mut self.sampler_scratch,
1941 );
1942 if self.confidence_on {
1943 confidence.push(top1_prob_t(&logits, t_next, calib_temp));
1944 }
1945 attention::recycle_buf(&mut logits);
1946 if trace_on {
1947 let skill = router.as_ref().and_then(|r| r.active_id());
1951 traces.push(TokenTrace {
1952 t: generated,
1953 token_id: t_next,
1954 confidence: confidence.last().copied().unwrap_or(0.0),
1955 active_skill: skill,
1956 recon: None,
1957 switched: false,
1958 });
1959 }
1960 if !commit!(t_next) {
1961 break 'decode;
1962 }
1963 if generated >= max_tokens {
1964 break 'decode;
1965 }
1966
1967 if self.kv_cache.needs_eviction() {
1968 let keep = (self.kv_cache.max_seq_len / 2).max(1);
1969 self.kv_cache.evict(keep);
1970 }
1971
1972 match &mut mtp {
1973 #[cfg(feature = "gpu")]
1975 Some(m) if graph_spec && generated + 1 < max_tokens && next_pos > 0 => {
1976 if let Some((extra, n_pos, new_h)) = self.graph_spec_step(
1977 m,
1978 &hidden,
1979 t_next,
1980 next_pos,
1981 &mut drafted,
1982 &mut accepted,
1983 ) {
1984 next_pos = n_pos;
1985 hidden = new_h;
1986 let mut stopped = false;
1987 for &id in &extra {
1988 if self.confidence_on {
1989 confidence.push(0.0);
1990 }
1991 if !commit!(id) {
1992 stopped = true;
1993 break;
1994 }
1995 }
1996 if stopped {
1997 break 'decode;
1998 }
1999 continue 'decode;
2000 }
2001 hidden = self.forward_layers(&self.embed_single(t_next), next_pos, task_mask);
2003 next_pos += 1;
2004 continue 'decode;
2005 }
2006 Some(m) if !graph_spec && generated + 1 < max_tokens => {
2008 let draft = self.mtp_step(m, &hidden, t_next, next_pos - 1);
2009 drafted += 1;
2010 let emb1 = self.embed_single(t_next);
2011 let emb2 = self.embed_single(draft);
2012 let (h1, h2) = self.forward_pair(&emb1, &emb2, next_pos);
2013
2014 inference::rms_norm_into(
2015 &h1,
2016 &self.weights.final_norm,
2017 self.rms_eps,
2018 self.norm_style,
2019 &mut self.ws.n1,
2020 );
2021 let mut logits1 = self.lm_head_forward(&self.ws.n1);
2022 let t_after = sampler::sample_with_scratch(
2023 &logits1,
2024 &self.sampler_config,
2025 &all_ids,
2026 &mut self.rng,
2027 &mut self.sampler_scratch,
2028 );
2029 if self.confidence_on {
2030 confidence.push(top1_prob_t(&logits1, t_after, calib_temp));
2031 }
2032 attention::recycle_buf(&mut logits1);
2033 if trace_on {
2034 traces.push(TokenTrace {
2037 t: generated,
2038 token_id: t_after,
2039 confidence: confidence.last().copied().unwrap_or(0.0),
2040 active_skill: None,
2041 recon: None,
2042 switched: false,
2043 });
2044 }
2045 let stop = !commit!(t_after);
2046
2047 if t_after == draft {
2048 accepted += 1;
2049 self.commit_linear_scratch();
2050 let _ = self.mtp_step(m, &h1, t_after, next_pos);
2051 hidden = h2;
2052 next_pos += 2;
2053 } else {
2054 for layer in &mut self.kv_cache.layers {
2056 layer.truncate_last(1);
2057 }
2058 if !stop {
2059 let _ = self.mtp_step(m, &h1, t_after, next_pos);
2060 hidden = self.forward_layers(
2061 &self.embed_single(t_after),
2062 next_pos + 1,
2063 None,
2064 );
2065 }
2066 next_pos += 2;
2067 }
2068 if stop {
2069 break 'decode;
2070 }
2071 }
2072 _ => {
2074 #[cfg(feature = "gpu")]
2079 if Self::dsv4_spec_on() && self.dsv4.is_some() {
2080 static SAID: std::sync::Once = std::sync::Once::new();
2081 SAID.call_once(|| {
2082 eprintln!(
2083 "dsv4-spec гейт: mtp={} mask={} router={} trace={} temp={} rep={} ",
2084 !self.dsv4_mtp.is_empty(),
2085 task_mask.is_none(),
2086 router.is_none(),
2087 !trace_on,
2088 self.sampler_config.temperature < 1e-6,
2089 self.sampler_config.repetition_penalty == 1.0,
2090 );
2091 });
2092 }
2093 #[cfg(feature = "gpu")]
2094 if Self::dsv4_spec_on()
2095 && self.dsv4.is_some()
2096 && !self.dsv4_mtp.is_empty()
2097 && task_mask.is_none()
2098 && router.is_none()
2099 && !trace_on
2100 && self.sampler_config.temperature < 1e-6
2101 && self.sampler_config.repetition_penalty == 1.0
2102 && generated + 1 < max_tokens
2103 && all_ids.len() >= 2
2104 {
2105 let tip_token = all_ids[all_ids.len() - 2];
2106 if let Some((extra, n_pos)) = self.dsv4_spec_step(
2107 tip_token,
2108 t_next,
2109 next_pos,
2110 &mut drafted,
2111 &mut accepted,
2112 ) {
2113 next_pos = n_pos;
2114 let mut stopped = false;
2115 for &id in &extra {
2116 if self.confidence_on {
2117 confidence.push(0.0);
2118 }
2119 if !commit!(id) {
2120 stopped = true;
2121 break;
2122 }
2123 }
2124 if stopped {
2125 break 'decode;
2126 }
2127 continue 'decode;
2128 }
2129 }
2130 self.graph_want_logits = fuse_lm;
2131 let mut t_fwd = t_next;
2137 let pure_greedy = self.sampler_config.temperature < 1e-6
2138 && self.sampler_config.repetition_penalty == 1.0
2139 && self.sampler_config.suppress_tokens.is_empty();
2140 let burst_k = std::env::var("CMF_MULTISTEP")
2145 .ok()
2146 .and_then(|v| v.parse::<usize>().ok())
2147 .unwrap_or(0);
2148 if pure_greedy
2149 && burst_k >= 1
2150 && fuse_lm
2151 && task_mask.is_none()
2152 && router.is_none()
2153 && !trace_on
2154 && !self.confidence_on
2155 {
2156 let mut stopped = false;
2157 loop {
2158 let room = max_tokens.saturating_sub(generated);
2159 if room <= 2 {
2160 break;
2161 }
2162 let k = burst_k.min(room - 1);
2163 if k < 1 {
2164 break;
2165 }
2166 let Some(ids) = self.try_multi_burst(t_fwd, next_pos, k) else {
2167 break;
2168 };
2169 next_pos += k;
2170 for &id in &ids {
2171 if !commit!(id) {
2172 stopped = true;
2173 break;
2174 }
2175 }
2176 if stopped {
2177 break;
2178 }
2179 t_fwd = *ids.last().unwrap();
2180 }
2181 if stopped {
2182 break 'decode;
2183 }
2184 }
2185 hidden = self.forward_layers(&self.embed_single(t_fwd), next_pos, task_mask);
2186 next_pos += 1;
2187 if let Some(r) = &mut router {
2190 let phi = self.dyn_phi_ema.clone();
2191 let decision = r.step(&phi, generated);
2192 if let Some(new_active) = decision {
2193 let _ = self.set_active_skill(new_active);
2194 }
2195 if trace_on {
2198 if let Some(last) = traces.last_mut() {
2199 let e = r.last_best_e();
2200 last.recon = e.is_finite().then_some(e);
2201 last.switched = decision.is_some();
2202 }
2203 }
2204 }
2205 }
2206 }
2207 }
2208
2209 self.graph_want_logits = false;
2210 self.graph_logits = None;
2211 if router.is_some() {
2213 let _ = self.set_active_skill(None);
2214 }
2215 self.dyn_router = router.or(self.dyn_router.take());
2216 self.mtp = mtp.or(self.mtp.take());
2217
2218 let output_ids = &all_ids[input_ids.len()..];
2219 let forwarded = input_ids.len() + output_ids.len().saturating_sub(1);
2223 self.kv_history = all_ids[..forwarded.min(all_ids.len())].to_vec();
2224 confidence.truncate(output_ids.len()); traces.truncate(output_ids.len());
2226 Ok(GenerateResult {
2227 text: self.tokenizer.decode(output_ids),
2228 token_ids: output_ids.to_vec(),
2229 prompt_tokens: input_ids.len(),
2230 tokens_generated: generated,
2231 finish_reason,
2232 mtp_drafted: drafted,
2233 mtp_accepted: accepted,
2234 token_confidence: confidence,
2235 traces,
2236 })
2237 }
2238
2239 fn mtp_step(
2243 &mut self,
2244 m: &mut MtpModule,
2245 hidden: &[f32],
2246 next_token: u32,
2247 position: usize,
2248 ) -> u32 {
2249 self.mtp_step_h(m, hidden, next_token, position).0
2250 }
2251
2252 fn chain_probe_note(depth: usize, prefix_ok: bool) {
2256 use std::sync::Mutex;
2257 static T: Mutex<Vec<(u64, u64)>> = Mutex::new(Vec::new());
2258 let mut t = T.lock().unwrap();
2259 if t.len() <= depth {
2260 t.resize(depth + 1, (0, 0));
2261 }
2262 t[depth].0 += 1;
2263 t[depth].1 += prefix_ok as u64;
2264 if depth == 0 && t[0].0 % 128 == 0 {
2265 let line: Vec<String> = t
2266 .iter()
2267 .enumerate()
2268 .map(|(d, (n, k))| format!("d{}={:.0}%({n})", d + 1, 100.0 * *k as f64 / (*n).max(1) as f64))
2269 .collect();
2270 eprintln!("mtp-chain: {}", line.join(" "));
2271 }
2272 }
2273
2274 fn mtp_step_h(
2278 &mut self,
2279 m: &mut MtpModule,
2280 hidden: &[f32],
2281 next_token: u32,
2282 position: usize,
2283 ) -> (u32, Vec<f32>) {
2284 let e = self.embed_single(next_token);
2288 let mut cat = vec![0.0f32; 2 * self.hidden_size];
2289 let (cat_e, cat_h) = cat.split_at_mut(self.hidden_size);
2290 inference::rms_norm_into(&e, &m.enorm, self.rms_eps, self.norm_style, cat_e);
2291 inference::rms_norm_into(hidden, &m.hnorm, self.rms_eps, self.norm_style, cat_h);
2292 let mut x = vec![0.0f32; self.hidden_size];
2293 m.eh_proj.matvec(&cat, &mut x, self.pool.as_deref());
2294
2295 let lw = &m.layer;
2297 inference::rms_norm_into(
2298 &x,
2299 &lw.input_norm,
2300 self.rms_eps,
2301 self.norm_style,
2302 &mut self.ws.n1,
2303 );
2304 let attn = match &lw.attn {
2305 AttnKind::Mla(_) => unreachable!("MLA has no MTP/pair path"),
2307 AttnKind::Kda(_) => unreachable!("KDA has no MTP/pair path"),
2308 AttnKind::Full {
2309 wq,
2310 wk,
2311 wv,
2312 wo,
2313 q_norm,
2314 k_norm,
2315 output_gate,
2316 softplus_gate,
2317 bias,
2318 } => {
2319 let mut cfg = self.attn_cfg(position);
2320 cfg.q_norm = q_norm.as_deref();
2321 cfg.k_norm = k_norm.as_deref();
2322 cfg.output_gate = *output_gate;
2323 cfg.softplus_gate = softplus_gate
2324 .as_ref()
2325 .map(|(gate, per_head)| (gate, *per_head));
2326 cfg.bias = bias
2327 .as_ref()
2328 .map(|(q, k, v)| (q.as_slice(), k.as_slice(), v.as_slice()));
2329 attention::qwen_attention(&self.ws.n1, wq, wk, wv, wo, &mut m.kv, &cfg)
2330 }
2331 AttnKind::Linear(_) | AttnKind::LinearGdn(_) | AttnKind::ShortConv(_) => {
2332 unreachable!("MTP block is full attention")
2333 }
2334 };
2335 for (i, &a) in attn.iter().enumerate() {
2336 x[i] += a;
2337 }
2338 inference::rms_norm_into(
2339 &x,
2340 &lw.post_norm,
2341 self.rms_eps,
2342 self.norm_style,
2343 &mut self.ws.p1,
2344 );
2345 let ffn = ffn_forward(&lw.ffn, &self.ws.p1, self.pool.as_deref(), None);
2346 for (i, &f) in ffn.iter().enumerate() {
2347 x[i] += f;
2348 }
2349
2350 inference::rms_norm_into(
2351 &x,
2352 &m.final_norm,
2353 self.rms_eps,
2354 self.norm_style,
2355 &mut self.ws.n1,
2356 );
2357 let mut lg = self.lm_head_forward(&self.ws.n1);
2358 let draft = sampler::argmax(&lg);
2359 attention::recycle_buf(&mut lg);
2360 (draft, x)
2361 }
2362
2363 fn mtp_warm(&mut self, m: &mut MtpModule, hidden: &[f32], next_token: u32, position: usize) {
2367 let e = self.embed_single(next_token);
2368 let mut cat = vec![0.0f32; 2 * self.hidden_size];
2369 let (cat_e, cat_h) = cat.split_at_mut(self.hidden_size);
2370 inference::rms_norm_into(&e, &m.enorm, self.rms_eps, self.norm_style, cat_e);
2371 inference::rms_norm_into(hidden, &m.hnorm, self.rms_eps, self.norm_style, cat_h);
2372 let mut x = vec![0.0f32; self.hidden_size];
2373 m.eh_proj.matvec(&cat, &mut x, self.pool.as_deref());
2374 inference::rms_norm_into(&x, &m.layer.input_norm, self.rms_eps, self.norm_style, &mut self.ws.n1);
2375 let attn = match &m.layer.attn {
2376 AttnKind::Full { wq, wk, wv, wo, q_norm, k_norm, output_gate, softplus_gate, bias } => {
2377 let mut cfg = self.attn_cfg(position);
2378 cfg.q_norm = q_norm.as_deref();
2379 cfg.k_norm = k_norm.as_deref();
2380 cfg.output_gate = *output_gate;
2381 cfg.softplus_gate = softplus_gate.as_ref().map(|(g, p)| (g, *p));
2382 cfg.bias = bias.as_ref().map(|(q, k, v)| (q.as_slice(), k.as_slice(), v.as_slice()));
2383 attention::qwen_attention(&self.ws.n1, wq, wk, wv, wo, &mut m.kv, &cfg)
2384 }
2385 _ => return,
2386 };
2387 let _ = attn;
2388 }
2389
2390 #[cfg(feature = "gpu")]
2397 #[allow(clippy::too_many_arguments)]
2398 fn graph_spec_step(
2399 &mut self,
2400 m: &mut MtpModule,
2401 hidden: &[f32],
2402 t_next: u32,
2403 next_pos: usize,
2404 drafted: &mut usize,
2405 accepted: &mut usize,
2406 ) -> Option<(Vec<u32>, usize, Vec<f32>)> {
2407 let k_spec: usize = std::env::var("CMF_GRAPH_SPEC_K")
2408 .ok()
2409 .and_then(|v| v.parse().ok())
2410 .filter(|&v| (1..=8).contains(&v))
2411 .unwrap_or(2);
2412 if next_pos == 0 {
2413 return None;
2414 }
2415 let t_round = std::time::Instant::now();
2416 let mut drafts = Vec::with_capacity(k_spec);
2421 let (d1, mut hx) = self.mtp_step_h(m, hidden, t_next, next_pos - 1);
2422 drafts.push(d1);
2423 for j in 1..k_spec {
2424 let (dj, hj) = self.mtp_step_h(m, &hx, drafts[j - 1], next_pos - 1 + j);
2425 drafts.push(dj);
2426 hx = hj;
2427 }
2428 *drafted += k_spec;
2429 let t_draft = t_round.elapsed();
2430 let b = k_spec + 1;
2433 let mut hiddens = vec![0.0f32; b * self.hidden_size];
2434 for (i, &t) in std::iter::once(&t_next).chain(drafts.iter()).enumerate() {
2435 let e = self.embed_single(t);
2436 hiddens[i * self.hidden_size..(i + 1) * self.hidden_size].copy_from_slice(&e);
2437 }
2438 let positions: Vec<usize> = (next_pos..next_pos + b).collect();
2439 let (lm_gw, lm_rows) = {
2440 let (_, i, kind, rs) = self.weights.lm_head.graph_weight()?;
2441 (
2442 crate::gpu::GraphW { idx: i, kind, row_scale: rs, data: &[] },
2443 self.weights.lm_head.rows(),
2444 )
2445 };
2446 let mut logits = Vec::new();
2447 let final_norm = self.weights.final_norm.clone();
2448 let ok = self.try_batch_graph_wgpu(
2449 &mut hiddens,
2450 &positions,
2451 b,
2452 Some(crate::gpu::SpecTail {
2453 lm: lm_gw,
2454 lm_rows,
2455 final_norm: &final_norm,
2456 logits_out: &mut logits,
2457 }),
2458 );
2459 if !ok {
2460 m.kv.truncate_last(k_spec);
2463 return None;
2464 }
2465 if std::env::var("CMF_GRAPH_SPEC_TIME").is_ok() {
2466 eprintln!(
2467 "spec-round: draft {:.1} ms | verify {:.1} ms",
2468 t_draft.as_secs_f64() * 1e3,
2469 (t_round.elapsed() - t_draft).as_secs_f64() * 1e3,
2470 );
2471 }
2472 let ids: Vec<u32> = (0..b)
2474 .map(|i| sampler::argmax(&logits[i * lm_rows..(i + 1) * lm_rows]))
2475 .collect();
2476 let mut a = 0usize;
2477 while a < k_spec && ids[a] == drafts[a] {
2478 a += 1;
2479 }
2480 if a + 1 < b {
2482 crate::gpu::gdn_spec_restore(self.graph_kv_id, a);
2483 }
2484 *accepted += a;
2485 m.kv.truncate_last(k_spec.saturating_sub(1));
2488 for j in 0..a {
2489 let row = &hiddens[j * self.hidden_size..(j + 1) * self.hidden_size];
2490 let row = row.to_vec();
2491 self.mtp_warm(m, &row, ids[j], next_pos + j);
2492 }
2493 let mut row = logits[a * lm_rows..(a + 1) * lm_rows].to_vec();
2495 row.resize(self.vocab_size, 0.0);
2496 if let Some(c) = self.final_softcap {
2497 for l in row.iter_mut() {
2498 *l = c * (*l / c).tanh();
2499 }
2500 }
2501 self.graph_logits = Some(row);
2502 let new_hidden = hiddens[a * self.hidden_size..(a + 1) * self.hidden_size].to_vec();
2503 Some((drafts[..a].to_vec(), next_pos + a + 1, new_hidden))
2504 }
2505
2506 pub fn measure_pair_fusion(&mut self, iters: usize) -> (f64, f64) {
2515 if !self.pair_supported() {
2516 return (0.0, 0.0);
2517 }
2518 let emb1 = self.embed_single(1);
2519 let emb2 = self.embed_single(2);
2520 let pos = self.kv_cache.seq_len();
2521
2522 let t0 = std::time::Instant::now();
2523 for _ in 0..iters {
2524 let _ = self.forward_layers(&emb1, pos, None);
2525 let _ = self.forward_layers(&emb2, pos + 1, None);
2526 for l in &mut self.kv_cache.layers {
2527 l.truncate_last(2);
2528 }
2529 }
2530 let singles_ms = t0.elapsed().as_secs_f64() * 1000.0 / iters as f64;
2531
2532 let t1 = std::time::Instant::now();
2533 for _ in 0..iters {
2534 let _ = self.forward_pair(&emb1, &emb2, pos);
2535 for l in &mut self.kv_cache.layers {
2536 l.truncate_last(2);
2537 }
2538 }
2539 let pair_ms = t1.elapsed().as_secs_f64() * 1000.0 / iters as f64;
2540 (singles_ms, pair_ms)
2541 }
2542
2543 fn pair_supported(&self) -> bool {
2551 !self.weights.layers.is_empty()
2558 && self.g3n.is_none()
2559 && !self
2560 .weights
2561 .layers
2562 .iter()
2563 .any(|lw| matches!(&lw.attn, AttnKind::Mla(_) | AttnKind::Kda(_)))
2564 }
2565
2566 fn forward_pair(
2567 &mut self,
2568 emb1: &[f32],
2569 emb2: &[f32],
2570 position: usize,
2571 ) -> (Vec<f32>, Vec<f32>) {
2572 let mut h1 = emb1.to_vec();
2573 let mut h2 = emb2.to_vec();
2574 let (_nkv, _hd, hs, _rd, eps) = (
2575 self.num_kv_heads,
2576 self.head_dim,
2577 self.hidden_size,
2578 self.rotary_dim,
2579 self.rms_eps,
2580 );
2581 let pool = self.pool.clone();
2582
2583 for li in 0..self.num_layers {
2584 let lw = &self.weights.layers[self.phys_layer(li)];
2585 inference::rms_norm_into(
2588 &h1,
2589 &lw.input_norm,
2590 self.rms_eps,
2591 self.norm_style,
2592 &mut self.ws.n1,
2593 );
2594 inference::rms_norm_into(
2595 &h2,
2596 &lw.input_norm,
2597 self.rms_eps,
2598 self.norm_style,
2599 &mut self.ws.n2,
2600 );
2601
2602 let (a1, a2) = match &lw.attn {
2603 AttnKind::Mla(_) => unreachable!("MLA has no MTP/pair path"),
2604 AttnKind::Kda(_) => unreachable!("KDA has no MTP/pair path"),
2605 AttnKind::Linear(w) => {
2606 let cfg = self.vmf_cfg.expect("linear layer without vmf_cfg");
2607 let layer = &mut self.kv_cache.layers[li];
2608 let (state, scratch) = (&mut layer.linear_state, &mut layer.linear_scratch);
2609 vmf_phase_pair(
2610 &self.ws.n1,
2611 &self.ws.n2,
2612 w,
2613 &cfg,
2614 state,
2615 scratch,
2616 self.pool.as_deref(),
2617 )
2618 }
2619 AttnKind::LinearGdn(w) => {
2620 let cfg = self.gdn_cfg.expect("gdn layer without gdn_cfg");
2621 let layer = &mut self.kv_cache.layers[li];
2622 let (state, scratch) = (&mut layer.linear_state, &mut layer.linear_scratch);
2623 gdn_pair(
2624 &self.ws.n1,
2625 &self.ws.n2,
2626 w,
2627 &cfg,
2628 state,
2629 scratch,
2630 self.pool.as_deref(),
2631 )
2632 }
2633 AttnKind::ShortConv(w) => {
2634 let cfg = self
2635 .short_conv_cfg
2636 .expect("short-conv layer without short_conv_cfg");
2637 let layer = &mut self.kv_cache.layers[li];
2638 let (state, scratch) = (&mut layer.linear_state, &mut layer.linear_scratch);
2639 short_conv_pair(
2640 &self.ws.n1,
2641 &self.ws.n2,
2642 w,
2643 &cfg,
2644 state,
2645 scratch,
2646 self.pool.as_deref(),
2647 )
2648 }
2649 AttnKind::Full {
2650 wq,
2651 wk,
2652 wv,
2653 wo,
2654 q_norm,
2655 k_norm,
2656 output_gate,
2657 softplus_gate,
2658 bias,
2659 } => {
2660 let inv_freq_l = self.layer_inv_freq(li);
2661 let (nkv_l, hd_l, rd_l) = self.layer_geom(li);
2662 let cfg = QwenAttnCfg {
2663 num_heads: self.layer_num_heads(li),
2664 num_kv_heads: nkv_l,
2665 head_dim: hd_l,
2666 hidden_size: hs,
2667 position,
2668 inv_freq: &inv_freq_l,
2669 rotary_dim: rd_l,
2670 scale: self.attn_scale,
2671 softcap: self.attn_softcap,
2672 window: self.layer_window(li),
2673 v_norm: self.attn_v_norm,
2674 q_norm: q_norm.as_deref(),
2675 k_norm: k_norm.as_deref(),
2676 output_gate: *output_gate,
2677 softplus_gate: softplus_gate
2678 .as_ref()
2679 .map(|(gate, per_head)| (gate, *per_head)),
2680 rope_scale: self.layer_rope_scale(li),
2681 bias: bias
2682 .as_ref()
2683 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
2684 rms_eps: eps,
2685 norm_style: self.norm_style,
2686 pool: pool.as_deref(),
2687 };
2688 attention::qwen_attention_pair(
2689 &self.ws.n1,
2690 &self.ws.n2,
2691 wq,
2692 wk,
2693 wv,
2694 wo,
2695 &mut self.kv_cache.layers[li],
2696 &cfg,
2697 )
2698 }
2699 };
2700 let (a1, a2) = match &self.weights.layers[self.phys_layer(li)].attn_out_norm {
2701 Some(w) => (
2702 inference::rms_norm(&a1, w, self.rms_eps, self.norm_style),
2703 inference::rms_norm(&a2, w, self.rms_eps, self.norm_style),
2704 ),
2705 None => (a1, a2),
2706 };
2707 for i in 0..self.hidden_size {
2708 h1[i] += a1[i];
2709 h2[i] += a2[i];
2710 }
2711 let (mut a1, mut a2) = (a1, a2);
2712 attention::recycle_buf(&mut a1);
2713 attention::recycle_buf(&mut a2);
2714
2715 let lw = &self.weights.layers[self.phys_layer(li)];
2716 inference::rms_norm_into(
2717 &h1,
2718 &lw.post_norm,
2719 self.rms_eps,
2720 self.norm_style,
2721 &mut self.ws.p1,
2722 );
2723 inference::rms_norm_into(
2724 &h2,
2725 &lw.post_norm,
2726 self.rms_eps,
2727 self.norm_style,
2728 &mut self.ws.p2,
2729 );
2730 let (f1, f2) = match &lw.ffn {
2731 FfnKind::DenseMoe(dm) => (
2734 dense_moe_ffn(
2735 dm,
2736 &self.ws.p1,
2737 &h1,
2738 self.rms_eps,
2739 self.norm_style,
2740 self.pool.as_deref(),
2741 ),
2742 dense_moe_ffn(
2743 dm,
2744 &self.ws.p2,
2745 &h2,
2746 self.rms_eps,
2747 self.norm_style,
2748 self.pool.as_deref(),
2749 ),
2750 ),
2751 _ => ffn_forward_pair(
2752 &lw.ffn,
2753 &self.ws.p1,
2754 &self.ws.p2,
2755 self.pool.as_deref(),
2756 None,
2757 ),
2758 };
2759 let (f1, f2) = match &self.weights.layers[self.phys_layer(li)].ffn_out_norm {
2760 Some(w) => (
2761 inference::rms_norm(&f1, w, self.rms_eps, self.norm_style),
2762 inference::rms_norm(&f2, w, self.rms_eps, self.norm_style),
2763 ),
2764 None => (f1, f2),
2765 };
2766 for i in 0..self.hidden_size {
2767 h1[i] += f1[i];
2768 h2[i] += f2[i];
2769 }
2770 let (mut f1, mut f2) = (f1, f2);
2771 attention::recycle_buf(&mut f1);
2772 attention::recycle_buf(&mut f2);
2773 if let Some(sc) = self.weights.layers[self.phys_layer(li)].layer_scale {
2774 for i in 0..self.hidden_size {
2775 h1[i] *= sc;
2776 h2[i] *= sc;
2777 }
2778 }
2779 if self.is_loop_end(li) && li + 1 < self.num_layers {
2781 h1 = inference::rms_norm(
2782 &h1,
2783 &self.weights.final_norm,
2784 self.rms_eps,
2785 self.norm_style,
2786 );
2787 h2 = inference::rms_norm(
2788 &h2,
2789 &self.weights.final_norm,
2790 self.rms_eps,
2791 self.norm_style,
2792 );
2793 }
2794 }
2795 (h1, h2)
2796 }
2797
2798 fn commit_linear_scratch(&mut self) {
2800 for layer in &mut self.kv_cache.layers {
2801 if !layer.linear_scratch.is_empty() {
2802 std::mem::swap(&mut layer.linear_state, &mut layer.linear_scratch);
2803 layer.linear_scratch.clear();
2804 }
2805 }
2806 }
2807
2808 pub fn forward_ids(
2811 &mut self,
2812 ids: &[u32],
2813 task_mask: Option<&TaskMask>,
2814 ) -> Result<Vec<f32>, String> {
2815 if ids.is_empty() {
2816 return Err("empty id sequence".to_string());
2817 }
2818 self.kv_cache.clear();
2819 self.kv_history.clear();
2820 self.o1_begin();
2821 let mut hidden = vec![0.0f32; self.hidden_size];
2822 let mut pos = 0usize;
2823 if task_mask.is_none() && self.can_prefill_batched() && ids.len() > 2 {
2824 let chunk = prefill_chunk();
2828 let hs = self.hidden_size;
2829 while pos < ids.len() {
2830 let end = (pos + chunk).min(ids.len());
2831 let hb = self.prefill_batch(&ids[pos..end], pos);
2832 hidden.copy_from_slice(&hb[(end - pos - 1) * hs..]);
2833 pos = end;
2834 }
2835 }
2836 if task_mask.is_none()
2840 && !std::env::var("CMF_PAIR").is_ok_and(|v| v == "0")
2841 && self.pair_supported()
2842 {
2843 while pos + 1 < ids.len() {
2844 let e1 = self.embed_single(ids[pos]);
2845 let e2 = self.embed_single(ids[pos + 1]);
2846 let (_, h2) = self.forward_pair(&e1, &e2, pos);
2847 self.commit_linear_scratch();
2848 hidden = h2;
2849 pos += 2;
2850 }
2851 }
2852 while pos < ids.len() {
2853 hidden = self.forward_layers(&self.embed_single(ids[pos]), pos, task_mask);
2854 pos += 1;
2855 }
2856 self.o1_seal();
2860 let normed = inference::rms_norm(
2861 &hidden,
2862 &self.weights.final_norm,
2863 self.rms_eps,
2864 self.norm_style,
2865 );
2866 Ok(self.lm_head_forward(&normed))
2867 }
2868
2869 pub fn ppl_ids(&mut self, ids: &[u32]) -> f64 {
2876 let (nll, cnt) = self.nll_ids_from(ids, 0);
2877 (nll / cnt.max(1) as f64).exp()
2878 }
2879
2880 pub fn probe_ffn_mass(&mut self, ids: &[u32]) -> Vec<Vec<f64>> {
2885 self.kv_cache.clear();
2886 self.kv_history.clear();
2887 FFN_PROBE.with(|p| {
2888 *p.borrow_mut() = Some(vec![vec![0f64; self.intermediate_size]; self.num_layers]);
2889 });
2890 crate::gpu::cpu_scope(|| {
2891 for (pos, &id) in ids.iter().enumerate() {
2892 let emb = self.embed_single(id);
2893 let _ = self.forward_layers(&emb, pos, None);
2894 }
2895 });
2896 self.kv_cache.clear();
2897 self.kv_history.clear();
2898 FFN_PROBE
2899 .with(|p| p.borrow_mut().take())
2900 .unwrap_or_default()
2901 }
2902
2903 pub fn ppl_ids_masked(&mut self, ids: &[u32], mask: &TaskMask) -> f64 {
2907 self.kv_cache.clear();
2908 self.kv_history.clear();
2909 let mut nll = 0f64;
2910 let mut cnt = 0usize;
2911 let mut hidden = vec![0f32; self.hidden_size];
2912 for (pos, &id) in ids.iter().enumerate() {
2913 if pos > 0 {
2914 inference::rms_norm_into(
2915 &hidden,
2916 &self.weights.final_norm,
2917 self.rms_eps,
2918 self.norm_style,
2919 &mut self.ws.n1,
2920 );
2921 let mut logits = self.lm_head_forward(&self.ws.n1);
2922 let max = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
2923 let sum: f64 = logits.iter().map(|&v| ((v - max) as f64).exp()).sum();
2924 let p = ((logits[id as usize] - max) as f64).exp() / sum.max(1e-300);
2925 nll -= p.max(1e-300).ln();
2926 cnt += 1;
2927 attention::recycle_buf(&mut logits);
2928 }
2929 let emb = self.embed_single(id);
2930 hidden = self.forward_layers(&emb, pos, Some(mask));
2931 }
2932 self.kv_cache.clear();
2933 self.kv_history.clear();
2934 (nll / cnt.max(1) as f64).exp()
2935 }
2936
2937 pub fn nll_ids_from(&mut self, ids: &[u32], start: usize) -> (f64, usize) {
2946 self.kv_cache.clear();
2947 self.kv_history.clear();
2948 let mut nll = 0f64;
2949 let mut cnt = 0usize;
2950 if self.can_prefill_batched() {
2951 const CHUNK: usize = 128;
2957 const LM_SUB: usize = 32;
2958 let n = ids.len().saturating_sub(1);
2959 let hs = self.hidden_size;
2960 let rows = self.weights.lm_head.rows();
2961 let mut pos = 0usize;
2962 while pos < n {
2963 let end = (pos + CHUNK).min(n);
2964 let bsz = end - pos;
2965 let hb = self.prefill_batch(&ids[pos..end], pos);
2966 let mut k0 = 0usize;
2967 while k0 < bsz {
2968 let k1 = (k0 + LM_SUB).min(bsz);
2969 let sb = k1 - k0;
2970 if pos + k1 <= start {
2973 k0 = k1;
2974 continue;
2975 }
2976 let mut normed = vec![0.0f32; sb * hs];
2977 for k in 0..sb {
2978 let r = inference::rms_norm(
2979 &hb[(k0 + k) * hs..(k0 + k + 1) * hs],
2980 &self.weights.final_norm,
2981 self.rms_eps,
2982 self.norm_style,
2983 );
2984 normed[k * hs..(k + 1) * hs].copy_from_slice(&r);
2985 }
2986 let mut logits = vec![0.0f32; sb * rows];
2987 self.weights
2988 .lm_head
2989 .matmat(&normed, sb, &mut logits, self.pool.as_deref());
2990 for k in 0..sb {
2991 if pos + k0 + k < start {
2992 continue;
2993 }
2994 let lg = &mut logits[k * rows..k * rows + self.vocab_size.min(rows)];
2995 if let Some(mu) = self.logit_multiplier {
2996 for v in lg.iter_mut() {
2997 *v *= mu;
2998 }
2999 }
3000 if let Some(c) = self.final_softcap {
3004 for v in lg.iter_mut() {
3005 *v = c * (*v / c).tanh();
3006 }
3007 }
3008 let lg = &logits[k * rows..k * rows + self.vocab_size.min(rows)];
3009 let target = ids[pos + k0 + k + 1] as usize;
3010 let max = lg.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
3011 let lse: f64 = lg
3012 .iter()
3013 .map(|&v| ((v - max) as f64).exp())
3014 .sum::<f64>()
3015 .ln()
3016 + max as f64;
3017 nll += lse - lg[target] as f64;
3018 cnt += 1;
3019 if std::env::var("CMF_PPL_TRACE").is_ok() {
3020 let top = lg
3021 .iter()
3022 .enumerate()
3023 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
3024 .map(|(i, _)| i)
3025 .unwrap_or(0);
3026 eprintln!(
3027 "BTRACE pos {} target {} nll {:.4} top {} lg_t {:.3} lg_top {:.3}",
3028 pos + k0 + k,
3029 target,
3030 lse - lg[target] as f64,
3031 top,
3032 lg[target],
3033 lg[top]
3034 );
3035 }
3036 }
3037 k0 = k1;
3038 }
3039 pos = end;
3040 }
3041 self.kv_cache.clear();
3042 self.kv_history.clear();
3043 return (nll, cnt);
3044 }
3045 for pos in 0..ids.len().saturating_sub(1) {
3046 let hidden = self.forward_layers(&self.embed_single(ids[pos]), pos, None);
3047 let out_of_band = self.graph_logits.take();
3055 if pos < start {
3056 continue;
3057 }
3058 let logits = match out_of_band {
3059 Some(lg) => lg,
3060 None => {
3061 let normed = inference::rms_norm(
3062 &hidden,
3063 &self.weights.final_norm,
3064 self.rms_eps,
3065 self.norm_style,
3066 );
3067 self.lm_head_forward(&normed)
3071 }
3072 };
3073 let target = ids[pos + 1] as usize;
3074 let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
3075 let lse: f64 = logits
3076 .iter()
3077 .map(|&v| ((v - max) as f64).exp())
3078 .sum::<f64>()
3079 .ln()
3080 + max as f64;
3081 let tok_nll = lse - logits[target] as f64;
3082 if std::env::var("CMF_PPL_TRACE").is_ok() && pos < 48 {
3083 let top = logits
3084 .iter()
3085 .enumerate()
3086 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
3087 .map(|(i, _)| i)
3088 .unwrap_or(0);
3089 eprintln!(
3090 "pos {pos:3} tgt {target:6} nll {tok_nll:7.3} | top1 {top:6} lg[t]={:.2} lg[top]={:.2}",
3091 logits[target], logits[top]
3092 );
3093 }
3094 nll += tok_nll;
3095 cnt += 1;
3096 }
3097 self.kv_cache.clear();
3098 self.kv_history.clear();
3099 (nll, cnt)
3100 }
3101
3102 pub fn nll_ids_o1(&mut self, ids: &[u32], prefill: usize) -> (f64, usize) {
3118 self.kv_cache.clear();
3119 self.kv_history.clear();
3120 self.o1_begin();
3121 let n = ids.len().saturating_sub(1);
3122 let p = prefill.min(n);
3123 let mut pos = 0usize;
3125 if self.can_prefill_batched() {
3126 const CHUNK: usize = 128;
3127 while pos < p {
3128 let end = (pos + CHUNK).min(p);
3129 let _ = self.prefill_batch(&ids[pos..end], pos);
3130 pos = end;
3131 }
3132 } else {
3133 while pos < p {
3134 let _ = self.forward_layers(&self.embed_single(ids[pos]), pos, None);
3135 pos += 1;
3136 }
3137 }
3138 self.o1_seal();
3139
3140 let mut nll = 0f64;
3141 let mut cnt = 0usize;
3142 for pos in p..n {
3143 let hidden = self.forward_layers(&self.embed_single(ids[pos]), pos, None);
3144 let normed = inference::rms_norm(
3145 &hidden,
3146 &self.weights.final_norm,
3147 self.rms_eps,
3148 self.norm_style,
3149 );
3150 let logits = self.lm_head_forward(&normed);
3154 let target = ids[pos + 1] as usize;
3155 let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
3156 let lse: f64 = logits
3157 .iter()
3158 .map(|&v| ((v - max) as f64).exp())
3159 .sum::<f64>()
3160 .ln()
3161 + max as f64;
3162 let tok_nll = lse - logits[target] as f64;
3163 if std::env::var("CMF_PPL_TRACE").is_ok() && pos < 48 {
3164 let top = logits
3165 .iter()
3166 .enumerate()
3167 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
3168 .map(|(i, _)| i)
3169 .unwrap_or(0);
3170 eprintln!(
3171 "pos {pos:3} tgt {target:6} nll {tok_nll:7.3} | top1 {top:6} lg[t]={:.2} lg[top]={:.2}",
3172 logits[target], logits[top]
3173 );
3174 }
3175 nll += tok_nll;
3176 cnt += 1;
3177 }
3178 self.kv_cache.clear();
3179 self.kv_history.clear();
3180 (nll, cnt)
3181 }
3182
3183 pub fn calib_ids(&mut self, ids: &[u32], temps: &[f32]) -> (Vec<bool>, Vec<Vec<f32>>) {
3191 self.kv_cache.clear();
3192 self.kv_history.clear();
3193 let n = ids.len().saturating_sub(1);
3194 let mut correct = Vec::with_capacity(n);
3195 let mut pmax = Vec::with_capacity(n);
3196 for pos in 0..n {
3197 let emb = self.embed_single(ids[pos]);
3198 let hidden = self.forward_layers(&emb, pos, None);
3199 let normed = inference::rms_norm(
3200 &hidden,
3201 &self.weights.final_norm,
3202 self.rms_eps,
3203 self.norm_style,
3204 );
3205 let logits = self.lm_head_forward(&normed);
3209 let target = ids[pos + 1] as usize;
3210 let (mut amax, mut mval) = (0usize, f32::NEG_INFINITY);
3211 for (i, &v) in logits.iter().enumerate() {
3212 if v > mval {
3213 mval = v;
3214 amax = i;
3215 }
3216 }
3217 correct.push(amax == target);
3218 let row: Vec<f32> = temps
3219 .iter()
3220 .map(|&t| {
3221 let tt = t.max(1e-3);
3222 let s: f32 = logits.iter().map(|&v| ((v - mval) / tt).exp()).sum();
3223 1.0 / s.max(1e-12) })
3225 .collect();
3226 pmax.push(row);
3227 }
3228 self.kv_cache.clear();
3229 self.kv_history.clear();
3230 (correct, pmax)
3231 }
3232
3233 pub fn ppl_ids_dynamic(&mut self, ids: &[u32]) -> (f64, usize) {
3240 let mut router = match self.dyn_router.take() {
3241 Some(r) => r,
3242 None => return (self.ppl_ids(ids), 0),
3243 };
3244 router.reset();
3245 self.dyn_phi_seen = 0;
3246 let _ = self.set_active_skill(None);
3247
3248 self.kv_cache.clear();
3249
3250 self.kv_history.clear();
3251 let mut nll = 0f64;
3252 let mut cnt = 0usize;
3253 for pos in 0..ids.len().saturating_sub(1) {
3254 let hidden = self.forward_layers(&self.embed_single(ids[pos]), pos, None);
3255 let normed = inference::rms_norm(
3256 &hidden,
3257 &self.weights.final_norm,
3258 self.rms_eps,
3259 self.norm_style,
3260 );
3261 let logits = self.lm_head_forward(&normed);
3265 let target = ids[pos + 1] as usize;
3266 let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
3267 let lse: f64 = logits
3268 .iter()
3269 .map(|&v| ((v - max) as f64).exp())
3270 .sum::<f64>()
3271 .ln()
3272 + max as f64;
3273 let tok_nll = lse - logits[target] as f64;
3274 if std::env::var("CMF_PPL_TRACE").is_ok() && pos < 48 {
3275 let top = logits
3276 .iter()
3277 .enumerate()
3278 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
3279 .map(|(i, _)| i)
3280 .unwrap_or(0);
3281 eprintln!(
3282 "pos {pos:3} tgt {target:6} nll {tok_nll:7.3} | top1 {top:6} lg[t]={:.2} lg[top]={:.2}",
3283 logits[target], logits[top]
3284 );
3285 }
3286 nll += tok_nll;
3287 cnt += 1;
3288 let phi = self.dyn_phi_ema.clone();
3290 if let Some(new_active) = router.step(&phi, pos) {
3291 let _ = self.set_active_skill(new_active);
3292 }
3293 }
3294 let switches = router.switches.len();
3295 let _ = self.set_active_skill(None);
3296 self.dyn_router = Some(router);
3297 self.kv_cache.clear();
3298 self.kv_history.clear();
3299 ((nll / cnt.max(1) as f64).exp(), switches)
3300 }
3301
3302 pub fn probe_phi(&mut self, ids: &[u32], layer: usize) -> Vec<f32> {
3304 self.kv_cache.clear();
3305 self.kv_history.clear();
3306 let mut acc = vec![0f32; self.hidden_size];
3307 for (pos, &id) in ids.iter().enumerate() {
3308 let h = self.forward_layers_upto(&self.embed_single(id), pos, None, Some(layer));
3309 for (a, v) in acc.iter_mut().zip(&h) {
3310 *a += v;
3311 }
3312 }
3313 let n = ids.len().max(1) as f32;
3314 for a in acc.iter_mut() {
3315 *a /= n;
3316 }
3317 self.kv_cache.clear();
3318 self.kv_history.clear();
3319 acc
3320 }
3321
3322 fn prefill_batch(&mut self, ids: &[u32], start_pos: usize) -> Vec<f32> {
3328 let b = ids.len();
3329 let hs = self.hidden_size;
3330 let mut h: Vec<f32> = vec![0.0; b * hs];
3333 let mut h_ready = false;
3334 let fill_h = |h: &mut Vec<f32>, me: &Self| {
3335 for (bi, &id) in ids.iter().enumerate() {
3336 let e = me.embed_single(id);
3337 h[bi * hs..(bi + 1) * hs].copy_from_slice(&e);
3338 }
3339 };
3340 let (_nkv, _hd, _rd, eps) = (
3341 self.num_kv_heads,
3342 self.head_dim,
3343 self.rotary_dim,
3344 self.rms_eps,
3345 );
3346 let pool = self.pool.clone();
3347 let norm_style = self.norm_style;
3348
3349 #[cfg(target_os = "macos")]
3350 let mut chunk_skip_until = 0usize;
3351 for li in 0..self.num_layers {
3352 crate::gpu::set_layer(li as i64); #[cfg(target_os = "macos")]
3359 {
3360 if li < chunk_skip_until {
3361 continue;
3362 }
3363 if !h_ready && li == 0 && self.weights.embed_tokens.q8_row_parts().is_none() {
3369 fill_h(&mut h, self);
3370 h_ready = true;
3371 }
3372 let ids_for_embed = (!h_ready && li == 0).then_some(ids);
3373 let end = self.chunk_run_gpu(li, &mut h, b, start_pos, ids_for_embed);
3374 if end > li {
3375 h_ready = true;
3376 chunk_skip_until = end;
3377 if self.is_loop_end(end - 1) && end < self.num_layers {
3380 for bi in 0..b {
3381 let normed = inference::rms_norm(
3382 &h[bi * hs..(bi + 1) * hs],
3383 &self.weights.final_norm,
3384 eps,
3385 norm_style,
3386 );
3387 h[bi * hs..(bi + 1) * hs].copy_from_slice(&normed);
3388 }
3389 }
3390 continue;
3391 }
3392 }
3393 if !h_ready {
3394 fill_h(&mut h, self);
3395 h_ready = true;
3396 }
3397 let lw = &self.weights.layers[self.phys_layer(li)];
3398 match &lw.attn {
3400 AttnKind::Kda(w) => {
3401 let cfg = self.kda_cfg.expect("kda layer without kda_cfg");
3403 let mut normed = vec![0.0f32; b * hs];
3404 for bi in 0..b {
3405 inference::rms_norm_into(
3406 &h[bi * hs..(bi + 1) * hs],
3407 &lw.input_norm,
3408 eps,
3409 norm_style,
3410 &mut normed[bi * hs..(bi + 1) * hs],
3411 );
3412 }
3413 let attn = crate::linear_core::kda_forward_batch(
3414 &normed,
3415 b,
3416 w,
3417 &cfg,
3418 &mut self.kv_cache.layers[li].linear_state,
3419 pool.as_deref(),
3420 );
3421 for (dst, &a) in h.iter_mut().zip(&attn) {
3422 *dst += a;
3423 }
3424 }
3425 AttnKind::LinearGdn(w) => {
3426 let cfg = self.gdn_cfg.expect("gdn layer without gdn_cfg");
3428 let mut normed = vec![0.0f32; b * hs];
3429 for bi in 0..b {
3430 let r = inference::rms_norm(
3431 &h[bi * hs..(bi + 1) * hs],
3432 &lw.input_norm,
3433 eps,
3434 norm_style,
3435 );
3436 normed[bi * hs..(bi + 1) * hs].copy_from_slice(&r);
3437 }
3438 let attn = crate::linear_core::gdn_forward_batch(
3439 &normed,
3440 b,
3441 w,
3442 &cfg,
3443 &mut self.kv_cache.layers[li].linear_state,
3444 pool.as_deref(),
3445 );
3446 for (dst, &a) in h.iter_mut().zip(&attn) {
3447 *dst += a;
3448 }
3449 }
3450 AttnKind::ShortConv(w) => {
3451 let cfg = self
3454 .short_conv_cfg
3455 .expect("short-conv layer without short_conv_cfg");
3456 let mut normed = vec![0.0f32; b * hs];
3457 for bi in 0..b {
3458 inference::rms_norm_into(
3459 &h[bi * hs..(bi + 1) * hs],
3460 &lw.input_norm,
3461 eps,
3462 norm_style,
3463 &mut normed[bi * hs..(bi + 1) * hs],
3464 );
3465 }
3466 let attn = short_conv_forward_batch(
3467 &normed,
3468 b,
3469 w,
3470 &cfg,
3471 &mut self.kv_cache.layers[li].linear_state,
3472 pool.as_deref(),
3473 );
3474 for (dst, &a) in h.iter_mut().zip(&attn) {
3475 *dst += a;
3476 }
3477 }
3478 AttnKind::Mla(w) => {
3479 let inv_freq_l = self.layer_inv_freq(li);
3482 let rs = self.layer_rope_scale(li);
3483 let mut normed = vec![0.0f32; hs];
3484 for bi in 0..b {
3485 inference::rms_norm_into(
3486 &h[bi * hs..(bi + 1) * hs],
3487 &lw.input_norm,
3488 eps,
3489 norm_style,
3490 &mut normed,
3491 );
3492 let ao = mla_attention(
3493 w,
3494 &normed,
3495 &mut self.kv_cache.layers[li],
3496 start_pos + bi,
3497 &inv_freq_l,
3498 rs,
3499 eps,
3500 pool.as_deref(),
3501 );
3502 for (dst, &a) in h[bi * hs..(bi + 1) * hs].iter_mut().zip(&ao) {
3503 *dst += a;
3504 }
3505 }
3506 }
3507 AttnKind::Full {
3508 wq,
3509 wk,
3510 wv,
3511 wo,
3512 q_norm,
3513 k_norm,
3514 output_gate,
3515 softplus_gate,
3516 bias,
3517 } => {
3518 let mut normed = vec![0.0f32; b * hs];
3522 for bi in 0..b {
3523 inference::rms_norm_into(
3524 &h[bi * hs..(bi + 1) * hs],
3525 &lw.input_norm,
3526 eps,
3527 norm_style,
3528 &mut normed[bi * hs..(bi + 1) * hs],
3529 );
3530 }
3531 let inv_freq_l = self.layer_inv_freq(li);
3532 let (nkv_l, hd_l, rd_l) = self.layer_geom(li);
3533 let cfg = QwenAttnCfg {
3534 num_heads: self.layer_num_heads(li),
3535 num_kv_heads: nkv_l,
3536 head_dim: hd_l,
3537 hidden_size: hs,
3538 position: start_pos,
3539 inv_freq: &inv_freq_l,
3540 rotary_dim: rd_l,
3541 scale: self.attn_scale,
3542 softcap: self.attn_softcap,
3543 window: self.layer_window(li),
3544 v_norm: self.attn_v_norm,
3545 q_norm: q_norm.as_deref(),
3546 k_norm: k_norm.as_deref(),
3547 output_gate: *output_gate,
3548 softplus_gate: softplus_gate
3549 .as_ref()
3550 .map(|(gate, per_head)| (gate, *per_head)),
3551 rope_scale: self.layer_rope_scale(li),
3552 bias: bias
3553 .as_ref()
3554 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
3555 rms_eps: eps,
3556 norm_style,
3557 pool: pool.as_deref(),
3558 };
3559 let mut attn = attention::qwen_attention_batch(
3560 &normed,
3561 b,
3562 wq,
3563 wk,
3564 wv,
3565 wo,
3566 &mut self.kv_cache.layers[li],
3567 &cfg,
3568 );
3569 if let Some(w) = &lw.attn_out_norm {
3570 for bi in 0..b {
3571 inference::rms_norm_into(
3572 &attn[bi * hs..(bi + 1) * hs],
3573 w,
3574 eps,
3575 norm_style,
3576 &mut normed[bi * hs..(bi + 1) * hs],
3577 );
3578 }
3579 attn.copy_from_slice(&normed);
3580 }
3581 for (dst, &a) in h.iter_mut().zip(&attn) {
3582 *dst += a;
3583 }
3584 }
3585 AttnKind::Linear(w) => {
3586 for bi in 0..b {
3587 let normed = inference::rms_norm(
3588 &h[bi * hs..(bi + 1) * hs],
3589 &lw.input_norm,
3590 eps,
3591 norm_style,
3592 );
3593 vmf_phase_forward(
3594 &normed,
3595 w,
3596 &self.vmf_cfg.expect("linear layer without vmf_cfg"),
3597 &mut self.kv_cache.layers[li].linear_state,
3598 pool.as_deref(),
3599 )
3600 .iter()
3601 .enumerate()
3602 .for_each(|(i, &a)| h[bi * hs + i] += a);
3603 }
3604 }
3605 }
3606
3607 let lw = &self.weights.layers[self.phys_layer(li)];
3609 let mut post = vec![0.0f32; b * hs];
3610 for bi in 0..b {
3611 let r =
3612 inference::rms_norm(&h[bi * hs..(bi + 1) * hs], &lw.post_norm, eps, norm_style);
3613 post[bi * hs..(bi + 1) * hs].copy_from_slice(&r);
3614 }
3615 let mut ffn = match &lw.ffn {
3616 FfnKind::Dense(d) => dense_ffn_batch(d, &post, b, pool.as_deref()),
3617 FfnKind::Moe(m) => moe_ffn_batch(m, &post, b, hs, pool.as_deref(), None),
3618 FfnKind::DenseMoe(dm) => {
3621 let mut out = vec![0.0f32; b * hs];
3622 for bi in 0..b {
3623 let r = dense_moe_ffn(
3624 dm,
3625 &post[bi * hs..(bi + 1) * hs],
3626 &h[bi * hs..(bi + 1) * hs],
3627 eps,
3628 norm_style,
3629 pool.as_deref(),
3630 );
3631 out[bi * hs..(bi + 1) * hs].copy_from_slice(&r);
3632 }
3633 out
3634 }
3635 };
3636 if let Some(w) = &lw.ffn_out_norm {
3637 for bi in 0..b {
3638 inference::rms_norm_into(
3639 &ffn[bi * hs..(bi + 1) * hs],
3640 w,
3641 eps,
3642 norm_style,
3643 &mut post[bi * hs..(bi + 1) * hs],
3644 );
3645 }
3646 ffn.copy_from_slice(&post);
3647 }
3648 for (dst, &f) in h.iter_mut().zip(&ffn) {
3649 *dst += f;
3650 }
3651 if let Some(sc) = lw.layer_scale {
3652 for v in h.iter_mut() {
3653 *v *= sc;
3654 }
3655 }
3656 if let Ok(tp) = std::env::var("CMF_TRACE_POS") {
3657 if let Some(t) = tp.parse::<usize>().ok() {
3658 if t >= start_pos && t < start_pos + b {
3659 let bi = t - start_pos;
3660 let row = &h[bi * hs..(bi + 1) * hs];
3661 let n: f32 = row.iter().map(|x| x * x).sum::<f32>().sqrt();
3662 eprintln!(
3663 "BATCH pos {t} after layer {li}: |h| = {n:.6} h0 {:.6} h1 {:.6}",
3664 row[0], row[1]
3665 );
3666 }
3667 }
3668 }
3669 if std::env::var("CMF_DEBUG_LAYERS").is_ok() {
3673 let row = &h[(b - 1) * hs..b * hs];
3674 let rms =
3675 (row.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / hs as f64).sqrt();
3676 let mx = row.iter().fold(0f32, |m, &v| m.max(v.abs()));
3677 eprintln!(
3678 "layer {li:>3} {:>10} ffn={:<5} rms={rms:>12.4} max={mx:>12.4}",
3679 match &self.weights.layers[self.phys_layer(li)].attn {
3680 AttnKind::LinearGdn(_) => "gdn",
3681 AttnKind::Linear(_) => "vmf",
3682 AttnKind::ShortConv(_) => "conv",
3683 _ => "attn",
3684 },
3685 match &lw.ffn {
3686 FfnKind::Moe(_) => "moe",
3687 FfnKind::Dense(_) => "dense",
3688 FfnKind::DenseMoe(_) => "dense+moe",
3689 },
3690 );
3691 }
3692 if self.is_loop_end(li) && li + 1 < self.num_layers {
3694 for bi in 0..b {
3695 let normed = inference::rms_norm(
3696 &h[bi * hs..(bi + 1) * hs],
3697 &self.weights.final_norm,
3698 eps,
3699 norm_style,
3700 );
3701 h[bi * hs..(bi + 1) * hs].copy_from_slice(&normed);
3702 }
3703 }
3704 if std::env::var("CMF_TRACE_H").is_ok() {
3705 let n = h[..hs].iter().map(|v| v.abs()).sum::<f32>() / hs as f32;
3706 let mx = h[..hs].iter().fold(0.0f32, |a, &v| a.max(v.abs()));
3707 eprintln!(
3708 "layer {li}: mean|h|={n:.4} max|h|={mx:.2} scale={:?}",
3709 lw.layer_scale
3710 );
3711 }
3712 }
3713 crate::gpu::set_layer(-1); h
3715 }
3716
3717 fn embed_single(&self, id: u32) -> Vec<f32> {
3719 let mut out = vec![0.0f32; self.hidden_size];
3720 if (id as usize) < self.weights.embed_tokens.rows() {
3721 self.weights.embed_tokens.row_f32(id as usize, &mut out);
3722 }
3723 if self.embed_multiplier != 1.0 {
3724 for v in out.iter_mut() {
3725 *v *= self.embed_multiplier;
3726 }
3727 }
3728 if self.dsv4.is_some() {
3732 let mut v = vec![0.0f32; self.hidden_size.max(1)];
3733 v[0] = id as f32;
3734 return v;
3735 }
3736 if let Some(b) = &self.g3n {
3739 return b.0.extend_embedding(id, &out, self.pool.as_deref());
3740 }
3741 out
3742 }
3743
3744 #[cfg(target_os = "macos")]
3750 fn chunk_run_gpu(
3751 &mut self,
3752 li0: usize,
3753 h: &mut [f32],
3754 b: usize,
3755 pos0: usize,
3756 embed_ids: Option<&[u32]>,
3757 ) -> usize {
3758 if !crate::gpu::enabled_here()
3762 || std::env::var("CMF_GPU_CHUNK")
3763 .map(|v| v == "0")
3764 .unwrap_or(false)
3765 || b < 32
3766 || self.swa.is_some()
3767 || self.global_attn.is_some()
3768 || self.attn_v_norm
3769 || (self.attn_scale - 1.0 / (self.head_dim as f32).sqrt()).abs() > 1e-9
3770 {
3771 return li0;
3772 }
3773 let Some(model) = self.model.clone() else {
3774 return li0;
3775 };
3776 let inv_freq = self.inv_freq.clone();
3777 let (nh, nkv, hd, hs) = (
3778 self.num_heads,
3779 self.num_kv_heads,
3780 self.head_dim,
3781 self.hidden_size,
3782 );
3783 let loop_end = if self.loop_final_norm {
3787 ((li0 / self.physical_layers) + 1) * self.physical_layers
3788 } else {
3789 self.num_layers
3790 };
3791 let mut layers: Vec<crate::gpu_metal::ChunkLayer> = Vec::new();
3792 let mut stored_at: Vec<usize> = Vec::new();
3793 for li in li0..self.num_layers.min(loop_end) {
3794 let lw = &self.weights.layers[self.phys_layer(li)];
3795 if lw.attn_out_norm.is_some() || lw.ffn_out_norm.is_some() || lw.layer_scale.is_some() {
3796 break;
3797 }
3798 let AttnKind::Full {
3799 wq,
3800 wk,
3801 wv,
3802 wo,
3803 q_norm,
3804 k_norm,
3805 output_gate: false,
3806 softplus_gate: None,
3807 bias,
3808 } = &lw.attn
3809 else {
3810 break;
3811 };
3812 let FfnKind::Dense(d) = &lw.ffn else { break };
3813 if d.act != Act::Silu {
3814 break;
3815 }
3816 fn cw(t: &QTensor) -> Option<(usize, usize, usize, &[f32])> {
3821 t.q8_row_parts()
3822 .or_else(|| t.q4t_parts().map(|(i, r, c)| (i, r, c, &[][..])))
3823 .or_else(|| t.q4tp_parts().map(|(i, r, c)| (i, r, c, &[][..])))
3824 }
3825 let parts = (
3826 cw(wq),
3827 cw(wk),
3828 cw(wv),
3829 cw(wo),
3830 cw(&d.gate_proj),
3831 cw(&d.up_proj),
3832 cw(&d.down_proj),
3833 );
3834 let (Some(pq), Some(pk), Some(pv), Some(po), Some(pg), Some(pu), Some(pd)) = parts
3835 else {
3836 break;
3837 };
3838 let layer = &self.kv_cache.layers[li];
3839 if layer.mode != crate::kv_cache::KvMode::F32 || layer.o1.is_some() {
3840 break;
3841 }
3842 stored_at.push(layer.head_len(0));
3843 layers.push(crate::gpu_metal::ChunkLayer {
3844 model: &model,
3845 kv_id: self.graph_kv_id,
3846 layer: li,
3847 wq: pq,
3848 wk: pk,
3849 wv: pv,
3850 wo: po,
3851 gate: pg,
3852 up: pu,
3853 down: pd,
3854 input_norm: &lw.input_norm,
3855 post_norm: &lw.post_norm,
3856 bias: bias
3857 .as_ref()
3858 .map(|(a, bb, cc)| (a.as_slice(), bb.as_slice(), cc.as_slice())),
3859 q_norm: q_norm.as_deref(),
3860 k_norm: k_norm.as_deref(),
3861 inv_freq: &inv_freq,
3862 rd: self.rotary_dim,
3863 nh,
3864 nkv,
3865 hd,
3866 hs,
3867 inter: d.gate_proj.rows(),
3868 gemma: matches!(self.norm_style, cortiq_core::NormStyle::Gemma),
3869 eps: self.rms_eps as f32,
3870 });
3871 }
3872 if layers.is_empty() {
3873 return li0;
3874 }
3875 let row = nkv * hd;
3876 let mut store: Vec<(Vec<f32>, Vec<f32>, Vec<f32>)> = stored_at
3877 .iter()
3878 .map(|&st| (vec![0f32; b * row], vec![0f32; b * row], vec![0f32; st + b]))
3879 .collect();
3880 let mut io: Vec<crate::gpu_metal::ChunkIo> = Vec::with_capacity(layers.len());
3881 for (i, (ok, ov, oi)) in store.iter_mut().enumerate() {
3882 let li = layers[i].layer;
3883 let layer = &self.kv_cache.layers[li];
3884 io.push(crate::gpu_metal::ChunkIo {
3885 cpu_stored: stored_at[i],
3886 cpu_k: (0..nkv).map(|g| layer.head_keys(g)).collect(),
3887 cpu_v: (0..nkv).map(|g| layer.head_values(g)).collect(),
3888 out_k: ok,
3889 out_v: ov,
3890 imp: oi,
3891 });
3892 }
3893 let n_run = layers.len();
3894 let last = layers.last().map(|l| l.layer + 1).unwrap_or(li0);
3895 let ep = embed_ids.and_then(|ids| {
3898 self.weights
3899 .embed_tokens
3900 .q8_row_parts()
3901 .map(|(idx, rows, _c, rs)| crate::gpu_metal::ChunkEmbed {
3902 idx,
3903 rows,
3904 row_scale: rs,
3905 ids,
3906 mult: self.embed_multiplier,
3907 })
3908 });
3909 if embed_ids.is_some() && ep.is_none() {
3910 return li0;
3911 }
3912 if !crate::gpu_metal::chunk_run_gpu(&layers, &mut io, h, b, pos0, ep.as_ref()) {
3913 return li0;
3914 }
3915 drop(io);
3916 drop(layers);
3917 for (i, (ok, ov, oi)) in store.iter().enumerate().take(n_run) {
3920 let li = li0 + i;
3921 let layer = &mut self.kv_cache.layers[li];
3922 for bi in 0..b {
3923 layer.append(
3924 &ok[bi * row..(bi + 1) * row],
3925 &ov[bi * row..(bi + 1) * row],
3926 &[],
3927 );
3928 }
3929 layer.accumulate_imp(oi);
3930 }
3931 last
3932 }
3933
3934 fn layer_is_local(&self, li: usize) -> bool {
3937 if let Some(layers) = &self.sliding_layers {
3938 return layers.get(li).copied().unwrap_or(false);
3939 }
3940 match self.swa {
3941 Some((_, pattern)) => (li + 1) % pattern.max(1) != 0,
3942 None => false,
3943 }
3944 }
3945
3946 fn layer_inv_freq(&self, li: usize) -> std::sync::Arc<Vec<f32>> {
3949 if self.layer_is_local(li) {
3950 if let Some(f) = &self.inv_freq_local {
3951 return f.clone();
3952 }
3953 } else if let Some(f) = &self.inv_freq_global {
3954 return f.clone();
3955 }
3956 self.inv_freq.clone()
3957 }
3958
3959 fn layer_window(&self, li: usize) -> Option<usize> {
3961 self.swa
3962 .and_then(|(w, _)| self.layer_is_local(li).then_some(w))
3963 }
3964
3965 fn layer_num_heads(&self, li: usize) -> usize {
3966 self.attention_heads_per_layer
3967 .as_ref()
3968 .and_then(|v| v.get(li).copied())
3969 .unwrap_or(self.num_heads)
3970 }
3971
3972 fn layer_rope_scale(&self, li: usize) -> f32 {
3973 if self.layer_is_local(li) {
3974 self.rope_scale_local
3975 } else {
3976 self.rope_scale
3977 }
3978 }
3979
3980 fn layer_geom(&self, li: usize) -> (usize, usize, usize) {
3983 if !self.layer_is_local(li) {
3984 if let Some((ghd, gkv)) = self.global_attn {
3985 return (gkv, ghd, ghd);
3986 }
3987 }
3988 (
3989 self.num_kv_heads,
3990 self.head_dim,
3991 if self.layer_is_local(li) {
3992 self.rotary_dim_local.unwrap_or(self.rotary_dim)
3993 } else {
3994 self.rotary_dim
3995 },
3996 )
3997 }
3998
3999 fn forward_layers(
4001 &mut self,
4002 hidden: &[f32],
4003 position: usize,
4004 task_mask: Option<&TaskMask>,
4005 ) -> Vec<f32> {
4006 self.forward_layers_upto(hidden, position, task_mask, None)
4007 }
4008
4009 fn try_token_graph_wgpu(
4013 &self,
4014 hidden: &[f32],
4015 position: usize,
4016 logits_out: &mut Vec<f32>,
4017 layers_run: &mut usize,
4018 ) -> Option<Vec<f32>> {
4019 self.try_token_graph_wgpu_steps(hidden, position, logits_out, 1, None, Some(layers_run))
4020 }
4021
4022 fn try_multi_burst(&self, t_next: u32, position: usize, k: usize) -> Option<Vec<u32>> {
4026 if self.o1_active() || self.attn_softcap > 0.0 {
4027 return None;
4028 }
4029 let graph_on = match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
4030 Some("0") => return None,
4031 Some(_) => true,
4032 None => crate::gpu::wgpu_graph_default(),
4033 };
4034 if !graph_on {
4035 return None;
4036 }
4037 let emb = self.embed_single(t_next);
4038 let mut lg = Vec::new();
4039 let mut ids = Vec::new();
4040 self.try_token_graph_wgpu_steps(&emb, position, &mut lg, k, Some(&mut ids), None)?;
4041 (ids.len() == k).then_some(ids)
4042 }
4043
4044 fn try_token_graph_wgpu_steps(
4048 &self,
4049 hidden: &[f32],
4050 position: usize,
4051 logits_out: &mut Vec<f32>,
4052 steps: usize,
4053 ids_out: Option<&mut Vec<u32>>,
4054 layers_run: Option<&mut usize>,
4055 ) -> Option<Vec<f32>> {
4056 let o1_gpu = std::env::var("CMF_O1_GPU").as_deref() == Ok("1");
4059 if (self.o1_active() && !o1_gpu) || self.attn_softcap > 0.0 {
4060 return None;
4064 }
4065 let o1_views: Vec<Option<Vec<crate::nystrom::O1DeviceView<'_>>>> = (0..self.num_layers)
4070 .map(|li| {
4071 if !o1_gpu {
4072 return None;
4073 }
4074 self.kv_cache.layers[self.phys_layer(li)].o1_views()
4075 })
4076 .collect();
4077 if self.o1_active() && o1_gpu {
4078 let want: usize = (0..self.num_layers)
4081 .filter(|li| !matches!(self.kv_cache.layers[self.phys_layer(*li)].o1, None))
4082 .count();
4083 let have = o1_views.iter().filter(|v| v.is_some()).count();
4084 if want == 0 || have != want {
4085 return None;
4086 }
4087 }
4088 let nh = self.num_heads;
4089 let (nkv, hd, rd) = self.layer_geom(0);
4090 let gemma = self.norm_style == cortiq_core::NormStyle::Gemma;
4091 let mut layers = Vec::with_capacity(self.num_layers);
4092 let mut model = None;
4093 let dbg = std::env::var("CMF_GRAPH_DEBUG").is_ok();
4094 fn gw(t: &QTensor) -> Option<crate::gpu::GraphW<'_>> {
4095 if let Some((_, i, kind, rs)) = t.graph_weight() {
4096 return Some(crate::gpu::GraphW {
4097 idx: i,
4098 kind,
4099 row_scale: rs,
4100 data: &[],
4101 });
4102 }
4103 t.as_f32().map(|d| crate::gpu::GraphW {
4105 idx: 0,
4106 kind: 4,
4107 row_scale: &[],
4108 data: d,
4109 })
4110 }
4111 for li in 0..self.num_layers {
4112 let lw = &self.weights.layers[self.phys_layer(li)];
4113 if dbg {
4114 let ak = match &lw.attn {
4115 AttnKind::Mla(_) => "Mla".into(),
4116 AttnKind::Full {
4117 output_gate, bias, ..
4118 } => format!("Full gate={output_gate} bias={}", bias.is_some()),
4119 AttnKind::LinearGdn(_) => "LinearGdn".into(),
4120 AttnKind::Kda(_) => "Kda".into(),
4121 AttnKind::Linear(_) => "Linear".into(),
4122 AttnKind::ShortConv(_) => "ShortConv".into(),
4123 };
4124 let fk = match &lw.ffn {
4125 FfnKind::Dense(_) => "Dense",
4126 FfnKind::Moe(_) => "Moe",
4127 FfnKind::DenseMoe(_) => "DenseMoe",
4128 };
4129 eprintln!("graph L{li}: attn={ak} ffn={fk}");
4130 }
4131 let gffn = match &lw.ffn {
4132 FfnKind::DenseMoe(_) => return None, FfnKind::Dense(d) => crate::gpu::GraphFfn::Dense {
4134 gate: gw(&d.gate_proj)?,
4135 up: gw(&d.up_proj)?,
4136 down: gw(&d.down_proj)?,
4137 },
4138 FfnKind::Moe(m) => {
4139 if m.router_sigmoid
4144 || m.expert_bias.is_some()
4145 || m.route_tau.is_some()
4146 || m.mask.is_some()
4147 {
4148 return None;
4149 }
4150 let (se, sg) = m.shared.as_ref()?;
4151 let sgate = gw(sg.as_ref()?)?;
4152 let router = gw(&m.router)?;
4153 let inter = m.experts.first()?.gate_proj.rows();
4154 let mut experts = Vec::with_capacity(m.experts.len() + 1);
4155 let mut q4tp: Option<bool> = None;
4158 let mut gu_q2: Option<bool> = None;
4161 for e in m.experts.iter().chain(std::iter::once(se)) {
4162 if !matches!(e.act, Act::Silu)
4163 || e.gate_proj.rows() != inter
4164 || e.up_proj.rows() != inter
4165 {
4166 return None;
4167 }
4168 let (mm, gi, ui, di, is_p, is_q2) = match e.gate_proj.mapped_q4t() {
4169 Some((mm, gi)) => (
4170 mm,
4171 gi,
4172 e.up_proj.mapped_q4t()?.1,
4173 e.down_proj.mapped_q4t()?.1,
4174 false,
4175 false,
4176 ),
4177 None => match e.gate_proj.mapped_q2tp() {
4178 Some((mm, gi)) => (
4179 mm,
4180 gi,
4181 e.up_proj.mapped_q2tp()?.1,
4182 e.down_proj.mapped_q4tp()?.1,
4183 true,
4184 true,
4185 ),
4186 None => {
4187 let (mm, gi) = e.gate_proj.mapped_q4tp()?;
4188 (
4189 mm,
4190 gi,
4191 e.up_proj.mapped_q4tp()?.1,
4192 e.down_proj.mapped_q4tp()?.1,
4193 true,
4194 false,
4195 )
4196 }
4197 },
4198 };
4199 if *q4tp.get_or_insert(is_p) != is_p || *gu_q2.get_or_insert(is_q2) != is_q2
4200 {
4201 tracing::warn!(
4207 "MoE layer mixes expert layouts (q4tp={is_p}, q2tp gate/up={is_q2}) — every expert of a layer, INCLUDING the shared one, must share a layout. The whole-token graph declines this layer."
4208 );
4209 return None;
4210 }
4211 model.get_or_insert_with(|| mm.clone());
4212 experts.push((gi, ui, di));
4213 }
4214 crate::gpu::GraphFfn::Moe {
4215 router,
4216 shared_gate: sgate,
4217 experts,
4218 n_exp: m.experts.len(),
4219 top_k: std::env::var("CMF_TOPK_PROBE")
4225 .ok()
4226 .and_then(|v| v.parse::<usize>().ok())
4227 .filter(|k| *k > 0 && *k <= m.top_k)
4228 .unwrap_or(m.top_k),
4229 inter,
4230 norm_topk: m.norm_topk_prob,
4231 q4tp: q4tp?,
4232 gu_q2: gu_q2.unwrap_or(false),
4233 }
4234 }
4235 };
4236 let attn = match &lw.attn {
4237 AttnKind::Full {
4238 wq,
4239 wk,
4240 wv,
4241 wo,
4242 q_norm,
4243 k_norm,
4244 output_gate,
4245 softplus_gate,
4246 bias,
4247 } => {
4248 if softplus_gate.is_some() || self.attention_heads_per_layer.is_some() {
4249 return None;
4250 }
4251 let (m, _, _, _) = wq.graph_weight()?;
4252 model = Some(m.clone());
4253 crate::gpu::GraphAttn::Full {
4254 wq: gw(wq)?,
4255 wk: gw(wk)?,
4256 wv: gw(wv)?,
4257 wo: gw(wo)?,
4258 q_norm: q_norm.as_deref(),
4259 k_norm: k_norm.as_deref(),
4260 bias: bias
4261 .as_ref()
4262 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
4263 output_gate: *output_gate,
4264 cpu_k: self.kv_cache.layers[li].k_heads(),
4265 cpu_v: self.kv_cache.layers[li].v_heads(),
4266 }
4267 }
4268 AttnKind::LinearGdn(w) => {
4269 let cfg = self.gdn_cfg?;
4270 let (m, _, _, _) = w.in_proj_qkv.graph_weight()?;
4271 model = Some(m.clone());
4272 crate::gpu::GraphAttn::Gdn {
4273 qkv: gw(&w.in_proj_qkv)?,
4274 z: gw(&w.in_proj_z)?,
4275 a: gw(&w.in_proj_a)?,
4276 b: gw(&w.in_proj_b)?,
4277 out: gw(&w.out_proj)?,
4278 conv1d: &w.conv1d,
4279 a_log: &w.a_log,
4280 dt_bias: &w.dt_bias,
4281 norm: &w.norm,
4282 nv: cfg.num_v_heads,
4283 nk: cfg.num_k_heads,
4284 dk: cfg.key_head_dim,
4285 dv: cfg.value_head_dim,
4286 kk: cfg.conv_kernel,
4287 cpu_state: &self.kv_cache.layers[self.phys_layer(li)].linear_state,
4288 }
4289 }
4290 _ => return None,
4291 };
4292 layers.push(crate::gpu::GraphLayer {
4293 input_norm: &lw.input_norm,
4294 attn,
4295 post_norm: &lw.post_norm,
4296 ffn: gffn,
4297 });
4298 }
4299 let model = model?;
4300 let lm_gw = if self.graph_want_logits
4306 && std::env::var("CMF_GPU_LMHEAD")
4307 .map(|v| v != "0")
4308 .unwrap_or(true)
4309 {
4310 self.weights.lm_head.graph_weight().map(|(_, i, kind, rs)| {
4311 (
4312 crate::gpu::GraphW {
4313 idx: i,
4314 kind,
4315 row_scale: rs,
4316 data: &[],
4317 },
4318 self.weights.lm_head.rows(),
4319 )
4320 })
4321 } else {
4322 None
4323 };
4324 let lm = lm_gw.as_ref().map(|(gw, rows)| (gw, *rows));
4325 let emb_gw = if steps > 1 {
4327 self.weights
4328 .embed_tokens
4329 .graph_weight()
4330 .map(|(_, i, kind, rs)| {
4331 (
4332 crate::gpu::GraphW {
4333 idx: i,
4334 kind,
4335 row_scale: rs,
4336 data: &[],
4337 },
4338 self.weights.embed_tokens.rows(),
4339 self.embed_multiplier as f32,
4340 )
4341 })
4342 } else {
4343 None
4344 };
4345
4346 let loop_norm_at: Vec<usize> = if self.loop_final_norm {
4349 (0..self.num_layers - 1)
4350 .filter(|&li| (li + 1) % self.physical_layers == 0)
4351 .collect()
4352 } else {
4353 Vec::new()
4354 };
4355 let mut h = hidden.to_vec();
4356 crate::gpu::forward_token_graph(
4357 &model,
4358 self.graph_kv_id,
4359 &layers,
4360 &o1_views,
4361 self.o1_epoch,
4362 &self.inv_freq,
4363 &mut h,
4364 nh,
4365 nkv,
4366 hd,
4367 rd,
4368 self.hidden_size,
4369 self.intermediate_size,
4370 position,
4371 self.kv_cache.max_seq_len,
4372 gemma,
4373 self.rms_eps as f32,
4374 lm,
4375 &self.weights.final_norm,
4376 logits_out,
4377 &loop_norm_at,
4378 steps,
4379 emb_gw.as_ref().map(|(gw, rows, m)| (gw, *rows, *m)),
4380 ids_out,
4381 layers_run,
4382 )
4383 .then_some(h)
4384 }
4385
4386 fn try_batch_graph_wgpu(
4391 &self,
4392 hiddens: &mut [f32],
4393 positions: &[usize],
4394 k: usize,
4395 spec: Option<crate::gpu::SpecTail<'_>>,
4396 ) -> bool {
4397 let _tb = std::time::Instant::now();
4398 if self.attn_softcap > 0.0 {
4399 return false; }
4401 if self.o1_active() {
4402 return false;
4403 }
4404 let nh = self.num_heads;
4405 let (nkv, hd, rd) = self.layer_geom(0);
4406 let gemma = self.norm_style == cortiq_core::NormStyle::Gemma;
4407 fn gw(t: &QTensor) -> Option<crate::gpu::GraphW<'_>> {
4408 if let Some((_, i, kind, rs)) = t.graph_weight() {
4409 return Some(crate::gpu::GraphW {
4410 idx: i,
4411 kind,
4412 row_scale: rs,
4413 data: &[],
4414 });
4415 }
4416 t.as_f32().map(|d| crate::gpu::GraphW {
4417 idx: 0,
4418 kind: 4,
4419 row_scale: &[],
4420 data: d,
4421 })
4422 }
4423 let built: Option<(
4424 Vec<crate::gpu::GraphLayer<'_>>,
4425 std::sync::Arc<cortiq_core::CmfModel>,
4426 )> = (|| {
4427 let mut layers = Vec::with_capacity(self.num_layers);
4428 let mut model = None;
4429 for li in 0..self.num_layers {
4430 let lw = &self.weights.layers[self.phys_layer(li)];
4431 let gffn = match &lw.ffn {
4438 FfnKind::Dense(d) => crate::gpu::GraphFfn::Dense {
4439 gate: gw(&d.gate_proj)?,
4440 up: gw(&d.up_proj)?,
4441 down: gw(&d.down_proj)?,
4442 },
4443 FfnKind::Moe(m) => {
4444 if m.router_sigmoid
4445 || m.expert_bias.is_some()
4446 || m.route_tau.is_some()
4447 || m.mask.is_some()
4448 {
4449 return None;
4450 }
4451 let (se, sg) = m.shared.as_ref()?;
4452 let sgate = gw(sg.as_ref()?)?;
4453 let router = gw(&m.router)?;
4454 let inter = m.experts.first()?.gate_proj.rows();
4455 let mut experts = Vec::with_capacity(m.experts.len() + 1);
4456 let mut q4tp: Option<bool> = None;
4457 for e in m.experts.iter().chain(std::iter::once(se)) {
4458 if !matches!(e.act, Act::Silu)
4459 || e.gate_proj.rows() != inter
4460 || e.up_proj.rows() != inter
4461 {
4462 return None;
4463 }
4464 let (mm, gi, ui, di, is_p) = match e.gate_proj.mapped_q4t() {
4465 Some((mm, gi)) => (
4466 mm,
4467 gi,
4468 e.up_proj.mapped_q4t()?.1,
4469 e.down_proj.mapped_q4t()?.1,
4470 false,
4471 ),
4472 None => {
4473 let (mm, gi) = e.gate_proj.mapped_q4tp()?;
4474 (
4475 mm,
4476 gi,
4477 e.up_proj.mapped_q4tp()?.1,
4478 e.down_proj.mapped_q4tp()?.1,
4479 true,
4480 )
4481 }
4482 };
4483 if *q4tp.get_or_insert(is_p) != is_p {
4484 return None;
4485 }
4486 model.get_or_insert_with(|| mm.clone());
4487 experts.push((gi, ui, di));
4488 }
4489 crate::gpu::GraphFfn::Moe {
4490 router,
4491 shared_gate: sgate,
4492 experts,
4493 n_exp: m.experts.len(),
4494 top_k: m.top_k,
4495 inter,
4496 norm_topk: m.norm_topk_prob,
4497 q4tp: q4tp?,
4498 gu_q2: false,
4501 }
4502 }
4503 _ => return None,
4504 };
4505 let attn = match &lw.attn {
4506 AttnKind::Full {
4507 wq,
4508 wk,
4509 wv,
4510 wo,
4511 q_norm,
4512 k_norm,
4513 output_gate,
4514 softplus_gate,
4515 bias,
4516 } => {
4517 if softplus_gate.is_some() || self.attention_heads_per_layer.is_some() {
4518 return None;
4519 }
4520 let (m, _, _, _) = wq.graph_weight()?;
4521 model = Some(m.clone());
4522 crate::gpu::GraphAttn::Full {
4523 wq: gw(wq)?,
4524 wk: gw(wk)?,
4525 wv: gw(wv)?,
4526 wo: gw(wo)?,
4527 q_norm: q_norm.as_deref(),
4528 k_norm: k_norm.as_deref(),
4529 bias: bias
4530 .as_ref()
4531 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
4532 output_gate: *output_gate,
4533 cpu_k: self.kv_cache.layers[li].k_heads(),
4534 cpu_v: self.kv_cache.layers[li].v_heads(),
4535 }
4536 }
4537 AttnKind::LinearGdn(w) => {
4538 let cfg = self.gdn_cfg?;
4539 let (m, _, _, _) = w.in_proj_qkv.graph_weight()?;
4540 model = Some(m.clone());
4541 crate::gpu::GraphAttn::Gdn {
4542 qkv: gw(&w.in_proj_qkv)?,
4543 z: gw(&w.in_proj_z)?,
4544 a: gw(&w.in_proj_a)?,
4545 b: gw(&w.in_proj_b)?,
4546 out: gw(&w.out_proj)?,
4547 conv1d: &w.conv1d,
4548 a_log: &w.a_log,
4549 dt_bias: &w.dt_bias,
4550 norm: &w.norm,
4551 nv: cfg.num_v_heads,
4552 nk: cfg.num_k_heads,
4553 dk: cfg.key_head_dim,
4554 dv: cfg.value_head_dim,
4555 kk: cfg.conv_kernel,
4556 cpu_state: &self.kv_cache.layers[self.phys_layer(li)].linear_state,
4557 }
4558 }
4559 _ => return None,
4560 };
4561 layers.push(crate::gpu::GraphLayer {
4562 input_norm: &lw.input_norm,
4563 attn,
4564 post_norm: &lw.post_norm,
4565 ffn: gffn,
4566 });
4567 }
4568 Some((layers, model?))
4569 })();
4570 let Some((layers, model)) = built else {
4571 {
4572 use std::sync::atomic::{AtomicBool, Ordering};
4573 static SAID: AtomicBool = AtomicBool::new(false);
4574 if !SAID.swap(true, Ordering::Relaxed) {
4575 tracing::warn!("batch graph: BUILDER refused (layer weights/kinds)");
4576 }
4577 }
4578 return false;
4579 };
4580 if std::env::var("CMF_GRAPH_SPEC_TIME").is_ok() {
4581 eprintln!("batch-build: {:.1} ms", _tb.elapsed().as_secs_f64() * 1e3);
4582 }
4583 crate::gpu::forward_batch_graph(
4584 &model,
4585 self.graph_kv_id,
4586 &layers,
4587 &self.inv_freq,
4588 hiddens,
4589 nh,
4590 nkv,
4591 hd,
4592 rd,
4593 self.hidden_size,
4594 self.intermediate_size,
4595 positions,
4596 self.kv_cache.max_seq_len,
4597 gemma,
4598 self.rms_eps as f32,
4599 k,
4600 spec,
4601 )
4602 }
4603
4604 fn draft_probe() -> bool {
4608 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4609 *ON.get_or_init(|| std::env::var("CMF_DSV4_DRAFT_PROBE").is_ok_and(|v| v != "0"))
4610}
4611
4612 #[cfg(feature = "gpu")]
4624 fn dsv4_spec_on() -> bool {
4625 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4626 *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC").map(|v| v != "0").unwrap_or(true))
4627 }
4628
4629 #[cfg(feature = "gpu")]
4636 fn dsv4_spec_step(
4637 &mut self,
4638 tip_token: u32,
4639 t_next: u32,
4640 next_pos: usize,
4641 drafted: &mut usize,
4642 accepted_ctr: &mut usize,
4643 ) -> Option<(Vec<u32>, usize)> {
4644 let t_all = std::time::Instant::now();
4645 if std::env::var("CMF_DSV4_SPEC_TIME").is_ok() {
4646 thread_local! {
4647 static LAST: std::cell::Cell<Option<std::time::Instant>> =
4648 const { std::cell::Cell::new(None) };
4649 }
4650 LAST.with(|l| {
4651 if let Some(prev) = l.get() {
4652 eprintln!("между раундами {:.1} мс", prev.elapsed().as_secs_f64() * 1e3);
4653 }
4654 l.set(Some(std::time::Instant::now()));
4655 });
4656 }
4657 if std::env::var("CMF_DSV4_SPEC_DEBUG").is_ok() {
4658 eprintln!("spec_step: вход pos={next_pos}");
4659 }
4660 let n_layers = self.dsv4.as_ref().map(|b| b.1.len())?;
4661 let cfg = self.dsv4.as_ref().map(|b| b.2)?;
4662 if self.dspark.is_none() {
4664 let t = crate::dsv4::dspark_targets(&self.dsv4_mtp, &cfg, n_layers);
4665 if t.is_empty() {
4666 return None;
4667 }
4668 crate::dsv4::dspark_arm(&t, cfg.dim);
4669 self.dspark = Some(crate::dsv4::DsparkState::new(
4670 self.dsv4_mtp.len(),
4671 &cfg,
4672 t.len(),
4673 ));
4674 }
4675 let targets = crate::dsv4::dspark_targets(&self.dsv4_mtp, &cfg, n_layers);
4676 let pack = crate::dsv4::dspark_pack_get(&self.dsv4_mtp, &cfg);
4677 if pack.is_none() && std::env::var("CMF_DSV4_SPEC_DEBUG").is_ok() {
4678 eprintln!("spec_step: пак не построился (targets {targets:?})");
4679 }
4680 let pack = pack?;
4681 let block = crate::dsv4::dspark_block();
4682 let b_box = self.dsv4.as_mut()?;
4683 let (g, layers, st) = (&b_box.0, &b_box.1, &mut b_box.3);
4684 let ds = self.dspark.as_mut()?;
4685 let dbg = std::env::var("CMF_DSV4_SPEC_DEBUG").is_ok();
4688 if !crate::dsv4::dspark_take(&mut ds.main_hidden) && !ds.have_hidden {
4689 if dbg {
4690 eprintln!("spec_step: нет захвата");
4691 }
4692 return None;
4693 }
4694 ds.have_hidden = true;
4695 let tip_pos = next_pos.checked_sub(1)?;
4696 let draft_started = std::time::Instant::now();
4697 let mut conf = Vec::new();
4698 let props = crate::dsv4::dspark_draft_gpu(
4699 g,
4700 &self.dsv4_mtp,
4701 &cfg,
4702 ds,
4703 pack,
4704 st.kv_id,
4705 tip_token,
4706 tip_pos,
4707 self.pool.as_deref(),
4708 &mut conf,
4709 );
4710 self.dspark_draft_ns += draft_started.elapsed().as_nanos();
4711 *drafted += block;
4712 if props.is_empty() || props[0] != t_next {
4713 if dbg {
4714 eprintln!(
4715 "spec_step: черновик {} (props0={:?} t_next={t_next})",
4716 if props.is_empty() { "пуст" } else { "мимо" },
4717 props.first()
4718 );
4719 }
4720 return None;
4721 }
4722 let mut k_verify = crate::dsv4::dspark_verify_k().min(props.len());
4723 let conf_min = {
4729 static M: std::sync::OnceLock<f32> = std::sync::OnceLock::new();
4730 *M.get_or_init(|| {
4731 std::env::var("CMF_DSPARK_CONF_MIN")
4732 .ok()
4733 .and_then(|v| v.parse().ok())
4734 .unwrap_or(0.0)
4735 })
4736 };
4737 if conf_min > 0.0 && conf.len() >= props.len() {
4738 let mut keep = 1usize;
4739 while keep < k_verify && conf.get(keep).copied().unwrap_or(0.0) >= conf_min {
4740 keep += 1;
4741 }
4742 k_verify = k_verify.min(keep.max(2));
4743 }
4744 if k_verify < 2 {
4745 return None;
4746 }
4747 let mut fed = Vec::with_capacity(k_verify);
4748 fed.push(t_next);
4749 fed.extend_from_slice(&props[1..k_verify]);
4750 let mut argmax = Vec::new();
4751 let mut logits_all = Vec::new();
4752 let mut walked = Vec::new();
4753 let txn = crate::dsv4::dsv4_verify_chunk(
4754 g,
4755 layers,
4756 &cfg,
4757 st,
4758 &fed,
4759 next_pos,
4760 &self.inv_freq,
4761 self.pool.as_deref(),
4762 &targets,
4763 &mut argmax,
4764 &mut logits_all,
4765 &mut walked,
4766 );
4767 if txn.is_none() && dbg {
4768 eprintln!("spec_step: verify отказал");
4769 }
4770 let txn = txn?;
4771 let b = fed.len();
4772 let mut accepted = 1usize;
4773 while accepted < b && fed[accepted] == argmax[accepted - 1] {
4774 accepted += 1;
4775 }
4776 if std::env::var("CMF_DSV4_SPEC_FORCE_REJECT").is_ok_and(|v| v != "0") {
4781 accepted = 1;
4782 }
4783 if std::env::var("CMF_DSV4_SPEC_TRACE").is_ok() {
4784 eprintln!(
4785 "spec@{next_pos}: fed={fed:?} argmax={argmax:?} accepted={accepted}"
4786 );
4787 }
4788 let t_fin = std::time::Instant::now();
4789 if !crate::dsv4::dsv4_spec_finish(
4790 g,
4791 layers,
4792 &cfg,
4793 st,
4794 txn,
4795 accepted,
4796 &fed,
4797 &self.inv_freq,
4798 self.pool.as_deref(),
4799 ) {
4800 tracing::warn!("dsv4: спекулятивный откат не удался — состояние подозрительно");
4801 return None;
4802 }
4803 if std::env::var("CMF_DSV4_SPEC_TIME").is_ok() {
4804 eprintln!("finish(k={accepted}): {:.1} мс", t_fin.elapsed().as_secs_f64() * 1e3);
4805 }
4806 *accepted_ctr += accepted - 1;
4807 let (hc, dim) = (cfg.hc_mult, cfg.dim);
4812 let dev_caps: Vec<usize> = targets
4819 .iter()
4820 .copied()
4821 .filter(|&t| {
4822 st.dev_set.get(t).copied().unwrap_or(false)
4823 && !st.partial_set.get(t).copied().unwrap_or(false)
4824 })
4825 .collect();
4826 let mut caps_all = vec![0.0f32; dev_caps.len() * b * hc * dim];
4827 if !crate::gpu_wgpu::dsv4_spec_cap_read_all(b, dev_caps.len(), hc * dim, &mut caps_all) {
4828 return None;
4829 }
4830 for t in 0..accepted {
4831 let tip = t + 1 == accepted;
4832 for (slot, &tl) in targets.iter().enumerate() {
4833 if let Some(di) = dev_caps.iter().position(|&d| d == tl) {
4834 let lo = (di * b + t) * hc * dim;
4835 crate::dsv4::dspark_capture(
4836 &caps_all[lo..lo + hc * dim],
4837 &cfg,
4838 slot,
4839 &mut ds.main_hidden,
4840 );
4841 } else if tip
4842 && crate::dsv4::dspark_peek_slot(slot, dim, {
4843 let lo = slot * dim;
4844 &mut ds.main_hidden[lo..lo + dim]
4845 })
4846 {
4847 } else {
4852 crate::dsv4::dspark_capture(
4856 &walked[t * hc * dim..(t + 1) * hc * dim],
4857 &cfg,
4858 slot,
4859 &mut ds.main_hidden,
4860 );
4861 }
4862 }
4863 crate::dsv4::dspark_ring_append(g, &self.dsv4_mtp, &cfg, ds, next_pos + t, self.pool.as_deref());
4864 }
4865 let row = logits_all[(accepted - 1) * cfg.vocab..accepted * cfg.vocab].to_vec();
4866 self.graph_logits = Some(row);
4867 if std::env::var("CMF_DSV4_TRUNK_PICK_DUMP").is_ok() {
4872 crate::dsv4::trunk_freq_note(&crate::dsv4::pick_tally_take());
4873 crate::dsv4::pick_tally_arm();
4874 }
4875 if std::env::var("CMF_DSV4_SPEC_TIME").is_ok() {
4876 eprintln!("spec_step total {:.1} мс (k={accepted})", t_all.elapsed().as_secs_f64() * 1e3);
4877 }
4878 Some((fed[1..accepted].to_vec(), next_pos + accepted))
4879 }
4880
4881 fn dspark_probe(&mut self, position: usize, token_id: u32) {
4882 if self.dsv4_mtp.is_empty() || !Self::draft_probe() {
4883 return;
4884 }
4885 let trunk_now = crate::dsv4::pick_tally_take();
4887 crate::dsv4::trunk_freq_note(&trunk_now);
4888 if !trunk_now.is_empty() {
4889 self.dspark_trunk_picks.push(trunk_now);
4890 let keep = crate::dsv4::dspark_block();
4891 if self.dspark_trunk_picks.len() > keep {
4892 self.dspark_trunk_picks.remove(0);
4893 }
4894 }
4895 for p in std::mem::take(&mut self.dspark_pending) {
4898 let Some(i) = position.checked_sub(p.0 + 1) else {
4899 continue;
4900 };
4901 let mut p = p;
4902 if i < p.1.len() {
4903 if p.2 && p.1[i] == token_id {
4904 p.3 = i + 1;
4905 } else {
4906 p.2 = false;
4907 }
4908 if i + 1 < p.1.len() {
4909 self.dspark_pending.push(p);
4910 continue;
4911 }
4912 }
4913 self.dspark_hist.push(p.3);
4914 self.dspark_real.push(token_id);
4915 }
4916 let Some(b) = &mut self.dsv4 else { return };
4917 let (g, layers, cfg) = (&b.0, &b.1, b.2);
4918 let n_layers = layers.len();
4919 if self.dspark.is_none() {
4920 let t = crate::dsv4::dspark_targets(&self.dsv4_mtp, &cfg, n_layers);
4921 if t.is_empty() {
4922 return;
4923 }
4924 eprintln!("DSpark: захват со слоёв {t:?}, блок {}", crate::dsv4::dspark_block());
4925 crate::dsv4::dspark_arm(&t, cfg.dim);
4926 self.dspark = Some(crate::dsv4::DsparkState::new(
4927 self.dsv4_mtp.len(),
4928 &cfg,
4929 t.len(),
4930 ));
4931 }
4932 let ds = self.dspark.as_mut().unwrap();
4933 if !crate::dsv4::dspark_take(&mut ds.main_hidden) {
4934 return; }
4936 let mut conf = Vec::new();
4937 crate::dsv4::pick_tally_arm();
4938 let draft_started = std::time::Instant::now();
4943 #[cfg(feature = "gpu")]
4944 let gpu_draft = crate::dsv4::dspark_gpu_on();
4945 #[cfg(not(feature = "gpu"))]
4946 let gpu_draft = false;
4947 let props = if gpu_draft {
4948 #[cfg(feature = "gpu")]
4949 {
4950 let kv_id = b.3.kv_id;
4951 match crate::dsv4::dspark_pack_get(&self.dsv4_mtp, &cfg) {
4952 Some(pk) => crate::dsv4::dspark_draft_gpu(
4953 g,
4954 &self.dsv4_mtp,
4955 &cfg,
4956 ds,
4957 pk,
4958 kv_id,
4959 token_id,
4960 position,
4961 self.pool.as_deref(),
4962 &mut conf,
4963 ),
4964 None => Vec::new(),
4965 }
4966 }
4967 #[cfg(not(feature = "gpu"))]
4968 Vec::new()
4969 } else {
4970 crate::gpu::cpu_scope(|| {
4971 crate::dsv4::dspark_draft(
4972 g,
4973 &self.dsv4_mtp,
4974 &cfg,
4975 ds,
4976 token_id,
4977 position,
4978 self.pool.as_deref(),
4979 &mut conf,
4980 )
4981 })
4982 };
4983 self.dspark_draft_ns += draft_started.elapsed().as_nanos();
4984 let draft_picks = crate::dsv4::pick_tally_take();
4985 crate::dsv4::dspark_freq_note(&draft_picks);
4986 crate::dsv4::pick_tally_arm();
4989 if !props.is_empty() {
4990 let (tu, tt) = {
4994 let flat: Vec<(usize, Vec<usize>)> = self
4995 .dspark_trunk_picks
4996 .iter()
4997 .flat_map(|v| v.iter().cloned())
4998 .collect();
4999 let mut per: std::collections::HashMap<usize, Vec<usize>> =
5001 std::collections::HashMap::new();
5002 for (li, picks) in flat {
5003 per.entry(li).or_default().extend(picks);
5004 }
5005 let n = per.len().max(1);
5006 let mut u = 0usize;
5007 let mut t = 0usize;
5008 for (_, v) in per {
5009 t += v.len();
5010 u += v.iter().collect::<std::collections::HashSet<_>>().len();
5011 }
5012 (u / n, t / n)
5013 };
5014 let (du, dt) = crate::dsv4::tally_unique(&draft_picks);
5015 self.dspark_exp.push((tu, tt, du, dt));
5016 self.dspark_pending.push((position, props, true, 0));
5017 }
5018 if self.dspark_hist.len() >= 8 && self.dspark_hist.len() % 8 == 0 {
5019 let n = self.dspark_hist.len() as f32;
5020 let mean: f32 = self.dspark_hist.iter().sum::<usize>() as f32 / n;
5021 let block = crate::dsv4::dspark_block();
5022 let mut at = vec![0usize; block + 1];
5023 for &k in &self.dspark_hist {
5024 at[k] += 1;
5025 }
5026 let mut surv = Vec::with_capacity(block);
5028 for i in 1..=block {
5029 let k = at[i..].iter().sum::<usize>() as f32 / n;
5030 surv.push(format!("{k:.2}"));
5031 }
5032 let distinct = self
5033 .dspark_real
5034 .iter()
5035 .collect::<std::collections::HashSet<_>>()
5036 .len();
5037 let (tu, tt, du, dt) = self.dspark_exp.iter().fold((0, 0, 0, 0), |a, b| {
5038 (a.0 + b.0, a.1 + b.1, a.2 + b.2, a.3 + b.3)
5039 });
5040 let m = self.dspark_exp.len().max(1);
5041 eprintln!(
5042 "DSpark: черновиков {}, принято в среднем {mean:.2} из {block} \
5043 (токенов за проход {:.2}), распределение {at:?}, выживание [{}]",
5044 self.dspark_hist.len(),
5045 mean + 1.0,
5046 surv.join(" ")
5047 );
5048 eprintln!(
5049 "DSpark: разных токенов {distinct} из {} (вырожденность), \
5050 эксперты ствол {}/{} на слой за {block} токенов, \
5051 черновик {}/{} за блок, draft {:.2} мс/блок",
5052 self.dspark_real.len(),
5053 tu / m,
5054 tt / m,
5055 du / m,
5056 dt / m,
5057 self.dspark_draft_ns as f64 / self.dspark_exp.len().max(1) as f64 / 1e6
5058 );
5059 }
5060 }
5061
5062 fn forward_layers_upto(
5063 &mut self,
5064 hidden: &[f32],
5065 position: usize,
5066 task_mask: Option<&TaskMask>,
5067 upto: Option<usize>,
5068 ) -> Vec<f32> {
5069 if let Some(b) = &mut self.dsv4 {
5075 let _ = (task_mask, upto);
5076 let token_id = hidden.first().copied().unwrap_or(0.0) as u32;
5077 let (g, layers, cfg, st) = (&b.0, &b.1, b.2, &mut b.3);
5078 st.pos = position;
5079 let mut logits = Vec::new();
5080 crate::dsv4::forward_token(
5081 g,
5082 layers,
5083 &cfg,
5084 st,
5085 token_id,
5086 &self.inv_freq,
5087 self.pool.as_deref(),
5088 &mut logits,
5089 );
5090 self.graph_logits = Some(logits);
5091 self.dspark_probe(position, token_id);
5092 return vec![0.0; self.hidden_size];
5095 }
5096 if let Some(b) = &self.g3n {
5099 let _ = (task_mask, upto);
5100 return crate::g3n::g3n_forward(
5101 &b.0,
5102 &b.1,
5103 hidden,
5104 position,
5105 &mut self.kv_cache.layers,
5106 self.num_heads,
5107 self.num_kv_heads,
5108 self.head_dim,
5109 self.pool.as_deref(),
5110 );
5111 }
5112 let mut h = hidden.to_vec();
5113 let (nh, _nkv, _hd, hs, _rd, eps) = (
5116 self.num_heads,
5117 self.num_kv_heads,
5118 self.head_dim,
5119 self.hidden_size,
5120 self.rotary_dim,
5121 self.rms_eps,
5122 );
5123 let pool = self.pool.clone();
5124 let graph_env = std::env::var("CMF_GPU_WGPU_GRAPH").ok();
5136 let graph_on = match graph_env.as_deref() {
5137 Some("0") => false,
5138 Some(_) => true,
5139 None => crate::gpu::wgpu_graph_default(),
5145 };
5146 let graph_trusted =
5147 graph_env.is_some() || crate::gpu::wgpu_graph_default() || self.gdn_cfg.is_some();
5148 let race_eligible = graph_on && upto.is_none() && task_mask.is_none();
5149 let mut tail_start = 0usize;
5150 if race_eligible && crate::gpu::graph_race_use_graph(graph_trusted) {
5151 let t_graph = std::time::Instant::now();
5152 let mut lg = Vec::new();
5153 let mut gl = 0usize;
5154 let built = self.try_token_graph_wgpu(hidden, position, &mut lg, &mut gl);
5155 graph_note(built.is_some());
5156 if let Some(hh) = built {
5157 let dur = t_graph.elapsed();
5158 if std::env::var("CMF_GRAPH_PROF").is_ok() {
5159 eprintln!("graph-call: {:.2} ms total", dur.as_secs_f64() * 1000.0);
5160 }
5161 if gl > 0 && gl < self.num_layers {
5162 h = hh;
5168 tail_start = gl;
5169 } else if graph_trusted || !crate::gpu::graph_race_first_token_hopeless(dur) {
5170 if !graph_trusted {
5171 crate::gpu::graph_race_record(true, dur);
5172 }
5173 if !lg.is_empty() {
5174 lg.resize(self.vocab_size, 0.0);
5177 if let Some(c) = self.final_softcap {
5178 for l in lg.iter_mut() {
5179 *l = c * (*l / c).tanh();
5180 }
5181 }
5182 self.graph_logits = Some(lg);
5183 }
5184 return hh;
5185 }
5186 }
5192 }
5193 let t_race_cpu = (race_eligible && !graph_trusted).then(std::time::Instant::now);
5194
5195 #[cfg(target_os = "macos")]
5196 let mut gpu_skip_until = 0usize;
5197 for li in tail_start..self.num_layers {
5198 crate::gpu::set_layer(li as i64); if let Some(u) = upto {
5200 if li > u {
5201 break;
5202 }
5203 }
5204 if let Some(mask) = task_mask {
5205 if !mask.layer_alive(li) {
5206 continue; }
5208 }
5209 #[cfg(target_os = "macos")]
5213 {
5214 if li < gpu_skip_until {
5215 continue;
5216 }
5217 if task_mask.is_none() {
5218 let end = self.q1_graph_gpu(li, upto, position, &mut h);
5219 if end > li {
5220 gpu_skip_until = end;
5221 if self.is_loop_end(end - 1) && end < self.num_layers {
5224 h = inference::rms_norm(
5225 &h,
5226 &self.weights.final_norm,
5227 self.rms_eps,
5228 self.norm_style,
5229 );
5230 }
5231 continue;
5232 }
5233 }
5234 }
5235
5236 let lw = &self.weights.layers[self.phys_layer(li)];
5237 if let Ok(tp) = std::env::var("CMF_TRACE_POS") {
5238 if tp.parse::<usize>().ok() == Some(position) {
5239 let n: f32 = h.iter().map(|x| x * x).sum::<f32>().sqrt();
5240 eprintln!(
5241 "TRACE pos {position} layer {li}: |h| = {n:.6} h0 {:.6} h1 {:.6}",
5242 h[0], h[1]
5243 );
5244 }
5245 }
5246 inference::rms_norm_into(
5249 &h,
5250 &lw.input_norm,
5251 self.rms_eps,
5252 self.norm_style,
5253 &mut self.ws.n1,
5254 );
5255
5256 let attn_out = match &lw.attn {
5257 AttnKind::Mla(w) => {
5258 let inv_freq_l = self.layer_inv_freq(li);
5259 let rs = self.layer_rope_scale(li);
5260 let eps = self.rms_eps;
5261 let pool = self.pool.clone();
5262 mla_attention(
5263 w,
5264 &self.ws.n1,
5265 &mut self.kv_cache.layers[li],
5266 position,
5267 &inv_freq_l,
5268 rs,
5269 eps,
5270 pool.as_deref(),
5271 )
5272 }
5273 AttnKind::Linear(w) => {
5274 let cfg = self.vmf_cfg.expect("linear layer without vmf_cfg");
5275 vmf_phase_forward(
5276 &self.ws.n1,
5277 w,
5278 &cfg,
5279 &mut self.kv_cache.layers[li].linear_state,
5280 self.pool.as_deref(),
5281 )
5282 }
5283 AttnKind::Kda(w) => {
5284 let cfg = self.kda_cfg.expect("kda layer without kda_cfg");
5285 crate::linear_core::kda_forward(
5286 &self.ws.n1,
5287 w,
5288 &cfg,
5289 &mut self.kv_cache.layers[li].linear_state,
5290 self.pool.as_deref(),
5291 )
5292 }
5293 AttnKind::LinearGdn(w) => {
5294 let cfg = self.gdn_cfg.expect("gdn layer without gdn_cfg");
5295 gdn_forward(
5296 &self.ws.n1,
5297 w,
5298 &cfg,
5299 &mut self.kv_cache.layers[li].linear_state,
5300 self.pool.as_deref(),
5301 )
5302 }
5303 AttnKind::ShortConv(w) => {
5304 let cfg = self
5305 .short_conv_cfg
5306 .expect("short-conv layer without short_conv_cfg");
5307 short_conv_forward(
5308 &self.ws.n1,
5309 w,
5310 &cfg,
5311 &mut self.kv_cache.layers[li].linear_state,
5312 self.pool.as_deref(),
5313 )
5314 }
5315 AttnKind::Full {
5316 wq,
5317 wk,
5318 wv,
5319 wo,
5320 q_norm,
5321 k_norm,
5322 output_gate,
5323 softplus_gate,
5324 bias,
5325 } if self.kv_cache.layers[li].o1_sealed() => {
5326 let inv_freq_l = self.layer_inv_freq(li);
5329 let (nkv_l, hd_l, rd_l) = self.layer_geom(li);
5330 let cfg = QwenAttnCfg {
5331 num_heads: self.layer_num_heads(li),
5332 num_kv_heads: nkv_l,
5333 head_dim: hd_l,
5334 hidden_size: hs,
5335 position,
5336 inv_freq: &inv_freq_l,
5337 rotary_dim: rd_l,
5338 scale: self.attn_scale,
5339 softcap: self.attn_softcap,
5340 window: None,
5341 v_norm: self.attn_v_norm,
5342 q_norm: q_norm.as_deref(),
5343 k_norm: k_norm.as_deref(),
5344 output_gate: *output_gate,
5345 softplus_gate: softplus_gate
5346 .as_ref()
5347 .map(|(gate, per_head)| (gate, *per_head)),
5348 rope_scale: self.layer_rope_scale(li),
5349 bias: bias
5350 .as_ref()
5351 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
5352 rms_eps: eps,
5353 norm_style: self.norm_style,
5354 pool: pool.as_deref(),
5355 };
5356 attention::qwen_attention_nystrom(
5357 &self.ws.n1,
5358 wq,
5359 wk,
5360 wv,
5361 wo,
5362 &mut self.kv_cache.layers[li],
5363 &cfg,
5364 )
5365 }
5366 AttnKind::Full {
5367 wq,
5368 wk,
5369 wv,
5370 wo,
5371 q_norm,
5372 k_norm,
5373 output_gate,
5374 softplus_gate,
5375 bias,
5376 } => 'attn: {
5377 if graph_on
5380 && !*output_gate
5381 && softplus_gate.is_none()
5382 && self.attention_heads_per_layer.is_none()
5383 && bias.is_none()
5384 && task_mask.is_none()
5385 {
5386 let inv_freq_l = self.layer_inv_freq(li);
5387 let (nkv_l, hd_l, rd_l) = self.layer_geom(li);
5388 let gemma = self.norm_style == cortiq_core::NormStyle::Gemma;
5389 if let (Some((gm, qi)), Some((_, ki)), Some((_, vi)), Some((_, oi))) = (
5390 wq.mapped_q1(),
5391 wk.mapped_q1(),
5392 wv.mapped_q1(),
5393 wo.mapped_q1(),
5394 ) {
5395 let gm = gm.clone();
5396 let mut out = vec![0f32; hs];
5397 let cache = &self.kv_cache.layers[li];
5398 if crate::gpu::attn_dropin(
5399 &gm,
5400 self.graph_kv_id,
5401 li,
5402 &self.ws.n1,
5403 qi,
5404 ki,
5405 vi,
5406 oi,
5407 q_norm.as_deref(),
5408 k_norm.as_deref(),
5409 &inv_freq_l,
5410 nh,
5411 nkv_l,
5412 hd_l,
5413 rd_l,
5414 hs,
5415 position,
5416 self.kv_cache.max_seq_len,
5417 gemma,
5418 eps as f32,
5419 cache.k_heads(),
5420 cache.v_heads(),
5421 &mut out,
5422 ) {
5423 break 'attn out;
5424 }
5425 }
5426 }
5427 let masked = task_mask
5428 .map(|m| m.head_flags(li, self.num_heads).iter().any(|&a| !a))
5429 .unwrap_or(false);
5430 let f32_view = (wq.as_f32(), wk.as_f32(), wv.as_f32(), wo.as_f32());
5431 match (masked, f32_view) {
5432 (true, (Some(q), Some(k), Some(v), Some(o))) => {
5435 let active_heads = task_mask.unwrap().head_flags(li, self.num_heads);
5436 attention::multi_head_attention(
5437 &self.ws.n1,
5438 q,
5439 k,
5440 v,
5441 o,
5442 &mut self.kv_cache.layers[li],
5443 self.num_heads,
5444 self.num_kv_heads,
5445 self.head_dim,
5446 self.hidden_size,
5447 position,
5448 &active_heads,
5449 &self.inv_freq,
5450 )
5451 }
5452 (masked, _) => {
5453 if masked {
5454 tracing::warn!(
5455 "layer {li}: head mask on quantized weights not \
5456 supported yet — executing dense"
5457 );
5458 }
5459 let inv_freq_l = self.layer_inv_freq(li);
5460 let (nkv_l, hd_l, rd_l) = self.layer_geom(li);
5461 let cfg = QwenAttnCfg {
5462 num_heads: self.layer_num_heads(li),
5463 num_kv_heads: nkv_l,
5464 head_dim: hd_l,
5465 hidden_size: hs,
5466 position,
5467 inv_freq: &inv_freq_l,
5468 rotary_dim: rd_l,
5469 scale: self.attn_scale,
5470 softcap: self.attn_softcap,
5471 window: self.layer_window(li),
5472 v_norm: self.attn_v_norm,
5473 q_norm: q_norm.as_deref(),
5474 k_norm: k_norm.as_deref(),
5475 output_gate: *output_gate,
5476 softplus_gate: softplus_gate
5477 .as_ref()
5478 .map(|(gate, per_head)| (gate, *per_head)),
5479 rope_scale: self.layer_rope_scale(li),
5480 bias: bias
5481 .as_ref()
5482 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
5483 rms_eps: eps,
5484 norm_style: self.norm_style,
5485 pool: pool.as_deref(),
5486 };
5487 attention::qwen_attention(
5488 &self.ws.n1,
5489 wq,
5490 wk,
5491 wv,
5492 wo,
5493 &mut self.kv_cache.layers[li],
5494 &cfg,
5495 )
5496 }
5497 }
5498 }
5499 };
5500 let attn_out = match &self.weights.layers[self.phys_layer(li)].attn_out_norm {
5503 Some(w) => inference::rms_norm(&attn_out, w, self.rms_eps, self.norm_style),
5504 None => attn_out,
5505 };
5506 let lw = &self.weights.layers[self.phys_layer(li)];
5507 inference::add_rmsnorm_fused_into(
5508 &mut h,
5509 &attn_out,
5510 &lw.post_norm,
5511 self.rms_eps,
5512 self.norm_style,
5513 &mut self.ws.p1,
5514 );
5515 let mut attn_out = attn_out;
5516 attention::recycle_buf(&mut attn_out);
5517 let post_normed = &self.ws.p1;
5518
5519 let ffn_masked = task_mask
5520 .map(|m| m.ffn_active_count(li) < self.intermediate_size)
5521 .unwrap_or(false);
5522 let f32_ffn = match &lw.ffn {
5525 FfnKind::Dense(d) => (
5526 d.gate_proj.as_f32(),
5527 d.up_proj.as_f32(),
5528 d.down_proj.as_f32(),
5529 ),
5530 FfnKind::Moe(_) | FfnKind::DenseMoe(_) => (None, None, None),
5531 };
5532 let ffn_out = match (ffn_masked, f32_ffn) {
5533 (true, (Some(g), Some(u), Some(d))) => {
5534 let active = task_mask.unwrap().ffn_active_indices(li);
5535 inference::sparse_ffn_forward(
5536 post_normed,
5537 g,
5538 u,
5539 d,
5540 self.hidden_size,
5541 self.intermediate_size,
5542 &active,
5543 self.pool.as_deref(),
5544 )
5545 }
5546 (true, _) => match &lw.ffn {
5550 FfnKind::Dense(d) if d.down_proj.sparse_col_ok() => {
5551 let active = task_mask.unwrap().ffn_active_indices(li);
5552 sparse_ffn_quant(
5553 d,
5554 post_normed,
5555 &active,
5556 self.hidden_size,
5557 self.pool.as_deref(),
5558 )
5559 }
5560 FfnKind::Dense(d) => {
5565 let active = task_mask.unwrap().ffn_active_indices(li);
5566 let (gf, uf, df) = dequant_dense_f32(d);
5567 inference::sparse_ffn_forward(
5568 post_normed,
5569 &gf,
5570 &uf,
5571 &df,
5572 self.hidden_size,
5573 self.intermediate_size,
5574 &active,
5575 self.pool.as_deref(),
5576 )
5577 }
5578 FfnKind::Moe(m) => {
5579 let allowed = task_mask.and_then(|tm| tm.expert_flags(li, m.experts.len()));
5583 ffn_forward(
5584 &lw.ffn,
5585 post_normed,
5586 self.pool.as_deref(),
5587 allowed.as_deref(),
5588 )
5589 }
5590 FfnKind::DenseMoe(dm) => dense_moe_ffn(
5591 dm,
5592 post_normed,
5593 &h,
5594 self.rms_eps,
5595 self.norm_style,
5596 self.pool.as_deref(),
5597 ),
5598 },
5599 (false, _) => match &lw.ffn {
5600 FfnKind::DenseMoe(dm) => dense_moe_ffn(
5601 dm,
5602 post_normed,
5603 &h,
5604 self.rms_eps,
5605 self.norm_style,
5606 self.pool.as_deref(),
5607 ),
5608 _ => {
5609 let allowed = match (&lw.ffn, task_mask) {
5610 (FfnKind::Moe(m), Some(tm)) => tm.expert_flags(li, m.experts.len()),
5611 _ => None,
5612 };
5613 ffn_forward(
5614 &lw.ffn,
5615 post_normed,
5616 self.pool.as_deref(),
5617 allowed.as_deref(),
5618 )
5619 }
5620 },
5621 };
5622 let ffn_out = match &self.weights.layers[self.phys_layer(li)].ffn_out_norm {
5623 Some(w) => inference::rms_norm(&ffn_out, w, self.rms_eps, self.norm_style),
5624 None => ffn_out,
5625 };
5626 for (i, &f) in ffn_out.iter().enumerate() {
5627 h[i] += f;
5628 }
5629 let mut ffn_out = ffn_out;
5630 attention::recycle_buf(&mut ffn_out);
5631
5632 if let Some(sc) = self.weights.layers[self.phys_layer(li)].layer_scale {
5634 for v in h.iter_mut() {
5635 *v *= sc;
5636 }
5637 }
5638
5639 if self.is_loop_end(li) && li + 1 < self.num_layers {
5642 h = inference::rms_norm(
5643 &h,
5644 &self.weights.final_norm,
5645 self.rms_eps,
5646 self.norm_style,
5647 );
5648 }
5649
5650 if self.dyn_phi_layer == Some(li) {
5654 self.update_dyn_phi(&h);
5655 }
5656 }
5657 crate::gpu::set_layer(-1); if let Some(t) = t_race_cpu {
5659 crate::gpu::graph_race_record(false, t.elapsed());
5660 }
5661
5662 h
5663 }
5664
5665 fn update_dyn_phi(&mut self, h: &[f32]) {
5668 const A: f32 = 0.2;
5669 if self.dyn_phi_ema.len() != h.len() {
5670 self.dyn_phi_ema = vec![0.0; h.len()];
5671 self.dyn_phi_seen = 0;
5672 }
5673 if self.dyn_phi_seen == 0 {
5674 self.dyn_phi_ema.copy_from_slice(h);
5675 } else {
5676 for (e, &v) in self.dyn_phi_ema.iter_mut().zip(h) {
5677 *e = (1.0 - A) * *e + A * v;
5678 }
5679 }
5680 self.dyn_phi_seen += 1;
5681 }
5682
5683 pub fn dyn_phi(&self) -> &[f32] {
5685 &self.dyn_phi_ema
5686 }
5687
5688 pub fn set_dyn_phi_layer(&mut self, layer: Option<usize>) {
5690 self.dyn_phi_layer = layer;
5691 self.dyn_phi_ema.clear();
5692 self.dyn_phi_seen = 0;
5693 }
5694
5695 pub fn dynamic_skills(&self) -> Vec<(usize, String, usize)> {
5697 let Some(model) = &self.model else {
5698 return Vec::new();
5699 };
5700 model
5701 .header
5702 .skills
5703 .iter()
5704 .enumerate()
5705 .filter_map(|(i, sk)| {
5706 let ok = matches!(self.dyn_skill_layers.get(i), Some(Some(_)));
5707 let sel = sk.selection.as_ref()?;
5708 (ok).then(|| (i, sk.id.clone(), sel.phi_layer))
5709 })
5710 .collect()
5711 }
5712
5713 pub fn active_skill(&self) -> Option<usize> {
5715 self.dyn_active
5716 }
5717
5718 pub fn enable_dynamic_routing(&mut self) -> usize {
5723 use crate::swarm::{DynRouter, RoutableSkill};
5724 let Some(model) = self.model.clone() else {
5725 return 0;
5726 };
5727 if self.dyn_blend_loaded {
5730 tracing::warn!("dynamic routing unavailable on a blend-loaded pipeline");
5731 return 0;
5732 }
5733 if let Some(a) = self.dyn_active {
5737 if !matches!(self.dyn_skill_layers.get(a), Some(Some(_))) {
5738 tracing::warn!("loaded skill is not FFN-eligible — dynamic routing unavailable");
5739 return 0;
5740 }
5741 }
5742 let hidden = self.hidden_size;
5743 let mut skills = Vec::new();
5744 for (idx, id, _phi) in self.dynamic_skills() {
5745 if let Some(sel) = model.header.skills[idx].selection.as_ref() {
5746 if let Some(rs) = RoutableSkill::from_descriptor(idx, id, sel, hidden) {
5747 skills.push(rs);
5748 }
5749 }
5750 }
5751 if skills.is_empty() {
5752 return 0;
5753 }
5754 let phi = skills[0].phi_layer;
5756 if skills.iter().any(|s| s.phi_layer != phi) {
5757 tracing::warn!("routable skills disagree on phi_layer; using {phi}");
5758 }
5759 let n = skills.len();
5760 self.set_dyn_phi_layer(Some(phi));
5761 self.dyn_router = Some(DynRouter::new(skills));
5762 n
5763 }
5764
5765 pub fn route_switches(&self) -> Vec<(usize, Option<String>, Option<String>)> {
5767 self.dyn_router
5768 .as_ref()
5769 .map(|r| r.switches.clone())
5770 .unwrap_or_default()
5771 }
5772
5773 fn lm_head_forward(&self, hidden: &[f32]) -> Vec<f32> {
5776 let rows = self.weights.lm_head.rows();
5777 let mut logits = attention::take_buf(rows.min(self.vocab_size));
5778 self.weights
5779 .lm_head
5780 .matvec(hidden, &mut logits, self.pool.as_deref());
5781 logits.resize(self.vocab_size, 0.0);
5782 if let Some(m) = self.logit_multiplier {
5783 for l in logits.iter_mut() {
5784 *l *= m;
5785 }
5786 }
5787 if let Some(c) = self.final_softcap {
5788 for l in logits.iter_mut() {
5789 *l = c * (*l / c).tanh();
5790 }
5791 }
5792 logits
5793 }
5794
5795 pub fn prefill_next_logits(&mut self, ids: &[u32], task_mask: Option<&TaskMask>) -> Vec<f32> {
5800 self.kv_cache.clear();
5801 self.kv_history.clear();
5802 let mut hidden = vec![0.0f32; self.hidden_size];
5803 for (pos, &id) in ids.iter().enumerate() {
5804 let emb = self.embed_single(id);
5805 hidden = self.forward_layers(&emb, pos, task_mask);
5806 }
5807 inference::rms_norm_into(
5808 &hidden,
5809 &self.weights.final_norm,
5810 self.rms_eps,
5811 self.norm_style,
5812 &mut self.ws.n1,
5813 );
5814 self.lm_head_forward(&self.ws.n1)
5815 }
5816}
5817
5818pub fn create_test_pipeline(
5820 hidden_size: usize,
5821 intermediate_size: usize,
5822 num_heads: usize,
5823 num_kv_heads: usize,
5824 head_dim: usize,
5825 num_layers: usize,
5826 vocab_size: usize,
5827) -> Pipeline {
5828 let synth = |n: usize, salt: usize| -> Vec<f32> {
5831 (0..n)
5832 .map(|i| (((i * 31 + salt * 17 + 7) % 97) as f32 / 97.0 - 0.5) * 0.2)
5833 .collect()
5834 };
5835 let qt = |rows: usize, cols: usize, salt: usize| -> QTensor {
5836 QTensor::from_f32(synth(rows * cols, salt), rows, cols)
5837 };
5838 let layer_weights: Vec<LayerWeights> = (0..num_layers)
5839 .map(|li| LayerWeights {
5840 input_norm: vec![1.0; hidden_size],
5841 post_norm: vec![1.0; hidden_size],
5842 attn_out_norm: None,
5843 ffn_out_norm: None,
5844 layer_scale: None,
5845 ffn: FfnKind::Dense(DenseFfn {
5846 gate_proj: qt(intermediate_size, hidden_size, li * 10 + 5),
5847 up_proj: qt(intermediate_size, hidden_size, li * 10 + 6),
5848 down_proj: qt(hidden_size, intermediate_size, li * 10 + 7),
5849 act: Act::Silu,
5850 }),
5851 attn: AttnKind::Full {
5852 bias: None,
5853 wq: qt(num_heads * head_dim, hidden_size, li * 10 + 1),
5854 wk: qt(num_kv_heads * head_dim, hidden_size, li * 10 + 2),
5855 wv: qt(num_kv_heads * head_dim, hidden_size, li * 10 + 3),
5856 wo: qt(hidden_size, num_heads * head_dim, li * 10 + 4),
5857 q_norm: None,
5858 k_norm: None,
5859 output_gate: false,
5860 softplus_gate: None,
5861 },
5862 })
5863 .collect();
5864
5865 Pipeline::new(
5866 Tokenizer::byte_level(),
5867 PipelineWeights {
5868 embed_tokens: qt(vocab_size, hidden_size, 100),
5869 layers: layer_weights,
5870 lm_head: qt(vocab_size, hidden_size, 200),
5871 final_norm: vec![1.0; hidden_size],
5872 },
5873 hidden_size,
5874 intermediate_size,
5875 num_heads,
5876 num_kv_heads,
5877 head_dim,
5878 num_layers,
5879 num_layers, false, vocab_size,
5882 1e-6,
5883 10_000.0,
5884 NormStyle::Qwen,
5885 4096,
5886 SamplerConfig {
5887 seed: Some(42),
5888 ..Default::default()
5889 },
5890 )
5891}
5892
5893fn dense_ffn_batch(d: &DenseFfn, xs: &[f32], b: usize, pool: Option<&Pool>) -> Vec<f32> {
5896 let inter = d.gate_proj.rows();
5897 let hidden = d.down_proj.rows();
5898 if d.act == Act::Silu && b >= 32 && crate::gpu::enabled_here() && !crate::gpu::mm_killed() {
5904 if let (Some((model, w1)), Some((_, w3)), Some((_, w2))) = (
5905 d.gate_proj.mapped_q4t(),
5906 d.up_proj.mapped_q4t(),
5907 d.down_proj.mapped_q4t(),
5908 ) {
5909 let mut out = vec![0.0f32; b * hidden];
5910 if crate::gpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, &mut out) {
5911 return out;
5912 }
5913 }
5914 }
5915 let mut g = vec![0.0f32; b * inter];
5916 d.gate_proj.matmat(xs, b, &mut g, pool);
5917 let mut u = vec![0.0f32; b * inter];
5918 d.up_proj.matmat(xs, b, &mut u, pool);
5919 for i in 0..b * inter {
5920 g[i] = d.act.combine(g[i], u[i]);
5921 }
5922 let mut out = vec![0.0f32; b * hidden];
5923 d.down_proj.matmat(&g, b, &mut out, pool);
5924 out
5925}
5926
5927fn accumulate_act(m: &MoeFfn, xs: &[f32], b: usize) {
5932 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5933 static DUMP: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5934 let on = *ON.get_or_init(|| std::env::var("CMF_RMS_TRACE").is_ok());
5935 let dump = *DUMP.get_or_init(|| std::env::var("CMF_ACT_DUMP").is_ok());
5936 if (!on && !dump) || b == 0 {
5937 return;
5938 }
5939 let hidden = xs.len() / b;
5940 if on {
5941 let mut acc = m.act_sq.borrow_mut();
5942 if acc.len() < hidden {
5943 acc.resize(hidden, 0.0);
5944 }
5945 for t in 0..b {
5946 let row = &xs[t * hidden..(t + 1) * hidden];
5947 for (a, &v) in acc.iter_mut().zip(row) {
5948 *a += (v as f64) * (v as f64);
5949 }
5950 }
5951 }
5952 if dump {
5953 let cap: usize = std::env::var("CMF_ACT_DUMP_ROWS")
5956 .ok()
5957 .and_then(|v| v.parse().ok())
5958 .unwrap_or(4096);
5959 let mut rows = m.act_rows.borrow_mut();
5960 if rows.len() < cap * hidden {
5961 let take = b.min((cap * hidden - rows.len()) / hidden.max(1));
5962 rows.extend_from_slice(&xs[..take * hidden]);
5963 }
5964 }
5965}
5966
5967fn moe_ffn_batch(
5968 m: &MoeFfn,
5969 xs: &[f32],
5970 b: usize,
5971 hidden: usize,
5972 pool: Option<&Pool>,
5973 allowed: Option<&[bool]>,
5974) -> Vec<f32> {
5975 accumulate_act(m, xs, b);
5976 let ne = m.experts.len();
5977 let mut logits = vec![0.0f32; b * ne];
5978 m.router.matmat(xs, b, &mut logits, pool);
5979
5980 let mut assign: Vec<Vec<(usize, f32)>> = vec![Vec::new(); ne];
5983 {
5984 let mut st = m.stats.borrow_mut();
5985 if st.len() < ne {
5986 st.resize(ne, 0);
5987 }
5988 for bi in 0..b {
5989 let (idx, p, wsum) = moe_route(&logits[bi * ne..(bi + 1) * ne], m, allowed);
5990 for &e in &idx {
5991 st[e] += 1;
5992 assign[e].push((bi, p[e] / wsum));
5993 }
5994 }
5995 }
5996
5997 let mut out = vec![0.0f32; b * hidden];
5998 let cols = m.experts[0].gate_proj.cols();
5999 let mut run_expert = |d: &DenseFfn, list: &[(usize, f32)]| {
6000 let sb = list.len();
6001 let mut sub = vec![0.0f32; sb * cols];
6002 for (k, &(bi, _)) in list.iter().enumerate() {
6003 sub[k * cols..(k + 1) * cols].copy_from_slice(&xs[bi * cols..(bi + 1) * cols]);
6004 }
6005 let eo = dense_ffn_batch(d, &sub, sb, pool);
6006 for (k, &(bi, w)) in list.iter().enumerate() {
6007 for i in 0..hidden {
6008 out[bi * hidden + i] += w * eo[k * hidden + i];
6009 }
6010 }
6011 };
6012 for (e, a) in assign.iter().enumerate().take(ne) {
6013 if !a.is_empty() {
6014 run_expert(&m.experts[e], a);
6015 }
6016 }
6017 if let Some((se, gate)) = &m.shared {
6018 let all: Vec<(usize, f32)> = if let Some(gate) = gate {
6019 let mut gl = vec![0.0f32; b];
6020 gate.matmat(xs, b, &mut gl, pool);
6021 (0..b)
6022 .map(|bi| (bi, 1.0 / (1.0 + (-gl[bi]).exp())))
6023 .collect()
6024 } else {
6025 (0..b).map(|bi| (bi, 1.0)).collect()
6026 };
6027 run_expert(se, &all);
6028 }
6029 out
6030}
6031
6032thread_local! {
6033 static FFN_SCRATCH: std::cell::RefCell<[Vec<f32>; 4]> =
6037 const { std::cell::RefCell::new([Vec::new(), Vec::new(), Vec::new(), Vec::new()]) };
6038}
6039
6040fn dense_ffn(d: &DenseFfn, x: &[f32], pool: Option<&Pool>) -> Vec<f32> {
6042 if crate::gpu::enabled_here()
6053 && (d.gate_proj.rows() >= crate::gpu::min_rows() || d.gate_proj.is_q1())
6054 {
6055 let arm = if d.gate_proj.is_q1() && crate::gpu::q1_force() {
6056 crate::gpu::ProbeArm::Gpu
6057 } else {
6058 crate::gpu::probe_arm(crate::gpu::OpClass::Ffn)
6059 };
6060 match arm {
6061 crate::gpu::ProbeArm::Gpu => {
6062 let t0 = std::time::Instant::now();
6063 if let Some(out) = dense_ffn_gpu(d, x, pool) {
6064 crate::gpu::probe_record(crate::gpu::OpClass::Ffn, true, t0.elapsed());
6065 return out;
6066 }
6067 }
6068 crate::gpu::ProbeArm::CpuTimed => {
6069 let t0 = std::time::Instant::now();
6070 let out = crate::gpu::cpu_scope(|| dense_ffn_cpu(d, x, pool));
6071 crate::gpu::probe_record(crate::gpu::OpClass::Ffn, false, t0.elapsed());
6072 return out;
6073 }
6074 crate::gpu::ProbeArm::Cpu => {
6075 return crate::gpu::cpu_scope(|| dense_ffn_cpu(d, x, pool));
6076 }
6077 }
6078 }
6079 dense_ffn_cpu(d, x, pool)
6080}
6081
6082fn dense_ffn_cpu(d: &DenseFfn, x: &[f32], pool: Option<&Pool>) -> Vec<f32> {
6084 let inter = d.gate_proj.rows();
6085 FFN_SCRATCH.with(|s| {
6086 let mut s = s.borrow_mut();
6087 let [g, u, ..] = &mut *s;
6088 g.resize(inter, 0.0);
6089 if d.act == Act::Silu && QTensor::matvec_silu_mul(&d.gate_proj, &d.up_proj, x, g, pool) {
6092 } else {
6094 u.resize(inter, 0.0);
6095 QTensor::matvec_many([&d.gate_proj, &d.up_proj], x, [g, u], pool);
6097 for i in 0..inter {
6098 g[i] = d.act.combine(g[i], u[i]);
6099 }
6100 }
6101 FFN_PROBE.with(|pr| {
6104 if let Some(acc) = pr.borrow_mut().as_mut() {
6105 let li = crate::gpu::cur_layer();
6106 if li >= 0 {
6107 if let Some(row) = acc.get_mut(li as usize) {
6108 for (a, &v) in row.iter_mut().zip(g.iter()) {
6109 *a += (v as f64).abs();
6110 }
6111 }
6112 }
6113 }
6114 });
6115 let mut out = attention::take_buf(d.down_proj.rows());
6116 d.down_proj.matvec(g, &mut out, pool);
6117 out
6118 })
6119}
6120
6121thread_local! {
6122 static FFN_PROBE: std::cell::RefCell<Option<Vec<Vec<f64>>>> =
6125 const { std::cell::RefCell::new(None) };
6126}
6127
6128fn dense_ffn_gpu(d: &DenseFfn, x: &[f32], _pool: Option<&Pool>) -> Option<Vec<f32>> {
6134 if d.act != Act::Silu {
6136 return None;
6137 }
6138 if d.gate_proj.rows() < crate::gpu::min_rows() && !d.gate_proj.is_q1() {
6141 return None;
6142 }
6143 let mut jobs: Vec<crate::gpu::MoeJob> = Vec::with_capacity(1);
6144 let mut model_ref = None;
6145 moe_push_job(d, x, 1.0, &mut jobs, &mut model_ref)?;
6146 let model = model_ref?;
6147 let hidden = jobs[0].down.1;
6148 let mut out = attention::take_buf(hidden);
6149 if crate::gpu::moe_block(&model, &jobs, &mut out) {
6150 Some(out)
6151 } else {
6152 let mut out = out;
6153 attention::recycle_buf(&mut out);
6154 None
6155 }
6156}
6157
6158#[allow(clippy::type_complexity)]
6163#[allow(clippy::type_complexity)]
6164pub(crate) fn moe_parts(
6165 t: &QTensor,
6166) -> Option<(
6167 &std::sync::Arc<cortiq_core::CmfModel>,
6168 usize,
6169 usize,
6170 usize,
6171 &[f32],
6172 &[f32],
6173 bool,
6174 bool,
6175)> {
6176 match t {
6177 QTensor::Mapped {
6178 model,
6179 idx,
6180 dtype: dt @ (cortiq_core::TensorDtype::Q8_2f | cortiq_core::TensorDtype::Q8Row),
6181 rows,
6182 cols,
6183 row_scale,
6184 col_field,
6185 ..
6186 } if (*dt == cortiq_core::TensorDtype::Q8Row) || !col_field.is_empty() => Some((
6187 model, *idx, *rows, *cols, row_scale, col_field, false, false,
6188 )),
6189 QTensor::Mapped {
6191 model,
6192 idx,
6193 dtype: cortiq_core::TensorDtype::Q1,
6194 rows,
6195 cols,
6196 ..
6197 } => Some((model, *idx, *rows, *cols, &[][..], &[][..], true, false)),
6198 QTensor::Mapped {
6200 model,
6201 idx,
6202 dtype: cortiq_core::TensorDtype::Q4Tiled,
6203 rows,
6204 cols,
6205 ..
6206 } => Some((model, *idx, *rows, *cols, &[][..], &[][..], false, true)),
6207 QTensor::Mapped {
6209 model,
6210 idx,
6211 dtype: cortiq_core::TensorDtype::Q4TiledP,
6212 rows,
6213 cols,
6214 ..
6215 } => Some((model, *idx, *rows, *cols, &[][..], &[][..], false, true)),
6216 _ => None,
6217 }
6218}
6219
6220pub(crate) fn moe_push_job_parts<'a>(
6224 gate: &'a QTensor,
6225 up: &'a QTensor,
6226 down: &'a QTensor,
6227 x: &[f32],
6228 w: f32,
6229 swiglu_limit: f32,
6230 jobs: &mut Vec<crate::gpu::MoeJob<'a>>,
6231 model_ref: &mut Option<std::sync::Arc<cortiq_core::CmfModel>>,
6232) -> Option<()> {
6233 use crate::qtensor::prescale;
6234 let (gm, gi, gr, gc, grs, gcf, gq1, gq4) = moe_parts(gate)?;
6235 let (_, ui, ur, uc, urs, ucf, uq1, uq4) = moe_parts(up)?;
6236 let (_, di, dr, dc, drs, dcf, dq1, dq4) = moe_parts(down)?;
6237 if gq1 != uq1 || uq1 != dq1 || gq4 != uq4 || uq4 != dq4 {
6238 return None; }
6240 model_ref.get_or_insert_with(|| gm.clone());
6241 let dt = |cf: &[f32]| {
6242 if cf.is_empty() {
6243 cortiq_core::TensorDtype::Q8Row
6244 } else {
6245 cortiq_core::TensorDtype::Q8_2f
6246 }
6247 };
6248 jobs.push(crate::gpu::MoeJob {
6249 gate: (gi, gr, gc, grs),
6250 up: (ui, ur, uc, urs),
6251 down: (di, dr, dc, drs),
6252 xs_gate: prescale(x, gcf, dt(gcf)).into_owned(),
6253 xs_up: prescale(x, ucf, dt(ucf)).into_owned(),
6254 down_col: dcf,
6255 w,
6256 q1: gq1,
6257 q4t: gq4 && gate.mapped_q4tp().is_none(),
6258 q4tp: gq4 && gate.mapped_q4tp().is_some(),
6259 swiglu_limit,
6260 });
6261 Some(())
6262}
6263
6264fn moe_push_job<'a>(
6266 d: &'a DenseFfn,
6267 x: &[f32],
6268 w: f32,
6269 jobs: &mut Vec<crate::gpu::MoeJob<'a>>,
6270 model_ref: &mut Option<std::sync::Arc<cortiq_core::CmfModel>>,
6271) -> Option<()> {
6272 use crate::qtensor::prescale;
6273 if d.act != Act::Silu {
6274 return None; }
6276 let (gm, gi, gr, gc, grs, gcf, gq1, gq4) = moe_parts(&d.gate_proj)?;
6277 let (_, ui, ur, uc, urs, ucf, uq1, uq4) = moe_parts(&d.up_proj)?;
6278 let (_, di, dr, dc, drs, dcf, dq1, dq4) = moe_parts(&d.down_proj)?;
6279 if gq1 != uq1 || uq1 != dq1 || gq4 != uq4 || uq4 != dq4 {
6280 return None; }
6282 model_ref.get_or_insert_with(|| gm.clone());
6283 let gdt = if gcf.is_empty() {
6284 cortiq_core::TensorDtype::Q8Row
6285 } else {
6286 cortiq_core::TensorDtype::Q8_2f
6287 };
6288 let udt = if ucf.is_empty() {
6289 cortiq_core::TensorDtype::Q8Row
6290 } else {
6291 cortiq_core::TensorDtype::Q8_2f
6292 };
6293 jobs.push(crate::gpu::MoeJob {
6294 gate: (gi, gr, gc, grs),
6295 up: (ui, ur, uc, urs),
6296 down: (di, dr, dc, drs),
6297 xs_gate: prescale(x, gcf, gdt).into_owned(),
6298 xs_up: prescale(x, ucf, udt).into_owned(),
6299 down_col: dcf,
6300 w,
6301 q1: gq1,
6302 q4t: gq4 && d.gate_proj.mapped_q4tp().is_none(),
6303 q4tp: gq4 && d.gate_proj.mapped_q4tp().is_some(),
6304 swiglu_limit: 0.0,
6305 });
6306 Some(())
6307}
6308
6309fn sparse_ffn_quant(
6316 d: &DenseFfn,
6317 x: &[f32],
6318 active: &[u16],
6319 hidden: usize,
6320 pool: Option<&Pool>,
6321) -> Vec<f32> {
6322 let n = active.len();
6323 let inter = d.gate_proj.rows();
6324 let mut act = vec![0.0f32; n];
6325 let need_scratch = !(d.gate_proj.sparse_col_ok() && d.up_proj.sparse_col_ok());
6328 let compute = |ai: usize| -> f32 {
6329 let idx = active[ai] as usize;
6330 if idx >= inter {
6331 return 0.0; }
6333 let mut s = if need_scratch {
6334 vec![0.0f32; hidden]
6335 } else {
6336 Vec::new()
6337 };
6338 let gate = d.gate_proj.row_dot(idx, x, &mut s);
6339 let up = d.up_proj.row_dot(idx, x, &mut s);
6340 d.act.combine(gate, up)
6341 };
6342 match pool {
6343 Some(p) if n >= 256 => {
6344 let ptr = SendMut(act.as_mut_ptr());
6345 p.run(&|widx, nw| {
6346 let chunk = n.div_ceil(nw);
6347 let (s, e) = (widx * chunk, ((widx + 1) * chunk).min(n));
6348 for ai in s..e {
6349 unsafe { *ptr.at(ai) = compute(ai) };
6350 }
6351 });
6352 }
6353 _ => {
6354 for (ai, a) in act.iter_mut().enumerate() {
6355 *a = compute(ai);
6356 }
6357 }
6358 }
6359 let mut out = vec![0.0f32; hidden];
6361 for (ai, &idx) in active.iter().enumerate() {
6362 let w = act[ai];
6363 if w.abs() >= 1e-12 && (idx as usize) < inter {
6364 d.down_proj.add_col_scaled(idx as usize, w, &mut out);
6365 }
6366 }
6367 out
6368}
6369
6370#[doc(hidden)]
6372pub fn sparse_ffn_quant_for_test(
6373 d: &DenseFfn,
6374 x: &[f32],
6375 active: &[u16],
6376 hidden: usize,
6377) -> Vec<f32> {
6378 sparse_ffn_quant(d, x, active, hidden, None)
6379}
6380
6381fn dequant_dense_f32(d: &DenseFfn) -> (Vec<f32>, Vec<f32>, Vec<f32>) {
6385 let deq = |t: &QTensor| -> Vec<f32> {
6386 let (rows, cols) = (t.rows(), t.cols());
6387 let mut out = vec![0.0f32; rows * cols];
6388 for r in 0..rows {
6389 t.row_f32(r, &mut out[r * cols..(r + 1) * cols]);
6390 }
6391 out
6392 };
6393 (deq(&d.gate_proj), deq(&d.up_proj), deq(&d.down_proj))
6394}
6395
6396struct SendMut(*mut f32);
6398unsafe impl Send for SendMut {}
6399unsafe impl Sync for SendMut {}
6400impl SendMut {
6401 #[inline]
6402 #[allow(clippy::mut_from_ref)]
6405 unsafe fn at(&self, i: usize) -> &mut f32 {
6406 unsafe { &mut *self.0.add(i) }
6407 }
6408}
6409
6410fn moe_route(logits: &[f32], m: &MoeFfn, allowed: Option<&[bool]>) -> (Vec<usize>, Vec<f32>, f32) {
6420 let ne = logits.len();
6421 let p: Vec<f32> = if m.router_sigmoid {
6422 logits.iter().map(|&l| 1.0 / (1.0 + (-l).exp())).collect()
6423 } else {
6424 let mx = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
6425 let mut e: Vec<f32> = logits.iter().map(|&l| (l - mx).exp()).collect();
6426 let s: f32 = e.iter().sum();
6427 for v in &mut e {
6428 *v /= s;
6429 }
6430 e
6431 };
6432 let admit = |e: usize| {
6438 m.mask.as_ref().is_none_or(|mk| mk[e])
6439 && allowed.is_none_or(|a| a.get(e).copied().unwrap_or(false))
6440 };
6441 let mut idx: Vec<usize> = (0..ne).filter(|&e| admit(e)).collect();
6442 match &m.expert_bias {
6444 Some(b) => idx.sort_unstable_by(|&x, &y| {
6445 (p[y] + b[y])
6446 .partial_cmp(&(p[x] + b[x]))
6447 .unwrap()
6448 .then(x.cmp(&y))
6449 }),
6450 None => idx.sort_unstable_by(|&x, &y| p[y].partial_cmp(&p[x]).unwrap().then(x.cmp(&y))),
6451 }
6452 idx.truncate(m.top_k);
6453 if let Some(tau) = m.route_tau {
6457 let total: f32 = idx.iter().map(|&e| p[e]).sum();
6458 if total > 0.0 {
6459 let mut acc = 0.0f32;
6460 let mut keep = idx.len();
6461 for (i, &e) in idx.iter().enumerate() {
6462 acc += p[e];
6463 if acc >= tau * total {
6464 keep = i + 1;
6465 break;
6466 }
6467 }
6468 idx.truncate(keep);
6469 }
6470 }
6471 let wsum: f32 = if m.norm_topk_prob {
6472 let s: f32 = idx.iter().map(|&e| p[e]).sum();
6473 (if m.router_sigmoid { s + 1e-6 } else { s }) / m.routed_scaling
6476 } else {
6477 1.0 / m.routed_scaling
6478 };
6479 (idx, p, wsum)
6480}
6481
6482fn moe_ffn(m: &MoeFfn, x: &[f32], pool: Option<&Pool>, allowed: Option<&[bool]>) -> Vec<f32> {
6485 accumulate_act(m, x, 1);
6486 let ne = m.experts.len();
6487 let mut logits = vec![0.0f32; ne];
6488 m.router.matvec(x, &mut logits, pool);
6489 let (idx, p, wsum) = moe_route(&logits, m, allowed);
6490 {
6491 let mut st = m.stats.borrow_mut();
6492 if st.len() < ne {
6493 st.resize(ne, 0);
6494 }
6495 for &e in &idx {
6496 st[e] += 1;
6497 }
6498 }
6499 if crate::gpu::enabled_here() {
6504 match crate::gpu::probe_arm(crate::gpu::OpClass::Ffn) {
6505 crate::gpu::ProbeArm::Gpu => {
6506 let t0 = std::time::Instant::now();
6507 if let Some(out) = moe_ffn_gpu(m, x, &idx, &p, wsum, pool) {
6508 crate::gpu::probe_record(crate::gpu::OpClass::Ffn, true, t0.elapsed());
6509 return out;
6510 }
6511 }
6512 crate::gpu::ProbeArm::CpuTimed => {
6513 let t0 = std::time::Instant::now();
6514 let out = crate::gpu::cpu_scope(|| moe_ffn_cpu(m, x, &idx, &p, wsum, pool));
6515 crate::gpu::probe_record(crate::gpu::OpClass::Ffn, false, t0.elapsed());
6516 return out;
6517 }
6518 crate::gpu::ProbeArm::Cpu => {
6519 return crate::gpu::cpu_scope(|| moe_ffn_cpu(m, x, &idx, &p, wsum, pool));
6520 }
6521 }
6522 }
6523 moe_ffn_cpu(m, x, &idx, &p, wsum, pool)
6524}
6525
6526fn graph_note(built: bool) {
6530 use std::sync::atomic::{AtomicBool, Ordering};
6531 static SAID: AtomicBool = AtomicBool::new(false);
6532 if !SAID.swap(true, Ordering::Relaxed) {
6533 if built {
6534 tracing::info!("wgpu whole-token graph: ACTIVE");
6535 } else {
6536 tracing::warn!("wgpu whole-token graph refused — per-op path");
6537 }
6538 }
6539}
6540
6541fn moe_batch_enabled() -> bool {
6544 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6545 *ON.get_or_init(|| std::env::var("CMF_MOE_BATCH").as_deref() != Ok("0"))
6546}
6547
6548fn moe_ffn_cpu_batched(
6554 m: &MoeFfn,
6555 x: &[f32],
6556 idx: &[usize],
6557 p: &[f32],
6558 wsum: f32,
6559 pool: Option<&Pool>,
6560) -> Option<Vec<f32>> {
6561 if idx.is_empty() || !moe_batch_enabled() {
6562 return None;
6563 }
6564 if FFN_PROBE.with(|pr| pr.borrow().is_some()) {
6568 return None;
6569 }
6570 let n = idx.len() + usize::from(m.shared.is_some());
6571 let mut pairs = Vec::with_capacity(n);
6572 let mut downs = Vec::with_capacity(n);
6573 let mut ws = Vec::with_capacity(n);
6574 for &e in idx {
6575 let d = &m.experts[e];
6576 if d.act != Act::Silu {
6577 return None;
6578 }
6579 pairs.push((&d.gate_proj, &d.up_proj));
6580 downs.push(&d.down_proj);
6581 ws.push(p[e] / wsum * m.per_expert_scale.as_ref().map_or(1.0, |v| v[e]));
6582 }
6583 if let Some((se, gate)) = &m.shared {
6586 if se.act != Act::Silu {
6587 return None;
6588 }
6589 let g = gate.as_ref().map_or(1.0, |gate| {
6590 let mut gl = [0.0f32; 1];
6591 gate.matvec(x, &mut gl, pool);
6592 1.0 / (1.0 + (-gl[0]).exp())
6593 });
6594 pairs.push((&se.gate_proj, &se.up_proj));
6595 downs.push(&se.down_proj);
6596 ws.push(g);
6597 }
6598 let inter = pairs[0].0.rows();
6599 let mut gs: Vec<Vec<f32>> = (0..pairs.len()).map(|_| vec![0f32; inter]).collect();
6600 if !QTensor::moe_gate_up_many(&pairs, x, &mut gs, pool) {
6601 return None;
6602 }
6603 let mut out = attention::take_buf(x.len());
6604 if !QTensor::moe_down_many(&downs, &gs, &ws, &mut out, pool) {
6605 attention::recycle_buf(&mut out);
6606 return None;
6607 }
6608 Some(out)
6609}
6610
6611fn moe_ffn_cpu(
6613 m: &MoeFfn,
6614 x: &[f32],
6615 idx: &[usize],
6616 p: &[f32],
6617 wsum: f32,
6618 pool: Option<&Pool>,
6619) -> Vec<f32> {
6620 if let Some(out) = moe_ffn_cpu_batched(m, x, idx, p, wsum, pool) {
6621 return out;
6622 }
6623 let mut out = attention::take_buf(x.len());
6624 for &e in idx {
6625 let mut eo = dense_ffn(&m.experts[e], x, pool);
6626 let w = p[e] / wsum * m.per_expert_scale.as_ref().map_or(1.0, |v| v[e]);
6627 for i in 0..out.len() {
6628 out[i] += w * eo[i];
6629 }
6630 attention::recycle_buf(&mut eo);
6631 }
6632 if let Some((se, gate)) = &m.shared {
6633 let mut so = dense_ffn(se, x, pool);
6634 let g = gate.as_ref().map_or(1.0, |gate| {
6635 let mut gl = [0.0f32; 1];
6636 gate.matvec(x, &mut gl, pool);
6637 1.0 / (1.0 + (-gl[0]).exp())
6638 });
6639 for i in 0..out.len() {
6640 out[i] += g * so[i];
6641 }
6642 attention::recycle_buf(&mut so);
6643 }
6644 out
6645}
6646
6647#[allow(clippy::too_many_arguments)]
6655fn mla_attention(
6656 w: &MlaWeights,
6657 normed: &[f32],
6658 cache: &mut crate::kv_cache::LayerKvCache,
6659 position: usize,
6660 inv_freq: &[f32],
6661 rope_scale: f32,
6662 eps: f64,
6663 pool: Option<&Pool>,
6664) -> Vec<f32> {
6665 let (nh, dr, dn, dv, lora) = (w.nh, w.qk_rope, w.qk_nope, w.v_dim, w.lora);
6666 let hd = dr + dn;
6667 let mut q = vec![0.0f32; nh * hd];
6668 match (&w.q_a, &w.q_a_norm) {
6669 (Some(qa), Some(qn)) => {
6670 let mut t = vec![0.0f32; qa.rows()];
6671 qa.matvec(normed, &mut t, pool);
6672 let tn = inference::rms_norm(&t, qn, eps, NormStyle::Qwen);
6673 w.q_proj.matvec(&tn, &mut q, pool);
6674 }
6675 _ => w.q_proj.matvec(normed, &mut q, pool),
6676 }
6677 let mut ca = vec![0.0f32; lora + dr];
6678 w.kv_a.matvec(normed, &mut ca, pool);
6679 let (c_lat, k_rope) = ca.split_at_mut(lora);
6680 let latn = inference::rms_norm(c_lat, &w.kv_a_norm, eps, NormStyle::Qwen);
6681 let mut kvb = vec![0.0f32; nh * (dn + dv)];
6682 w.kv_b.matvec(&latn, &mut kvb, pool);
6683 if !w.nope {
6684 attention::rope_rotate_scaled(k_rope, position, inv_freq, rope_scale);
6685 }
6686 for h in 0..nh {
6687 if !w.nope {
6688 attention::rope_rotate_scaled(
6689 &mut q[h * hd..h * hd + dr],
6690 position,
6691 inv_freq,
6692 rope_scale,
6693 );
6694 }
6695 }
6696 let mut k = vec![0.0f32; nh * hd];
6697 let mut v = vec![0.0f32; nh * hd];
6698 for h in 0..nh {
6699 k[h * hd..h * hd + dr].copy_from_slice(k_rope);
6700 k[h * hd + dr..(h + 1) * hd].copy_from_slice(&kvb[h * (dn + dv)..h * (dn + dv) + dn]);
6701 v[h * hd..h * hd + dv].copy_from_slice(&kvb[h * (dn + dv) + dn..(h + 1) * (dn + dv)]);
6702 }
6703 cache.append(&k, &v, &vec![true; nh]);
6704 let (ao, mut imp) = attention::attend_all_heads(&q, cache, nh, 1, hd, w.scale, None, 0.0);
6705 attention::recycle_buf(&mut imp);
6706 let mut ov = vec![0.0f32; nh * dv];
6707 for h in 0..nh {
6708 ov[h * dv..(h + 1) * dv].copy_from_slice(&ao[h * hd..h * hd + dv]);
6709 }
6710 let mut out = vec![0.0f32; w.o_proj.rows()];
6711 w.o_proj.matvec(&ov, &mut out, pool);
6712 out
6713}
6714
6715fn dense_moe_ffn(
6722 dm: &DenseMoeFfn,
6723 x_normed: &[f32],
6724 h_raw: &[f32],
6725 eps: f64,
6726 norm_style: NormStyle,
6727 pool: Option<&Pool>,
6728) -> Vec<f32> {
6729 let mut d = dense_ffn(&dm.dense, x_normed, pool);
6730 d = inference::rms_norm(&d, &dm.post_norm_1, eps, norm_style);
6731 let m = &dm.moe;
6732 let ne = m.experts.len();
6733 let mut logits = vec![0.0f32; ne];
6734 if m.router_input_norm {
6735 let ss: f32 = h_raw.iter().map(|v| v * v).sum::<f32>() / h_raw.len() as f32;
6736 let inv = 1.0 / (ss + eps as f32).sqrt();
6737 let xr: Vec<f32> = h_raw.iter().map(|v| v * inv).collect();
6738 m.router.matvec(&xr, &mut logits, pool);
6739 } else {
6740 m.router.matvec(h_raw, &mut logits, pool);
6741 }
6742 let (idx, p, wsum) = moe_route(&logits, m, None);
6743 {
6744 let mut st = m.stats.borrow_mut();
6745 if st.len() < ne {
6746 st.resize(ne, 0);
6747 }
6748 for &e in &idx {
6749 st[e] += 1;
6750 }
6751 }
6752 let x2 = inference::rms_norm(h_raw, &dm.pre_norm_2, eps, norm_style);
6753 let mo = moe_ffn_cpu(m, &x2, &idx, &p, wsum, pool);
6754 let mo = inference::rms_norm(&mo, &dm.post_norm_2, eps, norm_style);
6755 for (di, mi) in d.iter_mut().zip(&mo) {
6756 *di += mi;
6757 }
6758 d
6759}
6760
6761fn moe_gpu_refused(why: &'static str) {
6768 use std::sync::atomic::{AtomicBool, Ordering};
6769 static SAID: AtomicBool = AtomicBool::new(false);
6770 if !SAID.swap(true, Ordering::Relaxed) {
6771 tracing::warn!("MoE GPU block refused ({why}) — experts run on the CPU");
6772 }
6773}
6774
6775fn moe_ffn_gpu(
6776 m: &MoeFfn,
6777 x: &[f32],
6778 idx: &[usize],
6779 p: &[f32],
6780 wsum: f32,
6781 pool: Option<&Pool>,
6782) -> Option<Vec<f32>> {
6783 use crate::gpu::MoeJob;
6784
6785 let mut jobs: Vec<MoeJob> = Vec::with_capacity(idx.len() + 1);
6786 let mut model_ref = None;
6787 for &e in idx {
6788 if moe_push_job(&m.experts[e], x, p[e] / wsum, &mut jobs, &mut model_ref).is_none() {
6789 moe_gpu_refused("push_job(expert)");
6790 return None;
6791 }
6792 }
6793 if let Some((se, gate)) = &m.shared {
6794 let g = gate.as_ref().map_or(1.0, |gate| {
6795 let mut gl = [0.0f32; 1];
6796 gate.matvec(x, &mut gl, pool);
6797 1.0 / (1.0 + (-gl[0]).exp())
6798 });
6799 if moe_push_job(se, x, g, &mut jobs, &mut model_ref).is_none() {
6800 moe_gpu_refused("push_job(shared)");
6801 return None;
6802 }
6803 }
6804 let Some(model) = model_ref else {
6805 moe_gpu_refused("no model_ref");
6806 return None;
6807 };
6808 let hidden = jobs[0].down.1;
6809 let mut out = vec![0.0f32; hidden];
6810 if crate::gpu::moe_block(&model, &jobs, &mut out) {
6811 Some(out)
6812 } else {
6813 moe_gpu_refused("gpu::moe_block");
6814 None
6815 }
6816}
6817
6818fn ffn_forward(
6820 ffn: &FfnKind,
6821 x: &[f32],
6822 pool: Option<&Pool>,
6823 experts_allowed: Option<&[bool]>,
6824) -> Vec<f32> {
6825 match ffn {
6826 FfnKind::Dense(d) => dense_ffn(d, x, pool),
6827 FfnKind::Moe(m) => moe_ffn(m, x, pool, experts_allowed),
6828 FfnKind::DenseMoe(_) => unreachable!("DenseMoe dispatches via dense_moe_ffn"),
6832 }
6833}
6834
6835fn ffn_forward_pair(
6839 ffn: &FfnKind,
6840 x1: &[f32],
6841 x2: &[f32],
6842 pool: Option<&Pool>,
6843 experts_allowed: Option<&[bool]>,
6844) -> (Vec<f32>, Vec<f32>) {
6845 let d = match ffn {
6846 FfnKind::Dense(d) => d,
6847 FfnKind::Moe(m) => {
6848 return (
6849 moe_ffn(m, x1, pool, experts_allowed),
6850 moe_ffn(m, x2, pool, experts_allowed),
6851 );
6852 }
6853 FfnKind::DenseMoe(_) => unreachable!("DenseMoe dispatches via dense_moe_ffn"),
6854 };
6855 let inter = d.gate_proj.rows();
6856 FFN_SCRATCH.with(|s| {
6857 let mut s = s.borrow_mut();
6858 let [g1, g2, u1, u2] = &mut *s;
6859 g1.resize(inter, 0.0);
6860 g2.resize(inter, 0.0);
6861 u1.resize(inter, 0.0);
6862 u2.resize(inter, 0.0);
6863 QTensor::matvec2_many(
6866 [&d.gate_proj, &d.up_proj],
6867 x1,
6868 x2,
6869 [g1.as_mut_slice(), u1.as_mut_slice()],
6870 [g2.as_mut_slice(), u2.as_mut_slice()],
6871 pool,
6872 );
6873 for i in 0..inter {
6874 g1[i] = d.act.combine(g1[i], u1[i]);
6875 g2[i] = d.act.combine(g2[i], u2[i]);
6876 }
6877 let mut o1 = attention::take_buf(d.down_proj.rows());
6878 let mut o2 = attention::take_buf(d.down_proj.rows());
6879 d.down_proj.matvec2(g1, g2, &mut o1, &mut o2, pool);
6880 (o1, o2)
6881 })
6882}
6883
6884#[cfg(test)]
6885mod tests {
6886
6887 #[test]
6888 fn cancel_flag_stops_generation() {
6889 let mut p = create_test_pipeline(16, 32, 2, 2, 8, 2, 32);
6890 p.cancel.store(true, std::sync::atomic::Ordering::Relaxed);
6893 let r = p.generate_from_ids(&[1, 2, 3], 8, None, None).unwrap();
6894 assert_eq!(r.finish_reason, "cancelled");
6895 assert!(
6896 r.token_ids.is_empty(),
6897 "no tokens after cancel: {:?}",
6898 r.token_ids
6899 );
6900 let r2 = p.generate_from_ids(&[1, 2, 3], 4, None, None).unwrap();
6902 assert_ne!(r2.finish_reason, "cancelled");
6903 }
6904 use super::*;
6905
6906 #[test]
6912 fn sparse_ffn_quant_equals_dense_with_inactive_zeroed() {
6913 let (hidden, inter) = (16usize, 40usize);
6914 let synth = |n: usize, salt: usize| -> Vec<f32> {
6915 (0..n)
6916 .map(|i| (((i * 37 + salt * 11 + 3) % 101) as f32 / 101.0 - 0.5) * 0.4)
6917 .collect()
6918 };
6919 let d = DenseFfn {
6920 gate_proj: QTensor::from_f32(synth(inter * hidden, 1), inter, hidden),
6921 up_proj: QTensor::from_f32(synth(inter * hidden, 2), inter, hidden),
6922 down_proj: QTensor::from_f32(synth(hidden * inter, 3), hidden, inter),
6923 act: Act::Silu,
6924 };
6925 let x = synth(hidden, 9);
6926 let active: Vec<u16> = (0..inter as u16).filter(|i| i % 3 == 0).collect();
6928
6929 let sparse = sparse_ffn_quant(&d, &x, &active, hidden, None);
6930
6931 let mut g = vec![0.0f32; inter];
6933 d.gate_proj.matvec(&x, &mut g, None);
6934 let mut u = vec![0.0f32; inter];
6935 d.up_proj.matvec(&x, &mut u, None);
6936 let act_set: std::collections::HashSet<u16> = active.iter().copied().collect();
6937 for i in 0..inter {
6938 g[i] = if act_set.contains(&(i as u16)) {
6939 inference::silu(g[i]) * u[i]
6940 } else {
6941 0.0
6942 };
6943 }
6944 let mut reference = vec![0.0f32; hidden];
6945 d.down_proj.matvec(&g, &mut reference, None);
6946
6947 let max_d = sparse
6948 .iter()
6949 .zip(&reference)
6950 .map(|(a, b)| (a - b).abs())
6951 .fold(0.0f32, f32::max);
6952 assert!(max_d < 1e-5, "sparse != dense-zeroed: max|Δ| = {max_d}");
6953 }
6954
6955 fn attach_test_mtp(p: &mut Pipeline) {
6957 let (h, inter, heads, kv, hd) = (
6958 p.hidden_size,
6959 p.intermediate_size,
6960 p.num_heads,
6961 p.num_kv_heads,
6962 p.head_dim,
6963 );
6964 let synth = |n: usize, salt: usize| -> Vec<f32> {
6965 (0..n)
6966 .map(|i| (((i * 29 + salt * 23 + 5) % 101) as f32 / 101.0 - 0.5) * 0.2)
6967 .collect()
6968 };
6969 let qt = |rows: usize, cols: usize, salt: usize| -> QTensor {
6970 QTensor::from_f32(synth(rows * cols, salt), rows, cols)
6971 };
6972 p.mtp = Some(MtpModule {
6973 enorm: vec![1.0; h],
6974 hnorm: vec![1.0; h],
6975 eh_proj: qt(h, 2 * h, 301),
6976 layer: LayerWeights {
6977 input_norm: vec![1.0; h],
6978 post_norm: vec![1.0; h],
6979 attn_out_norm: None,
6980 ffn_out_norm: None,
6981 layer_scale: None,
6982 ffn: FfnKind::Dense(DenseFfn {
6983 gate_proj: qt(inter, h, 315),
6984 up_proj: qt(inter, h, 316),
6985 down_proj: qt(h, inter, 317),
6986 act: Act::Silu,
6987 }),
6988 attn: AttnKind::Full {
6989 bias: None,
6990 wq: qt(heads * hd, h, 311),
6991 wk: qt(kv * hd, h, 312),
6992 wv: qt(kv * hd, h, 313),
6993 wo: qt(h, heads * hd, 314),
6994 q_norm: None,
6995 k_norm: None,
6996 output_gate: false,
6997 softplus_gate: None,
6998 },
6999 },
7000 final_norm: vec![1.0; h],
7001 kv: crate::kv_cache::LayerKvCache::new(kv, hd),
7002 });
7003 }
7004
7005 #[test]
7006 fn speculative_equals_vanilla_greedy() {
7007 unsafe { std::env::set_var("CMF_GPU_WGPU_GRAPH", "0") };
7011 let run = |spec: bool| {
7012 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 2, 260);
7013 p.sampler_config.temperature = 0.0;
7014 attach_test_mtp(&mut p);
7015 p.speculative = spec;
7016 let r = p.generate("abcdef", 12, None, None).unwrap();
7017 (r.token_ids, r.mtp_drafted, r.mtp_accepted)
7018 };
7019 let (vanilla, d0, _) = run(false);
7020 let (spec, d1, a1) = run(true);
7021 assert_eq!(d0, 0, "vanilla path must not draft");
7022 assert!(d1 > 0, "speculative path must draft");
7023 assert_eq!(
7024 vanilla, spec,
7025 "speculative must reproduce the exact greedy sequence (accepted {a1}/{d1})"
7026 );
7027 }
7028
7029 #[test]
7030 fn speculative_accepts_constant_oracle() {
7031 unsafe { std::env::set_var("CMF_GPU_WGPU_GRAPH", "0") };
7033 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 64);
7034 p.sampler_config.temperature = 0.0;
7035 p.sampler_config.repetition_penalty = 1.0;
7036 p.weights.lm_head = QTensor::from_f32(vec![0.01; 64 * 8], 64, 8);
7039 attach_test_mtp(&mut p);
7040 p.speculative = true;
7041 let r = p.generate("abcd", 10, None, None).unwrap();
7042 assert!(r.mtp_drafted > 0);
7043 assert_eq!(
7044 r.mtp_accepted, r.mtp_drafted,
7045 "constant logits → every draft accepted"
7046 );
7047 assert!(r.token_ids.windows(2).all(|w| w[0] == w[1]));
7050 }
7051
7052 #[test]
7053 fn empty_prompt_is_an_error_not_a_panic() {
7054 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 260);
7055 let r = p.generate("", 4, None, None);
7056 assert!(r.is_err(), "empty prompt must be a clean error");
7057 }
7058
7059 #[test]
7060 fn every_token_enters_kv_exactly_once() {
7061 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 2, 260);
7062 p.sampler_config.temperature = 0.0;
7064 let r = p.generate("abc", 2, None, None).unwrap();
7065 assert_eq!(r.prompt_tokens, 3);
7066 assert_eq!(
7070 p.kv_cache.seq_len(),
7071 3 + r.tokens_generated - 1,
7072 "each token must be cached exactly once (v1 cached the last prompt token twice)"
7073 );
7074 }
7075
7076 #[test]
7077 fn generation_is_reproducible_with_seed() {
7078 let run = || {
7079 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 2, 260);
7080 p.generate("hello", 8, None, None).unwrap().token_ids
7081 };
7082 assert_eq!(run(), run());
7083 }
7084
7085 #[test]
7086 fn resetting_sampler_restarts_the_seeded_stream() {
7087 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 2, 260);
7088 let config = SamplerConfig {
7089 seed: Some(1234),
7090 ..SamplerConfig::default()
7091 };
7092 p.set_sampler_config(config.clone());
7093 let first = p.generate("hello", 8, None, None).unwrap().token_ids;
7094 p.set_sampler_config(config);
7095 let second = p.generate("hello", 8, None, None).unwrap().token_ids;
7096 assert_eq!(first, second);
7097 }
7098
7099 #[test]
7100 fn eviction_bounds_the_cache() {
7101 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 260);
7102 p.kv_cache.max_seq_len = 6;
7103 p.sampler_config.temperature = 0.0;
7104 let _ = p.generate("abcd", 12, None, None).unwrap();
7105 assert!(
7106 p.kv_cache.seq_len() <= 6 + 1,
7107 "cache must stay bounded by max_seq_len (got {})",
7108 p.kv_cache.seq_len()
7109 );
7110 }
7111
7112 #[test]
7113 fn confidence_matches_tokens_and_is_a_probability() {
7114 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 64);
7115 p.sampler_config.temperature = 0.0;
7116 p.sampler_config.repetition_penalty = 1.0;
7117 let r = p.generate("abcd", 10, None, None).unwrap();
7118 assert_eq!(
7119 r.token_confidence.len(),
7120 r.token_ids.len(),
7121 "one confidence per emitted token"
7122 );
7123 for &c in &r.token_confidence {
7124 assert!((0.0..=1.0).contains(&c), "confidence out of [0,1]: {c}");
7125 }
7126 let logits = [1.0f32, 3.0, 0.5, 3.0];
7128 let p0 = top1_prob_t(&logits, 1, 1.0);
7129 let p1 = top1_prob_t(&logits, 3, 1.0);
7130 assert!((p0 - p1).abs() < 1e-6, "equal logits → equal prob");
7131 assert!(p0 > 0.0 && p0 < 1.0);
7132 let sharp = top1_prob_t(&logits, 1, 1.0);
7134 let soft = top1_prob_t(&logits, 1, 2.0);
7135 assert!(soft < sharp, "higher temperature lowers peak confidence");
7136 }
7137
7138 #[test]
7139 fn trace_is_opt_in_and_parallels_the_output() {
7140 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 64);
7142 p.sampler_config.temperature = 0.0;
7143 p.sampler_config.repetition_penalty = 1.0;
7144 let r = p.generate("abcd", 10, None, None).unwrap();
7145 assert!(r.traces.is_empty(), "trace must be empty unless enabled");
7146
7147 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 64);
7149 p.sampler_config.temperature = 0.0;
7150 p.sampler_config.repetition_penalty = 1.0;
7151 p.set_trace(true);
7152 let r = p.generate("abcd", 10, None, None).unwrap();
7153 assert_eq!(r.traces.len(), r.token_ids.len(), "one trace row per token");
7154 for (i, tr) in r.traces.iter().enumerate() {
7155 assert_eq!(tr.t, i, "trace index is sequential");
7156 assert_eq!(tr.token_id, r.token_ids[i], "trace token_id matches output");
7157 assert_eq!(
7158 tr.confidence, r.token_confidence[i],
7159 "trace confidence matches the confidence channel"
7160 );
7161 assert!(tr.active_skill.is_none() && tr.recon.is_none() && !tr.switched);
7163 }
7164 }
7165
7166 #[test]
7167 fn explain_prefill_logits_match_greedy_first_token() {
7168 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 64);
7172 p.sampler_config.temperature = 0.0;
7173 p.sampler_config.repetition_penalty = 1.0;
7174 let ids = p.tokenizer.encode("abcd");
7175 let logits = p.prefill_next_logits(&ids, None);
7176 let argmax = logits
7177 .iter()
7178 .enumerate()
7179 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
7180 .unwrap()
7181 .0 as u32;
7182 let r = p.generate("abcd", 1, None, None).unwrap();
7183 assert_eq!(
7184 argmax, r.token_ids[0],
7185 "explain preview must match greedy emit"
7186 );
7187 }
7188
7189 #[test]
7190 fn laguna_shared_expert_is_unconditionally_added() {
7191 let matrix = |values: Vec<f32>| QTensor::from_f32(values, 2, 2);
7192 let identity = || matrix(vec![1.0, 0.0, 0.0, 1.0]);
7193 let zero_dense = || DenseFfn {
7194 gate_proj: matrix(vec![0.0; 4]),
7195 up_proj: matrix(vec![0.0; 4]),
7196 down_proj: matrix(vec![0.0; 4]),
7197 act: Act::Silu,
7198 };
7199 let shared = DenseFfn {
7200 gate_proj: identity(),
7201 up_proj: identity(),
7202 down_proj: identity(),
7203 act: Act::Silu,
7204 };
7205 let x = [1.0, 2.0];
7206 let expected = dense_ffn(&shared, &x, None);
7207 let moe = MoeFfn {
7208 router: QTensor::from_f32(vec![0.0, 0.0], 1, 2),
7209 experts: vec![zero_dense()],
7210 top_k: 1,
7211 norm_topk_prob: true,
7212 router_sigmoid: true,
7213 expert_bias: None,
7214 routed_scaling: 1.0,
7215 route_tau: None,
7216 shared: Some((shared, None)),
7217 stats: std::cell::RefCell::new(Vec::new()),
7218 act_sq: std::cell::RefCell::new(Vec::new()),
7219 act_rows: std::cell::RefCell::new(Vec::new()),
7220 mask: None,
7221 per_expert_scale: None,
7222 router_input_norm: false,
7223 };
7224 let actual = moe_ffn_cpu(&moe, &x, &[0], &[0.0], 1.0, None);
7225 for (actual, expected) in actual.iter().zip(expected) {
7226 assert!((actual - expected).abs() < 1e-6);
7227 }
7228 }
7229}