Programação de Processadores Massivamente Paralelos: Uma Abordagem Prática
Este curso oferece uma introdução abrangente ao computação em GPU e programação paralela usando o ambiente CUDA C. Aborda arquiteturas de GPU, paralelismo de dados, gerenciamento de threads, otimização de memória e considerações avançadas de desempenho, ilustradas por estudos de caso do mundo real como reconstrução de ressonância magnética e visualização molecular.
Visão Geral do Curso
📚 Resumo do Conteúdo
Este curso oferece uma introdução abrangente ao computação em GPU e programação paralela usando o ambiente CUDA C. Ele aborda arquiteturas de GPU, paralelismo de dados, gerenciamento de threads, otimização de memória e considerações avançadas de desempenho, ilustrados por estudos de caso do mundo real, como reconstrução de ressonância magnética e visualização molecular.
Domine a arte da computação paralela de alto desempenho com um guia prático e baseado em exercícios sobre CUDA e arquiteturas de GPU.
Autor: David B. Kirk, Wen-mei W. Hwu
Agradecimentos: Ian Buck, John Nickolls, equipe NVIDIA DevTech, Jensen Huang, David Luebke, Bill Bean, Simon Green, Mark Harris, Manju Hedge, Nadeem Mohammad, Brent Oster, Peter Shirley, Eric Young e Cyril Zeller.
🎯 Objetivos de Aprendizagem
- Distinguir entre os filosofias de design e trajetórias de desempenho de CPUs multicore e GPUs many-core.
- Identificar os componentes principais de uma arquitetura de GPU moderna, incluindo Multiprocessadores de Fluxo (SMs) e estruturas de memória.
- Aplicar a Lei de Amdahl para calcular o ganho teórico de velocidade e identificar o impacto de gargalos sequenciais.
- Contrastar as diferenças arquitetônicas entre pipelines fixos e matrizes de processadores unificadas programáveis.
- Explicar o papel do "GPGPU" como um passo intermediário e as restrições dos modelos iniciais de programação de shaders.
- Analisar como recursos de hardware como operações atômicas, sincronização de barreira e suporte a precisão dupla permitiram a transição para computação geral escalável.
- Identificar e explorar o paralelismo de dados em algoritmos de multiplicação de matrizes.
- Implementar o gerenciamento de memória do dispositivo, incluindo alocação, transferência de dados entre host e dispositivo e desalocação.
- Criar e lançar kernels CUDA usando indexação de thread e configurações apropriadas de grade/bloco.
- Projetar hierarquias multidimensionais de threads (grades e blocos) para mapear estruturas de dados complexas no hardware da GPU.
🔹 Lição 1: Introdução à Computação Paralela e Arquiteturas de GPU
Visão Geral: Esta lição explora a mudança fundamental da computação sequencial para a computação paralela, impulsionada pelas filosofias de design divergentes de CPUs e GPUs. Os alunos examinarão as trajetórias "Multicore" versus "Many-core", compreenderão a arquitetura de hardware que permite às GPUs alcançar grande throughput e aprenderão as restrições matemáticas do ganho de velocidade via Lei de Amdahl.
Resultados de Aprendizagem:
- Distinguir entre as filosofias de design e trajetórias de desempenho de CPUs multicore e GPUs many-core.
- Identificar os componentes principais de uma arquitetura de GPU moderna, incluindo Multiprocessadores de Fluxo (SMs) e estruturas de memória.
- Aplicar a Lei de Amdahl para calcular o ganho teórico de velocidade e identificar o impacto de gargalos sequenciais.
🔹 Lição 2: A Evolução e o Futuro da Computação em GPU
Visão Geral: Esta lição traça a jornada arquitetônica da Unidade de Processamento Gráfico (GPU) desde seu surgimento como hardware especializado de função fixa para renderizar triângulos até sua atual condição como poderoso processador paralelo unificado e de propósito geral. Os alunos explorarão a transição de pipelines rígidos para shaders programáveis, o surgimento do movimento GPGPU e as arquiteturas escaláveis modernas que impulsionam simulações científicas e de engenharia atuais.
Resultados de Aprendizagem:
- Contrastar as diferenças arquitetônicas entre pipelines fixos e matrizes de processadores unificadas programáveis.
- Explicar o papel do "GPGPU" como um passo intermediário e as limitações dos modelos iniciais de programação de shaders.
- Analisar como recursos de hardware como operações atômicas, sincronização de barreira e suporte a precisão dupla permitiram a transição para computação geral escalável.
🔹 Lição 3: Estrutura de Programas CUDA e Gerenciamento de Memória
Visão Geral: Esta lição cobre a arquitetura fundamental de um programa CUDA, enfatizando a distinção entre execução Host (CPU) e Device (GPU). Os alunos aprenderão a identificar paralelismo de dados em operações de matriz, gerenciar espaços de memória separados usando a API CUDA e organizar a execução paralela por meio de uma hierarquia de grades, blocos e threads usando o estilo Single-Program, Multiple-Data (SPMD).
Resultados de Aprendizagem:
- Identificar e explorar o paralelismo de dados em algoritmos de multiplicação de matrizes.
- Implementar o gerenciamento de memória do dispositivo, incluindo alocação, transferência de dados entre host e dispositivo e desalocação.
- Criar e lançar kernels CUDA usando indexação de thread e configurações apropriadas de grade/bloco.
🔹 Lição 4: Threads e Escalonamento Avançados em CUDA
Visão Geral: Esta lição explora a organização hierárquica de threads em CUDA, focando na forma como o indexamento multidimensional se mapeia para dados físicos e recursos de hardware. Detalha os mecanismos de sincronização de barreira e escalabilidade transparente, concluindo com os princípios arquitetônicos de atribuição de threads e escalonamento baseado em warp usados para alcançar tolerância a latência em computação de alto desempenho.
Resultados de Aprendizagem:
- Projetar hierarquias multidimensionais de threads (grades e blocos) para mapear estruturas de dados complexas no hardware da GPU.
- Implementar indexação precisa de dados usando variáveis embutidas do CUDA (
blockIdx,threadIdx,blockDim). - Aplicar sincronização de barreira para garantir integridade de dados enquanto mantém escalabilidade transparente em diferentes arquiteturas de GPU.
🔹 Lição 5: Otimização de Memória e Tileamento de Memória Compartilhada
Visão Geral: Esta lição explora como a largura de banda de memória e as restrições de recursos atuam como gargalos primários na computação paralela. Detalha o uso do "tileamento" para reduzir o tráfego de memória global e explica o papel crítico das barras de sincronização (__syncthreads()) e da escolha estratégica entre registradores e memória compartilhada para otimizar o desempenho.
Resultados de Aprendizagem:
- Analisar como os limites de registradores e memória compartilhada determinam o nível de paralelismo (ocupação) em um kernel.
- Quantificar a redução no consumo de largura de banda de memória global alcançada por técnicas de tileamento.
- Identificar a necessidade de funções de sincronização para manter a integridade dos dados durante o acesso à memória compartilhada.
🔹 Lição 6: Análise de Desempenho e Execução SIMT
Visão Geral: Esta lição explora as considerações arquitetônicas e algóricas essenciais para otimizar kernels CUDA. Transita dos modelos de execução básicos — especificamente a unidade Single-Instruction, Multiple-Thread (SIMT) e particionamento de warp — para técnicas avançadas de ajuste de desempenho, incluindo coalescimento de memória, multiplicação de matrizes com tileamento e particionamento dinâmico dos recursos do Streaming Multiprocessor (SM).
Resultados de Aprendizagem:
- Analisar o mapeamento de blocos de threads multidimensionais para a ordem linear de execução de warp do hardware.
- Avaliar e minimizar a divergência de fluxo de controle em algoritmos de redução paralela.
- Otimizar a largura de banda de memória global implementando padrões de acesso coalescido e com tileamento.
🔹 Lição 7: Aritmética de Ponto Flutuante e Precisão Numérica
Visão Geral: Esta lição aborda a arquitetura fundamental dos números de ponto flutuante, focando nos componentes do padrão IEEE 754: sinal, expoente codificado com excesso e mantissa normalizada. Os alunos explorarão como esses padrões de bits se mapeiam para uma reta numérica discreta e como as limitações dessa representação afetam a precisão de algoritmos complexos como somas em larga escala.
Resultados de Aprendizagem:
- Desmontar o formato de ponto flutuante para calcular valores numéricos a partir de padrões de bits usando representação normalizada e codificação com excesso.
- Visualizar a distribuição de números representáveis em uma reta numérica e explicar o impacto da alocação de bits entre expoente e mantissa.
- Quantificar a imprecisão numérica usando ULP e identificar como diferentes modos de arredondamento contribuem para erros.
🔹 Lição 8: Estudo de Caso: Paralelização da Reconstrução de Ressonância Magnética
Visão Geral: Esta lição explora a paralelização da reconstrução avançada de Ressonância Magnética (MRI) em GPUs. Foca no processo iterativo de reconstrução para trajetórias não-Cartesianas, especificamente otimizando o kernel computacionalmente intenso F^H d por meio de transformações de loop, gerenciamento de memória constante, reorganização de layout de dados e uso de funções trigonométricas aceleradas por hardware.
Resultados de Aprendizagem:
- Compreender a transição da reconstrução baseada em FFT Cartesiana para algoritmos baseados em resolvedores lineares iterativos para dados de k-space não-Cartesianos.
- Aplicar fission de loop e troca de loop para transformar código C sequencial em uma estrutura adequada para mapeamento massivo de threads CUDA.
- Otimizar o throughput de memória usando chunking de memória constante e layouts de dados Array-of-Structs (AoS).
🔹 Lição 9: Estudo de Caso: Visualização Molecular e Execução Multi-GPU
Visão Geral: Esta lição explora a aplicação prática da computação em GPU à visualização molecular, especificamente usando o método de Soma Direta de Coulomb (DCS) para calcular mapas de potencial eletrostático. Os alunos evoluirão de uma implementação básica do kernel até versões altamente otimizadas que aproveitam o desenrolamento de instruções, coalescimento de memória e preenchimento.
Resultados de Aprendizagem:
- Implementar um kernel de Soma Direta de Coulomb (DCS) usando memória constante CUDA e técnicas de ocultação de latência de memória global.
- Otimizar o desempenho do kernel por meio do desenrolamento de instruções e reaproveitamento de cálculos comuns de coordenadas.
- Aplicar estratégias de coalescimento de memória e preenchimento para alinhar acessos à memória global da GPU com a largura de banda máxima.
🔹 Lição 10: Pensamento Computacional e Seleção de Algoritmos Paralelos
Visão Geral: Esta lição explora a transição do pensamento sequencial para a resolução de problemas paralelos, focando nos objetivos da programação paralela e na seleção estratégica de algoritmos. Os alunos aprenderão a decompor problemas em unidades paralelizáveis, aplicar o pensamento computacional para pontuar entre ciência do domínio e arquitetura de hardware, e avaliar o desempenho de algoritmos.
Resultados de Aprendizagem:
- Identificar os objetivos principais da programação paralela e calcular o ganho teórico de velocidade usando a Lei de Amdahl.
- Diferenciar entre decomposição por tarefa e por dados e aplicar estratégias centradas no átomo (scatter) versus centradas na grade (gather).
- Avaliar e selecionar algoritmos paralelos com base em critérios como largura de banda de memória, complexidade computacional e restrições arquitetônicas.
🔹 Lição 11: Introdução ao Modelo de Programação OpenCL
Visão Geral: Esta lição apresenta o OpenCL como um framework para computação paralela heterogênea, focando em seu modelo de paralelismo de dados e abstração hierárquica de hardware. Os alunos aprenderão a mapear as estruturas NDRange e de memória do OpenCL para equivalentes CUDA e dominar a gestão do lado host de dispositivos por meio de um modelo de compilação dinâmica.
Resultados de Aprendizagem:
- Mapear o paralelismo e as hierarquias de memória do OpenCL para arquiteturas específicas do CUDA (por exemplo, mapear Work-groups para Blocos e Memória Local para Memória Compartilhada).
- Implementar funções de kernel OpenCL e gerenciar o ambiente de execução do lado host usando Contextos e Filas de Comandos.
- Executar o fluxo de trabalho de compilação dinâmica para construir kernels a partir de código-fonte em tempo de execução.
🔹 Lição 12: Recursos Modernos de GPU e Perspectiva Futura
Visão Geral: Esta lição explora a evolução arquitetônica e funcional das GPUs, focando na transição rumo a uma gestão de memória sofisticada, capacidades aprimoradas de execução de kernels e aumento do desempenho dos núcleos. Os alunos examinarão como recursos como Espaço de Memória Unificada do Dispositivo e chamadas de função de nível de kernel transpõem a GPU para um processador de propósito geral.
Resultados de Aprendizagem:
- Explicar a importância da Evolução da Arquitetura de Memória e a transição rumo a um Espaço de Memória Unificada de 64 bits.
- Analisar como Operações Atômicas Aprimoradas e Chamadas de Função de Nível de Kernel permitem a implementação de estruturas e algoritmos complexos.
- Avaliar os impactos de desempenho da Execução Simultânea de Kernels, melhorias no desempenho de precisão dupla e Eficiência de Fluxo de Controle em ambientes de GPU modernos.