Почему вложенный цикл OpenCL работает только для некоторых элементов

#c #opencl

#c #opencl

Вопрос:

Я пытаюсь реализовать следующий цикл в ядре OpenCL.

 for(i=0;i<N;i  ) for(j=0;j<M;j  ) weights[i*M j]  = gradients[i] * input[j];
  

Это мое ядро. В настоящее время я жестко кодирую M равным 4, и он работает только для первых 4 элементов.

 __kernel
void cwk3( __global float *gradients,  __global float *inputs,  __global float *weights)
{
    // The global id tells us the index of the vector for this thread.
    int gid1 = get_global_id(0);
    int gid2 = get_global_id(1);

    // Perform the addition.
    weights[(gid1 * 4)   gid2]  = gradients[gid1] * inputs[gid2];
}
  

Соответствующий код c

     float
        *gradients = (float*) malloc( N  *sizeof(float) ),
        *inputs    = (float*) malloc(   M*sizeof(float) ),
        *weights   = (float*) malloc( N*M*sizeof(float) );
    initialiseArrays( gradients, inputs, weights, N, M );

    cl_mem deviceGradients = clCreateBuffer( context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, N*sizeof(float), gradients
    , amp;status );
    cl_mem deviceInputs = clCreateBuffer( context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, M*sizeof(float), inputs
    , amp;status );
    cl_mem deviceWeights = clCreateBuffer( context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, N*M*sizeof(float), weights
    , amp;status );

    cl_kernel kernel = compileKernelFromFile( "kernel.cl", "cwk3", context, device );

    status = clSetKernelArg( kernel, 0, sizeof(deviceGradients), amp;deviceGradients );
    status = clSetKernelArg( kernel, 1, sizeof(deviceInputs), amp;deviceInputs );
    status = clSetKernelArg( kernel, 2, sizeof(deviceWeights), amp;deviceWeights );

    size_t indexSpaceSize[2], workGroupSize[1];
    indexSpaceSize[0] = N;
    indexSpaceSize[1] = M;
    workGroupSize [0] = 4;

    status = clEnqueueNDRangeKernel( queue, kernel, 1, NULL, indexSpaceSize, workGroupSize, 0, NULL, NULL );
    if( status != CL_SUCCESS )
    {
        printf( "Failure enqueuing kernel: Error %d.n", status );
        return EXIT_FAILURE;        
    }

    status = clEnqueueReadBuffer( queue, deviceWeights, CL_TRUE, 0, N*M*sizeof(float), weights, 0, NULL, NULL );
    if( status != CL_SUCCESS )
    {
        printf( "Could not copy device data to host: Error %d.n", status );
        return EXIT_FAILURE;
    }
  

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

Комментарии:

1. покажите нам, как вы его запускаете

2. @ShPavel Я отредактировал вопрос с помощью соответствующего кода

Ответ №1:

Вы, кажется, запутались в глобальных и локальных рабочих группах.

Глобальный рабочий размер определяет общее количество выполненных вызовов (рабочих элементов). global_work_size=[M,N] вызовет ядро MxN раз в общей сложности. Один рабочий элемент может определять свою позицию с помощью get_global_id . OpenCL мог бы реализовать это как-то так :

 for(i=0;i<N;i  )
   for(j=0;j<M;j  )
       call_kernel(set global_id=[i,j])
  

Локальные рабочие группы описывают, как группировать отдельные запущенные рабочие элементы (которые создаются в соответствии с глобальными размерами) и информировать их друг о друге и совместно использовать память между собой. Ни одна из этих функций, которые вы используете / нуждаетесь, поэтому игнорируйте их.
Итак, чтобы реализовать ваш цикл for в OpenCL:

 for(i=0;i<N;i  ) 
    for(j=0;j<M;j  ) 
        weights[i*M j]  = gradients[i] * input[j];
  

У вас было бы это ядро:

  __kernel
void cwk3( __global float *gradients,  __global float *inputs,  __global float *weights)
{
    int gid1 = get_global_id(0);
    int gid2 = get_global_id(1);
    int M = get_global_size(0);

    weights[(gid1 * M)   gid2]  = gradients[gid1] * inputs[gid2];
}
  

И назовите это так:

 size_t global_work[2];
global_work[0]=M;
global_work[1]=N;
// This is 2D kernel, not 1D
// Offsets are 0
// Global work size is M*N
// Ignore local work size 
status = clEnqueueNDRangeKernel( queue, kernel, 2, NULL, global_work);