Using apptainer containers to manage your Python environments

When it comes to our High-Performance Computing (HPC) systems, efficiency is the name of the game. But are you managing your Python environments efficiently?

Our HPC systems use network file systems that are optimized to access a small number of large files. This is particularly true for the parallel file systems that are used for the /scratch directories (which uses the Lustre file system). Using a typical Python environment with many thousands of small files creates overhead for the file system and can slow down the loading time of your program significantly, as well as slowing down the file system for all other users of the cluster.

In this article we learn how to put the Python environment inside a single container file instead, which greatly reduces the overhead on the file system. The used container definition files can also easily be tracked with version management to satisfy the requirements of scientific data management, similar to the source code you write.

A more permanent version to this article with updates and fixes can be found at https://gitlab-ce.gwdg.de/hpc-team-public/science-domains-blog/-/blob/main/20230907_python-apptainer.md

Sign up for NHR@Göttingen

If you don’t have access yet you can create an NHR account at https://zulassung.hlrn.de/. This is free for all researchers associated with german universities. Is this your first time on any cluster? Check out our bonus material on cluster concepts.

Apptainer on the Emmy & Grete systems

You can load the apptainer module on the login servers glogin[0-9]

module load apptainer

 

You can run apptainer build commands on the login nodes to create your container files, and submit a slurm job that uses apptainer run to run them on the compute nodes. It is currently not possible to run them directly on the login nodes. Note that for testing the test queues as well as interactive sessions can be used to prevent waiting for your job to start only to figure out later that you forgot a semicolon in your code.

To use the container you have to submit a slurm job to run them on one of the compute nodes.

I want to test it on my own system

You can also install apptainer on your local linux computer or virtual machine (VM). You can find the latest releases on https://github.com/apptainer/apptainer/releases/ If you don’t use Linux on your computer, you can also create a Virtual Machine (VM) with a Linux installation to test on (for example by using VirtualBox, VMWare Fusion or Parallels Desktop).

On Ubuntu 22.04 LTS Desktop the following should be sufficient to install version 1.2.2 (which might be outdated by the time you are reading this):

sudo nala install fuse-overlayfs
wget https://github.com/apptainer/apptainer/releases/download/v1.2.2/apptainer_1.2.2_amd64.deb
dpkg -i apptainer_1.2.2_amd64.deb

 

Note: nala is a more powerful and intuitive replacement for apt. If you don’t use nala yet you can install it with sudo apt install nala or use the old apt install instead of nala install. Check out nala history in particular!

Can I run it on the GWDG Scientific Compute Cluster (SCC)?

We currently do not support building your own containers on SCC. If you bring your own container image container.sif though (for example created on your own system, see previous section), you can run it:

module load singularity
singularity run container.sif

General workflow

When working with apptainer, there are usually three steps involved:

  1. Writing a container definition file (.def) (or recipe)
  2. Using apptainer build to create a container image file (.sif) ( or baking the recipe)
  3. Using apptainer run to run the container (if we continue with the metaphor, getting scientific results is like eating a cake)

 

It is not necessary to do all of the steps on the same computer or even by the same person: you can exchange recipes with your colleagues, and also make container image files you created available for download.

In this tutorial we will exemplify the process by providing you recipes that you can build & run on our Emmy & Grete clusters.

Editing text files on the cluster

Please keep in mind that you don’t have to use terminal text editors like vim and emacs but can use the graphical text editor you might already be familiar with if you use an SFTP/SCP client like FileZilla, CyberDuck or Transmit or a text-editor with a built-in client. You need to provide the same hostname, username and SSH key as when logging in via the terminal. The details depend on your operating system and SFTP/SCP client and might be more difficult for Windows users.

Python apptainer

 

On Linux your file browser probably already supports SFTP/SCP without having to install anything. (If you use Gnome and you have setup the $HOME/.ssh/config on your computer so that e.g. you only have to type ssh glogin-gpu to connect to Grete, just enter ssh://glogin-gpu in Files > + Other Locations > Connect To Server.)

Miniconda container: Physics Example

Don’t worry, you will not need any physics knowledge to run this example.

We want to install Quspin (https://quspin.github.io/QuSpin/) in a container to simulate a many-body quantum system. Create the following Apptainer Definition File:

quspin.def

Bootstrap: docker
From: continuumio/miniconda3
%post
    conda update -y conda
    conda install -y python=3.10
    conda install -y -c weinbe58 omp quspin 
  

If we build this file, the official miniconda3 container will be fetched from https://hub.docker.com/r/continuumio/miniconda3 and the commands in the %post section will be run to install the quspin module before creating a single Singularity Image Format file (SIF). If you read this article in the far future, you might have to re-adjust the Python version from 3.10 to one that is currently supported by quspin.

$ apptainer build quspin.sif quspin.def

 

The creation of the SIF file can take a few minutes.
We can now fetch an example from the QuSpin project and try out our new container (we use srun here to start a slurm job on the Emmy cluster):

$ wget https://quspin.github.io/QuSpin/downloads/be9497383fff21e4d03309a4d1a24ce1/example5.py
$ srun -p medium40:test ./quspin.sif python example5.py
srun: job 4813650 queued and waiting for resources
srun: job 4813650 has been allocated resources
Hermiticity check passed!
Symmetry checks passed!
Particle conservation check passed!
$ 


Note:
If you are reading this in the future and the examples have changed, feel free to substitute a different example script from the QuSpin website.

Congratulations! You might have just simulated your first many-body quantum system! Feel free to explore the other quspin examples and simulate your favourite many-body quantum system.

Instead of explicitly executing the .sif file as an executable, you can also use the apptainer run command:

srun -p medium40:test apptainer run quspin.sif python example5.py 

NOTE: By default your home directory on the cluster $HOME is mounted inside the container at the same location. If you need other directories — in particular $WORK and $TMPDIR — to be available inside the container, add e.g. --bind $WORK,$TMPDIR to your apptainer run command:

apptainer run --bind $WORK,$TMPDIR quspin.sif python example5.py 

 

Otherwise the files in your $WORK and $TMPDIR directories on Emmy/Grete will not be visible inside the container, since they are not subdirectories of $HOME.

NOTE: If you have installed Python modules directly into your $HOME with pip, this might cause all sorts of problems (not just with apptainer but also conda and venv environments) and you might consider running the apptainer with –no-home and –bind only the directories you need explicitly.

You can check if you have those modules contaminating your container/environment by running python -m pip list --user inside the container/environment and if you are sure you don’t need them, remove them with python -m pip uninstall <pkgname>.

Running the quspin example on your own machine

You will not be using the slurm resource manager if you are following the examples on your own machine. In that case you can execute the container directly:

./quspin.sif python example5.py

 

The other commands used were running directly on the login node and should also work on your computer.

Python container with pip: Deep Learning Example

In this example we will move the Python environment that is used for the A Beginner’s Guide to Deep Learning with GPUs on Grete at https://info.gwdg.de/news/a-beginners-guide-to-deep-learning-with-gpus-on-grete/ into a container. Since pip is used to install the modules, we can use the official Python container and do not have to mix pip with conda (which can break your environments if you are not careful). Also it is always good to be less reliant on commercial software like Anaconda. We just need the Apptainer Definition File (.def) and a file with the modules we want pip to install which we will call requirements.txt.

In this case we take the requirements.txt from the Course gitlab repository: https://gitlab-ce.gwdg.de/dmuelle3/deep-learning-with-gpu-cores/-/blob/main/code/requirements.txt. Note that we use Python version 3.8 to match what is used in the course. You can check out what other versions are currently available at https://hub.docker.com/_/python.

deep-learning.def

Bootstrap: docker
From: python:3.8

%files
    $PWD/requirements.txt requirements.txt

%post
    pip install --root-user-action=ignore -r requirements.txt

 

We run the following commands:

wget https://gitlab-ce.gwdg.de/dmuelle3/deep-learning-with-gpu-cores/-/raw/main/code/requirements.txt
apptainer build --nv deep-learing.sif deep-learning.def

 

We can now test our container on the Grete cluster. We will use a simple test script:

test-python-environment.py

import torch
import torch.nn as nn
import torch.optim as optim
torch.utils.collect_env
import sklearn

print(sklearn.show_versions())

print(torch.__config__.show())

print(torch.utils.collect_env.get_pretty_env_info())

if torch.cuda.is_available() and torch.cuda.device_count() > 0:
	print("Active CUDA device:", 
          torch.cuda.get_device_name(torch.cuda.current_device()))
else:
        print("No active CUDA devices.")

 

We start an interactive job on a grete compute node:

$ salloc -t 01:00:00 -p grete:interactive -N1 -G V100:1

 

And then run the test script:

$ module load apptainer
$ apptainer run --bind $WORK,$TMPDIR --nv deep-learning.sif python test-python-environment.py 
$ exit

 

NOTE: To get NVIDIA CUDA support the --nv option to the apptainer run is required. On AMD ROCm platforms this would instead be replaced by --rocm.

Example output on Grete (shortened)

login9:~/deep-learning-test $ salloc -t 01:00:00 -p grete:interactive -N1 -G V100:1
[... snip ...]
salloc: Nodes ggpu02 are ready for job
ggpu02:~/deep-learning-test $ module load apptainer
ggpu02:~/deep-learning-test $ apptainer run --bind $WORK,$TMPDIR --nv deep-learning.sif python test-python-environment.py 
[... snip ...]
Versions of relevant libraries:
[pip3] numpy==1.24.2
[pip3] torch==2.0.0
[pip3] torchvision==0.15.1
[conda] Could not collect
Active CUDA device: Tesla V100S-PCIE-32GB
ggpu02:~/deep-learning-test $ exit
exit
salloc: Relinquishing job allocation 4812554
salloc: Job allocation 4812554 has been revoked.
glogin9:~/deep-learning-test $ 

Similarly the submit scripts from the course can be changed to use apptainer instead of anaconda3. For example, if we put the container file at $WORK/deep-learning.sif, the code in submit_train.sh could look like this:

# SBATCH ...
module load apptainer
apptainer run --nv --bind /scratch $WORK/deep-learning.sif python train.py

 

Here we have bound the entire /scratch directory so we have access to $WORK, $TMPDIR and also the data folder in /scratch/projects/workshops/gpu-workshop from inside the container.

To get the debug output that was previously provided by python -m torch.utils.collect_env in the submit script, you can for example add the following line to the if __name__ == "__main__": block in train.py

print(torch.utils.collect_env.get_pretty_env_info())

 

A more advanced usage would be to create a shell script that is executed inside the container (apptainer run ... run_train.sh) that allows you to run e.g. multiple Python commands and emit extra debugging information about the container.

Running the deep learning example on your own machine

You will not be using the slurm resource manager if you are following the examples on your own machine. In that case you can execute the container directly:

apptainer run --nv deep-learning.sif python test-python-environment.py

 

If you don’t have a CUDA Stack you can also leave out the –nv flag. You should still see some output from PyTorch, even though the “Active CUDA device: …” will be missing.

The other commands used were running directly on the login node and should also work on your computer.

Using a sandbox

While you are testing how to best build your container, you can skip the creation of the .sif file and instead create a directory that contains all the individual files inside the container. If we change the earlier QuSpin example:

apptainer build --sandbox quspin quspin.def

 

You can now run commands inside the container (for a sandbox this will also work on the login nodes of NHR@Göttingen!)

apptainer shell quspin

 

You can even make changes (for example to install software with apt, yum or pip) to the container if you use the following command:

apptainer shell --writable quspin

 

If you are satisfied with the container you should add all the necessary commands to the .def file and build a .sif file for use on the cluster as shown in the previous sections, since the sandbox has all the same disadvantages for the file system that Python environments have.

Other containers

A few other interesting containers to start from are miniforge3 from conda-forge with access to the large community driven package repository and NVIDIA CUDA with the latest version of the NVIDIA CUDA Deep Neural Network library. Please be careful about only using containers from reputable sources!

I wish you a fun and educational time with apptainer!

Author

Niklas Bölter
https://gitlab-ce.gwdg.de/hpc-team-public/science-domains-blog/-/blob/main/20230907_python-apptainer.md

CUDA is a registered trademark of NVIDIA Corporation ROCm is a registered trademark of Advanced Micro Devices, Inc

Appendix: Example output on Grete (full, Click to expand)

login9:~/deep-learning-test $ salloc -t 01:00:00 -p grete:interactive -N1 -G V100:1
salloc: Pending job allocation 4812554
salloc: job 4812554 queued and waiting for resources
salloc: job 4812554 has been allocated resources
salloc: Granted job allocation 4812554
salloc: Waiting for resource configuration
salloc: Nodes ggpu02 are ready for job
ggpu02:~/deep-learning-test $ module load apptainer
ggpu02:~/deep-learning-test $ apptainer run –bind $WORK,$TMPDIR –nv deep-learning.sif python test-python-environment.py
INFO: fuse: warning: library too old, some operations may not work
INFO: underlay of /usr/bin/nvidia-smi required more than 50 (517) bind mounts

System:
python: 3.8.17 (default, Jul 28 2023, 06:03:56) [GCC 12.2.0]
executable: /usr/local/bin/python
machine: Linux-4.18.0-425.19.2.el8_7.x86_64-x86_64-with-glibc2.34

Python dependencies:
sklearn: 1.2.2
pip: 23.0.1
setuptools: 65.6.3
numpy: 1.24.2
scipy: 1.10.1
Cython: None
pandas: 1.5.3
matplotlib: 3.7.1
joblib: 1.2.0
threadpoolctl: 3.1.0

Built with OpenMP: True

threadpoolctl info:
user_api: openmp
internal_api: openmp
prefix: libgomp
filepath: /usr/local/lib/python3.8/site-packages/torch/lib/libgomp-a34b3233.so.1
version: None
num_threads: 1

user_api: blas
internal_api: openblas
prefix: libopenblas
filepath: /usr/local/lib/python3.8/site-packages/numpy.libs/libopenblas64_p-r0-15028c96.3.21.so
version: 0.3.21
threading_layer: pthreads
architecture: SkylakeX
num_threads: 1

user_api: openmp
internal_api: openmp
prefix: libgomp
filepath: /usr/local/lib/python3.8/site-packages/scikit_learn.libs/libgomp-a34b3233.so.1.0.0
version: None
num_threads: 1

user_api: blas
internal_api: openblas
prefix: libopenblas
filepath: /usr/local/lib/python3.8/site-packages/scipy.libs/libopenblasp-r0-41284840.3.18.so
version: 0.3.18
threading_layer: pthreads
architecture: SkylakeX
num_threads: 1
None
PyTorch built with:
– GCC 9.3
– C++ Version: 201703
– Intel(R) oneAPI Math Kernel Library Version 2022.2-Product Build 20220804 for Intel(R) 64 architecture applications
– Intel(R) MKL-DNN v2.7.3 (Git Hash 6dbeffbae1f23cbbeae17adb7b5b13f1f37c080e)
– OpenMP 201511 (a.k.a. OpenMP 4.5)
– LAPACK is enabled (usually provided by MKL)
– NNPACK is enabled
– CPU capability usage: AVX2
– CUDA Runtime 11.7
– NVCC architecture flags: -gencode;arch=compute_37,code=sm_37;-gencode;arch=compute_50,code=sm_50;-gencode;arch=compute_60,code=sm_60;-gencode;arch=compute_70,code=sm_70;-gencode;arch=compute_75,code=sm_75;-gencode;arch=compute_80,code=sm_80;-gencode;arch=compute_86,code=sm_86
– CuDNN 8.5
– Magma 2.6.1
– Build settings: BLAS_INFO=mkl, BUILD_TYPE=Release, CUDA_VERSION=11.7, CUDNN_VERSION=8.5.0, CXX_COMPILER=/opt/rh/devtoolset-9/root/usr/bin/c++, CXX_FLAGS= -D_GLIBCXX_USE_CXX11_ABI=0 -fabi-version=11 -Wno-deprecated -fvisibility-inlines-hidden -DUSE_PTHREADPOOL -DNDEBUG -DUSE_KINETO -DLIBKINETO_NOROCTRACER -DUSE_FBGEMM -DUSE_QNNPACK -DUSE_PYTORCH_QNNPACK -DUSE_XNNPACK -DSYMBOLICATE_MOBILE_DEBUG_HANDLE -O2 -fPIC -Wall -Wextra -Werror=return-type -Werror=non-virtual-dtor -Werror=bool-operation -Wnarrowing -Wno-missing-field-initializers -Wno-type-limits -Wno-array-bounds -Wno-unknown-pragmas -Wunused-local-typedefs -Wno-unused-parameter -Wno-unused-function -Wno-unused-result -Wno-strict-overflow -Wno-strict-aliasing -Wno-error=deprecated-declarations -Wno-stringop-overflow -Wno-psabi -Wno-error=pedantic -Wno-error=redundant-decls -Wno-error=old-style-cast -fdiagnostics-color=always -faligned-new -Wno-unused-but-set-variable -Wno-maybe-uninitialized -fno-math-errno -fno-trapping-math -Werror=format -Werror=cast-function-type -Wno-stringop-overflow, LAPACK_INFO=mkl, PERF_WITH_AVX=1, PERF_WITH_AVX2=1, PERF_WITH_AVX512=1, TORCH_DISABLE_GPU_ASSERTS=ON, TORCH_VERSION=2.0.0, USE_CUDA=ON, USE_CUDNN=ON, USE_EXCEPTION_PTR=1, USE_GFLAGS=OFF, USE_GLOG=OFF, USE_MKL=ON, USE_MKLDNN=ON, USE_MPI=OFF, USE_NCCL=1, USE_NNPACK=ON, USE_OPENMP=ON, USE_ROCM=OFF,

PyTorch version: 2.0.0+cu117
Is debug build: False
CUDA used to build PyTorch: 11.7
ROCM used to build PyTorch: N/A

OS: Debian GNU/Linux 12 (bookworm) (x86_64)
GCC version: (Debian 12.2.0-14) 12.2.0
Clang version: Could not collect
CMake version: version 3.26.1
Libc version: glibc-2.36

Python version: 3.8.17 (default, Jul 28 2023, 06:03:56) [GCC 12.2.0] (64-bit runtime)
Python platform: Linux-4.18.0-425.19.2.el8_7.x86_64-x86_64-with-glibc2.34
Is CUDA available: True
CUDA runtime version: Could not collect
CUDA_MODULE_LOADING set to: LAZY
GPU models and configuration: GPU 0: Tesla V100S-PCIE-32GB
Nvidia driver version: 530.30.02
cuDNN version: Could not collect
HIP runtime version: N/A
MIOpen runtime version: N/A
Is XNNPACK available: True

CPU:
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 46 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 80
On-line CPU(s) list: 0-79
Vendor ID: GenuineIntel
Model name: Intel(R) Xeon(R) Gold 6248 CPU @ 2.50GHz
CPU family: 6
Model: 85
Thread(s) per core: 2
Core(s) per socket: 20
Socket(s): 2
Stepping: 7
CPU(s) scaling MHz: 38%
CPU max MHz: 3900.0000
CPU min MHz: 1000.0000
BogoMIPS: 5000.00
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l3 cdp_l3 invpcid_single intel_ppin ssbd mba ibrs ibpb stibp ibrs_enhanced fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm mpx rdt_a avx512f avx512dq rdseed adx smap clflushopt clwb intel_pt avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts pku ospke avx512_vnni md_clear flush_l1d arch_capabilities
L1d cache: 1.3 MiB (40 instances)
L1i cache: 1.3 MiB (40 instances)
L2 cache: 40 MiB (40 instances)
L3 cache: 55 MiB (2 instances)
NUMA node(s): 2
NUMA node0 CPU(s): 0-19,40-59
NUMA node1 CPU(s): 20-39,60-79
Vulnerability Itlb multihit: KVM: Mitigation: VMX unsupported
Vulnerability L1tf: Not affected
Vulnerability Mds: Not affected
Vulnerability Meltdown: Not affected
Vulnerability Mmio stale data: Mitigation; Clear CPU buffers; SMT vulnerable
Vulnerability Retbleed: Mitigation; Enhanced IBRS
Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Vulnerability Spectre v2: Mitigation; Enhanced IBRS, IBPB conditional, RSB filling, PBRSB-eIBRS SW sequence
Vulnerability Srbds: Not affected
Vulnerability Tsx async abort: Mitigation; TSX disabled

Versions of relevant libraries:
[pip3] numpy==1.24.2
[pip3] torch==2.0.0
[pip3] torchvision==0.15.1
[conda] Could not collect
Active CUDA device: Tesla V100S-PCIE-32GB
ggpu02:~/deep-learning-test $ exit
exit
salloc: Relinquishing job allocation 4812554
salloc: Job allocation 4812554 has been revoked.
glogin9:~/deep-learning-test $

Categories

Archives

--