1use crate::hash::hash64;
24pub use crate::knowledge::{
25 ExpertAppend, GenomeInfo, GenomeParent, LineageEvent, LookupInfo, PhiSpec, RouterPolicy,
26 Segment, SkillBound, SkillOverride, StateEffect, expert_append_state_effect,
27 ffn_replace_state_effect, trunk_hash,
28};
29use crate::mask::{MaskCatalog, TaskMask, decode_masks_section, encode_masks_section};
30use crate::quant::expected_nbytes;
31use crate::types::{ModelArch, QuantType, TensorDtype};
32use serde::{Deserialize, Serialize};
33use std::collections::HashMap;
34use std::fs::File;
35use std::io::{self, BufWriter, Seek, SeekFrom, Write};
36use std::path::{Path, PathBuf};
37
38mod append;
39pub use append::{AppendReport, LegacyBitsMigration, PendingAppend};
40
41pub const CMF_MAGIC: [u8; 4] = *b"CMF\x01";
42pub const CMF_VERSION: u32 = 2;
43pub const ENVELOPE_LEN: usize = 128;
44pub const DATA_ALIGNMENT: u64 = 4096;
46pub const TENSOR_ALIGNMENT: u64 = 64;
48pub const LARGE_TENSOR_MIN: u64 = 16 * 1024;
56pub const LARGE_TENSOR_ALIGN: u64 = 4096;
61pub const DIR_RECORD_LEN: usize = 56;
63pub const DIR_MAX_NDIM: usize = 6;
64
65pub mod features {
68 pub const TENSOR_DIR: u32 = 1 << 0;
69 pub const BINARY_MASKS: u32 = 1 << 1;
70 pub const QUANT_2F: u32 = 1 << 2;
71 pub const DELTA_MASKS: u32 = 1 << 3;
72 pub const HOT_PACKS: u32 = 1 << 4;
73 pub const LOOP_MASKS: u32 = 1 << 5;
79 pub const SKILL_FILE: u32 = 1 << 6;
85 pub const PRISM_HADAMARD: u32 = 1 << 7;
87 pub const PRISM_AFFINE: u32 = 1 << 8;
91 pub const SKILLS_V2: u32 = 1 << 9;
95 pub const ROUTER_V2: u32 = 1 << 10;
99 pub const DECISION: u32 = 1 << 11;
107 pub const BOUNDED_STATE: u32 = 1 << 12;
120 pub const GENOME: u32 = 1 << 13;
128 pub const BLOCK_DIFFUSION: u32 = 1 << 15;
136
137 pub const SUPPORTED: u32 = TENSOR_DIR
139 | BINARY_MASKS
140 | QUANT_2F
141 | LOOP_MASKS
142 | SKILL_FILE
143 | PRISM_HADAMARD
144 | PRISM_AFFINE
145 | SKILLS_V2
146 | ROUTER_V2
147 | DECISION
148 | BOUNDED_STATE
149 | GENOME
150 | BLOCK_DIFFUSION;
151}
152
153pub const DECISION_ARCH_PREFIX: &str = "cortiq-decision-";
158
159pub fn is_decision_profile(arch_name: &str) -> bool {
161 arch_name.starts_with(DECISION_ARCH_PREFIX)
162}
163
164fn prism_bits(arch: &ModelArch) -> u32 {
167 let mut f = 0;
168 if let Some(prism) = arch.prism_hadamard.as_ref() {
169 f |= features::PRISM_HADAMARD;
170 if prism.affine.is_some() {
171 f |= features::PRISM_AFFINE;
172 }
173 }
174 f
175}
176
177fn legacy_embryo_bits_hint(header: &CmfHeader) -> &'static str {
181 if header.arch.anchor_core.is_some() || header.genome.is_some() {
182 " (this file carries anchor_core/genome: it was written by a pre-0.8.1 embryo-o1 \
183 build that numbered BOUNDED_STATE/GENOME as bits 7/8 — they are 12/13 now. \
184 Migrate it in place with `cortiq migrate-embryo-bits <file>`: it moves only those two \
185 envelope bits, which neither header_hash/dir_hash nor trunk_hash cover, and keeps \
186 every appended record. Do NOT re-export a file with appended records or a lineage — \
187 a re-export drops them. A detached .sig signs the whole file: re-sign after migrating)"
188 } else {
189 ""
190 }
191}
192
193pub mod legacy_embryo_bits {
197 pub const BOUNDED_STATE: u32 = 1 << 7;
198 pub const GENOME: u32 = 1 << 8;
199 pub const BOTH: u32 = BOUNDED_STATE | GENOME;
200}
201
202pub fn peek_header(path: impl AsRef<Path>) -> Result<Option<(u32, serde_json::Value)>, CmfError> {
208 use std::io::Read;
209 let path = path.as_ref();
210 let mut f = File::open(path)?;
211 let len = f.metadata()?.len();
212 if len < ENVELOPE_LEN as u64 {
213 return Ok(None);
214 }
215 let mut head = [0u8; ENVELOPE_LEN];
216 f.read_exact(&mut head)?;
217 if head[0..4] != CMF_MAGIC {
218 return Ok(None);
219 }
220 let u64le = |o: usize| u64::from_le_bytes(head[o..o + 8].try_into().unwrap());
221 let required = u32::from_le_bytes(head[12..16].try_into().unwrap());
222 let (off, n) = (u64le(0x10), u64le(0x18));
223 if n == 0 || n > (1 << 30) || off.checked_add(n).is_none_or(|end| end > len) {
224 return Err(CmfError::Bounds(format!(
225 "{}: header section [{off}, +{n}) lies outside the file ({len} bytes)",
226 path.display()
227 )));
228 }
229 let mut buf = vec![0u8; n as usize];
230 f.seek(SeekFrom::Start(off))?;
231 f.read_exact(&mut buf)?;
232 let v = serde_json::from_slice(&buf)
233 .map_err(|e| CmfError::Parse(format!("{}: header JSON: {e}", path.display())))?;
234 Ok(Some((required, v)))
235}
236
237pub fn header_json_is_genome(header: &serde_json::Value) -> bool {
242 let present = |k: &str| {
243 header
244 .get(k)
245 .is_some_and(|x| !x.is_null() && x.as_array().is_none_or(|a| !a.is_empty()))
246 };
247 present("genome") || present("lineage") || present("segments")
248}
249
250fn validate_prism_affine_targets(
251 arch: &ModelArch,
252 tensors: &[TensorEntry],
253) -> Result<(), CmfError> {
254 let Some(prism) = arch.prism_hadamard.as_ref() else {
255 return Ok(());
256 };
257 let Some(affine) = prism.affine.as_ref() else {
258 return Ok(());
259 };
260 affine.validate().map_err(CmfError::Parse)?;
261 for name in &affine.target_names {
262 let matches = tensors
263 .iter()
264 .filter(|t| t.name == *name)
265 .collect::<Vec<_>>();
266 if matches.len() != 1 {
267 return Err(CmfError::Parse(format!(
268 "Prism affine target '{}' appears {} times (expected exactly once)",
269 name,
270 matches.len()
271 )));
272 }
273 let t = matches[0];
274 if t.dtype != TensorDtype::Q2TiledP
275 || t.shape.len() != 2
276 || t.shape[1] == 0
277 || t.shape[1] % 32 != 0
278 {
279 return Err(CmfError::Parse(format!(
280 "Prism affine target '{}' must be dtype q2tp [rows, positive cols multiple of 32], got {:?}{:?}",
281 name, t.dtype, t.shape
282 )));
283 }
284 }
285 Ok(())
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct CmfHeader {
292 #[serde(default = "default_format")]
293 pub format: String,
294 pub version: u32,
295 pub arch: ModelArch,
296 pub quant_type: QuantType,
298 #[serde(default, skip_serializing_if = "Option::is_none")]
299 pub provenance: Option<serde_json::Value>,
300 #[serde(default, skip_serializing_if = "Option::is_none")]
303 pub tokenizer_config: Option<TokenizerBundle>,
304 #[serde(default, skip_serializing_if = "Option::is_none")]
308 pub section_hashes: Option<SectionHashes>,
309 #[serde(default, skip_serializing_if = "Vec::is_empty")]
313 pub skills: Vec<SkillRecord>,
314 #[serde(default, skip_serializing_if = "Option::is_none")]
318 pub shard: Option<ShardInfo>,
319 #[serde(default, skip_serializing_if = "Option::is_none")]
325 pub calibration: Option<Calibration>,
326 #[serde(default, skip_serializing_if = "Option::is_none")]
331 pub routing: Option<RoutingCalibration>,
332 #[serde(default, skip_serializing_if = "Option::is_none")]
335 pub genome: Option<GenomeInfo>,
336 #[serde(default, skip_serializing_if = "Vec::is_empty")]
338 pub lineage: Vec<LineageEvent>,
339 #[serde(default, skip_serializing_if = "Option::is_none")]
342 pub router: Option<RouterPolicy>,
343 #[serde(default, skip_serializing_if = "Vec::is_empty")]
346 pub segments: Vec<Segment>,
347}
348
349#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct RoutingCalibration {
356 pub temperature: f32,
357 pub novelty_theta: f32,
358 pub samples: usize,
360 pub target_fpr: f32,
362}
363
364#[derive(Debug, Clone, Serialize, Deserialize)]
368pub struct Calibration {
369 pub temperature: f32,
370 #[serde(default, skip_serializing_if = "Option::is_none")]
371 pub ece_before: Option<f32>,
372 #[serde(default, skip_serializing_if = "Option::is_none")]
373 pub ece_after: Option<f32>,
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize)]
378pub struct ShardInfo {
379 pub no: usize,
380 pub count: usize,
381}
382
383#[derive(Debug, Clone, Serialize, Deserialize)]
386pub struct SelectionDescriptor {
387 pub metric: String,
390 pub phi_layer: usize,
392 pub mean: String,
394 pub basis: String,
396 pub rank: usize,
397 #[serde(default, skip_serializing_if = "Option::is_none")]
400 pub err_mean: Option<f32>,
401 #[serde(default, skip_serializing_if = "Option::is_none")]
402 pub err_std: Option<f32>,
403 #[serde(default, skip_serializing_if = "Option::is_none")]
407 pub holdout: Option<String>,
408 #[serde(default, skip_serializing_if = "Option::is_none")]
409 pub holdout_n: Option<usize>,
410}
411
412#[derive(Debug, Clone, Default, Serialize, Deserialize)]
414pub struct SkillRecord {
415 pub id: String,
416 #[serde(default, skip_serializing_if = "Option::is_none")]
417 pub name: Option<String>,
418 #[serde(default)]
420 pub layers: Vec<usize>,
421 #[serde(default, skip_serializing_if = "Option::is_none")]
424 pub selection: Option<SelectionDescriptor>,
425 #[serde(default, skip_serializing_if = "Option::is_none")]
427 pub input_mask_task: Option<String>,
428 #[serde(default, skip_serializing_if = "Option::is_none")]
430 pub quality: Option<serde_json::Value>,
431
432 #[serde(default, skip_serializing_if = "Option::is_none")]
437 pub base_dir_hash: Option<String>,
438 #[serde(default, skip_serializing_if = "Option::is_none")]
441 pub base_arch: Option<String>,
442 #[serde(default, skip_serializing_if = "Option::is_none")]
444 pub task: Option<String>,
445 #[serde(default, skip_serializing_if = "Option::is_none")]
447 pub provenance: Option<serde_json::Value>,
448
449 #[serde(default, skip_serializing_if = "Option::is_none")]
454 pub kind: Option<String>,
455 #[serde(default, skip_serializing_if = "Option::is_none")]
459 pub experts: Option<ExpertAppend>,
460 #[serde(default, skip_serializing_if = "Option::is_none")]
464 pub lookup: Option<LookupInfo>,
465 #[serde(default, skip_serializing_if = "Vec::is_empty")]
468 pub overrides: Vec<SkillOverride>,
469 #[serde(default, skip_serializing_if = "Option::is_none")]
472 pub bound: Option<SkillBound>,
473 #[serde(default, skip_serializing_if = "Option::is_none")]
476 pub state_effect: Option<StateEffect>,
477 #[serde(default, skip_serializing_if = "Option::is_none")]
479 pub status: Option<String>,
480 #[serde(default, skip_serializing_if = "Option::is_none")]
483 pub gate: Option<serde_json::Value>,
484 #[serde(default, skip_serializing_if = "Option::is_none")]
486 pub prompt_contract: Option<String>,
487 #[serde(default, skip_serializing_if = "Option::is_none")]
489 pub origin: Option<serde_json::Value>,
490}
491
492#[derive(Debug, Clone, Default, Serialize, Deserialize)]
495pub struct SectionHashes {
496 #[serde(default, skip_serializing_if = "Option::is_none")]
497 pub masks: Option<String>,
498 #[serde(default, skip_serializing_if = "Option::is_none")]
499 pub vocab: Option<String>,
500 #[serde(default, skip_serializing_if = "Option::is_none")]
501 pub index: Option<String>,
502}
503
504#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
506pub struct TokenizerBundle {
507 #[serde(default, skip_serializing_if = "Option::is_none")]
509 pub chat_template: Option<String>,
510 #[serde(default)]
512 pub eos_token_ids: Vec<u32>,
513 #[serde(default, skip_serializing_if = "Option::is_none")]
514 pub bos_token_id: Option<u32>,
515 #[serde(default, skip_serializing_if = "Option::is_none")]
516 pub pad_token_id: Option<u32>,
517}
518
519fn default_format() -> String {
520 "cmf".to_string()
521}
522
523#[derive(Debug, Clone, PartialEq, Eq)]
525pub struct TensorEntry {
526 pub name: String,
527 pub dtype: TensorDtype,
528 pub shape: Vec<usize>,
529 pub off: u64,
531 pub nbytes: u64,
532 pub shard: usize,
535 pub hash: u64,
537}
538
539impl TensorEntry {
540 pub fn n_elems(&self) -> usize {
541 self.shape.iter().product()
542 }
543}
544
545#[derive(Debug, Clone)]
547pub struct TensorSpec {
548 pub name: String,
549 pub dtype: TensorDtype,
550 pub shape: Vec<usize>,
551 pub data: Vec<u8>,
552}
553
554pub struct TensorSpecRef<'a> {
556 pub name: String,
557 pub dtype: TensorDtype,
558 pub shape: Vec<usize>,
559 pub data: &'a [u8],
560}
561
562#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
564pub struct SparseIndexEntry {
565 pub task_id: u32,
566 pub layer_idx: usize,
567 pub active_ffn_groups: Vec<u16>,
569 pub active_heads: Vec<u8>,
571}
572
573#[derive(Debug, Clone, Copy, Default)]
575struct Envelope {
576 required_features: u32,
577 header: (u64, u64),
578 dir: (u64, u64),
579 data: (u64, u64),
580 masks: (u64, u64),
581 vocab: (u64, u64),
582 index: (u64, u64),
583 header_hash: u64,
585 dir_hash: u64,
587}
588
589enum Backing {
590 Mmap(memmap2::Mmap),
591 Owned(Vec<u8>),
592}
593
594impl Backing {
595 fn bytes(&self) -> &[u8] {
596 match self {
597 Backing::Mmap(m) => m,
598 Backing::Owned(v) => v,
599 }
600 }
601}
602
603pub struct CmfModel {
605 pub path: PathBuf,
606 uid: u64,
612 pub header: CmfHeader,
613 pub required_features: u32,
614 pub tensors: Vec<TensorEntry>,
615 by_name: HashMap<u64, u32>,
622 name_overflow: Vec<u32>,
623 pub masks: MaskCatalog,
624 pub sparse_index: Vec<SparseIndexEntry>,
625 pub vocab: Option<Vec<u8>>,
627 backing: Backing,
628 data_off: u64,
629 envelope: Envelope,
630 extra_shards: Vec<(Backing, u64)>,
633 trunk: Option<u64>,
635 exec: crate::knowledge::ExecHashes,
639}
640
641impl std::fmt::Debug for CmfModel {
642 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
643 f.debug_struct("CmfModel")
644 .field("path", &self.path)
645 .field("arch", &self.header.arch.arch_name)
646 .field("tensors", &self.tensors.len())
647 .field("masks", &self.masks.masks.len())
648 .finish()
649 }
650}
651
652static MODEL_UID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
654
655impl CmfModel {
656 pub fn uid(&self) -> u64 {
660 self.uid
661 }
662
663 pub fn open(path: impl AsRef<Path>) -> Result<Self, CmfError> {
666 let path = path.as_ref().to_path_buf();
667 if !path.exists() {
668 return Err(CmfError::FileNotFound(path.display().to_string()));
669 }
670 let file = File::open(&path)?;
671 let file_len = file.metadata()?.len();
672
673 let populate = std::env::var("CMF_MMAP_POPULATE").as_deref() == Ok("1");
678 let mut opts = memmap2::MmapOptions::new();
679 if populate {
680 opts.populate();
681 }
682 let backing = match unsafe { opts.map(&file) } {
683 Ok(m) => {
684 #[cfg(unix)]
694 {
695 let evicting = cfg!(target_os = "linux")
709 && std::env::var("CMF_GPU").is_ok_and(|v| v != "0" && v != "off")
710 && std::env::var("CMF_UPLOAD_EVICT")
711 .map(|v| v != "0")
712 .unwrap_or(true);
713 const ADVISE_CAP: usize = 4 << 30;
721 let advise = match std::env::var("CMF_MMAP_ADVISE").as_deref() {
722 Ok("0") => false,
723 Ok("1") => true,
724 _ => m.len() <= ADVISE_CAP,
725 };
726 if !evicting && advise {
727 let _ = m.advise(memmap2::Advice::WillNeed);
728 }
729 if std::env::var("CMF_MLOCK")
730 .map(|v| v == "1")
731 .unwrap_or(false)
732 {
733 if let Err(e) = m.lock() {
734 tracing::warn!(
735 "CMF_MLOCK=1: mlock refused ({e}) — continuing unpinned"
736 );
737 }
738 }
739 }
740 Backing::Mmap(m)
741 }
742 Err(e) => {
743 tracing::warn!("mmap failed ({e}), reading file into memory");
744 Backing::Owned(std::fs::read(&path)?)
745 }
746 };
747
748 let env = Self::parse_envelope(backing.bytes(), file_len)?;
749
750 let bytes = backing.bytes();
751 let section = |off: u64, len: u64| -> &[u8] { &bytes[off as usize..(off + len) as usize] };
752
753 let header: CmfHeader = serde_json::from_slice(section(env.header.0, env.header.1))
755 .map_err(|e| CmfError::Parse(format!("header JSON: {e}")))?;
756 let decision_bit = env.required_features & features::DECISION != 0;
761 if decision_bit != is_decision_profile(&header.arch.arch_name) {
762 return Err(CmfError::Parse(
763 "decision profile and DECISION feature bit disagree".into(),
764 ));
765 }
766 let prism_bit = env.required_features & features::PRISM_HADAMARD != 0;
767 let affine_bit = env.required_features & features::PRISM_AFFINE != 0;
768 let has_prism = header.arch.prism_hadamard.is_some();
769 let has_affine = header
770 .arch
771 .prism_hadamard
772 .as_ref()
773 .and_then(|p| p.affine.as_ref())
774 .is_some();
775 if prism_bit != has_prism {
776 return Err(CmfError::Parse(format!(
777 "required_features PRISM_HADAMARD={} disagrees with prism_hadamard metadata={has_prism}{}",
778 prism_bit,
779 legacy_embryo_bits_hint(&header)
780 )));
781 }
782 if affine_bit != has_affine {
783 return Err(CmfError::Parse(format!(
784 "required_features PRISM_AFFINE={} disagrees with affine metadata={has_affine}{}",
785 affine_bit,
786 legacy_embryo_bits_hint(&header)
787 )));
788 }
789 if let Some(prism) = header.arch.prism_hadamard.as_ref() {
790 prism.validate().map_err(CmfError::Parse)?;
791 if header.arch.arch_name != "prism_hadamard_qwen35" {
792 return Err(CmfError::Parse(
793 "prism_hadamard metadata requires arch_name prism_hadamard_qwen35".into(),
794 ));
795 }
796 }
797 header
798 .arch
799 .validate_operator_metadata()
800 .map_err(CmfError::Parse)?;
801 let bounded_declared = header.arch.anchor_core.is_some();
806 let bounded_bit = env.required_features & features::BOUNDED_STATE != 0;
807 if bounded_declared != bounded_bit {
808 return Err(CmfError::Parse(format!(
809 "anchor_core record ({}) and BOUNDED_STATE feature bit ({}) disagree",
810 if bounded_declared {
811 "present"
812 } else {
813 "absent"
814 },
815 if bounded_bit { "set" } else { "clear" }
816 )));
817 }
818
819 let diffusion_declared = header.arch.block_diffusion.is_some();
820 let diffusion_bit = env.required_features & features::BLOCK_DIFFUSION != 0;
821 if diffusion_declared != diffusion_bit {
822 return Err(CmfError::Parse(format!(
823 "block_diffusion record ({}) and BLOCK_DIFFUSION feature bit ({}) disagree",
824 if diffusion_declared { "present" } else { "absent" },
825 if diffusion_bit { "set" } else { "clear" }
826 )));
827 }
828
829 let tensors = Self::decode_directory(section(env.dir.0, env.dir.1))?;
831 validate_prism_affine_targets(&header.arch, &tensors)?;
832 for t in &tensors {
833 if t.off % TENSOR_ALIGNMENT != 0 {
834 return Err(CmfError::Bounds(format!(
835 "tensor '{}': offset {} not 64-aligned",
836 t.name, t.off
837 )));
838 }
839 let tensor_end = t.off.checked_add(t.nbytes).ok_or_else(|| {
840 CmfError::Bounds(format!("tensor '{}': offset + length overflows", t.name))
841 })?;
842 if tensor_end > env.data.1 {
843 return Err(CmfError::Bounds(format!(
844 "tensor '{}': [{}, {}) exceeds data section ({} bytes)",
845 t.name, t.off, tensor_end, env.data.1
846 )));
847 }
848 t.shape
849 .iter()
850 .try_fold(1usize, |n, &dim| n.checked_mul(dim))
851 .ok_or_else(|| {
852 CmfError::Bounds(format!(
853 "tensor '{}': shape product overflows usize",
854 t.name
855 ))
856 })?;
857 if let Some(expect) = expected_nbytes(t.dtype, &t.shape) {
858 if expect as u64 != t.nbytes {
859 return Err(CmfError::Bounds(format!(
860 "tensor '{}': nbytes {} != expected {} for {:?}{:?}",
861 t.name, t.nbytes, expect, t.dtype, t.shape
862 )));
863 }
864 }
865 if matches!(t.dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
868 let payload = section(env.data.0 + t.off, t.nbytes);
869 crate::quant::validate_payload(t.dtype, &t.shape, payload)
870 .map_err(|e| CmfError::Bounds(format!("tensor '{}': {e}", t.name)))?;
871 }
872 }
873 let mut by_name: HashMap<u64, u32> = HashMap::with_capacity(tensors.len());
877 let mut name_overflow: Vec<u32> = Vec::new();
878 for i in 0..tensors.len() {
879 let h = hash64(tensors[i].name.as_bytes());
880 match by_name.get(&h) {
881 Some(&j) if tensors[j as usize].name == tensors[i].name => {
882 return Err(CmfError::Parse(format!(
883 "duplicate tensor name '{}' in directory",
884 tensors[i].name
885 )));
886 }
887 Some(_) => name_overflow.push(i as u32), None => {
889 by_name.insert(h, i as u32);
890 }
891 }
892 }
893
894 let masks = if env.masks.1 > 0 {
896 decode_masks_section(section(env.masks.0, env.masks.1), &header.arch)
897 .map_err(CmfError::Parse)?
898 } else {
899 MaskCatalog::empty()
900 };
901
902 let vocab = if env.vocab.1 > 0 {
904 Some(section(env.vocab.0, env.vocab.1).to_vec())
905 } else {
906 None
907 };
908
909 let sparse_index = if env.index.1 > 0 {
911 decode_sparse_index(section(env.index.0, env.index.1))?
912 } else {
913 vec![]
914 };
915
916 let exec = if header.genome.is_some() {
926 crate::knowledge::ExecHashes::of(
927 (env.masks.1 > 0).then(|| section(env.masks.0, env.masks.1)),
928 (env.index.1 > 0).then(|| section(env.index.0, env.index.1)),
929 )
930 } else {
931 crate::knowledge::ExecHashes::default()
932 };
933 let trunk = if header.genome.is_some() {
934 let raw: serde_json::Value =
935 serde_json::from_slice(section(env.header.0, env.header.1))
936 .map_err(|e| CmfError::Parse(format!("header JSON: {e}")))?;
937 let null = serde_json::Value::Null;
938 Some(crate::knowledge::trunk_hash_json_exec(
939 raw.get("arch").unwrap_or(&null),
940 raw.get("tokenizer_config").unwrap_or(&null),
941 &tensors,
942 vocab.as_deref(),
943 exec,
944 ))
945 } else {
946 None
947 };
948 crate::knowledge::validate_knowledge(
949 &header,
950 &tensors,
951 vocab.as_deref(),
952 env.required_features,
953 Some(env.data.1),
954 trunk,
955 exec,
956 )?;
957 for (id, p) in crate::knowledge::unknown_lookup_policies(&header) {
960 tracing::warn!(
961 "skill '{id}': lookup.policy '{p}' is unknown to this reader (knows {}) — read as \
962 {}",
963 crate::knowledge::lookup_policy::ALL.join(" | "),
964 crate::knowledge::lookup_policy::DEFAULT
965 );
966 }
967 crate::knowledge::validate_expert_append_values(&header, &tensors, |t| {
970 let start = (env.data.0 + t.off) as usize;
971 bytes
972 .get(start..start + t.nbytes as usize)
973 .and_then(crate::knowledge::f32_le_head)
974 })?;
975 crate::knowledge::validate_lookup_values(&header, &tensors, false, |t| {
981 let start = (env.data.0 + t.off) as usize;
982 bytes
983 .get(start..start + t.nbytes as usize)
984 .map(std::borrow::Cow::Borrowed)
985 })?;
986
987 tracing::info!(
988 "Opened CMF v2: {} | {} tensors | {} masks | vocab {} | {:.1} MB",
989 header.arch.arch_name,
990 tensors.len(),
991 masks.masks.len(),
992 if vocab.is_some() { "embedded" } else { "none" },
993 file_len as f64 / 1e6
994 );
995
996 Ok(Self {
997 uid: MODEL_UID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
998 path,
999 header,
1000 required_features: env.required_features,
1001 tensors,
1002 by_name,
1003 name_overflow,
1004 masks,
1005 sparse_index,
1006 vocab,
1007 backing,
1008 data_off: env.data.0,
1009 envelope: env,
1010 extra_shards: Vec::new(),
1011 trunk,
1012 exec,
1013 })
1014 }
1015
1016 pub fn open_sharded(path: impl AsRef<Path>) -> Result<Self, CmfError> {
1020 let path = path.as_ref();
1021 let mut first = Self::open(path)?;
1022 let Some(info) = first.header.shard.clone() else {
1023 return Ok(first); };
1025 if info.no != 1 {
1026 return Err(CmfError::Parse(format!(
1027 "open shard 1, not {} (of {})",
1028 info.no, info.count
1029 )));
1030 }
1031 let name = path
1032 .file_name()
1033 .and_then(|n| n.to_str())
1034 .ok_or_else(|| CmfError::Parse("bad shard path".into()))?;
1035 let tag1 = format!("-{:05}-of-{:05}.cmf", 1, info.count);
1036 if !name.ends_with(&tag1) {
1037 return Err(CmfError::Parse(format!(
1038 "shard file must end with '{tag1}' (got '{name}')"
1039 )));
1040 }
1041 let stem = &name[..name.len() - tag1.len()];
1042 for no in 2..=info.count {
1043 let sib = path.with_file_name(format!("{stem}-{:05}-of-{:05}.cmf", no, info.count));
1044 let sh = Self::open(&sib)?;
1045 if sh.header.arch.linear_core_identity() != first.header.arch.linear_core_identity() {
1046 return Err(CmfError::Parse(format!(
1047 "{}: linear-core identity differs from shard 1",
1048 sib.display()
1049 )));
1050 }
1051 match &sh.header.shard {
1052 Some(si) if si.no == no && si.count == info.count => {}
1053 other => {
1054 return Err(CmfError::Parse(format!(
1055 "{}: wrong shard coords {other:?}",
1056 sib.display()
1057 )));
1058 }
1059 }
1060 let shard_idx = first.extra_shards.len() + 1;
1061 first.extra_shards.push((sh.backing, sh.envelope.data.0));
1062 for mut t in sh.tensors {
1063 t.shard = shard_idx;
1064 let idx = first.tensors.len() as u32;
1065 let h = hash64(t.name.as_bytes());
1066 match first.by_name.get(&h) {
1067 Some(&j) if first.tensors[j as usize].name == t.name => {
1068 return Err(CmfError::Parse(format!(
1069 "duplicate tensor name '{}' across shards",
1070 t.name
1071 )));
1072 }
1073 Some(_) => first.name_overflow.push(idx),
1074 None => {
1075 first.by_name.insert(h, idx);
1076 }
1077 }
1078 first.tensors.push(t);
1079 }
1080 }
1081 tracing::info!(
1082 "sharded model: {} files, {} tensors total",
1083 info.count,
1084 first.tensors.len()
1085 );
1086 Ok(first)
1087 }
1088
1089 fn parse_envelope(bytes: &[u8], file_len: u64) -> Result<Envelope, CmfError> {
1090 if bytes.len() < ENVELOPE_LEN {
1091 return Err(CmfError::Bounds(format!(
1092 "file too small for CMF envelope: {} bytes",
1093 bytes.len()
1094 )));
1095 }
1096 if bytes[0..4] != CMF_MAGIC {
1097 return Err(CmfError::InvalidMagic);
1098 }
1099 let u32le = |o: usize| u32::from_le_bytes(bytes[o..o + 4].try_into().unwrap());
1100 let u64le = |o: usize| u64::from_le_bytes(bytes[o..o + 8].try_into().unwrap());
1101
1102 let version = u32le(4);
1103 if version != CMF_VERSION {
1104 return Err(CmfError::UnsupportedVersion(version));
1105 }
1106 let _flags = u32le(8); let required_features = u32le(12);
1108 check_required_features(required_features, features::SUPPORTED)?;
1109
1110 let env = Envelope {
1111 required_features,
1112 header: (u64le(0x10), u64le(0x18)),
1113 dir: (u64le(0x20), u64le(0x28)),
1114 data: (u64le(0x30), u64le(0x38)),
1115 masks: (u64le(0x40), u64le(0x48)),
1116 vocab: (u64le(0x50), u64le(0x58)),
1117 index: (u64le(0x60), u64le(0x68)),
1118 header_hash: u64le(0x70),
1119 dir_hash: u64le(0x78),
1120 };
1121
1122 for (name, (off, len), required) in [
1123 ("header", env.header, true),
1124 ("dir", env.dir, true),
1125 ("data", env.data, false),
1126 ("masks", env.masks, false),
1127 ("vocab", env.vocab, false),
1128 ("index", env.index, false),
1129 ] {
1130 if required && len == 0 {
1131 return Err(CmfError::Bounds(format!("section '{name}' is required")));
1132 }
1133 if len > 0
1134 && off
1135 .checked_add(len)
1136 .map(|end| end > file_len)
1137 .unwrap_or(true)
1138 {
1139 return Err(CmfError::Bounds(format!(
1140 "section '{name}' [{off}, {}) exceeds file ({file_len} bytes)",
1141 off.saturating_add(len)
1142 )));
1143 }
1144 if len > 0
1145 && (usize::try_from(off).is_err()
1146 || usize::try_from(len).is_err()
1147 || usize::try_from(off + len).is_err())
1148 {
1149 return Err(CmfError::Bounds(format!(
1150 "section '{name}' cannot be addressed on this platform"
1151 )));
1152 }
1153 }
1154 if env.data.1 > 0 && env.data.0 % DATA_ALIGNMENT != 0 {
1155 return Err(CmfError::Bounds(format!(
1156 "data section offset {} not {}-aligned",
1157 env.data.0, DATA_ALIGNMENT
1158 )));
1159 }
1160 Ok(env)
1161 }
1162
1163 fn decode_directory(bytes: &[u8]) -> Result<Vec<TensorEntry>, CmfError> {
1164 if bytes.len() < 16 {
1165 return Err(CmfError::Parse("tensor directory too short".into()));
1166 }
1167 let count = u64::from_le_bytes(bytes[0..8].try_into().unwrap()) as usize;
1168 let pool_off = u64::from_le_bytes(bytes[8..16].try_into().unwrap()) as usize;
1169 let records_len = count
1170 .checked_mul(DIR_RECORD_LEN)
1171 .ok_or_else(|| CmfError::Parse("tensor directory record count overflows".into()))?;
1172 let records_end = 16usize
1173 .checked_add(records_len)
1174 .ok_or_else(|| CmfError::Parse("tensor directory size overflows".into()))?;
1175 if records_end > bytes.len() || pool_off > bytes.len() || pool_off < records_end {
1176 return Err(CmfError::Parse(format!(
1177 "tensor directory malformed: count={count}, pool_off={pool_off}, len={}",
1178 bytes.len()
1179 )));
1180 }
1181 let pool = &bytes[pool_off..];
1182
1183 let mut out = Vec::with_capacity(count);
1184 for i in 0..count {
1185 let r = &bytes[16 + i * DIR_RECORD_LEN..16 + (i + 1) * DIR_RECORD_LEN];
1186 let name_off = u32::from_le_bytes(r[0..4].try_into().unwrap()) as usize;
1187 let name_len = u16::from_le_bytes(r[4..6].try_into().unwrap()) as usize;
1188 let dtype_id = r[6];
1189 let ndim = r[7] as usize;
1190 if ndim > DIR_MAX_NDIM {
1191 return Err(CmfError::Parse(format!("tensor #{i}: ndim {ndim} > 6")));
1192 }
1193 let mut shape = Vec::with_capacity(ndim);
1194 for d in 0..ndim {
1195 shape.push(
1196 u32::from_le_bytes(r[8 + d * 4..12 + d * 4].try_into().unwrap()) as usize,
1197 );
1198 }
1199 let off = u64::from_le_bytes(r[32..40].try_into().unwrap());
1200 let nbytes = u64::from_le_bytes(r[40..48].try_into().unwrap());
1201 let hash = u64::from_le_bytes(r[48..56].try_into().unwrap());
1202
1203 let name_end = name_off
1204 .checked_add(name_len)
1205 .ok_or_else(|| CmfError::Parse(format!("tensor #{i}: name range overflows")))?;
1206 if name_end > pool.len() {
1207 return Err(CmfError::Parse(format!("tensor #{i}: name out of pool")));
1208 }
1209 let name = std::str::from_utf8(&pool[name_off..name_end])
1210 .map_err(|_| CmfError::Parse(format!("tensor #{i}: name is not UTF-8")))?
1211 .to_string();
1212 let dtype = TensorDtype::from_id(dtype_id).ok_or(CmfError::UnknownDtype(dtype_id))?;
1213
1214 out.push(TensorEntry {
1215 name,
1216 dtype,
1217 shape,
1218 off,
1219 nbytes,
1220 shard: 0,
1221 hash,
1222 });
1223 }
1224 Ok(out)
1225 }
1226
1227 pub fn arch(&self) -> &ModelArch {
1230 &self.header.arch
1231 }
1232
1233 pub fn tensor(&self, name: &str) -> Option<&TensorEntry> {
1234 self.tensor_index(name).map(|i| &self.tensors[i])
1235 }
1236
1237 pub fn tensor_index(&self, name: &str) -> Option<usize> {
1243 let h = hash64(name.as_bytes());
1244 if let Some(&i) = self.by_name.get(&h) {
1245 if self.tensors[i as usize].name == name {
1246 return Some(i as usize);
1247 }
1248 }
1249 self.name_overflow
1250 .iter()
1251 .copied()
1252 .find(|&i| self.tensors[i as usize].name == name)
1253 .map(|i| i as usize)
1254 }
1255
1256 pub fn resolve_tensor(&self, name: &str, skill: Option<&str>) -> Option<&TensorEntry> {
1265 match self.try_resolve_tensor(name, skill) {
1266 Ok(t) => t,
1267 Err(e) => {
1268 tracing::error!("{e}");
1269 None
1270 }
1271 }
1272 }
1273
1274 pub fn try_resolve_tensor(
1278 &self,
1279 name: &str,
1280 skill: Option<&str>,
1281 ) -> Result<Option<&TensorEntry>, CmfError> {
1282 if let Some(sid) = skill {
1283 if let Some(t) = self.tensor(&format!("skill.{sid}.{name}")) {
1284 return Ok(Some(t));
1285 }
1286 let declared = self
1287 .header
1288 .skills
1289 .iter()
1290 .find(|s| s.id == sid)
1291 .is_some_and(|s| s.kind.is_some() && s.overrides.iter().any(|o| o.name == name));
1292 if declared {
1293 return Err(CmfError::MissingTensor(format!(
1294 "skill.{sid}.{name} (v2 skill '{sid}' overrides '{name}'; \
1295 no fallback to the backbone tensor)"
1296 )));
1297 }
1298 }
1299 Ok(self.tensor(name))
1300 }
1301
1302 pub fn trunk_hash(&self) -> u64 {
1308 self.trunk.unwrap_or_else(|| {
1309 crate::knowledge::trunk_hash_exec(
1310 &self.header,
1311 &self.tensors,
1312 self.vocab.as_deref(),
1313 self.exec_hashes(),
1314 )
1315 })
1316 }
1317
1318 pub fn exec_hashes(&self) -> crate::knowledge::ExecHashes {
1321 if self.header.genome.is_some() {
1322 return self.exec;
1323 }
1324 let bytes = self.backing.bytes();
1325 let sec = |(off, len): (u64, u64)| -> Option<&[u8]> {
1326 (len > 0).then(|| bytes.get(off as usize..(off + len) as usize))?
1327 };
1328 crate::knowledge::ExecHashes::of(sec(self.envelope.masks), sec(self.envelope.index))
1329 }
1330
1331 pub fn skill_tensors(&self, skill_id: &str) -> impl Iterator<Item = &TensorEntry> {
1334 let prefix = format!("skill.{skill_id}.");
1335 self.tensors
1336 .iter()
1337 .filter(move |t| t.name.starts_with(&prefix))
1338 }
1339
1340 pub fn tensor_bytes(&self, name: &str) -> Result<&[u8], CmfError> {
1342 let entry = self
1343 .tensor(name)
1344 .ok_or_else(|| CmfError::MissingTensor(name.to_string()))?;
1345 Ok(self.entry_bytes(entry))
1346 }
1347
1348 pub fn dir_hash(&self) -> u64 {
1353 self.envelope.dir_hash
1354 }
1355
1356 pub fn primary_bytes(&self) -> &[u8] {
1357 self.backing.bytes()
1358 }
1359
1360 pub fn advise_done(&self, pred: impl Fn(&str) -> bool) -> usize {
1371 #[cfg(unix)]
1372 {
1373 let base = self.primary_bytes().as_ptr() as usize;
1374 let map_len = self.primary_bytes().len();
1375 let page = 16384usize.max(unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize);
1376 let mut ranges: Vec<(usize, usize)> = self
1377 .tensors
1378 .iter()
1379 .filter(|e| e.shard == 0 && pred(&e.name))
1380 .filter_map(|e| {
1381 let abs = self.entry_abs_offset(e)?;
1382 Some((abs, abs + e.nbytes as usize))
1383 })
1384 .collect();
1385 ranges.sort_unstable();
1386 let mut dropped = 0usize;
1387 let mut merged: Vec<(usize, usize)> = Vec::new();
1388 for (s, e) in ranges {
1389 match merged.last_mut() {
1390 Some(l) if s <= l.1 => l.1 = l.1.max(e),
1391 _ => merged.push((s, e)),
1392 }
1393 }
1394 for (s, e) in merged {
1395 let s = s.div_ceil(page) * page;
1397 let e = (e.min(map_len)) / page * page;
1398 if e > s {
1399 let r = unsafe {
1400 libc::madvise((base + s) as *mut libc::c_void, e - s, libc::MADV_DONTNEED)
1401 };
1402 if r == 0 {
1403 dropped += e - s;
1404 }
1405 }
1406 }
1407 dropped
1408 }
1409 #[cfg(not(unix))]
1410 {
1411 let _ = pred;
1412 0
1413 }
1414 }
1415
1416 pub fn entry_abs_offset(&self, entry: &TensorEntry) -> Option<usize> {
1417 (entry.shard == 0).then(|| (self.data_off + entry.off) as usize)
1418 }
1419
1420 pub fn entry_bytes(&self, entry: &TensorEntry) -> &[u8] {
1421 let (bytes, data_off) = if entry.shard == 0 {
1422 (self.backing.bytes(), self.data_off)
1423 } else {
1424 let (b, o) = &self.extra_shards[entry.shard - 1];
1425 (b.bytes(), *o)
1426 };
1427 let start = (data_off + entry.off) as usize;
1428 &bytes[start..start + entry.nbytes as usize]
1429 }
1430
1431 pub fn evict_ranges(&self, ranges: &[(usize, usize)]) {
1440 #[cfg(target_os = "linux")]
1441 {
1442 let Backing::Mmap(m) = &self.backing else {
1443 return;
1444 };
1445 let page = 4096usize;
1446 use std::os::unix::io::AsRawFd;
1449 let file = File::open(&self.path).ok();
1450 for &(off, len) in ranges {
1451 if len == 0 || off.saturating_add(len) > m.len() {
1452 continue;
1453 }
1454 let start = off & !(page - 1);
1455 let end = off + len;
1456 let alen = end.next_multiple_of(page).min(m.len()) - start;
1457 let _ = unsafe {
1460 m.unchecked_advise_range(memmap2::UncheckedAdvice::DontNeed, start, alen)
1461 };
1462 if let Some(f) = &file {
1463 unsafe {
1465 libc::posix_fadvise(
1466 f.as_raw_fd(),
1467 start as libc::off_t,
1468 alen as libc::off_t,
1469 libc::POSIX_FADV_DONTNEED,
1470 );
1471 }
1472 }
1473 }
1474 }
1475 #[cfg(not(target_os = "linux"))]
1476 let _ = ranges;
1477 }
1478
1479 pub fn layer_tensors(&self, layer_idx: usize) -> Vec<&TensorEntry> {
1481 let prefix = format!("model.layers.{layer_idx}.");
1482 self.tensors
1483 .iter()
1484 .filter(|t| t.name.starts_with(&prefix))
1485 .collect()
1486 }
1487
1488 pub fn total_param_count(&self) -> u64 {
1490 self.tensors
1491 .iter()
1492 .filter(|t| t.shape.len() >= 2)
1493 .map(|t| t.n_elems() as u64)
1494 .sum()
1495 }
1496
1497 pub fn recode_entries_in_place(
1508 path: &str,
1509 patches: &[(usize, TensorDtype, Vec<u8>)],
1510 ) -> Result<(), CmfError> {
1511 use std::io::{Read, Seek, SeekFrom, Write};
1512 let mut f = std::fs::OpenOptions::new()
1513 .read(true)
1514 .write(true)
1515 .open(path)?;
1516 let file_len = f.metadata()?.len();
1517 let mut head = vec![0u8; ENVELOPE_LEN];
1518 f.read_exact(&mut head)?;
1519 let env = Self::parse_envelope(&head, file_len)?;
1520 let genome_by_content = peek_header(path)?
1522 .is_some_and(|(_, header)| header.get("genome").is_some_and(|g| !g.is_null()));
1523 if env.required_features & features::GENOME != 0 || genome_by_content {
1524 return Err(CmfError::Parse(format!(
1525 "recode: {path} carries a frozen genome (GENOME bit) — rewriting its \
1526 payloads in place would change the trunk without a new genome. A requant \
1527 must write a new file that rewrites genome.trunk_hash/encoding and records \
1528 a `requant` lineage event"
1529 )));
1530 }
1531
1532 let mut dir = vec![0u8; env.dir.1 as usize];
1533 f.seek(SeekFrom::Start(env.dir.0))?;
1534 f.read_exact(&mut dir)?;
1535 let count = u64::from_le_bytes(dir[0..8].try_into().unwrap()) as usize;
1536
1537 for (i, dtype, data) in patches {
1538 if *i >= count {
1539 return Err(CmfError::Bounds(format!(
1540 "recode: tensor #{i} out of directory ({count} entries)"
1541 )));
1542 }
1543 let rb = 16 + i * DIR_RECORD_LEN;
1544 let rec = &mut dir[rb..rb + DIR_RECORD_LEN];
1545 let off = u64::from_le_bytes(rec[32..40].try_into().unwrap());
1546 let old_n = u64::from_le_bytes(rec[40..48].try_into().unwrap());
1547 if data.len() as u64 > old_n {
1548 return Err(CmfError::Bounds(format!(
1549 "recode: tensor #{i} payload {} > slot {old_n}",
1550 data.len()
1551 )));
1552 }
1553 f.seek(SeekFrom::Start(env.data.0 + off))?;
1554 f.write_all(data)?;
1555 rec[6] = dtype.id();
1556 rec[40..48].copy_from_slice(&(data.len() as u64).to_le_bytes());
1557 rec[48..56].copy_from_slice(&hash64(data).to_le_bytes());
1558 }
1559
1560 f.seek(SeekFrom::Start(env.dir.0))?;
1561 f.write_all(&dir)?;
1562 f.seek(SeekFrom::Start(0x78))?;
1563 f.write_all(&hash64(&dir).to_le_bytes())?;
1564 f.sync_all()?;
1565 Ok(())
1566 }
1567
1568 pub fn verify(&self) -> Vec<String> {
1571 let mut problems = Vec::new();
1572
1573 let bytes = self.backing.bytes();
1575 let env = &self.envelope;
1576 let sect = |(off, len): (u64, u64)| &bytes[off as usize..(off + len) as usize];
1577 let check = |name: &str, stored: u64, span: (u64, u64)| -> Option<String> {
1578 if stored != 0 && span.1 > 0 {
1579 let actual = hash64(sect(span));
1580 if actual != stored {
1581 return Some(format!(
1582 "section '{name}': hash mismatch (stored {stored:016x}, \
1583 actual {actual:016x})"
1584 ));
1585 }
1586 }
1587 None
1588 };
1589 problems.extend(check("header", env.header_hash, env.header));
1590 problems.extend(check("dir", env.dir_hash, env.dir));
1591 if let Some(sh) = &self.header.section_hashes {
1592 for (name, hex, span) in [
1593 ("masks", &sh.masks, env.masks),
1594 ("vocab", &sh.vocab, env.vocab),
1595 ("index", &sh.index, env.index),
1596 ] {
1597 if let Some(hex) = hex {
1598 match u64::from_str_radix(hex, 16) {
1599 Ok(stored) => problems.extend(check(name, stored, span)),
1600 Err(_) => {
1601 problems.push(format!("section '{name}': malformed hash '{hex}'"))
1602 }
1603 }
1604 }
1605 }
1606 }
1607
1608 for t in &self.tensors {
1609 let actual = hash64(self.entry_bytes(t));
1610 if actual != t.hash {
1611 problems.push(format!(
1612 "tensor '{}': hash mismatch (stored {:016x}, actual {:016x})",
1613 t.name, t.hash, actual
1614 ));
1615 }
1616 }
1617 problems
1618 }
1619
1620 pub fn compute_active_size(&self, mask: &TaskMask) -> u64 {
1623 let arch = &self.header.arch;
1624 let mut total = 0u64;
1625 for li in 0..arch.num_layers {
1626 if !mask.layer_alive(li) {
1627 continue;
1628 }
1629 let ffn_frac = mask.ffn_active_count(li) as f64 / arch.intermediate_size.max(1) as f64;
1630 let head_frac =
1631 mask.active_head_count(li) as f64 / arch.num_attention_heads.max(1) as f64;
1632 for t in self.layer_tensors(li) {
1633 let frac = if t.name.contains(".mlp.") {
1634 ffn_frac
1635 } else if t.name.contains(".self_attn.") {
1636 head_frac
1637 } else {
1638 1.0
1639 };
1640 total += (t.nbytes as f64 * frac) as u64;
1641 }
1642 }
1643 total
1644 }
1645
1646 pub fn write(
1651 path: impl AsRef<Path>,
1652 header: &CmfHeader,
1653 tensors: &[TensorSpec],
1654 masks: Option<&MaskCatalog>,
1655 vocab: Option<&[u8]>,
1656 ) -> Result<(), CmfError> {
1657 let refs: Vec<TensorSpecRef> = tensors
1658 .iter()
1659 .map(|t| TensorSpecRef {
1660 name: t.name.clone(),
1661 dtype: t.dtype,
1662 shape: t.shape.clone(),
1663 data: &t.data,
1664 })
1665 .collect();
1666 Self::write_ref(path, header, &refs, masks, vocab)
1667 }
1668
1669 pub fn write_ref(
1673 path: impl AsRef<Path>,
1674 header: &CmfHeader,
1675 tensors: &[TensorSpecRef],
1676 masks: Option<&MaskCatalog>,
1677 vocab: Option<&[u8]>,
1678 ) -> Result<(), CmfError> {
1679 let path = path.as_ref();
1680 if let Some(prism) = header.arch.prism_hadamard.as_ref() {
1681 prism.validate().map_err(CmfError::Parse)?;
1682 if header.arch.arch_name != "prism_hadamard_qwen35" {
1683 return Err(CmfError::Parse(
1684 "prism_hadamard metadata requires arch_name prism_hadamard_qwen35".into(),
1685 ));
1686 }
1687 }
1688 header
1689 .arch
1690 .validate_operator_metadata()
1691 .map_err(CmfError::Parse)?;
1692
1693 let mut entries = Vec::with_capacity(tensors.len());
1695 let mut data_cursor = 0u64;
1696 for t in tensors {
1697 if t.shape.len() > DIR_MAX_NDIM {
1698 return Err(CmfError::Parse(format!(
1699 "tensor '{}': ndim {} > 6",
1700 t.name,
1701 t.shape.len()
1702 )));
1703 }
1704 if let Some(expect) = expected_nbytes(t.dtype, &t.shape) {
1705 if expect != t.data.len() {
1706 return Err(CmfError::Bounds(format!(
1707 "tensor '{}': data {} bytes != expected {} for {:?}{:?}",
1708 t.name,
1709 t.data.len(),
1710 expect,
1711 t.dtype,
1712 t.shape
1713 )));
1714 }
1715 }
1716 let align = if t.data.len() as u64 >= LARGE_TENSOR_MIN {
1717 LARGE_TENSOR_ALIGN
1718 } else {
1719 TENSOR_ALIGNMENT
1720 };
1721 data_cursor = align_to(data_cursor, align);
1722 entries.push(TensorEntry {
1723 name: t.name.clone(),
1724 dtype: t.dtype,
1725 shape: t.shape.clone(),
1726 off: data_cursor,
1727 nbytes: t.data.len() as u64,
1728 shard: 0,
1729 hash: hash64(t.data),
1730 });
1731 data_cursor += t.data.len() as u64;
1732 }
1733 let data_len = data_cursor;
1734
1735 validate_prism_affine_targets(&header.arch, &entries)?;
1736
1737 let dir_bytes = Self::encode_directory(&entries);
1738
1739 let masks_bytes = match masks {
1740 Some(catalog) if !catalog.masks.is_empty() => {
1741 Some(encode_masks_section(catalog, &header.arch).map_err(CmfError::Parse)?)
1742 }
1743 _ => None,
1744 };
1745 let index_bytes = match masks {
1746 Some(catalog) if !catalog.masks.is_empty() => {
1747 let idx = build_sparse_index(catalog, &header.arch);
1748 Some(encode_sparse_index(&idx))
1749 }
1750 _ => None,
1751 };
1752
1753 let hex = |b: Option<&[u8]>| b.map(|b| format!("{:016x}", hash64(b)));
1756 let mut header = header.clone();
1757 if masks_bytes.is_some() || vocab.is_some() || index_bytes.is_some() {
1758 header.section_hashes = Some(SectionHashes {
1759 masks: hex(masks_bytes.as_deref()),
1760 vocab: hex(vocab),
1761 index: hex(index_bytes.as_deref()),
1762 });
1763 }
1764 header.segments.clear();
1767 let exec = crate::knowledge::ExecHashes::of(masks_bytes.as_deref(), index_bytes.as_deref());
1771 crate::knowledge::seal_genome(&mut header, &entries, vocab, exec)?;
1772
1773 let required_features = derive_required_features(&header, &entries, masks_bytes.is_some());
1774 crate::knowledge::validate_knowledge(
1777 &header,
1778 &entries,
1779 vocab,
1780 required_features,
1781 Some(data_len),
1782 None,
1783 exec,
1784 )?;
1785 crate::knowledge::check_lookup_policies(&header)?;
1786 crate::knowledge::validate_expert_append_values(&header, &entries, |e| {
1788 let i = entries.iter().position(|x| std::ptr::eq(x, e))?;
1789 crate::knowledge::f32_le_head(tensors[i].data)
1790 })?;
1791 crate::knowledge::validate_lookup_values(&header, &entries, true, |e| {
1792 let i = entries.iter().position(|x| std::ptr::eq(x, e))?;
1793 Some(std::borrow::Cow::Borrowed(tensors[i].data))
1794 })?;
1795 let header_json =
1796 serde_json::to_vec(&header).map_err(|e| CmfError::Parse(format!("header: {e}")))?;
1797
1798 let header_off = ENVELOPE_LEN as u64;
1800 let dir_off = header_off + header_json.len() as u64;
1801 let data_off = align_to(dir_off + dir_bytes.len() as u64, DATA_ALIGNMENT);
1802 let masks_off = data_off + data_len;
1803 let masks_len = masks_bytes.as_ref().map(|b| b.len() as u64).unwrap_or(0);
1804 let vocab_off = masks_off + masks_len;
1805 let vocab_len = vocab.map(|b| b.len() as u64).unwrap_or(0);
1806 let index_off = vocab_off + vocab_len;
1807 let index_len = index_bytes.as_ref().map(|b| b.len() as u64).unwrap_or(0);
1808
1809 let mut env = Vec::with_capacity(ENVELOPE_LEN);
1811 env.extend_from_slice(&CMF_MAGIC);
1812 env.extend_from_slice(&CMF_VERSION.to_le_bytes());
1813 env.extend_from_slice(&0u32.to_le_bytes()); env.extend_from_slice(&required_features.to_le_bytes());
1815 for (off, len) in [
1816 (header_off, header_json.len() as u64),
1817 (dir_off, dir_bytes.len() as u64),
1818 (data_off, data_len),
1819 (if masks_len > 0 { masks_off } else { 0 }, masks_len),
1820 (if vocab_len > 0 { vocab_off } else { 0 }, vocab_len),
1821 (if index_len > 0 { index_off } else { 0 }, index_len),
1822 ] {
1823 env.extend_from_slice(&off.to_le_bytes());
1824 env.extend_from_slice(&len.to_le_bytes());
1825 }
1826 env.extend_from_slice(&hash64(&header_json).to_le_bytes());
1828 env.extend_from_slice(&hash64(&dir_bytes).to_le_bytes());
1829 env.resize(ENVELOPE_LEN, 0);
1830
1831 let mut f = BufWriter::new(File::create(path)?);
1833 f.write_all(&env)?;
1834 f.write_all(&header_json)?;
1835 f.write_all(&dir_bytes)?;
1836 let mut pos = dir_off + dir_bytes.len() as u64;
1837 f.write_all(&zeros((data_off - pos) as usize))?;
1838 pos = data_off;
1839 for (spec, entry) in tensors.iter().zip(&entries) {
1840 let target = data_off + entry.off;
1841 f.write_all(&zeros((target - pos) as usize))?;
1842 f.write_all(spec.data)?;
1843 pos = target + spec.data.len() as u64;
1844 }
1845 debug_assert_eq!(pos, data_off + data_len);
1846 if let Some(mb) = &masks_bytes {
1847 f.write_all(mb)?;
1848 }
1849 if let Some(vb) = vocab {
1850 f.write_all(vb)?;
1851 }
1852 if let Some(ib) = &index_bytes {
1853 f.write_all(ib)?;
1854 }
1855 f.flush()?;
1856
1857 tracing::info!(
1858 "Wrote CMF v2: {} ({} tensors, {} masks, {:.1} MB)",
1859 path.display(),
1860 entries.len(),
1861 masks.map(|m| m.masks.len()).unwrap_or(0),
1862 std::fs::metadata(path)?.len() as f64 / 1e6
1863 );
1864 Ok(())
1865 }
1866
1867 pub(crate) fn encode_directory(entries: &[TensorEntry]) -> Vec<u8> {
1868 let mut pool = Vec::new();
1869 let mut name_offs = Vec::with_capacity(entries.len());
1870 for e in entries {
1871 name_offs.push((pool.len() as u32, e.name.len() as u16));
1872 pool.extend_from_slice(e.name.as_bytes());
1873 }
1874 let pool_off = 16 + entries.len() * DIR_RECORD_LEN;
1875
1876 let mut out = Vec::with_capacity(pool_off + pool.len());
1877 out.extend_from_slice(&(entries.len() as u64).to_le_bytes());
1878 out.extend_from_slice(&(pool_off as u64).to_le_bytes());
1879 for (e, (noff, nlen)) in entries.iter().zip(&name_offs) {
1880 out.extend_from_slice(&noff.to_le_bytes());
1881 out.extend_from_slice(&nlen.to_le_bytes());
1882 out.push(e.dtype.id());
1883 out.push(e.shape.len() as u8);
1884 for d in 0..DIR_MAX_NDIM {
1885 out.extend_from_slice(&(e.shape.get(d).copied().unwrap_or(0) as u32).to_le_bytes());
1886 }
1887 out.extend_from_slice(&e.off.to_le_bytes());
1888 out.extend_from_slice(&e.nbytes.to_le_bytes());
1889 out.extend_from_slice(&e.hash.to_le_bytes());
1890 }
1891 out.extend_from_slice(&pool);
1892 out
1893 }
1894}
1895
1896fn align_to(x: u64, a: u64) -> u64 {
1897 x.div_ceil(a) * a
1898}
1899
1900pub fn check_required_features(required: u32, supported: u32) -> Result<(), CmfError> {
1904 let unknown = required & !supported;
1905 if unknown != 0 {
1906 return Err(CmfError::UnsupportedFeature(unknown));
1907 }
1908 Ok(())
1909}
1910
1911pub fn derive_required_features(
1916 header: &CmfHeader,
1917 entries: &[TensorEntry],
1918 masks_present: bool,
1919) -> u32 {
1920 let mut f = features::TENSOR_DIR | prism_bits(&header.arch);
1921 if is_decision_profile(&header.arch.arch_name) {
1924 f |= features::DECISION;
1925 }
1926 if masks_present {
1927 f |= features::BINARY_MASKS;
1928 if header.arch.num_loops > 1 {
1929 f |= features::LOOP_MASKS;
1930 }
1931 }
1932 if entries
1933 .iter()
1934 .any(|t| matches!(t.dtype, TensorDtype::Q8_2f | TensorDtype::Vbit))
1935 {
1936 f |= features::QUANT_2F;
1937 }
1938 if header.skills.iter().any(|s| s.base_dir_hash.is_some()) {
1941 f |= features::SKILL_FILE;
1942 }
1943 if header.arch.anchor_core.is_some() {
1946 f |= features::BOUNDED_STATE;
1947 }
1948 if header.arch.block_diffusion.is_some() {
1950 f |= features::BLOCK_DIFFUSION;
1951 }
1952 f | crate::knowledge::knowledge_bits(header)
1953}
1954
1955fn zeros(n: usize) -> Vec<u8> {
1956 vec![0u8; n]
1957}
1958
1959fn sync_parent_dir(path: &Path) -> Result<(), CmfError> {
1968 #[cfg(unix)]
1969 {
1970 let parent = match path.parent() {
1973 Some(p) if !p.as_os_str().is_empty() => p,
1974 _ => Path::new("."),
1975 };
1976 File::open(parent)?.sync_all()?;
1977 }
1978 #[cfg(not(unix))]
1979 let _ = path;
1980 Ok(())
1981}
1982
1983pub struct ResumeState {
2002 pub names: Vec<String>,
2003 pub marks: Vec<String>,
2004}
2005
2006pub struct CmfStreamWriter {
2007 file: BufWriter<File>,
2008 path: PathBuf,
2009 data_off: u64,
2011 cursor: u64,
2013 entries: Vec<TensorEntry>,
2014 manifest: Option<BufWriter<File>>,
2018}
2019
2020impl CmfStreamWriter {
2021 pub fn head_reserve_for(n_tensors: usize, avg_name: usize) -> u64 {
2026 let dir = 16 + n_tensors * (DIR_RECORD_LEN + 6 + avg_name);
2027 let head = ENVELOPE_LEN + dir + (1 << 20);
2028 align_to(3 * head as u64, DATA_ALIGNMENT).max(1 << 20)
2033 }
2034
2035 fn create(path: impl AsRef<Path>, gap: u64, no_clobber: bool) -> Result<Self, CmfError> {
2036 let path = path.as_ref().to_path_buf();
2037 let data_off = align_to(gap.max(ENVELOPE_LEN as u64 + 1), DATA_ALIGNMENT);
2038 let mut options = std::fs::OpenOptions::new();
2041 options.read(true).write(true);
2042 if no_clobber {
2043 options.create_new(true);
2044 } else {
2045 options.create(true).truncate(true);
2046 }
2047 let file = options.open(&path)?;
2048 let mut file = BufWriter::new(file);
2049 file.write_all(&zeros(data_off as usize))?;
2050 Ok(Self {
2051 file,
2052 path,
2053 data_off,
2054 cursor: 0,
2055 entries: Vec::new(),
2056 manifest: None,
2057 })
2058 }
2059
2060 pub fn new(path: impl AsRef<Path>, gap: u64) -> Result<Self, CmfError> {
2064 Self::create(path, gap, false)
2065 }
2066
2067 pub fn new_no_clobber(path: impl AsRef<Path>, gap: u64) -> Result<Self, CmfError> {
2072 Self::create(path, gap, true)
2073 }
2074
2075 pub fn push(
2078 &mut self,
2079 name: &str,
2080 dtype: TensorDtype,
2081 shape: &[usize],
2082 data: &[u8],
2083 ) -> Result<(), CmfError> {
2084 self.push_bounded(name, dtype, shape, data, data.len().max(1))
2085 }
2086
2087 pub fn push_bounded(
2096 &mut self,
2097 name: &str,
2098 dtype: TensorDtype,
2099 shape: &[usize],
2100 data: &[u8],
2101 chunk_bytes: usize,
2102 ) -> Result<(), CmfError> {
2103 self.push_bounded_impl(name, dtype, shape, data, chunk_bytes, None)
2104 }
2105
2106 pub fn push_bounded_prehashed(
2115 &mut self,
2116 name: &str,
2117 dtype: TensorDtype,
2118 shape: &[usize],
2119 data: &[u8],
2120 hash: u64,
2121 chunk_bytes: usize,
2122 ) -> Result<(), CmfError> {
2123 self.push_bounded_impl(name, dtype, shape, data, chunk_bytes, Some(hash))
2124 }
2125
2126 fn push_bounded_impl(
2127 &mut self,
2128 name: &str,
2129 dtype: TensorDtype,
2130 shape: &[usize],
2131 data: &[u8],
2132 chunk_bytes: usize,
2133 known_hash: Option<u64>,
2134 ) -> Result<(), CmfError> {
2135 if shape.len() > DIR_MAX_NDIM {
2136 return Err(CmfError::Parse(format!(
2137 "tensor '{}': ndim {} > {}",
2138 name,
2139 shape.len(),
2140 DIR_MAX_NDIM
2141 )));
2142 }
2143 if let Some(expect) = expected_nbytes(dtype, shape) {
2144 if expect != data.len() {
2145 return Err(CmfError::Bounds(format!(
2146 "tensor '{}': data {} bytes != expected {} for {:?}{:?}",
2147 name,
2148 data.len(),
2149 expect,
2150 dtype,
2151 shape
2152 )));
2153 }
2154 }
2155 let align = if data.len() as u64 >= LARGE_TENSOR_MIN {
2156 LARGE_TENSOR_ALIGN
2157 } else {
2158 TENSOR_ALIGNMENT
2159 };
2160 let off = align_to(self.cursor, align);
2161 self.file.write_all(&zeros((off - self.cursor) as usize))?;
2162 for chunk in data.chunks(chunk_bytes.max(1)) {
2163 self.file.write_all(chunk)?;
2164 }
2165 self.entries.push(TensorEntry {
2166 name: name.to_string(),
2167 dtype,
2168 shape: shape.to_vec(),
2169 off,
2170 nbytes: data.len() as u64,
2171 shard: 0,
2172 hash: known_hash.unwrap_or_else(|| hash64(data)),
2173 });
2174 self.cursor = off + data.len() as u64;
2175 if let Some(m) = self.manifest.as_mut() {
2176 let e = self.entries.last().unwrap();
2177 writeln!(
2178 m,
2179 "{{\"name\":{},\"dtype\":{},\"shape\":{:?},\"off\":{},\"nbytes\":{},\"hash\":{}}}",
2180 serde_json::to_string(&e.name).unwrap_or_else(|_| "\"?\"".into()),
2181 dtype.id(),
2182 e.shape,
2183 e.off,
2184 e.nbytes,
2185 e.hash
2186 )?;
2187 m.flush()?;
2188 }
2189 Ok(())
2190 }
2191
2192 pub fn with_manifest(mut self, path: impl AsRef<Path>) -> Result<Self, CmfError> {
2195 let path = path.as_ref().to_path_buf();
2196 let mut f = BufWriter::new(File::create(&path)?);
2197 writeln!(f, "{{\"data_off\":{}}}", self.data_off)?;
2198 f.flush()?;
2199 f.get_ref().sync_data()?;
2203 sync_parent_dir(&path)?;
2204 self.manifest = Some(f);
2205 Ok(self)
2206 }
2207
2208 pub fn appending_manifest(mut self, path: impl AsRef<Path>) -> Result<Self, CmfError> {
2211 let path = path.as_ref().to_path_buf();
2212 let file = std::fs::OpenOptions::new().append(true).open(&path)?;
2213 file.sync_data()?;
2216 sync_parent_dir(&path)?;
2217 self.manifest = Some(BufWriter::new(file));
2218 Ok(self)
2219 }
2220
2221 pub fn resume(
2226 path: impl AsRef<Path>,
2227 manifest: impl AsRef<Path>,
2228 ) -> Result<(Self, ResumeState), CmfError> {
2229 let path = path.as_ref().to_path_buf();
2230 let text = std::fs::read_to_string(manifest.as_ref())?;
2231 let mut lines = text.lines();
2232 let first = lines
2233 .next()
2234 .ok_or_else(|| CmfError::Parse("manifest is empty".into()))?;
2235 let head: serde_json::Value = serde_json::from_str(first)
2236 .map_err(|e| CmfError::Parse(format!("manifest head: {e}")))?;
2237 let data_off = head["data_off"]
2238 .as_u64()
2239 .ok_or_else(|| CmfError::Parse("manifest head has no data_off".into()))?;
2240
2241 let mut entries = Vec::new();
2242 let mut names = Vec::new();
2243 let mut marks = Vec::new();
2244 let (mut safe_upto, mut safe_entries) = (0u64, 0usize);
2245 for (i, line) in lines.enumerate() {
2246 if line.trim().is_empty() {
2247 continue;
2248 }
2249 let Ok(v) = serde_json::from_str::<serde_json::Value>(line) else {
2252 tracing::warn!("manifest line {} is truncated — ignoring it", i + 2);
2253 break;
2254 };
2255 if let Some(mark) = v["mark"].as_str() {
2256 marks.push(mark.to_string());
2257 safe_upto = v["at"].as_u64().unwrap_or(0);
2262 safe_entries = entries.len();
2263 continue;
2264 }
2265 let dtype = TensorDtype::from_id(v["dtype"].as_u64().unwrap_or(0) as u8)
2266 .ok_or_else(|| CmfError::Parse(format!("manifest line {}: dtype", i + 2)))?;
2267 let name = v["name"].as_str().unwrap_or_default().to_string();
2268 names.push(name.clone());
2269 entries.push(TensorEntry {
2270 name,
2271 dtype,
2272 shape: v["shape"]
2273 .as_array()
2274 .map(|a| {
2275 a.iter()
2276 .filter_map(|x| x.as_u64())
2277 .map(|x| x as usize)
2278 .collect()
2279 })
2280 .unwrap_or_default(),
2281 off: v["off"].as_u64().unwrap_or(0),
2282 nbytes: v["nbytes"].as_u64().unwrap_or(0),
2283 shard: 0,
2284 hash: v["hash"].as_u64().unwrap_or(0),
2285 });
2286 }
2287 entries.truncate(safe_entries);
2288 names.truncate(safe_entries);
2289 let cursor = safe_upto;
2290 debug_assert_eq!(
2291 entries.last().map(|e| e.off + e.nbytes).unwrap_or(0),
2292 cursor,
2293 "the last mark disagrees with the entries before it"
2294 );
2295 let on_disk = std::fs::metadata(&path)?.len();
2296 if on_disk < data_off + cursor {
2297 return Err(CmfError::Bounds(format!(
2298 "{} is {on_disk} bytes but its last checkpoint claims {} — \
2299 the file is shorter than its own record",
2300 path.display(),
2301 data_off + cursor
2302 )));
2303 }
2304 let mut file = std::fs::OpenOptions::new().write(true).open(&path)?;
2305 file.seek(SeekFrom::Start(data_off + cursor))?;
2306 Ok((
2307 Self {
2308 file: BufWriter::new(file),
2309 path,
2310 data_off,
2311 cursor,
2312 entries,
2313 manifest: None,
2314 },
2315 ResumeState { names, marks },
2316 ))
2317 }
2318
2319 pub fn mark(&mut self, note: &str) -> Result<(), CmfError> {
2324 self.file.flush()?;
2328 self.file.get_ref().sync_data()?;
2329 if let Some(m) = self.manifest.as_mut() {
2330 writeln!(
2331 m,
2332 "{{\"mark\":{},\"at\":{}}}",
2333 serde_json::to_string(note).unwrap_or_default(),
2334 self.cursor
2335 )?;
2336 m.flush()?;
2337 m.get_ref().sync_data()?;
2338 }
2339 sync_parent_dir(&self.path)?;
2342 Ok(())
2343 }
2344
2345 pub fn tensor_count(&self) -> usize {
2346 self.entries.len()
2347 }
2348
2349 pub fn data_len(&self) -> u64 {
2351 self.cursor
2352 }
2353
2354 pub fn finish(
2356 mut self,
2357 header: &CmfHeader,
2358 masks: Option<&MaskCatalog>,
2359 vocab: Option<&[u8]>,
2360 ) -> Result<(), CmfError> {
2361 let data_len = self.cursor;
2362
2363 let masks_bytes = match masks {
2364 Some(catalog) if !catalog.masks.is_empty() => {
2365 Some(encode_masks_section(catalog, &header.arch).map_err(CmfError::Parse)?)
2366 }
2367 _ => None,
2368 };
2369 let index_bytes = match masks {
2370 Some(catalog) if !catalog.masks.is_empty() => Some(encode_sparse_index(
2371 &build_sparse_index(catalog, &header.arch),
2372 )),
2373 _ => None,
2374 };
2375 if let Some(mb) = &masks_bytes {
2376 self.file.write_all(mb)?;
2377 }
2378 if let Some(vb) = vocab {
2379 self.file.write_all(vb)?;
2380 }
2381 if let Some(ib) = &index_bytes {
2382 self.file.write_all(ib)?;
2383 }
2384 self.file.flush()?;
2385
2386 let dir_bytes = CmfModel::encode_directory(&self.entries);
2387
2388 validate_prism_affine_targets(&header.arch, &self.entries)?;
2389
2390 let hex = |b: Option<&[u8]>| b.map(|b| format!("{:016x}", hash64(b)));
2391 let mut header = header.clone();
2392 if masks_bytes.is_some() || vocab.is_some() || index_bytes.is_some() {
2393 header.section_hashes = Some(SectionHashes {
2394 masks: hex(masks_bytes.as_deref()),
2395 vocab: hex(vocab),
2396 index: hex(index_bytes.as_deref()),
2397 });
2398 }
2399 header
2403 .arch
2404 .validate_operator_metadata()
2405 .map_err(CmfError::Parse)?;
2406 header.segments.clear();
2407 let exec = crate::knowledge::ExecHashes::of(masks_bytes.as_deref(), index_bytes.as_deref());
2408 crate::knowledge::seal_genome(&mut header, &self.entries, vocab, exec)?;
2409 let required_features =
2410 derive_required_features(&header, &self.entries, masks_bytes.is_some());
2411 crate::knowledge::validate_knowledge(
2412 &header,
2413 &self.entries,
2414 vocab,
2415 required_features,
2416 Some(data_len),
2417 None,
2418 exec,
2419 )?;
2420 crate::knowledge::check_lookup_policies(&header)?;
2421 {
2423 use std::io::Read;
2424 let (file, data_off) = (self.file.get_mut(), self.data_off);
2425 crate::knowledge::validate_expert_append_values(&header, &self.entries, |e| {
2426 let mut b = [0u8; 4];
2427 file.seek(SeekFrom::Start(data_off + e.off)).ok()?;
2428 file.read_exact(&mut b).ok()?;
2429 Some(f32::from_le_bytes(b))
2430 })?;
2431 crate::knowledge::validate_lookup_values(&header, &self.entries, true, |e| {
2433 let mut b = vec![0u8; e.nbytes as usize];
2434 file.seek(SeekFrom::Start(data_off + e.off)).ok()?;
2435 file.read_exact(&mut b).ok()?;
2436 Some(std::borrow::Cow::Owned(b))
2437 })?;
2438 }
2439 let header_json =
2440 serde_json::to_vec(&header).map_err(|e| CmfError::Parse(format!("header: {e}")))?;
2441
2442 let header_off = ENVELOPE_LEN as u64;
2443 let dir_off = header_off + header_json.len() as u64;
2444 let head_len = dir_off + dir_bytes.len() as u64;
2445 if head_len > self.data_off {
2446 return Err(CmfError::Parse(format!(
2447 "streamed head is {head_len} bytes but only {} were reserved — \
2448 the payloads are already on disk at a fixed offset, so this \
2449 file cannot be salvaged; re-run with a larger reserve",
2450 self.data_off
2451 )));
2452 }
2453 let data_off = self.data_off;
2454 let masks_off = data_off + data_len;
2455 let masks_len = masks_bytes.as_ref().map(|b| b.len() as u64).unwrap_or(0);
2456 let vocab_off = masks_off + masks_len;
2457 let vocab_len = vocab.map(|b| b.len() as u64).unwrap_or(0);
2458 let index_off = vocab_off + vocab_len;
2459 let index_len = index_bytes.as_ref().map(|b| b.len() as u64).unwrap_or(0);
2460
2461 let mut env = Vec::with_capacity(ENVELOPE_LEN);
2462 env.extend_from_slice(&CMF_MAGIC);
2463 env.extend_from_slice(&CMF_VERSION.to_le_bytes());
2464 env.extend_from_slice(&0u32.to_le_bytes());
2465 env.extend_from_slice(&required_features.to_le_bytes());
2466 for (off, len) in [
2467 (header_off, header_json.len() as u64),
2468 (dir_off, dir_bytes.len() as u64),
2469 (data_off, data_len),
2470 (if masks_len > 0 { masks_off } else { 0 }, masks_len),
2471 (if vocab_len > 0 { vocab_off } else { 0 }, vocab_len),
2472 (if index_len > 0 { index_off } else { 0 }, index_len),
2473 ] {
2474 env.extend_from_slice(&off.to_le_bytes());
2475 env.extend_from_slice(&len.to_le_bytes());
2476 }
2477 env.extend_from_slice(&hash64(&header_json).to_le_bytes());
2478 env.extend_from_slice(&hash64(&dir_bytes).to_le_bytes());
2479 env.resize(ENVELOPE_LEN, 0);
2480
2481 let mut f = self
2482 .file
2483 .into_inner()
2484 .map_err(|e| CmfError::Io(e.into_error()))?;
2485 f.seek(SeekFrom::Start(0))?;
2486 f.write_all(&env)?;
2487 f.write_all(&header_json)?;
2488 f.write_all(&dir_bytes)?;
2489 f.flush()?;
2490 f.sync_all()?;
2491 sync_parent_dir(&self.path)?;
2492
2493 tracing::info!(
2494 "Wrote CMF v2 (streamed): {} ({} tensors, {:.1} MB)",
2495 self.path.display(),
2496 self.entries.len(),
2497 (data_off + data_len + masks_len + vocab_len + index_len) as f64 / 1e6
2498 );
2499 Ok(())
2500 }
2501}
2502
2503pub fn mtp_sidecar_path(main: &Path) -> std::path::PathBuf {
2512 let name = main
2513 .file_name()
2514 .map(|n| n.to_string_lossy().into_owned())
2515 .unwrap_or_default();
2516 if name.ends_with(".mtp.cmf") {
2517 return main.to_path_buf();
2518 }
2519 let stem = name.strip_suffix(".cmf").unwrap_or(&name);
2520 main.with_file_name(format!("{stem}.mtp.cmf"))
2521}
2522
2523pub fn dspark_sidecar_path(main: &Path) -> std::path::PathBuf {
2535 let name = main
2536 .file_name()
2537 .map(|n| n.to_string_lossy().into_owned())
2538 .unwrap_or_default();
2539 if name.ends_with(".dspark.cmf") {
2540 return main.to_path_buf();
2541 }
2542 let stem = name.strip_suffix(".cmf").unwrap_or(&name);
2543 main.with_file_name(format!("{stem}.dspark.cmf"))
2544}
2545
2546pub fn build_sparse_index(catalog: &MaskCatalog, arch: &ModelArch) -> Vec<SparseIndexEntry> {
2551 let mut out = Vec::new();
2552 for m in &catalog.masks {
2553 for li in 0..arch.num_layers {
2554 if !m.layer_alive(li) {
2555 continue;
2556 }
2557 let mut groups = Vec::new();
2558 if let Some(bits) = m.ffn_masks.get(li) {
2559 let n_groups = arch.intermediate_size.div_ceil(32);
2560 for g in 0..n_groups {
2561 let lo = (g * 4).min(bits.len());
2567 let active = bits[lo..(g * 4 + 4).min(bits.len())]
2568 .iter()
2569 .any(|&b| b != 0);
2570 if active {
2571 groups.push(g as u16);
2572 }
2573 }
2574 }
2575 let mut heads = Vec::new();
2576 if let Some(bits) = m.head_masks.get(li) {
2577 for h in 0..arch.num_attention_heads {
2578 if bits
2579 .get(h / 8)
2580 .map(|b| b & (1 << (h % 8)) != 0)
2581 .unwrap_or(false)
2582 {
2583 heads.push(h as u8);
2584 }
2585 }
2586 }
2587 out.push(SparseIndexEntry {
2588 task_id: m.task_id,
2589 layer_idx: li,
2590 active_ffn_groups: groups,
2591 active_heads: heads,
2592 });
2593 }
2594 }
2595 out
2596}
2597
2598pub fn encode_sparse_index(entries: &[SparseIndexEntry]) -> Vec<u8> {
2601 let mut out = Vec::new();
2602 out.extend_from_slice(&(entries.len() as u32).to_le_bytes());
2603 out.extend_from_slice(&0u32.to_le_bytes());
2604 for e in entries {
2605 out.extend_from_slice(&e.task_id.to_le_bytes());
2606 out.extend_from_slice(&(e.layer_idx as u32).to_le_bytes());
2607 out.extend_from_slice(&(e.active_ffn_groups.len() as u32).to_le_bytes());
2608 out.extend_from_slice(&(e.active_heads.len() as u32).to_le_bytes());
2609 for g in &e.active_ffn_groups {
2610 out.extend_from_slice(&g.to_le_bytes());
2611 }
2612 out.extend_from_slice(&e.active_heads);
2613 while out.len() % 4 != 0 {
2614 out.push(0);
2615 }
2616 }
2617 out
2618}
2619
2620pub fn decode_sparse_index(bytes: &[u8]) -> Result<Vec<SparseIndexEntry>, CmfError> {
2621 let err = |msg: &str| CmfError::Parse(format!("sparse index: {msg}"));
2622 if bytes.len() < 8 {
2623 return Err(err("too short"));
2624 }
2625 let n = u32::from_le_bytes(bytes[0..4].try_into().unwrap()) as usize;
2626 let mut pos = 8usize;
2627 let mut out = Vec::with_capacity(n);
2628 for _ in 0..n {
2629 if pos + 16 > bytes.len() {
2630 return Err(err("entry header out of bounds"));
2631 }
2632 let task_id = u32::from_le_bytes(bytes[pos..pos + 4].try_into().unwrap());
2633 let layer_idx = u32::from_le_bytes(bytes[pos + 4..pos + 8].try_into().unwrap()) as usize;
2634 let n_groups = u32::from_le_bytes(bytes[pos + 8..pos + 12].try_into().unwrap()) as usize;
2635 let n_heads = u32::from_le_bytes(bytes[pos + 12..pos + 16].try_into().unwrap()) as usize;
2636 pos += 16;
2637 if pos + n_groups * 2 + n_heads > bytes.len() {
2638 return Err(err("entry data out of bounds"));
2639 }
2640 let mut groups = Vec::with_capacity(n_groups);
2641 for g in 0..n_groups {
2642 groups.push(u16::from_le_bytes(
2643 bytes[pos + g * 2..pos + g * 2 + 2].try_into().unwrap(),
2644 ));
2645 }
2646 pos += n_groups * 2;
2647 let heads = bytes[pos..pos + n_heads].to_vec();
2648 pos += n_heads;
2649 pos = pos.div_ceil(4) * 4;
2650 out.push(SparseIndexEntry {
2651 task_id,
2652 layer_idx,
2653 active_ffn_groups: groups,
2654 active_heads: heads,
2655 });
2656 }
2657 Ok(out)
2658}
2659
2660#[derive(Debug, thiserror::Error)]
2662pub enum CmfError {
2663 #[error("File not found: {0}")]
2664 FileNotFound(String),
2665 #[error("Invalid CMF magic bytes")]
2666 InvalidMagic,
2667 #[error("Unsupported CMF version: {0}")]
2668 UnsupportedVersion(u32),
2669 #[error("File requires unsupported features (bits {0:#x})")]
2670 UnsupportedFeature(u32),
2671 #[error("Unknown tensor dtype id: {0}")]
2672 UnknownDtype(u8),
2673 #[error("Tensor not found: {0}")]
2674 MissingTensor(String),
2675 #[error("Bounds error: {0}")]
2676 Bounds(String),
2677 #[error("IO error: {0}")]
2678 Io(#[from] io::Error),
2679 #[error("Parse error: {0}")]
2680 Parse(String),
2681}