Skip to main content

logicaffeine_compile/spatial/
fit.rs

1//! The **fit report** — what a model or a workload needs from an RDU, sized against ONE source of chip
2//! facts ([`RduChipFacts`]) and written down with every number labeled.
3//!
4//! Two reports and a plan:
5//!   * [`gemm_fit`] — an int8 GEMM lowered through the spatial pipeline onto the chip: its arithmetic,
6//!     its working set against the SRAM, the VERIFIED program's census, the certificate that rechecks,
7//!     and the II view on the chip's unit model.
8//!   * [`qwen38_fit`] — Qwen3.8-27B (the forward authored in LOGOS, [`gen_qwen38_lg`]) on the chip: the
9//!     weights by storage, per-layer MACs and arithmetic intensity, the KV cache, the Gated-DeltaNet
10//!     memory and the conv rings, the peak live set of activations, residency tiers, and tensor-parallel
11//!     sharding over a node and a rack of RDUs with the HBM verdict at the model's full context.
12//!   * [`vllm_plan`] — the deployment plan the fit implies for the vendor's vLLM backend: model id, TP,
13//!     dtype, max-model-len, the op list the backend must support, the state each layer keeps.
14//!
15//! ★ DERIVED, NEVER TYPED. Every model number is a function of [`Qwen38Cfg`] and of the authored program's
16//! own declarations and statements — the tensor inventory is read from `gen_qwen38_lg`'s output, the MACs
17//! from its `matmul`/`attention`/`gated delta`/`causal conv` statements, the live set from a liveness walk
18//! over its `Let` chain. Every chip number is a [`Fact`] with its provenance. The generator locks grep this
19//! file and its examples for bare literals; a number a reader cannot trace to the config, the program or
20//! the facts cannot land here.
21//!
22//! ★ NO TIME. Nothing here ran on an RDU. No field is a duration, a rate of ours, or a speed; the vendor's
23//! published peak rates travel only under their `published` label with a citation, and `no_speed_claim`
24//! is true on every artifact. The II block spells modeled cycles on the unit model, never latency.
25
26use std::collections::BTreeMap;
27
28use serde::Serialize;
29use serde_json::{json, Value};
30
31use super::ir::{SpatialProgram, TensorRole};
32use super::lower::rdu::RduSpatialBackend;
33use super::lower::{Census, NeutralArtifact, SpatialArtifact, SpatialBackend};
34use super::pipeline::compile_to_spatial;
35use super::programs::gemm_program;
36use super::rdu_chip::{
37    rdu_ii_report, Fact, Provenance, RduChip, RduChipFacts, RduIiReport, RduUnitModel, ScaleUp,
38};
39use super::target::{precision_bytes, CreditLatency, SpatialTarget};
40use crate::codegen_cuda::qwen38::{gen_qwen38_lg, Qwen38Cfg};
41use crate::codegen_sva::fabric_target::Precision;
42use crate::codegen_sva::pmu::{BankingMode, PmuSpec};
43
44pub const FIT_SCHEMA: &str = "logos.rdu.fit";
45pub const VLLM_PLAN_SCHEMA: &str = "logos.rdu.vllm_plan";
46pub const FIT_VERSION: u32 = 1;
47
48pub const PROVENANCE_MODELED: &str = "modeled";
49pub const PROVENANCE_PUBLISHED: &str = "published";
50pub const PROVENANCE_DERIVED: &str = "derived";
51pub const PROVENANCE_MEASURED: &str = "measured";
52
53/// The HF checkpoint a vLLM run loads (safetensors, bf16).
54pub const QWEN38_MODEL_ID: &str = "Qwen/Qwen3.8-27B";
55/// The HF `model_type`, and the architecture it inherits in vLLM (the hybrid Gated-DeltaNet + attention).
56pub const QWEN38_HF_MODEL_TYPE: &str = "qwen3_5";
57pub const QWEN38_HF_INHERITS: &str = "qwen3_next";
58/// The GGUF our own CUDA lane runs from — NOT what vLLM loads.
59pub const QWEN38_GGUF: &str = "bartowski/Qwen3.8-27B-GGUF (Q8_0)";
60/// The dtype the deployment is sized in.
61pub const DEPLOYMENT_DTYPE: &str = "bfloat16";
62
63pub const GEMM_FIT_PATH: &str = "benchmarks/silicon/rdu_fit_gemm.json";
64pub const QWEN38_FIT_PATH: &str = "benchmarks/silicon/qwen38_sn50.json";
65pub const VLLM_PLAN_PATH: &str = "benchmarks/silicon/qwen38_vllm_plan.json";
66pub const GEMM_GENERATED_BY: &str = "cargo run -p logicaffeine-compile --example emit_rdu_fit_gemm";
67pub const QWEN38_GENERATED_BY: &str = "cargo run -p logicaffeine-compile --example emit_qwen38_rdu";
68
69/// GGUF Q8_0: a block of 32 int8 quants carries one f16 scale.
70pub const Q8_0_BLOCK: usize = 32;
71pub const Q8_0_SCALE_BYTES: usize = 2;
72pub const Q8_0_LAYOUT: &str =
73    "GGUF Q8_0: blocks of 32 int8 quants + one f16 scale (34 bytes per 32 elements)";
74pub const F32_BYTES: usize = 4;
75pub const BF16_BYTES: usize = 2;
76
77/// The GEMM fit's workload, in the chip's native tiles: an 8×8-tile output over 4 k-tiles.
78pub const GEMM_FIT_TILES: (usize, usize, usize) = (8, 8, 4);
79
80pub const HONESTY: &str = "Every number is a model's: sizes from the config and the authored program, the chip from its published facts (cited) or its modeled unit model (labeled). Nothing here ran on an RDU; no field is a time and no throughput is claimed.";
81
82const MIB: usize = 1 << 20;
83const GIB: usize = 1 << 30;
84
85/// The bytes a Q8_0 tensor of `elems` occupies: the quants plus one f16 scale per (partial) block.
86pub fn q8_0_bytes(elems: usize) -> usize {
87    elems + elems.div_ceil(Q8_0_BLOCK) * Q8_0_SCALE_BYTES
88}
89
90/// Pretty JSON with one trailing newline — the byte form every fit artifact is pinned in.
91pub fn render<T: Serialize>(report: &T) -> String {
92    format!(
93        "{}\n",
94        serde_json::to_string_pretty(report).expect("a fit report serializes")
95    )
96}
97
98/// The tensor-parallel width the plan brings: one SambaRack where the vendor tabulates it, else one node.
99pub fn default_tensor_parallel(facts: &RduChipFacts) -> usize {
100    facts
101        .scale_up
102        .map(|s| s.value.chips_per_rack)
103        .unwrap_or(facts.chips_per_node.value)
104}
105
106/// Why a tensor-parallel width does not split a model.
107#[derive(Clone, Debug, PartialEq, Eq)]
108pub struct TpRefusal {
109    pub tp: usize,
110    pub why: String,
111}
112
113/// ★ A TENSOR-PARALLEL WIDTH IS A FACT ABOUT THE MODEL AS MUCH AS THE CHIPS. Megatron splits attention and the
114/// Gated DeltaNet BY HEAD and the FFN and the vocabulary by column, so `tp` must divide the query heads, both GDN
115/// head counts, the FFN width and the vocabulary; the KV heads either split across the chips or replicate over them
116/// evenly (`kv % tp == 0` or `tp % kv == 0`). Any other width is refused by the serving stack before a weight loads —
117/// vLLM: "Total number of attention heads (24) must be divisible by tensor parallel size (16)". The widths the
118/// author does not yet split a layer over — a latent attention, an expert mixture, an attention with biases or sinks —
119/// are refused by its own name for what is missing (`author::check_tp`).
120pub fn admits_tp(spec: &crate::model::spec::ModelSpec, tp: usize) -> Result<(), TpRefusal> {
121    use crate::model::spec::{Ffn, Mixer};
122    let refuse = |why: String| Err(TpRefusal { tp, why });
123    if tp == 0 {
124        return refuse("a tensor-parallel width of zero chips".into());
125    }
126    let counts = |what: &str| -> Vec<usize> {
127        let mut v: Vec<usize> = spec
128            .layers
129            .iter()
130            .filter_map(|l| match (what, &l.mixer, &l.ffn) {
131                ("query heads", Mixer::Attention(a), _) => Some(a.heads),
132                ("Gated DeltaNet key heads", Mixer::GatedDelta(g), _) => Some(g.k_heads),
133                ("Gated DeltaNet value heads", Mixer::GatedDelta(g), _) => Some(g.v_heads),
134                ("FFN columns", _, Ffn::Dense { dff, .. }) => Some(*dff),
135                _ => None,
136            })
137            .collect();
138        v.dedup();
139        v
140    };
141    for what in ["query heads", "Gated DeltaNet key heads", "Gated DeltaNet value heads", "FFN columns"] {
142        if let Some(n) = counts(what).into_iter().find(|n| n % tp != 0) {
143            return refuse(format!("the {n} {what} do not split over {tp} chips"));
144        }
145    }
146    if spec.vocab % tp != 0 {
147        return refuse(format!("the {} vocabulary rows do not split over {tp} chips", spec.vocab));
148    }
149    for l in &spec.layers {
150        if let Mixer::Attention(a) = &l.mixer {
151            if a.kv_heads % tp != 0 && tp % a.kv_heads != 0 {
152                return refuse(format!(
153                    "the {} KV heads neither split over {tp} chips nor replicate over them evenly",
154                    a.kv_heads
155                ));
156            }
157        }
158    }
159    crate::model::author::check_tp(spec, tp).or_else(|e| refuse(format!("{} over {tp} chips is not written yet: {}", spec.arch, e.0)))
160}
161
162/// [`admits_tp`] for the Qwen3.8 configuration the fit report sizes.
163pub fn qwen38_admits_tp(cfg: &Qwen38Cfg, tp: usize) -> Result<(), TpRefusal> {
164    admits_tp(&cfg.spec(), tp)
165}
166
167/// The tensor-parallel width Qwen3.8 is served at on `facts`' chips: the widest the model admits
168/// ([`qwen38_admits_tp`]) within the chips' tensor-parallel domain ([`default_tensor_parallel`]); the rest of the
169/// domain serves more sequences as data-parallel replicas.
170pub fn qwen38_tensor_parallel(cfg: &Qwen38Cfg, facts: &RduChipFacts) -> usize {
171    (1..=default_tensor_parallel(facts))
172        .rev()
173        .find(|tp| qwen38_admits_tp(cfg, *tp).is_ok())
174        .unwrap_or(1)
175}
176
177/// How Qwen3.8 is served on a chip's scale-up domain at one context length — every choice read off the chips' memory
178/// hierarchy and topology and the model's structure, none typed. `provenance: modeled`; no field is a time.
179#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
180pub struct ServingPlan {
181    /// The chips one tensor-parallel group spans ([`qwen38_tensor_parallel`]).
182    pub tensor_parallel: usize,
183    /// The chips of the scale-up domain ([`default_tensor_parallel`]) the group does not span, as whole replicas.
184    pub data_parallel: usize,
185    pub ctx: usize,
186    /// One chip's HBM, its slice of the weights, and ONE sequence's carried state on it at `ctx`: the KV cache (sharded
187    /// over the KV heads, replicated beyond them), the Gated-DeltaNet memory and the conv rings.
188    pub hbm_bytes_per_chip: usize,
189    pub weights_per_chip_bytes: usize,
190    pub sequence_bytes_per_chip: usize,
191    /// The most sequences a replica holds at once: their carried state beside the weights in every chip's HBM.
192    pub max_sequences_per_replica: usize,
193    pub max_sequences: usize,
194    /// One chip's SRAM, one sequence's per-step activation working set, and the most sequences whose working set —
195    /// with their recurrent state — stays on-chip for a step.
196    pub sram_bytes_per_chip: usize,
197    pub activation_bytes_per_sequence: usize,
198    pub sequences_resident_in_sram: usize,
199    pub provenance: &'static str,
200    pub no_speed_claim: bool,
201}
202
203/// Qwen3.8's serving plan on `facts`' chips at context `ctx`: the tensor-parallel width the model's heads admit within
204/// the scale-up domain, the rest of the domain as data-parallel replicas, and how many sequences a replica holds —
205/// HBM-bound by each sequence's carried state beside the chip's weight slice, SRAM-resident as far as each
206/// sequence's per-step working set allows. The per-chip bytes are the fit report's own ([`qwen38_fit`]).
207pub fn qwen38_serving_plan(
208    cfg: &Qwen38Cfg,
209    facts: &RduChipFacts,
210    ctx: usize,
211) -> Result<ServingPlan, String> {
212    let tp = qwen38_tensor_parallel(cfg, facts);
213    let domain = default_tensor_parallel(facts);
214    let FitReport::Qwen38(fit) = qwen38_fit(cfg, facts, tp, ctx)? else {
215        return Err("the Qwen3.8 fit is a Qwen3.8 report".into());
216    };
217    let t = &fit.tensor_parallel;
218    let sequence = t.state_bytes_per_chip + t.kv_cache_at_ctx.per_chip_bytes;
219    let free = t.hbm_bytes_per_chip.checked_sub(t.weights_per_chip_bytes).ok_or_else(|| {
220        format!("a chip's {} bytes of HBM do not hold its {} bytes of weights at tensor parallel {tp}", t.hbm_bytes_per_chip, t.weights_per_chip_bytes)
221    })?;
222    let per_replica = free / sequence.max(1);
223    if per_replica == 0 {
224        return Err(format!("a chip's HBM holds its weights but not one sequence's {sequence} bytes of state at context {ctx}"));
225    }
226    let r = &fit.residency;
227    let per_sequence_on_chip =
228        r.activations_per_token_bytes + r.gdn_state_bytes_per_chip + r.conv_state_bytes_per_chip;
229    Ok(ServingPlan {
230        tensor_parallel: tp,
231        data_parallel: domain / tp,
232        ctx,
233        hbm_bytes_per_chip: t.hbm_bytes_per_chip,
234        weights_per_chip_bytes: t.weights_per_chip_bytes,
235        sequence_bytes_per_chip: sequence,
236        max_sequences_per_replica: per_replica,
237        max_sequences: per_replica * (domain / tp),
238        sram_bytes_per_chip: r.sram_bytes,
239        activation_bytes_per_sequence: r.activations_per_token_bytes,
240        sequences_resident_in_sram: r.sram_bytes / per_sequence_on_chip.max(1),
241        provenance: PROVENANCE_MODELED,
242        no_speed_claim: true,
243    })
244}
245
246/// The GEMM fit's `(m, n, k)` on a target: [`GEMM_FIT_TILES`] in that target's tile geometry.
247pub fn gemm_fit_shape(target: &SpatialTarget) -> (usize, usize, usize) {
248    let (mt, nt, kt) = GEMM_FIT_TILES;
249    (
250        mt * target.tile.rows,
251        nt * target.tile.cols,
252        kt * target.tile.cols,
253    )
254}
255
256// ── Labeled numbers ─────────────────────────────────────────────────────────────────────────────
257
258/// A chip number with its provenance spelled the way the fit artifacts spell it (`published`/`modeled`).
259#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
260pub struct Cited<T> {
261    pub value: T,
262    pub provenance: &'static str,
263    pub cite: &'static str,
264}
265
266fn label(p: Provenance) -> &'static str {
267    match p {
268        Provenance::Published => PROVENANCE_PUBLISHED,
269        Provenance::Derived => PROVENANCE_DERIVED,
270        Provenance::Measured => PROVENANCE_MEASURED,
271        Provenance::Modeled => PROVENANCE_MODELED,
272    }
273}
274
275fn cited<T: Copy>(f: Fact<T>) -> Cited<T> {
276    Cited {
277        value: f.value,
278        provenance: label(f.provenance),
279        cite: f.cite,
280    }
281}
282
283fn cited_map<T: Copy, U>(f: Fact<T>, map: impl Fn(T) -> U) -> Cited<U> {
284    Cited {
285        value: map(f.value),
286        provenance: label(f.provenance),
287        cite: f.cite,
288    }
289}
290
291// ── The chip block ──────────────────────────────────────────────────────────────────────────────
292
293/// The chip as the fit sees it: every vendor number under its own label, the derived per-PCU budget
294/// modeled, the descriptor's modeled ledger and the sources.
295#[derive(Clone, Debug, PartialEq, Serialize)]
296pub struct ChipBlock {
297    pub part: &'static str,
298    pub chip: RduChip,
299    pub target: &'static str,
300    pub peak_bf16_tflops: Cited<u32>,
301    pub peak_fp8_tflops: Option<Cited<u32>>,
302    pub sram_bytes: Cited<usize>,
303    pub hbm_bytes: Cited<usize>,
304    pub ddr_bytes_max: Cited<usize>,
305    pub node_nm: Cited<u32>,
306    pub dies: Cited<u32>,
307    pub chips_per_node: Cited<usize>,
308    pub chips_per_rack: Option<Cited<usize>>,
309    /// The tensor-parallel width the plan brings: the rack where tabulated, else the node.
310    pub tensor_parallel_width: Cited<usize>,
311    pub scale_up: Option<Cited<ScaleUp>>,
312    pub pcu_count: Cited<usize>,
313    pub pmu_count: Cited<usize>,
314    pub local_mem_bytes_per_pcu: usize,
315    pub witnessed_precisions: Vec<Precision>,
316    pub witness_pending_precisions: Vec<Precision>,
317    pub modeled_fields: Vec<String>,
318    pub cites: &'static [&'static str],
319    pub provenance: &'static str,
320}
321
322impl ChipBlock {
323    pub fn of(facts: &RduChipFacts) -> ChipBlock {
324        let rack = facts.scale_up.map(|s| cited_map(s, |v| v.chips_per_rack));
325        ChipBlock {
326            part: facts.part,
327            chip: facts.chip,
328            target: facts.chip.target_name(),
329            peak_bf16_tflops: cited(facts.peak_bf16_tflops),
330            peak_fp8_tflops: facts.peak_fp8_tflops.map(cited),
331            sram_bytes: cited_map(facts.sram_mib, |v| v * MIB),
332            hbm_bytes: cited_map(facts.hbm_gib, |v| v * GIB),
333            ddr_bytes_max: cited_map(facts.ddr_gib_max, |v| v * GIB),
334            node_nm: cited(facts.node_nm),
335            dies: cited(facts.dies),
336            chips_per_node: cited(facts.chips_per_node),
337            chips_per_rack: rack,
338            tensor_parallel_width: rack.unwrap_or_else(|| cited(facts.chips_per_node)),
339            scale_up: facts.scale_up.map(cited),
340            pcu_count: cited(facts.pcu_count),
341            pmu_count: cited(facts.pmu_count),
342            local_mem_bytes_per_pcu: facts.local_mem_bytes_per_pcu(),
343            witnessed_precisions: facts.witnessed.to_vec(),
344            witness_pending_precisions: facts.witness_pending.to_vec(),
345            modeled_fields: facts.modeled_fields(),
346            cites: facts.cites,
347            provenance: PROVENANCE_MODELED,
348        }
349    }
350}
351
352// ── The II block ────────────────────────────────────────────────────────────────────────────────
353
354/// The unit model with every value under the fit's labels.
355#[derive(Clone, Debug, PartialEq, Serialize)]
356pub struct UnitModelBlock {
357    pub pcu_stages: Cited<usize>,
358    pub pcu_lanes: Cited<usize>,
359    pub pcu_envelope: Cited<(usize, usize)>,
360    pub datapath_bits: Cited<u32>,
361    pub acc_tiles: Cited<usize>,
362    pub mesh_columns: Cited<usize>,
363    pub fusion_window: Cited<usize>,
364    pub counter_nest: Cited<usize>,
365    pub pmu: Cited<PmuSpec>,
366    pub credit: Option<Cited<CreditLatency>>,
367}
368
369impl UnitModelBlock {
370    pub fn of(um: &RduUnitModel) -> UnitModelBlock {
371        UnitModelBlock {
372            pcu_stages: cited(um.pcu_stages),
373            pcu_lanes: cited(um.pcu_lanes),
374            pcu_envelope: cited(um.pcu_envelope),
375            datapath_bits: cited(um.datapath_bits),
376            acc_tiles: cited(um.acc_tiles),
377            mesh_columns: cited(um.mesh_columns),
378            fusion_window: cited(um.fusion_window),
379            counter_nest: cited(um.counter_nest),
380            pmu: cited(um.pmu),
381            credit: um.credit.map(cited),
382        }
383    }
384}
385
386/// One compute stage on the unit model: its IIs and the cycles to its result.
387#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
388pub struct IiStageRow {
389    pub stage: usize,
390    pub name: String,
391    pub op: &'static str,
392    pub reduce: bool,
393    pub carried: bool,
394    pub lanes: usize,
395    pub cells: usize,
396    pub resource_ii: usize,
397    pub recurrence_ii: usize,
398    pub memory_ii: usize,
399    /// PCU pipeline depth, plus the reduction tree when the stage folds an axis.
400    pub result_cycles: usize,
401}
402
403/// One IR edge's credit loop on the unit model.
404#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
405pub struct IiCreditRow {
406    pub edge: usize,
407    pub from: usize,
408    pub to: usize,
409    pub hops: usize,
410    pub forward_cycles: usize,
411    pub backward_cycles: usize,
412    pub n_buffer_depth: usize,
413    pub credit_depth: usize,
414    pub depth: usize,
415}
416
417/// The II view of a placed program — [`rdu_ii_report`] with cycles spelled as cycles.
418#[derive(Clone, Debug, PartialEq, Serialize)]
419pub struct IiBlock {
420    pub chip: RduChip,
421    pub program: String,
422    /// What program the block is the view of, and why.
423    pub source: String,
424    pub unit_model: UnitModelBlock,
425    pub banking: BankingMode,
426    pub credit_declared: bool,
427    pub pipeline_ii: usize,
428    pub stages: Vec<IiStageRow>,
429    pub buffer_depths: Vec<(usize, usize, usize)>,
430    pub credit_depths: Vec<IiCreditRow>,
431    pub cycles_are: &'static str,
432    pub provenance: &'static str,
433}
434
435impl IiBlock {
436    pub fn of(report: &RduIiReport, source: String) -> IiBlock {
437        IiBlock {
438            chip: report.chip,
439            program: report.program.clone(),
440            source,
441            unit_model: UnitModelBlock::of(&report.unit_model),
442            banking: report.banking,
443            credit_declared: report.credit_declared,
444            pipeline_ii: report.pipeline_ii,
445            stages: report
446                .stages
447                .iter()
448                .map(|r| IiStageRow {
449                    stage: r.stage,
450                    name: r.name.clone(),
451                    op: r.op,
452                    reduce: r.reduce,
453                    carried: r.carried,
454                    lanes: r.lanes,
455                    cells: r.timing.cells,
456                    resource_ii: r.timing.resource_ii,
457                    recurrence_ii: r.timing.recurrence_ii,
458                    memory_ii: r.timing.memory_ii,
459                    result_cycles: r.timing.latency,
460                })
461                .collect(),
462            buffer_depths: report.buffer_depths.clone(),
463            credit_depths: report
464                .credit_depths
465                .iter()
466                .map(|c| IiCreditRow {
467                    edge: c.edge,
468                    from: c.from,
469                    to: c.to,
470                    hops: c.hops,
471                    forward_cycles: c.forward_latency,
472                    backward_cycles: c.backward_latency,
473                    n_buffer_depth: c.n_buffer_depth,
474                    credit_depth: c.credit_depth,
475                    depth: c.depth,
476                })
477                .collect(),
478            cycles_are: "cycles of the modeled unit model (Plasticine-inherited PCU/PMU), never a measurement of the chip",
479            provenance: report.provenance,
480        }
481    }
482}
483
484// ── Program + certificate summaries ─────────────────────────────────────────────────────────────
485
486#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
487pub struct ProgramSummary {
488    pub name: String,
489    pub phase: String,
490    pub tensors: usize,
491    pub stages: usize,
492    pub buffers: usize,
493    pub semaphores: usize,
494    pub edges: usize,
495    pub census: Census,
496    pub provenance: &'static str,
497}
498
499impl ProgramSummary {
500    pub fn of(p: &SpatialProgram, census: &Census) -> ProgramSummary {
501        ProgramSummary {
502            name: p.name.clone(),
503            phase: format!("{:?}", p.phase()),
504            tensors: p.tensors.len(),
505            stages: p.stages.len(),
506            buffers: p.buffers.len(),
507            semaphores: p.semaphores.len(),
508            edges: p.edges.len(),
509            census: census.clone(),
510            provenance: PROVENANCE_MODELED,
511        }
512    }
513}
514
515#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
516pub struct CertificateSummary {
517    pub schedule_digest: u64,
518    pub digest_hex: String,
519    /// Recomputed here against the program the certificate travels with.
520    pub rechecked: bool,
521    pub places: usize,
522    pub transitions: usize,
523    pub provenance: &'static str,
524}
525
526impl CertificateSummary {
527    pub fn of(artifact: &NeutralArtifact, p: &SpatialProgram) -> CertificateSummary {
528        let cert = &artifact.certificate;
529        CertificateSummary {
530            schedule_digest: cert.schedule_digest,
531            digest_hex: format!("{:016x}", cert.schedule_digest),
532            rechecked: cert.recheck(p),
533            places: cert.places(),
534            transitions: cert.transitions(),
535            provenance: PROVENANCE_MODELED,
536        }
537    }
538}
539
540/// Lower `source` on `facts`' descriptor through the RDU lane: the verified program and its SDK-gated artifact.
541fn lower_on(
542    source: &str,
543    facts: &RduChipFacts,
544) -> Result<(SpatialProgram, NeutralArtifact), String> {
545    let target = facts.spatial_target();
546    let program = compile_to_spatial(source, &target)
547        .map_err(|e| format!("the {} cannot host it: {e:?}", target.name))?;
548    match RduSpatialBackend::with_facts(facts.clone()).lower(&program) {
549        Ok(SpatialArtifact::SdkGated(a)) => Ok((program, a)),
550        Ok(other) => Err(format!("the RDU lane is SDK-gated, got {other:?}")),
551        Err(e) => Err(format!("the RDU lane refused the verified program: {e:?}")),
552    }
553}
554
555// ── The GEMM fit ────────────────────────────────────────────────────────────────────────────────
556
557#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
558pub struct GemmTiles {
559    pub rows_t: usize,
560    pub cols_t: usize,
561    pub k_tiles: usize,
562}
563
564#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
565pub struct GemmWorkload {
566    pub kind: &'static str,
567    pub m: usize,
568    pub n: usize,
569    pub k: usize,
570    pub macs: usize,
571    pub tile: (usize, usize),
572    pub tiles: GemmTiles,
573    pub source: &'static str,
574    pub provenance: &'static str,
575}
576
577#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
578pub struct GemmOperand {
579    pub name: String,
580    pub role: String,
581    pub rows: usize,
582    pub cols: usize,
583    pub dtype: Precision,
584    pub bytes: usize,
585}
586
587#[derive(Clone, Debug, PartialEq, Serialize)]
588pub struct GemmArithmetic {
589    pub operands: Vec<GemmOperand>,
590    pub bytes: usize,
591    pub arithmetic_intensity_macs_per_byte: f64,
592    pub provenance: &'static str,
593}
594
595#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
596pub struct GemmResidency {
597    pub working_set_bytes: usize,
598    pub sram_bytes: usize,
599    pub sram_fit: bool,
600    pub local_mem_bytes_per_pcu: usize,
601    pub fits_one_pcu: bool,
602    pub buffer_bytes_on_mesh: usize,
603    pub sites_used: usize,
604    pub sites_available: usize,
605    pub provenance: &'static str,
606}
607
608#[derive(Clone, Debug, PartialEq, Serialize)]
609pub struct GemmFit {
610    pub schema: &'static str,
611    pub version: u32,
612    #[serde(rename = "_generated_by")]
613    pub generated_by: &'static str,
614    pub provenance: &'static str,
615    pub no_speed_claim: bool,
616    pub honesty: &'static str,
617    pub workload: GemmWorkload,
618    pub chip: ChipBlock,
619    pub arithmetic: GemmArithmetic,
620    pub residency: GemmResidency,
621    pub program: ProgramSummary,
622    pub certificate: CertificateSummary,
623    pub gated: &'static str,
624    pub ii_report: IiBlock,
625}
626
627/// The int8 GEMM `C(m×n) = A(m×k)·B(k×n)` lowered onto `facts`' chip.
628pub fn gemm_fit(m: usize, n: usize, k: usize, facts: &RduChipFacts) -> Result<FitReport, String> {
629    let source = gemm_program(m, n, k);
630    let (program, artifact) = lower_on(&source, facts)?;
631    let target = facts.spatial_target();
632    let macs = m * n * k;
633    let output = program
634        .tensors
635        .iter()
636        .find(|t| t.role == TensorRole::Output)
637        .ok_or("the GEMM has an output tensor")?;
638    let operands: Vec<GemmOperand> = program
639        .tensors
640        .iter()
641        .map(|t| GemmOperand {
642            name: t.name.clone(),
643            role: format!("{:?}", t.role),
644            rows: t.elems.rows,
645            cols: t.elems.cols,
646            dtype: t.dtype,
647            bytes: t.elems.rows * t.elems.cols * precision_bytes(t.dtype),
648        })
649        .collect();
650    let bytes: usize = operands.iter().map(|o| o.bytes).sum();
651    let buffer_bytes_on_mesh: usize = program
652        .buffers
653        .iter()
654        .map(|b| program.buffer_bytes(b))
655        .sum();
656    let sites_available = target.sites.x * target.sites.y;
657    let ii = rdu_ii_report(&program, facts);
658    Ok(FitReport::Gemm(GemmFit {
659        schema: FIT_SCHEMA,
660        version: FIT_VERSION,
661        generated_by: GEMM_GENERATED_BY,
662        provenance: PROVENANCE_MODELED,
663        no_speed_claim: true,
664        honesty: HONESTY,
665        workload: GemmWorkload {
666            kind: "int8_gemm",
667            m,
668            n,
669            k,
670            macs,
671            tile: (program.tile.rows, program.tile.cols),
672            tiles: GemmTiles { rows_t: output.tiles.rows_t, cols_t: output.tiles.cols_t, k_tiles: k.div_ceil(program.tile.cols) },
673            source: "spatial::programs::gemm_program — the canonical LOGOS matmul with deterministic fills, lifted by the tensor recognizer",
674            provenance: PROVENANCE_MODELED,
675        },
676        chip: ChipBlock::of(facts),
677        arithmetic: GemmArithmetic { operands, bytes, arithmetic_intensity_macs_per_byte: macs as f64 / bytes as f64, provenance: PROVENANCE_MODELED },
678        residency: GemmResidency {
679            working_set_bytes: bytes,
680            sram_bytes: facts.sram_bytes(),
681            sram_fit: bytes <= facts.sram_bytes(),
682            local_mem_bytes_per_pcu: facts.local_mem_bytes_per_pcu(),
683            fits_one_pcu: bytes <= facts.local_mem_bytes_per_pcu(),
684            buffer_bytes_on_mesh,
685            sites_used: artifact.census.sites,
686            sites_available,
687            provenance: PROVENANCE_MODELED,
688        },
689        program: ProgramSummary::of(&program, &artifact.census),
690        certificate: CertificateSummary::of(&artifact, &program),
691        gated: artifact.reason,
692        ii_report: IiBlock::of(&ii, format!("the GEMM itself ({}), placed on the {}", program.name, target.name)),
693    }))
694}
695
696// ── Qwen3.8: the authored program, read back ────────────────────────────────────────────────────
697
698#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
699#[serde(rename_all = "snake_case")]
700pub enum Storage {
701    #[serde(rename = "q8_0")]
702    Q8_0,
703    F32,
704    Bf16,
705}
706
707#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
708#[serde(rename_all = "snake_case")]
709pub enum TensorClass {
710    Weight,
711    State,
712    RopeTable,
713    Activation,
714}
715
716/// One tensor of the authored forward, as it is declared there.
717#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
718pub struct TensorLine {
719    pub name: String,
720    pub rows: usize,
721    pub cols: usize,
722    pub storage: Storage,
723    pub class: TensorClass,
724    pub layer: Option<usize>,
725    pub bytes: usize,
726}
727
728impl TensorLine {
729    pub fn elems(&self) -> usize {
730        self.rows * self.cols
731    }
732    pub fn is_matrix(&self) -> bool {
733        self.rows > 1 && self.cols > 1
734    }
735}
736
737/// `Let V be a R by C tensor of Real.` → `(V, R, C)`.
738fn tensor_declaration(line: &str) -> Option<(&str, usize, usize)> {
739    let rest = line.strip_prefix("Let ")?;
740    let (name, shape) = rest.split_once(" be a ")?;
741    let shape = shape.strip_suffix(" tensor of Real.")?;
742    let (rows, cols) = shape.split_once(" by ")?;
743    Some((name, rows.parse().ok()?, cols.parse().ok()?))
744}
745
746/// The layer a name belongs to: its trailing digits (`Wq3` → 3; `Wout`, `x`, `RC` → none).
747fn layer_of(name: &str) -> Option<usize> {
748    let digits = name.trim_end_matches(|c: char| !c.is_ascii_digit()).len();
749    let start = name[..digits]
750        .rfind(|c: char| !c.is_ascii_digit())
751        .map_or(0, |i| i + 1);
752    name[start..digits]
753        .parse()
754        .ok()
755        .filter(|_| digits > 0 && start < digits)
756}
757
758fn classify_f32(name: &str) -> TensorClass {
759    if name.starts_with("St") || name.starts_with("Cs") {
760        TensorClass::State
761    } else if name == "RC" || name == "RS" {
762        TensorClass::RopeTable
763    } else {
764        TensorClass::Weight
765    }
766}
767
768fn bytes_of(storage: Storage, elems: usize) -> usize {
769    match storage {
770        Storage::Q8_0 => q8_0_bytes(elems),
771        Storage::F32 => elems * F32_BYTES,
772        Storage::Bf16 => elems * BF16_BYTES,
773    }
774}
775
776/// Every tensor the authored forward declares — read from [`gen_qwen38_lg`]'s own output and classified by
777/// the manifests it returns (Q8_0 matrices vs f32 tensors; the hidden state is the one undeclared-in-the-
778/// manifests activation) — plus the embedding table the decode step gathers its input row from.
779pub fn qwen38_tensors(cfg: &Qwen38Cfg) -> Vec<TensorLine> {
780    let (source, f32w, q8w) = gen_qwen38_lg(cfg, "w");
781    let mut out = Vec::new();
782    for line in source.lines() {
783        let Some((name, rows, cols)) = tensor_declaration(line) else {
784            continue;
785        };
786        let (storage, class) = if q8w.contains_key(name) {
787            (Storage::Q8_0, TensorClass::Weight)
788        } else if f32w.contains_key(name) {
789            (Storage::F32, classify_f32(name))
790        } else {
791            (Storage::Bf16, TensorClass::Activation)
792        };
793        out.push(TensorLine {
794            name: name.to_string(),
795            rows,
796            cols,
797            storage,
798            class,
799            layer: layer_of(name),
800            bytes: bytes_of(storage, rows * cols),
801        });
802    }
803    out.push(TensorLine {
804        name: "emb".to_string(),
805        rows: cfg.vocab,
806        cols: cfg.d,
807        storage: Storage::Q8_0,
808        class: TensorClass::Weight,
809        layer: None,
810        bytes: bytes_of(Storage::Q8_0, cfg.vocab * cfg.d),
811    });
812    out
813}
814
815/// One `Let name be <expr>.` op statement of the authored forward (declarations excluded).
816struct OpStmt {
817    name: String,
818    expr: String,
819    layer: Option<usize>,
820}
821
822fn op_statements(source: &str) -> Vec<OpStmt> {
823    source
824        .lines()
825        .filter(|l| tensor_declaration(l).is_none())
826        .filter_map(|l| {
827            let rest = l.strip_prefix("Let ")?;
828            let (name, expr) = rest.split_once(" be ")?;
829            let expr = expr.strip_suffix('.')?;
830            Some(OpStmt {
831                name: name.to_string(),
832                expr: expr.to_string(),
833                layer: layer_of(name),
834            })
835        })
836        .collect()
837}
838
839fn idents(expr: &str) -> impl Iterator<Item = &str> {
840    expr.split(|c: char| !(c.is_alphanumeric() || c == '_'))
841        .filter(|w| !w.is_empty())
842}
843
844/// The operand after `of`, `over … and`, or the left operand of an infix op — the one whose shape the result
845/// takes — except `gated delta`, whose result is as wide as its VALUE operand (after `and`).
846fn result_shape(expr: &str, shapes: &BTreeMap<String, (usize, usize)>) -> Option<(usize, usize)> {
847    let shape = |name: &str| shapes.get(name).copied();
848    if let Some((a, w)) = expr.split_once(" matmul ") {
849        return Some((shape(a.trim())?.0, shape(w.trim())?.1));
850    }
851    if let Some(rest) = expr.strip_prefix("gated delta of ") {
852        let (_, after) = rest.split_once(" and ")?;
853        return shape(after.split_whitespace().next()?);
854    }
855    if let Some((_, rest)) = expr.split_once(" of ") {
856        return shape(rest.split_whitespace().next()?);
857    }
858    shape(expr.split_whitespace().next()?)
859}
860
861/// The MACs one op statement costs per token, at context position `ctx`, from its operands' shapes.
862#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
863struct OpMacs {
864    weight: usize,
865    attention: usize,
866    gdn: usize,
867    conv: usize,
868}
869
870fn op_macs(expr: &str, shapes: &BTreeMap<String, (usize, usize)>, ctx: usize) -> OpMacs {
871    let shape = |name: &str| shapes.get(name).copied().unwrap_or((0, 0));
872    let mut m = OpMacs::default();
873    if let Some((a, w)) = expr.split_once(" matmul ") {
874        let (ar, _) = shape(a.trim());
875        let (wr, wc) = shape(w.trim());
876        m.weight = ar * wr * wc;
877    } else if let Some(rest) = expr.strip_prefix("attention of ") {
878        let q = rest.split_whitespace().next().unwrap_or("");
879        let (qr, qc) = shape(q);
880        m.attention = 2 * qr * qc * ctx;
881    } else if let Some(rest) = expr.strip_prefix("gated delta of ") {
882        let state = rest
883            .split(" state ")
884            .nth(1)
885            .and_then(|s| s.split_whitespace().next())
886            .unwrap_or("");
887        let (sr, sc) = shape(state);
888        m.gdn = 4 * sr * sc;
889    } else if let Some(rest) = expr.strip_prefix("causal conv of ") {
890        let a = rest.split_whitespace().next().unwrap_or("");
891        let taps: usize = rest
892            .split(" over ")
893            .nth(1)
894            .and_then(|s| s.split_whitespace().next())
895            .and_then(|t| t.parse().ok())
896            .unwrap_or(0);
897        let (ar, ac) = shape(a);
898        m.conv = taps * ar * ac;
899    }
900    m
901}
902
903/// The peak live set of activations (bf16) per statement index, from a liveness walk over the op chain:
904/// an activation is live from its definition to its last use.
905fn live_set_peaks(
906    stmts: &[OpStmt],
907    shapes: &BTreeMap<String, (usize, usize)>,
908    hidden: &str,
909) -> Vec<usize> {
910    let mut size: BTreeMap<&str, usize> = BTreeMap::new();
911    let mut def: BTreeMap<&str, usize> = BTreeMap::new();
912    let mut last: BTreeMap<&str, usize> = BTreeMap::new();
913    if let Some(&(r, c)) = shapes.get(hidden) {
914        size.insert(hidden, r * c * BF16_BYTES);
915        def.insert(hidden, 0);
916        last.insert(hidden, 0);
917    }
918    for (i, s) in stmts.iter().enumerate() {
919        let (r, c) = shapes.get(&s.name).copied().unwrap_or((0, 0));
920        size.insert(&s.name, r * c * BF16_BYTES);
921        def.insert(&s.name, i);
922        last.entry(&s.name).or_insert(i);
923        for used in idents(&s.expr) {
924            if size.contains_key(used) && used != s.name.as_str() {
925                last.insert(used, i);
926            }
927        }
928    }
929    (0..stmts.len())
930        .map(|i| {
931            size.iter()
932                .filter(|(n, _)| def[*n] <= i && last[*n] >= i)
933                .map(|(_, b)| *b)
934                .sum()
935        })
936        .collect()
937}
938
939// ── The Qwen3.8 fit ─────────────────────────────────────────────────────────────────────────────
940
941#[derive(Clone, Debug, PartialEq, Serialize)]
942pub struct CfgBlock {
943    pub d: usize,
944    pub layers: usize,
945    pub gdn_layers: usize,
946    pub attn_layers: usize,
947    pub full_attn_every: usize,
948    pub dff: usize,
949    pub vocab: usize,
950    pub h: usize,
951    pub kv: usize,
952    pub hd: usize,
953    pub rope_dim: usize,
954    pub rope_theta: f64,
955    pub rms_eps: f64,
956    pub gdn_k_heads: usize,
957    pub gdn_v_heads: usize,
958    pub gdn_head_dim: usize,
959    pub conv_taps: usize,
960    pub ctx_len: usize,
961    pub provenance: &'static str,
962}
963
964impl CfgBlock {
965    pub fn of(c: &Qwen38Cfg) -> CfgBlock {
966        CfgBlock {
967            d: c.d,
968            layers: c.layers,
969            gdn_layers: c.layers - c.attn_layers(),
970            attn_layers: c.attn_layers(),
971            full_attn_every: c.full_attn_every,
972            dff: c.dff,
973            vocab: c.vocab,
974            h: c.h,
975            kv: c.kv,
976            hd: c.hd,
977            rope_dim: c.rope_dim,
978            rope_theta: c.rope_theta,
979            rms_eps: c.rms_eps,
980            gdn_k_heads: c.gdn_k_heads,
981            gdn_v_heads: c.gdn_v_heads,
982            gdn_head_dim: c.gdn_head_dim,
983            conv_taps: c.conv_taps,
984            ctx_len: c.ctx_len,
985            provenance: PROVENANCE_MODELED,
986        }
987    }
988}
989
990#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
991pub struct EmbeddingBlock {
992    pub rows: usize,
993    pub cols: usize,
994    pub storage: Storage,
995    pub bytes: usize,
996    pub note: &'static str,
997}
998
999#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
1000pub struct WeightsBlock {
1001    pub params: usize,
1002    pub tensors: usize,
1003    pub q8_0_tensors: usize,
1004    pub f32_tensors: usize,
1005    /// Σ Q8_0 matrices: elems + one f16 scale per 32.
1006    pub q8_0_bytes: usize,
1007    /// Σ f32 tensors × 4.
1008    pub f32_bytes: usize,
1009    /// The on-disk Q8_0 checkpoint, minus what is excluded.
1010    pub gguf_bytes: usize,
1011    /// The deployment dtype: every parameter × 2.
1012    pub bf16_bytes: usize,
1013    pub embedding: EmbeddingBlock,
1014    pub excluded: Vec<&'static str>,
1015    pub q8_0_layout: &'static str,
1016    pub provenance: &'static str,
1017}
1018
1019#[derive(Clone, Debug, PartialEq, Serialize)]
1020pub struct ModelBlock {
1021    pub id: &'static str,
1022    pub hf_model_type: &'static str,
1023    pub inherits: &'static str,
1024    pub deployment_dtype: &'static str,
1025    pub gguf: &'static str,
1026    pub config: CfgBlock,
1027    pub weights: WeightsBlock,
1028    pub tensors: Vec<TensorLine>,
1029    pub provenance: &'static str,
1030}
1031
1032#[derive(Clone, Debug, PartialEq, Serialize)]
1033pub struct LayerRow {
1034    pub layer: usize,
1035    pub kind: &'static str,
1036    pub weight_params: usize,
1037    pub weight_bytes_bf16: usize,
1038    pub weight_bytes_gguf: usize,
1039    /// One MAC per weight element of every `matmul`, at batch 1.
1040    pub weight_macs: usize,
1041    /// Scores and the weighted V-sum over `ctx` positions, every head.
1042    pub attention_macs_at_ctx: usize,
1043    /// Four ghd² passes over every v-head's ghd×ghd memory: decay, S·k, the rank-1 update, S·q.
1044    pub gdn_recurrence_macs: usize,
1045    /// Taps × the q|k|v channels.
1046    pub conv_macs: usize,
1047    pub macs: usize,
1048    pub arithmetic_intensity_macs_per_byte: f64,
1049    pub activations_peak_bytes: usize,
1050    pub provenance: &'static str,
1051}
1052
1053#[derive(Clone, Debug, PartialEq, Serialize)]
1054pub struct LmHeadRow {
1055    pub weight_params: usize,
1056    pub weight_bytes_bf16: usize,
1057    pub weight_bytes_gguf: usize,
1058    pub macs: usize,
1059    pub arithmetic_intensity_macs_per_byte: f64,
1060    pub provenance: &'static str,
1061}
1062
1063#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
1064pub struct TotalsBlock {
1065    pub macs_per_token_at_ctx: usize,
1066    pub weight_macs: usize,
1067    pub attention_macs_at_ctx: usize,
1068    pub gdn_recurrence_macs: usize,
1069    pub conv_macs: usize,
1070    pub lm_head_macs: usize,
1071    pub provenance: &'static str,
1072}
1073
1074#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
1075pub struct KvAtCtx {
1076    pub ctx: usize,
1077    pub bytes: usize,
1078    /// The KV cache shards over kv heads, never wider.
1079    pub kv_shards: usize,
1080    /// Chips holding the same shard once TP exceeds the kv heads.
1081    pub replication: usize,
1082    pub per_chip_bytes: usize,
1083}
1084
1085#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
1086pub struct TpRow {
1087    pub chips: usize,
1088    pub sharded_weight_bytes_per_chip: usize,
1089    pub replicated_weight_bytes: usize,
1090    pub weights_per_chip_bytes: usize,
1091    pub state_bytes_per_chip: usize,
1092    pub kv_per_chip_bytes: usize,
1093    pub hbm_need_bytes: usize,
1094    pub hbm_bytes: usize,
1095    pub hbm_fit: bool,
1096    pub hbm_headroom_bytes: i64,
1097    pub max_ctx_in_hbm: usize,
1098}
1099
1100#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
1101pub struct TensorParallelBlock {
1102    pub chips: usize,
1103    pub dtype: &'static str,
1104    pub sharded_weight_bytes_per_chip: usize,
1105    pub replicated_weight_bytes: usize,
1106    pub weights_per_chip_bytes: usize,
1107    pub state_bytes_per_chip: usize,
1108    pub kv_cache_at_ctx: KvAtCtx,
1109    pub hbm_bytes_per_chip: usize,
1110    pub hbm_need_bytes: usize,
1111    pub hbm_fit: bool,
1112    pub hbm_headroom_bytes: i64,
1113    pub max_ctx_in_hbm: usize,
1114    pub ladder: Vec<TpRow>,
1115    pub sharding: &'static str,
1116    pub provenance: &'static str,
1117}
1118
1119#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
1120pub struct KvTier {
1121    pub sram_until_ctx: usize,
1122    pub then: &'static str,
1123}
1124
1125#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
1126pub struct Tiers {
1127    pub weights: &'static str,
1128    pub activations: &'static str,
1129    pub gdn_state: &'static str,
1130    pub conv_state: &'static str,
1131    pub kv_cache: KvTier,
1132}
1133
1134#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
1135pub struct ResidencyBlock {
1136    pub sram_bytes: usize,
1137    /// The largest live set of a layer, bf16 — from a liveness walk over the authored forward.
1138    pub activations_per_token_bytes: usize,
1139    pub activations_peak_layer: usize,
1140    pub activations_peak_layer_kind: &'static str,
1141    pub activations_fit_sram: bool,
1142    /// The live set at the LM head — the logits row beside the normed hidden state, bf16 — and its
1143    /// vocab-parallel share per chip.
1144    pub lm_head_live_set_bytes: usize,
1145    pub lm_head_live_set_per_chip_bytes: usize,
1146    pub gdn_state_bytes: usize,
1147    pub gdn_state_bytes_per_chip: usize,
1148    pub conv_state_bytes: usize,
1149    pub conv_state_bytes_per_chip: usize,
1150    pub kv_cache_bytes_per_token: usize,
1151    pub kv_cache_bytes_per_token_per_chip: usize,
1152    /// Activations + the GDN memory + the conv rings on one chip.
1153    pub recurrent_working_set_per_chip_bytes: usize,
1154    /// The recurrent working set, or the LM head's moment, whichever is larger, fits the SRAM.
1155    pub sram_fit_per_chip: bool,
1156    pub tiers: Tiers,
1157    pub provenance: &'static str,
1158}
1159
1160#[derive(Clone, Debug, PartialEq, Serialize)]
1161pub struct Qwen38Fit {
1162    pub schema: &'static str,
1163    pub version: u32,
1164    #[serde(rename = "_generated_by")]
1165    pub generated_by: &'static str,
1166    pub provenance: &'static str,
1167    pub no_speed_claim: bool,
1168    pub honesty: &'static str,
1169    pub model: ModelBlock,
1170    pub chip: ChipBlock,
1171    pub tensor_parallel: TensorParallelBlock,
1172    pub residency: ResidencyBlock,
1173    pub per_layer: Vec<LayerRow>,
1174    pub lm_head: LmHeadRow,
1175    pub totals: TotalsBlock,
1176    pub ii_report: IiBlock,
1177}
1178
1179fn tp_row(
1180    chips: usize,
1181    cfg: &Qwen38Cfg,
1182    sharded_bf16: usize,
1183    replicated_bf16: usize,
1184    gdn_state: usize,
1185    conv_state: usize,
1186    kv_per_token: usize,
1187    ctx: usize,
1188    hbm: usize,
1189) -> TpRow {
1190    let sharded_weight_bytes_per_chip = sharded_bf16 / chips;
1191    let weights_per_chip_bytes = sharded_weight_bytes_per_chip + replicated_bf16;
1192    let state_bytes_per_chip = gdn_state / chips.min(cfg.gdn_v_heads) + conv_state / chips;
1193    let kv_shards = chips.min(cfg.kv);
1194    let kv_per_chip_bytes = kv_per_token * ctx / kv_shards;
1195    let hbm_need_bytes = weights_per_chip_bytes + state_bytes_per_chip + kv_per_chip_bytes;
1196    let resident = weights_per_chip_bytes + state_bytes_per_chip;
1197    TpRow {
1198        chips,
1199        sharded_weight_bytes_per_chip,
1200        replicated_weight_bytes: replicated_bf16,
1201        weights_per_chip_bytes,
1202        state_bytes_per_chip,
1203        kv_per_chip_bytes,
1204        hbm_need_bytes,
1205        hbm_bytes: hbm,
1206        hbm_fit: hbm_need_bytes <= hbm,
1207        hbm_headroom_bytes: hbm as i64 - hbm_need_bytes as i64,
1208        max_ctx_in_hbm: hbm.saturating_sub(resident) / (kv_per_token / kv_shards).max(1),
1209    }
1210}
1211
1212/// The tiny-config RMSNorm chain — the first statement of the authored forward at width `d`, as a standalone
1213/// program the tensor lane lifts.
1214fn rmsnorm_chain_source(d: usize) -> String {
1215    let mut s = String::from("## Main\n");
1216    s.push_str(&format!("Let x be a 1 by {d} tensor of Real.\n"));
1217    for i in 1..=d {
1218        s.push_str(&format!("Set item {i} of x to {i}.0.\n"));
1219    }
1220    s.push_str(&format!("Let an0 be a 1 by {d} tensor of Real.\n"));
1221    for i in 1..=d {
1222        s.push_str(&format!("Set item {i} of an0 to 1.0.\n"));
1223    }
1224    s.push_str("Let xn0 be rmsnorm of x with weight an0.\n");
1225    for i in 1..=d {
1226        s.push_str(&format!("Show item {i} of xn0.\n"));
1227    }
1228    s
1229}
1230
1231/// The II block of the RMSNorm chain: at the real width when the chip hosts it, else at the tiny width with
1232/// the real width's refusal recorded — the block upgrades itself when composition lands.
1233fn qwen38_ii_block(cfg: &Qwen38Cfg, facts: &RduChipFacts) -> Result<IiBlock, String> {
1234    let target = facts.spatial_target();
1235    match compile_to_spatial(&rmsnorm_chain_source(cfg.d), &target) {
1236        Ok(p) => Ok(IiBlock::of(&rdu_ii_report(&p, facts), format!("the forward's first statement, rmsnorm of x with weight an0, at the real width 1×{} on the {}", cfg.d, target.name))),
1237        Err(refusal) => {
1238            let tiny = Qwen38Cfg::tiny();
1239            let p = compile_to_spatial(&rmsnorm_chain_source(tiny.d), &target).map_err(|e| format!("the {} cannot host the tiny rmsnorm chain: {e:?}", target.name))?;
1240            Ok(IiBlock::of(
1241                &rdu_ii_report(&p, facts),
1242                format!(
1243                    "the forward's first statement, rmsnorm of x with weight an0, at the tiny config's width 1×{} on the {} — the real width 1×{} is refused by one PCU's budget ({refusal:?}) until sections compose it",
1244                    tiny.d, target.name, cfg.d
1245                ),
1246            ))
1247        }
1248    }
1249}
1250
1251/// Qwen3.8 on `facts`' chip, sharded over `tp` RDUs, with the KV cache sized at context `ctx`.
1252pub fn qwen38_fit(
1253    cfg: &Qwen38Cfg,
1254    facts: &RduChipFacts,
1255    tp: usize,
1256    ctx: usize,
1257) -> Result<FitReport, String> {
1258    if tp == 0 {
1259        return Err("tensor parallel needs at least one chip".into());
1260    }
1261    let tensors = qwen38_tensors(cfg);
1262    let (source, _, _) = gen_qwen38_lg(cfg, "w");
1263    let mut shapes: BTreeMap<String, (usize, usize)> = tensors
1264        .iter()
1265        .map(|t| (t.name.clone(), (t.rows, t.cols)))
1266        .collect();
1267    let stmts = op_statements(&source);
1268    for s in &stmts {
1269        let shape = result_shape(&s.expr, &shapes)
1270            .ok_or_else(|| format!("no shape for `{}` = `{}`", s.name, s.expr))?;
1271        shapes.insert(s.name.clone(), shape);
1272    }
1273    let peaks = live_set_peaks(&stmts, &shapes, "x");
1274
1275    let weights: Vec<&TensorLine> = tensors
1276        .iter()
1277        .filter(|t| t.class == TensorClass::Weight)
1278        .collect();
1279    let params: usize = weights.iter().map(|t| t.elems()).sum();
1280    let q8: Vec<&&TensorLine> = weights
1281        .iter()
1282        .filter(|t| t.storage == Storage::Q8_0)
1283        .collect();
1284    let f32s: Vec<&&TensorLine> = weights
1285        .iter()
1286        .filter(|t| t.storage == Storage::F32)
1287        .collect();
1288    let q8_0_bytes_total: usize = q8.iter().map(|t| t.bytes).sum();
1289    let f32_bytes_total: usize = f32s.iter().map(|t| t.bytes).sum();
1290    let emb = tensors
1291        .iter()
1292        .find(|t| t.name == "emb")
1293        .expect("the embedding line");
1294    let sharded_bf16: usize = weights
1295        .iter()
1296        .filter(|t| t.is_matrix())
1297        .map(|t| t.elems() * BF16_BYTES)
1298        .sum();
1299    let replicated_bf16: usize = weights
1300        .iter()
1301        .filter(|t| !t.is_matrix())
1302        .map(|t| t.elems() * BF16_BYTES)
1303        .sum();
1304
1305    let gdn_state: usize = tensors
1306        .iter()
1307        .filter(|t| t.class == TensorClass::State && t.name.starts_with("St"))
1308        .map(|t| t.bytes)
1309        .sum();
1310    let conv_state: usize = tensors
1311        .iter()
1312        .filter(|t| t.class == TensorClass::State && t.name.starts_with("Cs"))
1313        .map(|t| t.bytes)
1314        .sum();
1315    let kv_per_token = 2 * cfg.akv() * BF16_BYTES * cfg.attn_layers();
1316
1317    let mut per_layer = Vec::with_capacity(cfg.layers);
1318    let mut totals = TotalsBlock {
1319        macs_per_token_at_ctx: 0,
1320        weight_macs: 0,
1321        attention_macs_at_ctx: 0,
1322        gdn_recurrence_macs: 0,
1323        conv_macs: 0,
1324        lm_head_macs: 0,
1325        provenance: PROVENANCE_MODELED,
1326    };
1327    for l in 0..cfg.layers {
1328        let layer_weights: Vec<&&TensorLine> =
1329            weights.iter().filter(|t| t.layer == Some(l)).collect();
1330        let weight_params: usize = layer_weights.iter().map(|t| t.elems()).sum();
1331        let weight_bytes_gguf: usize = layer_weights.iter().map(|t| t.bytes).sum();
1332        let mut macs = OpMacs::default();
1333        let mut peak = 0usize;
1334        for (i, s) in stmts.iter().enumerate().filter(|(_, s)| s.layer == Some(l)) {
1335            let m = op_macs(&s.expr, &shapes, ctx);
1336            macs.weight += m.weight;
1337            macs.attention += m.attention;
1338            macs.gdn += m.gdn;
1339            macs.conv += m.conv;
1340            peak = peak.max(peaks[i]);
1341        }
1342        let total = macs.weight + macs.attention + macs.gdn + macs.conv;
1343        let weight_bytes_bf16 = weight_params * BF16_BYTES;
1344        totals.weight_macs += macs.weight;
1345        totals.attention_macs_at_ctx += macs.attention;
1346        totals.gdn_recurrence_macs += macs.gdn;
1347        totals.conv_macs += macs.conv;
1348        per_layer.push(LayerRow {
1349            layer: l,
1350            kind: if cfg.is_attn(l) { "attention" } else { "gdn" },
1351            weight_params,
1352            weight_bytes_bf16,
1353            weight_bytes_gguf,
1354            weight_macs: macs.weight,
1355            attention_macs_at_ctx: macs.attention,
1356            gdn_recurrence_macs: macs.gdn,
1357            conv_macs: macs.conv,
1358            macs: total,
1359            arithmetic_intensity_macs_per_byte: total as f64 / weight_bytes_bf16 as f64,
1360            activations_peak_bytes: peak,
1361            provenance: PROVENANCE_MODELED,
1362        });
1363    }
1364    let head: Vec<&&TensorLine> = weights
1365        .iter()
1366        .filter(|t| t.layer.is_none() && t.name != "emb")
1367        .collect();
1368    let head_params: usize = head.iter().map(|t| t.elems()).sum();
1369    let head_macs: usize = stmts
1370        .iter()
1371        .filter(|s| s.layer.is_none())
1372        .map(|s| op_macs(&s.expr, &shapes, ctx).weight)
1373        .sum();
1374    totals.lm_head_macs = head_macs;
1375    totals.macs_per_token_at_ctx = totals.weight_macs
1376        + totals.attention_macs_at_ctx
1377        + totals.gdn_recurrence_macs
1378        + totals.conv_macs
1379        + head_macs;
1380    let lm_head = LmHeadRow {
1381        weight_params: head_params,
1382        weight_bytes_bf16: head_params * BF16_BYTES,
1383        weight_bytes_gguf: head.iter().map(|t| t.bytes).sum(),
1384        macs: head_macs,
1385        arithmetic_intensity_macs_per_byte: head_macs as f64 / (head_params * BF16_BYTES) as f64,
1386        provenance: PROVENANCE_MODELED,
1387    };
1388
1389    let hbm = facts.hbm_gib.value * GIB;
1390    let ladder: Vec<TpRow> = {
1391        let mut widths = vec![
1392            1,
1393            facts.chips_per_node.value,
1394            default_tensor_parallel(facts),
1395        ];
1396        widths.dedup();
1397        widths
1398            .into_iter()
1399            .map(|w| {
1400                tp_row(
1401                    w,
1402                    cfg,
1403                    sharded_bf16,
1404                    replicated_bf16,
1405                    gdn_state,
1406                    conv_state,
1407                    kv_per_token,
1408                    ctx,
1409                    hbm,
1410                )
1411            })
1412            .collect()
1413    };
1414    let here = tp_row(
1415        tp,
1416        cfg,
1417        sharded_bf16,
1418        replicated_bf16,
1419        gdn_state,
1420        conv_state,
1421        kv_per_token,
1422        ctx,
1423        hbm,
1424    );
1425    let kv_shards = tp.min(cfg.kv);
1426    let tensor_parallel = TensorParallelBlock {
1427        chips: tp,
1428        dtype: DEPLOYMENT_DTYPE,
1429        sharded_weight_bytes_per_chip: here.sharded_weight_bytes_per_chip,
1430        replicated_weight_bytes: here.replicated_weight_bytes,
1431        weights_per_chip_bytes: here.weights_per_chip_bytes,
1432        state_bytes_per_chip: here.state_bytes_per_chip,
1433        kv_cache_at_ctx: KvAtCtx { ctx, bytes: kv_per_token * ctx, kv_shards, replication: tp / kv_shards, per_chip_bytes: here.kv_per_chip_bytes },
1434        hbm_bytes_per_chip: hbm,
1435        hbm_need_bytes: here.hbm_need_bytes,
1436        hbm_fit: here.hbm_fit,
1437        hbm_headroom_bytes: here.hbm_headroom_bytes,
1438        max_ctx_in_hbm: here.max_ctx_in_hbm,
1439        ladder,
1440        sharding: "every weight matrix is split over the chips (column/row parallel); norm vectors are replicated; the KV cache shards over kv heads and replicates beyond them; the GDN memory shards over v-heads; the conv rings over channels",
1441        provenance: PROVENANCE_MODELED,
1442    };
1443
1444    let sram = facts.sram_bytes();
1445    let (peak_layer, act) = per_layer
1446        .iter()
1447        .map(|r| (r.layer, r.activations_peak_bytes))
1448        .max_by_key(|(l, b)| (*b, std::cmp::Reverse(*l)))
1449        .ok_or("a layer")?;
1450    let lm_head_live: usize = stmts
1451        .iter()
1452        .enumerate()
1453        .filter(|(_, s)| s.layer.is_none())
1454        .map(|(i, _)| peaks[i])
1455        .max()
1456        .unwrap_or(0);
1457    let lm_head_live_pc =
1458        lm_head_live.saturating_sub(cfg.vocab * BF16_BYTES) + cfg.vocab * BF16_BYTES / tp;
1459    let gdn_pc = gdn_state / tp.min(cfg.gdn_v_heads);
1460    let conv_pc = conv_state / tp;
1461    let recurrent = act + gdn_pc + conv_pc;
1462    let momentary = act.max(lm_head_live_pc) + gdn_pc + conv_pc;
1463    let kv_per_token_pc = kv_per_token / kv_shards;
1464    let free = sram.saturating_sub(recurrent);
1465    let tier = |bytes: usize| if bytes <= sram { "sram" } else { "hbm" };
1466    let residency = ResidencyBlock {
1467        sram_bytes: sram,
1468        activations_per_token_bytes: act,
1469        activations_peak_layer: peak_layer,
1470        activations_peak_layer_kind: if cfg.is_attn(peak_layer) {
1471            "attention"
1472        } else {
1473            "gdn"
1474        },
1475        activations_fit_sram: act <= sram,
1476        lm_head_live_set_bytes: lm_head_live,
1477        lm_head_live_set_per_chip_bytes: lm_head_live_pc,
1478        gdn_state_bytes: gdn_state,
1479        gdn_state_bytes_per_chip: gdn_pc,
1480        conv_state_bytes: conv_state,
1481        conv_state_bytes_per_chip: conv_pc,
1482        kv_cache_bytes_per_token: kv_per_token,
1483        kv_cache_bytes_per_token_per_chip: kv_per_token_pc,
1484        recurrent_working_set_per_chip_bytes: recurrent,
1485        sram_fit_per_chip: momentary <= sram,
1486        tiers: Tiers {
1487            weights: "hbm",
1488            activations: tier(act),
1489            gdn_state: tier(act + gdn_pc),
1490            conv_state: tier(recurrent),
1491            kv_cache: KvTier {
1492                sram_until_ctx: free / kv_per_token_pc.max(1),
1493                then: "hbm",
1494            },
1495        },
1496        provenance: PROVENANCE_MODELED,
1497    };
1498
1499    Ok(FitReport::Qwen38(Qwen38Fit {
1500        schema: FIT_SCHEMA,
1501        version: FIT_VERSION,
1502        generated_by: QWEN38_GENERATED_BY,
1503        provenance: PROVENANCE_MODELED,
1504        no_speed_claim: true,
1505        honesty: HONESTY,
1506        model: ModelBlock {
1507            id: QWEN38_MODEL_ID,
1508            hf_model_type: QWEN38_HF_MODEL_TYPE,
1509            inherits: QWEN38_HF_INHERITS,
1510            deployment_dtype: DEPLOYMENT_DTYPE,
1511            gguf: QWEN38_GGUF,
1512            config: CfgBlock::of(cfg),
1513            weights: WeightsBlock {
1514                params,
1515                tensors: weights.len(),
1516                q8_0_tensors: q8.len(),
1517                f32_tensors: f32s.len(),
1518                q8_0_bytes: q8_0_bytes_total,
1519                f32_bytes: f32_bytes_total,
1520                gguf_bytes: q8_0_bytes_total + f32_bytes_total,
1521                bf16_bytes: params * BF16_BYTES,
1522                embedding: EmbeddingBlock {
1523                    rows: emb.rows,
1524                    cols: emb.cols,
1525                    storage: emb.storage,
1526                    bytes: emb.bytes,
1527                    note: "not declared by the single-token forward (the step gathers one row of it); resident for decode",
1528                },
1529                excluded: vec!["the MTP (nextn) draft block: not used by greedy decode", "the rope tables and the recurrent state: not weights (listed under residency)"],
1530                q8_0_layout: Q8_0_LAYOUT,
1531                provenance: PROVENANCE_MODELED,
1532            },
1533            tensors,
1534            provenance: PROVENANCE_MODELED,
1535        },
1536        chip: ChipBlock::of(facts),
1537        tensor_parallel,
1538        residency,
1539        per_layer,
1540        lm_head,
1541        totals,
1542        ii_report: qwen38_ii_block(cfg, facts)?,
1543    }))
1544}
1545
1546// ── The two reports, one type ───────────────────────────────────────────────────────────────────
1547
1548#[derive(Clone, Debug, PartialEq, Serialize)]
1549#[serde(untagged)]
1550pub enum FitReport {
1551    Gemm(GemmFit),
1552    Qwen38(Qwen38Fit),
1553}
1554
1555impl FitReport {
1556    pub fn to_value(&self) -> Value {
1557        serde_json::to_value(self).expect("a fit report serializes")
1558    }
1559    pub fn as_qwen38(&self) -> Option<&Qwen38Fit> {
1560        match self {
1561            FitReport::Qwen38(q) => Some(q),
1562            FitReport::Gemm(_) => None,
1563        }
1564    }
1565    pub fn as_gemm(&self) -> Option<&GemmFit> {
1566        match self {
1567            FitReport::Gemm(g) => Some(g),
1568            FitReport::Qwen38(_) => None,
1569        }
1570    }
1571}
1572
1573// ── The vLLM plan ───────────────────────────────────────────────────────────────────────────────
1574
1575#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
1576pub struct VllmModel {
1577    pub id: &'static str,
1578    pub hf_model_type: &'static str,
1579    pub inherits: &'static str,
1580    pub checkpoint: &'static str,
1581    pub gguf_alternative: &'static str,
1582}
1583
1584#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
1585pub struct VllmServe {
1586    pub tensor_parallel_size: usize,
1587    pub dtype: &'static str,
1588    pub max_model_len: usize,
1589    pub command: String,
1590    pub provenance: &'static str,
1591}
1592
1593#[derive(Clone, Debug, PartialEq, Serialize)]
1594pub struct VllmChip {
1595    pub part: &'static str,
1596    pub chip: RduChip,
1597    pub chips_per_node: Cited<usize>,
1598    pub chips_per_rack: Option<Cited<usize>>,
1599    pub tensor_parallel_width: Cited<usize>,
1600    pub provenance: &'static str,
1601}
1602
1603#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
1604pub struct StatePerLayer {
1605    pub attention_kv_per_token: usize,
1606    pub gdn_memory: usize,
1607    pub conv_rings: usize,
1608    pub provenance: &'static str,
1609}
1610
1611#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
1612pub struct FromFit {
1613    pub weights_bf16_bytes: usize,
1614    pub weights_per_chip_bytes: usize,
1615    pub hbm_fit: bool,
1616    pub kv_cache_at_ctx: KvAtCtx,
1617    pub max_ctx_in_hbm: usize,
1618    pub sram_until_ctx: usize,
1619    pub provenance: &'static str,
1620}
1621
1622#[derive(Clone, Debug, PartialEq, Serialize)]
1623pub struct VllmPlan {
1624    pub schema: &'static str,
1625    pub version: u32,
1626    #[serde(rename = "_generated_by")]
1627    pub generated_by: &'static str,
1628    pub provenance: &'static str,
1629    pub no_speed_claim: bool,
1630    pub honesty: &'static str,
1631    pub model: VllmModel,
1632    pub serve: VllmServe,
1633    pub chip: VllmChip,
1634    pub ops_the_backend_must_support: Vec<Value>,
1635    pub state_bytes_per_layer: StatePerLayer,
1636    pub from_fit: FromFit,
1637    pub intake_question: &'static str,
1638    pub fallbacks: Vec<&'static str>,
1639}
1640
1641/// The ops the authored forward is made of, with the shapes the backend must support — every number from the config.
1642pub fn qwen38_op_list(cfg: &Qwen38Cfg) -> Vec<Value> {
1643    let gdn_layers = cfg.layers - cfg.attn_layers();
1644    vec![
1645        json!({ "op": "rmsnorm", "eps": cfg.rms_eps, "width": cfg.d, "per_head_variants": { "q": cfg.hd, "k": cfg.hd }, "provenance": PROVENANCE_MODELED }),
1646        json!({ "op": "matmul", "dtype": DEPLOYMENT_DTYPE, "widest": { "rows": cfg.d, "cols": cfg.vocab }, "provenance": PROVENANCE_MODELED }),
1647        json!({ "op": "rope", "rotary_dims": cfg.rope_dim, "head_dim": cfg.hd, "theta": cfg.rope_theta, "note": "partial rotary: only the first rotary_dims of each head rotate", "provenance": PROVENANCE_MODELED }),
1648        json!({ "op": "gqa_attention", "heads": cfg.h, "kv_heads": cfg.kv, "head_dim": cfg.hd, "layers": cfg.attn_layers(), "causal": true, "provenance": PROVENANCE_MODELED }),
1649        json!({ "op": "sigmoid_gate", "width": cfg.aq(), "note": "the attention output gate, hadamard with the context", "provenance": PROVENANCE_MODELED }),
1650        json!({ "op": "causal_conv", "taps": cfg.conv_taps, "channels": 2 * cfg.gdn_qk() + cfg.gdn_v(), "layers": gdn_layers, "provenance": PROVENANCE_MODELED }),
1651        json!({ "op": "silu", "provenance": PROVENANCE_MODELED }),
1652        json!({ "op": "l2norm_per_head", "heads": cfg.gdn_k_heads, "head_dim": cfg.gdn_head_dim, "provenance": PROVENANCE_MODELED }),
1653        json!({ "op": "decay", "heads": cfg.gdn_v_heads, "note": "exp(A · softplus(u + dt_bias)) — the per-head forget gate", "provenance": PROVENANCE_MODELED }),
1654        json!({ "op": "gated_delta", "v_heads": cfg.gdn_v_heads, "k_heads": cfg.gdn_k_heads, "head_dim": cfg.gdn_head_dim, "layers": gdn_layers, "state_elems_per_layer": cfg.gdn_v() * cfg.gdn_head_dim, "provenance": PROVENANCE_MODELED }),
1655        json!({ "op": "swiglu", "dff": cfg.dff, "provenance": PROVENANCE_MODELED }),
1656        json!({ "op": "argmax", "vocab": cfg.vocab, "note": "greedy next token", "provenance": PROVENANCE_MODELED }),
1657    ]
1658}
1659
1660/// The plan the fit implies for the vendor's vLLM backend at the model's full context.
1661pub fn vllm_plan(cfg: &Qwen38Cfg, facts: &RduChipFacts, tp: usize) -> Result<VllmPlan, String> {
1662    let fit = qwen38_fit(cfg, facts, tp, cfg.ctx_len)?;
1663    let q = fit.as_qwen38().ok_or("a Qwen3.8 fit")?;
1664    let chip = ChipBlock::of(facts);
1665    let gdn_layers = cfg.layers - cfg.attn_layers();
1666    Ok(VllmPlan {
1667        schema: VLLM_PLAN_SCHEMA,
1668        version: FIT_VERSION,
1669        generated_by: QWEN38_GENERATED_BY,
1670        provenance: PROVENANCE_MODELED,
1671        no_speed_claim: true,
1672        honesty: HONESTY,
1673        model: VllmModel {
1674            id: QWEN38_MODEL_ID,
1675            hf_model_type: QWEN38_HF_MODEL_TYPE,
1676            inherits: QWEN38_HF_INHERITS,
1677            checkpoint: "safetensors, bf16",
1678            gguf_alternative: QWEN38_GGUF,
1679        },
1680        serve: VllmServe {
1681            tensor_parallel_size: tp,
1682            dtype: DEPLOYMENT_DTYPE,
1683            max_model_len: cfg.ctx_len,
1684            command: format!("vllm serve {QWEN38_MODEL_ID} --tensor-parallel-size {tp} --dtype {DEPLOYMENT_DTYPE} --max-model-len {}", cfg.ctx_len),
1685            provenance: PROVENANCE_MODELED,
1686        },
1687        chip: VllmChip {
1688            part: chip.part,
1689            chip: chip.chip,
1690            chips_per_node: chip.chips_per_node,
1691            chips_per_rack: chip.chips_per_rack,
1692            tensor_parallel_width: chip.tensor_parallel_width,
1693            provenance: PROVENANCE_MODELED,
1694        },
1695        ops_the_backend_must_support: qwen38_op_list(cfg),
1696        state_bytes_per_layer: StatePerLayer {
1697            attention_kv_per_token: q.residency.kv_cache_bytes_per_token / cfg.attn_layers(),
1698            gdn_memory: q.residency.gdn_state_bytes / gdn_layers,
1699            conv_rings: q.residency.conv_state_bytes / gdn_layers,
1700            provenance: PROVENANCE_MODELED,
1701        },
1702        from_fit: FromFit {
1703            weights_bf16_bytes: q.model.weights.bf16_bytes,
1704            weights_per_chip_bytes: q.tensor_parallel.weights_per_chip_bytes,
1705            hbm_fit: q.tensor_parallel.hbm_fit,
1706            kv_cache_at_ctx: q.tensor_parallel.kv_cache_at_ctx.clone(),
1707            max_ctx_in_hbm: q.tensor_parallel.max_ctx_in_hbm,
1708            sram_until_ctx: q.residency.tiers.kv_cache.sram_until_ctx,
1709            provenance: PROVENANCE_MODELED,
1710        },
1711        intake_question: "does the RDU vLLM backend support HF model_type qwen3_5 (Gated DeltaNet: causal_conv + decay + gated_delta with a per-layer ghd×ghd memory per v-head)? If not, which of these ops is missing?",
1712        fallbacks: vec![
1713            "SambaFlow: `largo emit rdu --format sambaflow` on the tiny config first, then the real one (PEF compile time is the risk)",
1714            "RDA/Spatial-IR: `largo emit rdu` neutral.json v2 + certificate; `--format spatial` for the Spatial source",
1715            "API only: the SambaCloud baseline plus the `largo emit rdu` demo stand on their own",
1716        ],
1717    })
1718}