metrale_circuit/venn/families.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-29: The kernel-family manifest, `kernels/<hw>/common/KERNEL_FAMILIES.toml`: which
4//! circuit ops each family implements, the parameters it varies (runtime, compile-time or
5//! policy), the points it instantiates, the evidence envelope (the points with microbench
6//! records) and the rules that rediscover the instantiated points from the kernel sources.
7//!
8//! Owner: metrale-circuit (venn).
9//! Invariants:
10//! - A parameter is an axis the family's code is structured to vary (a runtime argument, a
11//! template or macro parameter, a policy or a per-point file copy), even when only one value
12//! is instantiated. A format the code cannot vary without a rewrite is a constraint on the
13//! op (`[[family.op]]`), not a parameter.
14//! - Every point states every compile-time and policy parameter, and nothing else: a runtime
15//! parameter never defines a point. Evidence sits on an instantiated point.
16//! - Nothing defaults: an op, format, parameter, extractor or mode the loader does not know is a
17//! typed error, and a parameter a node cannot supply is an error unless the manifest states
18//! its `absent` value.
19
20use std::collections::{BTreeMap, BTreeSet};
21
22use super::compute::{ComputeUnit, FamilyCompute};
23use crate::format::Format;
24use crate::ir::{LayerKind, LinearRole, OpKind};
25use crate::rules::{KernelId, Mode};
26
27/// 2026-09-29: How a parameter reaches the kernel.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub enum ParamKind {
30 /// 2026-09-29: A kernel argument: strides, counts, eps, scale pointers. Never defines a point.
31 Runtime,
32 /// 2026-09-29: Sizes registers, shared memory or unrolling: a template parameter or a
33 /// per-point copy (head_dim, group size, tiles).
34 Compile,
35 /// 2026-09-29: A policy the code plugs in: weight format, scale layout, activation
36 /// quantizer, activation epilogue, routing scoring.
37 Policy,
38}
39
40impl ParamKind {
41 /// 2026-09-29: The manifest and report spelling.
42 pub fn name(self) -> &'static str {
43 match self {
44 ParamKind::Runtime => "runtime",
45 ParamKind::Compile => "compile-time",
46 ParamKind::Policy => "policy",
47 }
48 }
49}
50
51/// 2026-09-29: Where a parameter's value is read from, for one node of one circuit.
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum Extract {
54 /// 2026-09-29: `dim:<name>`: the circuit's dim.
55 Dim(String),
56 /// 2026-09-29: `weight`: the node's weight format.
57 Weight,
58 /// 2026-09-29: `activation`: the format of the node's first input.
59 Activation,
60 /// 2026-09-29: `output`: the format of the node's first output.
61 Output,
62 /// 2026-09-29: `op`: the node's op name.
63 Op,
64 /// 2026-09-29: `consumer_op`: the op of the first node reading the node's first output.
65 ConsumerOp,
66 /// 2026-09-29: `param:<key>`: the node's template parameter.
67 Param(String),
68 /// 2026-09-29: `setting:<key>`: the instance's policy setting.
69 Setting(String),
70 /// 2026-09-29: `in_dim` / `out_dim`: the first input's / output's feature dimension.
71 InDim,
72 /// 2026-09-29: See [`Extract::InDim`].
73 OutDim,
74}
75
76impl Extract {
77 fn parse(s: &str) -> Option<Self> {
78 let (head, arg) = match s.split_once(':') {
79 Some((h, a)) if !a.is_empty() => (h, Some(a.to_string())),
80 Some(_) => return None,
81 None => (s, None),
82 };
83 match (head, arg) {
84 ("dim", Some(a)) => Some(Extract::Dim(a)),
85 ("param", Some(a)) => Some(Extract::Param(a)),
86 ("setting", Some(a)) => Some(Extract::Setting(a)),
87 ("weight", None) => Some(Extract::Weight),
88 ("activation", None) => Some(Extract::Activation),
89 ("output", None) => Some(Extract::Output),
90 ("op", None) => Some(Extract::Op),
91 ("consumer_op", None) => Some(Extract::ConsumerOp),
92 ("in_dim", None) => Some(Extract::InDim),
93 ("out_dim", None) => Some(Extract::OutDim),
94 _ => None,
95 }
96 }
97}
98
99/// 2026-09-29: One parameter of a family.
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct Param {
102 /// 2026-09-29: Name, e.g. `head_dim`.
103 pub name: String,
104 /// 2026-09-29: How it reaches the kernel.
105 pub kind: ParamKind,
106 /// 2026-09-29: Op base name to extractor. Ops of the family missing here do not carry it.
107 pub from: BTreeMap<String, Extract>,
108 /// 2026-09-29: The value when the extractor finds nothing (a dim or parameter the circuit
109 /// does not state), with the manifest's reason in a comment; `None` makes that an error.
110 pub absent: Option<String>,
111}
112
113/// 2026-09-29: An op a family implements, with the constraints a node must meet.
114#[derive(Debug, Clone, PartialEq, Eq)]
115pub struct OpSpec {
116 /// 2026-09-29: Op base name (`linear`, `paged_attention`, ...). 2026-10-02: or a quantizer
117 /// with its output format (`act_quant:nvfp4/g16`).
118 pub op: String,
119 /// 2026-09-29: For `linear`: the roles it takes; empty is every role.
120 pub roles: BTreeSet<LinearRole>,
121 /// 2026-09-29: Allowed weight formats; empty is any.
122 pub weight: BTreeSet<Format>,
123 /// 2026-09-29: Allowed first-input formats; empty is any.
124 pub activation: BTreeSet<Format>,
125 /// 2026-09-29: Node template parameters that must hold these values.
126 pub params: Values,
127 /// 2026-09-29: Ops one of which must read the node's first output, by base name
128 /// (`l2_norm`) or qualified name (`linear:q`); empty is no constraint. A kernel that fuses
129 /// its consumer (a conv with its L2 norm) implements the op only where that consumer
130 /// follows.
131 pub feeds: BTreeSet<String>,
132 /// 2026-09-29: Ops one of which must produce the node's first input, spelled as `feeds`;
133 /// empty is no constraint (a snapshot of the conv window, not of the SSM state).
134 pub after: BTreeSet<String>,
135 /// 2026-09-29: Ops one of which must also read the node's first input, spelled as
136 /// `feeds`; empty is no constraint. A conv kernel that writes its window snapshot and the
137 /// L2 norm of its output implements the snapshot only where the L2 norm reads beside it.
138 pub beside: BTreeSet<String>,
139}
140
141impl OpSpec {
142 /// 2026-10-02: The spec names `op`: by base name, or by its qualified name (an `act_quant`
143 /// with its format).
144 pub fn names(&self, op: &OpKind) -> bool {
145 self.op == op.base_name() || self.op == op.name()
146 }
147}
148
149/// 2026-09-29: How an instantiated point is realised in the sources.
150#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
151pub enum How {
152 /// 2026-09-29: A template or macro instantiation.
153 Instantiation,
154 /// 2026-09-29: A per-point copy of the source file (the parameterization target).
155 Copy,
156 /// 2026-09-29: A runtime branch inside one entry point.
157 Branch,
158}
159
160impl How {
161 /// 2026-09-29: The manifest spelling.
162 pub fn name(self) -> &'static str {
163 match self {
164 How::Instantiation => "instantiation",
165 How::Copy => "copy",
166 How::Branch => "branch",
167 }
168 }
169}
170
171/// 2026-09-29: Parameter name to value.
172pub type Values = BTreeMap<String, String>;
173
174/// 2026-09-29: One instantiated point.
175#[derive(Debug, Clone, PartialEq, Eq)]
176pub struct Point {
177 /// 2026-09-29: Every compile-time and policy parameter's value.
178 pub values: Values,
179 /// 2026-09-29: How it is realised.
180 pub how: How,
181 /// 2026-09-29: Repo-relative sources that realise it.
182 pub files: Vec<String>,
183 /// 2026-10-02: Its compute unit where it differs from the family's (`None`: the family's).
184 pub compute: Option<ComputeUnit>,
185 /// 2026-10-02: Its own pipeline declarations, where they differ from the family's
186 /// ([`crate::pipeline::declare`]).
187 pub pipeline: crate::pipeline::declare::ByOp,
188}
189
190/// 2026-09-29: Where an evidence record lives.
191#[derive(Debug, Clone, PartialEq, Eq)]
192pub enum EvidenceSource {
193 /// 2026-09-29: A `docs/kernel-perf/measurements.toml` row, keyed `<kernel> @ <regime>`.
194 Measurement(String),
195 /// 2026-09-29: A microbench not in measurements.toml: its method and result in words.
196 Microbench(String),
197}
198
199/// 2026-09-29: A microbench record at one point and the row counts it measured.
200#[derive(Debug, Clone, PartialEq, Eq)]
201pub struct Evidence {
202 /// 2026-09-29: The instantiated point measured.
203 pub point: Values,
204 /// 2026-09-29: The row counts measured; a record says nothing about any other count.
205 pub rows: BTreeSet<u64>,
206 /// 2026-09-29: The record.
207 pub source: EvidenceSource,
208}
209
210/// 2026-09-29: A rule that rediscovers instantiated points from the sources.
211#[derive(Debug, Clone, PartialEq, Eq)]
212pub enum Discover {
213 /// 2026-09-29: Each file matching `glob` realises `values` as a copy.
214 File {
215 /// 2026-09-29: Repo-relative glob (`*` matches any run).
216 glob: String,
217 /// 2026-09-29: The point's values.
218 values: Values,
219 },
220 /// 2026-09-29: Each `name(...)` invocation line in `file` realises one point: parameter
221 /// `p` is argument `args[p]` (0-based), mapped through `map[p]` when present.
222 Macro {
223 /// 2026-09-29: Repo-relative source.
224 file: String,
225 /// 2026-09-29: Macro or template name.
226 name: String,
227 /// 2026-09-29: Parameter to argument index.
228 args: BTreeMap<String, usize>,
229 /// 2026-09-29: Parameter to (argument text to value).
230 map: BTreeMap<String, BTreeMap<String, String>>,
231 },
232}
233
234/// 2026-09-29: One kernel family.
235#[derive(Debug, Clone, PartialEq, Eq)]
236pub struct Family {
237 /// 2026-09-29: Id.
238 pub id: String,
239 /// 2026-09-29: One line.
240 pub description: String,
241 /// 2026-09-29: Entry points, as FUSIONS.toml names them.
242 pub kernels: Vec<KernelId>,
243 /// 2026-09-29: FUSIONS.toml emitters of kernel-less groups (host copies) it stands for.
244 pub emitters: Vec<String>,
245 /// 2026-09-29: Inclusive range of rows one launch covers.
246 pub rows: (u64, u64),
247 /// 2026-09-29: Ops it implements.
248 pub ops: Vec<OpSpec>,
249 /// 2026-09-29: Its parameter space.
250 pub params: Vec<Param>,
251 /// 2026-09-29: Instantiated points.
252 pub points: Vec<Point>,
253 /// 2026-09-29: The evidence envelope.
254 pub evidence: Vec<Evidence>,
255 /// 2026-09-29: Discovery rules.
256 pub discover: Vec<Discover>,
257 /// 2026-10-02: The compute units it runs on ([`super::compute`]).
258 pub compute: FamilyCompute,
259 /// 2026-10-02: The numeric pipeline each op runs at, the family's and its kernels'
260 /// ([`crate::pipeline::declare`]).
261 pub pipeline: crate::pipeline::declare::FamilyPipelines,
262 /// 2026-10-02: Device scratch a launch needs beyond its edges (`crate::memory`).
263 pub workspace: Vec<Workspace>,
264}
265
266/// 2026-10-02: One workspace of a family (`[[family.workspace]]`).
267#[derive(Debug, Clone, PartialEq, Eq)]
268pub struct Workspace {
269 /// 2026-10-02: Name, unique in the family.
270 pub name: String,
271 /// 2026-10-02: Byte expressions over the circuit's dims, `n` (rows), `k` (the node's input
272 /// width) and `sm_count`; the largest is the workspace.
273 pub bytes: Vec<crate::dims::DimExpr>,
274 /// 2026-10-02: Held inside the legacy buffer arena.
275 pub arena: bool,
276 /// 2026-10-02: Where the engine allocates it, and why it has this size.
277 pub why: String,
278}
279
280impl Family {
281 /// 2026-09-29: One launch covers more than one row.
282 pub fn multi_row(&self) -> bool {
283 self.rows.1 > 1
284 }
285
286 /// 2026-09-29: The parameter named `name`.
287 pub fn param(&self, name: &str) -> Option<&Param> {
288 self.params.iter().find(|p| p.name == name)
289 }
290
291 /// 2026-10-02: Each point's values with its own pipeline declarations.
292 pub fn point_pipelines(&self) -> Vec<(&Values, &crate::pipeline::declare::ByOp)> {
293 self.points
294 .iter()
295 .map(|p| (&p.values, &p.pipeline))
296 .collect()
297 }
298}
299
300/// 2026-09-29: A fact about a legacy layer implementation the circuit cannot show: a mode in
301/// which the layer has no multi-row path and loops per sequence (or per row).
302#[derive(Debug, Clone, PartialEq, Eq)]
303pub struct LegacyPath {
304 /// 2026-09-29: Circuit arch.
305 pub arch: String,
306 /// 2026-09-29: Layer kind.
307 pub layer_kind: LayerKind,
308 /// 2026-09-29: Modes whose rows above one loop per sequence.
309 pub per_sequence: Vec<Mode>,
310 /// 2026-09-29: The sites (`block.node`) that loop; empty is every node of the layer kind.
311 pub sites: Vec<String>,
312 /// 2026-09-29: The loop runs only above this many rows (a batched path covers the rest).
313 pub rows_above: u64,
314 /// 2026-09-29: `path:line` of the loop or the trait default it falls back to.
315 pub cite: String,
316 /// 2026-09-29: Text the cited line holds (checked by the tests).
317 pub holds: String,
318 /// 2026-09-29: One line.
319 pub note: String,
320}
321
322/// 2026-09-29: The roofline constants of the hardware.
323#[derive(Debug, Clone, Copy, PartialEq)]
324pub struct Roofline {
325 /// 2026-09-29: DRAM bandwidth, GB/s.
326 pub dram_gbps: f64,
327 /// 2026-09-29: Dense BF16 tensor peak, TFLOPS.
328 pub bf16_tflops: f64,
329 /// 2026-09-29: FP8 tensor peak, TFLOPS.
330 pub fp8_tflops: f64,
331 /// 2026-09-29: NVFP4 tensor peak, TFLOPS.
332 pub nvfp4_tflops: f64,
333 /// 2026-09-29: KV context each sequence attends over in the estimate.
334 pub context_tokens: u64,
335}
336
337/// 2026-09-29: A parsed manifest.
338#[derive(Debug, Clone, PartialEq)]
339pub struct Families {
340 /// 2026-09-29: Hardware directory.
341 pub hardware: String,
342 /// 2026-09-29: Estimate constants.
343 pub roofline: Roofline,
344 /// 2026-09-29: Families, in file order.
345 pub families: Vec<Family>,
346 /// 2026-09-29: Legacy per-sequence facts.
347 pub legacy: Vec<LegacyPath>,
348}
349
350impl Families {
351 /// 2026-10-02: The compute unit `kernel` runs on: its family's answer; `None` when no
352 /// family lists it.
353 pub fn compute_of(&self, kernel: &KernelId) -> Option<&ComputeUnit> {
354 self.families
355 .iter()
356 .find(|f| f.kernels.contains(kernel))
357 .map(|f| f.compute.of(kernel))
358 }
359
360 /// 2026-09-29: The family that ran a node of `op` in a plan group launching `kernels`
361 /// (or, for a kernel-less group, emitted by `emitter`): the first, in manifest order, that
362 /// lists one of them and implements the op (and, for `linear`, the role). Formats are not
363 /// checked: a plan runs what its rules chose. `None` when no family lists them.
364 pub fn of_group(&self, kernels: &[KernelId], emitter: &str, op: &OpKind) -> Option<&Family> {
365 self.families.iter().find(|f| {
366 let listed = if kernels.is_empty() {
367 f.emitters.iter().any(|e| e == emitter)
368 } else {
369 kernels.iter().any(|k| f.kernels.contains(k))
370 };
371 listed
372 && f.ops.iter().any(|s| {
373 s.names(op)
374 && match op {
375 OpKind::Linear(r) => s.roles.is_empty() || s.roles.contains(r),
376 _ => true,
377 }
378 })
379 })
380 }
381}
382
383/// 2026-09-29: Why the manifest did not load.
384#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
385pub enum FamilyError {
386 /// 2026-09-29: Not TOML, or not the schema.
387 #[error("KERNEL_FAMILIES.toml: {0}")]
388 Parse(String),
389 /// 2026-09-29: A field of one family.
390 #[error("family `{family}`: {detail}")]
391 Field {
392 /// 2026-09-29: Family id.
393 family: String,
394 /// 2026-09-29: What was wrong.
395 detail: String,
396 },
397 /// 2026-09-29: An op outside the circuit vocabulary.
398 #[error("family `{family}`: unknown op `{op}`")]
399 UnknownOp {
400 /// 2026-09-29: Family id.
401 family: String,
402 /// 2026-09-29: The op.
403 op: String,
404 },
405 /// 2026-09-29: A point, evidence record or discovery rule naming a parameter the family
406 /// does not declare, or a runtime one.
407 #[error("family `{family}`: `{param}` is not a compile-time or policy parameter of it")]
408 UnknownParam {
409 /// 2026-09-29: Family id.
410 family: String,
411 /// 2026-09-29: The parameter.
412 param: String,
413 },
414}
415
416#[path = "families_compute.rs"]
417mod compute_file;
418#[path = "families_file.rs"]
419mod file;
420#[path = "families_legacy.rs"]
421mod legacy_file;
422#[path = "families_workspace.rs"]
423mod workspace_file;
424
425/// 2026-09-29: Parse the manifest text.
426pub fn parse_families(text: &str) -> Result<Families, FamilyError> {
427 file::parse(text)
428}
429
430#[cfg(test)]
431#[path = "families_tests.rs"]
432mod families_tests;