This article provides some basic information about OpenMPI and some tips for using it on NAS systems.
Background
OpenMPI is an open source Message-Passing Interface (MPI) implementation developed and maintained by a consortium. Its usage has recently been on the rise for various reasons. For example:
OpenMPI built with CUDA support is included in NVIDIA's software development kit (SDK) for running on the NVIDIA GPUs.
Note: The HPE Message Passing Tookit (MPT) also provides support for running MPI + CUDA applications on NVIDIA GPUs.
OpenMPI supports two different MPI IO implementations: ROMIO (developed by Argonne National Labs) and (OpenMPI's native IO implementation). OMPIO is the default for Lustre filesystems starting from OpenMPI versions 4.1.x. Some applications may see better IO performance using OMPIO than ROMIO.
Note: HPE MPT currently only supports ROMIO.
For portability across platforms, MPI applications in containers are often built with open source MPI (for example, OpenMPI, MVAPICH) instead of commercial MPI library (such as HPE MPT) for portability across platforms.
Systems not supported by HPE for evaluating new architectures by NAS staff and pioneer users likely do not include HPE MPT or other commercial MPI libraries in their software stack.
On the other hand, the usability of OpenMPI at NAS is hindered by the following reasons:
Being open source, there is no dedicated support for OpenMPI at NAS.
When errors occur, sometimes the root causes and solutions are not obvious.
OpenMPI's Modular Component Architecture
The MCA hierarchy includes a series of projects at top, frameworks in the middle and components at the bottom.
Some examples of projects are:
MPI
SHMEM (Symmetric Hierarchical MEMory)
OPAL (Open Portable Access Layer)
PMIx (Process Management for Exascale Environment)
PRRTE (PMIx Reference Run-Time Environment) to replace ORTE (Open Run-Time Environment) in OpenMPI 5.0.
Note: An MPI runtime takes care of tasks such as launching the MPI application's processes and helps the MPI library to establish connections between processes.
Under the MPI project, the frameworks that users mostly care about are:
btl: Byte Transfer Layer
pml: Point-to-point Messaging Layer
coll: MPI collective algorithms
mtl: MPI Matching Transport Layer
io: MPI I/O
The available components for each framework depend on the OpenMPI version. For example, in OpenMPI 3.x, btl includes components such as:
self: send-to-self MPI communication
vader: a shared memory transport supporting local communication
tcp: TCP/IP communication
openib: OpenFabrics InfiniBand communication
smcuda: CUDA-aware shared memory communication
However, note that in OpenMPI 5.x, vader is renamed to sm and openib is removed. For more information about MCA, read this OpenMPI page.
Communication Libraries
The most common communication libraries used by OpenMPI are Libfabric and Unified Communication X (UCX). To build OpenMPI with Libfabric or UCX, use the configure option --with-libfabric=path-to-libfabric-installation-directory or --with-ucx=path-to-ucx-installation-directory.
Libfabric is also known as Open Fabric Interfaces (OFI) and it supports all networks supported by the OpenMPI openib btl, including InfiniBand, RoCE (RDMA over Converged Ethernet, where RDMA stands for Remote Direct Memory Access), and iWARP (Internet Wide Area RDMA Protocol). Libfabric also supports many other network types, for example, Intel PSM/PSM2 (Performance Scaled Messaging for using Intel OmniPath Interconnect), Cray uGNI (userspace Generic Network Interfaces), and AWS EFA (Elastic Fabric Adapter).
Note: The openib btl uses the OpenFabrics Alliance (OFA) verbs API stack to support InfiniBand, RoCE, and iWARP devices. The OFA's original name was "OpenIB", which is why the btl is named openib.
UCX is an open source library and it is integrated into OpenMPI as a pml layer. It provides higher-abstraction support for InfiniBand and RoCE and is designed to achieve high performance for HPC applications.
For the InfiniBand network type, the OpenMPI developers suggest using the UCX pml over the openib btl.
Note: On NAS systems, there is no Libfabric installed at system paths. The version of UCX under /usr/bin and /usr/lib64 is very old (1.8.0). If you plan to build your own OpenMPI with either Libfabric or UCX, you will need to build these libraries first. When building OpenMPI to support CUDA (--with-cuda --with-ucx), NVIDIA recommends building UCX with GDRcopy support (--with-gdrcopy).
WARNING: For OpenMPI modules built with --with-ucx, if the UCX was built with --with-xpmem, then during runtime, the XPMEM library used to build UCX must be compatible with the XPMEM library and kernel on the system. Unfortunately, this is not always the case. For example, NVIDIA's HPCX implementation of OpenMPI on NVIDIA systems and distributed to be used at non-NVIDIA sites may include --with-xpmem using an open source XPMEM library. This open source XPMEM is different from HPE's XPMEM on most NAS systems. This incompatibility would lead to some runtime errors similar to:
"....hpcx-2.16/ucx/mt/lib/ucx/libuct_xpmem.so.0 : undefined symbol: xpmem_make."
Note: XPMEM is a Linux kernel module that enables a process to map the memory of another process into its virtual address space. UCT is a lower-level interface in UCX.
Tips on Using OpenMPI
Some OpenMPI modules installed by NAS staff are under /nasa/modulefiles/testing. Add this to your modulefiles search path in order to see all OpenMPI modules.
% module use -a /nasa/modulefiles/testing
% module avail nvhpc
% module avail openmpi
Note: NVIDIA provides version 3, 4, and HPCX-enhanced OpenMPI under its HPC SDK distributions. At NAS, they are installed under /nasa/nvidia/hpc_sdk/.../sdk_version_number/comm_libs/., where sdk_version_number is a number such as 23.11. When you load a modulefile such as nvhpc/23.11, an OpenMPI library is included. To find out which version of OpenMPI is used after you load a nvhpc modulefile, do, for example:
module load nvhpc/23.11
which mpiexec
mpiexec --version
The HPCX-enhanced OpenMPI is usually not set as the default. However, you may find the HPCX related modulefiles under: /nasa/nvidia/hpc_sdk/.../sdk_version_number/comm_libs/ CUDA_version_number/hpcx/latest/modulefiles,
where sdk_version_number is a number such as 23.11, and CUDA_version_number is a number such as 12.3.
Use the command ompi_info to learn how an OpenMPI module was built. For example, what version of OpenMPI (3.x, 4.x, 5.x), flavor of the architecture (x86_64, aarch64), what C, C++, Fortran compilers (GNU, Intel, NVIDIA, LLVM), and configure options were used to build it.
Note: In newer releases (23.5 or newer) of OpenMPI 4.x or 4.x + hpcx included in the NVIDIA HPC SDK, the utility ompi_info may not be included in the installation.
Compile Time Tips
OpenMPI provides the wrapper scripts mpicc, mpic++ (or mpicxx, mpiCC) and mpifort for compiling and linking MPI applications. The legacy wrappers mpif77 and mpif90 still exist but are symbolic links to mpifort. The compilers used by these wrappers default to the compilers used to build the OpenMPI library. Besides using the command ompi_info, one can also find the compilers used to build the MPI library by adding the option --showme when invoking the wrapper scripts. That is:
Note: OpenMPI in NVIDIA HPC SDK was built by NVIDIA with their nvc, nvc++ and nvfortran compilers.
When building an MPI application written in C, most likely you can use an OpenMPI library built with any C compiler. For example, if the OpenMPI was built with gcc but you want to build your MPI application with clang, you can re-define the variable OMPI_CC and use the mpicc --showme command to check that the desired C compiler will be used as shown in the example below:
If the OpenMPI library used was not successfully built with --with-tm to support PBS, a cross-node MPI job will need the mpiexec flag --hostfile $PBS_NODEFILE or --machinefile $PBS_NODEFILE during runtime. That is,
mpiexec -np N --hostfile $PBS_NODEFILE ./a.out
or
mpiexec -np N --machinefile $PBS_NODEFILE ./a.out
Another option is to use the --host flag as explained here.
Before running your MPI application with an OpenMPI module, do a few basic tests as recommended by the OpenMPI developers in Part 3 of the ABCs of OpenMPI.
Single-node tests
To test the basic OpenMPI runtime system:
mpirun -np 1 hostname
To test the start-up and shut-down of the MPI layer:
mpirun -np 1 hello_c
To test the MPI communication with more than 1 process:
Note: hostname is a Linux command. You can find the source code of hello_c and ring_c in github. host1, host2, host3 refer to the actual hostnames, for example, r705c1t1n[1-3] assigned to a PBS job.
The mpiexec flag -x PATH -x LD_LIBRARY_PATH may be needed in some situations. For example, when combining OpenMPI with other tools such as the NAS mpiprof profiling tool for running cross-node jobs, use:
Note: The mpiexec command line is shown on two lines due to formatting issues. It should be all on one line.
OpenMPI provides some process/thread binding capabilities through the use of a three-phase procedure using the flags --map-by <arg>, --rank-by <arg> and --bind-to <arg>.
mapping: Assigns a default location to each process.
The argument for --map-by can be slot, hwthread, core, socket (default), numa, board, node.
ranking: Assigns an MPI_COMM_WORKD rank value to each process
The argument for --rank-by can be slot (default), hwthread, core, socket, numa, board, node.
binding: Constrains each process to run on specific processors.
For --bind-to, the possible arguments are none, hwthread, core, l1cache, l2cache, l3cache, socket, numa, board, cpu-list. Among them, none is the default when oversubscribed, core is the default when np <= 2, and socket is the default when np > 2.
In addition, OpenMPI provides the flag --report-bindings to visualize bindings.
Experiment with the --map-by, --rank-by, --bind-to, and --report-bindings options to get a sense of how these options work.
For instance, running a.out with N=4 on 1 sky_ele node (2 sockets, each with 20 physical cores, or 40 logical cores when Hyperthreading is turned on in BIOS),
rank 0 bound to socket 0 [core 0[hwt 0-1]]
rank 1 bound to socket 0 [core 1[hwt 0-1]]
rank 2 bound to socket 0 [core 2[hwt 0-1]]
rank 3 bound to socket 0 [core 3[hwt 0-1]]
You can also use the NAS inhouse mbind.x tool for binding. For example, to use mbind.x to spread the MPI processes among the physical cores in each 2-socket node, do:
mpiexec -np N [mpiexec-options] /u/scicon/tools/bin/mbind.x -cs ./a.out
This yields the following binding scheme with N=4 on 1 sky_ele node:
Note: The mbind.x tool developer recommends to use the OpenMPI's own binding capabilities instead of mbind.x in case of bugs or incompatibilities.
For better control of the number of MPI ranks to run on each node, use one of the choices below. This is useful in situations where there are more slots availble in each node than what you want to use. For example, in a batch job with 2 nodes each with 40 cores, if you want to run just 8 MPI ranks across 2 nodes each with 4 MPI ranks, do:
Use the --host or -H option: (all one line) mpirun -np 8 --host host1:4,host2:4 --hostfile $PBS_NODEFILE -x
PATH -x LD_LIBRARY_PATH ./a.out
where host1 and host2 are actual hostnames such as r101i5n0 and r101i5n1. You can find the hostnames of nodes assigned to your job by: cat $PBS_NODEFILE | uniq
Use the -npernode option (all one line): mpirun -np 8 -npernode 4 --hostfile $PBS_NODEFILE -x
PATH -x LD_LIBRARY_PATH ./a.out
Use the -N option (all one line): mpirun -np 8 -N 4 --hostfile $PBS_NODEFILE -x
PATH -x LD_LIBRARY_PATH ./a.out
Use the --map-by option (all one line): mpirun -np 8 --map-by ppr:4:node --hostfile $PBS_NODEFILE -x
PATH -x LD_LIBRARY_PATH ./a.out
The inter-node network of NAS systems mainly use InfiniBand. The OpenMPI modules installed by NAS staff should have included support for InfiniBand. For example, if the OpenMPI was built with UCX, then UCX should automatically detect the presence of InfiniBand and will use it by default. If it was not built with UCX, then likely, it was built with support of openib in the btl framework. In this case, openib will also detect the presence of InfiniBand.
Although not recommended, you can still control whether or how to use InfiniBand by adding --mca flags. For example,
mpiexec -np N --mca pml ucx --mca btl ^openib ./a.out
mpiexec -np N --mca btl self,vader,tcp ./a.out
Note: ^openib means not to use openib for communication. TCP (Transmission Control Protocol) is a communication standard that allows devices to exchange messages over a network. Performance using TCP is slower than using InfiniBand RDMA (remote direct memory access).
Starting in OpenMPI 5.0.0, you can view which networks (such as sm, uct) are used during runtime between different pairs of nodes by adding the flag --mca ompi_display_comm 1. That is,
mpiexec -np N --hostfile $PBS_NODEFILE --mca_display_comm 1 ./a.out
This will generate a communication matrix to stdout.
If your application is built with a recent OpenMPI version that uses UCX and experiences large run-to-run variations and/or degraded performance in communication time, compared to an older OpenMPI version or other MPI implementations (for example, HPE MPT), consider adding the UCX environment variables UCX_PROTO_ENABLE=n and UCX_GVA_ENABLE=n as follows:
Adding these two variables can help applications including LAMMPS, NPB FT, and IS benchmarks.
For NPB FT, adding another variable UCX_IB_MLX5_DEVX=no also helps.
For more information, read Known Issues: 3606445 (discovered in HPCX 2.17.0) and 3884209 (discovered in HPCX 2.19.0) on the use of UCX_PROTO_ENABLE=n; 4050321 (discovered in HPCX 2.21.0) on the use of UCX_GVA_ENABLE=n; and 4026461 (discovered in HPCX 2.20.0) on the use of UCX_IB_MLX5_DEVX=no.
To explicitly choose which MPI IO implementation to use, add the flag --mca io .... For example,
mpiexec -np N --mca io ompio ./a.out
or
mpiexec -np N --mca io romio314 ./a.out