@@ -10,6 +10,7 @@ RUN_ID=""
1010CONDITIONS=" A,B,C"
1111TASK_LIMIT=1
1212TASK=" "
13+ SHARD_SPEC=" "
1314MAX_CLAUDE=" "
1415DRY_RUN=false
1516SUITE=$( code_suite_id)
@@ -20,6 +21,7 @@ while (( $# > 0 )); do
2021 --conditions) CONDITIONS=" ${2:? --conditions needs a value} " ; shift 2 ;;
2122 --task-limit) TASK_LIMIT=" ${2:? --task-limit needs a value} " ; shift 2 ;;
2223 --task) TASK=" ${2:? --task needs a value} " ; shift 2 ;;
24+ --shard) SHARD_SPEC=" ${2:? --shard needs N/ M} " ; shift 2 ;;
2325 --max-claude-dispatches) MAX_CLAUDE=" ${2:? --max-claude-dispatches needs a value} " ; shift 2 ;;
2426 --dry-run) DRY_RUN=true; shift ;;
2527 * ) code_die " unknown canary option: $1 " ; exit 2 ;;
3032[[ " $TASK_LIMIT " =~ ^[1-9][0-9]* $ ]] || { code_die " --task-limit must be positive" ; exit 2; }
3133[[ " $MAX_CLAUDE " =~ ^[0-9]+$ ]] || { code_die " --max-claude-dispatches is required" ; exit 2; }
3234
35+ # --shard N/M splits the subset across M processes that run at the same time.
36+ # Shards take disjoint instances, so their cell directories never collide, and
37+ # each writes its own prediction file because concurrent appends to one file
38+ # interleave. Wall clock falls by roughly M; provider concurrency rises by M,
39+ # which is the real limit on how far this can be pushed.
40+ SHARD_INDEX=0
41+ SHARD_COUNT=1
42+ if [[ -n " $SHARD_SPEC " ]]; then
43+ [[ " $SHARD_SPEC " =~ ^[0-9]+/[0-9]+$ ]] || { code_die " --shard must look like N/M" ; exit 2; }
44+ SHARD_INDEX=" ${SHARD_SPEC%%/* } "
45+ SHARD_COUNT=" ${SHARD_SPEC##*/ } "
46+ (( SHARD_COUNT >= 1 )) || { code_die " --shard M must be at least 1" ; exit 2; }
47+ (( SHARD_INDEX < SHARD_COUNT )) || { code_die " --shard N must be less than M" ; exit 2; }
48+ fi
49+
3350suite_json=$( code_suite_json " $SUITE " )
3451subset=$( code_subset_path " $suite_json " )
3552
5471pred_dir=" $CODE_BENCH_RESULTS_ROOT /predictions/$RUN_ID "
5572mkdir -p " $pred_dir "
5673
74+ runs_root=" $CODE_BENCH_RESULTS_ROOT /runs/$RUN_ID "
75+ failed_cells=0
76+ done_cells=0
77+ skipped_cells=0
78+
5779task_index=0
5880while IFS= read -r instance; do
5981 task_index=$(( task_index + 1 ))
6082 (( task_index <= TASK_LIMIT )) || break
83+ (( (task_index - 1 ) % SHARD_COUNT == SHARD_INDEX )) || continue
6184 for condition in $( printf ' %s' " $CONDITIONS " | tr ' ,' ' ' ) ; do
62- input=$( bash " $CODE_DIR /scripts/prepare-swebench-instance.sh" " $instance " " $RUN_ID " " $condition " )
85+ cell=" $runs_root /$instance /$condition "
86+ if (( SHARD_COUNT > 1 )) ; then
87+ pred_file=" $pred_dir /$condition .s$SHARD_INDEX .jsonl"
88+ else
89+ pred_file=" $pred_dir /$condition .jsonl"
90+ fi
91+ # A fifty-task batch is hours long, so it has to be restartable. A cell that
92+ # already holds a prediction is finished and is left alone; a cell without
93+ # one is an abandoned clone from an interrupted attempt, and re-preparing it
94+ # is the only way forward because prepare refuses an existing directory.
95+ if [[ -f " $cell /prediction.json" ]]; then
96+ # The cell is the record of truth; the prediction file is a projection of
97+ # it. Re-link a finished cell whose line is missing, so a resume under a
98+ # different shard layout still yields complete prediction files.
99+ if ! jq -e --arg id " $instance " ' select(.instance_id == $id)' " $pred_file " \
100+ > /dev/null 2>&1 ; then
101+ jq -c . " $cell /prediction.json" >> " $pred_file "
102+ fi
103+ printf ' SKIP: %s/%s already has a prediction\n' " $instance " " $condition "
104+ skipped_cells=$(( skipped_cells + 1 )) ; continue
105+ fi
106+ [[ ! -d " $cell " ]] || rm -rf " $cell "
107+ if ! input=$( bash " $CODE_DIR /scripts/prepare-swebench-instance.sh" " $instance " " $RUN_ID " " $condition " ) ; then
108+ printf ' CELL FAILED: could not prepare %s/%s\n' " $instance " " $condition " >&2
109+ failed_cells=$(( failed_cells + 1 )) ; continue
110+ fi
63111 tier=$( jq -r --arg id " $condition " ' .conditions[] | select(.id == $id) | .tier' \
64112 " $CODE_DIR /conditions.json" | tr -d ' \r' )
65113 # Single-shot cells have no agent loop, so they take the other driver. The
66114 # tier decides, not a hardcoded condition list: a new single-shot arm routes
67115 # itself the moment conditions.json declares its tier.
116+ #
117+ # A cell that produces no patch is a zero for that arm, not a reason to
118+ # abandon the batch: the subset is the denominator either way, and an abort
119+ # here used to throw away every remaining cell in the run.
120+ cell_rc=0
68121 if [[ " $tier " == " single-shot" ]]; then
69122 agent=$( jq -r --arg id " $condition " \
70123 ' .conditions[] | select(.id == $id) | .dispatches | to_entries
71124 | map(select(.value > 0)) | .[0].key' \
72125 " $CODE_DIR /conditions.json" | tr -d ' \r' )
73126 bash " $CODE_DIR /drivers/run-single-shot.sh" --input " $input " \
74- --predictions " $pred_dir / $condition .jsonl " --agent " $agent "
127+ --predictions " $pred_file " --agent " $agent " || cell_rc= $?
75128 else
76129 per_condition=$( jq -r --arg id " $condition " ' .conditions[] | select(.id == $id) | .dispatches.claude' \
77130 " $CODE_DIR /conditions.json" | tr -d ' \r' )
78131 bash " $CODE_DIR /drivers/run-workflow.sh" --input " $input " \
79- --predictions " $pred_dir /$condition .jsonl" \
80- --max-claude-dispatches " $per_condition "
132+ --predictions " $pred_file " \
133+ --max-claude-dispatches " $per_condition " || cell_rc=$?
134+ fi
135+ if (( cell_rc != 0 )) ; then
136+ printf ' CELL FAILED: %s/%s produced no prediction (rc=%s)\n' " $instance " " $condition " " $cell_rc " >&2
137+ failed_cells=$(( failed_cells + 1 ))
138+ else
139+ done_cells=$(( done_cells + 1 ))
81140 fi
82141 done
83142done < <( if [[ -n " $TASK " ]]; then printf ' %s\n' " $TASK "
84143 else jq -r ' .instances[].instance_id' " $subset " | tr -d ' \r' ; fi)
85144
86- for predictions in " $pred_dir " /* .jsonl; do
145+ # Validate only this shard's own files. A sibling shard is still appending to
146+ # its own, and validating a file mid-write reports a failure that is not real.
147+ shopt -s nullglob
148+ if (( SHARD_COUNT > 1 )) ; then
149+ validate_glob=(" $pred_dir " /* .s" $SHARD_INDEX " .jsonl)
150+ else
151+ validate_glob=(" $pred_dir " /* .jsonl)
152+ fi
153+ for predictions in " ${validate_glob[@]} " ; do
87154 bash " $CODE_DIR /validate-predictions.sh" " $predictions " " $SUITE "
88155done
89- printf ' COMPLETE: canary predictions -> %s\n' " $pred_dir "
156+ printf ' COMPLETE: %s cell(s) generated, %s reused, %s failed -> %s\n' \
157+ " $done_cells " " $skipped_cells " " $failed_cells " " $pred_dir "
158+ if (( failed_cells > 0 )) ; then
159+ printf ' INCOMPLETE: %s cell(s) produced no prediction. They score zero against the\n' " $failed_cells " >&2
160+ printf ' subset; rerun the same command to retry only those cells.\n' >&2
161+ fi
0 commit comments