Annex XIV: Hybrid MPI/OpenMP programs execution in batch

SLURM está configurado para el uso del plugin task/cgroup con la siguiente configuración:

/etc/slurm/slurm.conf:

SelectTypeParameters=CR_Core_Memory,CR_CORE_DEFAULT_DIST_BLOCK,CR_Pack_Nodes

***https://slurm.schedmd.com/archive/slurm-17.02.11/slurm.conf.html***

CR_Core_Memory

Cores and memory are consumable resources. On nodes with hyper-threads, each thread is counted as a CPU to satisfy a job’s resource requirement, but multiple jobs are not allocated threads on the same core. The count of CPUs allocated to a job may be rounded up to account for every CPU on an allocated core. Setting a value for DefMemPerCPU is strongly recommended.

CR_CORE_DEFAULT_DIST_BLOCK

Allocate cores within a node using block distribution by default. This is a pseudo-best-fit algorithm that minimizes the number of boards and minimizes the number of sockets (within minimum boards) used for the allocation. This default behavior can be overridden specifying a particular “-m” parameter with srun/salloc/sbatch. Without this option, cores will be allocated cyclicly across the sockets.

CR_Pack_Nodes

If a job allocation contains more resources than will be used for launching tasks (e.g. if whole nodes are allocated to a job), then rather than distributing a job’s tasks evenly across it’s allocated nodes, pack them as tightly as possible on these nodes. For example, consider a job allocation containing two entire nodes with eight CPUs each. If the job starts ten tasks across those two nodes without this option, it will start five tasks on each of the two nodes. With this option, eight tasks will be started on the first node and two tasks on the second node.

TaskPlugin=task/cgroup,task/affinity

TaskPlugin

Identifies the type of task launch plugin, typically used to provide resource management within a node (e.g. pinning tasks to specific processors). More than one task plugin can be specified in a comma separated list. The prefix of “task/” is optional. Acceptable values include:

task/affinity

enables resource containment using CPUSETs. This enables the –cpu_bind and/or –mem_bind srun options. If you use “task/affinity” and encounter problems, it may be due to the variety of system calls used to implement task affinity on different operating systems.

task/cgroup

enables resource containment using Linux control cgroups. This enables the –cpu_bind and/or –mem_bind srun options. NOTE: see “man cgroup.conf” for configuration details.

NOTE: It is recommended to stack task/affinity,task/cgroup together when configuring TaskPlugin, and setting TaskAffinity=no and ConstrainCores=yes in cgroup.conf. This setup uses the task/affinity plugin for setting the affinity of the tasks (which is better and different than task/cgroup) and uses the task/cgroup plugin to fence tasks into the specified resources, thus combining the best of both pieces.

/etc/slurm/cgroup.conf:

ConstrainCores=yes

ConstrainRAMSpace=yes

TaskAffinity=no

***https://slurm.schedmd.com/archive/slurm-17.02.11/cgroup.conf.html***

ConstrainCores=<yes|no>

** **If configured to “yes” then constrain allowed cores to the subset of allocated resources. This functionality makes use of the cpuset subsystem. Due to a bug fixed in version 1.11.5 of HWLOC, the task/affinity plugin may be required in addition to task/cgroup for this to function properly. The default value is “no”.

ConstrainRAMSpace=<yes|no>

If configured to “yes” then constrain the job’s RAM usage by setting the memory soft limit to the allocated memory and the hard limit to the allocated memory * AllowedRAMSpace. The default value is “no”, in which case the job’s RAM limit will be set to its swap space limit if ConstrainSwapSpace is set to “yes”. Also see AllowedSwapSpace, AllowedRAMSpace and ConstrainSwapSpace. NOTE: When enabled, ConstrainRAMSpace can lead to a noticiable decline in per-node job throughout. Sites with high-throughput requirements should carefully weigh the tradeoff between per-node throughput, versus potential problems that can arise from unconstrained memory usage on the node. See <*https://slurm.schedmd.com/high_throughput.html*> for further discussion.

TaskAffinity=<yes|no>

If configured to “yes” then set a default task affinity to bind each step task to a subset of the allocated cores using sched_setaffinity. The default value is “no”. Note: This feature requires the Portable Hardware Locality (hwloc) library to be installed.

Esta configuración produce que en la ejecución de cada trabajo en un nodo esté, entre otros, bajo los siguientes cgroups:

/sys/fs/cgroup/cpuset/slurm/id_<userid>/job_<jobid>

/sys/fs/cgroup/memory/slurm/id_<userid>/job_<jobid>

Los valores de estos cgroups son definidos acorde a la reserva de recursos que se haga. En este sentido cabe distinguir entre particiones de nodos exclusivos y compartidos.

Particiones exclusivas (thinnodes)

En este caso, un nodo es exclusivo para un único trabajo y cada step (ejecución de srun dentro del trabajo aparte de los iniciales) involucra un único cgroup bajo las jerarquía de memoria y cpusets globales del trabajo:

/step_batch : Ejecución en la reserva de comando sin srun en el script del trabajo

/step_extern : Ejecución en la reserva del prolog

/step_0 : Ejecución de los comandos bajo el primer srun

/step_1: Ejecución de los comandos bajo el segundo srun

Bajo esta configuración la ejecución de un programa híbrido MPI/OpenMP tiene estas posibilidades:

  1. Especificación de tareas, tareas por nodo y cores por tarea a usar especificando el binding adecuado par cada tarea dentro del nodo: -n 4 –ntasks-per-node=2 ** **-m cyclic -c 8 –cpu_bind=mask_cpu:ff,ff0000 ** La reserva derivada de la petición -n 4 –ntasks-per-node=2 **contendrá 2 nodos donde se ejecutarán 4 tareas. El reparto de las tareas en la ejecución viene definido por la opción -m cyclic donde se reparte una tarea por nodo de forma cíclica. En este caso, como cada tarea se ejecutará en 8 cores cada una es necesario definir en qué cores se ejecutarán (opción ** **–cpu_bind). La primera tarea en los cores 0,1,2,3,4,5,6,7 (máscara: ff) y la segunda en los cores 16,17,18,19,20,21,22,23 (máscara: ff0000). Consultar *https://vis.cesga.es/mask_affinity/mask_affinity.html* para ayuda para la definición de las correspondientes máscaras. Si no se definiesen los cores a usar todos estarían disponibles y sería responsabilidad del usuario definir el nº de hilos de memoria compartida a usar (librerías paralelas instaladas en el sistema para su uso mediante la carga del módulo correspondiente fijan automáticamente los hilos a usar acorde al valor de la opción -c).

  2. Especificación de tareas y tareas por nodo a usar desactivando el “binding” de tareas a cores, por ejemplo: -n 4 –ntasks-per-node=2 –cpu_bind=none ** La reserva contendrá 2 nodos donde se ejecutarán 4 tareas. El reparto de las tareas, al no definirse recursos de cada tarea, viene definido por la opción –ntasks-per-node siendo en este ejemplo 2 tareas por nodo. En este caso es **responsabilidad del usuario definir el nº de hilos de memoria compartida a usar. Por defecto se usarán en este caso 24 por tarea, 24 cores por nodo ya que el cgroup configurado corresponde a: /step_?/cpuset.cpus: 0-23 /step_?/memory.limit_in_bytes: 128849018880

Si se omite la opción “–cpu_bind=none” en este caso entra en juego la opción de TaskAffinity que asigna cada tarea con 12 cores siendo por defecto los 12 primeros del nodo para todas la tareas.

Esta opción no asigna la ejecución de cada tarea/hilos a cores determinados por lo que estos podrán migrar entre los distintos cores según las políticas establecidas por el sistema operativo.

Particiones compartidas (shared)

En las particiones compartidas, un nodo puede ser compartido por diferentes trabajos. Esto impide la posibilidad de selección de cores específicos dentro de cada nodo. Estas particiones no tienen una red de interconexión entre nodos de baja latencia con que no es aconsejable usar más de un nodo para ejecuciones MPI. Por estos motivos para la ejecución de este tipo de programas se recomienda el uso de las siguientes opciones reflejadas en este ejemplo:

-n 4 -c 4 –ntasks-per-node=4

En este caso se solicita una reserva de 16 cores (4 tareas de 4 cores cada una) las cuales se van a ejecutar exclusivamente en un nodo (4 tareas por nodo). Que cores se emplearán para la ejecución será una decisión del “scheduler” de SLURM según los recursos disponibles. Estos cores se definirán en el correspondiente cgroup, por ejemplo:

/step_0/cpuset.cpus: 12-15,24,27,34-43

Ejemplo:

En /opt/cesga/job-scripts-examples hay un ejemplo en fortran y C de un programa simple MPIOpenMP:

hybrid.f90

hybrid.c

El script compileMPIOpenMP.sh contiene las ordenes para su compilación y los scripts:

MPIOpenMP_Job_on_exclusive_nodes.sh

MPIOpenMP_Job_on_shared_nodes.sh

Son ejemplos de ejecución explorando las diferentes posibilidades.