Uso CUDA há algumas semanas, mas tenho algumas dúvidas sobre a alocação de blocos / warps / threads. Estou estudando a arquitetura do ponto de vista didático (projeto universitário), portanto, atingir o máximo desempenho não é minha preocupação.
Antes de mais, gostaria de entender se entendi esses fatos:
O programador escreve um kernel e organiza sua execução em uma grade de blocos de threads.
Cada bloco é atribuído a um Streaming Multiprocessor (SM). Uma vez atribuído, ele não pode migrar para outra SM.
Cada SM divide seus próprios blocos em Warps (atualmente com um tamanho máximo de 32 threads). Todos os threads em um warp são executados simultaneamente nos recursos do SM.
A execução real de um encadeamento é realizada pelos núcleos CUDA contidos no SM. Não há mapeamento específico entre threads e núcleos.
Se um warp contiver 20 threads, mas atualmente houver apenas 16 núcleos disponíveis, o warp não será executado.
Por outro lado, se um bloco contiver 48 threads, ele será dividido em 2 warps e eles serão executados em paralelo, desde que haja memória suficiente disponível.
Se um encadeamento iniciar em um núcleo, ele será interrompido para acesso à memória ou para uma operação longa de ponto flutuante, sua execução poderá continuar em um núcleo diferente.
Eles estão corretos?
Agora, eu tenho uma GeForce 560 Ti, de acordo com as especificações, ela é equipada com 8 SM, cada uma contendo 48 núcleos CUDA (384 núcleos no total).
Meu objetivo é garantir que todos os núcleos da arquitetura executem as mesmas instruções. Supondo que meu código não exija mais registro do que os disponíveis em cada SM, imaginei abordagens diferentes:
Crio 8 blocos de 48 threads cada, para que cada SM tenha 1 bloco para executar. Nesse caso, os 48 threads serão executados em paralelo no SM (explorando todos os 48 núcleos disponíveis para eles)?
Existe alguma diferença se eu lançar 64 blocos de 6 threads? (Supondo que eles sejam mapeados igualmente entre as SMs)
Se eu "submergir" a GPU no trabalho agendado (criando 1024 blocos de 1024 threads cada, por exemplo), é razoável supor que todos os núcleos serão usados em um determinado ponto e executem os mesmos cálculos (assumindo que os threads nunca parar)?
Existe alguma maneira de verificar essas situações usando o criador de perfil?
Existe alguma referência para essas coisas? Li o guia de programação da CUDA e os capítulos dedicados à arquitetura de hardware em "Programando processadores massivamente paralelos" e "Design e desenvolvimento de aplicativos CUDA"; mas não consegui uma resposta precisa.
fonte
Respostas:
Duas das melhores referências são
Vou tentar responder a cada uma das suas perguntas.
O programador divide o trabalho em threads, threads em blocos de threads e blocos de threads em grades. O distribuidor de trabalho de computação aloca blocos de encadeamento para Multiprocessadores de Streaming (SMs). Depois que um bloco de threads é distribuído para uma SM, os recursos para o bloco de threads são alocados (warps e memória compartilhada) e os threads são divididos em grupos de 32 threads chamados warps. Depois que um warp é alocado, ele é chamado de warp ativo. Os dois agendadores de warp selecionam dois warps ativos por ciclo e enviam warps para as unidades de execução. Para mais detalhes sobre unidades de execução e envio de instruções, consulte 1 p.7-10 e 2 .
4 ' . Existe um mapeamento entre laneid (índice de threads em um warp) e um núcleo.
5 ' . Se um warp contiver menos de 32 threads, na maioria dos casos, será executado da mesma forma que se tivesse 32 threads. Os warps podem ter menos de 32 encadeamentos ativos por vários motivos: o número de encadeamentos por bloco não é divisível por 32, o programa executa um bloco divergente para que os encadeamentos que não seguiram o caminho atual sejam marcados como inativos ou um encadeamento no warp encerrado.
6 ' . Um bloco de encadeamento será dividido em WarpsPerBlock = (ThreadsPerBlock + WarpSize - 1) / WarpSize Não é necessário que os planejadores de warp selecionem dois warps do mesmo bloco de encadeamento.
7 ' . Uma unidade de execução não irá parar em uma operação de memória. Se um recurso não estiver disponível quando uma instrução estiver pronta para ser despachada, a instrução será despachada novamente no futuro quando o recurso estiver disponível. Os warps podem parar em barreiras, operações de memória, operações de textura, dependências de dados, ... Um warp parado é inelegível para ser selecionado pelo agendador de warp. No Fermi, é útil ter pelo menos 2 warps elegíveis por ciclo para que o agendador de warp possa emitir uma instrução.
Consulte a referência 2 para obter diferenças entre um GTX480 e um GTX560.
Se você ler o material de referência (alguns minutos), acho que descobrirá que seu objetivo não faz sentido. Vou tentar responder aos seus pontos.
1 ' . Se você iniciar o kernel <<< 8, 48 >>>, obterá 8 blocos cada um com 2 warps de 32 e 16 threads. Não há garantia de que esses 8 blocos sejam atribuídos a diferentes SMs. Se 2 blocos forem alocados para uma SM, é possível que cada agendador de warp possa selecionar um warp e executá-lo. Você usará apenas 32 dos 48 núcleos.
2 ' . Há uma grande diferença entre 8 blocos de 48 threads e 64 blocos de 6 threads. Vamos supor que seu kernel não tenha divergências e cada thread execute 10 instruções.
Para obter a eficiência ideal, a divisão do trabalho deve estar em múltiplos de 32 threads. O hardware não unirá os threads de diferentes warps.
3 ' . Um GTX560 pode ter 8 blocos SM * 8 = 64 blocos por vez ou 8 warps SM * 48 = 512 warps se o kernel não atingir o máximo de registros ou memória compartilhada. A qualquer momento, parte do trabalho estará ativa nas SMs. Cada SM possui várias unidades de execução (mais de núcleos CUDA). Quais recursos estão em uso em um determinado momento depende dos agendadores de warp e do mix de instruções do aplicativo. Se você não executar operações TEX, as unidades TEX ficarão ociosas. Se você não fizer uma operação especial de ponto flutuante, as unidades SUFU ficarão inativas.
4 ' . Nsight paralelo e o Visual Profiler mostram
uma. IPC executado
b. IPC emitido
c. urdiduras ativas por ciclo ativo
d. urdidões elegíveis por ciclo ativo (apenas Nsight)
e motivos de distorção (apenas no Nsight)
f. threads ativos por instrução executada
O criador de perfil não mostra a porcentagem de utilização de nenhuma das unidades de execução. Para o GTX560, uma estimativa aproximada seria IssuedIPC / MaxIPC. Para o MaxIPC, assuma que GF100 (GTX480) é 2 GF10x (GTX560) é 4, mas o alvo é 3 é um alvo melhor.
fonte
"E. Se um warp contiver 20 threads, mas atualmente houver apenas 16 núcleos disponíveis, o warp não será executado."
está incorreto. Você está confundindo núcleos no sentido usual (também usado em CPUs) - o número de "multiprocessadores" em uma GPU, com núcleos no marketing da nVIDIA ("nossa placa possui milhares de núcleos CUDA").
Um warp em si só pode ser agendado em um único núcleo (= multiprocessador) e pode executar até 32 threads ao mesmo tempo; não pode usar mais que um único núcleo.
O número "48 warps" é o número máximo de warps ativos (warps que podem ser escolhidos para serem agendados para trabalhar no próximo ciclo, a qualquer ciclo) por multiprocessador, nas GPUs nVIDIA com Compute Capability 2.x; e esse número corresponde a 1536 = 48 x 32 threads.
Resposta baseada neste webinar
fonte