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

car*_*urs 5 python mpi openmpi docker mpi4py

设置

我正在使用 mpi4py 以元素方式减少跨多个进程的 numpy 数组。这个想法是将 numpy 数组按元素求和,这样如果我有两个进程,并且每个进程都有数组:

Rank 0: [1, 1, 1]
Rank 1: [2, 3, 4]
Run Code Online (Sandbox Code Playgroud)

减少后我应该有

[3, 4, 5]
Run Code Online (Sandbox Code Playgroud)

这种情况下,使用如此短的数组,效果很好。

问题

但是,在我的实际用例中,这些数组很长(array_length在我下面的示例代码中)。如果我发送长度小于或等于 505 个元素的 numpy 数组,我没有问题,但在此之上,我得到以下输出:

[83621b291fb8:01112] Read -1, expected 4048, errno = 1
Run Code Online (Sandbox Code Playgroud)

我一直无法找到任何记录在案的原因。然而,有趣的是,506*8 = 4048,这 - 假设有一些头数据 - 让我怀疑我在 mpi4py 或 MPI 本身的某处达到了 4kb 缓冲区限制。

一种可能的解决方法

我设法通过分解 numpy 数组来解决这个问题,我想按元素减少到大小为 200 的块(只是小于 505 的任意数字),并在每个块上调用 Reduce(),然后在主进程。然而,这有点慢。

我的问题:

  1. 有谁知道这是否确实是由于 mpi4py/MPI 中的 4kb 缓冲区限制(或​​类似)?

  2. 有没有比将数组切片并像我目前所做的那样多次调用 Reduce() 更好的解决方案,因为这似乎运行起来有点慢。


一些例子

下面是说明

  1. 问题,以及
  2. 一种可能的解决方案,基于将数组切成较短的部分并进行大量 MPI Reduce() 调用,而不仅仅是一个(由use_slices布尔值控制)

使用case=0and use_slices=False,可以看到错误(数组长度 506)

使用case=1and use_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)
Run Code Online (Sandbox Code Playgroud)

库版本

从源代码编译 - openmpi 3.1.4 mpi4py 3.0.3

Hri*_*iev 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": {}
    },
Run Code Online (Sandbox Code Playgroud)

将与相关的系统调用限制ptrace为具有该CAP_SYS_PTRACE功能的容器,该功能不属于默认授予的功能。因此,为了使 Open MPI 在 Docker 中正常运行,需要通过调用docker run以下附加选项来授予所需的功能:

--cap-add=SYS_PTRACE
Run Code Online (Sandbox Code Playgroud)

这将允许 Open MPI 正常运行,但启用ptrace可能会在某些容器部署中带来安全风险。因此,另一种方法是禁用 Open MPI 使用 CMA。这是通过根据 Open MPI 的版本和使用的共享内存 BTL 设置 MCA 参数来实现的:

  • 对于smBTL(Open MPI 1.8 之前的默认值):--mca btl_sm_use_cma 0
  • 对于vaderBTL(自 Open MPI 1.8 起默认):--mca btl_vader_single_copy_mechanism none

禁用单副本机制将强制 BTL 通过共享内存缓冲区使用管道复制,这可能会也可能不会影响 MPI 作业的运行时间。

请阅读此处了解 Open MPI 中的共享内存 BTL 和零(单?)复制机制。