Le non-déterminisme dans GPT-4 est causé par Sparse MoE

Nous savons déjà que GPT-4/GPT-3.5-turbo est non-déterministe, même à une température de 0.0. C'est un comportement étrange si on est habitué aux modèles décodeurs denses, où une température de 0 devrait impliquer un échantillonnage avide, donc un déterminisme complet, parce que les logits pour le token suivant devraient être une fonction pure de la séquence d'entrée et des poids du modèle.

💡
Le déterminisme est une philosophie selon laquelle tous les événements, y compris les actions, les choix et les décisions humaines, sont déterminés par des causes précédentes. Cela signifie que, étant donné certaines conditions initiales, il n'y a qu'une seule manière dont les choses peuvent se dérouler. Le déterminisme soutient donc que chaque événement est la conséquence inévitable de ses causes antérieures.

Lorsqu'on a demandé aux membres du personnel technique d'OpenAI de clarifier ce comportement lors des tables rondes pour développeurs durant leur tournée mondiale, leurs réponses étaient quelque chose comme :

"Honnêtement, nous sommes aussi confus. Nous pensons qu'il peut y avoir un bug dans nos systèmes, ou une certaine non-déterminisme dans les calculs de virgule flottante optimisés..."

Trois mois plus tard, alors que je lisais un article lors d'un vol ennuyeux de retour, j'ai trouvé ma réponse.

Dans le récent article sur Soft MoE, il y avait un passage intéressant dans la section 2.2 qui a suscité une connexion.

"Dans les contraintes de capacité, toutes les approches Sparse MoE acheminent les tokens en groupes d'une taille fixe et imposent (ou encouragent) un équilibre au sein du groupe. Lorsque les groupes contiennent des tokens de différentes séquences ou entrées, ces tokens sont souvent en compétition les uns contre les autres pour les places disponibles dans les tampons d'experts. En conséquence, le modèle n'est plus déterministe au niveau de la séquence, mais seulement au niveau du lot, car certaines séquences d'entrée peuvent affecter la prédiction finale pour d'autres entrées."

Il est désormais de notoriété publique que GPT-4 est un modèle Mixture of Experts (MoE). Etant donné que GPT-4 a été formé avant le deuxième trimestre de 2022, et que les Sparse Mixture-of-Experts existaient bien avant cela, je pense que l'hypothèse suivante est justifiée :

Le backend de l'API GPT-4 est hébergé avec une inférence par lots. Bien que certains des aléas puissent être expliqués par d'autres facteurs, la vaste majorité du non-déterminisme dans l'API est expliquable par son architecture Sparse MoE qui échoue à imposer un déterminisme par séquence.

Vérification de l'hypothèse

Mais comment vérifier cette affirmation ? La première étape a été de demander à GPT-4 d'écrire un script pour tester notre hypothèse. Suite à quelques problèmes rencontrés (comme une réponse lente de l'API et des modèles de complétion qui tronquaient leurs réponses très tôt), nous avons finalement obtenu confirmation.

import os
import json
import tqdm
import openai
from time import sleep
from pathlib import Path

chat_models = ["gpt-4", "gpt-3.5-turbo"]
message_history = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "Write a unique, surprising, extremely randomized story with highly unpredictable changes of events."}
]

completion_models = ["text-davinci-003", "text-davinci-001", "davinci-instruct-beta", "davinci"]
prompt = "[System: You are a helpful assistant]\n\nUser: Write a unique, surprising, extremely randomized story with highly unpredictable changes of events.\n\nAI:"

results = []

import time
class TimeIt:
    def __init__(self, name): self.name = name
    def __enter__(self): self.start = time.time()
    def __exit__(self, *args): print(f"{self.name} took {time.time() - self.start} seconds")

C = 30  # number of completions to make per model
N = 128 # max_tokens
# Testing chat models
for model in chat_models:
    sequences = set()
    errors = 0 # although I track errors, at no point were any errors ever emitted
    with TimeIt(model):
        for _ in range(C):
            try:
                completion = openai.ChatCompletion.create(
                    model=model,
                    messages=message_history,
                    max_tokens=N,
                    temperature=0,
                    logit_bias={"100257": -100.0}, # this doesn't really do anything, because chat models don't do <|endoftext|> much
                )
                sequences.add(completion.choices[0].message['content'])
                sleep(1) # cheaply avoid rate limiting
            except Exception as e:
                print('something went wrong for', model, e)
                errors += 1
    print(f"\nModel {model} created {len(sequences)} ({errors=}) unique sequences:")
    print(json.dumps(list(sequences)))
    results.append((len(sequences), model))
# Testing completion models
for model in completion_models:
    sequences = set()
    errors = 0
    with TimeIt(model):
        for _ in range(C):
            try:
                completion = openai.Completion.create(
                    model=model,
                    prompt=prompt,
                    max_tokens=N,
                    temperature=0,
                    logit_bias = {"50256": -100.0}, # prevent EOS
                )
                sequences.add(completion.choices[0].text)
                sleep(1)
            except Exception as e:
                print('something went wrong for', model, e)
                errors += 1
    print(f"\nModel {model} created {len(sequences)} ({errors=}) unique sequences:")
    print(json.dumps(list(sequences)))
    results.append((len(sequences), model))

# Printing table of results
print("\nTable of Results:")
print("Num_Sequences\tModel_Name")
for num_sequences, model_name in results:
    print(f"{num_sequences}\t{model_name}")

Les résultats empiriques (3 tentatives, N=30, max_tokens=128) montrent clairement que le nombre de complétions uniques de GPT-4 est ridiculement élevé - presque toujours non-déterministe avec des sorties plus longues. Cela confirme presque certainement que quelque chose se passe avec GPT-4. De plus, tous les autres modèles qui ne s'effondrent pas dans une boucle répétitive inutile font preuve d'un certain degré de non-déterminisme.

Implications

C'est en réalité assez incroyable, pour plusieurs raisons :

Nous sommes très en retard

Si le non-déterminisme est une caractéristique inhérente à l'inférence par lots avec Sparse MoE, alors ce fait devrait être visiblement évident pour toute personne travaillant avec des modèles de ce type. Étant donné que la grande majorité des utilisateurs de GPT-4 n'ont toujours aucune idée de ce qui cause l'instabilité de leurs appels API, on peut conclure que soit je me trompe complètement, soit trop peu de personnes connaissent quoi que ce soit sur les modèles MoE pour propager cette explication dans la conscience publique.

GPT-3.5-Turbo pourrait aussi être MoE

J'ai entendu une rumeur, une fois, que 3.5-turbo partageait la même architecture que GPT-4 ; mais avec beaucoup moins de paramètres que ce dernier, voire même que GPT-3.

Conclusion

Tout le monde sait que les modèles GPT d'OpenAI sont non-déterministes à une température de 0. Il est généralement attribué à des inexactitudes dans les opérations de virgule flottante optimisées par CUDA non-déterministes. L'inférence par lots dans les modèles Sparse MoE est la cause principale du non-déterminisme dans l'API GPT-4.

Les appels à l'API GPT-4 (et potentiellement à certains modèles 3.5) sont beaucoup plus non-déterministes que d'autres modèles OpenAI. GPT-3.5-turbo pourrait aussi être MoE, en raison de la vitesse, du non-déterminisme et de la suppression des logprobs.

Source