mpi4py Reduce() 中可能存在的缓冲区大小限制

问题描述 投票:4回答:1

设置

我正在使用 mpi4py 来对多个进程的 numpy 数组进行元素化的缩减。我们的想法是,numpy数组会被元素相加,所以如果我有两个进程,并且每个进程都有数组,那么在还原之后,我应该有两个数组。

Rank 0: [1, 1, 1]
Rank 1: [2, 3, 4]

在还原之后,我应该有

[3, 4, 5]

这种情况下,这样的短数组,效果还不错。

问题

然而,在我的实际使用案例中,这些数组是相当长的(array_length 在我下面的示例代码中)。) 如果我发送长度小于或等于505个元素的numpy数组,我没有任何问题,但是超过这个长度,我得到了如下的输出。

[83621b291fb8:01112] Read -1, expected 4048, errno = 1

我一直找不到任何记录在案的原因。然而有趣的是,506*8 = 4048,这让我怀疑我在 mpi4py 或 MPI 本身的某个地方遇到了 4kb 缓冲区限制。

一个可能的解决方法

我设法解决了这个问题,把要元素化的numpy数组分解成大小为200的块(只是一个小于505的任意数),并在每个块上调用Reduce(),然后在主进程上重新组装。但是,这有点慢。

我的问题。

  1. 有谁知道这是否真的是由于mpi4pyMPI的4kb缓冲区限制(或类似的)造成的?

  2. 是否有更好的解决方案,而不是像我目前所做的那样,将数组切成片,并多次调用Reduce(),因为这看起来运行得有点慢。


一些例子

以下是说明以下问题的代码

  1. 问题,并
  2. 一个可能的解决方案,基于将数组切成较短的片段,并进行大量的MPI Reduce()调用,而不是只进行一次(用 use_slices 布尔型)

随着 case=0use_slices=False可以看出,错误(数组长度506)。

随着 case=1use_slices=False误差消失(数组长度505)

随着 use_slices=True误差消失,无论 case,即使 case 被设置为一个很长的数组(case=2)


示例代码

import mpi4py, mpi4py.MPI
import numpy as np

###### CASE FLAGS ########
# Whether or not to break the array into 200-element pieces
# before calling MPI Reduce()
use_slices = False

# The total length of the array to be reduced:
case = 0
if case == 0:
    array_length= 506
elif case == 1:
    array_length= 505
elif case == 2:
    array_length= 1000000

comm = mpi4py.MPI.COMM_WORLD
rank = comm.Get_rank()
nprocs = comm.Get_size()


array_to_reduce = np.ones(array_length)*(rank+1)  #just some different numbers per rank
reduced_array = np.zeros(array_length)

if not use_slices:
    comm.Reduce(array_to_reduce,
                reduced_array,
                op = mpi4py.MPI.SUM,
                root = 0)

    if rank==0:
        print(reduced_array)
else:  # in this case, use_slices is True
    array_slice_length = 200
    sliced_array = np.array_split(array_to_reduce, range(200, array_length, 200))

    reduced_array_using_slices = np.array([])
    for array_slice in sliced_array:
        returnedval = np.zeros(shape=array_slice.shape)
        comm.Reduce(array_slice,
                    returnedval,
                    op = mpi4py.MPI.SUM,
                    root = 0)
        reduced_array_using_slices=np.concatenate((reduced_array_using_slices, returnedval))
        comm.Barrier()

    if rank==0:
        print(reduced_array_using_slices)

图书馆版本

从源头编纂----openmpi 3.1.4mpi4py 3.0.3

python docker mpi openmpi mpi4py
1个回答
3
投票

这不是一个问题 mpi4py 本身。问题来自于跨内存附件(CMA)系统的调用。process_vm_readv()process_vm_writev() Open MPI的共享内存BTL(字节传输层,也就是在行列之间移动字节的东西)用来加速在同一节点上运行的行列之间的共享内存通信,避免在共享内存缓冲区中复制两次数据。这种机制涉及到一些设置开销,因此只用于较大的消息,这也是为什么在消息大小越过急迫阈值后才开始出现问题的原因。

CMA属于 ptrace 内核服务系列。Docker使用 seccomp 来限制容器内运行的进程可以进行哪些系统调用。容器中的 默认配置文件 具有以下特点:

    {
        "names": [
            "kcmp",
            "process_vm_readv",
            "process_vm_writev",
            "ptrace"
        ],
        "action": "SCMP_ACT_ALLOW",
        "args": [],
        "comment": "",
        "includes": {
            "caps": [
                "CAP_SYS_PTRACE"
            ]
        },
        "excludes": {}
    },

限制性 ptrace-容器的相关系统调用。CAP_SYS_PTRACE 能力,而该能力不在默认授予的能力之列。因此,要想让Open MPI在Docker中正常运行,需要通过调用 docker run 并附加以下选项。

--cap-add=SYS_PTRACE

这将使Open MPI正常运行,但启用了以下选项 ptrace 可能会在某些容器部署中带来安全风险。因此,一个替代方案是禁止Open MPI使用CMA。这可以通过设置一个 MCA 参数来实现,该参数取决于 Open MPI 的版本和使用的共享内存 BTL。

  • 对于 sm BTL(Open MPI 1.8之前的默认值)。--mca btl_sm_use_cma 0
  • 对于 vader BTL(自Open MPI 1.8以来的默认值)。--mca btl_vader_single_copy_mechanism none

禁用单拷贝机制将迫使BTL通过共享内存缓冲区使用流水线拷贝,这可能会或不会影响MPI作业的运行时间。

读取 此处 关于共享内存BTL和Open MPI中的零(单?)拷贝机制。

© www.soinside.com 2019 - 2024. All rights reserved.