Améliorations de WebAssembly et WebGPU pour une IA Web plus rapide, partie 1

Découvrez comment les améliorations de WebAssembly et de WebGPU améliorent les performances du machine learning sur le Web.

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

Inférence par IA sur le Web

Nous avons tous entendu dire que l'IA est en train de transformer le monde. Le Web ne fait pas exception.

Cette année, Chrome a ajouté des fonctionnalités d'IA générative, y compris la création de thèmes personnalisés et/ou la rédaction d'un premier brouillon de texte. Mais l'IA, c'est bien plus que cela : elle peut elle-même enrichir les applications Web.

Les pages Web peuvent intégrer des composants intelligents pour la vision, comme la reconnaissance de visages ou de gestes, pour la classification de sons ou la détection de la langue. L'année dernière, l'IA générative a décollé, avec des démonstrations vraiment impressionnantes de grands modèles de langage sur le Web. N'oubliez pas de consulter Application pratique de l'IA sur l'appareil pour les développeurs Web.

L'inférence IA sur le Web est aujourd'hui disponible sur un grand nombre d'appareils. Le traitement de l'IA peut s'effectuer dans la page Web elle-même, en exploitant le matériel de l'appareil de l'utilisateur.

Cette approche est efficace pour plusieurs raisons:

  • Réduction des coûts: l'exécution d'inférences sur le navigateur client réduit considérablement les coûts de serveur. Cela peut s'avérer particulièrement utile pour les requêtes d'IA générative, qui peuvent s'avérer beaucoup plus coûteuses que les requêtes standards.
  • Latence: pour les applications particulièrement sensibles à la latence, comme les applications audio ou vidéo, le fait que tous les traitements soient effectués sur l'appareil permet de réduire la latence.
  • Confidentialité: l'exécution côté client peut également permettre de déverrouiller une nouvelle classe d'applications qui nécessitent une confidentialité accrue et où les données ne peuvent pas être envoyées au serveur.

Comment les charges de travail d'IA s'exécutent-elles sur le Web aujourd'hui ?

Aujourd'hui, les développeurs d'applications et les chercheurs créent des modèles à l'aide de frameworks, les modèles s'exécutent dans un navigateur à l'aide d'un environnement d'exécution tel que Tensorflow.js ou ONNX Runtime Web, et les environnements d'exécution utilisent des API Web pour l'exécution.

Tous ces environnements d'exécution finissent par s'exécuter sur le processeur via JavaScript ou WebAssembly ou sur le GPU via WebGL ou WebGPU.

Diagramme illustrant l'exécution actuelle des charges de travail d'IA sur le Web

Charges de travail de machine learning

Les charges de travail de machine learning (ML) envoient des Tensors via un graphe de nœuds de calcul. Les Tensors sont les entrées et les sorties de ces nœuds, qui effectuent d'importants calculs sur les données.

C'est important pour les raisons suivantes:

  • Les Tensors sont de très grandes structures de données qui effectuent des calculs sur des modèles pouvant avoir des milliards de pondérations.
  • Le scaling et l'inférence peuvent entraîner un parallélisme des données. Cela signifie que les mêmes opérations sont effectuées sur tous les éléments des Tensors.
  • Le ML ne nécessite pas de précision. Vous aurez peut-être besoin d'un nombre à virgule flottante de 64 bits pour atterrir sur la Lune, mais vous n'aurez peut-être besoin que d'une mer de 8 bits ou moins pour la reconnaissance faciale.

Heureusement, les concepteurs de puces ont ajouté des fonctionnalités pour rendre les modèles plus rapides et plus froids, et même permettre leur exécution.

Dans le même temps, les équipes WebAssembly et WebGPU s'efforcent de présenter ces nouvelles fonctionnalités aux développeurs Web. Si vous êtes développeur d'applications Web, il est peu probable que vous utilisiez fréquemment ces primitives de bas niveau. Les chaînes d'outils ou les frameworks que vous utilisez devraient être compatibles avec les nouvelles fonctionnalités et extensions. Vous pourrez donc bénéficier d'un minimum de modifications de votre infrastructure. Toutefois, si vous préférez régler manuellement les performances de vos applications, ces fonctionnalités sont pertinentes pour votre travail.

WebAssembly

WebAssembly (Wasm) est un format d'octet compact et efficace que les environnements d'exécution peuvent comprendre et exécuter. Il est conçu pour exploiter les capacités matérielles sous-jacentes afin de pouvoir s'exécuter à des vitesses quasiment natives. Le code est validé et s'exécute dans un environnement de bac à sable sécurisé pour la mémoire.

Les informations du module Wasm sont représentées par un encodage binaire dense. Par rapport au format texte, cela se traduit par un décodage et un chargement plus rapides, ainsi qu'une utilisation de mémoire réduite. Il est portable dans le sens où il ne fait pas d'hypothèses concernant l'architecture sous-jacente qui ne sont pas déjà courantes dans les architectures modernes.

La spécification WebAssembly est itérative et a été développée par un groupe de la communauté W3C.

Le format binaire ne fait aucune hypothèse concernant l'environnement hôte, il est donc conçu pour bien fonctionner également dans les représentations vectorielles continues non Web.

Votre application peut être compilée une seule fois et s'exécuter partout: ordinateur de bureau, ordinateur portable, téléphone ou tout autre appareil doté d'un navigateur. Pour en savoir plus, consultez la section Un seul code pour exécuter n'importe où avec WebAssembly.

Illustration d'un ordinateur portable, d'une tablette et d'un téléphone

La plupart des applications de production qui exécutent l'inférence IA sur le Web utilisent WebAssembly, aussi bien pour le calcul du processeur que pour l'interface avec des calculs à usage spécifique. Sur les applications natives, vous pouvez accéder à des calculs à des fins générales et spéciales, car l'application peut accéder aux fonctionnalités de l'appareil.

Sur le Web, pour des raisons de portabilité et de sécurité, nous évaluons soigneusement l'ensemble de primitives exposées. Cela permet de trouver un juste milieu entre l'accessibilité du Web et les performances maximales du matériel.

WebAssembly est une abstraction portable de processeurs, de sorte que toute l'inférence Wasm est exécutée sur le processeur. Bien que ce ne soit pas le choix le plus performant, les processeurs sont largement disponibles et fonctionnent pour la plupart des charges de travail et sur la plupart des appareils.

Pour les petites charges de travail, telles que les charges de travail textuelles ou audio, le GPU serait coûteux. Il existe un certain nombre d'exemples récents pour lesquels Wasm est le bon choix:

Vous pouvez en apprendre encore davantage dans les démos Open Source, par exemple whisper-tiny, llama.cpp et Gemma2B running in the browser (Gemma2B s'exécutant dans le navigateur).

Adopter une approche holistique de vos applications

Vous devez choisir des primitives en fonction du modèle de ML, de l'infrastructure d'application et de l'expérience globale des utilisateurs dans l'application

Par exemple, pour la détection des points de repère de visage de MediaPipe, l'inférence CPU et l'inférence GPU sont comparables (s'exécutant sur un appareil Apple M1), mais dans certains modèles, la variance peut être considérablement plus élevée.

En ce qui concerne les charges de travail de ML, nous tenons compte d'une vue globale des applications, tout en écoutant les auteurs de frameworks et nos partenaires d'applications, afin de développer et de proposer les améliorations les plus demandées. Ils appartiennent généralement à l'une des trois catégories suivantes:

  • Exposez les extensions de processeur essentielles aux performances
  • Activer l'exécution de modèles plus volumineux
  • Assurer une interopérabilité fluide avec d'autres API Web

Calcul plus rapide

En l'état, la spécification WebAssembly n'inclut qu'un ensemble spécifique d'instructions que nous mettons à disposition sur le Web. Toutefois, le matériel continue d'ajouter des instructions plus récentes qui augmentent l'écart entre les performances natives et WebAssembly.

N'oubliez pas que les modèles de ML n'exigent pas toujours des niveaux de précision élevés. La proposition SIMD assouplie est une proposition qui réduit certaines des exigences strictes de non-déterminisme, ce qui permet d'accélérer le générateur de code pour certaines opérations vectorielles qui sont des points chauds de performances. De plus, la solution Suge SIMD introduit de nouvelles instructions pour les produits scalaires et les instructions FMA, qui permettent de multiplier les charges de travail existantes par 1,5 à 3. Cet article a été expédié dans Chrome 114.

Le format à virgule flottante à demi-précision utilise 16 bits pour IEEE FP16, au lieu des 32 bits utilisés pour les valeurs de précision simple. Par rapport aux valeurs de précision simple, l'utilisation de valeurs de demi-précision présente plusieurs avantages, les besoins en mémoire réduits, ce qui permet d'entraîner et de déployer des réseaux de neurones plus importants et de réduire la bande passante mémoire. La précision réduite accélère le transfert de données et les opérations mathématiques.

Modèles plus volumineux

Les pointeurs dans la mémoire linéaire Wasm sont représentés par des entiers 32 bits. Cela a deux conséquences: la taille des tas de mémoire est limitée à 4 Go (lorsque les ordinateurs ont beaucoup plus de RAM physique) et le code d'application qui cible Wasm doit être compatible avec une taille de pointeur de 32 bits (ce qui est le cas).

Charger ces modèles dans WebAssembly peut s'avérer restrictif, en particulier pour les modèles volumineux comme ceux que nous utilisons aujourd'hui. La proposition Memory64 élimine ces restrictions grâce à la capacité de la mémoire linéaire, qui est supérieure à 4 Go, et qui correspond à l'espace d'adressage des plates-formes natives.

La mise en œuvre de cette fonctionnalité dans Chrome est entièrement opérationnelle, et elle devrait être disponible dans le courant de l'année. Pour le moment, vous pouvez effectuer des tests avec l'indicateur chrome://flags/#enable-experimental-webassembly-features et nous envoyer vos commentaires.

Meilleure interopérabilité Web

WebAssembly pourrait être le point d'entrée pour des calculs spécifiques sur le Web.

WebAssembly peut être utilisé pour transférer des applications GPU sur le Web. Cela signifie que la même application C++ pouvant s'exécuter sur l'appareil peut également s'exécuter sur le Web, avec de légères modifications.

Emscripten, la chaîne d'outils du compilateur Wasm, dispose déjà de liaisons pour WebGPU. Il s'agit du point d'entrée pour l'inférence IA sur le Web. Il est donc essentiel que Wasm puisse interagir de manière fluide avec le reste de la plate-forme Web. Nous travaillons sur quelques propositions différentes dans ce domaine.

Intégration de la promesse JavaScript (JSPI)

Les applications C et C++ classiques (ainsi que de nombreux autres langages) sont généralement écrites avec une API synchrone. Cela signifie que l'application arrête son exécution jusqu'à la fin de l'opération. Ces applications de blocage sont généralement plus intuitives à écrire que les applications asynchrones.

Lorsque des opérations coûteuses bloquent le thread principal, elles peuvent bloquer les E/S, et les utilisateurs peuvent voir les à-coups. Il existe une incohérence entre un modèle de programmation synchrone d'applications natives et le modèle asynchrone du Web. Cela est particulièrement problématique pour les anciennes applications, dont le portage serait coûteux. Emscripten offre un moyen d'y parvenir avec Asyncify, mais ce n'est pas toujours la meilleure option : elle utilise une plus grande taille de code et moins efficace.

L'exemple suivant calcule fibonacci à l'aide de promesses JavaScript d'addition.

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

Dans cet exemple, soyez attentif aux points suivants:

  • La macro EM_ASYNC_JS génère tout le code glue nécessaire pour que nous puissions utiliser la technologie JSPI pour accéder au résultat de la promesse, comme elle le ferait pour une fonction normale.
  • L'option de ligne de commande spéciale -s ASYNCIFY=2 Cette option permet d'appeler l'option permettant de générer du code qui utilise une interface JSP pour communiquer avec les importations JavaScript qui renvoient des promesses.

Pour en savoir plus sur la technologie JSPI, son utilisation et ses avantages, consultez l'article sur la présentation de l'API d'intégration JavaScript Promise de WebAssembly sur v8.dev. En savoir plus sur la phase d'évaluation en cours.

Contrôle de la mémoire

Les développeurs ont très peu de contrôle sur la mémoire Wasm ; le module possède sa propre mémoire. Toutes les API ayant besoin d'accéder à cette mémoire doivent effectuer une copie ou une sortie, ce qui peut s'accumuler. Par exemple, une application graphique peut avoir besoin d'effectuer des copier-coller pour chaque image.

La proposition de contrôle de la mémoire vise à fournir un contrôle plus précis de la mémoire linéaire Wasm et à réduire le nombre de copies dans le pipeline d'application. Cette proposition est en phase 1. Nous la prototypons dans V8, le moteur JavaScript de Chrome, afin d'éclairer l'évolution de la norme.

Choisir le backend le plus adapté à vos besoins

Bien que les processeurs soient omniprésents, ce n'est pas toujours la meilleure option. Les calculs à usage spécifique sur le GPU ou les accélérateurs peuvent offrir des performances bien plus élevées, en particulier pour les modèles plus volumineux et les appareils haut de gamme. Cela est vrai pour les applications natives comme pour les applications Web.

Le backend que vous choisissez dépend de l'application, du framework ou de la chaîne d'outils, ainsi que d'autres facteurs qui influencent les performances. Cela dit, nous continuons d'investir dans des propositions qui permettent à Wasm de fonctionner correctement avec le reste de la plate-forme Web, et plus particulièrement avec WebGPU.

Poursuivez la lecture de la partie 2