Transformers documentation

Stratégies de génération

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

and get access to the augmented documentation experience

to get started

Stratégies de génération

Une stratégie de décodage indique comment un modèle doit sélectionner le jeton généré suivant. Il existe de nombreux types de stratégies de décodage, et le choix de la stratégie appropriée a un impact significatif sur la qualité du texte généré.

Ce guide vous aidera à comprendre les différentes stratégies de décodage disponibles dans Transformers et comment et quand les utiliser.

Méthodes de décodage de base

Ce sont des méthodes de décodage bien établies et doivent constituer votre point de départ pour les tâches de génération de texte.

Recherche gloutonne

La recherche gloutonne est la stratégie de décodage par défaut. Elle sélectionne le jeton le plus probable à chaque étape. Sauf spécification dans GenerationConfig, cette stratégie génère un maximum de 20 nouveaux jetons.

La recherche gloutonne fonctionne bien pour les tâches produisant des sorties relativement courtes où la créativité n'est pas une priorité. Cependant, elle échoue lors de la génération de séquences plus longues, car elle commence à se répéter.

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from accelerate import Accelerator

device = Accelerator().device

tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")
inputs = tokenizer("Hugging Face is an open-source company", return_tensors="pt").to(device)

model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf", dtype=torch.float16).to(device)
# explicitly set to default length because Llama2 generation length is 4096
outputs = model.generate(**inputs, max_new_tokens=20)
tokenizer.batch_decode(outputs, skip_special_tokens=True)
'Hugging Face is an open-source company that provides a suite of tools and services for building, deploying, and maintaining natural language processing'

Échantillonnage

L'échantillonnage, ou échantillonnage multinomial, sélectionne aléatoirement un jeton en fonction de la distribution de probabilité sur l'ensemble du vocabulaire du modèle (contrairement au jeton le plus probable, comme dans la recherche gloutonne). Cela signifie que chaque jeton ayant une probabilité non nulle a une chance d'être sélectionné. Les stratégies d'échantillonnage réduisent la répétition et peuvent générer des sorties plus créatives et diversifiées.

Activez l'échantillonnage multinomial avec do_sample=True et num_beams=1.

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from accelerate import Accelerator

device = Accelerator().device

tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")
inputs = tokenizer("Hugging Face is an open-source company", return_tensors="pt").to(device)

model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf", dtype=torch.float16).to(device)
# explicitly set to 100 because Llama2 generation length is 4096
outputs = model.generate(**inputs, max_new_tokens=50, do_sample=True, num_beams=1)
tokenizer.batch_decode(outputs, skip_special_tokens=True)
'Hugging Face is an open-source company 🤗\nWe are open-source and believe that open-source is the best way to build technology. Our mission is to make AI accessible to everyone, and we believe that open-source is the best way to achieve that.'

Recherche par faisceau

La recherche par faisceaux (beam search) suit plusieurs séquences générées (faisceaux) à chaque étape temporelle. Après un certain nombre d'étapes, elle sélectionne la séquence ayant la probabilité globale la plus élevée. Contrairement à la recherche gloutonne, cette stratégie peut « regarder en avant » et choisir une séquence dont la probabilité globale est plus élevée, même si les jetons initiaux ont une probabilité plus faible. Elle est particulièrement adaptée aux tâches ancrées dans l'entrée, comme la description d'une image ou la reconnaissance vocale. Vous pouvez également utiliser do_sample=True avec la recherche par faisceaux pour effectuer un échantillonnage à chaque étape, mais la recherche par faisceaux éliminera toujours de manière gloutonne les séquences à faible probabilité entre les étapes.

Découvrez le visualiseur de beam search pour voir comment fonctionne le beam search.

Activez la recherche par faisceau avec le paramètre num_beams (il doit être supérieur à 1, sinon c’est équivalent à une recherche gloutonne).

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from accelerate import Accelerator

device = Accelerator().device

tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")
inputs = tokenizer("Hugging Face is an open-source company", return_tensors="pt").to(device)

model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf", dtype=torch.float16).to(device)
# explicitly set to 100 because Llama2 generation length is 4096
outputs = model.generate(**inputs, max_new_tokens=50, num_beams=2)
tokenizer.batch_decode(outputs, skip_special_tokens=True)
"['Hugging Face is an open-source company that develops and maintains the Hugging Face platform, which is a collection of tools and libraries for building and deploying natural language processing (NLP) models. Hugging Face was founded in 2018 by Thomas Wolf']"

Méthodes de génération personnalisées

Les méthodes de génération personnalisées permettent un comportement spécialisé tel que :

  • laisser le modèle continuer à réfléchir s'il est incertain ;
  • revenir en arrière dans la génération si le modèle est bloqué ;
  • gérer les jetons spéciaux avec une logique personnalisée ;
  • utiliser des caches KV spécialisés ;

Nous activons les méthodes de génération personnalisées via les dépôts de modèles, en supposant une balise de modèle et une structure de fichiers spécifiques (voir la sous-section ci-dessous). Cette fonctionnalité est une extension du code de modélisation personnalisé et, à ce titre, nécessite de définir trust_remote_code=True.

Si un dépôt de modèle contient une méthode de génération personnalisée, la façon la plus simple de l’essayer est de charger le modèle et de générer avec lui :

from transformers import AutoModelForCausalLM, AutoTokenizer

# `transformers-community/custom_generate_example` holds a copy of `Qwen/Qwen2.5-0.5B-Instruct`, but
# with custom generation code -> calling `generate` uses the custom generation method!
tokenizer = AutoTokenizer.from_pretrained("transformers-community/custom_generate_example")
model = AutoModelForCausalLM.from_pretrained(
    "transformers-community/custom_generate_example", device_map="auto", trust_remote_code=True
)

inputs = tokenizer(["The quick brown"], return_tensors="pt").to(model.device)
# The custom generation method is a minimal greedy decoding implementation. It also prints a custom message at run time.
gen_out = model.generate(**inputs)
# you should now see its custom message, "✨ using a custom generation method ✨"
print(tokenizer.batch_decode(gen_out, skip_special_tokens=True))
'The quick brown fox jumps over a lazy dog, and the dog is a type of animal. Is'

Les dépôts de modèles avec des méthodes de génération personnalisées possèdent une propriété particulière : leur méthode de génération peut être chargée depuis n’importe quel modèle via l’argument custom_generate de generate(). Cela signifie que n’importe qui peut créer et partager sa méthode de génération personnalisée pour qu’elle fonctionne potentiellement avec n’importe quel modèle Transformers, sans exiger des utilisateurs d’installer des paquets Python supplémentaires.

from transformers import AutoModelForCausalLM, AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-0.5B-Instruct")
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-0.5B-Instruct", device_map="auto")

inputs = tokenizer(["The quick brown"], return_tensors="pt").to(model.device)
# `custom_generate` replaces the original `generate` by the custom generation method defined in
# `transformers-community/custom_generate_example`
gen_out = model.generate(**inputs, custom_generate="transformers-community/custom_generate_example", trust_remote_code=True)
print(tokenizer.batch_decode(gen_out, skip_special_tokens=True)[0])
'The quick brown fox jumps over a lazy dog, and the dog is a type of animal. Is'

Vous devez lire le fichier README.md du dépôt contenant la stratégie de génération personnalisée pour voir quelles sont les différences entre les nouveaux arguments et le type de sortie, s'il y en a. Sinon, vous pouvez supposer qu'elle fonctionne comme la méthode de base generate().

Vous pouvez trouver toutes les méthodes de génération personnalisées en recherchant leur balise personnalisée, custom_generate.

Prenons le dépôt Hub transformers-community/custom_generate_example comme exemple. Le README.md indique qu'il possède un argument d'entrée supplémentaire, left_padding, qui ajoute un certain nombre de jetons de bourrage avant le prompt.

gen_out = model.generate(
    **inputs, custom_generate="transformers-community/custom_generate_example", trust_remote_code=True, left_padding=5
)
print(tokenizer.batch_decode(gen_out)[0])
'<|endoftext|><|endoftext|><|endoftext|><|endoftext|><|endoftext|>The quick brown fox jumps over the lazy dog.\n\nThe sentence "The quick'

Si la méthode personnalisée a épinglé des exigences Python que votre environnement ne satisfait pas, vous obtiendrez une exception concernant des exigences manquantes. Par exemple, transformers-community/custom_generate_bad_requirements définit un ensemble d’exigences impossible dans son fichier custom_generate/requirements.txt, et vous verrez le message d’erreur ci-dessous si vous essayez de l’exécuter.

ImportError: Missing requirements in your local environment for `transformers-community/custom_generate_bad_requirements`:
foo (installed: None)
bar==0.0.0 (installed: None)
torch>=99.0 (installed: 2.6.0)

Mettre à jour vos dépendances Python en conséquence supprimera ce message d'erreur.

Créer une méthode de génération personnalisée

Pour créer une nouvelle méthode de génération, vous devez créer un nouveau dépôt Model et y pousser quelques fichiers.

  1. Le modèle avec lequel vous avez conçu votre méthode de génération.
  2. custom_generate/generate.py, qui contient toute la logique de votre méthode de génération personnalisée.
  3. custom_generate/requirements.txt, utilisé pour ajouter éventuellement de nouvelles dépendances Python et/ou verrouiller des versions spécifiques afin d'utiliser correctement votre méthode.
  4. README.md, où vous devez ajouter le tag custom_generate et documenter ici les nouveaux arguments ou les différences de type de sortie de votre méthode personnalisée.

Après avoir ajouté tous les fichiers requis, votre dépôt doit ressembler à ceci

your_repo/
├── README.md          # include the 'custom_generate' tag
├── config.json
├── ...
└── custom_generate/
    ├── generate.py
    └── requirements.txt

Ajout du modèle de base

Le point de départ pour votre méthode de génération personnalisée est un dépôt de modèle comme tout autre. Le modèle à ajouter à ce dépôt doit être celui avec lequel vous avez conçu votre méthode, et il est destiné à faire partie d’une paire modèle-génération fonctionnelle et autonome. Lorsque le modèle de ce dépôt est chargé, votre méthode de génération personnalisée remplace generate. Pas d’inquiétude — votre méthode de génération peut toujours être chargée avec n’importe quel autre modèle Transformers, comme expliqué dans la section ci-dessus.

Si vous souhaitez simplement copier un modèle existant, vous pouvez

from transformers import AutoModelForCausalLM, AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("source/model_repo")
model = AutoModelForCausalLM.from_pretrained("source/model_repo")
tokenizer.save_pretrained("your/generation_method", push_to_hub=True)
model.save_pretrained("your/generation_method", push_to_hub=True)

generate.py

C'est le cœur de votre méthode de génération. Elle doit contenir une méthode nommée generate, et cette méthode doit contenir un argument model en premier argument. model est l'instance du modèle, ce qui signifie que vous avez accès à tous les attributs et méthodes du modèle, y compris ceux définis dans GenerationMixin (comme la méthode de base generate).

generate.py doit être placé dans un dossier nommé custom_generate, et non à la racine du dépôt. Les chemins de fichiers pour cette fonctionnalité sont codés en dur.

En interne, lorsque la méthode de base generate() est appelée avec un argument custom_generate, elle vérifie d’abord ses exigences Python (le cas échéant), puis localise la méthode generate personnalisée dans generate.py, et enfin appelle la méthode generate personnalisée. Tous les arguments reçus ainsi que model sont transmis à votre méthode generate personnalisée, à l’exception des arguments utilisés pour déclencher la génération personnalisée (trust_remote_code et custom_generate).

Cela signifie que votre generate peut comporter un mélange d'arguments originaux et d'arguments personnalisés (ainsi qu'un type de sortie différent), comme indiqué ci-dessous.

import torch

def generate(model, input_ids, generation_config=None, left_padding=None, **kwargs):
    generation_config = generation_config or model.generation_config  # default to the model generation config
    cur_length = input_ids.shape[1]
    max_length = generation_config.max_length or cur_length + generation_config.max_new_tokens

    # Example of custom argument: add `left_padding` (integer) pad tokens before the prompt
    if left_padding is not None:
        if not isinstance(left_padding, int) or left_padding < 0:
            raise ValueError(f"left_padding must be an integer larger than 0, but is {left_padding}")

        pad_token = kwargs.pop("pad_token", None) or generation_config.pad_token_id or model.config.pad_token_id
        if pad_token is None:
            raise ValueError("pad_token is not defined")
        batch_size = input_ids.shape[0]
        pad_tensor = torch.full(size=(batch_size, left_padding), fill_value=pad_token).to(input_ids.device)
        input_ids = torch.cat((pad_tensor, input_ids), dim=1)
        cur_length = input_ids.shape[1]

    # Simple greedy decoding loop
    while cur_length < max_length:
        logits = model(input_ids).logits
        next_token_logits = logits[:, -1, :]
        next_tokens = torch.argmax(next_token_logits, dim=-1)
        input_ids = torch.cat((input_ids, next_tokens[:, None]), dim=-1)
        cur_length += 1

    return input_ids

Suivez les bonnes pratiques recommandées ci-dessous pour vous assurer que votre méthode de génération personnalisée fonctionne comme prévu.

  • N'hésitez pas à réutiliser la logique de validation et de préparation des entrées présente dans la fonction generate() d'origine.
  • Épinglez la version de transformers dans les dépendances si vous utilisez une méthode ou un attribut privé de model.
  • Envisagez d’ajouter une validation du modèle, une validation des entrées, voire un fichier de test distinct pour aider les utilisateurs à vérifier la cohérence de votre code dans leur environnement.

Votre méthode generate personnalisée peut importer du code en importation relative depuis le dossier custom_generate. Par exemple, si vous avez un fichier utils.py, vous pouvez l'importer ainsi :

from .utils import some_function

Seuls les imports relatifs depuis le dossier custom_generate du même niveau sont pris en charge. Les imports depuis un dossier parent ou frère ne sont pas valides. L'argument custom_generate fonctionne également localement avec n'importe quel dossier contenant une structure custom_generate, ce qui est le flux de travail recommandé pour développer votre méthode de génération personnalisée.

gen_out = model.generate(**inputs, custom_generate="path/to/local/dir", trust_remote_code=True)

Le chargement d’un répertoire local exécute toujours son custom_generate/generate.py, ce qui nécessite trust_remote_code=True tout comme pour un dépôt du Hub. N’activez trust_remote_code=True que pour du code que vous avez écrit ou examiné.

requirements.txt

Vous pouvez éventuellement spécifier des dépendances Python supplémentaires dans un fichier requirements.txt à l’intérieur du dossier custom_generate. Elles sont vérifiées à l’exécution et une exception est levée si elles sont manquantes, incitant ainsi les utilisateurs à mettre à jour leur environnement en conséquence.

README.md

Le fichier README.md racine du dépôt de modèle décrit généralement le modèle concerné. Toutefois, étant donné que le dépôt met l’accent sur la méthode de génération personnalisée, nous recommandons vivement de réorienter son contenu vers la description de cette méthode. En plus de la description de la méthode, nous recommandons de documenter les éventuelles différences d’entrées et/ou de sorties par rapport à la fonction generate() d’origine. Ainsi, les utilisateurs peuvent se concentrer sur les nouveautés et se référer à la documentation de Transformers pour les détails d’implémentation génériques.

Pour améliorer la découvrabilité, nous vous recommandons vivement d’ajouter le tag custom_generate à votre dépôt. Pour ce faire, le début de votre fichier README.md doit ressembler à l’exemple ci-dessous. Après avoir poussé le fichier, vous devriez voir le tag dans votre dépôt !

---
library_name: transformers
tags:
  - custom_generate
---

(your markdown content here)

Pratiques recommandées :

  • Documentez les différences d'entrée et de sortie dans generate().
  • Ajoutez des exemples autonomes pour permettre une expérimentation rapide.
  • Décrivez les exigences non contraignantes, par exemple si la méthode ne fonctionne bien qu'avec une certaine famille de modèles.

Réutilisation de la préparation des entrées de generate

Si vous ajoutez une nouvelle boucle de décodage, vous souhaiterez peut-être conserver la préparation des entrées présente dans generate (extension du lot, masques d’attention, processeurs de logits, critères d’arrêt, etc.). Vous pouvez également passer un appelable à custom_generate pour réutiliser le pipeline de préparation complet de generate() tout en ne remplaçant que la boucle de décodage.

def custom_loop(model, input_ids, attention_mask, logits_processor, stopping_criteria, generation_config, **model_kwargs):
    next_tokens = input_ids
    while input_ids.shape[1] < stopping_criteria[0].max_length:
        logits = model(next_tokens, attention_mask=attention_mask, **model_kwargs).logits
        next_token_logits = logits_processor(input_ids, logits[:, -1, :])
        next_tokens = torch.argmax(next_token_logits, dim=-1)[:, None]
        input_ids = torch.cat((input_ids, next_tokens), dim=-1)
        attention_mask = torch.cat((attention_mask, torch.ones_like(next_tokens)), dim=-1)
    return input_ids

output = model.generate(
    **inputs,
    custom_generate=custom_loop,
    max_new_tokens=10,
)

Si vous publiez un dépôt custom_generate, votre implémentation de generate peut elle-même définir un appelable et le passer à model.generate(). Cela vous permet de personnaliser la boucle de décodage tout en bénéficiant de la logique de préparation des entrées intégrée à Transformers.

Rechercher des méthodes de génération personnalisées

Vous pouvez trouver toutes les méthodes de génération personnalisées en recherchant leur balise personnalisée, custom_generate. En plus de la balise, nous avons constitué deux collections de méthodes custom_generate :

Ressources

Lisez le billet de blog How to generate text: using different decoding methods for language generation with Transformers pour une explication du fonctionnement des stratégies de décodage courantes.

Update on GitHub