PowerMachine commited on
Commit
4e142c9
·
verified ·
1 Parent(s): 4347f1b

Delete inference/inference.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. inference/inference.py +0 -450
inference/inference.py DELETED
@@ -1,450 +0,0 @@
1
- """inference.py — Inferência com Temperatura, Top-K, Top-P e Penalidades.
2
-
3
- Implementa as técnicas descritas em dados.txt (seção sobre geração avançada):
4
- 1. Temperatura: ajusta entropia das previsões
5
- 2. Top-K: filtra os K tokens mais prováveis
6
- 3. Top-P (Nucleus Sampling): limite dinâmico por probabilidade acumulada
7
- 4. Presence Penalty: pune se já apareceu
8
- 5. Frequency Penalty: pune proporcionalmente à frequência
9
-
10
- VERSÃO 2.0:
11
- - Usa GeneratorCNNBiGRU (quando disponível) para geração autoregressiva REAL
12
- - Integra ContextWindowManager para sequências longas (sliding window)
13
- - Fallback para MultimodalCNNBiGRU.lm_head quando generator não disponível
14
- - PPL evaluation correta usando GeneratorCNNBiGRU (não single-step broadcast)
15
- """
16
- from __future__ import annotations
17
-
18
- import logging
19
- import math
20
- from typing import Any, Dict, List, Optional, Tuple
21
-
22
- import torch
23
- import torch.nn as nn
24
- import torch.nn.functional as F
25
-
26
- from ..models.context_window import ContextWindowManager, ContextWindowConfig
27
- from ..models.generator_verifier import GeneratorCNNBiGRU
28
- from ..models.multimodal_model import MultimodalCNNBiGRU
29
-
30
- logger = logging.getLogger(__name__)
31
-
32
-
33
- # ============================================================================
34
- # Aplicar penalidades, temperatura, top-k, top-p
35
- # ============================================================================
36
-
37
- def apply_sampling_filters(
38
- logits: torch.Tensor,
39
- token_counts: Dict[int, int],
40
- temperature: float,
41
- top_k: int,
42
- top_p: float,
43
- presence_penalty: float,
44
- frequency_penalty: float,
45
- ) -> Tuple[torch.Tensor, Dict[str, int]]:
46
- """Aplica presença/frequência penalidades + temperatura + top-k + top-p.
47
-
48
- Args:
49
- logits: [B, V] (será modificado in-place via clone)
50
- token_counts: dict {token_id: count} para penalidades
51
- temperature: ajusta entropia (0 = greedy)
52
- top_k: filtra K mais prováveis
53
- top_p: limite de probabilidade acumulada
54
- presence_penalty: pune token se já apareceu
55
- frequency_penalty: pune proporcionalmente à frequência
56
-
57
- Returns:
58
- (filtered_logits, metrics_dict)
59
- """
60
- metrics = {
61
- "tokens_after_topk": 0,
62
- "tokens_after_topp": 0,
63
- }
64
- logits = logits.clone()
65
-
66
- # Penalidades de repetição (direct logit modification)
67
- if presence_penalty != 0.0 or frequency_penalty != 0.0:
68
- for tok_id, count in token_counts.items():
69
- if 0 <= tok_id < logits.size(-1):
70
- logits[:, tok_id] -= (presence_penalty + count * frequency_penalty)
71
-
72
- # Temperatura
73
- if temperature > 0.0:
74
- logits = logits / temperature
75
- # Se temperature == 0, chamador deve usar greedy (argmax)
76
-
77
- # Top-K
78
- if top_k > 0 and top_k < logits.size(-1):
79
- k = min(top_k, logits.size(-1))
80
- top_vals, _ = torch.topk(logits, k, dim=-1)
81
- thresh = top_vals[:, -1:]
82
- logits[logits < thresh] = float("-inf")
83
- metrics["tokens_after_topk"] = k
84
-
85
- # Top-P (Nucleus Sampling)
86
- if top_p < 1.0:
87
- sorted_logits, sorted_idx = torch.sort(logits, descending=True, dim=-1)
88
- sorted_probs = F.softmax(sorted_logits, dim=-1)
89
- cum_probs = torch.cumsum(sorted_probs, dim=-1)
90
-
91
- # Remove tokens cuja probabilidade acumulada ultrapassa top_p
92
- # (mantém o primeiro token mesmo se já ultrapassar)
93
- remove = cum_probs > top_p
94
- remove[:, 1:] = remove[:, :-1].clone()
95
- remove[:, 0] = False
96
-
97
- indices_to_remove = remove.scatter(1, sorted_idx, remove)
98
- logits[indices_to_remove] = float("-inf")
99
- metrics["tokens_after_topp"] = int((~indices_to_remove[0]).sum().item())
100
-
101
- return logits, metrics
102
-
103
-
104
- # ============================================================================
105
- # Geração Autoregressiva (V2.0 — CORRIGIDA)
106
- # ============================================================================
107
-
108
- @torch.no_grad()
109
- def generate_with_sampling(
110
- model: nn.Module,
111
- tokenizer,
112
- prompt_a: str,
113
- prompt_b: str = "",
114
- images: Optional[torch.Tensor] = None,
115
- audios: Optional[torch.Tensor] = None,
116
- max_new_tokens: int = 32,
117
- temperature: float = 0.7,
118
- top_k: int = 40,
119
- top_p: float = 0.9,
120
- presence_penalty: float = 0.3,
121
- frequency_penalty: float = 0.3,
122
- device: str = "cpu",
123
- generator: Optional[GeneratorCNNBiGRU] = None,
124
- context_window: Optional[ContextWindowManager] = None,
125
- ) -> Dict[str, Any]:
126
- """Gera texto com amostragem avançada.
127
-
128
- VERSÃO 2.0:
129
- - Se `generator` (GeneratorCNNBiGRU) for fornecido, usa geração
130
- autoregressiva REAL com o decoder GRU + Bahdanau attention.
131
- - Caso contrário, fallback para MultimodalCNNBiGRU em modo "generate"
132
- (single-step — NÃO é autoregressivo real, apenas para testes).
133
- - Se `context_window` for fornecido, aplica sliding window para
134
- suportar sequências longas (> max_seq_len do modelo).
135
-
136
- Args:
137
- model: MultimodalCNNBiGRU
138
- tokenizer: BBPETokenizer
139
- prompt_a, prompt_b: textos de entrada
140
- images, audios: tensores opcionais
141
- max_new_tokens: máximo de tokens a gerar
142
- temperature: ajusta entropia (0 = greedy)
143
- top_k: filtra K mais prováveis
144
- top_p: limite de probabilidade acumulada
145
- presence_penalty: pune token se já apareceu
146
- frequency_penalty: pune proporcionalmente à frequência
147
- device: dispositivo de computação
148
- generator: GeneratorCNNBiGRU opcional (recomendado para geração real)
149
- context_window: gerenciador de janela de contexto opcional
150
-
151
- Returns:
152
- dict com:
153
- text: texto gerado
154
- token_ids: lista de IDs gerados
155
- metrics: dicionário de métricas
156
- used_generator: bool indicando se usou generator real
157
- """
158
- model.eval()
159
- if generator is not None:
160
- generator.eval()
161
-
162
- # Tokeniza prompts
163
- ids_a = tokenizer.encode(prompt_a, add_special=True)
164
- ids_b = tokenizer.encode(prompt_b, add_special=True) if prompt_b else [tokenizer.pad_id] * len(ids_a)
165
-
166
- # Pad para mesmo comprimento
167
- max_len = max(len(ids_a), len(ids_b), 8)
168
- pad_id = tokenizer.pad_id
169
- ids_a = ids_a + [pad_id] * (max_len - len(ids_a))
170
- ids_b = ids_b + [pad_id] * (max_len - len(ids_b))
171
-
172
- input_a = torch.tensor([ids_a], dtype=torch.long, device=device)
173
- input_b = torch.tensor([ids_b], dtype=torch.long, device=device)
174
-
175
- # Prepara imagens/áudios
176
- if images is not None:
177
- images = images.unsqueeze(0).to(device) if images.dim() == 3 else images.to(device)
178
- if audios is not None:
179
- audios = audios.unsqueeze(0).to(device) if audios.dim() == 3 else audios.to(device)
180
-
181
- # Histórico de contagem para penalidades
182
- token_counts: Dict[int, int] = {}
183
- generated_ids: List[int] = []
184
-
185
- eos_id = tokenizer.eos_id
186
- bos_id = tokenizer.bos_id
187
-
188
- metrics = {
189
- "steps": 0,
190
- "fallback_to_greedy": 0,
191
- "tokens_after_topk": 0,
192
- "tokens_after_topp": 0,
193
- "used_generator": generator is not None,
194
- }
195
-
196
- # Inicializar context window se fornecido
197
- # IMPORTANTE: o context_window é usado APENAS para truncar a janela de input
198
- # (não para cache KV — o generator faz seu próprio forward a cada step).
199
- # Para evitar misturar o histórico de input_a e input_b, usamos
200
- # truncamento simples em vez de context_window.append_tokens (que acumula).
201
- max_seq = 64 # janela máxima padrão
202
- if context_window is not None:
203
- try:
204
- max_seq = context_window.config.max_window
205
- except AttributeError:
206
- pass
207
-
208
- # Escolha do modo de geração
209
- use_generator = generator is not None
210
-
211
- for step in range(max_new_tokens):
212
- metrics["steps"] += 1
213
-
214
- # Truncar input para max_seq (janela deslizante simples)
215
- input_a = input_a[:, -max_seq:]
216
- input_b = input_b[:, -max_seq:]
217
-
218
- # --- Forward ---
219
- try:
220
- if use_generator:
221
- # GERAÇÃO AUTOREGRESSIVA REAL via GeneratorCNNBiGRU
222
- # O generator tem decoder GRU + Bahdanau attention
223
- # Passamos target_proof=None para que ele gere livremente
224
- # mas limitamos max_len ao tamanho atual + 1
225
- gen_out = generator(
226
- input_a, input_b,
227
- target_proof=None,
228
- bos_id=bos_id,
229
- eos_id=eos_id,
230
- max_len=1, # gera 1 token por vez
231
- )
232
- logits = gen_out["logits"] # [B, 1, V]
233
- logits = logits[:, -1, :] # [1, V]
234
- else:
235
- # FALLBACK: MultimodalCNNBiGRU em modo "generate" (single-step)
236
- out = model(
237
- input_a, input_b,
238
- images=images, audios=audios,
239
- mode="generate",
240
- )
241
- logits = out["logits"] # [B, V] ou [B, T, V]
242
- if logits.dim() == 3:
243
- logits = logits[:, -1, :]
244
- logits = logits.clone() # [1, V]
245
- except Exception as e:
246
- logger.warning(f"Forward falhou no step {step}: {e}")
247
- break
248
-
249
- # --- Greedy (temperature == 0) ---
250
- if temperature <= 0.0:
251
- next_token = torch.argmax(logits, dim=-1, keepdim=True)
252
- tok_id = next_token.item()
253
- if tok_id == eos_id:
254
- break
255
- generated_ids.append(tok_id)
256
- token_counts[tok_id] = token_counts.get(tok_id, 0) + 1
257
- metrics["fallback_to_greedy"] += 1
258
- # Anexa ao input (trunca para max_seq)
259
- input_a = torch.cat([input_a, next_token], dim=1)[:, -max_seq:]
260
- input_b = torch.cat([input_b, next_token], dim=1)[:, -max_seq:]
261
- continue
262
-
263
- # --- Aplicar filtros de sampling ---
264
- logits, filter_metrics = apply_sampling_filters(
265
- logits, token_counts,
266
- temperature=temperature,
267
- top_k=top_k,
268
- top_p=top_p,
269
- presence_penalty=presence_penalty,
270
- frequency_penalty=frequency_penalty,
271
- )
272
- metrics["tokens_after_topk"] = filter_metrics["tokens_after_topk"]
273
- metrics["tokens_after_topp"] = filter_metrics["tokens_after_topp"]
274
-
275
- # --- Amostragem Multinomial ---
276
- # Substituir -inf por um valor muito baixo (evita NaN em softmax)
277
- logits = torch.where(torch.isinf(logits), torch.full_like(logits, -1e9), logits)
278
- probs = F.softmax(logits, dim=-1)
279
- # Lidar com probs NaN (todas -inf)
280
- if torch.isnan(probs).any() or probs.sum() == 0:
281
- logger.warning(f"NaN/zero probs no step {step}; usando argmax")
282
- next_token = torch.argmax(logits, dim=-1, keepdim=True)
283
- else:
284
- try:
285
- next_token = torch.multinomial(probs, num_samples=1)
286
- except Exception as e:
287
- logger.warning(f"Multinomial falhou: {e}; usando argmax")
288
- next_token = torch.argmax(probs, dim=-1, keepdim=True)
289
-
290
- tok_id = next_token.item()
291
- if tok_id == eos_id:
292
- break
293
-
294
- generated_ids.append(tok_id)
295
- token_counts[tok_id] = token_counts.get(tok_id, 0) + 1
296
-
297
- # Anexa ao input (trunca para max_seq)
298
- input_a = torch.cat([input_a, next_token], dim=1)[:, -max_seq:]
299
- input_b = torch.cat([input_b, next_token], dim=1)[:, -max_seq:]
300
-
301
- # Decodifica
302
- text = tokenizer.decode(generated_ids, skip_special=True)
303
-
304
- return {
305
- "text": text,
306
- "token_ids": generated_ids,
307
- "metrics": metrics,
308
- "used_generator": use_generator,
309
- }
310
-
311
-
312
- # ============================================================================
313
- # Avaliação de Perplexidade (V2.0 — CORRIGIDA)
314
- # ============================================================================
315
-
316
- @torch.no_grad()
317
- def evaluate_perplexity(
318
- model: nn.Module,
319
- dataloader,
320
- tokenizer,
321
- device: str = "cpu",
322
- max_batches: int = 10,
323
- generator: Optional[GeneratorCNNBiGRU] = None,
324
- ) -> Dict[str, float]:
325
- """Avalia perplexidade do modelo em um dataloader.
326
-
327
- PPL = exp(loss_media)
328
-
329
- VERSÃO 2.0:
330
- - Se `generator` for fornecido, usa o generator com teacher forcing
331
- para gerar logits autoregressivos REAIS por timestep.
332
- - Caso contrário, faz fallback para MultimodalCNNBiGRU (single-step
333
- broadcast — métrica aproximada).
334
- """
335
- model.eval()
336
- if generator is not None:
337
- generator.eval()
338
-
339
- total_loss = 0.0
340
- total_tokens = 0
341
- pad_id = tokenizer.pad_id
342
- bos_id = tokenizer.bos_id
343
- eos_id = tokenizer.eos_id
344
-
345
- n_batches = 0
346
- for batch_idx, batch in enumerate(dataloader):
347
- if batch_idx >= max_batches:
348
- break
349
-
350
- input_ids_a = batch["input_ids_a"].to(device)
351
- input_ids_b = batch["input_ids_b"].to(device)
352
- attn_mask_a = batch["attn_mask_a"].to(device)
353
- attn_mask_b = batch["attn_mask_b"].to(device)
354
-
355
- try:
356
- if generator is not None:
357
- # USAR GENERATOR: geração autoregressiva com teacher forcing
358
- # target_proof = input_ids_b → logits por timestep
359
- gen_out = generator(
360
- input_ids_a, input_ids_b,
361
- target_proof=input_ids_b, # teacher forcing
362
- bos_id=bos_id,
363
- eos_id=eos_id,
364
- )
365
- logits = gen_out["logits"] # [B, T, V]
366
- else:
367
- # FALLBACK: single-step broadcast (NÃO É autoregressivo real)
368
- # Apenas para compatibilidade
369
- images = batch.get("images")
370
- audios = batch.get("audios")
371
- if images is not None:
372
- images = images.to(device)
373
- if audios is not None:
374
- audios = audios.to(device)
375
- out = model(
376
- input_ids_a, input_ids_b,
377
- images=images, audios=audios,
378
- attn_mask_a=attn_mask_a, attn_mask_b=attn_mask_b,
379
- mode="generate",
380
- )
381
- # out["logits"] é [B, V] (single-step) → expandir para [B, T, V]
382
- B = out["logits"].size(0)
383
- T = input_ids_b.size(1)
384
- V = out["logits"].size(-1)
385
- logits = out["logits"].unsqueeze(1).expand(-1, T, -1)
386
-
387
- # Calcular loss por token
388
- B, T, V = logits.shape
389
- loss = F.cross_entropy(
390
- logits.reshape(B * T, V),
391
- input_ids_b.reshape(B * T),
392
- ignore_index=pad_id,
393
- reduction="sum",
394
- )
395
- n_tokens = (input_ids_b != pad_id).sum().item()
396
- total_loss += loss.item()
397
- total_tokens += n_tokens
398
- n_batches += 1
399
- except Exception as e:
400
- logger.warning(f"PPL batch {batch_idx} falhou: {e}")
401
- continue
402
-
403
- if total_tokens == 0:
404
- return {"loss": float("inf"), "ppl": float("inf"), "n_batches": 0}
405
-
406
- avg_loss = total_loss / total_tokens
407
- try:
408
- ppl = math.exp(min(avg_loss, 20))
409
- except OverflowError:
410
- ppl = float("inf")
411
-
412
- return {
413
- "loss": avg_loss,
414
- "ppl": ppl,
415
- "n_batches": n_batches,
416
- "used_generator": generator is not None,
417
- }
418
-
419
-
420
- # ============================================================================
421
- # Helper: criar context window padrão
422
- # ============================================================================
423
-
424
- def make_default_context_window(
425
- max_window: int = 256,
426
- n_sink: int = 4,
427
- embed_dim: int = 64,
428
- n_heads: int = 4,
429
- n_layers: int = 2,
430
- device: str = "cpu",
431
- ) -> ContextWindowManager:
432
- """Factory para criar um ContextWindowManager com defaults sensatos."""
433
- config = ContextWindowConfig(
434
- max_window=max_window,
435
- eviction_strategy="sink_sliding",
436
- n_sink_tokens=n_sink,
437
- embed_dim=embed_dim,
438
- n_heads=n_heads,
439
- n_layers=n_layers,
440
- device=device,
441
- )
442
- return ContextWindowManager(config)
443
-
444
-
445
- __all__ = [
446
- "apply_sampling_filters",
447
- "generate_with_sampling",
448
- "evaluate_perplexity",
449
- "make_default_context_window",
450
- ]