dsh-eval-guard 0.7.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ISSUES.md +235 -0
- package/README.md +46 -18
- package/assets/neural-cifar/workspace.zip +0 -0
- package/assets/neural-cpp/workspace.zip +0 -0
- package/assets/neural-fp32/workspace.zip +0 -0
- package/assets/neural-pendulum/SPEC.md +39 -32
- package/assets/neural-pendulum/TASK.md +137 -32
- package/assets/neural-pendulum/workspace.zip +0 -0
- package/eval/README.md +19 -9
- package/eval/__pycache__/bomberman.cpython-310.pyc +0 -0
- package/eval/__pycache__/commit0.cpython-310.pyc +0 -0
- package/eval/__pycache__/common.cpython-310.pyc +0 -0
- package/eval/__pycache__/ctf.cpython-310.pyc +0 -0
- package/eval/__pycache__/featurebench.cpython-310.pyc +0 -0
- package/eval/__pycache__/land.cpython-310.pyc +0 -0
- package/eval/__pycache__/neural-cifar.cpython-310.pyc +0 -0
- package/eval/__pycache__/neural-cpp.cpython-310.pyc +0 -0
- package/eval/__pycache__/neural-fp32.cpython-310.pyc +0 -0
- package/eval/__pycache__/neural-pendulum.cpython-310.pyc +0 -0
- package/eval/__pycache__/swe-bench-pro.cpython-310.pyc +0 -0
- package/eval/__pycache__/tank.cpython-310.pyc +0 -0
- package/eval/__pycache__/warehouse.cpython-310.pyc +0 -0
- package/eval/bomberman.py +5 -3
- package/eval/commit0.py +10 -3
- package/eval/common.py +169 -5
- package/eval/ctf.py +4 -2
- package/eval/featurebench.py +12 -8
- package/eval/land.py +4 -2
- package/eval/legacy/__pycache__/bomberman.cpython-310.pyc +0 -0
- package/eval/legacy/__pycache__/commit0.cpython-310.pyc +0 -0
- package/eval/legacy/__pycache__/ctf.cpython-310.pyc +0 -0
- package/eval/legacy/__pycache__/featurebench.cpython-310.pyc +0 -0
- package/eval/legacy/__pycache__/land.cpython-310.pyc +0 -0
- package/eval/legacy/__pycache__/swe_bench_pro.cpython-310.pyc +0 -0
- package/eval/legacy/__pycache__/tank.cpython-310.pyc +0 -0
- package/eval/legacy/__pycache__/warehouse.cpython-310.pyc +0 -0
- package/eval/legacy/bomberman.py +5 -2
- package/eval/legacy/ctf.py +5 -2
- package/eval/legacy/featurebench.py +55 -15
- package/eval/legacy/land.py +6 -3
- package/eval/legacy/swe_bench_pro.py +57 -16
- package/eval/legacy/tank.py +2 -2
- package/eval/legacy/warehouse.py +4 -4
- package/eval/neural-cpp.py +9 -6
- package/eval/neural-fp32.py +17 -5
- package/eval/swe-bench-pro.py +11 -7
- package/eval/tank.py +11 -2
- package/eval/warehouse.py +17 -1
- package/lib/client/index.js +56 -29
- package/lib/client/index.js.map +1 -1
- package/lib/index.js +878 -297
- package/native/dsh-eval-supervisor +0 -0
- package/native/supervisor.c +190 -26
- package/neural-cifar-eval/README.md +5 -0
- package/neural-cifar-eval/__pycache__/cifar_cache.cpython-310.pyc +0 -0
- package/neural-cifar-eval/__pycache__/common.cpython-310.pyc +0 -0
- package/neural-cifar-eval/__pycache__/fetch_cifar.cpython-310.pyc +0 -0
- package/neural-cifar-eval/__pycache__/ops_eval.cpython-310.pyc +0 -0
- package/neural-cifar-eval/__pycache__/run_eval.cpython-310.pyc +0 -0
- package/neural-cifar-eval/common.py +13 -0
- package/neural-cifar-eval/ops_eval.py +17 -5
- package/neural-cifar-eval/run_eval.py +42 -32
- package/neural-pendulum-eval/README.md +62 -0
- package/neural-pendulum-eval/__pycache__/ops_eval.cpython-310.pyc +0 -0
- package/neural-pendulum-eval/__pycache__/run_eval.cpython-310.pyc +0 -0
- package/neural-pendulum-eval/ops_eval.py +603 -0
- package/neural-pendulum-eval/run_eval.py +218 -137
- package/package.json +7 -11
- package/assets/neural-pendulum/hidden/test_infra.cpp +0 -631
- package/assets/neural-pendulum/include/nn/nn.hpp +0 -88
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
- 训练时从随机小扰动、评测时从固定初始状态起摆
|
|
10
10
|
- 保持三根杆全部向上至少 t_tgt = 10.0 秒
|
|
11
|
-
-
|
|
11
|
+
- 同时保证神经网络基础设施通过 build/nn_task 固定 CLI 的数值评测
|
|
12
12
|
|
|
13
|
-
开发阶段可以使用 Python/PyTorch 做离线数值对照和实验;最终提交的基础设施和控制器源码必须是纯 C++,不得出现 torch
|
|
13
|
+
开发阶段可以使用 Python/PyTorch 做离线数值对照和实验;最终提交的基础设施和控制器源码必须是纯 C++,不得出现 torch,候选运行时不提供 Python/torch。
|
|
14
14
|
|
|
15
15
|
## 环境
|
|
16
16
|
|
|
@@ -100,7 +100,7 @@ t_can = 三根杆同时处于全向上容差内的最长连续时间
|
|
|
100
100
|
|
|
101
101
|
~~~
|
|
102
102
|
t_can >= t_tgt
|
|
103
|
-
|
|
103
|
+
且神经网络基础设施数值评测全部通过
|
|
104
104
|
~~~
|
|
105
105
|
|
|
106
106
|
任务完成,计时停止。
|
|
@@ -152,34 +152,140 @@ cmake --build build -j4
|
|
|
152
152
|
必须产出:
|
|
153
153
|
|
|
154
154
|
~~~
|
|
155
|
-
build/
|
|
155
|
+
build/nn_task
|
|
156
156
|
build/control
|
|
157
157
|
~~~
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
工作区模板 CMakeLists.txt 默认从 `src/core/*.cpp` 构建共享静态库,从 `src/nn_task.cpp` 和 `src/control.cpp` 构建两个可执行文件。可以按需调整目录结构和 CMakeLists.txt,但必须从零构建出上面两个二进制。
|
|
160
160
|
|
|
161
|
-
|
|
161
|
+
### 1. 神经网络基础设施入口 build/nn_task
|
|
162
162
|
|
|
163
|
-
|
|
163
|
+
基础设施通过固定 CLI 暴露,评测会逐操作调用。所有输入/输出文件都是连续 row-major float32,所有数值输出也必须写为 float32。
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
#### 逐元素
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
-
|
|
177
|
-
|
|
178
|
-
|
|
167
|
+
~~~bash
|
|
168
|
+
build/nn_task elementwise --op add|sub|mul|div --shape 64,256 --a A --b B --out O
|
|
169
|
+
~~~
|
|
170
|
+
|
|
171
|
+
支持广播:从尾部对齐,维度为 1 可广播。
|
|
172
|
+
|
|
173
|
+
#### 矩阵乘法
|
|
174
|
+
|
|
175
|
+
~~~bash
|
|
176
|
+
build/nn_task matmul --a-shape M,K --b-shape K,N --a A --b B --out O
|
|
177
|
+
~~~
|
|
178
|
+
|
|
179
|
+
只要求 2D 输入。
|
|
180
|
+
|
|
181
|
+
#### Linear
|
|
182
|
+
|
|
183
|
+
~~~bash
|
|
184
|
+
build/nn_task linear --x-shape B,IN --w-shape OUT,IN --b-shape OUT --x X --w W --bias B --out O
|
|
185
|
+
~~~
|
|
186
|
+
|
|
187
|
+
计算 `O = X @ W^T + bias`。
|
|
188
|
+
|
|
189
|
+
#### 激活
|
|
190
|
+
|
|
191
|
+
~~~bash
|
|
192
|
+
build/nn_task relu --shape N --x X --out O
|
|
193
|
+
build/nn_task softmax --shape B,C --x X --out O
|
|
194
|
+
~~~
|
|
195
|
+
|
|
196
|
+
Softmax 沿最后一维。
|
|
197
|
+
|
|
198
|
+
#### LayerNorm
|
|
199
|
+
|
|
200
|
+
~~~bash
|
|
201
|
+
build/nn_task layernorm --shape B,C --x X --gamma G --beta B --eps 1e-5 --out O
|
|
202
|
+
~~~
|
|
203
|
+
|
|
204
|
+
对最后一维归一化;gamma/beta 形状为 `C`。
|
|
205
|
+
|
|
206
|
+
#### MSE
|
|
207
|
+
|
|
208
|
+
~~~bash
|
|
209
|
+
build/nn_task mse --pred-shape S --target-shape S --pred P --target T --out O
|
|
210
|
+
~~~
|
|
211
|
+
|
|
212
|
+
输出 1 个 float32,为所有元素平均平方误差。
|
|
213
|
+
|
|
214
|
+
#### Conv2d
|
|
215
|
+
|
|
216
|
+
~~~bash
|
|
217
|
+
build/nn_task conv2d --x-shape N,C,H,W --w-shape O,Cg,KH,KW --b-shape O --x X --w W --bias B --stride SH,SW --padding PH,PW --dilation DH,DW --groups G --out OUT
|
|
218
|
+
~~~
|
|
219
|
+
|
|
220
|
+
标准 NCHW 卷积,`Cg = C / groups`。
|
|
221
|
+
|
|
222
|
+
#### 反向传播
|
|
223
|
+
|
|
224
|
+
~~~bash
|
|
225
|
+
build/nn_task matmul-backward --a-shape M,K --b-shape K,N --a A --b B --grad G --da DA --db DB
|
|
226
|
+
build/nn_task linear-backward --x-shape B,IN --w-shape OUT,IN --b-shape OUT --x X --w W --grad G --dx DX --dw DW --db DB
|
|
227
|
+
build/nn_task relu-backward --shape N --x X --grad G --dx DX
|
|
228
|
+
build/nn_task layernorm-backward --shape B,C --x X --gamma G --grad G --eps 1e-5 --dx DX --dgamma DG
|
|
229
|
+
build/nn_task mse-backward --pred-shape S --target-shape S --pred P --target T --grad G --dp DP --dtarget DT
|
|
230
|
+
build/nn_task conv2d-backward --x-shape N,C,H,W --w-shape O,Cg,KH,KW --b-shape O --x X --w W --grad G --stride SH,SW --padding PH,PW --dilation DH,DW --groups G --dx DX --dw DW --db DB
|
|
231
|
+
~~~
|
|
232
|
+
|
|
233
|
+
反向传播定义为:上游梯度 `G` 对输入、权重、bias 的梯度;`G` 与 forward 输出同形状。
|
|
234
|
+
|
|
235
|
+
#### 优化器
|
|
236
|
+
|
|
237
|
+
~~~bash
|
|
238
|
+
build/nn_task adamw --param P --grad G --m M --v V --lr LR --beta1 B1 --beta2 B2 --eps EPS --weight-decay WD --step T --out-param P2 --out-m M2 --out-v V2
|
|
239
|
+
build/nn_task sgd-momentum --param P --grad G --momentum-buffer MB --lr LR --momentum MU --out-param P2 --out-buffer MB2
|
|
240
|
+
~~~
|
|
241
|
+
|
|
242
|
+
AdamW 使用 decoupled weight decay:
|
|
243
|
+
|
|
244
|
+
~~~text
|
|
245
|
+
m_t = beta1*m + (1-beta1)*g
|
|
246
|
+
v_t = beta2*v + (1-beta2)*g*g
|
|
247
|
+
m_hat = m_t / (1-beta1^t)
|
|
248
|
+
v_hat = v_t / (1-beta2^t)
|
|
249
|
+
param = param*(1-lr*wd) - lr*m_hat/(sqrt(v_hat)+eps)
|
|
250
|
+
~~~
|
|
251
|
+
|
|
252
|
+
SGD momentum:
|
|
253
|
+
|
|
254
|
+
~~~text
|
|
255
|
+
buffer = momentum*buffer + grad
|
|
256
|
+
param = param - lr*buffer
|
|
257
|
+
~~~
|
|
258
|
+
|
|
259
|
+
#### 数值评测方式
|
|
260
|
+
|
|
261
|
+
评测侧在独立进程中生成数据,并用 PyTorch 在相同输入/参数上计算参考值,逐操作比较。工作区不会收到期望输出张量。
|
|
262
|
+
|
|
263
|
+
- 每个操作使用 3 组独立随机数据(固定种子),形状固定
|
|
264
|
+
- 误差指标为加权平均相对误差:
|
|
265
|
+
|
|
266
|
+
~~~
|
|
267
|
+
mre = sum(|candidate - reference|) / sum(|reference|)
|
|
268
|
+
~~~
|
|
269
|
+
|
|
270
|
+
- 每个操作有独立阈值,单位与 fp32 精度匹配:
|
|
271
|
+
|
|
272
|
+
| 操作 | 加权平均相对误差阈值 |
|
|
273
|
+
| --- | --- |
|
|
274
|
+
| elementwise.add / sub / mul / div | 1e-6 |
|
|
275
|
+
| relu | 1e-6 |
|
|
276
|
+
| mse | 1e-6 |
|
|
277
|
+
| sgd-momentum | 1e-6 |
|
|
278
|
+
| matmul / linear | 1e-5 |
|
|
279
|
+
| softmax / layernorm | 1e-5 |
|
|
280
|
+
| adamw | 1e-5 |
|
|
281
|
+
| conv2d | 1e-4 |
|
|
282
|
+
| matmul-backward / linear-backward / relu-backward / layernorm-backward / mse-backward / conv2d-backward | 1e-4 |
|
|
179
283
|
|
|
180
|
-
|
|
284
|
+
- 任一操作不可执行、崩溃、超时(单次调用 60 秒)、输出文件缺失或元素数不符,或 mre 超过阈值,都会判为基础设施失败(stage = infra)
|
|
285
|
+
- 速度(GFLOP/s)会同时报告,但速度不参与通过判定
|
|
286
|
+
- 不做完整训练,不要求模型保存/加载格式;控制器需要的权重可以放在源码目录或控制器自行打包
|
|
181
287
|
|
|
182
|
-
|
|
288
|
+
评测数据在测试进程内生成,不随工作区提供。不要伪造输出:评测直接执行你的二进制并检查它写出的文件。
|
|
183
289
|
|
|
184
290
|
### 2. 控制器入口
|
|
185
291
|
|
|
@@ -222,7 +328,7 @@ eval_test
|
|
|
222
328
|
|
|
223
329
|
### eval_test
|
|
224
330
|
|
|
225
|
-
|
|
331
|
+
由宿主对最新提交快照进行构建、算子数值评测和隐藏仿真评估,返回:
|
|
226
332
|
|
|
227
333
|
~~~text
|
|
228
334
|
stage
|
|
@@ -248,8 +354,8 @@ completed
|
|
|
248
354
|
阶段含义:
|
|
249
355
|
|
|
250
356
|
- no_submission:还没有提交快照
|
|
251
|
-
- compile:CMake
|
|
252
|
-
- infra
|
|
357
|
+
- compile:CMake 构建失败,或没有产出 build/nn_task / build/control
|
|
358
|
+
- infra:build/nn_task 算子数值评测失败(不可执行、崩溃、超时、输出缺失或误差超阈值)
|
|
253
359
|
- controller:控制器启动、协议、崩溃、超时或输出非法
|
|
254
360
|
- simulation:隐藏仿真推进异常、NaN 或超时
|
|
255
361
|
- scoring:测试有效,但 `t_can < 10.0`
|
|
@@ -278,10 +384,9 @@ t_can >= 10.0
|
|
|
278
384
|
|
|
279
385
|
- 只允许 C++17 + CMake + C++ 标准库
|
|
280
386
|
- 仅 CPU,fp32
|
|
281
|
-
- 提交源码中不得出现 torch
|
|
282
|
-
- 评测环境无 Python/torch
|
|
387
|
+
- 提交源码中不得出现 torch;候选运行时不提供 Python/torch(评测侧会用 PyTorch 计算数值参考值)
|
|
283
388
|
- 无网络
|
|
284
|
-
-
|
|
389
|
+
- 不得修改或替换 tools/sim;它只用于训练和自测
|
|
285
390
|
- 不得提交编译产物
|
|
286
391
|
- 赛后人工审核:控制器必须是神经网络,且必须实际调用你实现的神经网络基础设施;手写控制律、查表、固定轨迹或外部依赖会被判无效
|
|
287
392
|
|
|
@@ -361,9 +466,9 @@ T_done 是第一次返回 completed 的测试请求时间。
|
|
|
361
466
|
|
|
362
467
|
## 建议工作顺序
|
|
363
468
|
|
|
364
|
-
1. 阅读
|
|
365
|
-
2. 先实现最小可用的 tensor
|
|
366
|
-
3.
|
|
367
|
-
4. 用 tools/sim
|
|
469
|
+
1. 阅读 build/nn_task 的 CLI 协议与 tools/sim 协议
|
|
470
|
+
2. 先实现最小可用的 tensor/算子/反向/优化器/线程池,并让 build/nn_task 跑通
|
|
471
|
+
3. 用 PyTorch 或独立 double 标量参考、有限差分梯度检查逐算子核对误差,边界形状/广播/groups/stride/dilation 都要覆盖
|
|
472
|
+
4. 用 tools/sim 采集数据并训练控制器,导出权重供 build/control 使用
|
|
368
473
|
5. 反复提交和测试,优先把 t_can 提升到 10 秒
|
|
369
474
|
6. 完成后立即总结
|
|
Binary file
|
package/eval/README.md
CHANGED
|
@@ -4,15 +4,25 @@
|
|
|
4
4
|
|
|
5
5
|
| 任务 | 脚本 | 评分 |
|
|
6
6
|
|---|---|---|
|
|
7
|
-
| tank | eval/tank.py | 官方引擎 + BT
|
|
8
|
-
| warehouse | eval/warehouse.py | 正确性 +
|
|
9
|
-
| land | eval/land.py | 官方 round_robin/rate |
|
|
10
|
-
| bomberman | eval/bomberman.py | 官方 tournament/run |
|
|
11
|
-
| ctf | eval/ctf.py | 官方 arena plan/run/rate |
|
|
12
|
-
| commit0 | eval/commit0.py |
|
|
13
|
-
| swe-bench-pro | eval/swe-bench-pro.py | 隐藏 F2P/P2P
|
|
14
|
-
| featurebench | eval/featurebench.py | 隐藏
|
|
7
|
+
| tank | eval/tank.py | 官方引擎 + BT,对基线(quick 20 局) |
|
|
8
|
+
| warehouse | eval/warehouse.py | 正确性 + 相对基线加速比(quick 仅 pub_small) |
|
|
9
|
+
| land | eval/land.py | 官方 round_robin/rate(quick 仅单挑 2 图) |
|
|
10
|
+
| bomberman | eval/bomberman.py | 官方 tournament/run(quick 仅 2 人 1 种子) |
|
|
11
|
+
| ctf | eval/ctf.py | 官方 arena plan/run/rate(quick 仅 2/3 方) |
|
|
12
|
+
| commit0 | eval/commit0.py | 单元测试通过率(测试来自安装快照) |
|
|
13
|
+
| swe-bench-pro | eval/swe-bench-pro.py | 隐藏 F2P/P2P 节点全部通过才 resolved |
|
|
14
|
+
| featurebench | eval/featurebench.py | 隐藏 feature 测试 + 全部回归文件通过才 resolved |
|
|
15
|
+
| neural-cpp | eval/neural-cpp.py | 隐藏 C++ 测试;accuracy / matrixAccuracy / speedGeomean / score |
|
|
16
|
+
| neural-fp32 | eval/neural-fp32.py | 逐操作 MRE/阈值/速度;无统一 score |
|
|
17
|
+
| neural-cifar | eval/neural-cifar.py | FP32 算子指标 + 隐藏验证集准确率/FPS(独立目录) |
|
|
18
|
+
| neural-pendulum | eval/neural-pendulum.py | FP32 算子指标 + 固定初态控制 t_can(独立目录) |
|
|
15
19
|
|
|
16
20
|
eval/legacy/ 保存从独立评测目录迁移过来的评测器;eval/arena/ 保存官方引擎、裁判和历史对手。common.py 负责临时工作区、结果解析和结果文件写入。
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
## 完整性约束(宿主注入)
|
|
23
|
+
|
|
24
|
+
- `EVAL_BASELINE_DIR`:安装成功时快照的测试/配置目录。commit0/SWE/FeatureBench 的 wrapper 会在评测前用它覆盖候选副本,并删除候选新增的 `conftest.py`;同时校验 `.venv` 指纹(`venv.json`)。
|
|
25
|
+
- `EVAL_RESULT_FILE` / `EVAL_JOB_DIR`:只由顶层 evaluator 使用;所有 evaluator 再启动子进程时会剥掉这些变量,候选二进制拿不到结果文件路径。
|
|
26
|
+
- 隐藏资产(neural-cpp 的隐藏测试、CIFAR 的验证标签)在候选构建/推理开始前读入内存;CIFAR 的图像副本会放到独立临时目录,标签不落盘到候选可见路径。
|
|
27
|
+
|
|
28
|
+
后台任务由 host 的 JobManager 执行,日志和结果保存在 $DSH_HOME/eval-guard/jobs/<job-id>/。Job 子进程的环境变量已收敛为白名单(PATH/HOME/LANG/LC_*/TMPDIR/SSL_* 等)。
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/eval/bomberman.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Single-workspace
|
|
2
|
+
"""Single-workspace bomberman evaluator wrapper."""
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
import shutil, tempfile
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from common import EVAL_ROOT, candidate_row, emit, fail, parse_workspace, run_legacy
|
|
7
7
|
|
|
8
8
|
workspace = parse_workspace()
|
|
9
|
-
with tempfile.TemporaryDirectory(prefix='
|
|
9
|
+
with tempfile.TemporaryDirectory(prefix='bomberman-eval-') as td:
|
|
10
10
|
temp = Path(td)
|
|
11
11
|
bot = workspace / 'submission' / 'bot.cpp'
|
|
12
12
|
if not bot.is_file():
|
|
@@ -17,4 +17,6 @@ with tempfile.TemporaryDirectory(prefix='bomber-eval-') as td:
|
|
|
17
17
|
if not result.get('ok'):
|
|
18
18
|
raise SystemExit(fail({'task': 'bomberman', 'ok': False, 'error': result.get('error', 'evaluation failed')}))
|
|
19
19
|
row = candidate_row(result['summary'])
|
|
20
|
-
|
|
20
|
+
if not row:
|
|
21
|
+
raise SystemExit(fail({'task': 'bomberman', 'ok': False, 'error': 'candidate row missing from evaluator summary'}))
|
|
22
|
+
emit({'task': 'bomberman', 'ok': True, 'score': row.get('score', row.get('rating', 0)), 'rating': row.get('rating', row.get('theta', 0)), 'theta': row.get('theta', 0)})
|
package/eval/commit0.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Single-workspace Commit0 evaluator wrapper."""
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
import os,
|
|
4
|
+
import os, tempfile
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from common import EVAL_ROOT, candidate_row, clean_copy, emit, fail, parse_workspace, run_legacy
|
|
6
|
+
from common import EVAL_ROOT, candidate_row, clean_copy, emit, fail, parse_workspace, restore_baseline, run_legacy, verify_venv
|
|
7
7
|
|
|
8
8
|
workspace = parse_workspace()
|
|
9
|
+
problem = verify_venv(workspace)
|
|
10
|
+
if problem is not None:
|
|
11
|
+
raise SystemExit(fail({'task': 'commit0', 'ok': False, 'error': problem}))
|
|
9
12
|
with tempfile.TemporaryDirectory(prefix='commit0-eval-') as td:
|
|
10
13
|
temp = Path(td)
|
|
11
14
|
candidate = temp / 'candidate' / 'workspace'
|
|
@@ -13,7 +16,11 @@ with tempfile.TemporaryDirectory(prefix='commit0-eval-') as td:
|
|
|
13
16
|
clean_copy(workspace, candidate, ignore_venv=True)
|
|
14
17
|
venv = workspace / '.venv'
|
|
15
18
|
if venv.is_dir():
|
|
16
|
-
|
|
19
|
+
try:
|
|
20
|
+
os.symlink(venv, candidate / '.venv')
|
|
21
|
+
except FileExistsError:
|
|
22
|
+
pass
|
|
23
|
+
restore_baseline(candidate)
|
|
17
24
|
result = run_legacy(EVAL_ROOT / 'legacy' / 'commit0.py', ['--submissions', str(temp)], temp / 'out')
|
|
18
25
|
if not result.get('ok'):
|
|
19
26
|
raise SystemExit(fail({'task': 'commit0', 'ok': False, 'error': result.get('error', 'evaluation failed')}))
|
package/eval/common.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
|
+
import hashlib
|
|
5
6
|
import json
|
|
6
7
|
import os
|
|
7
8
|
import shutil
|
|
@@ -12,6 +13,22 @@ from pathlib import Path
|
|
|
12
13
|
|
|
13
14
|
EVAL_ROOT = Path(__file__).resolve().parent
|
|
14
15
|
|
|
16
|
+
# Variables the host injects for the evaluator itself. They must not leak into
|
|
17
|
+
# the legacy evaluator or into any candidate process it starts (a candidate
|
|
18
|
+
# that learns EVAL_RESULT_FILE could overwrite the result).
|
|
19
|
+
HOST_EVAL_KEYS = frozenset({
|
|
20
|
+
'EVAL_RESULT_FILE', 'EVAL_JOB_DIR', 'EVAL_INSTALL_TOKEN',
|
|
21
|
+
'EVAL_BASELINE_DIR', 'EVAL_VENV_MANIFEST',
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def child_env() -> dict[str, str]:
|
|
26
|
+
"""Environment for trusted legacy evaluators and their candidate children."""
|
|
27
|
+
return {
|
|
28
|
+
key: value for key, value in os.environ.items()
|
|
29
|
+
if key not in HOST_EVAL_KEYS and key not in {'PYTHONPATH', 'PYTHONHOME'}
|
|
30
|
+
}
|
|
31
|
+
|
|
15
32
|
|
|
16
33
|
def parse_workspace() -> Path:
|
|
17
34
|
parser = argparse.ArgumentParser(description='single-workspace evaluator')
|
|
@@ -28,18 +45,23 @@ def emit(payload: dict) -> None:
|
|
|
28
45
|
Path(target).write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding='utf-8')
|
|
29
46
|
|
|
30
47
|
|
|
31
|
-
def clean_copy(source: Path, target: Path, ignore_venv: bool = False
|
|
48
|
+
def clean_copy(source: Path, target: Path, ignore_venv: bool = False,
|
|
49
|
+
ignore_names: tuple[str, ...] = ()) -> None:
|
|
50
|
+
ignored = set(ignore_names)
|
|
51
|
+
|
|
32
52
|
def ignore(directory: str, names: list[str]) -> set[str]:
|
|
33
|
-
if
|
|
34
|
-
|
|
53
|
+
if Path(directory) == source:
|
|
54
|
+
skip = set(ignored)
|
|
55
|
+
if ignore_venv:
|
|
56
|
+
skip.add('.venv')
|
|
57
|
+
return {name for name in names if name in skip}
|
|
35
58
|
return set()
|
|
36
59
|
shutil.copytree(source, target, symlinks=True, ignore=ignore)
|
|
37
60
|
|
|
38
61
|
|
|
39
62
|
def run_legacy(script: Path, args: list[str], out: Path) -> dict:
|
|
40
63
|
cmd = [sys.executable, '-X', 'utf8', str(script), *args, '--out', str(out)]
|
|
41
|
-
|
|
42
|
-
completed = subprocess.run(cmd, cwd=str(EVAL_ROOT), capture_output=True, text=True, encoding='utf-8', errors='replace', env=env, timeout=1800)
|
|
64
|
+
completed = subprocess.run(cmd, cwd=str(EVAL_ROOT), capture_output=True, text=True, encoding='utf-8', errors='replace', env=child_env(), timeout=1800)
|
|
43
65
|
if completed.returncode != 0:
|
|
44
66
|
return {'ok': False, 'error': (completed.stderr or completed.stdout or 'evaluator failed')[-4000:]}
|
|
45
67
|
summary = out / 'summary.json'
|
|
@@ -59,3 +81,145 @@ def candidate_row(summary: dict, name: str = 'candidate') -> dict:
|
|
|
59
81
|
def fail(payload: dict) -> int:
|
|
60
82
|
emit(payload)
|
|
61
83
|
return 1
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def parse_pytest_results(output: str) -> dict[str, str]:
|
|
87
|
+
"""Map pytest node ids to PASSED/FAILED/ERROR/SKIPPED from -rA output."""
|
|
88
|
+
import re
|
|
89
|
+
pattern = re.compile(r'^(PASSED|FAILED|ERROR|SKIPPED|XFAIL|XPASS) (\S+)', re.MULTILINE)
|
|
90
|
+
results: dict[str, str] = {}
|
|
91
|
+
for match in pattern.finditer(output):
|
|
92
|
+
results[match.group(2)] = match.group(1)
|
|
93
|
+
return results
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def matches_node(node: str, selector: str) -> bool:
|
|
97
|
+
"""Whether a pytest node id belongs to a file/node selector."""
|
|
98
|
+
if node == selector:
|
|
99
|
+
return True
|
|
100
|
+
return node.startswith(selector + '::') or node.startswith(selector + '/')
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
# Pristine test material and venv integrity
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
def sha256_file(path: Path) -> str:
|
|
109
|
+
digest = hashlib.sha256()
|
|
110
|
+
digest.update(path.read_bytes())
|
|
111
|
+
return digest.hexdigest()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def sha256_tree(root: Path) -> str:
|
|
115
|
+
digest = hashlib.sha256()
|
|
116
|
+
|
|
117
|
+
def walk(current: Path) -> None:
|
|
118
|
+
for entry in sorted(current.iterdir(), key=lambda item: item.name):
|
|
119
|
+
digest.update(str(entry.relative_to(root)).encode('utf-8'))
|
|
120
|
+
if entry.is_dir():
|
|
121
|
+
walk(entry)
|
|
122
|
+
elif entry.is_file():
|
|
123
|
+
digest.update(entry.read_bytes())
|
|
124
|
+
elif entry.is_symlink():
|
|
125
|
+
digest.update(b'link:' + os.readlink(entry).encode('utf-8'))
|
|
126
|
+
|
|
127
|
+
walk(root)
|
|
128
|
+
return digest.hexdigest()
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def baseline_dir() -> Path | None:
|
|
132
|
+
raw = os.environ.get('EVAL_BASELINE_DIR', '')
|
|
133
|
+
if raw == '':
|
|
134
|
+
return None
|
|
135
|
+
path = Path(raw)
|
|
136
|
+
return path if path.is_dir() else None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def restore_baseline(candidate: Path) -> None:
|
|
140
|
+
"""Overwrite candidate test material with the pristine install-time copy.
|
|
141
|
+
|
|
142
|
+
Also removes conftest.py files the agent added (the pristine snapshot keeps
|
|
143
|
+
the project's own ones), so a workspace hook cannot rewrite pytest results.
|
|
144
|
+
"""
|
|
145
|
+
baseline = baseline_dir()
|
|
146
|
+
if baseline is None:
|
|
147
|
+
return
|
|
148
|
+
candidate = candidate.resolve()
|
|
149
|
+
baseline = baseline.resolve()
|
|
150
|
+
for hook in candidate.rglob('conftest.py'):
|
|
151
|
+
if hook.is_symlink():
|
|
152
|
+
hook.unlink()
|
|
153
|
+
continue
|
|
154
|
+
try:
|
|
155
|
+
relative = hook.relative_to(candidate)
|
|
156
|
+
except ValueError:
|
|
157
|
+
continue
|
|
158
|
+
if not (baseline / relative).is_file():
|
|
159
|
+
hook.unlink()
|
|
160
|
+
for entry in baseline.iterdir():
|
|
161
|
+
if entry.name in {'manifest.json', 'venv.json'}:
|
|
162
|
+
continue
|
|
163
|
+
destination = candidate / entry.name
|
|
164
|
+
if destination.exists() or destination.is_symlink():
|
|
165
|
+
if destination.is_dir() and not destination.is_symlink():
|
|
166
|
+
shutil.rmtree(destination, ignore_errors=True)
|
|
167
|
+
else:
|
|
168
|
+
destination.unlink()
|
|
169
|
+
if entry.is_dir():
|
|
170
|
+
shutil.copytree(entry, destination, symlinks=True)
|
|
171
|
+
else:
|
|
172
|
+
shutil.copy2(entry, destination)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def verify_venv(workspace: Path) -> str | None:
|
|
176
|
+
"""Verify the venv control plane against the install-time fingerprint.
|
|
177
|
+
|
|
178
|
+
Returns a human-readable reason when the venv was tampered with, else None.
|
|
179
|
+
"""
|
|
180
|
+
baseline = baseline_dir()
|
|
181
|
+
if baseline is None:
|
|
182
|
+
return None
|
|
183
|
+
manifest_path = baseline / 'venv.json'
|
|
184
|
+
if not manifest_path.is_file():
|
|
185
|
+
return None
|
|
186
|
+
try:
|
|
187
|
+
manifest = json.loads(manifest_path.read_text(encoding='utf-8'))
|
|
188
|
+
except Exception:
|
|
189
|
+
return 'venv manifest is corrupt'
|
|
190
|
+
venv = workspace / '.venv'
|
|
191
|
+
if not venv.is_dir():
|
|
192
|
+
return 'the evaluator requires the installed .venv; it is missing'
|
|
193
|
+
expected_python = manifest.get('python')
|
|
194
|
+
if isinstance(expected_python, str) and expected_python != '':
|
|
195
|
+
python = venv / 'bin' / 'python'
|
|
196
|
+
try:
|
|
197
|
+
actual = str(python.resolve())
|
|
198
|
+
except OSError:
|
|
199
|
+
actual = ''
|
|
200
|
+
if actual != expected_python:
|
|
201
|
+
return 'the .venv python interpreter was replaced'
|
|
202
|
+
for relative, expected in (manifest.get('files') or {}).items():
|
|
203
|
+
target = venv / relative
|
|
204
|
+
if not target.is_file():
|
|
205
|
+
return f'the .venv file {relative} is missing'
|
|
206
|
+
try:
|
|
207
|
+
actual = sha256_file(target)
|
|
208
|
+
except OSError as error:
|
|
209
|
+
return f'cannot read the .venv file {relative}: {error}'
|
|
210
|
+
if actual != expected:
|
|
211
|
+
return f'the .venv file {relative} was modified'
|
|
212
|
+
expected_pytest = manifest.get('pytest')
|
|
213
|
+
if isinstance(expected_pytest, str) and expected_pytest != '':
|
|
214
|
+
found = False
|
|
215
|
+
for site in venv.glob('lib*/python*/site-packages/pytest'):
|
|
216
|
+
found = True
|
|
217
|
+
try:
|
|
218
|
+
actual = sha256_tree(site)
|
|
219
|
+
except OSError as error:
|
|
220
|
+
return f'cannot read the venv pytest package: {error}'
|
|
221
|
+
if actual != expected_pytest:
|
|
222
|
+
return 'the venv pytest package was modified'
|
|
223
|
+
if not found:
|
|
224
|
+
return 'the venv pytest package disappeared'
|
|
225
|
+
return None
|
package/eval/ctf.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Single-workspace
|
|
2
|
+
"""Single-workspace ctf evaluator wrapper."""
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
import shutil, tempfile
|
|
5
5
|
from pathlib import Path
|
|
@@ -17,4 +17,6 @@ with tempfile.TemporaryDirectory(prefix='ctf-eval-') as td:
|
|
|
17
17
|
if not result.get('ok'):
|
|
18
18
|
raise SystemExit(fail({'task': 'ctf', 'ok': False, 'error': result.get('error', 'evaluation failed')}))
|
|
19
19
|
row = candidate_row(result['summary'])
|
|
20
|
-
|
|
20
|
+
if not row:
|
|
21
|
+
raise SystemExit(fail({'task': 'ctf', 'ok': False, 'error': 'candidate row missing from evaluator summary'}))
|
|
22
|
+
emit({'task': 'ctf', 'ok': True, 'score': row.get('score', row.get('rating', 0)), 'rating': row.get('rating', row.get('theta', 0)), 'theta': row.get('theta', 0)})
|
package/eval/featurebench.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Single-workspace
|
|
2
|
+
"""Single-workspace FeatureBench evaluator wrapper."""
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
import os, tempfile
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from common import EVAL_ROOT, candidate_row, clean_copy, emit, fail, parse_workspace, run_legacy
|
|
6
|
+
from common import EVAL_ROOT, candidate_row, clean_copy, emit, fail, parse_workspace, restore_baseline, run_legacy, verify_venv
|
|
7
7
|
|
|
8
8
|
workspace = parse_workspace()
|
|
9
|
+
problem = verify_venv(workspace)
|
|
10
|
+
if problem is not None:
|
|
11
|
+
raise SystemExit(fail({'task': 'featurebench', 'ok': False, 'error': problem}))
|
|
9
12
|
with tempfile.TemporaryDirectory(prefix='feature-eval-') as td:
|
|
10
13
|
temp = Path(td)
|
|
11
14
|
candidate = temp / 'candidate' / 'workspace'
|
|
@@ -13,13 +16,14 @@ with tempfile.TemporaryDirectory(prefix='feature-eval-') as td:
|
|
|
13
16
|
clean_copy(workspace, candidate, ignore_venv=True)
|
|
14
17
|
venv = workspace / '.venv'
|
|
15
18
|
if venv.is_dir():
|
|
16
|
-
|
|
19
|
+
try:
|
|
20
|
+
os.symlink(venv, candidate / '.venv')
|
|
21
|
+
except FileExistsError:
|
|
22
|
+
pass
|
|
23
|
+
restore_baseline(candidate)
|
|
17
24
|
result = run_legacy(EVAL_ROOT / 'legacy' / 'featurebench.py', ['--submissions', str(temp)], temp / 'out')
|
|
18
25
|
if not result.get('ok'):
|
|
19
26
|
raise SystemExit(fail({'task': 'featurebench', 'ok': False, 'error': result.get('error', 'evaluation failed')}))
|
|
20
27
|
row = candidate_row(result['summary'])
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
errors = row.get('errors', 0)
|
|
24
|
-
resolved = failed == 0 and errors == 0 and passed > 0
|
|
25
|
-
emit({'task': 'featurebench', 'ok': True, 'score': 100.0 if resolved else 0.0, 'resolved': resolved, 'passRate': row.get('score', 0), 'passed': passed, 'failed': failed, 'errors': errors, 'status': row.get('status')})
|
|
28
|
+
resolved = row.get('resolved') is True
|
|
29
|
+
emit({'task': 'featurebench', 'ok': True, 'score': 100.0 if resolved else 0.0, 'resolved': resolved, 'passRate': row.get('score', 0), 'passed': row.get('passed', 0), 'failed': row.get('failed', 0), 'errors': row.get('errors', 0), 'f2pPassed': row.get('f2p_passed', 0), 'f2pTotal': row.get('f2p_total', 0), 'status': row.get('status')})
|
package/eval/land.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Single-workspace
|
|
2
|
+
"""Single-workspace land evaluator wrapper."""
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
import shutil, tempfile
|
|
5
5
|
from pathlib import Path
|
|
@@ -17,4 +17,6 @@ with tempfile.TemporaryDirectory(prefix='land-eval-') as td:
|
|
|
17
17
|
if not result.get('ok'):
|
|
18
18
|
raise SystemExit(fail({'task': 'land', 'ok': False, 'error': result.get('error', 'evaluation failed')}))
|
|
19
19
|
row = candidate_row(result['summary'])
|
|
20
|
-
|
|
20
|
+
if not row:
|
|
21
|
+
raise SystemExit(fail({'task': 'land', 'ok': False, 'error': 'candidate row missing from evaluator summary'}))
|
|
22
|
+
emit({'task': 'land', 'ok': True, 'score': row.get('score', row.get('rating', 0)), 'rating': row.get('rating', row.get('theta', 0)), 'theta': row.get('theta', 0)})
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|