# EvalScope Benchmark Docker 镜像构建实录 > 本文档记录 `evalscope-complete-py312` Docker 镜像的完整搭建过程、关键参数、踩坑与解决方案,供后续复现和维护参考。 --- ## 1. 目标 构建一个**开箱即用**的 Docker 镜像,满足: - 基于 Python 3.12 - 内置 evalscope 源码及全部依赖 - 除 `swe_bench` 系列外,其他 benchmark 无需再安装任何东西即可运行 - 支持代码执行 sandbox(humaneval、bigcodebench) - 支持 Agent benchmark(tau2_bench、general_fc) - 支持 function calling 评测(bfcl_v3) - 在国内网络环境下可构建 --- ## 2. 基础镜像选择 ```dockerfile 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` 参数 ```bash 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` 参数 这是用得最多的命令: ```bash 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` 是本项目的核心**: ```bash -v /data1/sora/evalscope/datasets:/opt/evalscope/datasets ``` 意思是:把宿主机的 `/data1/sora/evalscope/datasets` 映射到容器内的 `/opt/evalscope/datasets`。这样容器就能读取宿主机上的数据,评测结果也能写回宿主机。 ### 4.5 其他常用命令 ```bash # 查看本地镜像 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` 命令是什么意思? ```bash 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 构建镜像 ```bash 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 ```bash 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 核心依赖 ```bash 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 支持 ```bash 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 依赖 ```bash pip install "harbor>=0.8.0,<1.0.0" ``` ### 6.4 tau2-bench 依赖 ```bash 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 评估依赖 ```bash pip install \ tree-sitter tree-sitter-python tempdir termcolor wget gradio-client ``` ### 6.6 bfcl_v3 依赖 ```bash 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 其他工具 ```bash 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。 **解决**: ```bash 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 中显式约束: ```bash 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`。 **解决**: ```python 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. 镜像验证 构建完成后,验证镜像内关键组件: ```bash 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` 运行: ```bash 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` 运行: ```bash python bash/run_standard.py --model YourModel --api-url http://localhost:30000/v1 --limit 100 ``` ### 9.3 Full 版 覆盖 `run.py` 中全部 benchmark,使用完整数据集和多次采样配置。 运行: ```bash 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. 重新构建流程 ```bash 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' ) " ```