cherubino

Cluster di Calcolo Scientifico

Il cluster Toeplitz

Il cluster Toeplitz è formato da 5 nodi costituiti da

  • 4 Nodi Intel(R) Xeon(R) CPU E5-2650 v4 @ 2.20GHz con 2 threads per core, 12 cores per socket e 2 socket con 256 GB di memoria di cui 250 disponibili.
  • 1 Nodo Intel(R) Xeon(R) CPU E5-2643 v4 @ 3.40GHz con 2 threads per core, 6 cores per socket e 2 socket con 128GB di memoria di cui 125 disponibili.

La connettività tra i nodi è garantita da schede di rete Intel Corporation Ethernet Controller 10-Gigabit X540-AT2 su un’infrastuttura in rame con switch a 10 Gbit/s.

Account

Gli account sono gestiti direttamente dal centro di calcolo del dipartimento di matematica. Si può avere un account a fini di ricerca e/o scrittura tesi scrivendo a help@dm.unipi.it.

Login

Una volta ricevute le credenziali del vostro account potete fare login sul cluster con i comandi

ssh nomeutente@toeplitz.cs.dm.unipi.it

alla prima connessione vi verrà richiesto di accettare il fingerprint della macchina.

Nota: se si intendono usare servizi con interfaccia grafica sulla macchina remota è necessario richiedere ad ssh di fare il forwarding del server grafico, ovvero aggiungere al comando precedente l’opzione --X

In generale è utile connettersi tramite chiave ssh, una chiave può essere generata sul vostri sistema seguendo le istruzioni date da:

ssh-keygen 

Importante: impostare una passphrase per la chiave generata.

Terminata la procedura, si deve copiare la chiave sulla macchina remota con

ssh-copy-id nomeutente@toeplitz.cs.dm.unipi.it

Ogni successivo login dalla vostra macchina non richiederà la password, il primo login dalla vostra macchina di ogni sessione richiederà la passphrase.

Software

La gestione del software sul cluster è eseguita tramite Spack, questo è un gestore di pacchetti per supercomputer. Semplifica l’installazione e la gestione del software scientifico. Non è legato ad un linguaggio di programmazione particolare; ovvero permette di creare un software stack in Python o R, linkare a librerie scritte in C, C++ o Fortran e scambiare facilmente compilatori o programmare per microarchitetture specifiche.

Per gestire il software installato tramite Spack, il sistema utilizza Environment Modules. Laddove In genere un utente su una macchina monouso inizializza il proprio ambiente in fase di accensione/login. Cioè, imposta le informazioni sull’ambiente per ogni applicazione a cui farà riferimento durante la sessione. Il pacchetto Environment Modules è uno strumento che semplifica l’inizializzazione della shell e consente agli utenti di modificare facilmente il proprio ambiente durante la sessione con i cosìddetti file di modulo (modulefiles).

Per visualizzare i moduli disponibili su Toeplitz è sufficiente dare il comando:

module avail

Questo restituirà un elenco dei moduli disponibili così formato:

-------------- /data/software/spack/share/spack/modules/linux-ubuntu22.04-x86_64 ---------------
anaconda3/2021.05-gcc-12.2.0                     intel-oneapi-mkl/2022.1.0-oneapi-2022.1.0  
autoconf/2.69-gcc-12.2.0                         intel-oneapi-mpi/2021.6.0-oneapi-2022.1.0  
automake/1.16.5-gcc-12.2.0                       mpsolve/3.2.1-gcc-12.2.0                   
cmake/3.23.3-gcc-12.2.0                          openblas/0.3.20-gcc-12.2.0                 
gcc/12.2.0                                       openmpi/4.1.4-gcc-12.2.0                   
intel-oneapi-compilers/2022.1.0                  valgrind/3.19.0-oneapi-2022.1.0            
intel-oneapi-inspector/2022.1.0-oneapi-2022.1.0  

------------------------------------ /data/software/modules ------------------------------------
advanpix/4.8.0  cocoa/5.4  julia/1.8.1  matlab/R2021a  

I moduli sono nominati con il seguente pattern:

nomeprogramma/versione-compilatore-versione

Per avere un ambiente consistente è necessario che tutti i moduli caricati facciano riferimento allo stesso compilatore. Un utilizzo non consistente può dare, nella migliore delle ipotesi, errori di compilazione o linkaggio, nella peggiore comportamenti non facilmente discernibili.

I moduli possono essere caricati con il comando:

module load nomeprogramma1/versione-compilatore-versione nomeprogramma2/versione-compilatore-versione

e rimossi dall’ambiente con:

module unload nomeprogramma1/versione-compilatore-versione

Se si vuole riportare l’ambiente allo stato originario (nessun modulo caricato) si può eseguire il comando:

module purge

Per visualizzare quali moduli sono attivi è sufficiente dare il comando:

module list

Q: Come faccio ad ottenere altro software?

Se il software è disponibile via Spack e può essere di interesse generale per l’utenza scrivere una mail a help@dm.unipi.it con una richiesta dettagliata.

Altrimenti, potete compilare il vostro software con il vostro utente e averlo disponibile per voi.

Gestore di code

Toeplitz è una macchina condivisa tra più utenti, è quindi necessario avere un modo per allocare le risorse disponibili. Su Toeplitz a questo scopo è installato il sistema di gestione dei cluster e pianificazione dei lavori chiamato Slurm (Simple Linux Utility for Resource Management.) Questo è un sistema open source, tollerante ai guasti e altamente scalabile per cluster Linux di ogni dimensione. In qualità di gestore del carico di lavoro del cluster, Slurm ha tre funzioni chiave. Innanzitutto, assegna agli utenti l’accesso esclusivo e/o non esclusivo alle risorse (nodi di calcolo) per un certo periodo di tempo in modo che possano eseguire il lavoro. In secondo luogo, fornisce un framework per avviare, eseguire e monitorare il lavoro (normalmente un lavoro parallelo) sull’insieme di nodi allocati. Infine, arbitra la contesa per le risorse gestendo una coda di lavoro in sospeso.

Toeplitz contiene tre partizioni differenti

PARTITIONDescrizoneTIMELIMITNODESNODELIST
cl1Singolo nodo con
2 threads per core
6 cores per socket
2 socket
infinite1lnx1
cl2Su ogni nodo si possono usare:
2 threads per core
12 cores per socket
2 socket
infinite4lnx[2-5]
allPartizione che contiene
tutti i nodi del cluster
infinite5lnx[1-5]
Partizioni/code disponibili su Toeplitz

Esistono due modi per avviare lavori con SLURM; in modo interattivo con srun o come script con sbatch.

Utilizzo di una shell interattiva

I job interattivi sono un buon modo per testare la vostra configurazione prima di inserirla in uno script o per lavorare con applicazioni interattive come MATLAB o Python. Potete vedere immediatamente i risultati e controllare se tutte le parti si comportano come previsto.

Le shell interattive possono essere lanciato con il comando srun seguito da un certo numero di flag. Le più comuni sono:

FlagEffetto
--job-name=<nome>Nome del job da visualizzare
--output=<path>Percorso del file in cui viene scritto l’output del job (errore).
--time=<d-hh:mm:ss>Tempo limite per il job. Il job verrà terminato da SLURM allo scadere del tempo. Formato: giorni-ore:minuti:secondi
--nodes=<num_nodes>Numero di nodi. I nodi multipli sono utili solo per job con memoria distribuita (ad es. MPI).
--mem=<MB>Memoria (RAM) per nodo. Numero seguito dal prefisso dell’unità, ad es. 16G
--mem-per-cpu=<MB>Memoria (RAM) per core CPU richiesto
--ntasks-per-node=<num_procs>Numero di processi (MPI) per nodo. L’utilizzo di più di uno è utile solo per job MPI. Il numero massimo dipende dai nodi (numero di core)
--cpus-per-task=<num_threads>CPU cores per task. Per MPI usane uno. Per le applicazioni parallele shared questo è il numero di thread.
--exclusiveIl job non condividerà i nodi con altri job in esecuzione. Ti verranno addebitati i nodi completi anche se hai chiesto di meno.
--partition=Partizione/coda in cui eseguire il job
Flag per l’uso del comando srun

Differenza tra CPU e task

Quando si è alle prime armi la differenza tra --ntasks e --cpus-per-taks è in genere piuttosto confusa. Supponendo che tu voglia eseguire il tuo programma su un singolo nodo con 16 core quali parametri SLURM devi speficare?

La risposta è: dipende. L’applicazione supporta o no MPI? MPI (Message Passing Protocol) è un’interfaccia di comunicazione utilizzata per lo sviluppo di programmi di calcolo parallelo su sistemi a memoria distribuita. Ciò è necessario affinché le applicazioni in esecuzione su più nodi (computer) possano condividere i risultati (intermedi).

Per decidere quale set di parametri utilizzare, devi controllare se la tua applicazione utilizza MPI e quindi può trarre vantaggio dall’esecuzione su più nodi contemporaneamente. D’altra parte, se hai un’applicazione che non supporta MPI non ha senso richiedere più di un nodo.

Esempi

Supponiamo di voler aprire la più semplice shell interattiva, questo può essere fatto con il comando:

srun --pty bash -i

questo avvierà una shell con 1 CPU logica, 4 Gb di RAM ed un tempo di esecuzione di 2 ore. All’interno possiam ora utilizzare il sistema module per caricare il software di cui abbiamo bisogno.

Supponiamo ad esempio di voler lanciare una sessione di MATLAB richiedendo più risorse:

srun --x11 --job-name=matlab --nodes=1 --mem=16G --partition=cl2 --time=02:30:00 --pty bash -i
module load matlab/R2021a
matlab &

Con questa scelta stiamo chiedendo che ci venga data la possibilità di usare un’interfaccia grafica (--x11), per un job di nome matlab (--job-name=matlab), su di un singolo nodo (--nodes=1), utilizzando 16Gb di memoria RAM (--mem=16G). Vogliamo che sia inoltre scelto un nodo dalla coda cl2 (--partition=cl2) e vogliamo occuparlo per 2 ore e mezza (--time=02:30:00). In generale dovremo attendere che le risorse richieste siano libere, non appena saranno disponibili potremo caricare il modulo che contiene Matlab (module load matlab/r2021a) ed eseguirlo (matlab &). Possiamo verificare nella shell cosa sta facendo SLURM inserendo il comando:

squeue

che ci stamperà a schermo informazioni su tutti i job in esecuzione (dandoci anche un’idea delle risorse in uso al momento, ed in caso di quanto dobbiamo aspettare prima che le risorse che abbiamo richiesto siano disponibili).

             JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
                48       cl2   matlab durastan  R       0:10      1 lnx2

Nota: per lanciare un job con l’opzione --x11 è necessario che anche la connessione a Toeplitz sia stata avvianda con l’opzione di forwarding del server grafico X11 (ovvero ssh -X etc.).

Esecuzione di script batch

L’alternativa alla shell interattiva è l’esecuzione di un job in modalità batch. Per eseguire un job su Toepltiz è necessario creare uno script di esecuzione. Uno script di esecuzione è un normale script di shell (bash) con alcune direttive che specificano il numero di CPU, memoria, ecc., che verranno interpretate dal sistema batch al momento dell’invio.

Dopo aver scritto il tuo job script come mostrato negli esempi seguenti, puoi avviarlo con:

sbatch jobscript.sh

Job OpenMP

Questo script funge da modello per applicazioni in grado di utilizzare più processori su un singolo nodo di Toeplitz. Queste applicazioni sono comunemente denominate applicazioni threaded, OpenMP, PTHREADS o di memoria shared (condivisa). Sebbene possano utilizzare più processori, non possono utilizzare più nodi e tutti i processori devono trovarsi sullo stesso nodo.

#!/bin/bash
#SBATCH --job-name=ilmiojob                     # Nome del job
#SBATCH --mail-type=END,FAIL                    # Eventi E-mail (NONE, BEGIN, END, FAIL, ALL)
#SBATCH --mail-user=mia-mail@unipi.it           # Dove inviare la mail con gli esiti
#SBATCH --nodes=1                               # Richiediamo un singolo nodo
#SBATCH --ntasks=1                              # Che deve eseguire una sola task
#SBATCH --cpus-per-task=X                       # Utilizzando X CPUs
#SBATCH --partition=<partizione>                # Coda su cui eseguire il job
#SBATCH --time=00:10:00                         # Per cui richiediamo 10 minuti
#SBATCH --output=%x_%n_%j.log                      # Logfile per l'output, si veda al
                                                # fondo per i pattern %

# Si caricano sul nodo tutti i moduli necessari ad eseguire il codice in oggetto:
# Tipicamente i relativi compilatori e librerie.
module load gcc/12.2.0
# Il numero riportato qui deve essere minore o uguale a quello in --cpus-per-task
export OMP_NUM_THREADS=X

./<nome dell'eseguibile> <parametri di input>

sbatch consente ai comandi che hanno come argomento il nome di file di contenere uno o più simboli di sostituzione, simboli che sono generalmente preceduti da un segno di percentuale “%” seguito da una lettera (ad es. %j).

SimboloSostituzione
\\Non elaborare nessuno dei simboli di sostituzione.
%%Il carattere “%”
%ANumero di allocazione del lavoro principale dell’array di lavori.
%aNumero dell’ID (indice) dell’array di lavori.
%Jjobid.stepid del lavoro in esecuzione. (ad es. “128.0”)
%jjobid del lavoro in esecuzione.
%Nnome host breve. Questo creerà un file IO separato per nodo.
%nIdentificatore del nodo relativo al job corrente (ad es. “0” è il primo nodo del job in esecuzione) Questo creerà un file IO separato per nodo.
%sstepid del lavoro in esecuzione.
%tidentificatore dell’attività (grado) relativo al job corrente. Questo creerà un file IO separato per attività.
%uNome utente
%xNome del job
Caratteri di sostituzione Slurm

Job MPI

A seconda della frequenza e della richiesta di larghezza di banda della tua applicazione, puoi semplicemente avviare una serie di job MPI oppure richiedere di riservare interi nodi (#SBATCH --exclusive). Sebbene l’utilizzo di interi nodi garantisca una bassa latenza e un’elevata larghezza di banda, di solito si traduce in un tempo di coda più lungo rispetto al livello di cluster: dovrete aspettare che nessun’altro stia usando quei nodi. Nel caso in cui l’esclusività non sia necessaria Slurm può distribuire la vostra attività su tutti i nodi e utilizzare tutti i core altrimenti inutilizzati sui nodi. Ad esempio, nel nodo lnx3 potete eseguire un lavoro a 24 dei 48 totali. Questo di solito si traduce in tempi di attesa in coda più brevi, ma ha velocità di connessione tra i processi di calcolo più lente.

Questo script funge da modello per applicazioni in grado di utilizzare più processori su uno o più nodi di Toeplitz. Queste applicazioni sono comunemente denominate applicazioni con memoria distribuita. Possono utilizzare più processori su più nodi ovvero i processori possono trovarsi su nodi diversi.

#!/bin/bash
#SBATCH --job-name=<nome-del-job> 
#SBATCH --nodes=X                               
#SBATCH --ntasks=Y                           
#SBATCH --cpus-per-task=1                       
#SBATCH --partition=<cl2,all>               
#SBATCH --time=00:10:00                         
#SBATCH --output=%x_%u_%j.log 

module purge
module load gcc/12.2.0 openmpi/4.1.4-gcc-12.2.0 openblas/0.3.20-gcc-12.2.0

srun ./<nome-dell'eseguibile> <eventuali-input> 

Il codice di esempio permette di eseguire un job sulla coda cl2 o all che utilizzi un numero di nodi X tra e 1 e 5 (--nodes=X) e un numero di task MPI Y (--ntasks=Y) che utilizzano una CPU fisica ognuna (--cpus-per-task=1). Trattandosi di un job che deve utilizzare più nodi è necessario avere nell’ambiente un’implementazione di MPI, e.g., qui carichiamo la versione openmpi/4.1.4-gcc-12.2.0 con il relativo compilatore gcc/12.2.0.

Performance

Quando si utilizzano i cluster, vale sempre la pena misurare la scalabiltà parallela dei propri codici. La misurazione della scalabilità forte (strong scaling) viene eseguita testando come il tempo di calcolo complessivo del lavoro scala con il numero crescente di elementi di calcolo (siano questi thread o processi MPI), mentre il test per la scalabilità debole (weak scaling) viene eseguito aumentando sia la dimensione del lavoro che il numero di elementi di calcolo. I risultati dei test di scalabilità forte forniranno una buona indicazione della quantità di risorse da richiedere per la dimensione del job che si vuole eseguire.

Consideriamo l’esempio del codice  julia_set_openmp.c che produce una stampa del frattale di Julia sfruttando un parallelismo con memoria shared. Modifichiamo il codice per produrre il frattale nella figura a lato

...
  int h = 5000;
  unsigned char *rgb;
  int w = 10000;
...

Compiliamo il codice con gcc come

module load gcc/12.2.0
gcc julia_set_openmp.c -fopenmp -O3 -o julia

E testiamo l’esecuzione in modalità batch sui due diversi tipi di nodi che abbiamo, quindi in un caso utilizzando da 1 a 24 threads, nel secondo utilizzandone da 1 a 48.

Partizione 1: da 1 a 24 threads

#!/bin/bash
#SBATCH --job-name=julia
#SBATCH --nodes=1       
#SBATCH --ntasks=1      
#SBATCH --cpus-per-task=24
#SBATCH --partition=cl1 
#SBATCH --time=00:10:00 
#SBATCH --output=%x_%N_%j.log

module load gcc/12.2.0

for i in `seq 1 24`
do
export OMP_NUM_THREADS=${i}
./julia
done

Partizione 2: da 1 a 48 threads

#!/bin/bash
#SBATCH --job-name=julia
#SBATCH --nodes=1       
#SBATCH --ntasks=1      
#SBATCH --cpus-per-task=48
#SBATCH --partition=cl2 
#SBATCH --time=00:10:00 
#SBATCH --output=%x_%N_%j.log

module load gcc/12.2.0

for i in `seq 1 48`
do
export OMP_NUM_THREADS=${i}
./julia
done

Da cui osserviamo la seguente decrescita dei tempi di calcolo e il corrispettivo speedup

Torna in cima