Scheduling jobs with Slurm
Slurm is the workload manager for the Duke Compute Cluster (DCC). Use it to request resources and run work on compute nodes rather than on the login nodes.
Most DCC partitions contain lab-owned nodes. Members of the owning group receive high-priority access through the corresponding lab partition. See Partitions for general-use partitions, access policies, preemption, and walltime limits.
The current Introduction to the DCC slides provide additional background.
Slurm commands
| Command | Description |
|---|---|
sbatch |
Submit a batch script |
srun |
Run a command or start an interactive job |
squeue |
List queued and running jobs |
scancel |
Cancel one or more jobs |
sinfo |
Show partition and node information |
scontrol |
Show detailed job, node, partition, and cluster configuration |
sacct |
Show accounting data for current and completed jobs |
#SBATCH is not a shell command. It introduces Slurm directives near the beginning of a batch script.
Submitting a batch job
A batch script contains the requested resources followed by the commands to execute. For example, save the following as test.sh,
#!/bin/bash
#SBATCH --job-name=hostname
#SBATCH --partition=common
#SBATCH --cpus-per-task=1
#SBATCH --mem=1G
#SBATCH --time=00:10:00
#SBATCH --output=hostname-%j.out
# Commands to run
hostname
Submit the script from a login node,
Slurm returns a job ID,
When the job finishes, its standard output is written to hostname-186554.out. %j in an output filename expands to the job ID.
Options can also be supplied to sbatch. Command-line options override matching directives in the script,
Specify an account when a partition requires one,
In general, the partition and account names are the same for lab-owned resources. See the sbatch documentation for all submission options.
Monitoring and managing jobs
List your jobs,
Show detailed information about a job,
Cancel a job,
Show accounting information after a job starts or finishes,
Running an interactive job
Use an interactive allocation for testing, debugging, or other work that requires a shell on a compute node. This example requests one CPU, 4 GB of memory, and one hour on common,
The shell prompt changes after Slurm allocates a node,
tm103@dcc-login-02 ~ $ srun --partition=common --mem=4G --time=01:00:00 --pty bash -i
srun: job 186535 queued and waiting for resources
srun: job 186535 has been allocated resources
tm103@dcc-core-ferc-u-ab39-5-1 ~ $
Specify an account when using a restricted partition,
Exit the shell to end the interactive job,
Requesting resources
Request only the resources the job will use. Oversized requests can increase queue time and prevent other users from using idle capacity.
Time
Set a walltime with --time. Common formats are HH:MM:SS and D-HH:MM:SS,
The job is terminated if it exceeds the requested walltime. Partition maxima are listed in Partitions.
Memory
The default memory allocation is 2 GB per allocated CPU. This is a hard limit, so request enough memory for the peak usage of the job.
| Directive | Meaning |
|---|---|
--mem=<size> |
Total memory per node, such as --mem=16G |
--mem-per-cpu=<size> |
Memory per allocated CPU, such as --mem-per-cpu=2G |
--mem and --mem-per-cpu are mutually exclusive.
CPUs, tasks, and nodes
| Directive | Meaning |
|---|---|
-N, --nodes |
Number of nodes |
-n, --ntasks |
Total number of tasks, normally MPI ranks |
--ntasks-per-node |
Number of tasks on each node |
-c, --cpus-per-task |
CPUs allocated to each task, normally threads |
Use --cpus-per-task for a multithreaded program,
#!/bin/bash
#SBATCH --job-name=threaded
#SBATCH --cpus-per-task=4
#SBATCH --mem-per-cpu=500M
#SBATCH --time=01:00:00
#SBATCH --output=threaded-%j.out
myApplication -n "$SLURM_CPUS_PER_TASK"
OpenMP multicore example
For OpenMP, set the thread count from the Slurm allocation,
#!/bin/bash
#SBATCH --job-name=openmp
#SBATCH --cpus-per-task=4
#SBATCH --mem=4G
#SBATCH --time=01:00:00
export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
myOpenMPApplication
R multicore example
The R-openblas module links R against a multi-threaded OpenBLAS, so dense linear algebra routines such as matrix multiplication (%*%, crossprod), Cholesky decomposition (chol), solving linear systems (solve), and eigen-decomposition (eigen), uses the CPUs allocated with --cpus-per-task without changes to the R code. Set the BLAS thread count from the Slurm allocation,
#!/bin/bash
#SBATCH --job-name=r-analysis
#SBATCH --cpus-per-task=4
#SBATCH --mem=4G
#SBATCH --time=01:00:00
#SBATCH --output=r-%j.out
#SBATCH --error=r-%j.err
module load R-openblas/4.6.1
export OPENBLAS_NUM_THREADS=$SLURM_CPUS_PER_TASK
export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
Rscript Rcode.R
Submit the job with sbatch R_job.sh as usual. See Running R in Parallel on DCC for details.
MPI multi-node example
DCC provides MPICH and OpenMPI through environment modules. Compile and test on a compute node requested through an interactive job.
For Open MPI,
For MPICH,
An example two-node batch script with 10 MPI ranks per node is,
#!/bin/bash
#SBATCH --job-name=openhello
#SBATCH --nodes=2
#SBATCH --ntasks-per-node=10
#SBATCH --time=01:00:00
#SBATCH --output=openhello-%j.out
#SBATCH --error=openhello-%j.err
module load OpenMPI/4.1.6
mpirun -n "$SLURM_NTASKS" ./openhello > openhello.out
If openhello prints its rank and hostname, its output will show ranks distributed across the allocated nodes. Rank output can arrive in any order,
dcc-core-01, rank 0 out of 20 processors
dcc-core-01, rank 1 out of 20 processors
dcc-core-01, rank 2 out of 20 processors
dcc-core-01, rank 3 out of 20 processors
dcc-core-01, rank 4 out of 20 processors
dcc-core-02, rank 13 out of 20 processors
dcc-core-02, rank 14 out of 20 processors
dcc-core-02, rank 10 out of 20 processors
dcc-core-02, rank 11 out of 20 processors
dcc-core-02, rank 12 out of 20 processors
dcc-core-02, rank 8 out of 20 processors
dcc-core-02, rank 9 out of 20 processors
dcc-core-02, rank 5 out of 20 processors
...
The DCC is heterogeneous. Multi-node MPI jobs should use homogeneous nodes with compatible interconnects; see Node configurations and CPU architectures. For a worked multi-node example, see Running a Multi-Node MPI Job on DCC.
Slurm job arrays
Job arrays submit many independent tasks from one batch script. Each task receives a different SLURM_ARRAY_TASK_ID.
- Add
--arrayor-ato the batch script orsbatchcommand. - An array can contain at most 50,001 tasks, with indices from 0 through 50,000.
- Array tasks must be independent.
%Ain a filename expands to the array job ID and%aexpands to the array index.
Common index specifications include,
# Continuous indices: 0 through 31
#SBATCH --array=0-31
# Selected indices: 3, 5, 7, 8, 9, and 12
#SBATCH --array=3,5,7-9,12
# At most 20 array tasks running simultaneously
#SBATCH --array=0-4999%20
# A single array index
#SBATCH --array=7
The array can instead be specified during submission,
A single failed task can be rerun by submitting only its index,
Use a discontinuous list to rerun several selected tasks,
For example, this three-task array writes each task ID to a separate output file,
#!/bin/bash
#SBATCH --job-name=array-id
#SBATCH --array=1-3
#SBATCH --output=slurm-%A_%a.out
echo "$SLURM_ARRAY_TASK_ID"
Submit it normally,
After the array finishes, the three output files contain 1, 2, and 3, respectively,
Python job array example
#!/bin/bash
#SBATCH --job-name=python-array
#SBATCH --array=1-5000
#SBATCH --mem=2G
#SBATCH --time=01:00:00
#SBATCH --output=slurm-%A_%a.out
#SBATCH --error=slurm-%A_%a.err
python task.py
Read the array index in task.py,
Processing separate input files
For an array indexed from 1 through the number of lines in files.txt,
#!/bin/bash
#SBATCH --array=1-100
mapfile -t files < files.txt
input_file=${files[$((SLURM_ARRAY_TASK_ID - 1))]}
myApplication "$input_file"
To construct the input list from a shell glob instead of a file, use a Bash array.
#!/bin/bash
#SBATCH --array=1-100
shopt -s nullglob
files=(myfile*)
index=$((SLURM_ARRAY_TASK_ID - 1))
if ((index >= ${#files[@]})); then
echo "No input file for array index $SLURM_ARRAY_TASK_ID" >&2
exit 1
fi
myApplication "${files[$index]}"
Set the array range to the number of matching files. For a reproducible mapping, keep the filenames stable between submission and execution or generate files.txt before submitting the array.
Alternatively, encode the array index directly in filenames and output directories,
#!/bin/bash
#SBATCH --array=1-100
#SBATCH --output=slurm-%A_%a.out
#SBATCH --error=slurm-%A_%a.err
output_dir="out_${SLURM_ARRAY_TASK_ID}"
mkdir -p "$output_dir"
cd "$output_dir" || exit 1
myApplication "../input_${SLURM_ARRAY_TASK_ID}.txt"
This creates directories out_1, out_2, and so on for input files input_1.txt, input_2.txt, and so on.
Unrolling a loop into an array
Consider a serial Python loop,
Move one loop iteration into fibonacci.py by reading the array index,
import os
fibonacci = [0, 1, 1, 2, 3, 5, 8, 13, 21]
i = int(os.environ["SLURM_ARRAY_TASK_ID"])
print(i, fibonacci[i])
Save the following as fibonacci-array.sh to request one array task for each index,
#!/bin/bash
#SBATCH --job-name=fibonacci
#SBATCH --array=0-8
#SBATCH --output=fibonacci-%A_%a.out
#SBATCH --error=fibonacci-%A_%a.err
python fibonacci.py
Submit the script as usual,
After the tasks finish, list the per-index output files,
fibonacci-24856052_0.out
fibonacci-24856052_1.out
fibonacci-24856052_2.out
fibonacci-24856052_3.out
fibonacci-24856052_4.out
fibonacci-24856052_5.out
fibonacci-24856052_6.out
fibonacci-24856052_7.out
fibonacci-24856052_8.out
After all tasks finish, concatenate the output files in index order,
This gives the same result as the serial loop,
Each task performs one independent iteration of the original loop. The tasks can finish in any order; the array index and output filename preserve the mapping to the original iteration. This pattern is appropriate only when iterations do not depend on results from earlier iterations.
Reading Slurm variables
Python
import os
num_cpus = int(os.environ["SLURM_CPUS_PER_TASK"])
task_id = int(os.environ["SLURM_ARRAY_TASK_ID"])
R
num_cpus <- as.integer(Sys.getenv("SLURM_CPUS_PER_TASK"))
task_id <- as.integer(Sys.getenv("SLURM_ARRAY_TASK_ID"))
MATLAB
numCPUs = str2double(getenv('SLURM_CPUS_PER_TASK'));
taskID = str2double(getenv('SLURM_ARRAY_TASK_ID'));
See the Slurm job array documentation for additional options.
Job dependencies
Submit a job that starts only after another job completes successfully,
Common dependency types are,
| Dependency | Meaning |
|---|---|
after:<job_id> |
Start after the specified job begins or is cancelled |
afterok:<job_id> |
Start after the specified job completes successfully |
afterany:<job_id> |
Start after the specified job completes in any state |
afternotok:<job_id> |
Start after the specified job fails |
Wait for specific array tasks,
Wait for an entire array to complete successfully,
See the Slurm dependency documentation for the complete syntax.
GPU jobs
Select a GPU partition and request a random GPU as a generic resource,
#!/bin/bash
#SBATCH --job-name=gpu-job
#SBATCH --partition=gpu-common
#SBATCH --gres=gpu:1
#SBATCH --mem=16G
#SBATCH --time=01:00:00
#SBATCH --output=gpu-%j.out
nvidia-smi
To start an interactive session with one GPU,
Cluster GPU types
Request a specific GPU type and count with,
For example, request one RTX 5000 Ada GPU,
The GPUs by type table is the authoritative list of GPU models, VRAM, and Slurm GRES names on DCC.
Checking GPU availability
From a DCC login node, gpuavail summarizes GPU configurations and currently available GPUs.
E.g.,
$ gpuavail -p gpu-common
CONFIGURATION
NODE NODE
TYPE COUNT
---------------------
gpu:a5000:1 10
gpu:2080:4 5
gpu:5000_ada:8 3
gpu:2080:3 1
AVAILABILITY
NODE GPU GPU GPUs CPUs GB MEM
NAME TYPE COUNT AVAIL AVAIL AVAIL
------------------------------------------------------------------
dcc-core-gpu-ferc-s-p15-20 2080 3 2 86 690
The slurm-usage tool provides a current usage summary by GPU type. Limit it to one or more comma-separated partitions with,
E.g.,
$ slurm-usage -r gpu-common
┌─────────────────┬───────┬──────┬───────────┬─────────────┬────────────────────────┐
│ Resource Type ┆ Total ┆ Used ┆ Available ┆ Utilization ┆ Details │
╞═════════════════╪═══════╪══════╪═══════════╪═════════════╪════════════════════════╡
│ GPUs (2080) ┆ 23 ┆ 21 ┆ 2 ┆ 91.3% ┆ 6 nodes │
│ GPUs (5000_ada) ┆ 24 ┆ 24 ┆ 0 ┆ 100.0% ┆ 3 nodes │
│ GPUs (a5000) ┆ 10 ┆ 9 ┆ 1 ┆ 90.0% ┆ 10 nodes │
│ GPUs (TOTAL) ┆ 57 ┆ 54 ┆ 3 ┆ 94.7% ┆ 19 nodes │
│ CPUs (TOTAL) ┆ 1008 ┆ 341 ┆ 667 ┆ 33.8% ┆ 19 nodes │
│ Queued Jobs ┆ 513 ┆ - ┆ - ┆ - ┆ 43237 CPUs, 17924 GPUs │
└─────────────────┴───────┴──────┴───────────┴─────────────┴────────────────────────┘
For a node-level view from Slurm, show configured and allocated GPUs in the general GPU partitions,
Gres is the configured GPU capacity and GresUsed is the allocated capacity. Their difference is the number of GPUs not currently allocated, although node state and other requested resources can still prevent a job from starting. Use these live commands rather than a captured output listing, which becomes stale as nodes and jobs change.