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 gpu_plan: Option<std::sync::Arc<Vec<(usize, usize, usize)>>>,
55 pub tokenizer: std::sync::Arc<Tokenizer>,
58 pub kv_cache: KvCache,
59 pub sampler_config: SamplerConfig,
60 pub weights: PipelineWeights,
61 pub hidden_size: usize,
62 pub intermediate_size: usize,
63 pub num_heads: usize,
64 pub num_kv_heads: usize,
65 pub head_dim: usize,
66 pub num_layers: usize,
68 pub physical_layers: usize,
70 pub loop_final_norm: bool,
72 pub vocab_size: usize,
73 pub rms_eps: f64,
74 pub rope_base: f32,
75 pub norm_style: NormStyle,
76 pub rotary_dim: usize,
78 pub attention_heads_per_layer: Option<Vec<usize>>,
80 pub vmf_cfg: Option<VmfPhaseCfg>,
82 pub gdn_cfg: Option<GdnCfg>,
84 pub logit_multiplier: Option<f32>,
86 pub cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
91 pub kv_history: Vec<u32>,
96 pub kda_cfg: Option<crate::linear_core::KdaCfg>,
98 pub g3n: Option<Box<(crate::g3n::G3nGlobals, Vec<crate::g3n::G3nLayer>)>>,
101 pub dsv4: Option<
105 Box<(
106 crate::dsv4::Dsv4Globals,
107 Vec<crate::dsv4::Dsv4Layer>,
108 crate::dsv4::Dsv4Cfg,
109 crate::dsv4::Dsv4State,
110 )>,
111 >,
112 pub qwen4_exp: Option<
115 Box<(
116 crate::qwen4_exp::Globals,
117 Vec<crate::qwen4_exp::Layer>,
118 crate::qwen4_exp::Cfg,
119 crate::qwen4_exp::State,
120 )>,
121 >,
122 pub dsv4_mtp: Vec<crate::dsv4::Dsv4Mtp>,
126 pub dspark: Option<crate::dsv4::DsparkState>,
128 pub dspark_pending: Vec<(usize, Vec<u32>, bool, usize)>,
131 pub dspark_hist: Vec<usize>,
133 pub dspark_real: Vec<u32>,
137 pub dspark_trunk_picks: Vec<Vec<(usize, Vec<usize>)>>,
141 pub dspark_exp: Vec<(usize, usize, usize, usize)>,
143 pub dspark_draft_ns: u128,
147 pub short_conv_cfg: Option<ShortConvCfg>,
150 pub mtp: Option<MtpModule>,
152 pub speculative: bool,
154 rng: SplitMix64,
155 sampler_scratch: SamplerScratch,
156 spec_forced: Option<u32>,
162 spec_q: Vec<Vec<f32>>,
163 spec_p: Vec<f32>,
164 spec_res: Vec<f32>,
165 spec_qs: Vec<sampler::Sparse>,
167 spec_ps: sampler::Sparse,
168 spec_ress: sampler::Sparse,
169 mtp_graph_mode: Option<bool>,
176 #[cfg(target_os = "macos")]
179 metal_verify: Option<MetalVerifyPending>,
180 pub(crate) inv_freq: std::sync::Arc<Vec<f32>>,
184 ws: ForwardScratch,
188 pool: Option<std::sync::Arc<Pool>>,
190 pub(crate) model: Option<std::sync::Arc<cortiq_core::CmfModel>>,
194 pub(crate) dyn_force_f32: bool,
196 pub(crate) dyn_skill_layers: Vec<Option<Vec<usize>>>,
201 pub(crate) dyn_active: Option<usize>,
207 pub(crate) dyn_blend_loaded: bool,
211 pub(crate) dyn_phi_layer: Option<usize>,
214 dyn_phi_ema: Vec<f32>,
216 dyn_phi_seen: usize,
217 pub dyn_router: Option<crate::swarm::DynRouter>,
220 o1_cfg: Option<crate::nystrom::O1Cfg>,
223 o1_epoch: u64,
226 o1_flags: Vec<bool>,
228 trace: bool,
231 calib_temp: f32,
234 #[cfg_attr(not(target_os = "macos"), allow(dead_code))]
236 graph_kv_id: u64,
237 #[cfg_attr(not(target_os = "macos"), allow(dead_code))]
240 graph_want_logits: bool,
241 graph_logits: Option<Vec<f32>>,
244 pub embed_multiplier: f32,
246 pub attn_scale: f32,
249 pub swa: Option<(usize, usize)>,
252 pub sliding_layers: Option<Vec<bool>>,
255 pub inv_freq_local: Option<std::sync::Arc<Vec<f32>>>,
258 pub rotary_dim_local: Option<usize>,
259 pub rope_scale: f32,
260 pub rope_scale_local: f32,
261 pub global_attn: Option<(usize, usize)>,
264 pub inv_freq_global: Option<std::sync::Arc<Vec<f32>>>,
267 pub attn_v_norm: bool,
269 pub final_softcap: Option<f32>,
271 pub head_clusters: Option<std::sync::Arc<Vec<f32>>>,
275 pub attn_softcap: f32,
277 confidence_on: bool,
281}
282
283#[cfg(target_os = "macos")]
284impl Drop for Pipeline {
285 fn drop(&mut self) {
286 crate::gpu::kv_mirror_drop(self.graph_kv_id);
287 }
288}
289
290pub struct PipelineWeights {
295 pub embed_tokens: QTensor,
297 pub layers: Vec<LayerWeights>,
299 pub lm_head: QTensor,
301 pub final_norm: Vec<f32>,
303}
304
305pub struct LayerWeights {
307 pub input_norm: Vec<f32>,
308 pub post_norm: Vec<f32>,
311 pub attn_out_norm: Option<Vec<f32>>,
314 pub layer_scale: Option<f32>,
316 pub ffn_out_norm: Option<Vec<f32>>,
319 pub ffn: FfnKind,
320 pub attn: AttnKind,
321}
322
323#[derive(Clone, Copy, PartialEq, Debug, Default)]
326pub enum Act {
327 #[default]
328 Silu,
329 GeluTanh,
330 Situ {
333 beta: f32,
334 linear_beta: f32,
335 },
336}
337
338impl Act {
339 pub fn from_arch(name: &str) -> Self {
340 if name == "gelu_tanh" {
341 Self::GeluTanh
342 } else {
343 Self::Silu
344 }
345 }
346
347 pub fn from_arch_full(arch: &cortiq_core::ModelArch) -> Self {
349 match arch.hidden_act.as_str() {
350 "situ" => Self::Situ {
351 beta: arch.activation_situ_beta.unwrap_or(1.0) as f32,
352 linear_beta: arch.activation_situ_linear_beta.unwrap_or(0.0) as f32,
353 },
354 other => Self::from_arch(other),
355 }
356 }
357
358 #[inline]
359 pub fn apply(self, x: f32) -> f32 {
360 match self {
361 Self::Silu => inference::silu(x),
362 Self::GeluTanh => inference::gelu_tanh(x),
363 Self::Situ { beta, .. } => beta * (x / beta).tanh() * (1.0 / (1.0 + (-x).exp())),
364 }
365 }
366
367 #[inline]
370 pub fn combine(self, g: f32, u: f32) -> f32 {
371 match self {
372 Self::Situ { linear_beta, .. } if linear_beta > 0.0 => {
373 self.apply(g) * (linear_beta * (u / linear_beta).tanh())
374 }
375 _ => self.apply(g) * u,
376 }
377 }
378}
379
380pub struct DenseFfn {
382 pub gate_proj: QTensor,
383 pub up_proj: QTensor,
384 pub down_proj: QTensor,
385 pub act: Act,
387 pub down_t: Option<QTensor>,
393 pub segs: Vec<FfnSeg>,
400}
401
402pub struct FfnSeg {
407 pub gate: QTensor,
408 pub up: QTensor,
409 pub down: QTensor,
410 pub start: usize,
411 pub width: usize,
412}
413
414pub enum FfnKind {
417 Dense(DenseFfn),
418 Moe(MoeFfn),
422 DenseMoe(Box<DenseMoeFfn>),
429}
430
431pub struct DenseMoeFfn {
433 pub dense: DenseFfn,
434 pub moe: MoeFfn,
435 pub post_norm_1: Vec<f32>,
437 pub pre_norm_2: Vec<f32>,
440 pub post_norm_2: Vec<f32>,
442}
443
444pub struct MoeFfn {
445 pub router: QTensor,
447 pub experts: Vec<DenseFfn>,
448 pub top_k: usize,
449 pub norm_topk_prob: bool,
450 pub router_sigmoid: bool,
453 pub expert_bias: Option<Vec<f32>>,
457 pub routed_scaling: f32,
460 pub route_tau: Option<f32>,
466 pub shared: Option<(DenseFfn, Option<QTensor>)>,
469 pub stats: std::cell::RefCell<Vec<u64>>,
473 pub act_sq: std::cell::RefCell<Vec<f64>>,
480 pub act_rows: std::cell::RefCell<Vec<f32>>,
486 pub mask: Option<Vec<bool>>,
491 pub per_expert_scale: Option<Vec<f32>>,
494 pub router_input_norm: bool,
498 pub resonance: Option<Resonance>,
502}
503
504pub struct Resonance {
506 pub mu: Vec<f32>,
508 pub u: Vec<f32>,
510 pub k: usize,
511 pub bias: Vec<f32>,
513}
514
515impl Resonance {
516 pub fn scores(&self, x: &[f32], out: &mut [f32]) {
518 let h = x.len();
519 let ne = out.len();
520 for e in 0..ne {
521 let mu = &self.mu[e * h..(e + 1) * h];
522 let mut d2 = 0.0f32;
523 for j in 0..h {
524 let d = x[j] - mu[j];
525 d2 += d * d;
526 }
527 let mut proj = 0.0f32;
528 for i in 0..self.k {
529 let u = &self.u[(e * self.k + i) * h..(e * self.k + i + 1) * h];
530 let mut p = 0.0f32;
531 for j in 0..h {
532 p += (x[j] - mu[j]) * u[j];
533 }
534 proj += p * p;
535 }
536 out[e] = self.bias.get(e).copied().unwrap_or(0.0) - (d2 - proj);
537 }
538 }
539}
540
541pub enum AttnKind {
544 Full {
546 wq: QTensor,
547 wk: QTensor,
548 wv: QTensor,
549 wo: QTensor,
550 q_norm: Option<Vec<f32>>,
551 k_norm: Option<Vec<f32>>,
552 output_gate: bool,
553 softplus_gate: Option<(QTensor, bool)>,
557 bias: Option<(Vec<f32>, Vec<f32>, Vec<f32>)>,
559 },
560 Linear(VmfPhaseWeights),
562 LinearGdn(GdnWeights),
564 ShortConv(ShortConvWeights),
567 Mla(Box<MlaWeights>),
575 Kda(Box<crate::linear_core::KdaWeights>),
579}
580
581pub struct MlaWeights {
583 pub q_proj: QTensor,
587 pub q_a: Option<QTensor>,
590 pub q_a_norm: Option<Vec<f32>>,
591 pub kv_a: QTensor,
593 pub kv_a_norm: Vec<f32>,
595 pub kv_b: QTensor,
597 pub o_proj: QTensor,
599 pub nh: usize,
600 pub qk_rope: usize,
601 pub qk_nope: usize,
602 pub v_dim: usize,
603 pub lora: usize,
604 pub scale: f32,
606 pub nope: bool,
608}
609
610pub struct MtpModule {
615 pub enorm: Vec<f32>,
616 pub hnorm: Vec<f32>,
617 pub eh_proj: QTensor,
619 pub layer: LayerWeights,
620 pub final_norm: Vec<f32>,
621 pub kv: crate::kv_cache::LayerKvCache,
622}
623
624#[cfg(target_os = "macos")]
631enum MetalRowsItem<'a> {
632 Gdn {
633 run: Vec<crate::gpu_metal::GdnGpuLayer<'a>>,
634 first: usize,
635 },
636 Attn {
637 l: crate::gpu_metal::AttnGpuLayer<'a>,
638 li: usize,
639 q_norm: Option<&'a [f32]>,
640 k_norm: Option<&'a [f32]>,
641 output_gate: bool,
642 },
643}
644
645#[cfg(target_os = "macos")]
646struct MetalVerifyPending {
647 graph: crate::gpu_metal::VerifyGraph,
648 gdn_layers: Vec<usize>,
649 attn_layers: Vec<(usize, usize)>,
650}
651
652#[derive(Clone, Copy)]
656enum SpecTrial {
657 Spec {
658 t0: std::time::Instant,
659 gen0: usize,
660 rounds: usize,
661 },
662 Plain {
663 t0: std::time::Instant,
664 gen0: usize,
665 },
666 Decided {
667 spec: bool,
668 recheck_at: usize,
669 },
670}
671
672#[derive(Default, Clone, Copy)]
683struct SpecMon {
684 round_ms: f64,
685 tokens: f64,
686 plain_ms: f64,
687 n: u32,
688 fails: u32,
689}
690
691impl SpecMon {
692 fn round(&mut self, dt_ms: f64, produced: usize) {
693 self.n += 1;
694 if self.n == 1 {
695 return; }
697 let a = if self.n == 2 { 1.0 } else { 0.3 };
698 self.round_ms += a * (dt_ms - self.round_ms);
699 self.tokens += a * (produced as f64 - self.tokens);
700 }
701 fn pays(&self) -> bool {
702 self.plain_ms > 0.0 && self.tokens * self.plain_ms > self.round_ms * 1.03
703 }
704}
705
706pub struct GenerateResult {
708 pub text: String,
709 pub token_ids: Vec<u32>,
710 pub prompt_tokens: usize,
711 pub tokens_generated: usize,
712 pub finish_reason: String,
713 pub mtp_drafted: usize,
715 pub mtp_accepted: usize,
716 pub token_confidence: Vec<f32>,
721 pub traces: Vec<TokenTrace>,
724}
725
726#[derive(Clone, Debug)]
731pub struct TokenTrace {
732 pub t: usize,
734 pub token_id: u32,
736 pub confidence: f32,
738 pub active_skill: Option<String>,
740 pub recon: Option<f32>,
744 pub switched: bool,
747}
748
749#[cfg_attr(not(test), allow(dead_code))]
754fn top1_prob_t(logits: &[f32], id: u32, temp: f32) -> f32 {
755 let t = if temp > 1e-3 { temp } else { 1.0 };
756 let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
757 let sum: f32 = logits.iter().map(|&v| ((v - max) / t).exp()).sum();
758 if sum > 0.0 {
759 (((logits[id as usize] - max) / t).exp()) / sum
760 } else {
761 0.0
762 }
763}
764
765fn prefill_batched() -> bool {
768 std::env::var("CMF_PREFILL")
769 .map(|v| v != "seq")
770 .unwrap_or(true)
771}
772
773#[derive(Clone, Copy)]
777enum PrefillIn<'a> {
778 Ids(&'a [u32]),
779 Hidden(&'a [f32]),
780}
781
782impl Pipeline {
789 fn can_prefill_batched(&self) -> bool {
790 prefill_batched() && !self.weights.layers.is_empty()
791 }
792
793 fn automatic_gpu_prefix(&self) -> Option<usize> {
796 let (model, _, _, _) = self.weights.embed_tokens.graph_weight()?;
797 crate::gpu::automatic_layer_prefix(&model, self.num_layers, self.physical_layers)
798 }
799}
800
801pub fn prefill_chunk() -> usize {
808 if let Some(n) = std::env::var("CMF_PREFILL_CHUNK")
809 .ok()
810 .and_then(|v| v.parse::<usize>().ok())
811 {
812 return n.max(1);
813 }
814 if cfg!(target_os = "macos") {
815 512
816 } else if cfg!(target_arch = "aarch64") {
817 256
820 } else {
821 48
822 }
823}
824
825pub type TokenCallback = Box<dyn FnMut(&str) -> bool + Send>;
827
828impl Pipeline {
829 #[inline]
833 pub fn phys_layer(&self, virtual_idx: usize) -> usize {
834 virtual_idx % self.physical_layers
835 }
836
837 #[inline]
840 pub fn is_loop_end(&self, virtual_idx: usize) -> bool {
841 self.loop_final_norm && (virtual_idx + 1) % self.physical_layers == 0
842 }
843
844 #[allow(clippy::too_many_arguments)]
846
847 #[cfg(target_os = "macos")]
866 fn graph_prefill_preferred(&self) -> bool {
867 if !crate::gpu::enabled_here()
868 || !crate::gpu::q1_force()
869 || std::env::var("CMF_GPU_BLOCK")
870 .map(|v| v == "0")
871 .unwrap_or(false)
872 || std::env::var("CMF_PREFILL_GRAPH").as_deref() == Ok("0")
875 {
876 return false;
877 }
878 self.weights
879 .layers
880 .iter()
881 .any(|lw| matches!(&lw.attn, AttnKind::LinearGdn(w) if w.in_proj_qkv.is_q1()))
882 }
883
884 #[cfg(not(target_os = "macos"))]
885 fn graph_prefill_preferred(&self) -> bool {
886 let graph_on = crate::gpu::wgpu_graph_on(crate::gpu::GraphPhase::Prefill);
894 if !graph_on || !crate::gpu::enabled_here() {
895 return false;
896 }
897 if self.o1_active() {
906 return false;
907 }
908 self.weights
909 .layers
910 .iter()
911 .any(|lw| matches!(&lw.attn, AttnKind::LinearGdn(_)))
912 }
913
914 #[cfg(target_os = "macos")]
915 fn q1_graph_gpu(
916 &mut self,
917 start: usize,
918 upto: Option<usize>,
919 position: usize,
920 h: &mut [f32],
921 ) -> usize {
922 let _mt0 = std::time::Instant::now(); use crate::gpu::{AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GraphDims, MetalFfn, TokenGraph};
924 if self.attn_softcap > 0.0 || !crate::gpu::enabled_here()
926 || !crate::gpu::q1_force()
927 || std::env::var("CMF_GPU_BLOCK")
928 .map(|v| v == "0")
929 .unwrap_or(false)
930 {
931 if std::env::var("CMF_GRAPH_DBG").is_ok() {
932 eprintln!(
933 "block-graph: front gate (softcap={} enabled_here={} q1_force={})",
934 self.attn_softcap > 0.0,
935 crate::gpu::enabled_here(),
936 crate::gpu::q1_force(),
937 );
938 }
939 return start;
940 }
941 if self.swa.is_some()
945 || self.global_attn.is_some()
946 || self.attention_heads_per_layer.is_some()
947 || self.attn_v_norm
948 || self.weights.layers.iter().any(|lw| {
949 lw.attn_out_norm.is_some()
950 || lw.ffn_out_norm.is_some()
951 || lw.layer_scale.is_some()
952 || matches!(&lw.ffn, FfnKind::Dense(d) if d.act != Act::Silu)
953 })
954 {
955 if std::env::var("CMF_GRAPH_DBG").is_ok() {
956 eprintln!(
957 "block-graph: arch ineligible (swa={} gattn={} hpl={} vnorm={} scale_delta={:.2e})",
958 self.swa.is_some(),
959 self.global_attn.is_some(),
960 self.attention_heads_per_layer.is_some(),
961 self.attn_v_norm,
962 (self.attn_scale - 1.0 / (self.head_dim as f32).sqrt()).abs(),
963 );
964 }
965 return start;
966 }
967 let limit = upto
970 .map(|u| u + 1)
971 .unwrap_or(self.num_layers)
972 .min(self.num_layers);
973
974 enum Item<'a> {
975 Gdn {
976 run: Vec<GdnGpuLayer<'a>>,
977 first: usize,
978 },
979 Attn {
980 l: AttnGpuLayer<'a>,
981 li: usize,
982 q_norm: Option<&'a [f32]>,
983 k_norm: Option<&'a [f32]>,
984 output_gate: bool,
985 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
986 full_gpu: bool,
989 },
990 }
991
992 let attend_mode = std::env::var("CMF_GPU_ATTEND").unwrap_or_else(|_| "auto".into());
999 let attend_contract = attend_mode != "0"
1000 && attend_mode != "off"
1001 && self.head_dim % 4 == 0
1002 && self.head_dim <= 256
1003 && self.rotary_dim >= 2
1004 && self.rotary_dim <= self.head_dim
1005 && (self.rotary_dim / 2) % 32 == 0
1006 && self.num_kv_heads > 0
1007 && self.num_heads % self.num_kv_heads == 0;
1008
1009 let mut plan: Vec<Item> = Vec::new();
1010 let mut model_ref: Option<std::sync::Arc<cortiq_core::CmfModel>> = None;
1011 let block_diag = std::env::var("CMF_GRAPH_DBG").is_ok();
1013 let mut scan = start;
1014 while scan < limit {
1015 let lw = &self.weights.layers[self.phys_layer(scan)];
1016 let ffn = match &lw.ffn {
1017 FfnKind::Dense(d) if d.segs.is_empty() => {
1018 let (Some(g), Some(u), Some(dn)) = (
1019 d.gate_proj.q1_parts(),
1020 d.up_proj.q1_parts(),
1021 d.down_proj.q1_parts(),
1022 ) else {
1023 if block_diag {
1024 eprintln!(
1025 "block-graph: L{scan} FFN trio not graph-mappable — run ends"
1026 );
1027 }
1028 break;
1029 };
1030 MetalFfn::Dense {
1031 gate: g,
1032 up: u,
1033 down: dn,
1034 }
1035 }
1036 FfnKind::Moe(m) => {
1037 let Some(moe) = metal_moe_graph_parts(m, self.hidden_size) else {
1038 if block_diag {
1039 eprintln!(
1040 "block-graph: L{scan} MoE outside the graph contract — run ends"
1041 );
1042 }
1043 break;
1044 };
1045 if let QTensor::Mapped { model, .. } = &m.experts[0].gate_proj {
1046 model_ref.get_or_insert_with(|| model.clone());
1047 }
1048 MetalFfn::Moe(moe)
1049 }
1050 _ => {
1051 if block_diag {
1052 eprintln!("block-graph: L{scan} non-graph FFN — run ends");
1053 }
1054 break;
1055 }
1056 };
1057 match &lw.attn {
1058 AttnKind::LinearGdn(w) if self.gdn_cfg.is_some() => {
1059 let parts = (
1060 w.in_proj_qkv.q1_parts(),
1061 w.in_proj_z.q1_parts(),
1062 w.in_proj_a.f32_parts(),
1063 w.in_proj_b.f32_parts(),
1064 w.out_proj.q1_parts(),
1065 );
1066 let (Some(qkv), Some(z), Some(a), Some(b), Some(out)) = parts else {
1067 if block_diag {
1068 eprintln!(
1069 "block-graph: L{scan} GDN parts refused (qkv={} z={} a_f32={} b_f32={} out={})",
1070 w.in_proj_qkv.q1_parts().is_some(),
1071 w.in_proj_z.q1_parts().is_some(),
1072 w.in_proj_a.f32_parts().is_some(),
1073 w.in_proj_b.f32_parts().is_some(),
1074 w.out_proj.q1_parts().is_some(),
1075 );
1076 }
1077 break;
1078 };
1079 if let QTensor::Mapped { model, .. } = &w.in_proj_qkv {
1080 model_ref.get_or_insert_with(|| model.clone());
1081 }
1082 let gl = GdnGpuLayer {
1083 attn_norm: &lw.input_norm,
1084 post_norm: &lw.post_norm,
1085 qkv,
1086 z,
1087 a,
1088 b,
1089 out,
1090 ffn,
1091 conv1d: &w.conv1d,
1092 a_log: &w.a_log,
1093 dt_bias: &w.dt_bias,
1094 gnorm: &w.norm,
1095 };
1096 match plan.last_mut() {
1097 Some(Item::Gdn { run, .. }) => run.push(gl),
1098 _ => plan.push(Item::Gdn {
1099 run: vec![gl],
1100 first: scan,
1101 }),
1102 }
1103 }
1104 AttnKind::Full {
1105 wq,
1106 wk,
1107 wv,
1108 wo,
1109 q_norm,
1110 k_norm,
1111 output_gate,
1112 softplus_gate: None,
1113 bias,
1114 } if !self.kv_cache.layers[scan].o1_sealed()
1115 || std::env::var("CMF_O1_METAL").as_deref() == Ok("1") =>
1120 {
1121 let parts = (wq.q1_parts(), wk.q1_parts(), wv.q1_parts(), wo.q1_parts());
1122 let (Some(pq), Some(pk), Some(pv), Some(po)) = parts else {
1123 break;
1124 };
1125 if let QTensor::Mapped { model, .. } = wq {
1126 model_ref.get_or_insert_with(|| model.clone());
1127 }
1128 let cache = &self.kv_cache.layers[scan];
1129 let o1_metal = cache.o1.is_some()
1133 && std::env::var("CMF_O1_METAL").as_deref() == Ok("1")
1134 && cache.o1_views().is_some();
1135 let full_gpu = attend_contract
1136 && cache.mode == crate::kv_cache::KvMode::F32
1137 && (cache.o1.is_none() || o1_metal)
1138 && bias.is_none()
1139 && pq.1 == self.num_heads * self.head_dim * (1 + *output_gate as usize)
1140 && pk.1 == self.num_kv_heads * self.head_dim
1141 && pv.1 == self.num_kv_heads * self.head_dim
1142 && po.2 == self.num_heads * self.head_dim;
1143 plan.push(Item::Attn {
1144 l: AttnGpuLayer {
1145 attn_norm: &lw.input_norm,
1146 post_norm: &lw.post_norm,
1147 wq: pq,
1148 wk: pk,
1149 wv: pv,
1150 wo: po,
1151 ffn,
1152 },
1153 li: scan,
1154 q_norm: q_norm.as_deref(),
1155 k_norm: k_norm.as_deref(),
1156 output_gate: *output_gate,
1157 bias: bias
1158 .as_ref()
1159 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
1160 full_gpu,
1161 });
1162 }
1163 _ => break,
1164 }
1165 scan += 1;
1166 }
1167 let Some(model) = model_ref else {
1168 if std::env::var("CMF_GRAPH_DBG").is_ok() {
1169 eprintln!("q1-graph: no model ref (start {start}, scanned to {scan})");
1170 }
1171 return start;
1172 };
1173 if plan.is_empty() {
1174 if std::env::var("CMF_GRAPH_DBG").is_ok() {
1175 eprintln!("q1-graph: empty plan at layer {start}");
1176 }
1177 return start;
1178 }
1179 let has_moe = plan.iter().any(|it| match it {
1180 Item::Gdn { run, .. } => run.iter().any(|l| matches!(l.ffn, MetalFfn::Moe(_))),
1181 Item::Attn { l, .. } => matches!(l.ffn, MetalFfn::Moe(_)),
1182 });
1183 let has_gdn = plan.iter().any(|it| matches!(it, Item::Gdn { .. }));
1184 let dev_attend = attend_contract
1185 && (self.head_dim <= 128
1186 || has_moe
1187 || (self.head_dim <= 256 && has_gdn)
1193 || attend_mode == "force"
1194 || attend_mode == "256");
1195 if !dev_attend {
1196 for it in &mut plan {
1197 if let Item::Attn { li, full_gpu, .. } = it {
1198 let keep_o1 = self.kv_cache.layers[*li].o1.is_some()
1201 && std::env::var("CMF_O1_METAL").as_deref() == Ok("1");
1202 if !keep_o1 {
1203 *full_gpu = false;
1204 }
1205 }
1206 }
1207 }
1208 if std::env::var("CMF_GRAPH_DBG").is_ok() {
1209 use std::sync::atomic::{AtomicBool, Ordering};
1210 static SAID: AtomicBool = AtomicBool::new(false);
1211 if !SAID.swap(true, Ordering::Relaxed) {
1212 let fg = plan
1213 .iter()
1214 .filter(|it| matches!(it, Item::Attn { full_gpu: true, .. }))
1215 .count();
1216 let att = plan
1217 .iter()
1218 .filter(|it| matches!(it, Item::Attn { .. }))
1219 .count();
1220 eprintln!(
1221 "q1-graph: plan of {} items from layer {start} to {scan} | dev_attend={dev_attend} full_gpu {fg}/{att} | hd={} rd={} nkv={} nh={}",
1222 plan.len(),
1223 self.head_dim,
1224 self.rotary_dim,
1225 self.num_kv_heads,
1226 self.num_heads,
1227 );
1228 }
1229 }
1230 let dims = GraphDims {
1231 hidden: self.hidden_size,
1232 eps: self.rms_eps as f32,
1233 gemma: self.norm_style == cortiq_core::NormStyle::Gemma,
1234 };
1235 let Some(mut graph) = TokenGraph::new(&model, dims, h) else {
1236 return start;
1237 };
1238 let gcfg = self.gdn_cfg.map(|cfg| GdnGpuCfg {
1239 nv: cfg.num_v_heads,
1240 nk: cfg.num_k_heads,
1241 dk: cfg.key_head_dim,
1242 dv: cfg.value_head_dim,
1243 kk: cfg.conv_kernel,
1244 hidden: self.hidden_size,
1245 inter: self.intermediate_size,
1246 c_dim: cfg.conv_dim(),
1247 eps: cfg.rms_eps as f32,
1248 gemma: self.norm_style == cortiq_core::NormStyle::Gemma,
1249 });
1250 let mut valid = 0usize;
1254 let mut end = start;
1255 crate::gpu::stageprof(1, _mt0.elapsed()); if std::env::var("CMF_PLAN_DUMP").is_ok() {
1257 static ONCE: std::sync::Once = std::sync::Once::new();
1258 ONCE.call_once(|| {
1259 for it in &plan {
1260 match it {
1261 Item::Gdn { first, run } => {
1262 eprintln!("plan: Gdn first={first} len={}", run.len())
1263 }
1264 Item::Attn { li, full_gpu, .. } => {
1265 eprintln!("plan: Attn li={li} full_gpu={full_gpu}")
1266 }
1267 }
1268 }
1269 });
1270 }
1271 for item in &plan {
1272 let ok = match item {
1273 Item::Gdn { run, .. } => gcfg
1274 .as_ref()
1275 .map(|gc| run.iter().all(|l| graph.gdn_ok(l, gc)))
1276 .unwrap_or(false),
1277 Item::Attn { l, .. } => graph.attn_ok(l),
1278 };
1279 if !ok {
1280 if block_diag {
1281 eprintln!(
1282 "block-graph: plan item {} ({}) failed graph preflight",
1283 valid,
1284 match item {
1285 Item::Gdn { run, first } => format!("GDN run L{first}+{}", run.len()),
1286 Item::Attn { li, .. } => format!("Attn L{li}"),
1287 }
1288 );
1289 }
1290 break;
1291 }
1292 valid += 1;
1293 end += match item {
1294 Item::Gdn { run, .. } => run.len(),
1295 Item::Attn { .. } => 1,
1296 };
1297 }
1298 plan.truncate(valid);
1299 if plan.is_empty() {
1300 return start;
1301 }
1302
1303 let inv_freq = self.inv_freq.clone();
1304 let pool = self.pool.clone();
1305 let (nh, nkv, hd, hs, rd, eps) = (
1306 self.num_heads,
1307 self.num_kv_heads,
1308 self.head_dim,
1309 self.hidden_size,
1310 self.rotary_dim,
1311 self.rms_eps,
1312 );
1313 let norm_style = self.norm_style;
1314 let gemma = norm_style == cortiq_core::NormStyle::Gemma;
1315 let want = self.gdn_cfg.map(|c| c.state_len()).unwrap_or(0);
1316 let kv_id = self.graph_kv_id;
1317 let mut pending: Vec<(usize, usize)> = Vec::new();
1320 let mut dev_attn: Vec<usize> = Vec::new();
1323 for item in &plan {
1324 let _xt0 = std::time::Instant::now();
1325 let _xkind: u32 = match item {
1326 Item::Gdn { .. } => 2,
1327 Item::Attn { .. } => 3,
1328 };
1329 if self.loop_final_norm {
1331 let item_start = match item {
1332 Item::Gdn { first, .. } => *first,
1333 Item::Attn { li, .. } => *li,
1334 };
1335 if item_start > start && self.is_loop_end(item_start - 1) {
1336 graph.encode_loop_norm(&self.weights.final_norm);
1337 }
1338 }
1339 match item {
1340 Item::Gdn { run, first } => {
1341 for l in &mut self.kv_cache.layers[*first..*first + run.len()] {
1342 if l.linear_state.len() != want {
1343 l.linear_state = vec![0f32; want];
1344 }
1345 }
1346 let ro: Vec<&[f32]> = self.kv_cache.layers[*first..*first + run.len()]
1347 .iter()
1348 .map(|l| l.linear_state.as_slice())
1349 .collect();
1350 let _ig = std::time::Instant::now();
1351 if !graph.encode_gdn_run(run, &ro, gcfg.as_ref().unwrap()) {
1352 tracing::error!("q1 graph: GDN run refused after validation");
1354 return start;
1355 }
1356 graph.commit_kind = 2;
1359 graph.commit();
1360 crate::gpu::stageprof(0, _ig.elapsed());
1361 pending.push((*first, run.len()));
1362 }
1363 Item::Attn {
1364 l,
1365 li,
1366 q_norm,
1367 k_norm,
1368 output_gate,
1369 bias,
1370 full_gpu,
1371 } => {
1372 let _ia = std::time::Instant::now();
1373 if *full_gpu {
1375 let cache = &self.kv_cache.layers[*li];
1376 let o1p = if cache.o1.is_some() {
1377 match cache.o1_views() {
1378 Some(views) => Some(crate::gpu::O1AttnParams {
1379 views,
1380 epoch: self.o1_epoch,
1381 }),
1382 None => None,
1384 }
1385 } else {
1386 None
1387 };
1388 let o1_layer = cache.o1.is_some();
1389 if o1_layer && o1p.is_none() {
1390 }
1392 let cpu_k: Vec<&[f32]> = (0..nkv).map(|g| cache.head_keys(g)).collect();
1393 let cpu_v: Vec<&[f32]> = (0..nkv).map(|g| cache.head_values(g)).collect();
1394 let cpu_stored = if o1_layer { 0 } else { cpu_k[0].len() / hd };
1395 let p = crate::gpu::AttnDeviceParams {
1396 kv_id,
1397 layer: *li,
1398 nh,
1399 nkv,
1400 hd,
1401 rd,
1402 position,
1403 scale: self.attn_scale,
1404 eps: eps as f32,
1405 gemma,
1406 output_gate: *output_gate,
1407 q_norm: *q_norm,
1408 k_norm: *k_norm,
1409 inv_freq: &inv_freq,
1410 cpu_k,
1411 cpu_v,
1412 cpu_stored,
1413 o1: o1p,
1414 };
1415 let o1_bad = o1_layer && p.o1.is_none();
1416 if !o1_bad && graph.attn_device_ok(l, &p) && graph.encode_attn_device(l, &p)
1417 {
1418 if p.o1.is_none() {
1420 dev_attn.push(*li);
1421 }
1422 graph.commit_kind = 3;
1423 graph.commit();
1424 crate::gpu::stageprof(_xkind, _xt0.elapsed());
1428 continue;
1429 }
1430 }
1432 graph.encode_attn_prefix(l);
1433 graph.sync();
1434 if !pending.is_empty() {
1435 let idxs: Vec<usize> =
1436 pending.drain(..).flat_map(|(f, n)| f..f + n).collect();
1437 let mut outs: Vec<&mut [f32]> = self
1438 .kv_cache
1439 .layers
1440 .iter_mut()
1441 .enumerate()
1442 .filter(|(i, _)| idxs.binary_search(i).is_ok())
1443 .map(|(_, s)| s.linear_state.as_mut_slice())
1444 .collect();
1445 graph.read_states(&mut outs);
1446 }
1447 let mut q_raw = attention::take_buf(l.wq.1);
1448 let mut k = attention::take_buf(l.wk.1);
1449 let mut v = attention::take_buf(l.wv.1);
1450 graph.read_qkv(&mut q_raw, &mut k, &mut v);
1451 let cfg = QwenAttnCfg {
1452 num_heads: nh,
1453 num_kv_heads: nkv,
1454 head_dim: hd,
1455 hidden_size: hs,
1456 position,
1457 inv_freq: &inv_freq,
1458 rotary_dim: rd,
1459 scale: self.attn_scale,
1460 softcap: self.attn_softcap,
1461 window: None,
1462 v_norm: false,
1463 q_norm: *q_norm,
1464 k_norm: *k_norm,
1465 output_gate: *output_gate,
1466 softplus_gate: None,
1467 rope_scale: 1.0,
1468 bias: *bias,
1469 rms_eps: eps,
1470 norm_style,
1471 pool: pool.as_deref(),
1472 };
1473 let oracle = std::env::var("CMF_ATTN_ORACLE").as_deref() == Ok("1")
1476 || std::env::var("CMF_ATTN_DUMP").is_ok();
1477 let _ = full_gpu;
1478 let oracle_in = oracle.then(|| (q_raw.clone(), k.clone(), v.clone()));
1479 let mut ao = attention::qwen_attention_core(
1480 q_raw,
1481 k,
1482 v,
1483 &mut self.kv_cache.layers[*li],
1484 &cfg,
1485 );
1486 if let Ok(dir) = std::env::var("CMF_ATTN_DUMP") {
1490 if let Some((qr0, k0, v0)) = oracle_in.clone() {
1491 let (cq, _cg, _ck, _cv) =
1492 attention::finish_projection_debug(qr0, k0, v0, &cfg, position);
1493 let cache = &self.kv_cache.layers[*li];
1494 let n = cache.head_keys(0).len() / hd;
1495 let mut bytes: Vec<u8> = Vec::new();
1496 for v in [nh as u32, nkv as u32, hd as u32, n as u32, position as u32] {
1497 bytes.extend_from_slice(&v.to_le_bytes());
1498 }
1499 for v in &cq {
1500 bytes.extend_from_slice(&v.to_le_bytes());
1501 }
1502 for g in 0..nkv {
1503 for v in cache.head_keys(g) {
1504 bytes.extend_from_slice(&v.to_le_bytes());
1505 }
1506 }
1507 for g in 0..nkv {
1508 for v in cache.head_values(g) {
1509 bytes.extend_from_slice(&v.to_le_bytes());
1510 }
1511 }
1512 let _ =
1513 std::fs::write(format!("{dir}/L{li}_pos{position}.bin"), &bytes);
1514 }
1515 }
1516 if let Some((qr0, k0, v0)) =
1517 oracle_in.filter(|_| std::env::var("CMF_ATTN_ORACLE").as_deref() == Ok("1"))
1518 {
1519 let (cq, _cg, ck, cv) =
1520 attention::finish_projection_debug(qr0, k0, v0, &cfg, position);
1521 let mut h_now = vec![0f32; hs];
1522 graph.read_h(&mut h_now);
1523 let cache = &self.kv_cache.layers[*li];
1524 let n_after = cache.head_keys(0).len() / hd;
1525 let cpu_k: Vec<&[f32]> = (0..nkv)
1526 .map(|g| &cache.head_keys(g)[..(n_after - 1) * hd])
1527 .collect();
1528 let cpu_v: Vec<&[f32]> = (0..nkv)
1529 .map(|g| &cache.head_values(g)[..(n_after - 1) * hd])
1530 .collect();
1531 let p = crate::gpu::AttnDeviceParams {
1532 kv_id,
1533 layer: *li,
1534 nh,
1535 nkv,
1536 hd,
1537 rd,
1538 position,
1539 scale: self.attn_scale,
1540 eps: eps as f32,
1541 gemma,
1542 output_gate: *output_gate,
1543 q_norm: *q_norm,
1544 k_norm: *k_norm,
1545 inv_freq: &inv_freq,
1546 cpu_k,
1547 cpu_v,
1548 cpu_stored: n_after - 1,
1549 o1: None,
1550 };
1551 if let Some((dq, dk, dv, dao)) = graph.debug_attn_device(l, &p, &h_now) {
1552 let md = |a: &[f32], b: &[f32]| {
1553 a.iter().zip(b).fold(0f32, |m, (x, y)| m.max((x - y).abs()))
1554 };
1555 let nn = |a: &[f32]| a.iter().map(|x| x * x).sum::<f32>().sqrt();
1556 eprintln!(
1557 "attn-oracle L{li} pos {position}: |q| {:.2} max|dq| {:.4} | |k| {:.2} max|dk| {:.4} | |v| {:.2} max|dv| {:.4} | |ao| {:.2} max|dao| {:.4}",
1558 nn(&cq),
1559 md(&cq, &dq),
1560 nn(&ck),
1561 md(&ck, &dk),
1562 nn(&cv),
1563 md(&cv, &dv),
1564 nn(&ao),
1565 md(&ao, &dao)
1566 );
1567 } else {
1568 eprintln!("attn-oracle L{li}: device probe declined");
1569 }
1570 }
1571 graph.encode_attn_suffix(l, &ao);
1572 graph.commit();
1575 attention::recycle_buf(&mut ao);
1576 }
1577 }
1578
1579 crate::gpu::stageprof(_xkind, _xt0.elapsed());
1580 }
1581 let mut lm_rows = None;
1586 if self.graph_want_logits
1587 && upto.is_none()
1588 && end == self.num_layers
1589 && std::env::var("CMF_GPU_LMHEAD")
1590 .map(|v| v != "0")
1591 .unwrap_or(true)
1592 {
1593 if let Some(lm) = self.weights.lm_head.q1_parts() {
1594 if graph.lm_head_ok(lm) {
1595 graph.encode_lm_head(&self.weights.final_norm, lm);
1596 lm_rows = Some(lm.1);
1597 }
1598 }
1599 }
1600 let _sy0 = std::time::Instant::now();
1601 graph.sync();
1602 let _rs0 = std::time::Instant::now();
1603 if !pending.is_empty() {
1604 let idxs: Vec<usize> = pending.drain(..).flat_map(|(f, n)| f..f + n).collect();
1605 let mut outs: Vec<&mut [f32]> = self
1606 .kv_cache
1607 .layers
1608 .iter_mut()
1609 .enumerate()
1610 .filter(|(i, _)| idxs.binary_search(i).is_ok())
1611 .map(|(_, s)| s.linear_state.as_mut_slice())
1612 .collect();
1613 graph.read_states(&mut outs);
1614 }
1615 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() == Ok("1") {
1616 use std::sync::atomic::{AtomicU64, Ordering};
1617 static SY: AtomicU64 = AtomicU64::new(0);
1618 static RS: AtomicU64 = AtomicU64::new(0);
1619 static N: AtomicU64 = AtomicU64::new(0);
1620 SY.fetch_add((_rs0 - _sy0).as_nanos() as u64, Ordering::Relaxed);
1621 RS.fetch_add(_rs0.elapsed().as_nanos() as u64, Ordering::Relaxed);
1622 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
1623 if n % 100 == 0 {
1624 eprintln!(
1625 "postprof: sync-wait {:.1} ms/ток | read_states {:.1} ms/ток ({n})",
1626 SY.load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
1627 RS.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
1628 );
1629 }
1630 }
1631 if let Some(rows) = lm_rows {
1632 crate::gpu::hostprof_encode_done(_mt0);
1633 let mut lg = attention::take_buf(rows.min(self.vocab_size));
1634 graph.read_logits(&mut lg);
1635 crate::gpu::hostprof_total(_mt0);
1636 lg.resize(self.vocab_size, 0.0);
1637 if let Some(c) = self.final_softcap {
1638 for l in lg.iter_mut() {
1639 *l = c * (*l / c).tanh();
1640 }
1641 }
1642 self.graph_logits = Some(lg);
1643 }
1644 graph.finish(h);
1645 for li in dev_attn {
1649 let mut krow = attention::take_buf(nkv * hd);
1650 let mut vrow = attention::take_buf(nkv * hd);
1651 if crate::gpu::kv_mirror_read_last(kv_id, li, nkv, hd, &mut krow, &mut vrow) {
1652 let cache = &mut self.kv_cache.layers[li];
1653 cache.append(&krow, &vrow, &[]);
1654 let n = cache.seq_len;
1655 let mut imp = attention::take_buf(n);
1656 crate::gpu::kv_mirror_take_imp(kv_id, li, &mut imp);
1657 cache.accumulate_imp(&imp);
1658 attention::recycle_buf(&mut imp);
1659 }
1660 attention::recycle_buf(&mut krow);
1661 attention::recycle_buf(&mut vrow);
1662 }
1663 end
1664 }
1665
1666 pub fn new(
1667 tokenizer: Tokenizer,
1668 weights: PipelineWeights,
1669 hidden_size: usize,
1670 intermediate_size: usize,
1671 num_heads: usize,
1672 num_kv_heads: usize,
1673 head_dim: usize,
1674 num_layers: usize,
1675 physical_layers: usize,
1676 loop_final_norm: bool,
1677 vocab_size: usize,
1678 rms_eps: f64,
1679 rope_base: f32,
1680 norm_style: NormStyle,
1681 max_seq_len: usize,
1682 sampler_config: SamplerConfig,
1683 ) -> Self {
1684 let rng = match sampler_config.seed {
1685 Some(s) => SplitMix64::new(s),
1686 None => SplitMix64::from_entropy(),
1687 };
1688 let inv_freq = std::sync::Arc::new(attention::rope_inv_freq(head_dim, rope_base));
1689 let pool = Pool::from_env();
1690 if let Some(p) = &pool {
1691 tracing::info!("worker pool: {} threads", p.n_workers());
1692 }
1693 Self {
1694 gpu_plan: None,
1695 tokenizer: std::sync::Arc::new(tokenizer),
1696 kv_cache: KvCache::new(num_layers, num_kv_heads, head_dim, max_seq_len),
1697 sampler_config,
1698 weights,
1699 hidden_size,
1700 intermediate_size,
1701 num_heads,
1702 num_kv_heads,
1703 head_dim,
1704 num_layers,
1705 physical_layers,
1706 loop_final_norm,
1707 vocab_size,
1708 rms_eps,
1709 rope_base,
1710 norm_style,
1711 rotary_dim: head_dim,
1712 attention_heads_per_layer: None,
1713 vmf_cfg: None,
1714 gdn_cfg: None,
1715 kda_cfg: None,
1716 g3n: None,
1717 dsv4: None,
1718 qwen4_exp: None,
1719 dsv4_mtp: Vec::new(),
1720 dspark: None,
1721 dspark_pending: Vec::new(),
1722 dspark_hist: Vec::new(),
1723 dspark_real: Vec::new(),
1724 dspark_trunk_picks: Vec::new(),
1725 dspark_exp: Vec::new(),
1726 dspark_draft_ns: 0,
1727 logit_multiplier: None,
1728 cancel: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
1729 kv_history: Vec::new(),
1730 short_conv_cfg: None,
1731 mtp: None,
1732 speculative: std::env::var("CMF_MTP").map(|v| v != "0").unwrap_or(true),
1733 rng,
1734 sampler_scratch: SamplerScratch::default(),
1735 spec_forced: None,
1736 spec_q: Vec::new(),
1737 spec_p: Vec::new(),
1738 spec_res: Vec::new(),
1739 spec_qs: Vec::new(),
1740 spec_ps: Vec::new(),
1741 spec_ress: Vec::new(),
1742 mtp_graph_mode: None,
1743 #[cfg(target_os = "macos")]
1744 metal_verify: None,
1745 inv_freq,
1746 ws: ForwardScratch::new(hidden_size),
1747 pool,
1748 model: None,
1749 dyn_force_f32: false,
1750 dyn_skill_layers: Vec::new(),
1751 dyn_active: None,
1752 dyn_blend_loaded: false,
1753 dyn_phi_layer: None,
1754 dyn_phi_ema: Vec::new(),
1755 dyn_phi_seen: 0,
1756 dyn_router: None,
1757 o1_cfg: None,
1758 o1_epoch: 0,
1759 o1_flags: Vec::new(),
1760 trace: false,
1761 calib_temp: 1.0,
1762 confidence_on: true,
1763 embed_multiplier: 1.0,
1764 attn_scale: 1.0 / (head_dim as f32).sqrt(),
1765 swa: None,
1766 sliding_layers: None,
1767 inv_freq_local: None,
1768 rotary_dim_local: None,
1769 rope_scale: 1.0,
1770 rope_scale_local: 1.0,
1771 global_attn: None,
1772 inv_freq_global: None,
1773 attn_v_norm: false,
1774 final_softcap: None,
1775 head_clusters: None,
1776 attn_softcap: 0.0,
1777 graph_want_logits: false,
1778 graph_logits: None,
1779 graph_kv_id: {
1780 static NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
1781 NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
1782 },
1783 }
1784 }
1785
1786 pub fn set_o1(&mut self, cfg: Option<crate::nystrom::O1Cfg>) {
1793 self.o1_flags = match &cfg {
1794 Some(c) => {
1795 let mut flags = c.layer_flags(self.num_layers);
1796 for (li, f) in flags.iter_mut().enumerate() {
1797 if *f
1798 && !matches!(
1799 self.weights.layers[self.phys_layer(li)].attn,
1800 AttnKind::Full { .. }
1801 )
1802 {
1803 *f = false;
1804 }
1805 }
1806 flags
1807 }
1808 None => Vec::new(),
1809 };
1810 if let Some(c) = &cfg {
1811 let n = self.o1_flags.iter().filter(|&&f| f).count();
1812 tracing::info!(
1813 "o1 nystrom attention: {n}/{} layer(s), m={} w={} sink={} rect={:?}",
1814 self.num_layers,
1815 c.m,
1816 c.w,
1817 c.sink,
1818 c.rect
1819 );
1820 }
1821 self.o1_cfg = cfg;
1822 }
1823
1824 pub fn o1_active(&self) -> bool {
1826 self.o1_cfg.is_some() && self.o1_flags.iter().any(|&f| f)
1827 }
1828
1829 pub fn o1_begin(&mut self) {
1834 if let Some(c) = &self.o1_cfg {
1835 let (m, w, sink, rect) = (c.m, c.w, c.sink, c.rect);
1836 for (li, &f) in self.o1_flags.iter().enumerate() {
1837 if f {
1838 self.kv_cache.layers[li].o1_begin(m, w, sink, rect);
1839 }
1840 }
1841 }
1842 }
1843
1844 pub fn o1_seal(&mut self) {
1848 self.o1_epoch = self.o1_epoch.wrapping_add(1);
1849 if self.o1_cfg.is_none() {
1850 return;
1851 }
1852 for li in 0..self.num_layers {
1853 if self.o1_flags.get(li).copied().unwrap_or(false) {
1854 self.kv_cache.layers[li].o1_seal(self.num_heads);
1855 }
1856 }
1857 }
1858
1859 pub fn set_trace(&mut self, on: bool) {
1861 self.trace = on;
1862 }
1863
1864 pub fn set_sampler_config(&mut self, config: SamplerConfig) {
1867 self.rng = match config.seed {
1868 Some(seed) => SplitMix64::new(seed),
1869 None => SplitMix64::from_entropy(),
1870 };
1871 self.sampler_config = config;
1872 }
1873
1874 pub fn set_confidence(&mut self, on: bool) {
1879 self.confidence_on = on;
1880 }
1881
1882 pub fn set_calib_temp(&mut self, t: f32) {
1885 self.calib_temp = if t > 1e-3 { t } else { 1.0 };
1886 }
1887
1888 pub fn calib_temp(&self) -> f32 {
1890 self.calib_temp
1891 }
1892
1893 pub fn set_rotary(&mut self, rotary_dim: usize, base: f32) {
1896 self.rotary_dim = rotary_dim.min(self.head_dim);
1897 self.inv_freq = std::sync::Arc::new(attention::rope_inv_freq(self.rotary_dim, base));
1898 }
1899
1900 fn attn_cfg(&self, position: usize) -> QwenAttnCfg<'_> {
1901 QwenAttnCfg {
1902 num_heads: self.num_heads,
1903 num_kv_heads: self.num_kv_heads,
1904 head_dim: self.head_dim,
1905 hidden_size: self.hidden_size,
1906 position,
1907 inv_freq: &self.inv_freq,
1908 rotary_dim: self.rotary_dim,
1909 scale: self.attn_scale,
1910 softcap: self.attn_softcap,
1911 window: None,
1912 v_norm: false,
1913 q_norm: None,
1914 k_norm: None,
1915 output_gate: false,
1916 softplus_gate: None,
1917 rope_scale: self.rope_scale,
1918 bias: None,
1919 rms_eps: self.rms_eps,
1920 norm_style: self.norm_style,
1921 pool: self.pool.as_deref(),
1922 }
1923 }
1924
1925 pub fn generate(
1927 &mut self,
1928 prompt: &str,
1929 max_tokens: usize,
1930 task_mask: Option<&TaskMask>,
1931 on_token: Option<TokenCallback>,
1932 ) -> Result<GenerateResult, String> {
1933 let input_ids = self.tokenizer.with_bos(self.tokenizer.encode(prompt));
1934 self.generate_from_ids(&input_ids, max_tokens, task_mask, on_token)
1935 }
1936
1937 fn drop_open_mask<'m>(&self, m: Option<&'m TaskMask>) -> Option<&'m TaskMask> {
1939 m.filter(|m| !m.fully_open(self.intermediate_size, self.num_heads))
1940 }
1941
1942 pub fn generate_from_ids(
1950 &mut self,
1951 input_ids: &[u32],
1952 max_tokens: usize,
1953 task_mask: Option<&TaskMask>,
1954 mut on_token: Option<TokenCallback>,
1955 ) -> Result<GenerateResult, String> {
1956 if std::env::var("CMF_TRACE_H").is_ok() {
1957 eprintln!("input_ids: {input_ids:?}");
1958 }
1959 if input_ids.is_empty() {
1960 return Err("empty prompt: nothing to generate from".to_string());
1961 }
1962 let task_mask = self.drop_open_mask(task_mask);
1967
1968 let reuse_from = {
1976 let on = !std::env::var("CMF_KV_REUSE").is_ok_and(|v| v == "0");
1977 let h = &self.kv_history;
1978 if on
1979 && task_mask.is_none()
1980 && self.mtp.is_none()
1981 && self.o1_cfg.is_none()
1982 && !h.is_empty()
1983 && h.len() < input_ids.len()
1984 && input_ids[..h.len()] == h[..]
1985 {
1986 h.len()
1987 } else {
1988 0
1989 }
1990 };
1991 if reuse_from == 0 {
1992 self.kv_cache.clear();
1994 self.kv_history.clear();
1995 crate::gpu::graph_kv_reset(self.graph_kv_id);
1996 } else if std::env::var("CMF_PREFILL_PROF").is_ok() {
1997 eprintln!(
1998 "kv-reuse: {} of {} prompt positions already cached",
1999 reuse_from,
2000 input_ids.len()
2001 );
2002 }
2003 crate::gpu::graph_race_begin_generation();
2004 self.o1_begin();
2005
2006 let graph_on = crate::gpu::wgpu_graph_on(crate::gpu::GraphPhase::Decode);
2012 let spec_sampling_ok = self.sampler_config.temperature < 1e-6
2039 || std::env::var("CMF_GRAPH_SPEC_SAMPLE").as_deref() == Ok("1");
2040 let (mut dense_n, mut dense_q4tp) = (0usize, 0usize);
2056 for lw in &self.weights.layers {
2057 if let FfnKind::Dense(d) = &lw.ffn {
2058 dense_n += 1;
2059 if matches!(d.gate_proj.graph_weight(), Some((_, _, 6, _)))
2060 && matches!(d.up_proj.graph_weight(), Some((_, _, 6, _)))
2061 && matches!(d.down_proj.graph_weight(), Some((_, _, 6, _)))
2062 {
2063 dense_q4tp += 1;
2064 }
2065 }
2066 }
2067 let spec_default_ok = dense_n == 0 || dense_q4tp * 10 >= dense_n * 9;
2068 let penalized = self.sampler_config.repetition_penalty != 1.0
2072 || self.sampler_config.presence_penalty != 0.0
2073 || !self.sampler_config.suppress_tokens.is_empty();
2074 #[cfg(feature = "gpu")]
2079 let metal_wgpu = graph_on && crate::gpu_wgpu::wgpu_backend_is_metal();
2080 #[cfg(not(feature = "gpu"))]
2081 let metal_wgpu = false;
2082 let spec_env = std::env::var("CMF_GRAPH_SPEC").ok();
2083 let spec_wanted = match spec_env.as_deref() {
2084 Some("0") => false,
2085 Some(_) => {
2086 if metal_wgpu {
2087 tracing::warn!(
2088 "CMF_GRAPH_SPEC forced on wgpu/Metal: the batched verify graph is not \
2089 verified on this backend (garbage measured on Qwen3.5-0.8B)"
2090 );
2091 }
2092 true
2093 }
2094 None => spec_default_ok && !penalized && !metal_wgpu,
2095 };
2096 #[cfg(target_os = "macos")]
2099 let metal_graph = crate::gpu::q1_force()
2100 && crate::gpu::enabled_here()
2101 && std::env::var("CMF_GPU_BLOCK")
2102 .map(|v| v != "0")
2103 .unwrap_or(true);
2104 #[cfg(not(target_os = "macos"))]
2105 let metal_graph = false;
2106 let graph_spec = self.speculative
2107 && (graph_on || metal_graph)
2108 && self.mtp.is_some()
2109 && task_mask.is_none()
2110 && !self.o1_active()
2111 && spec_sampling_ok
2112 && spec_wanted;
2113 let pair_pays = self.gdn_cfg.is_none() || std::env::var("CMF_MTP").as_deref() == Ok("1");
2120 let spec_active = self.speculative
2121 && self.mtp.is_some()
2122 && task_mask.is_none()
2123 && !self.o1_active()
2124 && ((!graph_on && pair_pays && self.sampler_config.temperature < 1e-6) || graph_spec);
2125 let mut mtp = if spec_active { self.mtp.take() } else { None };
2128 if std::env::var("CMF_MTP_CHAIN_PROBE").is_ok() {
2129 eprintln!(
2130 "mtp-probe gate: spec_active={spec_active} mtp={} speculative={} graph_on={graph_on} temp_ok={}",
2131 mtp.is_some(),
2132 self.speculative,
2133 self.sampler_config.temperature < 1e-6,
2134 );
2135 }
2136 if let Some(m) = &mut mtp {
2137 m.kv.clear();
2138 crate::gpu::graph_kv_reset(self.mtp_kv_id());
2140 self.mtp_graph_mode = None;
2141 }
2142 let mut router = if mtp.is_none() {
2146 self.dyn_router.take()
2147 } else {
2148 None
2149 };
2150 if let Some(r) = &mut router {
2151 r.reset(); self.dyn_phi_seen = 0; let _ = self.set_active_skill(None);
2154 }
2155
2156 let mut all_ids = input_ids.to_vec();
2157 let mut generated = 0usize;
2158 let mut finish_reason = "max_tokens".to_string();
2159 let mut drafted = 0usize;
2160 let mut accepted = 0usize;
2161 let mut dsv4_spec_bad = 0usize;
2168 let mut dsv4_spec_retry_at = 0usize;
2169 let mut confidence: Vec<f32> = Vec::new();
2170 let trace_on = self.trace;
2171 let calib_temp = self.calib_temp;
2172 let mut traces: Vec<TokenTrace> = Vec::new();
2173
2174 let mut hidden = vec![0.0f32; self.hidden_size];
2180 let mut pos = reuse_from;
2181 let fuse_lm = mtp.is_none()
2190 && router.is_none()
2191 && std::env::var("CMF_GPU_LMHEAD").as_deref() != Ok("0");
2192 self.graph_logits = None;
2193 self.graph_want_logits = false;
2194 let _tpf = std::time::Instant::now();
2195 let batch_k = std::env::var("CMF_BATCH_K")
2196 .ok()
2197 .and_then(|v| v.parse::<usize>().ok())
2198 .unwrap_or(0);
2199 while self.qwen4_exp.is_some()
2210 && mtp.is_none()
2211 && pos < input_ids.len()
2212 && !self.cancel.load(std::sync::atomic::Ordering::Relaxed)
2213 {
2214 let token_id = input_ids[pos];
2215 let want_logits = pos + 1 == input_ids.len();
2216 let mut lg = Vec::new();
2217 if let Some(b) = &mut self.qwen4_exp {
2218 crate::qwen4_exp::forward_token(
2219 &b.0,
2220 &b.1,
2221 &b.2,
2222 &mut b.3,
2223 token_id,
2224 pos,
2225 &self.inv_freq,
2226 self.pool.as_deref(),
2227 &mut lg,
2228 want_logits,
2229 );
2230 }
2231 if want_logits {
2232 self.graph_logits = Some(lg);
2233 }
2234 pos += 1;
2235 hidden.fill(0.0);
2236 }
2237 while self.dsv4.is_some()
2238 && mtp.is_none()
2239 && pos < input_ids.len()
2240 && !self.cancel.load(std::sync::atomic::Ordering::Relaxed)
2241 {
2242 let end = (pos + prefill_chunk()).min(input_ids.len());
2243 let ids: Vec<u32> = input_ids[pos..end].to_vec();
2244 let mut lg = Vec::new();
2245 if let Some(b) = &mut self.dsv4 {
2246 let (g, layers, cfg, st) = (&b.0, &b.1, b.2, &mut b.3);
2247 crate::dsv4::forward_chunk(
2248 g,
2249 layers,
2250 &cfg,
2251 st,
2252 &ids,
2253 pos,
2254 &self.inv_freq,
2255 self.pool.as_deref(),
2256 &mut lg,
2257 end == input_ids.len(),
2258 );
2259 }
2260 if end == input_ids.len() {
2261 self.graph_logits = Some(lg);
2262 }
2263 pos = end;
2264 hidden = vec![0.0; self.hidden_size];
2265 }
2266 let dyn_prefill = router.is_some();
2271 let graph_prefill = self.graph_prefill_preferred();
2277 #[cfg(target_os = "macos")]
2285 if task_mask.is_none()
2286 && !dyn_prefill
2287 && crate::gpu::q1_force()
2288 && crate::gpu::enabled_here()
2289 && self.gdn_cfg.is_some()
2290 && self.g3n.is_none()
2291 && input_ids.len() > 8
2292 && std::env::var("CMF_MTP_CHAIN_PROBE").is_err()
2293 && std::env::var("CMF_METAL_PREFILL").as_deref() != Ok("0")
2294 {
2295 let chunk: usize = std::env::var("CMF_METAL_PREFILL_CHUNK")
2296 .ok()
2297 .and_then(|v| v.parse().ok())
2298 .filter(|&v| (16..=512).contains(&v))
2299 .unwrap_or(256);
2300 let hs = self.hidden_size;
2301 let _tp = std::time::Instant::now();
2302 while pos < input_ids.len() && !self.cancel.load(std::sync::atomic::Ordering::Relaxed) {
2303 let end = (pos + chunk).min(input_ids.len());
2304 let Some(hb) = self.prefill_batch_metal(&input_ids[pos..end], pos) else {
2305 break;
2306 };
2307 if let Some(m) = &mut mtp {
2308 let n_pairs = if end < input_ids.len() {
2309 end - pos
2310 } else {
2311 end - pos - 1
2312 };
2313 if n_pairs > 0 {
2314 let pairs: Vec<(&[f32], u32)> = (0..n_pairs)
2315 .map(|j| (&hb[j * hs..(j + 1) * hs], input_ids[pos + j + 1]))
2316 .collect();
2317 if !self.mtp_warm_batch_metal(m, &pairs, pos) {
2318 for (j, (h, t)) in pairs.iter().enumerate() {
2319 let h = h.to_vec();
2320 let _ = self.mtp_step(m, &h, *t, pos + j);
2321 }
2322 }
2323 }
2324 }
2325 hidden.copy_from_slice(&hb[(end - pos - 1) * hs..]);
2326 pos = end;
2327 }
2328 if std::env::var("CMF_PREFILL_PROF").is_ok() {
2329 eprintln!(
2330 "metal-prefill: {} of {} tokens in {:.1} ms",
2331 pos,
2332 input_ids.len(),
2333 _tp.elapsed().as_secs_f64() * 1e3
2334 );
2335 }
2336 }
2337 if task_mask.is_none()
2338 && !dyn_prefill
2339 && !graph_prefill
2340 && self.can_prefill_batched()
2341 && self.g3n.is_none()
2342 && input_ids.len() > 2
2343 {
2344 let chunk = prefill_chunk();
2350 let hs = self.hidden_size;
2351 while pos < input_ids.len() && !self.cancel.load(std::sync::atomic::Ordering::Relaxed) {
2352 let end = (pos + chunk).min(input_ids.len());
2353 let hb = self.prefill_batch(&input_ids[pos..end], pos);
2354 if let Some(m) = &mut mtp {
2355 let probe: usize = std::env::var("CMF_MTP_CHAIN_PROBE")
2356 .ok()
2357 .and_then(|v| v.parse().ok())
2358 .unwrap_or(0);
2359 for p in pos..end {
2360 if p + 1 < input_ids.len() {
2361 if probe >= 1 && p + 2 < input_ids.len() {
2362 let (d1, mut hx) = self.mtp_step_h(
2366 m,
2367 &hb[(p - pos) * hs..(p - pos + 1) * hs],
2368 input_ids[p + 1],
2369 p,
2370 );
2371 let mut ok = d1 == input_ids[p + 2];
2372 Self::chain_probe_note(0, ok);
2373 let mut d_prev = d1;
2374 let mut extra = 0usize;
2375 for j in 1..probe {
2376 if p + 2 + j >= input_ids.len() {
2377 break;
2378 }
2379 let (dj, hj) = self.mtp_step_h(m, &hx, d_prev, p + 1 + j);
2380 extra += 1;
2381 ok = ok && dj == input_ids[p + 2 + j];
2382 Self::chain_probe_note(j, ok);
2383 d_prev = dj;
2384 hx = hj;
2385 }
2386 m.kv.truncate_last(extra);
2387 } else {
2388 let _ = self.mtp_step(
2389 m,
2390 &hb[(p - pos) * hs..(p - pos + 1) * hs],
2391 input_ids[p + 1],
2392 p,
2393 );
2394 }
2395 }
2396 }
2397 }
2398 hidden.copy_from_slice(&hb[(end - pos - 1) * hs..]);
2399 pos = end;
2400 }
2401 }
2402 let pair_off = std::env::var("CMF_PAIR").is_ok_and(|v| v == "0");
2403 if task_mask.is_none()
2404 && !dyn_prefill
2405 && !graph_prefill
2406 && !pair_off
2407 && self.pair_supported()
2408 {
2409 while pos + 1 < input_ids.len()
2410 && !self.cancel.load(std::sync::atomic::Ordering::Relaxed)
2411 {
2412 let e1 = self.embed_single(input_ids[pos]);
2413 let e2 = self.embed_single(input_ids[pos + 1]);
2414 let (h1, h2) = self.forward_pair(&e1, &e2, pos);
2415 self.commit_linear_scratch();
2417 if let Some(m) = &mut mtp {
2418 let _ = self.mtp_step(m, &h1, input_ids[pos + 1], pos);
2419 if pos + 2 < input_ids.len() {
2420 let probe: usize = std::env::var("CMF_MTP_CHAIN_PROBE")
2421 .ok()
2422 .and_then(|v| v.parse().ok())
2423 .unwrap_or(0);
2424 if probe >= 1 && pos + 3 < input_ids.len() {
2425 let (d1, mut hx) = self.mtp_step_h(m, &h2, input_ids[pos + 2], pos + 1);
2429 let mut ok = d1 == input_ids[pos + 3];
2430 Self::chain_probe_note(0, ok);
2431 let mut d_prev = d1;
2432 let mut extra = 0usize;
2433 for j in 1..probe {
2434 if pos + 3 + j >= input_ids.len() {
2435 break;
2436 }
2437 let (dj, hj) = self.mtp_step_h(m, &hx, d_prev, pos + 2 + j);
2438 extra += 1;
2439 ok = ok && dj == input_ids[pos + 3 + j];
2440 Self::chain_probe_note(j, ok);
2441 d_prev = dj;
2442 hx = hj;
2443 }
2444 m.kv.truncate_last(extra);
2445 } else {
2446 let _ = self.mtp_step(m, &h2, input_ids[pos + 2], pos + 1);
2447 }
2448 }
2449 }
2450 hidden = h2;
2451 pos += 2;
2452 }
2453 }
2454 if batch_k > 0
2463 && graph_prefill
2464 && task_mask.is_none()
2465 && !self.o1_active()
2466 && mtp.is_none()
2467 && !dyn_prefill
2468 && pos + 1 < input_ids.len()
2469 {
2470 let hs = self.hidden_size;
2471 let chunk = batch_k;
2472 while pos < input_ids.len() {
2473 let end = (pos + chunk).min(input_ids.len());
2474 let bk = end - pos;
2475 let mut hiddens = vec![0f32; bk * hs];
2476 for (j, &id) in input_ids[pos..end].iter().enumerate() {
2477 hiddens[j * hs..(j + 1) * hs].copy_from_slice(&self.embed_single(id));
2478 }
2479 let positions: Vec<usize> = (pos..end).collect();
2480 let t_chunk = std::time::Instant::now();
2481 let ok_b = self.try_batch_graph_wgpu(&mut hiddens, &positions, bk, None);
2482 if std::env::var("CMF_GRAPH_PROF").is_ok() {
2483 let ms = t_chunk.elapsed().as_secs_f64() * 1000.0;
2484 eprintln!(
2485 "batch-chunk: k={bk} ok={ok_b} {ms:.1} ms ({:.1} tok/s)",
2486 bk as f64 / (ms / 1000.0)
2487 );
2488 }
2489 {
2490 use std::sync::atomic::{AtomicBool, Ordering};
2491 static SAID: AtomicBool = AtomicBool::new(false);
2492 if !SAID.swap(true, Ordering::Relaxed) {
2493 if ok_b {
2494 tracing::info!("batched prefill: ACTIVE (k={bk})");
2495 } else {
2496 tracing::warn!("batched prefill declined — per-position graph");
2497 }
2498 }
2499 }
2500 if ok_b {
2501 hidden.copy_from_slice(&hiddens[(bk - 1) * hs..]);
2502 pos = end;
2503 } else {
2504 break; }
2506 }
2507 }
2508 while pos < input_ids.len() && !self.cancel.load(std::sync::atomic::Ordering::Relaxed) {
2509 self.graph_want_logits = fuse_lm && pos + 1 == input_ids.len();
2510 hidden = self.forward_layers(&self.embed_single(input_ids[pos]), pos, task_mask);
2511 if let Some(m) = &mut mtp {
2512 if pos + 1 < input_ids.len() {
2513 let probe: usize = std::env::var("CMF_MTP_CHAIN_PROBE")
2519 .ok()
2520 .and_then(|v| v.parse().ok())
2521 .unwrap_or(0);
2522 if probe >= 1 && pos + 2 < input_ids.len() {
2523 let (d1, mut hx) = self.mtp_step_h(m, &hidden, input_ids[pos + 1], pos);
2524 let mut ok = d1 == input_ids[pos + 2];
2525 Self::chain_probe_note(0, ok);
2526 let mut d_prev = d1;
2527 let mut extra = 0usize;
2528 for j in 1..probe {
2529 if pos + 2 + j >= input_ids.len() {
2530 break;
2531 }
2532 let (dj, hj) = self.mtp_step_h(m, &hx, d_prev, pos + 1 + j);
2533 extra += 1;
2534 ok = ok && dj == input_ids[pos + 2 + j];
2535 Self::chain_probe_note(j, ok);
2536 d_prev = dj;
2537 hx = hj;
2538 }
2539 m.kv.truncate_last(extra);
2542 } else {
2543 let _ = self.mtp_step(m, &hidden, input_ids[pos + 1], pos);
2544 }
2545 }
2546 }
2547 pos += 1;
2548 }
2549 if std::env::var("CMF_PREFILL_PROF").is_ok() {
2550 eprintln!(
2551 "prefill: {} tokens in {:.1} ms (batch_k={batch_k})",
2552 input_ids.len(),
2553 _tpf.elapsed().as_secs_f64() * 1000.0
2554 );
2555 }
2556 if self
2559 .cancel
2560 .swap(false, std::sync::atomic::Ordering::Relaxed)
2561 {
2562 self.kv_history.clear();
2563 if let Some(m) = mtp {
2564 self.mtp = Some(m);
2565 }
2566 return Ok(GenerateResult {
2567 text: String::new(),
2568 token_ids: Vec::new(),
2569 prompt_tokens: input_ids.len(),
2570 tokens_generated: 0,
2571 finish_reason: "cancelled".to_string(),
2572 mtp_drafted: 0,
2573 mtp_accepted: 0,
2574 token_confidence: Vec::new(),
2575 traces: Vec::new(),
2576 });
2577 }
2578
2579 self.o1_seal();
2582
2583 macro_rules! commit {
2585 ($id:expr) => {{
2586 all_ids.push($id);
2587 generated += 1;
2588 if self.tokenizer.is_eos($id) {
2589 finish_reason = "stop".to_string();
2590 false
2591 } else {
2592 let token_text = self.tokenizer.decode_token($id);
2593 let mut go = true;
2594 if let Some(ref mut cb) = on_token {
2595 if !cb(&token_text) {
2596 finish_reason = "cancelled".to_string();
2597 go = false;
2598 }
2599 }
2600 go
2601 }
2602 }};
2603 }
2604
2605 let mut spec_trial = SpecTrial::Spec {
2616 t0: std::time::Instant::now(),
2617 gen0: generated,
2618 rounds: 0,
2619 };
2620 let mut spec_mon = SpecMon::default();
2621 let mut spec_watchdog_off = false;
2622 let mut next_pos = input_ids.len();
2624 'decode: while generated < max_tokens {
2625 if self
2626 .cancel
2627 .swap(false, std::sync::atomic::Ordering::Relaxed)
2628 {
2629 finish_reason = "cancelled".to_string();
2630 break 'decode;
2631 }
2632 let forced = self.spec_forced.take();
2637 let mut logits = match (forced, self.graph_logits.take()) {
2638 (Some(_), _) => Vec::new(),
2639 (None, Some(lg)) => lg,
2640 (None, None) => {
2641 inference::rms_norm_into(
2642 &hidden,
2643 &self.weights.final_norm,
2644 self.rms_eps,
2645 self.norm_style,
2646 &mut self.ws.n1,
2647 );
2648 self.lm_head_forward(&self.ws.n1)
2649 }
2650 };
2651 if generated
2654 == std::env::var("CMF_LOGIT_DUMP_STEP")
2655 .ok()
2656 .and_then(|v| v.parse().ok())
2657 .unwrap_or(0)
2658 {
2659 if let Ok(path) = std::env::var("CMF_LOGIT_DUMP") {
2660 let mut bytes: Vec<u8> = Vec::with_capacity((hidden.len() + logits.len()) * 4);
2661 for v in hidden.iter().chain(logits.iter()) {
2662 bytes.extend_from_slice(&v.to_le_bytes());
2663 }
2664 let _ = std::fs::write(&path, &bytes);
2665 }
2666 }
2667 let t_next = match forced {
2668 Some(c) => c,
2669 None => sampler::sample_with_scratch_pool(
2670 &logits,
2671 &self.sampler_config,
2672 &all_ids,
2673 &mut self.rng,
2674 &mut self.sampler_scratch,
2675 self.pool.as_deref(),
2676 ),
2677 };
2678 if self.confidence_on {
2679 confidence.push(if logits.is_empty() {
2680 0.0
2681 } else {
2682 sampler::top1_prob_pool(
2683 self.pool.as_deref(),
2684 &mut self.sampler_scratch,
2685 &logits,
2686 t_next,
2687 calib_temp,
2688 )
2689 });
2690 }
2691 if !logits.is_empty() {
2692 attention::recycle_buf(&mut logits);
2693 }
2694 if trace_on {
2695 let skill = router.as_ref().and_then(|r| r.active_id());
2699 traces.push(TokenTrace {
2700 t: generated,
2701 token_id: t_next,
2702 confidence: confidence.last().copied().unwrap_or(0.0),
2703 active_skill: skill,
2704 recon: None,
2705 switched: false,
2706 });
2707 }
2708 if !commit!(t_next) {
2709 break 'decode;
2710 }
2711 if generated >= max_tokens {
2712 break 'decode;
2713 }
2714
2715 if self.kv_cache.needs_eviction() {
2716 static SAID: std::sync::Once = std::sync::Once::new();
2722 SAID.call_once(|| {
2723 tracing::warn!(
2724 "KV cache full at {} positions — evicting half; quality \
2725 will degrade. Raise CMF_MAX_SEQ.",
2726 self.kv_cache.max_seq_len,
2727 );
2728 });
2729 let keep = (self.kv_cache.max_seq_len / 2).max(1);
2730 self.kv_cache.evict(keep);
2731 }
2732
2733 if graph_spec {
2736 match spec_trial {
2737 SpecTrial::Plain { t0, gen0 } if generated >= gen0 + 8 => {
2738 spec_mon.plain_ms =
2739 t0.elapsed().as_secs_f64() * 1e3 / (generated - gen0) as f64;
2740 let keep = spec_mon.pays();
2741 tracing::info!(
2742 "speculation trial: {:.2} tok/round in {:.1} ms vs plain {:.1} ms/tok — {}",
2743 spec_mon.tokens,
2744 spec_mon.round_ms,
2745 spec_mon.plain_ms,
2746 if keep { "speculating" } else { "plain" }
2747 );
2748 spec_mon.fails = 0;
2749 spec_trial = SpecTrial::Decided {
2750 spec: keep,
2751 recheck_at: if keep { usize::MAX } else { generated + 128 },
2752 };
2753 }
2754 SpecTrial::Decided { recheck_at, .. } if generated >= recheck_at => {
2755 spec_mon.n = 0;
2756 spec_trial = SpecTrial::Spec {
2757 t0: std::time::Instant::now(),
2758 gen0: generated,
2759 rounds: 0,
2760 };
2761 }
2762 _ => {}
2763 }
2764 spec_watchdog_off = matches!(
2765 spec_trial,
2766 SpecTrial::Plain { .. } | SpecTrial::Decided { spec: false, .. }
2767 );
2768 }
2769 match &mut mtp {
2770 #[cfg(feature = "gpu")]
2772 Some(m)
2773 if graph_spec
2774 && !spec_watchdog_off
2775 && generated + 1 < max_tokens
2776 && next_pos > 0 =>
2777 {
2778 let t_round = std::time::Instant::now();
2779 if let Some((extra, n_pos, new_h)) = self.graph_spec_step(
2780 m,
2781 &hidden,
2782 t_next,
2783 next_pos,
2784 &mut drafted,
2785 &mut accepted,
2786 &mut all_ids,
2787 ) {
2788 next_pos = n_pos;
2789 hidden = new_h;
2790 if std::env::var("CMF_GRAPH_SPEC_TIME").is_ok() {
2791 eprintln!(
2792 "spec-round wall {:.1} ms → {} tokens",
2793 t_round.elapsed().as_secs_f64() * 1e3,
2794 extra.len() + 1
2795 );
2796 }
2797 spec_mon.round(t_round.elapsed().as_secs_f64() * 1e3, extra.len() + 1);
2801 spec_trial = Self::spec_trial_round(
2804 spec_trial,
2805 &mut spec_mon,
2806 generated + extra.len() + 1,
2807 );
2808 let mut stopped = false;
2809 for &id in &extra {
2810 if self.confidence_on {
2811 confidence.push(0.0);
2812 }
2813 if !commit!(id) {
2814 stopped = true;
2815 break;
2816 }
2817 }
2818 if stopped {
2819 break 'decode;
2820 }
2821 continue 'decode;
2822 }
2823 spec_mon.round(t_round.elapsed().as_secs_f64() * 1e3, 1);
2834 spec_mon.tokens = 0.0;
2835 spec_mon.fails = 3;
2836 spec_trial = Self::spec_trial_round(spec_trial, &mut spec_mon, generated + 1);
2837 hidden = self.forward_layers(&self.embed_single(t_next), next_pos, task_mask);
2838 next_pos += 1;
2839 continue 'decode;
2840 }
2841 Some(m) if !graph_spec && generated + 1 < max_tokens => {
2843 let draft = self.mtp_step(m, &hidden, t_next, next_pos - 1);
2844 drafted += 1;
2845 let emb1 = self.embed_single(t_next);
2846 let emb2 = self.embed_single(draft);
2847 let (h1, h2) = self.forward_pair(&emb1, &emb2, next_pos);
2848
2849 inference::rms_norm_into(
2850 &h1,
2851 &self.weights.final_norm,
2852 self.rms_eps,
2853 self.norm_style,
2854 &mut self.ws.n1,
2855 );
2856 let mut logits1 = self.lm_head_forward(&self.ws.n1);
2857 let t_after = sampler::sample_with_scratch_pool(
2858 &logits1,
2859 &self.sampler_config,
2860 &all_ids,
2861 &mut self.rng,
2862 &mut self.sampler_scratch,
2863 self.pool.as_deref(),
2864 );
2865 if self.confidence_on {
2866 confidence.push(sampler::top1_prob_pool(
2867 self.pool.as_deref(),
2868 &mut self.sampler_scratch,
2869 &logits1,
2870 t_after,
2871 calib_temp,
2872 ));
2873 }
2874 attention::recycle_buf(&mut logits1);
2875 if trace_on {
2876 traces.push(TokenTrace {
2879 t: generated,
2880 token_id: t_after,
2881 confidence: confidence.last().copied().unwrap_or(0.0),
2882 active_skill: None,
2883 recon: None,
2884 switched: false,
2885 });
2886 }
2887 let stop = !commit!(t_after);
2888
2889 if t_after == draft {
2890 accepted += 1;
2891 self.commit_linear_scratch();
2892 let _ = self.mtp_step(m, &h1, t_after, next_pos);
2893 hidden = h2;
2894 next_pos += 2;
2895 } else {
2896 for layer in &mut self.kv_cache.layers {
2898 layer.truncate_last(1);
2899 }
2900 if !stop {
2901 let _ = self.mtp_step(m, &h1, t_after, next_pos);
2902 hidden = self.forward_layers(
2903 &self.embed_single(t_after),
2904 next_pos + 1,
2905 None,
2906 );
2907 }
2908 next_pos += 2;
2909 }
2910 if stop {
2911 break 'decode;
2912 }
2913 }
2914 _ => {
2916 #[cfg(feature = "gpu")]
2921 if Self::dsv4_spec_on() && self.dsv4.is_some() {
2922 static SAID: std::sync::Once = std::sync::Once::new();
2923 SAID.call_once(|| {
2924 eprintln!(
2925 "dsv4-spec гейт: mtp={} mask={} router={} trace={} temp={} rep={} ",
2926 !self.dsv4_mtp.is_empty(),
2927 task_mask.is_none(),
2928 router.is_none(),
2929 !trace_on,
2930 self.sampler_config.temperature < 1e-6,
2931 self.sampler_config.repetition_penalty == 1.0,
2932 );
2933 });
2934 }
2935 #[cfg(feature = "gpu")]
2936 if Self::dsv4_spec_on()
2937 && self.dsv4.is_some()
2938 && !self.dsv4_mtp.is_empty()
2939 && task_mask.is_none()
2940 && router.is_none()
2941 && !trace_on
2942 && self.sampler_config.temperature < 1e-6
2943 && self.sampler_config.repetition_penalty == 1.0
2944 && generated + 1 < max_tokens
2945 && all_ids.len() >= 2
2946 && generated >= dsv4_spec_retry_at
2947 {
2948 let tip_token = all_ids[all_ids.len() - 2];
2949 let drafted0 = drafted;
2950 let round = self.dsv4_spec_step(
2951 tip_token,
2952 t_next,
2953 next_pos,
2954 max_tokens.saturating_sub(generated),
2955 &mut drafted,
2956 &mut accepted,
2957 );
2958 if drafted > drafted0 {
2959 let useful = round.as_ref().is_some_and(|(extra, _)| !extra.is_empty());
2960 if useful {
2961 dsv4_spec_bad = 0;
2962 } else {
2963 dsv4_spec_bad += 1;
2964 if dsv4_spec_bad >= 2 {
2965 dsv4_spec_bad = 0;
2966 dsv4_spec_retry_at = generated.saturating_add(32);
2967 tracing::info!(
2968 "dsv4: draft не окупился дважды — точный walk на 32 токена"
2969 );
2970 }
2971 }
2972 }
2973 if let Some((extra, n_pos)) = round {
2974 next_pos = n_pos;
2975 let mut stopped = false;
2976 for &id in &extra {
2977 if self.confidence_on {
2978 confidence.push(0.0);
2979 }
2980 if !commit!(id) {
2981 stopped = true;
2982 break;
2983 }
2984 }
2985 if stopped {
2986 break 'decode;
2987 }
2988 continue 'decode;
2989 }
2990 }
2991 self.graph_want_logits = fuse_lm;
2992 let mut t_fwd = t_next;
2998 let pure_greedy = self.sampler_config.temperature < 1e-6
2999 && self.sampler_config.repetition_penalty == 1.0
3000 && self.sampler_config.suppress_tokens.is_empty();
3001 let burst_k = std::env::var("CMF_MULTISTEP")
3006 .ok()
3007 .and_then(|v| v.parse::<usize>().ok())
3008 .unwrap_or(0);
3009 if pure_greedy
3010 && burst_k >= 1
3011 && fuse_lm
3012 && task_mask.is_none()
3013 && router.is_none()
3014 && !trace_on
3015 && !self.confidence_on
3016 {
3017 let mut stopped = false;
3018 loop {
3019 let room = max_tokens.saturating_sub(generated);
3020 if room <= 2 {
3021 break;
3022 }
3023 let k = burst_k.min(room - 1);
3024 if k < 1 {
3025 break;
3026 }
3027 let Some(ids) = self.try_multi_burst(t_fwd, next_pos, k) else {
3028 break;
3029 };
3030 next_pos += k;
3031 for &id in &ids {
3032 if !commit!(id) {
3033 stopped = true;
3034 break;
3035 }
3036 }
3037 if stopped {
3038 break;
3039 }
3040 t_fwd = *ids.last().unwrap();
3041 }
3042 if stopped {
3043 break 'decode;
3044 }
3045 }
3046 hidden = self.forward_layers(&self.embed_single(t_fwd), next_pos, task_mask);
3047 next_pos += 1;
3048 if let Some(r) = &mut router {
3051 let phi = self.dyn_phi_ema.clone();
3052 let decision = r.step(&phi, generated);
3053 if let Some(new_active) = decision {
3054 let _ = self.set_active_skill(new_active);
3055 }
3056 if trace_on {
3059 if let Some(last) = traces.last_mut() {
3060 let e = r.last_best_e();
3061 last.recon = e.is_finite().then_some(e);
3062 last.switched = decision.is_some();
3063 }
3064 }
3065 }
3066 }
3067 }
3068 }
3069
3070 self.graph_want_logits = false;
3071 self.graph_logits = None;
3072 if router.is_some() {
3074 let _ = self.set_active_skill(None);
3075 }
3076 self.dyn_router = router.or(self.dyn_router.take());
3077 self.mtp = mtp.or(self.mtp.take());
3078
3079 let output_ids = &all_ids[input_ids.len()..];
3080 let forwarded = input_ids.len() + output_ids.len().saturating_sub(1);
3084 self.kv_history = all_ids[..forwarded.min(all_ids.len())].to_vec();
3085 confidence.truncate(output_ids.len()); traces.truncate(output_ids.len());
3087 Ok(GenerateResult {
3088 text: self.tokenizer.decode(output_ids),
3089 token_ids: output_ids.to_vec(),
3090 prompt_tokens: input_ids.len(),
3091 tokens_generated: generated,
3092 finish_reason,
3093 mtp_drafted: drafted,
3094 mtp_accepted: accepted,
3095 token_confidence: confidence,
3096 traces,
3097 })
3098 }
3099
3100 fn mtp_step(
3104 &mut self,
3105 m: &mut MtpModule,
3106 hidden: &[f32],
3107 next_token: u32,
3108 position: usize,
3109 ) -> u32 {
3110 self.mtp_step_h(m, hidden, next_token, position).0
3111 }
3112
3113 fn chain_probe_note(depth: usize, prefix_ok: bool) {
3117 use std::sync::Mutex;
3118 static T: Mutex<Vec<(u64, u64)>> = Mutex::new(Vec::new());
3119 let mut t = T.lock().unwrap();
3120 if t.len() <= depth {
3121 t.resize(depth + 1, (0, 0));
3122 }
3123 t[depth].0 += 1;
3124 t[depth].1 += prefix_ok as u64;
3125 if depth == 0 && t[0].0 % 128 == 0 {
3126 let line: Vec<String> = t
3127 .iter()
3128 .enumerate()
3129 .map(|(d, (n, k))| {
3130 format!(
3131 "d{}={:.0}%({n})",
3132 d + 1,
3133 100.0 * *k as f64 / (*n).max(1) as f64
3134 )
3135 })
3136 .collect();
3137 eprintln!("mtp-chain: {}", line.join(" "));
3138 }
3139 }
3140
3141 fn mtp_step_hl(
3149 &mut self,
3150 m: &mut MtpModule,
3151 hidden: &[f32],
3152 next_token: u32,
3153 position: usize,
3154 ) -> (Vec<f32>, Vec<f32>) {
3155 #[cfg(target_os = "macos")]
3160 if self.mtp_graph_mode != Some(false) && crate::gpu::q1_force() {
3161 if let Some(r) = self.mtp_step_metal(m, hidden, next_token, position, true) {
3162 self.mtp_graph_mode = Some(true);
3163 return r;
3164 }
3165 self.mtp_graph_mode = Some(false);
3166 }
3167 #[cfg(feature = "gpu")]
3168 if self.mtp_graph_mode != Some(false) {
3169 if let Some(r) = self.mtp_step_graph(m, hidden, next_token, position) {
3170 self.mtp_graph_mode = Some(true);
3171 return r;
3172 }
3173 if self.mtp_graph_mode == Some(true) {
3174 tracing::warn!("mtp graph declined mid-run — draft falls to the per-op path");
3179 }
3180 self.mtp_graph_mode = Some(false);
3181 }
3182 let e = self.embed_single(next_token);
3186 let mut cat = vec![0.0f32; 2 * self.hidden_size];
3187 let (cat_e, cat_h) = cat.split_at_mut(self.hidden_size);
3188 inference::rms_norm_into(&e, &m.enorm, self.rms_eps, self.norm_style, cat_e);
3189 inference::rms_norm_into(hidden, &m.hnorm, self.rms_eps, self.norm_style, cat_h);
3190 let mut x = vec![0.0f32; self.hidden_size];
3191 m.eh_proj.matvec(&cat, &mut x, self.pool.as_deref());
3192
3193 let lw = &m.layer;
3195 inference::rms_norm_into(
3196 &x,
3197 &lw.input_norm,
3198 self.rms_eps,
3199 self.norm_style,
3200 &mut self.ws.n1,
3201 );
3202 let attn = match &lw.attn {
3203 AttnKind::Mla(_) => unreachable!("MLA has no MTP/pair path"),
3205 AttnKind::Kda(_) => unreachable!("KDA has no MTP/pair path"),
3206 AttnKind::Full {
3207 wq,
3208 wk,
3209 wv,
3210 wo,
3211 q_norm,
3212 k_norm,
3213 output_gate,
3214 softplus_gate,
3215 bias,
3216 } => {
3217 let mut cfg = self.attn_cfg(position);
3218 cfg.q_norm = q_norm.as_deref();
3219 cfg.k_norm = k_norm.as_deref();
3220 cfg.output_gate = *output_gate;
3221 cfg.softplus_gate = softplus_gate
3222 .as_ref()
3223 .map(|(gate, per_head)| (gate, *per_head));
3224 cfg.bias = bias
3225 .as_ref()
3226 .map(|(q, k, v)| (q.as_slice(), k.as_slice(), v.as_slice()));
3227 attention::qwen_attention(&self.ws.n1, wq, wk, wv, wo, &mut m.kv, &cfg)
3228 }
3229 AttnKind::Linear(_) | AttnKind::LinearGdn(_) | AttnKind::ShortConv(_) => {
3230 unreachable!("MTP block is full attention")
3231 }
3232 };
3233 for (i, &a) in attn.iter().enumerate() {
3234 x[i] += a;
3235 }
3236 inference::rms_norm_into(
3237 &x,
3238 &lw.post_norm,
3239 self.rms_eps,
3240 self.norm_style,
3241 &mut self.ws.p1,
3242 );
3243 let ffn = ffn_forward(&lw.ffn, &self.ws.p1, self.pool.as_deref(), None);
3244 for (i, &f) in ffn.iter().enumerate() {
3245 x[i] += f;
3246 }
3247
3248 inference::rms_norm_into(
3249 &x,
3250 &m.final_norm,
3251 self.rms_eps,
3252 self.norm_style,
3253 &mut self.ws.n1,
3254 );
3255 let lg = self.lm_head_forward(&self.ws.n1);
3256 (lg, x)
3257 }
3258
3259 fn mtp_step_h(
3261 &mut self,
3262 m: &mut MtpModule,
3263 hidden: &[f32],
3264 next_token: u32,
3265 position: usize,
3266 ) -> (u32, Vec<f32>) {
3267 let (mut lg, x) = self.mtp_step_hl(m, hidden, next_token, position);
3268 let draft = sampler::argmax(&lg);
3269 attention::recycle_buf(&mut lg);
3270 (draft, x)
3271 }
3272
3273 fn spec_trial_round(trial: SpecTrial, mon: &mut SpecMon, generated: usize) -> SpecTrial {
3279 match trial {
3280 SpecTrial::Spec { t0, gen0, rounds } => {
3281 let rounds = rounds + 1;
3282 if rounds >= 5 {
3283 if mon.plain_ms > 0.0 {
3284 let keep = mon.pays();
3285 mon.fails = 0;
3286 tracing::info!(
3287 "speculation re-check: {:.2} tok/round in {:.1} ms vs plain {:.1} ms/tok — {}",
3288 mon.tokens,
3289 mon.round_ms,
3290 mon.plain_ms,
3291 if keep { "speculating" } else { "plain" }
3292 );
3293 SpecTrial::Decided {
3294 spec: keep,
3295 recheck_at: if keep { usize::MAX } else { generated + 128 },
3296 }
3297 } else {
3298 SpecTrial::Plain {
3299 t0: std::time::Instant::now(),
3300 gen0: generated,
3301 }
3302 }
3303 } else {
3304 SpecTrial::Spec { t0, gen0, rounds }
3305 }
3306 }
3307 SpecTrial::Decided { spec: true, .. } => {
3308 if mon.pays() {
3309 mon.fails = 0;
3310 trial
3311 } else {
3312 mon.fails += 1;
3313 if mon.fails >= 4 {
3314 tracing::info!(
3315 "speculation stopped: {:.2} tok/round in {:.1} ms vs plain {:.1} ms/tok",
3316 mon.tokens,
3317 mon.round_ms,
3318 mon.plain_ms
3319 );
3320 SpecTrial::Decided {
3321 spec: false,
3322 recheck_at: generated + 128,
3323 }
3324 } else {
3325 trial
3326 }
3327 }
3328 }
3329 other => other,
3330 }
3331 }
3332
3333 fn mtp_kv_id(&self) -> u64 {
3336 self.graph_kv_id | (1u64 << 40)
3337 }
3338
3339 const MTP_LAYER_BASE: usize = 0;
3344
3345 fn mtp_block_input(&mut self, m: &MtpModule, hidden: &[f32], next_token: u32) -> Vec<f32> {
3348 let e = self.embed_single(next_token);
3349 let mut cat = vec![0.0f32; 2 * self.hidden_size];
3350 let (cat_e, cat_h) = cat.split_at_mut(self.hidden_size);
3351 inference::rms_norm_into(&e, &m.enorm, self.rms_eps, self.norm_style, cat_e);
3352 inference::rms_norm_into(hidden, &m.hnorm, self.rms_eps, self.norm_style, cat_h);
3353 let mut x = vec![0.0f32; self.hidden_size];
3354 m.eh_proj.matvec(&cat, &mut x, self.pool.as_deref());
3355 x
3356 }
3357
3358 #[cfg(feature = "gpu")]
3361 fn mtp_graph_ok(&self, m: &MtpModule) -> bool {
3362 if std::env::var("CMF_MTP_GRAPH").as_deref() == Ok("0") {
3363 return false;
3364 }
3365 if !crate::gpu::wgpu_graph_on(crate::gpu::GraphPhase::Decode)
3366 || !crate::gpu::enabled_here()
3367 || self.attn_softcap > 0.0
3368 || self.attention_heads_per_layer.is_some()
3369 {
3370 return false;
3371 }
3372 matches!(
3373 &m.layer.attn,
3374 AttnKind::Full {
3375 softplus_gate: None,
3376 ..
3377 }
3378 ) && matches!(&m.layer.ffn, FfnKind::Dense(_))
3379 }
3380
3381 #[cfg(feature = "gpu")]
3387 fn mtp_step_graph(
3388 &mut self,
3389 m: &mut MtpModule,
3390 hidden: &[f32],
3391 next_token: u32,
3392 position: usize,
3393 ) -> Option<(Vec<f32>, Vec<f32>)> {
3394 if !self.mtp_graph_ok(m) {
3395 return None;
3396 }
3397 let lw = &m.layer;
3398 let AttnKind::Full {
3399 wq,
3400 wk,
3401 wv,
3402 wo,
3403 q_norm,
3404 k_norm,
3405 output_gate,
3406 softplus_gate,
3407 bias,
3408 } = &lw.attn
3409 else {
3410 return None;
3411 };
3412 if softplus_gate.is_some() {
3413 return None;
3414 }
3415 let FfnKind::Dense(d) = &lw.ffn else {
3416 return None;
3417 };
3418 if !d.segs.is_empty() {
3419 return None; }
3421 let mut x = self.mtp_block_input(m, hidden, next_token);
3424 fn gw(t: &QTensor) -> Option<crate::gpu::GraphW<'_>> {
3425 let (_, i, kind, rs) = t.graph_weight()?;
3426 Some(crate::gpu::GraphW {
3427 idx: i,
3428 kind,
3429 row_scale: rs,
3430 data: &[],
3431 })
3432 }
3433 let (model, _, _, _) = wq.graph_weight()?;
3434 let model = model.clone();
3435 let (lm_gw, lm_rows) = {
3436 let (_, i, kind, rs) = self.weights.lm_head.graph_weight()?;
3437 (
3438 crate::gpu::GraphW {
3439 idx: i,
3440 kind,
3441 row_scale: rs,
3442 data: &[],
3443 },
3444 self.weights.lm_head.rows(),
3445 )
3446 };
3447 let layer = crate::gpu::GraphLayer {
3448 input_norm: &lw.input_norm,
3449 attn: crate::gpu::GraphAttn::Full {
3450 wq: gw(wq)?,
3451 wk: gw(wk)?,
3452 wv: gw(wv)?,
3453 wo: gw(wo)?,
3454 q_norm: q_norm.as_deref(),
3455 k_norm: k_norm.as_deref(),
3456 bias: bias
3457 .as_ref()
3458 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
3459 output_gate: *output_gate,
3460 cpu_k: m.kv.k_heads(),
3461 cpu_v: m.kv.v_heads(),
3462 },
3463 post_norm: &lw.post_norm,
3464 ffn: crate::gpu::GraphFfn::Dense {
3465 gate: gw(&d.gate_proj)?,
3466 up: gw(&d.up_proj)?,
3467 down: gw(&d.down_proj)?,
3468 },
3469 };
3470 let nh = self.num_heads;
3471 let (nkv, hd, rd) = self.layer_geom(0);
3472 let gemma = self.norm_style == cortiq_core::NormStyle::Gemma;
3473 let mut logits = Vec::new();
3474 let ok = crate::gpu::forward_token_graph(
3475 &model,
3476 self.mtp_kv_id(),
3477 std::slice::from_ref(&layer),
3478 &[None],
3479 self.o1_epoch,
3480 &self.inv_freq,
3481 &mut x,
3482 nh,
3483 nkv,
3484 hd,
3485 self.attn_scale,
3486 rd,
3487 self.hidden_size,
3488 self.intermediate_size,
3489 position,
3490 self.kv_cache.max_seq_len,
3491 gemma,
3492 self.rms_eps as f32,
3493 Some((&lm_gw, lm_rows)),
3494 &m.final_norm,
3495 &mut logits,
3496 &[],
3497 1,
3498 None,
3499 None,
3500 None,
3501 Self::MTP_LAYER_BASE,
3502 true,
3503 );
3504 if !ok {
3505 return None;
3506 }
3507 logits.resize(self.vocab_size, 0.0);
3508 Some((logits, x))
3509 }
3510
3511 #[cfg(feature = "gpu")]
3518 fn mtp_warm_graph(
3519 &mut self,
3520 m: &mut MtpModule,
3521 pairs: &[(&[f32], u32)],
3522 first_pos: usize,
3523 ) -> bool {
3524 if pairs.is_empty() || !self.mtp_graph_ok(m) {
3525 return pairs.is_empty();
3526 }
3527 let hs = self.hidden_size;
3528 let mut hiddens = Vec::with_capacity(pairs.len() * hs);
3531 for (h, t) in pairs {
3532 hiddens.extend_from_slice(&self.mtp_block_input(m, h, *t));
3533 }
3534 let lw = &m.layer;
3535 let AttnKind::Full {
3536 wq,
3537 wk,
3538 wv,
3539 wo,
3540 q_norm,
3541 k_norm,
3542 output_gate,
3543 bias,
3544 ..
3545 } = &lw.attn
3546 else {
3547 return false;
3548 };
3549 let FfnKind::Dense(d) = &lw.ffn else {
3550 return false;
3551 };
3552 if !d.segs.is_empty() {
3553 return false; }
3555 fn gw(t: &QTensor) -> Option<crate::gpu::GraphW<'_>> {
3556 let (_, i, kind, rs) = t.graph_weight()?;
3557 Some(crate::gpu::GraphW {
3558 idx: i,
3559 kind,
3560 row_scale: rs,
3561 data: &[],
3562 })
3563 }
3564 let Some((model, _, _, _)) = wq.graph_weight() else {
3565 return false;
3566 };
3567 let model = model.clone();
3568 let (Some(gwq), Some(gwk), Some(gwv), Some(gwo), Some(gg), Some(gu), Some(gd)) = (
3569 gw(wq),
3570 gw(wk),
3571 gw(wv),
3572 gw(wo),
3573 gw(&d.gate_proj),
3574 gw(&d.up_proj),
3575 gw(&d.down_proj),
3576 ) else {
3577 return false;
3578 };
3579 let layer = crate::gpu::GraphLayer {
3580 input_norm: &lw.input_norm,
3581 attn: crate::gpu::GraphAttn::Full {
3582 wq: gwq,
3583 wk: gwk,
3584 wv: gwv,
3585 wo: gwo,
3586 q_norm: q_norm.as_deref(),
3587 k_norm: k_norm.as_deref(),
3588 bias: bias
3589 .as_ref()
3590 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
3591 output_gate: *output_gate,
3592 cpu_k: m.kv.k_heads(),
3593 cpu_v: m.kv.v_heads(),
3594 },
3595 post_norm: &lw.post_norm,
3596 ffn: crate::gpu::GraphFfn::Dense {
3597 gate: gg,
3598 up: gu,
3599 down: gd,
3600 },
3601 };
3602 let positions: Vec<usize> = (first_pos..first_pos + pairs.len()).collect();
3603 let nh = self.num_heads;
3604 let (nkv, hd, rd) = self.layer_geom(0);
3605 let gemma = self.norm_style == cortiq_core::NormStyle::Gemma;
3606 crate::gpu::forward_batch_graph(
3607 &model,
3608 self.mtp_kv_id(),
3609 std::slice::from_ref(&layer),
3610 &self.inv_freq,
3611 &mut hiddens,
3612 nh,
3613 nkv,
3614 hd,
3615 rd,
3616 hs,
3617 self.intermediate_size,
3618 &positions,
3619 self.kv_cache.max_seq_len,
3620 gemma,
3621 self.rms_eps as f32,
3622 self.attn_scale,
3623 pairs.len(),
3624 None,
3625 )
3626 }
3627
3628 fn mtp_warm(&mut self, m: &mut MtpModule, hidden: &[f32], next_token: u32, position: usize) {
3632 let e = self.embed_single(next_token);
3633 let mut cat = vec![0.0f32; 2 * self.hidden_size];
3634 let (cat_e, cat_h) = cat.split_at_mut(self.hidden_size);
3635 inference::rms_norm_into(&e, &m.enorm, self.rms_eps, self.norm_style, cat_e);
3636 inference::rms_norm_into(hidden, &m.hnorm, self.rms_eps, self.norm_style, cat_h);
3637 let mut x = vec![0.0f32; self.hidden_size];
3638 m.eh_proj.matvec(&cat, &mut x, self.pool.as_deref());
3639 inference::rms_norm_into(
3640 &x,
3641 &m.layer.input_norm,
3642 self.rms_eps,
3643 self.norm_style,
3644 &mut self.ws.n1,
3645 );
3646 let attn = match &m.layer.attn {
3647 AttnKind::Full {
3648 wq,
3649 wk,
3650 wv,
3651 wo,
3652 q_norm,
3653 k_norm,
3654 output_gate,
3655 softplus_gate,
3656 bias,
3657 } => {
3658 let mut cfg = self.attn_cfg(position);
3659 cfg.q_norm = q_norm.as_deref();
3660 cfg.k_norm = k_norm.as_deref();
3661 cfg.output_gate = *output_gate;
3662 cfg.softplus_gate = softplus_gate.as_ref().map(|(g, p)| (g, *p));
3663 cfg.bias = bias
3664 .as_ref()
3665 .map(|(q, k, v)| (q.as_slice(), k.as_slice(), v.as_slice()));
3666 attention::qwen_attention(&self.ws.n1, wq, wk, wv, wo, &mut m.kv, &cfg)
3667 }
3668 _ => return,
3669 };
3670 let _ = attn;
3671 }
3672
3673 #[cfg(feature = "gpu")]
3680 #[allow(clippy::too_many_arguments)]
3681 fn graph_spec_step(
3682 &mut self,
3683 m: &mut MtpModule,
3684 hidden: &[f32],
3685 t_next: u32,
3686 next_pos: usize,
3687 drafted: &mut usize,
3688 accepted: &mut usize,
3689 all_ids: &mut Vec<u32>,
3693 ) -> Option<(Vec<u32>, usize, Vec<f32>)> {
3694 #[cfg(target_os = "macos")]
3705 let metal_native = crate::gpu::q1_force();
3706 #[cfg(not(target_os = "macos"))]
3707 let metal_native = false;
3708 #[cfg(feature = "gpu")]
3709 let k_default = if metal_native {
3710 7
3713 } else if crate::gpu_wgpu::verify_i8_on() {
3714 5
3715 } else {
3716 4
3717 };
3718 #[cfg(not(feature = "gpu"))]
3719 let k_default = 4;
3720 let k_spec: usize = std::env::var("CMF_GRAPH_SPEC_K")
3721 .ok()
3722 .and_then(|v| v.parse().ok())
3723 .filter(|&v| (1..=8).contains(&v))
3724 .unwrap_or(k_default);
3725 if next_pos == 0 {
3726 return None;
3727 }
3728 let t_round = std::time::Instant::now();
3729 let subs = || crate::gpu_wgpu::SUBMITS.load(std::sync::atomic::Ordering::Relaxed);
3745 let sub0 = subs();
3746 let cfg = self.sampler_config.clone();
3751 let penalized = !(cfg.repetition_penalty == 1.0
3752 && cfg.presence_penalty == 0.0
3753 && cfg.suppress_tokens.is_empty());
3754 let greedy_pen = cfg.temperature < 1e-6 && penalized;
3759 let sampling = cfg.temperature >= 1e-6;
3760 let sparse = sampling && sampler::sparse_ok(&cfg);
3766 let base_len = all_ids.len();
3767 if sampling && !sparse && self.spec_q.len() < k_spec {
3768 self.spec_q.resize_with(k_spec, Vec::new);
3769 }
3770 if sparse && self.spec_qs.len() < k_spec {
3771 self.spec_qs.resize_with(k_spec, Vec::new);
3772 }
3773 let mut drafts = Vec::with_capacity(k_spec);
3778 let mut hx = hidden.to_vec();
3779 let spec_dbg = std::env::var("CMF_SPEC_DBG").is_ok();
3782 for j in 0..k_spec {
3783 let tok_in = if j == 0 { t_next } else { drafts[j - 1] };
3784 let mut dbg_ref: Option<(Vec<f32>, Vec<f32>)> = None;
3785 if spec_dbg {
3786 let saved = self.mtp_graph_mode;
3787 self.mtp_graph_mode = Some(false);
3788 let r = self.mtp_step_hl(m, &hx, tok_in, next_pos - 1 + j);
3789 self.mtp_graph_mode = saved;
3790 m.kv.truncate_last(1);
3791 dbg_ref = Some(r);
3792 }
3793 let (mut lg, hj) = self.mtp_step_hl(m, &hx, tok_in, next_pos - 1 + j);
3794 if let Some((lg_cpu, h_cpu)) = dbg_ref {
3795 let n = |v: &[f32]| v.iter().map(|x| x * x).sum::<f32>().sqrt();
3796 let dl = lg
3797 .iter()
3798 .zip(&lg_cpu)
3799 .fold(0f32, |m, (a, b)| m.max((a - b).abs()));
3800 let dh = hj
3801 .iter()
3802 .zip(&h_cpu)
3803 .fold(0f32, |m, (a, b)| m.max((a - b).abs()));
3804 eprintln!(
3805 "spec-dbg j={j} pos {} tok_in {tok_in}: per-op draft {} graph draft {} | max|dlogit| {dl:.3} | |h_cpu| {:.2} |h_graph| {:.2} max|dh| {dh:.3} | kv rows {}",
3806 next_pos - 1 + j,
3807 sampler::argmax(&lg_cpu),
3808 sampler::argmax(&lg),
3809 n(&h_cpu),
3810 n(&hj),
3811 m.kv.seq_len
3812 );
3813 }
3814 let dj = if sparse {
3815 let mut q = std::mem::take(&mut self.spec_qs[j]);
3816 let ok = sampler::sparse_distribution_into(
3817 &lg,
3818 &cfg,
3819 all_ids,
3820 &mut self.sampler_scratch,
3821 self.pool.as_deref(),
3822 &mut q,
3823 );
3824 let d = if ok {
3825 sampler::draw_sparse(&q, &mut self.rng)
3826 } else {
3827 let t = sampler::argmax(&lg);
3829 q.clear();
3830 q.push((t, 1.0));
3831 t
3832 };
3833 self.spec_qs[j] = q;
3834 all_ids.push(d);
3835 d
3836 } else if sampling {
3837 let mut q = std::mem::take(&mut self.spec_q[j]);
3838 sampler::distribution_into(
3839 &lg,
3840 &cfg,
3841 all_ids,
3842 &mut self.sampler_scratch,
3843 self.pool.as_deref(),
3844 &mut q,
3845 );
3846 let d = sampler::draw(&q, &mut self.rng);
3847 self.spec_q[j] = q;
3848 all_ids.push(d); d
3850 } else if greedy_pen {
3851 let d = sampler::argmax_penalized(
3852 &lg,
3853 &cfg,
3854 all_ids,
3855 &mut self.sampler_scratch,
3856 self.pool.as_deref(),
3857 );
3858 all_ids.push(d);
3859 d
3860 } else {
3861 sampler::argmax(&lg)
3862 };
3863 attention::recycle_buf(&mut lg);
3864 drafts.push(dj);
3865 hx = hj;
3866 }
3867 all_ids.truncate(base_len);
3868 *drafted += k_spec;
3869 let t_draft = t_round.elapsed();
3870 let sub_draft = subs();
3871 let b = k_spec + 1;
3874 let mut hiddens = vec![0.0f32; b * self.hidden_size];
3875 for (i, &t) in std::iter::once(&t_next).chain(drafts.iter()).enumerate() {
3876 let e = self.embed_single(t);
3877 hiddens[i * self.hidden_size..(i + 1) * self.hidden_size].copy_from_slice(&e);
3878 }
3879 let positions: Vec<usize> = (next_pos..next_pos + b).collect();
3880 let (lm_gw, lm_rows) = {
3881 let (_, i, kind, rs) = self.weights.lm_head.graph_weight()?;
3882 (
3883 crate::gpu::GraphW {
3884 idx: i,
3885 kind,
3886 row_scale: rs,
3887 data: &[],
3888 },
3889 self.weights.lm_head.rows(),
3890 )
3891 };
3892 let mut logits = Vec::new();
3893 let final_norm = self.weights.final_norm.clone();
3894 #[cfg(target_os = "macos")]
3895 let ok = if metal_native {
3896 let lm = self.weights.lm_head.q1_parts()?;
3897 self.try_batch_graph_metal(
3898 &mut hiddens,
3899 &positions,
3900 b,
3901 Some((lm, &final_norm, &mut logits)),
3902 )
3903 } else {
3904 self.try_batch_graph_wgpu(
3905 &mut hiddens,
3906 &positions,
3907 b,
3908 Some(crate::gpu::SpecTail {
3909 lm: lm_gw,
3910 lm_rows,
3911 final_norm: &final_norm,
3912 logits_out: &mut logits,
3913 }),
3914 )
3915 };
3916 #[cfg(not(target_os = "macos"))]
3917 let ok = self.try_batch_graph_wgpu(
3918 &mut hiddens,
3919 &positions,
3920 b,
3921 Some(crate::gpu::SpecTail {
3922 lm: lm_gw,
3923 lm_rows,
3924 final_norm: &final_norm,
3925 logits_out: &mut logits,
3926 }),
3927 );
3928 if !ok {
3929 m.kv.truncate_last(k_spec);
3932 return None;
3933 }
3934 #[cfg(target_os = "macos")]
3940 if metal_native && std::env::var("CMF_METAL_VERIFY_CHECK").as_deref() == Ok("1") {
3941 let snap: Vec<Vec<f32>> = self
3942 .kv_cache
3943 .layers
3944 .iter()
3945 .map(|l| l.linear_state.clone())
3946 .collect();
3947 let attn_lens: Vec<usize> = self.kv_cache.layers.iter().map(|l| l.seq_len).collect();
3948 let toks: Vec<u32> = std::iter::once(t_next)
3949 .chain(drafts.iter().copied())
3950 .collect();
3951 let want_save = self.graph_want_logits;
3952 self.graph_want_logits = false;
3953 for (i, &t) in toks.iter().enumerate() {
3954 let hi = self.forward_layers(&self.embed_single(t), next_pos + i, None);
3955 let _ = self.graph_logits.take();
3956 if std::env::var("CMF_SPEC_PLAIN_HIDDEN").as_deref() == Ok("1") {
3960 hiddens[i * self.hidden_size..(i + 1) * self.hidden_size].copy_from_slice(&hi);
3961 }
3962 let ref_lg = self.logits_from_hidden(&hi);
3963 let row = &logits[i * lm_rows..(i + 1) * lm_rows];
3964 let ra = sampler::argmax(&ref_lg);
3965 let va = sampler::argmax(row);
3966 let mut md = 0f32;
3967 let mut rms = 0f64;
3968 for j in 0..lm_rows.min(ref_lg.len()) {
3969 let d = (ref_lg[j] - row[j]).abs();
3970 md = md.max(d);
3971 rms += (d as f64) * (d as f64);
3972 }
3973 let mut hd = 0f32;
3974 for j in 0..self.hidden_size {
3975 hd = hd.max((hi[j] - hiddens[i * self.hidden_size + j]).abs());
3976 }
3977 eprintln!(
3978 "verify-check row {i} tok {t} pos {}: ref argmax {ra} verify argmax {va} {} | max|dlogit| {md:.3} rms {:.4} | max|dhidden| {hd:.4}",
3979 next_pos + i,
3980 if ra == va { "OK" } else { "MISMATCH" },
3981 (rms / lm_rows as f64).sqrt()
3982 );
3983 }
3984 self.graph_want_logits = want_save;
3985 for (l, st) in self.kv_cache.layers.iter_mut().zip(snap) {
3988 if l.linear_state.len() == st.len() {
3989 l.linear_state.copy_from_slice(&st);
3990 } else {
3991 l.linear_state = st;
3992 }
3993 }
3994 for (li, (l, n0)) in self.kv_cache.layers.iter_mut().zip(attn_lens).enumerate() {
3995 let extra = l.seq_len.saturating_sub(n0);
3996 if extra > 0 {
3997 l.truncate_last(extra);
3998 crate::gpu_metal::kv_mirror_set_stored(self.graph_kv_id, li, n0);
3999 }
4000 }
4001 }
4002 let t_verify = t_round.elapsed();
4003 let sub_verify = subs();
4004 let mut a = 0usize;
4009 let mut forced: Option<u32> = None;
4010 let ids: Vec<u32> = if sparse {
4011 let mut p = std::mem::take(&mut self.spec_ps);
4012 let mut res = std::mem::take(&mut self.spec_ress);
4013 while a < k_spec {
4014 let ok = sampler::sparse_distribution_into(
4015 &logits[a * lm_rows..(a + 1) * lm_rows],
4016 &cfg,
4017 all_ids,
4018 &mut self.sampler_scratch,
4019 self.pool.as_deref(),
4020 &mut p,
4021 );
4022 if !ok {
4023 let t = sampler::argmax(&logits[a * lm_rows..(a + 1) * lm_rows]);
4024 p.clear();
4025 p.push((t, 1.0));
4026 }
4027 match sampler::spec_accept_or_correct_sparse(
4028 &p,
4029 &self.spec_qs[a],
4030 drafts[a],
4031 &mut self.rng,
4032 &mut res,
4033 ) {
4034 None => {
4035 all_ids.push(drafts[a]);
4036 a += 1;
4037 }
4038 Some(c) => {
4039 forced = Some(c);
4040 break;
4041 }
4042 }
4043 }
4044 all_ids.truncate(base_len);
4045 self.spec_ps = p;
4046 self.spec_ress = res;
4047 drafts.clone()
4048 } else if sampling {
4049 let mut p = std::mem::take(&mut self.spec_p);
4050 let mut res = std::mem::take(&mut self.spec_res);
4051 while a < k_spec {
4052 sampler::distribution_into(
4053 &logits[a * lm_rows..(a + 1) * lm_rows],
4054 &cfg,
4055 all_ids,
4056 &mut self.sampler_scratch,
4057 self.pool.as_deref(),
4058 &mut p,
4059 );
4060 match sampler::spec_accept_or_correct(
4061 &p,
4062 &self.spec_q[a],
4063 drafts[a],
4064 &mut self.rng,
4065 &mut res,
4066 self.pool.as_deref(),
4067 ) {
4068 None => {
4069 all_ids.push(drafts[a]);
4070 a += 1;
4071 }
4072 Some(c) => {
4073 forced = Some(c);
4074 break;
4075 }
4076 }
4077 }
4078 all_ids.truncate(base_len);
4079 self.spec_p = p;
4080 self.spec_res = res;
4081 drafts.clone()
4083 } else if greedy_pen {
4084 let mut ids: Vec<u32> = Vec::with_capacity(b);
4088 for i in 0..b {
4089 let t = sampler::argmax_penalized(
4090 &logits[i * lm_rows..(i + 1) * lm_rows],
4091 &cfg,
4092 all_ids,
4093 &mut self.sampler_scratch,
4094 self.pool.as_deref(),
4095 );
4096 ids.push(t);
4097 if i < k_spec && t == drafts[i] {
4098 all_ids.push(t);
4099 } else {
4100 break;
4101 }
4102 }
4103 all_ids.truncate(base_len);
4104 while a < k_spec && a < ids.len() && ids[a] == drafts[a] {
4105 a += 1;
4106 }
4107 ids
4110 } else {
4111 let ids: Vec<u32> = (0..b)
4112 .map(|i| sampler::argmax(&logits[i * lm_rows..(i + 1) * lm_rows]))
4113 .collect();
4114 while a < k_spec && ids[a] == drafts[a] {
4115 a += 1;
4116 }
4117 ids
4118 };
4119 if spec_dbg {
4120 eprintln!(
4121 "spec-dbg round: t_next {t_next} drafts {:?} verified {:?} accepted {a}",
4122 drafts, ids
4123 );
4124 }
4125 #[cfg(target_os = "macos")]
4129 let commit_ref: Option<(Vec<Vec<f32>>, Vec<(usize, Vec<f32>, Vec<f32>)>)> = if metal_native
4130 && std::env::var("CMF_METAL_VERIFY_CHECK").as_deref() == Ok("2")
4131 {
4132 let snap: Vec<Vec<f32>> = self
4133 .kv_cache
4134 .layers
4135 .iter()
4136 .map(|l| l.linear_state.clone())
4137 .collect();
4138 let attn_lens: Vec<usize> = self.kv_cache.layers.iter().map(|l| l.seq_len).collect();
4139 let toks: Vec<u32> = std::iter::once(t_next)
4140 .chain(drafts.iter().copied())
4141 .collect();
4142 let want_save = self.graph_want_logits;
4143 self.graph_want_logits = false;
4144 for (i, &t) in toks.iter().take(a + 1).enumerate() {
4145 let _ = self.forward_layers(&self.embed_single(t), next_pos + i, None);
4146 let _ = self.graph_logits.take();
4147 }
4148 self.graph_want_logits = want_save;
4149 let plain_states: Vec<Vec<f32>> = self
4150 .kv_cache
4151 .layers
4152 .iter()
4153 .map(|l| l.linear_state.clone())
4154 .collect();
4155 let (nkv, hd) = (self.num_kv_heads, self.head_dim);
4156 let mut rows = Vec::new();
4157 for (li, (l, n0)) in self
4158 .kv_cache
4159 .layers
4160 .iter_mut()
4161 .zip(attn_lens.iter())
4162 .enumerate()
4163 {
4164 let extra = l.seq_len.saturating_sub(*n0);
4165 if extra > 0 {
4166 let mut kk = Vec::new();
4167 let mut vv = Vec::new();
4168 for g in 0..nkv {
4169 kk.extend_from_slice(&l.head_keys(g)[n0 * hd..]);
4170 vv.extend_from_slice(&l.head_values(g)[n0 * hd..]);
4171 }
4172 rows.push((li, kk, vv));
4173 l.truncate_last(extra);
4174 crate::gpu_metal::kv_mirror_set_stored(self.graph_kv_id, li, *n0);
4175 }
4176 }
4177 for (l, st) in self.kv_cache.layers.iter_mut().zip(snap) {
4178 if l.linear_state.len() == st.len() {
4179 l.linear_state.copy_from_slice(&st);
4180 } else {
4181 l.linear_state = st;
4182 }
4183 }
4184 Some((plain_states, rows))
4185 } else {
4186 None
4187 };
4188 #[cfg(target_os = "macos")]
4190 if metal_native {
4191 self.metal_verify_commit(a);
4194 if let Some((plain_states, rows)) = commit_ref {
4195 crate::gpu_metal::queue_fence();
4196 let (nkv, hd) = (self.num_kv_heads, self.head_dim);
4197 let mut worst_s = 0f32;
4198 let mut worst_li = 0usize;
4199 for (li, (l, ps)) in self.kv_cache.layers.iter().zip(&plain_states).enumerate() {
4200 if l.linear_state.len() != ps.len() || ps.is_empty() {
4201 continue;
4202 }
4203 let d = l
4204 .linear_state
4205 .iter()
4206 .zip(ps)
4207 .fold(0f32, |m, (x, y)| m.max((x - y).abs()));
4208 let n = ps.iter().fold(0f32, |m, y| m.max(y.abs()));
4209 let rel = d / n.max(1e-6);
4210 if rel > worst_s {
4211 worst_s = rel;
4212 worst_li = li;
4213 }
4214 }
4215 let mut worst_k = 0f32;
4216 for (li, kk, vv) in &rows {
4217 let l = &self.kv_cache.layers[*li];
4218 let n0 = l.seq_len - (kk.len() / (nkv * hd));
4219 let mut ck = Vec::new();
4220 let mut cv = Vec::new();
4221 for g in 0..nkv {
4222 ck.extend_from_slice(&l.head_keys(g)[n0 * hd..]);
4223 cv.extend_from_slice(&l.head_values(g)[n0 * hd..]);
4224 }
4225 if ck.len() == kk.len() {
4226 let dk = ck
4227 .iter()
4228 .zip(kk)
4229 .fold(0f32, |m, (x, y)| m.max((x - y).abs()));
4230 let dv = cv
4231 .iter()
4232 .zip(vv)
4233 .fold(0f32, |m, (x, y)| m.max((x - y).abs()));
4234 worst_k = worst_k.max(dk).max(dv);
4235 } else {
4236 eprintln!(
4237 "commit-check L{li}: kv row count mismatch {} vs {}",
4238 ck.len(),
4239 kk.len()
4240 );
4241 }
4242 }
4243 eprintln!(
4244 "commit-check a={a}: worst GDN state rel-max diff {worst_s:.2e} (L{worst_li}) | worst K/V row abs diff {worst_k:.4}"
4245 );
4246 }
4247 } else if a + 1 < b {
4248 crate::gpu::gdn_spec_restore(self.graph_kv_id, a);
4249 }
4250 #[cfg(not(target_os = "macos"))]
4251 if a + 1 < b {
4252 crate::gpu::gdn_spec_restore(self.graph_kv_id, a);
4253 }
4254 *accepted += a;
4255 m.kv.truncate_last(k_spec.saturating_sub(1));
4266 #[cfg(target_os = "macos")]
4267 if metal_native && self.mtp_graph_mode == Some(true) {
4268 crate::gpu_metal::kv_mirror_set_stored(
4271 self.mtp_kv_id(),
4272 Self::MTP_LAYER_BASE,
4273 m.kv.seq_len,
4274 );
4275 }
4276 let warm_off = std::env::var("CMF_SPEC_WARM").is_ok_and(|v| v == "0");
4277 if !warm_off && a > 0 {
4278 let mut warmed = false;
4281 #[cfg(target_os = "macos")]
4282 if metal_native && self.mtp_graph_mode == Some(true) {
4283 let pairs: Vec<(&[f32], u32)> = (0..a)
4287 .map(|j| {
4288 (
4289 &hiddens[j * self.hidden_size..(j + 1) * self.hidden_size],
4290 ids[j],
4291 )
4292 })
4293 .collect();
4294 warmed = self.mtp_warm_batch_metal(m, &pairs, next_pos);
4295 if !warmed {
4296 warmed = true;
4297 for j in 0..a {
4298 let row =
4299 hiddens[j * self.hidden_size..(j + 1) * self.hidden_size].to_vec();
4300 if self
4301 .mtp_step_metal(m, &row, ids[j], next_pos + j, false)
4302 .is_none()
4303 {
4304 warmed = false;
4305 break;
4306 }
4307 }
4308 }
4309 }
4310 if !warmed && self.mtp_graph_mode == Some(true) && !metal_native {
4311 let rows: Vec<Vec<f32>> = (0..a)
4312 .map(|j| hiddens[j * self.hidden_size..(j + 1) * self.hidden_size].to_vec())
4313 .collect();
4314 let pairs: Vec<(&[f32], u32)> = rows
4315 .iter()
4316 .zip(ids.iter())
4317 .map(|(r, &t)| (r.as_slice(), t))
4318 .collect();
4319 warmed = self.mtp_warm_graph(m, &pairs, next_pos);
4320 if !warmed {
4321 warmed = true;
4323 for j in 0..a {
4324 if self
4325 .mtp_step_graph(m, &rows[j], ids[j], next_pos + j)
4326 .is_none()
4327 {
4328 warmed = false;
4329 break;
4330 }
4331 }
4332 }
4333 }
4334 if !warmed {
4335 for j in 0..a {
4336 let row = &hiddens[j * self.hidden_size..(j + 1) * self.hidden_size];
4337 let row = row.to_vec();
4338 self.mtp_warm(m, &row, ids[j], next_pos + j);
4339 }
4340 }
4341 }
4342 if let Some(c) = forced {
4346 self.spec_forced = Some(c);
4347 self.graph_logits = None;
4348 } else {
4349 let mut row = logits[a * lm_rows..(a + 1) * lm_rows].to_vec();
4350 row.resize(self.vocab_size, 0.0);
4351 if let Some(c) = self.final_softcap {
4352 for l in row.iter_mut() {
4353 *l = c * (*l / c).tanh();
4354 }
4355 }
4356 self.graph_logits = Some(row);
4357 }
4358 let new_hidden = hiddens[a * self.hidden_size..(a + 1) * self.hidden_size].to_vec();
4359 if std::env::var("CMF_GRAPH_SPEC_TIME").is_ok() {
4365 let end = subs();
4366 eprintln!(
4367 "spec-round: draft {:.1} ms/{} sub | verify {:.1} ms/{} sub | \
4368 commit {:.1} ms/{} sub (accepted {a} of {k_spec})",
4369 t_draft.as_secs_f64() * 1e3,
4370 sub_draft - sub0,
4371 (t_verify - t_draft).as_secs_f64() * 1e3,
4372 sub_verify - sub_draft,
4373 (t_round.elapsed() - t_verify).as_secs_f64() * 1e3,
4374 end - sub_verify,
4375 );
4376 }
4377 Some((drafts[..a].to_vec(), next_pos + a + 1, new_hidden))
4378 }
4379
4380 pub fn measure_pair_fusion(&mut self, iters: usize) -> (f64, f64) {
4389 if !self.pair_supported() {
4390 return (0.0, 0.0);
4391 }
4392 let emb1 = self.embed_single(1);
4393 let emb2 = self.embed_single(2);
4394 let pos = self.kv_cache.seq_len();
4395
4396 let t0 = std::time::Instant::now();
4397 for _ in 0..iters {
4398 let _ = self.forward_layers(&emb1, pos, None);
4399 let _ = self.forward_layers(&emb2, pos + 1, None);
4400 for l in &mut self.kv_cache.layers {
4401 l.truncate_last(2);
4402 }
4403 }
4404 let singles_ms = t0.elapsed().as_secs_f64() * 1000.0 / iters as f64;
4405
4406 let t1 = std::time::Instant::now();
4407 for _ in 0..iters {
4408 let _ = self.forward_pair(&emb1, &emb2, pos);
4409 for l in &mut self.kv_cache.layers {
4410 l.truncate_last(2);
4411 }
4412 }
4413 let pair_ms = t1.elapsed().as_secs_f64() * 1000.0 / iters as f64;
4414 (singles_ms, pair_ms)
4415 }
4416
4417 fn pair_supported(&self) -> bool {
4425 !self.weights.layers.is_empty()
4432 && self.g3n.is_none()
4433 && !self
4434 .weights
4435 .layers
4436 .iter()
4437 .any(|lw| matches!(&lw.attn, AttnKind::Mla(_) | AttnKind::Kda(_)))
4438 }
4439
4440 fn forward_pair(
4441 &mut self,
4442 emb1: &[f32],
4443 emb2: &[f32],
4444 position: usize,
4445 ) -> (Vec<f32>, Vec<f32>) {
4446 let mut h1 = emb1.to_vec();
4447 let mut h2 = emb2.to_vec();
4448 let (_nkv, _hd, hs, _rd, eps) = (
4449 self.num_kv_heads,
4450 self.head_dim,
4451 self.hidden_size,
4452 self.rotary_dim,
4453 self.rms_eps,
4454 );
4455 let pool = self.pool.clone();
4456
4457 for li in 0..self.num_layers {
4458 let lw = &self.weights.layers[self.phys_layer(li)];
4459 inference::rms_norm_into(
4462 &h1,
4463 &lw.input_norm,
4464 self.rms_eps,
4465 self.norm_style,
4466 &mut self.ws.n1,
4467 );
4468 inference::rms_norm_into(
4469 &h2,
4470 &lw.input_norm,
4471 self.rms_eps,
4472 self.norm_style,
4473 &mut self.ws.n2,
4474 );
4475
4476 let (a1, a2) = match &lw.attn {
4477 AttnKind::Mla(_) => unreachable!("MLA has no MTP/pair path"),
4478 AttnKind::Kda(_) => unreachable!("KDA has no MTP/pair path"),
4479 AttnKind::Linear(w) => {
4480 let cfg = self.vmf_cfg.expect("linear layer without vmf_cfg");
4481 let layer = &mut self.kv_cache.layers[li];
4482 let (state, scratch) = (&mut layer.linear_state, &mut layer.linear_scratch);
4483 vmf_phase_pair(
4484 &self.ws.n1,
4485 &self.ws.n2,
4486 w,
4487 &cfg,
4488 state,
4489 scratch,
4490 self.pool.as_deref(),
4491 )
4492 }
4493 AttnKind::LinearGdn(w) => {
4494 let cfg = self.gdn_cfg.expect("gdn layer without gdn_cfg");
4495 let layer = &mut self.kv_cache.layers[li];
4496 let (state, scratch) = (&mut layer.linear_state, &mut layer.linear_scratch);
4497 gdn_pair(
4498 &self.ws.n1,
4499 &self.ws.n2,
4500 w,
4501 &cfg,
4502 state,
4503 scratch,
4504 self.pool.as_deref(),
4505 )
4506 }
4507 AttnKind::ShortConv(w) => {
4508 let cfg = self
4509 .short_conv_cfg
4510 .expect("short-conv layer without short_conv_cfg");
4511 let layer = &mut self.kv_cache.layers[li];
4512 let (state, scratch) = (&mut layer.linear_state, &mut layer.linear_scratch);
4513 short_conv_pair(
4514 &self.ws.n1,
4515 &self.ws.n2,
4516 w,
4517 &cfg,
4518 state,
4519 scratch,
4520 self.pool.as_deref(),
4521 )
4522 }
4523 AttnKind::Full {
4524 wq,
4525 wk,
4526 wv,
4527 wo,
4528 q_norm,
4529 k_norm,
4530 output_gate,
4531 softplus_gate,
4532 bias,
4533 } => {
4534 let inv_freq_l = self.layer_inv_freq(li);
4535 let (nkv_l, hd_l, rd_l) = self.layer_geom(li);
4536 let cfg = QwenAttnCfg {
4537 num_heads: self.layer_num_heads(li),
4538 num_kv_heads: nkv_l,
4539 head_dim: hd_l,
4540 hidden_size: hs,
4541 position,
4542 inv_freq: &inv_freq_l,
4543 rotary_dim: rd_l,
4544 scale: self.attn_scale,
4545 softcap: self.attn_softcap,
4546 window: self.layer_window(li),
4547 v_norm: self.attn_v_norm,
4548 q_norm: q_norm.as_deref(),
4549 k_norm: k_norm.as_deref(),
4550 output_gate: *output_gate,
4551 softplus_gate: softplus_gate
4552 .as_ref()
4553 .map(|(gate, per_head)| (gate, *per_head)),
4554 rope_scale: self.layer_rope_scale(li),
4555 bias: bias
4556 .as_ref()
4557 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
4558 rms_eps: eps,
4559 norm_style: self.norm_style,
4560 pool: pool.as_deref(),
4561 };
4562 attention::qwen_attention_pair(
4563 &self.ws.n1,
4564 &self.ws.n2,
4565 wq,
4566 wk,
4567 wv,
4568 wo,
4569 &mut self.kv_cache.layers[li],
4570 &cfg,
4571 )
4572 }
4573 };
4574 let (a1, a2) = match &self.weights.layers[self.phys_layer(li)].attn_out_norm {
4575 Some(w) => (
4576 inference::rms_norm(&a1, w, self.rms_eps, self.norm_style),
4577 inference::rms_norm(&a2, w, self.rms_eps, self.norm_style),
4578 ),
4579 None => (a1, a2),
4580 };
4581 for i in 0..self.hidden_size {
4582 h1[i] += a1[i];
4583 h2[i] += a2[i];
4584 }
4585 let (mut a1, mut a2) = (a1, a2);
4586 attention::recycle_buf(&mut a1);
4587 attention::recycle_buf(&mut a2);
4588
4589 let lw = &self.weights.layers[self.phys_layer(li)];
4590 inference::rms_norm_into(
4591 &h1,
4592 &lw.post_norm,
4593 self.rms_eps,
4594 self.norm_style,
4595 &mut self.ws.p1,
4596 );
4597 inference::rms_norm_into(
4598 &h2,
4599 &lw.post_norm,
4600 self.rms_eps,
4601 self.norm_style,
4602 &mut self.ws.p2,
4603 );
4604 let (f1, f2) = match &lw.ffn {
4605 FfnKind::DenseMoe(dm) => (
4608 dense_moe_ffn(
4609 dm,
4610 &self.ws.p1,
4611 &h1,
4612 self.rms_eps,
4613 self.norm_style,
4614 self.pool.as_deref(),
4615 ),
4616 dense_moe_ffn(
4617 dm,
4618 &self.ws.p2,
4619 &h2,
4620 self.rms_eps,
4621 self.norm_style,
4622 self.pool.as_deref(),
4623 ),
4624 ),
4625 _ => ffn_forward_pair(
4626 &lw.ffn,
4627 &self.ws.p1,
4628 &self.ws.p2,
4629 self.pool.as_deref(),
4630 None,
4631 ),
4632 };
4633 let (f1, f2) = match &self.weights.layers[self.phys_layer(li)].ffn_out_norm {
4634 Some(w) => (
4635 inference::rms_norm(&f1, w, self.rms_eps, self.norm_style),
4636 inference::rms_norm(&f2, w, self.rms_eps, self.norm_style),
4637 ),
4638 None => (f1, f2),
4639 };
4640 for i in 0..self.hidden_size {
4641 h1[i] += f1[i];
4642 h2[i] += f2[i];
4643 }
4644 let (mut f1, mut f2) = (f1, f2);
4645 attention::recycle_buf(&mut f1);
4646 attention::recycle_buf(&mut f2);
4647 if let Some(sc) = self.weights.layers[self.phys_layer(li)].layer_scale {
4648 for i in 0..self.hidden_size {
4649 h1[i] *= sc;
4650 h2[i] *= sc;
4651 }
4652 }
4653 if self.is_loop_end(li) && li + 1 < self.num_layers {
4655 h1 = inference::rms_norm(
4656 &h1,
4657 &self.weights.final_norm,
4658 self.rms_eps,
4659 self.norm_style,
4660 );
4661 h2 = inference::rms_norm(
4662 &h2,
4663 &self.weights.final_norm,
4664 self.rms_eps,
4665 self.norm_style,
4666 );
4667 }
4668 }
4669 (h1, h2)
4670 }
4671
4672 fn commit_linear_scratch(&mut self) {
4674 for layer in &mut self.kv_cache.layers {
4675 if !layer.linear_scratch.is_empty() {
4676 std::mem::swap(&mut layer.linear_state, &mut layer.linear_scratch);
4677 layer.linear_scratch.clear();
4678 }
4679 }
4680 }
4681
4682 pub fn forward_ids(
4685 &mut self,
4686 ids: &[u32],
4687 task_mask: Option<&TaskMask>,
4688 ) -> Result<Vec<f32>, String> {
4689 if ids.is_empty() {
4690 return Err("empty id sequence".to_string());
4691 }
4692 self.kv_cache.clear();
4693 self.kv_history.clear();
4694 self.o1_begin();
4695 let mut hidden = vec![0.0f32; self.hidden_size];
4696 let mut pos = 0usize;
4697 if self.can_prefill_batched() && !self.graph_prefill_preferred() && ids.len() > 2 {
4705 let chunk = prefill_chunk();
4709 let hs = self.hidden_size;
4710 while pos < ids.len() {
4711 let end = (pos + chunk).min(ids.len());
4712 let hb = self.prefill_batch_masked(&ids[pos..end], pos, task_mask);
4713 hidden.copy_from_slice(&hb[(end - pos - 1) * hs..]);
4714 pos = end;
4715 }
4716 }
4717 if task_mask.is_none()
4726 && !self.graph_prefill_preferred()
4727 && !std::env::var("CMF_PAIR").is_ok_and(|v| v == "0")
4728 && self.pair_supported()
4729 {
4730 while pos + 1 < ids.len() {
4731 let e1 = self.embed_single(ids[pos]);
4732 let e2 = self.embed_single(ids[pos + 1]);
4733 let (_, h2) = self.forward_pair(&e1, &e2, pos);
4734 self.commit_linear_scratch();
4735 hidden = h2;
4736 pos += 2;
4737 }
4738 }
4739 while pos < ids.len() {
4740 hidden = self.forward_layers(&self.embed_single(ids[pos]), pos, task_mask);
4741 pos += 1;
4742 }
4743 self.o1_seal();
4747 let normed = inference::rms_norm(
4748 &hidden,
4749 &self.weights.final_norm,
4750 self.rms_eps,
4751 self.norm_style,
4752 );
4753 Ok(self.lm_head_forward(&normed))
4754 }
4755
4756 pub fn ppl_ids(&mut self, ids: &[u32]) -> f64 {
4763 let (nll, cnt) = self.nll_ids_from(ids, 0);
4764 (nll / cnt.max(1) as f64).exp()
4765 }
4766
4767 pub fn probe_ffn_mass(&mut self, ids: &[u32]) -> Vec<Vec<f64>> {
4772 self.kv_cache.clear();
4773 self.kv_history.clear();
4774 FFN_PROBE.with(|p| {
4775 *p.borrow_mut() = Some(vec![vec![0f64; self.intermediate_size]; self.num_layers]);
4776 });
4777 crate::gpu::cpu_scope(|| {
4778 for (pos, &id) in ids.iter().enumerate() {
4779 let emb = self.embed_single(id);
4780 let _ = self.forward_layers(&emb, pos, None);
4781 }
4782 });
4783 self.kv_cache.clear();
4784 self.kv_history.clear();
4785 FFN_PROBE
4786 .with(|p| p.borrow_mut().take())
4787 .unwrap_or_default()
4788 }
4789
4790 pub fn probe_ffn_mass_batch(&mut self, ids: &[u32]) -> Vec<Vec<f64>> {
4794 self.kv_cache.clear();
4795 self.kv_history.clear();
4796 FFN_PROBE.with(|p| {
4797 *p.borrow_mut() = Some(vec![vec![0f64; self.intermediate_size]; self.num_layers]);
4798 });
4799 for chunk in ids.chunks(256) {
4800 if chunk.len() < 2 {
4801 continue;
4802 }
4803 let _ = self.nll_ids_masked(chunk, 0, None);
4804 }
4805 self.kv_cache.clear();
4806 self.kv_history.clear();
4807 FFN_PROBE
4808 .with(|p| p.borrow_mut().take())
4809 .unwrap_or_default()
4810 }
4811
4812 pub fn ppl_ids_masked(&mut self, ids: &[u32], mask: &TaskMask) -> f64 {
4816 self.kv_cache.clear();
4817 self.kv_history.clear();
4818 let mut nll = 0f64;
4819 let mut cnt = 0usize;
4820 let mut hidden = vec![0f32; self.hidden_size];
4821 for (pos, &id) in ids.iter().enumerate() {
4822 if pos > 0 {
4823 inference::rms_norm_into(
4824 &hidden,
4825 &self.weights.final_norm,
4826 self.rms_eps,
4827 self.norm_style,
4828 &mut self.ws.n1,
4829 );
4830 let mut logits = self.lm_head_forward(&self.ws.n1);
4831 let max = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
4832 let sum: f64 = logits.iter().map(|&v| ((v - max) as f64).exp()).sum();
4833 let p = ((logits[id as usize] - max) as f64).exp() / sum.max(1e-300);
4834 nll -= p.max(1e-300).ln();
4835 cnt += 1;
4836 attention::recycle_buf(&mut logits);
4837 }
4838 let emb = self.embed_single(id);
4839 hidden = self.forward_layers(&emb, pos, Some(mask));
4840 }
4841 self.kv_cache.clear();
4842 self.kv_history.clear();
4843 (nll / cnt.max(1) as f64).exp()
4844 }
4845
4846 pub fn nll_ids_masked(
4865 &mut self,
4866 ids: &[u32],
4867 start: usize,
4868 task_mask: Option<&TaskMask>,
4869 ) -> (f64, usize) {
4870 let task_mask = self.drop_open_mask(task_mask);
4871 self.nll_ids_inner(ids, start, task_mask)
4872 }
4873
4874 pub fn nll_ids_from(&mut self, ids: &[u32], start: usize) -> (f64, usize) {
4875 self.nll_ids_inner(ids, start, None)
4876 }
4877
4878 fn nll_ids_inner(
4879 &mut self,
4880 ids: &[u32],
4881 start: usize,
4882 task_mask: Option<&TaskMask>,
4883 ) -> (f64, usize) {
4884 self.kv_cache.clear();
4885 self.kv_history.clear();
4886 let mut nll = 0f64;
4887 let mut cnt = 0usize;
4888 if self.can_prefill_batched() {
4889 const CHUNK: usize = 128;
4895 const LM_SUB: usize = 32;
4896 let n = ids.len().saturating_sub(1);
4897 let hs = self.hidden_size;
4898 let rows = self.weights.lm_head.rows();
4899 let mut pos = 0usize;
4900 while pos < n {
4901 let end = (pos + CHUNK).min(n);
4902 let bsz = end - pos;
4903 let hb = self.prefill_batch_masked(&ids[pos..end], pos, task_mask);
4904 let mut k0 = 0usize;
4905 while k0 < bsz {
4906 let k1 = (k0 + LM_SUB).min(bsz);
4907 let sb = k1 - k0;
4908 if pos + k1 <= start {
4911 k0 = k1;
4912 continue;
4913 }
4914 let mut normed = vec![0.0f32; sb * hs];
4915 for k in 0..sb {
4916 let r = inference::rms_norm(
4917 &hb[(k0 + k) * hs..(k0 + k + 1) * hs],
4918 &self.weights.final_norm,
4919 self.rms_eps,
4920 self.norm_style,
4921 );
4922 normed[k * hs..(k + 1) * hs].copy_from_slice(&r);
4923 }
4924 let mut logits = vec![0.0f32; sb * rows];
4925 self.weights
4926 .lm_head
4927 .matmat(&normed, sb, &mut logits, self.pool.as_deref());
4928 for k in 0..sb {
4929 if pos + k0 + k < start {
4930 continue;
4931 }
4932 let lg = &mut logits[k * rows..k * rows + self.vocab_size.min(rows)];
4933 if let Some(mu) = self.logit_multiplier {
4934 for v in lg.iter_mut() {
4935 *v *= mu;
4936 }
4937 }
4938 if let Some(c) = self.final_softcap {
4942 for v in lg.iter_mut() {
4943 *v = c * (*v / c).tanh();
4944 }
4945 }
4946 if let Some(cm) = self.head_clusters.clone() {
4949 self.hierarchical_head_logprobs(&normed[k * hs..(k + 1) * hs], &cm, lg);
4950 }
4951 let lg = &logits[k * rows..k * rows + self.vocab_size.min(rows)];
4952 let target = ids[pos + k0 + k + 1] as usize;
4953 let max = lg.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
4954 let lse: f64 = lg
4955 .iter()
4956 .map(|&v| ((v - max) as f64).exp())
4957 .sum::<f64>()
4958 .ln()
4959 + max as f64;
4960 nll += lse - lg[target] as f64;
4961 cnt += 1;
4962 if std::env::var("CMF_PPL_TRACE").is_ok() {
4963 let top = lg
4964 .iter()
4965 .enumerate()
4966 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
4967 .map(|(i, _)| i)
4968 .unwrap_or(0);
4969 eprintln!(
4970 "BTRACE pos {} target {} nll {:.4} top {} lg_t {:.3} lg_top {:.3}",
4971 pos + k0 + k,
4972 target,
4973 lse - lg[target] as f64,
4974 top,
4975 lg[target],
4976 lg[top]
4977 );
4978 }
4979 }
4980 k0 = k1;
4981 }
4982 pos = end;
4983 }
4984 self.kv_cache.clear();
4985 self.kv_history.clear();
4986 return (nll, cnt);
4987 }
4988 for pos in 0..ids.len().saturating_sub(1) {
4989 let hidden = self.forward_layers(&self.embed_single(ids[pos]), pos, task_mask);
4990 let out_of_band = self.graph_logits.take();
4998 if pos < start {
4999 continue;
5000 }
5001 let logits = match out_of_band {
5002 Some(lg) => lg,
5003 None => {
5004 let normed = inference::rms_norm(
5005 &hidden,
5006 &self.weights.final_norm,
5007 self.rms_eps,
5008 self.norm_style,
5009 );
5010 self.lm_head_forward(&normed)
5014 }
5015 };
5016 let target = ids[pos + 1] as usize;
5017 let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
5018 let lse: f64 = logits
5019 .iter()
5020 .map(|&v| ((v - max) as f64).exp())
5021 .sum::<f64>()
5022 .ln()
5023 + max as f64;
5024 let tok_nll = lse - logits[target] as f64;
5025 if std::env::var("CMF_PPL_TRACE").is_ok() && pos < 48 {
5026 let top = logits
5027 .iter()
5028 .enumerate()
5029 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
5030 .map(|(i, _)| i)
5031 .unwrap_or(0);
5032 eprintln!(
5033 "pos {pos:3} tgt {target:6} nll {tok_nll:7.3} | top1 {top:6} lg[t]={:.2} lg[top]={:.2}",
5034 logits[target], logits[top]
5035 );
5036 }
5037 nll += tok_nll;
5038 cnt += 1;
5039 }
5040 self.kv_cache.clear();
5041 self.kv_history.clear();
5042 (nll, cnt)
5043 }
5044
5045 pub fn nll_ids_o1(&mut self, ids: &[u32], prefill: usize) -> (f64, usize) {
5061 self.kv_cache.clear();
5062 self.kv_history.clear();
5063 self.o1_begin();
5064 let n = ids.len().saturating_sub(1);
5065 let p = prefill.min(n);
5066 let mut pos = 0usize;
5068 if self.can_prefill_batched() {
5069 const CHUNK: usize = 128;
5070 while pos < p {
5071 let end = (pos + CHUNK).min(p);
5072 let _ = self.prefill_batch(&ids[pos..end], pos);
5073 pos = end;
5074 }
5075 } else {
5076 while pos < p {
5077 let _ = self.forward_layers(&self.embed_single(ids[pos]), pos, None);
5078 pos += 1;
5079 }
5080 }
5081 self.o1_seal();
5082
5083 let mut nll = 0f64;
5084 let mut cnt = 0usize;
5085 for pos in p..n {
5086 let hidden = self.forward_layers(&self.embed_single(ids[pos]), pos, None);
5087 let normed = inference::rms_norm(
5088 &hidden,
5089 &self.weights.final_norm,
5090 self.rms_eps,
5091 self.norm_style,
5092 );
5093 let logits = self.lm_head_forward(&normed);
5097 let target = ids[pos + 1] as usize;
5098 let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
5099 let lse: f64 = logits
5100 .iter()
5101 .map(|&v| ((v - max) as f64).exp())
5102 .sum::<f64>()
5103 .ln()
5104 + max as f64;
5105 let tok_nll = lse - logits[target] as f64;
5106 if std::env::var("CMF_PPL_TRACE").is_ok() && pos < 48 {
5107 let top = logits
5108 .iter()
5109 .enumerate()
5110 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
5111 .map(|(i, _)| i)
5112 .unwrap_or(0);
5113 eprintln!(
5114 "pos {pos:3} tgt {target:6} nll {tok_nll:7.3} | top1 {top:6} lg[t]={:.2} lg[top]={:.2}",
5115 logits[target], logits[top]
5116 );
5117 }
5118 nll += tok_nll;
5119 cnt += 1;
5120 }
5121 self.kv_cache.clear();
5122 self.kv_history.clear();
5123 (nll, cnt)
5124 }
5125
5126 pub fn calib_ids(&mut self, ids: &[u32], temps: &[f32]) -> (Vec<bool>, Vec<Vec<f32>>) {
5134 self.kv_cache.clear();
5135 self.kv_history.clear();
5136 let n = ids.len().saturating_sub(1);
5137 let mut correct = Vec::with_capacity(n);
5138 let mut pmax = Vec::with_capacity(n);
5139 for pos in 0..n {
5140 let emb = self.embed_single(ids[pos]);
5141 let hidden = self.forward_layers(&emb, pos, None);
5142 let normed = inference::rms_norm(
5143 &hidden,
5144 &self.weights.final_norm,
5145 self.rms_eps,
5146 self.norm_style,
5147 );
5148 let logits = self.lm_head_forward(&normed);
5152 let target = ids[pos + 1] as usize;
5153 let (mut amax, mut mval) = (0usize, f32::NEG_INFINITY);
5154 for (i, &v) in logits.iter().enumerate() {
5155 if v > mval {
5156 mval = v;
5157 amax = i;
5158 }
5159 }
5160 correct.push(amax == target);
5161 let row: Vec<f32> = temps
5162 .iter()
5163 .map(|&t| {
5164 let tt = t.max(1e-3);
5165 let s: f32 = logits.iter().map(|&v| ((v - mval) / tt).exp()).sum();
5166 1.0 / s.max(1e-12) })
5168 .collect();
5169 pmax.push(row);
5170 }
5171 self.kv_cache.clear();
5172 self.kv_history.clear();
5173 (correct, pmax)
5174 }
5175
5176 pub fn ppl_ids_dynamic(&mut self, ids: &[u32]) -> (f64, usize) {
5183 let mut router = match self.dyn_router.take() {
5184 Some(r) => r,
5185 None => return (self.ppl_ids(ids), 0),
5186 };
5187 router.reset();
5188 self.dyn_phi_seen = 0;
5189 let _ = self.set_active_skill(None);
5190
5191 self.kv_cache.clear();
5192
5193 self.kv_history.clear();
5194 let mut nll = 0f64;
5195 let mut cnt = 0usize;
5196 for pos in 0..ids.len().saturating_sub(1) {
5197 let hidden = self.forward_layers(&self.embed_single(ids[pos]), pos, None);
5198 let normed = inference::rms_norm(
5199 &hidden,
5200 &self.weights.final_norm,
5201 self.rms_eps,
5202 self.norm_style,
5203 );
5204 let logits = self.lm_head_forward(&normed);
5208 let target = ids[pos + 1] as usize;
5209 let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
5210 let lse: f64 = logits
5211 .iter()
5212 .map(|&v| ((v - max) as f64).exp())
5213 .sum::<f64>()
5214 .ln()
5215 + max as f64;
5216 let tok_nll = lse - logits[target] as f64;
5217 if std::env::var("CMF_PPL_TRACE").is_ok() && pos < 48 {
5218 let top = logits
5219 .iter()
5220 .enumerate()
5221 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
5222 .map(|(i, _)| i)
5223 .unwrap_or(0);
5224 eprintln!(
5225 "pos {pos:3} tgt {target:6} nll {tok_nll:7.3} | top1 {top:6} lg[t]={:.2} lg[top]={:.2}",
5226 logits[target], logits[top]
5227 );
5228 }
5229 nll += tok_nll;
5230 cnt += 1;
5231 let phi = self.dyn_phi_ema.clone();
5233 if let Some(new_active) = router.step(&phi, pos) {
5234 let _ = self.set_active_skill(new_active);
5235 }
5236 }
5237 let switches = router.switches.len();
5238 let _ = self.set_active_skill(None);
5239 self.dyn_router = Some(router);
5240 self.kv_cache.clear();
5241 self.kv_history.clear();
5242 ((nll / cnt.max(1) as f64).exp(), switches)
5243 }
5244
5245 pub fn probe_phi(&mut self, ids: &[u32], layer: usize) -> Vec<f32> {
5247 self.kv_cache.clear();
5248 self.kv_history.clear();
5249 let mut acc = vec![0f32; self.hidden_size];
5250 for (pos, &id) in ids.iter().enumerate() {
5251 let h = self.forward_layers_upto(&self.embed_single(id), pos, None, Some(layer));
5252 for (a, v) in acc.iter_mut().zip(&h) {
5253 *a += v;
5254 }
5255 }
5256 let n = ids.len().max(1) as f32;
5257 for a in acc.iter_mut() {
5258 *a /= n;
5259 }
5260 self.kv_cache.clear();
5261 self.kv_history.clear();
5262 acc
5263 }
5264
5265 fn prefill_batch(&mut self, ids: &[u32], start_pos: usize) -> Vec<f32> {
5271 self.prefill_batch_masked(ids, start_pos, None)
5272 }
5273
5274 fn prefill_batch_masked(
5280 &mut self,
5281 ids: &[u32],
5282 start_pos: usize,
5283 task_mask: Option<&TaskMask>,
5284 ) -> Vec<f32> {
5285 self.prefill_batch_span(PrefillIn::Ids(ids), start_pos, task_mask, 0, usize::MAX)
5286 }
5287
5288 fn prefill_batch_span(
5294 &mut self,
5295 input: PrefillIn<'_>,
5296 start_pos: usize,
5297 task_mask: Option<&TaskMask>,
5298 from: usize,
5299 upto_excl: usize,
5300 ) -> Vec<f32> {
5301 let hs = self.hidden_size;
5302 let b = match input {
5303 PrefillIn::Ids(ids) => ids.len(),
5304 PrefillIn::Hidden(hb) => hb.len() / hs,
5305 };
5306 let upto_excl = upto_excl.min(self.num_layers);
5307 let mut h: Vec<f32>;
5311 let mut h_ready;
5312 match input {
5313 PrefillIn::Ids(_) => {
5314 h = vec![0.0; b * hs];
5315 h_ready = false;
5316 }
5317 PrefillIn::Hidden(hb) => {
5318 h = hb.to_vec();
5319 h_ready = true;
5320 }
5321 }
5322 let fill_h = |h: &mut Vec<f32>, me: &Self| {
5323 if let PrefillIn::Ids(ids) = input {
5324 for (bi, &id) in ids.iter().enumerate() {
5325 let e = me.embed_single(id);
5326 h[bi * hs..(bi + 1) * hs].copy_from_slice(&e);
5327 }
5328 if let Ok(tp) = std::env::var("CMF_TRACE_POS") {
5329 if let Ok(t) = tp.parse::<usize>() {
5330 if t >= start_pos && t < start_pos + ids.len() {
5331 let bi = t - start_pos;
5332 let row = &h[bi * hs..(bi + 1) * hs];
5333 let n: f32 = row.iter().map(|x| x * x).sum::<f32>().sqrt();
5334 eprintln!(
5335 "BATCH pos {t} embed: id {} |h| = {n:.6} h0 {:.6} h1 {:.6} | b={} start={start_pos} ids[..8]={:?}",
5336 ids[bi],
5337 row[0],
5338 row[1],
5339 ids.len(),
5340 &ids[..ids.len().min(8)]
5341 );
5342 }
5343 }
5344 }
5345 }
5346 };
5347 let (_nkv, _hd, _rd, eps) = (
5348 self.num_kv_heads,
5349 self.head_dim,
5350 self.rotary_dim,
5351 self.rms_eps,
5352 );
5353 let pool = self.pool.clone();
5354 let norm_style = self.norm_style;
5355 let automatic_gpu_prefix = self.automatic_gpu_prefix();
5356
5357 #[cfg(target_os = "macos")]
5358 let mut chunk_skip_until = 0usize;
5359 for li in from..upto_excl {
5360 let _capacity_tail = automatic_gpu_prefix
5361 .filter(|&prefix| li >= prefix)
5362 .map(|_| crate::gpu::enter_cpu_scope());
5363 crate::gpu::set_layer(li as i64); #[cfg(target_os = "macos")]
5370 if task_mask.is_none() {
5371 if li < chunk_skip_until {
5372 continue;
5373 }
5374 if !h_ready && li == 0 && self.weights.embed_tokens.q8_row_parts().is_none() {
5380 fill_h(&mut h, self);
5381 h_ready = true;
5382 }
5383 let ids_for_embed = match input {
5384 PrefillIn::Ids(ids) => (!h_ready && li == 0).then_some(ids),
5385 PrefillIn::Hidden(_) => None,
5386 };
5387 let end = self.chunk_run_gpu(li, &mut h, b, start_pos, ids_for_embed, upto_excl);
5388 if end > li {
5389 h_ready = true;
5390 chunk_skip_until = end;
5391 if self.is_loop_end(end - 1) && end < self.num_layers {
5394 for bi in 0..b {
5395 let normed = inference::rms_norm(
5396 &h[bi * hs..(bi + 1) * hs],
5397 &self.weights.final_norm,
5398 eps,
5399 norm_style,
5400 );
5401 h[bi * hs..(bi + 1) * hs].copy_from_slice(&normed);
5402 }
5403 }
5404 continue;
5405 }
5406 }
5407 if !h_ready {
5408 fill_h(&mut h, self);
5409 h_ready = true;
5410 }
5411 let lw = &self.weights.layers[self.phys_layer(li)];
5412 match &lw.attn {
5414 AttnKind::Kda(w) => {
5415 let cfg = self.kda_cfg.expect("kda layer without kda_cfg");
5417 let mut normed = vec![0.0f32; b * hs];
5418 for bi in 0..b {
5419 inference::rms_norm_into(
5420 &h[bi * hs..(bi + 1) * hs],
5421 &lw.input_norm,
5422 eps,
5423 norm_style,
5424 &mut normed[bi * hs..(bi + 1) * hs],
5425 );
5426 }
5427 let attn = crate::linear_core::kda_forward_batch(
5428 &normed,
5429 b,
5430 w,
5431 &cfg,
5432 &mut self.kv_cache.layers[li].linear_state,
5433 pool.as_deref(),
5434 );
5435 for (dst, &a) in h.iter_mut().zip(&attn) {
5436 *dst += a;
5437 }
5438 }
5439 AttnKind::LinearGdn(w) => {
5440 let cfg = self.gdn_cfg.expect("gdn layer without gdn_cfg");
5442 let mut normed = vec![0.0f32; b * hs];
5443 for bi in 0..b {
5444 let r = inference::rms_norm(
5445 &h[bi * hs..(bi + 1) * hs],
5446 &lw.input_norm,
5447 eps,
5448 norm_style,
5449 );
5450 normed[bi * hs..(bi + 1) * hs].copy_from_slice(&r);
5451 }
5452 let attn = crate::linear_core::gdn_forward_batch(
5453 &normed,
5454 b,
5455 w,
5456 &cfg,
5457 &mut self.kv_cache.layers[li].linear_state,
5458 pool.as_deref(),
5459 );
5460 for (dst, &a) in h.iter_mut().zip(&attn) {
5461 *dst += a;
5462 }
5463 }
5464 AttnKind::ShortConv(w) => {
5465 let cfg = self
5468 .short_conv_cfg
5469 .expect("short-conv layer without short_conv_cfg");
5470 let mut normed = vec![0.0f32; b * hs];
5471 for bi in 0..b {
5472 inference::rms_norm_into(
5473 &h[bi * hs..(bi + 1) * hs],
5474 &lw.input_norm,
5475 eps,
5476 norm_style,
5477 &mut normed[bi * hs..(bi + 1) * hs],
5478 );
5479 }
5480 let attn = short_conv_forward_batch(
5481 &normed,
5482 b,
5483 w,
5484 &cfg,
5485 &mut self.kv_cache.layers[li].linear_state,
5486 pool.as_deref(),
5487 );
5488 for (dst, &a) in h.iter_mut().zip(&attn) {
5489 *dst += a;
5490 }
5491 }
5492 AttnKind::Mla(w) => {
5493 let inv_freq_l = self.layer_inv_freq(li);
5496 let rs = self.layer_rope_scale(li);
5497 let mut normed = vec![0.0f32; hs];
5498 for bi in 0..b {
5499 inference::rms_norm_into(
5500 &h[bi * hs..(bi + 1) * hs],
5501 &lw.input_norm,
5502 eps,
5503 norm_style,
5504 &mut normed,
5505 );
5506 let ao = mla_attention(
5507 w,
5508 &normed,
5509 &mut self.kv_cache.layers[li],
5510 start_pos + bi,
5511 &inv_freq_l,
5512 rs,
5513 eps,
5514 pool.as_deref(),
5515 );
5516 for (dst, &a) in h[bi * hs..(bi + 1) * hs].iter_mut().zip(&ao) {
5517 *dst += a;
5518 }
5519 }
5520 }
5521 AttnKind::Full {
5522 wq,
5523 wk,
5524 wv,
5525 wo,
5526 q_norm,
5527 k_norm,
5528 output_gate,
5529 softplus_gate,
5530 bias,
5531 } => {
5532 let mut normed = vec![0.0f32; b * hs];
5536 for bi in 0..b {
5537 inference::rms_norm_into(
5538 &h[bi * hs..(bi + 1) * hs],
5539 &lw.input_norm,
5540 eps,
5541 norm_style,
5542 &mut normed[bi * hs..(bi + 1) * hs],
5543 );
5544 }
5545 let inv_freq_l = self.layer_inv_freq(li);
5546 let (nkv_l, hd_l, rd_l) = self.layer_geom(li);
5547 let cfg = QwenAttnCfg {
5548 num_heads: self.layer_num_heads(li),
5549 num_kv_heads: nkv_l,
5550 head_dim: hd_l,
5551 hidden_size: hs,
5552 position: start_pos,
5553 inv_freq: &inv_freq_l,
5554 rotary_dim: rd_l,
5555 scale: self.attn_scale,
5556 softcap: self.attn_softcap,
5557 window: self.layer_window(li),
5558 v_norm: self.attn_v_norm,
5559 q_norm: q_norm.as_deref(),
5560 k_norm: k_norm.as_deref(),
5561 output_gate: *output_gate,
5562 softplus_gate: softplus_gate
5563 .as_ref()
5564 .map(|(gate, per_head)| (gate, *per_head)),
5565 rope_scale: self.layer_rope_scale(li),
5566 bias: bias
5567 .as_ref()
5568 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
5569 rms_eps: eps,
5570 norm_style,
5571 pool: pool.as_deref(),
5572 };
5573 let mut attn = attention::qwen_attention_batch(
5574 &normed,
5575 b,
5576 wq,
5577 wk,
5578 wv,
5579 wo,
5580 &mut self.kv_cache.layers[li],
5581 &cfg,
5582 );
5583 if let Some(w) = &lw.attn_out_norm {
5584 for bi in 0..b {
5585 inference::rms_norm_into(
5586 &attn[bi * hs..(bi + 1) * hs],
5587 w,
5588 eps,
5589 norm_style,
5590 &mut normed[bi * hs..(bi + 1) * hs],
5591 );
5592 }
5593 attn.copy_from_slice(&normed);
5594 }
5595 for (dst, &a) in h.iter_mut().zip(&attn) {
5596 *dst += a;
5597 }
5598 }
5599 AttnKind::Linear(w) => {
5600 for bi in 0..b {
5601 let normed = inference::rms_norm(
5602 &h[bi * hs..(bi + 1) * hs],
5603 &lw.input_norm,
5604 eps,
5605 norm_style,
5606 );
5607 vmf_phase_forward(
5608 &normed,
5609 w,
5610 &self.vmf_cfg.expect("linear layer without vmf_cfg"),
5611 &mut self.kv_cache.layers[li].linear_state,
5612 pool.as_deref(),
5613 )
5614 .iter()
5615 .enumerate()
5616 .for_each(|(i, &a)| h[bi * hs + i] += a);
5617 }
5618 }
5619 }
5620
5621 let lw = &self.weights.layers[self.phys_layer(li)];
5623 let mut post = vec![0.0f32; b * hs];
5624 for bi in 0..b {
5625 let r =
5626 inference::rms_norm(&h[bi * hs..(bi + 1) * hs], &lw.post_norm, eps, norm_style);
5627 post[bi * hs..(bi + 1) * hs].copy_from_slice(&r);
5628 }
5629 let mask_row = task_mask
5632 .filter(|m| m.ffn_active_count(li) < self.intermediate_size)
5633 .and_then(|m| m.ffn_masks.get(li))
5634 .map(|v| v.as_slice());
5635 let mut ffn = match &lw.ffn {
5636 FfnKind::Dense(d) if !d.segs.is_empty() => {
5637 tube_ffn(d, &post, b, pool.as_deref(), mask_row)
5638 }
5639 FfnKind::Dense(d) => dense_ffn_batch(d, &post, b, pool.as_deref(), mask_row),
5640 FfnKind::Moe(m) => moe_ffn_batch(m, &post, b, hs, pool.as_deref(), None),
5641 FfnKind::DenseMoe(dm) => {
5644 let mut out = vec![0.0f32; b * hs];
5645 for bi in 0..b {
5646 let r = dense_moe_ffn(
5647 dm,
5648 &post[bi * hs..(bi + 1) * hs],
5649 &h[bi * hs..(bi + 1) * hs],
5650 eps,
5651 norm_style,
5652 pool.as_deref(),
5653 );
5654 out[bi * hs..(bi + 1) * hs].copy_from_slice(&r);
5655 }
5656 out
5657 }
5658 };
5659 if let Some(w) = &lw.ffn_out_norm {
5660 for bi in 0..b {
5661 inference::rms_norm_into(
5662 &ffn[bi * hs..(bi + 1) * hs],
5663 w,
5664 eps,
5665 norm_style,
5666 &mut post[bi * hs..(bi + 1) * hs],
5667 );
5668 }
5669 ffn.copy_from_slice(&post);
5670 }
5671 for (dst, &f) in h.iter_mut().zip(&ffn) {
5672 *dst += f;
5673 }
5674 if let Some(sc) = lw.layer_scale {
5675 for v in h.iter_mut() {
5676 *v *= sc;
5677 }
5678 }
5679 if let Ok(tp) = std::env::var("CMF_TRACE_POS") {
5680 if let Ok(t) = tp.parse::<usize>() {
5681 if t >= start_pos && t < start_pos + b {
5682 let bi = t - start_pos;
5683 let row = &h[bi * hs..(bi + 1) * hs];
5684 let n: f32 = row.iter().map(|x| x * x).sum::<f32>().sqrt();
5685 eprintln!(
5686 "BATCH pos {t} after layer {li}: |h| = {n:.6} h0 {:.6} h1 {:.6}",
5687 row[0], row[1]
5688 );
5689 }
5690 }
5691 }
5692 if std::env::var("CMF_DEBUG_LAYERS").is_ok() {
5696 let row = &h[(b - 1) * hs..b * hs];
5697 let rms =
5698 (row.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / hs as f64).sqrt();
5699 let mx = row.iter().fold(0f32, |m, &v| m.max(v.abs()));
5700 eprintln!(
5701 "layer {li:>3} {:>10} ffn={:<5} rms={rms:>12.4} max={mx:>12.4}",
5702 match &self.weights.layers[self.phys_layer(li)].attn {
5703 AttnKind::LinearGdn(_) => "gdn",
5704 AttnKind::Linear(_) => "vmf",
5705 AttnKind::ShortConv(_) => "conv",
5706 _ => "attn",
5707 },
5708 match &lw.ffn {
5709 FfnKind::Moe(_) => "moe",
5710 FfnKind::Dense(_) => "dense",
5711 FfnKind::DenseMoe(_) => "dense+moe",
5712 },
5713 );
5714 }
5715 if self.is_loop_end(li) && li + 1 < self.num_layers {
5717 for bi in 0..b {
5718 let normed = inference::rms_norm(
5719 &h[bi * hs..(bi + 1) * hs],
5720 &self.weights.final_norm,
5721 eps,
5722 norm_style,
5723 );
5724 h[bi * hs..(bi + 1) * hs].copy_from_slice(&normed);
5725 }
5726 }
5727 if std::env::var("CMF_TRACE_H").is_ok() {
5728 let n = h[..hs].iter().map(|v| v.abs()).sum::<f32>() / hs as f32;
5729 let mx = h[..hs].iter().fold(0.0f32, |a, &v| a.max(v.abs()));
5730 eprintln!(
5731 "layer {li}: mean|h|={n:.4} max|h|={mx:.2} scale={:?}",
5732 lw.layer_scale
5733 );
5734 }
5735 }
5736 crate::gpu::set_layer(-1); h
5738 }
5739
5740 fn embed_single(&self, id: u32) -> Vec<f32> {
5742 let mut out = vec![0.0f32; self.hidden_size];
5743 if (id as usize) < self.weights.embed_tokens.rows() {
5744 self.weights.embed_tokens.row_f32(id as usize, &mut out);
5745 }
5746 if self.embed_multiplier != 1.0 {
5747 for v in out.iter_mut() {
5748 *v *= self.embed_multiplier;
5749 }
5750 }
5751 if self.dsv4.is_some() || self.qwen4_exp.is_some() {
5755 let mut v = vec![0.0f32; self.hidden_size.max(1)];
5756 v[0] = id as f32;
5757 return v;
5758 }
5759 if let Some(b) = &self.g3n {
5762 return b.0.extend_embedding(id, &out, self.pool.as_deref());
5763 }
5764 out
5765 }
5766
5767 #[cfg(target_os = "macos")]
5773 fn chunk_run_gpu(
5774 &mut self,
5775 li0: usize,
5776 h: &mut [f32],
5777 b: usize,
5778 pos0: usize,
5779 embed_ids: Option<&[u32]>,
5780 cap: usize,
5781 ) -> usize {
5782 if !crate::gpu::enabled_here()
5786 || std::env::var("CMF_GPU_CHUNK")
5787 .map(|v| v == "0")
5788 .unwrap_or(false)
5789 || b < 32
5790 || self.swa.is_some()
5791 || self.global_attn.is_some()
5792 || self.attn_v_norm
5793 || (self.attn_scale - 1.0 / (self.head_dim as f32).sqrt()).abs() > 1e-9
5794 {
5795 return li0;
5796 }
5797 let Some(model) = self.model.clone() else {
5798 return li0;
5799 };
5800 let inv_freq = self.inv_freq.clone();
5801 let (nh, nkv, hd, hs) = (
5802 self.num_heads,
5803 self.num_kv_heads,
5804 self.head_dim,
5805 self.hidden_size,
5806 );
5807 let loop_end = if self.loop_final_norm {
5811 ((li0 / self.physical_layers) + 1) * self.physical_layers
5812 } else {
5813 self.num_layers
5814 };
5815 let mut layers: Vec<crate::gpu_metal::ChunkLayer> = Vec::new();
5816 let mut stored_at: Vec<usize> = Vec::new();
5817 for li in li0..self.num_layers.min(loop_end).min(cap) {
5818 let lw = &self.weights.layers[self.phys_layer(li)];
5819 if lw.attn_out_norm.is_some() || lw.ffn_out_norm.is_some() || lw.layer_scale.is_some() {
5820 break;
5821 }
5822 let AttnKind::Full {
5823 wq,
5824 wk,
5825 wv,
5826 wo,
5827 q_norm,
5828 k_norm,
5829 output_gate: false,
5830 softplus_gate: None,
5831 bias,
5832 } = &lw.attn
5833 else {
5834 break;
5835 };
5836 let FfnKind::Dense(d) = &lw.ffn else { break };
5837 if d.act != Act::Silu || !d.segs.is_empty() {
5838 break;
5839 }
5840 fn cw(t: &QTensor) -> Option<(usize, usize, usize, &[f32])> {
5845 t.q8_row_parts()
5846 .or_else(|| t.q4t_parts().map(|(i, r, c)| (i, r, c, &[][..])))
5847 .or_else(|| t.q4tp_parts().map(|(i, r, c)| (i, r, c, &[][..])))
5848 }
5849 let parts = (
5850 cw(wq),
5851 cw(wk),
5852 cw(wv),
5853 cw(wo),
5854 cw(&d.gate_proj),
5855 cw(&d.up_proj),
5856 cw(&d.down_proj),
5857 );
5858 let (Some(pq), Some(pk), Some(pv), Some(po), Some(pg), Some(pu), Some(pd)) = parts
5859 else {
5860 break;
5861 };
5862 let layer = &self.kv_cache.layers[li];
5863 if layer.mode != crate::kv_cache::KvMode::F32 || layer.o1.is_some() {
5864 break;
5865 }
5866 stored_at.push(layer.head_len(0));
5867 layers.push(crate::gpu_metal::ChunkLayer {
5868 model: &model,
5869 kv_id: self.graph_kv_id,
5870 layer: li,
5871 wq: pq,
5872 wk: pk,
5873 wv: pv,
5874 wo: po,
5875 gate: pg,
5876 up: pu,
5877 down: pd,
5878 input_norm: &lw.input_norm,
5879 post_norm: &lw.post_norm,
5880 bias: bias
5881 .as_ref()
5882 .map(|(a, bb, cc)| (a.as_slice(), bb.as_slice(), cc.as_slice())),
5883 q_norm: q_norm.as_deref(),
5884 k_norm: k_norm.as_deref(),
5885 inv_freq: &inv_freq,
5886 rd: self.rotary_dim,
5887 nh,
5888 nkv,
5889 hd,
5890 hs,
5891 inter: d.gate_proj.rows(),
5892 gemma: matches!(self.norm_style, cortiq_core::NormStyle::Gemma),
5893 eps: self.rms_eps as f32,
5894 });
5895 }
5896 if layers.is_empty() {
5897 return li0;
5898 }
5899 let row = nkv * hd;
5900 let mut store: Vec<(Vec<f32>, Vec<f32>, Vec<f32>)> = stored_at
5901 .iter()
5902 .map(|&st| (vec![0f32; b * row], vec![0f32; b * row], vec![0f32; st + b]))
5903 .collect();
5904 let mut io: Vec<crate::gpu_metal::ChunkIo> = Vec::with_capacity(layers.len());
5905 for (i, (ok, ov, oi)) in store.iter_mut().enumerate() {
5906 let li = layers[i].layer;
5907 let layer = &self.kv_cache.layers[li];
5908 io.push(crate::gpu_metal::ChunkIo {
5909 cpu_stored: stored_at[i],
5910 cpu_k: (0..nkv).map(|g| layer.head_keys(g)).collect(),
5911 cpu_v: (0..nkv).map(|g| layer.head_values(g)).collect(),
5912 out_k: ok,
5913 out_v: ov,
5914 imp: oi,
5915 });
5916 }
5917 let n_run = layers.len();
5918 let last = layers.last().map(|l| l.layer + 1).unwrap_or(li0);
5919 let ep = embed_ids.and_then(|ids| {
5922 self.weights
5923 .embed_tokens
5924 .q8_row_parts()
5925 .map(|(idx, rows, _c, rs)| crate::gpu_metal::ChunkEmbed {
5926 idx,
5927 rows,
5928 row_scale: rs,
5929 ids,
5930 mult: self.embed_multiplier,
5931 })
5932 });
5933 if embed_ids.is_some() && ep.is_none() {
5934 return li0;
5935 }
5936 if !crate::gpu_metal::chunk_run_gpu(&layers, &mut io, h, b, pos0, ep.as_ref()) {
5937 return li0;
5938 }
5939 drop(io);
5940 drop(layers);
5941 for (i, (ok, ov, oi)) in store.iter().enumerate().take(n_run) {
5944 let li = li0 + i;
5945 let layer = &mut self.kv_cache.layers[li];
5946 for bi in 0..b {
5947 layer.append(
5948 &ok[bi * row..(bi + 1) * row],
5949 &ov[bi * row..(bi + 1) * row],
5950 &[],
5951 );
5952 }
5953 layer.accumulate_imp(oi);
5954 }
5955 last
5956 }
5957
5958 fn layer_is_local(&self, li: usize) -> bool {
5961 if let Some(layers) = &self.sliding_layers {
5962 return layers.get(li).copied().unwrap_or(false);
5963 }
5964 match self.swa {
5965 Some((_, pattern)) => (li + 1) % pattern.max(1) != 0,
5966 None => false,
5967 }
5968 }
5969
5970 fn layer_inv_freq(&self, li: usize) -> std::sync::Arc<Vec<f32>> {
5973 if self.layer_is_local(li) {
5974 if let Some(f) = &self.inv_freq_local {
5975 return f.clone();
5976 }
5977 } else if let Some(f) = &self.inv_freq_global {
5978 return f.clone();
5979 }
5980 self.inv_freq.clone()
5981 }
5982
5983 fn layer_window(&self, li: usize) -> Option<usize> {
5985 self.swa
5986 .and_then(|(w, _)| self.layer_is_local(li).then_some(w))
5987 }
5988
5989 fn layer_num_heads(&self, li: usize) -> usize {
5990 self.attention_heads_per_layer
5991 .as_ref()
5992 .and_then(|v| v.get(li).copied())
5993 .unwrap_or(self.num_heads)
5994 }
5995
5996 fn layer_rope_scale(&self, li: usize) -> f32 {
5997 if self.layer_is_local(li) {
5998 self.rope_scale_local
5999 } else {
6000 self.rope_scale
6001 }
6002 }
6003
6004 fn layer_geom(&self, li: usize) -> (usize, usize, usize) {
6007 if !self.layer_is_local(li) {
6008 if let Some((ghd, gkv)) = self.global_attn {
6009 return (gkv, ghd, ghd);
6010 }
6011 }
6012 (
6013 self.num_kv_heads,
6014 self.head_dim,
6015 if self.layer_is_local(li) {
6016 self.rotary_dim_local.unwrap_or(self.rotary_dim)
6017 } else {
6018 self.rotary_dim
6019 },
6020 )
6021 }
6022
6023 fn forward_layers(
6025 &mut self,
6026 hidden: &[f32],
6027 position: usize,
6028 task_mask: Option<&TaskMask>,
6029 ) -> Vec<f32> {
6030 self.forward_layers_upto(hidden, position, task_mask, None)
6031 }
6032
6033 pub fn embed_id(&self, id: u32) -> Vec<f32> {
6041 self.embed_single(id)
6042 }
6043
6044 pub fn split_supported(&self) -> Result<(), String> {
6048 if self.dsv4.is_some() {
6049 return Err(
6050 "network split: DeepSeek-V4 runs its own fused stack (not splittable yet)".into(),
6051 );
6052 }
6053 if self.qwen4_exp.is_some() {
6054 return Err(
6055 "network split: Qwen3.8-Flash-Next hyper/QSA stack is not splittable yet".into(),
6056 );
6057 }
6058 if self.g3n.is_some() {
6059 return Err(
6060 "network split: Gemma-3n runs its own AltUp stack (not splittable yet)".into(),
6061 );
6062 }
6063 Ok(())
6064 }
6065
6066 pub fn forward_span(
6071 &mut self,
6072 hidden: &[f32],
6073 position: usize,
6074 from: usize,
6075 upto: usize,
6076 task_mask: Option<&TaskMask>,
6077 ) -> Result<Vec<f32>, String> {
6078 self.split_supported()?;
6079 if from > upto || upto >= self.num_layers {
6080 return Err(format!(
6081 "forward_span: layer range {from}..={upto} outside 0..{}",
6082 self.num_layers
6083 ));
6084 }
6085 if hidden.len() != self.hidden_size {
6086 return Err(format!(
6087 "forward_span: hidden len {} ≠ hidden_size {}",
6088 hidden.len(),
6089 self.hidden_size
6090 ));
6091 }
6092 Ok(self.forward_layers_span(hidden, position, task_mask, from, Some(upto)))
6093 }
6094
6095 pub fn logits_from_hidden(&mut self, hidden: &[f32]) -> Vec<f32> {
6098 let normed = inference::rms_norm(
6099 hidden,
6100 &self.weights.final_norm,
6101 self.rms_eps,
6102 self.norm_style,
6103 );
6104 self.lm_head_forward(&normed)
6105 }
6106
6107 pub fn sample_next(&mut self, logits: &[f32], past_tokens: &[u32]) -> u32 {
6109 sampler::sample_with_scratch(
6110 logits,
6111 &self.sampler_config,
6112 past_tokens,
6113 &mut self.rng,
6114 &mut self.sampler_scratch,
6115 )
6116 }
6117
6118 pub fn reset_session(&mut self) {
6120 self.kv_cache.clear();
6121 self.kv_history.clear();
6122 crate::gpu::graph_kv_reset(self.graph_kv_id);
6123 }
6124
6125 pub fn prefill_span_ids(
6131 &mut self,
6132 ids: &[u32],
6133 start_pos: usize,
6134 upto: usize,
6135 task_mask: Option<&TaskMask>,
6136 ) -> Result<Vec<f32>, String> {
6137 self.split_supported()?;
6138 if upto >= self.num_layers {
6139 return Err(format!(
6140 "prefill_span_ids: upto {upto} outside 0..{}",
6141 self.num_layers
6142 ));
6143 }
6144 if self.can_prefill_batched() && !self.graph_prefill_preferred() {
6148 Ok(self.prefill_batch_span(PrefillIn::Ids(ids), start_pos, task_mask, 0, upto + 1))
6149 } else {
6150 let hs = self.hidden_size;
6151 let mut out = Vec::with_capacity(ids.len() * hs);
6152 for (i, &id) in ids.iter().enumerate() {
6153 let emb = self.embed_id(id);
6154 out.extend_from_slice(&self.forward_span(
6155 &emb,
6156 start_pos + i,
6157 0,
6158 upto,
6159 task_mask,
6160 )?);
6161 }
6162 Ok(out)
6163 }
6164 }
6165
6166 pub fn prefill_span_hidden(
6169 &mut self,
6170 hidden: &[f32],
6171 start_pos: usize,
6172 from: usize,
6173 upto: usize,
6174 task_mask: Option<&TaskMask>,
6175 ) -> Result<Vec<f32>, String> {
6176 self.split_supported()?;
6177 let hs = self.hidden_size;
6178 if hidden.is_empty() || hidden.len() % hs != 0 {
6179 return Err(format!(
6180 "prefill_span_hidden: {} floats is not a multiple of hidden {hs}",
6181 hidden.len()
6182 ));
6183 }
6184 if from > upto || upto >= self.num_layers {
6185 return Err(format!(
6186 "prefill_span_hidden: layer range {from}..={upto} outside 0..{}",
6187 self.num_layers
6188 ));
6189 }
6190 if self.can_prefill_batched() && !self.graph_prefill_preferred() {
6191 Ok(self.prefill_batch_span(
6192 PrefillIn::Hidden(hidden),
6193 start_pos,
6194 task_mask,
6195 from,
6196 upto + 1,
6197 ))
6198 } else {
6199 let b = hidden.len() / hs;
6200 let mut out = Vec::with_capacity(hidden.len());
6201 for i in 0..b {
6202 let h = self.forward_span(
6203 &hidden[i * hs..(i + 1) * hs],
6204 start_pos + i,
6205 from,
6206 upto,
6207 task_mask,
6208 )?;
6209 out.extend_from_slice(&h);
6210 }
6211 Ok(out)
6212 }
6213 }
6214
6215 fn try_token_graph_wgpu(
6219 &self,
6220 hidden: &[f32],
6221 position: usize,
6222 logits_out: &mut Vec<f32>,
6223 layers_run: &mut usize,
6224 ) -> Option<Vec<f32>> {
6225 self.try_token_graph_wgpu_steps(
6226 hidden,
6227 position,
6228 logits_out,
6229 1,
6230 None,
6231 Some(layers_run),
6232 0,
6233 self.num_layers,
6234 )
6235 }
6236
6237 fn try_token_graph_wgpu_span(
6241 &self,
6242 hidden: &[f32],
6243 position: usize,
6244 logits_out: &mut Vec<f32>,
6245 from: usize,
6246 upto_excl: usize,
6247 layers_run: &mut usize,
6248 ) -> Option<Vec<f32>> {
6249 self.try_token_graph_wgpu_steps(
6250 hidden,
6251 position,
6252 logits_out,
6253 1,
6254 None,
6255 Some(layers_run),
6256 from,
6257 upto_excl,
6258 )
6259 }
6260
6261 fn try_multi_burst(&self, t_next: u32, position: usize, k: usize) -> Option<Vec<u32>> {
6265 if self.o1_active() || self.attn_softcap > 0.0 {
6266 return None;
6267 }
6268 let graph_on = crate::gpu::wgpu_graph_on(crate::gpu::GraphPhase::Decode);
6269 if !graph_on || crate::gpu::graph_unsupported() {
6270 return None;
6277 }
6278 let emb = self.embed_single(t_next);
6279 let mut lg = Vec::new();
6280 let mut ids = Vec::new();
6281 self.try_token_graph_wgpu_steps(
6282 &emb,
6283 position,
6284 &mut lg,
6285 k,
6286 Some(&mut ids),
6287 None,
6288 0,
6289 self.num_layers,
6290 )?;
6291 (ids.len() == k).then_some(ids)
6292 }
6293
6294 fn try_token_graph_wgpu_steps(
6298 &self,
6299 hidden: &[f32],
6300 position: usize,
6301 logits_out: &mut Vec<f32>,
6302 steps: usize,
6303 ids_out: Option<&mut Vec<u32>>,
6304 layers_run: Option<&mut usize>,
6305 from: usize,
6306 upto_excl: usize,
6307 ) -> Option<Vec<f32>> {
6308 let o1_gpu = std::env::var("CMF_O1_GPU").as_deref() == Ok("1");
6311 if (self.o1_active() && !o1_gpu) || self.attn_softcap > 0.0 {
6312 return None;
6316 }
6317 let o1_views: Vec<Option<Vec<crate::nystrom::O1DeviceView<'_>>>> = (from..upto_excl)
6322 .map(|li| {
6323 if !o1_gpu {
6324 return None;
6325 }
6326 self.kv_cache.layers[self.phys_layer(li)].o1_views()
6327 })
6328 .collect();
6329 if self.o1_active() && o1_gpu {
6330 let want: usize = (from..upto_excl)
6333 .filter(|li| self.kv_cache.layers[self.phys_layer(*li)].o1.is_some())
6334 .count();
6335 let have = o1_views.iter().filter(|v| v.is_some()).count();
6336 if want == 0 || have != want {
6337 use std::sync::atomic::{AtomicUsize, Ordering};
6347 static LAST: AtomicUsize = AtomicUsize::new(usize::MAX);
6348 let code = have * 1000 + want;
6349 if LAST.swap(code, Ordering::Relaxed) != code {
6350 tracing::warn!(
6351 "o1 graph: {have} of {want} layers sealed — per-op until all seal"
6352 );
6353 }
6354 return None;
6355 }
6356 }
6357 let nh = self.num_heads;
6358 let (nkv, hd, rd) = self.layer_geom(0);
6359 let gemma = self.norm_style == cortiq_core::NormStyle::Gemma;
6360 let mut layers = Vec::with_capacity(upto_excl - from);
6361 let mut model = None;
6362 let dbg = std::env::var("CMF_GRAPH_DEBUG").is_ok();
6363 fn gw(t: &QTensor) -> Option<crate::gpu::GraphW<'_>> {
6364 if let Some((_, i, kind, rs)) = t.graph_weight() {
6365 return Some(crate::gpu::GraphW {
6366 idx: i,
6367 kind,
6368 row_scale: rs,
6369 data: &[],
6370 });
6371 }
6372 t.as_f32().map(|d| crate::gpu::GraphW {
6374 idx: 0,
6375 kind: 4,
6376 row_scale: &[],
6377 data: d,
6378 })
6379 }
6380 for li in from..upto_excl {
6381 let lw = &self.weights.layers[self.phys_layer(li)];
6382 if dbg {
6383 let ak = match &lw.attn {
6384 AttnKind::Mla(_) => "Mla".into(),
6385 AttnKind::Full {
6386 output_gate, bias, ..
6387 } => format!("Full gate={output_gate} bias={}", bias.is_some()),
6388 AttnKind::LinearGdn(_) => "LinearGdn".into(),
6389 AttnKind::Kda(_) => "Kda".into(),
6390 AttnKind::Linear(_) => "Linear".into(),
6391 AttnKind::ShortConv(_) => "ShortConv".into(),
6392 };
6393 let fk = match &lw.ffn {
6394 FfnKind::Dense(_) => "Dense",
6395 FfnKind::Moe(_) => "Moe",
6396 FfnKind::DenseMoe(_) => "DenseMoe",
6397 };
6398 eprintln!("graph L{li}: attn={ak} ffn={fk}");
6399 }
6400 let gffn = match &lw.ffn {
6401 FfnKind::DenseMoe(_) => return None, FfnKind::Dense(d) if !d.segs.is_empty() => return None,
6405 FfnKind::Dense(d) => crate::gpu::GraphFfn::Dense {
6406 gate: gw(&d.gate_proj)?,
6407 up: gw(&d.up_proj)?,
6408 down: gw(&d.down_proj)?,
6409 },
6410 FfnKind::Moe(m) => {
6411 if m.route_tau.is_some()
6418 || m.mask.is_some()
6419 || (m.routed_scaling - 1.0).abs() > 1e-9
6420 {
6421 return None;
6422 }
6423 let shared = m.shared.as_ref();
6424 let has_shared = shared.is_some();
6425 let sgate = match shared {
6426 Some((_, sg)) => gw(sg.as_ref()?)?,
6427 None => gw(&m.router)?,
6430 };
6431 let router = gw(&m.router)?;
6432 let inter = m.experts.first()?.gate_proj.rows();
6433 let mut experts = Vec::with_capacity(m.experts.len() + 1);
6434 let mut q4tp: Option<bool> = None;
6437 let mut gu_q2: Option<bool> = None;
6440 for e in m.experts.iter().chain(shared.map(|(se, _)| se)) {
6441 if !matches!(e.act, Act::Silu)
6442 || e.gate_proj.rows() != inter
6443 || e.up_proj.rows() != inter
6444 {
6445 return None;
6446 }
6447 let (mm, gi, ui, di, is_p, is_q2) = match e.gate_proj.mapped_q4t() {
6448 Some((mm, gi)) => (
6449 mm,
6450 gi,
6451 e.up_proj.mapped_q4t()?.1,
6452 e.down_proj.mapped_q4t()?.1,
6453 false,
6454 false,
6455 ),
6456 None => match e.gate_proj.mapped_q2tp() {
6457 Some((mm, gi)) => (
6458 mm,
6459 gi,
6460 e.up_proj.mapped_q2tp()?.1,
6461 e.down_proj.mapped_q4tp()?.1,
6462 true,
6463 true,
6464 ),
6465 None => {
6466 let (mm, gi) = e.gate_proj.mapped_q4tp()?;
6467 (
6468 mm,
6469 gi,
6470 e.up_proj.mapped_q4tp()?.1,
6471 e.down_proj.mapped_q4tp()?.1,
6472 true,
6473 false,
6474 )
6475 }
6476 },
6477 };
6478 if *q4tp.get_or_insert(is_p) != is_p || *gu_q2.get_or_insert(is_q2) != is_q2
6479 {
6480 tracing::warn!(
6486 "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."
6487 );
6488 return None;
6489 }
6490 model.get_or_insert_with(|| mm.clone());
6491 experts.push((gi, ui, di));
6492 }
6493 crate::gpu::GraphFfn::Moe {
6494 router,
6495 shared_gate: sgate,
6496 experts,
6497 n_exp: m.experts.len(),
6498 top_k: std::env::var("CMF_TOPK_PROBE")
6504 .ok()
6505 .and_then(|v| v.parse::<usize>().ok())
6506 .filter(|k| *k > 0 && *k <= m.top_k)
6507 .unwrap_or(m.top_k),
6508 inter,
6509 norm_topk: m.norm_topk_prob,
6510 q4tp: q4tp?,
6511 gu_q2: gu_q2.unwrap_or(false),
6512 sigmoid: m.router_sigmoid,
6513 bias: m.expert_bias.as_deref(),
6514 has_shared,
6515 }
6516 }
6517 };
6518 let attn = match &lw.attn {
6519 AttnKind::Full {
6520 wq,
6521 wk,
6522 wv,
6523 wo,
6524 q_norm,
6525 k_norm,
6526 output_gate,
6527 softplus_gate,
6528 bias,
6529 } => {
6530 if softplus_gate.is_some() || self.attention_heads_per_layer.is_some() {
6531 return None;
6532 }
6533 let (m, _, _, _) = wq.graph_weight()?;
6534 model = Some(m.clone());
6535 crate::gpu::GraphAttn::Full {
6536 wq: gw(wq)?,
6537 wk: gw(wk)?,
6538 wv: gw(wv)?,
6539 wo: gw(wo)?,
6540 q_norm: q_norm.as_deref(),
6541 k_norm: k_norm.as_deref(),
6542 bias: bias
6543 .as_ref()
6544 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
6545 output_gate: *output_gate,
6546 cpu_k: self.kv_cache.layers[li].k_heads(),
6547 cpu_v: self.kv_cache.layers[li].v_heads(),
6548 }
6549 }
6550 AttnKind::LinearGdn(w) => {
6551 let cfg = self.gdn_cfg?;
6552 let (m, _, _, _) = w.in_proj_qkv.graph_weight()?;
6553 model = Some(m.clone());
6554 crate::gpu::GraphAttn::Gdn {
6555 qkv: gw(&w.in_proj_qkv)?,
6556 z: gw(&w.in_proj_z)?,
6557 a: gw(&w.in_proj_a)?,
6558 b: gw(&w.in_proj_b)?,
6559 out: gw(&w.out_proj)?,
6560 conv1d: &w.conv1d,
6561 a_log: &w.a_log,
6562 dt_bias: &w.dt_bias,
6563 norm: &w.norm,
6564 nv: cfg.num_v_heads,
6565 nk: cfg.num_k_heads,
6566 dk: cfg.key_head_dim,
6567 dv: cfg.value_head_dim,
6568 kk: cfg.conv_kernel,
6569 cpu_state: &self.kv_cache.layers[self.phys_layer(li)].linear_state,
6570 }
6571 }
6572 AttnKind::ShortConv(w) => {
6573 let cfg = self.short_conv_cfg?;
6574 let (m, _, _, _) = w.in_proj.graph_weight()?;
6575 model = Some(m.clone());
6576 crate::gpu::GraphAttn::ShortConv {
6577 inp: gw(&w.in_proj)?,
6578 out: gw(&w.out_proj)?,
6579 taps: &w.conv,
6580 kernel: cfg.kernel,
6581 cpu_state: &self.kv_cache.layers[self.phys_layer(li)].linear_state,
6582 }
6583 }
6584 _ => return None,
6585 };
6586 layers.push(crate::gpu::GraphLayer {
6587 input_norm: &lw.input_norm,
6588 attn,
6589 post_norm: &lw.post_norm,
6590 ffn: gffn,
6591 });
6592 }
6593 let model = model?;
6594 let lm_gw = if upto_excl == self.num_layers
6600 && self.graph_want_logits
6601 && std::env::var("CMF_GPU_LMHEAD")
6602 .map(|v| v != "0")
6603 .unwrap_or(true)
6604 {
6605 self.weights.lm_head.graph_weight().map(|(_, i, kind, rs)| {
6606 (
6607 crate::gpu::GraphW {
6608 idx: i,
6609 kind,
6610 row_scale: rs,
6611 data: &[],
6612 },
6613 self.weights.lm_head.rows(),
6614 )
6615 })
6616 } else {
6617 None
6618 };
6619 let lm = lm_gw.as_ref().map(|(gw, rows)| (gw, *rows));
6620 let emb_gw = if steps > 1 {
6622 self.weights
6623 .embed_tokens
6624 .graph_weight()
6625 .map(|(_, i, kind, rs)| {
6626 (
6627 crate::gpu::GraphW {
6628 idx: i,
6629 kind,
6630 row_scale: rs,
6631 data: &[],
6632 },
6633 self.weights.embed_tokens.rows(),
6634 self.embed_multiplier,
6635 )
6636 })
6637 } else {
6638 None
6639 };
6640
6641 let loop_norm_at: Vec<usize> = if self.loop_final_norm {
6647 (from..upto_excl.min(self.num_layers - 1))
6648 .filter(|&li| (li + 1) % self.physical_layers == 0)
6649 .map(|li| li - from)
6650 .collect()
6651 } else {
6652 Vec::new()
6653 };
6654 let mut h = hidden.to_vec();
6655 crate::gpu::forward_token_graph(
6656 &model,
6657 self.graph_kv_id,
6658 &layers,
6659 &o1_views,
6660 self.o1_epoch,
6661 &self.inv_freq,
6662 &mut h,
6663 nh,
6664 nkv,
6665 hd,
6666 self.attn_scale,
6667 rd,
6668 self.hidden_size,
6669 self.intermediate_size,
6670 position,
6671 self.kv_cache.max_seq_len,
6672 gemma,
6673 self.rms_eps as f32,
6674 lm,
6675 &self.weights.final_norm,
6676 logits_out,
6677 &loop_norm_at,
6678 steps,
6679 emb_gw.as_ref().map(|(gw, rows, m)| (gw, *rows, *m)),
6680 ids_out,
6681 layers_run,
6682 from,
6683 false,
6684 )
6685 .then_some(h)
6686 }
6687
6688 #[cfg(target_os = "macos")]
6697 #[allow(clippy::type_complexity)]
6698 fn metal_rows_plan(
6699 &self,
6700 ) -> Option<(
6701 Vec<MetalRowsItem<'_>>,
6702 std::sync::Arc<cortiq_core::CmfModel>,
6703 Option<crate::gpu_metal::GdnGpuCfg>,
6704 )> {
6705 use crate::gpu_metal::{AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, MetalFfn};
6706 if !crate::gpu::q1_force()
6707 || !crate::gpu::enabled_here()
6708 || std::env::var("CMF_GPU_BLOCK")
6709 .map(|v| v == "0")
6710 .unwrap_or(false)
6711 || self.attn_softcap > 0.0
6712 || self.o1_active()
6713 || self.swa.is_some()
6714 || self.global_attn.is_some()
6715 || self.attention_heads_per_layer.is_some()
6716 || self.attn_v_norm
6717 || self.loop_final_norm
6718 {
6719 return None;
6720 }
6721 let attend_contract = self.head_dim % 4 == 0
6722 && self.head_dim <= 256
6723 && self.rotary_dim >= 2
6724 && self.rotary_dim <= self.head_dim
6725 && (self.rotary_dim / 2) % 32 == 0
6726 && self.num_kv_heads > 0
6727 && self.num_heads % self.num_kv_heads == 0;
6728 if !attend_contract {
6729 return None;
6730 }
6731 let mut plan: Vec<MetalRowsItem> = Vec::new();
6732 let mut model_ref: Option<std::sync::Arc<cortiq_core::CmfModel>> = None;
6733 for li in 0..self.num_layers {
6734 let lw = &self.weights.layers[self.phys_layer(li)];
6735 if lw.attn_out_norm.is_some() || lw.ffn_out_norm.is_some() || lw.layer_scale.is_some() {
6736 return None;
6737 }
6738 let ffn = match &lw.ffn {
6739 FfnKind::Dense(d) if d.act == Act::Silu && d.segs.is_empty() => {
6740 let (Some(g), Some(u), Some(dn)) = (
6741 d.gate_proj.q1_parts(),
6742 d.up_proj.q1_parts(),
6743 d.down_proj.q1_parts(),
6744 ) else {
6745 return None;
6746 };
6747 MetalFfn::Dense {
6748 gate: g,
6749 up: u,
6750 down: dn,
6751 }
6752 }
6753 _ => return None,
6754 };
6755 match &lw.attn {
6756 AttnKind::LinearGdn(w) if self.gdn_cfg.is_some() => {
6757 let (Some(qkv), Some(z), Some(a), Some(bb), Some(out)) = (
6758 w.in_proj_qkv.q1_parts(),
6759 w.in_proj_z.q1_parts(),
6760 w.in_proj_a.f32_parts(),
6761 w.in_proj_b.f32_parts(),
6762 w.out_proj.q1_parts(),
6763 ) else {
6764 return None;
6765 };
6766 if let QTensor::Mapped { model, .. } = &w.in_proj_qkv {
6767 model_ref.get_or_insert_with(|| model.clone());
6768 }
6769 let gl = GdnGpuLayer {
6770 attn_norm: &lw.input_norm,
6771 post_norm: &lw.post_norm,
6772 qkv,
6773 z,
6774 a,
6775 b: bb,
6776 out,
6777 ffn,
6778 conv1d: &w.conv1d,
6779 a_log: &w.a_log,
6780 dt_bias: &w.dt_bias,
6781 gnorm: &w.norm,
6782 };
6783 match plan.last_mut() {
6784 Some(MetalRowsItem::Gdn { run, .. }) => run.push(gl),
6785 _ => plan.push(MetalRowsItem::Gdn {
6786 run: vec![gl],
6787 first: li,
6788 }),
6789 }
6790 }
6791 AttnKind::Full {
6792 wq,
6793 wk,
6794 wv,
6795 wo,
6796 q_norm,
6797 k_norm,
6798 output_gate,
6799 softplus_gate: None,
6800 bias: None,
6801 } => {
6802 let (Some(pq), Some(pk), Some(pv), Some(po)) =
6803 (wq.q1_parts(), wk.q1_parts(), wv.q1_parts(), wo.q1_parts())
6804 else {
6805 return None;
6806 };
6807 if let QTensor::Mapped { model, .. } = wq {
6808 model_ref.get_or_insert_with(|| model.clone());
6809 }
6810 let cache = &self.kv_cache.layers[li];
6811 if cache.mode != crate::kv_cache::KvMode::F32 || cache.o1.is_some() {
6812 return None;
6813 }
6814 plan.push(MetalRowsItem::Attn {
6815 l: AttnGpuLayer {
6816 attn_norm: &lw.input_norm,
6817 post_norm: &lw.post_norm,
6818 wq: pq,
6819 wk: pk,
6820 wv: pv,
6821 wo: po,
6822 ffn,
6823 },
6824 li,
6825 q_norm: q_norm.as_deref(),
6826 k_norm: k_norm.as_deref(),
6827 output_gate: *output_gate,
6828 });
6829 }
6830 _ => return None,
6831 }
6832 }
6833 let model = model_ref?;
6834 let gcfg = self.gdn_cfg.map(|cfg| GdnGpuCfg {
6835 nv: cfg.num_v_heads,
6836 nk: cfg.num_k_heads,
6837 dk: cfg.key_head_dim,
6838 dv: cfg.value_head_dim,
6839 kk: cfg.conv_kernel,
6840 hidden: self.hidden_size,
6841 inter: self.intermediate_size,
6842 c_dim: cfg.conv_dim(),
6843 eps: cfg.rms_eps as f32,
6844 gemma: self.norm_style == cortiq_core::NormStyle::Gemma,
6845 });
6846 Some((plan, model, gcfg))
6847 }
6848
6849 #[cfg(target_os = "macos")]
6851 #[allow(clippy::too_many_arguments)]
6852 fn metal_attn_params<'a>(
6853 li: usize,
6854 cache: &'a crate::kv_cache::LayerKvCache,
6855 q_norm: Option<&'a [f32]>,
6856 k_norm: Option<&'a [f32]>,
6857 output_gate: bool,
6858 inv_freq: &'a [f32],
6859 geom: (usize, usize, usize, usize),
6860 pos0: usize,
6861 kv_id: u64,
6862 scale: f32,
6863 eps: f32,
6864 gemma: bool,
6865 ) -> (crate::gpu_metal::AttnDeviceParams<'a>, usize) {
6866 let (nh, nkv, hd, rd) = geom;
6867 let cpu_k: Vec<&[f32]> = (0..nkv).map(|g| cache.head_keys(g)).collect();
6868 let cpu_v: Vec<&[f32]> = (0..nkv).map(|g| cache.head_values(g)).collect();
6869 let cpu_stored = cpu_k[0].len() / hd;
6870 (
6871 crate::gpu_metal::AttnDeviceParams {
6872 kv_id,
6873 layer: li,
6874 nh,
6875 nkv,
6876 hd,
6877 rd,
6878 position: pos0,
6879 scale,
6880 eps,
6881 gemma,
6882 output_gate,
6883 q_norm,
6884 k_norm,
6885 inv_freq,
6886 cpu_k,
6887 cpu_v,
6888 cpu_stored,
6889 o1: None,
6890 },
6891 cpu_stored,
6892 )
6893 }
6894
6895 #[cfg(target_os = "macos")]
6900 #[allow(clippy::type_complexity)]
6901 fn metal_rows_run(
6902 &mut self,
6903 hiddens: &mut [f32],
6904 pos0: usize,
6905 b: usize,
6906 prefill: bool,
6907 spec: Option<((usize, usize, usize), &[f32], &mut Vec<f32>)>,
6908 ) -> Option<MetalVerifyPending> {
6909 use crate::gpu_metal::{GraphDims, VerifyGraph};
6910 let want = self.gdn_cfg.map(|c| c.state_len()).unwrap_or(0);
6911 for l in &mut self.kv_cache.layers {
6912 if l.linear_state.len() != want && want > 0 {
6913 l.linear_state = vec![0f32; want];
6914 }
6915 }
6916 let (plan, model, gcfg) = self.metal_rows_plan()?;
6917 let dims = GraphDims {
6918 hidden: self.hidden_size,
6919 eps: self.rms_eps as f32,
6920 gemma: self.norm_style == cortiq_core::NormStyle::Gemma,
6921 };
6922 let mut graph = if prefill {
6923 VerifyGraph::new_prefill(&model, dims, hiddens, b)?
6924 } else {
6925 VerifyGraph::new(&model, dims, hiddens, b)?
6926 };
6927 let geom = (
6928 self.num_heads,
6929 self.num_kv_heads,
6930 self.head_dim,
6931 self.rotary_dim,
6932 );
6933 let gemma = self.norm_style == cortiq_core::NormStyle::Gemma;
6934 let eps = self.rms_eps as f32;
6935 let kv_id = self.graph_kv_id;
6936 let inv_freq = self.inv_freq.clone();
6937 for item in &plan {
6938 let ok = match item {
6939 MetalRowsItem::Gdn { run, .. } => gcfg
6940 .as_ref()
6941 .map(|gc| run.iter().all(|l| graph.gdn_ok(l, gc)))
6942 .unwrap_or(false),
6943 MetalRowsItem::Attn {
6944 l,
6945 li,
6946 q_norm,
6947 k_norm,
6948 output_gate,
6949 } => {
6950 let (p, _) = Self::metal_attn_params(
6951 *li,
6952 &self.kv_cache.layers[*li],
6953 *q_norm,
6954 *k_norm,
6955 *output_gate,
6956 &inv_freq,
6957 geom,
6958 pos0,
6959 kv_id,
6960 self.attn_scale,
6961 eps,
6962 gemma,
6963 );
6964 graph.attn_ok(l, &p)
6965 }
6966 };
6967 if !ok {
6968 use std::sync::atomic::{AtomicBool, Ordering};
6969 static SAID: AtomicBool = AtomicBool::new(false);
6970 if !SAID.swap(true, Ordering::Relaxed) {
6971 tracing::warn!("metal rows graph: a layer failed preflight — declining");
6972 }
6973 return None;
6974 }
6975 }
6976 let lm = match &spec {
6977 Some((lm, _, _)) => {
6978 if !graph.lm_head_ok(*lm) {
6979 return None;
6980 }
6981 Some(*lm)
6982 }
6983 None => None,
6984 };
6985 let mut gdn_layers = Vec::new();
6986 let mut attn_layers = Vec::new();
6987 for item in &plan {
6988 match item {
6989 MetalRowsItem::Gdn { run, first } => {
6990 let ro: Vec<&[f32]> = self.kv_cache.layers[*first..*first + run.len()]
6991 .iter()
6992 .map(|l| l.linear_state.as_slice())
6993 .collect();
6994 if !graph.encode_gdn_run_b(run, &ro, gcfg.as_ref().unwrap()) {
6995 return None;
6996 }
6997 gdn_layers.extend(*first..*first + run.len());
6998 }
6999 MetalRowsItem::Attn {
7000 l,
7001 li,
7002 q_norm,
7003 k_norm,
7004 output_gate,
7005 } => {
7006 let (p, cpu_stored) = Self::metal_attn_params(
7007 *li,
7008 &self.kv_cache.layers[*li],
7009 *q_norm,
7010 *k_norm,
7011 *output_gate,
7012 &inv_freq,
7013 geom,
7014 pos0,
7015 kv_id,
7016 self.attn_scale,
7017 eps,
7018 gemma,
7019 );
7020 if !graph.encode_attn_b(l, &p) {
7021 return None;
7022 }
7023 attn_layers.push((*li, cpu_stored));
7024 }
7025 }
7026 }
7027 if let (Some(lm), Some((_, final_norm, _))) = (lm, spec.as_ref()) {
7028 if !graph.encode_lm_head_b(final_norm, lm) {
7029 return None;
7030 }
7031 }
7032 graph.sync();
7033 if let Some((lm, _, logits)) = spec {
7034 logits.resize(b * lm.1, 0.0);
7035 graph.read_logits(logits);
7036 }
7037 graph.read_hidden(hiddens);
7038 Some(MetalVerifyPending {
7039 graph,
7040 gdn_layers,
7041 attn_layers,
7042 })
7043 }
7044
7045 #[cfg(target_os = "macos")]
7051 fn try_batch_graph_metal(
7052 &mut self,
7053 hiddens: &mut [f32],
7054 positions: &[usize],
7055 b: usize,
7056 spec: Option<((usize, usize, usize), &[f32], &mut Vec<f32>)>,
7057 ) -> bool {
7058 let _t0 = std::time::Instant::now();
7059 if positions.len() != b
7060 || positions.windows(2).any(|w| w[1] != w[0] + 1)
7061 || hiddens.len() != b * self.hidden_size
7062 {
7063 return false;
7064 }
7065 let Some(pending) = self.metal_rows_run(hiddens, positions[0], b, false, spec) else {
7066 return false;
7067 };
7068 if std::env::var("CMF_GRAPH_SPEC_TIME").is_ok() {
7069 eprintln!(
7070 "metal-verify: {:.1} ms | b={b}",
7071 _t0.elapsed().as_secs_f64() * 1e3
7072 );
7073 }
7074 self.metal_verify = Some(pending);
7075 true
7076 }
7077
7078 #[cfg(target_os = "macos")]
7083 fn prefill_batch_metal(&mut self, ids: &[u32], start_pos: usize) -> Option<Vec<f32>> {
7084 let b = ids.len();
7085 if b == 0 || b > 512 {
7086 return None;
7087 }
7088 let hs = self.hidden_size;
7089 let mut hiddens = vec![0f32; b * hs];
7090 for (j, &id) in ids.iter().enumerate() {
7091 let e = self.embed_single(id);
7092 hiddens[j * hs..(j + 1) * hs].copy_from_slice(&e);
7093 }
7094 let mut pending = self.metal_rows_run(&mut hiddens, start_pos, b, true, None)?;
7095 let idxs = pending.gdn_layers.clone();
7097 let mut outs: Vec<&mut [f32]> = self
7098 .kv_cache
7099 .layers
7100 .iter_mut()
7101 .enumerate()
7102 .filter(|(i, _)| idxs.binary_search(i).is_ok())
7103 .map(|(_, l)| l.linear_state.as_mut_slice())
7104 .collect();
7105 pending.graph.finish_states(&mut outs);
7106 let (nkv, hd) = (self.num_kv_heads, self.head_dim);
7107 let mut kbuf = vec![0f32; b * nkv * hd];
7108 let mut vbuf = vec![0f32; b * nkv * hd];
7109 for (li, cpu_stored) in &pending.attn_layers {
7110 if crate::gpu_metal::kv_mirror_read_rows(
7111 self.graph_kv_id,
7112 *li,
7113 nkv,
7114 hd,
7115 *cpu_stored,
7116 b,
7117 &mut kbuf,
7118 &mut vbuf,
7119 ) {
7120 let cache = &mut self.kv_cache.layers[*li];
7121 for r in 0..b {
7122 cache.append(
7123 &kbuf[r * nkv * hd..(r + 1) * nkv * hd],
7124 &vbuf[r * nkv * hd..(r + 1) * nkv * hd],
7125 &[],
7126 );
7127 }
7128 crate::gpu_metal::kv_mirror_set_stored(self.graph_kv_id, *li, cpu_stored + b);
7129 }
7130 }
7131 Some(hiddens)
7132 }
7133
7134 #[cfg(target_os = "macos")]
7138 fn metal_verify_commit(&mut self, a: usize) -> bool {
7139 let Some(mut pending) = self.metal_verify.take() else {
7140 return false;
7141 };
7142 let n = a + 1;
7143 let idxs = pending.gdn_layers.clone();
7145 let mut outs: Vec<&mut [f32]> = self
7146 .kv_cache
7147 .layers
7148 .iter_mut()
7149 .enumerate()
7150 .filter(|(i, _)| idxs.binary_search(i).is_ok())
7151 .map(|(_, l)| l.linear_state.as_mut_slice())
7152 .collect();
7153 if !pending.graph.commit(n, &mut outs) {
7154 return false;
7155 }
7156 let (nkv, hd) = (self.num_kv_heads, self.head_dim);
7157 let mut kbuf = vec![0f32; n * nkv * hd];
7158 let mut vbuf = vec![0f32; n * nkv * hd];
7159 for (li, cpu_stored) in &pending.attn_layers {
7160 if crate::gpu_metal::kv_mirror_read_rows(
7161 self.graph_kv_id,
7162 *li,
7163 nkv,
7164 hd,
7165 *cpu_stored,
7166 n,
7167 &mut kbuf,
7168 &mut vbuf,
7169 ) {
7170 let cache = &mut self.kv_cache.layers[*li];
7171 for r in 0..n {
7172 cache.append(
7173 &kbuf[r * nkv * hd..(r + 1) * nkv * hd],
7174 &vbuf[r * nkv * hd..(r + 1) * nkv * hd],
7175 &[],
7176 );
7177 }
7178 crate::gpu_metal::kv_mirror_set_stored(self.graph_kv_id, *li, cpu_stored + n);
7179 }
7180 }
7181 true
7182 }
7183
7184 #[cfg(target_os = "macos")]
7190 fn mtp_warm_batch_metal(
7191 &mut self,
7192 m: &mut MtpModule,
7193 pairs: &[(&[f32], u32)],
7194 first_pos: usize,
7195 ) -> bool {
7196 use crate::gpu_metal::{AttnDeviceParams, AttnGpuLayer, GraphDims, MetalFfn, VerifyGraph};
7197 let b = pairs.len();
7198 if b == 0 || b > 512 || m.kv.mode != crate::kv_cache::KvMode::F32 || m.kv.o1.is_some() {
7199 return false;
7200 }
7201 let AttnKind::Full {
7202 wq,
7203 wk,
7204 wv,
7205 wo,
7206 q_norm,
7207 k_norm,
7208 output_gate,
7209 softplus_gate: None,
7210 bias: None,
7211 } = &m.layer.attn
7212 else {
7213 return false;
7214 };
7215 let FfnKind::Dense(d) = &m.layer.ffn else {
7216 return false;
7217 };
7218 if !d.segs.is_empty() {
7219 return false;
7220 }
7221 let (Some(pq), Some(pk), Some(pv), Some(po)) =
7222 (wq.q1_parts(), wk.q1_parts(), wv.q1_parts(), wo.q1_parts())
7223 else {
7224 return false;
7225 };
7226 let (Some(g), Some(u), Some(dn)) = (
7227 d.gate_proj.q1_parts(),
7228 d.up_proj.q1_parts(),
7229 d.down_proj.q1_parts(),
7230 ) else {
7231 return false;
7232 };
7233 let Some(eh) = m.eh_proj.q1_parts() else {
7234 return false;
7235 };
7236 let QTensor::Mapped { model, .. } = wq else {
7237 return false;
7238 };
7239 let model = model.clone();
7240 let hs = self.hidden_size;
7241 let mut cat = vec![0f32; b * 2 * hs];
7243 for (j, (h, tok)) in pairs.iter().enumerate() {
7244 let e = self.embed_single(*tok);
7245 let (ce, ch) = cat[j * 2 * hs..(j + 1) * 2 * hs].split_at_mut(hs);
7246 inference::rms_norm_into(&e, &m.enorm, self.rms_eps, self.norm_style, ce);
7247 inference::rms_norm_into(h, &m.hnorm, self.rms_eps, self.norm_style, ch);
7248 }
7249 let dims = GraphDims {
7250 hidden: hs,
7251 eps: self.rms_eps as f32,
7252 gemma: self.norm_style == cortiq_core::NormStyle::Gemma,
7253 };
7254 let Some(mut graph) = VerifyGraph::new_via_proj(&model, dims, eh, &cat, b) else {
7255 return false;
7256 };
7257 let l = AttnGpuLayer {
7258 attn_norm: &m.layer.input_norm,
7259 post_norm: &m.layer.post_norm,
7260 wq: pq,
7261 wk: pk,
7262 wv: pv,
7263 wo: po,
7264 ffn: MetalFfn::Dense {
7265 gate: g,
7266 up: u,
7267 down: dn,
7268 },
7269 };
7270 let (nh, nkv, hd, rd) = (
7271 self.num_heads,
7272 self.num_kv_heads,
7273 self.head_dim,
7274 self.rotary_dim,
7275 );
7276 let inv_freq = self.inv_freq.clone();
7277 let cpu_stored;
7278 {
7279 let cache = &m.kv;
7280 let cpu_k: Vec<&[f32]> = (0..nkv).map(|g| cache.head_keys(g)).collect();
7281 let cpu_v: Vec<&[f32]> = (0..nkv).map(|g| cache.head_values(g)).collect();
7282 cpu_stored = cpu_k[0].len() / hd;
7283 if cpu_stored != first_pos {
7284 return false;
7285 }
7286 let p = AttnDeviceParams {
7287 kv_id: self.mtp_kv_id(),
7288 layer: Self::MTP_LAYER_BASE,
7289 nh,
7290 nkv,
7291 hd,
7292 rd,
7293 position: first_pos,
7294 scale: self.attn_scale,
7295 eps: self.rms_eps as f32,
7296 gemma: self.norm_style == cortiq_core::NormStyle::Gemma,
7297 output_gate: *output_gate,
7298 q_norm: q_norm.as_deref(),
7299 k_norm: k_norm.as_deref(),
7300 inv_freq: &inv_freq,
7301 cpu_k,
7302 cpu_v,
7303 cpu_stored,
7304 o1: None,
7305 };
7306 if !graph.attn_ok(&l, &p) || !graph.encode_attn_b(&l, &p) {
7307 return false;
7308 }
7309 }
7310 graph.sync();
7311 let mut kbuf = vec![0f32; b * nkv * hd];
7312 let mut vbuf = vec![0f32; b * nkv * hd];
7313 if !crate::gpu_metal::kv_mirror_read_rows(
7314 self.mtp_kv_id(),
7315 Self::MTP_LAYER_BASE,
7316 nkv,
7317 hd,
7318 cpu_stored,
7319 b,
7320 &mut kbuf,
7321 &mut vbuf,
7322 ) {
7323 return false;
7324 }
7325 for r in 0..b {
7326 m.kv.append(
7327 &kbuf[r * nkv * hd..(r + 1) * nkv * hd],
7328 &vbuf[r * nkv * hd..(r + 1) * nkv * hd],
7329 &[],
7330 );
7331 }
7332 crate::gpu_metal::kv_mirror_set_stored(
7333 self.mtp_kv_id(),
7334 Self::MTP_LAYER_BASE,
7335 cpu_stored + b,
7336 );
7337 true
7338 }
7339
7340 fn draft_vocab_rows(head_rows: usize) -> usize {
7343 static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
7344 let n = *N.get_or_init(|| {
7345 std::env::var("CMF_DRAFT_VOCAB")
7346 .ok()
7347 .and_then(|v| v.parse().ok())
7348 .unwrap_or(65536)
7349 });
7350 if n == 0 { head_rows } else { n.min(head_rows) }
7351 }
7352
7353 #[cfg(target_os = "macos")]
7358 fn mtp_step_metal(
7359 &mut self,
7360 m: &mut MtpModule,
7361 hidden: &[f32],
7362 next_token: u32,
7363 position: usize,
7364 want_logits: bool,
7365 ) -> Option<(Vec<f32>, Vec<f32>)> {
7366 use crate::gpu_metal::{AttnDeviceParams, AttnGpuLayer, GraphDims, MetalFfn, TokenGraph};
7367 if std::env::var("CMF_MTP_GRAPH").as_deref() == Ok("0")
7368 || !crate::gpu::q1_force()
7369 || !crate::gpu::enabled_here()
7370 || self.attn_softcap > 0.0
7371 || self.attention_heads_per_layer.is_some()
7372 || m.kv.mode != crate::kv_cache::KvMode::F32
7373 || m.kv.o1.is_some()
7374 {
7375 return None;
7376 }
7377 let AttnKind::Full {
7378 wq,
7379 wk,
7380 wv,
7381 wo,
7382 q_norm,
7383 k_norm,
7384 output_gate,
7385 softplus_gate: None,
7386 bias: None,
7387 } = &m.layer.attn
7388 else {
7389 return None;
7390 };
7391 let FfnKind::Dense(d) = &m.layer.ffn else {
7392 return None;
7393 };
7394 if d.act != Act::Silu || !d.segs.is_empty() {
7395 return None;
7396 }
7397 let (pq, pk, pv, po) = (
7398 wq.q1_parts()?,
7399 wk.q1_parts()?,
7400 wv.q1_parts()?,
7401 wo.q1_parts()?,
7402 );
7403 let (g, u, dn) = (
7404 d.gate_proj.q1_parts()?,
7405 d.up_proj.q1_parts()?,
7406 d.down_proj.q1_parts()?,
7407 );
7408 let QTensor::Mapped { model, .. } = wq else {
7409 return None;
7410 };
7411 let model = model.clone();
7412 let lm = if want_logits {
7413 Some(self.weights.lm_head.q1_parts()?)
7414 } else {
7415 None
7416 };
7417 let dims = GraphDims {
7418 hidden: self.hidden_size,
7419 eps: self.rms_eps as f32,
7420 gemma: self.norm_style == cortiq_core::NormStyle::Gemma,
7421 };
7422 let hs = self.hidden_size;
7425 let mut x = vec![0f32; hs];
7426 let mut graph = TokenGraph::new(&model, dims, &x)?;
7427 let mut folded = false;
7428 if let Some(eh) = m.eh_proj.q1_parts() {
7429 let e = self.embed_single(next_token);
7430 let mut cat = vec![0.0f32; 2 * hs];
7431 let (cat_e, cat_h) = cat.split_at_mut(hs);
7432 inference::rms_norm_into(&e, &m.enorm, self.rms_eps, self.norm_style, cat_e);
7433 inference::rms_norm_into(hidden, &m.hnorm, self.rms_eps, self.norm_style, cat_h);
7434 folded = graph.encode_input_proj(eh, &cat);
7435 }
7436 if !folded {
7437 x = self.mtp_block_input(m, hidden, next_token);
7438 graph = TokenGraph::new(&model, dims, &x)?;
7439 }
7440 let l = AttnGpuLayer {
7441 attn_norm: &m.layer.input_norm,
7442 post_norm: &m.layer.post_norm,
7443 wq: pq,
7444 wk: pk,
7445 wv: pv,
7446 wo: po,
7447 ffn: MetalFfn::Dense {
7448 gate: g,
7449 up: u,
7450 down: dn,
7451 },
7452 };
7453 let (nh, nkv, hd, rd) = (
7454 self.num_heads,
7455 self.num_kv_heads,
7456 self.head_dim,
7457 self.rotary_dim,
7458 );
7459 let inv_freq = self.inv_freq.clone();
7460 {
7461 let cache = &m.kv;
7462 let cpu_k: Vec<&[f32]> = (0..nkv).map(|g| cache.head_keys(g)).collect();
7463 let cpu_v: Vec<&[f32]> = (0..nkv).map(|g| cache.head_values(g)).collect();
7464 let cpu_stored = cpu_k[0].len() / hd;
7465 let p = AttnDeviceParams {
7466 kv_id: self.mtp_kv_id(),
7467 layer: Self::MTP_LAYER_BASE,
7468 nh,
7469 nkv,
7470 hd,
7471 rd,
7472 position,
7473 scale: self.attn_scale,
7474 eps: self.rms_eps as f32,
7475 gemma: self.norm_style == cortiq_core::NormStyle::Gemma,
7476 output_gate: *output_gate,
7477 q_norm: q_norm.as_deref(),
7478 k_norm: k_norm.as_deref(),
7479 inv_freq: &inv_freq,
7480 cpu_k,
7481 cpu_v,
7482 cpu_stored,
7483 o1: None,
7484 };
7485 if !graph.attn_device_ok(&l, &p) || !graph.encode_attn_device(&l, &p) {
7486 return None;
7487 }
7488 }
7489 let draft_rows = if let Some(lm) = lm {
7495 Self::draft_vocab_rows(lm.1)
7496 } else {
7497 0
7498 };
7499 if let Some(lm) = lm {
7500 if !graph.lm_head_ok(lm) {
7501 return None;
7502 }
7503 if draft_rows < lm.1 {
7504 if !graph.encode_lm_head_part(&m.final_norm, lm, draft_rows) {
7505 return None;
7506 }
7507 } else {
7508 graph.encode_lm_head(&m.final_norm, lm);
7509 }
7510 }
7511 graph.sync();
7512 let mut logits = Vec::new();
7513 if let Some(lm) = lm {
7514 let n_read = draft_rows.min(lm.1).min(self.vocab_size);
7515 logits = attention::take_buf(n_read);
7516 graph.read_logits(&mut logits);
7517 logits.resize(self.vocab_size, f32::NEG_INFINITY);
7519 }
7520 graph.finish(&mut x);
7521 let mut krow = attention::take_buf(nkv * hd);
7522 let mut vrow = attention::take_buf(nkv * hd);
7523 if crate::gpu_metal::kv_mirror_read_last(
7524 self.mtp_kv_id(),
7525 Self::MTP_LAYER_BASE,
7526 nkv,
7527 hd,
7528 &mut krow,
7529 &mut vrow,
7530 ) {
7531 m.kv.append(&krow, &vrow, &[]);
7532 }
7533 attention::recycle_buf(&mut krow);
7534 attention::recycle_buf(&mut vrow);
7535 Some((logits, x))
7536 }
7537
7538 fn try_batch_graph_wgpu(
7539 &self,
7540 hiddens: &mut [f32],
7541 positions: &[usize],
7542 k: usize,
7543 spec: Option<crate::gpu::SpecTail<'_>>,
7544 ) -> bool {
7545 let _tb = std::time::Instant::now();
7546 if self.attn_softcap > 0.0 {
7547 return false; }
7549 if self.o1_active() {
7550 return false;
7551 }
7552 let nh = self.num_heads;
7553 let (nkv, hd, rd) = self.layer_geom(0);
7554 let gemma = self.norm_style == cortiq_core::NormStyle::Gemma;
7555 fn gw(t: &QTensor) -> Option<crate::gpu::GraphW<'_>> {
7556 if let Some((_, i, kind, rs)) = t.graph_weight() {
7557 return Some(crate::gpu::GraphW {
7558 idx: i,
7559 kind,
7560 row_scale: rs,
7561 data: &[],
7562 });
7563 }
7564 t.as_f32().map(|d| crate::gpu::GraphW {
7565 idx: 0,
7566 kind: 4,
7567 row_scale: &[],
7568 data: d,
7569 })
7570 }
7571 let built: Option<(
7572 Vec<crate::gpu::GraphLayer<'_>>,
7573 std::sync::Arc<cortiq_core::CmfModel>,
7574 )> = (|| {
7575 let mut layers = Vec::with_capacity(self.num_layers);
7576 let mut model = None;
7577 for li in 0..self.num_layers {
7578 let lw = &self.weights.layers[self.phys_layer(li)];
7579 let gffn = match &lw.ffn {
7586 FfnKind::Dense(d) if !d.segs.is_empty() => return None,
7587 FfnKind::Dense(d) => crate::gpu::GraphFfn::Dense {
7588 gate: gw(&d.gate_proj)?,
7589 up: gw(&d.up_proj)?,
7590 down: gw(&d.down_proj)?,
7591 },
7592 FfnKind::Moe(m) => {
7593 if m.router_sigmoid
7594 || m.expert_bias.is_some()
7595 || m.route_tau.is_some()
7596 || m.mask.is_some()
7597 {
7598 return None;
7599 }
7600 let (se, sg) = m.shared.as_ref()?;
7601 let sgate = gw(sg.as_ref()?)?;
7602 let router = gw(&m.router)?;
7603 let inter = m.experts.first()?.gate_proj.rows();
7604 let mut experts = Vec::with_capacity(m.experts.len() + 1);
7605 let mut q4tp: Option<bool> = None;
7606 let mut gu_q2: Option<bool> = None;
7607 for e in m.experts.iter().chain(std::iter::once(se)) {
7608 if !matches!(e.act, Act::Silu)
7609 || e.gate_proj.rows() != inter
7610 || e.up_proj.rows() != inter
7611 {
7612 return None;
7613 }
7614 let (mm, gi, ui, di, is_p, is_q2) = match e.gate_proj.mapped_q4t() {
7618 Some((mm, gi)) => (
7619 mm,
7620 gi,
7621 e.up_proj.mapped_q4t()?.1,
7622 e.down_proj.mapped_q4t()?.1,
7623 false,
7624 false,
7625 ),
7626 None => match e.gate_proj.mapped_q2tp() {
7627 Some((mm, gi)) => (
7628 mm,
7629 gi,
7630 e.up_proj.mapped_q2tp()?.1,
7631 e.down_proj.mapped_q4tp()?.1,
7632 true,
7633 true,
7634 ),
7635 None => {
7636 let (mm, gi) = e.gate_proj.mapped_q4tp()?;
7637 (
7638 mm,
7639 gi,
7640 e.up_proj.mapped_q4tp()?.1,
7641 e.down_proj.mapped_q4tp()?.1,
7642 true,
7643 false,
7644 )
7645 }
7646 },
7647 };
7648 if *q4tp.get_or_insert(is_p) != is_p
7649 || *gu_q2.get_or_insert(is_q2) != is_q2
7650 {
7651 return None;
7652 }
7653 model.get_or_insert_with(|| mm.clone());
7654 experts.push((gi, ui, di));
7655 }
7656 crate::gpu::GraphFfn::Moe {
7657 router,
7658 shared_gate: sgate,
7659 experts,
7660 n_exp: m.experts.len(),
7661 top_k: m.top_k,
7662 inter,
7663 norm_topk: m.norm_topk_prob,
7664 q4tp: q4tp?,
7665 gu_q2: gu_q2.unwrap_or(false),
7666 sigmoid: false,
7667 bias: None,
7668 has_shared: true,
7669 }
7670 }
7671 _ => return None,
7672 };
7673 let attn = match &lw.attn {
7674 AttnKind::Full {
7675 wq,
7676 wk,
7677 wv,
7678 wo,
7679 q_norm,
7680 k_norm,
7681 output_gate,
7682 softplus_gate,
7683 bias,
7684 } => {
7685 if softplus_gate.is_some() || self.attention_heads_per_layer.is_some() {
7686 return None;
7687 }
7688 let (m, _, _, _) = wq.graph_weight()?;
7689 model = Some(m.clone());
7690 crate::gpu::GraphAttn::Full {
7691 wq: gw(wq)?,
7692 wk: gw(wk)?,
7693 wv: gw(wv)?,
7694 wo: gw(wo)?,
7695 q_norm: q_norm.as_deref(),
7696 k_norm: k_norm.as_deref(),
7697 bias: bias
7698 .as_ref()
7699 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
7700 output_gate: *output_gate,
7701 cpu_k: self.kv_cache.layers[li].k_heads(),
7702 cpu_v: self.kv_cache.layers[li].v_heads(),
7703 }
7704 }
7705 AttnKind::LinearGdn(w) => {
7706 let cfg = self.gdn_cfg?;
7707 let (m, _, _, _) = w.in_proj_qkv.graph_weight()?;
7708 model = Some(m.clone());
7709 crate::gpu::GraphAttn::Gdn {
7710 qkv: gw(&w.in_proj_qkv)?,
7711 z: gw(&w.in_proj_z)?,
7712 a: gw(&w.in_proj_a)?,
7713 b: gw(&w.in_proj_b)?,
7714 out: gw(&w.out_proj)?,
7715 conv1d: &w.conv1d,
7716 a_log: &w.a_log,
7717 dt_bias: &w.dt_bias,
7718 norm: &w.norm,
7719 nv: cfg.num_v_heads,
7720 nk: cfg.num_k_heads,
7721 dk: cfg.key_head_dim,
7722 dv: cfg.value_head_dim,
7723 kk: cfg.conv_kernel,
7724 cpu_state: &self.kv_cache.layers[self.phys_layer(li)].linear_state,
7725 }
7726 }
7727 _ => return None,
7728 };
7729 layers.push(crate::gpu::GraphLayer {
7730 input_norm: &lw.input_norm,
7731 attn,
7732 post_norm: &lw.post_norm,
7733 ffn: gffn,
7734 });
7735 }
7736 Some((layers, model?))
7737 })();
7738 let Some((layers, model)) = built else {
7739 {
7740 use std::sync::atomic::{AtomicBool, Ordering};
7741 static SAID: AtomicBool = AtomicBool::new(false);
7742 if !SAID.swap(true, Ordering::Relaxed) {
7743 tracing::warn!("batch graph: BUILDER refused (layer weights/kinds)");
7744 }
7745 }
7746 return false;
7747 };
7748 if std::env::var("CMF_GRAPH_SPEC_TIME").is_ok() {
7749 eprintln!("batch-build: {:.1} ms", _tb.elapsed().as_secs_f64() * 1e3);
7750 }
7751 crate::gpu::forward_batch_graph(
7752 &model,
7753 self.graph_kv_id,
7754 &layers,
7755 &self.inv_freq,
7756 hiddens,
7757 nh,
7758 nkv,
7759 hd,
7760 rd,
7761 self.hidden_size,
7762 self.intermediate_size,
7763 positions,
7764 self.kv_cache.max_seq_len,
7765 gemma,
7766 self.rms_eps as f32,
7767 self.attn_scale,
7768 k,
7769 spec,
7770 )
7771 }
7772
7773 fn draft_probe() -> bool {
7777 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7778 *ON.get_or_init(|| std::env::var("CMF_DSV4_DRAFT_PROBE").is_ok_and(|v| v != "0"))
7779 }
7780
7781 #[cfg(feature = "gpu")]
7793 fn dsv4_spec_on() -> bool {
7794 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7795 *ON.get_or_init(|| {
7796 if let Ok(v) = std::env::var("CMF_DSV4_SPEC_RUN") {
7800 return v != "0";
7801 }
7802 std::env::var("CMF_DSV4_SPEC")
7809 .map(|v| v != "0")
7810 .unwrap_or_else(|_| {
7811 crate::gpu_wgpu::DRAFT_RESERVE.load(std::sync::atomic::Ordering::Relaxed) > 0
7812 })
7813 })
7814 }
7815
7816 #[cfg(feature = "gpu")]
7823 fn dsv4_spec_step(
7824 &mut self,
7825 tip_token: u32,
7826 t_next: u32,
7827 next_pos: usize,
7828 max_extra: usize,
7829 drafted: &mut usize,
7830 accepted_ctr: &mut usize,
7831 ) -> Option<(Vec<u32>, usize)> {
7832 let t_all = std::time::Instant::now();
7833 if std::env::var("CMF_DSV4_SPEC_TIME").is_ok() {
7834 thread_local! {
7835 static LAST: std::cell::Cell<Option<std::time::Instant>> =
7836 const { std::cell::Cell::new(None) };
7837 }
7838 LAST.with(|l| {
7839 if let Some(prev) = l.get() {
7840 eprintln!(
7841 "между раундами {:.1} мс",
7842 prev.elapsed().as_secs_f64() * 1e3
7843 );
7844 }
7845 l.set(Some(std::time::Instant::now()));
7846 });
7847 }
7848 if std::env::var("CMF_DSV4_SPEC_DEBUG").is_ok() {
7849 eprintln!("spec_step: вход pos={next_pos}");
7850 }
7851 let n_layers = self.dsv4.as_ref().map(|b| b.1.len())?;
7852 let cfg = self.dsv4.as_ref().map(|b| b.2)?;
7853 if self.dspark.is_none() {
7855 let t = crate::dsv4::dspark_targets(&self.dsv4_mtp, &cfg, n_layers);
7856 if t.is_empty() {
7857 return None;
7858 }
7859 crate::dsv4::dspark_arm(&t, cfg.dim);
7860 self.dspark = Some(crate::dsv4::DsparkState::new(
7861 self.dsv4_mtp.len(),
7862 &cfg,
7863 t.len(),
7864 ));
7865 }
7866 let targets = crate::dsv4::dspark_targets(&self.dsv4_mtp, &cfg, n_layers);
7867 let pack = crate::dsv4::dspark_pack_get(&self.dsv4_mtp, &cfg);
7868 if pack.is_none() && std::env::var("CMF_DSV4_SPEC_DEBUG").is_ok() {
7869 eprintln!("spec_step: пак не построился (targets {targets:?})");
7870 }
7871 let pack = pack?;
7872 let block = crate::dsv4::dspark_block();
7873 let b_box = self.dsv4.as_mut()?;
7874 let (g, layers, st) = (&b_box.0, &b_box.1, &mut b_box.3);
7875 let ds = self.dspark.as_mut()?;
7876 let dbg = std::env::var("CMF_DSV4_SPEC_DEBUG").is_ok();
7879 if !crate::dsv4::dspark_take(&mut ds.main_hidden) && !ds.have_hidden {
7880 if dbg {
7881 eprintln!("spec_step: нет захвата");
7882 }
7883 return None;
7884 }
7885 ds.have_hidden = true;
7886 let tip_pos = next_pos.checked_sub(1)?;
7887 let draft_started = std::time::Instant::now();
7888 let mut conf = Vec::new();
7889 let props = crate::dsv4::dspark_draft_gpu(
7890 g,
7891 &self.dsv4_mtp,
7892 &cfg,
7893 ds,
7894 pack,
7895 st.kv_id,
7896 tip_token,
7897 tip_pos,
7898 self.pool.as_deref(),
7899 &mut conf,
7900 );
7901 self.dspark_draft_ns += draft_started.elapsed().as_nanos();
7902 *drafted += block;
7903 if props.is_empty() || props[0] != t_next {
7904 if dbg {
7905 eprintln!(
7906 "spec_step: черновик {} (props0={:?} t_next={t_next})",
7907 if props.is_empty() {
7908 "пуст"
7909 } else {
7910 "мимо"
7911 },
7912 props.first()
7913 );
7914 }
7915 return None;
7916 }
7917 let mut k_verify = crate::dsv4::dspark_verify_k()
7924 .min(props.len())
7925 .min(max_extra.saturating_add(1));
7926 let conf_min = {
7932 static M: std::sync::OnceLock<f32> = std::sync::OnceLock::new();
7933 *M.get_or_init(|| {
7934 std::env::var("CMF_DSPARK_CONF_MIN")
7935 .ok()
7936 .and_then(|v| v.parse().ok())
7937 .unwrap_or(0.0)
7938 })
7939 };
7940 if conf_min > 0.0 && conf.len() >= props.len() {
7941 let mut keep = 1usize;
7942 while keep < k_verify && conf.get(keep).copied().unwrap_or(0.0) >= conf_min {
7943 keep += 1;
7944 }
7945 k_verify = k_verify.min(keep.max(2));
7946 }
7947 if k_verify < 2 {
7948 return None;
7949 }
7950 let mut fed = Vec::with_capacity(k_verify);
7951 fed.push(t_next);
7952 fed.extend_from_slice(&props[1..k_verify]);
7953 let mut argmax = Vec::new();
7954 let mut logits_all = Vec::new();
7955 let mut walked = Vec::new();
7956 let txn = crate::dsv4::dsv4_verify_chunk(
7957 g,
7958 layers,
7959 &cfg,
7960 st,
7961 &fed,
7962 next_pos,
7963 &self.inv_freq,
7964 self.pool.as_deref(),
7965 &targets,
7966 &mut argmax,
7967 &mut logits_all,
7968 &mut walked,
7969 );
7970 if txn.is_none() && dbg {
7971 eprintln!("spec_step: verify отказал");
7972 }
7973 let txn = txn?;
7974 let spec_gpu_end = txn.gpu_end;
7975 let b = fed.len();
7976 let mut accepted = 1usize;
7977 while accepted < b && fed[accepted] == argmax[accepted - 1] {
7978 accepted += 1;
7979 }
7980 if std::env::var("CMF_DSV4_SPEC_FORCE_REJECT").is_ok_and(|v| v != "0") {
7985 accepted = 1;
7986 }
7987 if std::env::var("CMF_DSV4_SPEC_TRACE").is_ok() {
7988 eprintln!("spec@{next_pos}: fed={fed:?} argmax={argmax:?} accepted={accepted}");
7989 }
7990 let t_fin = std::time::Instant::now();
7991 if !crate::dsv4::dsv4_spec_finish(
7992 g,
7993 layers,
7994 &cfg,
7995 st,
7996 txn,
7997 accepted,
7998 &fed,
7999 &self.inv_freq,
8000 self.pool.as_deref(),
8001 ) {
8002 tracing::warn!("dsv4: спекулятивный откат не удался — состояние подозрительно");
8003 return None;
8004 }
8005 if std::env::var("CMF_DSV4_SPEC_TIME").is_ok() {
8006 eprintln!(
8007 "finish(k={accepted}): {:.1} мс",
8008 t_fin.elapsed().as_secs_f64() * 1e3
8009 );
8010 }
8011 *accepted_ctr += accepted - 1;
8012 let (hc, dim) = (cfg.hc_mult, cfg.dim);
8017 let dev_caps: Vec<usize> = targets
8022 .iter()
8023 .copied()
8024 .filter(|&t| t < spec_gpu_end)
8025 .collect();
8026 let mut caps_all = vec![0.0f32; dev_caps.len() * b * hc * dim];
8027 if !crate::gpu_wgpu::dsv4_spec_cap_read_all(b, dev_caps.len(), hc * dim, &mut caps_all) {
8028 return None;
8029 }
8030 for t in 0..accepted {
8031 let tip = t + 1 == accepted;
8032 for (slot, &tl) in targets.iter().enumerate() {
8033 if let Some(di) = dev_caps.iter().position(|&d| d == tl) {
8034 let lo = (di * b + t) * hc * dim;
8035 crate::dsv4::dspark_capture(
8036 &caps_all[lo..lo + hc * dim],
8037 &cfg,
8038 slot,
8039 &mut ds.main_hidden,
8040 );
8041 } else if tip
8042 && crate::dsv4::dspark_peek_slot(slot, dim, {
8043 let lo = slot * dim;
8044 &mut ds.main_hidden[lo..lo + dim]
8045 })
8046 {
8047 } else {
8052 crate::dsv4::dspark_capture(
8056 &walked[t * hc * dim..(t + 1) * hc * dim],
8057 &cfg,
8058 slot,
8059 &mut ds.main_hidden,
8060 );
8061 }
8062 }
8063 crate::dsv4::dspark_ring_append(
8064 g,
8065 &self.dsv4_mtp,
8066 &cfg,
8067 ds,
8068 next_pos + t,
8069 self.pool.as_deref(),
8070 );
8071 }
8072 let row = logits_all[(accepted - 1) * cfg.vocab..accepted * cfg.vocab].to_vec();
8073 self.graph_logits = Some(row);
8074 if std::env::var("CMF_DSV4_TRUNK_PICK_DUMP").is_ok() {
8079 crate::dsv4::trunk_freq_note(&crate::dsv4::pick_tally_take());
8080 crate::dsv4::pick_tally_arm();
8081 }
8082 if std::env::var("CMF_DSV4_SPEC_TIME").is_ok() {
8083 eprintln!(
8084 "spec_step total {:.1} мс (k={accepted})",
8085 t_all.elapsed().as_secs_f64() * 1e3
8086 );
8087 }
8088 Some((fed[1..accepted].to_vec(), next_pos + accepted))
8089 }
8090
8091 fn dspark_probe(&mut self, position: usize, token_id: u32) {
8092 if self.dsv4_mtp.is_empty() || !Self::draft_probe() {
8093 return;
8094 }
8095 let trunk_now = crate::dsv4::pick_tally_take();
8097 crate::dsv4::trunk_freq_note(&trunk_now);
8098 if !trunk_now.is_empty() {
8099 self.dspark_trunk_picks.push(trunk_now);
8100 let keep = crate::dsv4::dspark_block();
8101 if self.dspark_trunk_picks.len() > keep {
8102 self.dspark_trunk_picks.remove(0);
8103 }
8104 }
8105 for p in std::mem::take(&mut self.dspark_pending) {
8108 let Some(i) = position.checked_sub(p.0 + 1) else {
8109 continue;
8110 };
8111 let mut p = p;
8112 if i < p.1.len() {
8113 if p.2 && p.1[i] == token_id {
8114 p.3 = i + 1;
8115 } else {
8116 p.2 = false;
8117 }
8118 if i + 1 < p.1.len() {
8119 self.dspark_pending.push(p);
8120 continue;
8121 }
8122 }
8123 self.dspark_hist.push(p.3);
8124 self.dspark_real.push(token_id);
8125 }
8126 let Some(b) = &mut self.dsv4 else { return };
8127 let (g, layers, cfg) = (&b.0, &b.1, b.2);
8128 let n_layers = layers.len();
8129 if self.dspark.is_none() {
8130 let t = crate::dsv4::dspark_targets(&self.dsv4_mtp, &cfg, n_layers);
8131 if t.is_empty() {
8132 return;
8133 }
8134 eprintln!(
8135 "DSpark: захват со слоёв {t:?}, блок {}",
8136 crate::dsv4::dspark_block()
8137 );
8138 crate::dsv4::dspark_arm(&t, cfg.dim);
8139 self.dspark = Some(crate::dsv4::DsparkState::new(
8140 self.dsv4_mtp.len(),
8141 &cfg,
8142 t.len(),
8143 ));
8144 }
8145 let ds = self.dspark.as_mut().unwrap();
8146 if !crate::dsv4::dspark_take(&mut ds.main_hidden) {
8147 return; }
8149 let mut conf = Vec::new();
8150 crate::dsv4::pick_tally_arm();
8151 let draft_started = std::time::Instant::now();
8156 #[cfg(feature = "gpu")]
8157 let gpu_draft = crate::dsv4::dspark_gpu_on();
8158 #[cfg(not(feature = "gpu"))]
8159 let gpu_draft = false;
8160 let props = if gpu_draft {
8161 #[cfg(feature = "gpu")]
8162 {
8163 let kv_id = b.3.kv_id;
8164 match crate::dsv4::dspark_pack_get(&self.dsv4_mtp, &cfg) {
8165 Some(pk) => crate::dsv4::dspark_draft_gpu(
8166 g,
8167 &self.dsv4_mtp,
8168 &cfg,
8169 ds,
8170 pk,
8171 kv_id,
8172 token_id,
8173 position,
8174 self.pool.as_deref(),
8175 &mut conf,
8176 ),
8177 None => Vec::new(),
8178 }
8179 }
8180 #[cfg(not(feature = "gpu"))]
8181 Vec::new()
8182 } else {
8183 crate::gpu::cpu_scope(|| {
8184 crate::dsv4::dspark_draft(
8185 g,
8186 &self.dsv4_mtp,
8187 &cfg,
8188 ds,
8189 token_id,
8190 position,
8191 self.pool.as_deref(),
8192 &mut conf,
8193 )
8194 })
8195 };
8196 self.dspark_draft_ns += draft_started.elapsed().as_nanos();
8197 let draft_picks = crate::dsv4::pick_tally_take();
8198 crate::dsv4::dspark_freq_note(&draft_picks);
8199 crate::dsv4::pick_tally_arm();
8202 if !props.is_empty() {
8203 let (tu, tt) = {
8207 let flat: Vec<(usize, Vec<usize>)> = self
8208 .dspark_trunk_picks
8209 .iter()
8210 .flat_map(|v| v.iter().cloned())
8211 .collect();
8212 let mut per: std::collections::HashMap<usize, Vec<usize>> =
8214 std::collections::HashMap::new();
8215 for (li, picks) in flat {
8216 per.entry(li).or_default().extend(picks);
8217 }
8218 let n = per.len().max(1);
8219 let mut u = 0usize;
8220 let mut t = 0usize;
8221 for (_, v) in per {
8222 t += v.len();
8223 u += v.iter().collect::<std::collections::HashSet<_>>().len();
8224 }
8225 (u / n, t / n)
8226 };
8227 let (du, dt) = crate::dsv4::tally_unique(&draft_picks);
8228 self.dspark_exp.push((tu, tt, du, dt));
8229 self.dspark_pending.push((position, props, true, 0));
8230 }
8231 if self.dspark_hist.len() >= 8 && self.dspark_hist.len() % 8 == 0 {
8232 let n = self.dspark_hist.len() as f32;
8233 let mean: f32 = self.dspark_hist.iter().sum::<usize>() as f32 / n;
8234 let block = crate::dsv4::dspark_block();
8235 let mut at = vec![0usize; block + 1];
8236 for &k in &self.dspark_hist {
8237 at[k] += 1;
8238 }
8239 let mut surv = Vec::with_capacity(block);
8241 for i in 1..=block {
8242 let k = at[i..].iter().sum::<usize>() as f32 / n;
8243 surv.push(format!("{k:.2}"));
8244 }
8245 let distinct = self
8246 .dspark_real
8247 .iter()
8248 .collect::<std::collections::HashSet<_>>()
8249 .len();
8250 let (tu, tt, du, dt) = self.dspark_exp.iter().fold((0, 0, 0, 0), |a, b| {
8251 (a.0 + b.0, a.1 + b.1, a.2 + b.2, a.3 + b.3)
8252 });
8253 let m = self.dspark_exp.len().max(1);
8254 eprintln!(
8255 "DSpark: черновиков {}, принято в среднем {mean:.2} из {block} \
8256 (токенов за проход {:.2}), распределение {at:?}, выживание [{}]",
8257 self.dspark_hist.len(),
8258 mean + 1.0,
8259 surv.join(" ")
8260 );
8261 eprintln!(
8262 "DSpark: разных токенов {distinct} из {} (вырожденность), \
8263 эксперты ствол {}/{} на слой за {block} токенов, \
8264 черновик {}/{} за блок, draft {:.2} мс/блок",
8265 self.dspark_real.len(),
8266 tu / m,
8267 tt / m,
8268 du / m,
8269 dt / m,
8270 self.dspark_draft_ns as f64 / self.dspark_exp.len().max(1) as f64 / 1e6
8271 );
8272 }
8273 }
8274
8275 fn forward_layers_upto(
8276 &mut self,
8277 hidden: &[f32],
8278 position: usize,
8279 task_mask: Option<&TaskMask>,
8280 upto: Option<usize>,
8281 ) -> Vec<f32> {
8282 if let Some(plan) = self.gpu_plan.clone() {
8288 if upto.is_none() && plan.len() > 1 {
8289 let mut h = hidden.to_vec();
8290 for &(dev, from, upto_incl) in plan.iter() {
8291 h = crate::gpu::with_device(dev, || {
8292 self.forward_layers_span(&h, position, task_mask, from, Some(upto_incl))
8293 });
8294 }
8295 return h;
8296 }
8297 }
8298 self.forward_layers_span(hidden, position, task_mask, 0, upto)
8299 }
8300
8301 pub fn set_gpu_plan(&mut self, devices: Option<&[usize]>) -> Result<(), String> {
8306 self.set_gpu_plan_at(devices, None)
8307 }
8308
8309 pub fn set_gpu_plan_at(
8313 &mut self,
8314 devices: Option<&[usize]>,
8315 at: Option<usize>,
8316 ) -> Result<(), String> {
8317 let Some(devs) = devices.filter(|d| d.len() > 1) else {
8318 self.gpu_plan = None;
8319 return Ok(());
8320 };
8321 self.split_supported()?;
8322 let n = self.num_layers;
8323 if devs.len() > n {
8324 return Err(format!("{} devices for {n} layers", devs.len()));
8325 }
8326 if let Some(k) = at {
8327 if k == 0 || k >= n {
8328 return Err(format!("split at {k}: the model has {n} layers"));
8329 }
8330 if devs.len() == 2 {
8331 self.gpu_plan = Some(std::sync::Arc::new(vec![
8332 (devs[0], 0, k - 1),
8333 (devs[1], k, n - 1),
8334 ]));
8335 return Ok(());
8336 }
8337 return Err(format!(
8338 "an explicit split point takes exactly 2 devices, got {}",
8339 devs.len()
8340 ));
8341 }
8342 let per = n.div_ceil(devs.len());
8343 let mut plan = Vec::with_capacity(devs.len());
8344 let mut from = 0usize;
8345 for &d in devs {
8346 if from >= n {
8347 break;
8348 }
8349 let upto = (from + per - 1).min(n - 1);
8350 plan.push((d, from, upto));
8351 from = upto + 1;
8352 }
8353 self.gpu_plan = Some(std::sync::Arc::new(plan));
8354 Ok(())
8355 }
8356
8357 pub fn gpu_plan(&self) -> Option<Vec<(usize, usize, usize)>> {
8359 self.gpu_plan.as_ref().map(|p| p.as_ref().clone())
8360 }
8361
8362 fn forward_layers_span(
8368 &mut self,
8369 hidden: &[f32],
8370 position: usize,
8371 task_mask: Option<&TaskMask>,
8372 from: usize,
8373 upto: Option<usize>,
8374 ) -> Vec<f32> {
8375 debug_assert!(
8376 from == 0 || (self.dsv4.is_none() && self.qwen4_exp.is_none() && self.g3n.is_none())
8377 );
8378 if let Some(b) = &mut self.qwen4_exp {
8379 let _ = (task_mask, upto);
8380 let token_id = hidden.first().copied().unwrap_or(0.0) as u32;
8381 let mut logits = Vec::new();
8382 crate::qwen4_exp::forward_token(
8383 &b.0,
8384 &b.1,
8385 &b.2,
8386 &mut b.3,
8387 token_id,
8388 position,
8389 &self.inv_freq,
8390 self.pool.as_deref(),
8391 &mut logits,
8392 true,
8393 );
8394 self.graph_logits = Some(logits);
8395 return vec![0.0; self.hidden_size];
8396 }
8397 if let Some(b) = &mut self.dsv4 {
8403 let _ = (task_mask, upto);
8404 let token_id = hidden.first().copied().unwrap_or(0.0) as u32;
8405 let (g, layers, cfg, st) = (&b.0, &b.1, b.2, &mut b.3);
8406 st.pos = position;
8407 let mut logits = Vec::new();
8408 crate::dsv4::forward_token(
8409 g,
8410 layers,
8411 &cfg,
8412 st,
8413 token_id,
8414 &self.inv_freq,
8415 self.pool.as_deref(),
8416 &mut logits,
8417 );
8418 self.graph_logits = Some(logits);
8419 self.dspark_probe(position, token_id);
8420 return vec![0.0; self.hidden_size];
8423 }
8424 if let Some(b) = &self.g3n {
8427 let _ = (task_mask, upto);
8428 return crate::g3n::g3n_forward(
8429 &b.0,
8430 &b.1,
8431 hidden,
8432 position,
8433 &mut self.kv_cache.layers,
8434 self.num_heads,
8435 self.num_kv_heads,
8436 self.head_dim,
8437 self.pool.as_deref(),
8438 );
8439 }
8440 let mut h = hidden.to_vec();
8441 let (nh, _nkv, _hd, hs, _rd, eps) = (
8444 self.num_heads,
8445 self.num_kv_heads,
8446 self.head_dim,
8447 self.hidden_size,
8448 self.rotary_dim,
8449 self.rms_eps,
8450 );
8451 let pool = self.pool.clone();
8452 let graph_env = std::env::var("CMF_GPU_WGPU_GRAPH").ok();
8464 let graph_on = match graph_env.as_deref() {
8465 Some("0") => false,
8466 Some("prefill") => false, Some(_) => true,
8468 None => crate::gpu::wgpu_graph_default(),
8474 };
8475 let graph_trusted =
8476 graph_env.is_some() || crate::gpu::wgpu_graph_default() || self.gdn_cfg.is_some();
8477 let race_eligible = graph_on
8478 && upto.is_none()
8479 && task_mask.is_none()
8480 && from == 0
8481 && !crate::gpu::graph_unsupported();
8482 let mut tail_start = 0usize;
8483 if race_eligible && crate::gpu::graph_race_use_graph(graph_trusted) {
8484 let t_graph = std::time::Instant::now();
8485 let mut lg = Vec::new();
8486 let mut gl = 0usize;
8487 let built = self.try_token_graph_wgpu(hidden, position, &mut lg, &mut gl);
8488 if built.is_none() && !self.o1_active() && self.attn_softcap == 0.0 {
8493 crate::gpu::graph_mark_unsupported();
8494 }
8495 graph_note(built.is_some());
8496 if let Some(hh) = built {
8497 let dur = t_graph.elapsed();
8498 if std::env::var("CMF_GRAPH_PROF").is_ok() {
8499 eprintln!("graph-call: {:.2} ms total", dur.as_secs_f64() * 1000.0);
8500 }
8501 if gl > 0 && gl < self.num_layers {
8502 h = hh;
8508 tail_start = gl;
8509 } else if graph_trusted || !crate::gpu::graph_race_first_token_hopeless(dur) {
8510 if !graph_trusted {
8511 crate::gpu::graph_race_record(true, dur);
8512 }
8513 if !lg.is_empty() {
8514 lg.resize(self.vocab_size, 0.0);
8517 if let Some(c) = self.final_softcap {
8518 for l in lg.iter_mut() {
8519 *l = c * (*l / c).tanh();
8520 }
8521 }
8522 self.graph_logits = Some(lg);
8523 }
8524 return hh;
8525 }
8526 }
8532 }
8533 let span = from > 0 || upto.is_some();
8557 if span && graph_on && task_mask.is_none() && graph_trusted {
8558 let upto_excl = upto.map_or(self.num_layers, |u| u + 1);
8559 let mut lg = Vec::new();
8560 let mut gl = 0usize;
8561 let span_res =
8562 self.try_token_graph_wgpu_span(hidden, position, &mut lg, from, upto_excl, &mut gl);
8563 graph_note(span_res.is_some() && gl == upto_excl - from);
8564 if std::env::var("CMF_GPU_DEBUG").is_ok() {
8565 static SEEN: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
8569 if SEEN.fetch_add(1, std::sync::atomic::Ordering::Relaxed) < 4 {
8570 eprintln!(
8571 "span graph: covered {gl} of {} layers [{from}..{upto_excl}) res={}",
8572 upto_excl - from,
8573 span_res.is_some()
8574 );
8575 }
8576 }
8577 if let Some(hh) = span_res {
8578 if gl == upto_excl - from {
8579 if !lg.is_empty() {
8580 lg.resize(self.vocab_size, 0.0);
8581 if let Some(c) = self.final_softcap {
8582 for l in lg.iter_mut() {
8583 *l = c * (*l / c).tanh();
8584 }
8585 }
8586 self.graph_logits = Some(lg);
8587 }
8588 crate::gpu::set_layer(-1);
8589 return hh;
8590 }
8591 h = hh;
8593 tail_start = from + gl;
8594 }
8595 }
8596 let t_race_cpu = (race_eligible && !graph_trusted).then(std::time::Instant::now);
8597
8598 let _host_tail = (tail_start > from).then(crate::gpu::enter_cpu_scope);
8604 let automatic_gpu_prefix = self.automatic_gpu_prefix();
8605
8606 #[cfg(target_os = "macos")]
8607 let mut gpu_skip_until = 0usize;
8608 for li in tail_start.max(from)..self.num_layers {
8609 let _capacity_tail = automatic_gpu_prefix
8610 .filter(|&prefix| li >= prefix)
8611 .map(|_| crate::gpu::enter_cpu_scope());
8612 crate::gpu::set_layer(li as i64); if let Some(u) = upto {
8614 if li > u {
8615 break;
8616 }
8617 }
8618 if let Some(mask) = task_mask {
8619 if !mask.layer_alive(li) {
8620 continue; }
8622 }
8623 #[cfg(target_os = "macos")]
8627 {
8628 if li < gpu_skip_until {
8629 continue;
8630 }
8631 if task_mask.is_none() {
8632 let end = self.q1_graph_gpu(li, upto, position, &mut h);
8633 if end > li {
8634 gpu_skip_until = end;
8635 if self.is_loop_end(end - 1) && end < self.num_layers {
8638 h = inference::rms_norm(
8639 &h,
8640 &self.weights.final_norm,
8641 self.rms_eps,
8642 self.norm_style,
8643 );
8644 }
8645 continue;
8646 }
8647 }
8648 }
8649
8650 let lw = &self.weights.layers[self.phys_layer(li)];
8651 if let Ok(tp) = std::env::var("CMF_TRACE_POS") {
8652 if tp.parse::<usize>().ok() == Some(position) {
8653 let n: f32 = h.iter().map(|x| x * x).sum::<f32>().sqrt();
8654 eprintln!(
8655 "TRACE pos {position} layer {li}: |h| = {n:.6} h0 {:.6} h1 {:.6}",
8656 h[0], h[1]
8657 );
8658 }
8659 }
8660 inference::rms_norm_into(
8663 &h,
8664 &lw.input_norm,
8665 self.rms_eps,
8666 self.norm_style,
8667 &mut self.ws.n1,
8668 );
8669
8670 let attn_out = match &lw.attn {
8671 AttnKind::Mla(w) => {
8672 let inv_freq_l = self.layer_inv_freq(li);
8673 let rs = self.layer_rope_scale(li);
8674 let eps = self.rms_eps;
8675 let pool = self.pool.clone();
8676 mla_attention(
8677 w,
8678 &self.ws.n1,
8679 &mut self.kv_cache.layers[li],
8680 position,
8681 &inv_freq_l,
8682 rs,
8683 eps,
8684 pool.as_deref(),
8685 )
8686 }
8687 AttnKind::Linear(w) => {
8688 let cfg = self.vmf_cfg.expect("linear layer without vmf_cfg");
8689 vmf_phase_forward(
8690 &self.ws.n1,
8691 w,
8692 &cfg,
8693 &mut self.kv_cache.layers[li].linear_state,
8694 self.pool.as_deref(),
8695 )
8696 }
8697 AttnKind::Kda(w) => {
8698 let cfg = self.kda_cfg.expect("kda layer without kda_cfg");
8699 crate::linear_core::kda_forward(
8700 &self.ws.n1,
8701 w,
8702 &cfg,
8703 &mut self.kv_cache.layers[li].linear_state,
8704 self.pool.as_deref(),
8705 )
8706 }
8707 AttnKind::LinearGdn(w) => {
8708 let cfg = self.gdn_cfg.expect("gdn layer without gdn_cfg");
8709 gdn_forward(
8710 &self.ws.n1,
8711 w,
8712 &cfg,
8713 &mut self.kv_cache.layers[li].linear_state,
8714 self.pool.as_deref(),
8715 )
8716 }
8717 AttnKind::ShortConv(w) => {
8718 let cfg = self
8719 .short_conv_cfg
8720 .expect("short-conv layer without short_conv_cfg");
8721 short_conv_forward(
8722 &self.ws.n1,
8723 w,
8724 &cfg,
8725 &mut self.kv_cache.layers[li].linear_state,
8726 self.pool.as_deref(),
8727 )
8728 }
8729 AttnKind::Full {
8730 wq,
8731 wk,
8732 wv,
8733 wo,
8734 q_norm,
8735 k_norm,
8736 output_gate,
8737 softplus_gate,
8738 bias,
8739 } if self.kv_cache.layers[li].o1_sealed() => {
8740 let inv_freq_l = self.layer_inv_freq(li);
8743 let (nkv_l, hd_l, rd_l) = self.layer_geom(li);
8744 let cfg = QwenAttnCfg {
8745 num_heads: self.layer_num_heads(li),
8746 num_kv_heads: nkv_l,
8747 head_dim: hd_l,
8748 hidden_size: hs,
8749 position,
8750 inv_freq: &inv_freq_l,
8751 rotary_dim: rd_l,
8752 scale: self.attn_scale,
8753 softcap: self.attn_softcap,
8754 window: None,
8755 v_norm: self.attn_v_norm,
8756 q_norm: q_norm.as_deref(),
8757 k_norm: k_norm.as_deref(),
8758 output_gate: *output_gate,
8759 softplus_gate: softplus_gate
8760 .as_ref()
8761 .map(|(gate, per_head)| (gate, *per_head)),
8762 rope_scale: self.layer_rope_scale(li),
8763 bias: bias
8764 .as_ref()
8765 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
8766 rms_eps: eps,
8767 norm_style: self.norm_style,
8768 pool: pool.as_deref(),
8769 };
8770 attention::qwen_attention_nystrom(
8771 &self.ws.n1,
8772 wq,
8773 wk,
8774 wv,
8775 wo,
8776 &mut self.kv_cache.layers[li],
8777 &cfg,
8778 )
8779 }
8780 AttnKind::Full {
8781 wq,
8782 wk,
8783 wv,
8784 wo,
8785 q_norm,
8786 k_norm,
8787 output_gate,
8788 softplus_gate,
8789 bias,
8790 } => 'attn: {
8791 if graph_on
8794 && !*output_gate
8795 && softplus_gate.is_none()
8796 && self.attention_heads_per_layer.is_none()
8797 && bias.is_none()
8798 && task_mask.is_none()
8799 {
8800 let inv_freq_l = self.layer_inv_freq(li);
8801 let (nkv_l, hd_l, rd_l) = self.layer_geom(li);
8802 let gemma = self.norm_style == cortiq_core::NormStyle::Gemma;
8803 if let (Some((gm, qi)), Some((_, ki)), Some((_, vi)), Some((_, oi))) = (
8804 wq.mapped_q1(),
8805 wk.mapped_q1(),
8806 wv.mapped_q1(),
8807 wo.mapped_q1(),
8808 ) {
8809 let gm = gm.clone();
8810 let mut out = vec![0f32; hs];
8811 let cache = &self.kv_cache.layers[li];
8812 if crate::gpu::attn_dropin(
8813 &gm,
8814 self.graph_kv_id,
8815 li,
8816 &self.ws.n1,
8817 qi,
8818 ki,
8819 vi,
8820 oi,
8821 q_norm.as_deref(),
8822 k_norm.as_deref(),
8823 &inv_freq_l,
8824 nh,
8825 nkv_l,
8826 hd_l,
8827 rd_l,
8828 hs,
8829 position,
8830 self.kv_cache.max_seq_len,
8831 gemma,
8832 eps as f32,
8833 cache.k_heads(),
8834 cache.v_heads(),
8835 &mut out,
8836 ) {
8837 break 'attn out;
8838 }
8839 }
8840 }
8841 let masked = task_mask
8842 .map(|m| m.head_flags(li, self.num_heads).iter().any(|&a| !a))
8843 .unwrap_or(false);
8844 let f32_view = (wq.as_f32(), wk.as_f32(), wv.as_f32(), wo.as_f32());
8845 match (masked, f32_view) {
8846 (true, (Some(q), Some(k), Some(v), Some(o))) => {
8849 let active_heads = task_mask.unwrap().head_flags(li, self.num_heads);
8850 attention::multi_head_attention(
8851 &self.ws.n1,
8852 q,
8853 k,
8854 v,
8855 o,
8856 &mut self.kv_cache.layers[li],
8857 self.num_heads,
8858 self.num_kv_heads,
8859 self.head_dim,
8860 self.hidden_size,
8861 position,
8862 &active_heads,
8863 &self.inv_freq,
8864 )
8865 }
8866 (masked, _) => {
8867 if masked {
8868 tracing::warn!(
8869 "layer {li}: head mask on quantized weights not \
8870 supported yet — executing dense"
8871 );
8872 }
8873 let inv_freq_l = self.layer_inv_freq(li);
8874 let (nkv_l, hd_l, rd_l) = self.layer_geom(li);
8875 let cfg = QwenAttnCfg {
8876 num_heads: self.layer_num_heads(li),
8877 num_kv_heads: nkv_l,
8878 head_dim: hd_l,
8879 hidden_size: hs,
8880 position,
8881 inv_freq: &inv_freq_l,
8882 rotary_dim: rd_l,
8883 scale: self.attn_scale,
8884 softcap: self.attn_softcap,
8885 window: self.layer_window(li),
8886 v_norm: self.attn_v_norm,
8887 q_norm: q_norm.as_deref(),
8888 k_norm: k_norm.as_deref(),
8889 output_gate: *output_gate,
8890 softplus_gate: softplus_gate
8891 .as_ref()
8892 .map(|(gate, per_head)| (gate, *per_head)),
8893 rope_scale: self.layer_rope_scale(li),
8894 bias: bias
8895 .as_ref()
8896 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
8897 rms_eps: eps,
8898 norm_style: self.norm_style,
8899 pool: pool.as_deref(),
8900 };
8901 attention::qwen_attention(
8902 &self.ws.n1,
8903 wq,
8904 wk,
8905 wv,
8906 wo,
8907 &mut self.kv_cache.layers[li],
8908 &cfg,
8909 )
8910 }
8911 }
8912 }
8913 };
8914 let attn_out = match &self.weights.layers[self.phys_layer(li)].attn_out_norm {
8917 Some(w) => inference::rms_norm(&attn_out, w, self.rms_eps, self.norm_style),
8918 None => attn_out,
8919 };
8920 let lw = &self.weights.layers[self.phys_layer(li)];
8921 inference::add_rmsnorm_fused_into(
8922 &mut h,
8923 &attn_out,
8924 &lw.post_norm,
8925 self.rms_eps,
8926 self.norm_style,
8927 &mut self.ws.p1,
8928 );
8929 let mut attn_out = attn_out;
8930 attention::recycle_buf(&mut attn_out);
8931 let post_normed = &self.ws.p1;
8932
8933 let ffn_masked = task_mask
8934 .map(|m| m.ffn_active_count(li) < self.intermediate_size)
8935 .unwrap_or(false);
8936 let ffn_out = match (ffn_masked, &lw.ffn) {
8948 (_, FfnKind::Dense(d)) if !d.segs.is_empty() => {
8952 let row = task_mask
8953 .and_then(|tm| tm.ffn_masks.get(li))
8954 .map(|v| v.as_slice());
8955 tube_ffn(d, post_normed, 1, self.pool.as_deref(), row)
8956 }
8957 (true, FfnKind::Dense(d)) => {
8958 let tm = task_mask.unwrap();
8959 let alive = tm.ffn_active_count(li);
8960 let deep = alive * 2 <= self.intermediate_size;
8961 if deep && d.down_proj.sparse_col_ok() {
8962 let active = tm.ffn_active_indices(li);
8963 sparse_ffn_quant(
8964 d,
8965 post_normed,
8966 &active,
8967 self.hidden_size,
8968 self.pool.as_deref(),
8969 )
8970 } else if deep
8971 && let (Some(g), Some(u), Some(dn)) = (
8972 d.gate_proj.as_f32(),
8973 d.up_proj.as_f32(),
8974 d.down_proj.as_f32(),
8975 )
8976 {
8977 let active = tm.ffn_active_indices(li);
8978 inference::sparse_ffn_forward(
8979 post_normed,
8980 g,
8981 u,
8982 dn,
8983 self.hidden_size,
8984 self.intermediate_size,
8985 &active,
8986 self.pool.as_deref(),
8987 )
8988 } else {
8989 let row = tm.ffn_masks.get(li).map(|v| v.as_slice());
8990 dense_ffn_batch(d, post_normed, 1, self.pool.as_deref(), row)
8991 }
8992 }
8993 (true, FfnKind::Moe(m)) => {
8994 let allowed = task_mask.and_then(|tm| tm.expert_flags(li, m.experts.len()));
8998 ffn_forward(
8999 &lw.ffn,
9000 post_normed,
9001 self.pool.as_deref(),
9002 allowed.as_deref(),
9003 )
9004 }
9005 (true, FfnKind::DenseMoe(dm)) => dense_moe_ffn(
9006 dm,
9007 post_normed,
9008 &h,
9009 self.rms_eps,
9010 self.norm_style,
9011 self.pool.as_deref(),
9012 ),
9013 (false, _) => match &lw.ffn {
9014 FfnKind::DenseMoe(dm) => dense_moe_ffn(
9015 dm,
9016 post_normed,
9017 &h,
9018 self.rms_eps,
9019 self.norm_style,
9020 self.pool.as_deref(),
9021 ),
9022 _ => {
9023 let allowed = match (&lw.ffn, task_mask) {
9024 (FfnKind::Moe(m), Some(tm)) => tm.expert_flags(li, m.experts.len()),
9025 _ => None,
9026 };
9027 ffn_forward(
9028 &lw.ffn,
9029 post_normed,
9030 self.pool.as_deref(),
9031 allowed.as_deref(),
9032 )
9033 }
9034 },
9035 };
9036 let ffn_out = match &self.weights.layers[self.phys_layer(li)].ffn_out_norm {
9037 Some(w) => inference::rms_norm(&ffn_out, w, self.rms_eps, self.norm_style),
9038 None => ffn_out,
9039 };
9040 for (i, &f) in ffn_out.iter().enumerate() {
9041 h[i] += f;
9042 }
9043 let mut ffn_out = ffn_out;
9044 attention::recycle_buf(&mut ffn_out);
9045
9046 if let Some(sc) = self.weights.layers[self.phys_layer(li)].layer_scale {
9048 for v in h.iter_mut() {
9049 *v *= sc;
9050 }
9051 }
9052
9053 if self.is_loop_end(li) && li + 1 < self.num_layers {
9056 h = inference::rms_norm(
9057 &h,
9058 &self.weights.final_norm,
9059 self.rms_eps,
9060 self.norm_style,
9061 );
9062 }
9063
9064 if self.dyn_phi_layer == Some(li) {
9068 self.update_dyn_phi(&h);
9069 }
9070 }
9071 crate::gpu::set_layer(-1); if let Some(t) = t_race_cpu {
9073 crate::gpu::graph_race_record(false, t.elapsed());
9074 }
9075
9076 h
9077 }
9078
9079 fn update_dyn_phi(&mut self, h: &[f32]) {
9082 const A: f32 = 0.2;
9083 if self.dyn_phi_ema.len() != h.len() {
9084 self.dyn_phi_ema = vec![0.0; h.len()];
9085 self.dyn_phi_seen = 0;
9086 }
9087 if self.dyn_phi_seen == 0 {
9088 self.dyn_phi_ema.copy_from_slice(h);
9089 } else {
9090 for (e, &v) in self.dyn_phi_ema.iter_mut().zip(h) {
9091 *e = (1.0 - A) * *e + A * v;
9092 }
9093 }
9094 self.dyn_phi_seen += 1;
9095 }
9096
9097 pub fn dyn_phi(&self) -> &[f32] {
9099 &self.dyn_phi_ema
9100 }
9101
9102 pub fn set_dyn_phi_layer(&mut self, layer: Option<usize>) {
9104 self.dyn_phi_layer = layer;
9105 self.dyn_phi_ema.clear();
9106 self.dyn_phi_seen = 0;
9107 }
9108
9109 pub fn dynamic_skills(&self) -> Vec<(usize, String, usize)> {
9111 let Some(model) = &self.model else {
9112 return Vec::new();
9113 };
9114 model
9115 .header
9116 .skills
9117 .iter()
9118 .enumerate()
9119 .filter_map(|(i, sk)| {
9120 let ok = matches!(self.dyn_skill_layers.get(i), Some(Some(_)));
9121 let sel = sk.selection.as_ref()?;
9122 (ok).then(|| (i, sk.id.clone(), sel.phi_layer))
9123 })
9124 .collect()
9125 }
9126
9127 pub fn active_skill(&self) -> Option<usize> {
9129 self.dyn_active
9130 }
9131
9132 pub fn enable_dynamic_routing(&mut self) -> usize {
9137 use crate::swarm::{DynRouter, RoutableSkill};
9138 let Some(model) = self.model.clone() else {
9139 return 0;
9140 };
9141 if self.dyn_blend_loaded {
9144 tracing::warn!("dynamic routing unavailable on a blend-loaded pipeline");
9145 return 0;
9146 }
9147 if let Some(a) = self.dyn_active {
9151 if !matches!(self.dyn_skill_layers.get(a), Some(Some(_))) {
9152 tracing::warn!("loaded skill is not FFN-eligible — dynamic routing unavailable");
9153 return 0;
9154 }
9155 }
9156 let hidden = self.hidden_size;
9157 let mut skills = Vec::new();
9158 for (idx, id, _phi) in self.dynamic_skills() {
9159 if let Some(sel) = model.header.skills[idx].selection.as_ref() {
9160 if let Some(rs) = RoutableSkill::from_descriptor(idx, id, sel, hidden) {
9161 skills.push(rs);
9162 }
9163 }
9164 }
9165 if skills.is_empty() {
9166 return 0;
9167 }
9168 let phi = skills[0].phi_layer;
9170 if skills.iter().any(|s| s.phi_layer != phi) {
9171 tracing::warn!("routable skills disagree on phi_layer; using {phi}");
9172 }
9173 let n = skills.len();
9174 self.set_dyn_phi_layer(Some(phi));
9175 self.dyn_router = Some(DynRouter::new(skills));
9176 n
9177 }
9178
9179 pub fn route_switches(&self) -> Vec<(usize, Option<String>, Option<String>)> {
9181 self.dyn_router
9182 .as_ref()
9183 .map(|r| r.switches.clone())
9184 .unwrap_or_default()
9185 }
9186
9187 fn lm_head_forward(&self, hidden: &[f32]) -> Vec<f32> {
9190 let rows = self.weights.lm_head.rows();
9191 let mut logits = attention::take_buf(rows.min(self.vocab_size));
9192 self.weights
9193 .lm_head
9194 .matvec(hidden, &mut logits, self.pool.as_deref());
9195 logits.resize(self.vocab_size, 0.0);
9196 if let Some(m) = self.logit_multiplier {
9197 for l in logits.iter_mut() {
9198 *l *= m;
9199 }
9200 }
9201 if let Some(c) = self.final_softcap {
9202 for l in logits.iter_mut() {
9203 *l = c * (*l / c).tanh();
9204 }
9205 }
9206 if let Some(cm) = self.head_clusters.as_ref() {
9207 self.hierarchical_head_logprobs(hidden, cm, &mut logits);
9208 }
9209 logits
9210 }
9211
9212 fn hierarchical_head_logprobs(&self, hidden: &[f32], cm: &[f32], logits: &mut [f32]) {
9215 let h = hidden.len();
9216 let ncl = cm.len() / h.max(1);
9217 if ncl == 0 || logits.len() % ncl != 0 {
9218 return;
9219 }
9220 let cs = logits.len() / ncl;
9221 let mut lc = vec![0.0f32; ncl];
9223 for c in 0..ncl {
9224 let row = &cm[c * h..(c + 1) * h];
9225 let mut s = 0.0f32;
9226 for j in 0..h {
9227 s += row[j] * hidden[j];
9228 }
9229 lc[c] = s;
9230 }
9231 let mx = lc.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
9232 let lse: f32 = mx + lc.iter().map(|v| (v - mx).exp()).sum::<f32>().ln();
9233 for c in 0..ncl {
9234 let blk = &mut logits[c * cs..(c + 1) * cs];
9235 let bm = blk.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
9236 let bl: f32 = bm + blk.iter().map(|v| (v - bm).exp()).sum::<f32>().ln();
9237 let add = lc[c] - lse - bl;
9238 for v in blk.iter_mut() {
9239 *v += add;
9240 }
9241 }
9242 }
9243
9244 pub fn prefill_next_logits(&mut self, ids: &[u32], task_mask: Option<&TaskMask>) -> Vec<f32> {
9249 self.kv_cache.clear();
9250 self.kv_history.clear();
9251 let mut hidden = vec![0.0f32; self.hidden_size];
9252 for (pos, &id) in ids.iter().enumerate() {
9253 let emb = self.embed_single(id);
9254 hidden = self.forward_layers(&emb, pos, task_mask);
9255 }
9256 inference::rms_norm_into(
9257 &hidden,
9258 &self.weights.final_norm,
9259 self.rms_eps,
9260 self.norm_style,
9261 &mut self.ws.n1,
9262 );
9263 self.lm_head_forward(&self.ws.n1)
9264 }
9265}
9266
9267pub fn create_test_pipeline(
9269 hidden_size: usize,
9270 intermediate_size: usize,
9271 num_heads: usize,
9272 num_kv_heads: usize,
9273 head_dim: usize,
9274 num_layers: usize,
9275 vocab_size: usize,
9276) -> Pipeline {
9277 let synth = |n: usize, salt: usize| -> Vec<f32> {
9280 (0..n)
9281 .map(|i| (((i * 31 + salt * 17 + 7) % 97) as f32 / 97.0 - 0.5) * 0.2)
9282 .collect()
9283 };
9284 let qt = |rows: usize, cols: usize, salt: usize| -> QTensor {
9285 QTensor::from_f32(synth(rows * cols, salt), rows, cols)
9286 };
9287 let layer_weights: Vec<LayerWeights> = (0..num_layers)
9288 .map(|li| LayerWeights {
9289 input_norm: vec![1.0; hidden_size],
9290 post_norm: vec![1.0; hidden_size],
9291 attn_out_norm: None,
9292 ffn_out_norm: None,
9293 layer_scale: None,
9294 ffn: FfnKind::Dense(DenseFfn {
9295 gate_proj: qt(intermediate_size, hidden_size, li * 10 + 5),
9296 up_proj: qt(intermediate_size, hidden_size, li * 10 + 6),
9297 down_proj: qt(hidden_size, intermediate_size, li * 10 + 7),
9298 act: Act::Silu,
9299 down_t: None,
9300 segs: Vec::new(),
9301 }),
9302 attn: AttnKind::Full {
9303 bias: None,
9304 wq: qt(num_heads * head_dim, hidden_size, li * 10 + 1),
9305 wk: qt(num_kv_heads * head_dim, hidden_size, li * 10 + 2),
9306 wv: qt(num_kv_heads * head_dim, hidden_size, li * 10 + 3),
9307 wo: qt(hidden_size, num_heads * head_dim, li * 10 + 4),
9308 q_norm: None,
9309 k_norm: None,
9310 output_gate: false,
9311 softplus_gate: None,
9312 },
9313 })
9314 .collect();
9315
9316 Pipeline::new(
9317 Tokenizer::byte_level(),
9318 PipelineWeights {
9319 embed_tokens: qt(vocab_size, hidden_size, 100),
9320 layers: layer_weights,
9321 lm_head: qt(vocab_size, hidden_size, 200),
9322 final_norm: vec![1.0; hidden_size],
9323 },
9324 hidden_size,
9325 intermediate_size,
9326 num_heads,
9327 num_kv_heads,
9328 head_dim,
9329 num_layers,
9330 num_layers, false, vocab_size,
9333 1e-6,
9334 10_000.0,
9335 NormStyle::Qwen,
9336 4096,
9337 SamplerConfig {
9338 seed: Some(42),
9339 ..Default::default()
9340 },
9341 )
9342}
9343
9344#[inline]
9349fn mask_bit(row: &[u8], j: usize) -> bool {
9350 (row.get(j >> 3).copied().unwrap_or(0) >> (j & 7)) & 1 != 0
9351}
9352
9353fn mask_gain() -> f32 {
9364 static G: std::sync::OnceLock<f32> = std::sync::OnceLock::new();
9365 *G.get_or_init(|| {
9366 std::env::var("CMF_FFN_MASK_GAIN")
9367 .ok()
9368 .and_then(|v| v.parse().ok())
9369 .unwrap_or(1.0)
9370 })
9371}
9372
9373fn zero_masked_cols(g: &mut [f32], rows: usize, inter: usize, row: &[u8]) {
9374 let fill = meanfill().and_then(|(i, v)| {
9377 let li = crate::gpu::cur_layer();
9378 (*i == inter && li >= 0).then(|| &v[li as usize * inter..(li as usize + 1) * inter])
9379 });
9380 for r in 0..rows {
9381 let base = r * inter;
9382 for (bi, &byte) in row.iter().enumerate() {
9383 if byte == 0xFF {
9384 continue;
9385 }
9386 let j0 = bi * 8;
9387 for bit in 0..8 {
9388 let j = j0 + bit;
9389 if j < inter && byte & (1 << bit) == 0 {
9390 g[base + j] = fill.map_or(0.0, |f| f[j]);
9391 }
9392 }
9393 }
9394 }
9395 let gain = mask_gain();
9396 if gain != 1.0 {
9397 for v in g[..rows * inter].iter_mut() {
9398 *v *= gain;
9399 }
9400 }
9401}
9402
9403#[inline]
9405fn tube_bit(row: Option<&[u8]>, i: usize) -> bool {
9406 row.is_none_or(|r| mask_bit(r, i))
9407}
9408
9409fn all_bits_on(row: &[u8], n: usize) -> bool {
9412 (0..n).all(|i| mask_bit(row, i))
9413}
9414
9415fn tube_topk() -> usize {
9423 static K: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
9424 *K.get_or_init(|| {
9425 std::env::var("CMF_TUBE_TOPK")
9426 .ok()
9427 .and_then(|v| v.parse().ok())
9428 .unwrap_or(0)
9429 })
9430}
9431
9432fn tube_score_oracle() -> bool {
9433 static O: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9434 *O.get_or_init(|| std::env::var("CMF_TUBE_SCORE").is_ok_and(|v| v == "oracle"))
9435}
9436
9437fn tube_ffn_routed(
9444 d: &DenseFfn,
9445 xs: &[f32],
9446 b: usize,
9447 pool: Option<&Pool>,
9448 mask_row: Option<&[u8]>,
9449 k: usize,
9450) -> Vec<f32> {
9451 let hidden = d.down_proj.rows();
9452 let core = d.gate_proj.rows();
9453 let core_full = mask_row.is_none_or(|r| all_bits_on(r, core));
9454 let mut out = match (b, core_full, mask_row) {
9455 (1, true, _) => dense_ffn(d, xs, pool),
9456 (1, false, Some(row)) => dense_ffn_masked(d, xs, pool, row),
9457 (_, true, _) => dense_ffn_batch(d, xs, b, pool, None),
9458 (_, false, row) => dense_ffn_batch(d, xs, b, pool, row),
9459 };
9460 let cand: Vec<usize> = (0..d.segs.len())
9461 .filter(|&i| tube_bit(mask_row, d.segs[i].start))
9462 .collect();
9463 if cand.is_empty() {
9464 return out;
9465 }
9466 let oracle = tube_score_oracle();
9470 let mut acts: Vec<Vec<f32>> = Vec::with_capacity(cand.len());
9471 let mut scores = vec![0f32; b * cand.len()];
9472 for (ci, &i) in cand.iter().enumerate() {
9473 let seg = &d.segs[i];
9474 let w = seg.width;
9475 let mut g = vec![0.0f32; b * w];
9476 if b == 1 {
9477 seg.gate.matvec(xs, &mut g, pool);
9478 } else {
9479 seg.gate.matmat(xs, b, &mut g, pool);
9480 }
9481 for v in g.iter_mut() {
9482 *v = Act::Silu.combine(*v, 1.0);
9483 }
9484 if !oracle {
9485 for t in 0..b {
9486 scores[t * cand.len() + ci] =
9487 g[t * w..(t + 1) * w].iter().map(|v| v * v).sum::<f32>();
9488 }
9489 }
9490 if oracle || b > 1 {
9491 let mut u = vec![0.0f32; b * w];
9492 if b == 1 {
9493 seg.up.matvec(xs, &mut u, pool);
9494 } else {
9495 seg.up.matmat(xs, b, &mut u, pool);
9496 }
9497 for (a, &v) in g.iter_mut().zip(u.iter()) {
9498 *a *= v;
9499 }
9500 if oracle {
9501 for t in 0..b {
9502 scores[t * cand.len() + ci] =
9503 g[t * w..(t + 1) * w].iter().map(|v| v * v).sum::<f32>();
9504 }
9505 }
9506 }
9507 acts.push(g);
9508 }
9509 let keep = k.min(cand.len());
9511 let mut scratch: Vec<f32> = Vec::new();
9512 for t in 0..b {
9513 let mut sc: Vec<(f32, usize)> = (0..cand.len())
9514 .map(|ci| (scores[t * cand.len() + ci], ci))
9515 .collect();
9516 sc.sort_unstable_by(|x, y| y.0.total_cmp(&x.0));
9517 let mut alive = vec![false; cand.len()];
9518 for &(_, ci) in sc.iter().take(keep) {
9519 alive[ci] = true;
9520 }
9521 if b > 1 {
9522 for (ci, a) in acts.iter_mut().enumerate() {
9523 if !alive[ci] {
9524 let w = d.segs[cand[ci]].width;
9525 a[t * w..(t + 1) * w].fill(0.0);
9526 }
9527 }
9528 } else {
9529 for (ci, &i) in cand.iter().enumerate() {
9533 if !alive[ci] {
9534 continue;
9535 }
9536 let seg = &d.segs[i];
9537 let w = seg.width;
9538 let g = &mut acts[ci];
9539 if !tube_score_oracle() {
9540 scratch.clear();
9541 scratch.resize(w, 0.0);
9542 seg.up.matvec(xs, &mut scratch, pool);
9543 for (a, &v) in g.iter_mut().zip(scratch.iter()) {
9544 *a *= v;
9545 }
9546 }
9547 let mut acc = vec![0.0f32; hidden];
9548 seg.down.matvec(g, &mut acc, pool);
9549 for (o, a) in out.iter_mut().zip(&acc) {
9550 *o += *a;
9551 }
9552 }
9553 }
9554 }
9555 if b > 1 {
9556 for (ci, &i) in cand.iter().enumerate() {
9557 let seg = &d.segs[i];
9558 let mut acc = vec![0.0f32; b * hidden];
9559 seg.down.matmat(&acts[ci], b, &mut acc, pool);
9560 for (o, a) in out.iter_mut().zip(&acc) {
9561 *o += *a;
9562 }
9563 }
9564 }
9565 out
9566}
9567
9568fn tube_ffn(
9574 d: &DenseFfn,
9575 xs: &[f32],
9576 b: usize,
9577 pool: Option<&Pool>,
9578 mask_row: Option<&[u8]>,
9579) -> Vec<f32> {
9580 if tube_topk() > 0 {
9581 return tube_ffn_routed(d, xs, b, pool, mask_row, tube_topk());
9582 }
9583 let hidden = d.down_proj.rows();
9584 let core = d.gate_proj.rows();
9585 let core_full = mask_row.is_none_or(|r| all_bits_on(r, core));
9586 let mut out = match (b, core_full, mask_row) {
9587 (1, true, _) => dense_ffn(d, xs, pool),
9588 (1, false, Some(row)) => dense_ffn_masked(d, xs, pool, row),
9589 (_, true, _) => dense_ffn_batch(d, xs, b, pool, None),
9590 (_, false, row) => dense_ffn_batch(d, xs, b, pool, row),
9591 };
9592 TUBE_SCRATCH.with(|sc| {
9593 let mut sc = sc.borrow_mut();
9594 let [g, u, acc] = &mut *sc;
9595 for seg in &d.segs {
9596 if !tube_bit(mask_row, seg.start) {
9597 continue;
9598 }
9599 let w = seg.width;
9600 g.resize(b * w, 0.0);
9601 if b == 1
9602 && d.act == Act::Silu
9603 && QTensor::matvec_silu_mul(&seg.gate, &seg.up, xs, g, pool)
9604 {
9605 } else {
9607 u.resize(b * w, 0.0);
9608 if b == 1 {
9609 QTensor::matvec_many([&seg.gate, &seg.up], xs, [g, u], pool);
9610 } else {
9611 seg.gate.matmat(xs, b, g, pool);
9612 seg.up.matmat(xs, b, u, pool);
9613 }
9614 for i in 0..b * w {
9615 g[i] = d.act.combine(g[i], u[i]);
9616 }
9617 }
9618 acc.resize(b * hidden, 0.0);
9619 acc.fill(0.0);
9620 if b == 1 {
9621 seg.down.matvec(g, acc, pool);
9622 } else {
9623 seg.down.matmat(g, b, acc, pool);
9624 }
9625 for (o, a) in out.iter_mut().zip(acc.iter()) {
9626 *o += *a;
9627 }
9628 }
9629 out
9630 })
9631}
9632
9633thread_local! {
9634 static TUBE_SCRATCH: std::cell::RefCell<[Vec<f32>; 3]> =
9638 const { std::cell::RefCell::new([Vec::new(), Vec::new(), Vec::new()]) };
9639}
9640
9641fn dense_ffn_batch(
9642 d: &DenseFfn,
9643 xs: &[f32],
9644 b: usize,
9645 pool: Option<&Pool>,
9646 mask_row: Option<&[u8]>,
9647) -> Vec<f32> {
9648 let inter = d.gate_proj.rows();
9649 let hidden = d.down_proj.rows();
9650 if mask_row.is_none()
9658 && d.act == Act::Silu
9659 && b >= 32
9660 && crate::gpu::enabled_here()
9661 && !crate::gpu::mm_killed()
9662 && refit_dir().is_none()
9667 && !ffn_probe_active()
9672 {
9673 if let (Some((model, w1)), Some((_, w3)), Some((_, w2))) = (
9674 d.gate_proj.mapped_q4t(),
9675 d.up_proj.mapped_q4t(),
9676 d.down_proj.mapped_q4t(),
9677 ) {
9678 let mut out = vec![0.0f32; b * hidden];
9679 if crate::gpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, &mut out) {
9680 return out;
9681 }
9682 }
9683 if let (Some((model, w1)), Some((_, w3)), Some((_, w2))) = (
9688 d.gate_proj.mapped_q4tp(),
9689 d.up_proj.mapped_q4tp(),
9690 d.down_proj.mapped_q4tp(),
9691 ) {
9692 let mut out = vec![0.0f32; b * hidden];
9693 if crate::gpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, &mut out) {
9694 return out;
9695 }
9696 }
9697 }
9698 let mut g = vec![0.0f32; b * inter];
9699 d.gate_proj.matmat(xs, b, &mut g, pool);
9700 let mut u = vec![0.0f32; b * inter];
9701 d.up_proj.matmat(xs, b, &mut u, pool);
9702 if gate_topk() > 0 && d.act == Act::Silu {
9703 for t in 0..b {
9704 let row = &mut g[t * inter..(t + 1) * inter];
9705 for v in row.iter_mut() {
9706 *v = Act::Silu.combine(*v, 1.0);
9707 }
9708 keep_top_k(row, gate_topk());
9709 }
9710 for i in 0..b * inter {
9711 g[i] *= u[i];
9712 }
9713 } else {
9714 for i in 0..b * inter {
9715 g[i] = d.act.combine(g[i], u[i]);
9716 }
9717 }
9718 if let Some(row) = mask_row {
9719 zero_masked_cols(&mut g, b, inter, row);
9720 }
9721 if oracle_topk() > 0 {
9722 for t in 0..b {
9723 keep_top_k(&mut g[t * inter..(t + 1) * inter], oracle_topk());
9724 }
9725 }
9726 let mut out = vec![0.0f32; b * hidden];
9727 d.down_proj.matmat(&g, b, &mut out, pool);
9728 if refit_dir().is_some() {
9729 let li = crate::gpu::cur_layer();
9730 if li >= 0 {
9731 refit_accumulate(li as usize, &g, b, inter, &out, hidden, pool);
9732 }
9733 }
9734 FFN_PROBE.with(|pr| {
9738 if let Some(acc) = pr.borrow_mut().as_mut() {
9739 let li = crate::gpu::cur_layer();
9740 if li < 0 {
9741 return;
9742 }
9743 let Some(row) = acc.get_mut(li as usize) else {
9744 return;
9745 };
9746 let sq = probe_sq();
9747 for t in 0..b {
9748 for (a, &v) in row.iter_mut().zip(&g[t * inter..(t + 1) * inter]) {
9749 *a += if sq {
9750 (v as f64) * (v as f64)
9751 } else {
9752 (v as f64).abs()
9753 };
9754 }
9755 }
9756 }
9757 });
9758 out
9759}
9760
9761fn accumulate_act(m: &MoeFfn, xs: &[f32], b: usize) {
9766 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9767 static DUMP: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9768 let on = *ON.get_or_init(|| std::env::var("CMF_RMS_TRACE").is_ok());
9769 let dump = *DUMP.get_or_init(|| std::env::var("CMF_ACT_DUMP").is_ok());
9770 if (!on && !dump) || b == 0 {
9771 return;
9772 }
9773 let hidden = xs.len() / b;
9774 if on {
9775 let mut acc = m.act_sq.borrow_mut();
9776 if acc.len() < hidden {
9777 acc.resize(hidden, 0.0);
9778 }
9779 for t in 0..b {
9780 let row = &xs[t * hidden..(t + 1) * hidden];
9781 for (a, &v) in acc.iter_mut().zip(row) {
9782 *a += (v as f64) * (v as f64);
9783 }
9784 }
9785 }
9786 if dump {
9787 let cap: usize = std::env::var("CMF_ACT_DUMP_ROWS")
9790 .ok()
9791 .and_then(|v| v.parse().ok())
9792 .unwrap_or(4096);
9793 let mut rows = m.act_rows.borrow_mut();
9794 if rows.len() < cap * hidden {
9795 let take = b.min((cap * hidden - rows.len()) / hidden.max(1));
9796 rows.extend_from_slice(&xs[..take * hidden]);
9797 }
9798 }
9799}
9800
9801#[derive(Clone, Copy)]
9804struct SendVecs(*mut Vec<f32>);
9805unsafe impl Send for SendVecs {}
9806unsafe impl Sync for SendVecs {}
9807impl SendVecs {
9808 #[inline]
9809 fn at(self, i: usize) -> *mut Vec<f32> {
9810 unsafe { self.0.add(i) }
9811 }
9812}
9813
9814fn moe_ffn_batch(
9815 m: &MoeFfn,
9816 xs: &[f32],
9817 b: usize,
9818 hidden: usize,
9819 pool: Option<&Pool>,
9820 allowed: Option<&[bool]>,
9821) -> Vec<f32> {
9822 accumulate_act(m, xs, b);
9823 let ne = m.experts.len();
9824 let mut logits = vec![0.0f32; b * ne];
9825 match &m.resonance {
9826 Some(r) => {
9827 let hdim = xs.len() / b.max(1);
9828 for bi in 0..b {
9829 r.scores(
9830 &xs[bi * hdim..(bi + 1) * hdim],
9831 &mut logits[bi * ne..(bi + 1) * ne],
9832 );
9833 }
9834 }
9835 None => m.router.matmat(xs, b, &mut logits, pool),
9836 }
9837
9838 let mut assign: Vec<Vec<(usize, f32)>> = vec![Vec::new(); ne];
9841 {
9842 let mut st = m.stats.borrow_mut();
9843 if st.len() < ne {
9844 st.resize(ne, 0);
9845 }
9846 for bi in 0..b {
9847 let (idx, p, wsum) = moe_route(&logits[bi * ne..(bi + 1) * ne], m, allowed);
9848 for &e in &idx {
9849 st[e] += 1;
9850 assign[e].push((bi, p[e] / wsum));
9851 }
9852 }
9853 }
9854
9855 let mut out = vec![0.0f32; b * hidden];
9856 let cols = m.experts[0].gate_proj.cols();
9857 let run_expert = |d: &DenseFfn, list: &[(usize, f32)], out: &mut [f32]| {
9858 let sb = list.len();
9859 let mut sub = vec![0.0f32; sb * cols];
9860 for (k, &(bi, _)) in list.iter().enumerate() {
9861 sub[k * cols..(k + 1) * cols].copy_from_slice(&xs[bi * cols..(bi + 1) * cols]);
9862 }
9863 let eo = dense_ffn_batch(d, &sub, sb, pool, None);
9864 for (k, &(bi, w)) in list.iter().enumerate() {
9865 for i in 0..hidden {
9866 out[bi * hidden + i] += w * eo[k * hidden + i];
9867 }
9868 }
9869 };
9870 let active: Vec<usize> = (0..ne).filter(|&e| !assign[e].is_empty()).collect();
9876 if pool.is_some() && active.len() >= 8 {
9877 let mut panels: Vec<Vec<f32>> = vec![Vec::new(); active.len()];
9878 {
9879 let panel_ptr = SendVecs(panels.as_mut_ptr());
9880 let experts = &m.experts;
9883 let (active_r, assign_r) = (&active, &assign);
9884 let run = |start: usize, end: usize| {
9885 for ai in start..end {
9886 let e = active_r[ai];
9887 let list = &assign_r[e];
9888 let sb = list.len();
9889 let mut sub = vec![0.0f32; sb * cols];
9890 for (k, &(bi, _)) in list.iter().enumerate() {
9891 sub[k * cols..(k + 1) * cols]
9892 .copy_from_slice(&xs[bi * cols..(bi + 1) * cols]);
9893 }
9894 unsafe {
9896 *panel_ptr.at(ai) = dense_ffn_batch(&experts[e], &sub, sb, None, None);
9897 }
9898 }
9899 };
9900 match pool {
9901 Some(p) => p.run_rows(active.len(), &run),
9902 None => run(0, active.len()),
9903 }
9904 }
9905 for (ai, &e) in active.iter().enumerate() {
9906 for (k, &(bi, w)) in assign[e].iter().enumerate() {
9907 let eo = &panels[ai][k * hidden..(k + 1) * hidden];
9908 for i in 0..hidden {
9909 out[bi * hidden + i] += w * eo[i];
9910 }
9911 }
9912 }
9913 } else {
9914 for &e in &active {
9915 run_expert(&m.experts[e], &assign[e], &mut out);
9916 }
9917 }
9918 if let Some((se, gate)) = &m.shared {
9919 let all: Vec<(usize, f32)> = if let Some(gate) = gate {
9920 let mut gl = vec![0.0f32; b];
9921 gate.matmat(xs, b, &mut gl, pool);
9922 (0..b)
9923 .map(|bi| (bi, 1.0 / (1.0 + (-gl[bi]).exp())))
9924 .collect()
9925 } else {
9926 (0..b).map(|bi| (bi, 1.0)).collect()
9927 };
9928 run_expert(se, &all, &mut out);
9929 }
9930 out
9931}
9932
9933thread_local! {
9934 static FFN_SCRATCH: std::cell::RefCell<[Vec<f32>; 4]> =
9938 const { std::cell::RefCell::new([Vec::new(), Vec::new(), Vec::new(), Vec::new()]) };
9939}
9940
9941fn dense_ffn(d: &DenseFfn, x: &[f32], pool: Option<&Pool>) -> Vec<f32> {
9943 if gate_topk() > 0
9946 && let Some(out) = dense_ffn_dynamic(d, x, pool, gate_topk())
9947 {
9948 return out;
9949 }
9950 if crate::gpu::enabled_here()
9961 && (d.gate_proj.rows() >= crate::gpu::min_rows() || d.gate_proj.is_q1())
9962 {
9963 let arm = if d.gate_proj.is_q1() && crate::gpu::q1_force() {
9964 crate::gpu::ProbeArm::Gpu
9965 } else {
9966 crate::gpu::probe_arm(crate::gpu::OpClass::Ffn)
9967 };
9968 match arm {
9969 crate::gpu::ProbeArm::Gpu => {
9970 let t0 = std::time::Instant::now();
9971 if let Some(out) = dense_ffn_gpu(d, x, pool) {
9972 crate::gpu::probe_record(crate::gpu::OpClass::Ffn, true, t0.elapsed());
9973 return out;
9974 }
9975 crate::gpu::probe_note_decline(crate::gpu::OpClass::Ffn);
9979 }
9980 crate::gpu::ProbeArm::CpuTimed => {
9981 let t0 = std::time::Instant::now();
9982 let out = crate::gpu::cpu_scope(|| dense_ffn_cpu(d, x, pool));
9983 crate::gpu::probe_record(crate::gpu::OpClass::Ffn, false, t0.elapsed());
9984 return out;
9985 }
9986 crate::gpu::ProbeArm::Cpu => {
9987 return crate::gpu::cpu_scope(|| dense_ffn_cpu(d, x, pool));
9988 }
9989 }
9990 }
9991 dense_ffn_cpu(d, x, pool)
9992}
9993
9994fn dense_ffn_cpu(d: &DenseFfn, x: &[f32], pool: Option<&Pool>) -> Vec<f32> {
9996 let inter = d.gate_proj.rows();
9997 FFN_SCRATCH.with(|s| {
9998 let mut s = s.borrow_mut();
9999 let [g, u, ..] = &mut *s;
10000 g.resize(inter, 0.0);
10001 if gate_topk() > 0 {
10004 u.resize(inter, 0.0);
10008 QTensor::matvec_many([&d.gate_proj, &d.up_proj], x, [g, u], pool);
10009 for i in 0..inter {
10010 g[i] = Act::Silu.combine(g[i], 1.0);
10011 }
10012 keep_top_k(g, gate_topk());
10013 for i in 0..inter {
10014 g[i] *= u[i];
10015 }
10016 } else if d.act == Act::Silu
10017 && QTensor::matvec_silu_mul(&d.gate_proj, &d.up_proj, x, g, pool)
10018 {
10019 } else {
10021 u.resize(inter, 0.0);
10022 QTensor::matvec_many([&d.gate_proj, &d.up_proj], x, [g, u], pool);
10024 for i in 0..inter {
10025 g[i] = d.act.combine(g[i], u[i]);
10026 }
10027 }
10028 FFN_PROBE.with(|pr| {
10036 if let Some(acc) = pr.borrow_mut().as_mut() {
10037 let li = crate::gpu::cur_layer();
10038 if li >= 0 {
10039 if let Some(row) = acc.get_mut(li as usize) {
10040 match probe_topk() {
10041 0 if probe_sq() => {
10042 for (a, &v) in row.iter_mut().zip(g.iter()) {
10043 *a += (v as f64) * (v as f64);
10044 }
10045 }
10046 0 if probe_signed() => {
10047 for (a, &v) in row.iter_mut().zip(g.iter()) {
10048 *a += v as f64;
10049 }
10050 }
10051 0 => {
10052 for (a, &v) in row.iter_mut().zip(g.iter()) {
10053 *a += (v as f64).abs();
10054 }
10055 }
10056 k => {
10057 let n = g.len();
10058 let k = k.min(n);
10059 let mut mag: Vec<f32> = g.iter().map(|v| v.abs()).collect();
10060 let (_, kth, _) = mag.select_nth_unstable_by(k - 1, |a, b| {
10061 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
10062 });
10063 let thr = *kth;
10064 for (a, &v) in row.iter_mut().zip(g.iter()) {
10065 if v.abs() >= thr {
10066 *a += 1.0;
10067 }
10068 }
10069 }
10070 }
10071 }
10072 }
10073 }
10074 });
10075 if oracle_topk() > 0 {
10076 keep_top_k(g, oracle_topk());
10077 }
10078 {
10079 let li = crate::gpu::cur_layer();
10080 if li >= 0 {
10081 adump_row(li as usize, g);
10082 }
10083 }
10084 let mut out = attention::take_buf(d.down_proj.rows());
10085 d.down_proj.matvec(g, &mut out, pool);
10086 out
10087 })
10088}
10089
10090pub struct RefitAcc {
10103 pub support: Vec<u32>,
10104 pub gss: Vec<f32>,
10105 pub ya: Vec<f32>,
10106 pub hidden: usize,
10107 pub tokens: u64,
10108 pub buf_g: Vec<f32>,
10114 pub buf_o: Vec<f32>,
10115 pub buf_t: usize,
10116}
10117
10118type RefitState = (std::collections::HashMap<usize, RefitAcc>, Vec<f32>);
10122
10123static REFIT: std::sync::OnceLock<Option<(String, std::sync::Mutex<RefitState>)>> =
10124 std::sync::OnceLock::new();
10125
10126fn ffn_probe_active() -> bool {
10129 FFN_PROBE.with(|p| p.borrow().is_some())
10130}
10131
10132fn refit_dir() -> Option<&'static (String, std::sync::Mutex<RefitState>)> {
10133 REFIT
10134 .get_or_init(|| {
10135 std::env::var("CMF_FFN_REFIT").ok().map(|d| {
10136 (
10137 d,
10138 std::sync::Mutex::new((std::collections::HashMap::new(), Vec::new())),
10139 )
10140 })
10141 })
10142 .as_ref()
10143}
10144
10145fn refit_accumulate(
10147 li: usize,
10148 g: &[f32],
10149 b: usize,
10150 inter: usize,
10151 out: &[f32],
10152 hidden: usize,
10153 pool: Option<&Pool>,
10154) {
10155 let Some((dir, map)) = refit_dir() else {
10156 return;
10157 };
10158 static SPAN: std::sync::OnceLock<(usize, usize)> = std::sync::OnceLock::new();
10159 let (from, to) = *SPAN.get_or_init(|| {
10160 let g = |k: &str, d: usize| {
10161 std::env::var(k)
10162 .ok()
10163 .and_then(|v| v.parse().ok())
10164 .unwrap_or(d)
10165 };
10166 (
10167 g("CMF_FFN_REFIT_FROM", 0),
10168 g("CMF_FFN_REFIT_TO", usize::MAX),
10169 )
10170 });
10171 if li < from || li > to {
10172 return;
10173 }
10174 let mut guard = map.lock().unwrap();
10175 let (map, shared) = &mut *guard;
10176 let acc = match map.entry(li) {
10177 std::collections::hash_map::Entry::Occupied(e) => e.into_mut(),
10178 std::collections::hash_map::Entry::Vacant(e) => {
10179 let path = format!("{dir}/support.{li}.u32");
10180 let Ok(bytes) = std::fs::read(&path) else {
10181 eprintln!("refit: no {path} — layer {li} skipped");
10182 return;
10183 };
10184 let n = u32::from_le_bytes(bytes[0..4].try_into().unwrap()) as usize;
10185 let support: Vec<u32> = bytes[4..4 + n * 4]
10186 .chunks_exact(4)
10187 .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
10188 .collect();
10189 eprintln!(
10190 "refit: layer {li} support {n} ({:.0} MB of accumulator)",
10191 (n * n + hidden * n) as f64 * 4.0 / 1e6
10192 );
10193 e.insert(RefitAcc {
10194 gss: vec![0.0; n * n],
10195 ya: vec![0.0; hidden * n],
10196 buf_g: Vec::new(),
10197 buf_o: Vec::new(),
10198 buf_t: 0,
10199 support,
10200 hidden,
10201 tokens: 0,
10202 })
10203 }
10204 };
10205 let ns = acc.support.len();
10206 let cap = refit_batch();
10208 if acc.buf_g.is_empty() {
10209 acc.buf_g = vec![0.0; ns * cap];
10210 acc.buf_o = vec![0.0; hidden * cap];
10211 }
10212 let take = b.min(cap - acc.buf_t);
10213 for t in 0..take {
10214 let col = acc.buf_t + t;
10215 for (j, &n) in acc.support.iter().enumerate() {
10216 acc.buf_g[j * cap + col] = g[t * inter + n as usize];
10217 }
10218 for h in 0..hidden {
10219 acc.buf_o[h * cap + col] = out[t * hidden + h];
10220 }
10221 }
10222 acc.buf_t += take;
10223 acc.tokens += take as u64;
10224 if acc.buf_t < cap {
10225 return;
10226 }
10227 let bt = acc.buf_t;
10228 acc.buf_t = 0;
10229 let RefitAcc {
10239 gss,
10240 ya,
10241 buf_g,
10242 buf_o,
10243 ..
10244 } = acc;
10245 let need = (ns * ns).max(hidden * ns);
10246 if shared.len() < need {
10247 shared.resize(need, 0.0);
10248 }
10249 let scratch = &mut shared[..];
10250 let _ = bt;
10251 if crate::gpu::gemm_nt_f32_transient(buf_g, buf_g, &mut scratch[..ns * ns], ns, cap, ns) {
10252 add_into(gss, &scratch[..ns * ns], pool);
10253 if crate::gpu::gemm_nt_f32_transient(
10254 buf_o,
10255 buf_g,
10256 &mut scratch[..hidden * ns],
10257 hidden,
10258 cap,
10259 ns,
10260 ) {
10261 add_into(ya, &scratch[..hidden * ns], pool);
10262 } else {
10263 accum_outer_t(ya, hidden, ns, cap, buf_o, buf_g, pool);
10264 }
10265 } else {
10266 accum_outer_t(gss, ns, ns, cap, buf_g, buf_g, pool);
10267 accum_outer_t(ya, hidden, ns, cap, buf_o, buf_g, pool);
10268 }
10269 }
10273
10274fn refit_batch() -> usize {
10276 static B: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
10277 *B.get_or_init(|| {
10278 std::env::var("CMF_FFN_REFIT_BATCH")
10279 .ok()
10280 .and_then(|v| v.parse().ok())
10281 .unwrap_or(4096)
10282 })
10283}
10284
10285fn accum_outer_t(
10288 c: &mut [f32],
10289 m: usize,
10290 n: usize,
10291 b: usize,
10292 left: &[f32],
10293 right: &[f32],
10294 pool: Option<&Pool>,
10295) {
10296 let ptr = SendMut(c.as_mut_ptr());
10297 let body = |i: usize| {
10298 let ptr = &ptr;
10299 let row = unsafe { std::slice::from_raw_parts_mut(ptr.0.add(i * n), n) };
10300 for t in 0..b {
10301 let a = left[i * b + t];
10302 if a == 0.0 {
10303 continue;
10304 }
10305 for (j, o) in row.iter_mut().enumerate() {
10306 *o += a * right[j * b + t];
10307 }
10308 }
10309 };
10310 match pool {
10311 Some(p) if m > 1 => p.run_rows(m, &|s, e| {
10312 for i in s..e {
10313 body(i);
10314 }
10315 }),
10316 _ => {
10317 for i in 0..m {
10318 body(i);
10319 }
10320 }
10321 }
10322}
10323
10324fn add_into(dst: &mut [f32], src: &[f32], pool: Option<&Pool>) {
10327 let n = dst.len().min(src.len());
10328 match pool {
10329 Some(p) if n >= 1 << 16 => {
10330 let ptr = SendMut(dst.as_mut_ptr());
10331 let f = |s: usize, e: usize| {
10332 let ptr = &ptr;
10333 for blk in s..e {
10334 let (a, b) = (blk * 4096, ((blk + 1) * 4096).min(n));
10335 for i in a..b {
10336 unsafe { *ptr.0.add(i) += src[i] };
10337 }
10338 }
10339 };
10340 p.run_rows(n.div_ceil(4096), &f);
10341 }
10342 _ => {
10343 for (d, v) in dst.iter_mut().zip(&src[..n]) {
10344 *d += *v;
10345 }
10346 }
10347 }
10348}
10349
10350fn accum_outer(
10355 c: &mut [f32],
10356 m: usize,
10357 n: usize,
10358 b: usize,
10359 left: &[f32],
10360 right: &[f32],
10361 pool: Option<&Pool>,
10362) {
10363 const TILE: usize = 32;
10364 let tiles = m.div_ceil(TILE);
10365 let cp = SendMut(c.as_mut_ptr());
10366 let body = |ti: usize| {
10367 let cp = &cp;
10368 let i0 = ti * TILE;
10369 let i1 = (i0 + TILE).min(m);
10370 for t in 0..b {
10371 let r = &right[t * n..t * n + n];
10372 for i in i0..i1 {
10373 let a = left[i * b + t];
10374 if a == 0.0 {
10375 continue;
10376 }
10377 let row = unsafe { std::slice::from_raw_parts_mut(cp.0.add(i * n), n) };
10379 for (o, v) in row.iter_mut().zip(r) {
10380 *o += a * *v;
10381 }
10382 }
10383 }
10384 };
10385 match pool {
10386 Some(p) if tiles > 1 => p.run_rows(tiles, &|s, e| {
10387 for ti in s..e {
10388 body(ti);
10389 }
10390 }),
10391 _ => {
10392 for ti in 0..tiles {
10393 body(ti);
10394 }
10395 }
10396 }
10397}
10398
10399pub fn refit_flush() -> usize {
10401 let Some((dir, map)) = refit_dir() else {
10402 return 0;
10403 };
10404 let guard = map.lock().unwrap();
10405 let mut n = 0;
10406 for (li, acc) in guard.0.iter() {
10407 let w = |name: &str, v: &[f32]| {
10410 let path = format!("{dir}/{name}.{li}.f32");
10411 let bytes: Vec<u8> = v.iter().flat_map(|x| x.to_le_bytes()).collect();
10412 match std::fs::write(&path, &bytes) {
10413 Ok(()) => {}
10414 Err(e) => eprintln!(
10415 "refit: FAILED to write {path} ({} MB): {e}",
10416 bytes.len() / 1_000_000
10417 ),
10418 }
10419 };
10420 w("gss", &acc.gss);
10421 w("ya", &acc.ya);
10422 println!(
10423 "refit L{li}: {} support, {} tokens, hidden {}",
10424 acc.support.len(),
10425 acc.tokens,
10426 acc.hidden
10427 );
10428 n += 1;
10429 }
10430 n
10431}
10432
10433fn adump_row(li: usize, g: &[f32]) {
10438 use std::io::Write as _;
10439 static FILES: std::sync::OnceLock<
10440 Option<(
10441 String,
10442 std::sync::Mutex<std::collections::HashMap<usize, std::fs::File>>,
10443 )>,
10444 > = std::sync::OnceLock::new();
10445 let Some((prefix, map)) = FILES
10446 .get_or_init(|| {
10447 std::env::var("CMF_FFN_ADUMP")
10448 .ok()
10449 .map(|p| (p, std::sync::Mutex::new(std::collections::HashMap::new())))
10450 })
10451 .as_ref()
10452 else {
10453 return;
10454 };
10455 static SPAN: std::sync::OnceLock<(usize, usize)> = std::sync::OnceLock::new();
10458 let (from, to) = *SPAN.get_or_init(|| {
10459 let g = |k: &str, d: usize| {
10460 std::env::var(k)
10461 .ok()
10462 .and_then(|v| v.parse().ok())
10463 .unwrap_or(d)
10464 };
10465 (
10466 g("CMF_FFN_ADUMP_FROM", 0),
10467 g("CMF_FFN_ADUMP_TO", usize::MAX),
10468 )
10469 });
10470 if li < from || li > to {
10471 return;
10472 }
10473 let mut map = map.lock().unwrap();
10474 let f = map.entry(li).or_insert_with(|| {
10475 std::fs::File::create(format!("{prefix}.{li}.f16")).expect("adump file")
10476 });
10477 let mut bytes = Vec::with_capacity(g.len() * 2);
10478 for v in g {
10479 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(*v).to_le_bytes());
10480 }
10481 let _ = f.write_all(&bytes);
10482}
10483
10484fn oracle_topk() -> usize {
10490 static K: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
10491 *K.get_or_init(|| {
10492 std::env::var("CMF_FFN_ORACLE_TOPK")
10493 .ok()
10494 .and_then(|v| v.parse().ok())
10495 .unwrap_or(0)
10496 })
10497}
10498
10499fn gate_topk() -> usize {
10505 static K: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
10506 *K.get_or_init(|| {
10507 std::env::var("CMF_FFN_GATE_TOPK")
10508 .ok()
10509 .and_then(|v| v.parse().ok())
10510 .unwrap_or(0)
10511 })
10512}
10513
10514fn gate_block() -> usize {
10521 static B: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
10522 *B.get_or_init(|| {
10523 std::env::var("CMF_FFN_GATE_BLOCK")
10524 .ok()
10525 .and_then(|v| v.parse().ok())
10526 .unwrap_or(1)
10527 })
10528}
10529
10530fn keep_top_blocks(g: &mut [f32], keep_n: usize, block: usize) {
10532 let n = g.len();
10533 let nb = n.div_ceil(block);
10534 let kb = (keep_n.div_ceil(block)).clamp(1, nb);
10535 if kb >= nb {
10536 return;
10537 }
10538 let mut score: Vec<f32> = (0..nb)
10539 .map(|b| {
10540 g[b * block..((b + 1) * block).min(n)]
10541 .iter()
10542 .map(|v| v * v)
10543 .sum::<f32>()
10544 })
10545 .collect();
10546 let mut ord = score.clone();
10547 let (_, kth, _) = ord.select_nth_unstable_by(kb - 1, |a, b| {
10548 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
10549 });
10550 let thr = *kth;
10551 for b in 0..nb {
10552 if score[b] < thr {
10553 g[b * block..((b + 1) * block).min(n)].fill(0.0);
10554 }
10555 }
10556 score.clear();
10557}
10558
10559fn keep_top_k(g: &mut [f32], k: usize) {
10561 if gate_block() > 1 {
10562 return keep_top_blocks(g, k, gate_block());
10563 }
10564 let n = g.len();
10565 if k == 0 || k >= n {
10566 return;
10567 }
10568 let mut mag: Vec<f32> = g.iter().map(|v| v.abs()).collect();
10569 let (_, kth, _) = mag.select_nth_unstable_by(k - 1, |a, b| {
10570 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
10571 });
10572 let thr = *kth;
10573 for v in g.iter_mut() {
10574 if v.abs() < thr {
10575 *v = 0.0;
10576 }
10577 }
10578}
10579
10580fn probe_sq() -> bool {
10584 static S: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10585 *S.get_or_init(|| std::env::var("CMF_FFN_PROBE_SQ").is_ok())
10586}
10587
10588fn probe_signed() -> bool {
10592 static S: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10593 *S.get_or_init(|| std::env::var("CMF_FFN_PROBE_SIGNED").is_ok())
10594}
10595
10596fn meanfill() -> Option<&'static (usize, Vec<f32>)> {
10604 static M: std::sync::OnceLock<Option<(usize, Vec<f32>)>> = std::sync::OnceLock::new();
10605 M.get_or_init(|| {
10606 let p = std::env::var("CMF_FFN_MEANFILL").ok()?;
10607 let b = std::fs::read(&p).ok()?;
10608 let inter = u32::from_le_bytes(b[4..8].try_into().ok()?) as usize;
10609 let vals: Vec<f32> = b[8..]
10610 .chunks_exact(4)
10611 .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
10612 .collect();
10613 eprintln!("meanfill: {} value(s), inter {inter}", vals.len());
10614 Some((inter, vals))
10615 })
10616 .as_ref()
10617}
10618
10619fn probe_topk() -> usize {
10622 static K: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
10623 *K.get_or_init(|| {
10624 std::env::var("CMF_FFN_PROBE_TOPK")
10625 .ok()
10626 .and_then(|v| v.parse().ok())
10627 .unwrap_or(0)
10628 })
10629}
10630
10631thread_local! {
10632 static FFN_PROBE: std::cell::RefCell<Option<Vec<Vec<f64>>>> =
10635 const { std::cell::RefCell::new(None) };
10636}
10637
10638fn dense_ffn_dynamic(d: &DenseFfn, x: &[f32], pool: Option<&Pool>, k: usize) -> Option<Vec<f32>> {
10651 let dt = d.down_t.as_ref()?;
10652 let inter = d.gate_proj.rows();
10653 let hidden = dt.cols();
10654 if k == 0 || k >= inter || d.act != Act::Silu {
10655 return None;
10656 }
10657 DYN_SCRATCH.with(|sc| {
10658 let mut sc = sc.borrow_mut();
10659 let DynScratch {
10660 g,
10661 mag,
10662 live,
10663 parts,
10664 } = &mut *sc;
10665 g.resize(inter, 0.0);
10666 d.gate_proj.matvec(x, g, pool);
10667 for v in g.iter_mut() {
10668 *v = inference::silu(*v);
10669 }
10670 mag.clear();
10673 mag.extend(g.iter().map(|v| v.abs()));
10674 let (_, kth, _) = mag.select_nth_unstable_by(k - 1, |a, b| {
10675 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
10676 });
10677 let thr = *kth;
10678 live.clear();
10679 live.extend((0..inter as u32).filter(|&n| g[n as usize].abs() >= thr));
10680 let mut out = vec![0.0f32; hidden];
10681 match pool {
10682 Some(p) if live.len() >= 64 => {
10683 let nw = p.n_workers() + 1;
10684 parts.clear();
10685 parts.resize(nw * hidden, 0.0);
10686 let ptr = SendMut(parts.as_mut_ptr());
10687 let n = live.len();
10688 let live_ref: &[u32] = live;
10689 let g_ref: &[f32] = g;
10690 p.run(&|w, workers| {
10691 let chunk = n.div_ceil(workers);
10692 let (s, e) = (w * chunk, ((w + 1) * chunk).min(n));
10693 if s >= e {
10694 return;
10695 }
10696 WORKER_SCRATCH.with(|ws| {
10697 let mut ws = ws.borrow_mut();
10698 let [scratch, acc] = &mut *ws;
10699 scratch.resize(hidden.max(x.len()), 0.0);
10700 acc.clear();
10701 acc.resize(hidden, 0.0);
10702 for (o, &nrm) in live_ref[s..e].iter().enumerate() {
10703 if let Some(&nx) = live_ref[s..e].get(o + 1) {
10706 d.up_proj.prefetch_row(nx as usize);
10707 dt.prefetch_row(nx as usize);
10708 }
10709 let idx = nrm as usize;
10710 let up = d.up_proj.row_dot(idx, x, scratch);
10711 let a = g_ref[idx] * up;
10712 if a != 0.0 {
10713 dt.add_row_scaled(idx, a, acc, scratch);
10714 }
10715 }
10716 for (j, v) in acc.iter().enumerate() {
10717 unsafe { *ptr.at(w * hidden + j) = *v };
10718 }
10719 });
10720 });
10721 for w in 0..nw {
10722 for (j, o) in out.iter_mut().enumerate() {
10723 *o += parts[w * hidden + j];
10724 }
10725 }
10726 }
10727 _ => {
10728 WORKER_SCRATCH.with(|ws| {
10729 let mut ws = ws.borrow_mut();
10730 let [scratch, _acc] = &mut *ws;
10731 scratch.resize(hidden.max(x.len()), 0.0);
10732 for &nrm in live.iter() {
10733 let idx = nrm as usize;
10734 let up = d.up_proj.row_dot(idx, x, scratch);
10735 let a = g[idx] * up;
10736 if a != 0.0 {
10737 dt.add_row_scaled(idx, a, &mut out, scratch);
10738 }
10739 }
10740 });
10741 }
10742 }
10743 Some(out)
10744 })
10745}
10746
10747struct DynScratch {
10750 g: Vec<f32>,
10751 mag: Vec<f32>,
10752 live: Vec<u32>,
10753 parts: Vec<f32>,
10754}
10755
10756thread_local! {
10757 static DYN_SCRATCH: std::cell::RefCell<DynScratch> = const {
10758 std::cell::RefCell::new(DynScratch {
10759 g: Vec::new(),
10760 mag: Vec::new(),
10761 live: Vec::new(),
10762 parts: Vec::new(),
10763 })
10764 };
10765 static WORKER_SCRATCH: std::cell::RefCell<[Vec<f32>; 2]> =
10767 const { std::cell::RefCell::new([Vec::new(), Vec::new()]) };
10768}
10769
10770fn dense_ffn_masked(d: &DenseFfn, x: &[f32], pool: Option<&Pool>, mask_row: &[u8]) -> Vec<f32> {
10775 let inter = d.gate_proj.rows();
10776 FFN_SCRATCH.with(|s| {
10777 let mut s = s.borrow_mut();
10778 let [g, u, ..] = &mut *s;
10779 g.resize(inter, 0.0);
10780 if d.act == Act::Silu && QTensor::matvec_silu_mul(&d.gate_proj, &d.up_proj, x, g, pool) {
10781 } else {
10783 u.resize(inter, 0.0);
10784 QTensor::matvec_many([&d.gate_proj, &d.up_proj], x, [g, u], pool);
10785 for i in 0..inter {
10786 g[i] = d.act.combine(g[i], u[i]);
10787 }
10788 }
10789 zero_masked_cols(g, 1, inter, mask_row);
10790 let mut out = attention::take_buf(d.down_proj.rows());
10791 d.down_proj.matvec(g, &mut out, pool);
10792 out
10793 })
10794}
10795
10796fn dense_ffn_gpu(d: &DenseFfn, x: &[f32], _pool: Option<&Pool>) -> Option<Vec<f32>> {
10802 if d.act != Act::Silu {
10804 return None;
10805 }
10806 if d.gate_proj.rows() < crate::gpu::min_rows() && !d.gate_proj.is_q1() {
10809 return None;
10810 }
10811 let mut jobs: Vec<crate::gpu::MoeJob> = Vec::with_capacity(1);
10812 let mut model_ref = None;
10813 moe_push_job(d, x, 1.0, &mut jobs, &mut model_ref)?;
10814 let model = model_ref?;
10815 let hidden = jobs[0].down.1;
10816 let mut out = attention::take_buf(hidden);
10817 if crate::gpu::moe_block(&model, &jobs, &mut out) {
10818 Some(out)
10819 } else {
10820 let mut out = out;
10821 attention::recycle_buf(&mut out);
10822 None
10823 }
10824}
10825
10826#[allow(clippy::type_complexity)]
10831#[allow(clippy::type_complexity)]
10832pub(crate) fn moe_parts(
10833 t: &QTensor,
10834) -> Option<(
10835 &std::sync::Arc<cortiq_core::CmfModel>,
10836 usize,
10837 usize,
10838 usize,
10839 &[f32],
10840 &[f32],
10841 bool,
10842 bool,
10843 bool,
10844)> {
10845 match t {
10846 QTensor::Mapped {
10847 model,
10848 idx,
10849 dtype: dt @ (cortiq_core::TensorDtype::Q8_2f | cortiq_core::TensorDtype::Q8Row),
10850 rows,
10851 cols,
10852 row_scale,
10853 col_field,
10854 ..
10855 } if (*dt == cortiq_core::TensorDtype::Q8Row) || !col_field.is_empty() => Some((
10856 model, *idx, *rows, *cols, row_scale, col_field, false, false, false,
10857 )),
10858 QTensor::Mapped {
10860 model,
10861 idx,
10862 dtype: cortiq_core::TensorDtype::Q1,
10863 rows,
10864 cols,
10865 ..
10866 } => Some((
10867 model,
10868 *idx,
10869 *rows,
10870 *cols,
10871 &[][..],
10872 &[][..],
10873 true,
10874 false,
10875 false,
10876 )),
10877 QTensor::Mapped {
10879 model,
10880 idx,
10881 dtype: cortiq_core::TensorDtype::Q4Tiled,
10882 rows,
10883 cols,
10884 ..
10885 } => Some((
10886 model,
10887 *idx,
10888 *rows,
10889 *cols,
10890 &[][..],
10891 &[][..],
10892 false,
10893 true,
10894 false,
10895 )),
10896 QTensor::Mapped {
10898 model,
10899 idx,
10900 dtype: cortiq_core::TensorDtype::Q4TiledP,
10901 rows,
10902 cols,
10903 ..
10904 } => Some((
10905 model,
10906 *idx,
10907 *rows,
10908 *cols,
10909 &[][..],
10910 &[][..],
10911 false,
10912 true,
10913 false,
10914 )),
10915 QTensor::Mapped {
10919 model,
10920 idx,
10921 dtype: cortiq_core::TensorDtype::Q2TiledP,
10922 rows,
10923 cols,
10924 ..
10925 } => Some((
10926 model,
10927 *idx,
10928 *rows,
10929 *cols,
10930 &[][..],
10931 &[][..],
10932 false,
10933 true,
10934 true,
10935 )),
10936 _ => None,
10937 }
10938}
10939
10940#[cfg(target_os = "macos")]
10946fn metal_moe_graph_parts(m: &MoeFfn, hidden: usize) -> Option<crate::gpu::GpuMoe<'_>> {
10947 if m.router_sigmoid
10948 || m.router_input_norm
10949 || m.expert_bias.is_some()
10950 || m.route_tau.is_some()
10951 || m.mask.is_some()
10952 || m.per_expert_scale.is_some()
10953 || m.experts.is_empty()
10954 || m.top_k == 0
10955 || m.resonance.is_some()
10956 {
10957 return None;
10958 }
10959 let (sh, sg) = match &m.shared {
10962 Some((sh, Some(sg))) => (sh, sg),
10963 _ => return None,
10964 };
10965 let (rf, rr, rc) = m.router.f32_parts()?;
10966 if rr != m.experts.len() || rc != hidden {
10967 return None;
10968 }
10969 let (sf, sr, sc) = sg.f32_parts()?;
10970 if sr * sc != hidden {
10971 return None;
10972 }
10973 let inter = m.experts[0].gate_proj.rows();
10974 let gu_q2 = m.experts[0].gate_proj.mapped_q2tp().is_some();
10977 let trio = |e: &DenseFfn| -> Option<(usize, usize, usize)> {
10978 if e.act != Act::Silu
10979 || e.gate_proj.rows() != inter
10980 || e.gate_proj.cols() != hidden
10981 || e.up_proj.rows() != inter
10982 || e.up_proj.cols() != hidden
10983 || e.down_proj.rows() != hidden
10984 || e.down_proj.cols() != inter
10985 {
10986 return None;
10987 }
10988 let pick = |t: &QTensor| -> Option<usize> {
10989 if gu_q2 {
10990 t.mapped_q2tp().map(|(_, i)| i)
10991 } else {
10992 t.mapped_q4tp().map(|(_, i)| i)
10993 }
10994 };
10995 Some((
10996 pick(&e.gate_proj)?,
10997 pick(&e.up_proj)?,
10998 e.down_proj.mapped_q4tp().map(|(_, i)| i)?,
10999 ))
11000 };
11001 let experts = m.experts.iter().map(trio).collect::<Option<Vec<_>>>()?;
11002 let shared = trio(sh)?;
11003 Some(crate::gpu::GpuMoe {
11004 router: rf,
11005 sgate: sf,
11006 experts,
11007 shared,
11008 n_exp: m.experts.len(),
11009 top_k: m.top_k,
11010 inter,
11011 norm_topk: m.norm_topk_prob,
11012 route_scale: m.routed_scaling,
11013 gu_q2,
11014 })
11015}
11016
11017pub(crate) fn moe_push_job_parts<'a>(
11021 gate: &'a QTensor,
11022 up: &'a QTensor,
11023 down: &'a QTensor,
11024 x: &[f32],
11025 w: f32,
11026 swiglu_limit: f32,
11027 jobs: &mut Vec<crate::gpu::MoeJob<'a>>,
11028 model_ref: &mut Option<std::sync::Arc<cortiq_core::CmfModel>>,
11029) -> Option<()> {
11030 use crate::qtensor::prescale;
11031 let (gm, gi, gr, gc, grs, gcf, gq1, gq4, gq2) = moe_parts(gate)?;
11032 let (_, ui, ur, uc, urs, ucf, uq1, uq4, uq2) = moe_parts(up)?;
11033 let (_, di, dr, dc, drs, dcf, dq1, dq4, dq2) = moe_parts(down)?;
11034 if gq1 != uq1 || uq1 != dq1 || gq4 != uq4 || uq4 != dq4 || gq2 != uq2 {
11035 return None; }
11037 if gq2 && (dq2 || !dq4 || down.mapped_q4tp().is_none()) {
11040 return None;
11041 }
11042 if !gq2 && dq2 {
11043 return None;
11044 }
11045 model_ref.get_or_insert_with(|| gm.clone());
11046 let dt = |cf: &[f32]| {
11047 if cf.is_empty() {
11048 cortiq_core::TensorDtype::Q8Row
11049 } else {
11050 cortiq_core::TensorDtype::Q8_2f
11051 }
11052 };
11053 jobs.push(crate::gpu::MoeJob {
11054 gate: (gi, gr, gc, grs),
11055 up: (ui, ur, uc, urs),
11056 down: (di, dr, dc, drs),
11057 xs_gate: prescale(x, gcf, dt(gcf)).into_owned(),
11058 xs_up: prescale(x, ucf, dt(ucf)).into_owned(),
11059 down_col: dcf,
11060 w,
11061 q1: gq1,
11062 q4t: gq4 && !gq2 && gate.mapped_q4tp().is_none(),
11063 q4tp: gq4 && (gq2 || gate.mapped_q4tp().is_some()),
11064 gu_q2: gq2,
11065 swiglu_limit,
11066 });
11067 Some(())
11068}
11069
11070fn moe_push_job<'a>(
11072 d: &'a DenseFfn,
11073 x: &[f32],
11074 w: f32,
11075 jobs: &mut Vec<crate::gpu::MoeJob<'a>>,
11076 model_ref: &mut Option<std::sync::Arc<cortiq_core::CmfModel>>,
11077) -> Option<()> {
11078 use crate::qtensor::prescale;
11079 if d.act != Act::Silu {
11080 return None; }
11082 let (gm, gi, gr, gc, grs, gcf, gq1, gq4, gq2) = moe_parts(&d.gate_proj)?;
11083 let (_, ui, ur, uc, urs, ucf, uq1, uq4, uq2) = moe_parts(&d.up_proj)?;
11084 let (_, di, dr, dc, drs, dcf, dq1, dq4, dq2) = moe_parts(&d.down_proj)?;
11085 if gq1 != uq1 || uq1 != dq1 || gq4 != uq4 || uq4 != dq4 || gq2 != uq2 {
11086 return None; }
11088 if gq2 && (dq2 || !dq4 || d.down_proj.mapped_q4tp().is_none()) {
11089 return None;
11090 }
11091 if !gq2 && dq2 {
11092 return None;
11093 }
11094 model_ref.get_or_insert_with(|| gm.clone());
11095 let gdt = if gcf.is_empty() {
11096 cortiq_core::TensorDtype::Q8Row
11097 } else {
11098 cortiq_core::TensorDtype::Q8_2f
11099 };
11100 let udt = if ucf.is_empty() {
11101 cortiq_core::TensorDtype::Q8Row
11102 } else {
11103 cortiq_core::TensorDtype::Q8_2f
11104 };
11105 jobs.push(crate::gpu::MoeJob {
11106 gate: (gi, gr, gc, grs),
11107 up: (ui, ur, uc, urs),
11108 down: (di, dr, dc, drs),
11109 xs_gate: prescale(x, gcf, gdt).into_owned(),
11110 xs_up: prescale(x, ucf, udt).into_owned(),
11111 down_col: dcf,
11112 w,
11113 q1: gq1,
11114 q4t: gq4 && !gq2 && d.gate_proj.mapped_q4tp().is_none(),
11115 q4tp: gq4 && (gq2 || d.gate_proj.mapped_q4tp().is_some()),
11116 gu_q2: gq2,
11117 swiglu_limit: 0.0,
11118 });
11119 Some(())
11120}
11121
11122fn sparse_ffn_quant(
11129 d: &DenseFfn,
11130 x: &[f32],
11131 active: &[u16],
11132 hidden: usize,
11133 pool: Option<&Pool>,
11134) -> Vec<f32> {
11135 let n = active.len();
11136 let inter = d.gate_proj.rows();
11137 let mut act = vec![0.0f32; n];
11138 let need_scratch = !(d.gate_proj.sparse_col_ok() && d.up_proj.sparse_col_ok());
11141 let compute = |ai: usize| -> f32 {
11142 let idx = active[ai] as usize;
11143 if idx >= inter {
11144 return 0.0; }
11146 let mut s = if need_scratch {
11147 vec![0.0f32; hidden]
11148 } else {
11149 Vec::new()
11150 };
11151 let gate = d.gate_proj.row_dot(idx, x, &mut s);
11152 let up = d.up_proj.row_dot(idx, x, &mut s);
11153 d.act.combine(gate, up)
11154 };
11155 match pool {
11156 Some(p) if n >= 256 => {
11157 let ptr = SendMut(act.as_mut_ptr());
11158 p.run(&|widx, nw| {
11159 let chunk = n.div_ceil(nw);
11160 let (s, e) = (widx * chunk, ((widx + 1) * chunk).min(n));
11161 for ai in s..e {
11162 unsafe { *ptr.at(ai) = compute(ai) };
11163 }
11164 });
11165 }
11166 _ => {
11167 for (ai, a) in act.iter_mut().enumerate() {
11168 *a = compute(ai);
11169 }
11170 }
11171 }
11172 let mut out = vec![0.0f32; hidden];
11174 for (ai, &idx) in active.iter().enumerate() {
11175 let w = act[ai];
11176 if w.abs() >= 1e-12 && (idx as usize) < inter {
11177 d.down_proj.add_col_scaled(idx as usize, w, &mut out);
11178 }
11179 }
11180 out
11181}
11182
11183#[doc(hidden)]
11185pub fn sparse_ffn_quant_for_test(
11186 d: &DenseFfn,
11187 x: &[f32],
11188 active: &[u16],
11189 hidden: usize,
11190) -> Vec<f32> {
11191 sparse_ffn_quant(d, x, active, hidden, None)
11192}
11193
11194fn dequant_dense_f32(d: &DenseFfn) -> (Vec<f32>, Vec<f32>, Vec<f32>) {
11198 let deq = |t: &QTensor| -> Vec<f32> {
11199 let (rows, cols) = (t.rows(), t.cols());
11200 let mut out = vec![0.0f32; rows * cols];
11201 for r in 0..rows {
11202 t.row_f32(r, &mut out[r * cols..(r + 1) * cols]);
11203 }
11204 out
11205 };
11206 (deq(&d.gate_proj), deq(&d.up_proj), deq(&d.down_proj))
11207}
11208
11209struct SendMut(*mut f32);
11211unsafe impl Send for SendMut {}
11212unsafe impl Sync for SendMut {}
11213impl SendMut {
11214 #[inline]
11215 #[allow(clippy::mut_from_ref)]
11218 unsafe fn at(&self, i: usize) -> &mut f32 {
11219 unsafe { &mut *self.0.add(i) }
11220 }
11221}
11222
11223pub(crate) fn moe_route(
11233 logits: &[f32],
11234 m: &MoeFfn,
11235 allowed: Option<&[bool]>,
11236) -> (Vec<usize>, Vec<f32>, f32) {
11237 let ne = logits.len();
11238 let p: Vec<f32> = if m.router_sigmoid {
11239 logits.iter().map(|&l| 1.0 / (1.0 + (-l).exp())).collect()
11240 } else {
11241 let mx = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
11242 let mut e: Vec<f32> = logits.iter().map(|&l| (l - mx).exp()).collect();
11243 let s: f32 = e.iter().sum();
11244 for v in &mut e {
11245 *v /= s;
11246 }
11247 e
11248 };
11249 let admit = |e: usize| {
11255 m.mask.as_ref().is_none_or(|mk| mk[e])
11256 && allowed.is_none_or(|a| a.get(e).copied().unwrap_or(false))
11257 };
11258 let mut idx: Vec<usize> = (0..ne).filter(|&e| admit(e)).collect();
11259 match &m.expert_bias {
11261 Some(b) => idx.sort_unstable_by(|&x, &y| {
11262 (p[y] + b[y])
11263 .partial_cmp(&(p[x] + b[x]))
11264 .unwrap()
11265 .then(x.cmp(&y))
11266 }),
11267 None => idx.sort_unstable_by(|&x, &y| p[y].partial_cmp(&p[x]).unwrap().then(x.cmp(&y))),
11268 }
11269 idx.truncate(m.top_k);
11270 if let Some(tau) = m.route_tau {
11274 let total: f32 = idx.iter().map(|&e| p[e]).sum();
11275 if total > 0.0 {
11276 let mut acc = 0.0f32;
11277 let mut keep = idx.len();
11278 for (i, &e) in idx.iter().enumerate() {
11279 acc += p[e];
11280 if acc >= tau * total {
11281 keep = i + 1;
11282 break;
11283 }
11284 }
11285 idx.truncate(keep);
11286 }
11287 }
11288 let wsum: f32 = if m.norm_topk_prob {
11289 let s: f32 = idx.iter().map(|&e| p[e]).sum();
11290 (if m.router_sigmoid { s + 1e-6 } else { s }) / m.routed_scaling
11293 } else {
11294 1.0 / m.routed_scaling
11295 };
11296 (idx, p, wsum)
11297}
11298
11299fn moe_trace(idx: &[usize]) {
11301 moe_trace_at(crate::gpu::cur_layer() as i32, idx)
11302}
11303
11304pub(crate) fn moe_trace_at(li: i32, idx: &[usize]) {
11307 use std::io::Write;
11308 static F: std::sync::OnceLock<Option<std::sync::Mutex<std::fs::File>>> =
11309 std::sync::OnceLock::new();
11310 let Some(f) = F.get_or_init(|| {
11311 let p = std::env::var("CMF_MOE_TRACE").ok()?;
11312 Some(std::sync::Mutex::new(
11313 std::fs::OpenOptions::new()
11314 .create(true)
11315 .append(true)
11316 .open(p)
11317 .ok()?,
11318 ))
11319 }) else {
11320 return;
11321 };
11322 let ids: Vec<String> = idx.iter().map(|e| e.to_string()).collect();
11323 let _ = writeln!(f.lock().unwrap(), "{li}:{}", ids.join(","));
11324}
11325
11326pub(crate) fn moe_ffn(
11329 m: &MoeFfn,
11330 x: &[f32],
11331 pool: Option<&Pool>,
11332 allowed: Option<&[bool]>,
11333) -> Vec<f32> {
11334 accumulate_act(m, x, 1);
11335 let ne = m.experts.len();
11336 let mut logits = vec![0.0f32; ne];
11337 match &m.resonance {
11338 Some(r) => r.scores(x, &mut logits),
11339 None => m.router.matvec(x, &mut logits, pool),
11340 }
11341 let (idx, p, wsum) = moe_route(&logits, m, allowed);
11342 {
11343 let mut st = m.stats.borrow_mut();
11344 if st.len() < ne {
11345 st.resize(ne, 0);
11346 }
11347 for &e in &idx {
11348 st[e] += 1;
11349 }
11350 }
11351 moe_trace(&idx);
11357 if crate::gpu::enabled_here() {
11362 match crate::gpu::probe_arm(crate::gpu::OpClass::Ffn) {
11363 crate::gpu::ProbeArm::Gpu => {
11364 let t0 = std::time::Instant::now();
11365 if let Some(out) = moe_ffn_gpu(m, x, &idx, &p, wsum, pool) {
11366 crate::gpu::probe_record(crate::gpu::OpClass::Ffn, true, t0.elapsed());
11367 return out;
11368 }
11369 }
11370 crate::gpu::ProbeArm::CpuTimed => {
11371 let t0 = std::time::Instant::now();
11372 let out = crate::gpu::cpu_scope(|| moe_ffn_cpu(m, x, &idx, &p, wsum, pool));
11373 crate::gpu::probe_record(crate::gpu::OpClass::Ffn, false, t0.elapsed());
11374 return out;
11375 }
11376 crate::gpu::ProbeArm::Cpu => {
11377 return crate::gpu::cpu_scope(|| moe_ffn_cpu(m, x, &idx, &p, wsum, pool));
11378 }
11379 }
11380 }
11381 moe_ffn_cpu(m, x, &idx, &p, wsum, pool)
11382}
11383
11384fn graph_note(built: bool) {
11388 use std::sync::atomic::{AtomicBool, Ordering};
11389 if built {
11390 GRAPH_TOK_OK.fetch_add(1, Ordering::Relaxed);
11391 } else {
11392 GRAPH_TOK_MISS.fetch_add(1, Ordering::Relaxed);
11393 }
11394 static SAID: AtomicBool = AtomicBool::new(false);
11395 if !SAID.swap(true, Ordering::Relaxed) {
11396 if built {
11397 tracing::info!("wgpu whole-token graph: ACTIVE");
11398 } else {
11399 tracing::warn!("wgpu whole-token graph refused — per-op path");
11400 }
11401 }
11402}
11403
11404pub static GRAPH_TOK_OK: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11408pub static GRAPH_TOK_MISS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11409
11410fn moe_batch_enabled() -> bool {
11413 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11414 *ON.get_or_init(|| std::env::var("CMF_MOE_BATCH").as_deref() != Ok("0"))
11415}
11416
11417fn moe_ffn_cpu_batched(
11423 m: &MoeFfn,
11424 x: &[f32],
11425 idx: &[usize],
11426 p: &[f32],
11427 wsum: f32,
11428 pool: Option<&Pool>,
11429) -> Option<Vec<f32>> {
11430 if idx.is_empty() || !moe_batch_enabled() {
11431 return None;
11432 }
11433 if FFN_PROBE.with(|pr| pr.borrow().is_some()) {
11437 return None;
11438 }
11439 let n = idx.len() + usize::from(m.shared.is_some());
11440 let mut pairs = Vec::with_capacity(n);
11441 let mut downs = Vec::with_capacity(n);
11442 let mut ws = Vec::with_capacity(n);
11443 for &e in idx {
11444 let d = &m.experts[e];
11445 if d.act != Act::Silu {
11446 return None;
11447 }
11448 pairs.push((&d.gate_proj, &d.up_proj));
11449 downs.push(&d.down_proj);
11450 ws.push(p[e] / wsum * m.per_expert_scale.as_ref().map_or(1.0, |v| v[e]));
11451 }
11452 if let Some((se, gate)) = &m.shared {
11455 if se.act != Act::Silu {
11456 return None;
11457 }
11458 let g = gate.as_ref().map_or(1.0, |gate| {
11459 let mut gl = [0.0f32; 1];
11460 gate.matvec(x, &mut gl, pool);
11461 1.0 / (1.0 + (-gl[0]).exp())
11462 });
11463 pairs.push((&se.gate_proj, &se.up_proj));
11464 downs.push(&se.down_proj);
11465 ws.push(g);
11466 }
11467 let inter = pairs[0].0.rows();
11468 let mut gs: Vec<Vec<f32>> = (0..pairs.len()).map(|_| vec![0f32; inter]).collect();
11469 if !QTensor::moe_gate_up_many(&pairs, x, &mut gs, pool) {
11470 return None;
11471 }
11472 let mut out = attention::take_buf(x.len());
11473 if !QTensor::moe_down_many(&downs, &gs, &ws, &mut out, pool) {
11474 attention::recycle_buf(&mut out);
11475 return None;
11476 }
11477 Some(out)
11478}
11479
11480pub(crate) fn moe_cold_experts_cpu(
11486 experts: &[(&DenseFfn, f32)],
11487 x: &[f32],
11488 pool: Option<&Pool>,
11489) -> Vec<f32> {
11490 let mut out = attention::take_buf(x.len());
11491 if experts.is_empty() {
11492 return out;
11493 }
11494 let pairs: Vec<_> = experts
11495 .iter()
11496 .map(|(e, _)| (&e.gate_proj, &e.up_proj))
11497 .collect();
11498 let downs: Vec<_> = experts.iter().map(|(e, _)| &e.down_proj).collect();
11499 let weights: Vec<_> = experts.iter().map(|(_, w)| *w).collect();
11500 let inter = experts[0].0.gate_proj.rows();
11501 let mut activations: Vec<Vec<f32>> = (0..experts.len()).map(|_| vec![0.0; inter]).collect();
11502 if QTensor::moe_gate_up_many(&pairs, x, &mut activations, pool)
11503 && QTensor::moe_down_many(&downs, &activations, &weights, &mut out, pool)
11504 {
11505 return out;
11506 }
11507 out.fill(0.0);
11508 for &(expert, weight) in experts {
11509 let mut one = dense_ffn(expert, x, pool);
11510 for (o, v) in out.iter_mut().zip(&one) {
11511 *o += weight * v;
11512 }
11513 attention::recycle_buf(&mut one);
11514 }
11515 out
11516}
11517
11518fn moe_ffn_cpu(
11520 m: &MoeFfn,
11521 x: &[f32],
11522 idx: &[usize],
11523 p: &[f32],
11524 wsum: f32,
11525 pool: Option<&Pool>,
11526) -> Vec<f32> {
11527 if let Some(out) = moe_ffn_cpu_batched(m, x, idx, p, wsum, pool) {
11528 return out;
11529 }
11530 let mut out = attention::take_buf(x.len());
11531 for &e in idx {
11532 let mut eo = dense_ffn(&m.experts[e], x, pool);
11533 let w = p[e] / wsum * m.per_expert_scale.as_ref().map_or(1.0, |v| v[e]);
11534 for i in 0..out.len() {
11535 out[i] += w * eo[i];
11536 }
11537 attention::recycle_buf(&mut eo);
11538 }
11539 if let Some((se, gate)) = &m.shared {
11540 let mut so = dense_ffn(se, x, pool);
11541 let g = gate.as_ref().map_or(1.0, |gate| {
11542 let mut gl = [0.0f32; 1];
11543 gate.matvec(x, &mut gl, pool);
11544 1.0 / (1.0 + (-gl[0]).exp())
11545 });
11546 for i in 0..out.len() {
11547 out[i] += g * so[i];
11548 }
11549 attention::recycle_buf(&mut so);
11550 }
11551 out
11552}
11553
11554#[allow(clippy::too_many_arguments)]
11562fn mla_attention(
11563 w: &MlaWeights,
11564 normed: &[f32],
11565 cache: &mut crate::kv_cache::LayerKvCache,
11566 position: usize,
11567 inv_freq: &[f32],
11568 rope_scale: f32,
11569 eps: f64,
11570 pool: Option<&Pool>,
11571) -> Vec<f32> {
11572 let (nh, dr, dn, dv, lora) = (w.nh, w.qk_rope, w.qk_nope, w.v_dim, w.lora);
11573 let hd = dr + dn;
11574 let mut q = vec![0.0f32; nh * hd];
11575 match (&w.q_a, &w.q_a_norm) {
11576 (Some(qa), Some(qn)) => {
11577 let mut t = vec![0.0f32; qa.rows()];
11578 qa.matvec(normed, &mut t, pool);
11579 let tn = inference::rms_norm(&t, qn, eps, NormStyle::Qwen);
11580 w.q_proj.matvec(&tn, &mut q, pool);
11581 }
11582 _ => w.q_proj.matvec(normed, &mut q, pool),
11583 }
11584 let mut ca = vec![0.0f32; lora + dr];
11585 w.kv_a.matvec(normed, &mut ca, pool);
11586 let (c_lat, k_rope) = ca.split_at_mut(lora);
11587 let latn = inference::rms_norm(c_lat, &w.kv_a_norm, eps, NormStyle::Qwen);
11588 let mut kvb = vec![0.0f32; nh * (dn + dv)];
11589 w.kv_b.matvec(&latn, &mut kvb, pool);
11590 if !w.nope {
11591 attention::rope_rotate_scaled(k_rope, position, inv_freq, rope_scale);
11592 }
11593 for h in 0..nh {
11594 if !w.nope {
11595 attention::rope_rotate_scaled(
11596 &mut q[h * hd..h * hd + dr],
11597 position,
11598 inv_freq,
11599 rope_scale,
11600 );
11601 }
11602 }
11603 let mut k = vec![0.0f32; nh * hd];
11604 let mut v = vec![0.0f32; nh * hd];
11605 for h in 0..nh {
11606 k[h * hd..h * hd + dr].copy_from_slice(k_rope);
11607 k[h * hd + dr..(h + 1) * hd].copy_from_slice(&kvb[h * (dn + dv)..h * (dn + dv) + dn]);
11608 v[h * hd..h * hd + dv].copy_from_slice(&kvb[h * (dn + dv) + dn..(h + 1) * (dn + dv)]);
11609 }
11610 cache.append(&k, &v, &vec![true; nh]);
11611 let (ao, mut imp) = attention::attend_all_heads(&q, cache, nh, 1, hd, w.scale, None, 0.0);
11612 attention::recycle_buf(&mut imp);
11613 let mut ov = vec![0.0f32; nh * dv];
11614 for h in 0..nh {
11615 ov[h * dv..(h + 1) * dv].copy_from_slice(&ao[h * hd..h * hd + dv]);
11616 }
11617 let mut out = vec![0.0f32; w.o_proj.rows()];
11618 w.o_proj.matvec(&ov, &mut out, pool);
11619 out
11620}
11621
11622fn dense_moe_ffn(
11629 dm: &DenseMoeFfn,
11630 x_normed: &[f32],
11631 h_raw: &[f32],
11632 eps: f64,
11633 norm_style: NormStyle,
11634 pool: Option<&Pool>,
11635) -> Vec<f32> {
11636 let mut d = dense_ffn(&dm.dense, x_normed, pool);
11637 d = inference::rms_norm(&d, &dm.post_norm_1, eps, norm_style);
11638 let m = &dm.moe;
11639 let ne = m.experts.len();
11640 let mut logits = vec![0.0f32; ne];
11641 if m.router_input_norm {
11642 let ss: f32 = h_raw.iter().map(|v| v * v).sum::<f32>() / h_raw.len() as f32;
11643 let inv = 1.0 / (ss + eps as f32).sqrt();
11644 let xr: Vec<f32> = h_raw.iter().map(|v| v * inv).collect();
11645 m.router.matvec(&xr, &mut logits, pool);
11646 } else {
11647 m.router.matvec(h_raw, &mut logits, pool);
11648 }
11649 let (idx, p, wsum) = moe_route(&logits, m, None);
11650 {
11651 let mut st = m.stats.borrow_mut();
11652 if st.len() < ne {
11653 st.resize(ne, 0);
11654 }
11655 for &e in &idx {
11656 st[e] += 1;
11657 }
11658 }
11659 let x2 = inference::rms_norm(h_raw, &dm.pre_norm_2, eps, norm_style);
11660 let mo = moe_ffn_cpu(m, &x2, &idx, &p, wsum, pool);
11661 let mo = inference::rms_norm(&mo, &dm.post_norm_2, eps, norm_style);
11662 for (di, mi) in d.iter_mut().zip(&mo) {
11663 *di += mi;
11664 }
11665 d
11666}
11667
11668fn moe_gpu_refused(why: &'static str) {
11675 use std::sync::atomic::{AtomicBool, Ordering};
11676 static SAID: AtomicBool = AtomicBool::new(false);
11677 if !SAID.swap(true, Ordering::Relaxed) {
11678 tracing::warn!("MoE GPU block refused ({why}) — experts run on the CPU");
11679 }
11680}
11681
11682fn moe_ffn_gpu(
11683 m: &MoeFfn,
11684 x: &[f32],
11685 idx: &[usize],
11686 p: &[f32],
11687 wsum: f32,
11688 pool: Option<&Pool>,
11689) -> Option<Vec<f32>> {
11690 use crate::gpu::MoeJob;
11691
11692 let mut jobs: Vec<MoeJob> = Vec::with_capacity(idx.len() + 1);
11693 let mut model_ref = None;
11694 for &e in idx {
11695 if moe_push_job(&m.experts[e], x, p[e] / wsum, &mut jobs, &mut model_ref).is_none() {
11696 moe_gpu_refused("push_job(expert)");
11697 return None;
11698 }
11699 }
11700 if let Some((se, gate)) = &m.shared {
11701 let g = gate.as_ref().map_or(1.0, |gate| {
11702 let mut gl = [0.0f32; 1];
11703 gate.matvec(x, &mut gl, pool);
11704 1.0 / (1.0 + (-gl[0]).exp())
11705 });
11706 if moe_push_job(se, x, g, &mut jobs, &mut model_ref).is_none() {
11707 moe_gpu_refused("push_job(shared)");
11708 return None;
11709 }
11710 }
11711 let Some(model) = model_ref else {
11712 moe_gpu_refused("no model_ref");
11713 return None;
11714 };
11715 let hidden = jobs[0].down.1;
11716 let mut out = vec![0.0f32; hidden];
11717 if crate::gpu::moe_block(&model, &jobs, &mut out) {
11718 Some(out)
11719 } else {
11720 moe_gpu_refused("gpu::moe_block");
11721 None
11722 }
11723}
11724
11725fn ffn_forward(
11727 ffn: &FfnKind,
11728 x: &[f32],
11729 pool: Option<&Pool>,
11730 experts_allowed: Option<&[bool]>,
11731) -> Vec<f32> {
11732 match ffn {
11733 FfnKind::Dense(d) if !d.segs.is_empty() => tube_ffn(d, x, 1, pool, None),
11734 FfnKind::Dense(d) => dense_ffn(d, x, pool),
11735 FfnKind::Moe(m) => moe_ffn(m, x, pool, experts_allowed),
11736 FfnKind::DenseMoe(_) => unreachable!("DenseMoe dispatches via dense_moe_ffn"),
11740 }
11741}
11742
11743fn ffn_forward_pair(
11747 ffn: &FfnKind,
11748 x1: &[f32],
11749 x2: &[f32],
11750 pool: Option<&Pool>,
11751 experts_allowed: Option<&[bool]>,
11752) -> (Vec<f32>, Vec<f32>) {
11753 let d = match ffn {
11754 FfnKind::Dense(d) if !d.segs.is_empty() => {
11757 return (
11758 tube_ffn(d, x1, 1, pool, None),
11759 tube_ffn(d, x2, 1, pool, None),
11760 );
11761 }
11762 FfnKind::Dense(d) => d,
11763 FfnKind::Moe(m) => {
11764 return (
11765 moe_ffn(m, x1, pool, experts_allowed),
11766 moe_ffn(m, x2, pool, experts_allowed),
11767 );
11768 }
11769 FfnKind::DenseMoe(_) => unreachable!("DenseMoe dispatches via dense_moe_ffn"),
11770 };
11771 let inter = d.gate_proj.rows();
11772 FFN_SCRATCH.with(|s| {
11773 let mut s = s.borrow_mut();
11774 let [g1, g2, u1, u2] = &mut *s;
11775 g1.resize(inter, 0.0);
11776 g2.resize(inter, 0.0);
11777 u1.resize(inter, 0.0);
11778 u2.resize(inter, 0.0);
11779 QTensor::matvec2_many(
11782 [&d.gate_proj, &d.up_proj],
11783 x1,
11784 x2,
11785 [g1.as_mut_slice(), u1.as_mut_slice()],
11786 [g2.as_mut_slice(), u2.as_mut_slice()],
11787 pool,
11788 );
11789 for i in 0..inter {
11790 g1[i] = d.act.combine(g1[i], u1[i]);
11791 g2[i] = d.act.combine(g2[i], u2[i]);
11792 }
11793 let mut o1 = attention::take_buf(d.down_proj.rows());
11794 let mut o2 = attention::take_buf(d.down_proj.rows());
11795 d.down_proj.matvec2(g1, g2, &mut o1, &mut o2, pool);
11796 (o1, o2)
11797 })
11798}
11799
11800#[cfg(test)]
11801mod tests {
11802
11803 #[test]
11804 fn cancel_flag_stops_generation() {
11805 let mut p = create_test_pipeline(16, 32, 2, 2, 8, 2, 32);
11806 p.cancel.store(true, std::sync::atomic::Ordering::Relaxed);
11809 let r = p.generate_from_ids(&[1, 2, 3], 8, None, None).unwrap();
11810 assert_eq!(r.finish_reason, "cancelled");
11811 assert!(
11812 r.token_ids.is_empty(),
11813 "no tokens after cancel: {:?}",
11814 r.token_ids
11815 );
11816 let r2 = p.generate_from_ids(&[1, 2, 3], 4, None, None).unwrap();
11818 assert_ne!(r2.finish_reason, "cancelled");
11819 }
11820 use super::*;
11821
11822 #[test]
11830 fn dynamic_ffn_equals_the_zeroing_arm() {
11831 let (hidden, inter) = (8usize, 32usize);
11832 let synth = |n: usize, salt: usize| -> Vec<f32> {
11833 (0..n)
11834 .map(|i| (((i * 29 + salt * 13 + 7) % 89) as f32 / 89.0 - 0.5) * 0.6)
11835 .collect()
11836 };
11837 let down = synth(hidden * inter, 3);
11838 let mut down_t = vec![0.0f32; inter * hidden];
11839 for r in 0..hidden {
11840 for c in 0..inter {
11841 down_t[c * hidden + r] = down[r * inter + c];
11842 }
11843 }
11844 let d = DenseFfn {
11845 gate_proj: QTensor::from_f32(synth(inter * hidden, 1), inter, hidden),
11846 up_proj: QTensor::from_f32(synth(inter * hidden, 2), inter, hidden),
11847 down_proj: QTensor::from_f32(down.clone(), hidden, inter),
11848 act: Act::Silu,
11849 down_t: Some(QTensor::from_f32(down_t, inter, hidden)),
11850 segs: Vec::new(),
11851 };
11852 let x = synth(hidden, 11);
11853 let k = 12usize;
11854 let got = dense_ffn_dynamic(&d, &x, None, k).expect("down_t present");
11855 let mut g = vec![0.0f32; inter];
11857 d.gate_proj.matvec(&x, &mut g, None);
11858 let mut u = vec![0.0f32; inter];
11859 d.up_proj.matvec(&x, &mut u, None);
11860 for v in g.iter_mut() {
11861 *v = inference::silu(*v);
11862 }
11863 keep_top_k(&mut g, k);
11864 for i in 0..inter {
11865 g[i] *= u[i];
11866 }
11867 let mut want = vec![0.0f32; hidden];
11868 d.down_proj.matvec(&g, &mut want, None);
11869 for (a, b) in want.iter().zip(&got) {
11870 assert!((a - b).abs() < 1e-5, "dynamic {b} vs reference {a}");
11871 }
11872 }
11873
11874 #[test]
11880 fn tube_ffn_open_equals_dense_and_closed_equals_masked() {
11881 let (hidden, core, tube) = (8usize, 12usize, 8usize);
11882 let inter = core + tube;
11883 let synth = |n: usize, salt: usize| -> Vec<f32> {
11884 (0..n)
11885 .map(|i| (((i * 41 + salt * 17 + 5) % 97) as f32 / 97.0 - 0.5) * 0.5)
11886 .collect()
11887 };
11888 let (g_all, u_all) = (synth(inter * hidden, 1), synth(inter * hidden, 2));
11889 let d_all = synth(hidden * inter, 3);
11890 let dense = DenseFfn {
11892 gate_proj: QTensor::from_f32(g_all.clone(), inter, hidden),
11893 up_proj: QTensor::from_f32(u_all.clone(), inter, hidden),
11894 down_proj: QTensor::from_f32(d_all.clone(), hidden, inter),
11895 act: Act::Silu,
11896 down_t: None,
11897 segs: Vec::new(),
11898 };
11899 let rows =
11900 |v: &[f32], a: usize, b: usize| -> Vec<f32> { v[a * hidden..b * hidden].to_vec() };
11901 let cols = |v: &[f32], a: usize, b: usize| -> Vec<f32> {
11902 let mut o = Vec::with_capacity(hidden * (b - a));
11903 for r in 0..hidden {
11904 o.extend_from_slice(&v[r * inter + a..r * inter + b]);
11905 }
11906 o
11907 };
11908 let tubed = DenseFfn {
11909 down_t: None,
11910 gate_proj: QTensor::from_f32(rows(&g_all, 0, core), core, hidden),
11911 up_proj: QTensor::from_f32(rows(&u_all, 0, core), core, hidden),
11912 down_proj: QTensor::from_f32(cols(&d_all, 0, core), hidden, core),
11913 act: Act::Silu,
11914 segs: vec![FfnSeg {
11915 gate: QTensor::from_f32(rows(&g_all, core, inter), tube, hidden),
11916 up: QTensor::from_f32(rows(&u_all, core, inter), tube, hidden),
11917 down: QTensor::from_f32(cols(&d_all, core, inter), hidden, tube),
11918 start: core,
11919 width: tube,
11920 }],
11921 };
11922 let x = synth(hidden, 7);
11923 let want = dense_ffn(&dense, &x, None);
11924 let got = tube_ffn(&tubed, &x, 1, None, None);
11925 for (a, b) in want.iter().zip(&got) {
11926 assert!((a - b).abs() < 1e-5, "open tube: {a} vs {b}");
11927 }
11928 let mut bits = vec![0u8; inter.div_ceil(8)];
11930 for n in 0..core {
11931 bits[n / 8] |= 1 << (n % 8);
11932 }
11933 let closed = tube_ffn(&tubed, &x, 1, None, Some(&bits));
11934 let masked = dense_ffn_masked(&dense, &x, None, &bits);
11935 for (a, b) in masked.iter().zip(&closed) {
11936 assert!((a - b).abs() < 1e-5, "closed tube: {a} vs {b}");
11937 }
11938 let batch = tube_ffn(&tubed, &x, 1, None, Some(&bits));
11940 for (a, b) in closed.iter().zip(&batch) {
11941 assert_eq!(a, b, "batch arm disagrees with decode arm");
11942 }
11943 }
11944
11945 #[test]
11947 fn sparse_ffn_quant_equals_dense_with_inactive_zeroed() {
11948 let (hidden, inter) = (16usize, 40usize);
11949 let synth = |n: usize, salt: usize| -> Vec<f32> {
11950 (0..n)
11951 .map(|i| (((i * 37 + salt * 11 + 3) % 101) as f32 / 101.0 - 0.5) * 0.4)
11952 .collect()
11953 };
11954 let d = DenseFfn {
11955 gate_proj: QTensor::from_f32(synth(inter * hidden, 1), inter, hidden),
11956 up_proj: QTensor::from_f32(synth(inter * hidden, 2), inter, hidden),
11957 down_proj: QTensor::from_f32(synth(hidden * inter, 3), hidden, inter),
11958 act: Act::Silu,
11959 down_t: None,
11960 segs: Vec::new(),
11961 };
11962 let x = synth(hidden, 9);
11963 let active: Vec<u16> = (0..inter as u16).filter(|i| i % 3 == 0).collect();
11965
11966 let sparse = sparse_ffn_quant(&d, &x, &active, hidden, None);
11967
11968 let mut g = vec![0.0f32; inter];
11970 d.gate_proj.matvec(&x, &mut g, None);
11971 let mut u = vec![0.0f32; inter];
11972 d.up_proj.matvec(&x, &mut u, None);
11973 let act_set: std::collections::HashSet<u16> = active.iter().copied().collect();
11974 for i in 0..inter {
11975 g[i] = if act_set.contains(&(i as u16)) {
11976 inference::silu(g[i]) * u[i]
11977 } else {
11978 0.0
11979 };
11980 }
11981 let mut reference = vec![0.0f32; hidden];
11982 d.down_proj.matvec(&g, &mut reference, None);
11983
11984 let max_d = sparse
11985 .iter()
11986 .zip(&reference)
11987 .map(|(a, b)| (a - b).abs())
11988 .fold(0.0f32, f32::max);
11989 assert!(max_d < 1e-5, "sparse != dense-zeroed: max|Δ| = {max_d}");
11990 }
11991
11992 fn attach_test_mtp(p: &mut Pipeline) {
11994 let (h, inter, heads, kv, hd) = (
11995 p.hidden_size,
11996 p.intermediate_size,
11997 p.num_heads,
11998 p.num_kv_heads,
11999 p.head_dim,
12000 );
12001 let synth = |n: usize, salt: usize| -> Vec<f32> {
12002 (0..n)
12003 .map(|i| (((i * 29 + salt * 23 + 5) % 101) as f32 / 101.0 - 0.5) * 0.2)
12004 .collect()
12005 };
12006 let qt = |rows: usize, cols: usize, salt: usize| -> QTensor {
12007 QTensor::from_f32(synth(rows * cols, salt), rows, cols)
12008 };
12009 p.mtp = Some(MtpModule {
12010 enorm: vec![1.0; h],
12011 hnorm: vec![1.0; h],
12012 eh_proj: qt(h, 2 * h, 301),
12013 layer: LayerWeights {
12014 input_norm: vec![1.0; h],
12015 post_norm: vec![1.0; h],
12016 attn_out_norm: None,
12017 ffn_out_norm: None,
12018 layer_scale: None,
12019 ffn: FfnKind::Dense(DenseFfn {
12020 gate_proj: qt(inter, h, 315),
12021 up_proj: qt(inter, h, 316),
12022 down_proj: qt(h, inter, 317),
12023 act: Act::Silu,
12024 down_t: None,
12025 segs: Vec::new(),
12026 }),
12027 attn: AttnKind::Full {
12028 bias: None,
12029 wq: qt(heads * hd, h, 311),
12030 wk: qt(kv * hd, h, 312),
12031 wv: qt(kv * hd, h, 313),
12032 wo: qt(h, heads * hd, 314),
12033 q_norm: None,
12034 k_norm: None,
12035 output_gate: false,
12036 softplus_gate: None,
12037 },
12038 },
12039 final_norm: vec![1.0; h],
12040 kv: crate::kv_cache::LayerKvCache::new(kv, hd),
12041 });
12042 }
12043
12044 #[test]
12045 fn speculative_equals_vanilla_greedy() {
12046 unsafe { std::env::set_var("CMF_GPU_WGPU_GRAPH", "0") };
12050 let run = |spec: bool| {
12051 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 2, 260);
12052 p.sampler_config.temperature = 0.0;
12053 attach_test_mtp(&mut p);
12054 p.speculative = spec;
12055 let r = p.generate("abcdef", 12, None, None).unwrap();
12056 (r.token_ids, r.mtp_drafted, r.mtp_accepted)
12057 };
12058 let (vanilla, d0, _) = run(false);
12059 let (spec, d1, a1) = run(true);
12060 assert_eq!(d0, 0, "vanilla path must not draft");
12061 assert!(d1 > 0, "speculative path must draft");
12062 assert_eq!(
12063 vanilla, spec,
12064 "speculative must reproduce the exact greedy sequence (accepted {a1}/{d1})"
12065 );
12066 }
12067
12068 #[test]
12069 fn speculative_accepts_constant_oracle() {
12070 unsafe { std::env::set_var("CMF_GPU_WGPU_GRAPH", "0") };
12072 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 64);
12073 p.sampler_config.temperature = 0.0;
12074 p.sampler_config.repetition_penalty = 1.0;
12075 p.weights.lm_head = QTensor::from_f32(vec![0.01; 64 * 8], 64, 8);
12078 attach_test_mtp(&mut p);
12079 p.speculative = true;
12080 let r = p.generate("abcd", 10, None, None).unwrap();
12081 assert!(r.mtp_drafted > 0);
12082 assert_eq!(
12083 r.mtp_accepted, r.mtp_drafted,
12084 "constant logits → every draft accepted"
12085 );
12086 assert!(r.token_ids.windows(2).all(|w| w[0] == w[1]));
12089 }
12090
12091 #[test]
12092 fn empty_prompt_is_an_error_not_a_panic() {
12093 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 260);
12094 let r = p.generate("", 4, None, None);
12095 assert!(r.is_err(), "empty prompt must be a clean error");
12096 }
12097
12098 #[test]
12099 fn every_token_enters_kv_exactly_once() {
12100 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 2, 260);
12101 p.sampler_config.temperature = 0.0;
12103 let r = p.generate("abc", 2, None, None).unwrap();
12104 assert_eq!(r.prompt_tokens, 3);
12105 assert_eq!(
12109 p.kv_cache.seq_len(),
12110 3 + r.tokens_generated - 1,
12111 "each token must be cached exactly once (v1 cached the last prompt token twice)"
12112 );
12113 }
12114
12115 #[test]
12116 fn generation_is_reproducible_with_seed() {
12117 let run = || {
12118 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 2, 260);
12119 p.generate("hello", 8, None, None).unwrap().token_ids
12120 };
12121 assert_eq!(run(), run());
12122 }
12123
12124 #[test]
12125 fn resetting_sampler_restarts_the_seeded_stream() {
12126 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 2, 260);
12127 let config = SamplerConfig {
12128 seed: Some(1234),
12129 ..SamplerConfig::default()
12130 };
12131 p.set_sampler_config(config.clone());
12132 let first = p.generate("hello", 8, None, None).unwrap().token_ids;
12133 p.set_sampler_config(config);
12134 let second = p.generate("hello", 8, None, None).unwrap().token_ids;
12135 assert_eq!(first, second);
12136 }
12137
12138 #[test]
12139 fn eviction_bounds_the_cache() {
12140 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 260);
12141 p.kv_cache.max_seq_len = 6;
12142 p.sampler_config.temperature = 0.0;
12143 let _ = p.generate("abcd", 12, None, None).unwrap();
12144 assert!(
12145 p.kv_cache.seq_len() <= 6 + 1,
12146 "cache must stay bounded by max_seq_len (got {})",
12147 p.kv_cache.seq_len()
12148 );
12149 }
12150
12151 #[test]
12152 fn confidence_matches_tokens_and_is_a_probability() {
12153 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 64);
12154 p.sampler_config.temperature = 0.0;
12155 p.sampler_config.repetition_penalty = 1.0;
12156 let r = p.generate("abcd", 10, None, None).unwrap();
12157 assert_eq!(
12158 r.token_confidence.len(),
12159 r.token_ids.len(),
12160 "one confidence per emitted token"
12161 );
12162 for &c in &r.token_confidence {
12163 assert!((0.0..=1.0).contains(&c), "confidence out of [0,1]: {c}");
12164 }
12165 let logits = [1.0f32, 3.0, 0.5, 3.0];
12167 let p0 = top1_prob_t(&logits, 1, 1.0);
12168 let p1 = top1_prob_t(&logits, 3, 1.0);
12169 assert!((p0 - p1).abs() < 1e-6, "equal logits → equal prob");
12170 assert!(p0 > 0.0 && p0 < 1.0);
12171 let sharp = top1_prob_t(&logits, 1, 1.0);
12173 let soft = top1_prob_t(&logits, 1, 2.0);
12174 assert!(soft < sharp, "higher temperature lowers peak confidence");
12175 }
12176
12177 #[test]
12178 fn trace_is_opt_in_and_parallels_the_output() {
12179 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 64);
12181 p.sampler_config.temperature = 0.0;
12182 p.sampler_config.repetition_penalty = 1.0;
12183 let r = p.generate("abcd", 10, None, None).unwrap();
12184 assert!(r.traces.is_empty(), "trace must be empty unless enabled");
12185
12186 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 64);
12188 p.sampler_config.temperature = 0.0;
12189 p.sampler_config.repetition_penalty = 1.0;
12190 p.set_trace(true);
12191 let r = p.generate("abcd", 10, None, None).unwrap();
12192 assert_eq!(r.traces.len(), r.token_ids.len(), "one trace row per token");
12193 for (i, tr) in r.traces.iter().enumerate() {
12194 assert_eq!(tr.t, i, "trace index is sequential");
12195 assert_eq!(tr.token_id, r.token_ids[i], "trace token_id matches output");
12196 assert_eq!(
12197 tr.confidence, r.token_confidence[i],
12198 "trace confidence matches the confidence channel"
12199 );
12200 assert!(tr.active_skill.is_none() && tr.recon.is_none() && !tr.switched);
12202 }
12203 }
12204
12205 #[test]
12206 fn explain_prefill_logits_match_greedy_first_token() {
12207 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 64);
12211 p.sampler_config.temperature = 0.0;
12212 p.sampler_config.repetition_penalty = 1.0;
12213 let ids = p.tokenizer.encode("abcd");
12214 let logits = p.prefill_next_logits(&ids, None);
12215 let argmax = logits
12216 .iter()
12217 .enumerate()
12218 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
12219 .unwrap()
12220 .0 as u32;
12221 let r = p.generate("abcd", 1, None, None).unwrap();
12222 assert_eq!(
12223 argmax, r.token_ids[0],
12224 "explain preview must match greedy emit"
12225 );
12226 }
12227
12228 #[test]
12229 fn laguna_shared_expert_is_unconditionally_added() {
12230 let matrix = |values: Vec<f32>| QTensor::from_f32(values, 2, 2);
12231 let identity = || matrix(vec![1.0, 0.0, 0.0, 1.0]);
12232 let zero_dense = || DenseFfn {
12233 gate_proj: matrix(vec![0.0; 4]),
12234 up_proj: matrix(vec![0.0; 4]),
12235 down_proj: matrix(vec![0.0; 4]),
12236 act: Act::Silu,
12237 down_t: None,
12238 segs: Vec::new(),
12239 };
12240 let shared = DenseFfn {
12241 gate_proj: identity(),
12242 up_proj: identity(),
12243 down_proj: identity(),
12244 act: Act::Silu,
12245 down_t: None,
12246 segs: Vec::new(),
12247 };
12248 let x = [1.0, 2.0];
12249 let expected = dense_ffn(&shared, &x, None);
12250 let moe = MoeFfn {
12251 router: QTensor::from_f32(vec![0.0, 0.0], 1, 2),
12252 experts: vec![zero_dense()],
12253 top_k: 1,
12254 norm_topk_prob: true,
12255 router_sigmoid: true,
12256 expert_bias: None,
12257 routed_scaling: 1.0,
12258 route_tau: None,
12259 shared: Some((shared, None)),
12260 stats: std::cell::RefCell::new(Vec::new()),
12261 act_sq: std::cell::RefCell::new(Vec::new()),
12262 act_rows: std::cell::RefCell::new(Vec::new()),
12263 mask: None,
12264 per_expert_scale: None,
12265 router_input_norm: false,
12266 resonance: None,
12267 };
12268 let actual = moe_ffn_cpu(&moe, &x, &[0], &[0.0], 1.0, None);
12269 for (actual, expected) in actual.iter().zip(expected) {
12270 assert!((actual - expected).abs() < 1e-6);
12271 }
12272 }
12273}