metrale_circuit/venn/
compute.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-10-02: The compute unit a kernel runs on: tensor cores (with the MMA atom and the
4//! operand format it issues), CUDA cores, or memory (data movement, element-wise work and
5//! reductions). Declared per family in `KERNEL_FAMILIES.toml` (`compute`, `mma`), per point where
6//! a family's points differ, and per kernel where one family's kernels differ; read by the
7//! tensor-core policy (`hardware::tc_policy`) and shown on every planned group.
8//!
9//! Owner: metrale-circuit (venn).
10//! Invariants:
11//! - Nothing defaults: every family states its unit; `tensor_core` names its atom, the other
12//!   units name none.
13//! - A kernel's unit is its `kernel_compute` entry, else its family's. A family whose points
14//!   state different units names every kernel's unit, so no kernel's unit is inferred.
15
16use std::collections::BTreeMap;
17
18use crate::rules::KernelId;
19
20/// 2026-10-02: Where a kernel's arithmetic runs.
21#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub enum ComputeUnit {
23    /// 2026-10-02: Tensor cores, issuing `atom` (e.g. `mma.sync.m16n8k32.e4m3`).
24    TensorCore {
25        /// 2026-10-02: The MMA instruction shape and operand format.
26        atom: String,
27    },
28    /// 2026-10-02: CUDA-core FMA arithmetic (GEMV dot products, recurrences, convolutions).
29    CudaCore,
30    /// 2026-10-02: Data movement, element-wise work and reductions.
31    Memory,
32}
33
34impl ComputeUnit {
35    /// 2026-10-02: `tensor_core`, `cuda_core` or `memory`.
36    pub fn kind(&self) -> &'static str {
37        match self {
38            ComputeUnit::TensorCore { .. } => "tensor_core",
39            ComputeUnit::CudaCore => "cuda_core",
40            ComputeUnit::Memory => "memory",
41        }
42    }
43
44    /// 2026-10-02: The unit as a report shows it: `tensor_core <atom>`, `cuda_core`, `memory`.
45    pub fn name(&self) -> String {
46        match self {
47            ComputeUnit::TensorCore { atom } => format!("tensor_core {atom}"),
48            other => other.kind().to_string(),
49        }
50    }
51
52    /// 2026-10-02: The unit as one word, for a plan line: `tensor_core:<atom>`, `cuda_core`,
53    /// `memory`.
54    pub fn tag(&self) -> String {
55        match self {
56            ComputeUnit::TensorCore { atom } => format!("tensor_core:{atom}"),
57            other => other.kind().to_string(),
58        }
59    }
60
61    /// 2026-10-02: Runs on tensor cores.
62    pub fn is_tensor_core(&self) -> bool {
63        matches!(self, ComputeUnit::TensorCore { .. })
64    }
65
66    /// 2026-10-02: Parse a `compute` value and its `mma` atom.
67    pub fn parse(compute: &str, mma: Option<&str>) -> Result<Self, String> {
68        match (compute, mma.map(str::trim)) {
69            ("tensor_core", Some(atom)) if !atom.is_empty() => Ok(ComputeUnit::TensorCore {
70                atom: atom.to_string(),
71            }),
72            ("tensor_core", _) => Err("compute `tensor_core` needs its `mma` atom".into()),
73            ("cuda_core", None) => Ok(ComputeUnit::CudaCore),
74            ("memory", None) => Ok(ComputeUnit::Memory),
75            ("cuda_core" | "memory", Some(_)) => Err(format!(
76                "compute `{compute}` issues no MMA, so it names no `mma`"
77            )),
78            (other, _) => Err(format!(
79                "compute `{other}` (tensor_core | cuda_core | memory)"
80            )),
81        }
82    }
83}
84
85/// 2026-10-02: A family's units: its own, and the kernels that differ from it.
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct FamilyCompute {
88    /// 2026-10-02: The family's unit.
89    pub unit: ComputeUnit,
90    /// 2026-10-02: Kernels whose unit differs from the family's.
91    pub kernels: BTreeMap<KernelId, ComputeUnit>,
92}
93
94impl FamilyCompute {
95    /// 2026-10-02: The unit `kernel` runs on.
96    pub fn of(&self, kernel: &KernelId) -> &ComputeUnit {
97        self.kernels.get(kernel).unwrap_or(&self.unit)
98    }
99}
100
101/// 2026-10-02: The unit a plan group runs its node on, from its kernels' units: tensor cores
102/// when any kernel issues MMAs (the others quantize, stage or reduce around it), else CUDA
103/// cores when any kernel computes, else memory. `None` for a group without kernels.
104pub fn group_unit<'a>(units: impl IntoIterator<Item = &'a ComputeUnit>) -> Option<ComputeUnit> {
105    let mut best: Option<&ComputeUnit> = None;
106    for u in units {
107        let rank = |x: &ComputeUnit| match x {
108            ComputeUnit::TensorCore { .. } => 2,
109            ComputeUnit::CudaCore => 1,
110            ComputeUnit::Memory => 0,
111        };
112        if best.is_none_or(|b| rank(u) > rank(b)) {
113            best = Some(u);
114        }
115    }
116    best.cloned()
117}
118
119#[cfg(test)]
120#[path = "compute_tests.rs"]
121mod compute_tests;