Miglioramenti di WebAssembly e WebGPU per un'IA web più veloce, parte 1

Scopri come i miglioramenti di WebAssembly e WebGPU migliorano le prestazioni del machine learning sul web.

Austin Eng
Austin Eng
Deepti Gandluri
Deepti Gandluri
François Beaufort
François Beaufort

Inferenza IA sul web

Tutti ne hanno sentito parlare: l'IA sta trasformando il mondo. Il Web non fa eccezione.

Quest'anno Chrome ha aggiunto funzionalità di IA generativa, tra cui la creazione di temi personalizzati e/o l'aiuto a scrivere una prima bozza di testo. Ma l'IA è molto più di questo: L'IA può arricchire autonomamente le applicazioni web.

Le pagine web possono incorporare componenti intelligenti per la vista, come il riconoscimento dei volti o dei gesti, la classificazione audio o il rilevamento della lingua. Nell'ultimo anno, abbiamo visto decollare l'IA generativa, comprese alcune demo davvero impressionanti dei modelli linguistici di grandi dimensioni sul web. Dai un'occhiata al documento sull'IA pratica sul dispositivo per sviluppatori web.

L'inferenza IA sul web è oggi disponibile su un'ampia sezione di dispositivi e l'elaborazione dell'IA può avvenire nella pagina web stessa, sfruttando l'hardware del dispositivo dell'utente.

Si tratta di una funzionalità potente per diversi motivi:

  • Costi ridotti: l'esecuzione dell'inferenza sul client del browser riduce notevolmente i costi del server e questo può essere particolarmente utile per le query GenAI, che possono essere ordini di grandezza più costosi rispetto alle query normali.
  • Latenza: per le applicazioni particolarmente sensibili alla latenza, come applicazioni audio o video, l'esecuzione dell'intera elaborazione sul dispositivo riduce la latenza.
  • Privacy: l'esecuzione sul lato client consente inoltre di sbloccare una nuova classe di applicazioni che richiedono una maggiore privacy, in cui i dati non possono essere inviati al server.

Come vengono eseguiti oggi i carichi di lavoro di IA sul web

Oggi gli sviluppatori di applicazioni e i ricercatori creano modelli utilizzando framework, i modelli vengono eseguiti nel browser con un runtime come Tensorflow.js o ONNX Runtime Web e i runtime utilizzano le API web per l'esecuzione.

Tutti questi runtime alla fine finiscono per essere eseguiti sulla CPU tramite JavaScript o WebAssembly oppure sulla GPU tramite WebGL o WebGPU.

Diagramma di come vengono eseguiti oggi i carichi di lavoro di IA sul web

Carichi di lavoro di machine learning

I carichi di lavoro di machine learning (ML) eseguono il push dei tensori attraverso un grafico di nodi computazionali. I tensori sono gli input e gli output di questi nodi che eseguono una grande quantità di calcolo sui dati.

Questo è importante perché:

  • I tensori sono strutture di dati molto grandi che eseguono calcoli su modelli che possono avere miliardi di ponderazioni.
  • La scalabilità e l'inferenza possono portare al parallelismo dei dati. Ciò significa che vengono eseguite le stesse operazioni su tutti gli elementi dei tensori.
  • L'ML non richiede precisione. Potrebbe essere necessario un numero in virgola mobile a 64 bit per atterrare sulla luna, ma potrebbe essere necessario solo un mare di numeri a 8 bit o meno per il riconoscimento facciale.

Fortunatamente, i progettisti di chip hanno aggiunto funzionalità per far funzionare i modelli più velocemente e con meno frequenza, e persino per eseguirli del tutto.

Nel frattempo, qui tra i team WebAssembly e WebGPU, stiamo lavorando per rendere disponibili queste nuove funzionalità agli sviluppatori web. Se sei uno sviluppatore di applicazioni web, è improbabile che utilizzi spesso queste primitive di basso livello. Prevediamo che le catene di strumenti o i framework che utilizzi supporteranno nuove funzionalità ed estensioni, consentendoti di trarre vantaggio con modifiche minime all'infrastruttura. Ma se vuoi ottimizzare manualmente le prestazioni delle tue applicazioni, queste funzionalità sono rilevanti per il tuo lavoro.

WebAssembly

WebAssembly (Wasm) è un formato di byte code compatto ed efficiente che i runtime possono comprendere ed eseguire. È progettata per sfruttare le funzionalità hardware sottostanti, in modo da poter essere eseguita a velocità quasi native. Il codice viene convalidato ed eseguito in un ambiente con sandbox e sicuro per la memoria.

Le informazioni del modulo Wasm sono rappresentate con una codifica binaria densa. Rispetto a un formato basato su testo, ciò si traduce in una decodifica più rapida, un caricamento più rapido e una riduzione dell'utilizzo della memoria. È portabile nel senso che non fa ipotesi sull'architettura sottostante che non sono già comuni alle architetture moderne.

La specifica WebAssembly è iterativa e viene elaborata in un gruppo della community W3C aperto.

Il formato binario non fa ipotesi sull'ambiente host, quindi è progettato per funzionare bene anche negli incorporamenti non web.

L'applicazione può essere compilata una volta ed essere eseguita ovunque: un computer, un laptop, un telefono o qualsiasi altro dispositivo dotato di browser. Per saperne di più, dai un'occhiata a Scrivi una volta ed esegui ovunque grazie a WebAssembly.

Illustrazione di un laptop, un tablet e uno smartphone

La maggior parte delle applicazioni di produzione che eseguono l'inferenza IA sul web utilizzano WebAssembly, sia per il calcolo della CPU che per l'interfaccia con computing per scopi speciali. Nelle applicazioni native puoi accedere sia al computing per uso generico sia a quello per scopi speciali, in quanto l'applicazione può accedere alle funzionalità del dispositivo.

Sul web, per la portabilità e la sicurezza, valutiamo attentamente quale insieme di primitive è esposto. Questo approccio consente di bilanciare l'accessibilità del web con le prestazioni massime fornite dall'hardware.

WebAssembly è un'astrazione portatile delle CPU, quindi tutta l'inferenza Wasm viene eseguita sulla CPU. Sebbene questa non sia la scelta più performante, le CPU sono ampiamente disponibili e funzionano per la maggior parte dei carichi di lavoro, sulla maggior parte dei dispositivi.

Per carichi di lavoro più piccoli, come carichi di lavoro di testo o audio, la GPU sarebbe costosa. Di recente ci sono diversi esempi in cui Wasm è la scelta giusta:

Puoi trovarne ancora di più nelle demo open source, ad esempio: whisper-tiny, llama.cpp e Gemma2B in esecuzione nel browser.

Adotta un approccio completo alle applicazioni

Dovresti scegliere le primitive in base al particolare modello ML, all'infrastruttura applicativa e all'esperienza applicativa complessiva prevista per gli utenti

Ad esempio, nel rilevamento dei punti di riferimento del volto di MediaPipe, l'inferenza della CPU e l'inferenza GPU sono paragonabili (in esecuzione su un dispositivo Apple M1), ma ci sono modelli in cui la varianza potrebbe essere significativamente maggiore.

Per quanto riguarda i carichi di lavoro ML, prendiamo in considerazione una visione olistica delle applicazioni, ascoltando gli autori dei framework e i partner di applicazione per sviluppare e distribuire i miglioramenti più richiesti. Queste rientrano a grandi linee in tre categorie:

  • Esporre le estensioni della CPU critiche per le prestazioni
  • Abilita l'esecuzione di modelli più grandi
  • Consenti un'interoperabilità perfetta con altre API web

Calcolo più veloce

Allo stato attuale, le specifiche di WebAssembly includono solo un determinato insieme di istruzioni che vengono esposte sul web. Tuttavia, l'hardware continua ad aggiungere istruzioni più recenti che aumentano il divario tra le prestazioni native e di WebAssembly.

Ricorda che i modelli ML non richiedono sempre alti livelli di precisione. SIM rilassato è una proposta che riduce alcuni dei rigidi requisiti non deterministici, portando a un codegen più rapido per alcune operazioni vettoriali che sono aree critiche per le prestazioni. Inoltre, Confortevole SIMD introduce un nuovo prodotto dot e istruzioni FMA che accelerano i carichi di lavoro esistenti da 1,5 a 3 volte. Questa funzionalità è stata spedita in Chrome 114.

Il formato in virgola mobile a mezza precisione utilizza 16 bit per IEEE FP16 anziché i 32 bit utilizzati per i valori di precisione singola. Rispetto ai singoli valori di precisione, esistono diversi vantaggi nell'utilizzo di valori di mezza precisione, requisiti di memoria ridotti, che consentono l'addestramento e il deployment di reti neurali più grandi e una larghezza di banda della memoria ridotta. Una precisione ridotta velocizza il trasferimento di dati e le operazioni matematiche.

Modelli più grandi

I puntatori nella memoria lineare Wasm sono rappresentati come numeri interi a 32 bit. Ciò ha due conseguenze: le dimensioni dello heap sono limitate a 4 GB (quando i computer hanno molta più RAM fisica) e il codice dell'applicazione destinato a Wasm deve essere compatibile con una dimensione di puntatore a 32 bit (che).

Il caricamento di questi modelli in WebAssembly può essere restrittivo, soprattutto con i modelli di grandi dimensioni come quelli che abbiamo oggi. La proposta Memory64 rimuove queste limitazioni in quanto la memoria lineare è superiore a 4 GB e corrisponde allo spazio di indirizzi delle piattaforme native.

Abbiamo un'implementazione completamente funzionante in Chrome e si stima che verrà spedita entro la fine dell'anno. Per ora, puoi eseguire esperimenti con il flag chrome://flags/#enable-experimental-webassembly-features e inviarci feedback.

Migliore interoperabilità web

WebAssembly potrebbe essere il punto di accesso per il computing per scopi speciali sul web.

WebAssembly può essere utilizzato per portare le applicazioni GPU sul web. Ciò significa che la stessa applicazione C++ che può essere eseguita sul dispositivo può essere eseguita anche sul web, con piccole modifiche.

Emscripten, la toolchain del compilatore Wasm, ha già associazioni per WebGPU. È il punto di accesso per l'inferenza IA sul web, quindi è fondamentale che Wasm possa interagire perfettamente con il resto della piattaforma web. Stiamo lavorando a un paio di diverse proposte in questo ambito.

Integrazione della promessa JavaScript (JSPI)

Le tipiche applicazioni C e C++ (così come molti altri linguaggi) vengono comunemente scritte su un'API sincrona. Ciò significa che l'esecuzione dell'applicazione verrà arrestata fino al completamento dell'operazione. Queste applicazioni di blocco sono generalmente più intuitive da scrivere rispetto a quelle asincrone.

Quando operazioni costose bloccano il thread principale, possono bloccare l'I/O e il jank è visibile agli utenti. Esiste una mancata corrispondenza tra il modello di programmazione sincrona delle applicazioni native e il modello asincrono del web. Questo è particolarmente problematico per le applicazioni legacy, che sarebbero costose da trasferire. Emscripten offre un modo per farlo con Asyncify, ma non è sempre l'opzione migliore, perché richiede codice più grande e meno efficiente.

L'esempio seguente mostra il calcolo di fibonacci, utilizzando le promesse JavaScript per l'aggiunta.

long promiseFib(long x) {
 if (x == 0)
   return 0;
 if (x == 1)
   return 1;
 return promiseAdd(promiseFib(x - 1), promiseFib(x - 2));
}
// promise an addition
EM_ASYNC_JS(long, promiseAdd, (long x, long y), {
  return Promise.resolve(x+y);
});
emcc -O3 fib.c -o b.html -s ASYNCIFY=2

In questo esempio, presta attenzione a quanto segue:

  • La macro EM_ASYNC_JS genera tutto il glue codice necessario in modo da poter utilizzare JSPI per accedere al risultato della promessa, proprio come per una funzione normale.
  • L'opzione speciale della riga di comando, -s ASYNCIFY=2. Questo richiama l'opzione per generare codice che utilizza JSPI per interfacciarsi con importazioni JavaScript che restituiscono promesse.

Per saperne di più su JSPI, su come utilizzarlo e sui suoi vantaggi, leggi Introduzione all'API WebAssembly JavaScript Promise Integration su v8.dev. Scopri di più sull'attuale prova dell'origine.

Controllo della memoria

Gli sviluppatori hanno pochissimo controllo sulla memoria Wasm. il modulo possiede una propria memoria. Tutte le API che devono accedere a questa memoria devono essere copiate o copiate e questo utilizzo può essere davvero sommato. Ad esempio, un'applicazione di grafica potrebbe dover copiare e inserire elementi in ogni frame.

La proposta Controllo della memoria mira a fornire un controllo più granulare sulla memoria lineare Wasm e a ridurre il numero di copie nella pipeline dell'applicazione. Questa proposta è nella fase 1. Stiamo eseguendo la prototipazione nella versione V8, il motore JavaScript di Chrome, per supportare l'evoluzione dello standard.

Scegli il backend più adatto a te

Sebbene la CPU sia onnipresente, non è sempre l'opzione migliore. Il calcolo per scopi speciali sulla GPU o sugli acceleratori può offrire prestazioni di ordini di grandezza superiori, soprattutto per i modelli più grandi e sui dispositivi di fascia alta. Questo vale sia per le applicazioni native che per le applicazioni web.

Quale backend scegli dipende dall'applicazione, dal framework o dalla toolchain, nonché da altri fattori che influenzano le prestazioni. Detto questo, continuiamo a investire in proposte che consentano a Wasm di funzionare correttamente con il resto della piattaforma web e più specificamente con WebGPU.

Continua a leggere la Parte 2