"""Ranked play: the arena picks the models at random, runs the match on the server and records Elo.
Players can't choose who plays ranked, so nobody can farm Elo by pairing a model with weak opponents.
A ranked match runs in a background thread: it finishes and counts even if every viewer leaves, so a
match can't be aborted when it's going badly. Only one ranked match runs at a time; others watch it.
"""
from __future__ import annotations
import html
import random
import threading
import time
import uuid
from arena import MAX_PIECES, choose, rank_games
from players import MAX_PARAMS, MIN_PARAMS, ModelRejected, load_player, precheck
from render import arena_html, empty_html, results_html
from tetris import TetrisGame
PIECE_DELAY = 0.12 # seconds per piece, so viewers can follow the match
LOAD_ATTEMPTS = 4
# Exact parameter counts (tied weights counted once) of the pool models, so the random pick
# can respect the size gap without downloading anything. Models missing here are estimated
# from their Hub metadata at startup, and every count is corrected after the model loads.
KNOWN_PARAMS = {
'DedeProGames/Overaddicted-500K': 492_192,
'fromziro/Er-Tiny-1.3M': 1_332_744,
'AxiomicLabs/GPT-S-1.4M': 1_426_176,
'BananaMind/BananaMind-2.1-Pico-Preview': 1_480_516,
'SupraLabs/SupraNeo-4M': 4_070_240,
'SLM-Archive/LowOnMind-5M': 4_920_384,
'veyra-ai/Veyra2-Blueberry-5M-Base': 4_984_192,
'DedeProGames/Wisp-5M': 5_115_456,
'AxiomicLabs/GPT-S2-5M': 5_384_258,
'Novi-AI/Novi-Micro-Base': 5_656_240,
'DedeProGames/DynamicMind-Mini': 8_884_992,
'BananaMind/BananaMind-2-Nano': 9_968_128,
'DedeBckp/BackKiyo-10M': 9_976_832,
'fromziro/Er-Medium-12.5M': 12_497_520,
'DedeProGames/Wisp-15M': 15_531_840,
'DedeProGames/GPT-U-20M': 20_453_760,
'SupraLabs/Supra2-Medium-Base': 25_371_008,
'DedeProGames/DynamicMind-MoE': 30_150_912,
'veyra-ai/Veyra2-Mango-30M-Base': 30_683_520,
'fromziro/Er-Large-30M': 31_944_632,
'GODELEV/Rose-Mini': 49_443_074,
'veyra-ai/Veyra2-Apricot-50M-Base': 49_303_040,
'BananaMind/BananaMind-2-Medium': 49_559_552,
'DedeProGames/Kiyo-65M': 64_994_816,
'opencerebral/Boris-1.3-75M': 77_431_680,
'GODELEV/Rose-Medium': 97_820_162,
'SupraLabs/Supra2-100M-Base': 100_684_032,
'openai-community/gpt2': 124_439_808,
'HuggingFaceTB/SmolLM-135M': 134_515_008,
'HuggingFaceTB/SmolLM2-135M': 134_515_008,
'AxiomicLabs/GPT-X2.5-135M': 135_032_256,
'BananaMind/BananaMind-2-Pro': 138_971_520,
'GODELEV/Rose-Pro': 151_274_114,
'DedeProGames/Kiyo-230M-Preview': 229_688_064,
}
class Pool:
"""Models eligible for ranked play, with their parameter counts."""
def __init__(self, model_ids, gap: int, max_players: int, large_from: int | None = None):
self.ids = list(dict.fromkeys(model_ids))
self.gap = gap
self.max_players = max_players
self.large_from = large_from # models this size or bigger can all play each other
self.lock = threading.Lock()
self.sizes = {m: KNOWN_PARAMS[m] for m in self.ids if m in KNOWN_PARAMS}
self.broken: dict[str, str] = {} # models that failed this session -> reason
missing = [m for m in self.ids if m not in self.sizes]
if missing:
threading.Thread(target=self._estimate, args=(missing,), daemon=True).start()
def _estimate(self, ids):
for m in ids:
try:
est = precheck(m)["est_params"]
except Exception as e:
self.mark_broken(m, str(e))
continue
with self.lock:
self.sizes.setdefault(m, est)
def set_exact(self, model_id, n_params):
with self.lock:
self.sizes[model_id] = n_params
def mark_broken(self, model_id, reason):
with self.lock:
self.broken[model_id] = reason[:300]
def eligible(self) -> dict:
with self.lock:
return {m: p for m, p in self.sizes.items()
if m not in self.broken and MIN_PARAMS <= p <= MAX_PARAMS}
def fits(self, sizes) -> bool:
"""A ranked group is fair when all sizes fit within `gap`, or when every model is large.
With no gap (0/None) any models can meet: Elo already weighs each win by the opponent's rating."""
if not self.gap:
return True
if self.large_from is not None and min(sizes) >= self.large_from:
return True
return max(sizes) - min(sizes) <= self.gap
def pick(self, games_played: dict, rng) -> list:
"""Random group of up to `max_players` models that `fits` (any sizes when there is no gap; otherwise all sizes
in one `gap`-wide window, or, for a large anchor, any mix of large models).
Models with fewer ranked games are more likely to be drawn, so every model gets played."""
sizes = self.eligible()
ids = sorted(sizes)
anchors = [m for m in ids if any(o != m and self.fits([sizes[o], sizes[m]]) for o in ids)]
if not anchors:
return []
a = rng.choices(anchors, [1.0 / (1 + games_played.get(m, 0)) for m in anchors])[0]
if not self.gap:
windows = [[m for m in ids if m != a]] # no size limit: anyone can be drawn
else:
windows = self._windows(a, sizes, ids)
others = list(rng.choice(windows))
group = [a]
while others and len(group) < self.max_players: # weighted draw without replacement
o = rng.choices(others, [1.0 / (1 + games_played.get(m, 0)) for m in others])[0]
others.remove(o)
group.append(o)
return group
def _windows(self, a, sizes, ids):
"""Candidate opponent sets for anchor `a`: every `gap`-wide size window containing it (+ all large models)."""
windows = []
for s in sorted({sizes[m] for m in ids if sizes[a] - self.gap <= sizes[m] <= sizes[a]}):
members = [m for m in ids if m != a and s <= sizes[m] <= s + self.gap]
if members:
windows.append(members)
if self.large_from is not None and sizes[a] >= self.large_from:
members = [m for m in ids if m != a and sizes[m] >= self.large_from]
if members:
windows.append(members)
return windows
class RankedMatch:
"""State of one ranked match, shared by the worker thread and every viewer."""
def __init__(self, protocol: str):
self.id = uuid.uuid4().hex[:8]
self.protocol = protocol
self.lock = threading.Lock()
self.version = 0
self.status = f"⏳ Ranked · {protocol} · picking models…"
self.boards = empty_html("Picking models at random…")
self.results = ""
self.done = False
def update(self, status=None, boards=None, results=None, done=None):
with self.lock:
if status is not None:
self.status = status
if boards is not None:
self.boards = boards
if results is not None:
self.results = results
if done is not None:
self.done = done
self.version += 1
def snapshot(self):
with self.lock:
return self.version, self.status, self.boards, self.results, self.done
class RankedRunner:
def __init__(self, pool: Pool, store):
self.pool = pool
self.store = store
self.lock = threading.Lock()
self.current: RankedMatch | None = None
def start_or_join(self, protocol: str):
"""Returns (match, started). Joins the running match instead of starting a second one."""
with self.lock:
if self.current is not None and not self.current.done:
return self.current, False
match = RankedMatch(protocol)
self.current = match
threading.Thread(target=self._run, args=(match,), daemon=True, name=f"ranked-{match.id}").start()
return match, True
def _run(self, m: RankedMatch):
try:
self._play(m)
except Exception as e:
m.update(status=f"⛔ Ranked match cancelled: {str(e)[:300]} Nothing was recorded.", done=True)
def _load_group(self, m: RankedMatch, rng):
played = {e["model"]: e["games"] for e in self.store.rows(m.protocol)}
skipped = []
for _ in range(LOAD_ATTEMPTS):
ids = self.pool.pick(played, rng)
if len(ids) < 2:
raise RuntimeError("the pool has no two models of similar size.")
players = []
for i, model_id in enumerate(ids, 1):
m.update(status=f"⏳ Ranked · {m.protocol} · loading `{model_id}` ({i}/{len(ids)})… first load downloads the weights.",
boards=empty_html(f"Picked at random: {html.escape(', '.join(ids))}
Loading {i}/{len(ids)}…"))
try:
meta = precheck(model_id)
player = load_player(meta["id"], meta)
except ModelRejected as e:
self.pool.mark_broken(model_id, str(e))
skipped.append(model_id)
continue
self.pool.set_exact(model_id, player.n_params)
players.append(player)
if len(players) >= 2 and self.pool.fits([p.n_params for p in players]):
return players, skipped
raise RuntimeError("could not load two models of similar size.")
def _play(self, m: RankedMatch):
rng = random.SystemRandom()
players, skipped = self._load_group(m, rng)
seed = rng.randrange(1, 10**9)
games = [TetrisGame(seed) for _ in players]
head = f"Ranked · seed {seed} · {m.protocol}"
m.update(status=f"⏳ {head} · scoring the first moves…", boards=arena_html(games, players))
last = time.time()
while True:
active = [(g, p) for g, p in zip(games, players) if g.alive and g.pieces < MAX_PIECES]
if not active:
break
for g, p in active:
try:
choose(g, p, m.protocol, seed)
except Exception as e:
self.pool.mark_broken(p.model_id, str(e))
raise RuntimeError(f"`{p.model_id}` crashed during play ({type(e).__name__}: {str(e)[:150]}).")
elapsed = time.time() - last
if elapsed < PIECE_DELAY:
time.sleep(PIECE_DELAY - elapsed)
last = time.time()
n = max(g.pieces for g in games)
alive = sum(g.alive for g in games)
m.update(status=f"⏳ {head} · piece {n}/{MAX_PIECES} · {alive} still playing", boards=arena_html(games, players))
ranks = rank_games(games)
order = sorted(range(len(players)), key=lambda i: ranks[i])
record = self.store.record(m.protocol, seed, players, games)
elos = [(p["elo_before"], p["elo_after"]) for p in record["players"]]
note = self._note()
if skipped:
note += f" Skipped (failed to load): {html.escape(', '.join(skipped))}."
m.update(status=f"✅ Ranked match finished · seed {seed} · {m.protocol}.",
boards=arena_html(games, players, ranks, elos),
results=results_html(order, ranks, players, games, elos, note), done=True)
def _note(self):
s = self.store
if s.persistent and not s.save_error:
note = (f'Elo updated and saved to the public leaderboard '
f'({s.repo_id}).')
elif s.persistent:
note = f"⚠️ Elo updated in memory, but {html.escape(s.save_error)}."
else:
note = "⚠️ Elo updated in memory only: the Space has no HF_TOKEN secret, so results are not saved."
return note + f" Ranking: score, then lines, then pieces survived. ✓ = still alive at the {MAX_PIECES}-piece cap."