mjolnir-cli 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mjolnir_cli/__init__.py +8 -0
- mjolnir_cli/__main__.py +4 -0
- mjolnir_cli/accuracy.py +333 -0
- mjolnir_cli/autoresearch.py +539 -0
- mjolnir_cli/benchy.py +475 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4/config.yaml +78 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_DFLASH2/config.yaml +103 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_DSPARK/config.yaml +102 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_FA2/config.yaml +84 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_FA4hd256/config.yaml +104 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR/config.yaml +86 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3/config.yaml +93 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_DFLASH2/config.yaml +123 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_DSPARK/config.yaml +124 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8/.env +2 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8/config.yaml +98 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT32K/.env +4 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT32K/config.yaml +98 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT64K/.env +4 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT64K/config.yaml +102 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_VT32K/.env +3 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_VT32K/config.yaml +98 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/draft_vocab.json +1 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/kv_scales.json +35197 -0
- mjolnir_cli/bundled/configs/Qwen/Qwen3.8_chat_template.jinja +456 -0
- mjolnir_cli/bundled/docker/litellm/docker-compose.yaml +113 -0
- mjolnir_cli/bundled/docker/litellm/litellm.env.example +51 -0
- mjolnir_cli/bundled/docker/litellm/litellm_config.template.example.yaml +192 -0
- mjolnir_cli/bundled/docker/perses/README.md +89 -0
- mjolnir_cli/bundled/docker/perses/dashboards/global-datasource.yaml +17 -0
- mjolnir_cli/bundled/docker/perses/dashboards/performance_statistics.yaml +771 -0
- mjolnir_cli/bundled/docker/perses/dashboards/project.yaml +13 -0
- mjolnir_cli/bundled/docker/perses/dashboards/query_statistics.yaml +399 -0
- mjolnir_cli/bundled/docker/perses/docker-compose.yaml +51 -0
- mjolnir_cli/bundled/docker/perses/perses.yml +16 -0
- mjolnir_cli/bundled/docker/perses/prometheus.template.yaml +20 -0
- mjolnir_cli/bundled/scripts/hw/setup-thor.sh +1287 -0
- mjolnir_cli/cachectl.py +146 -0
- mjolnir_cli/charts.py +1687 -0
- mjolnir_cli/cli.py +1480 -0
- mjolnir_cli/clidoc.py +170 -0
- mjolnir_cli/config.py +346 -0
- mjolnir_cli/dockerctl.py +533 -0
- mjolnir_cli/drivers/fa2_driver.py +89 -0
- mjolnir_cli/drivers/fi_driver.py +134 -0
- mjolnir_cli/e2e.py +302 -0
- mjolnir_cli/fonts/InterDisplay-Bold.ttf +0 -0
- mjolnir_cli/fonts/InterDisplay-Medium.ttf +0 -0
- mjolnir_cli/fonts/InterDisplay-Regular.ttf +0 -0
- mjolnir_cli/fonts/InterDisplay-SemiBold.ttf +0 -0
- mjolnir_cli/fonts/OFL.txt +92 -0
- mjolnir_cli/gate.py +233 -0
- mjolnir_cli/history.py +159 -0
- mjolnir_cli/interactive.py +383 -0
- mjolnir_cli/legs.py +109 -0
- mjolnir_cli/litellm.py +254 -0
- mjolnir_cli/perses.py +214 -0
- mjolnir_cli/picker.py +70 -0
- mjolnir_cli/readme.py +1020 -0
- mjolnir_cli/stack.py +340 -0
- mjolnir_cli/tasks.py +122 -0
- mjolnir_cli/theme.py +281 -0
- mjolnir_cli/thor.py +108 -0
- mjolnir_cli/verify/__init__.py +2 -0
- mjolnir_cli/verify/cli.py +816 -0
- mjolnir_cli/verify/docker.py +334 -0
- mjolnir_cli/verify/kvcache.py +180 -0
- mjolnir_cli/verify/legs.py +134 -0
- mjolnir_cli/verify/ncu.py +141 -0
- mjolnir_cli/verify/profile.py +312 -0
- mjolnir_cli/verify/report.py +177 -0
- mjolnir_cli/verify/results.py +157 -0
- mjolnir_cli/verify/spec.py +279 -0
- mjolnir_cli/verify/stages.py +817 -0
- mjolnir_cli-0.2.0.dist-info/METADATA +87 -0
- mjolnir_cli-0.2.0.dist-info/RECORD +80 -0
- mjolnir_cli-0.2.0.dist-info/WHEEL +4 -0
- mjolnir_cli-0.2.0.dist-info/entry_points.txt +2 -0
- mjolnir_cli-0.2.0.dist-info/licenses/LICENSE +202 -0
- mjolnir_cli-0.2.0.dist-info/licenses/NOTICE +64 -0
mjolnir_cli/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""mjolnir-cli — the ``mjolnir`` command for Jetson AGX Thor (sm_110).
|
|
2
|
+
|
|
3
|
+
Serve the Mjolnir vLLM image, verify and benchmark the Mjolnir decode
|
|
4
|
+
kernel against FlashInfer and FlashAttention 2, and render the
|
|
5
|
+
data-driven README charts — one command surface.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.2.0"
|
mjolnir_cli/__main__.py
ADDED
mjolnir_cli/accuracy.py
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"""``mjolnir bench accuracy`` — does a serving config keep the model's quality?
|
|
2
|
+
|
|
3
|
+
Each leg (a config, optionally image-pinned, like ``bench ab``) is served in
|
|
4
|
+
turn and benchmarked with `tool-eval-bench
|
|
5
|
+
<https://github.com/SeraphimSerapis/tool-eval-bench>`_ over the OpenAI
|
|
6
|
+
endpoint. The default run is the standard 69 tool-calling scenarios plus the
|
|
7
|
+
28 Hard Mode ones (``tool-eval-bench run --hardmode``); any other
|
|
8
|
+
tool-eval-bench invocation is forwarded verbatim after ``--`` — GSM8K, MMLU
|
|
9
|
+
and IFEval go through the same adapter (``-- --gsm8k --mmlu --ifeval
|
|
10
|
+
--skip-tool-eval``).
|
|
11
|
+
|
|
12
|
+
tool-eval-bench keeps its results in ``./data/benchmarks.sqlite`` and
|
|
13
|
+
``./runs/`` of the directory it runs in; every leg runs in its own directory
|
|
14
|
+
under ``benchmarks/raw/accuracy/<run>/<leg>/``, so the database holds exactly
|
|
15
|
+
that leg's runs. :func:`summarize_db` reads it back into the committed
|
|
16
|
+
``accuracy.json`` (``mjolnir.accuracy/1``): per suite the score, per
|
|
17
|
+
tool-call category the share of points, per scenario the points — what the
|
|
18
|
+
docs table and charts are rendered from.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import shlex
|
|
26
|
+
import shutil
|
|
27
|
+
import sqlite3
|
|
28
|
+
import subprocess
|
|
29
|
+
import sys
|
|
30
|
+
import time
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from statistics import mean
|
|
33
|
+
|
|
34
|
+
FORMAT = "mjolnir.accuracy/1"
|
|
35
|
+
TOOL_REPO = "https://github.com/SeraphimSerapis/tool-eval-bench"
|
|
36
|
+
# pinned: a score is only comparable to one produced by the same scenario set
|
|
37
|
+
TOOL_COMMIT = "b60e2f00f6c44352a3680356e7c7948f777c5280"
|
|
38
|
+
TOOL_SPEC = f"tool-eval-bench[hf] @ git+{TOOL_REPO}.git@{TOOL_COMMIT}"
|
|
39
|
+
DEFAULT_ARGS = ("run", "--hardmode", "--seed", "42", "--timeout", "600")
|
|
40
|
+
HARD_CATEGORY = "P" # tool-eval-bench's Hard Mode category
|
|
41
|
+
|
|
42
|
+
# suite -> (label, unit note); order = table / chart order
|
|
43
|
+
SUITES = {
|
|
44
|
+
"tools": "Tool calls",
|
|
45
|
+
"hardmode": "Hard Mode",
|
|
46
|
+
"gsm8k": "GSM8K",
|
|
47
|
+
"mmlu": "MMLU",
|
|
48
|
+
"ifeval": "IFEval",
|
|
49
|
+
"needle": "Needle",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class AccuracyError(RuntimeError):
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def find_tool() -> list[str]:
|
|
58
|
+
"""The tool-eval-bench invocation: ``$MJOLNIR_TOOL_EVAL_BENCH`` if set,
|
|
59
|
+
else the pinned commit through ``uvx`` (no install needed), else one on
|
|
60
|
+
PATH."""
|
|
61
|
+
env = os.environ.get("MJOLNIR_TOOL_EVAL_BENCH", "").strip()
|
|
62
|
+
if env:
|
|
63
|
+
return shlex.split(env)
|
|
64
|
+
if shutil.which("uvx"):
|
|
65
|
+
return ["uvx", "--from", TOOL_SPEC, "tool-eval-bench"]
|
|
66
|
+
if shutil.which("tool-eval-bench"):
|
|
67
|
+
return ["tool-eval-bench"]
|
|
68
|
+
raise AccuracyError(f"tool-eval-bench not found — install uv (uvx runs it) or: uv tool install '{TOOL_SPEC}'")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def tool_args(passthrough: list[str] | tuple[str, ...], base_url: str) -> list[str]:
|
|
72
|
+
"""The forwarded tool-eval-bench arguments (the default run when none),
|
|
73
|
+
pointed at the leg's server unless they name one."""
|
|
74
|
+
args = list(passthrough) or list(DEFAULT_ARGS)
|
|
75
|
+
if not any(a == "--base-url" or a.startswith("--base-url=") for a in args):
|
|
76
|
+
args += ["--base-url", base_url]
|
|
77
|
+
return args
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ── reading tool-eval-bench's database ──────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _loads(v) -> dict:
|
|
84
|
+
try:
|
|
85
|
+
return json.loads(v) if v else {}
|
|
86
|
+
except (TypeError, json.JSONDecodeError):
|
|
87
|
+
return {}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _pct(earned: float, mx: float) -> float | None:
|
|
91
|
+
return round(earned / mx * 100, 2) if mx else None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def summarize_db(db: Path) -> dict:
|
|
95
|
+
"""tool-eval-bench's SQLite → ``{"suites": {suite: {...}}, "categories":
|
|
96
|
+
{...}, "scenarios": {...}}``. Several completed runs of a kind (``--trials``)
|
|
97
|
+
are averaged; each suite keeps its per-run scores."""
|
|
98
|
+
if not db.exists():
|
|
99
|
+
return {"suites": {}, "categories": {}, "scenarios": {}, "runs": []}
|
|
100
|
+
con = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
|
|
101
|
+
try:
|
|
102
|
+
rows = con.execute(
|
|
103
|
+
"SELECT run_id, created_at, status, run_type, scores_json, config_json FROM scenario_runs "
|
|
104
|
+
"ORDER BY created_at"
|
|
105
|
+
).fetchall()
|
|
106
|
+
finally:
|
|
107
|
+
con.close()
|
|
108
|
+
per_suite: dict[str, list[dict]] = {}
|
|
109
|
+
cats: dict[str, dict] = {}
|
|
110
|
+
scen: dict[str, list[float]] = {}
|
|
111
|
+
runs = []
|
|
112
|
+
for run_id, created, status, run_type, scores_json, config_json in rows:
|
|
113
|
+
runs.append({"run_id": run_id, "created_at": created, "status": status, "run_type": run_type})
|
|
114
|
+
if status != "completed":
|
|
115
|
+
continue
|
|
116
|
+
sc = _loads(scores_json)
|
|
117
|
+
if run_type == "tool_eval":
|
|
118
|
+
cs = sc.get("category_scores") or []
|
|
119
|
+
std = [c for c in cs if c.get("category") != HARD_CATEGORY]
|
|
120
|
+
hard = [c for c in cs if c.get("category") == HARD_CATEGORY]
|
|
121
|
+
for name, group in (("tools", std), ("hardmode", hard)):
|
|
122
|
+
e, m = sum(c.get("earned", 0) for c in group), sum(c.get("max", 0) for c in group)
|
|
123
|
+
if m:
|
|
124
|
+
per_suite.setdefault(name, []).append(
|
|
125
|
+
{
|
|
126
|
+
"score": _pct(e, m),
|
|
127
|
+
"earned": e,
|
|
128
|
+
"max": m,
|
|
129
|
+
"pass": sum(c.get("pass_count", 0) for c in group),
|
|
130
|
+
"partial": sum(c.get("partial_count", 0) for c in group),
|
|
131
|
+
"fail": sum(c.get("fail_count", 0) for c in group),
|
|
132
|
+
"final_score": sc.get("final_score"),
|
|
133
|
+
"completion_rate": sc.get("completion_rate", 100.0),
|
|
134
|
+
"safety_warnings": len(sc.get("safety_warnings") or []),
|
|
135
|
+
"run_id": run_id,
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
for c in cs:
|
|
139
|
+
d = cats.setdefault(c["category"], {"label": c.get("label", c["category"]), "percent": []})
|
|
140
|
+
if c.get("max"):
|
|
141
|
+
d["percent"].append(_pct(c.get("earned", 0), c["max"]))
|
|
142
|
+
for r in sc.get("scenario_results") or []:
|
|
143
|
+
sid = r.get("scenario_id")
|
|
144
|
+
if sid is not None and r.get("points") is not None:
|
|
145
|
+
scen.setdefault(sid, []).append(float(r["points"]))
|
|
146
|
+
else:
|
|
147
|
+
acc = sc.get("accuracy", sc.get("final_score"))
|
|
148
|
+
if acc is None:
|
|
149
|
+
continue
|
|
150
|
+
extra = {
|
|
151
|
+
k: sc[k]
|
|
152
|
+
for k in ("instruction_accuracy", "correct", "total", "errors", "completion_rate", "truncated")
|
|
153
|
+
if k in sc
|
|
154
|
+
}
|
|
155
|
+
per_suite.setdefault(run_type, []).append({"score": round(float(acc), 2), **extra, "run_id": run_id})
|
|
156
|
+
suites = {}
|
|
157
|
+
for name, items in per_suite.items():
|
|
158
|
+
scores = [i["score"] for i in items if i.get("score") is not None]
|
|
159
|
+
if not scores:
|
|
160
|
+
continue
|
|
161
|
+
last = dict(items[-1])
|
|
162
|
+
last.pop("score", None)
|
|
163
|
+
suites[name] = {"score": round(mean(scores), 2), "scores": scores, "n": len(scores), **last}
|
|
164
|
+
return {
|
|
165
|
+
"suites": suites,
|
|
166
|
+
"categories": {
|
|
167
|
+
k: {"label": v["label"], "percent": round(mean(v["percent"]), 2)}
|
|
168
|
+
for k, v in sorted(cats.items())
|
|
169
|
+
if v["percent"]
|
|
170
|
+
},
|
|
171
|
+
"scenarios": {k: round(mean(v), 3) for k, v in sorted(scen.items())},
|
|
172
|
+
"runs": runs,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# ── the run ─────────────────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def leg_id(model: str, image: str, config: str) -> str:
|
|
180
|
+
side = "official" if not _is_mjolnir(image) else "mjolnir"
|
|
181
|
+
return f"{side}-{config}"
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _is_mjolnir(image: str) -> bool:
|
|
185
|
+
from mjolnir_cli import e2e
|
|
186
|
+
|
|
187
|
+
return e2e.is_mjolnir({"image": image})
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def kv_label(rec: dict) -> str:
|
|
191
|
+
kv = str(rec.get("kv_cache_dtype") or "auto")
|
|
192
|
+
return {"fp6_e2m3": "fp6 e2m3 KV", "fp8": "fp8 KV", "fp8_e4m3": "fp8 KV", "auto": "bf16 KV"}.get(kv, f"{kv} KV")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def leg_label(rec: dict) -> str:
|
|
196
|
+
"""'official vLLM · FlashInfer · fp8 KV', 'Mjolnir · fp6 e2m3 KV + fp8 lm_head + 64K draft vocab', ...
|
|
197
|
+
— every label names the KV-cache format."""
|
|
198
|
+
from mjolnir_cli.config import BACKEND_LABELS
|
|
199
|
+
|
|
200
|
+
cfg = str(rec.get("config", ""))
|
|
201
|
+
kv = kv_label(rec)
|
|
202
|
+
if not _is_mjolnir(str(rec.get("image", ""))):
|
|
203
|
+
return f"official vLLM · {BACKEND_LABELS.get(cfg, cfg)} · {kv}"
|
|
204
|
+
if "MJOLNIR" not in cfg:
|
|
205
|
+
return f"Mjolnir image · {BACKEND_LABELS.get(cfg, cfg)} · {kv}"
|
|
206
|
+
extra = "".join(
|
|
207
|
+
f" + {name}"
|
|
208
|
+
for tag, name in (("LMH8", "fp8 lm_head"), ("VT32K", "32K draft vocab"), ("VT64K", "64K draft vocab"))
|
|
209
|
+
if tag in cfg.split("_")
|
|
210
|
+
)
|
|
211
|
+
return f"Mjolnir · {kv}{extra}"
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def run_accuracy(
|
|
215
|
+
s,
|
|
216
|
+
layout,
|
|
217
|
+
legs: list[tuple[str, str, str]],
|
|
218
|
+
passthrough: list[str],
|
|
219
|
+
restart: bool = True,
|
|
220
|
+
) -> Path:
|
|
221
|
+
"""Serve each ``(model, image, config)`` leg (restarted per leg unless
|
|
222
|
+
``restart`` is off: then the running server is the one leg), run
|
|
223
|
+
tool-eval-bench against it, and write ``accuracy.json``. Returns its path."""
|
|
224
|
+
from mjolnir_cli import dockerctl
|
|
225
|
+
from mjolnir_cli.config import config_field
|
|
226
|
+
|
|
227
|
+
tool = find_tool()
|
|
228
|
+
run_id = time.strftime("%Y%m%d-%H%M%S")
|
|
229
|
+
out_dir = layout.raw_dir / "accuracy" / run_id
|
|
230
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
231
|
+
doc = {
|
|
232
|
+
"format": FORMAT,
|
|
233
|
+
"run_id": run_id,
|
|
234
|
+
"created": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
235
|
+
"tool": {"repo": TOOL_REPO, "commit": TOOL_COMMIT, "command": tool},
|
|
236
|
+
"args": list(passthrough) or list(DEFAULT_ARGS),
|
|
237
|
+
"legs": [],
|
|
238
|
+
}
|
|
239
|
+
out = out_dir / "accuracy.json"
|
|
240
|
+
for model, image, config in legs:
|
|
241
|
+
s.model, s.image, s.quant = model, image, config
|
|
242
|
+
lid = leg_id(model, image, config)
|
|
243
|
+
leg_dir = out_dir / lid
|
|
244
|
+
leg_dir.mkdir(parents=True, exist_ok=True)
|
|
245
|
+
print(f"\n{'=' * 62}\n accuracy leg: {model}/{config} (image {image})\n{'=' * 62}", flush=True)
|
|
246
|
+
rec = {
|
|
247
|
+
"id": lid,
|
|
248
|
+
"model": model,
|
|
249
|
+
"image": image,
|
|
250
|
+
"config": config,
|
|
251
|
+
"kv_cache_dtype": config_field(s.config_path, "kv-cache-dtype") or "auto",
|
|
252
|
+
"env": dockerctl.config_env(s),
|
|
253
|
+
}
|
|
254
|
+
rec["label"] = leg_label(rec)
|
|
255
|
+
try:
|
|
256
|
+
if restart:
|
|
257
|
+
dockerctl.serve_down(s)
|
|
258
|
+
dockerctl.serve_up(s, wait=True)
|
|
259
|
+
cmd = tool + tool_args(passthrough, s.base_url)
|
|
260
|
+
print(f"[mjolnir] {' '.join(shlex.quote(c) for c in cmd)}\n[mjolnir] in {leg_dir}", flush=True)
|
|
261
|
+
t0 = time.time()
|
|
262
|
+
rc = subprocess.call(cmd, cwd=leg_dir)
|
|
263
|
+
rec["exit_code"] = rc
|
|
264
|
+
rec["wall_s"] = round(time.time() - t0, 1)
|
|
265
|
+
if rc not in (0, 2): # 2: e.g. --fail-on-safety tripped — the scores still stand
|
|
266
|
+
print(f"[mjolnir] tool-eval-bench exited {rc} — keeping what it saved", file=sys.stderr)
|
|
267
|
+
dockerctl.dump_server_log(None, leg_dir / "vllm-server.log")
|
|
268
|
+
finally:
|
|
269
|
+
if restart:
|
|
270
|
+
print(f" leg done: stopping the vLLM server ({image})", flush=True)
|
|
271
|
+
dockerctl.serve_down(s)
|
|
272
|
+
rec.update(summarize_db(leg_dir / "data" / "benchmarks.sqlite"))
|
|
273
|
+
doc["legs"].append(rec)
|
|
274
|
+
out.write_text(json.dumps(doc, indent=1) + "\n")
|
|
275
|
+
return out
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# ── reading the committed results ───────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def load_runs(layout) -> list[dict]:
|
|
282
|
+
root = layout.raw_dir / "accuracy"
|
|
283
|
+
docs = []
|
|
284
|
+
for p in sorted(root.glob("*/accuracy.json")):
|
|
285
|
+
try:
|
|
286
|
+
d = json.loads(p.read_text())
|
|
287
|
+
except (OSError, json.JSONDecodeError):
|
|
288
|
+
continue
|
|
289
|
+
if d.get("format") == FORMAT:
|
|
290
|
+
docs.append(d)
|
|
291
|
+
return sorted(docs, key=lambda d: d.get("run_id", ""))
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def latest_legs(docs: list[dict]) -> list[dict]:
|
|
295
|
+
"""One row per (model, image, config): every suite from the newest run
|
|
296
|
+
that measured it (an A/B run and a later GSM8K run merge into one row).
|
|
297
|
+
Order: first appearance."""
|
|
298
|
+
rows: dict[tuple, dict] = {}
|
|
299
|
+
for d in docs:
|
|
300
|
+
for leg in d.get("legs", []):
|
|
301
|
+
key = (leg.get("model"), leg.get("image"), leg.get("config"))
|
|
302
|
+
row = rows.setdefault(
|
|
303
|
+
key,
|
|
304
|
+
{k: leg.get(k) for k in ("id", "model", "image", "config", "label", "kv_cache_dtype", "env")}
|
|
305
|
+
| {"suites": {}, "categories": {}, "scenarios": {}, "runs": {}},
|
|
306
|
+
)
|
|
307
|
+
row["kv_cache_dtype"] = leg.get("kv_cache_dtype") or row.get("kv_cache_dtype")
|
|
308
|
+
row["label"] = leg_label(row) # recomputed: labels improve after a run is stored
|
|
309
|
+
for name, suite in (leg.get("suites") or {}).items():
|
|
310
|
+
row["suites"][name] = suite
|
|
311
|
+
row["runs"][name] = {"run_id": d.get("run_id"), "args": d.get("args")}
|
|
312
|
+
if (leg.get("suites") or {}).keys() & {"tools", "hardmode"}:
|
|
313
|
+
row["categories"] = leg.get("categories") or row["categories"]
|
|
314
|
+
row["scenarios"] = leg.get("scenarios") or row["scenarios"]
|
|
315
|
+
return [r for r in rows.values() if r["suites"]]
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def baseline(rows: list[dict]) -> dict | None:
|
|
319
|
+
"""The reference row for deltas: native FlashInfer on the official image
|
|
320
|
+
when measured, else the first row."""
|
|
321
|
+
for r in rows:
|
|
322
|
+
if not _is_mjolnir(str(r.get("image", ""))) and r.get("config") == "NVFP4":
|
|
323
|
+
return r
|
|
324
|
+
return rows[0] if rows else None
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def suites_in(rows: list[dict]) -> list[str]:
|
|
328
|
+
seen = {n for r in rows for n in r["suites"]}
|
|
329
|
+
return [n for n in SUITES if n in seen] + sorted(seen - set(SUITES))
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def suite_label(name: str) -> str:
|
|
333
|
+
return SUITES.get(name, name.upper())
|