Skip to content
Como Melhorar o Desempenho de Kernels CUDA com Spill de Registradores na Memória Compartilhada (CUDA 13.0)
Source: developer.nvidia.com

Como Melhorar o Desempenho de Kernels CUDA com Spill de Registradores na Memória Compartilhada (CUDA 13.0)

Sources: https://developer.nvidia.com/blog/how-to-improve-cuda-kernel-performance-with-shared-memory-register-spilling, https://developer.nvidia.com/blog/how-to-improve-cuda-kernel-performance-with-shared-memory-register-spilling/, NVIDIA Dev Blog

TL;DR

  • O CUDA 13.0 adiciona uma otimização que spill registradores para a memória compartilhada primeiro, usando memória local apenas se a memória compartilhada for insuficiente.
  • Quando houver espaço, a memória compartilhada on‑chip reduz a latência e diminui a pressão sobre o cache L2 em comparação com spills para memória local.
  • Antes do CUDA 13.0, todos os spills eram para memória local; isso podia causar evicção de cache e perda de desempenho em regiões de alta pressão.
  • Ative esse recurso com um pragma PTX dentro de uma função: enable_smem_spilling. O pragma é válido apenas dentro do escopo da função.
  • Resultados do mundo real na QUDA (lattice QCD) mostram ganhos típicos de cerca de 5–10% em kernels com pressão alta de registradores.

Contexto e antecedentes

Um kernel CUDA pode exigir mais registradores do que o disponível em um SM. Quando isso acontece, o compilador spillia os registradores excedentes para a memória local, que fica fisicamente em memória global off‑chip. Spillings aumentam o tráfego de memória e a latência, porque os dados spillados precisam ser lidos e escritos na memória local. Antes do CUDA 13.0, todos os spills iam para a memória local. Mesmo com caches L1 maiores, dados spillados podiam faltar e serem escritos no cache L2, potencialmente levando à evicção de linhas úteis e degradando o desempenho em laços e outras trilhas de código quentes com alta pressão de registradores. Um aspecto que contribuiu para a ineficiência foi que, em muitos workloads, uma porção considerável da memória compartilhada por bloco ficava ociosa em tempo de execução, especialmente quando os limites de lançamento ou a ocupação eram restritivos ou quando o kernel não maximizava o uso da memória compartilhada. O blog da CUDA explica como o comportamento de spill e a ocupação se conectam, e como limitar fatores como os limites de lançamento podem influenciar o desempenho do kernel. O exemplo usa um kernel que deliberadamente pressiona registradores para ilustrar o overhead dos spills e a ineficiência de não usar memória compartilhada quando possível. A conclusão principal é que manter spills no chip, quando possível, aproxima os dados spillados do SM e reduz o tráfego de memória off‑chip.

O que há de novo

O CUDA 13.0 introduz uma otimização PTXAS que permite spill de registradores para memória compartilhada on‑chip para kernels CUDA. Quando ativado, o compilador prioriza spills para memória compartilhada disponível. Se não há espaço suficiente, os spills restantes voltam para a memória local, garantindo a correção do programa. Essa mudança traz uma vantagem de desempenho clara ao explorar a memória on‑chip de menor latência para dados spillados sempre que possível. Em comparação com toolkits anteriores, onde spills residiam na memória global e podiam afetar significativamente o desempenho, o caminho de spilling para memória compartilhada reduz a latência e alivia a pressão no cache L2 em muitos cenários. A otimização foi demonstrada em kernels derivados da biblioteca QUDA (usada para cálculos de lattice QCD em GPUs). No trabalho, a ativação do spill para memória compartilhada tipicamente gerou ganhos de desempenho na faixa de 5–10%, impulsionados pela redução ou eliminação de spills locais. Para ativar, desenvolvedores que visam CUDA 13.0+ devem inserir um pragma PTX dentro da função, logo após a declaração da função:

// dentro do corpo da função, após a declaração
.pragma_enable_smem_spilling

Na forma de assembly inline, a diretiva é expressa como:

#pragma enable_smem_spilling

O recurso é válido apenas dentro do escopo da função. Deve‑se usar com cuidado quando os limites de lançamento não estiverem especificados; se os limites de lançamento forem desconhecidos, o PTXAS pode assumir o maior número possível de threads por bloco, o que pode subestimar o orçamento de memória compartilhada e reduzir a ocupação. Para comportamento mais previsível e melhor desempenho, use este recurso apenas quando os limites de lançamento estiverem explicitamente definidos. O recurso está disponível a partir do CUDA 13.0. O spill para memória compartilhada pode refletir no uso de memória por bloco (por exemplo, o exemplo mostra 46080 bytes de memória compartilhada alocados, indicando uso de memória compartilhada para spills).

Por que isso é importante (impacto para desenvolvedores/empresas)

Para desenvolvedores que constroem kernels GPU de alto desempenho, especialmente aqueles com alta pressão de registradores, esta otimização oferece uma maneira prática de melhorar o throughput sem alterar o algoritmo. Direcionar spills para memória compartilhada on‑chip pode reduzir a latência de acesso aos dados spillados e diminuir a pressão no cache L2, resultando em melhorias reais de tempo de execução em laços apertados e trechos de código quentes. No entanto, os benefícios não são universais. O ganho depende de ter limites de lançamento bem definidos e uso consistente da memória compartilhada para realmente aproveitar o caminho de spill para memória compartilhada. Se o orçamento de memória compartilhada for mal dimensionado, a ocupação pode sofrer, anulando ganhos potenciais. Por isso, uma abordagem baseada em dados, testando com e sem o caminho opt‑in em cargas representativas, é recomendada. Para organizações que executam grandes implantações CUDA, a opção oferece mais um parâmetro de ajuste para desempenho de kernels, especialmente em bibliotecas e aplicativos com alta pressão de registradores. Os ganhos de 5–10% observados na QUDA indicam melhoria tangível para kernels de lattice QCD sob alta pressão de registradores, e ganhos similares podem ocorrer em outras cargas de trabalho com características semelhantes.

Detalhes técnicos ou Implementação (como funciona)

  • A ideia central é priorizar spills para memória compartilhada on‑chip por padrão, sempre que houver espaço por bloco de threads.
  • Se o espaço de memória compartilhada for insuficiente, o spilling cai para a memória local, mantendo a correção do programa.
  • A otimização é apresentada como um recurso PTXAS no CUDA 13.0; habilitar requer uma instrução explícita de opt‑in no escopo da função, usando o pragma PTX enable_smem_spilling.
  • As restrições incluem: deve-se especificar limites de lançamento para orçar com precisão a memória; caso contrário, o compilador pode supor o maior número de threads por bloco, o que pode afetar a ocupação se os lançamentos reais forem menores.
  • O impacto de desempenho é dependente do workload. As avaliações da QUDA mostram ganhos típicos de 5–10%, resultantes da redução ou eliminação de spills locais devido ao caminho de spill para memória compartilhada.
  • O mecanismo mantém a correção ao cair de volta para a memória local quando necessário e mantendo os dados spillados próximos ao SM quando possível.

Tabela: alvos de spills e implicações

| Aspecto | Antes do CUDA 13.0 | Com Spill para Memória Compartilhada (CUDA 13.0) |---|---|---| | Alvo de spill | Memória local (off‑chip) | Memória compartilhada (on‑chip) quando disponível; fallback para memória local caso contrário |Latência | Maior devido ao acesso off‑chip | Menor quando spills ficam em memória compartilhada |Pressão de L2/cache | Possível evicção de linhas úteis | Redução da pressão quando spills residem na memória compartilhada |Ocupação | Não está diretamente ligada a spills | Requer limites de lançamento explícitos para evitar regressões de ocupação |

Exemplo de referência do post da NVIDIA

O artigo aponta um kernel com 46080 bytes de memória compartilhada usada ao spill para memória compartilhada, ilustrando o uso de memória on‑chip em um contexto prático. Esse exemplo reforça como o recurso pode se refletir no footprint de memória por bloco quando ativado.

Principais conclusões

  • Spill para memória compartilhada é um recurso opt‑in no CUDA 13.0 via pragma de função.
  • Spills são redirecionados para memória compartilhada on‑chip primeiro, com fallback para memória local se o espaço não for suficiente.
  • Ganhos de desempenho reais variam por workloads; ganhos observados na QUDA ficam tipicamente entre 5–10%.
  • Para evitar impactos de ocupação, defina limites de lançamento explicitamente ao usar esse recurso.
  • A otimização mantém a correção do código enquanto melhora a latência de acesso aos dados spillados.

FAQ

  • O que é spill de registradores para memória compartilhada?

    É um recurso do CUDA 13.0 que prioriza spills para memória compartilhada on‑chip, reduzindo latência e pressão no L2, com fallback para memória local conforme necessário.

  • Como habilito isso?

    Use o pragma PTX `enable_smem_spilling` dentro da função, logo após a declaração da função. O directive deve ser usado apenas no escopo da função.

  • uando devo usar esse recurso?

    Quando seu kernel tem limites de lançamento bem definidos e uso consistente de memória compartilhada, visando reduzir spills locais em caminhos quentes com alta pressão de registradores. Garanta que o orçamento de memória compartilhada esteja adequado para evitar perdas de ocupação.

  • uais ganhos posso esperar?

    valiações da QUDA indicam ganhos típicos de 5–10%, resultantes da redução ou eliminação de spills locais por meio do caminho de spill para memória compartilhada.

Referências

More news