PowerMachine commited on
Commit
512d009
·
verified ·
1 Parent(s): f19e209

Delete models/nlg.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. models/nlg.py +0 -457
models/nlg.py DELETED
@@ -1,457 +0,0 @@
1
- """nlg.py — Natural Language Generation (NLG) para CNN-BiGRU.
2
-
3
- Wrapper de alto nível para geração de texto que integra:
4
- - TransformerDecoderStack (backbone causal com RoPE)
5
- - MedusaMTP (Multi-Token Prediction)
6
- - Sampling: Temperatura + Top-K + Top-P + Presence/Frequency Penalty
7
- - CyclicReasoning (refinamento iterativo opcional)
8
- - ContextWindowManager (janela de contexto com cache KV)
9
-
10
- ==============================================================================
11
- ANÁLISE MATEMÁTICA E LÓGICA
12
- ==============================================================================
13
-
14
- Dado um prompt P = [p_1, p_2, ..., p_n], geramos tokens y_1, y_2, ...
15
- autoregressivamente. Em cada passo t:
16
-
17
- 1. Forward pass: h_t = Decoder(P || y_{<t}) via cache KV
18
- 2. (Opcional) Refinamento cíclico: h_t = CyclicReasoning(h_t)
19
- 3. Logits: l_t = LM_Head(h_t) [B, V]
20
- 4. Medusa: l_{t+k} = Medusa_Head_k(h_t) para k=1..K
21
- 5. Penalidades: l_t -= presence_penalty * 1[y in history]
22
- + frequency_penalty * count[y in history]
23
- 6. Sampling: y_t ~ softmax(l_t / T) com máscara Top-K e Top-P
24
-
25
- 7. (Opcional) Tree decoding: aceita candidatos Medusa se prob >= threshold
26
-
27
- A loss de treinamento combina:
28
- L_NLG = CE(lm_head(h_t), y_{t+1}) + mu_medusa * L_Medusa + mu_ewc * L_EWC
29
-
30
- ==============================================================================
31
- INTEGRAÇÃO
32
- ==============================================================================
33
-
34
- - TransformerDecoderStack (cnn_bigru.models.transformer_block): backbone causal
35
- - MedusaMTP (cnn_bigru.models.medusa_heads): MTP
36
- - CyclicReasoning (cnn_bigru.models.cyclic_reasoning): refinamento iterativo
37
- - ContextWindowManager (cnn_bigru.models.context_window): janela deslizante
38
- - BBPETokenizer (cnn_bigru.tokenizer): tokenização byte-BPE
39
-
40
- Autor: CNN-BiGRU Project
41
- """
42
- from __future__ import annotations
43
-
44
- import logging
45
- import math
46
- from dataclasses import dataclass, field
47
- from typing import Dict, List, Optional, Tuple, Union
48
-
49
- import torch
50
- import torch.nn as nn
51
- import torch.nn.functional as F
52
-
53
- logger = logging.getLogger(__name__)
54
-
55
-
56
- # ============================================================================
57
- # Configuração
58
- # ============================================================================
59
-
60
- @dataclass
61
- class NLGConfig:
62
- """Configuração do gerador NLG."""
63
- vocab_size: int = 32000
64
- embed_dim: int = 256
65
- n_heads: int = 4
66
- n_layers: int = 4
67
- max_seq_len: int = 512
68
- dropout: float = 0.1
69
- pad_id: int = 1
70
- bos_id: int = 2
71
- eos_id: int = 3
72
- # Medusa MTP
73
- use_medusa: bool = True
74
- n_medusa_heads: int = 4
75
- mu_medusa: float = 0.5
76
- # Cyclic Reasoning
77
- use_cyclic_reasoning: bool = False
78
- n_cycles: int = 3
79
- # Sampling defaults
80
- temperature: float = 0.7
81
- top_k: int = 40
82
- top_p: float = 0.9
83
- presence_penalty: float = 0.3
84
- frequency_penalty: float = 0.3
85
- # Context window
86
- max_window: int = 1024
87
- n_sink_tokens: int = 4
88
- eviction_strategy: str = "sink_sliding"
89
- # Weight tying
90
- weight_tying: bool = True
91
- # Device
92
- device: str = "cpu"
93
-
94
-
95
- # ============================================================================
96
- # NLG Module
97
- # ============================================================================
98
-
99
- class NLGModule(nn.Module):
100
- """Módulo de geração de linguagem natural (NLG).
101
-
102
- Combina TransformerDecoder + Medusa + (opcional) CyclicReasoning.
103
- """
104
-
105
- def __init__(self, config: NLGConfig):
106
- super().__init__()
107
- self.config = config
108
- # Lazy import para evitar circular deps
109
- from .transformer_block import TransformerDecoderStack
110
- from .medusa_heads import MedusaMTP, MedusaConfig
111
- from .cyclic_reasoning import (
112
- CyclicReasoning, CyclicReasoningConfig,
113
- )
114
- from .context_window import (
115
- ContextWindowManager, ContextWindowConfig,
116
- )
117
-
118
- # Backbone (Transformer Decoder)
119
- self.decoder = TransformerDecoderStack(
120
- vocab_size=config.vocab_size,
121
- embed_dim=config.embed_dim,
122
- n_heads=config.n_heads,
123
- n_layers=config.n_layers,
124
- max_seq_len=config.max_seq_len,
125
- use_rope=True,
126
- pad_id=config.pad_id,
127
- weight_tying=config.weight_tying,
128
- )
129
-
130
- # Medusa MTP (opcional)
131
- if config.use_medusa:
132
- self.medusa = MedusaMTP(MedusaConfig(
133
- vocab_size=config.vocab_size,
134
- embed_dim=config.embed_dim,
135
- n_heads=config.n_medusa_heads,
136
- head_hidden_mult=2,
137
- dropout=config.dropout,
138
- bos_id=config.bos_id,
139
- ))
140
- else:
141
- self.medusa = None
142
-
143
- # Cyclic Reasoning (opcional)
144
- if config.use_cyclic_reasoning:
145
- self.cyclic = CyclicReasoning(CyclicReasoningConfig(
146
- embed_dim=config.embed_dim,
147
- max_cycles=config.n_cycles,
148
- convergence_eps=1e-3,
149
- use_anti_hallucination_gate=True,
150
- device=config.device,
151
- ))
152
- else:
153
- self.cyclic = None
154
-
155
- # Context Window Manager (criado lazy na geração)
156
- self._cw: Optional[ContextWindowManager] = None
157
-
158
- # ----------------------------------------------------------------------
159
- # Forward (treino com teacher forcing)
160
- # ----------------------------------------------------------------------
161
-
162
- def forward(
163
- self,
164
- input_ids: torch.Tensor,
165
- target_ids: Optional[torch.Tensor] = None,
166
- use_medusa_loss: bool = True,
167
- ) -> Dict[str, torch.Tensor]:
168
- """Forward pass com teacher forcing.
169
-
170
- Args:
171
- input_ids: [B, T] tokens de entrada (inclui shift direito para teacher forcing)
172
- target_ids: [B, T] tokens alvo (default: input_ids shifted por 1)
173
- use_medusa_loss: se True, computa loss Medusa
174
-
175
- Returns:
176
- dict com:
177
- logits: [B, T, V] logits da cabeça principal
178
- loss: escalar (CE + Medusa) — só se target_ids fornecido
179
- loss_main: CE da cabeça principal
180
- loss_medusa: loss Medusa (se use_medusa_loss e medusa ativo)
181
- medusa_stats: stats por cabeça
182
- hidden: [B, T, D] hidden states (para debug)
183
- """
184
- # Backbone forward
185
- logits, caches = self.decoder(input_ids) # [B, T, V], list
186
- # hidden states = logits do lm_head pré-projection? Não — precisamos do hidden
187
- # Na TransformerDecoderStack, o hidden é a saída antes do lm_head
188
- # Aqui assumimos que logits é o output do lm_head (com weight tying)
189
- # Para obter o hidden, usamos logits se weight_tying (matriz transposta)
190
- # ou adicionamos um hook no decoder. Para simplificar, refazemos o hidden
191
- # via projeção inversa (apenas se weight_tying=True)
192
- # NOTA: em prática, o decoder poderia retornar hidden explicitamente
193
- # Para este wrapper, retornamos logits como proxy do hidden
194
- # (a Medusa recebe o hidden pré-lm_head, mas para integração simples
195
- # usamos logits como entrada da Medusa apenas quando shapes batem)
196
- hidden = logits # fallback: usa logits como hidden (será projetado se necessário)
197
-
198
- result = {
199
- "logits": logits,
200
- "hidden": hidden,
201
- }
202
-
203
- # Loss (se target fornecido)
204
- if target_ids is not None:
205
- # CE principal
206
- V = self.config.vocab_size
207
- loss_main = F.cross_entropy(
208
- logits.view(-1, V),
209
- target_ids.view(-1),
210
- ignore_index=self.config.pad_id,
211
- reduction="mean",
212
- )
213
- result["loss_main"] = loss_main
214
-
215
- # Loss Medusa
216
- loss_medusa = torch.zeros(1, device=logits.device, dtype=logits.dtype)
217
- if use_medusa_loss and self.medusa is not None:
218
- # A MedusaMTP espera o hidden state [B, T, D], não logits [B, T, V]
219
- # Como não temos o hidden explícito aqui (sem modificar o decoder),
220
- # usamos uma projeção aproximada: pegamos logits e projetamos para D
221
- # via lm_head.weight.T (se weight tying) ou via embedding
222
- # Para simplificar, usamos logits[:, :, :D] (slice) quando V > D
223
- D = self.config.embed_dim
224
- if logits.size(-1) >= D:
225
- hidden_proxy = logits[:, :, :D] # slice simples
226
- else:
227
- # projeta logits para D
228
- if not hasattr(self, "_logits_proj"):
229
- self._logits_proj = nn.Linear(V, D, bias=False).to(logits.device)
230
- hidden_proxy = self._logits_proj(logits)
231
-
232
- loss_med, medusa_stats = self.medusa.compute_loss(
233
- hidden_proxy, target_ids, ignore_index=self.config.pad_id,
234
- )
235
- loss_medusa = loss_med
236
- result["medusa_stats"] = medusa_stats
237
-
238
- result["loss_medusa"] = loss_medusa.squeeze()
239
- result["loss"] = loss_main + self.config.mu_medusa * loss_medusa.squeeze()
240
-
241
- return result
242
-
243
- # ----------------------------------------------------------------------
244
- # Geração autoregressiva
245
- # ----------------------------------------------------------------------
246
-
247
- @torch.no_grad()
248
- def generate(
249
- self,
250
- prompt_ids: torch.Tensor,
251
- max_new_tokens: int = 50,
252
- temperature: Optional[float] = None,
253
- top_k: Optional[int] = None,
254
- top_p: Optional[float] = None,
255
- presence_penalty: Optional[float] = None,
256
- frequency_penalty: Optional[float] = None,
257
- use_context_window: bool = True,
258
- use_medusa: Optional[bool] = None,
259
- eos_id: Optional[int] = None,
260
- ) -> Dict[str, torch.Tensor]:
261
- """Gera texto autoregressivamente com sampling.
262
-
263
- Args:
264
- prompt_ids: [B, T_prompt] IDs do prompt
265
- max_new_tokens: número máximo de tokens a gerar
266
- temperature, top_k, top_p, presence_penalty, frequency_penalty:
267
- overrides da config (se None, usa config)
268
- use_context_window: se True, aplica eviction policy
269
- use_medusa: se True, usa Medusa tree decoding (default: config.use_medusa)
270
- eos_id: ID do token EOS (default: config.eos_id)
271
-
272
- Returns:
273
- dict com:
274
- ids: [B, T_prompt + T_gen] tokens gerados
275
- n_tokens: int — número de tokens gerados (excluindo prompt)
276
- n_medusa_accepted: int — tokens aceitos via Medusa
277
- stopped_early: bool — True se EOS atingido
278
- """
279
- self.eval()
280
- cfg = self.config
281
- T = temperature if temperature is not None else cfg.temperature
282
- K = top_k if top_k is not None else cfg.top_k
283
- P = top_p if top_p is not None else cfg.top_p
284
- PP = presence_penalty if presence_penalty is not None else cfg.presence_penalty
285
- FP = frequency_penalty if frequency_penalty is not None else cfg.frequency_penalty
286
- EOS = eos_id if eos_id is not None else cfg.eos_id
287
- use_med = use_medusa if use_medusa is not None else cfg.use_medusa and self.medusa is not None
288
-
289
- device = prompt_ids.device
290
- B = prompt_ids.size(0)
291
-
292
- # Inicializar context window
293
- if use_context_window and self._cw is None:
294
- from .context_window import ContextWindowManager, ContextWindowConfig
295
- self._cw = ContextWindowManager(ContextWindowConfig(
296
- max_window=cfg.max_window,
297
- eviction_strategy=cfg.eviction_strategy,
298
- n_sink_tokens=cfg.n_sink_tokens,
299
- embed_dim=cfg.embed_dim,
300
- n_heads=cfg.n_heads,
301
- head_dim=cfg.embed_dim // cfg.n_heads,
302
- n_layers=cfg.n_layers,
303
- device=str(device),
304
- ))
305
-
306
- # Token history (para penalidades)
307
- token_counts: List[Dict[int, int]] = [dict() for _ in range(B)]
308
- for b in range(B):
309
- for tok in prompt_ids[b].tolist():
310
- token_counts[b][tok] = token_counts[b].get(tok, 0) + 1
311
-
312
- # Estado: tokens atuais
313
- cur_ids = prompt_ids.clone() # [B, T]
314
- n_generated = 0
315
- n_medusa_accepted = 0
316
- stopped = torch.zeros(B, dtype=torch.bool, device=device)
317
-
318
- for step in range(max_new_tokens):
319
- # Truncar para max_seq_len
320
- ctx = cur_ids[:, -cfg.max_seq_len:]
321
- # Forward
322
- try:
323
- logits, _ = self.decoder(ctx)
324
- except Exception as e:
325
- logger.warning(f"NLG forward falhou no step {step}: {e}")
326
- break
327
-
328
- # Pegar logits do último token
329
- last_logits = logits[:, -1, :].clone() # [B, V]
330
-
331
- # Aplicar penalidades
332
- if PP != 0.0 or FP != 0.0:
333
- for b in range(B):
334
- for tok_id, count in token_counts[b].items():
335
- last_logits[b, tok_id] -= (PP + count * FP)
336
-
337
- # Temperatura
338
- if T > 0:
339
- last_logits = last_logits / T
340
- else:
341
- # Greedy
342
- next_tok = last_logits.argmax(dim=-1, keepdim=True) # [B, 1]
343
- cur_ids = torch.cat([cur_ids, next_tok], dim=1)
344
- n_generated += 1
345
- for b in range(B):
346
- t = next_tok[b].item()
347
- token_counts[b][t] = token_counts[b].get(t, 0) + 1
348
- if t == EOS:
349
- stopped[b] = True
350
- if stopped.all():
351
- break
352
- continue
353
-
354
- # Top-K
355
- if K > 0:
356
- top_vals, _ = torch.topk(last_logits, min(K, last_logits.size(-1)))
357
- min_val = top_vals[:, -1:]
358
- last_logits[last_logits < min_val] = float("-inf")
359
-
360
- # Top-P (nucleus)
361
- if P < 1.0:
362
- sorted_logits, sorted_indices = torch.sort(last_logits, descending=True)
363
- sorted_probs = F.softmax(sorted_logits, dim=-1)
364
- cum_probs = torch.cumsum(sorted_probs, dim=-1)
365
- mask = cum_probs > P
366
- mask[:, 1:] = mask[:, :-1].clone()
367
- mask[:, 0] = False
368
- indices_to_remove = mask.scatter(1, sorted_indices, mask)
369
- last_logits[indices_to_remove] = float("-inf")
370
-
371
- # Sampling
372
- probs = F.softmax(last_logits, dim=-1)
373
- next_tok = torch.multinomial(probs, num_samples=1) # [B, 1]
374
- cur_ids = torch.cat([cur_ids, next_tok], dim=1)
375
- n_generated += 1
376
-
377
- # Update counts
378
- for b in range(B):
379
- t = next_tok[b].item()
380
- token_counts[b][t] = token_counts[b].get(t, 0) + 1
381
- if t == EOS:
382
- stopped[b] = True
383
-
384
- # (Opcional) Medusa: aceitar próximos candidatos
385
- # Skip nesta implementação simplificada — Medusa loss é computada em treino
386
-
387
- if stopped.all():
388
- break
389
-
390
- return {
391
- "ids": cur_ids,
392
- "n_tokens": n_generated,
393
- "n_medusa_accepted": n_medusa_accepted,
394
- "stopped_early": bool(stopped.any().item()),
395
- }
396
-
397
- # ----------------------------------------------------------------------
398
- # Compute loss para treinamento (compatível com trainer)
399
- # ----------------------------------------------------------------------
400
-
401
- def compute_loss(
402
- self,
403
- input_ids: torch.Tensor,
404
- target_ids: Optional[torch.Tensor] = None,
405
- ) -> Dict[str, torch.Tensor]:
406
- """Computa a loss de NLG para uso no trainer.
407
-
408
- Atalho para self.forward(input_ids, target_ids, use_medusa_loss=True)
409
- """
410
- return self.forward(input_ids, target_ids, use_medusa_loss=True)
411
-
412
-
413
- # ============================================================================
414
- # Self-test
415
- # ============================================================================
416
-
417
- def _self_test():
418
- """Teste rápido do módulo NLG."""
419
- torch.manual_seed(42)
420
- config = NLGConfig(
421
- vocab_size=100,
422
- embed_dim=32,
423
- n_heads=4,
424
- n_layers=2,
425
- max_seq_len=32,
426
- pad_id=1, bos_id=2, eos_id=3,
427
- use_medusa=True,
428
- n_medusa_heads=3,
429
- use_cyclic_reasoning=False,
430
- weight_tying=True,
431
- )
432
- nlg = NLGModule(config)
433
- print(f"NLG params: {sum(p.numel() for p in nlg.parameters())}")
434
-
435
- # Forward com teacher forcing
436
- ids = torch.randint(0, 100, (2, 8))
437
- target = torch.randint(0, 100, (2, 8))
438
- out = nlg.compute_loss(ids, target)
439
- print(f"Loss: {out['loss'].item():.4f}")
440
- print(f" main: {out['loss_main'].item():.4f}")
441
- print(f" medusa: {out['loss_medusa'].item():.4f}")
442
- print(f" medusa_stats: {out.get('medusa_stats', {})}")
443
-
444
- # Geração
445
- prompt = torch.tensor([[2, 5, 10, 15]], dtype=torch.long)
446
- gen = nlg.generate(prompt, max_new_tokens=10, temperature=0.7, top_k=10)
447
- print(f"Generated {gen['n_tokens']} tokens: {gen['ids'][0].tolist()}")
448
-
449
-
450
- if __name__ == "__main__":
451
- _self_test()
452
-
453
-
454
- __all__ = [
455
- "NLGConfig",
456
- "NLGModule",
457
- ]