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


161

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


O Guia de Programação da CUDA deve ser um bom ponto de partida para isso. Eu também recomendaria verificar a introdução da CUDA aqui .
Tom

Respostas:


287

Hardware

Se um dispositivo GPU tiver, por exemplo, 4 unidades de multiprocessamento e eles puderem executar 768 threads cada: então, em um determinado momento, não mais do que 4 * 768 threads estarão em execução paralelamente (se você planejou mais threads, eles estarão aguardando a sua vez).

Programas

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

Obviamente, se você precisar de mais do que os threads 4 * 768, precisará de mais de 4 blocos. Os blocos também podem ser indexados em 1D, 2D ou 3D. Há uma fila de blocos aguardando 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 que um thread processe um pixel (i, j).

Podemos usar blocos de 64 threads cada. Então precisamos de 512 * 512/64 = 4096 blocos (para ter segmentos de 512x512 = 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 threadsPerBlock.

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

e gridDim 2D = 64 x 64 blocos (são necessários os 4096 blocos). Eu prefiro chamá-lo numBlocks.

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", em que um bloco aguarda a atribuição de um dos multiprocessadores da GPU para executar seus 64 threads.

No kernel, o pixel (i, j) a ser processado por um thread é calculado da seguinte maneira:

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

11
Se cada bloco pode executar 768 threads, por que usar apenas 64? Se você usar o limite máximo de 768, terá menos blocos e, portanto, melhor desempenho.
Aliza

10
@Aliza: os blocos são lógicos , o limite de 768 threads é para cada unidade de processamento físico . Você usa blocos de acordo com as especificações do seu problema para distribuir o trabalho pelos encadeamentos. Não é provável que você sempre possa usar blocos de 768 threads para todos os problemas que tiver. Imagine que você precise processar uma imagem de 64x64 (4096 pixels). 4096/768 = 5,3333333 blocos?
cibercitizen1

1
bloco é lógico, mas cada bloco é atribuído a um núcleo. se houver mais blocos que o núcleo, os blocos serão colocados na fila até que os núcleos se tornem livres. No seu exemplo, você pode usar 6 blocos e fazer com que os threads extras não façam nada (2/3 dos threads no sexto bloco).
Aliza #

3
@ cibercitizen1 - Eu acho que o ponto de Aliza é bom: se possível, queremos usar o maior número possível de threads por bloco. Se houver uma restrição que exija menos encadeamentos, é melhor explicar por que esse pode ser o caso em um segundo exemplo (mas ainda assim explicar o caso mais simples e desejável).

6
@ thouis Sim, talvez. Mas o caso é que a quantidade de memória necessária para cada thread depende do aplicativo. Por exemplo, no meu último programa, cada thread chama uma função de otimização no mínimo quadrado, exigindo "muita" memória. Tanto, que os blocos não podem ser maiores que threads 4x4. Mesmo assim, a aceleração obtida foi dramática, em comparação à versão seqüencial.
cibercitizen1

9

Suponha uma GPU 9800GT:

  • possui 14 multiprocessadores (SM)
  • cada SM possui 8 processadores de threads (AKA stream-processadores, SP ou núcleos)
  • permite até 512 threads por bloco
  • warpsize é 32 (o que significa que cada um dos processadores de thread de 14x8 = 112 pode agendar até 32 threads)

https://www.tutorialspoint.com/cuda/cuda_threads.htm

Um bloco não pode ter threads mais ativos que 512, portanto, __syncthreadspode sincronizar apenas um número limitado de threads. ou seja, se você executar o seguinte com 600 threads:

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

então o kernel deve rodar duas vezes e a ordem de execução será:

  1. func1 é executado para os primeiros 512 threads
  2. func2 é executado para os primeiros 512 threads
  3. func1 é executado para os threads restantes
  4. func2 é executado para os threads restantes

Nota:

O ponto principal __syncthreadsé uma operação de todo o bloco e não sincroniza todos os threads.


Não tenho certeza sobre o número exato de threads que __syncthreadspodem ser sincronizados, pois você pode criar um bloco com mais de 512 threads e deixar o warp manipular o agendamento. No meu entender, é mais preciso dizer: func1 é executado pelo menos nos primeiros 512 threads.

Antes de editar esta resposta (em 2010), medi os threads de 14x8x32 que eram sincronizados usando __syncthreads.

Eu apreciaria muito se alguém testasse isso novamente para obter uma informação mais precisa.


O que acontece se func2 () depende dos resultados de func1 (). Eu acho que isso é errado
Chris

@ Chris Escrevi isso há sete anos, mas se bem me lembro, fiz um teste e cheguei à conclusão de que kernels com mais threads do que gpu se comportam dessa maneira. Se você testar este caso e alcançar um resultado diferente, terei que excluir esta postagem.
Bizhan

Desculpe, acho que isso está errado, também, que a GPU pode executar simultaneamente apenas 112 threads.
Steven Lu

@StevenLu você já tentou? Também não acho que 112 threads simultâneos façam sentido para uma GPU. 112 é o número de processadores de fluxo. Mal me lembro da CUDA agora :)
Bizhan

1
@StevenLu, o número máximo de threads não é o problema aqui, __syncthreadsé uma operação em todo o bloco e o fato de ele não sincronizar todos os threads é um incômodo para os alunos da CUDA. Atualizei minha resposta com base nas informações que você me deu. Eu realmente gostei disso.
Bizhan
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.