PowerMachine commited on
Commit
7e0516f
·
verified ·
1 Parent(s): a35c145

Delete src/bigru_t/model/unified_model.py

Browse files
Files changed (1) hide show
  1. src/bigru_t/model/unified_model.py +0 -523
src/bigru_t/model/unified_model.py DELETED
@@ -1,523 +0,0 @@
1
- """unified_model.py — UnifiedModel: orquestra todos os componentes (Lemas 1-4).
2
-
3
- Arquitetura BiGRU_T_version:
4
-
5
- x (batch, T, input_dim)
6
- │
7
- ├──→ u8cell_T_1 ─→ h_1 ─┐
8
- ├──→ u8cell_T_2 ─→ h_2 ─┤ Lema 1: alpha = softmax(logits / T)
9
- ├──→ ... ├─→ H = stack([alpha_k * h_k]) → OrqCell
10
- └──→ u8cell_T_K ─→ h_K ─┘ │
11
- ↓
12
- o (batch, d_cache)
13
- │
14
- ┌──────────────┴──────────────┐
15
- ↓ ↓
16
- TrainT(o) HypT(o, stop_grad=True)
17
- │ │
18
- ↓ ↓
19
- y_hat delta
20
- │ │
21
- └──────→ y_final = y_hat + delta * use_hyp ←── Lema 3
22
-
23
- Lema 2: gradient_surgery(g_main, g_hyp) → projeção ortogonal
24
- Lema 4: MetaConfigurator ajusta T (temperatura) e τ (limiar de ativação da hipótese)
25
-
26
- Diferenças vs. v13.9.2 GRURingV139:
27
- - 1 modelo agregador (OrqCell) em vez de 4 camadas cooperativas empilhadas
28
- - Seleção de módulos por softmax (Lema 1) em vez de Confidence Gate
29
- - Hipótese global (HypT) em vez de 16 hipóteses paralelas com LoRA
30
- - Quantização W8A8 fake-quant em todas as Linear (Lema 3) em vez de
31
- W8A8 QOperator só em inferência
32
- - Tau aprendível (Lema 4) em vez de trust_lr fixo
33
-
34
- Adaptação para Language Modeling:
35
- - input_dim = d_model (embedding dim)
36
- - output_dim = vocab_size
37
- - Adiciona camada de embedding (token IDs → embeddings)
38
- - Adiciona positional encoding (sinusoidal)
39
- """
40
- from __future__ import annotations
41
-
42
- import math
43
- from dataclasses import dataclass, field
44
- from typing import Optional
45
-
46
- import torch
47
- import torch.nn as nn
48
- import torch.nn.functional as F
49
-
50
- from .u8cell_t import u8cell_T
51
- from .orq_cell import OrqCell
52
- from .train_t import TrainT
53
- from .hyp_t import HypT
54
- from .module_selector import ModuleSelector
55
- # V5: aplicar_w8a8_to_model substitui apply_w8a8 (bug fix crítico)
56
- from ..quantization.quantized_linear import QuantizedLinear, apply_w8a8_to_model
57
- from ..reasoning.circular_reasoning_wasserstein import CircularReasoningWasserstein
58
-
59
-
60
- @dataclass
61
- class UnifiedModelConfig:
62
- """Configuração do UnifiedModel.
63
-
64
- Campos agrupados por componente:
65
- - Tokenizer / embedding: vocab_size, d_model, max_seq_len, pad_token_id
66
- - u8cell_T: input_dim (default = d_model), bigru_hidden, d_transformer, nhead_tu, d_ff_tu, output_dim_u8cell
67
- - Module selector: max_modules, lambda_ent
68
- - OrqCell: cache_len, d_cache, nhead_orq, d_ff_orq
69
- - TrainT: trainT_dim, nhead_train, d_ff_train, num_layers_train
70
- - HypT: hypT_dim, nhead_hyp, d_ff_hyp, num_layers_hyp
71
- - Quantization: num_bits (W8A8)
72
- - Misc: dropout
73
-
74
- Nota: `input_dim` é automaticamente sincronizado com `d_model` em
75
- __post_init__ se não for explicitamente especificado. Isso garante
76
- que a saída do embedding (dim=d_model) matches a entrada do BiGRU4
77
- (input_dim).
78
- """
79
- # Embedding
80
- vocab_size: int = 16384
81
- d_model: int = 128
82
- max_seq_len: int = 64
83
- pad_token_id: int = 1
84
-
85
- # u8cell_T
86
- input_dim: int = 0 # 0 = usar d_model automaticamente (ver __post_init__)
87
- bigru_hidden: int = 32
88
- d_transformer: int = 64
89
- nhead_tu: int = 4
90
- d_ff_tu: int = 128
91
- output_dim_u8cell: int = 64
92
-
93
- # Module selector (Lema 1)
94
- max_modules: int = 64
95
- lambda_ent: float = 0.01
96
-
97
- # OrqCell
98
- cache_len: int = 16
99
- d_cache: int = 128
100
- nhead_orq: int = 8
101
- d_ff_orq: int = 256
102
-
103
- # TrainT
104
- trainT_dim: int = 128
105
- nhead_train: int = 4
106
- d_ff_train: int = 256
107
- num_layers_train: int = 2
108
-
109
- # HypT
110
- # V4: num_layers_hyp aumentado de 2 → 8 (mais capacidade para correção delta)
111
- # V5: num_layers_hyp=8 é o PADRÃO PERMANENTE (não mudar)
112
- hypT_dim: int = 128
113
- nhead_hyp: int = 4
114
- d_ff_hyp: int = 256
115
- num_layers_hyp: int = 8
116
-
117
- # Quantization (Lema 3)
118
- num_bits: int = 8
119
-
120
- # Circular Reasoning (Teorema 4 — contração W₂)
121
- use_circular_reasoning: bool = True
122
- circular_n_cycles: int = 3
123
- circular_contraction_target: float = 0.9
124
- circular_init_gate: float = 0.05
125
-
126
- # Misc
127
- dropout: float = 0.1
128
-
129
- def __post_init__(self):
130
- """Sincroniza input_dim com d_model se não for explicitamente especificado."""
131
- if self.input_dim <= 0:
132
- self.input_dim = self.d_model
133
- # Validações de consistência
134
- if self.d_transformer % self.nhead_tu != 0:
135
- raise ValueError(
136
- f"d_transformer ({self.d_transformer}) must be divisible by nhead_tu ({self.nhead_tu})"
137
- )
138
- if self.d_cache % self.nhead_orq != 0:
139
- raise ValueError(
140
- f"d_cache ({self.d_cache}) must be divisible by nhead_orq ({self.nhead_orq})"
141
- )
142
- if self.trainT_dim % self.nhead_train != 0:
143
- raise ValueError(
144
- f"trainT_dim ({self.trainT_dim}) must be divisible by nhead_train ({self.nhead_train})"
145
- )
146
- if self.hypT_dim % self.nhead_hyp != 0:
147
- raise ValueError(
148
- f"hypT_dim ({self.hypT_dim}) must be divisible by nhead_hyp ({self.nhead_hyp})"
149
- )
150
-
151
-
152
- class PositionalEncoding(nn.Module):
153
- """Positional encoding sinusoidal (Vaswani et al., 2017).
154
-
155
- Reaproveita a filosofia do v13.9.2 (que usa RoPE), mas como o BiGRU_T
156
- processa a sequência via BiGRU (não via attention causal), o PE sinusoidal
157
- é suficiente e mais simples.
158
- """
159
-
160
- def __init__(self, d_model: int, max_len: int = 512):
161
- super().__init__()
162
- pe = torch.zeros(max_len, d_model)
163
- position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
164
- div_term = torch.exp(
165
- torch.arange(0, d_model, 2, dtype=torch.float) * (-math.log(10000.0) / d_model)
166
- )
167
- pe[:, 0::2] = torch.sin(position * div_term)
168
- pe[:, 1::2] = torch.cos(position * div_term)
169
- self.register_buffer("pe", pe.unsqueeze(0)) # (1, max_len, d_model)
170
-
171
- def forward(self, x: torch.Tensor) -> torch.Tensor:
172
- """x: (batch, T, d_model) → x + pe[:, :T]"""
173
- return x + self.pe[:, : x.size(1)]
174
-
175
-
176
- class UnifiedModel(nn.Module):
177
- """Modelo BiGRU_T_version — orquestra Lemas 1-4.
178
-
179
- Args:
180
- config: UnifiedModelConfig
181
-
182
- Forward:
183
- x: (batch, T) token IDs OU (batch, T, input_dim) embeddings
184
- (detetado por ndim: 2 = token IDs, 3 = embeddings)
185
- temperature: float (default 1.0, sobrescrito pelo MetaConfigurator)
186
- use_hypothesis: bool (default False, sobrescrito pelo trainer se loss > tau)
187
- stop_grad_hyp: bool (default True, Lema 3)
188
-
189
- Returns:
190
- (y_hat, delta) — ambos (batch, output_dim). y_final = y_hat + delta * use_hypothesis.
191
- """
192
-
193
- def __init__(self, config: Optional[UnifiedModelConfig] = None):
194
- super().__init__()
195
- self.config = config or UnifiedModelConfig()
196
- c = self.config
197
-
198
- # Embedding de tokens (para LM)
199
- self.token_embedding = nn.Embedding(c.vocab_size, c.d_model, padding_idx=c.pad_token_id)
200
- self.positional_encoding = PositionalEncoding(c.d_model, c.max_seq_len * 2)
201
- self.emb_dropout = nn.Dropout(c.dropout)
202
-
203
- # Module selector (Lema 1)
204
- self.module_selector = ModuleSelector(c.max_modules, lambda_ent=c.lambda_ent)
205
-
206
- # Lista de módulos u8cell_T
207
- self.u8cells = nn.ModuleList([
208
- u8cell_T(
209
- input_dim=c.input_dim,
210
- hidden_dim_bigru=c.bigru_hidden,
211
- d_transformer=c.d_transformer,
212
- nhead=c.nhead_tu,
213
- d_ff=c.d_ff_tu,
214
- output_dim=c.output_dim_u8cell,
215
- dropout=c.dropout,
216
- )
217
- for _ in range(c.max_modules)
218
- ])
219
-
220
- # Orquestrador
221
- self.orq_cell = OrqCell(
222
- cache_len=c.cache_len,
223
- d_cache=c.d_cache,
224
- d_model_in=c.output_dim_u8cell,
225
- nhead=c.nhead_orq,
226
- d_ff=c.d_ff_orq,
227
- dropout=c.dropout,
228
- )
229
-
230
- # Cabeça principal
231
- self.train_T = TrainT(
232
- d_input=c.d_cache,
233
- d_model=c.trainT_dim,
234
- nhead=c.nhead_train,
235
- d_ff=c.d_ff_train,
236
- output_dim=c.vocab_size, # LM: prevê próximo token
237
- num_layers=c.num_layers_train,
238
- dropout=c.dropout,
239
- )
240
-
241
- # Cabeça de hipótese (Lema 3)
242
- self.hyp_T = HypT(
243
- d_input=c.d_cache,
244
- d_model=c.hypT_dim,
245
- nhead=c.nhead_hyp,
246
- d_ff=c.d_ff_hyp,
247
- output_dim=c.vocab_size,
248
- num_layers=c.num_layers_hyp,
249
- dropout=c.dropout,
250
- )
251
-
252
- # Limiar de punição (τ) — atualizado pelo MetaConfigurator (Lema 4)
253
- # Init = 1.0 (sem hipótese até a loss cair abaixo de 1.0 — raro no início)
254
- self.register_buffer("tau", torch.tensor(float("inf")))
255
-
256
- # Circular Reasoning (Teorema 4 — contração W₂ sobre a representação agregada)
257
- # Aplicado APÓS OrqCell e ANTES de TrainT/HypT. Refina 'o' em n_cycles
258
- # iterações de Φ(s) = LayerNorm(s + gate * RefineHead(s)).
259
- self.circular_reasoning = (
260
- CircularReasoningWasserstein(
261
- d_model=c.d_cache,
262
- n_cycles=c.circular_n_cycles,
263
- contraction_target=c.circular_contraction_target,
264
- init_gate=c.circular_init_gate,
265
- )
266
- if c.use_circular_reasoning
267
- else None
268
- )
269
-
270
- # V5: Parâmetros APRENDÍVEIS do modelo (auto-ajuste próprio)
271
- # λ_ent: peso da regularização de entropia do module_selector (Lema 1)
272
- # Parametrização via exp (sempre positivo)
273
- # init: λ_ent = c.lambda_ent = 0.01
274
- # O residual_gate do CircularReasoning JÁ é Parameter (n_cycles params)
275
- # e é gerenciado diretamente pelo MetaConfigurator.
276
- self.log_lambda_ent = nn.Parameter(
277
- torch.tensor(math.log(max(1e-6, c.lambda_ent)), dtype=torch.float32)
278
- )
279
-
280
- # V5: Aplicar W8A8 em todas as camadas lineares (exceto Embedding, que é nn.Embedding)
281
- # BUG FIX CRÍTICO V5: model.apply(fn) NÃO substitui módulos (descarta retorno).
282
- # Usar apply_w8a8_to_model (substituição in-place recursiva).
283
- n_quantized = apply_w8a8_to_model(self)
284
- # Nota: nn.GRU não é afetada (não é Linear); Embedding não é afetada (nn.Embedding)
285
- # Log em runtime (não criticar se n_quantized=0 — modelo pode não ter Linear)
286
- self._n_w8a8_layers = n_quantized
287
-
288
- def forward(
289
- self,
290
- x: torch.Tensor,
291
- temperature: float = 1.0,
292
- use_hypothesis: bool = False,
293
- stop_grad_hyp: bool = True,
294
- return_aux: bool = False,
295
- use_circular: Optional[bool] = None,
296
- ) -> tuple[torch.Tensor, torch.Tensor]:
297
- """Forward do UnifiedModel.
298
-
299
- Args:
300
- x: (batch, T) token IDs OU (batch, T, input_dim) embeddings
301
- temperature: temperatura do softmax do Lema 1
302
- use_hypothesis: se True, calcula delta (cabeça HypT ativa)
303
- stop_grad_hyp: se True, detach a entrada da HypT (Lema 3)
304
- return_aux: se True, retorna também entropy_reg, alpha, e info circular
305
- use_circular: override p/ ativar/desativar CircularReasoning no forward
306
- (default: usa self.config.use_circular_reasoning)
307
-
308
- Returns:
309
- (y_hat, delta) por padrão.
310
- (y_hat, delta, aux_dict) se return_aux=True.
311
-
312
- Fluxo completo (V3):
313
- x → embedding → [L1: module_selector α] → K×u8cell_T → OrqCell → o
314
- → [CRW: Φ^k(o) por n_cycles] → o* (contração W₂)
315
- → TrainT(o*) = y_hat
316
- → HypT(o*, stop_grad) = delta (se use_hypothesis)
317
- """
318
- # Embedding se necessário
319
- if x.dim() == 2:
320
- # x: (batch, T) token IDs → (batch, T, d_model)
321
- x = self.token_embedding(x)
322
- x = self.positional_encoding(x)
323
- x = self.emb_dropout(x)
324
- # Agora x: (batch, T, input_dim)
325
-
326
- # V5: do_circular computed early (was previously duplicated below)
327
- do_circular = (
328
- use_circular if use_circular is not None
329
- else self.config.use_circular_reasoning
330
- )
331
-
332
- # Lema 1: seleciona módulos via softmax com temperatura
333
- alpha, entropy_reg = self.module_selector(temperature=temperature)
334
-
335
- # Calcula saídas de todos os módulos e pondera por alpha
336
- # V4: coleta métricas de diversificação BiGRU (paralelismo real)
337
- H_list = []
338
- H_unweighted = [] # V4: saídas h_k SEM ponderar, para diversidade INTER-módulos
339
- bigru_metrics_per_module = [] # lista de dicts (1 por u8cell_T)
340
- for k, cell in enumerate(self.u8cells):
341
- if return_aux:
342
- # V4: forward com return_aux para capturar diversidade BiGRU
343
- h_k, bigru_aux = cell(x, return_aux=True)
344
- bigru_metrics_per_module.append(bigru_aux)
345
- else:
346
- h_k = cell(x) # (batch, output_dim_u8cell)
347
- H_unweighted.append(h_k)
348
- h_k_weighted = h_k * alpha[k]
349
- H_list.append(h_k_weighted)
350
- H = torch.stack(H_list, dim=1) # (batch, max_modules, output_dim_u8cell)
351
- # V4: stack das saídas não-ponderadas para diversidade INTER-módulos
352
- H_unweighted_stack = torch.stack(H_unweighted, dim=1) # (batch, K, output_dim_u8cell)
353
-
354
- # Orquestrador: produz representação agregada
355
- o = self.orq_cell(H) # (batch, d_cache)
356
-
357
- # Circular Reasoning (Teorema 4): refine 'o' via contração W₂
358
- circular_info = None
359
- # do_circular já foi computado no início do forward (V5)
360
- if do_circular and self.circular_reasoning is not None:
361
- # CRW aceita (B, D) ou (B, T, D); passamos (B, D) — LayerNorm normaliza última dim
362
- crw_out = self.circular_reasoning(o)
363
- o = crw_out["output"] # representação refinada s*
364
- circular_info = {
365
- "converged": crw_out["converged"],
366
- "w2_final": crw_out["w2_final"],
367
- "n_cycles_used": crw_out["n_cycles_used"],
368
- "converged_step": crw_out["converged_step"],
369
- "contraction_valid": crw_out["contraction_valid"],
370
- "q_empirical_avg": (
371
- sum(crw_out["q_empirical"]) / max(1, len(crw_out["q_empirical"]))
372
- if crw_out["q_empirical"] else 1.0
373
- ),
374
- }
375
-
376
- # Predição principal
377
- y_hat_main = self.train_T(o) # (batch, vocab_size)
378
-
379
- # Predição de hipótese (se desejado)
380
- if use_hypothesis:
381
- delta = self.hyp_T(o, stop_grad=stop_grad_hyp)
382
- else:
383
- delta = torch.zeros_like(y_hat_main)
384
-
385
- if return_aux:
386
- # V4: agrega métricas BiGRU de todos os K u8cell_T módulos
387
- bigru_diversity_summary = self._aggregate_bigru_diversity(
388
- bigru_metrics_per_module, alpha, H_unweighted_stack
389
- )
390
- aux = {
391
- "entropy_reg": entropy_reg,
392
- "alpha": alpha.detach(),
393
- "o": o.detach(),
394
- "circular_info": circular_info,
395
- "bigru_diversity": bigru_diversity_summary, # V4
396
- }
397
- return y_hat_main, delta, aux
398
- return y_hat_main, delta
399
-
400
- def _aggregate_bigru_diversity(
401
- self,
402
- metrics_per_module: list[dict],
403
- alpha: torch.Tensor,
404
- H_unweighted: torch.Tensor,
405
- ) -> dict:
406
- """Agrega métricas BiGRU de todos os K u8cell_T módulos.
407
-
408
- Args:
409
- metrics_per_module: lista de K dicts (cada um = saída de u8cell_T._compute_bigru_diversity_metrics)
410
- alpha: (K,) — pesos do ModuleSelector
411
- H_unweighted: (batch, K, output_dim_u8cell) — saídas h_k SEM ponderar
412
-
413
- Returns:
414
- dict com médias agregadas sobre os K módulos (ponderadas por alpha):
415
- - mean_cosine_diversity: diversidade média inter-BiGRU (ponderada por alpha)
416
- - mean_gate_norm_ratio: variação média das normas
417
- - mean_activation_variance: variância média
418
- - mean_parallelism_score: score médio de paralelismo
419
- - module_level_cosine_diversity: 1 - mean(cos_sim entre h_k dos K módulos)
420
- (diversidade INTER-módulos, não intra-módulo)
421
- - per_module_summary: lista de K dicts com métricas essenciais
422
- """
423
- if not metrics_per_module:
424
- return {
425
- "mean_cosine_diversity": 0.0,
426
- "mean_gate_norm_ratio": 0.0,
427
- "mean_activation_variance": 0.0,
428
- "mean_parallelism_score": 0.0,
429
- "module_level_cosine_diversity": 0.0,
430
- "per_module_summary": [],
431
- }
432
-
433
- # Ponderar por alpha_k (módulos mais ativos pesam mais)
434
- alpha_np = alpha.detach().cpu().numpy()
435
- alpha_norm = alpha_np / (alpha_np.sum() + 1e-8)
436
-
437
- cd_list = [m["cosine_diversity"] for m in metrics_per_module]
438
- gnr_list = [m["gate_norm_ratio"] for m in metrics_per_module]
439
- av_list = [m["activation_variance"] for m in metrics_per_module]
440
- ps_list = [m["parallelism_score"] for m in metrics_per_module]
441
-
442
- mean_cd = sum(w * v for w, v in zip(alpha_norm, cd_list))
443
- mean_gnr = sum(w * v for w, v in zip(alpha_norm, gnr_list))
444
- mean_av = sum(w * v for w, v in zip(alpha_norm, av_list))
445
- mean_ps = sum(w * v for w, v in zip(alpha_norm, ps_list))
446
-
447
- # V4: Diversidade INTER-módulos usando H_unweighted (batch, K, output_dim_u8cell)
448
- # Média sobre batch → (K, output_dim_u8cell), depois cosine sim entre pares
449
- if H_unweighted is not None and H_unweighted.size(1) >= 2:
450
- module_reps = H_unweighted.detach().mean(dim=0) # (K, output_dim_u8cell)
451
- K = module_reps.size(0)
452
- inter_cos_sims = []
453
- for i in range(K):
454
- for j in range(i + 1, K):
455
- cs = F.cosine_similarity(
456
- module_reps[i].unsqueeze(0),
457
- module_reps[j].unsqueeze(0),
458
- dim=-1,
459
- )
460
- inter_cos_sims.append(float(cs.item()))
461
- mean_inter_cos = sum(inter_cos_sims) / len(inter_cos_sims)
462
- module_level_cd = 1.0 - mean_inter_cos
463
- else:
464
- module_level_cd = 0.0
465
-
466
- return {
467
- "mean_cosine_diversity": float(mean_cd),
468
- "mean_gate_norm_ratio": float(mean_gnr),
469
- "mean_activation_variance": float(mean_av),
470
- "mean_parallelism_score": float(mean_ps),
471
- "module_level_cosine_diversity": float(module_level_cd),
472
- "per_module_summary": [
473
- {
474
- "cosine_diversity": cd_list[i],
475
- "gate_norm_ratio": gnr_list[i],
476
- "parallelism_score": ps_list[i],
477
- "per_bigru_norms": metrics_per_module[i]["per_bigru_norms"],
478
- }
479
- for i in range(len(metrics_per_module))
480
- ],
481
- }
482
-
483
- def get_active_modules(self, threshold: float = 0.01) -> int:
484
- """Retorna nº efetivo de módulos ativos (para auto-configuração)."""
485
- return self.module_selector.get_active_modules(threshold)
486
-
487
- # =====================================================================
488
- # V5: Propriedades para parâmetros aprendíveis (auto-ajuste do modelo)
489
- # =====================================================================
490
- @property
491
- def lambda_ent_value(self) -> float:
492
- """λ_ent atual = exp(log_lambda_ent) — peso da reg. de entropia."""
493
- return float(torch.exp(self.log_lambda_ent.detach()).item())
494
-
495
- @property
496
- def init_gate_value(self) -> float:
497
- """init_gate atual = mean(circular_reasoning.residual_gate).
498
-
499
- V5: O gate do CircularReasoning é Parameter interno (n_cycles params).
500
- O MetaConfigurator gerencia esse Parameter diretamente.
501
- """
502
- if self.circular_reasoning is not None:
503
- return float(self.circular_reasoning.residual_gate.detach().mean().item())
504
- return 0.0
505
-
506
- def count_parameters(self) -> dict:
507
- """Conta parâmetros totais e treináveis."""
508
- total = sum(p.numel() for p in self.parameters())
509
- trainable = sum(p.numel() for p in self.parameters() if p.requires_grad)
510
- return {
511
- "total": total,
512
- "total_M": total / 1e6,
513
- "trainable": trainable,
514
- "trainable_M": trainable / 1e6,
515
- "n_w8a8_layers": getattr(self, "_n_w8a8_layers", 0),
516
- }
517
-
518
-
519
- def create_unified_model(config: Optional[UnifiedModelConfig] = None) -> tuple[UnifiedModel, UnifiedModelConfig]:
520
- """Factory: cria UnifiedModel e retorna (model, config)."""
521
- cfg = config or UnifiedModelConfig()
522
- model = UnifiedModel(cfg)
523
- return model, cfg