# Un chatbot IA qui tourne entièrement dans le navigateur > Comment on a construit un chatbot d'entreprise qui fonctionne sans serveur, sans API, sans backend — juste du WebGPU, Transformers.js et un modèle ONNX de 450 Mo chargé à la demande. Retour sur les joies et les galères de l'IA locale dans le browser. Date : 16/03/2026 Auteur : Aurélien N. Tags : IA, Transformers.js, WebGPU, ONNX, Hugging Face, Astro --- "Le blog ressuscité ne parle pas beaucoup d'IA." C'est Raphaël P. qui me l'a balancé un matin, entre deux PR reviews. Et il avait raison. On écrit sur Rails, sur Next.js, sur les Core Web Vitals — mais pas un mot sur l'IA alors qu'on en mange au petit-déjeuner depuis deux ans. Le pipeline Document AI + GPT-4 de [Réno Claire](/blog/retex-tdd-montee-version-coordination-ia-renovation) ? On en a parlé sous l'angle architecture. L'IA elle-même ? Pas vraiment. Alors j'ai voulu faire un truc. Un truc IA. Un chatbot. Sauf que — et c'est là que ça devient amusant — on est sur un site Astro. Du HTML statique. Zéro serveur. [Lighthouse à 100/100](/blog/core-web-vitals-en-2026). Connecter un LLM via une API, c'est trivial techniquement, mais ça implique un backend, des clés API, des coûts par requête, de la latence réseau. Tout ce qu'on a soigneusement évité en choisissant Astro. La question s'est posée naturellement : **est-ce qu'on peut faire tourner un LLM directement dans le navigateur ?** Spoiler : oui. Et vous pouvez l'essayer maintenant, directement depuis cette page — aucune installation, aucun compte, tout tourne dans votre navigateur : Le reste de cet article explique comment on en est arrivé là. ## L'état de l'art : ce qui tourne dans un browser en 2026 Avant de foncer tête baissée, un petit tour d'horizon de ce qui existe. Quand on parle de machine learning côté client en 2026, trois écosystèmes coexistent : - **[TensorFlow.js](https://www.tensorflow.org/js)** — le vétéran. Mature, bien documenté, orienté modèles "classiques" (classification, détection, segmentation). Pour la génération de texte avec des architectures transformer modernes, c'est techniquement possible mais pas son terrain de jeu naturel. L'écosystème de modèles est surtout centré vision et audio. - **[Web-LLM](https://github.com/mlc-ai/web-llm)** (MLC AI) — prometteur. L'approche est radicalement différente : compilation directe des modèles vers WebGPU via [Apache TVM](https://tvm.apache.org/). Performances brutes souvent supérieures à ONNX Runtime, mais l'écosystème de modèles disponibles est plus restreint, la compatibilité navigateur reste capricieuse, et le toolchain de conversion est plus complexe. Si votre priorité c'est la vitesse d'inférence pure sur un modèle précis, Web-LLM est un concurrent sérieux. - **[Transformers.js](https://huggingface.co/docs/transformers.js)** (Hugging Face) — le port JavaScript de la bibliothèque `transformers` de Python. Accès direct au catalogue de modèles ONNX du [Hugging Face Hub](https://huggingface.co/models?library=transformers.js), support WebGPU natif depuis la v3, plus de [120 architectures supportées](https://huggingface.co/blog/transformersjs-v3) (Llama, Qwen, Phi, Gemma, Whisper, Florence…), et une communauté massive. On a choisi Transformers.js. Pour une raison simple : c'est Hugging Face. Le même écosystème que celui qu'on utilise côté serveur. Les mêmes modèles. Les mêmes formats. La [v3 sortie fin 2024](https://huggingface.co/blog/transformersjs-v3) a été un tournant : support WebGPU natif, quantification flexible (`q4`, `q8`, `fp16`), et surtout plus de 1200 modèles pré-convertis sur le Hub. Quand un nouveau petit modèle sort, quelqu'un dans la communauté `onnx-community` publie la version ONNX quantifiée dans les jours qui suivent. ### Le socle : WebGPU et ONNX Runtime Sous le capot, Transformers.js repose sur [ONNX Runtime Web](https://onnxruntime.ai/docs/tutorials/web/), le runtime d'inférence de Microsoft porté en WebAssembly. C'est lui qui exécute effectivement le modèle — Transformers.js est la couche d'abstraction qui gère le chargement, le tokenizer, et l'API de pipeline. La pièce manquante, c'est [WebGPU](https://developer.chrome.com/blog/webgpu-io2023). Arrivé dans Chrome 113 (mai 2023), c'est le successeur de WebGL pour l'accès GPU depuis le navigateur. Contrairement à WebGL, WebGPU expose des **compute shaders** — des programmes GPU qui ne sont pas liés au rendu graphique. C'est ce qu'il faut pour les multiplications de matrices massives qui constituent 90% de l'inférence d'un transformer. Les gains sont significatifs : Google rapporte des accélérations de **3x à 5x** par rapport à WebGL pour les workloads ML. Dans notre cas, la différence entre WebGPU et le fallback WASM est encore plus marquée, surtout sur la phase de compilation du graphe. Côté support navigateur, [WebGPU couvre environ 83% des utilisateurs](https://caniuse.com/webgpu) en mars 2026 : - **Chrome / Edge** : support complet depuis la v113 - **Safari** : support partiel depuis la v26 - **Firefox** : toujours derrière un flag (`dom.webgpu.enabled`), ce qui est frustrant - **Mobile** : Samsung Internet et iOS Safari récents le supportent Pour les 17% restants, le fallback WASM fonctionne — c'est juste plus lent. Et vous, votre navigateur est-il prêt ? Ce widget détecte en temps réel ce que votre machine expose : ## Premier essai : la joie puis la douche froide Le premier prototype tenait en vingt lignes. Un `pipeline('text-generation', ...)`, un modèle ONNX, et hop — de la génération de texte dans la console du navigateur. Le frisson du "ça marche !" a duré environ trente secondes. Puis j'ai regardé l'onglet réseau de DevTools. **450 Mo de téléchargement** pour le modèle français ([Qwen 2.5 0.5B](https://huggingface.co/onnx-community/Qwen2.5-0.5B-Instruct) en quantification q4). Le modèle anglais plus léger ([SmolLM2 360M](https://huggingface.co/HuggingFaceTB/SmolLM2-360M-Instruct)) pesait dans les 250 Mo. Sur une connexion fibre, ça passe en une minute. Sur du 4G ? Plusieurs minutes d'attente avant de pouvoir poser la moindre question. Et ces 450 Mo, c'est *après* quantification. Le modèle en `fp32` pèse plus de 2 Go. La quantification `q4` (4 bits par poids au lieu de 32) divise la taille par ~8, avec une perte de qualité mesurable mais acceptable pour notre cas d'usage. Transformers.js v3 a rendu ça trivial — un paramètre `dtype: 'q4'` dans le pipeline et c'est réglé. On a aussi testé la quantification **`q4f16`** (poids en 4 bits, activations en float16 au lieu de float32) — prometteuse sur le papier parce qu'elle réduit encore l'empreinte mémoire et accélère les calculs. En pratique, c'est inutilisable sur WebGPU : le modèle tourne, mais produit des sorties incohérentes — tokens répétés, NaN dans les logits, parfois du charabia complet. Le même modèle en `q4f16` fonctionne correctement en WASM/CPU, ce qui pointe vers un [problème de stabilité numérique dans le chemin fp16 du backend WebGPU d'ONNX Runtime](https://github.com/microsoft/onnxruntime/issues/26367). Un [bug similaire](https://github.com/microsoft/onnxruntime/issues/26732) a été signalé sur les modèles Gemma 3 avec les mêmes symptômes — overflow dans les opérations fp16. Pour l'instant, on reste sur du `q4` classique (activations float32), qui fonctionne sans surprise. Puis il y a le temps d'initialisation. Même avec les poids en cache (merci la [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache) du navigateur), le chargement du modèle en mémoire et la compilation du graphe WebGPU prennent entre 20 et 40 secondes. La première fois, sans cache, on est plutôt sur 90 à 180 secondes. C'est le graphe de calcul ONNX qui est compilé en shaders WebGPU — une étape incompressible à chaque session. Pour un chatbot "corporate" censé répondre à "Que fait imagine-app ?", c'est… ambitieux. ## L'architecture : zéro serveur, vraiment On a quand même persévéré. Et le résultat, c'est J.A.R.V.I.S — *Just Another Rather Versatile Intelligent System* (on assume le clin d'œil). Un chatbot d'entreprise qui tourne **entièrement dans le navigateur**, sans aucun appel serveur pour l'inférence. L'architecture repose sur plusieurs briques : 1. **[Transformers.js](https://huggingface.co/docs/transformers.js) v3.8** — chargé dynamiquement depuis le CDN jsdelivr, uniquement quand l'utilisateur interagit avec le chatbot. Pas un octet de JavaScript IA sur le chargement initial du site. Le chargement dynamique depuis CDN évite aussi de gonfler le `node_modules` avec les 40+ Mo de la lib. 2. **Modèles ONNX quantifiés** — hébergés sur [Cloudflare R2](https://developers.cloudflare.com/r2/) (stockage objet avec CDN mondial, CORS natif, egress gratuit). Deux modèles disponibles : [Qwen 2.5 0.5B](https://huggingface.co/onnx-community/Qwen2.5-0.5B-Instruct) pour le français, [SmolLM2 360M](https://huggingface.co/HuggingFaceTB/SmolLM2-360M-Instruct) pour l'anglais. 3. **WebGPU** comme runtime principal, avec fallback automatique vers WASM. La détection est transparente — `'gpu' in navigator` — et le choix du backend est fait une seule fois au chargement. 4. **[Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache)** pour persister les poids du modèle entre les sessions. C'est la même API que celle utilisée par les Service Workers — elle survit à la fermeture de l'onglet et même au redémarrage du navigateur. 5. **[IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API)** pour l'historique des conversations, avec support multi-conversations et migration de schéma. Le tout est découpé en trois chunks Vite séparés (`jarvis-core`, `jarvis-ui`, `jarvis-shell`) via un `manualChunks` dans la config Astro, pour ne charger que ce qui est nécessaire. L'initialisation depuis la page Astro est un stub de quelques octets qui ne charge le vrai code que quand on appelle `jarvis.ask()` ou `jarvis.ui()`. ```javascript // astro.config.mjs — code-splitting du chatbot manualChunks(id) { if (id.includes('/src/lib/jarvis/')) } ``` ```javascript // Ce que voit le navigateur au chargement : un stub, pas le modèle window.jarvis = new Proxy(, { get: (_, prop) => async (...args) => }); ``` Le résultat : zéro impact sur les performances du site. Le LCP, l'INP, le CLS — tout reste à zéro. Le chatbot est invisible tant qu'on ne l'invoque pas. ## Le vrai problème : les petits modèles et les gros prompts Le moment où j'ai vraiment compris les limites, c'est quand j'ai essayé de donner au modèle assez de contexte pour répondre correctement. Un modèle de 500 millions de paramètres, ce n'est pas GPT-4. Sa fenêtre de contexte est limitée, et surtout — **il ne suit les instructions correctement que si le prompt est court et précis**. Premier essai : j'ai mis tout le JSON de connaissance de l'entreprise dans le system prompt. Équipe, services, timeline, articles de blog, FAQ… Le prompt faisait 3000 tokens. Le résultat ? Le modèle hallucinait complètement. Il inventait des services, mélangeait les fondateurs, et à un moment il a répondu en chinois (Qwen est un modèle multilingue de base, il ne demande qu'à parler mandarin). La solution : un **RAG statique**. Pas un RAG classique avec des embeddings et une base vectorielle — ça impliquerait un modèle d'embedding supplémentaire et beaucoup plus de complexité. Un RAG par mots-clés, brutalement simple. Le principe : 1. On découpe la connaissance de l'entreprise en **snippets** courts (50-100 tokens chacun), chacun associé à des mots-clés. 2. Quand l'utilisateur pose une question, on normalise le texte (NFD, suppression des accents, lowercase) et on **score chaque snippet** par correspondance de mots-clés. 3. On injecte les **3 snippets les plus pertinents** directement dans le message utilisateur — pas dans le system prompt. 4. Le system prompt reste ultra-court : identité, consigne de concision, interdiction d'inventer. ```typescript // Le prompt injecté est volontairement minimaliste const systemPrompt = "Tu es l'assistant console d'imagine-app. " + "Tu réponds en français, brièvement, sans inventer. " + "Si l'information n'est pas présente, tu le dis clairement."; // Les faits pertinents sont injectés côté user, pas système const userContent = `$\n\nRelevant facts:\n$\n\nQuestion: $`; ``` Pourquoi injecter dans le message user plutôt que dans le system prompt ? Parce que les petits modèles suivent mieux les instructions quand le contexte est proche de la question. C'est contre-intuitif quand on vient de GPT-4 où le system prompt est sacré, mais avec un modèle de 500M de paramètres, la proximité dans le prompt compte beaucoup. Ce phénomène est documenté dans la littérature sous le terme de *lost in the middle* — les LLMs ont tendance à mieux exploiter les informations au début et à la fin du prompt, et à "oublier" ce qui est au milieu. Sur un petit modèle, l'effet est dévastateur. La génération est aussi volontairement bridée : ```typescript ``` Quelques détails sur ces choix pour ceux que ça intéresse : - **`do_sample: false`** force le décodage glouton (*greedy decoding*). Avec un petit modèle, l'échantillonnage aléatoire produit plus de bruit que de créativité. On préfère la réponse la plus probable, même si elle est sèche. - **`max_new_tokens: 144`** empêche le modèle de divaguer. Les petits modèles ont une fâcheuse tendance à se répéter ou à partir en freestyle au-delà de ~100 tokens. Couper court est une feature. - **`repetition_penalty: 1.08`** est un multiplicateur appliqué aux tokens déjà générés pour réduire la probabilité qu'ils soient re-sélectionnés. C'est subtil — trop haut (>1.2) et le modèle commence à éviter des mots courants, ce qui donne un texte bizarre. Le résultat est correct pour des questions factuelles simples. "Que fait imagine-app ?", "Quelles sont vos technos ?", "Comment vous contacter ?" — le modèle répond juste, avec les bonnes informations, sans inventer. Dès que la question sort du périmètre, il répond poliment qu'il ne sait pas. C'est loin de la magie d'un Claude ou d'un GPT-4. Mais ça tourne dans un navigateur, sans serveur, sans coût par requête, sans latence réseau. Et ça, c'est fascinant. ## Les tests de modèles : un parcours du combattant On a itéré sur plusieurs modèles avant de trouver le bon compromis. Si vous regardez l'historique git du dossier `experiments/console-chatbot/`, vous verrez les allers-retours : | Modèle | Params | Taille q4 | Langue | Verdict | |--------|--------|-----------|--------|---------| | [SmolLM2 360M](https://huggingface.co/HuggingFaceTB/SmolLM2-360M-Instruct) | 360M | ~250 Mo | EN | Rapide, concis, excellents benchmarks pour sa taille | | [Qwen 2.5 0.5B](https://huggingface.co/onnx-community/Qwen2.5-0.5B-Instruct) | 500M | ~450 Mo | FR/multi | Le meilleur compromis taille/qualité en français | | [Phi-3 Mini](https://huggingface.co/microsoft/Phi-3-mini-4k-instruct) | 3.8B | ~2.3 Go | EN | Qualité impressionnante, trop lourd pour le browser | | [Gemma 2 2B](https://huggingface.co/google/gemma-2-2b-it) | 2B | ~1.4 Go | EN | Idem — bon modèle, mauvais contexte d'exécution | Quelques chiffres qui illustrent le fossé entre ces modèles. Sur le benchmark [HellaSwag](https://rowanzellers.com/hellaswag/) (compréhension de texte), SmolLM2 360M score à 54.5 — ce qui est remarquable pour un modèle entraîné sur 4 trillions de tokens et détaillé dans le [paper SmolLM2](https://arxiv.org/abs/2502.02737). Qwen 2.5 0.5B est à 51.2 sur le même benchmark, mais bat SmolLM2 sur les tâches multilingues grâce à un corpus d'entraînement nettement plus diversifié linguistiquement. Le constat est clair : en dessous de 500M de paramètres, la qualité en français chute drastiquement. Le français est sous-représenté dans les données d'entraînement de ces petits modèles. SmolLM2 est une prouesse en anglais — Hugging Face a montré qu'avec une curation de données agressive ([FineWeb-Edu](https://huggingface.co/datasets/HuggingFaceFW/fineweb-edu), [DCLM](https://www.datacomp.ai/dclm/), [The Stack](https://huggingface.co/datasets/bigcode/the-stack-v2)) et un overtraining maîtrisé, on peut faire beaucoup avec 360M de paramètres. Mais passer en français avec le même modèle donne des résultats médiocres. Les données francophones ne représentent qu'une fraction du corpus. Qwen 2.5 0.5B est le sweet spot actuel : assez petit pour tenir dans un navigateur, entraîné sur un corpus multilingue par Alibaba, et suffisamment bon en français pour être utilisable. Mais "utilisable" n'est pas "impressionnant". D'où l'idée de pousser l'expérience plus loin. ## La prochaine étape : fine-tuner notre propre modèle Les réponses du modèle de base sont correctes mais génériques. Il répond comme un assistant lambda, pas comme *l'assistant d'imagine-app*. Pour lui donner une personnalité, un vocabulaire, et surtout une connaissance intégrée de l'entreprise plutôt qu'injectée à chaque requête, la voie naturelle c'est le **fine-tuning**. Le pipeline d'entraînement est déjà en place : 1. **`generate_knowledge.py`** extrait les faits du site (équipe, services, blog, timeline) et produit un JSON structuré. 2. **`generate_training_data.py`** transforme ces faits en paires question/réponse au format JSONL — le format standard pour le SFT (Supervised Fine-Tuning). 3. **`train_hf_model.py`** prépare la configuration d'entraînement : modèle de base Qwen 2.5 0.5B, approche LoRA/QLoRA pour un fine-tuning léger. L'entraînement lui-même se fera sur Hugging Face, via leur infrastructure de [jobs GPU managés](https://huggingface.co/docs/hub/jobs). L'approche est standard : **[TRL](https://huggingface.co/docs/trl)** (Transformer Reinforcement Learning) pour le SFT, **[LoRA](https://arxiv.org/abs/2106.09685)** pour ne modifier qu'une fraction des poids (parameter-efficient fine-tuning), et **[Trackio](https://github.com/huggingface/trackio)** pour le monitoring en temps réel des métriques d'entraînement. Le format du dataset est du JSONL conversationnel standard — chaque ligne contient un échange `system` / `user` / `assistant`. C'est le format attendu par TRL pour le SFT, et c'est aussi le format natif des [datasets de chat](https://huggingface.co/docs/trl/dataset_formats) de Hugging Face : ```json {"messages": [ , , ]} ``` On inclut aussi des exemples négatifs — questions hors-périmètre, demandes de tarifs non documentés — pour que le modèle apprenne à refuser poliment plutôt qu'à inventer. C'est un pattern qu'on retrouve dans le [DPO (Direct Preference Optimization)](https://arxiv.org/abs/2305.18290) utilisé par SmolLM2 pour son post-training, et c'est souvent ce qui fait la différence entre un modèle qui hallucine et un modèle qui dit "je ne sais pas". ### Pourquoi Hugging Face (et pas un autre) On aurait pu entraîner en local (un Mac M2 suffit pour un modèle de cette taille) ou sur n'importe quel cloud GPU. On a choisi l'écosystème Hugging Face pour plusieurs raisons : **Les coûts.** Un GPU NVIDIA T4 sur les [Inference Endpoints](https://huggingface.co/docs/inference-endpoints/support/pricing) de Hugging Face coûte **$0.50/h**. Pour fine-tuner un modèle de 500M de paramètres sur un dataset de quelques centaines d'exemples, on parle de 10 à 30 minutes d'entraînement. Soit **moins d'un dollar** par run. Même en comptant les itérations et les expérimentations, le budget total pour trouver le bon modèle sera de l'ordre de quelques dizaines de dollars. Comparez ça à un fine-tuning GPT-4 chez OpenAI (facturé au token, opaque, sans accès aux poids) — le rapport qualité/contrôle/prix est incomparable. | Instance | GPU | VRAM | Prix/h | Usage typique | |----------|-----|------|--------|---------------| | nvidia-t4 x1 | T4 | 14 Go | $0.50 | Fine-tuning petits modèles (moins de 1B) | | nvidia-l4 x1 | L4 | 24 Go | $0.80 | Modèles jusqu'à 3B | | nvidia-a10g x1 | A10G | 24 Go | $1.00 | Modèles jusqu'à 7B | *Source : [Hugging Face Inference Endpoints — Pricing](https://huggingface.co/docs/inference-endpoints/support/pricing). Facturation à la minute.* **La praticité.** Le CLI [`huggingface-cli`](https://huggingface.co/docs/huggingface_hub/guides/cli), la documentation exhaustive, la communauté open source — tout est pensé pour les développeurs. Les [skills Hugging Face](https://huggingface.co/skills) permettent de lancer un entraînement en quelques lignes, sans configurer de cluster, sans gérer d'infrastructure. Le modèle entraîné atterrit directement sur le Hub, prêt à être téléchargé ou converti. Et pour ceux qui utilisent des outils de code assisté par IA (Codex, Claude Code…), le [skill `hugging-face-model-trainer`](https://github.com/huggingface/skills) permet de piloter l'entraînement directement depuis l'éditeur. **L'écosystème.** On utilise déjà Transformers.js (Hugging Face) pour l'inférence dans le navigateur. Le modèle fine-tuné sera exporté en ONNX via [Optimum](https://huggingface.co/docs/optimum), quantifié en q4, et déployé sur le même CDN. La boucle est complète : les données viennent du site, l'entraînement se fait sur le Hub, le modèle revient dans le navigateur. Tout l'écosystème est conçu pour que ces étapes s'enchaînent naturellement. ### Les evals : mesurer avant d'optimiser Avant de lancer le fine-tuning, on rédige les **évaluations**. Pas après — avant. C'est la même logique que le TDD qu'on applique sur [Réno Claire](/blog/retex-tdd-montee-version-coordination-ia-renovation) : on définit ce qu'on attend, puis on entraîne jusqu'à ce que ça passe. Les critères : - **Factualité** — le modèle cite-t-il des informations correctes sur l'entreprise ? - **Périmètre** — refuse-t-il poliment les questions hors-sujet ? - **Concision** — les réponses sont-elles courtes et précises ? - **Langue** — maintient-il le français sans basculer en anglais ou en mandarin ? - **Cohérence** — sur une conversation de 5 échanges, garde-t-il le fil ? L'entraînement est en cours. Les premiers résultats sont encourageants mais pas encore publiables — on y reviendra dans un article dédié. ## Le paradoxe de l'IA locale en 2026 Il y a un éléphant dans la pièce, et il faut en parler honnêtement. En mars 2026, les API des grands LLMs sont **extraordinairement bon marché**. Claude, GPT-4o, Gemini — pour quelques centimes par millier de requêtes, on a accès à des modèles qui surclassent n'importe quel petit modèle local de plusieurs ordres de grandeur. La qualité n'est même pas comparable. Fly.io — un hébergeur qu'on apprécie par ailleurs, une sorte de Google Cloud Run qui aurait été conçu par des gens qui aiment les développeurs — [a récemment jeté l'éponge sur le hosting GPU](https://fly.io/blog/wrong-about-gpu/). Leur constat est lucide : les développeurs ne veulent pas de GPUs, ils veulent des LLMs. Et pour ça, les API des gros providers sont imbattables en rapport qualité/prix/simplicité. Le marché du self-hosting d'IA est en train de se réduire aux cas très spécifiques. Alors pourquoi s'acharner ? **Parce qu'on est des geeks et qu'on y croit.** L'IA dans le navigateur, c'est une capacité qui n'existait pas il y a deux ans. WebGPU est arrivé dans Chrome en 2023, les modèles ONNX quantifiés en q4 sont devenus viables en 2024, Transformers.js v3 a stabilisé le support en 2025. On est au début de quelque chose. **Parce que pour certains clients, c'est un vrai sujet.** Les clients institutionnels — administrations, santé, défense — ont des contraintes de souveraineté et de confidentialité qui rendent les API cloud problématiques. Un modèle qui tourne localement, dont les données ne quittent jamais le terminal, c'est un argument technique concret. Pas de RGPD à négocier, pas de DPA à signer, pas de risque de fuite. Les données restent dans le navigateur de l'utilisateur. Point. **Parce que les coûts à l'échelle sont nuls.** Zéro serveur = zéro coût d'inférence. Que le chatbot soit utilisé par 10 ou 10 000 personnes, la facture d'infrastructure est la même : rien. C'est le calcul de l'utilisateur qui fait le travail, pas le nôtre. **Parce que la technologie progresse vite.** Les modèles de 1 à 3 milliards de paramètres commencent à rivaliser avec les modèles de 7B d'il y a 18 mois. La quantification s'améliore. WebGPU mûrit. D'ici un an ou deux, un modèle de 2B quantifié pourrait tourner dans un navigateur avec une qualité suffisante pour des cas d'usage réels — support client, FAQ interactive, assistant de navigation. On préfère avoir l'expertise maintenant. ## Ce qu'on a appris Quelques leçons tirées de cette expérimentation, pour ceux qui voudraient se lancer : 1. **Le chargement est le vrai goulot.** L'inférence elle-même est rapide (quelques secondes par réponse). C'est le téléchargement initial et la compilation du graphe WebGPU qui tuent l'expérience. Toute optimisation doit se concentrer là-dessus : quantification agressive (`q4` minimum), [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache) pour persister les poids, preload intelligent via `requestIdleCallback`. 2. **Les petits modèles ont besoin de rails.** Sans RAG, sans system prompt serré, sans limitation de tokens, un modèle de 500M de paramètres part dans tous les sens. La contrainte est une feature, pas un bug. Les paramètres de génération (`max_new_tokens`, `repetition_penalty`) sont aussi importants que le choix du modèle. 3. **Le français est un luxe.** Les petits modèles sont massivement entraînés sur de l'anglais. Pour du français correct, il faut soit un modèle explicitement multilingue (Qwen, entraîné par Alibaba sur un corpus CJK + occidental), soit du fine-tuning ciblé. C'est un surcoût réel que les benchmarks anglophones ne montrent pas. 4. **WebGPU fait une vraie différence, mais n'est pas universel.** Le fallback WASM fonctionne, mais WebGPU est 3 à 5 fois plus rapide pour la compilation des poids. Le problème : Firefox le cache toujours derrière un flag, et la couverture mobile est inégale. Il faut concevoir l'expérience pour les deux backends. 5. **Le code-splitting, on n'y coupe pas.** Sans lazy loading agressif, un import de Transformers.js suffit à ruiner le bundle size et les performances du site. Le pattern Proxy + dynamic import est obligatoire. Et héberger les modèles ONNX sur un CDN séparé (R2, S3, Bunny CDN) évite de les inclure dans le build Astro. 6. **Le rendu console est sous-estimé.** On a passé un temps surprenant sur le formatage des réponses dans la console — parsing Markdown, styles `%c` pour le gras, l'italique, le code, les listes. C'est un détail, mais c'est ce qui fait passer le chatbot de "prototype moche" à "démo qui impressionne". ## Et ensuite ? J.A.R.V.I.S est une démo technique avant tout. On ne va pas prétendre que ça remplace un vrai chatbot avec un LLM puissant derrière. Mais ça donne des idées, et certaines sont très concrètes : - **Assistants hors-ligne** pour des applications métier qui doivent fonctionner sans réseau — maintenance industrielle, audits terrain, outils pour les forces de vente en zone blanche - **Chatbots embarqués "privacy-first"** pour des clients avec des contraintes de confidentialité fortes — santé, administration, défense. Aucune donnée ne quitte le terminal. - **Pré-qualification côté client** avant d'envoyer au serveur — le petit modèle filtre, reformule et classifie la requête, le gros modèle ne traite que ce qui est pertinent. On divise les coûts API par le taux de pré-filtrage. - **Expériences interactives** sur des sites statiques — quiz adaptatifs, FAQ intelligente, assistants de navigation, onboarding personnalisé. Tout ça sans backend. Le fine-tuning LoRA sur Hugging Face est en cours. L'idée : un modèle spécialisé imagine-app, entraîné sur nos propres contenus, qui répond mieux qu'un modèle générique à une fraction du coût d'un appel API. Si les résultats sont convaincants, on écrira un retour d'expérience détaillé sur le process d'entraînement, les evals, et le déploiement du modèle fine-tuné dans le navigateur. Côté performances, la quantification `q4f16` reste une piste d'amélioration intéressante — le gain en mémoire et en vitesse est réel, c'est juste le backend WebGPU d'ONNX Runtime qui ne suit pas encore. Une option serait de compiler le modèle directement vers WASM (via [Apache TVM](https://tvm.apache.org/) ou un pipeline custom) pour contourner les bugs fp16 de WebGPU tout en gardant le bénéfice de la quantification mixte. Mais on se donnera ce mal après avoir un modèle fine-tuné qui vaut le coup d'être optimisé — pas avant. En attendant, si vous êtes curieux, essayez par vous-même. Le bouton ci-dessous lance J.A.R.V.I.S directement dans votre navigateur — choisissez la langue, attendez le chargement du modèle, et posez-lui une question. Il mettra peut-être une minute à se réveiller. Mais il finira par répondre — sans qu'un seul octet n'ait quitté votre machine. --- ## Pour aller plus loin Si le sujet vous intéresse, voici les ressources qui nous ont été les plus utiles : **Transformers.js et l'IA dans le navigateur :** - [Transformers.js v3 — annonce officielle](https://huggingface.co/blog/transformersjs-v3) — les nouveautés de la v3, avec des démos interactives - [Documentation Transformers.js](https://huggingface.co/docs/transformers.js) — référence API complète - [ONNX Runtime Web](https://onnxruntime.ai/docs/tutorials/web/) — le runtime d'inférence sous le capot - [Modèles `onnx-community`](https://huggingface.co/onnx-community) sur le Hub — modèles pré-convertis et quantifiés **WebGPU :** - [WebGPU — Chrome Developers](https://developer.chrome.com/blog/webgpu-io2023) — annonce du lancement dans Chrome 113 - [Support navigateur WebGPU](https://caniuse.com/webgpu) — état des lieux en temps réel - [WebGPU Fundamentals](https://webgpufundamentals.org/) — pour comprendre les compute shaders **Petits modèles et fine-tuning :** - [SmolLM2 — paper](https://arxiv.org/abs/2502.02737) — la recherche derrière le modèle de Hugging Face - [Qwen 2.5 — modèle card](https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct) — le modèle d'Alibaba qu'on utilise pour le français - [LoRA — paper original](https://arxiv.org/abs/2106.09685) — l'adaptation de rang faible expliquée - [TRL — documentation](https://huggingface.co/docs/trl) — la lib de Hugging Face pour le fine-tuning - [Hugging Face GPU Pricing](https://huggingface.co/docs/inference-endpoints/support/pricing) — les coûts réels **Contexte marché :** - [Fly.io — "We Were Wrong About GPUs"](https://fly.io/blog/wrong-about-gpu/) — pourquoi un hébergeur a arrêté le GPU hosting *Cet article vous parle ? Vous expérimentez avec l'IA dans le navigateur, le fine-tuning de petits modèles, ou vous avez un cas d'usage qui nécessite de l'inférence locale ? [On aimerait en discuter.](/contact)*