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.
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:
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ă.
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 %55N %8c %10m %20G" PARTITION NODELIST CPUS MEMORY GRES dgxa100 dgxa100-ncit-wn[01-04] 256 2063510 gpu:tesla_a100:8 dgxh100 dgxh100-precis-wn[01-03] 224 1998908+ gpu:tesla_h100:8 haswell* haswell-wn[29-42] 32 127309 (null) hd xl675dg10-wn175 96 773225 gpu:tesla_a100:10 ml sprmcrogpu-wn[140-141] 112 128224 gpu:tesla_a100:2 sprmcrogpu sprmcrogpu-wn13 64 515093 gpu:rtx_2080ti:8 ucsx ucsx-ncit-gpu-wn100 64 257158 gpu:tesla_a100:3 xl xl270-wn[161-162] 56 257138 gpu:tesla_p100:2
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ă.
Specificații CPU:
| Partiție | CPU | Arhitectura | An lansare | Sockets | Cores/Socket | Threads/Core | Total CPUs (threads) | Frecvență per core (bază / boost) | RAM/nod |
|---|---|---|---|---|---|---|---|---|---|
| dgxh100 | Intel Xeon Platinum 8480C | Sapphire Rapids | 2023 | 2 | 56 | 2 | 224 | până la 3.8 GHz | ~2 TB |
| dgxa100 | AMD EPYC 7742 | Zen 2 (Rome) | 2019 | 2 | 64 | 2 | 256 | până la 3.4 GHz | ~2 TB |
| ucsx | Intel Xeon Gold 6326 | Ice Lake | 2021 | 2 | 16 | 2 | 64 | 2.9 / 3.5 GHz | ~256 GB |
| xl | Intel Xeon E5-2680 v4 | Broadwell | 2016 | 2 | 14 | 2 | 56 | 2.4 / 3.3 GHz | ~256 GB |
| haswell | Intel Xeon E5-2640 v3 | Haswell | 2014 | 2 | 8 | 2 | 32 | 2.6 / 3.4 GHz | ~128 GB |
Specificații GPU:
| Partiție | GPU | Arhitectura | An lansare | Nuclee (cores) | Memorie (VRAM) |
|---|---|---|---|---|---|
| dgxh100 | NVIDIA H100 80GB | Hopper | 2022 | 14592 CUDA cores + 456 Tensor cores (Gen 4) | 80 GB HBM3 |
| dgxa100 | NVIDIA A100-SXM4-80GB | Ampere | 2020 | 6912 CUDA cores + 432 Tensor cores (Gen 3) | 80 GB HBM2e |
| ucsx | NVIDIA A100-PCIE-40GB | Ampere | 2020 | 6912 CUDA cores + 432 Tensor cores (Gen 3) | 40 GB HBM2e |
| xl | NVIDIA Tesla P100 16GB | Pascal | 2016 | 3584 CUDA cores | 16 GB HBM2 |
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).
Conectarea la FEP se realizează prin SSH:
ssh -X -o ServerAliveInterval=100 user.name@fep.grid.pub.ro
Explicația parametrilor:
-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ă.
Pentru o conectare mai simplă, puteți adăuga, local, în fișierul ~/.ssh/config, următorul bloc (după ce ați generat o pereche cheie privată/publică, de exemplu RSA):
Host fep fep.grid.pub.ro
HostName fep.grid.pub.ro
User <user.name>
ForwardX11 yes
IdentityFile ~/.ssh/id_fep
ServerAliveInterval 100
Apoi, copiați cheia publică în fișierul ~/.ssh/authorized_keys de pe FEP, pentru a vă putea conecta de acum direct prin ssh fep.
După conectare, utilizatorul se află pe serverul FEP.
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
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, ținând cont și de restricțiile fiecărei partiții. De exemplu, anumite partiții sunt rezervate exclusiv unor echipe de cercetare și sunt accesibile doar membrilor acestora. Altele pot impune limite de timp per job (e.g. maximum 10 minute pentru conturile de studenți), un număr maxim de job-uri rulate simulat, un număr maxim de noduri folosite per job, maxim RAM per job, etc.
Mai jos sunt două programe pe care le vom folosi pe parcursul laboratorului pentru a înțelege cum funcționează alocarea resurselor în SLURM.
Program Pthreads (paralelism pe CPU):
#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; }
Program CUDA (paralelism pe GPU):
#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; }
Pentru a rula aceste programe pe cluster, trebuie să le trimiteți ca job-uri folosind SLURM, specificând resursele necesare. De exemplu, programul CUDA va fi nevoie să îl rulăm pe o partiție care conține GPU (cum ar fi xl, ucsx), în timp ce pentru programul C este de ajuns haswell.
--ntasks-per-socket);--cpu-bind, --mem-bind);--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.
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 nu apare în terminal, ci se salvează în fișierul specificat prin --output (sau, implicit, slurm-<JOB_ID>.out). Job-ul continuă chiar dacă închideți terminalul. Este modul recomandat pentru aplcații reale, a căror rulare poat dura de la ore până la zile.
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).
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 |
Exemplu de script sbatch pentru hello_pthreads.c:
#!/bin/bash #SBATCH --cpus-per-task=8 gcc -o hello_pthreads hello_pthreads.c -lpthread ./hello_pthreads
Exemplu de script sbatch pentru hello_cuda.cu:
#!/bin/bash #SBATCH --partition=dgxa100 #SBATCH --gres=gpu:1 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
Apoi rulați sbatch hello_pthreads.sh, sbatch hello_cuda.sh pentru a submite jobul.
export PATH=… și export LD_LIBRARY_PATH=…). Când modulefile-urile vor fi disponibile, cele două linii de export vor fi înlocuite cu module load libraries/cuda-13.0.
Comenzi uzuale SLURM:
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=xl --gres=gpu:1 ./hello_cuda # rulare interactivă simplă srun --partition=xl --gres=gpu:1 --pty bash # sesiune interactivă pe nodul de calcul sinfo # afișare simplă 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 squeue # afișare joburi squeue --me # afișare doar joburile tale squeue -p xl --state=R --format="%.8i %.10P %.15u %.10T %.10M %.8C %.10m %.5b %.20R" # joburile active pe o partiție, format detaliat scancel <job_id> # oprește un job specific scancel --me # oprește TOATE joburile tale 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 sacctmgr show user $USER withassoc format=account,user,partition,MaxWallDurationPerJob # timp maxim de rulare permis per partiție
scontrol show) Cât RAM e disponibil vs. maximul permis per job (hint: scontrol show)?lscpu și pe clustere GPU comanda nvidia-smi. Ce informații ați putut extrage despre CPU/GPU?sbatch, cât și cu srun. Verificați rezultatele. Care este diferența dintre cele două comenzi?--cpus-per-task=N și o dată fără --cpus-per-task=N, unde N este numărul de thread-uri din program. Comparați timpii de execuție și explicați diferența.