Skip to content

Commit bd9e18c

Browse files
andre15silvaclaude
andcommitted
feat(probe): per-probe per-layer sweep pipeline with parallel coordinator
- configs: add layer 0 to probe_layers ([0, 10, 20, 30, 39]) for both models - run_probe.py: add --layer arg to scope sweep/final to a single layer; sweep mode loops over probe_layers when --layer is not set - src/probe.py: run_sweep uses probe_layers[0] as sweep target (no middle-layer heuristic); create_sweep names sweep with layer suffix; run_sweep/run_final tag and group W&B runs by layer; run_final merges into existing results.pt so per-layer jobs accumulate correctly - probe_sweep_coordinator.sh: redesigned as a pure orchestrator — creates one W&B sweep, submits N parallel worker jobs, submits final training with --dependency=afterok on all workers; --count is total trials (divided by --n-agents internally); coordinator exits immediately after submitting - probe_sweep.sh / probe_final.sh: bump --mem to 256G for large layer caches - extract_swebench_thin.sh / extract_swebench.sh: add --chunk-size 8192 to handle long trajectories without OOM on thin nodes - README: rewrite to document the current agentic SWE-bench pipeline only Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7807d09 commit bd9e18c

10 files changed

Lines changed: 223 additions & 437 deletions

README.md

Lines changed: 118 additions & 379 deletions
Large diffs are not rendered by default.

configs/models/laguna_xs2.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
model_id: "poolside/Laguna-XS.2"
2-
probe_layers: [10, 20, 30, 39]
2+
probe_layers: [0, 10, 20, 30, 39]
33
adapter: laguna

configs/models/qwen36_35b_a3b.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
model_id: "Qwen/Qwen3.6-35B-A3B"
2-
probe_layers: [10, 20, 30, 39]
2+
probe_layers: [0, 10, 20, 30, 39]
33
adapter: qwen

run_probe.py

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ def main():
6262
eval_p.add_argument("--output-run-id", required=True,
6363
help="Run ID under which to save results.pt")
6464

65+
parser.add_argument("--layer", type=int, default=None,
66+
help="Run sweep/final for this single layer only (overrides model config loop)")
67+
6568
args = parser.parse_args()
6669
if args.n_eval_bins is not None and args.n_eval_bins != args.n_bins and args.n_bins != 1:
6770
parser.error("--n-eval-bins requires --n-bins 1 or --n-eval-bins equal to --n-bins")
@@ -73,26 +76,29 @@ def main():
7376
run_id=args.run_id,
7477
probe_name=args.probe,
7578
fixed_params=args.fixed,
79+
layer=args.layer,
7680
)
7781
elif args.mode == "sweep":
78-
run_sweep(
79-
run_id=args.run_id,
80-
probe_name=args.probe,
81-
probe_layers=model_cfg.probe_layers,
82-
seed=args.seed,
83-
sweep_id=args.sweep_id,
84-
count=args.count,
85-
cache_dir=args.cache_dir,
86-
cache_run_id=cache_run_id,
87-
n_bins=args.n_bins,
88-
probe_arch=args.probe_arch,
89-
n_eval_bins=args.n_eval_bins,
90-
eval_bin_axis=args.eval_bin_axis,
91-
then_final=args.then_final,
92-
results_dir=args.results_dir,
93-
fixed_params=args.fixed,
94-
shuffle_labels=args.shuffle_labels,
95-
)
82+
layers = [args.layer] if args.layer is not None else model_cfg.probe_layers
83+
for layer in layers:
84+
run_sweep(
85+
run_id=args.run_id,
86+
probe_name=args.probe,
87+
probe_layers=[layer],
88+
seed=args.seed,
89+
sweep_id=args.sweep_id,
90+
count=args.count,
91+
cache_dir=args.cache_dir,
92+
cache_run_id=cache_run_id,
93+
n_bins=args.n_bins,
94+
probe_arch=args.probe_arch,
95+
n_eval_bins=args.n_eval_bins,
96+
eval_bin_axis=args.eval_bin_axis,
97+
then_final=args.then_final,
98+
results_dir=args.results_dir,
99+
fixed_params=args.fixed,
100+
shuffle_labels=args.shuffle_labels,
101+
)
96102
elif args.mode == "eval":
97103
run_eval(
98104
weights_run_id=args.weights_run_id or args.run_id,
@@ -111,10 +117,11 @@ def main():
111117
tool_nll_run_id=args.tool_nll_run_id,
112118
)
113119
else:
120+
layers = [args.layer] if args.layer is not None else model_cfg.probe_layers
114121
run_final(
115122
run_id=args.run_id,
116123
probe_name=args.probe,
117-
probe_layers=model_cfg.probe_layers,
124+
probe_layers=layers,
118125
lr=args.lr,
119126
weight_decay=args.weight_decay,
120127
batch_size=args.batch_size,

slurm/extract_swebench.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,5 @@ uv run python run_extract_swebench.py \
4242
--shard-rank "$RANK" \
4343
--num-shards "$NUM_SHARDS" \
4444
--extraction-batch-size 1 \
45+
--chunk-size 8192 \
4546
"$@"

slurm/extract_swebench_thin.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ uv run python run_extract_swebench.py \
3333
--shard-rank "$RANK" \
3434
--num-shards "$NUM_SHARDS" \
3535
--extraction-batch-size 1 \
36+
--chunk-size 8192 \
3637
"$@"

slurm/probe_final.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#SBATCH -J pp-probe-final
33
#SBATCH -p berzelius-cpu
44
#SBATCH -n 8
5-
#SBATCH --mem=150G
5+
#SBATCH --mem=256G
66
#SBATCH -t 02:00:00
77
#SBATCH -o logs/probe_final_%j.out
88
#SBATCH -e logs/probe_final_%j.err

slurm/probe_sweep.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#SBATCH -J pp-probe-sweep
33
#SBATCH -p berzelius-cpu
44
#SBATCH -n 8
5-
#SBATCH --mem=150G
5+
#SBATCH --mem=256G
66
#SBATCH -t 04:00:00
77
#SBATCH -o logs/probe_sweep_%j.out
88
#SBATCH -e logs/probe_sweep_%j.err

slurm/probe_sweep_coordinator.sh

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,74 @@
11
#!/bin/bash
2+
# Coordinator for a single (probe, layer) sweep.
3+
# Creates one W&B sweep, submits N parallel worker jobs, then submits final
4+
# training as a dependent job that runs after all workers complete.
5+
#
6+
# Usage — one call per (probe, layer):
7+
# sbatch slurm/probe_sweep_coordinator.sh \
8+
# --model-config configs/models/laguna_xs2.yaml \
9+
# --layer 0 \
10+
# --probe currently_correct \
11+
# --probe-arch linear \
12+
# --run-id laguna_xs2_full_pooled \
13+
# --cache-dir cache/swebench \
14+
# --cache-run-id laguna_xs2_full \
15+
# --results-dir results/swebench \
16+
# --n-bins 1 \
17+
# --n-agents 4 \
18+
# --count 5
19+
#
220
#SBATCH -J pp-probe-coord
321
#SBATCH -p berzelius-cpu
4-
#SBATCH -n 8
5-
#SBATCH --mem=150G
6-
#SBATCH -t 04:00:00
22+
#SBATCH -n 2
23+
#SBATCH --mem=8G
24+
#SBATCH -t 00:10:00
725
#SBATCH -o logs/probe_sweep_coordinator_%j.out
826
#SBATCH -e logs/probe_sweep_coordinator_%j.err
927

1028
set -euo pipefail
1129
mkdir -p logs
1230

1331
# ---------------------------------------------------------------------------
14-
# Parse --n-agents and --count; collect everything else as probe_args
32+
# Parse --n-agents and --count; pass everything else through to run_probe.py
1533
# ---------------------------------------------------------------------------
16-
N_AGENTS=2
17-
COUNT=10
34+
N_AGENTS=4
35+
COUNT=20
1836
PROBE_ARGS=()
1937

2038
while [[ $# -gt 0 ]]; do
2139
case "$1" in
22-
--n-agents) N_AGENTS="$2"; shift 2 ;;
23-
--count) COUNT="$2"; shift 2 ;;
24-
*) PROBE_ARGS+=("$1"); shift ;;
40+
--n-agents) N_AGENTS="$2"; shift 2 ;;
41+
--count) COUNT="$2"; shift 2 ;;
42+
*) PROBE_ARGS+=("$1"); shift ;;
2543
esac
2644
done
2745

46+
COUNT_PER_AGENT=$(( COUNT / N_AGENTS ))
47+
2848
# ---------------------------------------------------------------------------
29-
# 1. Create the sweep and capture only the ID (printed to stdout by create_sweep)
49+
# 1. Create the sweep — layer name baked in via --layer in PROBE_ARGS
3050
# ---------------------------------------------------------------------------
31-
echo "[coordinator] Creating sweep (n_agents=${N_AGENTS}, count_per_agent=${COUNT})"
51+
echo "[coordinator] Creating sweep (n_agents=${N_AGENTS}, count=${COUNT}, count_per_agent=${COUNT_PER_AGENT})"
3252
SWEEP_ID=$(uv run python run_probe.py "${PROBE_ARGS[@]}" create-sweep)
3353
echo "[coordinator] Sweep ID: ${SWEEP_ID}"
3454

3555
# ---------------------------------------------------------------------------
36-
# 2. Submit N-1 additional agent jobs
56+
# 2. Submit N parallel worker jobs
3757
# ---------------------------------------------------------------------------
38-
AGENT_SLURM_TIME="${SLURM_TIMELIMIT:-04:00:00}"
39-
40-
for i in $(seq 2 "${N_AGENTS}"); do
41-
JOB_ID=$(sbatch --parsable \
42-
--time="${AGENT_SLURM_TIME}" \
58+
WORKER_IDS=()
59+
for i in $(seq 1 "${N_AGENTS}"); do
60+
JID=$(sbatch --parsable \
4361
slurm/probe_sweep.sh \
44-
"${PROBE_ARGS[@]}" sweep --sweep-id "${SWEEP_ID}" --count "${COUNT}")
45-
echo "[coordinator] Submitted agent ${i} as SLURM job ${JOB_ID}"
62+
"${PROBE_ARGS[@]}" sweep --sweep-id "${SWEEP_ID}" --count "${COUNT_PER_AGENT}")
63+
WORKER_IDS+=("${JID}")
64+
echo "[coordinator] Worker ${i} → job ${JID}"
4665
done
4766

4867
# ---------------------------------------------------------------------------
49-
# 3. Run as agent 1
68+
# 3. Submit final training, dependent on all workers completing successfully
5069
# ---------------------------------------------------------------------------
51-
echo "[coordinator] Running as agent 1"
52-
uv run python run_probe.py "${PROBE_ARGS[@]}" sweep --sweep-id "${SWEEP_ID}" --count "${COUNT}"
70+
DEP="afterok:$(IFS=:; echo "${WORKER_IDS[*]}")"
71+
FINAL_JID=$(sbatch --parsable --dependency="${DEP}" \
72+
slurm/probe_final.sh \
73+
"${PROBE_ARGS[@]}" final --from-sweep "${SWEEP_ID}")
74+
echo "[coordinator] Final training → job ${FINAL_JID} (depends on ${WORKER_IDS[*]})"

src/probe.py

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -360,10 +360,11 @@ def train_probe_layer(
360360
return results, weights
361361

362362

363-
def create_sweep(run_id: str, probe_name: str, fixed_params: dict | None = None) -> str:
363+
def create_sweep(run_id: str, probe_name: str, fixed_params: dict | None = None, layer: int | None = None) -> str:
364364
import wandb
365+
layer_suffix = f"-layer{layer}" if layer is not None else ""
365366
sweep_config = {
366-
"name": f"sweep-{run_id}-{probe_name}",
367+
"name": f"sweep-{run_id}-{probe_name}{layer_suffix}",
367368
"method": "random",
368369
"metric": {"name": "mean_val_auc", "goal": "maximize"},
369370
"parameters": {
@@ -427,16 +428,16 @@ def run_sweep(
427428
import wandb
428429
cache_base = Path(cache_dir) / (cache_run_id or run_id) / probe_name
429430

430-
middle_layer = probe_layers[len(probe_layers) // 2]
431-
middle_cache = str(cache_base / f"layer_{middle_layer}.pt")
431+
sweep_layer = probe_layers[0]
432+
sweep_cache = str(cache_base / f"layer_{sweep_layer}.pt")
432433

433434
if sweep_id is None:
434-
sweep_id = create_sweep(run_id, probe_name, fixed_params=fixed_params)
435+
sweep_id = create_sweep(run_id, probe_name, fixed_params=fixed_params, layer=sweep_layer)
435436

436437
def sweep_fn():
437438
with wandb.init(
438-
group=f"{run_id}/{probe_name}",
439-
tags=[run_id, probe_name, "sweep"],
439+
group=f"{run_id}/{probe_name}/layer_{sweep_layer}",
440+
tags=[run_id, probe_name, f"layer_{sweep_layer}", "sweep"],
440441
) as run:
441442
cfg = run.config
442443

@@ -451,7 +452,7 @@ def log_fn(metrics: dict) -> None:
451452
})
452453

453454
results, _ = train_probe_layer(
454-
middle_cache, middle_layer,
455+
sweep_cache, sweep_layer,
455456
lr=cfg.lr, weight_decay=cfg.weight_decay,
456457
batch_size=cfg.batch_size, patience=cfg.patience,
457458
seed=seed, n_bins=n_bins, probe_arch=probe_arch,
@@ -543,12 +544,14 @@ def run_final(
543544
all_results: dict[int, list[ProbeResult]] = {}
544545
all_weights: dict[int, dict] = {}
545546

547+
layer_suffix = f"/layer_{probe_layers[0]}" if len(probe_layers) == 1 else ""
546548
with wandb.init(
547549
project="program-probes",
548550
job_type="final",
549-
name=f"final-{run_id}-{probe_name}-{probe_arch}",
550-
group=f"{run_id}/{probe_name}",
551-
tags=[run_id, probe_name, "final", probe_arch],
551+
name=f"final-{run_id}-{probe_name}-{probe_arch}{layer_suffix}",
552+
group=f"{run_id}/{probe_name}{layer_suffix}",
553+
tags=[run_id, probe_name, "final", probe_arch]
554+
+ ([f"layer_{probe_layers[0]}"] if len(probe_layers) == 1 else []),
552555
) as run:
553556
for layer_idx in probe_layers:
554557
cache_path = str(cache_base / f"layer_{layer_idx}.pt")
@@ -597,8 +600,21 @@ def log_fn(metrics: dict) -> None:
597600

598601
out = Path(results_dir) / run_id / probe_name
599602
out.mkdir(parents=True, exist_ok=True)
600-
torch.save(all_results, out / "results.pt")
601-
torch.save(all_weights, out / "weights.pt")
603+
604+
results_path = out / "results.pt"
605+
if results_path.exists():
606+
existing = torch.load(results_path, weights_only=False)
607+
existing.update(all_results)
608+
all_results = existing
609+
610+
weights_path = out / "weights.pt"
611+
if weights_path.exists():
612+
existing_w = torch.load(weights_path, weights_only=False)
613+
existing_w.update(all_weights)
614+
all_weights = existing_w
615+
616+
torch.save(all_results, results_path)
617+
torch.save(all_weights, weights_path)
602618
return {layer: [vars(r) for r in rs] for layer, rs in all_results.items()}
603619

604620

0 commit comments

Comments
 (0)