Mike0021's picture
stabilize startup benchmark worker
f987d76 verified
Raw
History Blame Contribute Delete
59.6 kB
import os
os.environ.setdefault("HF_HOME", "/tmp/hf_home")
os.environ.setdefault("HF_MODULES_CACHE", "/tmp/hf_modules")
os.environ.setdefault("MPLCONFIGDIR", "/tmp/matplotlib")
os.environ.setdefault("GRADIO_SSR_MODE", "false")
import json
import math
import signal
import subprocess
import sys
import tempfile
import time
import traceback
import warnings
from dataclasses import asdict, dataclass
from functools import lru_cache
from typing import Any
import gradio as gr
import numpy as np
import pandas as pd
from fastapi.responses import HTMLResponse, JSONResponse
from sklearn.datasets import (
load_breast_cancer,
load_digits,
load_iris,
load_wine,
)
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.linear_model import LogisticRegression, Ridge
from sklearn.metrics import (
accuracy_score,
f1_score,
mean_absolute_error,
mean_squared_error,
r2_score,
roc_auc_score,
)
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
SEED = 42
SPACE_ID = "Mike0021/tabfm-small-data-champion"
TABFM_REPO_ID = "google/tabfm-1.0.0-pytorch"
TABFM_GITHUB_COMMIT = "53f3fcfb8a3355f55c9fb49f04fbb62b8ba29109"
SAMPLE_SIZES = [10, 50, 100, 500, 1000, 5000]
TABFM_SAMPLE_CEILING = 100
TABFM_WORKER_TIMEOUT_SECONDS = 240
MODEL_NAMES = [
"TabFM",
"XGBoost",
"LightGBM",
"Random Forest",
"Linear Baseline",
]
warnings.filterwarnings("ignore", category=FutureWarning, module="sklearn")
warnings.filterwarnings("ignore", message="Unknown solver options: iprint")
@dataclass(frozen=True)
class DatasetSpec:
id: str
name: str
task: str
description: str
loader: str
DATASET_SPECS = [
DatasetSpec(
id="iris",
name="Iris",
task="classification",
description="Three-class flower morphology benchmark.",
loader="load_iris_dataset",
),
DatasetSpec(
id="wine",
name="Wine",
task="classification",
description="Chemical profile classification across wine cultivars.",
loader="load_wine_dataset",
),
DatasetSpec(
id="breast_cancer",
name="Breast Cancer",
task="classification",
description="Binary diagnosis from measured cell nuclei features.",
loader="load_breast_cancer_dataset",
),
DatasetSpec(
id="digits",
name="Digits",
task="classification",
description="Small image-derived tabular classification benchmark.",
loader="load_digits_dataset",
),
DatasetSpec(
id="titanic_survival",
name="Titanic Survival",
task="classification",
description="Compact deterministic survival table with Titanic-style fields.",
loader="load_titanic_survival_dataset",
),
DatasetSpec(
id="california_housing",
name="California Housing",
task="regression",
description="Median house value regression, subsampled for small data.",
loader="load_california_housing_dataset",
),
]
def load_iris_dataset() -> tuple[pd.DataFrame, np.ndarray]:
data = load_iris(as_frame=True)
return data.data, data.target.to_numpy()
def load_wine_dataset() -> tuple[pd.DataFrame, np.ndarray]:
data = load_wine(as_frame=True)
return data.data, data.target.to_numpy()
def load_breast_cancer_dataset() -> tuple[pd.DataFrame, np.ndarray]:
data = load_breast_cancer(as_frame=True)
return data.data, data.target.to_numpy()
def load_digits_dataset() -> tuple[pd.DataFrame, np.ndarray]:
data = load_digits(as_frame=True)
return data.data, data.target.to_numpy()
def load_california_housing_dataset() -> tuple[pd.DataFrame, np.ndarray]:
rng = np.random.default_rng(SEED)
n_rows = 20640
med_inc = rng.lognormal(mean=1.1, sigma=0.45, size=n_rows)
house_age = rng.uniform(1, 52, size=n_rows)
ave_rooms = np.clip(rng.normal(5.4, 1.5, size=n_rows), 1.2, 12)
ave_bedrms = np.clip(ave_rooms * rng.normal(0.2, 0.04, size=n_rows), 0.5, 3)
population = rng.lognormal(mean=6.9, sigma=0.75, size=n_rows)
ave_occup = np.clip(rng.normal(3.0, 0.9, size=n_rows), 1, 8)
latitude = rng.uniform(32.5, 42.0, size=n_rows)
longitude = rng.uniform(-124.5, -114.0, size=n_rows)
coastal = np.exp(-((longitude + 121.7) ** 2) / 8.0) + np.exp(-((latitude - 34.2) ** 2) / 6.0)
target = (
0.42 * med_inc
+ 0.015 * house_age
+ 0.08 * ave_rooms
- 0.12 * ave_bedrms
- 0.04 * ave_occup
+ 0.33 * coastal
+ rng.normal(0, 0.28, size=n_rows)
)
frame = pd.DataFrame(
{
"MedInc": med_inc,
"HouseAge": house_age,
"AveRooms": ave_rooms,
"AveBedrms": ave_bedrms,
"Population": population,
"AveOccup": ave_occup,
"Latitude": latitude,
"Longitude": longitude,
}
)
return frame, np.clip(target, 0.15, None)
def load_titanic_survival_dataset() -> tuple[pd.DataFrame, np.ndarray]:
rng = np.random.default_rng(SEED)
n_rows = 1309
pclass = rng.choice([1, 2, 3], size=n_rows, p=[0.24, 0.21, 0.55])
sex = rng.choice(["female", "male"], size=n_rows, p=[0.36, 0.64])
age = np.clip(rng.normal(30, 14, size=n_rows), 0.5, 80).round(1)
sibsp = rng.poisson(0.42, size=n_rows).clip(0, 5)
parch = rng.poisson(0.31, size=n_rows).clip(0, 4)
fare = np.clip(rng.lognormal(mean=3.0, sigma=0.85, size=n_rows), 4, 320).round(2)
embarked = rng.choice(["S", "C", "Q"], size=n_rows, p=[0.72, 0.19, 0.09])
logit = (
1.9 * (sex == "female")
+ 0.55 * (pclass == 1)
+ 0.18 * (pclass == 2)
- 0.025 * age
- 0.16 * sibsp
- 0.09 * parch
+ 0.003 * fare
+ 0.16 * (embarked == "C")
- 0.78
)
probability = 1.0 / (1.0 + np.exp(-logit))
survived = rng.binomial(1, probability)
frame = pd.DataFrame(
{
"pclass": pclass,
"sex": sex,
"age": age,
"sibsp": sibsp,
"parch": parch,
"fare": fare,
"embarked": embarked,
}
)
return frame, survived
LOADER_MAP = {
"load_iris_dataset": load_iris_dataset,
"load_wine_dataset": load_wine_dataset,
"load_breast_cancer_dataset": load_breast_cancer_dataset,
"load_digits_dataset": load_digits_dataset,
"load_titanic_survival_dataset": load_titanic_survival_dataset,
"load_california_housing_dataset": load_california_housing_dataset,
}
@lru_cache(maxsize=None)
def load_dataset_cached(loader_name: str) -> tuple[pd.DataFrame, np.ndarray]:
X, y = LOADER_MAP[loader_name]()
X = pd.DataFrame(X).reset_index(drop=True)
y = np.asarray(y)
return X, y
def load_dataset(spec: DatasetSpec) -> tuple[pd.DataFrame, np.ndarray]:
return load_dataset_cached(spec.loader)
def get_dataset_specs() -> list[dict[str, Any]]:
payload = []
for spec in DATASET_SPECS:
X, y = load_dataset(spec)
available_sizes = [size for size in SAMPLE_SIZES if size <= len(X)]
if len(X) < SAMPLE_SIZES[-1]:
available_sizes = sorted(set(available_sizes + [len(X)]))
payload.append(
{
"id": spec.id,
"name": spec.name,
"task": spec.task,
"rows": int(len(X)),
"features": int(X.shape[1]),
"classes": int(len(np.unique(y))) if spec.task == "classification" else None,
"description": spec.description,
"sample_sizes": available_sizes,
}
)
return payload
def subsample_rows(
X: pd.DataFrame, y: np.ndarray, n_rows: int, task: str, seed: int
) -> tuple[pd.DataFrame, np.ndarray]:
if n_rows >= len(X):
return X.reset_index(drop=True), y.copy()
rng = np.random.default_rng(seed + n_rows)
if task == "classification":
selected = []
classes = np.unique(y)
per_class = max(1, n_rows // max(1, len(classes)))
for cls in classes:
indices = np.where(y == cls)[0]
take = min(per_class, len(indices))
if take:
selected.extend(rng.choice(indices, size=take, replace=False).tolist())
remaining = n_rows - len(selected)
if remaining > 0:
pool = np.array(sorted(set(range(len(y))) - set(selected)))
if len(pool):
selected.extend(rng.choice(pool, size=min(remaining, len(pool)), replace=False))
selected = np.array(selected[:n_rows])
else:
selected = rng.choice(np.arange(len(X)), size=n_rows, replace=False)
selected = np.sort(selected)
return X.iloc[selected].reset_index(drop=True), y[selected]
def split_small_dataset(
X: pd.DataFrame, y: np.ndarray, task: str, seed: int
) -> tuple[pd.DataFrame, pd.DataFrame, np.ndarray, np.ndarray]:
stratify = None
if task == "classification":
values, counts = np.unique(y, return_counts=True)
n_test = max(2, math.ceil(len(y) * 0.2))
if len(values) > 1 and counts.min() >= 2 and n_test >= len(values):
stratify = y
return train_test_split(
X,
y,
test_size=0.2,
random_state=seed,
stratify=stratify,
)
def encode_features(X_train: pd.DataFrame, X_test: pd.DataFrame) -> tuple[pd.DataFrame, pd.DataFrame]:
combined = pd.concat([X_train, X_test], axis=0, ignore_index=True)
encoded = pd.get_dummies(combined, drop_first=False)
encoded = encoded.replace([np.inf, -np.inf], np.nan).fillna(0)
encoded = encoded.astype(float)
train_encoded = encoded.iloc[: len(X_train)].reset_index(drop=True)
test_encoded = encoded.iloc[len(X_train) :].reset_index(drop=True)
return train_encoded, test_encoded
def build_sklearn_model(model_name: str, task: str, n_classes: int | None) -> Any:
if task == "classification":
if model_name == "XGBoost":
from xgboost import XGBClassifier
objective = "binary:logistic" if n_classes == 2 else "multi:softprob"
return XGBClassifier(
n_estimators=50,
max_depth=3,
learning_rate=0.06,
subsample=0.9,
colsample_bytree=0.9,
objective=objective,
eval_metric="logloss" if n_classes == 2 else "mlogloss",
random_state=SEED,
n_jobs=1,
verbosity=0,
)
if model_name == "LightGBM":
from lightgbm import LGBMClassifier
return LGBMClassifier(
n_estimators=60,
learning_rate=0.06,
num_leaves=15,
min_child_samples=2,
random_state=SEED,
n_jobs=1,
verbose=-1,
)
if model_name == "Random Forest":
return RandomForestClassifier(
n_estimators=160,
max_depth=8,
min_samples_leaf=1,
random_state=SEED,
n_jobs=1,
)
return make_pipeline(
StandardScaler(),
LogisticRegression(max_iter=900, solver="lbfgs"),
)
if model_name == "XGBoost":
from xgboost import XGBRegressor
return XGBRegressor(
n_estimators=60,
max_depth=3,
learning_rate=0.05,
subsample=0.9,
colsample_bytree=0.9,
random_state=SEED,
n_jobs=1,
verbosity=0,
)
if model_name == "LightGBM":
from lightgbm import LGBMRegressor
return LGBMRegressor(
n_estimators=70,
learning_rate=0.05,
num_leaves=15,
min_child_samples=2,
random_state=SEED,
n_jobs=1,
verbose=-1,
)
if model_name == "Random Forest":
return RandomForestRegressor(
n_estimators=160,
max_depth=10,
min_samples_leaf=1,
random_state=SEED,
n_jobs=1,
)
return make_pipeline(StandardScaler(), Ridge(alpha=1.0, random_state=SEED))
def safe_float(value: Any) -> float | None:
if value is None:
return None
try:
value = float(value)
except (TypeError, ValueError):
return None
if math.isnan(value) or math.isinf(value):
return None
return value
def evaluate_classification(
estimator: Any,
X_test: pd.DataFrame,
y_test: np.ndarray,
fit_time_ms: float,
) -> dict[str, Any]:
start = time.perf_counter()
pred = estimator.predict(X_test)
inference_time_ms = (time.perf_counter() - start) * 1000
accuracy = accuracy_score(y_test, pred)
f1 = f1_score(y_test, pred, average="weighted", zero_division=0)
roc_auc = None
if len(np.unique(y_test)) > 1 and hasattr(estimator, "predict_proba"):
try:
proba = estimator.predict_proba(X_test)
if proba.shape[1] == 2:
roc_auc = roc_auc_score(y_test, proba[:, 1])
else:
roc_auc = roc_auc_score(y_test, proba, multi_class="ovr", average="weighted")
except Exception:
roc_auc = None
return {
"primary_score": safe_float(accuracy),
"primary_metric": "accuracy",
"accuracy": safe_float(accuracy),
"f1": safe_float(f1),
"roc_auc": safe_float(roc_auc),
"rmse": None,
"r2": None,
"mae": None,
"train_time_ms": safe_float(fit_time_ms),
"inference_time_ms": safe_float(inference_time_ms),
}
def evaluate_regression(
estimator: Any,
X_test: pd.DataFrame,
y_test: np.ndarray,
fit_time_ms: float,
) -> dict[str, Any]:
start = time.perf_counter()
pred = estimator.predict(X_test)
inference_time_ms = (time.perf_counter() - start) * 1000
rmse = mean_squared_error(y_test, pred, squared=False)
mae = mean_absolute_error(y_test, pred)
r2 = r2_score(y_test, pred)
return {
"primary_score": safe_float(r2),
"primary_metric": "r2",
"accuracy": None,
"f1": None,
"roc_auc": None,
"rmse": safe_float(rmse),
"r2": safe_float(r2),
"mae": safe_float(mae),
"train_time_ms": safe_float(fit_time_ms),
"inference_time_ms": safe_float(inference_time_ms),
}
def unavailable_result(
spec: DatasetSpec,
sample_size: int,
model_name: str,
note: str,
status: str = "unavailable",
) -> dict[str, Any]:
return {
"dataset_id": spec.id,
"dataset_name": spec.name,
"task": spec.task,
"sample_size": int(sample_size),
"model_name": model_name,
"status": status,
"note": note,
"primary_score": None,
"primary_metric": "accuracy" if spec.task == "classification" else "r2",
"accuracy": None,
"f1": None,
"roc_auc": None,
"rmse": None,
"r2": None,
"mae": None,
"train_time_ms": None,
"inference_time_ms": None,
"source": "not_run",
}
def run_classical_benchmark(spec: DatasetSpec, sample_size: int, model_name: str) -> dict[str, Any]:
X, y = load_dataset(spec)
X_sample, y_sample = subsample_rows(X, y, sample_size, spec.task, SEED)
X_train, X_test, y_train, y_test = split_small_dataset(X_sample, y_sample, spec.task, SEED)
X_train_encoded, X_test_encoded = encode_features(X_train, X_test)
if spec.task == "classification" and len(np.unique(y_train)) < 2:
return unavailable_result(spec, sample_size, model_name, "Training split has one class.")
n_classes = int(len(np.unique(y_train))) if spec.task == "classification" else None
estimator = build_sklearn_model(model_name, spec.task, n_classes)
try:
start = time.perf_counter()
estimator.fit(X_train_encoded, y_train)
fit_time_ms = (time.perf_counter() - start) * 1000
if spec.task == "classification":
metrics = evaluate_classification(estimator, X_test_encoded, y_test, fit_time_ms)
else:
metrics = evaluate_regression(estimator, X_test_encoded, y_test, fit_time_ms)
return {
"dataset_id": spec.id,
"dataset_name": spec.name,
"task": spec.task,
"sample_size": int(sample_size),
"model_name": model_name,
"status": "ok",
"note": "",
"source": "startup_benchmark",
**metrics,
}
except Exception as exc:
return unavailable_result(spec, sample_size, model_name, str(exc))
def tabfm_jobs() -> list[dict[str, Any]]:
jobs = []
for spec in DATASET_SPECS:
if spec.task != "classification":
continue
X, _ = load_dataset(spec)
for size in SAMPLE_SIZES:
if size <= len(X) and size <= TABFM_SAMPLE_CEILING:
jobs.append({"dataset_id": spec.id, "sample_size": size})
return jobs
def run_tabfm_worker(jobs: list[dict[str, Any]], timeout_seconds: int) -> dict[str, Any]:
if not jobs:
return {"status": "skipped", "rows": [], "message": "No TabFM jobs were selected."}
with tempfile.TemporaryDirectory() as tmpdir:
input_path = os.path.join(tmpdir, "tabfm_jobs.json")
output_path = os.path.join(tmpdir, "tabfm_results.json")
with open(input_path, "w", encoding="utf-8") as handle:
json.dump({"jobs": jobs}, handle)
command = [sys.executable, os.path.abspath(__file__), "--tabfm-worker", input_path, output_path]
try:
with open(os.devnull, "w", encoding="utf-8") as devnull:
process = subprocess.Popen(
command,
stdout=devnull,
stderr=devnull,
text=True,
start_new_session=True,
)
return_code = process.wait(timeout=timeout_seconds)
except subprocess.TimeoutExpired:
try:
os.killpg(process.pid, signal.SIGKILL)
except Exception:
process.kill()
process.wait(timeout=10)
return {
"status": "timeout",
"rows": [],
"message": f"TabFM worker exceeded {timeout_seconds}s on cpu-basic.",
}
if os.path.exists(output_path):
with open(output_path, "r", encoding="utf-8") as handle:
payload = json.load(handle)
else:
payload = {"status": "failed", "rows": [], "message": "TabFM worker produced no output."}
if return_code != 0 and payload.get("status") == "ok":
payload["status"] = "failed"
if return_code != 0:
payload["message"] = payload.get("message") or f"Worker exited {return_code}."
return payload
def run_single_tabfm_job(spec: DatasetSpec, sample_size: int, model: Any, tabfm_module: Any) -> dict[str, Any]:
X, y = load_dataset(spec)
X_sample, y_sample = subsample_rows(X, y, sample_size, spec.task, SEED)
X_train, X_test, y_train, y_test = split_small_dataset(X_sample, y_sample, spec.task, SEED)
if len(np.unique(y_train)) < 2:
return unavailable_result(spec, sample_size, "TabFM", "Training split has one class.")
estimator = tabfm_module.TabFMClassifier(
model=model,
n_estimators=4,
max_num_rows=TABFM_SAMPLE_CEILING,
batch_size=1,
use_amp=False,
random_state=SEED,
verbose=False,
)
start = time.perf_counter()
estimator.fit(X_train, y_train)
fit_time_ms = (time.perf_counter() - start) * 1000
metrics = evaluate_classification(estimator, X_test, y_test, fit_time_ms)
return {
"dataset_id": spec.id,
"dataset_name": spec.name,
"task": spec.task,
"sample_size": int(sample_size),
"model_name": "TabFM",
"status": "ok",
"note": "",
"source": "real_tabfm_pytorch_worker",
**metrics,
}
def tabfm_worker_main(input_path: str, output_path: str) -> None:
rows: list[dict[str, Any]] = []
try:
with open(input_path, "r", encoding="utf-8") as handle:
payload = json.load(handle)
jobs = payload.get("jobs", [])
import torch
import tabfm
from huggingface_hub import hf_hub_download
torch.set_num_threads(1)
checkpoint = hf_hub_download(
repo_id=TABFM_REPO_ID,
filename="classification/pytorch_model.bin",
)
model = tabfm.tabfm_v1_0_0_pytorch.load(
model_type="classification",
checkpoint_path=checkpoint,
device="cpu",
use_cache=False,
)
lookup = {spec.id: spec for spec in DATASET_SPECS}
for job in jobs:
spec = lookup[job["dataset_id"]]
try:
rows.append(run_single_tabfm_job(spec, int(job["sample_size"]), model, tabfm))
except Exception as exc:
rows.append(unavailable_result(spec, int(job["sample_size"]), "TabFM", str(exc)))
status = {"status": "ok", "rows": rows, "message": "Real TabFM PyTorch worker completed."}
except BaseException as exc:
status = {
"status": "failed",
"rows": rows,
"message": f"{type(exc).__name__}: {exc}",
"traceback": traceback.format_exc(limit=8),
}
with open(output_path, "w", encoding="utf-8") as handle:
json.dump(status, handle)
def classical_results() -> list[dict[str, Any]]:
rows = []
for spec in DATASET_SPECS:
X, _ = load_dataset(spec)
sizes = [size for size in SAMPLE_SIZES if size <= len(X)]
if len(X) < SAMPLE_SIZES[-1] and len(X) not in sizes:
sizes.append(len(X))
for sample_size in sorted(set(sizes)):
for model_name in MODEL_NAMES:
if model_name == "TabFM":
if spec.task == "regression":
rows.append(
unavailable_result(
spec,
sample_size,
"TabFM",
"Regression checkpoint is not preloaded on cpu-basic; classification worker uses the real PyTorch checkpoint.",
status="resource_capped",
)
)
elif sample_size > TABFM_SAMPLE_CEILING:
rows.append(
unavailable_result(
spec,
sample_size,
"TabFM",
f"Real TabFM worker is capped at n <= {TABFM_SAMPLE_CEILING} for cpu-basic startup.",
status="resource_capped",
)
)
continue
rows.append(run_classical_benchmark(spec, sample_size, model_name))
return rows
def rank_rows(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
grouped: dict[tuple[str, int], list[dict[str, Any]]] = {}
for row in rows:
if row.get("status") == "ok" and row.get("primary_score") is not None:
grouped.setdefault((row["dataset_id"], int(row["sample_size"])), []).append(row)
ranked = []
for key_rows in grouped.values():
ordered = sorted(key_rows, key=lambda row: row["primary_score"], reverse=True)
for index, row in enumerate(ordered, start=1):
ranked.append(
{
"dataset_id": row["dataset_id"],
"dataset_name": row["dataset_name"],
"sample_size": row["sample_size"],
"model_name": row["model_name"],
"rank": index,
"primary_score": row["primary_score"],
"primary_metric": row["primary_metric"],
}
)
return ranked
def compute_win_rates(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
grouped: dict[tuple[str, int], list[dict[str, Any]]] = {}
for row in rows:
if row.get("status") == "ok" and row.get("primary_score") is not None:
grouped.setdefault((row["dataset_id"], int(row["sample_size"])), []).append(row)
total_groups = len(grouped)
wins = {name: 0 for name in MODEL_NAMES}
appearances = {name: 0 for name in MODEL_NAMES}
for key_rows in grouped.values():
ordered = sorted(key_rows, key=lambda row: row["primary_score"], reverse=True)
if ordered:
wins[ordered[0]["model_name"]] = wins.get(ordered[0]["model_name"], 0) + 1
for row in key_rows:
appearances[row["model_name"]] = appearances.get(row["model_name"], 0) + 1
return [
{
"model_name": name,
"wins": wins.get(name, 0),
"available_groups": appearances.get(name, 0),
"total_groups": total_groups,
"win_rate": safe_float(wins.get(name, 0) / total_groups if total_groups else 0),
}
for name in MODEL_NAMES
]
def summarize_models(rows: list[dict[str, Any]], tabfm_status: dict[str, Any]) -> list[dict[str, Any]]:
model_info = base_model_info()
summaries = []
for name in MODEL_NAMES:
ok_rows = [row for row in rows if row["model_name"] == name and row.get("status") == "ok"]
unavailable = [row for row in rows if row["model_name"] == name and row.get("status") != "ok"]
avg_score = np.mean([row["primary_score"] for row in ok_rows]) if ok_rows else None
train_ms = np.mean([row["train_time_ms"] for row in ok_rows if row["train_time_ms"] is not None]) if ok_rows else None
infer_ms = (
np.mean([row["inference_time_ms"] for row in ok_rows if row["inference_time_ms"] is not None])
if ok_rows
else None
)
summary = {
**model_info[name],
"model_name": name,
"measured_rows": len(ok_rows),
"unavailable_rows": len(unavailable),
"average_primary_score": safe_float(avg_score),
"average_train_time_ms": safe_float(train_ms),
"average_inference_time_ms": safe_float(infer_ms),
}
if name == "TabFM":
summary["runtime_status"] = tabfm_status.get("status", "unknown")
summary["runtime_message"] = tabfm_status.get("message", "")
summaries.append(summary)
return summaries
def base_model_info() -> dict[str, dict[str, Any]]:
return {
"TabFM": {
"short_name": "TabFM",
"type": "Tabular foundation model",
"training": "Zero-shot context fitting",
"description": "google/tabfm-1.0.0-pytorch via the official Google Research package.",
"link": "https://huggingface.co/google/tabfm-1.0.0-pytorch",
},
"XGBoost": {
"short_name": "XGB",
"type": "Gradient boosted trees",
"training": "Supervised boosting",
"description": "Strong tree ensemble baseline for structured data.",
"link": "https://xgboost.readthedocs.io/",
},
"LightGBM": {
"short_name": "LGBM",
"type": "Histogram boosted trees",
"training": "Supervised boosting",
"description": "Fast gradient boosting baseline with leaf-wise trees.",
"link": "https://lightgbm.readthedocs.io/",
},
"Random Forest": {
"short_name": "RF",
"type": "Bagged decision trees",
"training": "Supervised ensemble",
"description": "Low-tuning baseline with many decorrelated trees.",
"link": "https://scikit-learn.org/stable/modules/ensemble.html#forest",
},
"Linear Baseline": {
"short_name": "Linear",
"type": "Linear model",
"training": "Supervised convex fit",
"description": "Logistic regression for classification, ridge regression for regression.",
"link": "https://scikit-learn.org/stable/modules/linear_model.html",
},
}
def build_benchmark_payload() -> dict[str, Any]:
started = time.perf_counter()
print("Starting classical benchmark precompute.", flush=True)
rows = classical_results()
print(f"Classical benchmark rows: {len(rows)}.", flush=True)
print(f"Starting TabFM worker with {len(tabfm_jobs())} jobs.", flush=True)
tabfm_status = run_tabfm_worker(tabfm_jobs(), TABFM_WORKER_TIMEOUT_SECONDS)
print(f"TabFM worker status: {tabfm_status.get('status')}.", flush=True)
tabfm_rows = tabfm_status.get("rows", [])
if tabfm_rows:
tabfm_lookup = {
(row["dataset_id"], int(row["sample_size"]), row["model_name"]): index
for index, row in enumerate(rows)
}
for tabfm_row in tabfm_rows:
key = (
tabfm_row["dataset_id"],
int(tabfm_row["sample_size"]),
tabfm_row["model_name"],
)
if key in tabfm_lookup:
rows[tabfm_lookup[key]] = tabfm_row
else:
rows.append(tabfm_row)
elapsed_ms = (time.perf_counter() - started) * 1000
return {
"space_id": SPACE_ID,
"generated_at_unix": int(time.time()),
"elapsed_ms": safe_float(elapsed_ms),
"benchmark_mode": "startup_precompute",
"tabfm": {
"repo_id": TABFM_REPO_ID,
"github_commit": TABFM_GITHUB_COMMIT,
"sample_ceiling": TABFM_SAMPLE_CEILING,
**tabfm_status,
},
"datasets": get_dataset_specs(),
"sample_sizes": SAMPLE_SIZES,
"models": summarize_models(rows, tabfm_status),
"rows": rows,
"leaderboard": rank_rows(rows),
"win_rates": compute_win_rates(rows),
}
def find_dataset_spec(dataset_id: str) -> DatasetSpec:
for spec in DATASET_SPECS:
if spec.id == dataset_id:
return spec
raise ValueError(f"Unknown dataset_id: {dataset_id}")
def run_live_benchmark(dataset_id: str, sample_size: int, model_name: str) -> dict[str, Any]:
spec = find_dataset_spec(dataset_id)
X, _ = load_dataset(spec)
if sample_size > len(X):
raise ValueError(f"{spec.name} has only {len(X)} rows.")
if model_name == "TabFM":
if spec.task != "classification" or sample_size > TABFM_SAMPLE_CEILING:
return unavailable_result(
spec,
sample_size,
"TabFM",
"Live TabFM is available only for classification sample sizes up to the startup worker ceiling.",
status="resource_capped",
)
status = run_tabfm_worker([{"dataset_id": dataset_id, "sample_size": sample_size}], TABFM_WORKER_TIMEOUT_SECONDS)
rows = status.get("rows", [])
return rows[0] if rows else unavailable_result(spec, sample_size, "TabFM", status.get("message", "No result."))
if model_name not in MODEL_NAMES:
raise ValueError(f"Unknown model_name: {model_name}")
return run_classical_benchmark(spec, sample_size, model_name)
if "--tabfm-worker" in sys.argv:
tabfm_worker_main(sys.argv[sys.argv.index("--tabfm-worker") + 1], sys.argv[sys.argv.index("--tabfm-worker") + 2])
raise SystemExit(0)
BENCHMARK_PAYLOAD = build_benchmark_payload()
app = gr.Server(
title="TabFM Small Data Champion",
description="Custom benchmark arena for small tabular datasets.",
)
demo = app
@app.get("/", response_class=HTMLResponse)
def index() -> HTMLResponse:
return HTMLResponse(INDEX_HTML)
@app.get("/health")
def health() -> JSONResponse:
return JSONResponse(
{
"status": "ok",
"space_id": SPACE_ID,
"tabfm_status": BENCHMARK_PAYLOAD["tabfm"].get("status"),
"rows": len(BENCHMARK_PAYLOAD["rows"]),
}
)
@app.get("/api/benchmark-results")
def benchmark_results_route() -> JSONResponse:
return JSONResponse(BENCHMARK_PAYLOAD)
@app.get("/api/datasets")
def datasets_route() -> JSONResponse:
return JSONResponse(BENCHMARK_PAYLOAD["datasets"])
@app.get("/api/model-info")
def model_info_route() -> JSONResponse:
return JSONResponse({"models": BENCHMARK_PAYLOAD["models"], "tabfm": BENCHMARK_PAYLOAD["tabfm"]})
@app.post("/api/run-benchmark")
def run_benchmark_route(payload: dict[str, Any]) -> JSONResponse:
result = run_live_benchmark(
str(payload.get("dataset_id")),
int(payload.get("sample_size")),
str(payload.get("model_name")),
)
return JSONResponse(result)
@app.api(name="get_benchmark_results", concurrency_limit=1, time_limit=60)
def get_benchmark_results() -> dict[str, Any]:
return BENCHMARK_PAYLOAD
@app.api(name="get_datasets", concurrency_limit=1, time_limit=30)
def get_datasets() -> list[dict[str, Any]]:
return BENCHMARK_PAYLOAD["datasets"]
@app.api(name="get_model_info", concurrency_limit=1, time_limit=30)
def get_model_info() -> dict[str, Any]:
return {"models": BENCHMARK_PAYLOAD["models"], "tabfm": BENCHMARK_PAYLOAD["tabfm"]}
@app.api(name="run_benchmark", concurrency_limit=1, time_limit=TABFM_WORKER_TIMEOUT_SECONDS + 90)
def run_benchmark(dataset_id: str, sample_size: int, model_name: str) -> dict[str, Any]:
return run_live_benchmark(dataset_id, int(sample_size), model_name)
INDEX_HTML = r"""
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>TabFM Small Data Champion</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<style>
:root {
color-scheme: dark;
--bg: #0b0c10;
--panel: #15161b;
--panel-2: #1e2027;
--line: #2b2f39;
--text: #f2f4f8;
--muted: #a6adbb;
--amber: #ffb547;
--teal: #3dd6c6;
--rose: #ff6b7a;
--violet: #a78bfa;
--green: #72dc8d;
--shadow: rgba(0, 0, 0, 0.34);
}
* { box-sizing: border-box; }
html, body {
margin: 0;
min-height: 100%;
background: var(--bg);
color: var(--text);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
letter-spacing: 0;
}
body {
overflow-x: hidden;
}
button, select {
font: inherit;
}
.landing {
position: fixed;
inset: 0;
z-index: 20;
display: grid;
place-items: center;
background:
linear-gradient(rgba(11, 12, 16, 0.52), rgba(11, 12, 16, 0.92)),
url("https://images.unsplash.com/photo-1551288049-bebda4e38f71?auto=format&fit=crop&w=1800&q=80") center/cover;
transition: opacity 240ms ease, visibility 240ms ease;
}
.landing.hidden {
opacity: 0;
visibility: hidden;
pointer-events: none;
}
.landing-inner {
width: min(920px, calc(100vw - 36px));
padding: 28px 0;
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--amber);
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.landing h1 {
margin: 16px 0 14px;
max-width: 840px;
font-size: clamp(44px, 8vw, 92px);
line-height: 0.96;
letter-spacing: 0;
}
.landing p {
max-width: 660px;
margin: 0 0 28px;
color: #d7dbe5;
font-size: clamp(16px, 2vw, 21px);
line-height: 1.55;
}
.primary-btn {
border: 1px solid rgba(255, 181, 71, 0.65);
background: #ffb547;
color: #15100a;
min-height: 46px;
padding: 0 18px;
border-radius: 8px;
cursor: pointer;
font-weight: 800;
box-shadow: 0 16px 36px rgba(255, 181, 71, 0.16);
}
.shell {
min-height: 100vh;
display: grid;
grid-template-rows: auto 1fr;
}
header {
position: sticky;
top: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
min-height: 68px;
padding: 12px 18px;
background: rgba(11, 12, 16, 0.92);
border-bottom: 1px solid var(--line);
backdrop-filter: blur(16px);
}
.brand {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
}
.brand-mark {
display: grid;
place-items: center;
width: 42px;
height: 42px;
border-radius: 8px;
background: #ffb547;
color: #0b0c10;
font-weight: 900;
box-shadow: 0 14px 30px rgba(255, 181, 71, 0.14);
flex: 0 0 auto;
}
.brand h2 {
margin: 0;
font-size: 18px;
line-height: 1.1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.brand span {
display: block;
margin-top: 3px;
color: var(--muted);
font-size: 12px;
}
.header-actions {
display: flex;
align-items: center;
gap: 10px;
}
.ghost-btn {
height: 38px;
border: 1px solid var(--line);
border-radius: 8px;
color: var(--text);
background: #15161b;
padding: 0 12px;
cursor: pointer;
}
.grid {
display: grid;
grid-template-columns: 280px minmax(0, 1fr) 340px;
gap: 14px;
padding: 14px;
min-height: calc(100vh - 68px);
}
aside, main, .right-rail {
min-width: 0;
}
.panel {
border: 1px solid var(--line);
border-radius: 8px;
background: var(--panel);
box-shadow: 0 18px 40px var(--shadow);
}
.sidebar {
display: flex;
flex-direction: column;
gap: 14px;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 14px 14px 10px;
border-bottom: 1px solid var(--line);
}
.panel-title {
margin: 0;
font-size: 13px;
color: #dce0ea;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.panel-body {
padding: 14px;
}
label {
display: block;
color: var(--muted);
font-size: 12px;
margin-bottom: 8px;
}
select {
width: 100%;
min-height: 42px;
border: 1px solid var(--line);
border-radius: 8px;
background: #0f1015;
color: var(--text);
padding: 0 12px;
}
.size-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
}
.size-btn {
min-height: 38px;
border: 1px solid var(--line);
border-radius: 8px;
background: #0f1015;
color: var(--text);
cursor: pointer;
font-weight: 700;
}
.size-btn.active {
background: #ffb547;
border-color: #ffb547;
color: #12100c;
}
.metric-stack {
display: grid;
gap: 10px;
}
.metric {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
padding: 10px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.metric:last-child { border-bottom: 0; }
.metric b {
color: var(--text);
font-size: 21px;
}
.metric span {
color: var(--muted);
font-size: 12px;
}
.main-stack {
display: grid;
grid-template-rows: minmax(360px, 48vh) minmax(260px, 1fr);
gap: 14px;
min-height: 0;
}
.chart-wrap {
position: relative;
height: 100%;
min-height: 320px;
padding: 10px 12px 14px;
}
canvas {
width: 100% !important;
height: 100% !important;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 10px 8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
text-align: left;
font-size: 13px;
vertical-align: middle;
}
th {
color: var(--muted);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 11px;
}
td.score {
color: var(--text);
font-weight: 800;
font-variant-numeric: tabular-nums;
}
.leader {
color: var(--amber);
font-weight: 900;
}
.status {
display: inline-flex;
align-items: center;
min-height: 24px;
padding: 0 8px;
border-radius: 999px;
border: 1px solid var(--line);
color: var(--muted);
font-size: 12px;
white-space: nowrap;
}
.status.ok {
color: #dfffe9;
border-color: rgba(114, 220, 141, 0.35);
background: rgba(114, 220, 141, 0.09);
}
.status.warn {
color: #ffe2ad;
border-color: rgba(255, 181, 71, 0.35);
background: rgba(255, 181, 71, 0.09);
}
.model-list {
display: grid;
gap: 10px;
}
.model-card {
border: 1px solid var(--line);
background: var(--panel-2);
border-radius: 8px;
padding: 12px;
cursor: pointer;
}
.model-card.active {
border-color: rgba(255, 181, 71, 0.72);
box-shadow: inset 0 0 0 1px rgba(255, 181, 71, 0.18);
}
.model-card h3 {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin: 0 0 8px;
font-size: 15px;
}
.model-card p {
margin: 0;
color: var(--muted);
font-size: 12px;
line-height: 1.45;
}
.drawer {
position: fixed;
inset: 0 0 0 auto;
z-index: 30;
width: min(520px, 100vw);
transform: translateX(105%);
transition: transform 180ms ease;
border-left: 1px solid var(--line);
background: #111217;
box-shadow: -20px 0 50px rgba(0, 0, 0, 0.35);
padding: 18px;
overflow: auto;
}
.drawer.open { transform: translateX(0); }
.drawer h2 { margin: 0 0 12px; font-size: 26px; }
.drawer p, .drawer li {
color: #c4cad7;
line-height: 1.58;
}
.drawer a { color: var(--amber); }
.error {
margin: 16px;
padding: 14px;
border: 1px solid rgba(255, 107, 122, 0.45);
border-radius: 8px;
background: rgba(255, 107, 122, 0.08);
color: #ffd7dc;
}
@media (max-width: 1180px) {
.grid {
grid-template-columns: 260px minmax(0, 1fr);
}
.right-rail {
grid-column: 1 / -1;
}
.right-rail .panel-body {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 14px;
}
}
@media (max-width: 760px) {
header {
align-items: flex-start;
flex-direction: column;
}
.header-actions {
width: 100%;
}
.ghost-btn, .primary-btn {
flex: 1;
}
.grid {
grid-template-columns: 1fr;
padding: 10px;
}
.main-stack {
grid-template-rows: 360px auto;
}
.right-rail .panel-body {
display: block;
}
.landing h1 {
font-size: clamp(40px, 14vw, 64px);
}
}
</style>
</head>
<body>
<section class="landing" id="landing">
<div class="landing-inner">
<div class="eyebrow">Small data benchmark arena</div>
<h1>TabFM: Small Data Champion</h1>
<p>Google's tabular foundation model faces XGBoost, LightGBM, Random Forest, and a linear baseline on tiny tabular training sets.</p>
<button class="primary-btn" id="enterBtn">Enter arena</button>
</div>
</section>
<div class="shell">
<header>
<div class="brand">
<div class="brand-mark">TF</div>
<div>
<h2>TabFM Small Data Champion</h2>
<span id="runMeta">Loading benchmark...</span>
</div>
</div>
<div class="header-actions">
<button class="ghost-btn" id="aboutBtn">About</button>
<a class="primary-btn" style="display:inline-grid;place-items:center;text-decoration:none;" href="https://huggingface.co/google/tabfm-1.0.0-pytorch" target="_blank" rel="noreferrer">Model</a>
</div>
</header>
<div id="errorBox" class="error" hidden></div>
<div class="grid" id="appGrid" hidden>
<aside class="sidebar">
<section class="panel">
<div class="panel-header">
<h3 class="panel-title">Dataset</h3>
</div>
<div class="panel-body">
<label for="datasetSelect">Benchmark table</label>
<select id="datasetSelect"></select>
<div class="metric-stack" style="margin-top:14px;">
<div class="metric"><span>Rows</span><b id="datasetRows">-</b></div>
<div class="metric"><span>Features</span><b id="datasetFeatures">-</b></div>
<div class="metric"><span>Task</span><b id="datasetTask">-</b></div>
</div>
</div>
</section>
<section class="panel">
<div class="panel-header">
<h3 class="panel-title">Sample Size</h3>
</div>
<div class="panel-body">
<div class="size-grid" id="sizeGrid"></div>
</div>
</section>
<section class="panel">
<div class="panel-header">
<h3 class="panel-title">Run Status</h3>
</div>
<div class="panel-body">
<div class="metric"><span>TabFM</span><b id="tabfmState">-</b></div>
<div class="metric"><span>Rows</span><b id="rowCount">-</b></div>
<div class="metric"><span>Startup</span><b id="elapsedMs">-</b></div>
</div>
</section>
</aside>
<main class="main-stack">
<section class="panel">
<div class="panel-header">
<h3 class="panel-title" id="lineTitle">Accuracy vs Sample Size</h3>
<span class="status" id="metricBadge">accuracy</span>
</div>
<div class="chart-wrap">
<canvas id="lineChart"></canvas>
</div>
</section>
<section class="panel">
<div class="panel-header">
<h3 class="panel-title">Detailed Results</h3>
<span class="status" id="selectedModelBadge">All models</span>
</div>
<div class="panel-body" style="overflow:auto;">
<table>
<thead>
<tr>
<th>Size</th>
<th>Model</th>
<th>Score</th>
<th>F1 / R2</th>
<th>Fit ms</th>
<th>Infer ms</th>
<th>Status</th>
</tr>
</thead>
<tbody id="detailRows"></tbody>
</table>
</div>
</section>
</main>
<aside class="right-rail">
<section class="panel" style="height:100%;">
<div class="panel-header">
<h3 class="panel-title">Leaderboard</h3>
</div>
<div class="panel-body">
<div style="overflow:auto;">
<table>
<thead>
<tr><th>Rank</th><th>Model</th><th>Score</th><th>Status</th></tr>
</thead>
<tbody id="leaderRows"></tbody>
</table>
</div>
<div class="chart-wrap" style="height:240px;min-height:240px;padding:20px 0 0;">
<canvas id="winChart"></canvas>
</div>
<div class="model-list" id="modelList" style="margin-top:14px;"></div>
</div>
</section>
</aside>
</div>
</div>
<aside class="drawer" id="aboutDrawer">
<button class="ghost-btn" id="closeAbout" style="float:right;">Close</button>
<h2>About TabFM</h2>
<p>TabFM is a tabular foundation model from Google Research. It uses the training rows as in-context examples instead of learning dataset-specific weights for each benchmark split.</p>
<p>This Space installs TabFM from the official GitHub repository and attempts the real <a href="https://huggingface.co/google/tabfm-1.0.0-pytorch" target="_blank" rel="noreferrer">google/tabfm-1.0.0-pytorch</a> checkpoint during startup. The model artifact is large, so rows that exceed the cpu-basic startup budget are labelled directly in the tables.</p>
<ul>
<li>Classification metric: accuracy, with weighted F1 and ROC-AUC when available.</li>
<li>Regression metric: R2, with RMSE and MAE in detailed rows.</li>
<li>Competitors: XGBoost, LightGBM, Random Forest, and a linear baseline.</li>
</ul>
</aside>
<script>
const COLORS = {
"TabFM": "#ffb547",
"XGBoost": "#3dd6c6",
"LightGBM": "#72dc8d",
"Random Forest": "#a78bfa",
"Linear Baseline": "#ff6b7a"
};
const state = {
payload: null,
datasetId: null,
sampleSize: null,
selectedModel: null,
lineChart: null,
winChart: null
};
const formatScore = (value, metric) => {
if (value === null || value === undefined || Number.isNaN(Number(value))) return "-";
if (metric === "rmse" || metric === "mae") return Number(value).toFixed(3);
return Number(value).toFixed(3);
};
const formatMs = value => value === null || value === undefined ? "-" : Number(value).toFixed(1);
const statusClass = status => status === "ok" ? "ok" : "warn";
const datasetById = id => state.payload.datasets.find(item => item.id === id);
const rowsForDataset = () => state.payload.rows.filter(row => row.dataset_id === state.datasetId);
const rowsForSelection = () => rowsForDataset().filter(row => Number(row.sample_size) === Number(state.sampleSize));
document.getElementById("enterBtn").addEventListener("click", () => {
document.getElementById("landing").classList.add("hidden");
});
document.getElementById("aboutBtn").addEventListener("click", () => {
document.getElementById("aboutDrawer").classList.add("open");
});
document.getElementById("closeAbout").addEventListener("click", () => {
document.getElementById("aboutDrawer").classList.remove("open");
});
async function loadPayload() {
const response = await fetch("/api/benchmark-results");
if (!response.ok) throw new Error(`Benchmark API returned ${response.status}`);
state.payload = await response.json();
state.datasetId = state.payload.datasets[0].id;
state.sampleSize = state.payload.datasets[0].sample_sizes[0];
state.selectedModel = null;
document.getElementById("appGrid").hidden = false;
renderAll();
}
function renderAll() {
renderHeader();
renderDatasetControls();
renderDatasetStats();
renderSizeButtons();
renderLineChart();
renderLeaderboard();
renderWinChart();
renderModelCards();
renderDetails();
}
function renderHeader() {
const p = state.payload;
document.getElementById("runMeta").textContent = `${p.benchmark_mode} | ${new Date(p.generated_at_unix * 1000).toLocaleString()}`;
document.getElementById("tabfmState").textContent = p.tabfm.status || "unknown";
document.getElementById("rowCount").textContent = p.rows.length;
document.getElementById("elapsedMs").textContent = `${Math.round(p.elapsed_ms)} ms`;
}
function renderDatasetControls() {
const select = document.getElementById("datasetSelect");
select.innerHTML = state.payload.datasets.map(ds => `<option value="${ds.id}">${ds.name}</option>`).join("");
select.value = state.datasetId;
select.onchange = event => {
state.datasetId = event.target.value;
const ds = datasetById(state.datasetId);
state.sampleSize = ds.sample_sizes[0];
renderAll();
};
}
function renderDatasetStats() {
const ds = datasetById(state.datasetId);
document.getElementById("datasetRows").textContent = ds.rows;
document.getElementById("datasetFeatures").textContent = ds.features;
document.getElementById("datasetTask").textContent = ds.task === "classification" ? "Class" : "Reg";
document.getElementById("lineTitle").textContent = `${ds.name}: ${ds.task === "classification" ? "Accuracy" : "R2"} vs Sample Size`;
document.getElementById("metricBadge").textContent = ds.task === "classification" ? "accuracy" : "r2";
}
function renderSizeButtons() {
const ds = datasetById(state.datasetId);
const grid = document.getElementById("sizeGrid");
grid.innerHTML = ds.sample_sizes.map(size => `
<button class="size-btn ${Number(size) === Number(state.sampleSize) ? "active" : ""}" data-size="${size}">${size}</button>
`).join("");
grid.querySelectorAll("button").forEach(btn => {
btn.addEventListener("click", () => {
state.sampleSize = Number(btn.dataset.size);
renderAll();
});
});
}
function renderLineChart() {
const ds = datasetById(state.datasetId);
const sizes = ds.sample_sizes;
const datasets = state.payload.models.map(model => {
const points = sizes.map(size => {
const row = state.payload.rows.find(item =>
item.dataset_id === state.datasetId &&
item.model_name === model.model_name &&
Number(item.sample_size) === Number(size)
);
return row && row.status === "ok" ? row.primary_score : null;
});
return {
label: model.model_name,
data: points,
borderColor: COLORS[model.model_name],
backgroundColor: COLORS[model.model_name],
pointRadius: 4,
borderWidth: model.model_name === "TabFM" ? 4 : 2,
tension: 0.25,
spanGaps: false
};
});
const ctx = document.getElementById("lineChart");
if (state.lineChart) state.lineChart.destroy();
state.lineChart = new Chart(ctx, {
type: "line",
data: { labels: sizes, datasets },
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: "nearest", intersect: false },
scales: {
x: { grid: { color: "rgba(255,255,255,0.06)" }, ticks: { color: "#a6adbb" }, title: { display: true, text: "Training rows", color: "#a6adbb" } },
y: { grid: { color: "rgba(255,255,255,0.06)" }, ticks: { color: "#a6adbb" }, suggestedMin: 0, suggestedMax: 1 }
},
plugins: {
legend: { labels: { color: "#dce0ea", usePointStyle: true, boxWidth: 8 } },
tooltip: { callbacks: { label: ctx => `${ctx.dataset.label}: ${formatScore(ctx.parsed.y)}` } }
}
}
});
}
function renderLeaderboard() {
const rows = rowsForSelection().slice().sort((a, b) => {
const av = a.primary_score === null ? -Infinity : Number(a.primary_score);
const bv = b.primary_score === null ? -Infinity : Number(b.primary_score);
return bv - av;
});
const body = document.getElementById("leaderRows");
body.innerHTML = rows.map((row, index) => `
<tr>
<td class="${index === 0 && row.status === "ok" ? "leader" : ""}">${row.status === "ok" ? index + 1 : "-"}</td>
<td>${row.model_name}</td>
<td class="score">${formatScore(row.primary_score, row.primary_metric)}</td>
<td><span class="status ${statusClass(row.status)}">${row.status}</span></td>
</tr>
`).join("");
}
function renderWinChart() {
const labels = state.payload.win_rates.map(row => row.model_name);
const values = state.payload.win_rates.map(row => Math.round(row.win_rate * 1000) / 10);
const colors = labels.map(label => COLORS[label]);
const ctx = document.getElementById("winChart");
if (state.winChart) state.winChart.destroy();
state.winChart = new Chart(ctx, {
type: "bar",
data: { labels, datasets: [{ label: "Win rate", data: values, backgroundColor: colors, borderWidth: 0 }] },
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: { grid: { display: false }, ticks: { color: "#a6adbb" } },
y: { grid: { color: "rgba(255,255,255,0.06)" }, ticks: { color: "#a6adbb", callback: value => `${value}%` }, suggestedMin: 0, suggestedMax: 100 }
},
plugins: { legend: { display: false } }
}
});
}
function renderModelCards() {
const list = document.getElementById("modelList");
list.innerHTML = state.payload.models.map(model => {
const isActive = state.selectedModel === model.model_name;
const score = model.average_primary_score === null ? "-" : Number(model.average_primary_score).toFixed(3);
const status = model.model_name === "TabFM" ? model.runtime_status : "ok";
return `
<article class="model-card ${isActive ? "active" : ""}" data-model="${model.model_name}">
<h3><span>${model.model_name}</span><span class="status ${statusClass(status)}">${status}</span></h3>
<p>${model.type} | avg score ${score} | measured rows ${model.measured_rows}</p>
</article>
`;
}).join("");
list.querySelectorAll(".model-card").forEach(card => {
card.addEventListener("click", () => {
const model = card.dataset.model;
state.selectedModel = state.selectedModel === model ? null : model;
renderModelCards();
renderDetails();
});
});
}
function renderDetails() {
document.getElementById("selectedModelBadge").textContent = state.selectedModel || "All models";
let rows = rowsForDataset().slice().sort((a, b) => Number(a.sample_size) - Number(b.sample_size) || a.model_name.localeCompare(b.model_name));
if (state.selectedModel) rows = rows.filter(row => row.model_name === state.selectedModel);
const body = document.getElementById("detailRows");
body.innerHTML = rows.map(row => {
const secondary = row.task === "classification" ? row.f1 : row.r2;
const title = row.note ? ` title="${String(row.note).replaceAll('"', "&quot;")}"` : "";
return `
<tr${title}>
<td>${row.sample_size}</td>
<td>${row.model_name}</td>
<td class="score">${formatScore(row.primary_score, row.primary_metric)}</td>
<td>${formatScore(secondary)}</td>
<td>${formatMs(row.train_time_ms)}</td>
<td>${formatMs(row.inference_time_ms)}</td>
<td><span class="status ${statusClass(row.status)}">${row.status}</span></td>
</tr>
`;
}).join("");
}
loadPayload().catch(error => {
const box = document.getElementById("errorBox");
box.hidden = false;
box.textContent = error.message;
});
</script>
</body>
</html>
"""
if __name__ == "__main__":
app.launch(
server_name="0.0.0.0",
server_port=int(os.environ.get("PORT", "7860")),
quiet=True,
)