|
|
|
|
|
|
| """
|
| benchmark_humaneval.py β EvaluaciΓ³n HumanEval (pass@1) para PamparV3.
|
|
|
| Descarga el dataset HumanEval de OpenAI (164 problemas), genera soluciones
|
| con el modelo y ejecuta los tests unitarios oficiales.
|
|
|
| Uso:
|
| python -X utf8 scripts/benchmark_humaneval.py
|
| python -X utf8 scripts/benchmark_humaneval.py --checkpoint checkpoints/v3_train.pt
|
| python -X utf8 scripts/benchmark_humaneval.py --samples-per-task 5 --temp 0.4
|
| python -X utf8 scripts/benchmark_humaneval.py --device cpu --verbose
|
|
|
| Requiere:
|
| pip install datasets (para descargar HumanEval desde HuggingFace)
|
|
|
| Resultados se guardan en benchmarks/humaneval_results.json
|
| """
|
|
|
| from __future__ import annotations
|
|
|
| import argparse
|
| import json
|
| import signal
|
| import sys
|
| import time
|
| import traceback
|
| from pathlib import Path
|
| from typing import Any
|
|
|
| import torch
|
| import torch.nn.functional as F
|
|
|
| sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| def cargar_humaneval() -> list[dict[str, Any]]:
|
| """Descarga HumanEval desde HuggingFace datasets."""
|
| try:
|
| from datasets import load_dataset
|
| except ImportError:
|
| print("ERROR: instalar datasets β pip install datasets")
|
| sys.exit(1)
|
|
|
| ds = load_dataset("openai_humaneval", split="test")
|
| problemas = []
|
| for row in ds:
|
| problemas.append(
|
| {
|
| "task_id": row["task_id"],
|
| "prompt": row["prompt"],
|
| "canonical_solution": row["canonical_solution"],
|
| "test": row["test"],
|
| "entry_point": row["entry_point"],
|
| }
|
| )
|
| print(f" HumanEval cargado: {len(problemas)} problemas")
|
| return problemas
|
|
|
|
|
|
|
|
|
|
|
|
|
| from pampar.inference import load_model
|
|
|
|
|
|
|
|
|
|
|
|
|
| @torch.no_grad()
|
| def generar(
|
| modelo,
|
| tokenizer,
|
| prompt: str,
|
| device: torch.device,
|
| max_tokens: int = 512,
|
| temperature: float = 0.2,
|
| top_p: float = 0.95,
|
| ) -> str:
|
| """Genera cΓ³digo completando el prompt con nucleus sampling."""
|
| ids = tokenizer.Encode(prompt)
|
| generados = list(ids)
|
|
|
| for _ in range(max_tokens):
|
| ctx = torch.tensor([generados[-512:]], dtype=torch.long, device=device)
|
| logits, _, _ = modelo(ctx)
|
| next_logits = logits[0, -1]
|
|
|
| if temperature <= 0.0:
|
| next_token = int(next_logits.argmax())
|
| else:
|
| next_logits = next_logits / temperature
|
|
|
| sorted_logits, sorted_indices = torch.sort(next_logits, descending=True)
|
| cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)
|
| mask = cumulative_probs - F.softmax(sorted_logits, dim=-1) >= top_p
|
| sorted_logits[mask] = float("-inf")
|
| probs = F.softmax(sorted_logits, dim=-1)
|
| idx = int(torch.multinomial(probs, 1))
|
| next_token = int(sorted_indices[idx])
|
|
|
| generados.append(next_token)
|
| decoded = tokenizer.Decode(generados[len(ids) :])
|
|
|
|
|
| if "\n\nclass " in decoded or "\n\ndef " in decoded:
|
|
|
| for stop in ["\n\nclass ", "\n\ndef "]:
|
| if stop in decoded:
|
| decoded = decoded[: decoded.index(stop)]
|
| return decoded
|
|
|
|
|
| if "\n\n\n" in decoded:
|
| decoded = decoded[: decoded.index("\n\n\n")]
|
| return decoded
|
|
|
| return tokenizer.Decode(generados[len(ids) :])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| class TimeoutError(Exception):
|
| """SeΓ±al de timeout."""
|
|
|
|
|
| def _timeout_handler(signum, frame):
|
| raise TimeoutError("Timeout")
|
|
|
|
|
| def ejecutar_con_timeout(code: str, timeout_sec: int = 5) -> tuple[bool, str]:
|
| """
|
| Ejecuta cΓ³digo Python con timeout.
|
|
|
| Returns:
|
| (passed, error_msg)
|
| """
|
|
|
| if sys.platform == "win32":
|
| import threading
|
|
|
| result: dict[str, Any] = {"passed": False, "error": "timeout"}
|
|
|
| def _run():
|
| try:
|
| ns: dict[str, Any] = {}
|
| exec(compile(code, "<humaneval>", "exec"), ns)
|
| result["passed"] = True
|
| result["error"] = ""
|
| except AssertionError as e:
|
| result["error"] = f"AssertionError: {e}"
|
| except Exception as e:
|
| result["error"] = f"{type(e).__name__}: {e}"
|
|
|
| t = threading.Thread(target=_run, daemon=True)
|
| t.start()
|
| t.join(timeout=timeout_sec)
|
| if t.is_alive():
|
| return False, "timeout"
|
| return result["passed"], result["error"]
|
| else:
|
|
|
| old_handler = signal.signal(signal.SIGALRM, _timeout_handler)
|
| signal.alarm(timeout_sec)
|
| try:
|
| ns: dict[str, Any] = {}
|
| exec(compile(code, "<humaneval>", "exec"), ns)
|
| signal.alarm(0)
|
| return True, ""
|
| except TimeoutError:
|
| return False, "timeout"
|
| except AssertionError as e:
|
| signal.alarm(0)
|
| return False, f"AssertionError: {e}"
|
| except Exception as e:
|
| signal.alarm(0)
|
| return False, f"{type(e).__name__}: {e}"
|
| finally:
|
| signal.signal(signal.SIGALRM, old_handler)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| def evaluar_problema(
|
| modelo,
|
| tokenizer,
|
| device: torch.device,
|
| problema: dict,
|
| n_samples: int = 1,
|
| temperature: float = 0.2,
|
| max_tokens: int = 512,
|
| verbose: bool = False,
|
| ) -> dict:
|
| """Genera n_samples completions y evalΓΊa cada una."""
|
| task_id = problema["task_id"]
|
| prompt = problema["prompt"]
|
| test_code = problema["test"]
|
| entry_point = problema["entry_point"]
|
|
|
| resultados = []
|
|
|
| for s in range(n_samples):
|
| temp = 0.0 if n_samples == 1 else temperature
|
| completion = generar(modelo, tokenizer, prompt, device, max_tokens, temp)
|
|
|
|
|
| full_code = prompt + completion + "\n\n" + test_code
|
|
|
| full_code += f"\n\ncheck({entry_point})\n"
|
|
|
| passed, error = ejecutar_con_timeout(full_code, timeout_sec=10)
|
|
|
| if verbose:
|
| status = "PASS" if passed else f"FAIL ({error[:60]})"
|
| print(f" sample {s}: {status}")
|
| if not passed:
|
|
|
| for line in completion.split("\n")[:15]:
|
| print(f" {line}")
|
|
|
| resultados.append(
|
| {
|
| "sample": s,
|
| "passed": passed,
|
| "error": error,
|
| "completion_len": len(completion),
|
| }
|
| )
|
|
|
| any_passed = any(r["passed"] for r in resultados)
|
| return {
|
| "task_id": task_id,
|
| "entry_point": entry_point,
|
| "passed": any_passed,
|
| "n_samples": n_samples,
|
| "pass_count": sum(1 for r in resultados if r["passed"]),
|
| "samples": resultados,
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| def pass_at_k(n: int, c: int, k: int) -> float:
|
| """
|
| Estimador insesgado de pass@k.
|
|
|
| n: total de muestras por problema
|
| c: cantidad de muestras correctas
|
| k: k para pass@k
|
| """
|
| if n - c < k:
|
| return 1.0
|
| result = 1.0
|
| for i in range(k):
|
| result *= (n - c - i) / (n - i)
|
| return 1.0 - result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| def main():
|
| parser = argparse.ArgumentParser(description="HumanEval benchmark para PamparV3")
|
| parser.add_argument("--checkpoint", default="checkpoints/v3_train.pt")
|
| parser.add_argument(
|
| "--samples-per-task",
|
| type=int,
|
| default=1,
|
| help="Muestras por problema (1=pass@1 determinista)",
|
| )
|
| parser.add_argument("--temp", type=float, default=0.2)
|
| parser.add_argument("--max-tokens", type=int, default=512)
|
| parser.add_argument("--device", default="auto")
|
| parser.add_argument("--verbose", action="store_true")
|
| parser.add_argument(
|
| "--limit", type=int, default=0, help="Limitar a N problemas (0=todos)"
|
| )
|
| parser.add_argument("--output", default="benchmarks/humaneval_results.json")
|
| args = parser.parse_args()
|
|
|
| device = torch.device(
|
| args.device
|
| if args.device != "auto"
|
| else ("cuda" if torch.cuda.is_available() else "cpu")
|
| )
|
| checkpoint = Path(args.checkpoint)
|
|
|
| print(f"\n{'β' * 65}")
|
| print(f" BENCHMARK HumanEval β PamparV3")
|
| print(f" Checkpoint : {checkpoint.name}")
|
| print(f" Device : {device}")
|
| print(f" Samples/task: {args.samples_per_task} | Temp: {args.temp}")
|
| print(f"{'β' * 65}\n")
|
|
|
|
|
| print(" Cargando modelo...", end=" ", flush=True)
|
| t0 = time.time()
|
| modelo, tokenizer = load_model(checkpoint, device, verbose=False)
|
| n_params = sum(p.numel() for p in modelo.parameters()) / 1e6
|
| print(f"OK ({n_params:.1f}M params, {time.time() - t0:.1f}s)")
|
|
|
|
|
| print(" Descargando HumanEval...", end=" ", flush=True)
|
| problemas = cargar_humaneval()
|
| if args.limit > 0:
|
| problemas = problemas[: args.limit]
|
| print(f"(limitado a {args.limit})")
|
|
|
|
|
| print(f"\n Evaluando {len(problemas)} problemas...\n")
|
| results = []
|
| t_start = time.time()
|
|
|
| for i, prob in enumerate(problemas, 1):
|
| print(
|
| f" [{i:03d}/{len(problemas)}] {prob['task_id']:30s}",
|
| end=" ",
|
| flush=True,
|
| )
|
| t_prob = time.time()
|
|
|
| try:
|
| res = evaluar_problema(
|
| modelo,
|
| tokenizer,
|
| device,
|
| prob,
|
| n_samples=args.samples_per_task,
|
| temperature=args.temp,
|
| max_tokens=args.max_tokens,
|
| verbose=args.verbose,
|
| )
|
| except Exception as e:
|
| traceback.print_exc()
|
| res = {
|
| "task_id": prob["task_id"],
|
| "entry_point": prob["entry_point"],
|
| "passed": False,
|
| "n_samples": args.samples_per_task,
|
| "pass_count": 0,
|
| "samples": [],
|
| "error": str(e),
|
| }
|
|
|
| dt = time.time() - t_prob
|
| icon = "β
" if res["passed"] else "β"
|
| pc = res["pass_count"]
|
| ns = res["n_samples"]
|
| print(f"[{dt:.1f}s] {icon} {pc}/{ns}")
|
| results.append(res)
|
|
|
| elapsed = time.time() - t_start
|
|
|
|
|
| total = len(results)
|
| passed = sum(1 for r in results if r["passed"])
|
| pass1 = passed / total * 100 if total > 0 else 0.0
|
|
|
|
|
| if args.samples_per_task > 1:
|
| pass_at_1_unbiased = (
|
| sum(pass_at_k(r["n_samples"], r["pass_count"], 1) for r in results)
|
| / total
|
| * 100
|
| )
|
| else:
|
| pass_at_1_unbiased = pass1
|
|
|
|
|
| print(f"\n{'β' * 65}")
|
| print(f" RESULTADO HumanEval β {elapsed:.0f}s total")
|
| print(f"{'β' * 65}")
|
| print(f" Problemas evaluados : {total}")
|
| print(f" pass@1 : {pass1:.1f}% ({passed}/{total})")
|
| if args.samples_per_task > 1:
|
| print(f" pass@1 (unbiased) : {pass_at_1_unbiased:.1f}%")
|
| print(f" Modelo : {n_params:.1f}M params")
|
| print(f" Device : {device}")
|
| print(f"{'β' * 65}\n")
|
|
|
|
|
| output_path = Path(args.output)
|
| output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
| summary = {
|
| "model": "PAMPAr-Coder V3",
|
| "params_m": round(n_params, 1),
|
| "checkpoint": str(checkpoint),
|
| "benchmark": "HumanEval",
|
| "n_problems": total,
|
| "samples_per_task": args.samples_per_task,
|
| "temperature": args.temp,
|
| "pass_at_1_pct": round(pass1, 2),
|
| "pass_at_1_unbiased_pct": round(pass_at_1_unbiased, 2),
|
| "passed": passed,
|
| "total": total,
|
| "elapsed_sec": round(elapsed, 1),
|
| "device": str(device),
|
| "date": time.strftime("%Y-%m-%d %H:%M"),
|
| "results": results,
|
| }
|
|
|
| output_path.write_text(json.dumps(summary, indent=2, ensure_ascii=False))
|
| print(f" Resultados guardados en: {output_path}\n")
|
|
|
|
|
| if __name__ == "__main__":
|
| main()
|
|
|