Tutorial 03 - Ejecución de Tareas

1) SLURM workload manager

La ejecución de trabajos se realiza mediante la herramienta SLURM, un workload manager y job scheduler que gestiona la ejecución de trabajos y recursos computacionales según el orden y prioridad. Gracias a SLURM, es posible una convivencia ordenada y justa entre múltiples usuarios sin interrumpirse entre ellos.

Particiones

Las particiones son un conjunto de recursos agrupados para un determinado propósito. En el caso del Patagón, existen dos particiones

Partición gpu

Para trabajos de GPU. Esta es la partición principal que caracteriza al Patagón. Puede consultar con el comando scontrol show partition gpu.

Particion cpu

Para trabajos de CPU secuenciales o paralelos. Esta partición existe para dar la posibilidad a investigaciones que por alguna razón aun no tienen programas que usan GPUs. Cabe mencionar que la cantidad de recursos computacionales de esta partición es mucho menor a la gpu, ya que el Patagón esta pensado para GPU Computing. Puede consultar con el comando scontrol show partition cpu.

2) Solicitar recursos hardware al ejecutar

Comencemos con algo simple, ejecutar el comando nvidia-smi en el nodo de computo. Este comando lista las GPUs disponibles para utilizar.

➜  ~ srun -p gpu nvidia-smi -L
No devices found.

Como vemos, no existen GPUs disponibles debido a que Slurm asume 0 GPUs por defecto. Para solicitar GPUs, es necesario adicionar el parámetro --gres=gpu:A100:<num>, donde <num> es la cantidad de GPUs a solicitar. Por ejemplo, si queremos ejecutar nvidia-smi -L con una GPU

➜  ~ srun -p gpu --gres=gpu:A100:1 nvidia-smi -L
GPU 0: A100-SXM4-40GB (UUID: GPU-35a012ac-2b34-b68f-d922-24aa07af1be6)

El valor de <num> define la cantidad de GPUs a reservar. Otro ejemplo, volvamos a ejecutar nvidia-smi -L ahora pidiendo 4 GPUs

➜  ~ srun -p gpu --gres=gpu:A100:4 nvidia-smi -L
GPU 0: A100-SXM4-40GB (UUID: GPU-baa4736e-088f-77ce-0290-ba745327ca95)
GPU 1: A100-SXM4-40GB (UUID: GPU-d40a3b1b-006b-37de-8b72-669c59d14954)
GPU 2: A100-SXM4-40GB (UUID: GPU-35a012ac-2b34-b68f-d922-24aa07af1be6)
GPU 3: A100-SXM4-40GB (UUID: GPU-b75a4bf8-123b-a8c0-dc75-7709626ead20)

El maximo de GPUs en el patagón es actualmente 8 GPUs. Si se solicitan mas GPUs que la cantidad máxima, se retornara un error de recursos no satisfechos. Si se solicita un numero de GPUs entre 1-8 pero estos no se encuentran disponibles aun (otros usuarios usando GPUs), entonces el trabajo será encolado por SLURM y se ejecutará cuando sea su turno.

3) Ejecución con Contenedores (pyxis/enroot)

En el Patagón, las dependencias de librerías o software se satisface mediante contenedores. Es decir, al ejecutar un trabajo por SLURM, el usuario indica el contenedor que necesita para ejecutar el trabajo. Este contenedor se descarga automáticamente por el plugin pyxis para SLURM y la herramienta enroot para ejecutar contenedores. Así, el contenedor se transforma en el nuevo entorno del trabajo, pudiendo satisfacer todas las dependencias necesarias sin afectar a los otros usuarios ni a la configuración nativa del Patagón. Al finalizar la tarea, el contenedor se desmonta automáticamente del nodo de computo por defecto y se elimina a menos que el usuario indique que quiere conservarlo para futuro uso (y evitar descargarlo nuevamente).

Una razón importante por la que el Patagón maneja las dependencias con contenedores es porque así los nodos quedan libre de software instalado nativamente, disminuyendo las probabilidades de fallas a nivel software, incompatibilidad de versiones y tiempos mantención como también compilación.

Para poder entender mejor la función de los contenedores, ejecutemos nvcc --version para consultar la versión del compilador de CUDA nvcc.

➜  ~ srun -p gpu nvcc --version         
slurmstepd: error: execve(): nvcc: No such file or directory
srun: error: nodeGPU01: task 0: Exited with exit code 2

Hemos tenido un error como resultado. Esto ocurre porque el nodo DGXA100 de cómputo carece de alguna versión particular de CUDA. Para lograr hacer funcionar este ejemplo, necesitamos indicar un contenedor que incluya CUDA al momento de ejecutar nuestra tarea, en este caso algo tan sencillo como nvcc --version. Nvidia GPU Cloud (NGC) ofrece diversos contenedores para utilizar, incluyendo el contenedor de CUDA. En este caso, hemos escogido la versión 11.2.2 de CUDA:

➜  ~ srun --container-name=cuda-11.2.2  --container-image='nvcr.io/nvidia/cuda:11.2.2-devel-ubuntu20.04' nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2021 NVIDIA Corporation
Built on Sun_Feb_14_21:12:58_PST_2021
Cuda compilation tools, release 11.2, V11.2.152
Build cuda_11.2.r11.2/compiler.29618528_0

Para lograr que pyxis/enroot puedan descargar y utilizar automáticamente contenedores de una URL especificada por el usuario, debe configurar sus credenciales. Una vez que se ha asociado el contenedor a un nombre con --container-name=<nombre>, luego puedes ejecutar solo utilizando --container-name=<nombre> para referirte a tu contenedor.

➜  srun --container-name=cuda-11.2.2 nvcc --version 
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2021 NVIDIA Corporation
Built on Sun_Feb_14_21:12:58_PST_2021
Cuda compilation tools, release 11.2, V11.2.152
Build cuda_11.2.r11.2/compiler.29618528_0

Nota (Noviembre 2023): Debido a cambios recientes en pyxis, para mantener la misma ubicacion dentro del job es necesario incluir --container-workdir=${PWD} como opcion a srun. Por ejemplo, si el usuario es user y esta actualmente ubicado en /home/user/, entonces:

➜  ~ pwd
/home/user
➜  ~ srun --container-name=cuda-11.2.2 pwd
/
➜  ~ srun --container-workdir=${PWD} --container-name=cuda-11.2.2 pwd
/home/user

4) Ejecución de trabajos

Usando comando srun (tiempo-real, anclado a la sesión)

El comando srun permite a los usuarios ejecutar trabajos para verlos en ejecución de forma directa e interactiva en el terminal. Este modo es síncrono con la sesión, es decir, el terminal queda anclado a la ejecución y progreso del trabajo, y si uno termina su sesión (se desconecta del Patagón), entonces el trabajo se pierde (a menos que este usando screen o algo similar). El comando srun sigue la estructura

srun  --container-workdir=${PWD} <contenedor>   <recursos>   <tarea>
Ejemplo srun

Supongamos que hemos escrito un programa para GPU que multiplica dos matrices y ahora queremos ejecutarlo en el Patagón usando una GPU, entonces primero compilaríamos el programa

➜  srun --container-workdir=${PWD} --container-name=cuda-11.2.2 make                                     
nvcc -O3 -arch=sm_80 -lnvidia-ml -Xcompiler -fopenmp main.cu -o prog
➜  

Y luego podríamos ejecutar el programa

➜ srun --container-workdir=${PWD} --container-name=cuda-11.2.2 --gres=gpu:A100:1 ./prog 0 $((1024*30)) 1
GPU MATMUL
size: 30720 x 30720
mode 1
Exploring GPUs
    Driver version: 450.102.04 
    NUM GPUS = 1
    Listing devices:
        GPU0 A100-SXM4-40GB, index=0, UUID=GPU-35a012ac-2b34-b68f-d922-24aa07af1be6  -> util = 0%
Choosing GPU 0
initializing A and B.......done
matmul shared mem..........done: time: 11.213796 secs
copying result to host.....done
verifying result...........done
➜

Usando comando sbatch (desacoplado de la sesión)

El comando sbatch permite ejecutar tareas por lotes sin la necesidad de mantener la sesión abierta, es decir el usuario puede desconectarse luego de haber lanzado su trabajo con sbatch. Para utilizar sbatch es necesario escribir un script, típicamente con extensión *.slurm, en el cual se indicaran los recursos a solicitar, el contenedor, y la tarea en particular. Una tarea sbatch puede contener múltiples llamadas a srun en su script *.slurm, y aun seguirá siendo considerada como una sola tarea. Esto ultimo es de gran utilidad cuando necesitamos construir un gran trabajo compuesto de trabajos menores. Por ejemplo, una tarea que conste de pre-procesamiento, procesamiento y post-procesamiento podría ser construida con tres llamadas a srun dentro de un script *.slurm.

Ejemplo sbatch

Supongamos que hemos escrito un programa para GPU como el producto de matrices. El siguiente script contiene el detalle de la ejecución para sbatch.

#!/bin/bash

# IMPORTANT PARAMS
#SBATCH -p gpu                       # Submit to which partition
#SBATCH --gres=gpu:A100:1            # GPU resources, format TYPE:device:quantity 

# OTHER PARAMS
#SBATCH -J TUT03-single-GPU          # Name the job
#SBATCH -o TUT03-single-GPU-%j.out   # Write the standard output to file named 'jMPItest-<job_number>.out'
#SBATCH -e TUT03-single-GPU-%j.err   # Write the standard error to file named 'jMPItest-<job_number>.err'

# COMMANDS ON THE COMPUTE NODE
pwd                         # prints current working directory
date                        # prints the date and time

# compile the GPU program
srun --container-workdir=${PWD} --container-name=cuda-11.2.2 make
# run the GPU program multiple times
srun --container-workdir=${PWD} --container-name=cuda-11.2.2 ./prog 0 $((1024*30)) 1

Luego para ejecutar el trabajo, ocupamos el comando sbatch pasando nuestro script como argumento.

➜  sbatch job.slurm 
Submitted batch job 2986
➜   

El trabajo genera dos archivos de salida que contienen el output estándar del programa en un archivo de extensión *.out y el output de error en caso de alguna falla, en el archivo de extensión *.err.

➜  ls
job.slurm  main.cu  Makefile  prog  TUT03-single-GPU-2986.err  TUT03-single-GPU-2986.out
➜  cat TUT03-single-GPU-2986.out 
/home/cnavarro/patagon-samples/01-custom-programs/TUT03-GPU-single
Mon 17 May 2021 09:58:39 PM -04
nvcc -O3 -arch=sm_80 -lnvidia-ml -Xcompiler -fopenmp main.cu -o prog
GPU MATMUL
size: 30720 x 30720
mode 1
Exploring GPUs
    Driver version: 450.102.04 
    NUM GPUS = 1
    Listing devices:
        GPU0 A100-SXM4-40GB, index=0, UUID=GPU-35a012ac-2b34-b68f-d922-24aa07af1be6  -> util = 0%
Choosing GPU 0
initializing A and B.......done
matmul shared mem..........done: time: 11.215649 secs
copying result to host.....done
verifying result...........done
➜  cat TUT03-single-GPU-2986.err
➜