Add sandbox images download from ModelScope; fix Excel sample count total

This commit is contained in:
sora 2026-07-27 05:34:16 +00:00
parent 10eecf787d
commit 991a4a5f7f
11 changed files with 643 additions and 871 deletions

170
README.md
View File

@ -76,7 +76,163 @@ python bash/run.py \
--- ---
## 3. `bash/run.py` 完整参数 ## 3. Docker 镜像下载与部署
根据是否跑 SWE-bench选择下面两种方式之一。
### 3.1 方式 A不测 SWE-bench 的 Docker 配置
如果只跑常规 benchmark不含 `swe_bench_*`),不需要迁移 Docker `data-root`,只需配置国内镜像加速并加载基础镜像:
```bash
# 1. 暂停服务
sudo systemctl stop docker.socket
sudo systemctl stop docker
# 2. 配置 Docker 国内镜像加速
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://docker.1ms.run",
"https://hub.rat.dev",
"https://docker.1panel.live",
"https://dockerproxy.com",
"https://hub-mirror.c.163.com",
"https://mirror.baidubce.com",
"https://docker.mirrors.ustc.edu.cn",
"https://docker.mirrors.sjtug.sjtu.edu.cn",
"https://docker.nju.edu.cn",
"https://docker.mirrors.tuna.tsinghua.edu.cn"
]
}
EOF
# 3. 重启 Docker
sudo systemctl start docker
# 4. 加载 evalscope 环境镜像(含 Python 3.12 + 全部依赖)
docker load -i /data1/sora/evalscope/docker/evalscope-complete-py312.tar.gz
# 5. 准备代码执行 sandbox 镜像humaneval / bigcodebench 必须)
# 推荐:从 ModelScope 下载预打包的 sandbox 镜像(避免 DockerHub 限流/超时)
python3 -c "
from modelscope.hub.file_download import model_file_download
model_file_download(
model_id='SoraAmami/evalscope-sandbox-images',
file_path='bigcodebench-sandbox.tar.gz',
local_dir='/data1/sora/evalscope/docker/sandbox_images'
)
model_file_download(
model_id='SoraAmami/evalscope-sandbox-images',
file_path='python-3.11-slim.tar.gz',
local_dir='/data1/sora/evalscope/docker/sandbox_images'
)
"
docker load -i /data1/sora/evalscope/docker/sandbox_images/bigcodebench-sandbox.tar.gz
docker load -i /data1/sora/evalscope/docker/sandbox_images/python-3.11-slim.tar.gz
# 备选:如果 ModelScope 下载失败,再尝试从 DockerHub / 镜像站拉取
# docker pull bigcodebench/bigcodebench-evaluate:latest
# docker build -t bigcodebench-sandbox:latest -f - . <<'EOF'
# FROM bigcodebench/bigcodebench-evaluate:latest
# ENTRYPOINT []
# CMD ["tail", "-f", "/dev/null"]
# EOF
# docker pull python:3.11-slim
```
验证:
```bash
docker images | grep -E 'evalscope-complete-py312|bigcodebench-sandbox|python:3.11-slim'
```
### 3.2 方式 B测 SWE-bench 的 Docker 配置(大容量存储路径)
SWE-bench 镜像数量多、体积大,必须把 Docker `data-root` 和 containerd `root` 都迁移到 `/data1` 大容量盘:
```bash
# 1. 暂停服务
sudo systemctl stop docker.socket
sudo systemctl stop docker
sudo systemctl stop containerd
# 2. 创建新的数据目录
mkdir -p /data1/sora/evalscope/docker/images
mkdir -p /data1/sora/evalscope/docker/containerd
# 3. 配置国内镜像加速 + 大容量 data-root
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"data-root": "/data1/sora/evalscope/docker/images",
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://docker.1ms.run",
"https://hub.rat.dev",
"https://docker.1panel.live",
"https://dockerproxy.com",
"https://hub-mirror.c.163.com",
"https://mirror.baidubce.com",
"https://docker.mirrors.ustc.edu.cn",
"https://docker.mirrors.sjtug.sjtu.edu.cn",
"https://docker.nju.edu.cn",
"https://docker.mirrors.tuna.tsinghua.edu.cn"
]
}
EOF
# 4. 配置 containerd 数据目录
sudo tee /etc/containerd/config.toml <<-'EOF'
root = "/data1/sora/evalscope/docker/containerd"
state = "/run/containerd"
EOF
# 5. 重启
sudo systemctl reset-failed docker.service
sudo systemctl start containerd
sudo systemctl start docker
# 6. 加载 evalscope 环境镜像
docker load -i /data1/sora/evalscope/docker/evalscope-complete-py312.tar.gz
# 7. 准备代码执行 sandbox 镜像
# 推荐:从 ModelScope 下载预打包的 sandbox 镜像(避免 DockerHub 限流/超时)
python3 -c "
from modelscope.hub.file_download import model_file_download
model_file_download(
model_id='SoraAmami/evalscope-sandbox-images',
file_path='bigcodebench-sandbox.tar.gz',
local_dir='/data1/sora/evalscope/docker/sandbox_images'
)
model_file_download(
model_id='SoraAmami/evalscope-sandbox-images',
file_path='python-3.11-slim.tar.gz',
local_dir='/data1/sora/evalscope/docker/sandbox_images'
)
"
docker load -i /data1/sora/evalscope/docker/sandbox_images/bigcodebench-sandbox.tar.gz
docker load -i /data1/sora/evalscope/docker/sandbox_images/python-3.11-slim.tar.gz
# 备选:如果 ModelScope 下载失败,再尝试从 DockerHub / 镜像站拉取
# docker pull bigcodebench/bigcodebench-evaluate:latest
# docker build -t bigcodebench-sandbox:latest -f - . <<'EOF'
# FROM bigcodebench/bigcodebench-evaluate:latest
# ENTRYPOINT []
# CMD ["tail", "-f", "/dev/null"]
# EOF
# docker pull python:3.11-slim
```
验证:
```bash
docker images | grep -E 'evalscope-complete-py312|bigcodebench-sandbox|python:3.11-slim'
```
---
## 4. `bash/run.py` 完整参数
| 参数 | 默认值 | 说明 | | 参数 | 默认值 | 说明 |
|------|--------|------| |------|--------|------|
@ -106,7 +262,7 @@ python bash/run.py \
--- ---
## 4. 套件与 Benchmark 覆盖 ## 5. 套件与 Benchmark 覆盖
### 4.1 `full` 全量套件 ### 4.1 `full` 全量套件
@ -160,7 +316,7 @@ python bash/run.py \
--- ---
## 5. Multi-run 配置 ## 6. Multi-run 配置
以下 benchmark 默认会重复跑多次再取平均,次数在 `bash/run.py``MULTI_RUN_CONFIG` 中定义: 以下 benchmark 默认会重复跑多次再取平均,次数在 `bash/run.py``MULTI_RUN_CONFIG` 中定义:
@ -174,7 +330,7 @@ python bash/run.py \
--- ---
## 6. 输出目录与统计 ## 7. 输出目录与统计
### 6.1 目录结构 ### 6.1 目录结构
@ -231,7 +387,7 @@ for f in sorted(glob.glob('output/{folder_name}/*/seed_*/reports/*/*.json')):
--- ---
## 7. 快速开始 ## 8. 快速开始
### 7.1 安装依赖 ### 7.1 安装依赖
@ -275,7 +431,7 @@ python bash/run.py --help
--- ---
## 8. 设计说明 ## 9. 设计说明
- **模型无关**`run.py` 通过 `--model` / `--api-url` 接入任意 OpenAI-compatible 服务。 - **模型无关**`run.py` 通过 `--model` / `--api-url` 接入任意 OpenAI-compatible 服务。
- **配置可覆盖**YAML 配置 + 命令行参数,未配置的 benchmark 自动使用默认生成参数。 - **配置可覆盖**YAML 配置 + 命令行参数,未配置的 benchmark 自动使用默认生成参数。
@ -285,7 +441,7 @@ python bash/run.py --help
--- ---
## 9. 相关文档 ## 10. 相关文档
- 详细使用手册:`myread.md` - 详细使用手册:`myread.md`
- Docker 构建说明:`DOCKER_BUILD.md` - Docker 构建说明:`DOCKER_BUILD.md`

View File

@ -0,0 +1 @@
python bash/run.py --datasets swe_bench_verified --folder-name DP4-flash-int8-not-thinking

View File

@ -0,0 +1,6 @@
python bash/run.py --datasets swe_bench_verified --folder-name DP4-flash-int8-not-thinking
python bash/run.py --suite official --folder-name DP4-flash-int8-thinking-add0 --thinking --max-tokens-add 0
python bash/run.py --suite official --folder-name DP4-flash-int8-thinking-add16k --thinking --max-tokens-add 16384
python bash/run.py --suite official --folder-name DP4-flash-int8-thinking-add32k --thinking --max-tokens-add 32768
python bash/run.py --suite official --folder-name DP4-flash-int8-thinking-add64k --thinking --max-tokens-add 65536
python bash/run.py --suite official --folder-name DP4-flash-int8-thinking-add128k --thinking --max-tokens-add 131072

View File

@ -348,8 +348,9 @@ def collect_benchmark(output_dir: Path, benchmark: str, model_name: str):
tpot_p99 = percentile(tpots, 99) tpot_p99 = percentile(tpots, 99)
# The actual sample count we just rebuilt from raw predictions is more # The actual sample count we just rebuilt from raw predictions is more
# reliable than the (possibly reset) report summary's n_samples. # reliable than the (possibly reset) report summary's n_samples.
if sample_indexes: # For multi-seed / multi-run benchmarks we report the total number of
n_samples_unique = len(sample_indexes) # evaluated predictions (all seeds/runs) rather than unique problem IDs.
n_samples_unique = len(latencies)
elif summary0: elif summary0:
# Fallback to report summary if raw predictions are unavailable # Fallback to report summary if raw predictions are unavailable
latency_mean = summary0.get('latency', {}).get('mean', np.nan) latency_mean = summary0.get('latency', {}).get('mean', np.nan)
@ -552,11 +553,15 @@ def collect_all(output_dir: Path, model_name: str, out_name: str = None,
# Add total row # Add total row
total_score = df['得分'].mean() total_score = df['得分'].mean()
total_time = df['实测时间(h)'].sum() total_time = df['实测时间(h)'].sum()
total_samples = df['总样本数'].sum() if '总样本数' in df.columns else np.nan
total_tokens = df['累计总tokens'].sum() if '累计总tokens' in df.columns else np.nan
total_row = { total_row = {
'分类': '总计', '分类': '总计',
'Benchmark': '', 'Benchmark': '',
'得分': round(total_score, 4), '得分': round(total_score, 4),
'实测时间(h)': round(total_time, 4), '实测时间(h)': round(total_time, 4),
'总样本数': total_samples if not np.isnan(total_samples) else np.nan,
'累计总tokens': total_tokens if not np.isnan(total_tokens) else np.nan,
} }
for col in OUTPUT_COLUMNS: for col in OUTPUT_COLUMNS:
if col not in total_row: if col not in total_row:

View File

@ -151,8 +151,12 @@ SUITES = {
'official': { 'official': {
'multi': ['aime25', 'aime26', 'live_code_bench'], 'multi': ['aime25', 'aime26', 'live_code_bench'],
'single': [ 'single': [
'hle', 'mmlu_pro', 'gpqa_diamond', 'longbench_v2',
# 'swe_bench_verified', 'hle',
'mmlu_pro',
'gpqa_diamond',
'longbench_v2',
'swe_bench_verified',
], ],
'agent': [ 'agent': [
'tau2_bench' 'tau2_bench'

View File

@ -1,659 +0,0 @@
#!/usr/bin/env python3
"""
Unified benchmark runner for EvalScope.
A single entry point for lite / mid / full / group1 / group2 / group3 evaluations.
All tunable parameters can be controlled via command-line arguments.
Examples:
# Full evaluation (all benchmarks, multi-run for stability)
python bash/run.py \
--model DeepSeek-V4-Flash-Int8 \
--api-url http://localhost:30000/v1 \
--dataset-dir /data1/sora/evalscope \
--output-dir /data1/sora/evalscope/output \
--suite full \
--limit none
# Lite smoke test (~5h with full samples)
python bash/run.py --suite lite --limit none
# Run only selected benchmarks
python bash/run.py --datasets aime24,gsm8k,arc --limit 20
# Custom judge model
python bash/run.py \
--judge-model deepseek-v4-pro \
--judge-api-url https://api.deepseek.com/v1 \
--judge-api-key sk-xxx
"""
import argparse
import json
import sys
import time
from copy import deepcopy
from pathlib import Path
import yaml
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
# Make collect_results importable
sys.path.insert(0, str(SCRIPT_DIR))
import collect_results as collect_results_module
import perf_backup as perf_backup_module
# ============================================================
# Default configuration (override via CLI)
# ============================================================
DEFAULT_MODEL = 'DeepSeek-V4-Flash-Int8'
DEFAULT_API_URL = 'http://localhost:30000/v1'
DEFAULT_DATASET_DIR = str(PROJECT_ROOT)
DEFAULT_OUTPUT_DIR = str(PROJECT_ROOT / 'output')
DEFAULT_CONFIG = str(PROJECT_ROOT / 'config' / 'dpv4-int8_nothinking.yaml')
DEFAULT_TOKENIZER_PATH = '/data1/models/DeepSeek-V4-Flash-INT8'
DEFAULT_LIMIT = None
DEFAULT_SEED = 42
DEFAULT_BATCH_SIZE = 4
DEFAULT_ENABLE_THINKING = False
DEFAULT_JUDGE_MODEL = 'DeepSeek/DeepSeek-V4-Pro'
DEFAULT_JUDGE_API_URL = 'https://api.vectron.meta-stone.com/v1'
DEFAULT_JUDGE_API_KEY = 'sk-dbd8a665f7634081b87ec409c7636500'
DEFAULT_JUDGE_MAX_TOKENS = 10240
# 长文本 middle-truncation 上限token 数)。当前默认 128k。
DEFAULT_TRUNCATION_TOKENS = 32768 * 4
# ============================================================
# Benchmark suites
# ============================================================
# 多次采样配置:总样本数控制在 ~400-500
MULTI_RUN_CONFIG = {
'aime24': 12,
'aime25': 12,
'aime26': 12,
'hmmt26': 12,
'live_code_bench': 5,
'imo_answerbench': 4,
'humaneval': 3,
'gpqa_diamond': 2,
}
# 能力域完整列表
ALL_MULTI_RUN = [
# 'humaneval', 'live_code_bench',
# 'aime24', 'aime25', 'aime26', 'hmmt26',
# 'imo_answerbench', 'gpqa_diamond',
]
ALL_SINGLE_RUN = [
# 'bigcodebench', 'bfcl_v3', 'competition_math', 'gsm8k', 'hle', 'super_gpqa',
# 'arc', 'bbh', 'cmmlu', 'drop', 'hellaswag', 'mmlu', 'mmlu_pro',
# 'simple_qa', 'trivia_qa', 'winogrande',
# 'openai_mrcr', 'longbench_v2',
'swe_bench_verified', 'swe_bench_pro',
]
ALL_AGENT = [
# 'tau2_bench', 'general_fc'
]
# 分组基于 CSV 单次时间 + multi-run 后的 wall time 平衡:
# Group1: ~61h | Group2: ~62h | Group3: ~55h
SUITES = {
'full': {
'multi': ALL_MULTI_RUN,
'single': ALL_SINGLE_RUN,
'agent': ALL_AGENT,
},
'lite': {
'multi': ['aime24', 'humaneval'],
'single': ['gsm8k', 'arc', 'longbench_v2'],
'agent': ['general_fc'],
},
'mid': {
'multi': ['aime24', 'humaneval'],
'single': [
'live_code_bench', 'bigcodebench', 'competition_math', 'gsm8k',
'gpqa_diamond', 'mmlu_pro', 'simple_qa', 'longbench_v2', 'openai_mrcr',
],
'agent': ['general_fc', 'tau2_bench'],
},
# 多机组分组,基于 CSV 实测完整时间(已含 multi-run平衡
# Group1: ~22.7h | Group2: ~25.6h | Group3: ~27.0h | 合计 ~75.2h
'group1': {
'multi': ['live_code_bench', 'aime24', 'aime25', 'aime26', 'hmmt26', 'imo_answerbench', 'humaneval'],
'single': ['bigcodebench', 'competition_math', 'gsm8k', 'drop', 'arc', 'hellaswag', 'winogrande'],
'agent': [],
},
'group2': {
'multi': [],
'single': ['hle', 'mmlu_pro', 'trivia_qa'],
'agent': [],
},
'group3': {
'multi': ['gpqa_diamond'],
'single': ['openai_mrcr', 'longbench_v2', 'bfcl_v3', 'mmlu', 'cmmlu', 'bbh', 'simple_qa'],
'agent': ['tau2_bench', 'general_fc'],
},
}
# ============================================================
# Fixed configuration
# ============================================================
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{{}}."
)
SANDBOX_DATASETS = {'humaneval', 'bigcodebench', 'swe_bench_verified', 'swe_bench_pro'}
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': {}
}
},
'swe_bench_verified': {
'image': 'swe-bench-sandbox:latest',
'working_dir': '/tmp',
'tools_config': {
'shell_executor': {},
'python_executor': {}
}
},
'swe_bench_pro': {
'image': 'swe-bench-sandbox:latest',
'working_dir': '/tmp',
'tools_config': {
'shell_executor': {},
'python_executor': {}
}
},
}
# ============================================================
# CLI parser
# ============================================================
def build_parser():
parser = argparse.ArgumentParser(
description='Unified EvalScope benchmark runner',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='Suites: full, lite, mid, group1, group2, group3',
)
# Model / API
parser.add_argument('--model', default=DEFAULT_MODEL,
help='Served model name (default: %(default)s)')
parser.add_argument('--api-url', default=DEFAULT_API_URL,
help='OpenAI-compatible API URL (default: %(default)s)')
# Paths
parser.add_argument('--dataset-dir', default=DEFAULT_DATASET_DIR,
help='Parent directory containing datasets/ subdir (default: %(default)s)')
parser.add_argument('--output-dir', default=DEFAULT_OUTPUT_DIR,
help='Output root directory (default: %(default)s)')
parser.add_argument('--config', default=DEFAULT_CONFIG,
help='YAML config path (default: %(default)s)')
parser.add_argument('--tokenizer-path', default=DEFAULT_TOKENIZER_PATH,
help='Local tokenizer path for middle-truncation (default: %(default)s)')
# Run control
parser.add_argument('--suite', default='full', choices=list(SUITES.keys()),
help='Benchmark suite to run (default: %(default)s)')
parser.add_argument('--datasets', '--benchmarks', dest='datasets', default=None,
help='Override suite with comma-separated benchmark names, e.g. aime24,gsm8k')
parser.add_argument('--exclude', default=None,
help='Comma-separated benchmarks to exclude from the chosen suite')
parser.add_argument('--limit', default=None,
help='Max samples per benchmark; "none"/"all" for no limit (default: none)')
parser.add_argument('--seed', type=int, default=DEFAULT_SEED,
help='Random seed (default: %(default)s)')
parser.add_argument('--batch-size', type=int, default=DEFAULT_BATCH_SIZE,
help='Evaluation batch size (default: %(default)s)')
# Decoding / thinking
parser.add_argument('--thinking', action='store_true', default=None,
help='Enable thinking mode (sglang chat_template_kwargs.thinking=True)')
parser.add_argument('--no-thinking', dest='thinking', action='store_false',
help='Disable thinking mode (default)')
# Judge model
parser.add_argument('--judge-model', default=DEFAULT_JUDGE_MODEL,
help='Judge model name (default: %(default)s)')
parser.add_argument('--judge-api-url', default=DEFAULT_JUDGE_API_URL,
help='Judge model API URL (default: %(default)s)')
parser.add_argument('--judge-api-key', default=DEFAULT_JUDGE_API_KEY,
help='Judge model API key')
parser.add_argument('--judge-max-tokens', type=int, default=DEFAULT_JUDGE_MAX_TOKENS,
help='Judge model max_tokens (default: %(default)s)')
# Truncation
parser.add_argument('--truncation-tokens', type=int, default=DEFAULT_TRUNCATION_TOKENS,
help='Middle-truncation token budget for long-context benchmarks (default: %(default)s)')
# Result collection
parser.add_argument('--no-summary', dest='write_summary', action='store_false',
help='Skip writing summary Excel/CSV after each benchmark')
parser.add_argument('--summary-name', default=None,
help='Output summary file name (without extension); defaults to safe model name')
return parser
# ============================================================
# Middle-truncation helpers
# ============================================================
_TOKENIZER = None
def get_tokenizer(tokenizer_path: str):
global _TOKENIZER
if _TOKENIZER is None:
from transformers import AutoTokenizer
try:
_TOKENIZER = AutoTokenizer.from_pretrained(tokenizer_path, trust_remote_code=True)
except Exception:
_TOKENIZER = AutoTokenizer.from_pretrained('deepseek-ai/DeepSeek-V4-Flash', trust_remote_code=True)
return _TOKENIZER
def truncate_middle(text: str, max_tokens: int, tokenizer_path: str) -> str:
if max_tokens <= 0:
return text
tokenizer = get_tokenizer(tokenizer_path)
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(tokenizer_path: str, truncation_tokens: int):
from evalscope.benchmarks.longbench_v2.longbench_v2_adapter import LongBenchV2Adapter
from evalscope.benchmarks.openai_mrcr.openai_mrcr_adapter import OpenAIMRCRAdapter
_orig_longbench_format = LongBenchV2Adapter.format_prompt_template
def _patched_longbench_format(self, sample):
if sample.metadata and 'context' in sample.metadata:
sample.metadata['context'] = truncate_middle(sample.metadata['context'], truncation_tokens, tokenizer_path)
return _orig_longbench_format(self, sample)
LongBenchV2Adapter.format_prompt_template = _patched_longbench_format
_orig_mrcr_record = OpenAIMRCRAdapter.record_to_sample
def _patched_mrcr_record(self, record):
per_msg_max_tok = 8192
if '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(tokenizer_path)
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 <= truncation_tokens:
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()
keep.update(range(min(2, n)))
keep.update(range(max(0, n - 2), n))
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, tokenizer_path)
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
# ============================================================
# Helpers
# ============================================================
def load_dataset_configs(config_path: str):
if not Path(config_path).exists():
raise FileNotFoundError(f'Config file not found: {config_path}')
with open(config_path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)
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,
limit,
output_dir: str,
model: str,
api_url: str,
dataset_dir: str,
judge_model_args: dict,
run_idx: int = 0,
) -> TaskConfig:
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
# ============================================================
def write_summary(output_dir: str, model_name: str, summary_name: str):
"""Re-aggregate results across all benchmarks under output_dir."""
try:
excel_output_dir = PROJECT_ROOT / 'results'
collect_results_module.collect_all(
Path(output_dir), model_name, summary_name,
excel_output_dir=excel_output_dir,
)
except Exception as e:
print(f'WARNING: failed to write summary: {e}')
def backup_after_run(output_dir: str, benchmark: str, model_name: str,
work_dir: Path):
"""Snapshot the just-finished run's perf summary and predictions."""
try:
report_json = work_dir / 'reports' / model_name / f'{benchmark}.json'
perf_backup_module.backup_perf_stats(
Path(output_dir), benchmark, model_name, report_json,
)
predictions_dir = work_dir / 'predictions' / model_name
perf_backup_module.archive_predictions(
Path(output_dir), benchmark, model_name, predictions_dir,
)
except Exception as e:
print(f'WARNING: perf backup for {benchmark} failed: {e}')
def restore_before_run(output_dir: str, benchmark: str, model_name: str,
work_dir: Path) -> bool:
"""If durable backups exist for a benchmark/model, materialise them into
the new ``work_dir`` before evalscope starts so the next run resumes from
the larger historical state instead of overwriting it.
"""
try:
predictions_dir = work_dir / 'predictions' / model_name
report_json = work_dir / 'reports' / model_name / f'{benchmark}.json'
restored = perf_backup_module.restore_from_backup(
Path(output_dir), benchmark, model_name,
predictions_dir, report_json,
)
if restored:
print(f'Restored {benchmark}/{model_name} from backup before run')
return restored
except Exception as e:
print(f'WARNING: perf restore for {benchmark} failed: {e}')
return False
def run_and_summarize(task_cfg, write_summary_flag: bool, output_dir: str, model_name: str, summary_name: str):
"""Run a benchmark task and optionally refresh the summary table.
After a successful ``run_task()`` we snapshot the cumulative
``perf_metrics.summary`` and the per-sample predictions into the durable
backup files maintained by ``perf_backup.py`` so checkpoint restarts can
recover them.
"""
dataset_name = task_cfg.datasets[0]
work_dir = Path(task_cfg.work_dir)
restore_before_run(output_dir, dataset_name, model_name, work_dir)
start_ts = time.monotonic()
try:
run_task(task_cfg)
finally:
elapsed = time.monotonic() - start_ts
perf_backup_module.record_active_time(output_dir, dataset_name, model_name, elapsed)
print(f'Active time for {dataset_name}: {elapsed:.1f}s (total accumulated)')
backup_after_run(output_dir, dataset_name, model_name, work_dir)
if write_summary_flag:
write_summary(output_dir, model_name, summary_name)
def main():
parser = build_parser()
args = parser.parse_args()
# Resolve limit
limit = args.limit
if limit is not None:
if str(limit).lower() in ('none', 'all'):
limit = None
else:
limit = int(limit)
enable_thinking = DEFAULT_ENABLE_THINKING if args.thinking is None else args.thinking
# Resolve suite or custom datasets
if args.datasets:
custom = [d.strip() for d in args.datasets.split(',') if d.strip()]
multi_run = [d for d in custom if d in MULTI_RUN_CONFIG]
single_run = [d for d in custom if d not in MULTI_RUN_CONFIG]
agent = [d for d in custom if d in ALL_AGENT]
single_run = [d for d in single_run if d not in ALL_AGENT]
else:
suite = SUITES[args.suite]
multi_run = list(suite['multi'])
single_run = list(suite['single'])
agent = list(suite['agent'])
# Apply --exclude
if args.exclude:
exclude = {d.strip() for d in args.exclude.split(',') if d.strip()}
multi_run = [d for d in multi_run if d not in exclude]
single_run = [d for d in single_run if d not in exclude]
agent = [d for d in agent if d not in exclude]
judge_model_args = {
'model_id': args.judge_model,
'api_url': args.judge_api_url,
'api_key': args.judge_api_key,
'eval_type': 'openai_api',
'generation_config': {
'temperature': 0.0,
'max_tokens': args.judge_max_tokens,
},
}
truncation_tokens = args.truncation_tokens
_patch_adapters_for_truncation(args.tokenizer_path, truncation_tokens)
dataset_configs = load_dataset_configs(args.config)
print('=' * 60)
print(f'Config: {args.config}')
print(f'Model: {args.model}')
print(f'API URL: {args.api_url}')
print(f'Dataset Dir: {args.dataset_dir}')
print(f'Output Dir: {args.output_dir}')
print(f'Suite: {args.suite}')
print(f'Limit: {limit if limit is not None else "ALL"}')
print(f'Thinking: {enable_thinking}')
print(f'Seed: {args.seed}')
print(f'Batch Size: {args.batch_size}')
print(f'Tokenizer Path: {args.tokenizer_path}')
print(f'Truncation Tokens: {truncation_tokens}')
print(f'Multi-run datasets: {multi_run}')
print(f'Single-run datasets: {single_run}')
print(f'Agent datasets: {agent}')
print(f'Write summary: {args.write_summary}')
print('=' * 60)
def run_one(dataset_name, run_idx=0):
ds_cfg = dataset_configs[dataset_name]
task_cfg = build_task_config(
dataset_name, ds_cfg, args.batch_size, enable_thinking, args.seed, limit,
args.output_dir, args.model, args.api_url, args.dataset_dir, judge_model_args,
run_idx=run_idx,
)
try:
run_and_summarize(task_cfg, args.write_summary, args.output_dir, args.model, args.summary_name)
except Exception as e:
print(f'ERROR in {dataset_name} (run {run_idx + 1 if run_idx else 1}): {e}')
for dataset_name in multi_run:
if dataset_name not in dataset_configs:
print(f'WARNING: {dataset_name} not in YAML config, skipping')
continue
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={args.seed})')
print(f"{'='*60}")
run_one(dataset_name, run_idx=run_idx)
for dataset_name in single_run:
if dataset_name not in dataset_configs:
print(f'WARNING: {dataset_name} not in YAML config, skipping')
continue
print(f"\n{'='*60}")
print(f'Running: {dataset_name} (seed={args.seed})')
print(f"{'='*60}")
run_one(dataset_name)
for dataset_name in agent:
if dataset_name not in dataset_configs:
print(f'WARNING: {dataset_name} not in YAML config, skipping')
continue
print(f"\n{'='*60}")
print(f'Running: {dataset_name} (seed={args.seed})')
print(f"{'='*60}")
run_one(dataset_name)
if args.write_summary:
write_summary(args.output_dir, args.model, args.summary_name)
print('\nAll benchmarks done!')
if __name__ == '__main__':
main()

View File

@ -1,179 +0,0 @@
#!/usr/bin/env python3
"""End-to-end offline test for bash/run.py + collect_results + perf_backup.
This script does NOT call any model API. It uses an existing benchmark
directory (aime24/seed_42) and walks through every code path that the real
``run.py`` would touch:
1. backup_perf_stats confirm a perf summary snapshot is written
2. archive_predictions confirm per-sample records are deduplicated into
the archive
3. restore_from_backup confirm a fresh work_dir can be rehydrated from
the backup files
4. collect_results aggregation confirm correct metrics are produced
5. Whitelist mode confirm only the requested benchmarks appear in the
summary
6. hle_low alias confirm hle_low directory maps to canonical "hle"
7. Reset recovery confirm that even after wiping the predictions file
and the report summary, the archive+backup still recover the metrics
Run from the project root:
python bash/test_e2e.py
"""
import json
import shutil
import sys
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
import collect_results as cr # noqa: E402
import perf_backup as pb # noqa: E402
PROJECT_ROOT = Path('/data1/sora/evalscope')
SOURCE_OUTPUT = PROJECT_ROOT / 'output'
def banner(msg):
print('\n' + '=' * 60)
print(f' {msg}')
print('=' * 60)
def assert_close(actual, expected, name, tol=1e-3):
if abs(actual - expected) > tol:
raise AssertionError(f'{name}: expected ~{expected}, got {actual}')
print(f' OK {name}={actual:.4f}')
def main():
banner('Setting up isolated test output dir')
test_root = PROJECT_ROOT / 'output_e2e_test'
if test_root.exists():
shutil.rmtree(test_root)
test_root.mkdir(parents=True)
# Copy the canonical aime24 data into the isolated dir
src = SOURCE_OUTPUT / 'aime24' / 'seed_42'
dst = test_root / 'aime24' / 'seed_42'
shutil.copytree(src, dst)
# And copy hle_low as hle_low under the test dir (so we exercise the
# alias code path).
hle_src = SOURCE_OUTPUT / 'hle_low' / 'seed_42'
hle_dst = test_root / 'hle_low' / 'seed_42'
if hle_src.exists():
shutil.copytree(hle_src, hle_dst)
benchmark = 'aime24'
model_name = 'DeepSeek-V4-Flash-Int8'
banner('1) backup_perf_stats — snapshot summary')
report_json = dst / 'reports' / model_name / f'{benchmark}.json'
backup_path = pb.backup_perf_stats(test_root, benchmark, model_name, report_json)
assert backup_path.exists(), 'backup file was not created'
payload = json.loads(backup_path.read_text())
assert payload['n_samples'] == 30, f'expected 30 samples, got {payload["n_samples"]}'
print(f' OK backup written: {backup_path}, n_samples={payload["n_samples"]}')
banner('2) archive_predictions — deduplicate into archive')
preds = dst / 'predictions' / model_name
archive_path = pb.archive_predictions(test_root, benchmark, model_name, preds)
assert archive_path.exists(), 'archive file was not created'
n_lines = sum(1 for line in archive_path.read_text().splitlines() if line.strip())
assert n_lines == 30, f'expected 30 archived samples, got {n_lines}'
print(f' OK archive written: {archive_path}, samples={n_lines}')
banner('3) restore_from_backup — rehydrate a fresh work_dir')
fresh_dir = test_root / 'fresh_work' / 'seed_42'
fresh_report = fresh_dir / 'reports' / model_name / f'{benchmark}.json'
fresh_preds = fresh_dir / 'predictions' / model_name
# Pre-create empty structure to mimic what eval_task would do
fresh_report.parent.mkdir(parents=True, exist_ok=True)
fresh_report.write_text(json.dumps({
'name': f'{model_name}@{benchmark}',
'perf_metrics': {'summary': {'n_samples': 1}},
}))
restored = pb.restore_from_backup(
test_root, benchmark, model_name, fresh_preds, fresh_report,
)
assert restored, 'restore_from_backup returned False'
restored_report = json.loads(fresh_report.read_text())
assert restored_report['perf_metrics']['summary']['n_samples'] == 30, \
f'restored n_samples={restored_report["perf_metrics"]["summary"]["n_samples"]}'
print(' OK restored report n_samples=30 (matches backup)')
banner('4) collect_results — aggregate metrics')
csv, xlsx = cr.eval_benchmark([benchmark], test_root, model_name)
import pandas as pd
df = pd.read_csv(csv)
aime24 = df[df['Benchmark'] == 'aime24'].iloc[0]
# Single seed_42 run, so values match the report directly (no multi-run
# averaging). The full-summary CSV averages across run_1..run_11.
assert_close(aime24['得分'], 0.6333, 'aime24 score (single seed)')
assert_close(aime24['实测时间(h)'], 0.1469, 'aime24 duration (h)')
assert_close(aime24['总样本数'], 30, 'aime24 sample count')
assert_close(aime24['延迟_mean(s)'], 35.24971, 'aime24 latency mean')
assert_close(aime24['TTFT_mean(s)'], 0.26301, 'aime24 TTFT mean')
assert_close(aime24['TPOT_mean(s)'], 0.02165, 'aime24 TPOT mean')
assert_close(aime24['输入tokens_mean'], 119.33, 'aime24 input_tokens_mean')
assert_close(aime24['输出tokens_mean'], 1609.8, 'aime24 output_tokens_mean')
banner('5) Whitelist mode — only the requested benchmarks appear')
csv2, _ = cr.eval_benchmark([benchmark], test_root, model_name)
df2 = pd.read_csv(csv2)
assert df2['Benchmark'].dropna().tolist() == ['aime24'], \
f'whitelist did not limit benchmarks: {df2["Benchmark"].tolist()}'
print(' OK summary only contains aime24 + total')
banner('6) hle_low → hle alias')
if hle_src.exists():
csv3, _ = cr.eval_benchmark(['hle'], test_root, model_name)
df3 = pd.read_csv(csv3)
assert 'hle' in df3['Benchmark'].tolist(), \
f'hle canonical name missing from: {df3["Benchmark"].tolist()}'
assert 'hle_low' not in df3['Benchmark'].tolist(), \
f'hle_low should be aliased away: {df3["Benchmark"].tolist()}'
print(' OK hle_low directory maps to canonical "hle"')
banner('7) Reset recovery — wipe report+predictions, expect archive to restore')
# Wipe the current predictions
shutil.rmtree(preds)
# Reset report.json
data = json.loads(report_json.read_text())
data['perf_metrics']['summary']['n_samples'] = 1
data['perf_metrics']['summary']['latency']['mean'] = 0.0
report_json.write_text(json.dumps(data, indent=2))
csv4, _ = cr.eval_benchmark([benchmark], test_root, model_name)
df4 = pd.read_csv(csv4)
aime24_after = df4[df4['Benchmark'] == 'aime24'].iloc[0]
assert_close(aime24_after['得分'], 0.6333, 'aime24 score (post-reset)')
assert_close(aime24_after['总样本数'], 30, 'aime24 sample count (post-reset)')
assert_close(aime24_after['延迟_mean(s)'], 35.24971, 'aime24 latency mean (post-reset)')
assert_close(aime24_after['TTFT_mean(s)'], 0.26301, 'aime24 TTFT mean (post-reset)')
print(' OK archive fully recovered metrics after predictions wipe')
banner('8) Backup monotonicity — new backup must not overwrite a larger one')
# We have a backup with n_samples=30. Now write a fresh report with
# n_samples=2 and call backup_perf_stats again — the backup should NOT
# be overwritten.
data = json.loads(report_json.read_text())
data['perf_metrics']['summary']['n_samples'] = 2
report_json.write_text(json.dumps(data, indent=2))
pb.backup_perf_stats(test_root, benchmark, model_name, report_json)
payload2 = json.loads(backup_path.read_text())
assert payload2['n_samples'] == 30, \
f'backup was clobbered: n_samples now {payload2["n_samples"]}'
print(' OK backup preserved n_samples=30 even after a reset run')
banner('All checks passed')
print(f'Test artifacts under {test_root} (kept for inspection)')
if __name__ == '__main__':
main()

View File

@ -101,6 +101,28 @@ def eval_instance(
log_dir = Path(log_dir) / 'swebench_log' / instance_id log_dir = Path(log_dir) / 'swebench_log' / instance_id
log_dir.mkdir(parents=True, exist_ok=True) log_dir.mkdir(parents=True, exist_ok=True)
# Resume: skip container evaluation if test output already exists.
test_output_path = log_dir / LOG_TEST_OUTPUT
if test_output_path.exists():
logger.info(f'Found existing test output for {instance_id}, skip container evaluation and reuse log.')
try:
report = get_eval_report(
test_spec=test_spec,
prediction=pred,
test_log_path=test_output_path,
include_tests_status=True,
)
logger.info(f'report: {report}\n'
f"Result for {instance_id}: resolved: {report[instance_id]['resolved']}")
return {
'completed': True,
'resolved': report.get(instance_id, {}).get('resolved', False),
'report': report,
}
except Exception as e:
logger.warning(f'Failed to reuse existing test output for {instance_id}: {e}. Will re-evaluate.')
logger.info(f'Starting evaluation for {instance_id} in log dir {log_dir}...') logger.info(f'Starting evaluation for {instance_id} in log dir {log_dir}...')
client = DockerClient.from_env() client = DockerClient.from_env()

110
myread.md
View File

@ -159,12 +159,65 @@ docker images | grep evalscope-complete-py312
| `python:3.11-slim` | 通用代码执行环境 | | `python:3.11-slim` | 通用代码执行环境 |
| `swebench/sweb.eval.x86_64.*` | SWE-bench 每个样本一个实例镜像 | | `swebench/sweb.eval.x86_64.*` | SWE-bench 每个样本一个实例镜像 |
#### 2.4.1 配置 Docker国内镜像加速 + 大容量存储路径) 根据是否跑 SWE-bench选择下面两种 Docker 配置方式之一。
SWE-bench 镜像数量多、体积大,建议把 Docker `data-root` 和 containerd `root` 都迁移到 `/data1` 大容量盘: #### 2.4.1 方式 A不测 SWE-bench 的 Docker 配置
如果只跑常规 benchmark不含 `swe_bench_*`),不需要迁移 Docker `data-root`,只需配置国内镜像加速并加载基础镜像:
```bash ```bash
# 1. 停止 Docker # 1. 暂停服务
sudo systemctl stop docker.socket
sudo systemctl stop docker
# 2. 配置 Docker 国内镜像加速
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://docker.1ms.run",
"https://hub.rat.dev",
"https://docker.1panel.live",
"https://dockerproxy.com",
"https://hub-mirror.c.163.com",
"https://mirror.baidubce.com",
"https://docker.mirrors.ustc.edu.cn",
"https://docker.mirrors.sjtug.sjtu.edu.cn",
"https://docker.nju.edu.cn",
"https://docker.mirrors.tuna.tsinghua.edu.cn"
]
}
EOF
# 3. 重启 Docker
sudo systemctl start docker
# 4. 加载 evalscope 环境镜像(含 Python 3.12 + 全部依赖)
docker load -i /data1/sora/evalscope/docker/evalscope-complete-py312.tar.gz
# 5. 准备代码执行 sandbox 镜像humaneval / bigcodebench 必须)
docker pull bigcodebench/bigcodebench-evaluate:latest
docker build -t bigcodebench-sandbox:latest -f - . <<'EOF'
FROM bigcodebench/bigcodebench-evaluate:latest
ENTRYPOINT []
CMD ["tail", "-f", "/dev/null"]
EOF
docker pull python:3.11-slim
```
验证:
```bash
docker images | grep -E 'evalscope-complete-py312|bigcodebench|python:3.11-slim'
```
#### 2.4.2 方式 B测 SWE-bench 的 Docker 配置(大容量存储路径)
SWE-bench 镜像数量多、体积大,必须把 Docker `data-root` 和 containerd `root` 都迁移到 `/data1` 大容量盘,否则默认盘很快会被占满:
```bash
# 1. 暂停服务
sudo systemctl stop docker.socket sudo systemctl stop docker.socket
sudo systemctl stop docker sudo systemctl stop docker
sudo systemctl stop containerd sudo systemctl stop containerd
@ -173,7 +226,7 @@ sudo systemctl stop containerd
mkdir -p /data1/sora/evalscope/docker/images mkdir -p /data1/sora/evalscope/docker/images
mkdir -p /data1/sora/evalscope/docker/containerd mkdir -p /data1/sora/evalscope/docker/containerd
# 3. 配置国内镜像加速(多填几个,自动轮询) # 3. 配置国内镜像加速 + 大容量 data-root
sudo tee /etc/docker/daemon.json <<-'EOF' sudo tee /etc/docker/daemon.json <<-'EOF'
{ {
"data-root": "/data1/sora/evalscope/docker/images", "data-root": "/data1/sora/evalscope/docker/images",
@ -203,22 +256,18 @@ EOF
sudo systemctl reset-failed docker.service sudo systemctl reset-failed docker.service
sudo systemctl start containerd sudo systemctl start containerd
sudo systemctl start docker sudo systemctl start docker
```
#### 2.4.2 拉取/构建代码执行镜像 # 6. 加载 evalscope 环境镜像
docker load -i /data1/sora/evalscope/docker/evalscope-complete-py312.tar.gz
```bash # 7. 准备代码执行 sandbox 镜像
# bigcodebench 官方镜像
docker pull bigcodebench/bigcodebench-evaluate:latest docker pull bigcodebench/bigcodebench-evaluate:latest
# 构建守护态 sandbox
docker build -t bigcodebench-sandbox:latest -f - . <<'EOF' docker build -t bigcodebench-sandbox:latest -f - . <<'EOF'
FROM bigcodebench/bigcodebench-evaluate:latest FROM bigcodebench/bigcodebench-evaluate:latest
ENTRYPOINT [] ENTRYPOINT []
CMD ["tail", "-f", "/dev/null"] CMD ["tail", "-f", "/dev/null"]
EOF EOF
# 通用 Python 执行环境
docker pull python:3.11-slim docker pull python:3.11-slim
``` ```
@ -267,25 +316,42 @@ python bash/pull_swe_bench_images.py \
> 2. 降低 `--max-workers` 到 1 > 2. 降低 `--max-workers` 到 1
> 3. 使用 `tmux` 挂后台运行,避免 SSH 断连导致中断。 > 3. 使用 `tmux` 挂后台运行,避免 SSH 断连导致中断。
#### 2.4.4 SWE-bench 镜像的本地备份与迁移(可选) #### 2.4.4 SWE-bench 镜像的备份与迁移(可选)
SWE-bench 全量镜像约 500 个、总大小约 **2TB**,无法上传到 ModelScope 这类代码/文件仓库分发。如需把已拉好的镜像迁移到另一台机器,建议用移动硬盘或内网直接拷贝 Docker `data-root`,或使用下面脚本分批导出/导入 SWE-bench Verified 全量约 500 个镜像、本地实际占用约 **160GB**。迁移到另一台机器有三种方式
**源机器:打包** **方式一:从 ModelScope 下载(推荐,已上传)**
`SoraAmami/swe-bench-verified-images` 已包含分批导出的 `docker save` tar.gz目标机器直接下载加载即可
```bash ```bash
bash bash/save_swe_images.sh /data1/sora/evalscope/docker/swe_images 50 # 下载
``` modelscope download \
--repo-type dataset \
--local_dir /data1/sora/evalscope/docker/swe_images \
SoraAmami/swe-bench-verified-images
脚本会把镜像按每 50 个一批保存为 `swebench_batch_001.tar.gz` 等。 # 加载
**目标机器:加载**
```bash
bash bash/load_swe_images.sh /data1/sora/evalscope/docker/swe_images bash bash/load_swe_images.sh /data1/sora/evalscope/docker/swe_images
``` ```
> 注意:分批 tar 会占用额外磁盘空间(约 2TB请确保目标盘容量足够。 **方式二:本地分批导出/导入**
如果已有本地镜像,可用脚本打包后复制到目标机器:
```bash
# 源机器打包(每 50 个一批)
bash bash/save_swe_images.sh /data1/sora/evalscope/docker/swe_images 50
# 目标机器加载
bash bash/load_swe_images.sh /data1/sora/evalscope/docker/swe_images
```
**方式三:直接拷贝 Docker data-root**
适用于内网或移动硬盘,把 `/data1/sora/evalscope/docker/images``/data1/sora/evalscope/docker/containerd` 完整复制到目标机器相同路径,再启动 Docker。注意这种方式对 Docker 版本和路径要求严格,不如前两种稳定。
> 注意:分批 tar 会临时占用额外磁盘空间,加载完成后可删除 tar 包。
--- ---

View File

@ -0,0 +1,58 @@
# bigcodebench-sandbox
# Minimal sandbox for running BigCodeBench/HumanEval code inside evalscope.
# Based on https://github.com/bigcode-project/bigcodebench/blob/main/Docker/Evaluate.Dockerfile
# but stripped to only the runtime dependencies needed to execute benchmark tests.
# Uses python:3.9-slim because some pinned packages (e.g. numba==0.55.0) do not have
# wheels for newer Python versions.
# Uses Chinese mirrors for apt/pip to speed up builds inside mainland network.
FROM python:3.9-slim
# Use Chinese Debian mirror
RUN sed -i 's|http://deb.debian.org/debian|http://mirrors.tuna.tsinghua.edu.cn/debian|g' /etc/apt/sources.list.d/debian.sources && \
sed -i 's|http://deb.debian.org/debian-security|http://mirrors.tuna.tsinghua.edu.cn/debian-security|g' /etc/apt/sources.list.d/debian.sources && \
sed -i 's|http://deb.debian.org/debian|http://mirrors.tuna.tsinghua.edu.cn/debian|g' /etc/apt/sources.list || true
# Configure Chinese PyPI mirror
RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple && \
pip config set global.timeout 2000
# Install system dependencies needed by the scientific/Python packages
RUN apt-get update && apt-get install -y \
git \
g++ \
python3-tk \
zip \
unzip \
procps \
r-base \
libgdal-dev \
libfreetype6-dev \
libpng-dev \
pkg-config \
python3-dev \
python3-matplotlib \
libgl1 \
&& rm -rf /var/lib/apt/lists/*
# Upgrade pip
RUN pip install --upgrade pip
# Add a non-root user (matches upstream image conventions)
RUN adduser --disabled-password --gecos "" bigcodebenchuser
# Copy and install the BigCodeBench evaluation requirements.
# These are the 70+ Python libraries referenced by the benchmark test cases.
COPY requirements-eval.txt /tmp/requirements-eval.txt
RUN pip install -I --timeout 2000 -r /tmp/requirements-eval.txt
# Ensure a compatible datasets version
RUN pip install datasets==2.17.0
WORKDIR /app
RUN chown -R bigcodebenchuser:bigcodebenchuser /app && \
chmod -R 777 /app
# For evalscope sandbox: keep container alive
ENTRYPOINT []
CMD ["tail", "-f", "/dev/null"]

292
scripts/deploy_evalscope.sh Executable file
View File

@ -0,0 +1,292 @@
#!/bin/bash
# ============================================================
# EvalScope 完整部署脚本
# 用法: bash deploy_evalscope.sh <BASE_DIR> [TOKEN]
# 示例: bash deploy_evalscope.sh /data1/sora ms-3d554a39-6e07-496d-8022-0b0ee64a6389
# ============================================================
set -euo pipefail
# --------------------------------------------------
# 1. 参数解析
# --------------------------------------------------
BASE_DIR="${1:-/data1/sora}" # 基础目录,默认 /data1/sora
DEFAULT_TOKEN="ms-3d554a39-6e07-496d-8022-0b0ee64a6389"
MODELSCOPE_TOKEN="${2:-$DEFAULT_TOKEN}"
# 派生路径
EVALSCOPE_DIR="$BASE_DIR/evalscope"
DOCKER_DIR="$EVALSCOPE_DIR/docker"
IMAGES_DIR="$DOCKER_DIR/images"
CONTAINERD_DIR="$DOCKER_DIR/containerd"
SWE_IMAGES_DIR="$DOCKER_DIR/swe_images"
DATASETS_DIR="$EVALSCOPE_DIR/datasets"
# --------------------------------------------------
# 2. 安装依赖
# --------------------------------------------------
echo "==> 安装 modelscope..."
pip install modelscope
# --------------------------------------------------
# 3. 登录 ModelScope
# --------------------------------------------------
if [ -n "$MODELSCOPE_TOKEN" ]; then
echo "==> 登录 ModelScope..."
modelscope login --token "$MODELSCOPE_TOKEN"
fi
# --------------------------------------------------
# 4. 下载数据集
# --------------------------------------------------
echo "==> 下载 evalscope 数据集..."
mkdir -p "$DATASETS_DIR"
python3 -c "
from modelscope.hub.snapshot_download import snapshot_download
snapshot_download(
'SoraAmami/evalscope-datasets',
repo_type='dataset',
cache_dir='$EVALSCOPE_DIR',
local_dir='$DATASETS_DIR'
)
"
# --------------------------------------------------
# 5. 下载 Docker 镜像包
# --------------------------------------------------
echo "==> 下载 evalscope Docker 镜像..."
mkdir -p "$DOCKER_DIR"
python3 -c "
from modelscope.hub.file_download import model_file_download
model_file_download(
model_id='SoraAmami/evalscope-docker',
file_path='evalscope-complete-py312.tar.gz',
local_dir='$DOCKER_DIR'
)
"
# --------------------------------------------------
# 6. 配置 Docker 和 containerd 数据目录
# --------------------------------------------------
echo "==> 配置 Docker 和 containerd..."
# 停止服务
sudo systemctl stop docker.socket 2>/dev/null || true
sudo systemctl stop docker 2>/dev/null || true
sudo systemctl stop containerd 2>/dev/null || true
# 创建数据目录
mkdir -p "$IMAGES_DIR"
mkdir -p "$CONTAINERD_DIR"
# 配置 Docker
echo "==> 写入 Docker 配置..."
sudo tee /etc/docker/daemon.json <<EOF
{
"data-root": "$IMAGES_DIR",
"features": {
"containerd-snapshotter": true
},
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://docker.1ms.run",
"https://hub.rat.dev",
"https://docker.1panel.live",
"https://dockerproxy.com",
"https://hub-mirror.c.163.com",
"https://mirror.baidubce.com",
"https://docker.mirrors.ustc.edu.cn",
"https://docker.mirrors.sjtug.sjtu.edu.cn",
"https://docker.nju.edu.cn",
"https://docker.mirrors.tuna.tsinghua.edu.cn"
]
}
EOF
# 配置 containerd
echo "==> 写入 containerd 配置..."
sudo tee /etc/containerd/config.toml <<EOF
root = "$CONTAINERD_DIR"
state = "/run/containerd"
EOF
# 重启服务
sudo systemctl reset-failed docker.service 2>/dev/null || true
sudo systemctl start containerd
sudo systemctl start docker
# --------------------------------------------------
# 7. 加载 evalscope 环境镜像
# --------------------------------------------------
echo "==> 加载 evalscope Docker 镜像..."
docker load -i "$DOCKER_DIR/evalscope-complete-py312.tar.gz"
# --------------------------------------------------
# 8. 克隆代码仓库
# --------------------------------------------------
echo "==> 克隆 evalscope 代码..."
cd "$EVALSCOPE_DIR"
if [ ! -d "evalstone" ]; then
git clone https://git.meta-stone.net/sora/evalstone.git
fi
# --------------------------------------------------
# 9. 准备 sandbox 镜像
# --------------------------------------------------
SANDBOX_IMAGES_DIR="$DOCKER_DIR/sandbox_images"
mkdir -p "$SANDBOX_IMAGES_DIR"
# 函数:带超时拉取镜像,本地已有则跳过
pull_with_timeout() {
local image="$1"
local timeout_sec="${2:-600}"
if docker images --format '{{.Repository}}:{{.Tag}}' | grep -qx "$image"; then
echo "镜像 $image 已存在,跳过 pull"
return 0
fi
echo "拉取 $image (超时 ${timeout_sec}s)..."
if timeout "$timeout_sec" docker pull "$image"; then
echo "$image 拉取成功"
return 0
else
echo "警告:$image 拉取失败或超时"
return 1
fi
}
# 优先从 ModelScope 下载预打包的 sandbox 镜像,避免 DockerHub 拉取慢/失败
download_sandbox_image_from_modelscope() {
local file="$1"
if [ -f "$SANDBOX_IMAGES_DIR/$file" ]; then
echo "sandbox 镜像包 $file 已存在,跳过下载"
return 0
fi
echo "==> 从 ModelScope 下载 $file ..."
# 优先使用新版 modelscope_hub API支持 dataset repo
python3 -c "
from modelscope_hub import HubApi
api = HubApi()
api.download_file(
repo_id='SoraAmami/evalscope-sandbox-images',
repo_type='dataset',
path='$file',
local_dir='$SANDBOX_IMAGES_DIR'
)
" && return 0
echo "警告:从 ModelScope 下载 $file 失败"
return 1
}
load_sandbox_image() {
local file="$1"
local expected_image="$2"
if docker images --format '{{.Repository}}:{{.Tag}}' | grep -qx "$expected_image"; then
echo "镜像 $expected_image 已存在,跳过加载"
return 0
fi
if [ -f "$SANDBOX_IMAGES_DIR/$file" ]; then
echo "==> 加载 $file ..."
docker load -i "$SANDBOX_IMAGES_DIR/$file"
return 0
fi
return 1
}
# 9.1 bigcodebench-sandbox:latest
if ! docker images --format '{{.Repository}}:{{.Tag}}' | grep -qx "bigcodebench-sandbox:latest"; then
if ! load_sandbox_image "bigcodebench-sandbox.tar.gz" "bigcodebench-sandbox:latest"; then
download_sandbox_image_from_modelscope "bigcodebench-sandbox.tar.gz" && \
load_sandbox_image "bigcodebench-sandbox.tar.gz" "bigcodebench-sandbox:latest"
fi
fi
# 9.2 python:3.11-slim
if ! docker images --format '{{.Repository}}:{{.Tag}}' | grep -qx "python:3.11-slim"; then
if ! load_sandbox_image "python-3.11-slim.tar.gz" "python:3.11-slim"; then
download_sandbox_image_from_modelscope "python-3.11-slim.tar.gz" && \
load_sandbox_image "python-3.11-slim.tar.gz" "python:3.11-slim"
fi
fi
# 9.3 fallback如果 ModelScope 失败,尝试从 DockerHub / 镜像站拉取
if ! docker images --format '{{.Repository}}:{{.Tag}}' | grep -qx "bigcodebench-sandbox:latest"; then
echo "==> 尝试 fallback拉取 bigcodebench 基础镜像并构建 sandbox..."
pull_with_timeout "bigcodebench/bigcodebench-evaluate:latest" 600 || true
if docker images --format '{{.Repository}}:{{.Tag}}' | grep -qx "bigcodebench/bigcodebench-evaluate:latest"; then
echo "==> 构建 bigcodebench-sandbox..."
mkdir -p /tmp/bigcodebench-sandbox-ctx
docker build -t bigcodebench-sandbox:latest -f - /tmp/bigcodebench-sandbox-ctx <<'EOF' || echo "警告bigcodebench-sandbox 构建失败"
FROM bigcodebench/bigcodebench-evaluate:latest
ENTRYPOINT []
CMD ["tail", "-f", "/dev/null"]
EOF
else
echo "警告bigcodebench 基础镜像不存在,跳过 bigcodebench-sandbox 构建"
fi
fi
if ! docker images --format '{{.Repository}}:{{.Tag}}' | grep -qx "python:3.11-slim"; then
pull_with_timeout "python:3.11-slim" 300 || true
fi
# --------------------------------------------------
# 10. 下载 SWE-Bench 镜像包(可选)
# --------------------------------------------------
if [ -n "$MODELSCOPE_TOKEN" ]; then
echo "==> 下载 SWE-Bench 镜像包..."
mkdir -p "$SWE_IMAGES_DIR"
modelscope download \
--repo-type dataset \
--local_dir "$SWE_IMAGES_DIR" \
SoraAmami/swe-bench-verified-images || echo "警告SWE-Bench 镜像下载失败"
fi
# --------------------------------------------------
# 11. 加载 SWE-Bench 镜像(如果存在)
# --------------------------------------------------
if [ -d "$SWE_IMAGES_DIR" ] && ls "$SWE_IMAGES_DIR"/swebench_batch_*.tar.gz 1>/dev/null 2>&1; then
echo "==> 加载 SWE-Bench 镜像..."
TARS=("$SWE_IMAGES_DIR"/swebench_batch_*.tar.gz)
TOTAL=${#TARS[@]}
echo "共找到 $TOTAL 个镜像包"
IDX=0
for TAR in "${TARS[@]}"; do
IDX=$((IDX + 1))
echo "[$IDX/$TOTAL] 加载 $(basename "$TAR")..."
docker load -i "$TAR"
done
echo "SWE-Bench 镜像加载完成,已加载 $(docker images | grep '^swebench/' | wc -l)"
else
echo "跳过 SWE-Bench 镜像加载(未找到镜像包)"
fi
# --------------------------------------------------
# 12. 验证部署
# --------------------------------------------------
echo ""
echo "========================================"
echo "部署完成!"
echo "========================================"
echo "基础目录: $BASE_DIR"
echo "EvalScope: $EVALSCOPE_DIR"
echo "数据集: $DATASETS_DIR"
echo "Docker 数据: $IMAGES_DIR"
echo "Containerd: $CONTAINERD_DIR"
echo ""
echo "Docker Root Dir:"
docker info 2>/dev/null | grep "Docker Root Dir" || echo "Docker 未运行"
echo ""
echo "已加载镜像:"
docker images | grep -E "evalscope|swebench|bigcodebench|python" || true
echo ""
echo "运行 EvalScope:"
echo " cd $EVALSCOPE_DIR"
echo " docker run -it --rm \\"
echo " --network host \\"
echo " -v $EVALSCOPE_DIR:/opt/evalscope \\"
echo " -v /var/run/docker.sock:/var/run/docker.sock \\"
echo " evalscope-complete-py312:latest \\"
echo " bash"
echo "========================================"