fitlab 0.1.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.
- fitlab/__init__.py +6 -0
- fitlab/bench.py +127 -0
- fitlab/cli.py +303 -0
- fitlab/data/seed_registry.json +1574 -0
- fitlab/estimate.py +34 -0
- fitlab/hardware.py +69 -0
- fitlab/registry.py +44 -0
- fitlab-0.1.0.dist-info/METADATA +478 -0
- fitlab-0.1.0.dist-info/RECORD +12 -0
- fitlab-0.1.0.dist-info/WHEEL +4 -0
- fitlab-0.1.0.dist-info/entry_points.txt +2 -0
- fitlab-0.1.0.dist-info/licenses/LICENSE +3 -0
fitlab/__init__.py
ADDED
fitlab/bench.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Real benchmark via Ollama — identical output schema to scripts/bench_gguf.py."""
|
|
2
|
+
import datetime
|
|
3
|
+
import json
|
|
4
|
+
import shutil
|
|
5
|
+
import statistics
|
|
6
|
+
import subprocess
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from urllib import request
|
|
12
|
+
|
|
13
|
+
OLLAMA = "http://127.0.0.1:11434"
|
|
14
|
+
PROMPT_ID = "fitlab-v1-256"
|
|
15
|
+
PROMPT = ("You are benchmarking. Write a precise 400-word technical explanation of how "
|
|
16
|
+
"KV-cache memory grows with context length in transformer decoding, with one worked example.")
|
|
17
|
+
INSTALL_CMD = "curl -fsSL https://ollama.com/install.sh | sh"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def api(path: str, payload: dict | None = None, timeout=600):
|
|
21
|
+
data = json.dumps(payload).encode() if payload else None
|
|
22
|
+
req = request.Request(OLLAMA + path, data=data, headers={"Content-Type": "application/json"})
|
|
23
|
+
with request.urlopen(req, timeout=timeout) as r:
|
|
24
|
+
return json.loads(r.read())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def ollama_installed() -> bool:
|
|
28
|
+
return shutil.which("ollama") is not None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def install_ollama() -> bool:
|
|
32
|
+
return subprocess.run(INSTALL_CMD, shell=True).returncode == 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def ensure_server(wait_s: int = 60) -> str:
|
|
36
|
+
"""Start `ollama serve` if needed; return its version."""
|
|
37
|
+
try:
|
|
38
|
+
return api("/api/version", timeout=3)["version"]
|
|
39
|
+
except Exception:
|
|
40
|
+
subprocess.Popen(["ollama", "serve"], stdout=subprocess.DEVNULL,
|
|
41
|
+
stderr=subprocess.DEVNULL, start_new_session=True)
|
|
42
|
+
for _ in range(wait_s):
|
|
43
|
+
time.sleep(1)
|
|
44
|
+
try:
|
|
45
|
+
return api("/api/version", timeout=3)["version"]
|
|
46
|
+
except Exception:
|
|
47
|
+
pass
|
|
48
|
+
raise RuntimeError("Ollama server did not start — try `ollama serve` in another terminal")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def pull(tag: str) -> None:
|
|
52
|
+
subprocess.run(["ollama", "pull", tag], check=True)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class VramPeak(threading.Thread):
|
|
56
|
+
def __init__(self):
|
|
57
|
+
super().__init__(daemon=True)
|
|
58
|
+
self.peak = 0.0
|
|
59
|
+
self.stop = threading.Event()
|
|
60
|
+
|
|
61
|
+
def run(self):
|
|
62
|
+
while not self.stop.is_set():
|
|
63
|
+
q = subprocess.run("nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits",
|
|
64
|
+
shell=True, text=True, capture_output=True)
|
|
65
|
+
if q.returncode == 0 and q.stdout.strip():
|
|
66
|
+
self.peak = max(self.peak, int(q.stdout.strip().splitlines()[0]) / 1024)
|
|
67
|
+
time.sleep(0.5)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def bench_model(tag: str, gpu: dict, *, model_id: str | None = None, reps: int = 3,
|
|
71
|
+
context: int = 4096, source: str = "community-local", submitter: str = "",
|
|
72
|
+
out_dir: str | Path = "fitlab-results", log=print) -> tuple[dict, Path]:
|
|
73
|
+
"""Run the standard FitLab benchmark. `gpu` is hardware.schema_gpu(...) shaped."""
|
|
74
|
+
version = ensure_server()
|
|
75
|
+
t0 = time.time()
|
|
76
|
+
log(f" pulling {tag} …")
|
|
77
|
+
pull(tag)
|
|
78
|
+
mon = VramPeak()
|
|
79
|
+
mon.start()
|
|
80
|
+
|
|
81
|
+
load_t0 = time.time()
|
|
82
|
+
api("/api/generate", {"model": tag, "prompt": "hi", "stream": False,
|
|
83
|
+
"options": {"num_predict": 1}}) # cold load
|
|
84
|
+
load_s = time.time() - load_t0
|
|
85
|
+
|
|
86
|
+
tps_runs, ttfts, prompt_tps, out_tokens = [], [], [], 0
|
|
87
|
+
for i in range(reps):
|
|
88
|
+
r = api("/api/generate", {"model": tag, "prompt": PROMPT, "stream": False,
|
|
89
|
+
"options": {"num_predict": 256, "num_ctx": context,
|
|
90
|
+
"temperature": 0.0, "seed": 42}})
|
|
91
|
+
tps_runs.append(r["eval_count"] / (r["eval_duration"] / 1e9))
|
|
92
|
+
ttfts.append(r["prompt_eval_duration"] / 1e6)
|
|
93
|
+
prompt_tps.append(r["prompt_eval_count"] / (r["prompt_eval_duration"] / 1e9))
|
|
94
|
+
out_tokens += r["eval_count"]
|
|
95
|
+
log(f" run {i + 1}/{reps}: {tps_runs[-1]:.1f} tok/s")
|
|
96
|
+
mon.stop.set()
|
|
97
|
+
mon.join(timeout=2)
|
|
98
|
+
|
|
99
|
+
details = next((m for m in api("/api/tags")["models"]
|
|
100
|
+
if m["name"].startswith(tag.split(":")[0])), {})
|
|
101
|
+
quant = (details.get("details") or {}).get("quantization_level", "Q4_K_M")
|
|
102
|
+
|
|
103
|
+
result = {
|
|
104
|
+
"schema_version": "1.0",
|
|
105
|
+
"model_id": model_id or tag.replace(":", "-").replace(".", ""),
|
|
106
|
+
"ollama_tag": tag,
|
|
107
|
+
"runtime": {"engine": "ollama", "version": version},
|
|
108
|
+
"gpu": gpu,
|
|
109
|
+
"quant": quant,
|
|
110
|
+
"context": context,
|
|
111
|
+
"metrics": {"load_s": round(load_s, 2), "ttft_ms": round(statistics.median(ttfts), 1),
|
|
112
|
+
"prompt_tps": round(statistics.median(prompt_tps), 1),
|
|
113
|
+
"gen_tps": round(statistics.median(tps_runs), 2),
|
|
114
|
+
"gen_tps_runs": [round(t, 2) for t in tps_runs],
|
|
115
|
+
"peak_vram_gb": round(mon.peak, 2), "output_tokens": out_tokens},
|
|
116
|
+
"run": {"date": datetime.date.today().isoformat(), "prompt_id": PROMPT_ID,
|
|
117
|
+
"repetitions": reps, "duration_s": round(time.time() - t0, 1)},
|
|
118
|
+
"provenance": {"source": source, "submitter": submitter,
|
|
119
|
+
"environment": os.environ.get("FITLAB_ENV", "cli"),
|
|
120
|
+
"run_url": os.environ.get("RUN_URL", "")},
|
|
121
|
+
}
|
|
122
|
+
out = Path(out_dir)
|
|
123
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
124
|
+
fname = f"{result['run']['date']}_{result['model_id']}_{gpu.get('profile_id') or 'unknown'}.json"
|
|
125
|
+
path = out / fname
|
|
126
|
+
path.write_text(json.dumps(result, indent=2))
|
|
127
|
+
return result, path
|
fitlab/cli.py
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"""fitlab CLI — `fitlab` with no arguments launches the wizard."""
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from urllib.parse import quote
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from . import __version__, bench, estimate, hardware, registry
|
|
12
|
+
|
|
13
|
+
console = Console(highlight=False)
|
|
14
|
+
REPO = os.environ.get("FITLAB_REPO", "OWNER/llm-fitlab")
|
|
15
|
+
|
|
16
|
+
VERDICT_STYLE = {"fits": "bold green", "tight": "bold yellow",
|
|
17
|
+
"offload": "dark_orange", "no": "bold red", "-": "dim"}
|
|
18
|
+
|
|
19
|
+
# reference profiles for `fitlab check --gpu <id>` (mirrors data/hardware.yaml)
|
|
20
|
+
PROFILES = {
|
|
21
|
+
"rtx3060-12": ("RTX 3060 12GB", 12, 360),
|
|
22
|
+
"t4-16": ("T4 · Colab free", 15, 320),
|
|
23
|
+
"rtx4070-12": ("RTX 4070 12GB", 12, 504),
|
|
24
|
+
"rtx4060ti-16": ("RTX 4060 Ti 16GB", 16, 288),
|
|
25
|
+
"apple-m-16": ("Apple M-series 16GB unified", 11, 120),
|
|
26
|
+
"cpu-only": ("CPU only", 0, 0),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def ask(prompt: str, default: str = "") -> str:
|
|
31
|
+
suffix = f" [dim]({default})[/]" if default else ""
|
|
32
|
+
console.print(f"[green]?[/] {prompt}{suffix} ", end="")
|
|
33
|
+
try:
|
|
34
|
+
val = input().strip()
|
|
35
|
+
except (EOFError, KeyboardInterrupt):
|
|
36
|
+
console.print("\n[dim]aborted[/]")
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
if val.lower() in {"q", "quit", "exit"}:
|
|
39
|
+
console.print("[dim]bye — nothing was run[/]")
|
|
40
|
+
sys.exit(0)
|
|
41
|
+
return val or default
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def banner():
|
|
45
|
+
console.print(f"[green]// llm fitlab[/] [dim]— fit-check console · v{__version__}[/]")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def load_registry(refresh: bool = False) -> dict:
|
|
49
|
+
reg, src = registry.load(refresh)
|
|
50
|
+
label = {"live": "live (weekly)", "cache": "cached <24h", "stale-cache": "stale cache — offline?",
|
|
51
|
+
"bundled-seed": "bundled seed — set FITLAB_REGISTRY_URL for weekly updates"}[src]
|
|
52
|
+
console.print(f"[dim]registry {reg.get('generated_at', '?')} · "
|
|
53
|
+
f"{len(reg.get('models', {}))} models · {label}[/]")
|
|
54
|
+
return reg
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def hw_summary(hw: dict):
|
|
58
|
+
vram = f"{hw['vram_gb']} GB VRAM" if hw["vram_gb"] else "no VRAM"
|
|
59
|
+
console.print(f"[bold]your rig:[/] {hw['name']} · {vram} · ~{hw['bw']} GB/s"
|
|
60
|
+
+ (f" [dim]{hw['note']}[/]" if hw.get("note") else ""))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def ordered_models(reg: dict, category: str):
|
|
64
|
+
cats = reg.get("categories", {})
|
|
65
|
+
if category == "all":
|
|
66
|
+
seen, out = set(), []
|
|
67
|
+
for c in cats.values():
|
|
68
|
+
for t in c.get("top", []):
|
|
69
|
+
if t["id"] not in seen and t["id"] in reg["models"]:
|
|
70
|
+
seen.add(t["id"])
|
|
71
|
+
out.append(t["id"])
|
|
72
|
+
return out
|
|
73
|
+
return [t["id"] for t in cats.get(category, {}).get("top", []) if t["id"] in reg["models"]]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def fit_table(reg: dict, ids, hw: dict, quant: str, ctx: int, limit: int | None = None):
|
|
77
|
+
tbl = Table(title=f"fit check · {hw['name']} · {quant} · {ctx // 1024}K ctx",
|
|
78
|
+
title_style="bold", header_style="dim")
|
|
79
|
+
for col in ("#", "model", "params", "est GB", "verdict", "~tok/s", "ollama tag"):
|
|
80
|
+
tbl.add_column(col)
|
|
81
|
+
rows = []
|
|
82
|
+
for i, mid in enumerate(ids[:limit] if limit else ids, 1):
|
|
83
|
+
m = reg["models"][mid]
|
|
84
|
+
if not m.get("arch"):
|
|
85
|
+
continue
|
|
86
|
+
f = estimate.fit(m, quant, ctx, hw["vram_gb"], hw["bw"])
|
|
87
|
+
rows.append((mid, m, f))
|
|
88
|
+
tps = "—" if f["verdict"] == "no" else (f"~{f['est_tps']}" if f["est_tps"] else "n/a")
|
|
89
|
+
tbl.add_row(str(i), m["hf_id"],
|
|
90
|
+
f"{m.get('active_params_b') or m['params_b']}B",
|
|
91
|
+
f"{f['est_gb']:.1f}",
|
|
92
|
+
f"[{VERDICT_STYLE[f['verdict']]}]{f['verdict'].upper()}[/]",
|
|
93
|
+
tps, m.get("ollama_tag") or "[dim]fit-only[/]")
|
|
94
|
+
console.print(tbl)
|
|
95
|
+
return rows
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def submit_url(result: dict) -> str:
|
|
99
|
+
title = f"[bench] {result['ollama_tag']} on {result['gpu']['name']}"
|
|
100
|
+
body = "```json\n" + json.dumps(result, indent=2) + "\n```"
|
|
101
|
+
return (f"https://github.com/{REPO}/issues/new?labels=benchmark-submission"
|
|
102
|
+
f"&title={quote(title)}&body={quote(body)}")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def run_benchmarks(picked, hw, reps: int, ctx: int, submitter: str):
|
|
106
|
+
if not bench.ollama_installed():
|
|
107
|
+
console.print("[yellow]Ollama is not installed[/] — it is the engine FitLab benchmarks with.")
|
|
108
|
+
if ask("Install now via the official script? y/N", "n").lower() == "y":
|
|
109
|
+
if not bench.install_ollama():
|
|
110
|
+
console.print(f"[red]install failed[/] — run manually: {bench.INSTALL_CMD}")
|
|
111
|
+
return []
|
|
112
|
+
else:
|
|
113
|
+
console.print(f"[dim]install later with:[/] {bench.INSTALL_CMD}")
|
|
114
|
+
return []
|
|
115
|
+
results = []
|
|
116
|
+
for mid, m, _f in picked:
|
|
117
|
+
console.rule(f"[green]{m['ollama_tag']}[/]")
|
|
118
|
+
try:
|
|
119
|
+
result, path = bench.bench_model(
|
|
120
|
+
m["ollama_tag"], hardware.schema_gpu(hw), model_id=mid, reps=reps,
|
|
121
|
+
context=ctx, submitter=submitter, log=lambda s: console.print(f"[dim]{s}[/]"))
|
|
122
|
+
results.append((result, path))
|
|
123
|
+
console.print(f"[bold green]✓[/] {result['metrics']['gen_tps']} tok/s median · "
|
|
124
|
+
f"ttft {result['metrics']['ttft_ms']:.0f} ms · "
|
|
125
|
+
f"peak {result['metrics']['peak_vram_gb']} GB → [dim]{path}[/]")
|
|
126
|
+
except Exception as e:
|
|
127
|
+
console.print(f"[red]✗ {mid}: {e}[/]")
|
|
128
|
+
if results:
|
|
129
|
+
console.rule("[green]done")
|
|
130
|
+
tbl = Table(header_style="dim")
|
|
131
|
+
for col in ("model", "tok/s", "ttft ms", "load s", "peak GB"):
|
|
132
|
+
tbl.add_column(col)
|
|
133
|
+
for r, _ in results:
|
|
134
|
+
mm = r["metrics"]
|
|
135
|
+
tbl.add_row(r["ollama_tag"], str(mm["gen_tps"]), f"{mm['ttft_ms']:.0f}",
|
|
136
|
+
str(mm["load_s"]), str(mm["peak_vram_gb"]))
|
|
137
|
+
console.print(tbl)
|
|
138
|
+
console.print("\n[bold]share your numbers[/] — open to file a prefilled submission "
|
|
139
|
+
"(CI validates it and your rig joins the radar):")
|
|
140
|
+
for r, _ in results:
|
|
141
|
+
console.print(f" [green]↗[/] {submit_url(r)[:120]}…" if len(submit_url(r)) > 120
|
|
142
|
+
else f" [green]↗[/] {submit_url(r)}")
|
|
143
|
+
return results
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def wizard():
|
|
147
|
+
banner()
|
|
148
|
+
hw = hardware.detect()
|
|
149
|
+
hw_summary(hw)
|
|
150
|
+
reg = load_registry()
|
|
151
|
+
console.print()
|
|
152
|
+
|
|
153
|
+
cats = list(reg.get("categories", {}).keys()) + ["all"]
|
|
154
|
+
for i, c in enumerate(cats, 1):
|
|
155
|
+
title = reg["categories"][c]["title"] if c in reg.get("categories", {}) else "everything, deduplicated"
|
|
156
|
+
console.print(f" [green]{i}[/] {c} [dim]— {title}[/]")
|
|
157
|
+
pick = ask(f"Category 1-{len(cats)}", "1")
|
|
158
|
+
try:
|
|
159
|
+
category = cats[int(pick) - 1]
|
|
160
|
+
except (ValueError, IndexError):
|
|
161
|
+
console.print("[red]not a valid choice[/]")
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
|
|
164
|
+
ids = ordered_models(reg, category)
|
|
165
|
+
rows = fit_table(reg, ids, hw, "Q4_K_M", 8192)
|
|
166
|
+
benchable = [(mid, m, f) for mid, m, f in rows if m.get("ollama_tag") and f["verdict"] != "no"]
|
|
167
|
+
|
|
168
|
+
sel = ask("Benchmark which? — e.g. 1,3 · a = all that fit · q = quit", "a")
|
|
169
|
+
if sel.lower() == "a":
|
|
170
|
+
picked = benchable
|
|
171
|
+
else:
|
|
172
|
+
picked = []
|
|
173
|
+
for tok in sel.split(","):
|
|
174
|
+
tok = tok.strip()
|
|
175
|
+
if not tok.isdigit() or not (1 <= int(tok) <= len(rows)):
|
|
176
|
+
console.print(f"[red]'{tok}' is not a row number[/]")
|
|
177
|
+
sys.exit(1)
|
|
178
|
+
mid, m, f = rows[int(tok) - 1]
|
|
179
|
+
if not m.get("ollama_tag"):
|
|
180
|
+
console.print(f"[yellow]{mid} has no ollama tag — fit-only, skipping[/]")
|
|
181
|
+
continue
|
|
182
|
+
picked.append((mid, m, f))
|
|
183
|
+
if not picked:
|
|
184
|
+
console.print("[dim]nothing to benchmark[/]")
|
|
185
|
+
return
|
|
186
|
+
console.print("picked: " + ", ".join(m["ollama_tag"] for _, m, _f in picked))
|
|
187
|
+
|
|
188
|
+
reps = int(ask("Repetitions per model", "3") or 3)
|
|
189
|
+
submitter = ask("GitHub handle for credit (optional)", "")
|
|
190
|
+
est_pull = ", ".join(f"{f['weights_gb']:.0f} GB" for _, _m, f in picked[:4])
|
|
191
|
+
console.print(f"[dim]heads-up: models download once via ollama (~{est_pull}{' …' if len(picked) > 4 else ''})[/]")
|
|
192
|
+
if ask(f"Run {len(picked)} benchmark(s) now? Y/n", "y").lower() not in {"y", "yes"}:
|
|
193
|
+
console.print("[dim]bye — nothing was run[/]")
|
|
194
|
+
return
|
|
195
|
+
run_benchmarks(picked, hw, reps, 4096, submitter)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def resolve_gpu(spec: str) -> dict:
|
|
199
|
+
if spec == "auto":
|
|
200
|
+
return hardware.detect()
|
|
201
|
+
if spec in PROFILES:
|
|
202
|
+
name, vram, bw = PROFILES[spec]
|
|
203
|
+
return {"kind": "profile", "name": name, "vram_gb": vram, "bw": bw,
|
|
204
|
+
"profile_id": spec, "driver": "", "cuda": "", "note": ""}
|
|
205
|
+
console.print(f"[red]unknown gpu '{spec}'[/] — use auto or one of: {', '.join(PROFILES)}")
|
|
206
|
+
sys.exit(1)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def main(argv=None):
|
|
210
|
+
ap = argparse.ArgumentParser(prog="fitlab",
|
|
211
|
+
description="Will it run on my setup? Run `fitlab` with no arguments for the wizard.")
|
|
212
|
+
ap.add_argument("--version", action="version", version=f"fitlab {__version__}")
|
|
213
|
+
sub = ap.add_subparsers(dest="cmd")
|
|
214
|
+
|
|
215
|
+
sub.add_parser("detect", help="show detected hardware")
|
|
216
|
+
sub.add_parser("update", help="refresh the weekly model registry now")
|
|
217
|
+
|
|
218
|
+
p_check = sub.add_parser("check", help="fit table for a GPU (no benchmarking)")
|
|
219
|
+
p_check.add_argument("--gpu", default="auto", help="auto or profile id, e.g. rtx3060-12")
|
|
220
|
+
p_check.add_argument("--quant", default="Q4_K_M", choices=sorted(estimate.BPW))
|
|
221
|
+
p_check.add_argument("--ctx", type=int, default=8192, choices=estimate.CTX_CHOICES)
|
|
222
|
+
p_check.add_argument("--category", default="all")
|
|
223
|
+
p_check.add_argument("--limit", type=int, default=None)
|
|
224
|
+
|
|
225
|
+
p_bench = sub.add_parser("bench", help="benchmark one model non-interactively")
|
|
226
|
+
p_bench.add_argument("--model", required=True, help="ollama tag (qwen3:8b) or registry id (qwen3-8b)")
|
|
227
|
+
p_bench.add_argument("--reps", type=int, default=3)
|
|
228
|
+
p_bench.add_argument("--context", type=int, default=4096)
|
|
229
|
+
p_bench.add_argument("--out", default="fitlab-results")
|
|
230
|
+
p_bench.add_argument("--submitter", default="")
|
|
231
|
+
p_bench.add_argument("--source", default="community-local",
|
|
232
|
+
choices=["ci-kaggle", "ci-actions-cpu", "community-colab",
|
|
233
|
+
"community-local", "maintainer"])
|
|
234
|
+
p_bench.add_argument("--dry-run", action="store_true", help="show the fit + plan, don't run")
|
|
235
|
+
|
|
236
|
+
args = ap.parse_args(argv)
|
|
237
|
+
|
|
238
|
+
if args.cmd is None:
|
|
239
|
+
wizard()
|
|
240
|
+
return 0
|
|
241
|
+
|
|
242
|
+
if args.cmd == "detect":
|
|
243
|
+
banner()
|
|
244
|
+
hw_summary(hardware.detect())
|
|
245
|
+
return 0
|
|
246
|
+
|
|
247
|
+
if args.cmd == "update":
|
|
248
|
+
load_registry(refresh=True)
|
|
249
|
+
return 0
|
|
250
|
+
|
|
251
|
+
if args.cmd == "check":
|
|
252
|
+
banner()
|
|
253
|
+
hw = resolve_gpu(args.gpu)
|
|
254
|
+
hw_summary(hw)
|
|
255
|
+
reg = load_registry()
|
|
256
|
+
ids = ordered_models(reg, args.category)
|
|
257
|
+
if not ids:
|
|
258
|
+
console.print(f"[red]unknown category[/] — one of: "
|
|
259
|
+
f"{', '.join(reg.get('categories', {}))}, all")
|
|
260
|
+
return 1
|
|
261
|
+
fit_table(reg, ids, hw, args.quant, args.ctx, args.limit)
|
|
262
|
+
return 0
|
|
263
|
+
|
|
264
|
+
if args.cmd == "bench":
|
|
265
|
+
banner()
|
|
266
|
+
hw = hardware.detect()
|
|
267
|
+
hw_summary(hw)
|
|
268
|
+
reg = load_registry()
|
|
269
|
+
mid, m = None, None
|
|
270
|
+
if args.model in reg.get("models", {}):
|
|
271
|
+
mid, m = args.model, reg["models"][args.model]
|
|
272
|
+
tag = m.get("ollama_tag")
|
|
273
|
+
if not tag:
|
|
274
|
+
console.print(f"[red]{mid} has no ollama tag in the registry[/]")
|
|
275
|
+
return 1
|
|
276
|
+
else:
|
|
277
|
+
tag = args.model
|
|
278
|
+
if m and m.get("arch"):
|
|
279
|
+
f = estimate.fit(m, "Q4_K_M", args.context, hw["vram_gb"], hw["bw"])
|
|
280
|
+
console.print(f"fit: [{VERDICT_STYLE[f['verdict']]}]{f['verdict'].upper()}[/] · "
|
|
281
|
+
f"est {f['est_gb']} GB / {hw['vram_gb']} GB")
|
|
282
|
+
if args.dry_run:
|
|
283
|
+
console.print(f"[dim]dry-run: would pull {tag}, run {args.reps}× "
|
|
284
|
+
f"(num_predict 256, seed 42, ctx {args.context}) → {args.out}/[/]")
|
|
285
|
+
return 0
|
|
286
|
+
try:
|
|
287
|
+
result, path = bench.bench_model(tag, hardware.schema_gpu(hw), model_id=mid,
|
|
288
|
+
reps=args.reps, context=args.context,
|
|
289
|
+
source=args.source, submitter=args.submitter,
|
|
290
|
+
out_dir=args.out,
|
|
291
|
+
log=lambda s: console.print(f"[dim]{s}[/]"))
|
|
292
|
+
except Exception as e:
|
|
293
|
+
console.print(f"[red]benchmark failed: {e}[/]")
|
|
294
|
+
return 1
|
|
295
|
+
console.print(f"[bold green]✓[/] {result['metrics']['gen_tps']} tok/s median → {path}")
|
|
296
|
+
console.print(f"[green]↗ submit:[/] {submit_url(result)}")
|
|
297
|
+
return 0
|
|
298
|
+
|
|
299
|
+
return 0
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
if __name__ == "__main__":
|
|
303
|
+
sys.exit(main())
|