Apptainer (Singularity)

Note

This guide is designed to help you get started using Singularity containers on ARC. It is not designed to be a full tutorial for how to use Singularity. However, a set of basic tutorials can be found here: Singularity Basics - Video Tutorial

Singularity is now known as Apptainer - so on later versions of workstation operating system (e.g. Ubuntu 20.04 onwards) you will need to install apptainer and use the command apptainer rather than singularity in the examples below.

Building a basic container

The following Singularity definition file configures and builds a container image based on Ubuntu which simply provides an executable installation of the R statistical language:

Bootstrap: docker
From: ubuntu:22.04

%post
# Recommended R installation from cran.r-project.org:
#
   apt update -y -qq
   apt install -y wget gpg-agent
   apt install -y --no-install-recommends software-properties-common dirmngr
   wget -qO- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc | tee -a /etc/apt/trusted.gpg.d/cran_ubuntu_key.asc
   add-apt-repository "deb https://cloud.r-project.org/bin/linux/ubuntu $(lsb_release -cs)-cran40/"
# fix locale
   apt-get install -y locales
   echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen
   locale-gen && update-locale LANG=en_US.UTF-8

# Install r-base
   apt install -y --no-install-recommends r-base

%runscript
   export LANG=en_US.UTF-8
   export LC_ALL=en_US.UTF-8
   R

To build an image from the above definition file, you need to run singularity (or apptainer) on your own workstation (a machine where you have sudo rights) - you cannot build singularity images on the ARC machines.

Running a basic container

So, assuming you have saved the above file as r_test.def, from your linux workstation you should run:

sudo singularity build /tmp/r_test.simg r_test.def

Once the build has completed, you will find that it has built the image file /tmp/r_test.simg

To test this on your workstation run:

$ singularity run /tmp/r_test.simg

R version 4.2.2 Patched (2022-11-10 r83330) -- "Innocent and Trusting"
Copyright (C) 2022 The R Foundation for Statistical Computing
Platform: x86_64-pc-linux-gnu (64-bit)

R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.

Natural language support but running in an English locale

R is a collaborative project with many contributors.
Type 'contributors()' for more information and
'citation()' on how to cite R or R packages in publications.

Type 'demo()' for some demos, 'help()' for on-line help, or
'help.start()' for an HTML browser interface to help.
Type 'q()' to quit R.

[Previously saved workspace restored]

>

The image file r_test.def generated in the above example, may be copied to the ARC systems and run in the same way.

We have already built this container image, and you can run it on ARC as follows:

singularity run /apps/common/examples/singularity/containers/r_test.simg

Directory Binding

You can bind directories from the ARC environment into your container.

For example to bind your ARC $DATA directory into the container as /arc_data - and set the environment variable $DATA inside the container to point to /arc_data you can use the following options:

singularity run -B $DATA:/arc_data --env DATA=/arc_data /apps/common/examples/singularity/containers/bind_test.simg

Note

By default Singularity is aware of $HOME so you do not need to bind this directory.

The container image bind_test.simg used in the above example, was built using the following definition file:

Bootstrap: docker
From: ubuntu:22.04

%post
    apt-get -y update
    apt-get clean

%runscript
   echo "If you've mounted $HOME it contains:"
   ls $HOME
   echo "If you've mounted $DATA it contains:"
   ls $DATA

Using Singularity with MPI code

To use singularity with MPI requires that the container image has been built with MPI support as part of its definition. Preferably the MPI type should match that being used on the host system (ARC).

To run an MPI container on ARC you simply need to run singularity from the mpirun wrapper. For example the following will run a pre-built MPI test container on ARC:

#!/bin/bash

#SBATCH --nodes=2
#SBATCH --ntasks-per-node=4
#SBATCH --cpus-per-task=2
#SBATCH --time=00:10:00
#SBATCH --partition=devel

module load OpenMPI/4.1.4-GCC-12.2.0

mpirun singularity run /apps/common/examples/singularity/containers/mpi_test.simg

Once submitted with the sbatch command the output from the above script should look something like the following:

Hello world from processor arc-c302, rank 0 out of 8 processors
Hello world from processor arc-c302, rank 1 out of 8 processors
Hello world from processor arc-c302, rank 2 out of 8 processors
Hello world from processor arc-c302, rank 3 out of 8 processors
Hello world from processor arc-c303, rank 7 out of 8 processors
Hello world from processor arc-c303, rank 6 out of 8 processors
Hello world from processor arc-c303, rank 4 out of 8 processors
Hello world from processor arc-c303, rank 5 out of 8 processors

The same container also has another command mpisize which is useful for debugging MPI resources. This may be run as follows:

#!/bin/bash

#SBATCH --nodes=2
#SBATCH --ntasks-per-node=4
#SBATCH --cpus-per-task=2
#SBATCH --time=00:10:00
#SBATCH --partition=devel

module load OpenMPI/4.1.4-GCC-12.2.0

mpirun --map-by numa:pe=${SLURM_CPUS_PER_TASK} singularity exec /apps/common/examples/singularity/containers/mpi_test.simg  /opt/mpisize

Here we are using the exec singularity command to run a specific program /opt/mpisize inside the container. We also add some options to mpirun to ensure the CPU thread binding is correct.

The output from the above script should look something like the following:

Allocated core list { 0 1 }
Allocated core list { 4 5 }
Allocated core list { 2 3 }
Allocated core list { 6 7 }
Allocated core list { 6 7 }
Allocated core list { 0 1 }
Allocated core list { 4 5 }
Allocated core list { 2 3 }
I am MPI task 0, the total MPI Size is 8, and there are 2 core(s) allocated to *this* MPI task.
I am MPI task 1, the total MPI Size is 8, and there are 2 core(s) allocated to *this* MPI task.
I am MPI task 3, the total MPI Size is 8, and there are 2 core(s) allocated to *this* MPI task.
I am MPI task 2, the total MPI Size is 8, and there are 2 core(s) allocated to *this* MPI task.
I am MPI task 5, the total MPI Size is 8, and there are 2 core(s) allocated to *this* MPI task.
I am MPI task 6, the total MPI Size is 8, and there are 2 core(s) allocated to *this* MPI task.
I am MPI task 4, the total MPI Size is 8, and there are 2 core(s) allocated to *this* MPI task.
I am MPI task 7, the total MPI Size is 8, and there are 2 core(s) allocated to *this* MPI task.

Note

You can find the source and definition file for the above MPI container at: /apps/common/examples/singularity/build/mpi

Using GPUs with containers

GPU enabled Singularity containers may be used with GPU nodes. For this the --nv flag is used. The --nv flag will:

  • Ensure that the /dev/nvidiaX device entries are available inside the container, so that the GPU cards in the host are accessible.

  • Locate and bind the basic CUDA libraries from the host into the container, so that they are available to the container, and match the kernel GPU driver on the host.

  • Set the LD_LIBRARY_PATH inside the container so that the bound-in version of the CUDA libraries are used by applications run inside the container.

As an interactive example, we can run the following from hpc-login on an interactive GPU node:

srun -p interactive --gres=gpu:1 --pty /bin/bash
srun: GPU gres requested, checking settings/requirements...
srun: job 2072540 queued and waiting for resources
srun: job 2072540 has been allocated resources

singularity exec --nv /apps/common/examples/singularity/containers/tensorflow-20.02-tf1-py3.sif python -c 'import tensorflow as tf; print("Num GPUs Available:
",len(tf.config.experimental.list_physical_devices("GPU")))'

2023-03-01 15:26:22.464915: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudart.so.10.2
2023-03-01 15:26:28.437732: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcuda.so.1
2023-03-01 15:26:28.456277: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1639] Found device 0 with properties:
name: Tesla V100-SXM2-16GB major: 7 minor: 0 memoryClockRate(GHz): 1.53
pciBusID: 0000:1d:00.0
2023-03-01 15:26:28.456302: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudart.so.10.2
2023-03-01 15:26:28.695652: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcublas.so.10
2023-03-01 15:26:28.824502: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcufft.so.10
2023-03-01 15:26:29.101684: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcurand.so.10
2023-03-01 15:26:29.294168: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcusolver.so.10
2023-03-01 15:26:29.390364: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcusparse.so.10
2023-03-01 15:26:29.727534: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudnn.so.7
2023-03-01 15:26:29.728266: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1767] Adding visible gpu devices: 0
Num GPUs Available:  1

For a batch job the same output can be generated by using the following submission script:

#! /bin/bash

#SBATCH --nodes=1
#SBATCH --ntasks-per-node=1
#SBATCH --cpus-per-task=5
#SBATCH --gres=gpu:1
#SBATCH --partition=devel
#SBATCH --time=00:10:00

singularity exec --nv /apps/common/examples/singularity/containers/tensorflow-20.02-tf1-py3.sif python -c 'import tensorflow as tf; print("Num GPUs Available:
",len(tf.config.experimental.list_physical_devices("GPU")))'

Note

Building GPU containers: There are some good notes here: Creating portable GPU containers which may be of help.