我正在使用 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(),然后在主进程上重新组装。但是,这有点慢。
有谁知道这是否真的是由于mpi4pyMPI的4kb缓冲区限制(或类似的)造成的?
是否有更好的解决方案,而不是像我目前所做的那样,将数组切成片,并多次调用Reduce(),因为这看起来运行得有点慢。
以下是说明以下问题的代码
use_slices
布尔型)随着 case=0
和 use_slices=False
可以看出,错误(数组长度506)。
随着 case=1
和 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)
从源头编纂----openmpi 3.1.4
mpi4py 3.0.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中的零(单?)拷贝机制。