Example 5: Containerized MPI Multi-Process Job¶
This example shows how to run a multi-process MPI job with Slurm and
Apptainer. It uses a small Python program with mpi4py to estimate pi using
a distributed Monte Carlo calculation.
The example follows a hybrid launch model: the host MPI launcher starts one
Apptainer container per MPI rank. The container provides Python and mpi4py;
the host MPI stack coordinates the ranks across the allocated nodes.
1. Connect to the CoSTAR cluster¶
Open a terminal and connect to the CoSTAR login node:
ssh ab1234@costar-login01
Replace ab1234 with your University username.
2. Create the Apptainer definition file¶
Download the definition file, or create a file called example-05-mpi.def in
your working directory and copy in the recipe below.
example-05-mpi.def (click to collapse / view)
1Bootstrap: docker
2From: ubuntu:22.04
3
4%post
5 set -e
6 export DEBIAN_FRONTEND=noninteractive
7
8 apt-get update
9 apt-get install -y --no-install-recommends \
10 python3 python3-pip python3-dev build-essential \
11 libopenmpi-dev openmpi-bin
12
13 python3 -m pip install --upgrade pip
14 python3 -m pip install --no-cache-dir mpi4py
15
16 apt-get clean
17 rm -rf /var/lib/apt/lists/* /root/.cache/pip || true
18
19%environment
20 export PATH=/usr/bin:$PATH
21 export PYTHONUNBUFFERED=1
22
23%labels
24 Author CoSTAR
25 Description "Python mpi4py example for Slurm multi-process jobs"
3. Create the MPI Python script¶
Download the Python script, or create a file called example-05-mpi.py in
your working directory and copy in the script below.
example-05-mpi.py (click to collapse / view)
1from mpi4py import MPI
2
3import random
4import socket
5
6
7comm = MPI.COMM_WORLD
8rank = comm.Get_rank()
9size = comm.Get_size()
10host = socket.gethostname()
11
12if rank == 0:
13 print(f"[INFO] World size = {size}")
14
15comm.Barrier()
16print(f"Rank {rank:02d} on host {host}")
17
18total_samples = 2_000_000
19local_samples = total_samples // size
20random.seed(1234 + rank)
21
22hits = 0
23for _ in range(local_samples):
24 x = random.random()
25 y = random.random()
26 if x * x + y * y <= 1.0:
27 hits += 1
28
29total_hits = comm.reduce(hits, op=MPI.SUM, root=0)
30
31if rank == 0:
32 samples_used = local_samples * size
33 pi_estimate = 4.0 * total_hits / samples_used
34 print(f"[RESULT] pi ~= {pi_estimate:.6f} using {samples_used} samples")
4. Build the Apptainer image¶
Note
On CoSTAR, Apptainer is available on compute nodes, not on the login node. If you want to build the image on the cluster, start an interactive Slurm session on a compute node first.
Start an interactive Slurm session:
srun -N 1 --time=00-01:00:00 --pty bash
Once the session starts on a compute node, build the image:
apptainer build example-05-mpi.sif example-05-mpi.def
When the build finishes, exit the interactive session:
exit
5. Run interactively¶
For a quick interactive test, first request an allocation across 2 nodes with 1 MPI rank per node:
salloc -N 2 --ntasks-per-node=1 -t 02:00:00
When the allocation starts, run the MPI program inside the Apptainer image:
mpirun -np 2 apptainer exec --cleanenv example-05-mpi.sif python3 example-05-mpi.py
This launches 2 MPI ranks in total. When you finish testing, leave the interactive allocation with:
exit
6. Create the batch submission script¶
Download the example submission script, or create a file called
example-05-mpi.sh in your working directory and copy in the script below.
example-05-mpi.sh (click to collapse / view)
1#!/bin/bash
2
3#SBATCH --job-name=mpi-container
4#SBATCH --partition=main
5#SBATCH --nodes=2
6#SBATCH --ntasks-per-node=1
7#SBATCH --time=00:10:00
8#SBATCH --hint=nomultithread
9#SBATCH --output=slurm.%N.%j.out
10#SBATCH --error=slurm.%N.%j.err
11
12module purge
13module load NVHPC/26.3-CUDA-13.1.0
14module load UCX-CUDA/1.19.0-GCCcore-14.3.0-CUDA-13.1.0
15
16IMAGE=$PWD/example-05-mpi.sif
17SCRIPT=$PWD/example-05-mpi.py
18
19echo "Job started on $(date)"
20echo "Nodes allocated:"
21scontrol show hostnames "$SLURM_NODELIST"
22
23mpirun -np "$SLURM_NTASKS" apptainer exec --cleanenv "$IMAGE" python3 "$SCRIPT"
24
25echo "Job finished on $(date)"
The job requests 2 nodes with 1 MPI rank per node, for 2 MPI ranks in total.
The mpirun command launches those ranks and runs the Python script inside
the Apptainer image.
7. Submit as a batch job¶
Submit the script to the Slurm scheduler:
sbatch example-05-mpi.sh
You can check the status of your job by running:
squeue --me
8. Check the output¶
When the job finishes, Slurm writes the standard output and error streams to files in the submission directory:
slurm.<node>.<job_id>.outcontains the allocated node list, MPI rank placement, and the final pi estimate.slurm.<node>.<job_id>.errcontains error messages, if any were produced.
The output file should include lines similar to:
Nodes allocated:
costar01
costar02
[INFO] World size = 2
Rank 00 on host costar01
Rank 01 on host costar02
[RESULT] pi ~= 3.141820 using 2000000 samples
The rank order may differ between runs, but the world size should match the number of MPI ranks requested by Slurm.
9. What to change next¶
For your own MPI workload, adjust:
--nodesfor the number of compute nodes.--ntasks-per-nodefor the number of MPI ranks per node.IMAGEto point to your Apptainer image.SCRIPTto point to your MPI application or Python script.The MPI modules to match the MPI version needed by your application.