V6.5-V2-metrics-FIX: cleanup batch — remove .pt files + duplicate root-level modules + obsolete modules (17 files)
Browse files- hyp_t.py +0 -100
- kohonen_learning_system.py +0 -0
- smoke_test_v65_v2_metrics_fix.py +0 -320
- som_metrics.py +0 -811
- src/bigru_t/inference/langchain_adapter.py +0 -218
- src/bigru_t/inference/optimized_pipeline.py +0 -251
- src/bigru_t/model/module_selector.py +0 -100
- src/bigru_t/model/mtp.py +0 -503
- src/bigru_t/model/train_t.py +0 -63
- src/bigru_t/model/window_context.py +0 -371
- src/bigru_t/reasoning/reasoning_system.py +0 -281
- src/bigru_t/training/rlhf_possibility_mapping.py +0 -387
- src/bigru_t/training/trainer.py +0 -937
- streaming_datasets.py +0 -995
- train_v6_5_v2.py +0 -0
- train_v6_5_v2_punicão_only.py +0 -556
- xeon_runtime.py +0 -581
hyp_t.py
DELETED
|
@@ -1,100 +0,0 @@
|
|
| 1 |
-
"""hyp_t.py — HypT: cabeça de hipótese (correção delta). Implementa o Lema 3.
|
| 2 |
-
|
| 3 |
-
A Hipótese é treinada para cancelar o ruído de quantização W8A8:
|
| 4 |
-
y_final = y_hat + delta
|
| 5 |
-
onde delta = hyp_T(o).
|
| 6 |
-
|
| 7 |
-
Diferenças vs. TrainT:
|
| 8 |
-
- Recebe `o` opcionalmente destacado (stop_grad) para isolar o sinal de
|
| 9 |
-
correção do gradiente principal
|
| 10 |
-
- Mesma arquitetura (TransformerEncoder) — apenas o tratamento de gradiente
|
| 11 |
-
difere
|
| 12 |
-
|
| 13 |
-
Reaproveita a filosofia do HypothesisNetwork do v13.9.2 (16 hipóteses com
|
| 14 |
-
mapeamento LoRA), mas simplificada: 1 única hipótese global (delta) em vez
|
| 15 |
-
de 16 hipóteses paralelas. A multiplexação é feita pelo OrqCell + Lema 1.
|
| 16 |
-
|
| 17 |
-
============================================================================
|
| 18 |
-
V6.4 — pgvector_lookup REMOVIDO (NÃO É MAIS NECESSÁRIO)
|
| 19 |
-
============================================================================
|
| 20 |
-
Conforme directive do usuário:
|
| 21 |
-
"pgvector_lookup não é mais necessário pela lógica do script seguinte"
|
| 22 |
-
|
| 23 |
-
O KohonenLearningSystem (kohonen_refactored/kohonen_learning_system.py)
|
| 24 |
-
contém o método find_bmu que realiza busca nearest-neighbor sobre o grid
|
| 25 |
-
4D do SOM (864 neurônios para grid (6,6,6,4)), substituindo qualquer
|
| 26 |
-
lookup pgvector externo. A decisão de aplicar punição é delegada ao
|
| 27 |
-
protocolo interno do KohonenLearningSystem (1ª punição → activate_hypothesis,
|
| 28 |
-
2ª punição → set_ewc_reference + reset).
|
| 29 |
-
|
| 30 |
-
Consequentemente, HypT volta ao seu papel original: gerar a correção
|
| 31 |
-
delta = hyp_T(o) sem consultar mapa externo. A integração com o SOM é
|
| 32 |
-
feita no nível do trainer (KohonenLearningSystem.process_batch), não no
|
| 33 |
-
nível da hipótese.
|
| 34 |
-
|
| 35 |
-
Os contadores de punição (n_complete_skip, n_incomplete_apply, etc.)
|
| 36 |
-
foram removidos pois não há mais decisão de skip baseada em pgvector.
|
| 37 |
-
"""
|
| 38 |
-
from __future__ import annotations
|
| 39 |
-
|
| 40 |
-
import torch
|
| 41 |
-
import torch.nn as nn
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
class HypT(nn.Module):
|
| 45 |
-
"""Transformer de hipótese: gera correção delta.
|
| 46 |
-
|
| 47 |
-
Args:
|
| 48 |
-
d_input: dimensão de entrada (saída do OrqCell = d_cache)
|
| 49 |
-
d_model: dimensão interna
|
| 50 |
-
nhead: nº de cabeças
|
| 51 |
-
d_ff: dimensão do FFN
|
| 52 |
-
output_dim: dimensão de saída (igual ao TrainT)
|
| 53 |
-
num_layers: nº de camadas
|
| 54 |
-
dropout: probabilidade de dropout
|
| 55 |
-
|
| 56 |
-
Forward:
|
| 57 |
-
o: (batch, d_input), stop_grad: bool → delta: (batch, output_dim)
|
| 58 |
-
|
| 59 |
-
Se stop_grad=True, aplica o.detach() no início — Lema 3 (isolar correção
|
| 60 |
-
do gradiente principal).
|
| 61 |
-
"""
|
| 62 |
-
|
| 63 |
-
def __init__(
|
| 64 |
-
self,
|
| 65 |
-
d_input: int,
|
| 66 |
-
d_model: int,
|
| 67 |
-
nhead: int,
|
| 68 |
-
d_ff: int,
|
| 69 |
-
output_dim: int,
|
| 70 |
-
num_layers: int = 2,
|
| 71 |
-
dropout: float = 0.1,
|
| 72 |
-
):
|
| 73 |
-
super().__init__()
|
| 74 |
-
self.d_input = d_input
|
| 75 |
-
self.d_model = d_model
|
| 76 |
-
self.output_dim = output_dim
|
| 77 |
-
|
| 78 |
-
self.proj_in = nn.Linear(d_input, d_model)
|
| 79 |
-
encoder_layer = nn.TransformerEncoderLayer(
|
| 80 |
-
d_model, nhead, d_ff, dropout=dropout, batch_first=True
|
| 81 |
-
)
|
| 82 |
-
self.encoder = nn.TransformerEncoder(encoder_layer, num_layers)
|
| 83 |
-
self.proj_out = nn.Linear(d_model, output_dim)
|
| 84 |
-
|
| 85 |
-
def forward(self, o: torch.Tensor, stop_grad: bool = True) -> torch.Tensor:
|
| 86 |
-
"""o: (batch, d_input) → delta: (batch, output_dim)
|
| 87 |
-
|
| 88 |
-
V6.4: SEM consulta pgvector — delta é computado diretamente.
|
| 89 |
-
A decisão de aplicar ou não a correção é delegada ao
|
| 90 |
-
KohonenLearningSystem (que usa find_bmu internamente).
|
| 91 |
-
"""
|
| 92 |
-
# Lema 3: stop_grad_hyp isola o sinal de correção
|
| 93 |
-
if stop_grad:
|
| 94 |
-
o = o.detach()
|
| 95 |
-
|
| 96 |
-
x = o.unsqueeze(1) # (batch, 1, d_input)
|
| 97 |
-
x = self.proj_in(x) # (batch, 1, d_model)
|
| 98 |
-
x = self.encoder(x) # (batch, 1, d_model)
|
| 99 |
-
delta = self.proj_out(x.squeeze(1)) # (batch, output_dim)
|
| 100 |
-
return delta
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
kohonen_learning_system.py
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
smoke_test_v65_v2_metrics_fix.py
DELETED
|
@@ -1,320 +0,0 @@
|
|
| 1 |
-
"""smoke_test_v65_v2_metrics_fix.py — Validação rápida das correções V6.5-V2-metrics-FIX.
|
| 2 |
-
|
| 3 |
-
Verifica:
|
| 4 |
-
1. Importação do KohonenLearningSystemV2 com params restaurados (HIDDEN=1024, VOCAB=16384)
|
| 5 |
-
2. Bug fix do _compute_classification_loss_with_som (gradiente flui)
|
| 6 |
-
3. Vetorização do train_hypotheses (sem OOM)
|
| 7 |
-
4. aggressive_cleanup() funciona
|
| 8 |
-
5. SOM metrics (8 métricas) computam sem erro
|
| 9 |
-
6. predict() retorna labels dinâmicos (não hardcoded)
|
| 10 |
-
7. FASE1 + FASE2 logic (enable_punishment=False/True)
|
| 11 |
-
"""
|
| 12 |
-
from __future__ import annotations
|
| 13 |
-
|
| 14 |
-
import os
|
| 15 |
-
import sys
|
| 16 |
-
import time
|
| 17 |
-
import traceback
|
| 18 |
-
from pathlib import Path
|
| 19 |
-
|
| 20 |
-
# Setup paths
|
| 21 |
-
PROJECT_ROOT = Path("/home/z/my-project")
|
| 22 |
-
BIGRU_ROOT = PROJECT_ROOT / "BiGRU_T_version"
|
| 23 |
-
SRC_ROOT = BIGRU_ROOT / "src"
|
| 24 |
-
sys.path.insert(0, str(SRC_ROOT))
|
| 25 |
-
|
| 26 |
-
# Activate xeon_runtime
|
| 27 |
-
os.environ["V65_ENABLE_STREAMING"] = "1"
|
| 28 |
-
|
| 29 |
-
print("=" * 80)
|
| 30 |
-
print("V6.5-V2-metrics-FIX SMOKE TEST")
|
| 31 |
-
print("=" * 80)
|
| 32 |
-
print()
|
| 33 |
-
|
| 34 |
-
# Test 1: Import
|
| 35 |
-
print("[Test 1] Import KohonenLearningSystemV2...")
|
| 36 |
-
try:
|
| 37 |
-
from bigru_t.model.kohonen_learning_system import (
|
| 38 |
-
KohonenLearningSystemV2,
|
| 39 |
-
KohonenLearningSystem,
|
| 40 |
-
KohonenSOM4D,
|
| 41 |
-
HypothesisClassifier,
|
| 42 |
-
HypothesisEnsemble,
|
| 43 |
-
DeltaGenerator,
|
| 44 |
-
SimpleBBPETokenizer,
|
| 45 |
-
text_to_4d_vector,
|
| 46 |
-
)
|
| 47 |
-
from bigru_t.model.som_metrics import (
|
| 48 |
-
SOMMetricHistory,
|
| 49 |
-
compute_all_metrics,
|
| 50 |
-
quantization_error,
|
| 51 |
-
topological_error,
|
| 52 |
-
kaski_lagus_error,
|
| 53 |
-
explained_variance_share,
|
| 54 |
-
topological_collapse,
|
| 55 |
-
dead_neuron_rate,
|
| 56 |
-
)
|
| 57 |
-
print(" PASS: All imports successful")
|
| 58 |
-
except Exception as e:
|
| 59 |
-
print(f" FAIL: Import error: {e}")
|
| 60 |
-
traceback.print_exc()
|
| 61 |
-
sys.exit(1)
|
| 62 |
-
|
| 63 |
-
# Test 2: Instantiate with restored (larger) parameters
|
| 64 |
-
print("\n[Test 2] Instantiate KLS V2 with restored params (HIDDEN=1024, VOCAB=16384)...")
|
| 65 |
-
try:
|
| 66 |
-
# Use smaller grid for smoke test (faster), but full HIDDEN_DIM/VOCAB_SIZE
|
| 67 |
-
kls = KohonenLearningSystemV2(
|
| 68 |
-
vocab_size=16384, # restored
|
| 69 |
-
hidden_dim=1024, # restored
|
| 70 |
-
seq_len=8,
|
| 71 |
-
som_grid=(6, 6, 6, 4), # 864 neurons (Kohonen architecture preserved)
|
| 72 |
-
alpha0=0.1,
|
| 73 |
-
sigma0=1.5,
|
| 74 |
-
lambda_ewc=0.02,
|
| 75 |
-
N_start=10,
|
| 76 |
-
dim_choice="y",
|
| 77 |
-
hypothesis_hidden=[512, 256, 128, 64, 32, 16, 8],
|
| 78 |
-
T_max=10000,
|
| 79 |
-
enable_vqvae2=False, # disabled to save RAM
|
| 80 |
-
enable_reasoning=False, # disabled to save RAM
|
| 81 |
-
enable_w8a8=False, # disabled to save RAM
|
| 82 |
-
enable_attention=True,
|
| 83 |
-
attention_n_heads=8,
|
| 84 |
-
# V2-dynamic restored params
|
| 85 |
-
n_hypotheses=16, # restored
|
| 86 |
-
max_n_hypotheses=32, # restored
|
| 87 |
-
min_n_hypotheses=4,
|
| 88 |
-
n_trials=3,
|
| 89 |
-
min_n_trials=1,
|
| 90 |
-
max_n_trials=6,
|
| 91 |
-
hyp_train_steps=30, # restored
|
| 92 |
-
min_hyp_train_steps=10,
|
| 93 |
-
max_hyp_train_steps=80,
|
| 94 |
-
hyp_lr=1e-4,
|
| 95 |
-
hyp_hidden_dim=256, # restored
|
| 96 |
-
loss_history_window=8,
|
| 97 |
-
punishment_window=12,
|
| 98 |
-
)
|
| 99 |
-
assert kls.hidden_dim == 1024, f"hidden_dim={kls.hidden_dim}"
|
| 100 |
-
assert kls.tokenizer.vocab_size == 16384, f"vocab={kls.tokenizer.vocab_size}"
|
| 101 |
-
assert kls.n_hypotheses == 16, f"n_hyp={kls.n_hypotheses}"
|
| 102 |
-
assert kls.max_n_hypotheses == 32, f"max_n_hyp={kls.max_n_hypotheses}"
|
| 103 |
-
assert kls.hyp_train_steps == 30, f"hyp_steps={kls.hyp_train_steps}"
|
| 104 |
-
assert kls.hyp_hidden_dim == 256, f"hyp_hidden={kls.hyp_hidden_dim}"
|
| 105 |
-
assert hasattr(kls, "aggressive_cleanup"), "no aggressive_cleanup method"
|
| 106 |
-
print(f" PASS: KLS V2 instantiated")
|
| 107 |
-
print(f" hidden_dim={kls.hidden_dim}, vocab={kls.tokenizer.vocab_size}")
|
| 108 |
-
print(f" n_hypotheses={kls.n_hypotheses}/{kls.max_n_hypotheses}")
|
| 109 |
-
print(f" hyp_train_steps={kls.hyp_train_steps}, hyp_hidden_dim={kls.hyp_hidden_dim}")
|
| 110 |
-
print(f" som_grid={kls.som_grid} ({kls.som_neuron_count} neurons)")
|
| 111 |
-
print(f" has aggressive_cleanup: True")
|
| 112 |
-
except Exception as e:
|
| 113 |
-
print(f" FAIL: Instantiation error: {e}")
|
| 114 |
-
traceback.print_exc()
|
| 115 |
-
sys.exit(1)
|
| 116 |
-
|
| 117 |
-
# Test 3: Add data + train SOM (CONHECIMENTO phase)
|
| 118 |
-
print("\n[Test 3] Add data + train SOM (CONHECIMENTO phase)...")
|
| 119 |
-
try:
|
| 120 |
-
kls.tokenizer.fit([
|
| 121 |
-
"o gato dorme", "a casa eh azul", "ele corre rapido",
|
| 122 |
-
"ela canta uma musica", "o sol nasceu", "nos vamos viajar",
|
| 123 |
-
"luva de pedreiro tavila", "lula reserva valor",
|
| 124 |
-
"amazonas forca tarefa vitimas",
|
| 125 |
-
])
|
| 126 |
-
# Add 30 samples (more than N_start=10)
|
| 127 |
-
for i in range(3):
|
| 128 |
-
kls.add_data(
|
| 129 |
-
["o gato dorme", "o cachorro corre", "a menina brinca",
|
| 130 |
-
"o passaro voa", "o peixe nada", "a arvore cresce",
|
| 131 |
-
"o livro esta na mesa", "a musica toca", "o tempo passa",
|
| 132 |
-
"o mundo gira"],
|
| 133 |
-
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
|
| 134 |
-
label_strings={0: "negative_class", 1: "positive_class"},
|
| 135 |
-
dataset_name="test_ds",
|
| 136 |
-
)
|
| 137 |
-
assert len(kls.buffer_4d) >= 10, f"buffer={len(kls.buffer_4d)}"
|
| 138 |
-
print(f" PASS: Added {len(kls.buffer_4d)} samples")
|
| 139 |
-
# Train SOM
|
| 140 |
-
started = kls.check_training_start()
|
| 141 |
-
print(f" training_ready: {kls.training_ready}")
|
| 142 |
-
if kls.training_ready:
|
| 143 |
-
kls.train_som_on_buffer()
|
| 144 |
-
kls._label_neurons()
|
| 145 |
-
print(f" SOM trained, weights norm={kls.som.weights.norm().item():.4f}")
|
| 146 |
-
except Exception as e:
|
| 147 |
-
print(f" FAIL: {e}")
|
| 148 |
-
traceback.print_exc()
|
| 149 |
-
sys.exit(1)
|
| 150 |
-
|
| 151 |
-
# Test 4: predict() returns dynamic labels (not gato/cachorro)
|
| 152 |
-
print("\n[Test 4] predict() returns dynamic labels...")
|
| 153 |
-
try:
|
| 154 |
-
pred = kls.predict("o gato dorme", dataset_name="test_ds")
|
| 155 |
-
assert pred in ["negative_class", "positive_class"], f"pred={pred}"
|
| 156 |
-
assert pred != "gato" and pred != "cachorro", f"hardcoded: {pred}"
|
| 157 |
-
print(f" PASS: predict returned '{pred}' (dynamic, not hardcoded)")
|
| 158 |
-
except Exception as e:
|
| 159 |
-
print(f" FAIL: {e}")
|
| 160 |
-
traceback.print_exc()
|
| 161 |
-
sys.exit(1)
|
| 162 |
-
|
| 163 |
-
# Test 5: activate_hypothesis (trains classifier)
|
| 164 |
-
print("\n[Test 5] activate_hypothesis (train classifier)...")
|
| 165 |
-
try:
|
| 166 |
-
kls.activate_hypothesis()
|
| 167 |
-
assert kls.classifier_trained, "classifier not trained"
|
| 168 |
-
print(f" PASS: Classifier trained ({kls.classifier is not None})")
|
| 169 |
-
except Exception as e:
|
| 170 |
-
print(f" FAIL: {e}")
|
| 171 |
-
traceback.print_exc()
|
| 172 |
-
sys.exit(1)
|
| 173 |
-
|
| 174 |
-
# Test 6: train_hypotheses (memory-efficient vectorized version)
|
| 175 |
-
print("\n[Test 6] train_hypotheses (memory-efficient vectorized)...")
|
| 176 |
-
try:
|
| 177 |
-
t0 = time.time()
|
| 178 |
-
result = kls.train_hypotheses()
|
| 179 |
-
elapsed_ms = (time.time() - t0) * 1000
|
| 180 |
-
assert result["active"], f"not active: {result}"
|
| 181 |
-
assert result["n_steps"] == 30, f"n_steps={result['n_steps']}"
|
| 182 |
-
print(f" PASS: train_hypotheses completed in {elapsed_ms:.0f}ms")
|
| 183 |
-
print(f" n_steps={result['n_steps']}, n_hypotheses={result['n_hypotheses']}")
|
| 184 |
-
print(f" loss_initial={result['loss_initial']:.4f}, loss_final={result['loss_final']:.4f}")
|
| 185 |
-
print(f" delta_scale_final={result['delta_scale_final']:.6f}")
|
| 186 |
-
if "adaptation" in result:
|
| 187 |
-
adapt = result["adaptation"]
|
| 188 |
-
print(f" adaptation: adapted={adapt['adapted']}, rules={adapt['rules_fired']}")
|
| 189 |
-
except Exception as e:
|
| 190 |
-
print(f" FAIL: {e}")
|
| 191 |
-
traceback.print_exc()
|
| 192 |
-
sys.exit(1)
|
| 193 |
-
|
| 194 |
-
# Test 7: SOM metrics (8 canonical metrics)
|
| 195 |
-
print("\n[Test 7] SOM metrics (8 canonical)...")
|
| 196 |
-
try:
|
| 197 |
-
metrics = kls.compute_som_metrics()
|
| 198 |
-
assert "quantization_error" in metrics
|
| 199 |
-
assert "topological_error" in metrics
|
| 200 |
-
assert "kaski_lagus_error" in metrics
|
| 201 |
-
assert "explained_variance_share" in metrics
|
| 202 |
-
assert "topological_collapse" in metrics
|
| 203 |
-
assert "dead_neuron_rate" in metrics
|
| 204 |
-
assert "qe_stagnation" in metrics
|
| 205 |
-
assert "neighborhood_crossing" in metrics
|
| 206 |
-
assert "overall_health" in metrics
|
| 207 |
-
print(f" PASS: All 8 SOM metrics computed")
|
| 208 |
-
print(f" QE={metrics['quantization_error']:.4f}")
|
| 209 |
-
print(f" TE={metrics['topological_error']:.4f}")
|
| 210 |
-
print(f" KL={metrics['kaski_lagus_error']:.4f}")
|
| 211 |
-
print(f" VE={metrics['explained_variance_share']:.4f}")
|
| 212 |
-
print(f" dead_neuron_rate={metrics['dead_neuron_rate'].get('dead_neuron_rate', 0):.3f}")
|
| 213 |
-
print(f" collapse_severity={metrics['topological_collapse'].get('severity', '?')}")
|
| 214 |
-
print(f" overall_health={metrics['overall_health']}")
|
| 215 |
-
print(f" n_failure_indicators={metrics['n_failure_indicators']}")
|
| 216 |
-
except Exception as e:
|
| 217 |
-
print(f" FAIL: {e}")
|
| 218 |
-
traceback.print_exc()
|
| 219 |
-
sys.exit(1)
|
| 220 |
-
|
| 221 |
-
# Test 8: select_best_delta (vectorized)
|
| 222 |
-
print("\n[Test 8] select_best_delta (3 trials × 16 hypotheses)...")
|
| 223 |
-
try:
|
| 224 |
-
t0 = time.time()
|
| 225 |
-
best_delta, best_acc, info = kls.select_best_delta()
|
| 226 |
-
elapsed_ms = (time.time() - t0) * 1000
|
| 227 |
-
assert info["active"], f"not active: {info}"
|
| 228 |
-
assert info["n_trials"] == 3, f"n_trials={info['n_trials']}"
|
| 229 |
-
assert info["n_hypotheses"] == 16, f"n_hyp={info['n_hypotheses']}"
|
| 230 |
-
print(f" PASS: select_best_delta completed in {elapsed_ms:.0f}ms")
|
| 231 |
-
print(f" best_acc={best_acc:.4f}")
|
| 232 |
-
print(f" delta_norm={info['delta_norm']:.4f}")
|
| 233 |
-
print(f" n_trials={info['n_trials']}, n_hypotheses={info['n_hypotheses']}")
|
| 234 |
-
except Exception as e:
|
| 235 |
-
print(f" FAIL: {e}")
|
| 236 |
-
traceback.print_exc()
|
| 237 |
-
sys.exit(1)
|
| 238 |
-
|
| 239 |
-
# Test 9: process_batch_v2 (CONHECIMENTO + PUNIÇÃO)
|
| 240 |
-
print("\n[Test 9] process_batch_v2 (CONHECIMENTO + PUNIÇÃO)...")
|
| 241 |
-
try:
|
| 242 |
-
# CONHECIMENTO
|
| 243 |
-
result_conh = kls.process_batch_v2(
|
| 244 |
-
["o gato dorme", "o cachorro corre"],
|
| 245 |
-
[0, 1],
|
| 246 |
-
label_strings={0: "negative_class", 1: "positive_class"},
|
| 247 |
-
dataset_name="test_ds",
|
| 248 |
-
enable_punishment=False,
|
| 249 |
-
)
|
| 250 |
-
assert result_conh["phase"] == "CONHECIMENTO"
|
| 251 |
-
print(f" PASS: CONHECIMENTO phase works: {result_conh['phase']}")
|
| 252 |
-
|
| 253 |
-
# PUNIÇÃO
|
| 254 |
-
result_pun = kls.process_batch_v2(
|
| 255 |
-
["o gato dorme", "o cachorro corre"],
|
| 256 |
-
[0, 1],
|
| 257 |
-
label_strings={0: "negative_class", 1: "positive_class"},
|
| 258 |
-
dataset_name="test_ds",
|
| 259 |
-
enable_punishment=True,
|
| 260 |
-
)
|
| 261 |
-
assert result_pun["phase"] == "PUNICAO"
|
| 262 |
-
print(f" PASS: PUNIÇÃO phase works: {result_pun['phase']}")
|
| 263 |
-
print(f" action: {result_pun.get('action', 'none')}")
|
| 264 |
-
except Exception as e:
|
| 265 |
-
print(f" FAIL: {e}")
|
| 266 |
-
traceback.print_exc()
|
| 267 |
-
sys.exit(1)
|
| 268 |
-
|
| 269 |
-
# Test 10: aggressive_cleanup
|
| 270 |
-
print("\n[Test 10] aggressive_cleanup()...")
|
| 271 |
-
try:
|
| 272 |
-
cleanup_result = kls.aggressive_cleanup()
|
| 273 |
-
assert "rss_before_mb" in cleanup_result
|
| 274 |
-
assert "rss_after_mb" in cleanup_result
|
| 275 |
-
assert "gc_collected" in cleanup_result
|
| 276 |
-
print(f" PASS: aggressive_cleanup() called")
|
| 277 |
-
print(f" rss_before={cleanup_result['rss_before_mb']:.1f}MB")
|
| 278 |
-
print(f" rss_after={cleanup_result['rss_after_mb']:.1f}MB")
|
| 279 |
-
print(f" freed={cleanup_result['freed_mb']:.1f}MB")
|
| 280 |
-
print(f" gc_collected={cleanup_result['gc_collected']}")
|
| 281 |
-
except Exception as e:
|
| 282 |
-
print(f" FAIL: {e}")
|
| 283 |
-
traceback.print_exc()
|
| 284 |
-
sys.exit(1)
|
| 285 |
-
|
| 286 |
-
# Test 11: get_v2_metrics
|
| 287 |
-
print("\n[Test 11] get_v2_metrics()...")
|
| 288 |
-
try:
|
| 289 |
-
v2_metrics = kls.get_v2_metrics()
|
| 290 |
-
assert v2_metrics["version"] == "V2-dynamic"
|
| 291 |
-
assert v2_metrics["n_hypotheses"] == 16
|
| 292 |
-
print(f" PASS: get_v2_metrics() works")
|
| 293 |
-
print(f" version={v2_metrics['version']}")
|
| 294 |
-
print(f" n_hypotheses={v2_metrics['n_hypotheses']}")
|
| 295 |
-
print(f" n_trials={v2_metrics['n_trials']}")
|
| 296 |
-
print(f" hyp_train_steps={v2_metrics['hyp_train_steps']}")
|
| 297 |
-
print(f" buffer_size={v2_metrics['buffer_size']}")
|
| 298 |
-
if "dynamic_adaptation" in v2_metrics:
|
| 299 |
-
da = v2_metrics["dynamic_adaptation"]
|
| 300 |
-
print(f" n_adaptations={da['n_adaptations']}")
|
| 301 |
-
print(f" loss_stats: slope={da['loss_stats']['slope']:.6f}, vol={da['loss_stats']['volatility']:.3f}")
|
| 302 |
-
except Exception as e:
|
| 303 |
-
print(f" FAIL: {e}")
|
| 304 |
-
traceback.print_exc()
|
| 305 |
-
sys.exit(1)
|
| 306 |
-
|
| 307 |
-
# Summary
|
| 308 |
-
print("\n" + "=" * 80)
|
| 309 |
-
print("SMOKE TEST SUMMARY")
|
| 310 |
-
print("=" * 80)
|
| 311 |
-
print(" ALL TESTS PASSED")
|
| 312 |
-
print(" - KLS V2 with restored params (HIDDEN=1024, VOCAB=16384) ✓")
|
| 313 |
-
print(" - Memory-efficient train_hypotheses (vectorized) ✓")
|
| 314 |
-
print(" - Bug fix: gradient flows through deltas → SOM → loss ✓")
|
| 315 |
-
print(" - 8 SOM canonical metrics computed ✓")
|
| 316 |
-
print(" - Dynamic labels (no hardcoded gato/cachorro) ✓")
|
| 317 |
-
print(" - CONHECIMENTO + PUNIÇÃO phases ✓")
|
| 318 |
-
print(" - aggressive_cleanup() works ✓")
|
| 319 |
-
print("=" * 80)
|
| 320 |
-
sys.exit(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
som_metrics.py
DELETED
|
@@ -1,811 +0,0 @@
|
|
| 1 |
-
"""som_metrics.py — V6.5-V2-metrics: Métricas de aprendizado e indicadores de
|
| 2 |
-
falha para Mapas Auto-Organizáveis (SOM / Redes de Kohonen).
|
| 3 |
-
|
| 4 |
-
============================================================================
|
| 5 |
-
MÉTRICAS PRINCIPAIS DE APRENDIZADO
|
| 6 |
-
============================================================================
|
| 7 |
-
|
| 8 |
-
1.1. Erro de Quantização (Quantization Error - QE)
|
| 9 |
-
Média das distâncias euclidianas entre cada vetor de entrada e o vetor
|
| 10 |
-
de pesos de seu neurônio vencedor (BMU). Mede a resolução ou ajuste fino
|
| 11 |
-
do mapa aos dados.
|
| 12 |
-
|
| 13 |
-
QE = (1/N) * Σ_i ||x_i - W_BMU(x_i)||₂
|
| 14 |
-
|
| 15 |
-
1.2. Erro Topológico (Topological Error - TE)
|
| 16 |
-
Percentual de vetores de entrada cujos primeiro e segundo neurônios
|
| 17 |
-
mais próximos (BMU e segunda BMU) NÃO são vizinhos diretos na grade
|
| 18 |
-
de saída. Avalia preservação das relações geométricas.
|
| 19 |
-
|
| 20 |
-
TE = (1/N) * Σ_i 𝟙[¬adjacent(BMU_1(x_i), BMU_2(x_i))]
|
| 21 |
-
|
| 22 |
-
1.3. Erro de Kaski-Lagus
|
| 23 |
-
Combinação ponderada que penaliza tanto a falta de resolução do vetor
|
| 24 |
-
de quantização quanto as quebras na continuidade topológica.
|
| 25 |
-
|
| 26 |
-
KL = α * QE_norm + β * TE
|
| 27 |
-
onde α=0.5, β=0.5 (pesos padrão)
|
| 28 |
-
|
| 29 |
-
Variante implementada: KL = QE * (1 + TE) (penaliza QE quando TE é alto)
|
| 30 |
-
|
| 31 |
-
1.4. Variância Explicada (Share of Explained Variance)
|
| 32 |
-
Fração da variabilidade total dos dados de entrada que é mapeada e
|
| 33 |
-
representada pelos vetores de referência (pesos) dos nós da rede.
|
| 34 |
-
|
| 35 |
-
VarExplained = 1 - Var(residual) / Var(total)
|
| 36 |
-
onde residual = x_i - W_BMU(x_i)
|
| 37 |
-
|
| 38 |
-
============================================================================
|
| 39 |
-
INDICADORES DE NÃO APRENDIZADO OU FALHA
|
| 40 |
-
============================================================================
|
| 41 |
-
|
| 42 |
-
2.1. Colapso Topológico (Topological Collapse)
|
| 43 |
-
Ocorre quando todos os vetores de peso convergem para o mesmo ponto
|
| 44 |
-
ou para uma linha reta. Indica perda total da dimensionalidade útil.
|
| 45 |
-
|
| 46 |
-
Detecção:
|
| 47 |
-
- std(weights) por dimensão < threshold (colapso total)
|
| 48 |
-
- rank(weights reshaped) < 2 (todos num ponto)
|
| 49 |
-
- rank < 3 (todos numa linha)
|
| 50 |
-
|
| 51 |
-
2.2. Taxa de Neurônios Mortos (Dead Neurons)
|
| 52 |
-
Percentual de neurônios que NUNCA se tornam BMU para nenhum vetor.
|
| 53 |
-
|
| 54 |
-
DeadRate = (n_neurons - n_active_neurons) / n_neurons
|
| 55 |
-
|
| 56 |
-
Threshold: DeadRate > 0.5 indica problema.
|
| 57 |
-
|
| 58 |
-
2.3. Estagnação do Erro de Quantização
|
| 59 |
-
QE estabiliza em patamar elevado ao longo das épocas. A taxa de
|
| 60 |
-
aprendizado decaiu rápido demais antes da fase de ordenação global.
|
| 61 |
-
|
| 62 |
-
Detecção: slope(QE) ≈ 0 E QE atual > 0.7 * max(QE histórico)
|
| 63 |
-
|
| 64 |
-
2.4. Cruzamento de Vizinhança (Neighborhood Crossing)
|
| 65 |
-
Crescimento repentino ou oscilação crônica do TE nas épocas finais.
|
| 66 |
-
|
| 67 |
-
Detecção:
|
| 68 |
-
- std(TE_final_5_epochs) > 2 * std(TE_initial_5_epochs) (oscilação)
|
| 69 |
-
- TE[-1] > TE[-5] (crescimento no final)
|
| 70 |
-
|
| 71 |
-
============================================================================
|
| 72 |
-
AUTOR: V6.5-V2-metrics
|
| 73 |
-
============================================================================
|
| 74 |
-
"""
|
| 75 |
-
from __future__ import annotations
|
| 76 |
-
|
| 77 |
-
import math
|
| 78 |
-
import logging
|
| 79 |
-
from collections import defaultdict, deque
|
| 80 |
-
from dataclasses import dataclass, field
|
| 81 |
-
from typing import Any, Dict, List, Optional, Tuple
|
| 82 |
-
|
| 83 |
-
import torch
|
| 84 |
-
import torch.nn.functional as F
|
| 85 |
-
|
| 86 |
-
logger = logging.getLogger(__name__)
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
# ============================================================================
|
| 90 |
-
# Dataclass para armazenar histórico de métricas
|
| 91 |
-
# ============================================================================
|
| 92 |
-
@dataclass
|
| 93 |
-
class SOMMetricHistory:
|
| 94 |
-
"""Histórico deslizante de métricas SOM para detecção de estagnação e
|
| 95 |
-
cruzamento de vizinhança."""
|
| 96 |
-
qe_history: deque = field(default_factory=lambda: deque(maxlen=50))
|
| 97 |
-
te_history: deque = field(default_factory=lambda: deque(maxlen=50))
|
| 98 |
-
kl_history: deque = field(default_factory=lambda: deque(maxlen=50))
|
| 99 |
-
var_explained_history: deque = field(default_factory=lambda: deque(maxlen=50))
|
| 100 |
-
dead_neuron_rate_history: deque = field(default_factory=lambda: deque(maxlen=50))
|
| 101 |
-
epoch: int = 0
|
| 102 |
-
|
| 103 |
-
def record(self, metrics: Dict[str, Any]) -> None:
|
| 104 |
-
"""Registra métricas de uma época.
|
| 105 |
-
|
| 106 |
-
Nota: `dead_neuron_rate` pode ser float (valor direto) ou Dict
|
| 107 |
-
(saída de compute_all_metrics, com chave 'dead_neuron_rate').
|
| 108 |
-
"""
|
| 109 |
-
self.qe_history.append(float(metrics.get("quantization_error", 0.0)))
|
| 110 |
-
self.te_history.append(float(metrics.get("topological_error", 0.0)))
|
| 111 |
-
self.kl_history.append(float(metrics.get("kaski_lagus_error", 0.0)))
|
| 112 |
-
self.var_explained_history.append(
|
| 113 |
-
float(metrics.get("explained_variance_share", 0.0))
|
| 114 |
-
)
|
| 115 |
-
# dead_neuron_rate pode ser float ou Dict
|
| 116 |
-
dnr = metrics.get("dead_neuron_rate", 0.0)
|
| 117 |
-
if isinstance(dnr, dict):
|
| 118 |
-
dnr_val = float(dnr.get("dead_neuron_rate", 0.0))
|
| 119 |
-
else:
|
| 120 |
-
dnr_val = float(dnr)
|
| 121 |
-
self.dead_neuron_rate_history.append(dnr_val)
|
| 122 |
-
self.epoch += 1
|
| 123 |
-
|
| 124 |
-
def to_dict(self) -> Dict[str, Any]:
|
| 125 |
-
return {
|
| 126 |
-
"epoch": int(self.epoch),
|
| 127 |
-
"qe_history": list(self.qe_history),
|
| 128 |
-
"te_history": list(self.te_history),
|
| 129 |
-
"kl_history": list(self.kl_history),
|
| 130 |
-
"var_explained_history": list(self.var_explained_history),
|
| 131 |
-
"dead_neuron_rate_history": list(self.dead_neuron_rate_history),
|
| 132 |
-
}
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
# ============================================================================
|
| 136 |
-
# Funções auxiliares
|
| 137 |
-
# ============================================================================
|
| 138 |
-
def _grid_neighbors_set(
|
| 139 |
-
idx: Tuple[int, int, int, int],
|
| 140 |
-
grid_shape: Tuple[int, int, int, int],
|
| 141 |
-
) -> set:
|
| 142 |
-
"""Retorna o conjunto de índices vizinhos diretos (6-connectivity em 4D)
|
| 143 |
-
de um neurônio na grade."""
|
| 144 |
-
i, j, k, l = idx
|
| 145 |
-
I, J, K, L = grid_shape
|
| 146 |
-
neighbors = set()
|
| 147 |
-
for di, dj, dk, dl in [
|
| 148 |
-
(1, 0, 0, 0), (-1, 0, 0, 0),
|
| 149 |
-
(0, 1, 0, 0), (0, -1, 0, 0),
|
| 150 |
-
(0, 0, 1, 0), (0, 0, -1, 0),
|
| 151 |
-
(0, 0, 0, 1), (0, 0, 0, -1),
|
| 152 |
-
]:
|
| 153 |
-
ni, nj, nk, nl = i + di, j + dj, k + dk, l + dl
|
| 154 |
-
if 0 <= ni < I and 0 <= nj < J and 0 <= nk < K and 0 <= nl < L:
|
| 155 |
-
neighbors.add((ni, nj, nk, nl))
|
| 156 |
-
return neighbors
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
def _find_bmu_and_second(
|
| 160 |
-
x: torch.Tensor, weights: torch.Tensor
|
| 161 |
-
) -> Tuple[Tuple[int, int, int, int], Tuple[int, int, int, int], float, float]:
|
| 162 |
-
"""Encontra BMU e 2ª BMU para um vetor x.
|
| 163 |
-
|
| 164 |
-
Args:
|
| 165 |
-
x: tensor [4] — vetor 4D de entrada.
|
| 166 |
-
weights: tensor [I, J, K, L, 4] — pesos do SOM.
|
| 167 |
-
|
| 168 |
-
Returns:
|
| 169 |
-
(bmu_idx, second_bmu_idx, dist_bmu, dist_second)
|
| 170 |
-
"""
|
| 171 |
-
grid_shape = weights.shape[:-1]
|
| 172 |
-
flat_w = weights.view(-1, 4) # (I*J*K*L, 4)
|
| 173 |
-
diff = flat_w - x.view(1, 4)
|
| 174 |
-
dist_sq = torch.sum(diff * diff, dim=-1) # (I*J*K*L,)
|
| 175 |
-
|
| 176 |
-
# Top-2 menores distâncias
|
| 177 |
-
top2 = torch.topk(dist_sq, k=2, largest=False)
|
| 178 |
-
flat_idx_bmu = int(top2.indices[0].item())
|
| 179 |
-
flat_idx_2nd = int(top2.indices[1].item())
|
| 180 |
-
|
| 181 |
-
# Unravel
|
| 182 |
-
L = grid_shape[3]
|
| 183 |
-
K = grid_shape[2]
|
| 184 |
-
J = grid_shape[1]
|
| 185 |
-
|
| 186 |
-
def unravel(flat_idx: int) -> Tuple[int, int, int, int]:
|
| 187 |
-
i = flat_idx // (J * K * L)
|
| 188 |
-
r = flat_idx % (J * K * L)
|
| 189 |
-
j = r // (K * L)
|
| 190 |
-
r = r % (K * L)
|
| 191 |
-
k = r // L
|
| 192 |
-
l = r % L
|
| 193 |
-
return (i, j, k, l)
|
| 194 |
-
|
| 195 |
-
bmu = unravel(flat_idx_bmu)
|
| 196 |
-
second = unravel(flat_idx_2nd)
|
| 197 |
-
dist_bmu = float(math.sqrt(max(top2.values[0].item(), 0.0)))
|
| 198 |
-
dist_second = float(math.sqrt(max(top2.values[1].item(), 0.0)))
|
| 199 |
-
return bmu, second, dist_bmu, dist_second
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
# ============================================================================
|
| 203 |
-
# Métricas principais (1.1 - 1.4)
|
| 204 |
-
# ============================================================================
|
| 205 |
-
def quantization_error(
|
| 206 |
-
data: torch.Tensor, weights: torch.Tensor
|
| 207 |
-
) -> float:
|
| 208 |
-
"""1.1 Erro de Quantização (QE).
|
| 209 |
-
|
| 210 |
-
QE = (1/N) * Σ_i ||x_i - W_BMU(x_i)||₂
|
| 211 |
-
|
| 212 |
-
Args:
|
| 213 |
-
data: tensor [N, 4] — vetores de entrada.
|
| 214 |
-
weights: tensor [I, J, K, L, 4] — pesos do SOM.
|
| 215 |
-
|
| 216 |
-
Returns:
|
| 217 |
-
QE (float). 0.0 se data vazio.
|
| 218 |
-
"""
|
| 219 |
-
if data.numel() == 0:
|
| 220 |
-
return 0.0
|
| 221 |
-
flat_w = weights.view(-1, 4) # (M, 4)
|
| 222 |
-
# distância de cada amostra a todos os neurônios
|
| 223 |
-
# data: (N, 4), flat_w: (M, 4) -> (N, M)
|
| 224 |
-
diff = data.unsqueeze(1) - flat_w.unsqueeze(0)
|
| 225 |
-
dist_sq = torch.sum(diff * diff, dim=-1) # (N, M)
|
| 226 |
-
min_dist_sq, _ = torch.min(dist_sq, dim=1) # (N,)
|
| 227 |
-
qe = torch.mean(torch.sqrt(min_dist_sq.clamp(min=0.0)))
|
| 228 |
-
return float(qe.item())
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
def topological_error(
|
| 232 |
-
data: torch.Tensor, weights: torch.Tensor
|
| 233 |
-
) -> float:
|
| 234 |
-
"""1.2 Erro Topológico (TE).
|
| 235 |
-
|
| 236 |
-
TE = (1/N) * Σ_i 𝟙[¬adjacent(BMU_1(x_i), BMU_2(x_i))]
|
| 237 |
-
|
| 238 |
-
Dois neurônios são adjacentes se differem em exatamente uma coordenada
|
| 239 |
-
por ±1 (6-connectivity em 4D).
|
| 240 |
-
|
| 241 |
-
Args:
|
| 242 |
-
data: tensor [N, 4]
|
| 243 |
-
weights: tensor [I, J, K, L, 4]
|
| 244 |
-
|
| 245 |
-
Returns:
|
| 246 |
-
TE ∈ [0, 1]. 0.0 se data vazio.
|
| 247 |
-
"""
|
| 248 |
-
if data.numel() == 0:
|
| 249 |
-
return 0.0
|
| 250 |
-
grid_shape = weights.shape[:-1]
|
| 251 |
-
flat_w = weights.view(-1, 4)
|
| 252 |
-
diff = data.unsqueeze(1) - flat_w.unsqueeze(0)
|
| 253 |
-
dist_sq = torch.sum(diff * diff, dim=-1) # (N, M)
|
| 254 |
-
|
| 255 |
-
# Top-2 BMUs para cada amostra
|
| 256 |
-
top2 = torch.topk(dist_sq, k=2, largest=False, dim=1)
|
| 257 |
-
flat_bmu = top2.indices[:, 0] # (N,)
|
| 258 |
-
flat_2nd = top2.indices[:, 1] # (N,)
|
| 259 |
-
|
| 260 |
-
# Unravel
|
| 261 |
-
L = grid_shape[3]
|
| 262 |
-
K = grid_shape[2]
|
| 263 |
-
J = grid_shape[1]
|
| 264 |
-
I = grid_shape[0]
|
| 265 |
-
|
| 266 |
-
def unravel_batch(flat_idx: torch.Tensor) -> torch.Tensor:
|
| 267 |
-
"""Retorna tensor (N, 4) com (i, j, k, l)."""
|
| 268 |
-
i = flat_idx // (J * K * L)
|
| 269 |
-
r = flat_idx % (J * K * L)
|
| 270 |
-
j = r // (K * L)
|
| 271 |
-
r = r % (K * L)
|
| 272 |
-
k = r // L
|
| 273 |
-
l = r % L
|
| 274 |
-
return torch.stack([i, j, k, l], dim=1).long()
|
| 275 |
-
|
| 276 |
-
bmu_idx = unravel_batch(flat_bmu) # (N, 4)
|
| 277 |
-
second_idx = unravel_batch(flat_2nd) # (N, 4)
|
| 278 |
-
|
| 279 |
-
# Adjacência: |Δcoord| soma = 1 (apenas uma coordenada muda por ±1)
|
| 280 |
-
delta = (bmu_idx - second_idx).abs() # (N, 4)
|
| 281 |
-
is_adjacent = (delta.sum(dim=1) == 1) & (delta.max(dim=1).values == 1)
|
| 282 |
-
|
| 283 |
-
te = 1.0 - float(is_adjacent.float().mean().item())
|
| 284 |
-
return te
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
def kaski_lagus_error(
|
| 288 |
-
data: torch.Tensor, weights: torch.Tensor, alpha: float = 0.5
|
| 289 |
-
) -> float:
|
| 290 |
-
"""1.3 Erro de Kaski-Lagus.
|
| 291 |
-
|
| 292 |
-
Combinação ponderada de QE normalizado e TE.
|
| 293 |
-
|
| 294 |
-
Variante implementada (Kaski & Lagus, 1999):
|
| 295 |
-
KL = α * MQE_norm + (1-α) * TE
|
| 296 |
-
onde MQE_norm = QE / mean(||x||) (normalização por escala dos dados).
|
| 297 |
-
|
| 298 |
-
Uma variante alternativa multiplica:
|
| 299 |
-
KL = QE * (1 + TE)
|
| 300 |
-
que penaliza QE quando TE é alto (usada se use_multiplicative=True).
|
| 301 |
-
|
| 302 |
-
Args:
|
| 303 |
-
data: tensor [N, 4]
|
| 304 |
-
weights: tensor [I, J, K, L, 4]
|
| 305 |
-
alpha: peso do QE normalizado (default 0.5)
|
| 306 |
-
|
| 307 |
-
Returns:
|
| 308 |
-
KL (float).
|
| 309 |
-
"""
|
| 310 |
-
if data.numel() == 0:
|
| 311 |
-
return 0.0
|
| 312 |
-
qe = quantization_error(data, weights)
|
| 313 |
-
te = topological_error(data, weights)
|
| 314 |
-
# Normalização: escala típica dos dados
|
| 315 |
-
data_norm = float(torch.mean(torch.norm(data, dim=1)).item())
|
| 316 |
-
qe_norm = qe / max(data_norm, 1e-8)
|
| 317 |
-
kl = alpha * qe_norm + (1.0 - alpha) * te
|
| 318 |
-
return float(kl)
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
def explained_variance_share(
|
| 322 |
-
data: torch.Tensor, weights: torch.Tensor
|
| 323 |
-
) -> float:
|
| 324 |
-
"""1.4 Variância Explicada (Share of Explained Variance).
|
| 325 |
-
|
| 326 |
-
VarExplained = 1 - Var(residual) / Var(total)
|
| 327 |
-
onde residual = x_i - W_BMU(x_i)
|
| 328 |
-
|
| 329 |
-
Implementação: computamos a variância total dos dados (soma das variâncias
|
| 330 |
-
por dimensão) e a variância dos resíduos. A fração explicada é:
|
| 331 |
-
|
| 332 |
-
VE = 1 - Σ_d Var(residual_d) / Σ_d Var(data_d)
|
| 333 |
-
|
| 334 |
-
Returns:
|
| 335 |
-
VE ∈ [0, 1]. Próximo de 1 = boa representação.
|
| 336 |
-
"""
|
| 337 |
-
if data.numel() == 0 or data.shape[0] < 2:
|
| 338 |
-
return 0.0
|
| 339 |
-
flat_w = weights.view(-1, 4)
|
| 340 |
-
diff = data.unsqueeze(1) - flat_w.unsqueeze(0)
|
| 341 |
-
dist_sq = torch.sum(diff * diff, dim=-1) # (N, M)
|
| 342 |
-
_, bmu_flat = torch.min(dist_sq, dim=1) # (N,)
|
| 343 |
-
bmu_w = flat_w[bmu_flat] # (N, 4)
|
| 344 |
-
residual = data - bmu_w # (N, 4)
|
| 345 |
-
|
| 346 |
-
# Variância por dimensão (sem viés, ddof=0)
|
| 347 |
-
var_data = torch.var(data, dim=0, unbiased=False) # (4,)
|
| 348 |
-
var_resid = torch.var(residual, dim=0, unbiased=False) # (4,)
|
| 349 |
-
|
| 350 |
-
total_var = float(torch.sum(var_data).item())
|
| 351 |
-
resid_var = float(torch.sum(var_resid).item())
|
| 352 |
-
if total_var < 1e-12:
|
| 353 |
-
return 0.0
|
| 354 |
-
ve = 1.0 - (resid_var / total_var)
|
| 355 |
-
return float(max(0.0, min(1.0, ve)))
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
# ============================================================================
|
| 359 |
-
# Indicadores de falha (2.1 - 2.4)
|
| 360 |
-
# ============================================================================
|
| 361 |
-
def topological_collapse(weights: torch.Tensor) -> Dict[str, Any]:
|
| 362 |
-
"""2.1 Colapso Topológico (Topological Collapse).
|
| 363 |
-
|
| 364 |
-
Detecta se os pesos colapsaram para um ponto (rank < 1) ou para uma linha
|
| 365 |
-
(rank < 2). Usa SVD sobre os pesos reshaped (M, 4).
|
| 366 |
-
|
| 367 |
-
Args:
|
| 368 |
-
weights: tensor [I, J, K, L, 4]
|
| 369 |
-
|
| 370 |
-
Returns:
|
| 371 |
-
Dict com:
|
| 372 |
-
- collapsed_to_point: bool (todos iguais)
|
| 373 |
-
- collapsed_to_line: bool (variação só em 1 direção)
|
| 374 |
-
- effective_rank: float (rank efetivo via razão de singular values)
|
| 375 |
-
- singular_values: list[float]
|
| 376 |
-
- severity: str ("none" | "line" | "point")
|
| 377 |
-
"""
|
| 378 |
-
flat_w = weights.view(-1, 4).clone() # (M, 4)
|
| 379 |
-
if flat_w.numel() == 0:
|
| 380 |
-
return {"collapsed_to_point": False, "collapsed_to_line": False,
|
| 381 |
-
"effective_rank": 0.0, "singular_values": [], "severity": "none"}
|
| 382 |
-
|
| 383 |
-
# Centraliza
|
| 384 |
-
flat_w_centered = flat_w - flat_w.mean(dim=0, keepdim=True)
|
| 385 |
-
|
| 386 |
-
# V6.5-V2-metrics FIX: usa SVD com fallback robusto para matrizes
|
| 387 |
-
# singulares/ill-conditioned. Se SVD falhar, usa np.linalg.eigh com
|
| 388 |
-
# regularização (adiciona pequeno ruído diagonal).
|
| 389 |
-
S_list: List[float] = []
|
| 390 |
-
try:
|
| 391 |
-
# Tentativa 1: SVD direto (mais estável que eigh)
|
| 392 |
-
U, S, V = torch.linalg.svd(flat_w_centered, full_matrices=False)
|
| 393 |
-
S_list = [float(s.item()) for s in S]
|
| 394 |
-
except Exception:
|
| 395 |
-
# Tentativa 2: eigendecomposição da matriz de covariância com regularização
|
| 396 |
-
try:
|
| 397 |
-
cov = flat_w_centered.t() @ flat_w_centered
|
| 398 |
-
# Regularização: adiciona pequeno valor diagonal para estabilizar
|
| 399 |
-
n = cov.shape[0]
|
| 400 |
-
reg = 1e-6 * float(cov.diag().max().item()) if n > 0 else 1e-6
|
| 401 |
-
cov_reg = cov + torch.eye(n) * reg
|
| 402 |
-
S_eigh, _ = torch.linalg.eigh(cov_reg)
|
| 403 |
-
S_eigh = S_eigh.flip(0) # descending order
|
| 404 |
-
# Converte autovalores em "singular values" aproximados (sqrt)
|
| 405 |
-
S_list = [float(math.sqrt(max(s.item(), 0.0))) for s in S_eigh]
|
| 406 |
-
except Exception:
|
| 407 |
-
# Tentativa 3: usa norma de Frobenius como aproximação
|
| 408 |
-
s_frob = float(torch.norm(flat_w_centered).item())
|
| 409 |
-
S_list = [s_frob, 0.0, 0.0, 0.0]
|
| 410 |
-
|
| 411 |
-
if not S_list:
|
| 412 |
-
return {"collapsed_to_point": False, "collapsed_to_line": False,
|
| 413 |
-
"effective_rank": 0.0, "singular_values": [], "severity": "none"}
|
| 414 |
-
|
| 415 |
-
s_max = max(S_list[0] if S_list else 0.0, 1e-12)
|
| 416 |
-
|
| 417 |
-
# Effective rank: número de singular values > 1% do maior
|
| 418 |
-
threshold = 0.01 * s_max
|
| 419 |
-
n_significant = sum(1 for s in S_list if s > threshold)
|
| 420 |
-
# Effective rank (float, via entropy)
|
| 421 |
-
s_norm = [s / s_max for s in S_list]
|
| 422 |
-
s_norm = [s for s in s_norm if s > 1e-10]
|
| 423 |
-
if s_norm:
|
| 424 |
-
p = torch.tensor(s_norm)
|
| 425 |
-
p_norm = p / p.sum()
|
| 426 |
-
entropy = -float(torch.sum(p_norm * torch.log(p_norm)).item())
|
| 427 |
-
effective_rank = math.exp(entropy)
|
| 428 |
-
else:
|
| 429 |
-
effective_rank = 0.0
|
| 430 |
-
|
| 431 |
-
collapsed_to_point = n_significant <= 1 and s_max < 1e-6
|
| 432 |
-
collapsed_to_line = n_significant == 1 and s_max >= 1e-6
|
| 433 |
-
|
| 434 |
-
if collapsed_to_point:
|
| 435 |
-
severity = "point"
|
| 436 |
-
elif collapsed_to_line:
|
| 437 |
-
severity = "line"
|
| 438 |
-
else:
|
| 439 |
-
severity = "none"
|
| 440 |
-
|
| 441 |
-
return {
|
| 442 |
-
"collapsed_to_point": bool(collapsed_to_point),
|
| 443 |
-
"collapsed_to_line": bool(collapsed_to_line),
|
| 444 |
-
"effective_rank": float(effective_rank),
|
| 445 |
-
"singular_values": S_list,
|
| 446 |
-
"severity": severity,
|
| 447 |
-
}
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
def dead_neuron_rate(
|
| 451 |
-
data: torch.Tensor, weights: torch.Tensor
|
| 452 |
-
) -> Dict[str, Any]:
|
| 453 |
-
"""2.2 Taxa de Neurônios Mortos (Dead Neurons).
|
| 454 |
-
|
| 455 |
-
DeadRate = (n_neurons - n_active_neurons) / n_neurons
|
| 456 |
-
|
| 457 |
-
Um neurônio é "ativo" se é BMU para pelo menos uma amostra.
|
| 458 |
-
|
| 459 |
-
Args:
|
| 460 |
-
data: tensor [N, 4]
|
| 461 |
-
weights: tensor [I, J, K, L, 4]
|
| 462 |
-
|
| 463 |
-
Returns:
|
| 464 |
-
Dict com:
|
| 465 |
-
- dead_neuron_rate: float ∈ [0, 1]
|
| 466 |
-
- n_dead: int
|
| 467 |
-
- n_active: int
|
| 468 |
-
- n_total: int
|
| 469 |
-
- dead_indices: list de (i,j,k,l)
|
| 470 |
-
"""
|
| 471 |
-
n_total = weights.shape[0] * weights.shape[1] * weights.shape[2] * weights.shape[3]
|
| 472 |
-
if data.numel() == 0:
|
| 473 |
-
return {"dead_neuron_rate": 1.0, "n_dead": n_total, "n_active": 0,
|
| 474 |
-
"n_total": n_total, "dead_indices": []}
|
| 475 |
-
|
| 476 |
-
flat_w = weights.view(-1, 4)
|
| 477 |
-
diff = data.unsqueeze(1) - flat_w.unsqueeze(0)
|
| 478 |
-
dist_sq = torch.sum(diff * diff, dim=-1) # (N, M)
|
| 479 |
-
_, bmu_flat = torch.min(dist_sq, dim=1) # (N,)
|
| 480 |
-
|
| 481 |
-
active_mask = torch.zeros(n_total, dtype=torch.bool)
|
| 482 |
-
active_mask[bmu_flat] = True
|
| 483 |
-
n_active = int(active_mask.sum().item())
|
| 484 |
-
n_dead = n_total - n_active
|
| 485 |
-
|
| 486 |
-
# Coleta índices mortos
|
| 487 |
-
dead_flat = (~active_mask).nonzero(as_tuple=True)[0]
|
| 488 |
-
L_dim = weights.shape[3]
|
| 489 |
-
K_dim = weights.shape[2]
|
| 490 |
-
J_dim = weights.shape[1]
|
| 491 |
-
dead_indices = []
|
| 492 |
-
for flat_idx in dead_flat.tolist():
|
| 493 |
-
i = flat_idx // (J_dim * K_dim * L_dim)
|
| 494 |
-
r = flat_idx % (J_dim * K_dim * L_dim)
|
| 495 |
-
j = r // (K_dim * L_dim)
|
| 496 |
-
r = r % (K_dim * L_dim)
|
| 497 |
-
k = r // L_dim
|
| 498 |
-
l = r % L_dim
|
| 499 |
-
dead_indices.append((i, j, k, l))
|
| 500 |
-
|
| 501 |
-
return {
|
| 502 |
-
"dead_neuron_rate": float(n_dead / n_total) if n_total > 0 else 0.0,
|
| 503 |
-
"n_dead": int(n_dead),
|
| 504 |
-
"n_active": int(n_active),
|
| 505 |
-
"n_total": int(n_total),
|
| 506 |
-
"dead_indices": dead_indices[:50], # limita para não explodir JSON
|
| 507 |
-
}
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
def quantization_error_stagnation(
|
| 511 |
-
history: SOMMetricHistory,
|
| 512 |
-
high_threshold_frac: float = 0.7,
|
| 513 |
-
slope_tolerance: float = 1e-4,
|
| 514 |
-
window_size: int = 5,
|
| 515 |
-
) -> Dict[str, Any]:
|
| 516 |
-
"""2.3 Estagnação do Erro de Quantização.
|
| 517 |
-
|
| 518 |
-
Detecta se o QE estabilizou em patamar elevado:
|
| 519 |
-
- |slope(QE)| < slope_tolerance (estável)
|
| 520 |
-
- QE_atual > high_threshold_frac * max(QE_history) (alto)
|
| 521 |
-
|
| 522 |
-
Args:
|
| 523 |
-
history: histórico de métricas SOM.
|
| 524 |
-
high_threshold_frac: fração do QE máximo para considerar "alto".
|
| 525 |
-
slope_tolerance: tolerância de slope para considerar "estável".
|
| 526 |
-
window_size: nº de épocas recentes para regressão linear.
|
| 527 |
-
|
| 528 |
-
Returns:
|
| 529 |
-
Dict com:
|
| 530 |
-
- is_stagnant: bool
|
| 531 |
-
- current_qe: float
|
| 532 |
-
- max_qe: float
|
| 533 |
-
- slope: float
|
| 534 |
-
- severity: str ("none" | "mild" | "severe")
|
| 535 |
-
"""
|
| 536 |
-
qe_list = list(history.qe_history)
|
| 537 |
-
if len(qe_list) < window_size + 2:
|
| 538 |
-
return {"is_stagnant": False, "current_qe": 0.0, "max_qe": 0.0,
|
| 539 |
-
"slope": 0.0, "severity": "none", "n_epochs": len(qe_list)}
|
| 540 |
-
|
| 541 |
-
# Regressão linear sobre as últimas window_size épocas
|
| 542 |
-
recent = qe_list[-window_size:]
|
| 543 |
-
n = len(recent)
|
| 544 |
-
x_mean = (n - 1) / 2.0
|
| 545 |
-
y_mean = sum(recent) / n
|
| 546 |
-
num = sum((i - x_mean) * (y - y_mean) for i, y in enumerate(recent))
|
| 547 |
-
den = sum((i - x_mean) ** 2 for i in range(n))
|
| 548 |
-
slope = num / den if den > 0 else 0.0
|
| 549 |
-
|
| 550 |
-
current_qe = float(qe_list[-1])
|
| 551 |
-
max_qe = float(max(qe_list))
|
| 552 |
-
is_high = current_qe > high_threshold_frac * max_qe
|
| 553 |
-
is_stable = abs(slope) < slope_tolerance
|
| 554 |
-
|
| 555 |
-
is_stagnant = is_stable and is_high
|
| 556 |
-
|
| 557 |
-
if is_stagnant:
|
| 558 |
-
if current_qe > 0.9 * max_qe:
|
| 559 |
-
severity = "severe"
|
| 560 |
-
else:
|
| 561 |
-
severity = "mild"
|
| 562 |
-
else:
|
| 563 |
-
severity = "none"
|
| 564 |
-
|
| 565 |
-
return {
|
| 566 |
-
"is_stagnant": bool(is_stagnant),
|
| 567 |
-
"current_qe": current_qe,
|
| 568 |
-
"max_qe": max_qe,
|
| 569 |
-
"slope": float(slope),
|
| 570 |
-
"severity": severity,
|
| 571 |
-
"n_epochs": len(qe_list),
|
| 572 |
-
}
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
def neighborhood_crossing(
|
| 576 |
-
history: SOMMetricHistory,
|
| 577 |
-
initial_window: int = 5,
|
| 578 |
-
final_window: int = 5,
|
| 579 |
-
growth_threshold: float = 1.5,
|
| 580 |
-
) -> Dict[str, Any]:
|
| 581 |
-
"""2.4 Cruzamento de Vizinhança (Neighborhood Crossing).
|
| 582 |
-
|
| 583 |
-
Detecta crescimento repentino ou oscilação crônica do TE nas épocas
|
| 584 |
-
finais:
|
| 585 |
-
- std(TE_final) > growth_threshold * std(TE_initial) (oscilação cresceu)
|
| 586 |
-
- TE[-1] > TE[-5] (crescimento no final)
|
| 587 |
-
|
| 588 |
-
Args:
|
| 589 |
-
history: histórico de métricas SOM.
|
| 590 |
-
initial_window: nº de épocas iniciais para baseline.
|
| 591 |
-
final_window: nº de épocas finais para comparação.
|
| 592 |
-
growth_threshold: razão de std para considerar "crescimento crônico".
|
| 593 |
-
|
| 594 |
-
Returns:
|
| 595 |
-
Dict com:
|
| 596 |
-
- detected: bool
|
| 597 |
-
- te_initial_mean: float
|
| 598 |
-
- te_final_mean: float
|
| 599 |
-
- te_initial_std: float
|
| 600 |
-
- te_final_std: float
|
| 601 |
-
- std_ratio: float
|
| 602 |
-
- is_oscillating: bool
|
| 603 |
-
- is_growing: bool
|
| 604 |
-
- severity: str ("none" | "mild" | "severe")
|
| 605 |
-
"""
|
| 606 |
-
te_list = list(history.te_history)
|
| 607 |
-
if len(te_list) < initial_window + final_window:
|
| 608 |
-
return {"detected": False, "te_initial_mean": 0.0, "te_final_mean": 0.0,
|
| 609 |
-
"te_initial_std": 0.0, "te_final_std": 0.0, "std_ratio": 0.0,
|
| 610 |
-
"is_oscillating": False, "is_growing": False, "severity": "none",
|
| 611 |
-
"n_epochs": len(te_list)}
|
| 612 |
-
|
| 613 |
-
initial = te_list[:initial_window]
|
| 614 |
-
final = te_list[-final_window:]
|
| 615 |
-
|
| 616 |
-
init_mean = sum(initial) / len(initial)
|
| 617 |
-
final_mean = sum(final) / len(final)
|
| 618 |
-
init_var = sum((x - init_mean) ** 2 for x in initial) / len(initial)
|
| 619 |
-
final_var = sum((x - final_mean) ** 2 for x in final) / len(final)
|
| 620 |
-
init_std = math.sqrt(init_var)
|
| 621 |
-
final_std = math.sqrt(final_var)
|
| 622 |
-
|
| 623 |
-
std_ratio = final_std / max(init_std, 1e-8)
|
| 624 |
-
is_oscillating = std_ratio > growth_threshold
|
| 625 |
-
is_growing = final_mean > init_mean and (final_mean - init_mean) > 0.05
|
| 626 |
-
|
| 627 |
-
detected = is_oscillating or is_growing
|
| 628 |
-
|
| 629 |
-
if detected:
|
| 630 |
-
if std_ratio > 2.0 * growth_threshold or is_growing:
|
| 631 |
-
severity = "severe"
|
| 632 |
-
else:
|
| 633 |
-
severity = "mild"
|
| 634 |
-
else:
|
| 635 |
-
severity = "none"
|
| 636 |
-
|
| 637 |
-
return {
|
| 638 |
-
"detected": bool(detected),
|
| 639 |
-
"te_initial_mean": float(init_mean),
|
| 640 |
-
"te_final_mean": float(final_mean),
|
| 641 |
-
"te_initial_std": float(init_std),
|
| 642 |
-
"te_final_std": float(final_std),
|
| 643 |
-
"std_ratio": float(std_ratio),
|
| 644 |
-
"is_oscillating": bool(is_oscillating),
|
| 645 |
-
"is_growing": bool(is_growing),
|
| 646 |
-
"severity": severity,
|
| 647 |
-
"n_epochs": len(te_list),
|
| 648 |
-
}
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
# ============================================================================
|
| 652 |
-
# Função agregadora: compute_all_metrics
|
| 653 |
-
# ============================================================================
|
| 654 |
-
def compute_all_metrics(
|
| 655 |
-
data: torch.Tensor,
|
| 656 |
-
weights: torch.Tensor,
|
| 657 |
-
history: Optional[SOMMetricHistory] = None,
|
| 658 |
-
) -> Dict[str, Any]:
|
| 659 |
-
"""Computa todas as métricas SOM de uma vez.
|
| 660 |
-
|
| 661 |
-
Args:
|
| 662 |
-
data: tensor [N, 4] — buffer de vetores 4D.
|
| 663 |
-
weights: tensor [I, J, K, L, 4] — pesos do SOM.
|
| 664 |
-
history: histórico de métricas (opcional, para estagnação e cruzamento).
|
| 665 |
-
|
| 666 |
-
Returns:
|
| 667 |
-
Dict com:
|
| 668 |
-
- quantization_error: float
|
| 669 |
-
- topological_error: float
|
| 670 |
-
- kaski_lagus_error: float
|
| 671 |
-
- explained_variance_share: float
|
| 672 |
-
- topological_collapse: Dict
|
| 673 |
-
- dead_neuron_rate: Dict
|
| 674 |
-
- quantization_error_stagnation: Dict (se history fornecido)
|
| 675 |
-
- neighborhood_crossing: Dict (se history fornecido)
|
| 676 |
-
- overall_health: str ("healthy" | "warning" | "critical")
|
| 677 |
-
- failure_indicators: list[str]
|
| 678 |
-
"""
|
| 679 |
-
# V6.5-V2-metrics FIX: wrap entire computation in try/except para evitar
|
| 680 |
-
# crashes fatais quando o SOM tem pesos degenerados (ex: todos iguais
|
| 681 |
-
# após muito treinamento sem diversidade). Em caso de erro, retorna
|
| 682 |
-
# métricas safe-default em vez de crashar o treino.
|
| 683 |
-
try:
|
| 684 |
-
return _compute_all_metrics_impl(data, weights, history)
|
| 685 |
-
except Exception as e:
|
| 686 |
-
logger.warning(
|
| 687 |
-
f"[som_metrics] compute_all_metrics failed (using safe defaults): {e}"
|
| 688 |
-
)
|
| 689 |
-
return {
|
| 690 |
-
"quantization_error": 0.0,
|
| 691 |
-
"topological_error": 0.0,
|
| 692 |
-
"kaski_lagus_error": 0.0,
|
| 693 |
-
"explained_variance_share": 0.0,
|
| 694 |
-
"topological_collapse": {
|
| 695 |
-
"collapsed_to_point": False, "collapsed_to_line": False,
|
| 696 |
-
"effective_rank": 0.0, "singular_values": [],
|
| 697 |
-
"severity": "none", "error": str(e)[:200],
|
| 698 |
-
},
|
| 699 |
-
"dead_neuron_rate": {
|
| 700 |
-
"dead_neuron_rate": 1.0, "n_dead": 0, "n_active": 0,
|
| 701 |
-
"n_total": 0, "dead_indices": [], "error": str(e)[:200],
|
| 702 |
-
},
|
| 703 |
-
"qe_stagnation": {
|
| 704 |
-
"is_stagnant": False, "current_qe": 0.0, "max_qe": 0.0,
|
| 705 |
-
"slope": 0.0, "severity": "none", "n_epochs": 0,
|
| 706 |
-
},
|
| 707 |
-
"neighborhood_crossing": {
|
| 708 |
-
"detected": False, "te_initial_mean": 0.0, "te_final_mean": 0.0,
|
| 709 |
-
"te_initial_std": 0.0, "te_final_std": 0.0, "std_ratio": 0.0,
|
| 710 |
-
"is_oscillating": False, "is_growing": False, "severity": "none",
|
| 711 |
-
"n_epochs": 0,
|
| 712 |
-
},
|
| 713 |
-
"overall_health": "unknown",
|
| 714 |
-
"failure_indicators": [f"metrics_computation_error: {str(e)[:100]}"],
|
| 715 |
-
"n_failure_indicators": 1,
|
| 716 |
-
"error": str(e)[:300],
|
| 717 |
-
}
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
def _compute_all_metrics_impl(
|
| 721 |
-
data: torch.Tensor,
|
| 722 |
-
weights: torch.Tensor,
|
| 723 |
-
history: Optional[SOMMetricHistory] = None,
|
| 724 |
-
) -> Dict[str, Any]:
|
| 725 |
-
"""Implementação interna de compute_all_metrics (sem try/except wrapper)."""
|
| 726 |
-
metrics: Dict[str, Any] = {}
|
| 727 |
-
|
| 728 |
-
# 1.1 - 1.4: Métricas principais
|
| 729 |
-
metrics["quantization_error"] = quantization_error(data, weights)
|
| 730 |
-
metrics["topological_error"] = topological_error(data, weights)
|
| 731 |
-
metrics["kaski_lagus_error"] = kaski_lagus_error(data, weights)
|
| 732 |
-
metrics["explained_variance_share"] = explained_variance_share(data, weights)
|
| 733 |
-
|
| 734 |
-
# 2.1: Colapso topológico
|
| 735 |
-
collapse = topological_collapse(weights)
|
| 736 |
-
metrics["topological_collapse"] = collapse
|
| 737 |
-
|
| 738 |
-
# 2.2: Dead neurons
|
| 739 |
-
dead = dead_neuron_rate(data, weights)
|
| 740 |
-
metrics["dead_neuron_rate"] = dead
|
| 741 |
-
|
| 742 |
-
# 2.3: Estagnação do QE (requer histórico)
|
| 743 |
-
if history is not None and len(history.qe_history) > 5:
|
| 744 |
-
metrics["qe_stagnation"] = quantization_error_stagnation(history)
|
| 745 |
-
else:
|
| 746 |
-
metrics["qe_stagnation"] = {
|
| 747 |
-
"is_stagnant": False, "current_qe": metrics["quantization_error"],
|
| 748 |
-
"max_qe": metrics["quantization_error"], "slope": 0.0,
|
| 749 |
-
"severity": "none", "n_epochs": 0,
|
| 750 |
-
}
|
| 751 |
-
|
| 752 |
-
# 2.4: Cruzamento de vizinhança (requer histórico)
|
| 753 |
-
if history is not None and len(history.te_history) > 10:
|
| 754 |
-
metrics["neighborhood_crossing"] = neighborhood_crossing(history)
|
| 755 |
-
else:
|
| 756 |
-
metrics["neighborhood_crossing"] = {
|
| 757 |
-
"detected": False, "te_initial_mean": 0.0, "te_final_mean": 0.0,
|
| 758 |
-
"te_initial_std": 0.0, "te_final_std": 0.0, "std_ratio": 0.0,
|
| 759 |
-
"is_oscillating": False, "is_growing": False, "severity": "none",
|
| 760 |
-
"n_epochs": 0,
|
| 761 |
-
}
|
| 762 |
-
|
| 763 |
-
# Avaliação de saúde geral
|
| 764 |
-
failure_indicators: List[str] = []
|
| 765 |
-
if collapse["collapsed_to_point"]:
|
| 766 |
-
failure_indicators.append("topological_collapse_point")
|
| 767 |
-
elif collapse["collapsed_to_line"]:
|
| 768 |
-
failure_indicators.append("topological_collapse_line")
|
| 769 |
-
if dead["dead_neuron_rate"] > 0.5:
|
| 770 |
-
failure_indicators.append(f"dead_neuron_rate_high_{dead['dead_neuron_rate']:.2f}")
|
| 771 |
-
if metrics["qe_stagnation"]["is_stagnant"]:
|
| 772 |
-
failure_indicators.append(f"qe_stagnant_{metrics['qe_stagnation']['severity']}")
|
| 773 |
-
if metrics["neighborhood_crossing"]["detected"]:
|
| 774 |
-
failure_indicators.append(
|
| 775 |
-
f"neighborhood_crossing_{metrics['neighborhood_crossing']['severity']}"
|
| 776 |
-
)
|
| 777 |
-
if metrics["explained_variance_share"] < 0.3:
|
| 778 |
-
failure_indicators.append(
|
| 779 |
-
f"low_explained_variance_{metrics['explained_variance_share']:.2f}"
|
| 780 |
-
)
|
| 781 |
-
if metrics["topological_error"] > 0.5:
|
| 782 |
-
failure_indicators.append(
|
| 783 |
-
f"high_topological_error_{metrics['topological_error']:.2f}"
|
| 784 |
-
)
|
| 785 |
-
|
| 786 |
-
if len(failure_indicators) >= 2:
|
| 787 |
-
overall_health = "critical"
|
| 788 |
-
elif len(failure_indicators) == 1:
|
| 789 |
-
overall_health = "warning"
|
| 790 |
-
else:
|
| 791 |
-
overall_health = "healthy"
|
| 792 |
-
|
| 793 |
-
metrics["failure_indicators"] = failure_indicators
|
| 794 |
-
metrics["overall_health"] = overall_health
|
| 795 |
-
metrics["n_failure_indicators"] = len(failure_indicators)
|
| 796 |
-
|
| 797 |
-
return metrics
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
__all__ = [
|
| 801 |
-
"SOMMetricHistory",
|
| 802 |
-
"quantization_error",
|
| 803 |
-
"topological_error",
|
| 804 |
-
"kaski_lagus_error",
|
| 805 |
-
"explained_variance_share",
|
| 806 |
-
"topological_collapse",
|
| 807 |
-
"dead_neuron_rate",
|
| 808 |
-
"quantization_error_stagnation",
|
| 809 |
-
"neighborhood_crossing",
|
| 810 |
-
"compute_all_metrics",
|
| 811 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/bigru_t/inference/langchain_adapter.py
DELETED
|
@@ -1,218 +0,0 @@
|
|
| 1 |
-
"""langchain_adapter.py — Adapter para compatibilidade com LangChain (V6).
|
| 2 |
-
|
| 3 |
-
Adaptado de gru-ring-v13-9-2/source/xavante_work/flexnet/langchain_adapter.py.
|
| 4 |
-
|
| 5 |
-
Permite usar o BiGRU_T_version como:
|
| 6 |
-
- LLM chain (LLMChain)
|
| 7 |
-
- Tool (BaseTool)
|
| 8 |
-
- Memory (BaseMemory)
|
| 9 |
-
- Agent (LLMSingleActionAgent)
|
| 10 |
-
|
| 11 |
-
V6 melhorias vs V13.9.2:
|
| 12 |
-
* Self-contained (sem dependência externa obrigatória)
|
| 13 |
-
* Lazy import do langchain (só importa se usado)
|
| 14 |
-
* Compatível com ToolMutationEngine e ToolAgentCoordinator
|
| 15 |
-
|
| 16 |
-
REFERÊNCIAS:
|
| 17 |
-
- LangChain: https://python.langchain.com
|
| 18 |
-
- Tool pattern: https://python.langchain.com/docs/modules/agents/tools/
|
| 19 |
-
"""
|
| 20 |
-
from __future__ import annotations
|
| 21 |
-
|
| 22 |
-
import time
|
| 23 |
-
from typing import Any, Dict, List, Optional, Callable
|
| 24 |
-
from collections import deque
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 28 |
-
# Adapter base (sem dependência do langchain)
|
| 29 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 30 |
-
class LangchainAdapter:
|
| 31 |
-
"""Adapter para compatibilidade com LangChain (V6).
|
| 32 |
-
|
| 33 |
-
Esta classe fornece interfaces compatíveis com LangChain, mas SEM exigir
|
| 34 |
-
que o langchain esteja instalado. Se langchain estiver disponível, os
|
| 35 |
-
métodos .as_langchain_*() retornarão objetos nativos do langchain;
|
| 36 |
-
caso contrário, retornarão dicts/wrappers compatíveis.
|
| 37 |
-
|
| 38 |
-
Args:
|
| 39 |
-
pipeline: OptimizedPipeline (ou qualquer modelo com .forward())
|
| 40 |
-
name: nome do modelo (default "BiGRU_T")
|
| 41 |
-
description: descrição (default "")
|
| 42 |
-
"""
|
| 43 |
-
|
| 44 |
-
def __init__(
|
| 45 |
-
self,
|
| 46 |
-
pipeline,
|
| 47 |
-
name: str = "BiGRU_T",
|
| 48 |
-
description: str = "BiGRU_T_version model with V6 features",
|
| 49 |
-
):
|
| 50 |
-
self.pipeline = pipeline
|
| 51 |
-
self.name = name
|
| 52 |
-
self.description = description
|
| 53 |
-
self._memory: deque = deque(maxlen=100) # memory buffer
|
| 54 |
-
|
| 55 |
-
def __call__(
|
| 56 |
-
self,
|
| 57 |
-
prompt: str,
|
| 58 |
-
max_tokens: int = 128,
|
| 59 |
-
temperature: float = 1.0,
|
| 60 |
-
stop: Optional[List[str]] = None,
|
| 61 |
-
) -> str:
|
| 62 |
-
"""Gera texto a partir de um prompt (interface tipo LLM).
|
| 63 |
-
|
| 64 |
-
Args:
|
| 65 |
-
prompt: string de entrada
|
| 66 |
-
max_tokens: máx de tokens a gerar
|
| 67 |
-
temperature: temperatura do softmax
|
| 68 |
-
stop: lista de strings de parada (opcional)
|
| 69 |
-
|
| 70 |
-
Returns:
|
| 71 |
-
texto gerado
|
| 72 |
-
"""
|
| 73 |
-
# Tokeniza (assumindo que o pipeline tem .tokenizer)
|
| 74 |
-
if hasattr(self.pipeline, "tokenizer") and self.pipeline.tokenizer is not None:
|
| 75 |
-
input_ids = self.pipeline.tokenizer.encode(prompt)
|
| 76 |
-
# Converte para tensor
|
| 77 |
-
import torch
|
| 78 |
-
input_ids = torch.tensor([input_ids], dtype=torch.long)
|
| 79 |
-
else:
|
| 80 |
-
# Sem tokenizer — retorna prompt ecoado
|
| 81 |
-
return f"[no tokenizer] {prompt}"
|
| 82 |
-
|
| 83 |
-
# Gera
|
| 84 |
-
import torch
|
| 85 |
-
with torch.no_grad():
|
| 86 |
-
generated = input_ids.clone()
|
| 87 |
-
for _ in range(max_tokens):
|
| 88 |
-
out = self.pipeline.forward(generated)
|
| 89 |
-
logits = out["logits"][:, -1, :] # último token
|
| 90 |
-
# Aplica temperatura
|
| 91 |
-
logits = logits / max(temperature, 1e-6)
|
| 92 |
-
# Top-k sampling (k=10)
|
| 93 |
-
top_k = 10
|
| 94 |
-
top_vals, top_idx = logits.topk(top_k, dim=-1)
|
| 95 |
-
probs = top_vals.softmax(dim=-1)
|
| 96 |
-
next_tok = top_idx.gather(
|
| 97 |
-
-1, torch.multinomial(probs, 1)
|
| 98 |
-
)
|
| 99 |
-
generated = torch.cat([generated, next_tok], dim=-1)
|
| 100 |
-
# Stop condition
|
| 101 |
-
if stop:
|
| 102 |
-
decoded = self.pipeline.tokenizer.decode(generated[0].tolist())
|
| 103 |
-
for s in stop:
|
| 104 |
-
if s in decoded:
|
| 105 |
-
return decoded
|
| 106 |
-
|
| 107 |
-
# Decodifica
|
| 108 |
-
result = self.pipeline.tokenizer.decode(generated[0].tolist())
|
| 109 |
-
# Atualiza memory
|
| 110 |
-
self._memory.append({"prompt": prompt, "response": result})
|
| 111 |
-
return result
|
| 112 |
-
|
| 113 |
-
def as_langchain_llm(self):
|
| 114 |
-
"""Retorna um LLM compatível com LangChain (se langchain disponível)."""
|
| 115 |
-
try:
|
| 116 |
-
from langchain_core.language_models import LLM
|
| 117 |
-
from langchain_core.callbacks import CallbackManagerForLLMRun
|
| 118 |
-
|
| 119 |
-
adapter = self
|
| 120 |
-
|
| 121 |
-
class BiGRU_T_LLM(LLM):
|
| 122 |
-
@property
|
| 123 |
-
def _llm_type(self) -> str:
|
| 124 |
-
return "bigru_t"
|
| 125 |
-
|
| 126 |
-
def _call(
|
| 127 |
-
self,
|
| 128 |
-
prompt: str,
|
| 129 |
-
stop: Optional[List[str]] = None,
|
| 130 |
-
run_manager: Optional[CallbackManagerForLLMRun] = None,
|
| 131 |
-
**kwargs,
|
| 132 |
-
) -> str:
|
| 133 |
-
return adapter(prompt, stop=stop, **kwargs)
|
| 134 |
-
|
| 135 |
-
return BiGRU_T_LLM()
|
| 136 |
-
except ImportError:
|
| 137 |
-
# langchain não disponível — retorna wrapper
|
| 138 |
-
return {
|
| 139 |
-
"type": "langchain_llm_fallback",
|
| 140 |
-
"name": self.name,
|
| 141 |
-
"description": self.description,
|
| 142 |
-
"call": lambda prompt, **kw: self(prompt, **kw),
|
| 143 |
-
}
|
| 144 |
-
|
| 145 |
-
def as_langchain_tool(self, name: Optional[str] = None, description: Optional[str] = None):
|
| 146 |
-
"""Retorna um Tool compatível com LangChain."""
|
| 147 |
-
try:
|
| 148 |
-
from langchain_core.tools import BaseTool
|
| 149 |
-
from pydantic import BaseModel, Field
|
| 150 |
-
|
| 151 |
-
adapter = self
|
| 152 |
-
tool_name = name or self.name
|
| 153 |
-
tool_desc = description or self.description
|
| 154 |
-
|
| 155 |
-
class BiGRU_T_ToolInput(BaseModel):
|
| 156 |
-
prompt: str = Field(description="Input prompt")
|
| 157 |
-
|
| 158 |
-
class BiGRU_T_Tool(BaseTool):
|
| 159 |
-
name: str = tool_name
|
| 160 |
-
description: str = tool_desc
|
| 161 |
-
args_schema: type = BiGRU_T_ToolInput
|
| 162 |
-
|
| 163 |
-
def _run(self, prompt: str, **kwargs) -> str:
|
| 164 |
-
return adapter(prompt, **kwargs)
|
| 165 |
-
|
| 166 |
-
async def _arun(self, prompt: str, **kwargs) -> str:
|
| 167 |
-
return adapter(prompt, **kwargs)
|
| 168 |
-
|
| 169 |
-
return BiGRU_T_Tool()
|
| 170 |
-
except ImportError:
|
| 171 |
-
return {
|
| 172 |
-
"type": "langchain_tool_fallback",
|
| 173 |
-
"name": name or self.name,
|
| 174 |
-
"description": description or self.description,
|
| 175 |
-
"call": lambda prompt, **kw: self(prompt, **kw),
|
| 176 |
-
}
|
| 177 |
-
|
| 178 |
-
def as_langchain_memory(self, max_items: int = 100):
|
| 179 |
-
"""Retorna um Memory compatível com LangChain."""
|
| 180 |
-
try:
|
| 181 |
-
from langchain_core.memory import BaseMemory
|
| 182 |
-
from pydantic import BaseModel
|
| 183 |
-
|
| 184 |
-
adapter = self
|
| 185 |
-
|
| 186 |
-
class BiGRU_T_Memory(BaseMemory):
|
| 187 |
-
memory_key: str = "history"
|
| 188 |
-
max_items: int = max_items
|
| 189 |
-
|
| 190 |
-
@property
|
| 191 |
-
def memory_variables(self) -> List[str]:
|
| 192 |
-
return [self.memory_key]
|
| 193 |
-
|
| 194 |
-
def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, str]:
|
| 195 |
-
history = "\n".join(
|
| 196 |
-
f"Human: {m['prompt']}\nAI: {m['response']}"
|
| 197 |
-
for m in list(adapter._memory)[-self.max_items:]
|
| 198 |
-
)
|
| 199 |
-
return {self.memory_key: history}
|
| 200 |
-
|
| 201 |
-
def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None:
|
| 202 |
-
prompt = inputs.get("input", "")
|
| 203 |
-
response = outputs.get("output", "")
|
| 204 |
-
adapter._memory.append({"prompt": prompt, "response": response})
|
| 205 |
-
|
| 206 |
-
def clear(self) -> None:
|
| 207 |
-
adapter._memory.clear()
|
| 208 |
-
|
| 209 |
-
return BiGRU_T_Memory()
|
| 210 |
-
except ImportError:
|
| 211 |
-
return {
|
| 212 |
-
"type": "langchain_memory_fallback",
|
| 213 |
-
"history": list(self._memory),
|
| 214 |
-
"clear": self._memory.clear,
|
| 215 |
-
}
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
__all__ = ["LangchainAdapter"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/bigru_t/inference/optimized_pipeline.py
DELETED
|
@@ -1,251 +0,0 @@
|
|
| 1 |
-
"""optimized_pipeline.py — Pipeline otimizado para BiGRU_T_version V6.
|
| 2 |
-
|
| 3 |
-
Adaptado de gru-ring-v13-9-2/source/xavante_work/flexnet/optimized_pipeline.py.
|
| 4 |
-
|
| 5 |
-
Pipeline unificado que integra todos os componentes V6:
|
| 6 |
-
- Tokenizer → Embedding
|
| 7 |
-
- WindowContext (2M tokens compression)
|
| 8 |
-
- Backbone (BiGRU_T modules + OrqCell com Kohonen 4D SOM)
|
| 9 |
-
- MTP (Multi-Token Prediction)
|
| 10 |
-
- MTPAdaptiveAttention
|
| 11 |
-
- DynamicRepetitionPenalty
|
| 12 |
-
- ReasoningEngine (opcional)
|
| 13 |
-
- EWC (durante treino continuado)
|
| 14 |
-
- RLHF Possibility Mapping (durante treino com feedback)
|
| 15 |
-
- Tools (ToolMutationEngine + ToolAgentCoordinator)
|
| 16 |
-
|
| 17 |
-
V6 melhorias vs V13.9.2:
|
| 18 |
-
* Self-contained (sem TrustNeuralAgent externo)
|
| 19 |
-
* Memory-efficient: ativa componentes sob demanda
|
| 20 |
-
* Compatível com langchain (via LangchainAdapter)
|
| 21 |
-
* Lazy loading: só instancia componentes se usados
|
| 22 |
-
|
| 23 |
-
REFERÊNCIAS:
|
| 24 |
-
- Pipeline pattern: https://refactoring.guru/design-patterns/pipeline
|
| 25 |
-
- PyTorch Lightning pipeline (inspirado em)
|
| 26 |
-
"""
|
| 27 |
-
from __future__ import annotations
|
| 28 |
-
|
| 29 |
-
import torch
|
| 30 |
-
import torch.nn as nn
|
| 31 |
-
import torch.nn.functional as F
|
| 32 |
-
from typing import Optional, Dict, List, Any, Tuple
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
class OptimizedPipeline:
|
| 36 |
-
"""Pipeline otimizado V6 — orquestra todos os componentes.
|
| 37 |
-
|
| 38 |
-
Esta classe NÃO herda de nn.Module (é um orquestrador puro).
|
| 39 |
-
Mantém referências aos componentes e os aplica em ordem.
|
| 40 |
-
|
| 41 |
-
Args:
|
| 42 |
-
model: BiGRU_T UnifiedModel (backbone)
|
| 43 |
-
tokenizer: tokenizer BBPE
|
| 44 |
-
window_context: WindowContext (opcional, para 2M tokens)
|
| 45 |
-
mtp: MTPModule (opcional)
|
| 46 |
-
mtp_adaptive_attn: MTPAdaptiveAttention (opcional)
|
| 47 |
-
rep_penalty: DynamicRepetitionPenalty (opcional)
|
| 48 |
-
reasoning_engine: ReasoningEngine (opcional)
|
| 49 |
-
ewc: ElasticWeightConsolidation (opcional)
|
| 50 |
-
rlhf_mapping: LearnablePossibilityMapping (opcional)
|
| 51 |
-
tool_engine: ToolMutationEngine (opcional)
|
| 52 |
-
tool_agent: ToolAgentCoordinator (opcional)
|
| 53 |
-
"""
|
| 54 |
-
|
| 55 |
-
def __init__(
|
| 56 |
-
self,
|
| 57 |
-
model: nn.Module,
|
| 58 |
-
tokenizer=None,
|
| 59 |
-
window_context: Optional[nn.Module] = None,
|
| 60 |
-
mtp: Optional[nn.Module] = None,
|
| 61 |
-
mtp_adaptive_attn: Optional[nn.Module] = None,
|
| 62 |
-
rep_penalty: Optional[nn.Module] = None,
|
| 63 |
-
reasoning_engine: Optional[nn.Module] = None,
|
| 64 |
-
ewc: Optional[nn.Module] = None,
|
| 65 |
-
rlhf_mapping: Optional[nn.Module] = None,
|
| 66 |
-
tool_engine=None,
|
| 67 |
-
tool_agent=None,
|
| 68 |
-
):
|
| 69 |
-
self.model = model
|
| 70 |
-
self.tokenizer = tokenizer
|
| 71 |
-
self.window_context = window_context
|
| 72 |
-
self.mtp = mtp
|
| 73 |
-
self.mtp_adaptive_attn = mtp_adaptive_attn
|
| 74 |
-
self.rep_penalty = rep_penalty
|
| 75 |
-
self.reasoning_engine = reasoning_engine
|
| 76 |
-
self.ewc = ewc
|
| 77 |
-
self.rlhf_mapping = rlhf_mapping
|
| 78 |
-
self.tool_engine = tool_engine
|
| 79 |
-
self.tool_agent = tool_agent
|
| 80 |
-
|
| 81 |
-
# Stats de execução
|
| 82 |
-
self._stats = {
|
| 83 |
-
"n_calls": 0,
|
| 84 |
-
"n_tokens_processed": 0,
|
| 85 |
-
"n_mtp_predictions": 0,
|
| 86 |
-
"n_reasoning_calls": 0,
|
| 87 |
-
"n_tool_calls": 0,
|
| 88 |
-
"n_rep_penalty_applied": 0,
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
def forward(
|
| 92 |
-
self,
|
| 93 |
-
input_ids: torch.Tensor,
|
| 94 |
-
targets: Optional[torch.Tensor] = None,
|
| 95 |
-
use_mtp: bool = True,
|
| 96 |
-
use_reasoning: bool = False,
|
| 97 |
-
use_rep_penalty: bool = True,
|
| 98 |
-
use_window_context: bool = False,
|
| 99 |
-
return_aux: bool = False,
|
| 100 |
-
) -> dict:
|
| 101 |
-
"""Forward do pipeline otimizado.
|
| 102 |
-
|
| 103 |
-
Args:
|
| 104 |
-
input_ids: (batch, T) — tokens
|
| 105 |
-
targets: (batch, T) — targets (opcional)
|
| 106 |
-
use_mtp: ativa MTP (default True)
|
| 107 |
-
use_reasoning: ativa ReasoningEngine (default False — custoso)
|
| 108 |
-
use_rep_penalty: ativa DynamicRepetitionPenalty (default True)
|
| 109 |
-
use_window_context: ativa WindowContext (default False)
|
| 110 |
-
return_aux: retorna aux dict com stats
|
| 111 |
-
|
| 112 |
-
Returns:
|
| 113 |
-
dict com logits, loss (se targets), aux
|
| 114 |
-
"""
|
| 115 |
-
self._stats["n_calls"] += 1
|
| 116 |
-
self._stats["n_tokens_processed"] += input_ids.numel()
|
| 117 |
-
aux: Dict[str, Any] = {}
|
| 118 |
-
|
| 119 |
-
# 1. Embedding (via modelo)
|
| 120 |
-
# Assumimos que o modelo cuida de embedding internamente
|
| 121 |
-
# 2. WindowContext (opcional)
|
| 122 |
-
if use_window_context and self.window_context is not None:
|
| 123 |
-
# Precisa dos embeddings — para simplificar, pula se não disponível
|
| 124 |
-
pass # integração futura
|
| 125 |
-
|
| 126 |
-
# 3. Backbone forward
|
| 127 |
-
# O UnifiedModel retorna (logits, delta) — 2 tensores
|
| 128 |
-
# Sem hidden states expostos por padrão
|
| 129 |
-
model_out = self.model(input_ids)
|
| 130 |
-
if isinstance(model_out, tuple):
|
| 131 |
-
logits = model_out[0]
|
| 132 |
-
if len(model_out) >= 2:
|
| 133 |
-
delta = model_out[1]
|
| 134 |
-
aux["delta_norm"] = float(delta.norm().item()) if isinstance(delta, torch.Tensor) else 0.0
|
| 135 |
-
else:
|
| 136 |
-
delta = None
|
| 137 |
-
else:
|
| 138 |
-
logits = model_out
|
| 139 |
-
delta = None
|
| 140 |
-
|
| 141 |
-
# Hidden states não estão disponíveis diretamente — usa logits como proxy
|
| 142 |
-
# para componentes que precisam de hidden (MTP, AdaptiveAttention, etc.)
|
| 143 |
-
hidden = None # será setado se necessário via forward com return_aux
|
| 144 |
-
# Tenta pegar aux do modelo se disponível
|
| 145 |
-
if hasattr(self.model, "last_aux") and isinstance(self.model.last_aux, dict):
|
| 146 |
-
aux.update(self.model.last_aux)
|
| 147 |
-
|
| 148 |
-
# 4. MTPAdaptiveAttention (opcional) — requer hidden, desativado se hidden=None
|
| 149 |
-
if use_mtp and self.mtp_adaptive_attn is not None and hidden is not None:
|
| 150 |
-
mtp_entropy = aux.get("mtp_entropy")
|
| 151 |
-
try:
|
| 152 |
-
hidden, mtp_attn_aux = self.mtp_adaptive_attn(hidden, mtp_entropy)
|
| 153 |
-
aux["mtp_adaptive_attn"] = mtp_attn_aux
|
| 154 |
-
except Exception as e:
|
| 155 |
-
aux["mtp_adaptive_attn_error"] = str(e)
|
| 156 |
-
|
| 157 |
-
# 5. MTP (opcional) — requer hidden + token_embedding, desativado se não disponível
|
| 158 |
-
mtp_result = None
|
| 159 |
-
if use_mtp and self.mtp is not None and hidden is not None:
|
| 160 |
-
# MTP precisa do embedding — assume que modelo tem .token_embedding
|
| 161 |
-
if hasattr(self.model, "token_embedding"):
|
| 162 |
-
self.mtp.set_embedding(self.model.token_embedding)
|
| 163 |
-
try:
|
| 164 |
-
mtp_result = self.mtp(hidden, logits, input_ids, targets)
|
| 165 |
-
aux["mtp"] = mtp_result.get("aux", {})
|
| 166 |
-
self._stats["n_mtp_predictions"] += 1
|
| 167 |
-
if "loss" in mtp_result and mtp_result["loss"] is not None:
|
| 168 |
-
aux["mtp_loss"] = mtp_result["loss"]
|
| 169 |
-
except Exception as e:
|
| 170 |
-
aux["mtp_error"] = str(e)
|
| 171 |
-
|
| 172 |
-
# 6. ReasoningEngine (opcional, custoso) — requer hidden, desativado se não disponível
|
| 173 |
-
if use_reasoning and self.reasoning_engine is not None and hidden is not None:
|
| 174 |
-
try:
|
| 175 |
-
reasoning_out = self.reasoning_engine(hidden, targets, return_chain=False)
|
| 176 |
-
logits = reasoning_out["logits"]
|
| 177 |
-
aux["reasoning"] = reasoning_out["aux"]
|
| 178 |
-
if reasoning_out.get("loss") is not None:
|
| 179 |
-
aux["reasoning_loss"] = reasoning_out["loss"]
|
| 180 |
-
self._stats["n_reasoning_calls"] += 1
|
| 181 |
-
except Exception as e:
|
| 182 |
-
aux["reasoning_error"] = str(e)
|
| 183 |
-
|
| 184 |
-
# 7. DynamicRepetitionPenalty (opcional)
|
| 185 |
-
if use_rep_penalty and self.rep_penalty is not None:
|
| 186 |
-
if hasattr(self.model, "token_embedding"):
|
| 187 |
-
self.rep_penalty.set_embedding(self.model.token_embedding)
|
| 188 |
-
try:
|
| 189 |
-
# logits pode ser (B, V) ou (B, T, V) — adapta
|
| 190 |
-
if logits.dim() == 2:
|
| 191 |
-
# (B, V) → adiciona dimensão T
|
| 192 |
-
logits_3d = logits.unsqueeze(1) # (B, 1, V)
|
| 193 |
-
logits_3d = self.rep_penalty(logits_3d)
|
| 194 |
-
logits = logits_3d.squeeze(1) # (B, V)
|
| 195 |
-
else:
|
| 196 |
-
logits = self.rep_penalty(logits)
|
| 197 |
-
self._stats["n_rep_penalty_applied"] += 1
|
| 198 |
-
except Exception as e:
|
| 199 |
-
aux["rep_penalty_error"] = str(e)
|
| 200 |
-
|
| 201 |
-
# 8. Loss (se targets)
|
| 202 |
-
loss = None
|
| 203 |
-
if targets is not None:
|
| 204 |
-
vocab_size = logits.shape[-1]
|
| 205 |
-
# Logits pode ser (B, V) ou (B, T, V) — adapta para cross_entropy
|
| 206 |
-
if logits.dim() == 2:
|
| 207 |
-
# (B, V) → usar apenas último token como target
|
| 208 |
-
# targets: (B, T) → usar targets[:, -1] (último token)
|
| 209 |
-
tgt_last = targets[:, -1] # (B,)
|
| 210 |
-
main_loss = F.cross_entropy(logits, tgt_last, ignore_index=-100)
|
| 211 |
-
else:
|
| 212 |
-
# (B, T, V) → reshape padrão
|
| 213 |
-
main_loss = F.cross_entropy(
|
| 214 |
-
logits.reshape(-1, vocab_size),
|
| 215 |
-
targets.reshape(-1),
|
| 216 |
-
ignore_index=-100,
|
| 217 |
-
)
|
| 218 |
-
loss = main_loss
|
| 219 |
-
# Adiciona MTP loss se disponível
|
| 220 |
-
if mtp_result and mtp_result.get("loss") is not None:
|
| 221 |
-
loss = loss + mtp_result["loss"]
|
| 222 |
-
# Adiciona EWC penalty se disponível
|
| 223 |
-
if self.ewc is not None and self.ewc.consolidated:
|
| 224 |
-
try:
|
| 225 |
-
ewc_pen = self.ewc.penalty()
|
| 226 |
-
loss = loss + ewc_pen
|
| 227 |
-
aux["ewc_penalty"] = float(ewc_pen.item())
|
| 228 |
-
except Exception as e:
|
| 229 |
-
aux["ewc_error"] = str(e)
|
| 230 |
-
aux["main_loss"] = float(main_loss.item())
|
| 231 |
-
|
| 232 |
-
result = {
|
| 233 |
-
"logits": logits,
|
| 234 |
-
"loss": loss,
|
| 235 |
-
"aux": aux if return_aux else None,
|
| 236 |
-
}
|
| 237 |
-
return result
|
| 238 |
-
|
| 239 |
-
def __call__(self, *args, **kwargs):
|
| 240 |
-
return self.forward(*args, **kwargs)
|
| 241 |
-
|
| 242 |
-
def stats(self) -> dict:
|
| 243 |
-
"""Retorna estatísticas do pipeline."""
|
| 244 |
-
return self._stats.copy()
|
| 245 |
-
|
| 246 |
-
def reset_stats(self) -> None:
|
| 247 |
-
"""Reseta estatísticas."""
|
| 248 |
-
self._stats = {k: 0 for k in self._stats}
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
__all__ = ["OptimizedPipeline"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/bigru_t/model/module_selector.py
DELETED
|
@@ -1,100 +0,0 @@
|
|
| 1 |
-
"""module_selector.py — Lema 1: seleção de módulos via softmax com temperatura.
|
| 2 |
-
|
| 3 |
-
Implementa o Lema 1 (Desacoplamento via atenção hierárquica).
|
| 4 |
-
|
| 5 |
-
Mantém logits aprendíveis `module_logits ∈ R^K` para K módulos u8cell_T.
|
| 6 |
-
A seleção é feita por:
|
| 7 |
-
|
| 8 |
-
alpha = softmax(module_logits / T)
|
| 9 |
-
|
| 10 |
-
onde T (temperatura) é controlada pelo MetaConfigurator (Lema 4).
|
| 11 |
-
|
| 12 |
-
A regularização de entropia (opcional) penaliza distribuições uniformes:
|
| 13 |
-
|
| 14 |
-
L_ent = -lambda_ent * sum(alpha * log(alpha + eps))
|
| 15 |
-
|
| 16 |
-
forçando especialização (poucos módulos com peso alto).
|
| 17 |
-
|
| 18 |
-
Reaproveita a filosofia da Confidence Gate do v13.9.2, mas:
|
| 19 |
-
- Substitui o gate binário por softmax diferenciável
|
| 20 |
-
- Adiciona regularização de entropia (não havia no v13.9.2)
|
| 21 |
-
- A temperatura é hiperparâmetro aprendível (no v13.9.2 era fixa)
|
| 22 |
-
"""
|
| 23 |
-
from __future__ import annotations
|
| 24 |
-
|
| 25 |
-
import math
|
| 26 |
-
|
| 27 |
-
import torch
|
| 28 |
-
import torch.nn as nn
|
| 29 |
-
import torch.nn.functional as F
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
class ModuleSelector(nn.Module):
|
| 33 |
-
"""Seletor de módulos via softmax com temperatura (Lema 1).
|
| 34 |
-
|
| 35 |
-
Args:
|
| 36 |
-
max_modules: nº máximo de módulos K
|
| 37 |
-
lambda_ent: peso da regularização de entropia (0 = desativada)
|
| 38 |
-
|
| 39 |
-
Forward:
|
| 40 |
-
temperature: float → alpha: (K,), entropy_reg: scalar
|
| 41 |
-
|
| 42 |
-
O forward retorna os pesos alpha e a penalização de entropia (a ser
|
| 43 |
-
somada à loss principal).
|
| 44 |
-
"""
|
| 45 |
-
|
| 46 |
-
def __init__(self, max_modules: int, lambda_ent: float = 0.01):
|
| 47 |
-
super().__init__()
|
| 48 |
-
self.max_modules = max_modules
|
| 49 |
-
self.lambda_ent = lambda_ent
|
| 50 |
-
|
| 51 |
-
# V3 BUGFIX: init com pequeno ruído aleatório (em vez de zeros) para
|
| 52 |
-
# quebrar a simetria. Com zeros, softmax(0/T) = uniforme, e o gradiente
|
| 53 |
-
# w.r.t. module_logits é o mesmo para todos os módulos → nenhum aprende
|
| 54 |
-
# a se diferenciar. Com ruído, cada módulo tem um leve viés inicial
|
| 55 |
-
# que o otimizador pode amplificar.
|
| 56 |
-
# Scale = 1/sqrt(K) para manter variance razoável.
|
| 57 |
-
init_scale = 1.0 / (max_modules ** 0.5)
|
| 58 |
-
self.module_logits = nn.Parameter(
|
| 59 |
-
torch.randn(max_modules) * init_scale
|
| 60 |
-
)
|
| 61 |
-
|
| 62 |
-
def forward(self, temperature=1.0) -> tuple[torch.Tensor, torch.Tensor]:
|
| 63 |
-
"""Retorna (alpha, entropy_reg).
|
| 64 |
-
|
| 65 |
-
alpha: (K,) — pesos de seleção dos módulos (soma = 1)
|
| 66 |
-
entropy_reg: scalar — penalização de entropia (a somar à loss)
|
| 67 |
-
|
| 68 |
-
V3 BUGFIX: temperature pode ser tensor (do MetaConfigurator) ou float.
|
| 69 |
-
Usamos torch.clamp (diferenciável) em vez de max() (que pode quebrar
|
| 70 |
-
o grafo computacional quando temperature é tensor 0-dim).
|
| 71 |
-
"""
|
| 72 |
-
# Garante que temperature é um tensor (preserva grafo se for tensor)
|
| 73 |
-
if not isinstance(temperature, torch.Tensor):
|
| 74 |
-
temperature = torch.tensor(float(temperature), device=self.module_logits.device)
|
| 75 |
-
# Softmax com temperatura (Lema 1) — torch.clamp é diferenciável
|
| 76 |
-
# T alta → distribuição uniforme; T baixa → argmax (especialização)
|
| 77 |
-
T_clamped = torch.clamp(temperature, min=1e-6)
|
| 78 |
-
alpha = F.softmax(self.module_logits / T_clamped, dim=0) # (K,)
|
| 79 |
-
|
| 80 |
-
# Regularização de entropia (minimizar H(alpha) → especialização)
|
| 81 |
-
# H(alpha) = -sum(alpha * log(alpha + eps))
|
| 82 |
-
# Para MINIMIZAR H, somamos +lambda_ent * H(alpha) à loss
|
| 83 |
-
# (gradient descent reduz H)
|
| 84 |
-
eps = 1e-8
|
| 85 |
-
entropy = -(alpha * torch.log(alpha + eps)).sum()
|
| 86 |
-
entropy_reg = self.lambda_ent * entropy # scalar
|
| 87 |
-
|
| 88 |
-
return alpha, entropy_reg
|
| 89 |
-
|
| 90 |
-
def get_active_modules(self, threshold: float = 0.01) -> int:
|
| 91 |
-
"""Retorna nº efetivo de módulos ativos (alpha > threshold)."""
|
| 92 |
-
with torch.no_grad():
|
| 93 |
-
alpha = F.softmax(self.module_logits, dim=0)
|
| 94 |
-
active = (alpha > threshold).sum().item()
|
| 95 |
-
return active
|
| 96 |
-
|
| 97 |
-
def get_alpha_distribution(self) -> torch.Tensor:
|
| 98 |
-
"""Retorna a distribuição alpha atual (para logging)."""
|
| 99 |
-
with torch.no_grad():
|
| 100 |
-
return F.softmax(self.module_logits, dim=0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/bigru_t/model/mtp.py
DELETED
|
@@ -1,503 +0,0 @@
|
|
| 1 |
-
"""mtp.py — Multi-Token Prediction (MTP) V6.
|
| 2 |
-
|
| 3 |
-
Adaptado de gru-ring-v13-9-2/source/xavante_work/flexnet/mtp.py.
|
| 4 |
-
|
| 5 |
-
MTP (DeepSeek 2024): em vez de prever 1 token por passo, prediz K tokens
|
| 6 |
-
futuros simultaneamente. Em inference, gera K tokens por forward pass →
|
| 7 |
-
speedup até K× (com speculative decoding).
|
| 8 |
-
|
| 9 |
-
Algoritmo:
|
| 10 |
-
1. Cabeça principal: prevê token t+1 (como LLM padrão)
|
| 11 |
-
2. Cabeças MTP (K-1 cabeças auxiliares):
|
| 12 |
-
- Cada cabeça k prevê token t+k+1
|
| 13 |
-
- Cada cabeça recebe: hidden_state + embedding do token previsto pela
|
| 14 |
-
cabeça anterior (concatenados)
|
| 15 |
-
- hidden_state compartilhado com a cabeça principal
|
| 16 |
-
|
| 17 |
-
Loss total: L = L_main + Σ_k α_k · L_k (α_k decrescente)
|
| 18 |
-
|
| 19 |
-
V6 melhoria vs V13.9.2:
|
| 20 |
-
* Vetorização total: K cabeças em 1 forward (não loop)
|
| 21 |
-
* α_k aprendível (via softmax sobre logits_α)
|
| 22 |
-
* Compatível com TripletPrototypeLoss (não conflita)
|
| 23 |
-
* Memory-efficient: embeddings das K cabeças são computados lazy
|
| 24 |
-
|
| 25 |
-
REFERÊNCIAS:
|
| 26 |
-
- DeepSeek (2024). "DeepSeek-V3 Technical Report" — Section 5.3 MTP.
|
| 27 |
-
- Leviathan et al. (2023). "Fast Inference from Transformers via Speculative Decoding".
|
| 28 |
-
- Cai et al. (2024). "Medusa: Simple LLM Inference Acceleration".
|
| 29 |
-
"""
|
| 30 |
-
from __future__ import annotations
|
| 31 |
-
|
| 32 |
-
import torch
|
| 33 |
-
import torch.nn as nn
|
| 34 |
-
import torch.nn.functional as F
|
| 35 |
-
from typing import Optional, Tuple
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
class MTPHead(nn.Module):
|
| 39 |
-
"""Cabeça MTP — prevê token t+k+1 dado hidden_state e token t+k.
|
| 40 |
-
|
| 41 |
-
Args:
|
| 42 |
-
d_model: dimensão do hidden state
|
| 43 |
-
vocab_size: tamanho do vocabulário
|
| 44 |
-
k: índice da cabeça (k=1 é a primeira MTP, k=0 é a principal)
|
| 45 |
-
"""
|
| 46 |
-
|
| 47 |
-
def __init__(
|
| 48 |
-
self,
|
| 49 |
-
d_model: int,
|
| 50 |
-
vocab_size: int,
|
| 51 |
-
k: int = 1,
|
| 52 |
-
):
|
| 53 |
-
super().__init__()
|
| 54 |
-
self.k = k
|
| 55 |
-
self.d_model = d_model
|
| 56 |
-
self.vocab_size = vocab_size
|
| 57 |
-
|
| 58 |
-
# Input: hidden_state (d_model) + embedding do token anterior (d_model)
|
| 59 |
-
# → projeção para d_model via Linear + ReLU + LayerNorm
|
| 60 |
-
self.proj_in = nn.Linear(2 * d_model, d_model)
|
| 61 |
-
self.act = nn.ReLU()
|
| 62 |
-
self.norm = nn.LayerNorm(d_model)
|
| 63 |
-
self.proj_out = nn.Linear(d_model, vocab_size)
|
| 64 |
-
|
| 65 |
-
def forward(
|
| 66 |
-
self,
|
| 67 |
-
hidden: torch.Tensor,
|
| 68 |
-
prev_token_emb: torch.Tensor,
|
| 69 |
-
) -> torch.Tensor:
|
| 70 |
-
"""hidden: (batch, T, d_model), prev_token_emb: (batch, T, d_model)
|
| 71 |
-
→ logits: (batch, T, vocab_size)
|
| 72 |
-
"""
|
| 73 |
-
x = torch.cat([hidden, prev_token_emb], dim=-1) # (B, T, 2*d_model)
|
| 74 |
-
x = self.proj_in(x)
|
| 75 |
-
x = self.act(x)
|
| 76 |
-
x = self.norm(x)
|
| 77 |
-
logits = self.proj_out(x) # (B, T, vocab_size)
|
| 78 |
-
return logits
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
class MTPModule(nn.Module):
|
| 82 |
-
"""Multi-Token Prediction com K cabeças.
|
| 83 |
-
|
| 84 |
-
Args:
|
| 85 |
-
d_model: dimensão do hidden state
|
| 86 |
-
vocab_size: tamanho do vocabulário
|
| 87 |
-
K: nº total de tokens previstos por passo (default 4 = 1 principal + 3 MTP)
|
| 88 |
-
alpha_init: peso inicial das cabeças MTP (default 0.5, decrescente)
|
| 89 |
-
learnable_alpha: se True, α_k é aprendível (default True)
|
| 90 |
-
"""
|
| 91 |
-
|
| 92 |
-
def __init__(
|
| 93 |
-
self,
|
| 94 |
-
d_model: int,
|
| 95 |
-
vocab_size: int,
|
| 96 |
-
K: int = 4,
|
| 97 |
-
alpha_init: float = 0.5,
|
| 98 |
-
learnable_alpha: bool = True,
|
| 99 |
-
):
|
| 100 |
-
super().__init__()
|
| 101 |
-
self.K = max(K, 1)
|
| 102 |
-
self.d_model = d_model
|
| 103 |
-
self.vocab_size = vocab_size
|
| 104 |
-
|
| 105 |
-
# Cabeças MTP (K-1 cabeças auxiliares)
|
| 106 |
-
if self.K > 1:
|
| 107 |
-
self.mtp_heads = nn.ModuleList([
|
| 108 |
-
MTPHead(d_model, vocab_size, k=k)
|
| 109 |
-
for k in range(1, self.K)
|
| 110 |
-
])
|
| 111 |
-
else:
|
| 112 |
-
self.mtp_heads = nn.ModuleList()
|
| 113 |
-
|
| 114 |
-
# α_k: pesos das cabeças MTP (aprendíveis via softmax)
|
| 115 |
-
if learnable_alpha and self.K > 1:
|
| 116 |
-
init_logits = torch.full((self.K - 1,), math.log(alpha_init))
|
| 117 |
-
self.alpha_logits = nn.Parameter(init_logits)
|
| 118 |
-
elif self.K > 1:
|
| 119 |
-
self.register_buffer(
|
| 120 |
-
"alpha_logits",
|
| 121 |
-
torch.full((self.K - 1,), math.log(alpha_init)),
|
| 122 |
-
)
|
| 123 |
-
|
| 124 |
-
# Embedding para tokens previstos (compartilhado externamente)
|
| 125 |
-
# Aqui mantemos uma referência que será setada via set_embedding()
|
| 126 |
-
self._token_embedding: Optional[nn.Embedding] = None
|
| 127 |
-
|
| 128 |
-
def set_embedding(self, emb: nn.Embedding) -> None:
|
| 129 |
-
"""Define o nn.Embedding compartilhado (deve ser chamado antes do forward)."""
|
| 130 |
-
self._token_embedding = emb
|
| 131 |
-
|
| 132 |
-
@property
|
| 133 |
-
def alphas(self) -> torch.Tensor:
|
| 134 |
-
"""Pesos α_k normalizados via softmax."""
|
| 135 |
-
if self.K <= 1:
|
| 136 |
-
return torch.tensor([])
|
| 137 |
-
return F.softmax(self.alpha_logits, dim=0)
|
| 138 |
-
|
| 139 |
-
def forward(
|
| 140 |
-
self,
|
| 141 |
-
hidden: torch.Tensor,
|
| 142 |
-
main_logits: torch.Tensor,
|
| 143 |
-
input_ids: torch.Tensor,
|
| 144 |
-
targets: Optional[torch.Tensor] = None,
|
| 145 |
-
) -> dict:
|
| 146 |
-
"""Forward MTP.
|
| 147 |
-
|
| 148 |
-
Args:
|
| 149 |
-
hidden: (batch, T, d_model) — hidden state do backbone
|
| 150 |
-
main_logits: (batch, T, vocab_size) — logits da cabeça principal
|
| 151 |
-
input_ids: (batch, T) — tokens de entrada (para embedding)
|
| 152 |
-
targets: (batch, T) — targets para loss (opcional)
|
| 153 |
-
|
| 154 |
-
Returns:
|
| 155 |
-
dict com:
|
| 156 |
-
- main_logits: (B, T, V)
|
| 157 |
-
- mtp_logits: lista de (B, T, V) para cada cabeça k
|
| 158 |
-
- loss: escalar (se targets fornecido) — main + Σ α_k · mtp_k
|
| 159 |
-
- alphas: (K-1,) pesos normalizados
|
| 160 |
-
- aux: dict com perdas individuais
|
| 161 |
-
"""
|
| 162 |
-
B, T, _ = hidden.shape
|
| 163 |
-
device = hidden.device
|
| 164 |
-
|
| 165 |
-
# Resultado da cabeça principal já está em main_logits
|
| 166 |
-
result = {
|
| 167 |
-
"main_logits": main_logits,
|
| 168 |
-
"mtp_logits": [],
|
| 169 |
-
"alphas": self.alphas,
|
| 170 |
-
"loss": None,
|
| 171 |
-
"aux": {},
|
| 172 |
-
}
|
| 173 |
-
|
| 174 |
-
if self.K <= 1 or len(self.mtp_heads) == 0:
|
| 175 |
-
# Sem MTP — só main
|
| 176 |
-
if targets is not None:
|
| 177 |
-
main_loss = F.cross_entropy(
|
| 178 |
-
main_logits.reshape(-1, self.vocab_size),
|
| 179 |
-
targets.reshape(-1),
|
| 180 |
-
ignore_index=-100,
|
| 181 |
-
)
|
| 182 |
-
result["loss"] = main_loss
|
| 183 |
-
result["aux"]["main_loss"] = float(main_loss.item())
|
| 184 |
-
return result
|
| 185 |
-
|
| 186 |
-
if self._token_embedding is None:
|
| 187 |
-
raise RuntimeError(
|
| 188 |
-
"MTPModule.set_embedding() deve ser chamado antes do forward"
|
| 189 |
-
)
|
| 190 |
-
|
| 191 |
-
# Para cada cabeça k (1 a K-1):
|
| 192 |
-
# - Recebe hidden + embedding do token previsto pela cabeça k-1
|
| 193 |
-
# - Em treino: usa target (teacher forcing)
|
| 194 |
-
# - Em inference: usa argmax da cabeça anterior
|
| 195 |
-
prev_logits = main_logits # (B, T, V)
|
| 196 |
-
mtp_logits_list = []
|
| 197 |
-
mtp_losses = []
|
| 198 |
-
|
| 199 |
-
for k, head in enumerate(self.mtp_heads):
|
| 200 |
-
# Token da cabeça anterior (em treino: target; em inference: argmax)
|
| 201 |
-
if self.training and targets is not None:
|
| 202 |
-
# Teacher forcing: usa target deslocado
|
| 203 |
-
# Cabeça k prevê token t+k+1, então precisa do token t+k
|
| 204 |
-
# target: (B, T) → shift por k+1
|
| 205 |
-
prev_tokens = targets # simplificação: usa target atual
|
| 206 |
-
else:
|
| 207 |
-
prev_tokens = prev_logits.argmax(dim=-1) # (B, T)
|
| 208 |
-
|
| 209 |
-
prev_emb = self._token_embedding(prev_tokens) # (B, T, d_model)
|
| 210 |
-
logits_k = head(hidden, prev_emb) # (B, T, V)
|
| 211 |
-
mtp_logits_list.append(logits_k)
|
| 212 |
-
prev_logits = logits_k # para próxima cabeça
|
| 213 |
-
|
| 214 |
-
if targets is not None:
|
| 215 |
-
# Loss da cabeça k: prevê token t+k+1 dado contexto até t
|
| 216 |
-
# Para simplicidade, usamos o mesmo target (aproximação)
|
| 217 |
-
loss_k = F.cross_entropy(
|
| 218 |
-
logits_k.reshape(-1, self.vocab_size),
|
| 219 |
-
targets.reshape(-1),
|
| 220 |
-
ignore_index=-100,
|
| 221 |
-
)
|
| 222 |
-
mtp_losses.append(loss_k)
|
| 223 |
-
|
| 224 |
-
result["mtp_logits"] = mtp_logits_list
|
| 225 |
-
|
| 226 |
-
if targets is not None:
|
| 227 |
-
main_loss = F.cross_entropy(
|
| 228 |
-
main_logits.reshape(-1, self.vocab_size),
|
| 229 |
-
targets.reshape(-1),
|
| 230 |
-
ignore_index=-100,
|
| 231 |
-
)
|
| 232 |
-
# Loss total = main + Σ α_k · mtp_k
|
| 233 |
-
alphas = self.alphas # (K-1,)
|
| 234 |
-
mtp_loss_total = sum(
|
| 235 |
-
alphas[k] * mtp_losses[k]
|
| 236 |
-
for k in range(len(mtp_losses))
|
| 237 |
-
)
|
| 238 |
-
total_loss = main_loss + mtp_loss_total
|
| 239 |
-
result["loss"] = total_loss
|
| 240 |
-
result["aux"]["main_loss"] = float(main_loss.item())
|
| 241 |
-
result["aux"]["mtp_loss_total"] = float(mtp_loss_total.item())
|
| 242 |
-
for k, l in enumerate(mtp_losses):
|
| 243 |
-
result["aux"][f"mtp_loss_{k+1}"] = float(l.item())
|
| 244 |
-
result["aux"][f"alpha_{k+1}"] = float(alphas[k].item())
|
| 245 |
-
|
| 246 |
-
return result
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 250 |
-
# MTPAdaptiveAttention — Attention adaptativa guiada por MTP
|
| 251 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 252 |
-
class MTPAdaptiveAttention(nn.Module):
|
| 253 |
-
"""Atenção adaptativa guiada pela confiança das cabeças MTP.
|
| 254 |
-
|
| 255 |
-
Adaptado de gru-ring-v13-9-2/source/xavante_work/flexnet/mtp_adaptive_attention.py.
|
| 256 |
-
|
| 257 |
-
Ideia: a confiança (entropy) das previsões MTP modula a mistura entre
|
| 258 |
-
atenção local (window) e global (full sequence). Alta confiança MTP →
|
| 259 |
-
mais peso em global; baixa confiança → mais peso em local.
|
| 260 |
-
|
| 261 |
-
Args:
|
| 262 |
-
d_model: dimensão do modelo
|
| 263 |
-
nhead: nº de cabeças de atenção
|
| 264 |
-
window_size: tamanho da janela local (default 256)
|
| 265 |
-
"""
|
| 266 |
-
|
| 267 |
-
def __init__(
|
| 268 |
-
self,
|
| 269 |
-
d_model: int,
|
| 270 |
-
nhead: int,
|
| 271 |
-
window_size: int = 256,
|
| 272 |
-
dropout: float = 0.1,
|
| 273 |
-
):
|
| 274 |
-
super().__init__()
|
| 275 |
-
self.d_model = d_model
|
| 276 |
-
self.nhead = nhead
|
| 277 |
-
self.window_size = window_size
|
| 278 |
-
|
| 279 |
-
# Atenção full
|
| 280 |
-
self.full_attn = nn.MultiheadAttention(
|
| 281 |
-
d_model, nhead, dropout=dropout, batch_first=True
|
| 282 |
-
)
|
| 283 |
-
# Atenção local (windowed) — usa a mesma MultiheadAttention
|
| 284 |
-
# mas com mask de janela
|
| 285 |
-
self.local_attn = nn.MultiheadAttention(
|
| 286 |
-
d_model, nhead, dropout=dropout, batch_first=True
|
| 287 |
-
)
|
| 288 |
-
|
| 289 |
-
# Gating: mistura local vs global baseado na confiança MTP
|
| 290 |
-
# Recebe hidden + mtp_entropy → gate ∈ [0, 1]
|
| 291 |
-
self.gate = nn.Sequential(
|
| 292 |
-
nn.Linear(d_model + 1, d_model // 4),
|
| 293 |
-
nn.ReLU(),
|
| 294 |
-
nn.Linear(d_model // 4, 1),
|
| 295 |
-
nn.Sigmoid(),
|
| 296 |
-
)
|
| 297 |
-
|
| 298 |
-
self.norm = nn.LayerNorm(d_model)
|
| 299 |
-
|
| 300 |
-
def _make_window_mask(self, T: int, device: torch.device) -> torch.Tensor:
|
| 301 |
-
"""Cria mask de janela local (T, T) — 1 dentro da janela, 0 fora."""
|
| 302 |
-
i = torch.arange(T, device=device).unsqueeze(1)
|
| 303 |
-
j = torch.arange(T, device=device).unsqueeze(0)
|
| 304 |
-
mask = (i - j).abs() <= self.window_size // 2
|
| 305 |
-
return mask # (T, T)
|
| 306 |
-
|
| 307 |
-
def forward(
|
| 308 |
-
self,
|
| 309 |
-
hidden: torch.Tensor,
|
| 310 |
-
mtp_entropy: Optional[torch.Tensor] = None,
|
| 311 |
-
) -> Tuple[torch.Tensor, dict]:
|
| 312 |
-
"""hidden: (B, T, d), mtp_entropy: (B,) ou None.
|
| 313 |
-
|
| 314 |
-
Returns:
|
| 315 |
-
out: (B, T, d)
|
| 316 |
-
aux: dict com gate_mean, local_weight, global_weight
|
| 317 |
-
"""
|
| 318 |
-
B, T, D = hidden.shape
|
| 319 |
-
device = hidden.device
|
| 320 |
-
|
| 321 |
-
# Se mtp_entropy não fornecido, usa 0 (gate neutro)
|
| 322 |
-
if mtp_entropy is None:
|
| 323 |
-
mtp_entropy = torch.zeros(B, device=device)
|
| 324 |
-
# Normaliza entropy para [0, 1] (aproximação)
|
| 325 |
-
mtp_entropy_norm = (mtp_entropy / 10.0).clamp(0.0, 1.0) # (B,)
|
| 326 |
-
|
| 327 |
-
# Gate: prob de usar global vs local
|
| 328 |
-
gate_input = torch.cat([
|
| 329 |
-
hidden.mean(dim=1), # (B, D)
|
| 330 |
-
mtp_entropy_norm.unsqueeze(-1), # (B, 1)
|
| 331 |
-
], dim=-1) # (B, D+1)
|
| 332 |
-
gate = self.gate(gate_input) # (B, 1) — prob de global
|
| 333 |
-
gate_expanded = gate.unsqueeze(1).unsqueeze(1) # (B, 1, 1, 1) p/ broadcast
|
| 334 |
-
|
| 335 |
-
# Atenção global (full)
|
| 336 |
-
h_norm = self.norm(hidden)
|
| 337 |
-
global_out, _ = self.full_attn(h_norm, h_norm, h_norm, need_weights=False)
|
| 338 |
-
|
| 339 |
-
# Atenção local (windowed) — aplica mask
|
| 340 |
-
window_mask = self._make_window_mask(T, device) # (T, T)
|
| 341 |
-
# MultiheadAttention espera mask onde True = posição a mascarar
|
| 342 |
-
attn_mask = ~window_mask # (T, T) — True onde NÃO atender
|
| 343 |
-
# Expande para (B*nhead, T, T)
|
| 344 |
-
local_out, _ = self.local_attn(
|
| 345 |
-
h_norm, h_norm, h_norm,
|
| 346 |
-
attn_mask=attr_mask_expand(attn_mask, B, self.nhead),
|
| 347 |
-
need_weights=False,
|
| 348 |
-
)
|
| 349 |
-
|
| 350 |
-
# Mistura: out = gate · global + (1 - gate) · local
|
| 351 |
-
gate_b = gate.unsqueeze(1) # (B, 1, 1)
|
| 352 |
-
out = gate_b * global_out + (1.0 - gate_b) * local_out
|
| 353 |
-
out = hidden + out # residual
|
| 354 |
-
|
| 355 |
-
return out, {
|
| 356 |
-
"gate_mean": float(gate.mean().item()),
|
| 357 |
-
"local_weight": float((1.0 - gate).mean().item()),
|
| 358 |
-
"global_weight": float(gate.mean().item()),
|
| 359 |
-
"mtp_entropy_mean": float(mtp_entropy.mean().item()),
|
| 360 |
-
}
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
def attr_mask_expand(attn_mask: torch.Tensor, B: int, nhead: int) -> torch.Tensor:
|
| 364 |
-
"""Expande mask (T, T) para (B*nhead, T, T)."""
|
| 365 |
-
return attn_mask.unsqueeze(0).expand(B * nhead, -1, -1)
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 369 |
-
# DynamicRepetitionPenalty — Penalidade de Repetição Dinâmica
|
| 370 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 371 |
-
class DynamicRepetitionPenalty(nn.Module):
|
| 372 |
-
"""Penalidade de Repetição Dinâmica (V6).
|
| 373 |
-
|
| 374 |
-
Adaptado de gru-ring-v13-9-2/source/xavante_work/flexnet/strong_repetition_penalty.py
|
| 375 |
-
e repetition_penalty_optimizer.py.
|
| 376 |
-
|
| 377 |
-
Estratégia:
|
| 378 |
-
1. Mantém buffer circular dos últimos W tokens gerados
|
| 379 |
-
2. Para cada token candidato, computa similaridade máxima com o buffer
|
| 380 |
-
3. Penalidade: E = -log(1 - σ(max_sim)) → barreira exponencial
|
| 381 |
-
4. Penalidade aplicada como logits adjustment: logits -= λ · E
|
| 382 |
-
5. λ otimizado via GP-UCB bayesiano (online)
|
| 383 |
-
|
| 384 |
-
V6: vetorizado, suporta batch, buffer circular em GPU.
|
| 385 |
-
|
| 386 |
-
Args:
|
| 387 |
-
vocab_size: tamanho do vocabulário
|
| 388 |
-
embedding_dim: dimensão dos embeddings (para similaridade)
|
| 389 |
-
window_size: tamanho do buffer circular (default 64)
|
| 390 |
-
lambda_init: λ inicial (default 1.0)
|
| 391 |
-
lambda_min, lambda_max: limites de λ
|
| 392 |
-
"""
|
| 393 |
-
|
| 394 |
-
def __init__(
|
| 395 |
-
self,
|
| 396 |
-
vocab_size: int,
|
| 397 |
-
embedding_dim: int,
|
| 398 |
-
window_size: int = 64,
|
| 399 |
-
lambda_init: float = 1.0,
|
| 400 |
-
lambda_min: float = 0.1,
|
| 401 |
-
lambda_max: float = 10.0,
|
| 402 |
-
):
|
| 403 |
-
super().__init__()
|
| 404 |
-
self.vocab_size = vocab_size
|
| 405 |
-
self.embedding_dim = embedding_dim
|
| 406 |
-
self.window_size = window_size
|
| 407 |
-
|
| 408 |
-
# λ como parâmetro aprendível (no log space para positividade)
|
| 409 |
-
self.log_lambda = nn.Parameter(torch.tensor(math.log(lambda_init)))
|
| 410 |
-
self.register_buffer("lambda_min", torch.tensor(lambda_min))
|
| 411 |
-
self.register_buffer("lambda_max", torch.tensor(lambda_max))
|
| 412 |
-
|
| 413 |
-
# Buffer circular dos últimos W tokens (por batch)
|
| 414 |
-
# Mantém apenas IDs (não embeddings) para economizar RAM
|
| 415 |
-
self.register_buffer(
|
| 416 |
-
"token_buffer",
|
| 417 |
-
torch.zeros(1, window_size, dtype=torch.long),
|
| 418 |
-
)
|
| 419 |
-
self.register_buffer("buffer_pos", torch.tensor(0, dtype=torch.long))
|
| 420 |
-
|
| 421 |
-
# Embedding temporário para computar similaridade (será substituído)
|
| 422 |
-
# Na prática, deve receber o embedding do modelo principal
|
| 423 |
-
self._embedding: Optional[nn.Embedding] = None
|
| 424 |
-
|
| 425 |
-
def set_embedding(self, emb: nn.Embedding) -> None:
|
| 426 |
-
"""Define o nn.Embedding compartilhado."""
|
| 427 |
-
self._embedding = emb
|
| 428 |
-
|
| 429 |
-
@property
|
| 430 |
-
def lambda_val(self) -> torch.Tensor:
|
| 431 |
-
"""λ atual = exp(log_lambda), clampado para [lambda_min, lambda_max]."""
|
| 432 |
-
return torch.exp(self.log_lambda).clamp(self.lambda_min, self.lambda_max)
|
| 433 |
-
|
| 434 |
-
def update_buffer(self, new_tokens: torch.Tensor) -> None:
|
| 435 |
-
"""Atualiza o buffer circular com novos tokens.
|
| 436 |
-
|
| 437 |
-
Args:
|
| 438 |
-
new_tokens: (batch,) ou (batch, T) — novos tokens gerados
|
| 439 |
-
"""
|
| 440 |
-
if new_tokens.dim() == 2:
|
| 441 |
-
new_tokens = new_tokens.reshape(-1)
|
| 442 |
-
W = self.window_size
|
| 443 |
-
for tok in new_tokens:
|
| 444 |
-
self.token_buffer[0, self.buffer_pos] = tok
|
| 445 |
-
self.buffer_pos = (self.buffer_pos + 1) % W
|
| 446 |
-
|
| 447 |
-
def compute_penalty(
|
| 448 |
-
self,
|
| 449 |
-
logits: torch.Tensor,
|
| 450 |
-
) -> torch.Tensor:
|
| 451 |
-
"""Computa logits ajustados com penalidade de repetição.
|
| 452 |
-
|
| 453 |
-
Args:
|
| 454 |
-
logits: (batch, T, vocab_size) ou (batch, vocab_size)
|
| 455 |
-
|
| 456 |
-
Returns:
|
| 457 |
-
adjusted_logits: mesmo shape que logits
|
| 458 |
-
"""
|
| 459 |
-
if self._embedding is None:
|
| 460 |
-
return logits # sem embedding, não aplica penalidade
|
| 461 |
-
|
| 462 |
-
lam = self.lambda_val
|
| 463 |
-
# Embeddings dos tokens no buffer: (W, embedding_dim)
|
| 464 |
-
buffer_emb = self._embedding(self.token_buffer[0]) # (W, d)
|
| 465 |
-
buffer_emb_norm = F.normalize(buffer_emb, dim=-1)
|
| 466 |
-
|
| 467 |
-
# Para cada token candidato no vocab, computa max similarity com buffer
|
| 468 |
-
vocab_emb = self._embedding.weight # (V, d)
|
| 469 |
-
vocab_emb_norm = F.normalize(vocab_emb, dim=-1)
|
| 470 |
-
# cosine sim: (V, W) — para cada token do vocab, similaridade com cada do buffer
|
| 471 |
-
sim_matrix = vocab_emb_norm @ buffer_emb_norm.t() # (V, W)
|
| 472 |
-
max_sim_per_vocab = sim_matrix.max(dim=-1).values # (V,)
|
| 473 |
-
# σ(max_sim) ∈ [0.5, 1] (max_sim ∈ [0, 1] para embeddings normalizados)
|
| 474 |
-
sigma_sim = torch.sigmoid(max_sim_per_vocab * 5.0) # amplifica
|
| 475 |
-
# E = -log(1 - σ(max_sim)) → barreira exponencial
|
| 476 |
-
# σ(max_sim) próximo de 1 → E → +inf (forte penalidade)
|
| 477 |
-
# σ(max_sim) próximo de 0 → E → 0 (sem penalidade)
|
| 478 |
-
penalty = -torch.log(1.0 - sigma_sim + 1e-8) # (V,)
|
| 479 |
-
|
| 480 |
-
# Aplica penalidade aos logits
|
| 481 |
-
adjusted = logits - lam * penalty.unsqueeze(0).unsqueeze(0)
|
| 482 |
-
return adjusted
|
| 483 |
-
|
| 484 |
-
def forward(self, logits: torch.Tensor) -> torch.Tensor:
|
| 485 |
-
"""Wrapper para compute_penalty."""
|
| 486 |
-
return self.compute_penalty(logits)
|
| 487 |
-
|
| 488 |
-
def extra_repr(self) -> str:
|
| 489 |
-
return (
|
| 490 |
-
f"vocab_size={self.vocab_size}, window={self.window_size}, "
|
| 491 |
-
f"lambda={float(self.lambda_val.item()):.3f}"
|
| 492 |
-
)
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
import math # no topo, mas mantido aqui para compatibilidade
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
__all__ = [
|
| 499 |
-
"MTPHead",
|
| 500 |
-
"MTPModule",
|
| 501 |
-
"MTPAdaptiveAttention",
|
| 502 |
-
"DynamicRepetitionPenalty",
|
| 503 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/bigru_t/model/train_t.py
DELETED
|
@@ -1,63 +0,0 @@
|
|
| 1 |
-
"""train_t.py — TrainT: cabeça de predição principal.
|
| 2 |
-
|
| 3 |
-
No v13.9.2, a predição principal é feita pela última camada cooperativa
|
| 4 |
-
+ projection ao vocabulário. No BiGRU_T_version, a predição é desacoplada
|
| 5 |
-
da extração de features (feita pelo OrqCell + u8cell_T) e feita por um
|
| 6 |
-
TransformerEncoder simples sobre a representação agregada `o`.
|
| 7 |
-
|
| 8 |
-
Diferenças vs. HypT:
|
| 9 |
-
- TrainT recebe `o` COM gradiente (caminho principal)
|
| 10 |
-
- HypT recebe `o` SEM gradiente quando stop_grad_hyp=True (Lema 3)
|
| 11 |
-
"""
|
| 12 |
-
from __future__ import annotations
|
| 13 |
-
|
| 14 |
-
import torch
|
| 15 |
-
import torch.nn as nn
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
class TrainT(nn.Module):
|
| 19 |
-
"""Transformer encoder para predição principal.
|
| 20 |
-
|
| 21 |
-
Args:
|
| 22 |
-
d_input: dimensão de entrada (saída do OrqCell = d_cache)
|
| 23 |
-
d_model: dimensão interna do Transformer
|
| 24 |
-
nhead: nº de cabeças
|
| 25 |
-
d_ff: dimensão do FFN
|
| 26 |
-
output_dim: dimensão de saída (vocab_size para LM, num_classes para classificação)
|
| 27 |
-
num_layers: nº de camadas do TransformerEncoder
|
| 28 |
-
dropout: probabilidade de dropout
|
| 29 |
-
|
| 30 |
-
Forward:
|
| 31 |
-
o: (batch, d_input) → y_hat: (batch, output_dim)
|
| 32 |
-
"""
|
| 33 |
-
|
| 34 |
-
def __init__(
|
| 35 |
-
self,
|
| 36 |
-
d_input: int,
|
| 37 |
-
d_model: int,
|
| 38 |
-
nhead: int,
|
| 39 |
-
d_ff: int,
|
| 40 |
-
output_dim: int,
|
| 41 |
-
num_layers: int = 2,
|
| 42 |
-
dropout: float = 0.1,
|
| 43 |
-
):
|
| 44 |
-
super().__init__()
|
| 45 |
-
self.d_input = d_input
|
| 46 |
-
self.d_model = d_model
|
| 47 |
-
self.output_dim = output_dim
|
| 48 |
-
|
| 49 |
-
self.proj_in = nn.Linear(d_input, d_model)
|
| 50 |
-
encoder_layer = nn.TransformerEncoderLayer(
|
| 51 |
-
d_model, nhead, d_ff, dropout=dropout, batch_first=True
|
| 52 |
-
)
|
| 53 |
-
self.encoder = nn.TransformerEncoder(encoder_layer, num_layers)
|
| 54 |
-
self.proj_out = nn.Linear(d_model, output_dim)
|
| 55 |
-
|
| 56 |
-
def forward(self, o: torch.Tensor) -> torch.Tensor:
|
| 57 |
-
"""o: (batch, d_input) → y_hat: (batch, output_dim)"""
|
| 58 |
-
# Adiciona dimensão de sequência (token único)
|
| 59 |
-
x = o.unsqueeze(1) # (batch, 1, d_input)
|
| 60 |
-
x = self.proj_in(x) # (batch, 1, d_model)
|
| 61 |
-
x = self.encoder(x) # (batch, 1, d_model)
|
| 62 |
-
y_hat = self.proj_out(x.squeeze(1)) # (batch, output_dim)
|
| 63 |
-
return y_hat
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/bigru_t/model/window_context.py
DELETED
|
@@ -1,371 +0,0 @@
|
|
| 1 |
-
"""window_context.py — Window Context para atenção de 2M tokens (V6).
|
| 2 |
-
|
| 3 |
-
Adaptado de gru-ring-v13-9-2/source/xavante_work/flexnet/token_compress.py
|
| 4 |
-
e mtp_adaptive_attention.py.
|
| 5 |
-
|
| 6 |
-
Estratégia para 2M tokens:
|
| 7 |
-
1. **ToMe Token Merging** (Bolya et al. 2023): reduz N → N-r tokens via
|
| 8 |
-
bipartite soft matching. R-prune por camada. Complexity: O(N · d).
|
| 9 |
-
2. **KNN Indexer** (IVF-like): k-means centroids, n_probe busca. Reduz
|
| 10 |
-
atenção O(N²) → O(N · n_probe · k_avg).
|
| 11 |
-
3. **Sliding Window**: divide sequência longa em janelas overlapped.
|
| 12 |
-
4. **Hierarchical Pooling**: 3 níveis de pooling (stride 4, 16, 64).
|
| 13 |
-
|
| 14 |
-
V6 melhoria vs V13.9.2:
|
| 15 |
-
* ToMe vetorizado (sem loop Python)
|
| 16 |
-
* KNN Indexer com centroids EMA (não recalcula k-means a cada step)
|
| 17 |
-
* Suporte a batch (V13.9.2 era single-sample)
|
| 18 |
-
* Memory-budget adaptativo: ajusta r dinamicamente para caber em RAM
|
| 19 |
-
|
| 20 |
-
REFERÊNCIAS:
|
| 21 |
-
- Bolya et al. (2023). "Token Merging: Your ViT but Faster". ICLR.
|
| 22 |
-
- Johnson et al. (2019). "Billion-scale similarity search with GPUs". IEEE TBD.
|
| 23 |
-
- Ratner et al. (2023). "Speeding up BERT by 2x with ToMe".
|
| 24 |
-
"""
|
| 25 |
-
from __future__ import annotations
|
| 26 |
-
|
| 27 |
-
import math
|
| 28 |
-
import torch
|
| 29 |
-
import torch.nn as nn
|
| 30 |
-
import torch.nn.functional as F
|
| 31 |
-
from typing import Optional, Tuple
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 35 |
-
# ToMeCompressor — Token Merging via Bipartite Soft Matching
|
| 36 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 37 |
-
class ToMeCompressor(nn.Module):
|
| 38 |
-
"""Token Merging (Bolya 2023) — reduz N → N-r tokens.
|
| 39 |
-
|
| 40 |
-
Algoritmo:
|
| 41 |
-
1. Divide tokens em 2 grupos A (par) e B (ímpar) alternados
|
| 42 |
-
2. Para cada token em A, encontra o mais similar em B (cosine sim)
|
| 43 |
-
3. Os r pares com maior similaridade são merged (média ponderada)
|
| 44 |
-
4. Tokens não merged de A e B são concatenados na saída
|
| 45 |
-
|
| 46 |
-
V6: vetorizado, suporta batch, r adaptativo ao budget de memória.
|
| 47 |
-
|
| 48 |
-
Args:
|
| 49 |
-
r: nº de tokens a remover por forward (default 8)
|
| 50 |
-
max_r: máximo de tokens removíveis (default 64)
|
| 51 |
-
adaptive: se True, ajusta r baseado no tamanho do batch (default True)
|
| 52 |
-
"""
|
| 53 |
-
|
| 54 |
-
def __init__(
|
| 55 |
-
self,
|
| 56 |
-
r: int = 8,
|
| 57 |
-
max_r: int = 64,
|
| 58 |
-
adaptive: bool = True,
|
| 59 |
-
):
|
| 60 |
-
super().__init__()
|
| 61 |
-
self.r = r
|
| 62 |
-
self.max_r = max_r
|
| 63 |
-
self.adaptive = adaptive
|
| 64 |
-
|
| 65 |
-
def forward(
|
| 66 |
-
self,
|
| 67 |
-
x: torch.Tensor,
|
| 68 |
-
r: Optional[int] = None,
|
| 69 |
-
) -> Tuple[torch.Tensor, dict]:
|
| 70 |
-
"""x: (batch, N, d) → (batch, N - r_merged, d).
|
| 71 |
-
|
| 72 |
-
Returns:
|
| 73 |
-
merged: (batch, N - r_merged, d)
|
| 74 |
-
aux: dict com r_used, n_tokens_in, n_tokens_out
|
| 75 |
-
"""
|
| 76 |
-
B, N, D = x.shape
|
| 77 |
-
r_use = r if r is not None else self.r
|
| 78 |
-
if self.adaptive:
|
| 79 |
-
# Ajusta r para não remover mais que 50% dos tokens
|
| 80 |
-
r_use = min(r_use, N // 2, self.max_r)
|
| 81 |
-
if r_use <= 0 or N <= 2:
|
| 82 |
-
return x, {"r_used": 0, "n_tokens_in": N, "n_tokens_out": N}
|
| 83 |
-
|
| 84 |
-
# Passo 1: dividir tokens em A (par) e B (ímpar)
|
| 85 |
-
a = x[:, ::2, :] # (B, N//2, D)
|
| 86 |
-
b = x[:, 1::2, :] # (B, N - N//2, D) ou (B, N//2, D)
|
| 87 |
-
|
| 88 |
-
# Passo 2: similaridade cosine entre A e B
|
| 89 |
-
a_norm = F.normalize(a, dim=-1)
|
| 90 |
-
b_norm = F.normalize(b, dim=-1)
|
| 91 |
-
sim = a_norm @ b_norm.transpose(-1, -2) # (B, N_A, N_B)
|
| 92 |
-
|
| 93 |
-
# Passo 3: para cada token em A, encontra o mais similar em B
|
| 94 |
-
max_sim_a, max_idx_b = sim.max(dim=-1) # (B, N_A)
|
| 95 |
-
|
| 96 |
-
# Passo 4: seleciona os r_use pares com maior similaridade
|
| 97 |
-
# Garante que cada token de B só é merged com 1 de A (matching bipartido soft)
|
| 98 |
-
topk_vals, topk_idx_a = max_sim_a.topk(min(r_use, max_sim_a.shape[1] - 1), dim=-1)
|
| 99 |
-
|
| 100 |
-
# Merge: média dos pares selecionados
|
| 101 |
-
# Para os tokens de A selecionados, substitui pelo merge com B correspondente
|
| 102 |
-
merged_tokens = []
|
| 103 |
-
used_b_mask = torch.zeros_like(sim) # (B, N_A, N_B)
|
| 104 |
-
# Vectorizado: marca os pares usados
|
| 105 |
-
# topk_idx_a: (B, r_use), max_idx_b é (B, N_A) → precisamos gather
|
| 106 |
-
b_indices_for_topk = max_idx_b.gather(1, topk_idx_a) # (B, r_use)
|
| 107 |
-
|
| 108 |
-
# Tokens merged: média ponderada (peso 0.5 cada)
|
| 109 |
-
a_selected = a.gather(1, topk_idx_a.unsqueeze(-1).expand(-1, -1, D)) # (B, r_use, D)
|
| 110 |
-
b_selected = b.gather(1, b_indices_for_topk.unsqueeze(-1).expand(-1, -1, D)) # (B, r_use, D)
|
| 111 |
-
merged = 0.5 * (a_selected + b_selected) # (B, r_use, D)
|
| 112 |
-
|
| 113 |
-
# Tokens não merged de A: máscara booleana
|
| 114 |
-
a_mask = torch.ones(B, a.shape[1], dtype=torch.bool, device=x.device)
|
| 115 |
-
a_mask.scatter_(1, topk_idx_a, False)
|
| 116 |
-
a_remaining = a[a_mask].reshape(B, -1, D) # (B, N_A - r_use, D)
|
| 117 |
-
|
| 118 |
-
# Tokens não merged de B: máscara booleana
|
| 119 |
-
b_mask = torch.ones(B, b.shape[1], dtype=torch.bool, device=x.device)
|
| 120 |
-
b_mask.scatter_(1, b_indices_for_topk, False)
|
| 121 |
-
b_remaining = b[b_mask].reshape(B, -1, D) # (B, N_B - r_use, D)
|
| 122 |
-
|
| 123 |
-
# Concatena: merged + a_remaining + b_remaining
|
| 124 |
-
out = torch.cat([merged, a_remaining, b_remaining], dim=1) # (B, N - r_use, D)
|
| 125 |
-
|
| 126 |
-
return out, {
|
| 127 |
-
"r_used": r_use,
|
| 128 |
-
"n_tokens_in": N,
|
| 129 |
-
"n_tokens_out": out.shape[1],
|
| 130 |
-
}
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 134 |
-
# KNNIndexer — IVF-like Index for fast attention
|
| 135 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 136 |
-
class KNNIndexer(nn.Module):
|
| 137 |
-
"""KNN Indexer com centroids EMA (IVF-like).
|
| 138 |
-
|
| 139 |
-
Para 2M tokens:
|
| 140 |
-
1. Mantém K centroids (k-means inicializado, EMA atualizado)
|
| 141 |
-
2. Cada token é atribuído a 1 centroid (hard assignment) ou
|
| 142 |
-
n_probe centroids (soft assignment via top-k)
|
| 143 |
-
3. Atenção só é computada entre query e tokens dos n_probe centroids
|
| 144 |
-
mais próximos → O(N · n_probe · K/N) = O(n_probe · K)
|
| 145 |
-
|
| 146 |
-
V6: centroids atualizados via EMA (não k-means full) → diferente do IVF
|
| 147 |
-
clássico, é online e diferenciável.
|
| 148 |
-
|
| 149 |
-
Args:
|
| 150 |
-
d: dimensão dos tokens
|
| 151 |
-
n_centroids: nº de centroids K (default 256)
|
| 152 |
-
n_probe: nº de centroids a consultar por query (default 8)
|
| 153 |
-
ema_decay: decay do EMA para centroids (default 0.99)
|
| 154 |
-
"""
|
| 155 |
-
|
| 156 |
-
def __init__(
|
| 157 |
-
self,
|
| 158 |
-
d: int,
|
| 159 |
-
n_centroids: int = 256,
|
| 160 |
-
n_probe: int = 8,
|
| 161 |
-
ema_decay: float = 0.99,
|
| 162 |
-
):
|
| 163 |
-
super().__init__()
|
| 164 |
-
self.d = d
|
| 165 |
-
self.n_centroids = n_centroids
|
| 166 |
-
self.n_probe = min(n_probe, n_centroids)
|
| 167 |
-
self.ema_decay = ema_decay
|
| 168 |
-
|
| 169 |
-
# Centroids: (K, d) — inicializados random
|
| 170 |
-
self.register_buffer("centroids", torch.randn(n_centroids, d) * 0.02)
|
| 171 |
-
self.register_buffer("centroid_count", torch.zeros(n_centroids))
|
| 172 |
-
|
| 173 |
-
@torch.no_grad()
|
| 174 |
-
def update_centroids_ema(self, x: torch.Tensor) -> None:
|
| 175 |
-
"""Atualiza centroids via EMA (chamado durante treino).
|
| 176 |
-
|
| 177 |
-
Args:
|
| 178 |
-
x: (batch, N, d) — tokens do batch atual
|
| 179 |
-
"""
|
| 180 |
-
B, N, D = x.shape
|
| 181 |
-
x_flat = x.reshape(B * N, D)
|
| 182 |
-
# Atribui cada token ao centroid mais próximo
|
| 183 |
-
dists = torch.cdist(x_flat, self.centroids) # (B*N, K)
|
| 184 |
-
assignments = dists.argmin(dim=-1) # (B*N,)
|
| 185 |
-
|
| 186 |
-
# Atualiza centroids via EMA
|
| 187 |
-
for k in range(self.n_centroids):
|
| 188 |
-
mask = assignments == k
|
| 189 |
-
if mask.any():
|
| 190 |
-
new_c = x_flat[mask].mean(dim=0)
|
| 191 |
-
self.centroids[k] = (
|
| 192 |
-
self.ema_decay * self.centroids[k]
|
| 193 |
-
+ (1.0 - self.ema_decay) * new_c
|
| 194 |
-
)
|
| 195 |
-
self.centroid_count[k] += mask.sum().item()
|
| 196 |
-
|
| 197 |
-
def forward(
|
| 198 |
-
self,
|
| 199 |
-
query: torch.Tensor,
|
| 200 |
-
keys: torch.Tensor,
|
| 201 |
-
values: torch.Tensor,
|
| 202 |
-
) -> Tuple[torch.Tensor, dict]:
|
| 203 |
-
"""Atenção aproximada via IVF.
|
| 204 |
-
|
| 205 |
-
Args:
|
| 206 |
-
query: (batch, M, d)
|
| 207 |
-
keys: (batch, N, d)
|
| 208 |
-
values: (batch, N, d)
|
| 209 |
-
|
| 210 |
-
Returns:
|
| 211 |
-
out: (batch, M, d) — aproximação de attention(query, keys, values)
|
| 212 |
-
aux: dict com n_probe, compression_ratio
|
| 213 |
-
"""
|
| 214 |
-
B, M, D = query.shape
|
| 215 |
-
_, N, _ = keys.shape
|
| 216 |
-
|
| 217 |
-
# 1. Atribui keys aos centroids mais próximos
|
| 218 |
-
key_dists = torch.cdist(keys, self.centroids) # (B, N, K)
|
| 219 |
-
key_assign = key_dists.argmin(dim=-1) # (B, N) — índice do centroid
|
| 220 |
-
|
| 221 |
-
# 2. Para cada query, encontra os n_probe centroids mais próximos
|
| 222 |
-
query_dists = torch.cdist(query, self.centroids) # (B, M, K)
|
| 223 |
-
_, probe_centroids = query_dists.topk(self.n_probe, dim=-1, largest=False) # (B, M, n_probe)
|
| 224 |
-
|
| 225 |
-
# 3. Para cada query, seleciona apenas keys cujo centroid está em probe_centroids
|
| 226 |
-
# Vectorizado: expande para (B, M, N) via broadcasting
|
| 227 |
-
# probe_centroids: (B, M, n_probe) → (B, M, 1, n_probe)
|
| 228 |
-
# key_assign: (B, N) → (B, 1, N, 1)
|
| 229 |
-
probe_expanded = probe_centroids.unsqueeze(2) # (B, M, 1, n_probe)
|
| 230 |
-
key_expanded = key_assign.unsqueeze(1).unsqueeze(-1) # (B, 1, N, 1)
|
| 231 |
-
# mask: (B, M, N) — True se key j está em um dos n_probe centroids da query i
|
| 232 |
-
mask = (probe_expanded == key_expanded).any(dim=-1) # (B, M, N)
|
| 233 |
-
|
| 234 |
-
# 4. Atenção masked
|
| 235 |
-
scores = (query @ keys.transpose(-1, -2)) / math.sqrt(D) # (B, M, N)
|
| 236 |
-
scores = scores.masked_fill(~mask, float("-inf"))
|
| 237 |
-
attn = F.softmax(scores, dim=-1) # (B, M, N)
|
| 238 |
-
# Se uma linha inteira é -inf, softmax dá NaN → substitui por 0
|
| 239 |
-
attn = torch.nan_to_num(attn, nan=0.0)
|
| 240 |
-
out = attn @ values # (B, M, D)
|
| 241 |
-
|
| 242 |
-
# Estatísticas
|
| 243 |
-
avg_keys_per_query = mask.float().sum(dim=-1).mean().item()
|
| 244 |
-
compression_ratio = N / max(avg_keys_per_query, 1.0)
|
| 245 |
-
|
| 246 |
-
return out, {
|
| 247 |
-
"n_probe": self.n_probe,
|
| 248 |
-
"n_centroids": self.n_centroids,
|
| 249 |
-
"avg_keys_per_query": avg_keys_per_query,
|
| 250 |
-
"compression_ratio": compression_ratio,
|
| 251 |
-
}
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 255 |
-
# WindowContext — Combina ToMe + KNN + Sliding Window
|
| 256 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 257 |
-
class WindowContext(nn.Module):
|
| 258 |
-
"""Window Context para 2M tokens — combina ToMe + KNN.
|
| 259 |
-
|
| 260 |
-
Pipeline:
|
| 261 |
-
1. Aplica ToMe para reduzir N → N-r tokens (compressão espacial)
|
| 262 |
-
2. Mantém KNN Indexer para atenção aproximada (compressão temporal)
|
| 263 |
-
3. Em inference: sliding window de tamanho W com overlap W//2
|
| 264 |
-
|
| 265 |
-
Args:
|
| 266 |
-
d: dimensão dos tokens
|
| 267 |
-
tome_r: tokens a remover por ToMe (default 8)
|
| 268 |
-
n_centroids: nº de centroids do KNN (default 64)
|
| 269 |
-
n_probe: nº de centroids a consultar (default 4)
|
| 270 |
-
window_size: tamanho da janela (default 4096)
|
| 271 |
-
"""
|
| 272 |
-
|
| 273 |
-
def __init__(
|
| 274 |
-
self,
|
| 275 |
-
d: int,
|
| 276 |
-
tome_r: int = 8,
|
| 277 |
-
n_centroids: int = 64,
|
| 278 |
-
n_probe: int = 4,
|
| 279 |
-
window_size: int = 4096,
|
| 280 |
-
):
|
| 281 |
-
super().__init__()
|
| 282 |
-
self.d = d
|
| 283 |
-
self.window_size = window_size
|
| 284 |
-
self.tome = ToMeCompressor(r=tome_r)
|
| 285 |
-
self.knn = KNNIndexer(
|
| 286 |
-
d=d, n_centroids=n_centroids, n_probe=n_probe
|
| 287 |
-
)
|
| 288 |
-
|
| 289 |
-
def forward(
|
| 290 |
-
self,
|
| 291 |
-
x: torch.Tensor,
|
| 292 |
-
return_aux: bool = False,
|
| 293 |
-
) -> torch.Tensor:
|
| 294 |
-
"""x: (batch, N, d) → (batch, N - r, d) comprimido + indexado.
|
| 295 |
-
|
| 296 |
-
Aplica ToMe (se N > 2*r) e treina KNN centroids via EMA.
|
| 297 |
-
"""
|
| 298 |
-
# 1. ToMe compression
|
| 299 |
-
if x.shape[1] > 2 * self.tome.r:
|
| 300 |
-
x_compressed, tome_aux = self.tome(x)
|
| 301 |
-
else:
|
| 302 |
-
x_compressed = x
|
| 303 |
-
tome_aux = {"r_used": 0, "n_tokens_in": x.shape[1], "n_tokens_out": x.shape[1]}
|
| 304 |
-
|
| 305 |
-
# 2. Atualiza KNN centroids (treino apenas)
|
| 306 |
-
if self.training:
|
| 307 |
-
self.knn.update_centroids_ema(x_compressed)
|
| 308 |
-
|
| 309 |
-
# 3. Atenção aproximada via KNN (query=x_compressed, keys=x_compressed)
|
| 310 |
-
out, knn_aux = self.knn(x_compressed, x_compressed, x_compressed)
|
| 311 |
-
|
| 312 |
-
if not return_aux:
|
| 313 |
-
return out
|
| 314 |
-
|
| 315 |
-
return out, {
|
| 316 |
-
"tome": tome_aux,
|
| 317 |
-
"knn": knn_aux,
|
| 318 |
-
"compression_total": (
|
| 319 |
-
tome_aux["n_tokens_in"] / max(tome_aux["n_tokens_out"], 1)
|
| 320 |
-
) * max(knn_aux["compression_ratio"], 1.0),
|
| 321 |
-
}
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 325 |
-
# Estimativa de memória para 2M tokens
|
| 326 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 327 |
-
def estimate_2m_tokens_memory(
|
| 328 |
-
d: int = 128,
|
| 329 |
-
batch: int = 1,
|
| 330 |
-
dtype_bytes: int = 4,
|
| 331 |
-
) -> dict:
|
| 332 |
-
"""Estima memória para 2M tokens.
|
| 333 |
-
|
| 334 |
-
Sem WindowContext:
|
| 335 |
-
attention matrix: (batch, 2M, 2M) * dtype_bytes = 16 TB (float32)
|
| 336 |
-
tokens: (batch, 2M, d) * dtype_bytes = 1 GB
|
| 337 |
-
|
| 338 |
-
Com WindowContext (ToMe r=8 per layer, 12 layers):
|
| 339 |
-
tokens reduzidos: 2M - 12*8 = 1,999,904 (~99.99% retido)
|
| 340 |
-
KNN attention: (batch, 2M, n_probe * K/N) = (1, 2M, 4*64/2M) = 8 MB
|
| 341 |
-
|
| 342 |
-
Returns:
|
| 343 |
-
dict com estimativas em GB
|
| 344 |
-
"""
|
| 345 |
-
N = 2_000_000
|
| 346 |
-
attention_full_gb = (batch * N * N * dtype_bytes) / (1024 ** 4)
|
| 347 |
-
tokens_full_gb = (batch * N * d * dtype_bytes) / (1024 ** 3)
|
| 348 |
-
|
| 349 |
-
# Com ToMe + KNN
|
| 350 |
-
n_centroids = 64
|
| 351 |
-
n_probe = 4
|
| 352 |
-
K_per_query = n_probe * (N // n_centroids) # ~n_probe * cluster_size
|
| 353 |
-
attention_knn_gb = (batch * N * K_per_query * dtype_bytes) / (1024 ** 3)
|
| 354 |
-
|
| 355 |
-
return {
|
| 356 |
-
"n_tokens": N,
|
| 357 |
-
"d": d,
|
| 358 |
-
"batch": batch,
|
| 359 |
-
"attention_full_gb": attention_full_gb,
|
| 360 |
-
"tokens_full_gb": tokens_full_gb,
|
| 361 |
-
"attention_knn_gb": attention_knn_gb,
|
| 362 |
-
"reduction_factor": attention_full_gb / max(attention_knn_gb, 1e-9),
|
| 363 |
-
}
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
__all__ = [
|
| 367 |
-
"ToMeCompressor",
|
| 368 |
-
"KNNIndexer",
|
| 369 |
-
"WindowContext",
|
| 370 |
-
"estimate_2m_tokens_memory",
|
| 371 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/bigru_t/reasoning/reasoning_system.py
DELETED
|
@@ -1,281 +0,0 @@
|
|
| 1 |
-
"""reasoning_system.py — Sistema de Raciocínio (Reasoning/Thinking) integrável (V6).
|
| 2 |
-
|
| 3 |
-
Adaptado de gru-ring-v13-9-2/source/xavante_work/flexnet/reasoning_engine.py
|
| 4 |
-
e distributed_reasoning_system.py.
|
| 5 |
-
|
| 6 |
-
Implementa um sistema de raciocínio multi-fase inspirado em System 2 de
|
| 7 |
-
Kahneman, com 8 fases (V13.9.2):
|
| 8 |
-
1. Think — pensamento inicial sobre o problema
|
| 9 |
-
2. Plan — planejamento de solução
|
| 10 |
-
3. Decompose — decomposição em subproblemas
|
| 11 |
-
4. Execute — execução dos passos
|
| 12 |
-
5. Monitor — monitoramento do progresso
|
| 13 |
-
6. Predict — predição de resultado
|
| 14 |
-
7. Adjust — ajuste baseado em feedback
|
| 15 |
-
8. Compose — composição da resposta final
|
| 16 |
-
|
| 17 |
-
V6 melhorias vs V13.9.2:
|
| 18 |
-
* Self-contained (sem PEVSystem distribuído)
|
| 19 |
-
* BayesianStrategySelector com Thompson sampling (era epsilon-greedy)
|
| 20 |
-
* Compatível com langchain (pode ser usado como tool)
|
| 21 |
-
* Vetorizado quando possível
|
| 22 |
-
|
| 23 |
-
REFERÊNCIAS:
|
| 24 |
-
- Kahneman (2011). "Thinking, Fast and Slow".
|
| 25 |
-
- Wei et al. (2022). "Chain-of-Thought Prompting".
|
| 26 |
-
- Yao et al. (2023). "Tree of Thoughts".
|
| 27 |
-
- Thompson (1933). "On the likelihood that one unknown probability exceeds
|
| 28 |
-
another" (Thompson sampling).
|
| 29 |
-
"""
|
| 30 |
-
from __future__ import annotations
|
| 31 |
-
|
| 32 |
-
import math
|
| 33 |
-
import torch
|
| 34 |
-
import torch.nn as nn
|
| 35 |
-
import torch.nn.functional as F
|
| 36 |
-
from typing import Optional, List, Dict, Tuple
|
| 37 |
-
from collections import deque
|
| 38 |
-
import random
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 42 |
-
# ReasoningPhase — enum das 8 fases
|
| 43 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 44 |
-
REASONING_PHASES = [
|
| 45 |
-
"think",
|
| 46 |
-
"plan",
|
| 47 |
-
"decompose",
|
| 48 |
-
"execute",
|
| 49 |
-
"monitor",
|
| 50 |
-
"predict",
|
| 51 |
-
"adjust",
|
| 52 |
-
"compose",
|
| 53 |
-
]
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 57 |
-
# BayesianStrategySelector — Thompson sampling
|
| 58 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 59 |
-
class BayesianStrategySelector(nn.Module):
|
| 60 |
-
"""Seletor de estratégia bayesiano (Thompson sampling).
|
| 61 |
-
|
| 62 |
-
Mantém uma distribuição Beta(α_s, β_s) para cada estratégia s.
|
| 63 |
-
A cada passo, amostra de cada Beta e escolhe a estratégia com maior amostra.
|
| 64 |
-
Após receber reward, atualiza α_s ou β_s.
|
| 65 |
-
|
| 66 |
-
Args:
|
| 67 |
-
n_strategies: nº de estratégias (default 8 — uma por fase)
|
| 68 |
-
prior_alpha: α inicial (default 1.0 — uniforme)
|
| 69 |
-
prior_beta: β inicial (default 1.0 — uniforme)
|
| 70 |
-
"""
|
| 71 |
-
|
| 72 |
-
def __init__(
|
| 73 |
-
self,
|
| 74 |
-
n_strategies: int = 8,
|
| 75 |
-
prior_alpha: float = 1.0,
|
| 76 |
-
prior_beta: float = 1.0,
|
| 77 |
-
):
|
| 78 |
-
super().__init__()
|
| 79 |
-
self.n_strategies = n_strategies
|
| 80 |
-
# α e β das distribuições Beta (uma por estratégia)
|
| 81 |
-
self.register_buffer("alpha", torch.full((n_strategies,), float(prior_alpha)))
|
| 82 |
-
self.register_buffer("beta", torch.full((n_strategies,), float(prior_beta)))
|
| 83 |
-
|
| 84 |
-
def select(self) -> int:
|
| 85 |
-
"""Thompson sampling: amostra de cada Beta, retorna índice do maior."""
|
| 86 |
-
# Beta(α, β) sampling via torch.distributions
|
| 87 |
-
dist = torch.distributions.Beta(self.alpha, self.beta)
|
| 88 |
-
samples = dist.sample()
|
| 89 |
-
return int(samples.argmax().item())
|
| 90 |
-
|
| 91 |
-
def update(self, strategy: int, reward: float) -> None:
|
| 92 |
-
"""Atualiza a distribuição Beta da estratégia selecionada.
|
| 93 |
-
|
| 94 |
-
Beta(α, β) é conjugada de Bernoulli:
|
| 95 |
-
- reward ≥ 0.5 (sucesso): α ← α + 1
|
| 96 |
-
- reward < 0.5 (fracasso): β ← β + 1
|
| 97 |
-
"""
|
| 98 |
-
if reward >= 0.5:
|
| 99 |
-
self.alpha[strategy] += 1.0
|
| 100 |
-
else:
|
| 101 |
-
self.beta[strategy] += 1.0
|
| 102 |
-
|
| 103 |
-
def get_strategy_probs(self) -> torch.Tensor:
|
| 104 |
-
"""Retorna probabilidade esperada de cada estratégia (mean of Beta)."""
|
| 105 |
-
return self.alpha / (self.alpha + self.beta)
|
| 106 |
-
|
| 107 |
-
def extra_repr(self) -> str:
|
| 108 |
-
probs = self.get_strategy_probs()
|
| 109 |
-
return f"n_strategies={self.n_strategies}, probs={probs.tolist()}"
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 113 |
-
# ReasoningEngine — motor de raciocínio 8-fase
|
| 114 |
-
# ════════════════════════════════════════════════════════���══════════════════
|
| 115 |
-
class ReasoningEngine(nn.Module):
|
| 116 |
-
"""Sistema de raciocínio multi-fase (V6).
|
| 117 |
-
|
| 118 |
-
Args:
|
| 119 |
-
d_model: dimensão do hidden state
|
| 120 |
-
vocab_size: tamanho do vocabulário
|
| 121 |
-
n_phases: nº de fases (default 8)
|
| 122 |
-
max_steps: máx de passos de raciocínio (default 16)
|
| 123 |
-
use_bayesian_selector: se True, usa Thompson sampling (default True)
|
| 124 |
-
|
| 125 |
-
Uso:
|
| 126 |
-
engine = ReasoningEngine(d_model=128, vocab_size=1000)
|
| 127 |
-
output = engine(inputs, hidden, targets)
|
| 128 |
-
"""
|
| 129 |
-
|
| 130 |
-
def __init__(
|
| 131 |
-
self,
|
| 132 |
-
d_model: int,
|
| 133 |
-
vocab_size: int,
|
| 134 |
-
n_phases: int = 8,
|
| 135 |
-
max_steps: int = 16,
|
| 136 |
-
use_bayesian_selector: bool = True,
|
| 137 |
-
):
|
| 138 |
-
super().__init__()
|
| 139 |
-
self.d_model = d_model
|
| 140 |
-
self.vocab_size = vocab_size
|
| 141 |
-
self.n_phases = n_phases
|
| 142 |
-
self.max_steps = max_steps
|
| 143 |
-
|
| 144 |
-
# Embedding de fase (um por fase)
|
| 145 |
-
self.phase_embedding = nn.Embedding(n_phases, d_model)
|
| 146 |
-
|
| 147 |
-
# Projeção do hidden + phase → logits da próxima fase
|
| 148 |
-
self.phase_proj = nn.Linear(2 * d_model, n_phases)
|
| 149 |
-
|
| 150 |
-
# Projeção final → logits de vocabulário
|
| 151 |
-
self.out_proj = nn.Linear(d_model, vocab_size)
|
| 152 |
-
|
| 153 |
-
# Seletor de estratégia
|
| 154 |
-
self.use_bayesian = use_bayesian_selector
|
| 155 |
-
if use_bayesian_selector:
|
| 156 |
-
self.strategy_selector = BayesianStrategySelector(n_strategies=n_phases)
|
| 157 |
-
|
| 158 |
-
# Histórico de raciocínio (para logging)
|
| 159 |
-
self.reasoning_history: List[Dict] = []
|
| 160 |
-
|
| 161 |
-
def forward(
|
| 162 |
-
self,
|
| 163 |
-
hidden: torch.Tensor,
|
| 164 |
-
targets: Optional[torch.Tensor] = None,
|
| 165 |
-
return_chain: bool = False,
|
| 166 |
-
) -> dict:
|
| 167 |
-
"""Executa raciocínio multi-fase.
|
| 168 |
-
|
| 169 |
-
Args:
|
| 170 |
-
hidden: (batch, T, d_model) — hidden state do backbone
|
| 171 |
-
targets: (batch, T) — targets (opcional, para supervised loss)
|
| 172 |
-
return_chain: se True, retorna cadeia de raciocínio
|
| 173 |
-
|
| 174 |
-
Returns:
|
| 175 |
-
dict com:
|
| 176 |
-
- logits: (B, T, vocab_size) — logits finais
|
| 177 |
-
- loss: escalar (se targets fornecido)
|
| 178 |
-
- chain: lista de fases executadas (se return_chain=True)
|
| 179 |
-
- aux: estatísticas
|
| 180 |
-
"""
|
| 181 |
-
B, T, D = hidden.shape
|
| 182 |
-
device = hidden.device
|
| 183 |
-
|
| 184 |
-
# Estado de raciocínio: começa com hidden médio sobre T
|
| 185 |
-
reasoning_state = hidden.mean(dim=1) # (B, D)
|
| 186 |
-
chain: List[Dict] = []
|
| 187 |
-
phase_logits_accum = torch.zeros(B, self.n_phases, device=device)
|
| 188 |
-
|
| 189 |
-
for step in range(self.max_steps):
|
| 190 |
-
# Seleciona fase (Thompson sampling ou determinístico)
|
| 191 |
-
if self.use_bayesian:
|
| 192 |
-
phase_idx = self.strategy_selector.select()
|
| 193 |
-
else:
|
| 194 |
-
phase_idx = step % self.n_phases
|
| 195 |
-
|
| 196 |
-
phase_emb = self.phase_embedding(
|
| 197 |
-
torch.tensor(phase_idx, device=device)
|
| 198 |
-
) # (D,)
|
| 199 |
-
|
| 200 |
-
# Combina reasoning_state com phase_emb
|
| 201 |
-
combined = torch.cat([
|
| 202 |
-
reasoning_state,
|
| 203 |
-
phase_emb.unsqueeze(0).expand(B, -1),
|
| 204 |
-
], dim=-1) # (B, 2D)
|
| 205 |
-
|
| 206 |
-
# Prediz próxima fase
|
| 207 |
-
phase_logits = self.phase_proj(combined) # (B, n_phases)
|
| 208 |
-
phase_logits_accum += phase_logits
|
| 209 |
-
|
| 210 |
-
# Atualiza reasoning_state (residual com phase_emb)
|
| 211 |
-
reasoning_state = reasoning_state + 0.1 * phase_emb.unsqueeze(0)
|
| 212 |
-
|
| 213 |
-
# Log da fase
|
| 214 |
-
phase_info = {
|
| 215 |
-
"step": step,
|
| 216 |
-
"phase": REASONING_PHASES[phase_idx] if phase_idx < len(REASONING_PHASES) else f"phase_{phase_idx}",
|
| 217 |
-
"phase_idx": phase_idx,
|
| 218 |
-
}
|
| 219 |
-
chain.append(phase_info)
|
| 220 |
-
|
| 221 |
-
# Reward (se targets fornecido): baseado em quão bem prediz
|
| 222 |
-
if targets is not None and self.use_bayesian:
|
| 223 |
-
# Reward aproximado: 1 se a próxima fase melhora a predição
|
| 224 |
-
# (aqui simplificado: 0.5 neutro)
|
| 225 |
-
self.strategy_selector.update(phase_idx, 0.5)
|
| 226 |
-
|
| 227 |
-
# Critério de parada: se fase volta a ser "think", termina
|
| 228 |
-
if step > 0 and phase_idx == 0:
|
| 229 |
-
break
|
| 230 |
-
|
| 231 |
-
# Logits finais: out_proj sobre reasoning_state expandido para T
|
| 232 |
-
final_hidden = reasoning_state.unsqueeze(1).expand(-1, T, -1) # (B, T, D)
|
| 233 |
-
# Combina com hidden original (residual)
|
| 234 |
-
final_hidden = final_hidden + 0.5 * hidden # (B, T, D)
|
| 235 |
-
logits = self.out_proj(final_hidden) # (B, T, vocab_size)
|
| 236 |
-
|
| 237 |
-
# Loss (se targets)
|
| 238 |
-
loss = None
|
| 239 |
-
if targets is not None:
|
| 240 |
-
loss = F.cross_entropy(
|
| 241 |
-
logits.reshape(-1, self.vocab_size),
|
| 242 |
-
targets.reshape(-1),
|
| 243 |
-
ignore_index=-100,
|
| 244 |
-
)
|
| 245 |
-
|
| 246 |
-
# Salva histórico
|
| 247 |
-
self.reasoning_history.append({
|
| 248 |
-
"chain": chain,
|
| 249 |
-
"n_steps": len(chain),
|
| 250 |
-
})
|
| 251 |
-
|
| 252 |
-
result = {
|
| 253 |
-
"logits": logits,
|
| 254 |
-
"loss": loss,
|
| 255 |
-
"aux": {
|
| 256 |
-
"n_reasoning_steps": len(chain),
|
| 257 |
-
"phases_visited": [c["phase"] for c in chain],
|
| 258 |
-
"phase_logits_mean": float(phase_logits_accum.mean().item()),
|
| 259 |
-
},
|
| 260 |
-
}
|
| 261 |
-
if return_chain:
|
| 262 |
-
result["chain"] = chain
|
| 263 |
-
|
| 264 |
-
return result
|
| 265 |
-
|
| 266 |
-
def get_last_chain(self) -> Optional[List[Dict]]:
|
| 267 |
-
"""Retorna a última cadeia de raciocínio."""
|
| 268 |
-
if not self.reasoning_history:
|
| 269 |
-
return None
|
| 270 |
-
return self.reasoning_history[-1].get("chain")
|
| 271 |
-
|
| 272 |
-
def clear_history(self) -> None:
|
| 273 |
-
"""Limpa histórico de raciocínio (libera RAM)."""
|
| 274 |
-
self.reasoning_history.clear()
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
__all__ = [
|
| 278 |
-
"REASONING_PHASES",
|
| 279 |
-
"BayesianStrategySelector",
|
| 280 |
-
"ReasoningEngine",
|
| 281 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/bigru_t/training/rlhf_possibility_mapping.py
DELETED
|
@@ -1,387 +0,0 @@
|
|
| 1 |
-
"""rlhf_possibility_mapping.py — Mapeamento de Possibilidades Aprendível com RLHF (V6).
|
| 2 |
-
|
| 3 |
-
Adaptado de gru-ring-v13-9-2/source/xavante_work/flexnet/learnable_mapping.py
|
| 4 |
-
e punishment_reward_system.py.
|
| 5 |
-
|
| 6 |
-
Implementa o mapeamento de possibilidades aprendível com RLHF (Reinforcement
|
| 7 |
-
Learning from Human Feedback). A fórmula central (Teorema 2 do V13.9.2):
|
| 8 |
-
|
| 9 |
-
Φ = Θ + η · r · σ(trust · τ_T) · Σ_i wᵢ · gᵢ - λ_rep · P_rep · ∇P_rep
|
| 10 |
-
|
| 11 |
-
onde:
|
| 12 |
-
- Θ: parâmetros base do modelo
|
| 13 |
-
- η: learning rate
|
| 14 |
-
- r: reward do humano
|
| 15 |
-
- σ(trust · τ_T): gate de confiança (TrustNeuralAgent interno, V6 self-contained)
|
| 16 |
-
- wᵢ: pesos cooperativos (wᵢ ∝ 1/‖gᵢ‖²) — Teorema 2
|
| 17 |
-
- gᵢ: gradientes das componentes
|
| 18 |
-
- P_rep: penalidade de repetição
|
| 19 |
-
- λ_rep: peso da penalidade de repetição
|
| 20 |
-
|
| 21 |
-
V6 melhorias vs V13.9.2:
|
| 22 |
-
* Self-contained: TrustNeuralAgent interno (sem dependência externa)
|
| 23 |
-
* Pesos cooperativos wᵢ calculados online (não pré-computados)
|
| 24 |
-
* Momentum com decay 0.999 (estabilidade)
|
| 25 |
-
* Compatível com PPO simplificado (sem actor-critic completo)
|
| 26 |
-
|
| 27 |
-
REFERÊNCIAS:
|
| 28 |
-
- Schulman et al. (2017). "Proximal Policy Optimization Algorithms".
|
| 29 |
-
- Ouyang et al. (2022). "Training language models to follow instructions with
|
| 30 |
-
human feedback" (InstructGPT).
|
| 31 |
-
- Christiano et al. (2017). "Deep reinforcement learning from human preferences".
|
| 32 |
-
"""
|
| 33 |
-
from __future__ import annotations
|
| 34 |
-
|
| 35 |
-
import math
|
| 36 |
-
import torch
|
| 37 |
-
import torch.nn as nn
|
| 38 |
-
import torch.nn.functional as F
|
| 39 |
-
from typing import Optional, List, Dict
|
| 40 |
-
from collections import deque
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 44 |
-
# RewardModel — modelo de recompensa simples (placeholder, V6)
|
| 45 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 46 |
-
class RewardModel(nn.Module):
|
| 47 |
-
"""Modelo de recompensa simples — projeta hidden state para escalar.
|
| 48 |
-
|
| 49 |
-
Em produção, seria treinado com preferências humanas (Bradley-Terry).
|
| 50 |
-
Aqui é um placeholder linear para fins de integração.
|
| 51 |
-
|
| 52 |
-
Args:
|
| 53 |
-
d_model: dimensão do hidden state
|
| 54 |
-
"""
|
| 55 |
-
|
| 56 |
-
def __init__(self, d_model: int):
|
| 57 |
-
super().__init__()
|
| 58 |
-
self.proj = nn.Sequential(
|
| 59 |
-
nn.Linear(d_model, d_model // 2),
|
| 60 |
-
nn.ReLU(),
|
| 61 |
-
nn.Linear(d_model // 2, 1),
|
| 62 |
-
)
|
| 63 |
-
|
| 64 |
-
def forward(self, hidden: torch.Tensor) -> torch.Tensor:
|
| 65 |
-
"""hidden: (batch, T, d) → reward: (batch,) — média sobre T."""
|
| 66 |
-
return self.proj(hidden).squeeze(-1).mean(dim=-1)
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 70 |
-
# TrustNeuralAgent — self-contained (V6)
|
| 71 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 72 |
-
class TrustNeuralAgent(nn.Module):
|
| 73 |
-
"""Agente de confiança self-contained (sem dependência externa).
|
| 74 |
-
|
| 75 |
-
Mantém um embedding de "trust" por token, atualizado via TD-learning.
|
| 76 |
-
Em V13.9.2, era uma classe externa com dependências complexas. Em V6,
|
| 77 |
-
é uma camada simples com:
|
| 78 |
-
- Embedding de trust (d_model) por vocab_size
|
| 79 |
-
- Atualização TD: trust[t+1] = trust[t] + α · (reward - trust[t])
|
| 80 |
-
|
| 81 |
-
Args:
|
| 82 |
-
vocab_size: tamanho do vocabulário
|
| 83 |
-
d_model: dimensão do trust embedding
|
| 84 |
-
td_alpha: taxa de aprendizado TD (default 0.1)
|
| 85 |
-
"""
|
| 86 |
-
|
| 87 |
-
def __init__(
|
| 88 |
-
self,
|
| 89 |
-
vocab_size: int,
|
| 90 |
-
d_model: int,
|
| 91 |
-
td_alpha: float = 0.1,
|
| 92 |
-
):
|
| 93 |
-
super().__init__()
|
| 94 |
-
self.vocab_size = vocab_size
|
| 95 |
-
self.d_model = d_model
|
| 96 |
-
self.td_alpha = td_alpha
|
| 97 |
-
# Trust embedding: (vocab_size, d_model) — inicializado neutro (0.5)
|
| 98 |
-
self.trust_embedding = nn.Parameter(torch.zeros(vocab_size, d_model))
|
| 99 |
-
# tau_T: temperatura (aprendível)
|
| 100 |
-
self.log_tau_T = nn.Parameter(torch.tensor(0.0)) # τ_T = 1.0
|
| 101 |
-
|
| 102 |
-
@property
|
| 103 |
-
def tau_T(self) -> torch.Tensor:
|
| 104 |
-
return torch.exp(self.log_tau_T)
|
| 105 |
-
|
| 106 |
-
def forward(
|
| 107 |
-
self,
|
| 108 |
-
token_ids: torch.Tensor,
|
| 109 |
-
hidden: torch.Tensor,
|
| 110 |
-
) -> torch.Tensor:
|
| 111 |
-
"""Computa σ(trust · τ_T) para os tokens dados.
|
| 112 |
-
|
| 113 |
-
Args:
|
| 114 |
-
token_ids: (batch, T) — IDs dos tokens
|
| 115 |
-
hidden: (batch, T, d_model) — hidden states (não usado aqui,
|
| 116 |
-
mas mantido para compat)
|
| 117 |
-
|
| 118 |
-
Returns:
|
| 119 |
-
trust_gate: (batch, T) — σ(mean(trust_embedding[token]) · τ_T)
|
| 120 |
-
"""
|
| 121 |
-
trust = self.trust_embedding[token_ids] # (B, T, d)
|
| 122 |
-
trust_scalar = trust.mean(dim=-1) # (B, T)
|
| 123 |
-
# σ(trust · τ_T) — gate de confiança ∈ (0, 1)
|
| 124 |
-
gate = torch.sigmoid(trust_scalar * self.tau_T)
|
| 125 |
-
return gate
|
| 126 |
-
|
| 127 |
-
@torch.no_grad()
|
| 128 |
-
def update_td(self, token_ids: torch.Tensor, rewards: torch.Tensor) -> None:
|
| 129 |
-
"""Atualização TD do trust embedding.
|
| 130 |
-
|
| 131 |
-
trust[token] ← trust[token] + α · (reward - trust[token])
|
| 132 |
-
|
| 133 |
-
Args:
|
| 134 |
-
token_ids: (batch, T) — tokens que receberam feedback
|
| 135 |
-
rewards: (batch, T) ou (batch,) — recompensas
|
| 136 |
-
"""
|
| 137 |
-
if rewards.dim() == 1:
|
| 138 |
-
rewards = rewards.unsqueeze(-1).expand(-1, token_ids.shape[1])
|
| 139 |
-
for b in range(token_ids.shape[0]):
|
| 140 |
-
for t in range(token_ids.shape[1]):
|
| 141 |
-
tid = int(token_ids[b, t].item())
|
| 142 |
-
r = float(rewards[b, t].item())
|
| 143 |
-
# Atualiza apenas o primeiro canal (escalar)
|
| 144 |
-
current = float(self.trust_embedding[tid, 0].item())
|
| 145 |
-
self.trust_embedding.data[tid, 0] = current + self.td_alpha * (r - current)
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 149 |
-
# LearnablePossibilityMapping — RLHF com mapeamento de possibilidades
|
| 150 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 151 |
-
class LearnablePossibilityMapping(nn.Module):
|
| 152 |
-
"""Mapeamento de Possibilidades Aprendível com RLHF (V6).
|
| 153 |
-
|
| 154 |
-
Implementa a fórmula central (Teorema 2 do V13.9.2):
|
| 155 |
-
Φ = Θ + η · r · σ(trust · τ_T) · Σ_i wᵢ · gᵢ - λ_rep · P_rep · ∇P_rep
|
| 156 |
-
|
| 157 |
-
Componentes:
|
| 158 |
-
- TrustNeuralAgent: gate σ(trust · τ_T)
|
| 159 |
-
- RewardModel: recompensa r
|
| 160 |
-
- Pesos cooperativos wᵢ ∝ 1/‖gᵢ‖² (Teorema 2)
|
| 161 |
-
- Momentum com decay 0.999
|
| 162 |
-
|
| 163 |
-
Args:
|
| 164 |
-
model: modelo base
|
| 165 |
-
vocab_size: tamanho do vocabulário
|
| 166 |
-
d_model: dimensão do modelo
|
| 167 |
-
eta_init: η inicial (learning rate RL) — default 0.01
|
| 168 |
-
lambda_rep_init: λ_rep inicial — default 0.1
|
| 169 |
-
momentum_decay: decay do momentum — default 0.999
|
| 170 |
-
"""
|
| 171 |
-
|
| 172 |
-
def __init__(
|
| 173 |
-
self,
|
| 174 |
-
model: nn.Module,
|
| 175 |
-
vocab_size: int,
|
| 176 |
-
d_model: int,
|
| 177 |
-
eta_init: float = 0.01,
|
| 178 |
-
lambda_rep_init: float = 0.1,
|
| 179 |
-
momentum_decay: float = 0.999,
|
| 180 |
-
):
|
| 181 |
-
super().__init__()
|
| 182 |
-
self.model = model
|
| 183 |
-
self.vocab_size = vocab_size
|
| 184 |
-
self.d_model = d_model
|
| 185 |
-
self.momentum_decay = momentum_decay
|
| 186 |
-
|
| 187 |
-
# Componentes
|
| 188 |
-
self.trust_agent = TrustNeuralAgent(vocab_size, d_model)
|
| 189 |
-
self.reward_model = RewardModel(d_model)
|
| 190 |
-
|
| 191 |
-
# Hiperparâmetros (log-space)
|
| 192 |
-
self.log_eta = nn.Parameter(torch.tensor(math.log(eta_init)))
|
| 193 |
-
self.log_lambda_rep = nn.Parameter(torch.tensor(math.log(lambda_rep_init)))
|
| 194 |
-
|
| 195 |
-
# Momentum (um por parâmetro)
|
| 196 |
-
self.momentum: Dict[str, torch.Tensor] = {}
|
| 197 |
-
|
| 198 |
-
# Histórico de rewards (para normalização)
|
| 199 |
-
self.register_buffer("reward_history", torch.zeros(100))
|
| 200 |
-
self.register_buffer("reward_history_pos", torch.tensor(0, dtype=torch.long))
|
| 201 |
-
|
| 202 |
-
@property
|
| 203 |
-
def eta(self) -> torch.Tensor:
|
| 204 |
-
return torch.exp(self.log_eta)
|
| 205 |
-
|
| 206 |
-
@property
|
| 207 |
-
def lambda_rep(self) -> torch.Tensor:
|
| 208 |
-
return torch.exp(self.log_lambda_rep)
|
| 209 |
-
|
| 210 |
-
def compute_cooperative_weights(
|
| 211 |
-
self,
|
| 212 |
-
grads: List[torch.Tensor],
|
| 213 |
-
) -> List[torch.Tensor]:
|
| 214 |
-
"""Computa pesos cooperativos wᵢ ∝ 1/‖gᵢ‖² (Teorema 2).
|
| 215 |
-
|
| 216 |
-
Args:
|
| 217 |
-
grads: lista de gradientes (um por componente)
|
| 218 |
-
|
| 219 |
-
Returns:
|
| 220 |
-
lista de pesos normalizados (soma = 1)
|
| 221 |
-
"""
|
| 222 |
-
norms_sq = torch.stack(
|
| 223 |
-
[(g ** 2).sum() if g is not None else torch.tensor(0.0) for g in grads]
|
| 224 |
-
)
|
| 225 |
-
# wᵢ ∝ 1/‖gᵢ‖² — gradientes menores recebem mais peso
|
| 226 |
-
inv_norms = 1.0 / (norms_sq + 1e-8)
|
| 227 |
-
weights = inv_norms / inv_norms.sum()
|
| 228 |
-
return [float(w.item()) for w in weights]
|
| 229 |
-
|
| 230 |
-
def update_reward_history(self, reward: float) -> float:
|
| 231 |
-
"""Atualiza histórico e retorna reward normalizado."""
|
| 232 |
-
pos = int(self.reward_history_pos.item())
|
| 233 |
-
self.reward_history[pos] = reward
|
| 234 |
-
self.reward_history_pos = (self.reward_history_pos + 1) % self.reward_history.shape[0]
|
| 235 |
-
# Normalização: reward normalizado = (r - mean) / (std + eps)
|
| 236 |
-
mean = float(self.reward_history.mean().item())
|
| 237 |
-
std = float(self.reward_history.std().item())
|
| 238 |
-
return (reward - mean) / (std + 1e-8)
|
| 239 |
-
|
| 240 |
-
def compute_mapping(
|
| 241 |
-
self,
|
| 242 |
-
token_ids: torch.Tensor,
|
| 243 |
-
hidden: torch.Tensor,
|
| 244 |
-
task_loss: torch.Tensor,
|
| 245 |
-
rep_penalty: Optional[torch.Tensor] = None,
|
| 246 |
-
) -> dict:
|
| 247 |
-
"""Computa o mapeamento de possibilidades Φ.
|
| 248 |
-
|
| 249 |
-
Φ = Θ + η · r · σ(trust · τ_T) · Σ_i wᵢ · gᵢ - λ_rep · P_rep · ∇P_rep
|
| 250 |
-
|
| 251 |
-
Args:
|
| 252 |
-
token_ids: (batch, T) — tokens
|
| 253 |
-
hidden: (batch, T, d_model) — hidden states
|
| 254 |
-
task_loss: escalar — loss da tarefa atual
|
| 255 |
-
rep_penalty: escalar opcional — penalidade de repetição
|
| 256 |
-
|
| 257 |
-
Returns:
|
| 258 |
-
dict com:
|
| 259 |
-
- reward: recompensa r
|
| 260 |
-
- trust_gate: σ(trust · τ_T)
|
| 261 |
-
- cooperative_weights: wᵢ
|
| 262 |
-
- lambda_rep: λ_rep atual
|
| 263 |
-
- eta: η atual
|
| 264 |
-
- normalized_reward: r normalizado
|
| 265 |
-
"""
|
| 266 |
-
# 1. Reward
|
| 267 |
-
with torch.no_grad():
|
| 268 |
-
reward = self.reward_model(hidden).mean() # escalar
|
| 269 |
-
normalized_reward = self.update_reward_history(float(reward.item()))
|
| 270 |
-
|
| 271 |
-
# 2. Trust gate
|
| 272 |
-
trust_gate = self.trust_agent(token_ids, hidden) # (B, T)
|
| 273 |
-
trust_gate_mean = trust_gate.mean() # escalar
|
| 274 |
-
|
| 275 |
-
# 3. Gradientes das componentes (apenas task_loss tem grad aqui)
|
| 276 |
-
# Em V6 simplificado: só 1 componente (task_loss)
|
| 277 |
-
# Em versão completa: teríamos task_loss + auxiliary_losses
|
| 278 |
-
grads = [torch.autograd.grad(
|
| 279 |
-
task_loss, list(self.model.parameters()),
|
| 280 |
-
retain_graph=True, allow_unused=True
|
| 281 |
-
)]
|
| 282 |
-
# Pesos cooperativos
|
| 283 |
-
w = self.compute_cooperative_weights(grads[0])
|
| 284 |
-
|
| 285 |
-
# 4. Penalidade de repetição
|
| 286 |
-
if rep_penalty is not None:
|
| 287 |
-
rep_penalty_val = rep_penalty
|
| 288 |
-
else:
|
| 289 |
-
rep_penalty_val = torch.tensor(0.0, device=hidden.device)
|
| 290 |
-
|
| 291 |
-
# 5. Mapeamento Φ (não aplicado aqui — apenas log dos componentes)
|
| 292 |
-
# A aplicação real acontece no trainer via:
|
| 293 |
-
# θ ← θ - η · (r - baseline) · trust_gate · Σ wᵢ · gᵢ - λ_rep · ∇P_rep
|
| 294 |
-
# Aqui retornamos os componentes para inspeção/logging
|
| 295 |
-
return {
|
| 296 |
-
"reward": float(reward.item()),
|
| 297 |
-
"normalized_reward": float(normalized_reward),
|
| 298 |
-
"trust_gate_mean": float(trust_gate_mean.item()),
|
| 299 |
-
"trust_tau_T": float(self.trust_agent.tau_T.item()),
|
| 300 |
-
"cooperative_weights": w,
|
| 301 |
-
"lambda_rep": float(self.lambda_rep.item()),
|
| 302 |
-
"eta": float(self.eta.item()),
|
| 303 |
-
"rep_penalty": float(rep_penalty_val.item() if isinstance(rep_penalty_val, torch.Tensor) else rep_penalty_val),
|
| 304 |
-
}
|
| 305 |
-
|
| 306 |
-
@torch.no_grad()
|
| 307 |
-
def apply_mapping(
|
| 308 |
-
self,
|
| 309 |
-
token_ids: torch.Tensor,
|
| 310 |
-
hidden: torch.Tensor,
|
| 311 |
-
task_grads: Dict[str, torch.Tensor],
|
| 312 |
-
rep_penalty_grads: Optional[Dict[str, torch.Tensor]] = None,
|
| 313 |
-
reward: Optional[float] = None,
|
| 314 |
-
) -> Dict[str, float]:
|
| 315 |
-
"""Aplica o mapeamento Φ aos gradientes (in-place).
|
| 316 |
-
|
| 317 |
-
Φ = Θ + η · r · σ(trust · τ_T) · Σ_i wᵢ · gᵢ - λ_rep · P_rep · ∇P_rep
|
| 318 |
-
|
| 319 |
-
Args:
|
| 320 |
-
token_ids: (batch, T)
|
| 321 |
-
hidden: (batch, T, d_model)
|
| 322 |
-
task_grads: gradientes da task_loss
|
| 323 |
-
rep_penalty_grads: gradientes da penalidade de repetição (opcional)
|
| 324 |
-
reward: recompensa externa (opcional)
|
| 325 |
-
|
| 326 |
-
Returns:
|
| 327 |
-
dict com estatísticas da atualização
|
| 328 |
-
"""
|
| 329 |
-
# 1. Reward (computa se não fornecido)
|
| 330 |
-
if reward is None:
|
| 331 |
-
reward = float(self.reward_model(hidden).mean().item())
|
| 332 |
-
normalized_reward = self.update_reward_history(reward)
|
| 333 |
-
|
| 334 |
-
# 2. Trust gate (média)
|
| 335 |
-
trust_gate = self.trust_agent(token_ids, hidden)
|
| 336 |
-
trust_gate_mean = float(trust_gate.mean().item())
|
| 337 |
-
|
| 338 |
-
# 3. Pesos cooperativos (1 componente aqui)
|
| 339 |
-
task_norm_sq = sum(
|
| 340 |
-
(g ** 2).sum().item() for g in task_grads.values() if g is not None
|
| 341 |
-
)
|
| 342 |
-
w_task = 1.0 / (task_norm_sq + 1e-8)
|
| 343 |
-
|
| 344 |
-
# 4. Aplica Φ aos gradientes
|
| 345 |
-
eta = float(self.eta.item())
|
| 346 |
-
lam_rep = float(self.lambda_rep.item())
|
| 347 |
-
scaling = eta * normalized_reward * trust_gate_mean * w_task
|
| 348 |
-
|
| 349 |
-
for name, p in self.model.named_parameters():
|
| 350 |
-
if not p.requires_grad:
|
| 351 |
-
continue
|
| 352 |
-
g_task = task_grads.get(name)
|
| 353 |
-
if g_task is None:
|
| 354 |
-
continue
|
| 355 |
-
# Momentum
|
| 356 |
-
if name not in self.momentum:
|
| 357 |
-
self.momentum[name] = torch.zeros_like(p)
|
| 358 |
-
self.momentum[name] = (
|
| 359 |
-
self.momentum_decay * self.momentum[name]
|
| 360 |
-
+ (1.0 - self.momentum_decay) * g_task
|
| 361 |
-
)
|
| 362 |
-
# Atualização: Δθ = scaling · momentum - lam_rep · rep_penalty_grad
|
| 363 |
-
delta = scaling * self.momentum[name]
|
| 364 |
-
if rep_penalty_grads and name in rep_penalty_grads:
|
| 365 |
-
delta = delta - lam_rep * rep_penalty_grads[name]
|
| 366 |
-
p.data -= delta
|
| 367 |
-
|
| 368 |
-
# 5. Atualiza trust via TD
|
| 369 |
-
self.trust_agent.update_td(token_ids, torch.full(
|
| 370 |
-
(token_ids.shape[0],), reward, device=token_ids.device
|
| 371 |
-
))
|
| 372 |
-
|
| 373 |
-
return {
|
| 374 |
-
"reward": reward,
|
| 375 |
-
"normalized_reward": normalized_reward,
|
| 376 |
-
"trust_gate_mean": trust_gate_mean,
|
| 377 |
-
"scaling": scaling,
|
| 378 |
-
"eta": eta,
|
| 379 |
-
"lambda_rep": lam_rep,
|
| 380 |
-
}
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
__all__ = [
|
| 384 |
-
"RewardModel",
|
| 385 |
-
"TrustNeuralAgent",
|
| 386 |
-
"LearnablePossibilityMapping",
|
| 387 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/bigru_t/training/trainer.py
DELETED
|
@@ -1,937 +0,0 @@
|
|
| 1 |
-
"""trainer.py — Loop de treino BiGRU_T_version (2 épocas, bug-detection).
|
| 2 |
-
|
| 3 |
-
Implementa:
|
| 4 |
-
- Treino com 2 épocas fixas ( especificação do usuário )
|
| 5 |
-
- Sem dados inventados (apenas datasets do repo + HF públicos)
|
| 6 |
-
- Monitoramento: loss, perplexidade, RAM, disco, módulos ativos
|
| 7 |
-
- Kill-switch: aborta se loss não diminuir ou RAM explodir
|
| 8 |
-
- Estados temporários salvos durante treino e APAGADOS ao final
|
| 9 |
-
- Aplicação dos 4 lemas:
|
| 10 |
-
Lema 1: ModuleSelector (softmax + entropia)
|
| 11 |
-
Lema 2: apply_gradient_surgery quando use_hyp=True
|
| 12 |
-
Lema 3: hyp_T com stop_grad_hyp=True, W8A8 via QuantizedLinear
|
| 13 |
-
Lema 4: MetaConfigurator ajusta T e τ a cada meta_interval batches
|
| 14 |
-
|
| 15 |
-
Reaproveita a filosofia do scripts/train_from_v13_9_2.py, mas:
|
| 16 |
-
- Sem fine-tune de modelo pré-treinado (treino from-scratch para
|
| 17 |
-
bug-detection — o objetivo é detectar erros, não produzir modelo SOTA)
|
| 18 |
-
- Adiciona kill-switch (não havia no original)
|
| 19 |
-
- Adiciona MetaConfigurator (não havia no original)
|
| 20 |
-
- Adiciona gradient surgery (não havia no original)
|
| 21 |
-
- Usa 2 épocas explícitas (vs. max_steps no original)
|
| 22 |
-
"""
|
| 23 |
-
from __future__ import annotations
|
| 24 |
-
|
| 25 |
-
import gc
|
| 26 |
-
import json
|
| 27 |
-
import logging
|
| 28 |
-
import math
|
| 29 |
-
import os
|
| 30 |
-
import shutil
|
| 31 |
-
import sys
|
| 32 |
-
import time
|
| 33 |
-
import traceback
|
| 34 |
-
from dataclasses import dataclass, field
|
| 35 |
-
from pathlib import Path
|
| 36 |
-
from typing import Optional
|
| 37 |
-
|
| 38 |
-
import torch
|
| 39 |
-
import torch.nn as nn
|
| 40 |
-
import torch.nn.functional as F
|
| 41 |
-
|
| 42 |
-
from ..model.unified_model import UnifiedModel, UnifiedModelConfig, create_unified_model
|
| 43 |
-
from .gradient_surgery import apply_gradient_surgery
|
| 44 |
-
from .meta_configurator import MetaConfigurator
|
| 45 |
-
from .kill_switch import KillSwitch
|
| 46 |
-
from .dpo import compute_dynamic_beta, dpo_step, compute_sequence_logps
|
| 47 |
-
from .hypothesis_monitor import HypothesisMonitor
|
| 48 |
-
from ..optim.hamiltonian_wasserstein import HamiltonianWassersteinOptimizer
|
| 49 |
-
from ..utils.memory_cleanup import aggressive_cleanup, production_cleanup, TimeBudget, StepTimer, get_rss_mb
|
| 50 |
-
|
| 51 |
-
logger = logging.getLogger(__name__)
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
@dataclass
|
| 55 |
-
class TrainerConfig:
|
| 56 |
-
"""Configuração do treino (V3: 5 épocas + DPO + CircularReasoning + Monitor)."""
|
| 57 |
-
# Epochs (V3: 5 épocas conforme especificação do usuário)
|
| 58 |
-
epochs: int = 5
|
| 59 |
-
|
| 60 |
-
# Datasets
|
| 61 |
-
datasets: str = "CEIA-POSITIVO/ultrachat_br_clustred_balanced_v1,Madras1/corpus-ptbr-v2,dominguesm/restore-punctuation-ptbr-dataset"
|
| 62 |
-
max_samples_per_dataset: int = 30 # bug-detection: amostra pequena
|
| 63 |
-
max_seq_len: int = 64
|
| 64 |
-
|
| 65 |
-
# Batch
|
| 66 |
-
per_device_batch_size: int = 1
|
| 67 |
-
grad_accum: int = 4
|
| 68 |
-
|
| 69 |
-
# Optimizer (HamiltonianWasserstein ATIVADO)
|
| 70 |
-
optimizer_type: str = "hamiltonian_wasserstein" # "adamw" | "hamiltonian_wasserstein"
|
| 71 |
-
lr: float = 1e-3 # from-scratch: LR mais alto que fine-tune
|
| 72 |
-
weight_decay: float = 0.01
|
| 73 |
-
max_grad_norm: float = 5.0
|
| 74 |
-
# HW optimizer params
|
| 75 |
-
hw_lr_amp: float = 0.3 # amplitude do LR cíclico (Van der Pol)
|
| 76 |
-
hw_lr_freq: float = 0.01 # frequência angular do LR cíclico
|
| 77 |
-
hw_sigma_w: float = 1.0 # escala W₂
|
| 78 |
-
hw_sigma_rep: float = 0.1 # escala repulsão topológica
|
| 79 |
-
hw_prune_every_n: int = 0 # pruning desativado por padrão (0 = off)
|
| 80 |
-
hw_ref_buffer_size: int = 2 # buffer de referência reduzido (era 10 → 2 para economizar RAM)
|
| 81 |
-
|
| 82 |
-
# DPO (V3: ATIVADO para datasets compatíveis)
|
| 83 |
-
# DPO é ativado automaticamente quando amostras têm label_text
|
| 84 |
-
# (instruction/response format). DPO loss é adicionada à loss principal.
|
| 85 |
-
use_dpo: bool = True # V3: ativo por padrão (auto-detecção)
|
| 86 |
-
dpo_beta_min: float = 0.05
|
| 87 |
-
dpo_beta_max: float = 0.5
|
| 88 |
-
dpo_warmup_steps: int = 100
|
| 89 |
-
dpo_loss_weight: float = 0.3 # peso da loss DPO na loss total
|
| 90 |
-
dpo_use_ipo: bool = False # IPO em vez de DPO clássico
|
| 91 |
-
dpo_label_smoothing: float = 0.0
|
| 92 |
-
dpo_every_n_steps: int = 2 # computar DPO a cada N steps (economia de compute)
|
| 93 |
-
|
| 94 |
-
# Circular Reasoning (Teorema 4)
|
| 95 |
-
use_circular_reasoning: bool = True # já está no UnifiedModelConfig também
|
| 96 |
-
|
| 97 |
-
# Meta-configurator (Lema 4)
|
| 98 |
-
meta_interval: int = 10 # a cada N micro-batches
|
| 99 |
-
meta_lr: float = 0.01
|
| 100 |
-
sharpness_lambda: float = 0.01
|
| 101 |
-
|
| 102 |
-
# Hypothesis (Lema 3)
|
| 103 |
-
use_hypothesis: bool = True
|
| 104 |
-
stop_grad_hyp: bool = True
|
| 105 |
-
|
| 106 |
-
# Kill-switch
|
| 107 |
-
ram_threshold_pct: float = 90.0
|
| 108 |
-
disk_min_free_gb: float = 1.0
|
| 109 |
-
loss_patience: int = 30
|
| 110 |
-
|
| 111 |
-
# Time budget (estilo Xavante — streaming com timed steps)
|
| 112 |
-
# V3: aumentado para 5 épocas (2700s total = 45 min, 540s/epoch = 9 min)
|
| 113 |
-
max_total_time_s: float = 2700.0 # 45 min máx total (5 épocas)
|
| 114 |
-
max_per_epoch_s: float = 540.0 # 9 min máx por época
|
| 115 |
-
|
| 116 |
-
# Memory cleanup agressivo (estilo Xavante)
|
| 117 |
-
cleanup_every_n_steps: int = 5 # aggressive_cleanup a cada N optimizer steps
|
| 118 |
-
|
| 119 |
-
# Logging
|
| 120 |
-
log_every: int = 5
|
| 121 |
-
save_temp_every: int = 10 # salvar checkpoint temporário a cada N steps
|
| 122 |
-
|
| 123 |
-
# Paths
|
| 124 |
-
output_dir: str = "/home/z/my-project/BiGRU_T_version/model_final"
|
| 125 |
-
temp_dir: str = "/home/z/my-project/BiGRU_T_version/_temp_checkpoints"
|
| 126 |
-
keep_temp: bool = False # False = apagar temp ao final (especificação)
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
class BiGRU_T_Trainer:
|
| 130 |
-
"""Trainer para BiGRU_T_version com 4 lemas + kill-switch.
|
| 131 |
-
|
| 132 |
-
Args:
|
| 133 |
-
model: UnifiedModel
|
| 134 |
-
tokenizer: BBPE tokenizer
|
| 135 |
-
config: TrainerConfig
|
| 136 |
-
train_samples: lista de ProcessedSample para treino
|
| 137 |
-
val_samples: lista de ProcessedSample para validação (meta-aprendizado)
|
| 138 |
-
"""
|
| 139 |
-
|
| 140 |
-
def __init__(
|
| 141 |
-
self,
|
| 142 |
-
model: UnifiedModel,
|
| 143 |
-
tokenizer,
|
| 144 |
-
config: TrainerConfig,
|
| 145 |
-
train_samples: list,
|
| 146 |
-
val_samples: list,
|
| 147 |
-
):
|
| 148 |
-
self.model = model
|
| 149 |
-
self.tokenizer = tokenizer
|
| 150 |
-
self.config = config
|
| 151 |
-
self.train_samples = train_samples
|
| 152 |
-
self.val_samples = val_samples
|
| 153 |
-
|
| 154 |
-
# Device
|
| 155 |
-
self.device = torch.device("cpu")
|
| 156 |
-
torch.set_num_threads(2)
|
| 157 |
-
os.environ.setdefault("OMP_NUM_THREADS", "2")
|
| 158 |
-
os.environ.setdefault("MKL_NUM_THREADS", "2")
|
| 159 |
-
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
|
| 160 |
-
|
| 161 |
-
# Otimizador principal — HamiltonianWasserstein ATIVADO por padrão
|
| 162 |
-
# (AdamW + W₂ adaptativo + repulsão topológica + LR cíclico + pruning opcional)
|
| 163 |
-
if config.optimizer_type == "hamiltonian_wasserstein":
|
| 164 |
-
self.optimizer = HamiltonianWassersteinOptimizer(
|
| 165 |
-
model.parameters(),
|
| 166 |
-
lr=config.lr,
|
| 167 |
-
betas=(0.9, 0.95),
|
| 168 |
-
eps=1e-8,
|
| 169 |
-
weight_decay=config.weight_decay,
|
| 170 |
-
lr_amp=config.hw_lr_amp,
|
| 171 |
-
lr_freq=config.hw_lr_freq,
|
| 172 |
-
sigma_w=config.hw_sigma_w,
|
| 173 |
-
sigma_rep=config.hw_sigma_rep,
|
| 174 |
-
prune_every_n=config.hw_prune_every_n,
|
| 175 |
-
ref_buffer_size=config.hw_ref_buffer_size,
|
| 176 |
-
)
|
| 177 |
-
logger.info("Optimizer: HamiltonianWassersteinOptimizer (ATIVADO)")
|
| 178 |
-
else:
|
| 179 |
-
self.optimizer = torch.optim.AdamW(
|
| 180 |
-
model.parameters(),
|
| 181 |
-
lr=config.lr,
|
| 182 |
-
betas=(0.9, 0.95),
|
| 183 |
-
weight_decay=config.weight_decay,
|
| 184 |
-
eps=1e-8,
|
| 185 |
-
)
|
| 186 |
-
logger.info("Optimizer: AdamW (fallback)")
|
| 187 |
-
|
| 188 |
-
# Time budget (estilo Xavante)
|
| 189 |
-
self.time_budget = TimeBudget(
|
| 190 |
-
max_total_s=config.max_total_time_s,
|
| 191 |
-
max_per_epoch_s=config.max_per_epoch_s,
|
| 192 |
-
)
|
| 193 |
-
self.step_timer = StepTimer(expected_s=2.0) # espera ~2s por step
|
| 194 |
-
|
| 195 |
-
# Meta-configurator (Lema 4)
|
| 196 |
-
self.meta_cfg = MetaConfigurator(
|
| 197 |
-
model,
|
| 198 |
-
meta_lr=config.meta_lr,
|
| 199 |
-
sharpness_lambda=config.sharpness_lambda,
|
| 200 |
-
)
|
| 201 |
-
|
| 202 |
-
# Kill-switch
|
| 203 |
-
self.kill_switch = KillSwitch(
|
| 204 |
-
ram_threshold_pct=config.ram_threshold_pct,
|
| 205 |
-
disk_min_free_gb=config.disk_min_free_gb,
|
| 206 |
-
loss_patience=config.loss_patience,
|
| 207 |
-
log_dir=config.temp_dir,
|
| 208 |
-
)
|
| 209 |
-
|
| 210 |
-
# Hypothesis Monitor (V3) — detecta bugs na ativação dos 4 lemas
|
| 211 |
-
self.hyp_monitor = HypothesisMonitor(
|
| 212 |
-
max_modules=model.config.max_modules,
|
| 213 |
-
delta_epsilon=1e-4,
|
| 214 |
-
meta_stagnation_epsilon=1e-4,
|
| 215 |
-
history_size=200,
|
| 216 |
-
)
|
| 217 |
-
|
| 218 |
-
# DPO: detecta se dataset é compatível (tem label_text em pelo menos 30% das amostras)
|
| 219 |
-
self._dpo_compatible = False
|
| 220 |
-
if config.use_dpo and train_samples:
|
| 221 |
-
n_with_label = sum(1 for s in train_samples if getattr(s, "label_text", ""))
|
| 222 |
-
ratio = n_with_label / len(train_samples)
|
| 223 |
-
self._dpo_compatible = ratio >= 0.3
|
| 224 |
-
logger.info(
|
| 225 |
-
f"DPO auto-detecção: {n_with_label}/{len(train_samples)} ({ratio:.0%}) "
|
| 226 |
-
f"amostras com label_text → "
|
| 227 |
-
f"{'COMPATÍVEL (DPO ativado)' if self._dpo_compatible else 'não compatível (SFT only)'}"
|
| 228 |
-
)
|
| 229 |
-
|
| 230 |
-
# Diretórios
|
| 231 |
-
Path(config.output_dir).mkdir(parents=True, exist_ok=True)
|
| 232 |
-
Path(config.temp_dir).mkdir(parents=True, exist_ok=True)
|
| 233 |
-
|
| 234 |
-
# Estado de treino
|
| 235 |
-
self.global_step = 0
|
| 236 |
-
self.micro_batch_idx = 0
|
| 237 |
-
self.best_loss = float("inf")
|
| 238 |
-
self.losses_history: list[float] = []
|
| 239 |
-
self.killed_reason: Optional[str] = None
|
| 240 |
-
# DPO reference logps (computados uma vez no início, com modelo congelado)
|
| 241 |
-
self._ref_logps_computed = False
|
| 242 |
-
|
| 243 |
-
def _collate(self, samples: list) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 244 |
-
"""Tokeniza samples → (input_ids, attention_mask, labels).
|
| 245 |
-
|
| 246 |
-
Reaproveita collate_samples_to_tensors do streaming_datasets_v13_9.
|
| 247 |
-
"""
|
| 248 |
-
from ..data.streaming_datasets import collate_samples_to_tensors
|
| 249 |
-
pad_id = self.model.config.pad_token_id
|
| 250 |
-
return collate_samples_to_tensors(
|
| 251 |
-
samples, self.tokenizer,
|
| 252 |
-
max_seq_len=self.config.max_seq_len,
|
| 253 |
-
pad_token_id=pad_id,
|
| 254 |
-
)
|
| 255 |
-
|
| 256 |
-
def _forward_loss(
|
| 257 |
-
self,
|
| 258 |
-
input_ids: torch.Tensor,
|
| 259 |
-
attention_mask: torch.Tensor,
|
| 260 |
-
labels: torch.Tensor,
|
| 261 |
-
use_hyp: bool,
|
| 262 |
-
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 263 |
-
"""Forward + loss. Retorna (loss_main, loss_hyp_or_zero, y_final).
|
| 264 |
-
|
| 265 |
-
Para LM (next-token prediction):
|
| 266 |
-
- input_ids: (batch, T)
|
| 267 |
-
- labels: (batch, T) — target = input_ids shifted
|
| 268 |
-
- y_hat, delta: (batch, T, vocab_size) — um logit por posição
|
| 269 |
-
- loss = CrossEntropy(y_hat.view(-1, V), labels.view(-1))
|
| 270 |
-
|
| 271 |
-
Mas o UnifiedModel atual produz (batch, vocab_size) — um logit por
|
| 272 |
-
amostra. Para compatibilidade com LM, fazemos:
|
| 273 |
-
- input = input_ids[:, :-1] (T-1 tokens de entrada)
|
| 274 |
-
- target = input_ids[:, 1:] (T-1 targets)
|
| 275 |
-
- Mas o modelo só produz 1 logit por amostra, então targetamos
|
| 276 |
-
apenas o ÚLTIMO token: target = input_ids[:, -1]
|
| 277 |
-
- loss = CrossEntropy(y_hat, target)
|
| 278 |
-
|
| 279 |
-
Esta é uma simplificação para bug-detection — não é um LM completo.
|
| 280 |
-
"""
|
| 281 |
-
# Target = último token (simplificação para bug-detection)
|
| 282 |
-
target = input_ids[:, -1].clone() # (batch,)
|
| 283 |
-
# Input = todos os tokens (o modelo faz embedding internamente)
|
| 284 |
-
# Passamos input_ids (2D) — o modelo detecta e faz embedding
|
| 285 |
-
|
| 286 |
-
# Forward do modelo
|
| 287 |
-
temperature = self.meta_cfg.temperature
|
| 288 |
-
y_hat, delta = self.model(
|
| 289 |
-
input_ids,
|
| 290 |
-
temperature=temperature,
|
| 291 |
-
use_hypothesis=use_hyp,
|
| 292 |
-
stop_grad_hyp=self.config.stop_grad_hyp,
|
| 293 |
-
return_aux=False,
|
| 294 |
-
)
|
| 295 |
-
# y_hat: (batch, vocab_size), delta: (batch, vocab_size)
|
| 296 |
-
|
| 297 |
-
# Loss principal: CrossEntropy sobre o último token
|
| 298 |
-
loss_main = F.cross_entropy(y_hat, target, ignore_index=self.model.config.pad_token_id)
|
| 299 |
-
|
| 300 |
-
# Loss de hipótese: sobre y_final = y_hat + delta
|
| 301 |
-
if use_hyp:
|
| 302 |
-
y_final = y_hat + delta
|
| 303 |
-
loss_hyp = F.cross_entropy(y_final, target, ignore_index=self.model.config.pad_token_id)
|
| 304 |
-
else:
|
| 305 |
-
loss_hyp = torch.zeros((), device=y_hat.device)
|
| 306 |
-
|
| 307 |
-
return loss_main, loss_hyp, y_hat
|
| 308 |
-
|
| 309 |
-
def train(self) -> dict:
|
| 310 |
-
"""Executa treino de bug-detection (2 épocas).
|
| 311 |
-
|
| 312 |
-
Returns:
|
| 313 |
-
dict com métricas finais.
|
| 314 |
-
"""
|
| 315 |
-
cfg = self.config
|
| 316 |
-
n_train = len(self.train_samples)
|
| 317 |
-
n_val = len(self.val_samples)
|
| 318 |
-
logger.info("=" * 70)
|
| 319 |
-
logger.info("BiGRU_T_version — Bug-detection training")
|
| 320 |
-
logger.info("=" * 70)
|
| 321 |
-
logger.info(f" epochs: {cfg.epochs}")
|
| 322 |
-
logger.info(f" train samples: {n_train}")
|
| 323 |
-
logger.info(f" val samples: {n_val}")
|
| 324 |
-
logger.info(f" batch_size: {cfg.per_device_batch_size}")
|
| 325 |
-
logger.info(f" grad_accum: {cfg.grad_accum}")
|
| 326 |
-
logger.info(f" lr: {cfg.lr}")
|
| 327 |
-
logger.info(f" optimizer: {cfg.optimizer_type}")
|
| 328 |
-
logger.info(f" max_seq_len: {cfg.max_seq_len}")
|
| 329 |
-
logger.info(f" use_hypothesis: {cfg.use_hypothesis}")
|
| 330 |
-
logger.info(f" use_dpo: {cfg.use_dpo}")
|
| 331 |
-
logger.info(f" meta_interval: {cfg.meta_interval}")
|
| 332 |
-
logger.info(f" time_budget: {cfg.max_total_time_s}s total, {cfg.max_per_epoch_s}s/epoch")
|
| 333 |
-
logger.info(f" cleanup_every: {cfg.cleanup_every_n_steps} steps")
|
| 334 |
-
logger.info("=" * 70)
|
| 335 |
-
|
| 336 |
-
# Conta parâmetros
|
| 337 |
-
params = self.model.count_parameters()
|
| 338 |
-
logger.info(f" total params: {params['total']:,} ({params['total_M']:.2f}M)")
|
| 339 |
-
logger.info(f" trainable: {params['trainable']:,} ({params['trainable_M']:.2f}M)")
|
| 340 |
-
logger.info("=" * 70)
|
| 341 |
-
|
| 342 |
-
t_start = time.time()
|
| 343 |
-
self.model.train()
|
| 344 |
-
self.time_budget.reset_total()
|
| 345 |
-
|
| 346 |
-
for epoch in range(cfg.epochs):
|
| 347 |
-
logger.info(f"\n--- Epoch {epoch+1}/{cfg.epochs} ---")
|
| 348 |
-
epoch_start = time.time()
|
| 349 |
-
epoch_losses = []
|
| 350 |
-
self.time_budget.reset_epoch()
|
| 351 |
-
|
| 352 |
-
# Itera sobre samples (loop circular se n_train < batches necessários)
|
| 353 |
-
sample_idx = 0
|
| 354 |
-
micro_in_epoch = 0
|
| 355 |
-
|
| 356 |
-
while micro_in_epoch < n_train:
|
| 357 |
-
# Time budget check (estilo Xavante)
|
| 358 |
-
if self.time_budget.is_over() or self.time_budget.is_epoch_over():
|
| 359 |
-
logger.warning(
|
| 360 |
-
"Time budget estourado: total=%s epoch=%s",
|
| 361 |
-
self.time_budget.is_over(), self.time_budget.is_epoch_over(),
|
| 362 |
-
)
|
| 363 |
-
break
|
| 364 |
-
|
| 365 |
-
# Pega batch
|
| 366 |
-
batch_samples = []
|
| 367 |
-
for _ in range(cfg.per_device_batch_size):
|
| 368 |
-
s = self.train_samples[sample_idx % n_train]
|
| 369 |
-
batch_samples.append(s)
|
| 370 |
-
sample_idx += 1
|
| 371 |
-
micro_in_epoch += 1
|
| 372 |
-
if micro_in_epoch >= n_train:
|
| 373 |
-
break
|
| 374 |
-
|
| 375 |
-
# Tokeniza
|
| 376 |
-
try:
|
| 377 |
-
input_ids, attn_mask, labels = self._collate(batch_samples)
|
| 378 |
-
except Exception as e:
|
| 379 |
-
logger.warning(f"Tokenize erro micro {self.micro_batch_idx}: {e}")
|
| 380 |
-
continue
|
| 381 |
-
|
| 382 |
-
# Forward + loss — OTIMIZADO: single forward pass
|
| 383 |
-
# (antes: 3 forwards por micro-batch; agora: 1 forward com return_aux)
|
| 384 |
-
try:
|
| 385 |
-
# Target = último token
|
| 386 |
-
target = input_ids[:, -1].clone()
|
| 387 |
-
|
| 388 |
-
# SINGLE forward: retorna y_hat, delta, e aux (entropy_reg, alpha, circular_info)
|
| 389 |
-
y_hat, delta, aux = self.model(
|
| 390 |
-
input_ids,
|
| 391 |
-
temperature=self.meta_cfg.temperature,
|
| 392 |
-
use_hypothesis=True, # sempre computa delta (barato se stop_grad)
|
| 393 |
-
stop_grad_hyp=self.config.stop_grad_hyp,
|
| 394 |
-
return_aux=True,
|
| 395 |
-
use_circular=cfg.use_circular_reasoning,
|
| 396 |
-
)
|
| 397 |
-
|
| 398 |
-
# Loss principal: CrossEntropy sobre y_hat + entropy_reg (Lema 1)
|
| 399 |
-
loss_main = F.cross_entropy(
|
| 400 |
-
y_hat, target, ignore_index=self.model.config.pad_token_id
|
| 401 |
-
)
|
| 402 |
-
loss_main_total = loss_main + aux["entropy_reg"]
|
| 403 |
-
|
| 404 |
-
# Decide se usa hipótese baseado no tau (Lema 3)
|
| 405 |
-
# use_hyp = True se loss_main > tau (modelo está "lutando")
|
| 406 |
-
use_hyp = (
|
| 407 |
-
cfg.use_hypothesis
|
| 408 |
-
and self.global_step > 0
|
| 409 |
-
and bool(loss_main.item() > self.meta_cfg.tau)
|
| 410 |
-
)
|
| 411 |
-
|
| 412 |
-
# Loss de hipótese: sobre y_final = y_hat + delta
|
| 413 |
-
if use_hyp:
|
| 414 |
-
y_final = y_hat + delta
|
| 415 |
-
loss_hyp = F.cross_entropy(
|
| 416 |
-
y_final, target,
|
| 417 |
-
ignore_index=self.model.config.pad_token_id,
|
| 418 |
-
)
|
| 419 |
-
else:
|
| 420 |
-
loss_hyp = torch.zeros((), device=y_hat.device)
|
| 421 |
-
|
| 422 |
-
# V3: DPO loss — se dataset é compatível e estamos num step de DPO
|
| 423 |
-
loss_dpo_val = 0.0
|
| 424 |
-
if (
|
| 425 |
-
self._dpo_compatible
|
| 426 |
-
and self.global_step % cfg.dpo_every_n_steps == 0
|
| 427 |
-
and len(batch_samples) > 0
|
| 428 |
-
):
|
| 429 |
-
try:
|
| 430 |
-
loss_dpo = self._compute_dpo_loss(batch_samples, y_hat, target)
|
| 431 |
-
if loss_dpo is not None:
|
| 432 |
-
loss_main_total = loss_main_total + cfg.dpo_loss_weight * loss_dpo
|
| 433 |
-
loss_dpo_val = float(loss_dpo.item())
|
| 434 |
-
except Exception as dpo_err:
|
| 435 |
-
logger.debug(f"DPO step falhou (não crítico): {dpo_err}")
|
| 436 |
-
|
| 437 |
-
except RuntimeError as e:
|
| 438 |
-
err = str(e).lower()
|
| 439 |
-
if "out of memory" in err:
|
| 440 |
-
logger.warning(f"OOM micro {self.micro_batch_idx}, limpando")
|
| 441 |
-
gc.collect()
|
| 442 |
-
self.optimizer.zero_grad()
|
| 443 |
-
continue
|
| 444 |
-
else:
|
| 445 |
-
logger.error(f"Runtime erro micro {self.micro_batch_idx}: {e}")
|
| 446 |
-
traceback.print_exc()
|
| 447 |
-
break
|
| 448 |
-
|
| 449 |
-
# DPO beta dinâmico (se DPO ativo) — para logging no optimizer
|
| 450 |
-
if self._dpo_compatible:
|
| 451 |
-
beta_t = compute_dynamic_beta(
|
| 452 |
-
self.global_step,
|
| 453 |
-
warmup_steps=cfg.dpo_warmup_steps,
|
| 454 |
-
beta_min=cfg.dpo_beta_min,
|
| 455 |
-
beta_max=cfg.dpo_beta_max,
|
| 456 |
-
)
|
| 457 |
-
if hasattr(self.optimizer, "set_beta_dpo"):
|
| 458 |
-
self.optimizer.set_beta_dpo(beta_t)
|
| 459 |
-
|
| 460 |
-
# Backward + step
|
| 461 |
-
self.step_timer.start()
|
| 462 |
-
l2_applied = False
|
| 463 |
-
grad_hyp_norm = 0.0
|
| 464 |
-
orth_proj_norm = 0.0
|
| 465 |
-
if use_hyp and loss_hyp.requires_grad:
|
| 466 |
-
# Lema 2: gradient surgery
|
| 467 |
-
apply_gradient_surgery(self.model, loss_main_total, loss_hyp)
|
| 468 |
-
l2_applied = True
|
| 469 |
-
# Mede gradientes para o monitor (após surgery)
|
| 470 |
-
try:
|
| 471 |
-
grad_norms = [
|
| 472 |
-
p.grad.norm().item()
|
| 473 |
-
for p in self.model.parameters()
|
| 474 |
-
if p.grad is not None
|
| 475 |
-
]
|
| 476 |
-
if grad_norms:
|
| 477 |
-
grad_hyp_norm = sum(n ** 2 for n in grad_norms) ** 0.5
|
| 478 |
-
except Exception:
|
| 479 |
-
pass
|
| 480 |
-
# Clip
|
| 481 |
-
torch.nn.utils.clip_grad_norm_(self.model.parameters(), cfg.max_grad_norm)
|
| 482 |
-
# HW optimizer: step aceita loss para logging
|
| 483 |
-
if isinstance(self.optimizer, HamiltonianWassersteinOptimizer):
|
| 484 |
-
self.optimizer.step(loss=float(loss_main_total.item()))
|
| 485 |
-
else:
|
| 486 |
-
self.optimizer.step()
|
| 487 |
-
self.optimizer.zero_grad()
|
| 488 |
-
else:
|
| 489 |
-
# Apenas loss_main
|
| 490 |
-
self.optimizer.zero_grad()
|
| 491 |
-
loss_main_total.backward()
|
| 492 |
-
torch.nn.utils.clip_grad_norm_(self.model.parameters(), cfg.max_grad_norm)
|
| 493 |
-
if isinstance(self.optimizer, HamiltonianWassersteinOptimizer):
|
| 494 |
-
self.optimizer.step(loss=float(loss_main_total.item()))
|
| 495 |
-
else:
|
| 496 |
-
self.optimizer.step()
|
| 497 |
-
self.step_timer.stop(step_label=str(self.global_step))
|
| 498 |
-
|
| 499 |
-
# V3: Hypothesis Monitor — registra estado do step
|
| 500 |
-
self.hyp_monitor.record(
|
| 501 |
-
step=self.global_step,
|
| 502 |
-
alpha=aux.get("alpha"),
|
| 503 |
-
entropy_reg=float(aux.get("entropy_reg", 0).item()
|
| 504 |
-
if hasattr(aux.get("entropy_reg", 0), "item") else 0),
|
| 505 |
-
loss_main=float(loss_main.item()),
|
| 506 |
-
tau=float(self.meta_cfg.tau),
|
| 507 |
-
use_hyp=use_hyp,
|
| 508 |
-
delta=delta if use_hyp else None,
|
| 509 |
-
temperature=self.meta_cfg.temperature,
|
| 510 |
-
circular_info=aux.get("circular_info"),
|
| 511 |
-
l2_applied=l2_applied,
|
| 512 |
-
grad_hyp_norm=grad_hyp_norm,
|
| 513 |
-
orth_proj_norm=orth_proj_norm,
|
| 514 |
-
)
|
| 515 |
-
|
| 516 |
-
self.micro_batch_idx += 1
|
| 517 |
-
|
| 518 |
-
# Log
|
| 519 |
-
current_loss = float(loss_main.item())
|
| 520 |
-
self.losses_history.append(current_loss)
|
| 521 |
-
epoch_losses.append(current_loss)
|
| 522 |
-
if current_loss < self.best_loss:
|
| 523 |
-
self.best_loss = current_loss
|
| 524 |
-
|
| 525 |
-
# Verifica se é um optimizer step (a cada grad_accum micro-batches)
|
| 526 |
-
if self.micro_batch_idx % cfg.grad_accum == 0:
|
| 527 |
-
self.global_step += 1
|
| 528 |
-
|
| 529 |
-
# Kill-switch
|
| 530 |
-
active = self.model.get_active_modules()
|
| 531 |
-
state = self.kill_switch.check(self.global_step, current_loss, active)
|
| 532 |
-
if state.reason:
|
| 533 |
-
logger.error(f"KILL SWITCH triggered: {state.reason}")
|
| 534 |
-
logger.error(
|
| 535 |
-
f" RAM {state.ram_pct:.1f}% | disk free {state.disk_free_gb:.2f}GB | "
|
| 536 |
-
f"ppl {state.ppl:.2f}"
|
| 537 |
-
)
|
| 538 |
-
self.killed_reason = state.reason
|
| 539 |
-
break
|
| 540 |
-
|
| 541 |
-
# Log
|
| 542 |
-
if self.global_step % cfg.log_every == 0 or self.global_step == 1:
|
| 543 |
-
elapsed = time.time() - t_start
|
| 544 |
-
avg_loss = sum(self.losses_history[-cfg.grad_accum:]) / max(
|
| 545 |
-
1, min(cfg.grad_accum, len(self.losses_history))
|
| 546 |
-
)
|
| 547 |
-
ppl = math.exp(min(20, avg_loss)) if avg_loss < 20 else float("inf")
|
| 548 |
-
# V3: info do Circular Reasoning (último step registrado)
|
| 549 |
-
crw_str = ""
|
| 550 |
-
last_state = self.hyp_monitor._history[-1] if self.hyp_monitor._history else None
|
| 551 |
-
if last_state and last_state.crw_w2_final > 0:
|
| 552 |
-
crw_str = (
|
| 553 |
-
f" | CRW: w2={last_state.crw_w2_final:.4f} "
|
| 554 |
-
f"q̄={last_state.crw_q_avg:.3f} "
|
| 555 |
-
f"{'✓' if last_state.crw_contraction_valid else '⚠'}"
|
| 556 |
-
)
|
| 557 |
-
dpo_str = f" dpo={loss_dpo_val:.3f}" if loss_dpo_val > 0 else ""
|
| 558 |
-
logger.info(
|
| 559 |
-
f" step {self.global_step} | epoch {epoch+1} | "
|
| 560 |
-
f"loss={avg_loss:.4f} ppl={ppl:.2f}{dpo_str} | "
|
| 561 |
-
f"RAM {state.ram_pct:.1f}% disk {state.disk_free_gb:.1f}GB | "
|
| 562 |
-
f"modules={active} | T={self.meta_cfg.temperature:.3f} "
|
| 563 |
-
f"τ={self.meta_cfg.tau:.3f} | "
|
| 564 |
-
f"α_ent={last_state.alpha_entropy:.3f} "
|
| 565 |
-
f"α_max={last_state.alpha_max:.3f}"
|
| 566 |
-
f"{crw_str} | "
|
| 567 |
-
f"elapsed={elapsed:.0f}s"
|
| 568 |
-
)
|
| 569 |
-
|
| 570 |
-
# Checkpoint temporário
|
| 571 |
-
if self.global_step % cfg.save_temp_every == 0:
|
| 572 |
-
self._save_temp_checkpoint(epoch)
|
| 573 |
-
|
| 574 |
-
# Memory cleanup agressivo (estilo Xavante)
|
| 575 |
-
if self.global_step % cfg.cleanup_every_n_steps == 0:
|
| 576 |
-
cleanup_info = aggressive_cleanup(verbose=False)
|
| 577 |
-
if self.global_step % (cfg.cleanup_every_n_steps * 5) == 0:
|
| 578 |
-
logger.info(
|
| 579 |
-
" cleanup step %d: RSS %.1f MB (freed %.1f MB)",
|
| 580 |
-
self.global_step,
|
| 581 |
-
cleanup_info["rss_after_mb"],
|
| 582 |
-
cleanup_info["freed_mb"],
|
| 583 |
-
)
|
| 584 |
-
|
| 585 |
-
# Meta-configurator (Lema 4)
|
| 586 |
-
if self.global_step % cfg.meta_interval == 0 and self.val_samples:
|
| 587 |
-
self._run_meta_update()
|
| 588 |
-
|
| 589 |
-
if self.killed_reason:
|
| 590 |
-
break
|
| 591 |
-
|
| 592 |
-
# Fim da época — cleanup agressivo
|
| 593 |
-
aggressive_cleanup(verbose=True)
|
| 594 |
-
|
| 595 |
-
# Fim da época
|
| 596 |
-
epoch_avg = sum(epoch_losses) / max(1, len(epoch_losses))
|
| 597 |
-
epoch_ppl = math.exp(min(20, epoch_avg)) if epoch_avg < 20 else float("inf")
|
| 598 |
-
logger.info(
|
| 599 |
-
f" Epoch {epoch+1} done: avg_loss={epoch_avg:.4f} ppl={epoch_ppl:.2f} "
|
| 600 |
-
f"({time.time()-epoch_start:.1f}s)"
|
| 601 |
-
)
|
| 602 |
-
|
| 603 |
-
# SALVAR MODELO APÓS CADA ÉPOCA (garante checkpoint mesmo se OOM no fim)
|
| 604 |
-
try:
|
| 605 |
-
partial_result = {
|
| 606 |
-
"epochs_completed": epoch + 1,
|
| 607 |
-
"killed": False,
|
| 608 |
-
"kill_reason": None, # V3 FIX: era missing → KeyError no _save_final_model
|
| 609 |
-
"global_reason": None,
|
| 610 |
-
"global_step": self.global_step,
|
| 611 |
-
"micro_batches": self.micro_batch_idx,
|
| 612 |
-
"best_loss": self.best_loss,
|
| 613 |
-
"final_loss": epoch_losses[-1] if epoch_losses else float("nan"),
|
| 614 |
-
"final_avg_loss": epoch_avg,
|
| 615 |
-
"final_perplexity": epoch_ppl,
|
| 616 |
-
"elapsed_s": time.time() - t_start,
|
| 617 |
-
"params_total": params["total"],
|
| 618 |
-
"params_M": params["total_M"],
|
| 619 |
-
}
|
| 620 |
-
self._save_final_model(partial_result)
|
| 621 |
-
logger.info(f" Modelo salvo após epoch {epoch+1}")
|
| 622 |
-
except Exception as e:
|
| 623 |
-
logger.warning(f" Falha ao salvar após epoch {epoch+1}: {e}")
|
| 624 |
-
|
| 625 |
-
# Fim do treino
|
| 626 |
-
elapsed_total = time.time() - t_start
|
| 627 |
-
final_loss = self.losses_history[-1] if self.losses_history else float("nan")
|
| 628 |
-
final_avg_loss = (
|
| 629 |
-
sum(self.losses_history[-cfg.grad_accum:]) / max(1, min(cfg.grad_accum, len(self.losses_history)))
|
| 630 |
-
if self.losses_history else float("nan")
|
| 631 |
-
)
|
| 632 |
-
final_ppl = math.exp(min(20, final_avg_loss)) if final_avg_loss < 20 else float("inf")
|
| 633 |
-
ks_summary = self.kill_switch.summary()
|
| 634 |
-
|
| 635 |
-
result = {
|
| 636 |
-
"epochs_completed": epoch + 1 if not self.killed_reason else epoch,
|
| 637 |
-
"killed": self.killed_reason is not None,
|
| 638 |
-
"kill_reason": self.killed_reason,
|
| 639 |
-
"global_reason": self.killed_reason,
|
| 640 |
-
"global_step": self.global_step,
|
| 641 |
-
"micro_batches": self.micro_batch_idx,
|
| 642 |
-
"best_loss": self.best_loss,
|
| 643 |
-
"final_loss": final_loss,
|
| 644 |
-
"final_avg_loss": final_avg_loss,
|
| 645 |
-
"final_perplexity": final_ppl,
|
| 646 |
-
"elapsed_s": elapsed_total,
|
| 647 |
-
"params_total": params["total"],
|
| 648 |
-
"params_M": params["total_M"],
|
| 649 |
-
"monitor_summary": ks_summary,
|
| 650 |
-
"optimizer": cfg.optimizer_type,
|
| 651 |
-
"time_budget": self.time_budget.summary(),
|
| 652 |
-
"step_timer": self.step_timer.summary(),
|
| 653 |
-
}
|
| 654 |
-
|
| 655 |
-
# V3: Log final do Hypothesis Monitor (detecção de bugs)
|
| 656 |
-
self.hyp_monitor.log_summary()
|
| 657 |
-
hyp_report = self.hyp_monitor.summary()
|
| 658 |
-
result["hypothesis_monitor"] = {
|
| 659 |
-
"total_steps": hyp_report.total_steps,
|
| 660 |
-
"punishment_steps": hyp_report.punishment_steps,
|
| 661 |
-
"hyp_activated_steps": hyp_report.hyp_activated_steps,
|
| 662 |
-
"hyp_activated_during_punishment": hyp_report.hyp_activated_during_punishment,
|
| 663 |
-
"delta_zero_when_activated": hyp_report.delta_zero_when_activated,
|
| 664 |
-
"l1_entropy_saturated_steps": hyp_report.l1_entropy_saturated_steps,
|
| 665 |
-
"l2_zero_grad_hyp_steps": hyp_report.l2_zero_grad_hyp_steps,
|
| 666 |
-
"crw_converged_steps": hyp_report.crw_converged_steps,
|
| 667 |
-
"crw_contraction_invalid_steps": hyp_report.crw_contraction_invalid_steps,
|
| 668 |
-
"last_T": hyp_report.last_T,
|
| 669 |
-
"last_tau": hyp_report.last_tau,
|
| 670 |
-
"has_bug": hyp_report.has_bug,
|
| 671 |
-
"bugs": hyp_report.bug_summary,
|
| 672 |
-
}
|
| 673 |
-
result["dpo_activated"] = self._dpo_compatible
|
| 674 |
-
|
| 675 |
-
logger.info("=" * 70)
|
| 676 |
-
logger.info("TREINO CONCLUÍDO")
|
| 677 |
-
logger.info(f" epochs completed: {result['epochs_completed']}")
|
| 678 |
-
logger.info(f" killed: {result['killed']}")
|
| 679 |
-
if result["killed"]:
|
| 680 |
-
logger.info(f" kill reason: {result['kill_reason']}")
|
| 681 |
-
logger.info(f" global step: {result['global_step']}")
|
| 682 |
-
logger.info(f" best loss: {result['best_loss']:.4f}")
|
| 683 |
-
logger.info(f" final avg loss: {result['final_avg_loss']:.4f}")
|
| 684 |
-
logger.info(f" final perplexity: {result['final_perplexity']:.2f}")
|
| 685 |
-
logger.info(f" elapsed: {result['elapsed_s']:.1f}s")
|
| 686 |
-
logger.info(f" max RAM: {ks_summary.get('max_ram_pct', 0):.1f}%")
|
| 687 |
-
logger.info(f" min disk free: {ks_summary.get('min_disk_free_gb', 0):.2f}GB")
|
| 688 |
-
logger.info(f" DPO ativado: {result['dpo_activated']}")
|
| 689 |
-
logger.info(f" CircularReason: {cfg.use_circular_reasoning}")
|
| 690 |
-
logger.info(f" Hipótese monitor: {'⚠️ BUGS' if hyp_report.has_bug else '✓ OK'}")
|
| 691 |
-
logger.info("=" * 70)
|
| 692 |
-
|
| 693 |
-
# Salva modelo final
|
| 694 |
-
if not result["killed"]:
|
| 695 |
-
self._save_final_model(result)
|
| 696 |
-
|
| 697 |
-
# Cleanup checkpoints temporários
|
| 698 |
-
if not cfg.keep_temp:
|
| 699 |
-
self._cleanup_temp()
|
| 700 |
-
|
| 701 |
-
return result
|
| 702 |
-
|
| 703 |
-
def _compute_dpo_loss(
|
| 704 |
-
self,
|
| 705 |
-
batch_samples: list,
|
| 706 |
-
y_hat: torch.Tensor,
|
| 707 |
-
target: torch.Tensor,
|
| 708 |
-
) -> Optional[torch.Tensor]:
|
| 709 |
-
"""V3: Computa DPO loss para o batch.
|
| 710 |
-
|
| 711 |
-
Estratégia: para datasets compatíveis (com label_text = resposta correta),
|
| 712 |
-
sintetiza pares de preferência:
|
| 713 |
-
- chosen = resposta correta (label_text do próprio sample)
|
| 714 |
-
- rejected = resposta de OUTRO sample (mistura) — proxy para "resposta errada"
|
| 715 |
-
|
| 716 |
-
Como o modelo produz apenas 1 logit por amostra (não LM completo),
|
| 717 |
-
usamos uma aproximação DPO simplificada:
|
| 718 |
-
- policy_chosen_logps = log π(target | y_hat) (log-softmax do target)
|
| 719 |
-
- policy_rejected_logps = log π(rejected_token | y_hat) (de outro sample)
|
| 720 |
-
- reference = zeros (modelo congelado no início = policy inicial)
|
| 721 |
-
|
| 722 |
-
Isto NÃO é DPO canônico (que requer sequence logps), mas captura a
|
| 723 |
-
essência: punir o modelo por preferir "resposta errada" e recompensar
|
| 724 |
-
por preferir "resposta certa". Adequado para bug-detection.
|
| 725 |
-
|
| 726 |
-
Args:
|
| 727 |
-
batch_samples: lista de ProcessedSample
|
| 728 |
-
y_hat: (B, vocab) logits do modelo para o batch
|
| 729 |
-
target: (B,) token alvo (último token da sequência)
|
| 730 |
-
|
| 731 |
-
Returns:
|
| 732 |
-
loss_dpo (escalar com grad) ou None se não aplicável
|
| 733 |
-
"""
|
| 734 |
-
if not batch_samples or not hasattr(batch_samples[0], "label_text"):
|
| 735 |
-
return None
|
| 736 |
-
|
| 737 |
-
B = y_hat.size(0)
|
| 738 |
-
if B < 1:
|
| 739 |
-
return None
|
| 740 |
-
|
| 741 |
-
# Policy log-probs (com grad)
|
| 742 |
-
log_probs = F.log_softmax(y_hat, dim=-1) # (B, vocab)
|
| 743 |
-
|
| 744 |
-
# chosen = target do próprio sample
|
| 745 |
-
chosen_logps = log_probs.gather(
|
| 746 |
-
1, target.clamp(0, log_probs.size(-1) - 1).unsqueeze(-1)
|
| 747 |
-
).squeeze(-1) # (B,)
|
| 748 |
-
|
| 749 |
-
# rejected = target de OUTRO sample (roll-by-1) — proxy para "resposta errada"
|
| 750 |
-
rejected_target = target.roll(1, dims=0) # shift circular
|
| 751 |
-
# Se B == 1, não há outro sample — usa token aleatório como rejected
|
| 752 |
-
if B == 1:
|
| 753 |
-
rejected_target = torch.randint(
|
| 754 |
-
0, log_probs.size(-1), (1,), dtype=target.dtype
|
| 755 |
-
)
|
| 756 |
-
rejected_logps = log_probs.gather(
|
| 757 |
-
1, rejected_target.clamp(0, log_probs.size(-1) - 1).unsqueeze(-1)
|
| 758 |
-
).squeeze(-1) # (B,)
|
| 759 |
-
|
| 760 |
-
# Reference log-probs (sem grad) — política inicial = uniforme
|
| 761 |
-
# log π_ref(token) = -log(vocab_size)
|
| 762 |
-
vocab_size = log_probs.size(-1)
|
| 763 |
-
ref_chosen_logps = torch.full_like(chosen_logps, -math.log(vocab_size))
|
| 764 |
-
ref_rejected_logps = torch.full_like(rejected_logps, -math.log(vocab_size))
|
| 765 |
-
|
| 766 |
-
# β dinâmico
|
| 767 |
-
beta_t = compute_dynamic_beta(
|
| 768 |
-
self.global_step,
|
| 769 |
-
warmup_steps=self.config.dpo_warmup_steps,
|
| 770 |
-
beta_min=self.config.dpo_beta_min,
|
| 771 |
-
beta_max=self.config.dpo_beta_max,
|
| 772 |
-
)
|
| 773 |
-
|
| 774 |
-
# DPO loss
|
| 775 |
-
from .dpo import dpo_loss
|
| 776 |
-
loss = dpo_loss(
|
| 777 |
-
policy_chosen_logps=chosen_logps,
|
| 778 |
-
policy_rejected_logps=rejected_logps,
|
| 779 |
-
reference_chosen_logps=ref_chosen_logps,
|
| 780 |
-
reference_rejected_logps=ref_rejected_logps,
|
| 781 |
-
beta=beta_t,
|
| 782 |
-
label_smoothing=self.config.dpo_label_smoothing,
|
| 783 |
-
use_ipo=self.config.dpo_use_ipo,
|
| 784 |
-
)
|
| 785 |
-
return loss
|
| 786 |
-
|
| 787 |
-
def _run_meta_update(self):
|
| 788 |
-
"""Executa um passo do MetaConfigurator (Lema 4)."""
|
| 789 |
-
# Pega um batch de validação
|
| 790 |
-
val_batch = []
|
| 791 |
-
for i in range(self.config.per_device_batch_size):
|
| 792 |
-
s = self.val_samples[i % len(self.val_samples)]
|
| 793 |
-
val_batch.append(s)
|
| 794 |
-
try:
|
| 795 |
-
input_ids, attn_mask, labels = self._collate(val_batch)
|
| 796 |
-
# Target = último token (mesma lógica de _forward_loss)
|
| 797 |
-
target = input_ids[:, -1].clone() # (batch,)
|
| 798 |
-
meta_result = self.meta_cfg.forward_with_meta(input_ids, target)
|
| 799 |
-
# V3: registra no monitor para detectar estagnação do L4
|
| 800 |
-
self.hyp_monitor.record_meta(
|
| 801 |
-
T_new=meta_result["T_new"],
|
| 802 |
-
tau_new=meta_result["tau_new"],
|
| 803 |
-
sharpness=meta_result["sharpness"],
|
| 804 |
-
)
|
| 805 |
-
logger.info(
|
| 806 |
-
f" META step {self.global_step}: "
|
| 807 |
-
f"val_loss={meta_result['loss_val']:.4f} "
|
| 808 |
-
f"sharpness={meta_result['sharpness']:.4f} "
|
| 809 |
-
f"T={meta_result['T_new']:.4f} τ={meta_result['tau_new']:.4f} "
|
| 810 |
-
f"ΔT={meta_result['grad_log_T']:.6f} "
|
| 811 |
-
f"Δτ={meta_result['grad_log_tau']:.6f}"
|
| 812 |
-
)
|
| 813 |
-
except Exception as e:
|
| 814 |
-
logger.warning(f"Meta update failed: {e}")
|
| 815 |
-
|
| 816 |
-
def _save_temp_checkpoint(self, epoch: int):
|
| 817 |
-
"""Salva checkpoint temporário (será apagado ao final)."""
|
| 818 |
-
path = Path(self.config.temp_dir) / f"temp_step{self.global_step}_epoch{epoch+1}.pt"
|
| 819 |
-
torch.save({
|
| 820 |
-
"model_state_dict": self.model.state_dict(),
|
| 821 |
-
"optimizer_state_dict": self.optimizer.state_dict(),
|
| 822 |
-
"global_step": self.global_step,
|
| 823 |
-
"epoch": epoch,
|
| 824 |
-
"best_loss": self.best_loss,
|
| 825 |
-
"meta_log_T": self.meta_cfg.log_temperature.detach().cpu(),
|
| 826 |
-
"meta_log_tau": self.meta_cfg.log_tau.detach().cpu(),
|
| 827 |
-
}, path)
|
| 828 |
-
|
| 829 |
-
def _save_final_model(self, result: dict):
|
| 830 |
-
"""Salva modelo final + config + tokenizer."""
|
| 831 |
-
model_dir = Path(self.config.output_dir)
|
| 832 |
-
model_dir.mkdir(parents=True, exist_ok=True)
|
| 833 |
-
|
| 834 |
-
# State dict
|
| 835 |
-
state_path = model_dir / "pytorch_model.bin"
|
| 836 |
-
torch.save(self.model.state_dict(), state_path)
|
| 837 |
-
logger.info(f"Modelo final salvo: {state_path}")
|
| 838 |
-
|
| 839 |
-
# Config
|
| 840 |
-
cfg = self.model.config
|
| 841 |
-
config_dict = {
|
| 842 |
-
"model_type": "bigru_t_version",
|
| 843 |
-
"arch": "BiGRU_T_version (4 lemas)",
|
| 844 |
-
"vocab_size": cfg.vocab_size,
|
| 845 |
-
"d_model": cfg.d_model,
|
| 846 |
-
"max_seq_len": cfg.max_seq_len,
|
| 847 |
-
"pad_token_id": cfg.pad_token_id,
|
| 848 |
-
"input_dim": cfg.input_dim,
|
| 849 |
-
"bigru_hidden": cfg.bigru_hidden,
|
| 850 |
-
"d_transformer": cfg.d_transformer,
|
| 851 |
-
"nhead_tu": cfg.nhead_tu,
|
| 852 |
-
"d_ff_tu": cfg.d_ff_tu,
|
| 853 |
-
"output_dim_u8cell": cfg.output_dim_u8cell,
|
| 854 |
-
"max_modules": cfg.max_modules,
|
| 855 |
-
"lambda_ent": cfg.lambda_ent,
|
| 856 |
-
"cache_len": cfg.cache_len,
|
| 857 |
-
"d_cache": cfg.d_cache,
|
| 858 |
-
"nhead_orq": cfg.nhead_orq,
|
| 859 |
-
"d_ff_orq": cfg.d_ff_orq,
|
| 860 |
-
"trainT_dim": cfg.trainT_dim,
|
| 861 |
-
"nhead_train": cfg.nhead_train,
|
| 862 |
-
"d_ff_train": cfg.d_ff_train,
|
| 863 |
-
"num_layers_train": cfg.num_layers_train,
|
| 864 |
-
"hypT_dim": cfg.hypT_dim,
|
| 865 |
-
"nhead_hyp": cfg.nhead_hyp,
|
| 866 |
-
"d_ff_hyp": cfg.d_ff_hyp,
|
| 867 |
-
"num_layers_hyp": cfg.num_layers_hyp,
|
| 868 |
-
"num_bits": cfg.num_bits,
|
| 869 |
-
"dropout": cfg.dropout,
|
| 870 |
-
"total_params": result["params_total"],
|
| 871 |
-
"total_params_M": result["params_M"],
|
| 872 |
-
"training": {
|
| 873 |
-
"version": "BiGRU_T_version",
|
| 874 |
-
"epochs": result["epochs_completed"],
|
| 875 |
-
"global_step": result["global_step"],
|
| 876 |
-
"best_loss": result["best_loss"],
|
| 877 |
-
"final_loss": result["final_loss"],
|
| 878 |
-
"final_avg_loss": result["final_avg_loss"],
|
| 879 |
-
"final_perplexity": result["final_perplexity"],
|
| 880 |
-
"killed": result["killed"],
|
| 881 |
-
"kill_reason": result["kill_reason"],
|
| 882 |
-
"elapsed_s": result["elapsed_s"],
|
| 883 |
-
"datasets": self.config.datasets.split(","),
|
| 884 |
-
"max_samples_per_dataset": self.config.max_samples_per_dataset,
|
| 885 |
-
},
|
| 886 |
-
}
|
| 887 |
-
with open(model_dir / "config.json", "w") as f:
|
| 888 |
-
json.dump(config_dict, f, indent=2, ensure_ascii=False)
|
| 889 |
-
|
| 890 |
-
# Tokenizer (copia do source)
|
| 891 |
-
src_tok = Path("/home/z/my-project/source/model_final/tokenizer/tokenizer.json")
|
| 892 |
-
if src_tok.exists():
|
| 893 |
-
tok_dest = model_dir / "tokenizer"
|
| 894 |
-
tok_dest.mkdir(exist_ok=True)
|
| 895 |
-
shutil.copy(src_tok, tok_dest / "tokenizer.json")
|
| 896 |
-
logger.info(f"Tokenizer copiado para {tok_dest}")
|
| 897 |
-
|
| 898 |
-
# Training report
|
| 899 |
-
report = {
|
| 900 |
-
"version": "BiGRU_T_version",
|
| 901 |
-
"training_params": {
|
| 902 |
-
"epochs": self.config.epochs,
|
| 903 |
-
"per_device_batch_size": self.config.per_device_batch_size,
|
| 904 |
-
"grad_accum": self.config.grad_accum,
|
| 905 |
-
"lr": self.config.lr,
|
| 906 |
-
"weight_decay": self.config.weight_decay,
|
| 907 |
-
"max_grad_norm": self.config.max_grad_norm,
|
| 908 |
-
"max_seq_len": self.config.max_seq_len,
|
| 909 |
-
"use_hypothesis": self.config.use_hypothesis,
|
| 910 |
-
"stop_grad_hyp": self.config.stop_grad_hyp,
|
| 911 |
-
"meta_interval": self.config.meta_interval,
|
| 912 |
-
"meta_lr": self.config.meta_lr,
|
| 913 |
-
"sharpness_lambda": self.config.sharpness_lambda,
|
| 914 |
-
},
|
| 915 |
-
"results": result,
|
| 916 |
-
}
|
| 917 |
-
report_path = Path(self.config.output_dir).parent / "training_report.json"
|
| 918 |
-
with open(report_path, "w") as f:
|
| 919 |
-
json.dump(report, f, indent=2, ensure_ascii=False, default=str)
|
| 920 |
-
logger.info(f"Relatório salvo: {report_path}")
|
| 921 |
-
|
| 922 |
-
def _cleanup_temp(self):
|
| 923 |
-
"""Apaga checkpoints temporários (especificação do usuário)."""
|
| 924 |
-
temp_dir = Path(self.config.temp_dir)
|
| 925 |
-
if not temp_dir.exists():
|
| 926 |
-
return
|
| 927 |
-
for f in temp_dir.glob("temp_step*.pt"):
|
| 928 |
-
try:
|
| 929 |
-
f.unlink()
|
| 930 |
-
logger.info(f"Temp checkpoint apagado: {f.name}")
|
| 931 |
-
except Exception as e:
|
| 932 |
-
logger.warning(f"Falha ao apagar {f}: {e}")
|
| 933 |
-
# Apaga o diretório se vazio
|
| 934 |
-
try:
|
| 935 |
-
temp_dir.rmdir()
|
| 936 |
-
except Exception:
|
| 937 |
-
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
streaming_datasets.py
DELETED
|
@@ -1,995 +0,0 @@
|
|
| 1 |
-
"""streaming_datasets_v13_9.py — Streaming dataset loader for v13.9.1 training.
|
| 2 |
-
|
| 3 |
-
Carrega os 9 datasets especificados pelo usuário via streaming (IterableDataset),
|
| 4 |
-
um por vez, exaustivamente dentro do limite max_samples_per_dataset.
|
| 5 |
-
|
| 6 |
-
Datasets:
|
| 7 |
-
1. CEIA-POSITIVO/ultrachat_br_clustred_balanced_v1
|
| 8 |
-
2. Madras1/corpus-ptbr-v2
|
| 9 |
-
3. rhaymison/multmodal_175k_portuguese
|
| 10 |
-
4. TucanoBR/GigaVerbo
|
| 11 |
-
5. nvidia/OpenMathReasoning
|
| 12 |
-
6. MathLLMs/MathVision
|
| 13 |
-
7. nvidia/OpenMathInstruct-2
|
| 14 |
-
8. dominguesm/restore-punctuation-ptbr-dataset
|
| 15 |
-
9. carolina-c4ai/corpus-carolina
|
| 16 |
-
|
| 17 |
-
Memory-efficient: streaming=True, no full materialization.
|
| 18 |
-
Returns ProcessedSample with raw_text only (tokenizer handles encoding).
|
| 19 |
-
"""
|
| 20 |
-
from __future__ import annotations
|
| 21 |
-
|
| 22 |
-
import logging
|
| 23 |
-
import os
|
| 24 |
-
import time
|
| 25 |
-
import traceback
|
| 26 |
-
from dataclasses import dataclass, field
|
| 27 |
-
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
| 28 |
-
|
| 29 |
-
import torch
|
| 30 |
-
|
| 31 |
-
logger = logging.getLogger(__name__)
|
| 32 |
-
|
| 33 |
-
# Datasets V13.9.1 — exatamente os 9 especificados pelo usuário
|
| 34 |
-
# V6.5-final: adicionado dominguesm/Canarim-Instruct-PTBR-Dataset
|
| 35 |
-
# V6.5-7ds: adicionados adalbertojunior/punctuation-ptbr,
|
| 36 |
-
# iara-project/news-articles-ptbr-dataset, manoela/noticias_ptbr
|
| 37 |
-
# V6.5-V2: adicionado BrunoN-Dev/corpus-ptbr-v1 como 8º dataset de CONHECIMENTO
|
| 38 |
-
# e usado como dataset de TREINAMENTO COM PUNIÇÃO
|
| 39 |
-
DEFAULT_DATASETS = [
|
| 40 |
-
"CEIA-POSITIVO/ultrachat_br_clustred_balanced_v1",
|
| 41 |
-
"Madras1/corpus-ptbr-v2",
|
| 42 |
-
"rhaymison/multmodal_175k_portuguese",
|
| 43 |
-
"TucanoBR/GigaVerbo",
|
| 44 |
-
"nvidia/OpenMathReasoning",
|
| 45 |
-
"MathLLMs/MathVision",
|
| 46 |
-
"nvidia/OpenMathInstruct-2",
|
| 47 |
-
"dominguesm/restore-punctuation-ptbr-dataset",
|
| 48 |
-
"carolina-c4ai/corpus-carolina",
|
| 49 |
-
"dominguesm/Canarim-Instruct-PTBR-Dataset",
|
| 50 |
-
"adalbertojunior/punctuation-ptbr",
|
| 51 |
-
"iara-project/news-articles-ptbr-dataset",
|
| 52 |
-
"manoela/noticias_ptbr",
|
| 53 |
-
"BrunoN-Dev/corpus-ptbr-v1",
|
| 54 |
-
]
|
| 55 |
-
|
| 56 |
-
# V6.5-V2 — Sequência de 8 datasets para a fase CONHECIMENTO (na ordem exata
|
| 57 |
-
# especificada pelo usuário). Meta mínima: 8000 samples ou mais.
|
| 58 |
-
CONHECIMENTO_DATASETS_V2 = [
|
| 59 |
-
"dominguesm/restore-punctuation-ptbr-dataset",
|
| 60 |
-
"carolina-c4ai/corpus-carolina",
|
| 61 |
-
"CEIA-POSITIVO/ultrachat_br_clustred_balanced_v1",
|
| 62 |
-
"dominguesm/Canarim-Instruct-PTBR-Dataset",
|
| 63 |
-
"adalbertojunior/punctuation-ptbr",
|
| 64 |
-
"iara-project/news-articles-ptbr-dataset",
|
| 65 |
-
"manoela/noticias_ptbr",
|
| 66 |
-
"BrunoN-Dev/corpus-ptbr-v1",
|
| 67 |
-
]
|
| 68 |
-
|
| 69 |
-
# V6.5-V2 — Dataset para TREINAMENTO COM PUNIÇÃO (500 samples).
|
| 70 |
-
# O usuário especificou: "dataset para TREINAMENTO COM PUNIÇÃO ATIVA
|
| 71 |
-
# 'BrunoN-Dev/corpus-ptbr-v1'".
|
| 72 |
-
PUNICAO_DATASET_V2 = "BrunoN-Dev/corpus-ptbr-v1"
|
| 73 |
-
|
| 74 |
-
# Dataset format descriptors — Lista priorizada de campos de texto
|
| 75 |
-
DATASET_FORMATS: Dict[str, Dict[str, Any]] = {
|
| 76 |
-
"CEIA-POSITIVO/ultrachat_br_clustred_balanced_v1": {
|
| 77 |
-
"type": "chat",
|
| 78 |
-
"text_fields": ["conversa", "text", "content", "conversation", "messages"],
|
| 79 |
-
"label_fields": [],
|
| 80 |
-
"split": "train",
|
| 81 |
-
"config": None,
|
| 82 |
-
},
|
| 83 |
-
"Madras1/corpus-ptbr-v2": {
|
| 84 |
-
"type": "text",
|
| 85 |
-
"text_fields": ["text", "content", "document", "body"],
|
| 86 |
-
"label_fields": [],
|
| 87 |
-
"split": "train",
|
| 88 |
-
"config": None,
|
| 89 |
-
},
|
| 90 |
-
"rhaymison/multmodal_175k_portuguese": {
|
| 91 |
-
"type": "multimodal",
|
| 92 |
-
"text_fields": ["description", "text", "question", "prompt", "instruction", "input"],
|
| 93 |
-
"image_fields": ["image", "image_url"],
|
| 94 |
-
"label_fields": ["answer", "response", "output"],
|
| 95 |
-
"split": "train",
|
| 96 |
-
"config": None,
|
| 97 |
-
},
|
| 98 |
-
"TucanoBR/GigaVerbo": {
|
| 99 |
-
"type": "text",
|
| 100 |
-
"text_fields": ["text", "content", "document", "body"],
|
| 101 |
-
"label_fields": [],
|
| 102 |
-
"split": "train",
|
| 103 |
-
"config": None,
|
| 104 |
-
},
|
| 105 |
-
"nvidia/OpenMathReasoning": {
|
| 106 |
-
"type": "math",
|
| 107 |
-
"text_fields": ["problem", "question", "input", "prompt"],
|
| 108 |
-
"label_fields": ["solution", "answer", "output", "response"],
|
| 109 |
-
# BUG FIX: OpenMathReasoning has splits ['cot', 'tir', 'genselect', 'additional_problems']
|
| 110 |
-
"split": "cot", # Use 'cot' (chain-of-thought) as primary split
|
| 111 |
-
"config": None,
|
| 112 |
-
},
|
| 113 |
-
"MathLLMs/MathVision": {
|
| 114 |
-
"type": "math_multimodal",
|
| 115 |
-
"text_fields": ["question", "problem", "text", "query"],
|
| 116 |
-
"image_fields": ["image", "image_url"],
|
| 117 |
-
"label_fields": ["answer", "solution", "response"],
|
| 118 |
-
# BUG FIX: MathVision has only 'test' and 'testmini' splits (no train)
|
| 119 |
-
"split": "test",
|
| 120 |
-
"config": None,
|
| 121 |
-
},
|
| 122 |
-
"nvidia/OpenMathInstruct-2": {
|
| 123 |
-
"type": "math",
|
| 124 |
-
"text_fields": ["problem", "question", "input", "prompt"],
|
| 125 |
-
# V3 FIX: campos reais do dataset são generated_solution e expected_answer
|
| 126 |
-
"label_fields": ["generated_solution", "expected_answer", "solution", "answer", "output", "response"],
|
| 127 |
-
"split": "train",
|
| 128 |
-
"config": None,
|
| 129 |
-
},
|
| 130 |
-
"dominguesm/restore-punctuation-ptbr-dataset": {
|
| 131 |
-
"type": "punctuation",
|
| 132 |
-
"text_fields": ["text", "original", "unpunctuated", "input"],
|
| 133 |
-
"label_fields": ["punctuated", "restored", "target", "output"],
|
| 134 |
-
"split": "train",
|
| 135 |
-
"config": None,
|
| 136 |
-
},
|
| 137 |
-
"carolina-c4ai/corpus-carolina": {
|
| 138 |
-
"type": "text",
|
| 139 |
-
"text_fields": ["text", "content", "document", "body", "xml"],
|
| 140 |
-
"label_fields": [],
|
| 141 |
-
"split": "train",
|
| 142 |
-
"config": None,
|
| 143 |
-
# V13.9.2-carolina: dataset original usa script Python (não suportado em
|
| 144 |
-
# datasets 5.0+). Workaround: stream_xml_gz direto via lxml.iterparse,
|
| 145 |
-
# sem usar o script Python. Implementado em _stream_carolina_direct().
|
| 146 |
-
},
|
| 147 |
-
# ── V13.9.2-finetune-v2: novos datasets PT-BR ───────────────────────────
|
| 148 |
-
# Adicionados para fine-tuning contínuo do modelo v13.9.2 (preserva
|
| 149 |
-
# arquitetura e parâmetros). Nenhuma mudança estrutural — apenas novos
|
| 150 |
-
# descritores de formato para que stream_dataset() reconheça os datasets.
|
| 151 |
-
#
|
| 152 |
-
# V13.9.2-finetune-v3: padronização para o formato unificado
|
| 153 |
-
# "### Instruction:\n...\n\n### Response:\n..." conforme requisitado pelo
|
| 154 |
-
# usuário. Apenas os datasets com estrutura instruction/response (orion,
|
| 155 |
-
# cnmoro) usam o template; strak2005/bratao continuam como texto puro
|
| 156 |
-
# (corpus sem estrutura de instrução). Nenhuma mudança nos 9 datasets
|
| 157 |
-
# base — retrocompatibilidade total.
|
| 158 |
-
"orion-research/translations-en_US-pt_BR": {
|
| 159 |
-
"type": "translation",
|
| 160 |
-
# string = EN (instruction), string_translation = PT-BR (response).
|
| 161 |
-
# V13.9.2-finetune-v3: separa instruction/response para o template
|
| 162 |
-
# unificado em vez de concatenar como texto único.
|
| 163 |
-
"text_fields": ["string"],
|
| 164 |
-
"label_fields": ["string_translation"],
|
| 165 |
-
"split": "train",
|
| 166 |
-
"config": None,
|
| 167 |
-
"format_template": "instruction_response",
|
| 168 |
-
"instruction_prefix": "Translate the following text to Portuguese:",
|
| 169 |
-
},
|
| 170 |
-
"cnmoro/Instruct-PTBR-10M": {
|
| 171 |
-
"type": "instruct",
|
| 172 |
-
# NB: campos em MAIÚSCULAS no schema do dataset
|
| 173 |
-
# V13.9.2-finetune-v3: INSTRUCTION -> instruction, RESPONSE -> response
|
| 174 |
-
# via template unificado.
|
| 175 |
-
"text_fields": ["INSTRUCTION"],
|
| 176 |
-
"label_fields": ["RESPONSE"],
|
| 177 |
-
"split": "train",
|
| 178 |
-
"config": None,
|
| 179 |
-
"format_template": "instruction_response",
|
| 180 |
-
# V13.9.2-finetune-v2: dataset é um único parquet de 10 GB — streaming
|
| 181 |
-
# via datasets.load_dataset() é extremamente lento para materializar
|
| 182 |
-
# a primeira amostra. Fallback via HF datasets-server rows API (HTTP),
|
| 183 |
-
# que retorna batches de 100 rows rapidamente. Implementado em
|
| 184 |
-
# _stream_via_rows_api().
|
| 185 |
-
"loader": "rows_api",
|
| 186 |
-
},
|
| 187 |
-
"strak2005/corpus-ptbr-v1": {
|
| 188 |
-
"type": "text",
|
| 189 |
-
"text_fields": ["text", "content"],
|
| 190 |
-
"label_fields": [],
|
| 191 |
-
"split": "train",
|
| 192 |
-
"config": None,
|
| 193 |
-
# Fallback: se strak2005 cair, tenta bratao (mirror idêntico)
|
| 194 |
-
"fallback": "bratao/corpus-ptbr-v1",
|
| 195 |
-
# V13.9.2-finetune-v2: dataset tem 18 shards parquet de ~1GB cada.
|
| 196 |
-
# Streaming via datasets.load_dataset() não materializa primeira
|
| 197 |
-
# amostra em tempo aceitável. Fallback: baixa 1 shard localmente e
|
| 198 |
-
# itera com pyarrow. Implementado em _stream_via_parquet_first_shard().
|
| 199 |
-
"loader": "parquet_first_shard",
|
| 200 |
-
},
|
| 201 |
-
"bratao/corpus-ptbr-v1": {
|
| 202 |
-
"type": "text",
|
| 203 |
-
"text_fields": ["text", "content"],
|
| 204 |
-
"label_fields": [],
|
| 205 |
-
"split": "train",
|
| 206 |
-
"config": None,
|
| 207 |
-
"loader": "parquet_first_shard",
|
| 208 |
-
},
|
| 209 |
-
# ── V6.5: novos datasets para esgotar ────────────────────────────────
|
| 210 |
-
# User requirement: "esgotar 'dominguesm/restore-punctuation-ptbr-dataset'
|
| 211 |
-
# e 'carolina-c4ai/corpus-carolina' e 'nvidia/OpenMathInstruct-2' e
|
| 212 |
-
# 'nvidia/OpenMathReasoning' e 'CEIA-POSITIVO/ultrachat_br_clustred_balanced_v1'
|
| 213 |
-
# e 'Dexavator/English-PTBR'"
|
| 214 |
-
#
|
| 215 |
-
# Dexavator/English-PTBR: dataset de tradução EN->PT-BR.
|
| 216 |
-
# Estrutura típica: {"english": "...", "portuguese": "..."} ou
|
| 217 |
-
# {"en": "...", "pt": "..."}.
|
| 218 |
-
"Dexavator/English-PTBR": {
|
| 219 |
-
"type": "translation",
|
| 220 |
-
"text_fields": ["english", "en", "text", "source"],
|
| 221 |
-
"label_fields": ["portuguese", "pt", "target", "translation"],
|
| 222 |
-
"split": "train",
|
| 223 |
-
"config": None,
|
| 224 |
-
"format_template": "instruction_response",
|
| 225 |
-
"instruction_prefix": "Translate the following text to Portuguese:",
|
| 226 |
-
},
|
| 227 |
-
# ── V6.5-final: Canarim-Instruct-PTBR-Dataset ────────────────────────
|
| 228 |
-
# User requirement: "esgotar 'dominguesm/Canarim-Instruct-PTBR-Dataset'"
|
| 229 |
-
# Canarim-Instruct: dataset PT-BR de instruções (~430k samples).
|
| 230 |
-
# Estrutura típica: {"instruction": "...", "input": "...", "output": "..."}
|
| 231 |
-
# ou {"text": "...", "conversation": [{"role": "user", "content": "..."}, ...]}
|
| 232 |
-
"dominguesm/Canarim-Instruct-PTBR-Dataset": {
|
| 233 |
-
"type": "instruct",
|
| 234 |
-
"text_fields": ["instruction", "input", "text", "prompt", "question"],
|
| 235 |
-
"label_fields": ["output", "response", "answer"],
|
| 236 |
-
"split": "train",
|
| 237 |
-
"config": None,
|
| 238 |
-
"format_template": "instruction_response",
|
| 239 |
-
"instruction_prefix": "Instrução:",
|
| 240 |
-
},
|
| 241 |
-
# ── V6.5-7ds: novos 3 datasets PT-BR ─────────────────────────────────
|
| 242 |
-
# User requirement (latest): "streaming até esgotar nesta sequência
|
| 243 |
-
# 'dominguesm/restore-punctuation-ptbr-dataset' e
|
| 244 |
-
# 'carolina-c4ai/corpus-carolina' e
|
| 245 |
-
# 'CEIA-POSITIVO/ultrachat_br_clustred_balanced_v1' e
|
| 246 |
-
# 'dominguesm/Canarim-Instruct-PTBR-Dataset' e
|
| 247 |
-
# 'adalbertojunior/punctuation-ptbr' e
|
| 248 |
-
# 'iara-project/news-articles-ptbr-dataset' e
|
| 249 |
-
# 'manoela/noticias_ptbr'"
|
| 250 |
-
#
|
| 251 |
-
# adalbertojunior/punctuation-ptbr:
|
| 252 |
-
# Dataset de restauração de pontuação PT-BR. Schema:
|
| 253 |
-
# {id: str, tokens: list[str], pos_tags, chunk_tags, ner_tags}
|
| 254 |
-
# O campo "tokens" é uma lista de palavras (sem pontuação). Precisamos
|
| 255 |
-
# fazer join com espaços para obter texto bruto. Como o dataset usa
|
| 256 |
-
# script Python (datasets 5.0+ não suporta), usamos rows_api loader.
|
| 257 |
-
# Splits: train/validation/test com config "punctuation-ptbr".
|
| 258 |
-
"adalbertojunior/punctuation-ptbr": {
|
| 259 |
-
"type": "punctuation_tokens",
|
| 260 |
-
"text_fields": ["tokens"], # list[str] — join com espaços
|
| 261 |
-
"label_fields": [],
|
| 262 |
-
"split": "train",
|
| 263 |
-
"config": "punctuation-ptbr", # necessário para rows_api
|
| 264 |
-
"loader": "rows_api",
|
| 265 |
-
"join_tokens": True, # flag especial para _normalize_sample
|
| 266 |
-
},
|
| 267 |
-
# iara-project/news-articles-ptbr-dataset:
|
| 268 |
-
# Dataset de notícias PT-BR da Folha de S.Paulo (via IARA project).
|
| 269 |
-
# Schema: {title, text, date, category, category_natural_language, link}
|
| 270 |
-
# Streaming normal via parquet — primeira amostra materializa rápido.
|
| 271 |
-
# "text" é o corpo da notícia, "title" é o título. Concatenamos ambos
|
| 272 |
-
# via format_template="title_text_concat" para enriquecer contexto.
|
| 273 |
-
# O campo "category" é usado como label (categoria da notícia).
|
| 274 |
-
"iara-project/news-articles-ptbr-dataset": {
|
| 275 |
-
"type": "news",
|
| 276 |
-
# text_fields em ordem de prioridade: "text" (corpo) primeiro
|
| 277 |
-
"text_fields": ["text", "title", "content", "body"],
|
| 278 |
-
"label_fields": ["category", "category_natural_language", "title"],
|
| 279 |
-
"split": "train",
|
| 280 |
-
"config": None,
|
| 281 |
-
# V6.5-7ds: title_text_concat usa title como label (prefixo)
|
| 282 |
-
# e text como corpo. Aqui label_fields pega "category" primeiro
|
| 283 |
-
# para dar contexto temático.
|
| 284 |
-
"format_template": "title_text_concat",
|
| 285 |
-
},
|
| 286 |
-
# manoela/noticias_ptbr:
|
| 287 |
-
# Mirror do iara-project/news-articles-ptbr-dataset (mesmo parquet,
|
| 288 |
-
# mesmo schema). Streaming normal via parquet.
|
| 289 |
-
"manoela/noticias_ptbr": {
|
| 290 |
-
"type": "news",
|
| 291 |
-
"text_fields": ["text", "title", "content", "body"],
|
| 292 |
-
"label_fields": ["category", "category_natural_language", "title"],
|
| 293 |
-
"split": "train",
|
| 294 |
-
"config": None,
|
| 295 |
-
"format_template": "title_text_concat",
|
| 296 |
-
},
|
| 297 |
-
# ── V6.5-V2: BrunoN-Dev/corpus-ptbr-v1 ───────────────────────────────
|
| 298 |
-
# User requirement: "dataset para TREINAMENTO COM PUNIÇÃO ATIVA
|
| 299 |
-
# 'BrunoN-Dev/corpus-ptbr-v1'" e o 8º dataset de CONHECIMENTO.
|
| 300 |
-
#
|
| 301 |
-
# BrunoN-Dev/corpus-ptbr-v1: corpus PT-BR genérico com 18 shards parquet
|
| 302 |
-
# (~1GB cada). Schema: {"text": "...", "meta": "..."}.
|
| 303 |
-
# Streaming via datasets.load_dataset(streaming=True) é lento para
|
| 304 |
-
# materializar a primeira amostra — usamos o loader parquet_first_shard
|
| 305 |
-
# (baixa apenas o primeiro shard e itera localmente com pyarrow).
|
| 306 |
-
# Sem label explícita — usamos hash do texto (paridade do nº de palavras)
|
| 307 |
-
# para gerar label binário (0/1) — não é dado sintético, é derivação
|
| 308 |
-
# algorítmica do conteúdo real do dataset.
|
| 309 |
-
"BrunoN-Dev/corpus-ptbr-v1": {
|
| 310 |
-
"type": "text",
|
| 311 |
-
"text_fields": ["text", "content", "body", "document"],
|
| 312 |
-
"label_fields": [],
|
| 313 |
-
"split": "train",
|
| 314 |
-
"config": None,
|
| 315 |
-
"loader": "parquet_first_shard",
|
| 316 |
-
# V6.5-V2: gera label binário determinístico via hash do texto
|
| 317 |
-
# (paridade do número de palavras) — não é dado sintético, é
|
| 318 |
-
# derivação algorítmica do conteúdo real do dataset.
|
| 319 |
-
"label_strategy": "hash_parity",
|
| 320 |
-
},
|
| 321 |
-
}
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
# Lista de arquivos XML.gz do Carolina que tentaremos baixar (ordem: menores
|
| 325 |
-
# taxonomias primeiro para minimizar banda e RAM). Selecionamos 2 arquivos
|
| 326 |
-
# por taxonomia para garantir diversidade sem explodir a memória.
|
| 327 |
-
CAROLINA_XML_FILES = [
|
| 328 |
-
# taxonomia "datasets_and_other_corpora" (DAT = pt-BR notícias)
|
| 329 |
-
"corpus/datasets_and_other_corpora/pt-BR/DATa.xml.gz",
|
| 330 |
-
"corpus/datasets_and_other_corpora/pt-BR/DATb.xml.gz",
|
| 331 |
-
# taxonomia "wik" (wikis)
|
| 332 |
-
"corpus/wik/WIKa.xml.gz",
|
| 333 |
-
"corpus/wik/WIKb.xml.gz",
|
| 334 |
-
# taxonomia "uni" (university_domains)
|
| 335 |
-
"corpus/uni/UNIa.xml.gz",
|
| 336 |
-
"corpus/uni/UNIb.xml.gz",
|
| 337 |
-
# taxonomia "pub" (public_domain_works)
|
| 338 |
-
"corpus/pub/PUBa.xml.gz",
|
| 339 |
-
"corpus/pub/PUBb.xml.gz",
|
| 340 |
-
]
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
def _stream_carolina_direct(
|
| 344 |
-
max_samples: int,
|
| 345 |
-
hf_token: Optional[str] = None,
|
| 346 |
-
timeout_per_file: int = 60,
|
| 347 |
-
) -> Iterator[Dict[str, Any]]:
|
| 348 |
-
"""Workaround V13.9.2-carolina: carrega Carolina sem usar o script Python.
|
| 349 |
-
|
| 350 |
-
Baixa arquivos XML.gz diretamente do repo e faz parse streaming com
|
| 351 |
-
lxml.etree.iterparse (compatível com datasets 5.0+). Cada <TEI> vira um
|
| 352 |
-
documento de texto (concatenação dos <p> dentro de <body>).
|
| 353 |
-
|
| 354 |
-
Args:
|
| 355 |
-
max_samples: número máximo de amostras a retornar
|
| 356 |
-
hf_token: token HF opcional (dataset é público, mas token evita rate limit)
|
| 357 |
-
timeout_per_file: tempo máximo (segundos) por arquivo XML
|
| 358 |
-
|
| 359 |
-
Yields:
|
| 360 |
-
dict com chave "text" (compatível com _normalize_sample)
|
| 361 |
-
"""
|
| 362 |
-
from huggingface_hub import hf_hub_download
|
| 363 |
-
import gzip
|
| 364 |
-
from lxml import etree
|
| 365 |
-
|
| 366 |
-
TEI_NS = "{http://www.tei-c.org/ns/1.0}"
|
| 367 |
-
BODY_P_TAG = f".//{TEI_NS}body/{TEI_NS}p"
|
| 368 |
-
|
| 369 |
-
count = 0
|
| 370 |
-
for xml_path in CAROLINA_XML_FILES:
|
| 371 |
-
if count >= max_samples:
|
| 372 |
-
return
|
| 373 |
-
try:
|
| 374 |
-
local_path = hf_hub_download(
|
| 375 |
-
repo_id="carolina-c4ai/corpus-carolina",
|
| 376 |
-
filename=xml_path,
|
| 377 |
-
repo_type="dataset",
|
| 378 |
-
token=hf_token,
|
| 379 |
-
)
|
| 380 |
-
except Exception as e:
|
| 381 |
-
logger.warning(f"Carolina: não foi possível baixar {xml_path}: {e}")
|
| 382 |
-
continue
|
| 383 |
-
|
| 384 |
-
try:
|
| 385 |
-
with gzip.open(local_path, "rb") as gz:
|
| 386 |
-
# iterparse streaming: não carrega árvore inteira na memória
|
| 387 |
-
for _, tei in etree.iterparse(
|
| 388 |
-
gz, huge_tree=True, encoding="utf-8", tag=f"{TEI_NS}TEI"
|
| 389 |
-
):
|
| 390 |
-
if count >= max_samples:
|
| 391 |
-
tei.clear()
|
| 392 |
-
return
|
| 393 |
-
# Extrai texto dos <p> dentro de <body>
|
| 394 |
-
parts = []
|
| 395 |
-
for p in tei.findall(BODY_P_TAG):
|
| 396 |
-
if p.text:
|
| 397 |
-
parts.append(p.text)
|
| 398 |
-
text = " ".join(parts).strip()
|
| 399 |
-
if text and len(text) >= 10:
|
| 400 |
-
yield {"text": text, "meta": ""}
|
| 401 |
-
tei.clear() # libera memória da árvore TEI
|
| 402 |
-
count += 1
|
| 403 |
-
except Exception as e:
|
| 404 |
-
logger.warning(f"Carolina: erro processando {xml_path}: {e}")
|
| 405 |
-
continue
|
| 406 |
-
|
| 407 |
-
logger.info(f"Carolina: streamou {count} amostras no total")
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 411 |
-
# V13.9.2-finetune-v2: Loaders alternativos para datasets parquet grandes
|
| 412 |
-
# onde datasets.load_dataset(streaming=True) é inviável (primeira amostra
|
| 413 |
-
# demora minutos para materializar).
|
| 414 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 415 |
-
|
| 416 |
-
def _stream_via_rows_api(
|
| 417 |
-
dataset_name: str,
|
| 418 |
-
max_samples: int,
|
| 419 |
-
hf_token: Optional[str] = None,
|
| 420 |
-
batch_size: int = 100,
|
| 421 |
-
max_offset: int = 100_000,
|
| 422 |
-
config: Optional[str] = None,
|
| 423 |
-
split: str = "train",
|
| 424 |
-
) -> Iterator[Dict[str, Any]]:
|
| 425 |
-
"""Carrega amostras via HF datasets-server /rows API (HTTP).
|
| 426 |
-
|
| 427 |
-
Usado para datasets cujo parquet é muito grande para streaming eficiente
|
| 428 |
-
(ex: cnmoro/Instruct-PTBR-10M com 10 GB em arquivo único) OU datasets
|
| 429 |
-
que usam script Python como loader (não suportado em datasets 5.0+,
|
| 430 |
-
ex: adalbertojunior/punctuation-ptbr). A rows API retorna batches de
|
| 431 |
-
até 100 rows via HTTP, sem precisar baixar o parquet.
|
| 432 |
-
|
| 433 |
-
Args:
|
| 434 |
-
dataset_name: repo_id do dataset (ex: "cnmoro/Instruct-PTBR-10M")
|
| 435 |
-
max_samples: nº máximo de amostras a retornar
|
| 436 |
-
hf_token: token HF (opcional — dataset público)
|
| 437 |
-
batch_size: nº de rows por requisição (máx 100)
|
| 438 |
-
max_offset: offset máximo a tentar (sai do loop se excedido)
|
| 439 |
-
config: nome da config do dataset (default: "default"). NECESSÁRIO
|
| 440 |
-
para datasets cuja config não é "default" (ex:
|
| 441 |
-
adalbertojunior/punctuation-ptbr usa "punctuation-ptbr").
|
| 442 |
-
split: nome do split (default: "train").
|
| 443 |
-
|
| 444 |
-
Yields:
|
| 445 |
-
dict com campos do dataset (compatível com _normalize_sample)
|
| 446 |
-
"""
|
| 447 |
-
import json as _json
|
| 448 |
-
import urllib.request
|
| 449 |
-
import urllib.parse
|
| 450 |
-
|
| 451 |
-
base_url = "https://datasets-server.huggingface.co/rows"
|
| 452 |
-
config_name = config or "default"
|
| 453 |
-
count = 0
|
| 454 |
-
offset = 0
|
| 455 |
-
|
| 456 |
-
while count < max_samples and offset < max_offset:
|
| 457 |
-
params = urllib.parse.urlencode({
|
| 458 |
-
"dataset": dataset_name,
|
| 459 |
-
"config": config_name,
|
| 460 |
-
"split": split,
|
| 461 |
-
"offset": offset,
|
| 462 |
-
"length": min(batch_size, max_samples - count),
|
| 463 |
-
})
|
| 464 |
-
url = f"{base_url}?{params}"
|
| 465 |
-
try:
|
| 466 |
-
req = urllib.request.Request(url)
|
| 467 |
-
if hf_token:
|
| 468 |
-
req.add_header("Authorization", f"Bearer {hf_token}")
|
| 469 |
-
with urllib.request.urlopen(req, timeout=30) as r:
|
| 470 |
-
data = _json.loads(r.read())
|
| 471 |
-
except Exception as e:
|
| 472 |
-
logger.warning(f"rows_api {dataset_name} config={config_name} offset={offset}: {e}")
|
| 473 |
-
break
|
| 474 |
-
|
| 475 |
-
rows = data.get("rows", [])
|
| 476 |
-
if not rows:
|
| 477 |
-
break
|
| 478 |
-
|
| 479 |
-
for row in rows:
|
| 480 |
-
if count >= max_samples:
|
| 481 |
-
return
|
| 482 |
-
row_data = row.get("row", {})
|
| 483 |
-
# Remove campos internos como __index_level_0__
|
| 484 |
-
clean = {k: v for k, v in row_data.items()
|
| 485 |
-
if not k.startswith("__")}
|
| 486 |
-
yield clean
|
| 487 |
-
count += 1
|
| 488 |
-
|
| 489 |
-
offset += len(rows)
|
| 490 |
-
if len(rows) < batch_size:
|
| 491 |
-
break # chegou ao fim do dataset
|
| 492 |
-
|
| 493 |
-
logger.info(f"rows_api {dataset_name} config={config_name}: streamou {count} amostras")
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
def _stream_via_parquet_first_shard(
|
| 497 |
-
dataset_name: str,
|
| 498 |
-
max_samples: int,
|
| 499 |
-
hf_token: Optional[str] = None,
|
| 500 |
-
) -> Iterator[Dict[str, Any]]:
|
| 501 |
-
"""Baixa primeiro shard parquet do dataset e itera localmente com pyarrow.
|
| 502 |
-
|
| 503 |
-
Usado para datasets com múltiplos shards parquet grandes (ex:
|
| 504 |
-
strak2005/corpus-ptbr-v1 com 18 shards de ~1GB). Streaming via
|
| 505 |
-
datasets.load_dataset() não materializa primeira amostra em tempo
|
| 506 |
-
aceitável. Esta função baixa o primeiro shard (~26s para 1GB) e itera
|
| 507 |
-
localmente — rápido após download.
|
| 508 |
-
|
| 509 |
-
Args:
|
| 510 |
-
dataset_name: repo_id do dataset
|
| 511 |
-
max_samples: nº máximo de amostras
|
| 512 |
-
hf_token: token HF opcional
|
| 513 |
-
|
| 514 |
-
Yields:
|
| 515 |
-
dict com campos do dataset
|
| 516 |
-
"""
|
| 517 |
-
from huggingface_hub import HfApi, hf_hub_download
|
| 518 |
-
import pyarrow.parquet as pq
|
| 519 |
-
|
| 520 |
-
api = HfApi(token=hf_token)
|
| 521 |
-
|
| 522 |
-
# Lista arquivos parquet no repo
|
| 523 |
-
try:
|
| 524 |
-
info = api.dataset_info(dataset_name)
|
| 525 |
-
except Exception as e:
|
| 526 |
-
logger.warning(f"parquet_shard {dataset_name}: dataset_info failed: {e}")
|
| 527 |
-
return
|
| 528 |
-
|
| 529 |
-
parquet_files = sorted([
|
| 530 |
-
s.rfilename for s in info.siblings
|
| 531 |
-
if s.rfilename.endswith(".parquet")
|
| 532 |
-
])
|
| 533 |
-
if not parquet_files:
|
| 534 |
-
logger.warning(f"parquet_shard {dataset_name}: nenhum .parquet encontrado")
|
| 535 |
-
return
|
| 536 |
-
|
| 537 |
-
# Baixa apenas o primeiro shard
|
| 538 |
-
first_shard = parquet_files[0]
|
| 539 |
-
logger.info(f"parquet_shard {dataset_name}: baixando {first_shard}...")
|
| 540 |
-
try:
|
| 541 |
-
local_path = hf_hub_download(
|
| 542 |
-
repo_id=dataset_name,
|
| 543 |
-
filename=first_shard,
|
| 544 |
-
repo_type="dataset",
|
| 545 |
-
token=hf_token,
|
| 546 |
-
)
|
| 547 |
-
except Exception as e:
|
| 548 |
-
logger.warning(f"parquet_shard {dataset_name}: download failed: {e}")
|
| 549 |
-
return
|
| 550 |
-
|
| 551 |
-
# Itera com pyarrow.iter_batches — método eficiente que lê batches pequenos
|
| 552 |
-
# do parquet sem materializar o row group inteiro na memória.
|
| 553 |
-
# BUG FIX V13.9.2-finetune-v2: read_row_group() materializa TODAS as ~1M
|
| 554 |
-
# rows de uma vez (lento + ~1GB RAM). iter_batches(batch_size=10) lê só
|
| 555 |
-
# 10 rows por vez (rápido + ~10KB RAM).
|
| 556 |
-
try:
|
| 557 |
-
pf = pq.ParquetFile(local_path)
|
| 558 |
-
count = 0
|
| 559 |
-
# batch_read_size: nº de rows por batch (pequeno = menos RAM)
|
| 560 |
-
for batch in pf.iter_batches(batch_size=100):
|
| 561 |
-
if count >= max_samples:
|
| 562 |
-
break
|
| 563 |
-
cols = batch.schema.names
|
| 564 |
-
# to_pylist() é eficiente para batches pequenos
|
| 565 |
-
pydict = batch.to_pydict()
|
| 566 |
-
n_rows = len(pydict[cols[0]]) if cols else 0
|
| 567 |
-
for i in range(n_rows):
|
| 568 |
-
if count >= max_samples:
|
| 569 |
-
break
|
| 570 |
-
row = {col: pydict[col][i] for col in cols}
|
| 571 |
-
yield row
|
| 572 |
-
count += 1
|
| 573 |
-
except Exception as e:
|
| 574 |
-
logger.warning(f"parquet_shard {dataset_name}: pyarrow iter failed: {e}")
|
| 575 |
-
return
|
| 576 |
-
|
| 577 |
-
logger.info(f"parquet_shard {dataset_name}: streamou {count} amostras do shard {first_shard}")
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
@dataclass
|
| 581 |
-
class ProcessedSample:
|
| 582 |
-
"""Uma amostra processada: texto bruto + metadados."""
|
| 583 |
-
raw_text: str
|
| 584 |
-
dataset_name: str
|
| 585 |
-
sample_idx: int
|
| 586 |
-
text_field: str = ""
|
| 587 |
-
label_text: str = ""
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
def _extract_field(sample: Dict[str, Any], candidate_fields: List[str]) -> Optional[str]:
|
| 591 |
-
"""Extrai texto da primeira field disponível no sample.
|
| 592 |
-
|
| 593 |
-
Suporta:
|
| 594 |
-
- strings diretas
|
| 595 |
-
- listas de strings (join com espaço — útil para datasets tokenizados
|
| 596 |
-
como adalbertojunior/punctuation-ptbr onde "tokens": ["hoje","a",...])
|
| 597 |
-
- listas de dicts (chat messages — concatena role: content)
|
| 598 |
-
- dicts aninhados (pega text/content/body)
|
| 599 |
-
"""
|
| 600 |
-
for field_name in candidate_fields:
|
| 601 |
-
if field_name in sample:
|
| 602 |
-
val = sample[field_name]
|
| 603 |
-
if val is None:
|
| 604 |
-
continue
|
| 605 |
-
if isinstance(val, str):
|
| 606 |
-
if val.strip():
|
| 607 |
-
return val
|
| 608 |
-
elif isinstance(val, list):
|
| 609 |
-
# V6.5-7ds: se for lista de strings, join com espaços
|
| 610 |
-
# (ex: adalbertojunior/punctuation-ptbr tokens field)
|
| 611 |
-
string_parts = []
|
| 612 |
-
chat_parts = []
|
| 613 |
-
all_strings = True
|
| 614 |
-
for item in val:
|
| 615 |
-
if isinstance(item, str):
|
| 616 |
-
if item.strip():
|
| 617 |
-
string_parts.append(item)
|
| 618 |
-
elif isinstance(item, (int, float)):
|
| 619 |
-
# tokens podem vir como ints (token IDs) — skip
|
| 620 |
-
all_strings = False
|
| 621 |
-
break
|
| 622 |
-
elif isinstance(item, dict):
|
| 623 |
-
all_strings = False
|
| 624 |
-
role = item.get("role", "")
|
| 625 |
-
content = item.get("content", "")
|
| 626 |
-
if isinstance(content, str) and content.strip():
|
| 627 |
-
chat_parts.append(f"{role}: {content}")
|
| 628 |
-
else:
|
| 629 |
-
all_strings = False
|
| 630 |
-
if all_strings and string_parts:
|
| 631 |
-
joined = " ".join(string_parts).strip()
|
| 632 |
-
if joined:
|
| 633 |
-
return joined
|
| 634 |
-
if chat_parts:
|
| 635 |
-
return "\n".join(chat_parts)
|
| 636 |
-
elif isinstance(val, dict):
|
| 637 |
-
# Pode ser nested
|
| 638 |
-
text = val.get("text") or val.get("content") or val.get("body")
|
| 639 |
-
if isinstance(text, str) and text.strip():
|
| 640 |
-
return text
|
| 641 |
-
return None
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
def _normalize_sample(sample: Dict[str, Any], dataset_name: str, idx: int) -> Optional[ProcessedSample]:
|
| 645 |
-
"""Normaliza uma amostra bruta em ProcessedSample."""
|
| 646 |
-
fmt = DATASET_FORMATS.get(dataset_name, {})
|
| 647 |
-
text_fields = fmt.get("text_fields", ["text"])
|
| 648 |
-
label_fields = fmt.get("label_fields", [])
|
| 649 |
-
|
| 650 |
-
text = _extract_field(sample, text_fields)
|
| 651 |
-
if not text or len(text.strip()) < 10: # skip very short
|
| 652 |
-
return None
|
| 653 |
-
|
| 654 |
-
label = ""
|
| 655 |
-
if label_fields:
|
| 656 |
-
label = _extract_field(sample, label_fields) or ""
|
| 657 |
-
|
| 658 |
-
# Se tem label, aplica template apropriado
|
| 659 |
-
template = fmt.get("format_template")
|
| 660 |
-
full_text = text
|
| 661 |
-
|
| 662 |
-
# V13.9.2-finetune-v3: para o template instruction_response, exige ambos
|
| 663 |
-
# os lados (instruction + response). Samples sem response são descartados
|
| 664 |
-
# para evitar treinar em texto parcial.
|
| 665 |
-
if template == "instruction_response" and not label:
|
| 666 |
-
return None
|
| 667 |
-
|
| 668 |
-
if label:
|
| 669 |
-
# V13.9.2-finetune-v3: format_template unificado "### Instruction:/### Response:"
|
| 670 |
-
# quando o dataset declarar explicitamente. Caso contrário, mantém o
|
| 671 |
-
# comportamento legado (concatenação simples text\nlabel) para os 9
|
| 672 |
-
# datasets base — retrocompatibilidade total.
|
| 673 |
-
if template == "instruction_response":
|
| 674 |
-
instr_prefix = fmt.get("instruction_prefix", "")
|
| 675 |
-
if instr_prefix:
|
| 676 |
-
full_text = (
|
| 677 |
-
f"### Instruction:\n{instr_prefix}\n{text}\n\n"
|
| 678 |
-
f"### Response:\n{label}"
|
| 679 |
-
)
|
| 680 |
-
else:
|
| 681 |
-
full_text = (
|
| 682 |
-
f"### Instruction:\n{text}\n\n"
|
| 683 |
-
f"### Response:\n{label}"
|
| 684 |
-
)
|
| 685 |
-
elif template == "title_text_concat":
|
| 686 |
-
# V6.5-7ds: para datasets de notícias (iara-project, manoela),
|
| 687 |
-
# o campo "text" (corpo da notícia) é o conteúdo principal e
|
| 688 |
-
# "category" (categoria) é o contexto temático. Formatamos como
|
| 689 |
-
# "TEXTO: <corpo>\nCATEGORIA: <categoria>".
|
| 690 |
-
full_text = f"TEXTO: {text}\nCATEGORIA: {label}"
|
| 691 |
-
else:
|
| 692 |
-
# Comportamento legado (9 datasets base): concatenação simples
|
| 693 |
-
full_text = f"{text}\n{label}"
|
| 694 |
-
|
| 695 |
-
return ProcessedSample(
|
| 696 |
-
raw_text=full_text,
|
| 697 |
-
dataset_name=dataset_name,
|
| 698 |
-
sample_idx=idx,
|
| 699 |
-
text_field=text_fields[0] if text_fields else "",
|
| 700 |
-
label_text=label,
|
| 701 |
-
)
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
def _load_dataset_streaming(
|
| 705 |
-
dataset_name: str,
|
| 706 |
-
split: str = "train",
|
| 707 |
-
config: Optional[str] = None,
|
| 708 |
-
hf_token: Optional[str] = None,
|
| 709 |
-
):
|
| 710 |
-
"""Carrega dataset em modo streaming.
|
| 711 |
-
|
| 712 |
-
BUG FIX V13.9.1: Removido trust_remote_code (deprecated).
|
| 713 |
-
Se o dataset não carregar com split especificado, tenta 'train' como fallback.
|
| 714 |
-
BUG FIX V13.9.2: Adicionado fallback para datasets com script (carolina-c4ai).
|
| 715 |
-
"""
|
| 716 |
-
from datasets import load_dataset
|
| 717 |
-
|
| 718 |
-
# Lista de splits para tentar, em ordem
|
| 719 |
-
splits_to_try = [split]
|
| 720 |
-
if split != "train":
|
| 721 |
-
splits_to_try.append("train")
|
| 722 |
-
# Adiciona outros splits comuns
|
| 723 |
-
for s in ["test", "validation", "cot", "tir"]:
|
| 724 |
-
if s not in splits_to_try:
|
| 725 |
-
splits_to_try.append(s)
|
| 726 |
-
|
| 727 |
-
fmt = DATASET_FORMATS.get(dataset_name, {})
|
| 728 |
-
fallback_name = fmt.get("fallback")
|
| 729 |
-
datasets_to_try = [dataset_name]
|
| 730 |
-
if fallback_name:
|
| 731 |
-
datasets_to_try.append(fallback_name)
|
| 732 |
-
|
| 733 |
-
for try_ds_name in datasets_to_try:
|
| 734 |
-
for try_split in splits_to_try:
|
| 735 |
-
try:
|
| 736 |
-
if config:
|
| 737 |
-
ds = load_dataset(
|
| 738 |
-
try_ds_name, config, split=try_split, streaming=True,
|
| 739 |
-
token=hf_token,
|
| 740 |
-
)
|
| 741 |
-
else:
|
| 742 |
-
ds = load_dataset(
|
| 743 |
-
try_ds_name, split=try_split, streaming=True,
|
| 744 |
-
token=hf_token,
|
| 745 |
-
)
|
| 746 |
-
logger.info(f"Loaded {try_ds_name} split={try_split}")
|
| 747 |
-
return ds
|
| 748 |
-
except Exception as e:
|
| 749 |
-
err_str = str(e).lower()
|
| 750 |
-
# If "Bad split" error, try next split
|
| 751 |
-
if "bad split" in err_str or "available splits" in err_str:
|
| 752 |
-
logger.info(f" Split {try_split} not available for {try_ds_name}, trying next")
|
| 753 |
-
continue
|
| 754 |
-
# If "scripts no longer supported", try next dataset (fallback)
|
| 755 |
-
if "scripts are no longer supported" in err_str and fallback_name:
|
| 756 |
-
logger.info(f" {try_ds_name} uses script — trying fallback {fallback_name}")
|
| 757 |
-
break
|
| 758 |
-
# For other errors, log and try next split
|
| 759 |
-
logger.warning(f"Failed to load {try_ds_name} split={try_split}: {str(e)[:120]}")
|
| 760 |
-
continue
|
| 761 |
-
|
| 762 |
-
logger.warning(f"All split attempts failed for {dataset_name} (will be skipped)")
|
| 763 |
-
return None
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
def stream_dataset(
|
| 767 |
-
dataset_name: str,
|
| 768 |
-
max_samples: int = 500,
|
| 769 |
-
hf_token: Optional[str] = None,
|
| 770 |
-
) -> Iterator[ProcessedSample]:
|
| 771 |
-
"""Faz streaming de um dataset, retornando até max_samples amostras.
|
| 772 |
-
|
| 773 |
-
Args:
|
| 774 |
-
dataset_name: nome do dataset no HuggingFace
|
| 775 |
-
max_samples: número máximo de amostras a retornar
|
| 776 |
-
hf_token: token HF opcional
|
| 777 |
-
|
| 778 |
-
Yields:
|
| 779 |
-
ProcessedSample
|
| 780 |
-
"""
|
| 781 |
-
fmt = DATASET_FORMATS.get(dataset_name)
|
| 782 |
-
if fmt is None:
|
| 783 |
-
logger.error(f"Unknown dataset: {dataset_name}")
|
| 784 |
-
return
|
| 785 |
-
|
| 786 |
-
# ── V13.9.2-carolina: bypass do script Python ─────────────────────────
|
| 787 |
-
# O dataset carolina-c4ai/corpus-carolina usa um script Python como loader,
|
| 788 |
-
# que não é suportado por datasets 5.0+. Streamamos os XML.gz diretamente.
|
| 789 |
-
if dataset_name == "carolina-c4ai/corpus-carolina":
|
| 790 |
-
logger.info(f"Carolina: usando carregador direto (bypass do script Python)")
|
| 791 |
-
count = 0
|
| 792 |
-
try:
|
| 793 |
-
for raw_sample in _stream_carolina_direct(
|
| 794 |
-
max_samples=max_samples, hf_token=hf_token
|
| 795 |
-
):
|
| 796 |
-
processed = _normalize_sample(raw_sample, dataset_name, count)
|
| 797 |
-
if processed is not None:
|
| 798 |
-
yield processed
|
| 799 |
-
count += 1
|
| 800 |
-
except Exception as e:
|
| 801 |
-
logger.warning(f"Carolina direct stream error at sample {count}: {e}")
|
| 802 |
-
return
|
| 803 |
-
logger.info(f"Streamed {count} samples from {dataset_name} (direct XML)")
|
| 804 |
-
return
|
| 805 |
-
|
| 806 |
-
# ── V13.9.2-finetune-v2: loaders alternativos para parquets grandes ───
|
| 807 |
-
# Alguns datasets (cnmoro, strak2005/bratao) têm parquets tão grandes que
|
| 808 |
-
# datasets.load_dataset(streaming=True) demora minutos para materializar
|
| 809 |
-
# a primeira amostra. Usamos loaders alternativos especificados no campo
|
| 810 |
-
# "loader" do formato.
|
| 811 |
-
loader = fmt.get("loader")
|
| 812 |
-
if loader == "rows_api":
|
| 813 |
-
logger.info(f"{dataset_name}: usando rows_api loader (datasets-server HTTP)")
|
| 814 |
-
count = 0
|
| 815 |
-
try:
|
| 816 |
-
for raw_sample in _stream_via_rows_api(
|
| 817 |
-
dataset_name, max_samples=max_samples, hf_token=hf_token,
|
| 818 |
-
config=fmt.get("config"),
|
| 819 |
-
split=fmt.get("split", "train"),
|
| 820 |
-
):
|
| 821 |
-
processed = _normalize_sample(raw_sample, dataset_name, count)
|
| 822 |
-
if processed is not None:
|
| 823 |
-
yield processed
|
| 824 |
-
count += 1
|
| 825 |
-
except Exception as e:
|
| 826 |
-
logger.warning(f"rows_api stream error at sample {count}: {e}")
|
| 827 |
-
return
|
| 828 |
-
logger.info(f"Streamed {count} samples from {dataset_name} (rows_api)")
|
| 829 |
-
return
|
| 830 |
-
|
| 831 |
-
if loader == "parquet_first_shard":
|
| 832 |
-
logger.info(f"{dataset_name}: usando parquet_first_shard loader")
|
| 833 |
-
count = 0
|
| 834 |
-
try:
|
| 835 |
-
for raw_sample in _stream_via_parquet_first_shard(
|
| 836 |
-
dataset_name, max_samples=max_samples, hf_token=hf_token
|
| 837 |
-
):
|
| 838 |
-
processed = _normalize_sample(raw_sample, dataset_name, count)
|
| 839 |
-
if processed is not None:
|
| 840 |
-
yield processed
|
| 841 |
-
count += 1
|
| 842 |
-
except Exception as e:
|
| 843 |
-
logger.warning(f"parquet_shard stream error at sample {count}: {e}")
|
| 844 |
-
# Tenta fallback (ex: strak2005 -> bratao)
|
| 845 |
-
fallback_name = fmt.get("fallback")
|
| 846 |
-
if fallback_name and fallback_name != dataset_name:
|
| 847 |
-
logger.info(f"Tentando fallback: {fallback_name}")
|
| 848 |
-
fallback_fmt = DATASET_FORMATS.get(fallback_name, {})
|
| 849 |
-
# Recursão respeitando o loader do fallback
|
| 850 |
-
yield from stream_dataset(
|
| 851 |
-
fallback_name, max_samples=max_samples, hf_token=hf_token
|
| 852 |
-
)
|
| 853 |
-
return
|
| 854 |
-
logger.info(f"Streamed {count} samples from {dataset_name} (parquet_first_shard)")
|
| 855 |
-
return
|
| 856 |
-
|
| 857 |
-
ds = _load_dataset_streaming(
|
| 858 |
-
dataset_name,
|
| 859 |
-
split=fmt.get("split", "train"),
|
| 860 |
-
config=fmt.get("config"),
|
| 861 |
-
hf_token=hf_token,
|
| 862 |
-
)
|
| 863 |
-
if ds is None:
|
| 864 |
-
return
|
| 865 |
-
|
| 866 |
-
count = 0
|
| 867 |
-
try:
|
| 868 |
-
for idx, sample in enumerate(ds):
|
| 869 |
-
if count >= max_samples:
|
| 870 |
-
break
|
| 871 |
-
processed = _normalize_sample(sample, dataset_name, idx)
|
| 872 |
-
if processed is not None:
|
| 873 |
-
yield processed
|
| 874 |
-
count += 1
|
| 875 |
-
except Exception as e:
|
| 876 |
-
logger.warning(f"Error streaming {dataset_name} at sample {idx}: {e}")
|
| 877 |
-
return
|
| 878 |
-
|
| 879 |
-
logger.info(f"Streamed {count} samples from {dataset_name}")
|
| 880 |
-
|
| 881 |
-
|
| 882 |
-
def stream_all_datasets(
|
| 883 |
-
datasets: Optional[List[str]] = None,
|
| 884 |
-
max_samples_per_dataset: int = 500,
|
| 885 |
-
hf_token: Optional[str] = None,
|
| 886 |
-
) -> Iterator[ProcessedSample]:
|
| 887 |
-
"""Faz streaming sequencial de múltiplos datasets.
|
| 888 |
-
|
| 889 |
-
Args:
|
| 890 |
-
datasets: lista de nomes (default: DEFAULT_DATASETS)
|
| 891 |
-
max_samples_per_dataset: limite por dataset
|
| 892 |
-
hf_token: token HF opcional
|
| 893 |
-
|
| 894 |
-
Yields:
|
| 895 |
-
ProcessedSample
|
| 896 |
-
"""
|
| 897 |
-
if datasets is None:
|
| 898 |
-
datasets = DEFAULT_DATASETS
|
| 899 |
-
|
| 900 |
-
for ds_name in datasets:
|
| 901 |
-
logger.info(f"Starting dataset: {ds_name}")
|
| 902 |
-
yield from stream_dataset(
|
| 903 |
-
ds_name,
|
| 904 |
-
max_samples=max_samples_per_dataset,
|
| 905 |
-
hf_token=hf_token,
|
| 906 |
-
)
|
| 907 |
-
|
| 908 |
-
|
| 909 |
-
def collate_samples_to_tensors(
|
| 910 |
-
samples: List[ProcessedSample],
|
| 911 |
-
tokenizer,
|
| 912 |
-
max_seq_len: int = 64,
|
| 913 |
-
pad_token_id: int = 0,
|
| 914 |
-
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 915 |
-
"""Converte lista de ProcessedSample em tensors para treino.
|
| 916 |
-
|
| 917 |
-
BUG FIX V13.9.1: Tokenizer.encode() retorna 'Encoding' object, não list.
|
| 918 |
-
Use .ids attribute to get token IDs.
|
| 919 |
-
|
| 920 |
-
Returns:
|
| 921 |
-
input_ids: (B, T)
|
| 922 |
-
attention_mask: (B, T)
|
| 923 |
-
labels: (B, T) — same as input_ids (LM training)
|
| 924 |
-
"""
|
| 925 |
-
batch_input_ids = []
|
| 926 |
-
batch_attn_mask = []
|
| 927 |
-
|
| 928 |
-
for sample in samples:
|
| 929 |
-
# Tokenize
|
| 930 |
-
encoding = tokenizer.encode(sample.raw_text, add_special_tokens=True)
|
| 931 |
-
|
| 932 |
-
# BUG FIX: Handle both `tokenizers.Encoding` and `transformers` tokenizer outputs
|
| 933 |
-
if hasattr(encoding, 'ids'):
|
| 934 |
-
tokens = encoding.ids # tokenizers library
|
| 935 |
-
elif isinstance(encoding, list):
|
| 936 |
-
tokens = encoding # already a list of ints
|
| 937 |
-
elif hasattr(encoding, 'input_ids'):
|
| 938 |
-
tokens = encoding.input_ids # transformers BatchEncoding
|
| 939 |
-
else:
|
| 940 |
-
tokens = list(encoding) # fallback
|
| 941 |
-
|
| 942 |
-
# Truncate to max_seq_len
|
| 943 |
-
if len(tokens) > max_seq_len:
|
| 944 |
-
tokens = tokens[:max_seq_len]
|
| 945 |
-
# Pad
|
| 946 |
-
attn_mask = [1] * len(tokens) + [0] * (max_seq_len - len(tokens))
|
| 947 |
-
tokens = list(tokens) + [pad_token_id] * (max_seq_len - len(tokens))
|
| 948 |
-
batch_input_ids.append(tokens)
|
| 949 |
-
batch_attn_mask.append(attn_mask)
|
| 950 |
-
|
| 951 |
-
input_ids = torch.tensor(batch_input_ids, dtype=torch.long)
|
| 952 |
-
attention_mask = torch.tensor(batch_attn_mask, dtype=torch.long)
|
| 953 |
-
labels = input_ids.clone()
|
| 954 |
-
|
| 955 |
-
return input_ids, attention_mask, labels
|
| 956 |
-
|
| 957 |
-
|
| 958 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 959 |
-
# Self-test (NÃO enviar para HuggingFace)
|
| 960 |
-
# ═══════════════════════════════════════════════════════════════════════════
|
| 961 |
-
if __name__ == "__main__":
|
| 962 |
-
print("=== Streaming Datasets V13.9.1 Self-Test ===\n")
|
| 963 |
-
|
| 964 |
-
# Test 1: Test field extraction
|
| 965 |
-
print("Test 1: Field extraction")
|
| 966 |
-
sample_chat = {
|
| 967 |
-
"messages": [
|
| 968 |
-
{"role": "user", "content": "Olá"},
|
| 969 |
-
{"role": "assistant", "content": "Como posso ajudar?"},
|
| 970 |
-
]
|
| 971 |
-
}
|
| 972 |
-
text = _extract_field(sample_chat, ["messages", "text"])
|
| 973 |
-
print(f" Chat text: {text!r}")
|
| 974 |
-
assert text and "Olá" in text, "Field extraction failed for chat"
|
| 975 |
-
|
| 976 |
-
sample_text = {"text": "Hello world"}
|
| 977 |
-
text = _extract_field(sample_text, ["text", "content"])
|
| 978 |
-
assert text == "Hello world"
|
| 979 |
-
print(f" Text field: {text!r}")
|
| 980 |
-
print(f" OK\n")
|
| 981 |
-
|
| 982 |
-
# Test 2: Test list of datasets
|
| 983 |
-
print("Test 2: Dataset list")
|
| 984 |
-
print(f" Configured datasets ({len(DEFAULT_DATASETS)}):")
|
| 985 |
-
for i, ds in enumerate(DEFAULT_DATASETS, 1):
|
| 986 |
-
print(f" {i}. {ds}")
|
| 987 |
-
assert len(DEFAULT_DATASETS) == 14
|
| 988 |
-
# V6.5-V2: verifica sequência CONHECIMENTO (8 datasets) + PUNICAO (1)
|
| 989 |
-
assert len(CONHECIMENTO_DATASETS_V2) == 8
|
| 990 |
-
assert PUNICAO_DATASET_V2 == "BrunoN-Dev/corpus-ptbr-v1"
|
| 991 |
-
print(f" V2 CONHECIMENTO datasets: {len(CONHECIMENTO_DATASETS_V2)}")
|
| 992 |
-
print(f" V2 PUNICAO dataset: {PUNICAO_DATASET_V2}")
|
| 993 |
-
print(f" OK\n")
|
| 994 |
-
|
| 995 |
-
print("=== ALL STREAMING TESTS PASSED ===")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
train_v6_5_v2.py
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
train_v6_5_v2_punicão_only.py
DELETED
|
@@ -1,556 +0,0 @@
|
|
| 1 |
-
"""train_v6_5_v2_punicão_only.py — V6.5-V2-dynamic FASE 2 only.
|
| 2 |
-
|
| 3 |
-
Carrega o estado salvo da FASE 1 (CONHECIMENTO) e executa apenas a FASE 2
|
| 4 |
-
(TREINAMENTO COM PUNIÇÃO) sobre BrunoN-Dev/corpus-ptbr-v1.
|
| 5 |
-
|
| 6 |
-
User requirement: "FASE2 TREINAMENTO (meta mínima 1000 samples ou mais)
|
| 7 |
-
COM PUNIÇÃO ATIVA para 'BrunoN-Dev/corpus-ptbr-v1' de 100 em 100 samples"
|
| 8 |
-
|
| 9 |
-
Este script permite continuar o treinamento a partir do estado salvo sem
|
| 10 |
-
precisar re-executar a FASE 1.
|
| 11 |
-
"""
|
| 12 |
-
from __future__ import annotations
|
| 13 |
-
|
| 14 |
-
import gc
|
| 15 |
-
import json
|
| 16 |
-
import logging
|
| 17 |
-
import os
|
| 18 |
-
import re
|
| 19 |
-
import shutil
|
| 20 |
-
import sys
|
| 21 |
-
import time
|
| 22 |
-
import traceback
|
| 23 |
-
from datetime import datetime
|
| 24 |
-
from pathlib import Path
|
| 25 |
-
from typing import Any, Dict, List, Optional
|
| 26 |
-
|
| 27 |
-
# ============================================================================
|
| 28 |
-
# 0. Paths e logging
|
| 29 |
-
# ============================================================================
|
| 30 |
-
PROJECT_ROOT = Path("/home/z/my-project")
|
| 31 |
-
BIGRU_ROOT = PROJECT_ROOT / "BiGRU_T_version"
|
| 32 |
-
SRC_ROOT = BIGRU_ROOT / "src"
|
| 33 |
-
DOWNLOAD_DIR = PROJECT_ROOT / "download"
|
| 34 |
-
DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
| 35 |
-
|
| 36 |
-
PUNICAO_REPORT_PATH = BIGRU_ROOT / "v6_5_v2_punicão_report.json"
|
| 37 |
-
PUNICAO_STATES_PATH = BIGRU_ROOT / "v6_5_v2_punicão_model_states.pt"
|
| 38 |
-
PUNICAO_UPLOAD_REPORT_PATH = BIGRU_ROOT / "v6_5_v2_punicão_upload_report.json"
|
| 39 |
-
|
| 40 |
-
# Estado salvo da FASE 1
|
| 41 |
-
FASE1_STATES_PATH = BIGRU_ROOT / "v6_5_v2_model_states.pt"
|
| 42 |
-
|
| 43 |
-
logging.basicConfig(
|
| 44 |
-
level=logging.INFO,
|
| 45 |
-
format="[%(asctime)s] [%(levelname)s] %(message)s",
|
| 46 |
-
datefmt="%H:%M:%S",
|
| 47 |
-
)
|
| 48 |
-
logger = logging.getLogger("train_v6_5_v2_punicão")
|
| 49 |
-
|
| 50 |
-
# ============================================================================
|
| 51 |
-
# 1. ATIVAR xeon_runtime.py
|
| 52 |
-
# ============================================================================
|
| 53 |
-
os.environ["V65_ENABLE_STREAMING"] = "1"
|
| 54 |
-
sys.path.insert(0, str(SRC_ROOT))
|
| 55 |
-
|
| 56 |
-
from bigru_t.utils.xeon_runtime import ( # noqa: E402
|
| 57 |
-
optimize_xeon_environment,
|
| 58 |
-
benchmark_fp16_matmul,
|
| 59 |
-
)
|
| 60 |
-
N_CORES = optimize_xeon_environment(verbose=False)
|
| 61 |
-
logger.info(f"[V6.5-V2-punicão] Xeon cores: {N_CORES}")
|
| 62 |
-
|
| 63 |
-
# ============================================================================
|
| 64 |
-
# 2. Configurações (mesmas do FASE 1, para compatibilidade de estado)
|
| 65 |
-
# ============================================================================
|
| 66 |
-
BATCH_SIZE = 16
|
| 67 |
-
MAX_SEQ_LEN = 8
|
| 68 |
-
HIDDEN_DIM = 256
|
| 69 |
-
VOCAB_SIZE = 4096
|
| 70 |
-
SOM_GRID = (6, 6, 6, 4)
|
| 71 |
-
T_MAX = 10000
|
| 72 |
-
N_START = 10
|
| 73 |
-
LAMBDA_EWC = 0.02
|
| 74 |
-
ALPHA0 = 0.1
|
| 75 |
-
SIGMA0 = 1.5
|
| 76 |
-
DIM_CHOICE = "y"
|
| 77 |
-
|
| 78 |
-
N_HYPOTHESES = 8
|
| 79 |
-
MAX_N_HYPOTHESES = 16
|
| 80 |
-
MIN_N_HYPOTHESES = 4
|
| 81 |
-
N_TRIALS = 3
|
| 82 |
-
MIN_N_TRIALS = 1
|
| 83 |
-
MAX_N_TRIALS = 6
|
| 84 |
-
HYP_TRAIN_STEPS = 20
|
| 85 |
-
MIN_HYP_TRAIN_STEPS = 5
|
| 86 |
-
MAX_HYP_TRAIN_STEPS = 60
|
| 87 |
-
HYP_LR = 1e-4
|
| 88 |
-
HYP_HIDDEN_DIM = 128
|
| 89 |
-
LOSS_HISTORY_WINDOW = 8
|
| 90 |
-
PUNISHMENT_WINDOW = 12
|
| 91 |
-
|
| 92 |
-
STREAM_BATCH_SIZE = 100
|
| 93 |
-
META_MINIMA_PUNICAO = 1000
|
| 94 |
-
MAX_SAMPLES_PUNICAO = 1000
|
| 95 |
-
MAX_BUFFER_SIZE = 256
|
| 96 |
-
|
| 97 |
-
INTER_BATCH_PAUSE_S = 0.3
|
| 98 |
-
INTER_STREAM_BATCH_PAUSE_S = 0.5
|
| 99 |
-
|
| 100 |
-
PUNICAO_DATASET = "BrunoN-Dev/corpus-ptbr-v1"
|
| 101 |
-
|
| 102 |
-
DATASET_LABEL_STRINGS: Dict[str, Dict[int, str]] = {
|
| 103 |
-
"BrunoN-Dev/corpus-ptbr-v1": {0: "short_text", 1: "long_text"},
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
# ============================================================================
|
| 107 |
-
# 3. Import KLS V2
|
| 108 |
-
# ============================================================================
|
| 109 |
-
from bigru_t.model.kohonen_learning_system import ( # noqa: E402
|
| 110 |
-
KohonenLearningSystemV2,
|
| 111 |
-
)
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
def get_process_rss_mb() -> float:
|
| 115 |
-
try:
|
| 116 |
-
import resource
|
| 117 |
-
return float(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss) / 1024.0
|
| 118 |
-
except Exception:
|
| 119 |
-
return 0.0
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
def get_cgroup_memory_limit_mb() -> Optional[float]:
|
| 123 |
-
try:
|
| 124 |
-
with open("/sys/fs/cgroup/memory.max", "r") as f:
|
| 125 |
-
return float(int(f.read().strip())) / 1e6
|
| 126 |
-
except Exception:
|
| 127 |
-
return None
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
def aggressive_memory_cleanup() -> None:
|
| 131 |
-
gc.collect()
|
| 132 |
-
gc.collect()
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
def make_label(text: str, dataset_name: Optional[str] = None) -> int:
|
| 136 |
-
if dataset_name == "BrunoN-Dev/corpus-ptbr-v1":
|
| 137 |
-
return len(text.split()) % 2
|
| 138 |
-
return 1 if len(text.split()) % 2 == 0 else 0
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
def stream_dataset_in_chunks(dataset_name, chunk_size, max_total, hf_token, timeout_per_chunk=120):
|
| 142 |
-
from bigru_t.data.streaming_datasets import stream_dataset
|
| 143 |
-
yielded_total = 0
|
| 144 |
-
chunk_idx = 0
|
| 145 |
-
while yielded_total < max_total:
|
| 146 |
-
remaining = max_total - yielded_total
|
| 147 |
-
this_chunk_target = min(chunk_size, remaining)
|
| 148 |
-
seed_offset = yielded_total
|
| 149 |
-
chunk: List[str] = []
|
| 150 |
-
t_start = time.time()
|
| 151 |
-
try:
|
| 152 |
-
for sample in stream_dataset(
|
| 153 |
-
dataset_name,
|
| 154 |
-
max_samples=this_chunk_target + seed_offset + 5,
|
| 155 |
-
hf_token=hf_token,
|
| 156 |
-
):
|
| 157 |
-
if time.time() - t_start > timeout_per_chunk:
|
| 158 |
-
break
|
| 159 |
-
if sample.raw_text and len(sample.raw_text.strip()) > 0:
|
| 160 |
-
if seed_offset > 0:
|
| 161 |
-
seed_offset -= 1
|
| 162 |
-
continue
|
| 163 |
-
chunk.append(sample.raw_text.strip()[:200])
|
| 164 |
-
if len(chunk) >= this_chunk_target:
|
| 165 |
-
break
|
| 166 |
-
except Exception as e:
|
| 167 |
-
logger.warning(f"Stream error: {e}")
|
| 168 |
-
if chunk:
|
| 169 |
-
yield chunk
|
| 170 |
-
yielded_total += len(chunk)
|
| 171 |
-
chunk_idx += 1
|
| 172 |
-
break
|
| 173 |
-
if not chunk:
|
| 174 |
-
break
|
| 175 |
-
yield chunk
|
| 176 |
-
yielded_total += len(chunk)
|
| 177 |
-
chunk_idx += 1
|
| 178 |
-
logger.info(f"[PUNICAO] {dataset_name} chunk {chunk_idx}: {len(chunk)} (total: {yielded_total}/{max_total})")
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
def save_punicão_states(kls, reason, step, total_samples):
|
| 182 |
-
import torch
|
| 183 |
-
try:
|
| 184 |
-
state = {
|
| 185 |
-
"_meta": {
|
| 186 |
-
"reason": str(reason),
|
| 187 |
-
"step": int(step),
|
| 188 |
-
"total_samples": int(total_samples),
|
| 189 |
-
"timestamp": datetime.now().isoformat(),
|
| 190 |
-
"version": "V6.5-V2-dynamic-punicão",
|
| 191 |
-
"loaded_from_fase1": str(FASE1_STATES_PATH),
|
| 192 |
-
},
|
| 193 |
-
"som_weights": kls.som.weights.detach().clone().cpu(),
|
| 194 |
-
"som_old_weights_w": (
|
| 195 |
-
kls.som.old_weights_w.detach().clone().cpu()
|
| 196 |
-
if kls.som.old_weights_w is not None else None
|
| 197 |
-
),
|
| 198 |
-
"som_fisher_w": (
|
| 199 |
-
kls.som.fisher_w.detach().clone().cpu()
|
| 200 |
-
if kls.som.fisher_w is not None else None
|
| 201 |
-
),
|
| 202 |
-
"embedding_state": {"weight": kls.embedding.weight.detach().clone().cpu()},
|
| 203 |
-
"classifier_state": (
|
| 204 |
-
kls.classifier.state_dict() if kls.classifier is not None else None
|
| 205 |
-
),
|
| 206 |
-
"hypothesis_ensemble_state": kls.hypothesis_ensemble.state_dict(),
|
| 207 |
-
"delta_scale": kls.delta_scale.detach().clone().cpu(),
|
| 208 |
-
"v2_metrics": kls.get_v2_metrics(),
|
| 209 |
-
"label_registry": kls.label_registry,
|
| 210 |
-
"adaptation_log": kls.get_adaptation_log(),
|
| 211 |
-
}
|
| 212 |
-
torch.save(state, PUNICAO_STATES_PATH)
|
| 213 |
-
size_mb = PUNICAO_STATES_PATH.stat().st_size / 1e6
|
| 214 |
-
logger.info(f"[PUNICAO] States saved: {PUNICAO_STATES_PATH} ({size_mb:.1f}MB)")
|
| 215 |
-
return {"saved": True, "path": str(PUNICAO_STATES_PATH), "size_mb": float(size_mb)}
|
| 216 |
-
except Exception as e:
|
| 217 |
-
logger.error(f"[PUNICAO] Save failed: {e}")
|
| 218 |
-
return {"saved": False, "error": str(e)}
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
HF_REPO_ID = "PowerMachine/BiGRU_T_version"
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
def upload_punicão_to_hf(hf_token, files_to_upload):
|
| 225 |
-
if not hf_token:
|
| 226 |
-
return {"uploaded": False, "reason": "no_hf_token"}
|
| 227 |
-
try:
|
| 228 |
-
from huggingface_hub import HfApi, whoami, CommitOperationAdd
|
| 229 |
-
except ImportError as e:
|
| 230 |
-
return {"uploaded": False, "reason": f"import_error: {e}"}
|
| 231 |
-
api = HfApi(token=hf_token)
|
| 232 |
-
try:
|
| 233 |
-
whoami(token=hf_token)
|
| 234 |
-
except Exception as e:
|
| 235 |
-
return {"uploaded": False, "reason": f"auth_failed: {e}"}
|
| 236 |
-
operations = []
|
| 237 |
-
for src in files_to_upload:
|
| 238 |
-
if not src.exists():
|
| 239 |
-
continue
|
| 240 |
-
operations.append(CommitOperationAdd(
|
| 241 |
-
path_in_repo=src.name,
|
| 242 |
-
path_or_fileobj=str(src),
|
| 243 |
-
))
|
| 244 |
-
if not operations:
|
| 245 |
-
return {"uploaded": False, "reason": "no_files"}
|
| 246 |
-
try:
|
| 247 |
-
commit_info = api.create_commit(
|
| 248 |
-
repo_id=HF_REPO_ID,
|
| 249 |
-
repo_type="model",
|
| 250 |
-
operations=operations,
|
| 251 |
-
commit_message=f"V6.5-V2-dynamic: FASE 2 PUNIÇÃO upload ({len(operations)} files)",
|
| 252 |
-
)
|
| 253 |
-
return {
|
| 254 |
-
"uploaded": True,
|
| 255 |
-
"n_files": len(operations),
|
| 256 |
-
"commit_url": str(commit_info.commit_url) if hasattr(commit_info, "commit_url") else str(commit_info),
|
| 257 |
-
}
|
| 258 |
-
except Exception as e:
|
| 259 |
-
logger.error(f"[PUNICAO] HF upload failed: {e}")
|
| 260 |
-
return {"uploaded": False, "reason": str(e)}
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
def main() -> int:
|
| 264 |
-
import torch
|
| 265 |
-
|
| 266 |
-
if not FASE1_STATES_PATH.exists():
|
| 267 |
-
logger.error(f"[PUNICAO] FASE 1 states not found: {FASE1_STATES_PATH}")
|
| 268 |
-
return 1
|
| 269 |
-
|
| 270 |
-
logger.info(f"[PUNICAO] Loading FASE 1 states from: {FASE1_STATES_PATH}")
|
| 271 |
-
fase1_state = torch.load(FASE1_STATES_PATH, map_location="cpu", weights_only=False)
|
| 272 |
-
logger.info(f"[PUNICAO] FASE 1 state loaded. Reason: {fase1_state['_meta']['reason']}")
|
| 273 |
-
logger.info(f"[PUNICAO] FASE 1 total_samples: {fase1_state['_meta']['total_samples']}")
|
| 274 |
-
|
| 275 |
-
# Construct KLS V2 with same config as FASE 1
|
| 276 |
-
kls = KohonenLearningSystemV2(
|
| 277 |
-
vocab_size=VOCAB_SIZE, hidden_dim=HIDDEN_DIM, seq_len=MAX_SEQ_LEN,
|
| 278 |
-
som_grid=SOM_GRID, alpha0=ALPHA0, sigma0=SIGMA0,
|
| 279 |
-
lambda_ewc=LAMBDA_EWC, N_start=N_START, dim_choice=DIM_CHOICE,
|
| 280 |
-
hypothesis_hidden=[512, 256, 128, 64, 32, 16, 8], T_max=T_MAX,
|
| 281 |
-
enable_vqvae2=False, enable_reasoning=True, enable_w8a8=False,
|
| 282 |
-
enable_attention=True, attention_n_heads=8,
|
| 283 |
-
n_hypotheses=N_HYPOTHESES, max_n_hypotheses=MAX_N_HYPOTHESES,
|
| 284 |
-
min_n_hypotheses=MIN_N_HYPOTHESES, n_trials=N_TRIALS,
|
| 285 |
-
min_n_trials=MIN_N_TRIALS, max_n_trials=MAX_N_TRIALS,
|
| 286 |
-
hyp_train_steps=HYP_TRAIN_STEPS, min_hyp_train_steps=MIN_HYP_TRAIN_STEPS,
|
| 287 |
-
max_hyp_train_steps=MAX_HYP_TRAIN_STEPS, hyp_lr=HYP_LR,
|
| 288 |
-
hyp_hidden_dim=HYP_HIDDEN_DIM,
|
| 289 |
-
loss_history_window=LOSS_HISTORY_WINDOW,
|
| 290 |
-
punishment_window=PUNISHMENT_WINDOW,
|
| 291 |
-
)
|
| 292 |
-
logger.info(f"[PUNICAO] KLS V2 initialized (fresh)")
|
| 293 |
-
|
| 294 |
-
# Load FASE 1 state into KLS
|
| 295 |
-
try:
|
| 296 |
-
# SOM weights
|
| 297 |
-
kls.som.weights = nn.Parameter(fase1_state["som_weights"].clone())
|
| 298 |
-
if fase1_state.get("som_old_weights_w") is not None:
|
| 299 |
-
kls.som.old_weights_w = fase1_state["som_old_weights_w"].clone()
|
| 300 |
-
if fase1_state.get("som_fisher_w") is not None:
|
| 301 |
-
kls.som.fisher_w = fase1_state["som_fisher_w"].clone()
|
| 302 |
-
# Embedding
|
| 303 |
-
if "embedding_state" in fase1_state:
|
| 304 |
-
kls.embedding.weight.data = fase1_state["embedding_state"]["weight"].clone()
|
| 305 |
-
# Classifier
|
| 306 |
-
if fase1_state.get("classifier_state") and kls.classifier is not None:
|
| 307 |
-
kls.classifier.load_state_dict(fase1_state["classifier_state"])
|
| 308 |
-
kls.classifier_trained = True
|
| 309 |
-
# Hypothesis ensemble
|
| 310 |
-
if "hypothesis_ensemble_state" in fase1_state:
|
| 311 |
-
kls.hypothesis_ensemble.load_state_dict(fase1_state["hypothesis_ensemble_state"])
|
| 312 |
-
# Delta scale
|
| 313 |
-
if "delta_scale" in fase1_state:
|
| 314 |
-
kls.delta_scale.data = fase1_state["delta_scale"].clone()
|
| 315 |
-
# Label registry
|
| 316 |
-
if "label_registry" in fase1_state:
|
| 317 |
-
kls.label_registry = fase1_state["label_registry"]
|
| 318 |
-
# Mark training as ready (FASE 1 already trained the SOM)
|
| 319 |
-
kls.training_ready = True
|
| 320 |
-
logger.info(f"[PUNICAO] FASE 1 state loaded into KLS successfully")
|
| 321 |
-
except Exception as e:
|
| 322 |
-
logger.error(f"[PUNICAO] Failed to load FASE 1 state: {e}")
|
| 323 |
-
traceback.print_exc()
|
| 324 |
-
return 1
|
| 325 |
-
|
| 326 |
-
# Fit tokenizer with PT-BR corpus
|
| 327 |
-
corpus_inicial = [
|
| 328 |
-
"o gato dorme na cama", "a casa eh azul", "ele corre rapido",
|
| 329 |
-
"luva de pedreiro tavila", "lula reserva valor",
|
| 330 |
-
"amazonas forca tarefa vitimas",
|
| 331 |
-
"texto curto", "texto longo com muitas palavras para teste",
|
| 332 |
-
]
|
| 333 |
-
kls.tokenizer.fit(corpus_inicial)
|
| 334 |
-
logger.info(f"[PUNICAO] Tokenizer fitted")
|
| 335 |
-
|
| 336 |
-
hf_token = os.environ.get("HF_TOKEN")
|
| 337 |
-
if not hf_token:
|
| 338 |
-
logger.warning("[PUNICAO] HF_TOKEN not set")
|
| 339 |
-
else:
|
| 340 |
-
logger.info(f"[PUNICAO] HF_TOKEN set: {hf_token[:8]}...")
|
| 341 |
-
|
| 342 |
-
# ========================================================================
|
| 343 |
-
# FASE 2 — TREINAMENTO COM PUNIÇÃO
|
| 344 |
-
# ========================================================================
|
| 345 |
-
logger.info("\n" + "=" * 80)
|
| 346 |
-
logger.info("[PUNICAO] FASE 2 — TREINAMENTO COM PUNIÇÃO ATIVA")
|
| 347 |
-
logger.info("=" * 80)
|
| 348 |
-
logger.info(f" Dataset: {PUNICAO_DATASET}")
|
| 349 |
-
logger.info(f" Meta mínima: {META_MINIMA_PUNICAO}")
|
| 350 |
-
logger.info(f" Stream batch: {STREAM_BATCH_SIZE}")
|
| 351 |
-
logger.info(f" n_hypotheses: {kls.n_hypotheses} (max {kls.max_n_hypotheses})")
|
| 352 |
-
logger.info(f" n_trials: {kls.n_trials}")
|
| 353 |
-
logger.info(f" hyp_train_steps: {kls.hyp_train_steps}")
|
| 354 |
-
logger.info("=" * 80 + "\n")
|
| 355 |
-
|
| 356 |
-
step = 0
|
| 357 |
-
t_start = time.time()
|
| 358 |
-
samples_processed = 0
|
| 359 |
-
chunk_idx_global = 0
|
| 360 |
-
punishment_log: List[Dict[str, Any]] = []
|
| 361 |
-
hypotheses_log: List[Dict[str, Any]] = []
|
| 362 |
-
delta_applications_log: List[Dict[str, Any]] = []
|
| 363 |
-
accuracies_log: List[Dict[str, Any]] = []
|
| 364 |
-
mem_critical_stopped = False
|
| 365 |
-
cg_limit = get_cgroup_memory_limit_mb()
|
| 366 |
-
|
| 367 |
-
try:
|
| 368 |
-
chunk_iter = stream_dataset_in_chunks(
|
| 369 |
-
dataset_name=PUNICAO_DATASET,
|
| 370 |
-
chunk_size=STREAM_BATCH_SIZE,
|
| 371 |
-
max_total=MAX_SAMPLES_PUNICAO,
|
| 372 |
-
hf_token=hf_token,
|
| 373 |
-
timeout_per_chunk=120,
|
| 374 |
-
)
|
| 375 |
-
for chunk_samples in chunk_iter:
|
| 376 |
-
if mem_critical_stopped:
|
| 377 |
-
break
|
| 378 |
-
if samples_processed >= META_MINIMA_PUNICAO:
|
| 379 |
-
logger.info(f"[PUNICAO] Meta atingida: {samples_processed}")
|
| 380 |
-
break
|
| 381 |
-
chunk_idx_global += 1
|
| 382 |
-
if not chunk_samples:
|
| 383 |
-
continue
|
| 384 |
-
samples_processed += len(chunk_samples)
|
| 385 |
-
labels = [make_label(s, PUNICAO_DATASET) for s in chunk_samples]
|
| 386 |
-
logger.info(f"[PUNICAO] Chunk {chunk_idx_global}: {len(chunk_samples)} samples (total: {samples_processed})")
|
| 387 |
-
|
| 388 |
-
for batch_start in range(0, len(chunk_samples), BATCH_SIZE):
|
| 389 |
-
try:
|
| 390 |
-
# Memory check
|
| 391 |
-
if step > 0 and step % 5 == 0 and cg_limit is not None:
|
| 392 |
-
rss = get_process_rss_mb()
|
| 393 |
-
mem_pct = (rss / cg_limit) * 100.0
|
| 394 |
-
logger.info(f"[PUNICAO] MEM: RSS={rss:.0f}MB ({mem_pct:.1f}%)")
|
| 395 |
-
if mem_pct > 80:
|
| 396 |
-
logger.error(f"[PUNICAO] MEM CRITICAL ({mem_pct:.1f}%). Stopping.")
|
| 397 |
-
save_punicão_states(kls, f"mem_critical_{mem_pct:.1f}pct", step, samples_processed)
|
| 398 |
-
mem_critical_stopped = True
|
| 399 |
-
break
|
| 400 |
-
|
| 401 |
-
batch_sents = chunk_samples[batch_start: batch_start + BATCH_SIZE]
|
| 402 |
-
batch_labels = labels[batch_start: batch_start + BATCH_SIZE]
|
| 403 |
-
try:
|
| 404 |
-
result = kls.process_batch_v2(
|
| 405 |
-
batch_sents, batch_labels,
|
| 406 |
-
label_strings=DATASET_LABEL_STRINGS.get(PUNICAO_DATASET),
|
| 407 |
-
dataset_name=PUNICAO_DATASET,
|
| 408 |
-
enable_punishment=True,
|
| 409 |
-
)
|
| 410 |
-
except Exception as e:
|
| 411 |
-
logger.error(f"[PUNICAO] process_batch_v2 error: {e}")
|
| 412 |
-
continue
|
| 413 |
-
|
| 414 |
-
action = result.get("action", "none")
|
| 415 |
-
if action != "none" and action != "success":
|
| 416 |
-
punishment_log.append({
|
| 417 |
-
"step": step,
|
| 418 |
-
"action": action,
|
| 419 |
-
"accuracy": float(result.get("accuracy", 0.0)),
|
| 420 |
-
"punishment_count": int(kls.punishment_count),
|
| 421 |
-
"cycle_completed": bool(result.get("cycle_completed", False)),
|
| 422 |
-
})
|
| 423 |
-
if "hypotheses_training" in result:
|
| 424 |
-
hyp_info = result["hypotheses_training"]
|
| 425 |
-
hypotheses_log.append({
|
| 426 |
-
"step": step,
|
| 427 |
-
"loss_initial": float(hyp_info.get("loss_initial", 0.0)),
|
| 428 |
-
"loss_final": float(hyp_info.get("loss_final", 0.0)),
|
| 429 |
-
"delta_scale": float(hyp_info.get("delta_scale_final", 0.0)),
|
| 430 |
-
})
|
| 431 |
-
if "delta_application" in result:
|
| 432 |
-
apply_info = result["delta_application"]
|
| 433 |
-
delta_applications_log.append({
|
| 434 |
-
"step": step,
|
| 435 |
-
"acc_before": float(apply_info.get("acc_before", 0.0)),
|
| 436 |
-
"acc_after": float(apply_info.get("acc_after", 0.0)),
|
| 437 |
-
"delta_norm": float(apply_info.get("delta_norm", 0.0)),
|
| 438 |
-
"ewc_reference_set": bool(apply_info.get("ewc_reference_set", False)),
|
| 439 |
-
})
|
| 440 |
-
|
| 441 |
-
acc = kls.evaluate_classification()
|
| 442 |
-
accuracies_log.append({
|
| 443 |
-
"step": step,
|
| 444 |
-
"accuracy": float(acc),
|
| 445 |
-
"action": action,
|
| 446 |
-
"punishment_count": int(kls.punishment_count),
|
| 447 |
-
"buffer_size": int(len(kls.buffer_4d)),
|
| 448 |
-
})
|
| 449 |
-
step += 1
|
| 450 |
-
time.sleep(INTER_BATCH_PAUSE_S)
|
| 451 |
-
except Exception as e:
|
| 452 |
-
logger.error(f"[PUNICAO] Batch error: {e}")
|
| 453 |
-
continue
|
| 454 |
-
|
| 455 |
-
time.sleep(INTER_STREAM_BATCH_PAUSE_S)
|
| 456 |
-
# Buffer sliding window
|
| 457 |
-
if len(kls.buffer_4d) > MAX_BUFFER_SIZE:
|
| 458 |
-
overflow = len(kls.buffer_4d) - MAX_BUFFER_SIZE
|
| 459 |
-
del kls.buffer_4d[:overflow]
|
| 460 |
-
del kls.buffer_labels[:overflow]
|
| 461 |
-
logger.info(f"[PUNICAO] Buffer trimmed: -{overflow} → {len(kls.buffer_4d)}")
|
| 462 |
-
if chunk_idx_global % 2 == 0:
|
| 463 |
-
aggressive_memory_cleanup()
|
| 464 |
-
except Exception as e:
|
| 465 |
-
logger.error(f"[PUNICAO] FASE 2 failed: {e}")
|
| 466 |
-
traceback.print_exc()
|
| 467 |
-
|
| 468 |
-
t_elapsed = time.time() - t_start
|
| 469 |
-
logger.info(f"\n[PUNICAO] FASE 2 concluída:")
|
| 470 |
-
logger.info(f" Total samples: {samples_processed}")
|
| 471 |
-
logger.info(f" Meta atingida: {samples_processed >= META_MINIMA_PUNICAO}")
|
| 472 |
-
logger.info(f" Punishment events: {len(punishment_log)}")
|
| 473 |
-
logger.info(f" Delta applications: {len(delta_applications_log)}")
|
| 474 |
-
logger.info(f" Tempo: {t_elapsed:.1f}s")
|
| 475 |
-
|
| 476 |
-
# Save final states
|
| 477 |
-
save_punicão_states(kls, "end_of_punicão", step, samples_processed)
|
| 478 |
-
|
| 479 |
-
# Final report
|
| 480 |
-
final_report = {
|
| 481 |
-
"version": "V6.5-V2-dynamic-punicão",
|
| 482 |
-
"timestamp": datetime.now().isoformat(),
|
| 483 |
-
"loaded_from_fase1": str(FASE1_STATES_PATH),
|
| 484 |
-
"fase1_total_samples": int(fase1_state["_meta"]["total_samples"]),
|
| 485 |
-
"fase2_total_samples": int(samples_processed),
|
| 486 |
-
"fase2_meta_atingida": bool(samples_processed >= META_MINIMA_PUNICAO),
|
| 487 |
-
"fase2_elapsed_s": float(t_elapsed),
|
| 488 |
-
"punishment_events": punishment_log,
|
| 489 |
-
"hypotheses_trainings": hypotheses_log,
|
| 490 |
-
"delta_applications": delta_applications_log,
|
| 491 |
-
"accuracies_log": accuracies_log[-30:],
|
| 492 |
-
"final_v2_metrics": kls.get_v2_metrics(),
|
| 493 |
-
"n_adaptations": len(kls.get_adaptation_log()),
|
| 494 |
-
"mem_critical_stopped": bool(mem_critical_stopped),
|
| 495 |
-
}
|
| 496 |
-
PUNICAO_REPORT_PATH.write_text(json.dumps(final_report, indent=2, ensure_ascii=False))
|
| 497 |
-
logger.info(f"[PUNICAO] Report saved: {PUNICAO_REPORT_PATH}")
|
| 498 |
-
|
| 499 |
-
# Copy to download
|
| 500 |
-
try:
|
| 501 |
-
for src in [PUNICAO_REPORT_PATH, PUNICAO_STATES_PATH]:
|
| 502 |
-
if src.exists():
|
| 503 |
-
shutil.copy2(src, DOWNLOAD_DIR / src.name)
|
| 504 |
-
except Exception as e:
|
| 505 |
-
logger.warning(f"[PUNICAO] Copy to download failed: {e}")
|
| 506 |
-
|
| 507 |
-
# HF upload (batch)
|
| 508 |
-
hf_upload_result = {"uploaded": False, "reason": "no_hf_token"}
|
| 509 |
-
if hf_token:
|
| 510 |
-
files_to_upload = [
|
| 511 |
-
PUNICAO_REPORT_PATH,
|
| 512 |
-
PUNICAO_STATES_PATH,
|
| 513 |
-
BIGRU_ROOT / "scripts" / "train_v6_5_v2_punicão_only.py",
|
| 514 |
-
]
|
| 515 |
-
# Scrub HF_TOKEN from scripts before upload
|
| 516 |
-
pattern = re.compile(r'hf_[A-Za-z0-9]{32,}')
|
| 517 |
-
for py_file in [BIGRU_ROOT / "scripts" / "train_v6_5_v2_punicão_only.py"]:
|
| 518 |
-
if py_file.exists():
|
| 519 |
-
content = py_file.read_text(encoding="utf-8")
|
| 520 |
-
if pattern.findall(content):
|
| 521 |
-
py_file.write_text(pattern.sub("<HF_TOKEN_REMOVED>", content), encoding="utf-8")
|
| 522 |
-
|
| 523 |
-
hf_upload_result = upload_punicão_to_hf(hf_token, files_to_upload)
|
| 524 |
-
PUNICAO_UPLOAD_REPORT_PATH.write_text(
|
| 525 |
-
json.dumps(hf_upload_result, indent=2, ensure_ascii=False)
|
| 526 |
-
)
|
| 527 |
-
logger.info(f"[PUNICAO] HF upload: {hf_upload_result}")
|
| 528 |
-
|
| 529 |
-
# Clean HF_TOKEN
|
| 530 |
-
if "HF_TOKEN" in os.environ:
|
| 531 |
-
del os.environ["HF_TOKEN"]
|
| 532 |
-
if "HUGGING_FACE_HUB_TOKEN" in os.environ:
|
| 533 |
-
del os.environ["HUGGING_FACE_HUB_TOKEN"]
|
| 534 |
-
logger.info("[PUNICAO] HF_TOKEN cleaned from environment")
|
| 535 |
-
|
| 536 |
-
print("\n" + "=" * 80)
|
| 537 |
-
print("V6.5-V2-dynamic FASE 2 PUNIÇÃO — CONCLUÍDO")
|
| 538 |
-
print("=" * 80)
|
| 539 |
-
print(f" Samples processados : {samples_processed}")
|
| 540 |
-
print(f" Meta atingida : {samples_processed >= META_MINIMA_PUNICAO}")
|
| 541 |
-
print(f" Punishment events : {len(punishment_log)}")
|
| 542 |
-
print(f" Delta applications : {len(delta_applications_log)}")
|
| 543 |
-
print(f" Hyp trainings : {len(hypotheses_log)}")
|
| 544 |
-
print(f" N adaptations : {len(kls.get_adaptation_log())}")
|
| 545 |
-
print(f" Final n_hypotheses : {kls.n_hypotheses}")
|
| 546 |
-
print(f" Final n_trials : {kls.n_trials}")
|
| 547 |
-
print(f" Final hyp_steps : {kls.hyp_train_steps}")
|
| 548 |
-
print(f" HF upload : {hf_upload_result.get('uploaded', False)}")
|
| 549 |
-
print(f" HF_TOKEN cleaned : True")
|
| 550 |
-
print("=" * 80)
|
| 551 |
-
return 0
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
if __name__ == "__main__":
|
| 555 |
-
import torch.nn as nn # needed for nn.Parameter in main
|
| 556 |
-
sys.exit(main())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
xeon_runtime.py
DELETED
|
@@ -1,581 +0,0 @@
|
|
| 1 |
-
"""xeon_runtime.py — Intel Xeon runtime (V6): AVX512 + AMX_INT8 + IPEX + OneDNN + FP16.
|
| 2 |
-
|
| 3 |
-
═══════════════════════════════════════════════════════════════════════════════
|
| 4 |
-
V6 UPGRADE
|
| 5 |
-
═══════════════════════════════════════════════════════════════════════════════
|
| 6 |
-
|
| 7 |
-
V5 only set OMP/MKL threads + KMP_AFFINITY. V6 adds:
|
| 8 |
-
|
| 9 |
-
1. MKL_ENABLE_INSTRUCTIONS=AVX512 — forces MKL to dispatch AVX512 kernels
|
| 10 |
-
2. ONEDNN_MAX_CPU_ISA=AMX_INT8 — lets oneDNN use AMX INT8 tiles
|
| 11 |
-
3. DNNL_PRIMITIVE_CACHE_CAPACITY=1024 — large primitive cache (default 1024)
|
| 12 |
-
4. MKL_DYNAMIC=FALSE — disables MKL dynamic thread adjustment
|
| 13 |
-
5. IPEX (intel_extension_for_pytorch) — Intel PyTorch extension
|
| 14 |
-
- ipex.optimize(model) on the BiGRU_T model
|
| 15 |
-
- torch.cpu.amp.autocast(dtype=torch.float16) for FP16 inference
|
| 16 |
-
6. FP16 benchmark: 8000×8000 matmul, TFLOPS measurement
|
| 17 |
-
7. libvirt AMX activation helper — exposes amx-tile/amx-int8/amx-bf16 to a VM
|
| 18 |
-
via host-passthrough CPU mode + feature policy='require'
|
| 19 |
-
|
| 20 |
-
The runtime is **always activated** (per user request: "sempre ativar otimização
|
| 21 |
-
para Xeon AVX512"). It degrades gracefully if IPEX / libvirt / AMX are not
|
| 22 |
-
available, but it never silently skips the optimization step.
|
| 23 |
-
|
| 24 |
-
═══════════════════════════════════════════════════════════════════════════════
|
| 25 |
-
USAGE
|
| 26 |
-
═══════════════════════════════════════════════════════════════════════════════
|
| 27 |
-
|
| 28 |
-
from bigru_t.utils.xeon_runtime import optimize_xeon_environment
|
| 29 |
-
N_CORES = optimize_xeon_environment() # call ONCE, before torch
|
| 30 |
-
# ... safe to import torch, ipex, etc. ...
|
| 31 |
-
|
| 32 |
-
For VM AMX exposure (requires libvirt-python and root):
|
| 33 |
-
from bigru_t.utils.xeon_runtime import ativar_amx_na_vm
|
| 34 |
-
ativar_amx_na_vm("my_vm_name")
|
| 35 |
-
|
| 36 |
-
═══════════════════════════════════════════════════════════════════════════════
|
| 37 |
-
"""
|
| 38 |
-
from __future__ import annotations
|
| 39 |
-
import os
|
| 40 |
-
import sys
|
| 41 |
-
import time
|
| 42 |
-
import logging
|
| 43 |
-
import platform
|
| 44 |
-
from typing import Optional, Tuple, Dict, Any
|
| 45 |
-
|
| 46 |
-
logger = logging.getLogger(__name__)
|
| 47 |
-
|
| 48 |
-
_NUCLEOS_ALOCADOS: Optional[int] = None
|
| 49 |
-
_IPEX_AVAILABLE: Optional[bool] = None
|
| 50 |
-
_AMX_CAPABLE: Optional[bool] = None
|
| 51 |
-
_V6_INIT_DONE: bool = False
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
# ============================================================================
|
| 55 |
-
# Helpers
|
| 56 |
-
# ============================================================================
|
| 57 |
-
|
| 58 |
-
def _detect_physical_cores() -> int:
|
| 59 |
-
"""Detect physical cores actually available to this process.
|
| 60 |
-
|
| 61 |
-
Respects cgroup limits without requiring root. Falls back to logical
|
| 62 |
-
cpu count if psutil is unavailable.
|
| 63 |
-
"""
|
| 64 |
-
try:
|
| 65 |
-
import psutil
|
| 66 |
-
n_phys = psutil.cpu_count(logical=False) or 1
|
| 67 |
-
except ImportError:
|
| 68 |
-
try:
|
| 69 |
-
with open("/proc/cpuinfo", "r") as f:
|
| 70 |
-
cores = set()
|
| 71 |
-
for line in f:
|
| 72 |
-
if line.startswith("core id"):
|
| 73 |
-
cores.add(line.strip())
|
| 74 |
-
n_phys = len(cores) or 1
|
| 75 |
-
except OSError:
|
| 76 |
-
n_phys = 1
|
| 77 |
-
|
| 78 |
-
try:
|
| 79 |
-
n_affine = len(os.sched_getaffinity(0))
|
| 80 |
-
n_logical = os.cpu_count() or 1
|
| 81 |
-
if n_affine < n_logical:
|
| 82 |
-
n_phys = max(1, n_affine // 2)
|
| 83 |
-
else:
|
| 84 |
-
n_phys = min(n_phys, n_affine)
|
| 85 |
-
except (AttributeError, OSError):
|
| 86 |
-
pass
|
| 87 |
-
|
| 88 |
-
return max(1, n_phys)
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
def _read_cpu_flags() -> str:
|
| 92 |
-
try:
|
| 93 |
-
with open("/proc/cpuinfo", "r") as f:
|
| 94 |
-
for line in f:
|
| 95 |
-
if line.startswith("flags"):
|
| 96 |
-
return line
|
| 97 |
-
except OSError:
|
| 98 |
-
pass
|
| 99 |
-
return ""
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
def get_avx512_capability() -> Tuple[bool, str]:
|
| 103 |
-
"""Check if the host CPU supports AVX512 VNNI."""
|
| 104 |
-
flags = _read_cpu_flags()
|
| 105 |
-
if "avx512_vnni" in flags:
|
| 106 |
-
return True, "AVX512_VNNI (full INT8 acceleration)"
|
| 107 |
-
elif "avx512f" in flags:
|
| 108 |
-
return True, "AVX512F (no VNNI; INT8 falls back to AVX512F)"
|
| 109 |
-
elif "avx2" in flags:
|
| 110 |
-
return False, "AVX2 only (INT8 quantization works but slower)"
|
| 111 |
-
else:
|
| 112 |
-
return False, "Legacy SSE (INT8 quantization not recommended)"
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
def get_amx_capability() -> Tuple[bool, str]:
|
| 116 |
-
"""V6: check if AMX (Advanced Matrix Extensions) is available."""
|
| 117 |
-
global _AMX_CAPABLE
|
| 118 |
-
flags = _read_cpu_flags()
|
| 119 |
-
has_tile = "amx_tile" in flags
|
| 120 |
-
has_int8 = "amx_int8" in flags
|
| 121 |
-
has_bf16 = "amx_bf16" in flags
|
| 122 |
-
if has_tile and has_int8 and has_bf16:
|
| 123 |
-
_AMX_CAPABLE = True
|
| 124 |
-
return True, "AMX (tile + int8 + bf16) — full AMX acceleration"
|
| 125 |
-
elif has_tile:
|
| 126 |
-
_AMX_CAPABLE = True
|
| 127 |
-
return True, f"AMX tile only (int8={has_int8}, bf16={has_bf16})"
|
| 128 |
-
else:
|
| 129 |
-
_AMX_CAPABLE = False
|
| 130 |
-
return False, "AMX not available (AVX512 path will be used)"
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
def _try_import_ipex() -> Optional[Any]:
|
| 134 |
-
"""V6: try to import IPEX (Intel Extension for PyTorch).
|
| 135 |
-
|
| 136 |
-
Returns the ipex module if available, else None. Caches the result.
|
| 137 |
-
"""
|
| 138 |
-
global _IPEX_AVAILABLE
|
| 139 |
-
if _IPEX_AVAILABLE is False:
|
| 140 |
-
return None
|
| 141 |
-
try:
|
| 142 |
-
import intel_extension_for_pytorch as ipex # type: ignore
|
| 143 |
-
_IPEX_AVAILABLE = True
|
| 144 |
-
return ipex
|
| 145 |
-
except (ImportError, AttributeError, OSError) as e:
|
| 146 |
-
_IPEX_AVAILABLE = False
|
| 147 |
-
if _V6_INIT_DONE is False:
|
| 148 |
-
logger.info(f"[Xeon V6] IPEX não disponível: {type(e).__name__}: {e}")
|
| 149 |
-
logger.info("[Xeon V6] Continuando com OneDNN/MKL nativo do PyTorch.")
|
| 150 |
-
return None
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
# ============================================================================
|
| 154 |
-
# FP16 benchmark (V6 — user-provided code)
|
| 155 |
-
# ============================================================================
|
| 156 |
-
|
| 157 |
-
def benchmark_fp16_matmul(size: int = 8000, warmup: int = 1, iters: int = 3) -> Dict[str, float]:
|
| 158 |
-
"""V6: FP16 matmul benchmark for Xeon AVX512/AMX.
|
| 159 |
-
|
| 160 |
-
Runs `size`×`size` FP16 matmul `iters` times and reports:
|
| 161 |
-
- best_time_ms: lowest wall time
|
| 162 |
-
- best_tflops: best achieved TFLOPS
|
| 163 |
-
- avg_tflops: average TFLOPS
|
| 164 |
-
|
| 165 |
-
Returns empty dict if torch unavailable.
|
| 166 |
-
"""
|
| 167 |
-
try:
|
| 168 |
-
import torch
|
| 169 |
-
except ImportError:
|
| 170 |
-
return {}
|
| 171 |
-
|
| 172 |
-
results: Dict[str, float] = {}
|
| 173 |
-
try:
|
| 174 |
-
# Warmup
|
| 175 |
-
a = torch.randn(size, size, dtype=torch.float16)
|
| 176 |
-
b = torch.randn(size, size, dtype=torch.float16)
|
| 177 |
-
for _ in range(warmup):
|
| 178 |
-
_ = torch.matmul(a, b)
|
| 179 |
-
# Bench
|
| 180 |
-
times = []
|
| 181 |
-
for _ in range(iters):
|
| 182 |
-
t0 = time.perf_counter()
|
| 183 |
-
_ = torch.matmul(a, b)
|
| 184 |
-
times.append(time.perf_counter() - t0)
|
| 185 |
-
best_t = min(times)
|
| 186 |
-
avg_t = sum(times) / len(times)
|
| 187 |
-
# 2*size^3 FLOPs per matmul (M*N*K)
|
| 188 |
-
flops = 2.0 * (size ** 3)
|
| 189 |
-
results["best_time_ms"] = best_t * 1000.0
|
| 190 |
-
results["avg_time_ms"] = avg_t * 1000.0
|
| 191 |
-
results["best_tflops"] = flops / best_t / 1e12
|
| 192 |
-
results["avg_tflops"] = flops / avg_t / 1e12
|
| 193 |
-
results["matrix_size"] = float(size)
|
| 194 |
-
except (RuntimeError, MemoryError) as e:
|
| 195 |
-
logger.warning(f"[Xeon V6] FP16 benchmark failed: {e}")
|
| 196 |
-
results["error"] = str(e)
|
| 197 |
-
return results
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
def benchmark_int8_matmul(size: int = 4096, warmup: int = 1, iters: int = 3) -> Dict[str, float]:
|
| 201 |
-
"""V6: INT8 matmul benchmark — uses AMX_INT8 when available via oneDNN.
|
| 202 |
-
|
| 203 |
-
Falls back to FP32 if INT8 path is unavailable.
|
| 204 |
-
"""
|
| 205 |
-
try:
|
| 206 |
-
import torch
|
| 207 |
-
except ImportError:
|
| 208 |
-
return {}
|
| 209 |
-
|
| 210 |
-
results: Dict[str, float] = {}
|
| 211 |
-
try:
|
| 212 |
-
a = torch.randint(-127, 127, (size, size), dtype=torch.int8)
|
| 213 |
-
b = torch.randint(-127, 127, (size, size), dtype=torch.int8)
|
| 214 |
-
# Warmup
|
| 215 |
-
for _ in range(warmup):
|
| 216 |
-
_ = torch.matmul(a.float(), b.float())
|
| 217 |
-
times = []
|
| 218 |
-
for _ in range(iters):
|
| 219 |
-
t0 = time.perf_counter()
|
| 220 |
-
_ = torch.matmul(a.float(), b.float())
|
| 221 |
-
times.append(time.perf_counter() - t0)
|
| 222 |
-
best_t = min(times)
|
| 223 |
-
flops = 2.0 * (size ** 3)
|
| 224 |
-
results["int8_best_time_ms"] = best_t * 1000.0
|
| 225 |
-
results["int8_best_tflops"] = flops / best_t / 1e12
|
| 226 |
-
results["int8_matrix_size"] = float(size)
|
| 227 |
-
except (RuntimeError, MemoryError) as e:
|
| 228 |
-
results["int8_error"] = str(e)
|
| 229 |
-
return results
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
# ============================================================================
|
| 233 |
-
# Main entry point: optimize_xeon_environment (V6)
|
| 234 |
-
# ============================================================================
|
| 235 |
-
|
| 236 |
-
def optimize_xeon_environment(
|
| 237 |
-
verbose: bool = True,
|
| 238 |
-
force_ipex: bool = False,
|
| 239 |
-
) -> int:
|
| 240 |
-
"""V6: configure Intel Xeon AVX512 + AMX_INT8 + IPEX + OneDNN.
|
| 241 |
-
|
| 242 |
-
Always activates — per user requirement "sempre ativar otimização para Xeon
|
| 243 |
-
AVX512". Idempotent. Sets every environment variable that influences MKL,
|
| 244 |
-
OpenMP, oneDNN, and (optionally) IPEX runtime behavior.
|
| 245 |
-
|
| 246 |
-
Args:
|
| 247 |
-
verbose: print configuration summary to stdout
|
| 248 |
-
force_ipex: if True, raise when IPEX import fails. Default False —
|
| 249 |
-
degrade gracefully to native PyTorch oneDNN.
|
| 250 |
-
|
| 251 |
-
Returns:
|
| 252 |
-
Number of physical cores allocated to this process.
|
| 253 |
-
"""
|
| 254 |
-
global _NUCLEOS_ALOCADOS, _V6_INIT_DONE
|
| 255 |
-
|
| 256 |
-
if _NUCLEOS_ALOCADOS is not None and _V6_INIT_DONE:
|
| 257 |
-
return _NUCLEOS_ALOCADOS
|
| 258 |
-
|
| 259 |
-
n_phys = _detect_physical_cores()
|
| 260 |
-
|
| 261 |
-
# ───────────────────────────────────────────────────────────────────────
|
| 262 |
-
# V6 — Environment variables (user-provided block)
|
| 263 |
-
# ───────────────────────────────────────────────────────────────────────
|
| 264 |
-
os.environ["MKL_ENABLE_INSTRUCTIONS"] = "AVX512"
|
| 265 |
-
NUM_CORES = str(n_phys)
|
| 266 |
-
os.environ["MKL_NUM_THREADS"] = NUM_CORES
|
| 267 |
-
os.environ["OMP_NUM_THREADS"] = NUM_CORES
|
| 268 |
-
os.environ["MKL_DYNAMIC"] = "FALSE"
|
| 269 |
-
os.environ["DNNL_PRIMITIVE_CACHE_CAPACITY"] = "1024"
|
| 270 |
-
os.environ["ONEDNN_MAX_CPU_ISA"] = "AMX_INT8"
|
| 271 |
-
|
| 272 |
-
# ───────────────────────────────────────────────────────────────────────
|
| 273 |
-
# V5 (kept) — OpenMP thread pinning
|
| 274 |
-
# ───────────────────────────────────────────────────────────────────────
|
| 275 |
-
os.environ.setdefault("KMP_AFFINITY", "granularity=fine,compact,1,0")
|
| 276 |
-
os.environ.setdefault("KMP_BLOCKTIME", "1")
|
| 277 |
-
os.environ.setdefault("TOKENIZERS_PARALLELISM", "true")
|
| 278 |
-
|
| 279 |
-
# ───────────────────────────────────────────────────────────────────────
|
| 280 |
-
# PyTorch backend flags
|
| 281 |
-
# ───────────────────────────────────────────────────────────────────────
|
| 282 |
-
try:
|
| 283 |
-
import torch
|
| 284 |
-
torch.set_num_threads(n_phys)
|
| 285 |
-
try:
|
| 286 |
-
torch.set_num_interop_threads(1)
|
| 287 |
-
except RuntimeError:
|
| 288 |
-
# V6.5: já inicializado (e.g., bigru_t package importou torch antes).
|
| 289 |
-
# Silenciosamente ignora — o paralelismo já está configurado.
|
| 290 |
-
pass
|
| 291 |
-
if hasattr(torch.backends, "mkldnn"):
|
| 292 |
-
torch.backends.mkldnn.enabled = True
|
| 293 |
-
if hasattr(torch.backends, "quantized"):
|
| 294 |
-
try:
|
| 295 |
-
torch.backends.quantized.engine = "fbgemm"
|
| 296 |
-
except (RuntimeError, AttributeError):
|
| 297 |
-
pass
|
| 298 |
-
# V6: enable TF32 for Ampere+ / Sapphire Rapids (irrelevant on CPU but
|
| 299 |
-
# harmless) and ensure oneDNN verbose is silent.
|
| 300 |
-
try:
|
| 301 |
-
torch.backends.cuda.matmul.allow_tf32 = True
|
| 302 |
-
except AttributeError:
|
| 303 |
-
pass
|
| 304 |
-
except ImportError:
|
| 305 |
-
if verbose:
|
| 306 |
-
print("[Xeon V6] WARNING: PyTorch not yet imported — env vars set,"
|
| 307 |
-
" call this BEFORE importing torch for full effect.")
|
| 308 |
-
|
| 309 |
-
# ───────────────────────────────────────────────────────────────────────
|
| 310 |
-
# V6 — IPEX (intel_extension_for_pytorch)
|
| 311 |
-
# ───────────────────────────────────────────────────────────────────────
|
| 312 |
-
ipex = _try_import_ipex()
|
| 313 |
-
if ipex is None and force_ipex:
|
| 314 |
-
raise ImportError(
|
| 315 |
-
"IPEX (intel_extension_for_pytorch) não disponível, mas force_ipex=True. "
|
| 316 |
-
"Instale com: pip install intel-extension-for-pytorch"
|
| 317 |
-
)
|
| 318 |
-
|
| 319 |
-
# ───────────────────────────────────────────────────────────────────────
|
| 320 |
-
# V6 — AMX capability check
|
| 321 |
-
# ───────────────────────────────────────────────────────────────────────
|
| 322 |
-
amx_ok, amx_desc = get_amx_capability()
|
| 323 |
-
avx_ok, avx_desc = get_avx512_capability()
|
| 324 |
-
|
| 325 |
-
_NUCLEOS_ALOCADOS = n_phys
|
| 326 |
-
_V6_INIT_DONE = True
|
| 327 |
-
|
| 328 |
-
if verbose:
|
| 329 |
-
print("\n" + "=" * 72)
|
| 330 |
-
print(f"[Xeon Runtime V6] Intel Xeon optimization activated")
|
| 331 |
-
print("=" * 72)
|
| 332 |
-
print(f" Physical cores : {n_phys}")
|
| 333 |
-
print(f" MKL_NUM_THREADS : {os.environ['MKL_NUM_THREADS']}")
|
| 334 |
-
print(f" OMP_NUM_THREADS : {os.environ['OMP_NUM_THREADS']}")
|
| 335 |
-
print(f" MKL_DYNAMIC : {os.environ['MKL_DYNAMIC']}")
|
| 336 |
-
print(f" MKL_ENABLE_INSTRUCTIONS: {os.environ['MKL_ENABLE_INSTRUCTIONS']}")
|
| 337 |
-
print(f" ONEDNN_MAX_CPU_ISA : {os.environ['ONEDNN_MAX_CPU_ISA']}")
|
| 338 |
-
print(f" DNNL_PRIMITIVE_CACHE : {os.environ['DNNL_PRIMITIVE_CACHE_CAPACITY']}")
|
| 339 |
-
print(f" KMP_AFFINITY : {os.environ['KMP_AFFINITY']}")
|
| 340 |
-
print(f" KMP_BLOCKTIME : {os.environ['KMP_BLOCKTIME']}")
|
| 341 |
-
print(f" AVX512 : {avx_desc}")
|
| 342 |
-
print(f" AMX : {amx_desc}")
|
| 343 |
-
print(f" IPEX : "
|
| 344 |
-
f"{'available' if ipex is not None else 'not installed (using native oneDNN)'}")
|
| 345 |
-
print("=" * 72 + "\n")
|
| 346 |
-
|
| 347 |
-
return n_phys
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
# ============================================================================
|
| 351 |
-
# V6 — apply_ipex_optimization (model-level)
|
| 352 |
-
# ============================================================================
|
| 353 |
-
|
| 354 |
-
def apply_ipex_optimization(model, dtype=None, optimizer=None):
|
| 355 |
-
"""V6: apply ipex.optimize() to a model.
|
| 356 |
-
|
| 357 |
-
Returns (model, optimizer) tuple. If IPEX is not available, returns the
|
| 358 |
-
inputs unchanged. The model is modified in-place when IPEX is present.
|
| 359 |
-
|
| 360 |
-
Args:
|
| 361 |
-
model: torch.nn.Module
|
| 362 |
-
dtype: optional torch.dtype for the model (e.g. torch.bfloat16)
|
| 363 |
-
optimizer: optional torch.optim.Optimizer to also optimize
|
| 364 |
-
"""
|
| 365 |
-
ipex = _try_import_ipex()
|
| 366 |
-
if ipex is None:
|
| 367 |
-
return model, optimizer
|
| 368 |
-
try:
|
| 369 |
-
import torch
|
| 370 |
-
if dtype is not None:
|
| 371 |
-
model = model.to(dtype)
|
| 372 |
-
if optimizer is not None:
|
| 373 |
-
model, optimizer = ipex.optimize(model=model, optimizer=optimizer, dtype=dtype)
|
| 374 |
-
else:
|
| 375 |
-
model = ipex.optimize(model=model, dtype=dtype)
|
| 376 |
-
logger.info(f"[Xeon V6] ipex.optimize applied (dtype={dtype})")
|
| 377 |
-
except (RuntimeError, AttributeError, TypeError) as e:
|
| 378 |
-
logger.warning(f"[Xeon V6] ipex.optimize failed: {e}")
|
| 379 |
-
return model, optimizer
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
# ============================================================================
|
| 383 |
-
# V6 — FP16 autocast context manager
|
| 384 |
-
# ============================================================================
|
| 385 |
-
|
| 386 |
-
class fp16_autocast:
|
| 387 |
-
"""V6: FP16 CPU autocast context manager.
|
| 388 |
-
|
| 389 |
-
Uses torch.cpu.amp.autocast(dtype=torch.float16) when available. Falls
|
| 390 |
-
back to a no-op context manager if autocast is not supported.
|
| 391 |
-
|
| 392 |
-
Usage:
|
| 393 |
-
with fp16_autocast():
|
| 394 |
-
y = model(x)
|
| 395 |
-
"""
|
| 396 |
-
|
| 397 |
-
def __init__(self, enabled: bool = True):
|
| 398 |
-
self.enabled = enabled
|
| 399 |
-
self._ctx = None
|
| 400 |
-
|
| 401 |
-
def __enter__(self):
|
| 402 |
-
if not self.enabled:
|
| 403 |
-
return self
|
| 404 |
-
try:
|
| 405 |
-
import torch
|
| 406 |
-
if hasattr(torch.cpu, "amp") and hasattr(torch.cpu.amp, "autocast"):
|
| 407 |
-
self._ctx = torch.cpu.amp.autocast(dtype=torch.float16)
|
| 408 |
-
self._ctx.__enter__()
|
| 409 |
-
except (ImportError, RuntimeError, AttributeError):
|
| 410 |
-
self._ctx = None
|
| 411 |
-
return self
|
| 412 |
-
|
| 413 |
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
| 414 |
-
if self._ctx is not None:
|
| 415 |
-
self._ctx.__exit__(exc_type, exc_val, exc_tb)
|
| 416 |
-
self._ctx = None
|
| 417 |
-
return False
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
# ============================================================================
|
| 421 |
-
# V6 — libvirt AMX activation helper (user-provided code, integrated)
|
| 422 |
-
# ============================================================================
|
| 423 |
-
|
| 424 |
-
def ativar_amx_na_vm(nome_vm: str, qemu_uri: str = "qemu:///system") -> bool:
|
| 425 |
-
"""V6: ativa AMX (amx-tile, amx-int8, amx-bf16) numa VM via libvirt.
|
| 426 |
-
|
| 427 |
-
Requer:
|
| 428 |
-
- pip install libvirt-python
|
| 429 |
-
- libvirtd rodando localmente (qemu:///system)
|
| 430 |
-
- permissão de root (ou membro do grupo libvirt)
|
| 431 |
-
- CPU física com AMX (verificado por get_amx_capability())
|
| 432 |
-
|
| 433 |
-
Implementação:
|
| 434 |
-
1. Conecta ao daemon libvirt
|
| 435 |
-
2. Busca a VM pelo nome
|
| 436 |
-
3. Lê o XML persistente (flag VIR_DOMAIN_XML_INACTIVE = 2)
|
| 437 |
-
4. Garante <cpu mode='host-passthrough'>
|
| 438 |
-
5. Adiciona <feature policy='require' name='amx-tile|int8|bf16'/>
|
| 439 |
-
6. Reescreve o XML via defineXML()
|
| 440 |
-
|
| 441 |
-
Retorna True se a VM foi atualizada com sucesso, False caso contrário.
|
| 442 |
-
Requer reboot da VM para aplicar.
|
| 443 |
-
|
| 444 |
-
Args:
|
| 445 |
-
nome_vm: nome da máquina virtual no libvirt
|
| 446 |
-
qemu_uri: URI do libvirt (default: qemu:///system)
|
| 447 |
-
|
| 448 |
-
Raises:
|
| 449 |
-
ImportError: se libvirt-python não estiver instalado
|
| 450 |
-
RuntimeError: se a conexão com libvirt falhar
|
| 451 |
-
"""
|
| 452 |
-
try:
|
| 453 |
-
import libvirt # type: ignore
|
| 454 |
-
except ImportError as e:
|
| 455 |
-
raise ImportError(
|
| 456 |
-
"libvirt-python não instalado. Rode: pip install libvirt-python "
|
| 457 |
-
"(também requer libvirt-dev no sistema: apt install libvirt-dev)"
|
| 458 |
-
) from e
|
| 459 |
-
|
| 460 |
-
try:
|
| 461 |
-
import xml.etree.ElementTree as ET
|
| 462 |
-
except ImportError:
|
| 463 |
-
return False
|
| 464 |
-
|
| 465 |
-
# Pré-checa AMX no host físico
|
| 466 |
-
amx_ok, amx_desc = get_amx_capability()
|
| 467 |
-
if not amx_ok:
|
| 468 |
-
print(f"[AMX VM] Host físico não tem AMX ({amx_desc}).")
|
| 469 |
-
print(" Não adianta ativar AMX na VM — o host precisa suportar.")
|
| 470 |
-
return False
|
| 471 |
-
|
| 472 |
-
try:
|
| 473 |
-
conn = libvirt.open(qemu_uri)
|
| 474 |
-
if conn is None:
|
| 475 |
-
print(f"[AMX VM] Falha ao abrir conexão com {qemu_uri}")
|
| 476 |
-
return False
|
| 477 |
-
|
| 478 |
-
try:
|
| 479 |
-
dom = conn.lookupByName(nome_vm)
|
| 480 |
-
except libvirt.libvirtError:
|
| 481 |
-
print(f"[AMX VM] VM '{nome_vm}' não encontrada.")
|
| 482 |
-
conn.close()
|
| 483 |
-
return False
|
| 484 |
-
|
| 485 |
-
xml_atual = dom.XMLDesc(2) # VIR_DOMAIN_XML_INACTIVE
|
| 486 |
-
root = ET.fromstring(xml_atual)
|
| 487 |
-
cpu_elem = root.find("cpu")
|
| 488 |
-
|
| 489 |
-
if cpu_elem is None:
|
| 490 |
-
cpu_elem = ET.SubElement(root, "cpu", mode="host-passthrough")
|
| 491 |
-
print("[AMX VM] Tag <cpu> criada com mode='host-passthrough'.")
|
| 492 |
-
else:
|
| 493 |
-
cpu_elem.set("mode", "host-passthrough")
|
| 494 |
-
print("[AMX VM] CPU mode atualizado para host-passthrough.")
|
| 495 |
-
|
| 496 |
-
flags_para_adicionar = ["amx-tile", "amx-int8", "amx-bf16"]
|
| 497 |
-
added = []
|
| 498 |
-
for flag in flags_para_adicionar:
|
| 499 |
-
existing = cpu_elem.find(f"./feature[@name='{flag}']")
|
| 500 |
-
if existing is None:
|
| 501 |
-
ET.SubElement(cpu_elem, "feature", policy="require", name=flag)
|
| 502 |
-
added.append(flag)
|
| 503 |
-
print(f"[AMX VM] Feature adicionada: {flag}")
|
| 504 |
-
|
| 505 |
-
novo_xml = ET.tostring(root, encoding="utf-8").decode("utf-8")
|
| 506 |
-
conn.defineXML(novo_xml)
|
| 507 |
-
print(f"[AMX VM] ✓ AMX ativado no XML da VM '{nome_vm}'. Reinicie a VM para aplicar.")
|
| 508 |
-
conn.close()
|
| 509 |
-
return True
|
| 510 |
-
|
| 511 |
-
except libvirt.libvirtError as e:
|
| 512 |
-
print(f"[AMX VM] Erro libvirt: {e}")
|
| 513 |
-
return False
|
| 514 |
-
except Exception as e:
|
| 515 |
-
print(f"[AMX VM] Erro inesperado: {e}")
|
| 516 |
-
return False
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
# ============================================================================
|
| 520 |
-
# Compatibility helpers (V5 API preserved)
|
| 521 |
-
# ============================================================================
|
| 522 |
-
|
| 523 |
-
def get_allocated_cores() -> int:
|
| 524 |
-
"""Return the number of cores allocated by `optimize_xeon_environment`."""
|
| 525 |
-
return _NUCLEOS_ALOCADOS or 0
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
def make_ort_session_options():
|
| 529 |
-
"""Build onnxruntime.SessionOptions tuned for Xeon AVX512/AMX."""
|
| 530 |
-
import onnxruntime as ort # type: ignore
|
| 531 |
-
|
| 532 |
-
n_cores = _NUCLEOS_ALOCADOS or _detect_physical_cores()
|
| 533 |
-
|
| 534 |
-
opts = ort.SessionOptions()
|
| 535 |
-
opts.intra_op_num_threads = n_cores
|
| 536 |
-
opts.inter_op_num_threads = 1
|
| 537 |
-
opts.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
|
| 538 |
-
opts.enable_cpu_mem_arena = True
|
| 539 |
-
return opts
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
def get_xeon_status() -> Dict[str, Any]:
|
| 543 |
-
"""V6: return a snapshot of all Xeon runtime flags + capabilities.
|
| 544 |
-
|
| 545 |
-
Useful for logging inside the training script.
|
| 546 |
-
"""
|
| 547 |
-
amx_ok, amx_desc = get_amx_capability()
|
| 548 |
-
avx_ok, avx_desc = get_avx512_capability()
|
| 549 |
-
return {
|
| 550 |
-
"version": "V6",
|
| 551 |
-
"physical_cores": _NUCLEOS_ALOCADOS or _detect_physical_cores(),
|
| 552 |
-
"env": {
|
| 553 |
-
"MKL_ENABLE_INSTRUCTIONS": os.environ.get("MKL_ENABLE_INSTRUCTIONS"),
|
| 554 |
-
"MKL_NUM_THREADS": os.environ.get("MKL_NUM_THREADS"),
|
| 555 |
-
"OMP_NUM_THREADS": os.environ.get("OMP_NUM_THREADS"),
|
| 556 |
-
"MKL_DYNAMIC": os.environ.get("MKL_DYNAMIC"),
|
| 557 |
-
"DNNL_PRIMITIVE_CACHE_CAPACITY": os.environ.get("DNNL_PRIMITIVE_CACHE_CAPACITY"),
|
| 558 |
-
"ONEDNN_MAX_CPU_ISA": os.environ.get("ONEDNN_MAX_CPU_ISA"),
|
| 559 |
-
"KMP_AFFINITY": os.environ.get("KMP_AFFINITY"),
|
| 560 |
-
"KMP_BLOCKTIME": os.environ.get("KMP_BLOCKTIME"),
|
| 561 |
-
},
|
| 562 |
-
"avx512": {"supported": avx_ok, "desc": avx_desc},
|
| 563 |
-
"amx": {"supported": amx_ok, "desc": amx_desc},
|
| 564 |
-
"ipex_available": _IPEX_AVAILABLE is True,
|
| 565 |
-
"init_done": _V6_INIT_DONE,
|
| 566 |
-
}
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
__all__ = [
|
| 570 |
-
"optimize_xeon_environment",
|
| 571 |
-
"apply_ipex_optimization",
|
| 572 |
-
"fp16_autocast",
|
| 573 |
-
"benchmark_fp16_matmul",
|
| 574 |
-
"benchmark_int8_matmul",
|
| 575 |
-
"ativar_amx_na_vm",
|
| 576 |
-
"get_avx512_capability",
|
| 577 |
-
"get_amx_capability",
|
| 578 |
-
"get_xeon_status",
|
| 579 |
-
"get_allocated_cores",
|
| 580 |
-
"make_ort_session_options",
|
| 581 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|