- Afin de réduire les lourdes dépendances et la charge de déploiement des stacks d’inférence centrées sur Python, une application Llama2 en Rust+Wasm est empaquetée dans un binaire de 2 Mo et exécutée sur plusieurs appareils
- L’implémentation adapte llama.cpp à Wasm et exploite les fichiers de modèle GGUF, le plugin GGML de WasmEdge et l’API WASI NN pour tirer parti de l’accélération matérielle locale
- L’exemple utilise un modèle GGUF quantifié en 5 bits de Llama-2-7B-Chat, avec des performances d’environ 25 tokens/s sur un MacBook M2 d’entrée de gamme et 50 tokens/s sur une Nvidia A10G
- Le runtime WasmEdge peut être utilisé avec des outils de conteneurisation, ce qui facilite le déploiement du même binaire Wasm sur des appareils edge, en on-premise et dans le cloud
- Les outils GGML sont déjà disponibles mais encore à un stade précoce : il reste à ajouter davantage de plugins matériels et OS, la prise en charge de plus de configurations de llama.cpp, ainsi que le support de l’API WASI NN dans plusieurs langages compatibles Wasm
Exécuter l’inférence Llama2 avec Rust+Wasm
- La stack Rust+Wasm peut servir d’alternative à Python pour l’inférence IA
- Une application Rust+Wasm peut être 100 fois plus petite que son équivalent Python et jusqu’à 100 fois plus rapide, tout en utilisant l’accélération matérielle sur plusieurs environnements sans modifier le même code binaire
- Second State a créé un programme Rust simple qui exécute l’inférence du modèle Llama2 à vitesse native
- L’application binaire compilée en Wasm fait 2 Mo
- Cette application Wasm est portable entre des appareils dotés d’accélérateurs matériels hétérogènes
- Le runtime WasmEdge fournit un environnement d’exécution sécurisé dans le cloud
- WasmEdge peut être utilisé avec des outils de conteneurisation pour orchestrer et exécuter des applications portables sur plusieurs appareils
Basé sur llama.cpp et GGUF
Procédure d’exécution
- Installer WasmEdge et le plugin GGML sur une machine Linux ou Mac
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/… | bash -s -- --plugins wasmedge_rustls wasi_nn-ggml
- Télécharger l’application Wasm précompilée
curl -LO https://github.com/LlamaEdge/LlamaEdge/…
- L’exemple utilise un fichier GGUF du modèle Llama2 7B chat-tuned quantifié en 5-bit weights
curl -LO https://huggingface.co/second-state/Llama-2-7B-Chat-GGUF/…
- Exécuter l’application d’inférence Wasm avec WasmEdge en lui passant le modèle GGUF permet ensuite de saisir des questions en mode interactif
wasmedge --dir .:. --nn-preload default:GGML:AUTO:Llama-2-7b-chat-hf-Q5_K_M.gguf llama-chat.wasm
Réglages du modèle et exemples de performances
llama-chat.wasm permet de définir l’interaction avec le modèle via des options en ligne de commande
--ctx-size : taille du contexte du prompt, 512 par défaut
--n-predict : nombre de tokens à générer, 1024 par défaut
--n-gpu-layers : nombre de couches exécutées sur le GPU, 100 par défaut
--batch-size : taille de batch pour le traitement du prompt, 512 par défaut
--temp : température d’échantillonnage, 0,8 par défaut
--repeat-penalty : pénalité de répétition des tokens, 1,1 par défaut
--prompt-template : prend en charge llama-2-chat, codellama-instruct, mistral-instruct-v0.1, chatml, deepseek-chat, deepseek-coder, etc.
--log-stat : affiche les statistiques
- Exemple d’exécution avec une longueur de contexte de 2048, une réponse limitée à 512 tokens et l’affichage des statistiques
wasmedge --dir .:. --nn-preload default:GGML:AUTO:Llama-2-7b-chat-hf-Q5_K_M.gguf llama-chat.wasm -c 2048 -n 512 --log-stat
- Sur un MacBook M2 d’entrée de gamme, la réponse du LLM est diffusée en streaming par défaut et générée à environ 25 tokens/s
- Un exemple de log affiche 25.64 tokens/s sur 82 exécutions selon
eval time
kv self size est affiché à 1024.00MB et compute buffer total size à 630.14MB
- Sur une Nvidia A10G, l’exécution atteint environ 50 tokens/s
- L’accélération GPU CUDA est utilisée et 35/35 couches sont déportées sur le GPU
- L’utilisation de la VRAM est indiquée à 4474.93MB pour le modèle, 1648.02MB pour le contexte, soit 6122.95MB au total
- Un exemple de log affiche 50.55 tokens/s sur 83 exécutions selon
eval time
Serveur d’API compatible OpenAI et applications LLM
- Un serveur d’API compatible OpenAI construit avec Rust et WasmEdge est également proposé
- Ce serveur permet de créer des agents et applications LLM avec des outils de développement compatibles OpenAI comme flows.network
- Des explications associées sont disponibles dans Build a super lightweight AI agent
Les limites des stacks d’inférence Python
- Les LLM comme Llama2 sont généralement entraînés avec des frameworks basés sur Python tels que PyTorch, Tensorflow et JAX
- Mais pour les applications d’inférence, qui représenteraient environ 95 % des usages de calcul IA, Python serait désavantagé
- Les packages Python peuvent devenir difficiles à configurer et à utiliser à cause de dépendances complexes
- Les images Docker Python ou PyTorch atteignent souvent plusieurs Go, voire des dizaines de Go, ce qui pèse lourd pour l’inférence IA sur serveurs ou appareils edge
- Python est bien plus lent que des langages compilés comme C, C++ ou Rust, jusqu’à 35 000 fois dans certains cas
- La majorité des workloads réels est en pratique déléguée à des bibliothèques partagées natives sous un wrapper Python
- Cette architecture convient bien aux démos, mais serait difficile à modifier en profondeur pour répondre à des besoins métier spécifiques
- La forte dépendance aux bibliothèques natives et la gestion complexe des dépendances nuisent à la portabilité des programmes IA Python lorsqu’il s’agit d’exploiter les capacités matérielles propres à chaque appareil
- Il existe aussi des conflits de versions de
pydantic entre des packages Python courants dans la chaîne d’outils LLM
llama-cpp-python exige pydantic 2.0.1 et ne fonctionne pas avec <=2.0
fastapi et chromadb exigent pydantic 1.9.2 et ne fonctionnent pas avec >=2.0
Les avantages de Rust+Wasm
- La stack Rust+Wasm peut servir d’infrastructure unifiée de cloud computing couvrant les appareils, l’edge cloud, les serveurs on-premise et le cloud public
- Pour les applications d’inférence IA, elle peut constituer une alternative solide à la stack Python
-
Ultra-léger
- L’application d’inférence fait 2 Mo avec toutes ses dépendances
- Cela représente moins de 1 % de la taille d’un conteneur PyTorch classique
-
Exécution rapide
- Elle peut atteindre des vitesses natives C/Rust pour le prétraitement, le calcul tensoriel et le post-traitement
-
Portabilité
- La même application en bytecode Wasm s’exécute sur les principales plateformes de calcul
- L’accélération matérielle hétérogène est également prise en charge
-
Simplicité de configuration, de développement et de déploiement
- Les dépendances complexes sont réduites
- Il est possible de compiler un unique fichier Wasm sur un laptop avec des outils standard, puis de le déployer dans plusieurs environnements
-
Sécurité et préparation au cloud
- Le runtime Wasm est conçu pour isoler du code utilisateur non fiable
- Il peut être géré avec des outils de conteneurisation et déployé sur des plateformes cloud-native
Structure du programme d’inférence Rust
- Le programme de démonstration d’inférence est écrit en Rust puis compilé en Wasm
- Le code source Rust central tient en environ 40 lignes
- Le programme Rust remplit les fonctions suivantes
- gérer l’entrée utilisateur
- suivre l’historique de conversation
- convertir le texte vers le template de chat Llama2
- exécuter l’inférence via l’API WASI NN
- Pour compiler soi-même, il faut installer le compilateur Rust et la cible de compilation
wasm32-wasi
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-wasi
- Ensuite, télécharger le projet source et construire le fichier Wasm avec
cargo
git clone https://github.com/second-state/llama-utils
cd llama-utils/chat/
cargo build --target wasm32-wasi --release
cp target/wasm32-wasi/release/llama-chat.wasm .
Déploiement cloud et edge
- Une fois le fichier bytecode Wasm disponible, il peut être déployé sur tout appareil prenant en charge le runtime WasmEdge
- À ce jour, le plugin GGML prend en charge generic Linux et Ubuntu Linux
- CPU x86 et ARM
- GPU Nvidia
- Apple M1/M2/M3
- Le plugin GGML de WasmEdge s’appuie sur llama.cpp pour exploiter automatiquement l’accélération matérielle de l’appareil
- Si un GPU Nvidia est présent, l’installateur installe automatiquement le plugin GGML optimisé pour CUDA
- Le plugin GGML pour Mac OS utilise l’API Metal pour exécuter les workloads d’inférence sur le neural processing engine intégré des M1/M2/M3
- Le build Linux CPU utilise OpenBLAS pour détecter et exploiter automatiquement les fonctions de calcul modernes du CPU comme AVX et SIMD
- L’objectif de cette approche est d’assurer la portabilité entre matériels et plateformes IA hétérogènes sans sacrifier les performances
Travaux à venir
- Les outils GGML de WasmEdge sont déjà disponibles et utilisés par des clients cloud-native, mais restent à un stade précoce
- Les domaines ouverts aux contributions sont les suivants
- ajout de plugins GGML pour davantage de matériels et de plateformes OS
- intérêt pour les TPU, les NPU ARM et les puces IA spécialisées sous Linux et Windows
- prise en charge de davantage de réglages de llama.cpp
- actuellement, seule une partie des options de configuration peut être transmise depuis Wasm vers le plugin GGML
- l’objectif est de prendre en charge toutes les options exposées par GGML
- support de l’API WASI NN dans d’autres langages compatibles Wasm
- intérêt pour Go, Zig, Kotlin, JavaScript, C et C++
Prise en charge de modèles au-delà des LLM
- WasmEdge et WASI NN peuvent aussi servir d’alternative légère, rapide, portable et sécurisée à Python pour créer des applications d’inférence basées sur d’autres modèles IA populaires que les LLM
- mediapipe-rs fournit une API Rust+Wasm pour la famille de modèles Tensorflow mediapipe de Google
- WasmEdge YOLO est un projet d’API Rust+Wasm pour manipuler le modèle PyTorch YOLOv8
- WasmEdge ADAS demo montre un exemple de segmentation de route pour voiture autonome avec des modèles Intel OpenVINO
- WasmEdge Document AI doit fournir une API Rust+Wasm pour une famille de modèles d’OCR et de traitement documentaire
- Les discussions et contributions autour de WasmEdge peuvent se faire sur le Discord WasmEdge
1 commentaires
Commentaires sur Hacker News
Rust et WASM, c’est bien, mais quand on regarde le code, il ne s’agit que d’un simple script en ligne de commande Rust de 150 lignes
Le gros du travail est effectué par une seule ligne qui transmet le modèle au backend WASI-NN, fourni ici par le runtime WasmEdge
Mais WasmEdge est en C++, pas en Rust, donc dans ce cas les avantages apportés par Rust sont quasi nuls, et le backend peut aussi être appelé depuis d’autres langages comme Python
Regrouper Python et PyTorch dans un format que l’utilisateur final peut lancer d’un double-clic reste aujourd’hui encore proche du cauchemar, et même si le code réellement haute performance est en C++ dans les deux cas, si on peut finir avec un seul exécutable de 2 MB au lieu de déployer plus de 2 GB et des dizaines de milliers de fichiers juste pour envoyer quelques commandes à ce C++, ça a du sens
C’est un travail impressionnant. Mais ceux qui veulent l’essayer doivent toujours télécharger des fichiers de poids volumineux
En substance, c’est une version de llama.cpp entièrement portable et sans dépendances en 2 MB
Pour un développeur d’app, c’est peut-être le moyen le plus simple d’empaqueter le moteur d’inférence dans un fichier distribuable. Les poids sont déjà portables et peuvent être téléchargés au besoin, donc la partie qu’on veut vraiment figer, c’est le moteur d’inférence
Comme tu l’as dit, les poids écrasent complètement cette taille
maincompilé par llama.cpp fait 1.2 MBLa taille de 2 MB n’a rien de particulièrement impressionnant en soi ; l’essentiel, c’est le ciblage wasm qui le rend plus portable, pas le fait qu’il soit spécialement plus compact
En gros, ce n’est pas juste un emballage autour de llama.cpp ? Franchement, je commence à en avoir assez des projets qui enveloppent
x.cppJe développe depuis 6 mois un framework de machine learning Rust + WebGPU, et j’ai vite compris à quel point le travail de GG est impressionnant
C’est encore tôt, mais on peut voir ça ici :
https://www.ratchet.sh/
https://github.com/FL33TW00D/whisper-turbo
Le wasm-nn dont cela dépend, à savoir https://github.com/WebAssembly/wasi-nn, est une proposition qui consiste à envoyer des blocs arbitraires à une implémentation fournie par un éditeur. En pratique, l’API se résume à définir les entrées, lancer le calcul et définir les sorties
Donc ce n’est absolument pas portable
Si cela fonctionne, c’est parce que ça repose sur l’abstraction déjà implémentée dans llama.cpp. On voit ici que WasmEdge reprend tel quel ce code qui mappe les modèles gguf vers différents matériels : https://github.com/WasmEdge/WasmEdge/tree/master/plugins/was...
Par conséquent, des explications du type « les développeurs peuvent utiliser des bindings pour écrire des applis de machine learning dans un langage de haut niveau, les compiler en WebAssembly, puis les exécuter sur un runtime compatible wasi-nn comme WasmEdge » sont totalement fausses. En réalité, on ne peut pas faire ça
Ce n’est ni portable, ni sandboxé, ni une couche d’abstraction matérielle
Même avec le binaire wasm, il n’est exécutable que si la version du runtime utilisée implémente par hasard le backend ggml spécifique nécessaire ; or rien ne l’exige, donc en général ce ne sera probablement pas le cas
Et même si ça marche, cela revient de toute façon à appeler le code ggml de llama.cpp, donc la sécurité dépend du niveau de sûreté de cette bibliothèque
L’article insiste beaucoup trop sur la « portabilité » et sur « Rust », alors qu’en pratique on ne voit guère les avantages d’aucun des deux
Si on suppose qu’un nouveau matériel dispose d’un runtime WASI, peut-on y exécuter le modèle ? Y a-t-il un support GPU ? La réponse devient : « vérifiez si llama.cpp se compile avec le support GPU sur cette plateforme, si le runtime utilisé contient un plugin ggml, et si ce plugin embarque la bonne version de ggml. Sinon, non »
Dans ce cas, à quoi bon utiliser WASI ?
Le support GPU cross-platform est certes difficile, mais là, ça paraît franchement absurde
Imaginez si WebGPU fonctionnait en mode « on jette un bloc binaire au GPU, et peut-être qu’il affichera quelque chose si ce bloc correspond au matériel actuel » : c’est exactement ce genre d’architecture
Est-ce qu’on peut faire tourner ça hors ligne sur iPhone ? Ça pourrait alors servir de pseudo-recherche Internet basique indépendamment de la réception, ce qui serait utile en camping
Mais il faut toujours plusieurs GB de mémoire rien que pour charger le modèle
[0] https://github.com/mlc-ai/mlc-llm
Au départ, je l’avais faite avec ggml, puis j’ai découvert mlc-llm et je l’ai portée sur cette base
[0]: https://apps.apple.com/us/app/private-llm/id6448106860
Dans le cours normal des choses, on trouvera sans doute d’autres moyens d’exécuter l’architecture Transformer plus efficacement et plus rapidement en edge, mais les besoins en VRAM ne peuvent pas être résolus avec Rust, donc on a l’impression d’approcher d’une limite
C’est le principal goulot d’étranglement lorsqu’on charge un modèle suffisamment grand
On peut dire que « les petits modèles s’améliorent, regardez Mistral et Llama 2 », mais même les petits modèles approchent de leurs limites de taille. Il y a une limite à la quantité d’information qu’on peut faire tenir dans 7 milliards de paramètres
Je ne pense pas que cette approche de l’IA mène à l’AGI. Cela me paraît beaucoup trop inefficace
L’affirmation selon laquelle « le plugin GGML pour Mac OS utilise l’API Metal pour exécuter les tâches d’inférence sur le moteur de traitement neuronal intégré des M1/M2/M3 » ne me semble pas exacte
Dire que GGML utilise l’API Metal signifie qu’il s’exécute sur le GPU des M1/2/3, pas sur le Neural Engine
Cela reste très bien en soi, mais si l’on veut être rigoureux, ce n’est pas la même chose
Je n’aime pas le marketing putaclic qui laisse entendre que, parce qu’un wrapper autour d’une bibliothèque centrale est écrit dans un autre langage, le projet réduirait la taille à 1/100 ou augmenterait la vitesse de 100 à 35 000 fois par rapport à d’autres solutions
Cela ignore aussi complètement les outils et l’expertise communautaire accumulés autour des autres solutions
D’abord, ce projet repose sur llama.cpp[1], et c’est llama.cpp qui prend en charge le gros du travail consistant à charger des fichiers de modèles de plusieurs Go sur GPU/CPU et à les exécuter
La vitesse d’inférence n’est pas limitée par le choix du wrapper ; il existe aussi des wrappers Go, Python, Node et Rust, et on peut également utiliser directement llama.cpp
La taille du binaire n’a pas tant d’importance. Les fichiers de modèles quantifiés courants se situent dans une fourchette de 5 Go à 40 Go, et nécessitent donc une machine avec un GPU puissant ou 16 à 64 Go de RAM
[1] https://github.com/ggerganov/llama.cpp
Si une grande partie de la taille provient au final des poids du modèle entraîné, comment peut-on réduire la taille de plusieurs ordres de grandeur sans perdre en précision ?
Les 2 Mo mentionnés ici semblent désigner la taille du programme hors modèle. On dirait une exécution de llama.cpp via wasm et un serveur Rust qui exécute llama.cpp
J’aime bien le petit
llama.cpp/examples/server, donc je l’intègre dans FreeChat, mais avoir davantage d’options d’outillage est toujours une bonne choseAprès vérification, l’exécutable arm64/x86 que j’embarque fait actuellement 4,2 Mo. FreeChat fait 12,1 Mo, mais comme le modèle par défaut pèse environ 3 Go, je ne m’inquiète pas trop d’un écart de 2 Mo
[0]: https://github.com/ggerganov/llama.cpp/tree/master/examples/...
Cet article parle de réduire par 100 la taille de l’application d’inférence
Je ne vois pas pourquoi utiliser ça au lieu d’employer directement llama.cpp
L’article explique aussi qu’il exploite l’accélération matérielle sur des appareils dotés d’accélérateurs matériels hétérogènes
Cela signifie qu’un programme compilé en wasm peut utiliser efficacement, sur plusieurs appareils, diverses ressources matérielles comme le GPU ou des puces IA spécialisées
Avec une implémentation directe en C++, il pourrait falloir des optimisations ou des versions distinctes selon chaque type de matériel pour obtenir des performances comparables