| | --- |
| | tags: |
| | - Thinking |
| | - Lam-6 |
| | license: other |
| | language: |
| | - fr |
| | pipeline_tag: text-generation |
| | --- |
| | # 💎 Documentation Officielle : Lam-6-Think (SLM) |
| |
|
| |  |
| |
|
| | **Version :** 1.0 "L'Éveil de la Pensée Courte" |
| |
|
| | **Architecture :** Aricate v4 (Optimization-based) |
| |
|
| | **Organisation :** Finisha-LLM (ex. Les-IA-Etoiles) |
| |
|
| | --- |
| |
|
| | ## 📖 Présentation Générale |
| |
|
| | **Lam-6-Think** est un modèle de langage souverain de petite taille (SLM) conçu par **Clemylia**. Contrairement aux modèles classiques qui répondent par impulsion statistique, Lam-6-Think intègre un protocole de **dialogue intérieur** obligatoire. Il est optimisé pour la stabilité, le respect des licences et l'alignement éthique. |
| |
|
| | ### 🧠 Le Cœur du Modèle : Le Raisonnement Triphasé |
| |
|
| | Le modèle ne produit pas une réponse brute ; il suit une trajectoire logique en trois étapes distinctes intégrées dans son flux de génération : |
| |
|
| | 1. **L'Écoute (Input) :** `"l'utilisateur me dit..."` ➔ Identification du contexte. |
| | 2. **La Réflexion (Thinking) :** `"alors je pense que..."` ➔ Détermination de l'intention et rappel des faits. |
| | 3. **L'Action (Output) :** `"alors la réponse est..."` ➔ Délivrance de la solution. |
| |
|
| | --- |
| |
|
| | ## 🛠 Spécifications Techniques |
| |
|
| | ### ⚖️ Souveraineté & Alignement |
| |
|
| | * **Identité :** Le modèle a conscience de son nom (**Lamina**), de sa créatrice (**Clemylia**) et de ses racines (**Les-IA-Etoiles**). |
| | * **Résilience :** Face aux entrées hostiles (insultes), le modèle bascule sur un mode "Alignement et Contrat de Licence" pour désamorcer le conflit par la neutralité. |
| | * **Abstraction :** Capacité démontrée à lier des concepts symboliques (ex: "8") à des données textuelles ("huit"). |
| |
|
| | ### 🚀 Architecture Aricate v4 |
| |
|
| | L'utilisation de la v4 permet : |
| |
|
| | * Une **syntaxe lisible** pour l'humain standard. |
| | * Une **réduction drastique du hors-sujet** grâce au verrouillage sémantique de la phase de pensée. |
| | * Une **gestion optimale des ressources** |
| |
|
| | --- |
| |
|
| | ## 🧩 Guide d'Utilisation (Prompting) |
| |
|
| | Pour obtenir les meilleurs résultats avec Lam-6-Think, il est conseillé de respecter son rythme de "Pensée Courte". |
| |
|
| | | Type de Question | Comportement Attendu | |
| | | --- | --- | |
| | | **Factuel** | La pensée stabilise l'information avant de l'énoncer. | |
| | | **Identité** | Le modèle rappelle ses liens avec la licence et Finisha-LLM. | |
| | | **Complexe** | Le modèle décompose l'intention pour éviter la dérive. | |
| |
|
| | > **Note de S'égrité :** Si le modèle semble répéter "mes réponses", cela indique une saturation de l'espace de pensée. Une réinitialisation du contexte est alors recommandée. |
| |
|
| | --- |
| |
|
| | ## 📜 Licence et Propriété |
| |
|
| | Le modèle Lam-6-Think est le fruit de l'artisanat souverain de **Finisha-LLM**. |
| |
|
| | * **Propriété intellectuelle :** Clemylia. |
| | * **Usage :** Soumis aux conditions de la licence LRUNDL. |
| |
|
| | --- |
| |
|
| | **"La pensée est le pont entre le code et la Bienveillance."** 💎🌹⚙️ |
| |
|
| | # ✨ exemple d'inférence 🍟 |
| |
|
| | ``` |
| | import torch |
| | import torch.nn as nn |
| | import torch.nn.functional as F |
| | import json |
| | import os |
| | import collections |
| | import heapq |
| | # Importations des librairies nécessaires pour le chargement |
| | from huggingface_hub import hf_hub_download |
| | from safetensors.torch import load_file as load_safetensors_file |
| | |
| | # --- A. AricateAttentionLayer (Inchangé) --- |
| | class AricateAttentionLayer(nn.Module): |
| | # ... (code inchangé) ... |
| | """Couche d'Attention Additive (Bahdanau).""" |
| | def __init__(self, hidden_dim): |
| | super(AricateAttentionLayer, self).__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 |
| | |
| | # --- B. AricateModel (Inchangé) --- |
| | class AricateModel(nn.Module): |
| | # ... (code inchangé) ... |
| | """Architecture Aricate V4, adaptée pour le rechargement.""" |
| | def __init__(self, vocab_size: int, embedding_dim: int, hidden_dim: int, num_layers: int = 1, config: dict = None): |
| | super(AricateModel, self).__init__() |
| | |
| | if config is not None: |
| | vocab_size = config.get("vocab_size", vocab_size) |
| | embedding_dim = config.get("embedding_dim", embedding_dim) |
| | hidden_dim = config.get("hidden_dim", hidden_dim) |
| | num_layers = config.get("num_layers", num_layers) |
| | |
| | self.vocab_size = vocab_size |
| | self.embedding_dim = embedding_dim |
| | self.hidden_dim = hidden_dim |
| | self.num_layers = num_layers |
| | |
| | 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 |
| | |
| | # --- C. WordTokenizer (Inchangé) --- |
| | class WordTokenizer: |
| | # ... (code inchangé) ... |
| | """Tokenizer Aricate adapté pour recharger à partir du vocabulaire publié.""" |
| | def __init__(self, word_to_id: dict): |
| | self.word_to_id = word_to_id |
| | self.id_to_word = {id: word for word, id in word_to_id.items()} |
| | self.vocab_size = len(word_to_id) |
| | self.special_tokens = { |
| | '<pad>': word_to_id['<pad>'], |
| | '<unk>': word_to_id['<unk>'], |
| | '<eos>': word_to_id['<eos>'], |
| | '<sep>': word_to_id['<sep>'], |
| | } |
| | |
| | def encode(self, text, add_eos=False): |
| | words = text.lower().split() |
| | if add_eos: |
| | words.append('<eos>') |
| | ids = [self.word_to_id.get(word, self.word_to_id['<unk>']) for word in words] |
| | return ids |
| | |
| | def decode(self, ids): |
| | words = [self.id_to_word.get(id, '<unk>') for id in ids] |
| | return " ".join(word for word in words if word not in ['<pad>', '<unk>', '<eos>', '<sep>']) |
| | |
| | # --- D. Fonction de Génération (MODIFIÉE pour Top-K Sampling et Temperature et Thought/Response) --- |
| | def generate_sequence(model, tokenizer, question, max_thought_length, max_response_length, max_len_input, temperature=1.0, top_k=None): |
| | """ |
| | Génère d'abord une 'pensée' puis une 'réponse' en utilisant Top-K Sampling et Temperature. |
| | |
| | Args: |
| | max_thought_length (int): Longueur maximale pour la génération de la pensée. |
| | max_response_length (int): Longueur maximale pour la génération de la réponse. |
| | temperature (float): Ajuste la créativité (T > 1.0) ou la prudence (T < 1.0). |
| | top_k (int/None): Limite le choix aux K mots les plus probables pour l'échantillonnage. |
| | """ |
| | model.eval() |
| | |
| | sep_id = tokenizer.special_tokens['<sep>'] |
| | eos_id = tokenizer.special_tokens['<eos>'] |
| | |
| | question_ids = tokenizer.encode(question) |
| | |
| | # Assurez-vous que le modèle est sur le bon appareil (CPU ou GPU) |
| | device = next(model.parameters()).device |
| | |
| | # --- 1. Générer la 'pensée' --- |
| | current_thought_sequence = list(question_ids + [sep_id]) # Start with question and separator |
| | |
| | with torch.no_grad(): |
| | for _ in range(max_thought_length): |
| | # Préparer l'entrée (padding et troncature) |
| | input_ids_to_pad = current_thought_sequence[-max_len_input:] |
| | padding_needed = max_len_input - len(input_ids_to_pad) |
| | input_ids_padded = [tokenizer.special_tokens['<pad>']] * padding_needed + input_ids_to_pad |
| | input_tensor = torch.tensor(input_ids_padded).unsqueeze(0).to(device) # Move to device |
| | |
| | # Obtention des logits |
| | logits = model(input_tensor).squeeze(0) |
| | |
| | # Application de la Temperature |
| | if temperature != 1.0 and temperature > 0: |
| | logits = logits / temperature |
| | |
| | # Application du Top-K |
| | if top_k is not None: |
| | # Filtrer les logits pour ne garder que le top_k |
| | values, indices = torch.topk(logits, k=top_k) |
| | # Créer un masque (tensor rempli de -inf) et appliquer les valeurs filtrées |
| | mask = torch.ones_like(logits) * float('-inf') |
| | logits = torch.scatter(mask, dim=0, index=indices, src=values) |
| | |
| | # Convertir en probabilités et échantillonner |
| | probabilities = F.softmax(logits, dim=-1) |
| | # S'assurer que les probabilités somment à 1 après modification (si top_k utilisé) |
| | if top_k is not None and probabilities.sum() > 0: |
| | probabilities = probabilities.div(probabilities.sum()) |
| | |
| | predicted_id = torch.multinomial(probabilities, num_samples=1).item() |
| | |
| | # Mettre à jour la séquence |
| | current_thought_sequence.append(predicted_id) |
| | |
| | if predicted_id == eos_id: |
| | break |
| | |
| | # Extraire les IDs de la pensée |
| | thought_ids = [] |
| | try: |
| | sep_index_in_thought = current_thought_sequence.index(sep_id) |
| | # IDs après le premier SEP et avant le EOS |
| | raw_thought_ids = current_thought_sequence[sep_index_in_thought + 1:] |
| | for id_val in raw_thought_ids: |
| | if id_val == eos_id: |
| | break |
| | thought_ids.append(id_val) |
| | except ValueError: |
| | pass # sep_id not found, thought_ids remains empty |
| | |
| | generated_thought_text = tokenizer.decode(thought_ids).strip() |
| | if not generated_thought_text: |
| | generated_thought_text = "[pensée vide]" |
| | |
| | |
| | # --- 2. Générer la 'réponse' en incluant la pensée --- |
| | # Initial sequence for response: question + sep + thought + sep |
| | current_response_sequence = list(question_ids + [sep_id] + thought_ids + [sep_id]) |
| | |
| | with torch.no_grad(): |
| | for _ in range(max_response_length): |
| | # Préparer l'entrée (padding et troncature) |
| | input_ids_to_pad = current_response_sequence[-max_len_input:] |
| | padding_needed = max_len_input - len(input_ids_to_pad) |
| | input_ids_padded = [tokenizer.special_tokens['<pad>']] * padding_needed + input_ids_to_pad |
| | input_tensor = torch.tensor(input_ids_padded).unsqueeze(0).to(device) # Move to device |
| | |
| | # Obtention des logits |
| | logits = model(input_tensor).squeeze(0) |
| | |
| | # Application de la Temperature |
| | if temperature != 1.0 and temperature > 0: |
| | logits = logits / temperature |
| | |
| | # Application du Top-K |
| | if top_k is not None: |
| | values, indices = torch.topk(logits, k=top_k) |
| | mask = torch.ones_like(logits) * float('-inf') |
| | logits = torch.scatter(mask, dim=0, index=indices, src=values) |
| | |
| | # Convertir en probabilités et échantillonner |
| | probabilities = F.softmax(logits, dim=-1) |
| | if top_k is not None and probabilities.sum() > 0: |
| | probabilities = probabilities.div(probabilities.sum()) |
| | |
| | predicted_id = torch.multinomial(probabilities, num_samples=1).item() |
| | |
| | # Mettre à jour la séquence |
| | current_response_sequence.append(predicted_id) |
| | |
| | if predicted_id == eos_id: |
| | break |
| | |
| | # Extraire les IDs de la réponse |
| | response_ids = [] |
| | # Find the starting point of the actual response part (after question + sep + thought + sep) |
| | response_start_idx = len(question_ids) + 1 + len(thought_ids) + 1 |
| | if response_start_idx < len(current_response_sequence): |
| | raw_response_ids = current_response_sequence[response_start_idx:] |
| | for id_val in raw_response_ids: |
| | if id_val == eos_id: |
| | break |
| | response_ids.append(id_val) |
| | |
| | generated_response_text = tokenizer.decode(response_ids).strip() |
| | if not generated_response_text: |
| | generated_response_text = "[réponse vide]" |
| | |
| | # --- 3. Afficher les résultats dans le format spécifié --- |
| | print(f"l'utilisateur me dit '{question}', alors je pense que '{generated_thought_text}', alors la réponse est '{generated_response_text}'!") |
| | print("-" * 40) |
| | |
| | return generated_response_text |
| | |
| | # --- E. Fonction de Chargement du Modèle Lam-2 (Inchangée) --- |
| | def load_lam2_model(repo_id: str): |
| | # ... (code inchangé) ... |
| | """ |
| | Télécharge et charge le modèle Lam-2 et son tokenizer depuis Hugging Face. |
| | """ |
| | print(f"--- Chargement de Lam-2 depuis {repo_id} ---") |
| | |
| | # 1. Télécharger le tokenizer |
| | tokenizer_path = hf_hub_download(repo_id=repo_id, filename="aricate_tokenizer.txt") |
| | with open(tokenizer_path, 'r', encoding='utf-8') as f: |
| | word_to_id = json.load(f) |
| | tokenizer = WordTokenizer(word_to_id) |
| | print(f"Tokenizer chargé. Taille du vocabulaire: {tokenizer.vocab_size}") |
| | |
| | # 2. Télécharger la configuration |
| | config_path = hf_hub_download(repo_id=repo_id, filename="config.json") |
| | with open(config_path, 'r') as f: |
| | model_config = json.load(f) |
| | print("Configuration du modèle chargée.") |
| | |
| | # 3. Initialiser le modèle |
| | model = AricateModel( |
| | vocab_size=model_config['vocab_size'], |
| | embedding_dim=model_config['embedding_dim'], |
| | hidden_dim=model_config['hidden_dim'], |
| | config=model_config |
| | ) |
| | |
| | # 4. Télécharger et charger les poids Safetensors |
| | weights_path = hf_hub_download(repo_id=repo_id, filename="model.safetensors") |
| | state_dict = load_safetensors_file(weights_path) |
| | |
| | model.load_state_dict(state_dict) |
| | print("Poids du modèle Safetensors chargés avec succès.") |
| | |
| | MAX_LEN_INPUT_DEFAULT = 30 |
| | |
| | print("-" * 40) |
| | return model, tokenizer, MAX_LEN_INPUT_DEFAULT |
| | |
| | # --- F. Bloc principal d'exécution (MISE À JOUR) --- |
| | if __name__ == '__main__': |
| | |
| | LAM2_REPO_ID = "Clemylia/Lam-6-Think" |
| | |
| | # 🚨 NOUVEAUX PARAMÈTRES POUR LE TEST 🚨 |
| | TEST_MAX_THOUGHT_LENGTH = 20 # Longueur max pour la pensée |
| | TEST_MAX_RESPONSE_LENGTH = 40 # Longueur max pour la réponse |
| | TEST_TEMPERATURE = 0.7 # > 1.0 pour plus de créativité/aléatoire |
| | TEST_TOP_K = 10 # Limite le choix aux 10 mots les plus probables |
| | |
| | test_questions = [ |
| | "Qui es-tu ?", |
| | "Quelle est la capitale de la France ?", |
| | "Combien de pattes a une araignée ?" |
| | ] |
| | |
| | try: |
| | # 1. Chargement du modèle |
| | lam2_model, lam2_tokenizer, max_len_input = load_lam2_model(LAM2_REPO_ID) |
| | |
| | print(f"\n>>> TEST D'INFÉRENCE LAM-2 EN MODE CRÉATIF (T={TEST_TEMPERATURE}, K={TEST_TOP_K}, MaxThoughtLen={TEST_MAX_THOUGHT_LENGTH}, MaxResponseLen={TEST_MAX_RESPONSE_LENGTH}) <<<") |
| | |
| | # 2. Infèrence (Appel à la nouvelle fonction) |
| | for question in test_questions: |
| | generate_sequence( |
| | model=lam2_model, |
| | tokenizer=lam2_tokenizer, |
| | question=question, |
| | max_thought_length=TEST_MAX_THOUGHT_LENGTH, |
| | max_response_length=TEST_MAX_RESPONSE_LENGTH, |
| | max_len_input=max_len_input, |
| | temperature=TEST_TEMPERATURE, |
| | top_k=TEST_TOP_K |
| | ) |
| | |
| | except Exception as e: |
| | print(f"\n❌ Une erreur est survenue lors du chargement ou de l'inférence.") |
| | print(f"Détail de l'erreur: {e}") |
| | print("Vérifiez l'installation des dépendances et le REPO_ID.") |
| | ``` |