Utilizzo di Java con GPU Nvidia (CUDA)


144

Sto lavorando a un progetto commerciale realizzato in Java e che ha bisogno di un enorme potere di calcolo per calcolare i mercati aziendali. Semplice matematica, ma con un'enorme quantità di dati.

Abbiamo ordinato alcune GPU CUDA per provarlo e poiché Java non è supportato da CUDA, mi chiedo da dove cominciare. Devo creare un'interfaccia JNI? Dovrei usare JCUDA o ci sono altri modi?

Non ho esperienza in questo campo e vorrei che qualcuno potesse indirizzarmi verso qualcosa in modo da poter iniziare a ricercare e apprendere.


2
Le GPU ti aiuteranno ad accelerare tipi specifici di problemi ad alta intensità di calcolo. Tuttavia, se disponi di un'enorme quantità di dati, è più probabile che tu sia legato all'IO. Molto probabilmente le GPU non sono la soluzione.
Steve Cook,

1
"Aumentare le prestazioni Java usando GPGPU" -> arxiv.org/abs/1508.06791
BlackBear

4
Sono una domanda aperta, sono contento che le mod non abbiano chiuso perché la risposta di Marco13 è incredibilmente utile! Dovrebbe essere un wiki IMHO
JimLohse,

Risposte:


442

Prima di tutto, dovresti essere consapevole del fatto che CUDA non renderà automaticamente i calcoli più veloci. Da un lato, perché la programmazione GPU è un'arte e può essere molto, molto impegnativo, farlo nel modo giusto . D'altra parte, poiché le GPU sono adatte solo per determinati tipi di calcoli.

Questo può sembrare confuso, perché puoi praticamente calcolare qualsiasi cosa sulla GPU. Il punto chiave è, ovviamente, se raggiungerai una buona velocità o no. La classificazione più importante qui è se un problema è il task parallelo o il parallelismo dei dati . Il primo si riferisce, approssimativamente, a problemi in cui diversi thread stanno lavorando ai propri compiti, più o meno in modo indipendente. Il secondo si riferisce a problemi in cui molti thread fanno tutti lo stesso , ma su parti diverse dei dati.

Quest'ultimo è il tipo di problema in cui le GPU sono brave: hanno molti core e tutti i core fanno lo stesso, ma operano su parti diverse dei dati di input.

Hai detto che hai "matematica semplice ma con un'enorme quantità di dati". Sebbene questo possa sembrare un problema perfettamente parallelo ai dati e quindi adatto per una GPU, c'è un altro aspetto da considerare: le GPU sono ridicolmente veloci in termini di potenza computazionale teorica (FLOPS, Floating Point Operations al secondo). Ma sono spesso limitati dalla larghezza di banda della memoria.

Questo porta ad un'altra classificazione dei problemi. Vale a dire se i problemi sono associati alla memoria o ai calcoli .

Il primo si riferisce ai problemi in cui il numero di istruzioni eseguite per ciascun elemento di dati è basso. Ad esempio, considera un'aggiunta vettoriale parallela: dovrai leggere due elementi di dati, quindi eseguire una singola aggiunta e quindi scrivere la somma nel vettore risultato. Non vedrai uno speedup quando lo fai sulla GPU, perché la singola aggiunta non compensa gli sforzi di leggere / scrivere la memoria.

Il secondo termine, "limite di calcolo", si riferisce a problemi in cui il numero di istruzioni è elevato rispetto al numero di letture / scritture di memoria. Ad esempio, considera una moltiplicazione di matrici: il numero di istruzioni sarà O (n ^ 3) quando n è la dimensione della matrice. In questo caso, ci si può aspettare che la GPU supererà una CPU con una certa dimensione della matrice. Un altro esempio potrebbe essere quando molti calcoli trigonometrici complessi (seno / coseno ecc.) Vengono eseguiti su "pochi" elementi di dati.

Come regola generale: si può presumere che la lettura / scrittura di un elemento di dati dalla memoria GPU "principale" abbia una latenza di circa 500 istruzioni ....

Pertanto, un altro punto chiave per le prestazioni delle GPU è la localizzazione dei dati : se devi leggere o scrivere dati (e nella maggior parte dei casi, dovrai ;-)), allora dovresti assicurarti che i dati siano mantenuti il ​​più vicino possibile possibile ai core della GPU. Le GPU hanno quindi determinate aree di memoria (denominate "memoria locale" o "memoria condivisa") che di solito hanno solo pochi KB, ma sono particolarmente efficienti per i dati che stanno per essere coinvolti in un calcolo.

Quindi, per sottolineare ancora una volta: la programmazione GPU è un'arte, che è solo lontanamente correlata alla programmazione parallela sulla CPU. Cose come Thread in Java, con tutte le infrastrutture di concorrenza come ThreadPoolExecutors, ForkJoinPoolsecc. Potrebbero dare l'impressione che devi solo dividere il tuo lavoro in qualche modo e distribuirlo tra più processori. Sulla GPU, potresti incontrare sfide a un livello molto più basso: occupazione, pressione del registro, pressione della memoria condivisa, coalescenza della memoria ... solo per citarne alcuni.

Tuttavia, quando si deve risolvere un problema parallelo ai dati e legato al calcolo, la GPU è la strada da percorrere.


Un'osservazione generale: hai chiesto specificamente CUDA. Ma ti consiglio vivamente di dare un'occhiata anche a OpenCL. Ha diversi vantaggi. Prima di tutto, è uno standard industriale aperto indipendente dal fornitore e ci sono implementazioni di OpenCL di AMD, Apple, Intel e NVIDIA. Inoltre, esiste un supporto molto più ampio per OpenCL nel mondo Java. L'unico caso in cui preferirei accontentarmi di CUDA è quando si desidera utilizzare le librerie di runtime CUDA, come CUFFT per FFT o CUBLAS per BLAS (operazioni Matrix / Vector). Sebbene esistano approcci per fornire librerie simili per OpenCL, non possono essere utilizzate direttamente dal lato Java, a meno che non si creino i propri collegamenti JNI per queste librerie.


Potresti anche trovare interessante sapere che nell'ottobre 2012, il gruppo OpenJDK HotSpot ha avviato il progetto "Sumatra": http://openjdk.java.net/projects/sumatra/ . L'obiettivo di questo progetto è fornire supporto GPU direttamente nella JVM, con il supporto della JIT. Lo stato corrente e i primi risultati possono essere visualizzati nella loro mailing list su http://mail.openjdk.java.net/mailman/listinfo/sumatra-dev


Tuttavia, qualche tempo fa, ho raccolto alcune risorse relative a "Java sulla GPU" in generale. Le riassumerò di nuovo qui, in nessun ordine particolare.

( Dichiarazione di non responsabilità : sono l'autore di http://jcuda.org/ e http://jocl.org/ )

(Byte) traduzione del codice e generazione del codice OpenCL:

https://github.com/aparapi/aparapi : una libreria open source creata e gestita attivamente da AMD. In una speciale classe "Kernel", si può sovrascrivere un metodo specifico che dovrebbe essere eseguito in parallelo. Il codice byte di questo metodo viene caricato in fase di esecuzione utilizzando un proprio lettore bytecode. Il codice viene tradotto in codice OpenCL, che viene quindi compilato utilizzando il compilatore OpenCL. Il risultato può quindi essere eseguito sul dispositivo OpenCL, che può essere una GPU o una CPU. Se la compilazione in OpenCL non è possibile (o non è disponibile OpenCL), il codice verrà comunque eseguito in parallelo, utilizzando un pool di thread.

https://github.com/pcpratts/rootbeer1 : una libreria open source per convertire parti di Java in programmi CUDA. Offre interfacce dedicate che possono essere implementate per indicare che una determinata classe deve essere eseguita sulla GPU. A differenza di Aparapi, tenta di serializzare automaticamente i dati "rilevanti" (ovvero la parte rilevante completa del grafico oggetto!) In una rappresentazione adatta alla GPU.

https://code.google.com/archive/p/java-gpu/ : una libreria per la traduzione di codice Java annotato (con alcune limitazioni) in codice CUDA, che viene quindi compilato in una libreria che esegue il codice sulla GPU. La Biblioteca è stata sviluppata nel contesto di una tesi di dottorato, che contiene profonde informazioni di base sul processo di traduzione.

https://github.com/ochafik/ScalaCL : attacchi Scala per OpenCL. Consente l'elaborazione di raccolte Scala speciali in parallelo con OpenCL. Le funzioni che vengono chiamate sugli elementi delle raccolte possono essere le normali funzioni Scala (con alcune limitazioni) che vengono poi tradotte in kernel OpenCL.

Estensioni di lingua

http://www.ateji.com/px/index.html : un'estensione di linguaggio per Java che consente costrutti paralleli (ad es. parallelo per loop, stile OpenMP) che vengono quindi eseguiti sulla GPU con OpenCL. Sfortunatamente, questo progetto molto promettente non viene più mantenuto.

http://www.habanero.rice.edu/Publications.html (JCUDA): una libreria in grado di tradurre codice Java speciale (chiamato codice JCUDA) in codice Java e CUDA-C, che può quindi essere compilato ed eseguito sul GPU. Tuttavia, la libreria non sembra essere disponibile al pubblico.

https://www2.informatik.uni-erlangen.de/EN/research/JavaOpenMP/index.html : estensione del linguaggio Java per costrutti OpenMP, con un backend CUDA

Librerie di associazione Java OpenCL / CUDA

https://github.com/ochafik/JavaCL : collegamenti Java per OpenCL: una libreria OpenCL orientata agli oggetti, basata su collegamenti di basso livello generati automaticamente

http://jogamp.org/jocl/www/ : collegamenti Java per OpenCL: una libreria OpenCL orientata agli oggetti, basata su collegamenti di basso livello generati automaticamente

http://www.lwjgl.org/ : Collegamenti Java per OpenCL: collegamenti di basso livello generati automaticamente e classi di convenienza orientate agli oggetti

http://jocl.org/ : collegamenti Java per OpenCL: collegamenti di basso livello che sono una mappatura 1: 1 dell'API OpenCL originale

http://jcuda.org/ : Collegamenti Java per CUDA: collegamenti di basso livello che sono una mappatura 1: 1 dell'API CUDA originale

miscellaneo

http://sourceforge.net/projects/jopencl/ : collegamenti Java per OpenCL. Sembra non essere più mantenuto dal 2010

http://www.hoopoe-cloud.com/ : collegamenti Java per CUDA. Sembra non essere più mantenuto



considerare un'operazione di aggiunta di 2 matrici e memorizzazione del risultato in una terza matrice. Quando si esegue il threading multiplo su CPU senza OpenCL, il collo di bottiglia sarà sempre il passaggio in cui avviene l'aggiunta. Questa operazione è ovviamente dati paralleli. Ma diciamo che non sappiamo se sarà associato in anticipo al calcolo o alla memoria. Ci vogliono molto tempo e risorse per implementare e quindi vedere che la CPU è molto meglio nel fare questa operazione. Quindi, come si può identificare in anticipo questo senza implementare il codice OpenCL.
Cool_Coder

2
@Cool_Coder In effetti è difficile dire in anticipo se (o quanto) una determinata attività trarrà beneficio da un'implementazione GPU. Per la prima volta, probabilmente è necessaria una certa esperienza con diversi casi d'uso (che devo ammettere che in realtà non ho). Un primo passo potrebbe essere quello di guardare nvidia.com/object/cuda_showcase_html.html e vedere se c'è un problema "simile" elencato. (È CUDA, ma concettualmente è così vicino a OpenCL che i risultati possono essere trasferiti nella maggior parte dei casi). Nella maggior parte dei casi, viene anche menzionato lo speedup, e molti di loro hanno collegamenti a documenti o persino a codice
Marco13

+1 per aparapi: è un modo semplice per iniziare a utilizzare Opencl in Java e ti consente di confrontare facilmente le prestazioni CPU vs GPU per casi semplici. Inoltre, è gestito da AMD ma funziona bene con le schede Nvidia.
Steve Cook,

12
Questa è una delle migliori risposte che abbia mai visto su StackOverflow. Grazie per il tempo e per lo sforzo!
ViggyNash,

1
@AlexPunnen Questo probabilmente va oltre lo scopo dei commenti. Per quanto ne so, OpenCV ha del supporto CUDA, come docs.opencv.org/2.4/modules/gpu/doc/introduction.html . Il developer.nvidia.com/npp ha molte routine di elaborazione di immagini, che possono essere a portata di mano. E github.com/GPUOpen-ProfessionalCompute-Tools/HIP potrebbe essere una "alternativa" per CUDA. Si potrebbe essere possibile chiedere questo come una nuova domanda, ma bisogna stare attenti a frase in modo corretto, per evitare per downvotes "parere sulla base" / "per chiedere librerie di terze parti" ...
Marco13


2

Dalla ricerca che ho fatto, se stai prendendo di mira le GPU Nvidia e hai deciso di utilizzare CUDA su OpenCL , ho trovato tre modi per utilizzare l'API CUDA in java.

  1. JCuda (o alternativa) - http://www.jcuda.org/ . Questa sembra la soluzione migliore per i problemi a cui sto lavorando. Molte librerie come CUBLAS sono disponibili in JCuda. I kernel sono comunque ancora scritti in C.
  2. JNI - Le interfacce JNI non sono le mie preferite da scrivere, ma sono molto potenti e ti permetterebbero di fare qualsiasi cosa CUDA possa fare.
  3. JavaCPP - Questo in sostanza ti consente di creare un'interfaccia JNI in Java senza scrivere direttamente il codice C. Ecco un esempio: qual è il modo più semplice per eseguire il codice CUDA funzionante in Java? di come usarlo con la spinta di CUDA. Per me, sembra che potresti anche scrivere un'interfaccia JNI.

Tutte queste risposte sono fondamentalmente solo modi di usare il codice C / C ++ in Java. Dovresti chiederti perché devi usare Java e se invece non puoi farlo in C / C ++.

Se ti piace Java e sai come usarlo e non vuoi lavorare con tutta la gestione del puntatore e quant'altro che viene fornito con C / C ++, probabilmente JCuda è la risposta. D'altra parte, la libreria CUDA Thrust e altre librerie simili possono essere usate per fare molta gestione del puntatore in C / C ++ e forse dovresti guardarlo.

Se ti piace C / C ++ e non ti preoccupi della gestione dei puntatori, ma ci sono altri vincoli che ti costringono a usare Java, allora JNI potrebbe essere l'approccio migliore. Tuttavia, se i tuoi metodi JNI saranno semplicemente wrapper per i comandi del kernel, potresti anche usare JCuda.

Ci sono alcune alternative a JCuda come Cuda4J e Root Beer, ma quelle non sembrano essere mantenute. Considerando che al momento della stesura di questo documento, JCuda supporta CUDA 10.1. che è l'SDK CUDA più aggiornato.

Inoltre ci sono alcune librerie Java che usano CUDA, come deeplearning4j e Hadoop, che potrebbero essere in grado di fare ciò che stai cercando senza che tu debba scrivere direttamente il codice del kernel. Non ho esaminato troppo in loro però.


1

Marco13 ha già fornito un'ottima risposta .

Se stai cercando un modo per usare la GPU senza implementare i kernel CUDA / OpenCL, vorrei aggiungere un riferimento alle estensioni finmath-lib-cuda (finmath-lib-gpu-extensions) http: // finmath .net / finmath-lib-cuda-extensions / (disclaimer: sono il manutentore di questo progetto).

Il progetto prevede un'implementazione di "classi vettoriali", per essere precisi, un'interfaccia chiamata RandomVariable, che fornisce operazioni aritmetiche e riduzione sui vettori. Esistono implementazioni per CPU e GPU. Ci sono implementazioni usando differenziazione algoritmica o valutazioni semplici.

I miglioramenti delle prestazioni sulla GPU sono attualmente piccoli (ma per i vettori di dimensioni 100.000 è possibile ottenere un fattore> 10 miglioramenti delle prestazioni). Ciò è dovuto alle dimensioni ridotte del kernel. Ciò migliorerà in una versione futura.

L'implementazione della GPU utilizza JCuda e JOCL e sono disponibili per le GPU Nvidia e ATI.

La libreria è Apache 2.0 e disponibile tramite Maven Central.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.