A apresentação está carregando. Por favor, espere

A apresentação está carregando. Por favor, espere

Tópicos sobre GPGPU em CUDA

Apresentações semelhantes


Apresentação em tema: "Tópicos sobre GPGPU em CUDA"— Transcrição da apresentação:

1 Tópicos sobre GPGPU em CUDA
Paulo A. Pagliosa Faculdade de Computação Universidade Federal de Mato Grosso do Sul Natal, maio de 2013

2 Apresentação Conteúdo Introdução a GPGPU (www.gpgpu.org) O que é CUDA
Fundamentos de programação CUDA Práticas de programação APIs e ferramentas Estudo de caso -Introdução a GPGPU: programação em GPU para propósito geral Programação em CUDA: fabricante específico Estudos de caso: exemplos simples

3 Introdução a GPGPU GPUs: unidades de processamento gráfico
Originalmente projetadas para processamento gráfico 3D Exemplos NVIDIA Tesla C1060 NVIDIA GeForce 8800 GTX

4 Introdução a GPGPU Pipeline gráfico fixo (OpenGL) Software Hardware
Aplicação Processamento de geometria Rasterização Cena Dados de geometria Dados de pixels Fontes de luz Materiais Processamento de fragmentos Framebuffer Exibição

5 Introdução a GPGPU Pipeline gráfico fixo (OpenGL) Hardware Exibição
Rasterização Framebuffer Transformação Iluminação Projeção Montagem de primitivos Recorte Primitivos Processamento de geometria Processamento de fragmentos

6 Introdução a GPGPU Pipeline gráfico fixo (OpenGL) Hardware
Exibição Framebuffer Processamento de geometria Rasterização Processamento de fragmentos

7 Introdução a GPGPU Pipeline gráfico fixo (OpenGL) Hardware
Exibição Framebuffer Processamento de geometria Rasterização Mapeamento de textura Aplicação de neblina Composição Tonalização Processamento de fragmentos

8 Introdução a GPGPU GPUs de primeira geração (1998)
Pipeline fixo Renderização de triângulos pré-transformados Exemplos: TNT2, Voodoo3 GPUs de segunda geração ( ) Transformações geométricas Iluminação Velocidade de renderização maior Exemplos: GeForce 2, ATI 5700

9 Introdução a GPGPU Pipeline gráfico programável (OpenGL 3.1) Hardware
Exibição Framebuffer Transformação Iluminação Projeção Montagem de primitivos Recorte Primitivos Processamento de geometria Rasterização Processamento de fragmentos

10 Introdução a GPGPU Pipeline gráfico programável (OpenGL 3.1) Hardware
Exibição Framebuffer Processamento de geometria Rasterização Mapeamento de textura Aplicação de neblina Composição Tonalização Processamento de fragmentos

11 Introdução a GPGPU Shaders Programa em GPU: um ou mais shaders
Módulos que executam em GPU Um shader pode ser de: Vértice Geometria (SM 4) Fragmento Substituem a funcionalidade do pipeline fixo Programa em GPU: um ou mais shaders SHADER DE VÉRTICE: opera sobre um vértice Entrada: atributos de um vértice (1)dados em variáveis embutidas com os valores correspondentes do estado da OpenGL (coordenadas espaciais e de textura, cor, etc.) e (2)definidos pela aplicação. Saída: atributos (1)dados por variáveis embutidas (pelo menos a posição do vértice) e (2)definidos pela aplicação, os quais são passados para o estágio seguinte. Operações: transformação de coordenadas espaciais e de textura, iluminação, geração de coordenadas de textura, etc. SHADER de GEOMETRIA: opera sobre um primitivo, com ou sem informação de adjacência -Entrada: primitivo de um tipo. -Saída: zero ou mais primitivos do mesmo tipo. -Operações: LOD, tesselação. SHADER DE FRAGMENTO: opera sobre fragmento Entrada: atributos de um fragmento (1)dados em variáveis embutidas com os valores correspondentes do estado da OpenGL (coordenadas de textura, etc.) e (2)definidos pela aplicação. Saída: cor do fragmento (coordenadas do fragmento não podem ser modificadas) ou fragmento descartado. Operações: mapeamento de textura, tonalização, aplicação de neblina. Programa GPU Shader de vértice Shader de geometria Shader de fragmento

12 Introdução a GPGPU Shader de fragmento
Opera isoladamente sobre um fragmento Entrada Variáveis pré-calculadas pela OpenGL Variáveis definidas pela aplicação Saída: cor do fragmento Operações Mapeamento de textura Tonalização Aplicação de neblina Fragmento pode ser descartado Coordenadas do fragmento não podem ser mudadas SHADER DE FRAGMENTO: opera sobre fragmento Entrada: atributos de um fragmento (1)dados em variáveis embutidas com os valores correspondentes do estado da OpenGL (coordenadas de textura, etc.) e (2)definidos pela aplicação. Saída: cor do fragmento (coordenadas do fragmento não podem ser modificadas) ou fragmento descartado. Operações: mapeamento de textura, tonalização, aplicação de neblina.

13 Introdução a GPGPU GPUs de terceira geração (2001-2002)
Pipeline programável com shaders de vértices Número limitado de instruções sem ponto flutuante Programação em linguagem de montagem Exemplos: GeForce3 e 4, ATI 8500 GPUs de quarta geração ( ) Shaders de vértices e fragmentos Expansão do número de instruções Ponto flutuante de 32 bits e dados em texturas Surgimento de linguagens de shaders Exemplos: GeForce FX, 6 e 7; ATI 9700 e 9800

14 Introdução a GPGPU NVIDIA GeForce 6800 Até 6 processadores de vértices
16 processadores de fragmentos

15 Introdução a GPGPU Linguagens de shaders Cg (C for graphics), NVIDIA
HLSL (High Level Shader Language), Microsoft GLSL (OpenGL Shader Language) GLSL: plataforma aberta; executa em qualquer hardware compatível com suporte a OpenGL

16 Introdução a GPGPU Programas GLSL Shader glCreateShader Programa
glCreateProgram glAttachShader glLinkProgram glUseProgram Shader glCreateShader glShaderSource glCompileShader Código GLSL

17 Introdução a GPGPU GPGPU: programação genérica em GPU
GPGPU com shaders Modelo de programação: streams e kernels Streams: dados de entrada e saída Vetores em CPU — texturas em GPU Kernels: shaders (de fragmento) Saída: renderização em textura Execução: rasterização Mapping Computational Concepts to GPUs (GPU Gems 2) Dificuldades Problema pode ser inadequado ao modelo de programação. Dependendo do problema, o mapeamento pode ser difícil; vários passos de renderização podem ser necessários.

18 Introdução a GPGPU Exemplos Simulação dinâmica de corpos rígidos
Multiplicação de matrizes potência de dois Sem restrições Com detecção de colisões

19 Introdução a GPGPU Desvantagens de GPGPU com shaders
GPU é programada através de uma API gráfica Curva de aprendizado da API Overhead para aplicações não gráficas Flexibilidade Memória da GPU pode ser lida (gather) mas não pode ser escrita (scatter) de maneira geral Shader de fragmento produz apenas saídas RGBA CUDA: Compute Unified Device Architecture GPUs de quinta geração (2007-) Computação de propósito geral Exemplos: GeForce 8, 9, 100 e 200 GPGPU com shader: Programação via API gráfica: aprendizado e overhead para aplicações não gráficas. Scatter: remove flexibilidade de programação. Mesmo com CUDA, programação não consiste simplesmente em “recompilar” código tradicional. Dependendo do problema, mapeamento ainda pode ser complicado. Programas realmente EFICIENTES quase sempre são mais difíceis de ser implementados. Primeiro passo: pensar “em paralelo”. Fermi: sexta geração?

20 Introdução a GPGPU NVIDIA GeForce 8800 GTX 16 multiprocessadores (SMs)
8 processadores (SPs) por multiprocessador

21 Introdução a GPGPU Aplicações CUDA Visualização e simulação
Traçado de raios Simulação de fluídos

22 Introdução a GPGPU CUDA: arquitetura de computação paralela para GPGPU

23 Introdução a GPGPU Por que CUDA? Instalação de CUDA
Desempenho versus custo NVIDIA é líder de mercado: mais de 108 GPUs Programação paralela para muitos Tesla, Fermi e Kepler Instalação de CUDA Driver CUDA toolkit (compilador e bibliotecas) CUDA SDK (utilitários e exemplos) https://developer.nvidia.com/cuda-downloads CUDA: modelo de programação + interface de programação (C para CUDA, APIs, runtime) + driver + hardware. Porque CUDA: performance versus custo, mais de 100 milhões de unidades vendidas (jogos), programação paralela. Como instalar.

24 Introdução a GPGPU Bibliografia (http://docs.nvidia.com/cuda/index.html) CUDA Programming Guide CUDA Reference Manual CUDA Best Practices Guide -Introdução a GPGPU: programação em GPU para propósito geral Programação em CUDA: fabricante específico Estudos de caso: exemplos simples

25 Fundamentos de programação CUDA
Modelo de programação Host: executa a aplicação (CPU) Dispositivo (GPU) Coprocessador da CPU Executa kernels Host e dispositivo tem DRAMs próprias Host: Aloca memória no dispositivo Transfere dados de entrada para o dispositivo Dispara a execução de kernels Transfere dados resultantes do dispositivo Libera memória no dispositivo

26 Fundamentos de programação CUDA
Modelo de programação Kernel Função geralmente escrita em C para CUDA Executa no dispositivo N vezes em N threads em paralelo Threads são organizadas em blocos Um bloco é um arranjo 1D, 2D ou 3D de threads Cada thread de um bloco tem um índice 1D, 2D ou 3D Blocos são organizados em grids Um grid é um arranjo 1D ou 2D de blocos Cada bloco de um grid tem um índice 1D ou 2D Os blocos de um grid têm o mesmo número de threads GPUs Fermi podem executar vários kernels em paralelo.

27 Fundamentos de programação CUDA
Exemplo: kernel executando em 72 threads Grid 2D com: Dimensão 3×2×1 6 blocos Blocos 2D com: Dimensão 4×3×1 12 threads cada

28 Fundamentos de programação CUDA
Modelo de programação Um kernel é uma função que: Começa com o especificador __global__ Tem tipo de retorno void Kernels podem invocar outras funções que: São especificadas como __device__ Podem invocar outras especificadas como __device__ (GPUs Kepler podem invocar kernels) Funções que executam no dispositivo: Não admitem número variável de argumentos Não admitem variáveis estáticas GPUs não Fermi não admitem recursão nem variáveis do tipo endereço de função GPUs Fermi podem executar vários kernels em paralelo.

29 Fundamentos de programação CUDA
Modelo de programação Kernels são invocados do host (ou de Kepler) Um dispositivo executa um kernel de cada vez (Fermi pode mais) GPUs Fermi podem executar vários kernels em paralelo.

30 Fundamentos de programação CUDA
Modelo de programação Configuração de execução de um kernel Dimensões do grid e dos blocos Tamanho da memória compartilhada (opcional) Especificada na invocação (ou lançamento) do kernel Dimensão 3D é representada por objeto do tipo dim3 Um grid: 1D de dimensão dim3 dG tem dG.x × 1 × 1 blocos 2D de dimensão dim3 dG tem dG.x × dG.y × 1 blocos Um bloco: 1D de dimensão dim3 dB tem dB.x × 1 × 1 threads 2D de dimensão dim3 dB tem dB.x × dB.y × 1 threads 3D de dimensão dim3 dB tem dB.x × dB.y × dB.z threads

31 Fundamentos de programação CUDA
Modelo de programação Identificador global de uma thread Pode ser usado para indexar vetores e matrizes em funções __global__ ou __device__ Determinado a partir das variáveis pré-definidas: dim3 gridDim Dimensão do grid dim3 blockDim Dimensão do bloco dim3 blockIdx Índice do bloco no grid dim3 threadIdx Índice da thread no bloco

32 Fundamentos de programação CUDA
Modelo de programação Hierarquia de memória acessada por uma thread Memória compartilhada do bloco da thread Visível para todas as threads do bloco Tempo de vida do bloco Memória local da thread Memória global Memória constante Memória de textura Tempo de vida da aplicação Somente leitura

33 Fundamentos de programação CUDA
Modelo de programação Capacidade de computação 1.x (Tesla) 2.x (Fermi) 3.x (Kepler) Especificações: depende do dispositivo Número de multiprocessadores e processadores Dimensões de grids e blocos DRAM Memória compartilhada, etc.

34 Fundamentos de programação CUDA
Interface de programação C para CUDA e API de runtime API de driver Ambas APIs têm funções para: Gerência de memória no dispositivo Transferência de dados entre host e dispositivo Gerência de sistemas com vários dispositivos, etc. API de runtime tem também funções para: Gerenciamento de threads Detecção de erros Manual de referência de CUDA APIs exclusivas

35 Fundamentos de programação CUDA
Programa básico em C para CUDA Seleção do dispositivo a ser usado Alocação de memória no host Dados de entrada na memória do host Alocação de memória no dispositivo Transferência de dados do host para dispositivo Invocação do(s) kernel(s) Transferência de dados do dispositivo para host Liberação de memória no host Liberação de memória no dispositivo Finalização

36 Fundamentos de programação CUDA
Programa básico em C para CUDA Seleção do dispositivo a ser usado cudaSetDevice() Alocação de memória no dispositivo cudaMalloc() Transferência de dados entre host e dispositivo cudaMemcpy() Liberação de memória no dispositivo cudaFree() Finalização cudaDeviceReset()

37 Fundamentos de programação CUDA
Exemplo: multiplicação de matrizes Sem memória compartilhada

38 Fundamentos de programação CUDA
Memória compartilhada Disponível para threads de um mesmo bloco Acesso 400 a 600 mais rápido que memória global Permite colaboração entre threads (do bloco) Cada thread do bloco transfere dados da memória global para memória compartilhada Após a transferência, threads devem ser sincronizadas com __syncthreads() Kernel efetua operações usando os dados da memória compartilhada: cada thread pode usar dados carregados por outras threads do bloco Se necessário, threads podem ser sincronizadas com __syncthreads(); cada thread do bloco transfere dados da memória compartilhada para memória global

39 Fundamentos de programação CUDA
Exemplo: multiplicação de matrizes Com memória compartilhada

40 Fundamentos de programação CUDA
Exemplo: transposição de matrizes Mem. compartilhada __syncthreads()

41 Fundamentos de programação CUDA
Exemplo: transposição de matrizes Mem. compartilhada

42 Fundamentos de programação CUDA
Implementação em hardware Arquitetura: arranjo de multiprocessadores (SMs) Cada SM consiste de: 8 processadores (SPs) 1 unidade de instrução Memória compartilhada Cada SM: Executa threads de um bloco em grupos de 32: warp Implementa barreira de sincronização: __syncthreads() Emprega arquitetura SIMT: Single Instruction Multiple Thread

43 Dicas de performance Estratégias básicas
Maximização da execução em paralelo Estruturação do algoritmo Escolha da configuração de execução do kernel Número de threads por bloco múltiplo do tamanho do warp Mínimo de 64 threads por bloco Configuração inicial: entre 128 e 256 threads por bloco Evitar divergência dentro do mesmo warp Otimização do uso de memória Minimização de transferência de dados host/dispositivo Acesso coalescido à memória global Uso de mem. compartilhada Acesso sem conflitos de bancos à mem. compartilhada Otimização do uso de instruções

44 Dicas de performance Memória global
O dispositivo é capaz de ler palavras de 4, 8 ou 16 bytes da memória global para registradores com UMA única instrução, DESDE que o endereço de leitura seja alinhado a (múltiplo de) 4, 8, ou 16. A largura de banda da memória global é mais eficientemente usada quando acessos simultâneos à memória por threads de um meio-warp (durante a execução de uma instrução de leitura ou escrita) podem ser coalescidos em uma única transação de 32, 64 ou 128 bytes de memória.

45 Dicas de performance Memória global
Coalescência em dispositivos 1.0 e 1.1 Ocorre em transação de 64 ou 128 ou duas transações de 128 bytes Threads de um meio-warp devem acessar: Palavras de 4 bytes, resultando numa transação de 64 bytes, Ou palavras de 8 bytes, resultando numa transação de 128 bytes, Ou palavras de 16 bytes, resultando em duas transações de 128 bytes Todas as 16 palavras (cada uma acessada por uma das 16 threads do meio-warp) devem estar no mesmo segmento de tamanho igual ao tamanho das transações (ou seja, 64, 128 ou 256 bytes). Como consequência, o segmento deve ser alinhado a este tamanho Threads devem acessar palavras na sequência: a thread k deve acessar a palavra k

46 Dicas de performance Memória global Com coalescência
More precisely, in each half-warp, thread number N within the half-warp should access address HalfWarpBaseAddress + N where HalfWarpBaseAddress is of type type* and type is such that it meets the size and alignment requirements discussed above. Moreover, HalfWarpBaseAddress should be aligned to 16*sizeof(type) bytes (i.e. be a multiple of 16*sizeof(type)). Any address BaseAddress of a variable residing in global memory or returned by one of the memory allocation routines is always aligned to at least 256 bytes, so to satisfy the memory alignment constraint, HalfWarpBaseAddress-BaseAddress should be a multiple of 16*sizeof(type).

47 Dicas de performance Memória global Sem coalescência

48 Dicas de performance Memória global Sem coalescência

49 Dicas de performance Memória global
Coalescência com vetor de estruturas (AOS) Tamanho da estrutura até 16 bytes Alinhamento deve ser 4, 8 ou 16 bytes, dependendo do tamanho Elementos devem estar no mesmo segmento da transação Estruturas maiores que 16 bytes: reorganizar em estrutura de vetores (SOA) com elementos de tamanho até 16 bytes Common Access Patterns Array of Structures A common global memory access pattern is when each thread of thread ID tid accesses one element of an array located at address BaseAddress of type type* using the following address: BaseAddress + tid To get memory coalescing, type must meet the size and alignment requirements discussed above. In particular, this means that if type is a structure larger than 16 bytes, it should be split into several structures that meet these requirements and the data should be laid out in memory as a list of several arrays of these structures instead of a single array of type type*. Two-Dimensional Array Another common global memory access pattern is when each thread of index (tx,ty) accesses one element of a 2D array located at address BaseAddress of type type* and of width width using the following address: BaseAddress + width * ty + tx In such a case, one gets memory coalescing for all half-warps of the thread block only if: - The width of the thread block is a multiple of half the warp size; - width is a multiple of 16. In particular, this means that an array whose width is not a multiple of 16 will be accessed much more efficiently if it is actually allocated with a width rounded up to the closest multiple of 16 and its rows padded accordingly. The cudaMallocPitch() and cuMemAllocPitch() functions and associated memory copy functions described in the reference manual enable programmers to write non-hardware-dependent code to allocate arrays that conform to these constraints.

50 Dicas de performance Memória compartilhada
Dividida em módulos chamados bancos Em dispositivos 1.x o número de bancos é 16 Bancos tem largura de 32 bits Palavras sucessivas de 32 bits estão em bancos sucessivos Acessos a n endereços que estão em n bancos distintos são efetuados simultaneamente Acessos a dois endereços que estão no mesmo banco geram um conflito de banco: acessos são serializados Threads de um meio-warp devem acessar endereços em bancos distintos para evitar conflitos de bancos Common Access Patterns Array of Structures A common global memory access pattern is when each thread of thread ID tid accesses one element of an array located at address BaseAddress of type type* using the following address: BaseAddress + tid To get memory coalescing, type must meet the size and alignment requirements discussed above. In particular, this means that if type is a structure larger than 16 bytes, it should be split into several structures that meet these requirements and the data should be laid out in memory as a list of several arrays of these structures instead of a single array of type type*. Two-Dimensional Array Another common global memory access pattern is when each thread of index (tx,ty) accesses one element of a 2D array located at address BaseAddress of type type* and of width width using the following address: BaseAddress + width * ty + tx In such a case, one gets memory coalescing for all half-warps of the thread block only if: - The width of the thread block is a multiple of half the warp size; - width is a multiple of 16. In particular, this means that an array whose width is not a multiple of 16 will be accessed much more efficiently if it is actually allocated with a width rounded up to the closest multiple of 16 and its rows padded accordingly. The cudaMallocPitch() and cuMemAllocPitch() functions and associated memory copy functions described in the reference manual enable programmers to write non-hardware-dependent code to allocate arrays that conform to these constraints.

51 Dicas de performance Mem. compartilhada Sem conflitos

52 Dicas de performance Mem. compartilhada Com conflitos

53 APIs para CUDA CUBLAS CUFFT Primitivos paralelos: Thrust (CUDA 5.0)
Algoritmos Soma prefixa Redução Compactação Ordenação Exemplos no SDK CUDA Scan Partículas Etc. Exemplos com shaders Multiplicação de matrizes: ok Corpos rígidos: ok Exemplos CUDA Adição de vetores: ok Exercício: multiplicação de matrizes de tamanhos quaisquer Exercício: soma de matrizes Soma prefixa Exercício: soma prefixa usando CUDAPP Produto escalar Matriz transposta Traçado de raios: ok (falta compilar v. 2.3) Busca de partículas vizinhas Projeto - Sistemas lineares: método de Jacobi

54 Primitivos paralelos Soma prefixa (scan) Entrada
Sequência A de n elementos de um tipo T Operador binário associativo op com identidade I Saída: sequência B de n elementos do tipo T Exemplo: T = int, op = +, I = 0 Soma prefixa exclusiva Entrada A 1 2 3 4 5 6 7 8 Saída B 1 3 6 10 15 21 28 36

55 Primitivos paralelos Soma prefixa (scan) Entrada
Sequência de n elementos de um tipo T Operador binário associativo op com identidade I Saída: sequência de n elementos do tipo T Exemplo: T = int, op = +, I = 0 Soma prefixa inclusiva Entrada A 1 2 3 4 5 6 7 8 Saída B 1 3 6 10 15 21 28 36

56 Primitivos paralelos Implementação da soma prefixa
Soma prefixa inclusiva intra-warp (warp de 8 threads) __device__ int scanWarp(int* data, int tidx, int lane) { if (lane >= 1) data[tidx] = data[tidx-1] + data[tidx]; if (lane >= 2) data[tidx] = data[tidx-2] + data[tidx]; if (lane >= 4) data[tidx] = data[tidx-4] + data[tidx]; return data[tidx]; } data 1 2 3 4 5 6 7 8 1 3 5 7 9 11 13 15

57 Primitivos paralelos Implementação da soma prefixa
Soma prefixa inclusiva intra-warp (warp de 8 threads) __device__ int scanWarp(int* data, int tidx, int lane) { if (lane >= 1) data[tidx] = data[tidx-1] + data[tidx]; if (lane >= 2) data[tidx] = data[tidx-2] + data[tidx]; if (lane >= 4) data[tidx] = data[tidx-4] + data[tidx]; return data[tidx]; } data 1 3 5 7 9 11 13 15 1 3 6 10 14 18 22 26

58 Primitivos paralelos Implementação da soma prefixa
Soma prefixa inclusiva intra-warp (warp de 8 threads) __device__ int scanWarp(int* data, int tidx, int lane) { if (lane >= 1) data[tidx] = data[tidx-1] + data[tidx]; if (lane >= 2) data[tidx] = data[tidx-2] + data[tidx]; if (lane >= 4) data[tidx] = data[tidx-4] + data[tidx]; return data[tidx]; } data 1 3 6 10 14 18 22 26 1 3 6 10 15 21 28 36

59 Primitivos paralelos Implementação da soma prefixa
Soma prefixa inclusiva intra-bloco (warp de 4 threads) __device__ int scanBlock(int* data, int tidx, int lane, int warp) { int val = scanWarp(data, tidx, lane); __syncthreads(); if (lane == 3) data[warp] = data[tidx]; __syncthreads(); if (warp == 0) scanWarp(data, tidx, lane); __syncthreads(); if (warp > 0) val = data[warp-1] + val; __syncthreads(); data[tidx] = val; __syncthreads(); return val; } data 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1 3 6 10 5 11 18 26 9 19 30 42 13 27 58

60 Primitivos paralelos Implementação da soma prefixa
Soma prefixa inclusiva intra-bloco (warp de 4 threads) __device__ int scanBlock(int* data, int tidx, int lane, int warp) { int val = scanWarp(data, tidx, lane); __syncthreads(); if (lane == 3) data[warp] = data[tidx]; __syncthreads(); if (warp == 0) scanWarp(data, tidx, lane); __syncthreads(); if (warp > 0) val = data[warp-1] + val; __syncthreads(); data[tidx] = val; __syncthreads(); return val; } data 1 3 6 10 5 11 18 26 9 19 30 42 13 27 58 10 26 42 58 5 11 18 9 19 30 13 27

61 Primitivos paralelos Implementação da soma prefixa
Soma prefixa inclusiva intra-bloco (warp de 4 threads) __device__ int scanBlock(int* data, int tidx, int lane, int warp) { int val = scanWarp(data, tidx, lane); __syncthreads(); if (lane == 3) data[warp] = data[tidx]; __syncthreads(); if (warp == 0) scanWarp(data, tidx, lane); __syncthreads(); if (warp > 0) val = data[warp-1] + val; __syncthreads(); data[tidx] = val; __syncthreads(); return val; } data 10 26 42 58 5 11 18 9 19 30 13 27 10 36 78 136 5 11 18 26 9 19 30 42 13 27 58

62 Primitivos paralelos Implementação da soma prefixa
Soma prefixa inclusiva intra-bloco (warp de 4 threads) __device__ int scanBlock(int* data, int tidx, int lane, int warp) { int val = scanWarp(data, tidx, lane); __syncthreads(); if (lane == 3) data[warp] = data[tidx]; __syncthreads(); if (warp == 0) scanWarp(data, tidx, lane); __syncthreads(); if (warp > 0) val = data[warp-1] + val; __syncthreads(); data[tidx] = val; __syncthreads(); return val; } data 10 36 78 136 5 11 18 26 9 19 30 42 13 27 58 val 1 3 6 10 5 11 18 26 9 19 30 42 13 27 58 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136

63 Primitivos paralelos Implementação da soma prefixa
Soma prefixa inclusiva intra-bloco (warp de 4 threads) __device__ int scanBlock(int* data, int tidx, int lane, int warp) { int val = scanWarp(data, tidx, lane); __syncthreads(); if (lane == 3) data[warp] = data[tidx]; __syncthreads(); if (warp == 0) scanWarp(data, tidx, lane); __syncthreads(); if (warp > 0) val = data[warp-1] + val; __syncthreads(); data[tidx] = val; __syncthreads(); return val; } data 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 val 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136

64 Primitivos paralelos Implementação da soma prefixa
Soma prefixa inclusiva global (exercício) Faça a soma prefixa de todos os blocos com scanBlock(). Armazene o resultado de cada bloco i em blockResults[i]. Faça a soma prefixa de blockResults[i]. Cada thread do bloco i adiciona elemento i do passo 3 a seu elemento de saída do passo 1. Passos 1 + 2, 3 e 4 requerem três invocações de kernels. Passo 3 pode requerer invocações repetidas da soma prefixa global se o número de blocos no passo 1 é maior que o tamanho do bloco.

65 Primitivos paralelos Redução Entrada
Sequência A de n elementos de um tipo T Operador binário associativo op Saída: valor do tipo T igual ao último elemento da soma prefixa inclusiva da sequência de entrada A Exemplo: T = int, op = + Entrada A 1 2 3 4 5 6 7 8 1 3 6 10 15 21 28 36 saída

66 Primitivos paralelos Compactação Entrada
Sequência A de n elementos de um tipo T Sequência V de n booleanos indicando os elementos válidos Saída: sequência B de m elementos do tipo T, onde m ≤ n é igual à redução (soma) do vetor V Exemplo: T = int A 1 2 3 4 5 6 7 8 B 1 4 5 7 V 1 m 1 2 3 4 Índices de B (soma prefixa exclusiva de V)

67 Estudo de caso: traçado de raios
Por que traçado de raios? Renderização fotorealística e fisicamente correta Mais fácil de combinar vários efeitos visuais Sombras suaves Iluminação indireta Superfícies reflexivas e polidas Transparência Profundidade de campo Motion blur Etc.

68 Estudo de caso: traçado de raios
Por que traçado de raios em GPU? Capacidade de processamento e paralelismo Algoritmos híbridos: RT e rasterização

69 Estudo de caso: traçado de raios
Traçado de raios em tempo real em GPU? Vários trabalhos na literatura NVIDIA city demo

70 Estudo de caso: traçado de raios
Soluções interativas NVIDIA OptiX (www.nvidia.com/object/optix.html) iray (https://www.mentalimages.com/products/nvidia-iray/rendering.html)

71 Estudo de caso: traçado de raios
Traçado de raios básico Implementação em CUDA Raios secundários de reflexão e transparência Número de atores e luzes limitado à memória disponível Traçado de raios não distribuído Sem tratamento de textura Geometria dos atores definida por malha de triângulos Iluminação direta pelo modelo de Phong

72 Traçado de raios básico
TR básico: um raio de pixel por pixel

73 Traçado de raios básico
Raios de pixel A partir da posição da câmera, e em direção ao centro de cada pixel da janela de projeção, trace um raio de pixel 𝑅 𝑝 . A cor de 𝑅 𝑝 é a cor do pixel correspondente da imagem Determine os objetos da cena que 𝑅 𝑝 intercepta Se nenhum objeto for interceptado, então atribua à cor do pixel a cor de fundo da cena Senão, seja 𝐼 𝑝 o ponto de interseção de 𝑅 𝑝 com o objeto 𝑂 𝑝 mais próximo da origem de 𝑅 𝑝

74 Traçado de raios básico
Raios de sombra ou luz A partir de 𝐼 𝑝 dispare um raio de luz 𝑅 𝑙 em direção à cada fonte de luz 𝑙 Se 𝑅 𝑙 não interceptar nenhum ator (opaco) da cena, então 𝑙 ilumina diretamente 𝐼 𝑝 : adicione à 𝑅 𝑝 a contribuição da luz determinada pelo modelo de iluminação de Phong Dependendo das propriedades de 𝑂 𝑝 , outros raios podem ser disparados (raio de reflexão e raio de refração)

75 Traçado de raios básico
Raios de reflexão Se 𝑂 𝑝 for suficientemene polido em 𝐼 𝑝 , um raio 𝑅 𝑟 é disparado de 𝐼 𝑝 em uma direção definida pela direção de 𝑅 𝑝 e pela normal da superfície de 𝑂 𝑝 no ponto 𝐼 𝑝 A determinação da cor de 𝑅 𝑟 é feita executando-se recursivamente o algoritmo, que trata 𝑅 𝑟 como um novo raio de pixel e 𝐼 𝑝 como o ponto de vista do observador. A cor de 𝑅 𝑟 é somada à cor de 𝑅 𝑝

76 Traçado de raios básico
Raios de refração ou transparência Se 𝑂 𝑝 for suficientemene transparente em 𝐼 𝑝 , um raio 𝑅 𝑡 é disparado de 𝐼 𝑝 em uma direção definida pela direção de 𝑅 𝑝 , pela normal da superfície de 𝑂 𝑝 no ponto 𝐼 𝑝 e pela razão dos índices de refração do meio do qual parte 𝑅 𝑝 e de 𝑂 𝑝 A determinação da cor de 𝑅 𝑡 é feita executando-se recursivamente o algoritmo, que trata 𝑅 𝑡 como um novo raio de pixel e 𝐼 𝑝 como o ponto de vista do observador. A cor de 𝑅 𝑡 é somada à cor de 𝑅 𝑝

77 Traçado de raios básico
Condições de parada Redução do peso de um raio em cada nível de recursão até um peso mínimo Nível de recursão máximo

78 Traçado de raios básico
Exemplo Cena com atores 4 fontes de luz triângulos 10 níveis de recursão Imagem 1024x768 pixels

79 Traçado de raios básico

80 Traçado de raios básico

81 Traçado de raios básico

82 Traçado de raios básico

83 Traçado de raios básico
Problemas com o traçado de raios Aliasing Solução: mais raios por pixel Super-amostragem regular ou adaptativa Traçado de raios distribuido Gargalo: interseção raio/objeto No exemplo: raios disparados interseções raio/triângulo Força bruta impraticável Solução: estruturas de aceleração Grade regular Octree Kd-tree BVH (boundary volume hierarchy)

84 Traçado de raios básico
BVH: árvore binária de AABBs 𝑡 5 𝑡 0 𝑡 1 𝑏 0 𝑡 3 𝑡 4 𝑡 3 𝑡 4 𝑡 5 𝑡 6 𝑡 0 𝑡 1 𝑡 2 𝑡 2 𝑡 6 𝑝 1 𝑝 2 𝑝 3

85 Traçado de raios básico
BVH: árvore binária de AABBs 𝑡 5 𝑡 0 𝑝 3 𝑡 1 𝑏 0 𝑏 1 𝑏 2 𝑡 3 𝑡 4 𝑝 2 𝑡 3 𝑡 4 𝑡 1 𝑡 6 𝑡 0 𝑡 5 𝑡 2 𝑝 1 𝑡 2 𝑡 6

86 Traçado de raios básico
BVH: árvore binária de AABBs 𝑡 5 𝑡 0 𝑡 1 𝑏 3 𝑏 4 𝑏 0 𝑏 1 𝑏 2 𝑡 3 𝑡 4 𝑡 2 𝑡 4 𝑡 1 𝑡 6 𝑡 0 𝑡 5 𝑡 3 𝑡 2 𝑡 6

87 Implementação em CUDA Algoritmo: megakernel Único kernel
Uma thread por pixel: raios primários e secundários Usa pilha de raios secundários (uma por thread) Cada entrada da pilha armazena origem, direção, coeficiente, e peso de um raio Armazenada em memória local (tão lenta quanto a global) Parâmetros Ponteiros para as estruturas de dados de entrada na memória global são agrupados em uma estrutura na memória constante Dados da câmera virtual, luz ambiente, cor de fundo da cena e ponteiro para a imagem a ser gerada (frame) são passados como argumentos para o kernel

88 Implementação em CUDA Algoritmo: megakernel
A 𝑘-ésima thread gera o raio de pixel a ser traçado e o armazena na pilha de raios 𝑆 𝑘 . A origem e direção do raio são determinadas em função da câmera virtual, o coeficiente do raio é a cor branca e o peso do raio é 1 Enquanto houver raios em 𝑆 𝑘 , faça os passos 3 a 6 Retire o raio 𝑅 da pilha 𝑆 𝑘 Se 𝑅 não interceptar a cena, então a cor de fundo da cena é ponderada pelo coeficiente do raio e é acumulada à cor do pixel frame[𝑘]. Vá ao passo 2

89 Implementação em CUDA Algoritmo: megakernel
Trace um raio de sombra 𝑅 𝑙 do ponto de interseção para cada fonte de luz da cena. Se 𝑅 𝑙 não interceptar um objeto opaco antes de atingir a fonte de luz, a contribuição de 𝑅 𝑙 é o produto do coeficiente de 𝑅 pela cor calculada segundo o modelo de iluminação de Phong. Este valor é então acumulado à cor final do pixel frame[𝑘] Se o material do objeto possuir propriedades reflexivas e/ou transparentes, e as condições de parada não foram atingidas, então gere o(s) raio(s) secundário(s) com base nestas propriedades e insira-o(s) em 𝑆 𝑘 . Vá ao passo 2

90 Implementação em CUDA Algoritmo: megakernel Vantagens Problemas
É simples e funciona... Só um kernel, só um contexto CUDA Problemas Divergência do caminho de execução de threads de um warp Acesso não coalescido à memória global Sem uso da memória compartilhada

91 Resultados Cena de teste Equipamento 708.108 triângulos reflexivos
BVH com nós Resolução da imagem, número de luzes e nível máximo de recursão variáveis Equipamento CPU: Intel Xeon 2.4 GHz, 12 GB RAM GPU: NVIDIA Tesla C2050 (448 núcleos, 3GB RAM)

92 Resultados 4 luzes, 10 níveis de recursão

93 Resultados 4 luzes, 10 níveis de recursão

94 Resultados 4 luzes, 10 níveis de recursão Resolução FPS Speedup
Eficiência % 320x240 3,3 14,4 3,2 640x480 1,0 18,1 4,0 800x600 0,7 19,8 4,4 1024x768 0,5 20,6 4,6

95 Resultados 1024x758 pixels, 10 níveis de recursão

96 Resultados 1024x758 pixels, 10 níveis de recursão

97 Resultados 1024x758 pixels, 10 níveis de recursão N. luzes FPS Speedup
Eficiência % 1 0,7 14,9 3,3 2 0,6 16,5 3,7 4 0,5 20,6 4,6 8 0,3 27,4 6,1

98 Resultados 1024x768 pixels, 4 luzes

99 Resultados 1024x768 pixels, 4 luzes

100 Resultados 1024x768 pixels, 4 luzes N. recursão FPS Speedup
Eficiência % 2,2 36,7 8,2 1 0,9 27,6 6,2 5 0,5 21,3 4,7 10 20,6 4,6


Carregar ppt "Tópicos sobre GPGPU em CUDA"

Apresentações semelhantes


Anúncios Google