Download evaluate.py from 56m/SmallMultiplicationLeaderboard: direct link, hf CLI and curl.
- Browser
- Download file 17.1 kB
-
https://huggingface.co/spaces/56m/SmallMultiplicationLeaderboard/resolve/main/evaluate.py
- Command line
-
hf download hf://spaces/56m/SmallMultiplicationLeaderboard/evaluate.py
-
curl -L -o evaluate.py https://huggingface.co/spaces/56m/SmallMultiplicationLeaderboard/resolve/main/evaluate.py
17.1 kB
| # ============================================================================== | |
| # Multiplication SLM Benchmark Suite (Interactive TUI Wizard) | |
| # Dynamic Dataset Generator corresponding to 'cot_partial', 'cot_direct', 'cot_cot' | |
| # ============================================================================== | |
| import json | |
| import math | |
| import os | |
| import random | |
| import subprocess | |
| import sys | |
| import torch | |
| import torch.nn.functional as F | |
| from torch.utils.data import DataLoader, Dataset | |
| # ------------------------------------------------------------------------------ | |
| # 1. Install & Import Rich | |
| # ------------------------------------------------------------------------------ | |
| try: | |
| from rich.align import Align | |
| from rich import box | |
| from rich.console import Console | |
| from rich.panel import Panel | |
| from rich.progress import ( | |
| BarColumn, | |
| Progress, | |
| SpinnerColumn, | |
| TextColumn, | |
| TimeRemainingColumn, | |
| ) | |
| from rich.prompt import Prompt | |
| from rich.table import Table | |
| except ImportError: | |
| subprocess.check_call( | |
| [sys.executable, "-m", "pip", "install", "rich", "-q"] | |
| ) | |
| from rich.align import Align | |
| from rich import box | |
| from rich.console import Console | |
| from rich.panel import Panel | |
| from rich.progress import ( | |
| BarColumn, | |
| Progress, | |
| SpinnerColumn, | |
| TextColumn, | |
| TimeRemainingColumn, | |
| ) | |
| from rich.prompt import Prompt | |
| from rich.table import Table | |
| from transformers import GPT2LMHeadModel | |
| console = Console() | |
| # ------------------------------------------------------------------------------ | |
| # 2. Tokenizer Definition | |
| # ------------------------------------------------------------------------------ | |
| DEFAULT_CHARS = sorted( | |
| list( | |
| set( | |
| "0123456789 *+=:\nQStepsAnsLet'sthinkstepby" | |
| + "<|endoftext|>" | |
| + "<|pad|>" | |
| ) | |
| ) | |
| ) | |
| class CharTokenizer: | |
| def __init__(self, chars=None): | |
| if chars is None: | |
| chars = DEFAULT_CHARS | |
| self.chars = sorted(list(set(chars))) | |
| if "<|pad|>" not in self.chars: | |
| self.chars.append("<|pad|>") | |
| self.c2i = {ch: i for i, ch in enumerate(self.chars)} | |
| self.i2c = {i: ch for i, ch in enumerate(self.chars)} | |
| self.pad_id = self.c2i["<|pad|>"] | |
| self.vocab_size = len(self.chars) | |
| def load(cls, filepath): | |
| with open(filepath, "r", encoding="utf-8") as f: | |
| data = json.load(f) | |
| return cls(data["chars"]) | |
| def encode(self, text): | |
| return [self.c2i[c] for c in text if c in self.c2i] | |
| def decode(self, ids): | |
| return "".join( | |
| [self.i2c[i] for i in ids if i in self.i2c and i != self.pad_id] | |
| ) | |
| # ------------------------------------------------------------------------------ | |
| # 3. Interactive TUI Wizard | |
| # ------------------------------------------------------------------------------ | |
| console.clear() | |
| console.print() | |
| console.print( | |
| Panel( | |
| Align.center( | |
| "[bold yellow]🤗 Hugging Face Micro-SLM Benchmark Harness[/bold yellow]\n" | |
| "[dim]10,000 Qs Log-Likelihood & Exact Match | 500 Qs Perplexity (PPL)\n" | |
| "Rule: Strictly for Small Models (≤ 250M Parameters)[/dim]" | |
| ), | |
| box=box.ROUNDED, | |
| border_style="yellow", | |
| padding=(1, 2), | |
| ) | |
| ) | |
| # Step 1: Model Directory | |
| candidate_paths = [ | |
| d | |
| for d in ["./slm_mult_model", "./slm_mult_checkpoints", "."] | |
| if os.path.exists(os.path.join(d, "config.json")) | |
| or os.path.exists(os.path.join(d, "tokenizer.json")) | |
| ] | |
| detected_hint = ( | |
| f"[green]Found model at:[/green] [bold]{candidate_paths[0]}[/bold]" | |
| if candidate_paths | |
| else "[dim]No model detected in current folder.[/dim]" | |
| ) | |
| input_dialog = Table.grid(padding=1) | |
| input_dialog.add_column(style="bold cyan") | |
| input_dialog.add_column() | |
| input_dialog.add_row("Auto Detected:", detected_hint) | |
| input_dialog.add_row("Default Path:", "[bold yellow]./slm_mult_model[/bold yellow]") | |
| console.print( | |
| Panel( | |
| input_dialog, | |
| title="[bold white] 📁 Step 1/4: Model Directory [/bold white]", | |
| title_align="left", | |
| border_style="cyan", | |
| box=box.ROUNDED, | |
| ) | |
| ) | |
| while True: | |
| model_dir = Prompt.ask( | |
| " [bold cyan]➤ Model Directory Path[/bold cyan]", | |
| default=candidate_paths[0] if candidate_paths else "./slm_mult_model", | |
| ) | |
| if os.path.exists(model_dir): | |
| break | |
| console.print("[bold red]Directory not found! Please try again.[/bold red]") | |
| # Step 2: Model Display Name | |
| default_name = f"YourOrg/{os.path.basename(os.path.abspath(model_dir))}" | |
| console.print() | |
| console.print( | |
| Panel( | |
| "[dim]Enter the model name for the leaderboard (e.g. OrgName/ModelName).[/dim]", | |
| title="[bold white] 🏷️ Step 2/4: Model ID / Display Name [/bold white]", | |
| title_align="left", | |
| border_style="cyan", | |
| box=box.ROUNDED, | |
| ) | |
| ) | |
| model_name = Prompt.ask( | |
| " [bold cyan]➤ Model Name[/bold cyan]", | |
| default=default_name | |
| ) | |
| # Step 3: CoT Mode Selection | |
| cot_table = Table(show_header=False, box=None, padding=(0, 1)) | |
| cot_table.add_row("[bold yellow]1. cot_partial[/bold yellow]", ": Step-by-step arithmetic decomposition (筆算分解)") | |
| cot_table.add_row("[bold yellow]2. cot_direct [/bold yellow]", ": Direct answer output without intermediate steps (直接出力)") | |
| cot_table.add_row("[bold yellow]3. cot_cot [/bold yellow]", ": Chain-of-Thought prompt -> Final Answer only (プロンプトCoT/最終回答のみ)") | |
| console.print() | |
| console.print( | |
| Panel( | |
| cot_table, | |
| title="[bold white] 🧠 Step 3/4: CoT (Reasoning) Mode [/bold white]", | |
| title_align="left", | |
| border_style="cyan", | |
| box=box.ROUNDED, | |
| ) | |
| ) | |
| cot_choice = Prompt.ask( | |
| " [bold cyan]➤ Select CoT Mode[/bold cyan]", | |
| choices=["cot_partial", "cot_direct", "cot_cot"], | |
| default="cot_partial" | |
| ) | |
| # Step 4: Highlight Tag | |
| console.print() | |
| console.print( | |
| Panel( | |
| "[dim]Highlight this entry as a BASE model on the leaderboard?[/dim]", | |
| title="[bold white] ⭐ Step 4/4: Base Model Highlight [/bold white]", | |
| title_align="left", | |
| border_style="cyan", | |
| box=box.ROUNDED, | |
| ) | |
| ) | |
| is_highlight_str = Prompt.ask( | |
| " [bold cyan]➤ Highlight as Base Model?[/bold cyan]", | |
| choices=["y", "n"], | |
| default="n" | |
| ) | |
| is_highlight = (is_highlight_str.lower() == "y") | |
| # ------------------------------------------------------------------------------ | |
| # 4. Model Loading & Verification | |
| # ------------------------------------------------------------------------------ | |
| tokenizer_path = os.path.join(model_dir, "tokenizer.json") | |
| if os.path.exists(tokenizer_path): | |
| tokenizer = CharTokenizer.load(tokenizer_path) | |
| tokenizer_status = f"[green]Loaded from ({tokenizer_path})[/green]" | |
| else: | |
| tokenizer = CharTokenizer(DEFAULT_CHARS) | |
| tokenizer_status = "[yellow]Standard Vocab (Fallback)[/yellow]" | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| with console.status(f"[bold cyan]Loading weights onto [bold green]{device.upper()}[/bold green]...[/bold cyan]"): | |
| model = GPT2LMHeadModel.from_pretrained(model_dir).to(device) | |
| model.eval() | |
| total_params = sum(p.numel() for p in model.parameters()) | |
| params_str = ( | |
| f"{total_params / 1e6:.1f}M" | |
| if total_params >= 1e6 | |
| else f"{total_params / 1e3:.1f}K" | |
| ) | |
| is_qualified = total_params <= 250_000_000 | |
| info_table = Table(show_header=False, box=None, padding=(0, 2)) | |
| info_table.add_row("Model Name:", f"[bold white]{model_name}[/bold white]") | |
| info_table.add_row("Directory:", f"[dim]{os.path.abspath(model_dir)}[/dim]") | |
| info_table.add_row("CoT Mode:", f"[bold yellow]{cot_choice}[/bold yellow]") | |
| info_table.add_row("Highlighted:", f"[bold magenta]{is_highlight}[/bold magenta]") | |
| info_table.add_row("Total Parameters:", f"[bold cyan]{params_str}[/bold cyan] ({total_params:,})") | |
| info_table.add_row( | |
| "Eligibility (≤ 250M):", | |
| "[bold green]✔ QUALIFIED[/bold green]" if is_qualified else "[bold red]✖ DISQUALIFIED (>250M)[/bold red]" | |
| ) | |
| console.print() | |
| console.print( | |
| Panel( | |
| info_table, | |
| title="[bold green] 🤖 Model Specification Confirmed [/bold green]", | |
| title_align="left", | |
| border_style="green", | |
| box=box.ROUNDED, | |
| ) | |
| ) | |
| # ------------------------------------------------------------------------------ | |
| # 5. Dynamic Dataset Generator (Based on cot_choice) | |
| # ------------------------------------------------------------------------------ | |
| def generate_benchmark_sample(cot_mode="cot_partial"): | |
| mode = random.choices(["2terms", "3terms"], weights=[0.7, 0.3])[0] | |
| if mode == "2terms": | |
| a = random.randint(2, 999) | |
| b = random.randint(2, 99) | |
| expr = f"{a} * {b}" | |
| ans = a * b | |
| if cot_mode == "cot_direct": | |
| prompt = f"Q: {expr}\nAns:" | |
| completion = f" {ans}<|endoftext|>" | |
| elif cot_mode == "cot_cot": | |
| prompt = f"Q: {expr}\nLet's think step by step.\nAns:" | |
| completion = f" {ans}<|endoftext|>" | |
| else: # cot_partial (standard step-by-step) | |
| if b >= 10: | |
| steps = [] | |
| sub_products = [] | |
| for i, digit in enumerate(reversed(str(b))): | |
| p_val = int(digit) * (10**i) | |
| if p_val > 0: | |
| part_ans = a * p_val | |
| sub_products.append(part_ans) | |
| steps.append(f"Step {len(steps)+1}: {a} * {p_val} = {part_ans}") | |
| if len(sub_products) > 1: | |
| add_expr = " + ".join(map(str, sub_products)) | |
| steps.append(f"Step {len(steps)+1}: {add_expr} = {ans}") | |
| steps_text = "\n".join(steps) | |
| else: | |
| steps_text = f"Step 1: {expr} = {ans}" | |
| prompt = f"Q: {expr}\nSteps:\n" | |
| completion = f"{steps_text}\nAns: {ans}<|endoftext|>" | |
| else: # 3terms | |
| a = random.randint(2, 50) | |
| b = random.randint(2, 20) | |
| c = random.randint(2, 20) | |
| expr = f"{a} * {b} * {c}" | |
| r1, r2 = a * b, a * b * c | |
| if cot_mode == "cot_direct": | |
| prompt = f"Q: {expr}\nAns:" | |
| completion = f" {r2}<|endoftext|>" | |
| elif cot_mode == "cot_cot": | |
| prompt = f"Q: {expr}\nLet's think step by step.\nAns:" | |
| completion = f" {r2}<|endoftext|>" | |
| else: # cot_partial | |
| prompt = f"Q: {expr}\nSteps:\n" | |
| completion = f"Step 1: {a} * {b} = {r1}\nStep 2: {r1} * {c} = {r2}\nAns: {r2}<|endoftext|>" | |
| return prompt, completion | |
| class EvalDataset(Dataset): | |
| def __init__(self, samples, max_len=160): | |
| self.items = [] | |
| for prompt, comp in samples: | |
| full_text = prompt + comp | |
| p_tokens = tokenizer.encode(prompt) | |
| full_tokens = tokenizer.encode(full_text) | |
| prompt_len = len(p_tokens) | |
| seq_len = len(full_tokens) | |
| if seq_len > max_len: | |
| full_tokens = full_tokens[:max_len] | |
| labels = list(full_tokens) | |
| for i in range(min(prompt_len, len(labels))): | |
| labels[i] = -100 | |
| pad_len = max_len - len(full_tokens) | |
| input_ids = full_tokens + [tokenizer.pad_id] * pad_len | |
| labels = labels + [-100] * pad_len | |
| self.items.append( | |
| { | |
| "input_ids": torch.tensor(input_ids, dtype=torch.long), | |
| "labels": torch.tensor(labels, dtype=torch.long), | |
| } | |
| ) | |
| def __len__(self): | |
| return len(self.items) | |
| def __getitem__(self, idx): | |
| return self.items[idx] | |
| with console.status(f"[bold blue]Generating 10,500 questions in '[yellow]{cot_choice}[/yellow]' mode...[/bold blue]"): | |
| random.seed(2025) | |
| eval_10k_data = [generate_benchmark_sample(cot_choice) for _ in range(10000)] | |
| eval_ppl_data = [generate_benchmark_sample(cot_choice) for _ in range(500)] | |
| dataset_10k = EvalDataset(eval_10k_data) | |
| dataset_ppl = EvalDataset(eval_ppl_data) | |
| # Auto Batch Size Discovery | |
| def find_optimal_batch_size(model, min_b=1, max_b=1024, seq_len=160, device="cuda"): | |
| if device == "cpu": return 64 | |
| candidate_sizes = [2**i for i in range(int(math.log2(min_b)), int(math.log2(max_b)) + 1)] | |
| optimal_b = min_b | |
| with console.status("[bold magenta]Probing maximum safe batch size...[/bold magenta]"): | |
| for b in candidate_sizes: | |
| try: | |
| torch.cuda.empty_cache() | |
| dummy_input = torch.randint(0, tokenizer.vocab_size, (b, seq_len), device=device) | |
| with torch.no_grad(): | |
| _ = model(dummy_input) | |
| optimal_b = b | |
| except Exception: | |
| break | |
| return optimal_b | |
| auto_batch_size = find_optimal_batch_size(model, device=device) | |
| console.print(f"\n[bold green]✔ Hardware Optimized:[/bold green] Batch Size set to [bold cyan]{auto_batch_size}[/bold cyan]\n") | |
| # ------------------------------------------------------------------------------ | |
| # 6. Benchmark Execution Loop | |
| # ------------------------------------------------------------------------------ | |
| loader_10k = DataLoader(dataset_10k, batch_size=auto_batch_size, shuffle=False) | |
| loader_ppl = DataLoader(dataset_ppl, batch_size=auto_batch_size, shuffle=False) | |
| total_log_likelihood = 0.0 | |
| total_tokens = 0 | |
| correct_tokens = 0 | |
| perfect_sequences = 0 | |
| total_sequences = 0 | |
| with Progress( | |
| SpinnerColumn(), | |
| TextColumn("[progress.description]{task.description}"), | |
| BarColumn(complete_style="cyan", finished_style="green"), | |
| TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), | |
| TimeRemainingColumn(), | |
| console=console, | |
| ) as progress: | |
| task1 = progress.add_task(f"[cyan]Evaluating 10,000 Qs ({cot_choice})...", total=len(loader_10k)) | |
| with torch.no_grad(): | |
| for batch in loader_10k: | |
| input_ids = batch["input_ids"].to(device) | |
| labels = batch["labels"].to(device) | |
| outputs = model(input_ids) | |
| logits = outputs.logits | |
| shift_logits = logits[:, :-1, :].contiguous() | |
| shift_labels = labels[:, 1:].contiguous() | |
| log_probs = F.log_softmax(shift_logits, dim=-1) | |
| mask = shift_labels != -100 | |
| gathered = torch.gather(log_probs, 2, shift_labels.unsqueeze(-1).clamp(min=0)).squeeze(-1) | |
| gathered = gathered * mask | |
| total_log_likelihood += gathered.sum().item() | |
| total_tokens += mask.sum().item() | |
| preds = torch.argmax(shift_logits, dim=-1) | |
| matches = (preds == shift_labels) & mask | |
| correct_tokens += matches.sum().item() | |
| seq_matches = (matches.sum(dim=1) == mask.sum(dim=1)).sum().item() | |
| perfect_sequences += seq_matches | |
| total_sequences += input_ids.size(0) | |
| progress.update(task1, advance=1) | |
| task2 = progress.add_task("[magenta]Calculating Perplexity on 500 Qs...", total=len(loader_ppl)) | |
| total_loss = 0.0 | |
| ppl_batches = 0 | |
| loss_fn = torch.nn.CrossEntropyLoss(ignore_index=-100) | |
| with torch.no_grad(): | |
| for batch in loader_ppl: | |
| input_ids = batch["input_ids"].to(device) | |
| labels = batch["labels"].to(device) | |
| outputs = model(input_ids) | |
| shift_logits = outputs.logits[:, :-1, :].contiguous() | |
| shift_labels = labels[:, 1:].contiguous() | |
| loss = loss_fn(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) | |
| total_loss += loss.item() | |
| ppl_batches += 1 | |
| progress.update(task2, advance=1) | |
| # Metrics Calculation | |
| token_accuracy = (correct_tokens / total_tokens) * 100 | |
| sequence_accuracy = (perfect_sequences / total_sequences) * 100 | |
| avg_loss = total_loss / ppl_batches | |
| ppl_score = math.exp(avg_loss) | |
| p = sequence_accuracy / 100.0 | |
| p_smooth = min(max(p, 1e-4), 1.0 - 1e-5) | |
| logit_val = math.log(p_smooth / (1.0 - p_smooth)) | |
| rating_score = max(0.0, 1050.0 + (252.47 * logit_val)) | |
| # ------------------------------------------------------------------------------ | |
| # 7. HTML Leaderboard Ready JSON Output | |
| # ------------------------------------------------------------------------------ | |
| hf_leaderboard_entry = { | |
| "name": model_name, | |
| "params_str": params_str, | |
| "params_num": total_params, | |
| "cot": cot_choice, | |
| "score": round(rating_score, 1), | |
| "exact": round(sequence_accuracy, 1), | |
| "token_acc": round(token_accuracy, 1), | |
| "ppl": round(ppl_score, 3), | |
| "highlight": is_highlight | |
| } | |
| json_str = json.dumps(hf_leaderboard_entry, indent=4) | |
| console.print() | |
| console.print( | |
| Panel( | |
| f"[bold green]{json_str}[/bold green]", | |
| title="[bold yellow] 📋 HTML Ready Leaderboard Entry (Copy & Paste to modelData) [/bold yellow]", | |
| title_align="left", | |
| border_style="yellow", | |
| box=box.ROUNDED, | |
| ) | |
| ) | |
| console.print("[dim]Copy the JSON above and paste it directly into the 'modelData' array inside index.html![/dim]\n") |