Programowanie współbieżne Wprowadzenie do programowania GPU Rafał Skinderowicz
Literatura Sanders J., Kandrot E., CUDA w przykładach, Helion. Czech Z., Wprowadzenie do obliczeń równoległych, PWN Ben-Ari M., Podstawy programowania współbieżnego i rozproszonego, WNT. Kirk, David B., and W. Hwu Wen-mei. Programming massively parallel processors: a hands-on approach. Newnes, 2012. Scarpino, Matthew. OpenCL in Action: how to accelerate graphics and computation. Manning, 2012. Specyfikacja OpenCL. Pacheco P., Parallel Programming with MPI, Morgan Kaufmann, 1996.
Plan wykładu 1 Wprowadzenie 2 Historia rozwoju GPU Historia rozwoju GPU 3 Model programowania GPU Model programowania GPU 4 CUDA CUDA 5 Architektura GPU Architektura GPU 6 OpenCL OpenCL
Bariery technologiczne Rozwój procesorów napotyka na kilka istotnych problemów: Tranzystory nie mogą być nieskończenie małe Taktowanie tranzystorów może być zwiększane do pewnej granicy Tranzystor pracujący z większą częstotliwością generuje więcej ciepła Chłodzenie miliardów tranzystorów we współczesnych procesorach jest dużym problemem
GPU Procesor graficzny (ang. Graphics Procssing Unit, GPU) to specjalizowana jednostka obliczeniowa zoptymalizowana pod kątem przetwarzania grafiki
GPU historia Pierwsze dedykowane układy graficzne pojawiły się w latach 80 XX wieku (grafika 2D) 1992 Wydano OpenGL v1.0 (v4.5 w 2014) 1995 Microsoft wydał Direct3D (v12 w 2014) 1996 3df Voodoo Graphics akcelerator tylko do grafiki 3D 1997 Riva 128 (DirectX5) 1999 GeForce 256 Nvidia popularyzuje skrót GPU 2007 Nvidia wydała CUDA v1.0 (v6.0 w 2014) 2009 Apple wydało OpenCL v1.0 (v2.0 w 2013)
CPU vs GPU CPU: tradycyjne CPU są bardzo złożone (bogata lista rozkazów, rozbudowana jednostka kontrolna) elastyczność programowania i spora wydajność potrzebują stosukowo dużo mocy GPU: mniej złożona jednostka kontrolna więcej jednostek do przetwarzania równoległego większa efektywność mierzona liczbą operacji / wat bardziej restrykcyjny model programowania
CPU vs GPU GPU Duża liczba prostych jednostek przetwarzania, zegar taktowania nie musi dorównywać CPU Większy nacisk na dużą przepustowość (ang. throughput), niż na niskie opóźnienie (ang. latency) w wykonywaniu pojedynczych instrukcji
Przepustowość a opóźnienie Załóżmy, że chcemy przetransportować 40 osób na odległość 1000km Mamy do wyboru 2 pojazdy: samochód (4 osoby, 140 km/h) oraz autobus (40 osób, 70 km/h)
Przepustowość a opóźnienie Załóżmy, że chcemy przetransportować 40 osób na odległość 1000km Mamy do wyboru 2 pojazdy: samochód (4 osoby, 140 km/h) oraz autobus (40 osób, 70 km/h) Samochód: Opóźnienie (czas podróży): 7h 10min Przepustowość: 0.56 osoby/godz.
Przepustowość a opóźnienie Załóżmy, że chcemy przetransportować 40 osób na odległość 1000km Mamy do wyboru 2 pojazdy: samochód (4 osoby, 140 km/h) oraz autobus (40 osób, 70 km/h) Samochód: Opóźnienie (czas podróży): 7h 10min Przepustowość: 0.56 osoby/godz. Autobus: Opóźnienie: 1000km/70km/h 14h 17min Przepustowość: 2.8 osoby/godz.
CPU vs GPU Rysunek : Porównanie zapotrzebowania na energię przykładowego CPU i GPU
CPU vs GPU Theoretical peak (GFLOP/s) 5500 5000 4500 4000 3500 3000 2500 2000 1500 1000 500 0 GeForce 8800 GTX GeForce GTX 280 GeForce GTX 480 GeForce 7800 GTX GeForce 6800 Ultra BloomfieldSandy Bridge GeForce FX 5800 Tesla C1060 Willamette Prescott Westmere WoodcrestHarpertown 2002 2004 2006 2008 2010 2012 2014 Release date GeForce GTX 580 GeForce GTX TITAN GeForce GTX 680 Tesla C2075 GeForce GTX 780 TI K20X Ivy Bridge Haswell K40 NVIDIA GPU SP NVIDIA GPU DP Intel SP Intel DP Rysunek : Porównanie teoretycznej wydajności CPU vs Nvidia GPU (Autor: Michael Galloy)
Zastosowania GPU Ze względu na dużą wydajność w stosunku do zapotrzebowania na energię GPU są obecenie wykorzystywane w większości najszybszych superkomputerów (patrz top500.org) Tianhe-2 (nr 1 na w czerwcu 2014) złożony jest z 32000 procesorów Intel Xeon E5-2692 (12 rdzeni) oraz 48000 koprocesorów Xeon Phi moc obliczeniowa 31 PFLOPS Titan (nr 2 w czerwcu 2014) zawiera procesorów 18688 AMD Opteron 6274 (16 rdzeni) oraz 18688 kart Nvidia Tesla K20X moc obliczeniowa 17,59 PFLOPS Rysunek : Tianhe 2 (źródło: phys.org)
Model programowania GPU Heterogeniczny - obliczenia wykonywane na CPU oraz GPU (kernele) CPU pełni rolę nadzorcy lub gospodarza (ang. host) GPU inaczej określany jest jako urządzenie (ang. device) GPU ma odrębną pamięć, stąd konieczne są operacje transferu danych i wyników obliczeń RAM GPU Programowanie GPU wymaga zastosowania dodatkowej biblioteki / języka, np. CUDA, OpenCL
Początki programowania GPU W 2001 roku pojawił się układ GPU z serii GeForce 3 z programowalnymi shaderami wierzchołków i pikseli Wykorzystywano udostępnianą przez DirectX, OpenGL oraz Cg możliwość tworzenia własnych shaderów, czyli krótkich programów do przetwarzania wierzchołków i pikseli Ten model pozwalał uzyskać istotne przyspieszenia w niektórych zastosowaniach
Shadery pikseli Jednostka cieniująca (shader) dla pikseli wyznacza kolor piksela na podstawie położenia (x, y) piksela na ekranie i dodatkowych informacji: kolorów tekstury współrzędnych teksturowych innych atrybutów przekazanych do shadera Szybko zauważono, że w miejsce kolorów można podstawić inne dane numeryczne Dane wejściowe kodowane były w teksturze, a wyniki odczytywane z kolorów poszczególnych pikseli wygenerowanej tekstury Wady: ograniczone możliwości odczytu i zapisu danych (w kolorach tekstur) ograniczone wsparcie dla liczb zmiennoprzecinkowych konieczna znajomość programowania grafiki, bibliotek OpenGL, Direct3D
Model programowania GPU wady Do wad należało: programista musiał posiadać wiedzę na temat programowania grafiki, w tym języka shaderów dane musiały być przekazywane w nienaturalnej postaci, tj. współrzędnych wierzchołków, pikseli tekstur API nie udostępniało możliwości swobodnego odczytu / zapisu pamięci, co istotnie ograniczało możliwe zastosowania brak obsługi obliczeń na liczbach zmiennoprzecinkowych podwójnej precyzji dodatkowo ograniczał zastosowania
Rozwój GPU obliczenia ogólne W 2006 r. Nvidia wyprodukowała karty graficzne (architektura G80) umożliwiające wykonywanie obliczeń ogólnego przeznaczenia Wraz z kartami graficznymi z serii G80 udostępniona została technologia CUDA (ang. Compute Unified Device Architecture) umożliwiająca stosunkowo łatwe wykorzystanie GPU do obliczeń ogólnego przeznaczenia Odpowiedzią ATI (AMD) jest AMD APP (Accelerated Parallel Processing) SDK, który wspiera standard OpenCL
Model programowania CUDA Programy korzystające z CUDA pisane są w językach C 1, C++ 2 oraz Fortran W modelu CUDA procesor główny CPU stanowi tzw. procesor macierzysty (ang. host) Procesor graficzny GPU nazywany jest urządzeniem (and. device) Procesor graficzny złożony jest z tzw. wieloprocesorów (ang. multiprocessors) Program uruchamiany jest na CPU, oznaczone funkcje nazywane jądrami obliczeniowymi (ang. kernel) wykonywane są przez GPU 1 Nie jest w pełni obsługiwany standard C99 2 Obsługiwany jest wybrany podzbiór C++ (host ISO14882:2003, na urządzeniu podzbiór)
CUDA kompilacja programu Kod źródłowy programu Kompilator NVCC Kod hosta Preprocesor, kompilator, linker Kod urządzenia (PTX) Kompilator just-in-time urządzenia Heterogeniczna platforma obliczeniowa z CPU i GPU
CUDA schemat wykonania programu CPU - kod sekwencyjny Kod równoległy na GPU KernelA<<<nBIK, ntid>>>(args)... CPU - kod sekwencyjny Kod równoległy na GPU KernelA<<<nBIK, ntid>>>(args)...
Schemat wykonania typowego programu dla GPU CPU alokuje pamięć na GPU CPU kopiuje dane wejściowe z RAM do pamięci GPU CPU uruchamia program (kernel) na GPU CPU kopiuje wynik z pamięci GPU do RAM
CUDA podział pracy Kernel w modelu CUDA wykonywany jest równolegle przez wiele wątków (ang. thread) Wszystkie wątki kernela należą do jednej kraty (ang. grid) Wątki wewnątrz kraty podzielone są na bloki
CUDA podział pracy Podział na bloki wynika z architektury GPU Wątki w bloku wykonywane są na tym samym wieloprocesorze strumieniowym (SM) Wątek ma unikalny ID wewnątrz bloku Blok ma unikalne ID wewnątrz kraty Host Kernel 1 Kernel 2 Block (1, 1) Thread (0, 0) Device Thread (1, 0) Grid 1 Block (0, 0) Block (0, 1) Grid 2 Thread (2, 0) Block (1, 0) Block (1, 1) Thread (3, 0) Thread (4, 0) Block (2, 0) Block (2, 1) Thread (0, 1) Thread (0, 2) Thread (1, 1) Thread (1, 2) Thread (2, 1) Thread (2, 2) Thread (3, 1) Thread (3, 2) Thread (4, 1) Thread (4, 2) 9
CUDA wątek Wątek to niezależny przepływ sterowania, ma: identyfikator (ang. thread ID) unikalny wewnątrz bloku wskaźnik instrukcji (ang. program counter) wartości rejestrów lokalną, prywatną pamięć dane wejściowe i wyniki obliczeń
CUDA blok Wątki w bloku wykonywane są na tym samym procesorze (SM) Wątki w ramach bloku mają dostęp do współdzielonej pamięci możliwa bezpośrednia komunikacja Mogą być synchronizowane za pomocą lokalnych blokad Wątki w ramach bloku dzielone są na tzw. osnowy (ang. warp) Blok watków Lokalna pamięć bloku
CUDA SIMT Wątki w ramach bloku CUDA wykonywane są w modelu SIMT (ang. single instruction multiple thread) odpowiednik modelu SIMD znanego z klasycznych procesorów Wszystkie wątki w ramach warpu wykonują tę samą instrukcję, ale dla różnych danych SISD a b a 1 a 2 b 1 b 2 + + SIMD szer = 2 c c 1 c 2
CUDA SIMT SIMT powoduje, że każde rozgałęzienie w kodzie skutkuje bezczynnością części wątków W celu zapewnienia dobrej wydajności należy eliminować rozgałęzienia w ramach warpu (32 wątki), ale niekoniecznie w ramach bloku n =... Branch if (n>0) Path B Ścieżka B Path A Ścieżka A
CUDA kraty Krata złożona jest z bloków, wykonujących ten sam kernel Bloki w kracie mają dostęp do pamięci globalnej zmiany widoczne po synchronizacji Bloki wewnątrz kraty muszą być niezależne - dowolna kolejność wykonania bloków, również sekwencyjnie Niezależność wykonania bloków poprawia skalowalność Możliwa jest komunikacja między blokami, ale nie synchronizacja Krata 0 Krata 1 Pamięć globalna
Architektura GPU na przykładzie Nvidia Rozwój technologiczny powoduje, że architektury programowalnych kart graficznych podlegają częstym zmianom Lista architektur GPU Nvidia obejmuje (od najstarszej): Tesla (2007) Fermi (2010) Kepler (2012) Maxwell (2014)
Architektura GPU na przykładzie Nvidia Fermi Rysunek : Schemat GPU w architekturze Nvidia Fermi
Architektura GPU na przykładzie Nvidia Fermi 3 miliardy tranzystorów 16 multiprocesorów (SM) x 32 rdzeni CUDA = 512 6 GB pamięci RAM (GDDR5) 384 bitowa szyna pamięci Połączenie CPU GPU za pomocą PCI-Express Moduł GigaThread pełni rolę planisty przydzielającego bloki wątków do poszczególnych SM
Nvidia Fermi multiprocesor strumieniowy Multiprocesor strumieniowy zawiera 32 rdzenie CUDA Każdy zawiera jednostę ALU oraz FPU (ang. Floating Point Unit) W pojedynczym cyklu zegara wykonywane są operacje arytmetyczne na liczbach całkowitych 32-bitowych oraz zmiennoprzecinkowych pojedynczej precyzji W poprzedniej generacji mnożenie l. całk. miało tylko 24 bitową precyzję (w jednym cyklu)
Nvidia Fermi multiprocesor strumieniowy LD/ST jednostki odczytu / zapisu danych dla poszczególnych wątków (DRAM, cache) SFU jednostki do obliczania funkcji takich jak sin, cos, odwrotność, pierw. kwadratowy łącznie 4 instr. cykl, 8 cykli na warp jeden SM może wykonać 16 operacji zmiennoprzecinkowych podwójnej precyzji / cykl
Nvidia Fermi planista SM SM wyposażony jest w 2 planistów (ang. warp scheduler) SM może jednocześnie wykonywać 2 grupy (osnowy, ang. warp) po 32 wątków Instrukcje z obu warpów są przydzielane do odpowiednich jednostek ALU/FPU (po 16) oraz SFU i LD/ST Operacje na liczbach podwójnej precyzji z jednej osnowy nie mogą być wykonywane jednocześnie z instrukcją drugiej osnowy
Nvidia Fermi pamięć SM SM wyposażony jest w 64kB konfigurowalnej pamięci 16kB pamięci współdzielonej + 48kB pamięci podręcznej 1 poziomu (L1) 48kB pamięci współdzielonej + 16kB pamięci podręcznej 1 poziomu Pamięć podręczna ma na celu wyeliminowanie opóźnień wynikających z konieczności transferu danych z pamięci głównej urządzenia oraz hosta Wątki wykonywane na SM mogą się efektywnie komunikować za pomocą pamięci współdzielonej Opóźnienie pamięci współdzielonej to średnio 10-20 cykli Przepustowość 1600 GB/s Karta wyposażona jest również w pamięć podręczną drugiego poziomu 768kB opóźnienie 400 800 cykli
Nvidia Fermi porównanie architektur GPU G80 GT200 Fermi Tranzystory 681 mln 1.4 mld 3 mld CUDA cores 128 240 512 DFLOPS - 30 op. / cykl 256 op. / cykl SFLOPS 128 op./cykl 240 op./cykl 512 op./cykl SFU 2 2 4 Planiści na SM 1 1 2 Pamięć współdzielona SM 16 kb 16 kb 16 kb / 48 kb L1 cache - - 16 kb / 48 kb L2 cache - - 768 kb Pamięć z ECC Nie Nie Tak Adresowanie 32-bitowe 32-bitowe 64-bitowe
Wprowadzenie Historia rozwoju GPU Model programowania GPU CUDA Architektura GPU OpenCL Nvidia Kepler 7,1 mld tranzystorów 1 Tflop operacji na liczbach zmiennoprzecinkowych podwójnej precyzji (Kepler GK110) Większa efektywność energetyczna, tj. stosunek liczby operacji na wat (ok 3 razy w stosunku do Fermi) Rysunek : Nvidia Kepler GK110 GPU
Wprowadzenie Historia rozwoju GPU Model programowania GPU CUDA Nvidia Kepler Rysunek : Schemat procesora GK110 Architektura GPU OpenCL
Nvidia Kepler multiprocesor strumieniowy 192 rdzenie CUDA (6x więcej od Fermi) 64 jednostki zmiennoprzecinkowe podwójnej precyzji 32 SFU 32 jednostki adresowe (LD/ST) 4x planista umożliwiający jednoczene wykonywanie do 4 warpów (po 32 wątki) Instrukcje na liczbach podówjnej precyzji mogą być wykonywane jednocześnie z innymi Liczba rejestrów na wątek zwiększona do 255 (4x więcej od Fermi) Pamięć podręczna L2 1536KB (2 x więcej od Fermi) Pamięć podręczna i współdzielona w przeliczeniu na rdzenie uległa zmniejszeniu Rysunek : Multiprocesor strumieniowy (SMX) w GPU Kepler
Nvidia Kepler vs Fermi Fermi GF104 Kepler GK110 L. warpów na SM 48 64 L. wątków na SM 1536 2048 L. bloków wątków na SM 8 16 Rejestry 32-bitowe 32768 65536 Rejestry / wątek 63 255 Wątki na blok 1024 1024 Pamięć współdzielona 16K/48K 16K/32K/64K Maks. wymiar kraty 2ˆ16-1 2ˆ32-1
Wydajność maksymalna a praktyczna GPU cechują się bardzo dużą wydajnością teoretyczną mierzoną FLOPS W praktyce jednak wydajność jest mniejsza od teoretycznej, co wynika z bardzo wielu czynników, m.in. Stopnia równoległości obliczeń w programie Rodzaju obliczeń, w tym stopnia heterogeniczności, liczby rozgałęzień Opóźnień wynikających z transferu danych we / wy Przykładowo, dla problemu mnożenia macierzy i ręcznie optymalizowanego kodu assemblerowego osiągnięto wydajność ok. 77.3% teoretycznej na GPU Kepler GK104 3 3 Lai, Junjie, and Andre Seznec. Performance upper bound analysis and optimization of sgemm on fermi and kepler gpus. Code Generation and Optimization (CGO), 2013 IEEE/ACM International Symposium on. IEEE, 2013.
Nvidia Compute Capability Każda karta Nvidii ma określony tzw. Compute Capability w postaci numeru wersji, np. 2.1 Jest to specyfikacja określająca architekturę karty, m.in. liczbę rdzeni na SM, maks. liczbę wątków, bloków, rejestrów Compute Capability może być stosowany przez programy do sprawdzenia możliwości obliczeniowych dostępnych kart graficznych w sposób usystematyzowany
OpenCL OpenCL to: Platforma programistyczna do tworzenia aplikacji działających na platformach sprzętowych złożonych z procesorów (CPU), kart graficznych (GPU) Architektura podobna do CUDA w większości przypadków istnieją bezpośrednie odpowiedniki dla poszczególnych metod, typów danych itp.
OpenCL historia Autorem OpenCL jest Apple, które posiada prawa do nazwy Apple przesłało propozycję przekształcenia OpenCL w otwarty standard do konsorcjum Khronos Group Do grupy pracującej nad OpenCL należą m.in. Nvidia, AMD, Intel, IBM Specyfikacja dla OpenCL 1.0 została udostępniona w 2008 roku Wersja OpenCL 1.1 została udostępniona w 2010 r. Wersja OpenCL 1.2 została udostępniona w 2011 r. W 2013 roku wydano OpenCL 2.0
WebCL W 2014 roku udostępniono wersję 1.0 nowej specyfikacji WebCL, która rozszerza HTML5 o możliwość wykonywania kosztownych obliczeniowo zadań za pomocą GPU i innych urządzeń wspierających OpenCL Definiuje sposób wykorzystania OpenCL z poziomu JavaScript W ramach OpenCL 1.0 udostępniono translator umożliwiający konwersję kodu OpenCL do WebCL Standard wspierany jest m.in. przez Adobe, AMD, Aptina, ARM, Google, Imagination Technologies, Mozilla, Intel, Nokia, NVIDIA, Opera Software, Samsung, and Qualcomm
OpenCL OpenCL umożliwia tworzenie oprogramowania działającego na platformach wyposażonywch w heterogeniczne jednostki obliczeniowe, takie jak: procesory ogólnego przeznaczenia (ang. Central Processing Unit, CPU) karty graficzne (ang. Graphic Processing Units, GPU) procesory sygnałowe (DSP) układy FPGA (ang. Field Programmable Gate Array) - cyfrowe programowalne układy logiczne dedykowane koprocesory do obliczeń równoległych (np. Intel Xeon Phi) Rysunek : Intel Xeon Phi
OpenCL Parametr Nazwa urządzenia Xeon E5-2670 Xeon Phi 5110P Tesla K20X Cores 8 60 14 SMX Logical Cores 16 (HT) 240 (HT) 2,688 CUDA cores Frequency 2.60GHz 1.053GHz 735MHz GFLOPs (double) 333 1,010 1,317 SIMD width 256 Bits 512 Bits N/A Memory 16-128GB 8GB 6GB Memory B/W 51.2GB/s 320GB/s 250GB/s Threading software software hardware Tabela : Porównanie przykładowych platform sprzętowych: CPU koprocesor GPU
CUDA czy OpenCL CUDA jest bardziej popularna więcej dokumentacji, przykładów, programów W większości przypadków obie technologie pozwalają uzyskać podobną wydajność 4 OpenCL to otwarty standard wspierany przez wiele firm, włączając Nvidię OpenCL oferuje większą przenośność kodu pomiędzy różnymi platformami 4 Fang, Jianbin, Ana Lucia Varbanescu, and Henk Sips. A comprehensive performance comparison of CUDA and OpenCL. Parallel Processing (ICPP), 2011 International Conference on. IEEE, 2011.
Perspektywy Intel planuje sprzedaż procesora Xeon z zintegrowanym układem FPGA, który umożliwia przyspieszenie wybranych zadań nawet do 20x (nie zweryfikowane) Microsoft zastosował układy FPGA (Startix V) do przyspieszenia działania serwerów Bing poprawiono przepustowość fragmentu systemu odpowiedzialnego za obliczanie rangi indeksowanych dokumentów o 95% przy zwiększeniu zapotrzebowania na energię elektryczną o 10% 5 5 Putnam A. i in. (23 osoby), A Reconfigurable Fabric for Accelerating Large-Scale Datacenter Services, 41st Annual International Symposium on Computer Architecture (ISCA), 2014.
Trudności programowania GPU Problem z kompatybilnością kodu np. CUDA działa na kartach Nvidia, ale nie ATI Mnogość modeli kart graficznych uzyskanie najlepszej wydajności wymaga często ręcznej optymalizacji dla konkretnego modelu Programowanie dla GPU wymaga dobrej znajomości architektury i jest w pewnym sensie niskopoziomowe Trudniejsze debugowanie programów dla GPU Wykorzystanie mocy GPU wymaga odpowiednich algorytmów Rozwój standardów i bibliotek z pewnością zniweluje dużą część ww problemów
C++ AMP W 2012 roku Microsoft opublikował otwartą specyfikację dla C++ AMP (Accelerated Massive Parallelism) rozszerzenie dla C++ plus biblioteka programistyczna (na wzór STL) Umożliwia tworzenie programów, w których część obliczeń wykonywana jest na GPU C++ AMP wykorzystuje DirectX11 i działa na Windows 7 i nowszych 1 void AddArrays(int n, int m, int * pa, int * pb, int * psum) { 2 concurrency::array_view<int,2> a(n, m, pa), b(n, m, pb), sum(n, m, psum); 3 concurrency::parallel_for_each(sum.extent, 4 [=](concurrency::index<2> i) restrict(amp) 5 { 6 sum[i] = a[i] + b[i]; 7 }); 8 }