metrale_bench/hardware/
ids.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-26: Box-class ids, the strings a gate baseline is keyed by, and the map
4//! from a GPU's reported name to its id.
5//!
6//! Owner: bench hardware.
7//! Invariants:
8//! - Every id [`hardware_id_from_gpu_name`] returns is in [`KNOWN_HARDWARE_IDS`]
9//!   (`every_id_the_sku_table_produces_is_registered`).
10//!
11//! A benchmark's thresholds live under `kernels/<hw>/<model>/BENCH.toml` and are
12//! assembled into `GateBaseline.hardware`. A run's key comes from `--hardware` or
13//! from a record's fingerprint via [`super::Hardware::gate_key`]. The baseline
14//! holds only what has been measured, so it cannot tell a registered class with no
15//! record yet from a misspelling; [`KNOWN_HARDWARE_IDS`] can, and the resolver
16//! gives the two different refusals.
17
18/// 2026-09-26: Every box class the engine recognises, whether or not any gate has
19/// a record on it yet. `--hardware` is validated against it (`bench_resolve.rs`).
20///
21/// Two kinds of id live here:
22///
23/// * **architecture classes**: `gb10`, `hopper`, `b200`, `b300`, `metal`, `strix`,
24///   `strix-hip`, the `kernels/<hw>/` directories that hold a `HARDWARE.toml`
25///   (`every_kernel_hardware_dir_is_registered`);
26/// * **bench SKUs**: the ids [`hardware_id_from_gpu_name`] maps a GPU name onto,
27///   which a record's fingerprint keys by.
28///
29/// They coincide for `gb10`, `b200` and `b300`.
30pub const KNOWN_HARDWARE_IDS: [&str; 12] = [
31    // 2026-09-26: NVIDIA GB10: architecture class and SKU at once.
32    "gb10",
33    // 2026-09-26: NVIDIA Hopper, the architecture class: `kernels/hopper/` (sm_90a).
34    "hopper",
35    // 2026-09-26: NVIDIA H100 and H200: separate baseline keys.
36    "h100",
37    "h200",
38    // 2026-09-26: NVIDIA GH200: its own baseline key.
39    "gh200",
40    // 2026-09-26: `b200` is both the `kernels/b200/` class (sm_100a) and the B200
41    // SKU. GB200 has its own baseline key.
42    "b200",
43    "gb200",
44    // 2026-09-26: `b300` is both the `kernels/b300/` class (sm_103a) and the B300
45    // SKU. GB300 is not registered and maps to no id.
46    "b300",
47    // 2026-09-26: Apple Silicon, via the Metal backend (`kernels/metal/`).
48    "metal",
49    // 2026-09-26: AMD Strix Halo (gfx1151) through two backends, each its own
50    // class: `strix` builds the CUDA sources with SCALE (`kernels/strix/`), and
51    // `strix-hip` builds them with hipcc (`kernels/strix-hip/`).
52    "strix",
53    "strix-hip",
54    // 2026-09-26: AMD Instinct MI300X. No `kernels/` directory; registered because
55    // `hardware_id_from_gpu_name` maps the SKU onto it.
56    "mi300x",
57];
58
59/// 2026-09-26: Every GPU-name token that names a box class, and the class it names.
60///
61/// Matched as a whole token, never a substring: `gh200` contains `h200`, and a
62/// substring match would file every Grace-Hopper run under the H200 baseline.
63///
64/// A name with no listed token falls through to [`super::Hardware::gate_key`]'s
65/// normalisation, which keeps capacity and generation in the key
66/// (`NVIDIA A100-SXM4-80GB` → `a100sxm480gb`).
67const SKU_TOKENS: [(&str, &str); 8] = [
68    ("gb10", "gb10"),
69    ("h100", "h100"),
70    ("h200", "h200"),
71    ("gh200", "gh200"),
72    ("b200", "b200"),
73    ("gb200", "gb200"),
74    ("b300", "b300"),
75    ("mi300x", "mi300x"),
76];
77
78/// 2026-09-26: Map a GPU's reported name onto the box class its numbers belong to:
79/// the first whole alphanumeric token, lowercased, that `SKU_TOKENS` lists.
80///
81/// The name is free text with SKU detail: `"NVIDIA H100 80GB HBM3"`,
82/// `"NVIDIA H100 PCIe"`, `"NVIDIA H200 NVL"`. The fallback normalisation alone
83/// would key those as `h10080gbhbm3`, `h100pcie` and `h200nvl`, three keys no
84/// baseline uses.
85///
86/// `None` means "no opinion", not "unknown box": the caller keeps its own
87/// normalisation. GB300 and the A100 capacities are deliberately absent, so they
88/// keep separate fallback keys rather than sharing a family id.
89///
90/// One SKU family, one id: `H100 PCIe` and `H100 SXM` share `h100`. The exact name
91/// stays verbatim in `Hardware::gpu`; only the baseline key is coarse.
92pub fn hardware_id_from_gpu_name(name: &str) -> Option<&'static str> {
93    name.split(|c: char| !c.is_ascii_alphanumeric())
94        .map(str::to_ascii_lowercase)
95        .find_map(|token| {
96            SKU_TOKENS
97                .iter()
98                .find(|(sku, _)| *sku == token)
99                .map(|(_, id)| *id)
100        })
101}
102
103/// 2026-09-26: True when `id` is in [`KNOWN_HARDWARE_IDS`].
104///
105/// Case-sensitive on purpose: the id is a directory name and a baseline key, so
106/// `H100` is not `h100`.
107pub fn is_known_hardware_id(id: &str) -> bool {
108    KNOWN_HARDWARE_IDS.contains(&id)
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    /// 2026-09-26: Registered ids are known whether or not a record exists, so the
116    /// resolver can tell "go measure it" from a misspelling.
117    #[test]
118    fn the_hopper_slots_are_registered_before_they_are_measured() {
119        assert!(is_known_hardware_id("h100"));
120        assert!(is_known_hardware_id("h200"));
121        assert!(is_known_hardware_id("gb10"));
122    }
123
124    /// 2026-09-26: The negative side: `h800` is not registered, and `H100` is the
125    /// right part in the wrong case.
126    #[test]
127    fn an_unregistered_or_miscased_id_is_not_known() {
128        assert!(!is_known_hardware_id("h800"));
129        assert!(!is_known_hardware_id("H100"));
130        assert!(!is_known_hardware_id(""));
131        // 2026-09-26: GB300 has no registered id.
132        assert!(!is_known_hardware_id("gb300"));
133    }
134
135    /// 2026-09-26: Both kinds of id are registered: architecture classes
136    /// (`kernels/<hw>/` directories) and bench SKUs. `b200` and `b300` are both.
137    #[test]
138    fn the_kernel_arch_classes_and_the_bench_skus_are_both_registered() {
139        for arch_class in ["gb10", "hopper", "b200", "b300"] {
140            assert!(is_known_hardware_id(arch_class), "{arch_class}");
141        }
142        for sku in ["h100", "h200", "b200", "gb200", "b300"] {
143            assert!(is_known_hardware_id(sku), "{sku}");
144        }
145    }
146
147    /// 2026-09-26: Spellings of each SKU land on its id, whatever the case or the
148    /// trailing detail.
149    #[test]
150    fn each_sku_spelling_lands_on_its_box_class() {
151        for name in ["NVIDIA GB10", "GB10", "nvidia gb10"] {
152            assert_eq!(hardware_id_from_gpu_name(name), Some("gb10"), "{name}");
153        }
154        for name in [
155            "NVIDIA H100 80GB HBM3",
156            "NVIDIA H100 PCIe",
157            "NVIDIA H100-SXM5-80GB",
158        ] {
159            assert_eq!(hardware_id_from_gpu_name(name), Some("h100"), "{name}");
160        }
161        for name in ["NVIDIA H200", "NVIDIA H200 NVL"] {
162            assert_eq!(hardware_id_from_gpu_name(name), Some("h200"), "{name}");
163        }
164        assert_eq!(
165            hardware_id_from_gpu_name("AMD Instinct MI300X"),
166            Some("mi300x")
167        );
168    }
169
170    /// 2026-09-26: GH200 is its own id, and survives the substring trap: `gh200`
171    /// contains `h200`.
172    #[test]
173    fn a_grace_hopper_superchip_is_neither_h100_nor_h200() {
174        for name in ["NVIDIA GH200 480GB", "NVIDIA GH200 120GB"] {
175            assert_eq!(hardware_id_from_gpu_name(name), Some("gh200"), "{name}");
176        }
177    }
178
179    /// 2026-09-26: A part the table has no entry for answers `None`, never the
180    /// nearest-looking id. The A100 capacities differ only in memory; `None` hands
181    /// them back to the normalisation that keeps the capacity in the key.
182    #[test]
183    fn an_unlisted_part_answers_none_rather_than_the_nearest_id() {
184        for name in [
185            // 2026-09-26: GB300's tokens are `nvidia` and `gb300`, neither listed.
186            "NVIDIA GB300",
187            "NVIDIA A100-SXM4-40GB",
188            "NVIDIA A100-SXM4-80GB",
189            "NVIDIA L40S",
190            "AMD Radeon 8060S (gfx1151)",
191            "",
192        ] {
193            let got = hardware_id_from_gpu_name(name);
194            assert!(got.is_none(), "{name:?} must not map anywhere, got {got:?}");
195        }
196    }
197
198    /// 2026-09-26: B200 and GB200 spellings land on separate ids, and `gb200`
199    /// survives the substring trap: it contains `b200`.
200    #[test]
201    fn each_blackwell_datacentre_spelling_lands_on_its_box_class() {
202        for name in [
203            "NVIDIA B200",
204            "NVIDIA B200 180GB HBM3e",
205            "NVIDIA B200-SXM-180GB",
206        ] {
207            assert_eq!(hardware_id_from_gpu_name(name), Some("b200"), "{name}");
208        }
209        for name in ["NVIDIA GB200", "NVIDIA GB200 NVL72"] {
210            assert_eq!(hardware_id_from_gpu_name(name), Some("gb200"), "{name}");
211        }
212    }
213
214    /// 2026-09-26: A Blackwell datacentre name never lands on a Hopper id, where it
215    /// would be scored against Hopper thresholds.
216    #[test]
217    fn a_blackwell_datacentre_part_is_never_filed_under_hopper() {
218        for name in [
219            "NVIDIA B200",
220            "NVIDIA B200 180GB HBM3e",
221            "NVIDIA GB200",
222            "NVIDIA GB200 NVL72",
223        ] {
224            let got = hardware_id_from_gpu_name(name);
225            assert_ne!(got, Some("h100"), "{name}");
226            assert_ne!(got, Some("h200"), "{name}");
227            assert_ne!(got, Some("gh200"), "{name}");
228        }
229    }
230
231    #[test]
232    fn b300_has_its_own_key_and_gb300_is_not_misfiled() {
233        assert!(is_known_hardware_id("b300"));
234        for name in ["NVIDIA B300", "NVIDIA B300 SXM6 AC", "nvidia b300"] {
235            assert_eq!(hardware_id_from_gpu_name(name), Some("b300"));
236        }
237        // 2026-09-26: GB300 maps to no id, so it keeps its fallback key.
238        for name in ["NVIDIA GB300", "NVIDIA GB300 NVL72"] {
239            assert_eq!(hardware_id_from_gpu_name(name), None);
240        }
241        assert!(!is_known_hardware_id("gb300"));
242    }
243
244    /// 2026-09-26: Every id the SKU table produces is registered; otherwise a box
245    /// could write records that `--hardware` refuses to ask for.
246    #[test]
247    fn every_id_the_sku_table_produces_is_registered() {
248        for (sku, id) in SKU_TOKENS {
249            assert!(
250                is_known_hardware_id(id),
251                "{sku} maps to {id:?}, which is not in KNOWN_HARDWARE_IDS"
252            );
253        }
254    }
255
256    /// 2026-09-26: Every `kernels/<hw>/` directory with a `HARDWARE.toml` is
257    /// registered, so its `--hardware` is never refused as unknown.
258    #[test]
259    fn every_kernel_hardware_dir_is_registered() {
260        let kernels = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../kernels");
261        let mut found = Vec::new();
262        for entry in std::fs::read_dir(&kernels)
263            .expect("kernels/ is in the tree")
264            .flatten()
265        {
266            if !entry.path().join("HARDWARE.toml").exists() {
267                continue;
268            }
269            let name = entry.file_name().to_string_lossy().to_string();
270            assert!(
271                is_known_hardware_id(&name),
272                "kernels/{name}/ declares a HARDWARE.toml but {name:?} is not in \
273                 KNOWN_HARDWARE_IDS; add it there or --hardware {name} reads as a typo"
274            );
275            found.push(name);
276        }
277        assert!(
278            !found.is_empty(),
279            "read no hardware dirs at all — the walk is broken, not the registry"
280        );
281    }
282}