【问题标题】:MPI neighbor reduce operationMPI邻居减少操作
【发布时间】:2018-01-30 15:44:20
【问题描述】:

此时我觉得我需要MPI_Neighbor_allreduce 这样的东西,但我知道it doesn't exist

前言

给定一个描述 3D 物理域如何在进程之间分布的 3D MPI 笛卡尔拓扑,我编写了一个函数 probe,它要求一个标量值(应该放在一个简单的 REAL :: val 中)给定 3域内点的坐标。

只有1248 进程实际参与val 的计算。

  • 1 如果该点是内部进程子域(并且它没有涉及邻居),
  • 2 如果该点位于 2 个进程的子域之间的面上(并且每个子域都涉及 1 个邻居),
  • 4 如果该点在 4 个进程的子域之间在一边(并且每个子域都涉及 2 个邻居),
  • 8 如果该点是 8 个进程的子域之间的一个顶点(并且每个进程都涉及 3 个邻居)。

在像现在一样调用probe 之后,每个进程都持有val,这对于所涉及的进程0NaN 来说是一些价值(我通过(de)评论适当的行来决定)不涉及的过程。每个进程都知道它是否参与(通过LOGICAL :: found 变量),但不知道它是否是唯一参与的进程,如果不是,也不知道谁是参与的邻居。

1涉及的进程的情况下,该唯一进程的唯一值就足够了,并且该进程可以编写它,使用它或任何需要的东西。 在后三种情况下,必须计算所涉及进程的不同标量值的总和(并除以邻居的数量+1,即自包含)。

问题

完成这种通信和计算的最佳策略是什么?

我在考虑什么解决方案

我正在考虑以下可能性。

  1. 每个进程在调用probe之前执行val = 0,然后可以使用MPI_(ALL)REDUCE,(所涉及的进程通常以val /= 0参与,所有其他进程以val == 0参与),但这意味着如果要求val 获得更多点,则这些点将被连续处理,即使每个点所涉及的过程集不与其他集重叠。
  2. 每个进程调用MPI_Neighbor_allgather 以在相邻进程之间共享found,以使每个参与的进程知道6 邻居中的哪一个参与总和,然后执行单独的MPI_send(s ) 和MPI_recv(s) 与val 通信。但这仍将涉及每个进程(即使每个进程仅与 6 邻居通信。
  3. 也许最好的选择是每个进程定义一个由自身加上6邻居组成的通信器,然后使用。

编辑

对于@JorgeBellón 提到的关于死锁风险的问题,我最初通过在MPI_RECV 之前调用MPI_SEND 进行正向通信来解决它,那些对应于@ 中偶数索引的通信987654357@,反之亦然。作为一种特殊情况,这不能处理只有两个进程的周期性方向(因为两个进程中的每一个都会将另一个进程视为正负方向的邻居,因此导致两个进程都调用MPI_SENDMPI_RECV 顺序相同,从而导致死锁);这种特殊情况的解决方案是对who_is_involved(我在我的代码中称为found_neigh)进行以下ad-hoc编辑:

DO id = 1, ndims
    IF (ALL(found_neigh(2*id - 1:2*id))) found_neigh(2*id -1 + mycoords(id)) = .FALSE.
END DO

作为读者参考,我目前实现的解决方案(我不太满意的解决方案)如下。

found = ... ! .TRUE. or .FALSE. depending whether the process  is/isn't involved in computation of val
IF (      found) val = ... ! compute own contribution
IF (.NOT. found) val = NaN

! share found among neighbors
found_neigh(:) = .FALSE.
CALL MPI_NEIGHBOR_ALLGATHER(found, 1, MPI_LOGICAL, found_neigh, 1, MPI_LOGICAL, procs_grid, ierr)
found_neigh = found_neigh .AND. found

! modify found_neigh to deal with special case of TWO processes along PERIODIC direction
DO id = 1, ndims
    IF (ALL(found_neigh(2*id - 1:2*id))) found_neigh(2*id -1 + mycoords(id)) = .FALSE.
END DO

! exchange contribution with neighbors
val_neigh(:) = NaN
IF (found) THEN
    DO id = 1, ndims
        IF (found_neigh(2*id))     THEN
            CALL MPI_SEND(val,                 1, MPI_DOUBLE_PRECISION, idp(id), 999, MPI_COMM_WORLD,                    ierr)
            CALL MPI_RECV(val_neigh(2*id),     1, MPI_DOUBLE_PRECISION, idp(id), 666, MPI_COMM_WORLD, MPI_STATUS_IGNORE, ierr)
        END IF
        IF (found_neigh(2*id - 1)) THEN
            CALL MPI_RECV(val_neigh(2*id - 1), 1, MPI_DOUBLE_PRECISION, idm(id), 999, MPI_COMM_WORLD, MPI_STATUS_IGNORE, ierr)
            CALL MPI_SEND(val,                 1, MPI_DOUBLE_PRECISION, idm(id), 666, MPI_COMM_WORLD,                    ierr)
        END IF
    END DO
END IF

! combine own contribution with others
val = somefunc(val, val_neigh)

【问题讨论】:

    标签: mpi nearest-neighbor topology cartesian-coordinates


    【解决方案1】:

    正如你所说,MPI_Neighbor_allreduce 不存在。 您可以创建仅包含相邻进程的派生通信器,然后对它们执行常规MPI_Allreduce。每个进程在 3D 网格中最多可以有 7 个通信器。

    • 将特定进程放置在模板中心的通信器。
    • 每个相邻进程的相应通信器。

    这可能是一个相当昂贵的过程,但这并不意味着它值得(例如,HPLinpack 广泛使用派生的通信器)。

    如果您已经有了笛卡尔拓扑,一个好的方法是使用MPI_Neighbor_allgather。这样一来,您不仅可以知道有多少邻居参与其中,还可以知道他们是谁。

    int found; // logical: either 1 or 0
    int num_neighbors; // how many neighbors i got
    int who_is_involved[num_neighbors]; // unknown, to be received
    MPI_Neighbor_allgather( &found, ..., who_is_involved, ..., comm );
    
    int actually_involved = 0;
    int r = 0;
    MPI_Request reqs[2*num_neighbors];
    for( int i = 0; i < num_neighbors; i++ ) {
      if( who_is_involved[i] != 0 ) {
        actually_involved++;
        MPI_Isend( &val, ..., reqs[r++]);
        MPI_Irecv( &val, ..., reqs[r++]);
      }
    }
    MPI_Waitall( r, reqs, MPI_STATUSES_IGNORE );
    

    请注意,我使用的是非阻塞点对点例程。这在大多数情况下很重要,因为MPI_Send 可能会等待接收者调用MPI_Recv。在所有进程中无条件调用MPI_Send,然后调用MPI_Recv,可能会导致死锁(参见MPI 3.1 standard section 3.4)。

    另一种可能性是在一次通信中同时发送实际值和实际值,从而减少传输次数。由于所有进程都包含在MPI_Neighbor_allgather 中,因此您可以使用它来完成所有工作(对于传输的数据量的小幅增加,它确实得到了回报)。

    INTEGER :: neighbor, num_neighbors, found
    REAL :: val
    REAL :: sendbuf(2)
    REAL :: recvbuf(2,num_neighbors)
    
    sendbuf(1) = found
    sendbuf(2) = val
    CALL MPI_Neighbor_allgather( sendbuf, 1, MPI_2REAL, recvbuf, num_neighbors, MPI_2REAL, ...)
    
    DO neighbor = 1,num_neighbors
      IF recvbuf(1,neighbor) .EQ. 1 THEN
         ! use neighbor val, placed in recvbuf(2,neighbor)
      END IF
    END DO
    

    【讨论】:

    • 我不清楚如何将第一个解决方案付诸实践(答案中的前两个项目符号)。关于第二种方法,我已经定义了MPI笛卡尔拓扑;需要指出的是,对于所涉及的每个过程,并非所有 6 个周围过程都涉及。请再次阅读问题并在需要时帮助我改进它。到目前为止,我一直依赖我想到的解决方案,但我想我会尝试你最后一个要点中的那个。有时间我会尽快通知你。
    • 我误解了你的问题。我已经更新了答案。
    • 您编写的代码块基本上是我在第 2 点中假设的解决方案的第一部分。(到目前为止,我实际上已经采用了)。事实上,我以您的who_is_involved 为基础,在参与共享(和总和,和blablabla)val 的那些进程中执行单个MPI_SEND/MPI_RECV
    • 如果您在邻居之间执行多个点对点通信(发送/接收),我强烈建议您改用MPI_ISENDMPI_IRECVMPI_WAITALL 例程。它降低了死锁的风险(用户代码中的错误顺序)并提高了效率(允许通信中的并行性)。我认为这个替代方案是最好的,因为像MPI_PROBE 这样的意外接收 例程非常低效。
    • Jorge Bellón,请看一下我的自我回答以获得更多见解。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-08
    • 2017-03-21
    • 2017-04-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多