Skip to content

Commit ad9e543

Browse files
committed
feat(bench): add depth axis to sweep matrix; aggregator parses L<depth>
Workflow `sweep-full` matrix now includes `depth: [-1, 0, 1, 2, 3, 4]` with EGO running on {0,1,2,3,4} and non-EGO methods (PPR, BM25, Aider) running only on the sentinel depth=-1. Excludes are explicit so the matrix doesn't accidentally multiply 5x for modes that ignore depth. Per-cell wiring: - CELL_TAG / CELL_DIR include `L${depth}` so EGO cells with the same (method, budget, test_set) but different depth land in distinct dirs. - The Run cell step exports `DIFFCTX_OP_GRAPH_DEPTH=${matrix.depth}` before invoking `run_final_eval` (skipped when depth=-1 sentinel, so non-EGO methods inherit the default). - metadata.json gains `cell.depth` for reproducibility. - upload-artifact name is `cell-<method>-b<budget>-L<depth>-<test_set>`, so 5 EGO cells under the same (budget, test_set) no longer collide. aggregate_sweep.py: - Regex split into _ARTIFACT_RE_WITH_DEPTH and _ARTIFACT_RE_LEGACY so the new dirs parse with depth and legacy dirs (no L segment) parse with depth=-1 sentinel. `_parse_artifact` returns a 4-tuple (method, budget, depth, test_set); legacy callers continue to use metadata.json values when present. - collect_cells captures depth into the cell record. - CSV gains a `depth` column so the L sweep is queryable downstream. Sanity checks performed before this commit (all PASS, no blockers): - source_benchmark adapter strings == manifest names (swebench_verified, polybench500, contextbench_verified) - Aider/BM25 do not crash on budget=0 (existing run_25288507618 shows 100% ok|empty_query|clone_fail status, no crashes) - Aider PolyBench 0.284 is real, not an integration bug; root cause documented in paper Limitations section. Run-time projection at full matrix (162 cells, 4 self-hosted runners): ~9-15h optimistic, ~50-70h pessimistic depending on per-cell variance. The multi-budget reuse path (`pool_eval_all_cells`) is implemented but not used by this workflow — each cell runs heavy phase from scratch because budget remains a matrix axis. Trade-off is explicit; collapse to per-(method, depth, test_set) cells with --budgets is a future optimization.
1 parent 73678ac commit ad9e543

3 files changed

Lines changed: 74 additions & 16 deletions

File tree

.github/workflows/bench-sweep.yml

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,10 +434,32 @@ jobs:
434434
# unlimited-budget ceiling. The 5 paying budgets {8k, 16k, 32k, 64k, 128k}
435435
# form the budget curve referenced in STATS_PLAN.md.
436436
budget: [-1, 0, 8000, 16000, 32000, 64000, 128000]
437+
# `depth` is the EGO graph traversal radius; only meaningful for
438+
# method=ego. PPR uses alpha (not depth), BM25 has no graph,
439+
# Aider is its own subprocess. Sentinel -1 means "depth does not
440+
# apply"; non-EGO methods run only with depth=-1.
441+
depth: [-1, 0, 1, 2, 3, 4]
437442
test_set: [contextbench_verified, polybench500, swebench_verified]
438443
exclude:
439444
- {method: bm25, budget: -1}
440445
- {method: aider, budget: -1}
446+
# Non-EGO methods: only the depth=-1 cell.
447+
- {method: ppr, depth: 0}
448+
- {method: ppr, depth: 1}
449+
- {method: ppr, depth: 2}
450+
- {method: ppr, depth: 3}
451+
- {method: ppr, depth: 4}
452+
- {method: bm25, depth: 0}
453+
- {method: bm25, depth: 1}
454+
- {method: bm25, depth: 2}
455+
- {method: bm25, depth: 3}
456+
- {method: bm25, depth: 4}
457+
- {method: aider, depth: 0}
458+
- {method: aider, depth: 1}
459+
- {method: aider, depth: 2}
460+
- {method: aider, depth: 3}
461+
- {method: aider, depth: 4}
462+
- {method: ego, depth: -1}
441463
runs-on: [self-hosted, large]
442464
timeout-minutes: 350
443465
container:
@@ -455,8 +477,8 @@ jobs:
455477
--ulimit nproc=65535:65535
456478
-v /data/bench_repos:/cache/contextbench_repos
457479
env:
458-
CELL_TAG: ${{ matrix.method }}_b${{ matrix.budget }}_${{ matrix.test_set }}
459-
CELL_DIR: results/sweep/${{ matrix.method }}_b${{ matrix.budget }}_${{ matrix.test_set }}
480+
CELL_TAG: ${{ matrix.method }}_b${{ matrix.budget }}_L${{ matrix.depth }}_${{ matrix.test_set }}
481+
CELL_DIR: results/sweep/${{ matrix.method }}_b${{ matrix.budget }}_L${{ matrix.depth }}_${{ matrix.test_set }}
460482
MANIFESTS_DIR: benchmarks/manifests/v1
461483
INPUT_TAU: '0.12'
462484
INPUT_CBF: '0.5'
@@ -475,6 +497,7 @@ jobs:
475497
"cell": {
476498
"method": "${{ matrix.method }}",
477499
"budget": ${{ matrix.budget }},
500+
"depth": ${{ matrix.depth }},
478501
"test_set": "${{ matrix.test_set }}",
479502
"tau": ${INPUT_TAU},
480503
"core_budget_fraction": ${INPUT_CBF}
@@ -653,6 +676,12 @@ jobs:
653676
set -o pipefail
654677
python3 /tmp/heartbeat.py >> "${CELL_DIR}/heartbeat.log" 2>&1 &
655678
HEARTBEAT_PID=$!
679+
# Apply EGO graph depth as an env override only when matrix.depth >= 0
680+
# (the sentinel -1 means "depth doesn't apply to this method"; in that
681+
# case we leave DIFFCTX_OP_GRAPH_DEPTH unset and the default kicks in).
682+
if [ "${MATRIX_DEPTH}" != "-1" ]; then
683+
export DIFFCTX_OP_GRAPH_DEPTH="${MATRIX_DEPTH}"
684+
fi
656685
python -m benchmarks.run_final_eval \
657686
--baseline "${BASELINE}" \
658687
--winner /tmp/winner.json \
@@ -665,6 +694,7 @@ jobs:
665694
kill "${HEARTBEAT_PID}" 2>/dev/null || true
666695
env:
667696
BASELINE: ${{ steps.cfg.outputs.baseline }}
697+
MATRIX_DEPTH: ${{ matrix.depth }}
668698

669699
- name: Per-instance metric summary (always)
670700
if: always()
@@ -764,7 +794,7 @@ jobs:
764794
if: always()
765795
uses: actions/upload-artifact@v7
766796
with:
767-
name: cell-${{ matrix.method }}-b${{ matrix.budget }}-${{ matrix.test_set }}
797+
name: cell-${{ matrix.method }}-b${{ matrix.budget }}-L${{ matrix.depth }}-${{ matrix.test_set }}
768798
path: ${{ env.CELL_DIR }}/
769799
retention-days: 90
770800
if-no-files-found: warn

benchmarks/aggregate_sweep.py

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
"""Aggregate per-cell sweep artifacts into one summary JSON + markdown table.
22
33
Input layout (from `actions/download-artifact@v4` with pattern=cell-*):
4-
<cells-dir>/cell-<method>-b<budget>-<test_set>/
4+
<cells-dir>/cell-<method>-b<budget>-L<depth>-<test_set>/
55
metadata.json
66
cell_summary.json
77
<test_set>.checkpoint.jsonl
88
<test_set>.json
99
run.log
1010
system_info.log
1111
12+
Legacy layout `cell-<method>-b<budget>-<test_set>/` is still parsed (depth=-1
13+
sentinel meaning "method does not consume depth") so old artifact dumps
14+
remain readable.
15+
1216
Output:
1317
<out>/grand_summary.json — every cell's metadata + summary in one file
1418
<out>/SWEEP_TABLE.md — markdown matrix of mean recall per cell
@@ -63,12 +67,14 @@ def collect_cells(cells_dir: Path) -> list[dict]:
6367
ckpts = sorted(cell_root.glob("*.checkpoint.jsonl"))
6468
rows = _load_jsonl(ckpts[0]) if ckpts else []
6569
cell_info = meta.get("cell") or {}
70+
parsed = _parse_artifact(cell_root.name)
6671
cells.append(
6772
{
6873
"artifact_dir": cell_root.name,
69-
"method": cell_info.get("method") or _parse_artifact(cell_root.name)[0],
70-
"budget": cell_info.get("budget") if cell_info.get("budget") is not None else _parse_artifact(cell_root.name)[1],
71-
"test_set": cell_info.get("test_set") or _parse_artifact(cell_root.name)[2],
74+
"method": cell_info.get("method") or parsed[0],
75+
"budget": cell_info.get("budget") if cell_info.get("budget") is not None else parsed[1],
76+
"depth": cell_info.get("depth") if cell_info.get("depth") is not None else parsed[2],
77+
"test_set": cell_info.get("test_set") or parsed[3],
7278
"metadata": meta,
7379
"summary": summary,
7480
"n_instances": len(rows),
@@ -78,23 +84,42 @@ def collect_cells(cells_dir: Path) -> list[dict]:
7884
return cells
7985

8086

81-
_ARTIFACT_RE = __import__("re").compile(r"^cell-(?P<method>[a-zA-Z0-9_]+)-b(?P<budget>-?\d+)-(?P<test_set>.+)$")
87+
# New artifact layout: cell-<method>-b<budget>-L<depth>-<test_set>
88+
_ARTIFACT_RE_WITH_DEPTH = __import__("re").compile(
89+
r"^cell-(?P<method>[a-zA-Z0-9_]+)-b(?P<budget>-?\d+)-L(?P<depth>-?\d+)-(?P<test_set>.+)$"
90+
)
91+
# Legacy artifact layout: cell-<method>-b<budget>-<test_set> (no depth segment)
92+
_ARTIFACT_RE_LEGACY = __import__("re").compile(r"^cell-(?P<method>[a-zA-Z0-9_]+)-b(?P<budget>-?\d+)-(?P<test_set>.+)$")
8293

8394

84-
def _parse_artifact(name: str) -> tuple[str | None, int | None, str | None]:
85-
"""Best-effort parse of a cell-<method>-b<budget>-<test_set> directory name.
95+
def _parse_artifact(name: str) -> tuple[str | None, int | None, int | None, str | None]:
96+
"""Parse a `cell-<method>-b<budget>-L<depth>-<test_set>` directory name.
8697
87-
Used as a fallback when metadata.json was not produced (e.g., the cell
88-
crashed before the metadata step ran).
98+
Returns (method, budget, depth, test_set). For legacy artifacts that
99+
predate the depth axis, depth resolves to -1 (the sentinel meaning
100+
"method does not consume depth"). Used as a fallback when
101+
`metadata.json` was not produced (e.g., the cell crashed before the
102+
metadata step ran).
89103
"""
90-
m = _ARTIFACT_RE.match(name)
104+
m = _ARTIFACT_RE_WITH_DEPTH.match(name)
105+
if m:
106+
try:
107+
budget = int(m.group("budget"))
108+
except ValueError:
109+
budget = None
110+
try:
111+
depth: int | None = int(m.group("depth"))
112+
except ValueError:
113+
depth = None
114+
return (m.group("method"), budget, depth, m.group("test_set"))
115+
m = _ARTIFACT_RE_LEGACY.match(name)
91116
if not m:
92-
return (None, None, None)
117+
return (None, None, None, None)
93118
try:
94119
budget = int(m.group("budget"))
95120
except ValueError:
96121
budget = None
97-
return (m.group("method"), budget, m.group("test_set"))
122+
return (m.group("method"), budget, -1, m.group("test_set"))
98123

99124

100125
_METHOD_ORDER = ["ppr", "ego", "bm25", "aider"]
@@ -147,6 +172,7 @@ def write_csv(cells: list[dict], path: Path) -> None:
147172
fields = [
148173
"method",
149174
"budget",
175+
"depth",
150176
"test_set",
151177
"n_instances",
152178
"n_ok",
@@ -170,6 +196,7 @@ def write_csv(cells: list[dict], path: Path) -> None:
170196
row = {
171197
"method": c["method"],
172198
"budget": c["budget"],
199+
"depth": c.get("depth"),
173200
"test_set": c["test_set"],
174201
"n_instances": s.get("n", c["n_instances"]),
175202
"n_ok": s.get("ok", 0),

benchmarks/run_final_eval.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@ def main() -> int:
193193
"because rel_scores depend on the traversal radius; budgets within a "
194194
"depth share scored state. Output: <name>_budget_sweep/L<depth>/b<budget>.checkpoint.jsonl. "
195195
"Empty (default): single depth from MODE.ego_depth_extended (= 2 unless "
196-
"DIFFCTX_OP_GRAPH_DEPTH is set in the calling shell).",
196+
"DIFFCTX_OP_GRAPH_DEPTH is set in the calling shell). Non-EGO scoring "
197+
"modes ignore --depths (PPR uses alpha; BM25 has no graph traversal).",
197198
)
198199
args = p.parse_args()
199200

0 commit comments

Comments
 (0)