OpenACC: um promissor standard
  para aceleração de códigos
           Pedro Pais Lopes
NVIDIA GPU Computing Developer Forum
         Curitiba, 17/07/2012
O que é OpenACC?
• Diretivas de compilação, na forma de pragmas
  (C/C++) ou comentários (Fortran) de alto nível,
  que orientam o compilador a executar trechos
  de código em um acelerador

• Esforço conjunto PGI + Cray + NVIDIA + CAPS,
  visando integrar padrão OpenMP

• Vide padrão em www.openacc.org
Sintaxe
• C:
   #pragma acc nome_diretiva [cláusula [,cláusula]…]
      bloco estruturado de código

• Fortran:
   !$acc nome_diretiva [cláusula [,cláusula]…]
      bloco estruturado de código
   !$acc end nome_diretiva
Exemplo #1: Jacobi
    • Converge iterativamente para um valor a
      partir da média dos pontos vizinhos
                                           0°C


           A(i+1,j)



                                    0°C           0°C
A(i-1,j)        A(i,j)   A(i+1,j)



           A(i,j-1)

                                          100°C
Código Fortran
do while ( err > tol .and. iter < iter_max )
  err=0.0
 do j=1,m
   do i=1,n
     Anew(i,j) = .25 * (A(i+1, j ) + A(i-1, j ) + &
                        A(i , j-1) + A(i , j+1))
     err = max(err, Anew(i,j) - A(i,j))
   end do
 end do

 do j=1,m-2
   do i=1,n-2
     A(i,j) = Anew(i,j)
   end do
 end do
  iter = iter +1
end do
Inserindo OpenMP
do while ( err > tol .and. iter < iter_max )
  err=0.0
!$omp parallel do private(i,j) reduction(max:err)
  do j=1,m
    do i=1,n
      Anew(i,j) = .25 * (A(i+1, j ) + A(i-1, j ) + &
                         A(i , j-1) + A(i , j+1))
      err = max(err, Anew(i,j) - A(i,j))
    end do
  end do
!$omp end parallel do
!$omp parallel do private(i,j)
  do j=1,m-2
    do i=1,n-2
      A(i,j) = Anew(i,j)
    end do
  end do
!$omp end parallel do
  iter = iter +1
end do
Código em Fortran e OpenACC
                Um kernel por ninho de laços
do while ( err > tol .and. iter < iter_max )
  err=0.0
!$acc kernels reduction(max:err)        Trafego GPU-CPU gerado
  do j=1,m                          automaticamente pelo compilador
    do i=1,n
      Anew(i,j) = .25 * (A(i+1, j ) + A(i-1, j ) + &
                         A(i , j-1) + A(i , j+1))
      err = max(err, Anew(i,j) - A(i,j))
    end do
  end do
!$acc end kernels         barreira
!$acc kernels
  do j=1,m-2                                   Outro kernel
    do i=1,n-2
      A(i,j) = Anew(i,j)
    end do
  end do
!$acc end kernels
  iter = iter +1
end do
Vantagens
• Código CUDA gerado automaticamente

• Mantém portabilidade entre o código sequencial e o
  código paralelo
   – Chave de compilação define se compilador gera código
     para GPU ou não


• Trafego de dados entre a CPU e a GPU determinado
  “automaticamente” pelo compilador
CPU: Intel Xeon X5680    Desempenho        GPU: NVIDIA Tesla M2070
6 Cores @ 3.33GHz
                        (fonte: NVIDIA)
    Execução                   Tempo (s)   Speedup
    CPU 1 OpenMP thread        69.80       --
    CPU 2 OpenMP threads       44.76       1.56x
    CPU 4 OpenMP threads       39.59       1.76x

    CPU 6 OpenMP threads       39.71       1.76x

    OpenACC GPU 1 thread       162.16      0.43x OPA!
Compilador fez o melhor que pode
• Mas ele NÃO pode desrespeitar o código
1.   Enquanto condicional do while for satisfeita…
2.   copia A para a placa
3.   Computa primeiro laço
4.   Copia Anew para CPU
5.   Copia Anew para placa
6.   Computa segundo laço
7.   Copia A para a CPU
8.   Volta para 1.
Código em Fortran e OpenACC
do while ( err > tol .and. iter < iter_max )
  err=0.0
!$acc kernels reduction(max:err)
  do j=1,m
    do i=1,n
     Anew(i,j) = .25 * (A(i+1, j ) + A(i-1, j ) + &
                        A(i , j-1) + A(i , j+1))
      err = max(err, Anew(i,j) - A(i,j))
    end do
  end do
!$acc end kernels
!$acc kernels
  do j=1,m-2
    do i=1,n-2
      A(i,j) = Anew(i,j)
    end do
  end do
!$acc end kernels
  iter = iter +1
end do
Melhoria #1
do while ( err > tol .and. iter < iter_max )
  err=0.0
!$acc kernels reduction(max:err)
  do j=1,m
    do i=1,n
      Anew(i,j) = .25 * (A(i+1, j ) + A(i-1, j ) + &
                         A(i , j-1) + A(i , j+1))
      err = max(err, Anew(i,j) - A(i,j))
    end do
  end do
               Juntar os dois kernels em um elimina tráfego
             CPU/GPU de A e Anew entre os dois aninamentos
  do j=1,m-2
    do i=1,n-2
      A(i,j) = Anew(i,j)
    end do
  end do
!$acc end kernels
  iter = iter +1
end do
Podemos ir além (melhoria #2)
                     Envie A da CPU para a GPU no início do
                      laço e envie de volta ao final do laço
!$acc data copy(A), create(Anew)              Crie e mantenha Anew na GPU
do while ( err > tol .and. iter < iter_max )
  err=0.0
!$acc kernels reduction(max:err)
  do j=1,m
    do i=1,n
      Anew(i,j) = .25 * (A(i+1, j ) + A(i-1, j ) + &
                           A(i , j-1) + A(i , j+1))
      err = max(err, Anew(i,j) - A(i,j))
    end do
  end do

  do j=1,m-2
    do i=1,n-2
      A(i,j) = Anew(i,j)
    end do
  end do
!$acc end kernels
  iter = iter +1
end do
!$acc end data
CPU: Intel Xeon X5680    Desempenho        GPU: NVIDIA Tesla M2070
6 Cores @ 3.33GHz
                        (fonte: NVIDIA)
    Execução                   Tempo (s)   Speedup

    CPU 1 OpenMP thread        69,80       --

    CPU 2 OpenMP threads       44,76       1,56x

    CPU 4 OpenMP threads       39,59       1,76x

    CPU 6 OpenMP threads       39,71       1,76x
                                           5,11x
    OpenACC GPU                13,65
                                           BOM!
Resumo
• Foram 3 linhas a mais para 5,11x de speedup
  – Inclusão de comentários: outros compiladores
    ignorarão esta linha e código segue inalterado
• Uso de diretivas deve ter “precisão cirúrgica”
• Corretude da computação: não somente se
  preocupar com “race conditions” mas
  também com gerenciamento de dados
  – Lembrar-se SEMPRE que temos cópia LOCAL e
    cópia REMOTA: memória não estará inicializada
Sumário
• OpenACC é forma promissora de programar
  aceleradores inserindo diretivas no programa original

• Como em OpenMP, é:
   – Incremental
   – Portátil

• Muito mais simples e fácil que CUDA, preservando o
  investimento na codificação original

Introdução ao Processamento Paralelo (4.2)

  • 1.
    OpenACC: um promissorstandard para aceleração de códigos Pedro Pais Lopes NVIDIA GPU Computing Developer Forum Curitiba, 17/07/2012
  • 2.
    O que éOpenACC? • Diretivas de compilação, na forma de pragmas (C/C++) ou comentários (Fortran) de alto nível, que orientam o compilador a executar trechos de código em um acelerador • Esforço conjunto PGI + Cray + NVIDIA + CAPS, visando integrar padrão OpenMP • Vide padrão em www.openacc.org
  • 3.
    Sintaxe • C: #pragma acc nome_diretiva [cláusula [,cláusula]…] bloco estruturado de código • Fortran: !$acc nome_diretiva [cláusula [,cláusula]…] bloco estruturado de código !$acc end nome_diretiva
  • 4.
    Exemplo #1: Jacobi • Converge iterativamente para um valor a partir da média dos pontos vizinhos 0°C A(i+1,j) 0°C 0°C A(i-1,j) A(i,j) A(i+1,j) A(i,j-1) 100°C
  • 5.
    Código Fortran do while( err > tol .and. iter < iter_max ) err=0.0 do j=1,m do i=1,n Anew(i,j) = .25 * (A(i+1, j ) + A(i-1, j ) + & A(i , j-1) + A(i , j+1)) err = max(err, Anew(i,j) - A(i,j)) end do end do do j=1,m-2 do i=1,n-2 A(i,j) = Anew(i,j) end do end do iter = iter +1 end do
  • 6.
    Inserindo OpenMP do while( err > tol .and. iter < iter_max ) err=0.0 !$omp parallel do private(i,j) reduction(max:err) do j=1,m do i=1,n Anew(i,j) = .25 * (A(i+1, j ) + A(i-1, j ) + & A(i , j-1) + A(i , j+1)) err = max(err, Anew(i,j) - A(i,j)) end do end do !$omp end parallel do !$omp parallel do private(i,j) do j=1,m-2 do i=1,n-2 A(i,j) = Anew(i,j) end do end do !$omp end parallel do iter = iter +1 end do
  • 7.
    Código em Fortrane OpenACC Um kernel por ninho de laços do while ( err > tol .and. iter < iter_max ) err=0.0 !$acc kernels reduction(max:err) Trafego GPU-CPU gerado do j=1,m automaticamente pelo compilador do i=1,n Anew(i,j) = .25 * (A(i+1, j ) + A(i-1, j ) + & A(i , j-1) + A(i , j+1)) err = max(err, Anew(i,j) - A(i,j)) end do end do !$acc end kernels barreira !$acc kernels do j=1,m-2 Outro kernel do i=1,n-2 A(i,j) = Anew(i,j) end do end do !$acc end kernels iter = iter +1 end do
  • 8.
    Vantagens • Código CUDAgerado automaticamente • Mantém portabilidade entre o código sequencial e o código paralelo – Chave de compilação define se compilador gera código para GPU ou não • Trafego de dados entre a CPU e a GPU determinado “automaticamente” pelo compilador
  • 9.
    CPU: Intel XeonX5680 Desempenho GPU: NVIDIA Tesla M2070 6 Cores @ 3.33GHz (fonte: NVIDIA) Execução Tempo (s) Speedup CPU 1 OpenMP thread 69.80 -- CPU 2 OpenMP threads 44.76 1.56x CPU 4 OpenMP threads 39.59 1.76x CPU 6 OpenMP threads 39.71 1.76x OpenACC GPU 1 thread 162.16 0.43x OPA!
  • 10.
    Compilador fez omelhor que pode • Mas ele NÃO pode desrespeitar o código 1. Enquanto condicional do while for satisfeita… 2. copia A para a placa 3. Computa primeiro laço 4. Copia Anew para CPU 5. Copia Anew para placa 6. Computa segundo laço 7. Copia A para a CPU 8. Volta para 1.
  • 11.
    Código em Fortrane OpenACC do while ( err > tol .and. iter < iter_max ) err=0.0 !$acc kernels reduction(max:err) do j=1,m do i=1,n Anew(i,j) = .25 * (A(i+1, j ) + A(i-1, j ) + & A(i , j-1) + A(i , j+1)) err = max(err, Anew(i,j) - A(i,j)) end do end do !$acc end kernels !$acc kernels do j=1,m-2 do i=1,n-2 A(i,j) = Anew(i,j) end do end do !$acc end kernels iter = iter +1 end do
  • 12.
    Melhoria #1 do while( err > tol .and. iter < iter_max ) err=0.0 !$acc kernels reduction(max:err) do j=1,m do i=1,n Anew(i,j) = .25 * (A(i+1, j ) + A(i-1, j ) + & A(i , j-1) + A(i , j+1)) err = max(err, Anew(i,j) - A(i,j)) end do end do Juntar os dois kernels em um elimina tráfego CPU/GPU de A e Anew entre os dois aninamentos do j=1,m-2 do i=1,n-2 A(i,j) = Anew(i,j) end do end do !$acc end kernels iter = iter +1 end do
  • 13.
    Podemos ir além(melhoria #2) Envie A da CPU para a GPU no início do laço e envie de volta ao final do laço !$acc data copy(A), create(Anew) Crie e mantenha Anew na GPU do while ( err > tol .and. iter < iter_max ) err=0.0 !$acc kernels reduction(max:err) do j=1,m do i=1,n Anew(i,j) = .25 * (A(i+1, j ) + A(i-1, j ) + & A(i , j-1) + A(i , j+1)) err = max(err, Anew(i,j) - A(i,j)) end do end do do j=1,m-2 do i=1,n-2 A(i,j) = Anew(i,j) end do end do !$acc end kernels iter = iter +1 end do !$acc end data
  • 14.
    CPU: Intel XeonX5680 Desempenho GPU: NVIDIA Tesla M2070 6 Cores @ 3.33GHz (fonte: NVIDIA) Execução Tempo (s) Speedup CPU 1 OpenMP thread 69,80 -- CPU 2 OpenMP threads 44,76 1,56x CPU 4 OpenMP threads 39,59 1,76x CPU 6 OpenMP threads 39,71 1,76x 5,11x OpenACC GPU 13,65 BOM!
  • 15.
    Resumo • Foram 3linhas a mais para 5,11x de speedup – Inclusão de comentários: outros compiladores ignorarão esta linha e código segue inalterado • Uso de diretivas deve ter “precisão cirúrgica” • Corretude da computação: não somente se preocupar com “race conditions” mas também com gerenciamento de dados – Lembrar-se SEMPRE que temos cópia LOCAL e cópia REMOTA: memória não estará inicializada
  • 16.
    Sumário • OpenACC éforma promissora de programar aceleradores inserindo diretivas no programa original • Como em OpenMP, é: – Incremental – Portátil • Muito mais simples e fácil que CUDA, preservando o investimento na codificação original