1use crate::kv_cache::LayerKvCache;
15use crate::linear_core::{
16 GdnCfg, GdnWeights, ShortConvCfg, ShortConvWeights, VmfPhaseCfg, VmfPhaseWeights,
17};
18use crate::pipeline::{
19 AttnKind, DenseFfn, FfnKind, LayerWeights, MoeFfn, MtpModule, Pipeline, PipelineWeights,
20};
21use crate::qtensor::QTensor;
22use crate::sampler::SamplerConfig;
23use crate::tokenizer::Tokenizer;
24use cortiq_core::quant::dequant_tensor;
25use cortiq_core::{CmfError, CmfModel, LayerType, ModelArch};
26use std::sync::Arc;
27
28pub const DECISION_MODEL_REFUSAL: &str =
32 "this is a DECISION model; use `cortiq decide` or `cortiq serve`";
33
34const FLINT_NO_ROPE_LAYERS: [usize; 14] = [0, 1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 48];
35
36fn validate_flint_profile(model: &CmfModel, arch: &ModelArch) -> Result<(), CmfError> {
40 if arch.arch_name != "flint" {
41 return Ok(());
42 }
43 if arch.num_layers != 49
44 || arch.hidden_size != 2048
45 || arch.num_attention_heads != 32
46 || arch.num_kv_heads != 4
47 || arch.head_dim != 128
48 || arch.vocab_size != 150_144
49 {
50 return Err(CmfError::Parse(
51 "Flint geometry is not the released RecursiveSI/flint profile".into(),
52 ));
53 }
54 if arch.sliding_window != Some(513) {
55 return Err(CmfError::Parse(
56 "Flint requires arch.sliding_window=513; run `cortiq repair-flint <model.cmf>` for legacy exports"
57 .into(),
58 ));
59 }
60 let expected_no_rope: Vec<bool> = (0..arch.num_layers)
61 .map(|li| FLINT_NO_ROPE_LAYERS.contains(&li))
62 .collect();
63 if arch.no_rope_layers.as_deref() != Some(expected_no_rope.as_slice()) {
64 return Err(CmfError::Parse(
65 "Flint requires its exact 14-layer no-RoPE schedule; run `cortiq repair-flint <model.cmf>` for legacy exports"
66 .into(),
67 ));
68 }
69 if !arch.attention_sink {
70 return Err(CmfError::Parse(
71 "Flint requires arch.attention_sink=true; run `cortiq repair-flint <model.cmf>` for legacy exports"
72 .into(),
73 ));
74 }
75 let Some(moe) = arch.moe.as_ref() else {
76 return Err(CmfError::Parse("Flint requires an MoE descriptor".into()));
77 };
78 if !(moe.router_sigmoid
79 && moe.norm_topk_prob
80 && moe.num_experts == 256
81 && moe.top_k == 6
82 && moe.moe_intermediate_size == 512
83 && moe.shared_expert_intermediate_size == Some(1024))
84 {
85 return Err(CmfError::Parse(
86 "Flint requires sigmoid top-6 normalized routing with its 256×512 + shared-1024 MoE profile"
87 .into(),
88 ));
89 }
90 for li in 0..arch.num_layers {
91 let name = format!("model.layers.{li}.self_attn.softmax_offset");
92 let Some(sink) = model.tensor(&name) else {
93 return Err(CmfError::MissingTensor(name));
94 };
95 if sink.shape.as_slice() != [arch.num_attention_heads] {
96 return Err(CmfError::Parse(format!(
97 "{name} has shape {:?}, expected [{}]",
98 sink.shape, arch.num_attention_heads
99 )));
100 }
101 }
102 Ok(())
103}
104
105pub enum Overlay<'a> {
108 None,
109 One(&'a str),
110 Blend(&'a [(String, f32)]),
112}
113
114impl Overlay<'_> {
115 fn blend_touches(&self, model: &CmfModel, name: &str) -> bool {
116 match self {
117 Overlay::Blend(list) => list
118 .iter()
119 .any(|(sid, _)| model.tensor(&format!("skill.{sid}.{name}")).is_some()),
120 _ => false,
121 }
122 }
123}
124
125fn dequant_by_name(model: &CmfModel, name: &str) -> Result<Vec<f32>, String> {
126 let entry = model
127 .tensor(name)
128 .ok_or_else(|| format!("tensor '{name}' not found in CMF directory"))?;
129 let mut out = vec![0.0f32; entry.n_elems()];
130 dequant_tensor(entry, model.entry_bytes(entry), &mut out)?;
131 Ok(out)
132}
133
134fn blend_f32(model: &CmfModel, name: &str, list: &[(String, f32)]) -> Result<Vec<f32>, String> {
137 let mut acc: Option<Vec<f32>> = None;
138 for (sid, w) in list {
139 let sname = format!("skill.{sid}.{name}");
140 let src = if model.tensor(&sname).is_some() {
141 &sname
142 } else {
143 name
144 };
145 let t = dequant_by_name(model, src)?;
146 match &mut acc {
147 None => {
148 let mut t = t;
149 for v in t.iter_mut() {
150 *v *= w;
151 }
152 acc = Some(t);
153 }
154 Some(a) => {
155 for (av, tv) in a.iter_mut().zip(&t) {
156 *av += w * tv;
157 }
158 }
159 }
160 }
161 acc.ok_or_else(|| "empty blend".into())
162}
163
164pub(crate) fn load_f32(model: &CmfModel, name: &str, ov: &Overlay) -> Result<Vec<f32>, String> {
166 if ov.blend_touches(model, name) {
167 if let Overlay::Blend(list) = ov {
168 return blend_f32(model, name, list);
169 }
170 }
171 let skill = match ov {
172 Overlay::One(s) => Some(*s),
173 _ => None,
174 };
175 let entry = model
176 .resolve_tensor(name, skill)
177 .ok_or_else(|| format!("tensor '{name}' not found in CMF directory"))?;
178 let bytes = model.entry_bytes(entry);
179 let mut out = vec![0.0f32; entry.n_elems()];
180 dequant_tensor(entry, bytes, &mut out)?;
181 Ok(out)
182}
183
184pub(crate) fn build_layer_ffn(
190 model: &Arc<CmfModel>,
191 arch: &ModelArch,
192 li: usize,
193 force_f32: bool,
194 ov: &Overlay,
195) -> Result<FfnKind, CmfError> {
196 build_ffn_at(model, arch, &format!("model.layers.{li}."), force_f32, ov)
197}
198
199pub(crate) fn build_ffn_at(
204 model: &Arc<CmfModel>,
205 arch: &ModelArch,
206 prefix: &str,
207 force_f32: bool,
208 ov: &Overlay,
209) -> Result<FfnKind, CmfError> {
210 let prefix = prefix.to_string();
211 let load_dense = |p: &str| -> Result<DenseFfn, CmfError> {
212 let gate_proj = load_matrix(model, &format!("{p}gate_proj.weight"), force_f32, ov)?;
213 let up_proj = load_matrix(model, &format!("{p}up_proj.weight"), force_f32, ov)?;
214 let down_proj = load_matrix(model, &format!("{p}down_proj.weight"), force_f32, ov)?;
215 let inter = gate_proj.rows();
219 if up_proj.rows() != inter || down_proj.cols() != inter {
220 return Err(CmfError::Parse(format!(
221 "{p}: FFN dims disagree (gate.rows={inter}, up.rows={}, \
222 down.cols={}); all three must equal inter'",
223 up_proj.rows(),
224 down_proj.cols()
225 )));
226 }
227 if down_proj.rows() != arch.hidden_size {
228 return Err(CmfError::Parse(format!(
229 "{p}: down_proj.rows={} != hidden_size={}",
230 down_proj.rows(),
231 arch.hidden_size
232 )));
233 }
234 let dt_name = format!("{p}down_proj.t.weight");
237 let down_t = match model.tensor(&dt_name) {
238 Some(_) => Some(load_matrix(model, &dt_name, force_f32, ov)?),
239 None => None,
240 };
241 if let Some(t) = &down_t
242 && (t.rows() != inter || t.cols() != arch.hidden_size)
243 {
244 return Err(CmfError::Parse(format!(
245 "{p}down_proj.t: [{}, {}] != [{inter}, {}]",
246 t.rows(),
247 t.cols(),
248 arch.hidden_size
249 )));
250 }
251 let mut segs = Vec::new();
257 let mut start = inter;
258 for k in 1.. {
259 let gn = format!("{p}gate_proj.tube{k}.weight");
260 if model.tensor(&gn).is_none() {
261 break;
262 }
263 let gate = load_matrix(model, &gn, force_f32, ov)?;
264 let up = load_matrix(model, &format!("{p}up_proj.tube{k}.weight"), force_f32, ov)?;
265 let down = load_matrix(
266 model,
267 &format!("{p}down_proj.tube{k}.weight"),
268 force_f32,
269 ov,
270 )?;
271 let width = gate.rows();
272 if up.rows() != width || down.cols() != width || down.rows() != arch.hidden_size {
273 return Err(CmfError::Parse(format!(
274 "{p}tube{k}: dims disagree (gate.rows={width}, up.rows={}, \
275 down=[{}, {}], hidden={})",
276 up.rows(),
277 down.rows(),
278 down.cols(),
279 arch.hidden_size
280 )));
281 }
282 segs.push(crate::pipeline::FfnSeg {
283 gate,
284 up,
285 down,
286 start,
287 width,
288 });
289 start += width;
290 }
291 Ok(DenseFfn {
292 gate_proj,
293 up_proj,
294 down_proj,
295 act: crate::pipeline::Act::from_arch_full(arch),
296 down_t,
297 segs,
298 })
299 };
300 let router_name = format!("{prefix}mlp.gate.weight");
301 let resonance_moe = model.tensor(&router_name).is_none()
305 && arch.moe.as_ref().is_some_and(|m| m.router_resonance)
306 && model
307 .tensor(&format!("{prefix}mlp.experts.0.gate_proj.weight"))
308 .is_some();
309 if model.tensor(&router_name).is_none() && !resonance_moe {
310 return Ok(FfnKind::Dense(load_dense(&format!("{prefix}mlp."))?));
311 }
312 let cfg = arch.moe.as_ref().ok_or_else(|| {
313 CmfError::Parse(format!(
314 "{router_name} present but header has no arch.moe block"
315 ))
316 })?;
317 let mut experts = Vec::new();
322 for e in 0..cfg.num_experts {
323 if model
324 .tensor(&format!("{prefix}mlp.experts.{e}.gate_proj.weight"))
325 .is_none()
326 {
327 break;
328 }
329 experts.push(load_dense(&format!("{prefix}mlp.experts.{e}."))?);
330 }
331 if experts.is_empty() {
332 return Err(CmfError::Parse(format!(
333 "{prefix}: router present but no expert tensors"
334 )));
335 }
336 let e0 = experts.len();
341 let grown_loads = if resonance_moe {
342 mount_growth(model, &prefix, ov, &load_dense)?
343 } else {
344 Vec::new()
345 };
346 let mut grown_desc: Vec<(Vec<f32>, Vec<f32>, f32, f32)> = Vec::new();
347 let mut grown: Vec<crate::pipeline::GrownExpert> = Vec::new();
348 for g in grown_loads {
349 experts.push(g.ffn);
350 grown_desc.push((g.mu, g.u, g.bias, g.shell));
351 grown.push(g.meta);
352 }
353 let shared = if model
354 .tensor(&format!("{prefix}mlp.shared_expert.gate_proj.weight"))
355 .is_some()
356 {
357 let gate_name = format!("{prefix}mlp.shared_expert_gate.weight");
358 Some((
359 load_dense(&format!("{prefix}mlp.shared_expert."))?,
360 if model.tensor(&gate_name).is_some() {
361 Some(load_matrix(model, &gate_name, force_f32, ov)?)
362 } else {
363 None
364 },
365 ))
366 } else {
367 None
368 };
369 let bias_name = format!("{prefix}mlp.expert_bias");
372 let expert_bias = if model.tensor(&bias_name).is_some() {
373 Some(load_f32(model, &bias_name, ov).map_err(CmfError::Parse)?)
374 } else {
375 None
376 };
377 let top_k = std::env::var("CMF_MOE_TOPK")
383 .ok()
384 .and_then(|v| v.parse::<usize>().ok())
385 .filter(|&k| k >= 1 && k <= cfg.top_k)
386 .inspect(|k| tracing::info!("MoE top_k override: {} (header {})", k, cfg.top_k))
387 .unwrap_or(cfg.top_k);
388 let route_tau = std::env::var("CMF_MOE_TAU")
390 .ok()
391 .and_then(|v| v.parse::<f32>().ok())
392 .filter(|&t| t > 0.0 && t < 1.0)
393 .inspect(|t| tracing::info!("MoE adaptive routing: tau {t}"));
394 let mask = moe_task_mask(model, &prefix, experts.len());
395 let router = if resonance_moe {
396 QTensor::from_f32(
398 vec![0.0; experts.len() * arch.hidden_size],
399 experts.len(),
400 arch.hidden_size,
401 )
402 } else {
403 load_matrix(model, &router_name, force_f32, ov)?
404 };
405 if router.rows() != experts.len() {
406 return Err(CmfError::Parse(format!(
407 "{router_name}: {} rows != {} experts",
408 router.rows(),
409 experts.len()
410 )));
411 }
412 let top_k = top_k.min(experts.len());
413 let pes_name = format!("{prefix}mlp.per_expert_scale");
417 let per_expert_scale = if model.tensor(&pes_name).is_some() {
418 Some(load_f32(model, &pes_name, ov).map_err(CmfError::Parse)?)
419 } else {
420 None
421 };
422 let router_input_norm = per_expert_scale.is_some();
423 let per_expert = model
428 .tensor(&format!("{prefix}mlp.experts.0.desc.mu"))
429 .is_some();
430 if !per_expert && !grown.is_empty() {
431 return Err(CmfError::Parse(format!(
432 "{prefix}: expert_append records need per-expert descriptors \
433 (mlp.experts.0.desc.mu); the stacked mlp.desc.* form cannot be grown"
434 )));
435 }
436 let resonance = if per_expert {
437 let ne_d = e0;
441 let hidden = arch.hidden_size;
442 let mut mu = Vec::with_capacity(experts.len() * hidden);
443 let mut u = Vec::new();
444 let mut bias = Vec::with_capacity(experts.len());
445 let mut k = 0usize;
446 for e in 0..ne_d {
447 let m = load_f32(model, &format!("{prefix}mlp.experts.{e}.desc.mu"), ov)
448 .map_err(CmfError::Parse)?;
449 if m.len() != hidden {
450 return Err(CmfError::Parse(format!(
451 "{prefix}mlp.experts.{e}.desc.mu: {} != {hidden}",
452 m.len()
453 )));
454 }
455 mu.extend_from_slice(&m);
456 let un = format!("{prefix}mlp.experts.{e}.desc.u");
457 if model.tensor(&un).is_some() {
458 let ue = load_f32(model, &un, ov).map_err(CmfError::Parse)?;
459 let ke = ue.len() / hidden.max(1);
460 if e == 0 {
461 k = ke;
462 }
463 if ke != k {
464 return Err(CmfError::Parse(format!("{un}: rank {ke} != {k}")));
465 }
466 u.extend_from_slice(&ue);
467 }
468 let bn = format!("{prefix}mlp.experts.{e}.desc.bias");
469 bias.push(if model.tensor(&bn).is_some() {
470 load_f32(model, &bn, ov)
471 .map_err(CmfError::Parse)?
472 .first()
473 .copied()
474 .unwrap_or(0.0)
475 } else {
476 0.0
477 });
478 }
479 let mut shell = vec![f32::INFINITY; ne_d];
480 for (gm, gu, gb, gs) in &grown_desc {
481 if gm.len() != hidden || gu.len() != k * hidden {
482 return Err(CmfError::Parse(format!(
483 "{prefix}: grown expert descriptor {}×{} != trunk {hidden}×{k}",
484 gm.len(),
485 gu.len() / hidden.max(1)
486 )));
487 }
488 mu.extend_from_slice(gm);
489 u.extend_from_slice(gu);
490 bias.push(*gb);
491 shell.push(*gs);
492 }
493 Some(crate::pipeline::Resonance {
494 mu,
495 u,
496 k,
497 bias,
498 shell,
499 })
500 } else if model.tensor(&format!("{prefix}mlp.desc.mu")).is_some() {
501 let mu = load_f32(model, &format!("{prefix}mlp.desc.mu"), ov).map_err(CmfError::Parse)?;
502 let ne_d = experts.len();
503 let hidden = arch.hidden_size;
504 if mu.len() != ne_d * hidden {
505 return Err(CmfError::Parse(format!(
506 "{prefix}mlp.desc.mu: {} != {ne_d}×{hidden}",
507 mu.len()
508 )));
509 }
510 let u_name = format!("{prefix}mlp.desc.u");
511 let (u, k) = if model.tensor(&u_name).is_some() {
512 let u = load_f32(model, &u_name, ov).map_err(CmfError::Parse)?;
513 let k = u.len() / (ne_d * hidden).max(1);
514 (u, k)
515 } else {
516 (Vec::new(), 0)
517 };
518 let b_name = format!("{prefix}mlp.desc.bias");
519 let bias = if model.tensor(&b_name).is_some() {
520 load_f32(model, &b_name, ov).map_err(CmfError::Parse)?
521 } else {
522 vec![0.0; ne_d]
523 };
524 Some(crate::pipeline::Resonance {
525 mu,
526 u,
527 k,
528 bias,
529 shell: Vec::new(),
530 })
531 } else {
532 None
533 };
534 let moe = MoeFfn {
535 grown,
536 router,
537 experts,
538 top_k,
539 route_tau,
540 norm_topk_prob: cfg.norm_topk_prob,
541 router_sigmoid: cfg.router_sigmoid,
542 sigmoid_denom_eps: match arch.arch_name.as_str() {
547 "flint" | "minimax_m3_vl" => 1e-20,
548 _ => 1e-6,
549 },
550 expert_bias,
551 routed_scaling: cfg.routed_scaling_factor.unwrap_or(1.0),
552 shared,
553 stats: std::cell::RefCell::new(Vec::new()),
554 act_sq: std::cell::RefCell::new(Vec::new()),
555 act_rows: std::cell::RefCell::new(Vec::new()),
556 mask,
557 per_expert_scale,
558 router_input_norm,
559 resonance,
560 };
561 if model
564 .tensor(&format!("{prefix}mlp.gate_proj.weight"))
565 .is_some()
566 {
567 let norm = |suffix: &str| -> Result<Vec<f32>, CmfError> {
568 load_f32(model, &format!("{prefix}{suffix}.weight"), ov).map_err(CmfError::Parse)
569 };
570 return Ok(FfnKind::DenseMoe(Box::new(crate::pipeline::DenseMoeFfn {
571 dense: load_dense(&format!("{prefix}mlp."))?,
572 moe,
573 post_norm_1: norm("post_feedforward_layernorm_1")?,
574 pre_norm_2: norm("pre_feedforward_layernorm_2")?,
575 post_norm_2: norm("post_feedforward_layernorm_2")?,
576 })));
577 }
578 Ok(FfnKind::Moe(moe))
579}
580
581#[derive(Clone, Copy, PartialEq, Eq, Debug)]
585pub enum GrowthMode {
586 Active,
588 All,
591 Off,
593}
594
595impl GrowthMode {
596 pub fn label(self) -> &'static str {
597 match self {
598 Self::Active => "active",
599 Self::All => "all",
600 Self::Off => "off",
601 }
602 }
603
604 pub fn admits(self, status: Option<&str>) -> bool {
606 match self {
607 Self::Off => false,
608 Self::Active => status == Some("active"),
609 Self::All => status != Some("retired"),
610 }
611 }
612}
613
614pub fn growth_mode() -> GrowthMode {
616 match std::env::var("CMF_GROWTH")
617 .ok()
618 .as_deref()
619 .map(|v| v.trim().to_ascii_lowercase())
620 .as_deref()
621 {
622 Some("off") | Some("0") | Some("none") => GrowthMode::Off,
623 Some("all") => GrowthMode::All,
624 _ => GrowthMode::Active,
625 }
626}
627
628pub fn mounted_growth_records(
632 header: &cortiq_core::CmfHeader,
633) -> Vec<(usize, &cortiq_core::SkillRecord)> {
634 let mode = growth_mode();
635 header
636 .skills
637 .iter()
638 .enumerate()
639 .filter(|(_, s)| {
640 s.kind.as_deref() == Some(cortiq_core::knowledge::skill_kind::EXPERT_APPEND)
641 && mode.admits(s.status.as_deref())
642 })
643 .collect()
644}
645
646struct GrownLoad {
648 ffn: DenseFfn,
649 mu: Vec<f32>,
650 u: Vec<f32>,
651 bias: f32,
652 shell: f32,
653 meta: crate::pipeline::GrownExpert,
654}
655
656fn layer_of_prefix(prefix: &str) -> Option<usize> {
659 prefix
660 .strip_prefix("model.layers.")?
661 .strip_suffix('.')?
662 .parse()
663 .ok()
664}
665
666fn mount_growth(
672 model: &Arc<CmfModel>,
673 prefix: &str,
674 ov: &Overlay,
675 load_dense: &dyn Fn(&str) -> Result<DenseFfn, CmfError>,
676) -> Result<Vec<GrownLoad>, CmfError> {
677 use cortiq_core::knowledge::expert_leaf;
678 let Some(layer) = layer_of_prefix(prefix) else {
679 return Ok(Vec::new());
680 };
681 let records = mounted_growth_records(&model.header);
682 let mut out = Vec::new();
683 for (at, rec) in records {
684 if !rec.layers.contains(&layer) {
685 continue;
686 }
687 let plan = cortiq_core::expert_append_layout(&model.header, &model.tensors, at, rec)
688 .map_err(|e| CmfError::Parse(format!("{prefix}: growth record '{}': {e}", rec.id)))?;
689 let mut by_expert: std::collections::BTreeMap<usize, Vec<&cortiq_core::ExpertTensorSpec>> =
690 Default::default();
691 for p in plan.iter().filter(|p| p.layer == layer) {
692 by_expert.entry(p.expert).or_default().push(p);
693 }
694 let first = out.len();
695 for (e, leaves) in &by_expert {
696 let name = |leaf: &str| -> Result<String, CmfError> {
697 leaves
698 .iter()
699 .find(|p| p.leaf == leaf)
700 .map(|p| p.name.clone())
701 .ok_or_else(|| {
702 CmfError::Parse(format!(
703 "{prefix}: growth record '{}' expert {e}: layout has no '{leaf}'",
704 rec.id
705 ))
706 })
707 };
708 let ffn = load_dense(&format!("skill.{}.{prefix}mlp.experts.{e}.", rec.id))?;
709 let mu = load_f32(model, &name(expert_leaf::MU)?, ov).map_err(CmfError::Parse)?;
710 let u = if leaves.iter().any(|p| p.leaf == expert_leaf::U) {
711 load_f32(model, &name(expert_leaf::U)?, ov).map_err(CmfError::Parse)?
712 } else {
713 Vec::new()
714 };
715 let scalar = |leaf: &str| -> Result<f32, CmfError> {
716 load_f32(model, &name(leaf)?, ov)
717 .map_err(CmfError::Parse)?
718 .first()
719 .copied()
720 .ok_or_else(|| {
721 CmfError::Parse(format!(
722 "{prefix}: growth record '{}' expert {e}: empty '{leaf}'",
723 rec.id
724 ))
725 })
726 };
727 let bias = scalar(expert_leaf::BIAS)?;
728 let shell = scalar(expert_leaf::SHELL)?;
729 if !shell.is_finite() {
730 return Err(CmfError::Parse(format!(
731 "{prefix}: growth record '{}' expert {e}: desc.shell {shell} is not finite",
732 rec.id
733 )));
734 }
735 out.push(GrownLoad {
736 ffn,
737 mu,
738 u,
739 bias,
740 shell,
741 meta: crate::pipeline::GrownExpert {
742 record: rec.id.clone(),
743 record_index: at,
744 layer,
745 expert: *e,
746 },
747 });
748 }
749 let declared: Vec<usize> = by_expert.keys().copied().collect();
750 tracing::info!(
751 "growth: layer {layer}: mounted expert_append '{}' (status {}) — {} experts, declared {:?}, shells {:?}",
752 rec.id,
753 rec.status.as_deref().unwrap_or("?"),
754 by_expert.len(),
755 declared,
756 out[first..].iter().map(|g| g.shell).collect::<Vec<_>>()
757 );
758 }
759 Ok(out)
760}
761
762pub(crate) fn moe_task_mask(
773 _model: &std::sync::Arc<CmfModel>,
774 prefix: &str,
775 ne: usize,
776) -> Option<Vec<bool>> {
777 use std::sync::OnceLock;
778 static CFG: OnceLock<Option<(std::collections::HashMap<usize, Vec<u64>>, f64)>> =
779 OnceLock::new();
780 let cfg = CFG.get_or_init(|| {
781 let path = std::path::PathBuf::from(std::env::var("CMF_MOE_MASK").ok()?);
782 let shown = path.display();
783 let text = std::fs::read_to_string(&path)
784 .map_err(|e| tracing::warn!("CMF_MOE_MASK: cannot read {shown}: {e}"))
785 .ok()?;
786 let map: std::collections::HashMap<String, Vec<u64>> = serde_json::from_str(&text)
787 .map_err(|e| tracing::warn!("CMF_MOE_MASK: bad JSON in {shown}: {e}"))
788 .ok()?;
789 let weighted = map
794 .values()
795 .any(|row| row.iter().copied().sum::<u64>() >= 1_000_000);
796 let cover = std::env::var("CMF_MOE_MASK_COVER")
797 .ok()
798 .and_then(|v| v.parse::<f64>().ok())
799 .filter(|&c| c > 0.0 && c <= 1.0)
800 .unwrap_or(if weighted { 0.925 } else { 0.9 });
801 tracing::info!("MoE task mask: {shown}, cover {cover}");
802 Some((
803 map.into_iter()
804 .filter_map(|(k, v)| Some((k.parse::<usize>().ok()?, v)))
805 .collect(),
806 cover,
807 ))
808 });
809 let (stats, cover) = cfg.as_ref()?;
810 let li: usize = prefix
812 .split("layers.")
813 .nth(1)?
814 .split('.')
815 .next()?
816 .parse()
817 .ok()?;
818 let counts = stats.get(&li)?;
819 if counts.len() != ne {
820 tracing::warn!(
821 "CMF_MOE_MASK: layer {li} has {} counts, model has {ne} experts — skipped",
822 counts.len()
823 );
824 return None;
825 }
826 let total: u64 = counts.iter().sum();
827 if total == 0 {
828 return None;
829 }
830 let mut order: Vec<usize> = (0..ne).collect();
831 order.sort_unstable_by_key(|&e| std::cmp::Reverse(counts[e]));
832 let mut mask = vec![false; ne];
833 let mut acc = 0u64;
834 let mut kept = 0usize;
835 for &e in &order {
836 mask[e] = true;
837 acc += counts[e];
838 kept += 1;
839 if (acc as f64) >= cover * (total as f64) {
840 break;
841 }
842 }
843 tracing::info!(
844 "MoE task mask L{li}: {kept}/{ne} experts for {:.0}% mass",
845 cover * 100.0
846 );
847 Some(mask)
848}
849
850pub(crate) fn load_matrix(
851 model: &Arc<CmfModel>,
852 name: &str,
853 force_f32: bool,
854 ov: &Overlay,
855) -> Result<QTensor, CmfError> {
856 if ov.blend_touches(model, name) {
860 if let Overlay::Blend(list) = ov {
861 let entry = model
862 .tensor(name)
863 .ok_or_else(|| CmfError::MissingTensor(name.to_string()))?;
864 let data =
865 blend_f32(model, name, list).map_err(|e| CmfError::Parse(format!("blend: {e}")))?;
866 return Ok(QTensor::from_f32(data, entry.shape[0], entry.shape[1]));
867 }
868 }
869 let skill = match ov {
870 Overlay::One(s) => Some(*s),
871 _ => None,
872 };
873 let name: &str = &match skill {
876 Some(sid) if model.tensor(&format!("skill.{sid}.{name}")).is_some() => {
877 format!("skill.{sid}.{name}")
878 }
879 _ => name.to_string(),
880 };
881 let err = |e: String| CmfError::Parse(format!("weight loading: {e}"));
882 if force_f32 {
883 let entry = model
884 .tensor(name)
885 .ok_or_else(|| CmfError::MissingTensor(name.to_string()))?;
886 if entry.shape.len() != 2 {
887 return Err(err(format!("'{name}' is not 2-D")));
888 }
889 let data = load_f32(model, name, &Overlay::None).map_err(err)?;
890 Ok(QTensor::from_f32(data, entry.shape[0], entry.shape[1]))
891 } else {
892 QTensor::from_model(model, name).map_err(err)
893 }
894}
895
896impl Pipeline {
897 pub fn from_model(
899 model: &Arc<CmfModel>,
900 sampler_config: SamplerConfig,
901 ) -> Result<Self, CmfError> {
902 Self::from_model_with_skill(model, sampler_config, None)
903 }
904
905 pub fn from_encoder_model(
918 model: &Arc<CmfModel>,
919 sampler_config: SamplerConfig,
920 ) -> Result<Self, CmfError> {
921 Self::from_model_with_overlay(model, sampler_config, &Overlay::None, true)
922 }
923
924 pub fn from_model_with_skill(
930 model: &Arc<CmfModel>,
931 sampler_config: SamplerConfig,
932 skill: Option<&str>,
933 ) -> Result<Self, CmfError> {
934 match skill {
935 Some(s) => {
936 Self::from_model_with_overlay(model, sampler_config, &Overlay::One(s), false)
937 }
938 None => Self::from_model_with_overlay(model, sampler_config, &Overlay::None, false),
939 }
940 }
941
942 pub fn from_model_with_blend(
945 model: &Arc<CmfModel>,
946 sampler_config: SamplerConfig,
947 blend: &[(String, f32)],
948 ) -> Result<Self, CmfError> {
949 Self::from_model_with_overlay(model, sampler_config, &Overlay::Blend(blend), false)
950 }
951
952 fn skill_file_guard(model: &CmfModel) -> Result<(), CmfError> {
953 if model.required_features & cortiq_core::format::features::DECISION != 0 {
957 return Err(CmfError::Parse(DECISION_MODEL_REFUSAL.into()));
958 }
959 if model.required_features & cortiq_core::format::features::SKILL_FILE != 0 {
962 return Err(CmfError::Parse(
963 "this file is a standalone SKILL, not a runnable model — attach it: \
964 cortiq skill apply <base.cmf> <this file> -o specialist.cmf"
965 .into(),
966 ));
967 }
968 Ok(())
969 }
970
971 fn from_model_with_overlay(
972 model: &Arc<CmfModel>,
973 sampler_config: SamplerConfig,
974 ov: &Overlay,
975 encoder_only: bool,
976 ) -> Result<Self, CmfError> {
977 Self::skill_file_guard(model)?;
981 if let Some(dir) = model.path.parent() {
986 crate::gpu::set_cache_dir(dir.to_path_buf());
987 }
988 let skill = match ov {
994 Overlay::One(s) => Some(*s),
995 _ => None,
996 };
997 if let Some(sid) = skill {
998 let known = model.header.skills.iter().any(|s| s.id == sid)
999 || model.skill_tensors(sid).next().is_some();
1000 if !known {
1001 return Err(CmfError::Parse(format!(
1002 "skill '{sid}' not in this container (header.skills: {:?})",
1003 model
1004 .header
1005 .skills
1006 .iter()
1007 .map(|s| &s.id)
1008 .collect::<Vec<_>>()
1009 )));
1010 }
1011 tracing::info!(
1012 "skill '{sid}': {} replacement tensors overlaid",
1013 model.skill_tensors(sid).count()
1014 );
1015 }
1016 let arch = model.arch().clone();
1017 let err = |e: String| CmfError::Parse(format!("weight loading: {e}"));
1018 if let Some(heads) = &arch.attention_heads_per_layer {
1019 if heads.len() != arch.num_layers {
1020 return Err(CmfError::Parse(format!(
1021 "arch.attention_heads_per_layer has {} entries, expected {}",
1022 heads.len(),
1023 arch.num_layers
1024 )));
1025 }
1026 if let Some((li, &nh)) = heads
1027 .iter()
1028 .enumerate()
1029 .find(|(_, nh)| **nh == 0 || **nh % arch.num_kv_heads != 0)
1030 {
1031 return Err(CmfError::Parse(format!(
1032 "layer {li} has {nh} Q heads, which must be nonzero and divisible by {} KV heads",
1033 arch.num_kv_heads
1034 )));
1035 }
1036 }
1037 if arch
1038 .layer_types
1039 .iter()
1040 .any(|t| matches!(t, LayerType::SlidingAttention))
1041 && arch.sliding_window.is_none()
1042 {
1043 return Err(CmfError::Parse(
1044 "model has SlidingAttention layers but no arch.sliding_window".into(),
1045 ));
1046 }
1047 validate_flint_profile(model, &arch)?;
1048
1049 let heads_masked = model.masks.masks.iter().any(|m| {
1059 m.head_masks.iter().any(|row| {
1060 let mut bits = 0usize;
1061 for &b in row.iter() {
1062 bits += b.count_ones() as usize;
1063 }
1064 !row.is_empty() && bits < arch.num_attention_heads
1065 })
1066 });
1067 let force_f32 = heads_masked; let mut tokenizer = if let Some(vocab_bytes) = &model.vocab {
1071 Tokenizer::from_bytes(vocab_bytes)
1072 .map_err(|e| CmfError::Parse(format!("embedded tokenizer: {e}")))?
1073 } else if let Some(g) = &model.header.genome {
1074 return Err(CmfError::Parse(format!(
1078 "genome '{}' carries no embedded tokenizer (VOCAB) — a sidecar tokenizer.json \
1079 is not part of the genome; refusing",
1080 g.id
1081 )));
1082 } else {
1083 let sidecar = model.path.with_file_name("tokenizer.json");
1084 if sidecar.exists() {
1085 Tokenizer::from_file(&sidecar)
1086 .map_err(|e| CmfError::Parse(format!("sidecar tokenizer: {e}")))?
1087 } else {
1088 tracing::warn!("no tokenizer in file or sidecar — using byte-level fallback");
1089 Tokenizer::byte_level()
1090 }
1091 };
1092 if let Some(tc) = &model.header.tokenizer_config {
1094 tokenizer.chat_template = tc.chat_template.clone();
1095 tokenizer.extra_eos.extend(tc.eos_token_ids.iter().copied());
1096 if tokenizer.bos_token_id.is_none() {
1097 tokenizer.bos_token_id = tc.bos_token_id;
1098 }
1099 tracing::info!(
1100 "chat bundle: template {} chars, {} stop ids",
1101 tc.chat_template.as_deref().map(str::len).unwrap_or(0),
1102 tc.eos_token_ids.len()
1103 );
1104 }
1105 if arch.arch_name.to_lowercase().contains("gemma") && tokenizer.bos_token_id.is_some() {
1109 tokenizer.add_bos = true;
1110 }
1111
1112 let embed_tokens = load_matrix(model, "model.embed_tokens.weight", false, ov)?;
1114 let final_norm = if arch.qwen4_exp.is_some() {
1115 vec![0.0; arch.hidden_size]
1118 } else {
1119 load_f32(model, "model.norm.weight", ov).map_err(err)?
1120 };
1121 let lm_head = if model.tensor("lm_head.weight").is_some() {
1122 load_matrix(model, "lm_head.weight", false, ov)?
1123 } else if arch.tie_word_embeddings {
1124 load_matrix(model, "model.embed_tokens.weight", false, ov)?
1126 } else if encoder_only {
1127 QTensor::from_f32(vec![0.0; arch.hidden_size], 1, arch.hidden_size)
1132 } else {
1133 return Err(CmfError::MissingTensor(
1134 "lm_head.weight (and tie_word_embeddings is false)".into(),
1135 ));
1136 };
1137
1138 let has_linear = arch
1140 .layer_types
1141 .iter()
1142 .any(|t| matches!(t, LayerType::LinearAttention));
1143 let mut vmf_cfg = None;
1144 let mut gdn_cfg = None;
1145 let mut phase_delta_layers: Option<Vec<usize>> = None;
1150 if !has_linear
1151 && arch
1152 .linear_core
1153 .as_ref()
1154 .is_some_and(|lc| lc.phase_delta_layers.is_some())
1155 {
1156 return Err(CmfError::Parse(
1157 "phase_delta_layers is only valid with LinearAttention and \
1158 vmf_phase_delta_v1"
1159 .into(),
1160 ));
1161 }
1162 if has_linear {
1163 let lc = arch.linear_core.as_ref().ok_or_else(|| {
1164 CmfError::Parse(
1165 "model has LinearAttention layers but no arch.linear_core — \
1166 reconvert with the current converter"
1167 .into(),
1168 )
1169 })?;
1170 let need = |v: Option<usize>, name: &str| {
1171 v.ok_or_else(|| CmfError::Parse(format!("linear core needs arch.{name}")))
1172 };
1173 match lc.kind.as_str() {
1174 "vmf_phase" => {
1175 if lc.phase_delta_layers.is_some() {
1176 return Err(CmfError::Parse(
1177 "legacy vmf_phase cannot carry phase_delta_layers; use vmf_phase_delta_v1"
1178 .into(),
1179 ));
1180 }
1181 vmf_cfg = Some(VmfPhaseCfg {
1182 num_heads: lc.num_heads,
1183 nphase: need(lc.nphase, "linear_core.nphase")?,
1184 value_head_dim: lc.value_head_dim,
1185 hidden_size: arch.hidden_size,
1186 phase_mass: std::env::var("CMF_PHASE_MASS")
1189 .ok()
1190 .and_then(|v| v.parse().ok())
1191 .unwrap_or(0.0),
1192 });
1193 }
1194 "vmf_phase_delta_v1" => {
1195 let mut selected = lc.phase_delta_layers.clone().ok_or_else(|| {
1196 CmfError::Parse(
1197 "vmf_phase_delta_v1 requires a non-empty phase_delta_layers selector"
1198 .into(),
1199 )
1200 })?;
1201 if selected.is_empty() {
1202 return Err(CmfError::Parse(
1203 "vmf_phase_delta_v1 phase_delta_layers is empty".into(),
1204 ));
1205 }
1206 if arch.layer_types.len() != arch.num_layers {
1207 return Err(CmfError::Parse(format!(
1208 "phase_delta layer schedule has {} entries, expected {}",
1209 arch.layer_types.len(),
1210 arch.num_layers
1211 )));
1212 }
1213 selected.sort_unstable();
1214 for pair in selected.windows(2) {
1215 if pair[0] == pair[1] {
1216 return Err(CmfError::Parse(format!(
1217 "phase_delta_layers contains duplicate layer {}",
1218 pair[0]
1219 )));
1220 }
1221 }
1222 for &li in &selected {
1223 if li >= arch.num_layers {
1224 return Err(CmfError::Parse(format!(
1225 "phase_delta layer {li} out of range for {} layers",
1226 arch.num_layers
1227 )));
1228 }
1229 if !matches!(arch.layer_types[li], LayerType::LinearAttention) {
1230 return Err(CmfError::Parse(format!(
1231 "phase_delta layer {li} is not a LinearAttention layer"
1232 )));
1233 }
1234 }
1235 let nphase = need(lc.nphase, "linear_core.nphase")?;
1236 if lc.num_heads == 0 || nphase == 0 || lc.value_head_dim == 0 {
1237 return Err(CmfError::Parse(
1238 "vmf_phase_delta_v1 requires positive heads, nphase, and value_head_dim"
1239 .into(),
1240 ));
1241 }
1242 phase_delta_layers = Some(selected);
1243 vmf_cfg = Some(VmfPhaseCfg {
1244 num_heads: lc.num_heads,
1245 nphase,
1246 value_head_dim: lc.value_head_dim,
1247 hidden_size: arch.hidden_size,
1248 phase_mass: 0.0,
1253 });
1254 }
1255 "gated_delta_net" => {
1256 if lc.phase_delta_layers.is_some() {
1257 return Err(CmfError::Parse(
1258 "gated_delta_net cannot carry phase_delta_layers".into(),
1259 ));
1260 }
1261 gdn_cfg = Some(GdnCfg {
1262 num_v_heads: lc.num_heads,
1263 num_k_heads: need(arch.linear_num_key_heads, "linear_num_key_heads")?,
1264 key_head_dim: need(arch.linear_key_head_dim, "linear_key_head_dim")?,
1265 value_head_dim: lc.value_head_dim,
1266 conv_kernel: need(arch.linear_conv_kernel_dim, "linear_conv_kernel_dim")?,
1267 hidden_size: arch.hidden_size,
1268 rms_eps: arch.rms_norm_eps,
1269 output_gate_sigmoid: false,
1270 });
1271 }
1272 other => {
1273 return Err(CmfError::Parse(format!(
1274 "unknown linear core '{other}' (this runtime executes: \
1275 gated_delta_net, vmf_phase, vmf_phase_delta_v1)"
1276 )));
1277 }
1278 }
1279 }
1280
1281 let has_kda = arch.layer_types.iter().any(|t| matches!(t, LayerType::Kda));
1283 let kda_cfg = if has_kda {
1284 let need = |v: Option<usize>, name: &str| {
1285 v.ok_or_else(|| CmfError::Parse(format!("KDA core needs arch.{name}")))
1286 };
1287 Some(crate::linear_core::KdaCfg {
1288 num_heads: need(arch.linear_num_key_heads, "linear_num_key_heads")?,
1289 head_k_dim: need(arch.linear_key_head_dim, "linear_key_head_dim")?,
1290 head_v_dim: need(arch.linear_value_head_dim, "linear_value_head_dim")?,
1291 conv_kernel: need(arch.linear_conv_kernel_dim, "linear_conv_kernel_dim")?,
1292 hidden_size: arch.hidden_size,
1293 rms_eps: arch.rms_norm_eps,
1294 })
1295 } else {
1296 None
1297 };
1298
1299 let has_short_conv = arch
1301 .layer_types
1302 .iter()
1303 .any(|t| matches!(t, LayerType::ShortConv));
1304 let short_conv_cfg = if has_short_conv {
1305 Some(ShortConvCfg {
1306 hidden_size: arch.hidden_size,
1307 kernel: arch.linear_conv_kernel_dim.ok_or_else(|| {
1308 CmfError::Parse(
1309 "model has ShortConv layers but no arch.linear_conv_kernel_dim — \
1310 reconvert with the current converter"
1311 .into(),
1312 )
1313 })?,
1314 })
1315 } else {
1316 None
1317 };
1318
1319 let load_full_attn = |prefix: &str, layer: Option<usize>| -> Result<AttnKind, CmfError> {
1321 let t = |suffix: &str| load_matrix(model, &format!("{prefix}{suffix}"), force_f32, ov);
1322 let n = |suffix: &str| -> Option<Vec<f32>> {
1323 model
1324 .tensor(&format!("{prefix}{suffix}"))
1325 .and_then(|_| load_f32(model, &format!("{prefix}{suffix}"), ov).ok())
1326 };
1327 if let Some(mla) = arch.mla.as_ref() {
1329 let (q_proj, q_a, q_a_norm) = if mla.q_lora_rank.is_some() {
1331 (
1332 t("self_attn.q_b_proj.weight")?,
1333 Some(t("self_attn.q_a_proj.weight")?),
1334 Some(n("self_attn.q_a_layernorm.weight").ok_or_else(|| {
1335 CmfError::Parse(format!("{prefix}: MLA needs q_a_layernorm"))
1336 })?),
1337 )
1338 } else {
1339 (t("self_attn.q_proj.weight")?, None, None)
1340 };
1341 let hd = mla.qk_rope_head_dim + mla.qk_nope_head_dim;
1342 let nh = q_proj.rows() / hd;
1343 let mut scale = 1.0 / (hd as f32).sqrt();
1346 if let Some(y) = arch.yarn.as_ref() {
1347 if let Some(m) = y.mscale_all_dim.filter(|&m| m > 0.0) {
1348 let ms = 0.1 * m * y.factor.ln() + 1.0;
1349 scale *= ms * ms;
1350 }
1351 }
1352 return Ok(AttnKind::Mla(Box::new(crate::pipeline::MlaWeights {
1353 q_proj,
1354 q_a,
1355 q_a_norm,
1356 kv_a: t("self_attn.kv_a_proj_with_mqa.weight")?,
1357 kv_a_norm: n("self_attn.kv_a_layernorm.weight").ok_or_else(|| {
1358 CmfError::Parse(format!("{prefix}: MLA needs kv_a_layernorm"))
1359 })?,
1360 kv_b: t("self_attn.kv_b_proj.weight")?,
1361 o_proj: t("self_attn.o_proj.weight")?,
1362 nh,
1363 qk_rope: mla.qk_rope_head_dim,
1364 qk_nope: mla.qk_nope_head_dim,
1365 v_dim: mla.v_head_dim,
1366 lora: mla.kv_lora_rank,
1367 scale,
1368 nope: mla.nope,
1369 })));
1370 }
1371 let wq = t("self_attn.q_proj.weight")?;
1372 let nh = layer
1373 .and_then(|li| {
1374 arch.attention_heads_per_layer
1375 .as_ref()
1376 .and_then(|v| v.get(li).copied())
1377 })
1378 .unwrap_or(arch.num_attention_heads);
1379 let output_gate = arch.global_head_dim.is_none() && wq.rows() == 2 * nh * arch.head_dim;
1383 let is_global_layer = arch.global_head_dim.is_some()
1386 && layer.is_some_and(|li| {
1387 arch.sliding_window_pattern
1388 .is_some_and(|p| p > 0 && (li + 1) % p == 0)
1389 });
1390 let expect = if is_global_layer {
1391 nh * arch.global_head_dim.unwrap_or(arch.head_dim)
1392 } else {
1393 nh * arch.head_dim
1394 };
1395 if !output_gate && wq.rows() != expect {
1396 return Err(CmfError::Parse(format!(
1397 "{prefix}self_attn.q_proj.weight rows={} != heads({nh}) * head_dim({})",
1398 wq.rows(),
1399 expect / nh.max(1)
1400 )));
1401 }
1402 let gate_name = format!("{prefix}self_attn.g_proj.weight");
1403 let softplus_gate = if model.tensor(&gate_name).is_some() {
1404 let gate = load_matrix(model, &gate_name, force_f32, ov)?;
1405 if gate.cols() != arch.hidden_size {
1406 return Err(CmfError::Parse(format!(
1407 "{gate_name} cols={} != hidden_size ({})",
1408 gate.cols(),
1409 arch.hidden_size
1410 )));
1411 }
1412 let per_head = if gate.rows() == nh {
1413 true
1414 } else if gate.rows() == nh * arch.head_dim {
1415 false
1416 } else {
1417 return Err(CmfError::Parse(format!(
1418 "{gate_name} rows={} must equal heads ({nh}) or heads*head_dim ({})",
1419 gate.rows(),
1420 nh * arch.head_dim
1421 )));
1422 };
1423 Some((gate, per_head))
1424 } else {
1425 None
1426 };
1427 let bias = match (
1429 n("self_attn.q_proj.bias"),
1430 n("self_attn.k_proj.bias"),
1431 n("self_attn.v_proj.bias"),
1432 ) {
1433 (Some(a), Some(b), Some(c)) => Some((a, b, c)),
1434 _ => None,
1435 };
1436 let wk = t("self_attn.k_proj.weight")?;
1437 let wv = t("self_attn.v_proj.weight")?;
1438 let wo = t("self_attn.o_proj.weight")?;
1439 let (nkv_l, hd_l) = if is_global_layer {
1447 (
1448 arch.num_global_kv_heads.unwrap_or(arch.num_kv_heads),
1449 arch.global_head_dim.unwrap_or(arch.head_dim),
1450 )
1451 } else {
1452 (
1453 layer
1454 .and_then(|li| {
1455 arch.kv_heads_per_layer
1456 .as_ref()
1457 .and_then(|v| v.get(li).copied())
1458 })
1459 .unwrap_or(arch.num_kv_heads),
1460 arch.head_dim,
1461 )
1462 };
1463 let vd = if is_global_layer {
1464 hd_l
1465 } else {
1466 arch.v_head_dim.unwrap_or(hd_l)
1467 };
1468 let shape_err = |what: String| {
1469 CmfError::Parse(format!(
1470 "{prefix}self_attn: {what} (heads {nh}, kv heads {nkv_l}, head_dim {hd_l}, \
1471 v_head_dim {vd}) — file and header disagree"
1472 ))
1473 };
1474 if nkv_l == 0 || nh % nkv_l != 0 {
1475 return Err(shape_err(format!(
1476 "{nkv_l} KV heads must be nonzero and divide {nh} Q heads"
1477 )));
1478 }
1479 if vd == 0 || vd > hd_l {
1480 return Err(shape_err(format!("v_head_dim {vd} must be in 1..={hd_l}")));
1481 }
1482 if wk.rows() != nkv_l * hd_l {
1483 return Err(shape_err(format!(
1484 "k_proj rows={} != kv_heads*head_dim={}",
1485 wk.rows(),
1486 nkv_l * hd_l
1487 )));
1488 }
1489 if wv.rows() != nkv_l * vd {
1490 return Err(shape_err(format!(
1491 "v_proj rows={} != kv_heads*v_head_dim={}",
1492 wv.rows(),
1493 nkv_l * vd
1494 )));
1495 }
1496 if wo.cols() != nh * vd {
1497 return Err(shape_err(format!(
1498 "o_proj cols={} != heads*v_head_dim={}",
1499 wo.cols(),
1500 nh * vd
1501 )));
1502 }
1503 if vd < hd_l && (output_gate || softplus_gate.is_some()) {
1504 return Err(shape_err(
1505 "an attention output gate with V heads narrower than Q/K is not supported"
1506 .into(),
1507 ));
1508 }
1509 Ok(AttnKind::Full {
1510 wq,
1511 wk,
1512 wv,
1513 wo,
1514 q_norm: n("self_attn.q_norm.weight"),
1515 k_norm: n("self_attn.k_norm.weight"),
1516 output_gate,
1517 softplus_gate,
1518 bias,
1519 })
1520 };
1521
1522 let load_linear_attn = |prefix: &str, li: usize| -> Result<AttnKind, CmfError> {
1523 if gdn_cfg.is_some() {
1524 let t = |suffix: &str| {
1526 load_matrix(
1527 model,
1528 &format!("{prefix}linear_attn.{suffix}"),
1529 force_f32,
1530 ov,
1531 )
1532 };
1533 let f = |suffix: &str| {
1534 load_f32(model, &format!("{prefix}linear_attn.{suffix}"), ov).map_err(err)
1535 };
1536 return Ok(AttnKind::LinearGdn(GdnWeights {
1537 in_proj_qkv: t("in_proj_qkv.weight")?,
1538 in_proj_z: t("in_proj_z.weight")?,
1539 in_proj_a: t("in_proj_a.weight")?,
1540 in_proj_b: t("in_proj_b.weight")?,
1541 conv1d: f("conv1d.weight")?,
1542 a_log: f("A_log")?,
1543 dt_bias: f("dt_bias")?,
1544 norm: f("norm.weight")?,
1545 out_proj: t("out_proj.weight")?,
1546 }));
1547 }
1548 let t = |suffix: &str| {
1549 load_matrix(model, &format!("{prefix}vmf_attn.{suffix}"), force_f32, ov)
1550 };
1551 let a_log = load_f32(model, &format!("{prefix}vmf_attn.A_log"), ov).map_err(err)?;
1552 let k_gate = if model
1556 .tensor(&format!("{prefix}vmf_attn.k_gate.weight"))
1557 .is_some()
1558 {
1559 Some((
1560 t("k_gate.weight")?,
1561 load_f32(model, &format!("{prefix}vmf_attn.k_gate.bias"), ov).map_err(err)?,
1562 ))
1563 } else {
1564 None
1565 };
1566 let conv = if model
1569 .tensor(&format!("{prefix}vmf_attn.conv1d.weight"))
1570 .is_some()
1571 {
1572 Some(load_f32(model, &format!("{prefix}vmf_attn.conv1d.weight"), ov).map_err(err)?)
1573 } else {
1574 None
1575 };
1576 let thq = t("thq.weight")?;
1577 let thk = t("thk.weight")?;
1578 let v_proj = t("v_proj.weight")?;
1579 let out_proj = t("out_proj.weight")?;
1580 let phase_delta = phase_delta_layers
1581 .as_ref()
1582 .is_some_and(|layers| layers.binary_search(&li).is_ok());
1583 if phase_delta {
1584 let cfg = vmf_cfg.ok_or_else(|| {
1585 CmfError::Parse("phase_delta layer has no VMF geometry".into())
1586 })?;
1587 let expect = |name: &str, got: (usize, usize), want: (usize, usize)| {
1588 if got != want {
1589 Err(CmfError::Parse(format!(
1590 "phase_delta {name} geometry [{}, {}] != [{}, {}]",
1591 got.0, got.1, want.0, want.1
1592 )))
1593 } else {
1594 Ok(())
1595 }
1596 };
1597 expect(
1598 "thq",
1599 (thq.rows(), thq.cols()),
1600 (cfg.num_heads * cfg.nphase, cfg.hidden_size),
1601 )?;
1602 expect(
1603 "thk",
1604 (thk.rows(), thk.cols()),
1605 (cfg.num_heads * cfg.nphase, cfg.hidden_size),
1606 )?;
1607 expect(
1608 "v_proj",
1609 (v_proj.rows(), v_proj.cols()),
1610 (cfg.num_heads * cfg.value_head_dim, cfg.hidden_size),
1611 )?;
1612 expect(
1613 "out_proj",
1614 (out_proj.rows(), out_proj.cols()),
1615 (cfg.hidden_size, cfg.num_heads * cfg.value_head_dim),
1616 )?;
1617 if a_log.len() != cfg.num_heads * 2 * cfg.nphase {
1618 return Err(CmfError::Parse(format!(
1619 "phase_delta A_log length {} != {}",
1620 a_log.len(),
1621 cfg.num_heads * 2 * cfg.nphase
1622 )));
1623 }
1624 if let Some((kw, kb)) = &k_gate {
1625 expect(
1626 "k_gate",
1627 (kw.rows(), kw.cols()),
1628 (cfg.num_heads, cfg.hidden_size),
1629 )?;
1630 if kb.len() != cfg.num_heads {
1631 return Err(CmfError::Parse(format!(
1632 "phase_delta k_gate.bias length {} != {}",
1633 kb.len(),
1634 cfg.num_heads
1635 )));
1636 }
1637 }
1638 if let Some(c) = &conv {
1639 let n = model
1640 .tensor(&format!("{prefix}vmf_attn.conv1d.weight"))
1641 .ok_or_else(|| {
1642 CmfError::Parse(
1643 "phase_delta conv tensor disappeared during load".into(),
1644 )
1645 })?;
1646 if n.shape.len() != 3
1647 || n.shape[0] != cfg.hidden_size
1648 || n.shape[1] != 1
1649 || n.shape[2] < 2
1650 || c.len() != n.shape.iter().product::<usize>()
1651 {
1652 return Err(CmfError::Parse(format!(
1653 "phase_delta conv geometry {:?} / {} bytes is incompatible",
1654 n.shape,
1655 c.len()
1656 )));
1657 }
1658 }
1659 }
1660 Ok(AttnKind::Linear(VmfPhaseWeights {
1661 thq,
1662 conv,
1663 thk,
1664 v_proj,
1665 out_proj,
1666 decay: a_log.iter().map(|&a| (-(a as f64).exp()).exp()).collect(),
1667 k_gate,
1668 phase_delta,
1669 }))
1670 };
1671
1672 let load_short_conv = |prefix: &str| -> Result<AttnKind, CmfError> {
1676 let t = |suffix: &str| {
1677 load_matrix(
1678 model,
1679 &format!("{prefix}short_conv.{suffix}"),
1680 force_f32,
1681 ov,
1682 )
1683 };
1684 Ok(AttnKind::ShortConv(ShortConvWeights {
1685 in_proj: t("in_proj.weight")?,
1686 conv: load_f32(model, &format!("{prefix}short_conv.conv.weight"), ov)
1687 .map_err(err)?,
1688 out_proj: t("out_proj.weight")?,
1689 }))
1690 };
1691
1692 let load_kda = |prefix: &str| -> Result<AttnKind, CmfError> {
1696 let t = |suffix: &str| {
1697 load_matrix(model, &format!("{prefix}kda_attn.{suffix}"), force_f32, ov)
1698 };
1699 let f = |suffix: &str| {
1700 load_f32(model, &format!("{prefix}kda_attn.{suffix}"), ov).map_err(err)
1701 };
1702 let gate = if model
1703 .tensor(&format!("{prefix}kda_attn.g_proj.weight"))
1704 .is_some()
1705 {
1706 crate::linear_core::KdaOutGate::Full(t("g_proj.weight")?)
1707 } else {
1708 crate::linear_core::KdaOutGate::LowRank(
1709 t("g_a_proj.weight")?,
1710 t("g_b_proj.weight")?,
1711 )
1712 };
1713 Ok(AttnKind::Kda(Box::new(crate::linear_core::KdaWeights {
1714 q_proj: t("q_proj.weight")?,
1715 k_proj: t("k_proj.weight")?,
1716 v_proj: t("v_proj.weight")?,
1717 conv_q: f("q_conv1d.weight")?,
1718 conv_k: f("k_conv1d.weight")?,
1719 conv_v: f("v_conv1d.weight")?,
1720 f_a: t("f_a_proj.weight")?,
1721 f_b: t("f_b_proj.weight")?,
1722 dt_bias: f("dt_bias")?,
1723 a_log: f("A_log")?,
1724 b_proj: t("b_proj.weight")?,
1725 gate,
1726 o_norm: f("o_norm.weight")?,
1727 o_proj: t("o_proj.weight")?,
1728 gate_lower_bound: arch.kda_gate_lower_bound.map(|v| v as f32),
1729 })))
1730 };
1731
1732 let load_bounded_attn = |prefix: &str, li: usize| -> Result<AttnKind, CmfError> {
1739 let ac = arch.anchor_core.as_ref().ok_or_else(|| {
1740 CmfError::Parse(format!(
1741 "layer {li} is BoundedAttention but the header carries no anchor_core"
1742 ))
1743 })?;
1744 let t = |suffix: &str| load_matrix(model, &format!("{prefix}{suffix}"), force_f32, ov);
1745 let (nkv, hd, nh) = (arch.num_kv_heads, arch.head_dim, arch.num_attention_heads);
1746 for extra in [
1747 "self_attn.q_norm.weight",
1748 "self_attn.k_norm.weight",
1749 "self_attn.g_proj.weight",
1750 "self_attn.q_proj.bias",
1751 "self_attn.k_proj.bias",
1752 "self_attn.v_proj.bias",
1753 ] {
1754 if model.tensor(&format!("{prefix}{extra}")).is_some() {
1755 return Err(CmfError::Parse(format!(
1756 "{prefix}{extra}: the bounded anchor operator carries no qk-norm, \
1757 gate or bias (docs/EMBRYO_BOUNDED_ANCHOR.md §1)"
1758 )));
1759 }
1760 }
1761 let sink = |name: &str| -> Result<Vec<f32>, CmfError> {
1762 let full = format!("{prefix}self_attn.{name}.weight");
1763 let e = model.tensor(&full).ok_or_else(|| {
1764 CmfError::MissingTensor(format!(
1765 "{full} (a bounded anchor needs its trained sinks)"
1766 ))
1767 })?;
1768 if e.shape.len() != 3
1769 || e.shape[0] != nkv
1770 || e.shape[1] != ac.sink
1771 || e.shape[2] != hd
1772 {
1773 return Err(CmfError::Parse(format!(
1774 "{full}: shape {:?} != [{nkv}, {}, {hd}] (num_kv_heads, anchor_core.sink, head_dim)",
1775 e.shape, ac.sink
1776 )));
1777 }
1778 load_f32(model, &full, ov).map_err(err)
1779 };
1780 let wq = t("self_attn.q_proj.weight")?;
1781 let wk = t("self_attn.k_proj.weight")?;
1782 let wv = t("self_attn.v_proj.weight")?;
1783 let wo = t("self_attn.o_proj.weight")?;
1784 let expect = |name: &str, got: (usize, usize), want: (usize, usize)| {
1785 if got != want {
1786 Err(CmfError::Parse(format!(
1787 "{prefix}self_attn.{name}.weight is {}x{}, expected {}x{}",
1788 got.0, got.1, want.0, want.1
1789 )))
1790 } else {
1791 Ok(())
1792 }
1793 };
1794 expect("q_proj", (wq.rows(), wq.cols()), (nh * hd, arch.hidden_size))?;
1795 expect("k_proj", (wk.rows(), wk.cols()), (nkv * hd, arch.hidden_size))?;
1796 expect("v_proj", (wv.rows(), wv.cols()), (nkv * hd, arch.hidden_size))?;
1797 expect("o_proj", (wo.rows(), wo.cols()), (arch.hidden_size, nh * hd))?;
1798 Ok(AttnKind::Bounded(Box::new(crate::bounded::BoundedWeights {
1799 wq,
1800 wk,
1801 wv,
1802 wo,
1803 sink_k: sink("sink_k")?,
1804 sink_v: sink("sink_v")?,
1805 sink: ac.sink,
1806 window: ac.window,
1807 })))
1808 };
1809
1810 fn anyhow_like(ok: bool) -> Result<(), ()> {
1811 if ok { Ok(()) } else { Err(()) }
1812 }
1813 let mut layers = Vec::with_capacity(arch.num_layers);
1814 let is_g3n = arch.g3n.is_some();
1815 let owns_its_layers = is_g3n
1820 || arch.arch_name == "deepseek_v4"
1821 || arch.arch_name == "deepseek_v41"
1822 || arch.qwen4_exp.is_some();
1823 for li in 0..(if owns_its_layers { 0 } else { arch.num_layers }) {
1824 let prefix = format!("model.layers.{li}.");
1825 let attn = match arch.layer_types.get(li) {
1826 Some(LayerType::LinearAttention) => load_linear_attn(&prefix, li)?,
1827 Some(LayerType::Kda) => load_kda(&prefix)?,
1828 Some(LayerType::ShortConv) => load_short_conv(&prefix)?,
1829 Some(LayerType::BoundedAttention) => load_bounded_attn(&prefix, li)?,
1830 _ => load_full_attn(&prefix, Some(li))?,
1831 };
1832 let pre_ffn = format!("{prefix}pre_feedforward_layernorm.weight");
1836 let sandwich = model.tensor(&pre_ffn).is_some();
1837 layers.push(LayerWeights {
1838 input_norm: load_f32(model, &format!("{prefix}input_layernorm.weight"), ov)
1839 .map_err(err)?,
1840 post_norm: if sandwich {
1841 load_f32(model, &pre_ffn, ov).map_err(err)?
1842 } else {
1843 load_f32(
1844 model,
1845 &format!("{prefix}post_attention_layernorm.weight"),
1846 ov,
1847 )
1848 .map_err(err)?
1849 },
1850 attn_out_norm: if sandwich {
1851 Some(
1852 load_f32(
1853 model,
1854 &format!("{prefix}post_attention_layernorm.weight"),
1855 ov,
1856 )
1857 .map_err(err)?,
1858 )
1859 } else {
1860 None
1861 },
1862 ffn_out_norm: if sandwich {
1863 Some(
1864 load_f32(
1865 model,
1866 &format!("{prefix}post_feedforward_layernorm.weight"),
1867 ov,
1868 )
1869 .map_err(err)?,
1870 )
1871 } else {
1872 None
1873 },
1874 layer_scale: model
1876 .tensor(&format!("{prefix}layer_scalar"))
1877 .and_then(|_| {
1878 load_f32(model, &format!("{prefix}layer_scalar"), ov)
1879 .ok()
1880 .and_then(|v| v.first().copied())
1881 }),
1882 ffn: build_layer_ffn(model, &arch, li, false, ov)?,
1884 attn,
1885 });
1886 }
1887
1888 let mtp_present = model
1897 .tensor("model.mtp.layers.0.self_attn.q_proj.weight")
1898 .is_some()
1899 || model.tensor("model.mtp.eh_proj.weight").is_some();
1900 let dsv4_mtp = model.tensor("model.mtp.0.main_proj.weight").is_some();
1905 if arch.mtp.is_some() && !mtp_present && !dsv4_mtp {
1906 tracing::info!(
1907 "header declares an MTP head but the file carries none — \
1908 loading without it"
1909 );
1910 }
1911 let mtp = if let Some(cfg) = arch
1914 .mtp
1915 .as_ref()
1916 .filter(|_| mtp_present && arch.arch_name != "mimo_v2")
1917 {
1918 if cfg.num_layers != 1 {
1919 return Err(CmfError::Parse(format!(
1920 "MTP with {} blocks not supported yet (only 1)",
1921 cfg.num_layers
1922 )));
1923 }
1924 let p = "model.mtp.";
1925 let attn = load_full_attn("model.mtp.layers.0.", None)?;
1926 Some(MtpModule {
1927 enorm: load_f32(model, &format!("{p}enorm.weight"), ov).map_err(err)?,
1928 hnorm: load_f32(model, &format!("{p}hnorm.weight"), ov).map_err(err)?,
1929 eh_proj: load_matrix(model, &format!("{p}eh_proj.weight"), false, ov)?,
1930 layer: LayerWeights {
1931 attn_out_norm: None,
1932 ffn_out_norm: None,
1933 layer_scale: None,
1934 input_norm: load_f32(model, &format!("{p}layers.0.input_layernorm.weight"), ov)
1935 .map_err(err)?,
1936 post_norm: load_f32(
1937 model,
1938 &format!("{p}layers.0.post_attention_layernorm.weight"),
1939 ov,
1940 )
1941 .map_err(err)?,
1942 ffn: build_ffn_at(model, &arch, &format!("{p}layers.0."), false, ov)?,
1946 attn,
1947 },
1948 final_norm: load_f32(model, &format!("{p}norm.weight"), ov).map_err(err)?,
1949 kv: LayerKvCache::new(arch.num_kv_heads, arch.head_dim),
1950 })
1951 } else {
1952 None
1953 };
1954
1955 tracing::info!(
1956 "Pipeline loaded: {} | {}L ({} linear) | {:.2}B params | storage: {} | MTP: {}",
1957 arch.arch_name,
1958 arch.num_layers,
1959 arch.layer_types
1960 .iter()
1961 .filter(|t| matches!(t, LayerType::LinearAttention))
1962 .count(),
1963 model.total_param_count() as f64 / 1e9,
1964 if force_f32 {
1965 "f32 (masked)"
1966 } else {
1967 "quantized mmap"
1968 },
1969 if mtp.is_some() { "yes" } else { "no" }
1970 );
1971
1972 let cap = std::env::var("CMF_MAX_SEQ")
1983 .ok()
1984 .and_then(|v| v.parse::<usize>().ok())
1985 .unwrap_or(32_768);
1986 let max_seq_len = arch.max_position_embeddings.min(cap);
1987
1988 let total_layers = arch.num_layers * arch.num_loops;
1990
1991 let mut pipeline = Pipeline::new(
1992 tokenizer,
1993 PipelineWeights {
1994 embed_tokens,
1995 layers,
1996 lm_head,
1997 final_norm,
1998 },
1999 arch.hidden_size,
2000 arch.intermediate_size,
2001 arch.num_attention_heads,
2002 arch.num_kv_heads,
2003 arch.head_dim,
2004 total_layers,
2005 arch.num_layers, arch.loop_final_norm,
2007 arch.vocab_size,
2008 arch.rms_norm_eps,
2009 arch.rope_theta as f32,
2010 arch.norm_style,
2011 max_seq_len,
2012 sampler_config,
2013 );
2014 let rotary = ((arch.head_dim as f32 * arch.partial_rotary_factor) as usize).max(2);
2015 pipeline.set_rotary(rotary, arch.rope_theta as f32);
2016 pipeline.attention_heads_per_layer = arch.attention_heads_per_layer.clone();
2017 if let Some(yarn) = &arch.yarn {
2018 pipeline.inv_freq = std::sync::Arc::new(crate::attention::yarn_inv_freq(
2019 rotary,
2020 arch.rope_theta as f32,
2021 yarn.factor,
2022 yarn.original_max_position_embeddings,
2023 yarn.beta_fast,
2024 yarn.beta_slow,
2025 ));
2026 pipeline.rope_scale = yarn.attention_factor;
2027 }
2028 pipeline.embed_multiplier = arch.embed_multiplier;
2032 pipeline.logit_multiplier = arch.logit_multiplier;
2033 if let Some(qpas) = arch.query_pre_attn_scalar {
2034 pipeline.attn_scale = 1.0 / (qpas as f32).sqrt();
2035 }
2036 if let (Some(w), Some(p)) = (arch.sliding_window, arch.sliding_window_pattern) {
2037 pipeline.swa = Some((w, p));
2038 if let Some(base) = arch.rope_local_base_freq {
2039 pipeline.inv_freq_local = Some(std::sync::Arc::new(
2040 crate::attention::rope_inv_freq(rotary, base as f32),
2041 ));
2042 }
2043 }
2044 let explicit_sliding: Vec<bool> = arch
2045 .layer_types
2046 .iter()
2047 .map(|t| matches!(t, cortiq_core::LayerType::SlidingAttention))
2048 .collect();
2049 if explicit_sliding.iter().any(|&v| v) {
2050 pipeline.sliding_layers = Some(explicit_sliding);
2051 if let Some(w) = arch.sliding_window {
2052 pipeline.swa = Some((w, usize::MAX));
2053 }
2054 let local_rotary = ((arch.head_dim as f32
2055 * arch
2056 .local_partial_rotary_factor
2057 .unwrap_or(arch.partial_rotary_factor))
2058 as usize)
2059 .max(2);
2060 pipeline.rotary_dim_local = Some(local_rotary);
2061 if let Some(base) = arch.rope_local_base_freq {
2062 pipeline.inv_freq_local = Some(std::sync::Arc::new(
2063 crate::attention::rope_inv_freq(local_rotary, base as f32),
2064 ));
2065 }
2066 }
2067 if let (Some(ghd), Some(gkv)) = (arch.global_head_dim, arch.num_global_kv_heads) {
2071 pipeline.global_attn = Some((ghd, gkv));
2072 let prf = arch.global_partial_rotary_factor.unwrap_or(1.0);
2073 let half = ghd / 2;
2074 let ra = (((prf * ghd as f32) as usize) / 2).min(half);
2075 let mut f = vec![0.0f32; half];
2076 for (i, slot) in f.iter_mut().enumerate().take(ra) {
2077 *slot = 1.0 / (arch.rope_theta as f32).powf(2.0 * i as f32 / ghd as f32);
2078 }
2079 pipeline.inv_freq_global = Some(std::sync::Arc::new(f));
2080 let global_at = |li: usize| -> bool {
2085 match &pipeline.sliding_layers {
2086 Some(map) => !map.get(li).copied().unwrap_or(false),
2087 None => pipeline
2088 .swa
2089 .map(|(_, p)| p > 0 && p != usize::MAX && (li + 1) % p == 0)
2090 .unwrap_or(false),
2091 }
2092 };
2093 for li in 0..arch.num_layers {
2094 if global_at(li) {
2095 pipeline.kv_cache.layers[li] = crate::kv_cache::LayerKvCache::new(gkv, ghd);
2096 }
2097 }
2098 }
2099 if let Some(mla) = arch.mla.as_ref() {
2102 let hd = mla.qk_rope_head_dim + mla.qk_nope_head_dim;
2103 pipeline.head_dim = hd;
2104 pipeline.num_kv_heads = arch.num_attention_heads;
2105 pipeline.rotary_dim = mla.qk_rope_head_dim;
2106 let half = mla.qk_rope_head_dim / 2;
2107 let mut f = vec![0.0f32; half];
2108 for (i, slot) in f.iter_mut().enumerate() {
2109 *slot = 1.0
2110 / (arch.rope_theta as f32).powf(2.0 * i as f32 / mla.qk_rope_head_dim as f32);
2111 }
2112 pipeline.inv_freq = std::sync::Arc::new(f);
2113 for li in 0..arch.num_layers {
2114 pipeline.kv_cache.layers[li] =
2115 crate::kv_cache::LayerKvCache::new(arch.num_attention_heads, hd);
2116 }
2117 }
2118 if !owns_its_layers {
2123 pipeline
2124 .set_attn_geometry(arch.kv_heads_per_layer.clone(), arch.v_head_dim)
2125 .map_err(|e| CmfError::Parse(format!("attention geometry: {e}")))?;
2126 } else if arch.kv_heads_per_layer.is_some() || arch.v_head_dim.is_some() {
2127 return Err(CmfError::Parse(format!(
2128 "{}: kv_heads_per_layer / v_head_dim are not supported by its own layer stack",
2129 arch.arch_name
2130 )));
2131 }
2132 if !owns_its_layers {
2139 for li in 0..arch.num_layers {
2140 let name = [
2141 format!("model.layers.{li}.self_attn.sinks"),
2142 format!("model.layers.{li}.self_attn.attention_sink_bias"),
2143 format!("model.layers.{li}.self_attn.softmax_offset"),
2147 ]
2148 .into_iter()
2149 .find(|n| model.tensor(n).is_some());
2150 if let Some(name) = name {
2151 let sinks = load_f32(model, &name, ov).map_err(err)?;
2152 pipeline
2153 .set_layer_sinks(li, sinks)
2154 .map_err(|e| CmfError::Parse(format!("{name}: {e}")))?;
2155 }
2156 }
2157 }
2158 if arch.arch_name == "flint" {
2162 pipeline.no_rope_layers = arch.no_rope_layers.clone();
2163 }
2164 if pipeline.kv_heads_per_layer.is_some()
2165 || pipeline.v_head_dim.is_some()
2166 || pipeline.kv_cache.layers.iter().any(|l| l.sinks.is_some())
2167 {
2168 let per_token: usize = pipeline
2169 .kv_cache
2170 .layers
2171 .iter()
2172 .map(|l| 2 * l.num_kv_heads * l.head_dim * std::mem::size_of::<f32>())
2173 .sum();
2174 tracing::info!(
2175 "attention geometry: kv heads per layer {:?}, v_head_dim {:?}, {} sink layer(s); \
2176 f32 KV {} B/token (V padded to head_dim), cap {} tokens",
2177 pipeline.kv_heads_per_layer,
2178 pipeline.v_head_dim,
2179 pipeline
2180 .kv_cache
2181 .layers
2182 .iter()
2183 .filter(|l| l.sinks.is_some())
2184 .count(),
2185 per_token,
2186 pipeline.kv_cache.max_seq_len
2187 );
2188 }
2189 if let Some(fac) = &arch.rope_freq_factors {
2193 let mut f = pipeline.inv_freq.as_ref().clone();
2194 for (i, v) in f.iter_mut().enumerate() {
2195 if let Some(&d) = fac.get(i) {
2196 *v /= d as f32;
2197 }
2198 }
2199 pipeline.inv_freq = std::sync::Arc::new(f);
2200 }
2201 if let Some(selected) = &phase_delta_layers {
2202 for &li in selected {
2203 pipeline.kv_cache.layers[li].set_linear_wire_allowed(false);
2204 }
2205 }
2206 pipeline.attn_v_norm = arch.attn_v_norm;
2207 pipeline.qk_norm_after_rope = arch.qk_norm_after_rope;
2208 pipeline.proj_gate_sigmoid = arch.arch_name == "spark2_5";
2209 pipeline.final_softcap = arch.final_logit_softcapping.map(|c| c as f32);
2210 if let Some(ncl) = arch.head_clusters {
2212 let cm = load_f32(model, "lm_head.clusters.weight", ov).map_err(err)?;
2213 if cm.len() != ncl * arch.hidden_size {
2214 return Err(CmfError::Parse(format!(
2215 "lm_head.clusters.weight: {} != {ncl}×{}",
2216 cm.len(),
2217 arch.hidden_size
2218 )));
2219 }
2220 pipeline.head_clusters = Some(std::sync::Arc::new(cm));
2221 }
2222 pipeline.attn_softcap = arch.attn_logit_softcapping.unwrap_or(0.0) as f32;
2223 pipeline.vmf_cfg = vmf_cfg;
2224 pipeline.gdn_cfg = gdn_cfg;
2225 pipeline.kda_cfg = kda_cfg;
2226 if arch.qwen4_exp.is_some() {
2227 let (globals, layers, cfg, state) = crate::qwen4_exp::load(model, &arch)?;
2228 pipeline.qwen4_exp = Some(Box::new((globals, layers, cfg, state)));
2229 }
2230 if let Some(gc) = arch.g3n.as_ref() {
2231 use crate::g3n::{G3nAltUp, G3nGlobals, G3nLaurel, G3nLayer};
2232 anyhow_like(gc.altup_num_inputs == crate::g3n::ALTUP_N).map_err(|_| {
2233 CmfError::Parse(format!(
2234 "g3n: altup_num_inputs {} != supported {}",
2235 gc.altup_num_inputs,
2236 crate::g3n::ALTUP_N
2237 ))
2238 })?;
2239 let t = |name: &str| load_matrix(model, name, force_f32, ov);
2240 let f = |name: &str| load_f32(model, name, ov).map_err(err);
2241 let mut altup_proj = Vec::new();
2242 let mut altup_unembed = Vec::new();
2243 for i in 0..crate::g3n::ALTUP_N - 1 {
2244 altup_proj.push(t(&format!("model.altup_projections.{i}.weight"))?);
2245 altup_unembed.push(t(&format!("model.altup_unembed_projections.{i}.weight"))?);
2246 }
2247 let first_shared = arch.num_layers.saturating_sub(gc.num_kv_shared_layers);
2248 let sliding_of = |li: usize| {
2249 matches!(
2250 arch.layer_types.get(li),
2251 Some(cortiq_core::LayerType::SlidingAttention)
2252 )
2253 };
2254 let mut g3n_layers = Vec::with_capacity(arch.num_layers);
2255 for li in 0..arch.num_layers {
2256 let pfx = format!("model.layers.{li}.");
2257 let shared = li >= first_shared && first_shared > 0;
2258 let share_src = if shared {
2259 let want = sliding_of(li);
2260 (0..first_shared).rev().find(|&j| sliding_of(j) == want)
2261 } else {
2262 None
2263 };
2264 g3n_layers.push(G3nLayer {
2265 altup: G3nAltUp {
2266 router_norm: f(&format!("{pfx}altup.router_norm.weight"))?,
2267 modality_router: t(&format!("{pfx}altup.modality_router.weight"))?,
2268 prediction_coefs: t(&format!("{pfx}altup.prediction_coefs.weight"))?,
2269 correction_coefs: t(&format!("{pfx}altup.correction_coefs.weight"))?,
2270 correct_output_scale: f(&format!("{pfx}altup.correct_output_scale"))?,
2271 },
2272 laurel: G3nLaurel {
2273 left: t(&format!("{pfx}laurel.linear_left.weight"))?,
2274 right: t(&format!("{pfx}laurel.linear_right.weight"))?,
2275 post_norm: f(&format!("{pfx}laurel.post_laurel_norm.weight"))?,
2276 },
2277 input_norm: f(&format!("{pfx}input_layernorm.weight"))?,
2278 post_attn_norm: f(&format!("{pfx}post_attention_layernorm.weight"))?,
2279 pre_ffw_norm: f(&format!("{pfx}pre_feedforward_layernorm.weight"))?,
2280 post_ffw_norm: f(&format!("{pfx}post_feedforward_layernorm.weight"))?,
2281 wq: t(&format!("{pfx}self_attn.q_proj.weight"))?,
2282 wk: if shared {
2283 None
2284 } else {
2285 Some(t(&format!("{pfx}self_attn.k_proj.weight"))?)
2286 },
2287 wv: if shared {
2288 None
2289 } else {
2290 Some(t(&format!("{pfx}self_attn.v_proj.weight"))?)
2291 },
2292 wo: t(&format!("{pfx}self_attn.o_proj.weight"))?,
2293 q_norm: f(&format!("{pfx}self_attn.q_norm.weight"))?,
2294 k_norm: if shared {
2295 None
2296 } else {
2297 Some(f(&format!("{pfx}self_attn.k_norm.weight"))?)
2298 },
2299 kv_share_src: share_src,
2300 sliding: sliding_of(li),
2301 gate: t(&format!("{pfx}mlp.gate_proj.weight"))?,
2302 up: t(&format!("{pfx}mlp.up_proj.weight"))?,
2303 down: t(&format!("{pfx}mlp.down_proj.weight"))?,
2304 sparsity: gc.activation_sparsity.get(li).copied().unwrap_or(0.0),
2305 ple_gate: t(&format!("{pfx}per_layer_input_gate.weight"))?,
2306 ple_proj: t(&format!("{pfx}per_layer_projection.weight"))?,
2307 post_ple_norm: f(&format!("{pfx}post_per_layer_input_norm.weight"))?,
2308 });
2309 }
2310 let hd = arch.head_dim;
2311 let globals = G3nGlobals {
2312 altup_proj,
2313 altup_unembed,
2314 ple_embed: t("model.embed_tokens_per_layer.weight")?,
2315 ple_model_proj: t("model.per_layer_model_projection.weight")?,
2316 ple_norm: f("model.per_layer_projection_norm.weight")?,
2317 ple_vocab: gc.ple_vocab,
2318 ple_dim: gc.ple_dim,
2319 num_layers: arch.num_layers,
2320 hidden: arch.hidden_size,
2321 rms_eps: arch.rms_norm_eps,
2322 inv_freq_local: crate::attention::rope_inv_freq(
2323 hd,
2324 arch.rope_local_base_freq.unwrap_or(10_000.0) as f32,
2325 ),
2326 inv_freq_global: crate::attention::rope_inv_freq(hd, arch.rope_theta as f32),
2327 window: arch.sliding_window.unwrap_or(512),
2328 };
2329 pipeline.g3n = Some(Box::new((globals, g3n_layers)));
2330 }
2331 if arch.arch_name == "deepseek_v41" {
2336 let source = arch.deepseek_v41.as_ref().ok_or_else(|| {
2337 CmfError::Parse("deepseek_v41: missing preserved source config".into())
2338 })?;
2339 let (cfg, n_layers) = crate::dsv41::Dsv41Cfg::from_source(source, &arch)
2340 .map_err(|e| CmfError::Parse(format!("deepseek_v41: {e}")))?;
2341 let token_map = crate::dsv41::token_map_from_model(model, cfg.vocab);
2342 let (g, dl, hash) = crate::dsv41::load(model, &cfg, n_layers, token_map)
2343 .map_err(|e| CmfError::Parse(format!("deepseek_v41: {e}")))?;
2344 let st = crate::dsv41::Dsv41State::new(&cfg, hash);
2345 if let Ok(vision_cfg) = crate::dsv41_vision::VisionConfig::from_source(source) {
2350 if vision_cfg.vision_enabled()
2351 && model.tensor("vision.patch_embed.proj.weight").is_some()
2352 {
2353 pipeline.dsv41_vision = Some(
2354 crate::dsv41_vision::VisionModel::from_model(model, vision_cfg)
2355 .map_err(|e| CmfError::Parse(format!("deepseek_v41 vision: {e}")))?,
2356 );
2357 }
2358 }
2359 tracing::info!(
2360 "deepseek_v41: loaded {} layers, {} KV sources, {} index sources, {} Engram layers; experts remain mmap-backed",
2361 dl.len(),
2362 cfg.kv_sources.len(),
2363 cfg.index_sources.len(),
2364 cfg.engram_layers.len()
2365 );
2366 pipeline.dsv41 = Some(Box::new((g, dl, cfg, st)));
2367 }
2368 if arch.arch_name == "deepseek_v4" {
2373 let moe = arch
2374 .moe
2375 .as_ref()
2376 .ok_or_else(|| CmfError::Parse("deepseek_v4: no moe config".into()))?;
2377 let cfg = crate::dsv4::Dsv4Cfg {
2378 dim: arch.hidden_size,
2379 n_heads: arch.num_attention_heads,
2380 head_dim: arch.head_dim,
2381 rope_head_dim: if arch.partial_rotary_factor < 1.0 {
2388 (((arch.head_dim as f32 * arch.partial_rotary_factor) as usize) & !1)
2389 .clamp(2, arch.head_dim)
2390 } else {
2391 64.min(arch.head_dim)
2392 },
2393 q_lora_rank: 0,
2397 o_lora_rank: 0,
2398 o_groups: 8,
2405 hc_mult: 4,
2406 hc_sinkhorn_iters: 20,
2407 hc_eps: 1e-6,
2408 norm_eps: arch.rms_norm_eps as f32,
2409 n_routed_experts: moe.num_experts,
2410 top_k: moe.top_k,
2411 moe_inter: moe.moe_intermediate_size,
2412 route_scale: moe.routed_scaling_factor.unwrap_or(1.0),
2413 swiglu_limit: 10.0,
2419 window: arch.sliding_window.unwrap_or(128),
2420 index_topk: 512,
2421 vocab: arch.vocab_size,
2422 };
2423 let (g, dl) = crate::dsv4::load(model, &cfg, arch.num_layers)
2424 .map_err(|e| CmfError::Parse(format!("deepseek_v4: {e}")))?;
2425 let mut cfg = cfg;
2430 if let Some(l0) = dl.first() {
2431 cfg.q_lora_rank = l0.wq_a.rows();
2432 let attn_width = arch.num_attention_heads * arch.head_dim;
2433 if l0.wo_a.cols() > 0 && attn_width % l0.wo_a.cols() == 0 {
2434 cfg.o_groups = (attn_width / l0.wo_a.cols()).max(1);
2435 }
2436 cfg.o_lora_rank = l0.wo_b.cols() / cfg.o_groups.max(1);
2437 cfg.hc_mult = (l0.hc_attn_fn.len() / l0.hc_attn_base.len().max(1)) / cfg.dim.max(1);
2438 if cfg.hc_mult == 0 {
2439 cfg.hc_mult = 4;
2440 }
2441 }
2442 let (yf, yo, ybf, ybs) = match &arch.yarn {
2455 Some(y) => (
2456 y.factor,
2457 y.original_max_position_embeddings,
2458 y.beta_fast,
2459 y.beta_slow,
2460 ),
2461 None => {
2462 tracing::warn!(
2463 "deepseek_v4: the header carries no YaRN profile — \
2464 falling back to the release's (factor 16, original \
2465 65536, beta 32/1). Re-converting with a build that \
2466 reads rope_scaling.type would make this exact."
2467 );
2468 (16.0, 65536, 32.0, 1.0)
2469 }
2470 };
2471 pipeline.inv_freq = std::sync::Arc::new(crate::attention::yarn_inv_freq(
2472 cfg.rope_head_dim,
2473 arch.rope_theta as f32,
2474 yf,
2475 yo,
2476 ybf,
2477 ybs,
2478 ));
2479 if let Ok(stats) = std::env::var("CMF_MOE_PIN") {
2484 let cover = std::env::var("CMF_MOE_PIN_COVER")
2485 .ok()
2486 .and_then(|v| v.parse::<f64>().ok())
2487 .filter(|&c| c > 0.0 && c <= 1.0)
2488 .unwrap_or(0.95);
2489 let hot = crate::pin::hot_experts(&stats, cover);
2490 let mut names: Vec<String> = Vec::new();
2491 for e in &model.tensors {
2492 let is_expert = e.name.contains(".mlp.experts.");
2493 if !is_expert {
2494 names.push(e.name.clone()); }
2496 }
2497 let mut kept_experts = 0usize;
2498 if let Some(hot) = &hot {
2499 for (li, experts) in hot {
2500 for e in experts {
2501 for w in ["gate_proj", "up_proj", "down_proj"] {
2502 names.push(format!("model.layers.{li}.mlp.experts.{e}.{w}.weight"));
2503 }
2504 kept_experts += 1;
2505 }
2506 }
2507 }
2508 let r = crate::pin::pin_tensors(model, &names);
2509 tracing::info!(
2510 "закреплено {:.1} ГБ ({} тензоров, горячих экспертов {kept_experts}, покрытие {cover}); лимит {}",
2511 r.bytes as f64 / 1e9,
2512 r.tensors,
2513 r.limit
2514 .map(|l| format!("{:.1} ГБ", l as f64 / 1e9))
2515 .unwrap_or_else(|| "неизвестен".into())
2516 );
2517 if r.skipped > 0 {
2518 tracing::warn!("не закреплено тензоров: {}", r.skipped);
2519 }
2520 }
2521 let st = crate::dsv4::Dsv4State::new(arch.num_layers);
2522 let depth = std::env::var("CMF_DSV4_MTP_DEPTH")
2526 .ok()
2527 .and_then(|v| v.parse::<usize>().ok())
2528 .unwrap_or(3);
2529 pipeline.dsv4_mtp = crate::dsv4::load_mtp(model, &cfg, depth);
2530 crate::dsv4::dspark_reserve_note(&pipeline.dsv4_mtp, &cfg, &dl);
2532 pipeline.dsv4 = Some(Box::new((g, dl, cfg, st)));
2533 }
2534 pipeline.short_conv_cfg = short_conv_cfg;
2535 pipeline.mtp = mtp;
2536 if arch.arch_name == "mimo_v2" {
2537 pipeline.mimo_mtp = crate::pipeline::mimo_mtp::load_for(model, &arch)?;
2538 }
2539 pipeline.swa_trim = (arch.arch_name == "spark2_5"
2548 && pipeline.swa.is_some()
2549 && pipeline.mtp.is_none()
2550 && pipeline.mimo_mtp.is_none()
2551 && pipeline.dsv4_mtp.is_empty()
2552 && std::env::var_os("CMF_ATTN_DUMP").is_none()
2553 && std::env::var("CMF_SWA_TRIM").as_deref() != Ok("0"))
2554 .then_some((64, 64));
2555 pipeline.install_dynamic_routing(model, false);
2556 match ov {
2560 Overlay::One(sid) => {
2561 pipeline.dyn_active = model.header.skills.iter().position(|s| &s.id == sid);
2562 }
2563 Overlay::Blend(_) => pipeline.dyn_blend_loaded = true,
2564 Overlay::None => {}
2565 }
2566 if let Some(c) = &model.header.calibration {
2569 pipeline.set_calib_temp(c.temperature);
2570 }
2571 let identity = arch
2574 .linear_core_identity()
2575 .and_then(|v| serde_json::to_vec(&v).ok())
2576 .map(|b| cortiq_core::hash64(&b))
2577 .unwrap_or(0);
2578 pipeline.install_wire_identity(identity);
2579 if let Some(ac) = &arch.anchor_core {
2583 pipeline.install_bounded(ac).map_err(CmfError::Parse)?;
2584 let why = pipeline.o1_refusal().unwrap_or_default();
2585 let mut knobs: Vec<String> = std::env::vars()
2586 .filter(|(k, v)| {
2587 (k == "CMF_O1" && !(v == "off" || v == "0")) || k.starts_with("CMF_O1_")
2588 })
2589 .map(|(k, _)| k)
2590 .collect();
2591 knobs.sort();
2592 if !knobs.is_empty() {
2593 return Err(CmfError::Parse(format!("{why} (set: {})", knobs.join(", "))));
2594 }
2595 if model
2596 .header
2597 .provenance
2598 .as_ref()
2599 .and_then(|p| p.get("o1_attn"))
2600 .is_some()
2601 {
2602 return Err(CmfError::Parse(format!(
2603 "{why} (the header carries a provenance.o1_attn hint)"
2604 )));
2605 }
2606 return Ok(pipeline);
2607 }
2608 let genome_hint = model.header.genome.is_some()
2618 && model
2619 .header
2620 .provenance
2621 .as_ref()
2622 .and_then(|p| p.get("o1_attn"))
2623 .is_some();
2624 if genome_hint {
2625 return Err(CmfError::Parse(
2626 "genome file with a provenance.o1_attn hint: the trunk's attention operator is \
2627 set by the genome's arch only — refusing"
2628 .into(),
2629 ));
2630 }
2631 let o1 = match crate::nystrom::o1_from_env() {
2632 crate::nystrom::O1Env::Off => None,
2633 crate::nystrom::O1Env::On(cfg) => Some(cfg),
2634 crate::nystrom::O1Env::Unset => model
2635 .header
2636 .provenance
2637 .as_ref()
2638 .and_then(|p| p.get("o1_attn"))
2639 .and_then(crate::nystrom::O1Cfg::from_json),
2640 };
2641 if o1.is_some() {
2642 if pipeline.attn_softcap > 0.0 {
2643 return Err(CmfError::Parse(
2644 "--o1 with attention-logit soft-capping (Gemma-2) is not supported: \
2645 the streaming operator has no capped-score form"
2646 .into(),
2647 ));
2648 }
2649 pipeline.set_o1(o1);
2650 }
2651 Ok(pipeline)
2652 }
2653
2654 pub(crate) fn install_dynamic_routing(&mut self, model: &Arc<CmfModel>, force_f32: bool) {
2659 self.model = Some(model.clone());
2660 self.dyn_force_f32 = force_f32;
2661 let mut per_skill = Vec::with_capacity(model.header.skills.len());
2662 for sk in &model.header.skills {
2663 if sk.kind.as_deref() == Some(cortiq_core::knowledge::skill_kind::LOOKUP) {
2666 per_skill.push(None);
2667 continue;
2668 }
2669 let mut ffn_layers = std::collections::BTreeSet::new();
2670 let mut non_ffn = false;
2671 let prefix = format!("skill.{}.", sk.id);
2672 for t in model.skill_tensors(&sk.id) {
2673 let rel = &t.name[prefix.len()..]; let toks: Vec<&str> = rel.split('.').collect();
2675 if toks.len() >= 5 && toks[0] == "model" && toks[1] == "layers" && toks[3] == "mlp"
2676 {
2677 if let Ok(li) = toks[2].parse::<usize>() {
2678 ffn_layers.insert(li);
2679 continue;
2680 }
2681 }
2682 non_ffn = true; }
2684 if non_ffn {
2685 tracing::warn!(
2686 "skill '{}' replaces non-FFN tensors — excluded from dynamic \
2687 routing (static overlay still works)",
2688 sk.id
2689 );
2690 per_skill.push(None);
2691 } else {
2692 per_skill.push(Some(ffn_layers.into_iter().collect::<Vec<_>>()));
2693 }
2694 }
2695 self.dyn_skill_layers = per_skill;
2696 }
2697
2698 pub fn set_active_skill(&mut self, idx: Option<usize>) -> Result<(), CmfError> {
2713 if self.dyn_active == idx {
2714 return Ok(());
2715 }
2716 let model = self.model.clone().ok_or_else(|| {
2717 CmfError::Parse("dynamic routing needs a model-backed pipeline".into())
2718 })?;
2719 let mut union: std::collections::BTreeSet<usize> = std::collections::BTreeSet::new();
2720 if let Some(old) = self.dyn_active {
2721 if let Some(Some(ls)) = self.dyn_skill_layers.get(old) {
2722 union.extend(ls.iter().copied());
2723 }
2724 }
2725 let new_id: Option<String> = match idx {
2726 Some(n) => match self.dyn_skill_layers.get(n) {
2727 Some(Some(ls)) => {
2728 union.extend(ls.iter().copied());
2729 Some(model.header.skills[n].id.clone())
2730 }
2731 _ => {
2732 return Err(CmfError::Parse(format!(
2733 "skill index {n} not dynamic-eligible"
2734 )));
2735 }
2736 },
2737 None => None,
2738 };
2739 let ov = match &new_id {
2740 Some(s) => Overlay::One(s),
2741 None => Overlay::None,
2742 };
2743 let arch = model.arch();
2744 let mut rebuilt = Vec::with_capacity(union.len());
2745 for li in union {
2746 rebuilt.push((
2747 li,
2748 build_layer_ffn(&model, arch, li, self.dyn_force_f32, &ov)?,
2749 ));
2750 }
2751 for (li, ffn) in rebuilt {
2752 self.weights.layers[li].ffn = ffn;
2753 }
2754 self.invalidate_for_weight_change();
2756 self.dyn_active = idx;
2757 Ok(())
2758 }
2759}
2760
2761#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
2766pub struct PerSequenceState {
2767 pub bounded_bytes: usize,
2768 pub recurrent_bytes: usize,
2769 pub growing_layers: usize,
2770}
2771
2772pub fn per_sequence_state_bytes(model: &CmfModel) -> Result<PerSequenceState, String> {
2773 let arch = model.arch();
2774 let f = std::mem::size_of::<f32>();
2775 let mut st = PerSequenceState::default();
2776 let need = |v: Option<usize>, name: &str| {
2777 v.ok_or_else(|| format!("linear core needs arch.{name}"))
2778 };
2779 for li in 0..arch.num_layers {
2780 match arch.layer_types.get(li) {
2781 Some(LayerType::BoundedAttention) => {
2782 let ac = arch
2783 .anchor_core
2784 .as_ref()
2785 .ok_or("BoundedAttention layer without anchor_core")?;
2786 st.bounded_bytes += ac.ring_state_elems(arch.num_kv_heads, arch.head_dim) * f;
2787 }
2788 Some(LayerType::LinearAttention) => {
2789 let lc = arch
2790 .linear_core
2791 .as_ref()
2792 .ok_or("LinearAttention layer without linear_core")?;
2793 let elems = match lc.kind.as_str() {
2794 "gated_delta_net" => GdnCfg {
2795 num_v_heads: lc.num_heads,
2796 num_k_heads: need(arch.linear_num_key_heads, "linear_num_key_heads")?,
2797 key_head_dim: need(arch.linear_key_head_dim, "linear_key_head_dim")?,
2798 value_head_dim: lc.value_head_dim,
2799 conv_kernel: need(arch.linear_conv_kernel_dim, "linear_conv_kernel_dim")?,
2800 hidden_size: arch.hidden_size,
2801 rms_eps: arch.rms_norm_eps,
2802 output_gate_sigmoid: false,
2803 }
2804 .state_len(),
2805 _ => {
2806 let nphase = need(lc.nphase, "linear_core.nphase")?;
2807 let mut n = lc.num_heads * 2 * nphase * lc.value_head_dim;
2808 if let Some(c) = model.tensor(&format!("model.layers.{li}.vmf_attn.conv1d.weight")) {
2809 if c.shape.len() == 3 && c.shape[2] >= 2 {
2810 n += (c.shape[2] - 1) * arch.hidden_size;
2811 }
2812 }
2813 n
2814 }
2815 };
2816 st.recurrent_bytes += elems * f;
2817 }
2818 Some(LayerType::ShortConv) => {
2819 let k = need(arch.linear_conv_kernel_dim, "linear_conv_kernel_dim")?;
2820 st.recurrent_bytes += ShortConvCfg {
2821 hidden_size: arch.hidden_size,
2822 kernel: k,
2823 }
2824 .state_len()
2825 * f;
2826 }
2827 Some(LayerType::Kda) => {
2828 st.recurrent_bytes += crate::linear_core::KdaCfg {
2829 num_heads: need(arch.linear_num_key_heads, "linear_num_key_heads")?,
2830 head_k_dim: need(arch.linear_key_head_dim, "linear_key_head_dim")?,
2831 head_v_dim: need(arch.linear_value_head_dim, "linear_value_head_dim")?,
2832 conv_kernel: need(arch.linear_conv_kernel_dim, "linear_conv_kernel_dim")?,
2833 hidden_size: arch.hidden_size,
2834 rms_eps: arch.rms_norm_eps,
2835 }
2836 .state_len()
2837 * f;
2838 }
2839 _ => st.growing_layers += 1,
2840 }
2841 }
2842 Ok(st)
2843}