@wendellresearch-ai/benchwarmers 0.2.0 → 0.2.1
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.
- package/AGENTS.md +147 -147
- package/BENCHMARKS.md +262 -262
- package/GATEWAY.md +272 -272
- package/NOTICE +28 -28
- package/PRIVACY.md +46 -46
- package/README.md +368 -314
- package/SECURITY.md +83 -83
- package/TERMS.md +22 -22
- package/USAGE.md +170 -163
- package/VERSION +1 -1
- package/bin/benchwarmers.js +16 -74
- package/cli.py +666 -0
- package/examples/config/gateway.json +5 -5
- package/examples/config/model-roles.json +11 -11
- package/gateway.py +755 -755
- package/launcher.py +120 -0
- package/mcp_server.py +266 -266
- package/metrics/alerts.py +116 -116
- package/metrics/aliases.py +41 -41
- package/metrics/bench_collections.py +141 -141
- package/metrics/catalog.py +295 -295
- package/metrics/client.py +394 -394
- package/metrics/cliproxy.py +191 -191
- package/metrics/connect.py +691 -691
- package/metrics/cost.py +152 -152
- package/metrics/decide.py +227 -227
- package/metrics/discover.py +318 -318
- package/metrics/endpoints.py +436 -436
- package/metrics/guard.py +617 -592
- package/metrics/learn.py +221 -221
- package/metrics/litellm_hub.py +206 -206
- package/metrics/model_roles.py +84 -84
- package/metrics/offload.py +166 -166
- package/metrics/owui.py +205 -205
- package/metrics/paths.py +60 -60
- package/metrics/route.py +579 -579
- package/metrics/scheduler.py +275 -275
- package/metrics/sources.py +167 -167
- package/metrics/wrong.py +144 -144
- package/package.json +68 -65
- package/perf/README.md +89 -89
- package/perf/run.py +261 -261
- package/scripts/auto_retire.py +367 -367
- package/scripts/check.py +108 -106
- package/scripts/check_deprecations.py +378 -378
- package/scripts/check_themes.py +103 -103
- package/scripts/init.py +124 -124
- package/scripts/list_served.py +78 -78
- package/scripts/notice.py +78 -78
- package/scripts/onboard.py +401 -331
- package/scripts/provider_tags.py +129 -129
- package/scripts/reprefix.py +136 -136
- package/scripts/sync_config.py +105 -105
- package/scripts/test_auth.py +255 -255
- package/scripts/test_cli.py +189 -0
- package/scripts/test_client.py +233 -233
- package/scripts/test_connect.py +222 -222
- package/scripts/test_first_run.py +180 -180
- package/scripts/test_hubs.py +328 -328
- package/scripts/test_launcher.py +67 -0
- package/scripts/test_llm_log.py +85 -81
- package/scripts/test_mcp.py +178 -178
- package/scripts/test_merged.py +214 -214
- package/scripts/test_no_owui.py +291 -291
- package/scripts/test_owui.py +90 -90
- package/scripts/test_serve_edit.py +448 -438
- package/scripts/test_verdict.py +98 -98
- package/scripts/test_vision.py +228 -228
- package/serve.py +3904 -3764
- package/suite/mine.py +154 -154
- package/suite/pace.py +145 -145
- package/suite/run.py +1046 -1042
- package/suite/seed.py +421 -421
- package/suite/tasks/claim-alias-unbound/fixtures/onboard.py +286 -286
- package/suite/tasks/claim-error-text-string/fixtures/decide.py +187 -187
- package/suite/tasks/claim-list-body/fixtures/gateway.py +235 -235
- package/suite/tasks/claim-member-nominated-twice/fixtures/gateway.py +284 -284
- package/suite/tasks/claim-nominee-uses-slot/fixtures/gateway.py +284 -284
- package/suite/tasks/claim-silent-exit/fixtures/gateway.py +235 -235
- package/suite/tasks/claim-sse-hang/fixtures/gateway.py +235 -235
- package/suite/tasks/claim-unknown-nominee-crash/fixtures/gateway.py +284 -284
- package/suite/tasks/rebench-matchms-809-bug/task.json +23 -23
- package/suite/tasks/rebench-matchms-809-control/task.json +27 -27
- package/suite/tasks/rebench-more-itertools-1028-bug/task.json +23 -23
- package/suite/tasks/rebench-more-itertools-1028-control/task.json +27 -27
- package/suite/tasks/rebench-narwhals-2864-bug/task.json +23 -23
- package/suite/tasks/rebench-narwhals-2864-control/task.json +27 -27
- package/suite/tasks/rebench-quantecon-py-769-bug/task.json +23 -23
- package/suite/tasks/rebench-quantecon-py-769-control/task.json +27 -27
- package/suite/tasks/rebench-scrapy-6867-bug/task.json +23 -23
- package/suite/tasks/rebench-scrapy-6867-control/task.json +27 -27
- package/ui/bench.css +430 -425
- package/ui/bench.js +1756 -1576
- package/ui/themes/dark.css +35 -35
- package/ui/themes/hardwood.css +52 -52
- package/ui/themes/rainforest.css +32 -32
package/metrics/offload.py
CHANGED
|
@@ -1,166 +1,166 @@
|
|
|
1
|
-
"""A record of REAL work handed to a model — not benchmark calls.
|
|
2
|
-
|
|
3
|
-
python metrics/offload.py list
|
|
4
|
-
python metrics/offload.py add kimi-k3 "audit the sync seam" --useful \\
|
|
5
|
-
--note "found fetch_assignments was never called"
|
|
6
|
-
python metrics/offload.py verdict p20261016-171200-a1b2.primary --useful --note "..."
|
|
7
|
-
|
|
8
|
-
WHY THIS IS WRITTEN DOWN RATHER THAN DERIVED
|
|
9
|
-
--------------------------------------------
|
|
10
|
-
The first attempt counted offloads as `logged calls − benchmark calls`, and it
|
|
11
|
-
was wrong in both directions. The benchmark ledger only started when it was
|
|
12
|
-
built, so every earlier suite run counted as work: `kimi-k3` showed 26
|
|
13
|
-
"offloads" and had never been given a real task. Meanwhile a model with a
|
|
14
|
-
164-call floor run looked like the most-used thing here.
|
|
15
|
-
|
|
16
|
-
Subtracting one imperfect number from another gives a third number nobody can
|
|
17
|
-
check. An offload is a deliberate act, so it is recorded deliberately.
|
|
18
|
-
|
|
19
|
-
**And a count alone answers the wrong question.** "Which models am I using
|
|
20
|
-
most" matters far less than what for, and whether it helped. Each entry keeps
|
|
21
|
-
the task and the verdict, so the routing table can be argued from evidence
|
|
22
|
-
rather than from a tally.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
from __future__ import annotations
|
|
26
|
-
|
|
27
|
-
import argparse
|
|
28
|
-
import collections
|
|
29
|
-
import datetime as dt
|
|
30
|
-
import json
|
|
31
|
-
import pathlib
|
|
32
|
-
|
|
33
|
-
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
34
|
-
from paths import DATA # noqa: E402 -- the install's data folder (metrics/paths.py)
|
|
35
|
-
LOG = DATA / "metrics" / "offloads.jsonl"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def entries() -> list[dict]:
|
|
39
|
-
"""Every offload, oldest first. An entry with a `call` id (written by the
|
|
40
|
-
bench gateway) is superseded by a later one with the same id: the gateway
|
|
41
|
-
records each seat unjudged as it answers, and the verdict comes after."""
|
|
42
|
-
if not LOG.exists():
|
|
43
|
-
return []
|
|
44
|
-
out, by_call = [], {}
|
|
45
|
-
from aliases import current
|
|
46
|
-
for line in LOG.read_text(encoding="utf-8").splitlines():
|
|
47
|
-
if line.strip():
|
|
48
|
-
try:
|
|
49
|
-
e = json.loads(line)
|
|
50
|
-
except ValueError:
|
|
51
|
-
continue
|
|
52
|
-
e["model"] = current(e.get("model", "")) # a renamed model keeps its record
|
|
53
|
-
if e.get("call") in by_call:
|
|
54
|
-
out[by_call[e["call"]]] = e
|
|
55
|
-
continue
|
|
56
|
-
if e.get("call"):
|
|
57
|
-
by_call[e["call"]] = len(out)
|
|
58
|
-
out.append(e)
|
|
59
|
-
return out
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def add(model: str, task: str, useful: bool | None, note: str, call: str | None = None,
|
|
63
|
-
real: list[str] | None = None, false: list[str] | None = None) -> dict:
|
|
64
|
-
entry = {"date": dt.date.today().isoformat(), "model": model,
|
|
65
|
-
"task": task, "useful": useful, "note": note}
|
|
66
|
-
if call:
|
|
67
|
-
entry["call"] = call
|
|
68
|
-
# Verified claims, one sentence each: what this answer got right, and what
|
|
69
|
-
# it asserted that is not true. suite/mine.py turns them into tasks.
|
|
70
|
-
if real:
|
|
71
|
-
entry["real"] = real
|
|
72
|
-
if false:
|
|
73
|
-
entry["false"] = false
|
|
74
|
-
LOG.parent.mkdir(parents=True, exist_ok=True)
|
|
75
|
-
with LOG.open("a", encoding="utf-8") as fh:
|
|
76
|
-
fh.write(json.dumps(entry) + "\n")
|
|
77
|
-
return entry
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def verdict(call: str, useful: bool | None, note: str,
|
|
81
|
-
real: list[str] | None = None, false: list[str] | None = None,
|
|
82
|
-
replace: bool = False) -> dict:
|
|
83
|
-
"""Judge one seat of a bench-gateway panel by its call id. A blank note or
|
|
84
|
-
claim list keeps the earlier one, unless `replace` (the dashboard's form,
|
|
85
|
-
which shows them for editing, so blank means cleared)."""
|
|
86
|
-
found = next((e for e in reversed(entries()) if e.get("call") == call), None)
|
|
87
|
-
if found is None:
|
|
88
|
-
raise KeyError(f"no offload with call id {call!r}; the gateway prints them under each answer")
|
|
89
|
-
if replace:
|
|
90
|
-
real, false = real or None, false or None
|
|
91
|
-
else:
|
|
92
|
-
note, real, false = note or found.get("note", ""), real or found.get("real"), false or found.get("false")
|
|
93
|
-
if (useful, note, real, false) == (found.get("useful"), found.get("note", ""), found.get("real"), found.get("false")):
|
|
94
|
-
return found # the same verdict again writes nothing
|
|
95
|
-
return add(found["model"], found["task"], useful, note, call, real, false)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def summary() -> dict[str, dict]:
|
|
99
|
-
"""Per model: how often, how often it helped, and the last thing it did."""
|
|
100
|
-
agg: dict[str, dict] = collections.defaultdict(
|
|
101
|
-
lambda: {"offloads": 0, "useful": 0, "judged": 0,
|
|
102
|
-
"last_task": "", "last_date": ""})
|
|
103
|
-
for e in entries():
|
|
104
|
-
a = agg[e["model"]]
|
|
105
|
-
a["offloads"] += 1
|
|
106
|
-
if e.get("useful") is not None:
|
|
107
|
-
a["judged"] += 1
|
|
108
|
-
a["useful"] += 1 if e["useful"] else 0
|
|
109
|
-
if e["date"] >= a["last_date"]:
|
|
110
|
-
a["last_date"], a["last_task"] = e["date"], e["task"]
|
|
111
|
-
return dict(agg)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def main() -> None:
|
|
115
|
-
ap = argparse.ArgumentParser(description=__doc__)
|
|
116
|
-
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
117
|
-
sub.add_parser("list")
|
|
118
|
-
a = sub.add_parser("add")
|
|
119
|
-
a.add_argument("model")
|
|
120
|
-
a.add_argument("task", help="what it was asked to do, in a few words")
|
|
121
|
-
a.add_argument("--useful", dest="useful", action="store_true", default=None)
|
|
122
|
-
a.add_argument("--not-useful", dest="useful", action="store_false")
|
|
123
|
-
a.add_argument("--note", default="")
|
|
124
|
-
# Structured, so metrics/wrong.py can count who asserts false things.
|
|
125
|
-
a.add_argument("--real", action="append", metavar="CLAIM",
|
|
126
|
-
help="a finding this answer got right, verified; repeatable")
|
|
127
|
-
a.add_argument("--false", action="append", dest="false_claims", metavar="CLAIM",
|
|
128
|
-
help="a claim this answer made that is not true, verified; repeatable")
|
|
129
|
-
v = sub.add_parser("verdict", help="judge one seat of a bench-gateway panel by call id")
|
|
130
|
-
v.add_argument("call")
|
|
131
|
-
judged = v.add_mutually_exclusive_group(required=True) # a verdict without one erases nothing
|
|
132
|
-
judged.add_argument("--useful", dest="useful", action="store_true", default=None)
|
|
133
|
-
judged.add_argument("--not-useful", dest="useful", action="store_false")
|
|
134
|
-
v.add_argument("--note", default="")
|
|
135
|
-
v.add_argument("--real", action="append", metavar="CLAIM",
|
|
136
|
-
help="a finding this answer got right, verified; repeatable")
|
|
137
|
-
v.add_argument("--false", action="append", dest="false_claims", metavar="CLAIM",
|
|
138
|
-
help="a claim this answer made that is not true, verified; repeatable")
|
|
139
|
-
args = ap.parse_args()
|
|
140
|
-
|
|
141
|
-
if args.cmd == "add":
|
|
142
|
-
e = add(args.model, args.task, args.useful, args.note, real=args.real, false=args.false_claims)
|
|
143
|
-
print(f"recorded: {e['model']} — {e['task']}")
|
|
144
|
-
return
|
|
145
|
-
if args.cmd == "verdict":
|
|
146
|
-
try:
|
|
147
|
-
e = verdict(args.call, args.useful, args.note, args.real, args.false_claims)
|
|
148
|
-
except KeyError as exc:
|
|
149
|
-
raise SystemExit(exc.args[0])
|
|
150
|
-
print(f"recorded: {e['model']} — {e['task']} — useful={e['useful']}")
|
|
151
|
-
return
|
|
152
|
-
|
|
153
|
-
data = summary()
|
|
154
|
-
if not data:
|
|
155
|
-
print("No offloads recorded yet.")
|
|
156
|
-
print("This is empty because it counts REAL WORK only — a benchmark "
|
|
157
|
-
"run is not an offload.")
|
|
158
|
-
return
|
|
159
|
-
print(f"{'model':<26} {'used':>5} {'helped':>7} last")
|
|
160
|
-
for model, a in sorted(data.items(), key=lambda kv: -kv[1]["offloads"]):
|
|
161
|
-
helped = (f"{a['useful']}/{a['judged']}" if a["judged"] else "—")
|
|
162
|
-
print(f"{model:<26} {a['offloads']:>5} {helped:>7} {a['last_task'][:44]}")
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if __name__ == "__main__":
|
|
166
|
-
main()
|
|
1
|
+
"""A record of REAL work handed to a model — not benchmark calls.
|
|
2
|
+
|
|
3
|
+
python metrics/offload.py list
|
|
4
|
+
python metrics/offload.py add kimi-k3 "audit the sync seam" --useful \\
|
|
5
|
+
--note "found fetch_assignments was never called"
|
|
6
|
+
python metrics/offload.py verdict p20261016-171200-a1b2.primary --useful --note "..."
|
|
7
|
+
|
|
8
|
+
WHY THIS IS WRITTEN DOWN RATHER THAN DERIVED
|
|
9
|
+
--------------------------------------------
|
|
10
|
+
The first attempt counted offloads as `logged calls − benchmark calls`, and it
|
|
11
|
+
was wrong in both directions. The benchmark ledger only started when it was
|
|
12
|
+
built, so every earlier suite run counted as work: `kimi-k3` showed 26
|
|
13
|
+
"offloads" and had never been given a real task. Meanwhile a model with a
|
|
14
|
+
164-call floor run looked like the most-used thing here.
|
|
15
|
+
|
|
16
|
+
Subtracting one imperfect number from another gives a third number nobody can
|
|
17
|
+
check. An offload is a deliberate act, so it is recorded deliberately.
|
|
18
|
+
|
|
19
|
+
**And a count alone answers the wrong question.** "Which models am I using
|
|
20
|
+
most" matters far less than what for, and whether it helped. Each entry keeps
|
|
21
|
+
the task and the verdict, so the routing table can be argued from evidence
|
|
22
|
+
rather than from a tally.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import argparse
|
|
28
|
+
import collections
|
|
29
|
+
import datetime as dt
|
|
30
|
+
import json
|
|
31
|
+
import pathlib
|
|
32
|
+
|
|
33
|
+
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
34
|
+
from paths import DATA # noqa: E402 -- the install's data folder (metrics/paths.py)
|
|
35
|
+
LOG = DATA / "metrics" / "offloads.jsonl"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def entries() -> list[dict]:
|
|
39
|
+
"""Every offload, oldest first. An entry with a `call` id (written by the
|
|
40
|
+
bench gateway) is superseded by a later one with the same id: the gateway
|
|
41
|
+
records each seat unjudged as it answers, and the verdict comes after."""
|
|
42
|
+
if not LOG.exists():
|
|
43
|
+
return []
|
|
44
|
+
out, by_call = [], {}
|
|
45
|
+
from aliases import current
|
|
46
|
+
for line in LOG.read_text(encoding="utf-8").splitlines():
|
|
47
|
+
if line.strip():
|
|
48
|
+
try:
|
|
49
|
+
e = json.loads(line)
|
|
50
|
+
except ValueError:
|
|
51
|
+
continue
|
|
52
|
+
e["model"] = current(e.get("model", "")) # a renamed model keeps its record
|
|
53
|
+
if e.get("call") in by_call:
|
|
54
|
+
out[by_call[e["call"]]] = e
|
|
55
|
+
continue
|
|
56
|
+
if e.get("call"):
|
|
57
|
+
by_call[e["call"]] = len(out)
|
|
58
|
+
out.append(e)
|
|
59
|
+
return out
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def add(model: str, task: str, useful: bool | None, note: str, call: str | None = None,
|
|
63
|
+
real: list[str] | None = None, false: list[str] | None = None) -> dict:
|
|
64
|
+
entry = {"date": dt.date.today().isoformat(), "model": model,
|
|
65
|
+
"task": task, "useful": useful, "note": note}
|
|
66
|
+
if call:
|
|
67
|
+
entry["call"] = call
|
|
68
|
+
# Verified claims, one sentence each: what this answer got right, and what
|
|
69
|
+
# it asserted that is not true. suite/mine.py turns them into tasks.
|
|
70
|
+
if real:
|
|
71
|
+
entry["real"] = real
|
|
72
|
+
if false:
|
|
73
|
+
entry["false"] = false
|
|
74
|
+
LOG.parent.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
with LOG.open("a", encoding="utf-8") as fh:
|
|
76
|
+
fh.write(json.dumps(entry) + "\n")
|
|
77
|
+
return entry
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def verdict(call: str, useful: bool | None, note: str,
|
|
81
|
+
real: list[str] | None = None, false: list[str] | None = None,
|
|
82
|
+
replace: bool = False) -> dict:
|
|
83
|
+
"""Judge one seat of a bench-gateway panel by its call id. A blank note or
|
|
84
|
+
claim list keeps the earlier one, unless `replace` (the dashboard's form,
|
|
85
|
+
which shows them for editing, so blank means cleared)."""
|
|
86
|
+
found = next((e for e in reversed(entries()) if e.get("call") == call), None)
|
|
87
|
+
if found is None:
|
|
88
|
+
raise KeyError(f"no offload with call id {call!r}; the gateway prints them under each answer")
|
|
89
|
+
if replace:
|
|
90
|
+
real, false = real or None, false or None
|
|
91
|
+
else:
|
|
92
|
+
note, real, false = note or found.get("note", ""), real or found.get("real"), false or found.get("false")
|
|
93
|
+
if (useful, note, real, false) == (found.get("useful"), found.get("note", ""), found.get("real"), found.get("false")):
|
|
94
|
+
return found # the same verdict again writes nothing
|
|
95
|
+
return add(found["model"], found["task"], useful, note, call, real, false)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def summary() -> dict[str, dict]:
|
|
99
|
+
"""Per model: how often, how often it helped, and the last thing it did."""
|
|
100
|
+
agg: dict[str, dict] = collections.defaultdict(
|
|
101
|
+
lambda: {"offloads": 0, "useful": 0, "judged": 0,
|
|
102
|
+
"last_task": "", "last_date": ""})
|
|
103
|
+
for e in entries():
|
|
104
|
+
a = agg[e["model"]]
|
|
105
|
+
a["offloads"] += 1
|
|
106
|
+
if e.get("useful") is not None:
|
|
107
|
+
a["judged"] += 1
|
|
108
|
+
a["useful"] += 1 if e["useful"] else 0
|
|
109
|
+
if e["date"] >= a["last_date"]:
|
|
110
|
+
a["last_date"], a["last_task"] = e["date"], e["task"]
|
|
111
|
+
return dict(agg)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def main() -> None:
|
|
115
|
+
ap = argparse.ArgumentParser(description=__doc__)
|
|
116
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
117
|
+
sub.add_parser("list")
|
|
118
|
+
a = sub.add_parser("add")
|
|
119
|
+
a.add_argument("model")
|
|
120
|
+
a.add_argument("task", help="what it was asked to do, in a few words")
|
|
121
|
+
a.add_argument("--useful", dest="useful", action="store_true", default=None)
|
|
122
|
+
a.add_argument("--not-useful", dest="useful", action="store_false")
|
|
123
|
+
a.add_argument("--note", default="")
|
|
124
|
+
# Structured, so metrics/wrong.py can count who asserts false things.
|
|
125
|
+
a.add_argument("--real", action="append", metavar="CLAIM",
|
|
126
|
+
help="a finding this answer got right, verified; repeatable")
|
|
127
|
+
a.add_argument("--false", action="append", dest="false_claims", metavar="CLAIM",
|
|
128
|
+
help="a claim this answer made that is not true, verified; repeatable")
|
|
129
|
+
v = sub.add_parser("verdict", help="judge one seat of a bench-gateway panel by call id")
|
|
130
|
+
v.add_argument("call")
|
|
131
|
+
judged = v.add_mutually_exclusive_group(required=True) # a verdict without one erases nothing
|
|
132
|
+
judged.add_argument("--useful", dest="useful", action="store_true", default=None)
|
|
133
|
+
judged.add_argument("--not-useful", dest="useful", action="store_false")
|
|
134
|
+
v.add_argument("--note", default="")
|
|
135
|
+
v.add_argument("--real", action="append", metavar="CLAIM",
|
|
136
|
+
help="a finding this answer got right, verified; repeatable")
|
|
137
|
+
v.add_argument("--false", action="append", dest="false_claims", metavar="CLAIM",
|
|
138
|
+
help="a claim this answer made that is not true, verified; repeatable")
|
|
139
|
+
args = ap.parse_args()
|
|
140
|
+
|
|
141
|
+
if args.cmd == "add":
|
|
142
|
+
e = add(args.model, args.task, args.useful, args.note, real=args.real, false=args.false_claims)
|
|
143
|
+
print(f"recorded: {e['model']} — {e['task']}")
|
|
144
|
+
return
|
|
145
|
+
if args.cmd == "verdict":
|
|
146
|
+
try:
|
|
147
|
+
e = verdict(args.call, args.useful, args.note, args.real, args.false_claims)
|
|
148
|
+
except KeyError as exc:
|
|
149
|
+
raise SystemExit(exc.args[0])
|
|
150
|
+
print(f"recorded: {e['model']} — {e['task']} — useful={e['useful']}")
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
data = summary()
|
|
154
|
+
if not data:
|
|
155
|
+
print("No offloads recorded yet.")
|
|
156
|
+
print("This is empty because it counts REAL WORK only — a benchmark "
|
|
157
|
+
"run is not an offload.")
|
|
158
|
+
return
|
|
159
|
+
print(f"{'model':<26} {'used':>5} {'helped':>7} last")
|
|
160
|
+
for model, a in sorted(data.items(), key=lambda kv: -kv[1]["offloads"]):
|
|
161
|
+
helped = (f"{a['useful']}/{a['judged']}" if a["judged"] else "—")
|
|
162
|
+
print(f"{model:<26} {a['offloads']:>5} {helped:>7} {a['last_task'][:44]}")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
main()
|