.. _hybrid: ============================== Запуск гибридных задач MPI+OMP ============================== Система очередей Slurm поддерживает запуск гибридных задач, использующих технологии MPI и OMP. В этом примере мы продемонстрируем работу гибридных задач на простейшей программе `xthi.c `_. Компиляция гибридных программ ----------------------------- Для экспериментов будем использовать пример простейшей гибридной программы ``xthi``, использующей технологии MPI и OMP. Код программы можно найти, например, на сайте `NERSC `_. .. code-block:: bash wget https://docs.nersc.gov/jobs/affinity/xthi.c Для компиляции программы с поддержкой MPI нам потребуется MPI-компилятор, для поддержки OMP как правило нужно указать дополнительный параметр. Например, при использовании компиляторов Intel и библиотеки Intel MPI используется параметр ``-qopenmp``: .. code-block:: bash # загружаем модули intel и impi module load intel impi # компилятор mpiicc, добавляем параметр -qopenmp mpiicc -qopenmp xthi.c -o xthi.impi При использовании компиляторов GCC и библиотеки OpenMPI используется параметр ``-fopenmp``: .. code-block:: bash # загружаем модули gnu9 и openmpi4 module load gnu9 openmpi4 # компилятор mpicc, добавляем параметр -fopenmp mpicc -fopenmp xthi.c -o xthi.openmpi Обратите внимание, что при использовании компиляторов Intel MPI-компилятор называется ``mpiicc``, а при использовании компиляторов GCC компилятор называется ``mpicc``. Также в первом случае для поддержки OMP параметр называется ``-qopenmp``, а во втором – ``-fopenmp``. В примерах выше мы скомпилировали две версии программы ``xthi``, одну с использованием Intel MPI – ``xthi.impi`` и одну с использованием OpenMPI – ``xthi.openmpi``. Запуск гибридных программ ------------------------- Подготовим скрипт для запуска программы в системе очередей Slurm: .. code-block:: slurm :linenos: #!/bin/bash #SBATCH --job-name=xthi # название задачи #SBATCH --nodes=2 # 2 вычислительных узла #SBATCH --ntasks-per-node=4 # по 4 MPI-процесса на узел #SBATCH --cpus-per-task=8 # по 8 ядер на MPI-процесс #SBATCH --time=05:00 # оценка времени - 5 минут # выгружаем все загруженные модули module purge # загружаем модули intel и impi module load intel impi # запускаем с помощью mpirun # сохраняем результат в result.impi.mpirun echo "Intel MPI, run with mpirun" mpirun ./xthi.impi | tee result.impi.mpirun # запускаем с помощью srun # сохраняем результат в result.impi.srun echo "Intel MPI, run with srun --mpi=pmi2" srun --mpi=pmi2 ./xthi.impi | tee result.impi.srun # выгружаем все загруженные модули module purge # загружаем модули gnu9 и openmpi4 module load gnu9 openmpi4 # запускаем с помощью mpirun # сохраняем результат в result.openmpi.mpirun echo "OpenMPI, run with mpirun" mpirun ./xthi.openmpi | tee result.openmpi.mpirun # добавляем опцию --map-by # сохраняем результат в result.openmpi.map echo "OpenMPI, run with mpirun" mpirun --map-by slot:PE=$SLURM_CPUS_PER_TASK ./xthi.openmpi | tee result.openmpi.map В этом скрипте мы просим 2 вычислительных узла (строка 3), на каждом по 4 MPI-процесса (строка 4), и по 8 ядер на один MPI-процесс (строка 5). В строках 9 и 22 вы удаляем все загруженные ранее модули, чтобы в строках 11 и 24 загрузить модули ``intel impi`` или ``gnu9 openmpi4``. При использовании Intel MPI у нас есть два способа запустить гибридную программу: с помощью ``mpirun`` (строка 15), и с помощью ``srun`` (строка 19). При использовании OpenMPI запуск гибридных программ возможен с помощью ``mpirun`` (строка 28), также необходимо добавить дополнительный параметр ``--map-by`` (строка 32). После запуска этого скрипта с помощью команды ``sbatch`` мы получим 4 файла, описывающих привязку OMP-thread'ов к ядрам вычислительных узлов. Пример файла ``result.impi.mpirun`` (отсортированный для удобства чтения): .. code-block:: text Hello from rank 0, thread 0, on c01. (core affinity = 0-7) Hello from rank 0, thread 1, on c01. (core affinity = 0-7) Hello from rank 0, thread 2, on c01. (core affinity = 0-7) Hello from rank 0, thread 3, on c01. (core affinity = 0-7) Hello from rank 0, thread 4, on c01. (core affinity = 0-7) Hello from rank 0, thread 5, on c01. (core affinity = 0-7) Hello from rank 0, thread 6, on c01. (core affinity = 0-7) Hello from rank 0, thread 7, on c01. (core affinity = 0-7) Hello from rank 1, thread 0, on c01. (core affinity = 8-15) Hello from rank 1, thread 1, on c01. (core affinity = 8-15) Hello from rank 1, thread 2, on c01. (core affinity = 8-15) Hello from rank 1, thread 3, on c01. (core affinity = 8-15) Hello from rank 1, thread 4, on c01. (core affinity = 8-15) Hello from rank 1, thread 5, on c01. (core affinity = 8-15) Hello from rank 1, thread 6, on c01. (core affinity = 8-15) Hello from rank 1, thread 7, on c01. (core affinity = 8-15) Hello from rank 2, thread 0, on c01. (core affinity = 16-23) Hello from rank 2, thread 1, on c01. (core affinity = 16-23) Hello from rank 2, thread 2, on c01. (core affinity = 16-23) Hello from rank 2, thread 3, on c01. (core affinity = 16-23) Hello from rank 2, thread 4, on c01. (core affinity = 16-23) Hello from rank 2, thread 5, on c01. (core affinity = 16-23) Hello from rank 2, thread 6, on c01. (core affinity = 16-23) Hello from rank 2, thread 7, on c01. (core affinity = 16-23) Hello from rank 3, thread 0, on c01. (core affinity = 26-33) Hello from rank 3, thread 1, on c01. (core affinity = 26-33) Hello from rank 3, thread 2, on c01. (core affinity = 26-33) Hello from rank 3, thread 3, on c01. (core affinity = 26-33) Hello from rank 3, thread 4, on c01. (core affinity = 26-33) Hello from rank 3, thread 5, on c01. (core affinity = 26-33) Hello from rank 3, thread 6, on c01. (core affinity = 26-33) Hello from rank 3, thread 7, on c01. (core affinity = 26-33) Hello from rank 4, thread 0, on c02. (core affinity = 0-7) Hello from rank 4, thread 1, on c02. (core affinity = 0-7) Hello from rank 4, thread 2, on c02. (core affinity = 0-7) Hello from rank 4, thread 3, on c02. (core affinity = 0-7) Hello from rank 4, thread 4, on c02. (core affinity = 0-7) Hello from rank 4, thread 5, on c02. (core affinity = 0-7) Hello from rank 4, thread 6, on c02. (core affinity = 0-7) Hello from rank 4, thread 7, on c02. (core affinity = 0-7) Hello from rank 5, thread 0, on c02. (core affinity = 8-15) Hello from rank 5, thread 1, on c02. (core affinity = 8-15) Hello from rank 5, thread 2, on c02. (core affinity = 8-15) Hello from rank 5, thread 3, on c02. (core affinity = 8-15) Hello from rank 5, thread 4, on c02. (core affinity = 8-15) Hello from rank 5, thread 5, on c02. (core affinity = 8-15) Hello from rank 5, thread 6, on c02. (core affinity = 8-15) Hello from rank 5, thread 7, on c02. (core affinity = 8-15) Hello from rank 6, thread 0, on c02. (core affinity = 16-23) Hello from rank 6, thread 1, on c02. (core affinity = 16-23) Hello from rank 6, thread 2, on c02. (core affinity = 16-23) Hello from rank 6, thread 3, on c02. (core affinity = 16-23) Hello from rank 6, thread 4, on c02. (core affinity = 16-23) Hello from rank 6, thread 5, on c02. (core affinity = 16-23) Hello from rank 6, thread 6, on c02. (core affinity = 16-23) Hello from rank 6, thread 7, on c02. (core affinity = 16-23) Hello from rank 7, thread 0, on c02. (core affinity = 26-33) Hello from rank 7, thread 1, on c02. (core affinity = 26-33) Hello from rank 7, thread 2, on c02. (core affinity = 26-33) Hello from rank 7, thread 3, on c02. (core affinity = 26-33) Hello from rank 7, thread 4, on c02. (core affinity = 26-33) Hello from rank 7, thread 5, on c02. (core affinity = 26-33) Hello from rank 7, thread 6, on c02. (core affinity = 26-33) Hello from rank 7, thread 7, on c02. (core affinity = 26-33) Из вывода видно, что на каждом из узлов ``c01`` и ``c02`` запущено по 4 MPI-процесса, в каждом создано 8 OMP-thread'ов, и они привязаны к ядрам, выделенным на узлах блоками по 8 штук. Причём виден пропуск в блоках: ``0-7, 8-15, 16-23, <пропуск>, 26-33``. Этот пропуск обусловлен выравниванием блоков по физическим процессорам. На каждом вычислительном узле установлены два процессора, первому соответствуют ядра ``0-25``, второму – ``26-51``. Так как мы запрашиваем на каждом узле по 4 блока по 8 ядер, то первые три блока попадают целиком на первый процессор, а четвёртый – на второй процессор. Также стоит обратить внимание на вывод в файле ``result.openmpi.mpirun``. Он сильно отличается от остальных, так как команда ``mpirun`` из библиотеки OpenMPI по умолчанию ограничивает каждый MPI-процесс одним ядром, поэтому в выводе мы не видим OMP-thread'ов: .. code-block:: text Hello from rank 0, thread 0, on c01. (core affinity = 0) Hello from rank 1, thread 0, on c01. (core affinity = 1) Hello from rank 2, thread 0, on c01. (core affinity = 2) Hello from rank 3, thread 0, on c01. (core affinity = 3) Hello from rank 4, thread 0, on c02. (core affinity = 0) Hello from rank 5, thread 0, on c02. (core affinity = 1) Hello from rank 6, thread 0, on c02. (core affinity = 2) Hello from rank 7, thread 0, on c02. (core affinity = 3) Использование дополнительной опции ``--map-by slot:PE=$SLURM_CPUS_PER_TASK`` увеличивает количество ядер на каждый MPI-процесс, и результат ``result.openmpi.map`` получается аналогичным результату ``result.impi.mpirun``. Дополнительные опции команд ``sbatch``, ``srun``, ``mpirun`` могут влиять на конфигурацию выделенных ядер и на привязку процессов к ядрам. Обратитесь к документации Slurm, OpenMPI, Intel MPI за дополнительной информацией.