Slurm array job with at most one concurrent job per node

Viewed 551

Problem

I have hundreds of files, for each i'd like to run a job with a fixed number of cores (let's say -c4) so that at no time more than one of these jobs run on any of the nodes.

(Reason if you're interested: complicated job setup out of my control. Each job starts a bunch of servers on hard-coded ports. These clash if run concurrently on one node :-/ (Yepp, don't tell me, i know.))

MVCE

I've already played around with all kinds of combinations of -N1, -n1, --ntasks-per-node=1 and an inner srun with --exclusive, but sadly no success:

sbatch -N1 -n1 -c4 --ntasks-per-node=1 --array=1-128 --wrap \
    'echo "$(hostname) $(date) $(sleep 15) $(date)"'

or

sbatch -N1 -n1 -c4 --ntasks-per-node=1 --array=1-128 --wrap \
    'srun --exclusive -n1 -c4 --ntasks-per-node=1 -- \
         bash -c '\''echo "$(hostname) $(date) $(sleep 15) $(date)"'\'

However, if you look at the output (cat slurm-*.out) you'll in all cases quickly spot overlapping runs :-/

Question

Is there a way to constrain an array job to never concurrently run more than 1 of its jobs on any node?

Our cluster is quite heterogeneous wrt. the CPUs in each node (ranges from 32 - 256), so simple workarounds like asking for a high enough -c so that no 2 can run on the nodes lead to very long wait-times and poor utilization.

Any ideas / pointers? Is there maybe a way to reserve a certain port per job?

2 Answers

I can think of two ways to achieve this, one with some admin-help and one without.:

  1. If you ask your Slurm admin very nicely, he may be able to add a 'fake' gres to the nodes. This allows you to request this gres for your jobs. If there is only one of those gres per node, you should be limited to one job per node, however many other resources you need.
  2. Instead of using an array, you could request a big job with lots of nodes, but one task per node and four cores per. Inside that job, you start the tasks with srun and as each node has one task, they should be distributed along the nodes. You might not want to wait for four cores on 128 nodes to be free at once, so cut your workload into chunks and submit them as dependencies (look into the singleton option).

Elaboration on the second option:

#SBATCH -N16
#SBATCH --ntasks-per-node=1
#SBATCH --job-name=something
#SBATCH --dependency=singleton

for i in `seq 1 $SLURM_JOB_NUM_NODES`; do
  srun -N1 -n1 <your_program> &
done
wait

You could submit 100 of these in a row and they would run chunks of size 16, sequentially. This is not really efficient, but waiting for 100 nodes with a free task at once (so no chunking), might take even longer. I certainly prefer the first option, but this might be an option, if your admin doesn't want to add some gres.

The most efficient way is the gres approach as suggested by @Marcus Boden.

But if the admins are not able to help you with that, you could add a small piece of code at the beginning of the submission script, that would check if the needed ports is available or not (for instance with the netstat command).

If the port is not available, requeue the job with scontrol requeue SLURM_JOB_ID. Before requeueing, in order to prevent the job from hitting the same, unavailable, node, you can edit the job to exclude that node: scontrol update jobid=$SLURM_JOB_ID ExcNodeList=$(hostname -s)`. Ideally, the code should be a bit more clever and retrieve the current excluded node list from the job and append the current node.

Another option would be to modify the job with scontrol update jobid=$SLURM_JOB_ID StartTime=.... with a start time set to the current time plus the typical wall time of your job, with the idea that when the job becomes eligible again, the job currently running on the node would be completed. But of course, there is no guarantee that the node will not be allocated to another job in the meantime.

Related