Ejecucion de trabajos

Magerit se explota mediante trabajos batch usando SLURM como gestor y planificador de recursos. Para ejecutar se indican las características del trabajo que se necesita y el sistema se encargará de reservar los recursos necesarios y ejecutar las tareas.

Uso básico

El trabajo mínimo para ejecutar sería un fichero job.sh con el contenido:

#!/bin/bash

module purge && module load <app>

srun <app> --app-param app_args

El trabajo se envía al sistema ejecutando sbatch job.sh y ejecutaría 1 tarea (1 CPU) durante un máximo de 24 horas. Estos parámetros se pueden modificar mediante directivas de SLURM.

Aunque es posible especificar las directivas como argumentos de sbatch en el momento de enviar el trabajo, se recomienda definir las ejecuciones en ficheros para permitir su trazabilidad en caso de problemas.

Slurm proporciona una serie de mandatos que permiten controlar los trabajos en ejecución:

Mandato Descripción

sbatch

Envía un trabajo al sistema devolviendo su identificador.

squeue

Lista los trabajos encolados en el sistema (aquellos que están ejecutando o esperando para ejecutar).

scancel

Cancela un trabajo encolado. Si el trabajo ya estaba ejecutando se abortará la ejecución en ese momento.

sstat

Muestra estadísticas de uso de un trabajo que esté ejecutando en ese momento.

sacct

Muestra información de la ejecución de un trabajo ya finalizado.

Definición de trabajos

Las directivas de SLURM son comentarios que empiezan con #SBATCH seguido de las mismas opciones que se indicarían a sbatch.

#!/bin/bash
##----------------------- Start job description -----------------------
#SBATCH --partition=standard
#SBATCH --job-name=my_job
#SBATCH --ntasks=1
#SBATCH --mem-per-cpu=1G
#SBATCH --time=12:00:00
#SBATCH --mail-type=ALL
#SBATCH --mail-user=user@example.com
##------------------------ End job description ------------------------

module purge && module load <app>

srun <app> --app-param app_args

Es recomendable especificar únicamente las directivas mínimas que sean necesarias.

Las directivas deben ser lo primero que aparezca en el fichero antes de cualquier línea que se pueda ejecutar.

Aunque SLURM proporciona multitud de directivas para configurar un trabajo, existe un conjunto reducido de directivas comunes:

Opción Abreviado Descripción

--job-name=<name>

-J <name>

Nombre del trabajo. Es meramente informativo y sirve para facilitar la identificación.

--partition=<name>

-p <name>

Partición a utilizar.

--nodes=<#>

-N <#>

Número de nodos a utilizar.

--ntasks=<# tasks>

-n <#>

Número de tareas a ejecutar (paralelismo distribuido).

--ntasks-per-node=<#>

Número de tareas asignadas a cada nodo físico.

--time=[[DD-]HH:]MM:SS

-t [[DD-]HH:]MM:SS

Duración máxima de ejecución del trabajo.

--mem-per-cpu=<#>

Memoria RAM (MiB) requerida por CPU. Se recomienda indicar la unidad (1G).

--mem=<#>

Memoria RAM (MiB) requerida por nodo. Se recomienda indicar la unidad (1G).

Es preferible utilizar --mem-per-cpu en su lugar.

--output=out-%j.log

-o out-%j.log

Fichero para almacenar la salida estandar del job.

Se desaconseja su uso.

--error=err-%j.log

-e err-%j.log

Fichero para almacenar la salida de errores del job.

Se desaconseja su uso.

--mail-user=email

Dirección de correo para el envío de notificaciones de eventos

--mail-type=ALL

Enviar correos electrónicos al usuario en todos los eventos de trabajo.

--chdir=<dir_name>

Directorio de trabajo.

Se puede obtener información detallada de todas las directivas disponibles consultando la documentación de sbatch o ejecutando man sbatch en uno de los nodos interactivos.

Aunque se puede utilizar la versión abreviada, se recomienda utilizar la versión larga por claridad.

Por ejemplo, es muy fácil confundir las opciones -N (número de nodos) y -n (número de tareas) en las cabeceras de un trabajo.

Límites de ejecución

Para facilitar una utilización eficiente de los recursos se han establecido una serie de límites. Estos límites permiten cierta flexibilidad para acomodar la ejecución de múltiples trabajos.

El principal límite es el trabajo máximo que se puede enviar al sistema. En el caso de la partición standard se pueden solicitar 600 cores o lo que es equivalente 2400 GiB de memoria RAM durante 72 horas. Sin embargo, es posible incrementar la duración de un trabajo si se solicitan menos cores (Ej.: se puede reservar 300 cores y 1200 GiB de RAM, que equivaldría a la mitad, hasta 144 horas, el doble de tiempo) aunque nunca se puede superar las 160 horas.

Partition Recursos máximos (duración máxima) Duración máxima

standard

600 cores = 2400 GiB (72 horas)

160 horas

standard-gpu

8 GPUs (72 horas)

160 horas

debug

48 cores = 160 GiB (15 minutos)

1 hora

debug-gpu

4 GPUs (5 minutos)

20 minutos

Los límites establecen una cota máxima siendo válida cualquier combinación que los cumpla.

En el caso de las particiones con GPU se aplican adicionalmente los límites de CPU y RAM de su partición equivalente.

Es muy importante hacer un uso correcto de los recursos, solicitando únicamente los necesarios (con cierto margen de seguridad) para la ejecución de cada trabajo. Esto facilita la planificación y hace que el trabajo pueda entrar antes a ejecutar.

Redirección de salidas

Las aplicaciones suelen escribir datos o resultados por la consola. Al procesarme mediante un sistema de proceso por lotes, estas salidas se perderían al no existir una consola en el momento de ejecución. Para evitarlo SLURM redirige esas salidas a ficheros en disco dónde se pueden consultar.

Por defecto, SLURM almacena ambas salidas en el fichero slurm-<id_job>.out. Este fichero se crea automáticamente al iniciar la ejecución en el directorio del trabajo.

Salvo que sea necesario realizar algún tratamiento sobre las salidas, está desaconsejado modificar el valor por omisión.

Es posible redefinir el nombre y ubicación mediante las directivas --output=<outfile> y --error=<errfile>, pudiendo incluso separarlas en dos ficheros.

El directorio donde se almacenan las redirecciones debe existir y se debe poder crear los ficheros en su interior. Si no es así el trabajo abortará sin ningún tipo de indicación.

Dentro del nombre existen algunos tokens que se reemplazarán automáticamente por los datos del trabajo:

Token Descripción

\

Evita el proceso de los reemplazos

%%

Genera un único "%".

%A

Identificador del trabajo (si se usan arrays)

%a

Índice del trabajo (si se usan arrays).

%J

Identificador de trabajo y paso (equivale a %j.%s)

%j

Identificador del trabajo

%N

Nombre del nodo. Creará un fichero diferente para cada nodo que participe en la ejecución.

%n

Identificador del nodo. Creará un fichero diferente para cada nodo que participe en la ejecución.

%s

Identificador del paso (número de srun ejecutados)

%t

Identificador de la tarea (rank) dentro del trabajo. Creará un fichero diferente por cada tarea.

%u

Usuario

%x

Nombre del trabajo.

Por ejemplo:

  • Redirigir la salida y el error a dos ficheros diferentes:

    --output=out-%j.log
    --output=err-%j.log
  • Redirigir la salida y el error de cada paso y tarea a su propio fichero:

    --output=out-%J.%t.log
    --output=err-%J.%t.log

Notificaciones

Se puede configurar el envío automático de un correo cuando se produzcan modificaciones en el estado de un trabajo.

Para ello hay que añadir las opciones --mail-user indicando el correo al que se deben enviar las notificaciones y --mail-type con los eventos de interés separados por comas:

  • BEGIN

  • END

  • FAIL

  • REQUEUE

  • STAGE_OUT (el trabajo ha finalizado incluyendo las labores de limpieza.)

Se puede indicar ALL como una abreviatura de todos los eventos anteriores y NONE para ninguno

Adicionalmente se pueden solicitar avisos cuando se alcance cierto porcentaje el límite de tiempo especificado:

Evento Porcentaje

TIME_LIMIT

100%

TIME_LIMIT_90

90%

TIME_LIMIT_80

80%

TIME_LIMIT_50

50%

Configuraciones específicas

Gracias a la flexibilidad en la configuración, Magerit permite la ejecución de trabajos con multitud de esquemas que dependen de la capacidad del software y de lo que se desee hacer.

En estos ejemplos sólo se indican las directivas mínimas de interés, aunque se pueden añadir y combinar con cualquier otra directiva.

Trabajos paralelos con MPI

MPI (Message Passing Interface) es un mecanismo para la programación paralela por paso de mensajes que no necesita que todos los procesos ejecuten en el mismo nodo. La cantidad de estos procesos se maneja con la directiva --ntasks.

#!/bin/bash
##----------------------- Start job description -----------------------
#SBATCH --ntasks=<# tareas>
##------------------------ End job description ------------------------

module purge && module load <app>

srun <app> --app-param app_args

Trabajos secuenciales

Los trabajos secuenciales no utilizan paralelismo por lo que sólo tiene sentido solicitar una única tarea.

#!/bin/bash
##----------------------- Start job description -----------------------
#SBATCH --ntasks=1
##------------------------ End job description ------------------------

module purge && module load <app>

srun <app> --app-param app_args

En este esquema de ejecución el número de tareas --ntasks debe ser 1. Al ser trabajos secuenciales sólo se utiliza la primera tarea desperdiciándose el resto de los recursos.

Este tipo de trabajos suelen soportar OpenMP ya sea directamente en la aplicación o mediante el uso de alguna librería que lo haga. En estos casos, la ejecución puede mejorar si se solicitan más recursos.

#!/bin/bash
##----------------------- Start job description -----------------------
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=<# cpus>
##------------------------ End job description ------------------------

module purge && module load <app>
export OMP_NUM_THREADS="${SLURM_CPUS_PER_TASK}"

srun <app> --app-param app_args

Trabajos híbridos

Los trabajos híbridos MPI + OpenMP consisten en ejecutar varios procesos MPI (definidos por --ntasks) y, cada uno de ellos, desplegará varios hilos OpenMP (definidos por --cpus-per-task). A efectos contables, equivale a reservar ntasks × cpus-per-task CPUs.

#!/bin/bash
##----------------------- Start job description -----------------------
#SBATCH --ntasks=<# tasks>
#SBATCH --cpus_per_task=<# cpus>
##------------------------ End job description ------------------------

module purge && module load <app>
export OMP_NUM_THREADS="${SLURM_CPUS_PER_TASK}"

srun <app> --app-param app_args

Es necesario que la aplicación implemente estos dos paralelismos para obtener el rendimiento deseado.

Trabajos usando aceleradores (CUDA/GPU)

Los trabajos pueden solicitar el uso de aceleradores CUDA en los nodos que disponen de ellos.

Es necesario que la aplicación esté adaptada para hacer uso de CUDA o no se observará ningún tipo de mejora.

Las aplicaciones compiladas expresamente con soporte de aceleradores contendrán cuda en la versión. Otras aplicaciones puede que lo soporten aunque no lo lleven. Es necesario consultar los detalles de la aplicación específica.

#!/bin/bash
##----------------------- Start job description -----------------------
#SBATCH --ntasks=<# tasks>
#SBATCH --partition=standard-gpu
#SBATCH --gres=gpu:<type>:<# gpus>
##------------------------ End job description ------------------------

module purge && module load <app>

srun <app> --app-param app_args

El tipo puede ser a100 o v100.

Tipo Descripción

a100

Una tarjeta NVIDIA A100.

Se dispone de 4 nodos con 4 NVIDIA A100 cada uno de ellos.

v100

Una tarjeta NVIDIA V100.

Se dispone de 2 nodos con 2 NVIDIA V100 cada uno de ellos.

También es posible solicitar cualquier tipo si no existe ninguna preferencia, omitiendo esa parte en la directiva (--gres=gpu:<# gpus>) Se asignará la primera que esté disponible pudiendo incluso mezclarlas.

Si no se indica el tipo expresamente, el sistema puede asignar cualquiera de los modelos, incluso mezclados si se solicita más de una tarea. Dado que las prestaciones son diferentes, se recomienda no utilizar esta opción.

Puede ser útil si se necesita un único acelerador y no se quiere esperar por la disponibilidad de un modelo concreto.

#!/bin/bash
##----------------------- Start job description -----------------------
#SBATCH --ntasks=<# tasks>
#SBATCH --partition=standard-gpu
#SBATCH --gres=gpu:<# gpus>
##------------------------ End job description ------------------------

module purge && module load <app>

srun <app> --app-param app_args

Como ya se indicó, estas directivas se pueden combinar con cualquier otra para generar configuraciones.

Una de las configuraciones más habituales es incrementar el número de procesadores asignados a una tarea (directiva --cores-per-task) para acelerar la ejecución de la parte que no ejecuta en CUDA. También se puede combinar con OpenMP, tal y como se describe en trabajos secuenciales.

Múltiples programas

Aunque lo más común es ejecutar el mismo programa en todas las tareas, en ocasiones es necesario ejecutar diferentes operaciones (MPMD — Multiple Program Multiple Data). Un posible caso es cuando se quiere aglutinar en una simple ejecución diferentes procesos de corta duración para ejecutarlos en un único job.

Para simplificar estas ejecuciones existe la configuración multi-programa de SLURM, que se activa con la opción --multi-prog. Para ello hay que definir dos ficheros:

#!/bin/bash
##----------------------- Start job description -----------------------
#SBATCH --ntasks=<# tasks>
##------------------------ End job description ------------------------

module purge && module load <app>
export OMP_NUM_THREADS="${SLURM_CPUS_PER_TASK}"

srun --multi-prog job.list

La línea de srun simplemente indica el fichero donde se encuentran las ejecuciones que se desean hacer. Este fichero tiene dos campos:

Campo Descripción

Rank

Identificador de la tarea a ejecutar (número de rank de MPI). Empieza en cero.

Es posible indicar conjuntos (0,2,5), rangos (0-3) o cualquier combinación (0,2-3,5).

Programa

Programa que se desea ejecutar seguido de los argumentos que precise.

Se puede utilizar la expresión %t, que será sustituida por el número de tarea, y %o que será sustituido por el desplazamiento (el rank 1-5 tendrán desplazamientos 0-4).

4-6       cmd1
1,7       cmd2 task:%t
0,2-3     cmd3 offset:%o

El número de tareas (--ntasks) debe ser compatible con las indicadas en job.list.

Distintas compilaciones

En ocasiones Magerit tiene varias compilaciones de aplicaciones. Una de estas versiones estará directamente cargada y será la que utilicen todos los trabajos si no se indica lo contrario.

Los trabajos pueden elegir cuál de ellas utilizar mediante el uso de un module llamado apps. Para elegir una compilación diferente, basta con cargar la versión del module apps adecuada.

#!/bin/bash
##----------------------- Start job description -----------------------
# [...]
##------------------------ End job description ------------------------

module purge && module load apps/<year> && module load <app>

srun <app> --app-param app_args

Para ver qué modules están disponibles es posible cargarlo el module apps en una sesión interactiva y listar los modules disponibles.

CeSViMaCentro de Supercomputación y Visualización de Madrid