Visualizzazione astratta di equazioni delle acque basse (shallow water equations) che vengono processate simultaneamente su diverse architetture hardware (CPU, GPU, FPGA simboli luminosi) collegate da flussi di dati interconnessi, rappresentando la portabilità delle prestazioni con SYCL. Wide-angle lens, 15mm, sharp focus, long exposure effect on data streams, sfondo blu scuro tecnologico.

SYCL: La Chiave Magica per Far Volare il Codice Scientifico su Ogni Hardware?

Ciao a tutti! Oggi voglio parlarvi di una sfida affascinante nel mondo del calcolo scientifico ad alte prestazioni (HPC): come far girare lo stesso codice in modo efficiente su hardware completamente diversi. Immaginate di avere un modello complesso, come quelli per prevedere il meteo o le correnti oceaniche, e di volerlo eseguire sia su classiche CPU, sia su potentissime GPU, sia su chip specializzati come gli FPGA. Non è un’impresa da poco!

Ed è qui che entra in gioco SYCL. Ne avete mai sentito parlare? È uno standard aperto, basato sul moderno C++, che promette di aiutarci proprio in questo: scrivere codice una volta sola e farlo girare su piattaforme eterogenee. Bello, vero? Ma la domanda è: funziona davvero? E soprattutto, le prestazioni sono buone ovunque, o è solo una bella favola?

Nel nostro lavoro, abbiamo deciso di mettere alla prova SYCL su un problema concreto e importante: la simulazione delle equazioni delle acque basse (shallow water equations, o SWE) in 2D. Queste equazioni sono fondamentali per modellare la circolazione dell’acqua in laghi, mari e oceani, e sono spesso usate come base per modelli climatici e meteorologici tridimensionali molto più complessi.

La Sfida: Acque Basse e Hardware Eterogeneo

Le equazioni delle acque basse descrivono come variano nel tempo l’elevazione della superficie dell’acqua e la velocità orizzontale integrata sulla profondità. La nostra implementazione usa un metodo numerico chiamato “Galerkin discontinuo” (DG) su mesh triangolari non strutturate – immaginate una rete di triangoli che copre l’area di interesse, come una baia o una porzione di oceano. Questo metodo ci permette di usare diversi ordini di accuratezza (da P0, il più semplice, a P2, più preciso).

Il problema è che l’hardware HPC è diventato incredibilmente vario. Nei supercomputer più potenti (la famosa lista TOP500), quasi tutti usano GPU di diversi produttori (Nvidia, AMD, Intel). Ci sono CPU basate su architetture diverse (x86 e ARM). E poi ci sono gli FPGA (Field-Programmable Gate Array), chip che possiamo “programmare” a livello hardware per compiti specifici. Far funzionare bene lo stesso codice su tutta questa varietà è il Sacro Graal della performance portability.

Il Nostro Approccio: SYCL e Ottimizzazioni Mirate

Abbiamo quindi preso un codice esistente per le SWE (chiamato UTBEST, originariamente in Fortran/C) e lo abbiamo riscritto usando SYCL. L’idea di SYCL è quella di avere un unico sorgente C++ che possa essere compilato per diverse architetture. Abbiamo usato due compilatori principali: Intel oneAPI e AdaptiveCpp (l’evoluzione di hipSYCL), che ci hanno permesso di targettizzare CPU x86 (Intel, AMD), CPU ARM (Nvidia Grace), GPU di tutti e tre i grandi produttori, e persino FPGA Intel Stratix 10.

Ma attenzione: SYCL garantisce la portabilità della piattaforma (il codice compila e gira), non automaticamente la portabilità delle prestazioni (gira velocemente ovunque). Per ottenere buone performance, abbiamo dovuto rimboccarci le maniche. La chiave è stata separare la gestione degli accessi alla memoria dal codice numerico.

Questo ci ha permesso di adottare strategie diverse a seconda dell’hardware:

  • Per CPU e GPU, abbiamo scoperto che un layout di memoria chiamato “struct of arrays” (SoA) funziona meglio. Immaginate di avere i dati organizzati per colonne invece che per righe: questo aiuta le unità di calcolo parallelo a caricare i dati più efficientemente.
  • Per gli FPGA, invece, il layout originale “array of structs” (AoS) era più adatto, perché gli FPGA lavorano in modo diverso, spesso processando tutti i dati di un “elemento” (un triangolo della nostra mesh) contemporaneamente.

Abbiamo implementato delle classi C++ “accessorie” che nascondono questi dettagli al codice numerico principale, rendendolo indipendente dal layout di memoria sottostante. Un trucco simile lo usa anche Kokkos, un altro framework popolare per la portabilità.

Visualizzazione astratta 3D di diversi layout di memoria, uno 'array of structs' (blocchi compatti) e uno 'struct of arrays' (colonne separate di dati), con frecce che indicano i pattern di accesso per CPU/GPU vs FPGA. Macro lens, 85mm, high detail, controlled lighting, sfondo tecnologico.

Altre ottimizzazioni hanno riguardato la riduzione degli accessi “sparpagliati” alla memoria (che sono lenti) duplicando alcune informazioni o ricalcolando valori al volo invece di leggerli dalla memoria principale. Questo ha richiesto un delicato bilanciamento: a volte un’ottimizzazione che aiuta le GPU può penalizzare leggermente le CPU! Abbiamo cercato un compromesso che funzionasse ragionevolmente bene ovunque, mantenendo il codice il più unificato possibile.

Per gli FPGA, abbiamo fatto un passo in più. Questi chip non hanno le cache gerarchiche come CPU e GPU. Quindi, o accedi alla lenta memoria principale, o usi la memoria interna (on-chip). Abbiamo implementato due strategie:

  1. Cache personalizzate: Abbiamo creato delle cache software nella memoria on-chip per i dati più usati. Se la mesh è piccola e sta tutta nella cache, le performance sono fantastiche! Se è grande, la cache aiuta solo per una parte dei dati.
  2. FPGA con HBM: Abbiamo testato anche FPGA dotati di High Bandwidth Memory (HBM), una memoria molto veloce e con tanti canali. Qui abbiamo dovuto riorganizzare i dati in modo certosino per distribuirli sui vari canali HBM e massimizzare il parallelismo degli accessi. Niente cache custom, ma tanta banda passante.

I Risultati: Performance su Tutta la Linea (con qualche sorpresa)

Abbiamo testato il nostro codice SYCL su due scenari realistici: la Baia di Abaco (Bahamas) e la Baia di Galveston (Texas), usando mesh di dimensioni diverse, da poche migliaia a milioni di triangoli.

E i risultati? Beh, sono stati davvero interessanti! (Potete vedere un riassunto nel grafico originale, Figura 5 dell’articolo).

  • Le GPU (Nvidia H100, AMD MI210) sono state le regine delle performance, specialmente con mesh grandi, dove possono sprigionare tutta la loro potenza parallela.
  • Le CPU (Intel Xeon Sapphire Rapids, AMD EPYC Genoa-X, Nvidia Grace ARM) hanno mostrato buone performance, scalando bene con il numero di core, anche se generalmente inferiori alle GPU. Qui abbiamo notato differenze significative a seconda del compilatore (oneAPI vs AdaptiveCpp) e del “backend” usato (OpenMP vs OpenCL). Ad esempio, AdaptiveCpp con OpenMP scalava meglio su AMD e Nvidia Grace, mentre oneAPI con OpenCL era più performante su Intel. Questo dimostra che la portabilità delle performance richiede anche attenzione alla toolchain!
  • Gli FPGA si sono rivelati dei complementi interessanti. La versione con cache (su Stratix 10 GX) era imbattibile sulle mesh più piccole, superando tutti gli altri! La versione con HBM (su Stratix 10 MX) offriva prestazioni molto costanti indipendentemente dalla dimensione della mesh, circa 2-3 volte migliori della versione con cache quando questa esauriva la sua capacità.

Grafico stilizzato che mostra le curve di performance (DOF/s) per CPU (linea tratteggiata), GPU (linea continua) e FPGA (linea tratto-punto) al variare della dimensione del problema. GPU domina per problemi grandi, FPGA cached eccelle per piccoli, FPGA HBM è costante. Wide-angle lens, 20mm, sharp focus, colori vivaci.

Un aspetto cruciale emerso è il compromesso tra codice unificato e ottimizzazione estrema. Ad esempio, la scelta di ricalcolare certi valori (le “proiezioni locali”) invece di leggerli dalla memoria ha aiutato molto GPU e FPGA, ma ha leggermente penalizzato le CPU con backend OpenMP. Abbiamo scelto la via del codice unificato, ma è importante essere consapevoli di questi trade-off.

Non Solo Velocità: L’Efficienza Energetica

Oggi non basta andare veloci, bisogna farlo consumando poca energia. Abbiamo quindi misurato anche l’efficienza energetica (DOF per Joule) delle varie architetture.

I risultati sono stati netti:

  • Le GPU sono state fino a 10 volte più efficienti delle CPU server di ultima generazione! Consumano, certo, ma producono molti più risultati per ogni Watt speso.
  • Gli FPGA hanno mostrato un’efficienza energetica paragonabile a quella delle CPU (la versione HBM) o addirittura superiore per le mesh piccole (la versione con cache), grazie al loro basso consumo energetico.

Questo è un dato fondamentale, specialmente per simulazioni climatiche che girano per giorni o settimane sui supercomputer.

Infografica comparativa dell'efficienza energetica (DOF/Joule) di CPU, GPU e FPGA. La barra della GPU è molto più alta delle altre. Icone stilizzate per ogni architettura. Prime lens, 35mm, depth of field, colori dominanti verde (efficienza) e grigio.

Portabilità Reale: Dal Data Center al Notebook

Ma la vera prova del nove della portabilità è: il codice gira anche su hardware comune, non solo sui bestioni da data center? Abbiamo provato! Abbiamo eseguito la simulazione della Baia di Galveston su:

  • CPU da workstation (Intel i9, Apple M1)
  • CPU da notebook (AMD Ryzen)
  • GPU integrate (Intel UHD)
  • GPU consumer (Nvidia RTX 3070, AMD RX 6900 XT)
  • GPU da data center più “piccole” (Nvidia L4)

Ebbene sì, il nostro codice SYCL ha girato ovunque senza modifiche! Ovviamente le performance variavano, ma la funzionalità era garantita. Le GPU consumer hanno mostrato performance vicine a quelle delle sorelle maggiori da data center, e la piccola Nvidia L4 si è distinta per un’ottima efficienza energetica grazie al suo basso TDP e alla cache L2 capiente.

Conclusioni: SYCL Mantiene la Promessa?

Allora, SYCL è la chiave magica? Direi che è uno strumento potentissimo, che rende la portabilità della piattaforma una realtà. Scrivere un codice complesso come il nostro e vederlo girare su CPU x86/ARM, GPU di 3 vendor e FPGA è già un risultato notevole.

La portabilità delle prestazioni è raggiungibile, ma richiede lavoro. Non basta scrivere codice SYCL generico; bisogna pensare alle specificità dell’hardware, soprattutto per la memoria, e usare astrazioni (come le nostre classi per i layout di memoria) per isolare queste ottimizzazioni. Bisogna anche essere consapevoli delle differenze tra compilatori e backend.

In sintesi, SYCL ci permette di avere una base di codice comune robusta, su cui poi innestare specializzazioni mirate per tirar fuori il meglio da ogni architettura. Le GPU restano le campionesse di performance ed efficienza per problemi grandi, ma gli FPGA offrono un’alternativa valida e complementare, specialmente per certi regimi di utilizzo o per la loro costanza.

Il futuro? Ci sono tante direzioni: esplorare parallelismo più avanzato (task graph), scalare su più nodi, provare altri metodi numerici o aggiungere fisica più complessa. SYCL sembra una base solida su cui costruire. È stata un’avventura affascinante esplorare questo mondo, e spero di avervi trasmesso un po’ della nostra curiosità!

Fonte: Springer

Articoli correlati

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *