web-dev-qa-db-de.com

Verwenden von Java mit Nvidia-GPUs (cuda)

Ich arbeite an einem Geschäftsprojekt, das in Java) ausgeführt wird und eine enorme Rechenleistung benötigt, um Geschäftsmärkte zu berechnen. Einfache Mathematik, aber mit einer enormen Datenmenge.

Wir haben ein paar cuda gpus bestellt, um es zu versuchen, und da Java von cuda nicht unterstützt wird, frage ich mich, wo ich anfangen soll. Soll ich eine JNI-Schnittstelle erstellen? Soll ich JCUDA verwenden oder gibt es andere Möglichkeiten?

Ich habe keine Erfahrung auf diesem Gebiet und möchte, dass mich jemand auf etwas hinweist, damit ich anfangen kann zu forschen und zu lernen.

124
Hans

Zuallererst sollten Sie sich der Tatsache bewusst sein, dass CUDA Berechnungen nicht automatisch beschleunigt. Einerseits, weil GPU-Programmierung eine Kunst ist und es sehr, sehr herausfordernd sein kann, sie zu bekommen richtig. Zum anderen, weil GPUs nur für bestimmte Arten von Berechnungen gut geeignet sind.

Dies mag verwirrend klingen, da Sie grundsätzlich alles auf der GPU berechnen können. Entscheidend ist natürlich, ob Sie eine gute Beschleunigung erreichen oder nicht. Die wichtigste Klassifizierung hierbei ist, ob es sich bei einem Problem um aufgabenparallele oder datenparallele . Der erste bezieht sich grob gesagt auf Probleme, bei denen mehrere Threads mehr oder weniger unabhängig voneinander an ihren eigenen Aufgaben arbeiten. Der zweite bezieht sich auf Probleme, bei denen viele Threads alle machen dasselbe - aber auf verschiedene Teile der Daten.

Letzteres ist das Problem, mit dem sich GPUs gut auskennen: Sie haben viele Kerne, und alle Kerne tun das Gleiche, arbeiten jedoch mit unterschiedlichen Teilen der Eingabedaten.

Sie haben erwähnt, dass Sie "einfache Mathematik, aber mit großen Datenmengen" haben. Obwohl dies wie ein perfekt datenparalleles Problem klingt und daher für eine GPU gut geeignet ist, ist ein weiterer Aspekt zu berücksichtigen: GPUs sind in Bezug auf die theoretische Rechenleistung (FLOPS, Floating Point Operations Per Second) unglaublich schnell. Sie werden jedoch häufig durch die Speicherbandbreite gedrosselt.

Dies führt zu einer anderen Klassifizierung von Problemen. Nämlich, ob Probleme speichergebunden oder rechnergebunden sind.

Der erste bezieht sich auf Probleme, bei denen die Anzahl der Anweisungen, die für jedes Datenelement ausgeführt werden, gering ist. Betrachten Sie zum Beispiel eine parallele Vektoraddition: Sie müssen read zwei Datenelemente, dann eine einzelne Addition durchführen und dann write die Summe in die Ergebnis Vektor. Sie werden keine Beschleunigung feststellen, wenn Sie dies auf der GPU tun, da der einzelne Zusatz den Aufwand für das Lesen/Schreiben des Speichers nicht kompensiert.

Der zweite Ausdruck "rechnergebunden" bezieht sich auf Probleme, bei denen die Anzahl der Befehle im Vergleich zur Anzahl der Speicherlese-/Schreibvorgänge hoch ist. Betrachten Sie zum Beispiel eine Matrixmultiplikation: Die Anzahl der Befehle ist O (n ^ 3), wenn n die Größe der Matrix ist. In diesem Fall ist zu erwarten, dass die GPU bei einer bestimmten Matrixgröße die Leistung einer CPU übertrifft. Ein anderes Beispiel könnte sein, wenn viele komplexe trigonometrische Berechnungen (Sinus/Cosinus usw.) an "wenigen" Datenelementen durchgeführt werden.

Als Faustregel gilt: Sie können davon ausgehen, dass das Lesen/Schreiben eines Datenelements aus dem "Haupt" -GPU-Speicher eine Latenz von ca. 500 Anweisungen hat.

Ein weiterer wichtiger Punkt für die Leistung von GPUs ist daher die Datenlokalität : Wenn Sie Daten lesen oder schreiben müssen (und in den meisten Fällen müssen Sie dies tun) ;-)), dann solltest du darauf achten, dass die Daten so nah wie möglich an den GPU-Kernen liegen. GPUs haben daher bestimmte Speicherbereiche (als "lokaler Speicher" oder "gemeinsam genutzter Speicher" bezeichnet), die normalerweise nur wenige KB groß sind, aber für Daten, die in eine Berechnung einbezogen werden sollen, besonders effizient sind.

Um dies noch einmal zu betonen: Die GPU-Programmierung ist eine Kunst, die sich nur aus der Ferne auf die parallele Programmierung auf der CPU bezieht. Dinge wie Threads in Java mit all der Parallelinfrastruktur wie ThreadPoolExecutors, ForkJoinPools usw. könnten den Eindruck erwecken, dass Sie Ihre Arbeit nur irgendwie aufteilen und auf mehrere Prozessoren verteilen müssen. Auf der GPU können Sie Herausforderungen auf einer viel niedrigeren Ebene begegnen: Belegung, Registerdruck, gemeinsamer Speicherdruck, Speicherzusammenführung ... um nur einige zu nennen.

Wenn Sie jedoch ein datenparalleles, rechnergebundenes Problem lösen müssen, ist die GPU der richtige Weg.


Eine allgemeine Bemerkung: Sie haben ausdrücklich nach CUDA gefragt. Ich würde Ihnen jedoch dringend empfehlen, sich auch OpenCL anzuschauen. Das hat mehrere Vorteile. Erstens ist es ein herstellerunabhängiger, offener Industriestandard, und es gibt Implementierungen von OpenCL durch AMD, Apple, Intel und NVIDIA. Darüber hinaus gibt es in der Java Welt eine viel breitere Unterstützung für OpenCL. Der einzige Fall, in dem ich mich lieber für CUDA entscheiden würde, ist, wenn Sie die CUDA-Laufzeitbibliotheken wie CUFFT für FFT oder CUBLAS für BLAS (Matrix-/Vektoroperationen) verwenden möchten. Es gibt zwar Ansätze, ähnliche Bibliotheken für OpenCL bereitzustellen, diese können jedoch nicht direkt von Java aus verwendet werden, es sei denn, Sie erstellen Ihre eigenen JNI-Bindungen für diese Bibliotheken.


Es könnte Sie auch interessieren zu hören, dass die OpenJDK HotSpot-Gruppe im Oktober 2012 das Projekt "Sumatra" gestartet hat: http://openjdk.Java.net/projects/sumatra/ . Ziel dieses Projekts ist es, die GPU-Unterstützung direkt in der JVM bereitzustellen, mit Unterstützung der JIT. Der aktuelle Status und die ersten Ergebnisse können in der Mailingliste unter http://mail.openjdk.Java.net/mailman/listinfo/sumatra-dev eingesehen werden


Vor einiger Zeit habe ich jedoch einige Ressourcen zum Thema "Java auf der GPU" gesammelt. Ich werde diese hier in keiner bestimmten Reihenfolge noch einmal zusammenfassen.

( Haftungsausschluss : Ich bin der Autor von http://jcuda.org/ und http://jocl.org/ )

(Byte-) Code-Übersetzung und OpenCL-Code-Generierung:

https://github.com/aparapi/aparapi : Eine Open-Source-Bibliothek, die von AMD erstellt und aktiv verwaltet wird. In einer speziellen "Kernel" -Klasse kann eine bestimmte Methode überschrieben werden, die parallel ausgeführt werden soll. Der Bytecode dieser Methode wird zur Laufzeit mit einem eigenen Bytecodeleser geladen. Der Code wird in OpenCL-Code übersetzt, der dann mit dem OpenCL-Compiler kompiliert wird. Das Ergebnis kann dann auf dem OpenCL-Gerät ausgeführt werden, das eine GPU oder eine CPU sein kann. Wenn die Kompilierung in OpenCL nicht möglich ist (oder keine OpenCL verfügbar ist), wird der Code weiterhin parallel unter Verwendung eines Thread-Pools ausgeführt.

https://github.com/pcpratts/rootbeer1 : Eine Open-Source-Bibliothek zum Konvertieren von Teilen von Java in CUDA-Programme. Es bietet dedizierte Schnittstellen, die implementiert werden können, um anzuzeigen, dass eine bestimmte Klasse auf der GPU ausgeführt werden soll. Im Gegensatz zu Aparapi wird versucht, die "relevanten" Daten (also den gesamten relevanten Teil des Objektgraphen!) Automatisch in eine für die GPU geeignete Darstellung zu serialisieren.

https://code.google.com/archive/p/Java-gpu/ : Eine Bibliothek zum Übersetzen von mit Anmerkungen versehenem Java Code (mit einigen Einschränkungen) in CUDA-Code dann in eine Bibliothek kompiliert, die den Code auf der GPU ausführt. Die Bibliothek wurde im Rahmen einer Doktorarbeit entwickelt, die fundierte Hintergrundinformationen zum Übersetzungsprozess enthält.

https://github.com/ochafik/ScalaCL : Scala Bindings für OpenCL. Ermöglicht die parallele Verarbeitung spezieller Scala Sammlungen mit OpenCL. Die Funktionen, die für die Elemente der Auflistungen aufgerufen werden, können gewöhnliche Scala -Funktionen sein (mit einigen Einschränkungen), die dann in OpenCL-Kernel übersetzt werden.

Spracherweiterungen

http://www.ateji.com/px/index.html : Eine Spracherweiterung für Java, die parallele Konstrukte (zB parallel für Schleifen, OpenMP-Stil) erlaubt, die dann sind auf der GPU mit OpenCL ausgeführt. Leider wird dieses vielversprechende Projekt nicht mehr weitergeführt.

http://www.habanero.rice.edu/Publications.html (JCUDA): Eine Bibliothek, die speziellen Java Code (genannt JCUDA-Code) in Java- und CUDA-Code übersetzen kann -C-Code, der dann auf der GPU kompiliert und ausgeführt werden kann. Die Bibliothek scheint jedoch nicht öffentlich zugänglich zu sein.

https://www2.informatik.uni-erlangen.de/DE/research/JavaOpenMP/index.html : Java Spracherweiterung für OpenMP-Konstrukte mit CUDA-Backend

Java OpenCL/CUDA-Bindungsbibliotheken

https://github.com/ochafik/JavaCL : Java Bindings für OpenCL: Eine objektorientierte OpenCL-Bibliothek, die auf automatisch generierten Low-Level-Bindings basiert

http://jogamp.org/jocl/www/ : Java Bindings für OpenCL: Eine objektorientierte OpenCL-Bibliothek, die auf automatisch generierten Low-Level-Bindings basiert

http://www.lwjgl.org/ : Java Bindings für OpenCL: Automatisch generierte Low-Level-Bindings und objektorientierte Convenience-Klassen

http://jocl.org/ : Java Bindungen für OpenCL: Low-Level-Bindungen, die eine 1: 1-Zuordnung der ursprünglichen OpenCL-API darstellen

http://jcuda.org/ : Java Bindungen für CUDA: Low-Level-Bindungen, die eine 1: 1-Zuordnung der ursprünglichen CUDA-API darstellen

Sonstiges

http://sourceforge.net/projects/jopencl/ : Java Bindings für OpenCL. Scheint seit 2010 nicht mehr gepflegt zu sein

http://www.hoopoe-cloud.com/ : Java Bindungen für CUDA. Scheint nicht mehr gepflegt zu sein


399
Marco13

Ich würde damit beginnen, eines der Projekte für Java und CUDA: http://www.jcuda.org/ zu verwenden

3
JohnKlehm

Nach meinen Recherchen habe ich drei Möglichkeiten gefunden, die Cuda-API in Java zu verwenden, wenn Sie auf Nvidia-GPUs abzielen und sich für die Verwendung von Cuda über openCL entschieden haben.

  1. JCuda (oder Alternative) - http://www.jcuda.org/ Dies scheint die beste Lösung für die Probleme zu sein, an denen ich arbeite. Viele Bibliotheken wie CUBLAS sind in JCuda verfügbar. Kernel sind aber immer noch in C geschrieben.
  2. JNI - JNI-Interfaces sind nicht mein Favorit beim Schreiben, aber sie sind sehr leistungsfähig und ermöglichen es Ihnen, alles zu tun, was Cuda kann.
  3. JavaCPP - Damit können Sie im Grunde eine JNI-Schnittstelle in Java erstellen, ohne C-Code direkt zu schreiben. Hier finden Sie ein Beispiel https://stackoverflow.com/a/12871248/8692546 of wie man das mit cuda thrust benutzt. Mir scheint, Sie könnten genauso gut eine JNI-Schnittstelle schreiben.

Alle diese Antworten sind im Grunde genommen nur Möglichkeiten, C/C++ - Code in Java zu verwenden. Sie sollten sich fragen, warum Sie Java verwenden müssen und ob Sie dies nicht in c/c ++ tun können.

Wenn Sie Java mögen und wissen, wie man es benutzt und nicht mit der gesamten Zeigerverwaltung arbeiten wollen und was nicht mit c/c ++ kommt, dann ist JCuda wahrscheinlich die Antwort. Auf der anderen Seite können die Cuda Thrust-Bibliothek und ähnliche Bibliotheken verwendet werden, um einen Großteil der Zeigerverwaltung in c/c ++ durchzuführen, und vielleicht sollten Sie sich das ansehen.

Wenn Sie C/C++ mögen und sich nicht um die Zeigerverwaltung kümmern, Sie jedoch aufgrund anderer Einschränkungen gezwungen sind, Java zu verwenden, ist JNI möglicherweise der beste Ansatz. Wenn Ihre JNI-Methoden nur Wrapper für Kernel-Befehle sein sollen, können Sie auch einfach JCuda verwenden.

Es gibt ein paar Alternativen zu JCuda wie Cuda4J und Root Beer, aber diese scheinen nicht beibehalten zu werden. Während zum Zeitpunkt des Schreibens dieses JCuda Cuda 10.1 unterstützt. das ist die aktuellste cuda sdk.

Darüber hinaus gibt es einige Java Bibliotheken, die cuda verwenden, z. B. deeplearning4j und Hadoop, und die möglicherweise in der Lage sind, das zu tun, wonach Sie suchen, ohne dass Sie Kernel-Code direkt schreiben müssen. Ich habe sie aber nicht zu sehr untersucht.

1
David Griffin