Использование Java с графическими процессорами Nvidia (cuda)

Я работаю над бизнес-проектом, который выполняется в Java и требует огромных вычислительных мощностей для вычисления бизнес-рынков. Простая математика, но с огромным количеством данных.

Мы заказали некоторые cuda gpu, чтобы попробовать его, и, поскольку Java не поддерживается cuda, мне интересно, с чего начать. Должен ли я построить интерфейс JNI? Должен ли я использовать JCUDA или есть другие способы?

У меня нет опыта в этой области, и я хотел бы, чтобы кто-то мог направить меня к чему-то, чтобы я мог начать исследования и изучение.

2 ответа

Решение

Прежде всего, вы должны знать, что CUDA не будет автоматически выполнять вычисления быстрее. С одной стороны, потому что программирование на GPU - это искусство, и может быть очень, очень сложно понять это правильно. С другой стороны, потому что графические процессоры хорошо подходят только для определенных видов вычислений.

Это может показаться запутанным, потому что вы можете вычислить что-нибудь на GPU. Ключевым моментом, конечно, является то, добьетесь ли вы хорошего ускорения или нет. Наиболее важной классификацией здесь является то, является ли проблема параллельной задачей или параллельной данными. Первый относится, грубо говоря, к проблемам, когда несколько потоков работают над своими собственными задачами, более или менее независимо. Второй относится к проблемам, когда все потоки делают одно и то же - но в разных частях данных.

Последнее является проблемой, с которой хорошо справляются графические процессоры: у них много ядер, и все ядра делают то же самое, но работают с разными частями входных данных.

Вы упомянули, что у вас "простая математика, но с огромным количеством данных". Хотя это может звучать как проблема, совершенно параллельная данным, и, таким образом, похоже, что она хорошо подходит для GPU, есть еще один аспект, который следует учитывать: GPU смехотворно быстры с точки зрения теоретической вычислительной мощности (FLOPS, операций с плавающей запятой в секунду). Но они часто подавляются пропускной способностью памяти.

Это приводит к другой классификации проблем. А именно, связаны ли проблемы с памятью или вычислениями.

Первый относится к проблемам, когда количество инструкций, выполняемых для каждого элемента данных, невелико. Например, рассмотрим добавление параллельного вектора: вам нужно будет прочитать два элемента данных, затем выполнить одно сложение, а затем записать сумму в вектор результата. Вы не увидите ускорения при выполнении этого на GPU, потому что одно добавление не компенсирует усилия чтения / записи памяти.

Второй термин, "привязанный к вычислениям", относится к проблемам, когда количество инструкций велико по сравнению с количеством операций чтения / записи в память. Например, рассмотрим умножение матриц: количество инструкций будет O(n^3), когда n - размер матрицы. В этом случае можно ожидать, что графический процессор превзойдет процессор при определенном размере матрицы. Другим примером может быть случай, когда многие сложные тригонометрические вычисления (синус / косинус и т. Д.) Выполняются для "нескольких" элементов данных.

Практическое правило. Можно предположить, что чтение / запись одного элемента данных из "основной" памяти графического процессора имеет задержку около 500 инструкций....

Поэтому еще одним ключевым моментом для производительности графических процессоров является локальность данных: если вам нужно читать или записывать данные (и в большинстве случаев вам придется;-)), то вы должны убедиться, что данные хранятся как можно ближе к ним. возможно до ядер GPU. Таким образом, графические процессоры имеют определенные области памяти (называемые "локальной памятью" или "разделяемой памятью"), размер которых обычно составляет всего несколько КБ, но особенно эффективен для данных, которые собираются участвовать в вычислениях.

Итак, еще раз подчеркну: программирование на GPU - это искусство, которое только отдаленно связано с параллельным программированием на CPU. Такие вещи, как Threads в Java, со всей инфраструктурой параллелизма, такой как ThreadPoolExecutors, ForkJoinPools и т.д. может сложиться впечатление, что вам просто нужно как-то разделить свою работу и распределить ее по нескольким процессорам. В графическом процессоре вы можете столкнуться с проблемами на гораздо более низком уровне: занятость, давление регистра, давление общей памяти, объединение памяти... и это лишь некоторые из них.

Однако, когда вам нужно решить проблему, связанную с данными, связанную с вычислениями, GPU - это путь.


Общее замечание: Вы специально просили CUDA. Но я настоятельно рекомендую вам также взглянуть на OpenCL. У этого есть несколько преимуществ. Прежде всего, это независимый от производителя, открытый отраслевой стандарт, и есть реализации OpenCL от AMD, Apple, Intel и NVIDIA. Кроме того, в мире Java существует гораздо более широкая поддержка OpenCL. Единственный случай, когда я предпочел бы согласиться на CUDA, - это когда вы хотите использовать библиотеки времени выполнения CUDA, такие как CUFFT для FFT или CUBLAS для BLAS (операции Matrix/Vector). Хотя существуют подходы к предоставлению аналогичных библиотек для OpenCL, их нельзя напрямую использовать со стороны Java, если только вы не создадите собственные привязки JNI для этих библиотек.


Вам также может быть интересно услышать, что в октябре 2012 года группа OpenJDK HotSpot запустила проект "Суматра": http://openjdk.java.net/projects/sumatra/. Цель этого проекта - предоставить поддержку GPU непосредственно в JVM при поддержке JIT. Текущий статус и первые результаты можно увидеть в их списке рассылки по http://mail.openjdk.java.net/mailman/listinfo/sumatra-dev


Однако некоторое время назад я собрал некоторые ресурсы, связанные с "Java на GPU" в целом. Я подведу итоги здесь снова, без особого порядка.

(Отказ от ответственности: я являюсь автором http://jcuda.org/ и http://jocl.org/)

(Байт) перевод кода и генерация кода OpenCL:

https://github.com/aparapi/aparapi: библиотека с открытым исходным кодом, которая создается и активно поддерживается AMD. В специальном классе "Ядро" можно переопределить определенный метод, который должен выполняться параллельно. Байт-код этого метода загружается во время выполнения с использованием собственного считывателя байт-кода. Код переводится в код OpenCL, который затем компилируется с использованием компилятора OpenCL. Затем результат может быть выполнен на устройстве OpenCL, которое может быть графическим процессором или процессором. Если компиляция в OpenCL невозможна (или нет OpenCL), код все равно будет выполняться параллельно с использованием пула потоков.

https://github.com/pcpratts/rootbeer1: библиотека с открытым исходным кодом для преобразования частей Java в программы CUDA. Он предлагает выделенные интерфейсы, которые могут быть реализованы для указания того, что определенный класс должен быть выполнен на GPU. В отличие от Aparapi, он пытается автоматически сериализовать "релевантные" данные (то есть полную релевантную часть графа объектов!) В представление, подходящее для графического процессора.

https://code.google.com/archive/p/java-gpu/: библиотека для перевода аннотированного кода Java (с некоторыми ограничениями) в код CUDA, который затем компилируется в библиотеку, которая выполняет код на графическом процессоре. Библиотека была разработана в рамках докторской диссертации, которая содержит основную информацию о процессе перевода.

https://github.com/ochafik/ScalaCL: привязки Scala для OpenCL. Позволяет обрабатывать специальные коллекции Scala параллельно с OpenCL. Функции, которые вызываются для элементов коллекций, могут быть обычными функциями Scala (с некоторыми ограничениями), которые затем переводятся в ядра OpenCL.

Расширения языка

http://www.ateji.com/px/index.html: Расширение языка для Java, которое допускает параллельные конструкции (например, параллельные для циклов, стиль OpenMP), которые затем выполняются на GPU с помощью OpenCL. К сожалению, этот очень многообещающий проект больше не поддерживается.

http://www.habanero.rice.edu/Publications.html (JCUDA): библиотека, которая может переводить специальный код Java (называемый кодом JCUDA) в код Java и CUDA-C, который затем можно скомпилировать и выполнить в GPU. Тем не менее, библиотека не является общедоступной.

https://www2.informatik.uni-erlangen.de/EN/research/JavaOpenMP/index.html: расширение языка Java для конструкций OpenMP с бэкэндом CUDA

Библиотеки связывания Java OpenCL/CUDA

https://github.com/ochafik/JavaCL: Java-привязки для OpenCL: объектно-ориентированная библиотека OpenCL, основанная на автоматически генерируемых низкоуровневых привязках

http://jogamp.org/jocl/www/: Java-привязки для OpenCL: объектно-ориентированная библиотека OpenCL, основанная на автоматически генерируемых низкоуровневых привязках

http://www.lwjgl.org/: Java-привязки для OpenCL: автоматически генерируемые низкоуровневые привязки и объектно-ориентированные классы удобства

http://jocl.org/: Java-привязки для OpenCL: низкоуровневые привязки, которые являются отображением 1:1 исходного OpenCL API

http://jcuda.org/: Java-привязки для CUDA: низкоуровневые привязки, которые являются отображением 1:1 исходного CUDA API

Разнообразный

http://sourceforge.net/projects/jopencl/: привязки Java для OpenCL. Кажется, больше не поддерживается с 2010 года

http://www.hoopoe-cloud.com/: Java-привязки для CUDA. Кажется, больше не поддерживается


Из проведенных мною исследований, если вы ориентируетесь на графические процессоры Nvidia и решили использовать Cuda вместо openCL, я нашел три способа использования Cuda api в java.

  1. JCuda (или альтернатива) - http://www.jcuda.org/ Это кажется лучшим решением проблем, над которыми я работаю. Многие из библиотек, таких как CUBLAS, доступны в JCuda. Ядра все еще написаны на C, хотя.
  2. JNI - JNI-интерфейсы мне не нравятся, но они очень мощные и позволяют вам делать все, что может Cuda.
  3. JavaCPP - это в основном позволяет вам создавать интерфейс JNI в java без непосредственного написания кода на C. Здесь есть пример /questions/19214048/kakoj-samyij-prostoj-sposob-zapustit-rabochij-kod-cuda-v-java/19214054#19214054 о том, как использовать это с помощью cuda thrust. Мне кажется, что вы могли бы просто написать интерфейс JNI.

Все эти ответы в основном просто способы использования кода C / C++ в Java. Вы должны спросить себя, почему вам нужно использовать Java, и если вы не можете сделать это на языке c/ C++.

Если вам нравится Java и вы знаете, как его использовать, и не хотите работать со всеми средствами управления указателями, а также с C / C++, то JCuda, вероятно, ответ. С другой стороны, библиотека Cuda Thrust и другие подобные ей библиотеки могут быть использованы для управления указателями в c/ C++, и, возможно, вам стоит взглянуть на это.

Если вам нравится c/ C++ и вы не возражаете против управления указателями, но есть и другие ограничения, заставляющие вас использовать java, то JNI может быть лучшим подходом. Тем не менее, если ваши JNI-методы просто являются обертками для команд ядра, вы также можете просто использовать JCuda.

Есть несколько альтернатив JCuda, таких как Cuda4J и Root Beer, но они, похоже, не поддерживаются. Принимая во внимание, что на момент написания этой статьи JCuda поддерживает Cuda 10.1. который является самым современным Cuda SDK.

Кроме того, есть несколько библиотек Java, которые используют cuda, такие как deeplearning4j и Hadoop, которые могут делать то, что вы ищете, не требуя от вас написания кода ядра напрямую. Я не смотрел на них слишком много, хотя.

Я бы начал с использования одного из проектов для Java и CUDA: http://www.jcuda.org/

Marco13 уже дал отличный ответ.

Если вы ищете способ использовать графический процессор без реализации ядер CUDA/OpenCL, я хотел бы добавить ссылку на finmath-lib-cuda-extensions (finmath-lib-gpu-extensions) http://finmath.net/finmath-lib-cuda-extensions/ (отказ от ответственности: я сопровождаю этот проект).

В проекте предусмотрена реализация "векторных классов", а точнее интерфейса, называемого RandomVariable, который обеспечивает арифметические операции и сокращение векторов. Есть реализации для CPU и GPU. Существуют реализации с использованием алгоритмического дифференцирования или простых оценок.

Улучшение производительности графического процессора в настоящее время невелико (но для векторов размером 100000 вы можете получить увеличение производительности в 10 раз). Это связано с небольшими размерами ядра. Это улучшится в будущей версии.

Реализация GPU использует JCuda и JOCL и доступна для графических процессоров Nvidia и ATI.

Это библиотека Apache 2.0, доступная через Maven Central.

Информации о характере проблемы и данных не так много, поэтому советовать сложно. Тем не менее, я бы рекомендовал оценить осуществимость других решений, которые могут быть проще интегрированы с java и позволяют горизонтальное, а также вертикальное масштабирование. Первое, на что я бы посоветовал взглянуть, - это аналитический движок с открытым исходным кодом под названием Apache Spark https://spark.apache.org/, который доступен в Microsoft Azure, но, вероятно, и у других поставщиков облачных IaaS. Если вы продолжаете использовать свой графический процессор, то советуем посмотреть на другие доступные на рынке аналитические базы данных с поддержкой графического процессора, которые соответствуют бюджету вашей организации.

Другие вопросы по тегам