Utilisation de Java avec les GPU Nvidia (CUDA)


144

Je travaille sur un projet d'entreprise réalisé en Java, et il a besoin d'une énorme puissance de calcul pour calculer les marchés commerciaux. Mathématiques simples, mais avec une énorme quantité de données.

Nous avons commandé des GPU CUDA pour l'essayer et comme Java n'est pas pris en charge par CUDA, je me demande par où commencer. Dois-je créer une interface JNI? Dois-je utiliser JCUDA ou existe-t-il d'autres moyens?

Je n'ai pas d'expérience dans ce domaine et j'aimerais que quelqu'un puisse me diriger vers quelque chose pour que je puisse commencer à faire des recherches et à apprendre.


2
Les GPU vous aideront à accélérer certains types de problèmes gourmands en calculs. Cependant, si vous disposez d'une énorme quantité de données, vous êtes plus susceptible d'être lié aux E / S. Les GPU ne sont probablement pas la solution.
steve cook

1
"Boosting Java Performance using GPGPUs" -> arxiv.org/abs/1508.06791
BlackBear

4
Une sorte de question ouverte, je suis heureux que les mods ne l'aient pas arrêtée car la réponse de Marco13 est incroyablement utile! Devrait être un wiki IMHO
JimLohse

Réponses:


443

Tout d'abord, vous devez être conscient du fait que CUDA ne rendra pas automatiquement les calculs plus rapides. D'une part, parce que la programmation GPU est un art, et il peut être très, très difficile de l' obtenir droit . D'autre part, parce que les GPU ne sont bien adaptés qu'à certains types de calculs.

Cela peut sembler déroutant, car vous pouvez essentiellement calculer n'importe quoi sur le GPU. Le point clé est, bien sûr, de savoir si vous obtiendrez une bonne accélération ou non. La classification la plus importante ici est de savoir si un problème est parallèle de tâches ou parallèle de données . Le premier se réfère, grosso modo, à des problèmes où plusieurs threads travaillent sur leurs propres tâches, plus ou moins indépendamment. Le second fait référence à des problèmes où de nombreux threads font tous la même chose - mais sur différentes parties des données.

Ce dernier est le genre de problème pour lequel les GPU sont bons: ils ont de nombreux cœurs, et tous les cœurs font de même, mais fonctionnent sur différentes parties des données d'entrée.

Vous avez mentionné que vous avez «des calculs simples mais avec une énorme quantité de données». Bien que cela puisse sembler un problème parfaitement parallèle aux données et donc qu'il était bien adapté pour un GPU, il y a un autre aspect à considérer: les GPU sont ridiculement rapides en termes de puissance de calcul théorique (FLOPS, Floating Point Operations Per Second). Mais ils sont souvent limités par la bande passante mémoire.

Cela conduit à une autre classification des problèmes. À savoir si les problèmes sont liés à la mémoire ou au calcul .

Le premier fait référence à des problèmes où le nombre d'instructions exécutées pour chaque élément de données est faible. Par exemple, considérons une addition de vecteur parallèle: vous devrez lire deux éléments de données, puis effectuer une seule addition, puis écrire la somme dans le vecteur de résultat. Vous ne verrez pas d'accélération en faisant cela sur le GPU, car l'ajout unique ne compense pas les efforts de lecture / écriture de la mémoire.

Le deuxième terme, «calcul lié», fait référence à des problèmes où le nombre d'instructions est élevé par rapport au nombre de lectures / écritures de mémoire. Par exemple, considérons une multiplication matricielle: le nombre d'instructions sera O (n ^ 3) lorsque n est la taille de la matrice. Dans ce cas, on peut s'attendre à ce que le GPU surclasse un CPU à une certaine taille de matrice. Un autre exemple pourrait être celui où de nombreux calculs trigonométriques complexes (sinus / cosinus, etc.) sont effectués sur "quelques" éléments de données.

En règle générale: vous pouvez supposer que la lecture / écriture d'un élément de données à partir de la mémoire GPU "principale" a une latence d'environ 500 instructions ....

Par conséquent, un autre point clé pour les performances des GPU est la localité des données : si vous devez lire ou écrire des données (et dans la plupart des cas, vous devrez le faire ;-)), vous devez vous assurer que les données sont conservées aussi près que possible. possible aux cœurs du GPU. Les GPU disposent ainsi de certaines zones mémoire (appelées «mémoire locale» ou «mémoire partagée») qui ne font généralement que quelques Ko, mais particulièrement efficaces pour les données sur le point d'être impliquées dans un calcul.

Donc, pour le souligner à nouveau: la programmation GPU est un art, qui n'est lié qu'à distance à la programmation parallèle sur le CPU. Des choses comme Threads en Java, avec toute l'infrastructure de concurrence comme ThreadPoolExecutors, ForkJoinPoolsetc. peuvent donner l'impression que vous devez simplement diviser votre travail d'une manière ou d'une autre et le répartir entre plusieurs processeurs. Sur le GPU, vous pouvez rencontrer des défis à un niveau beaucoup plus bas: occupation, pression de registre, pression de mémoire partagée, fusion de mémoire ... pour n'en nommer que quelques-uns.

Cependant, lorsque vous avez un problème lié au calcul et parallèle aux données à résoudre, le GPU est la solution.


Une remarque générale: vous avez spécifiquement demandé CUDA. Mais je vous recommande fortement de jeter également un œil à OpenCL. Il présente plusieurs avantages. Tout d'abord, il s'agit d'une norme industrielle ouverte et indépendante du fournisseur, et il existe des implémentations d'OpenCL par AMD, Apple, Intel et NVIDIA. De plus, il existe une prise en charge beaucoup plus large d'OpenCL dans le monde Java. Le seul cas où je préfère me contenter de CUDA est lorsque vous souhaitez utiliser les bibliothèques d'exécution CUDA, comme CUFFT pour FFT ou CUBLAS pour BLAS (opérations Matrix / Vector). Bien qu'il existe des approches pour fournir des bibliothèques similaires pour OpenCL, elles ne peuvent pas être utilisées directement du côté Java, sauf si vous créez vos propres liaisons JNI pour ces bibliothèques.


Vous pourriez également trouver intéressant d'entendre qu'en octobre 2012, le groupe OpenJDK HotSpot a lancé le projet "Sumatra": http://openjdk.java.net/projects/sumatra/ . Le but de ce projet est de fournir un support GPU directement dans la JVM, avec le support du JIT. Le statut actuel et les premiers résultats peuvent être vus dans leur liste de diffusion à http://mail.openjdk.java.net/mailman/listinfo/sumatra-dev


Cependant, il y a quelque temps, j'ai rassemblé quelques ressources liées à "Java sur le GPU" en général. Je vais les résumer à nouveau ici, sans ordre particulier.

( Avertissement : je suis l'auteur de http://jcuda.org/ et http://jocl.org/ )

Traduction de code (octet) et génération de code OpenCL:

https://github.com/aparapi/aparapi : une bibliothèque open-source créée et activement maintenue par AMD. Dans une classe spéciale "Kernel", on peut surcharger une méthode spécifique qui doit être exécutée en parallèle. Le code d'octet de cette méthode est chargé lors de l'exécution à l'aide d'un propre lecteur de bytecode. Le code est traduit en code OpenCL, qui est ensuite compilé à l'aide du compilateur OpenCL. Le résultat peut ensuite être exécuté sur le périphérique OpenCL, qui peut être un GPU ou un CPU. Si la compilation dans OpenCL n'est pas possible (ou si aucun OpenCL n'est disponible), le code sera toujours exécuté en parallèle, en utilisant un Thread Pool.

https://github.com/pcpratts/rootbeer1 : une bibliothèque open-source pour convertir des parties de Java en programmes CUDA. Il propose des interfaces dédiées qui peuvent être implémentées pour indiquer qu'une certaine classe doit être exécutée sur le GPU. Contrairement à Aparapi, il essaie de sérialiser automatiquement les données «pertinentes» (c'est-à-dire la partie pertinente complète du graphe d'objets!) En une représentation adaptée au GPU.

https://code.google.com/archive/p/java-gpu/ : une bibliothèque pour traduire du code Java annoté (avec certaines limitations) en code CUDA, qui est ensuite compilé dans une bibliothèque qui exécute le code sur le GPU. La bibliothèque a été développée dans le cadre d'une thèse de doctorat, qui contient des informations de base approfondies sur le processus de traduction.

https://github.com/ochafik/ScalaCL : liaisons Scala pour OpenCL. Permet aux collections Scala spéciales d'être traitées en parallèle avec OpenCL. Les fonctions qui sont appelées sur les éléments des collections peuvent être des fonctions Scala habituelles (avec quelques limitations) qui sont ensuite traduites en noyaux OpenCL.

Extensions de langue

http://www.ateji.com/px/index.html : Une extension de langage pour Java qui permet des constructions parallèles (par exemple des boucles for parallèles, style OpenMP) qui sont ensuite exécutées sur le GPU avec OpenCL. Malheureusement, ce projet très prometteur n'est plus maintenu.

http://www.habanero.rice.edu/Publications.html (JCUDA): Une bibliothèque qui peut traduire du code Java spécial (appelé code JCUDA) en code Java et CUDA-C, qui peut ensuite être compilé et exécuté sur le GPU. Cependant, la bibliothèque ne semble pas être accessible au public.

https://www2.informatik.uni-erlangen.de/EN/research/JavaOpenMP/index.html : extension de langage Java pour les constructions OpenMP, avec un backend CUDA

Bibliothèques de liaison Java OpenCL / CUDA

https://github.com/ochafik/JavaCL : liaisons Java pour OpenCL: bibliothèque OpenCL orientée objet, basée sur des liaisons de bas niveau générées automatiquement

http://jogamp.org/jocl/www/ : liaisons Java pour OpenCL: bibliothèque OpenCL orientée objet, basée sur des liaisons de bas niveau générées automatiquement

http://www.lwjgl.org/ : liaisons Java pour OpenCL: liaisons de bas niveau générées automatiquement et classes de commodité orientées objet

http://jocl.org/ : liaisons Java pour OpenCL: liaisons de bas niveau qui sont un mappage 1: 1 de l'API OpenCL d'origine

http://jcuda.org/ : liaisons Java pour CUDA: liaisons de bas niveau qui sont un mappage 1: 1 de l'API CUDA d'origine

Divers

http://sourceforge.net/projects/jopencl/ : liaisons Java pour OpenCL. Semblent ne plus être entretenus depuis 2010

http://www.hoopoe-cloud.com/ : liaisons Java pour CUDA. Semblent ne plus être entretenu



considérons une opération d'ajout de 2 matrices et de stockage du résultat dans une troisième matrice. Lorsque mutli threadé sur CPU sans OpenCL, le goulot d'étranglement sera toujours l'étape au cours de laquelle l'ajout se produit. Cette opération est évidemment parallèle aux données. Mais disons que nous ne savons pas s'il sera lié au calcul ou à la mémoire au préalable. Il faut beaucoup de temps et de ressources pour mettre en œuvre et voir ensuite que le processeur est bien meilleur pour effectuer cette opération. Alors, comment identifier à l'avance cela sans implémenter le code OpenCL.
Cool_Coder

2
@Cool_Coder En effet, il est difficile de dire à l'avance si (ou dans quelle mesure) une certaine tâche bénéficiera d'une implémentation GPU. Pour un premier instinct, il faut probablement une certaine expérience avec différents cas d'utilisation (ce que je n'ai certes pas vraiment). Une première étape pourrait être de regarder nvidia.com/object/cuda_showcase_html.html et de voir s'il existe un problème «similaire» répertorié. (C'est CUDA, mais il est conceptuellement si proche d'OpenCL que les résultats peuvent être transférés dans la plupart des cas). Dans la plupart des cas, l'accélération est également mentionnée, et beaucoup d'entre eux ont des liens vers des articles ou même du code
Marco13

+1 pour aparapi - c'est un moyen simple de démarrer avec opencl en java, et vous permet de comparer facilement les performances du processeur et du GPU pour des cas simples. En outre, il est maintenu par AMD mais fonctionne bien avec les cartes Nvidia.
steve cook

12
C'est l'une des meilleures réponses que j'ai jamais vues sur StackOverflow. Merci pour le temps et les efforts!
ViggyNash

1
@AlexPunnen Cela dépasse probablement la portée des commentaires. Autant que je sache, OpenCV a un support CUDA, à partir de docs.opencv.org/2.4/modules/gpu/doc/introduction.html . Le developer.nvidia.com/npp dispose de nombreuses routines de traitement d'image, qui peuvent être utiles. Et github.com/GPUOpen-ProfessionalCompute-Tools/HIP peut être une «alternative» pour CUDA. Il serait peut- être possible de poser cela comme une nouvelle question, mais il faut faire attention à la formuler correctement, pour éviter les votes négatifs pour "basé sur l'opinion" / "demander des bibliothèques tierces" ...
Marco13


2

D'après les recherches que j'ai faites, si vous ciblez les GPU Nvidia et avez décidé d'utiliser CUDA sur OpenCL , j'ai trouvé trois façons d'utiliser l'API CUDA en java.

  1. JCuda (ou alternative) - http://www.jcuda.org/ . Cela semble être la meilleure solution aux problèmes sur lesquels je travaille. De nombreuses bibliothèques telles que CUBLAS sont disponibles dans JCuda. Cependant, les noyaux sont toujours écrits en C.
  2. JNI - Les interfaces JNI ne sont pas mes préférées pour écrire, mais elles sont très puissantes et vous permettraient de faire tout ce que CUDA peut faire.
  3. JavaCPP - Cela vous permet essentiellement de créer une interface JNI en Java sans écrire directement de code C. Voici un exemple: Quelle est la manière la plus simple d'exécuter du code CUDA fonctionnel en Java? comment l'utiliser avec la poussée CUDA. Pour moi, cela semble que vous pourriez aussi bien écrire une interface JNI.

Toutes ces réponses ne sont fondamentalement que des moyens d'utiliser le code C / C ++ en Java. Vous devriez vous demander pourquoi vous devez utiliser Java et si vous ne pouvez pas le faire en C / C ++ à la place.

Si vous aimez Java et savez comment l'utiliser et que vous ne voulez pas travailler avec toute la gestion des pointeurs et ce qui n'est pas fourni avec C / C ++, JCuda est probablement la réponse. D'un autre côté, la bibliothèque CUDA Thrust et d'autres bibliothèques comme elle peuvent être utilisées pour faire une grande partie de la gestion des pointeurs en C / C ++ et peut-être devriez-vous regarder cela.

Si vous aimez C / C ++ et que la gestion des pointeurs ne vous dérange pas, mais qu'il existe d'autres contraintes qui vous obligent à utiliser Java, JNI pourrait être la meilleure approche. Cependant, si vos méthodes JNI ne sont que des wrappers pour les commandes du noyau, vous pouvez tout aussi bien utiliser JCuda.

Il existe quelques alternatives à JCuda telles que Cuda4J et Root Beer, mais celles-ci ne semblent pas être maintenues. Alors qu'au moment de la rédaction de cet article, ce JCuda prend en charge CUDA 10.1. qui est le SDK CUDA le plus à jour.

De plus, il existe quelques bibliothèques java qui utilisent CUDA, telles que deeplearning4j et Hadoop, qui peuvent être en mesure de faire ce que vous recherchez sans vous obliger à écrire directement le code du noyau. Je ne les ai pas trop examinés cependant.


1

Marco13 a déjà fourni une excellente réponse .

Dans le cas où vous êtes à la recherche d'un moyen d'utiliser le GPU sans implémenter les noyaux CUDA / OpenCL, je voudrais ajouter une référence à finmath-lib-cuda-extensions (finmath-lib-gpu-extensions) http: // finmath .net / finmath-lib-cuda-extensions / (avertissement: je suis le mainteneur de ce projet).

Le projet fournit une implémentation de "classes vectorielles", pour être précis, une interface appelée RandomVariable, qui fournit des opérations arithmétiques et une réduction sur les vecteurs. Il existe des implémentations pour le CPU et le GPU. Il existe des implémentations utilisant la différenciation algorithmique ou des évaluations simples.

Les améliorations de performances sur le GPU sont actuellement faibles (mais pour les vecteurs de taille 100.000, vous pouvez obtenir un facteur> 10 améliorations de performances). Cela est dû à la petite taille du noyau. Cela s'améliorera dans une version future.

L'implémentation GPU utilise JCuda et JOCL et est disponible pour les GPU Nvidia et ATI.

La bibliothèque est Apache 2.0 et disponible via Maven Central.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.