1use 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
53pub const QWEN38_MODEL_ID: &str = "Qwen/Qwen3.8-27B";
55pub const QWEN38_HF_MODEL_TYPE: &str = "qwen3_5";
57pub const QWEN38_HF_INHERITS: &str = "qwen3_next";
58pub const QWEN38_GGUF: &str = "bartowski/Qwen3.8-27B-GGUF (Q8_0)";
60pub 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
69pub 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
77pub 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
85pub fn q8_0_bytes(elems: usize) -> usize {
87 elems + elems.div_ceil(Q8_0_BLOCK) * Q8_0_SCALE_BYTES
88}
89
90pub 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
98pub 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#[derive(Clone, Debug, PartialEq, Eq)]
108pub struct TpRefusal {
109 pub tp: usize,
110 pub why: String,
111}
112
113pub 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
162pub fn qwen38_admits_tp(cfg: &Qwen38Cfg, tp: usize) -> Result<(), TpRefusal> {
164 admits_tp(&cfg.spec(), tp)
165}
166
167pub 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#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
180pub struct ServingPlan {
181 pub tensor_parallel: usize,
183 pub data_parallel: usize,
185 pub ctx: usize,
186 pub hbm_bytes_per_chip: usize,
189 pub weights_per_chip_bytes: usize,
190 pub sequence_bytes_per_chip: usize,
191 pub max_sequences_per_replica: usize,
193 pub max_sequences: usize,
194 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
203pub 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
246pub 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#[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#[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 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#[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#[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 pub result_cycles: usize,
401}
402
403#[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#[derive(Clone, Debug, PartialEq, Serialize)]
419pub struct IiBlock {
420 pub chip: RduChip,
421 pub program: String,
422 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#[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 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
540fn 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#[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
627pub 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#[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#[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
737fn 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
746fn 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
776pub 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
815struct 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
844fn 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#[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
903fn 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#[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 pub q8_0_bytes: usize,
1007 pub f32_bytes: usize,
1009 pub gguf_bytes: usize,
1011 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 pub weight_macs: usize,
1041 pub attention_macs_at_ctx: usize,
1043 pub gdn_recurrence_macs: usize,
1045 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 pub kv_shards: usize,
1080 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 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 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 pub recurrent_working_set_per_chip_bytes: usize,
1154 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
1212fn 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
1231fn 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
1251pub 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#[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#[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
1641pub 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
1660pub 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}