Замените CUDA cudaMemcpy2DToArray() на OpenCL clEnqueueCopyBufferRect()

Я уже некоторое время работаю с портированием CUDA-> OpenCL. У меня есть вопрос, на который я пока нигде не нашел ответа. Таким образом, я думаю, это будет интересный вопрос для портировщиков CUDA->OpenCL.

У меня все шло гладко, ожидая «перевода» одной функции. Это функция cudaMemcpy2DToArray(). Простой пример CUDA с использованием этой функции будет выглядеть так:

uint8_t*    srcBuffer;
cudaArray*  dstArray;

size_t      pitch;
size_t      x = 700;
size_t      y = 300;

cudaMallocPitch((void **)&srcBuffer,
                &pitch,                     // pitch = 1024
                x*sizeof(uint8_t),
                y);

struct cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc<uint8_t>();

cudaMallocArray(&dstArray,
                &channelDesc,
                x,
                y,
                cudaArrayDefault);

/* Fill srcBuffer with some valuable data via a kernel,
   USING pitch when writing to srcBuffer:

   (uint8_t*)((char*)srcBuffer + Row * pitch) + Column = someuint8Value; */

cudaMemcpy2DToArray(dstArray,
                    0,
                    0,
                    (void *) srcBuffer,
                    pitch,
                    x,
                    y,
                    cudaMemcpyDeviceToDevice);

Версия OpenCL будет выглядеть так:

cl_mem      srcBuffer;
cl_mem      dstImage;

size_t      pitch;
size_t      x = 700;
size_t      y = 300;

В OpenCL нет функции cudaMallocPitch(). Чтобы добиться того же поведения, что и cudaMallocPitch(), необходимо сначала запросить предпочтительный размер выравнивания устройства, затем вычислить шаг и, наконец, выделить исходный буфер с помощью clCreateBuffer(). функция:

uint32_t    alignBits;
uint32_t    alignBytes;

clGetDeviceInfo (deviceID, /* Definition not shown here */
                 CL_DEVICE_MEM_BASE_ADDR_ALIGN,
                 sizeof(uint32_t),
                 &alignBits,                        // alignBits = 4096
                 NULL);

alignBytes = alignBits/8;                           // alignBytes = 512
pitch = (x*sizeof(uint8_t) + alignBytes - 1)/       // pitch = 1024
                             alignBytes*alignBytes;

srcBuffer = clCreateBuffer(context, /* Definition not shown here */
                           CL_MEM_READ_WRITE,
                           y*pitchBytes,
                           NULL,
                           NULL);

В OpenCL есть функция, которая ведет себя как cudaMallocArray(). Это clCreateImage2D():

cl_image_format channelDesc = {CL_A, CL_UNSIGNED_INT8};

dstImage = clCreateImage2D(context, /* Definition not shown here */
                           CL_MEM_READ_ONLY,
                           &channelDesc,
                           x,
                           y,
                           0, NULL, NULL);

Для согласованности добавлен следующий блок кода:

/* Fill srcBuffer with some valuable data via a kernel,
   USING pitch when writing to srcBuffer:

   (uchar*)((char*)srcBuffer + Row * pitch) + Column = someucharValue; */

Теперь мы в интересной части. Функция OpenCL с поведением, наиболее близким к поведению cudaMemcpy2DToArray(), которое я впервые смог найти, была clEnqueueCopyBufferToImage(). Однако у этой функции не было параметра pitch. Без параметра шага было бы невозможно правильно сделать копию буфера->изображения, так как количество элементов различается. Я понял, что могу использовать функцию OpenCL clEnqueueCopyBufferRect(), чтобы сначала скопировать данные из srcBuffer в другой буфер с тем же количеством элементов, что и < em>dstImage. И, наконец, используйте clEnqueueCopyBufferToImage(). Вот как это выглядит:

srcBuffer_tmp = clCreateBuffer(context, /* Definition not shown here */
                               CL_MEM_READ_WRITE,
                               x*y*sizeof(uint8_t),
                               NULL,
                               NULL);

size_t src_origin = {0, 0, 0};
size_t dst_origin = {0, 0, 0};
size_t region     = {x, y, 1};

clEnqueueCopyBufferRect(commandQueue, /* Definition not shown here */
                        srcBuffer,
                        srcBuffer_tmp,
                        src_origin,
                        dst_origin,
                        region,
                        pitch,
                        0,
                        x*sizeof(uint8_t),
                        0,
                        0, NULL, NULL);

clEnqueueCopyBufferToImage(commandQueue,
                           srcBuffer_tmp,
                           dstImage,
                           0,
                           dst_origin,
                           region,
                           0, NULL, NULL);

clReleaseMemObject(srcBuffer_tmp);

Вы заметите, что это НЕ дает такого же поведения. Мы выделили больше памяти, буфер srcBuffer_tmp. Немного поэкспериментировав, я обнаружил, что приведенный выше код можно просто заменить на:

size_t src_origin = {0, 0, 0};
size_t dst_origin = {0, 0, 0};
size_t region     = {x, y, 1};

clEnqueueCopyBufferRect(commandQueue, /* Definition not shown here */
                        srcBuffer,
                        dstImage,     /* DIRECT TO IMAGE */
                        src_origin,
                        dst_origin,
                        region,
                        pitch,
                        0,
                        x*sizeof(uint8_t),
                        0,
                        0, NULL, NULL);

Это дало мне поведение при сохранении как cudaMemcpy2DToArray(). Однако использование функции clEnqueueCopyBufferRect() действительно выглядит НЕПРАВИЛЬНЫМ. Отсутствие функции Rect-copy для изображений очевидно, поскольку все уже обрабатывается в 2D.

Мой вопрос:

Можно ли предположить, что clEnqueueCopyBufferRect() будет вести себя как функция копирования буфера->изображения, если дескриптор формата изображения (cl_image_format) инициализирован с порядком каналов изображения < strong>CL_A и тип канала изображения, равный типу элемента, используемого в исходном буфере?


person betoz    schedule 11.07.2014    source источник
comment
С моей точки зрения, cudaMemcpy2DToArray и clEnqueueCopyBufferRect эквивалентны. Разница только в том, как вы их используете. В CUDA буфер — это изображение, поэтому он работает для обоих. Но в OpenCL изображение не является буфером, хотя в некоторых реализациях они эквивалентны, использовать его таким образом небезопасно.   -  person DarkZeros    schedule 29.07.2014
comment
Спасибо за ваш вклад!   -  person betoz    schedule 03.09.2014


Ответы (1)


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

Функция, которую вы ищете, действительно не существует, даже в OpenCL 2.0, поэтому нет прямого способа сделать это с хоста. И CUDA, и OpenCL проводят четкое различие между буферами и изображениями, так как в аппаратном обеспечении они могут следовать совершенно разным путям, протоколам кэширования и т. д. Поскольку функция копирования в OpenCL не существует, и нет возможности переинтерпретировать данные изображения как данные буфера. , лучше всего реализовать эту функцию в ядре с произвольным размером рабочей группы. Такое ядро ​​было бы довольно просто реализовать (хотя это требует гораздо больше работы, чем добавление запеченной функции), и оно также должно быть достаточно быстрым.

Хотя в стандарте и буферы, и изображения называются cl_mem, в описании функции четко указано, что она работает с буферами (однако не указано, что она не должна работать с изображениями). (Дырявые абстракции? Плохая документация?) Таким образом, хотя ваше решение может работать и даже работать на всех платформах, оно не соответствует стандарту. Даже если сегодня он работает на платформе, он может не работать завтра после обновления среды выполнения.

person Meteorhead    schedule 29.07.2014
comment
Прежде всего, спасибо за ваше время на чтение вопроса! Я понимаю и согласен с вами. Поскольку в спецификации этого не было указано, я решил пойти более безопасным путем и создал еще один промежуточный буфер. Во всяком случае, идея использовать ядро ​​как функцию копирования не приходила мне в голову, пока я не прочитал ваш пост. Спасибо за это! - person betoz; 03.09.2014