Запуск гибридных задач 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 за дополнительной информацией.