evalstone/bash/test.py
2026-07-21 09:32:49 +00:00

498 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import yaml
from copy import deepcopy
from pathlib import Path
import sys
import os
from evalscope import run_task, TaskConfig
from evalscope.api.agent import NativeAgentConfig
from evalscope.config import SandboxTaskConfig
SCRIPT_DIR = Path(__file__).parent.resolve()
PROJECT_ROOT = SCRIPT_DIR.parent
# ============================================================
# ★★★ 必改参数 (USER CONFIG) ★★★
# 每次评测新模型前,只需要检查/修改以下参数。
# 也可以通过命令行覆盖:--model / --api-url / --dataset-dir /
# --output-dir / --limit / --config
# ============================================================
# 模型名served model name
MODEL = 'DeepSeek-V4-Flash-Int8'
# 模型服务地址OpenAI 兼容 API
API_URL = 'http://localhost:30000/v1'
# 本地数据集根目录(提前下载好的 datasets 目录)
DATASET_DIR = str(PROJECT_ROOT / 'datasets')
# 评测输出目录(每个 benchmark 单独子目录)
OUTPUT_DIR = str(PROJECT_ROOT / 'output')
# 采样数量上限None 表示跑全部样本
LIMIT = None
# 每个 benchmark 的生成参数配置文件max_tokens / temperature 等)
CONFIG = None
# 是否开启 thinking 模式sglang chat_template_kwargs.thinking
ENABLE_THINKING = False
# 测试哪些 benchmark见下方 DATASETS = _multi_run_order + _single_run_order + _agent_order
# 想只跑部分 benchmark 时,注释掉对应行即可。
# ============================================================
# Command line argument overrides (不需要修改)
# ============================================================
for i, arg in enumerate(sys.argv):
if arg == '--limit' and i + 1 < len(sys.argv):
limit_val = sys.argv[i + 1]
if limit_val.lower() == 'none' or limit_val.lower() == 'all':
LIMIT = None
else:
LIMIT = int(limit_val)
elif arg == '--model' and i + 1 < len(sys.argv):
MODEL = sys.argv[i + 1]
elif arg == '--api-url' and i + 1 < len(sys.argv):
API_URL = sys.argv[i + 1]
elif arg == '--dataset-dir' and i + 1 < len(sys.argv):
DATASET_DIR = sys.argv[i + 1]
elif arg == '--output-dir' and i + 1 < len(sys.argv):
OUTPUT_DIR = sys.argv[i + 1]
elif arg == '--config' and i + 1 < len(sys.argv):
CONFIG = sys.argv[i + 1]
# Benchmarks that require sandboxed code execution
SANDBOX_DATASETS = {'humaneval', 'bigcodebench'}
# Per-dataset sandbox configs.
# bigcodebench's upstream image has ENTRYPOINT ["python3", "-m", "bigcodebench.evaluate"],
# which exits immediately and breaks ms_enclave exec. We built a derivative image
# `bigcodebench-sandbox:latest` that keeps the same Python environment but drops the
# entrypoint and runs `tail -f /dev/null` so the container stays alive.
SANDBOX_CONFIGS = {
'bigcodebench': {
'image': 'bigcodebench-sandbox:latest',
'working_dir': '/tmp',
'tools_config': {
'shell_executor': {},
'python_executor': {}
}
},
'humaneval': {
'image': 'python:3.11-slim',
'tools_config': {
'shell_executor': {},
'python_executor': {}
}
},
}
# ============================================================
# User-tunable parameters
# ============================================================
# Fixed seed for reproducibility. With temperature > 0 the model still samples
# randomly, so running N times with the same seed still yields variance.
SEED = 42
# Benchmarks to run multiple times with temperature=1.0.
# Format: {benchmark_name: num_runs}
# Number of runs chosen so total samples ≈ 400-500 per benchmark.
MULTI_RUN_CONFIG = {
# ~30 samples each -> 12 runs = ~360 samples
'aime24': 12,
'aime25': 12,
'aime26': 12,
'hmmt26': 12,
# ~100 samples -> 5 runs = ~500 samples
'live_code_bench': 5,
# ~120 samples -> 4 runs = ~480 samples
'imo_answerbench': 4,
# ~164 samples -> 3 runs = ~492 samples
'humaneval': 3,
# ~198 samples -> 2 runs = ~396 samples
'gpqa_diamond': 2,
}
# Single-run benchmarks (temperature=0.0 greedy)
SINGLE_RUN_DATASETS = [
'bigcodebench',
'bfcl_v3', # function calling: greedy decoding
'competition_math',
'gsm8k',
'hle',
'super_gpqa',
'arc',
'bbh',
'cmmlu',
'drop',
'hellaswag',
'mmlu',
'mmlu_pro',
'simple_qa',
'trivia_qa',
'winogrande',
'openai_mrcr', # long-context, run once
'longbench_v2', # long-context, run once
]
# Agent / tool benchmarks (temperature=0.0 greedy, run once)
AGENT_DATASETS = [
'tau2_bench',
'general_fc',
]
# Combine all datasets in order (shortest estimated time first)
# 1. Multi-run small benchmarks (temperature=1.0 for sampling diversity)
_multi_run_order = [
# 'humaneval', # ~164 samples x 3 runs = 492
# 'live_code_bench', # ~100 samples x 5 runs = 500
# 'aime26', # ~30 samples x 12 runs = 360
# 'aime24', # ~30 samples x 12 runs = 360
# 'aime25', # ~30 samples x 12 runs = 360
# 'gpqa_diamond', # ~198 samples x 2 runs = 396
# 'imo_answerbench', # ~120 samples x 4 runs = 480
# 'hmmt26', # ~30 samples x 12 runs = 360
]
# 2. Single-run benchmarks (temperature=0.0 greedy)
_single_run_order = [
# 'arc',
# 'bfcl_v3', # function calling: greedy decoding
# 'winogrande',
# 'competition_math',
# 'gsm8k',
# 'hellaswag',
'bigcodebench',
# 'drop',
# 'bbh',
# 'openai_mrcr',
# 'longbench_v2',
# 'mmlu',
# 'cmmlu',
# 'super_gpqa',
# 'simple_qa',
# 'mmlu_pro',
'hle',
# 'trivia_qa',
]
# 3. Agent / tool benchmarks (last)
_agent_order = [
# 'tau2_bench',
# 'general_fc',
]
DATASETS = _multi_run_order + _single_run_order + _agent_order
# ENABLE_THINKING 已移至文件头部「必改参数」区
BATCH_SIZE_LIST = [4]
# LIMIT is parsed from command line: --limit N
SHUFFLE = True
# ============================================================
# Fixed configuration
# ============================================================
CONFIG_PATH = str(Path(CONFIG) if CONFIG else SCRIPT_DIR / 'config' / 'dpv4-int8_nothinking.yaml')
if not Path(CONFIG_PATH).exists():
raise FileNotFoundError(f'Config file not found: {CONFIG_PATH}')
MODEL_PATH = '/data1/models/DeepSeek-V4-Flash-INT8'
# Judge model for benchmarks that require LLM-as-judge (simple_qa, tau2_bench, etc.)
# Vectron endpoint with DeepSeek-V4-Pro
JUDGE_MODEL_ARGS = {
'model_id': 'DeepSeek/DeepSeek-V4-Pro',
'api_url': 'https://api.vectron.meta-stone.com/v1',
'api_key': 'sk-dbd8a665f7634081b87ec409c7636500',
'eval_type': 'openai_api',
'generation_config': {
'temperature': 0.0,
'max_tokens': 10240,
},
}
TRUNCATION_CONFIG = {
'longbench_v2': 32768*4,
'openai_mrcr': 32768*4,
}
MATH_DATASETS = {
'aime24', 'aime25', 'aime26', 'hmmt26',
'gsm8k', 'competition_math', 'imo_answerbench',
}
MATH_PROMPT_TEMPLATE = (
"{question}\n"
"Please reason step by step, and put your final answer within \\boxed{{}}."
)
with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
DATASET_CONFIGS = yaml.safe_load(f)
# ============================================================
# Middle-truncation helpers
# ============================================================
_TOKENIZER = None
def get_tokenizer():
global _TOKENIZER
if _TOKENIZER is None:
from transformers import AutoTokenizer
try:
# Try local path first
_TOKENIZER = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True)
except Exception:
# Fallback to HuggingFace model ID
_TOKENIZER = AutoTokenizer.from_pretrained('deepseek-ai/DeepSeek-V4-Flash', trust_remote_code=True)
return _TOKENIZER
def truncate_middle(text: str, max_tokens: int) -> str:
if max_tokens <= 0:
return text
tokenizer = get_tokenizer()
token_ids = tokenizer.encode(text, add_special_tokens=False)
if len(token_ids) <= max_tokens:
return text
keep_head = max_tokens // 2
keep_tail = max_tokens - keep_head
truncated_ids = token_ids[:keep_head] + token_ids[-keep_tail:]
return tokenizer.decode(truncated_ids, skip_special_tokens=True)
def _patch_adapters_for_truncation():
from evalscope.benchmarks.longbench_v2.longbench_v2_adapter import LongBenchV2Adapter
from evalscope.benchmarks.openai_mrcr.openai_mrcr_adapter import OpenAIMRCRAdapter
# Patch LongBenchV2Adapter.format_prompt_template
_orig_longbench_format = LongBenchV2Adapter.format_prompt_template
def _patched_longbench_format(self, sample):
max_tok = TRUNCATION_CONFIG.get('longbench_v2')
if max_tok and sample.metadata and 'context' in sample.metadata:
sample.metadata['context'] = truncate_middle(sample.metadata['context'], max_tok)
return _orig_longbench_format(self, sample)
LongBenchV2Adapter.format_prompt_template = _patched_longbench_format
# Patch OpenAIMRCRAdapter.record_to_sample with needle-aware truncation.
# MRCR is a long chat history with needles hidden at desired_msg_index.
# We keep the head, tail, and a window around the needle, and truncate
# each kept message if it is still too long. This preserves the retrieval
# task while fitting GPU memory.
_orig_mrcr_record = OpenAIMRCRAdapter.record_to_sample
def _patched_mrcr_record(self, record):
import json
max_total_tok = TRUNCATION_CONFIG.get('openai_mrcr')
per_msg_max_tok = 8192
if max_total_tok and 'prompt' in record:
try:
prompt_data = json.loads(record['prompt'])
if not isinstance(prompt_data, list) or len(prompt_data) == 0:
return _orig_mrcr_record(self, record)
tokenizer = get_tokenizer()
total_tok = sum(
len(tokenizer.encode(msg.get('content', '') if isinstance(msg, dict) else '', add_special_tokens=False))
for msg in prompt_data
)
if total_tok <= max_total_tok:
return _orig_mrcr_record(self, record)
desired_idx = record.get('desired_msg_index', 0)
if not isinstance(desired_idx, int) or desired_idx < 0 or desired_idx >= len(prompt_data):
desired_idx = 0
n = len(prompt_data)
keep = set()
# Head and tail context
keep.update(range(min(2, n)))
keep.update(range(max(0, n - 2), n))
# Window around the needle
window = 2
keep.update(range(max(0, desired_idx - window), min(n, desired_idx + window + 1)))
keep = sorted(keep)
new_prompt = []
for idx in keep:
msg = prompt_data[idx]
if isinstance(msg, dict):
msg = dict(msg)
content = msg.get('content', '')
if len(tokenizer.encode(content, add_special_tokens=False)) > per_msg_max_tok:
msg['content'] = truncate_middle(content, per_msg_max_tok)
new_prompt.append(msg)
record = dict(record)
record['prompt'] = json.dumps(new_prompt)
except (json.JSONDecodeError, TypeError):
pass
return _orig_mrcr_record(self, record)
OpenAIMRCRAdapter.record_to_sample = _patched_mrcr_record
_patch_adapters_for_truncation()
# ============================================================
# Helpers
# ============================================================
def configure_thinking(generation_config: dict, enable: bool) -> dict:
extra_body = generation_config.get('extra_body', {})
chat_template_kwargs = extra_body.get('chat_template_kwargs', {})
if enable:
chat_template_kwargs['thinking'] = True
else:
chat_template_kwargs.pop('thinking', None)
if chat_template_kwargs:
extra_body['chat_template_kwargs'] = chat_template_kwargs
if extra_body:
generation_config['extra_body'] = extra_body
return generation_config
def build_agent_config(agent_cfg: dict) -> NativeAgentConfig:
agent_cfg = deepcopy(agent_cfg or {})
known_fields = {'mode', 'strategy', 'tools', 'max_steps', 'mcp_servers', 'environment', 'environment_extra'}
kwargs = agent_cfg.pop('kwargs', {})
for key in list(agent_cfg.keys()):
if key not in known_fields:
kwargs[key] = agent_cfg.pop(key)
if kwargs:
agent_cfg['kwargs'] = kwargs
return NativeAgentConfig(**agent_cfg)
def build_task_config(dataset_name: str, ds_cfg: dict, batch_size: int, enable_thinking: bool, seed: int, run_idx: int = 0) -> TaskConfig:
# Each run gets its own work_dir so use_cache does not reuse predictions
# across repeated samples. This is required for temperature=1.0 multi-run
# benchmarks to actually measure variance.
if run_idx > 0:
work_dir = Path(OUTPUT_DIR) / dataset_name / f'seed_{seed}_run_{run_idx}'
else:
work_dir = Path(OUTPUT_DIR) / dataset_name / f'seed_{seed}'
work_dir.mkdir(parents=True, exist_ok=True)
work_dir = str(work_dir)
generation_config = configure_thinking(deepcopy(ds_cfg['generation_config']), enable_thinking)
dataset_args = deepcopy(ds_cfg.get('dataset_args', {}))
dataset_args.setdefault('shuffle', True)
if dataset_name in MATH_DATASETS:
dataset_args['prompt_template'] = MATH_PROMPT_TEMPLATE
dataset_args_dict = {dataset_name: dataset_args}
agent_config = None
if 'agent_config' in ds_cfg:
agent_config = build_agent_config(ds_cfg['agent_config'])
return TaskConfig(
model=MODEL,
api_url=API_URL,
eval_type='openai_api',
dataset_dir=DATASET_DIR,
judge_model_args=JUDGE_MODEL_ARGS,
seed=seed,
limit=LIMIT,
collect_perf=True,
no_timestamp=True,
work_dir=work_dir,
use_cache=work_dir,
datasets=[dataset_name],
generation_config=generation_config,
dataset_args=dataset_args_dict,
agent_config=agent_config,
eval_batch_size=batch_size,
sandbox=SandboxTaskConfig(
enabled=True,
engine='docker',
default_config=SANDBOX_CONFIGS.get(dataset_name, {
'image': 'python:3.11-slim',
'tools_config': {
'shell_executor': {},
'python_executor': {}
}
})
) if dataset_name in SANDBOX_DATASETS else None,
)
# ============================================================
# Main loop
# ============================================================
def main():
print(f"Config: {CONFIG_PATH}")
print(f"Model: {MODEL}")
print(f"API URL: {API_URL}")
print(f"Dataset Dir: {DATASET_DIR}")
print(f"Output Dir: {OUTPUT_DIR}")
print(f"Limit: {LIMIT if LIMIT is not None else 'ALL'}")
print(f"Total benchmarks: {len(DATASETS)}")
print("="*60)
for batch_size in BATCH_SIZE_LIST:
# 1. Run multi-run benchmarks (temperature=1.0, same seed, variance from sampling)
for dataset_name in _multi_run_order:
if dataset_name not in DATASET_CONFIGS:
print(f"WARNING: {dataset_name} not in YAML config, skipping")
continue
ds_cfg = DATASET_CONFIGS[dataset_name]
num_runs = MULTI_RUN_CONFIG.get(dataset_name, 1)
for run_idx in range(num_runs):
print(f"\n{'='*60}")
print(f"Running: {dataset_name} (run {run_idx + 1}/{num_runs}, seed={SEED})")
print(f"{'='*60}")
task_cfg = build_task_config(dataset_name, ds_cfg, batch_size, ENABLE_THINKING, SEED, run_idx=run_idx)
try:
run_task(task_cfg)
except Exception as e:
print(f"ERROR in {dataset_name} (run {run_idx + 1}): {e}")
continue
# 2. Run single-run benchmarks (temperature=0.0 greedy)
for dataset_name in _single_run_order:
if dataset_name not in DATASET_CONFIGS:
print(f"WARNING: {dataset_name} not in YAML config, skipping")
continue
ds_cfg = DATASET_CONFIGS[dataset_name]
print(f"\n{'='*60}")
print(f"Running: {dataset_name} (seed={SEED})")
print(f"{'='*60}")
task_cfg = build_task_config(dataset_name, ds_cfg, batch_size, ENABLE_THINKING, SEED)
try:
run_task(task_cfg)
except Exception as e:
print(f"ERROR in {dataset_name}: {e}")
continue
# 3. Run agent benchmarks
for dataset_name in _agent_order:
if dataset_name not in DATASET_CONFIGS:
print(f"WARNING: {dataset_name} not in YAML config, skipping")
continue
ds_cfg = DATASET_CONFIGS[dataset_name]
print(f"\n{'='*60}")
print(f"Running: {dataset_name} (seed={SEED})")
print(f"{'='*60}")
task_cfg = build_task_config(dataset_name, ds_cfg, batch_size, ENABLE_THINKING, SEED)
try:
run_task(task_cfg)
except Exception as e:
print(f"ERROR in {dataset_name}: {e}")
continue
print("\nAll benchmarks done!")
if __name__ == '__main__':
main()