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ę.
Odpowiedzi:
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
,ForkJoinPools
itd. 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
źródło
Zacząłbym od wykorzystania jednego z projektów dla Javy i CUDA: http://www.jcuda.org/
źródło
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.
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.
źródło
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.
źródło