Using Python
Python on Cirrus is provided by a number of Miniconda modules and one Anaconda module. (Miniconda being a small bootstrap version of Anaconda).
The Anaconda module is called anaconda3 and is suitable for
running serial applications - for parallel applications using mpi4py
see mpi4py for CPU or mpi4py for GPU.
You can list the Miniconda modules by running module avail python on a
login node. Those module versions that have the gpu suffix are
suitable for use on the Cirrus GPU nodes. There are also
modules that extend these Python environments, e.g., pyfr, tensorflow
and pytorch - simply run module help <module name> for further info.
The Miniconda modules support Python-based parallel codes, i.e., each
such python module provides a suite of packages pertinent to parallel
processing and numerical analysis such as dask, ipyparallel,
jupyter, matplotlib, numpy, pandas and scipy.
All the packages provided by a module can be obtained by running
pip list. We now give some examples that show how the python modules
can be used on the Cirrus CPU/GPU nodes.
mpi4py for CPU
The python/3.9.13 module provides mpi4py 3.1.5 linked with OpenMPI
4.1.6.
See numpy-broadcast.py below which is a simple MPI Broadcast example,
and the Slurm script submit-broadcast.slurm which demonstrates how to
run across it two compute nodes.
numpy-broadcast.py
#!/usr/bin/env python
"""
Parallel Numpy Array Broadcast
"""
from mpi4py import MPI
import numpy as np
import sys
comm = MPI.COMM_WORLD
size = comm.Get_size()
rank = comm.Get_rank()
name = MPI.Get_processor_name()
arraySize = 100
if rank == 0:
data = np.arange(arraySize, dtype='i')
else:
data = np.empty(arraySize, dtype='i')
comm.Bcast(data, root=0)
if rank == 0:
sys.stdout.write(
"Rank %d of %d (%s) has broadcast %d integers.\n"
% (rank, size, name, arraySize))
else:
sys.stdout.write(
"Rank %d of %d (%s) has received %d integers.\n"
% (rank, size, name, arraySize))
arrayBad = False
for i in range(100):
if data[i] != i:
arrayBad = True
break
if arrayBad:
sys.stdout.write(
"Error, rank %d array is not as expected.\n"
% (rank))
The MPI initialisation is done automatically as a result of calling
from mpi4py import MPI.
submit-broadcast.slurm
#!/bin/bash
# Slurm job options (name, compute nodes, job time)
#SBATCH --job-name=broadcast
#SBATCH --time=00:20:00
#SBATCH --exclusive
#SBATCH --partition=standard
#SBATCH --qos=standard
#SBATCH --account=[budget code]
#SBATCH --nodes=2
#SBATCH --tasks-per-node=36
#SBATCH --cpus-per-task=1
module load python/3.9.13
export OMPI_MCA_mca_base_component_show_load_errors=0
srun numpy-broadcast.py
The Slurm submission script (submit-broadcast.slurm) above sets a
OMPI_MCA environment variable before launching the job. That
particular variable suppresses warnings written to the job output file;
it can of course be removed. Please see the OpenMPI
documentation
for info on all OMPI_MCA variables.
mpi4py for GPU
There's also an mpi4py module (again using OpenMPI 4.1.4) that is
tailored for CUDA 11.6 on the Cirrus GPU nodes, python/3.9.13-gpu. We
show below an example that features an MPI reduction performed on a
CuPy array
(cupy-allreduce.py).
cupy-allreduce.py
#!/usr/bin/env python
"""
Reduce-to-all CuPy Arrays
"""
from mpi4py import MPI
import cupy as cp
import sys
comm = MPI.COMM_WORLD
size = comm.Get_size()
rank = comm.Get_rank()
name = MPI.Get_processor_name()
sendbuf = cp.arange(10, dtype='i')
recvbuf = cp.empty_like(sendbuf)
assert hasattr(sendbuf, '__cuda_array_interface__')
assert hasattr(recvbuf, '__cuda_array_interface__')
cp.cuda.get_current_stream().synchronize()
comm.Allreduce(sendbuf, recvbuf)
assert cp.allclose(recvbuf, sendbuf*size)
sys.stdout.write(
"%d (%s): recvbuf = %s\n"
% (rank, name, str(recvbuf)))
By default, the CuPy cache will be located within the user's home
directory. And so, as /home is not accessible from the GPU nodes, it
is necessary to set CUPY_CACHE_DIR such that the cache is on the
/work file system instead.
submit-allreduce.slurm
#!/bin/bash
#SBATCH --job-name=allreduce
#SBATCH --time=00:20:00
#SBATCH --exclusive
#SBATCH --partition=gpu
#SBATCH --qos=gpu
#SBATCH --account=[budget code]
#SBATCH --nodes=2
#SBATCH --gres=gpu:4
module load python/3.9.13-gpu
export CUPY_CACHE_DIR=${HOME/home/work}/.cupy/kernel_cache
export OMPI_MCA_mpi_warn_on_fork=0
export OMPI_MCA_mca_base_component_show_load_errors=0
srun --ntasks=8 --tasks-per-node=4 --cpus-per-task=1 cupy-allreduce.py
Again, the submission script (submit-allreduce.slurm) is the place to
set OMPI_MCA variables - the two shown are optional, see the link
below for further details.
https://www.open-mpi.org/faq/?category=tuning#mca-def
Machine Learning frameworks
There are several more Python-based modules that also target the Cirrus
GPU nodes. These include two machine learning frameworks,
pytorch/1.13.1-gpu and tensorflow/2.13.0-gpu. Both modules are Python
virtual environments that extend python/3.10.8-gpu. The MPI comms is
handled by the Horovod
0.28.1 package along with the NVIDIA Collective Communications
Library v2.11.4.
A full package list for these environments can be obtained by loading
the module of interest and then running pip list.
Please click on the link indicated to see examples of how to use the PyTorch and TensorFlow modules .
Installing your own Python packages (with pip)
This section shows how to setup a local custom Python environment such
that it extends a centrally-installed python module. By extend, we
mean being able to install packages locally that are not provided by the
central python. This is needed because some packages such as mpi4py
must be built specifically for the Cirrus system and so are best
provided centrally.
You can do this by creating a lightweight virtual environment where the local packages can be installed. Further, this environment is created on top of an existing Python installation, known as the environment's base Python.
Select the base Python by loading the python module you wish to
extend, e.g., python/3.9.13-gpu (you can run module avail python to
list all the available python modules).
[auser@cirrus-login1 auser]$ module load python/3.9.13
Tip
In the commands below, remember to replace x01 with your project code
and auser with your username.
Next, create the virtual environment within a designated folder:
python -m venv --system-site-packages /work/x01/x01/auser/myvenv
In our example, the environment is created within a myvenv folder
located on /work, which means the environment will be accessible from
the compute nodes. The --system-site-packages option ensures that this
environment is based on the currently loaded python module. See
https://docs.python.org/3/library/venv.html for more details.
extend-venv-activate /work/x01/x01/auser/myvenv
The extend-venv-activate command ensures that your virtual
environment's activate script loads and unloads the base python module
when appropriate. You're now ready to activate your environment.
source /work/x01/x01/auser/myvenv/bin/activate
Important
The path above uses a fictitious project code, x01, and username,
auser. Please remember to replace those values with your actual
project code and username. Alternatively, you could enter
${HOME/home/work} in place of /work/x01/x01/auser. That command
fragment expands ${HOME} and then replaces the home part with
work.
Installing packages to your local environment can now be done as follows.
(myvenv) [auser@cirrus-login1 auser]$ python -m pip install <package name>
Running pip directly as in pip install <package name> will also
work, but we show the python -m approach as this is consistent with
the way the virtual environment was created. And when you have finished
installing packages, you can deactivate your environment by issuing the
deactivate command.
(myvenv) [auser@cirrus-login1 auser]$ deactivate
[auser@cirrus-login1 auser]$
The packages you have just installed locally will only be available once the local environment has been activated. So, when running code that requires these packages, you must first activate the environment, by adding the activation command to the submission script, as shown below.
submit-myvenv.slurm
#!/bin/bash
#SBATCH --job-name=myvenv
#SBATCH --time=00:20:00
#SBATCH --exclusive
#SBATCH --partition=gpu
#SBATCH --qos=gpu
#SBATCH --account=[budget code]
#SBATCH --nodes=2
#SBATCH --gres=gpu:4
source /work/x01/x01/auser/myvenv/bin/activate
srun --ntasks=8 --tasks-per-node=4 --cpus-per-task=10 myvenv-script.py
Lastly, the environment being extended does not have to come from one of
the centrally-installed python modules. You could just as easily
create a local virtual environment based on one of the Machine Learning
(ML) modules, e.g., tensorflow or pytorch. This means you would avoid
having to install ML packages within your local area. Each of those ML
modules is based on a python module. For example, tensorflow/2.13.0-gpu
is itself an extension of python/3.10.8-gpu.
Installing your own Python packages (with conda)
This section shows you how to setup a local custom Python environment
such that it duplicates a centrally-installed python module, ensuring
that your local conda environment will contain packages that are
compatible with the Cirrus system.
Select the base Python by loading the python module you wish to
duplicate, e.g., python/3.9.13-gpu (you can run module avail python
to list all the available python modules).
[auser@cirrus-login1 auser]$ module load python/3.9.13
Next, create the folder for holding your conda environments. This
folder should be on the /work file system as /home is not accessible
from the compute nodes.
CONDA_ROOT=/work/x01/x01/auser/condaenvs
mkdir -p ${CONDA_ROOT}
The following commands tell conda where to save your custom
environments and packages.
conda config --prepend envs_dirs ${CONDA_ROOT}/envs
conda config --prepend pkgs_dirs ${CONDA_ROOT}/pkgs
The conda config commands are executed just once and the configuration
details are held in a .condarc file located in your home directory.
You now need to move this .condarc file to a directory visible from
the compute nodes.
mv ~/.condarc ${CONDA_ROOT}
You can now activate the conda configuration.
export CONDARC=${CONDA_ROOT}/.condarc
eval "$(conda shell.bash hook)"
These two lines need to be called each time you want to use your virtual
conda environment. The next command creates that virtual environment.
conda create --clone base --name myvenv
The above creates an environment called myvenv that will hold the same
packages provided by the base python module. As this command involves
a significant amount of file copying and downloading, it may take a long
time to complete. When it has completed please activate the local
myvenv conda environment.
conda activate myvenv
You can now install packages using
conda install -p ${CONDA_ROOT}/envs/myvenv <package_name>. And you can
see the packages currently installed in the active environment with the
command conda list. After all packages have been installed, simply run
conda deactivate twice in order to restore the original command prompt.
(myvenv) [auser@cirrus-login1 auser]$ conda deactivate
(base) [auser@cirrus-login1 auser]$ conda deactivate
[auser@cirrus-login1 auser]$
The submission script below shows how to use the conda environment within a job running on the compute nodes.
submit-myvenv.slurm
#!/bin/bash
#SBATCH --job-name=myvenv
#SBATCH --time=00:20:00
#SBATCH --exclusive
#SBATCH --partition=gpu
#SBATCH --qos=gpu
#SBATCH --account=[budget code]
#SBATCH --nodes=2
#SBATCH --gres=gpu:4
module load python/3.9.13
CONDA_ROOT=/work/x01/x01/auser/condaenvs
export CONDARC=${CONDA_ROOT}/.condarc
eval "$(conda shell.bash hook)"
conda activate myvenv
srun --ntasks=8 --tasks-per-node=4 --cpus-per-task=10 myvenv-script.py
You can see that using conda is less convenient compared to pip. In
particular, the centrally-installed Python packages on copied in to the
local conda environment, consuming some of the disk space allocated to
your project. Secondly, activating the conda environment within a
submission script is more involved: five commands are required
(including an explicit load for the base python module), instead of
the single source command that is sufficient for a pip environment.
Further, conda cannot be used if the base environment is one of the
Machine Learning (ML) modules, as conda is not flexible enough to
gather Python packages from both the ML and base python modules (e.g.,
the ML module pytorch/1.13.1-gpu is itself based on
python/3.10.8-gpu, and so conda will only duplicate packages
provided by the python module and not the ones supplied by pytorch).
Using JupyterLab on Cirrus
It is possible to view and run JupyterLab on both the login and compute nodes of Cirrus. Please note, you can test notebooks on the login nodes, but please don’t attempt to run any computationally intensive work (such jobs will be killed should they reach the login node CPU limit).
If you want to run your JupyterLab on a compute node, you will need to enter an interactive session; otherwise you can start from a login node prompt.
-
As described above, load the Anaconda module on Cirrus using
module load anaconda3. -
Run
export JUPYTER_RUNTIME_DIR=$(pwd). Jobs running in the batch system may also need to set bothJUPYTER_CONFIG_DIRandJUPYTER_DATA_DIRis the same way. By default, these are related to${HOME}, which is not available on the back end. -
Start the JupyterLab server by running
jupyter lab --ip=0.0.0.0 --no-browser- once it’s started, you will see some lines resembling the following output.
Or copy and paste one of these URLs: ... or http://127.0.0.1:8888/lab?token=<string>You will need the URL shown above for step 6.
-
Please skip this step if you are connecting from Windows. If you are connecting from Linux or macOS, open a new terminal window, and run the following command.
ssh <username>@login.cirrus.ac.uk -L<port_number>:<node_id>:<port_number>where \<username> is your username, \<port_number> is as shown in the URL from the Jupyter output and \<node_id> is the name of the node we’re currently on. On a login node, this will be
cirrus-login1, or similar; on a compute node, it will be a mix of numbers and letters such asr2i5n5.Note
If, when you connect in the new terminal, you see a message of the form channel_setup_fwd_listener_tcpip: cannot listen to port: 8888, it means port 8888 is already in use. You need to go back to step 3 (kill the existing jupyter lab) and retry with a new explicit port number by adding the
--port=Noption. The port numberNcan be in the range 5000-65535. You should then use the same port number in place of 8888. -
Please skip this step if you are connecting from Linux or macOS. If you are connecting from Windows, you should use MobaXterm to configure an SSH tunnel as follows.
5.1. Click on the
Tunnellingbutton above the MobaXterm terminal. Create a new tunnel by clicking onNew SSH tunnelin the window that opens.5.2. In the new window that opens, make sure the
Local port forwardingradio button is selected.5.3. In the
forwarded porttext box on the left underMy computer with MobaXterm, enter the port number indicated in the Jupyter server output.5.4. In the three text boxes on the bottom right under
SSH serverenterlogin.cirrus.ac.uk, your Cirrus username, and then22.5.5. At the top right, under
Remote server, enter the name of the Cirrus login or compute node that you noted earlier followed by the port number (e.g. 8888).5.6. Click on the
Savebutton.5.7. In the tunnelling window, you will now see a new row for the settings you just entered. If you like, you can give a name to the tunnel in the leftmost column to identify it. Click on the small key icon close to the right for the new connection to tell MobaXterm which SSH private key to use when connecting to Cirrus. You should tell it to use the same
.ppkprivate key that you normally use.5.8. The tunnel should now be configured. Click on the small start button (like a play
>icon) for the new tunnel to open it. You'll be asked to enter your Cirrus password -- please do so. -
Now, if you open a browser window on your local machine, you should be able to navigate to the URL from step 3, and this should display the JupyterLab server.
- Please note, you will get a connection error if you haven't used the correct node name in step 4 or 5.
Note
If you have extended a central Python venv following the instructions about for Installing your own Python packages (with pip), Jupyter Lab will load the central ipython kernel, not the one for your venv. To enable loading of the ipython kernel for your venv from within Jupyter Lab, first install the ipykernel module and then use this to install the kernel for your venv.
source /work/x01/x01/auser/myvenv/bin/activate
python -m pip install ipykernel
python -m ipykernel install --user --name=myvenv
myvenv
kernel.
If you are on a compute node, the JupyterLab server will be available for the length of the interactive session you have requested.
You can also run Jupyter sessions using the centrally-installed Miniconda3 modules available on Cirrus. For example, the following link provides instructions for how to setup a Jupyter server on a GPU node.
https://github.com/hpc-uk/build-instructions/tree/main/pyenvs/ipyparallel