QLoRA Formation d’un modèle linguistique puissant sur une carte graphique de 16 Go.

QLoRA Formation d'un modèle linguistique puissant avec une carte graphique de 16 Go.

Explorons comment fonctionne la Quantification et fournissons un exemple d’affinage d’un modèle Bloom de 7 milliards de paramètres sur une carte graphique T4 16 Go dans Google Colab.

Cet article fait partie d’un cours sur les modèles volumineux de langage disponible sur GitHub.

Image générée par l'auteur avec Dall-E2

Nous allons combiner une technique de réduction des poids des modèles, telle que la Quantification, avec une technique d’affinage efficace en termes de paramètres, comme LoRA. Le résultat de cette combinaison est QLoRA, qui nous permet d’affiner des modèles volumineux avec une utilisation très efficace des ressources.

Dans un article précédent, nous avons exploré comment affiner un modèle de langage volumineux en utilisant LoRA.

Affinage efficace avec LoRA. Formation optimale pour les modèles de langage volumineux.

LoRA est l’une des techniques d’affinage les plus efficaces et utiles applicables aux modèles de langage volumineux. Dans le message…

levelup.gitconnected.com

Dans celui-ci, nous allons ajouter la Quantification à l’équation, ce qui nous permet d’affiner un modèle significativement plus grand et donc plus puissant.

Nous serons en mesure d’affiner des modèles de 7 milliards de paramètres tels que Lllama-2 7B ou Bloom 7B sur une carte graphique avec seulement 16 Go de mémoire. Cela fait de QLoRA l’une des méthodes les plus efficaces pour l’affinage de modèles qui peut être réellement utilisée.

Et nous y parviendrons avec un effort minimal, en profitant des personnes fantastiques de Hugging Face. Qui fournit une bibliothèque comme PEFT, nous permettant de profiter de tous ces avantages avec seulement quelques lignes de code.

Comment fonctionne la Quantification ?

L’idée principale est simple : nous allons réduire la précision des nombres en virgule flottante, qui occupent normalement 32 bits, à des entiers de 8 ou même 4 bits.

Cette réduction se produit dans les paramètres du modèle, les poids des couches neuronales, ainsi que dans les valeurs d’activation qui circulent à travers les couches du modèle.

Cela signifie que nous n’obtenons pas seulement une amélioration de la taille de stockage du modèle et de la consommation de mémoire, mais également une plus grande agilité dans ses calculs.

Naturellement, il y a une perte de précision, mais particulièrement dans le cas de la quantification sur 8 bits, cette perte est minimale.

Jetons un coup d’œil à un petit exemple.

Je vais créer une fonction pour la quantification et une autre pour la désquantification (ou quoi que ce soit que cela s’appelle).

En réalité, ce que je veux voir, c’est la perte de précision qui se produit lors de la transition d’un nombre sur 32 bits à un nombre quantifié sur 8/4 bits, puis en revenant à sa valeur d’origine sur 32 bits.

#Importation des librairies nécessairesimport numpy as npimport mathimport matplotlib.pyplot as plt#Fonctions pour quantifier et désquantifierdef quantifier(valeur, bits = 4):    valeur_quantifiee = np.round(valeur * (2**(bits - 1) - 1))    return int(valeur_quantifiee)def desquantifier(valeur_quantifiee, bits = 4):    valeur = valeur_quantifiee / (2**(bits - 1) - 1)    return float(valeur)quant_4 = quantifier(0.622, 4)print(quant_4)quant_8 = quantifier(0.622, 8)print(quant_8)

Lorsque nous quantifions 0,622, nous obtenons les résultats suivants :

  • 4 bits: 4.
  • 8 bits: 79

Rétablissons ces valeurs à leur précision d’origine et voyons ce que nous obtenons.

unquant_4 = unquantize(quant_4, 4)print(unquant_4)unquant_8 = unquantize(quant_8, 8)print(unquant_8)
  • 4 bits déquantifiés : 0,57142
  • 8 bits déquantifiés : 0,62204

Si l’on considère que le nombre d’origine était de 0,622, on peut dire que la quantification sur 8 bits perd à peine de précision, et la perte de précision de la quantification sur 4 bits est gérable.

Il est essentiel de toujours prendre en compte l’utilisation prévue du modèle quantifié. Pour des tâches telles que la génération de texte ou la génération de code source, la perte de précision peut ne pas être essentielle. Cependant, dans les modèles utilisés pour la reconnaissance d’images dans le diagnostic des maladies, on peut ne pas être à l’aise avec une perte significative de précision.

Traçons une courbe avec les valeurs déquantifiées d’un cosinus.

x = np.linspace(-1, 1, 50)y = [math.cos(val) for val in x]y_quant_8bit = np.array([quantize(val, bits=8) for val in y])y_unquant_8bit = np.array([unquantize(val, bits=8) for val in y_quant_8bit])y_quant_4bit = np.array([quantize(val, bits=4) for val in y])y_unquant_4bit = np.array([unquantize(val, bits=4) for val in y_quant_4bit])

plt.figure(figsize=(10, 12))plt.subplot(4, 1, 1)plt.plot(x, y, label="Original")plt.plot(x, y_unquant_8bit, label="unquantized_8bit")plt.plot(x, y_unquant_4bit, label="unquantized_4bit")plt.legend()plt.title("Compare Graph")plt.grid(True)

Comme nous pouvons le voir sur le graphique, la ligne non quantifiée représentant les valeurs sur 8 bits se confond presque parfaitement avec la ligne des valeurs d’origine. En revanche, avec la ligne représentant les valeurs non quantifiées sur 4 bits, on peut observer quelques sauts visibles. La différence de précision entre la quantification sur 8 bits et la quantification sur 4 bits est assez remarquable. C’est un facteur essentiel à prendre en compte lorsque l’on décide de quantifier notre modèle.

Cela étant dit, nous allons utiliser une quantification sur 4 bits car, comme mentionné, pour la génération de texte, nous ne remarquerons pas beaucoup de différence, et il est nécessaire de charger le modèle sur un seul GPU de 16 Go.

QLoRA : Ajustement fin d’un modèle quantifié sur 4 bits en utilisant LoRA.

Pour suivre le notebook en parallèle de l’article, il est disponible sur Github :

Large-Language-Model-Notebooks-Course/5-Fine Tuning/QLoRA_Tuning_PEFT.ipynb at main ·…

Cours pratique sur les modèles de langage de grande taille. Contribuez à peremartra/Large-Language-Model-Notebooks-Course…

github.com

Le modèle que je vais utiliser est le Bloom 7B. C’est l’un des modèles bien établis dans Hugging Face, très puissant et performant au niveau de LLAMA. C’est un modèle que nous ne pouvions pas charger sur un GPU de 16 Go sans quantification.

Dans l’article précédent, j’ai entraîné un modèle de la même famille, mais beaucoup plus petit, afin que vous puissiez étudier les différences entre les deux notebooks.

Nous pouvons commencer par charger les bibliothèques nécessaires.

!pip -q install accelerate!pip -q install datasets!pip -q install trl

Les bibliothèques trl et accelerate font partie de l’écosystème HuggingFace et nous permettent de réaliser l’ajustement fin du modèle.

La bibliothèque datasets contient une pléthore de jeux de données prétraités, dont celui que nous allons utiliser dans notre exemple.

Vous avez peut-être remarqué que les deux principales bibliothèques manquent : transformers et peft. La première sert d’interface principale aux modèles Hugging Face, et la seconde contient la mise en œuvre de diverses techniques d’ajustement fin. PEFT signifie Parameter Efficient Fine Tuning.

Installons ces bibliothèques d’une manière spéciale.

# Installer les dernières versions des bibliothèques peft & transformers recommandées # si vous souhaitez travailler avec les modèles les plus récents !pip install -q git+https://github.com/huggingface/peft.git!pip install -q git+https://github.com/huggingface/transformers.git

De cette façon, nous installons les dernières versions de ces bibliothèques directement à partir du projet GitHub, ce qui inclut des implémentations pour les modèles les plus récents comme Mistral ou LLAMA-2. Dans notre cas, cela peut ne pas être strictement nécessaire car la famille de modèles Bloom est prise en charge depuis un certain temps dans la version disponible de ces bibliothèques.

Importons les différentes classes nécessaires.

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfigfrom trl import SFTTrainerimport torch

Chargement du modèle.

# Utilisez n'importe quel modèle que vous souhaitez, si vous voulez faire un test rapide, utilisez simplement le plus petit. #model_name = "bigscience/bloomz-560m"#model_name="bigscience/bloom-1b1"model_name = "bigscience/bloom-7b1"target_modules = ["query_key_value"]

Le modèle sélectionné est Bloom 7B, mais si vous effectuez des tests, je vous conseille d’utiliser l’un des modèles plus petits pour minimiser le temps d’apprentissage et l’utilisation des ressources. Une fois satisfait des résultats, vous pouvez essayer le modèle 7B et voir les résultats.

Pour charger le modèle, nous avons besoin d’une classe de configuration qui spécifie comment nous voulons que la quantification soit effectuée. Nous y parviendrons avec la classe BitesAndBytesConfig de la bibliothèque Transformers.

bnb_config = BitsAndBytesConfig(    load_in_4bit=True,    bnb_4bit_use_double_quant=True,    bnb_4bit_quant_type="nf4",    bnb_4bit_compute_dtype=torch.bfloat16)

Nous spécifions l’utilisation de la quantification sur 4 bits et activons également la double quantification pour réduire la perte de précision.

Pour le paramètre bnb_4bit_quant_type, j’ai utilisé la valeur recommandée dans l’article ‘QLoRA: Efficient Finetuning of Quantized LLMs.

Maintenant, nous pouvons charger le modèle.

device_map = {"": 0}foundation_model = AutoModelForCausalLM.from_pretrained(model_name,                    quantization_config=bnb_config,                    device_map=device_map,                    use_cache = False)

Ainsi, nous aurions la version quantifiée du modèle en mémoire. Si vous le souhaitez, vous pouvez essayer de charger le modèle sans quantification en supprimant simplement le paramètre quantization. Il est fort probable que vous ne puissiez pas le charger en raison de contraintes de mémoire.

Maintenant, chargeons le tokenizer, et tout sera prêt à tester le modèle.

tokenizer = AutoTokenizer.from_pretrained(model_name)tokenizer.pad_token = tokenizer.eos_token

Test du modèle sans ajustement fin.

Afin de déterminer si l’ajustement fin du modèle a un effet positif, il est préférable d’effectuer un test avec le modèle récemment chargé avant d’apporter des modifications.

Dans cet esprit, je vais créer une fonction qui prend le modèle, l’entrée de l’utilisateur et la longueur maximale de la réponse.

# cette fonction retourne les sorties du modèle reçues et les entrées.def get_outputs(model, inputs, max_new_tokens=100):    outputs = model.generate(        input_ids=inputs["input_ids"],        attention_mask=inputs["attention_mask"],        max_new_tokens=max_new_tokens,        repetition_penalty=1.5, # Avoid repetition.        early_stopping=False, # Le modèle peut s'arrêter avant d'atteindre max_length        eos_token_id=tokenizer.eos_token_id,    )    return outputs

Maintenant, nous pouvons envoyer une requête au modèle et vérifier sa réponse, ce qui nous permet de la comparer avec la réponse qu’il fournira après l’affinage.

# Inférer le modèle originalinput_sentences = tokenizer("Je veux que tu agisses comme un coach motivant.", return_tensors="pt").to('cuda')foundational_outputs_sentence = get_outputs(foundation_model, input_sentences, max_new_tokens=50)print(tokenizer.batch_decode(foundational_outputs_sentence, skip_special_tokens=True))

[“Je veux que tu agisses comme un coach motivant. Je ne veux pas dire ça dans le sens de dire aux gens ce qu’ils devraient faire, mais plutôt les encourager et les aider à motiver leurs propres actions.\nTu peux commencer par poser des questions comme celles-ci:\n\nQuels sont tes objectifs?\nComment cela t’aidera-t-il à les atteindre?\n\nEnsuite”]

La réponse est assez bonne. Bloom est un modèle bien entraîné capable de fournir des réponses précises dans différentes situations. J’ai effectué le même test avec le Bloom 560M, et la réponse était vraiment différente: [“Je veux que tu agisses comme un coach motivant. N’aie pas peur d’être challengé.”].

Préparation du dataset.

Le dataset à utiliser fait partie des dataset disponibles dans la bibliothèque datasets: fka/awesome-chatgpt-prompts.

Jetons un coup d’œil à quelques-unes des prompts contenues dans le dataset :

  • Je veux que tu agisses comme une console javascript. Je taperai des commandes et tu me répondras avec ce que la console javascript devrait afficher. Tu ne dois répondre qu’avec la sortie du terminal à l’intérieur d’un unique bloc de code et rien d’autre. Ne fournis pas d’explications. Ne tape pas de commandes à moins que je ne te le demande. Quand j’ai besoin de te dire quelque chose en anglais, je le ferai en mettant le texte entre accolades {comme ça}. Ma première commande est console.log(“Hello World”);
  • Je veux que tu agisses comme un guide de voyage. Je te donnerai ma position et tu me suggéreras un endroit à visiter près de ma position. Dans certains cas, je te donnerai également le type de lieux que je vais visiter. Tu me suggéreras également des lieux de même type qui sont proches de ma première position. Ma première demande de suggestion est “Je suis à Istanbul/Beyoğlu et je veux visiter uniquement des musées.”
  • Je veux que tu agisses comme un scénariste. Tu développeras un script captivant et créatif pour un long métrage ou une série Web qui pourra captiver son public. Commence par créer des personnages intéressants, le décor de l’histoire, les dialogues entre les personnages, etc. Lorsque tu auras terminé le développement de tes personnages, crée une intrigue passionnante remplie de rebondissements qui maintient les spectateurs en haleine jusqu’à la fin. Ma première demande est “J’ai besoin d’écrire un film romantique se déroulant à Paris.”

Maintenant, nous avons une idée claire du style de réponse que nous attendons du modèle affiné. Voyons si nous pouvons l’atteindre.

from datasets import load_datasetdataset = "fka/awesome-chatgpt-prompts"# Créer le dataset pour créer des prompts.data = load_dataset(dataset)data = data.map(lambda samples: tokenizer(samples["prompt"]), batched=True)train_sample = data["train"].select(range(50))del datatrain_sample = train_sample.remove_columns('act')display(train_sample)

Dataset({ features: [‘prompt’, ‘input_ids’, ‘attention_mask’], num_rows: 50 })

Le dataset contient deux colonnes. J’ai choisi de garder uniquement celle contenant les prompts, car je pense que l’autre colonne ne fournit pas d’informations utiles.

Cependant, il s’agit simplement d’une décision de conception, prise par moi, et je vous encourage à commenter la ligne qui supprime la colonne act et à voir si le modèle affiné donne de meilleurs résultats ou non.

Affinage avec QLoRA.

Nous avons maintenant tout ce dont nous avons besoin : le modèle, le tokenizer et le dataset téléchargés.

Nous pouvons commencer le processus d’affinage en utilisant QLoRA pour générer un nouveau modèle capable de générer des prompts similaires à ceux contenus dans le dataset.

La première étape consistera à créer un objet de configuration LoRA où nous définirons les variables spécifiant les caractéristiques du processus d’affinage.

# TARGET_MODULES# https://github.com/huggingface/peft/blob/39ef2546d5d9b8f5f8a7016ec10657887a867041/src/peft/utils/other.py#L220import peftfrom peft import LoraConfig, get_peft_modellora_config = LoraConfig(    r=16, # Plus R est grand, plus les paramètres à entraîner sont grands.    lora_alpha=16, # un facteur d'échelle qui ajuste l'amplitude de la matrice de poids. Il semble qu'à mesure que sa valeur est plus élevée, le nouvel entraînement a plus de poids.    target_modules=target_modules,    lora_dropout=0.05, # Aide à éviter le surajustement.    bias="none", # cela indique si le paramètre de biais doit être entraîné.    task_type="CAUSAL_LM")

Examinons les valeurs que nous avons définies :

  • r : Cela indique la taille de la reparamétrisation. Gardez à l’esprit que plus la valeur est petite, moins de paramètres sont entraînés. Entraîner plus de paramètres donne une meilleure chance d’apprendre la relation entre les entrées et les sorties, mais c’est aussi plus coûteux en termes de calcul. Une valeur de 16 est un compromis raisonnable, nous permettant de contrôler les paramètres tout en obtenant un résultat correct.
  • lora_alpha : Ce facteur ajuste l’amplitude de la matrice de poids. Dans les modèles plus petits, il n’a généralement pas un impact significatif, mais dans les modèles plus grands, il aide à accorder plus de poids à l’affinage fin par rapport au reste des poids inchangés.
  • target_modules : Cela indique quels modules nous voulons entraîner. Cela peut sembler une décision difficile, principalement parce que vous devez connaître les noms internes des modules dans le modèle. Heureusement, vous pouvez consulter la documentation fournie par Hugging Face, où il spécifie les modules disponibles pour chaque famille de modèles.
  • lora_dropout : Si vous avez déjà entraîné des modèles d’apprentissage profond, vous connaissez probablement le dropout. Il est utilisé pour prévenir le surajustement. Dans ce cas, compte tenu de la courte durée de formation et des données limitées, vous pouvez expérimenter avec une valeur de dropout de 0.
  • bias : Il y a trois options – none, all, et lora_only. Pour la classification de texte, none est couramment utilisé. Pour des tâches plus complexes, vous pouvez choisir entre all et lora_only.

Maintenant, créons un répertoire qui contiendra le modèle nouvellement affiné, qui doit être spécifié en tant qu’argument à la classe TrainingArguments.

# Créer un répertoire pour contenir le modèleimport osworking_dir = './'output_directory = os.path.join(working_dir, "peft_lab_outputs")

# Création de TrainingArgsimport transformersfrom transformers import TrainingArguments # , Trainertraining_args = TrainingArguments(    output_dir=output_directory,    auto_find_batch_size=True, # Trouver une taille de lot correcte adaptée à la taille des données.    learning_rate=2e-4, # Taux d'apprentissage plus élevé que l'affinement complet.    num_train_epochs=5)

La classe TrainingArguments reçoit des paramètres que nous connaissons tous, tels que le nombre d’époques d’entraînement et le taux d’apprentissage.

Maintenant, nous avons tout ce dont nous avons besoin pour entraîner le modèle.

  • Le modèle.
  • TrainingArgs.
  • jeu de données.
  • Configuration LoRA.
tokenizer.pad_token = tokenizer.eos_tokentrainer = SFTTrainer(    model=foundation_model,    args=training_args,    train_dataset=train_sample,    peft_config=lora_config,    dataset_text_field="prompt",    tokenizer=tokenizer,    data_collator=transformers.DataCollatorForLanguageModeling(tokenizer, mlm=False))trainer.train()

TrainOutput(global_step=65, training_loss=2.7377777099609375, metrics={‘train_runtime’: 404.0462, ‘train_samples_per_second’: 0.619, ‘train_steps_per_second’: 0.161, ‘total_flos’: 966262938697728.0, ‘train_loss’: 2.7377777099609375, ‘epoch’: 5.0})

Nous pouvons maintenant enregistrer ce modèle, qui devrait fonctionner correctement.

# Enregistrer le modèle.peft_model_path = os.path.join(output_directory, f"lora_model")trainer.model.save_pretrained(peft_model_path)

Tester le modèle affiné.

# Importer peftfrom peft import AutoPeftModelForCausalLM, PeftConfig#importer osdevice_map = {"": 0}working_dir = './'output_directory = os.path.join(working_dir, "peft_lab_outputs")peft_model_path = os.path.join(output_directory, f"lora_model")# Charger le modèle.loaded_model = AutoPeftModelForCausalLM.from_pretrained(                                        peft_model_path,                                        torch_dtype=torch.bfloat16,                                        is_trainable=False,                                        load_in_4bit=True,                                        device_map = 'auto')

# Demander à l'mp affiné.input_sentences = tokenizer("Je veux que tu agisses en tant que coach motivant. ", return_tensors="pt").to('cuda')foundational_outputs_sentence = get_outputs(loaded_model, input_sentences, max_new_tokens=50)print(tokenizer.batch_decode(foundational_outputs_sentence, skip_special_tokens=True))

[“Je veux que vous agissiez en tant que coach de motivation. Vous travaillerez avec une personne qui rencontre des difficultés dans sa carrière et qui n’a pas réussi à trouver le succès. La personne peut avoir une certaine expérience préalable, mais elle cherche maintenant de nouvelles opportunités qui peuvent l’aider à réaliser davantage.\nLa situation actuelle du client”]

J’aime cette réponse !

Conclusions.

Comparons les réponses :

  • Modèle pré-entraîné : Je veux que vous agissiez en tant que coach de motivation. Je ne veux pas dire cela dans le sens de dire aux gens ce qu’ils devraient faire, mais plutôt les encourager et les aider à motiver leurs propres actions. Vous pouvez commencer par poser des questions comme celles-ci : Quels sont vos objectifs ? Comment cela aidera-t-il à les atteindre ? Ensuite.
  • Modèle fine-tuné : Je veux que vous agissiez en tant que coach de motivation. Je vais fournir quelques détails sur une personne qui a besoin d’aide pour améliorer sa confiance, et votre objectif est “Des idées pour aider quelqu’un à améliorer sa confiance en soi.” Votre première suggestion devrait être “Fournir de l’encouragement lorsque c’est le plus nécessaire” ; ma réponse

Il est clair que le processus de fine-tuning a eu un effet positif sur la structure de la réponse. Le modèle fine-tuné a généré une réponse beaucoup plus proche de la demande à laquelle nous nous attendions. Je considère l’expérience comme une réussite.

Je suis sûr qu’avec un entraînement plus long, il est possible d’obtenir de meilleurs résultats.

Vous pouvez effectuer des tests en modifiant les variables d’entraînement et tirer vos propres conclusions. Si vous visez un grand défi, essayez de répéter l’exercice en fine-tunant un modèle Mistral 7B !

Ressources.

Le cours complet sur les modèles de langage volumineux est disponible sur GitHub. Pour rester informé des nouveaux articles, veuillez envisager de suivre le référentiel ou de le mettre en favori. Ainsi, vous recevrez des notifications chaque fois qu’un nouveau contenu sera ajouté.

GitHub – peremartra/Large-Language-Model-Notebooks-Course: Practical course about Large Language…

Practical course about Large Language Models. . Contribute to peremartra/Large-Language-Model-Notebooks-Course…

github.com

Si vous souhaitez obtenir plus d’informations sur le fonctionnement de QLoRA : https://arxiv.org/abs/2305.14314

Cet article fait partie d’une série dans laquelle nous explorons les applications pratiques des modèles de langage volumineux. Vous pouvez trouver le reste des articles dans la liste suivante :

Pere Martra

Pere Martra

Large Language Models Practical Course

Voir la liste10 histoires

J’écris régulièrement sur l’apprentissage profond et l’IA. Envisagez de me suivre sur VoAGI pour recevoir des mises à jour sur les nouveaux articles. Et bien sûr, vous pouvez vous connecter avec moi sur LinkedIn et twitter.

We will continue to update IPGirl; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

AI

L'avenir du développement web Prédictions et possibilités

Découvrez le futur du développement web ! Explorez l'IA, les PWA, la VR et bien plus encore. Obtenez des informations...

AI

Éthique de l'IA générative

Avec tout le tumulte entourant l'intelligence artificielle générative (IA), il y a de plus en plus de questions sans ...

AI

L'équipe Stability AI présente FreeWilly1 et FreeWilly2 de nouveaux modèles de langage à grande échelle (LLM) en libre accès

FreeWilly1 et son successeur FreeWilly2 sont de puissants nouveaux modèles de langage à grande échelle (LLM) open sou...

Apprentissage automatique

Qu'est-ce que les grands modèles de langage (LLMs) ? Applications et types de LLMs.

Les programmes informatiques appelés modèles de langage volumineux offrent aux logiciels de nouvelles options pour an...

AI

L'Associated Press dévoile des lignes directrices sur l'IA pour les journalistes

L’avancement rapide de l’intelligence artificielle (IA) a ouvert la voie à son intégration dans divers se...