Comment fait-on entrer du son dans un modèle de langage
Un modèle de langage attend une file de nombres entiers. Le son en fournit seize mille par seconde. Voici la machinerie qui réconcilie les deux.
Ceci est une note de lecture. Le raisonnement, les exemples et la plupart des chiffres viennent de l’article de Václav Volhejn publié par Kyutai, qui explique comment on transforme du son en jetons pour un modèle de langage. Je l’ai lu, puis j’ai réimplémenté la mécanique pour vérifier que je l’avais comprise. Ce que j’ajoute est là : les ablations à la fin de chaque section, où je casse un mécanisme pour voir ce qu’il tenait.
Le son résiste
En 2025, la plupart des assistants vocaux ne comprennent pas vraiment la voix. Ils transcrivent, réfléchissent en texte, puis lisent leur réponse à voix haute. C’est efficace, mais c’est un emballage autour d’un modèle de texte, pas une écoute.
Volhejn propose un test cruel : parlez à un modèle nativement audio avec une voix très aiguë, et demandez-lui si votre voix est grave ou aiguë. Aucun ne sait répondre. L’information était pourtant dans le signal.
Du côté du texte, tout est réglé depuis longtemps. En 2015 déjà, Karpathy entraînait un réseau récurrent de trois couches sur un seul GPU et obtenait du code et du LaTeX plausibles, caractère par caractère.
Le son ne se laisse pas faire, et la raison tient en un nombre. Dix secondes d’audio, c’est 160 000 échantillons. WaveNet les prédit un par un : le résultat sonne comme une vraie voix humaine, avec le bon grain et la bonne respiration, mais ne produit presque jamais un mot correct.
Volhejn mène l’expérience naïve jusqu’au bout, en donnant du son brut à nanoGPT. Deux chiffres suffisent à expliquer l’échec : la fenêtre de contexte de 2 048 jetons représente 128 millisecondes — le modèle ne voit même pas un mot entier — et générer dix secondes demande trente minutes sur une H100.
Le texte, c’est une boîte de LEGO : les briques existent déjà, en nombre limité, on assemble. Le son, c’est du sable. Chaque grain ne veut rien dire tout seul, et pour écrire le mot bonjour il faut en placer seize mille par seconde.
La conclusion est immédiate : il faut réduire le nombre de décisions. Il faut compresser.
Comprimer avant de modéliser
L’outil qui comprime et décomprime s’appelle un codec audio neuronal. Le mot est emprunté au MP3, mais ici il désigne simplement un découpeur de son en jetons.
L’autoencodeur
Personne ne dit au modèle ce qu’il doit mettre dans son résumé. Il doit le découvrir seul, sous la contrainte que le résumé est trop petit pour tout contenir.
On vous demande de résumer un roman de trois cents pages sur une fiche bristol. Puis on donne votre fiche à quelqu’un qui doit réécrire le roman entier. Si le résultat ressemble à l’original, c’est que votre fiche contenait ce qui compte.
Pourquoi il faut quantifier
Un autoencodeur ordinaire produit des nombres à virgule, en quantité infinie. Un modèle de langage ne sait pas faire ça : il sait choisir un élément dans une liste finie. Il lui faut un vocabulaire.
On ne vous donne pas de la peinture liquide, mais une boîte de 1 024 crayons numérotés. Vous perdez un peu de fidélité, mais vous pouvez désormais décrire votre dessin entier au téléphone, en récitant une suite de numéros.
Les centres ne sont pas décidés à l’avance : ils sont appris, par une procédure proche des k-moyennes. Et il y a un détail malin — si un centre n’est utilisé par personne pendant un moment, on le téléporte à la position d’un vecteur pris au hasard. Sans cela, un centre mal placé au départ resterait mort à jamais.
L’estimateur direct, ou le mensonge assumé
Voici le vrai obstacle. On veut quantifier pendant l’entraînement, pour que l’encodeur apprenne à produire des vecteurs faciles à arrondir :
z = encoder(x)
z_quantized = to_nearest_cluster(z) # on arrondit
loss = reconstruction_loss(x, decoder(z_quantized))
Mais prendre le centre le plus proche n’est pas dérivable. Aucun gradient ne remonte jusqu’à l’encodeur. La solution est d’une honnêteté rafraîchissante : on règle le problème en faisant comme s’il n’existait pas.
residual = z - to_nearest_cluster(z)
z_quantized = z - residual.detach() # .detach() : oublie le gradient
À l’aller, la valeur transmise au décodeur est exactement la même. Au retour, le gradient traverse l’arrondi comme s’il n’était pas là. C’est l’estimateur direct.
Ce raisonnement ne tient que si les vecteurs restent proches de leur centre. On ajoute donc une pénalité proportionnelle à cette distance : la perte d’engagement. L’ensemble s’appelle un VQ-VAE — dont le variationnel est un vestige historique qui ne signifie plus rien.
Ce qui casse quand on retire chaque pièce
Voilà ma part du travail. J’ai entraîné un VQ-VAE en dimension latente 2 sur trois classes de Fashion MNIST, puis j’ai désactivé chaque mécanisme, un par un.
| Configuration | MSE test | Centres actifs | Perplexité |
|---|---|---|---|
| référence | 0,02707 | 32/32 | 25,2 |
| sans estimateur direct | 0,06844 | 32/32 | 21,7 |
| sans perte d’engagement | 0,03310 | 32/32 | 16,1 |
| sans téléportation | 0,02866 | 24/32 | 19,2 |
| codebook de 4 centres | 0,04227 | 4/4 | 3,9 |
Trois lectures.
L’estimateur direct est le mécanisme indispensable. Sans lui, l’erreur est multipliée par 2,5. C’est cohérent : l’encodeur n’apprend plus que par la perte d’engagement, ce qui est un signal bien plus pauvre.
La téléportation ne change presque rien à l’erreur, mais tue un quart du codebook. Vingt-quatre centres sur trente-deux. C’est exactement le symptôme annoncé : un centre jamais choisi ne reçoit aucune mise à jour, donc ne bouge pas, donc ne sera jamais choisi. Le coût est invisible dans la MSE et bien réel dans le débit — on paie huit centres pour rien.
La perte d’engagement se voit surtout dans la perplexité, qui tombe de 25 à 16. L’erreur bouge peu, mais le codebook est nettement moins bien réparti.
La quantification résiduelle
Pour gagner en fidélité, on pourrait augmenter le nombre de centres. Mais il faut comparer chaque vecteur à chacun d’eux, et ça devient vite prohibitif.
L’astuce est combinatoire. Plutôt qu’un million de centres, on en crée deux ensembles de 1 024. Chaque vecteur est décrit par un couple d’entiers, ce qui donne plus d’un million de combinaisons pour seulement 2 048 centres stockés.
Pour identifier tous les élèves d’une école, on n’invente pas un identifiant par élève : on combine un prénom et un nom.
Reste à savoir comment répartir l’information. La réponse est déjà dans le code plus haut, sous le nom de résidu : l’écart entre le vecteur exact et sa version arrondie, c’est-à-dire ce que le premier arrondi n’a pas su capturer. On le quantifie à son tour, puis on quantifie le résidu du résidu.
Une balance à plateaux. Pour peser 780 g, vous posez le poids de 500 g, il manque 280 g ; celui de 200 g, il manque 80 g ; puis 50, puis 20, puis 10. Chaque poids ne corrige pas le total, il corrige l’erreur des précédents. Et vous vous arrêtez quand la précision vous suffit.
Sur mes images, quatre niveaux de 64 centres donnent ceci :
| Niveaux | MSE | Norme du résidu restant |
|---|---|---|
| 1 | 0,02280 | 2,31 → 0,094 |
| 2 | 0,01807 | → 0,024 |
| 3 | 0,01712 | → 0,0076 |
| 4 | 0,01688 | → 0,0033 |
Le résidu est divisé par 700 entre le début et la fin, et chaque niveau rapporte environ la moitié du gain précédent. C’est la signature d’une RVQ en bonne santé — je vais y revenir longuement dans l’article suivant, parce que c’est précisément cette signature qui m’a manqué pendant des heures.
Une limite à garder en tête : la qualité maximale reste celle de la reconstruction sans quantification du tout. Si l’autoencodeur est mauvais, aucune quantification ne le sauvera. On améliore d’abord l’autoencodeur.
L’idée n’est pas neuve — SoundStream l’a appliquée aux codecs audio en 2021, et elle circule dans le traitement du signal depuis les années quatre-vingt.
Le multi-niveaux n’a pas de réponse canonique
Avec un codec à huit niveaux, chaque extrait devient un tableau à deux dimensions : le temps d’un côté, les niveaux de l’autre. Or un modèle de langage attend une seule file. Comment gérer huit jetons par instant ?
| Stratégie | Principe | Avantage | Inconvénient |
|---|---|---|---|
| Aplatissement | un jeton par niveau et par instant, à la queue leu leu | très simple, le modèle reste un modèle de texte ordinaire | la séquence est multipliée par le nombre de niveaux |
| Motif parallèle | tous les niveaux d’un instant prédits en même temps | aucune perte de compression temporelle | le modèle doit tout décider d’un coup, sans hiérarchie |
| Motifs décalés | les niveaux sont retardés les uns par rapport aux autres | compromis, testé dans MusicGen | réglage délicat |
| Motifs hiérarchiques | un modèle par échelle, ou entrelacement complexe | meilleurs résultats rapportés en 2025 | complexité importante |
En 2025, aucune ne s’est imposée. Pour qui débute, c’est une bonne nouvelle : il n’y a pas de solution canonique qu’on serait censé connaître, il y a un espace de conception encore ouvert.
Mimi, et le jeton qui porte le sens
Quatre années de recherche ont produit bien mieux que le codec artisanal. Mimi, développé chez Kyutai pour Moshi, apporte trois choses.
Une perte adverse plutôt que spectrale : un discriminateur apprend à repérer les reconstructions, le codec apprend à le tromper. Comme le juge s’améliore en même temps que l’élève, il n’y a pas de recette figée à exploiter.
Un abandon aléatoire de niveaux pendant l’entraînement — le RVQ dropout. Le codec apprend à ne pas dépendre de tous ses niveaux, et l’on peut donc tronquer librement à l’usage. J’ai mesuré ce que ça coûte : avec dropout, 0,01688 de MSE à quatre niveaux ; sans, 0,01560. On paie environ 8 % de qualité pour la liberté de tronquer. Sans lui, il faudrait réentraîner un modèle par nombre de niveaux.
Et surtout 12,5 vecteurs par seconde au lieu de 125, soit dix fois moins. Le corpus de 10 000 heures tombe de 134 Go à 54 Go. À fenêtre de contexte égale, le modèle voit dix fois plus loin : là où il apercevait une fraction de mot, il voit une phrase.
Le fond et la forme
Le tout premier niveau de Mimi n’est pas de même nature que les autres. C’est le jeton sémantique, distillé depuis WavLM, et son rôle est de porter le sens sans nécessairement contribuer à la qualité de reconstruction.
L’expérience qui le démontre est élégante. On entraîne un modèle sur les 32 niveaux, puis à la génération on impose de force les jetons sémantiques de l’audio d’origine, en laissant le modèle inventer tout le reste. Le résultat : la voix obtenue est complètement différente, mais elle dit exactement la même chose.
Le jeton sémantique est le scénario du film. Les jetons acoustiques sont le casting, les décors et la lumière. On peut refaire le film avec d’autres acteurs sans changer une ligne du scénario.
Le fossé entre modalités
Le bilan de Volhejn est mesuré. Avec un codec, un modèle de langage tout à fait ordinaire produit de la parole partiellement cohérente ; sans codec, le même modèle exactement produit du grésillement. La démonstration est faite. Mais on reste très loin des modèles de texte.
Moshi modélise trois flux en parallèle : l’audio de l’utilisateur, son propre audio, et un flux de texte qui joue le rôle de monologue intérieur. Les ablations montrent que ce flux textuel aide massivement — et c’est là que l’article devient un peu mélancolique. L’essentiel du raisonnement semble délégué au texte, tandis que les flux audio finissent par ne servir que de reconnaissance et de synthèse vocales intégrées.
Plus d’un an après Moshi, personne n’explique vraiment pourquoi les modèles audio restent en retard. Volhejn appelle ça le fossé entre modalités, et conclut que c’est ce mystère non résolu qui rend le domaine passionnant.
Une piste à surveiller : certains travaux récents se passent entièrement de jetons discrets et travaillent dans un espace latent continu. Si cette approche mûrit, toute la machinerie décrite ici deviendrait un détour historique.
Voilà pour la mécanique telle qu’elle est censée fonctionner.