Kahibaro
Discord Login Register

Writing job scripts

Goals of a Job Script

A job script is a text file that tells the scheduler:

In most HPC clusters, job scripts are submitted to a batch system (for example SLURM) using a command like sbatch. The rest of this chapter focuses on the practical aspects of writing such scripts.

Basic Structure of a Batch Job Script

A typical batch job script has three main parts:

  1. Shebang line – which shell to use
  2. Scheduler directives – special comments describing resources and job options
  3. Job body – the commands to execute

Minimal SLURM example:

#!/bin/bash
#SBATCH --job-name=my_test
#SBATCH --output=my_test_%j.out
#SBATCH --time=00:10:00
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=1
#SBATCH --mem=1G
echo "Running on host: $(hostname)"
echo "Starting at: $(date)"
# Load environment
module purge
module load gcc
# Run the program
./my_program input.dat

Key ideas:

Shebang and Shell Choice

The first line selects the shell:

The shell determines:

For beginners, #!/bin/bash is usually the best default.

Common SLURM Directives in Job Scripts

Directives control job behavior. They usually have both a long and often a short form. Some of the most commonly used:

Job identification and accounting

Example:

#SBATCH --job-name=matrix_mul
#SBATCH --account=project123
#SBATCH --partition=short

Time limits

Examples:

#SBATCH --time=00:30:00   # 30 minutes
#SBATCH --time=2-00:00:00 # 2 days (D-HH:MM:SS format)

Request only as much as you realistically need; this can improve queue wait times and system efficiency.

CPU and task layout

Typical directives (interpretation depends on how the job is launched):

Examples:

# Single-core serial job
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=1
# 16 MPI processes, one per task
#SBATCH --ntasks=16
#SBATCH --cpus-per-task=1
# 4 MPI processes, each using 8 threads (e.g., OpenMP)
#SBATCH --ntasks=4
#SBATCH --cpus-per-task=8

The job script usually combines these directives with the appropriate launch command in the body (srun, mpirun, omp settings, etc.), which is covered elsewhere.

Memory requests

Memory can be requested per node, per CPU, or per task depending on the cluster configuration.

Common forms:

Examples:

#SBATCH --mem=8G           # 8 GB total per node
#SBATCH --mem-per-cpu=2G   # 2 GB per CPU core

Check the local cluster documentation to know which style is expected.

GPUs and accelerators

If the cluster has GPUs, you typically request them like:

Example:

#SBATCH --partition=gpu
#SBATCH --gres=gpu:1
#SBATCH --cpus-per-task=8
#SBATCH --mem=32G

Here, one GPU is requested, with 8 CPU cores and 32 GB of memory.

Output and error handling

You can control where the job’s output and error messages go:

Useful placeholders:

Examples:

#SBATCH --output=logs/%x_%j.out
#SBATCH --error=logs/%x_%j.err
#SBATCH --mail-user=myname@example.edu
#SBATCH --mail-type=FAIL,END

Organizing the Job Body

Within the job body, you write ordinary shell commands, but in a way that:

A common pattern:

  1. Initial info and safety checks
  2. Environment modules and variables
  3. Directory setup
  4. Run commands
  5. Final logging

Example:

#!/bin/bash
#SBATCH --job-name=heat_2d
#SBATCH --output=logs/%x_%j.out
#SBATCH --time=01:00:00
#SBATCH --partition=short
#SBATCH --ntasks=4
#SBATCH --cpus-per-task=4
#SBATCH --mem-per-cpu=1G
# 1. Log basic info
echo "Job ID: $SLURM_JOB_ID"
echo "Job name: $SLURM_JOB_NAME"
echo "User: $USER"
echo "Running on nodes: $SLURM_NODELIST"
echo "Number of tasks: $SLURM_NTASKS"
echo "CPUs per task: $SLURM_CPUS_PER_TASK"
echo "Started at: $(date)"
# 2. Setup environment
module purge
module load gcc/12.2
module load openmpi
export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
# 3. Move to the directory from which the job was submitted
cd "$SLURM_SUBMIT_DIR"
# 4. Run the application
srun ./heat_2d_solver --nx 2048 --ny 2048 --steps 500
# 5. Final log
echo "Finished at: $(date)"

Using Environment Variables Provided by the Scheduler

Schedulers often set environment variables that job scripts can use. For SLURM, common ones include:

Practical uses:

  cd "$SLURM_SUBMIT_DIR"
  export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
  echo "Nodes: $SLURM_NODELIST"

Handling Working Directories and Paths

Common strategies in job scripts:

  DATA_DIR=/scratch/$USER/data
  RESULT_DIR=/scratch/$USER/results
  mkdir -p "$RESULT_DIR"
  cp input/*.dat "$SLURM_TMPDIR"/
  cd "$SLURM_TMPDIR"
  srun ./my_code
  cp results/* "$RESULT_DIR"/

Check your site documentation for recommended directories (e.g., $SCRATCH, $SLURM_TMPDIR, etc.).

Serial, OpenMP, MPI, and Hybrid Job Script Patterns

The directives and body structure vary with the parallel model you use. A few common patterns:

Pure serial job

#!/bin/bash
#SBATCH --job-name=serial_test
#SBATCH --output=serial_%j.out
#SBATCH --time=00:05:00
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=1
#SBATCH --mem=1G
cd "$SLURM_SUBMIT_DIR"
./serial_program input.dat

Shared-memory (OpenMP-style) job

#!/bin/bash
#SBATCH --job-name=openmp_job
#SBATCH --output=openmp_%j.out
#SBATCH --time=00:30:00
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=8
#SBATCH --mem=8G
cd "$SLURM_SUBMIT_DIR"
export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
./openmp_program input.dat

Distributed-memory (MPI-style) job

#!/bin/bash
#SBATCH --job-name=mpi_job
#SBATCH --output=mpi_%j.out
#SBATCH --time=02:00:00
#SBATCH --nodes=2
#SBATCH --ntasks-per-node=8
#SBATCH --mem=4G
cd "$SLURM_SUBMIT_DIR"
module load openmpi
srun ./mpi_program input.dat

Hybrid MPI + OpenMP job

#!/bin/bash
#SBATCH --job-name=hybrid_job
#SBATCH --output=hybrid_%j.out
#SBATCH --time=02:00:00
#SBATCH --nodes=2
#SBATCH --ntasks-per-node=4
#SBATCH --cpus-per-task=8
#SBATCH --mem=8G
cd "$SLURM_SUBMIT_DIR"
module load mpi
export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
srun ./hybrid_program input.dat

The details of MPI/OpenMP themselves are covered elsewhere; here the focus is on matching directives to how you intend to run the job.

Parameter Sweeps and Simple Loops in Job Scripts

Sometimes you want a single job to run multiple related simulations with different parameters. You can use shell loops inside the job body:

#!/bin/bash
#SBATCH --job-name=param_sweep
#SBATCH --output=param_sweep_%j.out
#SBATCH --time=04:00:00
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=4
#SBATCH --mem=4G
cd "$SLURM_SUBMIT_DIR"
export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
for NX in 128 256 512 1024; do
    echo "Running with NX=$NX at $(date)"
    ./solver --nx "$NX" --ny "$NX" --steps 200 > "run_NX${NX}.log"
done

This pattern is useful for small sweeps that can fit comfortably within a single job’s time and resource limits.

For large parameter sweeps, array jobs are more appropriate (introduced elsewhere), but the basic structure still resides in a job script.

Job Scripts for Interactive Sessions

Some clusters allow interactive jobs directly from the command line, but you can also use a script to request an interactive shell with certain resources:

#!/bin/bash
#SBATCH --job-name=interactive
#SBATCH --time=01:00:00
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=4
#SBATCH --mem=4G
# This command starts an interactive shell on a compute node
srun --pty bash

Submitting this script may give you a shell on a compute node with the resources you requested. You can then run commands interactively within that environment.

Common Pitfalls When Writing Job Scripts

Some frequent mistakes and how to avoid them:

Example for debugging:

#!/bin/bash
#SBATCH --job-name=debug_example
#SBATCH --output=debug_%j.out
#SBATCH --time=00:10:00
#SBATCH --ntasks=1
#SBATCH --mem=1G
set -e  # exit on error
set -x  # print commands
cd "$SLURM_SUBMIT_DIR"
./possibly_flaky_program

Local Customizations and Templates

Clusters often provide:

A good practice is to:

  1. Start from a working example provided by your site.
  2. Save your own minimal template scripts for common scenarios (serial, OpenMP, MPI, GPU).
  3. Modify copies of these templates for specific projects rather than starting from scratch.

Example template header you can adapt:

#!/bin/bash
#SBATCH --job-name=JOBNAME
#SBATCH --output=logs/%x_%j.out
#SBATCH --partition=PARTITION
#SBATCH --account=ACCOUNT
#SBATCH --time=HH:MM:SS
#SBATCH --nodes=N
#SBATCH --ntasks-per-node=T
#SBATCH --cpus-per-task=C
#SBATCH --mem=MEM
module purge
# module load ...
cd "$SLURM_SUBMIT_DIR"
# export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
# srun ./program ...

Filling in the placeholders consistently reduces errors and makes your jobs easier to manage.

Views: 16

Comments

Please login to add a comment.

Don't have an account? Register now!