Uso de Java con GPU Nvidia (CUDA)

144

Estoy trabajando en un proyecto empresarial que se realiza en Java, y necesita una gran potencia de cálculo para calcular los mercados empresariales. Matemáticas simples, pero con gran cantidad de datos.

Pedimos algunas GPU CUDA para probarlo y dado que Java no es compatible con CUDA, me pregunto por dónde empezar. ¿Debo construir una interfaz JNI? ¿Debo usar JCUDA o hay otras formas?

No tengo experiencia en este campo y me gustaría que alguien pudiera dirigirme a algo para que pueda comenzar a investigar y aprender.

Hans
fuente
2
Las GPU lo ayudarán a acelerar tipos específicos de problemas de computación intensiva. Sin embargo, si tiene una gran cantidad de datos, es más probable que esté vinculado a IO. Lo más probable es que las GPU no sean la solución.
Steve Cook
1
"Aumento del rendimiento de Java utilizando GPGPU" -> arxiv.org/abs/1508.06791
BlackBear
44
Como una pregunta abierta, me alegro de que los mods no lo cerraron porque la respuesta de Marco13 es increíblemente útil. Debería ser una wiki en mi humilde opinión
JimLohse

Respuestas:

442

En primer lugar, debe tener en cuenta el hecho de que CUDA no hará que los cálculos sean más rápidos de forma automática. Por un lado, porque la programación de GPU es un arte, y puede ser muy, muy difícil hacerlo bien . Por otro lado, porque las GPU son adecuadas solo para ciertos tipos de cálculos.

Esto puede sonar confuso, porque básicamente puedes calcular cualquier cosa en la GPU. El punto clave es, por supuesto, si logrará una buena aceleración o no. La clasificación más importante aquí es si un problema es tarea paralela o datos paralelos . El primero se refiere, en términos generales, a problemas en los que varios subprocesos están trabajando en sus propias tareas, de manera más o menos independiente. El segundo se refiere a problemas en los que muchos subprocesos hacen lo mismo , pero en diferentes partes de los datos.

Este último es el tipo de problema en el que las GPU son buenas: tienen muchos núcleos y todos los núcleos hacen lo mismo, pero operan en diferentes partes de los datos de entrada.

Usted mencionó que tiene "matemática simple pero con gran cantidad de datos". Aunque esto puede sonar como un problema perfectamente paralelo a los datos y, por lo tanto, como si fuera adecuado para una GPU, hay otro aspecto a considerar: las GPU son ridículamente rápidas en términos de potencia computacional teórica (FLOPS, operaciones de punto flotante por segundo). Pero a menudo se ven limitados por el ancho de banda de la memoria.

Esto lleva a otra clasificación de problemas. Es decir, si los problemas están vinculados a la memoria o al cálculo .

El primero se refiere a problemas en los que el número de instrucciones que se realizan para cada elemento de datos es bajo. Por ejemplo, considere una suma de vectores paralelos: tendrá que leer dos elementos de datos, luego realizar una sola suma y luego escribir la suma en el vector de resultados. No verá una aceleración al hacer esto en la GPU, porque la única adición no compensa los esfuerzos de lectura / escritura de la memoria.

El segundo término, "límite de cálculo", se refiere a problemas donde el número de instrucciones es alto en comparación con el número de lecturas / escrituras de memoria. Por ejemplo, considere una multiplicación de matriz: el número de instrucciones será O (n ^ 3) cuando n es el tamaño de la matriz. En este caso, uno puede esperar que la GPU supere a una CPU en un determinado tamaño de matriz. Otro ejemplo podría ser cuando se realizan muchos cálculos trigonométricos complejos (seno / coseno, etc.) en "pocos" elementos de datos.

Como regla general: puede suponer que leer / escribir un elemento de datos de la memoria de la GPU "principal" tiene una latencia de aproximadamente 500 instrucciones ...

Por lo tanto, otro punto clave para el rendimiento de las GPU es la localidad de los datos : si tiene que leer o escribir datos (y en la mayoría de los casos, deberá hacerlo ;-)), entonces debe asegurarse de que los datos se mantengan tan cerca como sea posible. posible a los núcleos de GPU. Por lo tanto, las GPU tienen ciertas áreas de memoria (denominadas "memoria local" o "memoria compartida") que generalmente tienen un tamaño de unos pocos KB, pero son particularmente eficientes para los datos que están a punto de participar en un cálculo.

Entonces, para enfatizar esto nuevamente: la programación de GPU es un arte, que solo está remotamente relacionado con la programación paralela en la CPU. Cosas como subprocesos en Java, con toda la infraestructura de concurrencia como ThreadPoolExecutors, ForkJoinPoolsetc. , pueden dar la impresión de que solo tiene que dividir su trabajo de alguna manera y distribuirlo entre varios procesadores. En la GPU, puede encontrar desafíos en un nivel mucho más bajo: ocupación, presión de registro, presión de memoria compartida, fusión de memoria ... solo por nombrar algunos.

Sin embargo, cuando tiene que resolver un problema de cómputo de datos en paralelo, la GPU es el camino a seguir.


Una observación general: Su pedido específico de CUDA. Pero le recomiendo que también eche un vistazo a OpenCL. Tiene varias ventajas. En primer lugar, es un estándar de la industria abierto e independiente del proveedor, y hay implementaciones de OpenCL de AMD, Apple, Intel y NVIDIA. Además, hay un soporte mucho más amplio para OpenCL en el mundo Java. El único caso en el que prefiero conformarme con CUDA es cuando desea usar las bibliotecas de tiempo de ejecución CUDA, como CUFFT para FFT o CUBLAS para BLAS (operaciones de matriz / vector). Aunque existen enfoques para proporcionar bibliotecas similares para OpenCL, no se pueden usar directamente desde Java, a menos que cree sus propios enlaces JNI para estas bibliotecas.


También puede resultarle interesante escuchar que en octubre de 2012, el grupo OpenJDK HotSpot inició el proyecto "Sumatra": http://openjdk.java.net/projects/sumatra/ . El objetivo de este proyecto es proporcionar soporte de GPU directamente en la JVM, con soporte del JIT. El estado actual y los primeros resultados se pueden ver en su lista de correo en http://mail.openjdk.java.net/mailman/listinfo/sumatra-dev


Sin embargo, hace un tiempo, recopilé algunos recursos relacionados con "Java en la GPU" en general. Resumiré esto nuevamente aquí, sin ningún orden en particular.

( Descargo de responsabilidad : soy el autor de http://jcuda.org/ y http://jocl.org/ )

(Byte) traducción de código y generación de código OpenCL:

https://github.com/aparapi/aparapi : una biblioteca de código abierto creada y mantenida activamente por AMD. En una clase especial "Kernel", uno puede anular un método específico que debe ejecutarse en paralelo. El código de bytes de este método se carga en tiempo de ejecución utilizando un lector de código de bytes propio. El código se traduce en código OpenCL, que luego se compila utilizando el compilador OpenCL. El resultado se puede ejecutar en el dispositivo OpenCL, que puede ser una GPU o una CPU. Si la compilación en OpenCL no es posible (o no hay OpenCL disponible), el código aún se ejecutará en paralelo, utilizando un Thread Pool.

https://github.com/pcpratts/rootbeer1 : una biblioteca de código abierto para convertir partes de Java en programas CUDA. Ofrece interfaces dedicadas que pueden implementarse para indicar que una determinada clase debe ejecutarse en la GPU. A diferencia de Aparapi, intenta serializar automáticamente los datos "relevantes" (es decir, ¡la parte relevante completa del gráfico de objetos!) En una representación adecuada para la GPU.

https://code.google.com/archive/p/java-gpu/ : una biblioteca para traducir código Java anotado (con algunas limitaciones) en código CUDA, que luego se compila en una biblioteca que ejecuta el código en la GPU. La Biblioteca se desarrolló en el contexto de una tesis doctoral, que contiene información de fondo profunda sobre el proceso de traducción.

https://github.com/ochafik/ScalaCL : enlaces Scala para OpenCL. Permite procesar colecciones especiales de Scala en paralelo con OpenCL. Las funciones que se invocan en los elementos de las colecciones pueden ser funciones Scala habituales (con algunas limitaciones) que luego se traducen en núcleos OpenCL.

Extensiones de idioma

http://www.ateji.com/px/index.html : una extensión de lenguaje para Java que permite construcciones paralelas (por ejemplo, paralelas para bucles, estilo OpenMP) que luego se ejecutan en la GPU con OpenCL. Desafortunadamente, este proyecto muy prometedor ya no se mantiene.

http://www.habanero.rice.edu/Publications.html (JCUDA): una biblioteca que puede traducir código Java especial (llamado código JCUDA) en código Java y CUDA-C, que luego se puede compilar y ejecutar en el GPU Sin embargo, la biblioteca no parece estar disponible públicamente.

https://www2.informatik.uni-erlangen.de/EN/research/JavaOpenMP/index.html : extensión de lenguaje Java para construcciones OpenMP, con un backend CUDA

Bibliotecas de enlace Java OpenCL / CUDA

https://github.com/ochafik/JavaCL : enlaces de Java para OpenCL: una biblioteca OpenCL orientada a objetos, basada en enlaces de bajo nivel autogenerados

http://jogamp.org/jocl/www/ : enlaces de Java para OpenCL: una biblioteca OpenCL orientada a objetos, basada en enlaces de bajo nivel autogenerados

http://www.lwjgl.org/ : enlaces de Java para OpenCL: enlaces de bajo nivel autogenerados y clases de conveniencia orientadas a objetos

http://jocl.org/ : enlaces de Java para OpenCL: enlaces de bajo nivel que son una asignación 1: 1 de la API original de OpenCL

http://jcuda.org/ : enlaces de Java para CUDA: enlaces de bajo nivel que son una asignación 1: 1 de la API de CUDA original

Diverso

http://sourceforge.net/projects/jopencl/ : enlaces de Java para OpenCL. Parece que ya no se mantiene desde 2010

http://www.hoopoe-cloud.com/ : enlaces de Java para CUDA. Parece que ya no se mantiene


Marco13
fuente
considere una operación de agregar 2 matrices y almacenar el resultado en una tercera matriz. Cuando mutli se enhebra en la CPU sin OpenCL, el cuello de botella siempre será el paso en el que se realiza la adición. Esta operación es obviamente datos paralelos. Pero digamos que no sabemos si será un límite de cómputo o de memoria de antemano. Se necesita mucho tiempo y recursos para implementar y luego ver que la CPU es mucho mejor en esta operación. Entonces, ¿cómo se identifica de antemano esto sin implementar el código OpenCL?
Cool_Coder
2
@Cool_Coder De hecho, es difícil saber de antemano si (o cuánto) una determinada tarea se beneficiará de una implementación de GPU. Para un primer presentimiento, uno probablemente necesita algo de experiencia con diferentes casos de uso (lo cual, ciertamente, tampoco tengo). Un primer paso podría ser mirar nvidia.com/object/cuda_showcase_html.html y ver si hay un problema "similar" en la lista. (Es CUDA, pero conceptualmente está tan cerca de OpenCL que los resultados se pueden transferir en la mayoría de los casos). En la mayoría de los casos, también se menciona la aceleración, y muchos de ellos tienen enlaces a documentos o incluso código
Marco13
+1 para aparapi: es una forma sencilla de comenzar con opencl en java, y le permite comparar fácilmente el rendimiento de la CPU frente a la GPU para casos simples. Además, es mantenido por AMD pero funciona bien con tarjetas Nvidia.
Steve Cook
12
Esta es una de las mejores respuestas que he visto en StackOverflow. Gracias por el tiempo y esfuerzo!
ViggyNash
1
@AlexPunnen Esto probablemente esté más allá del alcance de los comentarios. Hasta donde yo sé, OpenCV tiene cierto soporte de CUDA, a partir de docs.opencv.org/2.4/modules/gpu/doc/introduction.html . El developer.nvidia.com/npp tiene muchas rutinas de procesamiento de imágenes, que pueden ser útiles. Y github.com/GPUOpen-ProfessionalCompute-Tools/HIP puede ser una "alternativa" para CUDA. Que podría ser posible pedir esto como una nueva pregunta, pero uno tiene que tener cuidado de expresarlo adecuadamente, a downvotes evitar para "basada opinión" / "pidiendo bibliotecas de terceros" ...
Marco13
2

De la investigación que hice, si está apuntando a las GPU Nvidia y ha decidido usar CUDA sobre OpenCL , encontré tres formas de usar la API de CUDA en Java.

  1. JCuda (o alternativa): http://www.jcuda.org/ . Esta parece ser la mejor solución para los problemas en los que estoy trabajando. Muchas de las bibliotecas como CUBLAS están disponibles en JCuda. Sin embargo, los núcleos todavía están escritos en C.
  2. JNI: las interfaces JNI no son mis favoritas para escribir, pero son muy potentes y le permitirán hacer cualquier cosa que CUDA pueda hacer.
  3. JavaCPP: básicamente le permite crear una interfaz JNI en Java sin escribir el código C directamente. Aquí hay un ejemplo: ¿Cuál es la forma más fácil de ejecutar el código CUDA en Java? de cómo usar esto con empuje CUDA. Para mí, parece que también podrías escribir una interfaz JNI.

Todas estas respuestas son básicamente formas de usar el código C / C ++ en Java. Debería preguntarse por qué necesita usar Java y si no puede hacerlo en C / C ++.

Si te gusta Java y sabes cómo usarlo y no quieres trabajar con toda la administración de punteros y qué no viene con C / C ++, entonces JCuda es probablemente la respuesta. Por otro lado, la biblioteca de CUDA Thrust y otras bibliotecas como esta se pueden usar para administrar mucho el puntero en C / C ++ y tal vez deberías mirar eso.

Si le gusta C / C ++ y no le importa la administración de punteros, pero existen otras restricciones que lo obligan a usar Java, entonces JNI podría ser el mejor enfoque. Sin embargo, si sus métodos JNI van a ser envoltorios para los comandos del núcleo, también podría usar JCuda.

Hay algunas alternativas a JCuda como Cuda4J y Root Beer, pero parece que no se mantienen. Mientras que al momento de escribir esto, JCuda es compatible con CUDA 10.1. que es el SDK de CUDA más actualizado.

Además, hay algunas bibliotecas de Java que usan CUDA, como deeplearning4j y Hadoop, que pueden hacer lo que está buscando sin requerir que escriba el código del kernel directamente. Sin embargo, no los he investigado demasiado.

David Griffin
fuente
1

Marco13 ya proporcionó una excelente respuesta .

En caso de que esté buscando una manera de usar la GPU sin implementar los núcleos CUDA / OpenCL, me gustaría agregar una referencia a las extensiones finmath-lib-cuda (finmath-lib-gpu-extensions) http: // finmath .net / finmath-lib-cuda-extensions / (descargo de responsabilidad: soy el responsable de este proyecto).

El proyecto proporciona una implementación de "clases de vectores", para ser precisos, una interfaz llamada RandomVariable, que proporciona operaciones aritméticas y reducción de vectores. Hay implementaciones para la CPU y la GPU. Hay implementación mediante diferenciación algorítmica o valoraciones simples.

Las mejoras de rendimiento en la GPU son actualmente pequeñas (pero para los vectores de tamaño 100.000 puede obtener un factor> 10 mejoras de rendimiento). Esto se debe a los pequeños tamaños de grano. Esto mejorará en una versión futura.

La implementación de GPU utiliza JCuda y JOCL y está disponible para GPU Nvidia y ATI.

La biblioteca es Apache 2.0 y está disponible a través de Maven Central.

Papas fritas cristianas
fuente