Запуск гибридных задач MPI+OMP
Система очередей Slurm поддерживает запуск гибридных задач, использующих технологии MPI и OMP. В этом примере мы продемонстрируем работу гибридных задач на простейшей программе xthi.c.
Компиляция гибридных программ
Для экспериментов будем использовать пример простейшей гибридной программы xthi
, использующей технологии MPI и OMP. Код программы можно найти, например, на сайте NERSC.
wget https://docs.nersc.gov/jobs/affinity/xthi.c
Для компиляции программы с поддержкой MPI нам потребуется MPI-компилятор, для поддержки OMP как правило нужно указать дополнительный параметр. Например, при использовании компиляторов Intel и библиотеки Intel MPI используется параметр -qopenmp
:
# загружаем модули intel и impi
module load intel impi
# компилятор mpiicc, добавляем параметр -qopenmp
mpiicc -qopenmp xthi.c -o xthi.impi
При использовании компиляторов GCC и библиотеки OpenMPI используется параметр -fopenmp
:
# загружаем модули 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:
1#!/bin/bash
2#SBATCH --job-name=xthi # название задачи
3#SBATCH --nodes=2 # 2 вычислительных узла
4#SBATCH --ntasks-per-node=4 # по 4 MPI-процесса на узел
5#SBATCH --cpus-per-task=8 # по 8 ядер на MPI-процесс
6#SBATCH --time=05:00 # оценка времени - 5 минут
7
8# выгружаем все загруженные модули
9module purge
10# загружаем модули intel и impi
11module load intel impi
12# запускаем с помощью mpirun
13# сохраняем результат в result.impi.mpirun
14echo "Intel MPI, run with mpirun"
15mpirun ./xthi.impi | tee result.impi.mpirun
16# запускаем с помощью srun
17# сохраняем результат в result.impi.srun
18echo "Intel MPI, run with srun --mpi=pmi2"
19srun --mpi=pmi2 ./xthi.impi | tee result.impi.srun
20
21# выгружаем все загруженные модули
22module purge
23# загружаем модули gnu9 и openmpi4
24module load gnu9 openmpi4
25# запускаем с помощью mpirun
26# сохраняем результат в result.openmpi.mpirun
27echo "OpenMPI, run with mpirun"
28mpirun ./xthi.openmpi | tee result.openmpi.mpirun
29# добавляем опцию --map-by
30# сохраняем результат в result.openmpi.map
31echo "OpenMPI, run with mpirun"
32mpirun --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
(отсортированный для удобства чтения):
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’ов:
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 за дополнительной информацией.