Pybind11: можно ли использовать mpi4py?

Возможно ли в Pybind11 использовать mpi4py на стороне Python, а затем передать коммуникатор на сторону C++?

Если да, то как это будет работать?

Если нет, то возможно ли это, например, с помощью Boost? И если да, то как это будет сделано?

Я искал в Интернете буквально несколько часов, но ничего не нашел.


person Quasar    schedule 13.03.2018    source источник
comment
Вот, по крайней мере, обсуждение того же вопроса, который вы задаете, может быть, это поможет: github.com/pybind /pybind11/issues/23   -  person NOhs    schedule 25.04.2018


Ответы (2)


Это действительно возможно. Как было указано в комментариях Джона Цвинка, MPI_COMM_WORLD автоматически укажет на правильный коммуникатор, поэтому ничего не нужно передавать от python на сторону C++.

Пример

Во-первых, у нас есть простой модуль pybind11, который предоставляет единственную функцию, которая просто выводит некоторую информацию MPI (взятую из одного из множества онлайн-учебников). Чтобы скомпилировать модуль, см. здесь пример cmake pybind11.

#include <pybind11/pybind11.h>
#include <mpi.h>
#include <stdio.h>

void say_hi()
{
    int world_size;
    MPI_Comm_size(MPI_COMM_WORLD, &world_size);
    int world_rank;
    MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
    char processor_name[MPI_MAX_PROCESSOR_NAME];
    int name_len;
    MPI_Get_processor_name(processor_name, &name_len);
    printf("Hello world from processor %s, rank %d out of %d processors\n",
        processor_name,
        world_rank,
        world_size);
}

PYBIND11_MODULE(mpi_lib, pybind_module)
{
    constexpr auto MODULE_DESCRIPTION = "Just testing out mpi with python.";
    pybind_module.doc() = MODULE_DESCRIPTION;

    pybind_module.def("say_hi", &say_hi, "Each process is allowed to say hi");
}

Далее сторона питона. Здесь я повторно использую пример из этого поста: Скрытие MPI в Python и просто помещаю его в библиотеку pybind11. Итак, сначала скрипт Python, который будет вызывать скрипт Python MPI:

import sys
import numpy as np

from mpi4py import MPI

def parallel_fun():
    comm = MPI.COMM_SELF.Spawn(
        sys.executable,
        args = ['child.py'],
        maxprocs=4)

    N = np.array(0, dtype='i')

    comm.Reduce(None, [N, MPI.INT], op=MPI.SUM, root=MPI.ROOT)

    print(f'We got the magic number {N}')

И файл дочернего процесса. Здесь мы просто вызываем библиотечную функцию, и она просто работает.

from mpi4py import MPI
import numpy as np

from mpi_lib import say_hi


comm = MPI.Comm.Get_parent()

N = np.array(comm.Get_rank(), dtype='i')

say_hi()

comm.Reduce([N, MPI.INT], None, op=MPI.SUM, root=0)

Конечный результат:

from prog import parallel_fun
parallel_fun()
# Hello world from processor arch_zero, rank 1 out of 4 processors
# Hello world from processor arch_zero, rank 2 out of 4 processors
# Hello world from processor arch_zero, rank 0 out of 4 processors
# Hello world from processor arch_zero, rank 3 out of 4 processors
# We got the magic number 6
person NOhs    schedule 25.04.2018
comment
Вы передаете MPI._addressof(comm) в C++, а затем разыменовываете его там. Но вы можете просто использовать константу MPI_COMM_WORLD в C++, как это принято в программах с одним коммуникатором. Учитывая, что MPI — это общепроцессный процесс, вам на самом деле не нужно передавать коммуникатор по умолчанию с Python на C++, потому что он уже доступен для обоих. Другими словами: я думаю, вы обнаружите, что comm_world в C++ всегда имеет одно и то же значение, поэтому нет необходимости его передавать. - person John Zwinck; 25.04.2018
comment
Вы абсолютно правы. Я упрощу пример. - person NOhs; 25.04.2018
comment
@JohnZwinck, теперь, когда я думаю об этом, вопрос был о передаче коммуникаторов на сторону C ++ без упоминания, является ли это сценарием с одним коммуникатором. Поэтому я думаю, что мой более общий ответ мог бы быть лучше при ответе на вопрос. Но я не могу откатиться. Вы можете это сделать? Может быть, тогда я смогу просто добавить примечание о том, как в простом случае с одним коммуникатором все упрощается. - person NOhs; 25.04.2018
comment
ОП попросил использовать коммуникатор, а не коммуникатор. Учитывая, что подавляющее большинство программ MPI используют только один мировой коммуникатор, я думаю, что это хорошо. Исходный код был слишком сложным, но если кто-то придет сюда, чтобы передать дескриптор коммуникатора из Python в C++, все, что ему нужно знать, это то, что это дескриптор int, поэтому они могут просто передать этот int из Python в C++ и использовать его. вместо MPI_COMM_WORLD. Эта часть тривиальна. - person John Zwinck; 26.04.2018
comment
Инт только для mpich верно? Я думаю, что openmpi использует void* - person NOhs; 26.04.2018
comment
Да, вы можете просто бросить что угодно на uintptr_t и передать его. - person John Zwinck; 26.04.2018
comment
Я понимаю. Трудность заключалась главным образом в том, что pybind11 не нравился void*, но, думаю, тогда это не имеет значения. - person NOhs; 26.04.2018
comment
Использование MPI_COMM_WORLD везде — плохая практика. По сути, это так же плохо, как использовать везде глобальные переменные. Вы не знаете заранее, понадобятся ли вам в какой-то момент некоторые ранги MPI для работы над чем-то другим во время выполнения ваших основных вычислений, например, контрольных точек, постобработки данных, анализа данных или визуализации в реальном времени. В какой-то момент вы также можете захотеть включить свой код в более крупное вычисление, где некоторые другие вычисления могут выполняться параллельно с вашими вычислениями. Следовательно, вы всегда должны передавать определяемый пользователем коммуникатор своим вычислительным программам. - person H. Rittich; 18.06.2020

Передача коммуникатора mpi4py в C++ с использованием pybind11 можно выполнить с помощью C-API mpi4py. Соответствующие заголовочные файлы можно найти с помощью следующего кода Python:

import mpi4py
print(mpi4py.get_include())

Для удобного обмена коммуникаторами между Python и C++ используется настраиваемый генератор типов pybind11 можно реализовать. Для этого начнем с типичной преамбулы.

// native.cpp
#include <pybind11/pybind11.h>
#include <mpi.h>
#include <mpi4py/mpi4py.h>

namespace py = pybind11;

Чтобы pybind11 автоматически преобразовывал тип Python в тип C++, нам нужен отдельный тип, который может распознать компилятор C++. К сожалению, стандарт MPI не указывает тип для MPI_comm. Хуже того, в обычных реализациях MPI MPI_comm может быть определено как int или void*, что компилятор C++ не может отличить от обычного использования этих типов. Чтобы создать отдельный тип, мы определяем класс-оболочку для MPI_Comm, который неявно преобразует в MPI_Comm и обратно.

struct mpi4py_comm {
  mpi4py_comm() = default;
  mpi4py_comm(MPI_Comm value) : value(value) {}
  operator MPI_Comm () { return value; }

  MPI_Comm value;
};

Затем заклинатель типов реализуется следующим образом:

namespace pybind11 { namespace detail {
  template <> struct type_caster<mpi4py_comm> {
    public:
      PYBIND11_TYPE_CASTER(mpi4py_comm, _("mpi4py_comm"));

      // Python -> C++
      bool load(handle src, bool) {
        PyObject *py_src = src.ptr();

        // Check that we have been passed an mpi4py communicator
        if (PyObject_TypeCheck(py_src, &PyMPIComm_Type)) {
          // Convert to regular MPI communicator
          value.value = *PyMPIComm_Get(py_src);
        } else {
          return false;
        }

        return !PyErr_Occurred();
      }

      // C++ -> Python
      static handle cast(mpi4py_comm src,
                         return_value_policy /* policy */,
                         handle /* parent */)
      {
        // Create an mpi4py handle
        return PyMPIComm_New(src.value);
      }
  };
}} // namespace pybind11::detail

Ниже приведен код примера модуля, в котором используется заклинатель типов. Обратите внимание, что мы используем mpi4py_comm вместо MPI_Comm в определениях функций, предоставляемых pybind11. Однако из-за неявного преобразования мы можем использовать эти переменные как обычные переменные MPI_Comm. В частности, их можно передать любой функции, ожидающей аргумент типа MPI_Comm.

// recieve a communicator and check if it equals MPI_COMM_WORLD
void print_comm(mpi4py_comm comm)
{
  if (comm == MPI_COMM_WORLD) {
    std::cout << "Received the world." << std::endl;
  } else {
    std::cout << "Received something else." << std::endl;
  }
}

mpi4py_comm get_comm()
{
  return MPI_COMM_WORLD; // Just return MPI_COMM_WORLD for demonstration
}

PYBIND11_MODULE(native, m)
{
  // import the mpi4py API
  if (import_mpi4py() < 0) {
    throw std::runtime_error("Could not load mpi4py API.");
  }

  // register the test functions
  m.def("print_comm", &print_comm, "Do something with the mpi4py communicator.");
  m.def("get_comm", &get_comm, "Return some communicator.");
}

Модуль может быть скомпилирован, например, с использованием

mpicxx -O3 -Wall -shared -std=c++14 -fPIC \
  $(python3 -m pybind11 --includes) \
  -I$(python3 -c 'import mpi4py; print(mpi4py.get_include())') \
  native.cpp -o native$(python3-config --extension-suffix)

и протестировано с использованием

import native
from mpi4py import MPI
import math

native.print_comm(MPI.COMM_WORLD)

# Create a cart communicator for testing
# (MPI_COMM_WORLD.size has to be a square number)
d = math.sqrt(MPI.COMM_WORLD.size)
cart_comm = MPI.COMM_WORLD.Create_cart([d,d], [1,1], False)
native.print_comm(cart_comm)

print(f'native.get_comm() == MPI.COMM_WORLD '
      f'-> {native.get_comm() == MPI.COMM_WORLD}')

Вывод должен быть:

Received the world.
Received something else.
native.get_comm() == MPI.COMM_WORLD -> True
person H. Rittich    schedule 18.06.2020