Transformers documentation

Contribution de modèle hérité

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.23.0).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Contribution de modèle hérité

Essayez d’abord d’ajouter de nouveaux modèles avec une approche plus modulaire. Cela facilite considérablement la contribution d’un modèle à Transformers !

De nombreux modèles de Transformers sont contributés par des développeurs et des chercheurs. En tant que projet open source, nous nous engageons à permettre à la communauté d’ajouter activement et de manière indépendante de nouveaux modèles.

Lorsque vous ajoutez un modèle à Transformers, vous apprendrez :

  • en savoir plus sur les meilleures pratiques open source
  • sur l'architecture d'un modèle
  • sur les principes de conception de Transformers
  • comment tester efficacement de grands modèles
  • comment utiliser des utilitaires Python comme Black et Ruff pour créer un code propre et lisible

C'est un processus exigeant mais gratifiant.

Ce guide vous guide à travers l'ajout d'un exemple de modèle PyTorch BrandNewLlama à Transformers. Avant de commencer, il est judicieux de vous familiariser avec la bibliothèque.

Aperçu de Transformers

Transformers est une bibliothèque à l’opinion marquée, dotée de sa propre philosophie et de ses propres choix de conception. Ces choix nous aident à faire évoluer et à maintenir Transformers de manière durable.

En savoir plus sur nos principes de conception dans le document Philosophie.

Certains de ces choix de conception sont les suivants :

  • composition > sur-abstraction
  • Le code dupliqué n'est pas toujours mauvais s'il améliore considérablement la lisibilité et l'accessibilité
  • les fichiers de modèle sont autonomes et tout le code de modèle nécessaire se trouve dans le fichier modeling_mymodel.py

Ces choix de conception sont importants pour tous ceux qui interagissent avec le modèle. Il est plus facile de le lire, de le comprendre et de le modifier.

Cette section décrit l’interaction entre les classes de modèle et de configuration ainsi que le style de code de Transformers.

Modèle et configuration

Tous les modèles de Transformers héritent d'une classe de base PreTrainedModel et PreTrainedConfig. La configuration est le plan directeur des modèles.

Il n'y a jamais plus de deux niveaux d'abstraction pour un modèle afin de garder le code lisible. Le modèle d'exemple ici, BrandNewLlama, hérite de BrandNewLlamaPreTrainedModel et de PreTrainedModel. Il est important qu'un nouveau modèle ne dépende que de PreTrainedModel afin qu'il puisse utiliser les méthodes from_pretrained() et save_pretrained().

D'autres fonctions importantes, comme la méthode forward, sont définies dans le fichier modeling.py.

Les têtes de modèle spécifiques (par exemple, la classification de séquences ou la modélisation de langage) doivent appeler le modèle de base dans le passage avant plutôt que d’en hériter, afin de maintenir un niveau d’abstraction faible.

Les nouveaux modèles nécessitent une configuration, par exemple BrandNewLlamaConfig, qui est stockée en tant qu'attribut de PreTrainedModel.

model = BrandNewLlamaModel.from_pretrained("username/brand_new_llama")
model.config

PreTrainedConfig fournit les méthodes from_pretrained() et save_pretrained().

Lorsque vous utilisez PreTrainedModel.save_pretrained(), il appelle automatiquement PreTrainedConfig.save_pretrained() afin que le modèle et la configuration soient enregistrés ensemble.

Un modèle est enregistré dans un fichier model.safetensors et une configuration est enregistrée dans un fichier config.json.

Style de code

Transformers privilégie un code propre et lisible à un style de code plus abstrait. Certains des choix de style de code incluent :

  • Le code doit être accessible aux utilisateurs non anglophones. Choisissez des noms de variables descriptifs et évitez les abréviations. Par exemple, « activation » est préférable à « act ». Les noms de variables d’une seule lettre sont fortement déconseillés, sauf s’il s’agit d’un index dans une boucle for.

  • Le code explicite est préféré – même s’il est plus long – au code plus court.

  • Évitez de sous-classer nn.Sequential. Sous-classez plutôt nn.Module afin que le code puisse être débogué rapidement à l'aide d'énoncés d'impression ou de points d'arrêt.

  • Les signatures de fonctions doivent être annotées avec des types. Sinon, utilisez de bons noms de variables pour les rendre plus compréhensibles.

Problème lié à l'ajout d'un nouveau modèle

Ouvrez une issue Nouvelle addition de modèle pour ajouter un modèle spécifique.

Filtrez par l'étiquette New model sur GitHub pour afficher et ajouter les demandes de modèles existantes.

C’est le bon moment pour se familiariser avec BrandNewLlama. Il est utile de lire le papier de recherche d’un modèle pour comprendre sa conception et son implémentation techniques. Il n’est pas nécessaire de s’inquiéter outre mesure des détails théoriques. Concentrez-vous plutôt sur les aspects pratiques. Utilisez les questions ci-dessous pour guider votre lecture.

  • De quel type de modèle s'agit-il pour BrandNewLlama ? S'agit-il d'un modèle encoder, decoder ou encoder-decoder ?
  • À quelles tâches peut-on utiliser BrandNewLlama ?
  • Qu'est-ce qui distingue BrandNewLlama des autres modèles ?
  • Quels modèles de Transformers sont les plus similaires à BrandNewLlama ?
  • Quel tokenizer BrandNewLlama utilise-t-il ?

En plus d’en apprendre davantage sur votre modèle, utilisez les conseils ci-dessous pour vous aider à ajouter un modèle plus rapidement.

Chaque contributeur a un style et un flux de travail uniques pour ajouter des modèles à Transformers. Pour un exemple, regardez comment Gemma a été ajouté.

  • Ne réinventez pas la roue ! Prenez le temps d’explorer les modèles et les tokenizers existants pour voir ce que vous pouvez copier et réutiliser. Grep et ripgrep sont d’excellents outils pour cela.
  • Il s’agit davantage d’un défi d’ingénierie que d’un défi scientifique. Concentrez-vous sur les aspects plus pratiques (par exemple, la mise en place d’un environnement de débogage efficace) plutôt que sur les aspects théoriques du modèle.
  • N’hésitez pas à demander de l’aide ! Nous sommes là pour vous accompagner. 🤗

Environnement de développement

Cliquez sur le bouton Fork du dépôt Transformers pour créer votre propre copie de travail. Clonez le dépôt sur votre disque local et ajoutez le dépôt de base en tant que distant.

git clone https://github.com/[your Github handle]/transformers.git
cd transformers
git remote add upstream https://github.com/huggingface/transformers.git

Créez un environnement virtuel et effectuez une installation en mode éditable de la bibliothèque avec les dépendances « dev » ou de développement.

python -m venv .env
source .env/bin/activate
pip install -e ".[dev]"

En raison du nombre croissant de dépendances optionnelles de Transformers, cette commande peut échouer. Dans ce cas, installez les dépendances « quality ». Assurez-vous également d’avoir un framework d’apprentissage profond installé.

pip install -e ".[quality]"

Revenez au répertoire parent et clonez et installez le dépôt original BrandNewLlama.

git clone https://github.com/org_that_created_brand_new_llama_org/brand_new_llama.git
cd brand_new_bert
pip install -e .

Revenez à votre clone de Transformers pour commencer à porter BrandNewLlama.

cd transformers

Deux environnements de débogage sont possibles pour exécuter le modèle d'origine : un notebook (Google Colab ou Jupyter) ou un script Python local.

Nous ne recommandons pas de configurer un environnement GPU pour exécuter le modèle original, car cela peut être coûteux. Travaillez d’abord dans un environnement CPU pour vérifier que le modèle fonctionne dans Transformers. Une fois cela fait, vous pourrez le vérifier sur un GPU.

Les notebooks sont excellents pour exécuter du code cellule par cellule, ce qui permet de séparer les composants logiques les uns des autres. Ils peuvent également accélérer les cycles de débogage, car les résultats intermédiaires peuvent être stockés. Vous pouvez également partager des notebooks lorsque vous travaillez avec d'autres contributeurs.

L'inconvénient est que, si vous n'y êtes pas habitué, il peut vous falloir un certain temps pour vous y faire.

Si l’architecture du modèle est identique à celle d’un modèle existant, passez directement à convert the checkpoint, car vous pouvez réutiliser l’architecture du modèle existant.

Exécutez la commande ci-dessous pour démarrer et compléter le questionnaire avec quelques informations de base sur le nouveau modèle. Cette commande accélère le processus en générant automatiquement un certain code de modèle que vous devrez adapter.

transformers add-new-model-like

Créer une pull request

Avant de commencer à adapter le code, créez une pull request pour suivre votre progression et obtenir des retours de l’équipe Transformers. Donnez à votre pull request le titre [WIP] Add BrandNewLlama afin qu’il soit clair qu’il s’agit d’un travail en cours.

Créez une branche à partir de votre branche principale avec un nom descriptif.

git checkout -b add_brand_new_bert

Committez le code, puis récupérez et rebasez sur la branche principale.

git add .
git commit
git fetch upstream
git rebase upstream/main

Poussez les modifications vers votre branche et cliquez sur Compare & pull request pour ouvrir une pull request sur GitHub. Ouvrez la pull request en tant que draft pour indiquer qu’il s’agit d’un travail en cours.

git push -u origin a-descriptive-name-for-my-changes

Incluez les membres pertinents de l’équipe Hugging Face en ajoutant leurs identifiants GitHub dans la pull request pour les questions, les retours, les commentaires et les revues. Orientez les membres de l’équipe vers des parties spécifiques du code que vous souhaitez cibler en cliquant sur l’onglet Files changed, puis en cliquant sur + à gauche du numéro de ligne pour ajouter un commentaire. Une fois qu’une question ou un problème est résolu, cliquez sur Resolve pour indiquer que le problème est traité. Cela permet de garder la conversation organisée et claire.

N'oubliez pas d'engager et de pousser régulièrement votre travail, et de mettre à jour votre travail avec la branche principale actuelle.

git fetch upstream
git merge upstream/main

Point de contrôle original

Prenez le temps de travailler d'abord sur l'implémentation du modèle original afin de comprendre son fonctionnement.

Cela peut s’avérer difficile si le dépôt du modèle d’origine manque de documentation ou si la base de code est complexe. Mais vous devriez en faire votre motivation pour implémenter le modèle dans Transformers. Votre contribution le rend plus accessible et plus convivial pour tous !

Orientez-vous dans le dépôt d'origine en effectuant les étapes suivantes.

  • Localiser les poids pré-entraînés.
  • Déterminez comment charger les poids pré-entraînés dans le modèle.
  • Déterminez comment exécuter le tokenizer indépendamment du modèle.
  • Tracez un passage avant pour comprendre quelles classes et fonctions sont requises. Ce sont probablement les seules classes et fonctions que vous devrez implémenter.
  • Localisez tous les composants importants (classe du modèle, sous-classes du modèle, couche d'auto-attention, etc.) du modèle.
  • Déterminez comment déboguer le modèle dans le dépôt d’origine. Ajoutez des instructions d’affichage, utilisez des débogueurs interactifs comme ipdb, ou un environnement de développement intégré (IDE) efficace comme PyCharm.

Le dernier point est particulièrement important, car vous devez comprendre en profondeur ce qui se passe à l’intérieur du modèle d’origine avant de pouvoir le réimplémenter dans Transformers. N’hésitez pas à ouvrir des issues et des pull requests dans le dépôt d’origine si vous rencontrez des problèmes.

Une bonne première étape consiste à charger un point de contrôle pré-entraîné petit et à essayer de reproduire un seul passage avant avec un vecteur d'entiers en exemple. Par exemple, en pseudo-code, cela pourrait ressembler à ce qui suit.

model = BrandNewLlamaModel.load_pretrained_checkpoint("/path/to/checkpoint/")
input_ids = [0, 4, 5, 2, 3, 7, 9]  # vector of input ids
original_output = model.generate(input_ids)

Débogage

Si vous rencontrez des problèmes, vous devrez choisir l’une des stratégies de débogage suivantes en fonction de la base de code des modèles d’origine.

sous-composants
modèle et tokenizer

Cette stratégie repose sur la division du modèle d'origine en sous-composants plus petits, par exemple lorsque le code peut être exécuté facilement en mode eager. Bien que plus difficile, cette approche présente certains avantages.

  1. Il est plus simple par la suite de comparer le modèle d’origine à votre implémentation. Vous pouvez vérifier automatiquement que chaque composant individuel correspond à son composant correspondant dans l’implémentation de Transformers. C’est préférable à une comparaison visuelle basée sur des instructions d’affichage.
  2. Il est plus simple de porter des composants individuels plutôt que le modèle entier.
  3. Il est plus facile de comprendre le fonctionnement d'un modèle en le découpant en parties plus petites.
  4. Il est plus facile de prévenir les régressions à un stade ultérieur lors de la modification de votre code grâce aux tests composant par composant.

Reportez-vous aux tests d’intégration d’ELECTRA pour un bon exemple de la manière de décomposer un modèle en composants plus petits.

Quelle que soit la stratégie choisie, il est recommandé de déboguer d'abord les couches initiales et les couches finales en dernier. Récupérez la sortie, soit par des instructions d'affichage, soit par des fonctions de sous-composants, des couches suivantes dans cet ordre.

  1. identifiants d'entrée passés au modèle
  2. embeddings de mots
  3. entrée de la première couche Transformer
  4. sortie de la première couche Transformer
  5. sortie des n-1 couches Transformer précédentes
  6. sortie du modèle complet

Les identifiants d'entrée doivent simplement être un tableau d'entiers comme input_ids = [0, 4, 4, 3, 2, 4, 1, 7, 19].

Les sorties des couches consistent souvent en des tableaux de flottants multidimensionnels.

[[
 [-0.1465, -0.6501,  0.1993,  ...,  0.1451,  0.3430,  0.6024],
 [-0.4417, -0.5920,  0.3450,  ..., -0.3062,  0.6182,  0.7132],
 [-0.5009, -0.7122,  0.4548,  ..., -0.3662,  0.6091,  0.7648],
 ...,
 [-0.5613, -0.6332,  0.4324,  ..., -0.3792,  0.7372,  0.9288],
 [-0.5416, -0.6345,  0.4180,  ..., -0.3564,  0.6992,  0.9191],
 [-0.5334, -0.6403,  0.4271,  ..., -0.3339,  0.6533,  0.8694]]],

La sortie de chaque modèle Transformers doit avoir une précision ou une tolérance d’erreur de 1e-3. Cela tient compte des différences de sortie qui peuvent survenir lors de l’utilisation d’un autre framework de bibliothèque. Comparez les sorties intermédiaires du modèle original avec l’implémentation Transformers pour vous assurer qu’elles sont quasi identiques. Disposer d’un environnement de débogage efficace est essentiel pour cette étape.

Voici quelques conseils pour un environnement de débogage efficace.

  • Pour déboguer les résultats intermédiaires, cela dépend du framework d'apprentissage automatique utilisé par le dépôt de modèle d'origine. Pour PyTorch, vous devez écrire un script pour décomposer le modèle d'origine en sous-composants plus petits afin de récupérer les valeurs intermédiaires.

  • Il est plus rapide de déboguer avec un checkpoint pré-entraîné de petite taille plutôt qu’avec un checkpoint plus volumineux dont le passage avant prend plus de 10 secondes. Si seuls des checkpoints de grande taille sont disponibles, créez un modèle factice avec des poids initialisés aléatoirement et enregistrez ces poids pour les comparer à l’implémentation Transformers.

  • Trouvez la manière la plus simple d’appeler le passage avant du modèle. Idéalement, cette fonction (qui peut s’appeler predict, evaluate, forward ou __call__) ne devrait appeler le passage avant que une seule fois. Il est plus difficile de déboguer une fonction qui appelle le passage avant plusieurs fois.

  • Séparez la tokenisation du passage avant. Identifiez l’endroit où l’entrée sous forme de chaîne est convertie en identifiants d’entrée dans le passage avant et commencez par là. Il peut être nécessaire de créer un petit script ou de modifier le code d’origine afin d’entrer directement les identifiants d’entrée plutôt qu’une chaîne d’entrée.

  • Assurez-vous que le modèle n’est pas en mode d’entraînement. Cela peut produire des sorties aléatoires en raison de la présence de plusieurs couches de dropout dans le modèle. Le passage avant dans votre environnement de débogage doit être déterministe afin que les couches de dropout ne soient pas utilisées.

Une fois que vous êtes en mesure d’exécuter le point de contrôle original, vous êtes prêt à commencer à adapter le code du modèle pour Transformers.

Adapter le code du modèle

La commande transformers add-new-model-like aurait dû générer un fichier de modèle et un fichier de configuration.

  • src/transformers/models/brand_new_llama/modeling_brand_new_llama.py
  • src/transformers/models/brand_new_llama/configuration_brand_new_llama.py

Le code généré automatiquement dans le fichier modeling.py possède la même architecture que Llama si vous avez répondu qu’il s’agit d’un modèle uniquement décodeur, ou la même architecture que BART si vous avez répondu qu’il s’agit d’un modèle encodeur-décodeur. Le code généré n’est qu’un point de départ. En fonction de vos recherches sur le nouveau modèle, vous devrez implémenter les modifications spécifiques en adaptant le code généré. Cela peut impliquer des modifications de la couche d’auto-attention, de l’ordre de la couche de normalisation, et ainsi de suite.

Initialisation du modèle

À ce stade, votre code n’a pas besoin d’être propre, ni même entièrement correct. Il est plus efficace de créer rapidement un premier brouillon puis de l’améliorer itérativement. L’essentiel est que votre modèle puisse être instancié depuis Transformers. La commande ci-dessous crée un modèle à partir de la configuration avec des poids aléatoires, ce qui vérifie que la méthode __init__ fonctionne.

from transformers import BrandNewLlama, BrandNewLlamaConfig
model = BrandNewLlama(BrandNewLlamaConfig())

L’initialisation aléatoire a lieu dans la méthode _init_weights de BrandNewLlamaPreTrainedModel. Tous les modules feuilles sont initialisés en fonction des variables de la configuration. Utilisez les utilitaires du module transformers.initialization pour définir les valeurs, plutôt que d’appeler directement des opérations in situ sur un poids ou son .data.

from transformers import initialization as init

def _init_weights(self, module):
    """Initialize the weights"""
    if isinstance(module, nn.Linear):
        init.normal_(module.weight, mean=0.0, std=self.config.initializer_range)
        if module.bias is not None:
            init.zeros_(module.bias)
    elif isinstance(module, nn.Embedding):
        init.normal_(module.weight, mean=0.0, std=self.config.initializer_range)
        if module.padding_idx is not None:
            init.zeros_(module.weight[module.padding_idx])
    elif isinstance(module, nn.LayerNorm):
        init.zeros_(module.bias)
        init.ones_(module.weight)

Initialisez toujours les poids à l’aide des utilitaires transformers.initialization. Les opérations in-place telles que module.bias.zero_() ou toute manipulation de module.weight.data contournent _is_hf_initialized, qui signale quels paramètres sont déjà chargés. from_pretrained() exécute _init_weights après le chargement du point de contrôle, de sorte que les opérations in-place écrasent silencieusement les poids chargés par des valeurs aléatoires. Cette règle est appliquée aux modèles Transformers par la règle TRF012.

Lors de l’appel de from_pretrained(), les modèles sont généralement initialisés sur le périphérique meta. Le chargeur recrée les tampons non persistants sous forme de tenseurs vides, de sorte que les valeurs assignées dans __init__ ne sont pas conservées. Initialisez ces tampons dans _init_weights afin qu’ils aient les valeurs correctes après le chargement. Pour un exemple, consultez la façon dont BERT initialise ses identifiants de position et ses identifiants de type de jeton.

Le schéma d’initialisation peut différer si vous devez l’adapter à votre modèle. Un sous-module disposant de sa propre méthode reset_parameters peut l’appeler directement. Par exemple, Wav2Vec2ForPreTraining initialise nn.Linear dans ses deux dernières couches linéaires. L’appel est sûr car from_pretrained() modifie les fonctions sous-jacentes de torch.nn.init pour respecter le drapeau _is_hf_initialized pendant l’exécution de _init_weights, de sorte que les poids chargés ne soient pas écrasés.

def _init_weights(self, module):
    """Initialize the weights"""
    if isinstance(module, Wav2Vec2ForPreTraining):
        module.project_hid.reset_parameters()
        module.project_q.reset_parameters()

Convertir les points de contrôle vers Transformers

Le point de contrôle d'origine doit être converti en un point de contrôle compatible avec Transformers.

Essayez de chercher un script de conversion existant à copier, adapter et réutiliser pour votre modèle !

  • Si vous portez un modèle de TensorFlow vers PyTorch, un bon point de départ peut être le script de conversion de BERT.
  • Si vous portez un modèle de PyTorch vers PyTorch, un bon point de départ peut être le script de conversion de BART.

Vérifiez que tous les poids requis sont initialisés et affichez tous les poids du point de contrôle qui n’ont pas été utilisés pour l’initialisation afin de vous assurer que le modèle a été converti correctement.

Vous pouvez rencontrer des erreurs de forme ou des affectations de noms incorrectes lors de la conversion. Cela est le plus souvent dû à des paramètres incorrects dans BrandNewLlamaConfig, à une architecture erronée, à un bug dans la méthode init de votre implémentation, ou à la nécessité de transposer l'un des poids du checkpoint.

Continuez à itérer sur la section Adapter le code du modèle jusqu'à ce que tous les poids du checkpoint soient correctement chargés. Une fois que vous pouvez charger un checkpoint dans votre modèle, enregistrez-le dans un dossier. Celui-ci doit contenir un fichier model.safetensors et un fichier config.json.

model.save_pretrained("/path/to/converted/checkpoint/folder")

Pour faciliter la conversion, la section suivante décrit brièvement la manière dont les modèles PyTorch stockent et définissent les poids et les noms des couches.

Poids et noms des couches PyTorch

Il est utile de créer un modèle PyTorch de base pour comprendre comment les noms de couches sont définis et les poids initialisés.

from torch import nn

class SimpleModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.dense = nn.Linear(10, 10)
        self.intermediate = nn.Linear(10, 10)
        self.layer_norm = nn.LayerNorm(10)

Les noms des couches PyTorch sont définis par le nom de l'attribut de classe de la couche (dense, intermediate, layer_norm). Créez une instance de SimpleModel pour remplir toutes les couches avec des poids aléatoires.

model = SimpleModel()
print(model)
SimpleModel(
  (dense): Linear(in_features=10, out_features=10, bias=True)
  (intermediate): Linear(in_features=10, out_features=10, bias=True)
  (layer_norm): LayerNorm((10,), eps=1e-05, elementwise_affine=True)
)

Les valeurs de poids d'une couche spécifique sont initialisées aléatoirement.

print(model.dense.weight.data)
tensor([[-0.0818,  0.2207, -0.0749, -0.0030,  0.0045, -0.1569, -0.1598,  0.0212,
         -0.2077,  0.2157],
        [ 0.1044,  0.0201,  0.0990,  0.2482,  0.3116,  0.2509,  0.2866, -0.2190,
          0.2166, -0.0212],
        [-0.2000,  0.1107, -0.1999, -0.3119,  0.1559,  0.0993,  0.1776, -0.1950,
         -0.1023, -0.0447],
        [-0.0888, -0.1092,  0.2281,  0.0336,  0.1817, -0.0115,  0.2096,  0.1415,
         -0.1876, -0.2467],
        [ 0.2208, -0.2352, -0.1426, -0.2636, -0.2889, -0.2061, -0.2849, -0.0465,
          0.2577,  0.0402],
        [ 0.1502,  0.2465,  0.2566,  0.0693,  0.2352, -0.0530,  0.1859, -0.0604,
          0.2132,  0.1680],
        [ 0.1733, -0.2407, -0.1721,  0.1484,  0.0358, -0.0633, -0.0721, -0.0090,
          0.2707, -0.2509],
        [-0.1173,  0.1561,  0.2945,  0.0595, -0.1996,  0.2988, -0.0802,  0.0407,
          0.1829, -0.1568],
        [-0.1164, -0.2228, -0.0403,  0.0428,  0.1339,  0.0047,  0.1967,  0.2923,
          0.0333, -0.0536],
        [-0.1492, -0.1616,  0.1057,  0.1950, -0.2807, -0.2710, -0.1586,  0.0739,
          0.2220,  0.2358]]).

Dans le script de conversion, les poids aléatoires doivent être remplacés par les poids exacts de la couche correspondante dans le point de contrôle d'origine.

# retrieve matching layer weights with recursive algorithm
layer_name = "dense"
pretrained_weight = array_of_dense_layer

model_pointer = getattr(model, "dense")
model_pointer.weight.data = torch.from_numpy(pretrained_weight)

Vérifiez que les poids initialisés aléatoirement et les poids du checkpoint pré-entraîné correspondant ont la même forme et le même nom. Ajoutez des instructions assert pour la forme et affichez les noms des poids du checkpoint.

assert (
    model_pointer.weight.shape == pretrained_weight.shape
), f"Pointer shape of random weight {model_pointer.shape} and array shape of checkpoint weight {pretrained_weight.shape} mismatched"

logger.info(f"Initialize PyTorch weight {layer_name} from {pretrained_weight.name}")

Lorsque la forme ou le nom ne correspond pas, vous avez peut-être attribué un poids de checkpoint incorrect à une couche initialisée aléatoirement. Une forme incorrecte peut provenir du fait que les paramètres de BrandNewLlama ne correspondent pas exactement aux paramètres du modèle d'origine. Il est également possible que l'implémentation de la couche PyTorch exige que les poids soient d'abord transposés.

Implémenter le passage avant

La passe avant doit être implémentée ensuite si le modèle se charge correctement. Elle prend quelques entrées et renvoie la sortie du modèle.

model = BrandNewLlamaModel.from_pretrained("/path/to/converted/checkpoint/folder")
input_ids = [0, 4, 4, 3, 2, 4, 1, 7, 19]
output = model.generate(input_ids).last_hidden_states

Ne vous découragez pas si votre passe avant n’est pas identique à la sortie du modèle d’origine ou s’il renvoie une erreur. Vérifiez que la passe avant ne lève aucune erreur. Cela est souvent dû à des dimensions incorrectes ou à l’utilisation d’un type de données inadapté (torch.long au lieu de torch.float32).

La précision de votre sortie doit être de 1e-3. Assurez-vous que les formes de sortie et les valeurs de sortie sont identiques. Les raisons courantes pour lesquelles les sorties ne sont pas identiques incluent :

  • Certaines couches n'ont pas été ajoutées (couche d'activation ou connexion résiduelle).
  • La matrice d'incorporation de mots n'est pas liée.
  • Les embeddings positionnels incorrects sont utilisés car l'implémentation d'origine inclut un décalage.
  • Le dropout est appliqué lors de la passe avant. Corrigez cette erreur en vous assurant que model.training est False et en passant self.training à torch.nn.functional.dropout.

Comparez le passage avant du modèle d'origine et de votre implémentation pour vérifier s'il existe des différences. Idéalement, déboguez et affichez les sorties intermédiaires des deux implémentations du passage avant pour identifier précisément où l'implémentation d'origine diffère de la vôtre.

  1. Vérifiez que les input_ids codés en dur dans les deux implémentations sont identiques.
  2. Vérifiez que les sorties de la première transformation de input_ids (généralement les embeddings de mots) sont identiques, puis poursuivez jusqu'à la dernière couche.

Toute différence entre les deux implémentations doit pointer vers le bug dans votre implémentation.

L'une des meilleures stratégies consiste à ajouter de nombreuses instructions d'affichage aux mêmes positions dans les deux implémentations, puis à les supprimer successivement lorsqu'elles affichent des valeurs identiques pour les sorties intermédiaires.

Lorsque les deux implémentations produisent la même sortie, vérifiez que les sorties sont dans une précision de 1e-3.

torch.allclose(original_output, output, atol=1e-3)

C'est généralement la partie la plus difficile du processus. Félicitations si vous êtes arrivé(e) jusqu'ici !

Et si vous êtes bloqué ou que vous avez des difficultés avec cette étape, n’hésitez pas à demander de l’aide sur votre pull request.

Ajouter des tests de modèle

Bien que le modèle fonctionne, vous devez toujours ajouter des tests pour garantir sa compatibilité avec Transformers. Les tests sont importants car ils aident les utilisateurs à comprendre votre travail en examinant des tests spécifiques, et parce qu’ils empêchent votre modèle de se casser à l’avenir si des modifications sont apportées.

Cookiecutter a dû ajouter un fichier de test pour votre modèle. Exécutez le fichier de test ci-dessous pour vous assurer que tous les tests courants passent.

pytest tests/models/brand_new_llama/test_modeling_brand_new_llama.py

Les tests d’intégration doivent être ajoutés en premier, car ils remplissent le même rôle que les scripts de débogage utilisés précédemment pour implémenter le nouveau modèle dans Transformers. Un modèle de ces tests de modèle, BrandNewLlamaModelIntegrationTests, a été ajouté par Cookiecutter et doit être complété. Pour s’assurer qu’il passe, exécutez la commande suivante.

macOS
Windows
RUN_SLOW=1 pytest -sv tests/models/brand_new_llama/test_modeling_brand_new_llama.py::BrandNewLlamaModelIntegrationTests

Toutes les fonctionnalités propres à BrandNewLlama doivent être testées dans un test distinct sous BrandNewLlamaModelTester/BrandNewLlamaModelTest. Ce test est souvent négligé, mais il est extrêmement important car :

  • il aide à transmettre à la communauté les connaissances acquises au cours du processus en montrant comment fonctionnent les nouvelles fonctionnalités du modèle
  • les futurs contributeurs peuvent tester rapidement les modifications apportées au modèle en exécutant ces tests spéciaux

Implémenter le tokenizer

Nous recommandons d’ajouter un tokenizer rapide (PreTrainedTokenizerFast) afin d’offrir aux utilisateurs les meilleures performances. N’hésitez pas à taguer @ArthurZucker ou @itazap dans votre PR pour obtenir de l’aide sur la manière d’ajouter PreTrainedTokenizerFast.

Le modèle étant réglé, il est temps de se concentrer sur le tokenizer. Le tokenizer doit être identique ou très similaire à un tokenizer existant dans Transformers.

Trouvez et chargez le fichier de tokenizer d'origine dans votre implémentation. Créez un script dans le dépôt d'origine qui prend une chaîne en entrée et renvoie les input_ids. Le pseudocode doit ressembler au code ci-dessous.

input_str = "This is a long example input string containing special characters .$?-, numbers 2872 234 12 and words."
model = BrandNewLlamaModel.load_pretrained_checkpoint("/path/to/checkpoint/")
input_ids = model.tokenize(input_str)

Vous devrez peut-être consulter le dépôt d’origine pour trouver la fonction de tokenizer appropriée ou modifier le tokenizer existant dans votre clone du dépôt d’origine afin qu’il ne renvoie que input_ids. Le script de votre tokenizer devrait ressembler au suivant.

from transformers import BrandNewLlamaTokenizer

input_str = "This is a long example input string containing special characters .$?-, numbers 2872 234 12 and words."
tokenizer = BrandNewLlamaTokenizer.from_pretrained("/path/to/tokenizer/folder/")
input_ids = tokenizer(input_str).input_ids

Lorsque les deux implémentations ont les mêmes input_ids, ajoutez un fichier de test pour le tokenizer. Ce fichier est analogue aux fichiers de test de modélisation. Les fichiers de test du tokenizer doivent contenir quelques tests d’intégration codés en dur.

Implémenter le processeur d’images

Les processeurs d’images utilisent désormais une architecture basée sur des backends. Le backend par défaut est TorchvisionBackend, qui utilise la bibliothèque torchvision et permet d’effectuer le traitement d’images sur GPU. Un backend alternatif PIL/NumPy (PilBackend) est également fourni. Les deux backends sont importés depuis image_processing_backends. N’hésitez pas à taguer @yonigozlan pour obtenir de l’aide.

Bien que cet exemple n’inclue pas de processeur d’images, vous devrez peut-être en implémenter un si votre modèle nécessite des entrées d’images. Le processeur d’images est responsable de la conversion des images dans un format adapté à votre modèle. Avant d’en implémenter un nouveau, vérifiez si un processeur d’images existant dans la bibliothèque Transformers peut être réutilisé, car de nombreux modèles partagent des techniques de traitement d’images similaires. Notez que vous pouvez également utiliser modular pour les processeurs d’images afin de réutiliser des composants existants.

Si vous devez implémenter un nouveau processeur d'images, chaque modèle dispose de deux fichiers de processeur :

  • image_processing_<model>.py : le processeur par défaut basé sur torchvision (<Model>ImageProcessor), héritant de TorchvisionBackend. Il remplace l’ancien processeur « fast ».
  • image_processing_pil_<model>.py : le processeur alternatif PIL/NumPy (<Model>ImageProcessorPil), héritant de PilBackend. Il remplace l’ancien processeur « slow ».

Le fichier de backend torchvision définit également toute classe de kwargs personnalisée que le fichier PIL importe. Les deux fichiers utilisent le décorateur @auto_docstring — n'ajoutez pas de docstrings de classe manuelles.

Ajoutez des tests pour le processeur d’images dans tests/models/your_model_name/test_image_processing_your_model_name.py. Ces tests doivent être similaires à ceux des autres processeurs d’images et doivent vérifier que le processeur d’images gère correctement les entrées d’images. Si votre processeur d’images inclut des fonctionnalités ou des méthodes de traitement uniques, assurez-vous d’ajouter des tests spécifiques pour ceux-ci également.

Implémenter le processeur

Si votre modèle accepte plusieurs modalités, comme le texte et les images, vous devez ajouter un processeur. Le processeur centralise le prétraitement des différentes modalités avant de les transmettre au modèle.

Le processeur doit appeler les processeurs spécifiques à chaque modalité au sein de sa fonction __call__ afin de traiter correctement chaque type d'entrée. Pensez à consulter les processeurs existants dans la bibliothèque pour comprendre leur structure attendue. Transformers utilise la convention suivante dans la signature de la fonction __call__.

def __call__(
    self,
    images: ImageInput = None,
    text: Union[TextInput, PreTokenizedInput, list[TextInput], list[PreTokenizedInput]] = None,
    audio=None,
    videos=None,
    **kwargs: Unpack[YourModelProcessorKwargs],
) -> BatchFeature:
    ...

YourModelProcessorKwargs est un TypedDict qui inclut tous les arguments de traitement typiques ainsi que les arguments supplémentaires qu'un processeur spécifique peut requérir.

Ajoutez des tests pour le processeur dans tests/models/your_model_name/test_processor_your_model_name.py. Ces tests doivent être similaires à ceux des autres processeurs et doivent vérifier que le processeur gère correctement les différentes modalités.

Tests d'intégration

Maintenant que vous disposez d'un modèle et d'un tokenizer, ajoutez des tests d'intégration de bout en bout pour le modèle et le tokenizer à tests/models/brand_new_llama/test_modeling_brand_new_llama.py.

Le test doit fournir un exemple texte-à-texte significatif pour montrer que le modèle fonctionne comme prévu. Par exemple, vous pouvez inclure une paire de traduction source-cible, une paire article-résumé ou une paire question-réponse.

Si le point de contrôle n’a pas été affiné sur une tâche en aval, les tests du modèle sont suffisants.

Enfin, essayez de vous assurer que vos tests peuvent s’exécuter sur un GPU en ajoutant des instructions .to(self.device) aux tenseurs internes des modèles. Si vous n’avez pas accès à un GPU, nous pouvons nous en charger pour vous.

Ajouter la documentation

Votre modèle n’est utile que si les utilisateurs savent comment l’utiliser. C’est pourquoi il est important d’ajouter une documentation et des docstrings. Cookiecutter a ajouté un fichier modèle, docs/source/model_doc/brand_new_llama.md, que vous pouvez remplir avec des informations sur votre modèle.

Il s’agit généralement de la première interaction d’un utilisateur avec un modèle, la documentation doit donc être claire et concise. Il est souvent très utile d’ajouter des exemples d’utilisation du modèle.

Assurez-vous que des docstrings sont ajoutées à src/transformers/models/brand_new_llama/modeling_brand_new_llama.py et qu’elles incluent toutes les entrées et sorties nécessaires. Consultez notre guide pour la rédaction de la documentation et des docstrings.

Refactoriser

Il est temps de faire le ménage et de s’assurer que le style du code est cohérent avec le reste de la bibliothèque. Exécutez la commande suivante pour corriger automatiquement les styles incorrects.

make style

Pour vérifier que le style du code passe les contrôles de qualité, exécutez la commande ci-dessous.

make check-repo

D'autres tests ou vérifications (docstring manquante ou nommage incorrect) peuvent échouer sur votre pull request en raison des tests de conception stricts de Transformers. Nous pouvons vous aider à résoudre ces problèmes si vous êtes bloqué.

Après vous être assuré que le code s'exécute correctement, vous pouvez souhaiter le refactoriser pour le rendre plus lisible ou plus propre.

Téléverser sur le Hub

Convertissez et téléchargez tous les points de contrôle sur le Hub. Ajoutez une carte de modèle pour fournir plus de transparence et de contexte sur le modèle. La carte de modèle doit mettre en évidence les caractéristiques spécifiques d’un point de contrôle, la manière dont le modèle a été entraîné, ainsi que des exemples de code pour l’utiliser.

Dans de nombreux cas, ajouter un notebook interactif que les utilisateurs peuvent exécuter est un excellent moyen de démontrer comment utiliser le modèle pour l'inférence ou l'ajuster sur une tâche en aval. Bien que non obligatoire, l'inclusion d'un notebook peut favoriser une plus grande adoption de votre modèle.

Vous devriez également consulter l'équipe Transformers pour décider d'un nom approprié pour le modèle et obtenir les droits d'accès nécessaires pour téléverser le modèle.

Utilisez la méthode push_to_hub() pour téléverser le modèle.

brand_new_bert.push_to_hub("brand_new_llama")

Consultez le guide Partage pour plus d'informations sur la mise en ligne de modèles sur le Hub.

Fusionnez votre modèle

Vous êtes enfin prêt à fusionner votre pull request et à ajouter officiellement le modèle à Transformers ! Assurez-vous que tous les tests passent et que tous les commentaires et retours ont été traités.

Félicitations pour l'ajout d'un nouveau modèle à Transformers ! 🥳

C'est une contribution très importante. Votre travail rend Transformers plus accessible aux développeurs et aux chercheurs du monde entier. Vous devriez être fier de votre contribution et partager votre réussite avec la communauté !

Voir aussi

Update on GitHub