Créer son propre modèle local à partir d'un modèle existant performant

Créer son propre modèle local à partir d'un modèle existant performant

Dominique Delaire

L'objectif de ce tutoriel est de vous montrer comment créer son propre modèle IA à partir d'un autre et l'embellir avec notre contexte, nos données, etc... et le publier. Tout cela avec des outils open source ou gratuits :)

Nous allons bâtir un modèle en lien avec le management et des conseils sur la gestion des ressources.

Ceci est un modèle exemple, celui qui est disponible dans notre boutique est beaucoup plus précis et a énormément de contenus en management pour les décideurs, Vp, Directeurs, etc...

Prérequis

Tous les prérequis sont déjà installés dans ShellbotsOS. Voici les informations pour les autres systèmes d'exploitation :

  • Avoir au moins une carte Nvidia RTX 3060
  • les drivers nvidia à jour sous ShellbotsOS ou linux Ubuntu
  • CUDA : une plateforme de calcul parallèle développée par Nvidia (Bibliothèques, outils et langages). Pour les développeurs et qui permet d'utiliser la puissance de calcul des gpu nvidia.
  • Python3, Pytorch avec support Cuda
  • Qlora, librairies Hugging Face (modèle Phi-3 et dataset sur le management)
  • Ollama

Validation des prérequis

  • Vérification Nvidia et drivers :
    • Tapez la commande dans un terminal nvidia-smi
    • Cela vous permet de voir si vous avez une carte nvidia, quelle version, la version de CUDA aussi (noter la version, cela va nous servir plus tard)
  • Pour mettre à jour vos drivers nvidia si c'est requis : 
    • taper :
      • sudo apt update
      • sudo apt upgrade -y
      • sudo apt autoremove -y
      • puis pour mettre à jour vos drivers : sudo ubuntu-drivers autoinstall
      • le système vous demandera de rebooter : sudo reboot
  • Vérifier si Python 3.10 minimum est installé. ShellbotsOS vient avec Python préinstallé.
  • Pour installer Python 3.10 : sudo apt install python3-pip python3-venv -y
  • Nous allons maintenant créer un répertoire spécifique et un environnement virtuel pour notre projet :
    • mkdir ~/finetune_modelshellbotsnano
      cd ~/finetune_modelshellbotsnano
      python3 -m venv finetune_env
  • Activation de l'environnement virtuel :
    • source finetune_env/bin/activate
  • le fait d'avoir (finetune_env) devant le prompt indique que l'environnement virtuel est actif
  • Maintenant il est nécessaire si ce n'est pas déjà fait, d'installer le framework ML principal, Pytorch, mais qui correspond à notre version de Cuda. Dans notre premier écran, la version indiquait 12.8, donc la commande pour installer pytorch est :
    • pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu128
  • Ensuite, nous allons installer les bibliothèques nécessaires pour Qlora et Hugging face (j'y reviendrais en détail tout à l'heure) pour fine tune le modèle :
    • pip install transformers peft bitsandbytes accelerate datasets trl scipy
  • Ensuite, si ce n'est déjà pas fait dans votre environnement, nous allons installer Ollama (de meta), l'outil permettant d'exécuter notre modèle finetuné localement : 
    • curl -fsSL https://ollama.com/install.sh | sh
    • Vous pouvez vérifier ensuite l'installation par :
      • ollama --version
    • Et tester un petit modèle et vérifiez si le GPU est bien utilisé (nvidia-msi) pendant que vous lancez la commande ollama run llama2:7b et téléchargez le modèle et posez des questions :) 
  • Nous avons maintenant tous les éléments et prérequis pour commencer à "finetuner" un modèle existant.

 Préparation des données d'entraînement

Pour notre tutoriel, nous utilisons le modèle Phi-3 Mini de Microsoft. Ce modèle est un excellent exemple de Small Language Model (SLM) : il est performant malgré sa petite taille (3.8 milliards de paramètres). Nous allons y ajouter des données sur le management et la gestion des ressources avec un dataset assez connu sur hugging face : https://huggingface.co/datasets/tatsu-lab/alpaca/viewer/default/train?q=management

C'est pour cette raison qu'il est idéal pour l'IA locale : il offre un équilibre parfait entre des résultats de haute qualité et la possibilité de s'exécuter sur des GPU NVIDIA grand public.

Le fine-tuning de modèles comme celui-ci est rendu possible grâce à l'écosystème Hugging Face, qui sert de "GitHub de l'IA" : c'est le dépôt central où le modèle de base (microsoft/Phi-3-mini-4k-instruct) est hébergé via leur bibliothèque transformers.

Afin d'adapter ce modèle sur votre GPU NVIDIA sans surcharger la VRAM, nous utilisons la technique LoRA (Low-Rank Adaptation). Au lieu de réentraîner les milliards de paramètres du modèle (ce qui est impossible en local), LoRA ajoute et ajuste seulement de petites matrices d'adaptation (les "adaptateurs") qui apprennent la nouvelle spécialisation.

Nous utilisons spécifiquement QLoRA (Quantized LoRA), la version la plus efficace en ressources. QLoRA utilise des outils de quantification 4-bit (bitsandbytes) pour réduire la taille du modèle de base au maximum, lui permettant de tenir dans votre VRAM pendant que LoRA se charge de l'apprentissage ciblé.

En utilisant les bibliothèques peft et trl de Hugging Face, on orchestre l'ensemble de ce processus de manière optimisée.

 

Voici le code python permettant de formater nos données du dataset et enseigner au modèle de base Phi-3Mini) : 

Explications du code :

  • Configuration et chargement des librairies
  • Préparation des données et Tokenization
  • Configuration de la quantification (Qlora)
  • Chargement du modèle et Configuration Lora
  • Configuration de l'entraîneur (SFTTrainer)
  • Lancement de l'entraînement et sauvegarde
# Tutoriel finetuning de modèles
# Dominique Delaire, https://Shellbots.ai
# Tuto intégral sur le blog de notre site.

import torch
import os
import warnings
import logging
from datasets import load_dataset
# CORRECTION D'IMPORTATION : LoraConfig et PeftModel viennent de peft
from peft import LoraConfig, PeftModel
# CORRECTION D'IMPORTATION : BitsAndBytesConfig et autres viennent de transformers
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, TrainingArguments
from trl import SFTTrainer

# filtre des logs
# Masquer l'avertissement de trl sur 'tokenizer'
warnings.filterwarnings(
"ignore",
message="`tokenizer` is deprecated and will be removed in version 5.0.0 for `SFTTrainer.__init__`.*",
category=FutureWarning
)
# Cacher les warnings des librairies (casting torch, etc.)
logging.getLogger("transformers").setLevel(logging.ERROR)
logging.getLogger("trl").setLevel(logging.ERROR)
warnings.filterwarnings("ignore", category=UserWarning)


# variables de config

MODEL_NAME = "microsoft/Phi-3-mini-4k-instruct"
DATASET_NAME = "tatsu-lab/alpaca"
OUTPUT_DIR = "./phi3_management_checkpoint"
MERGED_MODEL_PATH = "./shellbots_model"
MANAGEMENT_KEYWORDS = ["team", "manager", "leadership", "business strategy", "project plan", "employee", "meeting", "productivity"]

if not torch.cuda.is_available():
raise SystemError("CUDA n'est pas disponible.")

# On doit formater les données comme un prompt avec des réponses

tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token

tokenizer.padding_side = 'right'

def format_data(example):
return {
"text": f"### Instruction: {example['instruction']}\n### Réponse: {example['output']}{tokenizer.eos_token}"
}

# QLoRA et modèle avec flash_attention pour les performances

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

lora_config = LoraConfig(
r=16,
lora_alpha=32,
lora_dropout=0.05,
target_modules="all-linear",
bias="none",
task_type="CAUSAL_LM",
)

print(f"Chargement du modèle {MODEL_NAME} en QLoRA avec Flash Attention 2...")
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
quantization_config=bnb_config,
dtype=torch.float16,
device_map="auto",
trust_remote_code=True,
attn_implementation="flash_attention_2"
)
model.config.use_cache = False
model.config.pretraining_tp = 1

# on charge le dataset exemple sur le management

print(f"Téléchargement et filtrage du dataset {DATASET_NAME}...")
dataset = load_dataset(DATASET_NAME, split="train")
dataset = dataset.filter(lambda x: any(k in x['instruction'].lower() for k in MANAGEMENT_KEYWORDS))
dataset = dataset.map(format_data, remove_columns=["instruction", "output", "input"])

# Entrainement avec les données du dataset

training_arguments = TrainingArguments(
output_dir=OUTPUT_DIR,
num_train_epochs=3,
per_device_train_batch_size=2,
gradient_accumulation_steps=2,
optim="paged_adamw_32bit",
logging_steps=50,
learning_rate=2e-4,
weight_decay=0.001,
fp16=True,
bf16=False,
max_grad_norm=0.3,
warmup_ratio=0.03,
group_by_length=True,
lr_scheduler_type="cosine",
save_strategy="epoch",
report_to="none",
)

trainer = SFTTrainer(
model=model,
train_dataset=dataset,
peft_config=lora_config,
dataset_text_field="text",
max_seq_length=512,
tokenizer=tokenizer,
args=training_arguments,
)

print("\nDébut du Fine-Tuning...")
trainer.train()

print("\nTerminé ! Sauvegarde des adaptateurs...")
trainer.model.save_pretrained(OUTPUT_DIR)

# Fusion du modèle

print("Fusion du modèle...")
# Libérer le GPU
del model, trainer
torch.cuda.empty_cache()

# Réutilisation de la config QLORA (critique pour la basse RAM)
bnb_config_merge = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=False,
)

# Rechargement du modèle de base
print("Chargement du modèle de base pour la fusion...")
base_model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
quantization_config=bnb_config_merge,
dtype=torch.float16,
device_map="auto",
trust_remote_code=True
)

# Fusion des adaptateurs sur le modèle
print("Fusion des adaptateurs PEFT...")
merged_model = PeftModel.from_pretrained(base_model, OUTPUT_DIR)
merged_model = merged_model.merge_and_unload()

# On sauvegarde le modèle
os.makedirs(MERGED_MODEL_PATH, exist_ok=True)
merged_model.save_pretrained(MERGED_MODEL_PATH)
tokenizer.save_pretrained(MERGED_MODEL_PATH)

print(f"Modèle fusionné prêt dans : {MERGED_MODEL_PATH}")

 

Voici ce que cela donne à l'exécution pour créer et fusionné notre modèle Phi-3 avec nos données du dataset :

Posts Traitements de notre modèle

Une fois le modèle entraîné et sauvegardé, si on veut l'exploiter avec Ollama, il est nécessaire de convertir notre modèle au format GGUF

Le GGUF (Gpt-GEneration Unified Format), est un format de fichier binaire spécialement conçu pour l'inférence des modèles de langages (LLM) sur le CPU et le GPU. C'est le format standard utilisé par plein d'outils, notamment par Ollama et son framework llama.cpp.

Tout est préinstallé sur ShellbotsOS mais si vous avez un linux différent ou Ubuntu, il faudra installer le framework llama.cpp et le compiler avec cmake.

J'ai fait une duplication du build de ollama avec l'ensemble de ces outils ici pour l'exemple : on utilise ici le script fournit par le framework ollama qui permet de convertir notre modèle généré avec notre python avec l'outil convert_hf_to_gguf.py

Puisque notre modèle était finetuné dans un modèle au format Hugging Face (.safetensors), nous l'avons converti dans un modèle plus générique :

Avec le script Ollama, nous spécifions le répertoire où se trouve notre modèle généré au format HuggingFace et le fichier de sortie de notre modèle GGUF. Le f16 signifie que chaque poids ou paramètre du modèle est stocké en utilisant 16 bits (2 octets)

A l'exécution : 

Quantification du modèle (Compression pour la vitesse et la taille)

On veut quantifier le modèle pour que le modèle puisse s'exécuter beaucoup plus rapidement sur du matériel standard comme un cpu ou gpu on va dire "grand public" :)

c'est donc un processus de compression des poids d'un modèle de llm, passant d'une haute précision (16 bits) à une basse précision (4 bits). Cette technique bien connue réduit drastiquement la taille du fichier et la consommation de mémoire sans avoir d'impacts.

Le framework d'Ollama, ollama.cpp, à une fonction ollama_quantize permettant de réaliser cette opération. Les noyaux de calcul de llama.cpp sont hyper optimisés, cela accélère la vitesse d'inférence, cela permet d'avoir une utilisation fluide en local :)

On tape la commande suivante :

./llama-quantize shellbots_modelmanagement_fp16.gguf shellbots_modelmanagement_q4_k_m.gguf Q4_K_M

Notre modèle passe de 7Gb à 2Gb :)

Le type Q4_K_M est le format optimisé pour la quantification. Il offre un excellent équilibre entre la vitesse d'inférence / performance et la préservation de la qualité du modèle.

Utilisation et tests dans Ollama

Pour utiliser maintenant notre modèle, on va créer un ModelFile (pour définir le rôle) qui pointe vers notre modèle GGUF optimisé.

On va lui donner un rôle de "Manager expert". On créé le fichier ModelFile suivant : 

FROM ./llama.cpp/build/shellbots_modelmanagement_q4_k_m.gguf

# Role du modèle créé et fusionné
SYSTEM """Tu es un expert en management, en leadership, et en stratégie d'entreprise. Tes réponses sont toujours
concises, extrêmement professionnelles et orientées solution. Tu t'adresses à l'utilisateur comme à un
collaborateur clé."""

# Paramètres spécifiques au modèle initial
TEMPLATE """{{ if .System }}<|system|>
{{ .System }}<|end|>
{{ end }}{{ if .Prompt }}<|user|>
{{ .Prompt }}<|end|>
{{ end }}<|assistant|>
{{ .Response }}<|end|>"""

# Indique le jeton d'arrêt pour éviter que le modèle ne continue de générer au-delà de sa réponse
PARAMETER stop "<|end|>"

# Paramètres d'inférence (conservateurs pour la qualité)
PARAMETER temperature 0.6
PARAMETER top_k 40
PARAMETER top_p 0.9


Ensuite, on va importer le modèle (enfin !) dans Ollama :)

On tape la commande : ollama create shellbots-manager -f ModelFile

On vérifie que notre modèle est bien dans l'écosystème de Ollama avec la commande ollama list :

Si on regarde les informations sur notre modèle, on retrouve bien notre architecture phi3 avec notre rôle bien défini :

Maintenant on teste notre modèle :) on tape la commande ollama run shellbots-manager

Voici 3 prompts d'exemples. Le résultat est instanté pas d'attente de réflexion grâce à la quantisation dans ollama.

Exemple d'utilisation en local avec OpenWebUI et notre modèle Ollama :

Si vous avez des questions, n'hésitez pas à contacter notre équipe :)

Dominique

Retour au blog

Laisser un commentaire