Programowanie współbieżne, Języki programowania, Aplikacje, Gry

GPGPU – podstawowe informacje

Listopad 28th, 2009 Marcin Borowiec

Zauważyłem ostatnio, że popularność GPGPU wśród użytkowników/programistów zaczęła gwałtownie wzrastać. Niestety wielu z nich nie wie co to tak na prawdę jest, jak z tego skorzystać, co trzeba posiadać (sprzęt, oprogramowanie) i jakie ma to możliwości. Postanowiłem więc przybliżyć nieco idę i możliwości GPGPU. Nie zamierzam jednak tworzyć tutaj jakiegoś kursu (przynajmniej na razie).

GPGPU to jak sama nazwa wskazuje możliwość wykorzystania procesorów graficznych do obliczeń ogólnego przeznaczenia. Idea GPGPU zaczeła się rodzić po udostępnieniu kart graficznych zgodnych z DirectX9 (głównie DirectX9c). Operacje zmiennoprzecinkowe z 32bitową precyzją, sensowna długość kodu shadera, dynamiczne instrukcje warunkowe w kodzie, pętle mogły dać początek GPGPU. Niestety w tamtym czasie jedynym możliwym sposobem wykorzystania kart graficznych było napisanie programu z wykorzystaniem DirectX czy OpenGL. Biblioteki te oprócz bezpośredniego wyświetlania obrazu na ekranie posiadają możliwość generowania wyników do tekstury. Programista mógł więc wysłać do karty graficznej dane w postaci tablicy danych (jako tekstura czy bufor wierzchołków), wygenerować wynik i pobrać wynik (teksturę). Do pewnych specyficznych operacji coś takiego wystarczyło, kiedyś czytałem o prostych operacjach na próbkach dźwiękowych dzięki takiemu chwytowi. Niestety do wielu zastowań to zdecydowanie za mało.

Kolejnym etapem było stworzenie kart graficznych z tzw. zunifikowanymi jednostkami cieniowania. Mowa tu seriach GeForce 8xxx i RadeonHD 2xxx z czego produkty NVidi były w kontekście GPGPU bardziej zaawansowane i nadal są, ale o tym nieco później. NVidia razem z premierą serii 8 GeForce’ów udostępniła technologię CUDA. Technologia ta pozwalała w dość prosty sposób wykorzystać moce drzemiące w karcie graficznej. CUDA tworzone było przede wszystkim do obliczeń GPGPU więc nie spotkamy tutaj niepotrzebnych ograniczeń. AMD(ATI) też myślało o czymś podobnym, najpierw zaczeli od ATI CTM (Close To Metal), które dość szybko anulowali bo zrozumieli że nie tędy droga a później stworzyli ATI Stream.

Obecnie interfejsów do programowania kart graficznych jest dość dużo. Poniżej wymienie najważniejsze z nich:

  • C for CUDA czyli programowanie w zmodyfikowanym przez nvidie języku C. Rozwiązanie do tej pory najpopularniejsze, procentowy rozkład użycia tej technologii może w przyszłości nieco zmaleć za sprawą interfejsów otwartych takich jak OpenCL czy DirectCompute.
  • CUDA PTX czyli możliwość pisania programów na GPU NVidii w asemblerze, w pewnych przypadkach sensownym może być utworzenie programu w C, skompilowanie go a później ręczna optymalizacja w asemblerze. Będąc scisłym, PTX nie jest asemblerem wykonywanym bezpośrednio przez GPU. PTX przed wykonaniem musi być przetłumaczony na kod maszynowy konkretnego modelu GPU, na którym będzie uruchomiony program.
  • Brook++ składnik ATI Stream w wersjach 1.x, stosunkowo prosty sposób programowania GPU, łatwy do użycia ale zarazem o niewielkich możliwościach (w porównaniu np do C for CUDA), trudno w Brook++ wykorzystać pełnię mocy Radeonów HD5xxx. Właśnie dlatego AMD przestaje oficjalnie wspierać Brook++ w ATI Stream 2.x. Mimo iż zarzekają się że projekt dalej będzie wspierany, z tym że od teraz jako oddzielny projekt open source to można uznać że jest to w tej chwili ślepa uliczka. AMD chce się teraz skupić na bardziej uniwersalnych interfejsach (OpenCL i DirectCompute)
  • AMD CAL z asemblerem AMD IL jest odpowiednikiem PTX od Nvidii. Asembler daje największe możliwości na zaoptymalizowanie kodu jednak ze wględu na poziom trudności jest bardzo rzadko stosowany.
  • OpenCL, otwarty standard rozwijany przez Khronos Group (przez tą samą grupę, która odpowiada za OpenGL), Każdy z producentów (AMD, NVidia) dostarcza implementację tego interfejsu. Khronos Group zajmuje się tylko certyfikacją gotowych bibliotek. OpenCL posiada także funkcje, które mają umożliwić lepszą współpracę z biblioteką OpenGL: m.in edycję buforów wierzchołków OpenGL z poziomu OpenCL.
  • DirectCompute (element DirectX11), biblioteka przygotowana we współpracy producentów kart graficznych z firmą Microsoft. Charakteryzuje się bardzo dobrą współpracą z innymi elementami DirectX.

Teoretycznie jest możliwe napisanie programu pod GPU nie posiadając odpowiedniego akceleratora u siebie. C for CUDA posiada możliwość emulacji napisanego kodu na CPU, OpenCL posiada implementacje także na CPU (ja przynajmniej znam jedną od AMD), DirectCompute można uruchomić poprzez urządzenie referencyjne (które jest bardzo wolne, WARP niestety nie obsługuje DirectCompute). W rzeczywistości sytuacje, które powstają podczas wykorzystania GPU nijak nie można zasymulować na CPU, tak więc bardzo mocno polecam zaopatrzyć  się w odpowiednią kartę graficzną. Jaką? Dla mnie jedynymi sensownymi układami są najnowsza seria Radeonów HD 5xxx i kart NVidii zgodnych z ComputeCapability przynajmniej 1.1 (czyli seria GTX 2xx, 9xxx i częściowo 8xxx, bez słynnego G80) a także odpowiadające im modele układów Tesla i FireStream. Dlaczego akurat takie karty?

  • Radeon HD seria 2xxx i HD seria 3xxx wypada bardzo ubogo pod względem wydajności obliczeniowej, obsługiwanych bibliotek jak i funkcji wbudowanych w GPU. Jeśli ktoś nie daje Ci za darmo takiej karty to jej nie bierz. Brak m.in obsługi OpenCL i DirectCompute!
  • Radeon HD seria 4xxx to układy pełne paradoksów. W karty wbudowano pamięć LDS – Local Data Store. NVidia coś podobnego (Shared Memory) miała już w pierwszych układach GeForce 8! Najciekawsze jest jednak to że Brook++ z racji swojej specyfikacji nie jest w stanie wykorzystać odpowiednio LDS, OpenCL mimo iż obsługiwany przez serie 4xxx to LDS nie jest zgodny z OpenCL i nie jest w ogóle wykorzystywany. DirectCompute 4.0, który teoretycznie jest kompatybilny z LDS nie jest jeszcze dostępny bo AMD nie przygotowała odpowiednich sterowników. Oprócz LDS seria 4xxx posiada pamięć GDS – Global Data Storage, która w zasadzie też nie może być wykorzystana, ponieważ Radeon 4xxx nie potrafi odpowiednio zsynchronizować dostępu do niej. Łącznie 176KB bardzo szybkiej pamięci wbudowanej wewnątrz układu nie może być wykorzystane. Brawo AMD!
  • Radeon HD seria 5xxx, w końcu układ z  bardziej zaawansowaną pamięcią LDS (pełny odczyt i zapis przez każdy wątek z grupy), obsługą operacji atomowych, obsługą konkretnego API do programowania GPU (OpenCL i DirectCompute). Tylko dlaczego dopiero teraz?
  • Jeśli chodzi o NVidię to tutaj każdy układ zgodny z CUDA jest sensowny. Każdy z tych układów dzięki nowszym sterownikom obsłuży także OpenCL i DirectCompute włącznie z wprowadzonym kilka lat temu do sprzedaży GeForcem 8800! Ja jednak polecam zaopatrzyć się w nieco nowszy układ (coś na bazie G92 albo najlepiej GT200), który posiada obsługę instrukcji atomowych.

Wrócę jeszcze na chwilę do interfejsów programowania. Myślę że można tutaj przyjąć następujący algorytm wyboru:

  • Rozszerzanie gry w DirectX o zaawansowane efekty: DirectCompute
  • Rozszerzanie gry w OpenGL o zaawansowane efekty: OpenCL
  • Wykorzystanie GPU do obliczeń innych niż graficzne: OpenCL
  • Specjalistyczne zadania niewymagające wspierania wszystkich producentów GPU: Najnowszy układ GeForce/Quadro/Tesla, C for CUDA lub ewentualnie PTX gdy konieczna jest maksymalna optymalizacja.

Na koniec pozostała jeszcze jedna ważna sprawa. Jak bardzo obecne GPGPU jest elastyczne, jakiego typu programy można uruchamiać na kartach graficznych?

  1. Obecnie żaden dotychczasowy program pisany z myślą o CPU nie może być bezpośrednio przeniesiony na GPU bez jakiejkolwiek modyfikacji kodu. Chodzi mi tutaj o programy pisane w C++, C# i innych tego typu językach.
  2. Algorytm przenoszony na GPU musi być wielowątkowy. Chodzi tutaj o rząd kilku tysięcy wątków. Co więcej każdy z tych wątków musi przetwarzać ten sam kod więc mamy tutaj do czynienia wyłącznie ze współbieżnością na poziomie danych. Oczywiście każdy wątek ma swój kontekst i wie czy powinien przetwarzać tą czy tą gałęź kodu (w przypadku pętli i instrukcji warunkowych). To ograniczenie będzie częściowo zniesione w nowym GPU NVidii „Fermi”, który zadebiutuje w przyszłym roku. Teoretycznie każdy multiprocesor będzie mógł wykonywać inny kernel (tak popularnie nazywana jest funkcja wywołana na GPU) co oznacza kilka-kilkanaście niezależnych grup wątków wykonujących inny kod.
  3. Z poziomu GPU program nie ma dostępu do pamięci głównej komputera, nie ma dostępu do urządzeń wejścia/wyjścia komputera. Część programu działająca po stronie CPU musi wysłać dane do urządzenia, uruchomić funkcję na tym urządzeniu i pobrać wyniki z urządzenia. Jeśli funkcja na CPU uzna że musi uaktualnić dane to funkcja na GPU musi zostać przerwana, nowe dane muszą zostać dostarczone i funkcja musi być ponownie uruchomiona.
  4. GPU nie obsługuje rekurencji, każdy algorytm musi być zapisany w wersji iteracyjnej.
  5. GPU nie obsługuje zaawansowanych sposobów synchronizacji wątków, mimo wszystko operacje atomowe (choć niedostępne w niektórych GPU) i bariery na grupy wątków wystarczają do zbudowania efektywnej współpracy pomiędzy wątkami. Wątki w grupie mogą odczytywać i zapisywać wspólną pamięć dzieloną. Wszystkie wątki na urządzeniu mogą odczytywać i zapisywać wspólną pamięć globalną.
  6. GPU nie posiada zaawansowanego mechanizmu cache (chociaż wspomniany wcześniej układ „Fermi” ma taki cache posiadać). Obecnie trzeba sobie pomóc trzema elementami dającymi namiastkę cache: odczyt/zapis tabel dwuwymiarowych ze specjalnym tylko do tego dedykowanym małym cache; odczyt z pamięci stałej (pamięć globalna oznaczona jako nie zmieniana podczas pojedynczego wywołania kernela), do tego też jest specjalny cache; stworzenie ręcznie zarządzanego ogólnego cache poprzez wykorzystanie wbudowanej pamięci dzielonej dla grupy wątków.

Przykładów wykorzystania GPU można szukać np. na stronie NVidii. Ja dodam że istnieje możliwość przeniesienia sztucznej inteligencji Paper Balla na GPU, robiłem już małe testy 😉

Post wyszedł dość długi a ja nie napisałem wszystkiego czego chciałem. Mimo wszystko na razie na tym zakończę, a do tematu wrócę w którymś z kolejnych mini artykułów.