cortiq-engine 0.3.1

Portable inference runtime for the CMF model format, with no ML framework underneath: runs on CPU, and on GPU (Vulkan / Metal / DX12) with the `gpu` feature; tokenizer, chat templates and dynamic per-skill weight overlay.
Documentation
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
//! FCD trainer end-to-end checks on a tiny synthetic .cmf:
//! 1. block-level gradcheck — finite differences over TRAINABLE weights
//!    through the WHOLE training graph (teacher + student + CE/KL),
//!    which transitively exercises every through-grad (attention, rope,
//!    qk-norm, GQA reduce, residuals, final norm, tied head);
//! 2. training smoke — loss decreases over 20 steps, the best
//!    checkpoint is restored, the polished file loads and forwards.

use cortiq_core::format::TensorSpec;
use cortiq_core::{CmfHeader, CmfModel, LayerType, ModelArch, NormStyle, QuantType, TensorDtype};
use cortiq_engine::fcd::{run_polish, FcdHyper, FcdModel, TrainState};
use cortiq_engine::nystrom::O1Cfg;
use cortiq_engine::Pipeline;
use cortiq_engine::SamplerConfig;
use std::sync::Arc;

const H: usize = 16;
const NH: usize = 2;
const NKV: usize = 1; // rep = 2 → the GQA sum-reduce path is live
const HD: usize = 8;
const NL: usize = 2;
const INTER: usize = 24;
const VOCAB: usize = 48;
const SEQ: usize = 16; // > w + sink + 8 = 13 → skeleton active

fn synth_f32(n: usize, salt: u64, scale: f32) -> Vec<f32> {
    (0..n)
        .map(|i| {
            let x = (i as u64)
                .wrapping_mul(6364136223846793005)
                .wrapping_add(salt.wrapping_mul(1442695040888963407) ^ 0x9E3779B97F4A7C15);
            let x = (x ^ (x >> 31)).wrapping_mul(0xBF58476D1CE4E5B9);
            (((x >> 11) as f64 / (1u64 << 53) as f64 - 0.5) as f32) * scale
        })
        .collect()
}

fn f32_bytes(v: &[f32]) -> Vec<u8> {
    let mut out = Vec::with_capacity(v.len() * 4);
    for x in v {
        out.extend_from_slice(&x.to_le_bytes());
    }
    out
}

fn tiny_arch() -> ModelArch {
    ModelArch {
        arch_name: "qwen3".into(),
        hidden_size: H,
        intermediate_size: INTER,
        num_layers: NL,
        num_attention_heads: NH,
        num_kv_heads: NKV,
        head_dim: HD,
        vocab_size: VOCAB,
        layer_types: vec![LayerType::FullAttention; NL],
        rms_norm_eps: 1e-6,
        norm_style: NormStyle::Qwen,
        rope_theta: 10_000.0,
        tie_word_embeddings: true,
        partial_rotary_factor: 1.0,
        mtp: None,
        moe: None,
        linear_core: None,
        max_position_embeddings: 4096,
        linear_conv_kernel_dim: None,
        linear_num_key_heads: None,
        linear_num_value_heads: None,
        linear_key_head_dim: None,
        linear_value_head_dim: None,
    }
}

/// GDN geometry of the tiny hybrid fixture (rep = nv/nk = 2).
const G_NV: usize = 2;
const G_NK: usize = 1;
const G_DK: usize = 4;
const G_DV: usize = 4;
const G_KK: usize = 3;

/// Write a tiny all-f32 model (with per-head qk-norms — that path must
/// be exercised) and return its path. `gate` adds the Qwen3.5 output
/// gate (q_proj rows = 2·nh·hd); `hybrid` makes layer 1 GatedDeltaNet.
fn write_tiny_model_variant(
    dir: &std::path::Path,
    name: &str,
    gate: bool,
    hybrid: bool,
) -> std::path::PathBuf {
    let mut specs: Vec<TensorSpec> = Vec::new();
    let mut push = |name: &str, shape: Vec<usize>, data: Vec<f32>| {
        specs.push(TensorSpec {
            name: name.into(),
            dtype: TensorDtype::F32,
            shape,
            data: f32_bytes(&data),
        });
    };
    push(
        "model.embed_tokens.weight",
        vec![VOCAB, H],
        synth_f32(VOCAB * H, 100, 0.6),
    );
    let norm1 = |n: usize, salt: u64| -> Vec<f32> {
        synth_f32(n, salt, 0.2).iter().map(|v| 1.0 + v).collect()
    };
    push("model.norm.weight", vec![H], norm1(H, 101));
    for li in 0..NL {
        let p = format!("model.layers.{li}.");
        let s = li as u64 * 20;
        push(&format!("{p}input_layernorm.weight"), vec![H], norm1(H, 102 + s));
        push(
            &format!("{p}post_attention_layernorm.weight"),
            vec![H],
            norm1(H, 103 + s),
        );
        if hybrid && li == 1 {
            // GatedDeltaNet layer (frozen in FCD; through-backward only).
            let c_dim = 2 * G_NK * G_DK + G_NV * G_DV;
            let vd = G_NV * G_DV;
            let la = format!("{p}linear_attn.");
            push(&format!("{la}in_proj_qkv.weight"), vec![c_dim, H], synth_f32(c_dim * H, 130 + s, 0.5));
            push(&format!("{la}in_proj_z.weight"), vec![vd, H], synth_f32(vd * H, 131 + s, 0.5));
            push(&format!("{la}in_proj_a.weight"), vec![G_NV, H], synth_f32(G_NV * H, 132 + s, 0.5));
            push(&format!("{la}in_proj_b.weight"), vec![G_NV, H], synth_f32(G_NV * H, 133 + s, 0.5));
            push(&format!("{la}conv1d.weight"), vec![c_dim, G_KK], synth_f32(c_dim * G_KK, 134 + s, 0.6));
            push(&format!("{la}A_log"), vec![G_NV], synth_f32(G_NV, 135 + s, 0.8));
            push(&format!("{la}dt_bias"), vec![G_NV], synth_f32(G_NV, 136 + s, 0.8));
            push(&format!("{la}norm.weight"), vec![G_DV], norm1(G_DV, 137 + s));
            push(&format!("{la}out_proj.weight"), vec![H, vd], synth_f32(H * vd, 138 + s, 0.5));
            push(&format!("{p}mlp.gate_proj.weight"), vec![INTER, H], synth_f32(INTER * H, 110 + s, 0.5));
            push(&format!("{p}mlp.up_proj.weight"), vec![INTER, H], synth_f32(INTER * H, 111 + s, 0.5));
            push(&format!("{p}mlp.down_proj.weight"), vec![H, INTER], synth_f32(H * INTER, 112 + s, 0.5));
            continue;
        }
        let q_rows = if gate { 2 * NH * HD } else { NH * HD };
        push(
            &format!("{p}self_attn.q_proj.weight"),
            vec![q_rows, H],
            synth_f32(q_rows * H, 104 + s, 0.5),
        );
        push(
            &format!("{p}self_attn.k_proj.weight"),
            vec![NKV * HD, H],
            synth_f32(NKV * HD * H, 105 + s, 0.5),
        );
        push(
            &format!("{p}self_attn.v_proj.weight"),
            vec![NKV * HD, H],
            synth_f32(NKV * HD * H, 106 + s, 0.5),
        );
        push(
            &format!("{p}self_attn.o_proj.weight"),
            vec![H, NH * HD],
            synth_f32(H * NH * HD, 107 + s, 0.5),
        );
        push(&format!("{p}self_attn.q_norm.weight"), vec![HD], norm1(HD, 108 + s));
        push(&format!("{p}self_attn.k_norm.weight"), vec![HD], norm1(HD, 109 + s));
        push(
            &format!("{p}mlp.gate_proj.weight"),
            vec![INTER, H],
            synth_f32(INTER * H, 110 + s, 0.5),
        );
        push(
            &format!("{p}mlp.up_proj.weight"),
            vec![INTER, H],
            synth_f32(INTER * H, 111 + s, 0.5),
        );
        push(
            &format!("{p}mlp.down_proj.weight"),
            vec![H, INTER],
            synth_f32(H * INTER, 112 + s, 0.5),
        );
    }
    let mut arch = tiny_arch();
    if hybrid {
        arch.layer_types = vec![LayerType::FullAttention, LayerType::LinearAttention];
        arch.linear_core = Some(cortiq_core::types::LinearCoreConfig {
            kind: "gated_delta_net".into(),
            num_heads: G_NV,
            nphase: None,
            value_head_dim: G_DV,
        });
        arch.linear_num_key_heads = Some(G_NK);
        arch.linear_num_value_heads = Some(G_NV);
        arch.linear_key_head_dim = Some(G_DK);
        arch.linear_conv_kernel_dim = Some(G_KK);
    }
    let header = CmfHeader {
        format: "cmf".into(),
        version: cortiq_core::format::CMF_VERSION,
        arch,
        quant_type: QuantType::F32,
        provenance: None,
        tokenizer_config: None,
        section_hashes: None,
        skills: Vec::new(),
        shard: None,
        calibration: None,
    };
    let path = dir.join(format!("{name}.cmf"));
    CmfModel::write(&path, &header, &specs, None, None).expect("write tiny model");
    path
}

fn write_tiny_model(dir: &std::path::Path) -> std::path::PathBuf {
    write_tiny_model_variant(dir, "tiny_fcd", false, false)
}

fn tiny_o1() -> O1Cfg {
    O1Cfg::from_spec("all", Some(4), Some(4), Some(1), None).unwrap()
}

fn rand_ids(n: usize, salt: u64) -> Vec<u32> {
    synth_f32(n, salt, 1.0)
        .iter()
        .map(|v| (((v + 0.5) * VOCAB as f32) as u32).min(VOCAB as u32 - 1))
        .collect()
}

/// FD over top-|g| trainable weights through the whole training graph.
/// Returns (per-tensor worst rel err). `only` filters which tensor
/// slots are asserted; the rest are printed.
fn block_fd(
    fm: &FcdModel,
    ts: &mut TrainState,
    ids: &[u32],
    tgt: &[u32],
    t: usize,
    tol: f64,
    only: &dyn Fn(usize) -> bool,
) -> f64 {
    let kl_w = 0.7;
    let l0 = fm.loss_and_grads_for_test(ids, tgt, 1, t, ts, kl_w);
    assert!(l0.is_finite());
    let analytic: Vec<Vec<f32>> = ts.grads().to_vec();
    let h = 1e-3f32;
    let mut worst: f64 = 0.0;
    for (pi, g) in analytic.iter().enumerate() {
        let mut order: Vec<usize> = (0..g.len()).collect();
        order.sort_by(|&a, &b| g[b].abs().partial_cmp(&g[a].abs()).unwrap());
        for &i in order.iter().take(3) {
            let ga = g[i] as f64;
            let orig = ts.data[pi][i];
            ts.data[pi][i] = orig + h;
            let lp = fm.loss_and_grads_for_test(ids, tgt, 1, t, ts, kl_w);
            ts.data[pi][i] = orig - h;
            let lm = fm.loss_and_grads_for_test(ids, tgt, 1, t, ts, kl_w);
            ts.data[pi][i] = orig;
            let fd = (lp - lm) / (2.0 * h as f64);
            let rel = (ga - fd).abs() / ga.abs().max(fd.abs()).max(1e-8);
            if only(pi) {
                worst = worst.max(rel);
                assert!(
                    rel < tol,
                    "tensor {pi} idx {i}: analytic {ga:.5e} vs fd {fd:.5e} (rel {rel:.2e})"
                );
            } else {
                println!("  (unasserted, M-gap zone) tensor {pi} idx {i}: rel {rel:.2e}");
            }
        }
    }
    worst
}

/// Full-graph FD with the skeleton INACTIVE (w large → the kernel's
/// exact fallback): there is no frozen M anywhere, so EVERY trainable
/// tensor must match tightly. This exercises the whole assembly —
/// checkpointed recompute, projections, qk-norm, rope, GQA reduce,
/// residuals, final norm, tied head, CE+KL.
#[test]
fn block_gradcheck_full_graph_exact_mode() {
    let dir = std::env::temp_dir().join(format!("fcd_block_ex_{}", std::process::id()));
    std::fs::create_dir_all(&dir).unwrap();
    let path = write_tiny_model(&dir);
    let model = CmfModel::open(&path).unwrap();
    let o1 = O1Cfg::from_spec("all", Some(4), Some(64), Some(0), None).unwrap(); // w ≥ t → exact
    let fm = FcdModel::from_cmf(&model, &o1).unwrap();
    let mut ts = TrainState::new(&fm);
    let seqs = rand_ids(SEQ + 1, 7);
    let worst = block_fd(
        &fm,
        &mut ts,
        &seqs[..SEQ],
        &seqs[1..],
        SEQ,
        3e-2,
        &|_| true,
    );
    println!("block gradcheck exact-mode (all 30 FD points): worst rel err {worst:.2e}");
    std::fs::remove_dir_all(&dir).ok();
}

/// Full-graph FD with the skeleton ACTIVE. The training gradient
/// deliberately freezes M (no-grad pinv — the certified convention), so
/// FD only matches tightly on tensors with NO attention above them:
/// the LAST layer's post-attention half (pln/gate/up/down). Everything
/// upstream moves M under perturbation — printed, not asserted (the
/// op-level frozen-M gradcheck covers those chains exactly).
#[test]
fn block_gradcheck_skeleton_active_last_layer_tight() {
    let dir = std::env::temp_dir().join(format!("fcd_block_ny_{}", std::process::id()));
    std::fs::create_dir_all(&dir).unwrap();
    let path = write_tiny_model(&dir);
    let model = CmfModel::open(&path).unwrap();
    let fm = FcdModel::from_cmf(&model, &tiny_o1()).unwrap(); // w=4 → skeleton on
    let mut ts = TrainState::new(&fm);
    let seqs = rand_ids(SEQ + 1, 7);
    // Trainable slots: layer-major, 5 per layer; the last layer's
    // pln/gate/up/down are slots (NL-1)*5 + 1..5.
    let base = (NL - 1) * 5;
    let worst = block_fd(
        &fm,
        &mut ts,
        &seqs[..SEQ],
        &seqs[1..],
        SEQ,
        3e-2,
        &move |pi| pi > base,
    );
    println!(
        "block gradcheck skeleton-active (last layer post-attn tight): worst rel err {worst:.2e}"
    );
    std::fs::remove_dir_all(&dir).ok();
}

/// E2E smoke: 20 steps on a tiny model — loss decreases, the best
/// checkpoint restore happens, the polished file loads and forwards.
#[test]
fn training_smoke_loss_decreases_and_output_loads() {
    let dir = std::env::temp_dir().join(format!("fcd_smoke_{}", std::process::id()));
    std::fs::create_dir_all(&dir).unwrap();
    let path = write_tiny_model(&dir);
    let model = Arc::new(CmfModel::open(&path).unwrap());
    let tr = rand_ids(4000, 11);
    let va = rand_ids(800, 13);
    let hp = FcdHyper {
        steps: 20,
        lr: 1e-3, // tiny random model: visible motion beats recipe-lr here
        kl_w: 0.7,
        eval_every: 5,
        bs: 2,
        seq: SEQ,
        seed: 0,
    };
    let out = dir.join("tiny_fcd.fcd.cmf");
    let report = run_polish(&model, &tiny_o1(), &hp, &tr, &va, &out, None).expect("polish");

    assert_eq!(report.losses.len(), 20, "one (ce, kl) per step");
    let total = |(ce, kl): &(f64, f64)| 0.3 * ce + 0.7 * kl;
    let first: f64 = report.losses[..5].iter().map(total).sum::<f64>() / 5.0;
    let last: f64 = report.losses[15..].iter().map(total).sum::<f64>() / 5.0;
    println!(
        "smoke: loss first5 {first:.4} → last5 {last:.4} | ppl start {:.2} final {:.2} \
         | best step {}",
        report.ppl_start, report.ppl_final, report.best_step
    );
    assert!(last < first, "loss must decrease over 20 steps ({first:.4} → {last:.4})");
    assert!(report.best_step > 0 && report.best_step % 5 == 0, "best from an eval point");
    assert!(report.ppl_final.is_finite() && report.ppl_start.is_finite());

    // The polished container: intact, trainables replaced as F32, o1 +
    // fcd provenance present, loadable and forwardable by the runtime.
    let polished = CmfModel::open(&out).expect("open polished");
    assert!(polished.verify().is_empty(), "hashes intact");
    let prov = polished.header.provenance.as_ref().expect("provenance");
    assert!(prov.get("o1_attn").is_some() && prov.get("fcd").is_some());
    let t0 = polished.tensor("model.layers.0.mlp.gate_proj.weight").unwrap();
    assert_eq!(t0.dtype, TensorDtype::F32);
    // Weights actually moved.
    let orig = model.tensor_bytes("model.layers.0.mlp.gate_proj.weight").unwrap();
    let new = polished.tensor_bytes("model.layers.0.mlp.gate_proj.weight").unwrap();
    assert_ne!(orig, new, "polish must change the trained tensors");
    // Untouched tensors byte-identical.
    assert_eq!(
        model.tensor_bytes("model.embed_tokens.weight").unwrap(),
        polished.tensor_bytes("model.embed_tokens.weight").unwrap()
    );

    let polished = Arc::new(polished);
    let mut p = Pipeline::from_model(&polished, SamplerConfig::default()).expect("pipeline");
    let logits = p.forward_ids(&va[..32], None).expect("forward");
    assert!(logits.iter().all(|v| v.is_finite()), "finite logits from the polished file");
    std::fs::remove_dir_all(&dir).ok();
}

/// Hybrid graph FD: layer 0 = Full attention (converted, TRAINABLE),
/// layer 1 = GatedDeltaNet (frozen). FD over layer-0 weights flows
/// through the ENTIRE GDN BPTT above it (conv+SiLU, l2-norms, gates,
/// delta-rule state chain, gated RMSNorm) — the dispatch integration
/// check the GDN op-level gradcheck cannot give. Exact attention mode
/// (w ≥ t) so the only new machinery under test is the GDN arm.
#[test]
fn block_gradcheck_hybrid_gdn_above_trainable() {
    let dir = std::env::temp_dir().join(format!("fcd_block_gdn_{}", std::process::id()));
    std::fs::create_dir_all(&dir).unwrap();
    let path = write_tiny_model_variant(&dir, "tiny_hybrid", false, true);
    let model = CmfModel::open(&path).unwrap();
    // "0" alone means OFF in the spec grammar — build the cfg directly.
    let o1 = O1Cfg {
        layers: cortiq_engine::nystrom::O1Layers::List(vec![0]),
        m: 4,
        w: 64,
        sink: 0,
        rect: cortiq_engine::nystrom::O1_DEFAULT_RECT,
    };
    let fm = FcdModel::from_cmf(&model, &o1).unwrap();
    let mut ts = TrainState::new(&fm);
    assert_eq!(ts.layers, vec![0], "only the Full layer is convertible");
    let seqs = rand_ids(SEQ + 1, 17);
    let worst = block_fd(
        &fm,
        &mut ts,
        &seqs[..SEQ],
        &seqs[1..],
        SEQ,
        3e-2,
        &|_| true,
    );
    println!("block gradcheck hybrid (through GDN BPTT): worst rel err {worst:.2e}");
    std::fs::remove_dir_all(&dir).ok();
}

/// Output-gate variant: both layers Full with the Qwen3.5 per-head
/// [q; gate] projection; exact mode; every trainable FD-checked tight
/// (validates the gate split forward + σ-gate backward + re-interleave).
#[test]
fn block_gradcheck_output_gate() {
    let dir = std::env::temp_dir().join(format!("fcd_block_gate_{}", std::process::id()));
    std::fs::create_dir_all(&dir).unwrap();
    let path = write_tiny_model_variant(&dir, "tiny_gate", true, false);
    let model = CmfModel::open(&path).unwrap();
    let o1 = O1Cfg::from_spec("all", Some(4), Some(64), Some(0), None).unwrap();
    let fm = FcdModel::from_cmf(&model, &o1).unwrap();
    let mut ts = TrainState::new(&fm);
    let seqs = rand_ids(SEQ + 1, 19);
    let worst = block_fd(
        &fm,
        &mut ts,
        &seqs[..SEQ],
        &seqs[1..],
        SEQ,
        3e-2,
        &|_| true,
    );
    println!("block gradcheck output-gate: worst rel err {worst:.2e}");
    std::fs::remove_dir_all(&dir).ok();
}