MPI Fortran程序在有限数量的MPI进程下挂起

3

我正在开发一个MPI应用程序,当使用超过2071个MPI进程启动时,该应用程序会挂起。我已经成功创建了一个小型的复现例:

program main
use mpi
integer :: ierr,rank
call mpi_init(ierr)
call mpi_comm_rank(MPI_COMM_WORLD,rank,ierr)
if (rank.eq.0) print *,'Start'
call test_func(ierr)
if (ierr.ne.0) call exit(ierr)
call mpi_finalize(ierr)
if (rank.eq.0) print *,'Stop'

contains

subroutine test_func(ierr)
integer, intent(out) :: ierr
real :: send,recv
integer :: i,j,status(MPI_STATUS_SIZE),mpi_rank,mpi_size,ires
character(len=10) :: procname
real(kind=8) :: t1,t2
ierr=0
call mpi_comm_size(MPI_COMM_WORLD,mpi_size,ierr)
call mpi_comm_rank(MPI_COMM_WORLD,mpi_rank,ierr)
call mpi_get_processor_name(procname, ires, ierr)
call mpi_barrier(MPI_COMM_WORLD,ierr)
t1 = mpi_wtime()
do j=0,mpi_size-1
  if (mpi_rank.eq.j) then
    do i=0,mpi_size-1
      if (i.eq.j) cycle
      call MPI_RECV(recv,1,MPI_REAL,i,0,MPI_COMM_WORLD,status,ierr)
      if (ierr.ne.0) return
      if (i.eq.mpi_size-1) print *,'Rank ',j,procname,' done'
    enddo
  else
    call MPI_SEND(send,1,MPI_REAL,j,0,MPI_COMM_WORLD,ierr)
    if (ierr.ne.0) return
  endif
enddo
call mpi_barrier(MPI_COMM_WORLD,ierr)
t2 = mpi_wtime()
if (mpi_rank.eq.0) print*,"time send/recv = ",t2-t1
end subroutine test_func
end program main

当我以少于2071个MPI进程运行此程序时,它可以正常工作,但当我使用多于2072个进程运行时,它就会停滞不前,就好像在send/recv上出现了死锁。 使用I_MPI_DEBUG=5运行该程序的输出为:

[0] MPI startup(): Intel(R) MPI Library, Version 2019 Update 9 Build 20200923 (id: abd58e492)
[0] MPI startup(): Copyright (C) 2003-2020 Intel Corporation. All rights reserved.
[0] MPI startup(): library kind: release
[0] MPI startup(): libfabric version: 1.10.1-impi
[0] MPI startup(): libfabric provider: verbs;ofi_rxm
[0] MPI startup(): Rank Pid Node name Pin cpu
[0] MPI startup(): 0 48487 r30i0n0 {0,24}
...
[0] MPI startup(): 2070 34737 r30i4n14 {18,19,20,42,43,44}
[0] MPI startup(): I_MPI_CC=icc
[0] MPI startup(): I_MPI_CXX=icpc
[0] MPI startup(): I_MPI_FC=ifort
[0] MPI startup(): I_MPI_F90=ifort
[0] MPI startup(): I_MPI_F77=ifort
[0] MPI startup(): I_MPI_ROOT=/data_local/sw/intel/RHEL7/compilers_and_libraries_2020.4.304/linux/mpi
[0] MPI startup(): I_MPI_MPIRUN=mpirun
[0] MPI startup(): I_MPI_HYDRA_RMK=lsf
[0] MPI startup(): I_MPI_HYDRA_TOPOLIB=hwloc
[0] MPI startup(): I_MPI_INTERNAL_MEM_POLICY=default
[0] MPI startup(): I_MPI_EXTRA_FILESYSTEM=1
[0] MPI startup(): I_MPI_EXTRA_FILESYSTEM_FORCE=lustre
[0] MPI startup(): I_MPI_DEBUG=5

问题1:有没有解释这种行为的原因?

请注意,如果我通过广播模式来改变发送/接收通信模式

do j=0,mpi_size-1
  if (mpi_rank.eq.j) then
    call MPI_BCAST(send,1,MPI_REAL,j,MPI_COMM_WORLD,ierr)
  else
    call MPI_BCAST(recv,1,MPI_REAL,j,MPI_COMM_WORLD,ierr)
  endif
  if (ierr.ne.0) return  
  print *,'Rank ',j,procname,' done'
enddo

或者是allgather

call MPI_ALLGATHER(MPI_IN_PLACE,0,MPI_DATATYPE_NULL,recv,1,MPI_REAL,MPI_COMM_WORLD,ierr)
print *,'Rank ',mpi_rank,procname,' done '

程序运行得更快(当然)但最多使用了4000个 MPI 进程(我没有试过更多的 MPI 进程)。然而,我不能通过 bcast 或者 allgather 来改变原始应用中的通信发送/接收模式。

问题2: 当我在2064个 MPI 进程(86个节点,每个节点有24个核心)上运行原始应用程序时,MPI 缓冲区的消耗内存约为每个节点60 GB,并且在1032个 MPI 进程(43个节点,每个节点有24个核心)上运行时,它大约是每个节点30 GB。 是否有一种方式(环境变量等...)来减少这种消耗的内存数量?

非常感谢您的帮助

Thierry


欢迎,我建议您参加[导览]。如果您有两个不同的问题,最好甚至必须将其分别发布在两个不同的问题帖子中,以便可以在单个答案帖子中实际回答您的帖子。过于宽泛的问题可能会被关闭。另请参见[提问]。 - Vladimir F Героям слава
3
关于死锁问题,可能是由于未处理的发送操作数量过多(MPI可能正在尝试缓冲它们以进行异步发送)而导致的资源不足。如果将MPI_Send替换为MPI_Ssend会发生什么?这样做效率可能会降低,但由于MPI_Ssend保证同步(直到与接收调用匹配后才会返回),因此您不会消耗太多系统资源。如果使用Ssend可以正常运行而使用Send则不能,那么就说明存在MPI资源限制。 - David Henty
另一个值得尝试的技巧是在do j循环中每隔n次(n必须经过实证确定。1很可能是过度的)添加MPI_Barrier()。和/或者将所有的MPI_Irecv()都发布,然后再执行MPI_Waitall()。目标是减少意外消息的数量,这些消息带来了一些副作用,如高资源消耗、减速和死锁。 - Gilles Gouaillardet
你在发出 MPI_Irecv() 和执行 MPI_Send() 之间是否使用了 MPI_Barrier() - Gilles Gouaillardet
1
@braconnier 我只想评论一下,在3D情况下,通常在FFT中,mpi_alltoall和mpi_alltoallv是标准的方法 - 例如查看任何数量的平面波DFT代码。如果那里使用的东西不能扩展到4D,我会非常惊讶的。 - Ian Bush
显示剩余5条评论
1个回答

0

我同意之前的评论,看起来你在单个进程上启动了太多的recvs,MPI实现可能会耗尽一些内部缓冲区。

我不建议使用Ssends或引入barriers。只需将所有MPI_Recv替换为MPI_Irecv,并将MPI_Send替换为MPI_Isend即可。这不会引入任何同步并避免死锁。然后,您还可以任意更改发布发送和接收的顺序,例如,您可以配对发送和接收,而不是为每个外部循环迭代执行大型for循环中的接收。

关于您的第二个关于内存消耗的问题:可能是相同的:您在内部循环中发布的接收数量与进程数成线性增长,因此处理这些未完成消息所需的内存量也随之增加。

最后,我也同意之前的评论,这看起来非常像一个集体MPI调用的情况,具体取决于您没有在此处显示的应用程序代码,但MPI_Alltoallv可能是一个不错的选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接