Jean Zay : Collection de travaux similaires (Job Array)

Principe

Les Jobs Arrays offrent la possibilité à un utilisateur de soumettre en une seule fois une collection de travaux similaires.

Attention : à l'IDRIS, les Jobs Arrays ne sont admis que pour des travaux soumis via la commande sbatch car ils peuvent générer un très grand nombre de travaux.

Ils se distinguent par l'utilisation de la directive SLURM #SBATCH --array permettant de spécifier les indices, ou un rang d'indices, pour les travaux de la collection comme indiqué ci-dessous :

  • Pour une collection de travaux dont les indices vont successivement de 0 à NB_JOBS (i.e. 0, 1, 2, … , NB_JOBS) :
    #SBATCH --array=0-NB_JOBS
  • Pour une collection de travaux dont les indices varient de 0 à, au plus, NB_JOBS par pas de STEP_SIZE (i.e. 0, STEP_SIZE, 2*STEP_SIZE, etc…) :
    #SBATCH --array=0-NB_JOBS:STEP_SIZE
  • Pour une collection de N travaux ayant les indices prédéfinis J_1, J_2, … , J_N :
    #SBATCH --array=J_1,J_2,...,J_N

Remarque : afin de ne pas saturer la file d'attente des travaux SLURM, le nombre maximal de travaux exécutés simultanément à partir d'un Job Array peut être spécifié à l'aide du séparateur % :

  • Pour exécuter tous les travaux d'une collection par séries de NB_MAX_RUNNING_JOBS travaux, utilisez la syntaxe suivante :
    #SBATCH --array=0-NB_JOBS%NB_MAX_RUNNING_JOBS

Variables propres aux Job Arrays

Lors de l'utilisation des Job Arrays, certaines variables d'environnement SLURM peuvent être utilisées dans le script shell afin de personnaliser les divers travaux d'une même collection. Par exemple, pour que chaque travail de la collection utilise des répertoires d'entrées et/ou de sorties différents. Les variables d’environnement suivantes sont ainsi automatiquement valorisées par SLURM :

  • SLURM_JOB_ID : l'identifiant du job
  • SLURM_ARRAY_JOB_ID : aussi l'identifiant du job
  • SLURM_ARRAY_TASK_ID : l'indice propre à chaque travail de la collection (peut être vue comme un compteur des travaux)
  • SLURM_ARRAY_TASK_COUNT : le nombre total de travaux de la collection qui seront exécutés.
  • SLURM_ARRAY_TASK_MIN : le plus petit indice de tous les travaux de la collection
  • SLURM_ARRAY_TASK_MAX : le plus grand indice de tous les travaux de la collection

De plus, avec les Job Arrays, deux options supplémentaires sont disponibles pour spécifier les noms des fichiers d'entrée et de sortie de chaque travail dans les directives #SBATCH --output=... et #SBATCH --error=... :

  • %A qui est automatiquement substituée par la valeur de SLURM_ARRAY_JOB_ID
  • %a qui est automatiquement substituée par la valeur de SLURM_ARRAY_TASK_ID.

Remarques :

  • Par défaut, le format du nom de fichier de sortie pour un Job Array est slurm-%A_%a.out.
  • En Bash, les variables propres aux Job Arrays peuvent être récupérées de la manière suivante :
    echo ${SLURM_JOB_ID}
    echo ${SLURM_ARRAY_JOB_ID}
    echo ${SLURM_ARRAY_TASK_ID}
    echo ${SLURM_ARRAY_TASK_COUNT}
    echo ${SLURM_ARRAY_TASK_MIN}
    echo ${SLURM_ARRAY_TASK_MAX}
  • Pour les scripts Python, les variables propres aux Job Arrays peuvent être récupérées de la manière suivante :
    import os
    slurm_job_id=int(os.environ["SLURM_JOB_ID"])
    slurm_array_job_id=int(os.environ["SLURM_ARRAY_JOB_ID"])
    slurm_array_task_id=int(os.environ["SLURM_ARRAY_TASK_ID"])
    slurm_array_task_count=int(os.environ["SLURM_ARRAY_TASK_COUNT"])
    slurm_array_task_min=int(os.environ["SLURM_ARRAY_TASK_MIN"])
    slurm_array_task_max=int(os.environ["SLURM_ARRAY_TASK_MAX"])

Exemples d’utilisation

  • Remarque préliminaire : les exemples ci-dessous concernent des exécutions sur la partition CPU. Le principe reste le même pour des exécutions sur les partitions GPU.
  • Exemple de script de soumission pour 20 travaux identiques avec un maximum de 5 travaux placés dans la file (exécution par séries de 5 travaux) :
    job_array_20.slurm
    #!/bin/bash
    #SBATCH --job-name=job-array   # nom du job
    #SBATCH --ntasks=1             # Nombre total de processus MPI
    #SBATCH --ntasks-per-node=1    # Nombre de processus MPI par noeud
    # Dans le vocabulaire Slurm "multithread" fait référence à l'hyperthreading.
    #SBATCH --hint=nomultithread   # 1 processus MPI par coeur physique (pas d'hyperthreading)
    #SBATCH --time=00:01:00        # Temps d’exécution maximum demande (HH:MM:SS)
    #SBATCH --output=%x_%A_%a.out  # Nom du fichier de sortie contenant l'ID et l'indice
    #SBATCH --error=%x_%A_%a.out   # Nom du fichier d'erreur (ici commun avec la sortie)
    #SBATCH --array=0-19%5         # 20 travaux en tout mais 5 travaux max dans la file
     
    # on se place dans le répertoire de soumission
    cd ${SLURM_SUBMIT_DIR}
     
    # nettoyage des modules charges en interactif et herites par defaut
    module purge
     
    # chargement des modules
    module load ...
     
    # echo des commandes lancées
    set -x
     
    # Execution du binaire "mon_exe" avec des donnees differentes pour chaque travail
    # La valeur de ${SLURM_ARRAY_TASK_ID} est differente pour chaque travail.
    srun ./mon_exe < fichier${SLURM_ARRAY_TASK_ID}.in > fichier${SLURM_ARRAY_TASK_ID}.out
  • Exemple de script de soumission pour 3 travaux identiques, ayant respectivement les indices 1, 3 et 8 :
    job_array_3.slurm
    #!/bin/bash
    #SBATCH --job-name=job-array   # nom du job
    #SBATCH --ntasks=1             # Nombre total de processus MPI
    #SBATCH --ntasks-per-node=1    # Nombre de processus MPI par noeud
    # Dans le vocabulaire Slurm "multithread" fait référence à l'hyperthreading.
    #SBATCH --hint=nomultithread   # 1 processus MPI par coeur physique (pas d'hyperthreading)
    #SBATCH --time=00:01:00        # Temps d’exécution maximum demande (HH:MM:SS)
    #SBATCH --output=%x_%A_%a.out  # Nom du fichier de sortie contenant l'ID et l'indice
    #SBATCH --error=%x_%A_%a.out   # Nom du fichier d'erreur (ici commun avec la sortie)
    #SBATCH --array=1,3,8          # 3 travaux en tout ayant les indices 1, 3 et 8
     
    # on se place dans le répertoire de soumission
    cd ${SLURM_SUBMIT_DIR}
     
    # nettoyage des modules charges en interactif et herites par defaut
    module purge
     
    # chargement des modules
    module load ...
     
    # echo des commandes lancées
    set -x
     
    # Execution du binaire "mon_exe" avec des donnees differentes pour chaque travail
    # La valeur de ${SLURM_ARRAY_TASK_ID} est differente pour chaque travail.
    srun ./mon_exe < fichier${SLURM_ARRAY_TASK_ID}.in > fichier${SLURM_ARRAY_TASK_ID}.out
  • Exemple de script de soumission pour 6 travaux identiques, ayant des indices compris entre 0 et 11 par pas de 2 :
    job_array_0-11.slurm
    #!/bin/bash
    #SBATCH --job-name=job-array   # nom du job
    #SBATCH --ntasks=1             # Nombre total de processus MPI
    #SBATCH --ntasks-per-node=1    # Nombre de processus MPI par noeud
    # Dans le vocabulaire Slurm "multithread" fait référence à l'hyperthreading.
    #SBATCH --hint=nomultithread   # 1 processus MPI par coeur physique (pas d'hyperthreading)
    #SBATCH --time=00:01:00        # Temps d’exécution maximum demande (HH:MM:SS)
    #SBATCH --output=%x_%A_%a.out  # Nom du fichier de sortie contenant l'ID et l'indice
    #SBATCH --error=%x_%A_%a.out   # Nom du fichier d'erreur (ici commun avec la sortie)
    #SBATCH --array=0-11:2         # 6 travaux ayant les indices 0, 2, 4, 6, 8, et 10
     
    # on se place dans le répertoire de soumission
    cd ${SLURM_SUBMIT_DIR}
     
    # nettoyage des modules charges en interactif et herites par defaut
    module purge
     
    # chargement des modules
    module load ...
     
    # echo des commandes lancées
    set -x
     
    # Execution du binaire "mon_exe" avec des donnees differentes pour chaque travail
    # La valeur de ${SLURM_ARRAY_TASK_ID} est differente pour chaque travail.
    srun ./mon_exe < fichier${SLURM_ARRAY_TASK_ID}.in > fichier${SLURM_ARRAY_TASK_ID}.out

Commande de contrôle des travaux

Un travail de type Job Array doit être exécuté via la commande sbatch en raison du grand nombre de travaux qu'il peut générer :

$ sbatch job_array.slurm

Le suivi de ces jobs s'effectue avec la commande squeue qui retourne alors une information adaptée. Par exemple, pour un Job Array comportant 7 travaux exécutés par séries de 2 travaux sur la partition cpu_p1 :

  • Le premier appel à squeue retourne :
    $ squeue -J 305813
                 JOBID PARTITION      NAME     USER ST       TIME  NODES NODELIST(REASON)
        305813_[2-6%2]    cpu_p1 job-array  mylogin PD       0:00      1 (JobArrayTaskLimit)
              305813_0    cpu_p1 job-array  mylogin  R       0:00      1 r7i1n0
              305813_1    cpu_p1 job-array  mylogin  R       0:00      1 r8i6n3

    Ici, on constate que les 2 premiers travaux sont en cours d'exécution et les 5 autres en attente.

  • Lorsque les 2 premiers travaux sont finis, un second appel à squeue retourne :
    $ squeue -J 305813
                 JOBID PARTITION      NAME     USER ST       TIME  NODES NODELIST(REASON)
        305813_[4-6%2]    cpu_p1 job-array  mylogin PD       0:00      1 (JobArrayTaskLimit)
              305813_2    cpu_p1 job-array  mylogin  R       0:05      1 r7i1n0
              305813_3    cpu_p1 job-array  mylogin  R       0:05      1 r8i6n3

    Maintenant, on constate que ce sont les 2 travaux suivants qui s'exécutent et qu'il n'en reste plus que 3 en attente. Notez qu'il n'y a plus aucune trace des 2 premiers travaux terminés.

Pour supprimer un Job Array, vous devez utiliser la commande scancel. Mais il y a alors plusieurs façons de procéder :

  • Pour annuler l'ensemble de la collection, indiquez son identifiant ${SLURM_JOB_ID}. Avec l'exemple ci-dessus, cela donne :
    $ scancel 305813
  • Pour annuler l'exécution d'un travail en particulier, indiquez l'identifiant de la collection ${SLURM_ARRAY_JOB_ID} et l'indice du travail ${SLURM_ARRAY_TASK_ID}. Avec l'exemple ci-dessus, cela donne :
    $ scancel 305813_2
  • Pour annuler l'exécution d'une série de travaux, indiquez l'identifiant de la collection ${SLURM_ARRAY_JOB_ID} et un intervalle d'indices (ici de 4 à 6). Avec l'exemple ci-dessus, cela donne :
    $ scancel 305813_[4-6]

Documentation