File size: 5,769 Bytes
3e02ab8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#!/bin/bash
# Run NequIP training locally or submit it to Slurm from one YAML file.
set -euo pipefail

DEMO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
NEQUIP_DIR="$(cd "$DEMO_DIR/.." && pwd)"
PARSER="$DEMO_DIR/_parse_config.py"
CONFIG=""
SUBMIT=false

while [[ $# -gt 0 ]]; do
    case "$1" in
        --config) CONFIG="$2"; shift 2 ;;
        --config=*) CONFIG="${1#*=}"; shift ;;
        --submit) SUBMIT=true; shift ;;
        -h|--help)
            echo "Usage: bash demo/run.sh --config configs/<name>.yaml [--submit]"
            echo "launch.mode: auto uses matching resources or submits when needed."
            echo "launch.mode: local runs directly; submit always submits to Slurm."
            exit 0
            ;;
        *) echo "Unknown argument: $1" >&2; exit 2 ;;
    esac
done

[[ -n "$CONFIG" ]] || { echo "Please specify --config configs/<name>.yaml" >&2; exit 2; }
[[ "$CONFIG" = /* ]] || CONFIG="$DEMO_DIR/$CONFIG"
[[ -f "$CONFIG" ]] || { echo "Config not found: $CONFIG" >&2; exit 2; }

if [[ -z "${CONDA_PREFIX:-}" ]]; then
    echo "Activate a OneScience MatChem conda environment before running this script." >&2
    exit 2
fi
if [[ -z "${ONESCIENCE_MODELS_DIR:-}" || -z "${ONESCIENCE_DATASETS_DIR:-}" ]]; then
    echo "Set ONESCIENCE_MODELS_DIR and ONESCIENCE_DATASETS_DIR before running this script." >&2
    exit 2
fi
export MATCHEM_CONDA_NAME="${MATCHEM_CONDA_NAME:-$(basename "$CONDA_PREFIX")}"

NAME="$(python3 "$PARSER" "$CONFIG" name)"
eval "$(python3 "$PARSER" "$CONFIG" launch)"
eval "$(python3 "$PARSER" "$CONFIG" slurm)"
ENV_EXPORTS="$(python3 "$PARSER" "$CONFIG" env)"
if [[ "$RUN_MODE" == "submit" ]]; then
    SUBMIT=true
fi

if [[ "$RUN_MODE" == "auto" ]] && ! $SUBMIT; then
    IN_SLURM_ALLOCATION=false
    AVAILABLE_NODES=1
    if [[ -n "${SLURM_JOB_ID:-}" ]]; then
        IN_SLURM_ALLOCATION=true
        AVAILABLE_NODES="${SLURM_NNODES:-${SLURM_JOB_NUM_NODES:-1}}"
        if ! [[ "$AVAILABLE_NODES" =~ ^[1-9][0-9]*$ ]]; then
            echo "Cannot determine allocated nodes from Slurm: $AVAILABLE_NODES" >&2
            exit 2
        fi
    fi

    AVAILABLE_GPUS="$(
        python3 -c 'import torch; print(torch.cuda.device_count() if torch.cuda.is_available() else 0)' \
            2>/dev/null || true
    )"
    if ! [[ "$AVAILABLE_GPUS" =~ ^[0-9]+$ ]]; then
        AVAILABLE_GPUS=0
    fi

    RESOURCE_MISMATCH=""
    if (( AVAILABLE_NODES < NODES )); then
        RESOURCE_MISMATCH="the config requests $NODES nodes but only $AVAILABLE_NODES are available"
    elif (( AVAILABLE_GPUS < GPUS_PER_NODE )); then
        RESOURCE_MISMATCH="the config requests $GPUS_PER_NODE DCUs per node but only $AVAILABLE_GPUS are visible"
    fi

    if [[ -n "$RESOURCE_MISMATCH" ]]; then
        if ! command -v sbatch >/dev/null 2>&1; then
            echo "Current resources are insufficient: $RESOURCE_MISMATCH, and sbatch is unavailable." >&2
            exit 2
        fi
        if $IN_SLURM_ALLOCATION; then
            echo "Current Slurm allocation is insufficient: $RESOURCE_MISMATCH. Submitting a new Slurm job."
        else
            echo "Current resources are insufficient: $RESOURCE_MISMATCH. Submitting to Slurm."
        fi
        SUBMIT=true
    else
        echo "Current resources satisfy the config: nodes=$NODES, DCUs/node=$GPUS_PER_NODE."
    fi
fi

TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
OUTPUT_ROOT="${ONESCIENCE_NEQUIP_OUTPUT_ROOT:-$NEQUIP_DIR/outputs}"
OUTPUT_DIR="$OUTPUT_ROOT/${NAME}_${TIMESTAMP}"
mkdir -p "$OUTPUT_DIR/checkpoints"
cp "$CONFIG" "$OUTPUT_DIR/source_config.yaml"
python3 "$PARSER" "$CONFIG" training-config > "$OUTPUT_DIR/config.yaml"

if $SUBMIT; then
    SLURM_SCRIPT="$OUTPUT_DIR/submit.sh"
    cat > "$SLURM_SCRIPT" <<EOF
#!/bin/bash
#SBATCH --job-name=$NAME
#SBATCH --partition=$PARTITION
#SBATCH --nodes=$NODES
#SBATCH --ntasks-per-node=$GPUS_PER_NODE
#SBATCH --cpus-per-task=$CPUS_PER_TASK
#SBATCH --gres=dcu:$GPUS_PER_NODE
#SBATCH --time=$TIME
#SBATCH --output=$OUTPUT_DIR/slurm_%j.out
#SBATCH --error=$OUTPUT_DIR/slurm_%j.err
EOF
    if [[ -n "$NODELIST" ]]; then
        printf '#SBATCH --nodelist=%s\n' "$NODELIST" >> "$SLURM_SCRIPT"
    fi
    cat >> "$SLURM_SCRIPT" <<EOF

set -euo pipefail
export MATCHEM_CONDA_NAME="$MATCHEM_CONDA_NAME"
export ONESCIENCE_MODELS_DIR="$ONESCIENCE_MODELS_DIR"
export ONESCIENCE_DATASETS_DIR="$ONESCIENCE_DATASETS_DIR"
export HSA_FORCE_FINE_GRAIN_PCIE=1
if (( $GPUS_PER_NODE > 1 )); then
    unset CUDA_VISIBLE_DEVICES HIP_VISIBLE_DEVICES ROCR_VISIBLE_DEVICES
fi
$ENV_EXPORTS
cd "$OUTPUT_DIR"
EOF
    if (( WORLD_SIZE > 1 )); then
        cat >> "$SLURM_SCRIPT" <<EOF
exec srun --kill-on-bad-exit=1 \
    --nodes=$NODES \
    --ntasks=$WORLD_SIZE \
    --ntasks-per-node=$GPUS_PER_NODE \
    python "$NEQUIP_DIR/train.py" "hydra.run.dir=$OUTPUT_DIR"
EOF
    else
        echo "exec python \"$NEQUIP_DIR/train.py\" \"hydra.run.dir=$OUTPUT_DIR\"" >> "$SLURM_SCRIPT"
    fi
    chmod u+x "$SLURM_SCRIPT"
    echo "Submitting NequIP job: $SLURM_SCRIPT"
    sbatch "$SLURM_SCRIPT"
    exit 0
fi

eval "$ENV_EXPORTS"
cd "$OUTPUT_DIR"
if (( WORLD_SIZE > 1 )); then
    if (( NODES > 1 )); then
        if [[ "$RUN_MODE" == "auto" && -n "${SLURM_JOB_ID:-}" ]]; then
            exec srun --kill-on-bad-exit=1 \
                --nodes="$NODES" \
                --ntasks="$WORLD_SIZE" \
                --ntasks-per-node="$GPUS_PER_NODE" \
                python "$NEQUIP_DIR/train.py" "hydra.run.dir=$OUTPUT_DIR"
        fi
        echo "Multi-node NequIP training must be launched through Slurm (--submit)." >&2
        exit 2
    fi
    exec torchrun --standalone --nproc_per_node="$GPUS_PER_NODE" \
        "$NEQUIP_DIR/train.py" "hydra.run.dir=$OUTPUT_DIR"
fi
exec python "$NEQUIP_DIR/train.py" "hydra.run.dir=$OUTPUT_DIR"