CUDA, ROCm, oneAPI? — Executando código em uma GPU, qualquer GPU

Jan 02 2023
Por que conhecer o modelo de programação de GPU de vários fornecedores é um mal necessário... ou é? Introdução Em meus dois últimos posts sobre programação paralela e aceleradora, falei sobre os fundamentos da programação paralela e do acelerador e alguns dos conceitos de programação necessários para garantir a correção do seu código. A pergunta interessante para o programador que está aprendendo a programar aceleradores é “Eu realmente preciso aprender CUDA, ROCm e SYCL para programar para aceleradores NVIDIA, AMD e Intel?” Ao planejar esta série, esperava mergulhar nas semelhanças e diferenças entre a execução de hardware e os modelos de memória de cada arquitetura.

Por que conhecer o modelo de programação de GPU de vários fornecedores é um mal necessário... ou é?

Sistema de teste, imagem cortesia do autor

Introdução

Em meus dois últimos posts sobre programação paralela e aceleradora, falei sobre os fundamentos da programação paralela e do acelerador e alguns dos conceitos de programação necessários para garantir a correção do seu código. A pergunta interessante para o programador que está aprendendo a programar aceleradores é “Eu realmente preciso aprender CUDA, ROCm e SYCL para programar para aceleradores NVIDIA, AMD e Intel?”

Ao planejar esta série, esperava mergulhar nas semelhanças e diferenças entre a execução de hardware e os modelos de memória de cada arquitetura. Eu ia falar sobre warps, wavefronts e workgroups, as respectivas construções que CUDA, ROCm e SYCL usam para agrupar a execução em threads de hardware. No entanto, quanto mais penso nisso, mais parece um pouco demais para um post de blog. Se você estiver interessado nesses tópicos, consulte o SYCL para desenvolvedores CUDA da Codeplay.

Em vez disso, hoje vou me concentrar em como passar de nenhum código para execução em uma GPU, qualquer GPU, o mais rápido possível. Nada é tão divertido quanto ver os benefícios de desempenho do seu acelerador o mais rápido possível. Para mim, isso é divertido porque coincide com o lançamento do Codeplay oneAPI para NVIDIA e oneAPI para AMD (beta) .

SYCL e GPUs de vários fornecedores

SYCL é um modelo de programação que possui suporte de compilador para GPUs NVIDIA, AMD e Intel. Você pode escrever seu código em SYCL e, em seguida, construí-lo e executá-lo nas GPUs desses fornecedores. Anteriormente, isso exigia o uso de vários compiladores da comunidade ou a criação do compilador Intel Open Source, dependendo do destino da GPU. Com o lançamento das mais recentes cadeias de ferramentas do Codeplay, agora você pode executar seu código de forma rápida e fácil por meio de uma única cadeia de ferramentas pré-construída. Para mais detalhes sobre isso, você pode ler o blog de Ruyman Reyes, CTO da Codeplay, em seu novo lançamento.

Uma pergunta que você pode estar se perguntando é: posso realmente criar meu código uma vez e depois decidir em tempo de execução em qual GPU executar? A resposta é SIM ! Isso é bom sobre esse suporte, você cria seu código uma vez e no tempo de execução, pode escolher seu destino manualmente ou deixar a biblioteca de tempo de execução escolher automaticamente para você.

Como você chegou até aqui, presumo que seja do seu interesse e meu objetivo é colocá-lo em funcionamento na GPU de sua escolha. Para meus propósitos, estou testando todas as 3 GPUs de fornecedores no Ubuntu 22.04 e fazendo a instalação via APT.

Sistema de teste, imagem cortesia do autor

Instalando a cadeia de ferramentas Codeplay

Primeiro, configuramos alguns pacotes básicos do sistema:

sudo apt update
sudo apt -y install cmake pkg-config build-essential

# download the key to system keyring
wget -O- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB \
| gpg --dearmor | sudo tee /usr/share/keyrings/oneapi-archive-keyring.gpg > /dev/null

# add signed entry to apt sources and configure the APT client to use Intel repository:
echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list

sudo apt-update
sudo apt install intel-basekit

Certifique-se de ter os drivers/frameworks básicos necessários para cada GPU que você possui/deseja segmentar, novamente estou vinculando as instruções do Ubuntu:

Instalação do driver da GPU Intel

As instruções para GPUs Intel em diferentes sistemas operacionais podem ser encontradas aqui . Como estou usando um Intel Arc no Ubuntu 22.04, segui estas instruções .

instalação oneAPI para NVIDIA

Você pode acessar aqui para obter as instruções oficiais mais recentes sobre como instalar o CUDA, estou inserindo o que uso para colocá-lo em funcionamento:

  1. Instale o chaveiro CUDA, CUDA e reinicie
  2. wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.0-1_all.deb
    sudo dpkg -i cuda-keyring_1.0-1_all.deb
    
    sudo apt-get update
    sudo apt-get install cuda-11-7
    sudo apt-get install nvidia-gds
    sudo reboot
    

    export PATH=/usr/local/cuda-11.7/bin${PATH:+:${PATH}}
    export LD_LIBRARY_PATH=/usr/local/cuda-11.7/lib64\
                             ${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
    

sh oneapi-for-nvidia-gpus-2023.0.0-linux.sh

. /opt/intel/oneapi/setvars.sh --include-intel-llvm
export PATH=/usr/local/cuda-11.7/bin${PATH:+:${PATH}}
export LD_LIBRARY_PATH=/usr/local/cuda-11.7/lib64\
                         ${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

Você pode ir aqui para obter as instruções oficiais mais recentes sobre como instalar o ROCm, novamente inlining aqui para tornar as coisas um pouco mais fáceis para você:

  1. Instale o programa AMD amdgpu-install via APT
  2. sudo apt-get update
    wget https://repo.radeon.com/amdgpu-install/5.4.1/ubuntu/jammy/amdgpu-install_5.4.50401-1_all.deb
    sudo apt-get install ./amdgpu-install_5.4.50401-1_all.deb
    

    amdgpu-install --usecase="dkms,graphics,opencl,hip,hiplibsdk"
    

sh oneapi-for-amd-gpus-2023.0.0-linux.sh

. /opt/intel/oneapi/setvars.sh --include-intel-llvm

export PATH=/PATH_TO_ROCM_ROOT/bin:$PATH
export LD_LIBRARY_PATH=/PATH_TO_ROCM_ROOT/lib:$LD_LIBRARY_PATH

Há muitos exemplos de códigos diferentes que você pode experimentar no repositório GitHub de amostras oneAPI de código aberto:

No exemplo Codeplay, você pode ver que eles criaram este simple-sycl-app.cpp:

#include <sycl/sycl.hpp>

int main() {
  // Creating buffer of 4 ints to be used inside the kernel code
  sycl::buffer<sycl::cl_int, 1> Buffer(4);

  // Creating SYCL queue
  sycl::queue Queue;

  // Size of index space for kernel
  sycl::range<1> NumOfWorkItems{Buffer.size()};

  // Submitting command group(work) to queue
  Queue.submit([&](sycl::handler &cgh) {
    // Getting write only access to the buffer on a device
    auto Accessor = Buffer.get_access<sycl::access::mode::write>(cgh);
    // Executing kernel
    cgh.parallel_for<class FillBuffer>(
        NumOfWorkItems, [=](sycl::id<1> WIid) {
          // Fill buffer with indexes
          Accessor[WIid] = (sycl::cl_int)WIid.get(0);
        });
  });

  // Getting read only access to the buffer on the host.
  // Implicit barrier waiting for queue to complete the work.
  const auto HostAccessor = Buffer.get_access<sycl::access::mode::read>();

  // Check the results
  bool MismatchFound = false;
  for (size_t I = 0; I < Buffer.size(); ++I) {
    if (HostAccessor[I] != I) {
      std::cout << "The result is incorrect for element: " << I
                << " , expected: " << I << " , got: " << HostAccessor[I]
                << std::endl;
      MismatchFound = true;
    }
  }

  if (!MismatchFound) {
    std::cout << "The results are correct!" << std::endl;
  }

  return MismatchFound;
}

Normalmente, para construir nossos vários alvos, podemos usar linhas de comando parecidas com esta:

NVIDIA:

clang++ -fsycl -fsycl-targets=nvptx64-nvidia-cuda simple-sycl-app.cpp -o simple-sycl-app

clang++ -fsycl -fsycl-targets=amdgcn-amd-amdhsa -Xsycl-target-backend --offload-arch=<ARCH> simple-sycl-app.cpp -o simple-sycl-app

clang++ -fsycl -fsycl-targets=spir64 simple-sycl-app.cpp -o simple-sycl-app

Criação para GPUs de vários fornecedores

Digamos que eu queira executar GPUs NVIDIA e Intel ao mesmo tempo. Na verdade, posso criar um binário gordo que será executado em uma GPU NVIDIA ou Intel. O truque aqui é definir os parâmetros da linha de comando de construção apropriadamente para ter vários destinos SYCL.

clang++ -fsycl -fsycl-targets=nvptx64-nvidia-cuda,spir64 simple-sycl-app.cpp -o simple-sycl-app

tonym@LianLi-Linux:~/dev/2023.0Test$ clang++ -fsycl -fsycl-targets=nvptx64-nvidia-cuda,spir64 simple-sycl-app.cpp -o simple-sycl-app
 
tonym@LianLi-Linux:~/dev/2023.0Test$ SYCL_DEVICE_FILTER=cuda SYCL_PI_TRACE=1 ./simple-sycl-app
SYCL_PI_TRACE[basic]: Plugin found and successfully loaded: libpi_cuda.so [ PluginVersion: 11.15.1 ]
SYCL_PI_TRACE[all]: Selected device: -> final score = 1500
SYCL_PI_TRACE[all]:   platform: NVIDIA CUDA BACKEND
SYCL_PI_TRACE[all]:   device: NVIDIA GeForce RTX 3080 Ti
The results are correct!
tonym@LianLi-Linux:~/dev/2023.0Test$
 
tonym@LianLi-Linux:~/dev/2023.0Test$ SYCL_DEVICE_FILTER=ext_oneapi_level_zero:gpu:0 SYCL_PI_TRACE=1 ./simple-sycl-app
SYCL_PI_TRACE[basic]: Plugin found and successfully loaded: libpi_level_zero.so [ PluginVersion: 11.15.1 ]
SYCL_PI_TRACE[all]: Selected device: -> final score = 1550
SYCL_PI_TRACE[all]:   platform: Intel(R) Level-Zero
SYCL_PI_TRACE[all]:   device: Intel(R) Graphics [0x56a0]
The results are correct!

Trabalho futuro

Alguns leitores astutos podem perceber que não incluí um exemplo de AMD em meu exemplo de GPU de vários fornecedores. Isso acontece porque recentemente substituí a GPU AMD 6800XT por uma nova GPU AMD RX 7900XT. Infelizmente, o ROCm atualmente não é instalado corretamente no meu sistema Linux, independentemente do kernel que estou usando com a placa RDNA3. Assim que isso for atualizado, voltarei e fornecerei instruções com um exemplo em execução em todos os 3 cartões.

Observe que anteriormente uma versão desse fluxo funcionava para o 6800XT, mas isso foi antes do software Codeplay ser lançado usando o fluxo DPC++ de código aberto documentado aqui:

Conclusão

A oneAPI para GPUs NVIDIA da Codeplay me permitiu criar binários para GPUs NVIDIA ou Intel facilmente. O tempo para configurar o oneAPI adicional para GPUs NVIDIA foi de cerca de 10 minutos além do tempo para instalar o Intel oneAPI Base Toolkit. Isso incluiu a instalação do CUDA e do software Codeplay.

O novo software Codeplay abre uma nova opção para indivíduos e empresas que consideram opções de GPU de vários fornecedores. Antes, as opções eram de código aberto, compiladores suportados pela comunidade. Esta é uma opção fantástica e o que eu normalmente uso para o trabalho do dia-a-dia.

No entanto, para pessoas que consideram suporte de longo prazo, também conhecidas como empresas, dependem de cadeias de ferramentas para sua subsistência, ter uma opção com suporte oficial do Codeplay e da Intel pode ajudar a proporcionar tranquilidade.

Se você quiser ver quais notícias aleatórias de tecnologia estou lendo, pode me seguir no Twitter. Além disso, confira o Code Together , um podcast da Intel para desenvolvedores que eu hospedo, onde conversamos sobre tecnologia.

Tony é arquiteto de software e evangelista técnico da Intel. Ele foi o arquiteto do Intel VTune Profiler e outras ferramentas de desempenho e, mais recentemente, liderou a equipe de engenharia de software que construiu a plataforma de data center que permitiu a solução escalável MLPerf da Habana.