Назад в библиотеку

Начало работы с JCuda

Авторы: jcuda.org

Автор перевода: В.В. Белый

Источник: jcuda.org

Содержание

Введение

CUDA предоставляет два различных API: Runtime API и Driver API. Они оба очень похожи с точки зрения базовых задач, таких как работа с памятью. Однако, между ними есть некоторые важные различия. Самое главное из них - это способ управления ядрами и то, как они исполняются.

В оригинальном CUDA Runtime API ядра определяются и компилируется вместе с С-файлами. Исходный код компилируется с помощью NVCC - NVIDIA CUDA Compiler. Этот компилятор использует другой компилятор C (например, GCC или VS C), чтобы откомпилировать чистый C-код, после чего берется за компиляцию CUDA-составляющей - ядер и их вызовов (<<<...>>>). В результате получается исполнимый файл, представляющий целую программу.

Конечно же, NVCC не может использоваться для компиляции Java-программ. Синтаксис вызова ядер (<<<...>>>) неупотребим в Java, и после компиляции тут не создается единого исполнимого файла. Поэтому не существует возможности вызывать свои собственные ядра с помощью JCuda Runtime API. Вместо этого должен использоваться JCuda Driver API, как это будет показано ниже.

JCuda Runtime API в основном предназначен для взаимодействия с Java-привязками библиотек CUDA Runtime, такими как JCublas и JCufft. Java-программист, который хочет воспользоваться этими библиотеками и не хочет создавать собственные ядра может обойтись только JCuda Runtime API.

Установка

Перед тем, как использовать JCuda, необходимо установить драйвер и инструменты CUDA, которые можно загрузить на сайте NVIDIA CUDA. Стоит обратить внимание, что между выпусками новых версий CUDA и JCuda могут быть задержки. Для корректной инсталяции данного ПО, нужно следовать инструкциям на сайте.

SDK и примеры исходных кодов для работы JCuda не нужны, но последние могут быть полезны, чтобы разобраться в общих механизмах работы CUDA.

После корректной инсталяции CUDA следует скачать нужный архив JCuda в зависимости от операционной системы. Архив содержит JAR-файлы и соответствующие нативные библиотеки.

JAR-файлы нужно добавить в CLASSPATH, а нативные библиотеки нужно расположить в доступном Java месте. В большинстве случаев это либо java.library.path конкретной JVM, либо корневая директория проекта. Также можно воспользоваться средствами операционной системы - например, PATH или LD_LIBRARY_PATH.

Первичное тестирование

В этом разделе дается пояснение, как выполнить запуск проекта JCuda с помощью консоли команд. Тем, кто знаком с использованием JAR-файлов и нативных библиотек в Java, возможно, стоит пропустить этот раздел и перейти к созданию нового проекта из любимой IDE. Иначе нужно выполнить следующие шаги:

Создание ядер

Как было сказано во введении, собственные ядра могут быть запущены с использованием Driver API. В этом разделе описывается стандартный рабочий процесс создания ядра в рамках JCuda. Большая часть информации относится как к CUDA, так и JCuda. Так что будут полезными ресурсы, описывающие CUDA - например, CUDA Programming Guide.

Написание ядра

Код ядра пишется точно так же, как это делается для CUDA. Обычно код ядра располагется в отдельном файле. В CUDA Runtime API ядро часто является частью большего файла C. В таком случае весь дополнительный код будет проигнорирован JCuda.

Нужно уделить внимание следующему моменту: когда ядро будет вызвано с помощью Driver API, функция будет идентифицироваться по имени. Поэтому её нужно отметить как extern "C", чтобы компилятор C не поменял ее имя. Пример функции сложения векторов:

extern "C" __global__ void add(int n, float *a, float *b, float *sum) { int i = blockIdx.x * blockDim.x + threadIdx.x; if (i<n) { sum[i] = a[i] + b[i]; } }

Компиляция ядра

Исходный код ядра должен компилироваться с помощью NVCC. В результате будет создан файл, который может быть загружен и исполнен с помощью Driver API. Возможны два формата файлов для компиляции ядра:

Ранее повсеместно использовались CUBIN-файлы, но сейчас предпочтенее отдается PTX, которые являются переносимыми и используют компиляцию на лету.

Подробнее об этих форматах, и командах для их создания можно узнать в документации по NVCC.

Загрузка и исполнение ядра в JCuda

Процесс загрузки и исполнения ядра в JCuda Driver API из PTX- и CUBIN- файлов одинаков, так же, как и в CUDA Driver API. В первую очередь должен быть загружен модуль и получен указатель на функцию ядра: // Load the ptx file. CUmodule module = new CUmodule(); cuModuleLoad(module, "JCudaVectorAddKernel.ptx"); // Obtain a function pointer to the kernel function. CUfunction function = new CUfunction(); cuModuleGetFunction(function, module, "add");

При вызове ядра проявляются некоторые языковые ограниченя Java. Функции установки параметров ядра довольно сложны в версиях вплоть до CUDA 3.2, а также в CUDA 4.0. Эти функции были заменены одной, которая принимает все параметры выполнения ядра. Дополнительно она принимает все входные параметры ядра в качестве одного void** указателя, который эмулируется с помощью класса Pointer. Таким образом, установка параметров ядра в JCuda может быть даже проще, чем в CUDA: // Set up the kernel parameters: A pointer to an array // of pointers which point to the actual values. Pointer kernelParameters = Pointer.to( Pointer.to(new int[]{numElements}), Pointer.to(deviceInputA), Pointer.to(deviceInputB), Pointer.to(deviceOutput) ); // Call the kernel function. cuLaunchKernel(function, gridSizeX, 1, 1, // Grid dimension blockSizeX, 1, 1, // Block dimension 0, null, // Shared memory size and stream kernelParameters, null // Kernel- and extra parameters );

Стоит отметить, что тут проявляются проблемы, характерные для C — нужно быть крайне внимательными при работе с указателями.

В принципе, все программы JCuda имеют характерный паттерн. Это позволяет продолжить изучение JCuda на основе примеров.