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


Как организованы потоки для выполнения графическим процессором?

2 134

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 x 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;

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

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

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

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

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

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

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

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