WorkForge 这类环境出问题,多数不是配置写错,而是变量太多:任务有十几个、依赖装了一堆、评测脚本还在改,某次跑挂了根本分不清是任务加载、执行器调用还是评分环节出的错。比较省事的做法是先只留一个任务、一个评测脚本、一个执行入口,把「加载任务 → 执行 → 评分 → 落盘」这条链路走通,再把依赖收进单一配置文件,然后才谈任务数量和并发。分层搭建的核心不是层数多,而是每加一层之前,上一层的结果是能被日志和产物单独验证的。
适用场景:刚接手或刚自建 WorkForge 环境、还没跑通过完整闭环时。操作动作:任务数压到 1、并发压到 1、评测脚本只留 1 个,先跑通再叠层,每加一个变量单独验证一次。验证方式:单次运行的日志能按 stage 逐段对上,目录里能看到对应产物,评分脚本能读到执行输出。风险边界:这套顺序保证的是「可定位、可重复」,不代表性能或效果达标;等到要接真实数据集和多模型时,仍需结合环境确认可用性。
先只留一个任务和一个评测脚本
第一层要压的不是代码量,是变量个数。任务数设为 1、并发设为 1、评测脚本只保留一个、执行器只接一种、随机种子写死成固定值。这样任何一次失败都只可能来自五个位置之一,排查面立刻收窄。可以先从一个打印或做简单字符串处理的任务入手,输入输出都用固定样本,先别接真实业务数据。
建议的最小目录结构如下,每个文件只承担一件事:
workforge/
├── config/
│ └── env.yaml # 环境入口:路径、seed、并发数=1、执行器名
├── tasks/
│ └── hello_task/
│ ├── task.yaml # 任务定义:任务 id、输入文件位置、期望输出格式
│ └── input.txt # 固定输入样本,内容越短越容易人眼比对
├── eval/
│ └── eval_basic.py # 唯一评测脚本:读 runs 产物,输出 score 和明细
├── scripts/
│ └── run_once.sh # 唯一入口命令,固定只跑一个任务
└── runs/ # 每次运行的产物落在这里,按 run_id 分目录
env.yaml 是唯一的环境真相来源,其他文件不要各自再写一遍路径和参数;task.yaml 只描述任务本身,不掺环境信息;eval_basic.py 只消费产物、不负责执行,避免评分逻辑和执行逻辑互相污染;run_once.sh 存在的意义是让「怎么跑」只有一个答案,减少手动敲命令带来的差异。这一层跑通前,不建议增加第二个任务——多一个任务就多一份输入格式和期望输出,排查时会分不清是环境问题还是任务写错。
把依赖收进单一配置文件
目录能跑起来之后,把依赖显式写进一个清单文件,让环境可重建。这一步的常见失误是顺手把整套依赖一次性装齐,结果某个包版本冲突导致执行器加载失败,反而更难判断链路是否通畅。建议清单先只写链路真正用到的包,等闭环稳定后再补其余部分,并且先不锁死版本号。
# requirements.txt —— 跑通阶段先不指定版本
pyyaml
jsonschema
# 执行器相关依赖,确认要用哪种接入方式后再放开
# some-executor-sdk
安装命令骨架大致是这样的,放在项目根目录执行:
python -m venv .venv
source .venv/bin/activate
python -m pip install -U pip
python -m pip install -r requirements.txt
闭环验证通过后,再用 pip freeze 或等价方式生成锁文件,把当时实际装上的版本固定下来,保证换机器能重建出同一套环境。锁文件属于「跑通之后」的动作,不要提前做,否则会把排查期间的中间状态固化下来。依赖清单和锁文件都应提交到版本库,但虚拟环境目录本身不提交。
跑通任务到执行再到评分的完整链路
这一层要看的是数据有没有真的传过去,而不是命令有没有返回 0。建议让一次运行的日志按阶段打点,每段都带上同一个 run_id,方便串起来看:
[run_id=r001] stage=load task=hello_task input=tasks/hello_task/input.txt status=ok
[run_id=r001] stage=execute executor=default seed=42 out=runs/r001/output.txt status=ok
[run_id=r001] stage=eval script=eval_basic.py target=runs/r001/output.txt score=1.0 status=ok
[run_id=r001] stage=done elapsed_s=<占位> status=ok
逐段确认的方法很朴素:先看 stage=load 是否报出正确的输入路径,再去 runs/r001/ 里确认执行产物真的生成了、内容不是空文件;然后打开评分脚本,手工把产物喂一遍,看它算出的 score 和日志里的是否一致。三段都能对上,说明任务、执行、评分三层之间的数据传递是通的。如果中间某段缺失,通常意味着上一层没有把结果写到约定位置,或者评分脚本读的是一个不存在的路径,此时不要急着改评分逻辑。用 grep stage= 过滤日志,是比通读全量输出更快的定位方式。
再逐步增加任务数量与并发
闭环稳定后,每次只加一个变量。第一轮把任务从 1 个加到 3 个,并发仍然保持 1,跑完确认三个任务都出现在日志里、评分结果分别落到各自的 run 目录;确认无误后再把并发从 1 调到 2,其余配置不动。任务数量和并发同时改,出问题时无法判断是任务定义不兼容还是并发下的资源竞争。
每次加完要对比的指标建议限定在可核验的范围内:任务成功率(成功条数 / 提交条数)、失败任务的 id 与失败阶段、单任务耗时、日志中是否存在缺失的 stage、以及评分结果分布是否和单任务时一致。对比的基线就是上一轮产物的 runs/ 目录,直接拿目录和日志比,比凭印象判断可靠。如果并发调高后出现失败阶段集中在 stage=execute,优先怀疑工作目录或临时文件被多个任务共用,而不是先怀疑任务本身写错。
用同一随机种子复跑确认结果稳定
可重复性是这套环境能不能用于后续对比的前提。种子的设置位置要统一:一般放在 config/env.yaml 顶层,由 run_once.sh 读取后显式传给执行器;如果评测脚本内部有样本打乱或采样逻辑,也要把同一个种子传进去,否则评测结果本身会漂。建议在执行日志里额外打印实际生效的 seed,避免配置写了但没被读取。
验证步骤可以这样走:用同一个种子连续跑两次,分别输出到 runs/seed_a 和 runs/seed_b;先比对两次日志里的 stage 序列是否一致,再比对产物文件内容,用 diff -r runs/seed_a runs/seed_b 或对输出文件做哈希比对;最后确认评分结果相同。比对时需要先把时间戳、run_id、耗时这类天然会变的字段排除掉,只看任务 id、输出内容和 score。如果两次输出存在差异,先确认差异是否来自执行器侧的非确定性(例如温度参数、并发写入顺序),再决定是收紧参数还是接受这一差异——这一步的结论需要结合具体执行器确认,不宜直接当成环境缺陷。