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

Possible buffer size limit in mpi4py Reduce()

设置

我正在使用 mpi4py element-wise 减少跨多个进程的 numpy 数组。这个想法是将 numpy 数组相加 element-wise,这样如果我有两个进程,并且每个进程都有数组:

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

减少后我应该

[3, 4, 5]

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

问题

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

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

而且我找不到任何有据可查的原因。然而,有趣的是,506*8 = 4048,这 - 假设一些 header 数据 - 让我怀疑我在 mpi4py 或 MPI 本身的某个地方达到了 4kb 的缓冲区限制。

一个可能Work-Around

我设法解决了这个问题,方法是分解我想 element-wise 缩小为大小为 200 的块(只是小于 505 的任意数字),然后调用 Reduce()每个块,然后在主进程上重新组装。但是,这有点慢。

我的问题:

  1. 有谁知道这是否确实是由于 mpi4py/MPI 中的 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.4 mpi4py 3.0.3

这不是 mpi4py 本身的问题。问题来自跨内存附加 (CMA) 系统调用 process_vm_readv()process_vm_writev(),共享内存 BTL(字节传输层,a.k.a。在队列之间移动字节的东西) Open MPI 的使用通过避免将数据复制到共享内存缓冲区和从共享内存缓冲区复制两次来加速同一节点上 运行 等级之间的共享内存通信。此机制涉及一些设置开销,因此仅用于较大的消息,这就是问题仅在消息大小超过急切阈值后才开始出现的原因。

CMA 是 ptrace 内核服务系列的一部分。 Docker 使用 seccomp 限制容器内进程 运行ning 可以进行的系统调用。 default profile 具有以下内容:

    {
        "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 能力的容器,这不属于默认授予的能力。因此,要在 Docker 中启用 Open MPI 的正常功能,需要通过使用以下附加选项调用 docker run 来授予所需的功能:

--cap-add=SYS_PTRACE

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

  • 对于 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 作业的 运行 时间。

阅读 here 关于共享内存 BTL 和 Open MPI 中的零(单?)复制机制。