Files
wehub-resource-sync e64161ec32
CI / ci (3.11) (push) Has been cancelled
CI / ci (3.10) (push) Has been cancelled
CI / dependabot (push) Has been cancelled
Release / release_and_publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:36:15 +08:00

513 lines
18 KiB
Python

"""
Standalone test script for API-based benchmark testing.
Usage:
python test_benchmark_api.py
Uses OpenAI-compatible API with Docker environment for running opencompass.
"""
from __future__ import annotations
import json
import os
from datetime import datetime
from pathlib import Path
# Set FT_file_path BEFORE importing rdagent modules (so Docker mounts correct path)
_project_root = Path(__file__).resolve().parents[2]
os.environ["FT_file_path"] = str(_project_root / "git_ignore_folder" / "finetune_files")
import pandas as pd
from rdagent.components.coder.finetune.conf import get_benchmark_env
from rdagent.scenarios.finetune.benchmark.benchmark import get_benchmark_ranges
from rdagent.scenarios.finetune.benchmark.data.adaptor import BENCHMARK_CONFIG_DICT
from rdagent.scenarios.finetune.benchmark.data.default import extract_error_samples
# OpenCompass API config template
API_CONFIG_TEMPLATE = """
from mmengine.config import read_base
from opencompass.models import OpenAI
# ==================== Dataset Import ====================
with read_base():
{dataset_imports}
# Aggregate all dataset variables
datasets = sum([v for k, v in locals().items() if (k == 'datasets' or k.endswith('_datasets')) and isinstance(v, list)], [])
# Apply dataset modifications
for ds in datasets:
{limit_config}
pass
# ==================== API Model Configuration ====================
api_meta_template = dict(round=[
dict(role='HUMAN', api_role='HUMAN'),
dict(role='BOT', api_role='BOT', generate=True),
])
models = [
dict(
abbr='{model_abbr}',
type=OpenAI,
path='{model_path}',
key='{api_key}',
openai_api_base='{api_base}',
meta_template=api_meta_template,
query_per_second={query_per_second},
max_out_len={max_out_len},
max_seq_len={max_seq_len},
batch_size={batch_size},
retry={retry},
),
]
# ==================== Inference Configuration ====================
infer = dict(
partitioner=dict(type='NaivePartitioner'),
runner=dict(
type='LocalRunner',
max_num_workers={max_num_workers},
retry=2,
task=dict(type='OpenICLInferTask'),
),
)
# ==================== Evaluation Configuration ====================
eval = dict(
partitioner=dict(type='NaivePartitioner'),
runner=dict(
type='LocalRunner',
max_num_workers=4,
retry=2,
task=dict(type='OpenICLEvalTask', dump_details=True),
),
)
# ==================== Work Directory ====================
work_dir = '{work_dir}'
"""
def generate_api_config(
model_abbr: str,
model_path: str,
api_key: str,
api_base: str,
dataset_imports: list[str],
limit: int | None = None,
offset: int = 0,
test_range: str | None = None,
work_dir: str = "/workspace",
max_out_len: int = 8192,
max_seq_len: int = 32768,
batch_size: int = 8,
query_per_second: int = 1,
max_num_workers: int = 16,
retry: int = 5,
) -> str:
"""Generate OpenCompass config for API-based model evaluation.
Args:
test_range: Direct test_range expression (e.g., "[:min(100, len(index_list)//2)]").
If provided, overrides limit/offset parameters.
"""
# Format dataset imports
dataset_import_lines = "\n".join(f" from {module} import *" for module in dataset_imports)
# Format limit config - support direct test_range or limit/offset
if test_range:
# Use direct test_range expression (supports dynamic expressions like len(index_list))
limit_config = f""" # Apply test_range for dataset sampling
if 'reader_cfg' not in ds:
ds['reader_cfg'] = {{}}
ds['reader_cfg']['test_range'] = '{test_range}'
# Sync to evaluator's dataset_cfg
if 'eval_cfg' in ds and 'evaluator' in ds['eval_cfg']:
evaluator = ds['eval_cfg']['evaluator']
if isinstance(evaluator, dict) and 'dataset_cfg' in evaluator:
if 'reader_cfg' not in evaluator['dataset_cfg']:
evaluator['dataset_cfg']['reader_cfg'] = {{}}
evaluator['dataset_cfg']['reader_cfg']['test_range'] = '{test_range}'"""
elif limit:
if offset:
computed_range = f"[{offset}:{offset + limit}]"
else:
computed_range = f"[:{limit}]"
limit_config = f""" # Limit dataset size for faster testing
if 'reader_cfg' not in ds:
ds['reader_cfg'] = {{}}
ds['reader_cfg']['test_range'] = '{computed_range}'
# Limit few-shot examples to avoid index out of range
# FixKRetriever uses fix_id_list to select examples from train/dev split
if 'infer_cfg' in ds and 'retriever' in ds['infer_cfg']:
retriever = ds['infer_cfg']['retriever']
if isinstance(retriever, dict) and 'fix_id_list' in retriever:
# Limit fix_id_list to valid range (0 to limit-1)
retriever['fix_id_list'] = [i for i in retriever['fix_id_list'] if i < {limit}]
# Sync to evaluator's dataset_cfg
if 'eval_cfg' in ds and 'evaluator' in ds['eval_cfg']:
evaluator = ds['eval_cfg']['evaluator']
if isinstance(evaluator, dict) and 'dataset_cfg' in evaluator:
if 'reader_cfg' not in evaluator['dataset_cfg']:
evaluator['dataset_cfg']['reader_cfg'] = {{}}
evaluator['dataset_cfg']['reader_cfg']['test_range'] = '{computed_range}'"""
else:
limit_config = ""
return API_CONFIG_TEMPLATE.format(
dataset_imports=dataset_import_lines,
limit_config=limit_config,
model_abbr=model_abbr,
model_path=model_path,
api_key=api_key,
api_base=api_base,
work_dir=work_dir,
max_out_len=max_out_len,
max_seq_len=max_seq_len,
batch_size=batch_size,
query_per_second=query_per_second,
max_num_workers=max_num_workers,
retry=retry,
)
def run_benchmark_api(
workspace_path: str,
model_name: str,
api_key: str,
api_base: str,
benchmark_name: str,
limit: int | None = 3,
offset: int = 0,
test_range: str | None = None,
max_error_samples: int = 5,
max_out_len: int = 8192,
max_seq_len: int = 32768,
batch_size: int = 8,
query_per_second: int = 1,
max_num_workers: int = 16,
retry: int = 5,
hf_token: str | None = None,
result_subdir: str = "",
):
"""
API-based benchmark runner using rdagent Docker env.
Args:
workspace_path: Local workspace path
model_name: API model name (e.g., gpt-4o-mini)
api_key: OpenAI API key
api_base: OpenAI API base URL (will be converted to Docker-accessible URL)
benchmark_name: Benchmark name
limit: Dataset limit (ignored if test_range is provided)
offset: Starting offset for dataset sampling (ignored if test_range is provided)
test_range: Direct test_range expression (e.g., "[:min(100, len(index_list)//2)]").
If provided, overrides limit/offset parameters.
max_error_samples: Max error samples to extract
max_out_len: Maximum output length
max_seq_len: Maximum sequence length
batch_size: Batch size for API calls
query_per_second: Rate limit for API calls
max_num_workers: Max number of workers for inference
hf_token: Hugging Face token for gated datasets
result_subdir: Subdirectory for results (e.g., "validation", "test")
"""
workspace = Path(workspace_path)
workspace.mkdir(parents=True, exist_ok=True)
cfg = BENCHMARK_CONFIG_DICT[benchmark_name]
# Auto download dependent data if configured
if cfg.download is not None:
cfg.download()
# Docker uses host network, so localhost works directly
# OpenAI class (inference) expects full URL with /chat/completions
docker_api_base = "http://localhost:3000/v1/chat/completions"
# OpenAISDK class (LLM judge) auto-appends /chat/completions, so use base only
docker_api_base_sdk = "http://localhost:3000/v1"
# Generate config.py
config_content = generate_api_config(
model_abbr=f"api-{benchmark_name}",
model_path=model_name,
api_key=api_key,
api_base=docker_api_base,
dataset_imports=[cfg.dataset],
limit=limit,
offset=offset,
test_range=test_range,
work_dir="/workspace",
max_out_len=max_out_len,
max_seq_len=max_seq_len,
batch_size=batch_size,
query_per_second=query_per_second,
max_num_workers=max_num_workers,
retry=retry,
)
config_file = workspace / "config.py"
config_file.write_text(config_content)
# Get Docker env with cache enabled
env = get_benchmark_env()
env.conf.enable_cache = True
# Environment variables for LLM judge (required for cascade eval benchmarks like AIME25)
# Note: LLM judge uses OpenAISDK which auto-appends /chat/completions
env_vars = {
"OC_JUDGE_MODEL": model_name,
"OC_JUDGE_API_KEY": api_key,
"OC_JUDGE_API_BASE": docker_api_base_sdk, # SDK auto-appends /chat/completions
"OC_JUDGE_RETRY": "3",
# Pass API credentials for use inside Docker
"OPENAI_API_KEY": api_key,
"OPENAI_BASE_URL": docker_api_base_sdk, # SDK auto-appends /chat/completions
}
# Add HF token for gated datasets (e.g., ChemCoTBench)
if hf_token:
env_vars["HF_TOKEN"] = hf_token
# Run opencompass in Docker with --debug to avoid subprocess segfault
if result_subdir:
benchmark_work_dir = f"/workspace/benchmark_results/{result_subdir}"
else:
benchmark_work_dir = "/workspace/benchmark_results"
cmd = f"opencompass /workspace/config.py --work-dir {benchmark_work_dir} --debug"
print(f"Running in Docker: {cmd}")
print(f"API Base (Docker): {docker_api_base}")
if offset:
print(f"Dataset range: [{offset}:{offset + limit}]")
result = env.run(
entry=cmd,
local_path=str(workspace),
env=env_vars,
)
print(f"Exit code: {result.exit_code}")
if result.exit_code != 0:
print(f"Error: {result.stdout[-2000:] if result.stdout else 'No output'}")
raise RuntimeError(f"Benchmark failed with exit code {result.exit_code}")
# Extract results from local workspace
work_dir = workspace / "benchmark_results"
if result_subdir:
work_dir = work_dir / result_subdir
timestamped_dirs = sorted(work_dir.glob("202*_*"), reverse=True)
if not timestamped_dirs:
raise RuntimeError(f"No results found in {work_dir}")
result_dir = timestamped_dirs[0]
csv_files = sorted(result_dir.rglob("summary/*.csv"), reverse=True)
if not csv_files:
raise RuntimeError(f"No CSV files found in {result_dir}")
# Parse benchmark results from CSV, grouped by dataset
df = pd.read_csv(csv_files[0])
# Get score column (the model name column, e.g., 'api-chemcotbench')
score_col = [c for c in df.columns if c not in ["dataset", "version", "metric", "mode"]][0]
# Pivot to group by dataset, with metrics as columns (use pivot_table to handle duplicates)
pivoted = df.pivot_table(index="dataset", columns="metric", values=score_col, aggfunc="first").to_dict("index")
# Filter out NaN values (different datasets have different metrics)
benchmark_results = {ds: {k: v for k, v in metrics.items() if pd.notna(v)} for ds, metrics in pivoted.items()}
# Extract error samples
errors = extract_error_samples(
result_dir,
max_samples=max_error_samples,
)
return {"benchmark_results": benchmark_results, "error_samples": errors}
if __name__ == "__main__":
# Change to project root (required for template resolution)
os.chdir(_project_root)
# ==================== API Configuration ====================
API_KEY = "sk-1234"
API_BASE = "http://localhost:3000"
MODEL = "gpt-4o-mini"
HF_TOKEN = "hf_xxxx" # For gated datasets
# ==================== Test Configuration ====================
MAX_OUT_LEN = 8192
MAX_SEQ_LEN = 32768
BATCH_SIZE = 8
QUERY_PER_SECOND = 1
MAX_NUM_WORKERS = 16
# Create test directory
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
test_base = _project_root / "git_ignore_folder" / "test_api" / timestamp
# ==================== Test Mode Selection ====================
# Set to True to test get_benchmark_ranges() with validation/test splits
TEST_BENCHMARK_RANGES = True
if TEST_BENCHMARK_RANGES:
# Test get_benchmark_ranges() with AIME25 (small dataset, 15 samples per subset)
val_range, test_range = get_benchmark_ranges()
print("=" * 60)
print("TESTING get_benchmark_ranges() NON-OVERLAPPING SPLITS")
print("=" * 60)
print(f"Validation range: {val_range}")
print(f"Test range: {test_range}")
print(f"API Base: {API_BASE}")
print(f"Output: {test_base}")
print("=" * 60)
# Test with AIME25 - a small dataset (15 samples per subset)
BENCHMARK = "aime25"
results_summary = {}
for split_name, split_range in [("validation", val_range), ("test", test_range)]:
print(f"\n{'='*60}")
print(f"Running: {BENCHMARK} - {split_name} split")
print(f"test_range: {split_range}")
print("=" * 60)
workspace = test_base / BENCHMARK / split_name
result = run_benchmark_api(
workspace_path=str(workspace),
model_name=MODEL,
api_key=API_KEY,
api_base=API_BASE,
benchmark_name=BENCHMARK,
limit=None, # Disabled, use test_range instead
test_range=split_range,
max_error_samples=5,
max_out_len=MAX_OUT_LEN,
max_seq_len=MAX_SEQ_LEN,
batch_size=BATCH_SIZE,
query_per_second=QUERY_PER_SECOND,
max_num_workers=MAX_NUM_WORKERS,
hf_token=HF_TOKEN,
result_subdir=split_name,
)
error_samples = result.get("error_samples", [])
benchmark_results = result.get("benchmark_results", {})
# Save result to workspace
result_file = workspace / "result.json"
with open(result_file, "w", encoding="utf-8") as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f" Result saved to: {result_file}")
print(f" Results: {benchmark_results}")
print(f" Error samples: {len(error_samples)}")
results_summary[f"{BENCHMARK}_{split_name}"] = {
"error_count": len(error_samples),
"benchmark_results": benchmark_results,
}
print("\n" + "=" * 60)
print("SUMMARY - get_benchmark_ranges() TEST")
print("=" * 60)
for name, info in results_summary.items():
print(f" {name}: {info['benchmark_results']}")
else:
# Original test mode with fixed limit/offset
LIMIT = 3
print("=" * 60)
print(f"API BENCHMARK TEST: {MODEL} (limit={LIMIT})")
print(f"API Base: {API_BASE}")
print(f"Output: {test_base}")
print("=" * 60)
results_summary = {}
# Hardcoded benchmark list - comment/uncomment to select benchmarks to test
BENCHMARKS_TO_TEST = [
# Math Reasoning
# "aime24",
# "aime25",
# "math",
# General Knowledge
# "mmlu",
# Code Generation
# "humaneval",
# "mbpp",
# PANORAMA - Patent Analysis (zero-shot)
"panorama",
"panorama_par4pc",
"panorama_pi4pc",
"panorama_noc4pc",
# PANORAMA - Patent Analysis (CoT)
"panorama_par4pc_cot",
"panorama_pi4pc_cot",
"panorama_noc4pc_cot",
# ChemCoTBench - Chemistry Reasoning
"chemcotbench",
"chemcotbench_mol_und",
"chemcotbench_mol_edit",
"chemcotbench_mol_opt",
"chemcotbench_reaction",
# TableBench - Table QA
"tablebench_data_analysis",
"tablebench_fact_checking",
"tablebench_numerical_reasoning",
"tablebench_visualization",
"tablebench_gen",
# Finance
"FinanceIQ_gen",
]
for benchmark_name in BENCHMARKS_TO_TEST:
print(f"\n{'='*60}")
print(f"Running: {benchmark_name}")
print("=" * 60)
workspace = test_base / benchmark_name
result = run_benchmark_api(
workspace_path=str(workspace),
model_name=MODEL,
api_key=API_KEY,
api_base=API_BASE,
benchmark_name=benchmark_name,
limit=LIMIT,
max_error_samples=5,
max_out_len=MAX_OUT_LEN,
max_seq_len=MAX_SEQ_LEN,
batch_size=BATCH_SIZE,
query_per_second=QUERY_PER_SECOND,
max_num_workers=MAX_NUM_WORKERS,
hf_token=HF_TOKEN,
offset=100,
)
error_samples = result.get("error_samples", [])
benchmark_results = result.get("benchmark_results", [])
# Save result to workspace
result_file = workspace / "result.json"
with open(result_file, "w", encoding="utf-8") as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f" Result saved to: {result_file}")
print(f" Results: {benchmark_results}")
print(f" Error samples: {len(error_samples)}")
if error_samples:
print(f" Sample: {error_samples[0]}")
results_summary[benchmark_name] = {
"error_count": len(error_samples),
"benchmark_results": benchmark_results,
}
print("\n" + "=" * 60)
print("SUMMARY")
print("=" * 60)
for name, info in results_summary.items():
print(f" {name}: errors={info['error_count']}")