Add or update a device template
The engine treats a template as official peak hardware: the physical roofline is computed from
these numbers with zero overhead, so they must be the vendor's spec, not a measured figure. Measured
behavior belongs in calibration (kernelOverheadScale, framework profiles), never in the peak numbers.
1. Get the facts
Use the vendor spec page (record it in sourceUrl): memory size, memory bandwidth (GB/s, decimal),
dense tensor throughput per precision, TDP. Do not use "with sparsity" TFLOPS. For Apple/AMD unified
memory, bandwidth is the SoC memory bus figure (M4 Max 546, M3 Ultra 819, Strix Halo 256 GB/s).
If a figure is unpublished (e.g. NVIDIA gives "AI TOPS" not dense TFLOPS), derive it and say so in
specNote.
2. Template fields
'RTX 5090': { // key = canonical short name; shown in lists, used by tests and scenarios
name: 'NVIDIA RTX 5090', // display name
memoryGB: 32, // usable device memory (unified systems: total RAM)
localBandwidthGBps: 1792, // OFFICIAL PEAK memory bandwidth
networkBandwidthGBps: 64, // default link to other devices (PCIe 5 x16 = 64; TB5 = 10; NVLink per the template)
pcieGeneration: 5, pcieLanes: 16, // optional, drives the interconnect presets
computeTFlops: { // DENSE peak per precision; the engine picks by quantization
'float32': 104.8, 'float16': 209.5, 'bfloat16': 209.5, 'int8': 838, 'fp8': 419, 'q4': 1676
},
powerWatts: 575, // TDP for the power/cost card (estimateDeviceTDP falls back to name heuristics)
kernelOverheadScale: 1.5, // ONLY for non-CUDA stacks: AMD ROCm/Vulkan 1.5, Intel SYCL/XPU 2.0; Apple M5 0.6 (fit on community runs)
prefillEfficiencyScale: 0.45, // ONLY when measured pp rates show immature prefill kernels (RDNA4 0.45, 780M 0.2, V100 0.15); a per-runtime map when only some backends miss the hardware's matrix paths (M5: { llama_cpp: 0.8, ollama: 0.8, default: 1 })
backend: 'metal', // ONLY Apple: lets runtimes apply their Metal efficiency (llama.cpp streams at 0.66x peak, MoE layers cost 6x)
sourceUrl: 'https://…',
specStatus: 'verified', // 'verified' | 'preview'
specNote: 'What was verified, what was derived, and the date.',
type: 'GPU' // 'GPU' | 'CPU/Integrated GPU' | 'AI PC' | 'CPU/NVMe' | …
}
Notes on precision keys: the planner uses float16 for the dequantized GEMMs of weight-only quants
(q4/q3/q2 and int8/fp8 outside TensorRT-LLM/vLLM/SGLang), fp8/int8 for true low-precision engines,
and q4 only as a planning value for FP4-native kernels. If a card has no FP8 units (Ampere), omit
fp8; the engine falls back to int8/float16.
kernelOverheadScale multiplies the fixed per-layer/per-token runtime cost (launch gaps, routing,
norms). It is a backend/driver property: CUDA = 1 (omit), ROCm/Vulkan on RDNA ≈ 1.5, SYCL/XPU ≈ 2,
Apple M1–M4 = 1, Apple M5 = 0.6 (faster GPU dispatch, fit on 11 MLX rows). Never use it to "fix"
one model.
prefillEfficiencyScale (default 1) scales the prompt-processing efficiency only — for backends whose
GEMM/flash-attention paths are immature (RDNA4 WMMA 0.45, iGPU 780M 0.2, Volta 0.15). It may be a
per-runtime map (getDevicePrefillScale): Apple M5 uses { llama_cpp: 0.8, ollama: 0.8, default: 1 }
because llama.cpp's Metal backend does not reach the Neural Accelerators MLX uses. Fit it on measured
pp rates (obs/pred on the gold rows with prefillTokS), never on decode.
backend: 'metal' marks Apple templates. Framework profiles carry per-backend overrides
(FRAMEWORK_PROFILES.llama_cpp.backends.metal = { bandwidthEfficiency: 0.66, moeOverheadScale: 6 }):
llama.cpp's Metal backend streams weights at ~65% of peak and its mul_mat_id MoE layers cost ~6x the
CUDA launch overhead (fit on 26 M1 Max–M5 Max rows, median 1.01). MLX has its own constants and no
backend override. A new non-Apple backend with the same pattern gets its own key the same way.
3. Where to add it (all of these)
DEVICE_TEMPLATES in engine.js (the physics engine; index.html loads it before the page
script), near its family. Keys must be unique (integrity test). npm test re-stamps the
engine.js?v=<hash> cache key in index.html and rebuilds dist/ — commit those too.
- Picker group:
getHardwarePresetGroup — add a regex clause only if the name does not already
land in the right group ("NVIDIA · GeForce", "AMD · Radeon & unified", "Intel · graphics",
"Apple silicon", "CPU, memory & storage", …).
- Default runtime:
getDefaultFrameworkForDevice picks the runtime when the user leaves
"AUTO" — what the evidence shows people actually run: Mac/M-series → MLX, H100/H200/B200/B300/
A100/DGX Station/GB300 → vLLM, MI3xx/Gaudi/TPU/Trainium → SGLang, everything else (GeForce, RTX
PRO, Radeon, Arc, Strix, DGX Spark, CPUs) → llama.cpp. Extend the regex if a new family should
default differently.
- Evidence mapping:
HARDWARE_RULES in scripts/refresh-localmaxxing.mjs — a regex on the
community run's hardware label → this template key. Without it, runs on this hardware are not
gold evidence and the planner cannot calibrate it. Beware of overlaps (RTX 5090 vs RTX 5090 D,
M4 Max vs M4 Max (128), M5 Max before M5 Pro): the first matching rule wins. Gold selection
keeps up to 4 rows per hardware template before filling by reproducibility, so one new template
with a handful of community runs is enough to calibrate it; rows whose prompt-processing rate
exceeds the template's dense tensor peak have their prefill dropped (prompt-cache hits), so an
understated computeTFlops silently discards real prefill evidence.
- Hardware search keywords:
filterHardwarePresets matches on name/key; nothing else needed.
- Scenarios (
loadScenarioPreset) if the device appears in a curated multi-device setup.
Unified-memory systems (Mac, DGX Spark, Strix Halo): memoryGB is the whole pool; the planner models
weights + KV + workspace against it, and overflow means swap — keep overflowTarget unset.
NVMe/CPU tiers: localBandwidthGBps is the sustained read rate the CPU path actually sees
(RAID striping and host overhead cap it below the drive sum); the 0.5 CPU-offload factor applies
only to overflow from a GPU, not to a CPU template's own bandwidth.
4. Sanity-check
Run a known model on the new device (see the snippet in the add-model skill) and compare with a
published number for the same class: decode at batch 1 ≈ active bytes / (bandwidth × 0.78) plus
layers × ~45 µs × kernelOverheadScale (more for MoE/GDN layers). For a new GPU generation with no
runs yet, compare against its predecessor scaled by bandwidth — decode scales with bandwidth, prefill
with dense TFLOPS.
5. Verify
npm test (duplicate keys; deduped catalog entries keep the physically correct specs pins a few
templates — extend it for a headline device). Add the device to tests/sanity-matrix.test.mjs
(SINGLE_GPUS / MACS, and a KNOWN band when a measured number exists) so future physics
changes cannot move it outside a realistic range unnoticed.
npm run refresh:localmaxxing && npm run audit:gold when the device has community runs. Look at
the hardware: group median in node scripts/fit-decode-constants.mjs: a median far from 1.0 on a
non-CUDA stack usually means kernelOverheadScale needs fitting (use --grid), a median far from
1.0 on CUDA means a spec number is wrong.
- Any run beating the physical roofline (>1.05×) on the new device means the template overstates
nothing and understates something (bandwidth too low? memory too small → false overflow?) or the
row is mislabeled (
-sm tensor, fewer GPUs than the host has, speculative decoding). Root-cause;
never absorb.
- Browser: the hardware picker search finds it, the spec meta line shows the right bandwidth/memory,
and the topology card draws it.
1---2name: add-hardware3description: Add or update a device template in DEVICE_TEMPLATES (engine.js) — GPU, Apple/AMD unified-memory system, CPU, NVMe tier, or accelerator — with official peak specs, the right backend overhead scale, default runtime, picker group, and evidence mapping. Use when a user asks to add/support/fix a GPU, Mac, DGX, Strix Halo, Arc, Instinct, TPU, CPU, or storage tier.4---56# Add or update a device template78The engine treats a template as **official peak** hardware: the physical roofline is computed from9these numbers with zero overhead, so they must be the vendor's spec, not a measured figure. Measured10behavior belongs in calibration (`kernelOverheadScale`, framework profiles), never in the peak numbers.1112## 1. Get the facts1314Use the vendor spec page (record it in `sourceUrl`): memory size, memory bandwidth (GB/s, decimal),15dense tensor throughput per precision, TDP. Do not use "with sparsity" TFLOPS. For Apple/AMD unified16memory, bandwidth is the SoC memory bus figure (M4 Max 546, M3 Ultra 819, Strix Halo 256 GB/s).17If a figure is unpublished (e.g. NVIDIA gives "AI TOPS" not dense TFLOPS), derive it and say so in18`specNote`.1920## 2. Template fields2122```js23'RTX 5090': { // key = canonical short name; shown in lists, used by tests and scenarios24 name: 'NVIDIA RTX 5090', // display name25 memoryGB: 32, // usable device memory (unified systems: total RAM)26 localBandwidthGBps: 1792, // OFFICIAL PEAK memory bandwidth27 networkBandwidthGBps: 64, // default link to other devices (PCIe 5 x16 = 64; TB5 = 10; NVLink per the template)28 pcieGeneration: 5, pcieLanes: 16, // optional, drives the interconnect presets29 computeTFlops: { // DENSE peak per precision; the engine picks by quantization30 'float32': 104.8, 'float16': 209.5, 'bfloat16': 209.5, 'int8': 838, 'fp8': 419, 'q4': 167631 },32 powerWatts: 575, // TDP for the power/cost card (estimateDeviceTDP falls back to name heuristics)33 kernelOverheadScale: 1.5, // ONLY for non-CUDA stacks: AMD ROCm/Vulkan 1.5, Intel SYCL/XPU 2.0; Apple M5 0.6 (fit on community runs)34 prefillEfficiencyScale: 0.45, // ONLY when measured pp rates show immature prefill kernels (RDNA4 0.45, 780M 0.2, V100 0.15); a per-runtime map when only some backends miss the hardware's matrix paths (M5: { llama_cpp: 0.8, ollama: 0.8, default: 1 })35 backend: 'metal', // ONLY Apple: lets runtimes apply their Metal efficiency (llama.cpp streams at 0.66x peak, MoE layers cost 6x)36 sourceUrl: 'https://…',37 specStatus: 'verified', // 'verified' | 'preview'38 specNote: 'What was verified, what was derived, and the date.',39 type: 'GPU' // 'GPU' | 'CPU/Integrated GPU' | 'AI PC' | 'CPU/NVMe' | …40}41```4243Notes on precision keys: the planner uses `float16` for the dequantized GEMMs of weight-only quants44(q4/q3/q2 and int8/fp8 outside TensorRT-LLM/vLLM/SGLang), `fp8`/`int8` for true low-precision engines,45and `q4` only as a planning value for FP4-native kernels. If a card has no FP8 units (Ampere), omit46`fp8`; the engine falls back to int8/float16.4748`kernelOverheadScale` multiplies the fixed per-layer/per-token runtime cost (launch gaps, routing,49norms). It is a backend/driver property: CUDA = 1 (omit), ROCm/Vulkan on RDNA ≈ 1.5, SYCL/XPU ≈ 2,50Apple M1–M4 = 1, Apple M5 = 0.6 (faster GPU dispatch, fit on 11 MLX rows). Never use it to "fix"51one model.5253`prefillEfficiencyScale` (default 1) scales the prompt-processing efficiency only — for backends whose54GEMM/flash-attention paths are immature (RDNA4 WMMA 0.45, iGPU 780M 0.2, Volta 0.15). It may be a55per-runtime map (`getDevicePrefillScale`): Apple M5 uses `{ llama_cpp: 0.8, ollama: 0.8, default: 1 }`56because llama.cpp's Metal backend does not reach the Neural Accelerators MLX uses. Fit it on measured57`pp` rates (obs/pred on the gold rows with `prefillTokS`), never on decode.5859`backend: 'metal'` marks Apple templates. Framework profiles carry per-backend overrides60(`FRAMEWORK_PROFILES.llama_cpp.backends.metal = { bandwidthEfficiency: 0.66, moeOverheadScale: 6 }`):61llama.cpp's Metal backend streams weights at ~65% of peak and its `mul_mat_id` MoE layers cost ~6x the62CUDA launch overhead (fit on 26 M1 Max–M5 Max rows, median 1.01). MLX has its own constants and no63backend override. A new non-Apple backend with the same pattern gets its own key the same way.6465## 3. Where to add it (all of these)66671. **`DEVICE_TEMPLATES`** in `engine.js` (the physics engine; `index.html` loads it before the page68 script), near its family. Keys must be unique (integrity test). `npm test` re-stamps the69 `engine.js?v=<hash>` cache key in `index.html` and rebuilds `dist/` — commit those too.702. **Picker group**: `getHardwarePresetGroup` — add a regex clause only if the name does not already71 land in the right group ("NVIDIA · GeForce", "AMD · Radeon & unified", "Intel · graphics",72 "Apple silicon", "CPU, memory & storage", …).733. **Default runtime**: `getDefaultFrameworkForDevice` picks the runtime when the user leaves74 "AUTO" — what the evidence shows people actually run: Mac/M-series → MLX, H100/H200/B200/B300/75 A100/DGX Station/GB300 → vLLM, MI3xx/Gaudi/TPU/Trainium → SGLang, everything else (GeForce, RTX76 PRO, Radeon, Arc, Strix, DGX Spark, CPUs) → llama.cpp. Extend the regex if a new family should77 default differently.784. **Evidence mapping**: `HARDWARE_RULES` in `scripts/refresh-localmaxxing.mjs` — a regex on the79 community run's hardware label → this template key. Without it, runs on this hardware are not80 gold evidence and the planner cannot calibrate it. Beware of overlaps (`RTX 5090` vs `RTX 5090 D`,81 `M4 Max` vs `M4 Max (128)`, `M5 Max` before `M5 Pro`): the first matching rule wins. Gold selection82 keeps up to 4 rows per hardware template before filling by reproducibility, so one new template83 with a handful of community runs is enough to calibrate it; rows whose prompt-processing rate84 exceeds the template's dense tensor peak have their prefill dropped (prompt-cache hits), so an85 understated `computeTFlops` silently discards real prefill evidence.865. **Hardware search keywords**: `filterHardwarePresets` matches on name/key; nothing else needed.876. **Scenarios** (`loadScenarioPreset`) if the device appears in a curated multi-device setup.8889Unified-memory systems (Mac, DGX Spark, Strix Halo): `memoryGB` is the whole pool; the planner models90weights + KV + workspace against it, and overflow means swap — keep `overflowTarget` unset.91NVMe/CPU tiers: `localBandwidthGBps` is the sustained read rate the CPU path actually sees92(RAID striping and host overhead cap it below the drive sum); the 0.5 CPU-offload factor applies93only to *overflow* from a GPU, not to a CPU template's own bandwidth.9495## 4. Sanity-check9697Run a known model on the new device (see the snippet in the `add-model` skill) and compare with a98published number for the same class: decode at batch 1 ≈ `active bytes / (bandwidth × 0.78)` plus99`layers × ~45 µs × kernelOverheadScale` (more for MoE/GDN layers). For a new GPU generation with no100runs yet, compare against its predecessor scaled by bandwidth — decode scales with bandwidth, prefill101with dense TFLOPS.102103## 5. Verify104105- `npm test` (duplicate keys; `deduped catalog entries keep the physically correct specs` pins a few106 templates — extend it for a headline device). Add the device to `tests/sanity-matrix.test.mjs`107 (`SINGLE_GPUS` / `MACS`, and a `KNOWN` band when a measured number exists) so future physics108 changes cannot move it outside a realistic range unnoticed.109- `npm run refresh:localmaxxing && npm run audit:gold` when the device has community runs. Look at110 the `hardware:` group median in `node scripts/fit-decode-constants.mjs`: a median far from 1.0 on a111 non-CUDA stack usually means `kernelOverheadScale` needs fitting (use `--grid`), a median far from112 1.0 on CUDA means a spec number is wrong.113- Any run beating the physical roofline (>1.05×) on the new device means the template overstates114 nothing and understates something (bandwidth too low? memory too small → false overflow?) or the115 row is mislabeled (`-sm tensor`, fewer GPUs than the host has, speculative decoding). Root-cause;116 never absorb.117- Browser: the hardware picker search finds it, the spec meta line shows the right bandwidth/memory,118 and the topology card draws it.