Quintus / sft /evaluate.py
iamrahulreddy's picture
release: publish Quintus project files
4fc1bb9 verified
Raw
History Blame Contribute Delete
10.4 kB
# Automated EvalPlus runner for HumanEval and MBPP benchmarks.
# Using the vLLM backend in greedy mode.
import os
import sys
import subprocess
import time
import json
import re
from pathlib import Path
from datetime import datetime
from huggingface_hub import snapshot_download
MODELS = [
{
"name": "Quintus-1.7B",
"id": "iamrahulreddy/Quintus",
"is_local": False
},
{
"name": "Qwen3-1.7B-Instruct",
"id": "Qwen/Qwen3-1.7B",
"is_local": False
},
{
"name": "Qwen3-1.7B-Base",
"id": "Qwen/Qwen3-1.7B-Base",
"is_local": False
}
]
DATASETS = [
"humaneval", "mbpp", # EvalPlus benchmarks
"gsm8k", "winogrande", # lm-eval fast benchmarks
"arc_challenge", "boolq", "piqa"
]
EVALPLUS_DATASETS = {"humaneval", "mbpp"}
LM_EVAL_SHOTS = {
"gsm8k": "10",
"winogrande": "5",
"arc_challenge": "25",
"boolq": "0",
"piqa": "0"
}
HF_TOKEN = os.environ.get("HF_TOKEN")
TRUST_REMOTE_CODE = os.environ.get("QUINTUS_TRUST_REMOTE_CODE", "").strip().lower() in {"1", "true", "yes", "on"}
def extract_lm_eval_score(results_dir: Path, task: str) -> str:
"""Finds and extracts the primary score from JSON files outputted by lm-evaluation-harness."""
for json_path in sorted(results_dir.rglob("*.json"), reverse=True):
try:
with open(json_path, encoding="utf-8") as fh:
data = json.load(fh)
task_results = data.get("results", {})
for candidate in (task, f"leaderboard_{task}"):
if candidate in task_results:
task_data = task_results[candidate]
# Try common metric names
for metric in ["acc,none", "acc_norm,none", "exact_match,strict-match", "exact_match,none"]:
if metric in task_data:
return f"{task_data[metric]*100:.1f}"
except Exception:
continue
return "N/A"
def is_noise(line: str) -> bool:
l = line.strip()
if not l:
return False
# Progress bar indicators & block characters
if any(c in l for c in ["█", "━", "╸", "•", "━━━━━━━━"]):
return True
# vLLM, ray, flash_attn, huggingface setup/warnings logs
noise_keywords = [
"INFO ", "WARNING ", "DEBUG ", "ERROR ", "(EngineCore",
"Loading safetensors", "Capturing CUDA graphs",
"Codegen:", "Downloading dataset", "downloading dataset",
"Initializing a decoder", "Unknown vLLM environment",
"world_size=", "Using V2 Model Runner", "Model loading took",
"Using FLASH_ATTN", "Using FlashAttention", "Kernel JIT monitor",
"autotuner.py", "autotuning", "Autotuning", "loading weights",
"Loading weights", "Failed to get device capability", "Sanitized code outputs",
"Raw outputs will be saved", "init engine", "Dynamo bytecode",
"Directly load the compiled graph", "Directly load AOT compilation", "torch.compile took"
]
if any(k.lower() in l.lower() for k in noise_keywords):
return True
# TQDM lines (e.g. 100%|... [00:17<00:00, 9.45it/s])
if "%|" in l and ("it/s" in l or "s/it" in l):
return True
return False
def main():
print("=" * 80)
print(" EVALPLUS BENCHMARK RUNNER (HUMANEVAL & MBPP)")
print("=" * 80)
print(f"Timestamp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Models to evaluate: {[m['name'] for m in MODELS]}")
print(f"Datasets: {DATASETS}")
print("=" * 80)
# Set optional HF token and runtime configuration.
if HF_TOKEN:
os.environ["HF_TOKEN"] = HF_TOKEN
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
os.environ["TOKENIZERS_PARALLELISM"] = "false"
os.environ["VLLM_MAX_MODEL_LEN"] = "4096"
# Step 1: Pre-download and prepare model caches
print("\n--- STAGE 1: WARMING UP MODEL WEIGHTS CACHE ---")
# Cache all models
for model in MODELS:
if model["is_local"]:
continue
print(f"\n[DOWNLOADING] Fetching cache for {model['name']} ({model['id']})...")
try:
snapshot_download(
repo_id=model["id"],
token=HF_TOKEN or None
)
print(f"[DOWNLOAD SUCCESS] {model['name']} is cached and ready.")
except Exception as e:
print(f"[DOWNLOAD WARNING] Could not pre-download model {model['name']} via snapshot_download: {e}")
print("The evaluation run will attempt to download it directly during execution.")
print("\n--- STAGE 2: SEQUENTIAL EVALPLUS EVALUATION ---")
results = []
# Run evaluations sequentially
for model in MODELS:
# Resolve path
model_path = str(Path(model["id"]).resolve()) if model["is_local"] else model["id"]
for dataset in DATASETS:
print(f"\n[STARTING] Evaluating {model['name']} on {dataset}...")
print("-" * 60)
if dataset in EVALPLUS_DATASETS:
cmd = [
sys.executable, "-m", "evalplus.evaluate",
"--model", model_path,
"--dataset", dataset,
"--backend", "vllm",
"--greedy"
]
else:
shots = LM_EVAL_SHOTS.get(dataset, "0")
out_dir = Path("eval_results") / model["name"] / dataset
out_dir.mkdir(parents=True, exist_ok=True)
model_args = (
f"pretrained={model_path},dtype=bfloat16,"
f"trust_remote_code={str(TRUST_REMOTE_CODE).lower()},"
"gpu_memory_utilization=0.9,max_model_len=4096"
)
cmd = [
sys.executable, "-m", "lm_eval",
"--model", "vllm",
"--model_args", model_args,
"--tasks", dataset,
"--num_fewshot", shots,
"--batch_size", "auto",
"--output_path", str(out_dir),
"--log_samples"
]
if dataset == "gsm8k":
cmd.extend(["--gen_kwargs", "max_gen_toks=512"])
print(f"Running command: {' '.join(cmd)}")
start_time = time.time()
try:
# Run the command and stream output
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1
)
# Stream and capture output (filtering out vLLM and progress bar noise)
stdout_text = ""
for line in process.stdout:
stdout_text += line
if not is_noise(line):
print(line, end="")
process.wait()
duration = time.time() - start_time
time.sleep(5) # Let OS/driver fully reclaim GPU VRAM before starting next subprocess
score_str = "N/A"
if process.returncode == 0:
print(f"[SUCCESS] Completed {model['name']} on {dataset} in {duration:.1f} seconds.")
# Parse scores
if dataset in EVALPLUS_DATASETS:
# Find all pass@1 scores
matches = re.findall(r"pass@1:\s+([0-9.]+)", stdout_text)
if len(matches) >= 2:
val0 = float(matches[0])
val1 = float(matches[1])
if val0 <= 1.0: val0 *= 100
if val1 <= 1.0: val1 *= 100
score_str = f"Base: {val0:.1f} | Plus: {val1:.1f}"
elif len(matches) == 1:
val0 = float(matches[0])
if val0 <= 1.0: val0 *= 100
score_str = f"Base: {val0:.1f}"
else:
score_str = extract_lm_eval_score(out_dir, dataset)
results.append({
"model": model["name"],
"dataset": dataset,
"status": "Success",
"score": score_str,
"duration": f"{duration/60:.1f} min"
})
else:
print(f"[ERROR] command failed with exit code {process.returncode}")
results.append({
"model": model["name"],
"dataset": dataset,
"status": f"Failed ({process.returncode})",
"score": "ERROR",
"duration": f"{duration/60:.1f} min"
})
except Exception as e:
duration = time.time() - start_time
print(f"[ERROR] Failed to run benchmark: {e}")
results.append({
"model": model["name"],
"dataset": dataset,
"status": f"Error",
"score": "ERROR",
"duration": f"{duration/60:.1f} min"
})
print("-" * 60)
# Print and save summary report
report_lines = []
report_lines.append("\n" + "=" * 100)
report_lines.append(" BENCHMARK RUN SUMMARY")
report_lines.append("=" * 100)
report_lines.append(f"| {'Model':<30} | {'Dataset':<15} | {'Score':<25} | {'Status':<10} | {'Time':<8} |")
report_lines.append(f"|{'-'*32}|{'-'*17}|{'-'*27}|{'-'*12}|{'-'*10}|")
for r in results:
report_lines.append(f"| {r['model']:<30} | {r['dataset']:<15} | {r['score']:<25} | {r['status']:<10} | {r['duration']:<8} |")
report_lines.append("=" * 100)
report_text = "\n".join(report_lines)
print(report_text)
print("\nNote: Results are saved in the default EvalPlus directory and eval_results/.")
# Save to file
with open("qwen_quintus_scores.txt", "w", encoding="utf-8") as f:
f.write(report_text + "\n")
print("\n[SUCCESS] Final score report saved to 'qwen_quintus_scores.txt'")
if __name__ == "__main__":
main()