-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbrun
More file actions
executable file
·257 lines (226 loc) · 7.77 KB
/
brun
File metadata and controls
executable file
·257 lines (226 loc) · 7.77 KB
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#!/bin/bash
(( DEBUG )) && set -x
function nop()
{
return -1
}
declare -i VERBOSE=${VERBOSE:-0}
typeset -r -A QUEUES=( ['v100']='v100-localqueue'
['a100']='a100-localqueue'
['h100']='h100-localqueue'
['none']='dummy-localqueue')
typeset -r -i CONTEXT=${CONTEXT:-1}
typeset -r DEFAULT_GPU='v100'
typeset -r GPU=${GPU:-"${DEFAULT_GPU}"}
# name must be lower case and no funny characters
typeset -r NAME=${NAME:-"job"}
typeset -r JOB_ID=${JOBID:-$$}
typeset -r JOB=${JOB:-"${NAME}-${GPU}-${JOB_ID}"}
typeset -r JOBS_DIR_NAME="${JOBS_DIR_NAME:-jobs}"
typeset -r JOBS_DIR="${JOBS_DIR:-$(pwd)/${JOBS_DIR_NAME}}"
typeset -r CONTAINER="${JOB}-container"
typeset -i -r MAX_SEC="${MAX_SEC:-$(( 60 * 15 ))}"
typeset -i -r JOB_DELETE="${JOB_DELETE:-1}"
typeset -i -r WAIT="${WAIT:-1}"
typeset -i -r JOB_WAIT_TMOUT="${JOB_WAIT_TMOUT:-$(( MAX_SEC * 2 ))}"
typeset OUT=/dev/null
typeset -i GPU_NUMREQ="${GPU_NUMREQ:-1}"
typeset -i GPU_NUMLIM="${GPU_NUMREQ:-1}"
typeset RESOURCES_YAML=""
typeset COMMAND_YAML=""
#typeset IMAGE=${IMAGE:-"image-registry.openshift-image-registry.svc:5000/redhat-ods-applications/minimal-gpu:2025.1"}
typeset IMAGE="${IMAGE:-"image-registry.openshift-image-registry.svc:5000/redhat-ods-applications/csw-run-f25:latest"}"
typeset CONTEXT_DIR="${CONTEXT_DIR:-$(pwd)}"
typeset OUTPUT_DIR="${JOBS_DIR}/${JOB}"
typeset -r GETLIST="${OUTPUT_DIR}/getlist"
typeset -i rc=0 complete_pid=0 failed_pid=0
typeset POD='' POD_INFO=''
# default behavior relies on HOSTNAME set to pod name of where we are running (true on openshift RHOAI)
typeset DEVPOD_NAME=${DEVPOD_NAME:-$HOSTNAME}
typeset DEVCONTAINER=$(oc get pods/${DEVPOD_NAME} -o=jsonpath="{.spec.containers[0].name}")
[[ $1 == -h ]] && {
cat <<EOF
brun [-h] <command line>
brun creates and submits a batch job to a GPU batch queue. The arguments are treated as a bash command line
that will execute as the batch job. The behaviour of the job submission can be controlled via several
environment variables. Please, feel free to improve this documentation and fix
bugs ;-). By default the files and subdirectories of your current working directory form a context for
the job. The context is copied to the container in which the batch job will execute. Thus the commandline
can reference files in the directory. Additinoally, files created in the working directory of commandline
in the container will be copied back so that you can inspect output of your job (eg. profiles, logs and outputs).
These files and directories are placed in a directory make in job specific subdirectory of a directory called jobs.
Eg.
1. The simple usage run a binary that exsits in the current directory on the default GPU type
$ br ./hello
Hello from CPU
Hello from GPU
Hello from GPU
Hello from GPU
Hello from GPU
Hello from GPU
Hello from GPU
Hello from GPU
Hello from GPU
Hello from GPU
Hello from GPU
RUNDIR: jobs/job-v100-9215
Note by default brun waits for the job to complete and displays after the standard output and error of the command line.
After that it display the directory where the outputs for the job where copied.
See repository README.md for more documentation and examples.
EOF
exit 0
}
(( VERBOSE )) && OUT=/dev/stderr
[[ ! -v QUEUES[$GPU] ]] && {
echo "ERROR: unsupported GPU $GPU : no queue found" > /dev/stderr
exit -1
}
typeset QUEUE=${QUEUES[$GPU]}
if [[ "$GPU" == 'none' ]]; then
RESOURCES_YAML=\
" resources:
requests:
cpu: \"1\"
memory: \"1Gi\"
limits:
cpu: \"1\"
memory: \"1Gi\"
"
else
RESOURCES_YAML=\
" resources:
requests:
nvidia.com/gpu: ${GPU_NUMREQ}
limits:
nvidia.com/gpu: ${GPU_NUMLIM}
"
fi
cmd="$@"
[[ -z "$cmd" ]] && {
cat > /dev/stderr <<EOF
USAGE: ${0##*/} <command line>
Runs the specified command on a GPU node
Supported GPU types are: ${!QUEUES[@]}
Default GPU type is $DEFAULT_GPU
To overide default set GPU enviroment variable eg.
$ GPU=a100 ${0##*/} nvidia-smi
EOF
exit 0
}
(( VERBOSE )) && {
echo "GPU:${GPU} JOB:${JOB} CONTAINER:${CONTAINER} QUEUE:$QUEUE" \
"MAX_SEC:${MAX_SEC} WAIT:${WAIT} JOB_WAIT:${JOB_WAIT} JOB_DELETE:${JOB_DELETE}" \
"CONTEXT_DIR:${CONTEXT_DIR} OUTPUT_DIR=${OUTPUT_DIR}" > /dev/stderr
echo "CMD: $cmd" > /dev/stderr
}
if (( CONTEXT )); then
[[ ! -d $CONTEXT_DIR ]] && {
echo "ERROR: CONTEXT_DIR: $CONTEXT_DIR is not a directory"
exit -1
}
[[ -a ${OUTPUT_DIR} ]] && {
echo "ERROR: $OUTPUT_DIR directory already exits"
exit -1
}
! mkdir -p ${OUTPUT_DIR} && {
echo "ERROR: Failed to make output dir"
exit -1
}
# create list of context to send files subdirectories for
# of $CONTEXT_DIR excluding the ${JOBS_DIR} if a direct
# child of $CONTEXT_DIR
(
jdir="${JOBS_DIR#${CONTEXT_DIR}/}"
[[ "$jdir" != "$JOBS_DIR" ]] && {
jdir="./${jdir}"
}
cd ${CONTEXT_DIR}
find . -mindepth 1 -maxdepth 1 -not -path ${jdir} -prune > ${GETLIST}
)
(( ! VERBOSE )) && {
RSYNC_VERBOSE="-q"
}
COMMAND_YAML=$(cat <<EOF
command: ["/bin/sh",
"-c",
"export RSYNC_RSH='oc rsh -c ${DEVCONTAINER}';
mkdir ${JOB} &&
rsync ${RSYNC_VERBOSE} --archive --no-owner --no-group --omit-dir-times --numeric-ids ${DEVPOD_NAME}:${GETLIST} ${JOB}/getlist >/dev/null 2>&1 &&
rsync ${RSYNC_VERBOSE} -r --archive --no-owner --no-group --omit-dir-times --numeric-ids --files-from=${JOB}/getlist ${DEVPOD_NAME}:${CONTEXT_DIR}/ ${JOB}/ &&
find ${JOB} -mindepth 1 -maxdepth 1 > ${JOB}/gotlist &&
cd ${JOB} && ${cmd} |& tee $JOB.log; cd ..;
rsync ${RSYNC_VERBOSE} --archive --no-owner --no-group --omit-dir-times --no-relative --numeric-ids --exclude-from=${JOB}/gotlist ${JOB} ${DEVPOD_NAME}:${JOBS_DIR}"]
EOF
)
else
COMMAND_YAML=$(cat <<EOF
command: ["/bin/sh", "-c", "${cmd}"]
EOF
)
fi
YAML=$(cat <<EOF
apiVersion: batch/v1
kind: Job
metadata:
name: ${JOB}
labels:
kueue.x-k8s.io/queue-name: ${QUEUE}
test_name: kueue_test
spec:
parallelism: 1
completions: 1
backoffLimit: 0
activeDeadlineSeconds: ${MAX_SEC}
template:
spec:
restartPolicy: Never
maximumExecutionTimeSeconds: ${MAX_SEC}
containers:
- name: ${CONTAINER}
image: ${IMAGE}
${COMMAND_YAML}
${RESOURCES_YAML}
EOF
)
(( VERBOSE )) && echo "$YAML"
oc apply -f - >$OUT 2>&1 <<< "$YAML"
(( VERBOSE )) && {
echo "Job Created: $JOB" > $OUT
oc describe job $JOB > $OUT 2>&1
}
if (( WAIT )); then
trap nop SIGINT
oc wait --for=condition=complete --timeout=${JOB_WAIT_TMOUT}s jobs/$JOB >$OUT 2>&1 & complete_pid=$!
oc wait --for=condition=failed --timeout=${JOB_WAIT_TMOUT}s jobs/$JOB >$OUT 2>&1 && exit -1 & failed_pid=$!
echo "Waiting $JOB for condition: complete or failed" > $OUT
wait -n $complete_pid $failed_pid
rc=$?
if (( $rc == 0 )); then
echo "$JOB: Completed" >$OUT
else
POD=$(oc get pods -l job-name=$JOB -o name)
POD_INFO="$(oc describe $POD 2>&1)"
echo "*** ERROR: $JOB FAILED!!!! *****"
if [[ -n $POD ]]; then
echo "$POD:"
grep -E 'State:|Reason:|Exit Code:|Started:|Finished:' <<<"$POD_INFO"
else
echo "ERROR: Execution exceeded $MAX_SEC ???"
fi
fi
oc logs jobs/$JOB
(( JOB_DELETE )) && {
oc delete jobs/$JOB >$OUT 2>&1
}
(( CONTEXT )) && {
[[ -a ${OUTPUT_DIR}/getlist ]] && rm ${OUTPUT_DIR}/getlist
echo RUNDIR: ${OUTPUT_DIR#$(pwd)/} > /dev/stderr
}
else
echo "WARNING: Not waiting for $JOB to finish you must manually delete with 'gel $JOB'" > /dev/stderr
if (( CONTEXT )); then
echo $JOB ${OUTPUT_DIR#$(pwd)/}
else
echo $JOB
fi
fi