Un âne ne se cogne jamais deux fois la tête sur la même pierre. Il semble que ChatGPT et les ânes aient ceci en commun : ChatGPT n'est pas un imbécile, il ne fait pas toujours la même erreur. Au lieu de cela, il fait une erreur différente à chaque fois. C'est bien ? Essayez de le forcer à faire toujours la même erreur. Mais ce n'est pas possible.
Nous observons que les modèles génératifs d'OpenAI (DALL-E, ChatGPT, ...) ne peuvent pas être contrôlés pour agir de manière déterministe. En d'autres termes, ils produisent des résultats incohérents même lorsque leur température, le paramètre qui contrôle leur "créativité", est réglée sur zéro. Vous trouverez plus d'informations sur la température dans la documentation de l'API OpenAI. Plutôt que de proposer des solutions, cet article de blog vise à expliquer ce comportement. Parce que la connaissance, c'est le pouvoir, n'est-ce pas ?
Mais quelle est la pertinence de cette question ? Cette question revient-elle régulièrement ? Apparemment, oui. Elle est actuellement en tête de la liste des FAQ de l'OpenAI GPT.
Il est probable que, si vous construisez une application qui s'appuie sur les résultats d'un modèle GPT, vous souhaitiez (au moins dans une phase de test) que le modèle se comporte de manière déterministe afin de pouvoir compter sur un comportement reproductible dans une certaine mesure. A titre d'exemple :
Mais trêve d'introduction, parce que nous apprécions à la fois la structure et la démesure, nous allons maintenant analyser le comportement des modèles GPT en vous faisant passer de l'observation à la compréhension , puis à l'explication . Nous devons entrer dans un niveau de détail assez élevé, mais grâce à une visualisation simplifiée, nous espérons vous épargner la nécessité de connaître des concepts mathématiques compliqués. Vive les simplifications !
Pour résumer à nouveau, la question à laquelle nous allons répondre ici se résume à :
"pourquoi je n'obtiens pas systématiquement les mêmes réponses lors d'un appel à n'importe quelle API générative OpenAI lorsque la température est de 0 ?
Il convient de noter que, bien que des discussions et des ressources pertinentes sur ce sujet puissent être trouvées ;
Nous avons estimé qu'aucune de ces explications n'était satisfaisante pour apporter une réponse complète et exhaustive. C'est une bonne nouvelle car cela nous permet de combler le vide et de vous offrir cette douce satisfaction.
"Il est difficile de comprendre exactement le fonctionnement d'un système à boîte noire. Mais si la boîte noire a évolué à partir de boîtes qui étaient, vous savez, moins noires et plus transparentes, alors certaines hypothèses de qualité peuvent encore être faites."
- quelqu'un, à un moment donné, peut-être
Comme le montre clairement la fausse citation ci-dessus, nous ne pouvons pas savoir exactement comment les LLM (Large Language Models) ChatGPT ou GPT-4 fonctionnent sous le capot (par exemple, l 'article GPT-4). Mais grâce à leurs prédécesseurs plus transparents (par exemple l'article GPT-2 [2] et le code source ouvert) et à leurs concurrents à code source ouvert (par exemple l'article LLaMA), nous connaissons l'essentiel de l'architecture basée sur les transformateurs.
Ci-dessous, nous nous concentrons sur les parties de l'architecture que nous estimons les plus pertinentes pour l'observation des comportements non déterministes. N'hésitez pas à parcourir rapidement cette partie et à passer à la section "Explication" si vous connaissez les bases de l'architecture des modèles génératifs textuels.
Premièrement, un passage de l'inférence vers l'avant à travers le réseau LLM fournit un seul "jeton" qui représente "l'unité de texte la plus granulaire que le modèle comprend" (peut être considéré comme une syllabe, mais peut tout aussi bien représenter un mot dans son ensemble). Pour des raisons d'interprétabilité, nous considérons que chaque "jeton" de notre histoire représente un mot entier. Cette simplification n'a aucune incidence sur les conclusions ultérieures.
Ceci étant dit, un réseau LLM possède un vocabulaire de tokens et grâce à sa compréhension apparemment magique du texte d'entrée, il est très doué pour indiquer quels tokens de son vocabulaire sont les plus susceptibles de suivre l'entrée donnée. Cette indication se fait par l'attribution de probabilités (voir le dessin ci-dessus). Par exemple, P(token_0) représente la probabilité estimée que le mot mâcher (représenté par le token_0) suive la séquence donnée de tokens d'entrée ( la vache est ...). La probabilité du mot bowling (représenté par le token_50.256) est, espérons-le, plus faible que celle du mot chewing ou grazing dans ce contexte.
Nous soulignons que, pour qu'un LLM génère une séquence complète de texte, il doit passer itérativement à travers le réseau plusieurs fois : chaque passage avant sert à sélectionner exactement un jeton qui, à son tour, contribue à déterminer le jeton suivant.
La pièce cruciale du puzzle vient maintenant de la façon dont un seul jeton de sortie est choisi à l'aide de cette liste de probabilités de jetons. Ci-dessous, nous plongerons brièvement dans le monde de l'échantillonnage. Pour une compréhension plus approfondie des méthodes d'échantillonnage, nous vous recommandons de lire cet article du blog Hugging Face.
Les LLM modernes utilisent (une variante de) l'échantillonnage top-p (c'est-à-dire l'échantillonnage du noyau introduit dans cet article de 2018) pour échantillonner la réponse. Cette méthode ne prend en compte que les jetons dont la probabilité cumulée dépasse la probabilité p et redistribue ensuite la masse de probabilité sur les jetons restants de manière à ce que la somme des probabilités soit égale à 1. Si vous vous dites "attendez quoi", félicitations, vous n'êtes pas statisticien ! N'hésitez pas à relire cette phrase et à passer ensuite à l'explication visuelle plus compréhensible ci-dessous :
Supposons que nous ayons fixé p=0,92. Lors de la première passe à partir du seul mot "le", nous avons besoin de 6 jetons pour dépasser cette probabilité de 92 % (ils totalisent 94 %). Nous pouvons échantillonner un jeton parmi ces six mots en considérant leurs probabilités redistribuées (où le mot gentil aura la plus grande chance d'être choisi). Pour la passe suivante, nous constatons que les trois jetons les plus probables dépassent déjà facilement le seuil de 92 % et que le jeton éventuel est donc échantillonné à partir de ces trois jetons seulement.
Ce qui est intéressant, c'est que le nombre de jetons à échantillonner dépend dynamiquement du niveau d'"incertitude" du modèle. Si le modèle considère qu'un petit sous-ensemble de jetons est le plus pertinent (par exemple, parce que les jetons d'entrée se composent de "the", "car", ce qui restreint le contexte), il ne prélèvera que ces jetons.
Anecdote pertinente : il y a quelques années, à ML6, nous avons créé un "résumé des conditionsgénérales" de base qui, par hasard, a généré le mot "lait" dans un résumé et s'est complètement orienté vers la nourriture, simplement parce qu'il n'utilisait pas l'échantillonnage de noyaux.
Nous comprenons maintenant à quoi se réfère le paramètre top_p de la documentation de l'API OpenAI. Notez que par défaut, ce paramètre fixe p=100%, ce qui signifie que tous les tokens de sortie sont pris en compte. Si par contre p=0%, le premier token à être vérifié, qui est algorithmiquement celui avec la plus grande probabilité, sera toujours choisi car il dépasse immédiatement le seuil super bas par lui-même.
D'accord, mais quel rôle joue le paramètre de la température ?
Imaginez que vous disposiez d'un ensemble de jetons de sortie (éventuellement avec une probabilité redistribuée si vous jouez avec le paramètre top_p) à échantillonner :
Le rôle de la température est de contrôler les poids relatifs dans la distribution des probabilités. Elle contrôle la mesure dans laquelle les différences de probabilité jouent un rôle dans l'échantillonnage. Prenons l'exemple ci-dessus : pour la séquence d'entrée de jetons "Le", nous nous attendrions (par défaut) à ce que le mot "beau" ait 75 % de chances d'être choisi P("beau")=75 %. C'est ce qui se produit à la température t=1. Ce paramètre peut être choisi entre 0 et 2.
À la température t=0, cette technique d'échantillonnage se transforme en ce que nous appelons la recherche avide/l'échantillonnageargmax , où le jeton ayant la probabilité la plus élevée est toujours sélectionné (ici : P("nice")=100% ).
À la température t=2, la différence entre les jetons les plus probables et les moins probables est réduite au moment de l'échantillonnage. Pour l'exemple de gauche ci-dessus, cela se traduirait par : P("gentil")=58% , P("chien")=32% , P("voiture")=10%.
Pour les personnes intéressées, la formule permettant de calculer les probabilités d'échantillonnage affectées par la température t est ajoutée ci-dessous (où K représente le nombre total de jetons pris en compte dans l'échantillonnage) :
Vous pouvez maintenant vous considérer comme un véritable guerrier des API génératives de l'OpenAI car l'extrait ci-dessous n'a plus de secrets pour vous.
Notez la phrase "we generally recommend altering this or top_p/temperature but not both" (nous recommandons généralement de modifier ceci ou top_p/temperature mais pas les deux) . Il s'agit simplement d'une suggestion visant à rendre vos modifications plus ou moins interprétables au fur et à mesure que vous jouez avec les valeurs. Le fait de fixer l'une ou l'autre de ces valeurs à leurs limites déterministes (c'est-à-dire p=0 ou top_p=0) a le même effet.
Rappelons que : par défaut, l'échantillonnage se fait sur l'ensemble du vocabulaire des jetons (top_p=1) et la distribution de probabilité n'est pas affectée (temperature=1).
En utilisant les connaissances ci-dessus, nous savons exactement ce qui devrait se passer si nous fixons température=0, à savoir : le jeton ayant la probabilité la plus élevée sera choisi.
Mais que se passe-t-il si, à un moment donné de la génération, la foudre frappe et que deux jetons se voient attribuer exactement la même probabilité ?
Le cas où au moins deux jetons ont la même probabilité peut sembler improbable, mais certains facteurs contribuent à cette probabilité qui n'est pas si faible :
Si un autre jeton est sélectionné une fois, la probabilité de suivre les passes suivantes sera directement affectée, ce qui se traduira par un "chemin de réponse" différent. Vous pouvez imaginer que si le mot "lait" est sélectionné une seule fois, le résumé des conditions générales sera complètement différent par la suite.
La question suivante est donc évidente : si deux jetons ont exactement la même probabilité, que se passe-t-il ensuite ?
Lorsque les ordinateurs doivent faire un choix entre plusieurs options également valables/probables, le pouvoir décisif du "pile ou face" est confié à une graine. La graine contrôle la valeur d'un générateur de nombres pseudo-aléatoires. Une simplification plus intuitive est présentée ci-dessous.
Nous nous attendons donc à ce que ces graines affectent la manière dont les situations ⚡ sont traitées. Généralement, lorsque vous hébergez votre propre algorithme/modèle, vous pouvez fixer cette graine de sorte que les décisions aléatoires "à pile ou face" soient toujours les mêmes.
Un agriculteur avisé aurait pu dire un jour :
si vous ne contrôlez pas ce que vous semez, comment pouvez-vous contrôler ce que vous récoltez ?"
- agriculteur hypothétique, probablement
Et mon Dieu, cet agriculteur hypothétique aurait mis le doigt sur le proverbial clou sur la tête. Si vous ne pouvez pas corriger la semence utilisée pour déterminer les décisions "à pile ou face" au sein de votre système, alors le contrôle total est inaccessible.
Nous avons donc expliqué pourquoi le déterminisme reste hors de portée lorsque l'on travaille avec les API les plus populaires de l'OpenAI.
Quod erat demonstrandum ?
D'accord, donc des graines. C'est tout un programme. Ce n'est pas si surprenant. La question qui subsiste est la suivante :
Pourquoi ne pouvez-vous pas passer une graine ? Comme le dit ce type sur Twitter:
il est assez fou que l'API d'OpenAI n'ait pas de paramètre de "graine aléatoire". Le comportement attendu est d'obtenir des résultats que l'on ne peut jamais reproduire."
- Sasha Rush (professeur associé à Cornell Tech et chercheur sur les visages qui s'étreignent)
Examinons donc les raisons qui peuvent expliquer le choix de ne pas autoriser le passage d'une graine fixe :
Au cours de ce voyage, nous avons offert des aperçus sur :
Pour des raisons qui ne nous sont pas immédiatement connues, OpenAI ne nous permet pas de définir la graine qui a un pouvoir concluant lorsque la foudre frappe pendant la génération par jetons d'une réponse.
Nous ne saurons peut-être pas pourquoi le déterminisme et donc la reproductibilité sont empêchés dans une certaine mesure. Mais au moins, le comportement est plus clair maintenant. Et cela nous rassure un peu.
N'est-ce pas ?