Как запускается ядро CUDA?

Я создал простое приложение CUDA для добавления двух матриц. Он отлично компилируется. Я хочу знать, как ядро будет запущено всеми потоками и каким будет поток внутри CUDA? Я имею в виду, каким образом каждый поток будет выполнять каждый элемент матриц.

Я знаю, что это очень базовая концепция, но я этого не знаю. Я смущен относительно потока.

3 ответов


вы запускаете систему блоков.

блоки нераздельно назначаются мультипроцессорам (где количество блоков на мультипроцессоре определяет объем доступной общей памяти).

блоки далее разделяются на искривления. Для графического процессора Fermi, который является 32 потоками, которые либо выполняют ту же инструкцию, либо неактивны (потому что они разветвились, например, выйдя из цикла раньше, чем соседи в том же warp или не принимая if Они сделали). На Fermi GPU не более двух искривлений работают на одном мультипроцессоре за раз.

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

это планирование происходит прозрачно. То есть, вам не придется думать об этом слишком много. Однако, вы можете использовать предопределенные целочисленные векторы threadIdx (где мой поток внутри блока?), blockDim (сколько один блок?), blockIdx (где мой блок в сетке?) и gridDim (насколько велика сетка?) разделить работу (чтение: вход и выход) между потоками. Вы также можете прочитать, как эффективно получить доступ к различным типам памяти ( поэтому несколько потоков могут обслуживаться в рамках одной транзакции), но это ведет тема.

NSight предоставляет графический отладчик, который дает вам хорошее представление о том, что происходит на устройстве, как только вы прошли через джунгли жаргона. То же самое касается его профилировщика в отношении тех вещей, которые вы не увидите в отладчике (например, причины остановки или давление памяти).

вы можете синхронизировать все потоки внутри сетки (все есть) с помощью другого запуска ядра. Для неперекрывающегося последовательного выполнения ядра дальнейшая синхронизация не выполняется необходимый.

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

вы можете синхронизировать все потоки внутри одного блока с внутренней инструкцией __syncthreads() (все потоки будут активны после этого-хотя, как всегда, не более двух искривлений могут работать на графическом процессоре Fermi). Потоки внутри одного блока могут взаимодействовать через общая или глобальная память с использованием атомарных операций (для арифметики) или соответствующих ограждений памяти (для загрузки или доступа к хранилищу).

как упоминалось ранее, все потоки в warp всегда "синхронизированы", хотя некоторые из них могут быть неактивны. Они могут взаимодействовать через общую или глобальную память (или "перестроение полосы" на предстоящем оборудовании с вычислительной способностью 3). Вы можете использовать атомарные операции (для арифметики) и изменчивые общие или глобальные переменные (загрузка или доступ к хранилищу последовательно в пределах одного и того же варпа). Квалификатор volatile указывает компилятору всегда обращаться к памяти и никогда не регистрирует состояние, которое не видно другим потокам.

кроме того, существуют функции голосования warp-wide, которые могут помочь Вам принимать решения о ветвях или вычислять целочисленные (префиксные) суммы.

хорошо, это в основном все. Надеюсь, это поможет. Был хороший поток написания: -).


давайте возьмем пример добавления 4 * 4 матриц.. у вас есть две матрицы A и B, имеющие размеры 4*4..

int main()
{
 int *a, *b, *c;            //To store your matrix A & B in RAM. Result will be stored in matrix C
 int *ad, *bd, *cd;         // To store matrices into GPU's RAM. 
 int N =4;                 //No of rows and columns.

 size_t size=sizeof(float)* N * N;

 a=(float*)malloc(size);     //Allocate space of RAM for matrix A
 b=(float*)malloc(size);     //Allocate space of RAM for matrix B

//allocate memory on device
  cudaMalloc(&ad,size);
  cudaMalloc(&bd,size);
  cudaMalloc(&cd,size);

//initialize host memory with its own indices
    for(i=0;i<N;i++)
      {
    for(j=0;j<N;j++)
         {
            a[i * N + j]=(float)(i * N + j);
            b[i * N + j]= -(float)(i * N + j);
         }
      }

//copy data from host memory to device memory
     cudaMemcpy(ad, a, size, cudaMemcpyHostToDevice);
     cudaMemcpy(bd, b, size, cudaMemcpyHostToDevice);

//calculate execution configuration 
   dim3 grid (1, 1, 1); 
   dim3 block (16, 1, 1);

//each block contains N * N threads, each thread calculates 1 data element

    add_matrices<<<grid, block>>>(ad, bd, cd, N);

   cudaMemcpy(c,cd,size,cudaMemcpyDeviceToHost);  
   printf("Matrix A was---\n");
    for(i=0;i<N;i++)
    {
        for(j=0;j<N;j++)
            printf("%f ",a[i*N+j]);
        printf("\n");
    }

   printf("\nMatrix B was---\n");
   for(i=0;i<N;i++)
    {
        for(j=0;j<N;j++)
            printf("%f ",b[i*N+j]);
        printf("\n");
    }

    printf("\nAddition of A and B gives C----\n");
    for(i=0;i<N;i++)
    {
        for(j=0;j<N;j++)
            printf("%f ",c[i*N+j]);   //if correctly evaluated, all values will be 0
        printf("\n");
    }



    //deallocate host and device memories
    cudaFree(ad); 
    cudaFree(bd); 
    cudaFree (cd);

    free(a);
    free(b);
    free(c);

    getch();
    return 1;
}

/////Kernel Part

__global__ void add_matrices(float *ad,float *bd,float *cd,int N)
{
  int index;
  index = blockIDx.x * blockDim.x + threadIDx.x            

  cd[index] = ad[index] + bd[index];
}

давайте возьмем пример добавления матриц 16*16.. у вас есть две матрицы A и B, имеющие размер 16*16..

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

теперь, одна сетка запускается с одной функцией ядра.. Сетка может иметь максимум 65,535 нет блоков, которые могут быть расположены в 3-х мерных способов. (65535 * 65535 * 65535).

каждый блок в сетке может иметь максимум 1024 без резьбы.Эти потоки можно также аранжировать в 3 габаритных путях (1024 * 1024 * 64)

теперь наша проблема-добавление матриц 16 * 16..

A | 1  2  3  4 |        B | 1  2  3  4 |      C| 1  2  3  4 |
  | 5  6  7  8 |   +      | 5  6  7  8 |   =   | 5  6  7  8 | 
  | 9 10 11 12 |          | 9 10 11 12 |       | 9 10 11 12 |  
  | 13 14 15 16|          | 13 14 15 16|       | 13 14 15 16|

нам нужно 16 потоков для выполнения вычисление.

i.e. A(1,1) + B (1,1) = C(1,1)
     A(1,2) + B (1,2) = C(1,2) 
     .        .          .
     .        .          . 
     A(4,4) + B (4,4) = C(4,4) 

все эти потоки будут выполняться одновременно. Поэтому нам нужен блок с 16 потоками. Для нашего удобства мы организуем нитки в (16 * 1 * 1) путь в блок Поскольку нет потоков 16, поэтому нам нужен один блок только для хранения этих 16 потоков.

Итак, конфигурация сетки будет dim3 Grid(1,1,1) т. е. сетка будет иметь только один блок и конфигурация блока будет dim3 block(16,1,1) т. е. блок будет иметь 16 потоков, расположенных в столбце.

следующий программа даст вам четкое представление о ее выполнении.. Понимание части индексирования (т. е. threadIDs, blockDim, blockID) является важной частью. Вам нужно пройти через литературу CUDA. Как только у вас будет четкое представление об индексации, вы выиграете половину битвы! Поэтому проведите некоторое время с книгами cuda, различными алгоритмами и бумагой-карандашом, конечно!


попробовать 'Cuda-gdb', который является отладчиком CUDA.