SlideShare a Scribd company logo
1 of 7
Download to read offline
Сейчас я буду рассказывать про совместное использование таких двух технологий, как CUDA и
MPI.
Доклад будет состоять из трех частей: первым делом я дам вам общее представление о том, что
такое MPI, затем расскажу как можно использовать связку CUDA с MPI и, под конец, покажу очень
простенький пример использования данной технологии, в котором я буду вычислять
аппроксимации производных в точках.
Все мы знаем как выглядят сегодняшние суперкомпьютеры. На слайде приведена фотография
суперкомпьютера BLUE GENE компании IBM. На данный момент суперкомпьютер есть ни что иное,
как объединение высокоскоростными каналами связи нескольких компьютеров, имеющих свой
собственный процессор, видеокарту (возможно не одну) и память. Сразу следует сделать
оговорку, что память может быть как общей, так и индивидуальной для каждого процессора или
даже процесса (все зависит от операционной системы). Под узлом кластера можно понимать либо
отдельный процессор, либо отдельный процесс (в зависимости от размера кластера). Если
пользователь требует запустить 10 узлов, а кластер имеет 10 CPU, то каждый узел — это
отдельный процессор, однако если запросить большее количество узлов, то хотя бы один из
процессоров будет вынужден выполнять две работы в двух разных процессах (возможно
параллельно: если процессор многоядерный). Во время работы узлов возникает необходимость
обмениваться информацией между различными узлами; именно для этого и создан MPI.
MPI расшифровывается как Message Passing Interface (Интерфейс Передачи Сообщений). Когда
говорят об MPI, то в подавляющем большинстве случаев подразумевают не низкоуровневую
структуру работы самого кластера, а некий высокоуровневый софт, удовлетворяющий общим
правилам. MPI — софт, работающий между операционной системой и вашим приложением, одной
из главных функций которого является упрощение процесса передачи информации между
процессами в сети: как локальной, так и глобальной, т.е. MPI API создает некую обертку над
сокетами, что очень сильно упрощает написание кода. Следует отдельно отметить, что, хоть,
формально MPI не является стандартом, подобным тем, что выпускают организации
CUDA-Aware MPi
(Слайд 1) — Вводный слайд
(Слайд 2) — Разделы
(Слайд 3) — Кластеры
(Слайд 4) — Что такое MPI
стандартизации, такие как ANSI или ISO, на данный момент он — самая развитая
кроссплатформенная библиотека параллельного программирования с передачей сообщений и, тем
самым, является де-факто стандартом. Принцип работы с MPI очень похож на работу с CUDA: он
работает по принципу SIMD (одиночный поток команд, множественный поток данных), т.е. мы
пишем одну программу, которая запускается на множестве процессах и процессорах. Однако,
несмотря на их внешнее сходство, между этими технологиями есть очень важные отличия.
Два принципиальных отличия между ними: сам принцип работы и организация памяти. GPU
организована таким образом, что параллельно на каждом мультипроцессоре выполняется один
варп (warp), который в современных GPU состоит из 32 потоков, причем очень важно знать, что
эти потоки не уникальны. Принцип их работы — SIMD. Если в нашей программе на CUDA есть
ветвление (if) и часть потоков в варпе выполняет одну часть кода, а вторая — другую, то эти
потоки не будут работать параллельно: сначала отработает одна часть потоков, в затем — другая,
и это надо учитывать при создании архитектуры программы. Так вот, MPI организован
принципиально по-другому: все узлы работают абсолютно независимо друг от друга. Второе
отличие — память: CUDA имеет несколько видов памяти: глобальная память, которая доступна
всем запущенным потокам, разделяемая память, доступная потокам лишь одного блока и
регистровая память, уникальная (приватная) для каждого потока. MPI предполагает, что у
каждого узла своя приватная память, к которой у других узлов нет доступа.
Лично у меня организации MPI ассоциируется с тем, что каждый узел работает на собственном
CPU в абсолютно параллельном режиме, однако по факту, как можно видеть на первой схеме, на
одном CPU могут работать сразу несколько узлов, причем они могут работать либо параллельно,
если процессор многоядерный, либо последовательно, когда планировщик операционной системы
распределяет процессорное время для каждого узла, которые в данном случае являются
отдельными процессами. По факту, программу, работающую с использованием MPI вообще можно
запустить на одном компьютере.
Для того, чтобы лучше понять принцип работы MPI, рассмотрим несколько стандартных функций:
MPIInit инициализирует окружение, т.е. устанавливает соединение с другими узлами программы.
MPICommsize возвращает количество запущенных узлов, а MPICommrank — номер конкретного
узла. MPISend — библиотечная функция отправки массива данных узлу, номер которого
указывается в аргументах функции. MPIRecv — соответственно, функция получения массива
данных. MPIFinalize — закрытие соединений и очистка памяти.
(Слайд 5) — MPI vs CUDA
(Слайд 6) — Виды кластеров
(Слайд 7) — Стандартные функции
(Слайд 8) — Пример программы
Рассмотрим исходный код программы из которого видно, как используются эти функции при
написании программ. Каждая программа должна начинаться с инициализации MPI окружения:
строчка 7. На строчках 8-9 происходит инициализация переменных: текущий узел и количество
запущенных узлов. В данном примере программа для своей работы требует запуска как минимум
трех потоков, что и проверяется на строках 11-14. Стандартный прием использующийся при
написании MPI приложений виден в строках 15-18: последний по номеру узел считается сервером
и его задачей является работа с файловой системой, сбор данных после того, как отработают
остальные узлы и визуализация результата, в то время как остальные узлы являются обычными
вычислительными узлами. Для того, чтобы правильно завершить работу программы необходимо
очистить память и закрыть соединения, что и делается на 20 строке.
Для компиляции программы необходимо использовать специальный компилятор, отличающийся от
привычного нам gcc или clang. Для компиляции исходников на C необходимо использовать mpicc,
для плюсов — mpicxx. Запускать программу надо не просто запуская исполняемый файл, а через
специальную программу mpirun, которой в качестве параметров передается количество узлов,
которое требуется запустить с исполняющейся программой, путь к исполняемому файлу самой
программы и аргументы программы. mpirun сама запускает процессы с указанной программой на
доступных CPU и ядрах.
Мы рассмотрели азы технологии MPI, поняли, что основная идея технологии — запустить
несколько программ с разными id’шниками на разных CPU/ядрах/процессах, которые могут
обмениваться массивами данных между собой. Следующий вопрос, который возникает при
использовании MPI: как конкретно использовать потенциал GPU при таком подходе
программирования. Всего возможны два принципиально разных принципа использования MPI и
CUDA: первый — использовать ядра мультипроцессоров GPU в качестве узлов, а второе — в
качестве узлов использовать только CPU/ядра CPU/процессы CPU. Рассмотрим по-подробнее
первый вариант. На самом деле его реализация в классическом стандарте MPI невозможна. И
причиной тому является “принцип передачи данных” между CPU и GPU реализованный в CUDA: в
этой технологии вообще нет понятия “сокет”, поток мультипроцессора при всем своем желании не
сможет обратиться к какому-либо процессу CPU и это, не говоря уже о взаимодействии с CPU
процессами через локальную сеть. У мультипроцессоров на GPU просто нет такой возможности в
силу их устройства, поэтому возможен лишь второй вариант взаимодействия MPI и CUDA. В
данном случае возникает другая проблема: если каждому узлу соответствует свой CPU и GPU, то
все прекрасно, но если узлами являются процессы на одном CPU, которые обращаются к одной
GPU, то необходимо очень аккуратно работать как с памятью, выделяющейся на GPU, так и
нагрузкой GPU, т.к. она будет работать в последовательном режиме: сначала с одним процессом,
потом — с другим, что значительно может замедлить работу программы.
(Слайд 9) — Компиляция и запуск
(Слайд 10) — Взаимодействие MPI & CUDA
Для того, чтобы показать на примере принципы взаимодействий MPI и CUDA, рассмотрим кластер,
состоящий из трех CPU, каждой из которых соответствует свой собственный GPU, и сам кластер
объединен в локальную сеть. Т.е. мы запускаем 3 узла, один из которых, как правило последний,
играет роль сервера.
Предположим, что мы хотим передать массив данных, хранящийся на GPU первого узла, GPU
второго узла. Это может быть сделано двумя способами: сначала скопировать данные на CPU
первого узла, передать на CPU второго узла, а потом — на GPU второго узла, что соответствует
использованию схемы MPI+CUDA, либо сразу передать с одного GPU на другой — такого эффекта
можно добиться только используя CUDA-Aware MPI: специальную модификацию библиотеки MPI,
грамотно работающей с CUDA. В пункте (1) приведен пример исходного кода двух узлов,
организующих передачу данных через память CPU . В пункте (2) приведен пример исходного кода,
реализующего “прямую” передачу данных. Если первый пункт более менее понятно как устроен, то
с реализацией второго возникают вопросы. Ведь сам MPI интерфейс должен быть универсален и
делать две разные функции для передачи и приема массива данных для CPU и GPU неправильно.
В принципе можно было бы добавить в набор аргументов функций флаг, указывающий на то,
какой тип передачи данных используется. Но тем не менее существует самое правильное решение
этой проблемы.
Начиная с CUDA 4.0 на устройствах с архитектурой 2.0 было введено единое пространство
виртуальных адресов для CPU и нескольких GPU. Т.е. не нужны никакие флаги, достаточно просто
посмотреть на адрес, на который ссылается указатель массива данных и можно будет сразу
понять относится ли этот адрес к CPU или к какому-нибудь GPU, а внутри функции уже, конечно,
придется вызывать различные функции для обработки передачи между CPU и GPU.
Логично задать вопрос в чем же разница между обычным использованием MPI+CUDA и
модификацией CUDA-Aware MPI. Из предыдущего слайда очевидно, что исходный код становится
проще, хотя лично я бы поспорил, что он становится более читабельным. Но ключевое
превосходство CUDA-Aware MPI над обычным MPI+CUDA кроется в технологии передачи данных
между узлами. Использование модифицированного MPI позволяет намного быстрее передавать
данные.
(Слайд 11) — Рассмотрим кластер
(Слайд 12) — Способы передачи данных
(Слайд 13) — Как этого добиться?
(Слайд 14) — CUDA-Aware MPI vs MPI+CUDA
(Слайд 15) — Принцип передачи данных Host -> Device
Для того, чтобы лучше понять как CUDA-Aware MPI ускоряет процесс передачи данных,
необходимо сперва вспомнить как данные передаются с CPU на GPU. Т.к. оперативная память не
так уж и велика, то в операционной системе реализован механизм выгрузки “лишней” памяти во
внешний носитель, такой как SSD. Эта технология основана на том, что вся память
представляется в виде “страниц”, т.е. некоторых блоков данных, которые, в зависимости от того
часто ли эти “страницы” используются, может находится в оперативной памяти (физической), либо
на SSD, до которой, как известно, не так уж и просто добраться, это долгий процесс. Такого рода
память называется Pagable memory (страничная память). Однако в силу некоторых причин была
создана так называемая Pinned memory — память, которая не может быть выгружена на внешний
носитель, т.е. у нее всегда есть физический адрес в оперативной памяти. Так вот, копирование
данных с хоста на девайс происходит в два этапа: как видно на первой схеме, сначала Pagabale
memory копируется в Pinned memory и только после этого на сам девайс. Так происходит по той
причине, что само копирование данных с одного устройства на другое происходит в режиме DMA
(Direct Memory Access) — режим обмена данными, в котором CPU не участвует, т.е. если вдруг
“страница” памяти была выгружена, только CPU сможет обратиться к этой памяти на внешнем
носителе, но так как CPU не участвует в данном копировании, то реализовать обмен данными
будет невозможно. Режим DMA используется для того, чтобы была возможность асинхронной
передачи данных, т.е. данные еще не передались, а CPU уже продолжает работу. CUDA позволяет
выделять память в pinned буфере, которую операционная система не может свопить. Т.о.
существует две схемы передачи данных с хоста на девайс, как видно на слайде, которые
реализуются в зависимости от того, как была выделена память на хосте.
Далее введем некоторые обозначения, чтобы лучше понимать диаграммы: память на GPU будем
обозначать зеленым прямоугольником, страничную память на хосте — синим, pinned memory, в
которую копируются данные при передаче данных между CPU и GPU — оранжевым
прямоугольником, pinned memory, в которую копируются данные перед передачей данных по сети
— красным прямоугольником. Зеленая стрелка — передача данных по PCI шине, синяя —
копирование данных внутри хоста, красная — передача данных по сети.
Давайте рассмотрим самый простой способ передачи данных между двумя GPU двух различных
узлов по сети. Сперва данные с GPU копируются во временный pinned буфер на хосте, после чего
происходит копирование в страничную память, далее снова в pinned буфер, потом данные
передаются по сети и процедура повторяется в обратном порядке. Если объем данных, который
требуется передать, большой, то он разбивается на несколько пакетов и последовательно
высылается, что можно видеть на нижней диаграмме.
(Слайд 16) — Введем обозначения
(Слайд 17) — Принцип передачи данных между узлами (схема)
(Слайд 18) — Принцип передачи данных между узлами (векторизация)
Вспомним что такое векторизация — это вид распараллеливания программы, при котором
однопоточные приложения, выполняющие одну операцию в каждый момент времени,
модифицируются для выполнения нескольких однотипных операций одновременно. Т.е. все
данные разбиваются сразу на пакеты и передаются последовательно. На диаграмме видно, что,
передав один пакет с GPU в pinned буфер, при передаче второго, параллельно с этим можно будет
скопировать первый в страничную память. И так далее пока все пакеты не окажутся в страничной
памяти. Аналогично происходит с передачей данных по сети и далее с выгрузкой пакетов на
нужную GPU. В этой диаграмме можно заметить одно узкое горлышко, которое не позволяет
полностью векторизовать передачу данных: копирование в страничную память. Т.к. копирование в
страничную память и в network pinned memory происходит на CPU, который работает, в нашем
предположении, в одном потоке, то приходится сперва выгрузить все пакеты в страничную
память, а только потом в network pinned memory.
Реализация CUDA-Aware MPI решает эту проблему. Сама функция MPI_Send реализована таким
образом, что она опускает копирование в страничную память и из cuda pinned memory копирует
сразу в network pinned memory.
Мы добились полной векторизации процесса передачи данных. Казалось бы, что это самый
оптимальный вариант передачи данных. Отчасти это так, однако, в 2010 году NVIDIA разработала
набор технологий, объединенных общим названием GPUDirect.
В 2010 году была разработана технология GPUDirect Shared Access, которая позволила исключить
копирования из cuda pinned memory в network pinned memory: после введения данной технологии
cuda pinned buffer и network pinned buffer являются чем-то единым и больше нет надобности их
разделять: чипсет взаимодействует с cuda pinned memory как с network pinned memory. Т.о.
используя вышеуказанную технологию мы еще больше сократили время передачи данных. Теперь
процесс состоит из трех шагов: передача с GPU в pinned buffer, далее по сети в pinned buffer
второго узла и на другую GPU.
Однако на этом разработчики NVIDIA не остановились и решили еще больше усовершенствовать
свою технологию. В 2011 году была разработана технология GPUDirect P2P. Она заключается в
том, что если на одном узле есть две GPU, то передача данных между ними происходит вообще
без участия CPU: всю работу выполняет чипсет (как видно на верхнем рисунке). Следует отдельно
(Слайд 19) — Принцип передачи данных между узлами (CUDA-Aware MPI)
(Слайд 20) — Принцип передачи данных между узлами (CUDA-Aware MPI)
(векторизация)
(Слайд 21) — GPUDirect Shared Access
(Слайд 22) — GPUDirect P2P & RDMA
заметить, что введение технологии NVLink в семействе видеокарт архитектуры Pascal позволит
передавать данные между видеокартами с невероятной, по сравнению с сегодняшней, скоростью.
В 2013 году была введена технология GPUDirect RDMA (Remote direct memory access), которая
позволяет передавать данные между двумя GPU на двух разных узлах без использования какого-
либо CPU (как видно на нижнем рисунке).
Т.о. мы получили самый быстрый из возможных на данный момент способ передачи данных между
двумя GPU на двух разных узлах. У такого способа, который не использует вообще CPU для
копирования данных, есть свое название — он называется Zero-copy. На диаграмме показан
окончательный способ передачи, который в данном случае является самым простым для
понимания: данные передаются непосредственно между видеокартами. В CUDA-Aware MPI
реализованы все эти методы, которые могут быть автоматически использованы без написания
лишнего кода. И преимущество CUDA-Aware MPI перед обычным CUDA+MPI заключается именно в
том, что все эти технологии уже использованы и имеют удобный универсальный интерфейс.
На этом я закончу рассказ о технологии CUDA-Aware MPI, покажу сравнение некоторых методов
передачи данных и подведу некий итог. На графике показана пропускная способность передачи
данных при различных методах передачи. Синяя линяя характеризует передачу данных между
хостами, зеленая — между двумя GPU с использованием GPUDirect Shared Access, т.е. случай,
когда происходит всего одно копирование на хост, а красная — стандартная передача данных
между двух GPU: с тремя копированиями на хост. Как видно из графика использование GPUDirect
значительно повышает пропускную способность, поэтому предпочтительней использовать именно
CUDA-Aware MPI.
(Слайд 23) — GPUDirect RDMA & P2P (продолжение)
(Слайд 24) — Сравнение работы при разных схемах передачи данных

More Related Content

Viewers also liked

Viewers also liked (13)

CUDA-Aware MPI
CUDA-Aware MPICUDA-Aware MPI
CUDA-Aware MPI
 
Lecture2 cuda spring 2010
Lecture2 cuda spring 2010Lecture2 cuda spring 2010
Lecture2 cuda spring 2010
 
Cbse class-xii-computer-science-projec
Cbse class-xii-computer-science-projecCbse class-xii-computer-science-projec
Cbse class-xii-computer-science-projec
 
Job Description
Job DescriptionJob Description
Job Description
 
Modelling and Simulation
Modelling and SimulationModelling and Simulation
Modelling and Simulation
 
KI_Booklet (1)
KI_Booklet (1)KI_Booklet (1)
KI_Booklet (1)
 
risk-awards-2015
risk-awards-2015risk-awards-2015
risk-awards-2015
 
calculus
calculuscalculus
calculus
 
Affordamac Campaign Presentation
Affordamac Campaign PresentationAffordamac Campaign Presentation
Affordamac Campaign Presentation
 
Classic photo album
Classic photo albumClassic photo album
Classic photo album
 
Cbse class-xii-computer-science-project
Cbse class-xii-computer-science-project Cbse class-xii-computer-science-project
Cbse class-xii-computer-science-project
 
Tư tưởng Hồ Chí Minh về đại đoàn kết dân tộc
Tư tưởng Hồ Chí Minh về đại đoàn kết dân tộcTư tưởng Hồ Chí Minh về đại đoàn kết dân tộc
Tư tưởng Hồ Chí Minh về đại đoàn kết dân tộc
 
Smart School
Smart SchoolSmart School
Smart School
 

Similar to CUDA-Aware MPI notes

Семинар 8. Параллельное программирование на MPI (часть 1)
Семинар 8. Параллельное программирование на MPI (часть 1)Семинар 8. Параллельное программирование на MPI (часть 1)
Семинар 8. Параллельное программирование на MPI (часть 1)Mikhail Kurnosov
 
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обменыЛекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обменыAlexey Paznikov
 
Обзор операционных систем Microsoft Windows.
Обзор операционных систем Microsoft Windows.Обзор операционных систем Microsoft Windows.
Обзор операционных систем Microsoft Windows.aizhanzhik
 
кластеры и суперкомпьютеры
кластеры и суперкомпьютерыкластеры и суперкомпьютеры
кластеры и суперкомпьютерыnastena07051995
 
Основные принципы управления процессором и процессами
Основные принципы управления процессором и процессамиОсновные принципы управления процессором и процессами
Основные принципы управления процессором и процессамиkurbanovafaina
 
Параллельное программирование на современных видеокартах
Параллельное программирование на современных видеокартахПараллельное программирование на современных видеокартах
Параллельное программирование на современных видеокартахAlex Tutubalin
 
Стандарт MPI (Message Passing Interface)
Стандарт MPI (Message Passing Interface)Стандарт MPI (Message Passing Interface)
Стандарт MPI (Message Passing Interface)Mikhail Kurnosov
 
Bgp методякоби
Bgp методякобиBgp методякоби
Bgp методякобиMichael Karpov
 
Distributed Systems Presentation for Business informatics students (Staroletov)
Distributed Systems Presentation for Business informatics students (Staroletov)Distributed Systems Presentation for Business informatics students (Staroletov)
Distributed Systems Presentation for Business informatics students (Staroletov)Sergey Staroletov
 
тест по темам принцип открытой архитектуры
тест по темам принцип открытой архитектурытест по темам принцип открытой архитектуры
тест по темам принцип открытой архитектурыJIuc
 
32 подводных камня OpenMP при программировании на Си++
32 подводных камня OpenMP при программировании на Си++32 подводных камня OpenMP при программировании на Си++
32 подводных камня OpenMP при программировании на Си++Tatyanazaxarova
 
01 готовимся к экзамену по информатике. теория. задачи 2002
01  готовимся к экзамену по информатике. теория. задачи 200201  готовимся к экзамену по информатике. теория. задачи 2002
01 готовимся к экзамену по информатике. теория. задачи 2002dfdkfjs
 
ляпушкин виктор ис 21
ляпушкин виктор ис 21ляпушкин виктор ис 21
ляпушкин виктор ис 21аыв цуакуца
 

Similar to CUDA-Aware MPI notes (20)

Семинар 8. Параллельное программирование на MPI (часть 1)
Семинар 8. Параллельное программирование на MPI (часть 1)Семинар 8. Параллельное программирование на MPI (часть 1)
Семинар 8. Параллельное программирование на MPI (часть 1)
 
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обменыЛекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
 
Обзор операционных систем Microsoft Windows.
Обзор операционных систем Microsoft Windows.Обзор операционных систем Microsoft Windows.
Обзор операционных систем Microsoft Windows.
 
кластеры и суперкомпьютеры
кластеры и суперкомпьютерыкластеры и суперкомпьютеры
кластеры и суперкомпьютеры
 
2
22
2
 
Основные принципы управления процессором и процессами
Основные принципы управления процессором и процессамиОсновные принципы управления процессором и процессами
Основные принципы управления процессором и процессами
 
Параллельное программирование на современных видеокартах
Параллельное программирование на современных видеокартахПараллельное программирование на современных видеокартах
Параллельное программирование на современных видеокартах
 
Gpgpu
GpgpuGpgpu
Gpgpu
 
Стандарт MPI (Message Passing Interface)
Стандарт MPI (Message Passing Interface)Стандарт MPI (Message Passing Interface)
Стандарт MPI (Message Passing Interface)
 
Bgp методякоби
Bgp методякобиBgp методякоби
Bgp методякоби
 
Процессор
ПроцессорПроцессор
Процессор
 
Это сложно
Это сложноЭто сложно
Это сложно
 
Zosh19
Zosh19Zosh19
Zosh19
 
Distributed Systems Presentation for Business informatics students (Staroletov)
Distributed Systems Presentation for Business informatics students (Staroletov)Distributed Systems Presentation for Business informatics students (Staroletov)
Distributed Systems Presentation for Business informatics students (Staroletov)
 
тест по темам принцип открытой архитектуры
тест по темам принцип открытой архитектурытест по темам принцип открытой архитектуры
тест по темам принцип открытой архитектуры
 
32 подводных камня OpenMP при программировании на Си++
32 подводных камня OpenMP при программировании на Си++32 подводных камня OpenMP при программировании на Си++
32 подводных камня OpenMP при программировании на Си++
 
01 готовимся к экзамену по информатике. теория. задачи 2002
01  готовимся к экзамену по информатике. теория. задачи 200201  готовимся к экзамену по информатике. теория. задачи 2002
01 готовимся к экзамену по информатике. теория. задачи 2002
 
ляпушкин виктор ис 21
ляпушкин виктор ис 21ляпушкин виктор ис 21
ляпушкин виктор ис 21
 
36арл
36арл36арл
36арл
 
Lirc или домашний медиацентр
Lirc или домашний медиацентрLirc или домашний медиацентр
Lirc или домашний медиацентр
 

CUDA-Aware MPI notes

  • 1. Сейчас я буду рассказывать про совместное использование таких двух технологий, как CUDA и MPI. Доклад будет состоять из трех частей: первым делом я дам вам общее представление о том, что такое MPI, затем расскажу как можно использовать связку CUDA с MPI и, под конец, покажу очень простенький пример использования данной технологии, в котором я буду вычислять аппроксимации производных в точках. Все мы знаем как выглядят сегодняшние суперкомпьютеры. На слайде приведена фотография суперкомпьютера BLUE GENE компании IBM. На данный момент суперкомпьютер есть ни что иное, как объединение высокоскоростными каналами связи нескольких компьютеров, имеющих свой собственный процессор, видеокарту (возможно не одну) и память. Сразу следует сделать оговорку, что память может быть как общей, так и индивидуальной для каждого процессора или даже процесса (все зависит от операционной системы). Под узлом кластера можно понимать либо отдельный процессор, либо отдельный процесс (в зависимости от размера кластера). Если пользователь требует запустить 10 узлов, а кластер имеет 10 CPU, то каждый узел — это отдельный процессор, однако если запросить большее количество узлов, то хотя бы один из процессоров будет вынужден выполнять две работы в двух разных процессах (возможно параллельно: если процессор многоядерный). Во время работы узлов возникает необходимость обмениваться информацией между различными узлами; именно для этого и создан MPI. MPI расшифровывается как Message Passing Interface (Интерфейс Передачи Сообщений). Когда говорят об MPI, то в подавляющем большинстве случаев подразумевают не низкоуровневую структуру работы самого кластера, а некий высокоуровневый софт, удовлетворяющий общим правилам. MPI — софт, работающий между операционной системой и вашим приложением, одной из главных функций которого является упрощение процесса передачи информации между процессами в сети: как локальной, так и глобальной, т.е. MPI API создает некую обертку над сокетами, что очень сильно упрощает написание кода. Следует отдельно отметить, что, хоть, формально MPI не является стандартом, подобным тем, что выпускают организации CUDA-Aware MPi (Слайд 1) — Вводный слайд (Слайд 2) — Разделы (Слайд 3) — Кластеры (Слайд 4) — Что такое MPI
  • 2. стандартизации, такие как ANSI или ISO, на данный момент он — самая развитая кроссплатформенная библиотека параллельного программирования с передачей сообщений и, тем самым, является де-факто стандартом. Принцип работы с MPI очень похож на работу с CUDA: он работает по принципу SIMD (одиночный поток команд, множественный поток данных), т.е. мы пишем одну программу, которая запускается на множестве процессах и процессорах. Однако, несмотря на их внешнее сходство, между этими технологиями есть очень важные отличия. Два принципиальных отличия между ними: сам принцип работы и организация памяти. GPU организована таким образом, что параллельно на каждом мультипроцессоре выполняется один варп (warp), который в современных GPU состоит из 32 потоков, причем очень важно знать, что эти потоки не уникальны. Принцип их работы — SIMD. Если в нашей программе на CUDA есть ветвление (if) и часть потоков в варпе выполняет одну часть кода, а вторая — другую, то эти потоки не будут работать параллельно: сначала отработает одна часть потоков, в затем — другая, и это надо учитывать при создании архитектуры программы. Так вот, MPI организован принципиально по-другому: все узлы работают абсолютно независимо друг от друга. Второе отличие — память: CUDA имеет несколько видов памяти: глобальная память, которая доступна всем запущенным потокам, разделяемая память, доступная потокам лишь одного блока и регистровая память, уникальная (приватная) для каждого потока. MPI предполагает, что у каждого узла своя приватная память, к которой у других узлов нет доступа. Лично у меня организации MPI ассоциируется с тем, что каждый узел работает на собственном CPU в абсолютно параллельном режиме, однако по факту, как можно видеть на первой схеме, на одном CPU могут работать сразу несколько узлов, причем они могут работать либо параллельно, если процессор многоядерный, либо последовательно, когда планировщик операционной системы распределяет процессорное время для каждого узла, которые в данном случае являются отдельными процессами. По факту, программу, работающую с использованием MPI вообще можно запустить на одном компьютере. Для того, чтобы лучше понять принцип работы MPI, рассмотрим несколько стандартных функций: MPIInit инициализирует окружение, т.е. устанавливает соединение с другими узлами программы. MPICommsize возвращает количество запущенных узлов, а MPICommrank — номер конкретного узла. MPISend — библиотечная функция отправки массива данных узлу, номер которого указывается в аргументах функции. MPIRecv — соответственно, функция получения массива данных. MPIFinalize — закрытие соединений и очистка памяти. (Слайд 5) — MPI vs CUDA (Слайд 6) — Виды кластеров (Слайд 7) — Стандартные функции (Слайд 8) — Пример программы
  • 3. Рассмотрим исходный код программы из которого видно, как используются эти функции при написании программ. Каждая программа должна начинаться с инициализации MPI окружения: строчка 7. На строчках 8-9 происходит инициализация переменных: текущий узел и количество запущенных узлов. В данном примере программа для своей работы требует запуска как минимум трех потоков, что и проверяется на строках 11-14. Стандартный прием использующийся при написании MPI приложений виден в строках 15-18: последний по номеру узел считается сервером и его задачей является работа с файловой системой, сбор данных после того, как отработают остальные узлы и визуализация результата, в то время как остальные узлы являются обычными вычислительными узлами. Для того, чтобы правильно завершить работу программы необходимо очистить память и закрыть соединения, что и делается на 20 строке. Для компиляции программы необходимо использовать специальный компилятор, отличающийся от привычного нам gcc или clang. Для компиляции исходников на C необходимо использовать mpicc, для плюсов — mpicxx. Запускать программу надо не просто запуская исполняемый файл, а через специальную программу mpirun, которой в качестве параметров передается количество узлов, которое требуется запустить с исполняющейся программой, путь к исполняемому файлу самой программы и аргументы программы. mpirun сама запускает процессы с указанной программой на доступных CPU и ядрах. Мы рассмотрели азы технологии MPI, поняли, что основная идея технологии — запустить несколько программ с разными id’шниками на разных CPU/ядрах/процессах, которые могут обмениваться массивами данных между собой. Следующий вопрос, который возникает при использовании MPI: как конкретно использовать потенциал GPU при таком подходе программирования. Всего возможны два принципиально разных принципа использования MPI и CUDA: первый — использовать ядра мультипроцессоров GPU в качестве узлов, а второе — в качестве узлов использовать только CPU/ядра CPU/процессы CPU. Рассмотрим по-подробнее первый вариант. На самом деле его реализация в классическом стандарте MPI невозможна. И причиной тому является “принцип передачи данных” между CPU и GPU реализованный в CUDA: в этой технологии вообще нет понятия “сокет”, поток мультипроцессора при всем своем желании не сможет обратиться к какому-либо процессу CPU и это, не говоря уже о взаимодействии с CPU процессами через локальную сеть. У мультипроцессоров на GPU просто нет такой возможности в силу их устройства, поэтому возможен лишь второй вариант взаимодействия MPI и CUDA. В данном случае возникает другая проблема: если каждому узлу соответствует свой CPU и GPU, то все прекрасно, но если узлами являются процессы на одном CPU, которые обращаются к одной GPU, то необходимо очень аккуратно работать как с памятью, выделяющейся на GPU, так и нагрузкой GPU, т.к. она будет работать в последовательном режиме: сначала с одним процессом, потом — с другим, что значительно может замедлить работу программы. (Слайд 9) — Компиляция и запуск (Слайд 10) — Взаимодействие MPI & CUDA
  • 4. Для того, чтобы показать на примере принципы взаимодействий MPI и CUDA, рассмотрим кластер, состоящий из трех CPU, каждой из которых соответствует свой собственный GPU, и сам кластер объединен в локальную сеть. Т.е. мы запускаем 3 узла, один из которых, как правило последний, играет роль сервера. Предположим, что мы хотим передать массив данных, хранящийся на GPU первого узла, GPU второго узла. Это может быть сделано двумя способами: сначала скопировать данные на CPU первого узла, передать на CPU второго узла, а потом — на GPU второго узла, что соответствует использованию схемы MPI+CUDA, либо сразу передать с одного GPU на другой — такого эффекта можно добиться только используя CUDA-Aware MPI: специальную модификацию библиотеки MPI, грамотно работающей с CUDA. В пункте (1) приведен пример исходного кода двух узлов, организующих передачу данных через память CPU . В пункте (2) приведен пример исходного кода, реализующего “прямую” передачу данных. Если первый пункт более менее понятно как устроен, то с реализацией второго возникают вопросы. Ведь сам MPI интерфейс должен быть универсален и делать две разные функции для передачи и приема массива данных для CPU и GPU неправильно. В принципе можно было бы добавить в набор аргументов функций флаг, указывающий на то, какой тип передачи данных используется. Но тем не менее существует самое правильное решение этой проблемы. Начиная с CUDA 4.0 на устройствах с архитектурой 2.0 было введено единое пространство виртуальных адресов для CPU и нескольких GPU. Т.е. не нужны никакие флаги, достаточно просто посмотреть на адрес, на который ссылается указатель массива данных и можно будет сразу понять относится ли этот адрес к CPU или к какому-нибудь GPU, а внутри функции уже, конечно, придется вызывать различные функции для обработки передачи между CPU и GPU. Логично задать вопрос в чем же разница между обычным использованием MPI+CUDA и модификацией CUDA-Aware MPI. Из предыдущего слайда очевидно, что исходный код становится проще, хотя лично я бы поспорил, что он становится более читабельным. Но ключевое превосходство CUDA-Aware MPI над обычным MPI+CUDA кроется в технологии передачи данных между узлами. Использование модифицированного MPI позволяет намного быстрее передавать данные. (Слайд 11) — Рассмотрим кластер (Слайд 12) — Способы передачи данных (Слайд 13) — Как этого добиться? (Слайд 14) — CUDA-Aware MPI vs MPI+CUDA (Слайд 15) — Принцип передачи данных Host -> Device
  • 5. Для того, чтобы лучше понять как CUDA-Aware MPI ускоряет процесс передачи данных, необходимо сперва вспомнить как данные передаются с CPU на GPU. Т.к. оперативная память не так уж и велика, то в операционной системе реализован механизм выгрузки “лишней” памяти во внешний носитель, такой как SSD. Эта технология основана на том, что вся память представляется в виде “страниц”, т.е. некоторых блоков данных, которые, в зависимости от того часто ли эти “страницы” используются, может находится в оперативной памяти (физической), либо на SSD, до которой, как известно, не так уж и просто добраться, это долгий процесс. Такого рода память называется Pagable memory (страничная память). Однако в силу некоторых причин была создана так называемая Pinned memory — память, которая не может быть выгружена на внешний носитель, т.е. у нее всегда есть физический адрес в оперативной памяти. Так вот, копирование данных с хоста на девайс происходит в два этапа: как видно на первой схеме, сначала Pagabale memory копируется в Pinned memory и только после этого на сам девайс. Так происходит по той причине, что само копирование данных с одного устройства на другое происходит в режиме DMA (Direct Memory Access) — режим обмена данными, в котором CPU не участвует, т.е. если вдруг “страница” памяти была выгружена, только CPU сможет обратиться к этой памяти на внешнем носителе, но так как CPU не участвует в данном копировании, то реализовать обмен данными будет невозможно. Режим DMA используется для того, чтобы была возможность асинхронной передачи данных, т.е. данные еще не передались, а CPU уже продолжает работу. CUDA позволяет выделять память в pinned буфере, которую операционная система не может свопить. Т.о. существует две схемы передачи данных с хоста на девайс, как видно на слайде, которые реализуются в зависимости от того, как была выделена память на хосте. Далее введем некоторые обозначения, чтобы лучше понимать диаграммы: память на GPU будем обозначать зеленым прямоугольником, страничную память на хосте — синим, pinned memory, в которую копируются данные при передаче данных между CPU и GPU — оранжевым прямоугольником, pinned memory, в которую копируются данные перед передачей данных по сети — красным прямоугольником. Зеленая стрелка — передача данных по PCI шине, синяя — копирование данных внутри хоста, красная — передача данных по сети. Давайте рассмотрим самый простой способ передачи данных между двумя GPU двух различных узлов по сети. Сперва данные с GPU копируются во временный pinned буфер на хосте, после чего происходит копирование в страничную память, далее снова в pinned буфер, потом данные передаются по сети и процедура повторяется в обратном порядке. Если объем данных, который требуется передать, большой, то он разбивается на несколько пакетов и последовательно высылается, что можно видеть на нижней диаграмме. (Слайд 16) — Введем обозначения (Слайд 17) — Принцип передачи данных между узлами (схема) (Слайд 18) — Принцип передачи данных между узлами (векторизация)
  • 6. Вспомним что такое векторизация — это вид распараллеливания программы, при котором однопоточные приложения, выполняющие одну операцию в каждый момент времени, модифицируются для выполнения нескольких однотипных операций одновременно. Т.е. все данные разбиваются сразу на пакеты и передаются последовательно. На диаграмме видно, что, передав один пакет с GPU в pinned буфер, при передаче второго, параллельно с этим можно будет скопировать первый в страничную память. И так далее пока все пакеты не окажутся в страничной памяти. Аналогично происходит с передачей данных по сети и далее с выгрузкой пакетов на нужную GPU. В этой диаграмме можно заметить одно узкое горлышко, которое не позволяет полностью векторизовать передачу данных: копирование в страничную память. Т.к. копирование в страничную память и в network pinned memory происходит на CPU, который работает, в нашем предположении, в одном потоке, то приходится сперва выгрузить все пакеты в страничную память, а только потом в network pinned memory. Реализация CUDA-Aware MPI решает эту проблему. Сама функция MPI_Send реализована таким образом, что она опускает копирование в страничную память и из cuda pinned memory копирует сразу в network pinned memory. Мы добились полной векторизации процесса передачи данных. Казалось бы, что это самый оптимальный вариант передачи данных. Отчасти это так, однако, в 2010 году NVIDIA разработала набор технологий, объединенных общим названием GPUDirect. В 2010 году была разработана технология GPUDirect Shared Access, которая позволила исключить копирования из cuda pinned memory в network pinned memory: после введения данной технологии cuda pinned buffer и network pinned buffer являются чем-то единым и больше нет надобности их разделять: чипсет взаимодействует с cuda pinned memory как с network pinned memory. Т.о. используя вышеуказанную технологию мы еще больше сократили время передачи данных. Теперь процесс состоит из трех шагов: передача с GPU в pinned buffer, далее по сети в pinned buffer второго узла и на другую GPU. Однако на этом разработчики NVIDIA не остановились и решили еще больше усовершенствовать свою технологию. В 2011 году была разработана технология GPUDirect P2P. Она заключается в том, что если на одном узле есть две GPU, то передача данных между ними происходит вообще без участия CPU: всю работу выполняет чипсет (как видно на верхнем рисунке). Следует отдельно (Слайд 19) — Принцип передачи данных между узлами (CUDA-Aware MPI) (Слайд 20) — Принцип передачи данных между узлами (CUDA-Aware MPI) (векторизация) (Слайд 21) — GPUDirect Shared Access (Слайд 22) — GPUDirect P2P & RDMA
  • 7. заметить, что введение технологии NVLink в семействе видеокарт архитектуры Pascal позволит передавать данные между видеокартами с невероятной, по сравнению с сегодняшней, скоростью. В 2013 году была введена технология GPUDirect RDMA (Remote direct memory access), которая позволяет передавать данные между двумя GPU на двух разных узлах без использования какого- либо CPU (как видно на нижнем рисунке). Т.о. мы получили самый быстрый из возможных на данный момент способ передачи данных между двумя GPU на двух разных узлах. У такого способа, который не использует вообще CPU для копирования данных, есть свое название — он называется Zero-copy. На диаграмме показан окончательный способ передачи, который в данном случае является самым простым для понимания: данные передаются непосредственно между видеокартами. В CUDA-Aware MPI реализованы все эти методы, которые могут быть автоматически использованы без написания лишнего кода. И преимущество CUDA-Aware MPI перед обычным CUDA+MPI заключается именно в том, что все эти технологии уже использованы и имеют удобный универсальный интерфейс. На этом я закончу рассказ о технологии CUDA-Aware MPI, покажу сравнение некоторых методов передачи данных и подведу некий итог. На графике показана пропускная способность передачи данных при различных методах передачи. Синяя линяя характеризует передачу данных между хостами, зеленая — между двумя GPU с использованием GPUDirect Shared Access, т.е. случай, когда происходит всего одно копирование на хост, а красная — стандартная передача данных между двух GPU: с тремя копированиями на хост. Как видно из графика использование GPUDirect значительно повышает пропускную способность, поэтому предпочтительней использовать именно CUDA-Aware MPI. (Слайд 23) — GPUDirect RDMA & P2P (продолжение) (Слайд 24) — Сравнение работы при разных схемах передачи данных