PowerMachine commited on
Commit
ceff207
·
verified ·
1 Parent(s): f07127f

Upload cnn_bigru/inference/inference.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. cnn_bigru/inference/inference.py +257 -0
cnn_bigru/inference/inference.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ from __future__ import annotations
11
+
12
+ import logging
13
+ import math
14
+ from typing import Dict, List, Optional, Tuple
15
+
16
+ import torch
17
+ import torch.nn as nn
18
+ import torch.nn.functional as F
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ @torch.no_grad()
24
+ def generate_with_sampling(
25
+ model: nn.Module,
26
+ tokenizer,
27
+ prompt_a: str,
28
+ prompt_b: str = "",
29
+ images: Optional[torch.Tensor] = None,
30
+ audios: Optional[torch.Tensor] = None,
31
+ max_new_tokens: int = 32,
32
+ temperature: float = 0.7,
33
+ top_k: int = 40,
34
+ top_p: float = 0.9,
35
+ presence_penalty: float = 0.3,
36
+ frequency_penalty: float = 0.3,
37
+ device: str = "cpu",
38
+ ) -> Dict[str, any]:
39
+ """Gera texto com amostragem avançada.
40
+
41
+ Args:
42
+ model: MultimodalCNNBiGRU
43
+ tokenizer: BBPETokenizer
44
+ prompt_a, prompt_b: textos de entrada
45
+ images, audios: tensores opcionais
46
+ max_new_tokens: máximo de tokens a gerar
47
+ temperature: ajusta entropia (0 = greedy)
48
+ top_k: filtra K mais prováveis
49
+ top_p: limite de probabilidade acumulada
50
+ presence_penalty: pune token se já apareceu
51
+ frequency_penalty: pune proporcionalmente à frequência
52
+ device: dispositivo de computação
53
+
54
+ Returns:
55
+ dict com:
56
+ text: texto gerado
57
+ token_ids: lista de IDs gerados
58
+ metrics: dicionário de métricas
59
+ """
60
+ model.eval()
61
+
62
+ # Tokeniza prompts
63
+ ids_a = tokenizer.encode(prompt_a, add_special=True)
64
+ ids_b = tokenizer.encode(prompt_b, add_special=True) if prompt_b else [tokenizer.pad_id] * len(ids_a)
65
+
66
+ # Pad para mesmo comprimento
67
+ max_len = max(len(ids_a), len(ids_b), 8)
68
+ pad_id = tokenizer.pad_id
69
+ ids_a = ids_a + [pad_id] * (max_len - len(ids_a))
70
+ ids_b = ids_b + [pad_id] * (max_len - len(ids_b))
71
+
72
+ input_a = torch.tensor([ids_a], dtype=torch.long, device=device)
73
+ input_b = torch.tensor([ids_b], dtype=torch.long, device=device)
74
+
75
+ # Prepara imagens/áudios
76
+ if images is not None:
77
+ images = images.unsqueeze(0).to(device) if images.dim() == 3 else images.to(device)
78
+ if audios is not None:
79
+ audios = audios.unsqueeze(0).to(device) if audios.dim() == 3 else audios.to(device)
80
+
81
+ # Histórico de contagem para penalidades
82
+ token_counts: Dict[int, int] = {}
83
+ generated_ids: List[int] = []
84
+
85
+ eos_id = tokenizer.eos_id
86
+ bos_id = tokenizer.bos_id
87
+
88
+ metrics = {
89
+ "steps": 0,
90
+ "fallback_to_greedy": 0,
91
+ "tokens_after_topk": 0,
92
+ "tokens_after_topp": 0,
93
+ }
94
+
95
+ for step in range(max_new_tokens):
96
+ metrics["steps"] += 1
97
+
98
+ # Forward
99
+ try:
100
+ out = model(
101
+ input_a, input_b,
102
+ images=images, audios=audios,
103
+ mode="generate",
104
+ )
105
+ logits = out["logits"] # [B, V] ou [B, T, V]
106
+ if logits.dim() == 3:
107
+ logits = logits[:, -1, :] # pega último token
108
+ logits = logits.clone() # [1, V]
109
+ except Exception as e:
110
+ logger.warning("Forward falhou no step %d: %s", step, e)
111
+ break
112
+
113
+ # Penalidades de repetição
114
+ if presence_penalty != 0.0 or frequency_penalty != 0.0:
115
+ for tok_id, count in token_counts.items():
116
+ logits[0, tok_id] -= (presence_penalty + count * frequency_penalty)
117
+
118
+ # Temperatura
119
+ if temperature > 0.0:
120
+ logits = logits / temperature
121
+ else:
122
+ # Greedy
123
+ next_token = torch.argmax(logits, dim=-1, keepdim=True)
124
+ tok_id = next_token.item()
125
+ if tok_id == eos_id:
126
+ break
127
+ generated_ids.append(tok_id)
128
+ token_counts[tok_id] = token_counts.get(tok_id, 0) + 1
129
+ metrics["fallback_to_greedy"] += 1
130
+ # Anexa ao input
131
+ input_a = torch.cat([input_a, next_token], dim=1)
132
+ input_b = torch.cat([input_b, next_token], dim=1)
133
+ continue
134
+
135
+ # Top-K
136
+ if top_k > 0:
137
+ k = min(top_k, logits.size(-1))
138
+ top_vals, _ = torch.topk(logits, k)
139
+ thresh = top_vals[:, -1:]
140
+ logits[logits < thresh] = float("-inf")
141
+ metrics["tokens_after_topk"] = k
142
+
143
+ # Top-P (Nucleus)
144
+ if top_p < 1.0:
145
+ sorted_logits, sorted_idx = torch.sort(logits, descending=True)
146
+ sorted_probs = F.softmax(sorted_logits, dim=-1)
147
+ cum_probs = torch.cumsum(sorted_probs, dim=-1)
148
+
149
+ remove = cum_probs > top_p
150
+ remove[:, 1:] = remove[:, :-1].clone()
151
+ remove[:, 0] = False
152
+
153
+ indices_to_remove = remove.scatter(1, sorted_idx, remove)
154
+ logits[indices_to_remove] = float("-inf")
155
+ metrics["tokens_after_topp"] = int((~indices_to_remove[0]).sum().item())
156
+
157
+ # Amostragem
158
+ probs = F.softmax(logits, dim=-1)
159
+ try:
160
+ next_token = torch.multinomial(probs, num_samples=1)
161
+ except Exception as e:
162
+ logger.warning("Multinomial falhou: %s — usando argmax", e)
163
+ next_token = torch.argmax(probs, dim=-1, keepdim=True)
164
+
165
+ tok_id = next_token.item()
166
+ if tok_id == eos_id:
167
+ break
168
+
169
+ generated_ids.append(tok_id)
170
+ token_counts[tok_id] = token_counts.get(tok_id, 0) + 1
171
+
172
+ # Anexa ao input (mantém janela)
173
+ max_seq = 64 # janela máxima
174
+ input_a = torch.cat([input_a, next_token], dim=1)[:, -max_seq:]
175
+ input_b = torch.cat([input_b, next_token], dim=1)[:, -max_seq:]
176
+
177
+ # Decodifica
178
+ text = tokenizer.decode(generated_ids, skip_special=True)
179
+
180
+ return {
181
+ "text": text,
182
+ "token_ids": generated_ids,
183
+ "metrics": metrics,
184
+ }
185
+
186
+
187
+ @torch.no_grad()
188
+ def evaluate_perplexity(
189
+ model: nn.Module,
190
+ dataloader,
191
+ tokenizer,
192
+ device: str = "cpu",
193
+ max_batches: int = 10,
194
+ ) -> Dict[str, float]:
195
+ """Avalia perplexidade do modelo em um dataloader.
196
+
197
+ PPL = exp(loss_media)
198
+ """
199
+ model.eval()
200
+ total_loss = 0.0
201
+ total_tokens = 0
202
+ pad_id = tokenizer.pad_id
203
+
204
+ n_batches = 0
205
+ for batch_idx, batch in enumerate(dataloader):
206
+ if batch_idx >= max_batches:
207
+ break
208
+
209
+ input_ids_a = batch["input_ids_a"].to(device)
210
+ input_ids_b = batch["input_ids_b"].to(device)
211
+ images = batch["images"].to(device)
212
+ audios = batch["audios"].to(device)
213
+ attn_mask_a = batch["attn_mask_a"].to(device)
214
+ attn_mask_b = batch["attn_mask_b"].to(device)
215
+
216
+ try:
217
+ out = model(
218
+ input_ids_a, input_ids_b,
219
+ images=images, audios=audios,
220
+ attn_mask_a=attn_mask_a, attn_mask_b=attn_mask_b,
221
+ mode="generate",
222
+ )
223
+ logits = out["logits"] # [B, V]
224
+ # Para PPL, usamos input_ids_b como target (próximo token)
225
+ # Expandimos logits para [B, T, V]
226
+ B = logits.size(0)
227
+ T = input_ids_b.size(1)
228
+ V = logits.size(-1)
229
+ expanded = logits.unsqueeze(1).expand(-1, T, -1)
230
+
231
+ loss = F.cross_entropy(
232
+ expanded.reshape(B * T, V),
233
+ input_ids_b.reshape(B * T),
234
+ ignore_index=pad_id,
235
+ reduction="sum",
236
+ )
237
+ n_tokens = (input_ids_b != pad_id).sum().item()
238
+ total_loss += loss.item()
239
+ total_tokens += n_tokens
240
+ n_batches += 1
241
+ except Exception as e:
242
+ logger.warning("PPL batch %d falhou: %s", batch_idx, e)
243
+ continue
244
+
245
+ if total_tokens == 0:
246
+ return {"loss": float("inf"), "ppl": float("inf"), "n_batches": 0}
247
+
248
+ avg_loss = total_loss / total_tokens
249
+ try:
250
+ ppl = math.exp(min(avg_loss, 20))
251
+ except OverflowError:
252
+ ppl = float("inf")
253
+
254
+ return {"loss": avg_loss, "ppl": ppl, "n_batches": n_batches}
255
+
256
+
257
+ __all__ = ["generate_with_sampling", "evaluate_perplexity"]