MPI应用¶
Message Passing Interface (MPI) 是一个接口标准,定义了多节点之间的告诉通信,MPI广泛应用在HPC领域。
MPI有两个主要的开源实现 OpenMPI 和 MPICH, singularity这两种MPI都支持,这一页的目标是展示怎么使用singularity容器运行MPI应用。
有几种方式来实现这个目标,最常用的方式是在容器内运行MPI应用,但是依赖host上的MPI。 这种方式叫做 Host MPI 模式或者 Hybrid 模式,这种模式host上和容器内都需要安装MPI。
另外一种方式是只使用host上的mpi,而容器中没有安装MPI,通过映射的方式将host上的mpi映射到容器中使用。 这种方式叫做 Bind 模式。
Note
bind 模式要求mount host上的MPI到容器中。而根据MPI在host上的安装位置,有时候mount需要特权操作,有时还需要访问其他用户的数据。
Hybrid模式¶
Hybrid 模式下,当你执行一个MPI程序的时候,你需要在host上调用 mpiexec
或者 mpirun
等launcher来启动程序。
Open MPI/Singularity的工作流程如下:
资源调度器或者用户直接在shell中调用MPI launcher (比如
mpirun
,mpiexec
)。Open MPI的lancher接着调用process management daemon (ORTED)。
ORTED启动singularity容器。
Singularity初始化容器和运行环境。
Singularity接着在容器内启动MPI程序。
MPI程序导入容器中的Open MPI库。
Open MPI库通过Process Management Interface (PMI)和ORTED交互。
至此,容器内的程序已经运行起来。
这种模式的优势:
和资源调度器(比如Slurm)集成。
和原生MPI程序执行相同,容器理解
这种模式的缺陷:
容器内的MPI和host上的MPI在版本上必须兼容。
在性能至关重要的情况下,容器中MPI的配置也必须能优化使用硬件的性能。
因为容器中的MPI必须和host上的MPI版本兼容, 因此在build自己的MPI容器的时候需要考虑host上的MPI。
下面是一个例子, mpitest.c 是一个简单的Hello World:
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char **argv) {
int rc;
int size;
int myrank;
rc = MPI_Init (&argc, &argv);
if (rc != MPI_SUCCESS) {
fprintf (stderr, "MPI_Init() failed");
return EXIT_FAILURE;
}
rc = MPI_Comm_size (MPI_COMM_WORLD, &size);
if (rc != MPI_SUCCESS) {
fprintf (stderr, "MPI_Comm_size() failed");
goto exit_with_error;
}
rc = MPI_Comm_rank (MPI_COMM_WORLD, &myrank);
if (rc != MPI_SUCCESS) {
fprintf (stderr, "MPI_Comm_rank() failed");
goto exit_with_error;
}
fprintf (stdout, "Hello, I am rank %d/%d", myrank, size);
MPI_Finalize();
return EXIT_SUCCESS;
exit_with_error:
MPI_Finalize();
return EXIT_FAILURE;
}
Note
MPI是一个接口标准, 常见是Fortran和C语言的实现,但是也可以有Python, R等其它类语言的实现。
下面的步骤是通过definition文件build一个MPI容器,这将依赖于host上的mpi。
如果host上的mpi是MPICH, definition文件如下所示:
Bootstrap: docker
From: ubuntu:latest
%files
mpitest.c /opt
%environment
export MPICH_DIR=/opt/mpich-3.3
export SINGULARITY_MPICH_DIR=$MPICH_DIR
export SINGULARITYENV_APPEND_PATH=$MPICH_DIR/bin
export SINGULAIRTYENV_APPEND_LD_LIBRARY_PATH=$MPICH_DIR/lib
%post
echo "Installing required packages..."
apt-get update && apt-get install -y wget git bash gcc gfortran g++ make
# Information about the version of MPICH to use
export MPICH_VERSION=3.3
export MPICH_URL="http://www.mpich.org/static/downloads/$MPICH_VERSION/mpich-$MPICH_VERSION.tar.gz"
export MPICH_DIR=/opt/mpich
echo "Installing MPICH..."
mkdir -p /tmp/mpich
mkdir -p /opt
# Download
cd /tmp/mpich && wget -O mpich-$MPICH_VERSION.tar.gz $MPICH_URL && tar xzf mpich-$MPICH_VERSION.tar.gz
# Compile and install
cd /tmp/mpich/mpich-$MPICH_VERSION && ./configure --prefix=$MPICH_DIR && make install
# Set env variables so we can compile our application
export PATH=$MPICH_DIR/bin:$PATH
export LD_LIBRARY_PATH=$MPICH_DIR/lib:$LD_LIBRARY_PATH
export MANPATH=$MPICH_DIR/share/man:$MANPATH
echo "Compiling the MPI application..."
cd /opt && mpicc -o mpitest mpitest.c
如果host MPI是Open MPI, definition文件如下所示:
Bootstrap: docker
From: ubuntu:latest
%files
mpitest.c /opt
%environment
export OMPI_DIR=/opt/ompi
export SINGULARITY_OMPI_DIR=$OMPI_DIR
export SINGULARITYENV_APPEND_PATH=$OMPI_DIR/bin
export SINGULAIRTYENV_APPEND_LD_LIBRARY_PATH=$OMPI_DIR/lib
%post
echo "Installing required packages..."
apt-get update && apt-get install -y wget git bash gcc gfortran g++ make file
echo "Installing Open MPI"
export OMPI_DIR=/opt/ompi
export OMPI_VERSION=4.0.1
export OMPI_URL="https://download.open-mpi.org/release/open-mpi/v4.0/openmpi-$OMPI_VERSION.tar.bz2"
mkdir -p /tmp/ompi
mkdir -p /opt
# Download
cd /tmp/ompi && wget -O openmpi-$OMPI_VERSION.tar.bz2 $OMPI_URL && tar -xjf openmpi-$OMPI_VERSION.tar.bz2
# Compile and install
cd /tmp/ompi/openmpi-$OMPI_VERSION && ./configure --prefix=$OMPI_DIR && make install
# Set env variables so we can compile our application
export PATH=$OMPI_DIR/bin:$PATH
export LD_LIBRARY_PATH=$OMPI_DIR/lib:$LD_LIBRARY_PATH
export MANPATH=$OMPI_DIR/share/man:$MANPATH
echo "Compiling the MPI application..."
cd /opt && mpicc -o mpitest mpitest.c
Bind模式¶
和 Hybrid 模式类似, 也是从host调用MPI的launcher(mpirun, mpiexec)。 不同的是在容器内没有安装MPI,运行的时候你需要将hostMPI绑定到容器中。
技术上来说这需要知道两个两点:
Host上MPI的安装位置。
Host上MPI映射到容器中的位置,这个位置需要保证程序在容器内能找到和使用。
这种模式的优势:
和资源调度器(比如Slurm)集成。
容器中没有安装MPI,因此容器比上一种要小。
这种模式的缺点:
用户必须知道host上MPI的安装位置。
用户必须确认host上的MPI能映射到容器中。
用户必须确认host上的MPI和容器中应用要求的MPI是兼容的。
Bind模式创建容器的过程如下:
Host上安装需要的MPI,在host上编译MPI程序。
创建definition文件,将编译好的程序和其依赖拷贝到容器中。
Build容器。
由于我们的程序是在host上编译好后拷贝到容器中的,因此 当运行容器的时候确保你的host也安装有编译时使用的MPI。 比如,如果在容器中我们有一个使用MPICH编译好的程序,在运行的时候我们也需要确保host上也安装有MPICH,如果你的host上安装的是Open MPI,映射到容器中并不一定能工作。
Bind模式下的definition文件很直观。 下面的例子将host上编译好的应用 /tmp/NetPIPE-5.1.4
拷贝到容器中。
Bootstrap: docker
From: ubuntu:disco
%files
/tmp/NetPIPE-5.1.4/NPmpi /opt
%environment
MPI_DIR=/opt/mpi
export MPI_DIR
export SINGULARITY_MPI_DIR=$MPI_DIR
export SINGULARITYENV_APPEND_PATH=$MPI_DIR/bin
export SINGULARITYENV_APPEND_LD_LIBRARY_PATH=$MPI_DIR/lib
%post
apt-get update && apt-get install -y wget git bash gcc gfortran g++ make file
mkdir -p /opt/mpi
apt-get clean
这个例子当中,NetPIPE-5.1.4被拷贝到 /opt
,
同时设置环境变量,这样运行时将host上的mpi映射到镜像中的/opt/mpi后就可以使用MPI运行程序。
运行¶
hybrid模式下,标准的运行方式是在host上执行 mpirun
。假定你的容器中有mpi和已经编译好的程序,下面是运行的例子:
$ mpirun -n <NUMBER_OF_RANKS> singularity exec <PATH/TO/MY/IMAGE> </PATH/TO/BINARY/WITHIN/CONTAINER>
实际中, 这个命令将首先启动一个进程初始化 mpirun
,接着会在多个不同的计算节点上运行singularity容器,MPI程序被执行。
bind模式下,唯一区别是需要映射mpi到容器中:
$ mpirun -n <NUMBER_OF_RANKS> singularity exec --bind <PATH/TO/HOST/MPI/DIRECTORY>:<PATH/IN/CONTAINER> <PATH/TO/MY/IMAGE> </PATH/TO/BINARY/WITHIN/CONTAINER>
接着上面的bind模式的例子,假定在host上,MPI安装在 /opt/openmpi
目录下,那么命令如下:
$ mpirun -n <NUMBER_OF_RANKS> singularity exec --bind /opt/openmpi:/opt/mpi <PATH/TO/MY/IMAGE> /opt/NPmpi
如果你的系统中安装有资源管理和作业调度器,比如SLURM,你也可以通过一个作业文件来运行MPI应用。 下面的例子,在调度器SLURM分配的每个节点上使用singularity运行mpi程序。 对其它的作业调度系统,也是类似。
$ cat my_job.sh
#!/bin/bash
#SBATCH --job-name singularity-mpi
#SBATCH -N $NNODES # total number of nodes
#SBATCH --time=00:05:00 # Max execution time
mpirun -n $NP singularity exec /var/nfsshare/gvallee/mpich.sif /opt/mpitest
这个例子中,作业通过 NNODES
环境变量申请需要的节点数, NP
是MPI程序需要的进程数。
这个例子假定是hybrid模式的,如果是bind模式,你需要映射mpi。
接着用户就可以使用SLURM的命令提交这个作业。
$ sbatch my_job.sh