evalstone/DOCKER_BUILD.md
2026-07-21 09:32:49 +00:00

17 KiB
Raw Blame History

EvalScope Benchmark Docker 镜像构建实录

本文档记录 evalscope-complete-py312 Docker 镜像的完整搭建过程、关键参数、踩坑与解决方案,供后续复现和维护参考。


1. 目标

构建一个开箱即用的 Docker 镜像,满足:

  • 基于 Python 3.12
  • 内置 evalscope 源码及全部依赖
  • swe_bench 系列外,其他 benchmark 无需再安装任何东西即可运行
  • 支持代码执行 sandboxhumaneval、bigcodebench
  • 支持 Agent benchmarktau2_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
  • bookwormDebian 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 的那台物理机/虚拟机。
  • 容器内ContainerDocker 容器里面的环境。

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 设置环境变量

RUNCOPY 的区别

  • RUN:执行命令,比如安装软件
  • COPY:复制文件,比如把源码复制进去

为什么先 pip installCOPY 源码?
因为 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.pyNo such file or directory

原因Docker 镜像构建后,项目根目录又新增了 run_group1/2/3.pyrun_1.py 等文件,但镜像没有重新构建。

解决:每次修改 bash/ 目录后,必须重新同步到 tools/docker/bash/,然后重新 docker builddocker 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
  • 推理/数学:aime24gsm8k
  • 知识:mmlu_prosimple_qa
  • 长上下文:longbench_v2
  • 智能体/工具:bfcl_v3

运行:

python bash/run_lite.py --model YourModel --api-url http://localhost:30000/v1 --limit 20

9.2 Standard 版

  • 代码:humanevallive_code_benchbigcodebench
  • 推理/数学:aime24aime25aime26hmmt26gsm8kcompetition_mathbbhdrop
  • 知识:gpqa_diamondmmlu_prosimple_qasuper_gpqammlucmmluarchellaswagtrivia_qawinogrande
  • 长上下文:longbench_v2openai_mrcr
  • 智能体/工具:tau2_benchgeneral_fcbfcl_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'
)
"