Usando Java com GPUs Nvidia (CUDA)

144

Estou trabalhando em um projeto comercial feito em Java, e ele precisa de um enorme poder de computação para calcular os mercados comerciais. Matemática simples, mas com grande quantidade de dados.

Pedimos algumas GPUs CUDA para experimentá-lo e, como o Java não é suportado pelo CUDA, estou pensando por onde começar. Devo construir uma interface JNI? Devo usar o JCUDA ou existem outras maneiras?

Não tenho experiência neste campo e gostaria que alguém me direcionasse para algo para que eu pudesse começar a pesquisar e aprender.

Hans
fonte
2
As GPUs o ajudarão a acelerar tipos específicos de problemas de uso intensivo de computação. No entanto, se você tiver uma quantidade enorme de dados, é mais provável que esteja vinculado a E / S. As GPUs mais prováveis ​​não são a solução.
Steve Cook
1
"Aumentando o desempenho do Java usando GPGPUs" -> arxiv.org/abs/1508.06791
BlackBear
4
Tipo de pergunta em aberto, fico feliz que os mods não tenham desligado porque a resposta de Marco13 é incrivelmente útil! Deve ser um wiki IMHO
JimLohse 28/11

Respostas:

442

Primeiro de tudo, você deve estar ciente do fato de que o CUDA não fará automagicamente os cálculos mais rapidamente. Por um lado, porque a programação da GPU é uma arte e pode ser muito, muito desafiador acertar . Por outro lado, porque GPUs são bem adaptados apenas para determinados tipos de cálculos.

Isso pode parecer confuso, porque você pode basicamente calcular qualquer coisa na GPU. O ponto chave é, é claro, se você conseguirá uma boa aceleração ou não. A classificação mais importante aqui é se um problema é paralelo a tarefas ou paralelo a dados . O primeiro refere-se, grosso modo, a problemas nos quais vários threads estão trabalhando em suas próprias tarefas, de forma mais ou menos independente. O segundo refere-se a problemas em que muitos threads estão fazendo o mesmo - mas em diferentes partes dos dados.

O último é o tipo de problema em que as GPUs são boas: elas têm muitos núcleos e todos os núcleos fazem o mesmo, mas operam em diferentes partes dos dados de entrada.

Você mencionou que tem "matemática simples, mas com grande quantidade de dados". Embora isso possa parecer um problema perfeitamente paralelo aos dados e, portanto, adequado para uma GPU, há outro aspecto a considerar: as GPUs são ridiculamente rápidas em termos de poder computacional teórico (FLOPS, operações de ponto flutuante por segundo). Mas eles geralmente são limitados pela largura de banda da memória.

Isso leva a outra classificação de problemas. Ou seja, se os problemas estão ligados à memória ou à computação .

O primeiro refere-se a problemas em que o número de instruções feitas para cada elemento de dados é baixo. Por exemplo, considere uma adição de vetor paralelo: você precisará ler dois elementos de dados, executar uma única adição e gravar a soma no vetor de resultado. Você não verá uma aceleração ao fazer isso na GPU, porque a adição única não compensa os esforços de leitura / gravação da memória.

O segundo termo, "limite de computação", refere-se a problemas em que o número de instruções é alto comparado ao número de leituras / gravações de memória. Por exemplo, considere uma multiplicação de matrizes: O número de instruções será O (n ^ 3) quando n for o tamanho da matriz. Nesse caso, pode-se esperar que a GPU supere a CPU em um determinado tamanho de matriz. Outro exemplo poderia ser quando muitos cálculos trigonométricos complexos (seno / cosseno, etc.) são executados em "poucos" elementos de dados.

Como regra geral: você pode assumir que a leitura / gravação de um elemento de dados da memória "principal" da GPU possui uma latência de cerca de 500 instruções ....

Portanto, outro ponto-chave para o desempenho das GPUs é a localidade dos dados : se você precisar ler ou gravar dados (e, na maioria dos casos, precisará ;-)), verifique se os dados são mantidos o mais próximo possível. possível para os núcleos da GPU. As GPUs, portanto, têm certas áreas de memória (conhecidas como "memória local" ou "memória compartilhada") que geralmente têm apenas alguns KB de tamanho, mas são particularmente eficientes para dados que estão prestes a se envolver em um cálculo.

Então, para enfatizar isso de novo: a programação da GPU é uma arte, relacionada remotamente à programação paralela na CPU. Coisas como Threads em Java, com toda a infraestrutura de concorrência ThreadPoolExecutors, ForkJoinPoolsetc. , podem dar a impressão de que você só precisa dividir seu trabalho de alguma forma e distribuí-lo entre vários processadores. Na GPU, você pode encontrar desafios em um nível muito mais baixo: ocupação, pressão de registro, pressão de memória compartilhada, coalescência de memória ... apenas para citar alguns.

No entanto, quando você tem um problema paralelo à computação e vinculado à computação para resolver, a GPU é o caminho a seguir.


Uma observação geral: você pediu especificamente a CUDA. Mas eu recomendo fortemente que você também dê uma olhada no OpenCL. Tem várias vantagens. Antes de tudo, é um padrão aberto do setor, independente de fornecedor, e há implementações do OpenCL da AMD, Apple, Intel e NVIDIA. Além disso, há um suporte muito mais amplo para o OpenCL no mundo Java. O único caso em que eu preferiria me contentar com o CUDA é quando você deseja usar as bibliotecas de tempo de execução do CUDA, como CUFFT for FFT ou CUBLAS for BLAS (operações de matriz / vetor). Embora existam abordagens para fornecer bibliotecas semelhantes para o OpenCL, elas não podem ser usadas diretamente do lado do Java, a menos que você crie suas próprias ligações JNI para essas bibliotecas.


Você também pode achar interessante saber que, em outubro de 2012, o grupo OpenJDK HotSpot iniciou o projeto "Sumatra": http://openjdk.java.net/projects/sumatra/ . O objetivo deste projeto é fornecer suporte à GPU diretamente na JVM, com suporte da JIT. O status atual e os primeiros resultados podem ser vistos em sua lista de discussão em http://mail.openjdk.java.net/mailman/listinfo/sumatra-dev


No entanto, há algum tempo, coletei alguns recursos relacionados ao "Java na GPU" em geral. Vou resumir isso novamente aqui, em nenhuma ordem específica.

( Isenção de responsabilidade : eu sou o autor de http://jcuda.org/ e http://jocl.org/ )

Tradução de código (byte) e geração de código OpenCL:

https://github.com/aparapi/aparapi : Uma biblioteca de código aberto criada e mantida ativamente pela AMD. Em uma classe especial "Kernel", pode-se substituir um método específico que deve ser executado em paralelo. O código de bytes deste método é carregado em tempo de execução usando um próprio leitor de códigos de bytes. O código é traduzido no código OpenCL, que é compilado usando o compilador OpenCL. O resultado pode ser executado no dispositivo OpenCL, que pode ser uma GPU ou uma CPU. Se a compilação no OpenCL não for possível (ou nenhum OpenCL estiver disponível), o código ainda será executado em paralelo, usando um Pool de Threads.

https://github.com/pcpratts/rootbeer1 : uma biblioteca de código aberto para converter partes de Java em programas CUDA. Ele oferece interfaces dedicadas que podem ser implementadas para indicar que uma determinada classe deve ser executada na GPU. Ao contrário de Aparapi, ele tenta serializar automaticamente os dados "relevantes" (ou seja, a parte relevante completa do gráfico de objetos!) Em uma representação adequada para a GPU.

https://code.google.com/archive/p/java-gpu/ : uma biblioteca para converter código Java anotado (com algumas limitações) em código CUDA, que é compilado em uma biblioteca que executa o código na GPU. A Biblioteca foi desenvolvida no contexto de uma tese de doutorado, que contém informações profundas sobre o processo de tradução.

https://github.com/ochafik/ScalaCL : ligações do Scala para OpenCL. Permite que coleções especiais do Scala sejam processadas em paralelo com o OpenCL. As funções chamadas nos elementos das coleções podem ser funções comuns do Scala (com algumas limitações) que são traduzidas para os kernels do OpenCL.

Extensões de idioma

http://www.ateji.com/px/index.html : Uma extensão de linguagem para Java que permite construções paralelas (por exemplo, paralela para loops, estilo OpenMP) que são executadas na GPU com OpenCL. Infelizmente, esse projeto muito promissor não é mais mantido.

http://www.habanero.rice.edu/Publications.html (JCUDA): Uma biblioteca que pode converter código Java especial (chamado código JCUDA) em código Java e CUDA-C, que pode ser compilado e executado no diretório GPU. No entanto, a biblioteca não parece estar disponível ao público.

https://www2.informatik.uni-erlangen.de/EN/research/JavaOpenMP/index.html : extensão de linguagem Java para construções OpenMP, com um back-end CUDA

Bibliotecas de ligação Java OpenCL / CUDA

https://github.com/ochafik/JavaCL : Ligações Java para OpenCL: Uma biblioteca OpenCL orientada a objetos, baseada em ligações de baixo nível geradas automaticamente

http://jogamp.org/jocl/www/ : Ligações Java para OpenCL: Uma biblioteca OpenCL orientada a objetos, baseada em ligações de baixo nível geradas automaticamente

http://www.lwjgl.org/ : Ligações Java para OpenCL: Ligações de baixo nível geradas automaticamente e classes de conveniência orientadas a objetos

http://jocl.org/ : Ligações Java para OpenCL: Ligações de baixo nível que são um mapeamento 1: 1 da API OpenCL original

http://jcuda.org/ : Ligações Java para CUDA: Ligações de baixo nível que são um mapeamento 1: 1 da API CUDA original

Diversos

http://sourceforge.net/projects/jopencl/ : ligações Java para OpenCL. Parece não ser mais mantida desde 2010

http://www.hoopoe-cloud.com/ : Ligações Java para CUDA. Parece não ser mais mantido


Marco13
fonte
considere uma operação de adicionar 2 matrizes e armazenar o resultado em uma terceira matriz. Quando o mutli é encadeado na CPU sem o OpenCL, o gargalo sempre será a etapa em que a adição ocorrerá. Esta operação é obviamente paralela aos dados. Mas digamos que não sabemos se será previamente vinculado à computação ou vinculado à memória. Leva muito tempo e recursos para implementar e, em seguida, verifica-se que a CPU é muito melhor ao executar esta operação. Então, como identificar isso de antemão sem implementar o código OpenCL.
Cool_Coder
2
@Cool_Coder De fato, é difícil dizer de antemão se (ou quanto) uma determinada tarefa se beneficiará da implementação da GPU. Para um primeiro pressentimento, provavelmente é necessário ter alguma experiência com diferentes casos de uso (que eu reconhecidamente também não tenho). Uma primeira etapa pode ser ver nvidia.com/object/cuda_showcase_html.html e verificar se há um problema "semelhante" listado. (É CUDA, mas é conceitualmente tão próximo ao OpenCL que os resultados podem ser transferidos na maioria dos casos). Na maioria dos casos, a aceleração também é mencionado, e muitos deles têm links para documentos ou mesmo código
Marco13
+1 para aparapi - é uma maneira simples de iniciar o opencl em java e permite comparar facilmente o desempenho da CPU versus GPU em casos simples. Além disso, é mantido pela AMD, mas funciona bem com placas Nvidia.
Steve Cook
12
Esta é uma das melhores respostas que eu já vi no StackOverflow. Obrigado pelo tempo e esforço!
ViggyNash
1
@AlexPunnen Isso provavelmente está além do escopo dos comentários. Até onde eu sei, o OpenCV tem algum suporte para CUDA, como em docs.opencv.org/2.4/modules/gpu/doc/introduction.html . O developer.nvidia.com/npp possui muitas rotinas de processamento de imagem, o que pode ser útil. E o github.com/GPUOpen-ProfessionalCompute-Tools/HIP pode ser uma "alternativa" para a CUDA. Ele pode ser possível pedir isso como uma questão nova, mas é preciso ter cuidado para a frase-lo corretamente, para downvotes evitar para "opinião baseada" / "pedindo bibliotecas de terceiros" ...
Marco13
4

Eu começaria usando um dos projetos disponíveis para Java e CUDA: http://www.jcuda.org/

JohnKlehm
fonte
2

A partir da pesquisa que fiz, se você tem como alvo as GPUs da Nvidia e decidiu usar o CUDA sobre o OpenCL , encontrei três maneiras de usar a API CUDA em java.

  1. JCuda (ou alternativa) - http://www.jcuda.org/ . Essa parece ser a melhor solução para os problemas em que estou trabalhando. Muitas bibliotecas como CUBLAS estão disponíveis no JCuda. Kernels ainda são escritos em C.
  2. As interfaces JNI - JNI não são as minhas favoritas para escrever, mas são muito poderosas e permitem que você faça qualquer coisa que a CUDA possa fazer.
  3. JavaCPP - Isso basicamente permite criar uma interface JNI em Java sem escrever diretamente o código C. Há um exemplo aqui: Qual é a maneira mais fácil de executar o código CUDA em Java? de como usar isso com o impulso CUDA. Para mim, parece que você também pode escrever uma interface JNI.

Todas essas respostas são basicamente formas de usar o código C / C ++ em Java. Você deve se perguntar por que precisa usar Java e se não pode fazê-lo em C / C ++.

Se você gosta de Java e sabe como usá-lo e não deseja trabalhar com todo o gerenciamento de ponteiros e outros itens que acompanham o C / C ++, o JCuda provavelmente é a resposta. Por outro lado, a biblioteca CUDA Thrust e outras bibliotecas como essa podem ser usadas para fazer muito do gerenciamento de ponteiros em C / C ++ e talvez você deva examinar isso.

Se você gosta de C / C ++ e não se importa com o gerenciamento de ponteiros, mas existem outras restrições que o obrigam a usar Java, o JNI pode ser a melhor abordagem. Porém, se seus métodos JNI forem apenas invólucros para comandos do kernel, você também pode usar o JCuda.

Existem algumas alternativas ao JCuda, como Cuda4J e Root Beer, mas essas não parecem ser mantidas. Enquanto no momento da redação deste documento, o JCuda suporta o CUDA 10.1. qual é o CUDA SDK mais atualizado.

Além disso, existem algumas bibliotecas java que usam CUDA, como deeplearning4j e Hadoop, que podem fazer o que você está procurando, sem exigir que você escreva o código do kernel diretamente. Eu não olhei muito para eles.

David Griffin
fonte
1

Marco13 já forneceu uma excelente resposta .

Caso você esteja procurando uma maneira de usar a GPU sem implementar os kernels CUDA / OpenCL, gostaria de adicionar uma referência às extensões finmath-lib-cuda-extensões (finmath-lib-gpu-extensions) http: // finmath .net / finmath-lib-cuda-extensions / (exoneração de responsabilidade: eu sou o mantenedor deste projeto).

O projeto fornece uma implementação de "classes de vetores", para ser mais preciso, uma interface chamada RandomVariable, que fornece operações aritméticas e redução de vetores. Existem implementações para a CPU e GPU. Existem implementações usando diferenciação algorítmica ou avaliações simples.

Atualmente, as melhorias de desempenho na GPU são pequenas (mas para vetores de tamanho 100.000, você pode obter um fator> 10 melhorias de desempenho). Isso ocorre devido aos pequenos tamanhos de kernel. Isso irá melhorar em uma versão futura.

A implementação da GPU usa JCuda e JOCL e está disponível para GPUs Nvidia e ATI.

A biblioteca é Apache 2.0 e está disponível via Maven Central.

Batatas Fritas
fonte