Loading
J'ai écrit le code et les tests à partir de la même idée fausse, et ils s'accordaient parfaitement. Tout était vert.
Ouvrir l'instrumentQuand une fonction de tri est fausse, cela se voit : la liste n'est pas triée.
Quand une matrice d'attention est fausse, elle ressemble à une matrice d'attention. Chaque valeur est entre 0 et 1, chaque ligne somme à 1, les nombres varient de façon plausible d'un mot à l'autre. Un transformeur subtilement cassé produit une sortie qu'un humain ne distingue pas d'une sortie correcte.
L'instinct habituel — exécuter, regarder la sortie, affirmer ce qu'on voit — n'est donc pas disponible. Pire, il est activement dangereux, puisque la sortie a l'air correcte.
L'idée décisive : on ne teste pas un système numérique contre son intuition. On le teste contre une seconde implémentation qui ne partage rien avec la première, y compris les hypothèses de celui qui l'a écrite.
J'ai écrit le tokenizer WordPiece. Puis ses tests. Les deux sortaient du même modèle mental de ce qu'est la tokenisation.
Ce modèle était faux en un point précis : j'ai donc produit une implémentation fausse et un test faux qui s'accordaient parfaitement. Suite verte, déploiement confiant, et £5 arrivant discrètement au modèle sous la forme £ + 5 pendant des semaines.
Aucun test supplémentaire écrit par moi, ce jour-là, ne l'aurait attrapé. Ils seraient tous nés du même malentendu.
Ce qui a rompu l'impasse, c'est la construction d'un second bert-mini ne partageant aucun code avec le premier : écrit en Python plutôt qu'en TypeScript, à partir de la description publiée plutôt que de l'implémentation existante, exécuté sur les poids fp32 d'origine en fp64 NumPy. Autre langage, autre arithmétique, autre jour, autres hypothèses.
Quand deux implémentations indépendantes divergent, une seule des deux croyances confortables survit, et il faut découvrir laquelle.
Elles ont divergé sur £5. L'oracle avait raison.
Et une fois, l'oracle a eu tort. Sa première version ignorait l'étape où HuggingFace entoure d'espaces chaque caractère CJK pour en faire un mot distinct. Il divergeait du runtime sur 中文 — et cette fois le runtime avait raison. Une fixture versionnée a tranché.
Cet épisode a changé l'usage de l'oracle. Un oracle non validé n'est qu'un second avis, et un oracle confiant et faux est pire que rien : il vous envoie corriger du code déjà correct. L'oracle reproduit donc désormais une référence PyTorch versionnée à 1,324e-6 avant d'être autorisé à juger quoi que ce soit, et cette vérification s'exécute au début de chaque comparaison plutôt que de résider dans la mémoire de quelqu'un.
Avec un oracle digne de confiance, la question devient : sur quoi comparer ? Le premier audit utilisait neuf phrases choisies à la main, et ce n'est pas assez — des phrases choisies à la main partagent les biais de la main.
Le corpus compte désormais 64 phrases choisies pour casser des choses : symboles, contractions, possessifs, CJK, emoji, latin accentué, allemand et français, chemins de fichiers, adresses e-mail, mots isolés, mots répétés, tabulations, un mot qui éclate en treize morceaux, des phrases à la limite de tokens et la paire de référence enregistrée. Chaque étape du pipeline est comparée, pas seulement les nombres finaux :
oracle vs PyTorch reference: 1.324e-06
sentences 64 compared 64 over token limit 0
worst abs error attention 2.251e-04 (bar 1e-03)
worst abs error rollout word mx 5.394e-06 (bar 5e-05)
worst abs error rollout sink 1.442e-05 (bar 1e-04)
worst abs error per-head word mx 1.700e-04 (bar 6e-04)
measured pairs quoted in prompts.ts: 29 hold
PASSLe test le plus fort est aussi le moins astucieux : prendre les 30 522 entrées du vocabulaire, repasser chacune seule dans le tokenizer et exiger qu'elle revienne identique. Cela a trouvé la classe de bug plutôt qu'une instance, et c'est pourquoi je sais qu'aucune dixième entrée cassée ne se cache derrière les neuf.
La première version de ce script de comparaison affichait les erreurs de rollout et par tête sans rien affirmer à leur sujet. Seule l'attention avait un seuil. J'ai donc perturbé le dump de 1e-4 pour voir.
Il a affiché PASS.
Deux étapes entières étaient rapportées et jamais vérifiées. Les chiffres défilaient à chaque exécution, rassurants, et auraient continué de rassurer à travers toute régression n'touchant pas l'attention.
Chaque étape porte désormais une barre, et chaque barre a été vérifiée en cassant délibérément quelque chose :
Chaque bouton corrompt une valeur du dump que le vérificateur lit. Un test qui ne peut pas échouer n'est pas un test : chaque étape a été vérifiée ainsi avant qu'on fasse confiance aux chiffres.
Six mutations, six échecs. Plus une septième : un oracle ayant dérivé de la référence PyTorch interrompt l'exécution avant toute comparaison.
Deux implémentations, c'est deux fois le travail, et cela ne vaut le coup que lorsque votre intuition ne peut réellement pas arbitrer la sortie. Pour une fonction de tri, ce serait absurde. Pour un transformeur quantifié, c'est la différence entre croire vos chiffres et les connaître.
Les tests qui ont attrapé de vrais bugs ici n'étaient pas ceux qui affirmaient des valeurs attendues. Une session précédente a tapé de mémoire trois identifiants de tokens « attendus », et deux étaient faux — un test qui aurait figé l'erreur si quelqu'un lui avait fait confiance. La règle qui en découle est brutale : ne jamais écrire à la main une valeur attendue. Dérivez-la de l'oracle, ou d'une référence versionnée, ou ne l'affirmez pas.
Votre intuition est un générateur d'hypothèses. Ce n'est pas un instrument de mesure, et sur tout ce qui est numérique elle ne devrait jamais être le dernier rempart entre un bug et la production.
Ensuite : un correctif publié pour le problème du puits, mesuré — puis écarté.
Plus à lire
Chaque ligne de rollout se situe entre 0,935 et 0,971 d'entropie normalisée. C'est une distribution uniforme déguisée en classement, et c'était la vue par défaut.
Il divise le problème par deux sans le dissoudre. [CLS] porte 0,63× la norme de valeur d'un token de contenu — pas le quasi-zéro annoncé. Quatre couches ne font pas douze.
En médiane, 24 % de chaque ligne affichée avait déjà été supprimée avant que vous ne la voyiez. Au pire, 95 %. L'arithmétique était juste depuis le début.