On oppose depuis dix ans deux façons de faire travailler un ordinateur : lui écrire des instructions, pour un comportement exact mais figé, ou lui montrer des exemples et laisser un réseau de neurones régler ses poids, au prix d’un résultat approché que personne ne sait vraiment relire. Handwired, annoncé par Cheng Lou le 15 octobre 2026, fait passer un programme du premier monde au second en gardant son exactitude. On obtient un objet curieux : un réseau de neurones qui ne devine rien, et que l’on peut pourtant entraîner.
Dans cet article
Que montre Cheng Lou avec Handwired ?
Cheng Lou est connu des développeurs d’interfaces : passé par Meta, où il a travaillé sur React, ReasonML et Messenger, auteur de la bibliothèque d’animation React Motion, il conçoit aujourd’hui le front-end de Midjourney. Il présente Handwired comme un « projet jouet », avec trois promesses : aucun entraînement, des résultats identiques à ceux du programme JavaScript, un réseau entièrement différentiable, c’est-à-dire dont on sait calculer comment la sortie varie quand on touche à chacun de ses nombres, condition pour l’entraîner, le tout dans un navigateur, sans carte graphique.
Trois démonstrations accompagnent l’annonce. Le Mini OS est le bureau décrit plus haut ; l’inspecteur du navigateur n’y montre que des éléments HTML ordinaires. La galerie apprise « dégèle » des poids : vous dessinez des rectangles, et un petit réseau logé dans le programme, initialisé au hasard puis entraîné pendant que vous dessinez, place les photos. La recherche dans 100 000 documents JSON exécute la même expression régulière sur tous les documents à la fois, sur la carte graphique, à chaque frappe. Le fil se termine sur une idée qui n’est pas encore une démonstration : greffer un programme Handwired sur nanoGPT, le petit modèle de langage pédagogique d’Andrej Karpathy.
Une précision mérite d’être lue avant tout commentaire. Le slogan promet de « transformer votre code JavaScript ordinaire en réseau de neurones » ; la documentation indique qu’aucun outil ne convertit aujourd’hui un code existant. On écrit un code de construction, du JavaScript qui s’exécute une seule fois et empile les opérations du réseau. Le nom du projet le dit : handwired, câblé à la main. Le compilateur expérimental du dépôt, lui, ne traite qu’un sous-ensemble de TypeScript : sur le code d’une vraie galerie photo, il compile et vérifie 64 instructions sur 221.
Sources : le fil d’annonce de Cheng Lou, le dépôt Handwired et sa documentation, les démonstrations en ligne et un portrait de Cheng Lou.
Comment une condition devient-elle deux neurones ?
Voici l’exemple du README, un simple opérateur ternaire :
const a = 5
const result = (a > 4) ? 10 : 12Et sa traduction en arithmétique, qui donne le même résultat pour tout entier a en deçà de seize millions, limite des flottants de 32 bits :
const relu = (x) => Math.max(0, x)
// vaut 1 si a > 4, 0 sinon, pour un entier a
const isAbove4 = relu(a - 4) - relu(a - 5)
// les deux branches sont calculées, isAbove4 choisit
const result = isAbove4 * 10 + (1 - isAbove4) * 12La fonction relu, pour rectified linear unit, est la fonction d’activation la plus répandue des réseaux de neurones, celle que chaque neurone applique à sa somme : elle laisse passer un nombre positif et remplace un nombre négatif par zéro. Deux ReLU décalées d’une unité, soustraites l’une à l’autre, dessinent une marche : 0 tant que a ne dépasse pas 4, 1 dès que a atteint 5, et entre les deux une rampe. Comme a est entier, il ne tombe jamais sur la rampe, et la marche se comporte comme une condition.
La suite est de l’algèbre. Puisque 10 et 12 sont des constantes, le résultat se simplifie :
result = 12 − 2 · relu(a − 4) + 2 · relu(a − 5)C’est exactement la forme d’un réseau à une couche cachée. Deux neurones calculent h = relu(W₁·a + b₁), avec W₁ = (1, 1) et b₁ = (−4, −5), puis la sortie vaut W₂·h + b₂, avec W₂ = (−2, 2) et b₂ = 12. Sept nombres en tout. Ce sont les poids du réseau, et personne ne les a appris : ils se lisent dans le code. Choisissez une valeur de a pour suivre le calcul.
result = 12 − 2 × 0 + 2 × 0 = 12
Les deux neurones restent éteints : la condition est fausse, la branche 12 l’emporte.
result = 12 − 2 × 1 + 2 × 0 = 10
Le premier neurone s’allume, le second pas encore : la marche vaut 1, la branche 10 l’emporte.
result = 12 − 2 × 2 + 2 × 1 = 10
Les deux neurones montent ensemble et leur différence reste 1. C’est le rôle du second : arrêter la marche à 1, quelle que soit la valeur de a.
result = 12 − 2 × 0,5 + 2 × 0 = 11
La valeur tombe sur la rampe : le résultat vaut 11, un nombre qu’aucune branche du programme ne produit.
La dernière valeur résume tout le problème. L’exactitude de l’exemple repose sur une hypothèse que le ternaire d’origine ne dit pas : a est entier, donc aucune valeur ne tombe entre 4 et 5.
Pourquoi le résultat reste-t-il exact ?
Une interface ne manipule pas que des entiers : une position de pointeur vaut volontiers 312,37 pixels. Handwired demande donc au développeur de déclarer, pour chaque entrée, un intervalle et, quand c’est possible, une grille, le pas des seules valeurs qu’elle peut prendre. Les coordonnées du pointeur sont arrondies au 1/64 de pixel ; deux valeurs de cette grille diffèrent d’au moins 1/64, et il suffit de multiplier leur écart par 64 pour retrouver des entiers et une marche exacte. emitCompare(circuit, x, '>=', y) produit alors un bit exact en deux opérations.
Sans grille, la bibliothèque s’appuie sur une propriété des nombres flottants eux-mêmes : ils sont discrets. Deux flottants de 32 bits différents ne peuvent pas être plus proches que 2⁻¹⁴⁹, et une cascade de signe, une suite de multiplications plafonnées, agrandit leur écart jusqu’à ce que tout écart non nul atteigne 1. La même décision coûte alors 15 opérations. Nous avons construit les deux versions avec le runtime du dépôt : 2 opérations avec grille, 15 sans, et la même réponse.
Le détail de la cascade, pour les curieux
L’écart d = a − b est calculé une seule fois en flottant de 32 bits : il vaut 0 exactement quand a = b, et garde sinon le signe de a − b, même minuscule. On n’en garde que la partie positive, puis chaque étage calcule min(limite, 2ᵏ · x), écrit x − relu(x − limite). Multiplier par une puissance de deux est exact pour un flottant, puisque seul l’exposant change. Pour des valeurs comprises entre 0 et 16 384, les facteurs sont 2¹⁰, cinq fois 2²⁴, puis 2¹⁹ : leurs exposants totalisent 149, juste ce qu’il faut pour qu’un écart de 2⁻¹⁴⁹ atteigne 1. Une dernière ReLU en fait un bit.
Ce que « exact » veut dire ici
Le réseau reproduit exactement le calcul en flottants de 32 bits, et non celui de JavaScript, qui calcule en 64 bits. Un nombre de colonnes calculé comme (1 732 − 24) × (1 / 244) vaut 7 en JavaScript et 6,9999995 en 32 bits : le compilateur expérimental a relevé six largeurs de fenêtre, sur 4 097, où une galerie affiche ainsi une colonne de moins. Les essais du dépôt comparent donc les programmes à une version JavaScript écrite à part : décisions identiques, positions égales à quelques dix-millièmes de pixel près.
Dans le dépôt : le modèle d’exécution et le compilateur expérimental.
Que deviennent les boucles et les couches ?
Un réseau est une liste fixe d’opérations : il ne peut ni répéter un calcul un nombre de fois connu seulement à l’exécution, ni s’arrêter en route. Chaque branche est calculée, chaque élément d’une liste aussi, et chaque boucle se réécrit en comptage, en arbre, en lecture de table ou en même travail appliqué à tous les éléments. Les ressorts qui animent les fenêtres montrent la méthode : le nombre de pas de 4 millisecondes à simuler dépend du temps écoulé, donc le JavaScript qui entoure le réseau calcule la matrice de ces n pas, et le réseau l’applique en une fois, avec 14 opérations par ressort, quel que soit n.
Les opérations sont ensuite rangées par profondeur : celles qui ne dépendent que de calculs déjà faits forment une couche, exécutée comme une multiplication par une matrice creuse, un tableau de poids presque entièrement fait de zéros, suivie de ses portes. La documentation recense quatre instructions : la somme pondérée, le produit ReGLU, qui multiplie une somme par la ReLU d’une autre, l’arrondi d’un score à 0 ou 1, et la lecture d’une table ; les deux dernières servent l’efficacité et ne transmettent aucun gradient utile à la décision qu’elles portent. Le ReGLU porte le nom de la variante d’activation que Noam Shazeer a proposée en 2020 pour les transformers, l’architecture des grands modèles de langage : la même opération sert aux deux mondes.
Transformer du code en réseau de neurones, est-ce nouveau ?
Que trois lignes de JavaScript deviennent deux neurones n’a rien de surprenant pour un mathématicien. Dès 1943, Warren McCulloch et Walter Pitts montraient que des neurones formels calculent des fonctions logiques ; en 1995, Hava Siegelmann et Eduardo Sontag établissaient qu’un réseau récurrent peut simuler n’importe quelle machine de Turing ; et l’on sait depuis les travaux de Raman Arora et de ses coauteurs que les réseaux à ReLU représentent exactement les fonctions continues affines par morceaux.
Compiler un programme en poids n’est pas neuf non plus. Le langage RASP décrit en 2021 ce qu’un transformer sait calculer ; Tracr, chez DeepMind en 2023, compile ces programmes en véritables transformers pour offrir aux chercheurs en interprétabilité des modèles dont on connaît le mécanisme. La même année, Angeliki Giannou et ses coauteurs montrent qu’un transformer bouclé peut servir d’ordinateur programmable. En mars 2026, Percepta a publié un transformer dont les poids sont calculés analytiquement pour simuler une machine virtuelle WebAssembly et exécuter des programmes C, en revendiquant environ 30 000 jetons par seconde. Le chemin inverse est exploré aussi : les réseaux de portes logiques différentiables apprennent un circuit par descente de gradient, puis le figent en portes exactes.
Qu’apporte vraiment Handwired ?
Rien de tout cela n’enlève son intérêt au projet, qui déplace la question sur quatre points.
L’échelle d’une application
Tracr compile des tris et des vérifications de parenthèses ; Percepta, un interpréteur. Handwired fait tourner une interface à chaque image : tests de clic, ordre des fenêtres, ressorts, défilement. Le Mini OS compte 4 646 opérations actives, et une évaluation prend environ 36 microsecondes sur la machine de mesure du projet, un Mac M5 Max. Sur un modeste serveur à deux cœurs, nous mesurons de 150 à 200 microsecondes, de l’ordre de 1 % d’une image à 60 Hz.
Un contrat d’exactitude tenu par des essais
Chaque comparaison est exacte pour toutes les valeurs de son intervalle déclaré, et la bibliothèque note les « points d’arrondi », ces opérations qu’un moteur d’exécution doit garder telles qu’elles sont écrites, parfois jusqu’à l’ordre exact des calculs. Les 174 essais du cœur du runtime passent ; nous les avons relancés.
Une forme que l’on peut entraîner
Puisque tout est somme et produit de ReLU, la rétropropagation, la méthode qui calcule dans quel sens corriger chaque poids pour réduire l’erreur, s’applique au programme tel qu’il est écrit. Dans la galerie apprise, 244 coefficients d’un réseau de 6 320 opérations sont réglés par l’optimiseur Adam à partir des gradients du programme lui-même, à 600 pas par seconde pendant que vous dessinez.
Une forme que l’on peut paralléliser
Le même programme s’exécute sur 100 000 documents JSON à la fois avec WebGPU. Sur ce même Mac, la page annonce par exemple 10 559 documents trouvés en 12 millisecondes, contre 21 pour l’expression régulière native du navigateur sur un seul fil. Le dépôt précise que, répartie sur huit fils, elle va aussi vite quand les recherches s’enchaînent, et plus vite à la frappe ; la page ne prétend pas battre RegExp, et cette honnêteté est assez rare pour être notée.
Où en est l’idée
Ce qui était déjà connu
Un réseau à ReLU représente exactement des conditions et des calculs affines par morceaux. Des programmes se compilent en poids de transformer (RASP, Tracr, Percepta). Un circuit peut être appris, puis figé en portes exactes.
Ce que Handwired a montré
Une interface complète exécutée à chaque image par un réseau écrit à la main, avec un contrat d’exactitude vérifié par des essais. Des coefficients entraînés en direct à l’intérieur d’un programme. Un programme exécuté sur 100 000 documents en parallèle.
Ce qui reste à démontrer
La conversion automatique d’un code existant. L’apprentissage de règles qui se généralisent. La greffe d’un programme exact dans un modèle de langage. Un gain de performance général face au code classique.
Mesures : l’état du projet tenu dans le dépôt. Taille du Mini OS et essais du runtime relancés par nos soins le 15 octobre 2026.
Quelles conséquences pour l’IA et le logiciel ?
La programmation différentiable
Dériver du code n’est pas nouveau : JAX, PyTorch ou le langage de shaders Slang savent différencier des programmes qui contiennent des conditions. Handwired met les décisions elles-mêmes dans la même matière que les poids d’un réseau, si bien que les nombres du programme peuvent être « dégelés », pourvu qu’on les range parmi ses entrées. Dans l’exemple, la dérivée du résultat par rapport à 10 vaut isAbove4, et par rapport à 12, 1 − isAbove4 : les valeurs des branches s’apprennent. On imagine des marges, des durées d’animation ou des raideurs de ressort réglées d’après ce que font les utilisateurs, plutôt qu’à l’œil.
L’optimisation des logiciels
Écrit en matrices, un programme hérite de l’outillage des réseaux de neurones : fusion d’opérations, matrices creuses, cartes graphiques. Dans la recherche JSON, un second générateur de code pour la carte graphique range sous forme de bits les valeurs dont il a prouvé qu’elles valent 0 ou 1 ; un motif de 32 états passe de 945 à 5,2 millisecondes quand les recherches s’enchaînent, sans changer une ligne du programme.
Les modèles de langage
Un modèle de langage prédit des chiffres plutôt qu’il ne les calcule, et la parade actuelle consiste à lui faire appeler un outil, comme le font les agents IA ou, dès 2023, Toolformer : le calcul sort alors du réseau. Un module compilé resterait à l’intérieur, dans les mêmes matrices, traversé par les mêmes gradients, et il calculerait juste. Percepta affirme qu’un transformer peut exécuter un programme pas à pas sans se tromper ; mais personne n’a encore montré comment un programme Handwired se brancherait sur un modèle déjà entraîné, ni ce que l’entraînement lui ferait.
Les architectures hybrides
Le raisonnement algorithmique neuronal propose d’associer des encodeurs appris à un processeur qui imite un algorithme classique. Handwired fournit un processeur écrit plutôt qu’appris, exact, exprimé dans la même représentation que le reste du réseau : la frontière entre la partie écrite et la partie apprise se place où l’on veut, poids par poids.
L’interprétabilité
La fenêtre Monitor du Mini OS affiche chaque cellule, chaque opération et chaque sortie du réseau avec sa valeur courante, et c’est le même réseau qui décide quelles lignes de cette liste s’affichent. Dans un réseau entraîné, on cherche ce que signifie un poids ; ici, chaque poids a un sens parce que quelqu’un l’a écrit. La perspective la plus intéressante est ailleurs : entraîner un programme dont chaque poids était lisible, puis lire l’apprentissage comme on lit une modification de code.
Quelles sont les limites de Handwired ?
Les performances
Un réseau calcule tout, à chaque fois : les deux branches de chaque condition, chaque tour de chaque boucle déroulée. Une interface seule ne profite pas de la carte graphique : en septembre, sur une version d’alors trois fois plus lourde, une évaluation du Mini OS prenait 2,2 millisecondes avec WebGPU contre 81 microsecondes sur le processeur, car le shader exécutait une à une les instructions d’une évaluation. Le GPU ne l’emporte qu’à partir d’une vingtaine de copies indépendantes exécutées ensemble. Un programme de 2,4 millions d’opérations demande en outre trois secondes de construction et 4 à 5 Go de mémoire.
Les gradients inutilisables
Demandons au runtime du dépôt la dérivée du résultat de l’exemple par rapport à a : elle vaut 0 pour a = 4 comme pour a = 6, et −2 pour a = 5, simple artefact de la pente choisie au coin d’une ReLU. Une décision franche est une marche, et la pente d’une marche est nulle partout où elle existe : elle n’indique jamais dans quel sens déplacer un seuil. L’arrondi à 0 ou 1 refuse d’ailleurs la rétropropagation, sauf à choisir l’estimateur « straight-through », qui fait comme si la marche était une droite. Le bilan interne du projet le dit sans détour : une exécution exacte n’est pas un gradient utile, et les expériences qui apprennent des décisions entraînent des versions adoucies avant de les durcir. On sait apprendre des valeurs continues à travers le programme exact ; apprendre une décision oblige à quitter, le temps de l’entraînement, la version exacte.
La généralisation
La galerie apprise est ajustée à un croquis à la fois, sans jeu de test séparé, et ne sait pas composer une page pour une taille de fenêtre qu’elle n’a jamais vue. Une expérience du laboratoire va plus loin : 121 paramètres partagés, entraînés sur des collections de 4 et 8 photos, proposent la largeur de chaque photo dans des collections de 6 et 12, que du JavaScript ordinaire met ensuite en page. Mais ils apprennent un objectif écrit par l’équipe, pas un goût humain.
Les garanties perdues
L’exactitude repose sur des promesses, intervalles, grilles, groupes dont un seul membre vaut 1, que le développeur déclare et que les essais vérifient. Dès qu’on dégèle les poids, plus aucun programme JavaScript ne leur correspond, et la référence des essais disparaît. Il suffit qu’un entraînement déplace le biais −5 de l’exemple jusqu’à −4,8 pour que isAbove4 vaille 0,8 quand a = 5, et le résultat 10,4. Même l’ordre des instructions compte : les sommes en flottants n’étant pas associatives, le projet a construit 300 programmes aléatoires dans deux ordres, et leurs gradients ont différé, ne serait-ce que d’un arrondi, dans 1 106 rétropropagations sur 1 200.
La capacité fixe
Tout est borné d’avance : pas d’allocation dynamique, pas de boucle sans borne connue d’avance, une capacité maximale par liste. Un programme qui manipule du texte ou des structures de taille libre doit d’abord être repensé dans ces termes.
Sources : le bilan interne du projet et l’état de ses mesures.
Programmer ou entraîner : faudra-t-il encore choisir ?
En 2017, Andrej Karpathy décrivait un « Software 2.0 » dans lequel les poids d’un réseau remplaceraient peu à peu le code, partout où des exemples suffisent à décrire un comportement. Le mouvement a eu lieu : la vision, la traduction ou la parole sont devenues des poids, et les modèles écrivent aujourd’hui une bonne part du code restant. Handwired propose un autre chemin : un programme devient des poids sans passer par les données ni perdre sa signification, et ces poids peuvent ensuite apprendre.
Le projet n’en montre aujourd’hui qu’une moitié avec certitude, des poids écrits à la main, exacts, et un apprentissage limité à des coefficients continus. Il rend pourtant concrète une question que l’on posait jusqu’ici en théorie. Si le code et les poids sont la même matière, rien n’oblige plus à choisir entre l’exactitude d’un programme et la souplesse d’un modèle : on peut écrire tout ce que l’on sait, figer ce qui doit rester vrai, et ne laisser aux données que ce que l’on ignore. Le développeur déciderait alors, poids par poids, de ce qu’il écrit et de ce qu’il confie à l’apprentissage. Ce jour-là, écrire un programme et entraîner un modèle ne seraient plus que deux gestes portés sur le même objet.


