mjolnir-cli 0.2.0__tar.gz → 0.2.1__tar.gz
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-0.2.0 → mjolnir_cli-0.2.1}/.gitignore +6 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/PKG-INFO +2 -4
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/__init__.py +1 -1
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/__main__.py +2 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/accuracy.py +101 -8
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/autoresearch.py +15 -16
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/benchy.py +7 -5
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/kv_scales.json +18842 -18842
- mjolnir_cli-0.2.1/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_DFLASH/.env +4 -0
- mjolnir_cli-0.2.1/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_DFLASH/config.yaml +42 -0
- mjolnir_cli-0.2.1/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR/config.yaml +55 -0
- mjolnir_cli-0.2.1/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR_E2M3/.env +3 -0
- mjolnir_cli-0.2.1/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR_E2M3/config.yaml +48 -0
- mjolnir_cli-0.2.1/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR_E2M3_LMH8_VT64K/.env +3 -0
- mjolnir_cli-0.2.1/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR_E2M3_LMH8_VT64K/config.yaml +41 -0
- mjolnir_cli-0.2.1/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR_E2M3_VT64K/.env +2 -0
- mjolnir_cli-0.2.1/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR_E2M3_VT64K/config.yaml +38 -0
- mjolnir_cli-0.2.1/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/draft_vocab.json +1 -0
- mjolnir_cli-0.2.1/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/kv_scales.json +11425 -0
- mjolnir_cli-0.2.0/mjolnir_cli/bundled/scripts/hw/setup-thor.sh → mjolnir_cli-0.2.1/mjolnir_cli/bundled/scripts/hw/setup-thor-steps.sh +2 -391
- mjolnir_cli-0.2.1/mjolnir_cli/bundled/scripts/hw/setup-thor.sh +395 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/cachectl.py +2 -3
- mjolnir_cli-0.2.1/mjolnir_cli/charts.py +539 -0
- mjolnir_cli-0.2.1/mjolnir_cli/charts_base.py +314 -0
- mjolnir_cli-0.2.1/mjolnir_cli/charts_e2e.py +420 -0
- mjolnir_cli-0.2.1/mjolnir_cli/charts_serving.py +541 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/cli.py +275 -820
- mjolnir_cli-0.2.1/mjolnir_cli/cli_bench.py +535 -0
- mjolnir_cli-0.2.1/mjolnir_cli/cli_shared.py +42 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/clidoc.py +1 -1
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/config.py +60 -9
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/dockerctl.py +69 -20
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/drivers/fa2_driver.py +16 -16
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/drivers/fi_driver.py +1 -1
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/e2e.py +23 -8
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/gate.py +4 -4
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/history.py +3 -3
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/litellm.py +4 -4
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/perses.py +6 -6
- mjolnir_cli-0.2.1/mjolnir_cli/py.typed +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/readme.py +133 -451
- mjolnir_cli-0.2.1/mjolnir_cli/readme_tables.py +400 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/stack.py +8 -7
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/tasks.py +1 -1
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/theme.py +13 -14
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/verify/cli.py +37 -37
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/verify/docker.py +13 -4
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/verify/kvcache.py +1 -1
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/verify/legs.py +1 -1
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/verify/ncu.py +45 -15
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/verify/profile.py +7 -5
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/verify/report.py +1 -2
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/verify/results.py +3 -3
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/verify/stages.py +16 -9
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/pyproject.toml +1 -3
- mjolnir_cli-0.2.0/mjolnir_cli/charts.py +0 -1687
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/LICENSE +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/NOTICE +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/README.md +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/hatch_build.py +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4/config.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_DFLASH2/config.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_DSPARK/config.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_FA2/config.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_FA4hd256/config.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR/config.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3/config.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_DFLASH2/config.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_DSPARK/config.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8/.env +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8/config.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT32K/.env +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT32K/config.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT64K/.env +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT64K/config.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_VT32K/.env +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_VT32K/config.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/draft_vocab.json +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8_chat_template.jinja +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/docker/litellm/docker-compose.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/docker/litellm/litellm.env.example +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/docker/litellm/litellm_config.template.example.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/docker/perses/README.md +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/docker/perses/dashboards/global-datasource.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/docker/perses/dashboards/performance_statistics.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/docker/perses/dashboards/project.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/docker/perses/dashboards/query_statistics.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/docker/perses/docker-compose.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/docker/perses/perses.yml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/bundled/docker/perses/prometheus.template.yaml +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/fonts/InterDisplay-Bold.ttf +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/fonts/InterDisplay-Medium.ttf +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/fonts/InterDisplay-Regular.ttf +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/fonts/InterDisplay-SemiBold.ttf +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/fonts/OFL.txt +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/interactive.py +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/legs.py +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/picker.py +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/thor.py +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/verify/__init__.py +0 -0
- {mjolnir_cli-0.2.0 → mjolnir_cli-0.2.1}/mjolnir_cli/verify/spec.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mjolnir-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Serve, benchmark and document LLMs on NVIDIA Jetson AGX Thor: YAML model configs, the Mjolnir vLLM image, KV-cache calibration, kernel / serving / accuracy benchmarks and auto-rendered reports.
|
|
5
5
|
Project-URL: Homepage, https://github.com/thor-arena/mjolnir
|
|
6
6
|
Project-URL: Documentation, https://github.com/thor-arena/mjolnir/tree/main/docs
|
|
@@ -18,13 +18,11 @@ Classifier: Environment :: Console
|
|
|
18
18
|
Classifier: Intended Audience :: Developers
|
|
19
19
|
Classifier: Operating System :: POSIX :: Linux
|
|
20
20
|
Classifier: Programming Language :: Python :: 3
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
23
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
25
23
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
26
24
|
Classifier: Topic :: System :: Benchmark
|
|
27
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.12
|
|
28
26
|
Requires-Dist: jinja2>=3.1.6
|
|
29
27
|
Requires-Dist: pyyaml>=6.0.3
|
|
30
28
|
Requires-Dist: rich>=15.0.0
|
|
@@ -31,6 +31,8 @@ import time
|
|
|
31
31
|
from pathlib import Path
|
|
32
32
|
from statistics import mean
|
|
33
33
|
|
|
34
|
+
from mjolnir_cli.config import by_model, model_of, model_short
|
|
35
|
+
|
|
34
36
|
FORMAT = "mjolnir.accuracy/1"
|
|
35
37
|
TOOL_REPO = "https://github.com/SeraphimSerapis/tool-eval-bench"
|
|
36
38
|
# pinned: a score is only comparable to one produced by the same scenario set
|
|
@@ -109,7 +111,7 @@ def summarize_db(db: Path) -> dict:
|
|
|
109
111
|
cats: dict[str, dict] = {}
|
|
110
112
|
scen: dict[str, list[float]] = {}
|
|
111
113
|
runs = []
|
|
112
|
-
for run_id, created, status, run_type, scores_json,
|
|
114
|
+
for run_id, created, status, run_type, scores_json, _config_json in rows:
|
|
113
115
|
runs.append({"run_id": run_id, "created_at": created, "status": status, "run_type": run_type})
|
|
114
116
|
if status != "completed":
|
|
115
117
|
continue
|
|
@@ -160,6 +162,11 @@ def summarize_db(db: Path) -> dict:
|
|
|
160
162
|
continue
|
|
161
163
|
last = dict(items[-1])
|
|
162
164
|
last.pop("score", None)
|
|
165
|
+
if all("max" in i for i in items): # every trial's points: deltas rescale each trial exactly
|
|
166
|
+
last["earneds"], last["maxes"] = [i["earned"] for i in items], [i["max"] for i in items]
|
|
167
|
+
comps = [i["completion_rate"] for i in items if i.get("completion_rate") is not None]
|
|
168
|
+
if comps:
|
|
169
|
+
last["completion_rate"] = min(comps) # the worst trial: an exclusion in any trial shows
|
|
163
170
|
suites[name] = {"score": round(mean(scores), 2), "scores": scores, "n": len(scores), **last}
|
|
164
171
|
return {
|
|
165
172
|
"suites": suites,
|
|
@@ -298,7 +305,7 @@ def latest_legs(docs: list[dict]) -> list[dict]:
|
|
|
298
305
|
rows: dict[tuple, dict] = {}
|
|
299
306
|
for d in docs:
|
|
300
307
|
for leg in d.get("legs", []):
|
|
301
|
-
key = (leg
|
|
308
|
+
key = (model_of(leg), leg.get("image"), leg.get("config"))
|
|
302
309
|
row = rows.setdefault(
|
|
303
310
|
key,
|
|
304
311
|
{k: leg.get(k) for k in ("id", "model", "image", "config", "label", "kv_cache_dtype", "env")}
|
|
@@ -312,16 +319,102 @@ def latest_legs(docs: list[dict]) -> list[dict]:
|
|
|
312
319
|
if (leg.get("suites") or {}).keys() & {"tools", "hardmode"}:
|
|
313
320
|
row["categories"] = leg.get("categories") or row["categories"]
|
|
314
321
|
row["scenarios"] = leg.get("scenarios") or row["scenarios"]
|
|
315
|
-
|
|
322
|
+
groups = by_model(r for r in rows.values() if r["suites"])
|
|
323
|
+
if len(groups) > 1: # rows of several models: each model's rows together, labels say which
|
|
324
|
+
for r in (r for g in groups.values() for r in g):
|
|
325
|
+
r["label"] = f"{model_short(r)} · {r['label']}"
|
|
326
|
+
return [r for g in groups.values() for r in g]
|
|
316
327
|
|
|
317
328
|
|
|
318
329
|
def baseline(rows: list[dict]) -> dict | None:
|
|
319
|
-
"""The reference row for deltas
|
|
320
|
-
when measured, else the first row
|
|
321
|
-
|
|
322
|
-
|
|
330
|
+
"""The reference row for deltas among ONE model's rows: native FlashInfer
|
|
331
|
+
on the official image when measured, else the first official-image row,
|
|
332
|
+
else the first row. For mixed rows use :func:`baselines`."""
|
|
333
|
+
official = [r for r in rows if not _is_mjolnir(str(r.get("image", "")))]
|
|
334
|
+
for r in official:
|
|
335
|
+
if r.get("config") == "NVFP4":
|
|
323
336
|
return r
|
|
324
|
-
return
|
|
337
|
+
return (official or rows or [None])[0]
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def baselines(rows: list[dict]) -> list[dict | None]:
|
|
341
|
+
"""Each row's baseline, in row order: its own model's :func:`baseline`.
|
|
342
|
+
Rows of another model are never a baseline — their scores are not comparable."""
|
|
343
|
+
per_model = {m: baseline(group) for m, group in by_model(rows).items()}
|
|
344
|
+
return [per_model[model_of(r)] for r in rows]
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _maxes(s: dict) -> list:
|
|
348
|
+
"""Every trial's max for a suite (older records keep only the last trial's)."""
|
|
349
|
+
return list(s.get("maxes") or ([s["max"]] if s.get("max") else []))
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _on_max(s: dict, m: float) -> float:
|
|
353
|
+
"""The suite's score with every trial's points over ``m``: exact from the
|
|
354
|
+
per-trial points when recorded, else from the one run's points, else from
|
|
355
|
+
the mean % (an older averaged record, which keeps only the last max)."""
|
|
356
|
+
if s.get("earneds"):
|
|
357
|
+
return mean(e / m * 100 for e in s["earneds"])
|
|
358
|
+
if s.get("n", 1) == 1 and "earned" in s:
|
|
359
|
+
return s["earned"] / m * 100
|
|
360
|
+
return s["score"] * s["max"] / m
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def delta(r: dict, base: dict | None, name: str) -> tuple[float, bool] | None:
|
|
364
|
+
"""Points ``r`` scored over ``base`` on suite ``name``, and whether an
|
|
365
|
+
excluded scenario was scored as zero (None when ``r`` is the baseline,
|
|
366
|
+
either lacks the suite, or the two measured different scenario sets).
|
|
367
|
+
|
|
368
|
+
tool-eval-bench drops a scenario it judged the endpoint unable to serve
|
|
369
|
+
(TC-45 when its ``tool_choice=required`` probe fails, a timeout, an error),
|
|
370
|
+
which shrinks that trial's max and lowers its completion rate, so its %
|
|
371
|
+
divides by less. Then every trial of both legs is scored on the largest
|
|
372
|
+
max, the excluded points counting as zero: an exclusion never raises a
|
|
373
|
+
delta. Different maxes at full completion mean different scenario sets
|
|
374
|
+
(another run's arguments), which no rescaling makes comparable."""
|
|
375
|
+
if base is None or r is base:
|
|
376
|
+
return None
|
|
377
|
+
v, b = r.get("suites", {}).get(name) or {}, base.get("suites", {}).get(name) or {}
|
|
378
|
+
if v.get("score") is None or b.get("score") is None:
|
|
379
|
+
return None
|
|
380
|
+
maxes = _maxes(v) + _maxes(b)
|
|
381
|
+
if len(set(maxes)) > 1:
|
|
382
|
+
if all(x.get("completion_rate", 100.0) >= 100.0 for x in (v, b)):
|
|
383
|
+
return None
|
|
384
|
+
m = max(maxes)
|
|
385
|
+
return _on_max(v, m) - _on_max(b, m), True
|
|
386
|
+
return v["score"] - b["score"], False
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def fmt_delta(d: tuple[float, bool] | None) -> str:
|
|
390
|
+
"""' (+1.2)', ' (-1.4‡)' when an excluded scenario was scored as zero, '' for no delta."""
|
|
391
|
+
return f" ({d[0]:+.1f}{'‡' if d[1] else ''})" if d else ""
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def leg_excluded(r: dict, base: dict | None, names: list[str]) -> bool:
|
|
395
|
+
"""Whether any of ``r``'s suite deltas scored an excluded scenario as zero."""
|
|
396
|
+
return any((d := delta(r, base, n)) and d[1] for n in names)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def any_excluded(rows: list[dict], bases: list[dict | None], names: list[str]) -> bool:
|
|
400
|
+
return any(leg_excluded(r, b, names) for r, b in zip(rows, bases, strict=True))
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
EXCLUDED_NOTE = (
|
|
404
|
+
"‡ = tool-eval-bench excluded a scenario on one of the two legs; the delta scores both on the full max, "
|
|
405
|
+
"the excluded points counting as zero"
|
|
406
|
+
)
|
|
407
|
+
EXCLUDED_NOTE_SHORT = "‡ = a scenario was excluded on one side, scored as zero"
|
|
408
|
+
CATEGORY_EXCLUDED_NOTE = (
|
|
409
|
+
"‡ = this leg had a scenario excluded: its category % divides by the smaller max, so one category reads high"
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def baseline_caption(bases: list[dict | None], item_sep: str = " · ") -> str:
|
|
414
|
+
"""The distinct baseline labels, first-seen order, ' / '-joined ('' if none);
|
|
415
|
+
``item_sep`` replaces the label's own ' · ' separator."""
|
|
416
|
+
labels = dict.fromkeys(b["label"].replace(" · ", item_sep) for b in bases if b is not None)
|
|
417
|
+
return " / ".join(labels)
|
|
325
418
|
|
|
326
419
|
|
|
327
420
|
def suites_in(rows: list[dict]) -> list[str]:
|
|
@@ -39,7 +39,6 @@ import json
|
|
|
39
39
|
import shutil
|
|
40
40
|
import subprocess
|
|
41
41
|
from pathlib import Path
|
|
42
|
-
from typing import Optional
|
|
43
42
|
|
|
44
43
|
import typer
|
|
45
44
|
from rich import box
|
|
@@ -119,7 +118,7 @@ MAX_SUSPECT = 2 # more suspect L than this and the measurement itself is reject
|
|
|
119
118
|
|
|
120
119
|
def _gm(xs) -> float:
|
|
121
120
|
xs = list(xs)
|
|
122
|
-
return results.geomean(xs)
|
|
121
|
+
return results.geomean(xs) or 1.0 # geomean is None only when no positive value survives: neutral 1.0
|
|
123
122
|
|
|
124
123
|
|
|
125
124
|
def _pct(x: float) -> str:
|
|
@@ -158,7 +157,7 @@ def judge(row: dict, best: dict | None, min_gain: float = 0.02, noise: float = N
|
|
|
158
157
|
if best is None:
|
|
159
158
|
return "baseline", "the starting kernel", {}
|
|
160
159
|
new, old = row.get("per_L") or {}, best.get("per_L") or {}
|
|
161
|
-
common = [L for L in new if
|
|
160
|
+
common = [L for L in new if old.get(L)]
|
|
162
161
|
if not common: # rows written before per-L judging: the plain geomean rule
|
|
163
162
|
st = decide(score, correct, best.get("score"), min_gain)
|
|
164
163
|
return st, f"geomean {_pct(score / best['score'])} (no per-L reference)", {}
|
|
@@ -172,8 +171,8 @@ def judge(row: dict, best: dict | None, min_gain: float = 0.02, noise: float = N
|
|
|
172
171
|
gain = _gm(trusted.values())
|
|
173
172
|
if gain <= 1 + min_gain:
|
|
174
173
|
return "discard", f"geomean {_pct(gain)} ≤ +{min_gain * 100:g}%{note}", rel
|
|
175
|
-
top = max(trusted, key=trusted
|
|
176
|
-
loo = _gm(v for L, v in trusted.items() if
|
|
174
|
+
top = max(trusted, key=lambda L: trusted[L])
|
|
175
|
+
loo = _gm(v for L, v in trusted.items() if top != L)
|
|
177
176
|
if loo <= 1 + min_gain / 2:
|
|
178
177
|
return (
|
|
179
178
|
"discard",
|
|
@@ -207,7 +206,7 @@ def _root() -> Path:
|
|
|
207
206
|
# ── commands ────────────────────────────────────────────────────────────────
|
|
208
207
|
|
|
209
208
|
|
|
210
|
-
def _init(root: Path, tag: str, base: str, evaluate_baseline: bool, image:
|
|
209
|
+
def _init(root: Path, tag: str, base: str, evaluate_baseline: bool, image: str | None) -> int:
|
|
211
210
|
branch = f"autoresearch/{tag}"
|
|
212
211
|
if _git(root, "branch", "--list", branch):
|
|
213
212
|
console.print(f"[red]{branch} already exists — pick a fresh tag (a run never reuses a branch)[/red]")
|
|
@@ -232,7 +231,7 @@ def init(
|
|
|
232
231
|
tag: str = typer.Argument(..., help="run tag, e.g. oct11 — the branch is autoresearch/<tag>"),
|
|
233
232
|
base: str = typer.Option("main", "--base", help="branch to start from"),
|
|
234
233
|
evaluate_baseline: bool = typer.Option(True, "--baseline/--no-baseline", help="score the starting kernel now"),
|
|
235
|
-
image:
|
|
234
|
+
image: str | None = typer.Option(None, "--image"),
|
|
236
235
|
):
|
|
237
236
|
"""Create the research branch and score the starting kernel (the baseline row)."""
|
|
238
237
|
raise typer.Exit(_init(_root(), tag, base, evaluate_baseline, image))
|
|
@@ -243,9 +242,9 @@ def _evaluate(
|
|
|
243
242
|
hypothesis: str,
|
|
244
243
|
agent: str,
|
|
245
244
|
sources: str,
|
|
246
|
-
image:
|
|
245
|
+
image: str | None,
|
|
247
246
|
min_gain: float = 0.02,
|
|
248
|
-
ls:
|
|
247
|
+
ls: str | None = None,
|
|
249
248
|
iters: int = 50,
|
|
250
249
|
gate: bool = True,
|
|
251
250
|
skip_correctness: bool = False,
|
|
@@ -301,7 +300,7 @@ def _evaluate(
|
|
|
301
300
|
by = results.series_by_id(doc)
|
|
302
301
|
if "fi" in by and "mjolnir" in by:
|
|
303
302
|
sp = results.speedup(by["fi"], by["mjolnir"])
|
|
304
|
-
want =
|
|
303
|
+
want = {int(x) for x in (ls or " ".join(map(str, ALL_LS))).replace(",", " ").split()}
|
|
305
304
|
if sp and set(sp) >= want:
|
|
306
305
|
score = results.geomean(sp.values())
|
|
307
306
|
per_L = {str(k): round(v, 4) for k, v in sp.items()}
|
|
@@ -310,7 +309,7 @@ def _evaluate(
|
|
|
310
309
|
sweep_run = doc["run_id"]
|
|
311
310
|
row = {
|
|
312
311
|
"id": len(rows) + 1,
|
|
313
|
-
"ts": _dt.datetime.now(_dt.
|
|
312
|
+
"ts": _dt.datetime.now(_dt.UTC).isoformat(timespec="seconds"),
|
|
314
313
|
"branch": _git(root, "rev-parse", "--abbrev-ref", "HEAD"),
|
|
315
314
|
"commit": commit,
|
|
316
315
|
"status": None,
|
|
@@ -390,10 +389,10 @@ def evaluate(
|
|
|
390
389
|
agent: str = typer.Option("autoresearch", "--agent", help="who proposed/implemented it"),
|
|
391
390
|
sources: str = typer.Option("", "--sources", help="comma-separated URLs / arXiv ids behind the idea"),
|
|
392
391
|
min_gain: float = typer.Option(0.02, "--min-gain", help="required geomean gain over the best kept score"),
|
|
393
|
-
ls:
|
|
392
|
+
ls: str | None = typer.Option(None, "--ls", help="L values (default: all 8; a subset is never a keep)"),
|
|
394
393
|
iters: int = typer.Option(50, "--iters"),
|
|
395
394
|
gate: bool = typer.Option(True, "--gate/--no-gate"),
|
|
396
|
-
image:
|
|
395
|
+
image: str | None = typer.Option(None, "--image"),
|
|
397
396
|
):
|
|
398
397
|
"""Score the committed kernel: quick correctness + FlashInfer vs Mjolnir sweep;
|
|
399
398
|
append the result and the fixed keep/discard decision to the ledger."""
|
|
@@ -464,8 +463,8 @@ def chart():
|
|
|
464
463
|
|
|
465
464
|
@app.command()
|
|
466
465
|
def run(
|
|
467
|
-
tag:
|
|
468
|
-
model:
|
|
466
|
+
tag: str | None = typer.Option(None, "--tag", help="start a new run first (init <tag>)"),
|
|
467
|
+
model: str | None = typer.Option(None, "--model", help="OpenCode model (provider/model) for the orchestrator"),
|
|
469
468
|
prompt: str = typer.Option(
|
|
470
469
|
"Read research/program.md and run the loop: setup is done when the ledger has a baseline row. "
|
|
471
470
|
"Never stop to ask; record every experiment with `mjolnir autoresearch evaluate`.",
|
|
@@ -494,7 +493,7 @@ def run(
|
|
|
494
493
|
|
|
495
494
|
@app.command()
|
|
496
495
|
def export(
|
|
497
|
-
tag:
|
|
496
|
+
tag: str | None = typer.Option(None, "--tag", help="run tag (default: from the autoresearch/<tag> branch)"),
|
|
498
497
|
):
|
|
499
498
|
"""Snapshot the ledger into research/runs/<tag>/ (ledger.jsonl, ledger.tsv,
|
|
500
499
|
SUMMARY.md) — commit that to keep the run's notebook with the repo."""
|
|
@@ -115,8 +115,8 @@ def _std(vals: list[float]) -> float:
|
|
|
115
115
|
return (sum((v - m) ** 2 for v in vals) / (len(vals) - 1)) ** 0.5
|
|
116
116
|
|
|
117
117
|
|
|
118
|
-
def _p95(vals: list[float]) -> float:
|
|
119
|
-
"""Linear-interpolated 95th percentile."""
|
|
118
|
+
def _p95(vals: list[float]) -> float | None:
|
|
119
|
+
"""Linear-interpolated 95th percentile (``None`` on empty input)."""
|
|
120
120
|
if not vals:
|
|
121
121
|
return None
|
|
122
122
|
s = sorted(vals)
|
|
@@ -295,7 +295,7 @@ def run_perf(
|
|
|
295
295
|
# pooled like the totals: accepted at each position over all drafts
|
|
296
296
|
"per_pos": [
|
|
297
297
|
round(sum(col) / tot["drafts"], 4)
|
|
298
|
-
for col in zip(*(x.get("per_pos_accepted") or [] for x in sweep_spec))
|
|
298
|
+
for col in zip(*(x.get("per_pos_accepted") or [] for x in sweep_spec), strict=False)
|
|
299
299
|
]
|
|
300
300
|
if tot["drafts"]
|
|
301
301
|
else [],
|
|
@@ -438,13 +438,15 @@ def _pipe_table(headers: list[str], rows: list[list[str]]) -> str:
|
|
|
438
438
|
|
|
439
439
|
def data_row(cells: list[str]) -> str:
|
|
440
440
|
return (
|
|
441
|
-
"| "
|
|
441
|
+
"| "
|
|
442
|
+
+ " | ".join(c.rjust(w) if a == ">" else c.ljust(w) for c, w, a in zip(cells, widths, aligns, strict=True))
|
|
443
|
+
+ " |"
|
|
442
444
|
)
|
|
443
445
|
|
|
444
446
|
def sep_cell(a: str, w: int) -> str:
|
|
445
447
|
return ":" + "-" * (w - 1) if a == "<" else "-" * (w - 1) + ":"
|
|
446
448
|
|
|
447
|
-
sep = "|" + "|".join(sep_cell(a, w) for w, a in zip(widths, aligns)) + "|"
|
|
449
|
+
sep = "|" + "|".join(sep_cell(a, w) for w, a in zip(widths, aligns, strict=True)) + "|"
|
|
448
450
|
return "\n".join([data_row(headers), sep, *map(data_row, rows)])
|
|
449
451
|
|
|
450
452
|
|