65.9K
CodeProject 正在变化。 阅读更多。
Home

改进 VASP 材料模拟性能

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2020年4月16日

CPOL

8分钟阅读

viewsIcon

12234

利用最新的 Intel® 软件开发工具更有效地利用硬件。

材料建模和设计是科学和工程领域中非常重要的一个分支。合成材料是现代生活的一部分。我们使用的电脑、汽车、飞机、道路,甚至房屋和食品的材料,都得益于材料科学的进步。高性能计算 (HPC) 使得现代材料模拟和设计成为可能。而 Intel® 软件开发工具 可以帮助开发人员和最终用户在模拟中达到最佳性能。

VASP* 是基于第一性原理进行量子材料建模的顶级应用之一。它是一款专为计算性能而设计的 HPC 应用。它使用 OpenMP* 来充分利用系统中的所有核心,并使用 MPI* 将计算分布到大型 HPC 集群上。

让我们看看 Intel 软件开发工具如何帮助对 VASP 工作负载进行性能剖析和调优。

基准测试 VASP

为了获取 VASP 性能的初步信息,我们在配备 Intel® Xeon® 可扩展处理器 的系统上对其进行了测试。(请注意,VASP 是受版权保护的软件,归维也纳大学(奥地利)所有,由物理学院的 Georg Kresse 教授代表。使用 VASP 必须获得适当的许可。)

我们的性能实验使用了 VASP 开发版本,该版本实现了 MPI/OpenMP 混合并行(参见 Porting VASP from MPI to MPI+OpenMP [SIMD]),以模拟硅——具体来说,是使用 HSE 混合泛函理论 来模拟块体硅中的空位。

我们使用此命令启动 VASP

$ mpiexec.hydra –genv OMP_NUM_THREADS ${NUM_OMP} -ppn ${PPN} -n ${NUM_MPI}
./vasp_std

命令中

  • ${NUM_OMP} 是 OpenMP 线程数。
  • ${PPN} 是每个节点上的 MPI 进程数。
  • ${NUM_MPI} 是当前模拟中的 MPI 进程总数。

对于基准测试,我们将使用带有 LOOP+ 时间的输出,该时间衡量主要计算的执行时间,不包括预处理和后处理。

初步结果是针对一个双路 Intel® Xeon® Gold 6148 处理器(2.4 GHz,每路 20 核,每节点 40 核),如 **表 1** 所示。

表 1. Intel Xeon Gold 处理器集群的初始 LOOP+ 和 MPI_Allreduce 时间

配置: 硬件:Intel® Xeon® Gold 6148 处理器 @ 2.40GHz;192 GB RAM。互连:Intel® Omni-Path Host Fabric Interface (Intel® OP HFI) Adapter 100 Series [独立]。软件:Red Hat Enterprise Linux* 7.3;IFS 10.2.0.0.158;Libfabric 1.3.0;Intel® MPI Library 2018 (I_MPI_FABRICS=shm:ofi);Intel® MPI Benchmarks 2018 (使用 Intel® C++ Compiler XE 18.0.0 for Linux* 构建)。基准测试来源:Intel Corporation。

我们将使用这些数据作为基准性能。本文将使用 MPI_Allreduce 代码路径。基于 MPI_Reduce 的默认方法可以使用相同的方法。

现在,让我们看看如何使用 Intel 软件开发工具来优化应用程序性能。

使用 Application Performance Snapshot 进行性能剖析

典型的 HPC 应用是一个由不同编程和执行模型组成的复杂系统。要在现代集群上实现最佳性能,开发人员应考虑系统的许多方面,例如

  • MPI 和 OpenMP 并行
  • 内存访问
  • FPU 利用率
  • I/O

任何一个方面的性能不佳都会降低整体应用程序性能。Application Performance Snapshot (APS)(在 Intel® VTune™ Amplifier 中)是一个可以快速总结这些区域性能的强大功能。它也可以作为独立实用程序免费提供。

根据 Getting Started with APS,启动 APS 的 MPI 应用程序的推荐方法是运行此命令来收集 MPI 应用程序的数据

$ <mpi launcher> <mpi parameters> aps <my app> <app parameters>

其中

  • <mpi launcher> 是 MPI 作业启动器,例如 mpirun、srun 或 aprun。
  • <mpi parameters> 是 MPI 启动器参数。

请注意

  • apps 必须是最后一个 <mpi launcher> 参数。
  • <my app> 是您的应用程序的位置。
  • <app parameters> 是您的应用程序参数。

APS 会启动应用程序并进行数据收集。分析完成后,将创建一个 aps_result_<date> 目录。

Intel® MPI Library 还提供了两个非常方便的选项,用于将外部工具集成到启动过程中

  1. aps
  2. gtool

让我们分别看看这两个选项。

-aps 选项

当您使用此选项时,会生成一个新的统计数据文件夹:aps_result_<date>-<time>。您可以使用 aps 实用程序分析收集到的数据。例如

$ mpirun -aps -n 2 ./myApp
$ aps aps_result_20171231_235959

此选项明确针对 aps,非常符合我们的目标。

-gtool 选项

使用此选项通过 mpiexec.hydrampirun 命令启动 Intel VTune Amplifier、Intel® Advisor、Valgrind* 和 GNU* Debugger (GDB*) 等工具,为指定的进程服务。此选项的替代方法是使用 I_MPI_GTOOL 环境变量。

–gtool 选项旨在简化特定进程的分析,让您不必在每个参数集(以冒号“:”分隔)中指定分析工具的命令行。虽然允许在参数集内使用 –gtool,但不要在多个参数集中使用,也不要混合两种分析方法(使用 -gtool 和参数集)。

语法

-gtool "<command line for tool 1>:<ranks set 1>[=launch mode 1][@arch 1];
     <command line for tool 2>:<ranks set 2>[=exclusive][@arch 2]; …
     ;<command line for a tool n>:<ranks set n>[=exclusive][@arch n]"
     <executable>

$ mpirun -n <# of processes>
-gtool "<command line for tool 1>:<ranks set 1>[=launch mode 1][@arch 1]"
-gtool "<command line for a tool 2>:<ranks set 2>[=launch mode 2][@arch 2]"
…
-gtool "<command line for a tool n>:<ranks set n>[=launch mode 3][@arch n]"
<executable>

表 2 显示了参数。

表 2. 参数

请注意,对于同一个 @arch 参数,进程集不能重叠。缺少 @arch 参数也被视为不同的体系结构。因此,以下语法被认为是有效的

-gtool "gdb:0-3=attach;gdb:0-3=attach@hsw;/usr/bin/gdb:0-3=attach@knl"

另外,请注意,某些工具可能无法协同工作,或者它们的同步使用可能导致不正确的结果。表 3 列出了 [=launch mode] 的参数值。

表 3. [=launch mode] 的参数值

这是一个非常强大且复杂的控制选项。Intel MPI Library Developer Reference 有一个专门介绍 gtool 选项的章节。我们将重点关注我们需要的选项。除了命令行选项外,还可以通过环境变量 (I_MPI_GTOOL) 来控制它们,这样我们就可以保持启动命令不变。这在您拥有复杂的启动脚本来在运行前设置好一切时非常有用。

我们期望的分析很简单,所以我们只需要设置

$ export I_MPI_GTOOL="aps:all"

并收集 VASP 的配置文件。配置文件名为 ,可以通过以下方式处理

$ aps –report=<name>

这将生成一个 HTML 文件,其结果如图 **1** 所示。

图 1 – VASP 的 APS 报告

APS 提供了可操作的信息,用于查找性能瓶颈。VASP 显示为 MPI 密集型(19.12% 的运行时花费在 MPI 上)。我们可以查看 MPI 时间部分了解更多详情。它显示 MPI 不平衡消耗了 10.66% 的已用时间。MPI 不平衡定义为在 MPI 中花费的无效时间,例如等待其他进程达到 MPI_Allreduce 调用所需的时间。顶级 5 个 MPI 函数部分显示了最耗时的函数。在此次运行中,MPI_Allreduce 是最主要的消耗者,这表明它应该是我们进行性能优化的首要目标。

我们将尝试使用最新的 Intel MPI Library 2019 和 Intel® Performance Scaled Messaging 2 (PSM2) Multi-Endpoint (Multi-EP) 技术来加速 MPI_Allreduce。让我们开始吧。

Intel® MPI Library 与 Intel® Omni-Path 架构

从前面提供的系统规格可以看出,我们正在使用 Intel® Omni-Path 架构 (Intel® OPA) 硬件。对于 Intel MPI Library,充分利用所有 Intel Omni-Path 架构功能的最佳方式是为 Intel MPI Library 2019 使用 Open Fabrics Interface (OFI)。您可以在以下位置找到与 Intel MPI Library 2018 Update 1 捆绑的 Intel MPI Library 2019 技术预览版:

<install_dir>/compilers_and_libraries_2018.1.163/linux/mpi_2019/

TMI 和 OFI 都支持 PSM2 作为使用 Intel OPA 的最佳选项。

$ export I_MPI_FABRICS=shm:ofi
$ export I_MPI_OFI_PROVIDER=psm2

Intel Performance Scaled Messaging 2 (PSM2) 是 PSM 的后继者,它提供了一个 API 和库,针对具有海量 MPI 进程的大型集群。有关概述,请参阅 PSM2 的新特性和功能。

Intel PSM2 Multi-Endpoint (Multi-EP) 和 Intel MPI Library 2019

Intel PSM2(以及 PSM)的一个关键概念是端点。Intel PSM2 遵循端点通信模型,其中端点被定义为一个对象(或句柄),用于支持与其他端点进行消息的发送和接收。(在此处了解更多信息 here。)默认情况下,每个进程只能使用一个端点。但随着 Intel PSM2 的最新版本,这种情况发生了变化,Intel PSM2 Multi-Endpoint (Multi-EP) 功能允许与多线程应用程序一起使用端点。Intel MPI Library 2019 使开发人员能够有效地使用 Intel PSM2 Multi-Endpoint。对于 Multi-Endpoint 支持,MPI 标准线程模型已扩展到 MPI_THREAD_SPLIT 编程模型。它允许程序有效地与 MPI 进行多线程编程,从而消除大部分同步并提高性能。

正如 Intel® MPI Library Developer Reference for Linux* OS 中所述,一个 MPI_THREAD_SPLIT- 兼容程序至少必须是一个线程兼容的 MPI 程序(支持 MPI_THREAD_MULTIPLE 线程级别)。除此之外,还有以下规则适用:

  • 进程的不同线程 不得同时使用同一通信器。
  • 在线程中创建的任何请求 不得被其他线程访问。也就是说,任何非阻塞操作都必须在同一线程中完成、检查完成或探测。
  • 通信完成调用(暗示操作进度,如 MPI_Wait()MPI_Test())从一个线程调用,并不保证在其他线程中的进度。

此外,所有线程都应有一个标识符 thread_id,并且 MPI 进程之间的通信只能针对具有相同 thread_id 的线程进行。

Collectives 中的 Multi-EP:MPI_Allreduce

由于 VASP 已经拥有混合 MPI/OpenMP 版本,因此可以轻松地适配 MPI Multi-Endpoint。对于每个 MPI 进程,我们使用 MPI_Comm_dup 例程创建与 OpenMP 线程数相同的额外 MPI 通信器。然后,每个 MPI 缓冲区将在 OpenMP 线程之间进行分割,如图 **2** 所示。

图 2 – 在线程之间分割 MPI 缓冲区

为了跳过所有代码处理,让我们使用 PMPI_ 接口并用所需的修改重写 MPI_Allreduce 例程

/*
*Copyright 2018 Intel Corporation.
*This software and the related documents are Intel copyrighted materials, and your use of
them is governed by the express license under which they were provided to you (License).
*Unless the License provides otherwise, you may not use, modify, copy, publish, distribute,
disclose or transmit this software or the related documents without Intel’s prior written
permission.
*This software and the related documents are provided as is, with no express or implied
warranties, other than those that are expressly stated in the License.
*/

#include <stdio.h>
#include <mpi.h>
#include <omp.h>
#include <unistd.h>

int comm_flag=0;
int new_comm=0;
int first_comm=0;
int init_flag=0;
int mep_enable=0;
int mep_num_threads=1;
int mep_allreduce_threshold=1000;
int mep_bcast_threshold=1000;
int k_comm=0;

void mep_init() {
  if(init_flag == 0) {
     mep_enable=atoi(getenv("MEP_ENABLE"));
     mep_num_threads=atoi(getenv("MEP_NUM_THREADS"));
     mep_allreduce_threshold=atoi(getenv("MEP_ALLREDUCE_THRESHOLD"));
     mep_bcast_threshold=atoi(getenv("MEP_BCAST_THRESHOLD"));
  }
  init_flag=1;
}
int MPI_Allreduce(const void *sendbuf, void *recvbuf, int count,
                  MPI_Datatype datatype, MPI_Op op, MPI_Comm comm) {
   int err;
   int count_thread;
   int rest=0;
   int num_threads=1;
   int thread_num=0;
   MPI_Aint lb,extent;
   size_t datatype_size;
   if (init_flag == 0) mep_init();
   MPI_Type_get_extent(datatype, &lb, &extent);
   datatype_size=extent;
    // if MPI_Allreduce use MPI_IN_PLACE
    if (&sendbuf[0] == MPI_IN_PLACE) {
      if(mep_enable == 1) {
         if(count >= mep_allreduce_threshold) {
            comm_duplicate(comm,mep_num_threads);
            count_thread=count/mep_num_threads;
            rest=count%mep_num_threads;
			
#pragma omp parallel num_threads(mep_num_threads)
                     private (num_threads, thread_num) {
   num_threads=omp_get_num_threads();
   thread_num=omp_get_thread_num();
   if(thread_num == num_threads-1) {
      err = PMPI_Allreduce(MPI_IN_PLACE,
                           recvbuf+thread_num*count_thread*datatype_size,
                           count_thread+rest,
                           datatype, op, comms.dup_comm[k_comm][thread_num]);
   }
   else {
     err = PMPI_Allreduce(MPI_IN_PLACE,
                          recvbuf+thread_num*count_thread*datatype_size,
                          count_thread,
                          datatype, op, comms.dup_comm[k_comm][thread_num]);
   }
 }
   }
   else {
     err = PMPI_Allreduce (MPI_IN_PLACE, recvbuf, count, datatype, op, comm);
   }
 }
 else {
   err = PMPI_Allreduce (MPI_IN_PLACE, recvbuf, count, datatype, op, comm);
 }
   }
   
   // else MPI_Allreduce not use MPI_IN_PLACE
   else {
      if(mep_enable == 1) {
         if(count >= mep_allreduce_threshold) {
            comm_duplicate(comm,mep_num_threads);
            count_thread=count/mep_num_threads;
            rest=count%mep_num_threads;
#pragma omp parallel num_threads(mep_num_threads)
                     private (num_threads,thread_num) {
   num_threads=omp_get_num_threads();
   thread_num=omp_get_thread_num();
   if(thread_num == num_threads-1) {
      err = PMPI_Allreduce(sendbuf+thread_num*count_thread*datatype_size,
                           recvbuf+thread_num*count_thread*datatype_size,
                           count_thread+rest,
                           datatype, op, comms.dup_comm[k_comm][thread_num]);
   }
   else {
      err = PMPI_Allreduce(sendbuf+thread_num*count_thread*datatype_size,
                           recvbuf+thread_num*count_thread*datatype_size,
                           count_thread,
                           datatype, op, comms.dup_comm[k_comm][thread_num]);
   }
   }
}
   }
   else {
      err = PMPI_Allreduce (sendbuf, recvbuf, count, datatype, op, comm);
   }
}
   else {
      err = PMPI_Allreduce (sendbuf, recvbuf, count, datatype, op, comm);
}
}
return err;
     }

请注意,comm_duplicate(comm,mep_num_threads) 是一个复制通信器的函数。我们将使用它与 LD_PRELOAD 来替换默认的 MPI_Allreduce

使用 Multi-EP 运行

修改后的命令行为:

$ export I_MPI_THREAD_SPLIT=1
$ export I_MPI_THREAD_RUNTIME=openmp

$ export MEP_ALLREDUCE_THRESHOLD=1000
$ export MEP_ENABLE=1
$ export OMP_NUM_THREADS=2
$ export MEP_NUM_THREADS=2

$ LD_PRELOAD= lib_mep.so
$ mpiexec.hydra -ppn ${PPN} -n ${NUM_MPI} ./vasp_std

性能结果如 **表 4** 所示。

表 4. Intel Xeon Gold 处理器集群的最终 LOOP+ 和 MPI_Allreduce 时间(报告了 OpenMP x MPI 的最佳组合),与初始数据(报告了 OpenMP x MPI 的最佳组合)进行比较

提高性能

使用 APS,我们发现 MPI_Allreduce 是应用程序中的一个性能“热点”。借助新的 Intel MPI Library 2019 技术预览版和 Intel OPA,我们能够更有效地利用系统硬件来加速 MPI_Allreduce 并改进整体应用程序。仅通过软件修改,观察到的最大加速比为 MPI_Allreduce 达到 2.34 倍,LOOP+ 达到 1.16 倍。

© . All rights reserved.