17 KiB
EvalScope Benchmark Docker 镜像构建实录
本文档记录
evalscope-complete-py312Docker 镜像的完整搭建过程、关键参数、踩坑与解决方案,供后续复现和维护参考。
1. 目标
构建一个开箱即用的 Docker 镜像,满足:
- 基于 Python 3.12
- 内置 evalscope 源码及全部依赖
- 除
swe_bench系列外,其他 benchmark 无需再安装任何东西即可运行 - 支持代码执行 sandbox(humaneval、bigcodebench)
- 支持 Agent benchmark(tau2_bench、general_fc)
- 支持 function calling 评测(bfcl_v3)
- 在国内网络环境下可构建
2. 基础镜像选择
FROM python:3.12-slim-bookworm
2.1 这句话是什么意思?
FROM 是 Dockerfile 的第一条指令,意思是:我要基于哪个镜像开始构建。
python:3.12-slim-bookworm 可以拆成三部分:
| 部分 | 含义 |
|---|---|
python |
镜像名称,官方 Python 镜像 |
3.12 |
Python 版本号 |
slim-bookworm |
镜像变体标签 |
slim 表示精简版,只保留最基础的东西,体积小。
bookworm 是 Debian 12 的代号,这是 Linux 发行版的一个版本。
所以整句意思是:基于官方 Python 3.12 精简版(Debian 12)镜像来构建我们的环境。
类比理解:
就像你要装修房子,
FROM就是你选择的一套毛坯房。python:3.12-slim-bookworm就是一套已经通了水电(装了 Python 和 pip)、但还没放家具(没装 evalscope 依赖)的毛坯房。
2.2 为什么选择这个镜像?
- Python 3.12:项目主力环境
- slim:体积小,比完整版少几百 MB
- bookworm:Debian 12 稳定,apt 包源丰富
- 已经包含 pip:不需要自己装 Python
3. 关键构建参数
| 参数 | 值 | 说明 |
|---|---|---|
DEBIAN_FRONTEND=noninteractive |
环境变量 | 避免 apt 交互式提示 |
PYTHONUNBUFFERED=1 |
环境变量 | Python 输出不缓冲 |
PIP_NO_CACHE_DIR=1 |
环境变量 | 不保留 pip 缓存,减小镜像 |
PYTHONDONTWRITEBYTECODE=1 |
环境变量 | 不生成 .pyc |
PYTHONPATH=/opt/evalscope/evalscope |
环境变量 | 解决 editable install 加载问题 |
| apt 源 | 清华镜像 | 国内加速 |
| pip 源 | 清华镜像 | 国内加速 |
| 基础包 | git/wget/curl/build-essential 等 | 编译依赖 |
| docker.io | apt 安装 | 容器内支持套 Docker 跑 sandbox |
4. Docker 命令参数详解(新手向)
如果你是 Docker 新手,先把下面几个概念和参数搞懂,后续命令就不难了。
4.1 核心概念
- 镜像(Image):一个只读的模板,相当于一个打包好的环境。比如
evalscope-complete-py312:latest。 - 容器(Container):镜像运行起来的实例。你可以把镜像理解为 Class,容器是 Object。
- 宿主机(Host):运行 Docker 的那台物理机/虚拟机。
- 容器内(Container):Docker 容器里面的环境。
4.2 Dockerfile 常用指令
本项目 Dockerfile.py312 里用到的指令:
| 指令 | 作用 | 示例 |
|---|---|---|
FROM |
基础镜像 | FROM python:3.12-slim-bookworm |
ENV |
设置环境变量 | ENV PYTHONPATH=/opt/evalscope/evalscope |
RUN |
执行命令(每行会产生一层镜像) | RUN pip install ... |
COPY |
把宿主机文件复制到镜像里 | COPY bash/ /opt/evalscope/bash/ |
WORKDIR |
设置工作目录 | WORKDIR /opt/evalscope |
CMD |
容器启动时默认执行的命令 | CMD ["/bin/bash"] |
4.3 docker build 参数
docker build -f Dockerfile.py312 -t evalscope-complete-py312:latest .
| 参数 | 含义 |
|---|---|
-f Dockerfile.py312 |
指定用哪个 Dockerfile(默认是当前目录的 Dockerfile) |
-t evalscope-complete-py312:latest |
给构建好的镜像打标签,name:tag 格式 |
. |
构建上下文路径,Docker 会把这个目录下的文件传给构建进程 |
4.4 docker run 参数
这是用得最多的命令:
docker run -it --rm \
--network host \
-v /data1/sora/evalscope/datasets:/opt/evalscope/datasets \
-v /data1/sora/evalscope/output:/opt/evalscope/output \
-v /var/run/docker.sock:/var/run/docker.sock \
evalscope-complete-py312:latest \
bash -c "cd /opt/evalscope && python bash/run_lite.py ..."
| 参数 | 含义 |
|---|---|
docker run |
创建并启动一个容器 |
-i |
交互模式,保持 STDIN 打开 |
-t |
分配一个伪终端,让你能看到彩色输出 |
-it |
上面两个一起用,几乎必加 |
--rm |
容器停止后自动删除,避免垃圾容器堆积 |
--network host |
容器和宿主机共用网络,localhost 指向宿主机 |
-v 宿主机路径:容器内路径 |
挂载目录/文件,容器内的改动会反映到宿主机 |
-e KEY=VALUE |
设置容器内环境变量 |
evalscope-complete-py312:latest |
要运行的镜像名 |
bash -c "..." |
容器启动后执行的命令 |
挂载参数 -v 是本项目的核心:
-v /data1/sora/evalscope/datasets:/opt/evalscope/datasets
意思是:把宿主机的 /data1/sora/evalscope/datasets 映射到容器内的 /opt/evalscope/datasets。这样容器就能读取宿主机上的数据,评测结果也能写回宿主机。
4.5 其他常用命令
# 查看本地镜像
docker images
# 查看运行中的容器
docker ps
# 查看所有容器(包括停止的)
docker ps -a
# 删除镜像
docker rmi evalscope-complete-py312:latest
# 删除容器
docker rm 容器ID
# 进入正在运行的容器
docker exec -it 容器ID /bin/bash
# 加载 tar.gz 镜像
docker load -i evalscope-complete-py312.tar.gz
# 导出镜像为 tar.gz
docker save -o evalscope-complete-py312.tar.gz evalscope-complete-py312:latest
4.6 为什么这个项目不需要 --gpus all
很多 Docker + GPU 的教程会写 --gpus all,但本项目不需要。
原因是:
- evalscope 容器本身只做 API 调用,不跑模型推理。
- 模型服务(sglang)是在宿主机上启动的。
- 容器只需要通过网络访问
http://localhost:30000/v1。
所以用 --network host 就够了,不需要把 GPU 分配给容器。
5. 构建步骤
5.1 准备构建上下文
tools/docker/ 这个文件夹里都要放什么?
tools/docker/
├── Dockerfile.py312 # 构建配方
├── evalscope/ # evalscope 源码(从项目根目录同步过来)
├── bash/ # bash 脚本(从项目根目录同步过来)
└── tau2-bench/ # tau2-bench 源码(从项目根目录同步过来)
为什么需要同步?
因为 Docker 构建时只能访问 tools/docker/ 这个目录下的文件(这叫构建上下文)。而项目的源码在 /data1/sora/evalscope/ 根目录下,所以需要先把需要的部分复制到 tools/docker/ 里。
rsync 命令是什么意思?
rsync -av --delete --exclude='*.log' --exclude='__pycache__' \
bash/ tools/docker/bash/
拆开看:
| 参数 | 含义 |
|---|---|
rsync |
文件同步工具 |
-a |
归档模式,保留文件权限、时间等 |
-v |
显示同步了哪些文件 |
--delete |
删除目标目录中源目录没有的文件,保持完全一致 |
--exclude='*.log' |
不同步 .log 日志文件 |
--exclude='__pycache__' |
不同步 Python 缓存目录 |
bash/ |
源目录(项目根目录的 bash 脚本) |
tools/docker/bash/ |
目标目录(构建上下文中的位置) |
用人话讲:把 bash/ 里的文件同步到 tools/docker/bash/,去掉日志和缓存,并且保证两边完全一样。
为什么用 rsync 而不用 cp -r?
因为 cp -r 只是简单复制,不会删除目标目录里多余的旧文件。如果用 cp -r,以前删除的脚本可能还会留在 tools/docker/bash/ 里,最终被打包进镜像。
5.2 构建镜像
cd /data1/sora/evalscope/tools/docker
docker build -f Dockerfile.py312 -t evalscope-complete-py312:latest .
5.3 环境是怎么装进 Docker 里的?
很多新手会问:Python 包是怎么装到镜像里的?答案是:Dockerfile 里的 RUN pip install ... 命令会在构建时执行,把包装到镜像里。
整个流程可以概括为:
毛坯房(python:3.12-slim-bookworm)
↓
装修第一层:换国内源、装系统工具(apt)
↓
装修第二层:RUN pip install ... 装 Python 包
↓
装修第三层:COPY 项目源码进镜像
↓
装修第四层:把源码用 pip install -e 安装好
↓
装修第五层:设置 PYTHONPATH、权限等收尾工作
↓
完工(evalscope-complete-py312:latest 镜像)
关键步骤对应 Dockerfile 里的这些行:
| Dockerfile 行 | 作用 |
|---|---|
FROM python:3.12-slim-bookworm |
选毛坯房 |
RUN apt-get update && apt-get install ... |
装系统工具 |
RUN pip install openai pandas ... |
装 Python 包 |
COPY evalscope/ /opt/evalscope/evalscope/ |
复制源码 |
RUN pip install -e /opt/evalscope/evalscope/ |
安装 evalscope 本身 |
ENV PYTHONPATH=/opt/evalscope/evalscope |
设置环境变量 |
RUN 和 COPY 的区别:
RUN:执行命令,比如安装软件COPY:复制文件,比如把源码复制进去
为什么先 pip install 再 COPY 源码?
因为 Docker 有缓存机制。如果依赖没有变化,只有源码变化,Docker 会直接使用之前装好的依赖层,只重新构建 COPY 和之后的层,节省大量时间。
5.4 导出 tar.gz
docker save -o evalscope-complete-py312.tar.gz evalscope-complete-py312:latest
md5sum evalscope-complete-py312.tar.gz > evalscope-complete-py312.tar.gz.md5
6. 安装的关键依赖
6.1 evalscope 核心依赖
pip install \
openai pandas numpy pyyaml requests tqdm \
tiktoken transformers \
scikit-learn matplotlib seaborn plotly \
jieba nltk rouge-score sacrebleu \
sympy latex2sympy2_extended pillow \
docker pexpect pytest \
tabulate rich jsonlines jsonschema \
langdetect word2number zhconv \
modelscope pydantic overrides \
more_itertools pylatexenc \
rouge-chinese markdown \
editdistance dotenv docstring_parser \
colorlog
6.2 Sandbox 支持
pip install evalscope[sandbox] 2>/dev/null || pip install ms-sandbox 2>/dev/null || true
ms-sandbox 是 ModelScope 的 sandbox 实现,用于 humaneval、bigcodebench 的隔离代码执行。
6.3 terminal_bench 依赖
pip install "harbor>=0.8.0,<1.0.0"
6.4 tau2-bench 依赖
pip install \
fastapi uvicorn psutil loguru \
litellm tenacity deepdiff addict toml
# 同时把本地 tau2-bench 源码 editable 安装
pip install -e /opt/evalscope/tools/tau2-bench/ --no-deps
6.5 bigcodebench 评估依赖
pip install \
tree-sitter tree-sitter-python tempdir termcolor wget gradio-client
6.6 bfcl_v3 依赖
pip install --no-deps bfcl-eval==2025.10.27.1
pip install \
anthropic cohere==5.18.0 datamodel-code-generator==0.25.7 \
faiss-cpu==1.11.0 google-genai==1.24.0 mistralai==1.7.0 \
networkx==3.3 numpy==1.26.4 google-search-results \
rank_bm25 html2text boto3 qwen-agent writer-sdk \
tree-sitter tree-sitter-python tree-sitter-javascript tree-sitter-java
注意:bfcl-eval 默认会装 torch,这里用 --no-deps 避免把 torch 拉进来,再手动补缺少的依赖。
6.7 其他工具
pip install soundfile openpyxl
7. 踩坑记录
7.1 editable install 加载失败
现象:
ImportError: cannot import name 'run_task' from 'evalscope' (unknown location)
原因:evalscope 用 pip install -e /opt/evalscope/evalscope/ 安装后,editable finder 有时不能正确加载 __init__.py。
解决:在 Dockerfile 末尾设置 ENV PYTHONPATH=/opt/evalscope/evalscope。
7.2 镜像里缺少新脚本
现象:其他机器拉下来跑 run_group2.py 报 No such file or directory。
原因:Docker 镜像构建后,项目根目录又新增了 run_group1/2/3.py、run_1.py 等文件,但镜像没有重新构建。
解决:每次修改 bash/ 目录后,必须重新同步到 tools/docker/bash/,然后重新 docker build 和 docker save。
7.3 镜像体积过大
现象:安装 bfcl-eval 默认会拉 torch,镜像暴涨几个 GB。
解决:
pip install --no-deps bfcl-eval==2025.10.27.1
再手动安装缺少的非 torch 依赖。
7.4 litellm 版本冲突
现象:tau2-bench 要求 litellm>=1.80.15,<1.82.7,但 bfcl-eval 可能装更高版本。
解决:在 Dockerfile 中显式约束:
pip install "litellm>=1.80.15,<1.82.7" "tenacity>=9.0.0"
7.5 国内网络下载慢/超时
现象:pip、apt、git clone 都很慢。
解决:
- apt 换清华源
- pip 换清华源
- git+https 安装 tau2-bench 时容易失败,改为本地 COPY 源码后 editable 安装
7.6 ModelScope 上传 API 参数错误
现象:
TypeError: HubApi.upload_file() got an unexpected keyword argument 'model_id'
原因:新版 ModelScope SDK 参数名是 repo_id,不是 model_id。
解决:
api.upload_file(
repo_id='SoraAmami/evalscope-docker',
path_or_fileobj='evalscope-complete-py312.tar.gz',
path_in_repo='evalscope-complete-py312.tar.gz'
)
7.7 数据集路径重复
现象:/data1/sora/evalscope/datasets/datasets/ 下又有一层数据。
原因:evalscope 会在 dataset_dir 后自动拼 datasets/。如果把 dataset_dir 指向了已经包含 datasets/ 的路径,就会重复。
解决:始终让 dataset_dir 指向 datasets/ 的父目录。
7.8 运行时缺少 tokenizer
现象:transformers 提示 PyTorch was not found。
原因:镜像里没有 torch( intentional,为了减小体积)。
影响:evalscope 的 tokenizer 加载通常只需要 transformers 的 tokenizer 部分,实际运行时通过 API 调用模型,不需要本地 PyTorch。该警告可忽略。
8. 镜像验证
构建完成后,验证镜像内关键组件:
docker run --rm evalscope-complete-py312:latest bash -c "
python -c 'import evalscope; print(evalscope.__file__)' && \
python -c 'import evalscope.api.agent' && \
python -c 'import docker' && \
python -c 'import harbor' && \
python -c 'import tau2' && \
python -c 'import bfcl_eval' && \
python -c 'import soundfile' && \
ls -la /opt/evalscope/bash/run_1.py /opt/evalscope/bash/run_group*.py
"
9. 三个评测版本
为了兼顾快速验证和完整评测,提供三个版本:
| 版本 | 预计时间 | 用途 |
|---|---|---|
| Lite | ~2-4h | 快速冒烟,每个能力域 1-2 个 benchmark |
| Standard | ~20-28h | 常规能力评测,覆盖主要 benchmark |
| Full | ~3-5 天 | 完整评测,全部 benchmark + 多次采样 |
每个版本都覆盖 5 大能力域:代码生成、推理/数学、知识、长上下文、智能体/工具。
9.1 Lite 版
- 代码:
humaneval - 推理/数学:
aime24、gsm8k - 知识:
mmlu_pro、simple_qa - 长上下文:
longbench_v2 - 智能体/工具:
bfcl_v3
运行:
python bash/run_lite.py --model YourModel --api-url http://localhost:30000/v1 --limit 20
9.2 Standard 版
- 代码:
humaneval、live_code_bench、bigcodebench - 推理/数学:
aime24、aime25、aime26、hmmt26、gsm8k、competition_math、bbh、drop - 知识:
gpqa_diamond、mmlu_pro、simple_qa、super_gpqa、mmlu、cmmlu、arc、hellaswag、trivia_qa、winogrande - 长上下文:
longbench_v2、openai_mrcr - 智能体/工具:
tau2_bench、general_fc、bfcl_v3
运行:
python bash/run_standard.py --model YourModel --api-url http://localhost:30000/v1 --limit 100
9.3 Full 版
覆盖 run.py 中全部 benchmark,使用完整数据集和多次采样配置。
运行:
python bash/run.py --model YourModel --api-url http://localhost:30000/v1 --limit none
10. 文件清单
tools/docker/
├── Dockerfile.py312 # 镜像构建定义
├── evalscope-complete-py312.tar.gz # 导出的镜像
├── evalscope-complete-py312.tar.gz.md5
├── deploy.sh # 目标机器部署脚本
├── export_image.sh # 导出脚本
├── build_*.log # 构建日志
└── README.md # 使用说明
11. 重新构建流程
cd /data1/sora/evalscope
# 1. 同步最新代码
rsync -av --delete bash/ tools/docker/bash/
rsync -av --delete evalscope/ tools/docker/evalscope/
rsync -av --delete tau2-bench/ tools/docker/tau2-bench/
# 2. 构建
cd tools/docker
docker build -f Dockerfile.py312 -t evalscope-complete-py312:latest .
# 3. 导出
docker save -o evalscope-complete-py312.tar.gz evalscope-complete-py312:latest
md5sum evalscope-complete-py312.tar.gz > evalscope-complete-py312.tar.gz.md5
# 4. 上传到 ModelScope(可选)
python3 -c "
from modelscope.hub.api import HubApi
api = HubApi()
api.login('ms-3d554a39-6e07-496d-8022-0b0ee64a6389')
api.upload_file(
repo_id='SoraAmami/evalscope-docker',
path_or_fileobj='evalscope-complete-py312.tar.gz',
path_in_repo='evalscope-complete-py312.tar.gz'
)
"