GPU 加速的 VASP

现在,开始阅读此《GPU Ready Apps Guide》(GPU Ready 应用程序指南)。

VASP

VASP 的全称是 Vienna Ab Initio Simulation Package(Vienna Ab Initio 模拟软件包),该应用程序通过求出薛定谔方程的近似解,从原子尺度进行电子结构的第一性原理计算。它实现了标准密度泛函理论,以及杂化泛函、格林函数方法(GW 和 ACFDT-RPA)和 MP2(二阶 Møller-Plesset)微扰理论等先进功能。

VASP 在 NVIDIA Tesla P100 上的运行速度比仅使用 CPU 的系统最高提升了约 10 倍,从而能在同一时间使用计算要求更高和更准确的方法。

VASP 在 GPU 上运行的速度最高提升 10 倍

安装

系统要求

VASP 以源代码的形式分发,并要依赖多个编译时库和运行时库。对于本指南,我们假设您的 Linux 系统已安装以下软件包,并已设置它们各自的环境变量:

  1. 英特尔编译器套件(特别是 Fortran、C/C++ 和 MKL)
  2. 英特尔 MPI
  3. NVIDIA CUDA 8.0

在大多数情况下,这些软件包已经由管理员安装在超级计算机上,并且可通过模块系统加载。安装上述软件包的过程超出了本指南的范围,因此,如果您需要进一步的协助,请联系您的集群支持团队。

可以使用 PGI 编译器套件来编译 VASP GPU 端口的最新修订版,而且可以免费获得该套件的社区版!但是,由于许多 VASP 用户历来使用英特尔编译器,因此我们在本教程中也同样使用该编译器。

下载和编译

下载所有必需的源代码

VASP 是商业软件,VASP 的普通被许可方可以下载 GPU 端口的最新版本。要获得许可证,请参阅此页。在“Community Portal”(社区门户)下的右侧输入您的登录凭据,然后单击“Login”(登录)以访问下载区。单击“VASP5”,然后选择“src”文件夹。撰写本指南时需要您下载以下文件:

vasp.5.4.4.tar.gz

确保定期查看 VASP 网站,以获得最新的补丁程序或新版本。

解压缩和修补

首先,解压缩您刚才下载的 VASP 源代码:

tar xfz vasp.5.4.4.tar.gz

现在,切换到刚才解压缩的包含源代码的目录,然后应用补丁程序:

cd vasp.5.4.4

VASP makefile 需要进行一些修改,以反映您的本地软件环境。VASP 附带了一系列适用于不同设置的 makefile 模板,它们位于 arch/ 子文件夹中。从 arch/ 文件夹中复制合适的 makefile.include(在本指南中,我们在 Linux 下使用英特尔的 Fortran 编译器和 NVIDIA CUDA):

cp arch/makefile.include.linux_intel makefile.include

如果需要修改 makefile.include,请参阅下面的问题排查部分。

makefile.include 中的大部分选项均设置为通过检测环境变量的必要值而立即可用,但是,强烈建议您为 GPU 恰当设置您刚才复制的文件中的 GENCODE_ARCH 变量。请检查 GPU 卡的计算能力 (CC),并使用编辑器(例如许多系统中默认提供的 nano、vim 或 emacs)编辑 makefile.include:

nano makefile.include

我们使用的是 NVIDIA P100 卡,所以为计算能力 6.0 进行编译,以产生最佳性能。因此,我们可以使用如下所示的默认 GENCODE_ARCH 行:

GENCODE_ARCH  := -gencode=arch=compute_30,code=\"sm_30,compute_30\" \
-gencode=arch=compute_35,code=\"sm_35,compute_35\" \
-gencode=arch=compute_60,code=\"sm_60,compute_60\"

将未使用的计算能力标志(例如 3.5)保留下来并不会带来麻烦,但这会使在其他 GPU 架构上也能运行所生成的二进制代码。如果目标 GPU 具有不同的计算能力,请务必相应地修改此行。例如,如果也想将 V100 作为目标,则按以下所示修改此行并使用 CUDA 9:

GENCODE_ARCH  := -gencode=arch=compute_30,code=\"sm_30,compute_30\" \
-gencode=arch=compute_35,code=\"sm_35,compute_35\" \
-gencode=arch=compute_60,code=\"sm_60,compute_60\" \
-gencode=arch=compute_70,code=\"sm_70,compute_70\"

现在,通过执行以下命令(确保添加 -j,因为 VASP 不支持并行编译)编译 VASP 的 GPU 端口:

make gpu

如果编译成功,您应会在 bin/vasp-gpu 中看到 VASP 的 GPU 加速版本。使用以下命令检查该目录是否存在二进制文件:

ls -l bin/vasp-gpu

如果编译失败,请参阅问题排查部分中的“修改编译变量”部分。

编译成功后,即可编译 VASP 的 GPU 端口,以便能使用 gpu_ncl target 进行非共线计算(在 INCAR 中 LNONCOLLINEAR=.TRUE.或 LSORBIT=.TRUE.时)。请注意,在 GPU 上尚不支持 VASP 的 Γ 点风味。

 

同样也可以编译仅限 CPU 的版本(std、ncl、gam):

make gpu_ncl std ncl gam

这将产生以下二进制文件列表,但对于本教程,将仅使用 vasp_gpu 和 vasp_std(可选):

表 1:为 VASP 编译的不同可执行文件的概览。

vasp_std Default version of VASP
vasp_ncl Special version required to run calculations with LNONCOLLINEAR=.TRUE. or LSORBIT=.TRUE. in the INCAR
vasp_gam Special version that saves memory and computations for calculations at Γ only.
vasp_gpu Same as vasp_std, but with GPU acceleration
vasp_gpu_ncl Same as vasp_ncl, but with GPU acceleration

我们建议将 VASP 二进制文件安装到编译目录以外的位置,例如安装到 ~/bin 中,以免被将来的版本意外覆盖:

mkdir -p ~/bin
cp bin/vasp* ~/bin

安装 POTCAR 数据库

VASP 依赖表格式数据来平滑全电子波函数(通常称为赝势)。可以下载这些赝势

在“Community Portal”(社区门户)下的右侧输入您的登录凭据,然后单击“Login”(登录)以访问下载区。然后,单击“Potentials”(势能)并从“LDA”开始。下载该处提供的所有文件,然后以同样的方式对“PBE”和“PW91”文件夹这样做。

这将让您获得以下这组文件:

  1. potpaw_PBE.tgz
  2. potpaw_PBE.54.tar.gz
  3. potpaw_PBE.52.tar.gz
  4. potpaw_LDA.tgz
  5. potpaw_LDA.54.tar.gz
  6. potpaw_LDA.52.tar.gz
  7. potpaw_GGA.tar.gz
  8.  potUSPP_LDA.tar.gz
  9. potUSPP_GGA.tar.gz

使用脚本 extractPOTCARs.sh 解压缩这些文件。

本教程中出现的所有脚本均可下载。也可以使用以下命令将仓库直接克隆到文件系统中:

运行作业

第一次 GPU 加速的 VASP 计算

主控制文件 INCAR 中有一些选项需要特别考虑 VASP 的 GPU 端口。如果不支持或不鼓励使用 INCAR 文件中的某些设置,则 GPU VASP 会输出错误和警告消息。

请勿忽略与 GPU 相关的消息并请采取相应措施!本部分说明与 GPU 相关的 INCAR 设置。

请仅将以下选项用于 ALGO 标志:

  1. Normal
  2. Fast
  3. Veryfast

VASP 中提供的其他算法未经广泛测试,因此不保证能够执行,甚至可能会产生不正确的结果。此外,您必须在 INCAR 文件中使用以下设置:

  1. LREAL = .TRUE.或者 LREAL = A
  2. NCORE = 1

为方便开始,我们提供了一些计算示例,稍后将用它们来说明如何获得比简单的设置更好的性能。您可以在 git 仓库中找到一些示范性的输入文件。请转到正确的目录并快速查看 INCAR 文件,您可以看到它与上述选项保持一致:

cd gpu-vasp-files/benchmarks

基于版权原因,您必须自行生成必需的 POTCAR 文件。我们假设您已下载并解压缩如文中所示的赝势数据库,并使用 ~/vasp/potcars/ 作为其驻留目录。示范性的输入文件附带一个能自动处理生成的脚本,但它需要知道 POTCAR 数据库的路径:

cd siHugeShort
bash generatePOTCAR.sh ~/vasp/potcars

之后即可开始执行第一次 GPU 加速的 VASP 计算:

~/bin/vasp_gpu

这将仅启动一个进程,该进程将仅使用一个 GPU 和一个 CPU 核心,而不管系统中有多少个可用的此类硬件。以这种方式运行它可能会耗费相对较长的时间,但能表明一切正常。要确认 GPU 正被频繁使用,请输入

nvidia-smi -l

(在连接到正运行进程的同一节点的终端中输入)。您应看到系统列出 VASP 进程和使用 GPU 的程度。可以按 CTRL+c 停止查看。

使用单个计算节点

就像 VASP 的标准版本一样,GPU 端口与 MPI 并行,并且可以在多个 CPU、GPU 和节点之间分配计算工作负载。我们在本指南中将使用英特尔 MPI,但文中介绍的所有方法同样也适用于所实现的其他 MPI。请参阅所实现的 MPI 的文档,以找到等效的命令行选项。

VASP 支持各种功能和算法,导致它的计算配置文件也多种多样。因此,根据您的特定计算,您可能需要使用不同的参数来尽可能快地执行计算。这些特性同样也会传播到 GPU 端口。

在本教程中,我们将提供不同的方法来帮助加快 GPU 运行。但是,由于没有一种最佳设置,因此,您需要对各种计算情况分别进行基准测试,以找到提供最佳性能的设置。

首先,确定节点提供的 GPU 数量和型号:

nvidia-smi –L

此命令的输出表明我们有 4 个可用的 Tesla P100 GPU,并且列出它们的唯一标识符 (UUID),供我们稍后使用:

GPU 0: Tesla P100-PCIE-16GB (UUID: GPU-74dff14b-a797-85d9-b64a-5293c94557a6)
GPU 1: Tesla P100-PCIE-16GB (UUID: GPU-576df4e5-8f0c-c773-23d2-7560fd29542e)
GPU 2: Tesla P100-PCIE-16GB (UUID: GPU-cff44500-e07e-afef-8231-0bdd32dde61f)
GPU 3: Tesla P100-PCIE-16GB (UUID: GPU-54c0a6db-b406-3e24-c28b-0b517549e824)

通常,GPU 需要在它们自己和主内存之间传输数据。在多插座系统上,传输性能取决于数据移动时需要遵循的路径。在最好的情况下,两个单独的内存区域之间存在直接总线。在最坏的情况下,CPU 进程需要访问在物理上位于与另一个 CPU 插座关联的 RAM 模块中的内存,然后将其复制到再次只能通过 PCI-E 数据通路(由另一个 CPU 插座控制)访问的 GPU 显存。可以使用以下命令显示总线拓扑的相关信息:

nvidia-smi topo -m

由于 GPU 加速的 VASP 尚不支持 GPU 与 GPU 直接通信,因此,我们可以忽略大部分输出,它们指出哪些 GPU 对彼此之间的通信速度最快(PIX 甚至 NV#)和最慢 (SOC):

  GPU0 GPU1 GPU2 GPU3 mlx5_0 CPU Affinity
GPU0 X SOC SOC SOC SOC 0-15
GPU1 SOC X PHB PHB PHB 16-31
GPU2 SOC PHB X PIX PHB 16-31
GPU3 SOC PHB PIX X PHB 16-31
mlx5_0 SOC PHB PHB PHB X  

名为“CPU Affinity”(CPU 关联)的最后一列很重要,因为它指出最好应在哪些 CPU 核心上运行 MPI rank 进程(如果它们与特定 GPU 通信的话)。我们看到,第一个插座的所有 CPU 核心 (0-15) 均能直接与 GPU0 通信,而第二个插座的 CPU (16-31) 预计在与 GPU1、GPU2 和 GPU3 结合使用时表现出最佳性能。

基准

预期性能

要想比较不同配置下作业运行的执行时间,必须避免意料之外的偏差。NVIDIA GPU 提供一些方法,允许根据当前的热量状况和计算负载临时提高和降低时钟速率。虽然这有利于节能,但对于基准测试而言,多次运行之间略高的执行时间偏差可能会导致给出误导性的数据。因此,为了进行比较性的基准测试,我们尝试为系统中的所有显卡关闭上述临时功能:

完成基准测试后,可以重置这些显卡,使它们以受支持的最高频率运行:

性能数据的免责声明

虽然已在生产系统上生成表示性能的数据,但它们仅用作指南以说明下面介绍的方法。请注意,您系统上的性能可能会不同,原因是 CPU 和GPU 的性能受到多个方面的影响。

最简单的方法:每个 GPU 一个进程

要使用我们系统中的所有 4 个 GPU,最简单的方法就是启动 VASP 的 4 个 MPI 进程,并让系统自动处理映射(即进程将在哪些 CPU 核心上运行):

mpirun -n 4 ~/bin/vasp_gpu

英特尔 MPI 环境自动将进程固定到特定的 CPU 核心,这就使操作系统无法在作业执行期间将进程移到其他核心,从而防止在一些不利的情况下进行数据移动。但是,这样做仍可能导致获得的是次佳的解决方案,原因是所实现的 MPI 并不了解 GPU 拓扑。我们可以通过增加详细程度来研究进程固定:

mpirun -n 4 -genv I_MPI_DEBUG=4 ~/bin/vasp_gpu

查看输出,并与我们有关互连拓扑的发现进行比较,可以看到事情似乎并不理想:

...

[0] MPI startup():
Rank
Pid
Node name
Pin cpu

 
[0] MPI startup():
0
41587
hsw227
{16,17,18,19,20,21,22,23}

 
[0] MPI startup():
1
41588
hsw227
{24,25,26,27,28,29,30,31}

 
[0] MPI startup():
2
41589
hsw227
{0,1,2,3,4,5,6,7}

 
[0] MPI startup():
3
41590
hsw227
{8,9,10,11,12,13,14,15}


Using device 0 (rank 0, local rank 0, local size 4) : Tesla P100-PCIE-16GB

Using device 1 (rank 1, local rank 1, local size 4) : Tesla P100-PCIE-16GB

Using device 2 (rank 2, local rank 2, local size 4) : Tesla P100-PCIE-16GB

Using device 3 (rank 3, local rank 3, local size 4) : Tesla P100-PCIE-16GB

...

rank 0 使用 GPU0,但绑定到更远的 CPU 核心 16-23。rank 2 和 3 同样出现此问题。只有 rank 1 使用 GPU1 并固定到核心 24-31,从而提供最佳传输性能。

我们现在来看看一些实际的性能数据。如果使用我们系统中的两个 Intel® Xeon® E5-2698 v3 CPU 的所有 32 个核心,并且不进行任何 GPU 加速,则需要 607.142 秒来完成 siHugeShort 基准测试。1如果以这种默认但次佳的方式使用 4 个 GPU,执行时间将为 273.320 秒(速度提高 2.22 倍)。使用 VASP 中包含的以下指标快速知道计算的运行时间

1 如果您以前编译过 VASP 的仅限 CPU 的版本,则可以使用以下命令知道它在您的系统上要运行多长时间:mpirun -n 32 -env I_MPI_PIN_PROCESSOR_LIST=allcores:map=scatter ~/bin/vasp_std

grep Elapsed\ time OUTCAR

VASP 将 GPU 连续映射到 MPI rank 进程,同时忽略计算能力不足的 GPU(如果有的话)。通过这样做并且使用以下语法,我们可以手动控制 CPU 上的进程放置和分配 rank,让每个进程均使用具有最短内存传输路径的 GPU:

mpirun -n 4 -genv I_MPI_DEBUG=4 -env I_MPI_PIN_PROCESSOR_LIST=0,16,21,26 ~/bin/vasp_gpu

这样做并未改善系统性能(运行时间为 273.370 秒) ,原因可能是没有均衡地使用公共的 CPU 资源,例如内存带宽和缓存(3 个进程共享一个 CPU)。作为折衷,用户可以分配 rank,让它们在 CPU 插座之间均匀分布,但只有一个 rank 必须使用较慢的内存路径向 GPU 传输数据:

mpirun -n 4 -genv I_MPI_DEBUG=4 -env I_MPI_PIN_PROCESSOR_LIST=0,8,16,24 ~/bin/vasp_gpu

这次的运行时间为 268.939 秒,对该基准测试而言, 性能小幅提升了约 3%,但如果内存传输所承担的工作负载更重,您可能会获得更多收益。

手动选择分配可能很繁琐,特别是对于数量较多的 rank。或者,您可能会决定在系统上平均地共享 CPU 资源比内存传输更重要。以下命令连续映射 rank,但尽可能避免共享公共的资源:

mpirun -n 4 -genv I_MPI_DEBUG=4 -env I_MPI_PIN_PROCESSOR_LIST=allcores:map=scatter ~/bin/vasp_gpu

这样做将运行时间缩短到 276.299 秒,而且,这在某些 CPU 核心保持空闲状态时可能特别有用。如果每个 GPU 一个进程使得限制性能的 GPU 资源达到饱和,则您可能会想特意这样做。让 GPU 进一步过载则会损害性能。这是在 siHugeShort 基准测试示例中提供的选择,因此,在我们的系统上,这就是最好的结果(无论如何,请随意尝试下面即将介绍的选择!)。但是,只要 GPU 未过载,浪费可用的 CPU 核心通常不是好的做法,因此,请自己进行试验!

第二简单的方法:每个 GPU 多个进程

为了说明如何使用比可用的 GPU 更多的 CPU 核心,我们将转向另一个名为 silicaIFPEN 的基准测试。仅使用 32 个 CPU 核心时,它的执行时间为 710.156 秒。如果使用 4 个 P100 GPU,并且每个 GPU 一个 MPI rank,同时作出有关进程放置的折衷,则它的完成时间为 241.674 秒(速度提高 2.9 倍)。NVIDIA GPU 能够在多个进程之间共享。要使用此功能,我们必须确保所有 GPU 均设置为“默认”的计算模式:

然后,我们运行实际的计算:

mpirun -n 8 -env I_MPI_PIN_PROCESSOR_LIST=0,8,16,24,4,12,20,28 ~/bin/vasp_gpu

对于 silicaIFPEN 基准测试,在我们的系统上 12 个进程共享 4 个 P100(即每个 GPU 3 个进程),将运行时间缩短到 211.576 秒(速度提高 3.36 倍)。但是,每个 GPU 4 个或更多个进程会对运行时间造成不利影响。下表中的比较结果显示手动放置的进程同样不再能够带来优势。

表 2:在 silicaIFPEN 基准测试中改变每个 GPU 的 MPI 进程数的运行时间对比

MPI ranks per GPU Total MPI ranks Elapsed time (map:scatter) Elapsed time (map:ideal)
0 32 (CPU only) 710.156 s  
1 4 242.247 s 241.674 s
2 8 214.519 s 212.389 s
3 12 212.623 s 211.576 s2
4 16 220.611 s 224.013 s3
5 20 234.540 s  
6 24 243.665 s  
7 28 259.757 s  
8 32 274.798 s  

2 mpirun -n 12 -env I_MPI_PIN_PROCESSOR_LIST=0,8,16,24,3,11,19,27,6,14,22,30 ~/bin/vasp_gpu
3 mpirun -n 16 -env I_MPI_PIN_PROCESSOR_LIST=0,8,16,24,2,10,18,26,4,12,20,28,6,14,22,30 ~/bin/vasp_gpu

达到最佳点后,增加每个 GPU 的进程数甚至会损害性能。但这是为什么?在 GPU 需要切换上下文(即允许另一个进程接管)时,它会引入一个硬同步点。因此,不同进程的指令不可能在 GPU 上重叠,而且过度使用此功能实际上可能会再次减慢运行速度。也请参考下图。

最后,测试多少过载量对您的计算类型有利可能是一种好的做法。当然,规模非常大的计算比规模较小的计算更容易通过一个进程使 GPU 资源达到饱和,但是,请务必自己进行试验!

VASP 在 GPU 上运行的速度最高提升 10 倍

NVIDIA MPS:共享 GPU 时允许重叠

此方法与前一种方法密切相关,但解决了此问题:多个进程的指令可能无法在 GPU 上重叠,如图中的第二行所示。在使用 MPS 时,建议将 GPU 设置为进程独占计算模式:

要利用这种可能性,唯一要做的就是在如常启动 MPI 作业之前启动 MPS 服务器:

nvidia-cuda-mps-control -d
mpirun -n 8 -env I_MPI_PIN_PROCESSOR_LIST=allcores:map=scatter ~/bin/vasp_gpu
echo quit | nvidia-cuda-mps-control

第一条命令在后台(守护进程模式)启动 MPS 服务器。在它运行时,它会解译共享 GPU 的进程发出的指令,然后将它们放入相同的上下文中,再发送给 GPU。与前一部分不同的地方是:从 GPU 的角度来看,这些指令属于单个进程和上下文,因此它们现在可以重叠,就像您在 CUDA 应用程序中使用多个流一样。您可以使用 nvidia-smi -l 检查是否只有单个进程正在访问 GPU。

在单个进程未能使 GPU 资源达到饱和时,以这种模式运行 VASP 的 GPU 端口有助于提高 GPU 利用率。为了证明这一点,我们使用第三个示例 B.hR105,它是使用 HSE06 泛函中的精确交换进行的计算。我们已在启用和禁用 MPS 的情况下运行该示例,而且每次运行时每个 GPU 的 MPI rank 数量都不同。

表 3:在 NSIM=4 的情况下 B.hR105 基准测试中的运行时间对比(每次都改变每个 GPU 的 MPI 进程数,并且启用和禁用 MPS)

MPI ranks per GPU Total MPI ranks Elapsed time without MPS Elapsed time with MPS
0 32 (CPU only) 1027.525 s
1 4 213.903 s 327.835 s
2 8 260.170 s 248.563 s
4 16 221.159 s 158.465 s
7 28 241.594 s 169.441 s
8 32 246.893 s 168.592 s

最重要的是,与禁用 MPS 时的最佳结果(213.903 秒)相比,启用 MPS 时的执行时间为 158.465 秒,缩短了 55.4 秒(即 26%)。在禁用 MPS 的情况下,在每个 GPU 4 个 rank 时达到最佳点,而在启用 MPS 的情况下,启动与可用的 CPU 核心数相同的进程产生了最佳性能。

我们特意跳过了每个 GPU 3 个、5 个和 6 个 rank 的计算,原因是此例中使用的条带数 (224) 不能被所得到的 rank 数整除,因此 VASP 自动增大了该条带数,从而增加了工作负载。如果仅关注求解时间,我们建议您使用 NSIM 参数试验一下。通过将它设置为 32 并且仅使用每个 GPU 1 个进程(因此无 MPS),我们能够将计算时间缩短到 108.193 秒,从而将速度提高大约 10 倍。

高级:每个 GPU 一个 MPS 实例

对于某些设置,特别是在 CUDA 的旧版本上,启动 MPS 守护进程的多个实例(例如每个 GPU 一个 MPS 服务器)可能很有好处。但是,这样做略为复杂,因为用户必须要告诉每个 MPI 进程它应使用哪个 MPS 服务器,而且必须将每个 MPS 实例绑定到另一个 GPU。特别是在带有 CUDA 8.0 的 P100 上,我们并不鼓励使用这种方法,但这不一定意味着您不会发现它有用。可以使用以下脚本启动 MPS 的实例:

为使此方法有效,必须使用包装器脚本迂回地启动 VASP 的 GPU 端口。这将会设置环境变量,以便每个进程使用我们刚才启动的 MPS 服务器的正确实例:runWithMultipleMPSservers-RR.sh

此脚本本质上会生成一个路径列表,以设置决定要使用哪个 MPS 实例的环境变量。倒数第四行 (myMpsInstance=...) 随后会选择此实例,具体视本地 MPI 进程 ID 而定。我们决定将进程 1 到 4 分配给 GPU0 到 GPU3,以采用循环方式。进程 5 再次使用 GPU 0,进程 9 同样如此,而进程 6 和 10 被映射到 GPU 2,依此类推。如果使用了另一个路径来安装 GPU VASP 二进制文件,请务必相应地修改以 runCommand 开始的行。然后,我们开始执行计算:

mpirun -n 16 -env I_MPI_PIN_PROCESSOR_LIST=allcores:map=scatter ./runWithMultipleMPSservers-RR.sh

使用 MPS 时,请记住 MPS 服务器本身使用 CPU。因此,在您启动与可用的 CPU 核心数相同的进程时,CPU 也可能会过载。为此,为 MPS 保留一到两个核心可能是一种好的做法。

计算完成时,以下脚本会清除 MPS 实例:

使用多个计算节点

每个 GPU 一个进程

基本上,与使用位于单个节点的 GPU 相关的所有说明也适用于多个节点。因此,您在进程映射方面做出的最适合您系统的任何决定可能在更多的节点上收到很好的效果。在下文中,我们假设您已设置列出了与作业关联的所有节点的主机文件。在我们的情况下,我们使用了两个节点,而主机文件如下所示:

hsw224
hsw225

如果您决定手动选择进程映射,则以下命令与前一章给出的命令只是略为不同:

mpirun -f hostfile -n 8 -ppn 4 -genv I_MPI_DEBUG=4 -env I_MPI_PIN_PROCESSOR_LIST=0,8,16,24 ~/bin/vasp_gpu

所实现的 MPI 的调试输出告诉我们,进程在两个节点之间分配,并且驻留在 CPU 核心上(这正如我们所料):

[0] MPI startup(): Rank Pid Node name Pin cpu
[0] MPI startup(): 0 38806 hsw224 0
[0] MPI startup(): 1 38807 hsw224 8
[0] MPI startup(): 2 38808 hsw224 16
[0] MPI startup(): 3 38809 hsw224 24
[0] MPI startup(): 4 49492 hsw225 0
[0] MPI startup(): 5 49493 hsw225 8
[0] MPI startup(): 6 49494 hsw225 16
[0] MPI startup(): 7 49495 hsw225 24

GPU VASP 的输出确认 GPU 正如我们打算的那样也映射到 MPI rank:

Using device 0 (rank 0, local rank 0, local size 4) : Tesla P100-PCIE-16GB
Using device 1 (rank 1, local rank 1, local size 4) : Tesla P100-PCIE-16GB
Using device 2 (rank 2, local rank 2, local size 4) : Tesla P100-PCIE-16GB
Using device 3 (rank 3, local rank 3, local size 4) : Tesla P100-PCIE-16GB
Using device 0 (rank 4, local rank 0, local size 4) : Tesla P100-PCIE-16GB
Using device 1 (rank 5, local rank 1, local size 4) : Tesla P100-PCIE-16GB
Using device 2 (rank 6, local rank 2, local size 4) : Tesla P100-PCIE-16GB
Using device 3 (rank 7, local rank 3, local size 4) : Tesla P100-PCIE-16GB

在 siHugeShort 基准测试中,此方法的性能略快一些(运行时间为 258.917 秒),但与在一个节点上的运行时间( 268.939 秒)相比,根本证明不了使用它是有效的。另一方面,在从 4 个 P100 GPU 改为 8 个 P100 GPU(每个 GPU 一个 MPI 进程)时,silicaIFPEN 基准测试的运行时间从 241.674 秒大幅缩短到 153.401 秒。

每个 GPU 多个进程

对于前面的部分,转向使用多个节点(每个 GPU 多个进程)是很简单的:

mpirun -f hostfile -n 16 -ppn 8 -genv I_MPI_DEBUG=4 -env I_MPI_PIN_PROCESSOR_LIST=0,8,16,24,4,12,20,28 ~/bin/vasp_gpu

或者,如果您想使用所有 CPU 核心:

mpirun -f hostfile -n 64 -ppn 32 -genv I_MPI_DEBUG=4 -env I_MPI_PIN_PROCESSOR_LIST=allcores:map=scatter ~/bin/vasp_gpu

如果增加每个 GPU 的 rank 数量,silicaIFPEN 基准测试表现出的行为与单节点情况略为不同(在单节点情况下,最快的配置用了 211.576 秒):使用每个 GPU 一个进程时的运行时间为 153.401 秒,相比之下,使用每个 GPU 两个进程仅使运行时间小幅缩短到 149.818 秒。让 GPU 进一步过载再次造成了不利影响,原因是使用每个 GPU 3 个进程已将运行时间增加到 153.516 秒,而且总共 64 个 rank 使运行时间达到 231.015 秒。因此,在这种情况下,很明显在每个节点上使用每个 GPU 1 或 2 个进程已经足够。

多个节点上的 NVIDIA MPS

启动实例时,每个节点使用一个 MPS 实例显得很繁琐。某些作业调度程序提供了代您这样做的提交选项,例如SLURM 有时候会提供 --cuda-mps。如果您的集群提供了类似的调度程序,我们强烈建议您使用它,并按照前一部分所述继续进行。

但是,如果调度程序未提供如此便捷的解决方案,怎么办?您必须确保这一点:在每个节点上,在启动 VASP 之前启动一个且仅启动一个 MPS 实例。我们提供了另一个脚本来处理此事:runWithOneMPSPerNode.sh

再次提醒,如果您将 GPU 加速的 VASP 二进制文件安装到另一个位置,请修改脚本开头的 runCommand 变量。该变量后面的变量计算每个节点上的本地 rank,原因是英特尔实现的 MPI 未能便捷地提供此信息。脚本在每个节点的各第一个 rank 上启动 MPS 服务器,以确保 MPS 进程未绑定到相同的核心,之后 VASP 进程将绑定到后面的核心。务必要执行这一步,不然的话 MPS 会被限制为只能使用一个核心(它能够使用多个核心),甚至更糟糕的是与 VASP 争用该核心上的 CPU 周期。脚本继续执行 VASP,然后停止 MPS。

必须通过 mpirun 命令(您可能已在高级部分中看到过该命令)调用脚本。mpirun 命令在运行时就像在禁用 MPS 的情况下运行一样,但注意我们调用的是脚本而不是 VASP 二进制文件:

mpirun -f hostfile -n 24 -ppn 12 -genv I_MPI_DEBUG=4 -env I_MPI_PIN_PROCESSOR_LIST=allcores:map=scatter ./runWithOneMPSPerNode.sh

对于 B.hR105 基准测试,MPS 缩短了单个节点上的运行时间,在两个节点上同样也如此:启用 MPS 能缩短计算时间,而且增加每个 GPU 的 rank 数量在达到特定的(过)饱和点之前很有利。在我们的系统上,最佳点是每个 GPU 4 个 rank,此时的运行时间为 104.052 秒。与 Haswell 单节点的基准相比,速度提高了 9.05 倍;而与所有 64 个 CPU 核心相比,速度仍然提高了 6.61 倍。

如果我们将 NSIM 设置为 32,并且在 2 个节点的每一个上使用每个 GPU 4 个 rank,同时禁用 MPS,则计算仅需用时 71.222 秒。

表 4:在使用 2 个节点且 NSIM=4 的情况下 B.hR105 基准测试中的运行时间对比(每次都改变每个 GPU 的 MPI 进程数,并且启用和禁用 MPS)

MPI ranks per GPU Total MPI ranks Elapsed time without MPS Elapsed time with MPS
0 32 (CPU only – 1 node) 1027.525 s
0 64 (CPU only) 763.939 s4
1 8 127.945 s 186.853 s
2 16 117.471 s 110.158 s
4 32 130.454 s 104.052 s
7 56 191.211 s 148.662 s
8 64 234.307 s5 182.260 s

4, 5 Here 256 bands were used, which increases the workload.

VASP 在 GPU 上运行的速度最高提升 10 倍

推荐的系统配置

硬件配置

Workstation

Parameter
Specs

CPU Architecture

x86_64

System Memory

32-64 GB

CPUs

8 Cores, 3+ GHz
10 cores, 2.2+ GHz
16 Cores, 2+ GHz

GPU Model

NVIDIA Quadro GP100

GPUs

2-4

Servers

Parameter
Specs

CPU Architecture

x86_64

System Memory

64-128 GB

CPUs

16+ Cores, 2.7+ GHz

GPU Model

NVIDIA Tesla P100, V100

GPUs per Node

2-4

软件配置

Software stack

Parameter
Version

OS

Linux 64

GPU Driver

352.79 or newer

CUDA Toolkit

8.0 or newer

Compiler

PGI Compiler 16.10
Intel Compiler Suite 16

MPI

OpenMPI
Intel MPI

问题排查

修改编译变量(可选)

您的本地软件环境可能不同于 VASP 编译系统能自动处理的软件环境。在这种情况下,编译将会失败,而您必须细微调整 makefile.include。在您爱用的编辑器(例如许多系统中默认提供的 nano、vim 或 emacs)中打开 makefile.include,然后进行必要的更改(见下文):

nano makefile.include

更改了任何文件后,请务必执行以下命令,以从头开始编译:

make veryclean

在下文中,我们列出了一些常见的错误消息及其解决方法:

mpiifort: Command not found

此错误消息只是告诉您,在您的系统上,MPI 感知型英特尔 Fortran 编译器的名称与我们猜测的不同。在 makefile.include 中,请将出现的所有 mpiifort 更改为它在您系统上的名称(例如 mpif90)。

# error "This Intel is for use with only the Intel compilers!"

要解决此错误,必须做两件事。首先,编辑 makefile.include,然后将 -ccbin=icc 添加到 NVCC 变量,使代码行变为:

NVCC := $(CUDA_ROOT)/bin/nvcc -ccbin=icc

之后,必须编辑第二个文件:

nano src/CUDA/common.mk

在该文件中,您会看到如下所示的部分:

# Compilers

NVCC ?= $(CUDA_INSTALL_PATH)/bin/nvcc

CXX := g++ -fPIC -DADD_ -Wall -openmp -DMAGMA_WITH_MKL -DMAGMA_SETAFFINITY -DGPUSHMEM=300 -DHAVE_CUBLAS

CC := gcc -fPIC -DADD_ -Wall -openmp -DMAGMA_WITH_MKL -DMAGMA_SETAFFINITY -DGPUSHMEM=300 -DHAVE_CUBLAS

#CXX := icc -fPIC -DADD_ -Wall -openmp -DMAGMA_WITH_MKL -DMAGMA_SETAFFINITY -DGPUSHMEM=300 -DHAVE_CUBLAS

#CC := icc -fPIC -DADD_ -Wall -openmp -DMAGMA_WITH_MKL -DMAGMA_SETAFFINITY -DGPUSHMEM=300 -DHAVE_CUBLAS

LINK := g++ -fPIC

请按以下所示更改该部分:

# Compilers

NVCC ?= $(CUDA_INSTALL_PATH)/bin/nvcc

#CXX := g++ -fPIC -DADD_ -Wall -openmp -DMAGMA_WITH_MKL -DMAGMA_SETAFFINITY -DGPUSHMEM=300 -DHAVE_CUBLAS

#CC := gcc -fPIC -DADD_ -Wall -openmp -DMAGMA_WITH_MKL -DMAGMA_SETAFFINITY -DGPUSHMEM=300 -DHAVE_CUBLAS

CXX := icc -fPIC -DADD_ -Wall -openmp -DMAGMA_WITH_MKL -DMAGMA_SETAFFINITY -DGPUSHMEM=300 -DHAVE_CUBLAS

CC := icc -fPIC -DADD_ -Wall -openmp -DMAGMA_WITH_MKL -DMAGMA_SETAFFINITY -DGPUSHMEM=300 -DHAVE_CUBLAS

LINK := g++ -fPIC

/usr/local/cuda//bin/nvcc: Command not found

该消息告诉您 make 无法找到 NVIDIA CUDA 编译器 nvcc。您可以更正以下行中的路径:

CUDA_ROOT := /usr/local/cuda/

甚至可以将该行注释掉(使用 # 作为该行的第一个符号),如果将 CUDA_ROOT 设置为环境变量的话。

No rule to make target `/cm/shared/apps/intel/composer_xe/2015.5.223/mkl/interfaces/fftw3xf/libfftw3xf_intel.a', needed by `vasp'.Stop.

可能您安装的本地 MKL 不支持作为静态库的 FFTW3 接口。如果您通过在引用此静态库的行的最开头插入 # 将该行注释掉,则链接器会调入动态的相似库。请务必将关联到 OBJECTS_GPU 的行和其后的行注释掉,而不是仅将 OBJECTS 之后的行注释掉。

My error is not covered here

如果您的系统符合开始时所述的要求,则极有可能是需要在 makefile.include 文件中更改某个库的路径。请转到 http://cms.mpi.univie.ac.at/wiki/index.php/Installing_VASP,详细了解在该文件中定义的变量。

立即构建理想的 GPU 解决方案。