File size: 8,451 Bytes
4dbc907
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#!/usr/bin/env python3
"""61_treino_v070.py — v0.7.0: treino REAL (fases A/B/C) + árvore de pesos
indexada com métricas por módulo + FNH com punição REAL da fase C + painel
de telemetria v070 (r-28 §5, r-32).

Pipeline com dados REAIS (Wikipédia PT-BR, streaming controlado):
  fase A — tokenizador GSQ+RCO (300 passos);
  fase B — gerador NCM (300 passos, teto 300s);
  fase C — treino conjunto (180 passos, teto 420s) — telemetria de punição;
  árvore — contagens de parâmetros por módulo (estimativas documentadas)
           + métricas REAIS por módulo da telemetria das fases;
  FNH    — gate alimentado pela punição NORMALIZADA medida na fase C.

Modo RESUME: se a telemetria da fase C já existe com dump final, extrai as
métricas dela (não re-treina — o fundo pode matar processos sem erro/OOM).
Saída: resultados/treino_v070.json
"""
import json, os, sys, time

CONTAGENS = {  # estimativas documentadas (a árvore INDEXA; métricas medem)
    "tronco": 10_000_000, "quantizador": 1_200_000, "som": 300_000,
    "jepa": 900_000, "moe": 800_000, "pensamento": 400_000,
    "verificador": 10_000, "fnh": 150_000, "nlp": 300_000,
    "nlg": 250_000, "acoplador": 60_000,
}

def extrai_fase_c(caminho):
    """Extrai as métricas do dump FINAL da telemetria da fase C."""
    with open(caminho) as f:
        linhas = [json.loads(l) for l in f]
    dumps = [l["dados"] for l in linhas if l.get("tipo") == "dump"]
    if not dumps:
        return None
    d = dumps[-1]
    def serie(k):
        return d.get(k, []) if isinstance(d.get(k), list) else []
    def v(k, i=-1):
        s = serie(k)
        return s[i][1] if s else 0.0
    n_passos = len(serie("perda"))
    return {
        "passos": n_passos,
        "ce_inicial": v("perda", 0), "ce_final": v("perda"),
        "ce_gatilhada_com_final": v("ce_gatilhada_com"),
        "ce_gatilhada_sem_final": v("ce_gatilhada_sem"),
        "confianca_inicial": v("confianca_media", 0),
        "confianca_final": v("confianca_media"),
        "confianca_pos_correcao_final": v("confianca_pos_correcao"),
        "taxa_gatilho_media": (sum(x[1] for x in serie("taxa_gatilho"))
                               / max(1, len(serie("taxa_gatilho")))),
        "ece_final": v("ece_pos_calibracao"),
        "temperatura_calibrada": v("temperatura_calibrada"),
        "norma_grad_final": v("norma_grad"),
    }

def main():
    pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.insert(0, pasta)
    os.makedirs(os.path.join(pasta, "resultados"), exist_ok=True)
    import khtst_rust as K

    cfg = K.Config(os.path.join(pasta, "configs", "base_rust.json"))
    ck_tok = os.path.join(pasta, "resultados", "tokenizador_conceitos.json")
    ck_ger = os.path.join(pasta, "resultados", "gerador_conceitos.json")
    tel_c = os.path.join(pasta, "resultados", "telemetria_fase_c.jsonl")

    bpe = K.Bpe.carregar(os.path.join(pasta, "dados", "bpe.json"))
    seqs = []
    with open(os.path.join(pasta, "dados", "shard_treino.jsonl")) as f:
        for linha in f:
            ids = bpe.codificar(json.loads(linha)["texto"])
            for i in range(0, max(1, len(ids) - 256), 128):
                seqs.append(ids[i:i + 256])
    seqs = seqs[:1400]
    rel = {"versao": "0.7.0", "dados": {"sequencias": len(seqs),
          "fonte": "wikimedia/wikipedia 20231101.pt"}, "fases": {}}
    print(f"sequências de treino: {len(seqs)} (ctx=256)", flush=True)

    # -------------------------------- fase A
    if not os.path.exists(ck_tok):
        print("=== FASE A — tokenizador (GSQ+RCO Stiefel) ===", flush=True)
        t0 = time.time()
        saida = K.treinar_fase_a(cfg, seqs, caminho_telemetria=None,
                                 caminho_checkpoint=ck_tok)
        rel["fases"]["a"] = {**saida["resumo"], "tempo_s": time.time() - t0}
    else:
        print("fase A: checkpoint presente — reutilizando", flush=True)

    # -------------------------------- fase B
    if not os.path.exists(ck_ger):
        print("=== FASE B — gerador NCM ===", flush=True)
        t0 = time.time()
        saida_b = K.treinar_fase_b(cfg, seqs, caminho_tokenizador=ck_tok,
                                   caminho_telemetria=None,
                                   caminho_checkpoint=ck_ger, teto_seg=300.0)
        rel["fases"]["b"] = {**saida_b["resumo"], "tempo_s": time.time() - t0}
    else:
        print("fase B: checkpoint presente — reutilizando", flush=True)

    # -------------------------------- fase C (com resume)
    resume = None
    if os.path.exists(tel_c):
        resume = extrai_fase_c(tel_c)
        if resume and resume.get("passos", 0) >= 100:
            print(f"fase C: telemetria presente ({resume['passos']} passos) — "
                  "extraindo métricas (resume)", flush=True)
    if resume is None:
        print("=== FASE C — treino conjunto (punição p/ FNH) ===", flush=True)
        t0 = time.time()
        saida_c = K.treinar_fase_c(cfg, seqs, caminho_tokenizador=ck_tok,
                                   passos=180, caminho_gerador=ck_ger,
                                   caminho_telemetria=tel_c, teto_seg=420.0)
        rc = saida_c["resumo"]
        rc["tempo_s"] = time.time() - t0
        rel["fases"]["c"] = rc
    else:
        rel["fases"]["c"] = resume
    rc = rel["fases"]["c"]
    print(f"  CE {rc.get('ce_inicial', 0):.4f} -> {rc.get('ce_final', 0):.4f} | "
          f"conf {rc.get('confianca_inicial', 0):.4f} -> {rc.get('confianca_final', 0):.4f} | "
          f"gatilho {rc.get('taxa_gatilho_media', 0):.3f} | ECE {rc.get('ece_final', 0):.4f}",
          flush=True)

    # -------------------------------- árvore de pesos (v0.7.0)
    print("=== ÁRVORE DE PESOS — métricas por módulo ===", flush=True)
    arv = K.ArvorePesos.nova(CONTAGENS)
    assert arv.invariantes_ok(), "T-A1 invariantes"
    arv.definir_metrica("tronco", "confianca", rc.get("confianca_final", 0.0))
    arv.definir_metrica("quantizador", "perda",
                        rel["fases"].get("a", {}).get("perda_final", 0.0))
    arv.definir_metrica("quantizador", "punicao",
                        rel["fases"].get("a", {}).get("delta_ort_max", 0.0) * 1e6)
    arv.definir_metrica("nlg", "perda", rel["fases"].get("b", {}).get("perda_final", 0.0))
    arv.definir_metrica("pensamento", "confianca",
                        rc.get("confianca_pos_correcao_final", 0.0))
    # punição do pensamento: ECE (erro de calibração) + (1 − taxa_gatilho)
    arv.definir_metrica("pensamento", "punicao",
                        rc.get("ece_final", 0.0) * 5.0)
    arv.definir_metrica("verificador", "punicao", rc.get("ece_final", 0.0))
    tot_conf = arv.agregar("confianca")
    tot_puni = arv.agregar("punicao")
    arv.regular("khtst", 0.1, 0.5)  # T-A3 exemplo de regulação
    rel["arvore"] = {
        "invariantes": arv.invariantes_ok(),
        "total_confianca": tot_conf, "total_punicao": tot_puni,
        "dump": arv.dump(),
    }
    print(f"  invariantes OK | Σconf={tot_conf:.4f} Σpunicao={tot_puni:.4f}", flush=True)

    # -------------------------------- FNH (v0.7.0) — punição REAL
    print("=== FNH — gate de punição ===", flush=True)
    # punição normalizada: ECE da calibração (0 = perfeito) mapeado por
    # 5× (teto ECE aceitável 0,2) — sinal REAL da fase C
    pun_norm = min(1.0, rc.get("ece_final", 0.0) / 0.2)
    fnh = K.demo_fnh()
    rel["fnh"] = {
        "punicao_normalizada_fase_c": pun_norm,
        "abacao_sintetica": fnh["resumo"],
        "gates": {
            "gate_abre_com_punicao_alta":
                fnh["resumo"]["punicao_alta"]["gate_final"] > 0.5,
            "gate_fecha_com_punicao_baixa":
                fnh["resumo"]["punicao_baixa"]["gate_final"] == 0.0,
            "rotacao_aprende": fnh["resumo"]["punicao_alta"]["theta_norma"] > 0.0,
            "so_n_exato": fnh["resumo"]["punicao_alta"]["delta_ort"] < 1e-6,
        },
    }
    print(f"  π(C)={pun_norm:.3f} | alta: gate {fnh['resumo']['punicao_alta']['gate_final']:.3f}, "
          f"θ {fnh['resumo']['punicao_alta']['theta_norma']:.4f}, "
          f"δ_ort {fnh['resumo']['punicao_alta']['delta_ort']:.1e}", flush=True)

    with open(os.path.join(pasta, "resultados", "treino_v070.json"), "w") as f:
        json.dump(rel, f, indent=1, ensure_ascii=False)
    print("OK: resultados/treino_v070.json", flush=True)

if __name__ == "__main__":
    main()