Использование узлов с Intel Xeon Phi
Поддержка вычислений на Xeon Phi прекращена в связи с отсутствием интереса со стороны пользователей и отсутствием поддержки MPSS современных операционных систем.
- 'Intel MIC' (Many Integrated Core) - архитектура многоядерной системы, основанная на классической архитектуре x86.
- 'Xeon Phi' - бренд для линейки продуктов Intel на архитектуре MIC.
- 'MPSS' (Manycore Platform Software Stack) - набор драйверов и утилит от Intel, необходимый для использования сопроцессоров Xeon Phi. Версию установленного пакета MPSS видно в директории '/opt/mpss' на узлах с Xeon Phi. На момент написания данной документации используется версия 3.8.4
- Написанное ниже относится к поколению архитектуры Intel MIC с кодовым названием 'Knights Corner' (KNC).
- Сопроцессор Xeon Phi представляет из себя устройство, подключаемое к компьютеру (называемому 'хост') через шину PCI Express и работающее под управлением собственной операционной системы, основанной на ядре Linux. Сопроцессор не имеет жёсткого диска и файловая система занимает место в его оперативной памяти, поэтому на сопроцессоре установлен минимум дополнительного программного обеспечения. Пользовательское окружение реализовано с помощью пакета BusyBox. С хоста сопроцессор доступен по протоколу SSH через виртуальную локальную сеть. Операционная система сопроцессоров настроена так, что имеется доступ к домашним директориям и рабочим областям пользователей.
- Ожидается, что следующие поколения Xeon Phi смогут использоваться как полноценные CPU, устанавливаемые непосредственно в сокет на материнской плате.
- Цитата с сайта Intel: 'Чтобы полностью раскрыть преимущества сопроцессоров Intel Xeon Phi, приложение должно масштабироваться до уровня более 100 программных потоков, а также либо активно использовать векторные вычисления, либо эффективно использовать полосу пропускания локальной памяти, более широкую, чем у процессора Intel Xeon.'
- На данный момент у нас имеется 5 серверов HP XL250a Gen9, каждый из которых содержит:
- Два 12-ядерных процессора Intel Xeon E5-2680v3 с тактовой частотой 2500 МГц
- 192 ГБ ОЗУ
- Два сопроцессора Intel Xeon Phi 7120P, у каждого из которых:
- 16 ГБ собственного ОЗУ
- 61 ядро, работающее в 244 потока на частотах 1,2 - 1,3 ГГц
- 1,2 Тфлопс пиковой производительности для вычислений с двойной точностью
Интеграция с PBS
- Узлы с Xeon Phi выделены в отдельную очередь с именем 'xl250g9q'. Чтобы задача запускалась на этих узлах, в скрипт для qsub необходимо добавить такую строку:
#PBS -q xl250g9q
- Каждый сопроцессор должен использоваться только одной задачей одновременно. При этом наличие двух сопроцессоров позволяет запускать на узле одновременно две задачи. Для этого каждый узел делится c точки зрения PBS на два виртуальных узла, называемых 'vnode' (аналогично узлам с графическими сопроцессорами). Каждый из виртуальных узлов содержит один сопроцессор, 12 ядер центрального процессора и 96 ГБ ОЗУ.
- Для запроса необходимого количества сопроцессоров используется ресурс 'nmics'. Например, так:
#PBS -l select=1:ncpus=4:mem=12gb:nmics=1
- Все запрашиваемые ресурсы (количество ядер, mpi-/openmp- процессов, ОЗУ, …) относятся к хосту. Сопроцессор выделяется в монопольное использование и вы можете запускать на нём любое разумное количество процессов.
- Обращаем внимание, что название 'виртуальный узел' подразумевает только логическое выделение части ресурсов физического сервера с точки зрения планировщика PBS, но не использование виртуализации. Например, при выполнении указанного выше запроса на сервере останутся неиспользумыми: второй сопроцессор, 20 ядер CPU и 180ГБ ОЗУ. И все эти ресурсы доступны другим задачам.
- После запуска задачи она должна узнать, какой виртуальный узел (или узлы) ей выделен и по его названию понять порядковый номер сопроцессора, который она должна использовать. Например, виртуальные узлы сервера cn331 называются cn331[0] и cn331[1]. Если задаче выделен виртуальный узел cn331[1], значит, она должна использовать именно сопроцессор №1 на узле cn331.
- Определить предоставленный виртуальный узел можно с помощью значения параметра 'exec_vnode' в выводе команды 'qstat -f $PBS_JOBID'. Данную операцию можно произвести как из скрипта (примеры приведены ниже), так и из самого запускаемого приложения, если имеется возможность модифицировать его код. Следует учитывать, что при запросе нескольких сопроцессоров вывод значения 'exec_vnode' может занимать более одной строки.
- Автоматического ограничения доступа к остальным сопроцессорам узла средствами PBS или драйверов Xeon Phi не происходит, пользователь должен сам настраивать свою задачу для использования именно тех сопроцессоров, которые ей выделены планировщиком.
Пример 1:
- В случае, если запрашивается только один сопроцессор, можно быть уверенным, что значение 'exec_vnode' поместится в одну строку. Кроме того, нет необходимости определять, какой физический сервер нам выделен, т.к. именно на нём задача и запустится.
- Код скрипта, определяющего номер предоставленного сопроцессора и его имя:
#!/bin/sh #PBS -q xl250g9q #PBS -l walltime=0:01:00 #PBS -l select=1:nmics=1:ncpus=1:mem=1gb cd $PBS_O_WORKDIR vnodes=$(qstat -f $PBS_JOBID|grep exec_vnode|sed -e 's/ *//') echo "$vnodes" if [ $(echo $vnodes|grep -c '+') != 0 ] ; then echo "Error: several vnodes are provided." exit 100 fi mic=$(echo $vnodes|sed 's/.*\[//'|sed 's/\].*//') echo "mic=$mic" micinfo -deviceInfo $mic|grep 'Device Name'
- После передачи этого скрипта команде qsub получим в файле стандартного вывода примерно следующее:
exec_vnode = (cn332[0]:ncpus=1:mem=102400kb:nmics=1) mic=0 Device No: 0, Device Name: mic0
В данном случае задаче был предоставлен сопроцессор с номером '0' и именем 'mic0'
Пример 2:
- Если задаче требуется несколько сопроцессоров, анализ предоставленных виртуальных узлов будет немного сложнее:
#!/bin/sh #PBS -q xl250g9q #PBS -l walltime=0:01:00 #PBS -l select=3:nmics=2:ncpus=4:mem=4gb cd $PBS_O_WORKDIR vnodes=$(qstat -f $PBS_JOBID|tr -d '\n'' ''\t'|sed 's/Hold_Types.*//'|sed 's/.*exec_vnode=//'|tr -d \(\)|tr + '\n'|sed 's/:.*//'|sort) echo "My vnodes:" for vnode in $vnodes ; do node=$(echo $vnode|sed 's/\[.*//') mic=$(echo $vnode|sed 's/.*\[//'|sed 's/\]//') echo "$vnode = Node $node, mic $mic" done
- В результате работы получим в файле стандартного вывода примерно следующее:
My vnodes: cn332[0] = Node cn332, mic 0 cn332[1] = Node cn332, mic 1 cn333[0] = Node cn333, mic 0 cn333[1] = Node cn333, mic 1 cn334[0] = Node cn334, mic 0 cn334[1] = Node cn334, mic 1
Использование
- Приведённая ниже инструкция описывает только основные моменты использования Xeon Phi, необходимые для начала работы. Более подробные описания смотрите в интернете, например, на следующих сайтах:
- Также может быть полезно посмотреть MPSS Users Guide.
- Для компиляции программ будут использоваться компиляторы Intel. Для настройки необходимых переменных окружения перед использованием нужно выполнять:
source /opt/intel/composerxe/bin/compilervars.sh intel64
Для изучения возможных нюансов будет полезно просмотреть вывод комадны 'man' на используемый компилятор (icc / icpc / ifort) и изучить параметры компиляции и переменные окружения, влияющие на генерацию кода и работу приложений для архитектуры Intel MIC.
- Существуют три модели использования сопроцессора:
- Offload. Программа работает на основном компьютере, но некоторые её части выполняются на сопроцессоре.
- Native. Программа работает только на сопроцессоре и не взаимодействует с основным компьютером.
- Symmetric. Нагрузка разделена между основным компьютером и сопроцессором. Например, это может быть программа, использующая стандарт MPI и работающая как на хосте, так и на сопроцессоре.
Offload
- Для выделения участка кода, который должен выполняться на сопроцессоре, используется директива
#pragma offload target(mic:номер)
, где 'номер' - номер используемого сопроцессора (в нашем случае это '0' или '1', в зависимости от виртуального узла, выделенного PBS ). Параметр 'номер' не является обязательным с точки зрения синтаксиса, но при работе на нашем кластера он должен указываться и сответствовать номеру сопроцессора, предоставленному планировщиком задач. Также эта директива может содержать дополнительные параметры, например, указывающие, значения каких переменных должны передаваться между ОЗУ узла и собственной памятью сопроцессора:- in - перед началом выполнения кода на сопроцессоре значения указанных переменных будут скопированы из памяти компьютера в память сопроцессора.
- out - после окончания выполнения кода на сопроцессоре значения указанных переменных будут скопированы из памяти сопроцессора в память компьютера.
- inout - перед началом выполнения кода на сопроцессоре значания указанных переменных будут скопированы из памяти компьютера в память сопроцессора, а после завершения - скопированы обратно.
- При использовании компиляторов Intel выделенный планировщиком сопроцессор можно указывать не в директиве
#pragma offload
, а при помощи переменной окружения 'OFFLOAD_DEVICES'. Подробности смотрите в выводе команды 'man' на используемый компилятор. - На сопроцессоре код будет выполняться не из под вашей учетной записи, а с правами специального пользователя 'micuser'. Поэтому вы не сможете, например, модифицировать свои файлы в домашней директории.
- Для распараллеливания можно использовать OpenMP внутри блока директивы
#pragma offload
. Также можно делать offload из MPI-процессов, работающих на хосте. - Компиляторы GCC поддерживают offloading только для сопроцессоров следующего поколения архитектуры MIC - 'Knights Landing' (KNL).
Пример:
- Создать файл 'show_cores.c' такого содержания:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main(int argc, char** argv) { int cores; char host[32]; char * endptr; long mic_number; if (argc < 2) { printf("ERROR: MIC number absent!\n"); exit(1); } mic_number = strtol (argv[1], &endptr, 10); if (endptr == argv[1] || (mic_number != 0 && mic_number != 1) ) { printf("ERROR: incorrect MIC number!\n"); exit(2); } printf("mic = %i\n",mic_number); gethostname(host,32); cores = sysconf( _SC_NPROCESSORS_ONLN ); printf("hostname = %s, logical cores = %d\n",host,cores); #pragma offload target(mic:mic_number) out(host,cores) { gethostname(host,32); cores = sysconf( _SC_NPROCESSORS_ONLN ); } printf("micname = %s, logical cores = %d\n",host,cores); return 0; }
- Для преобразования полученного в качестве параметра номера сопроцессора в число вместо часто используемой функции 'atoi' используется 'strtol'. Существенный недостаток 'atoi' состоит в том, что в случае ошибки (например, в качестве параметра передано не число) поведение этой функции и возвращаемое значение неопределены. В лучшем случае будет возвращено значение '0', что может привести к некорректному выбору сопроцессора с номером '0'.
- Для компиляции и выполнения будет использоваться такой скрипт для qsub:
#!/bin/sh #PBS -q xl250g9q #PBS -l walltime=0:01:00 #PBS -l select=1:ncpus=1:mem=100mb:nmics=1 cd $PBS_O_WORKDIR vnodes=$(qstat -f $PBS_JOBID|grep exec_vnode|sed -e 's/ *//') if [ $(echo $vnodes|grep -c '+') != 0 ] ; then echo "Error: several vnodes are provided." exit 100 fi mic=$(echo $vnodes|sed 's/.*\[//'|sed 's/\].*//') source /opt/intel/composerxe/bin/compilervars.sh intel64 icc -o show_cores show_cores.c ./show_cores $mic
- После завершения задачи в файле стандартного потока вывода будут находиться имена сервера и сопроцессора, а также количество их логических ядер, примерно так:
mic = 0 hostname = cn332, logical cores = 24 micname = cn332-mic0, logical cores = 244
Native
- Для генерации исполняемого кода, способного работать на архитектуре MIC, компиляторам Intel необходимо указывать параметр '-mmic'
- Вместо компиляторов Intel можно использовать GCC, поставляемый Intel в составе пакета MPSS. Более подробное описание находится в MPSS Users Guide.
- Полученный при компиляции файл сразу становится виден с сопроцессора, его не нужно туда копировать.
- Запуск программы в native режиме производится из скрипта для qsub командой 'ssh' с указанием в качестве параметров имени используемого сопроцессора (mic0 или mic1, в зависимости от выделенного планировщиком виртуального узла) и набора команд, которые должны быть выполнены на сопроцессоре.
- Программы в режиме native могут использовать как OpenMP, так и MPI.
Пример:
- Создать файл 'show_cores_natively.c' такого содержания:
#include <stdio.h> #include <unistd.h> int main(int argc, char** argv) { int cores; char host[32]; gethostname(host,32); cores = sysconf( _SC_NPROCESSORS_ONLN ); printf("micname=%s, logical cores=%d\n",host,cores); return 0; }
- Как видно, это вполне обычный код, без какой-либо специфики архитектуры Intel MIC.
- Для компиляции и выполнения использовать такой скрипт для qsub:
#!/bin/sh #PBS -q xl250g9q #PBS -l walltime=0:01:00 #PBS -l select=1:ncpus=1:mem=100mb:nmics=1 cd $PBS_O_WORKDIR vnodes=$(qstat -f $PBS_JOBID|grep exec_vnode|sed -e 's/ *//') if [ $(echo $vnodes|grep -c '+') != 0 ] ; then echo "Error: several vnodes are provided." exit 100 fi mic="mic$(echo $vnodes|sed 's/.*\[//'|sed 's/\].*//')" source /opt/intel/composerxe/bin/compilervars.sh intel64 icc -mmic -o show_cores_natively show_cores_natively.c ssh -q $mic "cd $PBS_O_WORKDIR ; ./show_cores_natively"
- После завершения задачи в файле стандартного потока вывода будет находиться примерно такое:
micname=cn333-mic0, logical cores=244
Symmetric
- В данном разделе рассматривается реализация симметричого режима при помощи стандарта MPI.
- Будет использоваться последняя имеющаяся у нас версия Intel MPI, поэтому на интерфейсном сервере необходимо выполнить:
mpi-selector --set intel_mpi-5.0.1.035
- Создать файл 'hello_mpi_world.c' такого вида:
#include <mpi.h> #include <stdio.h> #include <unistd.h> int main(int argc, char** argv) { int size, rank; char host[32]; MPI_Init(&argc,&argv); MPI_Comm_size(MPI_COMM_WORLD,&size); MPI_Comm_rank(MPI_COMM_WORLD,&rank); gethostname(host,32); printf("Hello, MPI world! I'm number %d from %d and I run on host %s\n",rank,size,host); MPI_Finalize(); return 0; }
- Этот файл необходимо скомпилировать два раза и получить исполняемые версии для хоста и для сопроцессора. Это можно сделать с помощью такого скрипта для qsub:
#!/bin/sh #PBS -l select=1:ncpus=1:mem=1g #PBS -l walltime=00:01:00 #PBS -q xl250g9q cd $PBS_O_WORKDIR source /opt/intel/composerxe/bin/compilervars.sh intel64 mpiicc hello_mpi_world.c -o hello_mpi_world mpiicc -mmic hello_mpi_world.c -o hello_mpi_world.mic
- Обратите внимание, что компиляция производится командой mpiicc (использующей icc), а не mpicc (по умолчанию использующей gcc). Подробнее см. на странице с описанием Intel MPI. В результате работы этой задачи должны получиться два исполняемых файла: hello_mpi_world (для запуска на хосте) и hello_mpi_world.mic (для запуска на сопроцессоре).
- Команде mpirun потребуется файл с описанием, сколько MPI-процессов на каких серверах и сопроцессорах нужно запускать. Это файл будет сообщаться mpirun через параметр '-machinefile'. Формат файла простой: каждому процессу соответствует одна строка, содержащая имя сервера, на котором этот процесс должен запускаться. Либо в строке после имени сервера через двоеточие может находиться число, указывающее, сколько потоков надо запустить на этом сервере. Например, так:
cn331 cn331 cn331-mic0:3
Первая строка соответствует процессу с рангом '0'. При использовании MPI на обычных серверах, без сопроцессоров, этот файл создаётся PBS на основе запроса 'select=' и путь до него находится в переменной окружения PBS_NODEFILE. Но при использовании MPI на сопроцессорах в запросе не указывается, сколько процессов будет запускаться на сопроцессоре. Поэтому файл надо будет создать самостоятельно, взяв за основу предоставленный PBS и добавив к нему нужное количество строк, соответствующих сопроцессору.
- В скрипте обязательно должна устанавливаться переменная окружения, разрешающая MPI-взаимодействие между хостом и сопроцессором:
export I_MPI_MIC=enable
- Также необходимо указать, что имя исполняемого файла для запуска на сопроцессоре отличается от имёни файла для хоста расширением '.mic':
export I_MPI_MIC_POSTFIX=.mic
Пример:
- Все MPI-потоки находятся в пределах одного сервера, используется только один сопроцессор с этого же сервера.
- Для запуска задачи используется такой скрипт для qsub:
#!/bin/sh #PBS -l select=2:ncpus=1:mpiprocs=1:mem=2g:nmics=2 #PBS -l walltime=00:01:00 #PBS -q xl250g9q cd $PBS_O_WORKDIR source /opt/intel/composerxe/bin/compilervars.sh intel64 ## Create machinefile: machinefile=$PBS_O_WORKDIR/machinefile.$(echo $PBS_JOBID|awk -F. '{print $1}') cat $PBS_NODEFILE > $machinefile vnodes=$(qstat -f $PBS_JOBID|tr -d '\n'' ''\t'|sed 's/Hold_Types.*//'|sed 's/.*exec_vnode=//'|tr -d \(\)|tr + '\n'|sed 's/:.*//'|sort) for vnode in $vnodes ; do node=$(echo $vnode|sed 's/\[.*//') mic="mic$(echo $vnode|sed 's/.*\[//'|sed 's/\]//')" # Add 2 MPI processes for each Xeon Phi: echo "${node}-${mic}:2" >> $machinefile done export I_MPI_MIC=enable export I_MPI_MIC_POSTFIX=.mic mpirun -genv I_MPI_FABRICS=shm:tcp -machinefile $machinefile $PBS_O_WORKDIR/hello_mpi_world rm $machinefile
- В результате работы этой задачи получим в файле потока стандартного вывода примерно такое:
Hello, MPI world! I'm number 0 from 10 and I run on host cn331 Hello, MPI world! I'm number 1 from 10 and I run on host cn332 Hello, MPI world! I'm number 8 from 10 and I run on host cn332-mic1 Hello, MPI world! I'm number 6 from 10 and I run on host cn332-mic0 Hello, MPI world! I'm number 4 from 10 and I run on host cn331-mic1 Hello, MPI world! I'm number 2 from 10 and I run on host cn331-mic0 Hello, MPI world! I'm number 9 from 10 and I run on host cn332-mic1 Hello, MPI world! I'm number 3 from 10 and I run on host cn331-mic0 Hello, MPI world! I'm number 5 from 10 and I run on host cn331-mic1 Hello, MPI world! I'm number 7 from 10 and I run on host cn332-mic0