Verwenden von Java mit Nvidia-GPUs (CUDA)

144

Ich arbeite an einem Geschäftsprojekt, das in Java ausgeführt wird und für die Berechnung der Geschäftsmärkte eine enorme Rechenleistung benötigt. Einfache Mathematik, aber mit einer großen Datenmenge.

Wir haben einige 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 würde gerne wissen, ob mich jemand auf etwas hinweisen könnte, damit ich anfangen kann zu forschen und zu lernen.

Hans
quelle
2
GPUs helfen Ihnen dabei, bestimmte Arten von rechenintensiven Problemen zu beschleunigen. Wenn Sie jedoch über eine große Datenmenge verfügen, ist die Wahrscheinlichkeit einer E / A-Bindung höher. Höchstwahrscheinlich sind GPUs nicht die Lösung.
Steve Cook
1
"Steigerung der Java-Leistung mit GPGPUs" -> arxiv.org/abs/1508.06791
BlackBear
4
Eine Art offene Frage, ich bin froh, dass die Mods sie nicht heruntergefahren haben, weil die Antwort von Marco13 unglaublich hilfreich ist! Sollte ein Wiki sein IMHO
JimLohse

Antworten:

442

Zunächst sollten Sie sich der Tatsache bewusst sein, dass CUDA Berechnungen nicht automatisch schneller macht. Einerseits, weil die GPU-Programmierung eine Kunst ist und es sehr, sehr schwierig sein kann, sie richtig zu machen . 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 . Der entscheidende Punkt ist natürlich, ob Sie eine gute Beschleunigung erreichen oder nicht. Die wichtigste Klassifizierung hierbei ist, ob ein Problem aufgabenparallel oder datenparallel ist . 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 dasselbe tun - jedoch auf verschiedenen Teilen der Daten.

Letzteres ist das Problem, bei dem GPUs gut sind: Sie haben viele Kerne, und alle Kerne tun dasselbe, arbeiten jedoch mit verschiedenen Teilen der Eingabedaten.

Sie haben erwähnt, dass Sie "einfache Mathematik, aber mit einer großen Datenmenge" haben. Obwohl dies wie ein perfekt datenparalleles Problem klingt und daher für eine GPU gut geeignet ist, ist noch ein weiterer Aspekt zu berücksichtigen: GPUs sind in Bezug auf die theoretische Rechenleistung (FLOPS, Gleitkommaoperationen pro Sekunde) lächerlich schnell. Sie werden jedoch häufig durch die Speicherbandbreite gedrosselt.

Dies führt zu einer weiteren Klassifizierung von Problemen. Nämlich, ob Probleme speicher- 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 beispielsweise eine parallele Vektoraddition: Sie müssen zwei Datenelemente lesen , dann eine einzelne Addition durchführen und dann die Summe in den Ergebnisvektor schreiben . Wenn Sie dies auf der GPU tun, wird keine Beschleunigung angezeigt, da der einzelne Zusatz den Aufwand für das Lesen / Schreiben des Speichers nicht kompensiert.

Der zweite Begriff "rechnergebunden" bezieht sich auf Probleme, bei denen die Anzahl der Befehle im Vergleich zur Anzahl der Lese- / Schreibvorgänge im Speicher hoch ist. Betrachten Sie beispielsweise eine Matrixmultiplikation: Die Anzahl der Anweisungen ist O (n ^ 3), wenn n die Größe der Matrix ist. In diesem Fall kann man erwarten, dass die GPU eine CPU bei einer bestimmten Matrixgröße ü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 ;-)), sollten Sie sicherstellen, dass die Daten so nah wie möglich gehalten werden möglich zu den GPU-Kernen. GPUs haben daher bestimmte Speicherbereiche (als "lokaler Speicher" oder "gemeinsam genutzter Speicher" bezeichnet), die normalerweise nur wenige KB groß sind, aber besonders effizient für Daten sind, die in eine Berechnung einbezogen werden sollen.

Um dies noch einmal zu betonen: Die GPU-Programmierung ist eine Kunst, die nur aus der Ferne mit der parallelen Programmierung auf der CPU zusammenhängt. Dinge wie Threads in Java, mit allen Gleichzeitigkeit Infrastruktur wie ThreadPoolExecutors, ForkJoinPoolsusw. könnte den Eindruck erwecken , dass Sie nur irgendwie Ihre Arbeit aufteilen müssen und es auf mehrere Prozessoren zu verteilen. Auf der GPU können Herausforderungen auf einer viel niedrigeren Ebene auftreten: Belegung, Registerdruck, Druck auf den gemeinsamen Speicher, Zusammenführen des Speichers ... um nur einige zu nennen.

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


Eine allgemeine Bemerkung: Sie haben speziell nach CUDA gefragt. Ich würde Ihnen jedoch dringend empfehlen, sich auch OpenCL anzuschauen. Es hat mehrere Vorteile. Erstens ist es ein herstellerunabhängiger, offener Industriestandard, und es gibt Implementierungen von OpenCL von 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 mit CUDA zufrieden geben möchte, ist, wenn Sie die CUDA-Laufzeitbibliotheken wie CUFFT für FFT oder CUBLAS für BLAS (Matrix / Vector-Operationen) verwenden möchten. Obwohl es Ansätze gibt, ähnliche Bibliotheken für OpenCL bereitzustellen, können sie nicht direkt von Java-Seite verwendet werden, es sei denn, Sie erstellen Ihre eigenen JNI-Bindungen für diese Bibliotheken.


Vielleicht ist es auch interessant 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, GPU-Unterstützung direkt in der JVM mit Unterstützung der JIT bereitzustellen . Der aktuelle Status und die ersten Ergebnisse sind in der Mailingliste unter http://mail.openjdk.java.net/mailman/listinfo/sumatra-dev aufgeführt


Vor einiger Zeit habe ich jedoch einige Ressourcen gesammelt, die sich auf "Java auf der GPU" im Allgemeinen beziehen. 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, bei dem es sich möglicherweise um eine GPU oder eine CPU handelt. Wenn die Kompilierung in OpenCL nicht möglich ist (oder kein OpenCL verfügbar ist), wird der Code weiterhin parallel mithilfe 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 anzugeben, dass eine bestimmte Klasse auf der GPU ausgeführt werden soll. Im Gegensatz zu Aparapi wird versucht, die "relevanten" Daten (dh 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 kommentiertem Java-Code (mit einigen Einschränkungen) in CUDA-Code, der dann in eine Bibliothek kompiliert wird, 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-Bindungen für OpenCL. Ermöglicht die parallele Verarbeitung spezieller Scala-Sammlungen parallel zu OpenCL. Die Funktionen, die für die Elemente der Sammlungen aufgerufen werden, können übliche Scala-Funktionen (mit einigen Einschränkungen) sein, die dann in OpenCL-Kernel übersetzt werden.

Spracherweiterungen

http://www.ateji.com/px/index.html : Eine Spracherweiterung für Java, die parallele Konstrukte (z. B. Parallel for Loops, OpenMP-Stil) ermöglicht, die dann mit OpenCL auf der GPU ausgeführt werden. Leider wird dieses vielversprechende Projekt nicht mehr gepflegt.

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

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

Java OpenCL / CUDA-Bindungsbibliotheken

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

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

http://www.lwjgl.org/ : Java-Bindungen für OpenCL: Automatisch generierte Low-Level-Bindungen 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

Verschiedenes

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

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


Marco13
quelle
Betrachten Sie eine Operation zum Hinzufügen von 2 Matrizen und Speichern des Ergebnisses in einer dritten Matrix. Bei mehreren Threads auf der CPU ohne OpenCL ist der Engpass immer der Schritt, in dem das Hinzufügen erfolgt. Diese Operation ist offensichtlich datenparallel. Nehmen wir jedoch an, wir wissen vorher nicht, ob es rechengebunden oder speichergebunden sein wird. Die Implementierung erfordert viel Zeit und Ressourcen, um festzustellen, ob die CPU bei dieser Operation viel besser ist. Wie kann man dies also vorher identifizieren, ohne den OpenCL-Code zu implementieren?
Cool_Coder
2
@Cool_Coder In der Tat ist es schwer im Voraus zu sagen, ob (oder wie viel) eine bestimmte Aufgabe von einer GPU-Implementierung profitiert. Für ein erstes Bauchgefühl braucht man wahrscheinlich etwas Erfahrung mit verschiedenen Anwendungsfällen (die ich zugegebenermaßen auch nicht wirklich habe). Ein erster Schritt könnte darin bestehen, unter nvidia.com/object/cuda_showcase_html.html nachzusehen, ob ein "ähnliches" Problem aufgeführt ist. (Es ist CUDA, aber konzeptionell so nah an OpenCL, dass die Ergebnisse in den meisten Fällen übertragen werden können.) In den meisten Fällen wird auch die Beschleunigung erwähnt, und viele von ihnen haben Links zu Papieren oder sogar Code
Marco13
+1 für aparapi - Dies ist eine einfache Möglichkeit, mit opencl in Java zu beginnen, und ermöglicht es Ihnen, die Leistung von CPU und GPU in einfachen Fällen einfach zu vergleichen. Es wird auch von AMD verwaltet, funktioniert aber gut mit Nvidia-Karten.
Steve Cook
12
Dies ist eine der besten Antworten, die ich je auf StackOverflow gesehen habe. Vielen Dank für die Zeit und Mühe!
ViggyNash
1
@AlexPunnen Dies würde wahrscheinlich den Rahmen der Kommentare sprengen. Soweit ich weiß, bietet OpenCV eine gewisse CUDA-Unterstützung ab docs.opencv.org/2.4/modules/gpu/doc/introduction.html . Unter developer.nvidia.com/npp gibt es viele Bildverarbeitungsroutinen, die nützlich sein können. Und github.com/GPUOpen-ProfessionalCompute-Tools/HIP kann eine "Alternative" für CUDA sein. Es mag möglich sein, dies als neue Frage zu stellen, aber man muss darauf achten, sie richtig zu formulieren, um Abstimmungen für "meinungsbasiert" / "nach Bibliotheken von Drittanbietern fragen" zu vermeiden ...
Marco13
4

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

JohnKlehm
quelle
2

Von der Forschung habe ich getan, wenn Sie Nvidia GPUs sind Targeting und haben beschlossen , CUDA über die Verwendung von OpenCL , fand ich drei Möglichkeiten , die CUDA - API in Java zu verwenden.

  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 jedoch immer noch in C geschrieben.
  2. JNI - JNI-Schnittstellen sind nicht mein Favorit, aber sie sind sehr leistungsfähig und ermöglichen es Ihnen, alles zu tun, was CUDA tun kann.
  3. JavaCPP - Auf diese Weise können Sie im Grunde eine JNI-Schnittstelle in Java erstellen, ohne C-Code direkt schreiben zu müssen. Hier gibt es ein Beispiel: Was ist der einfachste Weg, um funktionierenden CUDA-Code in Java auszuführen? wie man dies mit CUDA-Schub benutzt. Mir scheint, Sie könnten genauso gut eine JNI-Schnittstelle schreiben.

Alle diese Antworten sind im Grunde 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 verwendet, und nicht mit der gesamten Zeigerverwaltung arbeiten möchten und was nicht, das mit C / C ++ geliefert wird, 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 die Zeigerverwaltung nicht stören, aber andere Einschränkungen Sie zur Verwendung von Java zwingen, ist JNI möglicherweise der beste Ansatz. Wenn Ihre JNI-Methoden nur Wrapper für Kernel-Befehle sind, können Sie auch JCuda verwenden.

Es gibt einige 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. Dies ist das aktuellste CUDA SDK.

Darüber hinaus gibt es einige Java-Bibliotheken, die CUDA verwenden, wie z. B. deeplearning4j und Hadoop, die möglicherweise das tun können, wonach Sie suchen, ohne dass Sie den Kernel-Code direkt schreiben müssen. Ich habe sie jedoch nicht zu sehr untersucht.

David Griffin
quelle
1

Marco13 lieferte bereits eine hervorragende Antwort .

Falls Sie nach einer Möglichkeit suchen, die GPU zu verwenden, ohne CUDA / OpenCL-Kernel zu implementieren, möchte ich einen Verweis auf die finmath-lib-cuda-Erweiterungen (finmath-lib-gpu-Erweiterungen) http: // finmath hinzufügen .net / finmath-lib-cuda-extensions / (Haftungsausschluss: Ich bin der Betreuer dieses Projekts).

Das Projekt bietet eine Implementierung von "Vektorklassen", genauer gesagt eine Schnittstelle namens RandomVariable, die arithmetische Operationen und die Reduzierung von Vektoren ermöglicht. Es gibt Implementierungen für die CPU und die GPU. Die Implementierung erfolgt durch algorithmische Differenzierung oder einfache Bewertungen.

Die Leistungsverbesserungen auf der GPU sind derzeit gering (für Vektoren der Größe 100.000 erhalten Sie jedoch möglicherweise einen Faktor> 10 Leistungsverbesserungen). Dies liegt an den kleinen Kernelgrößen. Dies wird sich in einer zukünftigen Version verbessern.

Die GPU-Implementierung verwendet JCuda und JOCL und ist für Nvidia- und ATI-GPUs verfügbar.

Die Bibliothek ist Apache 2.0 und über Maven Central verfügbar.

Christian Fries
quelle