""" AIME Dataset Evaluation Module This module provides functions to evaluate language models on the combined AIME dataset (test2024 + test2025-I + test2025-II). """ import json import requests import os import re import logging from typing import List, Dict, Any from tqdm import tqdm from vllm import SamplingParams def download_and_combine_aime_datasets(data_dir: str = "./data/aime") -> str: """Download all AIME datasets and combine them into a single file""" datasets = { "test2024": "https://raw.githubusercontent.com/GAIR-NLP/AIME-Preview/main/eval/data/aime/test2024.jsonl", "test2025-I": "https://raw.githubusercontent.com/GAIR-NLP/AIME-Preview/main/eval/data/aime/test2025-I.jsonl", "test2025-II": "https://raw.githubusercontent.com/GAIR-NLP/AIME-Preview/main/eval/data/aime/test2025-II.jsonl" } os.makedirs(data_dir, exist_ok=True) combined_filepath = os.path.join(data_dir, "aime.jsonl") # Check if combined file already exists if os.path.exists(combined_filepath): print(f"Combined AIME dataset already exists at {combined_filepath}") return combined_filepath print("Downloading and combining AIME datasets...") all_problems = [] global_id = 0 for dataset_name, url in datasets.items(): print(f" Downloading {dataset_name}...") try: response = requests.get(url) response.raise_for_status() # Parse each line and add source information for line_num, line in enumerate(response.text.strip().split('\n')): if line.strip(): try: data = json.loads(line) # Add source dataset information and global ID data['source_dataset'] = dataset_name data['original_id'] = data.get('id', line_num) data['global_id'] = global_id global_id += 1 all_problems.append(data) except json.JSONDecodeError as e: print(f" Warning: Error parsing line {line_num + 1} in {dataset_name}: {e}") continue except requests.RequestException as e: print(f" Error downloading {dataset_name}: {e}") continue # Write combined dataset if all_problems: with open(combined_filepath, 'w', encoding='utf-8') as f: for problem in all_problems: f.write(json.dumps(problem, ensure_ascii=False) + '\n') print(f"āœ… Combined {len(all_problems)} problems from {len(datasets)} datasets") print(f" Saved to: {combined_filepath}") # Print summary by dataset for dataset_name in datasets.keys(): count = sum(1 for p in all_problems if p['source_dataset'] == dataset_name) print(f" {dataset_name}: {count} problems") else: raise RuntimeError("No problems were successfully downloaded") return combined_filepath def load_aime_dataset(data_dir: str = "./data/aime") -> List[Dict[str, Any]]: """Load combined AIME dataset and format for evaluation""" # Download and combine if needed filepath = download_and_combine_aime_datasets(data_dir) examples = [] with open(filepath, 'r', encoding='utf-8') as f: for line_num, line in enumerate(f): line = line.strip() if line: try: data = json.loads(line) # Format as expected by our evaluation formatted_example = { "global_id": data.get("global_id", line_num), "original_id": data.get("original_id", data.get("id", line_num)), "source_dataset": data.get("source_dataset", "unknown"), "problem": data["problem"], "answer": str(data["answer"]), # Ensure answer is string "solution": data.get("solution", ""), "url": data.get("url", ""), # Format as chat messages for the model "prompt": [ {"role": "system", "content": "You are a mathematical problem solver. Solve the given problem step by step and provide your final answer clearly."}, {"role": "user", "content": f"Problem: {data['problem']}\n\nSolve this step by step and provide your final numerical answer."} ] } examples.append(formatted_example) except json.JSONDecodeError as e: print(f"Error parsing line {line_num + 1}: {e}") continue print(f"Loaded {len(examples)} problems from combined AIME dataset") # Print breakdown by source source_counts = {} for example in examples: source = example['source_dataset'] source_counts[source] = source_counts.get(source, 0) + 1 for source, count in source_counts.items(): print(f" {source}: {count} problems") return examples def extract_aime_answer(response: str) -> str: """Extract numerical answer from AIME response""" # AIME answers are integers from 0-999 # Look for patterns like "The answer is 123" or just standalone numbers patterns = [ r"(?:the )?(?:final )?answer is (\d{1,3})", r"(?:therefore|thus|so),?\s*(?:the )?(?:final )?answer is (\d{1,3})", r"\\boxed\{(\d{1,3})\}", r"\$\\boxed\{(\d{1,3})\}\$", r"(?:answer|result):\s*(\d{1,3})", r"(?:^|\n)\s*(\d{1,3})\s*(?:\n|$)", # Standalone number ] response_lower = response.lower().strip() for pattern in patterns: matches = re.findall(pattern, response_lower, re.MULTILINE | re.IGNORECASE) if matches: # Get the last match (most likely to be final answer) answer = matches[-1] try: num = int(answer) if 0 <= num <= 999: # AIME answers are in range 0-999 return str(num) except ValueError: continue # If no clear pattern found, try to extract any 1-3 digit number numbers = re.findall(r'\b(\d{1,3})\b', response) if numbers: for num_str in reversed(numbers): # Check from end try: num = int(num_str) if 0 <= num <= 999: return str(num) except ValueError: continue return "" def get_num_tokens(text, tokenizer_instance): """Count tokens in text""" if not text: return 0 encoding = tokenizer_instance(text, return_tensors="pt") return len(encoding["input_ids"][0]) def evaluate_model_aime(model, tokenizer, model_type="base", lora_request=None, temperature=0.3, n_sampling=8, max_tokens=32768, top_p=0.95, seed=0): """Evaluate model on combined AIME dataset with official configuration""" print(f"\n{'='*70}") print(f"🧮 AIME EVALUATION - {model_type.upper()} MODEL") print(f"Combined Dataset: test2024 + test2025-I + test2025-II") print(f"{'='*70}") # Load combined AIME dataset try: eval_dataset = load_aime_dataset() except Exception as e: print(f"Error loading dataset: {e}") return None if not eval_dataset: print("No examples found in dataset") return None # Initialize tracking variables records = {} input_tokens = [] output_tokens = [] correct_answers = 0 # Track performance by source dataset source_stats = {} for example in eval_dataset: source = example['source_dataset'] if source not in source_stats: source_stats[source] = {'total': 0, 'correct': 0} source_stats[source]['total'] += 1 # Setup sampling parameters (AIME configuration) sampling_params = SamplingParams( temperature=temperature, top_p=top_p, max_tokens=max_tokens, n=n_sampling, # Multiple samples per question seed=seed, ) print(f"\nšŸ”§ Configuration:") print(f" Temperature: {temperature}") print(f" Samples per question: {n_sampling}") print(f" Max tokens: {max_tokens}") print(f" Top-p: {top_p}") print(f" Seed: {seed}") # Temporarily suppress verbose logging original_levels = {} loggers_to_suppress = ['vllm', 'vllm.engine', 'vllm.worker', 'vllm.model_executor', 'vllm.executor', 'ray'] for logger_name in loggers_to_suppress: logger = logging.getLogger(logger_name) original_levels[logger_name] = logger.level logger.setLevel(logging.WARNING) try: print(f"\nšŸš€ Evaluating {len(eval_dataset)} problems...") # Main evaluation loop with tqdm(total=len(eval_dataset), desc="Processing AIME problems", unit="problem") as pbar: for task_id, item in enumerate(eval_dataset): try: # Prepare prompt prompt_text = tokenizer.apply_chat_template( item["prompt"], add_generation_prompt=True, tokenize=False ) input_tokens.append(get_num_tokens(prompt_text, tokenizer)) # Generate multiple responses outputs = model.fast_generate( [prompt_text], sampling_params=sampling_params, lora_request=lora_request, use_tqdm=False, )[0].outputs # Process all generated responses responses = [output.text for output in outputs] extracted_answers = [extract_aime_answer(response) for response in responses] # Calculate total output tokens total_output_tokens = sum(get_num_tokens(response, tokenizer) for response in responses) output_tokens.append(total_output_tokens) # Check if any answer is correct ground_truth = item["answer"] correct_responses = [ans == ground_truth for ans in extracted_answers] is_correct = any(correct_responses) if is_correct: correct_answers += 1 source_stats[item['source_dataset']]['correct'] += 1 # Store detailed record records[task_id] = { "global_id": item["global_id"], "original_id": item["original_id"], "source_dataset": item["source_dataset"], "problem": item["problem"], "ground_truth": ground_truth, "responses": responses, "extracted_answers": extracted_answers, "correct_responses": correct_responses, "is_correct": is_correct, "input_tokens": input_tokens[-1], "output_tokens": total_output_tokens, "n_correct": sum(correct_responses), "n_total": len(responses), "solution": item.get("solution", ""), "url": item.get("url", "") } # Update progress current_accuracy = correct_answers / (task_id + 1) * 100 pbar.set_postfix({ 'accuracy': f'{current_accuracy:.1f}%', 'correct': correct_answers, 'total': task_id + 1 }) pbar.update(1) except Exception as e: print(f"\nError processing problem {task_id}: {str(e)}") records[task_id] = { "global_id": item.get("global_id", task_id), "original_id": item.get("original_id", task_id), "source_dataset": item.get("source_dataset", "unknown"), "problem": item["problem"], "ground_truth": item["answer"], "error": str(e), "is_correct": False } pbar.update(1) continue finally: # Restore logging levels for logger_name, level in original_levels.items(): logging.getLogger(logger_name).setLevel(level) # Calculate metrics total_problems = len(eval_dataset) accuracy = correct_answers / total_problems * 100 # Calculate Pass@k (probability that at least one of k samples is correct) pass_at_k_scores = [] for record in records.values(): if "n_correct" in record and "n_total" in record: n_correct = record["n_correct"] n_total = record["n_total"] if n_correct > 0: pass_at_k_scores.append(1.0) else: pass_at_k_scores.append(0.0) pass_at_k = sum(pass_at_k_scores) / len(pass_at_k_scores) if pass_at_k_scores else 0 # Calculate per-source accuracies source_accuracies = {} for source, stats in source_stats.items(): source_accuracies[source] = (stats['correct'] / stats['total'] * 100) if stats['total'] > 0 else 0 results = { "model_type": model_type, "dataset": "aime_combined", "total_problems": total_problems, "correct_answers": correct_answers, "accuracy": accuracy, "pass_at_k": pass_at_k * 100, "source_stats": source_stats, "source_accuracies": source_accuracies, "temperature": temperature, "n_sampling": n_sampling, "max_tokens": max_tokens, "top_p": top_p, "seed": seed, "avg_input_tokens": sum(input_tokens) / len(input_tokens) if input_tokens else 0, "avg_output_tokens": sum(output_tokens) / len(output_tokens) if output_tokens else 0, "max_input_tokens": max(input_tokens) if input_tokens else 0, "max_output_tokens": max(output_tokens) if output_tokens else 0, } # Save results filename = f"aime_eval_combined_{model_type}_t{temperature}_n{n_sampling}.json" with open(filename, "w", encoding="utf-8") as f: json.dump({"results": results, "records": records}, f, indent=4) # Print comprehensive summary print(f"\n{'='*70}") print(f"šŸ“Š AIME EVALUATION RESULTS - {model_type.upper()}") print(f"{'='*70}") print(f"\nšŸŽÆ Overall Performance:") print(f" Total problems: {total_problems:>6}") print(f" Correct answers: {correct_answers:>6}/{total_problems} ({accuracy:>5.1f}%)") print(f" Pass@{n_sampling}: {pass_at_k:>10.1f}%") print(f"\nšŸ“ˆ Performance by Dataset:") for source, stats in source_stats.items(): source_acc = source_accuracies[source] print(f" {source:>12}: {stats['correct']:>3}/{stats['total']:>3} ({source_acc:>5.1f}%)") print(f"\nšŸ”§ Configuration:") print(f" Temperature: {temperature}") print(f" Samples per problem: {n_sampling}") print(f" Max tokens: {max_tokens}") print(f" Top-p: {top_p}") print(f" Seed: {seed}") print(f"\nšŸ“ Token Statistics:") print(f" Avg input tokens: {results['avg_input_tokens']:>10.1f}") print(f" Avg output tokens: {results['avg_output_tokens']:>10.1f}") print(f" Max input tokens: {results['max_input_tokens']:>10}") print(f" Max output tokens: {results['max_output_tokens']:>10}") # Performance assessment for AIME if accuracy >= 50: tier = "šŸ† EXCEPTIONAL" elif accuracy >= 30: tier = "āœ… EXCELLENT" elif accuracy >= 20: tier = "šŸŽÆ VERY GOOD" elif accuracy >= 10: tier = "āš ļø GOOD" elif accuracy >= 5: tier = "šŸ“ˆ FAIR" else: tier = "āŒ NEEDS IMPROVEMENT" print(f"\nšŸŽ–ļø AIME Performance: {tier} ({accuracy:.1f}%)") print(f"\nšŸ’¾ Detailed results saved to: {filename}") print(f"\n{'='*70}") return results # Comparison functions for multiple model results def compare_aime_results(all_results): """Generate comprehensive comparison for AIME evaluation results""" print(f"\n{'='*80}") print("COMPREHENSIVE AIME MODEL COMPARISON") print(f"{'='*80}") # Main comparison table print(f"{'Model':<15} {'Accuracy %':<12} {'Pass@K %':<10} {'Correct':<8} {'Total':<8}") print("-" * 80) for result in all_results: print(f"{result['model_type']:<15} " f"{result['accuracy']:<12.1f} " f"{result['pass_at_k']:<10.1f} " f"{result['correct_answers']:<8} " f"{result['total_problems']:<8}") # Performance improvement analysis if len(all_results) > 1: print(f"\n{'='*50}") print("IMPROVEMENT ANALYSIS") print(f"{'='*50}") base_result = all_results[0] # Assume first is base model for i, result in enumerate(all_results[1:], 1): print(f"\n{result['model_type']} vs {base_result['model_type']}:") accuracy_improvement = result['accuracy'] - base_result['accuracy'] pass_k_improvement = result['pass_at_k'] - base_result['pass_at_k'] print(f" Accuracy improvement: {accuracy_improvement:+.1f}%") print(f" Pass@K improvement: {pass_k_improvement:+.1f}%") # Dataset breakdown print(f"\n{'='*50}") print("PERFORMANCE BY DATASET") print(f"{'='*50}") # Get all unique datasets from the first result if all_results and 'source_accuracies' in all_results[0]: datasets = list(all_results[0]['source_accuracies'].keys()) print(f"{'Model':<15}", end="") for dataset in datasets: print(f"{dataset:<15}", end="") print() print("-" * (15 + 15 * len(datasets))) for result in all_results: print(f"{result['model_type']:<15}", end="") for dataset in datasets: accuracy = result['source_accuracies'].get(dataset, 0) print(f"{accuracy:<15.1f}", end="") print() # Save comparison comparison_data = { "summary": all_results, "best_model": max(all_results, key=lambda x: x['accuracy']), } with open("aime_model_comparison.json", "w") as f: json.dump(comparison_data, f, indent=4) print(f"\nBest performing model: {comparison_data['best_model']['model_type']} " f"({comparison_data['best_model']['accuracy']:.1f}% accuracy)")