📚 Documentation Utilisateur : Charlotte-1 SLM (Aricate)
Présentation Générale ✨
Bienvenue dans la documentation du modèle Charlotte-1, un Small Language Model (SLM) entraîné from scratch par Clemylia, utilisant l'architecture Aricate (GRU + Attention).
Charlotte-1 est spécialisé dans la continuation de phrases et la génération de contenu textuel basé sur des articles (Espoir, amour, amitié). Il excelle à prédire le mot le plus pertinent suivant un contexte donné.
| Caractéristique | Détail |
|---|---|
| Architecte | Clemylia |
| Architecture | Aricate (GRU + Attention) |
| Dépôt Hugging Face | Clemylia/Charlotte-1.0 |
| Fonction | Prédiction du mot suivant et génération de phrases |
| Langue | Français |
🛠️ Prérequis et Installation
Pour utiliser Charlotte-1, vous devez disposer d'un environnement Python avec les bibliothèques suivantes :
- PyTorch : Le framework principal.
- Hugging Face Hub : Pour télécharger les fichiers du modèle.
- SafeTensors : Pour charger les poids du modèle.
# Installation des dépendances nécessaires
pip install torch huggingface-hub safetensors tqdm
📦 Utilisation du Modèle pour la Génération de Texte
Le code ci-dessous vous permet de charger le modèle directement depuis le profil Hugging Face de Clemylia et de générer du texte en fournissant une phrase de départ (prompt).
Code d'Inférence (À Exécuter)
Vous pouvez copier et exécuter le code suivant (en le sauvegardant par exemple sous le nom generate_charlotte.py) :
import torch
import torch.nn as nn
import torch.nn.functional as F
import json
import os
from safetensors.torch import load_file as load_safetensors_file
from huggingface_hub import hf_hub_download
# --- Configuration (CRITIQUE : DOIT ÊTRE IDENTIQUE À L'ENTRAÎNEMENT) ---
CONFIG = {
"HF_REPO_ID": "Clemylia/Charlotte-1.0",
"MODEL_NAME": "Charlotte-1",
"MAX_LEN_INPUT": 80,
"EMBEDDING_DIM": 100,
"HIDDEN_DIM": 128,
"NUM_LAYERS": 2,
"DEVICE": torch.device("cuda" if torch.cuda.is_available() else "cpu"),
}
WEIGHTS_FILENAME = f"{CONFIG['MODEL_NAME']}_best_weights.safetensors"
TOKENIZER_FILENAME = f"{CONFIG['MODEL_NAME']}_tokenizer.json"
# --- Définitions des Classes Aricate (Omisses pour la concision, mais doivent être incluses) ---
# NOTE: Inclure ici les classes AricateAttentionLayer, AricateModel et InferenceTokenizer
# exactement comme dans le script d'inférence précédent.
# Pour une exécution directe, ces classes DOIVENT être présentes dans le script.
# --- Fonctions de Chargement et Génération ---
def load_model_from_hub(repo_id, weights_filename, tokenizer_filename):
"""Télécharge les fichiers et charge le modèle Charlotte-1."""
#
print(f"🌍 Téléchargement des fichiers depuis {repo_id}...")
try:
# Téléchargement du Tokenizer
tokenizer_path = hf_hub_download(repo_id=repo_id, filename=tokenizer_filename)
tokenizer = InferenceTokenizer(tokenizer_path)
# Téléchargement des Poids
weights_path = hf_hub_download(repo_id=repo_id, filename=weights_filename)
state_dict = load_safetensors_file(weights_path)
# Initialisation du Modèle (Les classes AricateModel/Attention/Tokenizer doivent être définies ici)
model = AricateModel(
vocab_size=tokenizer.vocab_size,
embedding_dim=CONFIG['EMBEDDING_DIM'],
hidden_dim=CONFIG['HIDDEN_DIM'],
num_layers=CONFIG['NUM_LAYERS']
).to(CONFIG['DEVICE'])
model.load_state_dict(state_dict)
model.eval()
print(f"✅ Modèle {CONFIG['MODEL_NAME']} chargé avec succès.")
return model, tokenizer
except Exception as e:
print(f"❌ ERREUR lors du chargement: {e}")
return None, None
def generate_sentence(model, tokenizer, prompt, max_length=50, temperature=0.7):
"""Génère du texte mot par mot (boucle séquentielle Aricate)."""
model.eval()
current_prompt = prompt.lower()
separator_token = ' <sep>'
print(f"\n[Génération démarrée] Température: {temperature}")
print("--------------------------------------------------")
print(current_prompt, end=" ", flush=True)
generated_text = current_prompt.split()
with torch.no_grad():
for i in range(max_length):
input_text = current_prompt + separator_token
input_ids = tokenizer.encode(input_text)
if len(input_ids) > CONFIG['MAX_LEN_INPUT']:
input_ids = input_ids[-CONFIG['MAX_LEN_INPUT']:]
padding_needed = CONFIG['MAX_LEN_INPUT'] - len(input_ids)
padded_ids = input_ids + [tokenizer.special_tokens['<pad>']] * padding_needed
input_tensor = torch.tensor([padded_ids], dtype=torch.long, device=CONFIG['DEVICE'])
logits = model(input_tensor)
output_position = len(input_ids) - 1
if output_position < 0: output_position = 0
next_token_logits = logits[0, output_position, :]
if temperature == 0:
next_token_id = torch.argmax(next_token_logits).item()
else:
probabilities = F.softmax(next_token_logits / temperature, dim=-1).squeeze(0)
next_token_id = torch.multinomial(probabilities, num_samples=1).item()
next_word = tokenizer.decode_id(next_token_id)
if next_word in ['<unk>', '<sep>', '<pad>']:
print("...", end="")
break
print(next_word, end=" ", flush=True)
current_prompt += f" {next_word}"
generated_text.append(next_word)
if next_word.endswith(('.', '!', '?')) and len(generated_text) > 5:
break
print("\n--------------------------------------------------")
if __name__ == '__main__':
# ⚠️ REMPLACER LES CLASSES MANQUANTES ICI
# (Incluez les définitions d'AricateModel, AricateAttentionLayer, InferenceTokenizer)
# ... (Les classes AricateAttentionLayer, AricateModel et InferenceTokenizer du script précédent doivent être définies ici) ...
model, tokenizer = load_model_from_hub(
CONFIG['HF_REPO_ID'],
WEIGHTS_FILENAME,
TOKENIZER_FILENAME
)
if model:
# Exemple d'utilisation
prompt_1 = "l'intelligence artificielle est un domaine en pleine"
generate_sentence(model, tokenizer, prompt=prompt_1, max_length=25, temperature=0.8)
⚙️ Paramètres de Génération
Vous pouvez ajuster deux paramètres principaux dans la fonction generate_sentence pour contrôler le style du texte généré :
1. max_length 📏
- Description : Le nombre maximal de nouveaux mots que le modèle est autorisé à générer après le prompt.
- Recommandation : Fixez-le à une valeur raisonnable (ex. 20 à 50) pour les phrases courtes ou les continuations d'articles.
2. temperature 🌡️ (Créativité)
- Description : Contrôle la liberté et l'aléatoire de la sélection du prochain mot.
- Température basse (proche de 0.0) : Le modèle choisit les mots les plus probables, ce qui donne un texte cohérent et prédictible (Greedy). Idéal pour les suites logiques ou les faits.
- Température élevée (proche de 1.0) : Le modèle est plus créatif et explore des options moins probables, ce qui peut conduire à des résultats inattendus, mais potentiellement plus intéressants.
| Température | Résultat | Utilisation |
|---|---|---|
| 0.0 | Déterministe, logique. | Suites de faits, complétion simple. |
| 0.7 - 0.9 | Équilibré, fluide, créatif. | Génération d'articles, histoires courtes. |
| 1.0+ | Chaotique, hautement aléatoire. | Expérimentation. |
Si le code par défaut ne fonctionne pas, voici un exemple d'utilisation qui fonctionnera :
import torch
import torch.nn as nn
import torch.nn.functional as F
import json
import os
import collections
from safetensors.torch import load_file as load_safetensors_file
from huggingface_hub import hf_hub_download # Import essentiel pour le Hub
from tqdm import tqdm
# --- Configuration Globale (Doit correspondre à l'entraînement) ---
CONFIG = {
"HF_REPO_ID": "Clemylia/Charlotte-1.0", # Votre dépôt Hugging Face
"MODEL_NAME": "Charlotte-1", # Nom du modèle dans les fichiers
"MAX_LEN_INPUT": 80,
"EMBEDDING_DIM": 100,
"HIDDEN_DIM": 128,
"NUM_LAYERS": 2,
"DEVICE": torch.device("cuda" if torch.cuda.is_available() else "cpu"),
}
# --- Noms de Fichiers sur le Hub ---
WEIGHTS_FILENAME = f"{CONFIG['MODEL_NAME']}_best_weights.safetensors"
TOKENIZER_FILENAME = f"{CONFIG['MODEL_NAME']}_tokenizer.json"
# ----------------------------------------------------------------------
## 1. Classes (Tokenizer et Architecture Aricate)
# ----------------------------------------------------------------------
class InferenceTokenizer:
"""Charge le vocabulaire pour l'inférence."""
def __init__(self, tokenizer_file_path):
with open(tokenizer_file_path, 'r', encoding='utf-8') as f:
self.word_to_id = json.load(f)
self.id_to_word = {v: k for k, v in self.word_to_id.items()}
self.vocab_size = len(self.word_to_id)
self.special_tokens = {
'<pad>': self.word_to_id.get('<pad>', 0),
'<unk>': self.word_to_id.get('<unk>', 1),
'<sep>': self.word_to_id.get('<sep>', 2)
}
def encode(self, text):
tokens = text.lower().split()
ids = [self.word_to_id.get(token, self.special_tokens['<unk>']) for token in tokens]
return ids
def decode_id(self, token_id):
return self.id_to_word.get(int(token_id), '<unk>')
# --- Architecture Aricate (Identique à l'entraînement) ---
class AricateAttentionLayer(nn.Module):
def __init__(self, hidden_dim):
super().__init__()
self.W = nn.Linear(hidden_dim, hidden_dim)
self.U = nn.Linear(hidden_dim, hidden_dim)
self.V = nn.Linear(hidden_dim, 1, bias=False)
def forward(self, rnn_outputs, last_hidden):
last_hidden_expanded = last_hidden.unsqueeze(1)
energy = torch.tanh(self.W(rnn_outputs) + self.U(last_hidden_expanded))
attention_weights_raw = self.V(energy).squeeze(2)
attention_weights = F.softmax(attention_weights_raw, dim=1)
context_vector = torch.sum(rnn_outputs * attention_weights.unsqueeze(2), dim=1)
return context_vector
class AricateModel(nn.Module):
def __init__(self, vocab_size: int, embedding_dim: int, hidden_dim: int, num_layers: int):
super().__init__()
self.word_embeddings = nn.Embedding(
num_embeddings=vocab_size,
embedding_dim=embedding_dim,
padding_idx=0
)
self.rnn = nn.GRU(
input_size=embedding_dim,
hidden_size=hidden_dim,
num_layers=num_layers,
batch_first=True
)
self.attention = AricateAttentionLayer(hidden_dim)
self.hidden_to_vocab = nn.Linear(hidden_dim * 2, vocab_size)
def forward(self, input_words):
embeds = self.word_embeddings(input_words)
rnn_out, hn = self.rnn(embeds)
last_hidden = hn[-1]
context_vector = self.attention(rnn_out, last_hidden)
combined_features = torch.cat((context_vector, last_hidden), dim=1)
logits = self.hidden_to_vocab(combined_features)
return logits
# ----------------------------------------------------------------------
## 3. Fonctions de Chargement depuis Hugging Face et de Génération
# ----------------------------------------------------------------------
def load_model_from_hub(repo_id, weights_filename, tokenizer_filename):
"""Télécharge les fichiers et charge le modèle Charlotte-1."""
print(f"🌍 Téléchargement des fichiers depuis {repo_id}...")
try:
# Téléchargement du Tokenizer
tokenizer_path = hf_hub_download(repo_id=repo_id, filename=tokenizer_filename)
tokenizer = InferenceTokenizer(tokenizer_path)
# Téléchargement des Poids
weights_path = hf_hub_download(repo_id=repo_id, filename=weights_filename)
state_dict = load_safetensors_file(weights_path)
# Initialisation du Modèle
model = AricateModel(
vocab_size=tokenizer.vocab_size,
embedding_dim=CONFIG['EMBEDDING_DIM'],
hidden_dim=CONFIG['HIDDEN_DIM'],
num_layers=CONFIG['NUM_LAYERS']
).to(CONFIG['DEVICE'])
# Chargement des Poids
model.load_state_dict(state_dict)
model.eval()
print(f"✅ Modèle {CONFIG['MODEL_NAME']} chargé avec succès depuis Hugging Face.")
return model, tokenizer
except Exception as e:
print(f"❌ ERREUR lors du chargement depuis Hugging Face: {e}")
print("Vérifiez l'ID du dépôt, le nom des fichiers et les hyperparamètres du modèle.")
return None, None
def generate_sentence(model, tokenizer, prompt, max_length=50, temperature=0.7):
"""Génère du texte mot par mot."""
model.eval()
current_prompt = prompt.lower()
separator_token = ' <sep>'
print(f"\n[Génération démarrée] Température: {temperature}")
print("--------------------------------------------------")
print(current_prompt, end=" ", flush=True)
generated_text = current_prompt.split()
with torch.no_grad():
for i in range(max_length):
# 1. Préparation de l'entrée
input_text = current_prompt + separator_token
input_ids = tokenizer.encode(input_text)
# 2. Tronquage et Padding
if len(input_ids) > CONFIG['MAX_LEN_INPUT']:
input_ids = input_ids[-CONFIG['MAX_LEN_INPUT']:]
padding_needed = CONFIG['MAX_LEN_INPUT'] - len(input_ids)
padded_ids = input_ids + [tokenizer.special_tokens['<pad>']] * padding_needed
input_tensor = torch.tensor([padded_ids], dtype=torch.long, device=CONFIG['DEVICE'])
# 3. Prédiction
logits = model(input_tensor)
# L'output est pour la position du token <sep> (dernière position non-padding)
# CORRECTION: The AricateModel outputs a single prediction for the next token,
# so the logits tensor shape is [batch_size, vocab_size].
# We directly use the logits for the first (and only) item in the batch.
next_token_logits = logits[0, :]
# 4. Choix du token (Sampling ou Greedy)
if temperature == 0:
next_token_id = torch.argmax(next_token_logits).item()
else:
probabilities = F.softmax(next_token_logits / temperature, dim=-1).squeeze(0)
next_token_id = torch.multinomial(probabilities, num_samples=1).item()
# 5. Décodage et Arrêt
next_word = tokenizer.decode_id(next_token_id)
if next_word in ['<unk>', '<sep>', '<pad>']:
print("...", end="")
break
print(next_word, end=" ", flush=True)
# 6. Mise à jour du Prompt
current_prompt += f" {next_word}"
generated_text.append(next_word)
if next_word.endswith(('.', '!', '?')) and len(generated_text) > 5:
break
print("\n--------------------------------------------------")
return " ".join(generated_text)
# ----------------------------------------------------------------------
## 4. Bloc d'Exécution Principal
# ----------------------------------------------------------------------
if __name__ == '__main__':
model, tokenizer = load_model_from_hub(
CONFIG['HF_REPO_ID'],
WEIGHTS_FILENAME,
TOKENIZER_FILENAME
)
if model is None:
exit()
print("\n" + "="*70)
print(f"TEST DE GÉNÉRATION CHARLOTTE-1 DEPUIS {CONFIG['HF_REPO_ID']}")
print("="*70)
# --- Test 1: Génération Créative ---
prompt_1 = "l'intelligence artificielle permet aux entreprises de"
generate_sentence(
model,
tokenizer,
prompt=prompt_1,
max_length=30,
temperature=0.9
)
# --- Test 2: Génération plus déterministe ---
prompt_2 = "les dernières actualités scientifiques parlent de l'amitié"
generate_sentence(
model,
tokenizer,
prompt=prompt_2,
max_length=20,
temperature=0.8
)
print("\nTests de génération terminés.")
