wiki:HTCondor

Version 5 (modified by lambert, 9 months ago) ( diff )

--

Utilisation du système de batch HT-Condor

Machines pour la soumission de tâches

Quatre machines de soumission sont à votre disposition pour soumettre vos tâches avec le système de batch HTCondor:

  • lpsc-batch-almalinux et lpsc-batch-almalinux2 (AlmaLinux release 9.2)
  • lpsc-batch-fedora (Fedora 39)
  • lpsc-batch-centos7 (CentOS Linux release 7.4)

Prérequis pour soumettre une tâche

Pour soumettre une tâche sur HTCondor il faut :

  • un *exécutable* qui pourra dans un premier temps être testé en interactif sur les machines de soumission
  • un *fichier de description* qui sera lu par le système HTCondor et qui définira les ressources et le type d'environnement dont a besoin votre exécutable.

Commandes utiles

  • [xxx@lpsc-cyy] condor_submit <description.submit> : soumet une tâche décrite par un fichier <description.submit>
  • [xxx@lpsc-cyy] condor_q (-long)? (-run)? : Voir l'avancement de vos tâches soumises
  • [xxx@lpsc-cyy] condor_rm -all : Supprime toutes vos tâches de la file d'attente
  • [xxx@lpsc-cyy] condor_status -avail : Voir toutes les machines disponibles dans le batch
  • [xxx@lpsc-cyy] condor_status -avail : Voir toutes les machines disponibles dans le batch
  • [xxx@lpsc-cyy] condor_status (-long)? (<machine>)? : Voir tous les attributs "ClassAd" de l'ensemble des machines ou d'une machine donnée. (voir documentation). Cela permets de sélectionner les machines avec les ressources nécessaires pour exécuter une tâche en ajoutant une ligne "+Requirements" dans un fichier de soumission (voir exemples ci-dessous)

Liens utiles

Le concept d'univers

Un univers pour HTCondor est un environnement d’exécution pour une tâche. HTCondor définit plusieurs univers, plusieurs d'entre-eux ont été configurés sur la ferme de calcul du LPSC :

  • vanilla, l'univers "par défaut" avec lequel vous pouvez soumettre la plupart de vos tâches
  • parallel, l'univers qui prend en charge les tâches qui s'étendent sur plusieurs machines, où les multiples processus d'une tâche s'exécutent simultanément, éventuellement en communiquant entre eux.
  • docker, l'univers qui permet d'utiliser des containers docker depuis DockerHub pour faire s’exécuter vos tâches
  • java, l'univers pour les tâches à base d'exécutables Java

D'autres univers existent et pourront être proposés au besoin.

Univers vanilla

Python

test_python.submit

  # Définition de l'univers
  universe = vanilla

  # Chemin vers l'exécutable
  executable = ./test_python.py

  # Fichiers à produire en sortie
  output = test_python.out
  log = test_python.log
  error = test_python.err
  
  # directive pour le transfert des fichiers de sortie depuis le nœud d’exécution
  should_transfer_files = YES
  when_to_transfer_output = ON_EXIT
  
  queue

test_python.py

#!/usr/bin/env python3

import platform
from datetime import datetime

def afficher_informations_systeme():
    # Obtenir le nom de la machine
    nom_machine = platform.node()

    # Obtenir la date et l'heure actuelles
    date_heure_actuelles = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    # Afficher les informations
    print(f"Nom de la machine: {nom_machine}")
    print(f"Date et heure actuelles: {date_heure_actuelles}")

# Appeler la fonction pour afficher les informations
afficher_informations_systeme()

Shell

nombresPremiers.submit

  universe = vanilla

  executable = nombresPremiers.sh

  # Arguments à passer à l'executable
  arguments = 100

  # Fichier de sortie à rappatrier depuis les nœuds d'execution
  output=results.output.$(Process)
  error=results.error.$(Process)
  log=results.log

  should_transfer_files=YES
  when_to_transfer_output = ON_EXIT

  queue

nombresPremiers.sh

#!/bin/sh

limit=${1}
echo "Les nombres premiers entre 1 et ${limit} sont :"

# Utilisation du crible d'Ératosthène pour trouver les nombres premiers jusqu'à "limit"
sieve=( $(seq 2 $limit) )
count=0

for ((i=2; i*i<=limit; i++))
do
    if [ ${sieve[$i-2]} -ne 0 ]
    then
        for ((j=i*i; j<=limit; j+=i))
        do
            sieve[$j-2]=0
        done
    fi
done

# Affichage des nombres premiers restants
for num in "${sieve[@]}"
do
    if [ $num -ne 0 ]
    then
        echo -n "$num "
        count=$((count + 1))

        if [ $count -eq 50 ]
        then
            echo
            count=0
        fi
    fi
done

echo  # Saut de ligne final si nécessaire

MPI avec OpenMPI

Dans l'univers vanilla, les tâches MPI ne tourne que sur une seule machine en utilisant tous les cœurs et processeurs demandés dans la limite des capacités de la machine hôte.

run.sh

run.sh est un script shell pour faire le setup de l'environnement.

#!/bin/bash

export LD_LIBRARY_PATH=/usr/lib64/openmpi/lib/:${LD_LIBRARY_PATH}
export PATH=/usr/lib64/openmpi/bin/:${PATH}

mpirun $*

L'executable est compilé au préalable avec : /usr/lib64/mpi/bin/mpicc simple.c -o simple

simple.c

#include <stdio.h>
#include <mpi.h>

int main(int argc, char** argv) {
    int rank, size;

    // Initialisation de l'environnement MPI
    MPI_Init(&argc, &argv);

    // Obtention du rang du processus
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    // Obtention de la taille du communicateur
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    // Affichage du rang et de la taille
    printf("Hello from process %d of %d\n", rank, size);

    // Finalisation de l'environnement MPI
    MPI_Finalize();

    return 0;
}

simple.submit

universe              = vanilla
executable            = run.sh
arguments             = -np 8 simple

output                = simple.out
error                 = simple.err
log                   = simple.log

should_transfer_files = yes
when_to_transfer_output = on_exit
transfer_input_files = simple

# Spécifiez le nombre de slots (processus MPI) requis
request_cpus = 1
request_memory = 1024M
request_disk   = 10240K

#+Requirements           = OpSysAndVer =?= "AlmaLinux"
#+Requirements           = machine     =?= "lpsc-c21.in2p3.fr"
+Requirements            = member(machine, {"lpsc-c22.in2p3.fr", "lpsc-c23.in2p3.fr"})

queue

MPI avec MPICH

Dans l'univers vanilla, les tâches MPI ne tourne que sur une seule machine en utilisant tous les cœurs et processeurs demandés dans la limite des capacités de la machine hôte.

run.sh

run.sh est un script shell pour faire le setup de l'environnement.

#!/bin/bash

export LD_LIBRARY_PATH=/usr/lib64/mpich/lib/:${LD_LIBRARY_PATH}
export PATH=/usr/lib64/mpich/bin/:${PATH}

mpirun $*


L'executable est compilé au préalable avec : /usr/lib64/mpich/bin/mpicc simple.c -o simple

simple.c

#include <stdio.h>
#include <mpi.h>

int main(int argc, char** argv) {
    int rank, size;

    // Initialisation de l'environnement MPI
    MPI_Init(&argc, &argv);

    // Obtention du rang du processus
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    // Obtention de la taille du communicateur
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    // Affichage du rang et de la taille
    printf("Hello from process %d of %d\n", rank, size);

    // Finalisation de l'environnement MPI
    MPI_Finalize();

    return 0;
}

simple.submit

universe              = vanilla
executable            = run.sh

# Attention au chemin relatif pour l'executable avec MPICH: ./simple et pas simple
arguments             = -np 8 ./simple

output                = simple.out
error                 = simple.err
log                   = simple.log

should_transfer_files = yes
when_to_transfer_output = on_exit
transfer_input_files = simple

# Spécifiez le nombre de slots (processus MPI) requis
request_cpus = 1
request_memory = 1024M
request_disk   = 10240K

#+Requirements           = OpSysAndVer =?= "AlmaLinux"
#+Requirements           = machine     =?= "lpsc-c21.in2p3.fr"
+Requirements            = member(machine, {"lpsc-c22.in2p3.fr", "lpsc-c23.in2p3.fr"})

queue

Univers parallel

Shell

parallel.submit

#!/bin/sh

echo "Hello I am ${HOSTNAME} and I am node ${1}"

parallel.submit

universe                 = parallel
executable               = parallel.sh
arguments                = $(Node)

machine_count            = 2

output                   = parallel.$(Node).out
error                    = parallel.$(Node).err
# Il n'est pas possible de faire un fichier de log par nœuds
log                      = parallel.log

should_transfer_files    = yes
when_to_transfer_output  = on_exit

+Requirements             = member(machine,  {"lpsc-c21.in2p3.fr","lpsc-c22.in2p3.fr"})

queue

MPI avec OpenMPI

Pour soumettre une tâche avec OpenMPI dans l'univers parallel, vous pouvez utiliser un script fourni par HTCondor et qui se trouve sur les machines de soumission sous : /usr/share/doc/condor/examples/openmpiscript.

Dans l'exemple qui suit, on execute un fichier example préalablement compilé à partir d'un fichier source example.c : /usr/lib64/openmpi/bin/mpicc example.c -o example

example.c

#include <stdio.h>
#include <mpi.h>

int main(int argc, char **argv) {
    int rank, size;
    int local_array[5] = {1, 2, 3, 4, 5};
    int global_sum[5] = {0, 0, 0, 0, 0};

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    // Chaque processus ajoute son tableau local à global_sum

    MPI_Reduce(local_array, global_sum, 5, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);

    // Seul le processus de rang 0 affiche le résultat
    if (rank == 0) {
        printf("Résultat de la somme globale : ");
        for (int i = 0; i < 5; i++) {
            printf("%d ", global_sum[i]);
        }
        printf("\n");
    }

    MPI_Finalize();
    return 0;
}

example.submit

universe                 = parallel

executable               = /usr/share/doc/condor/examples/openmpiscript
arguments                = example

machine_count            = 2

output                   = example.out
error                    = example.err
log                      = example.log

should_transfer_files    = yes
when_to_transfer_output  = on_exit
transfer_input_files     = example
want_io_proxy            = True

request_cpus             = 8
#request_memory           = 1024M
#request_disk             = 10240K

#+Requirements            = OpSysAndVer =?= "AlmaLinux"
+Requirements             = member(machine,  {"lpsc-c21.in2p3.fr","lpsc-c22.in2p3.fr"})

queue

MPI avec MPICH

Non disponible dans l'univers parallèle...

Univers java

Les programmes Java peuvent être exécutés après avoir été préalablement "compilés" avec un JDK (Java Devlopment Kit) normalement présent sur les machines de soumission:

  • génération d'un fichier .class
    javac !MachineDate.java
    
  • génération d'une archive exécutable .jar (optionnel)
    jar cf !MachineDate.jar ./!MachineDate.class
    

MachineDate.java

import java.net.InetAddress;
import java.util.Date;

public class MachineDate {
    public static void main(String[] args) {
        try {
            InetAddress localMachine = InetAddress.getLocalHost();
            System.out.println("Machine: " + localMachine.getHostName());
            System.out.println("Date: " + new Date());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Option 1 : MachineDate.class

universe = java

executable              = MachineDate.class
arguments               = MachineDate 
transfer_input_files    = ${PWD}/MachineDate.class

log                     = MachineDateClass.log
output                  = MachineDateClass.out
error                   = MachineDateClass.err

should_transfer_files   = YES
when_to_transfer_output = ON_EXIT

request_cpus            = 1
request_memory          = 1024M
request_disk            = 10240K

queue

Option 2 : MachineDate.jar

universe = java

executable              = MachineDate.class
jar_files               = MachineDate.jar
arguments               = MachineDate 

log                     = MachineDateJar.log
output                  = MachineDateJar.out
error                   = MachineDateJar.err

transfer_input_files = ${PWD}/MachineDate.jar
should_transfer_files   = YES
when_to_transfer_output = ON_EXIT

queue

Univers docker

Docker est déployé sur les machines de la ferme locale. Il est possible d'utiliser n'importe quelle image pour définir un environnement d’exécution pour vos tâches. Les images docker peuvent être trouvées sur le site DockerHub

Exécuter "cat" sur Debian

docker.submit

  #universe = docker is optional
  universe                = docker
  
  # nom de l'image sur dockerHub
  docker_image            = debian

  # programme à exécuter dans l'environnement debian avec ses arguments
  executable              = /bin/cat
  arguments               = /etc/hosts

  # fichiers de sortie à rapatrier depuis le nœud d’exécution de la ferme locale
  should_transfer_files   = YES
  when_to_transfer_output = ON_EXIT
  output                  = out.$(Process)
  error                   = err.$(Process)
  log                     = log.$(Process)

  # pré-requis pour sélection d'un nœud de la ferme locale où faire tourner la tâche
  request_cpus   = 1
  request_memory = 1024M
  request_disk   = 10240K

  queue 1

Génération d'un histogramme avec ROOT

ROOT est un programme d'analyse qui n'est pas forcément installé ou configuré sur les machines de la ferme locale. Il est cependant possible de l'utiliser via une image docker.

Candle Histo.C : le fichier root à exécuter

#include <TROOT.h>
#include <TCanvas.h>
#include <TH2I.h>
#include <TRandom.h>

void candlehisto()
{
    // Définissez la taille du canevas avec une résolution plus élevée
    TCanvas *c1 = new TCanvas("c1", "Candle Presets", 1920, 1080);
    c1->Divide(3, 2);

    TRandom *rng = new TRandom();
    TH2I *h1 = new TH2I("h1", "Sin", 18, 0, 360, 100, -1.5, 1.5);
    h1->GetXaxis()->SetTitle("Deg");

    float myRand;
    for (int i = 0; i < 360; i += 10)
    {
        for (int j = 0; j < 100; j++)
        {
            myRand = rng->Gaus(sin(i * 3.14 / 180), 0.2);
            h1->Fill(i, myRand);
        }
    }

    c1->cd(1);
    for (int i = 1; i < 7; i++)
    {
        c1->cd(i);
        TString title = TString::Format("CANDLEX%d", i);
        TH2I *myhist = (TH2I *)h1->DrawCopy(title);
        myhist->SetTitle(title);
    }

    // Sauvegardez dans un seul fichier image dans le répertoire courant
    TString imgFileName = "output.png";
    c1->SaveAs(imgFileName);
}

int main()
{
    candlehisto();
    return 0;
}

candlehisto.submit : le fichier de soumission

  #universe = docker is optional
  universe                = docker
  
  # nom de l'image sur dockerHub
  docker_image            = rootproject/root

  # programme à exécuter dans l'environnement ROOT avec ses arguments
  executable              = /opt/root/bin/root.exe
  arguments               = -b -q -l candlehisto.C

  # Fichier d'input à transférer sur le nœud où va tourner la tâche
  transfer_input_files = candlehisto.C

  # Fichier de résultat à transférer depuis le nœud ou a tourner la tâche en fin d’exécution
  transfer_output_files   = output.png

  should_transfer_files   = YES
  when_to_transfer_output = ON_EXIT

  # Fichier de logs
  output                  = out.$(ClusterId).$(ProcId)
  error                   = err.$(ClusterId).$(ProcId)
  log                     = log.$(ClusterId).$(ProcId)

  # Pré-requis pour la sélection du nœud local ou faire tourner la tâche
  # mémoire requise
  request_memory          = 2000M
  # type de système requis
  +Requirements           = OpSysAndVer =?= "AlmaLinux"
  # nom du nœud d’exécution de la ferme locale
  +Requirements           = machine     =?= "lpsc-c27.in2p3.fr"

  queue 1

Univers container

Apptainer

Apptainer (anciennement !Singularity) simplifie la création et l'exécution de conteneurs. Le système permet l'encapsulation de composants logiciels pour la portabilité et la reproductibilité. L'univers container de !HTCondor permet de soumettre des tâches utilisant

  • soit des conteneurs préalablement construits sur vos machines à partir d'un fichier de définition : [xxx@lpsc-cyy] apptainer build conteneur.sif conteneur.def
  • soit à partir de conteneurs préexistants dans des registres de conteneurs.

Un exemple d'utilisation de conteneur

cowsay_deb.def un fichier de définition du conteneur apptainer

Bootstrap: docker
From: debian:latest

%post

    apt-get -y update
    apt-get -y install cowsay

%environment
    export LC_ALL=C
    export PATH=/usr/games:$PATH

%runscript
    cowsay "$*"

%labels
    Author Fabulous

apptainer.submit Le fichier pour de définition de la tâche à soumettre

#universe = container is optional
universe
                = container
# utilisation d'un conteneur construit localement
container_image         = ./cowsay.sif

#utilisation d'un conteneur préexistant dans un dépôt
#container_image         = docker://ghcr.io/apptainer/lolcow

arguments               = moo !

should_transfer_files   = YES
when_to_transfer_output = ON_EXIT

output                  = out.$(Process)
error                   = err.$(Process)
log                     = log.$(Process)

request_cpus            = 1
request_memory          = 1024M
request_disk            = 10240K

queue 1

Le concept de jobset

HTCondor permet la soumission d'ensemble de tâches. La syntaxe du fichier de soumission est légèrement différente que pour une tâche simple, vous pouvez trouver la documentation ici

La commande HTcondor diffère également, elle est de la forme suivante : htcondor jobset submit jobs.set

Soumission d'un ensemble de tâches OpenMPI

On retrouve dans le fichier jobs.set le contenu d'un fichier de soumission de l'univers vanilla. La différence réside dans son "encapsulation" dans un itérateur dans le ficher de soumission du jobset.

jobs.set

name = ExampleJobSet

# Définition d'un itérateur: un tableau contenant des items qui sont des liste d'arguments (dans notre cas, un seul argument par item, le nom d'une machine)

iterator = table machine {
    lpsc-c0.in2p3.fr
    lpsc-c8.in2p3.fr
    lpsc-c12.in2p3.fr
    lpsc-c13.in2p3.fr
    lpsc-c15.in2p3.fr
    lpsc-c16.in2p3.fr
    lpsc-c17.in2p3.fr
    lpsc-c18.in2p3.fr
    lpsc-c19.in2p3.fr
    lpsc-c20.in2p3.fr
    lpsc-c21.in2p3.fr
    lpsc-c22.in2p3.fr
    lpsc-c23.in2p3.fr
    lpsc-c24.in2p3.fr
    lpsc-c25.in2p3.fr
    lpsc-c26.in2p3.fr
    lpsc-c27.in2p3.fr
}

# définition de la tâche de base qui va être "itérée"

job {

    universe                = vanilla
    executable              = run.sh
    arguments               = -np 8 example

    output                  = example_$(machine).out
    error                   = example_$(machine).err
    log                     = example.log

    should_transfer_files   = yes
    when_to_transfer_output = on_exit
    transfer_input_files    = example

    # renommage du fichier de sortie en fonction du nom de la machine
    transfer_output_remaps = "example_$(machine).out=$(machine)/example_$(machine).out ; example_$(machine).err=$(machine)/example_$(machine).err"

    # Spécifiez le nombre de slots (processus MPI) requis
    request_cpus            = 1
    request_memory          = 1024M
    request_disk            = 10240K

    +Requirements           = machine =?= "$(machine)"
    +WantParallelSchedulingGroups = True

    queue
}

Autre exemples

 more testprog01.sh
#! /bin/sh
echo "HT-condor testprog01"
echo "I'm process id $$ on" `hostname`
echo "This is sent to standard error" 1>&2
date
echo "Running as binary $0" "$@"
echo "My name (argument 1) is $1"
echo "My sleep duration (argument 2) is $2"
sleep $2
echo "Sleep of $2 seconds finished.  Exiting"
exit 0

exemple d'un fichier de description de soumission:

more testprog01.submit
executable=testprog01.sh
universe=vanilla
arguments=Example.$(Cluster).$(Process) 100
output=results.output.$(Process)
error=results.error.$(Process)
log=results.log
notification=never
should_transfer_files=YES
when_to_transfer_output = ON_EXIT
queue

exemple d'un fichier de description de soumission en sélectionnant !AlmaLinux9 comme Operating System:

more testprog2.submit
executable=testprog01.sh
universe=vanilla
arguments=Example.$(Cluster).$(Process) 100
output=results.output.$(Process)
error=results.error.$(Process)
log=results.log
notification=never
should_transfer_files=YES
when_to_transfer_output = ON_EXIT
requirements = (OpSysAndVer =?= "AlmaLinux9")
queue

Pour choisir Centos7:

requirements = (OpSysAndVer =?= "CentOS7")

Pour choisir Fedora39:

requirements = (OpSysAndVer =?= "Fedora39")

L'utilisateur définit lui-même le groupe auxquel il appartient via une instruction de type :

accounting_group = informatique
Note: See TracWiki for help on using the wiki.