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}