CUDA как получить размер сетки, блока, потока и распараллелить вычисление неквадратичной матрицы

#c #visual-studio-2008 #gpu #cuda

#c #visual-studio-2008 #графический процессор #cuda

Вопрос:

Я новичок в CUDA и нуждаюсь в помощи в понимании некоторых вещей. Мне нужна помощь в распараллеливании этих двух циклов for. В частности, как настроить dimBlock и dimGrid, чтобы ускорить выполнение. Я знаю, что это похоже на пример добавления вектора в sdk, но этот пример предназначен только для квадратных матриц, и когда я пытаюсь изменить этот код для моей матрицы 128 x 1024, он не работает должным образом.

 __global__ void mAdd(float* A, float* B, float* C)
{
    for(int i = 0; i < 128; i  )
    {
        for(int j = 0; j < 1024; j  )
        {
            C[i * 1024   j] = A[i * 1024   j]   B[i * 1024   j];
        }
    }
}
  

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

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

1.В pycuda это просто C[i] = A[i] B[i] demo.py

Ответ №1:

Как вы уже написали, это ядро полностью последовательное. Каждый поток, запущенный для его выполнения, будет выполнять ту же работу.

Основная идея, лежащая в основе CUDA (а также OpenCL и других подобных моделей программирования типа «одна программа, несколько данных»), заключается в том, что вы выполняете операцию «параллелизации данных» — то есть ту, при которой одна и та же, в значительной степени независимая операция должна выполняться много раз — и пишете ядро, которое выполняет эту операцию. Затем запускается большое количество (полу) автономных потоков для выполнения этой операции над набором входных данных.

В вашем примере добавления массива параллельная операция с данными

 C[k] = A[k]   B[k];
  

для всех k от 0 до 128 * 1024. Каждая операция сложения полностью независима и не требует упорядочивания, и поэтому может выполняться другим потоком. Чтобы выразить это в CUDA, можно написать ядро следующим образом:

 __global__ void mAdd(float* A, float* B, float* C, int n)
{
    int k = threadIdx.x   blockIdx.x * blockDim.x;

    if (k < n)
        C[k] = A[k]   B[k];
}
  

[отказ от ответственности: код написан в браузере, не тестировался, используйте на свой страх и риск]

Здесь внутренний и внешний циклы из последовательного кода заменяются одним потоком CUDA на операцию, и я добавил в код проверку ограничения, чтобы в случаях, когда запускается больше потоков, чем требуется для выполнения операций, переполнения буфера произойти не могло. Если ядро затем запускается следующим образом:

 const int n = 128 * 1024;
int blocksize = 512; // value usually chosen by tuning and hardware constraints
int nblocks = n / blocksize; // value determine by block size and total work

madd<<<nblocks,blocksize>>>mAdd(A,B,C,n);
  

Затем 256 блоков, каждый из которых содержит 512 потоков, будут запущены на аппаратном обеспечении GPU для параллельного выполнения операции сложения массива. Обратите внимание, что если бы размер входных данных не был выражен в виде округления, кратного размеру блока, количество блоков необходимо было бы округлить в большую сторону, чтобы охватить полный набор входных данных.

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

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

1. int k = threadIdx.x gridDim.x * blockDim.x; Это, конечно, неверно? gridDim.x * blockDim.x в вашем примере всегда будет 256 * 512. Должно быть int k = threadIdx.x blockIdx.x * blockDim.x; Я попытался отредактировать его, но был отклонен.

2. Предупреждение для читателя skim: nblocks = ceil (n / nthreads); // если ваши данные не разделяются идеально.

3. @ofer.sheffer: Я написал «Обратите внимание, что если размер входных данных не был выражен как хорошее круглое кратное размеру блока, количество блоков нужно было бы округлить в большую сторону, чтобы охватить полный набор входных данных».. Это недостаточно ясно?

4. @talonmies, ваш ответ очень хорош, и я поддержал его. С другой стороны, когда я читал это, я подумал «он пропустил 1» на случай, если данные распределяются неравномерно … затем я продолжил читать несколько других вещей и вернулся сюда, чтобы закончить чтение, и я заметил, что вы написали это в. Как беглый читатель, который обычно сначала просматривает код, а потом рассматривает возможность прочтения каждого слова, я полагаю, что мое предупреждение помогло бы мне в будущем.

5. Откуда я знаю nthreads ? Это не blocksize количество потоков?