1use crate::attention::{self, QwenAttnCfg};
10use crate::inference;
11use crate::kv_cache::KvCache;
12use crate::linear_core::{
13 GdnCfg, GdnWeights, ShortConvCfg, ShortConvWeights, VmfPhaseCfg, VmfPhaseWeights, gdn_forward,
14 gdn_pair, short_conv_forward, short_conv_forward_batch, short_conv_pair, vmf_phase_forward,
15 vmf_phase_pair,
16};
17use crate::pool::Pool;
18use crate::qtensor::QTensor;
19use crate::sampler::{self, SamplerConfig, SamplerScratch, SplitMix64};
20use crate::tokenizer::Tokenizer;
21use cortiq_core::mask::TaskMask;
22use cortiq_core::types::NormStyle;
23
24pub static GLOBAL_USE_GPU: std::sync::atomic::AtomicBool =
25 std::sync::atomic::AtomicBool::new(false);
26
27struct ForwardScratch {
31 n1: Vec<f32>,
32 n2: Vec<f32>,
33 p1: Vec<f32>,
34 p2: Vec<f32>,
35}
36
37impl ForwardScratch {
38 fn new(hidden: usize) -> Self {
39 Self {
40 n1: vec![0.0; hidden],
41 n2: vec![0.0; hidden],
42 p1: vec![0.0; hidden],
43 p2: vec![0.0; hidden],
44 }
45 }
46}
47
48pub struct Pipeline {
50 pub tokenizer: std::sync::Arc<Tokenizer>,
53 pub kv_cache: KvCache,
54 pub sampler_config: SamplerConfig,
55 pub weights: PipelineWeights,
56 pub hidden_size: usize,
57 pub intermediate_size: usize,
58 pub num_heads: usize,
59 pub num_kv_heads: usize,
60 pub head_dim: usize,
61 pub num_layers: usize,
63 pub physical_layers: usize,
65 pub loop_final_norm: bool,
67 pub vocab_size: usize,
68 pub rms_eps: f64,
69 pub rope_base: f32,
70 pub norm_style: NormStyle,
71 pub rotary_dim: usize,
73 pub attention_heads_per_layer: Option<Vec<usize>>,
75 pub vmf_cfg: Option<VmfPhaseCfg>,
77 pub gdn_cfg: Option<GdnCfg>,
79 pub logit_multiplier: Option<f32>,
81 pub cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
86 pub kv_history: Vec<u32>,
91 pub kda_cfg: Option<crate::linear_core::KdaCfg>,
93 pub g3n: Option<Box<(crate::g3n::G3nGlobals, Vec<crate::g3n::G3nLayer>)>>,
96 pub dsv4: Option<
100 Box<(
101 crate::dsv4::Dsv4Globals,
102 Vec<crate::dsv4::Dsv4Layer>,
103 crate::dsv4::Dsv4Cfg,
104 crate::dsv4::Dsv4State,
105 )>,
106 >,
107 pub short_conv_cfg: Option<ShortConvCfg>,
110 pub mtp: Option<MtpModule>,
112 pub speculative: bool,
114 rng: SplitMix64,
115 sampler_scratch: SamplerScratch,
116 pub(crate) inv_freq: std::sync::Arc<Vec<f32>>,
120 ws: ForwardScratch,
124 pool: Option<std::sync::Arc<Pool>>,
126 pub(crate) model: Option<std::sync::Arc<cortiq_core::CmfModel>>,
130 pub(crate) dyn_force_f32: bool,
132 pub(crate) dyn_skill_layers: Vec<Option<Vec<usize>>>,
137 pub(crate) dyn_active: Option<usize>,
143 pub(crate) dyn_blend_loaded: bool,
147 pub(crate) dyn_phi_layer: Option<usize>,
150 dyn_phi_ema: Vec<f32>,
152 dyn_phi_seen: usize,
153 pub dyn_router: Option<crate::swarm::DynRouter>,
156 o1_cfg: Option<crate::nystrom::O1Cfg>,
159 o1_epoch: u64,
162 o1_flags: Vec<bool>,
164 trace: bool,
167 calib_temp: f32,
170 #[cfg_attr(not(target_os = "macos"), allow(dead_code))]
172 graph_kv_id: u64,
173 #[cfg_attr(not(target_os = "macos"), allow(dead_code))]
176 graph_want_logits: bool,
177 graph_logits: Option<Vec<f32>>,
180 pub embed_multiplier: f32,
182 pub attn_scale: f32,
185 pub swa: Option<(usize, usize)>,
188 pub sliding_layers: Option<Vec<bool>>,
191 pub inv_freq_local: Option<std::sync::Arc<Vec<f32>>>,
194 pub rotary_dim_local: Option<usize>,
195 pub rope_scale: f32,
196 pub rope_scale_local: f32,
197 pub global_attn: Option<(usize, usize)>,
200 pub inv_freq_global: Option<std::sync::Arc<Vec<f32>>>,
203 pub attn_v_norm: bool,
205 pub final_softcap: Option<f32>,
207 pub attn_softcap: f32,
209 confidence_on: bool,
213}
214
215#[cfg(target_os = "macos")]
216impl Drop for Pipeline {
217 fn drop(&mut self) {
218 crate::gpu::kv_mirror_drop(self.graph_kv_id);
219 }
220}
221
222pub struct PipelineWeights {
227 pub embed_tokens: QTensor,
229 pub layers: Vec<LayerWeights>,
231 pub lm_head: QTensor,
233 pub final_norm: Vec<f32>,
235}
236
237pub struct LayerWeights {
239 pub input_norm: Vec<f32>,
240 pub post_norm: Vec<f32>,
243 pub attn_out_norm: Option<Vec<f32>>,
246 pub layer_scale: Option<f32>,
248 pub ffn_out_norm: Option<Vec<f32>>,
251 pub ffn: FfnKind,
252 pub attn: AttnKind,
253}
254
255#[derive(Clone, Copy, PartialEq, Debug, Default)]
258pub enum Act {
259 #[default]
260 Silu,
261 GeluTanh,
262 Situ {
265 beta: f32,
266 linear_beta: f32,
267 },
268}
269
270impl Act {
271 pub fn from_arch(name: &str) -> Self {
272 if name == "gelu_tanh" {
273 Self::GeluTanh
274 } else {
275 Self::Silu
276 }
277 }
278
279 pub fn from_arch_full(arch: &cortiq_core::ModelArch) -> Self {
281 match arch.hidden_act.as_str() {
282 "situ" => Self::Situ {
283 beta: arch.activation_situ_beta.unwrap_or(1.0) as f32,
284 linear_beta: arch.activation_situ_linear_beta.unwrap_or(0.0) as f32,
285 },
286 other => Self::from_arch(other),
287 }
288 }
289
290 #[inline]
291 pub fn apply(self, x: f32) -> f32 {
292 match self {
293 Self::Silu => inference::silu(x),
294 Self::GeluTanh => inference::gelu_tanh(x),
295 Self::Situ { beta, .. } => beta * (x / beta).tanh() * (1.0 / (1.0 + (-x).exp())),
296 }
297 }
298
299 #[inline]
302 pub fn combine(self, g: f32, u: f32) -> f32 {
303 match self {
304 Self::Situ { linear_beta, .. } if linear_beta > 0.0 => {
305 self.apply(g) * (linear_beta * (u / linear_beta).tanh())
306 }
307 _ => self.apply(g) * u,
308 }
309 }
310}
311
312pub struct DenseFfn {
314 pub gate_proj: QTensor,
315 pub up_proj: QTensor,
316 pub down_proj: QTensor,
317 pub act: Act,
319}
320
321pub enum FfnKind {
324 Dense(DenseFfn),
325 Moe(MoeFfn),
329 DenseMoe(Box<DenseMoeFfn>),
336}
337
338pub struct DenseMoeFfn {
340 pub dense: DenseFfn,
341 pub moe: MoeFfn,
342 pub post_norm_1: Vec<f32>,
344 pub pre_norm_2: Vec<f32>,
347 pub post_norm_2: Vec<f32>,
349}
350
351pub struct MoeFfn {
352 pub router: QTensor,
354 pub experts: Vec<DenseFfn>,
355 pub top_k: usize,
356 pub norm_topk_prob: bool,
357 pub router_sigmoid: bool,
360 pub expert_bias: Option<Vec<f32>>,
364 pub routed_scaling: f32,
367 pub route_tau: Option<f32>,
373 pub shared: Option<(DenseFfn, Option<QTensor>)>,
376 pub stats: std::cell::RefCell<Vec<u64>>,
380 pub act_sq: std::cell::RefCell<Vec<f64>>,
387 pub act_rows: std::cell::RefCell<Vec<f32>>,
393 pub mask: Option<Vec<bool>>,
398 pub per_expert_scale: Option<Vec<f32>>,
401 pub router_input_norm: bool,
405}
406
407pub enum AttnKind {
410 Full {
412 wq: QTensor,
413 wk: QTensor,
414 wv: QTensor,
415 wo: QTensor,
416 q_norm: Option<Vec<f32>>,
417 k_norm: Option<Vec<f32>>,
418 output_gate: bool,
419 softplus_gate: Option<(QTensor, bool)>,
423 bias: Option<(Vec<f32>, Vec<f32>, Vec<f32>)>,
425 },
426 Linear(VmfPhaseWeights),
428 LinearGdn(GdnWeights),
430 ShortConv(ShortConvWeights),
433 Mla(Box<MlaWeights>),
441 Kda(Box<crate::linear_core::KdaWeights>),
445}
446
447pub struct MlaWeights {
449 pub q_proj: QTensor,
453 pub q_a: Option<QTensor>,
456 pub q_a_norm: Option<Vec<f32>>,
457 pub kv_a: QTensor,
459 pub kv_a_norm: Vec<f32>,
461 pub kv_b: QTensor,
463 pub o_proj: QTensor,
465 pub nh: usize,
466 pub qk_rope: usize,
467 pub qk_nope: usize,
468 pub v_dim: usize,
469 pub lora: usize,
470 pub scale: f32,
472 pub nope: bool,
474}
475
476pub struct MtpModule {
481 pub enorm: Vec<f32>,
482 pub hnorm: Vec<f32>,
483 pub eh_proj: QTensor,
485 pub layer: LayerWeights,
486 pub final_norm: Vec<f32>,
487 pub kv: crate::kv_cache::LayerKvCache,
488}
489
490pub struct GenerateResult {
492 pub text: String,
493 pub token_ids: Vec<u32>,
494 pub prompt_tokens: usize,
495 pub tokens_generated: usize,
496 pub finish_reason: String,
497 pub mtp_drafted: usize,
499 pub mtp_accepted: usize,
500 pub token_confidence: Vec<f32>,
505 pub traces: Vec<TokenTrace>,
508}
509
510#[derive(Clone, Debug)]
515pub struct TokenTrace {
516 pub t: usize,
518 pub token_id: u32,
520 pub confidence: f32,
522 pub active_skill: Option<String>,
524 pub recon: Option<f32>,
528 pub switched: bool,
531}
532
533fn top1_prob_t(logits: &[f32], id: u32, temp: f32) -> f32 {
538 let t = if temp > 1e-3 { temp } else { 1.0 };
539 let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
540 let sum: f32 = logits.iter().map(|&v| ((v - max) / t).exp()).sum();
541 if sum > 0.0 {
542 (((logits[id as usize] - max) / t).exp()) / sum
543 } else {
544 0.0
545 }
546}
547
548fn prefill_batched() -> bool {
551 std::env::var("CMF_PREFILL")
552 .map(|v| v != "seq")
553 .unwrap_or(true)
554}
555
556impl Pipeline {
563 fn can_prefill_batched(&self) -> bool {
564 prefill_batched() && !self.weights.layers.is_empty()
565 }
566}
567
568fn prefill_chunk() -> usize {
573 if let Some(n) = std::env::var("CMF_PREFILL_CHUNK")
574 .ok()
575 .and_then(|v| v.parse::<usize>().ok())
576 {
577 return n.max(1);
578 }
579 if cfg!(target_os = "macos") {
580 512
581 } else if cfg!(target_arch = "aarch64") {
582 256
585 } else {
586 48
587 }
588}
589
590pub type TokenCallback = Box<dyn FnMut(&str) -> bool + Send>;
592
593impl Pipeline {
594 #[inline]
598 pub fn phys_layer(&self, virtual_idx: usize) -> usize {
599 virtual_idx % self.physical_layers
600 }
601
602 #[inline]
605 pub fn is_loop_end(&self, virtual_idx: usize) -> bool {
606 self.loop_final_norm && (virtual_idx + 1) % self.physical_layers == 0
607 }
608
609 #[allow(clippy::too_many_arguments)]
611
612 #[cfg(target_os = "macos")]
631 fn graph_prefill_preferred(&self) -> bool {
632 if !crate::gpu::enabled_here()
633 || !crate::gpu::q1_force()
634 || std::env::var("CMF_GPU_BLOCK")
635 .map(|v| v == "0")
636 .unwrap_or(false)
637 {
638 return false;
639 }
640 self.weights
641 .layers
642 .iter()
643 .any(|lw| matches!(&lw.attn, AttnKind::LinearGdn(w) if w.in_proj_qkv.is_q1()))
644 }
645
646 #[cfg(not(target_os = "macos"))]
647 fn graph_prefill_preferred(&self) -> bool {
648 let graph_on = std::env::var("CMF_GPU_WGPU_GRAPH")
656 .map(|v| v != "0")
657 .unwrap_or_else(|_| {
658 crate::gpu::wgpu_graph_default()
662 });
663 if !graph_on || !crate::gpu::enabled_here() {
664 return false;
665 }
666 self.weights
667 .layers
668 .iter()
669 .any(|lw| matches!(&lw.attn, AttnKind::LinearGdn(_)))
670 }
671
672 #[cfg(target_os = "macos")]
673 fn q1_graph_gpu(
674 &mut self,
675 start: usize,
676 upto: Option<usize>,
677 position: usize,
678 h: &mut [f32],
679 ) -> usize {
680 use crate::gpu::{AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GraphDims, TokenGraph};
681 if self.attn_softcap > 0.0 || !crate::gpu::enabled_here()
683 || !crate::gpu::q1_force()
684 || std::env::var("CMF_GPU_BLOCK")
685 .map(|v| v == "0")
686 .unwrap_or(false)
687 {
688 return start;
689 }
690 if self.swa.is_some()
695 || self.global_attn.is_some()
696 || self.attention_heads_per_layer.is_some()
697 || self.attn_v_norm
698 || (self.attn_scale - 1.0 / (self.head_dim as f32).sqrt()).abs() > 1e-9
699 || self.weights.layers.iter().any(|lw| {
700 lw.attn_out_norm.is_some()
701 || lw.ffn_out_norm.is_some()
702 || lw.layer_scale.is_some()
703 || matches!(&lw.ffn, FfnKind::Dense(d) if d.act != Act::Silu)
704 })
705 {
706 return start;
707 }
708 let limit = upto
711 .map(|u| u + 1)
712 .unwrap_or(self.num_layers)
713 .min(self.num_layers);
714
715 enum Item<'a> {
716 Gdn {
717 run: Vec<GdnGpuLayer<'a>>,
718 first: usize,
719 },
720 Attn {
721 l: AttnGpuLayer<'a>,
722 li: usize,
723 q_norm: Option<&'a [f32]>,
724 k_norm: Option<&'a [f32]>,
725 output_gate: bool,
726 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
727 full_gpu: bool,
730 },
731 }
732
733 let attend_mode = std::env::var("CMF_GPU_ATTEND").unwrap_or_else(|_| "auto".into());
735 let dev_attend = attend_mode != "0"
736 && attend_mode != "off"
737 && (self.head_dim <= 128 || attend_mode == "force" || attend_mode == "256")
741 && self.head_dim % 4 == 0
742 && self.head_dim <= 256
743 && self.rotary_dim >= 2
744 && self.rotary_dim <= self.head_dim
745 && (self.rotary_dim / 2) % 32 == 0
746 && self.num_kv_heads > 0
747 && self.num_heads % self.num_kv_heads == 0;
748
749 let mut plan: Vec<Item> = Vec::new();
750 let mut model_ref: Option<std::sync::Arc<cortiq_core::CmfModel>> = None;
751 let mut scan = start;
752 while scan < limit {
753 let lw = &self.weights.layers[self.phys_layer(scan)];
754 let FfnKind::Dense(d) = &lw.ffn else { break };
755 let (Some(g), Some(u), Some(dn)) = (
756 d.gate_proj.q1_parts(),
757 d.up_proj.q1_parts(),
758 d.down_proj.q1_parts(),
759 ) else {
760 break;
761 };
762 match &lw.attn {
763 AttnKind::LinearGdn(w) if self.gdn_cfg.is_some() => {
764 let parts = (
765 w.in_proj_qkv.q1_parts(),
766 w.in_proj_z.q1_parts(),
767 w.in_proj_a.f32_parts(),
768 w.in_proj_b.f32_parts(),
769 w.out_proj.q1_parts(),
770 );
771 let (Some(qkv), Some(z), Some(a), Some(b), Some(out)) = parts else {
772 break;
773 };
774 if let QTensor::Mapped { model, .. } = &w.in_proj_qkv {
775 model_ref.get_or_insert_with(|| model.clone());
776 }
777 let gl = GdnGpuLayer {
778 attn_norm: &lw.input_norm,
779 post_norm: &lw.post_norm,
780 qkv,
781 z,
782 a,
783 b,
784 out,
785 gate: g,
786 up: u,
787 down: dn,
788 conv1d: &w.conv1d,
789 a_log: &w.a_log,
790 dt_bias: &w.dt_bias,
791 gnorm: &w.norm,
792 };
793 match plan.last_mut() {
794 Some(Item::Gdn { run, .. }) => run.push(gl),
795 _ => plan.push(Item::Gdn {
796 run: vec![gl],
797 first: scan,
798 }),
799 }
800 }
801 AttnKind::Full {
802 wq,
803 wk,
804 wv,
805 wo,
806 q_norm,
807 k_norm,
808 output_gate,
809 softplus_gate: None,
810 bias,
811 } if !self.kv_cache.layers[scan].o1_sealed() => {
812 let parts = (wq.q1_parts(), wk.q1_parts(), wv.q1_parts(), wo.q1_parts());
813 let (Some(pq), Some(pk), Some(pv), Some(po)) = parts else {
814 break;
815 };
816 if let QTensor::Mapped { model, .. } = wq {
817 model_ref.get_or_insert_with(|| model.clone());
818 }
819 let cache = &self.kv_cache.layers[scan];
820 let full_gpu = dev_attend
821 && cache.mode == crate::kv_cache::KvMode::F32
822 && cache.o1.is_none()
823 && bias.is_none()
824 && pq.1 == self.num_heads * self.head_dim * (1 + *output_gate as usize)
825 && pk.1 == self.num_kv_heads * self.head_dim
826 && pv.1 == self.num_kv_heads * self.head_dim
827 && po.2 == self.num_heads * self.head_dim;
828 plan.push(Item::Attn {
829 l: AttnGpuLayer {
830 attn_norm: &lw.input_norm,
831 post_norm: &lw.post_norm,
832 wq: pq,
833 wk: pk,
834 wv: pv,
835 wo: po,
836 gate: g,
837 up: u,
838 down: dn,
839 },
840 li: scan,
841 q_norm: q_norm.as_deref(),
842 k_norm: k_norm.as_deref(),
843 output_gate: *output_gate,
844 bias: bias
845 .as_ref()
846 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
847 full_gpu,
848 });
849 }
850 _ => break,
851 }
852 scan += 1;
853 }
854 let Some(model) = model_ref else { return start };
855 if plan.is_empty() {
856 return start;
857 }
858 let dims = GraphDims {
859 hidden: self.hidden_size,
860 eps: self.rms_eps as f32,
861 gemma: self.norm_style == cortiq_core::NormStyle::Gemma,
862 };
863 let Some(mut graph) = TokenGraph::new(&model, dims, h) else {
864 return start;
865 };
866 let gcfg = self.gdn_cfg.map(|cfg| GdnGpuCfg {
867 nv: cfg.num_v_heads,
868 nk: cfg.num_k_heads,
869 dk: cfg.key_head_dim,
870 dv: cfg.value_head_dim,
871 kk: cfg.conv_kernel,
872 hidden: self.hidden_size,
873 inter: self.intermediate_size,
874 c_dim: cfg.conv_dim(),
875 eps: cfg.rms_eps as f32,
876 gemma: self.norm_style == cortiq_core::NormStyle::Gemma,
877 });
878 let mut valid = 0usize;
882 let mut end = start;
883 for item in &plan {
884 let ok = match item {
885 Item::Gdn { run, .. } => gcfg
886 .as_ref()
887 .map(|gc| run.iter().all(|l| graph.gdn_ok(l, gc)))
888 .unwrap_or(false),
889 Item::Attn { l, .. } => graph.attn_ok(l),
890 };
891 if !ok {
892 break;
893 }
894 valid += 1;
895 end += match item {
896 Item::Gdn { run, .. } => run.len(),
897 Item::Attn { .. } => 1,
898 };
899 }
900 plan.truncate(valid);
901 if plan.is_empty() {
902 return start;
903 }
904
905 let inv_freq = self.inv_freq.clone();
906 let pool = self.pool.clone();
907 let (nh, nkv, hd, hs, rd, eps) = (
908 self.num_heads,
909 self.num_kv_heads,
910 self.head_dim,
911 self.hidden_size,
912 self.rotary_dim,
913 self.rms_eps,
914 );
915 let norm_style = self.norm_style;
916 let gemma = norm_style == cortiq_core::NormStyle::Gemma;
917 let want = self.gdn_cfg.map(|c| c.state_len()).unwrap_or(0);
918 let kv_id = self.graph_kv_id;
919 let mut pending: Vec<(usize, usize)> = Vec::new();
922 let mut dev_attn: Vec<usize> = Vec::new();
925 for item in &plan {
926 if self.loop_final_norm {
928 let item_start = match item {
929 Item::Gdn { first, .. } => *first,
930 Item::Attn { li, .. } => *li,
931 };
932 if item_start > start && self.is_loop_end(item_start - 1) {
933 graph.encode_loop_norm(&self.weights.final_norm);
934 }
935 }
936 match item {
937 Item::Gdn { run, first } => {
938 for l in &mut self.kv_cache.layers[*first..*first + run.len()] {
939 if l.linear_state.len() != want {
940 l.linear_state = vec![0f32; want];
941 }
942 }
943 let ro: Vec<&[f32]> = self.kv_cache.layers[*first..*first + run.len()]
944 .iter()
945 .map(|l| l.linear_state.as_slice())
946 .collect();
947 if !graph.encode_gdn_run(run, &ro, gcfg.as_ref().unwrap()) {
948 tracing::error!("q1 graph: GDN run refused after validation");
950 return start;
951 }
952 graph.commit();
955 pending.push((*first, run.len()));
956 }
957 Item::Attn {
958 l,
959 li,
960 q_norm,
961 k_norm,
962 output_gate,
963 bias,
964 full_gpu,
965 } => {
966 if *full_gpu {
968 let cache = &self.kv_cache.layers[*li];
969 let cpu_k: Vec<&[f32]> = (0..nkv).map(|g| cache.head_keys(g)).collect();
970 let cpu_v: Vec<&[f32]> = (0..nkv).map(|g| cache.head_values(g)).collect();
971 let cpu_stored = cpu_k[0].len() / hd;
972 let p = crate::gpu::AttnDeviceParams {
973 kv_id,
974 layer: *li,
975 nh,
976 nkv,
977 hd,
978 rd,
979 position,
980 eps: eps as f32,
981 gemma,
982 output_gate: *output_gate,
983 q_norm: *q_norm,
984 k_norm: *k_norm,
985 inv_freq: &inv_freq,
986 cpu_k,
987 cpu_v,
988 cpu_stored,
989 };
990 if graph.attn_device_ok(l, &p) && graph.encode_attn_device(l, &p) {
991 graph.commit();
992 dev_attn.push(*li);
993 continue;
994 }
995 }
997 graph.encode_attn_prefix(l);
998 graph.sync();
999 if !pending.is_empty() {
1000 let idxs: Vec<usize> =
1001 pending.drain(..).flat_map(|(f, n)| f..f + n).collect();
1002 let mut outs: Vec<&mut [f32]> = self
1003 .kv_cache
1004 .layers
1005 .iter_mut()
1006 .enumerate()
1007 .filter(|(i, _)| idxs.binary_search(i).is_ok())
1008 .map(|(_, s)| s.linear_state.as_mut_slice())
1009 .collect();
1010 graph.read_states(&mut outs);
1011 }
1012 let mut q_raw = attention::take_buf(l.wq.1);
1013 let mut k = attention::take_buf(l.wk.1);
1014 let mut v = attention::take_buf(l.wv.1);
1015 graph.read_qkv(&mut q_raw, &mut k, &mut v);
1016 let cfg = QwenAttnCfg {
1017 num_heads: nh,
1018 num_kv_heads: nkv,
1019 head_dim: hd,
1020 hidden_size: hs,
1021 position,
1022 inv_freq: &inv_freq,
1023 rotary_dim: rd,
1024 scale: self.attn_scale,
1025 softcap: self.attn_softcap,
1026 window: None,
1027 v_norm: false,
1028 q_norm: *q_norm,
1029 k_norm: *k_norm,
1030 output_gate: *output_gate,
1031 softplus_gate: None,
1032 rope_scale: 1.0,
1033 bias: *bias,
1034 rms_eps: eps,
1035 norm_style,
1036 pool: pool.as_deref(),
1037 };
1038 let mut ao = attention::qwen_attention_core(
1039 q_raw,
1040 k,
1041 v,
1042 &mut self.kv_cache.layers[*li],
1043 &cfg,
1044 );
1045 graph.encode_attn_suffix(l, &ao);
1046 graph.commit();
1049 attention::recycle_buf(&mut ao);
1050 }
1051 }
1052 }
1053 let mut lm_rows = None;
1058 if self.graph_want_logits
1059 && upto.is_none()
1060 && end == self.num_layers
1061 && std::env::var("CMF_GPU_LMHEAD")
1062 .map(|v| v != "0")
1063 .unwrap_or(true)
1064 {
1065 if let Some(lm) = self.weights.lm_head.q1_parts() {
1066 if graph.lm_head_ok(lm) {
1067 graph.encode_lm_head(&self.weights.final_norm, lm);
1068 lm_rows = Some(lm.1);
1069 }
1070 }
1071 }
1072 graph.sync();
1073 if !pending.is_empty() {
1074 let idxs: Vec<usize> = pending.drain(..).flat_map(|(f, n)| f..f + n).collect();
1075 let mut outs: Vec<&mut [f32]> = self
1076 .kv_cache
1077 .layers
1078 .iter_mut()
1079 .enumerate()
1080 .filter(|(i, _)| idxs.binary_search(i).is_ok())
1081 .map(|(_, s)| s.linear_state.as_mut_slice())
1082 .collect();
1083 graph.read_states(&mut outs);
1084 }
1085 if let Some(rows) = lm_rows {
1086 let mut lg = attention::take_buf(rows.min(self.vocab_size));
1087 graph.read_logits(&mut lg);
1088 lg.resize(self.vocab_size, 0.0);
1089 if let Some(c) = self.final_softcap {
1090 for l in lg.iter_mut() {
1091 *l = c * (*l / c).tanh();
1092 }
1093 }
1094 self.graph_logits = Some(lg);
1095 }
1096 graph.finish(h);
1097 for li in dev_attn {
1101 let mut krow = attention::take_buf(nkv * hd);
1102 let mut vrow = attention::take_buf(nkv * hd);
1103 if crate::gpu::kv_mirror_read_last(kv_id, li, nkv, hd, &mut krow, &mut vrow) {
1104 let cache = &mut self.kv_cache.layers[li];
1105 cache.append(&krow, &vrow, &[]);
1106 let n = cache.seq_len;
1107 let mut imp = attention::take_buf(n);
1108 crate::gpu::kv_mirror_take_imp(kv_id, li, &mut imp);
1109 cache.accumulate_imp(&imp);
1110 attention::recycle_buf(&mut imp);
1111 }
1112 attention::recycle_buf(&mut krow);
1113 attention::recycle_buf(&mut vrow);
1114 }
1115 end
1116 }
1117
1118 pub fn new(
1119 tokenizer: Tokenizer,
1120 weights: PipelineWeights,
1121 hidden_size: usize,
1122 intermediate_size: usize,
1123 num_heads: usize,
1124 num_kv_heads: usize,
1125 head_dim: usize,
1126 num_layers: usize,
1127 physical_layers: usize,
1128 loop_final_norm: bool,
1129 vocab_size: usize,
1130 rms_eps: f64,
1131 rope_base: f32,
1132 norm_style: NormStyle,
1133 max_seq_len: usize,
1134 sampler_config: SamplerConfig,
1135 ) -> Self {
1136 let rng = match sampler_config.seed {
1137 Some(s) => SplitMix64::new(s),
1138 None => SplitMix64::from_entropy(),
1139 };
1140 let inv_freq = std::sync::Arc::new(attention::rope_inv_freq(head_dim, rope_base));
1141 let pool = Pool::from_env();
1142 if let Some(p) = &pool {
1143 tracing::info!("worker pool: {} threads", p.n_workers());
1144 }
1145 Self {
1146 tokenizer: std::sync::Arc::new(tokenizer),
1147 kv_cache: KvCache::new(num_layers, num_kv_heads, head_dim, max_seq_len),
1148 sampler_config,
1149 weights,
1150 hidden_size,
1151 intermediate_size,
1152 num_heads,
1153 num_kv_heads,
1154 head_dim,
1155 num_layers,
1156 physical_layers,
1157 loop_final_norm,
1158 vocab_size,
1159 rms_eps,
1160 rope_base,
1161 norm_style,
1162 rotary_dim: head_dim,
1163 attention_heads_per_layer: None,
1164 vmf_cfg: None,
1165 gdn_cfg: None,
1166 kda_cfg: None,
1167 g3n: None,
1168 dsv4: None,
1169 logit_multiplier: None,
1170 cancel: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
1171 kv_history: Vec::new(),
1172 short_conv_cfg: None,
1173 mtp: None,
1174 speculative: std::env::var("CMF_MTP").map(|v| v != "0").unwrap_or(true),
1175 rng,
1176 sampler_scratch: SamplerScratch::default(),
1177 inv_freq,
1178 ws: ForwardScratch::new(hidden_size),
1179 pool,
1180 model: None,
1181 dyn_force_f32: false,
1182 dyn_skill_layers: Vec::new(),
1183 dyn_active: None,
1184 dyn_blend_loaded: false,
1185 dyn_phi_layer: None,
1186 dyn_phi_ema: Vec::new(),
1187 dyn_phi_seen: 0,
1188 dyn_router: None,
1189 o1_cfg: None,
1190 o1_epoch: 0,
1191 o1_flags: Vec::new(),
1192 trace: false,
1193 calib_temp: 1.0,
1194 confidence_on: true,
1195 embed_multiplier: 1.0,
1196 attn_scale: 1.0 / (head_dim as f32).sqrt(),
1197 swa: None,
1198 sliding_layers: None,
1199 inv_freq_local: None,
1200 rotary_dim_local: None,
1201 rope_scale: 1.0,
1202 rope_scale_local: 1.0,
1203 global_attn: None,
1204 inv_freq_global: None,
1205 attn_v_norm: false,
1206 final_softcap: None,
1207 attn_softcap: 0.0,
1208 graph_want_logits: false,
1209 graph_logits: None,
1210 graph_kv_id: {
1211 static NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
1212 NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
1213 },
1214 }
1215 }
1216
1217 pub fn set_o1(&mut self, cfg: Option<crate::nystrom::O1Cfg>) {
1224 self.o1_flags = match &cfg {
1225 Some(c) => {
1226 let mut flags = c.layer_flags(self.num_layers);
1227 for (li, f) in flags.iter_mut().enumerate() {
1228 if *f
1229 && !matches!(
1230 self.weights.layers[self.phys_layer(li)].attn,
1231 AttnKind::Full { .. }
1232 )
1233 {
1234 *f = false;
1235 }
1236 }
1237 flags
1238 }
1239 None => Vec::new(),
1240 };
1241 if let Some(c) = &cfg {
1242 let n = self.o1_flags.iter().filter(|&&f| f).count();
1243 tracing::info!(
1244 "o1 nystrom attention: {n}/{} layer(s), m={} w={} sink={} rect={:?}",
1245 self.num_layers,
1246 c.m,
1247 c.w,
1248 c.sink,
1249 c.rect
1250 );
1251 }
1252 self.o1_cfg = cfg;
1253 }
1254
1255 pub fn o1_active(&self) -> bool {
1257 self.o1_cfg.is_some() && self.o1_flags.iter().any(|&f| f)
1258 }
1259
1260 fn o1_begin(&mut self) {
1262 if let Some(c) = &self.o1_cfg {
1263 let (m, w, sink, rect) = (c.m, c.w, c.sink, c.rect);
1264 for (li, &f) in self.o1_flags.iter().enumerate() {
1265 if f {
1266 self.kv_cache.layers[li].o1_begin(m, w, sink, rect);
1267 }
1268 }
1269 }
1270 }
1271
1272 fn o1_seal(&mut self) {
1275 self.o1_epoch = self.o1_epoch.wrapping_add(1);
1276 if self.o1_cfg.is_none() {
1277 return;
1278 }
1279 for li in 0..self.num_layers {
1280 if self.o1_flags.get(li).copied().unwrap_or(false) {
1281 self.kv_cache.layers[li].o1_seal(self.num_heads);
1282 }
1283 }
1284 }
1285
1286 pub fn set_trace(&mut self, on: bool) {
1288 self.trace = on;
1289 }
1290
1291 pub fn set_sampler_config(&mut self, config: SamplerConfig) {
1294 self.rng = match config.seed {
1295 Some(seed) => SplitMix64::new(seed),
1296 None => SplitMix64::from_entropy(),
1297 };
1298 self.sampler_config = config;
1299 }
1300
1301 pub fn set_confidence(&mut self, on: bool) {
1306 self.confidence_on = on;
1307 }
1308
1309 pub fn set_calib_temp(&mut self, t: f32) {
1312 self.calib_temp = if t > 1e-3 { t } else { 1.0 };
1313 }
1314
1315 pub fn calib_temp(&self) -> f32 {
1317 self.calib_temp
1318 }
1319
1320 pub fn set_rotary(&mut self, rotary_dim: usize, base: f32) {
1323 self.rotary_dim = rotary_dim.min(self.head_dim);
1324 self.inv_freq = std::sync::Arc::new(attention::rope_inv_freq(self.rotary_dim, base));
1325 }
1326
1327 fn attn_cfg(&self, position: usize) -> QwenAttnCfg<'_> {
1328 QwenAttnCfg {
1329 num_heads: self.num_heads,
1330 num_kv_heads: self.num_kv_heads,
1331 head_dim: self.head_dim,
1332 hidden_size: self.hidden_size,
1333 position,
1334 inv_freq: &self.inv_freq,
1335 rotary_dim: self.rotary_dim,
1336 scale: self.attn_scale,
1337 softcap: self.attn_softcap,
1338 window: None,
1339 v_norm: false,
1340 q_norm: None,
1341 k_norm: None,
1342 output_gate: false,
1343 softplus_gate: None,
1344 rope_scale: self.rope_scale,
1345 bias: None,
1346 rms_eps: self.rms_eps,
1347 norm_style: self.norm_style,
1348 pool: self.pool.as_deref(),
1349 }
1350 }
1351
1352 pub fn generate(
1354 &mut self,
1355 prompt: &str,
1356 max_tokens: usize,
1357 task_mask: Option<&TaskMask>,
1358 on_token: Option<TokenCallback>,
1359 ) -> Result<GenerateResult, String> {
1360 let input_ids = self.tokenizer.with_bos(self.tokenizer.encode(prompt));
1361 self.generate_from_ids(&input_ids, max_tokens, task_mask, on_token)
1362 }
1363
1364 pub fn generate_from_ids(
1372 &mut self,
1373 input_ids: &[u32],
1374 max_tokens: usize,
1375 task_mask: Option<&TaskMask>,
1376 mut on_token: Option<TokenCallback>,
1377 ) -> Result<GenerateResult, String> {
1378 if std::env::var("CMF_TRACE_H").is_ok() {
1379 eprintln!("input_ids: {input_ids:?}");
1380 }
1381 if input_ids.is_empty() {
1382 return Err("empty prompt: nothing to generate from".to_string());
1383 }
1384
1385 let reuse_from = {
1393 let on = !std::env::var("CMF_KV_REUSE").is_ok_and(|v| v == "0");
1394 let h = &self.kv_history;
1395 if on
1396 && task_mask.is_none()
1397 && self.mtp.is_none()
1398 && self.o1_cfg.is_none()
1399 && !h.is_empty()
1400 && h.len() < input_ids.len()
1401 && input_ids[..h.len()] == h[..]
1402 {
1403 h.len()
1404 } else {
1405 0
1406 }
1407 };
1408 if reuse_from == 0 {
1409 self.kv_cache.clear();
1411 self.kv_history.clear();
1412 crate::gpu::graph_kv_reset(self.graph_kv_id);
1413 } else if std::env::var("CMF_PREFILL_PROF").is_ok() {
1414 eprintln!(
1415 "kv-reuse: {} of {} prompt positions already cached",
1416 reuse_from,
1417 input_ids.len()
1418 );
1419 }
1420 crate::gpu::graph_race_begin_generation();
1421 self.o1_begin();
1422
1423 let graph_on = std::env::var("CMF_GPU_WGPU_GRAPH")
1429 .map(|v| v != "0")
1430 .unwrap_or_else(|_| {
1431 crate::gpu::wgpu_graph_default()
1435 });
1436 let spec_active = self.speculative
1437 && self.mtp.is_some()
1438 && task_mask.is_none()
1439 && !self.o1_active()
1440 && !graph_on
1441 && self.sampler_config.temperature < 1e-6;
1442 let mut mtp = if spec_active { self.mtp.take() } else { None };
1445 if let Some(m) = &mut mtp {
1446 m.kv.clear();
1447 }
1448 let mut router = if mtp.is_none() {
1452 self.dyn_router.take()
1453 } else {
1454 None
1455 };
1456 if let Some(r) = &mut router {
1457 r.reset(); self.dyn_phi_seen = 0; let _ = self.set_active_skill(None);
1460 }
1461
1462 let mut all_ids = input_ids.to_vec();
1463 let mut generated = 0usize;
1464 let mut finish_reason = "max_tokens".to_string();
1465 let mut drafted = 0usize;
1466 let mut accepted = 0usize;
1467 let mut confidence: Vec<f32> = Vec::new();
1468 let trace_on = self.trace;
1469 let calib_temp = self.calib_temp;
1470 let mut traces: Vec<TokenTrace> = Vec::new();
1471
1472 let mut hidden = vec![0.0f32; self.hidden_size];
1478 let mut pos = reuse_from;
1479 let fuse_lm = mtp.is_none()
1488 && router.is_none()
1489 && std::env::var("CMF_GPU_LMHEAD").as_deref() != Ok("0");
1490 self.graph_logits = None;
1491 self.graph_want_logits = false;
1492 let dyn_prefill = router.is_some();
1497 let graph_prefill = self.graph_prefill_preferred();
1503 if task_mask.is_none()
1504 && !dyn_prefill
1505 && !graph_prefill
1506 && self.can_prefill_batched()
1507 && self.g3n.is_none()
1508 && input_ids.len() > 2
1509 {
1510 let chunk = prefill_chunk();
1516 let hs = self.hidden_size;
1517 while pos < input_ids.len() && !self.cancel.load(std::sync::atomic::Ordering::Relaxed) {
1518 let end = (pos + chunk).min(input_ids.len());
1519 let hb = self.prefill_batch(&input_ids[pos..end], pos);
1520 if let Some(m) = &mut mtp {
1521 for p in pos..end {
1522 if p + 1 < input_ids.len() {
1523 let _ = self.mtp_step(
1524 m,
1525 &hb[(p - pos) * hs..(p - pos + 1) * hs],
1526 input_ids[p + 1],
1527 p,
1528 );
1529 }
1530 }
1531 }
1532 hidden.copy_from_slice(&hb[(end - pos - 1) * hs..]);
1533 pos = end;
1534 }
1535 }
1536 let pair_off = std::env::var("CMF_PAIR").is_ok_and(|v| v == "0");
1537 if task_mask.is_none()
1538 && !dyn_prefill
1539 && !graph_prefill
1540 && !pair_off
1541 && self.pair_supported()
1542 {
1543 while pos + 1 < input_ids.len()
1544 && !self.cancel.load(std::sync::atomic::Ordering::Relaxed)
1545 {
1546 let e1 = self.embed_single(input_ids[pos]);
1547 let e2 = self.embed_single(input_ids[pos + 1]);
1548 let (h1, h2) = self.forward_pair(&e1, &e2, pos);
1549 self.commit_linear_scratch();
1551 if let Some(m) = &mut mtp {
1552 let _ = self.mtp_step(m, &h1, input_ids[pos + 1], pos);
1553 if pos + 2 < input_ids.len() {
1554 let _ = self.mtp_step(m, &h2, input_ids[pos + 2], pos + 1);
1555 }
1556 }
1557 hidden = h2;
1558 pos += 2;
1559 }
1560 }
1561 let _tpf = std::time::Instant::now();
1570 let batch_k = std::env::var("CMF_BATCH_K")
1571 .ok()
1572 .and_then(|v| v.parse::<usize>().ok())
1573 .unwrap_or(0);
1574 if batch_k > 0
1575 && graph_prefill
1576 && task_mask.is_none()
1577 && !self.o1_active()
1578 && mtp.is_none()
1579 && !dyn_prefill
1580 && pos + 1 < input_ids.len()
1581 {
1582 let hs = self.hidden_size;
1583 let chunk = batch_k;
1584 while pos < input_ids.len() {
1585 let end = (pos + chunk).min(input_ids.len());
1586 let bk = end - pos;
1587 let mut hiddens = vec![0f32; bk * hs];
1588 for (j, &id) in input_ids[pos..end].iter().enumerate() {
1589 hiddens[j * hs..(j + 1) * hs].copy_from_slice(&self.embed_single(id));
1590 }
1591 let positions: Vec<usize> = (pos..end).collect();
1592 let t_chunk = std::time::Instant::now();
1593 let ok_b = self.try_batch_graph_wgpu(&mut hiddens, &positions, bk);
1594 if std::env::var("CMF_GRAPH_PROF").is_ok() {
1595 let ms = t_chunk.elapsed().as_secs_f64() * 1000.0;
1596 eprintln!(
1597 "batch-chunk: k={bk} ok={ok_b} {ms:.1} ms ({:.1} tok/s)",
1598 bk as f64 / (ms / 1000.0)
1599 );
1600 }
1601 {
1602 use std::sync::atomic::{AtomicBool, Ordering};
1603 static SAID: AtomicBool = AtomicBool::new(false);
1604 if !SAID.swap(true, Ordering::Relaxed) {
1605 if ok_b {
1606 tracing::info!("batched prefill: ACTIVE (k={bk})");
1607 } else {
1608 tracing::warn!("batched prefill declined — per-position graph");
1609 }
1610 }
1611 }
1612 if ok_b {
1613 hidden.copy_from_slice(&hiddens[(bk - 1) * hs..]);
1614 pos = end;
1615 } else {
1616 break; }
1618 }
1619 }
1620 while pos < input_ids.len() && !self.cancel.load(std::sync::atomic::Ordering::Relaxed) {
1621 self.graph_want_logits = fuse_lm && pos + 1 == input_ids.len();
1622 hidden = self.forward_layers(&self.embed_single(input_ids[pos]), pos, task_mask);
1623 if let Some(m) = &mut mtp {
1624 if pos + 1 < input_ids.len() {
1625 let _ = self.mtp_step(m, &hidden, input_ids[pos + 1], pos);
1626 }
1627 }
1628 pos += 1;
1629 }
1630 if std::env::var("CMF_PREFILL_PROF").is_ok() {
1631 eprintln!(
1632 "prefill: {} tokens in {:.1} ms (batch_k={batch_k})",
1633 input_ids.len(),
1634 _tpf.elapsed().as_secs_f64() * 1000.0
1635 );
1636 }
1637 if self
1640 .cancel
1641 .swap(false, std::sync::atomic::Ordering::Relaxed)
1642 {
1643 self.kv_history.clear();
1644 if let Some(m) = mtp {
1645 self.mtp = Some(m);
1646 }
1647 return Ok(GenerateResult {
1648 text: String::new(),
1649 token_ids: Vec::new(),
1650 prompt_tokens: input_ids.len(),
1651 tokens_generated: 0,
1652 finish_reason: "cancelled".to_string(),
1653 mtp_drafted: 0,
1654 mtp_accepted: 0,
1655 token_confidence: Vec::new(),
1656 traces: Vec::new(),
1657 });
1658 }
1659
1660 self.o1_seal();
1663
1664 macro_rules! commit {
1666 ($id:expr) => {{
1667 all_ids.push($id);
1668 generated += 1;
1669 if self.tokenizer.is_eos($id) {
1670 finish_reason = "stop".to_string();
1671 false
1672 } else {
1673 let token_text = self.tokenizer.decode_token($id);
1674 let mut go = true;
1675 if let Some(ref mut cb) = on_token {
1676 if !cb(&token_text) {
1677 finish_reason = "cancelled".to_string();
1678 go = false;
1679 }
1680 }
1681 go
1682 }
1683 }};
1684 }
1685
1686 let mut next_pos = input_ids.len();
1688 'decode: while generated < max_tokens {
1689 if self
1690 .cancel
1691 .swap(false, std::sync::atomic::Ordering::Relaxed)
1692 {
1693 finish_reason = "cancelled".to_string();
1694 break 'decode;
1695 }
1696 let mut logits = match self.graph_logits.take() {
1697 Some(lg) => lg,
1698 None => {
1699 inference::rms_norm_into(
1700 &hidden,
1701 &self.weights.final_norm,
1702 self.rms_eps,
1703 self.norm_style,
1704 &mut self.ws.n1,
1705 );
1706 self.lm_head_forward(&self.ws.n1)
1707 }
1708 };
1709 let t_next = sampler::sample_with_scratch(
1710 &logits,
1711 &self.sampler_config,
1712 &all_ids,
1713 &mut self.rng,
1714 &mut self.sampler_scratch,
1715 );
1716 if self.confidence_on {
1717 confidence.push(top1_prob_t(&logits, t_next, calib_temp));
1718 }
1719 attention::recycle_buf(&mut logits);
1720 if trace_on {
1721 let skill = router.as_ref().and_then(|r| r.active_id());
1725 traces.push(TokenTrace {
1726 t: generated,
1727 token_id: t_next,
1728 confidence: confidence.last().copied().unwrap_or(0.0),
1729 active_skill: skill,
1730 recon: None,
1731 switched: false,
1732 });
1733 }
1734 if !commit!(t_next) {
1735 break 'decode;
1736 }
1737 if generated >= max_tokens {
1738 break 'decode;
1739 }
1740
1741 if self.kv_cache.needs_eviction() {
1742 let keep = (self.kv_cache.max_seq_len / 2).max(1);
1743 self.kv_cache.evict(keep);
1744 }
1745
1746 match &mut mtp {
1747 Some(m) if generated + 1 < max_tokens => {
1749 let draft = self.mtp_step(m, &hidden, t_next, next_pos - 1);
1750 drafted += 1;
1751 let emb1 = self.embed_single(t_next);
1752 let emb2 = self.embed_single(draft);
1753 let (h1, h2) = self.forward_pair(&emb1, &emb2, next_pos);
1754
1755 inference::rms_norm_into(
1756 &h1,
1757 &self.weights.final_norm,
1758 self.rms_eps,
1759 self.norm_style,
1760 &mut self.ws.n1,
1761 );
1762 let mut logits1 = self.lm_head_forward(&self.ws.n1);
1763 let t_after = sampler::sample_with_scratch(
1764 &logits1,
1765 &self.sampler_config,
1766 &all_ids,
1767 &mut self.rng,
1768 &mut self.sampler_scratch,
1769 );
1770 if self.confidence_on {
1771 confidence.push(top1_prob_t(&logits1, t_after, calib_temp));
1772 }
1773 attention::recycle_buf(&mut logits1);
1774 if trace_on {
1775 traces.push(TokenTrace {
1778 t: generated,
1779 token_id: t_after,
1780 confidence: confidence.last().copied().unwrap_or(0.0),
1781 active_skill: None,
1782 recon: None,
1783 switched: false,
1784 });
1785 }
1786 let stop = !commit!(t_after);
1787
1788 if t_after == draft {
1789 accepted += 1;
1790 self.commit_linear_scratch();
1791 let _ = self.mtp_step(m, &h1, t_after, next_pos);
1792 hidden = h2;
1793 next_pos += 2;
1794 } else {
1795 for layer in &mut self.kv_cache.layers {
1797 layer.truncate_last(1);
1798 }
1799 if !stop {
1800 let _ = self.mtp_step(m, &h1, t_after, next_pos);
1801 hidden = self.forward_layers(
1802 &self.embed_single(t_after),
1803 next_pos + 1,
1804 None,
1805 );
1806 }
1807 next_pos += 2;
1808 }
1809 if stop {
1810 break 'decode;
1811 }
1812 }
1813 _ => {
1815 self.graph_want_logits = fuse_lm;
1816 let mut t_fwd = t_next;
1822 let pure_greedy = self.sampler_config.temperature < 1e-6
1823 && self.sampler_config.repetition_penalty == 1.0
1824 && self.sampler_config.suppress_tokens.is_empty();
1825 let burst_k = std::env::var("CMF_MULTISTEP")
1830 .ok()
1831 .and_then(|v| v.parse::<usize>().ok())
1832 .unwrap_or(0);
1833 if pure_greedy
1834 && burst_k >= 1
1835 && fuse_lm
1836 && task_mask.is_none()
1837 && router.is_none()
1838 && !trace_on
1839 && !self.confidence_on
1840 {
1841 let mut stopped = false;
1842 loop {
1843 let room = max_tokens.saturating_sub(generated);
1844 if room <= 2 {
1845 break;
1846 }
1847 let k = burst_k.min(room - 1);
1848 if k < 1 {
1849 break;
1850 }
1851 let Some(ids) = self.try_multi_burst(t_fwd, next_pos, k) else {
1852 break;
1853 };
1854 next_pos += k;
1855 for &id in &ids {
1856 if !commit!(id) {
1857 stopped = true;
1858 break;
1859 }
1860 }
1861 if stopped {
1862 break;
1863 }
1864 t_fwd = *ids.last().unwrap();
1865 }
1866 if stopped {
1867 break 'decode;
1868 }
1869 }
1870 hidden = self.forward_layers(&self.embed_single(t_fwd), next_pos, task_mask);
1871 next_pos += 1;
1872 if let Some(r) = &mut router {
1875 let phi = self.dyn_phi_ema.clone();
1876 let decision = r.step(&phi, generated);
1877 if let Some(new_active) = decision {
1878 let _ = self.set_active_skill(new_active);
1879 }
1880 if trace_on {
1883 if let Some(last) = traces.last_mut() {
1884 let e = r.last_best_e();
1885 last.recon = e.is_finite().then_some(e);
1886 last.switched = decision.is_some();
1887 }
1888 }
1889 }
1890 }
1891 }
1892 }
1893
1894 self.graph_want_logits = false;
1895 self.graph_logits = None;
1896 if router.is_some() {
1898 let _ = self.set_active_skill(None);
1899 }
1900 self.dyn_router = router.or(self.dyn_router.take());
1901 self.mtp = mtp.or(self.mtp.take());
1902
1903 let output_ids = &all_ids[input_ids.len()..];
1904 let forwarded = input_ids.len() + output_ids.len().saturating_sub(1);
1908 self.kv_history = all_ids[..forwarded.min(all_ids.len())].to_vec();
1909 confidence.truncate(output_ids.len()); traces.truncate(output_ids.len());
1911 Ok(GenerateResult {
1912 text: self.tokenizer.decode(output_ids),
1913 token_ids: output_ids.to_vec(),
1914 prompt_tokens: input_ids.len(),
1915 tokens_generated: generated,
1916 finish_reason,
1917 mtp_drafted: drafted,
1918 mtp_accepted: accepted,
1919 token_confidence: confidence,
1920 traces,
1921 })
1922 }
1923
1924 fn mtp_step(
1928 &mut self,
1929 m: &mut MtpModule,
1930 hidden: &[f32],
1931 next_token: u32,
1932 position: usize,
1933 ) -> u32 {
1934 let e = self.embed_single(next_token);
1938 let mut cat = vec![0.0f32; 2 * self.hidden_size];
1939 let (cat_e, cat_h) = cat.split_at_mut(self.hidden_size);
1940 inference::rms_norm_into(&e, &m.enorm, self.rms_eps, self.norm_style, cat_e);
1941 inference::rms_norm_into(hidden, &m.hnorm, self.rms_eps, self.norm_style, cat_h);
1942 let mut x = vec![0.0f32; self.hidden_size];
1943 m.eh_proj.matvec(&cat, &mut x, self.pool.as_deref());
1944
1945 let lw = &m.layer;
1947 inference::rms_norm_into(
1948 &x,
1949 &lw.input_norm,
1950 self.rms_eps,
1951 self.norm_style,
1952 &mut self.ws.n1,
1953 );
1954 let attn = match &lw.attn {
1955 AttnKind::Mla(_) => unreachable!("MLA has no MTP/pair path"),
1957 AttnKind::Kda(_) => unreachable!("KDA has no MTP/pair path"),
1958 AttnKind::Full {
1959 wq,
1960 wk,
1961 wv,
1962 wo,
1963 q_norm,
1964 k_norm,
1965 output_gate,
1966 softplus_gate,
1967 bias,
1968 } => {
1969 let mut cfg = self.attn_cfg(position);
1970 cfg.q_norm = q_norm.as_deref();
1971 cfg.k_norm = k_norm.as_deref();
1972 cfg.output_gate = *output_gate;
1973 cfg.softplus_gate = softplus_gate
1974 .as_ref()
1975 .map(|(gate, per_head)| (gate, *per_head));
1976 cfg.bias = bias
1977 .as_ref()
1978 .map(|(q, k, v)| (q.as_slice(), k.as_slice(), v.as_slice()));
1979 attention::qwen_attention(&self.ws.n1, wq, wk, wv, wo, &mut m.kv, &cfg)
1980 }
1981 AttnKind::Linear(_) | AttnKind::LinearGdn(_) | AttnKind::ShortConv(_) => {
1982 unreachable!("MTP block is full attention")
1983 }
1984 };
1985 for (i, &a) in attn.iter().enumerate() {
1986 x[i] += a;
1987 }
1988 inference::rms_norm_into(
1989 &x,
1990 &lw.post_norm,
1991 self.rms_eps,
1992 self.norm_style,
1993 &mut self.ws.p1,
1994 );
1995 let ffn = ffn_forward(&lw.ffn, &self.ws.p1, self.pool.as_deref(), None);
1996 for (i, &f) in ffn.iter().enumerate() {
1997 x[i] += f;
1998 }
1999
2000 inference::rms_norm_into(
2001 &x,
2002 &m.final_norm,
2003 self.rms_eps,
2004 self.norm_style,
2005 &mut self.ws.n1,
2006 );
2007 let mut lg = self.lm_head_forward(&self.ws.n1);
2008 let draft = sampler::argmax(&lg);
2009 attention::recycle_buf(&mut lg);
2010 draft
2011 }
2012
2013 pub fn measure_pair_fusion(&mut self, iters: usize) -> (f64, f64) {
2017 let emb1 = self.embed_single(1);
2018 let emb2 = self.embed_single(2);
2019 let pos = self.kv_cache.seq_len();
2020
2021 let t0 = std::time::Instant::now();
2022 for _ in 0..iters {
2023 let _ = self.forward_layers(&emb1, pos, None);
2024 let _ = self.forward_layers(&emb2, pos + 1, None);
2025 for l in &mut self.kv_cache.layers {
2026 l.truncate_last(2);
2027 }
2028 }
2029 let singles_ms = t0.elapsed().as_secs_f64() * 1000.0 / iters as f64;
2030
2031 let t1 = std::time::Instant::now();
2032 for _ in 0..iters {
2033 let _ = self.forward_pair(&emb1, &emb2, pos);
2034 for l in &mut self.kv_cache.layers {
2035 l.truncate_last(2);
2036 }
2037 }
2038 let pair_ms = t1.elapsed().as_secs_f64() * 1000.0 / iters as f64;
2039 (singles_ms, pair_ms)
2040 }
2041
2042 fn pair_supported(&self) -> bool {
2050 !self.weights.layers.is_empty()
2057 && self.g3n.is_none()
2058 && !self
2059 .weights
2060 .layers
2061 .iter()
2062 .any(|lw| matches!(&lw.attn, AttnKind::Mla(_) | AttnKind::Kda(_)))
2063 }
2064
2065 fn forward_pair(
2066 &mut self,
2067 emb1: &[f32],
2068 emb2: &[f32],
2069 position: usize,
2070 ) -> (Vec<f32>, Vec<f32>) {
2071 let mut h1 = emb1.to_vec();
2072 let mut h2 = emb2.to_vec();
2073 let (_nkv, _hd, hs, _rd, eps) = (
2074 self.num_kv_heads,
2075 self.head_dim,
2076 self.hidden_size,
2077 self.rotary_dim,
2078 self.rms_eps,
2079 );
2080 let pool = self.pool.clone();
2081
2082 for li in 0..self.num_layers {
2083 let lw = &self.weights.layers[self.phys_layer(li)];
2084 inference::rms_norm_into(
2087 &h1,
2088 &lw.input_norm,
2089 self.rms_eps,
2090 self.norm_style,
2091 &mut self.ws.n1,
2092 );
2093 inference::rms_norm_into(
2094 &h2,
2095 &lw.input_norm,
2096 self.rms_eps,
2097 self.norm_style,
2098 &mut self.ws.n2,
2099 );
2100
2101 let (a1, a2) = match &lw.attn {
2102 AttnKind::Mla(_) => unreachable!("MLA has no MTP/pair path"),
2103 AttnKind::Kda(_) => unreachable!("KDA has no MTP/pair path"),
2104 AttnKind::Kda(_) => unreachable!("KDA has no MTP/pair path"),
2105 AttnKind::Linear(w) => {
2106 let cfg = self.vmf_cfg.expect("linear layer without vmf_cfg");
2107 let layer = &mut self.kv_cache.layers[li];
2108 let (state, scratch) = (&mut layer.linear_state, &mut layer.linear_scratch);
2109 vmf_phase_pair(
2110 &self.ws.n1,
2111 &self.ws.n2,
2112 w,
2113 &cfg,
2114 state,
2115 scratch,
2116 self.pool.as_deref(),
2117 )
2118 }
2119 AttnKind::LinearGdn(w) => {
2120 let cfg = self.gdn_cfg.expect("gdn layer without gdn_cfg");
2121 let layer = &mut self.kv_cache.layers[li];
2122 let (state, scratch) = (&mut layer.linear_state, &mut layer.linear_scratch);
2123 gdn_pair(
2124 &self.ws.n1,
2125 &self.ws.n2,
2126 w,
2127 &cfg,
2128 state,
2129 scratch,
2130 self.pool.as_deref(),
2131 )
2132 }
2133 AttnKind::ShortConv(w) => {
2134 let cfg = self
2135 .short_conv_cfg
2136 .expect("short-conv layer without short_conv_cfg");
2137 let layer = &mut self.kv_cache.layers[li];
2138 let (state, scratch) = (&mut layer.linear_state, &mut layer.linear_scratch);
2139 short_conv_pair(
2140 &self.ws.n1,
2141 &self.ws.n2,
2142 w,
2143 &cfg,
2144 state,
2145 scratch,
2146 self.pool.as_deref(),
2147 )
2148 }
2149 AttnKind::Full {
2150 wq,
2151 wk,
2152 wv,
2153 wo,
2154 q_norm,
2155 k_norm,
2156 output_gate,
2157 softplus_gate,
2158 bias,
2159 } => {
2160 let inv_freq_l = self.layer_inv_freq(li);
2161 let (nkv_l, hd_l, rd_l) = self.layer_geom(li);
2162 let cfg = QwenAttnCfg {
2163 num_heads: self.layer_num_heads(li),
2164 num_kv_heads: nkv_l,
2165 head_dim: hd_l,
2166 hidden_size: hs,
2167 position,
2168 inv_freq: &inv_freq_l,
2169 rotary_dim: rd_l,
2170 scale: self.attn_scale,
2171 softcap: self.attn_softcap,
2172 window: self.layer_window(li),
2173 v_norm: self.attn_v_norm,
2174 q_norm: q_norm.as_deref(),
2175 k_norm: k_norm.as_deref(),
2176 output_gate: *output_gate,
2177 softplus_gate: softplus_gate
2178 .as_ref()
2179 .map(|(gate, per_head)| (gate, *per_head)),
2180 rope_scale: self.layer_rope_scale(li),
2181 bias: bias
2182 .as_ref()
2183 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
2184 rms_eps: eps,
2185 norm_style: self.norm_style,
2186 pool: pool.as_deref(),
2187 };
2188 attention::qwen_attention_pair(
2189 &self.ws.n1,
2190 &self.ws.n2,
2191 wq,
2192 wk,
2193 wv,
2194 wo,
2195 &mut self.kv_cache.layers[li],
2196 &cfg,
2197 )
2198 }
2199 };
2200 let (a1, a2) = match &self.weights.layers[self.phys_layer(li)].attn_out_norm {
2201 Some(w) => (
2202 inference::rms_norm(&a1, w, self.rms_eps, self.norm_style),
2203 inference::rms_norm(&a2, w, self.rms_eps, self.norm_style),
2204 ),
2205 None => (a1, a2),
2206 };
2207 for i in 0..self.hidden_size {
2208 h1[i] += a1[i];
2209 h2[i] += a2[i];
2210 }
2211 let (mut a1, mut a2) = (a1, a2);
2212 attention::recycle_buf(&mut a1);
2213 attention::recycle_buf(&mut a2);
2214
2215 let lw = &self.weights.layers[self.phys_layer(li)];
2216 inference::rms_norm_into(
2217 &h1,
2218 &lw.post_norm,
2219 self.rms_eps,
2220 self.norm_style,
2221 &mut self.ws.p1,
2222 );
2223 inference::rms_norm_into(
2224 &h2,
2225 &lw.post_norm,
2226 self.rms_eps,
2227 self.norm_style,
2228 &mut self.ws.p2,
2229 );
2230 let (f1, f2) = match &lw.ffn {
2231 FfnKind::DenseMoe(dm) => (
2234 dense_moe_ffn(
2235 dm,
2236 &self.ws.p1,
2237 &h1,
2238 self.rms_eps,
2239 self.norm_style,
2240 self.pool.as_deref(),
2241 ),
2242 dense_moe_ffn(
2243 dm,
2244 &self.ws.p2,
2245 &h2,
2246 self.rms_eps,
2247 self.norm_style,
2248 self.pool.as_deref(),
2249 ),
2250 ),
2251 _ => ffn_forward_pair(
2252 &lw.ffn,
2253 &self.ws.p1,
2254 &self.ws.p2,
2255 self.pool.as_deref(),
2256 None,
2257 ),
2258 };
2259 let (f1, f2) = match &self.weights.layers[self.phys_layer(li)].ffn_out_norm {
2260 Some(w) => (
2261 inference::rms_norm(&f1, w, self.rms_eps, self.norm_style),
2262 inference::rms_norm(&f2, w, self.rms_eps, self.norm_style),
2263 ),
2264 None => (f1, f2),
2265 };
2266 for i in 0..self.hidden_size {
2267 h1[i] += f1[i];
2268 h2[i] += f2[i];
2269 }
2270 let (mut f1, mut f2) = (f1, f2);
2271 attention::recycle_buf(&mut f1);
2272 attention::recycle_buf(&mut f2);
2273 if let Some(sc) = self.weights.layers[self.phys_layer(li)].layer_scale {
2274 for i in 0..self.hidden_size {
2275 h1[i] *= sc;
2276 h2[i] *= sc;
2277 }
2278 }
2279 if self.is_loop_end(li) && li + 1 < self.num_layers {
2281 h1 = inference::rms_norm(
2282 &h1,
2283 &self.weights.final_norm,
2284 self.rms_eps,
2285 self.norm_style,
2286 );
2287 h2 = inference::rms_norm(
2288 &h2,
2289 &self.weights.final_norm,
2290 self.rms_eps,
2291 self.norm_style,
2292 );
2293 }
2294 }
2295 (h1, h2)
2296 }
2297
2298 fn commit_linear_scratch(&mut self) {
2300 for layer in &mut self.kv_cache.layers {
2301 if !layer.linear_scratch.is_empty() {
2302 std::mem::swap(&mut layer.linear_state, &mut layer.linear_scratch);
2303 layer.linear_scratch.clear();
2304 }
2305 }
2306 }
2307
2308 pub fn forward_ids(
2311 &mut self,
2312 ids: &[u32],
2313 task_mask: Option<&TaskMask>,
2314 ) -> Result<Vec<f32>, String> {
2315 if ids.is_empty() {
2316 return Err("empty id sequence".to_string());
2317 }
2318 self.kv_cache.clear();
2319 self.kv_history.clear();
2320 self.o1_begin();
2321 let mut hidden = vec![0.0f32; self.hidden_size];
2322 let mut pos = 0usize;
2323 if task_mask.is_none() && self.can_prefill_batched() && ids.len() > 2 {
2324 let chunk = prefill_chunk();
2328 let hs = self.hidden_size;
2329 while pos < ids.len() {
2330 let end = (pos + chunk).min(ids.len());
2331 let hb = self.prefill_batch(&ids[pos..end], pos);
2332 hidden.copy_from_slice(&hb[(end - pos - 1) * hs..]);
2333 pos = end;
2334 }
2335 }
2336 if task_mask.is_none() {
2337 while pos + 1 < ids.len() {
2338 let e1 = self.embed_single(ids[pos]);
2339 let e2 = self.embed_single(ids[pos + 1]);
2340 let (_, h2) = self.forward_pair(&e1, &e2, pos);
2341 self.commit_linear_scratch();
2342 hidden = h2;
2343 pos += 2;
2344 }
2345 }
2346 while pos < ids.len() {
2347 hidden = self.forward_layers(&self.embed_single(ids[pos]), pos, task_mask);
2348 pos += 1;
2349 }
2350 self.o1_seal();
2354 let normed = inference::rms_norm(
2355 &hidden,
2356 &self.weights.final_norm,
2357 self.rms_eps,
2358 self.norm_style,
2359 );
2360 Ok(self.lm_head_forward(&normed))
2361 }
2362
2363 pub fn ppl_ids(&mut self, ids: &[u32]) -> f64 {
2370 let (nll, cnt) = self.nll_ids_from(ids, 0);
2371 (nll / cnt.max(1) as f64).exp()
2372 }
2373
2374 pub fn probe_ffn_mass(&mut self, ids: &[u32]) -> Vec<Vec<f64>> {
2379 self.kv_cache.clear();
2380 self.kv_history.clear();
2381 FFN_PROBE.with(|p| {
2382 *p.borrow_mut() = Some(vec![vec![0f64; self.intermediate_size]; self.num_layers]);
2383 });
2384 crate::gpu::cpu_scope(|| {
2385 for (pos, &id) in ids.iter().enumerate() {
2386 let emb = self.embed_single(id);
2387 let _ = self.forward_layers(&emb, pos, None);
2388 }
2389 });
2390 self.kv_cache.clear();
2391 self.kv_history.clear();
2392 FFN_PROBE
2393 .with(|p| p.borrow_mut().take())
2394 .unwrap_or_default()
2395 }
2396
2397 pub fn ppl_ids_masked(&mut self, ids: &[u32], mask: &TaskMask) -> f64 {
2401 self.kv_cache.clear();
2402 self.kv_history.clear();
2403 let mut nll = 0f64;
2404 let mut cnt = 0usize;
2405 let mut hidden = vec![0f32; self.hidden_size];
2406 for (pos, &id) in ids.iter().enumerate() {
2407 if pos > 0 {
2408 inference::rms_norm_into(
2409 &hidden,
2410 &self.weights.final_norm,
2411 self.rms_eps,
2412 self.norm_style,
2413 &mut self.ws.n1,
2414 );
2415 let mut logits = self.lm_head_forward(&self.ws.n1);
2416 let max = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
2417 let sum: f64 = logits.iter().map(|&v| ((v - max) as f64).exp()).sum();
2418 let p = ((logits[id as usize] - max) as f64).exp() / sum.max(1e-300);
2419 nll -= p.max(1e-300).ln();
2420 cnt += 1;
2421 attention::recycle_buf(&mut logits);
2422 }
2423 let emb = self.embed_single(id);
2424 hidden = self.forward_layers(&emb, pos, Some(mask));
2425 }
2426 self.kv_cache.clear();
2427 self.kv_history.clear();
2428 (nll / cnt.max(1) as f64).exp()
2429 }
2430
2431 pub fn nll_ids_from(&mut self, ids: &[u32], start: usize) -> (f64, usize) {
2440 self.kv_cache.clear();
2441 self.kv_history.clear();
2442 let mut nll = 0f64;
2443 let mut cnt = 0usize;
2444 if self.can_prefill_batched() {
2445 const CHUNK: usize = 128;
2451 const LM_SUB: usize = 32;
2452 let n = ids.len().saturating_sub(1);
2453 let hs = self.hidden_size;
2454 let rows = self.weights.lm_head.rows();
2455 let mut pos = 0usize;
2456 while pos < n {
2457 let end = (pos + CHUNK).min(n);
2458 let bsz = end - pos;
2459 let hb = self.prefill_batch(&ids[pos..end], pos);
2460 let mut k0 = 0usize;
2461 while k0 < bsz {
2462 let k1 = (k0 + LM_SUB).min(bsz);
2463 let sb = k1 - k0;
2464 if pos + k1 <= start {
2467 k0 = k1;
2468 continue;
2469 }
2470 let mut normed = vec![0.0f32; sb * hs];
2471 for k in 0..sb {
2472 let r = inference::rms_norm(
2473 &hb[(k0 + k) * hs..(k0 + k + 1) * hs],
2474 &self.weights.final_norm,
2475 self.rms_eps,
2476 self.norm_style,
2477 );
2478 normed[k * hs..(k + 1) * hs].copy_from_slice(&r);
2479 }
2480 let mut logits = vec![0.0f32; sb * rows];
2481 self.weights
2482 .lm_head
2483 .matmat(&normed, sb, &mut logits, self.pool.as_deref());
2484 for k in 0..sb {
2485 if pos + k0 + k < start {
2486 continue;
2487 }
2488 let lg = &mut logits[k * rows..k * rows + self.vocab_size.min(rows)];
2489 if let Some(mu) = self.logit_multiplier {
2490 for v in lg.iter_mut() {
2491 *v *= mu;
2492 }
2493 }
2494 if let Some(c) = self.final_softcap {
2498 for v in lg.iter_mut() {
2499 *v = c * (*v / c).tanh();
2500 }
2501 }
2502 let lg = &logits[k * rows..k * rows + self.vocab_size.min(rows)];
2503 let target = ids[pos + k0 + k + 1] as usize;
2504 let max = lg.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
2505 let lse: f64 = lg
2506 .iter()
2507 .map(|&v| ((v - max) as f64).exp())
2508 .sum::<f64>()
2509 .ln()
2510 + max as f64;
2511 nll += lse - lg[target] as f64;
2512 cnt += 1;
2513 if std::env::var("CMF_PPL_TRACE").is_ok() {
2514 let top = lg
2515 .iter()
2516 .enumerate()
2517 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
2518 .map(|(i, _)| i)
2519 .unwrap_or(0);
2520 eprintln!(
2521 "BTRACE pos {} target {} nll {:.4} top {} lg_t {:.3} lg_top {:.3}",
2522 pos + k0 + k,
2523 target,
2524 lse - lg[target] as f64,
2525 top,
2526 lg[target],
2527 lg[top]
2528 );
2529 }
2530 }
2531 k0 = k1;
2532 }
2533 pos = end;
2534 }
2535 self.kv_cache.clear();
2536 self.kv_history.clear();
2537 return (nll, cnt);
2538 }
2539 for pos in 0..ids.len().saturating_sub(1) {
2540 let hidden = self.forward_layers(&self.embed_single(ids[pos]), pos, None);
2541 let out_of_band = self.graph_logits.take();
2549 if pos < start {
2550 continue;
2551 }
2552 let logits = match out_of_band {
2553 Some(lg) => lg,
2554 None => {
2555 let normed = inference::rms_norm(
2556 &hidden,
2557 &self.weights.final_norm,
2558 self.rms_eps,
2559 self.norm_style,
2560 );
2561 self.lm_head_forward(&normed)
2565 }
2566 };
2567 let target = ids[pos + 1] as usize;
2568 let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
2569 let lse: f64 = logits
2570 .iter()
2571 .map(|&v| ((v - max) as f64).exp())
2572 .sum::<f64>()
2573 .ln()
2574 + max as f64;
2575 let tok_nll = lse - logits[target] as f64;
2576 if std::env::var("CMF_PPL_TRACE").is_ok() && pos < 48 {
2577 let top = logits
2578 .iter()
2579 .enumerate()
2580 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
2581 .map(|(i, _)| i)
2582 .unwrap_or(0);
2583 eprintln!(
2584 "pos {pos:3} tgt {target:6} nll {tok_nll:7.3} | top1 {top:6} lg[t]={:.2} lg[top]={:.2}",
2585 logits[target], logits[top]
2586 );
2587 }
2588 nll += tok_nll;
2589 cnt += 1;
2590 }
2591 self.kv_cache.clear();
2592 self.kv_history.clear();
2593 (nll, cnt)
2594 }
2595
2596 pub fn nll_ids_o1(&mut self, ids: &[u32], prefill: usize) -> (f64, usize) {
2612 self.kv_cache.clear();
2613 self.kv_history.clear();
2614 self.o1_begin();
2615 let n = ids.len().saturating_sub(1);
2616 let p = prefill.min(n);
2617 let mut pos = 0usize;
2619 if self.can_prefill_batched() {
2620 const CHUNK: usize = 128;
2621 while pos < p {
2622 let end = (pos + CHUNK).min(p);
2623 let _ = self.prefill_batch(&ids[pos..end], pos);
2624 pos = end;
2625 }
2626 } else {
2627 while pos < p {
2628 let _ = self.forward_layers(&self.embed_single(ids[pos]), pos, None);
2629 pos += 1;
2630 }
2631 }
2632 self.o1_seal();
2633
2634 let mut nll = 0f64;
2635 let mut cnt = 0usize;
2636 for pos in p..n {
2637 let hidden = self.forward_layers(&self.embed_single(ids[pos]), pos, None);
2638 let normed = inference::rms_norm(
2639 &hidden,
2640 &self.weights.final_norm,
2641 self.rms_eps,
2642 self.norm_style,
2643 );
2644 let logits = self.lm_head_forward(&normed);
2648 let target = ids[pos + 1] as usize;
2649 let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
2650 let lse: f64 = logits
2651 .iter()
2652 .map(|&v| ((v - max) as f64).exp())
2653 .sum::<f64>()
2654 .ln()
2655 + max as f64;
2656 let tok_nll = lse - logits[target] as f64;
2657 if std::env::var("CMF_PPL_TRACE").is_ok() && pos < 48 {
2658 let top = logits
2659 .iter()
2660 .enumerate()
2661 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
2662 .map(|(i, _)| i)
2663 .unwrap_or(0);
2664 eprintln!(
2665 "pos {pos:3} tgt {target:6} nll {tok_nll:7.3} | top1 {top:6} lg[t]={:.2} lg[top]={:.2}",
2666 logits[target], logits[top]
2667 );
2668 }
2669 nll += tok_nll;
2670 cnt += 1;
2671 }
2672 self.kv_cache.clear();
2673 self.kv_history.clear();
2674 (nll, cnt)
2675 }
2676
2677 pub fn calib_ids(&mut self, ids: &[u32], temps: &[f32]) -> (Vec<bool>, Vec<Vec<f32>>) {
2685 self.kv_cache.clear();
2686 self.kv_history.clear();
2687 let n = ids.len().saturating_sub(1);
2688 let mut correct = Vec::with_capacity(n);
2689 let mut pmax = Vec::with_capacity(n);
2690 for pos in 0..n {
2691 let emb = self.embed_single(ids[pos]);
2692 let hidden = self.forward_layers(&emb, pos, None);
2693 let normed = inference::rms_norm(
2694 &hidden,
2695 &self.weights.final_norm,
2696 self.rms_eps,
2697 self.norm_style,
2698 );
2699 let logits = self.lm_head_forward(&normed);
2703 let target = ids[pos + 1] as usize;
2704 let (mut amax, mut mval) = (0usize, f32::NEG_INFINITY);
2705 for (i, &v) in logits.iter().enumerate() {
2706 if v > mval {
2707 mval = v;
2708 amax = i;
2709 }
2710 }
2711 correct.push(amax == target);
2712 let row: Vec<f32> = temps
2713 .iter()
2714 .map(|&t| {
2715 let tt = t.max(1e-3);
2716 let s: f32 = logits.iter().map(|&v| ((v - mval) / tt).exp()).sum();
2717 1.0 / s.max(1e-12) })
2719 .collect();
2720 pmax.push(row);
2721 }
2722 self.kv_cache.clear();
2723 self.kv_history.clear();
2724 (correct, pmax)
2725 }
2726
2727 pub fn ppl_ids_dynamic(&mut self, ids: &[u32]) -> (f64, usize) {
2734 let mut router = match self.dyn_router.take() {
2735 Some(r) => r,
2736 None => return (self.ppl_ids(ids), 0),
2737 };
2738 router.reset();
2739 self.dyn_phi_seen = 0;
2740 let _ = self.set_active_skill(None);
2741
2742 self.kv_cache.clear();
2743
2744 self.kv_history.clear();
2745 let mut nll = 0f64;
2746 let mut cnt = 0usize;
2747 for pos in 0..ids.len().saturating_sub(1) {
2748 let hidden = self.forward_layers(&self.embed_single(ids[pos]), pos, None);
2749 let normed = inference::rms_norm(
2750 &hidden,
2751 &self.weights.final_norm,
2752 self.rms_eps,
2753 self.norm_style,
2754 );
2755 let logits = self.lm_head_forward(&normed);
2759 let target = ids[pos + 1] as usize;
2760 let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
2761 let lse: f64 = logits
2762 .iter()
2763 .map(|&v| ((v - max) as f64).exp())
2764 .sum::<f64>()
2765 .ln()
2766 + max as f64;
2767 let tok_nll = lse - logits[target] as f64;
2768 if std::env::var("CMF_PPL_TRACE").is_ok() && pos < 48 {
2769 let top = logits
2770 .iter()
2771 .enumerate()
2772 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
2773 .map(|(i, _)| i)
2774 .unwrap_or(0);
2775 eprintln!(
2776 "pos {pos:3} tgt {target:6} nll {tok_nll:7.3} | top1 {top:6} lg[t]={:.2} lg[top]={:.2}",
2777 logits[target], logits[top]
2778 );
2779 }
2780 nll += tok_nll;
2781 cnt += 1;
2782 let phi = self.dyn_phi_ema.clone();
2784 if let Some(new_active) = router.step(&phi, pos) {
2785 let _ = self.set_active_skill(new_active);
2786 }
2787 }
2788 let switches = router.switches.len();
2789 let _ = self.set_active_skill(None);
2790 self.dyn_router = Some(router);
2791 self.kv_cache.clear();
2792 self.kv_history.clear();
2793 ((nll / cnt.max(1) as f64).exp(), switches)
2794 }
2795
2796 pub fn probe_phi(&mut self, ids: &[u32], layer: usize) -> Vec<f32> {
2798 self.kv_cache.clear();
2799 self.kv_history.clear();
2800 let mut acc = vec![0f32; self.hidden_size];
2801 for (pos, &id) in ids.iter().enumerate() {
2802 let h = self.forward_layers_upto(&self.embed_single(id), pos, None, Some(layer));
2803 for (a, v) in acc.iter_mut().zip(&h) {
2804 *a += v;
2805 }
2806 }
2807 let n = ids.len().max(1) as f32;
2808 for a in acc.iter_mut() {
2809 *a /= n;
2810 }
2811 self.kv_cache.clear();
2812 self.kv_history.clear();
2813 acc
2814 }
2815
2816 fn prefill_batch(&mut self, ids: &[u32], start_pos: usize) -> Vec<f32> {
2822 let b = ids.len();
2823 let hs = self.hidden_size;
2824 let mut h: Vec<f32> = vec![0.0; b * hs];
2827 let mut h_ready = false;
2828 let fill_h = |h: &mut Vec<f32>, me: &Self| {
2829 for (bi, &id) in ids.iter().enumerate() {
2830 let e = me.embed_single(id);
2831 h[bi * hs..(bi + 1) * hs].copy_from_slice(&e);
2832 }
2833 };
2834 let (_nkv, _hd, _rd, eps) = (
2835 self.num_kv_heads,
2836 self.head_dim,
2837 self.rotary_dim,
2838 self.rms_eps,
2839 );
2840 let pool = self.pool.clone();
2841 let norm_style = self.norm_style;
2842
2843 #[cfg(target_os = "macos")]
2844 let mut chunk_skip_until = 0usize;
2845 for li in 0..self.num_layers {
2846 crate::gpu::set_layer(li as i64); #[cfg(target_os = "macos")]
2853 {
2854 if li < chunk_skip_until {
2855 continue;
2856 }
2857 if !h_ready && li == 0 && self.weights.embed_tokens.q8_row_parts().is_none() {
2863 fill_h(&mut h, self);
2864 h_ready = true;
2865 }
2866 let ids_for_embed = (!h_ready && li == 0).then_some(ids);
2867 let end = self.chunk_run_gpu(li, &mut h, b, start_pos, ids_for_embed);
2868 if end > li {
2869 h_ready = true;
2870 chunk_skip_until = end;
2871 if self.is_loop_end(end - 1) && end < self.num_layers {
2874 for bi in 0..b {
2875 let normed = inference::rms_norm(
2876 &h[bi * hs..(bi + 1) * hs],
2877 &self.weights.final_norm,
2878 eps,
2879 norm_style,
2880 );
2881 h[bi * hs..(bi + 1) * hs].copy_from_slice(&normed);
2882 }
2883 }
2884 continue;
2885 }
2886 }
2887 if !h_ready {
2888 fill_h(&mut h, self);
2889 h_ready = true;
2890 }
2891 let lw = &self.weights.layers[self.phys_layer(li)];
2892 match &lw.attn {
2894 AttnKind::Kda(w) => {
2895 let cfg = self.kda_cfg.expect("kda layer without kda_cfg");
2897 let mut normed = vec![0.0f32; b * hs];
2898 for bi in 0..b {
2899 inference::rms_norm_into(
2900 &h[bi * hs..(bi + 1) * hs],
2901 &lw.input_norm,
2902 eps,
2903 norm_style,
2904 &mut normed[bi * hs..(bi + 1) * hs],
2905 );
2906 }
2907 let attn = crate::linear_core::kda_forward_batch(
2908 &normed,
2909 b,
2910 w,
2911 &cfg,
2912 &mut self.kv_cache.layers[li].linear_state,
2913 pool.as_deref(),
2914 );
2915 for (dst, &a) in h.iter_mut().zip(&attn) {
2916 *dst += a;
2917 }
2918 }
2919 AttnKind::LinearGdn(w) => {
2920 let cfg = self.gdn_cfg.expect("gdn layer without gdn_cfg");
2922 let mut normed = vec![0.0f32; b * hs];
2923 for bi in 0..b {
2924 let r = inference::rms_norm(
2925 &h[bi * hs..(bi + 1) * hs],
2926 &lw.input_norm,
2927 eps,
2928 norm_style,
2929 );
2930 normed[bi * hs..(bi + 1) * hs].copy_from_slice(&r);
2931 }
2932 let attn = crate::linear_core::gdn_forward_batch(
2933 &normed,
2934 b,
2935 w,
2936 &cfg,
2937 &mut self.kv_cache.layers[li].linear_state,
2938 pool.as_deref(),
2939 );
2940 for (dst, &a) in h.iter_mut().zip(&attn) {
2941 *dst += a;
2942 }
2943 }
2944 AttnKind::ShortConv(w) => {
2945 let cfg = self
2948 .short_conv_cfg
2949 .expect("short-conv layer without short_conv_cfg");
2950 let mut normed = vec![0.0f32; b * hs];
2951 for bi in 0..b {
2952 inference::rms_norm_into(
2953 &h[bi * hs..(bi + 1) * hs],
2954 &lw.input_norm,
2955 eps,
2956 norm_style,
2957 &mut normed[bi * hs..(bi + 1) * hs],
2958 );
2959 }
2960 let attn = short_conv_forward_batch(
2961 &normed,
2962 b,
2963 w,
2964 &cfg,
2965 &mut self.kv_cache.layers[li].linear_state,
2966 pool.as_deref(),
2967 );
2968 for (dst, &a) in h.iter_mut().zip(&attn) {
2969 *dst += a;
2970 }
2971 }
2972 AttnKind::Mla(w) => {
2973 let inv_freq_l = self.layer_inv_freq(li);
2976 let rs = self.layer_rope_scale(li);
2977 let mut normed = vec![0.0f32; hs];
2978 for bi in 0..b {
2979 inference::rms_norm_into(
2980 &h[bi * hs..(bi + 1) * hs],
2981 &lw.input_norm,
2982 eps,
2983 norm_style,
2984 &mut normed,
2985 );
2986 let ao = mla_attention(
2987 w,
2988 &normed,
2989 &mut self.kv_cache.layers[li],
2990 start_pos + bi,
2991 &inv_freq_l,
2992 rs,
2993 eps,
2994 pool.as_deref(),
2995 );
2996 for (dst, &a) in h[bi * hs..(bi + 1) * hs].iter_mut().zip(&ao) {
2997 *dst += a;
2998 }
2999 }
3000 }
3001 AttnKind::Full {
3002 wq,
3003 wk,
3004 wv,
3005 wo,
3006 q_norm,
3007 k_norm,
3008 output_gate,
3009 softplus_gate,
3010 bias,
3011 } => {
3012 let mut normed = vec![0.0f32; b * hs];
3016 for bi in 0..b {
3017 inference::rms_norm_into(
3018 &h[bi * hs..(bi + 1) * hs],
3019 &lw.input_norm,
3020 eps,
3021 norm_style,
3022 &mut normed[bi * hs..(bi + 1) * hs],
3023 );
3024 }
3025 let inv_freq_l = self.layer_inv_freq(li);
3026 let (nkv_l, hd_l, rd_l) = self.layer_geom(li);
3027 let cfg = QwenAttnCfg {
3028 num_heads: self.layer_num_heads(li),
3029 num_kv_heads: nkv_l,
3030 head_dim: hd_l,
3031 hidden_size: hs,
3032 position: start_pos,
3033 inv_freq: &inv_freq_l,
3034 rotary_dim: rd_l,
3035 scale: self.attn_scale,
3036 softcap: self.attn_softcap,
3037 window: self.layer_window(li),
3038 v_norm: self.attn_v_norm,
3039 q_norm: q_norm.as_deref(),
3040 k_norm: k_norm.as_deref(),
3041 output_gate: *output_gate,
3042 softplus_gate: softplus_gate
3043 .as_ref()
3044 .map(|(gate, per_head)| (gate, *per_head)),
3045 rope_scale: self.layer_rope_scale(li),
3046 bias: bias
3047 .as_ref()
3048 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
3049 rms_eps: eps,
3050 norm_style,
3051 pool: pool.as_deref(),
3052 };
3053 let mut attn = attention::qwen_attention_batch(
3054 &normed,
3055 b,
3056 wq,
3057 wk,
3058 wv,
3059 wo,
3060 &mut self.kv_cache.layers[li],
3061 &cfg,
3062 );
3063 if let Some(w) = &lw.attn_out_norm {
3064 for bi in 0..b {
3065 inference::rms_norm_into(
3066 &attn[bi * hs..(bi + 1) * hs],
3067 w,
3068 eps,
3069 norm_style,
3070 &mut normed[bi * hs..(bi + 1) * hs],
3071 );
3072 }
3073 attn.copy_from_slice(&normed);
3074 }
3075 for (dst, &a) in h.iter_mut().zip(&attn) {
3076 *dst += a;
3077 }
3078 }
3079 AttnKind::Linear(w) => {
3080 for bi in 0..b {
3081 let normed = inference::rms_norm(
3082 &h[bi * hs..(bi + 1) * hs],
3083 &lw.input_norm,
3084 eps,
3085 norm_style,
3086 );
3087 vmf_phase_forward(
3088 &normed,
3089 w,
3090 &self.vmf_cfg.expect("linear layer without vmf_cfg"),
3091 &mut self.kv_cache.layers[li].linear_state,
3092 pool.as_deref(),
3093 )
3094 .iter()
3095 .enumerate()
3096 .for_each(|(i, &a)| h[bi * hs + i] += a);
3097 }
3098 }
3099 }
3100
3101 let lw = &self.weights.layers[self.phys_layer(li)];
3103 let mut post = vec![0.0f32; b * hs];
3104 for bi in 0..b {
3105 let r =
3106 inference::rms_norm(&h[bi * hs..(bi + 1) * hs], &lw.post_norm, eps, norm_style);
3107 post[bi * hs..(bi + 1) * hs].copy_from_slice(&r);
3108 }
3109 let mut ffn = match &lw.ffn {
3110 FfnKind::Dense(d) => dense_ffn_batch(d, &post, b, pool.as_deref()),
3111 FfnKind::Moe(m) => moe_ffn_batch(m, &post, b, hs, pool.as_deref(), None),
3112 FfnKind::DenseMoe(dm) => {
3115 let mut out = vec![0.0f32; b * hs];
3116 for bi in 0..b {
3117 let r = dense_moe_ffn(
3118 dm,
3119 &post[bi * hs..(bi + 1) * hs],
3120 &h[bi * hs..(bi + 1) * hs],
3121 eps,
3122 norm_style,
3123 pool.as_deref(),
3124 );
3125 out[bi * hs..(bi + 1) * hs].copy_from_slice(&r);
3126 }
3127 out
3128 }
3129 };
3130 if let Some(w) = &lw.ffn_out_norm {
3131 for bi in 0..b {
3132 inference::rms_norm_into(
3133 &ffn[bi * hs..(bi + 1) * hs],
3134 w,
3135 eps,
3136 norm_style,
3137 &mut post[bi * hs..(bi + 1) * hs],
3138 );
3139 }
3140 ffn.copy_from_slice(&post);
3141 }
3142 for (dst, &f) in h.iter_mut().zip(&ffn) {
3143 *dst += f;
3144 }
3145 if let Some(sc) = lw.layer_scale {
3146 for v in h.iter_mut() {
3147 *v *= sc;
3148 }
3149 }
3150 if let Ok(tp) = std::env::var("CMF_TRACE_POS") {
3151 if let Some(t) = tp.parse::<usize>().ok() {
3152 if t >= start_pos && t < start_pos + b {
3153 let bi = t - start_pos;
3154 let row = &h[bi * hs..(bi + 1) * hs];
3155 let n: f32 = row.iter().map(|x| x * x).sum::<f32>().sqrt();
3156 eprintln!(
3157 "BATCH pos {t} after layer {li}: |h| = {n:.6} h0 {:.6} h1 {:.6}",
3158 row[0], row[1]
3159 );
3160 }
3161 }
3162 }
3163 if std::env::var("CMF_DEBUG_LAYERS").is_ok() {
3167 let row = &h[(b - 1) * hs..b * hs];
3168 let rms =
3169 (row.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / hs as f64).sqrt();
3170 let mx = row.iter().fold(0f32, |m, &v| m.max(v.abs()));
3171 eprintln!(
3172 "layer {li:>3} {:>10} ffn={:<5} rms={rms:>12.4} max={mx:>12.4}",
3173 match &self.weights.layers[self.phys_layer(li)].attn {
3174 AttnKind::LinearGdn(_) => "gdn",
3175 AttnKind::Linear(_) => "vmf",
3176 AttnKind::ShortConv(_) => "conv",
3177 _ => "attn",
3178 },
3179 match &lw.ffn {
3180 FfnKind::Moe(_) => "moe",
3181 FfnKind::Dense(_) => "dense",
3182 FfnKind::DenseMoe(_) => "dense+moe",
3183 },
3184 );
3185 }
3186 if self.is_loop_end(li) && li + 1 < self.num_layers {
3188 for bi in 0..b {
3189 let normed = inference::rms_norm(
3190 &h[bi * hs..(bi + 1) * hs],
3191 &self.weights.final_norm,
3192 eps,
3193 norm_style,
3194 );
3195 h[bi * hs..(bi + 1) * hs].copy_from_slice(&normed);
3196 }
3197 }
3198 if std::env::var("CMF_TRACE_H").is_ok() {
3199 let n = h[..hs].iter().map(|v| v.abs()).sum::<f32>() / hs as f32;
3200 let mx = h[..hs].iter().fold(0.0f32, |a, &v| a.max(v.abs()));
3201 eprintln!(
3202 "layer {li}: mean|h|={n:.4} max|h|={mx:.2} scale={:?}",
3203 lw.layer_scale
3204 );
3205 }
3206 }
3207 crate::gpu::set_layer(-1); h
3209 }
3210
3211 fn embed_single(&self, id: u32) -> Vec<f32> {
3213 let mut out = vec![0.0f32; self.hidden_size];
3214 if (id as usize) < self.weights.embed_tokens.rows() {
3215 self.weights.embed_tokens.row_f32(id as usize, &mut out);
3216 }
3217 if self.embed_multiplier != 1.0 {
3218 for v in out.iter_mut() {
3219 *v *= self.embed_multiplier;
3220 }
3221 }
3222 if self.dsv4.is_some() {
3226 let mut v = vec![0.0f32; self.hidden_size.max(1)];
3227 v[0] = id as f32;
3228 return v;
3229 }
3230 if let Some(b) = &self.g3n {
3233 return b.0.extend_embedding(id, &out, self.pool.as_deref());
3234 }
3235 out
3236 }
3237
3238 #[cfg(target_os = "macos")]
3244 fn chunk_run_gpu(
3245 &mut self,
3246 li0: usize,
3247 h: &mut [f32],
3248 b: usize,
3249 pos0: usize,
3250 embed_ids: Option<&[u32]>,
3251 ) -> usize {
3252 if !crate::gpu::enabled_here()
3256 || std::env::var("CMF_GPU_CHUNK")
3257 .map(|v| v == "0")
3258 .unwrap_or(false)
3259 || b < 32
3260 || self.swa.is_some()
3261 || self.global_attn.is_some()
3262 || self.attn_v_norm
3263 || (self.attn_scale - 1.0 / (self.head_dim as f32).sqrt()).abs() > 1e-9
3264 {
3265 return li0;
3266 }
3267 let Some(model) = self.model.clone() else {
3268 return li0;
3269 };
3270 let inv_freq = self.inv_freq.clone();
3271 let (nh, nkv, hd, hs) = (
3272 self.num_heads,
3273 self.num_kv_heads,
3274 self.head_dim,
3275 self.hidden_size,
3276 );
3277 let loop_end = if self.loop_final_norm {
3281 ((li0 / self.physical_layers) + 1) * self.physical_layers
3282 } else {
3283 self.num_layers
3284 };
3285 let mut layers: Vec<crate::gpu_metal::ChunkLayer> = Vec::new();
3286 let mut stored_at: Vec<usize> = Vec::new();
3287 for li in li0..self.num_layers.min(loop_end) {
3288 let lw = &self.weights.layers[self.phys_layer(li)];
3289 if lw.attn_out_norm.is_some() || lw.ffn_out_norm.is_some() || lw.layer_scale.is_some() {
3290 break;
3291 }
3292 let AttnKind::Full {
3293 wq,
3294 wk,
3295 wv,
3296 wo,
3297 q_norm,
3298 k_norm,
3299 output_gate: false,
3300 softplus_gate: None,
3301 bias,
3302 } = &lw.attn
3303 else {
3304 break;
3305 };
3306 let FfnKind::Dense(d) = &lw.ffn else { break };
3307 if d.act != Act::Silu {
3308 break;
3309 }
3310 fn cw(t: &QTensor) -> Option<(usize, usize, usize, &[f32])> {
3315 t.q8_row_parts()
3316 .or_else(|| t.q4t_parts().map(|(i, r, c)| (i, r, c, &[][..])))
3317 .or_else(|| t.q4tp_parts().map(|(i, r, c)| (i, r, c, &[][..])))
3318 }
3319 let parts = (
3320 cw(wq),
3321 cw(wk),
3322 cw(wv),
3323 cw(wo),
3324 cw(&d.gate_proj),
3325 cw(&d.up_proj),
3326 cw(&d.down_proj),
3327 );
3328 let (Some(pq), Some(pk), Some(pv), Some(po), Some(pg), Some(pu), Some(pd)) = parts
3329 else {
3330 break;
3331 };
3332 let layer = &self.kv_cache.layers[li];
3333 if layer.mode != crate::kv_cache::KvMode::F32 || layer.o1.is_some() {
3334 break;
3335 }
3336 stored_at.push(layer.head_len(0));
3337 layers.push(crate::gpu_metal::ChunkLayer {
3338 model: &model,
3339 kv_id: self.graph_kv_id,
3340 layer: li,
3341 wq: pq,
3342 wk: pk,
3343 wv: pv,
3344 wo: po,
3345 gate: pg,
3346 up: pu,
3347 down: pd,
3348 input_norm: &lw.input_norm,
3349 post_norm: &lw.post_norm,
3350 bias: bias
3351 .as_ref()
3352 .map(|(a, bb, cc)| (a.as_slice(), bb.as_slice(), cc.as_slice())),
3353 q_norm: q_norm.as_deref(),
3354 k_norm: k_norm.as_deref(),
3355 inv_freq: &inv_freq,
3356 rd: self.rotary_dim,
3357 nh,
3358 nkv,
3359 hd,
3360 hs,
3361 inter: d.gate_proj.rows(),
3362 gemma: matches!(self.norm_style, cortiq_core::NormStyle::Gemma),
3363 eps: self.rms_eps as f32,
3364 });
3365 }
3366 if layers.is_empty() {
3367 return li0;
3368 }
3369 let row = nkv * hd;
3370 let mut store: Vec<(Vec<f32>, Vec<f32>, Vec<f32>)> = stored_at
3371 .iter()
3372 .map(|&st| (vec![0f32; b * row], vec![0f32; b * row], vec![0f32; st + b]))
3373 .collect();
3374 let mut io: Vec<crate::gpu_metal::ChunkIo> = Vec::with_capacity(layers.len());
3375 for (i, (ok, ov, oi)) in store.iter_mut().enumerate() {
3376 let li = layers[i].layer;
3377 let layer = &self.kv_cache.layers[li];
3378 io.push(crate::gpu_metal::ChunkIo {
3379 cpu_stored: stored_at[i],
3380 cpu_k: (0..nkv).map(|g| layer.head_keys(g)).collect(),
3381 cpu_v: (0..nkv).map(|g| layer.head_values(g)).collect(),
3382 out_k: ok,
3383 out_v: ov,
3384 imp: oi,
3385 });
3386 }
3387 let n_run = layers.len();
3388 let last = layers.last().map(|l| l.layer + 1).unwrap_or(li0);
3389 let ep = embed_ids.and_then(|ids| {
3392 self.weights
3393 .embed_tokens
3394 .q8_row_parts()
3395 .map(|(idx, rows, _c, rs)| crate::gpu_metal::ChunkEmbed {
3396 idx,
3397 rows,
3398 row_scale: rs,
3399 ids,
3400 mult: self.embed_multiplier,
3401 })
3402 });
3403 if embed_ids.is_some() && ep.is_none() {
3404 return li0;
3405 }
3406 if !crate::gpu_metal::chunk_run_gpu(&layers, &mut io, h, b, pos0, ep.as_ref()) {
3407 return li0;
3408 }
3409 drop(io);
3410 drop(layers);
3411 for (i, (ok, ov, oi)) in store.iter().enumerate().take(n_run) {
3414 let li = li0 + i;
3415 let layer = &mut self.kv_cache.layers[li];
3416 for bi in 0..b {
3417 layer.append(
3418 &ok[bi * row..(bi + 1) * row],
3419 &ov[bi * row..(bi + 1) * row],
3420 &[],
3421 );
3422 }
3423 layer.accumulate_imp(oi);
3424 }
3425 last
3426 }
3427
3428 fn layer_is_local(&self, li: usize) -> bool {
3431 if let Some(layers) = &self.sliding_layers {
3432 return layers.get(li).copied().unwrap_or(false);
3433 }
3434 match self.swa {
3435 Some((_, pattern)) => (li + 1) % pattern.max(1) != 0,
3436 None => false,
3437 }
3438 }
3439
3440 fn layer_inv_freq(&self, li: usize) -> std::sync::Arc<Vec<f32>> {
3443 if self.layer_is_local(li) {
3444 if let Some(f) = &self.inv_freq_local {
3445 return f.clone();
3446 }
3447 } else if let Some(f) = &self.inv_freq_global {
3448 return f.clone();
3449 }
3450 self.inv_freq.clone()
3451 }
3452
3453 fn layer_window(&self, li: usize) -> Option<usize> {
3455 self.swa
3456 .and_then(|(w, _)| self.layer_is_local(li).then_some(w))
3457 }
3458
3459 fn layer_num_heads(&self, li: usize) -> usize {
3460 self.attention_heads_per_layer
3461 .as_ref()
3462 .and_then(|v| v.get(li).copied())
3463 .unwrap_or(self.num_heads)
3464 }
3465
3466 fn layer_rope_scale(&self, li: usize) -> f32 {
3467 if self.layer_is_local(li) {
3468 self.rope_scale_local
3469 } else {
3470 self.rope_scale
3471 }
3472 }
3473
3474 fn layer_geom(&self, li: usize) -> (usize, usize, usize) {
3477 if !self.layer_is_local(li) {
3478 if let Some((ghd, gkv)) = self.global_attn {
3479 return (gkv, ghd, ghd);
3480 }
3481 }
3482 (
3483 self.num_kv_heads,
3484 self.head_dim,
3485 if self.layer_is_local(li) {
3486 self.rotary_dim_local.unwrap_or(self.rotary_dim)
3487 } else {
3488 self.rotary_dim
3489 },
3490 )
3491 }
3492
3493 fn forward_layers(
3495 &mut self,
3496 hidden: &[f32],
3497 position: usize,
3498 task_mask: Option<&TaskMask>,
3499 ) -> Vec<f32> {
3500 self.forward_layers_upto(hidden, position, task_mask, None)
3501 }
3502
3503 fn try_token_graph_wgpu(
3507 &self,
3508 hidden: &[f32],
3509 position: usize,
3510 logits_out: &mut Vec<f32>,
3511 ) -> Option<Vec<f32>> {
3512 self.try_token_graph_wgpu_steps(hidden, position, logits_out, 1, None)
3513 }
3514
3515 fn try_multi_burst(&self, t_next: u32, position: usize, k: usize) -> Option<Vec<u32>> {
3519 if self.o1_active() || self.attn_softcap > 0.0 {
3520 return None;
3521 }
3522 let graph_on = match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
3523 Some("0") => return None,
3524 Some(_) => true,
3525 None => crate::gpu::wgpu_graph_default(),
3526 };
3527 if !graph_on {
3528 return None;
3529 }
3530 let emb = self.embed_single(t_next);
3531 let mut lg = Vec::new();
3532 let mut ids = Vec::new();
3533 self.try_token_graph_wgpu_steps(&emb, position, &mut lg, k, Some(&mut ids))?;
3534 (ids.len() == k).then_some(ids)
3535 }
3536
3537 fn try_token_graph_wgpu_steps(
3541 &self,
3542 hidden: &[f32],
3543 position: usize,
3544 logits_out: &mut Vec<f32>,
3545 steps: usize,
3546 ids_out: Option<&mut Vec<u32>>,
3547 ) -> Option<Vec<f32>> {
3548 let o1_gpu = std::env::var("CMF_O1_GPU").as_deref() == Ok("1");
3551 if (self.o1_active() && !o1_gpu) || self.attn_softcap > 0.0 {
3552 return None;
3556 }
3557 let o1_views: Vec<Option<Vec<crate::nystrom::O1DeviceView<'_>>>> = (0..self.num_layers)
3562 .map(|li| {
3563 if !o1_gpu {
3564 return None;
3565 }
3566 self.kv_cache.layers[self.phys_layer(li)].o1_views()
3567 })
3568 .collect();
3569 if self.o1_active() && o1_gpu {
3570 let want: usize = (0..self.num_layers)
3573 .filter(|li| !matches!(self.kv_cache.layers[self.phys_layer(*li)].o1, None))
3574 .count();
3575 let have = o1_views.iter().filter(|v| v.is_some()).count();
3576 if want == 0 || have != want {
3577 return None;
3578 }
3579 }
3580 let nh = self.num_heads;
3581 let (nkv, hd, rd) = self.layer_geom(0);
3582 let gemma = self.norm_style == cortiq_core::NormStyle::Gemma;
3583 let mut layers = Vec::with_capacity(self.num_layers);
3584 let mut model = None;
3585 let dbg = std::env::var("CMF_GRAPH_DEBUG").is_ok();
3586 fn gw(t: &QTensor) -> Option<crate::gpu::GraphW<'_>> {
3587 if let Some((_, i, kind, rs)) = t.graph_weight() {
3588 return Some(crate::gpu::GraphW {
3589 idx: i,
3590 kind,
3591 row_scale: rs,
3592 data: &[],
3593 });
3594 }
3595 t.as_f32().map(|d| crate::gpu::GraphW {
3597 idx: 0,
3598 kind: 4,
3599 row_scale: &[],
3600 data: d,
3601 })
3602 }
3603 for li in 0..self.num_layers {
3604 let lw = &self.weights.layers[self.phys_layer(li)];
3605 if dbg {
3606 let ak = match &lw.attn {
3607 AttnKind::Mla(_) => "Mla".into(),
3608 AttnKind::Full {
3609 output_gate, bias, ..
3610 } => format!("Full gate={output_gate} bias={}", bias.is_some()),
3611 AttnKind::LinearGdn(_) => "LinearGdn".into(),
3612 AttnKind::Kda(_) => "Kda".into(),
3613 AttnKind::Linear(_) => "Linear".into(),
3614 AttnKind::ShortConv(_) => "ShortConv".into(),
3615 };
3616 let fk = match &lw.ffn {
3617 FfnKind::Dense(_) => "Dense",
3618 FfnKind::Moe(_) => "Moe",
3619 FfnKind::DenseMoe(_) => "DenseMoe",
3620 };
3621 eprintln!("graph L{li}: attn={ak} ffn={fk}");
3622 }
3623 let gffn = match &lw.ffn {
3624 FfnKind::DenseMoe(_) => return None, FfnKind::Dense(d) => crate::gpu::GraphFfn::Dense {
3626 gate: gw(&d.gate_proj)?,
3627 up: gw(&d.up_proj)?,
3628 down: gw(&d.down_proj)?,
3629 },
3630 FfnKind::Moe(m) => {
3631 if m.router_sigmoid
3636 || m.expert_bias.is_some()
3637 || m.route_tau.is_some()
3638 || m.mask.is_some()
3639 {
3640 return None;
3641 }
3642 let (se, sg) = m.shared.as_ref()?;
3643 let sgate = gw(sg.as_ref()?)?;
3644 let router = gw(&m.router)?;
3645 let inter = m.experts.first()?.gate_proj.rows();
3646 let mut experts = Vec::with_capacity(m.experts.len() + 1);
3647 let mut q4tp: Option<bool> = None;
3650 let mut gu_q2: Option<bool> = None;
3653 for e in m.experts.iter().chain(std::iter::once(se)) {
3654 if !matches!(e.act, Act::Silu)
3655 || e.gate_proj.rows() != inter
3656 || e.up_proj.rows() != inter
3657 {
3658 return None;
3659 }
3660 let (mm, gi, ui, di, is_p, is_q2) = match e.gate_proj.mapped_q4t() {
3661 Some((mm, gi)) => (
3662 mm,
3663 gi,
3664 e.up_proj.mapped_q4t()?.1,
3665 e.down_proj.mapped_q4t()?.1,
3666 false,
3667 false,
3668 ),
3669 None => match e.gate_proj.mapped_q2tp() {
3670 Some((mm, gi)) => (
3671 mm,
3672 gi,
3673 e.up_proj.mapped_q2tp()?.1,
3674 e.down_proj.mapped_q4tp()?.1,
3675 true,
3676 true,
3677 ),
3678 None => {
3679 let (mm, gi) = e.gate_proj.mapped_q4tp()?;
3680 (
3681 mm,
3682 gi,
3683 e.up_proj.mapped_q4tp()?.1,
3684 e.down_proj.mapped_q4tp()?.1,
3685 true,
3686 false,
3687 )
3688 }
3689 },
3690 };
3691 if *q4tp.get_or_insert(is_p) != is_p || *gu_q2.get_or_insert(is_q2) != is_q2
3692 {
3693 tracing::warn!(
3699 "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."
3700 );
3701 return None;
3702 }
3703 model.get_or_insert_with(|| mm.clone());
3704 experts.push((gi, ui, di));
3705 }
3706 crate::gpu::GraphFfn::Moe {
3707 router,
3708 shared_gate: sgate,
3709 experts,
3710 n_exp: m.experts.len(),
3711 top_k: std::env::var("CMF_TOPK_PROBE")
3717 .ok()
3718 .and_then(|v| v.parse::<usize>().ok())
3719 .filter(|k| *k > 0 && *k <= m.top_k)
3720 .unwrap_or(m.top_k),
3721 inter,
3722 norm_topk: m.norm_topk_prob,
3723 q4tp: q4tp?,
3724 gu_q2: gu_q2.unwrap_or(false),
3725 }
3726 }
3727 };
3728 let attn = match &lw.attn {
3729 AttnKind::Full {
3730 wq,
3731 wk,
3732 wv,
3733 wo,
3734 q_norm,
3735 k_norm,
3736 output_gate,
3737 softplus_gate,
3738 bias,
3739 } => {
3740 if softplus_gate.is_some() || self.attention_heads_per_layer.is_some() {
3741 return None;
3742 }
3743 let (m, _, _, _) = wq.graph_weight()?;
3744 model = Some(m.clone());
3745 crate::gpu::GraphAttn::Full {
3746 wq: gw(wq)?,
3747 wk: gw(wk)?,
3748 wv: gw(wv)?,
3749 wo: gw(wo)?,
3750 q_norm: q_norm.as_deref(),
3751 k_norm: k_norm.as_deref(),
3752 bias: bias
3753 .as_ref()
3754 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
3755 output_gate: *output_gate,
3756 cpu_k: self.kv_cache.layers[li].k_heads(),
3757 cpu_v: self.kv_cache.layers[li].v_heads(),
3758 }
3759 }
3760 AttnKind::LinearGdn(w) => {
3761 let cfg = self.gdn_cfg?;
3762 let (m, _, _, _) = w.in_proj_qkv.graph_weight()?;
3763 model = Some(m.clone());
3764 crate::gpu::GraphAttn::Gdn {
3765 qkv: gw(&w.in_proj_qkv)?,
3766 z: gw(&w.in_proj_z)?,
3767 a: gw(&w.in_proj_a)?,
3768 b: gw(&w.in_proj_b)?,
3769 out: gw(&w.out_proj)?,
3770 conv1d: &w.conv1d,
3771 a_log: &w.a_log,
3772 dt_bias: &w.dt_bias,
3773 norm: &w.norm,
3774 nv: cfg.num_v_heads,
3775 nk: cfg.num_k_heads,
3776 dk: cfg.key_head_dim,
3777 dv: cfg.value_head_dim,
3778 kk: cfg.conv_kernel,
3779 cpu_state: &self.kv_cache.layers[self.phys_layer(li)].linear_state,
3780 }
3781 }
3782 _ => return None,
3783 };
3784 layers.push(crate::gpu::GraphLayer {
3785 input_norm: &lw.input_norm,
3786 attn,
3787 post_norm: &lw.post_norm,
3788 ffn: gffn,
3789 });
3790 }
3791 let model = model?;
3792 let lm_gw = if self.graph_want_logits
3798 && std::env::var("CMF_GPU_LMHEAD")
3799 .map(|v| v != "0")
3800 .unwrap_or(true)
3801 {
3802 self.weights.lm_head.graph_weight().map(|(_, i, kind, rs)| {
3803 (
3804 crate::gpu::GraphW {
3805 idx: i,
3806 kind,
3807 row_scale: rs,
3808 data: &[],
3809 },
3810 self.weights.lm_head.rows(),
3811 )
3812 })
3813 } else {
3814 None
3815 };
3816 let lm = lm_gw.as_ref().map(|(gw, rows)| (gw, *rows));
3817 let emb_gw = if steps > 1 {
3819 self.weights
3820 .embed_tokens
3821 .graph_weight()
3822 .map(|(_, i, kind, rs)| {
3823 (
3824 crate::gpu::GraphW {
3825 idx: i,
3826 kind,
3827 row_scale: rs,
3828 data: &[],
3829 },
3830 self.weights.embed_tokens.rows(),
3831 self.embed_multiplier as f32,
3832 )
3833 })
3834 } else {
3835 None
3836 };
3837
3838 let loop_norm_at: Vec<usize> = if self.loop_final_norm {
3841 (0..self.num_layers - 1)
3842 .filter(|&li| (li + 1) % self.physical_layers == 0)
3843 .collect()
3844 } else {
3845 Vec::new()
3846 };
3847 let mut h = hidden.to_vec();
3848 crate::gpu::forward_token_graph(
3849 &model,
3850 self.graph_kv_id,
3851 &layers,
3852 &o1_views,
3853 self.o1_epoch,
3854 &self.inv_freq,
3855 &mut h,
3856 nh,
3857 nkv,
3858 hd,
3859 rd,
3860 self.hidden_size,
3861 self.intermediate_size,
3862 position,
3863 self.kv_cache.max_seq_len,
3864 gemma,
3865 self.rms_eps as f32,
3866 lm,
3867 &self.weights.final_norm,
3868 logits_out,
3869 &loop_norm_at,
3870 steps,
3871 emb_gw.as_ref().map(|(gw, rows, m)| (gw, *rows, *m)),
3872 ids_out,
3873 )
3874 .then_some(h)
3875 }
3876
3877 fn try_batch_graph_wgpu(&self, hiddens: &mut [f32], positions: &[usize], k: usize) -> bool {
3882 if self.attn_softcap > 0.0 {
3883 return false; }
3885 if self.o1_active() {
3886 return false;
3887 }
3888 let nh = self.num_heads;
3889 let (nkv, hd, rd) = self.layer_geom(0);
3890 let gemma = self.norm_style == cortiq_core::NormStyle::Gemma;
3891 fn gw(t: &QTensor) -> Option<crate::gpu::GraphW<'_>> {
3892 if let Some((_, i, kind, rs)) = t.graph_weight() {
3893 return Some(crate::gpu::GraphW {
3894 idx: i,
3895 kind,
3896 row_scale: rs,
3897 data: &[],
3898 });
3899 }
3900 t.as_f32().map(|d| crate::gpu::GraphW {
3901 idx: 0,
3902 kind: 4,
3903 row_scale: &[],
3904 data: d,
3905 })
3906 }
3907 let built: Option<(
3908 Vec<crate::gpu::GraphLayer<'_>>,
3909 std::sync::Arc<cortiq_core::CmfModel>,
3910 )> = (|| {
3911 let mut layers = Vec::with_capacity(self.num_layers);
3912 let mut model = None;
3913 for li in 0..self.num_layers {
3914 let lw = &self.weights.layers[self.phys_layer(li)];
3915 let gffn = match &lw.ffn {
3922 FfnKind::Dense(d) => crate::gpu::GraphFfn::Dense {
3923 gate: gw(&d.gate_proj)?,
3924 up: gw(&d.up_proj)?,
3925 down: gw(&d.down_proj)?,
3926 },
3927 FfnKind::Moe(m) => {
3928 if m.router_sigmoid
3929 || m.expert_bias.is_some()
3930 || m.route_tau.is_some()
3931 || m.mask.is_some()
3932 {
3933 return None;
3934 }
3935 let (se, sg) = m.shared.as_ref()?;
3936 let sgate = gw(sg.as_ref()?)?;
3937 let router = gw(&m.router)?;
3938 let inter = m.experts.first()?.gate_proj.rows();
3939 let mut experts = Vec::with_capacity(m.experts.len() + 1);
3940 let mut q4tp: Option<bool> = None;
3941 for e in m.experts.iter().chain(std::iter::once(se)) {
3942 if !matches!(e.act, Act::Silu)
3943 || e.gate_proj.rows() != inter
3944 || e.up_proj.rows() != inter
3945 {
3946 return None;
3947 }
3948 let (mm, gi, ui, di, is_p) = match e.gate_proj.mapped_q4t() {
3949 Some((mm, gi)) => (
3950 mm,
3951 gi,
3952 e.up_proj.mapped_q4t()?.1,
3953 e.down_proj.mapped_q4t()?.1,
3954 false,
3955 ),
3956 None => {
3957 let (mm, gi) = e.gate_proj.mapped_q4tp()?;
3958 (
3959 mm,
3960 gi,
3961 e.up_proj.mapped_q4tp()?.1,
3962 e.down_proj.mapped_q4tp()?.1,
3963 true,
3964 )
3965 }
3966 };
3967 if *q4tp.get_or_insert(is_p) != is_p {
3968 return None;
3969 }
3970 model.get_or_insert_with(|| mm.clone());
3971 experts.push((gi, ui, di));
3972 }
3973 crate::gpu::GraphFfn::Moe {
3974 router,
3975 shared_gate: sgate,
3976 experts,
3977 n_exp: m.experts.len(),
3978 top_k: m.top_k,
3979 inter,
3980 norm_topk: m.norm_topk_prob,
3981 q4tp: q4tp?,
3982 gu_q2: false,
3985 }
3986 }
3987 _ => return None,
3988 };
3989 let attn = match &lw.attn {
3990 AttnKind::Full {
3991 wq,
3992 wk,
3993 wv,
3994 wo,
3995 q_norm,
3996 k_norm,
3997 output_gate,
3998 softplus_gate,
3999 bias,
4000 } => {
4001 if softplus_gate.is_some() || self.attention_heads_per_layer.is_some() {
4002 return None;
4003 }
4004 let (m, _, _, _) = wq.graph_weight()?;
4005 model = Some(m.clone());
4006 crate::gpu::GraphAttn::Full {
4007 wq: gw(wq)?,
4008 wk: gw(wk)?,
4009 wv: gw(wv)?,
4010 wo: gw(wo)?,
4011 q_norm: q_norm.as_deref(),
4012 k_norm: k_norm.as_deref(),
4013 bias: bias
4014 .as_ref()
4015 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
4016 output_gate: *output_gate,
4017 cpu_k: self.kv_cache.layers[li].k_heads(),
4018 cpu_v: self.kv_cache.layers[li].v_heads(),
4019 }
4020 }
4021 AttnKind::LinearGdn(w) => {
4022 let cfg = self.gdn_cfg?;
4023 let (m, _, _, _) = w.in_proj_qkv.graph_weight()?;
4024 model = Some(m.clone());
4025 crate::gpu::GraphAttn::Gdn {
4026 qkv: gw(&w.in_proj_qkv)?,
4027 z: gw(&w.in_proj_z)?,
4028 a: gw(&w.in_proj_a)?,
4029 b: gw(&w.in_proj_b)?,
4030 out: gw(&w.out_proj)?,
4031 conv1d: &w.conv1d,
4032 a_log: &w.a_log,
4033 dt_bias: &w.dt_bias,
4034 norm: &w.norm,
4035 nv: cfg.num_v_heads,
4036 nk: cfg.num_k_heads,
4037 dk: cfg.key_head_dim,
4038 dv: cfg.value_head_dim,
4039 kk: cfg.conv_kernel,
4040 cpu_state: &self.kv_cache.layers[self.phys_layer(li)].linear_state,
4041 }
4042 }
4043 _ => return None,
4044 };
4045 layers.push(crate::gpu::GraphLayer {
4046 input_norm: &lw.input_norm,
4047 attn,
4048 post_norm: &lw.post_norm,
4049 ffn: gffn,
4050 });
4051 }
4052 Some((layers, model?))
4053 })();
4054 let Some((layers, model)) = built else {
4055 {
4056 use std::sync::atomic::{AtomicBool, Ordering};
4057 static SAID: AtomicBool = AtomicBool::new(false);
4058 if !SAID.swap(true, Ordering::Relaxed) {
4059 tracing::warn!("batch graph: BUILDER refused (layer weights/kinds)");
4060 }
4061 }
4062 return false;
4063 };
4064 crate::gpu::forward_batch_graph(
4065 &model,
4066 self.graph_kv_id,
4067 &layers,
4068 &self.inv_freq,
4069 hiddens,
4070 nh,
4071 nkv,
4072 hd,
4073 rd,
4074 self.hidden_size,
4075 self.intermediate_size,
4076 positions,
4077 self.kv_cache.max_seq_len,
4078 gemma,
4079 self.rms_eps as f32,
4080 k,
4081 )
4082 }
4083
4084 fn forward_layers_upto(
4086 &mut self,
4087 hidden: &[f32],
4088 position: usize,
4089 task_mask: Option<&TaskMask>,
4090 upto: Option<usize>,
4091 ) -> Vec<f32> {
4092 if let Some(b) = &mut self.dsv4 {
4098 let _ = (task_mask, upto);
4099 let token_id = hidden.first().copied().unwrap_or(0.0) as u32;
4100 let (g, layers, cfg, st) = (&b.0, &b.1, b.2, &mut b.3);
4101 st.pos = position;
4102 let mut logits = Vec::new();
4103 crate::dsv4::forward_token(
4104 g,
4105 layers,
4106 &cfg,
4107 st,
4108 token_id,
4109 &self.inv_freq,
4110 self.pool.as_deref(),
4111 &mut logits,
4112 );
4113 self.graph_logits = Some(logits);
4114 return vec![0.0; self.hidden_size];
4117 }
4118 if let Some(b) = &self.g3n {
4121 let _ = (task_mask, upto);
4122 return crate::g3n::g3n_forward(
4123 &b.0,
4124 &b.1,
4125 hidden,
4126 position,
4127 &mut self.kv_cache.layers,
4128 self.num_heads,
4129 self.num_kv_heads,
4130 self.head_dim,
4131 self.pool.as_deref(),
4132 );
4133 }
4134 let mut h = hidden.to_vec();
4135 let (nh, _nkv, _hd, hs, _rd, eps) = (
4138 self.num_heads,
4139 self.num_kv_heads,
4140 self.head_dim,
4141 self.hidden_size,
4142 self.rotary_dim,
4143 self.rms_eps,
4144 );
4145 let pool = self.pool.clone();
4146 let graph_env = std::env::var("CMF_GPU_WGPU_GRAPH").ok();
4158 let graph_on = match graph_env.as_deref() {
4159 Some("0") => false,
4160 Some(_) => true,
4161 None => crate::gpu::wgpu_graph_default(),
4167 };
4168 let graph_trusted =
4169 graph_env.is_some() || crate::gpu::wgpu_graph_default() || self.gdn_cfg.is_some();
4170 let race_eligible = graph_on && upto.is_none() && task_mask.is_none();
4171 if race_eligible && crate::gpu::graph_race_use_graph(graph_trusted) {
4172 let t_graph = std::time::Instant::now();
4173 let mut lg = Vec::new();
4174 let built = self.try_token_graph_wgpu(hidden, position, &mut lg);
4175 graph_note(built.is_some());
4176 if let Some(hh) = built {
4177 let dur = t_graph.elapsed();
4178 if std::env::var("CMF_GRAPH_PROF").is_ok() {
4179 eprintln!("graph-call: {:.2} ms total", dur.as_secs_f64() * 1000.0);
4180 }
4181 if graph_trusted || !crate::gpu::graph_race_first_token_hopeless(dur) {
4182 if !graph_trusted {
4183 crate::gpu::graph_race_record(true, dur);
4184 }
4185 if !lg.is_empty() {
4186 lg.resize(self.vocab_size, 0.0);
4189 if let Some(c) = self.final_softcap {
4190 for l in lg.iter_mut() {
4191 *l = c * (*l / c).tanh();
4192 }
4193 }
4194 self.graph_logits = Some(lg);
4195 }
4196 return hh;
4197 }
4198 }
4204 }
4205 let t_race_cpu = (race_eligible && !graph_trusted).then(std::time::Instant::now);
4206
4207 #[cfg(target_os = "macos")]
4208 let mut gpu_skip_until = 0usize;
4209 for li in 0..self.num_layers {
4210 crate::gpu::set_layer(li as i64); if let Some(u) = upto {
4212 if li > u {
4213 break;
4214 }
4215 }
4216 if let Some(mask) = task_mask {
4217 if !mask.layer_alive(li) {
4218 continue; }
4220 }
4221 #[cfg(target_os = "macos")]
4225 {
4226 if li < gpu_skip_until {
4227 continue;
4228 }
4229 if task_mask.is_none() {
4230 let end = self.q1_graph_gpu(li, upto, position, &mut h);
4231 if end > li {
4232 gpu_skip_until = end;
4233 if self.is_loop_end(end - 1) && end < self.num_layers {
4236 h = inference::rms_norm(
4237 &h,
4238 &self.weights.final_norm,
4239 self.rms_eps,
4240 self.norm_style,
4241 );
4242 }
4243 continue;
4244 }
4245 }
4246 }
4247
4248 let lw = &self.weights.layers[self.phys_layer(li)];
4249 if let Ok(tp) = std::env::var("CMF_TRACE_POS") {
4250 if tp.parse::<usize>().ok() == Some(position) {
4251 let n: f32 = h.iter().map(|x| x * x).sum::<f32>().sqrt();
4252 eprintln!(
4253 "TRACE pos {position} layer {li}: |h| = {n:.6} h0 {:.6} h1 {:.6}",
4254 h[0], h[1]
4255 );
4256 }
4257 }
4258 inference::rms_norm_into(
4261 &h,
4262 &lw.input_norm,
4263 self.rms_eps,
4264 self.norm_style,
4265 &mut self.ws.n1,
4266 );
4267
4268 let attn_out = match &lw.attn {
4269 AttnKind::Mla(w) => {
4270 let inv_freq_l = self.layer_inv_freq(li);
4271 let rs = self.layer_rope_scale(li);
4272 let eps = self.rms_eps;
4273 let pool = self.pool.clone();
4274 mla_attention(
4275 w,
4276 &self.ws.n1,
4277 &mut self.kv_cache.layers[li],
4278 position,
4279 &inv_freq_l,
4280 rs,
4281 eps,
4282 pool.as_deref(),
4283 )
4284 }
4285 AttnKind::Linear(w) => {
4286 let cfg = self.vmf_cfg.expect("linear layer without vmf_cfg");
4287 vmf_phase_forward(
4288 &self.ws.n1,
4289 w,
4290 &cfg,
4291 &mut self.kv_cache.layers[li].linear_state,
4292 self.pool.as_deref(),
4293 )
4294 }
4295 AttnKind::Kda(w) => {
4296 let cfg = self.kda_cfg.expect("kda layer without kda_cfg");
4297 crate::linear_core::kda_forward(
4298 &self.ws.n1,
4299 w,
4300 &cfg,
4301 &mut self.kv_cache.layers[li].linear_state,
4302 self.pool.as_deref(),
4303 )
4304 }
4305 AttnKind::LinearGdn(w) => {
4306 let cfg = self.gdn_cfg.expect("gdn layer without gdn_cfg");
4307 gdn_forward(
4308 &self.ws.n1,
4309 w,
4310 &cfg,
4311 &mut self.kv_cache.layers[li].linear_state,
4312 self.pool.as_deref(),
4313 )
4314 }
4315 AttnKind::ShortConv(w) => {
4316 let cfg = self
4317 .short_conv_cfg
4318 .expect("short-conv layer without short_conv_cfg");
4319 short_conv_forward(
4320 &self.ws.n1,
4321 w,
4322 &cfg,
4323 &mut self.kv_cache.layers[li].linear_state,
4324 self.pool.as_deref(),
4325 )
4326 }
4327 AttnKind::Full {
4328 wq,
4329 wk,
4330 wv,
4331 wo,
4332 q_norm,
4333 k_norm,
4334 output_gate,
4335 softplus_gate,
4336 bias,
4337 } if self.kv_cache.layers[li].o1_sealed() => {
4338 let inv_freq_l = self.layer_inv_freq(li);
4341 let (nkv_l, hd_l, rd_l) = self.layer_geom(li);
4342 let cfg = QwenAttnCfg {
4343 num_heads: self.layer_num_heads(li),
4344 num_kv_heads: nkv_l,
4345 head_dim: hd_l,
4346 hidden_size: hs,
4347 position,
4348 inv_freq: &inv_freq_l,
4349 rotary_dim: rd_l,
4350 scale: self.attn_scale,
4351 softcap: self.attn_softcap,
4352 window: None,
4353 v_norm: self.attn_v_norm,
4354 q_norm: q_norm.as_deref(),
4355 k_norm: k_norm.as_deref(),
4356 output_gate: *output_gate,
4357 softplus_gate: softplus_gate
4358 .as_ref()
4359 .map(|(gate, per_head)| (gate, *per_head)),
4360 rope_scale: self.layer_rope_scale(li),
4361 bias: bias
4362 .as_ref()
4363 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
4364 rms_eps: eps,
4365 norm_style: self.norm_style,
4366 pool: pool.as_deref(),
4367 };
4368 attention::qwen_attention_nystrom(
4369 &self.ws.n1,
4370 wq,
4371 wk,
4372 wv,
4373 wo,
4374 &mut self.kv_cache.layers[li],
4375 &cfg,
4376 )
4377 }
4378 AttnKind::Full {
4379 wq,
4380 wk,
4381 wv,
4382 wo,
4383 q_norm,
4384 k_norm,
4385 output_gate,
4386 softplus_gate,
4387 bias,
4388 } => 'attn: {
4389 if graph_on
4392 && !*output_gate
4393 && softplus_gate.is_none()
4394 && self.attention_heads_per_layer.is_none()
4395 && bias.is_none()
4396 && task_mask.is_none()
4397 {
4398 let inv_freq_l = self.layer_inv_freq(li);
4399 let (nkv_l, hd_l, rd_l) = self.layer_geom(li);
4400 let gemma = self.norm_style == cortiq_core::NormStyle::Gemma;
4401 if let (Some((gm, qi)), Some((_, ki)), Some((_, vi)), Some((_, oi))) = (
4402 wq.mapped_q1(),
4403 wk.mapped_q1(),
4404 wv.mapped_q1(),
4405 wo.mapped_q1(),
4406 ) {
4407 let gm = gm.clone();
4408 let mut out = vec![0f32; hs];
4409 let cache = &self.kv_cache.layers[li];
4410 if crate::gpu::attn_dropin(
4411 &gm,
4412 self.graph_kv_id,
4413 li,
4414 &self.ws.n1,
4415 qi,
4416 ki,
4417 vi,
4418 oi,
4419 q_norm.as_deref(),
4420 k_norm.as_deref(),
4421 &inv_freq_l,
4422 nh,
4423 nkv_l,
4424 hd_l,
4425 rd_l,
4426 hs,
4427 position,
4428 self.kv_cache.max_seq_len,
4429 gemma,
4430 eps as f32,
4431 cache.k_heads(),
4432 cache.v_heads(),
4433 &mut out,
4434 ) {
4435 break 'attn out;
4436 }
4437 }
4438 }
4439 let masked = task_mask
4440 .map(|m| m.head_flags(li, self.num_heads).iter().any(|&a| !a))
4441 .unwrap_or(false);
4442 let f32_view = (wq.as_f32(), wk.as_f32(), wv.as_f32(), wo.as_f32());
4443 match (masked, f32_view) {
4444 (true, (Some(q), Some(k), Some(v), Some(o))) => {
4447 let active_heads = task_mask.unwrap().head_flags(li, self.num_heads);
4448 attention::multi_head_attention(
4449 &self.ws.n1,
4450 q,
4451 k,
4452 v,
4453 o,
4454 &mut self.kv_cache.layers[li],
4455 self.num_heads,
4456 self.num_kv_heads,
4457 self.head_dim,
4458 self.hidden_size,
4459 position,
4460 &active_heads,
4461 &self.inv_freq,
4462 )
4463 }
4464 (masked, _) => {
4465 if masked {
4466 tracing::warn!(
4467 "layer {li}: head mask on quantized weights not \
4468 supported yet — executing dense"
4469 );
4470 }
4471 let inv_freq_l = self.layer_inv_freq(li);
4472 let (nkv_l, hd_l, rd_l) = self.layer_geom(li);
4473 let cfg = QwenAttnCfg {
4474 num_heads: self.layer_num_heads(li),
4475 num_kv_heads: nkv_l,
4476 head_dim: hd_l,
4477 hidden_size: hs,
4478 position,
4479 inv_freq: &inv_freq_l,
4480 rotary_dim: rd_l,
4481 scale: self.attn_scale,
4482 softcap: self.attn_softcap,
4483 window: self.layer_window(li),
4484 v_norm: self.attn_v_norm,
4485 q_norm: q_norm.as_deref(),
4486 k_norm: k_norm.as_deref(),
4487 output_gate: *output_gate,
4488 softplus_gate: softplus_gate
4489 .as_ref()
4490 .map(|(gate, per_head)| (gate, *per_head)),
4491 rope_scale: self.layer_rope_scale(li),
4492 bias: bias
4493 .as_ref()
4494 .map(|(a, b, c)| (a.as_slice(), b.as_slice(), c.as_slice())),
4495 rms_eps: eps,
4496 norm_style: self.norm_style,
4497 pool: pool.as_deref(),
4498 };
4499 attention::qwen_attention(
4500 &self.ws.n1,
4501 wq,
4502 wk,
4503 wv,
4504 wo,
4505 &mut self.kv_cache.layers[li],
4506 &cfg,
4507 )
4508 }
4509 }
4510 }
4511 };
4512 let attn_out = match &self.weights.layers[self.phys_layer(li)].attn_out_norm {
4515 Some(w) => inference::rms_norm(&attn_out, w, self.rms_eps, self.norm_style),
4516 None => attn_out,
4517 };
4518 let lw = &self.weights.layers[self.phys_layer(li)];
4519 inference::add_rmsnorm_fused_into(
4520 &mut h,
4521 &attn_out,
4522 &lw.post_norm,
4523 self.rms_eps,
4524 self.norm_style,
4525 &mut self.ws.p1,
4526 );
4527 let mut attn_out = attn_out;
4528 attention::recycle_buf(&mut attn_out);
4529 let post_normed = &self.ws.p1;
4530
4531 let ffn_masked = task_mask
4532 .map(|m| m.ffn_active_count(li) < self.intermediate_size)
4533 .unwrap_or(false);
4534 let f32_ffn = match &lw.ffn {
4537 FfnKind::Dense(d) => (
4538 d.gate_proj.as_f32(),
4539 d.up_proj.as_f32(),
4540 d.down_proj.as_f32(),
4541 ),
4542 FfnKind::Moe(_) | FfnKind::DenseMoe(_) => (None, None, None),
4543 };
4544 let ffn_out = match (ffn_masked, f32_ffn) {
4545 (true, (Some(g), Some(u), Some(d))) => {
4546 let active = task_mask.unwrap().ffn_active_indices(li);
4547 inference::sparse_ffn_forward(
4548 post_normed,
4549 g,
4550 u,
4551 d,
4552 self.hidden_size,
4553 self.intermediate_size,
4554 &active,
4555 self.pool.as_deref(),
4556 )
4557 }
4558 (true, _) => match &lw.ffn {
4562 FfnKind::Dense(d) if d.down_proj.sparse_col_ok() => {
4563 let active = task_mask.unwrap().ffn_active_indices(li);
4564 sparse_ffn_quant(
4565 d,
4566 post_normed,
4567 &active,
4568 self.hidden_size,
4569 self.pool.as_deref(),
4570 )
4571 }
4572 FfnKind::Dense(d) => {
4577 let active = task_mask.unwrap().ffn_active_indices(li);
4578 let (gf, uf, df) = dequant_dense_f32(d);
4579 inference::sparse_ffn_forward(
4580 post_normed,
4581 &gf,
4582 &uf,
4583 &df,
4584 self.hidden_size,
4585 self.intermediate_size,
4586 &active,
4587 self.pool.as_deref(),
4588 )
4589 }
4590 FfnKind::Moe(m) => {
4591 let allowed = task_mask.and_then(|tm| tm.expert_flags(li, m.experts.len()));
4595 ffn_forward(
4596 &lw.ffn,
4597 post_normed,
4598 self.pool.as_deref(),
4599 allowed.as_deref(),
4600 )
4601 }
4602 FfnKind::DenseMoe(dm) => dense_moe_ffn(
4603 dm,
4604 post_normed,
4605 &h,
4606 self.rms_eps,
4607 self.norm_style,
4608 self.pool.as_deref(),
4609 ),
4610 },
4611 (false, _) => match &lw.ffn {
4612 FfnKind::DenseMoe(dm) => dense_moe_ffn(
4613 dm,
4614 post_normed,
4615 &h,
4616 self.rms_eps,
4617 self.norm_style,
4618 self.pool.as_deref(),
4619 ),
4620 _ => {
4621 let allowed = match (&lw.ffn, task_mask) {
4622 (FfnKind::Moe(m), Some(tm)) => tm.expert_flags(li, m.experts.len()),
4623 _ => None,
4624 };
4625 ffn_forward(
4626 &lw.ffn,
4627 post_normed,
4628 self.pool.as_deref(),
4629 allowed.as_deref(),
4630 )
4631 }
4632 },
4633 };
4634 let ffn_out = match &self.weights.layers[self.phys_layer(li)].ffn_out_norm {
4635 Some(w) => inference::rms_norm(&ffn_out, w, self.rms_eps, self.norm_style),
4636 None => ffn_out,
4637 };
4638 for (i, &f) in ffn_out.iter().enumerate() {
4639 h[i] += f;
4640 }
4641 let mut ffn_out = ffn_out;
4642 attention::recycle_buf(&mut ffn_out);
4643
4644 if let Some(sc) = self.weights.layers[self.phys_layer(li)].layer_scale {
4646 for v in h.iter_mut() {
4647 *v *= sc;
4648 }
4649 }
4650
4651 if self.is_loop_end(li) && li + 1 < self.num_layers {
4654 h = inference::rms_norm(
4655 &h,
4656 &self.weights.final_norm,
4657 self.rms_eps,
4658 self.norm_style,
4659 );
4660 }
4661
4662 if self.dyn_phi_layer == Some(li) {
4666 self.update_dyn_phi(&h);
4667 }
4668 }
4669 crate::gpu::set_layer(-1); if let Some(t) = t_race_cpu {
4671 crate::gpu::graph_race_record(false, t.elapsed());
4672 }
4673
4674 h
4675 }
4676
4677 fn update_dyn_phi(&mut self, h: &[f32]) {
4680 const A: f32 = 0.2;
4681 if self.dyn_phi_ema.len() != h.len() {
4682 self.dyn_phi_ema = vec![0.0; h.len()];
4683 self.dyn_phi_seen = 0;
4684 }
4685 if self.dyn_phi_seen == 0 {
4686 self.dyn_phi_ema.copy_from_slice(h);
4687 } else {
4688 for (e, &v) in self.dyn_phi_ema.iter_mut().zip(h) {
4689 *e = (1.0 - A) * *e + A * v;
4690 }
4691 }
4692 self.dyn_phi_seen += 1;
4693 }
4694
4695 pub fn dyn_phi(&self) -> &[f32] {
4697 &self.dyn_phi_ema
4698 }
4699
4700 pub fn set_dyn_phi_layer(&mut self, layer: Option<usize>) {
4702 self.dyn_phi_layer = layer;
4703 self.dyn_phi_ema.clear();
4704 self.dyn_phi_seen = 0;
4705 }
4706
4707 pub fn dynamic_skills(&self) -> Vec<(usize, String, usize)> {
4709 let Some(model) = &self.model else {
4710 return Vec::new();
4711 };
4712 model
4713 .header
4714 .skills
4715 .iter()
4716 .enumerate()
4717 .filter_map(|(i, sk)| {
4718 let ok = matches!(self.dyn_skill_layers.get(i), Some(Some(_)));
4719 let sel = sk.selection.as_ref()?;
4720 (ok).then(|| (i, sk.id.clone(), sel.phi_layer))
4721 })
4722 .collect()
4723 }
4724
4725 pub fn active_skill(&self) -> Option<usize> {
4727 self.dyn_active
4728 }
4729
4730 pub fn enable_dynamic_routing(&mut self) -> usize {
4735 use crate::swarm::{DynRouter, RoutableSkill};
4736 let Some(model) = self.model.clone() else {
4737 return 0;
4738 };
4739 if self.dyn_blend_loaded {
4742 tracing::warn!("dynamic routing unavailable on a blend-loaded pipeline");
4743 return 0;
4744 }
4745 if let Some(a) = self.dyn_active {
4749 if !matches!(self.dyn_skill_layers.get(a), Some(Some(_))) {
4750 tracing::warn!("loaded skill is not FFN-eligible — dynamic routing unavailable");
4751 return 0;
4752 }
4753 }
4754 let hidden = self.hidden_size;
4755 let mut skills = Vec::new();
4756 for (idx, id, _phi) in self.dynamic_skills() {
4757 if let Some(sel) = model.header.skills[idx].selection.as_ref() {
4758 if let Some(rs) = RoutableSkill::from_descriptor(idx, id, sel, hidden) {
4759 skills.push(rs);
4760 }
4761 }
4762 }
4763 if skills.is_empty() {
4764 return 0;
4765 }
4766 let phi = skills[0].phi_layer;
4768 if skills.iter().any(|s| s.phi_layer != phi) {
4769 tracing::warn!("routable skills disagree on phi_layer; using {phi}");
4770 }
4771 let n = skills.len();
4772 self.set_dyn_phi_layer(Some(phi));
4773 self.dyn_router = Some(DynRouter::new(skills));
4774 n
4775 }
4776
4777 pub fn route_switches(&self) -> Vec<(usize, Option<String>, Option<String>)> {
4779 self.dyn_router
4780 .as_ref()
4781 .map(|r| r.switches.clone())
4782 .unwrap_or_default()
4783 }
4784
4785 fn lm_head_forward(&self, hidden: &[f32]) -> Vec<f32> {
4788 let rows = self.weights.lm_head.rows();
4789 let mut logits = attention::take_buf(rows.min(self.vocab_size));
4790 self.weights
4791 .lm_head
4792 .matvec(hidden, &mut logits, self.pool.as_deref());
4793 logits.resize(self.vocab_size, 0.0);
4794 if let Some(m) = self.logit_multiplier {
4795 for l in logits.iter_mut() {
4796 *l *= m;
4797 }
4798 }
4799 if let Some(c) = self.final_softcap {
4800 for l in logits.iter_mut() {
4801 *l = c * (*l / c).tanh();
4802 }
4803 }
4804 logits
4805 }
4806
4807 pub fn prefill_next_logits(&mut self, ids: &[u32], task_mask: Option<&TaskMask>) -> Vec<f32> {
4812 self.kv_cache.clear();
4813 self.kv_history.clear();
4814 let mut hidden = vec![0.0f32; self.hidden_size];
4815 for (pos, &id) in ids.iter().enumerate() {
4816 let emb = self.embed_single(id);
4817 hidden = self.forward_layers(&emb, pos, task_mask);
4818 }
4819 inference::rms_norm_into(
4820 &hidden,
4821 &self.weights.final_norm,
4822 self.rms_eps,
4823 self.norm_style,
4824 &mut self.ws.n1,
4825 );
4826 self.lm_head_forward(&self.ws.n1)
4827 }
4828}
4829
4830pub fn create_test_pipeline(
4832 hidden_size: usize,
4833 intermediate_size: usize,
4834 num_heads: usize,
4835 num_kv_heads: usize,
4836 head_dim: usize,
4837 num_layers: usize,
4838 vocab_size: usize,
4839) -> Pipeline {
4840 let synth = |n: usize, salt: usize| -> Vec<f32> {
4843 (0..n)
4844 .map(|i| (((i * 31 + salt * 17 + 7) % 97) as f32 / 97.0 - 0.5) * 0.2)
4845 .collect()
4846 };
4847 let qt = |rows: usize, cols: usize, salt: usize| -> QTensor {
4848 QTensor::from_f32(synth(rows * cols, salt), rows, cols)
4849 };
4850 let layer_weights: Vec<LayerWeights> = (0..num_layers)
4851 .map(|li| LayerWeights {
4852 input_norm: vec![1.0; hidden_size],
4853 post_norm: vec![1.0; hidden_size],
4854 attn_out_norm: None,
4855 ffn_out_norm: None,
4856 layer_scale: None,
4857 ffn: FfnKind::Dense(DenseFfn {
4858 gate_proj: qt(intermediate_size, hidden_size, li * 10 + 5),
4859 up_proj: qt(intermediate_size, hidden_size, li * 10 + 6),
4860 down_proj: qt(hidden_size, intermediate_size, li * 10 + 7),
4861 act: Act::Silu,
4862 }),
4863 attn: AttnKind::Full {
4864 bias: None,
4865 wq: qt(num_heads * head_dim, hidden_size, li * 10 + 1),
4866 wk: qt(num_kv_heads * head_dim, hidden_size, li * 10 + 2),
4867 wv: qt(num_kv_heads * head_dim, hidden_size, li * 10 + 3),
4868 wo: qt(hidden_size, num_heads * head_dim, li * 10 + 4),
4869 q_norm: None,
4870 k_norm: None,
4871 output_gate: false,
4872 softplus_gate: None,
4873 },
4874 })
4875 .collect();
4876
4877 Pipeline::new(
4878 Tokenizer::byte_level(),
4879 PipelineWeights {
4880 embed_tokens: qt(vocab_size, hidden_size, 100),
4881 layers: layer_weights,
4882 lm_head: qt(vocab_size, hidden_size, 200),
4883 final_norm: vec![1.0; hidden_size],
4884 },
4885 hidden_size,
4886 intermediate_size,
4887 num_heads,
4888 num_kv_heads,
4889 head_dim,
4890 num_layers,
4891 num_layers, false, vocab_size,
4894 1e-6,
4895 10_000.0,
4896 NormStyle::Qwen,
4897 4096,
4898 SamplerConfig {
4899 seed: Some(42),
4900 ..Default::default()
4901 },
4902 )
4903}
4904
4905fn dense_ffn_batch(d: &DenseFfn, xs: &[f32], b: usize, pool: Option<&Pool>) -> Vec<f32> {
4908 let inter = d.gate_proj.rows();
4909 let hidden = d.down_proj.rows();
4910 if d.act == Act::Silu && b >= 32 && crate::gpu::enabled_here() && !crate::gpu::mm_killed() {
4916 if let (Some((model, w1)), Some((_, w3)), Some((_, w2))) = (
4917 d.gate_proj.mapped_q4t(),
4918 d.up_proj.mapped_q4t(),
4919 d.down_proj.mapped_q4t(),
4920 ) {
4921 let mut out = vec![0.0f32; b * hidden];
4922 if crate::gpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, &mut out) {
4923 return out;
4924 }
4925 }
4926 }
4927 let mut g = vec![0.0f32; b * inter];
4928 d.gate_proj.matmat(xs, b, &mut g, pool);
4929 let mut u = vec![0.0f32; b * inter];
4930 d.up_proj.matmat(xs, b, &mut u, pool);
4931 for i in 0..b * inter {
4932 g[i] = d.act.combine(g[i], u[i]);
4933 }
4934 let mut out = vec![0.0f32; b * hidden];
4935 d.down_proj.matmat(&g, b, &mut out, pool);
4936 out
4937}
4938
4939fn accumulate_act(m: &MoeFfn, xs: &[f32], b: usize) {
4944 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4945 static DUMP: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4946 let on = *ON.get_or_init(|| std::env::var("CMF_RMS_TRACE").is_ok());
4947 let dump = *DUMP.get_or_init(|| std::env::var("CMF_ACT_DUMP").is_ok());
4948 if (!on && !dump) || b == 0 {
4949 return;
4950 }
4951 let hidden = xs.len() / b;
4952 if on {
4953 let mut acc = m.act_sq.borrow_mut();
4954 if acc.len() < hidden {
4955 acc.resize(hidden, 0.0);
4956 }
4957 for t in 0..b {
4958 let row = &xs[t * hidden..(t + 1) * hidden];
4959 for (a, &v) in acc.iter_mut().zip(row) {
4960 *a += (v as f64) * (v as f64);
4961 }
4962 }
4963 }
4964 if dump {
4965 let cap: usize = std::env::var("CMF_ACT_DUMP_ROWS")
4968 .ok()
4969 .and_then(|v| v.parse().ok())
4970 .unwrap_or(4096);
4971 let mut rows = m.act_rows.borrow_mut();
4972 if rows.len() < cap * hidden {
4973 let take = b.min((cap * hidden - rows.len()) / hidden.max(1));
4974 rows.extend_from_slice(&xs[..take * hidden]);
4975 }
4976 }
4977}
4978
4979fn moe_ffn_batch(
4980 m: &MoeFfn,
4981 xs: &[f32],
4982 b: usize,
4983 hidden: usize,
4984 pool: Option<&Pool>,
4985 allowed: Option<&[bool]>,
4986) -> Vec<f32> {
4987 accumulate_act(m, xs, b);
4988 let ne = m.experts.len();
4989 let mut logits = vec![0.0f32; b * ne];
4990 m.router.matmat(xs, b, &mut logits, pool);
4991
4992 let mut assign: Vec<Vec<(usize, f32)>> = vec![Vec::new(); ne];
4995 {
4996 let mut st = m.stats.borrow_mut();
4997 if st.len() < ne {
4998 st.resize(ne, 0);
4999 }
5000 for bi in 0..b {
5001 let (idx, p, wsum) = moe_route(&logits[bi * ne..(bi + 1) * ne], m, allowed);
5002 for &e in &idx {
5003 st[e] += 1;
5004 assign[e].push((bi, p[e] / wsum));
5005 }
5006 }
5007 }
5008
5009 let mut out = vec![0.0f32; b * hidden];
5010 let cols = m.experts[0].gate_proj.cols();
5011 let mut run_expert = |d: &DenseFfn, list: &[(usize, f32)]| {
5012 let sb = list.len();
5013 let mut sub = vec![0.0f32; sb * cols];
5014 for (k, &(bi, _)) in list.iter().enumerate() {
5015 sub[k * cols..(k + 1) * cols].copy_from_slice(&xs[bi * cols..(bi + 1) * cols]);
5016 }
5017 let eo = dense_ffn_batch(d, &sub, sb, pool);
5018 for (k, &(bi, w)) in list.iter().enumerate() {
5019 for i in 0..hidden {
5020 out[bi * hidden + i] += w * eo[k * hidden + i];
5021 }
5022 }
5023 };
5024 for (e, a) in assign.iter().enumerate().take(ne) {
5025 if !a.is_empty() {
5026 run_expert(&m.experts[e], a);
5027 }
5028 }
5029 if let Some((se, gate)) = &m.shared {
5030 let all: Vec<(usize, f32)> = if let Some(gate) = gate {
5031 let mut gl = vec![0.0f32; b];
5032 gate.matmat(xs, b, &mut gl, pool);
5033 (0..b)
5034 .map(|bi| (bi, 1.0 / (1.0 + (-gl[bi]).exp())))
5035 .collect()
5036 } else {
5037 (0..b).map(|bi| (bi, 1.0)).collect()
5038 };
5039 run_expert(se, &all);
5040 }
5041 out
5042}
5043
5044thread_local! {
5045 static FFN_SCRATCH: std::cell::RefCell<[Vec<f32>; 4]> =
5049 const { std::cell::RefCell::new([Vec::new(), Vec::new(), Vec::new(), Vec::new()]) };
5050}
5051
5052fn dense_ffn(d: &DenseFfn, x: &[f32], pool: Option<&Pool>) -> Vec<f32> {
5054 if crate::gpu::enabled_here()
5065 && (d.gate_proj.rows() >= crate::gpu::min_rows() || d.gate_proj.is_q1())
5066 {
5067 let arm = if d.gate_proj.is_q1() && crate::gpu::q1_force() {
5068 crate::gpu::ProbeArm::Gpu
5069 } else {
5070 crate::gpu::probe_arm(crate::gpu::OpClass::Ffn)
5071 };
5072 match arm {
5073 crate::gpu::ProbeArm::Gpu => {
5074 let t0 = std::time::Instant::now();
5075 if let Some(out) = dense_ffn_gpu(d, x, pool) {
5076 crate::gpu::probe_record(crate::gpu::OpClass::Ffn, true, t0.elapsed());
5077 return out;
5078 }
5079 }
5080 crate::gpu::ProbeArm::CpuTimed => {
5081 let t0 = std::time::Instant::now();
5082 let out = crate::gpu::cpu_scope(|| dense_ffn_cpu(d, x, pool));
5083 crate::gpu::probe_record(crate::gpu::OpClass::Ffn, false, t0.elapsed());
5084 return out;
5085 }
5086 crate::gpu::ProbeArm::Cpu => {
5087 return crate::gpu::cpu_scope(|| dense_ffn_cpu(d, x, pool));
5088 }
5089 }
5090 }
5091 dense_ffn_cpu(d, x, pool)
5092}
5093
5094fn dense_ffn_cpu(d: &DenseFfn, x: &[f32], pool: Option<&Pool>) -> Vec<f32> {
5096 let inter = d.gate_proj.rows();
5097 FFN_SCRATCH.with(|s| {
5098 let mut s = s.borrow_mut();
5099 let [g, u, ..] = &mut *s;
5100 g.resize(inter, 0.0);
5101 if d.act == Act::Silu && QTensor::matvec_silu_mul(&d.gate_proj, &d.up_proj, x, g, pool) {
5104 } else {
5106 u.resize(inter, 0.0);
5107 QTensor::matvec_many([&d.gate_proj, &d.up_proj], x, [g, u], pool);
5109 for i in 0..inter {
5110 g[i] = d.act.combine(g[i], u[i]);
5111 }
5112 }
5113 FFN_PROBE.with(|pr| {
5116 if let Some(acc) = pr.borrow_mut().as_mut() {
5117 let li = crate::gpu::cur_layer();
5118 if li >= 0 {
5119 if let Some(row) = acc.get_mut(li as usize) {
5120 for (a, &v) in row.iter_mut().zip(g.iter()) {
5121 *a += (v as f64).abs();
5122 }
5123 }
5124 }
5125 }
5126 });
5127 let mut out = attention::take_buf(d.down_proj.rows());
5128 d.down_proj.matvec(g, &mut out, pool);
5129 out
5130 })
5131}
5132
5133thread_local! {
5134 static FFN_PROBE: std::cell::RefCell<Option<Vec<Vec<f64>>>> =
5137 const { std::cell::RefCell::new(None) };
5138}
5139
5140fn dense_ffn_gpu(d: &DenseFfn, x: &[f32], _pool: Option<&Pool>) -> Option<Vec<f32>> {
5146 if d.act != Act::Silu {
5148 return None;
5149 }
5150 if d.gate_proj.rows() < crate::gpu::min_rows() && !d.gate_proj.is_q1() {
5153 return None;
5154 }
5155 let mut jobs: Vec<crate::gpu::MoeJob> = Vec::with_capacity(1);
5156 let mut model_ref = None;
5157 moe_push_job(d, x, 1.0, &mut jobs, &mut model_ref)?;
5158 let model = model_ref?;
5159 let hidden = jobs[0].down.1;
5160 let mut out = attention::take_buf(hidden);
5161 if crate::gpu::moe_block(&model, &jobs, &mut out) {
5162 Some(out)
5163 } else {
5164 let mut out = out;
5165 attention::recycle_buf(&mut out);
5166 None
5167 }
5168}
5169
5170#[allow(clippy::type_complexity)]
5175#[allow(clippy::type_complexity)]
5176pub(crate) fn moe_parts(
5177 t: &QTensor,
5178) -> Option<(
5179 &std::sync::Arc<cortiq_core::CmfModel>,
5180 usize,
5181 usize,
5182 usize,
5183 &[f32],
5184 &[f32],
5185 bool,
5186 bool,
5187)> {
5188 match t {
5189 QTensor::Mapped {
5190 model,
5191 idx,
5192 dtype: dt @ (cortiq_core::TensorDtype::Q8_2f | cortiq_core::TensorDtype::Q8Row),
5193 rows,
5194 cols,
5195 row_scale,
5196 col_field,
5197 ..
5198 } if (*dt == cortiq_core::TensorDtype::Q8Row) || !col_field.is_empty() => Some((
5199 model, *idx, *rows, *cols, row_scale, col_field, false, false,
5200 )),
5201 QTensor::Mapped {
5203 model,
5204 idx,
5205 dtype: cortiq_core::TensorDtype::Q1,
5206 rows,
5207 cols,
5208 ..
5209 } => Some((model, *idx, *rows, *cols, &[][..], &[][..], true, false)),
5210 QTensor::Mapped {
5212 model,
5213 idx,
5214 dtype: cortiq_core::TensorDtype::Q4Tiled,
5215 rows,
5216 cols,
5217 ..
5218 } => Some((model, *idx, *rows, *cols, &[][..], &[][..], false, true)),
5219 QTensor::Mapped {
5221 model,
5222 idx,
5223 dtype: cortiq_core::TensorDtype::Q4TiledP,
5224 rows,
5225 cols,
5226 ..
5227 } => Some((model, *idx, *rows, *cols, &[][..], &[][..], false, true)),
5228 _ => None,
5229 }
5230}
5231
5232pub(crate) fn moe_push_job_parts<'a>(
5236 gate: &'a QTensor,
5237 up: &'a QTensor,
5238 down: &'a QTensor,
5239 x: &[f32],
5240 w: f32,
5241 swiglu_limit: f32,
5242 jobs: &mut Vec<crate::gpu::MoeJob<'a>>,
5243 model_ref: &mut Option<std::sync::Arc<cortiq_core::CmfModel>>,
5244) -> Option<()> {
5245 use crate::qtensor::prescale;
5246 let (gm, gi, gr, gc, grs, gcf, gq1, gq4) = moe_parts(gate)?;
5247 let (_, ui, ur, uc, urs, ucf, uq1, uq4) = moe_parts(up)?;
5248 let (_, di, dr, dc, drs, dcf, dq1, dq4) = moe_parts(down)?;
5249 if gq1 != uq1 || uq1 != dq1 || gq4 != uq4 || uq4 != dq4 {
5250 return None; }
5252 model_ref.get_or_insert_with(|| gm.clone());
5253 let dt = |cf: &[f32]| {
5254 if cf.is_empty() {
5255 cortiq_core::TensorDtype::Q8Row
5256 } else {
5257 cortiq_core::TensorDtype::Q8_2f
5258 }
5259 };
5260 jobs.push(crate::gpu::MoeJob {
5261 gate: (gi, gr, gc, grs),
5262 up: (ui, ur, uc, urs),
5263 down: (di, dr, dc, drs),
5264 xs_gate: prescale(x, gcf, dt(gcf)).into_owned(),
5265 xs_up: prescale(x, ucf, dt(ucf)).into_owned(),
5266 down_col: dcf,
5267 w,
5268 q1: gq1,
5269 q4t: gq4 && gate.mapped_q4tp().is_none(),
5270 q4tp: gq4 && gate.mapped_q4tp().is_some(),
5271 swiglu_limit,
5272 });
5273 Some(())
5274}
5275
5276fn moe_push_job<'a>(
5278 d: &'a DenseFfn,
5279 x: &[f32],
5280 w: f32,
5281 jobs: &mut Vec<crate::gpu::MoeJob<'a>>,
5282 model_ref: &mut Option<std::sync::Arc<cortiq_core::CmfModel>>,
5283) -> Option<()> {
5284 use crate::qtensor::prescale;
5285 if d.act != Act::Silu {
5286 return None; }
5288 let (gm, gi, gr, gc, grs, gcf, gq1, gq4) = moe_parts(&d.gate_proj)?;
5289 let (_, ui, ur, uc, urs, ucf, uq1, uq4) = moe_parts(&d.up_proj)?;
5290 let (_, di, dr, dc, drs, dcf, dq1, dq4) = moe_parts(&d.down_proj)?;
5291 if gq1 != uq1 || uq1 != dq1 || gq4 != uq4 || uq4 != dq4 {
5292 return None; }
5294 model_ref.get_or_insert_with(|| gm.clone());
5295 let gdt = if gcf.is_empty() {
5296 cortiq_core::TensorDtype::Q8Row
5297 } else {
5298 cortiq_core::TensorDtype::Q8_2f
5299 };
5300 let udt = if ucf.is_empty() {
5301 cortiq_core::TensorDtype::Q8Row
5302 } else {
5303 cortiq_core::TensorDtype::Q8_2f
5304 };
5305 jobs.push(crate::gpu::MoeJob {
5306 gate: (gi, gr, gc, grs),
5307 up: (ui, ur, uc, urs),
5308 down: (di, dr, dc, drs),
5309 xs_gate: prescale(x, gcf, gdt).into_owned(),
5310 xs_up: prescale(x, ucf, udt).into_owned(),
5311 down_col: dcf,
5312 w,
5313 q1: gq1,
5314 q4t: gq4 && d.gate_proj.mapped_q4tp().is_none(),
5315 q4tp: gq4 && d.gate_proj.mapped_q4tp().is_some(),
5316 swiglu_limit: 0.0,
5317 });
5318 Some(())
5319}
5320
5321fn sparse_ffn_quant(
5328 d: &DenseFfn,
5329 x: &[f32],
5330 active: &[u16],
5331 hidden: usize,
5332 pool: Option<&Pool>,
5333) -> Vec<f32> {
5334 let n = active.len();
5335 let inter = d.gate_proj.rows();
5336 let mut act = vec![0.0f32; n];
5337 let need_scratch = !(d.gate_proj.sparse_col_ok() && d.up_proj.sparse_col_ok());
5340 let compute = |ai: usize| -> f32 {
5341 let idx = active[ai] as usize;
5342 if idx >= inter {
5343 return 0.0; }
5345 let mut s = if need_scratch {
5346 vec![0.0f32; hidden]
5347 } else {
5348 Vec::new()
5349 };
5350 let gate = d.gate_proj.row_dot(idx, x, &mut s);
5351 let up = d.up_proj.row_dot(idx, x, &mut s);
5352 d.act.combine(gate, up)
5353 };
5354 match pool {
5355 Some(p) if n >= 256 => {
5356 let ptr = SendMut(act.as_mut_ptr());
5357 p.run(&|widx, nw| {
5358 let chunk = n.div_ceil(nw);
5359 let (s, e) = (widx * chunk, ((widx + 1) * chunk).min(n));
5360 for ai in s..e {
5361 unsafe { *ptr.at(ai) = compute(ai) };
5362 }
5363 });
5364 }
5365 _ => {
5366 for (ai, a) in act.iter_mut().enumerate() {
5367 *a = compute(ai);
5368 }
5369 }
5370 }
5371 let mut out = vec![0.0f32; hidden];
5373 for (ai, &idx) in active.iter().enumerate() {
5374 let w = act[ai];
5375 if w.abs() >= 1e-12 && (idx as usize) < inter {
5376 d.down_proj.add_col_scaled(idx as usize, w, &mut out);
5377 }
5378 }
5379 out
5380}
5381
5382#[doc(hidden)]
5384pub fn sparse_ffn_quant_for_test(
5385 d: &DenseFfn,
5386 x: &[f32],
5387 active: &[u16],
5388 hidden: usize,
5389) -> Vec<f32> {
5390 sparse_ffn_quant(d, x, active, hidden, None)
5391}
5392
5393fn dequant_dense_f32(d: &DenseFfn) -> (Vec<f32>, Vec<f32>, Vec<f32>) {
5397 let deq = |t: &QTensor| -> Vec<f32> {
5398 let (rows, cols) = (t.rows(), t.cols());
5399 let mut out = vec![0.0f32; rows * cols];
5400 for r in 0..rows {
5401 t.row_f32(r, &mut out[r * cols..(r + 1) * cols]);
5402 }
5403 out
5404 };
5405 (deq(&d.gate_proj), deq(&d.up_proj), deq(&d.down_proj))
5406}
5407
5408struct SendMut(*mut f32);
5410unsafe impl Send for SendMut {}
5411unsafe impl Sync for SendMut {}
5412impl SendMut {
5413 #[inline]
5414 #[allow(clippy::mut_from_ref)]
5417 unsafe fn at(&self, i: usize) -> &mut f32 {
5418 unsafe { &mut *self.0.add(i) }
5419 }
5420}
5421
5422fn moe_route(logits: &[f32], m: &MoeFfn, allowed: Option<&[bool]>) -> (Vec<usize>, Vec<f32>, f32) {
5432 let ne = logits.len();
5433 let p: Vec<f32> = if m.router_sigmoid {
5434 logits.iter().map(|&l| 1.0 / (1.0 + (-l).exp())).collect()
5435 } else {
5436 let mx = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
5437 let mut e: Vec<f32> = logits.iter().map(|&l| (l - mx).exp()).collect();
5438 let s: f32 = e.iter().sum();
5439 for v in &mut e {
5440 *v /= s;
5441 }
5442 e
5443 };
5444 let admit = |e: usize| {
5450 m.mask.as_ref().is_none_or(|mk| mk[e])
5451 && allowed.is_none_or(|a| a.get(e).copied().unwrap_or(false))
5452 };
5453 let mut idx: Vec<usize> = (0..ne).filter(|&e| admit(e)).collect();
5454 match &m.expert_bias {
5456 Some(b) => idx.sort_unstable_by(|&x, &y| {
5457 (p[y] + b[y])
5458 .partial_cmp(&(p[x] + b[x]))
5459 .unwrap()
5460 .then(x.cmp(&y))
5461 }),
5462 None => idx.sort_unstable_by(|&x, &y| p[y].partial_cmp(&p[x]).unwrap().then(x.cmp(&y))),
5463 }
5464 idx.truncate(m.top_k);
5465 if let Some(tau) = m.route_tau {
5469 let total: f32 = idx.iter().map(|&e| p[e]).sum();
5470 if total > 0.0 {
5471 let mut acc = 0.0f32;
5472 let mut keep = idx.len();
5473 for (i, &e) in idx.iter().enumerate() {
5474 acc += p[e];
5475 if acc >= tau * total {
5476 keep = i + 1;
5477 break;
5478 }
5479 }
5480 idx.truncate(keep);
5481 }
5482 }
5483 let wsum: f32 = if m.norm_topk_prob {
5484 let s: f32 = idx.iter().map(|&e| p[e]).sum();
5485 (if m.router_sigmoid { s + 1e-6 } else { s }) / m.routed_scaling
5488 } else {
5489 1.0 / m.routed_scaling
5490 };
5491 (idx, p, wsum)
5492}
5493
5494fn moe_ffn(m: &MoeFfn, x: &[f32], pool: Option<&Pool>, allowed: Option<&[bool]>) -> Vec<f32> {
5497 accumulate_act(m, x, 1);
5498 let ne = m.experts.len();
5499 let mut logits = vec![0.0f32; ne];
5500 m.router.matvec(x, &mut logits, pool);
5501 let (idx, p, wsum) = moe_route(&logits, m, allowed);
5502 {
5503 let mut st = m.stats.borrow_mut();
5504 if st.len() < ne {
5505 st.resize(ne, 0);
5506 }
5507 for &e in &idx {
5508 st[e] += 1;
5509 }
5510 }
5511 if crate::gpu::enabled_here() {
5516 match crate::gpu::probe_arm(crate::gpu::OpClass::Ffn) {
5517 crate::gpu::ProbeArm::Gpu => {
5518 let t0 = std::time::Instant::now();
5519 if let Some(out) = moe_ffn_gpu(m, x, &idx, &p, wsum, pool) {
5520 crate::gpu::probe_record(crate::gpu::OpClass::Ffn, true, t0.elapsed());
5521 return out;
5522 }
5523 }
5524 crate::gpu::ProbeArm::CpuTimed => {
5525 let t0 = std::time::Instant::now();
5526 let out = crate::gpu::cpu_scope(|| moe_ffn_cpu(m, x, &idx, &p, wsum, pool));
5527 crate::gpu::probe_record(crate::gpu::OpClass::Ffn, false, t0.elapsed());
5528 return out;
5529 }
5530 crate::gpu::ProbeArm::Cpu => {
5531 return crate::gpu::cpu_scope(|| moe_ffn_cpu(m, x, &idx, &p, wsum, pool));
5532 }
5533 }
5534 }
5535 moe_ffn_cpu(m, x, &idx, &p, wsum, pool)
5536}
5537
5538fn graph_note(built: bool) {
5542 use std::sync::atomic::{AtomicBool, Ordering};
5543 static SAID: AtomicBool = AtomicBool::new(false);
5544 if !SAID.swap(true, Ordering::Relaxed) {
5545 if built {
5546 tracing::info!("wgpu whole-token graph: ACTIVE");
5547 } else {
5548 tracing::warn!("wgpu whole-token graph refused — per-op path");
5549 }
5550 }
5551}
5552
5553fn moe_batch_enabled() -> bool {
5556 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5557 *ON.get_or_init(|| std::env::var("CMF_MOE_BATCH").as_deref() != Ok("0"))
5558}
5559
5560fn moe_ffn_cpu_batched(
5566 m: &MoeFfn,
5567 x: &[f32],
5568 idx: &[usize],
5569 p: &[f32],
5570 wsum: f32,
5571 pool: Option<&Pool>,
5572) -> Option<Vec<f32>> {
5573 if idx.is_empty() || !moe_batch_enabled() {
5574 return None;
5575 }
5576 if FFN_PROBE.with(|pr| pr.borrow().is_some()) {
5580 return None;
5581 }
5582 let n = idx.len() + usize::from(m.shared.is_some());
5583 let mut pairs = Vec::with_capacity(n);
5584 let mut downs = Vec::with_capacity(n);
5585 let mut ws = Vec::with_capacity(n);
5586 for &e in idx {
5587 let d = &m.experts[e];
5588 if d.act != Act::Silu {
5589 return None;
5590 }
5591 pairs.push((&d.gate_proj, &d.up_proj));
5592 downs.push(&d.down_proj);
5593 ws.push(p[e] / wsum * m.per_expert_scale.as_ref().map_or(1.0, |v| v[e]));
5594 }
5595 if let Some((se, gate)) = &m.shared {
5598 if se.act != Act::Silu {
5599 return None;
5600 }
5601 let g = gate.as_ref().map_or(1.0, |gate| {
5602 let mut gl = [0.0f32; 1];
5603 gate.matvec(x, &mut gl, pool);
5604 1.0 / (1.0 + (-gl[0]).exp())
5605 });
5606 pairs.push((&se.gate_proj, &se.up_proj));
5607 downs.push(&se.down_proj);
5608 ws.push(g);
5609 }
5610 let inter = pairs[0].0.rows();
5611 let mut gs: Vec<Vec<f32>> = (0..pairs.len()).map(|_| vec![0f32; inter]).collect();
5612 if !QTensor::moe_gate_up_many(&pairs, x, &mut gs, pool) {
5613 return None;
5614 }
5615 let mut out = attention::take_buf(x.len());
5616 if !QTensor::moe_down_many(&downs, &gs, &ws, &mut out, pool) {
5617 attention::recycle_buf(&mut out);
5618 return None;
5619 }
5620 Some(out)
5621}
5622
5623fn moe_ffn_cpu(
5625 m: &MoeFfn,
5626 x: &[f32],
5627 idx: &[usize],
5628 p: &[f32],
5629 wsum: f32,
5630 pool: Option<&Pool>,
5631) -> Vec<f32> {
5632 if let Some(out) = moe_ffn_cpu_batched(m, x, idx, p, wsum, pool) {
5633 return out;
5634 }
5635 let mut out = attention::take_buf(x.len());
5636 for &e in idx {
5637 let mut eo = dense_ffn(&m.experts[e], x, pool);
5638 let w = p[e] / wsum * m.per_expert_scale.as_ref().map_or(1.0, |v| v[e]);
5639 for i in 0..out.len() {
5640 out[i] += w * eo[i];
5641 }
5642 attention::recycle_buf(&mut eo);
5643 }
5644 if let Some((se, gate)) = &m.shared {
5645 let mut so = dense_ffn(se, x, pool);
5646 let g = gate.as_ref().map_or(1.0, |gate| {
5647 let mut gl = [0.0f32; 1];
5648 gate.matvec(x, &mut gl, pool);
5649 1.0 / (1.0 + (-gl[0]).exp())
5650 });
5651 for i in 0..out.len() {
5652 out[i] += g * so[i];
5653 }
5654 attention::recycle_buf(&mut so);
5655 }
5656 out
5657}
5658
5659#[allow(clippy::too_many_arguments)]
5667fn mla_attention(
5668 w: &MlaWeights,
5669 normed: &[f32],
5670 cache: &mut crate::kv_cache::LayerKvCache,
5671 position: usize,
5672 inv_freq: &[f32],
5673 rope_scale: f32,
5674 eps: f64,
5675 pool: Option<&Pool>,
5676) -> Vec<f32> {
5677 let (nh, dr, dn, dv, lora) = (w.nh, w.qk_rope, w.qk_nope, w.v_dim, w.lora);
5678 let hd = dr + dn;
5679 let mut q = vec![0.0f32; nh * hd];
5680 match (&w.q_a, &w.q_a_norm) {
5681 (Some(qa), Some(qn)) => {
5682 let mut t = vec![0.0f32; qa.rows()];
5683 qa.matvec(normed, &mut t, pool);
5684 let tn = inference::rms_norm(&t, qn, eps, NormStyle::Qwen);
5685 w.q_proj.matvec(&tn, &mut q, pool);
5686 }
5687 _ => w.q_proj.matvec(normed, &mut q, pool),
5688 }
5689 let mut ca = vec![0.0f32; lora + dr];
5690 w.kv_a.matvec(normed, &mut ca, pool);
5691 let (c_lat, k_rope) = ca.split_at_mut(lora);
5692 let latn = inference::rms_norm(c_lat, &w.kv_a_norm, eps, NormStyle::Qwen);
5693 let mut kvb = vec![0.0f32; nh * (dn + dv)];
5694 w.kv_b.matvec(&latn, &mut kvb, pool);
5695 if !w.nope {
5696 attention::rope_rotate_scaled(k_rope, position, inv_freq, rope_scale);
5697 }
5698 for h in 0..nh {
5699 if !w.nope {
5700 attention::rope_rotate_scaled(
5701 &mut q[h * hd..h * hd + dr],
5702 position,
5703 inv_freq,
5704 rope_scale,
5705 );
5706 }
5707 }
5708 let mut k = vec![0.0f32; nh * hd];
5709 let mut v = vec![0.0f32; nh * hd];
5710 for h in 0..nh {
5711 k[h * hd..h * hd + dr].copy_from_slice(k_rope);
5712 k[h * hd + dr..(h + 1) * hd].copy_from_slice(&kvb[h * (dn + dv)..h * (dn + dv) + dn]);
5713 v[h * hd..h * hd + dv].copy_from_slice(&kvb[h * (dn + dv) + dn..(h + 1) * (dn + dv)]);
5714 }
5715 cache.append(&k, &v, &vec![true; nh]);
5716 let (ao, mut imp) = attention::attend_all_heads(&q, cache, nh, 1, hd, w.scale, None, 0.0);
5717 attention::recycle_buf(&mut imp);
5718 let mut ov = vec![0.0f32; nh * dv];
5719 for h in 0..nh {
5720 ov[h * dv..(h + 1) * dv].copy_from_slice(&ao[h * hd..h * hd + dv]);
5721 }
5722 let mut out = vec![0.0f32; w.o_proj.rows()];
5723 w.o_proj.matvec(&ov, &mut out, pool);
5724 out
5725}
5726
5727fn dense_moe_ffn(
5734 dm: &DenseMoeFfn,
5735 x_normed: &[f32],
5736 h_raw: &[f32],
5737 eps: f64,
5738 norm_style: NormStyle,
5739 pool: Option<&Pool>,
5740) -> Vec<f32> {
5741 let mut d = dense_ffn(&dm.dense, x_normed, pool);
5742 d = inference::rms_norm(&d, &dm.post_norm_1, eps, norm_style);
5743 let m = &dm.moe;
5744 let ne = m.experts.len();
5745 let mut logits = vec![0.0f32; ne];
5746 if m.router_input_norm {
5747 let ss: f32 = h_raw.iter().map(|v| v * v).sum::<f32>() / h_raw.len() as f32;
5748 let inv = 1.0 / (ss + eps as f32).sqrt();
5749 let xr: Vec<f32> = h_raw.iter().map(|v| v * inv).collect();
5750 m.router.matvec(&xr, &mut logits, pool);
5751 } else {
5752 m.router.matvec(h_raw, &mut logits, pool);
5753 }
5754 let (idx, p, wsum) = moe_route(&logits, m, None);
5755 {
5756 let mut st = m.stats.borrow_mut();
5757 if st.len() < ne {
5758 st.resize(ne, 0);
5759 }
5760 for &e in &idx {
5761 st[e] += 1;
5762 }
5763 }
5764 let x2 = inference::rms_norm(h_raw, &dm.pre_norm_2, eps, norm_style);
5765 let mo = moe_ffn_cpu(m, &x2, &idx, &p, wsum, pool);
5766 let mo = inference::rms_norm(&mo, &dm.post_norm_2, eps, norm_style);
5767 for (di, mi) in d.iter_mut().zip(&mo) {
5768 *di += mi;
5769 }
5770 d
5771}
5772
5773fn moe_gpu_refused(why: &'static str) {
5780 use std::sync::atomic::{AtomicBool, Ordering};
5781 static SAID: AtomicBool = AtomicBool::new(false);
5782 if !SAID.swap(true, Ordering::Relaxed) {
5783 tracing::warn!("MoE GPU block refused ({why}) — experts run on the CPU");
5784 }
5785}
5786
5787fn moe_ffn_gpu(
5788 m: &MoeFfn,
5789 x: &[f32],
5790 idx: &[usize],
5791 p: &[f32],
5792 wsum: f32,
5793 pool: Option<&Pool>,
5794) -> Option<Vec<f32>> {
5795 use crate::gpu::MoeJob;
5796
5797 let mut jobs: Vec<MoeJob> = Vec::with_capacity(idx.len() + 1);
5798 let mut model_ref = None;
5799 for &e in idx {
5800 if moe_push_job(&m.experts[e], x, p[e] / wsum, &mut jobs, &mut model_ref).is_none() {
5801 moe_gpu_refused("push_job(expert)");
5802 return None;
5803 }
5804 }
5805 if let Some((se, gate)) = &m.shared {
5806 let g = gate.as_ref().map_or(1.0, |gate| {
5807 let mut gl = [0.0f32; 1];
5808 gate.matvec(x, &mut gl, pool);
5809 1.0 / (1.0 + (-gl[0]).exp())
5810 });
5811 if moe_push_job(se, x, g, &mut jobs, &mut model_ref).is_none() {
5812 moe_gpu_refused("push_job(shared)");
5813 return None;
5814 }
5815 }
5816 let Some(model) = model_ref else {
5817 moe_gpu_refused("no model_ref");
5818 return None;
5819 };
5820 let hidden = jobs[0].down.1;
5821 let mut out = vec![0.0f32; hidden];
5822 if crate::gpu::moe_block(&model, &jobs, &mut out) {
5823 Some(out)
5824 } else {
5825 moe_gpu_refused("gpu::moe_block");
5826 None
5827 }
5828}
5829
5830fn ffn_forward(
5832 ffn: &FfnKind,
5833 x: &[f32],
5834 pool: Option<&Pool>,
5835 experts_allowed: Option<&[bool]>,
5836) -> Vec<f32> {
5837 match ffn {
5838 FfnKind::Dense(d) => dense_ffn(d, x, pool),
5839 FfnKind::Moe(m) => moe_ffn(m, x, pool, experts_allowed),
5840 FfnKind::DenseMoe(_) => unreachable!("DenseMoe dispatches via dense_moe_ffn"),
5844 }
5845}
5846
5847fn ffn_forward_pair(
5851 ffn: &FfnKind,
5852 x1: &[f32],
5853 x2: &[f32],
5854 pool: Option<&Pool>,
5855 experts_allowed: Option<&[bool]>,
5856) -> (Vec<f32>, Vec<f32>) {
5857 let d = match ffn {
5858 FfnKind::Dense(d) => d,
5859 FfnKind::Moe(m) => {
5860 return (
5861 moe_ffn(m, x1, pool, experts_allowed),
5862 moe_ffn(m, x2, pool, experts_allowed),
5863 );
5864 }
5865 FfnKind::DenseMoe(_) => unreachable!("DenseMoe dispatches via dense_moe_ffn"),
5866 };
5867 let inter = d.gate_proj.rows();
5868 FFN_SCRATCH.with(|s| {
5869 let mut s = s.borrow_mut();
5870 let [g1, g2, u1, u2] = &mut *s;
5871 g1.resize(inter, 0.0);
5872 g2.resize(inter, 0.0);
5873 u1.resize(inter, 0.0);
5874 u2.resize(inter, 0.0);
5875 QTensor::matvec2_many(
5878 [&d.gate_proj, &d.up_proj],
5879 x1,
5880 x2,
5881 [g1.as_mut_slice(), u1.as_mut_slice()],
5882 [g2.as_mut_slice(), u2.as_mut_slice()],
5883 pool,
5884 );
5885 for i in 0..inter {
5886 g1[i] = d.act.combine(g1[i], u1[i]);
5887 g2[i] = d.act.combine(g2[i], u2[i]);
5888 }
5889 let mut o1 = attention::take_buf(d.down_proj.rows());
5890 let mut o2 = attention::take_buf(d.down_proj.rows());
5891 d.down_proj.matvec2(g1, g2, &mut o1, &mut o2, pool);
5892 (o1, o2)
5893 })
5894}
5895
5896#[cfg(test)]
5897mod tests {
5898
5899 #[test]
5900 fn cancel_flag_stops_generation() {
5901 let mut p = create_test_pipeline(16, 32, 2, 2, 8, 2, 32);
5902 p.cancel.store(true, std::sync::atomic::Ordering::Relaxed);
5905 let r = p.generate_from_ids(&[1, 2, 3], 8, None, None).unwrap();
5906 assert_eq!(r.finish_reason, "cancelled");
5907 assert!(
5908 r.token_ids.is_empty(),
5909 "no tokens after cancel: {:?}",
5910 r.token_ids
5911 );
5912 let r2 = p.generate_from_ids(&[1, 2, 3], 4, None, None).unwrap();
5914 assert_ne!(r2.finish_reason, "cancelled");
5915 }
5916 use super::*;
5917
5918 #[test]
5924 fn sparse_ffn_quant_equals_dense_with_inactive_zeroed() {
5925 let (hidden, inter) = (16usize, 40usize);
5926 let synth = |n: usize, salt: usize| -> Vec<f32> {
5927 (0..n)
5928 .map(|i| (((i * 37 + salt * 11 + 3) % 101) as f32 / 101.0 - 0.5) * 0.4)
5929 .collect()
5930 };
5931 let d = DenseFfn {
5932 gate_proj: QTensor::from_f32(synth(inter * hidden, 1), inter, hidden),
5933 up_proj: QTensor::from_f32(synth(inter * hidden, 2), inter, hidden),
5934 down_proj: QTensor::from_f32(synth(hidden * inter, 3), hidden, inter),
5935 act: Act::Silu,
5936 };
5937 let x = synth(hidden, 9);
5938 let active: Vec<u16> = (0..inter as u16).filter(|i| i % 3 == 0).collect();
5940
5941 let sparse = sparse_ffn_quant(&d, &x, &active, hidden, None);
5942
5943 let mut g = vec![0.0f32; inter];
5945 d.gate_proj.matvec(&x, &mut g, None);
5946 let mut u = vec![0.0f32; inter];
5947 d.up_proj.matvec(&x, &mut u, None);
5948 let act_set: std::collections::HashSet<u16> = active.iter().copied().collect();
5949 for i in 0..inter {
5950 g[i] = if act_set.contains(&(i as u16)) {
5951 inference::silu(g[i]) * u[i]
5952 } else {
5953 0.0
5954 };
5955 }
5956 let mut reference = vec![0.0f32; hidden];
5957 d.down_proj.matvec(&g, &mut reference, None);
5958
5959 let max_d = sparse
5960 .iter()
5961 .zip(&reference)
5962 .map(|(a, b)| (a - b).abs())
5963 .fold(0.0f32, f32::max);
5964 assert!(max_d < 1e-5, "sparse != dense-zeroed: max|Δ| = {max_d}");
5965 }
5966
5967 fn attach_test_mtp(p: &mut Pipeline) {
5969 let (h, inter, heads, kv, hd) = (
5970 p.hidden_size,
5971 p.intermediate_size,
5972 p.num_heads,
5973 p.num_kv_heads,
5974 p.head_dim,
5975 );
5976 let synth = |n: usize, salt: usize| -> Vec<f32> {
5977 (0..n)
5978 .map(|i| (((i * 29 + salt * 23 + 5) % 101) as f32 / 101.0 - 0.5) * 0.2)
5979 .collect()
5980 };
5981 let qt = |rows: usize, cols: usize, salt: usize| -> QTensor {
5982 QTensor::from_f32(synth(rows * cols, salt), rows, cols)
5983 };
5984 p.mtp = Some(MtpModule {
5985 enorm: vec![1.0; h],
5986 hnorm: vec![1.0; h],
5987 eh_proj: qt(h, 2 * h, 301),
5988 layer: LayerWeights {
5989 input_norm: vec![1.0; h],
5990 post_norm: vec![1.0; h],
5991 attn_out_norm: None,
5992 ffn_out_norm: None,
5993 layer_scale: None,
5994 ffn: FfnKind::Dense(DenseFfn {
5995 gate_proj: qt(inter, h, 315),
5996 up_proj: qt(inter, h, 316),
5997 down_proj: qt(h, inter, 317),
5998 act: Act::Silu,
5999 }),
6000 attn: AttnKind::Full {
6001 bias: None,
6002 wq: qt(heads * hd, h, 311),
6003 wk: qt(kv * hd, h, 312),
6004 wv: qt(kv * hd, h, 313),
6005 wo: qt(h, heads * hd, 314),
6006 q_norm: None,
6007 k_norm: None,
6008 output_gate: false,
6009 softplus_gate: None,
6010 },
6011 },
6012 final_norm: vec![1.0; h],
6013 kv: crate::kv_cache::LayerKvCache::new(kv, hd),
6014 });
6015 }
6016
6017 #[test]
6018 fn speculative_equals_vanilla_greedy() {
6019 unsafe { std::env::set_var("CMF_GPU_WGPU_GRAPH", "0") };
6023 let run = |spec: bool| {
6024 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 2, 260);
6025 p.sampler_config.temperature = 0.0;
6026 attach_test_mtp(&mut p);
6027 p.speculative = spec;
6028 let r = p.generate("abcdef", 12, None, None).unwrap();
6029 (r.token_ids, r.mtp_drafted, r.mtp_accepted)
6030 };
6031 let (vanilla, d0, _) = run(false);
6032 let (spec, d1, a1) = run(true);
6033 assert_eq!(d0, 0, "vanilla path must not draft");
6034 assert!(d1 > 0, "speculative path must draft");
6035 assert_eq!(
6036 vanilla, spec,
6037 "speculative must reproduce the exact greedy sequence (accepted {a1}/{d1})"
6038 );
6039 }
6040
6041 #[test]
6042 fn speculative_accepts_constant_oracle() {
6043 unsafe { std::env::set_var("CMF_GPU_WGPU_GRAPH", "0") };
6045 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 64);
6046 p.sampler_config.temperature = 0.0;
6047 p.sampler_config.repetition_penalty = 1.0;
6048 p.weights.lm_head = QTensor::from_f32(vec![0.01; 64 * 8], 64, 8);
6051 attach_test_mtp(&mut p);
6052 p.speculative = true;
6053 let r = p.generate("abcd", 10, None, None).unwrap();
6054 assert!(r.mtp_drafted > 0);
6055 assert_eq!(
6056 r.mtp_accepted, r.mtp_drafted,
6057 "constant logits → every draft accepted"
6058 );
6059 assert!(r.token_ids.windows(2).all(|w| w[0] == w[1]));
6062 }
6063
6064 #[test]
6065 fn empty_prompt_is_an_error_not_a_panic() {
6066 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 260);
6067 let r = p.generate("", 4, None, None);
6068 assert!(r.is_err(), "empty prompt must be a clean error");
6069 }
6070
6071 #[test]
6072 fn every_token_enters_kv_exactly_once() {
6073 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 2, 260);
6074 p.sampler_config.temperature = 0.0;
6076 let r = p.generate("abc", 2, None, None).unwrap();
6077 assert_eq!(r.prompt_tokens, 3);
6078 assert_eq!(
6082 p.kv_cache.seq_len(),
6083 3 + r.tokens_generated - 1,
6084 "each token must be cached exactly once (v1 cached the last prompt token twice)"
6085 );
6086 }
6087
6088 #[test]
6089 fn generation_is_reproducible_with_seed() {
6090 let run = || {
6091 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 2, 260);
6092 p.generate("hello", 8, None, None).unwrap().token_ids
6093 };
6094 assert_eq!(run(), run());
6095 }
6096
6097 #[test]
6098 fn resetting_sampler_restarts_the_seeded_stream() {
6099 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 2, 260);
6100 let config = SamplerConfig {
6101 seed: Some(1234),
6102 ..SamplerConfig::default()
6103 };
6104 p.set_sampler_config(config.clone());
6105 let first = p.generate("hello", 8, None, None).unwrap().token_ids;
6106 p.set_sampler_config(config);
6107 let second = p.generate("hello", 8, None, None).unwrap().token_ids;
6108 assert_eq!(first, second);
6109 }
6110
6111 #[test]
6112 fn eviction_bounds_the_cache() {
6113 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 260);
6114 p.kv_cache.max_seq_len = 6;
6115 p.sampler_config.temperature = 0.0;
6116 let _ = p.generate("abcd", 12, None, None).unwrap();
6117 assert!(
6118 p.kv_cache.seq_len() <= 6 + 1,
6119 "cache must stay bounded by max_seq_len (got {})",
6120 p.kv_cache.seq_len()
6121 );
6122 }
6123
6124 #[test]
6125 fn confidence_matches_tokens_and_is_a_probability() {
6126 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 64);
6127 p.sampler_config.temperature = 0.0;
6128 p.sampler_config.repetition_penalty = 1.0;
6129 let r = p.generate("abcd", 10, None, None).unwrap();
6130 assert_eq!(
6131 r.token_confidence.len(),
6132 r.token_ids.len(),
6133 "one confidence per emitted token"
6134 );
6135 for &c in &r.token_confidence {
6136 assert!((0.0..=1.0).contains(&c), "confidence out of [0,1]: {c}");
6137 }
6138 let logits = [1.0f32, 3.0, 0.5, 3.0];
6140 let p0 = top1_prob_t(&logits, 1, 1.0);
6141 let p1 = top1_prob_t(&logits, 3, 1.0);
6142 assert!((p0 - p1).abs() < 1e-6, "equal logits → equal prob");
6143 assert!(p0 > 0.0 && p0 < 1.0);
6144 let sharp = top1_prob_t(&logits, 1, 1.0);
6146 let soft = top1_prob_t(&logits, 1, 2.0);
6147 assert!(soft < sharp, "higher temperature lowers peak confidence");
6148 }
6149
6150 #[test]
6151 fn trace_is_opt_in_and_parallels_the_output() {
6152 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 64);
6154 p.sampler_config.temperature = 0.0;
6155 p.sampler_config.repetition_penalty = 1.0;
6156 let r = p.generate("abcd", 10, None, None).unwrap();
6157 assert!(r.traces.is_empty(), "trace must be empty unless enabled");
6158
6159 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 64);
6161 p.sampler_config.temperature = 0.0;
6162 p.sampler_config.repetition_penalty = 1.0;
6163 p.set_trace(true);
6164 let r = p.generate("abcd", 10, None, None).unwrap();
6165 assert_eq!(r.traces.len(), r.token_ids.len(), "one trace row per token");
6166 for (i, tr) in r.traces.iter().enumerate() {
6167 assert_eq!(tr.t, i, "trace index is sequential");
6168 assert_eq!(tr.token_id, r.token_ids[i], "trace token_id matches output");
6169 assert_eq!(
6170 tr.confidence, r.token_confidence[i],
6171 "trace confidence matches the confidence channel"
6172 );
6173 assert!(tr.active_skill.is_none() && tr.recon.is_none() && !tr.switched);
6175 }
6176 }
6177
6178 #[test]
6179 fn explain_prefill_logits_match_greedy_first_token() {
6180 let mut p = create_test_pipeline(8, 16, 2, 1, 4, 1, 64);
6184 p.sampler_config.temperature = 0.0;
6185 p.sampler_config.repetition_penalty = 1.0;
6186 let ids = p.tokenizer.encode("abcd");
6187 let logits = p.prefill_next_logits(&ids, None);
6188 let argmax = logits
6189 .iter()
6190 .enumerate()
6191 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
6192 .unwrap()
6193 .0 as u32;
6194 let r = p.generate("abcd", 1, None, None).unwrap();
6195 assert_eq!(
6196 argmax, r.token_ids[0],
6197 "explain preview must match greedy emit"
6198 );
6199 }
6200
6201 #[test]
6202 fn laguna_shared_expert_is_unconditionally_added() {
6203 let matrix = |values: Vec<f32>| QTensor::from_f32(values, 2, 2);
6204 let identity = || matrix(vec![1.0, 0.0, 0.0, 1.0]);
6205 let zero_dense = || DenseFfn {
6206 gate_proj: matrix(vec![0.0; 4]),
6207 up_proj: matrix(vec![0.0; 4]),
6208 down_proj: matrix(vec![0.0; 4]),
6209 act: Act::Silu,
6210 };
6211 let shared = DenseFfn {
6212 gate_proj: identity(),
6213 up_proj: identity(),
6214 down_proj: identity(),
6215 act: Act::Silu,
6216 };
6217 let x = [1.0, 2.0];
6218 let expected = dense_ffn(&shared, &x, None);
6219 let moe = MoeFfn {
6220 router: QTensor::from_f32(vec![0.0, 0.0], 1, 2),
6221 experts: vec![zero_dense()],
6222 top_k: 1,
6223 norm_topk_prob: true,
6224 router_sigmoid: true,
6225 expert_bias: None,
6226 routed_scaling: 1.0,
6227 route_tau: None,
6228 shared: Some((shared, None)),
6229 stats: std::cell::RefCell::new(Vec::new()),
6230 act_sq: std::cell::RefCell::new(Vec::new()),
6231 act_rows: std::cell::RefCell::new(Vec::new()),
6232 mask: None,
6233 per_expert_scale: None,
6234 router_input_norm: false,
6235 };
6236 let actual = moe_ffn_cpu(&moe, &x, &[0], &[0.0], 1.0, None);
6237 for (actual, expected) in actual.iter().zip(expected) {
6238 assert!((actual - expected).abs() < 1e-6);
6239 }
6240 }
6241}