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

Découvrez comment les améliorations apportées à WebAssembly et 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 IA sur le Web

Nous savons tous que l'IA transforme notre monde. Le Web ne fait pas exception.

Cette année, Chrome a ajouté des fonctionnalités d'IA générative, comme la création de thèmes personnalisés et/ou l'aide à la rédaction d'une première ébauche de texte. Mais l'IA est bien plus que cela : L'IA peut elles-mêmes enrichir les applications Web.

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

L'inférence IA sur le Web est actuellement disponible sur un grand nombre d'appareils, et le traitement de l'IA peut s'effectuer directement sur la page Web, en exploitant le matériel présent sur l'appareil de l'utilisateur.

Cette approche est efficace pour plusieurs raisons:

  • Coûts réduits: l'exécution d'inférences sur le client de navigateur réduit considérablement les coûts du serveur. Cela peut être particulièrement utile pour les requêtes d'IA générative, qui peuvent être bien plus coûteuses que les requêtes classiques.
  • Latence: pour les applications particulièrement sensibles à la latence, comme les applications audio ou vidéo, le traitement des données sur l'appareil permet de réduire la latence.
  • Confidentialité: l'exécution côté client permet également de débloquer une nouvelle catégorie d'applications nécessitant une confidentialité accrue, dans lesquelles les données ne peuvent pas être envoyées au serveur.

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

Aujourd'hui, les développeurs et les chercheurs d'applications créent des modèles à l'aide de frameworks. Les modèles s'exécutent dans le 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.

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) transmettent les Tensors via un graphe de nœuds de calcul. Les Tensors sont les entrées et les sorties de ces nœuds qui effectuent une grande quantité de calculs sur les données.

Ce point 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 qui peuvent 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 nombres de 8 bits ou moins pour la reconnaissance faciale.

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

En attendant, au sein des équipes WebAssembly et WebGPU, nous nous efforçons de faire découvrir ces nouvelles fonctionnalités aux développeurs Web. Si vous êtes développeur d'applications Web, il est peu probable que ces primitives de bas niveau soient fréquemment utilisées. Les chaînes d'outils ou les frameworks que vous utilisez devraient être compatibles avec de nouvelles fonctionnalités et extensions. Vous pouvez donc bénéficier de modifications minimes à votre infrastructure. Mais si vous souhaitez régler manuellement les performances de vos applications, ces fonctionnalités sont pertinentes pour votre travail.

WebAssembly

WebAssembly (Wasm) est un format de bytecode 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 presque à une vitesse native. Le code est validé et s'exécute dans un environnement de bac à sable à mémoire sécurisée.

Les informations du module Wasm sont représentées par un encodage binaire dense. Par rapport à un format texte, cela se traduit par un décodage et un chargement plus rapides, ainsi que par une utilisation réduite de la mémoire. 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à communes aux architectures modernes.

La spécification WebAssembly est itérative et est élaborée au sein d'un groupe communautaire W3C ouvert.

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

Votre application peut être compilée une fois et exécutée partout: ordinateur de bureau, ordinateur portable, téléphone ou tout autre appareil doté d'un navigateur. Pour en savoir plus, consultez l'article Écrire une seule fois, 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 des inférences par IA sur le Web utilisent WebAssembly, à la fois pour le calcul du processeur et pour l'interfaçage avec des opérations de calcul spéciales. Sur les applications natives, vous pouvez accéder à des ressources de calcul à usage général et à usage spécifique, 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 équilibre entre l'accessibilité du Web et les performances maximales du matériel.

WebAssembly est une abstraction portable des processeurs. Toutes les inférences Wasm sont donc exécutées sur le processeur. Bien que ce ne soit pas le choix le plus performant, les processeurs sont largement disponibles et fonctionnent sur la plupart des charges de travail et sur la plupart des appareils.

Pour les charges de travail plus petites, telles que les charges de travail textuelles ou audio, le GPU est coûteux. Voici quelques exemples récents dans lesquels Wasm est la solution idéale:

Vous pouvez en apprendre davantage dans les démos Open Source, telles que whisper-tiny, llama.cpp et Gemma2B s'exécutant dans le navigateur.

Adoptez une approche holistique de vos applications

Vous devez choisir des primitives en fonction du modèle de ML spécifique, de l'infrastructure d'application et de l'expérience applicative globale prévue pour les utilisateurs.

Par exemple, dans la détection de points de repère faciales de MediaPipe, l'inférence sur le processeur et l'inférence GPU sont comparables (exécution sur un appareil Apple M1), mais il existe des modèles où la variance peut être nettement supérieure.

Pour les charges de travail de ML, nous envisageons une vue globale de l'application, tout en écoutant les auteurs de frameworks et les partenaires d'application, afin de développer et de déployer les améliorations les plus demandées. Ceux-ci se répartissent généralement en trois catégories:

  • Exposer 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 certain ensemble d'instructions que nous présentons au 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 ne nécessitent pas toujours des niveaux élevés de précision. La proposition DIMD assoupli est une proposition qui réduit certaines des exigences strictes de non-déterminisme, ce qui accélère la génération de code pour certaines opérations vectorielles qui constituent des points chauds pour les performances. De plus, la version simplifiée du SIMD introduit de nouveaux produits scalaires et instructions FMA qui accélèrent les charges de travail existantes de 1,5 à 3 fois. 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 unique, l'utilisation de valeurs à 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 grands, et de réduire la bande passante mémoire. Une 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 de 32 bits. Cela a deux conséquences: les tailles de tas de mémoire sont limitées à 4 Go (lorsque les ordinateurs ont beaucoup plus de RAM physique que cela) et le code d'application qui cible Wasm doit être compatible avec une taille de pointeur 32 bits (qui).

Le chargement de ces modèles dans WebAssembly peut être restrictif, en particulier avec les grands modèles tels que ceux que nous avons aujourd'hui. La proposition Memory64 supprime ces restrictions par la mémoire linéaire, qui est alors supérieure à 4 Go et correspond à l'espace d'adressage des plates-formes natives.

La mise en œuvre dans Chrome est entièrement opérationnelle, et sa mise en service devrait être lancée d'ici la fin de l'année. Pour l'instant, vous pouvez effectuer des tests avec l'indicateur chrome://flags/#enable-experimental-webassembly-features et nous envoyer des commentaires.

Meilleure interopérabilité Web

WebAssembly pourrait être le point d'entrée pour des calculs à usage spécifique 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 un 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. C'est le point d'entrée de l'inférence IA sur le Web. Wasm doit donc impérativement pouvoir interagir de manière transparente avec le reste de la plate-forme Web. Nous travaillons sur plusieurs propositions différentes dans ce domaine.

Intégration de promesses JavaScript (JSPI)

Les applications C et C++ classiques (ainsi que de nombreux autres langages) sont généralement écrites sur une API synchrone. Cela signifie que l'application arrêtera l'exécution jusqu'à ce que l'opération soit terminée. L'écriture de ces applications de blocage est généralement plus intuitive que les applications asynchrones.

Lorsque des opérations coûteuses bloquent le thread principal, elles peuvent bloquer les E/S et les à-coups sont visibles par les utilisateurs. Il existe une incohérence entre un modèle de programmation synchrone d'applications natives et le modèle Web asynchrone. Cela est particulièrement problématique pour les anciennes applications, dont le transfert serait coûteux. Emscripten permet d'effectuer cette opération avec Asyncify, mais ce n'est pas toujours la meilleure option. En effet, la taille du code est plus importante et ce n'est pas aussi efficace.

L'exemple suivant illustre le calcul de fibonacci, en utilisant des promesses JavaScript pour des additions.

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, prêtez attention aux points suivants:

  • La macro EM_ASYNC_JS génère tout le code glue nécessaire pour nous permettre d'utiliser 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 commande appelle l'option permettant de générer du code qui utilise le système JSPI pour interagir avec les importations JavaScript qui renvoient des promesses.

Pour en savoir plus sur la page JSPI, son utilisation et ses avantages, consultez l'article Introducing the WebAssembly JavaScript Promise Integration API on v8.dev (Présentation de l'API d'intégration WebAssembly JavaScript Promise 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 qui ont besoin d'accéder à cette mémoire doivent effectuer une copie vers ou vers l'extérieur, et cette utilisation peut vraiment s'accumuler. Par exemple, une application graphique peut avoir besoin d'effectuer des copies pour chaque image.

La proposition de contrôle de la mémoire vise à fournir un contrôle plus précis sur la mémoire linéaire Wasm et à réduire le nombre de copies dans le pipeline d'application. Cette proposition est en phase 1. Elle est en cours de prototypage dans V8, le moteur JavaScript de Chrome, afin de faire évoluer la norme.

Déterminer le backend adapté à vos besoins

Bien que le processeur soit omniprésent, ce n'est pas toujours la meilleure option. Les calculs spéciaux 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 valable aussi bien pour les applications natives que 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 efficacement avec le reste de la plate-forme Web, et plus particulièrement avec WebGPU.

Poursuivre la lecture de la partie 2