11#! /usr/bin/env bash
22#
3- # Germany-only colleague-style LSTM: AgERA5 + FPAR, avg_pool tokenizer, no static context.
4- # Runs the same screening → walk-forward pipeline as the SLURM benchmark ( other torch models).
3+ # Germany colleague-style LSTM: AgERA5 + FPAR, avg_pool tokenizer, no static context.
4+ # Default: submit screening → walk-forward via SLURM (same pipeline as other torch models).
55#
66# Usage (from repo root):
77# cybench/runs/run_de_agera5fpar_lstm.sh all
88# cybench/runs/run_de_agera5fpar_lstm.sh screening --batch my_de_lstm_v1
9- # cybench/runs/run_de_agera5fpar_lstm.sh walk_forward --frozen-dir /path/to/.../2017_2018_2019_2020_2021
10- # cybench/runs/run_de_agera5fpar_lstm.sh all --crop maize --device cpu --hp-trials 5 -- dry-run
9+ # cybench/runs/run_de_agera5fpar_lstm.sh all --local # run poetry on this machine
10+ # cybench/runs/run_de_agera5fpar_lstm.sh all --cpu --dry-run # SLURM on CPU partition
1111#
1212set -euo pipefail
1313
@@ -18,23 +18,28 @@ Usage: run_de_agera5fpar_lstm.sh <screening|walk_forward|all> [options]
1818Phases:
1919 screening HPO on train/val, final fit on train+val, evaluate on held-out test block
2020 walk_forward Rolling forecasts using frozen hyperparameters from screening
21- all screening, then walk_forward (auto-discovers latest screening artifacts )
21+ all screening, then walk-forward (SLURM: wf chained with afterok on screening )
2222
2323Options:
2424 --batch NAME experiment.name / output folder (default: baselines_de_agera5fpar_lstm_v1)
2525 --crop CROP wheat | maize (default: maize)
2626 --country CC ISO country code (default: DE)
2727 --horizon H end_of_sequence (default: middle-of-season → run dirs *_mid_season_*)
28- --device DEV cuda | cpu (default: cuda)
2928 --hp-trials N Optuna trials in screening (default: 20)
30- --frozen-dir PATH Screening split folder with optimal_model.yaml (walk_forward only)
31- --collect After walk_forward, run collect_walk_forward_results.py
32- --dry-run Print commands without executing
29+ --local Run poetry/python on this machine (default: submit SLURM jobs)
30+ --cpu SLURM: main partition, no GPU (CYBENCH_FORCE_CPU=1)
31+ --no-dependency SLURM "all": submit walk-forward without afterok on screening
32+ --frozen-dir PATH Walk-forward only, local mode: explicit screening artifact folder
33+ --collect After walk-forward, run collect_walk_forward_results.py (local only)
34+ --dry-run Print planned commands without executing
3335 -h, --help Show this help
3436
35- Output (relative to repo root):
36- ../output/<batch>/maize_DE_lstm_lf_screening_mid_season_<timestamp>/
37- ../output/<batch>/maize_DE_lstm_lf_walk_forward_mid_season_<timestamp>/
37+ SLURM (default):
38+ Logs: output/screening/ and output/walk_forward/ under repo root
39+ Results: ../output/<batch>/maize_DE_lstm_lf_{screening,walk_forward}_mid_season_<timestamp>/
40+
41+ Local (--local):
42+ Same Hydra overrides; writes directly under ../output/<batch>/
3843EOF
3944}
4045
@@ -48,6 +53,9 @@ HP_TRIALS=20
4853FROZEN_DIR=" "
4954COLLECT=false
5055DRY_RUN=false
56+ USE_SLURM=true
57+ SLURM_CPU=false
58+ SLURM_NO_DEPENDENCY=false
5159
5260if [[ $# -lt 1 ]]; then
5361 usage
@@ -79,6 +87,9 @@ while [[ $# -gt 0 ]]; do
7987 --device) DEVICE=$2 ; shift 2 ;;
8088 --hp-trials) HP_TRIALS=$2 ; shift 2 ;;
8189 --frozen-dir) FROZEN_DIR=$2 ; shift 2 ;;
90+ --local) USE_SLURM=false; shift ;;
91+ --cpu) SLURM_CPU=true; DEVICE=cpu; shift ;;
92+ --no-dependency) SLURM_NO_DEPENDENCY=true; shift ;;
8293 --collect) COLLECT=true; shift ;;
8394 --dry-run) DRY_RUN=true; shift ;;
8495 -h|--help) usage; exit 0 ;;
@@ -92,11 +103,17 @@ done
92103
93104SCRIPT_DIR=" $( cd " $( dirname " ${BASH_SOURCE[0]} " ) " && pwd) "
94105REPO_ROOT=" $( cd " ${SCRIPT_DIR} /../.." && pwd) "
106+ SLURM_DIR=" ${REPO_ROOT} /cybench/runs/slurm"
107+ SUBMIT_ARRAY=" ${SLURM_DIR} /submit_array.sh"
95108cd " ${REPO_ROOT} "
96109
97110OUTPUT_ROOT=" ${REPO_ROOT} /../output"
98111BATCH_DIR=" ${OUTPUT_ROOT} /${BATCH} "
99- if [[ " ${DRY_RUN} " != true ]]; then
112+ MANIFEST_DIR=" ${SLURM_DIR} /manifests/${BATCH} "
113+ MANIFEST=" ${MANIFEST_DIR} /benchmark_jobs_gpu.txt"
114+ OVERRIDES_FILE=" ${MANIFEST_DIR} /extra_overrides.txt"
115+
116+ if [[ " ${DRY_RUN} " != true && " ${USE_SLURM} " == false ]]; then
100117 mkdir -p " ${BATCH_DIR} "
101118 OUTPUT_ROOT=" $( cd " ${OUTPUT_ROOT} " && pwd) "
102119 BATCH_DIR=" ${OUTPUT_ROOT} /${BATCH} "
@@ -112,8 +129,29 @@ run_cmd() {
112129 fi
113130}
114131
115- # Shared Hydra overrides (must match across screening and walk-forward).
116- shared_overrides () {
132+ # Dataset/model overrides shared by SLURM and local runs (crop/country/model set elsewhere on SLURM).
133+ write_extra_overrides_file () {
134+ mkdir -p " ${MANIFEST_DIR} "
135+ cat > " ${OVERRIDES_FILE} " << EOF
136+ dataset/temporal=no_aggregate
137+ model/torch_model/temporal_encoder/tokenizer=avg_pool
138+ model.torch_model.embed_dim=8
139+ ~dataset.temporal.sources.ndvi
140+ ~dataset.temporal.sources.soil_moisture
141+ dataset.temporal.sources.meteo.select=[tmin,tmax,tavg,prec,rad,et0,vpd]
142+ +process={name:select_context,drop:[year,sos_sin,sos_cos,eos_sin,eos_cos,loc_x,loc_y,loc_z,awc,bulk_density,drainage_class_1,drainage_class_2,drainage_class_3,drainage_class_4,drainage_class_5,drainage_class_6],keep:null}
143+ EOF
144+ }
145+
146+ write_job_manifest () {
147+ mkdir -p " ${MANIFEST_DIR} "
148+ cat > " ${MANIFEST} " << EOF
149+ # ${CROP} ${COUNTRY} colleague-style lstm_lf (AgERA5 + FPAR, avg_pool, no static)
150+ ${CROP} ${COUNTRY} lstm_lf torch yes no yes
151+ EOF
152+ }
153+
154+ shared_local_overrides () {
117155 cat << EOF
118156dataset/crop=${CROP}
119157dataset.country=${COUNTRY}
@@ -134,8 +172,8 @@ experiment.device=${DEVICE}
134172EOF
135173}
136174
137- read_overrides () {
138- mapfile -t OVERRIDES < <( shared_overrides )
175+ read_local_overrides () {
176+ mapfile -t OVERRIDES < <( shared_local_overrides )
139177}
140178
141179horizon_tag () {
@@ -161,37 +199,37 @@ find_frozen_screening_dir() {
161199 return 1
162200}
163201
164- run_screening () {
165- read_overrides
202+ run_local_screening () {
203+ read_local_overrides
166204 local -a cmd=(poetry run python cybench/runs/run_experiments.py)
167205 cmd+=(" ${OVERRIDES[@]} " )
168206 cmd+=(
169207 validation=screening
170208 +hp_search=bayesian
171209 " hp_search.n_trials=${HP_TRIALS} "
172210 )
173- echo " == Screening | ${CROP} /${COUNTRY} | batch=${BATCH} | device=${DEVICE} | out= ${BATCH_DIR }"
211+ echo " == Local screening | ${CROP} /${COUNTRY} | batch=${BATCH} | device=${DEVICE} "
174212 run_cmd " ${cmd[@]} "
175213}
176214
177- run_walk_forward () {
215+ run_local_walk_forward () {
178216 local frozen=$1
179217 if [[ ! -f " ${frozen} /optimal_model.yaml" ]]; then
180218 echo " Missing optimal_model.yaml in ${frozen} " >&2
181219 exit 1
182220 fi
183- read_overrides
221+ read_local_overrides
184222 local -a cmd=(poetry run python cybench/runs/run_experiments.py)
185223 cmd+=(" ${OVERRIDES[@]} " )
186224 cmd+=(
187225 validation=walk_forward
188226 " validation.frozen_screening_dir=${frozen} "
189227 )
190- echo " == Walk -forward | ${CROP} /${COUNTRY} | batch= ${BATCH } | frozen=${frozen} "
228+ echo " == Local walk -forward | ${CROP} /${COUNTRY} | frozen=${frozen} "
191229 run_cmd " ${cmd[@]} "
192230}
193231
194- run_collect () {
232+ run_local_collect () {
195233 local -a cmd=(
196234 poetry run python cybench/runs/analysis/collect_walk_forward_results.py
197235 --batch " ${BATCH} "
@@ -201,32 +239,120 @@ run_collect() {
201239 run_cmd " ${cmd[@]} "
202240}
203241
204- case " ${PHASE} " in
205- screening)
206- run_screening
207- ;;
208- walk_forward)
209- if [[ -z " ${FROZEN_DIR} " ]]; then
210- FROZEN_DIR=$( find_frozen_screening_dir)
211- fi
212- run_walk_forward " ${FROZEN_DIR} "
213- if [[ " ${COLLECT} " == true ]]; then
214- run_collect
215- fi
216- ;;
217- all)
218- run_screening
219- if [[ " ${DRY_RUN} " == true ]]; then
220- echo " (dry-run: would auto-discover frozen screening dir for walk-forward)"
221- run_cmd echo walk_forward skipped in dry-run after screening
222- else
223- FROZEN_DIR=$( find_frozen_screening_dir)
224- run_walk_forward " ${FROZEN_DIR} "
242+ submit_slurm_phase () {
243+ local phase=$1
244+ local dependency=${2:- }
245+ local -a cmd=(
246+ " ${SUBMIT_ARRAY} " " ${phase} " " ${MANIFEST} "
247+ --batch " ${BATCH} "
248+ --array 0
249+ --group gpu
250+ )
251+ if [[ " ${SLURM_CPU} " == true ]]; then
252+ cmd+=(--cpu)
253+ else
254+ cmd+=(--gpu)
255+ fi
256+ if [[ -n " ${dependency} " ]]; then
257+ cmd+=(--dependency " ${dependency} " )
258+ fi
259+ echo " == SLURM ${phase} | ${CROP} /${COUNTRY} | batch=${BATCH} | horizon=${HORIZON} "
260+ if [[ " ${DRY_RUN} " == true ]]; then
261+ echo " export PREDICTION_HORIZON=${HORIZON} "
262+ echo " export HP_TRIALS=${HP_TRIALS} "
263+ echo " export CYBENCH_EXTRA_OVERRIDES_FILE=${OVERRIDES_FILE} "
264+ run_cmd " ${cmd[@]} "
265+ SLURM_LAST_JOB_ID=" "
266+ return 0
267+ fi
268+ if ! command -v sbatch > /dev/null 2>&1 ; then
269+ echo " sbatch not found. Use --local or run on a cluster login node." >&2
270+ exit 1
271+ fi
272+ local out
273+ out=$( " ${cmd[@]} " 2>&1 )
274+ echo " ${out} "
275+ SLURM_LAST_JOB_ID=$( echo " ${out} " | awk -F= ' /^job_id=/{print $2}' )
276+ if [[ -z " ${SLURM_LAST_JOB_ID} " ]]; then
277+ echo " Failed to parse SLURM job id from submit_array output" >&2
278+ exit 1
279+ fi
280+ }
281+
282+ SLURM_LAST_JOB_ID=" "
283+
284+ prepare_slurm_assets () {
285+ write_job_manifest
286+ write_extra_overrides_file
287+ export PREDICTION_HORIZON=" ${HORIZON} "
288+ export HP_TRIALS=" ${HP_TRIALS} "
289+ export CYBENCH_EXTRA_OVERRIDES_FILE=" ${OVERRIDES_FILE} "
290+ export CYBENCH_EXPERIMENT_NAME=" ${BATCH} "
291+ echo " Manifest: ${MANIFEST} "
292+ echo " Overrides: ${OVERRIDES_FILE} "
293+ echo " Output batch: ../output/${BATCH} /"
294+ }
295+
296+ run_slurm_screening () {
297+ prepare_slurm_assets
298+ submit_slurm_phase screening
299+ }
300+
301+ run_slurm_walk_forward () {
302+ local dependency=${1:- }
303+ prepare_slurm_assets
304+ submit_slurm_phase walk_forward " ${dependency} "
305+ }
306+
307+ run_slurm_all () {
308+ local wf_dep=" "
309+ prepare_slurm_assets
310+ submit_slurm_phase screening
311+ if [[ " ${SLURM_NO_DEPENDENCY} " != true && -n " ${SLURM_LAST_JOB_ID} " ]]; then
312+ wf_dep=" afterok:${SLURM_LAST_JOB_ID} "
313+ fi
314+ submit_slurm_phase walk_forward " ${wf_dep} "
315+ if [[ " ${COLLECT} " == true ]]; then
316+ echo " "
317+ echo " Note: --collect is not submitted as a SLURM job. After walk-forward completes, run:"
318+ echo " poetry run python cybench/runs/analysis/collect_walk_forward_results.py \\ "
319+ echo " --batch ${BATCH} --output-dir ${OUTPUT_ROOT} /paper_${BATCH} "
320+ fi
321+ }
322+
323+ if [[ " ${USE_SLURM} " == true ]]; then
324+ case " ${PHASE} " in
325+ screening) run_slurm_screening ;;
326+ walk_forward) run_slurm_walk_forward ;;
327+ all) run_slurm_all ;;
328+ esac
329+ else
330+ case " ${PHASE} " in
331+ screening)
332+ run_local_screening
333+ ;;
334+ walk_forward)
335+ if [[ -z " ${FROZEN_DIR} " ]]; then
336+ FROZEN_DIR=$( find_frozen_screening_dir)
337+ fi
338+ run_local_walk_forward " ${FROZEN_DIR} "
225339 if [[ " ${COLLECT} " == true ]]; then
226- run_collect
340+ run_local_collect
227341 fi
228- fi
229- ;;
230- esac
342+ ;;
343+ all)
344+ run_local_screening
345+ if [[ " ${DRY_RUN} " == true ]]; then
346+ echo " (dry-run: would auto-discover frozen screening dir for walk-forward)"
347+ else
348+ FROZEN_DIR=$( find_frozen_screening_dir)
349+ run_local_walk_forward " ${FROZEN_DIR} "
350+ if [[ " ${COLLECT} " == true ]]; then
351+ run_local_collect
352+ fi
353+ fi
354+ ;;
355+ esac
356+ fi
231357
232358echo " Done."
0 commit comments