Spaces:
Sleeping
Sleeping
File size: 4,770 Bytes
5db30ac | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | """Model sizes (billions of parameters) for the dashboard's size filter.
OpenRouter lists no parameter counts. For open-weight models it gives a Hugging Face
repo id, and the Hugging Face API reports the parameter total from the safetensors
metadata. Those totals are cached in ``model_sizes.json`` next to this file so pages
never wait on Hugging Face; refresh the cache with::
python -m elbench.dashboard.model_sizes
Models with no repo id fall back to the size in their name (``qwen3-235b-a22b`` is
235B total, 22B active). Closed models have no size at all.
"""
from __future__ import annotations
import json
import os
import re
import sys
import time
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass
from pathlib import Path
import httpx
SIZES_PATH = Path(__file__).with_name("model_sizes.json")
HF_MODEL_API = "https://huggingface.co/api/models/{repo}?expand[]=safetensors"
OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models"
# "-235b" or "/8b" but not "a22b" or "8x7b"; "-a22b" is the active size of a mixture-of-experts model.
_TOTAL_IN_NAME = re.compile(r"(?:^|[-_/.])(\d+(?:\.\d+)?)b(?=$|[-_:.])", re.IGNORECASE)
_ACTIVE_IN_NAME = re.compile(r"(?:^|[-_/.])a(\d+(?:\.\d+)?)b(?=$|[-_:.])", re.IGNORECASE)
@dataclass(frozen=True)
class ModelSize:
total_b: float
active_b: float | None = None
def label(self) -> str:
text = f"{_billions(self.total_b)}B"
if self.active_b is not None and self.active_b < self.total_b:
text += f" ({_billions(self.active_b)}B active)"
return text
def _billions(value: float) -> str:
return f"{value:.0f}" if value >= 10 else f"{value:.1f}".rstrip("0").rstrip(".")
def size_from_name(model_id: str) -> ModelSize | None:
name = str(model_id or "").split("/", 1)[-1]
total = _TOTAL_IN_NAME.search(name)
if total is None:
return None
active = _ACTIVE_IN_NAME.search(name)
return ModelSize(float(total.group(1)), float(active.group(1)) if active else None)
def load_sizes(path: Path = SIZES_PATH) -> dict[str, float]:
"""Cached totals: Hugging Face repo id -> billions of parameters."""
try:
data = json.loads(path.read_text(encoding="utf-8"))
except (OSError, ValueError):
return {}
return {str(repo): float(total) for repo, total in (data.get("total_b") or {}).items()}
def model_size(model_id: str, hugging_face_id: str | None, sizes: dict[str, float]) -> ModelSize | None:
named = size_from_name(model_id)
total = sizes.get(hugging_face_id or "")
if total is not None:
return ModelSize(total, named.active_b if named else None)
return named
def fetch_total_b(repo: str, client: httpx.Client) -> float | None:
response = client.get(HF_MODEL_API.format(repo=repo))
if response.status_code != 200:
return None
total = ((response.json() or {}).get("safetensors") or {}).get("total")
return float(total) / 1e9 if isinstance(total, (int, float)) and total > 0 else None
def refresh_sizes(repos: list[str], *, token: str | None = None, path: Path = SIZES_PATH) -> dict[str, float]:
"""Fetch totals for ``repos`` not yet cached, then rewrite the cache file."""
sizes = load_sizes(path)
missing = sorted({repo for repo in repos if repo and repo not in sizes})
headers = {"Authorization": f"Bearer {token}"} if token else {}
with httpx.Client(headers=headers, timeout=20, follow_redirects=True) as client:
def fetch(repo: str) -> tuple[str, float | None]:
try:
return repo, fetch_total_b(repo, client)
except httpx.HTTPError:
return repo, None
with ThreadPoolExecutor(max_workers=8) as pool:
for repo, total in pool.map(fetch, missing):
if total is not None:
sizes[repo] = round(total, 3)
path.write_text(
json.dumps(
{
"source": "Hugging Face API safetensors.total, for OpenRouter models with a hugging_face_id",
"updated": time.strftime("%Y-%m-%d", time.gmtime()),
"total_b": dict(sorted(sizes.items())),
},
indent=2,
ensure_ascii=False,
)
+ "\n",
encoding="utf-8",
)
return sizes
def main() -> int:
catalog = httpx.get(OPENROUTER_MODELS_URL, timeout=45).json()
repos = [str(item.get("hugging_face_id") or "") for item in catalog.get("data") or []]
token = os.environ.get("HF_TOKEN") or os.environ.get("HF_API_KEY")
sizes = refresh_sizes([repo for repo in repos if repo], token=token)
print(f"{len(sizes)} model sizes cached in {SIZES_PATH}")
return 0
if __name__ == "__main__":
sys.exit(main())
|