Возможный предел размера буфера в mpi4py Reduce()

Установка

Я использую mpi4py для поэлементного уменьшения массива numpy в нескольких процессах. Идея состоит в том, что массивы numpy суммируются поэлементно, так что если у меня есть два процесса, и у каждого есть массивы:

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

после сокращения у меня должно было быть

[3, 4, 5]

Этот случай с такими короткими массивами работает нормально.

Эта проблема

Однако в моем реальном случае использования эти массивы довольно длинные (array_length в моем примере кода ниже). У меня нет проблем, если я отправляю массивы numpy длиной меньше или равной 505 элементам, но выше этого я получаю следующий вывод:

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

и я не смог найти какой-либо документированной причины, почему это может быть. Интересно, однако, что 506 * 8 = 4048, что - при условии некоторых данных заголовка - заставляет меня подозревать, что я достиг предела буфера в 4 КБ где-то внутри mpi4py или самого MPI.

Один из возможных обходных путей

Мне удалось обойти эту проблему, разбив массив numpy, который я хочу поэлементно уменьшить, на куски размером 200 (просто произвольное число меньше 505) и вызвав Reduce () для каждого фрагмента, а затем повторная сборка на мастер процесс. Однако это несколько медленно.

Мои вопросы:

  1. Кто-нибудь знает, действительно ли это связано с ограничением буфера в 4 КБ (или аналогичным) в mpi4py/MPI?

  2. Есть ли лучшее решение, чем разрезать массив на части и делать много вызовов Reduce(), как я сейчас делаю, так как это кажется немного медленным.


Несколько примеров

Ниже приведен код, иллюстрирующий

  1. проблема, и
  2. одно возможное решение, основанное на разрезании массива на более короткие части и выполнении большого количества вызовов MPI 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


person carthurs    schedule 17.05.2020    source источник
comment
Это поможет, если вы предоставите версию библиотеки MPI и среды выполнения, с которой собран mpi4py.   -  person Hristo Iliev    schedule 18.05.2020
comment
openmpi 3.1.4, mpi4py 3.0.3. Вопрос отредактирован, чтобы отразить это.   -  person carthurs    schedule 18.05.2020
comment
Попробуйте mpirun --mca coll ^tuned ... и посмотрите, поможет ли это   -  person Gilles Gouaillardet    schedule 18.05.2020
comment
Вы запускаете два ранга на одном узле или на двух разных узлах? Если вы работаете на отдельных узлах, какая у вас сеть связи?   -  person Hristo Iliev    schedule 18.05.2020
comment
@GillesGouaillardet - спасибо, к сожалению, это ничего не изменило.   -  person carthurs    schedule 18.05.2020
comment
@HristoIliev - я на одном узле. Я работаю в контейнере Linux Docker на рабочем столе Linux с одним физическим процессором (четыре ядра). Я не думаю, что это проблема Docker — подобные ошибки уже довольно давно преследуют меня при различных настройках, включая родной Linux и Linux на виртуальной машине Virtualbox. Ubuntu 12.04, 16.04, openSuse 15.2. Я получаю аналогичную ошибку от create_dataset() h5py (скомпилированной против MPI), которую я сейчас пытаюсь обойти. Мог ли я неправильно скомпилировать openMPI? - хотя я подозреваю, что пробовал репозиторий ОС openMPI по крайней мере на некоторых из этих хостов...   -  person carthurs    schedule 18.05.2020
comment
@HristoIliev - если это поможет, я могу избежать сообщения об ошибке h5py create_dataset(), не указав maxshape= в этом вызове. Это очень похоже на эту предыдущую (нерешенную) проблему, с которой я столкнулся в другой системе: of-mpi-threads" title="hdf5 h5py segfault с mpi с определенным количеством потоков mpi"> stackoverflow.com/questions/56131655/ - я был бы удивлен, если бы это не было связано с проблемой Reduce() Я вижу сейчас.   -  person carthurs    schedule 18.05.2020
comment
Итак, вы вызываете mpirun внутри своего контейнера, и, следовательно, ваши 2 задачи MPI будут выполняться внутри одного и того же контейнера?   -  person Gilles Gouaillardet    schedule 18.05.2020
comment
Что произойдет, если вы не запустите в контейнере Docker и с Open MPI, поставляемым дистрибутивом? Проверьте свои пути и каталоги библиотек, чтобы убедиться, что вы не смешиваете разные версии.   -  person Hristo Iliev    schedule 18.05.2020
comment
@GillesGouaillardet да, именно так - например, я вижу ошибку, даже если открываю контейнер в интерактивном режиме и ввожу mpirun... в оболочку контейнера.   -  person carthurs    schedule 18.05.2020
comment
ошибка EPERM, и для меня это указывает на проблему, связанную с контейнером.   -  person Gilles Gouaillardet    schedule 18.05.2020
comment
сколько интерфейсов в вашем контейнере? вы можете ограничиться одним с (например) mpirun --mca btl_tcp_if_include eth0 ...   -  person Gilles Gouaillardet    schedule 18.05.2020
comment
У меня case = 2, use_slices = False отлично работает на Ubuntu 16.04 с Open MPI 2.1.1 из дистрибутива.   -  person Hristo Iliev    schedule 18.05.2020
comment
@GillesGouaillardet, разве одноузловые запуски не используют BTL с общей памятью?   -  person Hristo Iliev    schedule 18.05.2020
comment
@GillesGouaillardet - только один интерфейс -eth0 - (плюс петля). Использование btl_tcp_if_include eth0 дает ту же ошибку.   -  person carthurs    schedule 18.05.2020
comment
@HristoIliev - да, я проверил, и у меня тоже нет ошибки изначально (openMPI 1.10.7). Однако я очень подозреваю, что здесь может быть какой-то UB. - Я продолжу изучение - спасибо за ваши советы, оба (+@GillesGouaillardet).   -  person carthurs    schedule 18.05.2020
comment
Что, если вы mpirun -mca btl vader,self... или mpirun -mca btl sm,self или mpirun -mca tcp,self?   -  person Gilles Gouaillardet    schedule 18.05.2020
comment
@GillesGouaillardet - по порядку: 1) тот же эффект; 2) ошибка Начиная с версии 3.0.0 sm BTL больше не доступен в Open MPI.; 3) не удалось найти исполняемый файл - но я думаю, вы имели в виду mpirun -mca btl tcp,selfbtl). Это не отображает ошибку. Любые мысли о том, почему? Мне все еще интересно, является ли это UB, а не исправлением, потому что я пересобрал свой контейнер с помощью openMPI 1.10.7 (из исходного кода, поскольку эта версия, по-видимому, изначально работала в моей системе), и вместо исходной ошибки возникает ошибка сообщение, независимо от того, использую ли я tcp. Других версий MPI в контейнере нет.   -  person carthurs    schedule 18.05.2020
comment
Может быть, потому что вы находитесь в контейнере :-) как насчет mpirun -mca btl_vader_single_copy_mechanism none ...?   -  person Gilles Gouaillardet    schedule 18.05.2020
comment
Это тоже работает — опять же в исходном контейнере с openMPI 3.1.4. Как вы думаете, что происходит? Что-то связанное с тем, как контейнер взаимодействует с MPI?   -  person carthurs    schedule 18.05.2020
comment
по умолчанию btl/vader использует механизм единственного копирования, основанный на process_vm_readv() и process_vm_writev(), и эти системные вызовы не разрешены внутри контейнера докера по умолчанию.   -  person Gilles Gouaillardet    schedule 18.05.2020
comment
конфигурация по умолчанию для Docker, похоже, ограничить вызовы CMA, используемые Open MPI, контейнерами с возможностью CAP_SYS_PTRACE. Попробуйте запустить контейнер с --add-cap=SYS_PTRACE, а затем попробуйте без установки btl_vader_single_copy_mechanism.   -  person Hristo Iliev    schedule 19.05.2020
comment
@HristoIliev Я проверил ваше предложение, и оно тоже работает: docker run --cap-add=SYS_PTRACE... без установки btl_vader_single_copy_mechanism на none. (Обратите внимание, что синтаксис --cap-add, а не --add-cap). В моем случае я не заметил разницы в производительности между двумя решениями.   -  person carthurs    schedule 19.05.2020
comment
Я подведу итоги в ответе на случай, если кто-то еще запустит Open MPI в контейнерах докеров и столкнется с той же проблемой.   -  person Hristo Iliev    schedule 19.05.2020


Ответы (1)


Это не проблема с mpi4py как таковым. Проблема возникает из-за системных вызовов Cross-Memory Attach (CMA) process_vm_readv() и process_vm_writev(), которые BTL с общей памятью (Byte Transfer Layers, также известные как вещи, которые перемещают байты между рангами) Open MPI используют для ускорения связи с общей памятью между рангами, которые выполняться на одном узле, избегая двойного копирования данных в буфер с общей памятью и из него. Этот механизм включает в себя некоторые накладные расходы на настройку и поэтому используется только для больших сообщений, поэтому проблема начинает возникать только после того, как размер сообщения пересекает порог ожидания.

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 может представлять угрозу безопасности в определенных развертываниях контейнеров. Следовательно, альтернативой является отключение использования CMA Open MPI. Это достигается установкой параметра 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.

person Hristo Iliev    schedule 19.05.2020