Utilisation du cluster

Introduction

Qu’est-ce qu’un “cluster de calcul” ?

Notre cluster est composé de 12 ordinateurs. On appelle ces ordinateurs des nœuds de calcul. Au total, sur l’ensemble des nœuds il y a actuellement 208 processeurs et 1,2 To de mémoire vive et environ 80To d’espace de stockage.

Un cluster c’est simplement un ensemble d’ordinateurs que vous pouvez utilisez pour vos calculs. Toutefois vous n’avez pas un accès direct à ces ordinateurs. Un logiciel est là pour gérer un peu les choses, il s’appelle SGE (Sun Greed Engine). Au lieu d’envoyer directement les calculs sur les nœuds, vous les soumettez à SGE qui les place dans une file d’attente. On appelle ces calculs des « job ».

Un cluster, ce sont des ressources partagées, vous êtes nombreux à les utiliser. Plutôt que de mettre en place des limites et des restrictions, nous préférons compter sur votre “courtoisie” pour que la cohabitation se passe bien.

Vous pouvez voir le cluster, comme un hôtel. Pour pouvoir bien repartir tous le monde dans une chambre le réceptionniste a un cahier avec les réservations. Il en est de même avec le cluster. Vous devez indiquer à SGE, notre gentil réceptionniste, les ressources nécessaires à vos calculs pour qu’il puisse les repartir de manière optimale.

Globalement, les ressources limitantes sont le nombre de processeurs et la quantité de mémoire.

Les processeurs

Dans le cluster eBio, les nœuds ne sont pas tous identiques. Certains disposent de 8 processeurs, d’autres de 64. Les calculs parallèles ne pouvant s’exécuter que sur un nœud, il est donc inutile d’en demander plus de processeurs qu’il n’y en a sur un nœud.

La mémoire

Certains algorithmes requièrent beaucoup de mémoire vive. Ce paramètre n’est pas facile
à estimer à l’avance, nous vous donnons quelques pistes pour son estimation dans la partie Gestion des ressources mémoire.

Pensez à regarder la consommation a posteriori.

Par ailleurs, tous les nœuds ne disposent pas de la même quantité de mémoire. Selon votre demande, votre calcul (job) sera donc soumis sur un des nœuds qui a les ressources nécessaires.

Comme pour les processeurs, il est inutile de demander plus de mémoire qu’il n’y en a sur un nœud, votre job ne pourra jamais partir et rester “coincé” dans la file d’attente.

L’espace disque

Vous avez à votre disposition votre partition utilisateur (/home). Cette partition est accessible depuis tous les nœuds et est très volumineuse (plusieurs dizaines de tera octets). Cela procure un grand confort d’utilisation mais au prix de la performance (en particulier en vitesse d’écriture). Si vous avez des programmes qui sollicitent beaucoup les disques, vous pouvez utiliser la partition /tmp pour y écrire des données temporaires. Il s’agit là de disques locaux, ils sont bien plus rapides. L’inconvénient cette fois-ci c’est que la place est plus restreinte.

Nous avons ajouté une partition (/scratch) dédié à ce type d’usage sur le noeud1.

Pensez à faire le ménage derrière vous et à ne pas laisser les données occuper inutilement cet espace si vous l’utilisez.

Les propriétés du cluster

Pour connaître les propriétés des nœuds, vous pouvez utiliser la commande suivante :

johndoe@frontal:~$ qhost
HOSTNAME                ARCH         NCPU  LOAD  MEMTOT  MEMUSE  SWAPTO  SWAPUS
-------------------------------------------------------------------------------
global                  -               -     -       -       -       -       -
node1                   lx26-amd64      8  0.01  102.5G  505.8M     0.0     0.0
node10                  lx26-amd64      8  0.01   23.6G  268.1M     0.0     0.0
node11                  lx26-amd64     64  0.01  126.1G  887.1M     0.0     0.0
node12                  lx26-amd64     64  0.01  757.4G    2.9G     0.0     0.0
node2                   lx26-amd64      8  0.01   55.2G  424.3M     0.0     0.0
node3                   lx26-amd64      8  0.01   31.5G  295.6M     0.0     0.0
node4                   lx26-amd64      8  0.01   31.5G  286.0M     0.0     0.0
node5                   lx26-amd64      8  0.01   27.5G  295.9M     0.0     0.0
node6                   lx26-amd64      8  0.01   27.5G  304.7M     0.0     0.0
node7                   lx26-amd64      8  6.12   23.6G  485.6M     0.0     0.0
node8                   lx26-amd64      8  0.01   23.6G  277.3M     0.0     0.0
node9                   lx26-amd64      8  0.01   23.6G  308.2M     0.0     0.0

Vous pouvez voir le nombre de processeurs (NCPU), la charge (LOAD) du noeud, la quantité totale de mémoire (MEMTOT), la quantité de mémoire utilisée (MEMUSE).

L’état du cluster

Pour connaître en détail l’état de la file (queue) et des nœuds:

johndoe@frontal:~$ qstat -f -u \*
queuename                      qtype resv/used/tot. load_avg arch          states
---------------------------------------------------------------------------------
main.q@node1                   BIP   0/8/8          3.88     lx26-amd64
   1589 0.05738 hmmsearch     johndoe     r     04/02/2013 16:00:33     2 8
   1589 0.05738 hmmsearch     johndoe     r     04/02/2013 16:00:33     2 9
   1589 0.05738 hmmsearch     johndoe     r     04/02/2013 16:00:33     2 10
   1589 0.05738 hmmsearch     johndoe     r     04/02/2013 16:00:33     2 11
---------------------------------------------------------------------------------
main.q@node10                  BIP   0/5/8          3.02     lx26-amd64
   1579 0.05923 cufflinks.sh  johndoe     r     04/02/2013 14:59:33     2 17
   1587 0.50006 date.sh       eidle       r     04/02/2013 15:59:03     1
   1589 0.05738 hmmsearch     johndoe     r     04/02/2013 16:00:33     2 12
---------------------------------------------------------------------------------
main.q@node2                   BIP   0/8/8          2.89     lx26-amd64
    931 0.74997 myblast.sh    hsimpson    r     03/22/2013 11:13:53     8
---------------------------------------------------------------------------------
main.q@node3                   BIP   0/8/8          2.86     lx26-amd64
    928 0.75000 myblast.sh    hsimpson    r     03/22/2013 11:12:53     8
---------------------------------------------------------------------------------
main.q@node4                   BIP   0/8/8          4.02     lx26-amd64
   1589 0.05738 hmmsearch     johndoe     r     04/02/2013 16:00:33     2 4
   1589 0.05738 hmmsearch     johndoe     r     04/02/2013 16:00:33     2 5
   1589 0.05738 hmmsearch     johndoe     r     04/02/2013 16:00:33     2 6
   1589 0.05738 hmmsearch     johndoe     r     04/02/2013 16:00:33     2 7
---------------------------------------------------------------------------------
main.q@node5                   BIP   0/6/8          2.97     lx26-amd64
   1589 0.05738 hmmsearch     johndoe     r     04/02/2013 16:00:33     2 1
   1589 0.05738 hmmsearch     johndoe     r     04/02/2013 16:00:33     2 2
   1589 0.05738 hmmsearch     johndoe     r     04/02/2013 16:00:33     2 3
---------------------------------------------------------------------------------
main.q@node6                   BIP   0/6/8          2.95     lx26-amd64
   1295 0.32111 s51           johndoe     r     04/02/2013 10:03:03     2
   1296 0.32111 s52           johndoe     r     04/02/2013 10:38:18     2
   1297 0.32111 s53           johndoe     r     04/02/2013 12:26:33     2
---------------------------------------------------------------------------------
main.q@node7                   BIP   0/8/8          2.82     lx26-amd64
    929 0.74999 Ae1_1_kegg    hsimpson    r     03/22/2013 11:13:23     8
---------------------------------------------------------------------------------
main.q@node8                   BIP   0/8/8          2.91     lx26-amd64
    930 0.74998 Ae1_2_kegg    hsimpson    r     03/22/2013 11:13:38     8
---------------------------------------------------------------------------------
main.q@node9                   BIP   0/5/8          2.93     lx26-amd64
   1588 0.50003 stat.sh       eidle       r     04/02/2013 16:00:03     1
   1589 0.05738 hmmsearch     johndoe     r     04/02/2013 16:00:33     2 13
   1589 0.05738 hmmsearch     johndoe     r     04/02/2013 16:00:33     2 14
---------------------------------------------------------------------------------
demo.q@demohost                BIP   0/0/1          0.00     lx26-amd64

############################################################################
 - PENDING JOBS - PENDING JOBS - PENDING JOBS - PENDING JOBS - PENDING JOBS
############################################################################
   1298 0.27871 s54        johndoe     qw    03/27/2013 14:32:13     2
   1299 0.27805 s55        johndoe     qw    03/27/2013 14:32:14     2
   1300 0.27746 s56        johndoe     qw    03/27/2013 14:32:14     2
   1301 0.27692 s57        johndoe     qw    03/27/2013 14:32:14     2
   1302 0.27642 s58        johndoe     qw    03/27/2013 14:32:15     2
   1303 0.27597 s59        johndoe     qw    03/27/2013 14:32:15     2
   1304 0.27555 s60        johndoe     qw    03/27/2013 14:32:15     2
   1305 0.27517 s61        johndoe     qw    03/27/2013 14:32:16     2
   1306 0.27481 s62        johndoe     qw    03/27/2013 14:32:16     2
   1307 0.27448 s63        johndoe     qw    03/27/2013 14:32:16     2

Vous voyez ainsi les jobs qui sont en train d’être traités et ceux qui sont en attente.

Ceci vous donne une vue instantanée. Pour visualiser l’activité du cluster vous pouvez vous rendre ici : monitoring. (> FIXME: le lien à changé)

Exemple d’utilisation

Les jobs

Un job (ou tâche) est un script shell, comme vous avez probablement l’habitude d’en utiliser. Celui-ci est soumis dans la queue à l’aide de la commande qsub.

Un exemple de script:

#!/bin/bash
date

Ce script écrit à l’écran la date.

Nous pouvons déjà le soumettre au cluster. Pour cela, écrivez ces deux lignes dans un fichier date-sge.job, puis :

johndoe@frontal:~$ qsub date-sge.job

Il sera soumis au cluster avec un identifiant : job_id.

Pour connaître le statut de votre script:

johndoe@frontal:~$ qstat -j job_id
Your job 1717 ("date-sge.job") has been submitted

Une fois traité, votre script produit deux fichiers dans votre répertoire utilisateur :

  • date-sge.e1717
  • date-sge.o1717

L’id_job est automatiquement ajouté à la fin du nom du fichier. Cela vous permet de distinguer les sorties dans le cas où vous devez soumettre plusieurs fois le même script.

Le fichier qui a un “o” après le point contient la sortie de votre script, l’autre, avec un “e”, contient les éventuels messages d’erreur.

Pour plus de commodité, vous pouvez ajouter quelques instructions supplémentaires dans l’en-tête du fichier.

#!/bin/sh
#$ -S /bin/sh
#$ -N test
#$ -V
#$ -cwd
date
Signification de ces options :
  • -S : interpréteur shell à utiliser pour exécuter le job
  • -N : le nom de votre job
  • -V : conserver toutes les variables d’environnement lors de la soumission
  • -cwd : placer les sorties dans le répertoire courant

Pour encore plus de contrôle sur l’emplacement des fichiers d’erreur et de sortie, vous pouvez ajouter:

#$ -e ~/erreurs/
#$ -o ~/sorties/
Pour avoir la liste complète des options, vous pouvez lire le manuel de qsub: ::
man qsub

Exécution parallèle

La majorité des programmes peuvent utiliser plusieurs processeurs d’un nœud. Pour exemple, BLAST en est capable. Pour que BLAST utilise plusieurs processeurs il faut spécifier le nombre de cœurs qu’il pourra utiliser avec l’option -num_threads.

blastn -task blastn -db my.blast.db -query myquery.fna -num_threads 4 -out blastresult.out

Notez qu’il est inutile d’en demander plus de 64 (nombre maximum de coeurs sur un noeud au moment de la rédaction de ce document).

Pour exécuter ce type de calculs, vous devez spécifier que votre programme doit être soumis dans un environnement parallèle et qu’il utilisera 4 processeurs. Pour cela :

#!/bin/sh
#$ -S /bin/sh
#$ -N myBLAST
#$ -V
#$ -cwd
#$ -e blast.err
#$ -o blast.out
#$ -pe parallel 4
blastn -task blastn -db my.blast.db -query myquery.fna -num_threads 4 -out blastresult.out

Gestion des ressources mémoire

Pour les jobs qui nécessitent beaucoup de mémoire, il est préférable de l’estimer à l’avance. Pour exemple, les tâches d’assemblage de novo sont très gourmandes en mémoire.

Certains outils mettent à disposition des estimateurs de la mémoire nécessaire pour leur exécution. Par exemple, pour l’assembleur de novo Velvet : Velvet Memory Calculator.

Pourquoi spécifier la mémoire ?

Pour exemple, la version actuelle de Velvet, ne sais utiliser qu’un processeur. Si vous soumettez votre job, il va occuper un slot (un processeur). Sept autres slots seront libres et prêts à accepter des jobs d’autres utilisateurs. Au moment où un autre utilisateur soumet son job, SGE regardera la charge du nœud. Si à ce moment précis le nœud semble disposer de beaucoup de mémoire non utilisée, il risque d’y envoyer le job suivant. Comme Velvet met du temps à “monter en mémoire”, l’utilisation de celle-ci croit très progressivement. Les deux jobs peuvent finir par se gêner (plantage possible des deux calculs).

Pour reprendre notre analogie de l’hôtel, si vous ne précisez pas, dès le départ, que vous voulez un lit king-size pour vous seul, vous risquez de vous réveiller le matin très à l’étroit avec une horde de trolls dans votre lit.

Donc pour prévenir SGE que votre job risque d’utiliser une certaine quantité de mémoire, vous devez ajouter:

#$ -l h_vmem=2G,h_stack=124M

Cela réservera 2Go de mémoire vive (RAM) par slot demandé pour votre job.

Attention : Si vous soumettez votre job dans un environnement parallèle, il faut penser à diviser la quantité de mémoire totale dont vous avez besoin par le nombre de slots que vous avez demandé.

Ainsi, le script de blast précédemment utilisé devient

#!/bin/sh
#$ -S /bin/sh
#$ -N myBLAST
#$ -V
#$ -cwd
#$ -e blast.err
#$ -o blast.out
#$ -pe parallel 4
#$ -l h_vmem=2G,h_stack=124M
blastn -task blastn -db my.blast.db -query myquery.fna -num_threads 4 -out blastresult.out

En soumettant de cette manière votre BLAST s’exécutera sur 4 processeurs et aura 8Go de RAM à sa disposition.

Comment analyser la consommation mémoire

Pour analyser a posteriori les ressources que votre job a utilisé, vous pouvez utiliser la commande qacct

eidle@frontal:~$ qacct -j 994
==============================================================
qname        main.q
hostname     node9
group        eidle
owner        eidle
project      NONE
department   defaultdepartment
jobname      myjob.sh
jobnumber    994
taskid       undefined
account      sge
priority     0
qsub_time    Mon Mar 25 11:41:31 2013
start_time   Mon Mar 25 11:41:41 2013
end_time     Mon Mar 25 11:43:02 2013
granted_pe   NONE
slots        1
failed       0
exit_status  1
ru_wallclock 81
ru_utime     76.341
ru_stime     2.292
ru_maxrss    2564
ru_ixrss     0
ru_ismrss    0
ru_idrss     0
ru_isrss     0
ru_minflt    1953
ru_majflt    0
ru_nswap     0
ru_inblock   7437720
ru_oublock   216
ru_msgsnd    0
ru_msgrcv    0
ru_nsignals  0
ru_nvcsw     102
ru_nivcsw    214
cpu          78.633
mem          0.726
io           3.531
iow          0.000
maxvmem      22.012M
arid         undefined

Ce job n’a pas utilisé d’environnement parallèle, il a utilisé 22Mo de RAM ...

Voici l’explication (en anglais) qui vous aidera à analyser cette sortie :

department
The department which was assigned to the job.
granted_pe
The parallel environment which was selected for that job.
slots
The number of slots which were dispatched to the job by the scheduler.
task_number
Array job task index number.
cpu
The cpu time usage in seconds.
mem
The integral memory usage in Gbytes cpu seconds.
io
The amount of data transferred in input/output operations.
category
A string specifying the job category.
iow
The io wait time in seconds.
pe_taskid
If this identifier is set the task was part of a parallel job and was passed to Sun Grid Engine via the qrsh -inherit interface.
maxvmem
The maximum vmem size in bytes.
arid
Advance reservation identifier. If the job used resources of an advance reservation then this field contains a positive integer identifier otherwise the value is “0” .
ar_submission_time
If the job used resources of an advance reservation then this field contains the submission time (GMT unix time stamp) of the advance reservation, otherwise the value is “0” .

Les jobs arrays

On a couramment besoin d’exécuter un grand nombre de jobs presque identiques, pour lancer par exemple un même programme sur 1000 fichiers d’entrée différents. La solution naïve serait de générer 1000 scripts et de les soumettre à SGE. Cette solution est bonne ... si vous avez du temps à perdre.

Solution : Avec les jobs arrays vous pouvez soumettre un unique script.

Prenons l’exemple du programme myProg.py que l’on veut lancer sur 1000 fichiers d’entrée différents, le script de soumission pour SGE est alors :

#!/bin/sh
#$ -cwd
#$ -t 1-1000
#$ -e myProg.err
#$ -o myProg.out
#$ -N myProg
#$ -q main.q@node12,main.q@node11,main.q@node10
infile="input"$SGE_TASK_ID
outfile="output"$SGE_TASK_ID
./myProg.py -i $infile -o $outfile

L’option -t permet d’indiquer à SGE qu’il s’agit d’un job array avec les “tâches” numérotées de 1 à 1000.

Lorsque le job array est soumis, chaque sous-job est donc numéroté de 1 à 1000 (instruction -t) et cet identifiant est stocké dans la variable SGE_TASK_ID. L’array est alors soumis comme un job, et toutes les sous-tâches reçoivent un TASK_ID.

Dans un cadre de bonnes pratiques, nous vous conseillons de soumettre vos jobs array sur un nombre limité de noeud (instruction -q) afin de ne pas saturer le cluster.

Nota bene : il n’y a aucune différence de vitesse d’exécution entre soumettre 1000 scripts à l’aide d’un script ou les soumettre dans un job array. Les jobs array sont là uniquement pour vous faciliter cette tâche.

Cela étant dit, vous pouvez vouloir soumettre un grand nombre de jobs très courts. Il y a un délai entre le moment où une place se libère sur un nœud et le moment où le job suivant est démarré. Plutôt que d’utiliser de très gros job arrays, il est donc sage de grouper un certain nombre de ce type de petits jobs dans un script et de lancer ces scripts via un job array.

QLOGIN

Il est interdit de lancer des calculs sur le nœud de soumission (frontal). Celui-ci n’est pas dimensionné pour ça. Même les tâches comme la compressions/décompressions de vos archives doivent être considérées comme des calculs. Pour faire ceci vous pouvez utiliser la commande qlogin. Elle vous connecte sur un nœud et vous donne un shell interactif sur celui-ci. Vous pouvez alors lancer vos commandes interactives tout en utilisant les ressources du nœud.

Qlogin prend les mêmes paramètres que qsub. Vous pouvez donc ‘réserver’ de la mémoire, de slots etc ...