PowerMachine commited on
Commit
4dbc907
·
verified ·
1 Parent(s): bb2363d

v0.7.0: FNH gate de punição + árvore de pesos indexada (arestas duplas) + quebra-cabeça do Pensamento + NLP/NLG + decodificador especulativo + auto-ajuste SOM limitado + correções v070-1/2/3 — 73/73 gates, 84/84 cargo, provas r-28..r-32

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .cargo/config.toml +2 -0
  2. .gitattributes +2 -0
  3. Cargo.lock +1 -1
  4. Cargo.toml +1 -1
  5. docs/memorias/r-28-analise-repos-design-v070.md +194 -0
  6. docs/memorias/r-29-provas-fnh-rotacao-v070.md +203 -0
  7. docs/memorias/r-30-provas-arvore-quebra-cabeca-v070.md +205 -0
  8. docs/memorias/r-31-provas-nlp-nlg-especulativo-v070.md +185 -0
  9. docs/memorias/r-32-resultados-v070.md +126 -0
  10. pyproject.toml +1 -1
  11. resultados/arvore_quebra_v070.json +199 -0
  12. resultados/gerador_conceitos.json +3 -0
  13. resultados/suite_fnh_v070.json +32 -0
  14. resultados/suite_nlp_nlg_v070.json +65 -0
  15. resultados/suite_v070_final.json +87 -0
  16. resultados/telemetria_fase_c.jsonl +0 -0
  17. resultados/telemetria_v070.json +555 -0
  18. resultados/tokenizador_conceitos.json +3 -0
  19. resultados/treino_v070.json +182 -0
  20. scripts/61_treino_v070.py +177 -0
  21. scripts/62_suite_fnh.py +72 -0
  22. scripts/63_arvore_quebra.py +78 -0
  23. scripts/64_suite_nlp_nlg.py +54 -0
  24. scripts/65_telemetria_v070.py +93 -0
  25. scripts/66_suite_v070.py +74 -0
  26. scripts/67_publicar_v070.py +158 -0
  27. src/amostrador.rs +56 -5
  28. src/arvore_pesos.rs +395 -0
  29. src/camadas.rs +7 -0
  30. src/controlador.rs +163 -0
  31. src/fnh.rs +601 -0
  32. src/lib.rs +438 -1
  33. src/nlg.rs +431 -0
  34. src/nlp.rs +252 -0
  35. src/pensamento.rs +361 -1
  36. src/stiefel.rs +17 -0
  37. src/telemetria.rs +63 -0
  38. src/treino.rs +9 -4
  39. src/tronco.rs +36 -8
  40. target/.rustc_info.json +1 -0
  41. target/release/.cargo-artifact-lock +0 -0
  42. target/release/.cargo-build-lock +0 -0
  43. target/release/.cargo-lock +0 -0
  44. target/release/.fingerprint/ahash-1239384bf8e0b488/dep-lib-ahash +0 -0
  45. target/release/.fingerprint/ahash-1239384bf8e0b488/invoked.timestamp +1 -0
  46. target/release/.fingerprint/ahash-1239384bf8e0b488/lib-ahash +1 -0
  47. target/release/.fingerprint/ahash-1239384bf8e0b488/lib-ahash.json +1 -0
  48. target/release/.fingerprint/ahash-83fe43707d427afc/build-script-build-script-build +1 -0
  49. target/release/.fingerprint/ahash-83fe43707d427afc/build-script-build-script-build.json +1 -0
  50. target/release/.fingerprint/ahash-83fe43707d427afc/dep-build-script-build-script-build +0 -0
.cargo/config.toml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [build]
2
+ jobs = 2
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ resultados/gerador_conceitos.json filter=lfs diff=lfs merge=lfs -text
37
+ resultados/tokenizador_conceitos.json filter=lfs diff=lfs merge=lfs -text
Cargo.lock CHANGED
@@ -734,7 +734,7 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
734
 
735
  [[package]]
736
  name = "khtst-rust"
737
- version = "0.6.0"
738
  dependencies = [
739
  "anyhow",
740
  "built",
 
734
 
735
  [[package]]
736
  name = "khtst-rust"
737
+ version = "0.7.0"
738
  dependencies = [
739
  "anyhow",
740
  "built",
Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
  [package]
2
  name = "khtst-rust"
3
- version = "0.6.0"
4
  edition = "2021"
5
  description = "KHTST_RUST — NCM com verificador calibrado, treino conjunto Pensamento↔backbone, MoEs redirecionáveis com atenção própria e autoajuste por telemetria"
6
  license = "Apache-2.0"
 
1
  [package]
2
  name = "khtst-rust"
3
+ version = "0.7.0"
4
  edition = "2021"
5
  description = "KHTST_RUST — NCM com verificador calibrado, treino conjunto Pensamento↔backbone, MoEs redirecionáveis com atenção própria e autoajuste por telemetria"
6
  license = "Apache-2.0"
docs/memorias/r-28-analise-repos-design-v070.md ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # r-28 — Análise dos repositórios PowerMachine e design da v0.7.0
2
+
3
+ Data: 2026-10-28 · Escopo: aprimoramentos da v0.7.0 a partir da análise das
4
+ introduções dos scripts de 4 repositórios + port da `fnh_layer.py` + módulos
5
+ NLG/NLP + árvore de pesos indexada + quebra-cabeça do Pensamento.
6
+
7
+ ## 1. Auditoria da v0.6.0 (pré-requisito — 3 bugs reais corrigidos)
8
+
9
+ A reconstrução da baseline revelou que `tests/provas.rs` estava obsoleto
10
+ (v0.3.0 — APIs removidas). Ao reativar os gates de integração com as APIs
11
+ atuais, **três desvios reais** foram encontrados e corrigidos:
12
+
13
+ | id | achado | evidência | correção |
14
+ |---|---|---|---|
15
+ | v070-1 | FiLM sem `W_f` zero-init: r-04 §1 especifica `x̃ = x⊙(1+γ·tanh(W_f·h)) + β·(W_f·h)` com W_f=0 no nascimento; a v0.6.0 injetava o resumo do âncora DIRETO (projeção aleatória N(0,0.02)) | gate G5 falhava com dif=2,19 (níveis ≥ 2 perturbados no nascimento) | `Tronco::film_w: Option<Linear>` com `Linear::zeros_sem_vies` — identidade bit-a-bit para QUALQUER resumo (Teo R.2 restaurado) |
16
+ | v070-2 | FiLM aplicado SÓ em `forward_treino`; a geração incremental (`forward_nivel`) não o aplicava | violação de paridade treino↔inferência em TODAS as gerações v0.5/v0.6 (adba.ativo=true no config base) | `forward_nivel` ganhou `film: Option<(&Tensor, usize)>` + captura de estados pós-bloco-0; `AmostradorConceitos::gerar` acumula `estados_b0` por ramo (cond/uncond) e calcula o resumo-âncora exatamente como `forward_treino` (que usa o x PRÉ-FiLM — o cat é tardio) |
17
+ | v070-3 | `pcgrad_achatado` projetava g_i contra grads[j] JÁ MODIFICADOS (j<i) | gate M8 falhava: ⟨g_comb, g_1⟩ = −0,1 < 0 (não-piora violada); a versão tensor (`perdas.rs::pcgrad`) estava correta | snapshot dos originais antes do laço; projeção contra `originais[j]` (Yu et al. 2020, Algoritmo 1) |
18
+
19
+ Resultado: **56/56 testes** (54 inline + 2 gates de integração reativados).
20
+ Nenhum estado de treino existia (todos apagados), então a mudança de
21
+ arquitetura do W_f não invalida checkpoints.
22
+
23
+ ## 2. Análise das introduções dos 4 repositórios
24
+
25
+ ### 2.1 gru-ring-v13-9-2 (fonte da FNH — alvo do port Rust)
26
+
27
+ `fnh_layer.py` ("Flexible Network of Hypotheses", Teorema 4.2/4.3):
28
+
29
+ ```
30
+ x_rot = x @ R_Atv^T (rotação aprendível na ativação)
31
+ W_eff = R_wp @ W_smooth (rotação aprendível no peso + suavização)
32
+ z = x_rot @ W_eff^T
33
+ y = Atv(z) + bias
34
+ ```
35
+
36
+ - `SkewRotation`: R = expm(A), A skew-simétrica parametrizada por
37
+ θ ∈ R^{n(n−1)/2}; série de Taylor com 3 termos e clamp θ ≤ 0,5;
38
+ `orthogonality_error()` = ‖R^T R − I‖_F.
39
+ - `SmoothQuantCompressor` (Lema 4.1, Xiao et al. 2022):
40
+ s_j = max|X_j|^α / max|W_j|^(1−α), W_smooth = W·diag(s), X_smooth = X/diag(s)
41
+ — migra a escala da ativação para o peso (equilíbrio de canais).
42
+ - `FNHLayer`: ativações flexíveis (gelu/silu/mish/selu/relu/learnable).
43
+
44
+ **Decisão v0.7.0**: port para `fnh.rs` com DUAS melhorias sobre o original,
45
+ exigidas pelas provas da casa:
46
+ (a) **Ortogonalidade EXATA** em vez de truncada: a série de 3 termos do
47
+ original NÃO garante R ∈ SO(n) (erro O(θ⁴) por termo truncado — com θ=0,5 o
48
+ erro pode alcançar ~10⁻²); usamos a retração polar de `stiefel.rs`
49
+ (Newton–Schulz, já provada no projeto) sobre a exponencial truncada, dando
50
+ ‖R^T R − I‖_F ≤ 10⁻⁶ comprovado por teste.
51
+ (b) **Gate de punição** (requisito do usuário): a camada só ativa quando a
52
+ punição normalizada π_t excede limiar θ_π — `g = σ((π_t − θ_π)/τ_g)` — e o
53
+ efeito é convexo: `W_eff = (1−g)·W + g·R_wp·(W·diag(s))`, nascendo neutra
54
+ (g=0 ⇒ bit-a-bit W). O original era incondicional.
55
+
56
+ ### 2.2 BiGRU_T_version (hyp_t.py + som_auto_adjust.py)
57
+
58
+ **hyp_t.py** (V6.5-V3 — ensemble sinérgico de hipóteses): 4 pilares —
59
+ (1) DIVERSIDADE: k-means++ D² scoring na inicialização; (2) COOPERAÇÃO:
60
+ perda de coerência penaliza deltas que divergem do baricentro; (3) COMPETIÇÃO:
61
+ softmax gating winner-takes-more; (4) ORTOGONALIDADE: Gram-Schmidt suave
62
+ (penalidade na matriz de Gram G = DD^T). Protocolo em 2 fases: FASE1
63
+ (conhecimento — só o SOM organiza), FASE2 (punição — HypT gera deltas
64
+ corretivos quando o BMU não representa bem a entrada).
65
+
66
+ **som_auto_adjust.py** (V6.5-V2): auto-ajuste dos SOMs pelas 4 métricas
67
+ (QE/TE/KL/VE) + 4 indicadores de falha (colapso topológico, neurônios mortos,
68
+ estagnação de QE, cruzamento de vizinhança); boosts multiplicativos com
69
+ tetos: α₀_eff ∈ [α₀, 2α₀], σ₀_eff ∈ [σ₀, 2σ₀]; detecção de stop-learning
70
+ (parar quando o modelo não está aprendendo).
71
+
72
+ **Decisão v0.7.0**: herdar (i) os **tetos de boost** para o AutoAjuste do
73
+ SOM (limites formais que faltavam ao `controlador.rs`), (ii) o critério de
74
+ **stop-learning** como gate de segurança do treino (já tínhamos parada por
75
+ marginal em r-26; adicionamos os 5 gatilhos catalogados), (iii) o pilar de
76
+ **ortogonalidade** como penalidade de Gram no quebra-cabeça do Pensamento
77
+ (diversidade dos candidatos — ver §4.3). As fases 1/2 do hyp_t já têm
78
+ equivalente: fase A/B (conhecimento) e fase C (punição conjunta).
79
+
80
+ ### 2.3 aurora-multimodal-ptbr (decodificador_especulativo.py)
81
+
82
+ Medusa em árvore: rascunho = MTP (K cabeças, logits EMPATADOS), verificação
83
+ em UM forward com correção residual, aceitação TÍPICA determinística
84
+ (Heim 2022 eq. 14.2: aceita sse −log p(t) − H ≤ τ·H), poda por cabeça
85
+ c_k = min(c, max(1, c−k)), EMA de estatísticas (0,9/0,1), guarda contra
86
+ laço infinito. Teoremas: 14.1 (inocuidade — a especulação nunca muda a
87
+ distribuição, só o custo), 14.2 (speedup > 1 sse ρ > |T|/(V_ef+|T|)).
88
+
89
+ **Decisão v0.7.0**: integrar ao `nlg.rs` — nosso `gerador.rs` já tem MTP
90
+ com logits empatados (rascunho pronto); a verificação usa a MESMA política
91
+ de punição/filtro da geração normal. Prova de inocuidade reemitida no
92
+ contexto NCM (r-32).
93
+
94
+ ### 2.4 khtst-multimodal-ptbr (unidade_nlg.py / unidade_nlp.py / doc 15)
95
+
96
+ **UnidadeNLP** (especialista de entrada): (i) traços morfo-sintáticos por
97
+ conv de n-gramas (k=3 e k=5, groups=4); (ii) cabeça de intenção (tabela
98
+ CANÔNICA de 12 tarefas + "nlp-outro" — fonte única, Teo 22.1); (iii)
99
+ apontador de entidades por posição. Enriquecimento ADITIVO com porta:
100
+ h⁺ = h + g⊙Δ(h), g = σ(W_g h), bias −2 (g≈0,12), W_Δ zero-init (Δ=0).
101
+ Teorema 15.1 (não-interferência).
102
+
103
+ **UnidadeNLG** (especialista de saída): (i) vetor de PLANO por pooling do
104
+ prompt; (ii) re-ranking de coerência com tabela EMA de bigramas (hashing
105
+ trick, O(|V_cand|)); (iii) condicionamento de estilo h_dec = h + W_estilo·p,
106
+ p ∈ R^8, W_estilo zero-init. SEM parâmetros da lm_head (modula o estado
107
+ oculto — evita duplicar 16384×192).
108
+
109
+ **Decisão v0.7.0**: port integral para `nlp.rs`/`nlg.rs` com as mesmas
110
+ garantias (Teo 15.1 reemitido como N-1 em r-32), integrados ao
111
+ GeradorConceitos (o tronco do KHTST_RUST) e ao amostrador (re-ranking de
112
+ bigramas como critério adicional no MBR).
113
+
114
+ ## 3. Requisitos do usuário → módulos v0.7.0
115
+
116
+ | requisito | módulo | provas |
117
+ |---|---|---|
118
+ | FNH em Rust, ativa quando punição alta, autorregula params treináveis | `fnh.rs` | r-29 (F-1..F-6) |
119
+ | árvore de pesos e subárvores indexada em TODO o projeto | `arvore_pesos.rs` | r-30 (T-A1..T-A5) |
120
+ | Pensamento: quebra-cabeça pós-treino, válido ajusta árvore bidirecional, inválido descarta | extensão `pensamento.rs` | r-31 (Q-1..Q-6) |
121
+ | módulos NLG e NLP integrados | `nlp.rs` + `nlg.rs` | r-32 (N-1..N-4, E-1..E-2) |
122
+ | rotação e ortogonalidade em treino e Pensamento (integrar se ganho) | `fnh.rs` + quebra-cabeça ortogonal + ablação 62 | r-29 §5, r-33 |
123
+ | telemetria em todo o projeto | `telemetria.rs::painel_v070` | r-33 |
124
+ | provas em markdown organizadas | docs/memorias/ | r-28..r-33 |
125
+ | datasets reais para avaliação qualificada | Wikipédia PT-BR (streaming controlado) | scripts 61-66 |
126
+
127
+ ## 4. Especificação dos módulos novos
128
+
129
+ ### 4.1 fnh.rs — CamadaFNH (port melhorado)
130
+
131
+ ```
132
+ punição π_t = (λ_ort·δ_ort + λ_anc·viol + conflito_PCGrad) / π_max (normalizada)
133
+ gate g_t = σ((π_t − θ_π)/τ_g) (0 se π baixa)
134
+ s_j = max|X_j|^α / max|W_j|^(1−α) (EMA, α=0,5) (SmoothQuant)
135
+ R_wp, R_Atv = retrair_polar(expm_trunc(A(θ))) (SO(n) exato)
136
+ W_eff = (1−g)·W + g·(R_wp @ (W·diag(s))) (convexo)
137
+ y = Atv(x @ R_Atv^T @ W_eff^T) + b
138
+ ```
139
+
140
+ Treináveis: θ_wp, θ_atv (rotações), W, b. Autorregulação: quando π_t sobe,
141
+ g→1 e as rotações aprendem a realinhar as direções principais do peso e a
142
+ suavização equilibra os canais — o gradiente flui pelos θ (C-1 generalizado).
143
+
144
+ ### 4.2 arvore_pesos.rs — Árvore Enraizada com Arestas Duplas indexada
145
+
146
+ - Nós = módulos (raiz "khtst", filhos: tronco, quantizador, som, jepa, moe,
147
+ pensamento, verificador, fnh, nlp, nlg, acoplador); cada nó possui um
148
+ intervalo [ini, fim) do índice global de parâmetros (partição).
149
+ - Arestas duplas: (pai→filho, filho→pai) com o MESMO id — regulação desce,
150
+ crédito/telemetria sobe; `vizinho(no, aresta)` O(1) por índice.
151
+ - Operações: `agregar_fundo_topo` (soma das métricas dos filhos — indução),
152
+ `regular_topo_fundo` (propaga ajustes com decaimento por profundidade),
153
+ `subarvore(no)` (fatia do índice), `caminho(no, raiz)`.
154
+
155
+ ### 4.3 pensamento.rs — Quebra-cabeça pós-treino
156
+
157
+ ```
158
+ 1. P = particao_aleatoria(conceitos_treinados, K peças) (RngDeterministico)
159
+ 2. C = remontar(P, permutações π ∈ S_K e composições ortogonais Q_j)
160
+ 3. para cada candidato c: conf = VerificadorNcm.confianca(c) (V-2: soundness)
161
+ válido (conf ≥ τ_c): ajuste δ na árvore de pesos nos nós indexados
162
+ pelas peças de c — passo regulado ‖δ‖ ≤ Δ_max,
163
+ agregação fundo-topo atualiza os pais
164
+ inválido (conf < τ_c): descarta; nova rodada (novo embaralhamento)
165
+ 4. parada: orçamento B rodadas ou taxa de aceitação < ρ_min
166
+ ```
167
+
168
+ Diversidade (pilar 4 do hyp_t herdado): penalidade de Gram nos candidatos
169
+ gerados por rotações — mantém o conjunto de hipóteses bem condicionado.
170
+
171
+ ### 4.4 nlp.rs / nlg.rs
172
+
173
+ Port fiel das unidades (§2.4) com: `UnidadeNlp::enriquecer(h) -> h + g⊙Δ`;
174
+ `UnidadeNlg::plano(h_prompt)`, `condicionar(h, estilo)`, `reranking_coerencia`
175
+ (tabela EMA de bigramas via hashing) + `DecodificadorEspeculativo` (§2.3).
176
+
177
+ ## 5. Plano de verificação (datasets reais)
178
+
179
+ - 61_treino_v070: fases A/B/C reais (Wikipédia PT-BR) + fase D (FNH+NLP+NLG+
180
+ árvore ativas, punição monitorada);
181
+ - 62_suite_fnh: ablação baseline × FNH-gate (ganho em CE gatilhada + Δπ),
182
+ rotação/ortogonalidade em TREINO;
183
+ - 63_arvore_quebra: árvore (invariantes + agregação) + quebra-cabeça sobre o
184
+ modelo TREINADO (aceitação, ganho de confiança, ajustes na árvore);
185
+ - 64_suite_nlp_nlg: gates neutros + coerência de bigramas + intenção +
186
+ speedup especulativo;
187
+ - 65_telemetria_v070: painel completo (todos os módulos, polaridade);
188
+ - 66_suite_v070: consolidador de gates (herda 46 da v0.6.0 + novos);
189
+ - 67_publicar_v070: UM upload_folder organizado + limpeza de estados + token.
190
+
191
+ Critério de integração da rotação/ortogonalidade (requisito explícito):
192
+ integrar permanentemente **sse** a ablação 62/63 demonstrar ganho (CE ou
193
+ confiança) sem regressão dos gates legados; caso contrário, documentar como
194
+ feature opt-in neutra (o gate de punição permite desativar sem custo).
docs/memorias/r-29-provas-fnh-rotacao-v070.md ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # r-29 — Provas da FNH (Flexible Network of Hypotheses) e da rotação/ortogonalidade — v0.7.0
2
+
3
+ Módulos: `src/fnh.rs` (port melhorado de `gru-ring-v13-9-2/fnh_layer.py`),
4
+ `src/stiefel.rs::retrair_polar_frobenius`, `src/controlador.rs::AutoAjusteSom`.
5
+ Convenção: ∎ fecha demonstração; "teste X" aponta o gate executável.
6
+
7
+ ## 1. Estrutura da camada (r-28 §4.1)
8
+
9
+ ```
10
+ π_t = punição normalizada ∈ [0,1] (medida ANTES do passo)
11
+ g_t = max(0, 2σ((π_t − θ_π)/τ_g) − 1) (sigmoide retificada)
12
+ s_j = max|X_j|^α / max|W_j|^(1−α) (EMA β=0,98, α=0,5) (SmoothQuant)
13
+ ŝ_j = s_j / (∏_k s_k)^(1/d) (det-preservante)
14
+ R_wp = P(I + A(θ) + A²/2 + A³/6), A(θ) = U(θ) − U(θ)ᵀ (skew → polar)
15
+ W_eff = W + g·((W·diag(ŝ))·R_wp − W) (forma residual)
16
+ y = Atv((x·R_atvᵀ)·W_effᵀ) + b
17
+ ```
18
+
19
+ ## 2. Teoremas
20
+
21
+ ### Teo F-1 (ortogonalidade EXATA — melhora sobre o original)
22
+
23
+ **Enunciado.** Seja `trunc(A) = I + A + A²/2 + A³/6` a série exponencial
24
+ truncada em 3 termos de A skew-simétrica. Então `R = P(trunc)` — o fator
25
+ polar obtido pela iteração de Newton–Schulz escalada — satisfaz
26
+ ‖RᵀR − I‖_F ≤ 10⁻⁶ (f32), enquanto a série TRUNCADA sozinha satisfaz
27
+ apenas ‖truncᵀ·trunc − I‖_F = O(‖A‖⁴/4!) — com clamp θ ≤ 0,5 e n = 12,
28
+ o erro do original alcança ~10⁻².
29
+
30
+ **Demonstração.** (i) expm(A) ∈ SO(n) exatamente (A skew ⇒ ortogonal por
31
+ colunas com det = +1 — série inteira). (ii) O truncamento de ordem 3 deixa
32
+ resíduo ‖trunc − expm(A)‖ = O(‖A‖⁴/24). (iii) O fator polar P(M) de uma
33
+ matriz M não-singular é a matriz ortogonal MAIS PRÓXIMA em norma de
34
+ Frobenius (Higham, *Functions of Matrices*, Teo 8.4): ‖P(M) − M‖_F ≤
35
+ ‖Q − M‖_F ∀ Q ∈ O(n). Como o Newton–Schulz escalado converge
36
+ quadraticamente ao fator polar quando σ_max(Y₀) < √3, e `retrair_polar`
37
+ escala por estimativa espectral antes da iteração, R = P(trunc) com
38
+ δ_ort ≤ 10⁻⁶ em ≤ 40 iterações. (iv) det R = +1: P(M) = M(MᵀM)^{−1/2} ⇒
39
+ det P = det M/|det M| = sign(det M); trunc está a O(‖A‖⁴) de expm(A) com
40
+ det expm(A) = 1 e I + A + ... é não-singular para ‖A‖ pequeno
41
+ (clamp θ ≤ 0,5 ⇒ ‖A‖_F ≤ 0,5·√(n(n−1)/2) mas o termo dominante I mantém
42
+ os valores singulares ≥ 1 − O(‖A‖) > 0) ⇒ sign(det trunc) = +1. ∎
43
+ **Teste:** `f1_rotacao_so_n_exata` — δ_ort < 10⁻⁶ com θ aleatório ≤ 0,5;
44
+ R = I bit-a-bit com θ = 0.
45
+
46
+ ### Teo F-2 (nascimento neutro bit-a-bit)
47
+
48
+ **Enunciado.** Com θ_wp = θ_atv = 0 (inicialização) e qualquer π_t:
49
+ `forward(x, π) = Atv(x·Wᵀ) + b` EXATAMENTE (dif = 0 em f32), para
50
+ `equilibrar = false` (padrão) ou `equilibrar = true` sem calibração.
51
+
52
+ **Demonstração.** θ = 0 ⇒ A = 0 (construção por matriz de seleção:
53
+ U(0) = 0 ⇒ A = U − Uᵀ = 0 bit-a-bit) ⇒ trunc = I + 0 + 0 + 0 = I exato.
54
+ O atalho de nascimento devolve o PRÓPRIO trunc (valor I, grafo conectado
55
+ a θ — sem deadlock de gradiente): W_rot = W·I = W exato (matmul com
56
+ identidade preserva cada elemento: soma de produtos com 0s e um 1).
57
+ **Forma residual:** W_eff = W + g·(W_rot − W) = W + g·0 = W bit-a-bit
58
+ para QUALQUER g — ao contrário da forma convexa (1−g)W + g·W_rot que
59
+ acumula erro de arredondamento ~10⁻⁹ (0,036 + 0,964 ≠ 1 em f32; ver
60
+ r-28 §1 v070-1). Finalmente ŝ sem calibração é indefinida ⇒ o ramo de
61
+ equilíbrio é pulso (guarda `s.len() == d_in`). ∎
62
+ **Teste:** `f2_nascimento_neutro` — dif = 0 para π ∈ {0; 0,3; 0,85; 1}.
63
+
64
+ ### Teo F-3 (gate: zona morta exata e monotonia)
65
+
66
+ **Enunciado.** g(π) = max(0, 2σ((π−θ_π)/τ) − 1) é contínua, não-decrescente,
67
+ g(θ_π) = 0 exato e g(π) = 0 ∀ π ≤ θ_π; g → 1 quando π → +∞.
68
+
69
+ **Demonstração.** σ é estritamente crescente; 2σ−1 anula-se em z = 0
70
+ (σ(0) = 1/2); o max com 0 preserva monotonia (composição de funções
71
+ não-decrescentes) e zera o ramo negativo. Continuidade em θ_π: o ramo
72
+ esquerdo é 0 = valor-limite do direito. ∎
73
+ **Teste:** `f3_gate_monotono` — g ∈ {0,3; 0,6} = 0 exato; g(1) > 0,95.
74
+
75
+ ### Teo F-4 (homeostase — autorregulação causal)
76
+
77
+ **Enunciado.** (i) O gradiente de qualquer perda L w.r.t. θ é EXATAMENTE
78
+ zero quando π_t ≤ θ_π (rotações congeladas); (ii) é não-nulo em geral
79
+ quando π_t > θ_π e θ ≠ 0; (iii) π_t é função da filtração F_t (medida
80
+ antes do passo — sem informação futura).
81
+
82
+ **Demonstração.** (i) A cadeia computacional de L a θ passa por W_eff =
83
+ W + g·(W_rot(θ) − W): ∂L/∂θ = g·(∂L/∂W_rot)·(∂W_rot/∂θ). Com g = 0
84
+ (zona morta exata do Teo F-3 — sem ε de ponto flutuante), o produto é o
85
+ vetor nulo BIT-A-BIT. (ii) Com g > 0 e ∂W_rot/∂θ ≠ 0 (A ≠ 0 na
86
+ parametrização skew: ∂A/∂θ_k = E_k − E_kᵀ ≠ 0), o gradiente flui salvo
87
+ casos degenerados de perda (medida: teste). (iii) π_t é computada das
88
+ penalidades do passo t−1 (λ_ort·δ_ort + âncora + conflito PCGrad) antes
89
+ da avaliação do forward — mensurável w.r.t. F_t. O loop homeostático:
90
+ π alto → g abre → θ aprende a realinhar → punição cai → g fecha → θ
91
+ congela (regulação em closed-loop com atraso 1 — a mesma estrutura do
92
+ AcopladorDuplo AC-1). ∎
93
+ **Teste:** `f4_homeostase_gradiente` — ‖∂L/∂θ‖ = 0 com π = 0; > 0 com
94
+ π = 1 e θ ≠ 0.
95
+
96
+ ### Teo F-5 (interferência limitada — Lipschitz no gate)
97
+
98
+ **Enunciado.** ‖W_eff − W‖_F ≤ g·(‖W·diag(ŝ)·R_wp − W‖_F) ≤
99
+ g·(‖W‖_F·(‖diag(ŝ)‖₂·‖R_wp‖₂ + 1)) ≤ 2g·‖W‖_F·max_j ŝ_j
100
+ (usando ‖R_wp‖₂ = 1 — Teo F-1, e ‖diag(ŝ)‖₂ = max_j |ŝ_j|).
101
+
102
+ **Demonstração.** Da forma residual: W_eff − W = g·(W_rot − W);
103
+ desigualdade triangular + submultiplicatividade; a norma espectral de
104
+ uma rotação é 1 (ortogonalidade exata do F-1). ∎
105
+ Corolário (não-interferência no acoplamento): adicionada a um modelo
106
+ pré-treinado com g = 0 no primeiro passo, a FNH não muda a saída
107
+ (F-2) e a perturtação cresce continuamente com g — o mesmo padrão dos
108
+ Teos R.2/15.1/M-R2 da casa.
109
+
110
+ ### Teo F-6 (migração compensada é identidade algébrica)
111
+
112
+ **Enunciado.** Para s > 0 qualquer: (X/diag(s))·(W·diag(s))ᵀ = X·Wᵀ.
113
+
114
+ **Demonstração.** (X·D^{−1})·(W·D)ᵀ = X·D^{−1}·D·Wᵀ = X·Wᵀ (D diagonal:
115
+ D^{−1}D = I exato em álgebra; em f32 o erro relativo é
116
+ O(ε·κ(D)) — medido < 10⁻⁴ no teste). É a propriedade do SmoothQuant que
117
+ torna a migração de escala PERDA-NEUTRA pré-quantização. ∎
118
+ **Teste:** `f6_migracao_neutra`.
119
+
120
+ ### Teo F-7 (balanço det-preservante equaliza energias — AM-GM)
121
+
122
+ **Enunciado.** Seja ŝ = s/geomean(s) (∏ŝ_j = 1). Então
123
+ E(ŝ) = Σ_j ŝ_j²‖W_:,j‖²_F, sujeito a ∏ŝ_j = 1, é minimizada quando
124
+ ŝ_j‖W_:,j‖ é constante em j; o balanço REDUZ a razão
125
+ max_j/min_j das energias das colunas.
126
+
127
+ **Demonstração.** Minimizar Σ_j ŝ_j²c_j com vínculo Σ_j ln ŝ_j = 0
128
+ (c_j = ‖W_:,j‖²): Lagrangiano ⇒ 2ŝ_jc_j = λ/ŝ_j ⇒ ŝ_j²c_j = λ/2
129
+ constante — as energias ŝ_j²c_j igualam-se (AM-GM: Σ x_j ≥ d·(∏x_j)^{1/d}
130
+ com igualdade sse constantes; ∏(ŝ_j²c_j) fixado pelo vínculo). A razão
131
+ max/min estritamente diminui a menos que já fosse constante. ∎
132
+ **Teste:** `f7_balanceamento_am_gm` — razão depois < razão antes.
133
+
134
+ ### Teo F-8 (quantização fake com STE explícito)
135
+
136
+ **Enunciado.** A operação out = q + (qc − q).detach() com
137
+ q = round(t/Δ), qc = clamp(q, −qmax, qmax) realiza o estimador
138
+ straight-through: forward = valor clipado; backward = identidade em t.
139
+
140
+ **Demonstração.** Forward: out = q + qc − q = qc. Backward:
141
+ ∂out/∂t = ∂q/∂t + 0 (detach) = 1 (o round é tratado como identidade —
142
+ o adjunto exato de round é 0 q.t.p., logo QUALQUER pass-through é uma
143
+ escolha de STE; a escolha padrão da literatura QAT é a identidade). ∎
144
+ **Teste:** `f8_forward_quantizado_finito`.
145
+
146
+ ## 3. Rotação/ortogonalidade no TREINO e no Pensamento (requisito)
147
+
148
+ - **Treino:** a FNH É o veículo de rotação no treino — as rotações
149
+ aprendem APENAS quando a punição abre o gate (F-4); a ablação
150
+ `{punicação baixa × alta}` do `demo_fnh` mede o efeito: com π alta,
151
+ ‖θ‖ cresce (0 → 0,0897 em 60 passos) mantendo δ_ort ≤ 10⁻⁸; com π
152
+ baixa, θ permanece 0 (homeostase). O ganho medido no backbone real é
153
+ reportado honestamente em r-32 §3 (sem gate de magnitude — postura
154
+ r-07 §4 mantida).
155
+ - **Pensamento:** o quebra-cabeça remonta as peças com mistura ortogonal
156
+ Q ∈ O(d) EXATA (`q_ortogonal_deterministica` — polar de Frobenius,
157
+ determinística; ver r-30 Q-1) — preserva normas (‖xQ‖_F = ‖x‖_F) e
158
+ explora direções (Teo da isometria: rotações preservam distâncias,
159
+ mudam correlações com o avaliador).
160
+ - **Integração permanente (critério do usuário):** a rotação fica
161
+ INTEGRADA por padrão no Pensamento (exploração sem custo de norma) e
162
+ OPT-IN no treino (gate de punição com θ_π = 0,6 — desativa sem custo,
163
+ F-2). A ablação quantificada com dados reais está em r-32.
164
+
165
+ ## 4. Auto-ajuste SOM limitado (herança BiGRU — r-28 §2.2)
166
+
167
+ ### Teo S-AB (boost limitado)
168
+
169
+ **Enunciado.** O mapa α_eff = clamp(α₀(1+0,3·qe)(1−0,2·cross), α₀, 2α₀),
170
+ σ_eff = clamp(σ₀(1+0,4·te)+0,25·cross, σ₀, 2σ₀) satisfaz
171
+ |α_eff − α₀| ≤ α₀ e |σ_eff − σ₀| ≤ σ₀ para TODAS as entradas
172
+ qe, te, cross ∈ [0,1].
173
+
174
+ **Demonstração.** O argumento do clamp é ≥ (1−0,2)α₀ ≥ α₀ (pois
175
+ qe ≥ 0) e ≤ 1,3α₀ ≤ 2α₀; idem σ. O clamp é redundante porém mantido
176
+ como barreira de segurança (defesa em profundidade contra entradas
177
+ fora de contrato). O mapa é SEM ESTADO — sem acúmulo entre chunks ⇒
178
+ recorrência x_{k+1} = f(x_k, sinal) com f limitada não pode divergir
179
+ pelo termo de controle. ∎ **Teste:** `sab_tetos_dos_boosts`.
180
+
181
+ ### Teo S-SL (stop-learning conservador)
182
+
183
+ **Enunciado.** A decisão de parar é F_t-mensurável (função dos contadores
184
+ de chunks consecutivos e flags observados) e NÃO toca nos pesos — parar
185
+ só congela o custo; o estado aprendido é preservado exatamente.
186
+
187
+ **Demonstração.** Cada gatilho é um limiar sobre um contador que reseta
188
+ quando a condição cessa (monotônico por episódio); a decisão é determinística
189
+ da história observada. Como nenhuma operação de peso está no caminho da
190
+ decisão, o efeito de parar em t é idêntico a treinar com lr = 0 a partir
191
+ de t — a curva de perda passada é inalterada. ∎
192
+ **Teste:** `ssl_stop_learning` (5 gatilhos + reset de contadores).
193
+
194
+ ## 5. Custos computacionais (lógica computacional)
195
+
196
+ | operação | complexidade | nota |
197
+ |---|---|---|
198
+ | rotacao() | O(n³) (3 matmuls + polar ~40·n³) | polar dominado; n = d_in ≤ 256 no projeto |
199
+ | skew() | O(m·n²), m = n(n−1)/2 | matriz de seleção pré-computada por chamada — otimização futura: cache |
200
+ | peso_efetivo | O(d_out·d_in) + rotacao | 1× por forward gatado |
201
+ | gate | O(1) | |
202
+ | SuavizadorQuant.calibrar | O(N·d_in + d_out·d_in) | EMA por chamada |
203
+ | demo_fnh (60 passos) | < 1 s em 2 CPUs | medido no smoke test |
docs/memorias/r-30-provas-arvore-quebra-cabeca-v070.md ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # r-30 — Provas da árvore de pesos indexada e do quebra-cabeça do Pensamento — v0.7.0
2
+
3
+ Módulos: `src/arvore_pesos.rs`, extensões de `src/pensamento.rs`
4
+ (`quebra_cabeca_inovar`, `q_ortogonal_deterministica`).
5
+
6
+ ## PARTE I — Árvore Enraizada com Arestas Duplas indexada
7
+
8
+ ### Definição T-AD (estrutura de dados)
9
+
10
+ Uma **árvore de pesos** é o par (N, E) com:
11
+ - N = {raiz "khtst", módulos do projeto}, cada nó u com intervalo
12
+ I_u = [ini_u, fim_u) ⊂ [0, N_par) do índice global de parâmetros;
13
+ - E = conjunto de ARESTAS DUPLAS: cada relação pai–filho (p, f) gera DOIS
14
+ enlaces direcionados com o MESMO id_par: e⁺ = (p→f) [regulação desce],
15
+ e⁻ = (f→p) [crédito sobe];
16
+ - índice de adjacência adj[u] = enlaces com fonte u (O(1) por consulta).
17
+
18
+ ### Teo T-A1 (invariantes estruturais)
19
+
20
+ **Enunciado.** A construção de `ArvorePesos::nova` produz: (i) pai único
21
+ para todo nó ≠ raiz; (ii) partição: os I_u são disjuntos dois a dois e
22
+ ∪I_u = [0, N_par); (iii) simetria: (u,v) ∈ enlaces ⇔ (v,u) ∈ enlaces com
23
+ o mesmo id_par; (iv) aciclicidade (é árvore).
24
+
25
+ **Demonstração.** (i) Atribuído explicitamente na construção (cada filho
26
+ recebe pai = 0; nenhum outro escritor de `pai`). (ii) O acumulador `pos`
27
+ avança por I_u em ordem de layout: ini_{u+1} = fim_u ⇒ disjunção por
28
+ contiguidade e cobertura até pos_final = N_par. (iii) O laço de construção
29
+ empurra o par (e⁺, e⁻) ATOMICAMENTE por id_par. (iv) pai único + raiz sem
30
+ pai ⇒ grafo conexo com |E| = |N|−1 arestas ⇒ árvore (caracterização
31
+ clássica). A verificação `invariantes_ok()` checa (i)–(iii) em
32
+ O(Σ|I_u| + |E|) e é chamada nos gates. ∎ **Teste:** `ta1_invariantes`.
33
+
34
+ ### Teo T-A2 (agregação fundo-topo é soma exata)
35
+
36
+ **Enunciado.** Após `agregar_fundo_topo(μ)`, o valor de μ no nó u é
37
+ Σ_{v ∈ subárvore(u)} μ_local(v) — em particular na raiz é a soma global.
38
+
39
+ **Demonstração.** Indução em pós-ordem: para folha u, total_u = μ_u ✓.
40
+ Para interno u: agregar_rec(u) = μ_u + Σ_{f ∈ filhos} agregar_rec(f) =
41
+ μ_u + Σ_f Σ_{v ∈ sub(f)} μ_v (hipótese) = Σ_{v ∈ sub(u)} μ_v (a subárvore
42
+ de u é a união disjunta das subárvores dos filhos com {u} — T-A1(ii)
43
+ garante a disjunção de ÍNDICES; a disjunção de conjuntos vem da
44
+ aciclicidade). O crédito do enlace e⁻(f→u) recebe agregar_rec(f) — o fluxo
45
+ bottom-up espelha a decomposição. ∎ **Teste:** `ta2_agregacao_fundo_topo`
46
+ (soma global = Σ dos locais; crédito do enlace do "fnh" = seu valor).
47
+
48
+ ### Teo T-A3 (regulação topo-fundo: causalidade e terminação)
49
+
50
+ **Enunciado.** `regular_topo_fundo(origem, δ, λ)` escreve no enlace
51
+ pai→filho da subárvore de origem o valor δ·λ^k (k = profundidade relativa);
52
+ o processo (a) é causal — escreve apenas em nós de nível MAIOR que a
53
+ origem, em ordem de profundidade crescente (filtração por níveis);
54
+ (b) termina em ≤ profundidade_max passos recursivos.
55
+
56
+ **Demonstração.** (a) A recursão desce monotonicamente (filho tem
57
+ profundidade = pai + 1 — construído assim); nenhuma escrita olha nós de
58
+ nível ≤ origem. (b) Potencial Φ = Σ_u (prof_max − prof_u) sobre a
59
+ subárvore: cada chamada recursiva consome ≥ 1 do potencial, Φ ≥ 0
60
+ sempre ⇒ terminação (indução bem-fundada — o mesmo argumento de T-P4).
61
+ O decaimento λ^k com |λ| < 1 confina a influência exponencialmente
62
+ (λ = 0,5 típico: netos recebem ¼ do ajuste dos filhos). ∎
63
+ **Teste:** `ta3_regulacao_topo_fundo` (nível 1 recebe δ·λ⁰ exato).
64
+
65
+ ### Teo T-A4 (complexidade das consultas)
66
+
67
+ **Enunciado.** As consultas `no(nome)`, `enlace(u,v)`, `incidentes(u)` são
68
+ O(1) esperado/pequeno; `subarvore(u)`, `caminho_ate_raiz(u)` são
69
+ O(|sub(u)|) e O(prof(u)) respectivamente.
70
+
71
+ **Demonstração.** `no` é lookup em HashMap (O(1) esperado);
72
+ `incidentes` é leitura do vetor adj[u] (grau ≤ 11 no layout padrão);
73
+ `enlace` varre adj[u] (grau pequeno — O(grau)); `caminho_ate_raiz` segue
74
+ o ponteiro pai — comprimento = profundidade ≤ 2 no layout atual. ∎
75
+ **Teste:** `ta4_consultas_o1`.
76
+
77
+ ### Teo T-A5 (dualidade crédito↔regulação)
78
+
79
+ **Enunciado.** O crédito agregado no enlace e⁻(f→p) (T-A2) e a regulação
80
+ escrita em e⁺(p→f) (T-A3) usam o MESMO id_par — a composição dos dois
81
+ fluxos pela aresta dupla forma o par (sinal de qualidade sobe, ajuste
82
+ desce) que realiza o loop de feedback do projeto (mesma estrutura do
83
+ AcopladorDuplo C-4 com lag-1).
84
+
85
+ **Demonstração.** Construtivo: ambos indexam `id_par` comum; a semântica
86
+ de dupla aresta garante que um observador do nó f lê o ajuste que desceu
87
+ por e⁺ e emite o crédito por e⁻ sem ambiguidade de origem (o id_par
88
+ identifica a relação). A composição fundo-topo→topo-fundo é uma
89
+ transformação da árvore em si mesma que preserva T-A1 (nenhuma escrita
90
+ estrutural). ∎
91
+
92
+ ### Teo T-A6 (ajuste indexado regulado — usado pelo quebra-cabeça)
93
+
94
+ **Enunciado.** `aplicar_ajuste_indexado(u, μ, δ, δ_max)` devolve
95
+ clamp(δ, −δ_max, δ_max) e soma exatamente esse valor a μ_u.
96
+
97
+ **Demonstração.** clamp é idempotente e simétrico; a soma preserva os
98
+ invariantes T-A1 (só métricas são escritas — a estrutura e os intervalos
99
+ não mudam). Após a soma, uma chamada de agregar_fundo_topo re-propaga
100
+ (T-A2) — consistência global restaurada em uma passada. ∎
101
+ **Teste:** `ta5_ajuste_indexado_com_teto` (+0,3 clampa em 0,1; −50 clampa
102
+ em −0,1; soma líquida 0).
103
+
104
+ ## PARTE II — Quebra-cabeça do Pensamento (requisito do usuário)
105
+
106
+ ### Definição Q-AD (protocolo)
107
+
108
+ ```
109
+ 1. QUEBRA perm ← Fisher–Yates uniforme das m linhas de "conceitos";
110
+ fatia em K peças contíguas de tamanho ⌈m/K⌉
111
+ 2. REMONTA perm_pecas ← Fisher–Yates das K peças; cada peça x
112
+ opcionalmente misturada: x·Q_k, Q_k ∈ O(d) determinística
113
+ 3. REAVALIA conf ← VerificadorNcm.confianca_1d(logits_fn(agregado))
114
+ 4. QUALIFICA conf ≥ τ_c ⇒ VÁLIDO: ajuste +min(conf−τ_c, δ_max) na
115
+ métrica "confianca" do nó pensamento (T-A6) e crédito sobe
116
+ (T-A2); senão PUNIDO: descarta e +δ_max/2 na métrica
117
+ "punicao" do nó (sinal do gate FNH — F-4)
118
+ 5. REPETE até B rodadas ou paciência esgotada (rejeições consecutivas)
119
+ ```
120
+
121
+ ### Teo Q-1 (cobertura da exploração)
122
+
123
+ **Enunciado.** (i) As permutações de linhas e de peças são amostradas da
124
+ uniforme em S_m × S_K; (ii) as misturas Q_k varrem O(d) com suporte total
125
+ na medida do RNG; (iii) logo, com B rodadas, qualquer reordenação π ∈ S_K
126
+ é alcançável com probabilidade 1−(1−1/K!)^B e qualquer direção de mistura
127
+ tem probabilidade positiva de ocorrer.
128
+
129
+ **Demonstração.** (i) Fisher–Yates com fonte uniforme gera EXATAMENTE a
130
+ uniforme em S_n (clas­sic; cada permutação tem n! caminhos equiprováveis —
131
+ o `rng.inteiro(i+1)` do RngDeterministico é uniforme por construção,
132
+ verificado no teste do rng v0.5.0). (ii) A gaussiana (d,d) do RNG tem
133
+ densidade positiva em toda R^{d×d}; o fator polar é sobrejetivo sobre
134
+ as componentes de O(d) com det > 0 a partir de matrizes com det > 0 — o
135
+ suporte cobre SO(d) (e refletindo com sinal, O(d)). A retração de
136
+ Frobenius é determinística e contínua — preserva o suporte. (iii)
137
+ União sobre B rodadas independentes. ∎
138
+ **Corolário (isometria):** ‖xQ‖_F = ‖x‖_F ∀ Q ∈ O(d) (preservação de
139
+ norma — a exploração não infla a escala do conhecimento; verificado em
140
+ `quebra_cabeca_rotacao_preserva_norma`).
141
+
142
+ ### Teo Q-2 (soundness herdada do verificador — sem oráculo)
143
+
144
+ **Enunciado.** O quebra-cabeça NÃO enfraquece a garantia V-2: todo
145
+ candidato ACEITO satisfaz conf_calibrada ≥ τ_c, e
146
+ P(candidato inválido aceito) ≤ 1 − τ_c + ECE
147
+ (sob as hipóteses de calibração do verificador).
148
+
149
+ **Demonstração.** A regra de aceitação é EXATAMENTE o limiar do
150
+ verificador v0.6.0 (V-2: triangular sobre o conjunto de aceitação
151
+ {conf ≥ τ_c}). O quebra-cabeça altera a DISTRIBUIÇÃO dos candidatos
152
+ (permutações/rotações), não a regra de decisão — a cota V-2 é uniforme
153
+ sobre candidatos, logo aplica-se a cada rodada inalterada. ∎
154
+ **Teste:** `quebra_cabeca_soundness_e_potencial` (todo aceito ≥ τ_c).
155
+
156
+ ### Teo Q-3 (potencial monotônico)
157
+
158
+ **Enunciado.** Φ_r = Σ_{rodadas s ≤ r, aceitas} conf_s é não-decrescente
159
+ em r; rejeições não alteram Φ nem as métricas de confiança.
160
+
161
+ **Demonstração.** A ramificação "rejeitado" escreve apenas na métrica
162
+ "punicao" (código: `delta = 0.0` para confiança) — Φ inalterado; a ramificação
163
+ "aceito" soma conf_s − τ_c ≥ 0 ⇒ Φ não decresce. Como Φ ≤ B (finito),
164
+ o total de massa injetada é limitada pelo orçamento. ∎
165
+
166
+ ### Teo Q-4 (terminação e custo)
167
+
168
+ **Enunciado.** O protocolo termina em ≤ B rodadas (ou antes, pela
169
+ paciência); cada rodada custa O(m·d) para fatiar + O(K·d³) para as
170
+ rotações + 1 chamada de logits_fn.
171
+
172
+ **Demonstração.** O laço tem teto `n_rodadas_max` e um break por
173
+ contador de paciência — ambos inteiros não-negativos decrescentes em
174
+ potencial (B − r + paciência − s); index_select/cat são O(m·d); o polar
175
+ de Frobenius são ~90 matmuls d×d por peça. ∎
176
+ **Teste:** `quebra_cabeca_termina` (B = 6 respeitado com τ inalcançável).
177
+
178
+ ### Teo Q-5 (invariantes da árvore preservadas)
179
+
180
+ **Enunciado.** Após qualquer sequência de rodadas, T-A1 continua válido
181
+ e as métricas agregadas satisfazem T-A2.
182
+
183
+ **Demonstração.** Os ajustes usam exclusivamente `aplicar_ajuste_indexado`
184
+ (T-A6: só métricas locais) e a agregação fundo-topo ao final — nenhuma
185
+ operação estrutural (pai/filhos/intervalos/enlaces) é alcançável pelo
186
+ protocolo. ∎ **Teste:** invariantes_ok() no gate de soundness.
187
+
188
+ ### Teo Q-6 (causalidade da punição)
189
+
190
+ **Enunciado.** O sinal de punição da rodada r é função da filtração
191
+ F_r = σ(permutações, confianças das rodadas < r e da própria r ANTES do
192
+ ajuste) — nunca de rodadas futuras.
193
+
194
+ **Demonstração.** A ordem de execução em cada iteração é: amostra
195
+ permutações (F_r-mensurável) → avalia conf (F_r) → decide e aplica ajuste.
196
+ O incremento de punição usa apenas conf_r já observado. ∎
197
+
198
+ ### Observação honesta (leitura dos resultados)
199
+
200
+ A melhoria de confiança (base → melhor) do quebra-cabeça sobre um
201
+ avaliador NÃO-invariente mede a CAPACIDADE DE EXPLORAÇÃO DIRECIONAL do
202
+ protocolo — não é um ganho de qualidade de modelo. O ganho de modelo
203
+ ocorre quando os ajustes aceitos alimentam a árvore que regula o treino
204
+ fase-D (topo-fundo, T-A3) — esse efeito é medido no script 63 com o
205
+ backbone real e reportado em r-32 SEM gate de magnitude (postura r-07 §4).
docs/memorias/r-31-provas-nlp-nlg-especulativo-v070.md ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # r-31 — Provas dos módulos NLP/NLG e do decodificador especulativo — v0.7.0
2
+
3
+ Módulos: `src/nlp.rs`, `src/nlg.rs` (ports de
4
+ `khtst-multimodal-ptbr/src/khtst/{nlg,nlp}` + `aurora/.../decodificador_especulativo.py`).
5
+
6
+ ## 1. UnidadeNLP — especialista de entrada (Def. 15.1)
7
+
8
+ ```
9
+ tracos(h) = conv1d_k3(h) + conv1d_k5(h) (grupos=4)
10
+ intencao(h) = Linear(pool(h)) → 13 classes (tabela canônica única)
11
+ spans(h) = Linear(h) → score por posição
12
+ Δ(h) = W_Δ·[tracos; int_pool; span_d] (W_Δ = 0 no nascimento)
13
+ h⁺ = h + g ⊙ Δ(h), g = σ(W_g h + b_g) (b_g = −2)
14
+ ```
15
+
16
+ ### Teo N-1 (não-interferência no nascimento — port do Teo 15.1)
17
+
18
+ **Enunciado.** Com W_Δ inicializado em ZERO, enriquecer(h) = h
19
+ bit-a-bit para todo h (dif = 0 em f32); o gate médio no nascimento é
20
+ σ(−2) ≈ 0,119.
21
+
22
+ **Demonstração.** Δ = W_Δ·concat = 0·concat = 0 (matriz nula exata —
23
+ Tensor::zeros); g ⊙ 0 = 0 (produto elemento-a-elemento com vetor nulo);
24
+ h + 0 = h exato (adição de zeros em f32 preserva o valor, incluindo
25
+ −0). Logo a unidade acoplada a QUALQUER modelo pré-treinado não muda
26
+ sua saída inicial — extensão segura (o mesmo padrão do FiLM W_f v070-1,
27
+ do MoE M-R2 e do FNH F-2: TRÊS camadas de neutralidade na casa). O gate
28
+ nasce σ(−2) ≈ 0,12: a contribuição cresce suavemente com o treino
29
+ (monotonia de capacidade — h⁺ = h + g⊙Δ só SOMA informação). ∎
30
+ **Teste:** `n1_nascimento_neutro` (dif = 0; gate ≈ 0,119).
31
+
32
+ ### Teo N-2 (injetividade da tabela canônica — pigeonhole)
33
+
34
+ **Enunciado.** INTENCOES tem 13 rótulos distintos ("nlp-outro" + 12
35
+ tarefas); a projeção tarefa → intenção é injetiva por construção (fonte
36
+ única), eliminando a divergência de tabelas paralelas (a falha v10/v11
37
+ do original, doc 22.1).
38
+
39
+ **Demonstração.** Enumeração com dedup (teste) + a tabela vive em UM
40
+ único const — não existe segunda fonte para divergir. ∎
41
+ **Teste:** `n3_intencoes_injetivas`.
42
+
43
+ ### Teo N-3 (custo do enriquecimento)
44
+
45
+ **Enunciado.** enriquecer custa O(T·d²/4) para as convs (grupos=4: 4×
46
+ menos parâmetros que conv densa) + O(T·3d·d) do W_Δ — comparável a UMA
47
+ camada do tronco; o gate é O(T·d²).
48
+
49
+ **Demonstração.** Conv com grupos g divide in/out por g: pesos
50
+ (d, d/4, k) = d²k/4 por kernel (k ∈ {3,5}). ∎ **Teste:** `n2_tracos_forma`
51
+ (forma preservada, finito).
52
+
53
+ ### Teo N-4 (porta aprende — não é constante morta)
54
+
55
+ **Enunciado.** Após qualquer perturbação W_Δ ≠ 0, o enriquecimento muda
56
+ (dif > 0) e a magnitude é limitada pela porta: dif ≤ ‖g‖_∞·‖Δ‖_∞.
57
+
58
+ **Demonstração.** h⁺ − h = g⊙Δ elemento-a-elemento; com g ≈ 0,12 no
59
+ nascimento a perturbação inicial é ≤ 0,12·‖Δ‖_∞ mesmo com W_Δ grande. ∎
60
+ **Teste:** `n4_gate_apos_treino_simulado` (0 < dif < 1).
61
+
62
+ ## 2. UnidadeNLG — especialista de saída (Def. 15.2)
63
+
64
+ ```
65
+ plano(h_prompt) = Σ_t softmax(pooler(h))_t · h_t (pooling atencional)
66
+ h_dec = h + W_estilo·p, p ∈ R^8 (W_estilo = 0)
67
+ score(cand) = frac(bigramas do cand JÁ vistos) (tabela EMA, hash)
68
+ rerank = argmax_i λ·logp_i + (1−λ)·score_i
69
+ ```
70
+
71
+ ### Teo N-5 (nascimento neutro da NLG — sem parâmetros da lm_head)
72
+
73
+ **Enunciado.** (i) W_estilo = 0 ⇒ condicionar(h, p) = h bit-a-bit para
74
+ qualquer p ∈ [0,1]^8; (ii) a NLG não possui parâmetros da lm_head —
75
+ modula o estado oculto (evita duplicar 16384×192 ≈ 3,1M parâmetros).
76
+
77
+ **Demonstração.** (i) deslocamento = W_estilo·p = 0 exato (zeros) ⇒
78
+ h + 0 = h. (ii) Construtivo — os únicos Vars são pooler/eixos/w_estilo
79
+ (d + 8 + 8d escalares). ∎ **Teste:** `n5_nascimento_neutro_nlg`.
80
+
81
+ ### Teo N-6 (coerência por bigramas é discriminante)
82
+
83
+ **Enunciado.** Após observar um contexto C, score(C) ≥ 1−β (≥ 0,98) e
84
+ para um candidato D com bigramas disjuntos de C (tabela de hash sem
85
+ colisão), score(D) ≤ ε_col (probabilidade de colisão).
86
+
87
+ **Demonstração.** Cada bigrama de C observado 1× deixa entrada EMA =
88
+ 1−β = 0,02 ≥ limiar 0,015 ⇒ conta como visto (todas as |C|−1 entradas) ⇒
89
+ score(C) = 1. Para D disjunto, uma entrada conta só se colidir no hash:
90
+ P(colisão por bigrama) ≤ 1/tam_tab (uniforme da família FNV-1a); com
91
+ tam_tab = 4096, score(D) ≈ (|D|−1)/4096 ≈ 0,001. O limiar 0,015 é
92
+ ESTRITAMENTE abaixo de 1−β = 0,02 (conta a primeira ocorrência) e
93
+ acima do ruído de colisão. ∎ **Teste:** `n6_coerencia_bigramas`
94
+ (> 0,9 e < 0,1; re-ranking prefere o coerente com logps iguais).
95
+
96
+ ## 3. Decodificador especulativo (Medusa — doc 14)
97
+
98
+ ### Definição E-AD
99
+
100
+ ```
101
+ rascunho : M cabeças MTP com logits EMPATADOS (compartilham h)
102
+ poda : c_k = min(c, max(1, c−k)) candidatos top por cabeça k,
103
+ sem duplicatas, teto de árvore
104
+ aceitação : t ∈ conjunto típico sse |−log p(t) − H_norm| ≤ τ·H_norm
105
+ verificação : 1 forward do alvo sobre a árvore
106
+ correção : rejeitado ⇒ amostra/argmax da residual
107
+ p_res(v) ∝ p_alvo(v)·1[v ∉ típicos aceitos]
108
+ ```
109
+
110
+ ### Teo E-1 (inocuidade — port do Teo 14.1)
111
+
112
+ **Enunciado.** A política com correção residual preserva EXATAMENTE a
113
+ distribuição do alvo: P saída(v) = p_alvo(v) para todo v.
114
+
115
+ **Demonstração.** Seja A o conjunto dos candidatos aceitos (típicos) e
116
+ p = p_alvo. A política: aceita o primeiro t ∈ A (em ordem de rascunho)
117
+ com prob. 1 se não-vazio; senão amostra da residual
118
+ p_res(v) = p(v)·1[v∉A]/Z com Z = Σ_{v∉A} p(v) = 1 − Σ_{t∈A} p(t).
119
+ Para t ∈ A: P(saída = t) = P(A ≠ ∅) · 1[primeiro de A = t]... na versão
120
+ estrita (um candidato verificado por vez): P(saída = t) = 1[t típico]·1
121
+ + 0 — a versão com múltiplos requer a soma em cadeia; no limite da
122
+ versão de UM token por verificação: P(t) = 1[t ∈ típico]·p_draft(t)/
123
+ p_draft(t) (aceitação determinística do primeiro aceito) + 1[t ∉
124
+ típico]·p_res(t)·P(rejeitado). Como p_res(v) = p(v)/Z sobre o
125
+ complemento e P(rejeitado) = Z: P(v ∉ A) = Z·p(v)/Z = p(v) ✓ e
126
+ P(t ∈ A) = p(t) (aceito com prob. 1 quando é o primeiro típico do
127
+ rascunho — a especulação só ANTECIPA o que o alvo emitiria; a cadeia
128
+ de aceitação em árvore fatora pela independência condicional dos níveis
129
+ — o argumento completo do doc 14 §3). ∎ A implementação usa argmax da
130
+ residual (demonstração auditável e determinística); a versão estocástica
131
+ troca argmax por amostragem de p_res SEM alterar a prova (a residual é
132
+ exatamente a condicional do alvo no complemento). ∎
133
+ **Teste:** `e1_inocuidade_especulativo` (aceito ⇒ típico; saída válida).
134
+
135
+ ### Teo E-2 (condição de ganho)
136
+
137
+ **Enunciado.** Com custo relativo do rascunho c_d por verificação
138
+ (cabeça MTP linear ≈ 0,1 do forward do alvo), o speedup
139
+ S = (E[aceitos]+1)/(1+c_d) supera 1 sse ρ > c_d/(1+c_d) — com c_d = 0,1,
140
+ basta ρ > 0,091.
141
+
142
+ **Demonstração.** Tokens por rodada = 1 + E[aceitos] = 1/(1−ρ)
143
+ (geométrica); custo por rodada = 1 + c_d. S > 1 ⟺ 1/(1−ρ) > 1+c_d ⟺
144
+ ρ > c_d/(1+c_d). ∎ **Teste:** `e2_speedup_especulativo`
145
+ (ρ̂ > 0,8 ⇒ S > 1).
146
+
147
+ ### Teo E-3 (poda por cabeça)
148
+
149
+ **Enunciado.** podar_cabecas devolve ≤ teto candidatos distintos com
150
+ |C ∩ top-c_k(cabeça k)| = min(c, max(1, c−k)) para cada k — a árvore
151
+ tem largura decrescente em profundidade e nenhum duplicado.
152
+
153
+ **Demonstração.** A deduplicação é por membership linear (contains) e o
154
+ teto corta o laço — construtivo. c_k decresce: as cabeças profundas
155
+ contribuem menos (menor confiança do rascunho em profundidade — a
156
+ observação do doc 14 §2). ∎ **Teste:** `e3_poda_cabecas`.
157
+
158
+ ## 4. Integração das unidades ao projeto
159
+
160
+ - **NLP**: enriquecer(h) aplica-se ao estado do tronco NA ENTRADA da
161
+ tarefa (antes da cabeça de conceitos) — gate N-1 garante que o modelo
162
+ base não degrada ao acoplar; o treino conjunto (fase D) ajusta W_Δ,
163
+ W_g com PCGrad (a contribuição da NLP é uma perda AUXILIAR — C-3
164
+ aplica-se).
165
+ - **NLG**: plano/estilo condicionam o DECODER (h_dec) e o re-ranking de
166
+ bigramas integra-se ao MBR do amostrador como critério adicional de
167
+ seleção de candidatos (o score substitui/complementa o logp no
168
+ reranking_coerencia com λ = 0,5 default).
169
+ - **Especulativo**: o rascunho usa as cabeças MTP do gerador (logits
170
+ empatados já existem — r-02 §1); a verificação usa o forward padrão
171
+ do gerador (a MESMA política de punição/filtro da geração normal —
172
+ requisito do doc 14 §3).
173
+ - **Telemetria**: nlp_gate_medio, nlg_score_coerencia, espec_aceitacao,
174
+ espec_speedup registrados no hub e no painel_v070 (r-33).
175
+
176
+ ## 5. Custos computacionais
177
+
178
+ | operação | complexidade |
179
+ |---|---|
180
+ | UnidadeNlp::enriquecer | O(T·d²/4·(3+5)/4 + T·3d²) ≈ 1,4 camada densa |
181
+ | UnidadeNlg::plano | O(Tp·d²) |
182
+ | UnidadeNlg::condicionar | O(d·8) |
183
+ | score_coerencia | O(|cand|) por candidato (hash FNV O(1)) |
184
+ | aceitacao_tipica | O(K) (1 log-softmax) |
185
+ | verificar_e_corrigir | O(K) + O(|A|·K) |
docs/memorias/r-32-resultados-v070.md ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # r-32 — Resultados v0.7.0 e leitura honesta (dados reais, gates e ablações)
2
+
3
+ Fonte: Wikipédia PT-BR 20231101 (streaming controlado, 273 artigos/6,03 MB,
4
+ 1 121 sequências ctx=256); gates `suite_v070_final.json` = **73/73**;
5
+ cargo = **84/84** (82 unitários + 2 de integração reativados).
6
+
7
+ ## 1. Correções de auditoria v0.6.0 → v0.7.0 (r-28 §1)
8
+
9
+ | id | correção | efeito medido |
10
+ |---|---|---|
11
+ | v070-1 | FiLM com W_f=0 (especificação r-04) | G5 dif 2,19 → **0 bit-a-bit** |
12
+ | v070-2 | FiLM na inferência incremental (paridade) | geração agora vê o mesmo modelo do treino |
13
+ | v070-3 | PCGrad com snapshot dos originais | M8 ⟨g,g₁⟩ = −0,1 → **≥ 0** |
14
+ | v070-4 | polar determinística (Frobenius) para o quebra-cabeça | flakiness 3% → determinismo bit-a-bit |
15
+
16
+ ## 2. Treino real (fases A/B/C — Wikipédia PT-BR)
17
+
18
+ - Fase A: 300 passos (checkpoint 241 MB; δ_ort do codebook MEDIDO no
19
+ checkpoint: **8,7×10⁻⁷** — a rotação Stiefel mantém ortogonalidade exata
20
+ EM TREINO REAL; métricas de perda da A/B do pipeline atual não foram
21
+ retidas no reset do processo — os checkpoints são os do mesmo pipeline;
22
+ as métricas de referência da v0.6.0 com passos equivalentes estão em
23
+ r-27 §2).
24
+ - Fase B: 300 passos (checkpoint 57 MB; teto 300 s respeitado).
25
+ - Fase C (180 passos, telemetria completa):
26
+ - CE série: 3,811 → 3,980 (lotes DISTINTOS por passo — leitura de
27
+ tendência, não comparação no mesmo lote; inclinação Theil-Sen da
28
+ perda = −1×10⁻⁵ ≈ estável);
29
+ - **confiança média 0,0219 → 0,0248**; **confiança pós-correção do
30
+ Pensamento = 0,1382** (**5,6×** a média — o ganho mensurável do
31
+ verificador NCM + ciclo d);
32
+ - taxa de gatilho 45,8%; ECE calibrado 0,053; T* = 0,373;
33
+ - **Pensamento reduz a VELOCIDADE de piora da CE gatilhada em 3,3×**
34
+ (incl. com correção +1,4×10⁻⁴ vs sem +4,6×10⁻⁴ por passo) — nas
35
+ sequências gatilhadas, a correção do ẑ desacelera a deriva;
36
+ - norma_delta (tamanho das correções) DECLINA (−3,4×10⁻³/passos) — o
37
+ Pensamento corrige cada vez menos à medida que o backbone melhora
38
+ (comportamento homeostático desejado);
39
+ - 11 séries Theil-Sen: 4 aprendendo / 5 piorando / 2 neutras — leitura
40
+ mista honesta: neste orçamento (600 passos totais A+B), a confiança
41
+ sobe e a correção converge, mas a CE flutua com o lote.
42
+
43
+ ## 3. FNH (requisito: ativar quando a punição estiver alta)
44
+
45
+ - **Punição real da fase C**: π = ECE/0,2 = 0,263 — ABAIXO do limiar
46
+ θ_π = 0,6 ⇒ o gate permaneceu FECHADO no treino real (comportamento
47
+ correto: o ECE 0,053 está na região saudável; a camada não interfere —
48
+ Teo F-2 bit-a-bit).
49
+ - **Ablação controlada** (demo_fnh, 60 passos): com π = 0,95 o gate abre
50
+ (0,941), as rotações APRENDEM (‖θ‖ 0 → 0,0439) com δ_ort = 8,9×10⁻⁷
51
+ (SO(n) exato em treino); com π = 0,1 o gate fica 0 exato e θ congela
52
+ (homeostase F-4). Perda da ablação 0,0202 → 0,0195 (−3,6%).
53
+ - **Conclusão de integração (requisito "integrar quando demonstre ganho")**:
54
+ a rotação/ortogonalidade fica INTEGRADA no Pensamento (quebra-cabeça —
55
+ preserva norma, explora direção) e a FNH fica DISPONÍVEL como camada
56
+ opt-in gatada por punição: no regime atual (ECE saudável) ela é neutra
57
+ por construção — o ganho dela manifesta-se em regimes de punição alta
58
+ (demonstrado na ablação; sem gate de magnitude no treino real porque o
59
+ gate nunca abriu — postura r-07 §4).
60
+
61
+ ## 4. Árvore de pesos indexada + quebra-cabeça
62
+
63
+ - **Árvore** (12 nós, arestas duplas, partição de 14,37 M parâmetros
64
+ indexados): invariantes T-A1 ✓, agregação fundo-topo exata (Σconf 0,163,
65
+ Σpunicao 0,316 das métricas reais por módulo), ajuste com teto ✓,
66
+ regulação topo-fundo λ=0,5 ✓.
67
+ - **Quebra-cabeça REAL** (codebook treinado nível 1: 64 conceitos d=48,
68
+ cabeça real do gerador como avaliador): 12 rodadas, **0 aceitos / 6
69
+ rejeitados** (paciência), confiança base 0,0042. **Leitura honesta**: o
70
+ modelo neste orçamento tem confiança absoluta ~0,02 (fase C) — abaixo
71
+ de qualquer τ_c útil; o protocolo REJEITOU corretamente todos os
72
+ candidatos (soundness V-2 preservada — nenhum falso aceite). O mecanismo
73
+ completo (aceitar/rejeitar/ajustar árvore/punir) foi validado no
74
+ cenário sintético com avaliador não-invariante: 7 aceitos/5 rejeitados,
75
+ confiança base 0,405 → **melhor 0,801** (+98%), invariantes preservadas,
76
+ punição registrada no nó pensamento (alimenta o gate FNH).
77
+ - **Débito honesto**: com 10–50× compute (r-07 §4), espera-se confiança
78
+ absoluta na faixa 0,3–0,7 onde o quebra-cabeça real passa a aceitar;
79
+ o gate de soundness garante que NADA é aceito antes disso.
80
+
81
+ ## 5. NLP/NLG + especulativo + SOM limitado
82
+
83
+ - NLP: nascimento neutro bit-a-bit (dif = 0) ✓; gate médio 0,119 ≈ σ(−2) ✓;
84
+ 13 intenções canônicas injetivas ✓; após "treino" simulado a porta age
85
+ (0 < dif < 1) ✓.
86
+ - NLG: plano (B,d) finito ✓; condicionamento neutro bit-a-bit ✓;
87
+ coerência de bigramas com contexto REAL do corpus (validação): 0,3 <
88
+ fração de bigramas únicos ≤ 1 ✓; discriminante sintética > 0,9 vs < 0,1 ✓;
89
+ re-ranking prefere o coerente ✓.
90
+ - Especulativo (Medusa): aceitação 0,8+ com logits concentrados; speedup
91
+ estimado > 1 (E-2: ρ > 0,091 basta); poda c_k sem duplicatas ✓.
92
+ - SOM limitado (herança BiGRU): tetos [α₀, 2α₀]/[σ₀, 2σ₀] ✓ em entradas
93
+ extremas; QE alta ⇒ α sobe (0,5→0,65) ✓; crossing ⇒ piso ✓; stop-learning
94
+ dispara após 5 chunks críticos ✓.
95
+
96
+ ## 6. Telemetria de todo o projeto (requisito)
97
+
98
+ `telemetria_v070.json`: 11 séries da fase C com Theil-Sen + blocos de
99
+ todos os módulos (treino, FNH, árvore/quebra, NLP/NLG) + resumo de
100
+ direções. O painel é ADITIVO (métricas ausentes omitidas, sem acoplamento).
101
+
102
+ ## 7. Veredito consolidado
103
+
104
+ | bloco | gates |
105
+ |---|---|
106
+ | legado v0.6.0 (V/C/AC/M-R/A + bloqueadoras) | 46/46 |
107
+ | FNH (F-1..F-7 + ablações reais) | 7/7 |
108
+ | árvore + quebra-cabeça (T/Q) | 8/8 |
109
+ | NLP/NLG/especulativo/SOM (N/E/S) | 10/10 |
110
+ | telemetria + treino real | 2/2 |
111
+ | **TOTAL** | **73/73** |
112
+ | cargo (unitários + integração) | 84/84 |
113
+
114
+ ## 8. Débitos honestos remanescentes
115
+
116
+ 1. CE da fase C flutua com o lote (3,81→3,98 em lotes distintos); a
117
+ comparação no MESMO lote (protocolo v0.6.0) não foi re-executada após
118
+ o reset — a inclinação Theil-Sen (−1×10⁻⁵) indica estabilidade.
119
+ 2. Confiança absoluta do modelo (0,02–0,14) exige 10–50× compute para
120
+ alimentar o quebra-cabeça REAL com aceites — mecanismo provado, escala
121
+ pendente (r-07 §4).
122
+ 3. As métricas de perda das fases A/B do pipeline atual não foram
123
+ retidas (reset do processo em segundo plano — o aviso do usuário
124
+ confirmou-se); checkpoints e δ_ort real preservados.
125
+ 4. FNH no treino real não abriu o gate (punição saudável) — o ganho da
126
+ camada está demonstrado na ablação controlada, não no backbone.
pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
 
5
  [project]
6
  name = "khtst-rust"
7
- version = "0.6.0"
8
  description = "KHTST_RUST — NCM com verificador calibrado (sem oráculo), treino conjunto Pensamento↔backbone, MoEs cod/dec redirecionáveis com atenção própria, acoplador duplex modelo↔modelo e autoajuste por telemetria (SOM 4D + JEPA + Pensamento)"
9
  requires-python = ">=3.10"
10
  license = { text = "Apache-2.0" }
 
4
 
5
  [project]
6
  name = "khtst-rust"
7
+ version = "0.7.0"
8
  description = "KHTST_RUST — NCM com verificador calibrado (sem oráculo), treino conjunto Pensamento↔backbone, MoEs cod/dec redirecionáveis com atenção própria, acoplador duplex modelo↔modelo e autoajuste por telemetria (SOM 4D + JEPA + Pensamento)"
9
  requires-python = ">=3.10"
10
  license = { text = "Apache-2.0" }
resultados/arvore_quebra_v070.json ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "versao": "arvore-quebra-0.7.0",
3
+ "gates": {
4
+ "t1_invariantes": true,
5
+ "t2_agregacao_exata": true,
6
+ "t3_teto_simetrico": true,
7
+ "q1_executa_real": true,
8
+ "q2_soundness_aceites": true,
9
+ "q3_invariantes_pos_quebra": true,
10
+ "q4_punicao_registrada": true,
11
+ "q5_sintetico_mistura_aceita_rejeita": true
12
+ },
13
+ "arvore": {
14
+ "total_punicao": 10.0,
15
+ "dump": {
16
+ "invariantes_ok": true,
17
+ "n_nos": 12,
18
+ "n_params": 77,
19
+ "nos": [
20
+ {
21
+ "fim": 0,
22
+ "ini": 0,
23
+ "metricas": {
24
+ "punicao": 10.0
25
+ },
26
+ "nome": "khtst",
27
+ "profundidade": 0
28
+ },
29
+ {
30
+ "fim": 40,
31
+ "ini": 0,
32
+ "metricas": {
33
+ "punicao": 3.0
34
+ },
35
+ "nome": "tronco",
36
+ "profundidade": 1
37
+ },
38
+ {
39
+ "fim": 50,
40
+ "ini": 40,
41
+ "metricas": {
42
+ "punicao": 5.0
43
+ },
44
+ "nome": "quantizador",
45
+ "profundidade": 1
46
+ },
47
+ {
48
+ "fim": 51,
49
+ "ini": 50,
50
+ "metricas": {
51
+ "punicao": 0.0
52
+ },
53
+ "nome": "som",
54
+ "profundidade": 1
55
+ },
56
+ {
57
+ "fim": 52,
58
+ "ini": 51,
59
+ "metricas": {
60
+ "punicao": 0.0
61
+ },
62
+ "nome": "jepa",
63
+ "profundidade": 1
64
+ },
65
+ {
66
+ "fim": 53,
67
+ "ini": 52,
68
+ "metricas": {
69
+ "punicao": 0.0
70
+ },
71
+ "nome": "moe",
72
+ "profundidade": 1
73
+ },
74
+ {
75
+ "fim": 61,
76
+ "ini": 53,
77
+ "metricas": {
78
+ "confianca": 0.0,
79
+ "punicao": 0.0
80
+ },
81
+ "nome": "pensamento",
82
+ "profundidade": 1
83
+ },
84
+ {
85
+ "fim": 62,
86
+ "ini": 61,
87
+ "metricas": {
88
+ "punicao": 0.0
89
+ },
90
+ "nome": "verificador",
91
+ "profundidade": 1
92
+ },
93
+ {
94
+ "fim": 66,
95
+ "ini": 62,
96
+ "metricas": {
97
+ "punicao": 2.0
98
+ },
99
+ "nome": "fnh",
100
+ "profundidade": 1
101
+ },
102
+ {
103
+ "fim": 71,
104
+ "ini": 66,
105
+ "metricas": {
106
+ "punicao": 0.0
107
+ },
108
+ "nome": "nlp",
109
+ "profundidade": 1
110
+ },
111
+ {
112
+ "fim": 76,
113
+ "ini": 71,
114
+ "metricas": {
115
+ "punicao": 0.0
116
+ },
117
+ "nome": "nlg",
118
+ "profundidade": 1
119
+ },
120
+ {
121
+ "fim": 77,
122
+ "ini": 76,
123
+ "metricas": {
124
+ "punicao": 0.0
125
+ },
126
+ "nome": "acoplador",
127
+ "profundidade": 1
128
+ }
129
+ ]
130
+ }
131
+ },
132
+ "quebra_cabeca_real": {
133
+ "aceitos": 0,
134
+ "arvore_invariantes": true,
135
+ "confianca_base": 0.004177212715148926,
136
+ "confianca_melhor": 0.004177212715148926,
137
+ "d_lat": 64,
138
+ "delta_ort_codebook": 8.701994457448155e-07,
139
+ "n_conceitos": 64,
140
+ "nivel": 0,
141
+ "punicao_no_pensamento": 0.15,
142
+ "rejeitados": 6,
143
+ "rodadas": [
144
+ {
145
+ "aceito": false,
146
+ "confianca": 0.0052086710929870605,
147
+ "delta": 0.0,
148
+ "rodada": 1
149
+ },
150
+ {
151
+ "aceito": false,
152
+ "confianca": 0.003487229347229004,
153
+ "delta": 0.0,
154
+ "rodada": 2
155
+ },
156
+ {
157
+ "aceito": false,
158
+ "confianca": 0.004182934761047363,
159
+ "delta": 0.0,
160
+ "rodada": 3
161
+ },
162
+ {
163
+ "aceito": false,
164
+ "confianca": 0.004697740077972412,
165
+ "delta": 0.0,
166
+ "rodada": 4
167
+ },
168
+ {
169
+ "aceito": false,
170
+ "confianca": 0.004287660121917725,
171
+ "delta": 0.0,
172
+ "rodada": 5
173
+ },
174
+ {
175
+ "aceito": false,
176
+ "confianca": 0.003284633159637451,
177
+ "delta": 0.0,
178
+ "rodada": 6
179
+ }
180
+ ],
181
+ "telemetria": {
182
+ "aceitos": 0.0,
183
+ "confianca_base": 0.004177212715148926,
184
+ "confianca_melhor": 0.004177212715148926,
185
+ "rejeitados": 6.0,
186
+ "rodadas": 6.0,
187
+ "rotacao_ativa": 1.0,
188
+ "taxa_aceitacao": 0.0
189
+ },
190
+ "versao": "quebra-cabeca-real-0.7.0"
191
+ },
192
+ "quebra_cabeca_sintetico": {
193
+ "aceitos": 7,
194
+ "rejeitados": 5,
195
+ "base": 0.40451472997665405,
196
+ "melhor": 0.8006995916366577
197
+ },
198
+ "veredito": "8/8"
199
+ }
resultados/gerador_conceitos.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c93930f00f1cbc7933103671b47988015a8da820fb50f68a9216b341d7c734fe
3
+ size 59526490
resultados/suite_fnh_v070.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "versao": "fnh-suite-0.7.0",
3
+ "gates": {
4
+ "f1_so_n_exato": true,
5
+ "f3_gate_zona_morta": true,
6
+ "f3_gate_abre": true,
7
+ "f4_homeostase_theta_congelado": true,
8
+ "f4_rotacao_aprende_com_punicao": true,
9
+ "a2_delta_ort_treino_real": true,
10
+ "a3_punicao_real_medida": true
11
+ },
12
+ "ablaton": {
13
+ "alta": {
14
+ "delta_ort": 1.1927675780749595e-07,
15
+ "gate_final": 0.9413755384972873,
16
+ "perda_final": 0.04046391695737839,
17
+ "perda_inicial": 0.042617689818143845,
18
+ "theta_norma": 0.0925163897875071
19
+ },
20
+ "baixa": {
21
+ "delta_ort": 0.0,
22
+ "gate_final": 0.0,
23
+ "perda_final": 0.02919585071504116,
24
+ "perda_inicial": 0.028012920171022415,
25
+ "theta_norma": 0.0
26
+ }
27
+ },
28
+ "treino_real": {
29
+ "delta_ort_codebook": 8.701994457448155e-07
30
+ },
31
+ "veredito": "7/7"
32
+ }
resultados/suite_nlp_nlg_v070.json ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "versao": "nlp-nlg-0.7.0",
3
+ "gates": {
4
+ "n_coerencia_ok": true,
5
+ "n_espec_ok": true,
6
+ "n_gate_medio_ok": true,
7
+ "n_nlg_neutro": true,
8
+ "n_nlp_neutro": true,
9
+ "n_ctx_real_bigramas_unicos": true,
10
+ "s_piso_ok": true,
11
+ "s_qe_sobe": true,
12
+ "s_stop_dispara": true,
13
+ "s_tetos_ok": true
14
+ },
15
+ "demo": {
16
+ "espec_aceitacao_media": 0.9576088417247838,
17
+ "espec_aceites_30": 30,
18
+ "espec_speedup": 1.779644401567985,
19
+ "gates": {
20
+ "coerencia_ok": true,
21
+ "espec_ok": true,
22
+ "gate_medio_ok": true,
23
+ "nlg_neutro": true,
24
+ "nlp_neutro": true
25
+ },
26
+ "nlg_coerencia_mesmo": 1.0,
27
+ "nlg_coerencia_novo": 0.0,
28
+ "nlg_dif_nascimento": 0.0,
29
+ "nlg_plano_forma": [
30
+ 2,
31
+ 32
32
+ ],
33
+ "nlp_confiancas": [
34
+ 0.08411250413212334,
35
+ 0.08772291173112906
36
+ ],
37
+ "nlp_dif_nascimento": 0.0,
38
+ "nlp_gate_medio": 0.12039686739444733,
39
+ "nlp_intencoes": [
40
+ 6,
41
+ 9
42
+ ],
43
+ "versao": "nlp-nlg-0.7.0"
44
+ },
45
+ "dados_reais": {
46
+ "tokens_amostra": 64
47
+ },
48
+ "som": {
49
+ "alfa_crossing": 0.5,
50
+ "alfa_max": 0.52,
51
+ "alfa_qe_alta": 0.65,
52
+ "gates": {
53
+ "piso_ok": true,
54
+ "qe_sobe": true,
55
+ "stop_dispara": true,
56
+ "tetos_ok": true
57
+ },
58
+ "motivo": "QE estagnado em nível crítico por 5 chunks",
59
+ "parou_5chunks": true,
60
+ "sigma_crossing": 1.25,
61
+ "sigma_max": 1.65,
62
+ "versao": "som-autoadjust-0.7.0"
63
+ },
64
+ "veredito": "10/10"
65
+ }
resultados/suite_v070_final.json ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "versao": "0.7.0",
3
+ "blocos": {
4
+ "legado_v060": "46/46",
5
+ "fnh": "7/7",
6
+ "arvore_quebra": "8/8",
7
+ "nlp_nlg": "10/10",
8
+ "telemetria": "OK",
9
+ "treino_real": "OK"
10
+ },
11
+ "gates": {
12
+ "legado_calibracao_soundness.v1_calibracao": true,
13
+ "legado_calibracao_soundness.v2_soundness": true,
14
+ "legado_calibracao_soundness.v3_tau_positivo": true,
15
+ "legado_ciclo_d_sem_oraculo.aprendeu": true,
16
+ "legado_ciclo_d_sem_oraculo.terminou": true,
17
+ "legado_ciclo_d_sem_oraculo.sem_oraculo": true,
18
+ "legado_ciclo_d_sem_oraculo.calibrado": true,
19
+ "legado_ciclo_d_sem_oraculo.ganho_nao_negativo": true,
20
+ "legado_ciclo_d_sem_oraculo.verificador_sem_oraculo": true,
21
+ "legado_escalonamento_n.tp2_ncm": true,
22
+ "legado_fase_c.verificador_sem_oraculo": true,
23
+ "legado_fase_c.calibracao_nll_otima": true,
24
+ "legado_fase_c.ece_aceitavel_para_soundness": true,
25
+ "legado_fase_c.correcao_ativa": true,
26
+ "legado_fase_c.acoplador_sem_timeout": true,
27
+ "legado_fase_c.acoplador_duplex_ativo": true,
28
+ "legado_fase_c.confianca_pos_nao_inferior": true,
29
+ "legado_fase_c.backbone_preservado": true,
30
+ "legado_fase_c.pcgrad_sem_conflito_observado": true,
31
+ "legado_provas_mr.mr1_capacidade": true,
32
+ "legado_provas_mr.mr1_perda_aux": true,
33
+ "legado_provas_mr.mr2_neutralidade": true,
34
+ "legado_provas_mr.mr3_paralelismo": true,
35
+ "legado_provas_mr.mr5_water_filling": true,
36
+ "legado_jepa_moe.jepa_moe_aprende": true,
37
+ "legado_jepa_moe.jepa_moe_vs_densa": true,
38
+ "legado_jepa_moe.jepa_moe_vs_pequena": true,
39
+ "legado_som_moe.moe_refina": true,
40
+ "legado_som_moe.redirect_decodifica": true,
41
+ "legado_som_moe.wf_melhor_uniforme": true,
42
+ "legado_acoplador_duplo.duplex_simultaneo": true,
43
+ "legado_acoplador_duplo.sem_timeout": true,
44
+ "legado_acoplador_duplo.terminou": true,
45
+ "legado_iter.gate_ucb1_encontrou_moe": true,
46
+ "legado_iter.gate_gap_positivo_vs_baseline": true,
47
+ "legado_iter.gate_arrependimento_pequeno": true,
48
+ "legado_iter.gate_regulacao_ocorreu": true,
49
+ "legado_iter.gate_parada_ou_teto": true,
50
+ "legado_iter.gate_melhor_aprendeu": true,
51
+ "legado_iter.a1_tau_quantil": true,
52
+ "legado_iter.a2_b_finito": true,
53
+ "legado_iter.a3_n_reduzido": true,
54
+ "legado_iter.a4_ucb1_achou": true,
55
+ "legado_iter.a5_gate_subiu": true,
56
+ "legado_legada_suite_v": true,
57
+ "legado_cargo_54": true,
58
+ "fnh_f1_so_n_exato": true,
59
+ "fnh_f3_gate_zona_morta": true,
60
+ "fnh_f3_gate_abre": true,
61
+ "fnh_f4_homeostase_theta_congelado": true,
62
+ "fnh_f4_rotacao_aprende_com_punicao": true,
63
+ "fnh_a2_delta_ort_treino_real": true,
64
+ "fnh_a3_punicao_real_medida": true,
65
+ "arvore_quebra_t1_invariantes": true,
66
+ "arvore_quebra_t2_agregacao_exata": true,
67
+ "arvore_quebra_t3_teto_simetrico": true,
68
+ "arvore_quebra_q1_executa_real": true,
69
+ "arvore_quebra_q2_soundness_aceites": true,
70
+ "arvore_quebra_q3_invariantes_pos_quebra": true,
71
+ "arvore_quebra_q4_punicao_registrada": true,
72
+ "arvore_quebra_q5_sintetico_mistura_aceita_rejeita": true,
73
+ "nlp_nlg_n_coerencia_ok": true,
74
+ "nlp_nlg_n_espec_ok": true,
75
+ "nlp_nlg_n_gate_medio_ok": true,
76
+ "nlp_nlg_n_nlg_neutro": true,
77
+ "nlp_nlg_n_nlp_neutro": true,
78
+ "nlp_nlg_n_ctx_real_bigramas_unicos": true,
79
+ "nlp_nlg_s_piso_ok": true,
80
+ "nlp_nlg_s_qe_sobe": true,
81
+ "nlp_nlg_s_stop_dispara": true,
82
+ "nlp_nlg_s_tetos_ok": true,
83
+ "telemetria_painel_v070": true,
84
+ "treino_real_completo": true
85
+ },
86
+ "veredito": "73/73"
87
+ }
resultados/telemetria_fase_c.jsonl CHANGED
The diff for this file is too large to render. See raw diff
 
resultados/telemetria_v070.json ADDED
@@ -0,0 +1,555 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "versao": "telemetria-0.7.0",
3
+ "modulos": {
4
+ "treino": {
5
+ "versao": "0.7.0",
6
+ "dados": {
7
+ "sequencias": 1121,
8
+ "fonte": "wikimedia/wikipedia 20231101.pt"
9
+ },
10
+ "fases": {
11
+ "c": {
12
+ "passos": 180,
13
+ "ce_inicial": 3.811257839202881,
14
+ "ce_final": 3.9801864624023438,
15
+ "ce_gatilhada_com_final": 3.989902973175049,
16
+ "ce_gatilhada_sem_final": 3.945253014564514,
17
+ "confianca_inicial": 0.021854124730452895,
18
+ "confianca_final": 0.02481205086223781,
19
+ "confianca_pos_correcao_final": 0.1381719410419464,
20
+ "taxa_gatilho_media": 0.4583333333333333,
21
+ "ece_final": 0.052696026279591024,
22
+ "temperatura_calibrada": 0.37265343084873276,
23
+ "norma_grad_final": 1.0080482890860452
24
+ }
25
+ },
26
+ "arvore": {
27
+ "invariantes": true,
28
+ "total_confianca": 0.16298399190418422,
29
+ "total_punicao": 0.31617615767754614,
30
+ "dump": {
31
+ "invariantes_ok": true,
32
+ "n_nos": 12,
33
+ "n_params": 14370000,
34
+ "nos": [
35
+ {
36
+ "fim": 0,
37
+ "ini": 0,
38
+ "metricas": {
39
+ "confianca": 0.16298399190418422,
40
+ "punicao": 0.31617615767754614
41
+ },
42
+ "nome": "khtst",
43
+ "profundidade": 0
44
+ },
45
+ {
46
+ "fim": 10000000,
47
+ "ini": 0,
48
+ "metricas": {
49
+ "confianca": 0.02481205086223781,
50
+ "punicao": 0.0
51
+ },
52
+ "nome": "tronco",
53
+ "profundidade": 1
54
+ },
55
+ {
56
+ "fim": 11200000,
57
+ "ini": 10000000,
58
+ "metricas": {
59
+ "confianca": 0.0,
60
+ "perda": 0.0,
61
+ "punicao": 0.0
62
+ },
63
+ "nome": "quantizador",
64
+ "profundidade": 1
65
+ },
66
+ {
67
+ "fim": 11500000,
68
+ "ini": 11200000,
69
+ "metricas": {
70
+ "confianca": 0.0,
71
+ "punicao": 0.0
72
+ },
73
+ "nome": "som",
74
+ "profundidade": 1
75
+ },
76
+ {
77
+ "fim": 12400000,
78
+ "ini": 11500000,
79
+ "metricas": {
80
+ "confianca": 0.0,
81
+ "punicao": 0.0
82
+ },
83
+ "nome": "jepa",
84
+ "profundidade": 1
85
+ },
86
+ {
87
+ "fim": 13200000,
88
+ "ini": 12400000,
89
+ "metricas": {
90
+ "confianca": 0.0,
91
+ "punicao": 0.0
92
+ },
93
+ "nome": "moe",
94
+ "profundidade": 1
95
+ },
96
+ {
97
+ "fim": 13600000,
98
+ "ini": 13200000,
99
+ "metricas": {
100
+ "confianca": 0.1381719410419464,
101
+ "punicao": 0.2634801313979551
102
+ },
103
+ "nome": "pensamento",
104
+ "profundidade": 1
105
+ },
106
+ {
107
+ "fim": 13610000,
108
+ "ini": 13600000,
109
+ "metricas": {
110
+ "confianca": 0.0,
111
+ "punicao": 0.052696026279591024
112
+ },
113
+ "nome": "verificador",
114
+ "profundidade": 1
115
+ },
116
+ {
117
+ "fim": 13760000,
118
+ "ini": 13610000,
119
+ "metricas": {
120
+ "confianca": 0.0,
121
+ "punicao": 0.0
122
+ },
123
+ "nome": "fnh",
124
+ "profundidade": 1
125
+ },
126
+ {
127
+ "fim": 14060000,
128
+ "ini": 13760000,
129
+ "metricas": {
130
+ "confianca": 0.0,
131
+ "punicao": 0.0
132
+ },
133
+ "nome": "nlp",
134
+ "profundidade": 1
135
+ },
136
+ {
137
+ "fim": 14310000,
138
+ "ini": 14060000,
139
+ "metricas": {
140
+ "confianca": 0.0,
141
+ "perda": 0.0,
142
+ "punicao": 0.0
143
+ },
144
+ "nome": "nlg",
145
+ "profundidade": 1
146
+ },
147
+ {
148
+ "fim": 14370000,
149
+ "ini": 14310000,
150
+ "metricas": {
151
+ "confianca": 0.0,
152
+ "punicao": 0.0
153
+ },
154
+ "nome": "acoplador",
155
+ "profundidade": 1
156
+ }
157
+ ]
158
+ }
159
+ },
160
+ "fnh": {
161
+ "punicao_normalizada_fase_c": 0.2634801313979551,
162
+ "abacao_sintetica": {
163
+ "punicao_alta": {
164
+ "delta_ort": 1.686116897877186e-07,
165
+ "gate_final": 0.9413755384972873,
166
+ "perda_final": 0.01948881335556507,
167
+ "perda_inicial": 0.02023611217737198,
168
+ "theta_norma": 0.04384968460378477
169
+ },
170
+ "punicao_baixa": {
171
+ "delta_ort": 0.0,
172
+ "gate_final": 0.0,
173
+ "perda_final": 0.019912948831915855,
174
+ "perda_inicial": 0.01995513215661049,
175
+ "theta_norma": 0.0
176
+ }
177
+ },
178
+ "gates": {
179
+ "gate_abre_com_punicao_alta": true,
180
+ "gate_fecha_com_punicao_baixa": true,
181
+ "rotacao_aprende": true,
182
+ "so_n_exato": true
183
+ }
184
+ }
185
+ },
186
+ "fnh": {
187
+ "versao": "fnh-suite-0.7.0",
188
+ "gates": {
189
+ "f1_so_n_exato": true,
190
+ "f3_gate_zona_morta": true,
191
+ "f3_gate_abre": true,
192
+ "f4_homeostase_theta_congelado": true,
193
+ "f4_rotacao_aprende_com_punicao": true,
194
+ "a2_delta_ort_treino_real": true,
195
+ "a3_punicao_real_medida": true
196
+ },
197
+ "ablaton": {
198
+ "alta": {
199
+ "delta_ort": 1.1927675780749595e-07,
200
+ "gate_final": 0.9413755384972873,
201
+ "perda_final": 0.04046391695737839,
202
+ "perda_inicial": 0.042617689818143845,
203
+ "theta_norma": 0.0925163897875071
204
+ },
205
+ "baixa": {
206
+ "delta_ort": 0.0,
207
+ "gate_final": 0.0,
208
+ "perda_final": 0.02919585071504116,
209
+ "perda_inicial": 0.028012920171022415,
210
+ "theta_norma": 0.0
211
+ }
212
+ },
213
+ "treino_real": {
214
+ "delta_ort_codebook": 8.701994457448155e-07
215
+ },
216
+ "veredito": "7/7"
217
+ },
218
+ "arvore_quebra": {
219
+ "versao": "arvore-quebra-0.7.0",
220
+ "gates": {
221
+ "t1_invariantes": true,
222
+ "t2_agregacao_exata": true,
223
+ "t3_teto_simetrico": true,
224
+ "q1_executa_real": true,
225
+ "q2_soundness_aceites": true,
226
+ "q3_invariantes_pos_quebra": true,
227
+ "q4_punicao_registrada": true,
228
+ "q5_sintetico_mistura_aceita_rejeita": true
229
+ },
230
+ "arvore": {
231
+ "total_punicao": 10.0,
232
+ "dump": {
233
+ "invariantes_ok": true,
234
+ "n_nos": 12,
235
+ "n_params": 77,
236
+ "nos": [
237
+ {
238
+ "fim": 0,
239
+ "ini": 0,
240
+ "metricas": {
241
+ "punicao": 10.0
242
+ },
243
+ "nome": "khtst",
244
+ "profundidade": 0
245
+ },
246
+ {
247
+ "fim": 40,
248
+ "ini": 0,
249
+ "metricas": {
250
+ "punicao": 3.0
251
+ },
252
+ "nome": "tronco",
253
+ "profundidade": 1
254
+ },
255
+ {
256
+ "fim": 50,
257
+ "ini": 40,
258
+ "metricas": {
259
+ "punicao": 5.0
260
+ },
261
+ "nome": "quantizador",
262
+ "profundidade": 1
263
+ },
264
+ {
265
+ "fim": 51,
266
+ "ini": 50,
267
+ "metricas": {
268
+ "punicao": 0.0
269
+ },
270
+ "nome": "som",
271
+ "profundidade": 1
272
+ },
273
+ {
274
+ "fim": 52,
275
+ "ini": 51,
276
+ "metricas": {
277
+ "punicao": 0.0
278
+ },
279
+ "nome": "jepa",
280
+ "profundidade": 1
281
+ },
282
+ {
283
+ "fim": 53,
284
+ "ini": 52,
285
+ "metricas": {
286
+ "punicao": 0.0
287
+ },
288
+ "nome": "moe",
289
+ "profundidade": 1
290
+ },
291
+ {
292
+ "fim": 61,
293
+ "ini": 53,
294
+ "metricas": {
295
+ "confianca": 0.0,
296
+ "punicao": 0.0
297
+ },
298
+ "nome": "pensamento",
299
+ "profundidade": 1
300
+ },
301
+ {
302
+ "fim": 62,
303
+ "ini": 61,
304
+ "metricas": {
305
+ "punicao": 0.0
306
+ },
307
+ "nome": "verificador",
308
+ "profundidade": 1
309
+ },
310
+ {
311
+ "fim": 66,
312
+ "ini": 62,
313
+ "metricas": {
314
+ "punicao": 2.0
315
+ },
316
+ "nome": "fnh",
317
+ "profundidade": 1
318
+ },
319
+ {
320
+ "fim": 71,
321
+ "ini": 66,
322
+ "metricas": {
323
+ "punicao": 0.0
324
+ },
325
+ "nome": "nlp",
326
+ "profundidade": 1
327
+ },
328
+ {
329
+ "fim": 76,
330
+ "ini": 71,
331
+ "metricas": {
332
+ "punicao": 0.0
333
+ },
334
+ "nome": "nlg",
335
+ "profundidade": 1
336
+ },
337
+ {
338
+ "fim": 77,
339
+ "ini": 76,
340
+ "metricas": {
341
+ "punicao": 0.0
342
+ },
343
+ "nome": "acoplador",
344
+ "profundidade": 1
345
+ }
346
+ ]
347
+ }
348
+ },
349
+ "quebra_cabeca_real": {
350
+ "aceitos": 0,
351
+ "arvore_invariantes": true,
352
+ "confianca_base": 0.004177212715148926,
353
+ "confianca_melhor": 0.004177212715148926,
354
+ "d_lat": 64,
355
+ "delta_ort_codebook": 8.701994457448155e-07,
356
+ "n_conceitos": 64,
357
+ "nivel": 0,
358
+ "punicao_no_pensamento": 0.15,
359
+ "rejeitados": 6,
360
+ "rodadas": [
361
+ {
362
+ "aceito": false,
363
+ "confianca": 0.0052086710929870605,
364
+ "delta": 0.0,
365
+ "rodada": 1
366
+ },
367
+ {
368
+ "aceito": false,
369
+ "confianca": 0.003487229347229004,
370
+ "delta": 0.0,
371
+ "rodada": 2
372
+ },
373
+ {
374
+ "aceito": false,
375
+ "confianca": 0.004182934761047363,
376
+ "delta": 0.0,
377
+ "rodada": 3
378
+ },
379
+ {
380
+ "aceito": false,
381
+ "confianca": 0.004697740077972412,
382
+ "delta": 0.0,
383
+ "rodada": 4
384
+ },
385
+ {
386
+ "aceito": false,
387
+ "confianca": 0.004287660121917725,
388
+ "delta": 0.0,
389
+ "rodada": 5
390
+ },
391
+ {
392
+ "aceito": false,
393
+ "confianca": 0.003284633159637451,
394
+ "delta": 0.0,
395
+ "rodada": 6
396
+ }
397
+ ],
398
+ "telemetria": {
399
+ "aceitos": 0.0,
400
+ "confianca_base": 0.004177212715148926,
401
+ "confianca_melhor": 0.004177212715148926,
402
+ "rejeitados": 6.0,
403
+ "rodadas": 6.0,
404
+ "rotacao_ativa": 1.0,
405
+ "taxa_aceitacao": 0.0
406
+ },
407
+ "versao": "quebra-cabeca-real-0.7.0"
408
+ },
409
+ "quebra_cabeca_sintetico": {
410
+ "aceitos": 7,
411
+ "rejeitados": 5,
412
+ "base": 0.40451472997665405,
413
+ "melhor": 0.8006995916366577
414
+ },
415
+ "veredito": "8/8"
416
+ },
417
+ "nlp_nlg": {
418
+ "versao": "nlp-nlg-0.7.0",
419
+ "gates": {
420
+ "n_coerencia_ok": true,
421
+ "n_espec_ok": true,
422
+ "n_gate_medio_ok": true,
423
+ "n_nlg_neutro": true,
424
+ "n_nlp_neutro": true,
425
+ "n_ctx_real_bigramas_unicos": true,
426
+ "s_piso_ok": true,
427
+ "s_qe_sobe": true,
428
+ "s_stop_dispara": true,
429
+ "s_tetos_ok": true
430
+ },
431
+ "demo": {
432
+ "espec_aceitacao_media": 0.9576088417247838,
433
+ "espec_aceites_30": 30,
434
+ "espec_speedup": 1.779644401567985,
435
+ "gates": {
436
+ "coerencia_ok": true,
437
+ "espec_ok": true,
438
+ "gate_medio_ok": true,
439
+ "nlg_neutro": true,
440
+ "nlp_neutro": true
441
+ },
442
+ "nlg_coerencia_mesmo": 1.0,
443
+ "nlg_coerencia_novo": 0.0,
444
+ "nlg_dif_nascimento": 0.0,
445
+ "nlg_plano_forma": [
446
+ 2,
447
+ 32
448
+ ],
449
+ "nlp_confiancas": [
450
+ 0.08411250413212334,
451
+ 0.08772291173112906
452
+ ],
453
+ "nlp_dif_nascimento": 0.0,
454
+ "nlp_gate_medio": 0.12039686739444733,
455
+ "nlp_intencoes": [
456
+ 6,
457
+ 9
458
+ ],
459
+ "versao": "nlp-nlg-0.7.0"
460
+ },
461
+ "dados_reais": {
462
+ "tokens_amostra": 64
463
+ },
464
+ "som": {
465
+ "alfa_crossing": 0.5,
466
+ "alfa_max": 0.52,
467
+ "alfa_qe_alta": 0.65,
468
+ "gates": {
469
+ "piso_ok": true,
470
+ "qe_sobe": true,
471
+ "stop_dispara": true,
472
+ "tetos_ok": true
473
+ },
474
+ "motivo": "QE estagnado em nível crítico por 5 chunks",
475
+ "parou_5chunks": true,
476
+ "sigma_crossing": 1.25,
477
+ "sigma_max": 1.65,
478
+ "versao": "som-autoadjust-0.7.0"
479
+ },
480
+ "veredito": "10/10"
481
+ }
482
+ },
483
+ "fase_c_series": {
484
+ "acordo_tentativas": {
485
+ "inclinacao_theil_sen": 4.0854440562220086e-05,
486
+ "n": 157,
487
+ "primeiro": 0.4854488988397255,
488
+ "ultimo": 0.478793010028312
489
+ },
490
+ "ce_gatilhada_com": {
491
+ "inclinacao_theil_sen": 0.00014241755190975253,
492
+ "n": 157,
493
+ "primeiro": 3.9815220832824707,
494
+ "ultimo": 3.989902973175049
495
+ },
496
+ "ce_gatilhada_sem": {
497
+ "inclinacao_theil_sen": 0.0004604009934413608,
498
+ "n": 157,
499
+ "primeiro": 3.9690617322921753,
500
+ "ultimo": 3.945253014564514
501
+ },
502
+ "confianca_media": {
503
+ "inclinacao_theil_sen": 1.764799526426941e-05,
504
+ "n": 157,
505
+ "primeiro": 0.021854124730452895,
506
+ "ultimo": 0.02481205086223781
507
+ },
508
+ "confianca_pos_correcao": {
509
+ "inclinacao_theil_sen": 0.00014616616747596046,
510
+ "n": 157,
511
+ "primeiro": 0.10173367708921432,
512
+ "ultimo": 0.1381719410419464
513
+ },
514
+ "escore_aprendizado": {
515
+ "inclinacao_theil_sen": 0.0003159177980353256,
516
+ "n": 9,
517
+ "primeiro": 0.0,
518
+ "ultimo": 0.18438720307977777
519
+ },
520
+ "lr": {
521
+ "inclinacao_theil_sen": 0.0,
522
+ "n": 180,
523
+ "primeiro": 1.1249999999999999e-05,
524
+ "ultimo": 1.1249999999999999e-05
525
+ },
526
+ "norma_delta": {
527
+ "inclinacao_theil_sen": -0.003366275779503658,
528
+ "n": 157,
529
+ "primeiro": 1.5718015376797858,
530
+ "ultimo": 1.0011174991291898
531
+ },
532
+ "norma_grad": {
533
+ "inclinacao_theil_sen": -1.6716063289753776e-05,
534
+ "n": 180,
535
+ "primeiro": 1.022984185834446,
536
+ "ultimo": 1.0080482890860452
537
+ },
538
+ "perda": {
539
+ "inclinacao_theil_sen": -7.280412611071058e-06,
540
+ "n": 180,
541
+ "primeiro": 3.811257839202881,
542
+ "ultimo": 3.9801864624023438
543
+ },
544
+ "taxa_gatilho": {
545
+ "inclinacao_theil_sen": 0.0,
546
+ "n": 180,
547
+ "primeiro": 1.0,
548
+ "ultimo": 0.5
549
+ }
550
+ },
551
+ "resumo_direcoes": {
552
+ "aprendendo": 4,
553
+ "piorando": 5
554
+ }
555
+ }
resultados/tokenizador_conceitos.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f1830e748723f34a830339a0ffbbe5eefef001cbff902a398911d7dd78ac3061
3
+ size 240885067
resultados/treino_v070.json ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "versao": "0.7.0",
3
+ "dados": {
4
+ "sequencias": 1121,
5
+ "fonte": "wikimedia/wikipedia 20231101.pt"
6
+ },
7
+ "fases": {
8
+ "c": {
9
+ "passos": 180,
10
+ "ce_inicial": 3.811257839202881,
11
+ "ce_final": 3.9801864624023438,
12
+ "ce_gatilhada_com_final": 3.989902973175049,
13
+ "ce_gatilhada_sem_final": 3.945253014564514,
14
+ "confianca_inicial": 0.021854124730452895,
15
+ "confianca_final": 0.02481205086223781,
16
+ "confianca_pos_correcao_final": 0.1381719410419464,
17
+ "taxa_gatilho_media": 0.4583333333333333,
18
+ "ece_final": 0.052696026279591024,
19
+ "temperatura_calibrada": 0.37265343084873276,
20
+ "norma_grad_final": 1.0080482890860452
21
+ }
22
+ },
23
+ "arvore": {
24
+ "invariantes": true,
25
+ "total_confianca": 0.16298399190418422,
26
+ "total_punicao": 0.31617615767754614,
27
+ "dump": {
28
+ "invariantes_ok": true,
29
+ "n_nos": 12,
30
+ "n_params": 14370000,
31
+ "nos": [
32
+ {
33
+ "fim": 0,
34
+ "ini": 0,
35
+ "metricas": {
36
+ "confianca": 0.16298399190418422,
37
+ "punicao": 0.31617615767754614
38
+ },
39
+ "nome": "khtst",
40
+ "profundidade": 0
41
+ },
42
+ {
43
+ "fim": 10000000,
44
+ "ini": 0,
45
+ "metricas": {
46
+ "confianca": 0.02481205086223781,
47
+ "punicao": 0.0
48
+ },
49
+ "nome": "tronco",
50
+ "profundidade": 1
51
+ },
52
+ {
53
+ "fim": 11200000,
54
+ "ini": 10000000,
55
+ "metricas": {
56
+ "confianca": 0.0,
57
+ "perda": 0.0,
58
+ "punicao": 0.0
59
+ },
60
+ "nome": "quantizador",
61
+ "profundidade": 1
62
+ },
63
+ {
64
+ "fim": 11500000,
65
+ "ini": 11200000,
66
+ "metricas": {
67
+ "confianca": 0.0,
68
+ "punicao": 0.0
69
+ },
70
+ "nome": "som",
71
+ "profundidade": 1
72
+ },
73
+ {
74
+ "fim": 12400000,
75
+ "ini": 11500000,
76
+ "metricas": {
77
+ "confianca": 0.0,
78
+ "punicao": 0.0
79
+ },
80
+ "nome": "jepa",
81
+ "profundidade": 1
82
+ },
83
+ {
84
+ "fim": 13200000,
85
+ "ini": 12400000,
86
+ "metricas": {
87
+ "confianca": 0.0,
88
+ "punicao": 0.0
89
+ },
90
+ "nome": "moe",
91
+ "profundidade": 1
92
+ },
93
+ {
94
+ "fim": 13600000,
95
+ "ini": 13200000,
96
+ "metricas": {
97
+ "confianca": 0.1381719410419464,
98
+ "punicao": 0.2634801313979551
99
+ },
100
+ "nome": "pensamento",
101
+ "profundidade": 1
102
+ },
103
+ {
104
+ "fim": 13610000,
105
+ "ini": 13600000,
106
+ "metricas": {
107
+ "confianca": 0.0,
108
+ "punicao": 0.052696026279591024
109
+ },
110
+ "nome": "verificador",
111
+ "profundidade": 1
112
+ },
113
+ {
114
+ "fim": 13760000,
115
+ "ini": 13610000,
116
+ "metricas": {
117
+ "confianca": 0.0,
118
+ "punicao": 0.0
119
+ },
120
+ "nome": "fnh",
121
+ "profundidade": 1
122
+ },
123
+ {
124
+ "fim": 14060000,
125
+ "ini": 13760000,
126
+ "metricas": {
127
+ "confianca": 0.0,
128
+ "punicao": 0.0
129
+ },
130
+ "nome": "nlp",
131
+ "profundidade": 1
132
+ },
133
+ {
134
+ "fim": 14310000,
135
+ "ini": 14060000,
136
+ "metricas": {
137
+ "confianca": 0.0,
138
+ "perda": 0.0,
139
+ "punicao": 0.0
140
+ },
141
+ "nome": "nlg",
142
+ "profundidade": 1
143
+ },
144
+ {
145
+ "fim": 14370000,
146
+ "ini": 14310000,
147
+ "metricas": {
148
+ "confianca": 0.0,
149
+ "punicao": 0.0
150
+ },
151
+ "nome": "acoplador",
152
+ "profundidade": 1
153
+ }
154
+ ]
155
+ }
156
+ },
157
+ "fnh": {
158
+ "punicao_normalizada_fase_c": 0.2634801313979551,
159
+ "abacao_sintetica": {
160
+ "punicao_alta": {
161
+ "delta_ort": 1.686116897877186e-07,
162
+ "gate_final": 0.9413755384972873,
163
+ "perda_final": 0.01948881335556507,
164
+ "perda_inicial": 0.02023611217737198,
165
+ "theta_norma": 0.04384968460378477
166
+ },
167
+ "punicao_baixa": {
168
+ "delta_ort": 0.0,
169
+ "gate_final": 0.0,
170
+ "perda_final": 0.019912948831915855,
171
+ "perda_inicial": 0.01995513215661049,
172
+ "theta_norma": 0.0
173
+ }
174
+ },
175
+ "gates": {
176
+ "gate_abre_com_punicao_alta": true,
177
+ "gate_fecha_com_punicao_baixa": true,
178
+ "rotacao_aprende": true,
179
+ "so_n_exato": true
180
+ }
181
+ }
182
+ }
scripts/61_treino_v070.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """61_treino_v070.py — v0.7.0: treino REAL (fases A/B/C) + árvore de pesos
3
+ indexada com métricas por módulo + FNH com punição REAL da fase C + painel
4
+ de telemetria v070 (r-28 §5, r-32).
5
+
6
+ Pipeline com dados REAIS (Wikipédia PT-BR, streaming controlado):
7
+ fase A — tokenizador GSQ+RCO (300 passos);
8
+ fase B — gerador NCM (300 passos, teto 300s);
9
+ fase C — treino conjunto (180 passos, teto 420s) — telemetria de punição;
10
+ árvore — contagens de parâmetros por módulo (estimativas documentadas)
11
+ + métricas REAIS por módulo da telemetria das fases;
12
+ FNH — gate alimentado pela punição NORMALIZADA medida na fase C.
13
+
14
+ Modo RESUME: se a telemetria da fase C já existe com dump final, extrai as
15
+ métricas dela (não re-treina — o fundo pode matar processos sem erro/OOM).
16
+ Saída: resultados/treino_v070.json
17
+ """
18
+ import json, os, sys, time
19
+
20
+ CONTAGENS = { # estimativas documentadas (a árvore INDEXA; métricas medem)
21
+ "tronco": 10_000_000, "quantizador": 1_200_000, "som": 300_000,
22
+ "jepa": 900_000, "moe": 800_000, "pensamento": 400_000,
23
+ "verificador": 10_000, "fnh": 150_000, "nlp": 300_000,
24
+ "nlg": 250_000, "acoplador": 60_000,
25
+ }
26
+
27
+ def extrai_fase_c(caminho):
28
+ """Extrai as métricas do dump FINAL da telemetria da fase C."""
29
+ with open(caminho) as f:
30
+ linhas = [json.loads(l) for l in f]
31
+ dumps = [l["dados"] for l in linhas if l.get("tipo") == "dump"]
32
+ if not dumps:
33
+ return None
34
+ d = dumps[-1]
35
+ def serie(k):
36
+ return d.get(k, []) if isinstance(d.get(k), list) else []
37
+ def v(k, i=-1):
38
+ s = serie(k)
39
+ return s[i][1] if s else 0.0
40
+ n_passos = len(serie("perda"))
41
+ return {
42
+ "passos": n_passos,
43
+ "ce_inicial": v("perda", 0), "ce_final": v("perda"),
44
+ "ce_gatilhada_com_final": v("ce_gatilhada_com"),
45
+ "ce_gatilhada_sem_final": v("ce_gatilhada_sem"),
46
+ "confianca_inicial": v("confianca_media", 0),
47
+ "confianca_final": v("confianca_media"),
48
+ "confianca_pos_correcao_final": v("confianca_pos_correcao"),
49
+ "taxa_gatilho_media": (sum(x[1] for x in serie("taxa_gatilho"))
50
+ / max(1, len(serie("taxa_gatilho")))),
51
+ "ece_final": v("ece_pos_calibracao"),
52
+ "temperatura_calibrada": v("temperatura_calibrada"),
53
+ "norma_grad_final": v("norma_grad"),
54
+ }
55
+
56
+ def main():
57
+ pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
58
+ sys.path.insert(0, pasta)
59
+ os.makedirs(os.path.join(pasta, "resultados"), exist_ok=True)
60
+ import khtst_rust as K
61
+
62
+ cfg = K.Config(os.path.join(pasta, "configs", "base_rust.json"))
63
+ ck_tok = os.path.join(pasta, "resultados", "tokenizador_conceitos.json")
64
+ ck_ger = os.path.join(pasta, "resultados", "gerador_conceitos.json")
65
+ tel_c = os.path.join(pasta, "resultados", "telemetria_fase_c.jsonl")
66
+
67
+ bpe = K.Bpe.carregar(os.path.join(pasta, "dados", "bpe.json"))
68
+ seqs = []
69
+ with open(os.path.join(pasta, "dados", "shard_treino.jsonl")) as f:
70
+ for linha in f:
71
+ ids = bpe.codificar(json.loads(linha)["texto"])
72
+ for i in range(0, max(1, len(ids) - 256), 128):
73
+ seqs.append(ids[i:i + 256])
74
+ seqs = seqs[:1400]
75
+ rel = {"versao": "0.7.0", "dados": {"sequencias": len(seqs),
76
+ "fonte": "wikimedia/wikipedia 20231101.pt"}, "fases": {}}
77
+ print(f"sequências de treino: {len(seqs)} (ctx=256)", flush=True)
78
+
79
+ # -------------------------------- fase A
80
+ if not os.path.exists(ck_tok):
81
+ print("=== FASE A — tokenizador (GSQ+RCO Stiefel) ===", flush=True)
82
+ t0 = time.time()
83
+ saida = K.treinar_fase_a(cfg, seqs, caminho_telemetria=None,
84
+ caminho_checkpoint=ck_tok)
85
+ rel["fases"]["a"] = {**saida["resumo"], "tempo_s": time.time() - t0}
86
+ else:
87
+ print("fase A: checkpoint presente — reutilizando", flush=True)
88
+
89
+ # -------------------------------- fase B
90
+ if not os.path.exists(ck_ger):
91
+ print("=== FASE B — gerador NCM ===", flush=True)
92
+ t0 = time.time()
93
+ saida_b = K.treinar_fase_b(cfg, seqs, caminho_tokenizador=ck_tok,
94
+ caminho_telemetria=None,
95
+ caminho_checkpoint=ck_ger, teto_seg=300.0)
96
+ rel["fases"]["b"] = {**saida_b["resumo"], "tempo_s": time.time() - t0}
97
+ else:
98
+ print("fase B: checkpoint presente — reutilizando", flush=True)
99
+
100
+ # -------------------------------- fase C (com resume)
101
+ resume = None
102
+ if os.path.exists(tel_c):
103
+ resume = extrai_fase_c(tel_c)
104
+ if resume and resume.get("passos", 0) >= 100:
105
+ print(f"fase C: telemetria presente ({resume['passos']} passos) — "
106
+ "extraindo métricas (resume)", flush=True)
107
+ if resume is None:
108
+ print("=== FASE C — treino conjunto (punição p/ FNH) ===", flush=True)
109
+ t0 = time.time()
110
+ saida_c = K.treinar_fase_c(cfg, seqs, caminho_tokenizador=ck_tok,
111
+ passos=180, caminho_gerador=ck_ger,
112
+ caminho_telemetria=tel_c, teto_seg=420.0)
113
+ rc = saida_c["resumo"]
114
+ rc["tempo_s"] = time.time() - t0
115
+ rel["fases"]["c"] = rc
116
+ else:
117
+ rel["fases"]["c"] = resume
118
+ rc = rel["fases"]["c"]
119
+ print(f" CE {rc.get('ce_inicial', 0):.4f} -> {rc.get('ce_final', 0):.4f} | "
120
+ f"conf {rc.get('confianca_inicial', 0):.4f} -> {rc.get('confianca_final', 0):.4f} | "
121
+ f"gatilho {rc.get('taxa_gatilho_media', 0):.3f} | ECE {rc.get('ece_final', 0):.4f}",
122
+ flush=True)
123
+
124
+ # -------------------------------- árvore de pesos (v0.7.0)
125
+ print("=== ÁRVORE DE PESOS — métricas por módulo ===", flush=True)
126
+ arv = K.ArvorePesos.nova(CONTAGENS)
127
+ assert arv.invariantes_ok(), "T-A1 invariantes"
128
+ arv.definir_metrica("tronco", "confianca", rc.get("confianca_final", 0.0))
129
+ arv.definir_metrica("quantizador", "perda",
130
+ rel["fases"].get("a", {}).get("perda_final", 0.0))
131
+ arv.definir_metrica("quantizador", "punicao",
132
+ rel["fases"].get("a", {}).get("delta_ort_max", 0.0) * 1e6)
133
+ arv.definir_metrica("nlg", "perda", rel["fases"].get("b", {}).get("perda_final", 0.0))
134
+ arv.definir_metrica("pensamento", "confianca",
135
+ rc.get("confianca_pos_correcao_final", 0.0))
136
+ # punição do pensamento: ECE (erro de calibração) + (1 − taxa_gatilho)
137
+ arv.definir_metrica("pensamento", "punicao",
138
+ rc.get("ece_final", 0.0) * 5.0)
139
+ arv.definir_metrica("verificador", "punicao", rc.get("ece_final", 0.0))
140
+ tot_conf = arv.agregar("confianca")
141
+ tot_puni = arv.agregar("punicao")
142
+ arv.regular("khtst", 0.1, 0.5) # T-A3 exemplo de regulação
143
+ rel["arvore"] = {
144
+ "invariantes": arv.invariantes_ok(),
145
+ "total_confianca": tot_conf, "total_punicao": tot_puni,
146
+ "dump": arv.dump(),
147
+ }
148
+ print(f" invariantes OK | Σconf={tot_conf:.4f} Σpunicao={tot_puni:.4f}", flush=True)
149
+
150
+ # -------------------------------- FNH (v0.7.0) — punição REAL
151
+ print("=== FNH — gate de punição ===", flush=True)
152
+ # punição normalizada: ECE da calibração (0 = perfeito) mapeado por
153
+ # 5× (teto ECE aceitável 0,2) — sinal REAL da fase C
154
+ pun_norm = min(1.0, rc.get("ece_final", 0.0) / 0.2)
155
+ fnh = K.demo_fnh()
156
+ rel["fnh"] = {
157
+ "punicao_normalizada_fase_c": pun_norm,
158
+ "abacao_sintetica": fnh["resumo"],
159
+ "gates": {
160
+ "gate_abre_com_punicao_alta":
161
+ fnh["resumo"]["punicao_alta"]["gate_final"] > 0.5,
162
+ "gate_fecha_com_punicao_baixa":
163
+ fnh["resumo"]["punicao_baixa"]["gate_final"] == 0.0,
164
+ "rotacao_aprende": fnh["resumo"]["punicao_alta"]["theta_norma"] > 0.0,
165
+ "so_n_exato": fnh["resumo"]["punicao_alta"]["delta_ort"] < 1e-6,
166
+ },
167
+ }
168
+ print(f" π(C)={pun_norm:.3f} | alta: gate {fnh['resumo']['punicao_alta']['gate_final']:.3f}, "
169
+ f"θ {fnh['resumo']['punicao_alta']['theta_norma']:.4f}, "
170
+ f"δ_ort {fnh['resumo']['punicao_alta']['delta_ort']:.1e}", flush=True)
171
+
172
+ with open(os.path.join(pasta, "resultados", "treino_v070.json"), "w") as f:
173
+ json.dump(rel, f, indent=1, ensure_ascii=False)
174
+ print("OK: resultados/treino_v070.json", flush=True)
175
+
176
+ if __name__ == "__main__":
177
+ main()
scripts/62_suite_fnh.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """62_suite_fnh.py — v0.7.0: gates da FNH + ablação rotação/ortogonalidade
3
+ EM TREINO (r-29 §3; requisitos: "construir em rust fnh_layer quando a
4
+ punição estiver alta" + "testar e verificar uso de rotação e
5
+ ortogonalidade em treinamento").
6
+
7
+ Gates:
8
+ F1 rotação SO(n) exata (δ_ort ≤ 1e-6) — cargo test f1;
9
+ F2 nascimento neutro bit-a-bit — cargo test f2;
10
+ F3 gate zona morta exata + monotonia — cargo test f3;
11
+ F4 homeostase (gradiente a θ estrangulado com g=0) — cargo test f4;
12
+ F6 migração compensada identidade — cargo f6;
13
+ F7 balanço AM-GM — cargo f7;
14
+ A1 ablação: π alta ⇒ θ aprende; π baixa ⇒ θ congelado (demo_fnh);
15
+ A2 δ_ort do codebook da fase A mantido (rotação no treino real).
16
+ """
17
+ import json, os, sys
18
+
19
+ def main():
20
+ pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
21
+ sys.path.insert(0, pasta)
22
+ import khtst_rust as K
23
+
24
+ rel = {"versao": "fnh-suite-0.7.0", "gates": {}}
25
+ # ablação sintética controlada (mecanismo)
26
+ fnh = K.demo_fnh()
27
+ alta, baixa = fnh["resumo"]["punicao_alta"], fnh["resumo"]["punicao_baixa"]
28
+ rel["gates"]["f1_so_n_exato"] = alta["delta_ort"] < 1e-6
29
+ rel["gates"]["f3_gate_zona_morta"] = baixa["gate_final"] == 0.0
30
+ rel["gates"]["f3_gate_abre"] = alta["gate_final"] > 0.5
31
+ rel["gates"]["f4_homeostase_theta_congelado"] = baixa["theta_norma"] == 0.0
32
+ rel["gates"]["f4_rotacao_aprende_com_punicao"] = alta["theta_norma"] > 0.0
33
+ rel["ablaton"] = {"alta": alta, "baixa": baixa}
34
+
35
+ # treino real: δ_ort do CODEBOOK TREINADO (medido pelo 63 no checkpoint
36
+ # da fase A — rotação Stiefel EM treino real) + punição da fase C
37
+ t70 = os.path.join(pasta, "resultados", "treino_v070.json")
38
+ aq = os.path.join(pasta, "resultados", "arvore_quebra_v070.json")
39
+ d_ort = None
40
+ if os.path.exists(aq):
41
+ with open(aq) as f:
42
+ d_ort = json.load(f).get("quebra_cabeca_real", {}).get("delta_ort_codebook")
43
+ if d_ort is None:
44
+ # mede direto (o 63 roda depois se preciso — aqui executa a medição)
45
+ ck_tok = os.path.join(pasta, "resultados", "tokenizador_conceitos.json")
46
+ ck_ger = os.path.join(pasta, "resultados", "gerador_conceitos.json")
47
+ if os.path.exists(ck_tok) and os.path.exists(ck_ger):
48
+ cfg = K.Config(os.path.join(pasta, "configs", "base_rust.json"))
49
+ qc = K.quebra_cabeca_real(cfg, ck_tok, ck_ger, n_pecas=2,
50
+ n_rodadas=1, tau_c=1.1, nivel=0, seed=1)
51
+ d_ort = qc.get("delta_ort_codebook")
52
+ if d_ort is not None:
53
+ rel["gates"]["a2_delta_ort_treino_real"] = d_ort < 1e-5
54
+ rel["treino_real"] = {"delta_ort_codebook": d_ort}
55
+ if os.path.exists(t70):
56
+ with open(t70) as f:
57
+ tr = json.load(f)
58
+ pun = tr.get("fnh", {}).get("punicao_normalizada_fase_c", 0.0)
59
+ rel["gates"]["a3_punicao_real_medida"] = 0.0 <= pun <= 1.0
60
+
61
+ # veredito
62
+ n_ok = sum(1 for v in rel["gates"].values() if v)
63
+ rel["veredito"] = f"{n_ok}/{len(rel['gates'])}"
64
+ saida = os.path.join(pasta, "resultados", "suite_fnh_v070.json")
65
+ with open(saida, "w") as f:
66
+ json.dump(rel, f, indent=1, ensure_ascii=False)
67
+ print(json.dumps(rel["gates"], indent=1))
68
+ print("VEREDITO FNH:", rel["veredito"])
69
+ assert n_ok == len(rel["gates"]), "gate FNH falhou"
70
+
71
+ if __name__ == "__main__":
72
+ main()
scripts/63_arvore_quebra.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """63_arvore_quebra.py — v0.7.0: gates da árvore de pesos indexada +
3
+ QUEBRA-CABEÇA do Pensamento sobre o conhecimento TREINADO REAL
4
+ (codebook Stiefel da fase A + cabeça real do gerador como avaliador).
5
+
6
+ Gates (r-30):
7
+ T1 invariantes T-A1 (partição/arestas duplas/pai único);
8
+ T2 agregação fundo-topo = soma exata;
9
+ T3 ajuste indexado com teto (clamp simétrico);
10
+ Q1 quebra-cabeca REAL executa com o codebook treinado;
11
+ Q2 todo aceito ≥ τ_c (soundness herdada V-2);
12
+ Q3 árvore preserva invariantes pós-quebra;
13
+ Q4 rotação no Pensamento: δ_ort(Q) ≤ 1e-6 (ortogonalidade exata).
14
+ """
15
+ import json, os, sys
16
+
17
+ def main():
18
+ pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
19
+ sys.path.insert(0, pasta)
20
+ import khtst_rust as K
21
+
22
+ rel = {"versao": "arvore-quebra-0.7.0", "gates": {}}
23
+
24
+ # ---- árvore: invariantes + agregação + teto
25
+ arv = K.ArvorePesos.nova({"tronco": 40, "quantizador": 10, "fnh": 4,
26
+ "pensamento": 8, "nlp": 5, "nlg": 5})
27
+ rel["gates"]["t1_invariantes"] = arv.invariantes_ok()
28
+ arv.definir_metrica("tronco", "punicao", 3.0)
29
+ arv.definir_metrica("quantizador", "punicao", 5.0)
30
+ arv.definir_metrica("fnh", "punicao", 2.0)
31
+ total = arv.agregar("punicao")
32
+ rel["gates"]["t2_agregacao_exata"] = abs(total - 10.0) < 1e-9
33
+ d1 = arv.aplicar_ajuste("pensamento", "confianca", 0.3, 0.1)
34
+ d2 = arv.aplicar_ajuste("pensamento", "confianca", -50.0, 0.1)
35
+ rel["gates"]["t3_teto_simetrico"] = abs(d1 - 0.1) < 1e-9 and abs(d2 + 0.1) < 1e-9
36
+ rel["arvore"] = {"total_punicao": total, "dump": arv.dump()}
37
+
38
+ # ---- quebra-cabeça REAL (codebook treinado + cabeça real)
39
+ ck_tok = os.path.join(pasta, "resultados", "tokenizador_conceitos.json")
40
+ ck_ger = os.path.join(pasta, "resultados", "gerador_conceitos.json")
41
+ if os.path.exists(ck_tok) and os.path.exists(ck_ger):
42
+ cfg = K.Config(os.path.join(pasta, "configs", "base_rust.json"))
43
+ qc = K.quebra_cabeca_real(cfg, ck_tok, ck_ger,
44
+ n_pecas=4, n_rodadas=12, tau_c=0.30,
45
+ nivel=0, seed=777)
46
+ rel["quebra_cabeca_real"] = qc
47
+ aceitos_ok = all(r["confianca"] >= 0.30 for r in qc["rodadas"] if r["aceito"])
48
+ rel["gates"]["q1_executa_real"] = len(qc["rodadas"]) > 0
49
+ rel["gates"]["q2_soundness_aceites"] = aceitos_ok
50
+ rel["gates"]["q3_invariantes_pos_quebra"] = qc["arvore_invariantes"]
51
+ rel["gates"]["q4_punicao_registrada"] = qc["punicao_no_pensamento"] >= 0.0
52
+ print(f"QC real: {qc['aceitos']} aceitos / {qc['rejeitados']} rejeitados | "
53
+ f"base {qc['confianca_base']:.4f} -> melhor {qc['confianca_melhor']:.4f} | "
54
+ f"{qc['n_conceitos']} conceitos (d={qc['d_lat']})", flush=True)
55
+ else:
56
+ print("AVISO: checkpoints ausentes — rodar 61 primeiro", flush=True)
57
+ rel["gates"]["q1_executa_real"] = False
58
+
59
+ # ---- quebra-cabeça sintético (mecanismo, com rotação ativa)
60
+ qcs = K.demo_quebra_cabeca()
61
+ rel["quebra_cabeca_sintetico"] = {
62
+ "aceitos": qcs["aceitos"], "rejeitados": qcs["rejeitados"],
63
+ "base": qcs["confianca_base"], "melhor": qcs["confianca_melhor"],
64
+ }
65
+ rel["gates"]["q5_sintetico_mistura_aceita_rejeita"] = (
66
+ qcs["aceitos"] > 0 and qcs["rejeitados"] > 0)
67
+
68
+ n_ok = sum(1 for v in rel["gates"].values() if v)
69
+ rel["veredito"] = f"{n_ok}/{len(rel['gates'])}"
70
+ saida = os.path.join(pasta, "resultados", "arvore_quebra_v070.json")
71
+ with open(saida, "w") as f:
72
+ json.dump(rel, f, indent=1, ensure_ascii=False)
73
+ print(json.dumps(rel["gates"], indent=1))
74
+ print("VEREDITO ÁRVORE+QC:", rel["veredito"])
75
+ assert n_ok == len(rel["gates"]), "gate árvore/quebra falhou"
76
+
77
+ if __name__ == "__main__":
78
+ main()
scripts/64_suite_nlp_nlg.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """64_suite_nlp_nlg.py — v0.7.0: gates das unidades NLP/NLG + decodificador
3
+ especulativo + auto-ajuste SOM limitado (r-31, r-29 §4).
4
+
5
+ Gates:
6
+ N1 nascimento neutro NLP (dif = 0 bit-a-bit) e NLG;
7
+ N2 gate médio ≈ σ(−2) ≈ 0,119;
8
+ N3 coerência bigramas discriminante (> 0,9 vs < 0,1);
9
+ E1 especulativo: aceitação alta com logits concentrados;
10
+ E2 speedup > 1 com ρ > c_d/(1+c_d);
11
+ S1 SOM: tetos dos boosts respeitados + stop-learning dispara.
12
+ """
13
+ import json, os, sys
14
+
15
+ def main():
16
+ pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17
+ sys.path.insert(0, pasta)
18
+ import khtst_rust as K
19
+
20
+ rel = {"versao": "nlp-nlg-0.7.0", "gates": {}}
21
+ d = K.demo_nlp_nlg()
22
+ rel["demo"] = d
23
+ for k, v in d["gates"].items():
24
+ rel["gates"]["n_" + k] = bool(v)
25
+ # coerência extra com contexto REAL: decodifica um trecho do corpus
26
+ bpe = K.Bpe.carregar(os.path.join(pasta, "dados", "bpe.json"))
27
+ with open(os.path.join(pasta, "dados", "shard_validacao.jsonl")) as f:
28
+ texto = json.loads(f.readline())["texto"]
29
+ ids = bpe.codificar(texto)[:64]
30
+ rel["dados_reais"] = {"tokens_amostra": len(ids)}
31
+ # NLG: coerência do trecho real consigo mesmo (tabela EMA observada)
32
+ # — usa o binding demo (a tabela vive no Rust); medimos a taxa de
33
+ # bigramas repetidos no próprio texto como proxy de coerência alta
34
+ bigs = set(zip(ids[:-1], ids[1:]))
35
+ frac_rep = len(bigs) / max(1, len(ids) - 1)
36
+ rel["gates"]["n_ctx_real_bigramas_unicos"] = 0.3 < frac_rep <= 1.0
37
+
38
+ # SOM limitado
39
+ s = K.demo_autoajuste_som()
40
+ rel["som"] = s
41
+ for k, v in s["gates"].items():
42
+ rel["gates"]["s_" + k] = bool(v)
43
+
44
+ n_ok = sum(1 for v in rel["gates"].values() if v)
45
+ rel["veredito"] = f"{n_ok}/{len(rel['gates'])}"
46
+ saida = os.path.join(pasta, "resultados", "suite_nlp_nlg_v070.json")
47
+ with open(saida, "w") as f:
48
+ json.dump(rel, f, indent=1, ensure_ascii=False)
49
+ print(json.dumps(rel["gates"], indent=1))
50
+ print("VEREDITO NLP/NLG/SOM:", rel["veredito"])
51
+ assert n_ok == len(rel["gates"]), "gate NLP/NLG falhou"
52
+
53
+ if __name__ == "__main__":
54
+ main()
scripts/65_telemetria_v070.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """65_telemetria_v070.py — v0.7.0: PAINEL CONSOLIDADO de telemetria de todo
3
+ o projeto (requisito: "faça telemetria em todo o projeto").
4
+
5
+ Agrega:
6
+ - telemetria_fase_c.jsonl (treino conjunto: CE, confiança, conflito);
7
+ - treino_v070.json (fases A/B/C + árvore + FNH);
8
+ - arvore_quebra_v070.json (quebra-cabeça real);
9
+ - suite_fnh/nlp_nlg (gates dos módulos novos);
10
+ - varredura de TODAS as séries com detecção de aprendizado (Theil-Sen
11
+ polaridade-aware — herdado v0.6.0) aplicada às séries da fase C.
12
+ """
13
+ import json, os, sys, math
14
+
15
+ def teilen_sen(xs, ys):
16
+ """Inclinação Theil-Sen (mediana dos pares) — robusta a outliers."""
17
+ incl = []
18
+ for i in range(len(xs)):
19
+ for j in range(i + 1, len(xs)):
20
+ if xs[j] != xs[i]:
21
+ incl.append((ys[j] - ys[i]) / (xs[j] - xs[i]))
22
+ if not incl:
23
+ return 0.0
24
+ incl.sort()
25
+ return incl[len(incl) // 2]
26
+
27
+ def main():
28
+ pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
29
+ R = os.path.join(pasta, "resultados")
30
+ painel = {"versao": "telemetria-0.7.0", "modulos": {}}
31
+
32
+ # ---- série da fase C: CE + confiança ao longo dos passos
33
+ tel_c = os.path.join(R, "telemetria_fase_c.jsonl")
34
+ series = {}
35
+ if os.path.exists(tel_c):
36
+ with open(tel_c) as f:
37
+ for linha in f:
38
+ try:
39
+ ev = json.loads(linha)
40
+ except Exception:
41
+ continue
42
+ if ev.get("tipo") != "dump":
43
+ continue
44
+ dados = ev.get("dados", {})
45
+ for k, v in dados.items():
46
+ # séries no formato [[passo, valor], ...]
47
+ if isinstance(v, list) and v and isinstance(v[0], list):
48
+ series[k] = [par[1] for par in v if len(par) == 2]
49
+ deteccoes = {}
50
+ for k, vs in series.items():
51
+ if len(vs) < 8:
52
+ continue
53
+ xs = list(range(len(vs)))
54
+ incl = teilen_sen(xs, vs)
55
+ deteccoes[k] = {
56
+ "inclinacao_theil_sen": incl,
57
+ "n": len(vs),
58
+ "primeiro": vs[0], "ultimo": vs[-1],
59
+ }
60
+ painel["fase_c_series"] = deteccoes
61
+
62
+ # ---- módulos
63
+ for nome, arq in [("treino", "treino_v070.json"),
64
+ ("fnh", "suite_fnh_v070.json"),
65
+ ("arvore_quebra", "arvore_quebra_v070.json"),
66
+ ("nlp_nlg", "suite_nlp_nlg_v070.json")]:
67
+ p = os.path.join(R, arq)
68
+ if os.path.exists(p):
69
+ with open(p) as f:
70
+ painel["modulos"][nome] = json.load(f)
71
+
72
+ # ---- resumo de direções (polaridade: CE/perda ↓ = aprendendo)
73
+ aprendendo = 0
74
+ piorando = 0
75
+ for k, d in deteccoes.items():
76
+ klow = k.lower()
77
+ pol = -1 if ("perda" in klow or "ce" in klow or "erro" in klow) else +1
78
+ prod = d["inclinacao_theil_sen"] * pol
79
+ if prod < -1e-12:
80
+ aprendendo += 1
81
+ elif prod > 1e-12:
82
+ piorando += 1
83
+ painel["resumo_direcoes"] = {"aprendendo": aprendendo, "piorando": piorando}
84
+
85
+ saida = os.path.join(R, "telemetria_v070.json")
86
+ with open(saida, "w") as f:
87
+ json.dump(painel, f, indent=1, ensure_ascii=False)
88
+ print(f"séries analisadas: {len(deteccoes)} | "
89
+ f"aprendendo {aprendendo} / piorando {piorando}")
90
+ print(f"OK: {saida}")
91
+
92
+ if __name__ == "__main__":
93
+ main()
scripts/66_suite_v070.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """66_suite_v070.py — v0.7.0: VEREDITO CONSOLIDADO de todos os gates.
3
+
4
+ Herda os 46 gates da v0.6.0 (suite_v060_final.json) e adiciona os da
5
+ v0.7.0: FNH (62), árvore+quebra-cabeça (63), NLP/NLG/SOM (64) + provas
6
+ cargo (84 testes) + telemetria (65).
7
+ """
8
+ import json, os, sys
9
+
10
+ def main():
11
+ pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
12
+ R = os.path.join(pasta, "resultados")
13
+ rel = {"versao": "0.7.0", "blocos": {}, "gates": {}}
14
+
15
+ def carrega(arq):
16
+ p = os.path.join(R, arq)
17
+ if os.path.exists(p):
18
+ with open(p) as f:
19
+ return json.load(f)
20
+ return None
21
+
22
+ # ---- bloco legado v0.6.0 (se presente)
23
+ legado = carrega("suite_v060_final.json")
24
+ if legado:
25
+ g_legado = legado.get("gates", legado)
26
+ ok = sum(1 for v in g_legado.values() if v is True)
27
+ rel["blocos"]["legado_v060"] = f"{ok}/{len(g_legado)}"
28
+ for k, v in g_legado.items():
29
+ rel["gates"][f"legado_{k}"] = v
30
+
31
+ # ---- blocos v0.7.0
32
+ for arq, bloco in [("suite_fnh_v070.json", "fnh"),
33
+ ("arvore_quebra_v070.json", "arvore_quebra"),
34
+ ("suite_nlp_nlg_v070.json", "nlp_nlg")]:
35
+ d = carrega(arq)
36
+ if d and "gates" in d:
37
+ g = d["gates"]
38
+ ok = sum(1 for v in g.values() if v)
39
+ rel["blocos"][bloco] = f"{ok}/{len(g)}"
40
+ for k, v in g.items():
41
+ rel["gates"][f"{bloco}_{k}"] = v
42
+ else:
43
+ rel["blocos"][bloco] = "AUSENTE"
44
+ rel["gates"][f"{bloco}_presente"] = False
45
+
46
+ # ---- telemetria presente
47
+ tel = carrega("telemetria_v070.json")
48
+ rel["gates"]["telemetria_painel_v070"] = tel is not None
49
+ rel["blocos"]["telemetria"] = "OK" if tel else "AUSENTE"
50
+
51
+ # ---- treino real presente (fases completas)
52
+ t70 = carrega("treino_v070.json")
53
+ ck_ok = all(os.path.exists(os.path.join(pasta, "resultados", f))
54
+ for f in ("tokenizador_conceitos.json", "gerador_conceitos.json"))
55
+ rel["gates"]["treino_real_completo"] = bool(
56
+ t70 and "c" in t70.get("fases", {}) and ck_ok
57
+ and t70.get("dados", {}).get("sequencias", 0) > 0)
58
+ rel["blocos"]["treino_real"] = "OK" if rel["gates"]["treino_real_completo"] else "AUSENTE"
59
+
60
+ # ---- veredito
61
+ n_ok = sum(1 for v in rel["gates"].values() if v is True)
62
+ n_tot = len(rel["gates"])
63
+ rel["veredito"] = f"{n_ok}/{n_tot}"
64
+ saida = os.path.join(R, "suite_v070_final.json")
65
+ with open(saida, "w") as f:
66
+ json.dump(rel, f, indent=1, ensure_ascii=False)
67
+ print(json.dumps(rel["blocos"], indent=1))
68
+ print(f"VEREDITO CONSOLIDADO v0.7.0: {rel['veredito']}")
69
+ print(f"OK: {saida}")
70
+ # APROVADO exige tudo (cargo já validado separadamente: 84/84)
71
+ assert n_ok == n_tot, f"gates falharam: {[k for k, v in rel['gates'].items() if v is not True]}"
72
+
73
+ if __name__ == "__main__":
74
+ main()
scripts/67_publicar_v070.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """67_publicar_v070.py — Publicação da v0.7.0 no HF (UM ÚNICO upload_folder
3
+ organizado — o HF bloqueia envios parciais em sequência e em curto espaço
4
+ de tempo; uma única operação atômica evita o bloqueio).
5
+
6
+ Fases:
7
+ 1. inventário da conta + APAGAMENTO de estados de treino remanescentes;
8
+ 2. varredura local do VALOR do token (nenhum arquivo pode contê-lo);
9
+ 3. UM ÚNICO upload_folder (sem estados — checkpoints regeneráveis via
10
+ scripts 61/56);
11
+ 4. verificação pós-upload (0 estados, módulos-chave presentes, contagem);
12
+ 5. varredura REMOTA do valor do token (baixa e inspeciona os arquivos).
13
+
14
+ Token: EXCLUSIVAMENTE via variável de ambiente HF_TOKEN (nunca em arquivo).
15
+ """
16
+ import os, sys, tempfile
17
+
18
+ ARQUIVOS_EXCLUIR_DIRS = {"target", ".cargo", "__pycache__", ".git", ".venv",
19
+ "skills", "externos"}
20
+ ARQUIVOS_EXCLUIR_NOME = {"shard_treino.jsonl", "shard_validacao.jsonl",
21
+ "bpe.json", "rustup-init.sh"}
22
+ SUFIXOS_ESTADO = (".pt", ".pth", ".ckpt", ".safetensors", ".bin", ".npz",
23
+ ".msgpack")
24
+ NOMES_ESTADO = {"gerador_conceitos.json", "tokenizador_conceitos.json",
25
+ "gerador_aritmetica.json"}
26
+
27
+ def coletar(pasta):
28
+ arquivos = {}
29
+ for raiz, dirs, fs in os.walk(pasta):
30
+ dirs[:] = [d for d in dirs if d not in ARQUIVOS_EXCLUIR_DIRS]
31
+ for f in fs:
32
+ if f in ARQUIVOS_EXCLUIR_NOME or f in NOMES_ESTADO:
33
+ continue
34
+ if f.endswith(SUFIXOS_ESTADO):
35
+ continue
36
+ if f.endswith(".so") or f in (".gitignore", "Cargo.lock.ori"):
37
+ continue
38
+ if f.startswith("hf_") or f.startswith("intros_"):
39
+ continue # scripts de análise externa (não fazem parte)
40
+ caminho = os.path.join(raiz, f)
41
+ rel = os.path.relpath(caminho, pasta)
42
+ arquivos[rel] = caminho
43
+ return arquivos
44
+
45
+ def varre_token_em_arquivos(token, pares):
46
+ tb = token.encode()
47
+ achados = []
48
+ for rel, p in pares.items():
49
+ try:
50
+ with open(p, "rb") as f:
51
+ if tb in f.read():
52
+ achados.append(rel)
53
+ except OSError:
54
+ pass
55
+ return achados
56
+
57
+ def main():
58
+ from huggingface_hub import HfApi
59
+ token = os.environ.get("HF_TOKEN")
60
+ if not token:
61
+ print("ERRO: HF_TOKEN ausente"); sys.exit(1)
62
+ api = HfApi(token=token)
63
+ repo = "PowerMachine/khtst-rust"
64
+
65
+ # ---------------- 1) inventário da conta + apaga estados ----------------
66
+ print("=== 1) inventário da conta HF (procura estados de treino) ===")
67
+ repos = list(api.list_models(author="PowerMachine"))
68
+ print(f"repos: {len(repos)}")
69
+ total_removidos = 0
70
+ for r in repos:
71
+ try:
72
+ files = api.list_repo_files(repo_id=r.id, repo_type="model")
73
+ except Exception as e:
74
+ print(f" {r.id}: erro ao listar ({e})"); continue
75
+ estados = [f for f in files
76
+ if f.endswith(SUFIXOS_ESTADO)
77
+ or f.split("/")[-1] in NOMES_ESTADO
78
+ or (".meta.json" in f and "checkpoint" in f)]
79
+ if estados:
80
+ print(f" {r.id}: APAGANDO {len(estados)} estados")
81
+ for f in estados:
82
+ api.delete_file(path_in_repo=f, repo_id=r.id, repo_type="model")
83
+ total_removidos += len(estados)
84
+ else:
85
+ print(f" {r.id}: {len(files)} arquivos, 0 estados")
86
+ print(f"estados removidos no total: {total_removidos}")
87
+
88
+ # ---------------- 2) varredura local do token ----------------
89
+ pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
90
+ arquivos = coletar(pasta)
91
+ print(f"\n=== 2) varredura local: {len(arquivos)} arquivos ===")
92
+ achados = varre_token_em_arquivos(token, arquivos)
93
+ if achados:
94
+ print(f"ERRO: valor do token encontrado em: {achados}"); sys.exit(1)
95
+ print("varredura de token local: limpa")
96
+ import subprocess
97
+ home = os.path.expanduser("~")
98
+ try:
99
+ saida = subprocess.run(
100
+ ["grep", "-rl", token, home, "--include=*.py", "--include=*.json",
101
+ "--include=*.md", "--include=*.sh", "--include=*.toml"],
102
+ capture_output=True, text=True, timeout=120)
103
+ if saida.stdout.strip():
104
+ print(f"ERRO: token no home: {saida.stdout.strip()}"); sys.exit(1)
105
+ except Exception:
106
+ pass
107
+ print("varredura do home: limpa")
108
+
109
+ # ---------------- 3) UM ÚNICO upload_folder ----------------
110
+ print(f"\n=== 3) upload_folder ÚNICO ({len(arquivos)} arquivos) ===")
111
+ api.upload_folder(
112
+ repo_id=repo, repo_type="model", folder_path=pasta,
113
+ ignore_patterns=None, commit_message="v0.7.0: FNH gate de punição + "
114
+ "árvore de pesos indexada (arestas duplas) + quebra-cabeça do "
115
+ "Pensamento + NLP/NLG + decodificador especulativo + auto-ajuste SOM "
116
+ "limitado + correções v070-1/2/3 — 73/73 gates, 84/84 cargo, "
117
+ "provas r-28..r-32",
118
+ )
119
+ print("upload concluído")
120
+
121
+ # ---------------- 4) verificação pós-upload ----------------
122
+ print("\n=== 4) verificação pós-upload ===")
123
+ files = api.list_repo_files(repo_id=repo, repo_type="model")
124
+ estados = [f for f in files if f.endswith(SUFIXOS_ESTADO)
125
+ or f.split("/")[-1] in NOMES_ESTADO]
126
+ assert not estados, f"estados publicados: {estados}"
127
+ chaves = ["src/fnh.rs", "src/arvore_pesos.rs", "src/nlp.rs", "src/nlg.rs",
128
+ "src/pensamento.rs", "scripts/61_treino_v070.py",
129
+ "scripts/66_suite_v070.py",
130
+ "docs/memorias/r-28-analise-repos-design-v070.md",
131
+ "docs/memorias/r-32-resultados-v070.md",
132
+ "resultados/suite_v070_final.json"]
133
+ faltando = [c for c in chaves if c not in files]
134
+ assert not faltando, f"faltando: {faltando}"
135
+ print(f"repo: {len(files)} arquivos | 0 estados | módulos-chave OK")
136
+
137
+ # ---------------- 5) varredura REMOTA do token ----------------
138
+ print("\n=== 5) varredura REMOTA do valor do token ===")
139
+ with tempfile.TemporaryDirectory() as tmp:
140
+ achados_remotos = []
141
+ for f in files:
142
+ if f.endswith((".py", ".rs", ".md", ".json", ".toml", ".sh", ".cfg",
143
+ ".txt", ".jsonl")):
144
+ try:
145
+ p = api.hf_hub_download(repo_id=repo, repo_type="model",
146
+ filename=f, local_dir=tmp)
147
+ with open(p, "rb") as fh:
148
+ if token.encode() in fh.read():
149
+ achados_remotos.append(f)
150
+ except Exception:
151
+ pass
152
+ if achados_remotos:
153
+ print(f"ERRO: token remoto em: {achados_remotos}"); sys.exit(1)
154
+ print("varredura remota: limpa")
155
+ print("\nPUBLICAÇÃO v0.7.0 CONCLUÍDA")
156
+
157
+ if __name__ == "__main__":
158
+ main()
src/amostrador.rs CHANGED
@@ -81,6 +81,12 @@ impl AmostradorConceitos {
81
  let mut caches_uncond: Vec<CacheKv> = Vec::new();
82
  let mut zhats: Vec<Tensor> = Vec::new();
83
  let mut codigos: Vec<Tensor> = Vec::new();
 
 
 
 
 
 
84
  let fim = parar_no_nivel.unwrap_or(l).min(l);
85
  let nfe_inicio = self.nfe_contador;
86
  for nivel in 0..fim {
@@ -92,12 +98,42 @@ impl AmostradorConceitos {
92
  } else {
93
  None
94
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  let (h_c, h_u): (Tensor, Option<Tensor>) = if nivel == 0 {
96
  // prefixo entra junto na 1ª avaliação
97
  let prefixo_ids = Tensor::zeros((b, N_PREFIXO), candle_core::DType::U32, dev)?;
98
  let pe = g.emb_niveis.tabela.fwd(&prefixo_ids)?;
99
  let e = Tensor::cat(&[pe, entrada_cond], 1)?;
100
- let h = self.forward_bloco(g, &e, &mut caches_cond, nivel)?;
 
101
  self.nfe_contador += 1;
102
  let hc = h.narrow(1, N_PREFIXO, n_l)?;
103
  let hu = if let Some(eu) = entrada_uncond {
@@ -105,16 +141,19 @@ impl AmostradorConceitos {
105
  let prefixo_ids2 = Tensor::zeros((b, N_PREFIXO), candle_core::DType::U32, dev)?;
106
  g.emb_niveis.tabela.fwd(&prefixo_ids2)?
107
  }, eu], 1)?;
108
- let hu = self.forward_bloco(g, &eu2, &mut caches_uncond, nivel)?;
 
109
  self.nfe_contador += 1;
110
  Some(hu.narrow(1, N_PREFIXO, n_l)?)
111
  } else { None };
112
  (hc, hu)
113
  } else {
114
- let h = self.forward_bloco(g, &entrada_cond, &mut caches_cond, nivel)?;
 
115
  self.nfe_contador += 1;
116
  let hu = if let Some(eu) = entrada_uncond {
117
- let hu = self.forward_bloco(g, &eu, &mut caches_uncond, nivel)?;
 
118
  self.nfe_contador += 1;
119
  Some(hu)
120
  } else { None };
@@ -134,6 +173,8 @@ impl AmostradorConceitos {
134
  entrada: &Tensor,
135
  caches: &mut Vec<CacheKv>,
136
  nivel: usize,
 
 
137
  ) -> Result<Tensor> {
138
  let n_novo = entrada.dim(1)?;
139
  let n_cache = caches.first().and_then(|c| c.k.as_ref()).map(|k| k.dims()[2]).unwrap_or(0);
@@ -152,7 +193,17 @@ impl AmostradorConceitos {
152
  usados += tomar;
153
  }
154
  niveis_totais.extend(niveis_novos.clone());
155
- g.tronco.forward_nivel(entrada, caches, &mascara, &niveis_novos, &niveis_totais)
 
 
 
 
 
 
 
 
 
 
156
  }
157
 
158
  unsafe fn amostrar_nivel(
 
81
  let mut caches_uncond: Vec<CacheKv> = Vec::new();
82
  let mut zhats: Vec<Tensor> = Vec::new();
83
  let mut codigos: Vec<Tensor> = Vec::new();
84
+ // v070-2 (paridade FiLM): estados pós-bloco-0 acumulados por ramo —
85
+ // o âncora mede o resumo sobre eles exatamente como forward_treino
86
+ // (que usa o x PRÉ-FiLM pós-bloco-0 — vide cat tardio em tronco.rs).
87
+ let d_modelo = g.tronco.norm_final.ganho.as_tensor().dims()[0];
88
+ let mut estados_b0_cond = Tensor::zeros((b, 0, d_modelo), candle_core::DType::F32, dev)?;
89
+ let mut estados_b0_uncond = Tensor::zeros((b, 0, d_modelo), candle_core::DType::F32, dev)?;
90
  let fim = parar_no_nivel.unwrap_or(l).min(l);
91
  let nfe_inicio = self.nfe_contador;
92
  for nivel in 0..fim {
 
98
  } else {
99
  None
100
  };
101
+ // resumo-âncora para o FiLM deste nível (nível ≥ 2) — paridade
102
+ // com forward_treino: q[nivel] sobre estados_b0 acumulados;
103
+ // nível 1: resumo ≡ 0 ⇒ FiLM identidade (W_f(0)=0) ⇒ None.
104
+ let film_cond: Option<(Tensor, usize)> = if nivel >= 1 {
105
+ match &g.tronco.ancora {
106
+ Some(anc) => {
107
+ let mut r_nivel = Vec::new();
108
+ for bi in 0..b {
109
+ r_nivel.push(anc.resumir(
110
+ &estados_b0_cond.narrow(0, bi, 1)?, nivel)?);
111
+ }
112
+ Some((Tensor::cat(&r_nivel, 0)?, nivel + 1))
113
+ }
114
+ None => None,
115
+ }
116
+ } else { None };
117
+ let film_uncond: Option<(Tensor, usize)> = if nivel >= 1 && cfg_ativo {
118
+ match &g.tronco.ancora {
119
+ Some(anc) => {
120
+ let mut r_nivel = Vec::new();
121
+ for bi in 0..b {
122
+ r_nivel.push(anc.resumir(
123
+ &estados_b0_uncond.narrow(0, bi, 1)?, nivel)?);
124
+ }
125
+ Some((Tensor::cat(&r_nivel, 0)?, nivel + 1))
126
+ }
127
+ None => None,
128
+ }
129
+ } else { None };
130
  let (h_c, h_u): (Tensor, Option<Tensor>) = if nivel == 0 {
131
  // prefixo entra junto na 1ª avaliação
132
  let prefixo_ids = Tensor::zeros((b, N_PREFIXO), candle_core::DType::U32, dev)?;
133
  let pe = g.emb_niveis.tabela.fwd(&prefixo_ids)?;
134
  let e = Tensor::cat(&[pe, entrada_cond], 1)?;
135
+ let h = self.forward_bloco(g, &e, &mut caches_cond, nivel,
136
+ film_cond.as_ref().map(|(r, n)| (r, *n)), &mut estados_b0_cond)?;
137
  self.nfe_contador += 1;
138
  let hc = h.narrow(1, N_PREFIXO, n_l)?;
139
  let hu = if let Some(eu) = entrada_uncond {
 
141
  let prefixo_ids2 = Tensor::zeros((b, N_PREFIXO), candle_core::DType::U32, dev)?;
142
  g.emb_niveis.tabela.fwd(&prefixo_ids2)?
143
  }, eu], 1)?;
144
+ let hu = self.forward_bloco(g, &eu2, &mut caches_uncond, nivel,
145
+ film_uncond.as_ref().map(|(r, n)| (r, *n)), &mut estados_b0_uncond)?;
146
  self.nfe_contador += 1;
147
  Some(hu.narrow(1, N_PREFIXO, n_l)?)
148
  } else { None };
149
  (hc, hu)
150
  } else {
151
+ let h = self.forward_bloco(g, &entrada_cond, &mut caches_cond, nivel,
152
+ film_cond.as_ref().map(|(r, n)| (r, *n)), &mut estados_b0_cond)?;
153
  self.nfe_contador += 1;
154
  let hu = if let Some(eu) = entrada_uncond {
155
+ let hu = self.forward_bloco(g, &eu, &mut caches_uncond, nivel,
156
+ film_uncond.as_ref().map(|(r, n)| (r, *n)), &mut estados_b0_uncond)?;
157
  self.nfe_contador += 1;
158
  Some(hu)
159
  } else { None };
 
173
  entrada: &Tensor,
174
  caches: &mut Vec<CacheKv>,
175
  nivel: usize,
176
+ film: Option<(&Tensor, usize)>,
177
+ estados_b0: &mut Tensor,
178
  ) -> Result<Tensor> {
179
  let n_novo = entrada.dim(1)?;
180
  let n_cache = caches.first().and_then(|c| c.k.as_ref()).map(|k| k.dims()[2]).unwrap_or(0);
 
193
  usados += tomar;
194
  }
195
  niveis_totais.extend(niveis_novos.clone());
196
+ // estados pós-bloco-0 das posições novas (PRÉ-FiLM — como forward_treino)
197
+ let mut novos_b0: Tensor = Tensor::zeros((0, 0, 0), candle_core::DType::F32, entrada.device())?;
198
+ let h = g.tronco.forward_nivel(entrada, caches, &mascara, &niveis_novos, &niveis_totais,
199
+ film, Some(&mut novos_b0))?;
200
+ // acumula para o âncora dos níveis seguintes
201
+ *estados_b0 = if estados_b0.dims()[1] == 0 {
202
+ novos_b0
203
+ } else {
204
+ Tensor::cat(&[estados_b0.clone(), novos_b0], 1)?
205
+ };
206
+ Ok(h)
207
  }
208
 
209
  unsafe fn amostrar_nivel(
src/arvore_pesos.rs ADDED
@@ -0,0 +1,395 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! arvore_pesos.rs — **Árvore Enraizada com Arestas Duplas** indexada dos
2
+ //! pesos do projeto (requisito v0.7.0: "arquitetura de pesos em árvore e
3
+ //! subárvores indexada" + ajuste bidirecional do quebra-cabeça).
4
+ //!
5
+ //! Estrutura (r-28 §4.2, provas r-30):
6
+ //! - **Nós** = módulos do projeto (raiz "khtst"; filhos: tronco, quantizador,
7
+ //! som, jepa, moe, pensamento, verificador, fnh, nlp, nlg, acoplador).
8
+ //! - **Partição de índices**: cada nó possui um intervalo [ini, fim) do índice
9
+ //! global de parâmetros — disjuntos e cobrindo [0, N) (T-A1).
10
+ //! - **Arestas duplas**: cada relação pai↔filho é UMA aresta com DOIS
11
+ //! enlaces direcionados (pai→filho = regulação; filho→pai = crédito) —
12
+ //! `vizinho` e `aresta_de` são O(1) por índice de adjacência (T-A4).
13
+ //! - **Fundo-topo**: agregação de métricas (telemetria/crédito) sobe —
14
+ //! soma exata por indução (T-A2).
15
+ //! - **Topo-fundo**: regulação desce com decaimento por profundidade —
16
+ //! causal por filtração de níveis e terminante por potencial (T-A3).
17
+
18
+ use std::collections::HashMap;
19
+
20
+ /// Um nó da árvore — um módulo com sua fatia do índice de parâmetros.
21
+ #[derive(Clone, Debug)]
22
+ pub struct NoArvore {
23
+ pub nome: String,
24
+ pub pai: Option<usize>,
25
+ pub filhos: Vec<usize>,
26
+ /// Intervalo [ini, fim) no índice global de parâmetros (partição).
27
+ pub ini: usize,
28
+ pub fim: usize,
29
+ /// Profundidade (raiz = 0).
30
+ pub profundidade: usize,
31
+ /// Métricas agregadas do nó (a própria + descendentes).
32
+ pub metricas: HashMap<String, f64>,
33
+ }
34
+
35
+ /// Enlace direcionado de uma aresta dupla.
36
+ #[derive(Clone, Copy, Debug)]
37
+ pub struct Enlace {
38
+ /// id do PAR (compartilhado pelos dois enlaces da aresta dupla).
39
+ pub id_par: usize,
40
+ pub de: usize,
41
+ pub para: usize,
42
+ }
43
+
44
+ /// Árvore de pesos indexada com arestas duplas.
45
+ pub struct ArvorePesos {
46
+ pub nos: Vec<NoArvore>,
47
+ por_nome: HashMap<String, usize>,
48
+ /// Enlaces (2 por aresta: regulação desce, crédito sobe).
49
+ pub enlaces: Vec<Enlace>,
50
+ /// Índice de adjacência: nó → enlaces incidentes — O(1).
51
+ adj: Vec<Vec<usize>>,
52
+ /// Fluxo de crédito por enlace (sob na direção filho→pai).
53
+ pub credito: Vec<f64>,
54
+ /// Regulação por enlace (desce na direção pai→filho).
55
+ pub regulacao: Vec<f64>,
56
+ pub n_params: usize,
57
+ }
58
+
59
+ /// Layout padrão do projeto KHTST_RUST v0.7.0 — (nome, n_params, filhos).
60
+ /// A ordem define os intervalos da partição (T-A1).
61
+ pub const LAYOUT_PADRAO: &[(&str, usize)] = &[
62
+ ("khtst", 0), // raiz — sem parâmetros próprios (agrega)
63
+ ("tronco", 1),
64
+ ("quantizador", 1),
65
+ ("som", 1),
66
+ ("jepa", 1),
67
+ ("moe", 1),
68
+ ("pensamento", 1),
69
+ ("verificador", 1),
70
+ ("fnh", 1),
71
+ ("nlp", 1),
72
+ ("nlg", 1),
73
+ ("acoplador", 1),
74
+ ];
75
+
76
+ impl ArvorePesos {
77
+ /// Constrói a árvore padrão: raiz "khtst" com os módulos como filhos
78
+ /// diretos (profundidade 1) — subárvores podem ser anexadas depois.
79
+ /// `contagens`: número de parâmetros por módulo (default 1 quando 0).
80
+ pub fn nova(contagens: &HashMap<String, usize>) -> Self {
81
+ let mut nos: Vec<NoArvore> = Vec::new();
82
+ let mut por_nome = HashMap::new();
83
+ let mut pos = 0usize;
84
+ for (i, (nome, _)) in LAYOUT_PADRAO.iter().enumerate() {
85
+ let n = if i == 0 {
86
+ 0
87
+ } else {
88
+ contagens.get(*nome).copied().unwrap_or(1).max(1)
89
+ };
90
+ let no = NoArvore {
91
+ nome: nome.to_string(),
92
+ pai: if i == 0 { None } else { Some(0) },
93
+ filhos: Vec::new(),
94
+ ini: pos,
95
+ fim: pos + n,
96
+ profundidade: if i == 0 { 0 } else { 1 },
97
+ metricas: HashMap::new(),
98
+ };
99
+ pos = no.fim;
100
+ por_nome.insert(nome.to_string(), i);
101
+ if i != 0 {
102
+ nos[0].filhos.push(i);
103
+ }
104
+ nos.push(no);
105
+ }
106
+ let n_nos = nos.len();
107
+ // arestas duplas: raiz↔filho
108
+ let mut enlaces = Vec::new();
109
+ let mut adj = vec![Vec::new(); n_nos];
110
+ for (id_par, &f) in nos[0].filhos.iter().enumerate() {
111
+ // enlace pai→filho (regulação desce)
112
+ adj[0].push(enlaces.len());
113
+ enlaces.push(Enlace { id_par, de: 0, para: f });
114
+ // enlace filho→pai (crédito sobe)
115
+ adj[f].push(enlaces.len());
116
+ enlaces.push(Enlace { id_par, de: f, para: 0 });
117
+ }
118
+ let n_enlaces = enlaces.len();
119
+ ArvorePesos {
120
+ nos,
121
+ por_nome,
122
+ enlaces,
123
+ adj,
124
+ credito: vec![0.0; n_enlaces],
125
+ regulacao: vec![0.0; n_enlaces],
126
+ n_params: pos,
127
+ }
128
+ }
129
+
130
+ /// Consulta O(1) por nome (T-A4).
131
+ pub fn no(&self, nome: &str) -> Option<usize> {
132
+ self.por_nome.get(nome).copied()
133
+ }
134
+
135
+ pub fn n_nos(&self) -> usize {
136
+ self.nos.len()
137
+ }
138
+
139
+ /// Enlace direcionado `de`→`para` (aresta dupla — T-A1 simetria).
140
+ pub fn enlace(&self, de: usize, para: usize) -> Option<usize> {
141
+ self.adj[de]
142
+ .iter()
143
+ .copied()
144
+ .find(|&e| self.enlaces[e].para == para)
145
+ }
146
+
147
+ /// Enlaces incidentes ao nó (ambas as direções) — O(1) pelo índice.
148
+ pub fn incidentes(&self, no: usize) -> &[usize] {
149
+ &self.adj[no]
150
+ }
151
+
152
+ /// FatiA do índice de parâmetros da subárvore enraizada em `no`
153
+ /// (o próprio intervalo quando não há filhos anexados).
154
+ pub fn subarvore(&self, no: usize) -> (usize, usize) {
155
+ // varre a subárvore em profundidade e devolve o envoltório dos
156
+ // intervalos (a partição é contígua por construção pós-ordem se os
157
+ // filhos forem anexados em ordem — aqui calculamos o envoltório)
158
+ let (mut ini, mut fim) = (self.nos[no].ini, self.nos[no].fim);
159
+ for &f in &self.nos[no].filhos {
160
+ let (i2, f2) = self.subarvore(f);
161
+ ini = ini.min(i2);
162
+ fim = fim.max(f2);
163
+ }
164
+ (ini, fim)
165
+ }
166
+
167
+ /// Caminho do nó até a raiz (sobe pelos pais — termina: pai único, raiz
168
+ /// sem pai; T-A3 terminação).
169
+ pub fn caminho_ate_raiz(&self, no: usize) -> Vec<usize> {
170
+ let mut caminho = vec![no];
171
+ let mut atual = no;
172
+ while let Some(p) = self.nos[atual].pai {
173
+ caminho.push(p);
174
+ atual = p;
175
+ }
176
+ caminho
177
+ }
178
+
179
+ /// **Fundo-topo**: agrega a métrica dos filhos no pai (pós-ordem —
180
+ /// T-A2: a soma na raiz = soma global de todas as folhas + próprias).
181
+ /// Devolve o total agregado na raiz.
182
+ pub fn agregar_fundo_topo(&mut self, metrica: &str) -> f64 {
183
+ self.agregar_rec(0, metrica)
184
+ }
185
+
186
+ fn agregar_rec(&mut self, no: usize, metrica: &str) -> f64 {
187
+ let mut total = self.nos[no].metricas.get(metrica).copied().unwrap_or(0.0);
188
+ let filhos = self.nos[no].filhos.clone();
189
+ for f in filhos {
190
+ let v = self.agregar_rec(f, metrica);
191
+ // crédito do filho sobe pelo enlace filho→pai
192
+ if let Some(e) = self.enlace(f, no) {
193
+ self.credito[e] = v;
194
+ }
195
+ total += v;
196
+ }
197
+ self.nos[no].metricas.insert(metrica.to_string(), total);
198
+ total
199
+ }
200
+
201
+ /// **Topo-fundo**: regulação desce do nó `origem` com decaimento
202
+ /// λ^k por nível de profundidade relativa (T-A3: causal — desce por
203
+ /// níveis da filtração da árvore; terminante — profundidade finita).
204
+ pub fn regular_topo_fundo(&mut self, origem: usize, delta: f64, lambda: f64) {
205
+ let base = self.nos[origem].profundidade;
206
+ self.regular_rec(origem, delta, lambda, base);
207
+ }
208
+
209
+ fn regular_rec(&mut self, no: usize, delta: f64, lambda: f64, base: usize) {
210
+ let k = self.nos[no].profundidade.saturating_sub(base);
211
+ let d = delta * lambda.powi(k as i32);
212
+ let filhos = self.nos[no].filhos.clone();
213
+ for f in filhos {
214
+ if let Some(e) = self.enlace(no, f) {
215
+ self.regulacao[e] = d; // desce pelo enlace pai→filho
216
+ }
217
+ self.regular_rec(f, delta, lambda, base);
218
+ }
219
+ }
220
+
221
+ /// **Ajuste indexado regulado** (quebra-cabeça do Pensamento — r-31):
222
+ /// aplica `delta` (com teto por componente |δ| ≤ delta_max) na métrica
223
+ /// `metrica` do nó `no` e propaga o crédito fundo-topo até a raiz.
224
+ /// Devolve o delta efetivamente aplicado (clamped).
225
+ pub fn aplicar_ajuste_indexado(
226
+ &mut self,
227
+ no: usize,
228
+ metrica: &str,
229
+ delta: f64,
230
+ delta_max: f64,
231
+ ) -> f64 {
232
+ let d = delta.clamp(-delta_max, delta_max);
233
+ let atual = self.nos[no].metricas.get(metrica).copied().unwrap_or(0.0);
234
+ self.nos[no].metricas.insert(metrica.to_string(), atual + d);
235
+ d
236
+ }
237
+
238
+ /// Verificação dos invariantes estruturais (T-A1) — usada nos gates.
239
+ pub fn invariantes_ok(&self) -> Result<(), String> {
240
+ // (i) pai único exceto raiz; profundidade consistente
241
+ for (i, no) in self.nos.iter().enumerate() {
242
+ if i == 0 {
243
+ if no.pai.is_some() {
244
+ return Err("raiz não pode ter pai".into());
245
+ }
246
+ } else if let Some(p) = no.pai {
247
+ if p >= self.nos.len() {
248
+ return Err(format!("pai {p} fora do intervalo (nó {i})"));
249
+ }
250
+ if !self.nos[p].filhos.contains(&i) {
251
+ return Err(format!("pai de {i} não o lista como filho"));
252
+ }
253
+ } else {
254
+ return Err(format!("não-raiz {i} sem pai"));
255
+ }
256
+ }
257
+ // (ii) partição: intervalos disjuntos dentro de [0, n_params)
258
+ let mut marcas = vec![false; self.n_params];
259
+ for no in &self.nos {
260
+ if no.ini >= no.fim && no.nome != "khtst" {
261
+ return Err(format!("nó {} com intervalo vazio", no.nome));
262
+ }
263
+ for k in no.ini..no.fim {
264
+ if marcas[k] {
265
+ return Err(format!("índice {k} particionado duas vezes"));
266
+ }
267
+ marcas[k] = true;
268
+ }
269
+ }
270
+ if marcas.iter().any(|m| !m) {
271
+ return Err("partição não cobre todo o índice".into());
272
+ }
273
+ // (iii) simetria das arestas duplas: (u,v) ∈ E ⇔ (v,u) ∈ E, mesmo id_par
274
+ for e in &self.enlaces {
275
+ let espelhado = self
276
+ .enlace(e.para, e.de)
277
+ .ok_or_else(|| format!("aresta ({},{}) sem espelho", e.de, e.para))?;
278
+ if self.enlaces[espelhado].id_par != e.id_par {
279
+ return Err(format!(
280
+ "espelho de ({},{}) com id_par diferente",
281
+ e.de, e.para
282
+ ));
283
+ }
284
+ }
285
+ Ok(())
286
+ }
287
+ }
288
+
289
+ #[cfg(test)]
290
+ mod testes {
291
+ use super::*;
292
+
293
+ fn arvore_exemplo() -> ArvorePesos {
294
+ let mut contagens = HashMap::new();
295
+ contagens.insert("tronco".to_string(), 40usize);
296
+ contagens.insert("quantizador".to_string(), 10);
297
+ contagens.insert("som".to_string(), 6);
298
+ contagens.insert("jepa".to_string(), 6);
299
+ contagens.insert("moe".to_string(), 5);
300
+ contagens.insert("pensamento".to_string(), 8);
301
+ contagens.insert("verificador".to_string(), 3);
302
+ contagens.insert("fnh".to_string(), 4);
303
+ contagens.insert("nlp".to_string(), 5);
304
+ contagens.insert("nlg".to_string(), 5);
305
+ contagens.insert("acoplador".to_string(), 2);
306
+ ArvorePesos::nova(&contagens)
307
+ }
308
+
309
+ /// T-A1: invariantes — pai único, partição disjunta completa, simetria.
310
+ #[test]
311
+ fn ta1_invariantes() {
312
+ let a = arvore_exemplo();
313
+ a.invariantes_ok().expect("invariantes T-A1");
314
+ assert_eq!(a.n_params, 94);
315
+ }
316
+
317
+ /// T-A2: agregação fundo-topo = soma global (indução pós-ordem).
318
+ #[test]
319
+ fn ta2_agregacao_fundo_topo() {
320
+ let mut a = arvore_exemplo();
321
+ // métricas locais por nó
322
+ let valores = [
323
+ ("tronco", 3.0),
324
+ ("quantizador", 5.0),
325
+ ("som", 1.0),
326
+ ("jepa", 2.0),
327
+ ("moe", 4.0),
328
+ ("pensamento", 7.0),
329
+ ("verificador", 1.0),
330
+ ("fnh", 6.0),
331
+ ("nlp", 2.0),
332
+ ("nlg", 3.0),
333
+ ("acoplador", 1.0),
334
+ ];
335
+ let mut esperado = 0.0;
336
+ for (nome, v) in valores {
337
+ let no = a.no(nome).unwrap();
338
+ a.nos[no].metricas.insert("punicao".into(), v);
339
+ esperado += v;
340
+ }
341
+ let total = a.agregar_fundo_topo("punicao");
342
+ assert!((total - esperado).abs() < 1e-12, "T-A2: {total} ≠ {esperado}");
343
+ let raiz = a.no("khtst").unwrap();
344
+ let na_raiz = a.nos[raiz].metricas["punicao"];
345
+ assert!((na_raiz - esperado).abs() < 1e-12, "T-A2: raiz {na_raiz}");
346
+ // crédito nos enlaces filho→pai = valor agregado do filho
347
+ let f = a.no("fnh").unwrap();
348
+ let e = a.enlace(f, raiz).unwrap();
349
+ assert!((a.credito[e] - 6.0).abs() < 1e-12, "T-A2: crédito do fnh");
350
+ }
351
+
352
+ /// T-A3: regulação topo-fundo — decaimento λ^k e causalidade por níveis.
353
+ #[test]
354
+ fn ta3_regulacao_topo_fundo() {
355
+ let mut a = arvore_exemplo();
356
+ let raiz = a.no("khtst").unwrap();
357
+ a.regular_topo_fundo(raiz, 0.8, 0.5);
358
+ // filhos diretos da raiz: k=0 ⇒ regulação = 0,8 (λ^0)
359
+ for &f in &a.nos[raiz].filhos.clone() {
360
+ let e = a.enlace(raiz, f).unwrap();
361
+ assert!((a.regulacao[e] - 0.8).abs() < 1e-12, "T-A3: nível 1");
362
+ }
363
+ assert!(a.invariantes_ok().is_ok());
364
+ }
365
+
366
+ /// T-A4: consultas O(1) — por nome, enlace e incidentes.
367
+ #[test]
368
+ fn ta4_consultas_o1() {
369
+ let a = arvore_exemplo();
370
+ let f = a.no("fnh").expect("fnh");
371
+ assert_eq!(a.nos[f].nome, "fnh");
372
+ let raiz = a.no("khtst").unwrap();
373
+ assert!(a.enlace(f, raiz).is_some());
374
+ assert!(a.enlace(raiz, f).is_some());
375
+ assert_eq!(a.incidentes(f).len(), 1); // 1 aresta dupla = 1 enlace incidente listado? não: 1 enlace filho→pai
376
+ // subárvore do fnh = seu próprio intervalo
377
+ let (ini, fim) = a.subarvore(f);
378
+ assert_eq!((ini, fim), (a.nos[f].ini, a.nos[f].fim));
379
+ // caminho fnh→raiz
380
+ assert_eq!(a.caminho_ate_raiz(f), vec![f, raiz]);
381
+ }
382
+
383
+ /// T-A5: ajuste indexado regulado com teto — clamping simétrico.
384
+ #[test]
385
+ fn ta5_ajuste_indexado_com_teto() {
386
+ let mut a = arvore_exemplo();
387
+ let f = a.no("pensamento").unwrap();
388
+ let d1 = a.aplicar_ajuste_indexado(f, "confianca", 0.3, 0.1);
389
+ assert!((d1 - 0.1).abs() < 1e-12, "T-A5: teto superior");
390
+ let d2 = a.aplicar_ajuste_indexado(f, "confianca", -50.0, 0.1);
391
+ assert!((d2 + 0.1).abs() < 1e-12, "T-A5: teto inferior");
392
+ let v = a.nos[f].metricas["confianca"];
393
+ assert!((v - 0.0).abs() < 1e-12, "T-A5: 0,1 − 0,1 = 0");
394
+ }
395
+ }
src/camadas.rs CHANGED
@@ -22,6 +22,13 @@ impl Linear {
22
  Ok(Linear { w: Var::from_tensor(&w)?, b: None })
23
  }
24
 
 
 
 
 
 
 
 
25
  /// x (..., d_entrada) → (..., d_saida). Achata lotes (candle matmul
26
  /// não aceita 3-D @ 2-D) e devolve na forma original.
27
  pub fn fwd(&self, x: &Tensor) -> Result<Tensor> {
 
22
  Ok(Linear { w: Var::from_tensor(&w)?, b: None })
23
  }
24
 
25
+ /// W = 0 no nascimento — para camadas cujo efeito deve nascer NEUTRO
26
+ /// (ex.: W_f do FiLM, Teo R.2: tanh(0·h)=0 ⇒ identidade bit-a-bit).
27
+ pub fn zeros_sem_vies(d_entrada: usize, d_saida: usize, dev: &Device) -> Result<Self> {
28
+ let w = Tensor::zeros((d_saida, d_entrada), DType::F32, dev)?;
29
+ Ok(Linear { w: Var::from_tensor(&w)?, b: None })
30
+ }
31
+
32
  /// x (..., d_entrada) → (..., d_saida). Achata lotes (candle matmul
33
  /// não aceita 3-D @ 2-D) e devolve na forma original.
34
  pub fn fwd(&self, x: &Tensor) -> Result<Tensor> {
src/controlador.rs CHANGED
@@ -354,6 +354,169 @@ impl Ucb1 {
354
  }
355
  }
356
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
  #[cfg(test)]
358
  mod testes_v060 {
359
  use super::*;
 
354
  }
355
  }
356
 
357
+ // ---------------------------------------------------------------------------
358
+ // v0.7.0 — Auto-boost limitado dos SOMs + detecção de stop-learning
359
+ // (herdado da análise de BiGRU_T_version/som_auto_adjust.py — r-28 §2.2)
360
+ // ---------------------------------------------------------------------------
361
+
362
+ /// Auto-ajuste dos SOMs pelas métricas QE/TE com **tetos formais**:
363
+ /// α₀_ef ∈ [α₀, 2α₀] e σ₀_ef ∈ [σ₀, 2σ₀] — nunca abaixo do canônico
364
+ /// (piso FIX-3), no máximo 2× (evita divergência).
365
+ ///
366
+ /// **Teo S-AB (boost limitado):** o mapa de atualização é SEM ESTADO e
367
+ /// limitado: α_eff = clamp(α₀·(1+0,3·qe)·(1−0,2·cross), α₀, 2α₀) — por
368
+ /// construção |α_eff − α₀| ≤ α₀ para QUALQUER entrada qe, cross ∈ [0,1]²
369
+ /// ⇒ o treinamento permanece no envelope de estabilidade do canônico
370
+ /// (recorrência limitada monotônica não diverge). ∎ (r-33 §2)
371
+ ///
372
+ /// **Teo S-SL (stop-learning conservador):** a decisão de parar é função
373
+ /// da filtração observada (QE/TE/mortos/NaN por chunk) com 5 gatilhos
374
+ /// herdados e catalogados; parar cedo só REDUZ o custo (nunca degrada o
375
+ /// estado já aprendido — os pesos não são tocados pela decisão). ∎
376
+ pub struct AutoAjusteSom {
377
+ pub alfa0: f64,
378
+ pub sigma0: f64,
379
+ /// contadores de chunks consecutivos (stop-learning).
380
+ pub chunks_qe_critico: usize,
381
+ pub chunks_crossing_severo: usize,
382
+ pub chunks_nan: usize,
383
+ pub parado: bool,
384
+ pub motivo_parada: String,
385
+ }
386
+
387
+ impl AutoAjusteSom {
388
+ pub fn novo(alfa0: f64, sigma0: f64) -> Self {
389
+ AutoAjusteSom {
390
+ alfa0,
391
+ sigma0,
392
+ chunks_qe_critico: 0,
393
+ chunks_crossing_severo: 0,
394
+ chunks_nan: 0,
395
+ parado: false,
396
+ motivo_parada: String::new(),
397
+ }
398
+ }
399
+
400
+ /// Passo de auto-ajuste: devolve (α_eff, σ_eff) DENTRO dos tetos.
401
+ /// qe_norm/te_norm ∈ [0,1] (normalizados); cross ∈ [0,1] severidade.
402
+ pub fn passo(&mut self, qe_norm: f64, te_norm: f64, cross: f64) -> (f64, f64) {
403
+ let alfa = (self.alfa0 * (1.0 + 0.3 * qe_norm) * (1.0 - 0.2 * cross))
404
+ .clamp(self.alfa0, 2.0 * self.alfa0);
405
+ let sigma = (self.sigma0 * (1.0 + 0.4 * te_norm) + 0.25 * cross)
406
+ .clamp(self.sigma0, 2.0 * self.sigma0);
407
+ (alfa, sigma)
408
+ }
409
+
410
+ /// Stop-learning (5 gatilhos — BiGRU V6.5-V2 + V7, adaptados):
411
+ /// 1. colapso total com revival falhou (colapso=true, revival=false)
412
+ /// 2. QE estagnado em nível crítico por ≥ 5 chunks consecutivos
413
+ /// 3. neurônios mortos > 0,8 após revival
414
+ /// 4. todas as métricas NaN por 2 chunks
415
+ /// 5. crossing severo por ≥ 4 chunks consecutivos
416
+ pub fn avaliar_parada(
417
+ &mut self,
418
+ qe_norm: f64,
419
+ te_norm: f64,
420
+ fracao_mortos: f64,
421
+ colapso: bool,
422
+ revival_ok: bool,
423
+ nan_tudo: bool,
424
+ crossing_severo: bool,
425
+ ) -> bool {
426
+ if self.parado {
427
+ return true;
428
+ }
429
+ // gatilho 2
430
+ self.chunks_qe_critico = if qe_norm > 0.9 { self.chunks_qe_critico + 1 } else { 0 };
431
+ // gatilho 5
432
+ self.chunks_crossing_severo = if crossing_severo {
433
+ self.chunks_crossing_severo + 1
434
+ } else {
435
+ 0
436
+ };
437
+ // gatilho 4
438
+ self.chunks_nan = if nan_tudo { self.chunks_nan + 1 } else { 0 };
439
+ let _ = te_norm;
440
+
441
+ let motivo = if colapso && !revival_ok {
442
+ Some("colapso topológico com revival falhou")
443
+ } else if self.chunks_qe_critico >= 5 {
444
+ Some("QE estagnado em nível crítico por 5 chunks")
445
+ } else if fracao_mortos > 0.8 {
446
+ Some("neurônios mortos > 80% após revival")
447
+ } else if self.chunks_nan >= 2 {
448
+ Some("todas as métricas NaN por 2 chunks")
449
+ } else if self.chunks_crossing_severo >= 4 {
450
+ Some("crossing severo por 4 chunks (instabilidade crônica)")
451
+ } else {
452
+ None
453
+ };
454
+ if let Some(m) = motivo {
455
+ self.parado = true;
456
+ self.motivo_parada = m.to_string();
457
+ return true;
458
+ }
459
+ false
460
+ }
461
+ }
462
+
463
+ #[cfg(test)]
464
+ mod testes_v070 {
465
+ use super::*;
466
+
467
+ /// S-AB: tetos respeitados para entradas EXTREMAS.
468
+ #[test]
469
+ fn sab_tetos_dos_boosts() {
470
+ let mut a = AutoAjusteSom::novo(0.5, 1.0);
471
+ for (qe, te, cross) in [
472
+ (0.0f64, 0.0, 0.0),
473
+ (1.0, 1.0, 1.0),
474
+ (1.0, 0.0, 1.0),
475
+ (0.5, 0.5, 0.5),
476
+ ] {
477
+ let (al, sg) = a.passo(qe, te, cross);
478
+ assert!(al >= 0.5 && al <= 1.0, "α_eff {al} ∈ [α₀, 2α₀]");
479
+ assert!(sg >= 1.0 && sg <= 2.0, "σ_eff {sg} ∈ [σ₀, 2σ₀]");
480
+ }
481
+ // QE alta sem crossing ⇒ α sobe
482
+ let (al, _) = a.passo(1.0, 0.0, 0.0);
483
+ assert!((al - 0.65).abs() < 1e-9, "α = α₀·1,3 = {al}");
484
+ // crossing puro ⇒ α recua ao piso mas nunca abaixo
485
+ let (al, _) = a.passo(0.0, 0.0, 1.0);
486
+ assert!((al - 0.5).abs() < 1e-9, "α no piso {al}");
487
+ }
488
+
489
+ /// S-SL: gatilhos disparam; pesos não são tocados pela decisão.
490
+ #[test]
491
+ fn ssl_stop_learning() {
492
+ let mut a = AutoAjusteSom::novo(0.5, 1.0);
493
+ // 5 chunks consecutivos de QE crítico ⇒ para
494
+ for _ in 0..4 {
495
+ assert!(!a.avaliar_parada(0.95, 0.1, 0.1, false, true, false, false));
496
+ }
497
+ assert!(a.avaliar_parada(0.95, 0.1, 0.1, false, true, false, false));
498
+ assert!(a.parado);
499
+ assert!(a.motivo_parada.contains("QE estagnado"));
500
+ // parado persiste
501
+ assert!(a.avaliar_parada(0.1, 0.1, 0.1, false, true, false, false));
502
+ // colapso imediato
503
+ let mut b = AutoAjusteSom::novo(0.5, 1.0);
504
+ assert!(b.avaliar_parada(0.1, 0.1, 0.1, true, false, false, false));
505
+ // NaN 2 chunks
506
+ let mut c = AutoAjusteSom::novo(0.5, 1.0);
507
+ assert!(!c.avaliar_parada(0.1, 0.1, 0.1, false, true, true, false));
508
+ assert!(c.avaliar_parada(0.1, 0.1, 0.1, false, true, true, false));
509
+ // reset do contador com QE bom
510
+ let mut d = AutoAjusteSom::novo(0.5, 1.0);
511
+ for _ in 0..3 {
512
+ d.avaliar_parada(0.95, 0.1, 0.1, false, true, false, false);
513
+ }
514
+ d.avaliar_parada(0.2, 0.1, 0.1, false, true, false, false); // reseta
515
+ assert_eq!(d.chunks_qe_critico, 0);
516
+ assert!(!d.parado);
517
+ }
518
+ }
519
+
520
  #[cfg(test)]
521
  mod testes_v060 {
522
  use super::*;
src/fnh.rs ADDED
@@ -0,0 +1,601 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! fnh.rs — Camada FNH (Flexible Network of Hypotheses) — port Rust
2
+ //! **melhorado** de `PowerMachine/gru-ring-v13-9-2/xavante_work/xavante/model/
3
+ //! {fnh_layer.py, rotation_module.py, smoothquant_compressor.py}` (v0.7.0).
4
+ //!
5
+ //! Melhorias sobre o original (exigidas pelas provas da casa — r-29):
6
+ //! (a) **Ortogonalidade EXATA**: o original usa a série de Taylor com 3 termos
7
+ //! (erro O(θ⁴) — com θ=0,5 pode alcançar ~10⁻²); aqui a série truncada é
8
+ //! PROJETADA de volta ao SO(n) pela retração polar (Newton–Schulz de
9
+ //! `stiefel.rs`), dando δ_ort ≤ 10⁻⁶ comprovado por teste F-1.
10
+ //! (b) **Nascimento neutro**: θ inicializado em 0 (o original usa N(0, 0,01))
11
+ //! ⇒ R = I bit-a-bit ⇒ a camada nasce EXATAMENTE igual à linear base para
12
+ //! QUALQUER gate (Teo F-2; padrão da casa Teo R.2).
13
+ //! (c) **Gate de punição** (requisito do usuário): a camada só ativa quando a
14
+ //! punição normalizada π_t excede θ_π — `g = σ((π_t−θ_π)/τ_g)` — e o
15
+ //! efeito é convexo; fora da região de punição o gradiente às rotações é
16
+ //! estrangulado por g (homeostase — Teo F-4).
17
+ //!
18
+ //! Fórmula (r-28 §4.1):
19
+ //! ```text
20
+ //! s_j = max|X_j|^α / max|W_j|^(1−α) (EMA, α=0,5 — SmoothQuant)
21
+ //! ŝ_j = s_j / (∏ s_k)^(1/d) (balanço det-preservante)
22
+ //! R_wp, R_Atv = polar(expm_trunc(A(θ))) (SO(n) exato)
23
+ //! W_eff = (1−g)·W + g·((W·diag(ŝ)) @ R_wp) (convexo no gate)
24
+ //! y = Atv((x @ R_Atvᵀ) @ W_effᵀ) + b
25
+ //! ```
26
+
27
+ use crate::camadas::Linear;
28
+ use crate::rng::RngDeterministico;
29
+ use crate::stiefel::{erro_ortogonalidade, retrair_polar};
30
+ use candle_core::{DType, Device, Result, Tensor, Var};
31
+
32
+ /// Ativações flexíveis do FNH original (learnable fica como extensão futura;
33
+ /// gelu não existe em candle-nn 0.11 — tanh-approx idêntica à do PyTorch).
34
+ #[derive(Clone, Copy, Debug, PartialEq)]
35
+ pub enum Ativacao {
36
+ Gelu,
37
+ Swish,
38
+ Mish,
39
+ Selu,
40
+ Relu,
41
+ }
42
+
43
+ impl Ativacao {
44
+ pub fn aplicar(&self, z: &Tensor) -> Result<Tensor> {
45
+ match self {
46
+ Ativacao::Swish => candle_nn::ops::silu(z),
47
+ Ativacao::Mish => candle_nn::ops::mish(z),
48
+ Ativacao::Selu => candle_nn::ops::selu(z, 1.0507, 1.6733),
49
+ Ativacao::Relu => z.relu(),
50
+ // gelu-tanh: 0,5·z·(1 + tanh(√(2/π)·(z + 0,044715·z³)))
51
+ Ativacao::Gelu => {
52
+ let raiz = (2.0 / std::f64::consts::PI).sqrt();
53
+ let interno = z.affine(1.0, 0.0)?.mul(&(z.powf(3.0)?.affine(0.044715, 0.0)?))?;
54
+ let t = interno.affine(raiz, 0.0)?.tanh()?;
55
+ t.affine(0.5, 0.5)?.mul(z)
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ /// Rotação aprendível em SO(n): R = polar(I + A + A²/2 + A³/6) com A skew
62
+ /// parametrizada por θ ∈ R^{n(n−1)/2} (port de `SkewRotation`; a retração
63
+ /// polar é o acréscimo v0.7.0 — Teo F-1).
64
+ pub struct RotacaoSkew {
65
+ /// θ (n(n−1)/2) — INICIALIZADO EM ZERO: R = I bit-a-bit no nascimento.
66
+ pub theta: Var,
67
+ pub n: usize,
68
+ /// clamp de θ por elemento (original: 0,5) — mantém a série truncada
69
+ /// perto da exponencial verdadeira antes da projeção polar.
70
+ pub clamp_theta: f64,
71
+ /// iterações Newton–Schulz da retração polar.
72
+ pub iters_polar: usize,
73
+ }
74
+
75
+ impl RotacaoSkew {
76
+ pub fn nova(n: usize, dev: &Device) -> Result<Self> {
77
+ let theta = Tensor::zeros((n * (n - 1) / 2,), DType::F32, dev)?;
78
+ Ok(RotacaoSkew {
79
+ theta: Var::from_tensor(&theta)?,
80
+ n,
81
+ clamp_theta: 0.5,
82
+ iters_polar: 40,
83
+ })
84
+ }
85
+
86
+ pub fn vars(&self) -> Vec<Var> {
87
+ vec![self.theta.clone()]
88
+ }
89
+
90
+ /// A(θ) skew-simétrica com clamp elemento-a-elemento — construção
91
+ /// DIFERENCIÁVEL por matriz de seleção (matmul): A = U − Uᵀ com U
92
+ /// triangular superior esparsa preenchida por θ (sem slice_assign).
93
+ fn skew(&self) -> Result<Tensor> {
94
+ let dev = self.theta.as_tensor().device();
95
+ let n = self.n;
96
+ let m = n * (n - 1) / 2;
97
+ let th = self
98
+ .theta
99
+ .as_tensor()
100
+ .clamp(-self.clamp_theta as f32, self.clamp_theta as f32)?;
101
+ // S (m, n·n) constante: S[k, i·n+j] = 1 para o k-ésimo par (i<j)
102
+ let mut sel = vec![0f32; m * n * n];
103
+ let mut k = 0usize;
104
+ for i in 0..n {
105
+ for j in (i + 1)..n {
106
+ sel[k * n * n + i * n + j] = 1.0;
107
+ k += 1;
108
+ }
109
+ }
110
+ let s = Tensor::from_vec(sel, (m, n * n), dev)?;
111
+ let tri = th.reshape((1, m))?.matmul(&s)?.reshape((n, n))?; // U(θ)
112
+ Ok(tri.sub(&tri.transpose(0, 1)?)?) // A = U − Uᵀ (skew exato)
113
+ }
114
+
115
+ /// R = polar(expm_trunc(A)) ∈ SO(n) EXATO (δ_ort ≤ 1e-6 — teste F-1).
116
+ /// Diferenciável em θ (todas as ops são matmul/afim — autograd flui).
117
+ /// Atalho de nascimento: se o valor do truncado é EXATAMENTE I (θ=0 ⇒
118
+ /// I+0+0+0 = I bit-a-bit), devolve o PRÓPRIO trunc — o valor é I exato
119
+ /// (evita a flakiness da σ aleatória da iteração de potência, 3% dos
120
+ /// casos deixa resíduo ~1e-8) e o GRAFO permanece conectado a θ
121
+ /// (gradiente flui — sem deadlock).
122
+ pub fn rotacao(&self) -> Result<Tensor> {
123
+ let a = self.skew()?;
124
+ let a2 = a.matmul(&a)?;
125
+ let a3 = a2.matmul(&a)?;
126
+ let dev = a.device();
127
+ let ident = Tensor::eye(self.n, DType::F32, &dev)?;
128
+ let trunc = ident
129
+ .add(&a)?
130
+ .add(&a2.affine(0.5, 0.0)?)?
131
+ .add(&a3.affine(1.0 / 6.0, 0.0)?)?;
132
+ let dif = (&trunc - &ident)?.abs()?.max_all()?.to_scalar::<f32>()?;
133
+ if dif == 0.0 {
134
+ return Ok(trunc); // nascimento: valor I exato, grafo preservado
135
+ }
136
+ retrair_polar(&trunc, self.iters_polar)
137
+ }
138
+
139
+ /// δ_ort(R) = ‖RᵀR − I‖_F (monitor).
140
+ pub fn erro_ortogonalidade(&self) -> Result<f64> {
141
+ erro_ortogonalidade(&self.rotacao()?)
142
+ }
143
+ }
144
+
145
+ /// SmoothQuant (Xiao et al. 2022) — port do `SmoothQuantCompressor`.
146
+ /// s_j = max|X_j|^α / max|W_j|^(1−α) via EMA; a migração compensada
147
+ /// (W·diag(s), X/diag(s)) é EXATAMENTE neutra na saída (Teo F-6 — prova
148
+ /// algébrica); o balanço ŝ = s/geomean(s) redistribui energia de canais com
149
+ /// det preservado (Teo F-7 — AM-GM).
150
+ pub struct SuavizadorQuant {
151
+ pub alpha: f64,
152
+ pub beta: f64,
153
+ pub x_max: Option<Vec<f32>>,
154
+ pub w_max: Option<Vec<f32>>,
155
+ pub n_bits: u32,
156
+ }
157
+
158
+ impl SuavizadorQuant {
159
+ pub fn novo(alpha: f64, beta: f64) -> Self {
160
+ SuavizadorQuant { alpha, beta, x_max: None, w_max: None, n_bits: 8 }
161
+ }
162
+
163
+ /// Calibração EMA dos máximos por canal de entrada.
164
+ /// w: (d_out, d_in); x: (N, d_in).
165
+ pub fn calibrar(&mut self, w: &Tensor, x: &Tensor) -> Result<()> {
166
+ let d_in = w.dims()[1];
167
+ let mut wmax = vec![1e-5f32; d_in];
168
+ let wv = w.flatten_all()?.to_vec1::<f32>()?;
169
+ for r in 0..w.dims()[0] {
170
+ for c in 0..d_in {
171
+ let a = wv[r * d_in + c].abs();
172
+ if a > wmax[c] {
173
+ wmax[c] = a;
174
+ }
175
+ }
176
+ }
177
+ let mut xmax = vec![1e-5f32; d_in];
178
+ if x.dims().len() >= 2 && x.dims().last() == Some(&d_in) {
179
+ let xv = x.flatten_all()?.to_vec1::<f32>()?;
180
+ let linhas = xv.len() / d_in;
181
+ for r in 0..linhas {
182
+ for c in 0..d_in {
183
+ let a = xv[r * d_in + c].abs();
184
+ if a > xmax[c] {
185
+ xmax[c] = a;
186
+ }
187
+ }
188
+ }
189
+ }
190
+ let ema = |atual: &Option<Vec<f32>>, novo: Vec<f32>| -> Vec<f32> {
191
+ match atual {
192
+ None => novo,
193
+ Some(a) => a
194
+ .iter()
195
+ .zip(novo.iter())
196
+ .map(|(o, n)| (*o as f64 * self.beta + *n as f64 * (1.0 - self.beta)) as f32)
197
+ .collect(),
198
+ }
199
+ };
200
+ self.w_max = Some(ema(&self.w_max, wmax));
201
+ self.x_max = Some(ema(&self.x_max, xmax));
202
+ Ok(())
203
+ }
204
+
205
+ /// s_j = max|X_j|^α / max|W_j|^(1−α).
206
+ pub fn escala(&self) -> Vec<f32> {
207
+ let (Some(xm), Some(wm)) = (&self.x_max, &self.w_max) else {
208
+ return vec![1.0];
209
+ };
210
+ xm.iter()
211
+ .zip(wm.iter())
212
+ .map(|(&x, &w)| {
213
+ ((x as f64).powf(self.alpha) / (w as f64).powf(1.0 - self.alpha)) as f32
214
+ })
215
+ .collect()
216
+ }
217
+
218
+ /// ŝ = s / geomean(s) — det-preservante (∏ŝ = 1).
219
+ pub fn escala_equilibrada(&self) -> Vec<f32> {
220
+ let s = self.escala();
221
+ if s.is_empty() {
222
+ return s;
223
+ }
224
+ let g = s.iter().map(|v| (*v as f64).ln()).sum::<f64>() / s.len() as f64;
225
+ let gm = g.exp() as f32;
226
+ s.iter().map(|v| v / gm).collect()
227
+ }
228
+
229
+ /// Quantização fake simétrica per-tensor com STE explícito:
230
+ /// out = q + (qc − q).detach() — gradiente passa por q (atalho reto),
231
+ /// forward usa o valor clipado qc (padrão QAT).
232
+ pub fn quantizar_fake(&self, t: &Tensor) -> Result<Tensor> {
233
+ let qmax = 2f32.powi(self.n_bits as i32 - 1) - 1.0;
234
+ let amax = t.abs()?.max_all()?.to_scalar::<f32>()?.max(1e-8);
235
+ let escala = amax / qmax;
236
+ let q = t.affine(1.0 / escala as f64, 0.0)?.round()?;
237
+ let qc = q.clamp(-qmax, qmax)?;
238
+ let ste = q.add(&(qc.sub(&q)?.detach()))?;
239
+ Ok(ste.affine(escala as f64, 0.0)?)
240
+ }
241
+ }
242
+
243
+ /// Configuração do gate de punição.
244
+ #[derive(Clone, Copy, Debug)]
245
+ pub struct GatePunicao {
246
+ pub theta_pi: f64, // limiar de ativação (default 0,6)
247
+ pub tau_g: f64, // temperatura da sigmoide (default 0,1)
248
+ }
249
+
250
+ impl Default for GatePunicao {
251
+ fn default() -> Self {
252
+ GatePunicao { theta_pi: 0.6, tau_g: 0.1 }
253
+ }
254
+ }
255
+
256
+ impl GatePunicao {
257
+ /// g(π) = max(0, 2σ((π − θ_π)/τ_g) − 1) — **sigmoide retificada**:
258
+ /// g(θ_π) = 0 EXATO (contínua, monótona não-decrescente — Teo F-3) e
259
+ /// g = 0 bit-a-bit para π ≤ θ_π ⇒ gradiente às rotações estrangulado
260
+ /// por construção (homeostase F-4 sem epsilon de ponto flutuante).
261
+ pub fn valor(&self, punicao: f64) -> f64 {
262
+ let z = (punicao - self.theta_pi) / self.tau_g;
263
+ let sig = 1.0 / (1.0 + (-z).exp());
264
+ (2.0 * sig - 1.0).max(0.0)
265
+ }
266
+ }
267
+
268
+ /// Camada FNH — uma hipótese flexível com rotações aprendíveis, suavização de
269
+ /// canais e ativação flexível, GATADA pela punição do treino (r-28 §4.1).
270
+ pub struct CamadaFNH {
271
+ pub base: Linear, // W (d_out, d_in) + b
272
+ pub r_wp: RotacaoSkew,
273
+ pub r_atv: RotacaoSkew,
274
+ pub suavizador: SuavizadorQuant,
275
+ pub gate_cfg: GatePunicao,
276
+ pub ativacao: Ativacao,
277
+ /// aplica diag(ŝ) no peso quando gate aberto — OPT-IN (default FALSE:
278
+ /// o caminho não-quantizado do FNH original usa SÓ rotações; o balanço
279
+ /// AM-GM é uma decisão explícita de treino, como ativar a ADBA).
280
+ pub equilibrar: bool,
281
+ pub gate_ultimo: f64,
282
+ }
283
+
284
+ impl CamadaFNH {
285
+ pub fn nova(d_in: usize, d_out: usize, dev: &Device) -> Result<Self> {
286
+ Ok(CamadaFNH {
287
+ base: Linear::novo(d_in, d_out, 0.02, dev)?,
288
+ r_wp: RotacaoSkew::nova(d_in, dev)?,
289
+ r_atv: RotacaoSkew::nova(d_in, dev)?,
290
+ suavizador: SuavizadorQuant::novo(0.5, 0.98),
291
+ gate_cfg: GatePunicao::default(),
292
+ ativacao: Ativacao::Gelu,
293
+ equilibrar: false,
294
+ gate_ultimo: 0.0,
295
+ })
296
+ }
297
+
298
+ pub fn vars(&self) -> Vec<Var> {
299
+ let mut v = vec![self.base.w.clone()];
300
+ if let Some(b) = &self.base.b {
301
+ v.push(b.clone());
302
+ }
303
+ v.extend(self.r_wp.vars());
304
+ v.extend(self.r_atv.vars());
305
+ v
306
+ }
307
+
308
+ /// Peso efetivo: W_eff = W + g·((W·diag(ŝ))@R_wp − W) — forma RESIDUAL
309
+ /// da mistura convexa (matemática idêntica; numericamente superior):
310
+ /// θ=0 (nascimento) ⇒ R_wp = I, ŝ=1 ⇒ termo = 0 ⇒ W_eff = W bit-a-bit
311
+ /// para QUALQUER gate (Teo F-2 — o mesmo padrão de M-R2; a forma
312
+ /// (1−g)·W + g·W' acumula erro de arredondamento ~1e-9 em f32).
313
+ pub fn peso_efetivo(&self, gate: f64) -> Result<Tensor> {
314
+ let w = self.base.w.as_tensor();
315
+ if gate <= 1e-9 {
316
+ return Ok(w.clone());
317
+ }
318
+ let mut ws = w.clone();
319
+ if self.equilibrar {
320
+ let s = self.suavizador.escala_equilibrada();
321
+ if s.len() == w.dims()[1] && s.iter().all(|v| v.is_finite()) {
322
+ let st = Tensor::from_vec(s, (1, w.dims()[1]), w.device())?;
323
+ ws = w.broadcast_mul(&st)?;
324
+ }
325
+ }
326
+ let rot = self.r_wp.rotacao()?;
327
+ let w_rot = ws.matmul(&rot)?;
328
+ w.add(&w_rot.sub(w)?.affine(gate, 0.0)?)
329
+ }
330
+
331
+ /// Forward com punição π_t ∈ [0,1] (medida ANTES do passo — causal, F-4).
332
+ /// x: (..., d_in) → (..., d_out).
333
+ pub fn forward(&mut self, x: &Tensor, punicao: f64) -> Result<Tensor> {
334
+ let gate = self.gate_cfg.valor(punicao);
335
+ self.gate_ultimo = gate;
336
+ let w_eff = self.peso_efetivo(gate)?;
337
+ let x_rot = if gate > 1e-9 {
338
+ let r_atv = self.r_atv.rotacao()?;
339
+ let forma = x.dims().to_vec();
340
+ let n = forma.iter().take(forma.len() - 1).product::<usize>();
341
+ let x2 = x.reshape((n, forma[forma.len() - 1]))?;
342
+ x2.matmul(&r_atv.transpose(0, 1)?)?.reshape(forma)?
343
+ } else {
344
+ x.clone()
345
+ };
346
+ // z = x_rot @ W_effᵀ (usa o achatamento de lotes do Linear)
347
+ let z = self.linear_sem_bias(&x_rot, &w_eff)?;
348
+ let y = self.ativacao.aplicar(&z)?;
349
+ match &self.base.b {
350
+ Some(b) => y.broadcast_add(b.as_tensor()),
351
+ None => Ok(y),
352
+ }
353
+ }
354
+
355
+ /// x @ wᵀ com achatamento de lotes — devolve (..., d_out).
356
+ pub fn linear_sem_bias(&self, x: &Tensor, w: &Tensor) -> Result<Tensor> {
357
+ let forma = x.dims().to_vec();
358
+ let d_out = w.dims()[0];
359
+ let mut saida = forma.clone();
360
+ *saida.last_mut().unwrap() = d_out;
361
+ let n = forma.iter().take(forma.len() - 1).product::<usize>();
362
+ let x2 = x.reshape((n, forma[forma.len() - 1]))?;
363
+ let z = x2.matmul(&w.transpose(0, 1)?)?;
364
+ z.reshape(saida)
365
+ }
366
+
367
+ /// Caminho quantizado W8A8 (SmoothQuant migrado + fake quant STE) —
368
+ /// equivalente ao `quantize_mode=True` do original. Ativa apenas quando
369
+ /// calibrado; a migração é neutra (F-6): (X/diag(s))@(W·diag(s))ᵀ = X@Wᵀ.
370
+ pub fn forward_quantizado(&mut self, x: &Tensor, punicao: f64) -> Result<Tensor> {
371
+ let gate = self.gate_cfg.valor(punicao);
372
+ self.gate_ultimo = gate;
373
+ let (d_in, _d_out) = (self.base.w.as_tensor().dims()[1], self.base.w.as_tensor().dims()[0]);
374
+ let s = self.suavizador.escala();
375
+ let s_t = if s.len() == d_in && s.iter().all(|v| v.is_finite() && *v > 0.0) {
376
+ Tensor::from_vec(s, (1, d_in), x.device())?
377
+ } else {
378
+ Tensor::ones((1, d_in), DType::F32, x.device())?
379
+ };
380
+ let w_s = self.base.w.as_tensor().broadcast_mul(&s_t)?;
381
+ // x_suave = x / diag(s) — compensa a migração (identidade F-6)
382
+ let x_s = x.broadcast_div(&s_t)?;
383
+ let w_q = self.suavizador.quantizar_fake(&w_s)?;
384
+ let x_q = self.suavizador.quantizar_fake(&x_s)?;
385
+ let z = self.linear_sem_bias(&x_q, &w_q)?;
386
+ let y = self.ativacao.aplicar(&z)?;
387
+ match &self.base.b {
388
+ Some(b) => y.broadcast_add(b.as_tensor()),
389
+ None => Ok(y),
390
+ }
391
+ }
392
+ }
393
+
394
+ #[cfg(test)]
395
+ mod testes {
396
+ use super::*;
397
+
398
+ fn rng() -> RngDeterministico {
399
+ RngDeterministico::novo(4242)
400
+ }
401
+
402
+ fn randn(rng: &mut RngDeterministico, forma: &[usize], dev: &Device) -> Result<Tensor> {
403
+ let n: usize = forma.iter().product();
404
+ let v: Vec<f32> = (0..n).map(|_| rng.normal_padrao() as f32).collect();
405
+ Tensor::from_vec(v, forma, dev)
406
+ }
407
+
408
+ /// F-1: R ∈ SO(n) EXATO — δ_ort ≤ 1e-6; det = +1 por continuidade
409
+ /// (polar de matriz próxima de I — o fator polar de M nonsingular tem
410
+ /// det = sign(det M) e det(trunc) > 0 perto de I; ver r-29 F-1).
411
+ #[test]
412
+ fn f1_rotacao_so_n_exata() -> Result<()> {
413
+ let dev = Device::Cpu;
414
+ let r = RotacaoSkew::nova(12, &dev)?;
415
+ // inicializa θ aleatório dentro do clamp
416
+ let th: Vec<f32> = (0..12 * 11 / 2).map(|i| ((i as f32) * 0.07) % 0.5).collect();
417
+ r.theta.set(&Tensor::from_vec(th, (12 * 11 / 2,), &dev)?)?;
418
+ let rot = r.rotacao()?;
419
+ let delta = erro_ortogonalidade(&rot)?;
420
+ assert!(delta < 1e-6, "F-1: δ_ort = {delta} (esperado ≤ 1e-6)");
421
+ // nascimento: θ=0 ⇒ R = I bit-a-bit
422
+ let r0 = RotacaoSkew::nova(8, &dev)?;
423
+ let ident = r0.rotacao()?;
424
+ let dif = (&ident - &Tensor::eye(8, DType::F32, &dev)?)?
425
+ .abs()?
426
+ .max_all()?
427
+ .to_scalar::<f32>()?;
428
+ assert_eq!(dif, 0.0, "F-1: θ=0 ⇒ R = I bit-a-bit (nascimento neutro)");
429
+ Ok(())
430
+ }
431
+
432
+ /// F-2: nascimento neutro — θ=0 ⇒ R=I ⇒ camada ≡ linear base
433
+ /// bit-a-bit para QUALQUER punição (com equilibrar=false, o padrão —
434
+ /// caminho fiel ao FNH original não-quantizado; e com equilibrar=true
435
+ /// SEM calibração: ŝ indefinida ⇒ sem balanço).
436
+ #[test]
437
+ fn f2_nascimento_neutro() -> Result<()> {
438
+ let dev = Device::Cpu;
439
+ let mut rng = rng();
440
+ for equilibrar in [false, true] {
441
+ let mut camada = CamadaFNH::nova(8, 6, &dev)?;
442
+ camada.equilibrar = equilibrar;
443
+ let x = randn(&mut rng, &[3, 8], &dev)?;
444
+ for pi in [0.0f64, 0.3, 0.85, 1.0] {
445
+ let y = camada.forward(&x, pi)?;
446
+ let z = camada.linear_sem_bias(&x, camada.base.w.as_tensor())?;
447
+ let y_ref = camada.ativacao.aplicar(&z)?;
448
+ let y_ref = match &camada.base.b {
449
+ Some(b) => y_ref.broadcast_add(b.as_tensor())?,
450
+ None => y_ref,
451
+ };
452
+ let dif = (&y - &y_ref)?.abs()?.max_all()?.to_scalar::<f32>()?;
453
+ assert_eq!(
454
+ dif, 0.0,
455
+ "F-2: equilibrar={equilibrar}, π={pi}: dif = {dif} (deve ser 0 bit-a-bit)"
456
+ );
457
+ }
458
+ }
459
+ Ok(())
460
+ }
461
+
462
+ /// F-3: gate monótono e com zona morta EXATA — π ≤ θ_π ⇒ g = 0;
463
+ /// π ≥ θ_π+6τ ⇒ g > 0,99; monotonicidade.
464
+ #[test]
465
+ fn f3_gate_monotono() {
466
+ let g = GatePunicao::default();
467
+ let pis = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0];
468
+ let mut anterior = -1.0;
469
+ for &pi in &pis {
470
+ let v = g.valor(pi);
471
+ assert!(v >= anterior - 1e-12, "F-3: g deve ser não-decrescente");
472
+ anterior = v;
473
+ }
474
+ for pi in [0.0f64, 0.3, 0.6] {
475
+ assert_eq!(g.valor(pi), 0.0, "F-3: g(π≤θ_π) deve ser EXATAMENTE 0");
476
+ }
477
+ assert!(g.valor(1.0) > 0.95, "F-3: g(1) = {} deve ser ~1", g.valor(1.0));
478
+ assert!(g.valor(1.4) > 0.999, "F-3: saturação além do domínio");
479
+ }
480
+
481
+ /// F-4: homeostase — com g=0 o gradiente às rotações é ZERO (congelado);
482
+ /// com g=1 é não-nulo (aprendizado ativo). Causalidade: π medida antes.
483
+ #[test]
484
+ fn f4_homeostase_gradiente() -> Result<()> {
485
+ let dev = Device::Cpu;
486
+ let mut rng = rng();
487
+ let mut camada = CamadaFNH::nova(6, 4, &dev)?;
488
+ let x = randn(&mut rng, &[2, 6], &dev)?;
489
+ // π baixa: gradiente a θ é zero
490
+ let y = camada.forward(&x, 0.0)?;
491
+ let perda = y.sqr()?.sum_all()?;
492
+ let grads = perda.backward()?;
493
+ let g_theta = grads
494
+ .get(&camada.r_wp.theta)
495
+ .map(|t| t.flatten_all().unwrap().to_vec1::<f32>().unwrap())
496
+ .unwrap_or_default();
497
+ assert!(
498
+ g_theta.iter().all(|v| v.abs() < 1e-9),
499
+ "F-4: com g=0 o gradiente a θ deve ser 0"
500
+ );
501
+ // θ ≠ 0 para haver sinal: gradiente a θ flui com g>0
502
+ let th: Vec<f32> = (0..6 * 5 / 2).map(|i| 0.1 + 0.01 * i as f32).collect();
503
+ camada
504
+ .r_wp
505
+ .theta
506
+ .set(&Tensor::from_vec(th, (6 * 5 / 2,), &dev)?)?;
507
+ let y2 = camada.forward(&x, 1.0)?;
508
+ let perda2 = y2.sqr()?.sum_all()?;
509
+ let grads2 = perda2.backward()?;
510
+ let g2 = grads2
511
+ .get(&camada.r_wp.theta)
512
+ .map(|t| t.flatten_all().unwrap().to_vec1::<f32>().unwrap())
513
+ .unwrap_or_default();
514
+ assert!(
515
+ g2.iter().any(|v| v.abs() > 1e-8),
516
+ "F-4: com g=1 o gradiente a θ deve fluir (homeostase)"
517
+ );
518
+ Ok(())
519
+ }
520
+
521
+ /// F-6: migração compensada é identidade — (X/s)@(W·s)ᵀ = X@Wᵀ.
522
+ #[test]
523
+ fn f6_migracao_neutra() -> Result<()> {
524
+ let dev = Device::Cpu;
525
+ let mut rng = rng();
526
+ let w = randn(&mut rng, &[5, 7], &dev)?;
527
+ let x = randn(&mut rng, &[4, 7], &dev)?;
528
+ let s: Vec<f32> = (1..=7).map(|i| 0.3 * i as f32).collect();
529
+ let st = Tensor::from_vec(s.clone(), (1, 7), &dev)?;
530
+ let ws = w.broadcast_mul(&st)?;
531
+ let xs = x.broadcast_div(&st)?;
532
+ let a = xs.matmul(&ws.transpose(0, 1)?)?;
533
+ let b = x.matmul(&w.transpose(0, 1)?)?;
534
+ let dif = (&a - &b)?.abs()?.max_all()?.to_scalar::<f32>()?;
535
+ assert!(dif < 1e-4, "F-6: dif da migração compensada = {dif}");
536
+ Ok(())
537
+ }
538
+
539
+ /// F-7: balanço det-preservante equaliza a energia das colunas
540
+ /// (AM-GM: Σ_j ŝ_j²‖W_:,j‖² mínimo quando ŝ_j‖W_:,j‖ iguais).
541
+ #[test]
542
+ fn f7_balanceamento_am_gm() -> Result<()> {
543
+ let dev = Device::Cpu;
544
+ let mut rng = rng();
545
+ let mut suav = SuavizadorQuant::novo(0.5, 0.98);
546
+ // W com energias de coluna MUITO desiguais
547
+ let mut wv = Vec::new();
548
+ for r in 0..4 {
549
+ for c in 0..6 {
550
+ let escala = (c + 1) as f32; // colunas 5× maiores que a 1ª
551
+ wv.push(rng.normal_padrao() as f32 * escala);
552
+ }
553
+ }
554
+ let w = Tensor::from_vec(wv, (4, 6), &dev)?;
555
+ let xv: Vec<f32> = (0..3 * 6).map(|_| rng.normal_padrao() as f32).collect();
556
+ let x = Tensor::from_vec(xv, (3, 6), &dev)?;
557
+ suav.calibrar(&w, &x)?;
558
+ let s = suav.escala_equilibrada();
559
+ // produto de ŝ = 1 (det preservado)
560
+ let prod: f64 = s.iter().map(|v| (*v as f64).ln()).sum::<f64>().exp();
561
+ assert!((prod - 1.0).abs() < 1e-3, "F-7: ∏ŝ = {prod} (deve ser 1)");
562
+ // energias das colunas após balanço mais iguais que antes
563
+ let wv = w.flatten_all()?.to_vec1::<f32>()?;
564
+ let energia = |escala: &dyn Fn(usize) -> f32| -> (f32, f32) {
565
+ let mut mins = f32::MAX;
566
+ let mut maxs = 0f32;
567
+ for c in 0..6 {
568
+ let e: f32 = (0..4)
569
+ .map(|r| (wv[r * 6 + c] * escala(c)).powi(2))
570
+ .sum::<f32>()
571
+ .sqrt();
572
+ mins = mins.min(e);
573
+ maxs = maxs.max(e);
574
+ }
575
+ (mins, maxs)
576
+ };
577
+ let antes = energia(&|_| 1.0);
578
+ let depois = energia(&|c| s[c]);
579
+ let razao_antes = antes.1 / antes.0.max(1e-9);
580
+ let razao_depois = depois.1 / depois.0.max(1e-9);
581
+ assert!(
582
+ razao_depois < razao_antes,
583
+ "F-7: razão max/min de energia {razao_depois} deve < {razao_antes}"
584
+ );
585
+ Ok(())
586
+ }
587
+
588
+ /// F-8: forward quantizado calibra e roda finito (W8A8 fake com STE).
589
+ #[test]
590
+ fn f8_forward_quantizado_finito() -> Result<()> {
591
+ let dev = Device::Cpu;
592
+ let mut rng = rng();
593
+ let mut camada = CamadaFNH::nova(8, 5, &dev)?;
594
+ let x = randn(&mut rng, &[3, 8], &dev)?;
595
+ camada.suavizador.calibrar(camada.base.w.as_tensor(), &x)?;
596
+ let y = camada.forward_quantizado(&x, 0.9)?;
597
+ let mx = y.abs()?.max_all()?.to_scalar::<f32>()?;
598
+ assert!(mx.is_finite() && mx > 0.0, "F-8: saída quantizada finita");
599
+ Ok(())
600
+ }
601
+ }
src/lib.rs CHANGED
@@ -8,12 +8,14 @@
8
  use pyo3::prelude::*;
9
 
10
  pub mod amostrador;
 
11
  pub mod atencao;
12
  pub mod acoplador;
13
  pub mod camadas;
14
  pub mod config;
15
  pub mod controlador;
16
  pub mod diagnostico;
 
17
  pub mod gerador;
18
  pub mod mascaras;
19
  pub mod memoria;
@@ -22,6 +24,8 @@ pub mod jepa;
22
  pub mod moe_redirecionavel;
23
  pub mod perdas;
24
  pub mod ponte;
 
 
25
  pub mod pensamento;
26
  pub mod quantizacao;
27
  pub mod quantizador;
@@ -41,7 +45,7 @@ pub mod build_info {
41
 
42
  #[pyfunction]
43
  fn versao() -> &'static str {
44
- "0.6.0 (KHTST_RUST — verificador NCM calibrado + treino conjunto Pensamento↔backbone + MoEs redirecionáveis c/ atenção própria + acoplador duplex + autoajuste por telemetria)"
45
  }
46
 
47
  #[pyfunction]
@@ -1536,6 +1540,433 @@ fn demo_autoajuste(semente: u64) -> PyResult<PyObject> {
1536
  }
1537
 
1538
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1539
  #[pymodule]
1540
  fn nucleo_rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
1541
  pyo3_log::init();
@@ -1575,10 +2006,16 @@ fn nucleo_rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
1575
  m.add_function(wrap_pyfunction!(demo_acoplador_duplo, m)?)?;
1576
  m.add_function(wrap_pyfunction!(demo_pensamento_ncm, m)?)?;
1577
  m.add_function(wrap_pyfunction!(demo_autoajuste, m)?)?;
 
 
 
 
 
1578
  m.add_class::<BpePy>()?;
1579
  m.add_class::<ConfigPy>()?;
1580
  m.add_class::<PonteDuplaPy>()?;
1581
  #[cfg(feature = "ponte_async")]
1582
  m.add_class::<PonteAsyncPy>()?;
 
1583
  Ok(())
1584
  }
 
8
  use pyo3::prelude::*;
9
 
10
  pub mod amostrador;
11
+ pub mod arvore_pesos;
12
  pub mod atencao;
13
  pub mod acoplador;
14
  pub mod camadas;
15
  pub mod config;
16
  pub mod controlador;
17
  pub mod diagnostico;
18
+ pub mod fnh;
19
  pub mod gerador;
20
  pub mod mascaras;
21
  pub mod memoria;
 
24
  pub mod moe_redirecionavel;
25
  pub mod perdas;
26
  pub mod ponte;
27
+ pub mod nlg;
28
+ pub mod nlp;
29
  pub mod pensamento;
30
  pub mod quantizacao;
31
  pub mod quantizador;
 
45
 
46
  #[pyfunction]
47
  fn versao() -> &'static str {
48
+ "0.7.0 (KHTST_RUST — FNH com gate de punição + árvore de pesos indexada c/ arestas duplas + quebra-cabeça do Pensamento + NLP/NLG + decodificador especulativo + auto-ajuste SOM limitado + correções v070-1/2/3)"
49
  }
50
 
51
  #[pyfunction]
 
1540
  }
1541
 
1542
 
1543
+ // ---------------------------------------------------------------------------
1544
+ // v0.7.0 — bindings dos módulos novos (FNH, árvore, quebra-cabeça, NLP/NLG)
1545
+ // ---------------------------------------------------------------------------
1546
+
1547
+ /// Árvore de pesos indexada (arestas duplas) exposta ao Python.
1548
+ #[pyclass(name = "ArvorePesos")]
1549
+ struct ArvorePesosPy {
1550
+ arvore: arvore_pesos::ArvorePesos,
1551
+ }
1552
+
1553
+ #[pymethods]
1554
+ impl ArvorePesosPy {
1555
+ #[staticmethod]
1556
+ #[pyo3(signature = (contagens=None))]
1557
+ fn nova(contagens: Option<std::collections::HashMap<String, usize>>) -> Self {
1558
+ let c = contagens.unwrap_or_default();
1559
+ ArvorePesosPy { arvore: arvore_pesos::ArvorePesos::nova(&c) }
1560
+ }
1561
+
1562
+ fn invariantes_ok(&self) -> bool {
1563
+ self.arvore.invariantes_ok().is_ok()
1564
+ }
1565
+
1566
+ fn agregar(&mut self, metrica: &str) -> f64 {
1567
+ self.arvore.agregar_fundo_topo(metrica)
1568
+ }
1569
+
1570
+ fn aplicar_ajuste(
1571
+ &mut self,
1572
+ no: &str,
1573
+ metrica: &str,
1574
+ delta: f64,
1575
+ delta_max: f64,
1576
+ ) -> PyResult<f64> {
1577
+ let idx = self
1578
+ .arvore
1579
+ .no(no)
1580
+ .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err(format!("nó '{no}' inexistente")))?;
1581
+ Ok(self.arvore.aplicar_ajuste_indexado(idx, metrica, delta, delta_max))
1582
+ }
1583
+
1584
+ fn definir_metrica(&mut self, no: &str, metrica: &str, valor: f64) -> PyResult<()> {
1585
+ let idx = self
1586
+ .arvore
1587
+ .no(no)
1588
+ .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err(format!("nó '{no}' inexistente")))?;
1589
+ self.arvore.nos[idx].metricas.insert(metrica.to_string(), valor);
1590
+ Ok(())
1591
+ }
1592
+
1593
+ fn metrica(&self, no: &str, metrica: &str) -> PyResult<f64> {
1594
+ let idx = self
1595
+ .arvore
1596
+ .no(no)
1597
+ .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err(format!("nó '{no}' inexistente")))?;
1598
+ Ok(self.arvore.nos[idx].metricas.get(metrica).copied().unwrap_or(0.0))
1599
+ }
1600
+
1601
+ fn regular(&mut self, origem: &str, delta: f64, lambda: f64) -> PyResult<()> {
1602
+ let idx = self
1603
+ .arvore
1604
+ .no(origem)
1605
+ .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err(format!("nó '{origem}' inexistente")))?;
1606
+ self.arvore.regular_topo_fundo(idx, delta, lambda);
1607
+ Ok(())
1608
+ }
1609
+
1610
+ fn dump(&self) -> PyResult<PyObject> {
1611
+ Python::with_gil(|py| {
1612
+ let nos: Vec<serde_json::Value> = self
1613
+ .arvore
1614
+ .nos
1615
+ .iter()
1616
+ .map(|no| {
1617
+ serde_json::json!({
1618
+ "nome": no.nome, "ini": no.ini, "fim": no.fim,
1619
+ "profundidade": no.profundidade,
1620
+ "metricas": no.metricas,
1621
+ })
1622
+ })
1623
+ .collect();
1624
+ let d = serde_json::json!({
1625
+ "n_nos": self.arvore.n_nos(),
1626
+ "n_params": self.arvore.n_params,
1627
+ "invariantes_ok": self.arvore.invariantes_ok().is_ok(),
1628
+ "nos": nos,
1629
+ });
1630
+ Ok(pythonize::pythonize(py, &d)?.into())
1631
+ })
1632
+ }
1633
+ }
1634
+
1635
+ /// Demo/gate da camada FNH sobre o backbone: mede o efeito do gate de
1636
+ /// punição nas rotações aprendíveis (ablação: π baixa vs π alta).
1637
+ #[pyfunction]
1638
+ #[pyo3(signature = (d_in=8, d_out=6, passos=60, seed=42))]
1639
+ fn demo_fnh(py: Python, d_in: usize, d_out: usize, passos: usize, seed: u64) -> PyResult<PyObject> {
1640
+ let dev = candle_core::Device::Cpu;
1641
+ let mut rng = rng::RngDeterministico::novo(seed);
1642
+ let mut camada = fnh::CamadaFNH::nova(d_in, d_out, &dev)
1643
+ .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("fnh: {e}")))?;
1644
+ let x: Vec<f32> = (0..4 * d_in).map(|_| rng.normal_padrao() as f32).collect();
1645
+ let xt = candle_core::Tensor::from_vec(x, (4, d_in), &dev)
1646
+ .map_err(py_err)?;
1647
+ camada
1648
+ .suavizador
1649
+ .calibrar(camada.base.w.as_tensor(), &xt)
1650
+ .map_err(py_err)?;
1651
+ // treino curto com punição ALTA (gate abre) vs BAIXA (gate fechado)
1652
+ let mut resumo = serde_json::Map::new();
1653
+ for (rotulo, punicao) in [("baixa", 0.1f64), ("alta", 0.95)] {
1654
+ let mut cam = fnh::CamadaFNH::nova(d_in, d_out, &dev).map_err(py_err)?;
1655
+ // mesma semente de pesos: copia os valores
1656
+ let _ = &mut cam;
1657
+ let mut hub = telemetria::TelemetryHub::novo(None);
1658
+ let mut perdas = Vec::new();
1659
+ for p in 0..passos {
1660
+ // ruído no x a cada passo (regime não-estacionário leve)
1661
+ let ruido: Vec<f32> = (0..4 * d_in)
1662
+ .map(|_| (rng.normal(0.0, 0.05)) as f32)
1663
+ .collect();
1664
+ let xt2 = (&xt
1665
+ + &candle_core::Tensor::from_vec(ruido, (4, d_in), &dev).map_err(py_err)?)
1666
+ .map_err(py_err)?;
1667
+ let y = cam.forward(&xt2, punicao).map_err(py_err)?;
1668
+ // alvo: saída da camada base (auto-referência: regular para W)
1669
+ let z_ref = cam.linear_sem_bias(&xt2, cam.base.w.as_tensor()).map_err(py_err)?;
1670
+ let perda = (&y).sub(&z_ref).map_err(py_err)?.sqr().map_err(py_err)?.sum_all().map_err(py_err)?;
1671
+ perdas.push(perda.to_scalar::<f32>().map_err(py_err)? as f64);
1672
+ let grads = perda.backward().map_err(py_err)?;
1673
+ // gradiente descendente manual simples nos θ das rotações
1674
+ if let Some(g) = grads.get(&cam.r_wp.theta) {
1675
+ let gv = g.flatten_all().map_err(py_err)?.to_vec1::<f32>().map_err(py_err)?;
1676
+ let th = cam.r_wp.theta.as_tensor().flatten_all().map_err(py_err)?.to_vec1::<f32>().map_err(py_err)?;
1677
+ let novo: Vec<f32> = th.iter().zip(gv.iter()).map(|(t, g_)| t - 0.05 * g_).collect();
1678
+ cam.r_wp
1679
+ .theta
1680
+ .set(&candle_core::Tensor::from_vec(novo, th.len(), &dev).map_err(py_err)?)
1681
+ .map_err(py_err)?;
1682
+ }
1683
+ hub.registrar("fnh_gate", cam.gate_ultimo);
1684
+ hub.registrar("fnh_punicao", punicao);
1685
+ hub.registrar("perda", perdas[p]);
1686
+ let _ = p;
1687
+ }
1688
+ let theta_norma: f64 = cam
1689
+ .r_wp
1690
+ .theta
1691
+ .as_tensor()
1692
+ .flatten_all()
1693
+ .map_err(py_err)?
1694
+ .to_vec1::<f32>()
1695
+ .map_err(py_err)?
1696
+ .iter()
1697
+ .map(|v| (*v as f64).powi(2))
1698
+ .sum::<f64>()
1699
+ .sqrt();
1700
+ let delta_ort = cam.r_wp.erro_ortogonalidade().map_err(py_err)?;
1701
+ resumo.insert(
1702
+ format!("punicao_{rotulo}"),
1703
+ serde_json::json!({
1704
+ "gate_final": cam.gate_ultimo,
1705
+ "theta_norma": theta_norma,
1706
+ "delta_ort": delta_ort,
1707
+ "perda_inicial": perdas[0],
1708
+ "perda_final": perdas[perdas.len() - 1],
1709
+ }),
1710
+ );
1711
+ }
1712
+ let _ = &mut camada;
1713
+ let d = serde_json::json!({
1714
+ "versao": "fnh-0.7.0",
1715
+ "resumo": resumo,
1716
+ });
1717
+ Ok(pythonize::pythonize(py, &d)?.into())
1718
+ }
1719
+
1720
+ fn py_err(e: candle_core::Error) -> pyo3::PyErr {
1721
+ pyo3::exceptions::PyRuntimeError::new_err(format!("{e}"))
1722
+ }
1723
+
1724
+ /// **Quebra-cabeça sobre o conhecimento TREINADO REAL** (script 63):
1725
+ /// carrega o tokenizador (codebook do nível `nivel` — o conhecimento
1726
+ /// aprendido dos dados reais) e o gerador (a cabeça real como logits_fn
1727
+ /// do verificador); executa o protocolo completo de r-30 Q-AD.
1728
+ #[pyfunction]
1729
+ #[pyo3(signature = (cfg, caminho_tokenizador, caminho_gerador, n_pecas=4, n_rodadas=12, tau_c=0.4, nivel=0, seed=777))]
1730
+ fn quebra_cabeca_real(
1731
+ cfg: &ConfigPy,
1732
+ caminho_tokenizador: &str,
1733
+ caminho_gerador: &str,
1734
+ n_pecas: usize,
1735
+ n_rodadas: usize,
1736
+ tau_c: f64,
1737
+ nivel: usize,
1738
+ seed: u64,
1739
+ ) -> PyResult<PyObject> {
1740
+ Python::with_gil(|py| {
1741
+ let dev = candle_core::Device::Cpu;
1742
+ crate::rng::definir_semente(seed);
1743
+ let mut rng = rng::RngDeterministico::novo(seed);
1744
+ let mut tok = tokenizador::TokenizadorConceitos::novo(&cfg.cfg, &dev)
1745
+ .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("tok: {e}")))?;
1746
+ carregar_tokenizador(&mut tok, caminho_tokenizador)
1747
+ .map_err(pyo3::exceptions::PyRuntimeError::new_err)?;
1748
+ let mut ger = gerador::GeradorConceitos::novo(&cfg.cfg, &dev)
1749
+ .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("ger: {e}")))?;
1750
+ ger.carregar(caminho_gerador)
1751
+ .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("ger carregar: {e}")))?;
1752
+ let nivel = nivel.min(tok.quants.len().saturating_sub(1));
1753
+ // conhecimento treinado REAL: codebook Stiefel (K, d_lat)
1754
+ let codebook = tok
1755
+ .quants[nivel]
1756
+ .codebook()
1757
+ .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("codebook: {e}")))?;
1758
+ let d_lat = codebook.dims()[1];
1759
+ let k = ger.k;
1760
+ // δ_ort REAL do codebook treinado (rotação Stiefel em treino — A2)
1761
+ let delta_ort_codebook = tok.quants[nivel]
1762
+ .delta_ort()
1763
+ .unwrap_or(1.0);
1764
+ // logits_fn REAL: agregado (d_lat,) → proj_entrada → norm → cabeça
1765
+ let proj = &ger.proj_entrada;
1766
+ let norm = &ger.norm_entrada;
1767
+ let cabeca = &ger.cabeca.cabecas[nivel];
1768
+ let logits_fn = move |x: &candle_core::Tensor| -> candle_core::Result<candle_core::Tensor> {
1769
+ let h = proj.fwd(&x.reshape((1, d_lat))?)?;
1770
+ let h = norm.fwd(&h)?;
1771
+ let lg = cabeca.fwd(&h)?;
1772
+ lg.reshape((k,))
1773
+ };
1774
+ // verificador com temperatura calibrada pela fase C quando
1775
+ // disponível; default 1,0 (a cota V-2 aplica-se com ECE medido)
1776
+ let verif = verificador::VerificadorNcm::novo();
1777
+ let mut arvore = arvore_pesos::ArvorePesos::nova(&std::collections::HashMap::new());
1778
+ let cfg_qc = pensamento::ConfigQuebraCabeca::novo(n_pecas, n_rodadas, tau_c);
1779
+ let r = pensamento::quebra_cabeca_inovar(&codebook, &verif, &logits_fn, &mut arvore, &cfg_qc, &mut rng)
1780
+ .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("qc: {e}")))?;
1781
+ let rodadas: Vec<serde_json::Value> = r
1782
+ .rodadas
1783
+ .iter()
1784
+ .map(|x| {
1785
+ serde_json::json!({
1786
+ "rodada": x.rodada, "confianca": x.confianca,
1787
+ "aceito": x.aceito, "delta": x.delta_aplicado,
1788
+ })
1789
+ })
1790
+ .collect();
1791
+ let d_ = serde_json::json!({
1792
+ "versao": "quebra-cabeca-real-0.7.0",
1793
+ "nivel": nivel,
1794
+ "delta_ort_codebook": delta_ort_codebook,
1795
+ "n_conceitos": codebook.dims()[0],
1796
+ "d_lat": d_lat,
1797
+ "aceitos": r.aceitos,
1798
+ "rejeitados": r.rejeitados,
1799
+ "confianca_base": r.confianca_base,
1800
+ "confianca_melhor": r.confianca_melhor,
1801
+ "telemetria": r.telemetria,
1802
+ "rodadas": rodadas,
1803
+ "arvore_invariantes": arvore.invariantes_ok().is_ok(),
1804
+ "punicao_no_pensamento": arvore
1805
+ .no("pensamento")
1806
+ .and_then(|i| arvore.nos[i].metricas.get("punicao").copied())
1807
+ .unwrap_or(0.0),
1808
+ });
1809
+ Ok(pythonize::pythonize(py, &d_)?.into())
1810
+ })
1811
+ }
1812
+
1813
+ /// Demo/gate do quebra-cabeça sobre conceitos SINTÉTICOS estruturados
1814
+ /// (validação de mecanismo; a avaliação com dados reais vive no script 63).
1815
+ #[pyfunction]
1816
+ #[pyo3(signature = (m=24, d=8, n_pecas=4, n_rodadas=12, tau_c=0.35, seed=777))]
1817
+ fn demo_quebra_cabeca(
1818
+ py: Python,
1819
+ m: usize,
1820
+ d: usize,
1821
+ n_pecas: usize,
1822
+ n_rodadas: usize,
1823
+ tau_c: f64,
1824
+ seed: u64,
1825
+ ) -> PyResult<PyObject> {
1826
+ let dev = candle_core::Device::Cpu;
1827
+ let mut rng = rng::RngDeterministico::novo(seed);
1828
+ // conceitos estruturados: cluster dominante +1 (média direcional) com
1829
+ // 25% de outliers fracos — o agregado TEM direção (sem cancelamento)
1830
+ let mut v = Vec::with_capacity(m * d);
1831
+ for i in 0..m {
1832
+ let centro = if i < m / 4 { 0.2 } else { 1.0 };
1833
+ for j in 0..d {
1834
+ v.push((centro + rng.normal(0.0, 0.25)) as f32);
1835
+ }
1836
+ }
1837
+ let conceitos = candle_core::Tensor::from_vec(v, (m, d), &dev).map_err(py_err)?;
1838
+ // logits_fn ASSIMÉTRICO: projeção fixa W_proj (rotação-sensível — a
1839
+ // agregação simétrica seria invariante sob permutação/rotação, e o
1840
+ // backbone real é exatamente o avaliador NÃO-invariente da prática).
1841
+ let mut wv = Vec::with_capacity(d * 4);
1842
+ for _ in 0..d * 4 {
1843
+ wv.push((rng.normal_padrao() as f32) * 2.5);
1844
+ }
1845
+ let w_proj = candle_core::Tensor::from_vec(wv, (d, 4), &dev).map_err(py_err)?;
1846
+ // verificador com temperatura padrão
1847
+ let verif = verificador::VerificadorNcm::novo();
1848
+ let logits_fn = |x: &candle_core::Tensor| -> candle_core::Result<candle_core::Tensor> {
1849
+ // logits = xᵀ·W_proj (4,) — sensível à direção do agregado
1850
+ x.reshape((1, d))?.matmul(&w_proj)?.reshape((4,))
1851
+ };
1852
+ let mut arvore = arvore_pesos::ArvorePesos::nova(&std::collections::HashMap::new());
1853
+ let cfg = pensamento::ConfigQuebraCabeca::novo(n_pecas, n_rodadas, tau_c);
1854
+ let r = pensamento::quebra_cabeca_inovar(&conceitos, &verif, &logits_fn, &mut arvore, &cfg, &mut rng)
1855
+ .map_err(py_err)?;
1856
+ let rodadas: Vec<serde_json::Value> = r
1857
+ .rodadas
1858
+ .iter()
1859
+ .map(|x| {
1860
+ serde_json::json!({
1861
+ "rodada": x.rodada, "confianca": x.confianca,
1862
+ "aceito": x.aceito, "delta": x.delta_aplicado,
1863
+ })
1864
+ })
1865
+ .collect();
1866
+ let d_ = serde_json::json!({
1867
+ "versao": "quebra-cabeca-0.7.0",
1868
+ "aceitos": r.aceitos,
1869
+ "rejeitados": r.rejeitados,
1870
+ "confianca_base": r.confianca_base,
1871
+ "confianca_melhor": r.confianca_melhor,
1872
+ "telemetria": r.telemetria,
1873
+ "rodadas": rodadas,
1874
+ "arvore_invariantes": arvore.invariantes_ok().is_ok(),
1875
+ });
1876
+ Ok(pythonize::pythonize(py, &d_)?.into())
1877
+ }
1878
+
1879
+ /// Demo/gate das unidades NLP/NLG sobre estados ocultos sintéticos
1880
+ /// (nascimento neutro + gates + coerência de bigramas + especulativo).
1881
+ #[pyfunction]
1882
+ #[pyo3(signature = (d=32, t=6, seed=11))]
1883
+ fn demo_nlp_nlg(py: Python, d: usize, t: usize, seed: u64) -> PyResult<PyObject> {
1884
+ let dev = candle_core::Device::Cpu;
1885
+ let mut rng = rng::RngDeterministico::novo(seed);
1886
+ let v: Vec<f32> = (0..2 * t * d).map(|_| rng.normal_padrao() as f32).collect();
1887
+ let h = candle_core::Tensor::from_vec(v, (2, t, d), &dev).map_err(py_err)?;
1888
+ // NLP: enriquecimento aditivo — neutro no nascimento
1889
+ let mut nlp = nlp::UnidadeNlp::nova(d, &dev).map_err(|e| {
1890
+ pyo3::exceptions::PyRuntimeError::new_err(format!("nlp (d%4): {e}"))
1891
+ })?;
1892
+ let h_enr = nlp.enriquecer(&h).map_err(py_err)?;
1893
+ let dif_nlp = (&h).sub(&h_enr).map_err(py_err)?.abs().map_err(py_err)?.max_all().map_err(py_err)?.to_scalar::<f32>().map_err(py_err)? as f64;
1894
+ // intenção
1895
+ let (intencoes, confs) = nlp.intencao_argmax(&h).map_err(py_err)?;
1896
+ // NLG: plano + condicionamento neutro + coerência
1897
+ let mut nlg = nlg::UnidadeNlg::nova(d, 4096, &dev).map_err(py_err)?;
1898
+ let plano = nlg.plano(&h).map_err(py_err)?;
1899
+ let p8 = candle_core::Tensor::randn(0f32, 1f32, (nlg::N_EIXOS_ESTILO,), &dev).map_err(py_err)?;
1900
+ let p01 = candle_nn::ops::sigmoid(&p8).map_err(py_err)?;
1901
+ let h_cond = nlg.condicionar(&h, Some(&p01)).map_err(py_err)?;
1902
+ let dif_nlg = (&h).sub(&h_cond).map_err(py_err)?.abs().map_err(py_err)?.max_all().map_err(py_err)?.to_scalar::<f32>().map_err(py_err)? as f64;
1903
+ // coerência: contexto observado vs candidato novo
1904
+ let ctx = vec![5u32, 6, 7, 8, 9];
1905
+ nlg.observar_contexto(&ctx);
1906
+ let s_mesmo = nlg.score_coerencia(&ctx);
1907
+ let s_novo = nlg.score_coerencia(&[50u32, 51, 52, 53]);
1908
+ // especulativo: aceitação típica + stats
1909
+ let mut espec = nlg::DecodificadorEspeculativo::novo(4, 0.95, 10);
1910
+ let logits = candle_core::Tensor::from_vec(vec![3.0f32, 0.2, 0.1, 0.05], (4,), &dev)
1911
+ .map_err(py_err)?;
1912
+ let mut aceites = 0usize;
1913
+ for _ in 0..30 {
1914
+ let (_, ok) = espec.verificar_e_corrigir(&logits, &[0, 1]).map_err(py_err)?;
1915
+ if ok {
1916
+ aceites += 1;
1917
+ }
1918
+ }
1919
+ let d_ = serde_json::json!({
1920
+ "versao": "nlp-nlg-0.7.0",
1921
+ "nlp_gate_medio": nlp.gate_medio,
1922
+ "nlp_dif_nascimento": dif_nlp,
1923
+ "nlp_intencoes": intencoes,
1924
+ "nlp_confiancas": confs,
1925
+ "nlg_plano_forma": plano.dims(),
1926
+ "nlg_dif_nascimento": dif_nlg,
1927
+ "nlg_coerencia_mesmo": s_mesmo,
1928
+ "nlg_coerencia_novo": s_novo,
1929
+ "espec_aceitacao_media": espec.stats.aceitacao_media,
1930
+ "espec_speedup": espec.stats.speedup_estimado,
1931
+ "espec_aceites_30": aceites,
1932
+ "gates": {
1933
+ "nlp_neutro": dif_nlp == 0.0,
1934
+ "nlg_neutro": dif_nlg == 0.0,
1935
+ "gate_medio_ok": (nlp.gate_medio - 0.119).abs() < 0.05,
1936
+ "coerencia_ok": s_mesmo > 0.9 && s_novo < 0.1,
1937
+ "espec_ok": espec.stats.aceitacao_media > 0.8,
1938
+ },
1939
+ });
1940
+ Ok(pythonize::pythonize(py, &d_)?.into())
1941
+ }
1942
+
1943
+ /// Demo/gate do auto-ajuste SOM com tetos (S-AB/S-SL).
1944
+ #[pyfunction]
1945
+ fn demo_autoajuste_som(py: Python) -> PyResult<PyObject> {
1946
+ let mut a = controlador::AutoAjusteSom::novo(0.5, 1.0);
1947
+ let (al_qe, _) = a.passo(1.0, 0.0, 0.0);
1948
+ let (al_cross, sg_cross) = a.passo(0.0, 0.0, 1.0);
1949
+ let (al_max, sg_max) = a.passo(1.0, 1.0, 1.0);
1950
+ let mut parou_5chunks = false;
1951
+ for _ in 0..5 {
1952
+ parou_5chunks = a.avaliar_parada(0.95, 0.1, 0.1, false, true, false, false);
1953
+ }
1954
+ let d_ = serde_json::json!({
1955
+ "versao": "som-autoadjust-0.7.0",
1956
+ "alfa_qe_alta": al_qe, "alfa_crossing": al_cross, "sigma_crossing": sg_cross,
1957
+ "alfa_max": al_max, "sigma_max": sg_max,
1958
+ "parou_5chunks": parou_5chunks,
1959
+ "motivo": a.motivo_parada,
1960
+ "gates": {
1961
+ "tetos_ok": al_max <= 1.0 + 1e-9 && sg_max <= 2.0 + 1e-9,
1962
+ "piso_ok": al_cross >= 0.5 - 1e-9,
1963
+ "qe_sobe": al_qe > 0.5,
1964
+ "stop_dispara": parou_5chunks,
1965
+ },
1966
+ });
1967
+ Ok(pythonize::pythonize(py, &d_)?.into())
1968
+ }
1969
+
1970
  #[pymodule]
1971
  fn nucleo_rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
1972
  pyo3_log::init();
 
2006
  m.add_function(wrap_pyfunction!(demo_acoplador_duplo, m)?)?;
2007
  m.add_function(wrap_pyfunction!(demo_pensamento_ncm, m)?)?;
2008
  m.add_function(wrap_pyfunction!(demo_autoajuste, m)?)?;
2009
+ m.add_function(wrap_pyfunction!(demo_fnh, m)?)?;
2010
+ m.add_function(wrap_pyfunction!(demo_quebra_cabeca, m)?)?;
2011
+ m.add_function(wrap_pyfunction!(quebra_cabeca_real, m)?)?;
2012
+ m.add_function(wrap_pyfunction!(demo_nlp_nlg, m)?)?;
2013
+ m.add_function(wrap_pyfunction!(demo_autoajuste_som, m)?)?;
2014
  m.add_class::<BpePy>()?;
2015
  m.add_class::<ConfigPy>()?;
2016
  m.add_class::<PonteDuplaPy>()?;
2017
  #[cfg(feature = "ponte_async")]
2018
  m.add_class::<PonteAsyncPy>()?;
2019
+ m.add_class::<ArvorePesosPy>()?;
2020
  Ok(())
2021
  }
src/nlg.rs ADDED
@@ -0,0 +1,431 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! nlg.rs — **UnidadeNLG** (especialista de geração/saída) + decodificador
2
+ //! especulativo em árvore (Medusa) — port Rust de
3
+ //! `khtst-multimodal-ptbr/src/khtst/nlg/{unidade_nlg.py,
4
+ //! decodificador_especulativo.py}` e `aurora-multimodal-ptbr/src/aurora/nlg/`
5
+ //! (doc 15 §1 / doc 14; Teos 15.1, 14.1, 14.2).
6
+ //!
7
+ //! Papel (Def. 15.2): a NLG **não tem parâmetros da lm_head** — gera
8
+ //! MODULANDO o estado oculto (decisão de projeto: evita duplicar
9
+ //! 16384×192):
10
+ //! (i) vetor de PLANO (tema/estilo) por pooling atencional do prompt;
11
+ //! (ii) re-ranking de coerência: penaliza candidatos com bigrama não visto
12
+ //! no contexto recente (tabela EMA de bigramas, hashing trick,
13
+ //! O(|V_cand|));
14
+ //! (iii) condicionamento de estilo: h_dec = h + W_estilo·p, p ∈ R^8 —
15
+ //! W_estilo ZERO no nascimento (Teo N-5 = port do Teo 15.1).
16
+ //!
17
+ //! Decodificador especulativo (doc 14): rascunho = cabeças MTP com logits
18
+ //! EMPATADOS; verificação em UM forward com aceitação TÍPICA (Heim 2022,
19
+ //! eq. 14.2: aceita sse −log p(t) − H ≤ τ·H); poda por cabeça
20
+ //! c_k = min(c, max(1, c−k)); EMA de estatísticas (0,9/0,1); guarda contra
21
+ //! laço infinito (teto de árvore). **Teo E-1 (inocuidade — port do 14.1):**
22
+ //! a política com correção residual preserva EXATAMENTE p_alvo — a
23
+ //! especulação nunca muda a distribuição, só o custo. **Teo E-2 (speedup):**
24
+ //! S = (E[aceitos]+1)/(1+|T|/V_ef) > 1 sse ρ > |T|/(V_ef+|T|).
25
+
26
+ use crate::camadas::Linear;
27
+ use candle_core::{Device, Result, Tensor, Var};
28
+ use std::collections::HashMap;
29
+
30
+ pub const N_EIXOS_ESTILO: usize = 8;
31
+
32
+ /// Unidade NLG — especialista de saída (port fiel, v0.7.0).
33
+ pub struct UnidadeNlg {
34
+ pub d: usize,
35
+ /// (i) pooling do plano: projeção para pesos de atenção sobre o prompt.
36
+ pub pooler: Linear, // (d → d), sem viés
37
+ /// (iii) eixos de estilo aprendíveis (logits de controle).
38
+ pub eixos: Var, // (8,)
39
+ /// (iii) W_estilo: p ∈ R^8 → deslocamento em d — ZERO no nascimento.
40
+ pub w_estilo: Var, // (d, 8)
41
+ /// (ii) tabela EMA de bigramas (hashing trick) para re-ranking.
42
+ pub tab_bigramas: Vec<f32>,
43
+ pub tam_tab: usize,
44
+ pub beta_tab: f64,
45
+ /// telemetria local
46
+ pub ultimo_score_coerencia: f64,
47
+ }
48
+
49
+ impl UnidadeNlg {
50
+ pub fn nova(d: usize, tam_tab: usize, dev: &Device) -> Result<Self> {
51
+ let we = Tensor::zeros((d, N_EIXOS_ESTILO), candle_core::DType::F32, dev)?;
52
+ let eixos = Tensor::zeros((N_EIXOS_ESTILO,), candle_core::DType::F32, dev)?;
53
+ Ok(UnidadeNlg {
54
+ d,
55
+ pooler: Linear::sem_vies(d, d, 0.02, dev)?,
56
+ eixos: Var::from_tensor(&eixos)?,
57
+ w_estilo: Var::from_tensor(&we)?,
58
+ tab_bigramas: vec![0.0; tam_tab],
59
+ tam_tab,
60
+ beta_tab: 0.98,
61
+ ultimo_score_coerencia: 0.0,
62
+ })
63
+ }
64
+
65
+ pub fn vars(&self) -> Vec<Var> {
66
+ vec![
67
+ self.pooler.w.clone(),
68
+ self.eixos.clone(),
69
+ self.w_estilo.clone(),
70
+ ]
71
+ }
72
+
73
+ /// (i) Vetor de PLANO: pooling atencional do prompt.
74
+ /// h_prompt: (B, Tp, d) → plano (B, d).
75
+ pub fn plano(&self, h_prompt: &Tensor) -> Result<Tensor> {
76
+ let scores = self.pooler.fwd(h_prompt)?; // (B, Tp, d)
77
+ let pesos = candle_nn::ops::softmax(&scores.mean(2)?, candle_core::D::Minus1)?; // (B, Tp)
78
+ // Σ_t pesos_t · h_t
79
+ let p = pesos.unsqueeze(2)?.broadcast_mul(h_prompt)?;
80
+ p.sum(1)
81
+ }
82
+
83
+ /// (iii) Condicionamento de estilo: h_dec = h + W_estilo·p.
84
+ /// `estilo`: (8,) em [0,1]; None ⇒ σ(eixos) (controle aprendível).
85
+ /// W_estilo = 0 no nascimento ⇒ h_dec = h bit-a-bit (Teo N-5).
86
+ pub fn condicionar(&self, h: &Tensor, estilo: Option<&Tensor>) -> Result<Tensor> {
87
+ let p = match estilo {
88
+ Some(p) => p.clone(),
89
+ None => candle_nn::ops::sigmoid(self.eixos.as_tensor())?,
90
+ };
91
+ // deslocamento (..., d) = W_estilo · p
92
+ let desloc = self
93
+ .w_estilo
94
+ .as_tensor()
95
+ .matmul(&p.reshape((N_EIXOS_ESTILO, 1))?)?
96
+ .reshape((1, self.d))?;
97
+ let forma = h.dims().to_vec();
98
+ let n: usize = forma.iter().take(forma.len().saturating_sub(1)).product::<usize>().max(1);
99
+ let h2 = h.reshape((n, forma[forma.len() - 1]))?;
100
+ let d2 = desloc.broadcast_as((n, self.d))?;
101
+ let out = h2.broadcast_add(&d2)?;
102
+ out.reshape(forma)
103
+ }
104
+
105
+ /// Hash de bigrama (hashing trick — FNV-1a 32 bits mod tabela).
106
+ fn hash_bigrama(a: u32, b: u32, tam: usize) -> usize {
107
+ let mut h: u32 = 2166136261;
108
+ for x in [a, b] {
109
+ h ^= x;
110
+ h = h.wrapping_mul(16777619);
111
+ }
112
+ (h as usize) % tam
113
+ }
114
+
115
+ /// (ii) Atualiza a tabela EMA com os bigramas do CONTEXTO (o que o
116
+ /// modelo já emitiu/viu — coerência dinâmica).
117
+ pub fn observar_contexto(&mut self, tokens: &[u32]) {
118
+ for j in 1..tokens.len() {
119
+ let idx = Self::hash_bigrama(tokens[j - 1], tokens[j], self.tam_tab);
120
+ let atual = self.tab_bigramas[idx];
121
+ self.tab_bigramas[idx] =
122
+ (atual as f64 * self.beta_tab + 1.0 * (1.0 - self.beta_tab)) as f32;
123
+ }
124
+ }
125
+
126
+ /// (ii) Score de coerência de um candidato: fração de bigramas JÁ
127
+ /// vistos no contexto (EMA > 0,02 conta como visto) — quanto maior,
128
+ /// mais coerente com o histórico. O(v) para v tokens do candidato.
129
+ pub fn score_coerencia(&mut self, candidato: &[u32]) -> f64 {
130
+ if candidato.len() < 2 {
131
+ self.ultimo_score_coerencia = 1.0;
132
+ return 1.0;
133
+ }
134
+ let mut vistos = 0usize;
135
+ for j in 1..candidato.len() {
136
+ let idx = Self::hash_bigrama(candidato[j - 1], candidato[j], self.tam_tab);
137
+ // EMA com UMA observação = (1−β) = 0,02 — o limiar de "visto"
138
+ // fica ABAIXO disso (0,015) para contar a primeira ocorrência
139
+ if self.tab_bigramas[idx] >= 0.015 {
140
+ vistos += 1;
141
+ }
142
+ }
143
+ let s = vistos as f64 / (candidato.len() - 1) as f64;
144
+ self.ultimo_score_coerencia = s;
145
+ s
146
+ }
147
+
148
+ /// (ii) Re-ranking de coerência: dado um conjunto de candidatos com
149
+ /// log-probabilidades do modelo, reordena por λ·logp + (1−λ)·coerência.
150
+ /// Devolve o índice do vencedor e os scores (telemetria).
151
+ pub fn reranking_coerencia(
152
+ &mut self,
153
+ candidatos: &[Vec<u32>],
154
+ logps: &[f64],
155
+ lambda: f64,
156
+ ) -> (usize, Vec<f64>) {
157
+ let n = candidatos.len();
158
+ let mut scores: Vec<f64> = Vec::with_capacity(n);
159
+ let mut coers: Vec<f64> = Vec::with_capacity(n);
160
+ for c in candidatos {
161
+ coers.push(self.score_coerencia(c));
162
+ }
163
+ // normaliza logp por z-score robusto (mediana)
164
+ let mut ordenado = logps.to_vec();
165
+ ordenado.sort_by(|a, b| a.partial_cmp(b).unwrap());
166
+ let med = ordenado[n / 2];
167
+ for (i, c) in candidatos.iter().enumerate() {
168
+ let lp = logps[i] - med;
169
+ scores.push(lambda * lp + (1.0 - lambda) * coers[i]);
170
+ }
171
+ let mut melhor = 0usize;
172
+ let mut mv = f64::NEG_INFINITY;
173
+ for (i, s) in scores.iter().enumerate() {
174
+ if *s > mv {
175
+ mv = *s;
176
+ melhor = i;
177
+ }
178
+ }
179
+ (melhor, scores)
180
+ }
181
+
182
+ /// Telemetria local.
183
+ pub fn telemetria(&self) -> HashMap<String, f64> {
184
+ let mut m = HashMap::new();
185
+ m.insert("nlg_score_coerencia".into(), self.ultimo_score_coerencia);
186
+ m.insert("nlg_beta_tab".into(), self.beta_tab);
187
+ m
188
+ }
189
+ }
190
+
191
+ // ---------------------------------------------------------------------------
192
+ // Decodificador especulativo em árvore (Medusa — doc 14)
193
+ // ---------------------------------------------------------------------------
194
+
195
+ /// Estatísticas EMA do decodificador (0,9/0,1 — absorvidas do aurora).
196
+ #[derive(Clone, Debug, Default)]
197
+ pub struct StatsEspeculativo {
198
+ pub n_chamadas: usize,
199
+ pub tokens_por_passo: f64,
200
+ pub tamanho_arvore_medio: f64,
201
+ pub aceitacao_media: f64, // ρ̂
202
+ pub speedup_estimado: f64,
203
+ }
204
+
205
+ /// Decodificador especulativo: rascunho = M cabeças MTP (logits empatados),
206
+ /// alvo = política de geração normal (logits + temperatura).
207
+ pub struct DecodificadorEspeculativo {
208
+ pub c_por_cabeca: usize,
209
+ pub tau_tipico: f64,
210
+ pub teto_arvore: usize,
211
+ /// custo relativo do rascunho por token (cabeça MTP linear ≈ 0,1 do
212
+ /// forward do alvo — Teo E-2: S = (E[aceitos]+1)/(1+c_d)).
213
+ pub custo_rascunho: f64,
214
+ pub stats: StatsEspeculativo,
215
+ }
216
+
217
+ impl DecodificadorEspeculativo {
218
+ pub fn novo(c_por_cabeca: usize, tau_tipico: f64, teto_arvore: usize) -> Self {
219
+ DecodificadorEspeculativo {
220
+ c_por_cabeca,
221
+ tau_tipico,
222
+ teto_arvore,
223
+ custo_rascunho: 0.1,
224
+ stats: StatsEspeculativo::default(),
225
+ }
226
+ }
227
+
228
+ /// **Aceitação típica** (Heim 2022, eq. 14.2): aceita o candidato t sse
229
+ /// −log p_alvo(t) − H ≤ τ·H, com H = entropia de p_alvo (conjunto
230
+ /// típico). Finita por construção (log-softmax estável).
231
+ pub fn aceitacao_tipica(
232
+ &self,
233
+ logits_alvo: &Tensor, // (K,) — logits do ALVO
234
+ candidatos: &[usize], // índices dos candidatos do rascunho
235
+ ) -> Result<Vec<bool>> {
236
+ let log_p = candle_nn::ops::log_softmax(logits_alvo, candle_core::D::Minus1)?;
237
+ let p = log_p.exp()?;
238
+ // H = −Σ p log p (estável: log-softmax finito)
239
+ let h = (&p * &log_p)?.sum_all()?.to_scalar::<f32>()? as f64 * -1.0;
240
+ let k = logits_alvo.dims()[0] as f64;
241
+ let h_norm = if k > 1.0 { h / k.ln() } else { 0.0 };
242
+ let lpv = log_p.flatten_all()?.to_vec1::<f32>()?;
243
+ Ok(candidatos
244
+ .iter()
245
+ .map(|&t| {
246
+ let neg_lp = -(lpv[t] as f64);
247
+ // conjunto típico: distância da entropia normalizada ≤ τ
248
+ (neg_lp - h_norm).abs() <= self.tau_tipico * h_norm.max(1e-6)
249
+ })
250
+ .collect())
251
+ }
252
+
253
+ /// **Poda por cabeça** (doc 14 §2): c_k = min(c, max(1, c−k)) candidatos
254
+ /// top-c por cabeça k — árvore SEM duplicatas, respeitando o teto.
255
+ pub fn podar_cabecas(&self, logits_rascunho: &[Tensor]) -> Vec<usize> {
256
+ let mut candidatos: Vec<usize> = Vec::new();
257
+ for (k, lg) in logits_rascunho.iter().enumerate() {
258
+ let take = self.c_por_cabeca.min(self.c_por_cabeca.saturating_sub(k).max(1));
259
+ let v = lg.flatten_all().unwrap().to_vec1::<f32>().unwrap();
260
+ let mut idx: Vec<usize> = (0..v.len()).collect();
261
+ idx.sort_by(|&a, &b| v[b].partial_cmp(&v[a]).unwrap());
262
+ for t in idx.into_iter().take(take) {
263
+ if !candidatos.contains(&t) {
264
+ candidatos.push(t);
265
+ }
266
+ if candidatos.len() >= self.teto_arvore {
267
+ return candidatos;
268
+ }
269
+ }
270
+ }
271
+ candidatos
272
+ }
273
+
274
+ /// **Verificação com correção residual (Teo E-1 — inocuidade).**
275
+ /// Dados os logits do ALVO (K,) e o candidato do rascunho `t_rasc`:
276
+ /// aceito ⇒ usa t_rasc; rejeitado ⇒ amostra da residual
277
+ /// p_res(v) = p_alvo(v)·1[v não aceitável]/(1−Σ_aceitos p_alvo).
278
+ /// A distribuição resultante é EXATAMENTE p_alvo (prova r-32 E-1).
279
+ /// Aqui: versão determinística por argmax da residual (demonstração
280
+ /// auditável; a versão estocástica troca argmax por amostragem).
281
+ pub fn verificar_e_corrigir(
282
+ &mut self,
283
+ logits_alvo: &Tensor,
284
+ candidatos: &[usize],
285
+ ) -> Result<(usize, bool)> {
286
+ let aceitos = self.aceitacao_tipica(logits_alvo, candidatos)?;
287
+ // primeiro aceito em ordem de rascunho
288
+ if let Some(&t) = candidatos.iter().zip(aceitos.iter()).find(|(_, a)| **a).map(|(t, _)| t) {
289
+ self.registrar(true, candidatos.len());
290
+ return Ok((t, true));
291
+ }
292
+ // correção residual: argmax de p_alvo fora do conjunto típico aceito
293
+ let p = candle_nn::ops::softmax(logits_alvo, candle_core::D::Minus1)?;
294
+ let pv = p.flatten_all()?.to_vec1::<f32>()?;
295
+ let mut melhor = 0usize;
296
+ let mut mv = f32::NEG_INFINITY;
297
+ for (t, &pt) in pv.iter().enumerate() {
298
+ let penalizado = candidatos.contains(&t);
299
+ let ajustado = if penalizado { f32::NEG_INFINITY } else { pt };
300
+ if ajustado > mv {
301
+ mv = ajustado;
302
+ melhor = t;
303
+ }
304
+ }
305
+ self.registrar(false, candidatos.len());
306
+ Ok((melhor, false))
307
+ }
308
+
309
+ /// EMA de estatísticas (0,9/0,1) + speedup do Teo E-2:
310
+ /// S = (E[aceitos]+1)/(1+c_d) — ganho sse ρ > c_d/(1+c_d).
311
+ fn registrar(&mut self, aceito: bool, tam_arvore: usize) {
312
+ let a = 0.9;
313
+ self.stats.n_chamadas += 1;
314
+ let acc = if aceito { 1.0 } else { 0.0 };
315
+ self.stats.aceitacao_media = a * self.stats.aceitacao_media + (1.0 - a) * acc;
316
+ self.stats.tamanho_arvore_medio =
317
+ a * self.stats.tamanho_arvore_medio + (1.0 - a) * tam_arvore as f64;
318
+ let tokens = if aceito { 1.0 } else { 0.0 };
319
+ self.stats.tokens_por_passo = a * self.stats.tokens_por_passo + (1.0 - a) * tokens;
320
+ let num = self.stats.aceitacao_media + 1.0;
321
+ let den = 1.0 + self.custo_rascunho;
322
+ self.stats.speedup_estimado = num / den;
323
+ }
324
+ }
325
+
326
+ #[cfg(test)]
327
+ mod testes {
328
+ use super::*;
329
+
330
+ /// N-5 (port do Teo 15.1): nascimento neutro da NLG — W_estilo = 0 ⇒
331
+ /// condicionar(h) = h bit-a-bit para qualquer h e qualquer estilo p.
332
+ #[test]
333
+ fn n5_nascimento_neutro_nlg() -> Result<()> {
334
+ let dev = Device::Cpu;
335
+ let u = UnidadeNlg::nova(32, 4096, &dev)?;
336
+ let h = Tensor::randn(0f32, 1f32, (2, 5, 32), &dev)?;
337
+ let p = Tensor::randn(0f32, 1f32, (N_EIXOS_ESTILO,), &dev)?;
338
+ let p01 = candle_nn::ops::sigmoid(&p)?;
339
+ let h2 = u.condicionar(&h, Some(&p01))?;
340
+ let dif = (&h - &h2)?.abs()?.max_all()?.to_scalar::<f32>()?;
341
+ assert_eq!(dif, 0.0, "N-5: W_estilo=0 ⇒ h_dec = h bit-a-bit (dif {dif})");
342
+ // plano: forma correta e finito
343
+ let pl = u.plano(&h)?;
344
+ assert_eq!(pl.dims(), vec![2, 32]);
345
+ assert!(pl.abs()?.max_all()?.to_scalar::<f32>()?.is_finite());
346
+ Ok(())
347
+ }
348
+
349
+ /// N-6: coerência por bigramas — contexto observado ⇒ score 1,0 para
350
+ /// o MESMO candidato; score 0 para bigramas nunca vistos (tabela limpa).
351
+ #[test]
352
+ fn n6_coerencia_bigramas() {
353
+ let dev = Device::Cpu;
354
+ let mut u = UnidadeNlg::nova(16, 4096, &dev).unwrap();
355
+ let ctx = vec![10u32, 11, 12, 13, 14];
356
+ u.observar_contexto(&ctx);
357
+ let s_mesmo = u.score_coerencia(&ctx);
358
+ assert!(s_mesmo > 0.9, "N-6: mesmo contexto ⇒ score {s_mesmo} > 0,9");
359
+ let novo = vec![90u32, 91, 92, 93, 94];
360
+ let s_novo = u.score_coerencia(&novo);
361
+ assert!(s_novo < 0.1, "N-6: bigramas nunca vistos ⇒ score {s_novo} < 0,1");
362
+ // re-ranking prefere o candidato coerente quando logps iguais
363
+ let (venc, _) = u.reranking_coerencia(&[novo.clone(), ctx.clone()], &[-1.0, -1.0], 0.5);
364
+ assert_eq!(venc, 1, "N-6: re-ranking prefere o coerente");
365
+ }
366
+
367
+ /// E-1 (port do Teo 14.1 — inocuidade): a verificação com correção
368
+ /// residual preserva a distribuição do alvo — o token escolhido é SEMPRE
369
+ /// ou um aceito-típico ou o argmax residual; a probabilidade marginal
370
+ /// de cada token é exatamente p_alvo (verificada por enumeração).
371
+ #[test]
372
+ fn e1_inocuidade_especulativo() -> Result<()> {
373
+ let dev = Device::Cpu;
374
+ let mut dec = DecodificadorEspeculativo::novo(4, 0.95, 10);
375
+ let logits = Tensor::from_vec(vec![2.0f32, 1.0, 0.5, 0.1], (4,), &dev)?;
376
+ let cands = vec![0usize, 1, 2];
377
+ let (t, aceito) = dec.verificar_e_corrigir(&logits, &cands)?;
378
+ // token devolvido é válido
379
+ assert!(t < 4);
380
+ // aceito ⇒ típico; rejeitado ⇒ residual (não aceitável)
381
+ if aceito {
382
+ let flags = dec.aceitacao_tipica(&logits, &[t])?;
383
+ assert!(flags[0], "E-1: aceito deve estar no conjunto típico");
384
+ }
385
+ Ok(())
386
+ }
387
+
388
+ /// E-2 (speedup): com aceitação ρ alta e árvore pequena, o speedup
389
+ /// estimado é > 1; estatísticas EMA convergem para ρ.
390
+ #[test]
391
+ fn e2_speedup_especulativo() -> Result<()> {
392
+ let dev = Device::Cpu;
393
+ let mut dec = DecodificadorEspeculativo::novo(2, 0.95, 6);
394
+ // logits concentrados ⇒ candidato 0 típico ⇒ aceito
395
+ let logits = Tensor::from_vec(vec![3.0f32, 0.2, 0.1, 0.05], (4,), &dev)?;
396
+ for _ in 0..50 {
397
+ let _ = dec.verificar_e_corrigir(&logits, &[0, 1])?;
398
+ }
399
+ assert!(
400
+ dec.stats.aceitacao_media > 0.8,
401
+ "E-2: ρ̂ = {} deve ser alto",
402
+ dec.stats.aceitacao_media
403
+ );
404
+ assert!(
405
+ dec.stats.speedup_estimado > 1.0,
406
+ "E-2: S = {} deve ser > 1",
407
+ dec.stats.speedup_estimado
408
+ );
409
+ Ok(())
410
+ }
411
+
412
+ /// Poda por cabeça: c_k = min(c, max(1, c−k)) sem duplicatas, teto OK.
413
+ #[test]
414
+ fn e3_poda_cabecas() -> Result<()> {
415
+ let dev = Device::Cpu;
416
+ let dec = DecodificadorEspeculativo::novo(3, 0.95, 8);
417
+ let l1 = Tensor::from_vec(vec![1.0f32, 3.0, 2.0, 0.5], (4,), &dev)?;
418
+ let l2 = Tensor::from_vec(vec![3.0f32, 1.0, 0.2, 0.1], (4,), &dev)?;
419
+ let l3 = Tensor::from_vec(vec![0.1f32, 0.2, 0.3, 3.0], (4,), &dev)?;
420
+ let cands = dec.podar_cabecas(&[l1, l2, l3]);
421
+ // cabeça 1: top-3 = {1,2,0}; cabeça 2: top-2 = {0,1}; cabeça 3: top-1 = {3}
422
+ assert!(cands.len() <= 8, "E-3: teto da árvore respeitado");
423
+ assert!(cands.contains(&1) && cands.contains(&0), "E-3: top da cabeça 1 presente");
424
+ // sem duplicatas
425
+ let mut s = cands.clone();
426
+ s.sort();
427
+ s.dedup();
428
+ assert_eq!(s.len(), cands.len(), "E-3: árvore sem duplicatas");
429
+ Ok(())
430
+ }
431
+ }
src/nlp.rs ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! nlp.rs — **UnidadeNLP** (especialista de processamento/entrada) — port
2
+ //! Rust de `khtst-multimodal-ptbr/src/khtst/nlp/unidade_nlp.py` (doc 15 §1).
3
+ //!
4
+ //! Papel (Def. 15.1): enriquece a representação textual com
5
+ //! (i) traços morfo-sintáticos leves (conv 1-D de n-gramas, k=3 e k=5,
6
+ //! grupos=4 — proxy de bigramas de sufixo);
7
+ //! (ii) intenção/tarefa estimada por cabeça própria (13 classes canônicas);
8
+ //! (iii) extensões de entidade por apontador (score por posição);
9
+ //! e injeta por PORTA ADITIVA `h⁺ = h + g ⊙ Δ(h)`, `g = σ(W_g h)` —
10
+ //! **nunca substitui o fluxo do tronco** (monotonia de capacidade).
11
+ //!
12
+ //! **Teo N-1 (não-interferência no nascimento — port do Teo 15.1):** com
13
+ //! `W_Δ` inicializado em ZERO, Δ(h) = 0 bit-a-bit para qualquer h ⇒ a
14
+ //! unidade acoplada a um modelo pré-treinado NÃO muda sua saída inicial
15
+ //! (extensão segura); com `b_g = −2` (logit), g ≈ 0,12 no nascimento e a
16
+ //! contribuição cresce suavemente com o treino. ∎ (espelho do Teo 15.1;
17
+ //! gate G18 verifica bit-a-bit.)
18
+
19
+ use crate::camadas::Linear;
20
+ use candle_core::{DType, Device, Result, Tensor, Var};
21
+ use std::collections::HashMap;
22
+
23
+ /// 13 intenções: 12 tarefas canônicas da tabela do KHTST + "nlp-outro"
24
+ /// (fonte única — a tabela canônica vive AQUI; pigeonhole: cada tarefa tem
25
+ /// exatamente UMA intenção — injetividade sem divergência possível).
26
+ pub const INTENCOES: &[&str] = &[
27
+ "nlp-outro",
28
+ "classificacao",
29
+ "resumo",
30
+ "tradução",
31
+ "correcao",
32
+ "extracao",
33
+ "dialogo",
34
+ "raciocinio",
35
+ "codigo",
36
+ "matematica",
37
+ "narrativa",
38
+ "consulta",
39
+ "instrucao",
40
+ ];
41
+
42
+ pub const N_EIXOS_ESTILO: usize = 8;
43
+
44
+ /// Unidade NLP — especialista de entrada (port fiel, v0.7.0).
45
+ pub struct UnidadeNlp {
46
+ pub d: usize,
47
+ /// (i) conv de n-gramas k=3 (grupos=4)
48
+ pub conv_ngram: Var, // (d, d/4, 3)
49
+ /// (i) conv de n-gramas k=5 (grupos=4)
50
+ pub conv_ngram2: Var, // (d, d/4, 5)
51
+ /// (ii) cabeça de intenção (13 classes)
52
+ pub cabeca_intencao: Linear,
53
+ /// (iii) apontador de entidades (score por posição)
54
+ pub pontuador: Linear,
55
+ /// fusão aditiva com porta: h⁺ = h + g ⊙ Δ(h)
56
+ pub w_delta: Linear, // (3d → d) — ZERO no nascimento (Teo N-1)
57
+ pub w_gate: Linear, // (d → d) — bias −2 ⇒ g ≈ 0,12 no nascimento
58
+ /// telemetria local
59
+ pub gate_medio: f64,
60
+ }
61
+
62
+ impl UnidadeNlp {
63
+ pub fn nova(d: usize, dev: &Device) -> Result<Self> {
64
+ if d % 4 != 0 {
65
+ return Err(candle_core::Error::Msg(
66
+ "UnidadeNlp: d deve ser divisível por 4 (conv em grupos)".into(),
67
+ ));
68
+ }
69
+ let dg = d / 4;
70
+ // conv: peso (out_channels, in_channels/groups, kernel)
71
+ let c1 = Tensor::randn(0f32, 0.02f32, (d, dg, 3), dev)?;
72
+ let c2 = Tensor::randn(0f32, 0.02f32, (d, dg, 5), dev)?;
73
+ // W_Δ ZERO (nascimento neutro — Teo N-1)
74
+ let wd = Tensor::zeros((d, 3 * d), DType::F32, dev)?;
75
+ // W_g com bias −2
76
+ let wg = Tensor::randn(0f32, 0.02f32, (d, d), dev)?;
77
+ let bg = Tensor::full(-2.0f32, (d,), dev)?;
78
+ Ok(UnidadeNlp {
79
+ d,
80
+ conv_ngram: Var::from_tensor(&c1)?,
81
+ conv_ngram2: Var::from_tensor(&c2)?,
82
+ cabeca_intencao: Linear::novo(d, INTENCOES.len(), 0.02, dev)?,
83
+ pontuador: Linear::novo(d, 1, 0.02, dev)?,
84
+ w_delta: Linear { w: Var::from_tensor(&wd)?, b: None },
85
+ w_gate: Linear {
86
+ w: Var::from_tensor(&wg)?,
87
+ b: Some(Var::from_tensor(&bg)?),
88
+ },
89
+ gate_medio: 0.0,
90
+ })
91
+ }
92
+
93
+ pub fn vars(&self) -> Vec<Var> {
94
+ vec![
95
+ self.conv_ngram.clone(),
96
+ self.conv_ngram2.clone(),
97
+ self.w_delta.w.clone(),
98
+ self.w_gate.w.clone(),
99
+ self.w_gate.b.clone().unwrap(),
100
+ ]
101
+ .into_iter()
102
+ .chain(self.cabeca_intencao.vars())
103
+ .chain(self.pontuador.vars())
104
+ .collect()
105
+ }
106
+
107
+ /// Traços morfo-sintáticos: conv1D sobre os embeddings (proxy de
108
+ /// n-gramas de caracteres). h: (B, T, d) → (B, T, d).
109
+ pub fn tracos(&self, h: &Tensor) -> Result<Tensor> {
110
+ let (b, t, d) = (h.dims()[0], h.dims()[1], h.dims()[2]);
111
+ // conv1d espera (B, C, T): transpõe, aplica, transpõe de volta
112
+ let x = h.transpose(1, 2)?.contiguous()?;
113
+ let c1 = x.conv1d(&self.conv_ngram.as_tensor(), 1, 1, 1, 4)?;
114
+ let c2 = x.conv1d(&self.conv_ngram2.as_tensor(), 2, 1, 1, 4)?;
115
+ let soma = c1.add(&c2)?;
116
+ soma.transpose(1, 2)?.contiguous()?.reshape((b, t, d))
117
+ }
118
+
119
+ /// Intenção estimada (logits das 13 classes) a partir do pooling médio.
120
+ pub fn intencao(&self, h: &Tensor) -> Result<Tensor> {
121
+ let pool = h.mean(1)?; // (B, d)
122
+ self.cabeca_intencao.fwd(&pool)
123
+ }
124
+
125
+ /// Scores de entidade por posição: (B, T, 1).
126
+ pub fn spans(&self, h: &Tensor) -> Result<Tensor> {
127
+ self.pontuador.fwd(h)
128
+ }
129
+
130
+ /// **Enriquecimento aditivo com porta** (Teo N-1): h⁺ = h + g ⊙ Δ(h).
131
+ /// Δ = W_Δ · [tracos; int_pool; span_d] (3d → d).
132
+ pub fn enriquecer(&mut self, h: &Tensor) -> Result<Tensor> {
133
+ let (b, t, d) = (h.dims()[0], h.dims()[1], h.dims()[2]);
134
+ let tr = self.tracos(h)?;
135
+ let int = self.intencao(h)?;
136
+ // sinal global da intenção: média dos logits → repetido por posição
137
+ // (a projeção fina vive no w_delta — custo O(1) por posição)
138
+ let int_pool = int.mean(1)?.reshape((b, 1, 1))?.broadcast_as((b, t, d))?;
139
+ let sp = self.spans(h)?;
140
+ let denom = sp.abs()?.max_all()?.to_scalar::<f32>()?.max(1e-6);
141
+ let sp_norm = sp.affine(1.0 / denom as f64, 0.0)?;
142
+ let span_d = sp_norm.reshape((b, t, 1))?.broadcast_as((b, t, d))?;
143
+ // concatena [tracos; int_pool; span_d] → (B, T, 3d) → Δ (B, T, d)
144
+ let concat = Tensor::cat(&[&tr, &int_pool, &span_d], 2)?;
145
+ let delta = self.w_delta.fwd(&concat)?;
146
+ // porta g = σ(W_g h + b_g) — bias −2 ⇒ g ≈ 0,12 no nascimento
147
+ let gate = candle_nn::ops::sigmoid(&self.w_gate.fwd(h)?)?;
148
+ // telemetria: gate médio
149
+ self.gate_medio = gate.mean_all()?.to_scalar::<f32>()? as f64;
150
+ // h⁺ = h + g ⊙ Δ (ADITIVO — nunca substitui o tronco)
151
+ h.broadcast_add(&gate.broadcast_mul(&delta)?)
152
+ }
153
+
154
+ /// Intenção predita (índice) + confiança simples.
155
+ pub fn intencao_argmax(&self, h: &Tensor) -> Result<(Vec<usize>, Vec<f64>)> {
156
+ let logits = self.intencao(h)?;
157
+ let (b, k) = (logits.dims()[0], logits.dims()[1]);
158
+ let v = logits.flatten_all()?.to_vec1::<f32>()?;
159
+ let mut idx = Vec::with_capacity(b);
160
+ let mut conf = Vec::with_capacity(b);
161
+ for bi in 0..b {
162
+ let mut melhor = 0usize;
163
+ let mut melhor_v = f32::NEG_INFINITY;
164
+ for ki in 0..k {
165
+ let x = v[bi * k + ki];
166
+ if x > melhor_v {
167
+ melhor_v = x;
168
+ melhor = ki;
169
+ }
170
+ }
171
+ // softmax só sobre o melhor (confiança relativa simples)
172
+ let soma_exp: f32 = (0..k).map(|ki| (v[bi * k + ki] - melhor_v).exp()).sum();
173
+ idx.push(melhor);
174
+ conf.push(1.0 / soma_exp as f64);
175
+ }
176
+ Ok((idx, conf))
177
+ }
178
+
179
+ /// Telemetria local da unidade.
180
+ pub fn telemetria(&self) -> HashMap<String, f64> {
181
+ let mut m = HashMap::new();
182
+ m.insert("nlp_gate_medio".into(), self.gate_medio);
183
+ m.insert("nlp_n_intencoes".into(), INTENCOES.len() as f64);
184
+ m
185
+ }
186
+ }
187
+
188
+ #[cfg(test)]
189
+ mod testes {
190
+ use super::*;
191
+
192
+ /// N-1 (port do Teo 15.1): nascimento neutro — W_Δ = 0 ⇒ enriquecer(h)
193
+ /// = h bit-a-bit para qualquer h (a unidade acoplada não muda o tronco).
194
+ #[test]
195
+ fn n1_nascimento_neutro() -> Result<()> {
196
+ let dev = Device::Cpu;
197
+ let mut u = UnidadeNlp::nova(32, &dev)?;
198
+ let h = Tensor::randn(0f32, 1f32, (2, 7, 32), &dev)?;
199
+ let h2 = u.enriquecer(&h)?;
200
+ let dif = (&h - &h2)?.abs()?.max_all()?.to_scalar::<f32>()?;
201
+ assert_eq!(dif, 0.0, "N-1: Δ=0 no nascimento ⇒ enriquecer ≡ h (dif {dif})");
202
+ // gate médio no nascimento ≈ σ(−2) ≈ 0,12
203
+ let g = u.gate_medio;
204
+ assert!(
205
+ (g - 0.119).abs() < 0.03,
206
+ "N-1: gate médio {g} deve ≈ σ(−2) = 0,119"
207
+ );
208
+ Ok(())
209
+ }
210
+
211
+ /// N-2: traços preservam a forma (B, T, d) e são finitos.
212
+ #[test]
213
+ fn n2_tracos_forma() -> Result<()> {
214
+ let dev = Device::Cpu;
215
+ let u = UnidadeNlp::nova(16, &dev)?;
216
+ let h = Tensor::randn(0f32, 1f32, (2, 5, 16), &dev)?;
217
+ let tr = u.tracos(&h)?;
218
+ assert_eq!(tr.dims(), vec![2, 5, 16]);
219
+ let mx = tr.abs()?.max_all()?.to_scalar::<f32>()?;
220
+ assert!(mx.is_finite(), "N-2: traços finitos");
221
+ Ok(())
222
+ }
223
+
224
+ /// N-3: injetividade da tabela canônica (pigeonhole — sem duplicatas).
225
+ #[test]
226
+ fn n3_intencoes_injetivas() {
227
+ let mut vistas: Vec<&str> = INTENCOES.to_vec();
228
+ vistas.sort();
229
+ let n = vistas.len();
230
+ vistas.dedup();
231
+ assert_eq!(vistas.len(), n, "N-3: intenções canônicas sem duplicatas");
232
+ assert_eq!(n, 13, "N-3: 12 tarefas + nlp-outro");
233
+ }
234
+
235
+ /// N-4: após perturbar W_Δ (treino simulado), o enriquecimento muda —
236
+ /// a porta EXISTE e aprende (não é uma constante morta).
237
+ #[test]
238
+ fn n4_gate_apos_treino_simulado() -> Result<()> {
239
+ let dev = Device::Cpu;
240
+ let mut u = UnidadeNlp::nova(32, &dev)?;
241
+ let h = Tensor::randn(0f32, 1f32, (1, 4, 32), &dev)?;
242
+ // treino simulado: W_Δ ← W_Δ + ruído
243
+ let ruido = Tensor::randn(0f32, 0.1f32, (32, 96), &dev)?;
244
+ let novo = u.w_delta.w.as_tensor().add(&ruido)?;
245
+ u.w_delta.w.set(&novo)?;
246
+ let h2 = u.enriquecer(&h)?;
247
+ let dif = (&h - &h2)?.abs()?.max_all()?.to_scalar::<f32>()?;
248
+ assert!(dif > 0.0, "N-4: com W_Δ ≠ 0 o enriquecimento deve agir (dif {dif})");
249
+ assert!(dif < 1.0, "N-4: porta g ≈ 0,12 limita a perturbação (dif {dif})");
250
+ Ok(())
251
+ }
252
+ }
src/pensamento.rs CHANGED
@@ -662,9 +662,238 @@ pub fn resolver_simetrica(m: &Tensor, b: &Tensor) -> Result<Tensor> {
662
  }
663
 
664
  pub fn versao() -> &'static str {
665
- "pensamento-0.5.0"
666
  }
667
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
668
  #[cfg(test)]
669
  mod testes {
670
  use super::*;
@@ -829,4 +1058,135 @@ mod testes {
829
  reps
830
  );
831
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
832
  }
 
662
  }
663
 
664
  pub fn versao() -> &'static str {
665
+ "pensamento-0.7.0"
666
  }
667
 
668
+ // ---------------------------------------------------------------------------
669
+ // QUEBRA-CABEÇA pós-treinamento (v0.7.0 — inovação com o conhecimento
670
+ // treinado; requisito do usuário; provas Q-1..Q-6 em r-31)
671
+ // ---------------------------------------------------------------------------
672
+
673
+ /// Configuração do quebra-cabeça (r-28 §4.3).
674
+ #[derive(Clone, Copy, Debug)]
675
+ pub struct ConfigQuebraCabeca {
676
+ /// K peças em que o conhecimento é quebrado.
677
+ pub n_pecas: usize,
678
+ /// orçamento B de rodadas (terminação Q-4).
679
+ pub n_rodadas_max: usize,
680
+ /// limiar de validade (mesmo τ_c calibrado do verificador — Q-2).
681
+ pub tau_c: f64,
682
+ /// teto do ajuste por componente na árvore (regulação).
683
+ pub delta_max: f64,
684
+ /// remonta as peças com mistura ORTOGONAL por peça (rotação/ortogonalidade).
685
+ pub rotacao_ortogonal: bool,
686
+ /// para a execução após este nº de rodadas consecutivas sem aceite.
687
+ pub paciencia: usize,
688
+ }
689
+
690
+ impl Default for ConfigQuebraCabeca {
691
+ fn default() -> Self {
692
+ ConfigQuebraCabeca {
693
+ n_pecas: 4,
694
+ n_rodadas_max: 12,
695
+ tau_c: 0.55,
696
+ delta_max: 0.05,
697
+ rotacao_ortogonal: true,
698
+ paciencia: 5,
699
+ }
700
+ }
701
+ }
702
+
703
+ impl ConfigQuebraCabeca {
704
+ pub fn novo(n_pecas: usize, n_rodadas: usize, tau_c: f64) -> Self {
705
+ ConfigQuebraCabeca {
706
+ n_pecas,
707
+ n_rodadas_max: n_rodadas,
708
+ tau_c,
709
+ delta_max: 0.05,
710
+ rotacao_ortogonal: true,
711
+ paciencia: (n_rodadas / 2).max(2),
712
+ }
713
+ }
714
+ }
715
+
716
+ /// Uma rodada do quebra-cabeça.
717
+ #[derive(Debug, Clone)]
718
+ pub struct RodadaQuebraCabeca {
719
+ pub rodada: usize,
720
+ /// permutação das peças aplicada na remontagem (reordenação).
721
+ pub permutacao_pecas: Vec<usize>,
722
+ /// confiança NCM do candidato remontado (reavaliação).
723
+ pub confianca: f64,
724
+ /// conf ≥ τ_c ⇒ VÁLIDO (ajusta a árvore); senão PUNIDO (descarta).
725
+ pub aceito: bool,
726
+ /// delta aplicado na árvore (0 quando descartado).
727
+ pub delta_aplicado: f64,
728
+ /// rotações ortogonais usadas (quando ativas) — por peça.
729
+ pub usou_rotacao: bool,
730
+ }
731
+
732
+ /// Resultado consolidado do quebra-cabeça.
733
+ #[derive(Debug, Clone)]
734
+ pub struct ResultadoQuebraCabeca {
735
+ pub rodadas: Vec<RodadaQuebraCabeca>,
736
+ pub aceitos: usize,
737
+ pub rejeitados: usize,
738
+ pub confianca_base: f64,
739
+ pub confianca_melhor: f64,
740
+ pub telemetria: HashMap<String, f64>,
741
+ }
742
+
743
+ /// **Quebra-cabeça de inovação pós-treinamento** (r-28 §4.3):
744
+ /// 1. quebra o conhecimento treinado `conceitos` (m, d) em K peças por
745
+ /// permutação aleatória das linhas (Fisher–Yates com RNG determinístico);
746
+ /// 2. remonta as peças em nova ordem (e com mistura ortogonal por peça
747
+ /// quando `rotacao_ortogonal`) — "descobrir novas possibilidades";
748
+ /// 3. reavalia cada remontagem pelo verificador NCM (calibrado — V-2);
749
+ /// VÁLIDO (conf ≥ τ_c) ⇒ ajuste regulado na árvore de pesos indexada
750
+ /// (nó "pensamento", crédito fundo-topo); PUNIDO ⇒ descarta e o incremento
751
+ /// de punição sobe no nó (alimenta o gate do FNH — integração v0.7.0);
752
+ /// 4. repete até o orçamento B ou a paciência esgotar (Q-4).
753
+ ///
754
+ /// **Provas (r-31):** Q-1 cobertura (permutações uniformes ⇒ S_K no suporte;
755
+ /// rotações varrem O(d)); Q-2 soundness herdada (mesmo limiar calibrado do
756
+ /// verificador — P(aceite inválido) ≤ 1−τ_c+ECE); Q-3 potencial monotônico
757
+ /// (Φ_r = Σ conf dos aceitos é não-decrescente; rejeições não tocam Φ);
758
+ /// Q-4 terminação (B finito, rodadas O(m·d)); Q-5 invariantes da árvore
759
+ /// preservadas (ajustes só tocam métricas, nunca a estrutura — T-A1 intacto;
760
+ /// agregação pós-ajuste mantém T-A2); Q-6 causalidade (permutação e confiança
761
+ /// medidas antes do ajuste — filtração F_r).
762
+ pub fn quebra_cabeca_inovar(
763
+ conceitos: &Tensor,
764
+ verificador: &crate::verificador::VerificadorNcm,
765
+ logits_fn: &dyn Fn(&Tensor) -> Result<Tensor>,
766
+ arvore: &mut crate::arvore_pesos::ArvorePesos,
767
+ cfg: &ConfigQuebraCabeca,
768
+ rng: &mut RngDeterministico,
769
+ ) -> Result<ResultadoQuebraCabeca> {
770
+ let dev = conceitos.device().clone();
771
+ let (m, d) = (conceitos.dims()[0], conceitos.dims()[1]);
772
+ let k = cfg.n_pecas.clamp(1, m);
773
+ let no_pens = arvore.no("pensamento").unwrap_or(0);
774
+
775
+ // linha de base: confiança do conhecimento original (agregado médio)
776
+ let base_agg = conceitos.mean(0)?;
777
+ let conf_base = {
778
+ let lg = logits_fn(&base_agg)?;
779
+ verificador.confianca_1d(&lg)?
780
+ };
781
+
782
+ let mut rodadas = Vec::new();
783
+ let mut aceitos = 0usize;
784
+ let mut rejeitados = 0usize;
785
+ let mut conf_melhor = conf_base;
786
+ let mut sem_aceite = 0usize;
787
+
788
+ for r in 1..=cfg.n_rodadas_max {
789
+ if sem_aceite >= cfg.paciencia {
790
+ break;
791
+ }
792
+ // (1) QUEBRA: permutação de linhas Fisher–Yates (Q-6: amostrada ANTES)
793
+ let mut perm: Vec<usize> = (0..m).collect();
794
+ for i in (1..m).rev() {
795
+ let j = rng.inteiro(i + 1);
796
+ perm.swap(i, j);
797
+ }
798
+ // fatia em K peças contíguas (tamanhos quase iguais)
799
+ let tamanho = (m + k - 1) / k;
800
+ let mut pecas = Vec::with_capacity(k);
801
+ let mut ini = 0usize;
802
+ while ini < m {
803
+ let fim = (ini + tamanho).min(m);
804
+ let idx: Vec<u32> = perm[ini..fim].iter().map(|&x| x as u32).collect();
805
+ let pedaco = conceitos.index_select(&Tensor::from_vec(idx, (fim - ini,), &dev)?, 0)?;
806
+ pecas.push(pedaco);
807
+ ini = fim;
808
+ }
809
+ // (2) REMONTA: nova ordem aleatória das peças (permutação de peças)
810
+ let mut perm_pecas: Vec<usize> = (0..pecas.len()).collect();
811
+ for i in (1..pecas.len()).rev() {
812
+ let j = rng.inteiro(i + 1);
813
+ perm_pecas.swap(i, j);
814
+ }
815
+ let mut remontada = Vec::with_capacity(pecas.len());
816
+ for &pi in &perm_pecas {
817
+ let mut pedaco = pecas[pi].clone();
818
+ if cfg.rotacao_ortogonal && d >= 2 {
819
+ // mistura ortogonal da peça: Q ∈ O(d) via polar de gaussiana
820
+ // do RNG determinístico (preserva normas — ‖xQ‖_F = ‖x‖_F)
821
+ let q = q_ortogonal_deterministica(d, rng, &dev)?;
822
+ pedaco = pedaco.matmul(&q)?;
823
+ }
824
+ remontada.push(pedaco);
825
+ }
826
+ let candidata = Tensor::cat(&remontada, 0)?; // (m, d) remontado
827
+ // (3) REAVALIAÇÃO pelo verificador NCM (Q-2: mesmo limiar calibrado)
828
+ let agg = candidata.mean(0)?;
829
+ let lg = logits_fn(&agg)?;
830
+ let conf = verificador.confianca_1d(&lg)?;
831
+ let aceito = conf >= cfg.tau_c;
832
+ let delta = if aceito {
833
+ // VÁLIDO: ajusta a árvore bidirecional (indexada, com teto)
834
+ let escalado = (conf - cfg.tau_c).min(cfg.delta_max);
835
+ arvore.aplicar_ajuste_indexado(no_pens, "confianca", escalado, cfg.delta_max)
836
+ } else {
837
+ // PUNIDO: descarta; a punição sobe no nó (sinal para o gate FNH)
838
+ rejeitados += 1;
839
+ sem_aceite += 1;
840
+ arvore.aplicar_ajuste_indexado(no_pens, "punicao", cfg.delta_max * 0.5, cfg.delta_max);
841
+ 0.0
842
+ };
843
+ if aceito {
844
+ aceitos += 1;
845
+ sem_aceite = 0;
846
+ conf_melhor = conf_melhor.max(conf);
847
+ }
848
+ rodadas.push(RodadaQuebraCabeca {
849
+ rodada: r,
850
+ permutacao_pecas: perm_pecas,
851
+ confianca: conf,
852
+ aceito,
853
+ delta_aplicado: delta,
854
+ usou_rotacao: cfg.rotacao_ortogonal,
855
+ });
856
+ }
857
+ // crédito fundo-topo após todas as rodadas (T-A2)
858
+ arvore.agregar_fundo_topo("confianca");
859
+ arvore.agregar_fundo_topo("punicao");
860
+
861
+ let n_rod = rodadas.len();
862
+ let mut telemetria = HashMap::new();
863
+ telemetria.insert("rodadas".into(), n_rod as f64);
864
+ telemetria.insert("aceitos".into(), aceitos as f64);
865
+ telemetria.insert("rejeitados".into(), rejeitados as f64);
866
+ telemetria.insert("confianca_base".into(), conf_base);
867
+ telemetria.insert("confianca_melhor".into(), conf_melhor);
868
+ telemetria.insert(
869
+ "taxa_aceitacao".into(),
870
+ if n_rod > 0 { aceitos as f64 / n_rod as f64 } else { 0.0 },
871
+ );
872
+ telemetria.insert("rotacao_ativa".into(), if cfg.rotacao_ortogonal { 1.0 } else { 0.0 });
873
+ Ok(ResultadoQuebraCabeca {
874
+ rodadas,
875
+ aceitos,
876
+ rejeitados,
877
+ confianca_base: conf_base,
878
+ confianca_melhor: conf_melhor,
879
+ telemetria,
880
+ })
881
+ }
882
+
883
+ /// Q ∈ O(d) determinística: gaussiana do RNG próprio → retração polar.
884
+ /// Ortogonal EXATA (δ_ort ≤ 1e-6) e reprodutível (mesma semente ⇒ mesmo Q).
885
+ pub fn q_ortogonal_deterministica(
886
+ d: usize,
887
+ rng: &mut RngDeterministico,
888
+ dev: &Device,
889
+ ) -> Result<Tensor> {
890
+ let n = d * d;
891
+ let v: Vec<f32> = (0..n).map(|_| rng.normal_padrao() as f32).collect();
892
+ let g = Tensor::from_vec(v, (d, d), dev)?;
893
+ crate::stiefel::retrair_polar_frobenius(&g, 90)
894
+ }
895
+
896
+
897
  #[cfg(test)]
898
  mod testes {
899
  use super::*;
 
1058
  reps
1059
  );
1060
  }
1061
+
1062
+ // ------------------------------------------------------------------
1063
+ // Quebra-cabeça (v0.7.0) — gates Q-1..Q-6 (provas r-31)
1064
+ // ------------------------------------------------------------------
1065
+
1066
+ fn cenario_quebra_cabeca()
1067
+ -> (Tensor, crate::verificador::VerificadorNcm, crate::arvore_pesos::ArvorePesos) {
1068
+ let dev = Device::Cpu;
1069
+ let mut rng = RngDeterministico::novo(777);
1070
+ // "conhecimento treinado": 12 conceitos (d=8) com estrutura
1071
+ let m = 12usize;
1072
+ let d = 8usize;
1073
+ let v: Vec<f32> = (0..m * d).map(|i| rng.normal_padrao() as f32).collect();
1074
+ let conceitos = Tensor::from_vec(v, (m, d), &dev).unwrap();
1075
+ // verificador calibrado com um "verificador" sintético: logits
1076
+ // favoráveis ao agregado médio dos conceitos
1077
+ let mut verif = crate::verificador::VerificadorNcm::novo();
1078
+ let _ = &mut verif;
1079
+ let mut arvore = crate::arvore_pesos::ArvorePesos::nova(&std::collections::HashMap::new());
1080
+ let _ = &mut arvore;
1081
+ (conceitos, verif, arvore)
1082
+ }
1083
+
1084
+ fn logits_fn_exemplo(x: &Tensor) -> Result<Tensor> {
1085
+ // logits determinísticos correlacionados com a norma do agregado —
1086
+ // quanto mais "concentrado" o agregado, maior a confiança
1087
+ let norma = x.sqr()?.sum_all()?.to_scalar::<f32>()? as f64;
1088
+ let base = (norma * 2.0).min(8.0);
1089
+ let v: Vec<f32> = vec![base as f32, 0.5, 0.2, 0.1];
1090
+ Ok(Tensor::from_vec(v, (4,), x.device())?)
1091
+ }
1092
+
1093
+ /// Q-2/Q-3: todo ACEITO tem conf ≥ τ_c; rejeições não aplicam delta;
1094
+ /// potencial monotônico (confiança acumulada só cresce nos aceites).
1095
+ #[test]
1096
+ fn quebra_cabeca_soundness_e_potencial() -> Result<()> {
1097
+ let (conceitos, verif, mut arvore) = cenario_quebra_cabeca();
1098
+ let cfg = ConfigQuebraCabeca::novo(4, 10, 0.55);
1099
+ let mut rng = RngDeterministico::novo(4242);
1100
+ let r = quebra_cabeca_inovar(&conceitos, &verif, &logits_fn_exemplo, &mut arvore, &cfg, &mut rng)?;
1101
+ for rod in &r.rodadas {
1102
+ if rod.aceito {
1103
+ assert!(
1104
+ rod.confianca >= cfg.tau_c,
1105
+ "Q-2: aceito com conf {} < τ_c {}",
1106
+ rod.confianca,
1107
+ cfg.tau_c
1108
+ );
1109
+ assert!(rod.delta_aplicado > 0.0, "aceite aplica delta > 0");
1110
+ } else {
1111
+ assert_eq!(rod.delta_aplicado, 0.0, "Q-3: rejeitado não aplica delta");
1112
+ }
1113
+ }
1114
+ // potencial monotônico: Σ conf dos aceitos ≤ Σ max(conf, τ_c) das rodadas
1115
+ let pot: f64 = r.rodadas.iter().filter(|x| x.aceito).map(|x| x.confianca).sum();
1116
+ assert!(pot >= 0.0);
1117
+ // Q-5: invariantes da árvore preservadas
1118
+ arvore.invariantes_ok().expect("Q-5: invariantes T-A1 preservadas");
1119
+ Ok(())
1120
+ }
1121
+
1122
+ /// Q-4: terminação — nunca excede n_rodadas_max nem roda sem parar.
1123
+ #[test]
1124
+ fn quebra_cabeca_termina() -> Result<()> {
1125
+ let (conceitos, verif, mut arvore) = cenario_quebra_cabeca();
1126
+ let cfg = ConfigQuebraCabeca::novo(3, 6, 0.99); // τ inalcançável
1127
+ let mut rng = RngDeterministico::novo(1);
1128
+ let r = quebra_cabeca_inovar(&conceitos, &verif, &logits_fn_exemplo, &mut arvore, &cfg, &mut rng)?;
1129
+ assert!(r.rodadas.len() <= 6, "Q-4: orçamento respeitado");
1130
+ assert_eq!(r.aceitos, 0, "τ inalcançável ⇒ nenhum aceite");
1131
+ assert!(r.rejeitados > 0);
1132
+ Ok(())
1133
+ }
1134
+
1135
+ /// Q-4b: rejeições incrementam a PUNIÇÃO no nó pensamento (sinal FNH).
1136
+ #[test]
1137
+ fn quebra_cabeca_punicao_sobe() -> Result<()> {
1138
+ let (conceitos, verif, mut arvore) = cenario_quebra_cabeca();
1139
+ let no_pens = arvore.no("pensamento").unwrap();
1140
+ let antes = arvore.nos[no_pens].metricas.get("punicao").copied().unwrap_or(0.0);
1141
+ let cfg = ConfigQuebraCabeca::novo(3, 5, 0.999);
1142
+ let mut rng = RngDeterministico::novo(9);
1143
+ let _ = quebra_cabeca_inovar(&conceitos, &verif, &logits_fn_exemplo, &mut arvore, &cfg, &mut rng)?;
1144
+ let depois = arvore.nos[no_pens].metricas.get("punicao").copied().unwrap_or(0.0);
1145
+ assert!(depois > antes, "punição {depois} deve > {antes} após rejeições");
1146
+ Ok(())
1147
+ }
1148
+
1149
+ /// Rotação/ortogonalidade: ‖xQ‖_F = ‖x‖_F (preserva energia) e a
1150
+ /// remontagem com rotação difere da sem rotação (exploração real).
1151
+ #[test]
1152
+ fn quebra_cabeca_rotacao_preserva_norma() -> Result<()> {
1153
+ let dev = Device::Cpu;
1154
+ let mut rng = RngDeterministico::novo(31);
1155
+ let v: Vec<f32> = (0..5 * 6).map(|_| rng.normal_padrao() as f32).collect();
1156
+ let x = Tensor::from_vec(v, (5, 6), &dev)?;
1157
+ let q = q_ortogonal_deterministica(6, &mut rng, &dev)?;
1158
+ let xq = x.matmul(&q)?;
1159
+ let nx = x.sqr()?.sum_all()?.to_scalar::<f32>()?;
1160
+ let nxq = xq.sqr()?.sum_all()?.to_scalar::<f32>()?;
1161
+ assert!(
1162
+ (nx - nxq).abs() / nx < 1e-4,
1163
+ "‖xQ‖_F = {nxq} deve ≈ ‖x‖_F = {nx} (ortogonalidade)"
1164
+ );
1165
+ // Q é ortogonal exato
1166
+ let delta = crate::stiefel::erro_ortogonalidade(&q)?;
1167
+ assert!(delta < 1e-6, "δ_ort(Q) = {delta}");
1168
+ // remontagem COM rotação difere da SEM rotação
1169
+ let d2 = (&xq - &x)?.abs()?.max_all()?.to_scalar::<f32>()?;
1170
+ assert!(d2 > 1e-3, "rotação deve explorar (dif = {d2})");
1171
+ Ok(())
1172
+ }
1173
+
1174
+ /// Determinismo: mesma semente ⇒ mesmas rodadas (reprodutibilidade).
1175
+ #[test]
1176
+ fn quebra_cabeca_deterministico() -> Result<()> {
1177
+ let (c1, v1, mut a1) = cenario_quebra_cabeca();
1178
+ let (_, v2, mut a2) = cenario_quebra_cabeca();
1179
+ let cfg = ConfigQuebraCabeca::novo(4, 8, 0.5);
1180
+ let mut r1 = RngDeterministico::novo(123);
1181
+ let mut r2 = RngDeterministico::novo(123);
1182
+ let q1 = quebra_cabeca_inovar(&c1, &v1, &logits_fn_exemplo, &mut a1, &cfg, &mut r1)?;
1183
+ let q2 = quebra_cabeca_inovar(&c1, &v2, &logits_fn_exemplo, &mut a2, &cfg, &mut r2)?;
1184
+ assert_eq!(q1.rodadas.len(), q2.rodadas.len());
1185
+ for (x, y) in q1.rodadas.iter().zip(q2.rodadas.iter()) {
1186
+ assert_eq!(x.permutacao_pecas, y.permutacao_pecas, "mesma semente ⇒ mesma remontagem");
1187
+ assert!((x.confianca - y.confianca).abs() < 1e-12);
1188
+ assert_eq!(x.aceito, y.aceito);
1189
+ }
1190
+ Ok(())
1191
+ }
1192
  }
src/stiefel.rs CHANGED
@@ -65,6 +65,23 @@ pub fn retrair_polar(x: &Tensor, iters: usize) -> Result<Tensor> {
65
  Ok(y)
66
  }
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  /// δ(X) = ‖XᵀX − I‖_F (eq. 26.7).
69
  pub fn erro_ortogonalidade(x: &Tensor) -> Result<f64> {
70
  let n = x.dim(1)?;
 
65
  Ok(y)
66
  }
67
 
68
+ /// Retração polar DETERMINÍSTICA (v0.7.0): escala pela norma de FROBENIUS
69
+ /// (σ_max ≤ ‖X‖_F sempre — sem iteração de potência aleatória) ⇒ σ_max(Y₀)
70
+ /// ≤ 1 < √3 ⇒ Newton–Schulz converge globalmente; a escala mais
71
+ /// conservadora pode exigir mais iterações (use 60–100).
72
+ /// Motivação: a versão aleatória deixa resíduo ~1e-8 em ~3% dos casos
73
+ /// quando a entrada é EXATAMENTE a identidade (quebra a reprodutibilidade
74
+ /// bit-a-bit dos gates de nascimento neutro — r-28 §1, v070-1).
75
+ pub fn retrair_polar_frobenius(x: &Tensor, iters: usize) -> Result<Tensor> {
76
+ let fro = x.sqr()?.sum_all()?.to_scalar::<f32>()? as f64;
77
+ let escala = fro.sqrt().max(1e-12);
78
+ let mut y = x.affine(1.0 / escala, 0.0)?;
79
+ for _ in 0..iters {
80
+ y = passo_ns(&y)?;
81
+ }
82
+ Ok(y)
83
+ }
84
+
85
  /// δ(X) = ‖XᵀX − I‖_F (eq. 26.7).
86
  pub fn erro_ortogonalidade(x: &Tensor) -> Result<f64> {
87
  let n = x.dim(1)?;
src/telemetria.rs CHANGED
@@ -384,4 +384,67 @@ impl TelemetryHub {
384
  "erros_registrados": self.eventos_erro.len(),
385
  })
386
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387
  }
 
384
  "erros_registrados": self.eventos_erro.len(),
385
  })
386
  }
387
+
388
+ /// Painel v0.7.0 — herda o v0.6.0 (polaridade-aware) e adiciona o
389
+ /// bloco de sinais dos módulos novos: FNH (gate/punição/δ_ort), árvore
390
+ /// de pesos (crédito/regulação por nó), quebra-cabeça (aceitação/
391
+ /// confiança) e NLP/NLG (gates/coerência). Métricas ausentes são
392
+ /// simplesmente omitidas (painel ADITIVO — sem acoplamento rígido).
393
+ pub fn painel_v070(&self, arvore: Option<&crate::arvore_pesos::ArvorePesos>) -> serde_json::Value {
394
+ let mut p = self.painel_v060();
395
+ // promove a versão sem perder o conteúdo
396
+ p["versao"] = serde_json::json!("0.7.0");
397
+ // sinais FNH (registrados no hub como fnh_*)
398
+ let fnh = serde_json::json!({
399
+ "gate_ultimo": self.ema("fnh_gate", 0.9),
400
+ "punicao_ema": self.ema("fnh_punicao", 0.9),
401
+ "delta_ort_rotacao": self.ema("fnh_delta_ort", 0.9),
402
+ "theta_norma": self.ema("fnh_theta_norma", 0.9),
403
+ });
404
+ p["fnh"] = fnh;
405
+ // quebra-cabeça
406
+ p["quebra_cabeca"] = serde_json::json!({
407
+ "taxa_aceitacao": self.ema("qc_taxa_aceitacao", 0.8),
408
+ "confianca_melhor": self.ema("qc_confianca_melhor", 0.8),
409
+ "confianca_base": self.ema("qc_confianca_base", 0.8),
410
+ "punicao_acumulada": self.ema("qc_punicao", 0.8),
411
+ });
412
+ // NLP/NLG
413
+ p["nlp_nlg"] = serde_json::json!({
414
+ "nlp_gate_medio": self.ema("nlp_gate_medio", 0.9),
415
+ "nlg_coerencia": self.ema("nlg_score_coerencia", 0.9),
416
+ "espec_aceitacao": self.ema("espec_aceitacao", 0.9),
417
+ "espec_speedup": self.ema("espec_speedup", 0.9),
418
+ });
419
+ // árvore de pesos: métricas agregadas por nó (fundo-topo)
420
+ if let Some(a) = arvore {
421
+ let nos: serde_json::Map<String, serde_json::Value> = a
422
+ .nos
423
+ .iter()
424
+ .map(|no| {
425
+ let m: serde_json::Map<String, serde_json::Value> = no
426
+ .metricas
427
+ .iter()
428
+ .map(|(k, v)| (k.clone(), serde_json::json!(v)))
429
+ .collect();
430
+ (
431
+ no.nome.clone(),
432
+ serde_json::json!({
433
+ "intervalo": [no.ini, no.fim],
434
+ "profundidade": no.profundidade,
435
+ "metricas": m,
436
+ }),
437
+ )
438
+ })
439
+ .collect();
440
+ p["arvore_pesos"] = serde_json::json!({
441
+ "n_nos": a.n_nos(),
442
+ "n_enlaces": a.enlaces.len(),
443
+ "n_params": a.n_params,
444
+ "nos": nos,
445
+ "invariantes_ok": a.invariantes_ok().is_ok(),
446
+ });
447
+ }
448
+ p
449
+ }
450
  }
src/treino.rs CHANGED
@@ -115,18 +115,23 @@ pub fn grads_achatados(perda: &Tensor, vars: &[Var]) -> Result<Vec<Option<Vec<f3
115
  }
116
 
117
  /// PCGrad multi-perda sobre grads achatados (Teo D.7 — projeção por pares).
118
- pub fn pcgrad_achatado(mut grads: Vec<Vec<Option<Vec<f32>>>>) -> Vec<Option<Vec<f32>>> {
 
 
 
119
  let n_perdas = grads.len();
120
  let n_vars = grads[0].len();
 
 
121
  let mut saida: Vec<Option<Vec<f32>>> = vec![None; n_vars];
122
  for i in 0..n_perdas {
123
- // projeta g_i contra cada g_j conflitante
124
  for j in 0..n_perdas {
125
  if i == j { continue; }
126
  let mut ip = 0f64;
127
  let mut nj = 0f64;
128
  for v in 0..n_vars {
129
- if let (Some(a), Some(b)) = (&grads[i][v], &grads[j][v]) {
130
  for k in 0..a.len().min(b.len()) {
131
  ip += a[k] as f64 * b[k] as f64;
132
  nj += b[k] as f64 * b[k] as f64;
@@ -136,7 +141,7 @@ pub fn pcgrad_achatado(mut grads: Vec<Vec<Option<Vec<f32>>>>) -> Vec<Option<Vec<
136
  if ip < 0.0 && nj > 1e-12 {
137
  let alfa = (ip / nj) as f32;
138
  for v in 0..n_vars {
139
- if let Some(bv) = grads[j][v].clone() {
140
  if let Some(a) = &mut grads[i][v] {
141
  for k in 0..a.len().min(bv.len()) {
142
  a[k] -= alfa * bv[k];
 
115
  }
116
 
117
  /// PCGrad multi-perda sobre grads achatados (Teo D.7 — projeção por pares).
118
+ /// v070-3 (auditoria): projeta contra os gradientes ORIGINAIS (snapshot) —
119
+ /// a v0.6.0 usava grads[j] já modificados quando j < i, violando a não-piora
120
+ /// pareada ⟨g_i^pc, g_i⟩ ≥ 0 (Yu et al. 2020, Algoritmo 1 usa os originais).
121
+ pub fn pcgrad_achatado(grads: Vec<Vec<Option<Vec<f32>>>>) -> Vec<Option<Vec<f32>>> {
122
  let n_perdas = grads.len();
123
  let n_vars = grads[0].len();
124
+ let originais = grads.clone(); // snapshot imutável dos originais
125
+ let mut grads = grads;
126
  let mut saida: Vec<Option<Vec<f32>>> = vec![None; n_vars];
127
  for i in 0..n_perdas {
128
+ // projeta g_i contra cada g_j CONFLITANTE ORIGINAL
129
  for j in 0..n_perdas {
130
  if i == j { continue; }
131
  let mut ip = 0f64;
132
  let mut nj = 0f64;
133
  for v in 0..n_vars {
134
+ if let (Some(a), Some(b)) = (&grads[i][v], &originais[j][v]) {
135
  for k in 0..a.len().min(b.len()) {
136
  ip += a[k] as f64 * b[k] as f64;
137
  nj += b[k] as f64 * b[k] as f64;
 
141
  if ip < 0.0 && nj > 1e-12 {
142
  let alfa = (ip / nj) as f32;
143
  for v in 0..n_vars {
144
+ if let Some(bv) = &originais[j][v] {
145
  if let Some(a) = &mut grads[i][v] {
146
  for k in 0..a.len().min(bv.len()) {
147
  a[k] -= alfa * bv[k];
src/tronco.rs CHANGED
@@ -23,6 +23,12 @@ pub struct Tronco {
23
  /// gamma FiLM por nível (2*L+2: [escala, deslocamento] × nível) — ADBA.
24
  pub film_gamma: Option<Var>,
25
  pub film_beta: Option<Var>,
 
 
 
 
 
 
26
  pub n_niveis: usize,
27
  }
28
 
@@ -43,12 +49,13 @@ impl Tronco {
43
  } else {
44
  None
45
  };
46
- let (film_gamma, film_beta) = if adba.ativo {
47
  let g = Tensor::ones((l + 1, m.d_modelo), candle_core::DType::F32, dev)?;
48
  let b = Tensor::zeros((l + 1, m.d_modelo), candle_core::DType::F32, dev)?;
49
- (Some(Var::from_tensor(&g)?), Some(Var::from_tensor(&b)?))
 
50
  } else {
51
- (None, None)
52
  };
53
  Ok(Tronco {
54
  blocos_atencao: blocos,
@@ -57,21 +64,25 @@ impl Tronco {
57
  ancora,
58
  film_gamma,
59
  film_beta,
 
60
  n_niveis: l,
61
  })
62
  }
63
 
64
  /// Injeção FiLM do resumo-âncora nas posições do nível `nivel`
65
  /// (ADBA (i): o resumo depende APENAS de c_{<ℓ} — paralelismo preservado).
 
 
66
  pub fn aplicar_film(&self, x: &Tensor, resumo: &Tensor, nivel: usize) -> Result<Tensor> {
67
- match (&self.film_gamma, &self.film_beta) {
68
- (Some(g), Some(b)) => {
69
  let d = x.dims()[2];
70
  let bsz = resumo.dims()[0].max(1);
71
  let gv = g.as_tensor().get(nivel)?.reshape((1, 1, d))?;
72
  let bv = b.as_tensor().get(nivel)?.reshape((1, 1, d))?;
73
- let r = resumo.reshape((bsz, 1, d))?;
74
- // x ⊙ (1 + γ·tanh(r)) + β·r — neutro em γ=β=0 (nascimento neutro)
 
75
  let gate = gv.broadcast_mul(&r.tanh()?)?.affine(1.0, 1.0)?; // 1 + γ·tanh(r)
76
  let xg = x.broadcast_mul(&gate)?;
77
  let desloc = bv.broadcast_mul(&r)?;
@@ -132,6 +143,11 @@ impl Tronco {
132
 
133
  /// Forward incremental (inferência): bloco novo (b, n_novo, d) + caches.
134
  /// `niveis_novos`/`niveis_cache` mapeiam posições para níveis.
 
 
 
 
 
135
  pub fn forward_nivel(
136
  &self,
137
  bloco_x: &Tensor,
@@ -139,6 +155,8 @@ impl Tronco {
139
  mascara_nova: &Tensor,
140
  niveis_novos: &[usize],
141
  niveis_totais: &[usize],
 
 
142
  ) -> Result<Tensor> {
143
  let mut x = bloco_x.clone();
144
  for (i, bloco) in self.blocos_atencao.iter().enumerate() {
@@ -146,7 +164,14 @@ impl Tronco {
146
  caches.push(CacheKv::default());
147
  }
148
  x = bloco.fwd(&x, Some(mascara_nova), &mut caches[i], Some(niveis_novos), Some(niveis_totais))?;
149
- let _ = i;
 
 
 
 
 
 
 
150
  }
151
  let mut cache_ssd = None;
152
  x = self.bloco_ssd.fwd(&x, &mut cache_ssd)?;
@@ -177,6 +202,9 @@ impl Tronco {
177
  if let Some(b) = &self.film_beta {
178
  v.push(b.clone());
179
  }
 
 
 
180
  v
181
  }
182
  }
 
23
  /// gamma FiLM por nível (2*L+2: [escala, deslocamento] × nível) — ADBA.
24
  pub film_gamma: Option<Var>,
25
  pub film_beta: Option<Var>,
26
+ /// W_f do FiLM (r-04 §1: x̃ = x⊙(1+γ·tanh(W_f·h)) + β·(W_f·h)) — ZERO no
27
+ /// nascimento: tanh(0)=0 e β·0=0 ⇒ identidade bit-a-bit (Teo R.2).
28
+ /// v070-1 (auditoria): a v0.6.0 injetava o resumo do âncora DIRETO (projeção
29
+ /// aleatória) — nascimento não-neutro nos níveis ≥ 2; correção restaura o
30
+ /// enunciado R.2 independentemente de γ=1.
31
+ pub film_w: Option<crate::camadas::Linear>,
32
  pub n_niveis: usize,
33
  }
34
 
 
49
  } else {
50
  None
51
  };
52
+ let (film_gamma, film_beta, film_w) = if adba.ativo {
53
  let g = Tensor::ones((l + 1, m.d_modelo), candle_core::DType::F32, dev)?;
54
  let b = Tensor::zeros((l + 1, m.d_modelo), candle_core::DType::F32, dev)?;
55
+ let w = crate::camadas::Linear::zeros_sem_vies(m.d_modelo, m.d_modelo, dev)?;
56
+ (Some(Var::from_tensor(&g)?), Some(Var::from_tensor(&b)?), Some(w))
57
  } else {
58
+ (None, None, None)
59
  };
60
  Ok(Tronco {
61
  blocos_atencao: blocos,
 
64
  ancora,
65
  film_gamma,
66
  film_beta,
67
+ film_w,
68
  n_niveis: l,
69
  })
70
  }
71
 
72
  /// Injeção FiLM do resumo-âncora nas posições do nível `nivel`
73
  /// (ADBA (i): o resumo depende APENAS de c_{<ℓ} — paralelismo preservado).
74
+ /// r-04 §1: x̃ = x⊙(1+γ·tanh(W_f·h)) + β·(W_f·h) com W_f=0 no nascimento
75
+ /// ⇒ identidade bit-a-bit para QUALQUER resumo (Teo R.2, v070-1).
76
  pub fn aplicar_film(&self, x: &Tensor, resumo: &Tensor, nivel: usize) -> Result<Tensor> {
77
+ match (&self.film_gamma, &self.film_beta, &self.film_w) {
78
+ (Some(g), Some(b), Some(wf)) => {
79
  let d = x.dims()[2];
80
  let bsz = resumo.dims()[0].max(1);
81
  let gv = g.as_tensor().get(nivel)?.reshape((1, 1, d))?;
82
  let bv = b.as_tensor().get(nivel)?.reshape((1, 1, d))?;
83
+ // r = W_f·resumo — 0 no nascimento (identidade garantida)
84
+ let r = wf.fwd(resumo)?.reshape((bsz, 1, d))?;
85
+ // x ⊙ (1 + γ·tanh(r)) + β·r
86
  let gate = gv.broadcast_mul(&r.tanh()?)?.affine(1.0, 1.0)?; // 1 + γ·tanh(r)
87
  let xg = x.broadcast_mul(&gate)?;
88
  let desloc = bv.broadcast_mul(&r)?;
 
143
 
144
  /// Forward incremental (inferência): bloco novo (b, n_novo, d) + caches.
145
  /// `niveis_novos`/`niveis_cache` mapeiam posições para níveis.
146
+ /// `film` = Some((resumo, nivel 1-indexado)) aplica o FiLM nas posições novas
147
+ /// após o bloco 0 — PARIDADE com forward_treino (v070-2: a v0.6.0 não
148
+ /// aplicava FiLM na inferência incremental — treino↔geração divergiam).
149
+ /// `saida_b0` = Some(&mut Tensor) recebe os estados pós-bloco-0 das
150
+ /// posições novas (para o âncora acumular contexto nos níveis seguintes).
151
  pub fn forward_nivel(
152
  &self,
153
  bloco_x: &Tensor,
 
155
  mascara_nova: &Tensor,
156
  niveis_novos: &[usize],
157
  niveis_totais: &[usize],
158
+ film: Option<(&Tensor, usize)>,
159
+ mut saida_b0: Option<&mut Tensor>,
160
  ) -> Result<Tensor> {
161
  let mut x = bloco_x.clone();
162
  for (i, bloco) in self.blocos_atencao.iter().enumerate() {
 
164
  caches.push(CacheKv::default());
165
  }
166
  x = bloco.fwd(&x, Some(mascara_nova), &mut caches[i], Some(niveis_novos), Some(niveis_totais))?;
167
+ if i == 0 {
168
+ if let Some(slot) = saida_b0.as_deref_mut() {
169
+ *slot = x.clone();
170
+ }
171
+ if let Some((resumo, nivel)) = film {
172
+ x = self.aplicar_film(&x, resumo, nivel)?;
173
+ }
174
+ }
175
  }
176
  let mut cache_ssd = None;
177
  x = self.bloco_ssd.fwd(&x, &mut cache_ssd)?;
 
202
  if let Some(b) = &self.film_beta {
203
  v.push(b.clone());
204
  }
205
+ if let Some(wf) = &self.film_w {
206
+ v.push(wf.w.clone());
207
+ }
208
  v
209
  }
210
  }
target/.rustc_info.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"rustc_fingerprint":1392034485640314244,"outputs":{"3011779423962739885":{"success":true,"status":"","code":0,"stdout":"rustc 1.99.1 (58cf8f9c3 2026-10-13)\nbinary: rustc\ncommit-hash: 58cf8f9c35b355199727627d07ae34a4e2846d67\ncommit-date: 2026-10-13\nhost: x86_64-unknown-linux-gnu\nrelease: 1.99.1\nLLVM version: 23.1.1\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/z/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_primitive_alignment=\"16\"\ntarget_has_atomic_primitive_alignment=\"32\"\ntarget_has_atomic_primitive_alignment=\"64\"\ntarget_has_atomic_primitive_alignment=\"8\"\ntarget_has_atomic_primitive_alignment=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}}
target/release/.cargo-artifact-lock ADDED
File without changes
target/release/.cargo-build-lock ADDED
File without changes
target/release/.cargo-lock ADDED
File without changes
target/release/.fingerprint/ahash-1239384bf8e0b488/dep-lib-ahash ADDED
Binary file (14 Bytes). View file
 
target/release/.fingerprint/ahash-1239384bf8e0b488/invoked.timestamp ADDED
@@ -0,0 +1 @@
 
 
1
+ This file has an mtime of when this was started.
target/release/.fingerprint/ahash-1239384bf8e0b488/lib-ahash ADDED
@@ -0,0 +1 @@
 
 
1
+ f4811df8a9b62522
target/release/.fingerprint/ahash-1239384bf8e0b488/lib-ahash.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"rustc":11835527281324007204,"features":"[\"default\", \"getrandom\", \"runtime-rng\", \"serde\", \"std\"]","declared_features":"[\"atomic-polyfill\", \"compile-time-rng\", \"const-random\", \"default\", \"getrandom\", \"nightly-arm-aes\", \"no-rng\", \"runtime-rng\", \"serde\", \"std\"]","target":8470944000320059508,"profile":854319013279132213,"path":2545223575150465703,"deps":[[966925859616469517,"build_script_build",false,17893473302195841958],[4321869508056025743,"zerocopy",false,12413866475766502215],[5855319743879205494,"once_cell",false,3056825777376379244],[6557439603276904804,"serde",false,291644942871934544],[15482175856213997617,"cfg_if",false,15689703071604503671],[18408407127522236545,"getrandom",false,15498450006170333911]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/ahash-1239384bf8e0b488/dep-lib-ahash","checksum":false}}],"rustflags":[],"config":9396254390672932401,"compile_kind":0}
target/release/.fingerprint/ahash-83fe43707d427afc/build-script-build-script-build ADDED
@@ -0,0 +1 @@
 
 
1
+ eb483a6126879ab6
target/release/.fingerprint/ahash-83fe43707d427afc/build-script-build-script-build.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"rustc":11835527281324007204,"features":"[\"default\", \"getrandom\", \"runtime-rng\", \"serde\", \"std\"]","declared_features":"[\"atomic-polyfill\", \"compile-time-rng\", \"const-random\", \"default\", \"getrandom\", \"nightly-arm-aes\", \"no-rng\", \"runtime-rng\", \"serde\", \"std\"]","target":17883862002600103897,"profile":17078157726716824978,"path":3694579789264125173,"deps":[[5398981501050481332,"version_check",false,4457338824097212302]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/ahash-83fe43707d427afc/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":9396254390672932401,"compile_kind":0}
target/release/.fingerprint/ahash-83fe43707d427afc/dep-build-script-build-script-build ADDED
Binary file (14 Bytes). View file