Używanie języka Java z procesorami graficznymi Nvidia (CUDA)

144

Pracuję nad projektem biznesowym, który jest wykonywany w Javie i wymaga ogromnej mocy obliczeniowej do obliczania rynków biznesowych. Prosta matematyka, ale z ogromną ilością danych.

Zamówiliśmy kilka procesorów graficznych CUDA, aby to wypróbować, a ponieważ Java nie jest obsługiwana przez CUDA, zastanawiam się, od czego zacząć. Czy powinienem zbudować interfejs JNI? Czy powinienem używać JCUDA, czy są inne sposoby?

Nie mam doświadczenia w tej dziedzinie i chciałbym, aby ktoś skierował mnie do czegoś, abym mógł rozpocząć badania i uczyć się.

Hans
źródło
2
Procesory GPU pomogą Ci przyspieszyć określone typy problemów wymagających dużej mocy obliczeniowej. Jeśli jednak masz ogromną ilość danych, bardziej prawdopodobne jest, że będziesz związany z IO. Najprawdopodobniej układy GPU nie są rozwiązaniem.
Steve Cook
1
„Zwiększanie wydajności Java za pomocą GPGPU” -> arxiv.org/abs/1508.06791
BlackBear
4
Coś w rodzaju otwartego pytania, cieszę się, że modyfikacje go nie zamknęły, ponieważ odpowiedź od Marco13 jest niesamowicie pomocna! Powinna być wiki IMHO
JimLohse

Odpowiedzi:

442

Przede wszystkim należy mieć świadomość, że CUDA nie przyspieszy automagicznie obliczeń. Z jednej strony, ponieważ programowanie na GPU to sztuka i może być bardzo, bardzo trudne, aby to zrobić dobrze . Z drugiej strony, ponieważ procesory graficzne są dobrze przystosowane tylko do niektórych rodzajów obliczeń.

Może to wydawać się zagmatwane, ponieważ w zasadzie możesz obliczyć wszystko na GPU. Kluczową kwestią jest oczywiście to, czy osiągniesz dobre przyspieszenie, czy nie. Najważniejsza klasyfikacja dotyczy tego, czy problem dotyczy zadań równoległych, czy danych . Pierwsza z nich odnosi się z grubsza do problemów, w których kilka wątków pracuje nad własnymi zadaniami, mniej lub bardziej niezależnie. Drugi odnosi się do problemów, w których wiele wątków robi to samo - ale dotyczy różnych części danych.

Ten ostatni jest rodzajem problemu, w którym GPU są dobre: ​​mają wiele rdzeni i wszystkie rdzenie robią to samo, ale działają na różnych częściach danych wejściowych.

Wspomniałeś, że masz „prostą matematykę, ale z ogromną ilością danych”. Chociaż może to brzmieć jak problem idealnie równoległy do ​​danych, a zatem dobrze dopasowany do GPU, jest jeszcze jeden aspekt do rozważenia: GPU są absurdalnie szybkie pod względem teoretycznej mocy obliczeniowej (FLOPS, Floating Point Operations Per Second). Ale często są ograniczane przez przepustowość pamięci.

Prowadzi to do innej klasyfikacji problemów. Mianowicie, czy problemy są związane z pamięcią, czy z obliczeniami .

Pierwsza odnosi się do problemów, w których liczba instrukcji wykonywanych dla każdego elementu danych jest mała. Na przykład, rozważ dodanie równoległego wektora: musisz odczytać dwa elementy danych, następnie wykonać pojedyncze dodawanie, a następnie zapisać sumę w wektorze wynikowym. Nie zobaczysz przyśpieszenia, robiąc to na GPU, ponieważ pojedynczy dodatek nie rekompensuje wysiłków związanych z odczytem / zapisem pamięci.

Drugi termin, „związany z obliczeniami”, odnosi się do problemów, w których liczba instrukcji jest duża w porównaniu z liczbą odczytów / zapisów pamięci. Na przykład rozważmy mnożenie macierzy: liczba instrukcji będzie wynosić O (n ^ 3), gdy n jest rozmiarem macierzy. W takim przypadku można się spodziewać, że procesor graficzny będzie przewyższał procesor przy określonym rozmiarze matrycy. Innym przykładem może być sytuacja, gdy wiele złożonych obliczeń trygonometrycznych (sinus / cosinus itp.) Jest wykonywanych na „kilku” elementach danych.

Ogólna zasada: można założyć, że odczyt / zapis jednego elementu danych z „głównej” pamięci GPU ma opóźnienie około 500 instrukcji ....

Dlatego kolejnym kluczowym punktem dla wydajności GPU jest lokalizacja danych : jeśli musisz czytać lub zapisywać dane (a w większości przypadków będziesz musiał ;-)), powinieneś upewnić się, że dane są przechowywane tak blisko, jak możliwe do rdzeni GPU. W związku z tym procesory graficzne mają określone obszary pamięci (zwane „pamięcią lokalną” lub „pamięcią współdzieloną”), które zwykle zajmują tylko kilka KB, ale są szczególnie wydajne w przypadku danych, które mają zostać wykorzystane w obliczeniach.

Aby to jeszcze raz podkreślić: programowanie GPU to sztuka, która jest tylko zdalnie związana z programowaniem równoległym na CPU. Rzeczy takie jak wątki w Javie, z całą infrastrukturą współbieżności jak ThreadPoolExecutors, ForkJoinPoolsitd. Może sprawiać wrażenie, że po prostu trzeba podzielić swoją pracę i jakoś rozprowadzić go wśród wielu procesorów. Na GPU możesz napotkać wyzwania na znacznie niższym poziomie: zajętość, presja rejestru, presja pamięci współdzielonej, łączenie pamięci ... żeby wymienić tylko kilka.

Jeśli jednak masz do rozwiązania problem związany z równoległymi danymi i obliczeniami, najlepszym rozwiązaniem jest GPU.


Ogólna uwaga: Twoja prośba o CUDA. Ale zdecydowanie polecam przyjrzenie się OpenCL. Ma kilka zalet. Przede wszystkim jest to niezależny od dostawcy, otwarty standard branżowy i istnieją implementacje OpenCL firm AMD, Apple, Intel i NVIDIA. Ponadto w świecie Javy istnieje znacznie szersze wsparcie dla OpenCL. Jedynym przypadkiem, w którym wolałbym zadowolić się CUDA, jest użycie bibliotek wykonawczych CUDA, takich jak CUFFT dla FFT lub CUBLAS dla BLAS (operacje macierzowe / wektorowe). Chociaż istnieją podejścia do udostępniania podobnych bibliotek dla OpenCL, nie można ich bezpośrednio używać po stronie języka Java, chyba że utworzysz własne powiązania JNI dla tych bibliotek.


Ciekawe może być również to, że w październiku 2012 roku grupa OpenJDK HotSpot rozpoczęła projekt „Sumatra”: http://openjdk.java.net/projects/sumatra/ . Celem tego projektu jest zapewnienie wsparcia GPU bezpośrednio w JVM, przy wsparciu JIT. Aktualny stan i pierwsze wyniki można zobaczyć na ich liście mailingowej pod adresem http://mail.openjdk.java.net/mailman/listinfo/sumatra-dev


Jednak jakiś czas temu zebrałem trochę zasobów związanych z „Javą na GPU”. Ponownie podsumuję je tutaj, bez określonej kolejności.

( Zastrzeżenie : jestem autorem http://jcuda.org/ i http://jocl.org/ )

(Bajtowe) tłumaczenie kodu i generowanie kodu OpenCL:

https://github.com/aparapi/aparapi : biblioteka open source, która jest tworzona i aktywnie obsługiwana przez firmę AMD. W specjalnej klasie „Kernel” można przesłonić określoną metodę, która powinna być wykonywana równolegle. Kod bajtowy tej metody jest ładowany w czasie wykonywania przy użyciu własnego czytnika kodów bajtowych. Kod jest tłumaczony na kod OpenCL, który jest następnie kompilowany za pomocą kompilatora OpenCL. Wynik można następnie wykonać na urządzeniu OpenCL, którym może być GPU lub CPU. Jeśli kompilacja do OpenCL nie jest możliwa (lub OpenCL nie jest dostępny), kod nadal będzie wykonywany równolegle przy użyciu puli wątków.

https://github.com/pcpratts/rootbeer1 : Biblioteka typu open source do konwersji części Java do programów CUDA. Oferuje dedykowane interfejsy, które można zaimplementować w celu wskazania, że ​​dana klasa powinna zostać uruchomiona na GPU. W przeciwieństwie do Aparapi, próbuje on automatycznie serializować „istotne” dane (czyli całą istotną część wykresu obiektu!) W reprezentację odpowiednią dla GPU.

https://code.google.com/archive/p/java-gpu/ : biblioteka do tłumaczenia kodu Java z adnotacjami (z pewnymi ograniczeniami) na kod CUDA, który jest następnie kompilowany do biblioteki wykonującej kod na GPU. Biblioteka została opracowana w kontekście rozprawy doktorskiej, która zawiera głębokie informacje na temat procesu tłumaczenia.

https://github.com/ochafik/ScalaCL : Wiązania Scala dla OpenCL. Pozwala na przetwarzanie specjalnych kolekcji Scala równolegle z OpenCL. Funkcje wywoływane w elementach kolekcji mogą być zwykłymi funkcjami Scali (z pewnymi ograniczeniami), które są następnie tłumaczone na jądra OpenCL.

Rozszerzenia językowe

http://www.ateji.com/px/index.html : Rozszerzenie języka dla Java, które umożliwia równoległe konstrukcje (np. równoległe pętle, styl OpenMP), które są następnie wykonywane na GPU z OpenCL. Niestety ten bardzo obiecujący projekt nie jest już utrzymywany.

http://www.habanero.rice.edu/Publications.html (JCUDA): biblioteka, która może tłumaczyć specjalny kod Java (zwany kodem JCUDA) na kod Java i CUDA-C, który można następnie skompilować i wykonać na GPU. Jednak biblioteka nie wydaje się być publicznie dostępna.

https://www2.informatik.uni-erlangen.de/EN/research/JavaOpenMP/index.html : Rozszerzenie języka Java dla konstrukcji OpenMP z zapleczem CUDA

Biblioteki powiązań Java OpenCL / CUDA

https://github.com/ochafik/JavaCL : Powiązania Java dla OpenCL: zorientowana obiektowo biblioteka OpenCL, oparta na automatycznie generowanych powiązaniach niskiego poziomu

http://jogamp.org/jocl/www/ : powiązania Java dla OpenCL: zorientowana obiektowo biblioteka OpenCL, oparta na automatycznie generowanych powiązaniach niskiego poziomu

http://www.lwjgl.org/ : Powiązania Java dla OpenCL: automatycznie generowane powiązania niskiego poziomu i zorientowane obiektowo klasy wygody

http://jocl.org/ : powiązania Java dla OpenCL: powiązania niskiego poziomu, które są mapowaniem 1: 1 oryginalnego interfejsu API OpenCL

http://jcuda.org/ : powiązania Java dla CUDA: powiązania niskiego poziomu, które są mapowaniem 1: 1 oryginalnego interfejsu API CUDA

Różne

http://sourceforge.net/projects/jopencl/ : powiązania Java dla OpenCL. Wydaje się, że nie jest już obsługiwany od 2010 roku

http://www.hoopoe-cloud.com/ : powiązania Java dla CUDA. Wydaje się, że nie jest już utrzymywany


Marco13
źródło
rozważ operację dodania 2 macierzy i zapisania wyniku w trzeciej macierzy. W przypadku połączenia wielowątkowego na CPU bez OpenCL, wąskie gardło zawsze będzie krokiem, w którym nastąpi dodanie. Ta operacja jest oczywiście równoległa do danych. Ale powiedzmy, że nie wiemy, czy będzie to wcześniej związane z obliczeniami, czy z pamięcią. Wykonanie tej operacji zajmuje dużo czasu i zasobów, a następnie widać, że procesor znacznie lepiej wykonuje tę operację. Jak więc można to wcześniej zidentyfikować bez implementacji kodu OpenCL.
Cool_Coder
2
@Cool_Coder Rzeczywiście, trudno z góry przewidzieć, czy (lub ile) dane zadanie odniesie korzyści z implementacji GPU. Aby uzyskać pierwsze przeczucie, prawdopodobnie potrzebne jest doświadczenie z różnymi przypadkami użycia (których, co prawda, również tak naprawdę nie mam). Pierwszym krokiem może być przejrzenie strony nvidia.com/object/cuda_showcase_html.html i sprawdzenie, czy na liście znajduje się „podobny” problem. (To CUDA, ale koncepcyjnie jest tak blisko OpenCL, że w większości przypadków wyniki można przenieść). W większości przypadków wspomina się również o przyspieszeniu, a wiele z nich ma linki do dokumentów lub nawet kodu
Marco13
+1 dla aparapi - to prosty sposób na rozpoczęcie pracy z opencl w Javie i pozwala na łatwe porównanie wydajności CPU i GPU w prostych przypadkach. Jest również utrzymywany przez AMD, ale działa dobrze z kartami Nvidia.
Steve Cook
12
To jedna z najlepszych odpowiedzi, jakie kiedykolwiek widziałem w StackOverflow. Dzięki za czas i wysiłek!
ViggyNash
1
@AlexPunnen To prawdopodobnie wykracza poza zakres komentarzy. O ile wiem, OpenCV ma pewne wsparcie dla CUDA, od docs.opencv.org/2.4/modules/gpu/doc/introduction.html . Developer.nvidia.com/npp ma wiele procedur przetwarzania obrazu, które mogą być przydatne. A github.com/GPUOpen-ProfessionalCompute-Tools/HIP może być „alternatywą” dla CUDA. To może być możliwe, aby zadać to pytanie jako nowy, ale trzeba uważać, aby wyrażenie to prawidłowo, aby uniknąć downvotes dla „opinia opiera się” / „z prośbą o bibliotekach trzecich” ...
Marco13
4

Zacząłbym od wykorzystania jednego z projektów dla Javy i CUDA: http://www.jcuda.org/

JohnKlehm
źródło
2

Z przeprowadzonych przeze mnie badań , jeśli celujesz w procesory graficzne Nvidia i zdecydowałeś się używać CUDA zamiast OpenCL , znalazłem trzy sposoby wykorzystania interfejsu API CUDA w javie.

  1. JCuda (lub alternatywa) - http://www.jcuda.org/ . Wydaje się, że jest to najlepsze rozwiązanie problemów, nad którymi pracuję. Wiele bibliotek, takich jak CUBLAS, jest dostępnych w JCuda. Jednak jądra są nadal pisane w C.
  2. JNI - interfejsy JNI nie należą do moich ulubionych do pisania, ale są bardzo wydajne i pozwolą ci zrobić wszystko, co może zrobić CUDA.
  3. JavaCPP - w zasadzie pozwala to stworzyć interfejs JNI w Javie bez bezpośredniego pisania kodu w C. Oto przykład: Jaki jest najłatwiejszy sposób uruchomienia działającego kodu CUDA w Javie? jak używać tego z ciągiem CUDA. Wydaje mi się, że równie dobrze możesz po prostu napisać interfejs JNI.

Wszystkie te odpowiedzi to po prostu sposoby wykorzystania kodu C / C ++ w Javie. Powinieneś zadać sobie pytanie, dlaczego musisz używać Javy i jeśli nie możesz tego zrobić w C / C ++.

Jeśli lubisz Javę i wiesz, jak z niej korzystać, i nie chcesz pracować z całym zarządzaniem wskaźnikami i tym, czego nie ma w C / C ++, to JCuda jest prawdopodobnie odpowiedzią. Z drugiej strony, biblioteka CUDA Thrust i inne biblioteki podobne do niej mogą być używane do zarządzania wskaźnikami w C / C ++ i być może powinieneś się temu przyjrzeć.

Jeśli lubisz C / C ++ i nie masz nic przeciwko zarządzaniu wskaźnikami, ale istnieją inne ograniczenia zmuszające Cię do korzystania z Javy, JNI może być najlepszym podejściem. Chociaż, jeśli twoje metody JNI będą tylko opakowaniami dla poleceń jądra, równie dobrze możesz po prostu użyć JCuda.

Istnieje kilka alternatyw dla JCuda, takich jak Cuda4J i Root Beer, ale nie wydaje się, aby zostały one utrzymane. Zważywszy, że w chwili pisania tego JCuda obsługuje CUDA 10.1. który jest najnowszym CUDA SDK.

Ponadto istnieje kilka bibliotek Java korzystających z CUDA, takich jak deeplearning4j i Hadoop, które mogą być w stanie zrobić to, czego szukasz, bez konieczności bezpośredniego pisania kodu jądra. Jednak nie zaglądałem im zbytnio.

David Griffin
źródło
1

Marco13 już udzielił doskonałej odpowiedzi .

Jeśli szukasz sposobu na użycie GPU bez implementacji jąder CUDA / OpenCL, chciałbym dodać odniesienie do finmath-lib-cuda-extensions (finmath-lib-gpu-extensions) http: // finmath .net / finmath-lib-cuda-extensions / (zastrzeżenie: jestem opiekunem tego projektu).

Projekt zapewnia implementację „klas wektorowych”, a dokładniej interfejs o nazwie RandomVariable, który zapewnia operacje arytmetyczne i redukcję na wektorach. Istnieją implementacje dla procesora i karty graficznej. Istnieją implementacje wykorzystujące różniczkowanie algorytmiczne lub zwykłe wartościowanie.

Poprawa wydajności GPU jest obecnie niewielka (ale w przypadku wektorów o rozmiarze 100 000 można uzyskać współczynnik> 10 ulepszeń wydajności). Wynika to z małych rozmiarów jądra. Poprawi się to w przyszłej wersji.

Implementacja GPU wykorzystuje JCuda i JOCL i jest dostępna dla procesorów graficznych Nvidia i ATI.

Biblioteka to Apache 2.0 i dostępna za pośrednictwem Maven Central.

Christian Fries
źródło