Skip to main content

cortiq_core/
format.rs

1//! CMF v2 binary container — envelope, tensor directory, mmap access.
2//!
3//! See `docs/CMF_V2_SPEC.md`. Layout summary:
4//!
5//! ```text
6//! [0x00]  magic "CMF\x01" | version u32 = 2 | flags u32 | required_features u32
7//! [0x10]  header_off/len | dir_off/len | data_off/len   (u64 LE each)
8//! [0x40]  masks_off/len  | vocab_off/len | index_off/len
9//! [0x70]  header_hash u64 | dir_hash u64   (hash64; 0 = absent, §8.1)
10//! [0x80]  header JSON → tensor directory → weight blob (4096-aligned,
11//!         tensors 64-aligned) → masks → vocab → sparse index
12//! ```
13//!
14//! That order is what a fresh write produces; readers address every
15//! section ONLY through the envelope. A tail append (§9.3,
16//! [`CmfModel::append_skill`]) extends the data section over the old
17//! masks/vocab/index and republishes header + directory after it.
18//!
19//! The tensor directory is the ONLY source of truth for the weight blob
20//! layout — there is no computable layout, by design (v1 bug class #1).
21//! Every validation failure is a hard error: no silent fallbacks.
22
23use 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;
44/// Weight blob is page-aligned for mmap.
45pub const DATA_ALIGNMENT: u64 = 4096;
46/// Every tensor inside the blob is 64-byte aligned (SIMD / cache line).
47pub const TENSOR_ALIGNMENT: u64 = 64;
48/// Tensors at least this large are additionally page-aligned to
49/// [`LARGE_TENSOR_ALIGN`], so a cold skill / MoE-expert / mask weight sits on
50/// its own page(s): the "unused weights cost 0 RSS" guarantee then holds at
51/// page granularity (a lazily-paged tensor pulls exactly its own bytes, not a
52/// neighbour's), and per-layer `madvise(WILLNEED)` covers clean ranges. Small
53/// tensors (norms, biases, 1-D f16) keep the 64-byte SIMD alignment so the
54/// padding stays negligible.
55pub const LARGE_TENSOR_MIN: u64 = 16 * 1024;
56/// Page alignment applied to large tensors. 4096 is the common page size
57/// (x86, most ARM/Android); it is a multiple of [`TENSOR_ALIGNMENT`], so
58/// existing readers — which only require `off % 64 == 0` — accept these files
59/// unchanged. Purely a writer-side, backward-compatible layout choice.
60pub const LARGE_TENSOR_ALIGN: u64 = 4096;
61/// One directory record is 56 bytes (see `.vmfc` v2).
62pub const DIR_RECORD_LEN: usize = 56;
63pub const DIR_MAX_NDIM: usize = 6;
64
65/// `required_features` bits. A reader MUST refuse a file with any bit
66/// it does not support.
67pub 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    /// FFN mask rows are stored PER VIRTUAL LAYER (physical × loops) so
74    /// a Looped Transformer can mask a neuron in one pass and keep it in
75    /// the other. A reader without this bit would slice the mask area by
76    /// the physical count and misread every row after the first pass —
77    /// silently — so the bit makes it refuse instead.
78    pub const LOOP_MASKS: u32 = 1 << 5;
79    /// The file is a STANDALONE SKILL — a partial tensor set cut against a
80    /// specific base (`SkillRecord.base_dir_hash`), not a runnable model.
81    /// Readers that predate the bit refuse the file loudly instead of
82    /// running half a network; runtimes that know it refuse to RUN it and
83    /// say `cortiq skill apply` instead.
84    pub const SKILL_FILE: u32 = 1 << 6;
85    /// Signed FWHT Prism/Bonsai matrices and activation-boundary contract.
86    pub const PRISM_HADAMARD: u32 = 1 << 7;
87    /// Explicit q2tp affine correction (`(c - 1.0) * s`) for the listed
88    /// Prism matrices.  This is deliberately separate from the transform bit
89    /// so readers cannot infer the correction from arch_name alone.
90    pub const PRISM_AFFINE: u32 = 1 << 8;
91    /// Some skill record is v2 (`kind` set): every v2 record is validated
92    /// against its genome binding and the directory; an unknown or
93    /// reserved `kind` is refused. Derived from content.
94    pub const SKILLS_V2: u32 = 1 << 9;
95    /// The file declares a router policy v2 (`header.router`): only that
96    /// `backbone_gated` policy may auto-route — a reader that would fall
97    /// back to argmin-over-skills must refuse. Derived from content.
98    pub const ROUTER_V2: u32 = 1 << 10;
99    /// Non-generative decision profile (`cortiq-decision`): an encoder,
100    /// a hashing contract and resonance skills, described by the
101    /// `decision.manifest` tensor. It is executed by the decision runtime
102    /// (`cortiq decide`, `cortiq serve`), never by the language-model
103    /// pipeline. Derived by every writer from the arch name prefix
104    /// [`super::DECISION_ARCH_PREFIX`]; a reader refuses a file whose bit
105    /// and prefix disagree.
106    pub const DECISION: u32 = 1 << 11;
107    /// The file carries a natively BOUNDED attention operator
108    /// (`LayerType::BoundedAttention` + `arch.anchor_core`): every layer's
109    /// state is a fixed-size record and the reader MUST execute the anchor
110    /// as ring + trained sinks — never as a masked full-attention layer over
111    /// a growing KV. Readers that predate the bit refuse the file loudly;
112    /// readers that know it must not let `CMF_O1_*` / `--o1` override the
113    /// operator the file declares.
114    ///
115    /// Numbered `1 << 12` since 0.8.1. Unreleased embryo-o1 builds wrote
116    /// it as `1 << 7`, which every released reader knows as
117    /// [`PRISM_HADAMARD`]; such a file is refused (bit ⇔ metadata) and must
118    /// be re-exported.
119    pub const BOUNDED_STATE: u32 = 1 << 12;
120    /// The file carries a FROZEN GENOME (`header.genome`): the reader
121    /// recomputes `trunk_hash` from the directory, arch, vocab and
122    /// tokenizer bundle and refuses the file on mismatch — the trunk cannot
123    /// change without a new genome. Derived from content by every writer.
124    ///
125    /// Numbered `1 << 13` since 0.8.1 (unreleased embryo-o1 builds: `1 << 8`,
126    /// which released readers know as [`PRISM_AFFINE`]).
127    pub const GENOME: u32 = 1 << 13;
128    /// Bit 14 is reserved for `ORGANISM_V1` (docs/CMF_ORGANISM_V1_SPEC_RU.md).
129    ///
130    /// The file is a BLOCK-DIFFUSION language model (`arch.block_diffusion`,
131    /// Clover): it generates by denoising blocks of mask tokens, never one
132    /// token at a time. A reader that predates the bit would load the
133    /// Qwen3.6-MoE trunk and decode it autoregressively — fluent-looking
134    /// garbage — so it must refuse instead. Derived from content.
135    pub const BLOCK_DIFFUSION: u32 = 1 << 15;
136
137    /// Features this reader implements today.
138    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
153/// Arch-name prefix of every decision profile (`cortiq-decision-ph-v1`,
154/// `cortiq-decision-overlay-v1`, …). The [`features::DECISION`] bit and
155/// this prefix travel together: writers derive the bit from the name,
156/// readers refuse a file where they disagree.
157pub const DECISION_ARCH_PREFIX: &str = "cortiq-decision-";
158
159/// Is `arch_name` a decision profile (see [`DECISION_ARCH_PREFIX`])?
160pub fn is_decision_profile(arch_name: &str) -> bool {
161    arch_name.starts_with(DECISION_ARCH_PREFIX)
162}
163
164/// The Prism bits a header demands: `PRISM_HADAMARD` with the
165/// `prism_hadamard` record, `PRISM_AFFINE` with its `affine` descriptor.
166fn 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
177/// Extra words for a Prism bit/metadata mismatch on a file that carries an
178/// Embryo record: an unreleased embryo-o1 build wrote `BOUNDED_STATE` and
179/// `GENOME` on bits 7/8, which are the Prism bits of every released reader.
180fn 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
193/// The bits a pre-0.8.1 embryo-o1 build wrote for `BOUNDED_STATE` and
194/// `GENOME` (now [`features::BOUNDED_STATE`] / [`features::GENOME`]). They
195/// collide with the released `PRISM_HADAMARD` / `PRISM_AFFINE`.
196pub 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
202/// The envelope `required_features` and the raw header JSON of a CMF
203/// file, read with no feature gate and no validation — for tooling that
204/// must recognise a file every reader refuses (overwrite guards, the
205/// legacy-bit migration). `Ok(None)`: not a CMF file (too short or wrong
206/// magic).
207pub 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
237/// Does a raw header carry a frozen genome, a lineage journal or appended
238/// data segments? Keyed on CONTENT, not on the envelope bit: a pre-0.8.1
239/// embryo-o1 file carries `GENOME` on bit 8, and a file with appended
240/// segments loses them on any rewrite.
241pub 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/// JSON header — architecture and provenance (human-readable part;
289/// machine-critical data lives in binary sections).
290#[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    /// Informational default; per-tensor truth is in the directory.
297    pub quant_type: QuantType,
298    #[serde(default, skip_serializing_if = "Option::is_none")]
299    pub provenance: Option<serde_json::Value>,
300    /// Chat/eos bundle (spec §6.1): the file — not the binary — defines
301    /// chat behavior. Additive: absent in older files.
302    #[serde(default, skip_serializing_if = "Option::is_none")]
303    pub tokenizer_config: Option<TokenizerBundle>,
304    /// Section-level integrity (spec §8.1): hex hash64 of the raw bytes
305    /// of the optional sections. header/dir hashes live in the envelope
306    /// reserved bytes — JSON cannot protect the JSON that carries it.
307    #[serde(default, skip_serializing_if = "Option::is_none")]
308    pub section_hashes: Option<SectionHashes>,
309    /// Per-skill records (spec §9): replacement tensors live in the
310    /// directory as `skill.{id}.{name}`; this registry carries the
311    /// selection descriptor and the honest quality contract.
312    #[serde(default, skip_serializing_if = "Vec::is_empty")]
313    pub skills: Vec<SkillRecord>,
314    /// Sharding (spec §10): this file is shard `no` of `count`; every
315    /// shard is a standalone valid .cmf carrying a tensor subset.
316    /// Naming convention: `…-{no:05}-of-{count:05}.cmf`.
317    #[serde(default, skip_serializing_if = "Option::is_none")]
318    pub shard: Option<ShardInfo>,
319    /// Measured confidence calibration (B1): a temperature fit on held-out
320    /// so the displayed softmax confidence is a true property of the
321    /// model (softmax(logits/T)), not a raw estimate. Additive; absent =
322    /// use raw (T=1). Written by `set_calibration.py` after `cortiq
323    /// calibrate` measures the reliability/ECE.
324    #[serde(default, skip_serializing_if = "Option::is_none")]
325    pub calibration: Option<Calibration>,
326    /// Skill-router calibration (spec §9 routing; the cortiq-router recipe):
327    /// temperature of the softmax over −error and the novelty threshold θ,
328    /// fitted on the skills' held-out φ samples carried in their descriptors.
329    /// Additive; absent = raw recon-argmin with a fixed E threshold.
330    #[serde(default, skip_serializing_if = "Option::is_none")]
331    pub routing: Option<RoutingCalibration>,
332    /// Frozen genome (bit `GENOME`, spec §9.2): identity + trunk hash of
333    /// the backbone every v2 skill binds to.
334    #[serde(default, skip_serializing_if = "Option::is_none")]
335    pub genome: Option<GenomeInfo>,
336    /// Append-only journal of the genome's life (spec §9.2).
337    #[serde(default, skip_serializing_if = "Vec::is_empty")]
338    pub lineage: Vec<LineageEvent>,
339    /// Router policy v2 (bit `ROUTER_V2`, spec §9.4): per-request
340    /// backbone-gated routing with the backbone as the default.
341    #[serde(default, skip_serializing_if = "Option::is_none")]
342    pub router: Option<RouterPolicy>,
343    /// Data-range ownership after tail appends (spec §9.3). Layout
344    /// metadata: a full rewrite clears it.
345    #[serde(default, skip_serializing_if = "Vec::is_empty")]
346    pub segments: Vec<Segment>,
347}
348
349/// Router calibration (see `SelectionDescriptor`): the recipe of the
350/// cortiq-router service. Confidence is `softmax(−err/T)`; novelty is
351/// `0.5·σ(z_top) + 0.25·1/(1+8·margin) + 0.25·(1−confidence)`; an input is
352/// novel iff its novelty exceeds θ, the `1−fpr` quantile of the in-scope
353/// held-out novelty scores.
354#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct RoutingCalibration {
356    pub temperature: f32,
357    pub novelty_theta: f32,
358    /// held-out samples the calibration used
359    pub samples: usize,
360    /// target in-scope false-positive rate of the novelty flag
361    pub target_fpr: f32,
362}
363
364/// Confidence-calibration record (spec §6.2). `temperature` scales the
365/// logits before softmax when reporting confidence; `ece_before`/`after`
366/// are the measured Expected Calibration Error (honest provenance).
367#[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/// Shard coordinates (1-based, gguf-split style).
377#[derive(Debug, Clone, Serialize, Deserialize)]
378pub struct ShardInfo {
379    pub no: usize,
380    pub count: usize,
381}
382
383/// Recon-argmin routing parameters (spec §9; P1 signal-consistency):
384/// E = ‖(φ−mean) − B·Bᵀ(φ−mean)‖² / ‖φ−mean‖²; pick argmin over skills.
385#[derive(Debug, Clone, Serialize, Deserialize)]
386pub struct SelectionDescriptor {
387    /// "mse" (normalized reconstruction error over raw φ) or "mse_unit"
388    /// (φ unit-normalized first — the only metric router v2 accepts).
389    pub metric: String,
390    /// Backbone layer whose mean-pooled hidden is φ(x).
391    pub phi_layer: usize,
392    /// Subspace mean, f16 LE base64, len = hidden.
393    pub mean: String,
394    /// Orthonormal basis rows, f16 LE base64, len = rank·hidden.
395    pub basis: String,
396    pub rank: usize,
397    /// Training reconstruction-error statistics (mean, std) — the z-score
398    /// "energy" of the novelty ensemble. Additive (cortiq-router recipe).
399    #[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    /// Held-out in-scope φ samples, f16 LE base64, len = holdout_n·hidden —
404    /// what the file-level temperature/θ calibration is fitted on, so a
405    /// container recalibrates itself after every appended skill.
406    #[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/// One skill of the swarm (spec §9; Patent 15 per-skill record).
413#[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    /// Layers this skill specializes (a proper subset).
419    #[serde(default)]
420    pub layers: Vec<usize>,
421    /// Selection descriptor for recon-argmin routing (208c, P1):
422    /// per-skill affine subspace over φ(x) = mean-pooled hidden state.
423    #[serde(default, skip_serializing_if = "Option::is_none")]
424    pub selection: Option<SelectionDescriptor>,
425    /// Optional input-mask task name (208b), applied with the skill.
426    #[serde(default, skip_serializing_if = "Option::is_none")]
427    pub input_mask_task: Option<String>,
428    /// Measured quality (claim 16): overlaid vs backbone, held-out.
429    #[serde(default, skip_serializing_if = "Option::is_none")]
430    pub quality: Option<serde_json::Value>,
431
432    // ── Standalone-skill-file keys (features::SKILL_FILE) ──
433    /// hex hash64 of the BASE model's tensor directory this skill was cut
434    /// against. `skill apply` refuses a base whose directory hash differs:
435    /// a skill is a delta against exact bytes, not an architecture.
436    #[serde(default, skip_serializing_if = "Option::is_none")]
437    pub base_dir_hash: Option<String>,
438    /// The base's architecture name — the cheap human-readable identity
439    /// check next to the exact one above.
440    #[serde(default, skip_serializing_if = "Option::is_none")]
441    pub base_arch: Option<String>,
442    /// Mask-catalog task this skill activates once applied.
443    #[serde(default, skip_serializing_if = "Option::is_none")]
444    pub task: Option<String>,
445    /// Free-form provenance: corpus, steps, recipe, who baked it.
446    #[serde(default, skip_serializing_if = "Option::is_none")]
447    pub provenance: Option<serde_json::Value>,
448
449    // ── v2 record keys (features::SKILLS_V2, spec §9.5) ──
450    /// Record kind: "ffn_replace" and "expert_append" (implemented).
451    /// Reserved and refused until implemented: "anchor_sinks", "mask_only",
452    /// "fcd_vector". `None` = a v1 record (unchanged semantics).
453    #[serde(default, skip_serializing_if = "Option::is_none")]
454    pub kind: Option<String>,
455    /// `expert_append` parameters (spec §9.5.1): `count` new experts in
456    /// each of `layers`, their descriptor `rank` and the `shell_quantile`
457    /// the trainer used. Required for that kind, refused on any other.
458    #[serde(default, skip_serializing_if = "Option::is_none")]
459    pub experts: Option<ExpertAppend>,
460    /// `lookup` parameters (spec §9.5.2): the explicit key → card table
461    /// under `skill.{id}.lookup.*` — entries, keys, key normalisation,
462    /// languages, fields. Required for that kind, refused on any other.
463    #[serde(default, skip_serializing_if = "Option::is_none")]
464    pub lookup: Option<LookupInfo>,
465    /// Base tensors this record replaces, with the hash of the base entry
466    /// it was trained against. Exactly one per `skill.{id}.X` tensor.
467    #[serde(default, skip_serializing_if = "Vec::is_empty")]
468    pub overrides: Vec<SkillOverride>,
469    /// The genome (id, generation, f32-master trunk hash) the record is
470    /// bound to.
471    #[serde(default, skip_serializing_if = "Option::is_none")]
472    pub bound: Option<SkillBound>,
473    /// What switching this skill does to the sequence state (computed by
474    /// the writer from `arch.layer_types`).
475    #[serde(default, skip_serializing_if = "Option::is_none")]
476    pub state_effect: Option<StateEffect>,
477    /// "quarantine" | "active" | "stale_regate" | "retired".
478    #[serde(default, skip_serializing_if = "Option::is_none")]
479    pub status: Option<String>,
480    /// Measured gate; auto-routing requires `gate.status == "measured"`
481    /// and `status == "active"`.
482    #[serde(default, skip_serializing_if = "Option::is_none")]
483    pub gate: Option<serde_json::Value>,
484    /// Prompt template the skill was trained under (e.g. "cmf-im-v1").
485    #[serde(default, skip_serializing_if = "Option::is_none")]
486    pub prompt_contract: Option<String>,
487    /// `{trigger, dataset_sha256[], recipe, …}`.
488    #[serde(default, skip_serializing_if = "Option::is_none")]
489    pub origin: Option<serde_json::Value>,
490}
491
492/// Hex-encoded hash64 per optional section (u64 as JSON number would
493/// lose precision past 2^53).
494#[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/// Chat template + generation stop tokens carried by the container.
505#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
506pub struct TokenizerBundle {
507    /// Jinja chat template (chat_template.jinja / tokenizer_config.json)
508    #[serde(default, skip_serializing_if = "Option::is_none")]
509    pub chat_template: Option<String>,
510    /// All ids that terminate generation (generation_config + im_end)
511    #[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/// One tensor directory entry.
524#[derive(Debug, Clone, PartialEq, Eq)]
525pub struct TensorEntry {
526    pub name: String,
527    pub dtype: TensorDtype,
528    pub shape: Vec<usize>,
529    /// Offset relative to the OWNING shard's `data_off`, multiple of 64.
530    pub off: u64,
531    pub nbytes: u64,
532    /// Runtime-only: which shard's mmap holds the bytes (0 for the
533    /// single-file case; not part of the 56-byte record).
534    pub shard: usize,
535    /// `hash64` of the tensor bytes.
536    pub hash: u64,
537}
538
539impl TensorEntry {
540    pub fn n_elems(&self) -> usize {
541        self.shape.iter().product()
542    }
543}
544
545/// Input for the Rust writer: one tensor with its encoded bytes.
546#[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
554/// `TensorSpec` with a borrowed payload — see [`CmfModel::write_ref`].
555pub struct TensorSpecRef<'a> {
556    pub name: String,
557    pub dtype: TensorDtype,
558    pub shape: Vec<usize>,
559    pub data: &'a [u8],
560}
561
562/// Sparse index entry — precomputed per-task per-layer active group IDs.
563#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
564pub struct SparseIndexEntry {
565    pub task_id: u32,
566    pub layer_idx: usize,
567    /// Active quant-group indices for FFN (sorted, group = 32 neurons).
568    pub active_ffn_groups: Vec<u16>,
569    /// Active head indices for attention (sorted).
570    pub active_heads: Vec<u8>,
571}
572
573/// Section ranges parsed from the fixed envelope.
574#[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    /// hash64 of the header JSON bytes (reserved [0x70]); 0 = absent.
584    header_hash: u64,
585    /// hash64 of the tensor-directory bytes (reserved [0x78]); 0 = absent.
586    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
603/// A loaded CMF model: metadata owned, weights zero-copy via mmap.
604pub struct CmfModel {
605    pub path: PathBuf,
606    /// Identifies THIS open of the file, monotonically. GPU backends cache
607    /// device weights by (model, tensor); keying that on the mapping's
608    /// address made a reloaded model inherit the previous one's buffers
609    /// whenever the new mmap landed where the old had been — silent wrong
610    /// weights in any process that unloads and reloads, which a server does.
611    uid: u64,
612    pub header: CmfHeader,
613    pub required_features: u32,
614    pub tensors: Vec<TensorEntry>,
615    /// name-hash → tensor index. Keying on the hash (not the name) avoids
616    /// cloning every tensor name into the map at `open()` — that halves the
617    /// open-time allocations and the map's footprint, which matters for large
618    /// MoE / skills files with tens of thousands of tensors. A genuine 64-bit
619    /// hash collision between two *distinct* names — astronomically unlikely —
620    /// lands in `name_overflow`, so lookups stay exact.
621    by_name: HashMap<u64, u32>,
622    name_overflow: Vec<u32>,
623    pub masks: MaskCatalog,
624    pub sparse_index: Vec<SparseIndexEntry>,
625    /// Embedded tokenizer.json bytes, if present.
626    pub vocab: Option<Vec<u8>>,
627    backing: Backing,
628    data_off: u64,
629    envelope: Envelope,
630    /// Shards 2..N (spec §10): (backing, data_off) per extra file;
631    /// `TensorEntry.shard` 0 = this file, i>0 = extra_shards[i-1].
632    extra_shards: Vec<(Backing, u64)>,
633    /// Trunk hash verified at open (GENOME files), from the raw header.
634    trunk: Option<u64>,
635    /// hash64 of the raw mask / sparse-index sections (execution-relevant
636    /// parts of the trunk outside the directory; see
637    /// [`crate::knowledge::trunk_hash_exec`]).
638    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
652/// Hands out a fresh id per open. Wraps only after 2^64 opens.
653static MODEL_UID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
654
655impl CmfModel {
656    /// A key that is unique to this open of the file and never recycled.
657    /// Backends that cache anything derived from the weights must key on
658    /// this, not on the mapping's address, which the allocator reuses.
659    pub fn uid(&self) -> u64 {
660        self.uid
661    }
662
663    /// Open and strictly validate a CMF v2 file. Any inconsistency is an
664    /// error — this function never substitutes defaults.
665    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        // `CMF_MMAP_POPULATE=1`: fault the whole file in at open (MAP_POPULATE)
674        // — for hosts whose RAM holds the file, so a device path that
675        // re-reads cold experts from the mapping every token never pays a
676        // page fault inside the token. Costs the read time once, up front.
677        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                // Decode touches every weight page each token, so tell
685                // the kernel up front: WillNeed front-loads readahead
686                // (first-token page-fault storm becomes streaming I/O —
687                // this is TTFT on phones, where the file is a large
688                // share of RAM). Advisory only: a memory-pressured
689                // kernel is free to ignore it. CMF_MMAP_ADVISE=0 turns
690                // it off; CMF_MLOCK=1 additionally tries to pin the
691                // mapping (needs RLIMIT_MEMLOCK headroom — refusal is
692                // logged, not fatal).
693                #[cfg(unix)]
694                {
695                    // …unless these pages are headed for a device that will
696                    // then drop them. Reading the file ahead only to throw it
697                    // out behind the uploader has the kernel fetching the same
698                    // bytes twice. The two advices contradict each other, so
699                    // the one that asked for eviction wins.
700                    //
701                    // The conditions are the uploader's, approximated from what
702                    // is knowable at open time (the GPU is not up yet): Linux,
703                    // because `evict_ranges` is a no-op elsewhere; a backend
704                    // actually requested, because a CPU run wants the readahead;
705                    // and eviction not turned off. On UMA the mapping IS the
706                    // working copy and nothing is evicted — that is why this
707                    // cannot key on `CMF_GPU` alone.
708                    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                    // WILLNEED is a whole-file readahead and macOS runs it
714                    // SYNCHRONOUSLY — on a 12.9 GB MoE file it held open()
715                    // for ~6.5 s while polluting RAM with experts that a
716                    // routed decode never touches. Small dense files keep
717                    // the readahead (it pays there); big files rely on
718                    // demand paging. CMF_MMAP_ADVISE=1 forces the old
719                    // blanket advise, =0 disables it entirely.
720                    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        // Header JSON
754        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        // A decision profile and its feature bit travel together: the bit
757        // keeps generative readers away from the file, the prefix is what
758        // the decision runtime keys on. Either one alone is a file that
759        // some reader would misread.
760        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        // The bounded-anchor operator and its feature bit travel together:
802        // a file that declares the operator without the bit would run as
803        // full-KV on a reader that ignores unknown header fields, and a bit
804        // without the record has nothing to execute.
805        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        // Tensor directory
830        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            // Payload-dependent lengths (vbit): exact check against the
866            // width header, bounds-before-slice (roadmap §4.9).
867            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        // Duplicate names would silently shadow each other in the
874        // HashMap (directory scan and by_name would disagree) — refuse
875        // the file instead (roadmap §4.9).
876        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), // hash collision of distinct names
888                None => {
889                    by_name.insert(h, i as u32);
890                }
891            }
892        }
893
894        // Masks
895        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        // Vocab (tokenizer.json)
903        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        // Sparse index
910        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        // Knowledge contract (§9.2–§9.5): GENOME / SKILLS_V2 / ROUTER_V2
917        // bits ⇔ content both ways, the trunk hash recomputed from the
918        // directory and the header JSON AS WRITTEN (an arch field this
919        // reader's struct does not know is still covered), every v2 skill
920        // record and the router policy. A file without the bits and without
921        // v2 content passes untouched.
922        // The mask catalog and sparse index are part of how the trunk
923        // executes (run/serve apply the catalog's fallback mask to every
924        // request): a genome's hash covers their raw bytes.
925        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        // A lookup policy this reader does not know (a newer writer's) is
958        // read as router_and_key by the runtime — the file stays readable.
959        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        // expert_append descriptor values (bias 0, finite shell): a few
968        // 4-byte reads from the mapped payloads.
969        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        // lookup tables (sorted unique key hashes, entries in range,
976        // monotone offsets, UTF-8 blob, object-shaped slots): one pass
977        // over the mapped payloads of each lookup record — the slots are
978        // NOT parsed here (the runtime reads a card lazily; the writers
979        // parsed every slot before committing it).
980        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    /// Open a sharded model (spec §10): pass shard 1; siblings found by
1017    /// the `-{no:05}-of-{count:05}.cmf` convention. Directories merge;
1018    /// masks/vocab/index/skills come from shard 1.
1019    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); // not sharded — plain open
1024        };
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); // reserved
1107        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    // ───────────────────────── access ─────────────────────────
1228
1229    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    /// Directory index of a tensor by name (same resolution as
1238    /// [`Self::tensor`] — engines must not re-scan the directory). O(1) via the
1239    /// name-hash index; the name is verified against the entry so a hash
1240    /// collision can never return the wrong tensor, and the rare distinct-name
1241    /// collision falls back to the tiny overflow list.
1242    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    /// Tensor-source indirection (spec §9, Patent 15 fig3/302): the
1257    /// skill's replacement is read IN PLACE OF the backbone tensor —
1258    /// either/or, never combined. None skill → backbone directly.
1259    ///
1260    /// For a v2 skill record, a name listed in its `overrides` whose
1261    /// `skill.{id}.X` tensor is missing resolves to `None` (logged) — never
1262    /// silently to the backbone tensor. [`Self::try_resolve_tensor`] returns
1263    /// the reason.
1264    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    /// [`Self::resolve_tensor`] with the no-silent-fallback case as an
1275    /// error: `Ok(None)` = no such tensor at all, `Err` = a v2 skill
1276    /// declares an override for `name` but carries no replacement.
1277    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    /// hash64 of the trunk as this file encodes it ([`trunk_hash`]): every
1303    /// directory entry outside `skill.`/`route.`, the arch, the vocab, the
1304    /// tokenizer bundle and — when present — the mask catalog and sparse
1305    /// index ([`crate::knowledge::trunk_hash_exec`]). Equals
1306    /// `header.genome.trunk_hash` for any GENOME file that opened.
1307    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    /// hash64 of this file's raw mask / sparse-index sections (computed at
1319    /// open for GENOME files, on demand otherwise).
1320    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    /// The per-skill delta index view (claim 2): directory entries of
1332    /// one skill — exactly the byte ranges lazy loading pages in.
1333    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    /// Zero-copy bytes of a tensor from the mmap'd data section.
1341    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    /// All bytes of the primary mapping (GPU path: no-copy Metal buffer
1349    /// over the same mmap — unified memory, zero copying).
1350    /// hash64 of this file's tensor directory — the identity a standalone
1351    /// skill binds to (`SkillRecord.base_dir_hash`).
1352    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    /// Absolute offset of the tensor within the primary mapping
1361    /// (None for tensors from sibling shards).
1362    /// Best-effort page-cache release for every tensor whose name passes
1363    /// `pred` (unix, primary shard only): the merged ranges are madvised
1364    /// DONTNEED so a one-shot stage's weights — a prompt encoder that
1365    /// runs once per generation — stop competing for RAM with the
1366    /// stages after it. A 25.7 GB fl2va file on a 24 GB Mac spent 40
1367    /// minutes paging the SSD during denoise for exactly this reason.
1368    /// Re-reading a dropped range later just refaults from disk.
1369    /// Returns the bytes released.
1370    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                // Align INWARD: a page shared with a kept tensor stays.
1396                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    /// The CPU is done with these byte ranges of the primary mapping
1432    /// (absolute offsets, as `entry_abs_offset` hands them out): drop them
1433    /// from the resident set and let the page cache release the file pages.
1434    /// Both calls are advisory and the mapping stays valid — a range that
1435    /// gets touched again re-faults from disk, so a caller can only cost
1436    /// time here, never correctness. Ranges are aligned OUTWARD to page
1437    /// boundaries; the neighbours those edges claw in re-fault the same way.
1438    /// Linux + mmap backing only; everywhere else a no-op.
1439    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            // One fd for the whole batch: fadvise targets the inode's page
1447            // cache, any fd on the same file will do.
1448            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                // SAFETY: read-only MAP_SHARED file mapping — DONTNEED here
1458                // only drops clean pages; the next access re-faults them.
1459                let _ = unsafe {
1460                    m.unchecked_advise_range(memmap2::UncheckedAdvice::DontNeed, start, alen)
1461                };
1462                if let Some(f) = &file {
1463                    // SAFETY: plain fd + numeric range; advisory by contract.
1464                    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    /// Tensors belonging to layer `i` (prefix `model.layers.{i}.`).
1480    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    /// Total parameter count estimated from matrix tensors (ndim ≥ 2).
1489    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    /// Recode selected tensors IN PLACE: each new payload must fit its old
1498    /// slot, the entry keeps its offset and the file keeps its length — the
1499    /// bytes between the new end and the old simply go dark (every reader
1500    /// walks the directory, nothing addresses the gap). This is what lets a
1501    /// published 100+ GB file change a tensor's layout on a disk too small
1502    /// to hold two copies of it. Patches are `(directory index, new dtype,
1503    /// new payload)`; entry hashes and the directory hash are recomputed so
1504    /// `verify` stays clean. Not atomic: a crash between the payload writes
1505    /// and the directory write leaves the old dtype over new bytes — verify
1506    /// (or re-fetch the source) after an interrupted run.
1507    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        // By content too: a pre-0.8.1 embryo-o1 file carries GENOME on bit 8.
1521        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    /// Recompute all tensor hashes; returns human-readable problems
1569    /// (empty = file intact).
1570    pub fn verify(&self) -> Vec<String> {
1571        let mut problems = Vec::new();
1572
1573        // Section-level integrity (spec §8.1). Zero/absent = legacy file.
1574        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    /// Approximate active weight bytes under a mask, from real tensor
1621    /// sizes in the directory (not from a formula).
1622    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    // ───────────────────────── writer ─────────────────────────
1647
1648    /// Write a CMF v2 file. Offsets, alignment, hashes and the sparse
1649    /// index are computed here — the caller supplies content only.
1650    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    /// `write` with BORROWED tensor payloads — repack tools slice the
1670    /// source file's mmap directly, so a 19 GB container rewrites without
1671    /// materializing its tensors in RAM (the OS streams pages through).
1672    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        // Directory + data layout.
1694        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        // Section hashes go INTO the header (so the envelope's header
1754        // hash transitively covers them), then the header is serialized.
1755        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        // A full rewrite lays every tensor out afresh: it is a compaction,
1765        // and the tail-append segment table no longer describes the file.
1766        header.segments.clear();
1767        // Genome: fill an empty trunk hash from this content, refuse a
1768        // stale one (the trunk changed without a new genome). The mask
1769        // catalog / sparse index are part of the trunk's execution.
1770        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        // The writer refuses what open() would refuse — before a byte hits
1775        // the disk.
1776        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        // entries[i] is tensors[i]: the payload of an entry by position.
1787        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        // Section offsets.
1799        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        // Envelope.
1810        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()); // flags
1814        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        // Reserved bytes carry header/dir integrity (spec §8.1).
1827        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        // Write out.
1832        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
1900/// Refuse `required` bits outside `supported` — the envelope gate every
1901/// reader applies before touching a section (`UnsupportedFeature`). Public
1902/// so tooling can ask what a reader with an older `SUPPORTED` would do.
1903pub 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
1911/// The `required_features` a header + directory demand — the ONE place
1912/// the writers (`write_ref`, the streaming `finish`, the tail append)
1913/// derive the bits from content, so no bit is ever set by hand and none is
1914/// silently lost on a rewrite.
1915pub 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    // A decision profile is not a language model: the bit makes every
1922    // generative reader refuse it (older ones by the unknown bit).
1923    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    // A skill record bound to a base directory makes this file a
1939    // standalone skill, and the bit keeps every reader honest about it.
1940    if header.skills.iter().any(|s| s.base_dir_hash.is_some()) {
1941        f |= features::SKILL_FILE;
1942    }
1943    // A declared bounded anchor is an operator every reader must execute
1944    // exactly; the bit makes readers that cannot refuse.
1945    if header.arch.anchor_core.is_some() {
1946        f |= features::BOUNDED_STATE;
1947    }
1948    // A block-diffusion model must never be decoded token by token.
1949    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
1959/// Persist a directory entry after a file it contains is created or updated.
1960///
1961/// A checkpoint mark is the boundary at which the converter may consume its
1962/// source shard.  Syncing the containing directory after the output payload
1963/// and manifest has been synced closes the small rename/create window where a
1964/// crash could otherwise leave durable bytes without a durable directory
1965/// entry.  Unix filesystems expose directory fsync; other platforms retain
1966/// the file-level ordering and use the no-op fallback below.
1967fn sync_parent_dir(path: &Path) -> Result<(), CmfError> {
1968    #[cfg(unix)]
1969    {
1970        // A bare relative name has the EMPTY parent (`Path::parent` of
1971        // "x.cmf" is ""), which `open` refuses: that is the current directory.
1972        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
1983// ─────────────────── one-pass streaming writer (§8.4) ───────────────────
1984
1985/// Writes a CMF file in a single pass, payloads first.
1986///
1987/// [`CmfModel::write_ref`] needs every payload addressable at once, so a
1988/// converter has to hold the whole encoded model — RAM, or a spill file it
1989/// then copies into the output. For a 300B-class MoE that is ~120 GB written
1990/// twice. This writer instead reserves a gap at the head of the file, appends
1991/// each payload the moment it is encoded, and patches the envelope, header and
1992/// directory into that gap at the end. The bytes are written once and peak
1993/// disk cost is the finished file.
1994///
1995/// The gap is the one thing that can go wrong: the directory is not sized
1996/// until the last tensor arrives. [`CmfStreamWriter::finish`] therefore
1997/// refuses loudly if the head does not fit rather than truncating it, and
1998/// [`CmfStreamWriter::head_reserve_for`] gives callers a safe estimate.
1999/// What a resumed writer recovers from its manifest: the tensors already on
2000/// disk and any milestones the producer noted.
2001pub 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    /// Absolute offset of the weight blob — also the size of the reserved gap.
2010    data_off: u64,
2011    /// Write cursor, relative to `data_off`.
2012    cursor: u64,
2013    entries: Vec<TensorEntry>,
2014    /// Append-only sidecar describing every payload already on disk. A Colab
2015    /// box can vanish mid-conversion; with this the finished payloads can be
2016    /// turned into a valid file instead of re-encoding for hours.
2017    manifest: Option<BufWriter<File>>,
2018}
2019
2020impl CmfStreamWriter {
2021    /// A gap that comfortably holds the head for `n_tensors` whose names run
2022    /// to `avg_name` bytes: the directory's fixed records, the name pool, the
2023    /// envelope, and a header JSON with room for arch metadata — then doubled,
2024    /// because being wrong here costs a whole re-run.
2025    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        // Tripled, on top of a megabyte of slack that is already ~20x a
2029        // small model's directory. The asymmetry is deliberate: an
2030        // over-estimate costs zeros at the head of the file, an
2031        // under-estimate costs the entire conversion that produced it.
2032        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        // Read + write: `finish` reads a few descriptor values back from
2039        // the payloads already on disk (expert_append records).
2040        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    /// `gap` bytes are reserved for envelope + header + directory. Existing
2061    /// files are truncated; callers that publish a new artifact must instead
2062    /// use [`Self::new_no_clobber`].
2063    pub fn new(path: impl AsRef<Path>, gap: u64) -> Result<Self, CmfError> {
2064        Self::create(path, gap, false)
2065    }
2066
2067    /// Create a new stream output without ever truncating an existing path.
2068    /// This is the safe primitive for private staging names: `create_new`
2069    /// closes the reserve/reopen race that exists when a writer only receives
2070    /// a path rather than an already-open file descriptor.
2071    pub fn new_no_clobber(path: impl AsRef<Path>, gap: u64) -> Result<Self, CmfError> {
2072        Self::create(path, gap, true)
2073    }
2074
2075    /// Append one tensor. The payload is consumed here, so the caller can drop
2076    /// it immediately — that is the entire point of this writer.
2077    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    /// Append one tensor while limiting each write syscall to `chunk_bytes`.
2088    ///
2089    /// The source is normally an mmap, so this does not change the caller's
2090    /// memory residency. It does make the bounded-copy contract explicit for
2091    /// very large auxiliary tensors (DeepSeek-V4.1's native Engram tables are
2092    /// hundreds of gigabytes): the writer never asks an I/O layer to stage the
2093    /// whole payload as one buffer. Hashing remains one streaming pass over the
2094    /// borrowed bytes, and the directory entry is identical to [`Self::push`].
2095    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    /// Append a payload whose hash was already established by an integrity
2107    /// check of the immutable source. This avoids re-reading every preserved
2108    /// mmap payload solely to compute the same directory hash during a large
2109    /// streaming rewrite. Callers must perform a final [`CmfModel::verify`]
2110    /// of the completed output before treating it as valid: that validation
2111    /// is what detects a source mutated while it was being copied.
2112    ///
2113    /// New or transformed payloads must use [`Self::push_bounded`] instead.
2114    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    /// Start recording a sidecar manifest at `path`. One JSON line per
2193    /// tensor, flushed as it goes, plus a first line pinning the gap size.
2194    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        // The first line establishes the resume geometry.  Make both its
2200        // contents and its directory entry durable before any source shard
2201        // can reach a later checkpoint.
2202        f.get_ref().sync_data()?;
2203        sync_parent_dir(&path)?;
2204        self.manifest = Some(f);
2205        Ok(self)
2206    }
2207
2208    /// Keep recording into an existing manifest — for a writer from
2209    /// [`CmfStreamWriter::resume`], whose earlier lines must survive.
2210    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        // A resumed writer is allowed to consume the next shard immediately;
2214        // preserve the same directory ordering guarantee as a fresh writer.
2215        file.sync_data()?;
2216        sync_parent_dir(&path)?;
2217        self.manifest = Some(BufWriter::new(file));
2218        Ok(self)
2219    }
2220
2221    /// Rebuild a writer over an output file whose payloads are already on
2222    /// disk, from the manifest that recorded them. The file is reopened for
2223    /// writing without truncation and the cursor is placed after the last
2224    /// recorded tensor, so `finish` can complete a conversion that died.
2225    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            // A truncated last line is expected if the process was killed
2250            // mid-write; it is dropped, not an error.
2251            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                // Everything up to here is durable; anything the manifest
2258                // records after the LAST mark belongs to a shard that was
2259                // interrupted and will be redone, so it must not be kept —
2260                // otherwise the redo appends those tensors a second time.
2261                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    /// Note a milestone in the manifest — a source shard fully consumed, say.
2320    /// Resume reads these back, which is what lets a restart skip work whose
2321    /// payloads are already in the file rather than only skipping tensors it
2322    /// happens to recognise by name.
2323    pub fn mark(&mut self, note: &str) -> Result<(), CmfError> {
2324        // The payloads must be on disk BEFORE the mark claims they are.
2325        // Without this the manifest runs ahead of a buffered writer, and a
2326        // kill in between leaves a record of bytes that were never written.
2327        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        // This is deliberately after both file syncs: the converter removes
2340        // a consumed source shard only after mark() returns.
2341        sync_parent_dir(&self.path)?;
2342        Ok(())
2343    }
2344
2345    pub fn tensor_count(&self) -> usize {
2346        self.entries.len()
2347    }
2348
2349    /// Bytes of weight blob written so far.
2350    pub fn data_len(&self) -> u64 {
2351        self.cursor
2352    }
2353
2354    /// Write the trailing sections, then patch the head into the reserved gap.
2355    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        // Same contract as the ordinary writer: the bounded anchor and its
2400        // bit travel together (open() refuses a file where they disagree),
2401        // and the knowledge bits are derived from content, never set by hand.
2402        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        // The payloads are already on disk: read the descriptor values back.
2422        {
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            // Lookup tables are small next to the weights: read them back.
2432            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
2503// ───────────────────── MTP sidecar naming ─────────────────────
2504
2505/// The multi-token-prediction sidecar that belongs to a main CMF file:
2506/// `<stem>.mtp.cmf` beside it (`mimo-v26-flash-q4tp.cmf` →
2507/// `mimo-v26-flash-q4tp.mtp.cmf`). A path that already names a sidecar is
2508/// returned unchanged. The converter writes the draft layers there so a
2509/// 164 GB main file never has to be rewritten to gain (or drop) them, and
2510/// the loader looks exactly there — one rule, shared by both.
2511pub 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
2523/// The Qwen3 DFlash/DSpark draft sidecar that belongs to a main CMF file:
2524/// `<stem>.dspark.cmf` beside it (`minicpm-v47-q4tp.cmf` →
2525/// `minicpm-v47-q4tp.dspark.cmf`).  DSpark is a separate draft architecture,
2526/// not an MTP head, so this deliberately never returns an `.mtp.cmf` path.
2527/// In particular, passing an MTP path produces a distinct
2528/// `…mtp.dspark.cmf` name rather than overwriting the MTP payload.
2529///
2530/// A path that already names a DSpark sidecar is returned unchanged.  The
2531/// runtime does not auto-load this file until it has a dedicated DFlash
2532/// executor; keeping the naming rule here makes converter output and that
2533/// future loader share one collision-free identity.
2534pub 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
2546// ───────────────────── sparse index (§7 of the spec) ─────────────────────
2547
2548/// Build the sparse index from mask bitfields: a 32-neuron FFN group is
2549/// active if it contains at least one active bit.
2550pub 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                    // Group g covers bits [g*32, g*32+32) = bytes [g*4, g*4+4).
2562                    // A per-layer FFN width may be SHORTER than the
2563                    // arch's (tube files size the arch to the widest
2564                    // layer), so the start needs clamping too — not just
2565                    // the end, or a narrow layer indexes past its row.
2566                    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
2598/// `[u32 n_entries][u32 reserved]` then per entry:
2599/// `[u32 task][u32 layer][u32 n_groups][u32 n_heads][u16×g][u8×h][pad→4]`.
2600pub 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/// Errors from CMF operations. Every failure mode is loud.
2661#[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}