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 %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.
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.
| 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.
| 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
| 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
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
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
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
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.
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 |
Mai jos sunt două programe pe care le vom folosi pentru a înțelege cum funcționează alocarea resurselor în SLURM.
Script sbatch corespunzător:
#!/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
Script sbatch corespunzător:
#!/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).
# === 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
scontrol show partition)lscpu și pe un nod GPU comanda nvidia-smi -q. Ce informații ați putut extrage despre CPU/GPU? Comparați cu datele din tabelele de mai sus.scontrol show partition <nume>)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 (hint: time ./program) și explicați diferența.