Table of Contents

Laboratorul 1 - Introducere în clusterul HPC al UPB

Scopul laboratorului

Scopul acestui laborator este de a vă familiariza cu infrastructura de calcul de înaltă performanță (HPC – High Performance Computing) a facultății.

Concret, veți învăța:

La finalul laboratorului ar trebui să puteți lansa un job pe cluster fără ajutor.

Ce este un cluster HPC?

Aplicațiile moderne din știință și inginerie precum simulări numerice, antrenarea modelelor de inteligență artificială, procesare Big Data, etc. necesită resurse de calcul care depășesc cu mult capacitatea unui singur calculator.

Un cluster HPC rezolvă această problemă combinând puterea a zeci sau sute de noduri de calcul interconectate printr-o rețea internă de mare viteză (de obicei InfiniBand sau Ethernet RDMA, de ordinul sutelor de Gbps).

Fiecare nod este un server echipat cu:

Alte tipuri de acceleratoare întâlnite în infrastructurile HPC moderne (click pentru detalii)

Alte tipuri de acceleratoare întâlnite în infrastructurile HPC moderne (click pentru detalii)

Pe lângă GPU-uri, în infrastructurile HPC moderne apar tot mai frecvent acceleratoare specializate:

  • TPU (Tensor Processing Unit) – ASIC (Application-Specific Integrated Circuit) dezvoltat de Google, optimizat pentru operații cu tensori (înmulțiri de matrici) folosite în antrenarea și inferența rețelelor neuronale. Disponibil exclusiv prin Google Cloud.
  • NPU (Neural Processing Unit) – procesor specializat pentru inferență AI eficientă energetic, integrat de obicei în SoC-uri mobile și dispozitive edge (smartphone-uri, IoT). Prezent în procesoarele Intel, Qualcomm, Apple, Huawei.
  • DPU (Data Processing Unit) – procesor dedicat operațiilor de rețea, securitate și stocare în data center-uri, scutind CPU-ul și GPU-ul de aceste sarcini (ex: NVIDIA BlueField).
  • FPGA (Field-Programmable Gate Array) – circuit integrat reconfigurabil post-fabricație, care poate fi programat la nivel hardware pentru sarcini specifice. Oferă un compromis între flexibilitatea GPU-urilor și eficiența ASIC-urilor, fiind utilizat în prototipare, procesare de semnal și inferență AI cu latență redusă (ex: Intel/Altera, AMD/Xilinx).
  • QPU (Quantum Processing Unit) – unitatea de procesare a calculatoarelor cuantice, bazată pe qubiți care pot exista simultan în mai multe stări (superpoziție). Utilizat în optimizare, simulări moleculare și criptografie. Tehnologie încă în stadiu experimental.


Componentele principale ale unui cluster HPC sunt:

Nodurile de calcul sunt organizate în partiții — grupuri de noduri cu configurație hardware omogenă. Partițiile sunt, în schimb, heterogene între ele (de exemplu: o partiție doar cu CPU-uri, una cu GPU-uri A100, alta cu H100, etc.).

Aplicațiile rulate pe un cluster HPC folosesc paradigme de programare distribuită și paralelă, precum:

Modelele pot fi, bineînțeles, combinate (programe hibride MPI+OpenMP+CUDA). Prin combinarea acestor tehnologii, sistemele HPC ating performanțe de ordinul tera– sau peta–flopilor, fiind esențiale în cercetarea științifică modernă.

Clusterul HPC al UPB

Universitățile tehnice dispun adesea de clustere HPC proprii, folosite pentru activități de cercetare și educație. Universitatea Politehnica din București deține un astfel de cluster modern, echipat cu GPU-uri performante (precum NVIDIA H100, A100).

Exemplu de afișare a partițiilor:

[nume.student@fep10 ~]$  sinfo -o "%10P %30N %8c %10m %20G %10a"
PARTITION  NODELIST                       CPUS     MEMORY     GRES                 AVAIL
dgxa100    dgxa100-ncit-wn[01-04]         256      2063510    gpu:tesla_a100:8     up
dgxh100    dgxh100-precis-wn[01-03]       224      1998908+   gpu:tesla_h100:8     up
h200       ucsc-precis-h200-wn[11-17]     256      2321395+   gpu:nvidia_h200:8    up
haswell*   haswell-wn[29-42]              32       127309     (null)               up
hd         xl675dg10-wn175                96       773225     gpu:tesla_a100:10    up
ml         sprmcrogpu-wn[140-141]         112      128224     gpu:tesla_a100:2     up
sprmcrogpu sprmcrogpu-wn13                64       515093     gpu:rtx_2080ti:8     up
ucsx       ucsx-ncit-gpu-wn100            64       257158     gpu:tesla_a100:3     up
xl         xl270-wn[161-162]              56       257138     gpu:tesla_p100:2     up

Explicații coloane: CPUS = număr total de thread-uri hardware per nod, MEMORY = RAM în MB per nod, GRES = resurse generice (GPU-uri: tip și număr per nod), AVAIL = starea partiției.

Asteriscul din dreptul partiției (haswell* în cazul nostru) indică partiția implicită. Dacă nu specificați o partiție, SLURM va aloca jobul pe partiția implicită.

Nu toate partițiile sunt accesibile tuturor utilizatorilor! Puteți verifica accesul vostru folosind următoarele comenzi:

# Vedeți account-ul vostru per partiție, inclusiv timpul maxim de rulare a unui job
# Rândurile fără partiție specificată arată limitele implicite ale account-ului vostru, care se aplică pe orice partiție unde nu există o regulă specifică.
sacctmgr show user $USER withassoc format=user%20,account,partition,MaxWallDurationPerJob
 
# Exemplu:
[tudor.calafeteanu@fep10 ~]$ sacctmgr show user $USER withassoc format=user%20,account,partition,MaxWallDurationPerJob
                User    Account  Partition     MaxWall
-------------------- ---------- ---------- -----------
   tudor.calafeteanu    student               12:00:00
 
# Vedeți restricțiile unei partiții
scontrol show partition <nume_partiție> | grep -E "AllowAccounts|DenyAccounts"
 
# Exemplu:
[nume.student@fep10 ~]$ scontrol show partition ucsx | grep -E "AllowAccounts|DenyAccounts"
   AllowGroups=ALL DenyAccounts=ml,nlp,nlp_highprio,ml_test,dataset-proc AllowQos=ALL

Contul din care faceți parte la această materie (asc) are acces pe partițiile: dgxa100, dgxh100, haswell, ucsx și xl. Celelalte partiții (h200, hd, ml, sprmcrogpu) sunt rezervate unor echipe de cercetare specifice.

În SLURM, un account este o grupare administrativă de utilizatori (ex: asc, student, prof, ml). Fiecare utilizator este asociat la unul sau mai multe account-uri, iar partițiile pot restricționa accesul pe baza acestora prin AllowAccounts / DenyAccounts. Separat, AllowGroups / DenyGroups controlează accesul pe baza grupurilor Linux (cele vizibile prin comanda id). Pe clusterul UPB, toate partițiile au AllowGroups=ALL, deci accesul este controlat exclusiv prin account-uri SLURM.

Arhitectura nodurilor de calcul ale clusterului UPB

CPU

Partiție CPU Arhitectură Topologie CPU¹ NUMA nodes Frecvență core (bază; boost)
dgxh100 Intel Xeon Platinum 8480C Sapphire Rapids 2 S/N x 56 C/S x 2 T/C = 224 T/N 2 NUMA (1 per socket) 2.0 GHz; 3.8 GHz
dgxa100 AMD EPYC 7742 Zen 2 (Rome) 2 S/N x 64 C/S x 2 T/C = 256 T/N 8 NUMA (4 per socket²) 2.25 GHz; 3.4 GHz
ucsx Intel Xeon Gold 6326 Ice Lake 2 S/N x 16 C/S x 2 T/C = 64 T/N 2 NUMA (1 per socket) 2.9 GHz; 3.5 GHz
xl Intel Xeon E5-2680 v4 Broadwell 2 S/N x 14 C/S x 2 T/C = 56 T/N 2 NUMA (1 per socket) 2.4 GHz; 3.3 GHz
haswell Intel Xeon E5-2640 v3 Haswell 2 S/N x 8 C/S x 2 T/C = 32 T/N 2 NUMA (1 per socket) 2.6 GHz; 3.4 GHz

¹ Sockets/Node x Cores/Socket x Threads/Core = Threads/Node
² Pe AMD EPYC, fiecare socket expune 4 NUMA nodes datorită arhitecturii chiplet (CCD/CCX). Fiecare NUMA node conține 16 core-uri și o porțiune de memorie locală. Pe Intel, de regulă 1 socket = 1 NUMA node.

Alte informații utile (click pentru detalii)

Alte informații utile (click pentru detalii)

Toate partițiile folosesc arhitectura x86_64 cu 16 registre generale de 64 biți (RAX, RBX, …, R8–R15). Partițiile mai noi (Sapphire Rapids, Ice Lake) dispun și de 32 registre AVX-512 de 512 biți, iar Sapphire Rapids adaugă registre AMX (tile registers) pentru operații matriceale.


Ierarhia de memorie

Partiție Linie cache L1d SRAM/core L1i SRAM/core L2 SRAM/core L3 SRAM/socket DRAM/nod¹
dgxh100 64B 48 KB 32 KB 2 MB 105 MB ~2 TB DDR5-4800; 8 canale; ~307 GB/s
dgxa100 64B 32 KB 32 KB 512 KB 256 MB ~2 TB DDR4-3200; 8 canale; ~204 GB/s
ucsx 64B 48 KB 32 KB 1.25 MB 24 MB ~512 GB DDR4-3200; 4 canale; ~102 GB/s
xl 64B 32 KB 32 KB 256 KB 35 MB ~256 GB DDR4-2400; 2 canale; ~38 GB/s
haswell 64B 32 KB 32 KB 256 KB 20 MB ~128 GB DDR4-1866; 4 canale; ~60 GB/s

¹ capacitate tip; canale per socket; bandwidth teoretic per socket

Alte informații utile (click pentru detalii)

Alte informații utile (click pentru detalii)

Bandwidth-ul teoretic per socket se calculează astfel:
lățime_bandă (GB/s) = nr._canale x rată_transfer (MT/s) x lățime_magistrală_date (B/T)
De exemplu, pentru dgxh100: 8 × 4800 MT/s × 8 B/T = ~307 GB/s.


GPU

Partiție GPU Arhitectură GPUs/nod Topologie GPU¹ Tensor Cores Compute Capability Memorie GPU²
dgxh100 NVIDIA H100 80GB HBM3 Hopper 8 132 SM/GPU x 128 C/SM = 16896 C/GPU 528 (Gen 4) sm_90 80 GB HBM3; 5120-bit bus; ~3350 GB/s
dgxa100 NVIDIA A100-SXM4-80GB Ampere 8 108 SM/GPU x 64 C/SM = 6912 C/GPU 432 (Gen 3) sm_80 80 GB HBM2e; 5120-bit bus; ~2039 GB/s
ucsx NVIDIA A100-PCIE-40GB Ampere 3 108 SM/GPU x 64 C/SM = 6912 C/GPU 432 (Gen 3) sm_80 40 GB HBM2; 5120-bit bus; ~1555 GB/s
xl Tesla P100-PCIE-16GB Pascal 2 56 SM/GPU x 64 C/SM = 3584 C/GPU 0 sm_60 16 GB HBM2; 4096-bit bus; ~732 GB/s

¹ Streaming Multiprocessors (SMs)/GPU x CUDA cores/SM = CUDA cores/GPU
² capacitate tip; bus; bandwidth teoretic

Alte informații utile (click pentru detalii)

Alte informații utile (click pentru detalii)

Un Streaming Multiprocessor (SM) este unitatea fundamentală de execuție pe GPU, echivalentul unui core pe CPU. Fiecare SM conține multiple CUDA cores (pentru operații generale FP32/INT32) și Tensor Cores (pentru operații matriceale accelerate, folosite în deep learning). Toate CUDA cores dintr-un SM partajează aceleași resurse: registre, shared memory și cache L1.

Compute Capability (sm_XX) determină ce instrucțiuni și funcționalități hardware sunt disponibile. Este esențial la compilarea CUDA: nvcc -arch=sm_90 pentru H100, sm_80 pentru A100. Un binar compilat pentru un anumit compute capability nu rulează pe GPU-uri cu o versiune mai veche.


Ce este FEP-ul?

FEP-ul reprezintă nodul de acces către clusterul HPC al universității. Pe scurt, este serverul prin intermediul căruia utilizatorii se conectează la infrastructură pentru a pregăti și lansa job-urile pe resursele de calcul ale clusterului.

FEP-ul are un rol limitat, oferind:

Execuția efectivă a joburilor are loc exclusiv pe nodurile de calcul, care dispun de resursele reale de procesare (CPU, GPU, RAM extins).

Vă rugăm NU rulați aplicațiile direct PE FEP! Trimiteți-le ca joburi către cluster, folosind SLURM. FEP-ul este un nod comun de acces pentru toți utilizatorii, iar rularea de programe intensive pe el afectează toți utilizatorii conectați.

Conectarea la FEP se realizează prin SSH:

 ssh -X -o ServerAliveInterval=100 user.name@fep.grid.pub.ro 

Explicația parametrilor SSH (click pentru detalii)

Explicația parametrilor SSH (click pentru detalii)

  • -X – permite redirecționarea interfeței grafice (dacă folosiți aplicații GUI);
  • ServerAliveInterval=100 – menține conexiunea activă dacă terminalul e inactiv o perioadă.


Configurare SSH pentru conectare simplificată (click pentru detalii)

Configurare SSH pentru conectare simplificată (click pentru detalii)

Pentru o conectare mai simplă, puteți configura SSH să rețină parametrii de conectare.

1. Generați o pereche de chei SSH (dacă nu aveți deja una):

# Linux / macOS / WSL
ssh-keygen -t ed25519 -f ~/.ssh/id_fep
# Windows (PowerShell)
ssh-keygen -t ed25519 -f $env:USERPROFILE\.ssh\id_fep

2. Copiați cheia publică pe FEP:

# Linux / macOS / WSL
ssh-copy-id -i ~/.ssh/id_fep.pub user.name@fep.grid.pub.ro
# Windows (PowerShell)
type $env:USERPROFILE\.ssh\id_fep.pub | ssh user.name@fep.grid.pub.ro "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

3. Adăugați un alias în fișierul local ~/.ssh/config (Linux/macOS/WSL) sau %USERPROFILE%\.ssh\config (Windows):

Host fep fep.grid.pub.ro
    HostName fep.grid.pub.ro
    User <user.name>
    ForwardX11 yes
    IdentityFile ~/.ssh/id_fep
    ServerAliveInterval 100

De acum vă puteți conecta direct prin ssh fep.


Montarea sistemului de fișiere FEP pe calculatorul local (click pentru detalii)

Montarea sistemului de fișiere FEP pe calculatorul local (click pentru detalii)

Pentru o experiență confortabilă, recomandăm montarea sistemului de fișiere de pe FEP pe calculatorul local folosind sshfs. Astfel, puteți edita codul cu un IDE local (e.g. VS Code) și compila/rula pe cluster prin SSH:

Pregătire (o singură dată):

# Pe local, creați un director pentru mount
mkdir ~/asc_labs

Montare:

# Linux / macOS / WSL
sshfs user.name@fep.grid.pub.ro: ~/asc_labs
 
# Sau dacă ați configurat alias-ul SSH prezentat mai sus:
sshfs fep: ~/asc_labs

Demontare:

fusermount -u ~/asc_labs    # Linux / WSL
umount ~/asc_labs           # macOS

Pe Windows, puteți folosi SSHFS-Win sau Rclone pentru funcționalitate echivalentă.

Configurarea mediului de lucru pe cluster ​

Pe un cluster sunt instalate simultan mai multe versiuni de compilatoare și biblioteci (GCC, CUDA, OpenMPI, etc.). Pentru a evita conflicte și pentru a permite fiecărui utilizator să aleagă versiunile dorite, clusterele HPC folosesc Environment Modules — un sistem care modifică temporar variabilele de mediu (PATH, LD_LIBRARY_PATH) la cerere. Astfel, utilizatorul poate folosi exact versiunea dorită a unui compilator sau a unei biblioteci, fără a afecta sistemul global.

Comenzi uzuale:

module help
module avail                      # afișează modulele disponibile
module load libraries/cuda-13.0   # încarcă biblioteca CUDA corespunzătoare
module list                       # arată modulele active în sesiunea curentă
module unload libraries/cuda-13.0 # dezactivează un modul
module purge                      # dezactivează toate modulele încărcate

În prezent, modulefile-urile nu sunt disponibile pe cluster. Până la rezolvarea acestei probleme, configurarea mediului CUDA se face manual prin variabilele de mediu:

export PATH=/usr/local/cuda/bin:$PATH                          # pentru a găsi compilatorul nvcc
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH  # pentru a găsi bibliotecile CUDA la runtime

Când modulefile-urile vor fi disponibile, cele două linii de export vor fi înlocuite cu:

module load libraries/cuda-13.0

Utilizare SLURM ​

SLURM este scheduler-ul de joburi folosit pe clusterul UPB. Rolul său este să primească cererile utilizatorilor, să le pună în coadă și să aloce resursele disponibile (CPU-uri, GPU-uri, memorie) în mod echitabil. Fiecare cont are restricții per partiție: în primul rând dacă are acces sau nu, timp maxim per job (de exemplu maximum 10 minute pentru conturile voastre, după cum ați văzut mai sus), număr maxim de joburi simultane, număr maxim de noduri folosite per job, limită de RAM per job, etc.

În mod implicit, SLURM alocă 1 CPU logic și 0 GPU-uri pentru orice job. Dacă programul vostru are nevoie de mai multe resurse (mai multe CPU-uri, GPU, memorie suplimentară), trebuie să le cereți explicit. Altfel, un program care are nevoie de N thread-uri logice va rula pe 1 singur thread hardware, iar un program care are nevoie de CUDA nu va merge deloc, chiar dacă codul în sine este corect.

Lansarea joburilor: sbatch vs srun

Există două moduri de a lansa joburi pe cluster: sbatch și srun.

sbatch trimite un job în coada SLURM și returnează imediat controlul — jobul rulează asincron, în fundal. Outputul standard se salvează în fișierul specificat prin --output (implicit slurm-<JOB_ID>.out), iar erorile în --error (implicit tot în slurm-<JOB_ID>.out dacă nu e specificat separat).

[nume.student@fep10 ~]$ sbatch hello_pthreads.sh
Submitted batch job 125771
[nume.student@fep10 ~]$ cat slurm-125771.out    # output-ul apare aici după finalizare
Hello from thread 0
Hello from thread 3
Hello from thread 1
...

srun lansează comanda sincron — terminalul se blochează și așteaptă finalizarea, iar outputul apare direct în consolă. Dacă închideți terminalul, job-ul se oprește. Este util pentru testare rapidă sau sesiuni interactive (srun –pty bash).

[nume.student@fep10 ~]$ srun --cpus-per-task=8 ./hello_pthreads
Hello from thread 0
Hello from thread 3
Hello from thread 1
...
sbatch srun
Mod execuție Asincron (fundal) Sincron (blocant)
Output Fișiere .out / .err Direct în terminal
La închiderea terminalului Jobul continuă Jobul se oprește
Utilizare tipică Aplicații complexe Testare, debugging, interactiv

Exemple de joburi

Mai jos sunt două programe pe care le vom folosi pentru a înțelege cum funcționează alocarea resurselor în SLURM.

Program Pthreads hello_pthreads.c (paralelism pe CPU) (click pentru a vedea codul)

Program Pthreads hello_pthreads.c (paralelism pe CPU) (click pentru a vedea codul)

hello_pthreads.c
#include <stdio.h>
#include <pthread.h>
 
void* thread_func(void* arg) {
    int id = *(int*)arg;
    printf("Hello from thread %d\n", id);
    return NULL;
}
 
int main() {
    int N = 8;
    pthread_t threads[N];
    int ids[N];
    for (int i = 0; i < N; i++) {
        ids[i] = i;
        pthread_create(&threads[i], NULL, thread_func, &ids[i]);
    }
    for (int i = 0; i < N; i++)
        pthread_join(threads[i], NULL);
    return 0;
}


Script sbatch corespunzător:

hello_pthreads.sh
#!/bin/bash
# Nu specificăm --partition, deci SLURM va folosi partiția implicită (haswell)
#SBATCH --cpus-per-task=8    # programul creează 8 thread-uri, deci cerem 8 CPU-uri logice
gcc -o hello_pthreads hello_pthreads.c -lpthread
./hello_pthreads

Program CUDA hello_cuda.cu (paralelism pe GPU) (click pentru a vedea codul)

Program CUDA hello_cuda.cu (paralelism pe GPU) (click pentru a vedea codul)

hello_cuda.cu
#include <stdio.h>
 
__global__ void hello() {
    printf("Hello from GPU thread %d\n", threadIdx.x);
}
 
int main() {
    hello<<<1, 4>>>();
    cudaDeviceSynchronize();
    printf("CUDA test done!\n");
    return 0;
}


Script sbatch corespunzător:

hello_cuda.sh
#!/bin/bash
#SBATCH --partition=ucsx
#SBATCH --gres=gpu:1
# Configurare manuală CUDA (vezi secțiunea "Configurarea mediului de lucru")
export PATH=/usr/local/cuda/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
nvcc -o hello_cuda hello_cuda.cu
./hello_cuda

Submitere:

sbatch hello_pthreads.sh    # lansare job CPU
sbatch hello_cuda.sh        # lansare job GPU

Pentru programul Pthreads, partiția implicită (haswell) este suficientă. Pentru programul CUDA, trebuie specificată o partiție cu GPU (de exemplu ucsx).

Opțiuni avansate SLURM (click pentru detalii)

Opțiuni avansate SLURM (click pentru detalii)

SLURM oferă un control mult mai granular asupra resurselor hardware decât simpla specificare a numărului de CPU-uri sau GPU-uri. De exemplu:

  • distribuția proceselor MPI pe socket-uri (--ntasks-per-socket);
  • memory binding NUMA — procesele alocă și accesează memorie doar din nodul NUMA local, reducând latența (--cpu-bind, --mem-bind);
  • activarea sau dezactivarea SMT (--hint=nomultithread).

Aceste opțiuni devin relevante când scrieți aplicații hibride MPI+OpenMP+CUDA și doriți să vă folosiți la maximum de arhitectura hardware a nodului de calcul.


Comenzi uzuale SLURM

# === Submitere și rulare ===
sbatch hello_cuda.sh                             # submitere job; returnează un job_id
sbatch --partition=ucsx hello_cuda.sh            # override partiție din linia de comandă
srun --partition=ucsx --gres=gpu:1 ./hello_cuda  # rulare interactivă simplă
srun --partition=ucsx --gres=gpu:1 --pty bash    # sesiune interactivă pe nodul de calcul
 
# === Informații despre cluster ===
sinfo                                              # afișare simplă a partițiilor
sinfo -o "%20P %6a %8D %8c %10m %20G %10l %8t %N"  # format detaliat
sinfo -o '%9P %4c %8z %8X %8Y %8Z'                 # distribuție sockets/cores/threads per partiție
 
# === Monitorizare joburi ===
squeue                          # afișare toate joburile
squeue --me                     # afișare doar joburile tale
squeue -p ucsx --state=R --format="%.8i %.10P %.15u %.10T %.10M %.8C %.10m %.5b %.20R"  # joburi active pe o partiție
 
# === Oprire joburi ===
scancel <job_id>                # oprește un job specific
scancel --me                    # oprește TOATE joburile tale
 
# === Detalii despre resurse ===
scontrol show partition <nume>  # detalii complete despre o partiție
scontrol show node <nume>       # detalii despre un nod specific
scontrol show job <job_id>      # detalii și stare completă a unui job
 
# === Account și limite ===
sacctmgr show user $USER withassoc format=user%20,account,partition,MaxWallDurationPerJob

Exerciții

Resurse utile