Spaces:
Running on Zero
Running on Zero
Commit ·
a425bcf
0
Parent(s):
Duplicate from gradio-templates/leaderboard
Browse filesCo-authored-by: Abubakar Abid <abidlabs@users.noreply.huggingface.co>
- .gitattributes +35 -0
- .gitignore +13 -0
- .pre-commit-config.yaml +53 -0
- Makefile +13 -0
- README.md +51 -0
- app.py +187 -0
- pyproject.toml +14 -0
- requirements.txt +9 -0
- src/about.py +68 -0
- src/display/formatting.py +15 -0
- src/display/utils.py +114 -0
- src/envs.py +24 -0
- src/leaderboard/read_evals.py +195 -0
- src/populate.py +62 -0
- src/submission/check_validity.py +107 -0
- src/submission/submit.py +127 -0
- static/app.js +199 -0
- static/index.html +92 -0
- static/styles.css +143 -0
.gitattributes
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
scale-hf-logo.png filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
auto_evals/
|
| 2 |
+
venv/
|
| 3 |
+
__pycache__/
|
| 4 |
+
.env
|
| 5 |
+
.ipynb_checkpoints
|
| 6 |
+
*ipynb
|
| 7 |
+
.vscode/
|
| 8 |
+
|
| 9 |
+
eval-queue/
|
| 10 |
+
eval-results/
|
| 11 |
+
eval-queue-bk/
|
| 12 |
+
eval-results-bk/
|
| 13 |
+
logs/
|
.pre-commit-config.yaml
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
default_language_version:
|
| 16 |
+
python: python3
|
| 17 |
+
|
| 18 |
+
ci:
|
| 19 |
+
autofix_prs: true
|
| 20 |
+
autoupdate_commit_msg: '[pre-commit.ci] pre-commit suggestions'
|
| 21 |
+
autoupdate_schedule: quarterly
|
| 22 |
+
|
| 23 |
+
repos:
|
| 24 |
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
| 25 |
+
rev: v4.3.0
|
| 26 |
+
hooks:
|
| 27 |
+
- id: check-yaml
|
| 28 |
+
- id: check-case-conflict
|
| 29 |
+
- id: detect-private-key
|
| 30 |
+
- id: check-added-large-files
|
| 31 |
+
args: ['--maxkb=1000']
|
| 32 |
+
- id: requirements-txt-fixer
|
| 33 |
+
- id: end-of-file-fixer
|
| 34 |
+
- id: trailing-whitespace
|
| 35 |
+
|
| 36 |
+
- repo: https://github.com/PyCQA/isort
|
| 37 |
+
rev: 5.12.0
|
| 38 |
+
hooks:
|
| 39 |
+
- id: isort
|
| 40 |
+
name: Format imports
|
| 41 |
+
|
| 42 |
+
- repo: https://github.com/psf/black
|
| 43 |
+
rev: 22.12.0
|
| 44 |
+
hooks:
|
| 45 |
+
- id: black
|
| 46 |
+
name: Format code
|
| 47 |
+
additional_dependencies: ['click==8.0.2']
|
| 48 |
+
|
| 49 |
+
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
| 50 |
+
# Ruff version.
|
| 51 |
+
rev: 'v0.0.267'
|
| 52 |
+
hooks:
|
| 53 |
+
- id: ruff
|
Makefile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.PHONY: style format
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
style:
|
| 5 |
+
python -m black --line-length 119 .
|
| 6 |
+
python -m isort .
|
| 7 |
+
ruff check --fix .
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
quality:
|
| 11 |
+
python -m black --check --line-length 119 .
|
| 12 |
+
python -m isort --check-only .
|
| 13 |
+
ruff check .
|
README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Example Leaderboard Template
|
| 3 |
+
emoji: 🥇
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: gradio
|
| 7 |
+
app_file: app.py
|
| 8 |
+
pinned: true
|
| 9 |
+
license: apache-2.0
|
| 10 |
+
short_description: Duplicate this leaderboard to initialize your own!
|
| 11 |
+
sdk_version: 6.26.0
|
| 12 |
+
tags:
|
| 13 |
+
- leaderboard
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
# Start the configuration
|
| 17 |
+
|
| 18 |
+
This template uses Gradio 6 Server mode: a custom responsive frontend backed by named Gradio API endpoints. Duplicate the Space, then customize the copy and benchmark definitions below.
|
| 19 |
+
|
| 20 |
+
Most of the variables to change for a default leaderboard are in `src/envs.py` (replace the paths for your leaderboard) and `src/about.py` (for its title, copy, and tasks).
|
| 21 |
+
|
| 22 |
+
Results files should have the following format and be stored as json files:
|
| 23 |
+
```json
|
| 24 |
+
{
|
| 25 |
+
"config": {
|
| 26 |
+
"model_dtype": "torch.float16", # or torch.bfloat16 or 8bit or 4bit
|
| 27 |
+
"model_name": "path of the model on the hub: org/model",
|
| 28 |
+
"model_sha": "revision on the hub",
|
| 29 |
+
},
|
| 30 |
+
"results": {
|
| 31 |
+
"task_name": {
|
| 32 |
+
"metric_name": score,
|
| 33 |
+
},
|
| 34 |
+
"task_name2": {
|
| 35 |
+
"metric_name": score,
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
Request files are created automatically by this tool.
|
| 42 |
+
|
| 43 |
+
If you encounter problem on the space, don't hesitate to restart it to remove the create eval-queue, eval-queue-bk, eval-results and eval-results-bk created folder.
|
| 44 |
+
|
| 45 |
+
# Code logic for more complex edits
|
| 46 |
+
|
| 47 |
+
You'll find
|
| 48 |
+
- the main table' columns names and properties in `src/display/utils.py`
|
| 49 |
+
- the logic to read all results and request files, then convert them in dataframe lines, in `src/leaderboard/read_evals.py`, and `src/populate.py`
|
| 50 |
+
- the logic to allow or filter submissions in `src/submission/submit.py` and `src/submission/check_validity.py`
|
| 51 |
+
- the custom frontend in `static/index.html`, `static/styles.css`, and `static/app.js`
|
app.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import html
|
| 4 |
+
import logging
|
| 5 |
+
import re
|
| 6 |
+
from datetime import datetime, timezone
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from threading import RLock
|
| 9 |
+
from typing import Any
|
| 10 |
+
|
| 11 |
+
import gradio as gr
|
| 12 |
+
import pandas as pd
|
| 13 |
+
from apscheduler.schedulers.background import BackgroundScheduler
|
| 14 |
+
from fastapi.responses import HTMLResponse
|
| 15 |
+
from fastapi.staticfiles import StaticFiles
|
| 16 |
+
from huggingface_hub import snapshot_download
|
| 17 |
+
|
| 18 |
+
from src.about import (
|
| 19 |
+
CITATION_BUTTON_LABEL,
|
| 20 |
+
CITATION_BUTTON_TEXT,
|
| 21 |
+
EVALUATION_QUEUE_TEXT,
|
| 22 |
+
LLM_BENCHMARKS_TEXT,
|
| 23 |
+
TITLE,
|
| 24 |
+
Tasks,
|
| 25 |
+
)
|
| 26 |
+
from src.display.utils import (
|
| 27 |
+
BENCHMARK_COLS,
|
| 28 |
+
COLS,
|
| 29 |
+
EVAL_COLS,
|
| 30 |
+
ModelType,
|
| 31 |
+
Precision,
|
| 32 |
+
WeightType,
|
| 33 |
+
)
|
| 34 |
+
from src.envs import EVAL_REQUESTS_PATH, EVAL_RESULTS_PATH, QUEUE_REPO, RESULTS_REPO, TOKEN
|
| 35 |
+
from src.populate import get_evaluation_queue_df, get_leaderboard_df
|
| 36 |
+
from src.submission.submit import add_new_eval
|
| 37 |
+
|
| 38 |
+
logging.basicConfig(level=logging.INFO)
|
| 39 |
+
LOGGER = logging.getLogger("leaderboard")
|
| 40 |
+
|
| 41 |
+
ROOT = Path(__file__).parent
|
| 42 |
+
STATIC_DIR = ROOT / "static"
|
| 43 |
+
MODEL_LINK_RE = re.compile(r'href=["\']https://huggingface\.co/([^"\']+)["\']')
|
| 44 |
+
DATA_LOCK = RLock()
|
| 45 |
+
DATA: dict[str, Any] = {}
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _download_dataset(repo_id: str, local_dir: str) -> None:
|
| 49 |
+
Path(local_dir).mkdir(parents=True, exist_ok=True)
|
| 50 |
+
snapshot_download(
|
| 51 |
+
repo_id=repo_id,
|
| 52 |
+
local_dir=local_dir,
|
| 53 |
+
repo_type="dataset",
|
| 54 |
+
tqdm_class=None,
|
| 55 |
+
etag_timeout=30,
|
| 56 |
+
token=TOKEN,
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def _json_value(value: Any) -> Any:
|
| 61 |
+
if pd.isna(value):
|
| 62 |
+
return None
|
| 63 |
+
if hasattr(value, "item"):
|
| 64 |
+
value = value.item()
|
| 65 |
+
return value
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def _model_id(value: Any) -> str:
|
| 69 |
+
text = str(value or "")
|
| 70 |
+
if match := MODEL_LINK_RE.search(text):
|
| 71 |
+
return html.unescape(match.group(1))
|
| 72 |
+
return html.unescape(re.sub("<[^>]+>", "", text))
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def _records(frame: pd.DataFrame) -> list[dict[str, Any]]:
|
| 76 |
+
records = []
|
| 77 |
+
for row in frame.to_dict(orient="records"):
|
| 78 |
+
clean_row = {str(key): _json_value(value) for key, value in row.items()}
|
| 79 |
+
if "Model" in clean_row:
|
| 80 |
+
clean_row["Model"] = _model_id(clean_row["Model"])
|
| 81 |
+
if "model" in clean_row:
|
| 82 |
+
clean_row["model"] = _model_id(clean_row["model"])
|
| 83 |
+
records.append(clean_row)
|
| 84 |
+
return records
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def _empty_payload(message: str | None = None) -> dict[str, Any]:
|
| 88 |
+
return {
|
| 89 |
+
"rows": [],
|
| 90 |
+
"queues": {"finished": [], "running": [], "pending": []},
|
| 91 |
+
"benchmarks": [task.value.col_name for task in Tasks],
|
| 92 |
+
"model_types": [item.to_str(" · ") for item in ModelType if item is not ModelType.Unknown],
|
| 93 |
+
"precisions": [item.value.name for item in Precision if item is not Precision.Unknown],
|
| 94 |
+
"weight_types": [item.value.name for item in WeightType],
|
| 95 |
+
"copy": {
|
| 96 |
+
"title": TITLE,
|
| 97 |
+
"about": LLM_BENCHMARKS_TEXT.strip(),
|
| 98 |
+
"submission": EVALUATION_QUEUE_TEXT.strip(),
|
| 99 |
+
"citation_label": CITATION_BUTTON_LABEL,
|
| 100 |
+
"citation": CITATION_BUTTON_TEXT.strip(),
|
| 101 |
+
},
|
| 102 |
+
"updated_at": datetime.now(timezone.utc).isoformat(),
|
| 103 |
+
"error": message,
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def refresh_data(*, download: bool = True) -> dict[str, Any]:
|
| 108 |
+
"""Refresh leaderboard and queue data without taking the Space offline."""
|
| 109 |
+
errors = []
|
| 110 |
+
if download:
|
| 111 |
+
for repo_id, local_dir in (
|
| 112 |
+
(QUEUE_REPO, EVAL_REQUESTS_PATH),
|
| 113 |
+
(RESULTS_REPO, EVAL_RESULTS_PATH),
|
| 114 |
+
):
|
| 115 |
+
try:
|
| 116 |
+
_download_dataset(repo_id, local_dir)
|
| 117 |
+
except Exception as exc: # Keep serving the last successful snapshot.
|
| 118 |
+
LOGGER.exception("Could not refresh %s", repo_id)
|
| 119 |
+
errors.append(f"Could not refresh {repo_id}: {exc}")
|
| 120 |
+
|
| 121 |
+
payload = _empty_payload("; ".join(errors) or None)
|
| 122 |
+
try:
|
| 123 |
+
leaderboard = get_leaderboard_df(EVAL_RESULTS_PATH, EVAL_REQUESTS_PATH, COLS, BENCHMARK_COLS)
|
| 124 |
+
payload["rows"] = _records(leaderboard)
|
| 125 |
+
except Exception as exc:
|
| 126 |
+
LOGGER.exception("Could not build leaderboard data")
|
| 127 |
+
payload["error"] = f"Could not build leaderboard data: {exc}"
|
| 128 |
+
|
| 129 |
+
try:
|
| 130 |
+
finished, running, pending = get_evaluation_queue_df(EVAL_REQUESTS_PATH, EVAL_COLS)
|
| 131 |
+
payload["queues"] = {
|
| 132 |
+
"finished": _records(finished),
|
| 133 |
+
"running": _records(running),
|
| 134 |
+
"pending": _records(pending),
|
| 135 |
+
}
|
| 136 |
+
except Exception as exc:
|
| 137 |
+
LOGGER.exception("Could not build evaluation queues")
|
| 138 |
+
payload["error"] = f"Could not build evaluation queues: {exc}"
|
| 139 |
+
|
| 140 |
+
with DATA_LOCK:
|
| 141 |
+
DATA.clear()
|
| 142 |
+
DATA.update(payload)
|
| 143 |
+
return dict(DATA)
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
server = gr.Server(
|
| 147 |
+
title="Open LLM Benchmark",
|
| 148 |
+
description="A clean, customizable Gradio 6 leaderboard template.",
|
| 149 |
+
version="2.0.0",
|
| 150 |
+
)
|
| 151 |
+
server.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
@server.get("/", response_class=HTMLResponse, include_in_schema=False)
|
| 155 |
+
async def homepage() -> HTMLResponse:
|
| 156 |
+
return HTMLResponse((STATIC_DIR / "index.html").read_text())
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
@server.api(name="leaderboard_data", queue=False, concurrency_limit=None)
|
| 160 |
+
def leaderboard_data() -> dict[str, Any]:
|
| 161 |
+
"""Return the current leaderboard, evaluation queues, filters, and template copy."""
|
| 162 |
+
with DATA_LOCK:
|
| 163 |
+
return dict(DATA) if DATA else _empty_payload("Data is still loading.")
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
@server.api(name="submit_model", concurrency_limit=4)
|
| 167 |
+
def submit_model(
|
| 168 |
+
model: str,
|
| 169 |
+
base_model: str,
|
| 170 |
+
revision: str,
|
| 171 |
+
precision: str,
|
| 172 |
+
weight_type: str,
|
| 173 |
+
model_type: str,
|
| 174 |
+
) -> dict[str, str]:
|
| 175 |
+
"""Validate a public Hugging Face model and add it to the evaluation queue."""
|
| 176 |
+
result = add_new_eval(model, base_model, revision, precision, weight_type, model_type)
|
| 177 |
+
if result["status"] == "success":
|
| 178 |
+
refresh_data(download=False)
|
| 179 |
+
return result
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
if __name__ == "__main__":
|
| 183 |
+
refresh_data()
|
| 184 |
+
scheduler = BackgroundScheduler(daemon=True)
|
| 185 |
+
scheduler.add_job(refresh_data, "interval", minutes=10, max_instances=1, coalesce=True)
|
| 186 |
+
scheduler.start()
|
| 187 |
+
server.launch()
|
pyproject.toml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[tool.ruff]
|
| 2 |
+
line-length = 119
|
| 3 |
+
|
| 4 |
+
[tool.ruff.lint]
|
| 5 |
+
select = ["E", "F"]
|
| 6 |
+
ignore = ["E501"] # line too long (black is taking care of this)
|
| 7 |
+
fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"]
|
| 8 |
+
|
| 9 |
+
[tool.isort]
|
| 10 |
+
profile = "black"
|
| 11 |
+
line_length = 119
|
| 12 |
+
|
| 13 |
+
[tool.black]
|
| 14 |
+
line-length = 119
|
requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
APScheduler
|
| 2 |
+
gradio==6.26.0
|
| 3 |
+
huggingface-hub>=0.18.0
|
| 4 |
+
numpy
|
| 5 |
+
pandas
|
| 6 |
+
python-dateutil
|
| 7 |
+
sentencepiece
|
| 8 |
+
tokenizers>=0.15.0
|
| 9 |
+
transformers
|
src/about.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dataclasses import dataclass
|
| 2 |
+
from enum import Enum
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
@dataclass
|
| 6 |
+
class Task:
|
| 7 |
+
benchmark: str
|
| 8 |
+
metric: str
|
| 9 |
+
col_name: str
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
# Select your tasks here
|
| 13 |
+
# ---------------------------------------------------
|
| 14 |
+
class Tasks(Enum):
|
| 15 |
+
# task_key in the json file, metric_key in the json file, name to display in the leaderboard
|
| 16 |
+
task0 = Task("anli_r1", "acc", "ANLI")
|
| 17 |
+
task1 = Task("logiqa", "acc_norm", "LogiQA")
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
NUM_FEWSHOT = 0 # Change with your few shot
|
| 21 |
+
# ---------------------------------------------------
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
# Your leaderboard name
|
| 25 |
+
TITLE = "Open Leaderboard"
|
| 26 |
+
|
| 27 |
+
# Which evaluations are you running? how can people reproduce what you have?
|
| 28 |
+
LLM_BENCHMARKS_TEXT = """
|
| 29 |
+
## How it works
|
| 30 |
+
|
| 31 |
+
## Reproducibility
|
| 32 |
+
To reproduce our results, here is the commands you can run:
|
| 33 |
+
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
EVALUATION_QUEUE_TEXT = """
|
| 37 |
+
## Some good practices before submitting a model
|
| 38 |
+
|
| 39 |
+
### 1) Make sure you can load your model and tokenizer using AutoClasses:
|
| 40 |
+
```python
|
| 41 |
+
from transformers import AutoConfig, AutoModel, AutoTokenizer
|
| 42 |
+
config = AutoConfig.from_pretrained("your model name", revision=revision)
|
| 43 |
+
model = AutoModel.from_pretrained("your model name", revision=revision)
|
| 44 |
+
tokenizer = AutoTokenizer.from_pretrained("your model name", revision=revision)
|
| 45 |
+
```
|
| 46 |
+
If this step fails, follow the error messages to debug your model before submitting it. It's likely your model has been improperly uploaded.
|
| 47 |
+
|
| 48 |
+
Note: make sure your model is public!
|
| 49 |
+
Note: if your model needs `use_remote_code=True`, we do not support this option yet but we are working on adding it, stay posted!
|
| 50 |
+
|
| 51 |
+
### 2) Convert your model weights to [safetensors](https://huggingface.co/docs/safetensors/index)
|
| 52 |
+
It's a new format for storing weights which is safer and faster to load and use. It will also allow us to add the number of parameters of your model to the `Extended Viewer`!
|
| 53 |
+
|
| 54 |
+
### 3) Make sure your model has an open license!
|
| 55 |
+
This is a leaderboard for Open LLMs, and we'd love for as many people as possible to know they can use your model 🤗
|
| 56 |
+
|
| 57 |
+
### 4) Fill up your model card
|
| 58 |
+
When we add extra information about models to the leaderboard, it will be automatically taken from the model card
|
| 59 |
+
|
| 60 |
+
## In case of model failure
|
| 61 |
+
If your model is displayed in the `FAILED` category, its execution stopped.
|
| 62 |
+
Make sure you have followed the above steps first.
|
| 63 |
+
If everything is done, check you can launch the EleutherAIHarness on your model locally, using the above command without modifications (you can add `--limit` to limit the number of examples per task).
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
CITATION_BUTTON_LABEL = "Copy the following snippet to cite these results"
|
| 67 |
+
CITATION_BUTTON_TEXT = r"""
|
| 68 |
+
"""
|
src/display/formatting.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def model_hyperlink(link, model_name):
|
| 2 |
+
return f'<a target="_blank" href="{link}" style="color: var(--link-text-color); text-decoration: underline;text-decoration-style: dotted;">{model_name}</a>'
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def make_clickable_model(model_name):
|
| 6 |
+
link = f"https://huggingface.co/{model_name}"
|
| 7 |
+
return model_hyperlink(link, model_name)
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def has_no_nan_values(df, columns):
|
| 11 |
+
return df[columns].notna().all(axis=1)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def has_nan_values(df, columns):
|
| 15 |
+
return df[columns].isna().any(axis=1)
|
src/display/utils.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dataclasses import dataclass, make_dataclass
|
| 2 |
+
from enum import Enum
|
| 3 |
+
|
| 4 |
+
from src.about import Tasks
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def fields(raw_class):
|
| 8 |
+
return [v for k, v in raw_class.__dict__.items() if k[:2] != "__" and k[-2:] != "__"]
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
# These classes are for user facing column names,
|
| 12 |
+
# to avoid having to change them all around the code
|
| 13 |
+
# when a modif is needed
|
| 14 |
+
@dataclass(frozen=True)
|
| 15 |
+
class ColumnContent:
|
| 16 |
+
name: str
|
| 17 |
+
type: str
|
| 18 |
+
displayed_by_default: bool
|
| 19 |
+
hidden: bool = False
|
| 20 |
+
never_hidden: bool = False
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
## Leaderboard columns
|
| 24 |
+
auto_eval_column_dict = []
|
| 25 |
+
# Init
|
| 26 |
+
auto_eval_column_dict.append(["model_type_symbol", ColumnContent, ColumnContent("T", "str", True, never_hidden=True)])
|
| 27 |
+
auto_eval_column_dict.append(["model", ColumnContent, ColumnContent("Model", "markdown", True, never_hidden=True)])
|
| 28 |
+
# Scores
|
| 29 |
+
auto_eval_column_dict.append(["average", ColumnContent, ColumnContent("Average ⬆️", "number", True)])
|
| 30 |
+
for task in Tasks:
|
| 31 |
+
auto_eval_column_dict.append([task.name, ColumnContent, ColumnContent(task.value.col_name, "number", True)])
|
| 32 |
+
# Model information
|
| 33 |
+
auto_eval_column_dict.append(["model_type", ColumnContent, ColumnContent("Type", "str", False)])
|
| 34 |
+
auto_eval_column_dict.append(["architecture", ColumnContent, ColumnContent("Architecture", "str", False)])
|
| 35 |
+
auto_eval_column_dict.append(["weight_type", ColumnContent, ColumnContent("Weight type", "str", False, True)])
|
| 36 |
+
auto_eval_column_dict.append(["precision", ColumnContent, ColumnContent("Precision", "str", False)])
|
| 37 |
+
auto_eval_column_dict.append(["license", ColumnContent, ColumnContent("Hub License", "str", False)])
|
| 38 |
+
auto_eval_column_dict.append(["params", ColumnContent, ColumnContent("#Params (B)", "number", False)])
|
| 39 |
+
auto_eval_column_dict.append(["likes", ColumnContent, ColumnContent("Hub ❤️", "number", False)])
|
| 40 |
+
auto_eval_column_dict.append(["still_on_hub", ColumnContent, ColumnContent("Available on the hub", "bool", False)])
|
| 41 |
+
auto_eval_column_dict.append(["revision", ColumnContent, ColumnContent("Model sha", "str", False, False)])
|
| 42 |
+
|
| 43 |
+
# We use make dataclass to dynamically fill the scores from Tasks
|
| 44 |
+
AutoEvalColumn = make_dataclass("AutoEvalColumn", auto_eval_column_dict, frozen=True)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
## For the queue columns in the submission tab
|
| 48 |
+
@dataclass(frozen=True)
|
| 49 |
+
class EvalQueueColumn: # Queue column
|
| 50 |
+
model = ColumnContent("model", "markdown", True)
|
| 51 |
+
revision = ColumnContent("revision", "str", True)
|
| 52 |
+
private = ColumnContent("private", "bool", True)
|
| 53 |
+
precision = ColumnContent("precision", "str", True)
|
| 54 |
+
weight_type = ColumnContent("weight_type", "str", "Original")
|
| 55 |
+
status = ColumnContent("status", "str", True)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
## All the model information that we might need
|
| 59 |
+
@dataclass
|
| 60 |
+
class ModelDetails:
|
| 61 |
+
name: str
|
| 62 |
+
display_name: str = ""
|
| 63 |
+
symbol: str = "" # emoji
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class ModelType(Enum):
|
| 67 |
+
PT = ModelDetails(name="pretrained", symbol="🟢")
|
| 68 |
+
FT = ModelDetails(name="fine-tuned", symbol="🔶")
|
| 69 |
+
IFT = ModelDetails(name="instruction-tuned", symbol="⭕")
|
| 70 |
+
RL = ModelDetails(name="RL-tuned", symbol="🟦")
|
| 71 |
+
Unknown = ModelDetails(name="", symbol="?")
|
| 72 |
+
|
| 73 |
+
def to_str(self, separator=" "):
|
| 74 |
+
return f"{self.value.symbol}{separator}{self.value.name}"
|
| 75 |
+
|
| 76 |
+
@staticmethod
|
| 77 |
+
def from_str(type):
|
| 78 |
+
if "fine-tuned" in type or "🔶" in type:
|
| 79 |
+
return ModelType.FT
|
| 80 |
+
if "pretrained" in type or "🟢" in type:
|
| 81 |
+
return ModelType.PT
|
| 82 |
+
if "RL-tuned" in type or "🟦" in type:
|
| 83 |
+
return ModelType.RL
|
| 84 |
+
if "instruction-tuned" in type or "⭕" in type:
|
| 85 |
+
return ModelType.IFT
|
| 86 |
+
return ModelType.Unknown
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
class WeightType(Enum):
|
| 90 |
+
Adapter = ModelDetails("Adapter")
|
| 91 |
+
Original = ModelDetails("Original")
|
| 92 |
+
Delta = ModelDetails("Delta")
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
class Precision(Enum):
|
| 96 |
+
float16 = ModelDetails("float16")
|
| 97 |
+
bfloat16 = ModelDetails("bfloat16")
|
| 98 |
+
Unknown = ModelDetails("?")
|
| 99 |
+
|
| 100 |
+
def from_str(precision):
|
| 101 |
+
if precision in ["torch.float16", "float16"]:
|
| 102 |
+
return Precision.float16
|
| 103 |
+
if precision in ["torch.bfloat16", "bfloat16"]:
|
| 104 |
+
return Precision.bfloat16
|
| 105 |
+
return Precision.Unknown
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
# Column selection
|
| 109 |
+
COLS = [c.name for c in fields(AutoEvalColumn) if not c.hidden]
|
| 110 |
+
|
| 111 |
+
EVAL_COLS = [c.name for c in fields(EvalQueueColumn)]
|
| 112 |
+
EVAL_TYPES = [c.type for c in fields(EvalQueueColumn)]
|
| 113 |
+
|
| 114 |
+
BENCHMARK_COLS = [t.value.col_name for t in Tasks]
|
src/envs.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
from huggingface_hub import HfApi
|
| 4 |
+
|
| 5 |
+
# Info to change for your repository
|
| 6 |
+
# ----------------------------------
|
| 7 |
+
TOKEN = os.environ.get("HF_TOKEN") # A read/write token for your org
|
| 8 |
+
|
| 9 |
+
OWNER = "demo-leaderboard-backend" # Change to your org - don't forget to create a results and request dataset, with the correct format!
|
| 10 |
+
# ----------------------------------
|
| 11 |
+
|
| 12 |
+
QUEUE_REPO = f"{OWNER}/requests"
|
| 13 |
+
RESULTS_REPO = f"{OWNER}/results"
|
| 14 |
+
|
| 15 |
+
# If you setup a cache later, just change HF_HOME
|
| 16 |
+
CACHE_PATH = os.getenv("HF_HOME", ".")
|
| 17 |
+
|
| 18 |
+
# Local caches
|
| 19 |
+
EVAL_REQUESTS_PATH = os.path.join(CACHE_PATH, "eval-queue")
|
| 20 |
+
EVAL_RESULTS_PATH = os.path.join(CACHE_PATH, "eval-results")
|
| 21 |
+
EVAL_REQUESTS_PATH_BACKEND = os.path.join(CACHE_PATH, "eval-queue-bk")
|
| 22 |
+
EVAL_RESULTS_PATH_BACKEND = os.path.join(CACHE_PATH, "eval-results-bk")
|
| 23 |
+
|
| 24 |
+
API = HfApi(token=TOKEN)
|
src/leaderboard/read_evals.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import glob
|
| 2 |
+
import json
|
| 3 |
+
import os
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
|
| 6 |
+
import dateutil
|
| 7 |
+
import numpy as np
|
| 8 |
+
|
| 9 |
+
from src.display.formatting import make_clickable_model
|
| 10 |
+
from src.display.utils import AutoEvalColumn, ModelType, Tasks, Precision, WeightType
|
| 11 |
+
from src.submission.check_validity import is_model_on_hub
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@dataclass
|
| 15 |
+
class EvalResult:
|
| 16 |
+
"""Represents one full evaluation. Built from a combination of the result and request file for a given run."""
|
| 17 |
+
|
| 18 |
+
eval_name: str # org_model_precision (uid)
|
| 19 |
+
full_model: str # org/model (path on hub)
|
| 20 |
+
org: str
|
| 21 |
+
model: str
|
| 22 |
+
revision: str # commit hash, "" if main
|
| 23 |
+
results: dict
|
| 24 |
+
precision: Precision = Precision.Unknown
|
| 25 |
+
model_type: ModelType = ModelType.Unknown # Pretrained, fine tuned, ...
|
| 26 |
+
weight_type: WeightType = WeightType.Original # Original or Adapter
|
| 27 |
+
architecture: str = "Unknown"
|
| 28 |
+
license: str = "?"
|
| 29 |
+
likes: int = 0
|
| 30 |
+
num_params: int = 0
|
| 31 |
+
date: str = "" # submission date of request file
|
| 32 |
+
still_on_hub: bool = False
|
| 33 |
+
|
| 34 |
+
@classmethod
|
| 35 |
+
def init_from_json_file(self, json_filepath):
|
| 36 |
+
"""Inits the result from the specific model result file"""
|
| 37 |
+
with open(json_filepath) as fp:
|
| 38 |
+
data = json.load(fp)
|
| 39 |
+
|
| 40 |
+
config = data.get("config")
|
| 41 |
+
|
| 42 |
+
# Precision
|
| 43 |
+
precision = Precision.from_str(config.get("model_dtype"))
|
| 44 |
+
|
| 45 |
+
# Get model and org
|
| 46 |
+
org_and_model = config.get("model_name", config.get("model_args", None))
|
| 47 |
+
org_and_model = org_and_model.split("/", 1)
|
| 48 |
+
|
| 49 |
+
if len(org_and_model) == 1:
|
| 50 |
+
org = None
|
| 51 |
+
model = org_and_model[0]
|
| 52 |
+
result_key = f"{model}_{precision.value.name}"
|
| 53 |
+
else:
|
| 54 |
+
org = org_and_model[0]
|
| 55 |
+
model = org_and_model[1]
|
| 56 |
+
result_key = f"{org}_{model}_{precision.value.name}"
|
| 57 |
+
full_model = "/".join(org_and_model)
|
| 58 |
+
|
| 59 |
+
still_on_hub, _, model_config = is_model_on_hub(
|
| 60 |
+
full_model, config.get("model_sha", "main"), trust_remote_code=True, test_tokenizer=False
|
| 61 |
+
)
|
| 62 |
+
architecture = "?"
|
| 63 |
+
if model_config is not None:
|
| 64 |
+
architectures = getattr(model_config, "architectures", None)
|
| 65 |
+
if architectures:
|
| 66 |
+
architecture = ";".join(architectures)
|
| 67 |
+
|
| 68 |
+
# Extract results available in this file (some results are split in several files)
|
| 69 |
+
results = {}
|
| 70 |
+
for task in Tasks:
|
| 71 |
+
task = task.value
|
| 72 |
+
|
| 73 |
+
# We average all scores of a given metric (not all metrics are present in all files)
|
| 74 |
+
accs = np.array([v.get(task.metric, None) for k, v in data["results"].items() if task.benchmark == k])
|
| 75 |
+
if accs.size == 0 or any([acc is None for acc in accs]):
|
| 76 |
+
continue
|
| 77 |
+
|
| 78 |
+
mean_acc = np.mean(accs) * 100.0
|
| 79 |
+
results[task.benchmark] = mean_acc
|
| 80 |
+
|
| 81 |
+
return self(
|
| 82 |
+
eval_name=result_key,
|
| 83 |
+
full_model=full_model,
|
| 84 |
+
org=org,
|
| 85 |
+
model=model,
|
| 86 |
+
results=results,
|
| 87 |
+
precision=precision,
|
| 88 |
+
revision=config.get("model_sha", ""),
|
| 89 |
+
still_on_hub=still_on_hub,
|
| 90 |
+
architecture=architecture,
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
def update_with_request_file(self, requests_path):
|
| 94 |
+
"""Finds the relevant request file for the current model and updates info with it"""
|
| 95 |
+
request_file = get_request_file_for_model(requests_path, self.full_model, self.precision.value.name)
|
| 96 |
+
|
| 97 |
+
try:
|
| 98 |
+
with open(request_file, "r") as f:
|
| 99 |
+
request = json.load(f)
|
| 100 |
+
self.model_type = ModelType.from_str(request.get("model_type", ""))
|
| 101 |
+
self.weight_type = WeightType[request.get("weight_type", "Original")]
|
| 102 |
+
self.license = request.get("license", "?")
|
| 103 |
+
self.likes = request.get("likes", 0)
|
| 104 |
+
self.num_params = request.get("params", 0)
|
| 105 |
+
self.date = request.get("submitted_time", "")
|
| 106 |
+
except Exception:
|
| 107 |
+
print(
|
| 108 |
+
f"Could not find request file for {self.org}/{self.model} with precision {self.precision.value.name}"
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
def to_dict(self):
|
| 112 |
+
"""Converts the Eval Result to a dict compatible with our dataframe display"""
|
| 113 |
+
average = sum([v for v in self.results.values() if v is not None]) / len(Tasks)
|
| 114 |
+
data_dict = {
|
| 115 |
+
"eval_name": self.eval_name, # not a column, just a save name,
|
| 116 |
+
AutoEvalColumn.precision.name: self.precision.value.name,
|
| 117 |
+
AutoEvalColumn.model_type.name: self.model_type.value.name,
|
| 118 |
+
AutoEvalColumn.model_type_symbol.name: self.model_type.value.symbol,
|
| 119 |
+
AutoEvalColumn.weight_type.name: self.weight_type.value.name,
|
| 120 |
+
AutoEvalColumn.architecture.name: self.architecture,
|
| 121 |
+
AutoEvalColumn.model.name: make_clickable_model(self.full_model),
|
| 122 |
+
AutoEvalColumn.revision.name: self.revision,
|
| 123 |
+
AutoEvalColumn.average.name: average,
|
| 124 |
+
AutoEvalColumn.license.name: self.license,
|
| 125 |
+
AutoEvalColumn.likes.name: self.likes,
|
| 126 |
+
AutoEvalColumn.params.name: self.num_params,
|
| 127 |
+
AutoEvalColumn.still_on_hub.name: self.still_on_hub,
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
for task in Tasks:
|
| 131 |
+
data_dict[task.value.col_name] = self.results[task.value.benchmark]
|
| 132 |
+
|
| 133 |
+
return data_dict
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def get_request_file_for_model(requests_path, model_name, precision):
|
| 137 |
+
"""Selects the correct request file for a given model. Only keeps runs tagged as FINISHED"""
|
| 138 |
+
request_files = os.path.join(
|
| 139 |
+
requests_path,
|
| 140 |
+
f"{model_name}_eval_request_*.json",
|
| 141 |
+
)
|
| 142 |
+
request_files = glob.glob(request_files)
|
| 143 |
+
|
| 144 |
+
# Select correct request file (precision)
|
| 145 |
+
request_file = ""
|
| 146 |
+
request_files = sorted(request_files, reverse=True)
|
| 147 |
+
for tmp_request_file in request_files:
|
| 148 |
+
with open(tmp_request_file, "r") as f:
|
| 149 |
+
req_content = json.load(f)
|
| 150 |
+
if req_content["status"] in ["FINISHED"] and req_content["precision"] == precision.split(".")[-1]:
|
| 151 |
+
request_file = tmp_request_file
|
| 152 |
+
return request_file
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def get_raw_eval_results(results_path: str, requests_path: str) -> list[EvalResult]:
|
| 156 |
+
"""From the path of the results folder root, extract all needed info for results"""
|
| 157 |
+
model_result_filepaths = []
|
| 158 |
+
|
| 159 |
+
for root, directories, files in os.walk(results_path):
|
| 160 |
+
directories[:] = [directory for directory in directories if not directory.startswith(".")]
|
| 161 |
+
# We should only have json files in model results
|
| 162 |
+
if len(files) == 0 or any([not f.endswith(".json") for f in files]):
|
| 163 |
+
continue
|
| 164 |
+
|
| 165 |
+
# Sort the files by date
|
| 166 |
+
try:
|
| 167 |
+
files.sort(key=lambda x: x.removesuffix(".json").removeprefix("results_")[:-7])
|
| 168 |
+
except dateutil.parser._parser.ParserError:
|
| 169 |
+
files = [files[-1]]
|
| 170 |
+
|
| 171 |
+
for file in files:
|
| 172 |
+
model_result_filepaths.append(os.path.join(root, file))
|
| 173 |
+
|
| 174 |
+
eval_results = {}
|
| 175 |
+
for model_result_filepath in model_result_filepaths:
|
| 176 |
+
# Creation of result
|
| 177 |
+
eval_result = EvalResult.init_from_json_file(model_result_filepath)
|
| 178 |
+
eval_result.update_with_request_file(requests_path)
|
| 179 |
+
|
| 180 |
+
# Store results of same eval together
|
| 181 |
+
eval_name = eval_result.eval_name
|
| 182 |
+
if eval_name in eval_results.keys():
|
| 183 |
+
eval_results[eval_name].results.update({k: v for k, v in eval_result.results.items() if v is not None})
|
| 184 |
+
else:
|
| 185 |
+
eval_results[eval_name] = eval_result
|
| 186 |
+
|
| 187 |
+
results = []
|
| 188 |
+
for v in eval_results.values():
|
| 189 |
+
try:
|
| 190 |
+
v.to_dict() # we test if the dict version is complete
|
| 191 |
+
results.append(v)
|
| 192 |
+
except KeyError: # not all eval values present
|
| 193 |
+
continue
|
| 194 |
+
|
| 195 |
+
return results
|
src/populate.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
import pandas as pd
|
| 5 |
+
|
| 6 |
+
from src.display.formatting import has_no_nan_values, make_clickable_model
|
| 7 |
+
from src.display.utils import AutoEvalColumn, EvalQueueColumn
|
| 8 |
+
from src.leaderboard.read_evals import get_raw_eval_results
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def get_leaderboard_df(results_path: str, requests_path: str, cols: list, benchmark_cols: list) -> pd.DataFrame:
|
| 12 |
+
"""Creates a dataframe from all the individual experiment results"""
|
| 13 |
+
raw_data = get_raw_eval_results(results_path, requests_path)
|
| 14 |
+
all_data_json = [v.to_dict() for v in raw_data]
|
| 15 |
+
|
| 16 |
+
df = pd.DataFrame.from_records(all_data_json)
|
| 17 |
+
df = df.sort_values(by=[AutoEvalColumn.average.name], ascending=False)
|
| 18 |
+
df = df[cols].round(decimals=2)
|
| 19 |
+
|
| 20 |
+
# filter out if any of the benchmarks have not been produced
|
| 21 |
+
df = df[has_no_nan_values(df, benchmark_cols)]
|
| 22 |
+
return df
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def get_evaluation_queue_df(save_path: str, cols: list) -> list[pd.DataFrame]:
|
| 26 |
+
"""Creates the different dataframes for the evaluation queues requestes"""
|
| 27 |
+
entries = [entry for entry in os.listdir(save_path) if not entry.startswith(".")]
|
| 28 |
+
all_evals = []
|
| 29 |
+
|
| 30 |
+
for entry in entries:
|
| 31 |
+
if ".json" in entry:
|
| 32 |
+
file_path = os.path.join(save_path, entry)
|
| 33 |
+
with open(file_path) as fp:
|
| 34 |
+
data = json.load(fp)
|
| 35 |
+
|
| 36 |
+
data[EvalQueueColumn.model.name] = make_clickable_model(data["model"])
|
| 37 |
+
data[EvalQueueColumn.revision.name] = data.get("revision", "main")
|
| 38 |
+
|
| 39 |
+
all_evals.append(data)
|
| 40 |
+
elif ".md" not in entry:
|
| 41 |
+
entry_path = os.path.join(save_path, entry)
|
| 42 |
+
sub_entries = [
|
| 43 |
+
name
|
| 44 |
+
for name in os.listdir(entry_path)
|
| 45 |
+
if os.path.isfile(os.path.join(entry_path, name)) and not name.startswith(".")
|
| 46 |
+
]
|
| 47 |
+
for sub_entry in sub_entries:
|
| 48 |
+
file_path = os.path.join(entry_path, sub_entry)
|
| 49 |
+
with open(file_path) as fp:
|
| 50 |
+
data = json.load(fp)
|
| 51 |
+
|
| 52 |
+
data[EvalQueueColumn.model.name] = make_clickable_model(data["model"])
|
| 53 |
+
data[EvalQueueColumn.revision.name] = data.get("revision", "main")
|
| 54 |
+
all_evals.append(data)
|
| 55 |
+
|
| 56 |
+
pending_list = [e for e in all_evals if e["status"] in ["PENDING", "RERUN"]]
|
| 57 |
+
running_list = [e for e in all_evals if e["status"] == "RUNNING"]
|
| 58 |
+
finished_list = [e for e in all_evals if e["status"].startswith("FINISHED") or e["status"] == "PENDING_NEW_EVAL"]
|
| 59 |
+
df_pending = pd.DataFrame.from_records(pending_list, columns=cols)
|
| 60 |
+
df_running = pd.DataFrame.from_records(running_list, columns=cols)
|
| 61 |
+
df_finished = pd.DataFrame.from_records(finished_list, columns=cols)
|
| 62 |
+
return df_finished[cols], df_running[cols], df_pending[cols]
|
src/submission/check_validity.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
from collections import defaultdict
|
| 4 |
+
|
| 5 |
+
import huggingface_hub
|
| 6 |
+
from huggingface_hub import ModelCard
|
| 7 |
+
from huggingface_hub.hf_api import ModelInfo
|
| 8 |
+
from transformers import AutoConfig
|
| 9 |
+
from transformers.models.auto.tokenization_auto import AutoTokenizer
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def check_model_card(repo_id: str) -> tuple[bool, str]:
|
| 13 |
+
"""Checks if the model card and license exist and have been filled"""
|
| 14 |
+
try:
|
| 15 |
+
card = ModelCard.load(repo_id)
|
| 16 |
+
except huggingface_hub.utils.EntryNotFoundError:
|
| 17 |
+
return False, "Please add a model card to your model to explain how you trained/fine-tuned it."
|
| 18 |
+
|
| 19 |
+
# Enforce license metadata
|
| 20 |
+
if card.data.license is None:
|
| 21 |
+
if not ("license_name" in card.data and "license_link" in card.data):
|
| 22 |
+
return False, (
|
| 23 |
+
"License not found. Please add a license to your model card using the `license` metadata or a"
|
| 24 |
+
" `license_name`/`license_link` pair."
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
# Enforce card content
|
| 28 |
+
if len(card.text) < 200:
|
| 29 |
+
return False, "Please add a description to your model card, it is too short."
|
| 30 |
+
|
| 31 |
+
return True, ""
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def is_model_on_hub(
|
| 35 |
+
model_name: str, revision: str, token: str = None, trust_remote_code=False, test_tokenizer=False
|
| 36 |
+
) -> tuple[bool, str]:
|
| 37 |
+
"""Checks if the model model_name is on the hub, and whether it (and its tokenizer) can be loaded with AutoClasses."""
|
| 38 |
+
try:
|
| 39 |
+
config = AutoConfig.from_pretrained(
|
| 40 |
+
model_name, revision=revision, trust_remote_code=trust_remote_code, token=token
|
| 41 |
+
)
|
| 42 |
+
if test_tokenizer:
|
| 43 |
+
try:
|
| 44 |
+
AutoTokenizer.from_pretrained(
|
| 45 |
+
model_name, revision=revision, trust_remote_code=trust_remote_code, token=token
|
| 46 |
+
)
|
| 47 |
+
except ValueError as e:
|
| 48 |
+
return (False, f"uses a tokenizer which is not in a transformers release: {e}", None)
|
| 49 |
+
except Exception:
|
| 50 |
+
return (
|
| 51 |
+
False,
|
| 52 |
+
"'s tokenizer cannot be loaded. Is your tokenizer class in a stable transformers release, and correctly configured?",
|
| 53 |
+
None,
|
| 54 |
+
)
|
| 55 |
+
return True, None, config
|
| 56 |
+
|
| 57 |
+
except ValueError:
|
| 58 |
+
return (
|
| 59 |
+
False,
|
| 60 |
+
"needs to be launched with `trust_remote_code=True`. For safety reason, we do not allow these models to be automatically submitted to the leaderboard.",
|
| 61 |
+
None,
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
except Exception:
|
| 65 |
+
return False, "was not found on hub!", None
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def get_model_size(model_info: ModelInfo, precision: str):
|
| 69 |
+
"""Gets the model size from the configuration, or the model name if the configuration does not contain the information."""
|
| 70 |
+
try:
|
| 71 |
+
model_size = round(model_info.safetensors["total"] / 1e9, 3)
|
| 72 |
+
except (AttributeError, TypeError):
|
| 73 |
+
return 0 # Unknown model sizes are indicated as 0, see NUMERIC_INTERVALS in app.py
|
| 74 |
+
|
| 75 |
+
size_factor = 8 if (precision == "GPTQ" or "gptq" in model_info.modelId.lower()) else 1
|
| 76 |
+
model_size = size_factor * model_size
|
| 77 |
+
return model_size
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def get_model_arch(model_info: ModelInfo):
|
| 81 |
+
"""Gets the model architecture from the configuration"""
|
| 82 |
+
return model_info.config.get("architectures", "Unknown")
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def already_submitted_models(requested_models_dir: str) -> set[str]:
|
| 86 |
+
"""Gather a list of already submitted models to avoid duplicates"""
|
| 87 |
+
depth = 1
|
| 88 |
+
file_names = []
|
| 89 |
+
users_to_submission_dates = defaultdict(list)
|
| 90 |
+
|
| 91 |
+
for root, _, files in os.walk(requested_models_dir):
|
| 92 |
+
current_depth = root.count(os.sep) - requested_models_dir.count(os.sep)
|
| 93 |
+
if current_depth == depth:
|
| 94 |
+
for file in files:
|
| 95 |
+
if not file.endswith(".json"):
|
| 96 |
+
continue
|
| 97 |
+
with open(os.path.join(root, file), "r") as f:
|
| 98 |
+
info = json.load(f)
|
| 99 |
+
file_names.append(f"{info['model']}_{info['revision']}_{info['precision']}")
|
| 100 |
+
|
| 101 |
+
# Select organisation
|
| 102 |
+
if info["model"].count("/") == 0 or "submitted_time" not in info:
|
| 103 |
+
continue
|
| 104 |
+
organisation, _ = info["model"].split("/")
|
| 105 |
+
users_to_submission_dates[organisation].append(info["submitted_time"])
|
| 106 |
+
|
| 107 |
+
return set(file_names), users_to_submission_dates
|
src/submission/submit.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
import re
|
| 4 |
+
from datetime import datetime, timezone
|
| 5 |
+
|
| 6 |
+
from src.envs import API, EVAL_REQUESTS_PATH, QUEUE_REPO, TOKEN
|
| 7 |
+
from src.submission.check_validity import (
|
| 8 |
+
already_submitted_models,
|
| 9 |
+
check_model_card,
|
| 10 |
+
get_model_size,
|
| 11 |
+
is_model_on_hub,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
REQUESTED_MODELS = None
|
| 15 |
+
USERS_TO_SUBMISSION_DATES = None
|
| 16 |
+
MODEL_ID_PATTERN = re.compile(r"^[\w.-]+/[\w.-]+$")
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def response(status: str, message: str) -> dict[str, str]:
|
| 20 |
+
return {"status": status, "message": message}
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def add_new_eval(
|
| 24 |
+
model: str,
|
| 25 |
+
base_model: str,
|
| 26 |
+
revision: str,
|
| 27 |
+
precision: str,
|
| 28 |
+
weight_type: str,
|
| 29 |
+
model_type: str,
|
| 30 |
+
):
|
| 31 |
+
global REQUESTED_MODELS
|
| 32 |
+
global USERS_TO_SUBMISSION_DATES
|
| 33 |
+
if REQUESTED_MODELS is None:
|
| 34 |
+
REQUESTED_MODELS, USERS_TO_SUBMISSION_DATES = already_submitted_models(EVAL_REQUESTS_PATH)
|
| 35 |
+
|
| 36 |
+
model = model.strip()
|
| 37 |
+
base_model = base_model.strip()
|
| 38 |
+
revision = revision.strip() or "main"
|
| 39 |
+
if not MODEL_ID_PATTERN.fullmatch(model):
|
| 40 |
+
return response("error", "Enter a model ID in the form organization/model-name.")
|
| 41 |
+
|
| 42 |
+
user_name, model_path = model.split("/", 1)
|
| 43 |
+
precision = precision.split(" ")[0]
|
| 44 |
+
current_time = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
| 45 |
+
|
| 46 |
+
if not model_type:
|
| 47 |
+
return response("error", "Please select a model type.")
|
| 48 |
+
|
| 49 |
+
if weight_type in ["Delta", "Adapter"]:
|
| 50 |
+
if not MODEL_ID_PATTERN.fullmatch(base_model):
|
| 51 |
+
return response("error", "Enter a base model ID in the form organization/model-name.")
|
| 52 |
+
base_model_on_hub, error, _ = is_model_on_hub(
|
| 53 |
+
model_name=base_model,
|
| 54 |
+
revision=revision,
|
| 55 |
+
token=TOKEN,
|
| 56 |
+
test_tokenizer=True,
|
| 57 |
+
)
|
| 58 |
+
if not base_model_on_hub:
|
| 59 |
+
return response("error", f'Base model "{base_model}" {error}')
|
| 60 |
+
|
| 61 |
+
if weight_type != "Adapter":
|
| 62 |
+
model_on_hub, error, _ = is_model_on_hub(
|
| 63 |
+
model_name=model,
|
| 64 |
+
revision=revision,
|
| 65 |
+
token=TOKEN,
|
| 66 |
+
test_tokenizer=True,
|
| 67 |
+
)
|
| 68 |
+
if not model_on_hub:
|
| 69 |
+
return response("error", f'Model "{model}" {error}')
|
| 70 |
+
|
| 71 |
+
try:
|
| 72 |
+
model_info = API.model_info(repo_id=model, revision=revision)
|
| 73 |
+
except Exception:
|
| 74 |
+
return response("error", "Could not read this model. Check its ID and revision, then try again.")
|
| 75 |
+
|
| 76 |
+
model_size = get_model_size(model_info=model_info, precision=precision)
|
| 77 |
+
try:
|
| 78 |
+
license_name = model_info.cardData["license"]
|
| 79 |
+
except Exception:
|
| 80 |
+
return response("error", "Please select a license in your model card.")
|
| 81 |
+
|
| 82 |
+
modelcard_ok, error_msg = check_model_card(model)
|
| 83 |
+
if not modelcard_ok:
|
| 84 |
+
return response("error", error_msg)
|
| 85 |
+
|
| 86 |
+
eval_entry = {
|
| 87 |
+
"model": model,
|
| 88 |
+
"base_model": base_model,
|
| 89 |
+
"revision": revision,
|
| 90 |
+
"precision": precision,
|
| 91 |
+
"weight_type": weight_type,
|
| 92 |
+
"status": "PENDING",
|
| 93 |
+
"submitted_time": current_time,
|
| 94 |
+
"model_type": model_type,
|
| 95 |
+
"likes": model_info.likes,
|
| 96 |
+
"params": model_size,
|
| 97 |
+
"license": license_name,
|
| 98 |
+
"private": False,
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
request_key = f"{model}_{revision}_{precision}"
|
| 102 |
+
if request_key in REQUESTED_MODELS:
|
| 103 |
+
return response("warning", "This model has already been submitted.")
|
| 104 |
+
|
| 105 |
+
out_dir = os.path.join(EVAL_REQUESTS_PATH, user_name)
|
| 106 |
+
os.makedirs(out_dir, exist_ok=True)
|
| 107 |
+
out_path = os.path.join(out_dir, f"{model_path}_eval_request_False_{precision}_{weight_type}.json")
|
| 108 |
+
with open(out_path, "w") as file:
|
| 109 |
+
json.dump(eval_entry, file)
|
| 110 |
+
|
| 111 |
+
try:
|
| 112 |
+
API.upload_file(
|
| 113 |
+
path_or_fileobj=out_path,
|
| 114 |
+
path_in_repo=os.path.relpath(out_path, EVAL_REQUESTS_PATH),
|
| 115 |
+
repo_id=QUEUE_REPO,
|
| 116 |
+
repo_type="dataset",
|
| 117 |
+
commit_message=f"Add {model} to eval queue",
|
| 118 |
+
)
|
| 119 |
+
except Exception:
|
| 120 |
+
os.remove(out_path)
|
| 121 |
+
return response("error", "The model passed validation, but the queue could not be updated. Please try again.")
|
| 122 |
+
|
| 123 |
+
REQUESTED_MODELS.add(request_key)
|
| 124 |
+
return response(
|
| 125 |
+
"success",
|
| 126 |
+
"Your model is in the evaluation queue. It should appear in the pending list shortly.",
|
| 127 |
+
)
|
static/app.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
|
| 2 |
+
|
| 3 |
+
const state = {
|
| 4 |
+
api: null,
|
| 5 |
+
payload: null,
|
| 6 |
+
rows: [],
|
| 7 |
+
sort: { key: "Average ⬆️", direction: "desc" },
|
| 8 |
+
};
|
| 9 |
+
|
| 10 |
+
let columns = [];
|
| 11 |
+
|
| 12 |
+
function configureColumns(benchmarks = []) {
|
| 13 |
+
columns = [
|
| 14 |
+
{ key: "rank", label: "Rank" },
|
| 15 |
+
{ key: "Model", label: "Model" },
|
| 16 |
+
{ key: "Average ⬆️", label: "Average", score: true },
|
| 17 |
+
...benchmarks.map((benchmark) => ({ key: benchmark, label: benchmark, score: true })),
|
| 18 |
+
{ key: "Type", label: "Type" },
|
| 19 |
+
{ key: "Precision", label: "Precision" },
|
| 20 |
+
{ key: "#Params (B)", label: "Params (B)", score: true },
|
| 21 |
+
{ key: "Hub License", label: "License" },
|
| 22 |
+
{ key: "Hub ❤️", label: "Likes", score: true },
|
| 23 |
+
{ key: "Available on the hub", label: "Hub" },
|
| 24 |
+
];
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
const $ = (selector) => document.querySelector(selector);
|
| 28 |
+
const $$ = (selector) => [...document.querySelectorAll(selector)];
|
| 29 |
+
const escapeHtml = (value) => String(value ?? "").replace(/[&<>'"]/g, (char) => ({ "&": "&", "<": "<", ">": ">", "'": "'", '"': """ })[char]);
|
| 30 |
+
const safeModelId = (value) => /^[\w.-]+\/[\w.-]+$/.test(String(value || "")) ? value : "";
|
| 31 |
+
const formatScore = (value) => value == null || Number.isNaN(Number(value)) ? "—" : Number(value).toFixed(2);
|
| 32 |
+
const firstData = (response) => Array.isArray(response.data) ? response.data[0] : response.data;
|
| 33 |
+
|
| 34 |
+
function markdown(source = "") {
|
| 35 |
+
let text = escapeHtml(source.trim());
|
| 36 |
+
text = text.replace(/```(?:[a-zA-Z0-9_+-]+)?\n([\s\S]*?)```/g, (_, code) => `<pre><code>${code.trim()}</code></pre>`);
|
| 37 |
+
text = text.replace(/^### (.+)$/gm, "<h3>$1</h3>").replace(/^## (.+)$/gm, "<h2>$1</h2>");
|
| 38 |
+
text = text.replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g, '<a href="$2" target="_blank" rel="noreferrer">$1</a>');
|
| 39 |
+
text = text.replace(/`([^`]+)`/g, "<code>$1</code>");
|
| 40 |
+
return text.split(/\n{2,}/).map((block) => /^<(h2|h3|pre)/.test(block) ? block : `<p>${block.replace(/\n/g, "<br>")}</p>`).join("");
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
function renderHead() {
|
| 44 |
+
$("#leaderboard-head").innerHTML = `<tr>${columns.map((column) => {
|
| 45 |
+
const sortable = column.score || column.key === "Model";
|
| 46 |
+
const active = state.sort.key === column.key;
|
| 47 |
+
const arrow = active ? (state.sort.direction === "asc" ? " ↑" : " ↓") : "";
|
| 48 |
+
return `<th class="${sortable ? "sortable" : ""} ${active ? "active-sort" : ""}" data-key="${escapeHtml(column.key)}">${column.label}${arrow}</th>`;
|
| 49 |
+
}).join("")}</tr>`;
|
| 50 |
+
$$("th.sortable").forEach((header) => header.addEventListener("click", () => {
|
| 51 |
+
const key = header.dataset.key;
|
| 52 |
+
state.sort = { key, direction: state.sort.key === key && state.sort.direction === "desc" ? "asc" : "desc" };
|
| 53 |
+
renderHead();
|
| 54 |
+
renderRows();
|
| 55 |
+
}));
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
function filteredRows() {
|
| 59 |
+
const query = $("#search").value.trim().toLowerCase();
|
| 60 |
+
const type = $("#type-filter").value;
|
| 61 |
+
const precision = $("#precision-filter").value;
|
| 62 |
+
const available = $("#available-filter").checked;
|
| 63 |
+
const rows = state.rows.filter((row) => {
|
| 64 |
+
const haystack = `${row.Model || ""} ${row["Hub License"] || ""}`.toLowerCase();
|
| 65 |
+
return (!query || haystack.includes(query)) && (!type || row.Type === type) && (!precision || row.Precision === precision) && (!available || row["Available on the hub"] === true);
|
| 66 |
+
});
|
| 67 |
+
const multiplier = state.sort.direction === "asc" ? 1 : -1;
|
| 68 |
+
return rows.sort((a, b) => {
|
| 69 |
+
const av = a[state.sort.key];
|
| 70 |
+
const bv = b[state.sort.key];
|
| 71 |
+
if (typeof av === "number" && typeof bv === "number") return (av - bv) * multiplier;
|
| 72 |
+
return String(av ?? "").localeCompare(String(bv ?? "")) * multiplier;
|
| 73 |
+
});
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
function cell(row, column, rank) {
|
| 77 |
+
const value = row[column.key];
|
| 78 |
+
if (column.key === "rank") return `<td class="rank ${rank < 3 ? "top" : ""}">${String(rank + 1).padStart(2, "0")}</td>`;
|
| 79 |
+
if (column.key === "Model") {
|
| 80 |
+
const id = safeModelId(value);
|
| 81 |
+
return `<td class="model-cell">${id ? `<a href="https://huggingface.co/${encodeURI(id)}" target="_blank" rel="noreferrer">${escapeHtml(id)}</a>` : escapeHtml(value)}</td>`;
|
| 82 |
+
}
|
| 83 |
+
if (column.key === "Available on the hub") return `<td><span class="availability ${value === true ? "yes" : ""}" title="${value === true ? "Available" : "Unavailable"}"></span></td>`;
|
| 84 |
+
if (["Type", "Precision", "Hub License"].includes(column.key)) return `<td><span class="pill">${escapeHtml(value || "—")}</span></td>`;
|
| 85 |
+
if (column.score) return `<td class="score ${column.key === "Average ⬆️" ? "primary" : ""}">${formatScore(value)}</td>`;
|
| 86 |
+
return `<td>${escapeHtml(value ?? "—")}</td>`;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
function renderRows() {
|
| 90 |
+
const rows = filteredRows();
|
| 91 |
+
$("#leaderboard-body").innerHTML = rows.map((row, rank) => `<tr>${columns.map((column) => cell(row, column, rank)).join("")}</tr>`).join("");
|
| 92 |
+
$(".table-shell").classList.toggle("is-empty", rows.length === 0);
|
| 93 |
+
$("#empty-state").classList.toggle("hidden", rows.length > 0);
|
| 94 |
+
$("#result-count").textContent = `${rows.length} model${rows.length === 1 ? "" : "s"} shown`;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
function populateSelect(selector, values, selected = "") {
|
| 98 |
+
const select = $(selector);
|
| 99 |
+
const existing = select.querySelector("option")?.outerHTML || "";
|
| 100 |
+
select.innerHTML = existing + values.map((value) => `<option value="${escapeHtml(value)}" ${value === selected ? "selected" : ""}>${escapeHtml(value)}</option>`).join("");
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
function renderQueues(queues) {
|
| 104 |
+
const definitions = [
|
| 105 |
+
["finished", "Finished", "Recently completed"],
|
| 106 |
+
["running", "Running", "Evaluations in progress"],
|
| 107 |
+
["pending", "Pending", "Waiting to be evaluated"],
|
| 108 |
+
];
|
| 109 |
+
$("#queue-grid").innerHTML = definitions.map(([key, label, help]) => {
|
| 110 |
+
const rows = queues[key] || [];
|
| 111 |
+
const latest = rows[0]?.model ? `Latest: ${escapeHtml(rows[0].model)}` : help;
|
| 112 |
+
return `<article class="queue-card ${key}"><header><h3>${label}</h3><strong>${rows.length}</strong></header><p>${latest}</p></article>`;
|
| 113 |
+
}).join("");
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
function applyPayload(payload) {
|
| 117 |
+
state.payload = payload;
|
| 118 |
+
state.rows = payload.rows || [];
|
| 119 |
+
configureColumns(payload.benchmarks);
|
| 120 |
+
document.title = payload.copy?.title || "Open Leaderboard";
|
| 121 |
+
$("#leaderboard-name").textContent = payload.copy?.title || "Open Leaderboard";
|
| 122 |
+
$("#updated-at").textContent = `Updated ${new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(new Date(payload.updated_at))}`;
|
| 123 |
+
$("#data-warning").textContent = payload.error || "";
|
| 124 |
+
$("#data-warning").classList.toggle("hidden", !payload.error);
|
| 125 |
+
|
| 126 |
+
const types = [...new Set(state.rows.map((row) => row.Type).filter(Boolean))];
|
| 127 |
+
const precisions = [...new Set(state.rows.map((row) => row.Precision).filter(Boolean))];
|
| 128 |
+
populateSelect("#type-filter", types);
|
| 129 |
+
populateSelect("#precision-filter", precisions);
|
| 130 |
+
populateSelect('[name="model_type"]', payload.model_types || []);
|
| 131 |
+
populateSelect('[name="precision"]', payload.precisions || [], "float16");
|
| 132 |
+
populateSelect('[name="weight_type"]', payload.weight_types || [], "Original");
|
| 133 |
+
|
| 134 |
+
renderHead();
|
| 135 |
+
renderRows();
|
| 136 |
+
renderQueues(payload.queues || {});
|
| 137 |
+
$("#submission-copy").innerHTML = markdown(payload.copy?.submission);
|
| 138 |
+
$("#about-copy").innerHTML = markdown(payload.copy?.about);
|
| 139 |
+
if (payload.copy?.citation) {
|
| 140 |
+
$("#citation-card").classList.remove("hidden");
|
| 141 |
+
$("#citation-label").textContent = payload.copy.citation_label;
|
| 142 |
+
$("#citation").textContent = payload.copy.citation;
|
| 143 |
+
}
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
async function loadData() {
|
| 147 |
+
try {
|
| 148 |
+
state.api = await client(location.origin);
|
| 149 |
+
const response = await state.api.predict("/leaderboard_data", {});
|
| 150 |
+
applyPayload(firstData(response));
|
| 151 |
+
} catch (error) {
|
| 152 |
+
$("#leaderboard-body").innerHTML = "";
|
| 153 |
+
$(".table-shell").classList.add("is-empty");
|
| 154 |
+
$("#data-warning").textContent = `The leaderboard could not be loaded: ${error.message}`;
|
| 155 |
+
$("#data-warning").classList.remove("hidden");
|
| 156 |
+
$("#empty-state").classList.remove("hidden");
|
| 157 |
+
}
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
$$('.tab').forEach((tab) => tab.addEventListener("click", () => {
|
| 161 |
+
$$('.tab').forEach((item) => item.classList.toggle("active", item === tab));
|
| 162 |
+
$$('.panel').forEach((panel) => panel.classList.toggle("active", panel.id === `panel-${tab.dataset.tab}`));
|
| 163 |
+
history.replaceState(null, "", `#${tab.dataset.tab}`);
|
| 164 |
+
}));
|
| 165 |
+
|
| 166 |
+
[$("#search"), $("#type-filter"), $("#precision-filter"), $("#available-filter")].forEach((control) => control.addEventListener("input", renderRows));
|
| 167 |
+
|
| 168 |
+
$("#submit-form").addEventListener("submit", async (event) => {
|
| 169 |
+
event.preventDefault();
|
| 170 |
+
const button = event.currentTarget.querySelector("button[type=submit]");
|
| 171 |
+
const result = $("#submission-result");
|
| 172 |
+
const values = Object.fromEntries(new FormData(event.currentTarget));
|
| 173 |
+
button.disabled = true;
|
| 174 |
+
button.firstElementChild.textContent = "Validating model…";
|
| 175 |
+
result.className = "notice hidden";
|
| 176 |
+
try {
|
| 177 |
+
const response = await state.api.predict("/submit_model", values);
|
| 178 |
+
const data = firstData(response);
|
| 179 |
+
result.textContent = data.message;
|
| 180 |
+
result.className = `notice ${data.status}`;
|
| 181 |
+
if (data.status === "success") event.currentTarget.reset();
|
| 182 |
+
} catch (error) {
|
| 183 |
+
result.textContent = `Submission failed: ${error.message}`;
|
| 184 |
+
result.className = "notice error";
|
| 185 |
+
} finally {
|
| 186 |
+
button.disabled = false;
|
| 187 |
+
button.firstElementChild.textContent = "Submit evaluation";
|
| 188 |
+
}
|
| 189 |
+
});
|
| 190 |
+
|
| 191 |
+
$("#copy-citation").addEventListener("click", async () => {
|
| 192 |
+
await navigator.clipboard.writeText(state.payload.copy.citation);
|
| 193 |
+
$("#copy-citation").textContent = "Copied";
|
| 194 |
+
setTimeout(() => $("#copy-citation").textContent = "Copy citation", 1600);
|
| 195 |
+
});
|
| 196 |
+
|
| 197 |
+
const initialTab = location.hash.slice(1);
|
| 198 |
+
if (["submit", "about"].includes(initialTab)) $(`.tab[data-tab="${initialTab}"]`).click();
|
| 199 |
+
loadData();
|
static/index.html
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 6 |
+
<meta name="description" content="A customizable leaderboard for comparing model evaluation results." />
|
| 7 |
+
<title>Open Leaderboard</title>
|
| 8 |
+
<link rel="stylesheet" href="/static/styles.css" />
|
| 9 |
+
</head>
|
| 10 |
+
<body>
|
| 11 |
+
<main>
|
| 12 |
+
<header class="app-header">
|
| 13 |
+
<h1 id="leaderboard-name">Open Leaderboard</h1>
|
| 14 |
+
</header>
|
| 15 |
+
|
| 16 |
+
<nav class="tabs" aria-label="Leaderboard sections">
|
| 17 |
+
<button class="tab active" data-tab="leaderboard">
|
| 18 |
+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 20V10M10 20V4M16 20v-7M22 20H2" /></svg>
|
| 19 |
+
<span>Leaderboard</span>
|
| 20 |
+
</button>
|
| 21 |
+
<button class="tab" data-tab="submit">
|
| 22 |
+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 16V3M7 8l5-5 5 5M5 21h14" /></svg>
|
| 23 |
+
<span>Submit</span>
|
| 24 |
+
</button>
|
| 25 |
+
<button class="tab" data-tab="about">
|
| 26 |
+
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9" /><path d="M12 11v5M12 8h.01" /></svg>
|
| 27 |
+
<span>About</span>
|
| 28 |
+
</button>
|
| 29 |
+
</nav>
|
| 30 |
+
|
| 31 |
+
<section class="panel active" id="panel-leaderboard">
|
| 32 |
+
<div class="panel-heading">
|
| 33 |
+
<div><h2>LLM Benchmark</h2><p>Compare evaluation results across models.</p></div>
|
| 34 |
+
<p id="updated-at" class="updated">Loading the latest results…</p>
|
| 35 |
+
</div>
|
| 36 |
+
|
| 37 |
+
<div class="toolbar">
|
| 38 |
+
<label class="search"><span>⌕</span><input id="search" type="search" placeholder="Search models or licenses" /></label>
|
| 39 |
+
<label class="select-control"><span>Model type</span><select id="type-filter"><option value="">All types</option></select></label>
|
| 40 |
+
<label class="select-control"><span>Precision</span><select id="precision-filter"><option value="">Any precision</option></select></label>
|
| 41 |
+
<label class="toggle"><input id="available-filter" type="checkbox" /> Available only</label>
|
| 42 |
+
</div>
|
| 43 |
+
|
| 44 |
+
<div id="data-warning" class="notice warning hidden"></div>
|
| 45 |
+
<div class="table-shell">
|
| 46 |
+
<table>
|
| 47 |
+
<thead id="leaderboard-head"></thead>
|
| 48 |
+
<tbody id="leaderboard-body">
|
| 49 |
+
<tr class="loading-row"><td><span class="skeleton"></span></td><td><span class="skeleton wide"></span></td><td><span class="skeleton"></span></td><td><span class="skeleton"></span></td></tr>
|
| 50 |
+
</tbody>
|
| 51 |
+
</table>
|
| 52 |
+
<div class="empty-state hidden" id="empty-state"><h3>No models found</h3><p>Try clearing one of your filters.</p></div>
|
| 53 |
+
</div>
|
| 54 |
+
<div class="table-footer"><span id="result-count">—</span><span>Click a score heading to sort</span></div>
|
| 55 |
+
</section>
|
| 56 |
+
|
| 57 |
+
<section class="panel" id="panel-submit">
|
| 58 |
+
<div class="panel-heading">
|
| 59 |
+
<div><h2>Submit a model</h2><p>Validate a public Hugging Face model and add it to the evaluation queue.</p></div>
|
| 60 |
+
</div>
|
| 61 |
+
|
| 62 |
+
<div class="queue-grid" id="queue-grid"></div>
|
| 63 |
+
|
| 64 |
+
<div class="submit-layout">
|
| 65 |
+
<form id="submit-form" class="block submit-card">
|
| 66 |
+
<div class="block-heading"><h3>Model details</h3><p>Required fields must be completed before submission.</p></div>
|
| 67 |
+
<div class="form-grid">
|
| 68 |
+
<label class="field full"><span>Model ID <b>required</b></span><input name="model" placeholder="organization/model-name" required /></label>
|
| 69 |
+
<label class="field"><span>Revision</span><input name="revision" placeholder="main" /></label>
|
| 70 |
+
<label class="field"><span>Model type <b>required</b></span><select name="model_type" required><option value="">Choose a type</option></select></label>
|
| 71 |
+
<label class="field"><span>Precision <b>required</b></span><select name="precision" required></select></label>
|
| 72 |
+
<label class="field"><span>Weights <b>required</b></span><select name="weight_type" required></select></label>
|
| 73 |
+
<label class="field full"><span>Base model <small>for adapter or delta weights</small></span><input name="base_model" placeholder="organization/base-model" /></label>
|
| 74 |
+
</div>
|
| 75 |
+
<button class="primary-button" type="submit"><span>Submit evaluation</span></button>
|
| 76 |
+
<div id="submission-result" class="notice hidden" role="status"></div>
|
| 77 |
+
</form>
|
| 78 |
+
|
| 79 |
+
<aside class="block guidance-card"><h3>Before you submit</h3><div id="submission-copy" class="prose"></div></aside>
|
| 80 |
+
</div>
|
| 81 |
+
</section>
|
| 82 |
+
|
| 83 |
+
<section class="panel" id="panel-about">
|
| 84 |
+
<div class="panel-heading"><div><h2>About</h2><p>Methodology and reproducibility information.</p></div></div>
|
| 85 |
+
<div id="about-copy" class="block prose about-copy"></div>
|
| 86 |
+
<div id="citation-card" class="block citation-card hidden"><div><h3 id="citation-label"></h3></div><button id="copy-citation">Copy</button><pre id="citation"></pre></div>
|
| 87 |
+
</section>
|
| 88 |
+
</main>
|
| 89 |
+
|
| 90 |
+
<script type="module" src="/static/app.js"></script>
|
| 91 |
+
</body>
|
| 92 |
+
</html>
|
static/styles.css
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
color-scheme: light;
|
| 3 |
+
--background: #ffffff;
|
| 4 |
+
--block: #ffffff;
|
| 5 |
+
--text: #111827;
|
| 6 |
+
--body-text: #374151;
|
| 7 |
+
--muted: #6b7280;
|
| 8 |
+
--border: #e5e7eb;
|
| 9 |
+
--border-strong: #d1d5db;
|
| 10 |
+
--primary: #f97316;
|
| 11 |
+
--primary-hover: #ea580c;
|
| 12 |
+
--primary-soft: #fff7ed;
|
| 13 |
+
--success: #15803d;
|
| 14 |
+
--warning: #a16207;
|
| 15 |
+
--danger: #b91c1c;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
* { box-sizing: border-box; }
|
| 19 |
+
body { margin: 0; min-height: 100vh; background: var(--background); color: var(--text); font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; }
|
| 20 |
+
button, input, select { font: inherit; }
|
| 21 |
+
button { cursor: pointer; }
|
| 22 |
+
main { width: min(1180px, calc(100% - 32px)); margin: 0 auto; padding: 36px 0 52px; }
|
| 23 |
+
|
| 24 |
+
.app-header { max-width: 760px; margin: 0 auto 28px; text-align: center; }
|
| 25 |
+
.app-header h1 { margin: 0 0 10px; font-size: 32px; line-height: 1.2; font-weight: 700; }
|
| 26 |
+
.tabs { display: flex; justify-content: center; gap: 4px; margin-bottom: 24px; padding-bottom: 10px; border-bottom: 1px solid var(--border); }
|
| 27 |
+
.tab { display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 8px 14px; border: 1px solid transparent; border-radius: 8px; background: transparent; color: var(--muted); font-weight: 500; }
|
| 28 |
+
.tab svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
|
| 29 |
+
.tab:hover { color: var(--text); background: #f3f4f6; }
|
| 30 |
+
.tab.active { color: var(--text); background: var(--block); border-color: var(--border); box-shadow: 0 1px 2px rgba(0, 0, 0, .04); }
|
| 31 |
+
|
| 32 |
+
.panel { display: none; }
|
| 33 |
+
.panel.active { display: block; }
|
| 34 |
+
.panel-heading { display: flex; align-items: end; justify-content: space-between; gap: 24px; margin-bottom: 14px; }
|
| 35 |
+
.panel-heading h2 { margin: 0 0 4px; font-size: 20px; line-height: 1.3; }
|
| 36 |
+
.panel-heading p { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.45; }
|
| 37 |
+
.updated { white-space: nowrap; font-size: 11px !important; }
|
| 38 |
+
|
| 39 |
+
.block, .toolbar, .table-shell, .queue-card { background: var(--block); border: 1px solid var(--border); border-radius: 10px; }
|
| 40 |
+
.toolbar { display: grid; grid-template-columns: minmax(240px, 1fr) auto auto auto; gap: 10px; align-items: end; padding: 12px; margin-bottom: 12px; }
|
| 41 |
+
.search { display: flex; align-items: center; gap: 8px; height: 38px; padding: 0 11px; border: 1px solid var(--border-strong); border-radius: 7px; background: var(--block); }
|
| 42 |
+
.search > span { color: var(--muted); font-size: 18px; }
|
| 43 |
+
.search input { width: 100%; border: 0; outline: 0; background: transparent; color: var(--text); font-size: 13px; }
|
| 44 |
+
.select-control { display: grid; gap: 4px; color: var(--muted); font-size: 11px; }
|
| 45 |
+
.select-control select { height: 38px; min-width: 130px; padding: 0 28px 0 10px; border: 1px solid var(--border-strong); border-radius: 7px; outline: 0; background: var(--block); color: var(--text); font-size: 13px; }
|
| 46 |
+
.toggle { display: flex; align-items: center; gap: 7px; height: 38px; padding: 0 9px; color: var(--body-text); font-size: 12px; white-space: nowrap; }
|
| 47 |
+
.toggle input { width: 15px; height: 15px; accent-color: var(--primary); }
|
| 48 |
+
|
| 49 |
+
.table-shell { position: relative; overflow-x: auto; }
|
| 50 |
+
.table-shell.is-empty { min-height: 180px; }
|
| 51 |
+
table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
| 52 |
+
th { height: 42px; padding: 0 12px; border-bottom: 1px solid var(--border); background: #f9fafb; color: var(--muted); text-align: right; font-size: 11px; font-weight: 600; white-space: nowrap; }
|
| 53 |
+
th:first-child, th:nth-child(2), td:first-child, td:nth-child(2) { text-align: left; }
|
| 54 |
+
th.sortable { cursor: pointer; user-select: none; }
|
| 55 |
+
th.sortable:hover, th.active-sort { color: var(--text); }
|
| 56 |
+
td { height: 54px; padding: 0 12px; border-bottom: 1px solid var(--border); text-align: right; white-space: nowrap; }
|
| 57 |
+
tbody tr:last-child td { border-bottom: 0; }
|
| 58 |
+
tbody tr:hover { background: #fafafa; }
|
| 59 |
+
.rank { width: 42px; color: var(--muted); }
|
| 60 |
+
.rank.top, .score.primary { color: var(--primary-hover); font-weight: 600; }
|
| 61 |
+
.model-cell a { display: inline-flex; align-items: center; gap: 5px; max-width: 290px; overflow: hidden; color: var(--text); font-weight: 600; text-decoration: none; text-overflow: ellipsis; }
|
| 62 |
+
.model-cell a::after { content: "↗"; color: var(--muted); font-size: 10px; }
|
| 63 |
+
.model-cell a:hover { color: var(--primary-hover); }
|
| 64 |
+
.pill { display: inline-flex; padding: 3px 7px; border-radius: 6px; background: #f3f4f6; color: var(--body-text); font-size: 11px; }
|
| 65 |
+
.availability { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--danger); }
|
| 66 |
+
.availability.yes { background: var(--success); }
|
| 67 |
+
.table-footer { display: flex; justify-content: space-between; padding: 8px 3px 0; color: var(--muted); font-size: 11px; }
|
| 68 |
+
.empty-state { position: absolute; inset: 43px 0 0; display: grid; place-content: center; text-align: center; color: var(--muted); }
|
| 69 |
+
.empty-state h3 { margin: 0 0 4px; color: var(--body-text); font-size: 14px; }
|
| 70 |
+
.empty-state p { margin: 0; font-size: 12px; }
|
| 71 |
+
.skeleton { display: block; width: 48px; height: 10px; border-radius: 4px; background: #e5e7eb; }
|
| 72 |
+
.skeleton.wide { width: 160px; }
|
| 73 |
+
|
| 74 |
+
.queue-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 14px; }
|
| 75 |
+
.queue-card { padding: 14px; }
|
| 76 |
+
.queue-card header { display: flex; align-items: center; justify-content: space-between; }
|
| 77 |
+
.queue-card h3 { margin: 0; font-size: 13px; }
|
| 78 |
+
.queue-card strong { font-size: 18px; }
|
| 79 |
+
.queue-card p { margin: 7px 0 0; overflow: hidden; color: var(--muted); font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }
|
| 80 |
+
.queue-card.finished strong { color: var(--success); }
|
| 81 |
+
.queue-card.running strong { color: var(--primary-hover); }
|
| 82 |
+
.queue-card.pending strong { color: var(--warning); }
|
| 83 |
+
|
| 84 |
+
.submit-layout { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(300px, .85fr); gap: 14px; align-items: start; }
|
| 85 |
+
.submit-card, .guidance-card, .about-copy, .citation-card { padding: 18px; }
|
| 86 |
+
.block-heading { padding-bottom: 14px; border-bottom: 1px solid var(--border); }
|
| 87 |
+
.block-heading h3, .guidance-card > h3 { margin: 0 0 4px; font-size: 15px; }
|
| 88 |
+
.block-heading p { margin: 0; color: var(--muted); font-size: 12px; }
|
| 89 |
+
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 12px; padding: 16px 0; }
|
| 90 |
+
.field { display: grid; gap: 6px; }
|
| 91 |
+
.field.full { grid-column: 1 / -1; }
|
| 92 |
+
.field > span { color: var(--body-text); font-size: 12px; font-weight: 500; }
|
| 93 |
+
.field b { margin-left: 3px; color: var(--muted); font-size: 10px; font-weight: 400; }
|
| 94 |
+
.field small { color: var(--muted); font-weight: 400; }
|
| 95 |
+
.field input, .field select { width: 100%; height: 39px; padding: 0 10px; border: 1px solid var(--border-strong); border-radius: 7px; outline: 0; background: var(--block); color: var(--text); font-size: 13px; }
|
| 96 |
+
.field input:focus, .field select:focus, .search:focus-within { border-color: var(--primary); box-shadow: 0 0 0 2px var(--primary-soft); }
|
| 97 |
+
.primary-button { min-height: 38px; padding: 0 14px; border: 1px solid var(--primary-hover); border-radius: 7px; background: var(--primary); color: #fff; font-weight: 600; }
|
| 98 |
+
.primary-button:hover { background: var(--primary-hover); }
|
| 99 |
+
.primary-button:disabled { opacity: .6; cursor: wait; }
|
| 100 |
+
.guidance-card { max-height: 560px; overflow: auto; }
|
| 101 |
+
|
| 102 |
+
.prose { color: var(--body-text); font-size: 13px; line-height: 1.6; }
|
| 103 |
+
.prose h2, .prose h3 { color: var(--text); }
|
| 104 |
+
.prose h2 { margin: 20px 0 6px; font-size: 17px; }
|
| 105 |
+
.prose h3 { margin: 16px 0 5px; font-size: 14px; }
|
| 106 |
+
.prose p { margin: 7px 0; }
|
| 107 |
+
.prose pre { margin: 10px 0; overflow-wrap: anywhere; white-space: pre-wrap; padding: 11px; border: 1px solid var(--border); border-radius: 7px; background: #f9fafb; font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
| 108 |
+
.prose code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .92em; }
|
| 109 |
+
.prose pre code { font-size: inherit; }
|
| 110 |
+
.prose a { color: #2563eb; }
|
| 111 |
+
.about-copy { min-height: 160px; }
|
| 112 |
+
.about-copy h2:first-child { margin-top: 0; }
|
| 113 |
+
.citation-card { display: grid; grid-template-columns: 1fr auto; gap: 12px; margin-top: 12px; }
|
| 114 |
+
.citation-card h3 { margin: 5px 0 0; font-size: 14px; }
|
| 115 |
+
.citation-card button { align-self: start; padding: 6px 10px; border: 1px solid var(--border-strong); border-radius: 6px; background: var(--block); color: var(--text); }
|
| 116 |
+
.citation-card pre { grid-column: 1 / -1; overflow: auto; margin: 0; padding: 10px; border-radius: 6px; background: #f9fafb; font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; }
|
| 117 |
+
|
| 118 |
+
.notice { margin: 10px 0; padding: 10px 12px; border: 1px solid; border-radius: 7px; font-size: 12px; line-height: 1.45; }
|
| 119 |
+
.notice.warning { color: var(--warning); background: #fffbeb; border-color: #fde68a; }
|
| 120 |
+
.notice.error { color: var(--danger); background: #fef2f2; border-color: #fecaca; }
|
| 121 |
+
.notice.success { color: var(--success); background: #f0fdf4; border-color: #bbf7d0; }
|
| 122 |
+
.hidden { display: none !important; }
|
| 123 |
+
@media (max-width: 820px) {
|
| 124 |
+
.toolbar { grid-template-columns: 1fr 1fr; }
|
| 125 |
+
.search { grid-column: 1 / -1; }
|
| 126 |
+
.submit-layout { grid-template-columns: 1fr; }
|
| 127 |
+
.guidance-card { max-height: none; }
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
@media (max-width: 560px) {
|
| 131 |
+
main { width: min(100% - 24px, 1180px); padding-top: 24px; }
|
| 132 |
+
.app-header h1 { font-size: 26px; }
|
| 133 |
+
.tabs { justify-content: stretch; }
|
| 134 |
+
.tab { flex: 1; padding-inline: 8px; }
|
| 135 |
+
.panel-heading { display: block; }
|
| 136 |
+
.updated { margin-top: 6px !important; }
|
| 137 |
+
.toolbar { grid-template-columns: 1fr; }
|
| 138 |
+
.search { grid-column: auto; }
|
| 139 |
+
.select-control select { width: 100%; }
|
| 140 |
+
.queue-grid, .form-grid { grid-template-columns: 1fr; }
|
| 141 |
+
.field.full { grid-column: auto; }
|
| 142 |
+
.table-footer span:last-child { display: none; }
|
| 143 |
+
}
|