Entendendo as dimensões da grade CUDA, dimensões de blocos e organização de threads (explicação simples) [fechado

StackOverflow https://stackoverflow.com/questions/2392250

  •  25-09-2019
  •  | 
  •  

Pergunta

Como os threads são organizados para serem executados por uma GPU?

Foi útil?

Solução

Hardware

Se um dispositivo GPU tiver, por exemplo, 4 unidades multiprocessantes, e elas podem executar 768 fios cada: em um determinado momento, não mais de 4*768 threads estarão realmente em paralelo (se você planejou mais threads, eles estarão esperando a sua vez).

Programas

Os threads são organizados em blocos. Um bloco é executado por uma unidade multiprocessante. Os fios de um bloco podem ser indentificados (indexados) usando índices 1dimension (x), 2dimensions (x, y) ou 3dim (x, y, z), mas em qualquer caso xyZ <= 768 para o nosso exemplo (outras restrições se aplicam a x, y, z, consulte o guia e a capacidade do seu dispositivo).

Obviamente, se você precisar de mais do que esses 4*768 threads, precisará de mais de 4 blocos. Os blocos também podem ser indexados 1D, 2D ou 3D. Há uma fila de blocos esperando para entrar na GPU (porque, em nosso exemplo, a GPU possui 4 multiprocessadores e apenas 4 blocos estão sendo executados simultaneamente).

Agora um caso simples: processando uma imagem de 512x512

Suponha que queremos um thread para processar um pixel (i, j).

Podemos usar blocos de 64 threads cada. Então precisamos de 512*512/64 = 4096 blocos (para ter 512x512 threads = 4096*64)

É comum organizar (para facilitar a indexação da imagem) os encadeamentos em blocos 2D com blockdim = 8 x 8 (os 64 encadeamentos por bloco). Eu prefiro chamá -lo de threadsperblock.

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

e 2d griddim = 64 x 64 blocos (os blocos 4096 necessários). Eu prefiro chamá -lo de números.

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

O kernel é lançado assim:

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

Finalmente: haverá algo como "uma fila de 4096 blocos", onde um bloco está esperando para receber um dos multiprocessadores da GPU para executar seus 64 threads.

No núcleo, o pixel (i, j) a ser processado por um encadeamento é calculado desta maneira:

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

Outras dicas

Suponha que uma GPU 9800GT: 14 multiprocessadores, cada um possui 8 processadores de threads e Warpsize é 32, o que significa que cada fhreadprocessador lida com até 32 threads. 14*8*32 = 3584 é o número máximo de encadeamentos de cuncurrent atuais.

Se você executar esse kernel com mais de 3584 threads (digamos 4000 threads e não é importante como você define o bloco e a grade. A GPU os tratará como o mesmo):

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

Então a ordem de execução dessas duas funções é a seguinte:

1.FUNC1 é executado para os primeiros 3584 threads

2.FUNC2 é executado para os primeiros 3584 threads

3.FUNC1 é executado para os fios restantes

4.FUNC2 é executado para os fios restantes

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top