11#! /bin/bash
22# Run PR and master benchmarks and verify outputs.
3- # Both SLURM jobs are submitted up front so they run concurrently on
4- # compute nodes (fair comparison under the same cluster load), but
5- # monitoring happens sequentially to stay within the per-user cgroup
6- # memory limit on login nodes (4 GB on Phoenix shared by 7 runners).
3+ #
4+ # Phoenix GPU runs both trees in ONE single-node job (see bench-pair.sh): same
5+ # node, same GPUs, benchmarked back-to-back -- identical hardware with no
6+ # contention, and it only needs one node so it backfills across a partition list.
7+ #
8+ # Other clusters (Frontier) and Phoenix CPU keep the two-job model: both SLURM
9+ # jobs are submitted up front so they run concurrently on compute nodes (fair
10+ # comparison under the same cluster load), but monitoring happens sequentially to
11+ # stay within the per-user cgroup memory limit on login nodes (4 GB on Phoenix
12+ # shared by 7 runners).
713# Usage: run_parallel_benchmarks.sh <device> <interface> <cluster>
814
915set -euo pipefail
@@ -24,96 +30,119 @@ echo "===================="
2430echo " Starting benchmark jobs..."
2531echo " ===================="
2632
27- # For Phoenix GPU benchmarks, select a consistent GPU partition so PR and
28- # master always land on the same GPU type.
29- if [ " $device " = " gpu" ] && [ " $cluster " = " phoenix" ]; then
30- echo " Selecting Phoenix GPU partition for benchmark consistency..."
31- # Require 2 nodes so both jobs can run concurrently on compute.
32- GPU_PARTITION_MIN_NODES=2 source " ${SCRIPT_DIR} /select-gpu-partition.sh"
33- BENCH_GPU_PARTITION=" $SELECTED_GPU_PARTITION "
34- export BENCH_GPU_PARTITION
35- fi
36-
37- # The bench script must come from the PR tree (master may not have it).
38- PR_BENCH_SCRIPT=" $( cd " ${SCRIPT_DIR} /../workflows/common" && pwd) /bench.sh"
39- # Must match the slug computed by submit-slurm-job.sh:
40- # basename("bench.sh") → "bench" → "bench-${device}-${interface}"
33+ # job_slug names the benchmark YAML each tree writes; bench.yml's bench_diff requires
34+ # exactly this name, and it is the same whether one job or two produced the results.
35+ # The SLURM .out/.slurm_job_id basename is NOT always the same string: submit-slurm-job.sh
36+ # derives that from the submitted script's basename, so the single-node path logs to
37+ # bench-pair-<device>-<interface> instead. Keep the two apart.
4138job_slug=" bench-${device} -${interface} "
39+ log_slug=" $job_slug "
4240
43- # --- Phase 1: Submit both SLURM jobs (no monitoring yet) ---
44- echo " Submitting PR benchmark..."
45- (cd pr && SUBMIT_ONLY=1 bash " ${SCRIPT_DIR} /submit-slurm-job.sh" " $PR_BENCH_SCRIPT " " $device " " $interface " " $cluster " )
46- pr_job_id=$( cat " pr/${job_slug} .slurm_job_id" )
47- echo " PR job submitted: $pr_job_id "
48-
49- echo " Submitting master benchmark..."
50- (cd master && SUBMIT_ONLY=1 bash " ${SCRIPT_DIR} /submit-slurm-job.sh" " $PR_BENCH_SCRIPT " " $device " " $interface " " $cluster " )
51- master_job_id=$( cat " master/${job_slug} .slurm_job_id" )
52- echo " Master job submitted: $master_job_id "
53-
54- echo " Both SLURM jobs submitted — running concurrently on compute nodes."
55- echo " Monitoring sequentially to conserve login node memory."
56-
57- # --- Phase 2: Monitor sequentially (one at a time on login node) ---
58- # On Phoenix 'embers' a long benchmark job can be preempted (PreemptMode=CANCEL,
59- # so it is killed rather than requeued). On preemption (run_monitored exit 76)
60- # resubmit a fresh job in the same tree and re-monitor, bounded by
61- # MAX_PREEMPT_RESUBMITS (the 480m job timeout is the real backstop). Note: a
62- # resubmitted job no longer overlaps its counterpart, slightly reducing
63- # same-load fairness -- still preferable to failing the run on an infra preempt.
64- : " ${MAX_PREEMPT_RESUBMITS:= 10} "
65- monitor_bench_with_resubmit () { # arg: <dir> (pr|master); sets BENCH_MON_RC
66- local dir=" $1 "
67- local out=" ${dir} /${job_slug} .out"
68- local jobid attempt=0 rc
69- jobid=$( cat " ${dir} /${job_slug} .slurm_job_id" )
70- while : ; do
71- rc=0
72- bash " ${SCRIPT_DIR} /run_monitored_slurm_job.sh" " $jobid " " $out " || rc=$?
73- if [ " $rc " -ne 76 ]; then
74- BENCH_MON_RC=" $rc "
75- return
76- fi
77- if [ " $attempt " -ge " $MAX_PREEMPT_RESUBMITS " ]; then
78- echo " ::error::${dir} benchmark preempted ${MAX_PREEMPT_RESUBMITS} x without completing; giving up."
79- BENCH_MON_RC=1
80- return
81- fi
82- attempt=$(( attempt + 1 ))
83- echo " ::warning::${dir} benchmark job $jobid was preempted; resubmitting (attempt ${attempt} /${MAX_PREEMPT_RESUBMITS} )."
84- rm -f " $out "
85- ( cd " $dir " && SUBMIT_ONLY=1 bash " ${SCRIPT_DIR} /submit-slurm-job.sh" " $PR_BENCH_SCRIPT " " $device " " $interface " " $cluster " )
86- jobid=$( cat " ${dir} /${job_slug} .slurm_job_id" )
87- echo " ${dir} benchmark resubmitted as job $jobid "
88- done
89- }
90-
91- echo " "
92- echo " === Monitoring PR job $pr_job_id ==="
93- monitor_bench_with_resubmit pr
94- pr_exit=$BENCH_MON_RC
95- if [ " $pr_exit " -ne 0 ]; then
96- echo " PR job exited with code: $pr_exit "
97- tail -n 50 " pr/${job_slug} .out" 2> /dev/null || echo " Could not read PR log"
98- # The PR benchmark run genuinely failed (cases crashed/hung/SIGTERM'd, not a
99- # monitor false-positive -- run_monitored_slurm_job.sh re-checks sacct). Fail
100- # the job instead of falling through to the YAML-exists check, which would let
101- # a broken PR pass green as long as a partial YAML was written. Scoped to PR
102- # only: a master/baseline infra flake stays a warning and does not red-cross.
103- exit 1
41+ if [ " $device " = " gpu" ] && [ " $cluster " = " phoenix" ]; then
42+ # --- Phoenix GPU: build + bench BOTH trees in ONE single-node job ---
43+ # bench-pair.sh builds master and PR, then benchmarks them back-to-back on
44+ # the same node and GPUs: identical hardware, no cross-run contention. Needing
45+ # only one node (-G2) lets submit-slurm-job.sh submit to a partition list and
46+ # backfill, instead of the old model's two-idle-nodes-in-one-partition wait.
47+ # Launched from the PR tree so ../master resolves and mfc.sh load finds a tree.
48+ # NOT SUBMIT_ONLY: submit-slurm-job.sh monitors and handles preemption (76)
49+ # and node faults (77) itself, so this even gains node-exclude-on-fault, which
50+ # the old two-job bench path never had.
51+ PAIR_SCRIPT=" $( cd " ${SCRIPT_DIR} /../workflows/common" && pwd) /bench-pair.sh"
52+ log_slug=" bench-pair-${device} -${interface} "
53+ echo " Phoenix GPU: building and benchmarking master and PR in one single-node job."
54+ pair_rc=0
55+ ( cd pr && bash " ${SCRIPT_DIR} /submit-slurm-job.sh" " $PAIR_SCRIPT " " $device " " $interface " " $cluster " ) || pair_rc=$?
56+ if [ " $pair_rc " -ne 0 ]; then
57+ # One job builds+benches both trees, so a failure is not attributable to
58+ # one side. Fail hard rather than fall through to the YAML-exists check,
59+ # which would let a broken run pass green on a stale/partial YAML.
60+ echo " ::error::single-node bench-pair job failed (exit ${pair_rc} )."
61+ exit 1
62+ fi
63+ pr_exit=0
64+ master_exit=0
65+ echo " Single-node bench-pair job completed successfully."
10466else
105- echo " PR job completed successfully"
106- fi
67+ # --- Other clusters / Phoenix CPU: two concurrent jobs, monitored serially ---
68+ # The bench script must come from the PR tree (master may not have it).
69+ PR_BENCH_SCRIPT=" $( cd " ${SCRIPT_DIR} /../workflows/common" && pwd) /bench.sh"
70+
71+ # Phase 1: Submit both SLURM jobs (no monitoring yet)
72+ echo " Submitting PR benchmark..."
73+ (cd pr && SUBMIT_ONLY=1 bash " ${SCRIPT_DIR} /submit-slurm-job.sh" " $PR_BENCH_SCRIPT " " $device " " $interface " " $cluster " )
74+ pr_job_id=$( cat " pr/${log_slug} .slurm_job_id" )
75+ echo " PR job submitted: $pr_job_id "
76+
77+ echo " Submitting master benchmark..."
78+ (cd master && SUBMIT_ONLY=1 bash " ${SCRIPT_DIR} /submit-slurm-job.sh" " $PR_BENCH_SCRIPT " " $device " " $interface " " $cluster " )
79+ master_job_id=$( cat " master/${log_slug} .slurm_job_id" )
80+ echo " Master job submitted: $master_job_id "
81+
82+ echo " Both SLURM jobs submitted — running concurrently on compute nodes."
83+ echo " Monitoring sequentially to conserve login node memory."
84+
85+ # Phase 2: Monitor sequentially (one at a time on login node)
86+ # On Phoenix 'embers' a long benchmark job can be preempted (PreemptMode=CANCEL,
87+ # so it is killed rather than requeued). On preemption (run_monitored exit 76)
88+ # resubmit a fresh job in the same tree and re-monitor, bounded by
89+ # MAX_PREEMPT_RESUBMITS (the 480m job timeout is the real backstop). Note: a
90+ # resubmitted job no longer overlaps its counterpart, slightly reducing
91+ # same-load fairness -- still preferable to failing the run on an infra preempt.
92+ : " ${MAX_PREEMPT_RESUBMITS:= 10} "
93+ monitor_bench_with_resubmit () { # arg: <dir> (pr|master); sets BENCH_MON_RC
94+ local dir=" $1 "
95+ local out=" ${dir} /${log_slug} .out"
96+ local jobid attempt=0 rc
97+ jobid=$( cat " ${dir} /${log_slug} .slurm_job_id" )
98+ while : ; do
99+ rc=0
100+ bash " ${SCRIPT_DIR} /run_monitored_slurm_job.sh" " $jobid " " $out " || rc=$?
101+ if [ " $rc " -ne 76 ]; then
102+ BENCH_MON_RC=" $rc "
103+ return
104+ fi
105+ if [ " $attempt " -ge " $MAX_PREEMPT_RESUBMITS " ]; then
106+ echo " ::error::${dir} benchmark preempted ${MAX_PREEMPT_RESUBMITS} x without completing; giving up."
107+ BENCH_MON_RC=1
108+ return
109+ fi
110+ attempt=$(( attempt + 1 ))
111+ echo " ::warning::${dir} benchmark job $jobid was preempted; resubmitting (attempt ${attempt} /${MAX_PREEMPT_RESUBMITS} )."
112+ rm -f " $out "
113+ ( cd " $dir " && SUBMIT_ONLY=1 bash " ${SCRIPT_DIR} /submit-slurm-job.sh" " $PR_BENCH_SCRIPT " " $device " " $interface " " $cluster " )
114+ jobid=$( cat " ${dir} /${log_slug} .slurm_job_id" )
115+ echo " ${dir} benchmark resubmitted as job $jobid "
116+ done
117+ }
107118
108- echo " "
109- echo " === Monitoring master job $master_job_id ==="
110- monitor_bench_with_resubmit master
111- master_exit=$BENCH_MON_RC
112- if [ " $master_exit " -ne 0 ]; then
113- echo " Master job exited with code: $master_exit "
114- tail -n 50 " master/${job_slug} .out" 2> /dev/null || echo " Could not read master log"
115- else
116- echo " Master job completed successfully"
119+ echo " "
120+ echo " === Monitoring PR job $pr_job_id ==="
121+ monitor_bench_with_resubmit pr
122+ pr_exit=$BENCH_MON_RC
123+ if [ " $pr_exit " -ne 0 ]; then
124+ echo " PR job exited with code: $pr_exit "
125+ tail -n 50 " pr/${log_slug} .out" 2> /dev/null || echo " Could not read PR log"
126+ # The PR benchmark run genuinely failed (cases crashed/hung/SIGTERM'd, not a
127+ # monitor false-positive -- run_monitored_slurm_job.sh re-checks sacct). Fail
128+ # the job instead of falling through to the YAML-exists check, which would let
129+ # a broken PR pass green as long as a partial YAML was written. Scoped to PR
130+ # only: a master/baseline infra flake stays a warning and does not red-cross.
131+ exit 1
132+ else
133+ echo " PR job completed successfully"
134+ fi
135+
136+ echo " "
137+ echo " === Monitoring master job $master_job_id ==="
138+ monitor_bench_with_resubmit master
139+ master_exit=$BENCH_MON_RC
140+ if [ " $master_exit " -ne 0 ]; then
141+ echo " Master job exited with code: $master_exit "
142+ tail -n 50 " master/${log_slug} .out" 2> /dev/null || echo " Could not read master log"
143+ else
144+ echo " Master job completed successfully"
145+ fi
117146fi
118147
119148# --- Phase 3: Verify outputs ---
@@ -146,15 +175,15 @@ if [ ! -f "$pr_yaml" ]; then
146175 echo " ERROR: PR benchmark output not found: $pr_yaml "
147176 ls -la pr/ || true
148177 echo " "
149- tail -n 100 " pr/${job_slug } .out" 2> /dev/null || echo " Could not read PR log"
178+ tail -n 100 " pr/${log_slug } .out" 2> /dev/null || echo " Could not read PR log"
150179 exit 1
151180fi
152181
153182if [ ! -f " $master_yaml " ]; then
154183 echo " ERROR: Master benchmark output not found: $master_yaml "
155184 ls -la master/ || true
156185 echo " "
157- tail -n 100 " master/${job_slug } .out" 2> /dev/null || echo " Could not read master log"
186+ tail -n 100 " master/${log_slug } .out" 2> /dev/null || echo " Could not read master log"
158187 exit 1
159188fi
160189
0 commit comments