Files
Vijay Janapa Reddi 96e4bd9b09 feat(staffml): take hardware, network, and model numbers from MLSysIM
StaffML is a static site and cannot run MLSysIM, so its numbers lived in a
hand-copied table that cited a deleted mlsysim/core/constants.py. Five
devices had drifted: Jetson Orin NX showed its FP16 rate under an INT8
label, the A17 Pro listed 100 GB/s against 51.2 GB/s, B200 memory was 192
against 180, TPU v5p had the wrong year, and every ESP32-S3 field was off.

scripts/export_mlsysim_data.py now reads the MLSysIM registries straight
from the repo and writes src/data/mlsysim.json, which hardware.ts serves.
Each row records the registry entry it came from. Interconnect and latency
values move to MLSysIM's measured or sourced figures (NVLink B200 0.5 us,
InfiniBand XDR 5 us, 100GbE 50 us, Hopper HBM 273 ns). The cross-US fiber
row stays local because MLSysIM has no route distance. The simulator's
flops_per_token is 6 x parameters, now labeled as training cost.
2026-10-27 20:27:16 +01:00

217 lines
8.4 KiB
Python

#!/usr/bin/env python3
"""Export the MLSysIM numbers StaffML displays into src/data/mlsysim.json.
StaffML is a static site, so it cannot import MLSysIM at runtime. This script
reads the MLSysIM registries straight from the repo (folder-based import, no
pip install of mlsysim) and writes the hardware reference, interconnect,
latency, and simulator model tables that src/lib/hardware.ts serves.
Every StaffML build in CI (dev validate, dev preview, live publish) runs this
before `npm run build`, so a deployed site always carries the current MLSysIM
values. The committed JSON keeps local `npm run dev` working without Python.
Usage (from the repo root or anywhere):
python3 staffml/app/scripts/export_mlsysim_data.py # write
python3 staffml/app/scripts/export_mlsysim_data.py --check # fail if stale
python3 staffml/app/scripts/export_mlsysim_data.py --print-deps
History: 2026-10-27, replaces a hand-copied table in hardware.ts that cited a
deleted mlsysim/core/constants.py and had drifted on four devices.
"""
from __future__ import annotations
import argparse
import difflib
import json
import sys
import tomllib
from pathlib import Path
REPO = Path(__file__).resolve().parents[3]
MLSYSIM_ROOT = REPO / "mlsysim"
OUT = REPO / "staffml" / "app" / "src" / "data" / "mlsysim.json"
# Which registry entries StaffML shows, in display order. Display names, tiers,
# and memory-type labels are presentation; every number comes from MLSysIM.
# precision: the precision_flops key to show, or "peak" for peak_flops.
HARDWARE = [
("NVIDIA H100 SXM", "cloud", "Cloud.H100", "fp16_dense", "FP16", "HBM3"),
("NVIDIA H200", "cloud", "Cloud.H200", "peak", "FP16", "HBM3e"),
("NVIDIA B200", "cloud", "Cloud.B200", "fp16_dense", "FP16", "HBM3e"),
("NVIDIA A100 80GB", "cloud", "Cloud.A100", "fp16_dense", "FP16", "HBM2e"),
("AMD MI300X", "cloud", "Cloud.MI300X", "peak", "FP16", "HBM3"),
("Google TPU v5p", "cloud", "Cloud.TPUv5p", "peak", "BF16", "HBM"),
("Jetson Orin NX", "edge", "Edge.JetsonOrinNX", "int8", "INT8", "LPDDR5"),
("Google Coral Edge TPU", "edge", "Edge.Coral", "peak", "INT8", "DRAM"),
("Apple A17 Pro", "mobile", "Mobile.iPhone15Pro", "peak", "INT8", "LPDDR5"),
("Snapdragon 8 Gen 3", "mobile", "Mobile.Snapdragon8Gen3", "peak", "INT8", "LPDDR5X"),
("ESP32-S3", "tinyml", "Tiny.ESP32_S3", "int8", "INT8", "Flash + SRAM"),
]
# (display name, source) for the interconnect table. A "nvlink:" source reads
# a GPU's NVLink; "pcie:" its host interface; "fabric:" a Systems.Fabrics entry.
INTERCONNECTS = [
("NVLink H100", "nvlink:Cloud.H100"),
("NVLink B200", "nvlink:Cloud.B200"),
("PCIe Gen5 x16", "pcie:Cloud.H100"),
("InfiniBand NDR", "fabric:InfiniBand_NDR"),
("InfiniBand XDR", "fabric:InfiniBand_XDR"),
("Ethernet 100GbE", "fabric:Ethernet_100G"),
]
# (display name, Tech path or fabric) for the latency ladder.
LATENCY = [
("L1 Cache / Register", "Memory.L1"),
("L2 Cache", "Memory.L2"),
("HBM Access (Hopper)", "Memory.HopperGlobalMemory"),
("NVLink Transfer", "Interconnect.NVLink"),
("PCIe Gen5", "Interconnect.PCIeGen5"),
("InfiniBand NDR", "fabric:InfiniBand_NDR"),
("NVMe SSD Read", "Storage.NvmeGen4"),
]
MODELS = [
("GPT-2 (1.5B)", "Language.GPT2"),
("Llama-2-7B", "Language.Llama2_7B"),
("Llama-2-70B", "Language.Llama2_70B"),
("Llama-3.1-405B", "Language.Llama3_405B"),
("BERT-Large (340M)", "Language.BERT_Large"),
]
def mlsysim_deps() -> list[str]:
meta = tomllib.loads((MLSYSIM_ROOT / "pyproject.toml").read_text())
return list(meta["project"]["dependencies"])
def walk(root, dotted: str):
obj = root
for part in dotted.split("."):
obj = getattr(obj, part)
return obj
def num(value: float) -> float | int:
"""Round away float noise so the JSON diff stays readable."""
value = float(f"{value:.6g}")
return int(value) if value.is_integer() else value
def build() -> dict:
sys.path.insert(0, str(MLSYSIM_ROOT))
try:
from mlsysim.hardware.registry import Hardware
from mlsysim.models.registry import Models
from mlsysim.systems.registry import Systems
except ImportError as exc: # pragma: no cover - environment problem
sys.exit(
f"Cannot import MLSysIM from {MLSYSIM_ROOT}: {exc}\n"
"Install its dependencies first:\n"
" python3 -m pip install $(python3 staffml/app/scripts/export_mlsysim_data.py --print-deps)"
)
hardware = []
for name, tier, ref, precision, unit, memory_type in HARDWARE:
hw = walk(Hardware, ref)
rate = hw.compute.peak_flops if precision == "peak" else hw.compute.precision_flops[precision]
is_ops = "FLOP" not in str(rate.units)
if is_ops != (unit == "INT8"):
sys.exit(f"{ref}: {precision} is {rate.units}, which does not match the {unit} label")
tera = rate.to("TOPS" if is_ops else "TFLOPs/s").magnitude
hardware.append({
"name": name,
"tier": tier,
"compute_tflops": num(tera),
"compute_unit": unit,
"bandwidth_tbs": num(hw.memory.bandwidth.to("TB/s").magnitude),
# Vendors quote HBM/LPDDR capacity in binary units ("80 GB" is
# 80 GiB), so this field carries the GiB value under the GB label.
"memory_gb": num(hw.memory.capacity.to("GiB").magnitude),
"memory_type": memory_type,
"tdp_w": num(hw.tdp.to("W").magnitude),
"year": int(hw.release_year),
"mlsysim": f"Hardware.{ref}",
})
interconnects = []
for name, source in INTERCONNECTS:
kind, ref = source.split(":")
if kind == "fabric":
link = getattr(Systems.Fabrics, ref)
bw, lat = link.bandwidth, link.latency
else:
hw = walk(Hardware, ref)
link = hw.nvlink if kind == "nvlink" else hw.interconnect
bw = link.bandwidth
lat = link.latency if link.latency is not None else Hardware.Tech.Interconnect.PCIeGen5.latency
interconnects.append({
"name": name,
"bandwidth_gbs": num(bw.to("GB/s").magnitude),
"latency_us": num(lat.to("us").magnitude),
"mlsysim": f"{'Systems.Fabrics' if kind == 'fabric' else 'Hardware'}.{ref}",
})
latency = []
for name, ref in LATENCY:
if ref.startswith("fabric:"):
q = getattr(Systems.Fabrics, ref.split(":")[1]).latency
else:
q = walk(Hardware.Tech, ref).latency
latency.append({"operation": name, "latency_ns": num(q.to("ns").magnitude), "mlsysim": ref})
models = []
for name, ref in MODELS:
m = walk(Models, ref)
params = m.parameters.to("param").magnitude
models.append({
"name": name,
"params_b": num(params / 1e9),
"layers": int(m.layers),
"hidden_dim": int(m.hidden_dim),
"heads": int(m.heads),
# Training cost per token: 2N forward plus 4N backward.
"flops_per_token": num(6 * params),
"mlsysim": f"Models.{ref}",
})
return {
"_generated_by": "staffml/app/scripts/export_mlsysim_data.py (do not edit by hand)",
"hardware": hardware,
"interconnects": interconnects,
"latency": latency,
"models": models,
}
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
ap.add_argument("--check", action="store_true", help="fail if the committed JSON differs from MLSysIM")
ap.add_argument("--print-deps", action="store_true", help="print MLSysIM's pip requirements and exit")
args = ap.parse_args()
if args.print_deps:
print(" ".join(mlsysim_deps()))
return 0
text = json.dumps(build(), indent=2, ensure_ascii=False) + "\n"
if args.check:
current = OUT.read_text() if OUT.exists() else ""
if current == text:
print(f"✓ {OUT.relative_to(REPO)} matches MLSysIM")
return 0
sys.stdout.writelines(difflib.unified_diff(
current.splitlines(keepends=True), text.splitlines(keepends=True),
fromfile="committed", tofile="mlsysim"))
print(f"\n✗ {OUT.relative_to(REPO)} is out of date with MLSysIM. Regenerate it with:\n"
" python3 staffml/app/scripts/export_mlsysim_data.py")
return 1
OUT.write_text(text)
print(f"wrote {OUT.relative_to(REPO)}")
return 0
if __name__ == "__main__":
sys.exit(main())