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.
Respostas:
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
,ForkJoinPools
etc. , 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
fonte
Eu começaria usando um dos projetos disponíveis para Java e CUDA: http://www.jcuda.org/
fonte
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.
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.
fonte
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.
fonte