Понимание размеров сетки CUDA, размеров блоков и организации потоков (простое объяснение) [закрыто]

как организованы потоки для выполнения GPU?

2 ответов


оборудование

если устройство GPU имеет, например, 4 многопроцессорных блока, и они могут запускать 768 потоков каждый: тогда в данный момент не более 4*768 потоков будут действительно работать параллельно (если вы запланировали больше потоков, они будут ждать своей очереди).

программа

потоки организованы в блоки. Блок выполняется многопроцессорным блоком. Потоки блока могут быть индентифицированы (индексированы) с помощью 1Dimension (x), 2Dimensions (x,y) или Индексы 3Dim (x,y, z), но в любом случае xyz

очевидно, что если вам нужно больше, чем эти 4*768 потоков, вам нужно более 4 блоков. Блоки также могут быть проиндексированы 1D, 2D или 3D. Существует очередь блоков, ожидающих входа GPU (потому что, в нашем примере, GPU имеет 4 мультипроцессора и только 4 блока выполняется одновременно).

теперь простой случай: обработка изображения с разрешением 512 х 512

Предположим, мы хотим, чтобы один поток обрабатывал один пиксель (i,j).

мы можем использовать блоки по 64 потока каждый. Тогда нам нужно 512*512/64 = 4096 блоков (так, чтобы иметь 512x512 потоков = 4096*64)

обычно организовать (чтобы упростить индексирование изображения) потоки в 2D-блоках, имеющих blockDim = 8 x 8 (64 потока на блок). Я предпочитаю называть его threadsPerBlock.

dim3 threadsPerBlock(8, 8);  // 64 threads

и 2D gridDim = 64 х 64 блоков (4096 блоков необходимый.) Я предпочитаю называть это тупиками.

dim3 numBlocks(imageWidth/threadsPerBlock.x,  /* for instance 512/8 = 64*/
              imageHeight/threadsPerBlock.y); 

ядро запускается следующим образом:

myKernel <<<numBlocks,threadsPerBlock>>>( /* params for the kernel function */ );       

наконец: будет что-то вроде "очереди из 4096 блоков", где блок ждет, чтобы ему был назначен один из мультипроцессоров GPU для выполнения его 64 потоков.

в ядре пиксель (i, j), обрабатываемый потоком, рассчитывается следующим образом:

uint i = (blockIdx.x * blockDim.x) + threadIdx.x;
uint j = (blockIdx.y * blockDim.y) + threadIdx.y;

предположим 9800ГТ ГПУ: 14 мультипроцессоров, каждый имеет 8 threadprocessors и warpsize 32 что означает, что каждый threadprocessor обрабатывает до 32 потоков. 14*8*32=3584-максимальное число близких cuncurrent нитей.

Если вы выполняете это ядро с более чем 3584 потоками (скажем, 4000 потоков, и не важно, как вы определяете блок и сетку. gpu будет относиться к ним так же):

func1();
__syncthreads();
func2();
__syncthreads();

тогда порядок выполнения этих двух функций выглядит следующим образом следует:

1.func1 выполняется для первых 3584 потоков

2.func2 выполняется для первых 3584 потоков

3.func1 выполняется для остальных потоков

4.func2 выполняется для остальных потоков