#!/usr/bin/env bash # # Train the assessment + smoking + alcohol extra-information experiment. # # Fixed model: # TrajMixer + all_future + relative + Weibull + timed disease history + sex # # Extra information: # - 65 routine assessment/body/laboratory variables # - smoking # - alcohol # - BMI is already included in the assessment variables # - continuous values use train-split RobustScaler statistics # # A6000 48 GB default: # batch_size=256 # # Each task uses one GPU. Tasks assigned to the same GPU run sequentially; # different GPUs run in parallel. # # Examples: # bash train_extra_info_assessment_all_multiseed_linux.sh --gpus 0 # bash train_extra_info_assessment_all_multiseed_linux.sh --gpus 0,1,2 # bash train_extra_info_assessment_all_multiseed_linux.sh \ # --gpus 0 --seeds 42 --dry-run # set -uo pipefail SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" GPU_CSV="" SEED_CSV="42,43,44" NUM_WORKERS=4 BATCH_SIZE=256 PYTHON_BIN="${PYTHON_BIN:-python}" CAMPAIGN_NAME="extra_info_assessment_smoking_alcohol_robust_multiseed" DRY_RUN=0 ENTRYPOINT="$SCRIPT_DIR/train_all_future.py" EXTRA_INFO_TYPES_FILE="$SCRIPT_DIR/extra_info_types_assessment_smoking_alcohol.txt" usage() { cat <<'EOF' Usage: bash train_extra_info_assessment_all_multiseed_linux.sh \ --gpus GPU_LIST [options] Required: --gpus LIST Comma-separated GPU ids, for example 0,1,2. Options: --seeds LIST Comma-separated seeds (default: 42,43,44). --batch-size N Batch size per task (default: 256). --num-workers N DataLoader workers per task (default: 4). --python PATH Python executable (default: $PYTHON_BIN or python). --campaign NAME Output campaign name. --dry-run Print commands without creating files or training. -h, --help Show this help message. Fixed experiment settings: architecture traj_mixer_v5 target all_future time mode relative distribution weibull disease history timed sex enabled by the model extra information extra_info_types_assessment_smoking_alcohol.txt continuous scaling robust (training-subset median/IQR) A6000 48 GB default: batch_size 256 Outputs: runs//seed_/traj_mixer_v5/... batch_logs//seed_/assessment_smoking_alcohol.log EOF } while (($# > 0)); do case "$1" in --gpus) [[ $# -ge 2 ]] || { echo "ERROR: --gpus requires a value." >&2 exit 2 } GPU_CSV="$2" shift 2 ;; --seeds) [[ $# -ge 2 ]] || { echo "ERROR: --seeds requires a value." >&2 exit 2 } SEED_CSV="$2" shift 2 ;; --batch-size) [[ $# -ge 2 ]] || { echo "ERROR: --batch-size requires a value." >&2 exit 2 } BATCH_SIZE="$2" shift 2 ;; --num-workers) [[ $# -ge 2 ]] || { echo "ERROR: --num-workers requires a value." >&2 exit 2 } NUM_WORKERS="$2" shift 2 ;; --python) [[ $# -ge 2 ]] || { echo "ERROR: --python requires a value." >&2 exit 2 } PYTHON_BIN="$2" shift 2 ;; --campaign) [[ $# -ge 2 ]] || { echo "ERROR: --campaign requires a value." >&2 exit 2 } CAMPAIGN_NAME="$2" shift 2 ;; --dry-run) DRY_RUN=1 shift ;; -h|--help) usage exit 0 ;; *) echo "ERROR: unknown argument: $1" >&2 usage >&2 exit 2 ;; esac done [[ -n "$GPU_CSV" ]] || { echo "ERROR: --gpus is required." >&2 usage >&2 exit 2 } [[ -n "$SEED_CSV" ]] || { echo "ERROR: --seeds must not be empty." >&2 exit 2 } [[ "$BATCH_SIZE" =~ ^[1-9][0-9]*$ ]] || { echo "ERROR: --batch-size must be a positive integer." >&2 exit 2 } [[ "$NUM_WORKERS" =~ ^[0-9]+$ ]] || { echo "ERROR: --num-workers must be a non-negative integer." >&2 exit 2 } [[ "$CAMPAIGN_NAME" =~ ^[A-Za-z0-9._-]+$ ]] || { echo "ERROR: --campaign may contain only letters, numbers, ., _, and -." >&2 exit 2 } [[ -f "$ENTRYPOINT" ]] || { echo "ERROR: missing training entrypoint: $ENTRYPOINT" >&2 exit 2 } [[ -f "$EXTRA_INFO_TYPES_FILE" ]] || { echo "ERROR: missing extra-info file: $EXTRA_INFO_TYPES_FILE" >&2 exit 2 } command -v "$PYTHON_BIN" >/dev/null 2>&1 || { echo "ERROR: Python executable not found: $PYTHON_BIN" >&2 exit 2 } IFS=',' read -r -a GPU_IDS <<< "$GPU_CSV" declare -A SEEN_GPUS=() for gpu in "${GPU_IDS[@]}"; do [[ -n "$gpu" && "$gpu" =~ ^[A-Za-z0-9._:-]+$ ]] || { echo "ERROR: invalid GPU id: $gpu" >&2 exit 2 } [[ -z "${SEEN_GPUS[$gpu]+x}" ]] || { echo "ERROR: duplicate GPU id: $gpu" >&2 exit 2 } SEEN_GPUS["$gpu"]=1 done IFS=',' read -r -a SEEDS <<< "$SEED_CSV" declare -A SEEN_SEEDS=() for seed in "${SEEDS[@]}"; do [[ "$seed" =~ ^[0-9]+$ ]] || { echo "ERROR: invalid seed: $seed" >&2 exit 2 } [[ -z "${SEEN_SEEDS[$seed]+x}" ]] || { echo "ERROR: duplicate seed: $seed" >&2 exit 2 } SEEN_SEEDS["$seed"]=1 done RUNS_ROOT="$SCRIPT_DIR/runs/$CAMPAIGN_NAME" LOG_ROOT="$SCRIPT_DIR/batch_logs/$CAMPAIGN_NAME" if ((!DRY_RUN)); then mkdir -p "$RUNS_ROOT" "$LOG_ROOT" fi print_command() { printf '%q ' "$@" printf '\n' } run_job() { local seed="$1" local gpu="$2" local seed_runs_root="$RUNS_ROOT/seed_$seed" local seed_log_root="$LOG_ROOT/seed_$seed" local log_file="$seed_log_root/assessment_smoking_alcohol.log" local -a command=( "$PYTHON_BIN" -u "$ENTRYPOINT" --runs_root "$seed_runs_root" --seed "$seed" --batch_size "$BATCH_SIZE" --num_workers "$NUM_WORKERS" --device cuda --model_architecture traj_mixer_v5 --time_mode relative --dist_mode weibull --disease_history_mode timed --extra_info_types_file "$EXTRA_INFO_TYPES_FILE" ) if ((!DRY_RUN)); then mkdir -p "$seed_runs_root" "$seed_log_root" fi echo "[$(date '+%F %T')] START seed=$seed gpu=$gpu batch=$BATCH_SIZE" echo " log=$log_file" if ((DRY_RUN)); then printf ' CUDA_VISIBLE_DEVICES=%q ' "$gpu" print_command "${command[@]}" return 0 fi if CUDA_VISIBLE_DEVICES="$gpu" PYTHONUNBUFFERED=1 \ "${command[@]}" >"$log_file" 2>&1; then echo "[$(date '+%F %T')] DONE seed=$seed gpu=$gpu" return 0 else local exit_code=$? echo "[$(date '+%F %T')] FAIL seed=$seed gpu=$gpu exit=$exit_code" >&2 echo " See: $log_file" >&2 return "$exit_code" fi } worker() { local slot="$1" local gpu="${GPU_IDS[$slot]}" local seed_index local failed=0 for ((seed_index = slot; seed_index < ${#SEEDS[@]}; seed_index += ${#GPU_IDS[@]})); do run_job "${SEEDS[$seed_index]}" "$gpu" || failed=1 done return "$failed" } echo "Campaign: $CAMPAIGN_NAME" echo "Seeds: ${SEEDS[*]}" echo "GPUs: ${GPU_IDS[*]}" echo "Batch size: $BATCH_SIZE" echo "Extra-info file: $EXTRA_INFO_TYPES_FILE" echo "Total tasks: ${#SEEDS[@]}" echo "Runs root: $RUNS_ROOT" echo "Log root: $LOG_ROOT" if command -v nvidia-smi >/dev/null 2>&1; then echo "Selected GPU inventory:" for gpu in "${GPU_IDS[@]}"; do nvidia-smi \ --id="$gpu" \ --query-gpu=index,name,memory.total \ --format=csv,noheader \ 2>/dev/null || true done fi echo declare -a WORKER_PIDS=() for ((slot = 0; slot < ${#GPU_IDS[@]}; slot++)); do worker "$slot" & WORKER_PIDS+=("$!") done overall_status=0 for pid in "${WORKER_PIDS[@]}"; do wait "$pid" || overall_status=1 done if ((overall_status != 0)); then echo "One or more training tasks failed. Inspect logs under: $LOG_ROOT" >&2 exit 1 fi if ((DRY_RUN)); then echo "Dry run completed successfully." else echo "All assessment + smoking + alcohol tasks completed successfully." fi