File size: 14,120 Bytes
dd71abd
 
 
 
 
 
 
 
 
 
 
 
792e430
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76166fd
792e430
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
---
license: mit
language:
- fr
pipeline_tag: text-generation
tags:
- MICRO
- TEST
- slm
- 6 phrases train
- minimaliste
- Aricate
---

## 📄 Documentation du Modèle : Mini-Groutouille 🥖

| Détail | Valeur |
| :--- | :--- |
| 🧑‍💻 **Auteur** | Clemylia |
| 🏗️ **Architecture** | Aricate V4 (GRU + Attention Additive) |
| 🎯 **Type** | Micro-Modèle de Langage (SLM) |
| 🗣️ **Langue** | Français |
| 🗂️ **Tâche** | Modélisation Causal (Continuation de phrase) / Expérimentation Q/R |

-----

## 🌟 Présentation

**Mini-Groutouille** est un **Micro-Modèle de Langage (SLM)** développé par Clemylia dans le cadre d'une expérimentation sur des architectures légères. Il est basé sur l'architecture propriétaire **Aricate V4**, conçue spécifiquement pour être entraînée et utilisée efficacement sur **CPU**.

Contrairement aux modèles basés sur l'architecture Transformer (comme GPT), Mini-Groutouille utilise une combinaison de **Réseaux Neuronaux Récurrents (GRU)** et d'un mécanisme d'**Attention Additive (Bahdanau)** pour la prédiction du mot suivant.

### 🥐 Corpus d'Entraînement

Le modèle a été entraîné sur un **corpus extrêmement réduit** (environ 6 phrases) centré sur le thème d'un boulanger et de ses villageois, d'où son nom amusant \!

L'objectif de cet entraînement était de **valider le pipeline** d'entraînement et de publication de l'architecture Aricate V4.

-----

## 🛠️ Détails Techniques de l'Architecture Aricate V4

L'architecture Aricate V4 se distingue par son approche **Séquence-à-Séquence (Seq2Seq)** simplifiée, qui lui confère une performance surprenante sur les tâches ciblées comme les Questions/Réponses.

  * **1. Couche Récurrente (GRU) :** Un **GRU** (Gated Recurrent Unit) est utilisé pour lire la séquence d'entrée (l'historique de la phrase) et en conserver une mémoire (`hn`). Cela permet au modèle de bien suivre l'ordre des mots.
  * **2. Couche d'Attention Additive :** Une couche d'attention de type **Bahdanau** est appliquée sur les sorties du GRU. Cette couche permet au modèle de **peser l'importance** de chaque mot de l'historique pour la prédiction du mot suivant, produisant un **vecteur de contexte** ciblé.
  * **3. Prédiction :** La prédiction finale est effectuée en combinant le **Vecteur de Contexte** et l'**État Caché Final** du GRU avant de projeter sur le vocabulaire.

| Paramètre | Valeur (Approximative) | Rôle dans Aricate V4 |
| :--- | :--- | :--- |
| **`Embedding Dim`** | 32 | Taille du vecteur qui représente chaque mot. |
| **`Hidden Dim`** | 64 | Taille de la mémoire de l'unité GRU. |
| **`Num Layers`** | 1 | Nombre de couches récurrentes (Minimal pour le micro-modèle). |

-----

## ⚙️ Utilisation (Inférence)

Puisque **Mini-Groutouille** utilise une architecture customisée, vous ne pouvez pas utiliser directement le `pipeline` de `transformers`.

Vous devez importer et utiliser les classes définies (`AricateModel` et `WordTokenizer`) ainsi que la fonction d'inférence (comme `generate_sequence_greedy`) pour charger et tester le modèle.

### 📥 Fichiers à Charger

| Fichier | Rôle |
| :--- | :--- |
| model.safetensors | Les poids entraînés du modèle Aricate V4. |
| `config.json` | La configuration du modèle (dimensions, couches, etc.). |
| `aricate_tokenizer.json` | Le **vocabulaire customisé** basé sur les mots, essentiel pour l'encodage et le décodage. |

### 💻 Exemple de Chargement (Inférence CPU)

Vous devez utiliser `huggingface_hub.from_pretrained_fast` pour charger les poids dans la classe `AricateModel`.

```python
# Exemple de chargement (nécessite la définition des classes Aricate)
from huggingface_hub import from_pretrained_fast
# ... (définition des classes AricateModel, WordTokenizer et fonction de génération)

REPO_ID = "Clemylia/Mini-Groutouille"
model = from_pretrained_fast(REPO_ID, filename="pytorch_model.bin", custom_model_class=AricateModel)

# Ensuite, chargez et initialisez le WordTokenizer avec aricate_tokenizer.json
# ...
```

-----

## 🚀 Performances Notables

  * **Efficacité CPU :** Conçu pour être entraîné et s'exécuter rapidement sur des ressources CPU limitées.
  * **Génération Ciblée :** L'architecture Seq2Seq simplifiée permet des **réponses ciblées** dans les tâches de Questions/Réponses, montrant moins de tendance à générer du "charabia" ou des phrases non pertinentes que certains modèles Transformer sous-entraînés--

**Exemple dinference** :

```
# ==============================================================================
# 🚀 Inférence Aricate V4 - Chargement depuis Hugging Face
# Ce code charge le modèle et le tokenizer customisés pour la génération de texte.
# ==============================================================================

import torch
import torch.nn as nn
import torch.nn.functional as F
import collections
from huggingface_hub import hf_hub_download, PyTorchModelHubMixin
import os
import json

# --- 1. Définitions des Classes (Réplique de l'architecture d'entraînement) ---

# --- A. WordTokenizer (Customisé pour le chargement) ---
class WordTokenizer:
    """Tokenizer simple pour l'architecture Aricate, chargé depuis le vocabulaire publié."""
    def __init__(self, vocab_data):
        self.word_to_id = vocab_data["word_to_id"]
        # Récupérer max_len_input pour le padding lors de l'inférence
        self.max_len_input = vocab_data["max_len_input"]
        self.id_to_word = {id: word for word, id in self.word_to_id.items()}
        self.vocab_size = len(self.word_to_id)

        # Définition des tokens spéciaux
        self.special_tokens = {
            '<pad>': self.word_to_id.get('<pad>', 0),
            '<unk>': self.word_to_id.get('<unk>', 1),
            '<eos>': self.word_to_id.get('<eos>', 2),
        }

        print(f"Tokenizer chargé. Taille du vocabulaire : {self.vocab_size}")
        print(f"Longueur maximale d'entrée pour le padding : {self.max_len_input}")

    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.special_tokens['<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>'])

# --- B. AricateAttentionLayer (Inchangé) ---
class AricateAttentionLayer(nn.Module):
    """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

# --- C. AricateModel V4 (Doit hériter de PyTorchModelHubMixin) ---
class AricateModel(nn.Module, PyTorchModelHubMixin):
    """Architecture Aricate V4 pour Modélisation de Langage."""
    def __init__(self, vocab_size: int, embedding_dim: int, hidden_dim: int, num_layers: int = 1, config: dict = None, **kwargs):
        super(AricateModel, self).__init__()

        # Chargement de la configuration si disponible (utilisé par from_pretrained_fast)
        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

# --- D. Fonction de Génération (Adaptée) ---
def generate_sequence(model, tokenizer, prompt, max_length, temperature=1.0, do_sample=False):
    """Génère la continuation en utilisant le décodage glouton ou l'échantillonnage avec température."""
    model.eval()

    # Récupérer la longueur maximale d'entrée pour le padding
    max_len_input = tokenizer.max_len_input
    eos_id = tokenizer.special_tokens['<eos>']

    current_ids = tokenizer.encode(prompt, add_eos=False)

    print(f"\n--- Génération ({'Échantillonnée' if do_sample else 'Gloutonne'}) ---")
    print(f"Amorce: '{prompt}'")
    if do_sample:
        print(f"Température: {temperature}")

    with torch.no_grad():
        for _ in range(max_length):
            # 1. Préparer l'entrée (Longueur fixe, padding à gauche)
            input_ids_to_pad = current_ids[-max_len_input:] if len(current_ids) > max_len_input else current_ids
            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)

            # Assurez-vous que le modèle est sur le bon appareil (CPU)
            device = next(model.parameters()).device
            input_tensor = input_tensor.to(device)

            # 2. Forward Pass et Prédiction
            logits = model(input_tensor)

            if do_sample:
                # Appliquer la température et échantillonner
                probabilities = F.softmax(logits / temperature, dim=-1)
                predicted_id = torch.multinomial(probabilities, num_samples=1).item()
            else:
                # Décoding glouton (choix du mot le plus probable)
                predicted_id = torch.argmax(logits, dim=-1).item()

            # 3. Arrêt
            if predicted_id == eos_id:
                break

            # 4. Ajouter le nouveau mot
            current_ids.append(predicted_id)

    # Décodage de la partie générée seulement
    prompt_length = len(tokenizer.encode(prompt))
    generated_ids = current_ids[prompt_length:]
    final_response = tokenizer.decode(generated_ids)

    print(f"Continuation générée: '{final_response}'")
    print("-" * 40)

    return final_response


# --- 2. Code de Chargement et de Test ---
def load_and_test_aricate_model(repo_id: str):
    """Charge le modèle Aricate V4 et son tokenizer depuis Hugging Face et lance l'inférence."""

    # Fichiers à télécharger
    MODEL_FILE = "model.safetensors"
    TOKENIZER_FILE = "aricate_tokenizer.json"

    # Force l'utilisation du CPU
    device = torch.device("cpu")
    print(f"Appareil d'inférence sélectionné: {device}")

    try:
        print(f"\n↻ 1. Chargement du modèle {repo_id}...")

        # ⚠️ Utilisez model_file_name pour spécifier le fichier de poids du modèle
        model = AricateModel.from_pretrained(repo_id, model_file_name=MODEL_FILE)
        model.to(device)
        model.eval()

        print("✅ Modèle Aricate V4 chargé avec succès.")

        print(f"\n↻ 2. Chargement du tokenizer custom...")
        # Téléchargement du fichier de vocabulaire
        tokenizer_path = hf_hub_download(repo_id=repo_id, filename=TOKENIZER_FILE)

        # Chargement des données du tokenizer
        with open(tokenizer_path, 'r', encoding='utf-8') as f:
            tokenizer_data = json.load(f)

        tokenizer = WordTokenizer(tokenizer_data)
        print("✅ Tokenizer custom chargé.")


        # 3. Test d'Inférence
        print("\n--- 3. TEST D'INFÉRENCE ---")

        # Longueur maximale de la continuation
        MAX_GENERATION_LENGTH = 10

        # --- Test en mode Glouton (Greedy) ---
        print("\n--- Test en mode Glouton (Greedy) ---")
        prompts_to_test_greedy = [
            "le boulanger donnait",
            "Marie allait chercher du",
            "il aimait cuisiner"
        ]
        for prompt in prompts_to_test_greedy:
            generate_sequence(model, tokenizer, prompt, MAX_GENERATION_LENGTH, do_sample=False)

        # --- Test en mode Échantillonnage (Sampling) ---
        print(f"\n--- Test en mode Échantillonnage (Sampling, Température=0.7) ---")
        prompts_to_test_sampled = [
            "le boulanger cuisinait des gâteaux qui",
            "Marie et sa mère allaient voir",
            "un met extrêmement délicieux pour"
        ]
        TEMPERATURE_VALUE = 0.7 # Une valeur courante pour l'échantillonnage
        for prompt in prompts_to_test_sampled:
            generate_sequence(model, tokenizer, prompt, MAX_GENERATION_LENGTH, temperature=TEMPERATURE_VALUE, do_sample=True)

    except Exception as e:
        print(f"\n❌ ÉCHEC DU CHARGEMENT OU DE L'INFÉRENCE :")
        print("Ceci peut se produire si le modèle custom n'est pas correctement publié ou si les classes ne sont pas définies.")
        print(f"Détail de l'erreur: {e}")


# --- Lancement de la fonction principale ---
if __name__ == '__main__':
    # Le REPO_ID que vous avez fourni
    ARICATE_REPO_ID = "Clemylia/Mini-Groutouille"
    load_and_test_aricate_model(ARICATE_REPO_ID)
```