CUDA, ROCm, una API? — Ejecutar código en una GPU, cualquier GPU

Jan 02 2023
¿Por qué conocer el modelo de programación de GPU de múltiples proveedores es un mal necesario... o lo es? Introducción En mis últimas dos publicaciones sobre programación paralela y con aceleradores, hablé sobre los conceptos básicos de la programación con aceleradores y paralelos y algunos de los conceptos de programación necesarios para garantizar la corrección de su código. La pregunta interesante para el programador que aprende a programar aceleradores es "¿Realmente necesito aprender CUDA, ROCm y SYCL para programar para aceleradores NVIDIA, AMD e Intel?" Al planificar esta serie, esperaba sumergirme en las similitudes y diferencias entre la ejecución del hardware y los modelos de memoria de cada arquitectura.

¿Por qué conocer el modelo de programación de GPU de múltiples proveedores es un mal necesario... o lo es?

Sistema de prueba, imagen cortesía del autor

Introducción

En mis últimas dos publicaciones sobre programación paralela y con aceleradores, hablé sobre los conceptos básicos de la programación con aceleradores y paralelos y algunos de los conceptos de programación necesarios para garantizar la corrección de su código. La pregunta interesante para el programador que aprende a programar aceleradores es "¿Realmente necesito aprender CUDA, ROCm y SYCL para programar para aceleradores NVIDIA, AMD e Intel?"

Al planificar esta serie, esperaba sumergirme en las similitudes y diferencias entre la ejecución del hardware y los modelos de memoria de cada arquitectura. Iba a hablar sobre deformaciones, frentes de onda y grupos de trabajo, las construcciones respectivas que CUDA, ROCm y SYCL usan para agrupar la ejecución en subprocesos de hardware. Sin embargo, cuanto más lo pienso, más parece demasiado para una publicación de blog. Si está interesado en esos temas, consulte el agradable SYCL para desarrolladores de CUDA de Codeplay.

En su lugar, hoy me centraré en cómo pasar de no tener código a ejecutarse en una GPU, cualquier GPU, lo más rápido posible. Nada es tan divertido como ver los beneficios de rendimiento de su acelerador lo más rápido posible. Para mí, esto es divertido porque coincide con el lanzamiento de Codeplay oneAPI para NVIDIA y oneAPI para AMD (beta) .

SYCL y GPU de varios proveedores

SYCL es un modelo de programación que tiene soporte de compilador para GPU NVIDIA, AMD e Intel. Puede escribir su código en SYCL y luego compilarlo y ejecutarlo en las GPU de esos proveedores. Anteriormente, esto requería el uso de varios compiladores de la comunidad o la creación del compilador de código abierto de Intel, según su objetivo de GPU. Con el lanzamiento de las últimas cadenas de herramientas de Codeplay, ahora puede hacer que su código se ejecute rápida y fácilmente a través de una única cadena de herramientas preconstruida. Para obtener más detalles sobre esto, puede leer el blog de Ruyman Reyes, CTO de Codeplay, sobre su nuevo lanzamiento.

Una pregunta que puede estar haciendo es ¿puedo construir mi código una vez y luego decidir en tiempo de ejecución en qué GPU ejecutar? ¡ La respuesta es ! Esto es lo bueno de este soporte, construye su código una vez y en tiempo de ejecución, puede elegir su objetivo manualmente o dejar que la biblioteca de tiempo de ejecución elija por usted automáticamente.

Ya que ha llegado hasta aquí, asumiré que esto es de su interés y mi objetivo es ponerlo en funcionamiento con la GPU de su elección. Para mis propósitos, estoy probando las GPU de los 3 proveedores en Ubuntu 22.04 y realizo la instalación a través de APT.

Sistema de prueba, imagen cortesía del autor

Instalación de la cadena de herramientas de Codeplay

Primero, configuramos algunos paquetes básicos del 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

Asegúrese de tener los controladores/marcos básicos que necesita para cada GPU que tiene/quiere apuntar, nuevamente estoy vinculando las instrucciones de Ubuntu:

Instalación del controlador Intel GPU

Las instrucciones para GPU Intel en diferentes sistemas operativos se pueden encontrar aquí . Debido a que estoy usando Intel Arc en Ubuntu 22.04, seguí estas instrucciones .

oneAPI para la instalación de NVIDIA

Puede ir aquí para obtener las últimas instrucciones oficiales sobre cómo instalar CUDA, estoy destacando lo que uso para ponerlo en funcionamiento:

  1. Instale el llavero CUDA, CUDA y 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}}

Puede ir aquí para obtener las instrucciones oficiales más recientes sobre cómo instalar ROCm, nuevamente insertando aquí para facilitarle un poco las cosas:

  1. Instale el programa de instalación AMD amdgpu a través de 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

Hay muchos ejemplos de códigos diferentes que puede probar en el repositorio de GitHub de ejemplos de oneAPI de código abierto:

En el ejemplo de Codeplay, puede ver que crearon 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;
}

Por lo general, para compilar para nuestros diversos objetivos, podríamos usar líneas de comando que se vean así:

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

Creación para GPU de varios proveedores

Digamos que quería ejecutar GPU NVIDIA e Intel al mismo tiempo, en realidad puedo crear un binario gordo que se ejecutará en una GPU NVIDIA o Intel. El truco aquí es configurar los parámetros de la línea de comando de compilación de manera adecuada para tener múltiples objetivos 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!

Trabajo futuro

Algunos lectores astutos pueden notar que no incluí un ejemplo de AMD en mi ejemplo de GPU de múltiples proveedores. Esto sucede porque recientemente reemplacé la GPU AMD 6800XT con una GPU AMD RX 7900XT completamente nueva. Desafortunadamente, ROCm actualmente no se instala correctamente en mi sistema Linux, independientemente del kernel que esté usando con la tarjeta RDNA3. Una vez que esto se actualice, regresaré y proporcionaré instrucciones con un ejemplo que se ejecuta en las 3 tarjetas.

Tenga en cuenta que anteriormente una versión de este flujo funcionó para el 6800XT, pero eso fue antes de que se lanzara el software Codeplay usando el flujo DPC++ de código abierto documentado aquí:

Conclusión

OneAPI para GPU NVIDIA de Codeplay me permitió crear archivos binarios para GPU NVIDIA o Intel fácilmente. El tiempo para configurar oneAPI adicional para GPU NVIDIA fue de aproximadamente 10 minutos además del tiempo para instalar Intel oneAPI Base Toolkit. Esto incluyó la instalación de CUDA y el software Codeplay.

El nuevo software Codeplay abre una nueva opción para individuos y empresas que consideran opciones de GPU de múltiples proveedores. Antes, las opciones eran compiladores de código abierto respaldados por la comunidad. Esta es una opción fantástica y lo que normalmente uso para el trabajo diario.

Sin embargo, para las personas que consideran un soporte a largo plazo, es decir, las empresas llegan a depender de las cadenas de herramientas para su sustento, tener una opción con soporte oficial de Codeplay e Intel puede ayudar a brindar tranquilidad.

Si quieres ver qué noticias de tecnología al azar estoy leyendo, puedes seguirme en Twitter. Además, consulte Code Together , un podcast de Intel para desarrolladores que presento donde hablamos de tecnología.

Tony es arquitecto de software y evangelista técnico en Intel. Fue arquitecto de Intel VTune Profiler y otras herramientas de rendimiento y, más recientemente, dirigió el equipo de ingeniería de software que construyó la plataforma del centro de datos que permitió la solución escalable MLPerf de Habana.