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

147

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

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

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

Ганс
источник
2
Графические процессоры помогут вам ускорить выполнение определенных типов вычислительных задач. Однако, если у вас огромный объем данных, вы, скорее всего, будете привязаны к вводу-выводу. Скорее всего, графические процессоры - не решение.
Стив Кук
1
«Повышение производительности Java с помощью GPGPU» -> arxiv.org/abs/1508.06791
BlackBear
4
Вроде открытый вопрос, я рад, что моды не закрыли его, потому что ответ от Marco13 невероятно полезен! ИМХО, должно быть вики
JimLohse

Ответы:

449

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

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

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

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

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

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

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

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

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

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

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


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


Возможно, вам будет интересно узнать, что в октябре 2012 года группа OpenJDK HotSpot начала проект «Суматра»: http://openjdk.java.net/projects/sumatra/ . Цель этого проекта - обеспечить поддержку графического процессора непосредственно в 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. В специальном классе «Kernel» можно переопределить конкретный метод, который должен выполняться параллельно. Байт-код этого метода загружается во время выполнения с помощью собственного считывателя байт-кода. Код переводится в код OpenCL, который затем компилируется с помощью компилятора OpenCL. Затем результат может быть выполнен на устройстве OpenCL, которое может быть графическим процессором или процессором. Если компиляция в OpenCL невозможна (или OpenCL недоступен), код все равно будет выполняться параллельно с использованием пула потоков.

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

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, которое позволяет создавать параллельные конструкции (например, параллельные циклы for, стиль OpenMP), которые затем выполняются на графическом процессоре с помощью 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 с исходным API OpenCL.

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

Разное

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

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


Marco13
источник
рассмотрим операцию сложения двух матриц и сохранения результата в третьей матрице. При многопоточном использовании CPU без OpenCL узким местом всегда будет этап, на котором происходит добавление. Эта операция, очевидно, параллельна данным. Но, допустим, мы не знаем, будет ли это связано с вычислением или с привязкой к памяти заранее. Требуется много времени и ресурсов, чтобы реализовать, а затем увидеть, что ЦП намного лучше выполняет эту операцию. Итак, как это определить заранее, не реализуя код OpenCL.
Cool_Coder
2
@Cool_Coder Действительно, заранее трудно сказать, выиграет ли (и насколько) определенная задача от реализации на GPU. Для первого интуитивного ощущения вам, вероятно, понадобится некоторый опыт работы с различными вариантами использования (которого, по общему признанию, у меня тоже нет). Первым шагом может быть просмотр nvidia.com/object/cuda_showcase_html.html и проверка , есть ли в списке «похожая» проблема. (Это CUDA, но концептуально он настолько близок к OpenCL, что в большинстве случаев результаты можно передавать). В большинстве случаев также упоминается ускорение, и многие из них имеют ссылки на документы или даже код
Marco13
+1 для aparapi - это простой способ начать работу с opencl в java, и он позволяет легко сравнивать производительность CPU и GPU для простых случаев. Кроме того, он поддерживается AMD, но отлично работает с картами Nvidia.
Стив Кук
12
Это один из лучших ответов, которые я когда-либо видел на StackOverflow. Спасибо за время и усилия!
ViggyNash
1
@AlexPunnen Это, вероятно, выходит за рамки комментариев. Насколько мне известно, OpenCV имеет некоторую поддержку CUDA, начиная с docs.opencv.org/2.4/modules/gpu/doc/introduction.html . На сайте developer.nvidia.com/npp есть множество процедур обработки изображений, которые могут оказаться полезными. И github.com/GPUOpen-ProfessionalCompute-Tools/HIP может быть «альтернативой» CUDA. Можно было бы задать это как новый вопрос, но нужно быть осторожным, чтобы сформулировать его правильно, чтобы избежать отрицательных голосов за «основанный на мнении» / «запрос сторонних библиотек» ...
Marco13,
2

Из проведенного мною исследования , если вы ориентируетесь на графические процессоры Nvidia и решили использовать CUDA вместо OpenCL , я нашел три способа использования CUDA API в java.

  1. JCuda (или альтернатива) - http://www.jcuda.org/ . Это кажется лучшим решением проблем, над которыми я работаю. Многие библиотеки, такие как CUBLAS, доступны в JCuda. Однако ядра по-прежнему пишутся на C.
  2. JNI - интерфейсы JNI я не люблю писать, но они очень мощные и позволят вам делать все, что может CUDA.
  3. JavaCPP - это в основном позволяет создавать интерфейс JNI на Java без непосредственного написания кода C. Вот пример: Как проще всего запустить рабочий код CUDA на Java? о том, как использовать это с CUDA. Мне кажется, что вы могли бы просто написать интерфейс 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, которые могут делать то, что вы ищете, не требуя написания кода ядра напрямую. Однако я не слишком много их изучал.

Дэвид Гриффин
источник
1

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.

Кристиан Фрис
источник
0

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

Бен
источник