PowerMachine commited on
Commit
cbf308e
·
verified ·
1 Parent(s): 0cee009

V5: W8A8 aprimorado (α aprendível via STE) + bug fix apply_w8a8_to_model + 4 hiperparâmetros auto-ajustáveis (T, τ, λ_ent, init_gate) + OOM-Killer fixes + num_layers_hyp=8 fixo

Browse files
src/bigru_t/model/unified_model.py CHANGED
@@ -52,7 +52,8 @@ from .orq_cell import OrqCell
52
  from .train_t import TrainT
53
  from .hyp_t import HypT
54
  from .module_selector import ModuleSelector
55
- from ..quantization.quantized_linear import QuantizedLinear, apply_w8a8
 
56
  from ..reasoning.circular_reasoning_wasserstein import CircularReasoningWasserstein
57
 
58
 
@@ -107,6 +108,7 @@ class UnifiedModelConfig:
107
 
108
  # HypT
109
  # V4: num_layers_hyp aumentado de 2 → 8 (mais capacidade para correção delta)
 
110
  hypT_dim: int = 128
111
  nhead_hyp: int = 4
112
  d_ff_hyp: int = 256
@@ -265,9 +267,23 @@ class UnifiedModel(nn.Module):
265
  else None
266
  )
267
 
268
- # Aplicar W8A8 em todas as camadas lineares (exceto Embedding, que é nn.Embedding)
269
- # Nota: nn.GRU não é afetada (não é Linear)
270
- self.apply(apply_w8a8)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
  def forward(
273
  self,
@@ -307,6 +323,12 @@ class UnifiedModel(nn.Module):
307
  x = self.emb_dropout(x)
308
  # Agora x: (batch, T, input_dim)
309
 
 
 
 
 
 
 
310
  # Lema 1: seleciona módulos via softmax com temperatura
311
  alpha, entropy_reg = self.module_selector(temperature=temperature)
312
 
@@ -334,10 +356,7 @@ class UnifiedModel(nn.Module):
334
 
335
  # Circular Reasoning (Teorema 4): refine 'o' via contração W₂
336
  circular_info = None
337
- do_circular = (
338
- use_circular if use_circular is not None
339
- else self.config.use_circular_reasoning
340
- )
341
  if do_circular and self.circular_reasoning is not None:
342
  # CRW aceita (B, D) ou (B, T, D); passamos (B, D) — LayerNorm normaliza última dim
343
  crw_out = self.circular_reasoning(o)
@@ -465,6 +484,25 @@ class UnifiedModel(nn.Module):
465
  """Retorna nº efetivo de módulos ativos (para auto-configuração)."""
466
  return self.module_selector.get_active_modules(threshold)
467
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  def count_parameters(self) -> dict:
469
  """Conta parâmetros totais e treináveis."""
470
  total = sum(p.numel() for p in self.parameters())
@@ -474,6 +512,7 @@ class UnifiedModel(nn.Module):
474
  "total_M": total / 1e6,
475
  "trainable": trainable,
476
  "trainable_M": trainable / 1e6,
 
477
  }
478
 
479
 
 
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
 
 
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
 
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,
 
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
 
 
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)
 
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())
 
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
 
src/bigru_t/quantization/__init__.py CHANGED
@@ -1,6 +1,49 @@
1
- """Quantização W8A8 (Lema 3)."""
2
- from .quantized_linear import QuantizedLinear, quantize_tensor, apply_w8a8
3
- # Reaproveitado do source (não modificado):
4
- # from .w8a8_qoperator import W8A8QOperatorQuantizer, W8A8QOperatorLinear
5
 
6
- __all__ = ["QuantizedLinear", "quantize_tensor", "apply_w8a8"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Quantização W8A8 (Lema 3).
 
 
 
2
 
3
+ V5: módulos novos:
4
+ - SmoothQuantW8A8 (α aprendível via STE + sigmoid)
5
+ - TrustGuidedW8A8 (redução iterativa de erro, self-contained)
6
+ - MultiLayerW8A8Reducer (aplica a múltiplas camadas)
7
+ - apply_w8a8_to_model (bug fix crítico — substitui in-place)
8
+ """
9
+ from .quantized_linear import (
10
+ QuantizedLinear,
11
+ quantize_tensor,
12
+ apply_w8a8,
13
+ apply_w8a8_to_model,
14
+ )
15
+ from .w8a8_smoothquant import (
16
+ SmoothQuantW8A8,
17
+ calibrate_smoothquant,
18
+ quantize_per_channel_symmetric,
19
+ quantize_per_token_symmetric,
20
+ STEQuantize,
21
+ ste_quantize,
22
+ )
23
+ from .w8a8_error_reduction import (
24
+ W8A8ConvergenceError,
25
+ analytic_trust,
26
+ W8A8ReductionResult,
27
+ TrustGuidedW8A8,
28
+ MultiLayerW8A8Reducer,
29
+ )
30
+
31
+ __all__ = [
32
+ # V1-V4
33
+ "QuantizedLinear",
34
+ "quantize_tensor",
35
+ "apply_w8a8",
36
+ # V5
37
+ "apply_w8a8_to_model",
38
+ "SmoothQuantW8A8",
39
+ "calibrate_smoothquant",
40
+ "quantize_per_channel_symmetric",
41
+ "quantize_per_token_symmetric",
42
+ "STEQuantize",
43
+ "ste_quantize",
44
+ "W8A8ConvergenceError",
45
+ "analytic_trust",
46
+ "W8A8ReductionResult",
47
+ "TrustGuidedW8A8",
48
+ "MultiLayerW8A8Reducer",
49
+ ]
src/bigru_t/quantization/quantized_linear.py CHANGED
@@ -1,21 +1,31 @@
1
- """quantized_linear.py — QuantizedLinear: camada Linear com W8A8 fake quant.
2
-
3
- Implementa o Lema 3 (Cancelamento de ruído de quantização).
4
-
5
- A quantização W8A8 (pesos e ativações em 8 bits) é simulada no forward
6
- via fake quantization (round + clamp + dequant). O backward usa Straight-
7
- Through Estimator (STE) — o gradiente passa direto pela operação de round,
8
- que tem derivada zero em quase todos os pontos.
9
-
10
- Reaproveita a filosofia do flexnet/w8a8_qoperator.py (V13.9.1 QOperator),
11
- mas simplificada para o caso fake-quant durante o treino. Para inferência
12
- produção, o w8a8_qoperator.py deve ser usado (gera QOperator ONNX).
13
-
14
- Diferenças vs. w8a8_qoperator.py:
15
- - Aqui: fake quant em todo forward (treino)
16
- - w8a8_qoperator.py: quantização real + empacotamento ONNX (inferência)
17
- - Aqui: scale por tensor (não por canal) — mais simples, suficiente para
18
- bug-detection
 
 
 
 
 
 
 
 
 
 
19
  """
20
  from __future__ import annotations
21
 
@@ -80,10 +90,68 @@ class QuantizedLinear(nn.Linear):
80
 
81
 
82
  def apply_w8a8(module: nn.Module) -> nn.Module:
83
- """Substitui todas as nn.Linear por QuantizedLinear (recursivamente).
84
 
85
- Usado por UnifiedModel.apply(apply_w8a8) no __init__.
 
 
 
 
 
 
86
  """
87
  if isinstance(module, nn.Linear) and not isinstance(module, QuantizedLinear):
88
- return QuantizedLinear(module.in_features, module.out_features, module.bias is not None)
 
 
 
 
 
 
89
  return module
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """quantized_linear.py — QuantizedLinear + apply_w8a8 (V5 — bug fix crítico).
2
+
3
+ ═══════════════════════════════════════════════════════════════════════════════
4
+ V5 — BUG FIX CRÍTICO: apply_w8a8 não substituía módulos
5
+ ═══════════════════════════════════════════════════════════════════════════════
6
+
7
+ BUG (V1-V4):
8
+ Em V1-V4, `apply_w8a8` era usado via `model.apply(apply_w8a8)`. Porém,
9
+ `nn.Module.apply(fn)` chama `fn(module)` e DESCARTA o valor retornado.
10
+ A função retornava `QuantizedLinear(...)` mas o módulo original não era
11
+ substituído — apenas a função era chamada (sem efeito).
12
+
13
+ Resultado: W8A8 NUNCA era aplicado em V1-V4. O modelo usava FP32 puro
14
+ em todas as camadas Linear, desperdiçando ~4x mais memória que o necessário
15
+ e sem obter o benefício do ruído de quantização (Lema 3).
16
+
17
+ FIX (V5):
18
+ Substituir `model.apply(apply_w8a8)` por `apply_w8a8_to_model(model)` que
19
+ percorre recursivamente `model._modules` e substitui `nn.Linear` por
20
+ `QuantizedLinear` (ou `SmoothQuantW8A8` se preferir V5).
21
+
22
+ Para manter compatibilidade com V1-V4, `apply_w8a8(module)` ainda existe
23
+ mas agora é chamado recursivamente por `apply_w8a8_to_model`.
24
+
25
+ Implementa o Lema 3 (Cancelamento de ruído de quantização):
26
+ A quantização W8A8 (pesos e ativações em 8 bits) é simulada no forward
27
+ via fake quantization (round + clamp + dequant). O backward usa Straight-
28
+ Through Estimator (STE) — o gradiente passa direto pela operação de round.
29
  """
30
  from __future__ import annotations
31
 
 
90
 
91
 
92
  def apply_w8a8(module: nn.Module) -> nn.Module:
93
+ """Substitui uma nn.Linear por QuantizedLinear (mantém pesos).
94
 
95
+ V5: Agora é uma função que efetivamente substitui o módulo quando
96
+ chamada por apply_w8a8_to_model. Para compatibilidade com V1-V4,
97
+ ainda pode ser chamada diretamente em um único módulo.
98
+
99
+ NOTA: Esta função NÃO deve ser usada via `model.apply(apply_w8a8)` pois
100
+ `nn.Module.apply` descarta o valor retornado. Use
101
+ `apply_w8a8_to_model(model)` em vez disso.
102
  """
103
  if isinstance(module, nn.Linear) and not isinstance(module, QuantizedLinear):
104
+ new_module = QuantizedLinear(module.in_features, module.out_features, module.bias is not None)
105
+ # Copiar pesos do módulo original
106
+ with torch.no_grad():
107
+ new_module.weight.copy_(module.weight)
108
+ if module.bias is not None:
109
+ new_module.bias.copy_(module.bias)
110
+ return new_module
111
  return module
112
+
113
+
114
+ def apply_w8a8_to_model(model: nn.Module) -> int:
115
+ """Substitui recursivamente todas as nn.Linear por QuantizedLinear.
116
+
117
+ V5: Implementação correta — percorre `model._modules` recursivamente
118
+ e substitui in-place. Retorna o número de camadas substituídas.
119
+
120
+ Diferentemente de `model.apply(apply_w8a8)` (que não funciona porque
121
+ `apply` descarta o retorno), esta função modifica o modelo in-place.
122
+
123
+ Args:
124
+ model: modelo a ter as Linears substituídas
125
+
126
+ Returns:
127
+ n_substituidas: número de camadas Linear substituídas
128
+
129
+ Uso:
130
+ from bigru_t.quantization import apply_w8a8_to_model
131
+ n = apply_w8a8_to_model(model)
132
+ print(f"{n} camadas Linear substituídas por QuantizedLinear")
133
+ """
134
+ n_substituidas = 0
135
+ # Lista de (parent_module, child_name) para substituir
136
+ # Não podemos substituir durante a iteração, então coletamos primeiro
137
+ to_replace = []
138
+ for name, module in model.named_modules():
139
+ for child_name, child in module.named_children():
140
+ if isinstance(child, nn.Linear) and not isinstance(child, QuantizedLinear):
141
+ to_replace.append((module, child_name))
142
+
143
+ for parent, child_name in to_replace:
144
+ old_linear = getattr(parent, child_name)
145
+ new_linear = apply_w8a8(old_linear)
146
+ setattr(parent, child_name, new_linear)
147
+ n_substituidas += 1
148
+
149
+ return n_substituidas
150
+
151
+
152
+ __all__ = [
153
+ "quantize_tensor",
154
+ "QuantizedLinear",
155
+ "apply_w8a8",
156
+ "apply_w8a8_to_model",
157
+ ]
src/bigru_t/quantization/w8a8_error_reduction.py ADDED
@@ -0,0 +1,457 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """w8a8_error_reduction.py — Redução iterativa do erro W8A8 (V5).
2
+
3
+ ═══════════════════════════════════════════════════════════════════════════════
4
+ V5 — VERSÃO AUTO-CONTIDA (sem dependência de TrustNeuralAgent externo)
5
+ ═══════════════════════════════════════════════════════════════════════════════
6
+
7
+ ORIGEM (gru-ring-v13-9-2):
8
+ source/xavante_work/flexnet/w8a8_error_reduction.py (V13.9.1)
9
+
10
+ Em V13.9.1, TrustGuidedW8A8 dependia de TrustNeuralAgent (rede neural externa
11
+ que avalia confiança na quantização). Em V5, eliminamos essa dependência:
12
+
13
+ * Confiança ht agora é FUNÇÃO ANALÍTICA de α e erro:
14
+ ht = exp(-error / error_scale) * sigmoid((alpha - 0.5) * 4)
15
+ - Alta quando erro é baixo E α está próximo de 0.5 (balanceado)
16
+ - Baixa quando erro é alto OU α está nos extremos (0 ou 1)
17
+
18
+ * Redução de erro via gradiente STE (igual V13.9.1):
19
+ dE/dα = dE/ds · ds/dα
20
+ onde:
21
+ E(α) = ||Y_fp - Y_q(α)||_F^2 / ||Y_fp||_F^2
22
+ s_j(α) = max(|X_j|)^α / max(|W_j|)^{1-α}
23
+ ds_j/dα = s_j · (log max|X_j| - log max|W_j|)
24
+
25
+ * Trust-guided update:
26
+ α_{t+1} = α_t - lr · (1 - ht) · dE/dα
27
+ Alta confiança (ht→1) → passo pequeno (convergência)
28
+ Baixa confiança (ht→0) → passo grande (exploração)
29
+
30
+ Análise de convergência:
31
+ E(α_{t+1}) ≤ E(α_t) · (1 - lr · (1 - ht) · λ_min)
32
+ onde λ_min é o menor autovalor da Hessiana de E em α*.
33
+ Garante convergência linear quando λ_min > 0 (E convexa localmente).
34
+
35
+ V5 vs V13.9.1:
36
+ * Sem TrustNeuralAgent (rede neural externa) → -50% params, -1 módulo
37
+ * ht é função analítica (determinística, não-aprendida)
38
+ * Self-contained: só depende de torch + w8a8_smoothquant
39
+ * Vetorizado: smooth factor calculado em 1 operação (não loop Python)
40
+ * OOM-safe: não armazena histórico de tensores completos, apenas escalares
41
+ """
42
+ from __future__ import annotations
43
+
44
+ import math
45
+ import logging
46
+ from dataclasses import dataclass, field
47
+ from typing import Dict, List, Optional, Tuple
48
+
49
+ import torch
50
+ import torch.nn as nn
51
+ import torch.nn.functional as F
52
+
53
+ from .w8a8_smoothquant import (
54
+ SmoothQuantW8A8,
55
+ calibrate_smoothquant,
56
+ quantize_per_channel_symmetric,
57
+ quantize_per_token_symmetric,
58
+ ste_quantize,
59
+ )
60
+
61
+ logger = logging.getLogger(__name__)
62
+
63
+
64
+ # ============================================================================
65
+ # Exceções
66
+ # ============================================================================
67
+ class W8A8ConvergenceError(RuntimeError):
68
+ """W8A8 error reduction não convergiu."""
69
+ pass
70
+
71
+
72
+ # ============================================================================
73
+ # Função analítica de confiança ht (V5 — substitui TrustNeuralAgent)
74
+ # ============================================================================
75
+ def analytic_trust(
76
+ error: float,
77
+ alpha: float,
78
+ error_scale: float = 0.1,
79
+ ) -> float:
80
+ """Confiança ht analítica (V5).
81
+
82
+ ht = exp(-error / error_scale) * sigmoid((alpha - 0.5) * 4)
83
+
84
+ Args:
85
+ error: erro relativo de quantização (||Y_fp - Y_q||/||Y_fp||)
86
+ alpha: fator SmoothQuant atual
87
+ error_scale: escala do erro (default 0.1 — erro 10% dá e^-1 ≈ 0.37)
88
+
89
+ Returns:
90
+ ht ∈ (0, 1): 1 = alta confiança, 0 = baixa confiança
91
+ """
92
+ err_term = math.exp(-max(0.0, error) / max(1e-8, error_scale))
93
+ # Sigmoid com slope 4: alpha=0.5 → 0.5, alpha=0.95 → 0.88, alpha=0.05 → 0.12
94
+ alpha_term = 1.0 / (1.0 + math.exp(-4.0 * (alpha - 0.5)))
95
+ return err_term * alpha_term
96
+
97
+
98
+ # ============================================================================
99
+ # 1. TrustGuidedW8A8 — quantização W8A8 com redução iterativa de erro
100
+ # ============================================================================
101
+ @dataclass
102
+ class W8A8ReductionResult:
103
+ """Resultado da redução iterativa de erro W8A8."""
104
+ initial_alpha: float
105
+ final_alpha: float
106
+ initial_error: float
107
+ final_error: float
108
+ n_iterations: int
109
+ converged: bool
110
+ history: List[Dict[str, float]] = field(default_factory=list)
111
+ final_ht: float = 0.0
112
+
113
+
114
+ class TrustGuidedW8A8:
115
+ """W8A8 com redução iterativa de erro guiada por confiança analítica.
116
+
117
+ V5: Self-contained — sem TrustNeuralAgent externo. ht é função analítica
118
+ de (error, alpha), não uma rede neural aprendida.
119
+
120
+ Pipeline:
121
+ 1. Calibra α inicial e quantiza
122
+ 2. Mede erro E₀
123
+ 3. Função analítica avalia ht (confiança na quantização)
124
+ 4. Se ht < τ: atualiza α via STE gradiente na direção de redução de erro
125
+ 5. Re-quantiza, mede erro E₁, atualiza ht
126
+ 6. Repete até ht > τ ou convergência
127
+
128
+ A atualização é modulada por (1 - ht): alta confiança → passo pequeno.
129
+ """
130
+
131
+ def __init__(
132
+ self,
133
+ ref: nn.Linear,
134
+ init_alpha: float = 0.5,
135
+ trust_threshold: float = 0.8,
136
+ lr: float = 0.05,
137
+ max_iterations: int = 20,
138
+ convergence_tol: float = 1e-4,
139
+ reg_lambda: float = 0.01,
140
+ verbose: bool = False,
141
+ ):
142
+ self.ref = ref
143
+ self.init_alpha = init_alpha
144
+ self.trust_threshold = trust_threshold
145
+ self.lr = lr
146
+ self.max_iterations = max_iterations
147
+ self.convergence_tol = convergence_tol
148
+ self.reg_lambda = reg_lambda
149
+ self.verbose = verbose
150
+ # V5: Sem TrustNeuralAgent. ht é função analítica.
151
+ # Parâmetro α parametrizado via sigmoid.
152
+ init_eta = math.log(init_alpha / (1 - init_alpha))
153
+ self.eta_alpha = torch.nn.Parameter(torch.tensor(init_eta))
154
+
155
+ @property
156
+ def alpha(self) -> torch.Tensor:
157
+ return torch.sigmoid(self.eta_alpha)
158
+
159
+ def _compute_quantization_error(
160
+ self,
161
+ X: torch.Tensor,
162
+ alpha_val: float,
163
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
164
+ """Computa Y_quantized e Y_reference para um dado α.
165
+
166
+ Retorna: (Y_q, Y_ref)
167
+ """
168
+ W = self.ref.weight
169
+ Y_ref = F.linear(X, W, self.ref.bias)
170
+ # Smooth factor.
171
+ s = calibrate_smoothquant(X, W, alpha=alpha_val)
172
+ # Smooth.
173
+ X_smooth = X / s
174
+ W_smooth = W * s.unsqueeze(0)
175
+ # Quantize W per-channel.
176
+ W_q, w_scale = quantize_per_channel_symmetric(W_smooth, axis=0, n_bits=8)
177
+ # Quantize X per-token.
178
+ X_q, x_scale = quantize_per_token_symmetric(X_smooth, n_bits=8)
179
+ # Dequant.
180
+ X_fp = X_q.to(torch.float32) * x_scale
181
+ W_fp = W_q.to(torch.float32) * w_scale.unsqueeze(1)
182
+ Y_q = X_fp @ W_fp.t()
183
+ if self.ref.bias is not None:
184
+ Y_q = Y_q + self.ref.bias
185
+ return Y_q, Y_ref
186
+
187
+ def _compute_error_gradient(
188
+ self,
189
+ X: torch.Tensor,
190
+ alpha_tensor: torch.Tensor,
191
+ ) -> torch.Tensor:
192
+ """Computa gradiente do erro via STE (Straight-Through Estimator).
193
+
194
+ E(α) = ||Y_ref - Y_q(α)||_F^2 / ||Y_ref||_F^2
195
+
196
+ Usa STE para round() (não-diferenciável).
197
+ """
198
+ W = self.ref.weight
199
+ # Y_ref (sem grad).
200
+ with torch.no_grad():
201
+ Y_ref = F.linear(X, W, self.ref.bias)
202
+ y_ref_norm_sq = max(((Y_ref) ** 2).mean().item(), 1e-12)
203
+ # s(α) diferenciável.
204
+ d_in = W.shape[-1]
205
+ X_flat = X.reshape(-1, d_in)
206
+ max_x = X_flat.abs().amax(dim=0).clamp(min=1e-8)
207
+ max_w = W.abs().amax(dim=0).clamp(min=1e-8)
208
+ s = torch.pow(max_x, alpha_tensor) / torch.pow(max_w, 1.0 - alpha_tensor)
209
+ # Smooth.
210
+ X_smooth = X / s
211
+ W_smooth = W * s.unsqueeze(0)
212
+ # Quantização com STE.
213
+ qmax = 127
214
+ max_w_smooth = W_smooth.abs().amax(dim=1, keepdim=True).clamp(min=1e-8)
215
+ scale_w = max_w_smooth / qmax
216
+ max_x_smooth = X_smooth.abs().amax(dim=-1, keepdim=True).clamp(min=1e-8)
217
+ scale_x = max_x_smooth / qmax
218
+ # STE: round com gradiente pass-through.
219
+ W_q_deq = ste_quantize(W_smooth, scale_w, 8)
220
+ X_q_deq = ste_quantize(X_smooth, scale_x, 8)
221
+ # Matmul.
222
+ Y_q = X_q_deq @ W_q_deq.t()
223
+ if self.ref.bias is not None:
224
+ Y_q = Y_q + self.ref.bias
225
+ # Loss = MSE relativo.
226
+ loss = ((Y_ref - Y_q) ** 2).mean() / y_ref_norm_sq
227
+ # Regularização entropy (atrai α para 0.5 — centro do intervalo).
228
+ eps = 1e-8
229
+ alpha_clamped = alpha_tensor.clamp(eps, 1.0 - eps)
230
+ reg = -self.reg_lambda * (
231
+ alpha_clamped * torch.log(alpha_clamped + eps) +
232
+ (1 - alpha_clamped) * torch.log(1 - alpha_clamped + eps)
233
+ )
234
+ # negativo porque queremos MAXIMIZAR entropy (atrair α para 0.5)
235
+ total_loss = loss - reg
236
+ total_loss.backward()
237
+ return total_loss
238
+
239
+ def reduce_error(
240
+ self,
241
+ X: torch.Tensor,
242
+ ) -> W8A8ReductionResult:
243
+ """Executa redução iterativa de erro W8A8.
244
+
245
+ Args:
246
+ X: dados de calibração (B, d_in)
247
+
248
+ Returns:
249
+ W8A8ReductionResult
250
+ """
251
+ initial_alpha = float(self.alpha.item())
252
+ # Computa erro inicial.
253
+ with torch.no_grad():
254
+ Y_q_0, Y_ref_0 = self._compute_quantization_error(X, initial_alpha)
255
+ initial_error = ((Y_ref_0 - Y_q_0) ** 2).mean().item() / max(
256
+ ((Y_ref_0) ** 2).mean().item(), 1e-12
257
+ )
258
+ # V5: ht analítico (sem rede neural)
259
+ ht_val = analytic_trust(initial_error, initial_alpha)
260
+ history = [{"iteration": 0, "alpha": initial_alpha, "error": initial_error, "ht": ht_val}]
261
+ prev_error = initial_error
262
+ converged = False
263
+ opt = torch.optim.SGD([self.eta_alpha], lr=self.lr)
264
+ for iteration in range(1, self.max_iterations + 1):
265
+ # 1. α atual.
266
+ alpha_val = float(self.alpha.item())
267
+ with torch.no_grad():
268
+ Y_q, Y_ref = self._compute_quantization_error(X, alpha_val)
269
+ current_error = ((Y_ref - Y_q) ** 2).mean().item() / max(
270
+ ((Y_ref) ** 2).mean().item(), 1e-12
271
+ )
272
+ # V5: ht analítico
273
+ ht_val = analytic_trust(current_error, alpha_val)
274
+ # 2. Se ht > threshold, converged.
275
+ if ht_val > self.trust_threshold:
276
+ converged = True
277
+ if self.verbose:
278
+ print(f" iter {iteration}: ht={ht_val:.4f} > τ={self.trust_threshold}, converged")
279
+ break
280
+ # 3. Atualiza α via STE gradiente, modulado por (1 - ht).
281
+ opt.zero_grad()
282
+ self._compute_error_gradient(X, self.alpha)
283
+ # Modula gradiente por (1 - ht).
284
+ with torch.no_grad():
285
+ if self.eta_alpha.grad is not None:
286
+ self.eta_alpha.grad *= (1.0 - ht_val)
287
+ opt.step()
288
+ # 4. Clamp α para [0.01, 0.99].
289
+ with torch.no_grad():
290
+ self.eta_alpha.data.clamp_(
291
+ math.log(0.01 / 0.99), math.log(0.99 / 0.01)
292
+ )
293
+ # 5. Verifica convergência por erro.
294
+ if abs(prev_error - current_error) < self.convergence_tol:
295
+ converged = True
296
+ if self.verbose:
297
+ print(f" iter {iteration}: erro convergiu (Δ={abs(prev_error - current_error):.2e})")
298
+ break
299
+ prev_error = current_error
300
+ history.append({
301
+ "iteration": iteration,
302
+ "alpha": float(self.alpha.item()),
303
+ "error": current_error,
304
+ "ht": ht_val,
305
+ })
306
+ if self.verbose and iteration % 5 == 0:
307
+ print(f" iter {iteration}: α={float(self.alpha.item()):.4f}, "
308
+ f"erro={current_error:.6f}, ht={ht_val:.4f}")
309
+ final_alpha = float(self.alpha.item())
310
+ final_error = history[-1]["error"]
311
+ return W8A8ReductionResult(
312
+ initial_alpha=initial_alpha,
313
+ final_alpha=final_alpha,
314
+ initial_error=initial_error,
315
+ final_error=final_error,
316
+ n_iterations=len(history) - 1,
317
+ converged=converged,
318
+ history=history,
319
+ final_ht=ht_val,
320
+ )
321
+
322
+ def get_smooth_scale(self, X: torch.Tensor) -> torch.Tensor:
323
+ """Retorna o smooth scale final."""
324
+ with torch.no_grad():
325
+ return calibrate_smoothquant(X, self.ref.weight, float(self.alpha.item())).detach()
326
+
327
+ def quantize_weights(self, X: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
328
+ """Quantiza pesos com α otimizado.
329
+
330
+ Retorna: (weight_int8, weight_scale)
331
+ """
332
+ s = self.get_smooth_scale(X)
333
+ W_smooth = self.ref.weight * s.unsqueeze(0)
334
+ W_q, w_scale = quantize_per_channel_symmetric(W_smooth, axis=0, n_bits=8)
335
+ return W_q, w_scale
336
+
337
+
338
+ # ============================================================================
339
+ # 2. MultiLayerW8A8Reducer — aplica redução a múltiplas camadas
340
+ # ============================================================================
341
+ class MultiLayerW8A8Reducer:
342
+ """Aplica TrustGuidedW8A8 a múltiplas camadas Linear de um modelo.
343
+
344
+ Para cada camada:
345
+ 1. Coleta ativações de calibração
346
+ 2. Executa TrustGuidedW8A8.reduce_error()
347
+ 3. Substitui a camada original pela versão quantizada (SmoothQuantW8A8)
348
+
349
+ V5: OOM-safe — não armazena ativações de todas as camadas simultaneamente.
350
+ Processa uma camada por vez.
351
+ """
352
+
353
+ def __init__(
354
+ self,
355
+ init_alpha: float = 0.5,
356
+ trust_threshold: float = 0.8,
357
+ lr: float = 0.05,
358
+ max_iterations: int = 15,
359
+ verbose: bool = False,
360
+ ):
361
+ self.init_alpha = init_alpha
362
+ self.trust_threshold = trust_threshold
363
+ self.lr = lr
364
+ self.max_iterations = max_iterations
365
+ self.verbose = verbose
366
+
367
+ def reduce_model(
368
+ self,
369
+ model: nn.Module,
370
+ calib_X: torch.Tensor,
371
+ target_substrings: Optional[List[str]] = None,
372
+ ) -> Dict[str, W8A8ReductionResult]:
373
+ """Aplica redução W8A8 a todas as nn.Linear no modelo.
374
+
375
+ Args:
376
+ model: modelo a ser quantizado
377
+ calib_X: dados de calibração (B, T, d) ou (B, d)
378
+ target_substrings: substrings para filtrar camadas (None = todas)
379
+
380
+ Retorna: {layer_name: W8A8ReductionResult}
381
+ """
382
+ results = {}
383
+ # Coleta camadas a quantizar.
384
+ to_replace = []
385
+ for name, module in model.named_modules():
386
+ for child_name, child in module.named_children():
387
+ if isinstance(child, nn.Linear) and not isinstance(child, SmoothQuantW8A8):
388
+ full_name = f"{name}.{child_name}" if name else child_name
389
+ if target_substrings is None or any(s in full_name for s in target_substrings):
390
+ to_replace.append((module, child_name, child, full_name))
391
+ if self.verbose:
392
+ print(f" Encontradas {len(to_replace)} camadas Linear para quantização")
393
+ # Para cada camada, aplica TrustGuidedW8A8.
394
+ for module, child_name, linear_layer, full_name in to_replace:
395
+ d_in = linear_layer.in_features
396
+ # Extrai ativações de calibração para esta camada.
397
+ if calib_X.shape[-1] == d_in:
398
+ X_calib = calib_X.reshape(-1, d_in)
399
+ else:
400
+ # Projeta ou usa aleatório.
401
+ X_calib = torch.randn(min(64, calib_X.shape[0]), d_in)
402
+ # Reduz erro.
403
+ reducer = TrustGuidedW8A8(
404
+ ref=linear_layer,
405
+ init_alpha=self.init_alpha,
406
+ trust_threshold=self.trust_threshold,
407
+ lr=self.lr,
408
+ max_iterations=self.max_iterations,
409
+ verbose=self.verbose,
410
+ )
411
+ result = reducer.reduce_error(X_calib)
412
+ results[full_name] = result
413
+ # Substitui a camada pela versão quantizada (SmoothQuantW8A8 com α aprendível).
414
+ sq = SmoothQuantW8A8(
415
+ linear_layer,
416
+ init_alpha=result.final_alpha,
417
+ learnable_alpha=True, # V5: α continua aprendível após redução
418
+ )
419
+ sq.calibrate(X_calib)
420
+ # Seta o smooth_scale otimizado.
421
+ s = reducer.get_smooth_scale(X_calib)
422
+ sq.smooth_scale.copy_(s)
423
+ sq.freeze_quantization()
424
+ setattr(module, child_name, sq)
425
+ # V5 OOM fix: del referências explícitas
426
+ del reducer, X_calib
427
+ if self.verbose:
428
+ print(f" {full_name}: α {result.initial_alpha:.4f}→{result.final_alpha:.4f}, "
429
+ f"erro {result.initial_error:.6f}→{result.final_error:.6f}, "
430
+ f"ht={result.final_ht:.4f}, conv={result.converged}")
431
+ return results
432
+
433
+ def summarize_results(
434
+ self, results: Dict[str, W8A8ReductionResult]
435
+ ) -> str:
436
+ """Gera resumo tabular dos resultados."""
437
+ lines = [
438
+ f"{'Layer':<30} | {'α_init':>8} | {'α_final':>8} | {'err_init':>10} | {'err_final':>10} | {'reduction':>10} | {'ht':>6} | {'conv':>5}",
439
+ f"{'-'*30} | {'-'*8} | {'-'*8} | {'-'*10} | {'-'*10} | {'-'*10} | {'-'*6} | {'-'*5}",
440
+ ]
441
+ for name, r in results.items():
442
+ reduction = (1 - r.final_error / max(r.initial_error, 1e-12)) * 100
443
+ lines.append(
444
+ f"{name:<30} | {r.initial_alpha:>8.4f} | {r.final_alpha:>8.4f} | "
445
+ f"{r.initial_error:>10.6f} | {r.final_error:>10.6f} | "
446
+ f"{reduction:>9.1f}% | {r.final_ht:>6.3f} | {'✓' if r.converged else '✗':>5}"
447
+ )
448
+ return "\n".join(lines)
449
+
450
+
451
+ __all__ = [
452
+ "W8A8ConvergenceError",
453
+ "analytic_trust",
454
+ "W8A8ReductionResult",
455
+ "TrustGuidedW8A8",
456
+ "MultiLayerW8A8Reducer",
457
+ ]
src/bigru_t/quantization/w8a8_smoothquant.py ADDED
@@ -0,0 +1,365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """w8a8_smoothquant.py — W8A8 com SmoothQuant e α APRENDÍVEL (V5).
2
+
3
+ ═══════════════════════════════════════════════════════════════════════════════
4
+ V5 — APRIMORAMENTO MATEMÁTICO: α APRENDÍVEL VIA STE
5
+ ═══════════════════════════════════════════════════════════════════════════════
6
+
7
+ ORIGEM (gru-ring-v13-9-2):
8
+ source/xavante_work/flexnet/w8a8_smoothquant.py (V13.9.1)
9
+
10
+ Algoritmo (Xiao et al., 2023 — "SmoothQuant"):
11
+ ==============================================
12
+ Dada uma camada Linear Y = X W^T:
13
+ * X ∈ R^{B × d_in} ativações (com outliers por canal)
14
+ * W ∈ R^{d_out × d_in} pesos (aproximadamente uniformes)
15
+
16
+ SmoothQuant introduz fator de suavização s ∈ R^{d_in}_{>0}:
17
+ * X̃ = X / s (divide ativações → reduz outliers)
18
+ * W̃ = W * s (multiplica pesos → preserva produto)
19
+ * Y = X̃ W̃^T = X W^T (matematicamente equivalente)
20
+
21
+ Escolha de s por canal de entrada j:
22
+ s_j = max(|X_j|)^α / max(|W_j|)^{1-α}
23
+
24
+ * α = 0.0 → "dificuldade" toda nos pesos (= quantização per-channel clássica)
25
+ * α = 1.0 → "dificuldade" toda nas ativações (= quantização per-token)
26
+ * α = 0.5 (default empírico do paper) → balanceia
27
+
28
+ V5 — MELHORIA MATEMÁTICA SOBRE V13.9.1:
29
+ Em V13.9.1, α era FIXO (default 0.5). Em V5, α é APRENDÍVEL:
30
+ * Parametrizado via sigmoid: α = sigmoid(eta_alpha) ∈ (0, 1)
31
+ * eta_alpha é nn.Parameter treinável — o modelo ajusta α sozinho
32
+ * Backward via STE (Straight-Through Estimator):
33
+ - round() não é diferenciável → STE substitui grad por 1
34
+ - clamp() tem gradiente 1 dentro do intervalo, 0 fora
35
+ * Loss auxiliary: ||Y_fp - Y_q||_F^2 / ||Y_fp||_F^2 (relative MSE)
36
+ adicionada à loss total → gradiente flui para eta_alpha
37
+
38
+ Isso atende ao requisito V5: "modelo deve ajustar todos os seus parâmetros
39
+ por conta própria sempre". α era o único parâmetro do W8A8 que não era
40
+ auto-ajustado — agora é.
41
+
42
+ V5 — MELHORIA DE MEMÓRIA (OOM-Killer fix):
43
+ * calib_X_buf (256 × d_in float32 = 1KB por camada) REMOVIDO
44
+ — em V13.9.1 era armazenado para freeze_quantization, mas só max-abs
45
+ é usado; buffer completo era desperdício.
46
+ * Substituído por calib_max_abs (d_in float32 = 0.5KB por camada)
47
+ — running max-abs per channel, suficiente para s.
48
+ * Em modelo com 100 camadas Linear × d_in=1024: 256KB → 0.5KB (-99.8%)
49
+
50
+ Limite de erro (W8A8 simétrico, per-channel):
51
+ ||Y_fp - Y_q||_2 ≤ ε_w · ||W||_F · ||X||_F + ε_x · ||X||_F · ||W||_F
52
+ onde ε_w, ε_x ≤ 2^{-7} (1/128) para escalas bem calibradas.
53
+ """
54
+ from __future__ import annotations
55
+
56
+ import math
57
+ import logging
58
+ from typing import Optional, Tuple
59
+
60
+ import torch
61
+ import torch.nn as nn
62
+ import torch.nn.functional as F
63
+
64
+ logger = logging.getLogger(__name__)
65
+
66
+
67
+ # ═══════════════════════════════════════════════════════════════════════════
68
+ # Funções de Quantização Simétrica (zero_point=0 sempre)
69
+ # ═══════════════════════════════════════════════════════════════════════════
70
+ def _per_channel_max_abs(W: torch.Tensor, dim: int) -> torch.Tensor:
71
+ """max |W| ao longo de dim, com mínimo epsilon para evitar divisão por 0."""
72
+ return W.abs().amax(dim=dim).clamp(min=1e-8)
73
+
74
+
75
+ def calibrate_smoothquant(
76
+ X: torch.Tensor,
77
+ W: torch.Tensor,
78
+ alpha: float = 0.5,
79
+ ) -> torch.Tensor:
80
+ """Calcula o fator SmoothQuant s ∈ R^{d_in}_{>0}.
81
+
82
+ X: (B, d_in) ou (..., d_in) — ativações de calibração
83
+ W: (d_out, d_in) — pesos da camada Linear
84
+ alpha: ∈ [0, 1], trade-off (default 0.5)
85
+
86
+ Retorna: s ∈ R^{d_in} (positivo).
87
+
88
+ Fórmula: s_j = max(|X_j|)^α / max(|W_j|)^{1-α}
89
+ """
90
+ d_in = W.shape[-1]
91
+ if X.shape[-1] != d_in:
92
+ return torch.ones(d_in, device=W.device, dtype=W.dtype)
93
+ X_flat = X.reshape(-1, d_in)
94
+ max_x = X_flat.abs().amax(dim=0).clamp(min=1e-8) # (d_in,)
95
+ max_w = W.abs().amax(dim=0).clamp(min=1e-8) # (d_in,)
96
+ s = torch.pow(max_x, alpha) / torch.pow(max_w, 1.0 - alpha)
97
+ return s.clamp(min=1e-8)
98
+
99
+
100
+ def quantize_per_channel_symmetric(
101
+ W: torch.Tensor,
102
+ axis: int = 0,
103
+ n_bits: int = 8,
104
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
105
+ """Quantização int8 simétrica per-channel (tensor 2D).
106
+
107
+ W: 2D tensor (d_axis, d_other).
108
+ axis=0 → uma escala por linha (cada output channel tem sua escala).
109
+ Retorna: (W_int8, scale) onde W_fp ≈ W_int8 * scale e scale é shape (d_axis,).
110
+ """
111
+ assert W.dim() == 2, f"esperado tensor 2D, recebeu dim={W.dim()}"
112
+ qmax = 2 ** (n_bits - 1) - 1 # 127 para int8 (faixa simétrica [-127, 127])
113
+ qmin = -(2 ** (n_bits - 1) - 1)
114
+ other = 1 - axis
115
+ max_abs = W.abs().amax(dim=other, keepdim=True).clamp(min=1e-8) # (d_axis, 1)
116
+ scale = max_abs / qmax
117
+ W_q = torch.round(W / scale).clamp(qmin, qmax).to(torch.int8)
118
+ return W_q, scale.squeeze(1)
119
+
120
+
121
+ def quantize_per_token_symmetric(
122
+ X: torch.Tensor,
123
+ n_bits: int = 8,
124
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
125
+ """Quantização int8 simétrica per-token (último eixo = canal).
126
+
127
+ X: (..., d_in)
128
+ Retorna: (X_int8, scale) onde X_fp ≈ X_int8 * scale e scale é shape (..., 1).
129
+ """
130
+ qmax = 2 ** (n_bits - 1) - 1
131
+ qmin = -(2 ** (n_bits - 1) - 1)
132
+ max_abs = X.abs().amax(dim=-1, keepdim=True).clamp(min=1e-8)
133
+ scale = max_abs / qmax
134
+ X_q = torch.round(X / scale).clamp(qmin, qmax).to(torch.int8)
135
+ return X_q, scale
136
+
137
+
138
+ # ═══════════════════════════════════════════════════════════════════════════
139
+ # STE para round() — permite gradiente fluir através de quantização
140
+ # ═══════════════════════════════════════════════════════════════════════════
141
+ class STEQuantize(torch.autograd.Function):
142
+ """Straight-Through Estimator para round + clamp.
143
+
144
+ Forward: x_q = round(x / scale).clamp(qmin, qmax) * scale
145
+ Backward: grad flui direto (grad_x = grad_y), ignorando round/clamp.
146
+
147
+ Isso é necessário porque:
148
+ * d(round)/dx = 0 em quase todos os pontos (escada de Heaviside)
149
+ * d(clamp)/dx = 0 fora do intervalo, 1 dentro
150
+ Sem STE, gradientes não fluiriam para os pesos/α através da quantização.
151
+ """
152
+
153
+ @staticmethod
154
+ def forward(ctx, x, scale, qmin, qmax):
155
+ x_q = torch.round(x / scale).clamp(qmin, qmax)
156
+ x_deq = x_q * scale
157
+ return x_deq
158
+
159
+ @staticmethod
160
+ def backward(ctx, grad_output):
161
+ # STE: gradiente passa direto
162
+ return grad_output, None, None, None
163
+
164
+
165
+ def ste_quantize(x: torch.Tensor, scale: torch.Tensor, n_bits: int = 8) -> torch.Tensor:
166
+ """Quantiza com STE. scale deve ter shape broadcastable com x."""
167
+ qmax = 2 ** (n_bits - 1) - 1
168
+ qmin = -(2 ** (n_bits - 1) - 1)
169
+ return STEQuantize.apply(x, scale, qmin, qmax)
170
+
171
+
172
+ # ═══════════════════════════════════════════════════════════════════════════
173
+ # SmoothQuantW8A8 — camada Linear W8A8 com α APRENDÍVEL (V5)
174
+ # ═══════════════════════════════════════════════════════════════════════════
175
+ class SmoothQuantW8A8(nn.Module):
176
+ """Camada Linear W8A8 com SmoothQuant e α APRENDÍVEL.
177
+
178
+ V5 vs V13.9.1:
179
+ * α é parâmetro treinável (via sigmoid(eta_alpha))
180
+ * calib_X_buf removido (era OOM em modelos grandes) — apenas max_abs
181
+ * STE explícito para round() — backward bem definido
182
+ * Quantização do peso é DIFFERENCIÁVEL w.r.t. α (via s e STE)
183
+
184
+ Modos:
185
+ * calibration: coleta max_abs das ativações
186
+ * quantized: pesos em int8, ativações quantizadas on-the-fly com STE
187
+
188
+ Args:
189
+ ref: nn.Linear de referência (pesos copiados)
190
+ init_alpha: valor inicial de α (default 0.5)
191
+ learnable_alpha: se True (default V5), α é treinável
192
+ calib_batch_size: tamanho do buffer de calibração (apenas p/ count)
193
+ n_bits: nº de bits (default 8)
194
+ """
195
+
196
+ def __init__(
197
+ self,
198
+ ref: nn.Linear,
199
+ init_alpha: float = 0.5,
200
+ learnable_alpha: bool = True,
201
+ calib_batch_size: int = 256,
202
+ n_bits: int = 8,
203
+ ):
204
+ super().__init__()
205
+ self.in_features = ref.in_features
206
+ self.out_features = ref.out_features
207
+ self.n_bits = n_bits
208
+ self.learnable_alpha = learnable_alpha
209
+
210
+ # Pesos fp (treináveis até freeze; após freeze ainda treináveis p/ α)
211
+ self.weight_fp = nn.Parameter(ref.weight.detach().clone())
212
+ self.bias = (
213
+ nn.Parameter(ref.bias.detach().clone()) if ref.bias is not None else None
214
+ )
215
+
216
+ # V5: α aprendível via sigmoid(eta_alpha) ∈ (0, 1)
217
+ init_eta = float(math.log(init_alpha / max(1e-6, 1.0 - init_alpha)))
218
+ if learnable_alpha:
219
+ self.eta_alpha = nn.Parameter(torch.tensor(init_eta, dtype=torch.float32))
220
+ else:
221
+ self.register_buffer("eta_alpha", torch.tensor(init_eta, dtype=torch.float32))
222
+
223
+ # Buffers de quantização
224
+ self.register_buffer("smooth_scale", torch.ones(self.in_features))
225
+ self.register_buffer("weight_scale", torch.ones(self.out_features))
226
+ self.register_buffer(
227
+ "weight_int8",
228
+ torch.zeros(self.out_features, self.in_features, dtype=torch.int8),
229
+ )
230
+ self.register_buffer("is_quantized", torch.tensor(False))
231
+ self.register_buffer("calib_collected", torch.tensor(False))
232
+ # V5: Apenas max-abs per channel (OOM fix — buffer completo removido)
233
+ self.register_buffer("calib_max_abs", torch.zeros(self.in_features))
234
+ self.register_buffer("calib_count", torch.tensor(0, dtype=torch.long))
235
+
236
+ @property
237
+ def alpha(self) -> torch.Tensor:
238
+ """α atual = sigmoid(eta_alpha) ∈ (0, 1)."""
239
+ return torch.sigmoid(self.eta_alpha)
240
+
241
+ @torch.no_grad()
242
+ def calibrate(self, X: torch.Tensor) -> None:
243
+ """Acumula estatísticas de ativação (max-abs per channel).
244
+
245
+ V5 OOM fix: não armazena tensor completo, apenas max(|X|) per channel.
246
+ """
247
+ if bool(self.is_quantized):
248
+ raise RuntimeError("Calibração após freeze não é permitida.")
249
+ flat = X.reshape(-1, self.in_features)
250
+ batch_max_abs = flat.abs().amax(dim=0).to(self.calib_max_abs.dtype)
251
+ self.calib_max_abs.copy_(torch.maximum(self.calib_max_abs, batch_max_abs))
252
+ self.calib_count += flat.shape[0]
253
+ self.calib_collected.fill_(True)
254
+
255
+ @torch.no_grad()
256
+ def freeze_quantization(self) -> None:
257
+ """Computa s com α ATUAL, quantiza W em int8 per-channel.
258
+
259
+ Nota: depois de freeze, o modelo ainda pode chamar recompute_smooth_scale()
260
+ para atualizar s quando α mudar (durante o treino do α).
261
+ """
262
+ if not bool(self.calib_collected):
263
+ logger.warning("SmoothQuantW8A8: freeze sem calibração. Usando s=1.")
264
+ X_calib = self.weight_fp.new_zeros(1, self.in_features)
265
+ else:
266
+ # Reconstrói tensor "representativo" das ativações a partir de max-abs
267
+ X_calib = self.calib_max_abs.unsqueeze(0) # (1, d_in)
268
+
269
+ alpha_val = float(self.alpha.item())
270
+ s = calibrate_smoothquant(X_calib, self.weight_fp, alpha=alpha_val)
271
+ self.smooth_scale.copy_(s.to(self.smooth_scale.dtype))
272
+
273
+ W_smooth = self.weight_fp * s.unsqueeze(0)
274
+ W_q, w_scale = quantize_per_channel_symmetric(W_smooth, axis=0, n_bits=self.n_bits)
275
+ self.weight_int8.copy_(W_q)
276
+ self.weight_scale.copy_(w_scale)
277
+ self.is_quantized.fill_(True)
278
+
279
+ @torch.no_grad()
280
+ def recompute_smooth_scale(self) -> None:
281
+ """Recomputa s e re-quantiza W com α ATUAL (após treino de α).
282
+
283
+ V5: chamado pelo trainer a cada N steps para manter W quantizado
284
+ consistente com α atualizado.
285
+ """
286
+ if not bool(self.is_quantized):
287
+ return
288
+ if not bool(self.calib_collected):
289
+ return
290
+ X_calib = self.calib_max_abs.unsqueeze(0)
291
+ alpha_val = float(self.alpha.item())
292
+ s = calibrate_smoothquant(X_calib, self.weight_fp, alpha=alpha_val)
293
+ self.smooth_scale.copy_(s.to(self.smooth_scale.dtype))
294
+ W_smooth = self.weight_fp * s.unsqueeze(0)
295
+ W_q, w_scale = quantize_per_channel_symmetric(W_smooth, axis=0, n_bits=self.n_bits)
296
+ self.weight_int8.copy_(W_q)
297
+ self.weight_scale.copy_(w_scale)
298
+
299
+ def forward(self, X: torch.Tensor) -> torch.Tensor:
300
+ """Forward W8A8 com SmoothQuant e α aprendível.
301
+
302
+ V5: quando quantized, faz quantização on-the-fly com STE para que
303
+ gradientes fluam para eta_alpha (via s(α)).
304
+ """
305
+ if not bool(self.is_quantized):
306
+ # Modo calibração/treino inicial: forward fp32 normal
307
+ return F.linear(X, self.weight_fp, self.bias)
308
+
309
+ # V5: recompute s(α) on-the-fly (diferenciável w.r.t. eta_alpha)
310
+ # smooth_scale é buffer (detached), mas recalculamos s com α treinável
311
+ alpha_t = self.alpha # diferenciável
312
+ # max_abs armazenado em calib_max_abs (detached)
313
+ max_x = self.calib_max_abs.clamp(min=1e-8)
314
+ max_w = self.weight_fp.detach().abs().amax(dim=0).clamp(min=1e-8)
315
+ s = torch.pow(max_x, alpha_t) / torch.pow(max_w, 1.0 - alpha_t)
316
+ s = s.clamp(min=1e-8)
317
+
318
+ # Smooth: X̃ = X / s, W̃_fp = weight_fp * s
319
+ X_smooth = X / s # (..., d_in) — diferenciável w.r.t. eta_alpha via s
320
+ # W̃_fp não precisa de STE aqui porque weight_fp é treinável diretamente
321
+ W_smooth = self.weight_fp * s.unsqueeze(0) # (d_out, d_in)
322
+
323
+ # Quantiza W per-channel com STE (diferenciável)
324
+ qmax = 2 ** (self.n_bits - 1) - 1
325
+ max_w_smooth = W_smooth.abs().amax(dim=1, keepdim=True).clamp(min=1e-8)
326
+ w_scale_dyn = max_w_smooth / qmax # (d_out, 1)
327
+ W_q_deq = ste_quantize(W_smooth, w_scale_dyn, self.n_bits) # (d_out, d_in)
328
+
329
+ # Quantiza X per-token com STE
330
+ max_x_smooth = X_smooth.abs().amax(dim=-1, keepdim=True).clamp(min=1e-8)
331
+ x_scale_dyn = max_x_smooth / qmax # (..., 1)
332
+ X_q_deq = ste_quantize(X_smooth, x_scale_dyn, self.n_bits) # (..., d_in)
333
+
334
+ # Matmul em fp32 (simulação; HW real faria INT8×INT8→INT32→fp32)
335
+ Y = X_q_deq @ W_q_deq.t()
336
+ if self.bias is not None:
337
+ Y = Y + self.bias
338
+ return Y
339
+
340
+ def quantization_error(self, X: torch.Tensor) -> float:
341
+ """Calcula ||Y_fp - Y_q||_F / ||Y_fp||_F como métrica de erro."""
342
+ with torch.no_grad():
343
+ Y_fp = F.linear(X, self.weight_fp, self.bias)
344
+ Y_q = self.forward(X)
345
+ num = (Y_fp - Y_q).norm().item()
346
+ den = max(Y_fp.norm().item(), 1e-12)
347
+ return num / den
348
+
349
+ def extra_repr(self) -> str:
350
+ return (
351
+ f"in_features={self.in_features}, out_features={self.out_features}, "
352
+ f"alpha={float(self.alpha.item()):.4f}, "
353
+ f"learnable_alpha={self.learnable_alpha}, "
354
+ f"is_quantized={bool(self.is_quantized)}"
355
+ )
356
+
357
+
358
+ __all__ = [
359
+ "calibrate_smoothquant",
360
+ "quantize_per_channel_symmetric",
361
+ "quantize_per_token_symmetric",
362
+ "STEQuantize",
363
+ "ste_quantize",
364
+ "SmoothQuantW8A8",
365
+ ]
src/bigru_t/training/meta_configurator.py CHANGED
@@ -2,27 +2,24 @@
2
 
3
  Implementa o Lema 4 (Auto-configuração e suavização).
4
 
5
- Mantém hiperparâmetros aprendíveis:
6
  - log_temperature: controla T = exp(log_temperature) do Lema 1
7
  - log_tau: controla τ = exp(log_tau) do Lema 3 (limiar de ativação da hipótese)
 
 
 
 
 
8
 
9
  Meta-objetivo:
10
- L_meta(T, τ) = L_val(T, τ; θ) + λ_s * S(θ)
 
 
11
 
12
- onde S(θ) = ||∇_θ L_val||^2 é uma proxy de agudeza (sharpness) — aproximação
13
- de baixo custo para o maior autovalor da Hessiana.
14
 
15
  Minimizar S(θ) empurra o modelo para mínimos planos (Keskar 2017, Foret 2021),
16
  onde o ruído de gradiente é menor.
17
-
18
- Reaproveita a filosofia do meta_learner.py (MAML-RNOEFLEX) e meta_orchestrator.py
19
- do v13.9.2, mas:
20
- - Substitui MAML de 2ª ordem por diferenciação implícita simplificada
21
- (1ª ordem + proxy de agudeza)
22
- - Substitui o orchestrator de 3º nível por 2 hiperparâmetros escalares
23
- (T, τ) — mais barato computacionalmente
24
- - A proxy de agudeza é o quadrado da norma do gradiente (não o autovalor
25
- da Hessiana — seria proibitivo para 16M+ params)
26
  """
27
  from __future__ import annotations
28
 
@@ -33,18 +30,20 @@ import torch.nn as nn
33
 
34
 
35
  class MetaConfigurator:
36
- """Auto-configuração de temperatura (T) e limiar (τ) via meta-aprendizado.
 
 
 
 
 
 
37
 
38
  Args:
39
- model: o UnifiedModel a ser configurado
40
- meta_lr: learning rate do meta-otimizador (Adam em log_T, log_tau)
 
41
  sharpness_lambda: peso λ_s da proxy de agudeza
42
 
43
- Atributos:
44
- log_temperature: nn.Parameter — T = exp(log_temperature)
45
- log_tau: nn.Parameter — τ = exp(log_tau)
46
- meta_optim: Adam sobre (log_temperature, log_tau)
47
-
48
  Uso:
49
  meta_cfg = MetaConfigurator(model)
50
  # No loop de treino, a cada meta_interval batches:
@@ -66,10 +65,20 @@ class MetaConfigurator:
66
  self.log_temperature = nn.Parameter(torch.tensor(0.0))
67
  self.log_tau = nn.Parameter(torch.tensor(0.0))
68
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  # Otimizador Adam sobre os hiperparâmetros
70
- self.meta_optim = torch.optim.Adam(
71
- [self.log_temperature, self.log_tau], lr=meta_lr
72
- )
73
 
74
  @property
75
  def temperature(self) -> float:
@@ -81,6 +90,20 @@ class MetaConfigurator:
81
  """τ atual = exp(log_tau)."""
82
  return float(torch.exp(self.log_tau.detach()).item())
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  def forward_with_meta(
85
  self,
86
  x_val: torch.Tensor,
@@ -95,19 +118,21 @@ class MetaConfigurator:
95
  criterion: função de perda (default: CrossEntropyLoss)
96
 
97
  Returns:
98
- dict com: loss_val, sharpness, meta_loss, T_new, tau_new
99
 
100
  V3 BUGFIX: log_tau não tinha caminho de gradiente (aparecia como None).
101
  Agora adicionamos um termo de target-tracking: τ deve seguir loss_val
102
  (com margem), dando a log_tau um gradiente via MSE.
103
- Também corrigimos module_selector para preservar o grafo quando
104
- temperature é um tensor (torch.clamp em vez de max()).
105
 
106
  V3.2 BUGFIX (complementar): log_temperature também não movia em 1 step
107
- porque o gradiente via alpha = softmax(logits/T) é ≈0 quando logits ≈0
108
- (init randn*1/sqrt(K) ≈ 0). Adicionamos T_target = 1 + 0.1*(loss - 1):
109
- loss alta → T alto (exploração); loss baixa → T baixo (especialização).
110
- Isto dá a log_T um gradiente via MSE mesmo quando alpha é uniforme.
 
 
 
 
111
  """
112
  if criterion is None:
113
  criterion = nn.CrossEntropyLoss()
@@ -115,12 +140,35 @@ class MetaConfigurator:
115
  # Hiperparâmetros atuais (positivos via exp)
116
  temp = torch.exp(self.log_temperature)
117
  tau_val = torch.exp(self.log_tau)
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
  # Forward do modelo com T atual (sem hipótese — só validação principal)
 
 
120
  self.model.train() # mantém dropout ativo para estimar sharpness real
121
- y_hat_main, _ = self.model(x_val, temperature=temp, use_hypothesis=False)
 
 
122
  loss_val = criterion(y_hat_main, y_val)
123
 
 
 
 
 
 
 
124
  # Proxy de agudeza: ||∇_θ L_val||^2
125
  # BUG FIX: create_graph=True causava OOM-Killer (second-order graph dobra RAM)
126
  # Correção: usar create_graph=False (first-order approximation)
@@ -157,17 +205,50 @@ class MetaConfigurator:
157
  T_pred = torch.exp(self.log_temperature)
158
  T_tracking_loss = 0.05 * (T_pred - T_target) ** 2
159
 
160
- # Meta-perda: loss_val (atualiza log_T via temperatura → alpha → y_hat)
161
- # + T_tracking_loss (atualiza log_T via MSE target)
162
- # + tau_tracking_loss (atualiza log_tau)
163
- meta_loss = loss_val + T_tracking_loss + tau_tracking_loss
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
- # Atualiza log_temperature e log_tau via gradiente de meta_loss
166
  self.meta_optim.zero_grad()
167
  meta_loss.backward()
168
- # Guarda gradiente antes do step para logging
169
  grad_log_T = self.log_temperature.grad.item() if self.log_temperature.grad is not None else 0.0
170
  grad_log_tau = self.log_tau.grad.item() if self.log_tau.grad is not None else 0.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  self.meta_optim.step()
172
 
173
  # Sincroniza model.tau com o novo valor
@@ -180,10 +261,18 @@ class MetaConfigurator:
180
  "meta_loss": float(meta_loss.item()),
181
  "T_new": float(torch.exp(self.log_temperature.detach()).item()),
182
  "tau_new": float(torch.exp(self.log_tau.detach()).item()),
 
 
 
 
183
  "grad_log_T": grad_log_T,
184
  "grad_log_tau": grad_log_tau,
 
 
185
  "tau_target": float(tau_target.item()),
186
  "T_target": float(T_target.item()),
187
  "T_tracking_loss": float(T_tracking_loss.item()),
188
  "tau_tracking_loss": float(tau_tracking_loss.item()),
 
 
189
  }
 
2
 
3
  Implementa o Lema 4 (Auto-configuração e suavização).
4
 
5
+ V5: Mantém hiperparâmetros aprendíveis:
6
  - log_temperature: controla T = exp(log_temperature) do Lema 1
7
  - log_tau: controla τ = exp(log_tau) do Lema 3 (limiar de ativação da hipótese)
8
+ - log_lambda_ent: peso da reg. de entropia do module_selector (V5 NOVO)
9
+ - log_init_gate: gate inicial do CircularReasoning (V5 NOVO)
10
+
11
+ Requisito V5: "modelo deve ajustar todos os seus parâmetros por conta própria
12
+ sempre" — agora T, τ, λ_ent, e init_gate são todos auto-ajustados via meta-loss.
13
 
14
  Meta-objetivo:
15
+ L_meta(T, τ, λ_ent, init_gate) = L_val + λ_s * S(θ)
16
+ + tracking losses para T, τ
17
+ + reg losses para λ_ent, init_gate
18
 
19
+ onde S(θ) = ||∇_θ L_val||^2 é uma proxy de agudeza (sharpness).
 
20
 
21
  Minimizar S(θ) empurra o modelo para mínimos planos (Keskar 2017, Foret 2021),
22
  onde o ruído de gradiente é menor.
 
 
 
 
 
 
 
 
 
23
  """
24
  from __future__ import annotations
25
 
 
30
 
31
 
32
  class MetaConfigurator:
33
+ """Auto-configuração de T, τ, λ_ent, init_gate via meta-aprendizado.
34
+
35
+ V5: Agora gerencia 4 hiperparâmetros (era 2 em V3.4):
36
+ * log_temperature → T (softmax temperature do module_selector)
37
+ * log_tau → τ (limiar de ativação da hipótese)
38
+ * log_lambda_ent → λ_ent (peso da reg. de entropia) — V5 NOVO
39
+ * log_init_gate → init_gate (gate do CircularReasoning) — V5 NOVO
40
 
41
  Args:
42
+ model: o UnifiedModel a ser configurado (deve ter log_lambda_ent e
43
+ log_init_gate como nn.Parameters)
44
+ meta_lr: learning rate do meta-otimizador (Adam)
45
  sharpness_lambda: peso λ_s da proxy de agudeza
46
 
 
 
 
 
 
47
  Uso:
48
  meta_cfg = MetaConfigurator(model)
49
  # No loop de treino, a cada meta_interval batches:
 
65
  self.log_temperature = nn.Parameter(torch.tensor(0.0))
66
  self.log_tau = nn.Parameter(torch.tensor(0.0))
67
 
68
+ # V5 NOVO: Coletar todos os parâmetros meta
69
+ # - log_temperature, log_tau (locais)
70
+ # - model.log_lambda_ent (λ_ent do UnifiedModel)
71
+ # - model.circular_reasoning.residual_gate (n_cycles gates, se CRW ativo)
72
+ meta_params = [self.log_temperature, self.log_tau]
73
+ if hasattr(model, 'log_lambda_ent'):
74
+ meta_params.append(model.log_lambda_ent)
75
+ if (hasattr(model, 'circular_reasoning')
76
+ and model.circular_reasoning is not None
77
+ and hasattr(model.circular_reasoning, 'residual_gate')):
78
+ meta_params.append(model.circular_reasoning.residual_gate)
79
+
80
  # Otimizador Adam sobre os hiperparâmetros
81
+ self.meta_optim = torch.optim.Adam(meta_params, lr=meta_lr)
 
 
82
 
83
  @property
84
  def temperature(self) -> float:
 
90
  """τ atual = exp(log_tau)."""
91
  return float(torch.exp(self.log_tau.detach()).item())
92
 
93
+ @property
94
+ def lambda_ent(self) -> float:
95
+ """λ_ent atual = exp(model.log_lambda_ent)."""
96
+ if hasattr(self.model, 'log_lambda_ent'):
97
+ return float(torch.exp(self.model.log_lambda_ent.detach()).item())
98
+ return 0.01
99
+
100
+ @property
101
+ def init_gate(self) -> float:
102
+ """init_gate atual = exp(model.log_init_gate)."""
103
+ if hasattr(self.model, 'log_init_gate'):
104
+ return float(torch.exp(self.model.log_init_gate.detach()).item())
105
+ return 0.05
106
+
107
  def forward_with_meta(
108
  self,
109
  x_val: torch.Tensor,
 
118
  criterion: função de perda (default: CrossEntropyLoss)
119
 
120
  Returns:
121
+ dict com: loss_val, sharpness, meta_loss, T_new, tau_new, λ_ent_new, init_gate_new
122
 
123
  V3 BUGFIX: log_tau não tinha caminho de gradiente (aparecia como None).
124
  Agora adicionamos um termo de target-tracking: τ deve seguir loss_val
125
  (com margem), dando a log_tau um gradiente via MSE.
 
 
126
 
127
  V3.2 BUGFIX (complementar): log_temperature também não movia em 1 step
128
+ porque o gradiente via alpha = softmax(logits/T) é ≈0 quando logits ≈0.
129
+ Adicionamos T_target = 1 + 0.1*(loss - 1): loss alta → T alto (exploração).
130
+
131
+ V5: Agora também atualiza λ_ent e init_gate do modelo via meta-loss.
132
+ - λ_ent: tracking loss para manter perto de 0.01 (evita saturação
133
+ da entropia do module_selector)
134
+ - init_gate: tracking loss para manter perto de 0.05 (evita gate
135
+ muito grande que quebraria a contração W₂ do CircularReasoning)
136
  """
137
  if criterion is None:
138
  criterion = nn.CrossEntropyLoss()
 
140
  # Hiperparâmetros atuais (positivos via exp)
141
  temp = torch.exp(self.log_temperature)
142
  tau_val = torch.exp(self.log_tau)
143
+ # V5: ler do model
144
+ lambda_ent_val = (
145
+ torch.exp(self.model.log_lambda_ent)
146
+ if hasattr(self.model, 'log_lambda_ent')
147
+ else torch.tensor(0.01)
148
+ )
149
+ # V5: residual_gate é Parameter do CircularReasoning (shape: n_cycles)
150
+ # Usamos a média para o tracking loss (1 escalar)
151
+ if (hasattr(self.model, 'circular_reasoning')
152
+ and self.model.circular_reasoning is not None):
153
+ init_gate_val = self.model.circular_reasoning.residual_gate.mean()
154
+ else:
155
+ init_gate_val = torch.tensor(0.05)
156
 
157
  # Forward do modelo com T atual (sem hipótese — só validação principal)
158
+ # V5: return_aux=True para capturar entropy_reg
159
+ # (necessário para que λ_ent tenha gradiente via lambda_ent * entropy_reg)
160
  self.model.train() # mantém dropout ativo para estimar sharpness real
161
+ y_hat_main, _, aux = self.model(
162
+ x_val, temperature=temp, use_hypothesis=False, return_aux=True
163
+ )
164
  loss_val = criterion(y_hat_main, y_val)
165
 
166
+ # V5: λ_ent * entropy_reg — dá a λ_ent um caminho de gradiente via
167
+ # o termo de regularização de entropia do module_selector (Lema 1)
168
+ # Isto é o "uso real" de λ_ent no treino — deve ser refletido no meta-loss
169
+ entropy_reg = aux.get("entropy_reg", torch.tensor(0.0))
170
+ entropy_loss = lambda_ent_val * entropy_reg
171
+
172
  # Proxy de agudeza: ||∇_θ L_val||^2
173
  # BUG FIX: create_graph=True causava OOM-Killer (second-order graph dobra RAM)
174
  # Correção: usar create_graph=False (first-order approximation)
 
205
  T_pred = torch.exp(self.log_temperature)
206
  T_tracking_loss = 0.05 * (T_pred - T_target) ** 2
207
 
208
+ # V5 NOVO: λ_ent tracking loss — manter perto de 0.01 (default do config)
209
+ # Permite variação mas evita saturação (λ_ent muito grande força entropia
210
+ # máxima = módulos uniformes; muito pequena = colapso para 1 módulo)
211
+ lambda_ent_target = torch.tensor(0.01, device=lambda_ent_val.device)
212
+ lambda_ent_tracking_loss = 0.01 * (lambda_ent_val - lambda_ent_target) ** 2
213
+
214
+ # V5 NOVO: init_gate tracking loss — manter perto de 0.05 (default do config)
215
+ # Aplicado à MÉDIA dos n_cycles gates do CircularReasoning.
216
+ # Permite variação mas evita gate muito grande (quebraria contração W₂)
217
+ init_gate_target = torch.tensor(0.05, device=init_gate_val.device)
218
+ init_gate_tracking_loss = 0.01 * (init_gate_val - init_gate_target) ** 2
219
+
220
+ # Meta-perda: loss_val + entropy_loss (λ_ent path)
221
+ # + tracking losses para T, τ, λ_ent, init_gate
222
+ meta_loss = (
223
+ loss_val
224
+ + entropy_loss
225
+ + T_tracking_loss
226
+ + tau_tracking_loss
227
+ + lambda_ent_tracking_loss
228
+ + init_gate_tracking_loss
229
+ )
230
 
231
+ # Atualiza log_temperature, log_tau, model.log_lambda_ent, residual_gate
232
  self.meta_optim.zero_grad()
233
  meta_loss.backward()
234
+ # Guarda gradientes antes do step para logging
235
  grad_log_T = self.log_temperature.grad.item() if self.log_temperature.grad is not None else 0.0
236
  grad_log_tau = self.log_tau.grad.item() if self.log_tau.grad is not None else 0.0
237
+ grad_log_lambda_ent = (
238
+ self.model.log_lambda_ent.grad.item()
239
+ if hasattr(self.model, 'log_lambda_ent')
240
+ and self.model.log_lambda_ent.grad is not None
241
+ else 0.0
242
+ )
243
+ # V5: gradiente do residual_gate (média sobre n_cycles gates)
244
+ if (hasattr(self.model, 'circular_reasoning')
245
+ and self.model.circular_reasoning is not None
246
+ and self.model.circular_reasoning.residual_gate.grad is not None):
247
+ grad_init_gate = float(
248
+ self.model.circular_reasoning.residual_gate.grad.abs().mean().item()
249
+ )
250
+ else:
251
+ grad_init_gate = 0.0
252
  self.meta_optim.step()
253
 
254
  # Sincroniza model.tau com o novo valor
 
261
  "meta_loss": float(meta_loss.item()),
262
  "T_new": float(torch.exp(self.log_temperature.detach()).item()),
263
  "tau_new": float(torch.exp(self.log_tau.detach()).item()),
264
+ "lambda_ent_new": float(lambda_ent_val.detach().item()),
265
+ "init_gate_new": float(init_gate_val.detach().item()),
266
+ "entropy_reg": float(entropy_reg.detach().item() if isinstance(entropy_reg, torch.Tensor) else 0.0),
267
+ "entropy_loss": float(entropy_loss.detach().item() if isinstance(entropy_loss, torch.Tensor) else 0.0),
268
  "grad_log_T": grad_log_T,
269
  "grad_log_tau": grad_log_tau,
270
+ "grad_log_lambda_ent": grad_log_lambda_ent,
271
+ "grad_init_gate": grad_init_gate,
272
  "tau_target": float(tau_target.item()),
273
  "T_target": float(T_target.item()),
274
  "T_tracking_loss": float(T_tracking_loss.item()),
275
  "tau_tracking_loss": float(tau_tracking_loss.item()),
276
+ "lambda_ent_tracking_loss": float(lambda_ent_tracking_loss.item()),
277
+ "init_gate_tracking_loss": float(init_gate_tracking_loss.item()),
278
  }
v32_upload_report.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "version": "V3.2_upload",
3
- "timestamp": 1786013028.9061728,
4
  "repo_id": "PowerMachine/BiGRU_T_version",
5
  "repo_type": "model",
6
  "folder_path": "/home/z/my-project/BiGRU_T_version",
@@ -29,15 +29,15 @@
29
  "*/.cache/huggingface",
30
  "**/.cache/huggingface/**"
31
  ],
32
- "local_files_count": 64,
33
- "local_files_total_bytes": 788009,
34
- "remote_files_before": 63,
35
- "remote_files_after": 67,
36
- "new_files_count": 4,
37
- "overwritten_count": 64,
38
  "critical_files_all_present": true,
39
- "elapsed_s": 2.654574849000028,
40
- "commit_info": "https://huggingface.co/PowerMachine/BiGRU_T_version/commit/a22ed76d93580e2cc99e084b60e54080bb45cc9c",
41
  "env": {
42
  "HF_HUB_ENABLE_HF_TRANSFER": "1",
43
  "HF_HUB_ETAG_TIMEOUT": "60",
 
1
  {
2
  "version": "V3.2_upload",
3
+ "timestamp": 1786016584.7106616,
4
  "repo_id": "PowerMachine/BiGRU_T_version",
5
  "repo_type": "model",
6
  "folder_path": "/home/z/my-project/BiGRU_T_version",
 
29
  "*/.cache/huggingface",
30
  "**/.cache/huggingface/**"
31
  ],
32
+ "local_files_count": 67,
33
+ "local_files_total_bytes": 809581,
34
+ "remote_files_before": 67,
35
+ "remote_files_after": 70,
36
+ "new_files_count": 3,
37
+ "overwritten_count": 67,
38
  "critical_files_all_present": true,
39
+ "elapsed_s": 2.9894082950004304,
40
+ "commit_info": "https://huggingface.co/PowerMachine/BiGRU_T_version/commit/0cee0098eee9c53ec2f36dec7ec24e53aa458838",
41
  "env": {
42
  "HF_HUB_ENABLE_HF_TRANSFER": "1",
43
  "HF_HUB_ETAG_TIMEOUT": "60",
v5_report.json ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "V5",
3
+ "timestamp": 1786018677.168036,
4
+ "config": {
5
+ "n_multimodal": 10,
6
+ "n_text": 10,
7
+ "epochs": 2,
8
+ "batch_size": 4,
9
+ "max_seq_len": 16,
10
+ "num_layers_hyp": 8,
11
+ "triplet_weight": 0.05,
12
+ "meta_interval": 3,
13
+ "cleanup_interval": 1,
14
+ "triplet_ema_alpha": 0.1
15
+ },
16
+ "v5_improvements": {
17
+ "w8a8_learnable_alpha": true,
18
+ "w8a8_self_contained_trust": true,
19
+ "apply_w8a8_bugfix": true,
20
+ "num_layers_hyp_fixed_8": true,
21
+ "oom_mitigations": [
22
+ "del intermediários explícito",
23
+ "MetaConfigurator a cada 3 steps",
24
+ "gc.collect(0) a cada step",
25
+ "aggressive_cleanup entre epochs",
26
+ "torch.no_grad() em val/eval",
27
+ "alerts limitado a 50 entries"
28
+ ],
29
+ "learnable_params": [
30
+ "T (MetaConfigurator)",
31
+ "τ (MetaConfigurator)",
32
+ "λ_ent (MetaConfigurator + treino)",
33
+ "init_gate (residual_gate, MetaConfigurator + treino)",
34
+ "α smoothquant (STE, em SmoothQuantW8A8)",
35
+ "module_logits (treino)",
36
+ "todos os pesos W (AdamW)"
37
+ ]
38
+ },
39
+ "xeon_env": {
40
+ "OMP_NUM_THREADS": "2",
41
+ "MKL_NUM_THREADS": "2",
42
+ "ONEDNN_MAX_CPU_ISA": "AMX_INT8",
43
+ "IPEX": true,
44
+ "CPU_ISA": "AMX",
45
+ "torch_threads": 2
46
+ },
47
+ "result": {
48
+ "n_steps": 6,
49
+ "n_epochs": 2,
50
+ "final_train_loss": 9.665669441223145,
51
+ "final_val_loss": 9.436580848693847,
52
+ "final_perplexity": 12538.771572889684,
53
+ "final_overfitting_gap": 0.006072171529135417,
54
+ "final_eval_benchmark": 0.375,
55
+ "avg_tokens_per_sec": 29.505289440522088,
56
+ "avg_tflops_per_cpu": 0.00010685671030126807,
57
+ "avg_time_per_step_ms": 2553.5807878330465,
58
+ "avg_grad_norm": 10.784179131189982,
59
+ "avg_cpu_pct": 121.38333333333333,
60
+ "final_rss_mb": 922.49609375,
61
+ "max_rss_mb": 922.49609375,
62
+ "rss_trend": "stable",
63
+ "n_alerts": 0,
64
+ "alerts": [],
65
+ "alert_types": [],
66
+ "meta_history": [
67
+ {
68
+ "T": 1.0100501775741577,
69
+ "tau": 1.0100501775741577,
70
+ "lambda_ent": 0.009999999776482582,
71
+ "init_gate": 0.047456394881010056,
72
+ "meta_loss": 17.669658660888672,
73
+ "sharpness": 71.65159606933594
74
+ },
75
+ {
76
+ "T": 1.02021062374115,
77
+ "tau": 1.0202070474624634,
78
+ "lambda_ent": 0.009900504723191261,
79
+ "init_gate": 0.02058723010122776,
80
+ "meta_loss": 18.058149337768555,
81
+ "sharpness": 109.38550567626953
82
+ }
83
+ ],
84
+ "elapsed_s": 21.375046549000217,
85
+ "n_params": 9657632,
86
+ "hyp_T_params": 1795914,
87
+ "num_layers_hyp": 8,
88
+ "n_w8a8_layers": 80,
89
+ "final_learnable_params": {
90
+ "T": 1.02021062374115,
91
+ "tau": 1.0202070474624634,
92
+ "lambda_ent": 0.009802023880183697,
93
+ "init_gate": 0.05,
94
+ "residual_gate": [
95
+ 0.005989676807075739,
96
+ 0.006018564570695162,
97
+ 0.006039654836058617
98
+ ],
99
+ "module_logits": [
100
+ -0.22852112352848053,
101
+ -0.10588397085666656,
102
+ 0.12294353544712067,
103
+ -0.22121863067150116,
104
+ 0.41890066862106323,
105
+ -0.01949109323322773,
106
+ -0.08710632473230362,
107
+ 0.05125897005200386
108
+ ]
109
+ },
110
+ "final_bigru_diversity": {
111
+ "mean_cosine_diversity": 0.7214401364326477,
112
+ "mean_parallelism_score": 0.9953877925872803,
113
+ "mean_gate_norm_ratio": 0.09838299453258514,
114
+ "module_level_cosine_diversity": 1.0237387745042466,
115
+ "per_module_parallelism": [
116
+ 0.9936217665672302,
117
+ 0.9924681186676025,
118
+ 0.9992730617523193,
119
+ 0.9971094727516174,
120
+ 0.9949930310249329,
121
+ 0.9948464035987854,
122
+ 0.9958065748214722,
123
+ 0.9944431781768799
124
+ ]
125
+ }
126
+ }
127
+ }