任务格式
SForge 运行的是 benchmark。一个 benchmark 就是一个目录,其中每道题也是一个目录,以明文存放这道题的全部内容:prompt、工作区、评分程序、隐藏测试和数据。镜像是由它构建出来的产物。
<benchmark>/
benchmark.toml # 名称、镜像 registry、blob 地址
tasks/<task_id>/ # 一道题一个目录
bases/<name>/Dockerfile # 可选:多道题共用的工具链镜像用 -b <目录> 或 SFORGE_BENCHMARK 把这个目录交给 SForge;都不设置时用 edgebench/。本仓库的 EdgeBench 在 edgebench/ 下;它自己的出题规则(固定版本、数据上限、NOTES.md、状态)见 edgebench/tasks/SCHEMA.md。
benchmark.toml
name = "edgebench" # 镜像名:<name>.{base,work,judge}.<id>:<hash>
registry = "docker.io/seededge" # 可选:sforge pull 和 Kubernetes 从这里拉镜像
blob_url = "https://github.com/ByteDance-Seed/EdgeBench/releases/download/task-blobs" # 可选,见 Blob题目目录
tasks/<task_id>/
task.toml # 元数据和运行约定
instruction.md # 给 Agent 的 prompt
environment/
Dockerfile # work 镜像(构建上下文:任务目录)
... # 工作区文件:README、工具、初始代码
judge/
Dockerfile # judge 镜像(构建上下文:任务目录)
... # 评分程序、隐藏测试、测试数据NOTES.md、参考解等其他文件 SForge 不读取。镜像以 root 运行;Agent 由 SForge 在运行时拷入,不要装进镜像。
task.toml
下面是 SForge 读取的字段;其他 key(例如 EdgeBench 的 category、status)是自由的元数据。
id = "ad_placement_optimization" # 必须与目录名一致
name = "Ad Placement Optimization"
version = "1.0.0" # 任何可能改变分数的修改都要提升版本
[environment]
base = "cpp" # bases/<name>;Dockerfile 直接用公开镜像时省略
platform = "linux/amd64"
cwd = "/home/workspace/ad-placement" # Agent 的工作目录
internet = false # work 容器能否访问外网
cpus = 4 # work 容器的 CPU 和内存(可选;运行时可覆盖)
memory = "16g"
[submission]
paths = ["solution.cpp"] # sforge-submit 提交的内容,相对 cwd("." 表示全部)
exclude = [] # 要排除的路径前缀或文件名 glob
[judge]
cmd = "bash /judge/run.sh" # 提交解压到 cwd 后,在 cwd 中运行
timeout_sec = 600
internet = false # 默认:judge 容器没有网络
cpus = 4 # judge 容器的 CPU 和内存,同 work 容器
memory = "16g"
[score]
direction = "maximize" # maximize | minimize
rescale = { kind = "piecewise_max", baseline = 0.0, rank30 = 47169682940.0, rank1 = 49702568341.0, super_anchor = 50000000000.0 }
[[blobs]] # 只用于放在 git 之外的数据
path = "judge/blobs/hidden.tar.gz"
sha256 = "..."
[game] # 只用于游戏类任务
server_cmd = "..."rescale 把原始分映射到 0–100。可用的 kind 包括 linear、log_max、log_min、log_anchor、piecewise_max、piecewise_min 和 piecewise_log_min,参数见 sforge/harness/score_rescale.py。
评分约定
SForge 在一个全新的 judge 容器中把过滤后的提交解压到
cwd,然后在那里运行[judge] cmd。评分程序必须写出
/opt/sforge/result.json:json{"score": 43623268300.0, "valid": true, "summary": "50/50 cases ok", "details": [{"name": "case_0000", "status": "PASSED", "score": 1.0}], "metrics": {}}score是原始分,valid为 true 时必须给出。valid: false表示拒绝这次提交。summary、details和metrics只作为反馈。stdout 和 stderr 只是日志,SForge 不解析。
judge 容器通过
SFORGE_CPUS和SFORGE_MEMORY得到自己的资源限制(work 容器同样有自己的一份)。并发数请按SFORGE_CPUS设置:在 CPU 配额下,nproc和os.cpu_count()看到的是宿主机的核数。没有
result.json、分数不是数字或 judge 超时,这次提交都无效。编译失败或空提交,只要评分程序能给出分数,就应当是有效分(通常为 0)。一次运行的最终结果是所有有效提交中按
direction最好的score。
镜像与 base
镜像 tag 是 SForge 计算的内容哈希,前缀是 benchmark 的 name:
| 镜像 | tag 哈希的内容 |
|---|---|
<name>.base.<base>:<hash> | bases/<base>/ |
<name>.work.<id>:<hash> | environment/ 和 base 的 tag |
<name>.judge.<id>:<hash> | environment/、judge/ 和 base 的 tag |
Blob 按声明的 sha256 计入哈希。修改 task.toml 或 instruction.md 不会触发镜像重建。使用 base 的任务 Dockerfile 以 ARG BASE_IMAGE / FROM ${BASE_IMAGE} 开头;sforge build 会先构建 base 并传入它的 tag。
Blob
放不进 git 的大数据打成 blob:在 [[blobs]] 里列出 tar 包及其 sha256,文件放在任务目录中被 gitignore 的路径下,由 Dockerfile ADD。构建前,sforge build 会校验每个 blob,缺失的从 $SFORGE_BLOB_URL/<sha256> 下载,没有设置时用 benchmark 的 blob_url。file:// 地址可以指向本地副本。
修改或新增任务
# 1. 修改 edgebench/tasks/<task_id>/(可能改变分数时提升 version)
# 2. 构建:base、work 和 judge 镜像,并下载所需的 blob
sforge build --task <task_id>
# 3. 用新的 judge 给一个提交包评分
tar -czf sub.tar.gz -C <workspace> .
sforge eval --task <task_id> --archive sub.tar.gz
# 4. 可选:让 Agent 实际跑一遍
sforge run --task <task_id> --agent claude-code --timeout 3600tools/judge_local.py 可以绕过 judge server,直接用 judge 镜像给一个 archive 评分,并输出一行 JSON 结果。
EdgeBench 题目问题的报告和修复流程见 CONTRIBUTING.md,出题规则见 edgebench/tasks/SCHEMA.md。CI 会构建改动的镜像,合并后发布到 Docker Hub(见预构建镜像)。