Spaces:
Running
Running
| import re | |
| import json | |
| from pathlib import Path | |
| from typing import Dict, List | |
| import pandas as pd | |
| from huggingface_hub import HfApi, hf_hub_download | |
| # Task to primary metric mapping | |
| TASK_METRICS = { | |
| "armenian:finer|0": "ner_accuracy", | |
| "armenian:pioner|0": "ner_accuracy", | |
| "armenian:pos|0": "ud_pos_regex_acc", | |
| "armenian:squad|0": "bleu", | |
| "armenian:belebele|0": "exact_match_mcqa", | |
| "armenian:hartak|0": "exact_match_mcqa", | |
| "armenian:include|0": "exact_match_mcqa", | |
| "armenian:syndarin|0": "exact_match_mcqa", | |
| "armenian:dream|0": "exact_match_mcqa", | |
| "armenian:topic-14class|0": "exact_match_mcqa", | |
| "armenian:scientific|0": "exact_match_mcqa", | |
| "armenian:sentiment|0": "exact_match_mcqa", | |
| "armenian:exam_math|0": "armenian_exam_score", | |
| "armenian:exam_literature|0": "armenian_exam_score", | |
| "armenian:exam_history|0": "armenian_exam_score", | |
| "armenian:email|0": "bleu", | |
| "armenian:short_sentences_translation|0": "bleu", | |
| "armenian:conversation|0": "bleu", | |
| "armenian:arak|0": "bleu", | |
| "armenian:paraphrase|0": "bleu", | |
| "armenian:ms_marco|0": "bleu", | |
| "armenian:mmlu_pro|0": "armenian_mmlu_pro_score", | |
| "armenian:punctuation|0": "punctuation_accuracy", | |
| "armenian:space_fix|0": "space_accuracy", | |
| } | |
| # Task categories for grouping | |
| TASK_CATEGORIES = { | |
| "NER": ["armenian:finer|0", "armenian:pioner|0"], | |
| "POS": ["armenian:pos|0"], | |
| "Reading Comprehension": [ | |
| "armenian:squad|0", | |
| "armenian:belebele|0", | |
| "armenian:dream|0", | |
| "armenian:hartak|0", | |
| "armenian:ms_marco|0", | |
| ], | |
| "Classification": [ | |
| "armenian:topic-14class|0", | |
| "armenian:sentiment|0" | |
| ], | |
| "MCQA": [ "armenian:include|0", | |
| "armenian:syndarin|0","armenian:scientific|0"] | |
| , | |
| "Generation": [ | |
| "armenian:email|0", | |
| "armenian:conversation|0", | |
| "armenian:arak|0", | |
| "armenian:paraphrase|0", | |
| ], | |
| "Translation": ["armenian:short_sentences_translation|0"], | |
| "Exams": [ | |
| "armenian:exam_math|0", | |
| "armenian:exam_literature|0", | |
| "armenian:exam_history|0", | |
| ], | |
| "Text Processing": ["armenian:punctuation|0", "armenian:space_fix|0"], | |
| "MMLU": ["armenian:mmlu_pro|0"], | |
| } | |
| # Short display names for tasks | |
| TASK_DISPLAY_NAMES = { | |
| "armenian:finer|0": "FiNER", | |
| "armenian:pioner|0": "PioNER", | |
| "armenian:pos|0": "POS", | |
| "armenian:squad|0": "SQuAD", | |
| "armenian:belebele|0": "Belebele", | |
| "armenian:hartak|0": "Hartak - Public Services MCQA", | |
| "armenian:include|0": "INCLUDE", | |
| "armenian:syndarin|0": "Syndarin", | |
| "armenian:dream|0": "DREAM", | |
| "armenian:topic-14class|0": "Topic-14", | |
| "armenian:scientific|0": "Scientific", | |
| "armenian:sentiment|0": "Sentiment", | |
| "armenian:exam_math|0": "Math Exam", | |
| "armenian:exam_literature|0": "Lit Exam", | |
| "armenian:exam_history|0": "Hist Exam", | |
| "armenian:email|0": "Email Summary", | |
| "armenian:short_sentences_translation|0": "Short Trans", | |
| "armenian:conversation|0": "Conversation Summary", | |
| "armenian:arak|0": "Simple QA", | |
| "armenian:paraphrase|0": "Paraphrase", | |
| "armenian:ms_marco|0": "MS MARCO", | |
| "armenian:mmlu_pro|0": "MMLU-Pro", | |
| "armenian:punctuation|0": "Punctuation", | |
| "armenian:space_fix|0": "Space Fix", | |
| } | |
| class ModelHandler: | |
| def __init__(self, local_results_folder: str = "results"): | |
| print("[DEBUG] ModelHandler.__init__: Creating HfApi()...") | |
| self.api = HfApi() | |
| print("[DEBUG] ModelHandler.__init__: HfApi created") | |
| self._cache = {} # Simple cache for fetched results | |
| self.local_results_folder = Path(local_results_folder) | |
| print("[DEBUG] ModelHandler.__init__: Initialization complete") | |
| def _parse_lighteval_results(self, results: Dict) -> Dict: | |
| """Parse lighteval format results into structured format. | |
| Keeps raw scores for display (0-20 for exams, 0-100 for BLEU), | |
| but normalizes to 0-1 when averaging. | |
| """ | |
| parsed = { | |
| "tasks": {}, | |
| "categories": {}, | |
| } | |
| task_results = results.get("results", {}) | |
| for task_key, metrics in task_results.items(): | |
| if task_key in ["all", "armenian:_average|0"]: | |
| continue | |
| if task_key in TASK_METRICS: | |
| primary_metric = TASK_METRICS[task_key] | |
| if primary_metric in metrics: | |
| display_name = TASK_DISPLAY_NAMES.get(task_key, task_key) | |
| value = metrics[primary_metric] | |
| # Keep raw values for display | |
| parsed["tasks"][display_name] = value | |
| # Calculate category averages | |
| for category, tasks in TASK_CATEGORIES.items(): | |
| scores = [] | |
| for task in tasks: | |
| display_name = TASK_DISPLAY_NAMES.get(task, task) | |
| if display_name in parsed["tasks"]: | |
| value = parsed["tasks"][display_name] | |
| primary_metric = TASK_METRICS.get(task) | |
| # Normalize BLEU to 0-1, keep exam scores as 0-20 | |
| if primary_metric == "bleu": | |
| value = value / 100.0 | |
| scores.append(value) | |
| if scores: | |
| parsed["categories"][category] = sum(scores) / len(scores) | |
| # Calculate overall average as average of category averages | |
| # Normalize exam categories (0-20) to 0-1 for final average | |
| if parsed["categories"]: | |
| category_scores = [] | |
| for category, avg_score in parsed["categories"].items(): | |
| # Check if this category contains only exam tasks | |
| tasks_in_category = TASK_CATEGORIES.get(category, []) | |
| all_exam_tasks = all( | |
| TASK_METRICS.get(task) == "armenian_exam_score" | |
| for task in tasks_in_category | |
| ) | |
| # Normalize exam categories from 0-20 to 0-1 | |
| if all_exam_tasks: | |
| avg_score = avg_score / 20.0 | |
| category_scores.append(avg_score) | |
| parsed["average"] = sum(category_scores) / len(category_scores) | |
| return parsed | |
| def _fetch_results_from_repo(self, repo_id: str) -> Dict: | |
| """Fetch results.json from a single HuggingFace repository.""" | |
| # Check cache first | |
| if repo_id in self._cache: | |
| return self._cache[repo_id] | |
| try: | |
| result_path = hf_hub_download( | |
| repo_id, filename="results.json", cache_dir=".hf_cache" | |
| ) | |
| with open(result_path) as f: | |
| results = json.load(f) | |
| self._cache[repo_id] = results | |
| return results | |
| except FileNotFoundError: | |
| print(f" No results.json found in {repo_id}") | |
| return None | |
| except Exception as e: | |
| print(f" Error fetching results from {repo_id}: {e}") | |
| return None | |
| def _is_openrouter_only(self, repo_id: str) -> bool: | |
| """Check if a repository is OpenRouter-only.""" | |
| return repo_id.lower().startswith("openrouter/") | |
| def _extract_model_name(self, results: Dict) -> str: | |
| """Extract model name from results, preferring model_config.model_name.""" | |
| # Try to get from model_config first | |
| try: | |
| model_name = ( | |
| results.get("config_general", {}) | |
| .get("model_config", {}) | |
| .get("model_name", "") | |
| ) | |
| if model_name: | |
| # Remove 'openrouter/' prefix if present and keep only the model part | |
| if "/" in model_name: | |
| model_name = "/".join(model_name.split("/")[-2:]) | |
| return model_name | |
| except (KeyError, AttributeError, TypeError): | |
| pass | |
| return None | |
| def _load_local_results(self) -> List[tuple]: | |
| """Load results from local results folder. Returns list of (model_name, results) tuples.""" | |
| local_models = [] | |
| if not self.local_results_folder.exists(): | |
| return local_models | |
| print(f"\nLoading local results from {self.local_results_folder}...") | |
| json_files = list(self.local_results_folder.glob("*.json")) | |
| if not json_files: | |
| print(f" No JSON files found in {self.local_results_folder}") | |
| return local_models | |
| print(f" Found {len(json_files)} local files\n") | |
| for json_file in sorted(json_files): | |
| try: | |
| with open(json_file) as f: | |
| results = json.load(f) | |
| if "results" in results: | |
| # Extract model name from config | |
| model_name = self._extract_model_name(results) | |
| if not model_name: | |
| # Fallback to filename stem | |
| model_name = json_file.stem | |
| local_models.append((model_name, results)) | |
| print(f" β Loaded {model_name} from {json_file.name}") | |
| else: | |
| print(f" β Invalid format in {json_file.name}") | |
| except json.JSONDecodeError: | |
| print(f" β Invalid JSON in {json_file.name}") | |
| except Exception as e: | |
| print(f" β Error reading {json_file.name}: {e}") | |
| return local_models | |
| def get_llm_benchmark_data(self) -> pd.DataFrame: | |
| """Fetch LLM benchmark results from HuggingFace and local results folder.""" | |
| data = [] | |
| # Fetch from HuggingFace | |
| print("[DEBUG] get_llm_benchmark_data: Starting...") | |
| print("Fetching models with ArmBench-LLM tag from HuggingFace...") | |
| try: | |
| print("[DEBUG] get_llm_benchmark_data: Calling api.list_models()...") | |
| models = self.api.list_models(filter="ArmBench-LLM") | |
| print("[DEBUG] get_llm_benchmark_data: api.list_models() returned") | |
| repositories = [model.modelId for model in models] | |
| print(f"Found {len(repositories)} models\n") | |
| print( | |
| f"[DEBUG] get_llm_benchmark_data: Processing {len(repositories)} repositories" | |
| ) | |
| for i, repo_id in enumerate(repositories, 1): | |
| # Skip OpenRouter-only models | |
| if self._is_openrouter_only(repo_id): | |
| print( | |
| f"[{i}/{len(repositories)}] Skipping OpenRouter-only: {repo_id}" | |
| ) | |
| continue | |
| print(f"[{i}/{len(repositories)}] Fetching {repo_id}...") | |
| results = self._fetch_results_from_repo(repo_id) | |
| if results and "results" in results: | |
| parsed = self._parse_lighteval_results(results) | |
| row = {"model_name": repo_id} | |
| model_size = self._get_model_size(model_name) | |
| row.update(parsed.get("categories", {})) | |
| row["Size"] = model_size | |
| if "average" in parsed: | |
| row["Average"] = parsed["average"] | |
| if len(row) > 1: | |
| data.append(row) | |
| print(" β Added to leaderboard") | |
| else: | |
| print(" β Invalid results format") | |
| else: | |
| print(" β Could not fetch or parse results") | |
| except Exception as e: | |
| print(f"Error fetching from HuggingFace: {e}") | |
| print(f"[DEBUG] get_llm_benchmark_data: Exception during HF fetch: {e}") | |
| # Load local results | |
| print("[DEBUG] get_llm_benchmark_data: Loading local results...") | |
| local_models = self._load_local_results() | |
| for model_name, results in local_models: | |
| parsed = self._parse_lighteval_results(results) | |
| model_size = self._get_model_size(model_name) | |
| row = {"model_name": model_name} | |
| row.update(parsed.get("categories", {})) | |
| row["Size"] = model_size | |
| if "average" in parsed: | |
| row["Average"] = parsed["average"] | |
| if len(row) > 1: | |
| # Remove if already exists from HuggingFace (local overrides) | |
| data = [m for m in data if m["model_name"] != model_name] | |
| data.append(row) | |
| print(f" β Added {model_name} to leaderboard\n") | |
| print( | |
| f"[DEBUG] get_llm_benchmark_data: Returning DataFrame with {len(data)} rows" | |
| ) | |
| return pd.DataFrame(data) | |
| def get_detailed_results(self) -> Dict[str, pd.DataFrame]: | |
| """Get detailed task-level results for all models (HuggingFace + local).""" | |
| print("[DEBUG] get_detailed_results: Starting...") | |
| print("Fetching detailed results from HuggingFace models...") | |
| detailed_data = [] | |
| # Fetch from HuggingFace | |
| try: | |
| print("[DEBUG] get_detailed_results: Calling api.list_models()...") | |
| models = self.api.list_models(filter="ArmBench-LLM") | |
| print("[DEBUG] get_detailed_results: api.list_models() returned") | |
| repositories = [model.modelId for model in models] | |
| print(f"Found {len(repositories)} models\n") | |
| print( | |
| f"[DEBUG] get_detailed_results: Processing {len(repositories)} repositories" | |
| ) | |
| for i, repo_id in enumerate(repositories, 1): | |
| # Skip OpenRouter-only models | |
| if self._is_openrouter_only(repo_id): | |
| print( | |
| f"[{i}/{len(repositories)}] Skipping OpenRouter-only: {repo_id}" | |
| ) | |
| continue | |
| print(f"[{i}/{len(repositories)}] Processing {repo_id}...") | |
| results = self._fetch_results_from_repo(repo_id) | |
| if results and "results" in results: | |
| parsed = self._parse_lighteval_results(results) | |
| row = {"model_name": repo_id} | |
| row.update(parsed.get("tasks", {})) | |
| if len(row) > 1: | |
| detailed_data.append(row) | |
| print(f" β Added {len(parsed.get('tasks', {}))} tasks") | |
| else: | |
| print(" β No valid tasks") | |
| else: | |
| print(" β Could not fetch results") | |
| except Exception as e: | |
| print(f"Error fetching detailed results: {e}") | |
| print(f"[DEBUG] get_detailed_results: Exception during HF fetch: {e}") | |
| # Load local results | |
| print("[DEBUG] get_detailed_results: Loading local results...") | |
| local_models = self._load_local_results() | |
| for model_name, results in local_models: | |
| parsed = self._parse_lighteval_results(results) | |
| row = {"model_name": model_name} | |
| row.update(parsed.get("tasks", {})) | |
| if len(row) > 1: | |
| # Remove if already exists from HuggingFace (local overrides) | |
| detailed_data = [ | |
| m for m in detailed_data if m["model_name"] != model_name | |
| ] | |
| detailed_data.append(row) | |
| print( | |
| f" β Added {model_name} with {len(parsed.get('tasks', {}))} tasks\n" | |
| ) | |
| print( | |
| f"[DEBUG] get_detailed_results: Returning with {len(detailed_data)} models" | |
| ) | |
| return { | |
| "tasks": pd.DataFrame(detailed_data) if detailed_data else pd.DataFrame(), | |
| } | |
| def _get_model_size(self, model_id: str) -> str: | |
| csv_path = "model_sizes.csv" | |
| csv_file = Path(csv_path) | |
| size = None | |
| if csv_file.is_file(): | |
| df = pd.read_csv(csv_file) | |
| else: | |
| df = pd.DataFrame(columns=["Model Name", "Size"]) | |
| try: | |
| if "Model Name" in df.columns and "Size" in df.columns: | |
| matching = df[df["Model Name"] == model_id] | |
| if not matching.empty: | |
| size = matching["Size"].iloc[0].strip() | |
| except Exception as e: | |
| print(f"Warning: Could not read {csv_path} ({e})") | |
| lower_id = model_id.lower().replace("/", "-") | |
| patterns = [ | |
| r"(\d+(?:\.\d+)?)[bB](?:[-_]([a-z]?\d+(?:\.\d+)?[bB]?|[a-z]+\d+[bB]?|[0-9]+x[0-9]+[bB]?))?", | |
| ] | |
| if size is None: | |
| for pattern in patterns: | |
| match = re.search(pattern, lower_id) | |
| if match: | |
| total = match.group(1) | |
| suffix = match.group(2) | |
| if suffix: | |
| suffix = suffix.upper() | |
| if ( | |
| len(suffix) <= 8 | |
| and not suffix.isdigit() | |
| and not suffix in ["INSTRUCT", "IT", "V2", "BASE"] | |
| ): | |
| size = f"{total}B-{suffix}" | |
| else: | |
| size = f"{float(total):g}B" | |
| else: | |
| size = f"{float(total):g}B" | |
| break | |
| if size is None: | |
| try: | |
| api = HfApi() | |
| info = api.model_info(model_id, timeout=10) | |
| if hasattr(info, "cardData") and info.cardData: | |
| card = info.cardData | |
| for key in [ | |
| "parameters", | |
| "num_parameters", | |
| "total_params", | |
| "model_size", | |
| "params", | |
| ]: | |
| if key in card: | |
| val = card[key] | |
| if isinstance(val, (int, float)) and val > 1000: | |
| size = f"{val / 1e-9:g}B" | |
| break | |
| if isinstance(val, str): | |
| m = re.search(r"(\d+(?:\.\d+)?)\s*[bB]", val.lower()) | |
| if m: | |
| size = f"{float(m.group(1)):g}B" | |
| break | |
| if ( | |
| size is None | |
| and hasattr(info, "config") | |
| and isinstance(info.config, dict) | |
| ): | |
| cfg = info.config | |
| if "num_parameters" in cfg and isinstance( | |
| cfg["num_parameters"], (int, float) | |
| ): | |
| size = f"{cfg['num_parameters'] / 1e-9:g}B" | |
| except Exception: | |
| pass | |
| if size is None: | |
| size = " - " | |
| try: | |
| if model_id not in df["Model Name"].values: | |
| new_row = pd.DataFrame({"Model Name": [model_id], "Size": [size]}) | |
| df = pd.concat([df, new_row], ignore_index=True) | |
| df.to_csv(csv_file, index=False) | |
| print(f"Appended {model_id} β {size} to {csv_path}") | |
| except Exception as e: | |
| print(f"Warning: Could not append to {csv_path} ({e})") | |
| return size | |