2014-11-30 3 views
4

Около двух лет назад я написал ядро ​​для работы с несколькими числовыми сетками одновременно. Возникло очень странное поведение, что привело к неправильным результатам. При поиске ошибки с использованием printf() - операторов внутри ядра ошибка исчезла.Heisenbug в ядре CUDA, доступ к глобальной памяти

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

__launch_bounds__(672, 2) 
__global__ void heisenkernel(float *d_u, float *d_r, float *d_du, int radius, 
     int numNodesPerGrid, int numBlocksPerSM, int numGridsPerSM, int numGrids) 
{ 
    __syncthreads(); 
    int id_sm   = blockIdx.x/ numBlocksPerSM;         // (arbitrary) ID of Streaming Multiprocessor (SM) this thread works upon   - (constant over lifetime of thread) 
    int id_blockOnSM = blockIdx.x % numBlocksPerSM;          // Block number on this specific SM             - (constant over lifetime of thread) 
    int id_r   = id_blockOnSM * (blockDim.x - 2*radius) + threadIdx.x - radius; // Grid point number this thread is to work upon         - (constant over lifetime of thread) 
    int id_grid   = id_sm   * numGridsPerSM;         // Grid ID this thread is to work upon            - (not constant over lifetime of thread) 

    while(id_grid < numGridsPerSM * (id_sm + 1)) // this loops over numGridsPerSM grids 
    { 
     __syncthreads(); 
     int id_numInArray  = id_grid * numNodesPerGrid + id_r;  // Entry in array this thread is responsible for (read and possibly write) - (not constant over lifetime of thread) 
     float uchange   = 0.0f; 
     //uchange     = 1.0f;         // if this line is uncommented, results will be computed correctly ("Solution 1") 
     float du    = 0.0f; 

     if((threadIdx.x > radius-1) && (threadIdx.x < blockDim.x - radius) && (id_r < numNodesPerGrid) && (id_grid < numGrids)) 
     { 
      if (id_r == 0) // FO-forward difference 
       du = (d_u[id_numInArray+1] - d_u[id_numInArray])/(d_r[id_numInArray+1] - d_r[id_numInArray]); 
      else if (id_r == numNodesPerGrid - 1) // FO-rearward difference 
       du = (d_u[id_numInArray] - d_u[id_numInArray-1])/(d_r[id_numInArray] - d_r[id_numInArray-1]); 
      else if (id_r == 1 || id_r == numNodesPerGrid - 2) //SO-central difference 
       du = (d_u[id_numInArray+1] - d_u[id_numInArray-1])/(d_r[id_numInArray+1] - d_r[id_numInArray-1]); 
      else if(id_r > 1 && id_r < numNodesPerGrid - 2) 
       du = d_fourpoint_constant * ((d_u[id_numInArray+1] - d_u[id_numInArray-1])/(d_r[id_numInArray+1] - d_r[id_numInArray-1])) + (1-d_fourpoint_constant) * ((d_u[id_numInArray+2] - d_u[id_numInArray-2])/(d_r[id_numInArray+2] - d_r[id_numInArray-2])); 
      else 
       du = 0; 
     } 

     __syncthreads(); 
     if((threadIdx.x > radius-1 && threadIdx.x < blockDim.x - radius) && (id_r < numNodesPerGrid) && (id_grid < numGrids)) 
     { 
      d_u[ id_numInArray] = d_u[id_numInArray] * uchange;   // if this line is commented out, results will be computed correctly ("Solution 2") 
      d_du[ id_numInArray] = du; 
     } 

    __syncthreads(); 
    ++id_grid; 
} 

Это ядро ​​вычисляет производную некоторого значения во всех точках сетки для ряда числовых 1D-сеток.

Вещи рассмотреть: (см полной базы коды в нижней части)

    • сетка состоит из 1300 точек сетки
    • каждой решетка должна быть разработана на два блоков (из-за ограничений памяти/регистра)
    • каждый блок последовательно работает на 37 сетках (или лучше: половинки решетки, цикл while заботится об этом)
    • каждый поток несет ответственность за ту же точку сетки в каждой сетке
    • для производной быть вычислена, потоки должны иметь доступ к данным из четырех следующих точек сетки
    • для того, чтобы держать блоки indepentend от друга от друга, (точки сетки 666, 667, 668, 669 каждой сетки считываются двумя потоками из разных блоков, хотя для них записывается только один поток, это является перекрытием, где возникают проблемы)
    • из-за процесса кипения, два потока с каждой стороны блоков не выполняют вычислений, в оригинале они отвечают за запись соответствующих значений сетки в общую память

    Значения сетки хранятся в u_arr, du_arr и r_arr (и их соответствующие устройства массивов d_u, d_du и d_r). Каждая сетка занимает 1300 последовательных значений в каждом из этих массивов. Цикл while в ядре выполняет итерацию по 37 сеткам для каждого блока.

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

    странность из гейзенбага:

    я сравнил вычисленные значения сетки 0 с каждым из других сетей, и существует различие в перекрытии (сетка указует 666-669), хотя и не последовательно. Некоторые решетки имеют правильные значения, некоторые - нет. Два последовательных прогона будут отмечать разные сетки как ошибочные. Первое, что пришло в голову, это то, что два потока при этом перекрытии пытаются одновременно записывать в память, хотя это, похоже, не так (я проверил .... и снова проверил).

    Комментируя или отменить комментирование строки или с помощью printf() для целей отладки изменят результат программы, а также: Когда «просим» нить, ответственная за точки сетки в вопросе, они говорят мне, что все ли в порядке, и они на самом деле правильные. Как только я заставляю поток распечатывать свои переменные, они будут правильно вычислены (а что еще важнее: сохранены). То же самое касается отладки с Nsight Eclipse.

    Memcheck/Racecheck:

    CUDA-MemCheck (MemCheck и racecheck) не сообщает никаких проблем памяти/состояния гонки, хотя даже использование одного из этих инструментов имеет возможность повлиять на правильность результатов. Valgrind дает некоторые предупреждения, хотя я думаю, что они имеют какое-то отношение к API CUDA, на которые я не могу влиять и которые, похоже, не связаны с моей проблемой.

    (обновление) Как было отмечено, cuda-memcheck --tool racecheck работает только для общих условий гонки памяти, в то время как проблема под рукой есть условие гонки на d_u, т.е. глобальной памяти.

    Тестирование среда:

    Оригинальное ядро ​​было протестировано на разных устройствах CUDA и с различными возможностями вычислений (2.0, 3.0 и 3.5) с ошибкой показ в каждой конфигурации (в той или иной форме) ,

    My (основной) testsystem является следующее:

    • 2 х GTX 460, испытанный на обоих GPU, который управлял X-сервер, а также другой версии один
    • Driver: 340,46
    • Cuda Toolkit 6.5
    • Linux Kernel 3.11.0-12-родовым (Linux Mint 16 - Xfce)

    Состояние решения:

    К настоящему моменту я уверен, что доступ к памяти является виновником, возможно, некоторой оптимизацией из компилятора или использованием неинициализированных значений и что я, очевидно, не понимаю фундаментальную парадигму CUDA. Тот факт, что операторы printf() внутри ядра (в какой-то темной магии должны использовать память устройства и хоста), а также алгоритмы memcheck (cuda-memcheck и valgrind) влияют на точку схождения в том же направлении.

    Прошу прощения за это несколько сложное ядро, но я откидывал исходное ядро ​​и вызывал столько, сколько мог, и это насколько я понял. К настоящему моменту я научился восхищаться этой проблемой, и я с нетерпением жду возможности узнать, что здесь происходит.

    Два «решения», которые заставляют ядро ​​работать по назначению, помечены в коде.

    (Обновить) Как указано в правильном ответе ниже, проблема с моим кодом является условием гонки на границе блоков потоков.Поскольку на каждой сетке работают два блока, и нет гарантии того, какой блок работает первым, что приводит к описанному ниже поведению. Он также объясняет правильные результаты при использовании «решения 1», как указано в коде, поскольку значение ввода/вывода d_u не изменяется при uchange = 1.0.

    Простое решение состоит в том, чтобы разделить это ядро ​​на два ядра, одно вычисление d_u, другое вычислительное производное d_du. Было бы более желательно иметь только один вызов ядра вместо двух, хотя я не знаю, как это сделать с помощью -arch=sm_20. С -arch=sm_35 можно было бы использовать динамический параллелизм для достижения этого, хотя накладные расходы для второго вызова ядра незначительны.

    heisenbug.cu:

    #include <cuda.h> 
    #include <cuda_runtime.h> 
    #include <stdio.h> 
    
    const float r_sol = 6.955E8f; 
    __constant__ float d_fourpoint_constant = 0.2f; 
    
    __launch_bounds__(672, 2) 
    __global__ void heisenkernel(float *d_u, float *d_r, float *d_du, int radius, 
         int numNodesPerGrid, int numBlocksPerSM, int numGridsPerSM, int numGrids) 
    { 
        __syncthreads(); 
        int id_sm   = blockIdx.x/numBlocksPerSM;          // (arbitrary) ID of Streaming Multiprocessor (SM) this thread works upon   - (constant over lifetime of thread) 
        int id_blockOnSM = blockIdx.x % numBlocksPerSM;          // Block number on this specific SM             - (constant over lifetime of thread) 
        int id_r   = id_blockOnSM * (blockDim.x - 2*radius) + threadIdx.x - radius; // Grid point number this thread is to work upon         - (constant over lifetime of thread) 
        int id_grid   = id_sm   * numGridsPerSM;         // Grid ID this thread is to work upon            - (not constant over lifetime of thread) 
    
        while(id_grid < numGridsPerSM * (id_sm + 1)) // this loops over numGridsPerSM grids 
        { 
         __syncthreads(); 
         int id_numInArray  = id_grid * numNodesPerGrid + id_r;  // Entry in array this thread is responsible for (read and possibly write) - (not constant over lifetime of thread) 
         float uchange   = 0.0f; 
         //uchange     = 1.0f;         // if this line is uncommented, results will be computed correctly ("Solution 1") 
         float du    = 0.0f; 
    
         if((threadIdx.x > radius-1) && (threadIdx.x < blockDim.x - radius) && (id_r < numNodesPerGrid) && (id_grid < numGrids)) 
         { 
          if (id_r == 0) // FO-forward difference 
           du = (d_u[id_numInArray+1] - d_u[id_numInArray])/(d_r[id_numInArray+1] - d_r[id_numInArray]); 
          else if (id_r == numNodesPerGrid - 1) // FO-rearward difference 
           du = (d_u[id_numInArray] - d_u[id_numInArray-1])/(d_r[id_numInArray] - d_r[id_numInArray-1]); 
          else if (id_r == 1 || id_r == numNodesPerGrid - 2) //SO-central difference 
           du = (d_u[id_numInArray+1] - d_u[id_numInArray-1])/(d_r[id_numInArray+1] - d_r[id_numInArray-1]); 
          else if(id_r > 1 && id_r < numNodesPerGrid - 2) 
           du = d_fourpoint_constant * ((d_u[id_numInArray+1] - d_u[id_numInArray-1])/(d_r[id_numInArray+1] - d_r[id_numInArray-1])) + (1-d_fourpoint_constant) * ((d_u[id_numInArray+2] - d_u[id_numInArray-2])/(d_r[id_numInArray+2] - d_r[id_numInArray-2])); 
          else 
           du = 0; 
         } 
    
         __syncthreads(); 
         if((threadIdx.x > radius-1 && threadIdx.x < blockDim.x - radius) && (id_r < numNodesPerGrid) && (id_grid < numGrids)) 
         { 
          d_u[ id_numInArray] = d_u[id_numInArray] * uchange;   // if this line is commented out, results will be computed correctly ("Solution 2") 
          d_du[ id_numInArray] = du; 
         } 
    
         __syncthreads(); 
         ++id_grid; 
        } 
    } 
    
    bool gridValuesEqual(float *matarray, uint id0, uint id1, const char *label, int numNodesPerGrid){ 
    
        bool retval = true; 
        for(uint i=0; i<numNodesPerGrid; ++i) 
         if(matarray[id0 * numNodesPerGrid + i] != matarray[id1 * numNodesPerGrid + i]) 
         { 
          printf("value %s at position %u of grid %u not equal that of grid %u: %E != %E, diff: %E\n", 
            label, i, id0, id1, matarray[id0 * numNodesPerGrid + i], matarray[id1 * numNodesPerGrid + i], 
            matarray[id0 * numNodesPerGrid + i] - matarray[id1 * numNodesPerGrid + i]); 
          retval = false; 
         } 
        return retval; 
    } 
    
    int main(int argc, const char* argv[]) 
    { 
        float *d_u; 
        float *d_du; 
        float *d_r; 
    
        float *u_arr; 
        float *du_arr; 
        float *r_arr; 
    
        int numNodesPerGrid = 1300; 
        int numBlocksPerSM = 2; 
        int numGridsPerSM = 37; 
        int numSM   = 7; 
        int TPB    = 672; 
        int radius   = 2; 
        int numGrids  = 259; 
        int memsize_grid = sizeof(float) * numNodesPerGrid; 
    
        int numBlocksPerGrid = numNodesPerGrid/(TPB - 2 * radius) + (numNodesPerGrid%(TPB - 2 * radius) == 0 ? 0 : 1); 
    
        printf("---------------------------------------------------------------------------\n"); 
        printf("--- Heisenbug Extermination Tracker ---------------------------------------\n"); 
        printf("---------------------------------------------------------------------------\n\n"); 
    
        cudaSetDevice(0); 
        cudaDeviceReset(); 
    
        cudaMalloc((void **) &d_u,  memsize_grid * numGrids); 
        cudaMalloc((void **) &d_du,  memsize_grid * numGrids); 
        cudaMalloc((void **) &d_r,  memsize_grid * numGrids); 
    
        u_arr = new float[numGrids * numNodesPerGrid]; 
        du_arr = new float[numGrids * numNodesPerGrid]; 
        r_arr = new float[numGrids * numNodesPerGrid]; 
    
        for(uint k=0; k<numGrids; ++k) 
         for(uint i=0; i<numNodesPerGrid; ++i) 
         { 
          uint index = k * numNodesPerGrid + i; 
    
          if (i < 585) 
           r_arr[index] = i * (6000.0f); 
          else 
          { 
           if (i == 585) 
            r_arr[index] = r_arr[index - 1] + 8.576E-6f * r_sol; 
           else 
            r_arr[index] = r_arr[index - 1] + 1.02102f * (r_arr[index - 1] - r_arr[index - 2]); 
          } 
    
          u_arr[index] = 1E-10f * (i+1); 
          du_arr[index] = 0.0f; 
         } 
    
        /* 
        printf("\n\nbefore kernel start\n\n"); 
        for(uint k=0; k<numGrids; ++k) 
         printf("matrix->du_arr[k*paramH.numNodes + 668]:\t%E\n", du_arr[k*numNodesPerGrid + 668]);//*/ 
    
        bool equal = true; 
        for(int k=1; k<numGrids; ++k) 
        { 
         equal &= gridValuesEqual(u_arr, 0, k, "u", numNodesPerGrid); 
         equal &= gridValuesEqual(du_arr, 0, k, "du", numNodesPerGrid); 
         equal &= gridValuesEqual(r_arr, 0, k, "r", numNodesPerGrid); 
        } 
    
        if(!equal) 
         printf("Input values are not identical for different grids!\n\n"); 
        else 
         printf("All grids contain the same values at same grid points.!\n\n"); 
    
        cudaMemcpy(d_u, u_arr,  memsize_grid * numGrids, cudaMemcpyHostToDevice); 
        cudaMemcpy(d_du, du_arr, memsize_grid * numGrids, cudaMemcpyHostToDevice); 
        cudaMemcpy(d_r, r_arr,  memsize_grid * numGrids, cudaMemcpyHostToDevice); 
    
        printf("Configuration:\n\n"); 
        printf("numNodesPerGrid:\t%i\nnumBlocksPerSM:\t\t%i\nnumGridsPerSM:\t\t%i\n", numNodesPerGrid, numBlocksPerSM, numGridsPerSM); 
        printf("numSM:\t\t\t\t%i\nTPB:\t\t\t\t%i\nradius:\t\t\t\t%i\nnumGrids:\t\t\t%i\nmemsize_grid:\t\t%i\n", numSM, TPB, radius, numGrids, memsize_grid); 
        printf("numBlocksPerGrid:\t%i\n\n", numBlocksPerGrid); 
        printf("Kernel launch parameters:\n\n"); 
        printf("moduleA2_3<<<%i, %i, %i>>>(...)\n\n", numBlocksPerSM * numSM, TPB, 0); 
        printf("Launching Kernel...\n\n"); 
    
        heisenkernel<<<numBlocksPerSM * numSM, TPB, 0>>>(d_u, d_r, d_du, radius, numNodesPerGrid, numBlocksPerSM, numGridsPerSM, numGrids); 
        cudaDeviceSynchronize(); 
    
        cudaMemcpy(u_arr, d_u,  memsize_grid * numGrids, cudaMemcpyDeviceToHost); 
        cudaMemcpy(du_arr, d_du, memsize_grid * numGrids, cudaMemcpyDeviceToHost); 
        cudaMemcpy(r_arr, d_r,  memsize_grid * numGrids, cudaMemcpyDeviceToHost); 
    
        /* 
        printf("\n\nafter kernel finished\n\n"); 
        for(uint k=0; k<numGrids; ++k) 
         printf("matrix->du_arr[k*paramH.numNodes + 668]:\t%E\n", du_arr[k*numNodesPerGrid + 668]);//*/ 
    
        equal = true; 
        for(int k=1; k<numGrids; ++k) 
        { 
         equal &= gridValuesEqual(u_arr, 0, k, "u", numNodesPerGrid); 
         equal &= gridValuesEqual(du_arr, 0, k, "du", numNodesPerGrid); 
         equal &= gridValuesEqual(r_arr, 0, k, "r", numNodesPerGrid); 
        } 
    
        if(!equal) 
         printf("Results are wrong!!\n"); 
        else 
         printf("All went well!\n"); 
    
        cudaFree(d_u); 
        cudaFree(d_du); 
        cudaFree(d_r); 
    
        delete [] u_arr; 
        delete [] du_arr; 
        delete [] r_arr; 
    
        return 0; 
    } 
    

    Makefile:

    CUDA   = 1 
    DEFINES   = 
    
    ifeq ($(CUDA), 1) 
        DEFINES  += -DCUDA 
        CUDAPATH = /usr/local/cuda-6.5 
        CUDAINCPATH = -I$(CUDAPATH)/include 
        CUDAARCH = -arch=sm_20 
    endif 
    
    CXX    = g++ 
    CXXFLAGS  = -pipe -g -std=c++0x -fPIE -O0 $(DEFINES) 
    VALGRIND  = valgrind 
    VALGRIND_FLAGS = -v --leak-check=yes --log-file=out.memcheck 
    CUDAMEMCHECK = cuda-memcheck 
    CUDAMC_FLAGS = --tool memcheck 
    RACECHECK  = $(CUDAMEMCHECK) 
    RACECHECK_FLAGS = --tool racecheck 
    INCPATH   = -I. $(CUDAINCPATH) 
    LINK   = g++ 
    LFLAGS   = -O0 
    LIBS   = 
    
    ifeq ($(CUDA), 1) 
        NVCC  = $(CUDAPATH)/bin/nvcc 
        LIBS  += -L$(CUDAPATH)/lib64/ 
        LIBS  += -lcuda -lcudart -lcudadevrt 
        NVCCFLAGS = -g -G -O0 --ptxas-options=-v 
        NVCCFLAGS += -lcuda -lcudart -lcudadevrt -lineinfo --machine 64 -x cu $(CUDAARCH) $(DEFINES) 
    endif 
    
    all: 
        $(NVCC) $(NVCCFLAGS) $(INCPATH) -c -o $(DST_DIR)heisenbug.o $(SRC_DIR)heisenbug.cu 
        $(LINK) $(LFLAGS) -o heisenbug heisenbug.o $(LIBS) 
    
    clean: 
        rm heisenbug.o 
        rm heisenbug 
    
    memrace: all 
        ./heisenbug > out 
        $(VALGRIND) $(VALGRIND_FLAGS) ./heisenbug > out.memcheck.log 
        $(CUDAMEMCHECK) $(CUDAMC_FLAGS) ./heisenbug > out.cudamemcheck 
        $(RACECHECK) $(RACECHECK_FLAGS) ./heisenbug > out.racecheck 
    

    ответ

    9

    Обратите внимание, что в полном объеме вашей рецензии, я не вижу вопрос, который явно просил, поэтому я в ответ на:

    Я с нетерпением жду возможности узнать, что здесь происходит.

    У вас есть состояние гонки на d_u.

    своего собственного утверждения:

    • для того, чтобы сохранить блоки indepentend друг от друга, небольшое перекрытие на сетке вводится (точек сетки 666, 667, 668, 669 каждой сетка считываются с двумя нитями из разных блоков, хотя только один поток записывает на них, то это перекрытие, где возникают проблемы)

    Кроме того, если вы закомментировать запись в d_u, согласно вашему заявлению в коде , проблема исчезает.

    Блокировочные блоки CUDA могут выполняться в любом порядке. У вас есть по крайней мере 2 различных блоков, которые читают из точек сетки 666, 667, 668, 669. Результаты будут отличаться в зависимости от случая на самом деле происходит:

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

    Блоки не являются независимыми друг от друга (в отличие от вашего утверждения), если один блок считывает значение, которое может быть записано другим блоком. Порядок выполнения блока определит результат в этом случае, а CUDA не укажет порядок выполнения блока.

    Отметьте, что cuda-memcheck с опцией -tool racecheckonly captures race conditions related to __shared__ memory usage. Ваше ядро, как опубликовано, не использует память __shared__, поэтому я бы не ожидал, что cuda-memcheck сообщит что-нибудь.

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

    in-kernel printf представляет собой дорогостоящий вызов функции, записывающий в буфер глобальной памяти. Таким образом, это также влияет на поведение/шаблоны выполнения. И если вы печатаете большой объем данных, превышающих буферные строки вывода, эффект является чрезвычайно дорогостоящим (с точки зрения времени выполнения) в случае переполнения буфера.

    В отличие от Монетного двора Linux not a supported distro for CUDA, насколько я могу судить. Однако я не думаю, что это имеет отношение к вашей проблеме; Я могу воспроизвести поведение в поддерживаемой конфигурации.

    +0

    Точно, что происходит, спасибо за тестирование его на другой системе. –