Programowanie procesorów graficznych GPGPU Krzysztof Banaś Obliczenia równoległe 1
GPGPU Modele programowania GPGPU CUDA pierwszy naprawdę popularny model programowania GPGPU OpenCL wzorowany na CUDA, dla szerszej grupy urządzeń (GPU, CPU, procesory heterogeniczne, akceleratory) modele dyrektyw kompilatora OpenACC, OpenMP Obydwa modele wykorzystują język C i biblioteki kod dla GPU w języku będącym rozszerzeniem standardu C99 założenie istnienia kompilatora, przetwarzającego kod dla GPU konieczne wsparcie ze strony systemu operacyjnego, który pośredniczy w wykonaniu programu (m.in. poprzez sterownik karty graficznej) Wykonanie programu realizowane jest w modelu SPMD/SIMD(SIMT) ten sam program wykonywany jest przez wiele wątków synchronizowanych sprzętowo Krzysztof Banaś Obliczenia równoległe 2
CUDA OpenCL Przebieg obliczeń program hosta Alokacja i inicjowanie pamięci na urządzeniu Przesłanie kernela do urządzenia w OpenCL można dokonywać w locie kompilacji kodu z dostarczanych źródeł Zlecenie kopiowania danych wejściowych do urządzenia Zlecenie wykonania obliczeń Zlecenie kopiowania danych wyjściowych z pamięci urządzenia Dodatkowo OpenCL umożliwia realizacje fazy wstępnej, w której pobiera się dane o środowisku wykonania i dostosowuje wykonywany program do konkretnego środowiska (urządzenia) Krzysztof Banaś Obliczenia równoległe 3
OpenCL przykład programu hosta... // wybór platformy, urządzenia, tworzenie kontekstu i kolejek poleceń,... // wczytanie, kompilacja i tworzenie kernela tworzenie zmiennych (buforów) w pamięci GPU cl_mem memobject = clcreatebuffer(context, CL_MEM_READ_WRITE, sizeof(float), NULL, NULL); przesłanie zmiennych z pamięci CPU do pamięci GPU float a[1]; clenqueuewritebuffer(commandqueue, memobject, CL_FALSE, 0, sizeof(float), a, 0, NULL, NULL); wskazanie zmiennych jako kolejnych argumentów kernela clsetkernelarg(kernel, 0, sizeof(cl_mem), &memobject); uruchomienie kernela dla określonej liczby wątków w określonej konfiguracji size_t globalworksize[1] = { 1 }; size_t localworksize[1] = { 1 }; clenqueuendrangekernel(commandqueue, kernel, 1, NULL, globalworksize, localworksize, 0, NULL, NULL); Krzysztof Banaś Obliczenia równoległe 4
OpenCL projektowanie kerneli Kod kernela można rozumieć jako funkcję wykonywaną przez wątki na urządzeniu OpenCL, w ramach potrójnej zrównoleglonej pętli, zgodnie z wzorcem: for(i=0; i<rozmiar_z; i++){ for(j=0; j<rozmiar_y; j++){ for(k=0; k<rozmiar_x; k++){ funkcja_wątków(i, j, k,... // pozostałe argumenty ); } } } Każdy wątek otrzymuje swój indeks będący trójką (i, j, k) w ramach przestrzeni wątków o wymiarach (rozmiar_z, rozmiar_y, rozmiar_x) Prostotę obrazu zaburza dwupoziomowa (a nawet trzypoziomowa) organizacja pracy wątków trzy podstawowe pętle są w rozbijane na pętle bo blokach i pętle lokalne Rozmiary Z i Y mogą zostać zredukowane do 1 Krzysztof Banaś Obliczenia równoległe 5
OpenCL organizacja wątków Krzysztof Banaś Obliczenia równoległe 6
Przestrzeń wątków Krzysztof Banaś Obliczenia równoległe 7
OpenCL projektowanie kerneli Dwa poziomy organizacji pracy wątków wątki (work items, threads) realizują obliczenia grupy wątków (workgroups, threadblocks) umożliwiają synchronizacją pracy wątków poziom grup wątków można pominąć (np. dla programów zawstydzająco równoległych (embarrassingly parallel) ) Środowiska programowania GPU pozwalają na wykorzystanie liczb wątków rzędu milionów Krzysztof Banaś Obliczenia równoległe 8
OpenCL projektowanie kerneli Model SPMD jest wprowadzany klasycznie poprzez identyfikatory wątków wątek na podstawie swojego identyfikatora może: zlokalizować dane, na których dokonuje przetwarzania dane[ f(my_id) ] wybrać ścieżkę wykonania programu if(my_id ==...){...} określić iteracje pętli, które ma wykonać for( i=f1(my_id); i< f2(my_id); i += f3(my_id) ){... } w tym aspekcie programowanie GPU jest zbliżone do modeli Pthreads i MPI Identyfikator wątku może być pojedynczym indeksem, lub parą ewentualnie trójką indeksów Krzysztof Banaś Obliczenia równoległe 9
OpenCL projektowanie kerneli Odwzorowanie na architekturę sprzętu: wątek (work item) rdzeń (processing element) jeden wątek wykonywany jest przez jeden rdzeń grupa wątków (work group) jednostka obliczeniowa (compute unit) grupa wątków wykonuje obliczenia na jednej jednostce obliczeniowej grupa dzielona jest na podgrupy, wavefronts/warps, wątków wykonywanych jednocześnie w modelu SIMD przestrzeń wątków urządzenie: wszystkie watki z przestrzeni realizują zlecony kernel na urządzeniu Krzysztof Banaś Obliczenia równoległe 10
Krzysztof Banaś Obliczenia równoległe 11
OpenCL projektowanie kerneli wielopoziomowa hierarchia pamięci: rejestry najszybsze zmienne lokalne (jeśli nie jest ich za dużo, register spilling) prywatne dla każdego wątku pamięć wspólna szybka zmienne (tablice) z atrybutem local wspólne dla wątków z jednej grupy roboczej pamięć globalna powolna zmienne lokalne nie mieszczące się w rejestrach zmienne (tablice) z atrybutem global wspólne dla wszystkich wątków dodatkowe rodzaje pamięci: constant, texture Krzysztof Banaś Obliczenia równoległe 12
GPGPU Tworzenie programów GPGPU wykrycie współbieżności uwaga: masowa wielowątkowość opłacalność wykonywania na GPU tylko w przypadku wysokiego stopnia współbieżności (setki, tysiące wątków) odwzorowanie na architekturę uwaga: konieczność synchronizacji przy dostępie do pamięci GPU dobrze nadają się do obliczeń w stylu równoległości danych (te same obliczenia dla wielu egzemplarzy danych) duże możliwości konfiguracji obliczeń, a co za tym, idzie optymalizacji optymalizacja jawna (w ramach pojęć modelu programowania) optymalizacja niejawna (taka organizacja obliczeń, aby optymalnie wykorzystać elementy sprzętowe niewidoczne z poziomu modelu (języka, środowiska) programowania Krzysztof Banaś Obliczenia równoległe 13
SIMT Krzysztof Banaś Obliczenia równoległe 14
Przykład Przykład dodawania wektorów (OpenCL) dekompozycja zadania analiza wydajności wydajność przetwarzania wydajność dostępu do pamięci czynniki wpływające na wydajność:» liczba wątków» liczba współbieżnych żądań dostępu do pamięci dla pojedynczego wątku» rozmiar danych w pojedynczym żądaniu Krzysztof Banaś Obliczenia równoległe 15
Projektowanie kerneli przykład Model równoległości danych (data parallel programming) model zrównoleglenia: dekompozycja danych (data decomposition) każdy wątek realizuje ten sam kod (SPMD) synchronizacja sprzętowa (SIMD, SIMT) OpenCL pozwala uzyskać informacje o liczbie wymiarów przestrzeni wątków, liczbie wątków (globalnej oraz w pojedynczej grupie), liczbie grup oraz o identyfikatorze wątku: lokalnym (w grupie), globalnym, a także o identyfikatorze grupy: get_work_dim() get_global_id(dim_id), get_local_id(dim_id) get_global_size(dim_id), get_local_size(dim_id) get_num_groups(dim_id), get_group_id(dim_id) Krzysztof Banaś Obliczenia równoległe 16
Projektowanie kerneli przykład Przykład programu dodawania wektorów dekompozycja danych wariant 1 jeden wątek na jeden element wektora wykorzystanie możliwości tworzenia milionów wątków zawstydzająco równoległy algorytm brak komunikacji, synchronizacji kernel void vecadd_1_kernel( global const float *a, global const float *b, global float *result) { int gid = get_global_id(0); result[gid] = a[gid] + b[gid]; } Krzysztof Banaś Obliczenia równoległe 17
Projektowanie kerneli przykład Prosty przykład programu dodawania wektorów dekompozycja danych wariant 2 wykorzystanie wariantów podziału danych blokowanie jeden wątek operuje na bloku danych kernel void vecadd_2_blocks_kernel(..., const int size, const int size_per_thread) { int gid = get_global_id(0); int index_start = gid * size_per_thread; int index_end = (gid+1) * size_per_thread; for (int i=index_start; i < index_end && i < size; i++) { result[i] = a[i]+b[i]; } } Krzysztof Banaś Obliczenia równoległe 18
Projektowanie kerneli przykład Prosty przykład programu dodawania wektorów dekompozycja danych wariant 3 wykorzystanie wariantów podziału danych podział cykliczny kolejne wątki z grupy operują na kolejnych elementach wektorów strategia niewłaściwa dla CPU, najlepsza dla GPU kernel void vecadd_3_cyclic_kernel(..., const int size) { int index_start = get_global_id(0); int index_end = size; int stride = get_local_size(0) * get_num_groups(0); for (int i=index_start; i < index_end; i+=stride) { result[i] = a[i]+b[i]; } } Krzysztof Banaś Obliczenia równoległe 19
Projektowanie kerneli przykład Prosty przykład programu dodawania wektorów dekompozycja danych wariant 3 wykorzystanie wariantów podziału danych podział cykliczny wykorzystanie typów wektorowych żądanie dostępu do pamięci dla pojedynczego wątku dotyczy 128 bitów, zamiast 32 kernel void vecadd_4_cyclic_vect_kernel( global const float4 *a, global const float4 *b, global float4 *result, const int size) { int index_start = 4 * get_global_id(0); int index_end = size/4; int stride = 4 * get_local_size(0) * get_num_groups(0); for (int i=index_start; i < index_end; i+=stride) { result[i] = a[i]+b[i]; } } Krzysztof Banaś Obliczenia równoległe 20
Analiza wydajności Analiza wydajności może być przeprowadzana na dwa standardowe sposoby: wydajność względna klasycznie oznacza to przyspieszenie i efektywność jako funkcje liczby procesorów/rdzeni dla pojedynczego GPU trudno przeprowadzać takie analizy analizy porównujące wydajność CPU i GPU stwarzają wiele problemów metodologicznych i powinny być używane tylko jako pewne wskazówki wydajność bezwzględna procent teoretycznej maksymalnej wydajności uzyskany dla danego wykonania programu wydajność może być ograniczana przez wydajność przetwarzania lub wydajność transferu danych z/do pamięci (dla obliczeń na GPU powinno się uwzględnić szybkości transferu dla wszystkich poziomów pamięci) Krzysztof Banaś Obliczenia równoległe 21
Analiza wydajności W przypadku prostego kernela dodawania wektorów wydajność jest w całości ograniczana przez szybkość dostępu do pamięci W celu maksymalizacji szybkości zapisu/odczytu należy wygenerować maksymalna liczbę żądań dostępu do pamięci: należy zwiększać liczbę aktywnych wątków ale istnieją ograniczenia na liczbę aktywnych wątków i grup wątków można zwiększać liczbę niezależnych dostępów do danych przez pojedynczy wątek np. poprzez wykorzystanie pobierania float4 (128 bitów) zamiast zamiast zwykłego float (32 bity) lub przez modyfikację kodu źródłowego, np. rozwijanie pętli (należy upewnić się, że dostępy są traktowane jako niezależne) Krzysztof Banaś Obliczenia równoległe 22
Przykład paramerów GPU Przykład karty graficznej (laptop Dell Vostro 3450): AMD Radeon HD 6630M 6 CU 480 PE 16384 rejestrów wektorowych (128 bitowych) / CU 32 kb pamięci wspólnej (32 banki) / CU 8 kb L1 / CU, 256 kb L2 / GPU 480 Gflops 25,6 GB/s DDR3, 51,2 GDDR5 ok. 250 GB/s pamięć wspólna maksymalny rozmiar grupy 256 (cztery wavefronts ) maksymalna liczba wavefronts 248 / GPU Krzysztof Banaś Obliczenia równoległe 23
Przykład paramerów GPU NVIDIA Tesla M2075 (architektura Fermi) 14 CU 448 PE ( 32PE / CU ) 32768 rejestrów 32 bitowych / CU 48 kb pamięci wspólnej / CU 73.6 GB/s / CU 16 kb L1 / CU, 224 kb L2 / GPU 515 DP GFLOPS 150 GB/s GDDR5 maksymalny rozmiar grupy 1024 (32 warps ) maksymalna liczba aktywnych grup / CU 8 maksymalna liczba aktywnych warps / CU 48 maksymalna liczba rejestrów na wątek 63 maksymalna liczba aktywnych wątków 1536 Krzysztof Banaś Obliczenia równoległe 24
Przykład paramerów GPU NVIDIA Tesla K20 (architektura Kepler) 13 CU 2496 PE ( 192PE / CU ) 65536 rejestrów 32 bitowych / CU 48 kb pamięci wspólnej / CU 16 kb L1 / CU, 208 kb L2 / GPU 1170 DP GFLOPS 5GB GDDR5 RAM przepustowość 208 GB/s maksymalny rozmiar grupy 1024 (32 warps ) maksymalna liczba aktywnych grup / CU 16 maksymalna liczba aktywnych warps / CU 64 maksymalna liczba rejestrów na wątek 255 maksymalna liczba aktywnych wątków 2048 Krzysztof Banaś Obliczenia równoległe 25
Przykład paramerów GPU AMD (ATI) Radeon HD 5870 20 CU 320x5 PE ( 16x5PE / CU ) każdy rdzeń posiada 5 ALU 544 DP GFLOPS 16384 rejestrów 4x32 bitowych / CU 13056 GB/s 32 kb pamięci wspólnej / CU 100 GB/s / CU, 2176 GB/s 8 kb L1 / CU przepustowość 1088 GB/s 512 kb L2 / GPU przepustowość L1 L2: 435 GB/s 1 GB GDDR5 RAM przepustowość 154 GB/s maksymalny rozmiar grupy 1024 (32 warps ) maksymalna liczba aktywnych grup / CU 8 maksymalna liczba aktywnych wavefronts / CU 24.8 maksymalna liczba rejestrów na wątek 63 maksymalna liczba aktywnych wątków 1536 Krzysztof Banaś Obliczenia równoległe 26
Wyniki AMD Radeon HD 5870 PARAMETERS: nr_cu 20, nr_cores 6400, nr_cores_per_cu 320 workgroup size 256, nr_workgroups 240,nr_workgroups_per_CU 12 nr_threads 61440, nr_threads_per_cu 3072, nr_threads_per_core 3840 array size 31457280, nr_entries_per_thread 512, nr_entries_per_core 1966080 4. executing kernel 1, on platform 0 and device 0 EXECUTION TIME: executing kernel: 0.079807 (profiler: 0.079346) Number of operations 31457280, performance 0.396455 GFlops GBytes transferred to processor 0.377487, speed 4.757464 GB/s 4. executing kernel 2, on platform 0 and device 0 EXECUTION TIME: executing kernel: 0.003577 (profiler: 0.003200) Number of operations 31457280, performance 9.829715 GFlops GBytes transferred to processor 0.377487, speed 117.956580 GB/s 4. executing kernel 3, on platform 0 and device 0 (GPU) EXECUTION TIME: executing kernel: 0.003008 (profiler: 0.002649) Number of operations 31457280, performance 11.873163 GFlops GBytes transferred to processor 0.377487, speed 142.477954 GB/s Krzysztof Banaś Obliczenia równoległe 27
GPGPU Krzysztof Banaś Obliczenia równoległe 28