CoolFace
Apppublic

56m/SmallMultiplicationLeaderboard

sourceHugging Faceapache-2.0updated 1mo agoView on Hugging Face
0likes
evaluate.py484 linesDownload Raw Back to root
1# ==============================================================================2# Multiplication SLM Benchmark Suite (Interactive TUI Wizard)3# Dynamic Dataset Generator corresponding to 'cot_partial', 'cot_direct', 'cot_cot'4# ==============================================================================5 6import json7import math8import os9import random10import subprocess11import sys12import torch13import torch.nn.functional as F14from torch.utils.data import DataLoader, Dataset15 16# ------------------------------------------------------------------------------17# 1. Install & Import Rich18# ------------------------------------------------------------------------------19try:20    from rich.align import Align21    from rich import box22    from rich.console import Console23    from rich.panel import Panel24    from rich.progress import (25        BarColumn,26        Progress,27        SpinnerColumn,28        TextColumn,29        TimeRemainingColumn,30    )31    from rich.prompt import Prompt32    from rich.table import Table33except ImportError:34    subprocess.check_call(35        [sys.executable, "-m", "pip", "install", "rich", "-q"]36    )37    from rich.align import Align38    from rich import box39    from rich.console import Console40    from rich.panel import Panel41    from rich.progress import (42        BarColumn,43        Progress,44        SpinnerColumn,45        TextColumn,46        TimeRemainingColumn,47    )48    from rich.prompt import Prompt49    from rich.table import Table50 51from transformers import GPT2LMHeadModel52 53console = Console()54 55# ------------------------------------------------------------------------------56# 2. Tokenizer Definition57# ------------------------------------------------------------------------------58DEFAULT_CHARS = sorted(59    list(60        set(61            "0123456789 *+=:\nQStepsAnsLet'sthinkstepby"62            + "<|endoftext|>"63            + "<|pad|>"64        )65    )66)67 68class CharTokenizer:69    def __init__(self, chars=None):70        if chars is None:71            chars = DEFAULT_CHARS72        self.chars = sorted(list(set(chars)))73        if "<|pad|>" not in self.chars:74            self.chars.append("<|pad|>")75        self.c2i = {ch: i for i, ch in enumerate(self.chars)}76        self.i2c = {i: ch for i, ch in enumerate(self.chars)}77        self.pad_id = self.c2i["<|pad|>"]78        self.vocab_size = len(self.chars)79 80    @classmethod81    def load(cls, filepath):82        with open(filepath, "r", encoding="utf-8") as f:83            data = json.load(f)84        return cls(data["chars"])85 86    def encode(self, text):87        return [self.c2i[c] for c in text if c in self.c2i]88 89    def decode(self, ids):90        return "".join(91            [self.i2c[i] for i in ids if i in self.i2c and i != self.pad_id]92        )93 94# ------------------------------------------------------------------------------95# 3. Interactive TUI Wizard96# ------------------------------------------------------------------------------97console.clear()98console.print()99console.print(100    Panel(101        Align.center(102            "[bold yellow]🤗 Hugging Face Micro-SLM Benchmark Harness[/bold yellow]\n"103            "[dim]10,000 Qs Log-Likelihood & Exact Match  |  500 Qs Perplexity (PPL)\n"104            "Rule: Strictly for Small Models (≤ 250M Parameters)[/dim]"105        ),106        box=box.ROUNDED,107        border_style="yellow",108        padding=(1, 2),109    )110)111 112# Step 1: Model Directory113candidate_paths = [114    d115    for d in ["./slm_mult_model", "./slm_mult_checkpoints", "."]116    if os.path.exists(os.path.join(d, "config.json"))117    or os.path.exists(os.path.join(d, "tokenizer.json"))118]119detected_hint = (120    f"[green]Found model at:[/green] [bold]{candidate_paths[0]}[/bold]"121    if candidate_paths122    else "[dim]No model detected in current folder.[/dim]"123)124 125input_dialog = Table.grid(padding=1)126input_dialog.add_column(style="bold cyan")127input_dialog.add_column()128input_dialog.add_row("Auto Detected:", detected_hint)129input_dialog.add_row("Default Path:", "[bold yellow]./slm_mult_model[/bold yellow]")130 131console.print(132    Panel(133        input_dialog,134        title="[bold white] 📁 Step 1/4: Model Directory [/bold white]",135        title_align="left",136        border_style="cyan",137        box=box.ROUNDED,138    )139)140 141while True:142    model_dir = Prompt.ask(143        " [bold cyan]➤ Model Directory Path[/bold cyan]",144        default=candidate_paths[0] if candidate_paths else "./slm_mult_model",145    )146    if os.path.exists(model_dir):147        break148    console.print("[bold red]Directory not found! Please try again.[/bold red]")149 150# Step 2: Model Display Name151default_name = f"YourOrg/{os.path.basename(os.path.abspath(model_dir))}"152console.print()153console.print(154    Panel(155        "[dim]Enter the model name for the leaderboard (e.g. OrgName/ModelName).[/dim]",156        title="[bold white] 🏷️ Step 2/4: Model ID / Display Name [/bold white]",157        title_align="left",158        border_style="cyan",159        box=box.ROUNDED,160    )161)162model_name = Prompt.ask(163    " [bold cyan]➤ Model Name[/bold cyan]",164    default=default_name165)166 167# Step 3: CoT Mode Selection168cot_table = Table(show_header=False, box=None, padding=(0, 1))169cot_table.add_row("[bold yellow]1. cot_partial[/bold yellow]", ": Step-by-step arithmetic decomposition (筆算分解)")170cot_table.add_row("[bold yellow]2. cot_direct [/bold yellow]", ": Direct answer output without intermediate steps (直接出力)")171cot_table.add_row("[bold yellow]3. cot_cot    [/bold yellow]", ": Chain-of-Thought prompt -> Final Answer only (プロンプトCoT/最終回答のみ)")172 173console.print()174console.print(175    Panel(176        cot_table,177        title="[bold white] 🧠 Step 3/4: CoT (Reasoning) Mode [/bold white]",178        title_align="left",179        border_style="cyan",180        box=box.ROUNDED,181    )182)183 184cot_choice = Prompt.ask(185    " [bold cyan]➤ Select CoT Mode[/bold cyan]",186    choices=["cot_partial", "cot_direct", "cot_cot"],187    default="cot_partial"188)189 190# Step 4: Highlight Tag191console.print()192console.print(193    Panel(194        "[dim]Highlight this entry as a BASE model on the leaderboard?[/dim]",195        title="[bold white] ⭐ Step 4/4: Base Model Highlight [/bold white]",196        title_align="left",197        border_style="cyan",198        box=box.ROUNDED,199    )200)201is_highlight_str = Prompt.ask(202    " [bold cyan]➤ Highlight as Base Model?[/bold cyan]",203    choices=["y", "n"],204    default="n"205)206is_highlight = (is_highlight_str.lower() == "y")207 208# ------------------------------------------------------------------------------209# 4. Model Loading & Verification210# ------------------------------------------------------------------------------211tokenizer_path = os.path.join(model_dir, "tokenizer.json")212if os.path.exists(tokenizer_path):213    tokenizer = CharTokenizer.load(tokenizer_path)214    tokenizer_status = f"[green]Loaded from ({tokenizer_path})[/green]"215else:216    tokenizer = CharTokenizer(DEFAULT_CHARS)217    tokenizer_status = "[yellow]Standard Vocab (Fallback)[/yellow]"218 219device = "cuda" if torch.cuda.is_available() else "cpu"220 221with console.status(f"[bold cyan]Loading weights onto [bold green]{device.upper()}[/bold green]...[/bold cyan]"):222    model = GPT2LMHeadModel.from_pretrained(model_dir).to(device)223    model.eval()224 225total_params = sum(p.numel() for p in model.parameters())226params_str = (227    f"{total_params / 1e6:.1f}M"228    if total_params >= 1e6229    else f"{total_params / 1e3:.1f}K"230)231is_qualified = total_params <= 250_000_000232 233info_table = Table(show_header=False, box=None, padding=(0, 2))234info_table.add_row("Model Name:", f"[bold white]{model_name}[/bold white]")235info_table.add_row("Directory:", f"[dim]{os.path.abspath(model_dir)}[/dim]")236info_table.add_row("CoT Mode:", f"[bold yellow]{cot_choice}[/bold yellow]")237info_table.add_row("Highlighted:", f"[bold magenta]{is_highlight}[/bold magenta]")238info_table.add_row("Total Parameters:", f"[bold cyan]{params_str}[/bold cyan] ({total_params:,})")239info_table.add_row(240    "Eligibility (≤ 250M):",241    "[bold green]✔ QUALIFIED[/bold green]" if is_qualified else "[bold red]✖ DISQUALIFIED (>250M)[/bold red]"242)243 244console.print()245console.print(246    Panel(247        info_table,248        title="[bold green] 🤖 Model Specification Confirmed [/bold green]",249        title_align="left",250        border_style="green",251        box=box.ROUNDED,252    )253)254 255# ------------------------------------------------------------------------------256# 5. Dynamic Dataset Generator (Based on cot_choice)257# ------------------------------------------------------------------------------258def generate_benchmark_sample(cot_mode="cot_partial"):259    mode = random.choices(["2terms", "3terms"], weights=[0.7, 0.3])[0]260 261    if mode == "2terms":262        a = random.randint(2, 999)263        b = random.randint(2, 99)264        expr = f"{a} * {b}"265        ans = a * b266 267        if cot_mode == "cot_direct":268            prompt = f"Q: {expr}\nAns:"269            completion = f" {ans}<|endoftext|>"270 271        elif cot_mode == "cot_cot":272            prompt = f"Q: {expr}\nLet's think step by step.\nAns:"273            completion = f" {ans}<|endoftext|>"274 275        else:  # cot_partial (standard step-by-step)276            if b >= 10:277                steps = []278                sub_products = []279                for i, digit in enumerate(reversed(str(b))):280                    p_val = int(digit) * (10**i)281                    if p_val > 0:282                        part_ans = a * p_val283                        sub_products.append(part_ans)284                        steps.append(f"Step {len(steps)+1}: {a} * {p_val} = {part_ans}")285                if len(sub_products) > 1:286                    add_expr = " + ".join(map(str, sub_products))287                    steps.append(f"Step {len(steps)+1}: {add_expr} = {ans}")288                steps_text = "\n".join(steps)289            else:290                steps_text = f"Step 1: {expr} = {ans}"291 292            prompt = f"Q: {expr}\nSteps:\n"293            completion = f"{steps_text}\nAns: {ans}<|endoftext|>"294 295    else:  # 3terms296        a = random.randint(2, 50)297        b = random.randint(2, 20)298        c = random.randint(2, 20)299        expr = f"{a} * {b} * {c}"300        r1, r2 = a * b, a * b * c301 302        if cot_mode == "cot_direct":303            prompt = f"Q: {expr}\nAns:"304            completion = f" {r2}<|endoftext|>"305 306        elif cot_mode == "cot_cot":307            prompt = f"Q: {expr}\nLet's think step by step.\nAns:"308            completion = f" {r2}<|endoftext|>"309 310        else:  # cot_partial311            prompt = f"Q: {expr}\nSteps:\n"312            completion = f"Step 1: {a} * {b} = {r1}\nStep 2: {r1} * {c} = {r2}\nAns: {r2}<|endoftext|>"313 314    return prompt, completion315 316class EvalDataset(Dataset):317    def __init__(self, samples, max_len=160):318        self.items = []319        for prompt, comp in samples:320            full_text = prompt + comp321            p_tokens = tokenizer.encode(prompt)322            full_tokens = tokenizer.encode(full_text)323            prompt_len = len(p_tokens)324            seq_len = len(full_tokens)325 326            if seq_len > max_len:327                full_tokens = full_tokens[:max_len]328 329            labels = list(full_tokens)330            for i in range(min(prompt_len, len(labels))):331                labels[i] = -100332 333            pad_len = max_len - len(full_tokens)334            input_ids = full_tokens + [tokenizer.pad_id] * pad_len335            labels = labels + [-100] * pad_len336 337            self.items.append(338                {339                    "input_ids": torch.tensor(input_ids, dtype=torch.long),340                    "labels": torch.tensor(labels, dtype=torch.long),341                }342            )343 344    def __len__(self):345        return len(self.items)346 347    def __getitem__(self, idx):348        return self.items[idx]349 350with console.status(f"[bold blue]Generating 10,500 questions in '[yellow]{cot_choice}[/yellow]' mode...[/bold blue]"):351    random.seed(2025)352    eval_10k_data = [generate_benchmark_sample(cot_choice) for _ in range(10000)]353    eval_ppl_data = [generate_benchmark_sample(cot_choice) for _ in range(500)]354    dataset_10k = EvalDataset(eval_10k_data)355    dataset_ppl = EvalDataset(eval_ppl_data)356 357# Auto Batch Size Discovery358def find_optimal_batch_size(model, min_b=1, max_b=1024, seq_len=160, device="cuda"):359    if device == "cpu": return 64360    candidate_sizes = [2**i for i in range(int(math.log2(min_b)), int(math.log2(max_b)) + 1)]361    optimal_b = min_b362    with console.status("[bold magenta]Probing maximum safe batch size...[/bold magenta]"):363        for b in candidate_sizes:364            try:365                torch.cuda.empty_cache()366                dummy_input = torch.randint(0, tokenizer.vocab_size, (b, seq_len), device=device)367                with torch.no_grad():368                    _ = model(dummy_input)369                optimal_b = b370            except Exception:371                break372    return optimal_b373 374auto_batch_size = find_optimal_batch_size(model, device=device)375console.print(f"\n[bold green]✔ Hardware Optimized:[/bold green] Batch Size set to [bold cyan]{auto_batch_size}[/bold cyan]\n")376 377# ------------------------------------------------------------------------------378# 6. Benchmark Execution Loop379# ------------------------------------------------------------------------------380loader_10k = DataLoader(dataset_10k, batch_size=auto_batch_size, shuffle=False)381loader_ppl = DataLoader(dataset_ppl, batch_size=auto_batch_size, shuffle=False)382 383total_log_likelihood = 0.0384total_tokens = 0385correct_tokens = 0386perfect_sequences = 0387total_sequences = 0388 389with Progress(390    SpinnerColumn(),391    TextColumn("[progress.description]{task.description}"),392    BarColumn(complete_style="cyan", finished_style="green"),393    TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),394    TimeRemainingColumn(),395    console=console,396) as progress:397 398    task1 = progress.add_task(f"[cyan]Evaluating 10,000 Qs ({cot_choice})...", total=len(loader_10k))399    with torch.no_grad():400        for batch in loader_10k:401            input_ids = batch["input_ids"].to(device)402            labels = batch["labels"].to(device)403            outputs = model(input_ids)404            logits = outputs.logits405 406            shift_logits = logits[:, :-1, :].contiguous()407            shift_labels = labels[:, 1:].contiguous()408 409            log_probs = F.log_softmax(shift_logits, dim=-1)410            mask = shift_labels != -100411 412            gathered = torch.gather(log_probs, 2, shift_labels.unsqueeze(-1).clamp(min=0)).squeeze(-1)413            gathered = gathered * mask414 415            total_log_likelihood += gathered.sum().item()416            total_tokens += mask.sum().item()417 418            preds = torch.argmax(shift_logits, dim=-1)419            matches = (preds == shift_labels) & mask420            correct_tokens += matches.sum().item()421 422            seq_matches = (matches.sum(dim=1) == mask.sum(dim=1)).sum().item()423            perfect_sequences += seq_matches424            total_sequences += input_ids.size(0)425 426            progress.update(task1, advance=1)427 428    task2 = progress.add_task("[magenta]Calculating Perplexity on 500 Qs...", total=len(loader_ppl))429    total_loss = 0.0430    ppl_batches = 0431    loss_fn = torch.nn.CrossEntropyLoss(ignore_index=-100)432 433    with torch.no_grad():434        for batch in loader_ppl:435            input_ids = batch["input_ids"].to(device)436            labels = batch["labels"].to(device)437            outputs = model(input_ids)438            shift_logits = outputs.logits[:, :-1, :].contiguous()439            shift_labels = labels[:, 1:].contiguous()440 441            loss = loss_fn(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))442            total_loss += loss.item()443            ppl_batches += 1444            progress.update(task2, advance=1)445 446# Metrics Calculation447token_accuracy = (correct_tokens / total_tokens) * 100448sequence_accuracy = (perfect_sequences / total_sequences) * 100449avg_loss = total_loss / ppl_batches450ppl_score = math.exp(avg_loss)451 452p = sequence_accuracy / 100.0453p_smooth = min(max(p, 1e-4), 1.0 - 1e-5)454logit_val = math.log(p_smooth / (1.0 - p_smooth))455rating_score = max(0.0, 1050.0 + (252.47 * logit_val))456 457# ------------------------------------------------------------------------------458# 7. HTML Leaderboard Ready JSON Output459# ------------------------------------------------------------------------------460hf_leaderboard_entry = {461    "name": model_name,462    "params_str": params_str,463    "params_num": total_params,464    "cot": cot_choice,465    "score": round(rating_score, 1),466    "exact": round(sequence_accuracy, 1),467    "token_acc": round(token_accuracy, 1),468    "ppl": round(ppl_score, 3),469    "highlight": is_highlight470}471 472json_str = json.dumps(hf_leaderboard_entry, indent=4)473 474console.print()475console.print(476    Panel(477        f"[bold green]{json_str}[/bold green]",478        title="[bold yellow] 📋 HTML Ready Leaderboard Entry (Copy & Paste to modelData) [/bold yellow]",479        title_align="left",480        border_style="yellow",481        box=box.ROUNDED,482    )483)484console.print("[dim]Copy the JSON above and paste it directly into the 'modelData' array inside index.html![/dim]\n")