clBuildProgram выдает исключение AccessViolationException при построении этого конкретного ядра


Это часть какого-то параллельного ядра редукции/экстремума. Я сократил его до минимального кода, который все еще получает clBuildProgram сбой (обратите внимание, что он действительно падает, а не просто возвращает код ошибки):

EDIT : похоже, что это также происходит, когда local_value объявляется global вместо local.

EDIT2 / SOLUTION : проблема заключалась в том, что существовал бесконечный цикл. Я должен был написать remaining_items >>= 1 вместо remaining_items >> 1. Как уже было сказано в ответах, компилятор nvidia кажется не очень надежным, когда речь заходит об ошибках компиляции/оптимизации.

kernel void testkernel(local float *local_value)
{
    size_t thread_id = get_local_id(0);

    int remaining_items = 1024;

    while (remaining_items > 1)
    {
        // throw away the right half of the threads
        remaining_items >> 1; // <-- SPOTTED THE BUG
        if (thread_id > remaining_items)
        {
            return;
        }

        // look for a greater value in the right half of the memory space
        int right_index = thread_id + remaining_items;
        float right_value = local_value[right_index];
        if (right_value > local_value[thread_id])
        {
            local_value[thread_id] = right_value;
        }

        barrier(CLK_GLOBAL_MEM_FENCE);
    }
}

Удаление строк return; и / или local_value[thread_id] = right_value; приводит к успешному завершению программы clbuild.

Я могу воспроизвести эту проблему на всех своих компьютерах (NVIDIA GTX 560, GT 555M, GT 540M, все они имеют архитектуру Fermi 2.1). Это видно на NVIDIA CUDA Toolkit SDK версий 4.0, 4.1 и 4.2, при использовании библиотек x64 или x86.

Есть ли у кого-нибудь идея, что могло бы быть проблема?

Возможно ли, что локальная (она же общая) память автоматически принимается за (WORK_GROUP_SIZE) * siezof(its_base_type)? Это объясняет, почему он работает, когда строки, о которых я упоминал выше, удаляются.


Минимальный код хоста (совместимый с C99) для воспроизведения:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifdef __APPLE__
#include <OpenCL/opencl.h>
#else
#include <CL/cl.h>
#endif

#define RETURN_THROW(expression) do { cl_int ret = expression; if (ret) { printf(#expression " FAILED: %dn" , ret); exit(1); } } while (0)
#define REF_THROW(expression) do { cl_int ret; expression; if (ret) { printf(#expression " FAILED: %dn" , ret); exit(1); } } while (0)

int main(int argc, char **argv)
{
    // Load the kernel source code into the array source_str
    FILE *fp;

    fp = fopen("testkernel.cl", "rb");
    if (!fp)
    {
        fprintf(stderr, "Failed to load kernel.n");
        exit(1);
    }
    fseek(fp, 0, SEEK_END);
    int filesize = ftell(fp);
    rewind(fp);
    char *source_str = (char*)calloc(filesize, sizeof(char));
    size_t bytes_read = fread(source_str, 1, filesize, fp);
    source_str[bytes_read] = 0;
    fclose(fp);

    // Get platform information
    cl_uint num_platforms;
    RETURN_THROW(clGetPlatformIDs(0, NULL, &num_platforms));

    cl_platform_id *platform_ids = (cl_platform_id *)calloc(num_platforms, sizeof(cl_platform_id));
    RETURN_THROW(clGetPlatformIDs(num_platforms, platform_ids, NULL));

    cl_device_id selected_device_id = NULL;

    printf("available platforms:n");
    for (cl_uint i = 0; i < num_platforms; i++)
    {
        char platform_name[50];
        RETURN_THROW(clGetPlatformInfo(platform_ids[i], CL_PLATFORM_NAME, 50, platform_name, NULL));
        printf("%sn", platform_name);

        // get devices for this platform
        cl_uint num_devices;
        RETURN_THROW(clGetDeviceIDs(platform_ids[i], CL_DEVICE_TYPE_GPU, 0, NULL, &num_devices));

        cl_device_id *device_ids = (cl_device_id *)calloc(num_devices, sizeof(cl_device_id));
        RETURN_THROW(clGetDeviceIDs(platform_ids[i], CL_DEVICE_TYPE_GPU, num_devices, device_ids, NULL));

        // select first nvidia device
        if (strstr(platform_name, "NVIDIA"))        // ADAPT THIS ACCORDINGLY
        {
            selected_device_id = device_ids[0];
        }
    }

    if (selected_device_id == NULL)
    {
        printf("No NVIDIA device foundn");
        exit(1);
    }

    // Create an OpenCL context
    cl_context context;
    REF_THROW(context = clCreateContext(NULL, 1, &selected_device_id, NULL, NULL, &ret));

    // Create a program from the kernel source
    cl_program program;
    REF_THROW(program = clCreateProgramWithSource(context, 1, (const char **)&source_str, NULL, &ret));

    // Build the program
    cl_int ret = clBuildProgram(program, 1, &selected_device_id, NULL, NULL, NULL);
    if (ret)
    {
        printf("BUILD ERRORn");
        // build error - get build log and display it
        size_t build_log_size;
        ret = clGetProgramBuildInfo(program, selected_device_id, CL_PROGRAM_BUILD_LOG, 0, NULL, &build_log_size);
        char *build_log = new char[build_log_size];
        ret = clGetProgramBuildInfo(program, selected_device_id, CL_PROGRAM_BUILD_LOG, build_log_size, build_log, NULL);
        printf("%sn", build_log);
        exit(1);
    }

    printf("build finished successfullyn");
    return 0;
}
1 2

1 ответ:

По моему опыту компилятор nvidia не очень надежен, когда дело доходит до обработки ошибок сборки, так что у вас, вероятно, где-то есть ошибка компиляции.

Я думаю, что ваша проблема-это действительно return, или, точнее, его комбинация с barrier. Согласно спецификации opencl о барьерах:

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

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

Если Баррер находится внутри цикла, все рабочие элементы необходимо выполнить барьер для каждой итерации цикла, прежде чем любой из них будет разрешили продолжить экзекуцию за барьером.

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

kernel void testkernel(local float *local_value) {
    size_t thread_id = get_local_id(0);
    int remaining_items = 1024;
    while (remaining_items > 1) {
        remaining_items >>= 1;// throw away the right half of the threads
        if (thread_id <= remaining_items) {
             // look for a greater value in the right half of the memory space
             int right_index = thread_id + remaining_items;
             float right_value = local_value[right_index];
             if (right_value > local_value[thread_id])
                 local_value[thread_id] = right_value;
        }
        barrier(CLK_GLOBAL_MEM_FENCE);
    }
}

Кроме того, как отмечалось в комментариях, он должен быть remaining_items>>=1 вместо remaining_items>>1, чтобы избежать создания бесконечного цикла.