File size: 31,379 Bytes
e9433fd 8f22b5e e9433fd 8f22b5e e9433fd 8f22b5e e9433fd 8f22b5e e9433fd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 | # CMF v2 — 格式规范
*语言:[English](SPEC.md) · [Русский](SPEC.ru.md) · **中文***
**Cortiq Model Format** — 一个文件即可承载稀疏、任务路由推理所需的一切:
量化权重、分词器、逐任务掩码、预计算的稀疏索引 —— 以及独一无二的、
共享同一骨干的**技能集群**(Patent 15)。
> 规范来源:本文档。参考实现:Rust 读取器/运行时
> (`crates/cortiq-core`、`crates/cortiq-engine`)、Python 写入器
> (`converter/`),以及一个无依赖的 Python 读取器
> (`python/cmf_reader.py`,仅需 numpy)。
三项要求,按优先级排序:
1. **正确。** 没有静默损坏模式:严格的魔数、版本、
`required_features`、每个区段的边界检查、每个张量的 64 位哈希。
文件要么有效,要么 open() 返回错误 —— 不存在
第三种状态。
2. **快速。** 权重区段按页对齐以便 mmap,每个张量
都按 64 字节对齐(零拷贝 SIMD),张量目录为二进制格式 ——
无需解析即可读取。冷(被掩掉的)权重不占用 RSS。
3. **紧凑。** 掩码按位打包(每个神经元 1 比特),权重采用
q4/q8/可变位宽,整个文件由一个 128 字节的
信封寻址。
和谐并非来自特性数量,而是来自**单一正典**:每个层级只有一种
布局(信封、目录、量化块、掩码),在各自领域重叠之处
(张量目录、量化布局、`hash64`)与经过验证的 `.vmfc` v2 格式逐字节
兼容。永远不存在同一事物的两种
定义。
物理基础(VMF):模型是一个真空凝聚态 𝒲;技能是它在临界密度之上的
规则核;任务掩码在不改变权重的情况下选取一个活跃
子集。格式承载了该物理的推论(双场 𝒲×θ 量化、Born 重要性、临界掩码
阈值)—— 但**仅限那些经测量确认的部分**。
---
## 1. 信封(固定 128 字节)
所有整数均为小端序。
```
[0x00 : 0x04] magic = b"CMF\x01" (4 bytes)
[0x04 : 0x08] version : u32 = 2
[0x08 : 0x0C] flags : u32 (reserved, 0)
[0x0C : 0x10] required_features : u32 (bitmask, §1.1)
[0x10 : 0x18] header_off : u64 (= 128)
[0x18 : 0x20] header_len : u64 — JSON header (§2)
[0x20 : 0x28] dir_off : u64 — tensor directory (§3)
[0x28 : 0x30] dir_len : u64
[0x30 : 0x38] data_off : u64 — weight blob; multiple of 4096 (§4)
[0x38 : 0x40] data_len : u64
[0x40 : 0x48] masks_off : u64 — masks section (§5); 0 = absent
[0x48 : 0x50] masks_len : u64
[0x50 : 0x58] vocab_off : u64 — tokenizer (§6); 0 = absent
[0x58 : 0x60] vocab_len : u64
[0x60 : 0x68] index_off : u64 — sparse index (§7); 0 = absent
[0x68 : 0x70] index_len : u64
[0x70 : 0x80] reserved : 16 bytes (§8.1: header/dir hashes)
```
磁盘上的区段顺序:信封 → 头部 JSON → 目录 → **权重
blob(对齐到 4096)** → 掩码 → 词表 → 稀疏索引。读取器必须
仅通过信封来寻址区段,绝不假设其顺序。
### 1.1 `required_features`
读取器不认识的某一位 → `UnsupportedFeature` 错误(快速失败;
不做"尽力读取")。
| bit | name | 含义 |
|-----|----------------|---------|
| 0 | `TENSOR_DIR` | 二进制张量目录(v2 中始终置位) |
| 1 | `BINARY_MASKS` | 存在掩码区段(§5) |
| 2 | `QUANT_2F` | 目录包含 `q8_2f`/`vbit` 张量(双场 𝒲×θ 量化) |
| 3 | `DELTA_MASKS` | 保留:来自父级的 XOR 掩码增量 |
| 4 | `HOT_PACKS` | 保留:物化的稠密切片 |
| 5 | `LOOP_MASKS` | 掩码行按访问(VISIT)存储(物理层 × 循环数,按遍历优先)——循环 Transformer 的每一遍携带独立掩码(§5.1) |
| 6 | `SKILL_FILE` | 该文件是独立技能文件:针对特定基座裁出的部分张量集,由 `SkillRecord.base_dir_hash` 绑定(§9.1)。不可运行——用 `cortiq skill apply` 附加 |
未知的**头部 JSON** 字段被忽略(增量式演进);
破坏性变更只通过特性位或 `version` 递增来引入。
### 1.2 校验规则(规范性)
在以下情况下,读取器必须返回错误(而非默认值、也非警告):
- magic ≠ `CMF\x01` → `InvalidMagic`;
- `version` ≠ 2 → `UnsupportedVersion`(v1 已死:不存在真实的 v1
文件,不会启动任何支持计划);
- 置位了未知的 `required_features` 位 → `UnsupportedFeature`;
- 任何区段越过 EOF、`data_off` 不是 4096 的倍数、某张量的
`off + nbytes` 超过 `data_len` → `Bounds`;
- 张量名非 UTF-8、dtype 未知、`ndim > 6` → `Parse`。
张量哈希校验按需进行(`cortiq verify`、加载器标志),
并非每次打开都做:mmap 页面按需惰性读取。
## 2. 头部 JSON
UTF-8 JSON,不对齐。机器关键数据存放于二进制区段;
JSON 承载架构与来源信息 —— 供人阅读的部分。
```jsonc
{
"format": "cmf",
"version": 2,
"arch": {
"arch_name": "qwen3.5",
"hidden_size": 5120, "intermediate_size": 17408,
"num_layers": 64, "num_attention_heads": 24, "num_kv_heads": 4,
"head_dim": 256, "vocab_size": 248320,
"layer_types": ["LinearAttention", "...", "FullAttention"],
"rms_norm_eps": 1e-6,
"norm_style": "qwen", // "qwen": x̂·w | "gemma": x̂·(1+w)
"rope_theta": 1000000.0,
"tie_word_embeddings": false,
"max_position_embeddings": 262144,
"linear_conv_kernel_dim": 4,
"linear_num_key_heads": 16, "linear_num_value_heads": 48
},
"quant_type": "Q4_BLOCK", // informational default; truth = per-tensor dtype in the directory
"provenance": { "tool": "…", "source_model": "…" } // optional, free-form
}
```
`norm_style` 对引擎而言是强制的:把 Gemma 风格的 `(1+w)` 应用到
Qwen 权重上,会在一次前向传播的全部约 130 次归一化中产生静默的垃圾结果。
能力分派由**张量存在与否驱动**:引擎根据目录中存在什么来
逐层决定算子(q/k 偏置、qk 归一化、由投影宽度决定的输出门、MoE 路由器、GDN 投影)
—— 而非通过匹配模型名称。已知家族的新模型可以在
零引擎改动的情况下加载。
### 2.1 MTP —— 多 token 预测(可选)
如果模型携带 MTP 头(DeepSeek/Qwen 风格),arch 声明:
```jsonc
"mtp": { "num_layers": 1, "share_lm_head": true, "share_embed": true }
```
MTP 张量是使用规范名称的普通目录条目
(`model.mtp.*`):`enorm.weight`、`hnorm.weight`、
`eh_proj.weight [hidden, 2·hidden]`、`layers.{i}.*`(一个标准的
transformer 块)、`norm.weight`。
语义:`x = eh_proj·[enorm(embed(t_{p+1})); hnorm(h_p)]` —— 嵌入
在先(oracle 验证:反过来的顺序会得到恰好 0% 的接受率)
→ 块 → 共享 lm_head → 对 token `t_{p+2}` 的草稿。读取器无需
执行 MTP(元数据 + 普通张量,增量式
演进,无特性位);CMF 运行时将该头用于
推测解码,并有严格保证:**输出与纯贪婪解码完全相等** ——
被拒绝的草稿会从 KV 中回滚。
### 2.2 MoE —— 专家混合 FFN(可选)
如果模型携带 MoE 层(Qwen2-MoE / Qwen3-MoE / Qwen3.5-MoE),
arch 声明:
```jsonc
"moe": {
"num_experts": 256, "top_k": 8, "moe_intermediate_size": 512,
"norm_topk_prob": true, // Qwen2-MoE: false
"shared_expert_intermediate_size": 512 // absent if no shared expert
}
```
张量是使用 HF 名称的普通目录条目:
```
model.layers.{i}.mlp.gate.weight [num_experts, hidden] router
model.layers.{i}.mlp.experts.{e}.{gate,up,down}_proj.weight
model.layers.{i}.mlp.shared_expert.{gate,up,down}_proj.weight
model.layers.{i}.mlp.shared_expert_gate.weight [1, hidden]
```
哪些层是 MoE,由目录中路由器的**存在与否**决定
(逐层而非逐模型):Qwen2-MoE 的
`mlp_only_layers`/`decoder_sparse_step` 会产生混合模型,稠密
层保留普通的 `mlp.*_proj`。
执行语义(HF 对齐,由 `tests/moe_parity.sh` 在
四个家族上把关,包括融合的 AgentWorld 布局):对全部
路由器 logits 做 softmax → top-k(平局时:取较低索引,torch.topk 顺序)→ 若
`norm_topk_prob`,对所选的 k 个重归一化 → Σwₑ·FFNₑ(x);共享
专家总是以权重 `sigmoid(shared_expert_gate·x)` 加入。
专家在 mmap 中保持量化状态;每个 token 只触及所选
k 个的页面 —— 与技能相同的驻留机制。写入器应当把一层的专家张量按角色
连续排布(专家 0…N−1 的全部 `gate_proj` 相邻,然后全部 `up_proj`,再
全部 `down_proj`)——GPU 后端便可把一层的专家库当作一个区域处理,而
不是收集数百个切片;原生导入器与 `moe-defrag` 都按此顺序写出。每个专家是一个
单独的目录条目,各有**自己的** dtype:这正是
逐专家位宽分配的载体(P15 claim 12)—— 已实现,由
`tests/moe_vbit.sh` 把关;B 场(通过 `--route-stats` 得到的路由器选择频率)
已在一个 35B 模型上端到端测量。
## 3. 张量目录
逐字节采用 `.vmfc` v2 布局(单一正典,共享参考
解析器):
```
[0 : 8 ] count : u64
[8 : 16] pool_off : u64 (name-pool offset from section start)
[16 : 16 + count·56] 56-byte records:
name_off : u32 (relative to pool_off)
name_len : u16
dtype : u8 (§3.1)
ndim : u8 (≤ 6)
shape : u32 × 6 (zero-padded tail)
off : u64 (RELATIVE to data_off; multiple of 64)
nbytes : u64
hash : u64 (hash64 of the tensor bytes, §8)
[pool_off : …] UTF-8 name pool
```
张量名与源模型**一一对应**
(`model.layers.{i}.mlp.gate_proj.weight`、`model.embed_tokens.weight`、
`lm_head.weight`、…)。格式不规定张量集合:
目录是 blob 内容的唯一真相来源。
不存在"可计算布局"。
### 3.1 `dtype`
编号与 `.vmfc` 共享(id 从不重用):
| id | name | 在 CMF v2 中的状态 |
|----|-----------|------------------|
| 0 | `f32` | ✅ 读/写 |
| 1 | `f16` | ✅ 读/写(归一化及 1 维张量始终为 f16) |
| 2 | `bf16` | ✅ 读/写 |
| 3 | `q8_row` | ✅ 读/写 |
| 4 | `q4_block`| ✅ 读/写 |
| 5 | `mix8_4` | 保留 |
| 6 | `u8` | 保留 |
| 7 | `q4_col` | 保留 |
| 8 | `vbit` | ✅ 读/写(`QUANT_2F` 位),可变 3–8 位 |
| 9 | `q8_2f` | ✅ 读/写(`QUANT_2F` 位),𝒲×θ |
| 10 | `vbit_ro` | ✅ 读/写——`vbit` + 文件内行偏移表(O(1) 行访问);`--quant vbit` 的转换器默认 |
| 11 | `q4_tiled`| ✅ 读/写——交错平铺的 q4 `[f16 scale][16B nibbles]`(`--quant q4t`) |
| 12 | `q1` | ✅ 读/写——1 位二值权重,仅用于按 1 位训练的模型(`--quant q1`);每 32 组一个 `[f16 scale][4B 符号位]` 平铺,`w = s·(2·bit−1)` |
| 13 | `q1s` | ✅ 读/写——`q1` 基底 + 稀疏高精度离群值覆盖层(普通检查点的 1 位 PTQ):基底之后是 `[u32 count]` 与 `count × { [u32 扁平索引][f16 值] }`;变长,读取器信任目录中记录的字节数 |
| 14 | `q1t` | ✅ 读/写——三值 `{−s, 0, +s}`(BitNet b1.58 风格):每 32 组 `[f16 scale][7B base-3 码]`(每字节 5 个三值,3⁵=243≤256;码 0→0、1→+s、2→−s,约 2.25 bpw),随后按行的离群值覆盖层 `[u32 row_ptr[rows+1]]` + `{ [u16 col][f16 值] }`(第 r 行的离群值在 `[row_ptr[r], row_ptr[r+1])`,`col` 为行内索引);变长,规则同 `q1s` |
| 15 | `q4tp` | ✅ 读/写——`q4_tiled` 的 nibble,但每块 scale 改为按行阶梯上的 5 位档位(`--quant q4tp`,或用 `requant` 就地转换) |
### 3.2 量化布局(正典 = `.vmfc`:"先量化值,后 scale")
- **`q8_row`**(仅限二维 `[out, in]`):
`[int8 : out·in][f16 : out]` —— 每行一个 scale,
`w = q[o,i]·scale[o]`,`scale[o] = absmax(row_o)/127`。
- **`q4tp`**(仅限二维,`in % 32 == 0`):
`[nibbles: rows·gpr·16][行参数: rows × (f16 lo, f16 step)]
[codes: rows × ceil(gpr·5/8),5 位 LSB-first,按行对齐]`,`gpr = in/32`。
每块 scale 为 `2^(lo[r] + code·step[r])`,因此读取方只需展开该行的 32 级
阶梯一次,之后按表查 scale。nibble 的取值与顺序同 `q4_tiled`,仅 scale 的
表示不同:4.17 bit/weight 对 4.50——f16 scale 曾占 q4t 文件的 11%。
`lo`/`step` 取自该行 log-scale 的精确 min/max,故码值绝不会越界,格式无需
逃逸机制。编码器**必须**先把 `lo`/`step` 舍入到 f16 再选码,并按重建后的
scale 量化 nibble——否则写入方与读取方会不一致(这正是 q4 编码器必须先舍入
scale 的同一个陷阱)。
- **`q4_block`**:在展平张量上按 32 分组,零填充;
`[u8 : ceil(n/32)·16][f16 : ceil(n/32)]`。
半字节:元素 `2k` 为低位,`2k+1` 为高位;`w = (q − 8)·scale`,
`scale = absmax(group)/7`。
- **一维张量以及元素数 < 32 的张量始终为 `f16`**
(在矩阵最大压缩下保持归一化精度)。
- **`q8_2f`**:`[int8][f16 row-scale][f16 col-field]`,
`w = q·scale[o]·col[i]` —— 双场 Madelung 拆分 𝒲×θ,在
vmfcore 中已验证(同尺寸下 +37%;在离群输入通道上恢复了 q8→f16 差距的约 75%)。
- **`vbit`**(仅限二维,`in % 32 == 0`;P13 FIG.3):
`[u8 bits: rows][f16 scales: rows·in/32][bit-packed rows, MSB-first,
each row padded to a byte]`;`w = (u − L)·scale[r,g]`,
`L = 2^{b−1}−1`,位宽 b ∈ {3,4,5,6,8},下限 3(claim 13)。
分配 b_r:在 log2 行幅度上朝张量的平均预算做注水;
对于 MoE 专家,预算在**家族内共享**(层 × 投影):偏移
`ā_expert − ā_family` 等价于在所有专家的行上做联合注水 —— 一个响亮的
专家获得更多位,一个安静的被钉在下限(P15
claim 12;gate `tests/moe_vbit.sh`)。分配可选地
与一个 B 场取积 —— 在标定时收集的路由器选择频率
(`b ∝ log2(A·B)`,截断 Fisher)。
## 4. 权重 blob
`data_off` 是 4096 的倍数(按页对齐的 mmap);其中每个张量
都从 64 字节边界开始(SIMD 加载、缓存行)。张量之间为零
填充。读取器只能通过目录来解释该 blob。
## 5. 掩码区段
任务掩码 = 在共享权重之上"哪些活跃"的位字段(权重不变
—— VMF 原则:技能选取凝聚态的一个
子集)。
```
[0 : 4] n_masks : u32
[4 : 8] meta_len : u32
[8 : 8 + meta_len] JSON meta (§5.1)
[…] mask blobs, each aligned to 8 from the section start
```
一个掩码 blob(尺寸由 arch 推导,无内部头部):
```
[n_layers × ffn_bytes] FFN bitfields ffn_bytes = ceil(intermediate_size / 8)
[n_layers × head_bytes] head bitfields head_bytes = ceil(num_attention_heads / 8)
[gates_bytes] layer_gates gates_bytes = ceil(num_layers / 8)
[n_layers × expert_bytes] expert bitfields 可选——仅当该掩码的 meta 设置
"has_expert_fields": true;
expert_bytes = ceil(moe.num_experts / 8)
```
位序为 LSB 优先:神经元 `i` = 字节 `i / 8` 的第 `i % 8` 位;置位
→ 活跃。**超出维度的尾部位必须为零**(否则
popcount 会看到幻影神经元/头)。
可选的专家区域(增量式:旧读取器从不读 gates 之后的内容,且每个掩码
的 `blob_len` 是显式的)让任务掩码收窄 MoE **路由**:第 `l` 层行的第
`e` 位置位 → 专家 `e` 对该任务可路由;选择只在可路由集合上进行,
路由器 softmax 在其上重新归一化。这是 §11.1 物理专家碎片整理的运行时
可切换孪生——一个携带完整专家集的文件服务多个专才(`cortiq moe-mask`
写入此类掩码,`run --task <名称>` 激活;已验证与等价的运行时限制逐
词元一致)。整行为 1 的层不受限制;不带该区域的掩码不做任何收窄。
### 5.1 掩码 JSON meta
```jsonc
{
"default_task": "general",
"masks": [{
"task_id": 0, "name": "general", "description": null,
"sparsity": 0.62,
"quality": { // null = NOT MEASURED (declaring 1.0 is forbidden)
"metric": "heldout_ppl_ratio", "value": 0.97,
"baseline_dense": 6.10, "n_samples": 512, "dataset_sha256": "…"
},
"parent": null, "priority": "Fallback", "has_hot_pack": false,
"blob_off": 4096, "blob_len": 139328 // relative to section start
}]
}
```
`quality` 是一份**留出集契约**,而非一个声明:没有实测指标的
转换器写入 `null`;运行时在切换到未测量掩码时会记录警告。
## 6. 分词器区段
HuggingFace `tokenizer.json` 的字节,逐字保留。模型
自包含:一个文件 = 一个分发单元。附带文件
仍作为调试回退。
### 6.1 聊天捆绑(`header.tokenizer_config`)
是文件 —— 而非运行时二进制 —— 定义了聊天行为。头部
携带一个可选块(增量式演进,无特性位):
```json
"tokenizer_config": {
"chat_template": "<Jinja template from chat_template.jinja or tokenizer_config.json>",
"eos_token_ids": [248044, 248045],
"bos_token_id": null,
"pad_token_id": 248055
}
```
运行时以 HF 语义渲染模板(trim_blocks、
lstrip_blocks、循环控制、Python 字符串方法),并在
`eos_token_ids` 中的任一 id 处停止生成。Gate:
`tests/chat_template_parity.sh` —— 运行时渲染结果与参考
jinja2 逐字节相等。没有该块的文件获得 ChatML 回退。
## 7. 稀疏索引
一座预计算的"掩码 → 计算跳过"桥梁:逐 (task, layer) 对的
活跃 FFN 量化组(每组 32 个神经元)与头。
> 诚实的状态:引擎直接从掩码位字段取活跃索引;索引会被 CLI 读取并
> 显示,但从未用于执行。**待弃用**:写入器应停止生成它(读取器继续
> 解析既有文件);只有当"掩码 × 量化 mmap"路径以实测收益落地时才会
> 复活。
```
[0 : 4] n_entries : u32
[4 : 8] reserved : u32 (0)
entry (4-aligned):
task_id : u32
layer_idx : u32
n_groups : u32
n_heads : u32
[u16 × n_groups] active FFN-group indices (sorted)
[u8 × n_heads] active head indices (sorted)
zero padding to a multiple of 4
```
一个组只要包含至少一个活跃掩码位即为活跃。
## 8. `hash64`
张量字节的非加密 64 位哈希:在 64 位 LE 字上做 murmur3
`fmix64`,带位置盐 `i·0x9E3779B97F4A7C15`,XOR 折叠、
`xor len`、最终 `fmix64`。与
`vmfcore.hash64`(Python)及 `vmfcore::hash64`(Rust)逐位兼容 ——
`.cmf` 与 `.vmfc` 之间共享张量的哈希相符(跨技能文件的
骨干去重是免费的)。
用途:`cortiq verify`(损坏检测)、去重、缓存键。
### 8.1 区段哈希
元数据完整性(不仅是张量):
- 信封保留区 `[0x70:0x78]` = hash64(header JSON),`[0x78:0x80]` =
hash64(directory)。零 = "缺失"(旧文件可通过)。
- 头部 JSON 携带 `section_hashes` —— masks/vocab/index 的十六进制
hash64(u64 若作为 JSON 数字,超过 2^53 会损失
精度)。信封中的头部哈希传递性地覆盖它们。
- 信封自身(前 0x70 字节)不被哈希:哈希无法
保护自己;损坏的偏移量会被链条下游的边界/哈希捕获。
- `cortiq verify` 检查整条链;单个被翻转的头部字节
就是一个错误。
### 8.2 分离式签名(真实性,可选)
哈希链证明完整性,但不证明作者身份。`cortiq sign` 写出分离的
`<model>.sig` —— JSON `{alg: "ed25519-sha256", pubkey, sha256, sig}`,
即对文件 SHA-256 的 Ed25519 签名——容器本身从不被重写,旧工具不受
影响。当 `.sig` 位于模型旁边时,`cortiq verify` 自动校验签名;缺失
不算错误。密钥是签名者私存的 32 字节十六进制种子文件。
## 反特性 —— 格式刻意不具备的东西
- **可计算权重布局** —— v1 的一号 bug 类别(写入器与读取器
各自"计算"布局并发生分歧)。
- **静默回退** —— v1 会把任何垃圾文件解释为"一个 27B
模型";v2 必须失败。
- **用 JSON 存位数据** —— v1 掩码用 JSON 存储会膨胀 3–4 倍。
- **声明字段** —— 默认的 `quality_score: 1.0`、面积律
"容量"、动力学中的 Born 乘子:隐喻在被测量之前
不会成为格式字段。
## 9. 技能 —— 一个文件中的集群(Patent 15,claims 2/12/15)
一个共享骨干 + K 条逐技能记录;没有任何记录存储完整
模型。存储量按 |backbone| + Σ|deltas| 伸缩。
**替换张量**是命名为
`skill.{skill_id}.{name_of_replaced_tensor}` 的普通目录条目,例如
`skill.sql.model.layers.3.mlp.gate_proj.weight`。被替换张量的完整逻辑形状
(full-shape —— 非低秩、非差异列表、非
掩码),可采用 §3 的任意编码。逐技能增量索引(claim 2)由
目录物化:一次前缀过滤得到 技能 →
字节偏移;惰性分页 = mmap 精确访问那些偏移
(claim 12)。
**注册表** —— 头部 JSON,增量式:
```json
"skills": [{
"id": "sql",
"name": "SQL assistant",
"layers": [3, 4, 5],
"selection": {"metric": "mse", "phi_layer": 20,
"mean": "<f16 base64>", "basis": "<f16 base64>"},
"input_mask_task": null,
"quality": {"metric": "ppl", "backbone": 21.4, "overlaid": 17.9,
"dataset_sha256": "…"}
}]
```
`selection` 保存用于 recon-argmin 路由的仿射子空间参数
(`E = ‖r − BBᵀr‖²/‖φ‖²`,选择 E 最小的技能);
文件对于选择是自足的。`quality` 是诚实的 claim-16
契约(留出数据上的叠加态 vs 骨干)。
**执行语义(claims 1/3/18)**:张量来源间接寻址 —— 对于
每个张量,运行时读取**要么**骨干条目,**要么**存在时的
`skill.{active}.{name}`;是替换而非相加,从不组装
完整的逐技能模型(所有张量都是指向
同一 mmap 的指针)。软叠加(claim 14):混合工作张量
`Σwᵢ·Tᵢ`,`wᵢ = softmax(−E/T)`。
**仅追加式增长(claim 11)**:添加一个技能 = 在文件尾部追加新
张量 + 在尾部重新发出 目录/头部/索引 + 就地
更新信封偏移量(偏移 0 固定)。已写入张量的字节与
偏移量永不改变;旧的 dir/header 字节
成为死区段尾(兼容:读取器只通过
信封导航)。压实(`converter/cmf_compact.py`)= 一次普通重写。
### 9.1 独立技能文件(`SKILL_FILE`,位 6)
技能也可以脱离基座旅行:一个 `.cmf`,其张量集只包含烘焙改变的部分
(加上掩码目录),并通过注册表记录中的身份键绑定到它所裁自的基座:
```json
"skills": [{
"id": "gfx-html",
"layers": [0, 1, "...", 21],
"base_dir_hash": "9f22593eb458bc6f",
"base_arch": "nanbeige",
"task": "specialist",
"provenance": {"corpus": "…", "tensors": 30}
}]
```
- `base_dir_hash` —— 基座文件张量目录字节的 hex `hash64`(信封在
`[0x78]` 携带的同一值)。技能是针对精确字节的增量,而非针对架构:
当基座目录哈希不同,`apply` 必须拒绝(显式 `--force` 可覆盖;
其结果超出规范)。
- `base_arch`、`task`、`provenance` —— 信息键:人工校验、激活的掩码
任务、来源。
任何带 `base_dir_hash` 的记录都会升起位 6:旧读取器大声拒绝,知道
该位的运行时拒绝运行它(部分张量集不是模型),并指向
`cortiq skill apply <base> <skill> -o out.cmf` —— 验证密钥、把张量与
掩码叠加到基座上、写出完整文件(与技能裁出时的专家逐字节等价)。
生命周期:`skill bake`(专家)→ `skill export --base`(增量 + 密钥)→
发布小文件 → 在任意基座副本上 `skill apply`。
状态:完全实现并把关(容器 + 间接寻址、
生产配方、recon-argmin 路由、仅追加 + 压实、
软混合);claim 16 由测量满足(运行时任务 PPL −24.9%)。
## 10. 分片 —— 一个模型分为 N 个文件
命名:`{base}-{no:05}-of-{count:05}.cmf`(在精神上与
safetensors 兼容)。用户可打开**任意**名称;运行时归一化到分片 1
并按模式拾取兄弟文件。
**每个分片都是一个独立的有效 .cmf**:完整信封、头部 JSON、
一份属于**自己**张量的目录、自己的数据 blob、自己的哈希
(`section_hashes` + 逐张量)。`cortiq verify` 可在没有兄弟文件的情况下
对任意单个分片工作。
每个分片的头部携带:
```json
"shard": { "no": 1, "count": 5 }
```
没有该块 = 一个普通的单文件(向后兼容:旧读取器把
分片 1 视为一个有效但不完整的模型,并在缺失张量处诚实地失败)。
**内容分布**:张量按规范顺序贪婪地拆分
(`--shard-max-gb` 阈值,粗略的 f32 尺寸);masks/vocab/稀疏
索引区段、`tokenizer_config`(聊天捆绑)以及 `skills`
注册表**仅**存在于分片 1 中 —— 其余分片的这些区段为空,且
`tokenizer_config: null`。技能张量(`skill.{id}.*`)作为
普通目录条目分布 —— 分片 1 的注册表通过合并后的目录按名称
引用它们。
**加载**(`CmfModel::open_sharded`):打开分片 1 → mmap 所有兄弟文件
→ 合并目录(每个条目记住其分片索引 —— 一个运行时
字段,绝不写入磁盘)→ 运行时随后如同处理单个
文件一般工作。错误:直接打开一个非首分片、缺失一个兄弟文件、
`count` 不匹配。
Gate(Qwen3.5-0.8B q8_2f,5 个分片 ≤ 0.6 GB):分片化 PPL == 未分片化,
在同一二进制上逐字节相等;`verify` 在每个单独分片上皆为绿。
## 11. 碎片整理——物理剪枝(USPTO App. 19/452,464,claims 9/10 —— [PATENTS.md](../PATENTS.md))
掩码(§5)是**虚拟稀疏**:被剪掉的神经元只是被标记,仍完整存储(所有
任务共享一个骨干——在承诺单一任务之前无法物理切除)。碎片整理把虚拟
稀疏变成**物理压缩**:死 FFN 神经元从文件中删除——**既不存储也不计算**。
**表示——无新特性位,向后兼容。** 物理剪枝只通过目录中更小的张量形状
表达(§3"无可计算布局";目录是形状的唯一权威)。运行时从张量形状
(`gate_proj.rows()`)推导 FFN 尺寸,而非 `arch.intermediate_size`,
因此整理后的文件就是一个普通的更小稠密模型,现有读取器无需修改即可
加载。整理后的文件**没有**掩码节(剪枝后掩码即恒等)。
`arch.intermediate_size` 变为名义值(= 各层最大值);真实尺寸在每个
张量里——且每层收缩到自己的存活神经元数。
**强制不变量:** 每层三元组 `gate_proj.rows() == up_proj.rows() ==
down_proj.cols() == inter'ₗ` 且 `down_proj.rows() == hidden_size`;
神经元轴:`gate/up` 为行、`down` 为列;量化组 32:`inter'` 非 32 倍数
时 `down_proj` 写为 `q8_2f`(转换器自动降级);这不是字节截断——
反量化 → 收集存活神经元 → 在更小形状上**重量化**,张量哈希重新计算;
`hidden_size`、嵌入、`lm_head` 与归一化权重不动。
**一个任务,一个独立文件。** 碎片整理是破坏性的:一个 `.cmf` 只烘焙
一个任务。多任务服务仍走掩码(§5)或技能(§9)。溯源写在
`provenance.defrag` 中(诚实契约)。
**覆盖范围:** 本节为稠密 FFN 神经元;MoE 专家见 §11.1。注意力头
剪枝不在范围内。
### 11.1 MoE 专家碎片整理(`cortiq moe-defrag`)
§11 的 MoE 孪生,由路由 B 场而非神经元掩码驱动:专家使用高度依赖
任务(在 34.7B 代码模型上实测:代码与散文的 top-64 专家集合 Jaccard
仅 0.25),因此单任务文件可以丢弃该任务从不路由到的专家。从
`CMF_MOE_STATS` 转储(代表性任务运行的逐层专家选择计数)出发,每层
保留达到 `--cover` 路由质量分数的最小 top 专家集,丢弃其余。
**表示——同样的哲学,无特性位。**
- 保留的专家重新编号为**连续的逐层前缀** `mlp.experts.0 …
mlp.experts.{k−1}`,保持相对顺序;读取器按张量**存在性**枚举一层
的专家,上限为 `arch.moe.num_experts`(变为名义值 = 原始数量)——
与 §11 对 `intermediate_size` 的规则互为镜像。
- 路由器的行按同一顺序收集:`mlp.gate.weight` 变为 `[kept_l,
hidden]`,且 `router.rows() ==(存在的专家条目数)`是加载期不变量。
`top_k` 收缩到逐层专家数。
- 选择语义不变(§2.2):softmax 在保留集合上重新归一化。同一限制可
在**运行时**应用而无需重写文件(`CMF_MOE_MASK=<stats.json>` +
`CMF_MOE_MASK_COVER`)——两者在数学上相等,据此在物理切除前用
困惑度为 cover 水平把关。
- 专家载荷逐字节复制(无需重量化——专家轴切的是整张量而非量化组),
幸存权重与源逐字节相同,重写从源 mmap 流式进行。
实测参考(KAT-Coder 34.7B-A3B,代码校准,cover 0.95):19.6 → 12.7
GB(−35%),保留集代码困惑度 +2.8%;在 24 GB 内存机器上——完整模型
需要换页——解码 ×1.8、prefill ×3.3。
任务外质量按设计下降;与 §11 一样,一个整理后的文件只烘焙一个任务。
## 12. 流水线容器——单文件文生图
同一套信封/目录/数据块机制承载非 LLM 流水线。区别只有 `arch_name`
标签和张量名的命名空间;没有新节、没有特性位(不执行流水线的读取器
仍可验证与检视文件)。
当前实例——`arch_name: "lumina2-image"`(Lumina-Image 2.0,
`cortiq imagine-pack` / `cortiq imagine`):一个文件装下整个文生图栈。
- **命名空间**:`te.*`——文本编码器 Transformer(Gemma-2 类 LLM;
头部的 `arch` 块描述的正是该组件,通用工具因此能读到有意义的
维度)、`dit.*`——Next-DiT 去噪器、`vae.*`——VAE 解码器。各组件的
config JSON 以 `{prefix}.config_json` u8 张量随行——文件自足。
- **量化**:一如既往逐张量(§3 目录即真相)——通常 te/dit 矩阵为
q4t/q8,VAE 卷积与归一化为 f16。
- **分词器节**(§6)携带文本编码器的分词器;`provenance.pipeline` +
`provenance.components` 记录配方。
---
*相关:[COMPARISON.md](COMPARISON.md)(CMF 与其他模型格式的对比)、
[项目 README](../README.md)(概览与快速上手)、
`python/cmf_reader.py`(独立读取器:标准库 + numpy,读取所有
dtype、分片、技能、verify)。*
|