Введення в gpu-обчислення, мої it-замітки

У цій замітці зібрана інформація яка допоможе зрозуміти загальні принципи GPU-програмування.

Введення в архітектуру GPU

Розділяють два види пристроїв - то яке управляє загальною логікою - host. і то яке вміє швидко виконати певний набір інструкцій над великим об'ємом даних - device.

Введення в gpu-обчислення, мої it-замітки

Програмування для GPU

Введення в gpu-обчислення, мої it-замітки

Програми пишуться на розширенні мови Сі від NVidia / OpenCL і компілюються за допомогою спеціальних компіляторів входять в SDK. У кожного виробника зрозуміло свій. Є два варіанти складання - під цільову платформу - коли явно вказується на якому залозі буде здійсняться код або в певний проміжний код, який при запуску на цільовому залозі буде перетворений драйвером в набір конкретних інструкцій для використовуваної архітектури (з поправкою на обчислювальні можливості заліза).

Введення в gpu-обчислення, мої it-замітки
Виконувана на GPU програма називається ядром - kernel - що для CUDA що для OpenCL це і буде той набір інструкцій які застосовуються до всіх даних. Функція одна, а дані на яких вона виконується - різні - принцип SIMD.

Драйвер CUDA / OpenCL розбиває вхідні дані на безліч частин (потоки виконання об'єднані в блоки) і призначає для виконання на кожен потоковий процесор. Програміст може і повинен вказувати драйверу як максимально ефективно задіяти існуючі обчислювальні ресурси, задаючи розміри блоків і число потоків в них. Зрозуміло, максимально допустимі значення варіюються від пристрою до пристрою. Хороша практика - перед виконанням запросити параметри заліза, на якому буде виконуватися ядро ​​і на їх підставі обчислити оптимальні розміри блоків.

Схематично, розподіл завдань на GPU відбувається так:

Введення в gpu-обчислення, мої it-замітки

Виконання програми на GPU

work-item (OpenCL) або thread (CUDA) - ядро ​​і набір даних, виконується на Stream Processor (Processing Element в разі ATI пристроїв).
work group (OpenCL) або thread block (CUDA) виконується на Multi Processor (SIMD Engine)
Grid (набір блоків таке поняття є тільки у НВідіа) = виконується на цілому пристрої - GPU. Для виконання на GPU все потоки об'єднуються в варпа (warp - CUDA) або вейффронти (wavefront - OpenCL) - пул потоків, призначених на виконання на одному окремому мультипроцесорі. Тобто якщо число блоків або робочих груп виявилося більше ніж число мултіпроцессоров - фактично, в кожен момент часу виконується група (або групи) об'єднані в варп - всі інші чекають своєї черги.

Одне ядро ​​може виконуватися на кількох GPU пристроях (як для CUDA так і для OpenCL, як для карток ATI так і для NVidia).
Одне GPU-пристрій може одночасно виконувати кілька ядер (як для CUDA так і для OpenCL, для NVidia - починаючи з архітектури 20 і вище). Посилання по даних питаннях см. В кінці статті.

Модель пам'яті OpenCL (в дужках - термінологія CUDA)

Введення в gpu-обчислення, мої it-замітки

Тут головне запам'ятати про час доступу до кожного виду пам'яті. Найповільніший це глобальна пам'ять - у сучасних відекарти її аж до 6 Гб. Далі по швидкості йде колективна пам'ять (shared - CUDA, local - OpenCL) - загальна для всіх потоків в блоці (thread block - CUDA, work-group - OpenCL) - проте її завжди мало - 32-48 Кб для мультипроцессора. Найшвидшим є локальна пам'ять за рахунок використання регістрів і кешування, але треба розуміти що все що ні вмістилося в кеши \ регістри - буде зберігається в глобальній пам'яті з усіма наслідками, що випливають.

Патерни паралельного програмування для GPU

Введення в gpu-обчислення, мої it-замітки

Map - GPU parallel pattern

Тут все просто - беремо вхідний масив даних і до кожного елементу застосовуємо якийсь оператор - ядро ​​- ніяк що не зачіпає інші елементи - тобто читаємо і пишемо в певні осередки пам'яті.

Ставлення - як один до одного (one-to-one).

приклад - множення матриць, оператор інкремента або декремента застосований до кожного елементу матриці і т.п.

Введення в gpu-обчислення, мої it-замітки

Scatter - GPU parallel pattern

Для кожного елемента вхідного масиву ми обчислюємо позицію в вихідному масиві, на яке він вплине (шляхом застосування відповідного оператора).

Ставлення - як один до багатьох (one-to-many).

3. Transpose

Введення в gpu-обчислення, мої it-замітки

Transpose - GPU parallel pattern

Даний патерн можна розглядати як окремий випадок паттерна scatter.
Використовується для оптимізації обчислень - перерозподіляючи елементи в пам'яті можна досягти значного підвищення продуктивності.

Введення в gpu-обчислення, мої it-замітки

Gather - GPU parallel pattern

Є зворотним до паттерну Scatter - для кожного елемента у вихідному масиві ми обчислюємо індекси елементів з вхідного масиву, які нададуть на нього вплив:

Ставлення - кілька до одного (many-to-one).

Введення в gpu-обчислення, мої it-замітки

Stencil - GPU parallel pattern

Даний патерн можна розглядати як окремий випадок паттерна gather. Тут для отримання значення в кожному осередку вихідного масиву використовується певний шаблон для обчислення всіх елементів вхідного масиву, які вплинуть на фінальне значення. Всілякі фільтри побудовані саме за цим принципом.

Ставлення кілька до одного (several-to-one)

Приклад: фільтр Гауссіана.

Введення в gpu-обчислення, мої it-замітки

Reduce - GPU parallel pattern

Ставлення все до одного (All-to-one)

Приклад - обчислення суми або максимуму в масиві.

При обчисленні значення в кожному осередку вихідного масиву необхідно враховувати значення кожного елемента вхідного. Існує дві основні реалізації - Hillis and Steele і Blelloch.

out [i] = F [i] = operator (F [i-1], in [i])

Ставлення все до всіх (all-to-all).

Приклади - сортування даних.

Корисні посилання

Введення в CUDA: