Vai al contenuto
LF Lomazzi Federico

Home/Appunti/Intelligenza artificiale

Intelligenza artificiale

Mojo, sarà il futuro della Edge AI? Decisamente potrebbe.

Lomazzi Federico 10 min di lettura

Mojo potrebbe diventare il linguaggio capace di portare l’AI dal prototipo al prodotto reale, perchè ha una sintassi vicina a Python ma produce codice nativo, supporta SIMD, calcolo vettoriale e parallelismo con prestazioni già molto vicine al C++. La vera svolta (per lui) arriverà solo se estenderà la compilazione ad ARM, microcontrollori, NPU e acceleratori Edge. Allora maker, robotica e droni potrebbero passare dalla sperimentazione pura all’industrializzazione senza dover poi riscrivere tutto in C++...

C'è un problema del quale si parla relativamente poco quando si discute di Intelligenza Artificiale, soprattutto in ambiente maker, universitario e prototipale. Abbiamo una generazione intera che ormai considera Python quasi la lingua naturale con la quale avvicinarsi all'AI perchè grossomodo lo stack è questo: si impara Python, si apre PyTorch, si scarica un modello, si collega una telecamera e nel giro di poco tempo qualche cosa si muove e si muove anche benino grazie ormai all'aiuto delle AI. Si riconosce un oggetto anche in situazioni complessi, si classifica un'immagine e poi prende una decisione complessa. Ormai la sperimentazione corre 10 volte più rapida di quando AI non c'era. Poi però arriva il momento di togliere la roba dal tavolo ed è lì che spesso cominciano i problemi. Perchè poi non è che l'incredibile drone della mia startup lo posso fare volare con sopra una Jetson e chissà che altro host (pesante), giusto? Non so se vi ritrovate in questo. Ma alla fine quando c'è da fare il salto dal prototipo alla preserie ed è li che si deve lavorare sodo per cambiare la piattaforma mantenendo però il prezioso know-how con tutti gli obiettivi raggiunti; quindi la mainbaord etx deve diventare magari un modulo Halo che ha consumi di 2W, e che assiste ad un controller ARM. Ma qui cambia la solfe perchè qualcuno deve prendere tutto e sostanzialmente girarlo in C++. Ecco, Mojo potrebbe diventare interessante proprio in questo punto e non perché sia un "Python più veloce", definizione che trovo riduttiva, ma perché tenta una cosa molto più ambiziosa ossia conservare buona parte della familiarità sintattica di Python e portarla dentro un linguaggio compilato, capace di produrre codice macchina nativo e di ragionare esplicitamente in termini di memoria, SIMD, calcolo vettoriale, GPU e hardware eterogeneo. Non so ancora se ci riuscirà fino in fondo ma vedo in questo lavoro una strada intrapresa che secondo me continuerà nella direzione giusta, e le conseguenze potrebbero andare ben oltre il mondo dei server AI, dove Python è la lingua franca dell'ecosistema. Chi oggi studia machine learning, sperimenta con LLM, computer vision o robotica incontra normalmente Python molto prima di C++ e questo è perfettamente comprensibile; un programma Python si legge con relativa facilità, permette di ottenere velocemente dei risultati e dispone di un ecosistema sterminato. Il problema è che Python, da solo, non è particolarmente veloce nel calcolo numerico intensivo. Ovvero quando scriviamo:

for i in range(...):

...

e dentro quel ciclo pretendiamo di fare elaborare milioni o miliardi di valori, presto arriviamo al limite, al collo di bottiglia. Naturalmente Python aggira brillantemente il problema (da anni) con NumPy, OpenCV, PyTorch e compagnia chantant che fanno eseguire il lavoro pesante a librerie native scritte in C++, CUDA e altri linguaggi compilati. In pratica il programma è Python, ma il calcolo vero non lo è. Ecco, Mojo parte proprio da qui.

A prima vista può sembrare Python:

def somma(a: Int, b: Int) -> Int:

return a + b

ma il modello di esecuzione è completamente diverso perchè Mojo è compilato e arriva direttamente al codice macchina nativo (su Linux richiede attualmente una toolchain C per la fase di linking e supporta ufficialmente distribuzioni con glibc 2.34 o successiva, come Ubuntu 22.04 e superiori). Questa è una differenza enorme.

Per il fatto che Python è stato anteposto a C++ per ovvi motivi di curva di apprendimento e di un mercato che và sempre troppo in fretta, si è dovuto accettare quasi come inevitabile questo percorso: Python --> Prototipo --> funziona? SI --> Acceleriamolo e rimpiccioliamo tutto! --> C / C++ / CUDA...

Il problema quindi non è soltanto quello di dover imparare un altro linguaggio, ma di cambiare mentalità, toolchain, gestione della memoria, debugging per riscrivere quello che già si è fatto.

Mojo tenta invece questo : Python-like (Mojo) --> codice nativo

Naturalmente, specialmente quando si inzierà a spremere davvero la macchina, compariranno concetti che Python normalmente nasconde, come i puntatori, le memoria unsafe, layout, SIMD, parallelismo, ma il salto resta decisamente meno traumatico rispetto al passaggio diretto verso C++, anche perchè via via che le versioni di Mojo si avvicenderanno sarà carico del gruppo di sviluppo rendere certe strutture distrattive sempre più gestite nel modo migliore da Mojo stesso. E qui cosa diventa ancora più interessante per l'AI, perchè in Mojo SIMD, FMA e calcolo vettoriale non sono un'aggiunta posticcia ma è Mojo stesso che nasce con il calcolo numerico intensivo fra i suoi obiettivi principali. Permette di lavorare esplicitamente con SIMD, di utilizzare FMA, di parallelizzare il lavoro sui core e di programmare GPU NVIDIA, AMD e Apple Silicon. La toolchain supporta inoltre build eterogenee nelle quali il codice host CPU e quello destinato all'acceleratore vengono generati nello stesso processo di compilazione.

Questo significa che una semplice operazione C = A × B può essere affrontata non soltanto come una serie di moltiplicazioni scalari, ma lavorando contemporaneamente su più valori e distribuendo il carico su più core.

Ed è esattamente quello che ho voluto provare; forte dell'aiuto di Gemini (sennò mi ci passavo il pomeriggio a realizzare dei benchmark..) ho voluto creare non il solito tester simile a quello che normalmente si fà, ma qualcosa di un po' più centrato ed esoso, ovvero un programma scritto, compilato ed eseguito su un normale VPS e poi ho provato ad eseguire la medesima versione di test Mojo (ma proprio uguale identica nonostante le sintassi) sia contro Node.js e sia contro C++. Ho utilizzato un mio VPS per il test, VPS che dispone di 16 vCPU AMD EPYC Milan, supporto AVX2 e FMA. Il test è volutamente semplice ma molto intesivo come la moltiplicazione di due matrici 1024×1024 in Float64. Consideriamo che parliamo di circa 2.147.483.648 operazioni floating point per OGNI moltiplicazione. Niente librerie, quindi niente BLAS, NumPy, esterne, nemmeno GPU. Dati iniziali deterministici e ogni implementazione deve produrre la stessa somma di controllo finale, somma di controllo che deve essere 60397977600. Così siamo ancora più sicuri che tutte e 3 le versioni eseguano gli stessi calcoli tensori. Sono partito dal test che ritengo più interessante che è il multicore, perchè ormai il mercato controller entry-level a salire è multicore. Node.js è stato eseguito con 16 worker_threads e SharedArrayBuffer, facendo addirittura il warm-up del JIT prima di iniziare la misurazione. Mojo utilizzava 16 worker CPU, SIMD a 256 bit su quattro Float64 alla volta e FMA.

Per verificare che il compilatore non stesse semplicemente facendo finta di capire le nostre intenzioni, mi sono testato tutte e tre le compilazioni aprendo l'eseguibile con objdump ed ho visto che dentro c'era realmente:

vfmadd213pd ymm1,ymm0,...

quindi FMA vettoriale hardware sui registri YMM a 256 bit..

Poi ho fatto scrivere l'equivalente C++ dando come mandatory usare espressamente AVX2,FMA e 16 std::thread. Ho infatti compilato con "-O3 -march=native -mavx2 -mfma"

I risultati mediani sono stati questi:

ImplementazioneTempoPrestazioni
-----------------------------------:------------:
Node.js 24.19 — 16 worker0,23287 s9,22 GFLOP/s
Mojo 1.0 — SIMD + 16 worker0,05046 s42,56 GFLOP/s
C++ — AVX2/FMA + 16 thread0,03172 s67,70 GFLOP/s

Quindi, nel mio test:

Mojo è risultato circa 4,6 volte più veloce di Node.js.

C++ è ancora davanti, e non di poco: circa 1,6 volte più veloce di Mojo.

Ma personalmente è proprio quest'ultimo dato che trovo interessante, perchè non mi aspettavo che un linguaggio così giovane, con una sintassi che a prima vista potrebbe essere mostrata tranquillamente a uno studente Python, arrivasse già a questo livello e soprattutto considerando che C++ beneficia di decenni di lavoro su compilatori, ottimizzazioni, intrinsics ed ecosistema.

Mojo ha ancora moltissima strada davanti, ma vedete anche voi che promette bene?

C'è poi un altro aspetto che per il mondo maker secondo me non va sottovalutato, ed è che Mojo produce un vero eseguibile nativo; compilato un banale Hello World e Linux lo identifica come:

ELF 64-bit LSB pie executable, x86-64

Quindi non uno script travestito da eseguibile, ma puro codice nativo. L'eseguibile standard mantiene attualmente alcune dipendenze dinamiche dal runtime Mojo, quindi non è sufficiente prendere il singolo file e copiarlo ciecamente su qualsiasi Linux ma anche qui mi ha stuzzicato un esperimento: ho raccolto l'eseguibile e le relative librerie runtime in una singola directory:

programma/

├── eseguibile

├── run.sh

└── lib/

ed ho modificato il RUNPATH affinché cercasse le dipendenze nella folder relativa all'eseguibile: funziona alla grande, trasportato su altro VPS con Ubuntu 20 ha girato al primo colpo, senza peccare di dipendenze. Ho alzato ulteriormente l'asticella per vedere se il sistema regg,; ho compilato su con glibc 2.39 e nuovamente l'ho trasferito su un vecchio Ubuntu 20.04 MA con glibc 2.31, come mi aspettavo, ovviamente, non partiva. Ma in questo caso ho semplicemente inserito nella cartella anche il loader dinamico e la glibc della macchina di compilazione, lasciando completamente intatta quella del sistema ospite, ed ha funzionato immediatamente. Questo non significa che Mojo produca "magicamente" un singolo ELF statico universale, ovviamente non lo fa, ma significa che già adesso è possibile costruire una piccola directory autosufficiente contenente programma e runtime e trasferirla su un'altra distribuzione Linux compatibile con l'architettura, senza installare Mojo, Pixi, quindi senza dover modificare il sistema ospite, e questo per molte applicazioni embedded Linux e prototipali è una caratteristica estremamente interessante!

Il vero problema, attuale, di Mojo deve ancora essere risolto è che dovrà girare e girare su quante più piattaforme possibile, perchè oggi la cross-compilazione in MOJOS esiste ma è ancora (dichiaratamente) un "work in progress", e benchè il compilatore sia già in grado di produrre object file e assembly per target differenti, compreso AArch64, per ottenere un eseguibile completamente cross-compilato è ancora necessario utilizzare un linker esterno della piattaforma destinazione. Secondo me e su questo particolare non da poco che si decide se Mojo rimarrà un ottimo linguaggio AI/HPC oppure diventerà qualche cosa di molto più grosso.

Immaginiamo che domani la copertura diventi x86-64,ARM64,RISC-V,Cortex-A/M,STM32,DSP,GPU,NPU,acceleratori AI...e chi lo fermerebbe più? Tutti con Mojo! Con HAL,GPIO, SPI,I²C,UART,DMA,interrupt e tutto ciò che serve realmente per costruire hardware ed a quel punto cambierebbe completamente il gioco, perchè il problema del maker inizia proprio quando il prototipo funziona, e per una dimostrazione va benissimo, ma come ho già scritto sopra se devo costruire un prodotto piccolo, economico, a lunga autonomia e magari venderne migliaia, mettere a bordo un secondo computer general-purpose relativamente grosso e vorace di energia diventa tutto molto meno interessante. Usando roba "leggera" ma potente (Hailo-8, per esempio, raggiunge 26 TOPS ed esiste in moduli M.2 estremamente piccoli) potrei immaginare, come maker, di arrivare direttamente a fare un prototipo camera --> piccolo SoC ARM --> ↓

Hailo-8L --> inferenza AI --> flight controller --> motori anzichè, come ora, camera --> Jetson (completa) --> flight controller. Vero che un Hailo non sostituisce l'host, ma meglio ancora, perchè quell'host con MOJO lo cambio da una Jetson ad un modulo ARM enormemente più piccolo ed efficiente. Qui è dove Mojo (maturo) potrebbe diventare devastante, facendo passare dalla scrivania alla preserie senza riscrivere tutto. I giovani maker che sono bravissimi in Python, e conoscono in maniera notevole PyTorch, i modelli di computer vision, LLM e sistemi di inferenza, troverebbero in MOJO la rivoluzione vera e propria. Ovviamente non voglio dire il destino di Mojo sia sostituire Python, questo no, anche perchè Python continuerà probabilmente a essere fantastico per sperimentare. Penso che C++ resterà difficilissimo da scalzare proprio dove decenni di ottimizzazione e controllo assoluto dell'hardware fanno la differenza, ed il mio stesso benchmark lo dimostra perchè C++ ha vinto, ma Mojo potrebbe infilarsi esattamente nello spazio che esiste fra i due, perchè ha tutte le carte in regola : rapidità di sviluppo,

ecosistema AI, facilità di apprendimento. Mojo può davvero calamitare milioni di persone che oggi arrivano al mondo AI attraverso Python. Mojo oggi non è ancora questo ma possiede già quegli ingredienti più difficili da aggiungere dopo e potrebbe diventare il linguaggio con il quale una generazione cresciuta con Python comincerà finalmente a costruire le macchine sulle quali gira DAVVERO la propria AI.

134 visite

Fonti

  1. documentazione generale ufficiale di Mojo mojolang.org
  2. documentazione sul tipo SIMD e sul calcolo vettoriale in Mojo mojolang.org
  3. documentazione sui tipi numerici e sulle caratteristiche SIMD di Mojo mojolang.org
  4. documentazione sulla compilazione, sui target e sulla cross-compilation in Mojo mojolang.org
  5. documentazione sul comando `mojo build` e sulle opzioni del compilatore mojolang.org
  6. requisiti di sistema, piattaforme supportate, glibc e supporto hardware di Mojo mojolang.org
  7. documentazione di riferimento su `std::thread` in C++ en.cppreference.com
  8. documentazione generale sulla concorrenza e il multithreading in C++ en.cppreference.com
  9. documentazione GCC sulle opzioni di ottimizzazione, incluso `-O3` gcc.gnu.org
  10. documentazione GCC sulle opzioni x86 come `-march=native`, AVX2 e caratteristiche specifiche della CPU gcc.gnu.org

Commenti

Nessun commento, per ora.

Per commentare serve un accesso. Qui siamo tutti tecnici e colleghi!

Accedi per commentare

Scarica