tuieval 0.2.0.dev79__py3-none-any.whl → 0.2.0.dev86__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.
- tuieval/_version.py +2 -2
- tuieval/engine.py +99 -39
- tuieval/run_evals.py +9 -2
- tuieval/tui.py +60 -9
- {tuieval-0.2.0.dev79.dist-info → tuieval-0.2.0.dev86.dist-info}/METADATA +1 -1
- {tuieval-0.2.0.dev79.dist-info → tuieval-0.2.0.dev86.dist-info}/RECORD +9 -9
- {tuieval-0.2.0.dev79.dist-info → tuieval-0.2.0.dev86.dist-info}/WHEEL +0 -0
- {tuieval-0.2.0.dev79.dist-info → tuieval-0.2.0.dev86.dist-info}/entry_points.txt +0 -0
- {tuieval-0.2.0.dev79.dist-info → tuieval-0.2.0.dev86.dist-info}/licenses/LICENSE +0 -0
tuieval/_version.py
CHANGED
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.2.0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2, 0, '
|
|
21
|
+
__version__ = version = '0.2.0.dev86'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 0, 'dev86')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
tuieval/engine.py
CHANGED
|
@@ -47,6 +47,7 @@ import subprocess
|
|
|
47
47
|
import threading
|
|
48
48
|
import time
|
|
49
49
|
import tomllib
|
|
50
|
+
import urllib.parse
|
|
50
51
|
import urllib.request
|
|
51
52
|
|
|
52
53
|
from . import client
|
|
@@ -703,6 +704,7 @@ class Engine:
|
|
|
703
704
|
self._procs = []
|
|
704
705
|
self._stop_lock = threading.Lock()
|
|
705
706
|
self._run_lock = threading.Lock()
|
|
707
|
+
self._machine_take = threading.Lock() # one lane takes the machine lock for a hosted-only run
|
|
706
708
|
self._run = None # the run going: {"jobs", "pending", "open", "machine", "cond"} (add_jobs)
|
|
707
709
|
self._header_cache = {}
|
|
708
710
|
self._serving = {}
|
|
@@ -1258,6 +1260,36 @@ class Engine:
|
|
|
1258
1260
|
except (TypeError, ValueError):
|
|
1259
1261
|
return 1
|
|
1260
1262
|
|
|
1263
|
+
def parallel_hosted(self):
|
|
1264
|
+
"""How many hosted models (APIs) a run serves at a time, besides the local ones: models.toml
|
|
1265
|
+
[machines.<id>] parallel_hosted (default 4). They don't use this machine's GPU or memory."""
|
|
1266
|
+
try:
|
|
1267
|
+
return max(1, int(self.machine_settings().get("parallel_hosted", 4)))
|
|
1268
|
+
except (TypeError, ValueError):
|
|
1269
|
+
return 4
|
|
1270
|
+
|
|
1271
|
+
def models_at_once(self, labels, parallel=None, parallel_hosted=None):
|
|
1272
|
+
"""How many of these models run at the same time: local ones up to parallel (parallel_models),
|
|
1273
|
+
hosted ones beside them up to parallel_hosted."""
|
|
1274
|
+
labels = set(labels)
|
|
1275
|
+
hosted = sum(self.hosted(self.model(l)) for l in labels)
|
|
1276
|
+
return (min(parallel or self.parallel_models(), len(labels) - hosted)
|
|
1277
|
+
+ min(parallel_hosted or self.parallel_hosted(), hosted))
|
|
1278
|
+
|
|
1279
|
+
def hosted(self, m):
|
|
1280
|
+
"""A model served by an API elsewhere: tuieval doesn't start its server and its url isn't this
|
|
1281
|
+
machine (or its server sets hosted = true). A server you started yourself on localhost still
|
|
1282
|
+
counts as local (it uses the GPU)."""
|
|
1283
|
+
if m.get("remote"):
|
|
1284
|
+
return True
|
|
1285
|
+
server = self.cfg["servers"][m["server"]]
|
|
1286
|
+
if "hosted" in server: # say so for a server the url doesn't give away (e.g. a local proxy to an API)
|
|
1287
|
+
return bool(server["hosted"])
|
|
1288
|
+
if server.get("cmd"):
|
|
1289
|
+
return False
|
|
1290
|
+
host = urllib.parse.urlparse(server.get("url") or "").hostname or ""
|
|
1291
|
+
return bool(host) and host not in ("localhost", "127.0.0.1", "::1", "0.0.0.0")
|
|
1292
|
+
|
|
1261
1293
|
def memory_need_gb(self, m):
|
|
1262
1294
|
"""GPU memory a model takes while served here: 0 for one we don't start (a hosted API or a
|
|
1263
1295
|
server already running), the fit check's estimate for a local GGUF, else None (unknown)."""
|
|
@@ -1616,17 +1648,17 @@ class Engine:
|
|
|
1616
1648
|
return any(self.cfg["servers"][j.model["server"]].get("cmd") for j in jobs if j.status == "waiting")
|
|
1617
1649
|
|
|
1618
1650
|
# ---- running
|
|
1619
|
-
def run(self, jobs, parallel=None):
|
|
1651
|
+
def run(self, jobs, parallel=None, parallel_hosted=None):
|
|
1620
1652
|
"""Run the jobs (blocking). Jobs of the same model share one server start. parallel: how many
|
|
1621
|
-
models are served at a time (default: parallel_models for this machine)
|
|
1622
|
-
|
|
1653
|
+
local models are served at a time (default: parallel_models for this machine); hosted models
|
|
1654
|
+
(APIs) run beside them, up to parallel_hosted at a time (default: parallel_hosted). add_jobs()
|
|
1655
|
+
can add more while it runs; they run after the ones before them."""
|
|
1623
1656
|
self._cancel.clear()
|
|
1624
1657
|
self.emit("queue_started", jobs=jobs)
|
|
1625
1658
|
models = sorted({j.label for j in jobs if j.status == "waiting"})
|
|
1626
1659
|
machine = bool(jobs) and self.needs_machine(jobs)
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
if machine else contextlib.nullcontext())
|
|
1660
|
+
what = f"{jobs[0].tier} run: {', '.join(models[:3])}" + (f" +{len(models) - 3}" if len(models) > 3 else "")
|
|
1661
|
+
stack = contextlib.ExitStack() # holds the machine lock; a hosted-only run takes it if local models are added
|
|
1630
1662
|
order = []
|
|
1631
1663
|
for j in jobs:
|
|
1632
1664
|
if j.label not in order:
|
|
@@ -1634,10 +1666,13 @@ class Engine:
|
|
|
1634
1666
|
groups = [(label, [j for j in jobs if j.label == label and j.status == "waiting"]) for label in order]
|
|
1635
1667
|
with self._run_lock:
|
|
1636
1668
|
self._run = {"jobs": jobs, "pending": [g for g in groups if g[1]], "open": True, "machine": machine,
|
|
1637
|
-
"cond": threading.Condition(self._run_lock)}
|
|
1669
|
+
"cond": threading.Condition(self._run_lock), "stack": stack, "what": what}
|
|
1638
1670
|
try:
|
|
1639
|
-
with
|
|
1640
|
-
|
|
1671
|
+
with stack:
|
|
1672
|
+
if machine:
|
|
1673
|
+
stack.enter_context(self.machine_lock(what))
|
|
1674
|
+
self._run_lanes(max(1, int(parallel or self.parallel_models())),
|
|
1675
|
+
max(1, int(parallel_hosted or self.parallel_hosted())))
|
|
1641
1676
|
if self._cancel.is_set():
|
|
1642
1677
|
raise Cancelled()
|
|
1643
1678
|
except Cancelled:
|
|
@@ -1664,14 +1699,13 @@ class Engine:
|
|
|
1664
1699
|
def add_jobs(self, new):
|
|
1665
1700
|
"""Add planned jobs to the run going on, after its other jobs. Returns (added, why not): a
|
|
1666
1701
|
pack already waiting or running in it isn't added twice, and nothing is added once the run is
|
|
1667
|
-
finishing or cancelled
|
|
1702
|
+
finishing or cancelled. Local models added to a hosted-only run take the machine lock when the
|
|
1703
|
+
first of them starts.
|
|
1668
1704
|
Runs on the caller's thread, so the caller shows what was added (no event)."""
|
|
1669
1705
|
with self._run_lock:
|
|
1670
1706
|
r = self._run
|
|
1671
1707
|
if not r or not r["open"] or self._cancel.is_set():
|
|
1672
1708
|
return [], "the run is finishing"
|
|
1673
|
-
if not r["machine"] and self.needs_machine(new):
|
|
1674
|
-
return [], "the run going uses only hosted models, and these need this machine"
|
|
1675
1709
|
busy = {j.key for j in r["jobs"] if j.status in ("waiting", "loading", "running")}
|
|
1676
1710
|
added = [j for j in new if j.status == "waiting" and j.key not in busy]
|
|
1677
1711
|
r["jobs"].extend(added)
|
|
@@ -1684,33 +1718,51 @@ class Engine:
|
|
|
1684
1718
|
r["cond"].notify_all()
|
|
1685
1719
|
return added, None if added else "everything picked is already in the run"
|
|
1686
1720
|
|
|
1687
|
-
def _run_lanes(self, parallel):
|
|
1688
|
-
"""Run each model's jobs in its own lane
|
|
1689
|
-
|
|
1690
|
-
next to the ones running (memory_need_gb; an unknown need is left to the setting)
|
|
1691
|
-
|
|
1721
|
+
def _run_lanes(self, parallel, parallel_hosted=4):
|
|
1722
|
+
"""Run each model's jobs in its own lane: up to `parallel` local models at a time, in order (the
|
|
1723
|
+
next one starts once a lane is free, nothing running stops it (runs_alone), and its memory fits
|
|
1724
|
+
next to the ones running (memory_need_gb; an unknown need is left to the setting)), and beside
|
|
1725
|
+
them up to `parallel_hosted` hosted models, which need none of this machine. Lanes wait for jobs
|
|
1726
|
+
added meanwhile (add_jobs) until every model is done."""
|
|
1692
1727
|
pending, running, told = self._run["pending"], {}, set()
|
|
1693
1728
|
cond = self._run["cond"]
|
|
1729
|
+
hosted = {}
|
|
1730
|
+
|
|
1731
|
+
def is_hosted(label):
|
|
1732
|
+
if label not in hosted:
|
|
1733
|
+
hosted[label] = self.hosted(self.model(label))
|
|
1734
|
+
return hosted[label]
|
|
1694
1735
|
|
|
1695
1736
|
def blocked(label):
|
|
1696
1737
|
"""Why the next model can't start now (None: it can)."""
|
|
1697
|
-
if
|
|
1738
|
+
if is_hosted(label):
|
|
1739
|
+
return "" if sum(map(is_hosted, running)) >= parallel_hosted else None
|
|
1740
|
+
local = [l for l in running if not is_hosted(l)]
|
|
1741
|
+
if not local:
|
|
1698
1742
|
return None
|
|
1699
|
-
if len(
|
|
1743
|
+
if len(local) >= parallel:
|
|
1700
1744
|
return ""
|
|
1701
1745
|
m = self.model(label)
|
|
1702
|
-
if self.runs_alone(m) or any(self.runs_alone(self.model(l)) for l in
|
|
1703
|
-
alone = label if self.runs_alone(m) else next(l for l in
|
|
1746
|
+
if self.runs_alone(m) or any(self.runs_alone(self.model(l)) for l in local):
|
|
1747
|
+
alone = label if self.runs_alone(m) else next(l for l in local if self.runs_alone(self.model(l)))
|
|
1704
1748
|
return (f"{alone}'s server runs a before_start step that may stop other servers, so it runs "
|
|
1705
1749
|
"on its own")
|
|
1706
1750
|
need = self.memory_need_gb(m)
|
|
1707
|
-
used = sum(
|
|
1751
|
+
used = sum(running[l] or 0 for l in local)
|
|
1708
1752
|
room = self.memory_available_gb()
|
|
1709
1753
|
if need and used + need > room:
|
|
1710
|
-
return (f"needs ~{need:.0f} GB; {', '.join(
|
|
1754
|
+
return (f"needs ~{need:.0f} GB; {', '.join(local)} take ~{used:.0f} of {room:.0f} GB, so it "
|
|
1711
1755
|
"starts when there's room")
|
|
1712
1756
|
return None
|
|
1713
1757
|
|
|
1758
|
+
def take_machine():
|
|
1759
|
+
"""A hosted-only run that was given local models takes the machine lock before the first
|
|
1760
|
+
of them starts (it may wait for another tuieval window, like any run)."""
|
|
1761
|
+
with self._machine_take:
|
|
1762
|
+
if not self._run["machine"]:
|
|
1763
|
+
self._run["stack"].enter_context(self.machine_lock(self._run["what"]))
|
|
1764
|
+
self._run["machine"] = True
|
|
1765
|
+
|
|
1714
1766
|
def lane():
|
|
1715
1767
|
while True:
|
|
1716
1768
|
mine, note = None, None
|
|
@@ -1720,33 +1772,41 @@ class Engine:
|
|
|
1720
1772
|
cond.notify_all()
|
|
1721
1773
|
return
|
|
1722
1774
|
# in order, except that a model added again while it runs waits for itself to end
|
|
1723
|
-
# without holding up the ones behind it
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1775
|
+
# without holding up the ones behind it; the first waiting hosted model doesn't wait
|
|
1776
|
+
# for local ones (and the first local one doesn't wait for hosted ones)
|
|
1777
|
+
waiting = [i for i, g in enumerate(pending) if g[0] not in running]
|
|
1778
|
+
first = {}
|
|
1779
|
+
for i in waiting:
|
|
1780
|
+
first.setdefault(is_hosted(pending[i][0]), i)
|
|
1781
|
+
why, label = "", None
|
|
1782
|
+
for i in sorted(first.values()):
|
|
1783
|
+
why = blocked(pending[i][0])
|
|
1784
|
+
if why is None:
|
|
1785
|
+
label, mine = pending.pop(i)
|
|
1786
|
+
running[label] = self.memory_need_gb(self.model(label))
|
|
1787
|
+
break
|
|
1788
|
+
if mine is None:
|
|
1789
|
+
label = pending[min(first.values())][0] if first else None
|
|
1790
|
+
if why and label and label not in told:
|
|
1791
|
+
told.add(label)
|
|
1792
|
+
note = f"{label} waits: {why}"
|
|
1793
|
+
else:
|
|
1794
|
+
cond.wait(1.0)
|
|
1735
1795
|
if note:
|
|
1736
1796
|
self.emit("model_waiting", label=label, message=note)
|
|
1737
1797
|
if mine is None:
|
|
1738
1798
|
continue
|
|
1739
1799
|
try:
|
|
1800
|
+
if not is_hosted(label) and self.cfg["servers"][mine[0].model["server"]].get("cmd"):
|
|
1801
|
+
take_machine()
|
|
1740
1802
|
self._run_lane(label, mine)
|
|
1741
1803
|
finally:
|
|
1742
1804
|
with cond:
|
|
1743
1805
|
running.pop(label, None)
|
|
1744
1806
|
cond.notify_all()
|
|
1745
1807
|
|
|
1746
|
-
if
|
|
1747
|
-
|
|
1748
|
-
return
|
|
1749
|
-
threads = [threading.Thread(target=lane, name=f"lane-{i + 1}") for i in range(parallel)]
|
|
1808
|
+
# lanes for hosted models too, even if none is queued yet: add_jobs may bring some
|
|
1809
|
+
threads = [threading.Thread(target=lane, name=f"lane-{i + 1}") for i in range(parallel + parallel_hosted)]
|
|
1750
1810
|
for t in threads:
|
|
1751
1811
|
t.start()
|
|
1752
1812
|
for t in threads:
|
tuieval/run_evals.py
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
tuieval run --tier smoke --only openrouter:qwen/qwen3-32b # any OpenRouter model, no models.toml edit
|
|
18
18
|
tuieval run --dry-run # print the plan and server commands only
|
|
19
19
|
tuieval run --parallel 2 # serve two models at a time (parallel_models in models.toml)
|
|
20
|
+
tuieval run --parallel-hosted 6 # hosted models at a time beside them (default 4)
|
|
20
21
|
tuieval add ~/models/New-Model-Q4_K_M.gguf # register a model (--vision/--mmproj, --no-think, --tags)
|
|
21
22
|
tuieval scan # GGUFs in model_dirs that aren't registered yet
|
|
22
23
|
tuieval list # the models tuieval knows; hidden ones listed separately
|
|
@@ -755,6 +756,9 @@ def main(argv=None):
|
|
|
755
756
|
p.add_argument("--smoke", action="store_const", const="smoke", dest="tier", help="same as --tier smoke")
|
|
756
757
|
p.add_argument("--parallel", type=int, metavar="N",
|
|
757
758
|
help="models served at a time (default: parallel_models for this machine in models.toml, else 1)")
|
|
759
|
+
p.add_argument("--parallel-hosted", type=int, metavar="N",
|
|
760
|
+
help="hosted models (APIs) served at a time beside the local ones (default: parallel_hosted "
|
|
761
|
+
"in models.toml, else 4)")
|
|
758
762
|
p.add_argument("--dry-run", action="store_true", help="print the plan and commands without running")
|
|
759
763
|
p.add_argument("--brief", action="store_true", help="end with one line per model instead of the scorecards")
|
|
760
764
|
p.add_argument("--models", default=None, help="default: the workspace's models.toml")
|
|
@@ -769,6 +773,9 @@ def main(argv=None):
|
|
|
769
773
|
if args.parallel is not None and args.parallel < 1:
|
|
770
774
|
sys.exit("--parallel must be 1 or more")
|
|
771
775
|
parallel = args.parallel or e.parallel_models()
|
|
776
|
+
if args.parallel_hosted is not None and args.parallel_hosted < 1:
|
|
777
|
+
sys.exit("--parallel-hosted must be 1 or more")
|
|
778
|
+
parallel_hosted = args.parallel_hosted or e.parallel_hosted()
|
|
772
779
|
models = e.cfg["models"]
|
|
773
780
|
labels = [m["label"] for m in models if not m.get("remote")] # hosted APIs cost money: only by name
|
|
774
781
|
if args.only:
|
|
@@ -837,12 +844,12 @@ def main(argv=None):
|
|
|
837
844
|
|
|
838
845
|
signal.signal(signal.SIGINT, stop)
|
|
839
846
|
signal.signal(signal.SIGTERM, stop)
|
|
840
|
-
side_by_side =
|
|
847
|
+
side_by_side = e.models_at_once({j.label for j in jobs if j.status == "waiting"}, parallel, parallel_hosted)
|
|
841
848
|
if side_by_side > 1:
|
|
842
849
|
printer.side_by_side = True
|
|
843
850
|
say(f"up to {side_by_side} models at a time: one line per answer; each answer notes the models "
|
|
844
851
|
"it ran alongside, since they share this machine's speed")
|
|
845
|
-
worker = threading.Thread(target=e.run, args=(jobs, parallel))
|
|
852
|
+
worker = threading.Thread(target=e.run, args=(jobs, parallel, parallel_hosted))
|
|
846
853
|
worker.start()
|
|
847
854
|
while worker.is_alive():
|
|
848
855
|
worker.join(0.5)
|
tuieval/tui.py
CHANGED
|
@@ -975,7 +975,7 @@ class SetupScreen(Screen):
|
|
|
975
975
|
resumed = [j for j in run if "done earlier" in j.note]
|
|
976
976
|
if resumed:
|
|
977
977
|
text += f" [yellow]{len(resumed)} pack(s) continue from earlier results[/yellow]"
|
|
978
|
-
at_once =
|
|
978
|
+
at_once = e.models_at_once({j.label for j in run}, self.parallel())
|
|
979
979
|
if at_once > 1:
|
|
980
980
|
text += (f"\n[cyan]Up to {at_once} models at a time[/cyan][dim] (the time above assumes one at a "
|
|
981
981
|
"time). Answers note the models they ran alongside, since those share the machine's "
|
|
@@ -1550,6 +1550,13 @@ def run_title(tier, models, packs):
|
|
|
1550
1550
|
f"{', '.join(packs) if len(packs) <= 2 else f'{len(packs)} packs'}")
|
|
1551
1551
|
|
|
1552
1552
|
|
|
1553
|
+
def short_keys(keys, n=4):
|
|
1554
|
+
"""A few "model/pack" pairs as "model · pack", the rest counted."""
|
|
1555
|
+
keys = sorted(keys)
|
|
1556
|
+
shown = ", ".join(k.replace("/", " · ", 1) for k in keys[:n])
|
|
1557
|
+
return rich_escape(shown + (f" and {len(keys) - n} more" if len(keys) > n else ""))
|
|
1558
|
+
|
|
1559
|
+
|
|
1553
1560
|
class QueuedRun:
|
|
1554
1561
|
"""A run waiting its turn. It's planned again when it starts, so it picks up what the runs before
|
|
1555
1562
|
it finished; it never sets aside answers that weren't confirmed when it was queued. retest: the
|
|
@@ -1557,13 +1564,29 @@ class QueuedRun:
|
|
|
1557
1564
|
kind = "Run"
|
|
1558
1565
|
|
|
1559
1566
|
def __init__(self, sel, retest, leave, accepted, eng):
|
|
1560
|
-
self.sel, self.retest, self.leave, self.accepted = sel, retest, leave, accepted
|
|
1567
|
+
self.sel, self.retest, self.leave, self.accepted = sel, retest, set(leave), accepted
|
|
1561
1568
|
self.queued = time.time()
|
|
1569
|
+
self.replan(eng)
|
|
1570
|
+
|
|
1571
|
+
def replan(self, eng):
|
|
1562
1572
|
self.jobs, self.problem = self.plan(eng)
|
|
1563
1573
|
jobs = self.jobs or []
|
|
1564
|
-
self.title = run_title(sel["tier"], {j.label for j in jobs}, {j.pack.label for j in jobs})
|
|
1574
|
+
self.title = run_title(self.sel["tier"], {j.label for j in jobs}, {j.pack.label for j in jobs})
|
|
1565
1575
|
self.est = eng.estimate_seconds(jobs)[0]
|
|
1566
1576
|
|
|
1577
|
+
def keys(self):
|
|
1578
|
+
"""The "model/pack" pairs it covers."""
|
|
1579
|
+
return {f"{m}/{p}" for m in self.sel["models"] for p in self.sel["packs"]} - self.leave
|
|
1580
|
+
|
|
1581
|
+
def deliberate(self):
|
|
1582
|
+
"""Retest or picked questions: asked again on purpose, never treated as a duplicate."""
|
|
1583
|
+
return bool(self.sel.get("force") or self.sel.get("tests"))
|
|
1584
|
+
|
|
1585
|
+
def drop(self, keys, eng):
|
|
1586
|
+
"""Leave these pairs out (they're already running or queued)."""
|
|
1587
|
+
self.leave |= set(keys)
|
|
1588
|
+
self.replan(eng)
|
|
1589
|
+
|
|
1567
1590
|
def tests(self, eng):
|
|
1568
1591
|
"""The picked tests that are still in their packs (a pack edited while queued drops the rest)."""
|
|
1569
1592
|
out = {}
|
|
@@ -2130,8 +2153,7 @@ class RunScreen(Screen):
|
|
|
2130
2153
|
@property
|
|
2131
2154
|
def side_by_side(self):
|
|
2132
2155
|
"""Several models run at a time in this run."""
|
|
2133
|
-
return
|
|
2134
|
-
len({j.label for j in self.jobs if j.status != "skipped"})) > 1
|
|
2156
|
+
return self.app.engine.models_at_once({j.label for j in self.jobs if j.status != "skipped"}, self.parallel) > 1
|
|
2135
2157
|
|
|
2136
2158
|
def active(self):
|
|
2137
2159
|
"""Models being loaded or run now, in queue order."""
|
|
@@ -2446,7 +2468,7 @@ class RunScreen(Screen):
|
|
|
2446
2468
|
rough |= j.key in self.rough
|
|
2447
2469
|
left += (j.total - j.done) * spr
|
|
2448
2470
|
models.add(j.label)
|
|
2449
|
-
lanes = max(1,
|
|
2471
|
+
lanes = max(1, self.app.engine.models_at_once(models, self.parallel))
|
|
2450
2472
|
return left / lanes, rough
|
|
2451
2473
|
|
|
2452
2474
|
def update_overall(self):
|
|
@@ -3237,8 +3259,32 @@ class EvalsApp(App):
|
|
|
3237
3259
|
what = s.live_text() if s else "The last run is still stopping its server"
|
|
3238
3260
|
return what + (f" (+{len(self.queue)} queued)" if self.queue else "")
|
|
3239
3261
|
|
|
3262
|
+
def taken(self, tier):
|
|
3263
|
+
"""The "model/pack" pairs of this tier already in the run going (not finished) or in the queue."""
|
|
3264
|
+
out = set()
|
|
3265
|
+
run = self.active_session
|
|
3266
|
+
if isinstance(run, RunScreen) and run.running and run.jobs and run.jobs[0].tier == tier:
|
|
3267
|
+
out |= {j.key for j in run.jobs if j.status in ("waiting", "loading", "running")}
|
|
3268
|
+
for q in self.queue:
|
|
3269
|
+
if q.kind == "Run" and q.sel["tier"] == tier:
|
|
3270
|
+
out |= q.keys()
|
|
3271
|
+
return out
|
|
3272
|
+
|
|
3240
3273
|
# -- the queue: Start or t while something runs adds to it; each item starts when the one before ends
|
|
3241
3274
|
def start_or_queue(self, item):
|
|
3275
|
+
skipped = set()
|
|
3276
|
+
if self.is_busy() and item.kind == "Run" and not item.deliberate():
|
|
3277
|
+
mine = item.keys()
|
|
3278
|
+
skipped = mine & self.taken(item.sel["tier"])
|
|
3279
|
+
if skipped == mine:
|
|
3280
|
+
self.notify(f"Already running or queued, so nothing was added: {short_keys(skipped)}.", timeout=10)
|
|
3281
|
+
return
|
|
3282
|
+
if skipped:
|
|
3283
|
+
item.drop(skipped, self.engine)
|
|
3284
|
+
if not item.jobs:
|
|
3285
|
+
self.notify(f"Nothing new to run: {item.problem}. Already running or queued: "
|
|
3286
|
+
f"{short_keys(skipped)}.", timeout=10)
|
|
3287
|
+
return
|
|
3242
3288
|
if not self.is_busy():
|
|
3243
3289
|
screen, problem = item.make(self.engine)
|
|
3244
3290
|
if problem:
|
|
@@ -3257,11 +3303,11 @@ class EvalsApp(App):
|
|
|
3257
3303
|
self.add_to_run(run, item)
|
|
3258
3304
|
elif choice == "queue":
|
|
3259
3305
|
self.enqueue(item)
|
|
3260
|
-
what =
|
|
3261
|
-
|
|
3306
|
+
what = short_keys(item.keys(), 6)
|
|
3307
|
+
skip = (f"\n\nLeft out, already running or queued: {short_keys(skipped)}." if skipped else "")
|
|
3262
3308
|
self.push_screen(ChoiceScreen(
|
|
3263
3309
|
f"A run is going: {run.live_text()}.\n\nAdd {what} to it? Its models run after the ones "
|
|
3264
|
-
"already in it.\nOr queue it as a separate run that starts when this one ends.",
|
|
3310
|
+
f"already in it.\nOr queue it as a separate run that starts when this one ends.{skip}",
|
|
3265
3311
|
[("add", "Add to this run", "success"), ("queue", f"Queue it as #{len(self.queue) + 1}", "primary"),
|
|
3266
3312
|
("keep", "Cancel", "default")]), picked)
|
|
3267
3313
|
return
|
|
@@ -3271,6 +3317,9 @@ class EvalsApp(App):
|
|
|
3271
3317
|
jobs, problem = item.plan(self.engine)
|
|
3272
3318
|
added, why = self.engine.add_jobs(jobs) if jobs else ([], problem)
|
|
3273
3319
|
if not added:
|
|
3320
|
+
if why and why.startswith("everything picked is already in the run"):
|
|
3321
|
+
self.notify(f"Not added: {why}.", timeout=10) # queueing it again would only duplicate it
|
|
3322
|
+
return
|
|
3274
3323
|
self.notify(f"Not added: {why}. Queued it instead.", severity="warning", timeout=10)
|
|
3275
3324
|
self.enqueue(item)
|
|
3276
3325
|
return
|
|
@@ -3282,6 +3331,8 @@ class EvalsApp(App):
|
|
|
3282
3331
|
def enqueue(self, item):
|
|
3283
3332
|
ahead = self.busy_text()
|
|
3284
3333
|
self.queue.append(item)
|
|
3334
|
+
if not self.active_session: # the last run already ended (its server may still be stopping):
|
|
3335
|
+
self.set_timer(0.3, self.start_next) # nothing else would start the queue
|
|
3285
3336
|
self.notify(f"Queued #{len(self.queue)}: {item.title}. It starts by itself after: {ahead}. "
|
|
3286
3337
|
"w shows the queue.", timeout=10)
|
|
3287
3338
|
self.refresh_live()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tuieval
|
|
3
|
-
Version: 0.2.0.
|
|
3
|
+
Version: 0.2.0.dev86
|
|
4
4
|
Summary: Evaluate local and frontier LLMs on your own questions: accuracy, speed, tokens and PASS/FAIL verdicts, in the terminal.
|
|
5
5
|
Project-URL: Homepage, https://github.com/ashe-wb/tuieval
|
|
6
6
|
Project-URL: Issues, https://github.com/ashe-wb/tuieval/issues
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
tuieval/__init__.py,sha256=myVXVX7htbwezW6k3x_FuKgn1RSZ2WcTaDEMByGn6Wg,312
|
|
2
2
|
tuieval/__main__.py,sha256=E6Gls0DNz8GQK2K-kOUIx8cYhgANW_CH54VKrfCfs14,52
|
|
3
|
-
tuieval/_version.py,sha256=
|
|
3
|
+
tuieval/_version.py,sha256=7FTn_JFfR9SMZYo_qUDsTGSERBUrJVBqTkEmPGBsf3c,535
|
|
4
4
|
tuieval/builtin.toml,sha256=Kxw22RZCJhURUyu5Wz8tj5Y1nM85A8hFVRbA4ocoPUg,4293
|
|
5
5
|
tuieval/cli.py,sha256=VwiaU1F9bZtU8C-gEowJq58kPeYQY4t6SY_HwrV-qgc,4702
|
|
6
6
|
tuieval/client.py,sha256=kpsyLEZKfxCsvIGBHBbBy83TpI97CvnGCDq-4AbJS4Y,9986
|
|
7
7
|
tuieval/compare.py,sha256=uXU_Yzf4zDSHoupHQrTmBhsfc8wCyI1nMaZQN81ybiA,30522
|
|
8
8
|
tuieval/doctor.py,sha256=j2MdDORstsAGPnRmpsnxau1_fR6sTc_oEFGM5zugTJU,7246
|
|
9
|
-
tuieval/engine.py,sha256=
|
|
9
|
+
tuieval/engine.py,sha256=yMWvYq85WmYmk8fpMLRzYcxJxo7XcjEIUTa_y2VK7Kk,124538
|
|
10
10
|
tuieval/export.py,sha256=dbHQgL_ubWtIPM1sLzAew_gETnrDJyd0zgqVA5vkpXM,9657
|
|
11
11
|
tuieval/logs.py,sha256=01gZK6tf53dIThaIrs1bEsz9eqKYVlcxpyaLnSPTVxo,8127
|
|
12
12
|
tuieval/machines.py,sha256=t9tu6CZeXZON4EtOVnEwP4VtaynpVLf9Cz8TNjf7jtY,20124
|
|
@@ -15,10 +15,10 @@ tuieval/packs.py,sha256=8bkF3xIDnWlt1JD2t3seHpMXW_RbMBgrCdIGEC-w-ZU,11458
|
|
|
15
15
|
tuieval/profiles.py,sha256=soeLxjdB-l-OxSY8jZ4yjwfdSnsBV-QICcwKxRJS7nQ,3663
|
|
16
16
|
tuieval/pta.py,sha256=i5oPj-Pz3N9u7bzzuZwsQvMGdxWdAncww2nR15K6PHY,12108
|
|
17
17
|
tuieval/remove.py,sha256=Vwh43nm9lVb9U9aN-h6CMYbkTwdR3UWfZObwweF72n0,5157
|
|
18
|
-
tuieval/run_evals.py,sha256=
|
|
18
|
+
tuieval/run_evals.py,sha256=FU3bvu3jt9ZWg3YLsXNINbdeClXMioyc4zQ4yEhmBQs,47855
|
|
19
19
|
tuieval/scaffold.py,sha256=YBOqniJpf_GtKKAFF3_OHxxMGbmTWdFjcClGcOnrrKE,12130
|
|
20
20
|
tuieval/selftest.py,sha256=nExkrOidgymlmvkxImEBfrQG5iTUN5WuRMIKtqusCCs,9835
|
|
21
|
-
tuieval/tui.py,sha256=
|
|
21
|
+
tuieval/tui.py,sha256=G6sLRXdQiOrm0sOooAn7HFbawIAUMRx4FZ28mMOvE50,175744
|
|
22
22
|
tuieval/tune.py,sha256=azxxthgJeag8JyoQUgaRxq6oI1XvEgzb9vkszjJZH7w,39645
|
|
23
23
|
tuieval/verdict.py,sha256=3SM8qrVdWNIFDENagaR5ZPBYEnjsBtkYB9WS5TDVssU,20517
|
|
24
24
|
tuieval/watch_proxy.py,sha256=bnDO1i6n6yUs1hODoUWcMppJMGPavoQqs6aNYrIxpT8,11416
|
|
@@ -47,8 +47,8 @@ tuieval/templates/packs/tool_call/pack.toml,sha256=n4bx0lGJHSmW41uVqhfIXJ2BWZpmT
|
|
|
47
47
|
tuieval/templates/packs/tool_call/system.txt,sha256=mKxu-lB-0YjLHXdgeFJa8x2C180OvcNz5cHv2N5Bg3U,254
|
|
48
48
|
tuieval/templates/packs/tool_call/tests.yaml,sha256=Nv7c4syrzJ9cU8EBKSK_1ZSFx9V02aCjW4-2TBCPT7s,1859
|
|
49
49
|
tuieval/templates/packs/tool_call/tools.yaml,sha256=HgxCkh0On2VwbHSsjAGM8tNCK7ZtKLGrs7lMCn3Fprs,1048
|
|
50
|
-
tuieval-0.2.0.
|
|
51
|
-
tuieval-0.2.0.
|
|
52
|
-
tuieval-0.2.0.
|
|
53
|
-
tuieval-0.2.0.
|
|
54
|
-
tuieval-0.2.0.
|
|
50
|
+
tuieval-0.2.0.dev86.dist-info/METADATA,sha256=Sa106UV1xU4bJvzPjWU-XCpo8ypc2Gn08bJmszFPmUc,14580
|
|
51
|
+
tuieval-0.2.0.dev86.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
52
|
+
tuieval-0.2.0.dev86.dist-info/entry_points.txt,sha256=7eIhi1GpFxBP2OLJV02Nn8LwsNuO512eKOGt1IBbj04,45
|
|
53
|
+
tuieval-0.2.0.dev86.dist-info/licenses/LICENSE,sha256=7wfRgGtBGH1aXhZkIn47oUznqKC18kEPMvUVgol7Ffc,1077
|
|
54
|
+
tuieval-0.2.0.dev86.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|