Які проблеми піддаються обчислювачам GPU?


84

Тож у мене є гідна голова щодо того, які проблеми, з якими я працюю, найкращі в серіалі, і з якими можна керуватися паралельно. Але зараз я не маю великого уявлення про те, що найкраще справляється з обчисленнями на базі процесора, і що слід завантажувати в GPU.

Я знаю, що це основне питання, але багато моїх пошуків потрапляють у людей, які явно виступають за те чи інше, не дійсно обґрунтовуючи, чому , чи дещо розпливчасті правила. Шукайте більш корисну відповідь тут.

Відповіді:


63

Апаратне забезпечення графічного процесора має дві особливі потужності: необроблені обчислення (FLOP) та пропускну здатність пам'яті. Найбільш складні обчислювальні задачі належать до однієї з цих двох категорій. Наприклад, щільна лінійна алгебра (A * B = C або Solve [Ax = y] або Діагоналізація [A] тощо) потрапляє десь на спектр пропускної здатності обчислення / пам'яті залежно від розміру системи. Швидкі перетворення Фур'є (FFT) також підходять до цієї форми з високими сукупними потребами в пропускній здатності. Як і інші трансформації, алгоритми на основі сітки / сітки, Монте-Карло тощо. Якщо ви подивитеся на приклади коду NVIDIA SDK , ви можете відчути всі типи проблем, які найчастіше вирішуються.

Думаю, більш повчальною є відповідь на питання "У яких проблемах справді погані GPU?" Більшість проблем, які не належать до цієї категорії, можна зробити для роботи на графічному процесорі, хоча деякі вимагають більше зусиль, ніж інші.

Проблеми, які не відображаються добре, як правило, занадто малі або занадто непередбачувані. Дуже невеликі проблеми не мають паралелізму, необхідного для використання всіх потоків на графічному процесорі та / або можуть вміститися в кеш-пам'ять низького рівня в процесорі, істотно підвищивши продуктивність процесора. Непередбачувані проблеми мають занадто багато значущих гілок, які можуть запобігти ефективному потоку даних з пам'яті GPU до ядер або зменшити паралелізм, порушивши парадигму SIMD (див. " Розбіжні основи "). Приклади таких проблем включають:

  • Більшість алгоритмів графіків (занадто непередбачувано, особливо в просторі пам'яті)
  • Рідка лінійна алгебра (але це погано і на процесорі)
  • Невеликі проблеми з обробкою сигналу (наприклад, FFT менше 1000 балів)
  • Пошук
  • Сортувати

3
Тим НЕ менше, GPU рішення для цих «непередбачуваних» проблеми є можливим і, в той час як в нині не видається можливим , як правило, може отримати значення в майбутньому.
близько

6
Я хотів би спеціально додати гілки до списку вимикачів продуктивності GPU. Ви хочете, щоб усі ваші (сотні) виконували ту саму інструкцію (як у SIMD), щоб виконати справді паралельні обчислення. Наприклад, на картках AMD, якщо будь-яка з потоків інструкцій стикається з гілкою і повинна розходитися - всі хвилі (паралельна група) розходяться. Якщо інші одиниці з фронту хвилі не повинні розходитися - вони повинні виконати другий прохід. Має на увазі, що означає манджут передбачуваність.
Фіолетова жирафа

2
@VioletGiraffe, це не обов'язково так. У CUDA (тобто на графічних процесорах Nvidia) розбіжність гілок впливає лише на поточну основу, яка становить щонайбільше 32 потоки. Різні основи, хоча і виконують один і той же код, не є синхронними, якщо явно не синхронізовано (наприклад, з __synchtreads()).
Педро

1
@Pedro: Це правда, але розгалуження взагалі шкодить продуктивності. Для високопродуктивних кодів (який код GPU немає?), Це майже важливо враховувати.
jvriesem

21

Проблеми, що мають високу арифметичну інтенсивність і регулярний патерн доступу до пам'яті, як правило, легко (іer) реалізувати на графічних процесорах і на них добре працювати.

Основна складність у наявності високопродуктивного GPU-коду полягає в тому, що у вас є тонна ядер, і ви хочете, щоб вони максимально використовувались на повну потужність. Проблеми, які мають нерегулярні схеми доступу до пам’яті або не мають високої арифметичної інтенсивності, ускладнюють це: або ви витрачаєте тривалий час на спілкування результатів, або витрачаєте тривалий час на отримання інформації з пам'яті (що повільно!), І не вистачає часу на стискання чисел. Звичайно, потенціал для одночасності у вашому коді має вирішальне значення для його здатності добре реалізовуватися і на GPU.


Чи можете ви вказати, що ви маєте на увазі під звичайними моделями доступу до пам'яті?
Фоміт

1
Відповідь maxhutch краща за мою. Що я маю на увазі під регулярною схемою доступу, це те, що доступ до пам'яті є тимчасовим та просторово локальним способом. Тобто: ти не робиш величезних стрибків навколо пам’яті повторно. Це теж щось із пакетної угоди, яку я помітив. Це також означає, що шаблони доступу до даних можуть бути визначені або компілятором, або програмістом, щоб розгалуження (умовні висловлювання в коді) було мінімізоване.
Reid.Atcheson

15

Це не призначено як відповідь самостійно, а скоріше як доповнення до інших відповідей maxhutch та Reid.Atcheson .

Щоб отримати найкраще з графічних процесорів, для вашої проблеми потрібно не лише бути високо (або масово) паралельним, але й основний алгоритм, який буде виконуватися на GPU, повинен бути якомога меншим. У термінах OpenCL це в основному називається ядром .

Якщо точніше, ядро ​​повинно вписуватися в реєстр кожного багатопроцесорного блоку (або обчислювальної одиниці ) GPU. Точний розмір регістра залежить від GPU.

Враховуючи, що ядро ​​є достатньо малим, необроблені дані проблеми повинні вміщуватися в локальну пам'ять GPU (читання: локальна пам'ять (OpenCL) або спільна пам'ять (CUDA) обчислювальної одиниці). В іншому випадку навіть висока пропускна здатність пам’яті GPU не є достатньо швидкою, щоб весь час зайняти елементи обробки .
Зазвичай ця пам'ять становить близько 16 до 32 KiByte великих .


Чи не розділена локальна / спільна пам'ять кожного процесорного блоку серед усіх десятків (?) Потоків, що працюють в межах одного кластера ядер? У цьому випадку, чи вам насправді не потрібно зберігати робочий набір даних значно меншим, щоб отримати повну продуктивність від GPU?
Dan Neely

Локальна / спільна пам'ять процесорного блоку доступна лише самим обчислювальним блоком і, таким чином, розділяється лише елементами обробки цього обчислювального блоку. Світова пам'ять відеокарти (зазвичай 1 Гб) доступна всіма процесорними підрозділами. Пропускна здатність між обробними елементами та локальною / спільною пам'яттю дуже швидка (> 1 ТБ / с), але пропускна здатність до глобальної пам'яті набагато повільніше (~ 100 Гб / с) і її потрібно ділити між усіма обчислювальними одиницями.
Torbjörn

Я не питав про основну пам'ять GPU. Я думав, що пам'ять на матрицю виділяється лише на кластері рівня ядра, а не на кожному ядрі. ex для nVidia GF100 / 110 gpu; для кожного з 16 кластерів SM не 512 ядер куда. З кожним SM, розробленим для запуску до 32 потоків паралельно для максимального збільшення продуктивності GPU, знадобиться збереження робочого набору в діапазоні 1kb / thread.
Дан Нілі

@Torbjoern Те, що ви хочете, - це зайняти всі конвеєри виконання GPU зайнятими, GPU досягають цього двома способами: (1) найпоширеніший спосіб - збільшити зайнятість або, якщо говорити по-іншому, збільшуючи кількість одночасних потоків (малі ядра використовують менше спільні ресурси, щоб у вас було більше активних потоків); можливо, краще (2) збільшити паралелізм рівня інструкцій у вашому ядрі, так що ви можете мати більше ядро ​​з відносно низькою зайнятістю (невелика кількість активних потоків). Дивіться bit.ly/Q3KdI0
fcruz

11

Можливо, більш технічне доповнення до попередніх відповідей: графічні процесори CUDA (тобто Nvidia) можна описати як набір процесорів, які працюють автономно по 32 потоки кожен. Потоки в кожному процесорі працюють у режимі блокування (подумайте SIMD з векторами довжини 32).

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

Якщо ваш код не паралельно / автоматично паралельно розташований до сотень / тисяч потоків, ви, можливо, зможете розбити його на окремі асинхронні завдання, які добре паралелізуються, і виконати ті, що мають лише 32 потоки, що працюють у режимі блокування. CUDA надає набір атомних інструкцій, які дозволяють реалізувати мутекси, що, в свою чергу, дозволяє процесорам синхронізуватись між собою та обробляти список завдань у парадигмі пулу потоків . Ваш код тоді буде працювати так само, як і в багатоядерній системі, тільки майте на увазі, що кожен ядро ​​має 32 власні нитки.

Ось невеликий приклад використання CUDA, як це працює

/* Global index of the next available task, assume this has been set to
   zero before spawning the kernel. */
__device__ int next_task;

/* We will use this value as our mutex variable. Assume it has been set to
   zero before spawning the kernel. */
__device__ int tasks_mutex;

/* Mutex routines using atomic compare-and-set. */
__device__ inline void cuda_mutex_lock ( int *m ) {
    while ( atomicCAS( m , 0 , 1 ) != 0 );
    }
__device__ inline void cuda_mutex_unlock ( int *m ) {
    atomicExch( m , 0 );
    }

__device__ void task_do ( struct task *t ) {

    /* Do whatever needs to be done for the task t using the 32 threads of
       a single warp. */
    }

__global__ void main ( struct task *tasks , int nr_tasks ) {

    __shared__ task_id;

    /* Main task loop... */
    while ( next_task < nr_tasks ) {

        /* The first thread in this block is responsible for picking-up a task. */
        if ( threadIdx.x == 0 ) {

            /* Get a hold of the task mutex. */
            cuda_mutex_lock( &tasks_mutex );

            /* Store the next task in the shared task_id variable so that all
               threads in this warp can see it. */
            task_id = next_task;

            /* Increase the task counter. */
            next_tast += 1;

            /* Make sure those last two writes to local and global memory can
               be seen by everybody. */
            __threadfence();

            /* Unlock the task mutex. */
            cuda_mutex_unlock( &tasks_mutex );

            }

        /* As of here, all threads in this warp are back in sync, so if we
           got a valid task, perform it. */
        if ( task_id < nr_tasks )
            task_do( &tasks[ task_id ] );

        } /* main loop. */

    }

Тоді вам доведеться зателефонувати до ядра, main<<<N,32>>>(tasks,nr_tasks)щоб переконатися, що кожен блок містить лише 32 потоки і таким чином вписується в одну основу. У цьому прикладі я також для простоти припустив, що завдання не мають ніяких залежностей (наприклад, одне завдання залежить від результатів іншого) або конфліктів (наприклад, робота в одній глобальній пам'яті). Якщо це так, то вибір завдань стає дещо складнішим, але структура по суті однакова.

Це, звичайно, складніше, ніж просто робити все на одній великій партії комірок, але значно розширює тип проблем, для яких можна використовувати GPU.


2
Це технічно вірно, але потрібен високий паралелізм, щоб отримати високу пропускну здатність пам'яті, і існує обмеження кількості викликів асинхронних ядер (зараз 16). Тебе також є безліч бездокументованої поведінки, пов’язаної з плануванням в поточному випуску. Я б радив не покладатися на асинхронні ядра, щоб покращити продуктивність на даний момент ...
Макс Хатчінсон,

2
Те, що я описую, можна зробити все за один виклик ядра. Ви можете зробити з N блоків по 32 нитки кожен, так що кожен блок вписується в одну основу. Кожен блок потім отримує завдання із глобального списку завдань (доступ керований за допомогою атоміки / мютексів) і обчислює його за допомогою 32-х потокових ступінь блокування. Все це відбувається в одному дзвінку ядра. Якщо вам потрібен приклад коду, дайте мені знати, і я опублікую його.
Педро

4

Один з моментів, що не зроблений дотепер, полягає в тому, що поточне покоління GPU не справляється з обчисленнями з плаваючою точкою подвійної точності, як при обчисленнях з одноточною точністю. Якщо ваші обчислення повинні бути виконані в подвійній точності, тоді ви можете очікувати, що час виконання збільшиться в 10 разів або більше за одну точність.


Я хочу погодитися. Більшість (або всі) новіші GPU мають вбудовану підтримку подвійної точності. Майже кожен такий графічний процесор повідомляє про обчислення подвійної точності, що працюють приблизно на половині швидкості одноточної точності, ймовірно, завдяки простому подвоєнню необхідного доступу до пам'яті / пропускної здатності.
Годрік Провид

1
Хоча це правда, що новітні та найбільші карти Nvidia Tesla дійсно пропонують пікову продуктивність подвійної точності, що становить половину максимальної продуктивності одномісної точності, співвідношення становить 8 до 1 для більш поширених карток споживчих класів архітектури Fermi.
Брайан Борчерс

@GodricSeer Співвідношення плаваючої точки SP і DP 2: 1 має дуже мало спільного з пропускною здатністю і майже все, що стосується того, скільки апаратних одиниць існує для виконання цих операцій. Звичайним є повторне використання файлу регістра для SP та DP, отже, блок з плаваючою комою може виконувати 2 рази SP ops як DP ops. У цієї конструкції є численні винятки, наприклад, IBM Blue Gene / Q (не має логіки SP і, таким чином, SP працює при ~ 1,05x DP). Деякі графічні процесори мають співвідношення, відмінне від 2, наприклад 3 і 5.
Джефф

Минуло цю відповідь через чотири роки, і теперішня ситуація з процесорами NVIDIA полягає в тому, що для ліній GeForce і Quadro співвідношення DP / SP зараз становить 1/32. Графічні процесори NVIDIA Tesla мають значно більшу продуктивність з подвійною точністю, але також коштують набагато дорожче. З іншого боку, AMD не збільшила продуктивність подвійної точності на своїх графічних процесорах Radeon таким же чином.
Брайан Борчерс

4

З метафоричної точки зору, гпу можна розглядати як людину, що лежить на гвоздиці. Людина, що лежить зверху, - це дані, і в основі кожного цвяха є процесор, тому цвях - це фактично стрілка, що вказує від процесора до пам'яті. Всі нігті мають звичайний візерунок, як сітка. Якщо тіло добре розкинуте, воно почуває себе добре (працездатність хороша), якщо тіло торкається лише деяких плям нігтьового ложа, то біль погана (погана працездатність).

Це можна сприймати як додаткову відповідь на відмінні відповіді вище.


4

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


2

Графічні процесори мають затримку введення / виводу, тому потрібно багато потоків використовувати для насичення пам'яті. Для збереження основи потрібно багато ниток. Якщо шлях коду становить 10 годин, а затримка вводу / виводу - 320 годин, 32 нитки повинні наблизитися до насичення основи. Якщо шлях коду становить 5 годин, подвійні нитки подвійні.

Маючи тисячу ядер, шукайте тисячі потоків, щоб повністю використовувати GPU.

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

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

Моделювання наближеності великих наборів повинно добре оптимізуватися.

Випадкові введення / виведення та одиночна нарізка - це вбивча радість ...


Це справді захоплююче питання; Я сперечаюся з собою про те, чи можна (або варто докласти зусиль) для "паралельного використання" досить простого завдання (виявлення ребра в повітряних зображеннях), коли кожне завдання займає ~ 0,06 сек, але для виконання потрібно ~ 1,8 мільйона завдань ( на рік, для даних про 6 років: завдання, безумовно, відокремлюються) ... таким чином, для розрахунку часу на одному ядрі варто ~ 7,5 днів. Якщо кожен виклик був швидшим на GPU, а завдання можна було паралелізувати 1 на nGPUcores [n small], чи справді ймовірно, що час роботи може впасти до ~ 1 години? Здається, малоймовірним.
GT.

0

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

Просто вирішіть ВСІ можливі маршрути продавця, а потім відсортуйте час / відстань / деяку метрику. Впевнені, що ви викидаєте майже 100% своєї роботи, але груба сила - це прийнятне рішення.


Я мав доступ до невеликої ферми з 4 таких серверів протягом тижня, і за п’ять днів я зробив більше розподілених блоків.net, ніж за попередні 10 років.
Criggie

-1

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

Багато формул може бути простим для написання, але болісно обчислити, наприклад, в математиці матриці ви не отримаєте однозначної відповіді, але багато значень.

Це важливо при обчисленні, як швидко комп'ютер обчислює значення та працює формули, оскільки деякі формули не можуть працювати без усіх обчислених значень (отже, уповільнення). Комп'ютер не дуже добре знає, який порядок запуску формул або обчислення значень для використання в цих програмах. Він, головним чином, нагнітає сили на швидких швидкостях і розбиває формули на патрони, щоб обчислити, але багато програм сьогодні потребують цих обчислених патронів прямо зараз і чекають у питанні (і питання питання і більше запитань).

Наприклад, в імітаційній грі, яку слід розраховувати спочатку при зіткненнях пошкодження зіткнення, положення об'єктів, нову швидкість? Скільки часу це повинно зайняти? Як будь-який процесор може впоратися з цим навантаженням? Крім того, більшість програм є дуже абстрактними, вимагають більше часу для обробки даних, і не завжди розроблені для багатопотокової передачі даних або не є хорошими способами в абстрактних програмах зробити це ефективно.

У міру того, як процесор став кращим і кращими, люди стали неохайними в програмуванні, і ми також повинні програмувати для багатьох різних типів комп'ютерів. ГПУ розроблений для того, щоб одночасно виконувати багато простих обчислень (не кажучи вже про пам'ять (вторинна / таран), а нагрівання охолодження - основні шийки пляшок в обчисленні). Процесор керує багатьма багатьма запитаннями одночасно або втягується в багато напрямків, він з'ясовує, що робити, не маючи змоги це зробити. (Ей, це майже людина)

ГПУ - бурхливий працівник копітка робота. Процесор керує повним хаосом і не може впоратися з кожною деталлю.

То що ми вчимо? Gpu виконує детальну копітку роботу відразу, а процесор - це багатозадачна машина, яка не може дуже добре зосередитись із занадто великою кількістю завдань. (Це наче має розлад уваги і аутизм одночасно).

У інженерії є ідеї, дизайн, реальність і багато бурхливої ​​роботи.

Коли я залишаю пам'ятати, щоб почати просто, починати швидко, невдало, невдало і ніколи не припиняти спроби.

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