O que acontece nos bastidores quando você executa um kernel CUDA
Aprofundamento CEVIU
Aprofundamento
Quando você executa um kernel CUDA com a sintaxe vadd<<<4096, 256>>>(da, db, dc, n), não há chamada direta à GPU, há uma cadeia de abstrações profundas. O código passa primeiro por um pipeline de compilação com nvcc, que orquestra o cicc (para gerar PTX, uma ISA virtual) e o ptxas (para gerar SASS, o código de máquina real do chip). O resultado é um fatbinary: um executável que embute tanto o SASS nativo quanto o PTX comprimido, este último serve como fallback para GPUs mais novas, que recompilam o PTX em tempo de carregamento. Na execução, o host não chama funções na GPU; ele preenche um buffer de argumentos, aciona o runtime CUDA, que delega ao driver libcuda.so.1, que por sua vez monta um comando de lançamento no formato QMD (Queue Management Descriptor) e o envia via pushbuffer e escrita MMIO no registrador doorbell.
O kernel então é agendado nos multiprocessadores de streaming (SMs) da GPU. Cada SM executa threads em grupos de 32 chamados warps, seguindo o modelo SIMT. Em arquiteturas modernas como a H100, cada SM tem até 4 warp schedulers, permitindo alternância quase instantânea entre warps bloqueados, sem troca de contexto. Os argumentos do kernel são lidos da constant bank 0, uma região pequena e rápida de memória controlada pelo driver. A memória global é acessada coalescidamente por padrão, mas divergência de warp ou acesso não alinhado pode reduzir drasticamente o throughput. Desde a CUDA 11, buffers persistentes e Unified Memory com cudaMemPrefetchAsync permitem maior controle sobre localização e migração de dados entre CPU e GPU.
Por que isso importa
Entender esse fluxo não é só curiosidade técnica: é essencial para depurar gargalos reais. Um kernel que roda lentamente pode não ser problema de algoritmo, mas de má alocação de registradores (detectável com ncu --set full), de warp divergence em branches condicionais mal projetados, ou de uso ineficiente da memória compartilhada. A pesquisa mostra que otimizações automáticas de kernels, como as feitas por sistemas multiagentes em abril de 2026 para GPUs Blackwell, conseguiram ganhos médios de 38% em velocidade simplesmente ajustando parâmetros de lançamento, uso de memória e agrupamento de threads. Isso impacta diretamente custo operacional, latência de inferência e eficiência energética em cargas de IA.
Além disso, o modelo de execução baseado em QMD e pushbuffer explica por que kernels muito longos ainda não suportam preempção nativa: o hardware espera que o comando seja consumido integralmente. Por isso, boas práticas exigem dividir kernels pesados em unidades menores ou usar CUDA streams para concorrência fina, mas sempre com atenção à contenção em recursos compartilhados como memória global ou SMs.
Impacto para desenvolvedores
Para desenvolvedores de alto desempenho, esse conhecimento muda decisões concretas. Usar __shared__ não é só sobre velocidade: é saber que essa memória está fisicamente dentro do SM e que seu tamanho afeta quantos blocos cabem simultaneamente em um SM, o que define a ocupância real. Escolher threadsPerBlock como múltiplo de 32 não é convenção: é requisito para evitar desperdício de ciclo em warps incompletos. E quando nvcc -Xptxas -v reporta launch__registers_per_thread = 16, isso significa que cada thread ocupa 16 registradores físicos, e exceder o limite disponível por SM reduz a quantidade de warps ativos, diminuindo a ocultação de latência.
Ferramentas como cuobjdump e nsight-compute deixam de ser opcionais: elas revelam o SASS gerado, os acessos à constant bank, o uso de Tensor Cores e até a taxa de utilização dos warp schedulers. Em ambientes de produção com GPUs Blackwell, onde o custo por hora de uso é elevado, entender o que acontece entre o <<< e o primeiro byte escrito na memória global é o que separa aplicações que escalam de forma linear de outras que estagnam em 30% de utilização.
Perguntas frequentes
O que é PTX e SASS em CUDA?
PTX (Parallel Thread Execution) é uma ISA virtual da NVIDIA, gerada pelo compilador cicc. É portátil entre arquiteturas, mas não executa diretamente na GPU. SASS (Streaming Assembler) é o código de máquina nativo de cada geração de GPU, gerado pelo ptxas a partir do PTX. O executável final contém ambos: o SASS para execução imediata e o PTX como fallback para compatibilidade futura.
Como funciona o lançamento de um kernel CUDA na GPU?
A sintaxe kernel<<<...>>>() gera um 'host launch stub' que empacota argumentos em memória host. O runtime CUDA usa o endereço da função como chave para encontrar o símbolo no fatbinary, chama o driver libcuda.so.1, que monta um comando QMD e o envia à GPU via pushbuffer e escrita no registrador doorbell. Nada disso é uma chamada de função tradicional, é um fluxo de comandos controlado pelo driver.
O que é warp divergence e por que ela prejudica o desempenho?
Warp divergence ocorre quando threads dentro do mesmo warp (grupo de 32) seguem caminhos diferentes em um if/else ou loop com condições variáveis. Como todas as threads do warp executam a mesma instrução simultaneamente, o hardware serializa as ramificações, executando primeiro o bloco verdadeiro para todos, depois o falso para os que precisam, desperdiçando ciclos. Isso reduz drasticamente a taxa de ocupação efetiva dos SMs.
Qual é o papel do fatbinary em um executável CUDA?
O fatbinary é um contêiner ELF que embute o código SASS compilado para uma arquitetura específica (ex.: sm_86 para RTX 4090) e o código PTX correspondente, geralmente comprimido. Ele permite que o mesmo binário rode em GPUs mais novas: se o SASS não for compatível, o driver JIT-compila o PTX em tempo de carregamento. É a base da compatibilidade cruzada entre gerações de GPUs NVIDIA.
Fontes
- fergusfinn.comfonte original
- Categoria
- CEVIU IA
- Publicado
- 01 de julho de 2026
- Editoria
- CEVIU IA

