Houzeric commited on
Commit
5afdfb7
·
verified ·
1 Parent(s): d2971b9

Upload folder using huggingface_hub

Browse files
Files changed (7) hide show
  1. README.md +138 -0
  2. config.json +25 -0
  3. configuration.py +38 -0
  4. model.py +346 -0
  5. modeling_minigpt.py +209 -0
  6. modeling_minigpt_core.py +176 -0
  7. pytorch_model.bin +3 -0
README.md ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language:
3
+ - fr
4
+ license: apache-2.0
5
+ tags:
6
+ - causal-lm
7
+ - french
8
+ - minigpt
9
+ - text-generation
10
+ - transformers
11
+ - pytorch
12
+ pipeline_tag: text-generation
13
+ ---
14
+
15
+ # MiniGPT-FR
16
+
17
+ MiniGPT-FR est un modèle de langage causal entraîné pour la génération de texte en français.
18
+ Il s’agit d’un modèle de type decoder-only Transformer, conçu pour apprendre la structure de la langue française et générer des textes cohérents à partir d’un prompt.
19
+
20
+ Le modèle a été entraîné progressivement sur des corpus textuels français, avec une montée en taille du dataset afin de stabiliser l’apprentissage linguistique.
21
+
22
+ ---
23
+
24
+ ## Architecture
25
+
26
+ Type : Causal Language Model (decoder-only)
27
+ Architecture : Transformer
28
+ Position encoding : RoPE
29
+ Activation FFN : SwiGLU
30
+ Weight sharing : FFN sharing
31
+ Nombre de paramètres : ~60M
32
+ Contexte maximal : 256 tokens
33
+
34
+ Configuration principale :
35
+ - Layers : 20
36
+ - Hidden size : 640
37
+ - Attention heads : 10
38
+ - FFN hidden size : 2560
39
+ - Dropout : 0.15
40
+
41
+ ---
42
+
43
+ ## Entraînement
44
+
45
+ - Entraînement en next-token prediction
46
+ - Curriculum learning avec augmentation progressive du dataset
47
+ - Dataset final : 200k entrées
48
+ - Langue : français
49
+ - Optimiseur : AdamW
50
+ - Scheduler : Cosine decay avec warmup
51
+ - Validation suivie via la cross-entropy loss
52
+
53
+ Ce modèle n’est pas instruction-tuned.
54
+ Il est optimisé pour la complétion de texte et la génération libre.
55
+
56
+ ---
57
+
58
+ ## Capacités
59
+
60
+ - Génération de texte en français
61
+ - Complétion de phrases
62
+ - Reformulation simple
63
+ - Génération de paragraphes descriptifs
64
+ - Style encyclopédique et informatif dominant
65
+
66
+ Limitations connues :
67
+ - Pas d’alignement instructionnel
68
+ - Peut halluciner des faits
69
+ - Pas optimisé pour le raisonnement complexe
70
+ - Contexte limité à 256 tokens
71
+
72
+ ---
73
+
74
+ ## Utilisation avec Transformers
75
+
76
+ Exemple minimal en PyTorch :
77
+
78
+ from transformers import AutoTokenizer, AutoModelForCausalLM
79
+
80
+ model_name = "Houzeric/MiniGPT-FR"
81
+
82
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
83
+ model = AutoModelForCausalLM.from_pretrained(
84
+ model_name,
85
+ trust_remote_code=True
86
+ )
87
+
88
+ prompt = "Il est principalement connu pour"
89
+ inputs = tokenizer(prompt, return_tensors="pt")
90
+
91
+ outputs = model.generate(
92
+ **inputs,
93
+ max_new_tokens=100,
94
+ temperature=0.8,
95
+ top_p=0.95,
96
+ do_sample=True
97
+ )
98
+
99
+ print(tokenizer.decode(outputs[0], skip_special_tokens=True))
100
+
101
+ ---
102
+
103
+ ## Tokenizer
104
+
105
+ Tokenizer utilisé : camembert-base
106
+ Vocabulaire partagé
107
+ Padding aligné sur le token EOS
108
+
109
+ ---
110
+
111
+ ## Fichiers du dépôt
112
+
113
+ - pytorch_model.bin : poids du modèle
114
+ - config.json : configuration du modèle
115
+ - tokenizer.json
116
+ - tokenizer_config.json
117
+ - special_tokens_map.json
118
+ - fichiers modeling et configuration chargés via trust_remote_code
119
+
120
+ ---
121
+
122
+ ## Licence
123
+
124
+ Ce modèle est distribué sous licence Apache 2.0.
125
+
126
+ ---
127
+
128
+ ## Avertissement
129
+
130
+ Ce modèle est fourni à des fins de recherche et d’expérimentation.
131
+ Les textes générés peuvent être inexacts, incomplets ou incohérents.
132
+ Aucune garantie n’est fournie quant à l’exactitude des informations produites.
133
+
134
+ ---
135
+
136
+ ## Crédits
137
+
138
+ Modèle développé et entraîné indépendamment dans un cadre expérimental, avec un focus sur l’apprentissage progressif du français et l’optimisation de modèles de taille intermédiaire.
config.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "minigpt",
3
+ "architectures": [
4
+ "MiniGPTForCausalLM"
5
+ ],
6
+ "auto_map": {
7
+ "AutoConfig": "configuration.MiniGPTConfig",
8
+ "AutoModelForCausalLM": "modeling_minigpt.MiniGPTForCausalLM"
9
+ },
10
+ "tokenizer_class": "CamembertTokenizer",
11
+ "tokenizer_name": "camembert-base",
12
+ "vocab_size": 32005,
13
+ "pad_token_id": 1,
14
+ "bos_token_id": 0,
15
+ "eos_token_id": 2,
16
+ "embed_dim": 640,
17
+ "depth": 20,
18
+ "heads": 10,
19
+ "hidden_dim": 2560,
20
+ "block_size": 256,
21
+ "dropout": 0.1,
22
+ "use_rope": true,
23
+ "weight_sharing": "ffn",
24
+ "tie_word_embeddings": false
25
+ }
configuration.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import PretrainedConfig
2
+
3
+
4
+ class MiniGPTConfig(PretrainedConfig):
5
+ """
6
+ Configuration pour le modèle MiniGPT.
7
+
8
+ Cette classe hérite de PretrainedConfig pour être compatible avec
9
+ l'écosystème Hugging Face.
10
+ """
11
+ model_type = "minigpt"
12
+
13
+ def __init__(
14
+ self,
15
+ vocab_size=32000,
16
+ block_size=256,
17
+ embed_dim=256,
18
+ depth=8,
19
+ heads=8,
20
+ dropout=0.1,
21
+ hidden_dim=512,
22
+ weight_sharing="none",
23
+ use_rope=True,
24
+ use_gradient_checkpointing=False,
25
+ **kwargs
26
+ ):
27
+ super().__init__(**kwargs)
28
+ self.vocab_size = vocab_size
29
+ self.block_size = block_size
30
+ self.embed_dim = embed_dim
31
+ self.depth = depth
32
+ self.heads = heads
33
+ self.dropout = dropout
34
+ self.hidden_dim = hidden_dim
35
+ self.weight_sharing = weight_sharing
36
+ self.use_rope = use_rope
37
+ self.use_gradient_checkpointing = use_gradient_checkpointing
38
+
model.py ADDED
@@ -0,0 +1,346 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ from torch.utils.checkpoint import checkpoint
5
+ from transformers import PreTrainedModel
6
+ from .configuration import MiniGPTConfig
7
+
8
+
9
+ class RoPEEmbedding(nn.Module):
10
+ """Rotary Position Embedding (RoPE) comme utilisé dans LLaMA et autres LLMs modernes.
11
+
12
+ RoPE encode les positions directement dans les queries et keys via des rotations,
13
+ sans nécessiter de paramètres apprenables.
14
+
15
+ Args:
16
+ dim: Dimension de chaque tête d'attention (embed_dim // num_heads)
17
+ max_seq_len: Longueur de séquence maximale
18
+ base: Base pour le calcul des fréquences (10000 par défaut)
19
+ """
20
+ def __init__(self, dim, max_seq_len=2048, base=10000):
21
+ super().__init__()
22
+ self.dim = dim
23
+ self.max_seq_len = max_seq_len
24
+ self.base = base
25
+
26
+ # Précalculer les fréquences
27
+ inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim))
28
+ self.register_buffer("inv_freq", inv_freq)
29
+
30
+ # Précalculer cos et sin pour toutes les positions
31
+ t = torch.arange(max_seq_len).type_as(self.inv_freq)
32
+ freqs = torch.einsum("i,j->ij", t, self.inv_freq)
33
+ emb = torch.cat((freqs, freqs), dim=-1)
34
+ self.register_buffer("cos_cached", emb.cos()[None, None, :, :])
35
+ self.register_buffer("sin_cached", emb.sin()[None, None, :, :])
36
+
37
+ def rotate_half(self, x):
38
+ """Rotation de moitié des dimensions."""
39
+ x1, x2 = x[..., :x.shape[-1]//2], x[..., x.shape[-1]//2:]
40
+ return torch.cat((-x2, x1), dim=-1)
41
+
42
+ def forward(self, q, k):
43
+ """Applique RoPE aux queries et keys.
44
+
45
+ Args:
46
+ q: queries [batch, heads, seq_len, head_dim]
47
+ k: keys [batch, heads, seq_len, head_dim]
48
+
49
+ Returns:
50
+ q_rot, k_rot: queries et keys avec positions encodées
51
+ """
52
+ seq_len = q.shape[2]
53
+
54
+ # Tronquer les embeddings si la séquence est plus courte
55
+ cos = self.cos_cached[:, :, :seq_len, :]
56
+ sin = self.sin_cached[:, :, :seq_len, :]
57
+
58
+ # Appliquer la rotation
59
+ q_rot = (q * cos) + (self.rotate_half(q) * sin)
60
+ k_rot = (k * cos) + (self.rotate_half(k) * sin)
61
+
62
+ return q_rot, k_rot
63
+
64
+
65
+ class SwiGLU(nn.Module):
66
+ """SwiGLU activation function as described in the Super Tiny LM paper.
67
+ SwiGLU(x) = (Swish(xW) ⊗ xV)W2
68
+ where Swish(x) = SiLU(x) in PyTorch
69
+ """
70
+ def __init__(self, embed_dim, hidden_dim):
71
+ super().__init__()
72
+ self.w = nn.Linear(embed_dim, hidden_dim, bias=False)
73
+ self.v = nn.Linear(embed_dim, hidden_dim, bias=False)
74
+ self.w2 = nn.Linear(hidden_dim, embed_dim, bias=False)
75
+
76
+ def forward(self, x):
77
+ return self.w2(F.silu(self.w(x)) * self.v(x))
78
+
79
+
80
+ class SelfAttention(nn.Module):
81
+ def __init__(self, embed_dim, heads, dropout, max_seq_len=2048, use_rope=True):
82
+ super().__init__()
83
+ self.embed_dim = embed_dim
84
+ self.heads = heads
85
+ self.head_dim = embed_dim // heads
86
+ self.use_rope = use_rope
87
+
88
+ self.q_proj = nn.Linear(embed_dim, embed_dim)
89
+ self.k_proj = nn.Linear(embed_dim, embed_dim)
90
+ self.v_proj = nn.Linear(embed_dim, embed_dim)
91
+ self.out = nn.Linear(embed_dim, embed_dim)
92
+ self.attn_dropout = dropout
93
+ self.resid_dropout = nn.Dropout(dropout)
94
+
95
+ # RoPE embeddings (pas de paramètres apprenables)
96
+ if use_rope:
97
+ self.rope = RoPEEmbedding(self.head_dim, max_seq_len=max_seq_len)
98
+
99
+ def forward(self, x, mask=None):
100
+ B, T, C = x.size()
101
+ q = self.q_proj(x).reshape(B, T, self.heads, self.head_dim).transpose(1, 2)
102
+ k = self.k_proj(x).reshape(B, T, self.heads, self.head_dim).transpose(1, 2)
103
+ v = self.v_proj(x).reshape(B, T, self.heads, self.head_dim).transpose(1, 2)
104
+
105
+ # Appliquer RoPE aux queries et keys si activé
106
+ if self.use_rope:
107
+ q, k = self.rope(q, k)
108
+
109
+ attn = F.scaled_dot_product_attention(
110
+ q, k, v,
111
+ attn_mask=None,
112
+ is_causal=True,
113
+ dropout_p=self.attn_dropout if self.training else 0.0,
114
+ )
115
+ attn = attn.transpose(1, 2).contiguous().view(B, T, C)
116
+ return self.resid_dropout(self.out(attn))
117
+
118
+ class TransformerBlock(nn.Module):
119
+ def __init__(self, embed_dim, heads, dropout=0.1, hidden_dim = 512, layerdrop=0.1, shared_ff=None, max_seq_len=2048, use_rope=True):
120
+ super().__init__()
121
+ self.attn = SelfAttention(embed_dim, heads, dropout, max_seq_len=max_seq_len, use_rope=use_rope)
122
+ self.ln1 = nn.LayerNorm(embed_dim)
123
+ # Utiliser un FFN partagé si fourni, sinon créer un nouveau
124
+ self.ff = shared_ff if shared_ff is not None else SwiGLU(embed_dim, hidden_dim)
125
+ self.ln2 = nn.LayerNorm(embed_dim)
126
+ self.dropout = nn.Dropout(dropout)
127
+ self.layerdrop = layerdrop
128
+
129
+ def forward(self, x, mask=None):
130
+ if self.training and torch.rand(1).item() < self.layerdrop:
131
+ return x
132
+ x = x + self.dropout(self.attn(self.ln1(x), mask))
133
+ x = x + self.dropout(self.ff(self.ln2(x)))
134
+ return x
135
+
136
+ def forward_checkpointed(self, x, mask=None):
137
+ """Version avec gradient checkpointing pour économiser VRAM."""
138
+ return self.forward(x, mask)
139
+
140
+ class MiniGPT(PreTrainedModel):
141
+ config_class = MiniGPTConfig
142
+
143
+ def __init__(self, config=None, **kwargs):
144
+ """
145
+ Initialise le modèle MiniGPT.
146
+
147
+ Args:
148
+ config: Instance de MiniGPTConfig ou None. Si None, les paramètres
149
+ doivent être fournis via kwargs.
150
+ **kwargs: Paramètres du modèle si config n'est pas fourni.
151
+ """
152
+ # Si config n'est pas fourni, créer une config à partir des kwargs
153
+ if config is None:
154
+ config = MiniGPTConfig(**kwargs)
155
+
156
+ super().__init__(config)
157
+
158
+ # Extraire les paramètres de la config
159
+ vocab_size = config.vocab_size
160
+ block_size = config.block_size
161
+ embed_dim = config.embed_dim
162
+ depth = config.depth
163
+ heads = config.heads
164
+ dropout = config.dropout
165
+ hidden_dim = config.hidden_dim
166
+ weight_sharing = config.weight_sharing
167
+ use_rope = config.use_rope
168
+ use_gradient_checkpointing = config.use_gradient_checkpointing
169
+ self.token_emb = nn.Embedding(vocab_size, embed_dim)
170
+ self.use_rope = use_rope
171
+ self.use_gradient_checkpointing = use_gradient_checkpointing
172
+
173
+ # Positional embeddings uniquement si on n'utilise pas RoPE
174
+ if not use_rope:
175
+ self.pos_emb = nn.Embedding(block_size, embed_dim)
176
+ else:
177
+ self.pos_emb = None
178
+
179
+ self.depth = depth
180
+ self.weight_sharing = weight_sharing
181
+ self.vocab_size = vocab_size
182
+ self.block_size = block_size
183
+ self.embed_dim = embed_dim
184
+ self.heads = heads
185
+ self.hidden_dim = hidden_dim
186
+
187
+ # Créer les blocs selon le type de weight sharing
188
+ if weight_sharing == "none":
189
+ # Comportement original : chaque bloc a ses propres poids
190
+ self.blocks = nn.ModuleList([
191
+ TransformerBlock(embed_dim, heads, dropout, hidden_dim, layerdrop=0.1,
192
+ max_seq_len=block_size, use_rope=use_rope)
193
+ for _ in range(depth)
194
+ ])
195
+ elif weight_sharing == "ffn":
196
+ # Partage uniquement les FFN, attention séparée
197
+ shared_ff = SwiGLU(embed_dim, hidden_dim)
198
+ self.blocks = nn.ModuleList([
199
+ TransformerBlock(embed_dim, heads, dropout, hidden_dim, layerdrop=0.1,
200
+ shared_ff=shared_ff, max_seq_len=block_size, use_rope=use_rope)
201
+ for _ in range(depth)
202
+ ])
203
+ elif weight_sharing == "full":
204
+ # ALBERT-style : un seul bloc réutilisé depth fois
205
+ self.shared_block = TransformerBlock(embed_dim, heads, dropout, hidden_dim, layerdrop=0.1,
206
+ max_seq_len=block_size, use_rope=use_rope)
207
+ self.blocks = None # On n'utilise pas de ModuleList dans ce cas
208
+ else:
209
+ raise ValueError(f"weight_sharing doit être 'none', 'ffn' ou 'full', pas '{weight_sharing}'")
210
+
211
+ self.ln_f = nn.LayerNorm(embed_dim)
212
+ self.head = nn.Linear(embed_dim, vocab_size, bias=False) # on enleve bias pour que head et token_emb est la meme taille
213
+ self.head.weight = self.token_emb.weight #On réutilise les poids de la matrice token_emb pour les tetes
214
+ self.block_size = block_size
215
+ self.apply(self._init_weights)
216
+
217
+ def forward(self, idx):
218
+ B, T = idx.shape
219
+
220
+ # Token embeddings
221
+ x = self.token_emb(idx)
222
+
223
+ # Ajouter positional embeddings uniquement si on n'utilise pas RoPE
224
+ if not self.use_rope:
225
+ pos = torch.arange(0, T, device=idx.device).unsqueeze(0)
226
+ x = x + self.pos_emb(pos)
227
+
228
+ mask = torch.tril(torch.ones(T, T, device=idx.device)).unsqueeze(0).unsqueeze(0)
229
+
230
+ # Gradient checkpointing : économise VRAM en recalculant les activations
231
+ if self.use_gradient_checkpointing and self.training:
232
+ if self.weight_sharing == "full":
233
+ for _ in range(self.depth):
234
+ x = checkpoint(self.shared_block.forward_checkpointed, x, mask, use_reentrant=False)
235
+ else:
236
+ for block in self.blocks:
237
+ x = checkpoint(block.forward_checkpointed, x, mask, use_reentrant=False)
238
+ else:
239
+ # Mode normal (pas de checkpointing)
240
+ if self.weight_sharing == "full":
241
+ for _ in range(self.depth):
242
+ x = self.shared_block(x, mask)
243
+ else:
244
+ for block in self.blocks:
245
+ x = block(x, mask)
246
+
247
+ x = self.ln_f(x)
248
+ return self.head(x)
249
+
250
+ def _init_weights(self, module):
251
+ if isinstance(module, nn.Linear):
252
+ torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
253
+ if module.bias is not None:
254
+ torch.nn.init.zeros_(module.bias)
255
+ elif isinstance(module, nn.Embedding):
256
+ torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
257
+ elif isinstance(module, nn.LayerNorm):
258
+ torch.nn.init.ones_(module.weight)
259
+ torch.nn.init.zeros_(module.bias)
260
+
261
+
262
+ def count_parameters(self):
263
+ """Compte le nombre de paramètres selon le type de weight sharing et use_rope."""
264
+ total = sum(p.numel() for p in self.parameters())
265
+ trainable = sum(p.numel() for p in self.parameters() if p.requires_grad)
266
+
267
+ # Détails par composant
268
+ token_emb_params = self.token_emb.weight.numel()
269
+ pos_emb_params = self.pos_emb.weight.numel() if self.pos_emb is not None else 0
270
+ embedding_params = token_emb_params + pos_emb_params
271
+
272
+ if self.weight_sharing == "full":
273
+ block_params = sum(p.numel() for p in self.shared_block.parameters())
274
+ else:
275
+ block_params = sum(p.numel() for p in self.blocks.parameters())
276
+
277
+ return {
278
+ "total": total,
279
+ "trainable": trainable,
280
+ "embedding": embedding_params,
281
+ "token_emb": token_emb_params,
282
+ "pos_emb": pos_emb_params,
283
+ "blocks": block_params,
284
+ "head": 0, # Head partage les poids avec embedding
285
+ "weight_sharing": self.weight_sharing,
286
+ "use_rope": self.use_rope
287
+ }
288
+
289
+ @torch.no_grad()
290
+ def generate(self, idx, max_new_tokens, temperature=1.0, top_k=None, top_p=None, min_new_tokens=0, eos_token_id=None):
291
+ """
292
+ Génération de texte avec contrôle de la diversité.
293
+
294
+ Args:
295
+ idx: Context initial [batch, seq_len]
296
+ max_new_tokens: Nombre de tokens à générer
297
+ temperature: Contrôle la diversité (0.1=conservateur, 1.0=normal, 2.0=créatif)
298
+ top_k: Garde seulement les k tokens les plus probables
299
+ top_p: Nucleus sampling, garde les tokens dont la somme des probas = p
300
+ min_new_tokens: Génère au moins ce nombre de tokens avant d'autoriser l'arrêt sur eos_token_id
301
+ eos_token_id: Id du token EOS pour stopper la génération (optionnel)
302
+ """
303
+ for step in range(max_new_tokens):
304
+ idx_cond = idx[:, -self.block_size:]
305
+ logits = self(idx_cond)
306
+ logits = logits[:, -1, :]
307
+
308
+ # Appliquer la température
309
+ if temperature != 1.0:
310
+ logits = logits / temperature
311
+
312
+ # Top-k filtering
313
+ if top_k is not None:
314
+ v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
315
+ logits[logits < v[:, [-1]]] = -float('Inf')
316
+
317
+ # Top-p (nucleus) filtering
318
+ if top_p is not None:
319
+ sorted_logits, sorted_indices = torch.sort(logits, descending=True)
320
+ cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)
321
+
322
+ # Retirer les tokens au-delà du seuil top_p
323
+ sorted_indices_to_remove = cumulative_probs > top_p
324
+ # Garder au moins le premier token
325
+ sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
326
+ sorted_indices_to_remove[..., 0] = 0
327
+
328
+ # Scatter les valeurs -inf
329
+ indices_to_remove = sorted_indices_to_remove.scatter(1, sorted_indices, sorted_indices_to_remove)
330
+ logits[indices_to_remove] = -float('Inf')
331
+
332
+ # Échantillonner
333
+ probs = F.softmax(logits, dim=-1)
334
+ next_token = torch.multinomial(probs, num_samples=1)
335
+
336
+ # Éviter un EOS trop tôt
337
+ if eos_token_id is not None and step < min_new_tokens:
338
+ while next_token.item() == eos_token_id:
339
+ next_token = torch.multinomial(probs, num_samples=1)
340
+
341
+ idx = torch.cat((idx, next_token), dim=1)
342
+
343
+ # Arrêt précoce si EOS après le minimum requis
344
+ if eos_token_id is not None and step >= min_new_tokens and next_token.item() == eos_token_id:
345
+ break
346
+ return idx
modeling_minigpt.py ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Modèle MiniGPT pour Hugging Face Transformers.
3
+
4
+ Ce fichier contient MiniGPTForCausalLM qui est la classe standard
5
+ attendue par Hugging Face pour les modèles de génération de texte.
6
+
7
+ MiniGPTForCausalLM hérite de MiniGPTModel et ajoute uniquement la tête de langage.
8
+ """
9
+ import torch
10
+ import torch.nn as nn
11
+ import torch.nn.functional as F
12
+ from transformers import PreTrainedModel
13
+ from transformers.modeling_outputs import CausalLMOutputWithPast
14
+ from .configuration import MiniGPTConfig
15
+ from .modeling_minigpt_core import MiniGPTModel
16
+
17
+
18
+
19
+ class MiniGPTForCausalLM(PreTrainedModel):
20
+ """
21
+ MiniGPT model avec une tête de langage pour la génération de texte.
22
+
23
+ Cette classe est compatible avec l'écosystème Hugging Face et peut être
24
+ utilisée avec AutoModelForCausalLM une fois enregistrée.
25
+
26
+ Elle contient :
27
+ - L'enrobage Hugging Face (méthodes standard)
28
+ - La logique LM (tête de prédiction)
29
+ - L'appel au modèle interne (MiniGPTModel)
30
+ """
31
+ config_class = MiniGPTConfig
32
+ base_model_prefix = "model"
33
+
34
+ def __init__(self, config):
35
+ super().__init__(config)
36
+
37
+ # Modèle core (architecture sans la tête)
38
+ self.model = MiniGPTModel(config)
39
+
40
+ # Tête de langage (prédiction de tokens)
41
+ self.lm_head = nn.Linear(config.embed_dim, config.vocab_size, bias=False)
42
+
43
+ # Weight tying : partager les poids entre token_emb et lm_head
44
+ self.lm_head.weight = self.model.token_emb.weight
45
+
46
+ # Post-initialisation
47
+ self.post_init()
48
+
49
+ def get_input_embeddings(self):
50
+ """Retourne les embeddings d'entrée."""
51
+ return self.model.get_input_embeddings()
52
+
53
+ def set_input_embeddings(self, value):
54
+ """Définit les embeddings d'entrée."""
55
+ self.model.set_input_embeddings(value)
56
+ # Mettre à jour le weight tying
57
+ self.lm_head.weight = self.model.token_emb.weight
58
+
59
+ def get_output_embeddings(self):
60
+ """Retourne la tête de sortie."""
61
+ return self.lm_head
62
+
63
+ def set_output_embeddings(self, new_embeddings):
64
+ """Définit la tête de sortie."""
65
+ self.lm_head = new_embeddings
66
+ # Mettre à jour le weight tying
67
+ self.lm_head.weight = self.model.token_emb.weight
68
+
69
+ def forward(
70
+ self,
71
+ input_ids=None,
72
+ attention_mask=None,
73
+ labels=None,
74
+ past_key_values=None,
75
+ use_cache=None,
76
+ output_attentions=None,
77
+ output_hidden_states=None,
78
+ return_dict=None,
79
+ **kwargs
80
+ ):
81
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
82
+
83
+ if input_ids is None:
84
+ raise ValueError("input_ids doit être fourni")
85
+
86
+ # Appel au modèle core
87
+ outputs = self.model(
88
+ input_ids=input_ids,
89
+ attention_mask=attention_mask,
90
+ past_key_values=past_key_values,
91
+ use_cache=use_cache,
92
+ output_attentions=output_attentions,
93
+ output_hidden_states=output_hidden_states,
94
+ return_dict=return_dict,
95
+ )
96
+
97
+ # Extraire les hidden states selon le format de retour
98
+ if return_dict:
99
+ hidden_states = outputs.last_hidden_state
100
+ else:
101
+ hidden_states = outputs[0]
102
+
103
+ # Appliquer la tête de langage
104
+ logits = self.lm_head(hidden_states)
105
+
106
+ # Calculer la loss si labels fournis
107
+ loss = None
108
+ if labels is not None:
109
+ # Shift logits et labels pour l'alignement (prédire le token suivant)
110
+ shift_logits = logits[..., :-1, :].contiguous()
111
+ shift_labels = labels[..., 1:].contiguous()
112
+ loss_fct = nn.CrossEntropyLoss()
113
+ loss = loss_fct(
114
+ shift_logits.view(-1, shift_logits.size(-1)),
115
+ shift_labels.view(-1)
116
+ )
117
+
118
+ # Format de sortie selon return_dict
119
+ if not return_dict:
120
+ output = (logits,)
121
+ if loss is not None:
122
+ return (loss,) + output
123
+ return output
124
+
125
+ return CausalLMOutputWithPast(
126
+ loss=loss,
127
+ logits=logits,
128
+ past_key_values=outputs.past_key_values,
129
+ hidden_states=outputs.hidden_states,
130
+ attentions=outputs.attentions,
131
+ )
132
+
133
+
134
+ def prepare_inputs_for_generation(self, input_ids, past_key_values=None, **kwargs):
135
+ """Prépare les inputs pour la génération."""
136
+ # Pour l'instant, on ne supporte pas le past_key_values
137
+ # Mais on garde la structure pour compatibilité future
138
+ return {"input_ids": input_ids}
139
+
140
+ @torch.no_grad()
141
+ def generate(self, input_ids=None, max_new_tokens=100, temperature=1.0, top_k=None, top_p=None,
142
+ min_new_tokens=0, eos_token_id=None, **kwargs):
143
+ """
144
+ Génération de texte avec contrôle de la diversité.
145
+
146
+ Args:
147
+ input_ids: Context initial [batch, seq_len]
148
+ max_new_tokens: Nombre de tokens à générer
149
+ temperature: Contrôle la diversité (0.1=conservateur, 1.0=normal, 2.0=créatif)
150
+ top_k: Garde seulement les k tokens les plus probables
151
+ top_p: Nucleus sampling, garde les tokens dont la somme des probas = p
152
+ min_new_tokens: Génère au moins ce nombre de tokens avant d'autoriser l'arrêt sur eos_token_id
153
+ eos_token_id: Id du token EOS pour stopper la génération (optionnel)
154
+ """
155
+ if input_ids is None:
156
+ raise ValueError("input_ids doit être fourni")
157
+
158
+ idx = input_ids
159
+ block_size = self.config.block_size
160
+
161
+ for step in range(max_new_tokens):
162
+ idx_cond = idx[:, -block_size:]
163
+ logits = self.forward(idx_cond).logits
164
+ logits = logits[:, -1, :]
165
+
166
+ # Appliquer la température
167
+ if temperature != 1.0:
168
+ logits = logits / temperature
169
+
170
+ # Top-k filtering
171
+ if top_k is not None:
172
+ v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
173
+ logits[logits < v[:, [-1]]] = -float('Inf')
174
+
175
+ # Top-p (nucleus) filtering
176
+ if top_p is not None:
177
+ sorted_logits, sorted_indices = torch.sort(logits, descending=True)
178
+ cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)
179
+
180
+ # Retirer les tokens au-delà du seuil top_p
181
+ sorted_indices_to_remove = cumulative_probs > top_p
182
+ # Garder au moins le premier token
183
+ sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
184
+ sorted_indices_to_remove[..., 0] = 0
185
+
186
+ # Scatter les valeurs -inf
187
+ indices_to_remove = sorted_indices_to_remove.scatter(1, sorted_indices, sorted_indices_to_remove)
188
+ logits[indices_to_remove] = -float('Inf')
189
+
190
+ # Échantillonner
191
+ probs = F.softmax(logits, dim=-1)
192
+ next_token = torch.multinomial(probs, num_samples=1)
193
+
194
+ # Éviter un EOS trop tôt
195
+ if eos_token_id is not None and step < min_new_tokens:
196
+ while next_token.item() == eos_token_id:
197
+ next_token = torch.multinomial(probs, num_samples=1)
198
+
199
+ idx = torch.cat((idx, next_token), dim=1)
200
+
201
+ # Arrêt précoce si EOS après le minimum requis
202
+ if eos_token_id is not None and step >= min_new_tokens and next_token.item() == eos_token_id:
203
+ break
204
+
205
+ return idx
206
+
207
+ def count_parameters(self):
208
+ """Délègue au modèle core MiniGPTModel."""
209
+ return self.model.count_parameters()
modeling_minigpt_core.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ from torch.utils.checkpoint import checkpoint
5
+ from transformers.modeling_outputs import BaseModelOutputWithPast
6
+
7
+ from .model import RoPEEmbedding, SwiGLU, SelfAttention, TransformerBlock
8
+
9
+ from .configuration import MiniGPTConfig
10
+
11
+
12
+ class MiniGPTModel(nn.Module):
13
+ """
14
+ Modèle core MiniGPT — sans tête LM, pure architecture Transformer.
15
+ NE DOIT PAS hériter de PreTrainedModel.
16
+ """
17
+
18
+ def __init__(self, config: MiniGPTConfig):
19
+ super().__init__()
20
+
21
+ vocab_size = config.vocab_size
22
+ block_size = config.block_size
23
+ embed_dim = config.embed_dim
24
+ depth = config.depth
25
+ heads = config.heads
26
+ dropout = config.dropout
27
+ hidden_dim = config.hidden_dim
28
+
29
+ self.token_emb = nn.Embedding(vocab_size, embed_dim)
30
+ self.use_rope = config.use_rope
31
+ self.use_gradient_checkpointing = config.use_gradient_checkpointing
32
+ self.block_size = block_size
33
+ self.depth = depth
34
+ self.weight_sharing = config.weight_sharing
35
+
36
+ # Positional embeddings only if not using RoPE
37
+ if not config.use_rope:
38
+ self.pos_emb = nn.Embedding(block_size, embed_dim)
39
+ else:
40
+ self.pos_emb = None
41
+
42
+ # Blocks
43
+ if self.weight_sharing == "none":
44
+ self.blocks = nn.ModuleList([
45
+ TransformerBlock(embed_dim, heads, dropout, hidden_dim,
46
+ max_seq_len=block_size, use_rope=config.use_rope)
47
+ for _ in range(depth)
48
+ ])
49
+
50
+ elif self.weight_sharing == "ffn":
51
+ shared_ff = SwiGLU(embed_dim, hidden_dim)
52
+ self.blocks = nn.ModuleList([
53
+ TransformerBlock(embed_dim, heads, dropout, hidden_dim,
54
+ shared_ff=shared_ff, max_seq_len=block_size,
55
+ use_rope=config.use_rope)
56
+ for _ in range(depth)
57
+ ])
58
+
59
+ elif self.weight_sharing == "full":
60
+ self.shared_block = TransformerBlock(embed_dim, heads, dropout, hidden_dim,
61
+ max_seq_len=block_size, use_rope=config.use_rope)
62
+ self.blocks = None
63
+
64
+ self.ln_f = nn.LayerNorm(embed_dim)
65
+
66
+ def get_input_embeddings(self):
67
+ return self.token_emb
68
+
69
+ def set_input_embeddings(self, value):
70
+ self.token_emb = value
71
+
72
+ def get_output_embeddings(self):
73
+ return None
74
+
75
+ def forward(
76
+ self,
77
+ input_ids=None,
78
+ attention_mask=None,
79
+ past_key_values=None,
80
+ use_cache=None,
81
+ output_attentions=None,
82
+ output_hidden_states=None,
83
+ return_dict=None,
84
+ **kwargs
85
+ ):
86
+ """
87
+ Forward pass du modèle core.
88
+
89
+ Args:
90
+ input_ids: Tokens d'entrée [batch_size, seq_len]
91
+ attention_mask: Masque d'attention (non utilisé pour l'instant)
92
+ past_key_values: Cache KV pour génération (non supporté pour l'instant)
93
+ use_cache: Si True, retourne past_key_values (non supporté pour l'instant)
94
+ output_attentions: Si True, retourne les attentions (non supporté pour l'instant)
95
+ output_hidden_states: Si True, retourne tous les hidden states (non supporté pour l'instant)
96
+ return_dict: Si True, retourne un BaseModelOutputWithPast, sinon un tuple
97
+
98
+ Returns:
99
+ BaseModelOutputWithPast si return_dict=True, sinon tuple (hidden_states,)
100
+ """
101
+ return_dict = return_dict if return_dict is not None else True
102
+
103
+ # Pour l'instant, on ignore ces paramètres (non supportés pour l'instant)
104
+ # On les ignore silencieusement pour la compatibilité avec l'écosystème Hugging Face
105
+ if past_key_values is not None:
106
+ # TODO: Implémenter le support de past_key_values pour la génération efficace
107
+ pass
108
+ if output_attentions:
109
+ # TODO: Implémenter le retour des attentions
110
+ pass
111
+ if output_hidden_states:
112
+ # TODO: Implémenter le retour de tous les hidden states
113
+ pass
114
+
115
+ B, T = input_ids.shape
116
+ x = self.token_emb(input_ids)
117
+
118
+ if self.pos_emb is not None: # not using RoPE
119
+ pos = torch.arange(T, device=input_ids.device).unsqueeze(0)
120
+ x = x + self.pos_emb(pos)
121
+
122
+ mask = torch.tril(torch.ones(T, T, device=input_ids.device)).unsqueeze(0).unsqueeze(0)
123
+
124
+ if self.use_gradient_checkpointing and self.training:
125
+ if self.weight_sharing == "full":
126
+ for _ in range(self.depth):
127
+ x = checkpoint(self.shared_block.forward_checkpointed, x, mask, use_reentrant=False)
128
+ else:
129
+ for block in self.blocks:
130
+ x = checkpoint(block.forward_checkpointed, x, mask, use_reentrant=False)
131
+ else:
132
+ if self.weight_sharing == "full":
133
+ for _ in range(self.depth):
134
+ x = self.shared_block(x, mask)
135
+ else:
136
+ for block in self.blocks:
137
+ x = block(x, mask)
138
+
139
+ hidden_states = self.ln_f(x)
140
+
141
+ if not return_dict:
142
+ return (hidden_states,)
143
+
144
+ return BaseModelOutputWithPast(
145
+ last_hidden_state=hidden_states,
146
+ past_key_values=None,
147
+ hidden_states=None,
148
+ attentions=None,
149
+ )
150
+
151
+ def count_parameters(self):
152
+ """Compte le nombre de paramètres selon le type de weight sharing et l’utilisation de RoPE."""
153
+ total = sum(p.numel() for p in self.parameters())
154
+ trainable = sum(p.numel() for p in self.parameters() if p.requires_grad)
155
+
156
+ token_emb_params = self.token_emb.weight.numel()
157
+ pos_emb_params = self.pos_emb.weight.numel() if self.pos_emb is not None else 0
158
+ embedding_params = token_emb_params + pos_emb_params
159
+
160
+ if self.weight_sharing == "full":
161
+ block_params = sum(p.numel() for p in self.shared_block.parameters())
162
+ else:
163
+ block_params = sum(p.numel() for p in self.blocks.parameters())
164
+
165
+ return {
166
+ "total": total,
167
+ "trainable": trainable,
168
+ "embedding": embedding_params,
169
+ "token_emb": token_emb_params,
170
+ "pos_emb": pos_emb_params,
171
+ "blocks": block_params,
172
+ "head": 0,
173
+ "weight_sharing": self.weight_sharing,
174
+ "use_rope": self.use_rope
175
+ }
176
+
pytorch_model.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:511713ff23bba51a7cf5dfff7e05724c0b893852406b49281a1ae981cc173283
3
+ size 235821291