7

Click here to load reader

CUDA-Aware MPI notes

Embed Size (px)

Citation preview

Page 1: CUDA-Aware MPI notes

Сейчас я буду рассказывать про совместное использование таких двух технологий, как 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

Page 2: CUDA-Aware MPI notes

стандартизации, такие как 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) — Пример программы

Page 3: CUDA-Aware MPI notes

Рассмотрим исходный код программы из которого видно, как используются эти функции принаписании программ. Каждая программа должна начинаться с инициализации 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

Page 4: CUDA-Aware MPI notes

Для того, чтобы показать на примере принципы взаимодействий 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

Page 5: CUDA-Aware MPI notes

Для того, чтобы лучше понять как CUDA-Aware MPI ускоряет процесс передачи данных,необходимо сперва вспомнить как данные передаются с CPU на GPU. Т.к. оперативная память нетак уж и велика, то в операционной системе реализован механизм выгрузки “лишней” памяти вовнешний носитель, такой как SSD. Эта технология основана на том, что вся памятьпредставляется в виде “страниц”, т.е. некоторых блоков данных, которые, в зависимости от тогочасто ли эти “страницы” используются, может находится в оперативной памяти (физической), либона SSD, до которой, как известно, не так уж и просто добраться, это долгий процесс. Такого родапамять называется Pagable memory (страничная память). Однако в силу некоторых причин быласоздана так называемая Pinned memory — память, которая не может быть выгружена на внешнийноситель, т.е. у нее всегда есть физический адрес в оперативной памяти. Так вот, копированиеданных с хоста на девайс происходит в два этапа: как видно на первой схеме, сначала Pagabalememory копируется в 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) — Принцип передачи данных между узлами (векторизация)

Page 6: CUDA-Aware MPI notes

Вспомним что такое векторизация — это вид распараллеливания программы, при которомоднопоточные приложения, выполняющие одну операцию в каждый момент времени,модифицируются для выполнения нескольких однотипных операций одновременно. Т.е. вседанные разбиваются сразу на пакеты и передаются последовательно. На диаграмме видно, что,передав один пакет с 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

Page 7: CUDA-Aware MPI notes

заметить, что введение технологии 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) — Сравнение работы при разных схемах передачи данных