Що таке CUDA, що таке злиття пам'яті, і як це досягається?


77

Що "злито" в транзакції глобальної пам'яті CUDA? Я не міг зрозуміти навіть після перегляду мого керівництва CUDA. Як це зробити? У прикладі матриці програмування CUDA доступ до рядка матриці за рядком називається "злитим" або col .. за зб .. називається злитим? Що правильно і чому?

Відповіді:


153

Цілком ймовірно, що ця інформація стосується лише обчислювальних можливостей 1.x або cuda 2.0. Більш пізні архітектури та Cuda 3.0 мають більш складний доступ до глобальної пам'яті, і насправді "об'єднані глобальні навантаження" навіть не профілізовані для цих мікросхем.

Крім того, цю логіку можна застосувати до спільної пам'яті, щоб уникнути конфліктів у банках.


Спільна транзакція пам'яті - це така транзакція, при якій усі потоки в напівдеформації одночасно отримують доступ до глобальної пам'яті. Це занадто просто, але правильний спосіб зробити це - просто послідовні потоки отримують доступ до послідовних адрес пам’яті.

Отже, якщо потоки 0, 1, 2 та 3 читають глобальну пам’ять 0x0, 0x4, 0x8 та 0xc, це має бути об’єднане читання.

У прикладі матриці майте на увазі, що ви хочете, щоб ваша матриця зберігалася лінійно в пам'яті. Ви можете робити це як завгодно, і ваш доступ до пам'яті повинен відображати, як викладена ваша матриця. Отже, матриця 3x4 нижче

0 1 2 3
4 5 6 7
8 9 a b

можна робити рядок за рядком, наприклад, так, щоб (r, c) відображалося в пам'яті (r * 4 + c)

0 1 2 3 4 5 6 7 8 9 a b

Припустимо, вам потрібно отримати доступ до елемента один раз і скажімо, що у вас є чотири потоки. Які нитки будуть використані для якого елемента? Можливо, і те, і інше

thread 0:  0, 1, 2
thread 1:  3, 4, 5
thread 2:  6, 7, 8
thread 3:  9, a, b

або

thread 0:  0, 4, 8
thread 1:  1, 5, 9
thread 2:  2, 6, a
thread 3:  3, 7, b

Який краще? Що призведе до злиття читань, а що ні?

У будь-якому випадку, кожна нитка робить три доступу. Давайте розглянемо перший доступ і подивимося, чи потоки послідовно отримують доступ до пам'яті. У першому варіанті перший доступ - 0, 3, 6, 9. Не послідовно, не злитий. Другий варіант, це 0, 1, 2, 3. Послідовно! Злитий! Ага!

Найкращий спосіб - це, мабуть, написати своє ядро, а потім профайлювати його, щоб перевірити, чи є у вас не з’єднані глобальні завантаження та сховища.


Дякуємо за пояснення щодо того, який потік отримує доступ до якого елемента. На даний момент у мене є перший варіант ( thread 0: 0, 1, 2 etc...), тому я зараз шукаю кращого варіанту :-)
Тім,

@jmilloy - Я хочу запитати, як профілізувати ядро, щоб побачити глобальні завантаження та сховища, що не об'єднані.
muradin

1
@muradin Чи можете ви використовувати Visual Profiler? developer.nvidia.com/nvidia-visual-profiler
jmilloy

@jmilloy - Оскільки я працюю в неграфічному середовищі, я шукав і знаходив nvprof в режимі командного рядка. але коли я хотів його запустити, сталася помилка: nvprof не зміг завантажити libcuda.so.1, такого файлу чи каталогу немає! ти знаєш чому?
muradin

@jmilloy: Привіт, дуже гарний приклад! Дякую! Я хотів запитати вас, коли ви говорите, що можете запустити профайлер, щоб перевірити, чи з’єднаний ви чи ні, як ви можете це зробити? Для прикладу запустіть: nvprof --metrics gld_efficiency? І чим вище, тим краще?
Джордж

11

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

введіть тут опис зображення

Приклад на малюнку вище допомагає пояснити поєднане розташування:

На рис. (А) n векторів довжиною m зберігаються лінійно. Елемент i вектора j позначається v j i . Кожному потоку в ядрі графічного процесора присвоюється один m- довжина вектора. Потоки в CUDA згруповані в масив блоків, і кожен потік в графічному процесорі має унікальний ідентифікатор, який можна визначити як indx=bd*bx+tx, де bdпредставляє розмірність блоку, bxпозначає індекс блоку таtx є індексом потоку в кожному блоці.

Вертикальні стрілки демонструють випадок, що паралельні потоки отримують доступ до перших компонентів кожного вектора, тобто адрес 0, m , 2m ... пам'яті. Як показано на рис. (А), у цьому випадку доступ до пам'яті не є послідовним. Обнуляючи розрив між цими адресами (червоні стрілки, показані на малюнку вище), доступ до пам'яті стає об'єднаним.

Однак проблема стає дещо складною, оскільки дозволений розмір потоків, що перебувають на один блок графічного процесора, обмежений bd . Тому згруповане розташування даних може бути здійснено шляхом збереження перших елементів перших bdвекторів у послідовному порядку, за якими слідують перші елементи других bd векторів тощо. Решта елементів векторів зберігаються подібним чином, як показано на рис. (B). Якщо n (кількість векторів) не є фактором bd, необхідно залишити дані, що залишилися в останньому блоці, деяким тривіальним значенням, наприклад 0.

У лінійному сховищі даних на рис. (А) складова i (0 ≤ i < m ) вектора indx (0 ≤ indx < n ) адресується m × indx +i; той самий компонент у схемі злитого зберігання на рис. (b) розглядається як

(m × bd) ixC + bd × ixB + ixA,

де ixC = floor[(m.indx + j )/(m.bd)]= bx , ixB = jі ixA = mod(indx,bd) = tx.

Підсумовуючи, на прикладі зберігання ряду векторів із розміром m лінійне індексування відображається на об'єднане індексування відповідно до:

m.indx +i −→ m.bd.bx +i .bd +tx

Ця перестановка даних може призвести до значно вищої пропускної здатності глобальної пам'яті GPU.


джерело: "Прискорення обчислень на основі GPU при нелінійному аналізі деформації кінцевих елементів". Міжнародний журнал чисельних методів у біомедичній інженерії (2013).


9

Якщо потоки в блоці отримують доступ до послідовних розташувань глобальної пам'яті, тоді всі звернення об'єднуються в один запит (або об'єднуються) апаратним забезпеченням. У прикладі матриці елементи матриці в рядку розташовані лінійно, за ними слід наступний рядок тощо. Наприклад, матриця 2x2 та 2 потоки в блоці, місця в пам'яті розташовані так:

(0,0) (0,1) (1,0) (1,1)

У доступі до рядка доступ до потоку1 (0,0) та (1,0) неможливий. У доступі до стовпця доступ до потоку1 (0,0) та (0,1) може бути об'єднаний, оскільки вони сусідні.


7
приємно і коротко, але .. пам’ятайте, що злиття - це не два послідовних доступу через thread1, а одночасний паралельний доступ через thread1 і thread2. У вашому прикладі доступу до рядка, якщо thread1 звертається до (0,0) та (1,0), тоді я припускаю, що thread2 звертається до (0,1) та (1,1). Таким чином, перший паралельний доступ - це 1: (0,0) та 2: (0,1) -> злитий!
jmilloy

3

Критерії злиття добре описані в Посібнику з програмування CUDA 3.2 , розділ G.3.2. Коротка версія така: потоки в деформації повинні отримувати доступ до пам'яті послідовно, а слова, до яких здійснюється доступ, повинні складати> = 32 біта. Крім того, базова адреса, до якої отримує доступ деформація, повинна бути вирівняна на 64-, 128- або 256-байт для 32-, 64- і 128-бітового доступу відповідно.

Обладнання Tesla2 і Fermi нормально поєднує 8- і 16-розрядний доступ, але їх краще уникати, якщо ви хочете пікову пропускну здатність.

Зауважте, що, незважаючи на вдосконалення апаратного забезпечення Tesla2 та Fermi, злиття НІ ЯКИМ чином НЕ ЗАСТОРОЖАЄ. Навіть на апаратному забезпеченні класу Tesla2 або Fermi, відсутність поєднання транзакцій глобальної пам'яті може призвести до збільшення продуктивності в 2 рази. (На апаратному забезпеченні класу Fermi це, здається, відповідає дійсності, лише якщо ввімкнено ECC. Суміжні, але незв'язані транзакції пам'яті займають близько 20% удару Fermi.)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.